summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Lewis <mlewis@gitlab.com>2019-06-07 20:13:17 +0000
committerMike Lewis <mlewis@gitlab.com>2019-06-07 20:13:17 +0000
commit99df0218f82b851b017bd0eea1b8351dc89df6ed (patch)
treeb01f884fbd1418dd5465fc1741f1620061ae8c5c
parent3eea6906747d10bea501426febaf15d2c209e06a (diff)
parente07b2b277f79bc25cdce22ca2defba1ba80791aa (diff)
downloadgitlab-ce-99df0218f82b851b017bd0eea1b8351dc89df6ed.tar.gz
Merge branch 'master' into 'docs/fix-example-dot-net'
# Conflicts: # doc/user/project/clusters/serverless/index.md
-rw-r--r--.codeclimate.yml58
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc.yml3
-rw-r--r--.gitignore3
-rw-r--r--.gitlab-ci.yml1069
-rw-r--r--.gitlab/CODEOWNERS21
-rw-r--r--.gitlab/CODEOWNERS.disabled20
-rw-r--r--.gitlab/ci/cng.gitlab-ci.yml16
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml76
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml243
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml62
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml26
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml17
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml349
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml98
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml235
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml43
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml82
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml9
-rw-r--r--.gitlab/issue_templates/Bug.md6
-rw-r--r--.gitlab/issue_templates/Database Reviewer.md34
-rw-r--r--.gitlab/issue_templates/Documentation.md2
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md43
-rw-r--r--.gitlab/issue_templates/Feature proposal.md36
-rw-r--r--.gitlab/issue_templates/Refactoring.md41
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md5
-rw-r--r--.gitlab/issue_templates/Test plan.md2
-rw-r--r--.gitlab/merge_request_templates/Security Release.md8
-rw-r--r--.gitlab/route-map.yml4
-rw-r--r--.haml-lint.yml10
-rw-r--r--.haml-lint_todo.yml523
-rw-r--r--.pkgr.yml4
-rw-r--r--.prettierrc10
-rw-r--r--.rubocop.yml30
-rw-r--r--.rubocop_todo.yml516
-rw-r--r--.ruby-version2
-rw-r--r--.scss-lint.yml4
-rw-r--r--.stylelintrc137
-rw-r--r--.yamllint7
-rw-r--r--CHANGELOG.md1075
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--Dangerfile34
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION1
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile108
-rw-r--r--Gemfile.lock452
-rw-r--r--PROCESS.md4
-rw-r--r--Pipfile12
-rw-r--r--Pipfile.lock30
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/favicon-yellow.pngbin1667 -> 1481 bytes
-rw-r--r--app/assets/images/select2-spinner.gifbin0 -> 1849 bytes
-rw-r--r--app/assets/images/select2.pngbin0 -> 613 bytes
-rw-r--r--app/assets/images/select2x2.pngbin0 -> 845 bytes
-rw-r--r--app/assets/javascripts/api.js26
-rw-r--r--app/assets/javascripts/avatar_picker.js16
-rw-r--r--app/assets/javascripts/awards_handler.js6
-rw-r--r--app/assets/javascripts/batch_comments/mixins/resolved_status.js15
-rw-r--r--app/assets/javascripts/behaviors/copy_to_clipboard.js5
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js10
-rw-r--r--app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js3
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js19
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js4
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js5
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js4
-rw-r--r--app/assets/javascripts/blob/balsamiq_viewer.js3
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js5
-rw-r--r--app/assets/javascripts/blob/sketch/index.js5
-rw-r--r--app/assets/javascripts/blob/template_selectors/dockerfile_selector.js3
-rw-r--r--app/assets/javascripts/blob/viewer/index.js14
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js2
-rw-r--r--app/assets/javascripts/boards/boards_util.js7
-rw-r--r--app/assets/javascripts/boards/components/board.js5
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue13
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js3
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue13
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js33
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue91
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner_scoped_label.vue45
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue8
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue4
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue6
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue15
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue4
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js9
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue2
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js4
-rw-r--r--app/assets/javascripts/boards/index.js64
-rw-r--r--app/assets/javascripts/boards/mixins/issue_card_inner.js5
-rw-r--r--app/assets/javascripts/boards/models/assignee.js (renamed from app/assets/javascripts/vue_shared/models/assignee.js)0
-rw-r--r--app/assets/javascripts/boards/models/issue.js30
-rw-r--r--app/assets/javascripts/boards/models/label.js11
-rw-r--r--app/assets/javascripts/boards/models/list.js28
-rw-r--r--app/assets/javascripts/boards/models/milestone.js11
-rw-r--r--app/assets/javascripts/boards/services/board_service.js3
-rw-r--r--app/assets/javascripts/boards/stores/actions.js65
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js62
-rw-r--r--app/assets/javascripts/boards/stores/boards_store_ee.js5
-rw-r--r--app/assets/javascripts/boards/stores/index.js14
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js21
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js91
-rw-r--r--app/assets/javascripts/boards/stores/state.js3
-rw-r--r--app/assets/javascripts/branches/branches_delete_modal.js2
-rw-r--r--app/assets/javascripts/breakpoints.js3
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js11
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js40
-rw-r--r--app/assets/javascripts/ci_variable_list/native_form_variable_list.js1
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js141
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue309
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue276
-rw-r--r--app/assets/javascripts/clusters/components/knative_domain_editor.vue150
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_button.vue33
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue74
-rw-r--r--app/assets/javascripts/clusters/constants.js28
-rw-r--r--app/assets/javascripts/clusters/mixins/track_uninstall_button_click.js5
-rw-r--r--app/assets/javascripts/clusters/services/application_state_machine.js174
-rw-r--r--app/assets/javascripts/clusters/services/clusters_service.js11
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js123
-rw-r--r--app/assets/javascripts/commit/image_file.js44
-rw-r--r--app/assets/javascripts/commons/bootstrap.js60
-rw-r--r--app/assets/javascripts/commons/jquery.js4
-rw-r--r--app/assets/javascripts/commons/polyfills.js31
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/contextual_sidebar.js58
-rw-r--r--app/assets/javascripts/create_item_dropdown.js2
-rw-r--r--app/assets/javascripts/create_label.js9
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js6
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js3
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js9
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js3
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js5
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js12
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js5
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js4
-rw-r--r--app/assets/javascripts/diffs/components/app.vue74
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue5
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue22
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue39
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue46
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue157
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue6
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue31
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue24
-rw-r--r--app/assets/javascripts/diffs/components/edit_button.vue27
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue9
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue2
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue15
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue9
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue4
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue4
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue52
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue59
-rw-r--r--app/assets/javascripts/diffs/constants.js15
-rw-r--r--app/assets/javascripts/diffs/index.js2
-rw-r--r--app/assets/javascripts/diffs/mixins/draft_comments.js10
-rw-r--r--app/assets/javascripts/diffs/mixins/image_diff.js13
-rw-r--r--app/assets/javascripts/diffs/store/actions.js164
-rw-r--r--app/assets/javascripts/diffs/store/getters.js7
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js8
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js10
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js58
-rw-r--r--app/assets/javascripts/diffs/store/utils.js47
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js5
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js13
-rw-r--r--app/assets/javascripts/dropzone_input.js15
-rw-r--r--app/assets/javascripts/due_date_select.js4
-rw-r--r--app/assets/javascripts/emoji/no_emoji_validator.js44
-rw-r--r--app/assets/javascripts/environments/components/confirm_rollback_modal.vue108
-rw-r--r--app/assets/javascripts/environments/components/container.vue20
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue57
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue42
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue2
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue17
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue79
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js3
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue11
-rw-r--r--app/assets/javascripts/environments/index.js3
-rw-r--r--app/assets/javascripts/environments/mixins/canary_callout_mixin.js5
-rw-r--r--app/assets/javascripts/environments/mixins/container_mixin.js29
-rw-r--r--app/assets/javascripts/environments/mixins/environment_item_mixin.js13
-rw-r--r--app/assets/javascripts/environments/mixins/environments_app_mixin.js32
-rw-r--r--app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js29
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js40
-rw-r--r--app/assets/javascripts/environments/mixins/environments_table_mixin.js10
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js25
-rw-r--r--app/assets/javascripts/environments/stores/helpers.js8
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue26
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js17
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/app.vue129
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue91
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue82
-rw-r--r--app/assets/javascripts/error_tracking_settings/index.js27
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/actions.js91
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/getters.js44
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/index.js16
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/mutation_types.js11
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/mutations.js61
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/state.js12
-rw-r--r--app/assets/javascripts/error_tracking_settings/utils.js18
-rw-r--r--app/assets/javascripts/event_tracking/notes.js1
-rw-r--r--app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js30
-rw-r--r--app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js8
-rw-r--r--app/assets/javascripts/filtered_search/available_dropdown_mappings.js164
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_ajax_filter.js68
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_emoji.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js91
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js108
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js30
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js21
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js153
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js21
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_storage_keys.js4
-rw-r--r--app/assets/javascripts/filtered_search/services/recent_searches_service_error.js4
-rw-r--r--app/assets/javascripts/filtered_search/visual_token_value.js117
-rw-r--r--app/assets/javascripts/fly_out_nav.js3
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue57
-rw-r--r--app/assets/javascripts/frequent_items/store/actions.js2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js16
-rw-r--r--app/assets/javascripts/gl_dropdown.js40
-rw-r--r--app/assets/javascripts/gl_field_error.js3
-rw-r--r--app/assets/javascripts/gl_field_errors.js2
-rw-r--r--app/assets/javascripts/gl_form.js6
-rw-r--r--app/assets/javascripts/gpg_badges.js2
-rw-r--r--app/assets/javascripts/group_avatar.js15
-rw-r--r--app/assets/javascripts/groups/components/app.vue2
-rw-r--r--app/assets/javascripts/groups/transfer_dropdown.js3
-rw-r--r--app/assets/javascripts/groups_select.js3
-rw-r--r--app/assets/javascripts/helpers/monitor_helper.js17
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue33
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue1
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue43
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue7
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue5
-rw-r--r--app/assets/javascripts/ide/components/ide.vue40
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue19
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue25
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue59
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue2
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue14
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue10
-rw-r--r--app/assets/javascripts/ide/constants.js7
-rw-r--r--app/assets/javascripts/ide/ide_router.js13
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js2
-rw-r--r--app/assets/javascripts/ide/lib/files.js119
-rw-r--r--app/assets/javascripts/ide/lib/keymap.json8
-rw-r--r--app/assets/javascripts/ide/services/index.js3
-rw-r--r--app/assets/javascripts/ide/stores/actions.js133
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js12
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js33
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js118
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js64
-rw-r--r--app/assets/javascripts/ide/stores/getters.js20
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js66
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/constants.js10
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js19
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutation_types.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutations.js14
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/state.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/actions.js25
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/getters.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/getters.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/mutations.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/state.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js22
-rw-r--r--app/assets/javascripts/ide/stores/mutations/branch.js6
-rw-r--r--app/assets/javascripts/ide/stores/mutations/merge_request.js3
-rw-r--r--app/assets/javascripts/ide/stores/mutations/project.js5
-rw-r--r--app/assets/javascripts/ide/stores/mutations/tree.js14
-rw-r--r--app/assets/javascripts/ide/stores/utils.js47
-rw-r--r--app/assets/javascripts/ide/stores/workers/files_decorator_worker.js100
-rw-r--r--app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js2
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js4
-rw-r--r--app/assets/javascripts/image_diff/view_types.js2
-rw-r--r--app/assets/javascripts/import_projects/components/import_projects_table.vue2
-rw-r--r--app/assets/javascripts/import_projects/components/provider_repo_table_row.vue2
-rw-r--r--app/assets/javascripts/import_projects/index.js3
-rw-r--r--app/assets/javascripts/import_projects/store/getters.js6
-rw-r--r--app/assets/javascripts/import_projects/store/index.js15
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js9
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js6
-rw-r--r--app/assets/javascripts/issuable_form.js2
-rw-r--r--app/assets/javascripts/issuable_index.js10
-rw-r--r--app/assets/javascripts/issue.js29
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue22
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue4
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue47
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue3
-rw-r--r--app/assets/javascripts/issue_show/index.js7
-rw-r--r--app/assets/javascripts/issue_show/utils/parse_data.js15
-rw-r--r--app/assets/javascripts/issue_status_select.js3
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue43
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue8
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue6
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue3
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue56
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue30
-rw-r--r--app/assets/javascripts/jobs/index.js1
-rw-r--r--app/assets/javascripts/jobs/store/actions.js26
-rw-r--r--app/assets/javascripts/jobs/store/getters.js14
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js19
-rw-r--r--app/assets/javascripts/jobs/store/state.js1
-rw-r--r--app/assets/javascripts/label_manager.js22
-rw-r--r--app/assets/javascripts/labels_select.js143
-rw-r--r--app/assets/javascripts/lib/graphql.js31
-rw-r--r--app/assets/javascripts/lib/utils/accessor.js2
-rw-r--r--app/assets/javascripts/lib/utils/autosave.js32
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js54
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js47
-rw-r--r--app/assets/javascripts/lib/utils/highlight.js44
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js1
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js34
-rw-r--r--app/assets/javascripts/lib/utils/simple_poll.js4
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js6
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js40
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js38
-rw-r--r--app/assets/javascripts/lib/utils/webpack.js10
-rw-r--r--app/assets/javascripts/locale/index.js2
-rw-r--r--app/assets/javascripts/main.js23
-rw-r--r--app/assets/javascripts/members.js27
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js15
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js3
-rw-r--r--app/assets/javascripts/merge_request_tabs.js7
-rw-r--r--app/assets/javascripts/milestone.js3
-rw-r--r--app/assets/javascripts/milestone_select.js15
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js3
-rw-r--r--app/assets/javascripts/mirrors/mirror_repos.js2
-rw-r--r--app/assets/javascripts/mirrors/ssh_mirror.js118
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue185
-rw-r--r--app/assets/javascripts/monitoring/components/charts/single_stat.vue37
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue258
-rw-r--r--app/assets/javascripts/monitoring/constants.js29
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js7
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js75
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js117
-rw-r--r--app/assets/javascripts/monitoring/stores/index.js21
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js75
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js12
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js45
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js12
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js83
-rw-r--r--app/assets/javascripts/monitoring/utils.js33
-rw-r--r--app/assets/javascripts/mr_notes/index.js69
-rw-r--r--app/assets/javascripts/mr_notes/init_notes.js70
-rw-r--r--app/assets/javascripts/mr_notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/mr_popover/components/mr_popover.vue110
-rw-r--r--app/assets/javascripts/mr_popover/constants.js12
-rw-r--r--app/assets/javascripts/mr_popover/index.js67
-rw-r--r--app/assets/javascripts/mr_popover/queries/merge_request.graphql14
-rw-r--r--app/assets/javascripts/namespace_select.js3
-rw-r--r--app/assets/javascripts/namespaces/leave_by_url.js22
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue29
-rw-r--r--app/assets/javascripts/notes.js83
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue18
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue10
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue58
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue14
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue28
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue52
-rw-r--r--app/assets/javascripts/notes/components/discussion_locked_widget.vue20
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue155
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue31
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue6
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue17
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue176
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue50
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue273
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue37
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue10
-rw-r--r--app/assets/javascripts/notes/constants.js7
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js13
-rw-r--r--app/assets/javascripts/notes/index.js14
-rw-r--r--app/assets/javascripts/notes/mixins/autosave.js3
-rw-r--r--app/assets/javascripts/notes/mixins/diff_line_note_form.js10
-rw-r--r--app/assets/javascripts/notes/mixins/draft.js8
-rw-r--r--app/assets/javascripts/notes/mixins/get_discussion.js7
-rw-r--r--app/assets/javascripts/notes/mixins/issuable_state.js15
-rw-r--r--app/assets/javascripts/notes/mixins/note_form.js24
-rw-r--r--app/assets/javascripts/notes/mixins/resolvable.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js54
-rw-r--r--app/assets/javascripts/notes/stores/getters.js5
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js4
-rw-r--r--app/assets/javascripts/notes/stores/utils.js14
-rw-r--r--app/assets/javascripts/notifications_dropdown.js4
-rw-r--r--app/assets/javascripts/operation_settings/components/external_dashboard.vue67
-rw-r--r--app/assets/javascripts/operation_settings/index.js23
-rw-r--r--app/assets/javascripts/operation_settings/store/actions.js38
-rw-r--r--app/assets/javascripts/operation_settings/store/index.js16
-rw-r--r--app/assets/javascripts/operation_settings/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/operation_settings/store/mutations.js7
-rw-r--r--app/assets/javascripts/operation_settings/store/state.js5
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js2
-rw-r--r--app/assets/javascripts/pages/admin/clusters/destroy/index.js5
-rw-r--r--app/assets/javascripts/pages/admin/clusters/edit/index.js5
-rw-r--r--app/assets/javascripts/pages/admin/clusters/index.js21
-rw-r--r--app/assets/javascripts/pages/admin/clusters/index/index.js6
-rw-r--r--app/assets/javascripts/pages/admin/clusters/show/index.js5
-rw-r--r--app/assets/javascripts/pages/admin/groups/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/admin/groups/new/index.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/clusters/index/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/details/index.js5
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/labels/new/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/new/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js31
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_tabs.js (renamed from app/assets/javascripts/pages/groups/show/group_tabs.js)0
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js29
-rw-r--r--app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue8
-rw-r--r--app/assets/javascripts/pages/profiles/keys/index.js2
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js21
-rw-r--r--app/assets/javascripts/pages/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js2
-rw-r--r--app/assets/javascripts/pages/projects/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/labels/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js2
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/form.js43
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js77
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js8
-rw-r--r--app/assets/javascripts/pages/projects/project.js12
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue5
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js10
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js10
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js8
-rw-r--r--app/assets/javascripts/pages/search/show/search.js7
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/length_validator.js32
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js63
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js1
-rw-r--r--app/assets/javascripts/pdf/index.vue6
-rw-r--r--app/assets/javascripts/performance_bar/components/detailed_metric.vue34
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue2
-rw-r--r--app/assets/javascripts/performance_bar/components/request_selector.vue7
-rw-r--r--app/assets/javascripts/persistent_user_callout.js8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_name_component.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue30
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue97
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_triggerer.vue35
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue28
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue53
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue12
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js16
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js6
-rw-r--r--app/assets/javascripts/pipelines/mixins/stage_column_mixin.js7
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js18
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediator.js16
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/pipeline_store.js1
-rw-r--r--app/assets/javascripts/profile/account/index.js2
-rw-r--r--app/assets/javascripts/profile/profile.js12
-rw-r--r--app/assets/javascripts/project_label_subscription.js4
-rw-r--r--app/assets/javascripts/project_select.js5
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js6
-rw-r--r--app/assets/javascripts/projects/project_new.js47
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js4
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js3
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js3
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js4
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_create.js3
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_edit.js7
-rw-r--r--app/assets/javascripts/raven/index.js7
-rw-r--r--app/assets/javascripts/raven/raven_config.js11
-rw-r--r--app/assets/javascripts/registry/stores/mutations.js4
-rw-r--r--app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue118
-rw-r--r--app/assets/javascripts/related_merge_requests/index.js24
-rw-r--r--app/assets/javascripts/related_merge_requests/store/actions.js37
-rw-r--r--app/assets/javascripts/related_merge_requests/store/index.js14
-rw-r--r--app/assets/javascripts/related_merge_requests/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/related_merge_requests/store/mutations.js19
-rw-r--r--app/assets/javascripts/related_merge_requests/store/state.js7
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue4
-rw-r--r--app/assets/javascripts/releases/store/actions.js2
-rw-r--r--app/assets/javascripts/reports/components/issue_status_icon.vue7
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue22
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue17
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue50
-rw-r--r--app/assets/javascripts/reports/constants.js6
-rw-r--r--app/assets/javascripts/reports/store/state.js5
-rw-r--r--app/assets/javascripts/reports/store/utils.js6
-rw-r--r--app/assets/javascripts/repository/components/app.vue3
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue61
-rw-r--r--app/assets/javascripts/repository/components/table/header.vue9
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue145
-rw-r--r--app/assets/javascripts/repository/components/table/parent_row.vue37
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue77
-rw-r--r--app/assets/javascripts/repository/fragmentTypes.json1
-rw-r--r--app/assets/javascripts/repository/graphql.js43
-rw-r--r--app/assets/javascripts/repository/index.js59
-rw-r--r--app/assets/javascripts/repository/mixins/get_ref.js14
-rw-r--r--app/assets/javascripts/repository/pages/index.vue18
-rw-r--r--app/assets/javascripts/repository/pages/tree.vue20
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.graphql57
-rw-r--r--app/assets/javascripts/repository/queries/getProjectPath.graphql3
-rw-r--r--app/assets/javascripts/repository/queries/getProjectShortPath.graphql3
-rw-r--r--app/assets/javascripts/repository/queries/getRef.graphql3
-rw-r--r--app/assets/javascripts/repository/router.js29
-rw-r--r--app/assets/javascripts/repository/utils/icon.js99
-rw-r--r--app/assets/javascripts/repository/utils/title.js9
-rw-r--r--app/assets/javascripts/right_sidebar.js11
-rw-r--r--app/assets/javascripts/search_autocomplete.js11
-rw-r--r--app/assets/javascripts/serverless/components/area.vue146
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue54
-rw-r--r--app/assets/javascripts/serverless/components/function_row.vue5
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue66
-rw-r--r--app/assets/javascripts/serverless/components/missing_prometheus.vue63
-rw-r--r--app/assets/javascripts/serverless/components/url.vue2
-rw-r--r--app/assets/javascripts/serverless/constants.js7
-rw-r--r--app/assets/javascripts/serverless/serverless_bundle.js126
-rw-r--r--app/assets/javascripts/serverless/services/get_functions_service.js11
-rw-r--r--app/assets/javascripts/serverless/store/actions.js128
-rw-r--r--app/assets/javascripts/serverless/store/getters.js10
-rw-r--r--app/assets/javascripts/serverless/store/index.js18
-rw-r--r--app/assets/javascripts/serverless/store/mutation_types.js11
-rw-r--r--app/assets/javascripts/serverless/store/mutations.js49
-rw-r--r--app/assets/javascripts/serverless/store/state.js14
-rw-r--r--app/assets/javascripts/serverless/stores/serverless_details_store.js11
-rw-r--r--app/assets/javascripts/serverless/stores/serverless_store.js29
-rw-r--r--app/assets/javascripts/serverless/utils.js23
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue12
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.vue39
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/todo.vue5
-rw-r--r--app/assets/javascripts/sidebar/lib/sidebar_move_issue.js7
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js3
-rw-r--r--app/assets/javascripts/snippet/snippet_embed.js6
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/javascripts/subscription_select.js3
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js3
-rw-r--r--app/assets/javascripts/terminal/terminal.js4
-rw-r--r--app/assets/javascripts/test_utils/index.js4
-rw-r--r--app/assets/javascripts/u2f/error.js12
-rw-r--r--app/assets/javascripts/usage_ping_consent.js3
-rw-r--r--app/assets/javascripts/users_select.js65
-rw-r--r--app/assets/javascripts/validators/input_validator.js34
-rw-r--r--app/assets/javascripts/visual_review_toolbar/index.js2
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css149
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue45
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue46
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue62
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue112
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue38
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue111
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/mr_widget_pipeline.js15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue74
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js20
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue72
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue89
-rw-r--r--app/assets/javascripts/vue_shared/components/empty_component.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue141
-rw-r--r--app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js20
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue42
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue40
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue27
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue121
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/timeline_entry_item.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue74
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue103
-rw-r--r--app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue40
-rw-r--r--app/assets/javascripts/vue_shared/components/select2_select.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue56
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_regular_label.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_scoped_label.vue47
-rw-r--r--app/assets/javascripts/vue_shared/components/svg_gradient.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue39
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue25
-rw-r--r--app/assets/javascripts/vue_shared/directives/tooltip.js1
-rw-r--r--app/assets/javascripts/vue_shared/mixins/is_ee.js10
-rw-r--r--app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js217
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js13
-rw-r--r--app/assets/stylesheets/application.scss42
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss14
-rw-r--r--app/assets/stylesheets/components/avatar.scss202
-rw-r--r--app/assets/stylesheets/components/dashboard_skeleton.scss77
-rw-r--r--app/assets/stylesheets/components/popover.scss63
-rw-r--r--app/assets/stylesheets/components/project_list_item.scss24
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss416
-rw-r--r--app/assets/stylesheets/components/toast.scss53
-rw-r--r--app/assets/stylesheets/errors.scss2
-rw-r--r--app/assets/stylesheets/framework.scss3
-rw-r--r--app/assets/stylesheets/framework/animations.scss52
-rw-r--r--app/assets/stylesheets/framework/asciidoctor.scss2
-rw-r--r--app/assets/stylesheets/framework/avatar.scss160
-rw-r--r--app/assets/stylesheets/framework/awards.scss16
-rw-r--r--app/assets/stylesheets/framework/blank.scss28
-rw-r--r--app/assets/stylesheets/framework/blocks.scss29
-rw-r--r--app/assets/stylesheets/framework/buttons.scss46
-rw-r--r--app/assets/stylesheets/framework/callout.scss4
-rw-r--r--app/assets/stylesheets/framework/ci_variable_list.scss2
-rw-r--r--app/assets/stylesheets/framework/common.scss139
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss133
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/emojis.scss2
-rw-r--r--app/assets/stylesheets/framework/files.scss39
-rw-r--r--app/assets/stylesheets/framework/filters.scss24
-rw-r--r--app/assets/stylesheets/framework/flash.scss29
-rw-r--r--app/assets/stylesheets/framework/forms.scss125
-rw-r--r--app/assets/stylesheets/framework/gfm.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss80
-rw-r--r--app/assets/stylesheets/framework/highlight.scss3
-rw-r--r--app/assets/stylesheets/framework/icons.scss5
-rw-r--r--app/assets/stylesheets/framework/lists.scss20
-rw-r--r--app/assets/stylesheets/framework/logo.scss35
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss123
-rw-r--r--app/assets/stylesheets/framework/mixins.scss85
-rw-r--r--app/assets/stylesheets/framework/modal.scss16
-rw-r--r--app/assets/stylesheets/framework/notes.scss2
-rw-r--r--app/assets/stylesheets/framework/page_title.scss4
-rw-r--r--app/assets/stylesheets/framework/panels.scss1
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss79
-rw-r--r--app/assets/stylesheets/framework/selects.scss12
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss52
-rw-r--r--app/assets/stylesheets/framework/snippets.scss4
-rw-r--r--app/assets/stylesheets/framework/sortable.scss92
-rw-r--r--app/assets/stylesheets/framework/spinner.scss51
-rw-r--r--app/assets/stylesheets/framework/system_messages.scss3
-rw-r--r--app/assets/stylesheets/framework/tables.scss1
-rw-r--r--app/assets/stylesheets/framework/terms.scss1
-rw-r--r--app/assets/stylesheets/framework/timeline.scss5
-rw-r--r--app/assets/stylesheets/framework/toggle.scss14
-rw-r--r--app/assets/stylesheets/framework/typography.scss142
-rw-r--r--app/assets/stylesheets/framework/variables.scss181
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss11
-rw-r--r--app/assets/stylesheets/framework/vue_transitions.scss6
-rw-r--r--app/assets/stylesheets/framework/wells.scss4
-rw-r--r--app/assets/stylesheets/highlight/common.scss2
-rw-r--r--app/assets/stylesheets/highlight/embedded.scss2
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss74
-rw-r--r--app/assets/stylesheets/mailers/highlighted_diff_email.scss67
-rw-r--r--app/assets/stylesheets/notify.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_mixins.scss14
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss10
-rw-r--r--app/assets/stylesheets/page_bundles/xterm.scss6
-rw-r--r--app/assets/stylesheets/pages/boards.scss215
-rw-r--r--app/assets/stylesheets/pages/builds.scss59
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/assets/stylesheets/pages/commits.scss56
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss1
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss420
-rw-r--r--app/assets/stylesheets/pages/environments.scss336
-rw-r--r--app/assets/stylesheets/pages/events.scss10
-rw-r--r--app/assets/stylesheets/pages/graph.scss2
-rw-r--r--app/assets/stylesheets/pages/groups.scss52
-rw-r--r--app/assets/stylesheets/pages/help.scss10
-rw-r--r--app/assets/stylesheets/pages/import.scss14
-rw-r--r--app/assets/stylesheets/pages/issuable.scss71
-rw-r--r--app/assets/stylesheets/pages/issues.scss18
-rw-r--r--app/assets/stylesheets/pages/labels.scss78
-rw-r--r--app/assets/stylesheets/pages/login.scss19
-rw-r--r--app/assets/stylesheets/pages/members.scss100
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss96
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss176
-rw-r--r--app/assets/stylesheets/pages/milestone.scss5
-rw-r--r--app/assets/stylesheets/pages/monitor.scss5
-rw-r--r--app/assets/stylesheets/pages/note_form.scss49
-rw-r--r--app/assets/stylesheets/pages/notes.scss158
-rw-r--r--app/assets/stylesheets/pages/notifications.scss28
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss7
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss58
-rw-r--r--app/assets/stylesheets/pages/profile.scss65
-rw-r--r--app/assets/stylesheets/pages/projects.scss101
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss270
-rw-r--r--app/assets/stylesheets/pages/reports.scss5
-rw-r--r--app/assets/stylesheets/pages/search.scss15
-rw-r--r--app/assets/stylesheets/pages/settings.scss63
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss10
-rw-r--r--app/assets/stylesheets/pages/status.scss9
-rw-r--r--app/assets/stylesheets/pages/todos.scss79
-rw-r--r--app/assets/stylesheets/pages/tree.scss20
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss2
-rw-r--r--app/assets/stylesheets/pages/wiki.scss6
-rw-r--r--app/assets/stylesheets/performance_bar.scss8
-rw-r--r--app/assets/stylesheets/print.scss18
-rw-r--r--app/assets/stylesheets/utilities.scss17
-rw-r--r--app/assets/stylesheets/vendors/atwho.scss92
-rw-r--r--app/controllers/abuse_reports_controller.rb6
-rw-r--r--app/controllers/acme_challenges_controller.rb17
-rw-r--r--app/controllers/admin/appearances_controller.rb11
-rw-r--r--app/controllers/admin/application_controller.rb7
-rw-r--r--app/controllers/admin/application_settings_controller.rb29
-rw-r--r--app/controllers/admin/applications_controller.rb4
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb4
-rw-r--r--app/controllers/admin/clusters/applications_controller.rb11
-rw-r--r--app/controllers/admin/clusters_controller.rb13
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb2
-rw-r--r--app/controllers/admin/groups_controller.rb11
-rw-r--r--app/controllers/admin/hooks_controller.rb4
-rw-r--r--app/controllers/admin/identities_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb8
-rw-r--r--app/controllers/admin/keys_controller.rb4
-rw-r--r--app/controllers/admin/labels_controller.rb6
-rw-r--r--app/controllers/admin/logs_controller.rb3
-rw-r--r--app/controllers/admin/projects_controller.rb17
-rw-r--r--app/controllers/admin/runners_controller.rb16
-rw-r--r--app/controllers/admin/spam_logs_controller.rb6
-rw-r--r--app/controllers/admin/users_controller.rb42
-rw-r--r--app/controllers/application_controller.rb42
-rw-r--r--app/controllers/autocomplete_controller.rb9
-rw-r--r--app/controllers/clusters/applications_controller.rb42
-rw-r--r--app/controllers/clusters/clusters_controller.rb12
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb13
-rw-r--r--app/controllers/concerns/boards_actions.rb38
-rw-r--r--app/controllers/concerns/continue_params.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb25
-rw-r--r--app/controllers/concerns/enforces_admin_authentication.rb19
-rw-r--r--app/controllers/concerns/enforces_two_factor_authentication.rb2
-rw-r--r--app/controllers/concerns/import_url_params.rb19
-rw-r--r--app/controllers/concerns/issuable_actions.rb9
-rw-r--r--app/controllers/concerns/issuable_collections.rb17
-rw-r--r--app/controllers/concerns/lfs_request.rb6
-rw-r--r--app/controllers/concerns/membership_actions.rb35
-rw-r--r--app/controllers/concerns/milestone_actions.rb10
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/concerns/preview_markdown.rb2
-rw-r--r--app/controllers/concerns/project_unauthorized.rb19
-rw-r--r--app/controllers/concerns/renders_notes.rb2
-rw-r--r--app/controllers/concerns/routable_actions.rb18
-rw-r--r--app/controllers/concerns/spammable_actions.rb6
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/confirmations_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb22
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/dashboard_controller.rb5
-rw-r--r--app/controllers/explore/projects_controller.rb6
-rw-r--r--app/controllers/google_api/authorizations_controller.rb32
-rw-r--r--app/controllers/graphql_controller.rb61
-rw-r--r--app/controllers/groups/boards_controller.rb39
-rw-r--r--app/controllers/groups/group_members_controller.rb1
-rw-r--r--app/controllers/groups/runners_controller.rb10
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb20
-rw-r--r--app/controllers/groups/variables_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb43
-rw-r--r--app/controllers/help_controller.rb35
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb16
-rw-r--r--app/controllers/import/fogbugz_controller.rb6
-rw-r--r--app/controllers/import/gitea_controller.rb2
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb4
-rw-r--r--app/controllers/import/google_code_controller.rb12
-rw-r--r--app/controllers/import/phabricator_controller.rb35
-rw-r--r--app/controllers/invites_controller.rb8
-rw-r--r--app/controllers/jwt_controller.rb8
-rw-r--r--app/controllers/ldap/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb21
-rw-r--r--app/controllers/passwords_controller.rb4
-rw-r--r--app/controllers/profiles/accounts_controller.rb4
-rw-r--r--app/controllers/profiles/active_sessions_controller.rb11
-rw-r--r--app/controllers/profiles/chat_names_controller.rb10
-rw-r--r--app/controllers/profiles/groups_controller.rb24
-rw-r--r--app/controllers/profiles/notifications_controller.rb4
-rw-r--r--app/controllers/profiles/passwords_controller.rb12
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb8
-rw-r--r--app/controllers/profiles/preferences_controller.rb10
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb25
-rw-r--r--app/controllers/profiles/u2f_registrations_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb7
-rw-r--r--app/controllers/projects/application_controller.rb11
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb65
-rw-r--r--app/controllers/projects/boards_controller.rb39
-rw-r--r--app/controllers/projects/branches_controller.rb18
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb4
-rw-r--r--app/controllers/projects/clusters_controller.rb8
-rw-r--r--app/controllers/projects/commit_controller.rb6
-rw-r--r--app/controllers/projects/commits_controller.rb1
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/environments/prometheus_api_controller.rb60
-rw-r--r--app/controllers/projects/environments_controller.rb53
-rw-r--r--app/controllers/projects/git_http_client_controller.rb15
-rw-r--r--app/controllers/projects/git_http_controller.rb18
-rw-r--r--app/controllers/projects/graphs_controller.rb8
-rw-r--r--app/controllers/projects/group_links_controller.rb7
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/imports_controller.rb13
-rw-r--r--app/controllers/projects/issues_controller.rb25
-rw-r--r--app/controllers/projects/jobs_controller.rb4
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/lfs_api_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb12
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb82
-rw-r--r--app/controllers/projects/mirrors_controller.rb5
-rw-r--r--app/controllers/projects/pages_domains_controller.rb4
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb10
-rw-r--r--app/controllers/projects/pipelines_controller.rb16
-rw-r--r--app/controllers/projects/refs_controller.rb2
-rw-r--r--app/controllers/projects/repositories_controller.rb2
-rw-r--r--app/controllers/projects/runners_controller.rb10
-rw-r--r--app/controllers/projects/serverless/functions_controller.rb35
-rw-r--r--app/controllers/projects/services_controller.rb10
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb11
-rw-r--r--app/controllers/projects/settings/operations_controller.rb47
-rw-r--r--app/controllers/projects/stages_controller.rb25
-rw-r--r--app/controllers/projects/tags/releases_controller.rb10
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/projects/triggers_controller.rb14
-rw-r--r--app/controllers/projects/variables_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb19
-rw-r--r--app/controllers/projects_controller.rb26
-rw-r--r--app/controllers/registrations_controller.rb25
-rw-r--r--app/controllers/root_controller.rb2
-rw-r--r--app/controllers/search_controller.rb10
-rw-r--r--app/controllers/sent_notifications_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb25
-rw-r--r--app/controllers/uploads_controller.rb13
-rw-r--r--app/finders/admin/runners_finder.rb11
-rw-r--r--app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb44
-rw-r--r--app/finders/autocomplete/users_finder.rb10
-rw-r--r--app/finders/clusters/knative_services_finder.rb112
-rw-r--r--app/finders/groups_finder.rb8
-rw-r--r--app/finders/issuable_finder.rb114
-rw-r--r--app/finders/issues_finder.rb18
-rw-r--r--app/finders/members_finder.rb43
-rw-r--r--app/finders/merge_requests_finder.rb24
-rw-r--r--app/finders/projects/daily_statistics_finder.rb21
-rw-r--r--app/finders/projects/serverless/functions_finder.rb62
-rw-r--r--app/finders/projects_finder.rb8
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/graphql/gitlab_schema.rb84
-rw-r--r--app/graphql/mutations/merge_requests/base.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb19
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb10
-rw-r--r--app/graphql/resolvers/full_path_resolver.rb6
-rw-r--r--app/graphql/resolvers/group_resolver.rb13
-rw-r--r--app/graphql/resolvers/issues_resolver.rb17
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb4
-rw-r--r--app/graphql/resolvers/metadata_resolver.rb11
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb33
-rw-r--r--app/graphql/resolvers/namespace_resolver.rb13
-rw-r--r--app/graphql/resolvers/project_resolver.rb4
-rw-r--r--app/graphql/resolvers/tree_resolver.rb26
-rw-r--r--app/graphql/types/base_field.rb42
-rw-r--r--app/graphql/types/base_object.rb5
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb17
-rw-r--r--app/graphql/types/ci/pipeline_type.rb12
-rw-r--r--app/graphql/types/group_type.rb23
-rw-r--r--app/graphql/types/issue_type.rb26
-rw-r--r--app/graphql/types/merge_request_type.rb12
-rw-r--r--app/graphql/types/metadata_type.rb10
-rw-r--r--app/graphql/types/milestone_type.rb2
-rw-r--r--app/graphql/types/namespace_type.rb24
-rw-r--r--app/graphql/types/permission_types/group.rb11
-rw-r--r--app/graphql/types/project_statistics_type.rb16
-rw-r--r--app/graphql/types/project_type.rb27
-rw-r--r--app/graphql/types/query_type.rb22
-rw-r--r--app/graphql/types/repository_type.rb14
-rw-r--r--app/graphql/types/tree/blob_type.rb14
-rw-r--r--app/graphql/types/tree/entry_type.rb14
-rw-r--r--app/graphql/types/tree/submodule_type.rb10
-rw-r--r--app/graphql/types/tree/tree_entry_type.rb15
-rw-r--r--app/graphql/types/tree/tree_type.rb18
-rw-r--r--app/graphql/types/tree/type_enum.rb14
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/helpers/appearances_helper.rb8
-rw-r--r--app/helpers/application_settings_helper.rb51
-rw-r--r--app/helpers/auth_helper.rb10
-rw-r--r--app/helpers/auto_devops_helper.rb13
-rw-r--r--app/helpers/blob_helper.rb25
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/helpers/broadcast_messages_helper.rb2
-rw-r--r--app/helpers/builds_helper.rb6
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb25
-rw-r--r--app/helpers/ci_variables_helper.rb19
-rw-r--r--app/helpers/clusters_helper.rb6
-rw-r--r--app/helpers/dashboard_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb67
-rw-r--r--app/helpers/environments_helper.rb3
-rw-r--r--app/helpers/events_helper.rb12
-rw-r--r--app/helpers/form_helper.rb44
-rw-r--r--app/helpers/groups/group_members_helper.rb7
-rw-r--r--app/helpers/groups_helper.rb8
-rw-r--r--app/helpers/issuables_helper.rb15
-rw-r--r--app/helpers/labels_helper.rb164
-rw-r--r--app/helpers/markup_helper.rb11
-rw-r--r--app/helpers/merge_requests_helper.rb8
-rw-r--r--app/helpers/milestones_helper.rb25
-rw-r--r--app/helpers/mirror_helper.rb4
-rw-r--r--app/helpers/namespaces_helper.rb7
-rw-r--r--app/helpers/nav_helper.rb8
-rw-r--r--app/helpers/notes_helper.rb12
-rw-r--r--app/helpers/notifications_helper.rb11
-rw-r--r--app/helpers/page_layout_helper.rb3
-rw-r--r--app/helpers/preferences_helper.rb3
-rw-r--r--app/helpers/projects_helper.rb82
-rw-r--r--app/helpers/search_helper.rb73
-rw-r--r--app/helpers/sidekiq_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb149
-rw-r--r--app/helpers/storage_helper.rb13
-rw-r--r--app/helpers/tracking_helper.rb7
-rw-r--r--app/helpers/tree_helper.rb21
-rw-r--r--app/helpers/user_callouts_helper.rb3
-rw-r--r--app/helpers/visibility_level_helper.rb54
-rw-r--r--app/helpers/wiki_helper.rb20
-rw-r--r--app/mailers/abuse_report_mailer.rb4
-rw-r--r--app/mailers/devise_mailer.rb1
-rw-r--r--app/mailers/email_rejection_mailer.rb4
-rw-r--r--app/mailers/emails/issues.rb12
-rw-r--r--app/mailers/emails/members.rb3
-rw-r--r--app/mailers/emails/merge_requests.rb12
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb8
-rw-r--r--app/mailers/emails/pipelines.rb2
-rw-r--r--app/mailers/emails/projects.rb10
-rw-r--r--app/mailers/emails/remote_mirrors.rb2
-rw-r--r--app/mailers/notify.rb18
-rw-r--r--app/mailers/repository_check_mailer.rb4
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/active_session.rb55
-rw-r--r--app/models/appearance.rb3
-rw-r--r--app/models/application_record.rb31
-rw-r--r--app/models/application_setting.rb345
-rw-r--r--app/models/application_setting/term.rb2
-rw-r--r--app/models/application_setting_implementation.rb299
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/badge.rb4
-rw-r--r--app/models/blob.rb2
-rw-r--r--app/models/board.rb2
-rw-r--r--app/models/board_group_recent_visit.rb11
-rw-r--r--app/models/board_project_recent_visit.rb11
-rw-r--r--app/models/broadcast_message.rb4
-rw-r--r--app/models/chat_name.rb2
-rw-r--r--app/models/chat_team.rb2
-rw-r--r--app/models/ci/bridge.rb9
-rw-r--r--app/models/ci/build.rb310
-rw-r--r--app/models/ci/build_metadata.rb2
-rw-r--r--app/models/ci/build_runner_session.rb22
-rw-r--r--app/models/ci/build_trace_chunk.rb4
-rw-r--r--app/models/ci/build_trace_section.rb2
-rw-r--r--app/models/ci/build_trace_section_name.rb2
-rw-r--r--app/models/ci/group_variable.rb3
-rw-r--r--app/models/ci/job_artifact.rb43
-rw-r--r--app/models/ci/legacy_stage.rb4
-rw-r--r--app/models/ci/pipeline.rb167
-rw-r--r--app/models/ci/pipeline_chat_data.rb2
-rw-r--r--app/models/ci/pipeline_enums.rb2
-rw-r--r--app/models/ci/pipeline_schedule.rb30
-rw-r--r--app/models/ci/pipeline_schedule_variable.rb2
-rw-r--r--app/models/ci/pipeline_variable.rb2
-rw-r--r--app/models/ci/runner.rb5
-rw-r--r--app/models/ci/runner_namespace.rb2
-rw-r--r--app/models/ci/runner_project.rb2
-rw-r--r--app/models/ci/stage.rb13
-rw-r--r--app/models/ci/trigger.rb2
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb3
-rw-r--r--app/models/clusters/applications/cert_manager.rb8
-rw-r--r--app/models/clusters/applications/helm.rb9
-rw-r--r--app/models/clusters/applications/ingress.rb10
-rw-r--r--app/models/clusters/applications/jupyter.rb41
-rw-r--r--app/models/clusters/applications/knative.rb61
-rw-r--r--app/models/clusters/applications/prometheus.rb20
-rw-r--r--app/models/clusters/applications/runner.rb28
-rw-r--r--app/models/clusters/cluster.rb101
-rw-r--r--app/models/clusters/concerns/application_core.rb16
-rw-r--r--app/models/clusters/concerns/application_data.rb66
-rw-r--r--app/models/clusters/concerns/application_status.rb13
-rw-r--r--app/models/clusters/group.rb2
-rw-r--r--app/models/clusters/instance.rb17
-rw-r--r--app/models/clusters/kubernetes_namespace.rb4
-rw-r--r--app/models/clusters/platforms/kubernetes.rb81
-rw-r--r--app/models/clusters/project.rb3
-rw-r--r--app/models/clusters/providers/gcp.rb2
-rw-r--r--app/models/commit.rb11
-rw-r--r--app/models/commit_collection.rb39
-rw-r--r--app/models/commit_range.rb14
-rw-r--r--app/models/commit_status.rb25
-rw-r--r--app/models/commit_status_enums.rb3
-rw-r--r--app/models/concerns/artifact_migratable.rb46
-rw-r--r--app/models/concerns/atomic_internal_id.rb16
-rw-r--r--app/models/concerns/avatarable.rb3
-rw-r--r--app/models/concerns/blob_language_from_git_attributes.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb76
-rw-r--r--app/models/concerns/ci/contextable.rb108
-rw-r--r--app/models/concerns/ci/pipeline_delegator.rb21
-rw-r--r--app/models/concerns/ci/processable.rb4
-rw-r--r--app/models/concerns/deployment_platform.rb13
-rw-r--r--app/models/concerns/deprecated_assignee.rb86
-rw-r--r--app/models/concerns/feature_gate.rb2
-rw-r--r--app/models/concerns/group_descendant.rb4
-rw-r--r--app/models/concerns/has_ref.rb16
-rw-r--r--app/models/concerns/has_status.rb21
-rw-r--r--app/models/concerns/has_variable.rb11
-rw-r--r--app/models/concerns/ignorable_column.rb2
-rw-r--r--app/models/concerns/issuable.rb74
-rw-r--r--app/models/concerns/issuable_states.rb25
-rw-r--r--app/models/concerns/maskable.rb22
-rw-r--r--app/models/concerns/milestoneish.rb43
-rw-r--r--app/models/concerns/mirror_authentication.rb2
-rw-r--r--app/models/concerns/noteable.rb20
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/prometheus_adapter.rb4
-rw-r--r--app/models/concerns/reactive_caching.rb43
-rw-r--r--app/models/concerns/redactable.rb2
-rw-r--r--app/models/concerns/referable.rb1
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb10
-rw-r--r--app/models/concerns/sortable.rb28
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb24
-rw-r--r--app/models/concerns/strip_attribute.rb2
-rw-r--r--app/models/concerns/taskable.rb9
-rw-r--r--app/models/concerns/token_authenticatable.rb19
-rw-r--r--app/models/concerns/token_authenticatable_strategies/base.rb18
-rw-r--r--app/models/concerns/token_authenticatable_strategies/encrypted.rb52
-rw-r--r--app/models/concerns/update_project_statistics.rb72
-rw-r--r--app/models/container_repository.rb2
-rw-r--r--app/models/conversational_development_index/metric.rb2
-rw-r--r--app/models/deploy_keys_project.rb2
-rw-r--r--app/models/deploy_token.rb2
-rw-r--r--app/models/deployment.rb21
-rw-r--r--app/models/diff_note.rb32
-rw-r--r--app/models/email.rb4
-rw-r--r--app/models/environment.rb26
-rw-r--r--app/models/epic.rb2
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb60
-rw-r--r--app/models/event.rb56
-rw-r--r--app/models/fork_network.rb2
-rw-r--r--app/models/fork_network_member.rb2
-rw-r--r--app/models/generic_commit_status.rb2
-rw-r--r--app/models/global_label.rb8
-rw-r--r--app/models/global_milestone.rb4
-rw-r--r--app/models/gpg_key.rb2
-rw-r--r--app/models/gpg_key_subkey.rb2
-rw-r--r--app/models/gpg_signature.rb9
-rw-r--r--app/models/group.rb31
-rw-r--r--app/models/group_custom_attribute.rb2
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb2
-rw-r--r--app/models/identity.rb3
-rw-r--r--app/models/identity/uniqueness_scopes.rb2
-rw-r--r--app/models/import_export_upload.rb2
-rw-r--r--app/models/individual_note_discussion.rb2
-rw-r--r--app/models/instance_configuration.rb2
-rw-r--r--app/models/internal_id.rb57
-rw-r--r--app/models/issue.rb49
-rw-r--r--app/models/issue/metrics.rb2
-rw-r--r--app/models/issue_assignee.rb2
-rw-r--r--app/models/key.rb7
-rw-r--r--app/models/label.rb18
-rw-r--r--app/models/label_link.rb2
-rw-r--r--app/models/label_note.rb2
-rw-r--r--app/models/label_priority.rb2
-rw-r--r--app/models/legacy_diff_note.rb2
-rw-r--r--app/models/lfs_file_lock.rb2
-rw-r--r--app/models/lfs_object.rb4
-rw-r--r--app/models/lfs_objects_project.rb2
-rw-r--r--app/models/list.rb2
-rw-r--r--app/models/member.rb10
-rw-r--r--app/models/members/group_member.rb6
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb273
-rw-r--r--app/models/merge_request/metrics.rb2
-rw-r--r--app/models/merge_request_assignee.rb6
-rw-r--r--app/models/merge_request_diff.rb206
-rw-r--r--app/models/merge_request_diff_commit.rb2
-rw-r--r--app/models/merge_request_diff_file.rb4
-rw-r--r--app/models/merge_requests_closing_issues.rb2
-rw-r--r--app/models/milestone.rb28
-rw-r--r--app/models/namespace.rb66
-rw-r--r--app/models/note.rb10
-rw-r--r--app/models/note_diff_file.rb6
-rw-r--r--app/models/notification_recipient.rb70
-rw-r--r--app/models/notification_setting.rb11
-rw-r--r--app/models/pages_domain.rb31
-rw-r--r--app/models/pages_domain_acme_order.rb24
-rw-r--r--app/models/personal_access_token.rb6
-rw-r--r--app/models/pool_repository.rb12
-rw-r--r--app/models/postgresql/replication_slot.rb2
-rw-r--r--app/models/programming_language.rb2
-rw-r--r--app/models/project.rb209
-rw-r--r--app/models/project_authorization.rb2
-rw-r--r--app/models/project_auto_devops.rb27
-rw-r--r--app/models/project_ci_cd_setting.rb20
-rw-r--r--app/models/project_custom_attribute.rb2
-rw-r--r--app/models/project_daily_statistic.rb10
-rw-r--r--app/models/project_deploy_token.rb2
-rw-r--r--app/models/project_feature.rb19
-rw-r--r--app/models/project_group_link.rb6
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_import_state.rb2
-rw-r--r--app/models/project_metrics_setting.rb9
-rw-r--r--app/models/project_repository.rb2
-rw-r--r--app/models/project_services/asana_service.rb8
-rw-r--r--app/models/project_services/bamboo_service.rb12
-rw-r--r--app/models/project_services/campfire_service.rb2
-rw-r--r--app/models/project_services/chat_message/deployment_message.rb77
-rw-r--r--app/models/project_services/chat_notification_service.rb4
-rw-r--r--app/models/project_services/discord_service.rb9
-rw-r--r--app/models/project_services/emails_on_push_service.rb14
-rw-r--r--app/models/project_services/external_wiki_service.rb6
-rw-r--r--app/models/project_services/flowdock_service.rb4
-rw-r--r--app/models/project_services/hangouts_chat_service.rb5
-rw-r--r--app/models/project_services/hipchat_service.rb311
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb36
-rw-r--r--app/models/project_services/kubernetes_service.rb18
-rw-r--r--app/models/project_services/microsoft_teams_service.rb5
-rw-r--r--app/models/project_services/mock_ci_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb24
-rw-r--r--app/models/project_services/pivotaltracker_service.rb8
-rw-r--r--app/models/project_services/prometheus_service.rb6
-rw-r--r--app/models/project_services/pushover_service.rb26
-rw-r--r--app/models/project_services/youtrack_service.rb40
-rw-r--r--app/models/project_statistics.rb27
-rw-r--r--app/models/project_wiki.rb32
-rw-r--r--app/models/prometheus_metric.rb2
-rw-r--r--app/models/protected_branch.rb16
-rw-r--r--app/models/protected_branch/merge_access_level.rb2
-rw-r--r--app/models/protected_branch/push_access_level.rb2
-rw-r--r--app/models/protected_tag.rb2
-rw-r--r--app/models/protected_tag/create_access_level.rb2
-rw-r--r--app/models/push_event.rb2
-rw-r--r--app/models/push_event_payload.rb2
-rw-r--r--app/models/redirect_route.rb2
-rw-r--r--app/models/release.rb10
-rw-r--r--app/models/releases/link.rb4
-rw-r--r--app/models/remote_mirror.rb18
-rw-r--r--app/models/repository.rb127
-rw-r--r--app/models/repository_language.rb2
-rw-r--r--app/models/resource_label_event.rb2
-rw-r--r--app/models/route.rb18
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/serverless/function.rb26
-rw-r--r--app/models/service.rb7
-rw-r--r--app/models/shard.rb2
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/spam_log.rb2
-rw-r--r--app/models/ssh_host_key.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/models/suggestion.rb51
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/term_agreement.rb2
-rw-r--r--app/models/timelog.rb2
-rw-r--r--app/models/todo.rb14
-rw-r--r--app/models/trending_project.rb2
-rw-r--r--app/models/u2f_registration.rb4
-rw-r--r--app/models/upload.rb8
-rw-r--r--app/models/user.rb101
-rw-r--r--app/models/user_agent_detail.rb2
-rw-r--r--app/models/user_callout.rb2
-rw-r--r--app/models/user_custom_attribute.rb2
-rw-r--r--app/models/user_interacted_project.rb20
-rw-r--r--app/models/user_preference.rb6
-rw-r--r--app/models/user_status.rb2
-rw-r--r--app/models/user_synced_attributes_metadata.rb2
-rw-r--r--app/models/users_star_project.rb2
-rw-r--r--app/models/wiki_page.rb21
-rw-r--r--app/policies/base_policy.rb9
-rw-r--r--app/policies/ci/pipeline_policy.rb12
-rw-r--r--app/policies/clusters/cluster_policy.rb1
-rw-r--r--app/policies/clusters/instance_policy.rb21
-rw-r--r--app/policies/global_policy.rb5
-rw-r--r--app/policies/group_policy.rb22
-rw-r--r--app/policies/identity_provider_policy.rb15
-rw-r--r--app/policies/issuable_policy.rb1
-rw-r--r--app/policies/merge_request_policy.rb3
-rw-r--r--app/policies/personal_snippet_policy.rb13
-rw-r--r--app/policies/project_policy.rb41
-rw-r--r--app/presenters/blob_presenter.rb4
-rw-r--r--app/presenters/blobs/unfold_presenter.rb75
-rw-r--r--app/presenters/ci/bridge_presenter.rb9
-rw-r--r--app/presenters/ci/build_runner_presenter.rb22
-rw-r--r--app/presenters/ci/pipeline_presenter.rb45
-rw-r--r--app/presenters/clusterable_presenter.rb4
-rw-r--r--app/presenters/clusters/cluster_presenter.rb12
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/presenters/group_clusterable_presenter.rb5
-rw-r--r--app/presenters/instance_clusterable_presenter.rb69
-rw-r--r--app/presenters/issue_presenter.rb12
-rw-r--r--app/presenters/label_presenter.rb51
-rw-r--r--app/presenters/member_presenter.rb5
-rw-r--r--app/presenters/merge_request_presenter.rb51
-rw-r--r--app/presenters/project_clusterable_presenter.rb5
-rw-r--r--app/presenters/project_presenter.rb18
-rw-r--r--app/presenters/tree_entry_presenter.rb9
-rw-r--r--app/serializers/acts_as_taggable_on/tag_entity.rb6
-rw-r--r--app/serializers/acts_as_taggable_on/tag_serializer.rb5
-rw-r--r--app/serializers/analytics_stage_entity.rb5
-rw-r--r--app/serializers/build_details_entity.rb17
-rw-r--r--app/serializers/cluster_application_entity.rb2
-rw-r--r--app/serializers/concerns/user_status_tooltip.rb4
-rw-r--r--app/serializers/deployment_entity.rb33
-rw-r--r--app/serializers/detailed_status_entity.rb16
-rw-r--r--app/serializers/diff_file_base_entity.rb10
-rw-r--r--app/serializers/diff_file_entity.rb6
-rw-r--r--app/serializers/environment_entity.rb3
-rw-r--r--app/serializers/group_variable_entity.rb1
-rw-r--r--app/serializers/issuable_sidebar_extras_entity.rb2
-rw-r--r--app/serializers/issue_board_entity.rb2
-rw-r--r--app/serializers/issue_entity.rb10
-rw-r--r--app/serializers/issue_sidebar_extras_entity.rb1
-rw-r--r--app/serializers/job_artifact_report_entity.rb13
-rw-r--r--app/serializers/merge_request_assignee_entity.rb7
-rw-r--r--app/serializers/merge_request_basic_entity.rb3
-rw-r--r--app/serializers/merge_request_for_pipeline_entity.rb17
-rw-r--r--app/serializers/merge_request_serializer.rb4
-rw-r--r--app/serializers/merge_request_sidebar_basic_entity.rb11
-rw-r--r--app/serializers/merge_request_sidebar_extras_entity.rb7
-rw-r--r--app/serializers/merge_request_widget_entity.rb17
-rw-r--r--app/serializers/pipeline_details_entity.rb5
-rw-r--r--app/serializers/pipeline_entity.rb20
-rw-r--r--app/serializers/pipeline_serializer.rb39
-rw-r--r--app/serializers/projects/serverless/service_entity.rb7
-rw-r--r--app/serializers/suggestion_entity.rb2
-rw-r--r--app/serializers/suggestion_serializer.rb9
-rw-r--r--app/serializers/test_case_entity.rb1
-rw-r--r--app/serializers/variable_entity.rb1
-rw-r--r--app/services/after_branch_delete_service.rb23
-rw-r--r--app/services/application_settings/update_service.rb10
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/auto_merge/base_service.rb52
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb30
-rw-r--r--app/services/auto_merge_service.rb50
-rw-r--r--app/services/boards/visits/latest_service.rb14
-rw-r--r--app/services/ci/create_pipeline_service.rb18
-rw-r--r--app/services/ci/destroy_pipeline_service.rb2
-rw-r--r--app/services/ci/expire_pipeline_cache_service.rb62
-rw-r--r--app/services/ci/pipeline_schedule_service.rb13
-rw-r--r--app/services/ci/play_manual_stage_service.rb29
-rw-r--r--app/services/ci/prepare_build_service.rb27
-rw-r--r--app/services/ci/register_job_service.rb7
-rw-r--r--app/services/ci/stop_environments_service.rb16
-rw-r--r--app/services/clusters/applications/base_helm_service.rb30
-rw-r--r--app/services/clusters/applications/base_service.rb92
-rw-r--r--app/services/clusters/applications/check_ingress_ip_address_service.rb16
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb6
-rw-r--r--app/services/clusters/applications/check_uninstall_progress_service.rb62
-rw-r--r--app/services/clusters/applications/create_service.rb75
-rw-r--r--app/services/clusters/applications/destroy_service.rb23
-rw-r--r--app/services/clusters/applications/install_service.rb31
-rw-r--r--app/services/clusters/applications/patch_service.rb32
-rw-r--r--app/services/clusters/applications/schedule_installation_service.rb31
-rw-r--r--app/services/clusters/applications/uninstall_service.rb29
-rw-r--r--app/services/clusters/applications/update_service.rb17
-rw-r--r--app/services/clusters/applications/upgrade_service.rb34
-rw-r--r--app/services/clusters/build_service.rb2
-rw-r--r--app/services/clusters/create_service.rb2
-rw-r--r--app/services/clusters/refresh_service.rb2
-rw-r--r--app/services/commits/create_service.rb13
-rw-r--r--app/services/compare_service.rb4
-rw-r--r--app/services/concerns/suggestible.rb37
-rw-r--r--app/services/concerns/users/participable_service.rb26
-rw-r--r--app/services/concerns/validates_classification_label.rb27
-rw-r--r--app/services/delete_branch_service.rb34
-rw-r--r--app/services/error_tracking/list_issues_service.rb11
-rw-r--r--app/services/error_tracking/list_projects_service.rb8
-rw-r--r--app/services/files/delete_service.rb2
-rw-r--r--app/services/files/multi_service.rb3
-rw-r--r--app/services/files/update_service.rb2
-rw-r--r--app/services/git/base_hooks_service.rb105
-rw-r--r--app/services/git/branch_hooks_service.rb144
-rw-r--r--app/services/git/branch_push_service.rb92
-rw-r--r--app/services/git/tag_hooks_service.rb36
-rw-r--r--app/services/git/tag_push_service.rb14
-rw-r--r--app/services/git_push_service.rb240
-rw-r--r--app/services/git_tag_push_service.rb66
-rw-r--r--app/services/groups/auto_devops_service.rb17
-rw-r--r--app/services/groups/base_service.rb6
-rw-r--r--app/services/groups/create_service.rb12
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/groups/nested_create_service.rb2
-rw-r--r--app/services/groups/transfer_service.rb22
-rw-r--r--app/services/groups/update_service.rb1
-rw-r--r--app/services/import/github_service.rb2
-rw-r--r--app/services/issuable/clone/content_rewriter.rb1
-rw-r--r--app/services/issuable_base_service.rb57
-rw-r--r--app/services/issues/base_service.rb22
-rw-r--r--app/services/issues/build_service.rb4
-rw-r--r--app/services/issues/close_service.rb13
-rw-r--r--app/services/issues/move_service.rb4
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/labels/available_labels_service.rb60
-rw-r--r--app/services/lfs/file_transformer.rb9
-rw-r--r--app/services/members/create_service.rb15
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb4
-rw-r--r--app/services/merge_requests/base_service.rb48
-rw-r--r--app/services/merge_requests/build_service.rb1
-rw-r--r--app/services/merge_requests/close_service.rb6
-rw-r--r--app/services/merge_requests/create_pipeline_service.rb37
-rw-r--r--app/services/merge_requests/create_service.rb2
-rw-r--r--app/services/merge_requests/delete_non_latest_diffs_service.rb4
-rw-r--r--app/services/merge_requests/merge_base_service.rb63
-rw-r--r--app/services/merge_requests/merge_service.rb60
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb57
-rw-r--r--app/services/merge_requests/merge_when_pipeline_succeeds_service.rb47
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb82
-rw-r--r--app/services/merge_requests/migrate_external_diffs_service.rb23
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb162
-rw-r--r--app/services/merge_requests/rebase_service.rb12
-rw-r--r--app/services/merge_requests/refresh_service.rb29
-rw-r--r--app/services/merge_requests/reopen_service.rb2
-rw-r--r--app/services/merge_requests/squash_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb22
-rw-r--r--app/services/milestones/promote_service.rb10
-rw-r--r--app/services/note_summary.rb4
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/notes/quick_actions_service.rb26
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/services/notification_recipient_service.rb28
-rw-r--r--app/services/notification_service.rb30
-rw-r--r--app/services/pages_domains/create_acme_order_service.rb31
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb41
-rw-r--r--app/services/preview_markdown_service.rb30
-rw-r--r--app/services/projects/cleanup_service.rb47
-rw-r--r--app/services/projects/create_service.rb14
-rw-r--r--app/services/projects/destroy_service.rb10
-rw-r--r--app/services/projects/detect_repository_languages_service.rb12
-rw-r--r--app/services/projects/download_service.rb2
-rw-r--r--app/services/projects/fetch_statistics_increment_service.rb32
-rw-r--r--app/services/projects/fork_service.rb20
-rw-r--r--app/services/projects/git_deduplication_service.rb64
-rw-r--r--app/services/projects/group_links/create_service.rb10
-rw-r--r--app/services/projects/hashed_storage/base_attachment_service.rb51
-rw-r--r--app/services/projects/hashed_storage/base_repository_service.rb22
-rw-r--r--app/services/projects/hashed_storage/migrate_attachments_service.rb49
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb16
-rw-r--r--app/services/projects/hashed_storage/rollback_attachments_service.rb34
-rw-r--r--app/services/projects/hashed_storage/rollback_repository_service.rb40
-rw-r--r--app/services/projects/hashed_storage/rollback_service.rb37
-rw-r--r--app/services/projects/housekeeping_service.rb12
-rw-r--r--app/services/projects/import_error_filter.rb2
-rw-r--r--app/services/projects/import_service.rb23
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_link_list_service.rb32
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_service.rb20
-rw-r--r--app/services/projects/lfs_pointers/lfs_import_service.rb92
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb4
-rw-r--r--app/services/projects/lfs_pointers/lfs_object_download_list_service.rb96
-rw-r--r--app/services/projects/move_project_group_links_service.rb2
-rw-r--r--app/services/projects/operations/update_service.rb32
-rw-r--r--app/services/projects/propagate_service_template.rb2
-rw-r--r--app/services/projects/repository_languages_service.rb24
-rw-r--r--app/services/projects/transfer_service.rb19
-rw-r--r--app/services/projects/update_service.rb17
-rw-r--r--app/services/projects/update_statistics_service.rb19
-rw-r--r--app/services/prometheus/proxy_service.rb116
-rw-r--r--app/services/push_event_payload_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb641
-rw-r--r--app/services/releases/concerns.rb2
-rw-r--r--app/services/releases/create_service.rb20
-rw-r--r--app/services/releases/destroy_service.rb1
-rw-r--r--app/services/resource_events/change_labels_service.rb2
-rw-r--r--app/services/search/global_service.rb3
-rw-r--r--app/services/search/group_service.rb6
-rw-r--r--app/services/search/project_service.rb7
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/services/service_response.rb32
-rw-r--r--app/services/suggestions/apply_service.rb14
-rw-r--r--app/services/suggestions/create_service.rb42
-rw-r--r--app/services/suggestions/outdate_service.rb19
-rw-r--r--app/services/system_hooks_service.rb6
-rw-r--r--app/services/system_note_service.rb24
-rw-r--r--app/services/tags/destroy_service.rb11
-rw-r--r--app/services/test_hooks/project_service.rb16
-rw-r--r--app/services/test_hooks/system_service.rb2
-rw-r--r--app/services/todo_service.rb16
-rw-r--r--app/services/todos/destroy/base_service.rb2
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb35
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb13
-rw-r--r--app/services/update_deployment_service.rb2
-rw-r--r--app/services/upload_service.rb2
-rw-r--r--app/services/users/activity_service.rb2
-rw-r--r--app/services/users/build_service.rb6
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb2
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb4
-rw-r--r--app/services/validate_new_branch_service.rb4
-rw-r--r--app/services/verify_pages_domain_service.rb15
-rw-r--r--app/uploaders/attachment_uploader.rb2
-rw-r--r--app/uploaders/avatar_uploader.rb2
-rw-r--r--app/uploaders/file_mover.rb8
-rw-r--r--app/uploaders/file_uploader.rb16
-rw-r--r--app/uploaders/import_export_uploader.rb4
-rw-r--r--app/uploaders/legacy_artifact_uploader.rb24
-rw-r--r--app/uploaders/object_storage.rb8
-rw-r--r--app/uploaders/personal_file_uploader.rb70
-rw-r--r--app/uploaders/records_uploads.rb4
-rw-r--r--app/validators/addressable_url_validator.rb112
-rw-r--r--app/validators/cluster_name_validator.rb8
-rw-r--r--app/validators/devise_email_validator.rb36
-rw-r--r--app/validators/email_validator.rb7
-rw-r--r--app/validators/public_url_validator.rb19
-rw-r--r--app/validators/sha_validator.rb9
-rw-r--r--app/validators/url_validator.rb104
-rw-r--r--app/validators/x509_certificate_credentials_validator.rb86
-rw-r--r--app/views/abuse_reports/new.html.haml14
-rw-r--r--app/views/admin/appearances/_system_header_footer_form.html.haml9
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml29
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml29
-rw-r--r--app/views/admin/application_settings/_email.html.haml16
-rw-r--r--app/views/admin/application_settings/_external_authorization_service_form.html.haml51
-rw-r--r--app/views/admin/application_settings/_help_page.html.haml12
-rw-r--r--app/views/admin/application_settings/_logging.html.haml6
-rw-r--r--app/views/admin/application_settings/_outbound.html.haml8
-rw-r--r--app/views/admin/application_settings/_pages.html.haml28
-rw-r--r--app/views/admin/application_settings/_performance_bar.html.haml4
-rw-r--r--app/views/admin/application_settings/_repository_mirrors_form.html.haml10
-rw-r--r--app/views/admin/application_settings/_visibility_and_access.html.haml18
-rw-r--r--app/views/admin/application_settings/metrics_and_profiling.html.haml2
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/applications/_form.html.haml12
-rw-r--r--app/views/admin/applications/show.html.haml4
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml16
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--app/views/admin/dashboard/index.html.haml7
-rw-r--r--app/views/admin/deploy_keys/edit.html.haml8
-rw-r--r--app/views/admin/deploy_keys/index.html.haml20
-rw-r--r--app/views/admin/groups/_form.html.haml7
-rw-r--r--app/views/admin/groups/show.html.haml10
-rw-r--r--app/views/admin/health_check/show.html.haml2
-rw-r--r--app/views/admin/identities/_form.html.haml6
-rw-r--r--app/views/admin/labels/_form.html.haml14
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/admin/projects/_projects.html.haml6
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml20
-rw-r--r--app/views/admin/runners/_runner.html.haml4
-rw-r--r--app/views/admin/runners/index.html.haml19
-rw-r--r--app/views/admin/users/_access_levels.html.haml24
-rw-r--r--app/views/admin/users/_form.html.haml68
-rw-r--r--app/views/admin/users/_head.html.haml1
-rw-r--r--app/views/admin/users/_user_detail.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml14
-rw-r--r--app/views/award_emoji/_awards_block.html.haml6
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml8
-rw-r--r--app/views/ci/status/_icon.html.haml16
-rw-r--r--app/views/ci/variables/_content.html.haml2
-rw-r--r--app/views/ci/variables/_header.html.haml2
-rw-r--r--app/views/ci/variables/_index.html.haml3
-rw-r--r--app/views/ci/variables/_variable_header.html.haml16
-rw-r--r--app/views/ci/variables/_variable_row.html.haml38
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml2
-rw-r--r--app/views/clusters/clusters/_banner.html.haml12
-rw-r--r--app/views/clusters/clusters/_form.html.haml6
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml2
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml58
-rw-r--r--app/views/clusters/clusters/show.html.haml16
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml80
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml94
-rw-r--r--app/views/dashboard/_activity_head.html.haml2
-rw-r--r--app/views/dashboard/_groups_head.html.haml2
-rw-r--r--app/views/dashboard/_projects_head.html.haml26
-rw-r--r--app/views/dashboard/_snippets_head.html.haml2
-rw-r--r--app/views/dashboard/activity.html.haml2
-rw-r--r--app/views/dashboard/groups/_groups.html.haml4
-rw-r--r--app/views/dashboard/groups/index.html.haml2
-rw-r--r--app/views/dashboard/issues.html.haml4
-rw-r--r--app/views/dashboard/merge_requests.html.haml4
-rw-r--r--app/views/dashboard/milestones/index.html.haml2
-rw-r--r--app/views/dashboard/projects/_nav.html.haml27
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml15
-rw-r--r--app/views/dashboard/projects/index.html.haml4
-rw-r--r--app/views/dashboard/projects/starred.html.haml4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/devise/confirmations/new.html.haml2
-rw-r--r--app/views/devise/mailer/email_changed.html.haml2
-rw-r--r--app/views/devise/mailer/email_changed.text.erb2
-rw-r--r--app/views/devise/passwords/edit.html.haml2
-rw-r--r--app/views/devise/passwords/new.html.haml2
-rw-r--r--app/views/devise/registrations/edit.html.erb2
-rw-r--r--app/views/devise/shared/_signin_box.html.haml8
-rw-r--r--app/views/devise/shared/_signup_box.html.haml27
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml1
-rw-r--r--app/views/devise/unlocks/new.html.haml2
-rw-r--r--app/views/discussions/_diff_discussion.html.haml2
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml4
-rw-r--r--app/views/discussions/_notes.html.haml20
-rw-r--r--app/views/discussions/_parallel_diff_discussion.html.haml8
-rw-r--r--app/views/doorkeeper/applications/index.html.haml2
-rw-r--r--app/views/doorkeeper/applications/show.html.haml4
-rw-r--r--app/views/events/_event.html.haml6
-rw-r--r--app/views/events/event/_common.html.haml3
-rw-r--r--app/views/events/event/_note.html.haml3
-rw-r--r--app/views/events/event/_push.html.haml3
-rw-r--r--app/views/explore/groups/_groups.html.haml4
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml10
-rw-r--r--app/views/explore/projects/index.html.haml6
-rw-r--r--app/views/explore/projects/starred.html.haml6
-rw-r--r--app/views/explore/projects/trending.html.haml6
-rw-r--r--app/views/groups/_archived_projects.html.haml4
-rw-r--r--app/views/groups/_create_chat_team.html.haml9
-rw-r--r--app/views/groups/_group_admin_settings.html.haml11
-rw-r--r--app/views/groups/_home_panel.html.haml4
-rw-r--r--app/views/groups/_shared_projects.html.haml4
-rw-r--r--app/views/groups/_subgroups_and_projects.html.haml4
-rw-r--r--app/views/groups/edit.html.haml4
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml4
-rw-r--r--app/views/groups/labels/index.html.haml7
-rw-r--r--app/views/groups/new.html.haml4
-rw-r--r--app/views/groups/settings/_general.html.haml20
-rw-r--r--app/views/groups/settings/_permissions.html.haml1
-rw-r--r--app/views/groups/settings/_project_creation_level.html.haml3
-rw-r--r--app/views/groups/settings/ci_cd/_auto_devops_form.html.haml15
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml16
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml12
-rw-r--r--app/views/help/index.html.haml13
-rw-r--r--app/views/help/instance_configuration.html.haml2
-rw-r--r--app/views/help/show.html.haml2
-rw-r--r--app/views/help/ui.html.haml4
-rw-r--r--app/views/import/bitbucket_server/status.html.haml2
-rw-r--r--app/views/import/gitea/new.html.haml6
-rw-r--r--app/views/import/gitea/status.html.haml2
-rw-r--r--app/views/import/github/new.html.haml2
-rw-r--r--app/views/import/gitlab_projects/new.html.haml23
-rw-r--r--app/views/import/manifest/new.html.haml6
-rw-r--r--app/views/import/phabricator/new.html.haml25
-rw-r--r--app/views/import/shared/_errors.html.haml4
-rw-r--r--app/views/import/shared/_new_project_form.html.haml21
-rw-r--r--app/views/issues/_issue.atom.builder1
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_mailer.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/_piwik.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml15
-rw-r--r--app/views/layouts/empty_mailer.html.haml5
-rw-r--r--app/views/layouts/empty_mailer.text.erb5
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml5
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/mailer.text.erb5
-rw-r--r--app/views/layouts/nav/_classification_level_banner.html.haml5
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml37
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml26
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml17
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml12
-rw-r--r--app/views/layouts/notify.html.haml4
-rw-r--r--app/views/layouts/notify.text.erb5
-rw-r--r--app/views/notify/_note_email.text.erb4
-rw-r--r--app/views/notify/_reassigned_issuable_email.html.haml10
-rw-r--r--app/views/notify/_removal_notification.html.haml9
-rw-r--r--app/views/notify/closed_issue_email.html.haml2
-rw-r--r--app/views/notify/closed_issue_email.text.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/issue_due_email.html.haml2
-rw-r--r--app/views/notify/issue_due_email.text.erb2
-rw-r--r--app/views/notify/issue_moved_email.html.haml11
-rw-r--r--app/views/notify/issue_moved_email.text.erb4
-rw-r--r--app/views/notify/links/ci/builds/_build.text.erb2
-rw-r--r--app/views/notify/member_access_granted_email.html.haml11
-rw-r--r--app/views/notify/member_access_granted_email.text.erb7
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.text.erb2
-rw-r--r--app/views/notify/new_merge_request_email.html.haml6
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/notify/new_user_email.html.haml3
-rw-r--r--app/views/notify/new_user_email.text.erb11
-rw-r--r--app/views/notify/pages_domain_disabled_email.html.haml4
-rw-r--r--app/views/notify/pages_domain_verification_failed_email.html.haml4
-rw-r--r--app/views/notify/reassigned_issue_email.html.haml11
-rw-r--r--app/views/notify/reassigned_merge_request_email.html.haml11
-rw-r--r--app/views/notify/reassigned_merge_request_email.text.erb4
-rw-r--r--app/views/profiles/_email_settings.html.haml16
-rw-r--r--app/views/profiles/_event_table.html.haml6
-rw-r--r--app/views/profiles/accounts/_providers.html.haml21
-rw-r--r--app/views/profiles/accounts/show.html.haml19
-rw-r--r--app/views/profiles/active_sessions/_active_session.html.haml17
-rw-r--r--app/views/profiles/active_sessions/index.html.haml4
-rw-r--r--app/views/profiles/audit_log.html.haml4
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml4
-rw-r--r--app/views/profiles/chat_names/index.html.haml16
-rw-r--r--app/views/profiles/emails/index.html.haml6
-rw-r--r--app/views/profiles/gpg_keys/_form.html.haml6
-rw-r--r--app/views/profiles/gpg_keys/_key.html.haml16
-rw-r--r--app/views/profiles/gpg_keys/_key_table.html.haml4
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml12
-rw-r--r--app/views/profiles/keys/_form.html.haml4
-rw-r--r--app/views/profiles/keys/_key.html.haml9
-rw-r--r--app/views/profiles/keys/_key_details.html.haml13
-rw-r--r--app/views/profiles/keys/_key_table.html.haml4
-rw-r--r--app/views/profiles/keys/index.html.haml8
-rw-r--r--app/views/profiles/keys/show.html.haml2
-rw-r--r--app/views/profiles/notifications/_email_settings.html.haml6
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml23
-rw-r--r--app/views/profiles/notifications/show.html.haml25
-rw-r--r--app/views/profiles/passwords/edit.html.haml25
-rw-r--r--app/views/profiles/passwords/new.html.haml15
-rw-r--r--app/views/profiles/preferences/show.html.haml53
-rw-r--r--app/views/profiles/show.html.haml47
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml10
-rw-r--r--app/views/profiles/two_factor_auths/codes.html.haml5
-rw-r--r--app/views/profiles/two_factor_auths/create.html.haml4
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml61
-rw-r--r--app/views/projects/_classification_policy_settings.html.haml6
-rw-r--r--app/views/projects/_export.html.haml67
-rw-r--r--app/views/projects/_files.html.haml9
-rw-r--r--app/views/projects/_flash_messages.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml26
-rw-r--r--app/views/projects/_import_project_pane.html.haml30
-rw-r--r--app/views/projects/_md_preview.html.haml8
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml19
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml46
-rw-r--r--app/views/projects/_merge_request_merge_options_settings.html.haml14
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml23
-rw-r--r--app/views/projects/_merge_request_settings.html.haml4
-rw-r--r--app/views/projects/_new_project_fields.html.haml18
-rw-r--r--app/views/projects/_wiki.html.haml5
-rw-r--r--app/views/projects/_zen.html.haml1
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/blob/_header_content.html.haml4
-rw-r--r--app/views/projects/blob/_markdown_buttons.html.haml20
-rw-r--r--app/views/projects/blob/diff.html.haml6
-rw-r--r--app/views/projects/blob/preview.html.haml41
-rw-r--r--app/views/projects/blob/viewers/_dependency_manager.html.haml5
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_route_map.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_route_map_loading.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/branches/_commit.html.haml4
-rw-r--r--app/views/projects/branches/index.html.haml3
-rw-r--r--app/views/projects/buttons/_clone.html.haml5
-rw-r--r--app/views/projects/buttons/_download.html.haml45
-rw-r--r--app/views/projects/buttons/_download_links.html.haml5
-rw-r--r--app/views/projects/ci/builds/_build.html.haml9
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml17
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml2
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml2
-rw-r--r--app/views/projects/default_branch/_show.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml4
-rw-r--r--app/views/projects/deployments/_commit.html.haml6
-rw-r--r--app/views/projects/deployments/_confirm_rollback_modal.html.haml23
-rw-r--r--app/views/projects/deployments/_deployment.html.haml2
-rw-r--r--app/views/projects/deployments/_rollback.haml3
-rw-r--r--app/views/projects/diffs/_content.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml8
-rw-r--r--app/views/projects/diffs/_replaced_image_diff.html.haml6
-rw-r--r--app/views/projects/diffs/_single_image_diff.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml4
-rw-r--r--app/views/projects/edit.html.haml352
-rw-r--r--app/views/projects/empty.html.haml133
-rw-r--r--app/views/projects/environments/_form.html.haml2
-rw-r--r--app/views/projects/forks/error.html.haml14
-rw-r--r--app/views/projects/forks/index.html.haml13
-rw-r--r--app/views/projects/forks/new.html.haml16
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml4
-rw-r--r--app/views/projects/graphs/show.html.haml4
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml10
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml36
-rw-r--r--app/views/projects/issues/_merge_requests_status.html.haml25
-rw-r--r--app/views/projects/issues/_new_branch.html.haml8
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/new.html.haml9
-rw-r--r--app/views/projects/issues/show.html.haml18
-rw-r--r--app/views/projects/jobs/_table.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml1
-rw-r--r--app/views/projects/labels/index.html.haml41
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml10
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml6
-rw-r--r--app/views/projects/merge_requests/conflicts/_submit_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/show.html.haml46
-rw-r--r--app/views/projects/milestones/show.html.haml7
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml22
-rw-r--r--app/views/projects/mirrors/_disabled_mirror_badge.html.haml1
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml38
-rw-r--r--app/views/projects/mirrors/_mirror_repos_push.html.haml2
-rw-r--r--app/views/projects/mirrors/_ssh_host_keys.html.haml2
-rw-r--r--app/views/projects/new.html.haml11
-rw-r--r--app/views/projects/notes/_actions.html.haml6
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml2
-rw-r--r--app/views/projects/pages/_https_only.html.haml2
-rw-r--r--app/views/projects/pages_domains/_form.html.haml91
-rw-r--r--app/views/projects/pages_domains/_helper_text.html.haml9
-rw-r--r--app/views/projects/pages_domains/edit.html.haml1
-rw-r--r--app/views/projects/pages_domains/new.html.haml1
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml6
-rw-r--r--app/views/projects/pipelines/_info.html.haml14
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml30
-rw-r--r--app/views/projects/pipelines/charts.html.haml6
-rw-r--r--app/views/projects/pipelines/index.html.haml4
-rw-r--r--app/views/projects/pipelines/new.html.haml12
-rw-r--r--app/views/projects/pipelines/show.html.haml10
-rw-r--r--app/views/projects/project_members/_groups.html.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml3
-rw-r--r--app/views/projects/project_members/_team.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml42
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml4
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml4
-rw-r--r--app/views/projects/protected_tags/shared/_index.html.haml2
-rw-r--r--app/views/projects/registry/repositories/_tag.html.haml2
-rw-r--r--app/views/projects/serverless/functions/index.html.haml5
-rw-r--r--app/views/projects/serverless/functions/show.html.haml11
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml40
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml38
-rw-r--r--app/views/projects/settings/_general.html.haml42
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml17
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml17
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml4
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml30
-rw-r--r--app/views/projects/settings/operations/_external_dashboard.html.haml3
-rw-r--r--app/views/projects/settings/operations/show.html.haml7
-rw-r--r--app/views/projects/settings/repository/_protected_branches.html.haml2
-rw-r--r--app/views/projects/settings/repository/show.html.haml7
-rw-r--r--app/views/projects/tags/_tag.html.haml5
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml9
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml3
-rw-r--r--app/views/projects/tree/_tree_header.html.haml121
-rw-r--r--app/views/projects/wikis/pages.html.haml13
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/repository_check_mailer/notify.html.haml2
-rw-r--r--app/views/repository_check_mailer/notify.text.haml2
-rw-r--r--app/views/search/_category.html.haml10
-rw-r--r--app/views/search/_form.html.haml1
-rw-r--r--app/views/search/_results.html.haml8
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/search/results/_user.html.haml10
-rw-r--r--app/views/search/results/_wiki_blob.html.haml4
-rw-r--r--app/views/sent_notifications/unsubscribe.html.haml2
-rw-r--r--app/views/shared/_choose_avatar_button.html.haml4
-rw-r--r--app/views/shared/_choose_group_avatar_button.html.haml4
-rw-r--r--app/views/shared/_clone_panel.html.haml5
-rw-r--r--app/views/shared/_confirm_modal.html.haml8
-rw-r--r--app/views/shared/_delete_label_modal.html.haml6
-rw-r--r--app/views/shared/_file_highlight.html.haml2
-rw-r--r--app/views/shared/_import_form.html.haml30
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml2
-rw-r--r--app/views/shared/_label.html.haml10
-rw-r--r--app/views/shared/_label_row.html.haml17
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml6
-rw-r--r--app/views/shared/_mobile_clone_panel.html.haml1
-rw-r--r--app/views/shared/_old_visibility_level.html.haml2
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml2
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml4
-rw-r--r--app/views/shared/boards/_show.html.haml10
-rw-r--r--app/views/shared/boards/components/_board.html.haml13
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml7
-rw-r--r--app/views/shared/boards/components/sidebar/_assignee.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml27
-rw-r--r--app/views/shared/boards/components/sidebar/_time_tracker.html.haml6
-rw-r--r--app/views/shared/deploy_keys/_form.html.haml9
-rw-r--r--app/views/shared/form_elements/_description.html.haml2
-rw-r--r--app/views/shared/groups/_dropdown.html.haml6
-rw-r--r--app/views/shared/icons/_emoji_slightly_smiling_face.svg1
-rw-r--r--app/views/shared/icons/_emoji_smile.svg1
-rw-r--r--app/views/shared/icons/_emoji_smiley.svg1
-rw-r--r--app/views/shared/icons/_gitea_logo.svg.erb1
-rw-r--r--app/views/shared/issuable/_assignees.html.haml6
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml5
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml4
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml12
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml12
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml16
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml56
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml17
-rw-r--r--app/views/shared/issuable/form/_contribution.html.haml2
-rw-r--r--app/views/shared/issuable/form/_merge_request_assignee.html.haml31
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml7
-rw-r--r--app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml11
-rw-r--r--app/views/shared/issuable/form/_metadata_issue_assignee.html.haml11
-rw-r--r--app/views/shared/issuable/form/_title.html.haml2
-rw-r--r--app/views/shared/labels/_form.html.haml20
-rw-r--r--app/views/shared/members/_access_request_links.html.haml2
-rw-r--r--app/views/shared/members/_group.html.haml19
-rw-r--r--app/views/shared/members/_member.html.haml41
-rw-r--r--app/views/shared/milestones/_issuable.html.haml3
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml5
-rw-r--r--app/views/shared/milestones/_milestone.html.haml4
-rw-r--r--app/views/shared/milestones/_tabs.html.haml6
-rw-r--r--app/views/shared/milestones/_top.html.haml32
-rw-r--r--app/views/shared/notes/_form.html.haml2
-rw-r--r--app/views/shared/notes/_hints.html.haml5
-rw-r--r--app/views/shared/notes/_note.html.haml17
-rw-r--r--app/views/shared/notifications/_button.html.haml14
-rw-r--r--app/views/shared/notifications/_new_button.html.haml6
-rw-r--r--app/views/shared/notifications/_notification_dropdown.html.haml2
-rw-r--r--app/views/shared/projects/_dropdown.html.haml21
-rw-r--r--app/views/shared/projects/_project.html.haml5
-rw-r--r--app/views/shared/projects/_search_bar.html.haml28
-rw-r--r--app/views/shared/projects/_search_form.html.haml7
-rw-r--r--app/views/shared/projects/_sort_dropdown.html.haml39
-rw-r--r--app/views/shared/snippets/_form.html.haml12
-rw-r--r--app/views/shared/snippets/_header.html.haml10
-rw-r--r--app/views/snippets/_actions.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/notes/_actions.html.haml6
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/views/users/show.html.haml8
-rw-r--r--app/workers/all_queues.yml16
-rw-r--r--app/workers/auto_merge_process_worker.rb14
-rw-r--r--app/workers/build_finished_worker.rb1
-rw-r--r--app/workers/ci/build_prepare_worker.rb16
-rw-r--r--app/workers/cluster_configure_worker.rb6
-rw-r--r--app/workers/cluster_patch_app_worker.rb13
-rw-r--r--app/workers/clusters/applications/uninstall_worker.rb17
-rw-r--r--app/workers/clusters/applications/wait_for_uninstall_app_worker.rb20
-rw-r--r--app/workers/concerns/application_worker.rb2
-rw-r--r--app/workers/concerns/waitable_worker.rb8
-rw-r--r--app/workers/create_gpg_signature_worker.rb12
-rw-r--r--app/workers/deployments/finished_worker.rb13
-rw-r--r--app/workers/detect_repository_languages_worker.rb7
-rw-r--r--app/workers/email_receiver_worker.rb16
-rw-r--r--app/workers/emails_on_push_worker.rb34
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb51
-rw-r--r--app/workers/git_garbage_collect_worker.rb15
-rw-r--r--app/workers/hashed_storage/base_worker.rb21
-rw-r--r--app/workers/hashed_storage/project_migrate_worker.rb26
-rw-r--r--app/workers/hashed_storage/project_rollback_worker.rb26
-rw-r--r--app/workers/hashed_storage/rollbacker_worker.rb16
-rw-r--r--app/workers/migrate_external_diffs_worker.rb12
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb22
-rw-r--r--app/workers/pages_domain_removal_cron_worker.rb14
-rw-r--r--app/workers/pages_domain_verification_cron_worker.rb2
-rw-r--r--app/workers/pages_domain_verification_worker.rb2
-rw-r--r--app/workers/pipeline_metrics_worker.rb2
-rw-r--r--app/workers/pipeline_schedule_worker.rb36
-rw-r--r--app/workers/pipeline_success_worker.rb8
-rw-r--r--app/workers/post_receive.rb39
-rw-r--r--app/workers/process_commit_worker.rb2
-rw-r--r--app/workers/project_cache_worker.rb25
-rw-r--r--app/workers/project_daily_statistics_worker.rb13
-rw-r--r--app/workers/project_migrate_hashed_storage_worker.rb43
-rw-r--r--app/workers/reactive_caching_worker.rb7
-rw-r--r--app/workers/remove_expired_members_worker.rb8
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb26
-rw-r--r--app/workers/schedule_migrate_external_diffs_worker.rb14
-rw-r--r--app/workers/todos_destroyer/confidential_issue_worker.rb4
-rw-r--r--app/workers/update_project_statistics_worker.rb18
-rw-r--r--babel.config.js13
-rwxr-xr-xbin/background_jobs9
-rw-r--r--changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml5
-rw-r--r--changelogs/unreleased/10798-remove-dind-req-for-dast.yml5
-rw-r--r--changelogs/unreleased/11105-fix-cs-with-proxy.yml5
-rw-r--r--changelogs/unreleased/11204-turn-off-mask-by-default.yml5
-rw-r--r--changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml5
-rw-r--r--changelogs/unreleased/1340-request-job-with-age.yml5
-rw-r--r--changelogs/unreleased/13453_minimal_atom_fix.yml5
-rw-r--r--changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml5
-rw-r--r--changelogs/unreleased/237-style-toast-component.yml5
-rw-r--r--changelogs/unreleased/24642-activity_service_optimization.yml5
-rw-r--r--changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml5
-rw-r--r--changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml5
-rw-r--r--changelogs/unreleased/28996-create-mvc-ui-in-haml.yml5
-rw-r--r--changelogs/unreleased/30138-display-cycle-analytics-issue.yml5
-rw-r--r--changelogs/unreleased/34555-empty-state-for-starred-projects.yml5
-rw-r--r--changelogs/unreleased/35638-move-language-setting-to-preferences.yml5
-rw-r--r--changelogs/unreleased/37495.yml5
-rw-r--r--changelogs/unreleased/38561-border-radii.yml5
-rw-r--r--changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml5
-rw-r--r--changelogs/unreleased/39304-broadcast-message-buttons.yml5
-rw-r--r--changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml5
-rw-r--r--changelogs/unreleased/40795-set-project-name-on-fork-api.yml5
-rw-r--r--changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml5
-rw-r--r--changelogs/unreleased/45687-web-ide-empty-state.yml5
-rw-r--r--changelogs/unreleased/47150-update-sshkey.yml5
-rw-r--r--changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml5
-rw-r--r--changelogs/unreleased/49041-issue-board-input-height.yml5
-rw-r--r--changelogs/unreleased/49502-gpg-signature-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/49517-fix-notes-import-export.yml5
-rw-r--r--changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml5
-rw-r--r--changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml5
-rw-r--r--changelogs/unreleased/50070-legacy-attachments.yml5
-rw-r--r--changelogs/unreleased/50106-hide-whitespace-changes.yml5
-rw-r--r--changelogs/unreleased/50433-make-emoji-picker-bigger.yml5
-rw-r--r--changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml5
-rw-r--r--changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml5
-rw-r--r--changelogs/unreleased/51636-task-list-api-pderichs.yml5
-rw-r--r--changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml5
-rw-r--r--changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml5
-rw-r--r--changelogs/unreleased/52424-goodbye-hipchat.yml5
-rw-r--r--changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml5
-rw-r--r--changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml5
-rw-r--r--changelogs/unreleased/53134-multiple-extendes-for-a-job.yml5
-rw-r--r--changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/53411-remove_personal_access_tokens_token.yml5
-rw-r--r--changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml5
-rw-r--r--changelogs/unreleased/54169-flash-warning-rebrush.yml5
-rw-r--r--changelogs/unreleased/54725-fix-emoji-button-active-state.yml5
-rw-r--r--changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml5
-rw-r--r--changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml5
-rw-r--r--changelogs/unreleased/54924-refactor-notes-actions-params.yml5
-rw-r--r--changelogs/unreleased/55033-discussion-system-note-alignment.yml5
-rw-r--r--changelogs/unreleased/55057-system-message-to-core.yml5
-rw-r--r--changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml5
-rw-r--r--changelogs/unreleased/55125-mr-tab-scrolling.yml5
-rw-r--r--changelogs/unreleased/55209-tool-tip-hides-menu-item.yml5
-rw-r--r--changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml5
-rw-r--r--changelogs/unreleased/55312-svg.yml5
-rw-r--r--changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml5
-rw-r--r--changelogs/unreleased/55447-validate-k8s-ca-cert.yml5
-rw-r--r--changelogs/unreleased/55447-validate-k8s-credentials.yml5
-rw-r--r--changelogs/unreleased/55703-md-image-borders.yml5
-rw-r--r--changelogs/unreleased/55893-artifacts-download.yml5
-rw-r--r--changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml5
-rw-r--r--changelogs/unreleased/56237-api-truncated-commit-title.yml5
-rw-r--r--changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml5
-rw-r--r--changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml5
-rw-r--r--changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml5
-rw-r--r--changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml5
-rw-r--r--changelogs/unreleased/56851-blank-values-in-reactive-cache.yml5
-rw-r--r--changelogs/unreleased/56871-list-issues-error.yml5
-rw-r--r--changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml5
-rw-r--r--changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml5
-rw-r--r--changelogs/unreleased/56959-drop-project_auto_devops_domain.yml5
-rw-r--r--changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml5
-rw-r--r--changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml5
-rw-r--r--changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml5
-rw-r--r--changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml5
-rw-r--r--changelogs/unreleased/57223-wiki-finder.yml5
-rw-r--r--changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml5
-rw-r--r--changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml5
-rw-r--r--changelogs/unreleased/57414-show-pipeline-iid.yml5
-rw-r--r--changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml5
-rw-r--r--changelogs/unreleased/57564-contributing-button-border.yml5
-rw-r--r--changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml5
-rw-r--r--changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml5
-rw-r--r--changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml5
-rw-r--r--changelogs/unreleased/57768-remove-vertical-line.yml5
-rw-r--r--changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml5
-rw-r--r--changelogs/unreleased/57785-create-project-template-for-netlify.yml5
-rw-r--r--changelogs/unreleased/57794-project-template-for-net.yml5
-rw-r--r--changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml5
-rw-r--r--changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml5
-rw-r--r--changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml5
-rw-r--r--changelogs/unreleased/58269-separate-update-patch.yml5
-rw-r--r--changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml5
-rw-r--r--changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml5
-rw-r--r--changelogs/unreleased/58538-discussion-top-radius.yml5
-rw-r--r--changelogs/unreleased/58632-fix-mr-widget-padding.yml5
-rw-r--r--changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml5
-rw-r--r--changelogs/unreleased/59026-replace-favicon.yml5
-rw-r--r--changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml5
-rw-r--r--changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml5
-rw-r--r--changelogs/unreleased/59587-add-graphql-logging.yml5
-rw-r--r--changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml5
-rw-r--r--changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml5
-rw-r--r--changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml5
-rw-r--r--changelogs/unreleased/60250-remove-mr_push_options-flag.yml5
-rw-r--r--changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml5
-rw-r--r--changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml5
-rw-r--r--changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml5
-rw-r--r--changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml5
-rw-r--r--changelogs/unreleased/60750-milestone-header.yml5
-rw-r--r--changelogs/unreleased/60818_yamllint_project_root.yml5
-rw-r--r--changelogs/unreleased/60819_yamllint_gitlabci.yml5
-rw-r--r--changelogs/unreleased/609120-ref-link.yml5
-rw-r--r--changelogs/unreleased/60987-emoji-picker-popup.yml5
-rw-r--r--changelogs/unreleased/61024-update-resolved-icon.yml5
-rw-r--r--changelogs/unreleased/6104-ee-ce-difference.yml5
-rw-r--r--changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml5
-rw-r--r--changelogs/unreleased/61049-links-activity-stream.yml5
-rw-r--r--changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml5
-rw-r--r--changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml5
-rw-r--r--changelogs/unreleased/61313-fix-dropdown-searchbar.yml5
-rw-r--r--changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml5
-rw-r--r--changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml5
-rw-r--r--changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml5
-rw-r--r--changelogs/unreleased/61393-emoji-button.yml5
-rw-r--r--changelogs/unreleased/61441.yml5
-rw-r--r--changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml5
-rw-r--r--changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml5
-rw-r--r--changelogs/unreleased/61606-support-string-piwik-website-ids.yml5
-rw-r--r--changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml5
-rw-r--r--changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml5
-rw-r--r--changelogs/unreleased/61697-add-project-id-to-le-common-name.yml5
-rw-r--r--changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml5
-rw-r--r--changelogs/unreleased/61795-fix-error-when-moving-issues.yml5
-rw-r--r--changelogs/unreleased/61821-tooltip-consistency.yml5
-rw-r--r--changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml5
-rw-r--r--changelogs/unreleased/61880-download-btn-group.yml5
-rw-r--r--changelogs/unreleased/61914-fix-emojis-urls.yml5
-rw-r--r--changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml6
-rw-r--r--changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml5
-rw-r--r--changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml5
-rw-r--r--changelogs/unreleased/61990-spinner.yml5
-rw-r--r--changelogs/unreleased/62061-note-icon-color.yml5
-rw-r--r--changelogs/unreleased/62091-remove-time-windows-flag.yml5
-rw-r--r--changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/62107-fix-detail-page-header-height.yml5
-rw-r--r--changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml6
-rw-r--r--changelogs/unreleased/62124-new-threaded-discussion-design.yml5
-rw-r--r--changelogs/unreleased/62227-webkit-icon-overlap.yml5
-rw-r--r--changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml5
-rw-r--r--changelogs/unreleased/62408-dropdown-truncate.yml5
-rw-r--r--changelogs/unreleased/62418-project-default-git-depth.yml5
-rw-r--r--changelogs/unreleased/62432-fix-participants-wrapping.yml5
-rw-r--r--changelogs/unreleased/62485-label-weights.yml5
-rw-r--r--changelogs/unreleased/62487-external-policy-desc.yml5
-rw-r--r--changelogs/unreleased/62656-adjusted-dropdown-styles.yml5
-rw-r--r--changelogs/unreleased/62713-fix-uninstalling-cluster-apps.yml5
-rw-r--r--changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml5
-rw-r--r--changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml5
-rw-r--r--changelogs/unreleased/9121-sort-relative-position.yml5
-rw-r--r--changelogs/unreleased/9578-adjust-milestone-completion-rate.yml5
-rw-r--r--changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml5
-rw-r--r--changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/abstract-auto-merge.yml5
-rw-r--r--changelogs/unreleased/ac-graphql-stats.yml5
-rw-r--r--changelogs/unreleased/ac-graphql-wikisize.yml5
-rw-r--r--changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml5
-rw-r--r--changelogs/unreleased/add-allow_failure-to-job-api.yml5
-rw-r--r--changelogs/unreleased/add-branch-to-project-search-api.yml5
-rw-r--r--changelogs/unreleased/add-constraint-for-milestone-dates.yml5
-rw-r--r--changelogs/unreleased/add-related-merge-request-count-to-api-response.yml5
-rw-r--r--changelogs/unreleased/add-title-attribute-to-file-row.yml5
-rw-r--r--changelogs/unreleased/add-warning-to-backup-rake-task.yml5
-rw-r--r--changelogs/unreleased/add-wiki-size-to-statistics.yml5
-rw-r--r--changelogs/unreleased/allow-maintainers-to-remove-pages.yml5
-rw-r--r--changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml5
-rw-r--r--changelogs/unreleased/an-peek-jaeger.yml5
-rw-r--r--changelogs/unreleased/antonyliu-i18n-user-profile.yml5
-rw-r--r--changelogs/unreleased/api-issuable-bulk-update.yml5
-rw-r--r--changelogs/unreleased/api_make_protected_boolean_type.yml5
-rw-r--r--changelogs/unreleased/api_masked_variables.yml5
-rw-r--r--changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml5
-rw-r--r--changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml5
-rw-r--r--changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml5
-rw-r--r--changelogs/unreleased/bvl-graphql-multiplex.yml5
-rw-r--r--changelogs/unreleased/bvl-use-global-ids-graphql.yml5
-rw-r--r--changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml5
-rw-r--r--changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml5
-rw-r--r--changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml5
-rw-r--r--changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml5
-rw-r--r--changelogs/unreleased/change-badges-example-to-pipeline.yml5
-rw-r--r--changelogs/unreleased/changelogs-readme.yml5
-rw-r--r--changelogs/unreleased/chore-remove-circuit-breaker-api.yml5
-rw-r--r--changelogs/unreleased/ci-variable-conjunction.yml5
-rw-r--r--changelogs/unreleased/copy-button-in-modals.yml5
-rw-r--r--changelogs/unreleased/deprecated-migration-inheritance.yml5
-rw-r--r--changelogs/unreleased/diff-tree-resizable.yml5
-rw-r--r--changelogs/unreleased/diff-whitespace-setting-changes.yml5
-rw-r--r--changelogs/unreleased/display-junit-classname-in-modal.yml5
-rw-r--r--changelogs/unreleased/dm-http-hostname-override.yml5
-rw-r--r--changelogs/unreleased/docs-add-chatops-request-doc.yml5
-rw-r--r--changelogs/unreleased/dz-patch-58.yml5
-rw-r--r--changelogs/unreleased/dz-scope-project-routes.yml5
-rw-r--r--changelogs/unreleased/dz-sort-labels-alphabetically.yml5
-rw-r--r--changelogs/unreleased/ee-11040-added-conditional-rendering.yml5
-rw-r--r--changelogs/unreleased/expose-merge-ref-to-runner.yml5
-rw-r--r--changelogs/unreleased/fast-destroy-uploads.yml5
-rw-r--r--changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml5
-rw-r--r--changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml5
-rw-r--r--changelogs/unreleased/filter-confidential-issues.yml5
-rw-r--r--changelogs/unreleased/filter-note-parameters.yml5
-rw-r--r--changelogs/unreleased/fix-allow-lower-case-issue-ids.yml5
-rw-r--r--changelogs/unreleased/fix-autodevops-postgres-versioning.yml5
-rw-r--r--changelogs/unreleased/fix-badges-logs.yml5
-rw-r--r--changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml5
-rw-r--r--changelogs/unreleased/fix-format-date-safari-ff.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml5
-rw-r--r--changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml5
-rw-r--r--changelogs/unreleased/fix-import-member-access.yml5
-rw-r--r--changelogs/unreleased/fix-import-param-ordering.yml5
-rw-r--r--changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml5
-rw-r--r--changelogs/unreleased/fix-search-dropdown-blur-close.yml5
-rw-r--r--changelogs/unreleased/fix-time-window-default.yml5
-rw-r--r--changelogs/unreleased/fix-too-many-loops-cron-error.yml5
-rw-r--r--changelogs/unreleased/fix_-56347.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.43.0.yml5
-rw-r--r--changelogs/unreleased/gitlab_kubernetes_helm_bump.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-commit.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-profiles-preferences.yml5
-rw-r--r--changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml5
-rw-r--r--changelogs/unreleased/gt-update-new-password-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/helm-2-12-3.yml5
-rw-r--r--changelogs/unreleased/i18n-active_sessions-in-user-profile.yml5
-rw-r--r--changelogs/unreleased/i18n-chat-of-user-profile.yml5
-rw-r--r--changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml5
-rw-r--r--changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml5
-rw-r--r--changelogs/unreleased/import-go-to-project-cta.yml5
-rw-r--r--changelogs/unreleased/improve-email-text-part.yml5
-rw-r--r--changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml5
-rw-r--r--changelogs/unreleased/improve-snippets-empty-state.yml5
-rw-r--r--changelogs/unreleased/increase-move-issue-dropdown-height.yml5
-rw-r--r--changelogs/unreleased/issue_49897.yml5
-rw-r--r--changelogs/unreleased/jc-omit-count-diverging-commits-max.yml5
-rw-r--r--changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml5
-rw-r--r--changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml5
-rw-r--r--changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml5
-rw-r--r--changelogs/unreleased/kinolaev-master-patch-13154.yml5
-rw-r--r--changelogs/unreleased/kinolaev-master-patch-87865.yml5
-rw-r--r--changelogs/unreleased/leipert-node-12-compatibility.yml5
-rw-r--r--changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml5
-rw-r--r--changelogs/unreleased/mark-comment-resolved.yml5
-rw-r--r--changelogs/unreleased/mm12935.yml5
-rw-r--r--changelogs/unreleased/move_chatops_to_core.yml5
-rw-r--r--changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml5
-rw-r--r--changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml5
-rw-r--r--changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml5
-rw-r--r--changelogs/unreleased/patch-55.yml5
-rw-r--r--changelogs/unreleased/pb-update-gitaly-1-45-0.yml5
-rw-r--r--changelogs/unreleased/pipelines-email-default-branch-filter.yml5
-rw-r--r--changelogs/unreleased/ravlen-fix-spaces-unicode.yml5
-rw-r--r--changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml5
-rw-r--r--changelogs/unreleased/referenced-labels.yml5
-rw-r--r--changelogs/unreleased/relative-urls-for-system-notes.yml5
-rw-r--r--changelogs/unreleased/remove-legacy-artifacts-related-code.yml5
-rw-r--r--changelogs/unreleased/remove-mr-diff-header-height.yml5
-rw-r--r--changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml5
-rw-r--r--changelogs/unreleased/revert-git-depth-for-merge-request.yml5
-rw-r--r--changelogs/unreleased/rs-admin-user-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml5
-rw-r--r--changelogs/unreleased/security-60039.yml5
-rw-r--r--changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml5
-rw-r--r--changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml5
-rw-r--r--changelogs/unreleased/security-fix-project-existence-disclosure-master.yml5
-rw-r--r--changelogs/unreleased/security-fix_milestones_search_api_leak.yml5
-rw-r--r--changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml5
-rw-r--r--changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml5
-rw-r--r--changelogs/unreleased/security-unsubscribing-from-issue.yml5
-rw-r--r--changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml5
-rw-r--r--changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml5
-rw-r--r--changelogs/unreleased/sh-default-visibility-fix.yml5
-rw-r--r--changelogs/unreleased/sh-fix-cpp-templates-404.yml5
-rw-r--r--changelogs/unreleased/sh-fix-double-xhr-pipelines.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-55869.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-58103.yml5
-rw-r--r--changelogs/unreleased/sh-fix-openid-connect-defaults.yml5
-rw-r--r--changelogs/unreleased/sh-fix-rebase-error-clearing.yml5
-rw-r--r--changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml5
-rw-r--r--changelogs/unreleased/sh-include-project-path-for-internal-api.yml5
-rw-r--r--changelogs/unreleased/sh-log-rails-queue-duration.yml5
-rw-r--r--changelogs/unreleased/sh-project-import-visibility-error.yml5
-rw-r--r--changelogs/unreleased/sh-update-process-mem.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml5
-rw-r--r--changelogs/unreleased/support-only-changes-on-mr-pipelines.yml5
-rw-r--r--changelogs/unreleased/t1.yml5
-rw-r--r--changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml5
-rw-r--r--changelogs/unreleased/update-babel-to-7-4-4.yml5
-rw-r--r--changelogs/unreleased/update-gitaly-to-v1-42-1.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-shell-9-3-0.yml5
-rw-r--r--changelogs/unreleased/update-pages.yml5
-rw-r--r--changelogs/unreleased/update-psd-doc.yml5
-rw-r--r--changelogs/unreleased/update-smileys-new.yml5
-rw-r--r--changelogs/unreleased/variables-boolean-type.yml5
-rw-r--r--changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml5
-rw-r--r--changelogs/unreleased/weimeng-email-routing.yml5
-rw-r--r--changelogs/unreleased/winh-notes-error-handling.yml5
-rw-r--r--changelogs/unreleased/zj-load-languages-from-database.yml5
-rw-r--r--changelogs/unreleased/zj-remove-delta-island-feature-flag.yml3
-rw-r--r--changelogs/unreleased/zj-usage-ping-pool-repository.yml5
-rw-r--r--config.ru4
-rw-r--r--config/application.rb7
-rw-r--r--config/database.yml.example0
-rw-r--r--config/environments/development.rb3
-rw-r--r--config/environments/test.rb2
-rw-r--r--config/gitlab.yml.example98
-rw-r--r--config/helpers/is_ee_env.js9
-rw-r--r--config/initializers/01_secret_token.rb11
-rw-r--r--config/initializers/1_settings.rb43
-rw-r--r--config/initializers/7_prometheus_metrics.rb14
-rw-r--r--config/initializers/8_devise.rb5
-rw-r--r--config/initializers/action_dispatch_http_mime_negotiation.rb2
-rw-r--r--config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb94
-rw-r--r--config/initializers/active_record_build_select.rb24
-rw-r--r--config/initializers/active_record_locking.rb76
-rw-r--r--config/initializers/ar_native_database_types.rb3
-rw-r--r--config/initializers/config_initializers_active_record_locking.rb45
-rw-r--r--config/initializers/console_message.rb2
-rw-r--r--config/initializers/fog_core_patch.rb52
-rw-r--r--config/initializers/gettext_rails_i18n_patch.rb2
-rw-r--r--config/initializers/graphql.rb7
-rw-r--r--config/initializers/hipchat_client_patch.rb15
-rw-r--r--config/initializers/http_hostname_override.rb49
-rw-r--r--config/initializers/jira.rb11
-rw-r--r--config/initializers/lograge.rb9
-rw-r--r--config/initializers/peek.rb14
-rw-r--r--config/initializers/postgresql_opclasses_support.rb2
-rw-r--r--config/initializers/premailer.rb3
-rw-r--r--config/initializers/rack_attack_logging.rb10
-rw-r--r--config/initializers/rack_timeout.rb23
-rw-r--r--config/initializers/rspec_profiling.rb50
-rw-r--r--config/initializers/seed_fu.rb4
-rw-r--r--config/initializers/sentry.rb16
-rw-r--r--config/initializers/sidekiq.rb26
-rw-r--r--config/initializers/tracing.rb16
-rw-r--r--config/initializers/trusted_proxies.rb6
-rw-r--r--config/initializers/zz_metrics.rb1
-rw-r--r--config/karma.config.js99
-rw-r--r--config/locales/doorkeeper.en.yml5
-rw-r--r--config/locales/en.yml6
-rw-r--r--config/prometheus/common_metrics.yml165
-rw-r--r--config/puma.rb.example70
-rw-r--r--config/routes.rb5
-rw-r--r--config/routes/admin.rb9
-rw-r--r--config/routes/git_http.rb2
-rw-r--r--config/routes/group.rb4
-rw-r--r--config/routes/import.rb2
-rw-r--r--config/routes/profile.rb6
-rw-r--r--config/routes/project.rb306
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/sidekiq_queues.yml6
-rw-r--r--config/unicorn.rb.example.development1
-rw-r--r--config/webpack.config.js67
-rw-r--r--config/webpack.config.review_toolbar.js58
-rw-r--r--danger/ce_ee_vue_templates/Dangerfile56
-rw-r--r--danger/commit_messages/Dangerfile6
-rw-r--r--danger/documentation/Dangerfile24
-rw-r--r--danger/gitlab_ui_wg/Dangerfile55
-rw-r--r--danger/plugins/helper.rb3
-rw-r--r--danger/plugins/roulette.rb10
-rw-r--r--danger/roulette/Dangerfile38
-rw-r--r--danger/single_codebase/Dangerfile63
-rw-r--r--db/fixtures/development/02_application_settings.rb10
-rw-r--r--db/fixtures/development/03_project.rb137
-rw-r--r--db/fixtures/development/03_settings.rb8
-rw-r--r--db/fixtures/development/04_labels.rb49
-rw-r--r--db/fixtures/development/04_project.rb137
-rw-r--r--db/fixtures/development/08_settings.rb7
-rw-r--r--db/fixtures/development/09_issues.rb16
-rw-r--r--db/fixtures/development/10_merge_requests.rb6
-rw-r--r--db/fixtures/development/22_labeled_issues_seed.rb103
-rw-r--r--db/fixtures/development/25_api_personal_access_token.rb15
-rw-r--r--db/fixtures/production/001_application_settings.rb2
-rw-r--r--db/importers/common_metrics_importer.rb26
-rw-r--r--db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb1
-rw-r--r--db/migrate/20150509180749_convert_legacy_reference_notes.rb3
-rw-r--r--db/migrate/20161031181638_add_unique_index_to_subscriptions.rb4
-rw-r--r--db/migrate/20170330141723_disable_invalid_service_templates2.rb2
-rw-r--r--db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb7
-rw-r--r--db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb9
-rw-r--r--db/migrate/20171211131502_add_external_classification_authorization_settings_to_appliction_settings.rb29
-rw-r--r--db/migrate/20171218140451_add_external_authorization_service_classification_label_to_projects.rb11
-rw-r--r--db/migrate/20180115094742_add_default_project_creation_setting.rb19
-rw-r--r--db/migrate/20180115113902_add_project_creation_level_to_groups.rb17
-rw-r--r--db/migrate/20180314100728_add_external_authorization_service_timeout_to_application_settings.rb18
-rw-r--r--db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb16
-rw-r--r--db/migrate/20180702134423_generate_missing_routes.rb1
-rw-r--r--db/migrate/20181205171941_create_project_daily_statistics.rb18
-rw-r--r--db/migrate/20190107151029_remove_hipchat_services.rb16
-rw-r--r--db/migrate/20190114184258_migrate_legacy_attachments.rb32
-rw-r--r--db/migrate/20190206193120_add_index_to_tags.rb18
-rw-r--r--db/migrate/20190211131150_add_state_id_to_issuables.rb17
-rw-r--r--db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb11
-rw-r--r--db/migrate/20190218134158_add_masked_to_ci_variables.rb21
-rw-r--r--db/migrate/20190218134209_add_masked_to_ci_group_variables.rb21
-rw-r--r--db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb17
-rw-r--r--db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb12
-rw-r--r--db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb42
-rw-r--r--db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb9
-rw-r--r--db/migrate/20190225160300_steal_encrypt_runners_tokens.rb19
-rw-r--r--db/migrate/20190225160301_add_runner_tokens_indexes.rb24
-rw-r--r--db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb19
-rw-r--r--db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb10
-rw-r--r--db/migrate/20190312071108_add_detected_repository_languages_to_projects.rb12
-rw-r--r--db/migrate/20190312113229_add_remove_at_to_pages_domains.rb10
-rw-r--r--db/migrate/20190312113634_add_remove_at_index_to_pages_domains.rb19
-rw-r--r--db/migrate/20190315191339_create_merge_request_assignees_table.rb22
-rw-r--r--db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb15
-rw-r--r--db/migrate/20190322164830_add_auto_ssl_enabled_to_pages_domain.rb17
-rw-r--r--db/migrate/20190325080727_truncate_user_fullname.rb21
-rw-r--r--db/migrate/20190325105715_add_fields_to_user_preferences.rb24
-rw-r--r--db/migrate/20190325165127_add_managed_to_cluster.rb17
-rw-r--r--db/migrate/20190326164045_import_common_metrics_knative.rb17
-rw-r--r--db/migrate/20190327163904_add_notification_email_to_notification_settings.rb11
-rw-r--r--db/migrate/20190329085614_add_lets_encrypt_terms_of_service_accepted_to_application_settings.rb21
-rw-r--r--db/migrate/20190408163745_prometheus_knative05_fix.rb20
-rw-r--r--db/migrate/20190412155659_add_merge_request_blocks.rb25
-rw-r--r--db/migrate/20190415030217_add_variable_type_to_ci_variables.rb17
-rw-r--r--db/migrate/20190415095825_add_packages_size_to_project_statistics.rb11
-rw-r--r--db/migrate/20190416185130_add_merge_train_enabled_to_ci_cd_settings.rb17
-rw-r--r--db/migrate/20190416213556_add_variable_type_to_ci_group_variables.rb17
-rw-r--r--db/migrate/20190416213615_add_variable_type_to_ci_pipeline_variables.rb17
-rw-r--r--db/migrate/20190416213631_add_variable_type_to_ci_pipeline_schedule_variables.rb17
-rw-r--r--db/migrate/20190418182545_create_merge_request_trains_table.rb18
-rw-r--r--db/migrate/20190422082247_create_project_metrics_settings.rb14
-rw-r--r--db/migrate/20190426180107_add_deployment_events_to_services.rb17
-rw-r--r--db/migrate/20190429082448_create_pages_domain_acme_orders.rb28
-rw-r--r--db/migrate/20190506135337_add_temporary_indexes_to_state_id.rb34
-rw-r--r--db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb10
-rw-r--r--db/migrate/20190516011213_add_build_queued_at_index.rb19
-rw-r--r--db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb16
-rw-r--r--db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb24
-rw-r--r--db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb38
-rw-r--r--db/migrate/20190524062810_generate_lets_encrypt_private_key.rb18
-rw-r--r--db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb16
-rw-r--r--db/migrate/20190527194830_add_wiki_size_to_statistics.rb9
-rw-r--r--db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb23
-rw-r--r--db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb11
-rw-r--r--db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb21
-rw-r--r--db/post_migrate/20190214112022_schedule_sync_issuables_state_id.rb56
-rw-r--r--db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb15
-rw-r--r--db/post_migrate/20190313092516_clean_up_noteable_id_for_notes_on_commits.rb33
-rw-r--r--db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb23
-rw-r--r--db/post_migrate/20190325111602_rename_v2_root_namespaces.rb27
-rw-r--r--db/post_migrate/20190424134256_drop_projects_ci_id.rb33
-rw-r--r--db/post_migrate/20190506135400_schedule_sync_issuables_state_id_where_nil.rb63
-rw-r--r--db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb11
-rw-r--r--db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb34
-rw-r--r--db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb32
-rw-r--r--db/post_migrate/20190528180441_enqueue_reset_merge_status.rb30
-rw-r--r--db/schema.rb416
-rw-r--r--doc/README.md121
-rw-r--r--doc/administration/audit_events.md116
-rw-r--r--doc/administration/audit_log.pngbin0 -> 25767 bytes
-rw-r--r--doc/administration/auditor_access_form.pngbin0 -> 11910 bytes
-rw-r--r--doc/administration/auditor_users.md88
-rw-r--r--doc/administration/auth/README.md2
-rw-r--r--doc/administration/auth/google_secure_ldap.md207
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md8
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/admin_group.pngbin0 -> 17543 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_link_final.pngbin0 -> 14524 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gifbin0 -> 1504079 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gifbin0 -> 1703084 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/multi_login.gifbin0 -> 180218 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md119
-rw-r--r--doc/administration/auth/img/google_secure_ldap_add_step_1.pngbin0 -> 28849 bytes
-rw-r--r--doc/administration/auth/img/google_secure_ldap_add_step_2.pngbin0 -> 82115 bytes
-rw-r--r--doc/administration/auth/img/google_secure_ldap_client_settings.pngbin0 -> 63959 bytes
-rw-r--r--doc/administration/auth/ldap-ee.md557
-rw-r--r--doc/administration/auth/ldap.md78
-rw-r--r--doc/administration/auth/oidc.md140
-rw-r--r--doc/administration/auth/okta.md18
-rw-r--r--doc/administration/auth/smartcard.md186
-rw-r--r--doc/administration/build_artifacts.md4
-rw-r--r--doc/administration/compliance.md18
-rw-r--r--doc/administration/container_registry.md59
-rw-r--r--doc/administration/custom_hooks.md90
-rw-r--r--doc/administration/database_load_balancing.md277
-rw-r--r--doc/administration/dependency_proxy.md150
-rw-r--r--doc/administration/geo/disaster_recovery/background_verification.md182
-rw-r--r--doc/administration/geo/disaster_recovery/bring_primary_back.md61
-rw-r--r--doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.pngbin0 -> 202130 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.pngbin0 -> 85997 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/replication-status.pngbin0 -> 7716 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/reverification-interval.pngbin0 -> 33620 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/verification-status-primary.pngbin0 -> 13329 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/verification-status-secondary.pngbin0 -> 12186 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/index.md322
-rw-r--r--doc/administration/geo/disaster_recovery/planned_failover.md227
-rw-r--r--doc/administration/geo/replication/configuration.md308
-rw-r--r--doc/administration/geo/replication/database.md508
-rw-r--r--doc/administration/geo/replication/docker_registry.md23
-rw-r--r--doc/administration/geo/replication/external_database.md219
-rw-r--r--doc/administration/geo/replication/faq.md62
-rw-r--r--doc/administration/geo/replication/high_availability.md295
-rw-r--r--doc/administration/geo/replication/img/geo_architecture.pngbin0 -> 116506 bytes
-rw-r--r--doc/administration/geo/replication/img/geo_node_dashboard.pngbin0 -> 26266 bytes
-rw-r--r--doc/administration/geo/replication/img/geo_node_healthcheck.pngbin0 -> 28285 bytes
-rw-r--r--doc/administration/geo/replication/img/geo_overview.pngbin0 -> 37246 bytes
-rw-r--r--doc/administration/geo/replication/index.md292
-rw-r--r--doc/administration/geo/replication/object_storage.md43
-rw-r--r--doc/administration/geo/replication/remove_geo_node.md50
-rw-r--r--doc/administration/geo/replication/security_review.md284
-rw-r--r--doc/administration/geo/replication/troubleshooting.md514
-rw-r--r--doc/administration/geo/replication/tuning.md17
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md448
-rw-r--r--doc/administration/geo/replication/using_a_geo_server.md18
-rw-r--r--doc/administration/git_protocol.md13
-rw-r--r--doc/administration/gitaly/index.md135
-rw-r--r--doc/administration/high_availability/README.md218
-rw-r--r--doc/administration/high_availability/alpha_database.md6
-rw-r--r--doc/administration/high_availability/consul.md105
-rw-r--r--doc/administration/high_availability/database.md1155
-rw-r--r--doc/administration/high_availability/gitaly.md21
-rw-r--r--doc/administration/high_availability/gitlab.md14
-rw-r--r--doc/administration/high_availability/img/fully-distributed.pngbin0 -> 46918 bytes
-rw-r--r--doc/administration/high_availability/img/geo-ha-diagram.pngbin0 -> 43826 bytes
-rw-r--r--doc/administration/high_availability/img/horizontal.pngbin0 -> 18660 bytes
-rw-r--r--doc/administration/high_availability/img/hybrid.pngbin0 -> 20698 bytes
-rw-r--r--doc/administration/high_availability/img/pg_ha_architecture.pngbin0 -> 18412 bytes
-rw-r--r--doc/administration/high_availability/nfs.md49
-rw-r--r--doc/administration/high_availability/nfs_host_client_setup.md135
-rw-r--r--doc/administration/high_availability/pgbouncer.md132
-rw-r--r--doc/administration/high_availability/redis.md165
-rw-r--r--doc/administration/high_availability/redis_source.md4
-rw-r--r--doc/administration/housekeeping.md2
-rw-r--r--doc/administration/img/custom_hooks_error_msg.pngbin44892 -> 80442 bytes
-rw-r--r--doc/administration/img/db_load_balancing_postgres_stats.pngbin0 -> 21543 bytes
-rw-r--r--doc/administration/img/high_availability/active-active-diagram.pngbin14649 -> 0 bytes
-rw-r--r--doc/administration/img/high_availability/active-passive-diagram.pngbin11699 -> 0 bytes
-rw-r--r--doc/administration/img/housekeeping_settings.pngbin12025 -> 24754 bytes
-rw-r--r--doc/administration/img/instance_review_button.pngbin0 -> 24525 bytes
-rw-r--r--doc/administration/incoming_email.md2
-rw-r--r--doc/administration/index.md49
-rw-r--r--doc/administration/instance_review.md17
-rw-r--r--doc/administration/integration/plantuml.md5
-rw-r--r--doc/administration/integration/terminal.md7
-rw-r--r--doc/administration/job_artifacts.md18
-rw-r--r--doc/administration/job_traces.md8
-rw-r--r--doc/administration/logs.md36
-rw-r--r--doc/administration/maven_packages.md5
-rw-r--r--doc/administration/maven_repository.md5
-rw-r--r--doc/administration/merge_request_diffs.md44
-rw-r--r--doc/administration/monitoring/index.md2
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md8
-rw-r--r--doc/administration/monitoring/performance/img/request_profiling_token.pngbin17425 -> 17396 bytes
-rw-r--r--doc/administration/monitoring/performance/introduction.md4
-rw-r--r--doc/administration/monitoring/performance/prometheus.md4
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md72
-rw-r--r--doc/administration/monitoring/prometheus/index.md27
-rw-r--r--doc/administration/monitoring/prometheus/pgbouncer_exporter.md34
-rw-r--r--doc/administration/npm_registry.md5
-rw-r--r--doc/administration/operations.md4
-rw-r--r--doc/administration/operations/cleaning_up_redis_sessions.md2
-rw-r--r--doc/administration/operations/extra_sidekiq_processes.md218
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md18
-rw-r--r--doc/administration/operations/img/sidekiq-cluster.pngbin0 -> 61923 bytes
-rw-r--r--doc/administration/operations/index.md1
-rw-r--r--doc/administration/operations/sidekiq_memory_killer.md5
-rw-r--r--doc/administration/operations/speed_up_ssh.md4
-rw-r--r--doc/administration/packages.md174
-rw-r--r--doc/administration/pages/index.md49
-rw-r--r--doc/administration/pages/source.md2
-rw-r--r--doc/administration/pseudonymizer.md103
-rw-r--r--doc/administration/raketasks/geo.md57
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/administration/raketasks/project_import_export.md1
-rw-r--r--doc/administration/raketasks/storage.md51
-rw-r--r--doc/administration/raketasks/uploads/migrate.md2
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md62
-rw-r--r--doc/administration/repository_checks.md2
-rw-r--r--doc/administration/repository_storage_paths.md2
-rw-r--r--doc/administration/repository_storage_types.md119
-rw-r--r--doc/administration/repository_storages.md4
-rw-r--r--doc/administration/troubleshooting/migration.md82
-rw-r--r--doc/administration/uploads.md5
-rw-r--r--doc/administration/user_settings.md35
-rw-r--r--doc/analytics/README.md5
-rw-r--r--doc/analytics/contribution_analytics.md5
-rw-r--r--doc/api/README.md142
-rw-r--r--doc/api/boards.md179
-rw-r--r--doc/api/build_triggers.md6
-rw-r--r--doc/api/builds.md4
-rw-r--r--doc/api/commits.md35
-rw-r--r--doc/api/discussions.md231
-rw-r--r--doc/api/environments.md105
-rw-r--r--doc/api/epic_issues.md413
-rw-r--r--doc/api/epic_links.md254
-rw-r--r--doc/api/epics.md360
-rw-r--r--doc/api/geo_nodes.md404
-rw-r--r--doc/api/graphql/index.md28
-rw-r--r--doc/api/group_level_variables.md31
-rw-r--r--doc/api/group_milestones.md3
-rw-r--r--doc/api/groups.md1
-rw-r--r--doc/api/issue_links.md215
-rw-r--r--doc/api/issues.md141
-rw-r--r--doc/api/issues_statistics.md177
-rw-r--r--doc/api/jobs.md178
-rw-r--r--doc/api/license.md176
-rw-r--r--doc/api/license_templates.md5
-rw-r--r--doc/api/managed_licenses.md136
-rw-r--r--doc/api/members.md4
-rw-r--r--doc/api/merge_request_approvals.md425
-rw-r--r--doc/api/merge_requests.md225
-rw-r--r--doc/api/milestones.md19
-rw-r--r--doc/api/namespaces.md12
-rw-r--r--doc/api/notes.md139
-rw-r--r--doc/api/packages.md152
-rw-r--r--doc/api/pipeline_schedules.md13
-rw-r--r--doc/api/pipelines.md33
-rw-r--r--doc/api/project_clusters.md10
-rw-r--r--doc/api/project_level_variables.md50
-rw-r--r--doc/api/project_snippets.md1
-rw-r--r--doc/api/project_statistics.md49
-rw-r--r--doc/api/project_templates.md2
-rw-r--r--doc/api/projects.md12
-rw-r--r--doc/api/releases/links.md10
-rw-r--r--doc/api/repository_files.md2
-rw-r--r--doc/api/repository_storage_health.md5
-rw-r--r--doc/api/resource_label_events.md86
-rw-r--r--doc/api/runners.md76
-rw-r--r--doc/api/scim.md235
-rw-r--r--doc/api/search.md259
-rw-r--r--doc/api/services.md156
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/api/snippets.md10
-rw-r--r--doc/api/suggestions.md2
-rw-r--r--doc/api/users.md5
-rw-r--r--doc/api/vulnerabilities.md113
-rw-r--r--doc/articles/artifactory_and_gitlab/index.md4
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md4
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ee/index.md5
-rw-r--r--doc/articles/how_to_install_git/index.md4
-rw-r--r--doc/articles/index.md4
-rw-r--r--doc/articles/laravel_with_gitlab_and_envoy/index.md4
-rw-r--r--doc/articles/numerous_undo_possibilities_in_git/index.md4
-rw-r--r--doc/articles/runner_autoscale_aws/index.md4
-rw-r--r--doc/ci/README.md283
-rw-r--r--doc/ci/autodeploy/index.md4
-rw-r--r--doc/ci/autodeploy/quick_start_guide.md4
-rw-r--r--doc/ci/build_artifacts/README.md4
-rw-r--r--doc/ci/caching/index.md28
-rw-r--r--doc/ci/chatops/README.md33
-rw-r--r--doc/ci/ci_cd_for_external_repos/bitbucket_integration.md166
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md134
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/bitbucket_app_password.pngbin0 -> 31488 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/bitbucket_webhook.pngbin0 -> 26142 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/ci_cd_for_external_repo.pngbin0 -> 66760 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/external_repository.pngbin0 -> 22832 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/github_omniauth.pngbin0 -> 29022 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/github_push_webhook.pngbin0 -> 45725 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/github_repo_list.pngbin0 -> 14282 bytes
-rw-r--r--doc/ci/ci_cd_for_external_repos/index.md39
-rw-r--r--doc/ci/docker/README.md4
-rw-r--r--doc/ci/docker/using_docker_build.md97
-rw-r--r--doc/ci/docker/using_docker_images.md66
-rw-r--r--doc/ci/docker/using_kaniko.md42
-rw-r--r--doc/ci/enable_or_disable_ci.md61
-rw-r--r--doc/ci/environments.md759
-rw-r--r--doc/ci/environments/protected_environments.md65
-rw-r--r--doc/ci/examples/README.md154
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md10
-rw-r--r--doc/ci/examples/browser_performance.md2
-rw-r--r--doc/ci/examples/code_quality.md46
-rw-r--r--doc/ci/examples/container_scanning.md122
-rw-r--r--doc/ci/examples/dast.md105
-rw-r--r--doc/ci/examples/dependency_scanning.md5
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md10
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md25
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/index.md4
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/img/laravel_with_gitlab_and_envoy.pngbin177704 -> 0 bytes
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.pngbin11836 -> 11827 bytes
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md13
-rw-r--r--doc/ci/examples/license_management.md5
-rw-r--r--doc/ci/examples/sast.md5
-rw-r--r--doc/ci/examples/sast_docker.md6
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md13
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md6
-rw-r--r--doc/ci/examples/test-scala-application.md11
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/img/job-succeeded.pngbin23148 -> 0 bytes
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md9
-rw-r--r--doc/ci/git_submodules.md4
-rw-r--r--doc/ci/img/add_file_template_11_10.pngbin0 -> 55910 bytes
-rw-r--r--doc/ci/img/cicd_pipeline_infograph.pngbin32493 -> 0 bytes
-rw-r--r--doc/ci/img/deployments_view.pngbin23352 -> 58498 bytes
-rw-r--r--doc/ci/img/environments_available.pngbin8464 -> 20410 bytes
-rw-r--r--doc/ci/img/environments_mr_review_app.pngbin13394 -> 30140 bytes
-rw-r--r--doc/ci/img/metrics_reports.pngbin0 -> 19450 bytes
-rw-r--r--doc/ci/img/multi_pipeline_mini_graph.gifbin0 -> 11466 bytes
-rw-r--r--doc/ci/img/multi_project_pipeline_graph.pngbin0 -> 10671 bytes
-rw-r--r--doc/ci/img/pipelines-goal.pngbin15284 -> 0 bytes
-rw-r--r--doc/ci/img/types-of-pipelines.pngbin12268 -> 0 bytes
-rw-r--r--doc/ci/interactive_web_terminal/index.md9
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example_11_9.pngbin0 -> 71292 bytes
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.pngbin0 -> 139184 bytes
-rw-r--r--doc/ci/introduction/img/job_running.pngbin0 -> 237781 bytes
-rw-r--r--doc/ci/introduction/img/pipeline_status.pngbin0 -> 54243 bytes
-rw-r--r--doc/ci/introduction/img/rollback.pngbin0 -> 41693 bytes
-rw-r--r--doc/ci/introduction/index.md223
-rw-r--r--doc/ci/junit_test_reports.md16
-rw-r--r--doc/ci/large_repositories/index.md242
-rw-r--r--doc/ci/merge_request_pipelines/img/merge_request.pngbin18834 -> 14044 bytes
-rw-r--r--doc/ci/merge_request_pipelines/img/merge_request_pipeline.pngbin0 -> 10152 bytes
-rw-r--r--doc/ci/merge_request_pipelines/img/merge_request_pipeline_config.pngbin0 -> 10889 bytes
-rw-r--r--doc/ci/merge_request_pipelines/img/pipeline_detail.pngbin15561 -> 0 bytes
-rw-r--r--doc/ci/merge_request_pipelines/index.md140
-rw-r--r--doc/ci/metrics_reports.md44
-rw-r--r--doc/ci/multi_project_pipeline_graphs.md5
-rw-r--r--doc/ci/multi_project_pipelines.md189
-rw-r--r--doc/ci/permissions/README.md6
-rw-r--r--doc/ci/pipelines.md431
-rw-r--r--doc/ci/quick_start/README.md26
-rw-r--r--doc/ci/review_apps/index.md150
-rw-r--r--doc/ci/runners/README.md4
-rw-r--r--doc/ci/runners/shared_to_specific_admin.pngbin5715 -> 0 bytes
-rw-r--r--doc/ci/services/README.md13
-rw-r--r--doc/ci/services/mysql.md31
-rw-r--r--doc/ci/services/postgres.md6
-rw-r--r--doc/ci/services/redis.md4
-rw-r--r--doc/ci/ssh_keys/README.md5
-rw-r--r--doc/ci/triggers/README.md77
-rw-r--r--doc/ci/triggers/img/trigger_variables.pngbin10853 -> 10841 bytes
-rw-r--r--doc/ci/variables/README.md807
-rw-r--r--doc/ci/variables/deprecated_variables.md34
-rw-r--r--doc/ci/variables/img/ci_job_stage_output_example.pngbin0 -> 68964 bytes
-rw-r--r--doc/ci/variables/img/custom_variables_output.pngbin0 -> 89185 bytes
-rw-r--r--doc/ci/variables/img/new_custom_variables_example.pngbin0 -> 216497 bytes
-rw-r--r--doc/ci/variables/img/override_value_via_manual_pipeline_output.pngbin0 -> 72420 bytes
-rw-r--r--doc/ci/variables/img/override_variable_manual_pipeline.pngbin0 -> 41714 bytes
-rw-r--r--doc/ci/variables/img/variable_types_usage_example.pngbin0 -> 67003 bytes
-rw-r--r--doc/ci/variables/predefined_variables.md122
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md57
-rw-r--r--doc/ci/yaml/README.md884
-rw-r--r--doc/container_registry/README.md4
-rw-r--r--doc/container_registry/troubleshooting.md4
-rw-r--r--doc/customization/issue_and_merge_request_template.md5
-rw-r--r--doc/customization/system_header_and_footer_messages.md4
-rw-r--r--doc/customization/system_header_and_footer_messages/appearance.pngbin100302 -> 124214 bytes
-rw-r--r--doc/customization/system_header_and_footer_messages/custom_header_footer.pngbin484001 -> 116778 bytes
-rw-r--r--doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.pngbin360832 -> 78678 bytes
-rw-r--r--doc/customization/welcome_message.md4
-rw-r--r--doc/development/README.md16
-rw-r--r--doc/development/api_graphql_styleguide.md151
-rw-r--r--doc/development/architecture.md528
-rw-r--r--doc/development/changelog.md25
-rw-r--r--doc/development/chatops_on_gitlabcom.md21
-rw-r--r--doc/development/code_review.md83
-rw-r--r--doc/development/contributing/index.md17
-rw-r--r--doc/development/contributing/issue_workflow.md57
-rw-r--r--doc/development/contributing/merge_request_workflow.md326
-rw-r--r--doc/development/contributing/style_guides.md3
-rw-r--r--doc/development/database_debugging.md2
-rw-r--r--doc/development/distributed_tracing.md182
-rw-r--r--doc/development/documentation/feature-change-workflow.md14
-rw-r--r--doc/development/documentation/index.md170
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md2
-rw-r--r--doc/development/documentation/structure.md8
-rw-r--r--doc/development/documentation/styleguide.md308
-rw-r--r--doc/development/ee_features.md152
-rw-r--r--doc/development/elasticsearch.md166
-rw-r--r--doc/development/fe_guide/architecture.md7
-rw-r--r--doc/development/fe_guide/event_tracking.md91
-rw-r--r--doc/development/fe_guide/frontend_faq.md41
-rw-r--r--doc/development/fe_guide/graphql.md36
-rw-r--r--doc/development/fe_guide/index.md39
-rw-r--r--doc/development/fe_guide/principles.md15
-rw-r--r--doc/development/fe_guide/style_guide_js.md20
-rw-r--r--doc/development/fe_guide/style_guide_scss.md43
-rw-r--r--doc/development/fe_guide/testing.md6
-rw-r--r--doc/development/fe_guide/vue.md19
-rw-r--r--doc/development/fe_guide/vuex.md12
-rw-r--r--doc/development/feature_flags.md15
-rw-r--r--doc/development/geo.md529
-rw-r--r--doc/development/git_object_deduplication.md261
-rw-r--r--doc/development/gitaly.md134
-rw-r--r--doc/development/gitlab_architecture_diagram.pngbin34253 -> 0 bytes
-rw-r--r--doc/development/go_guide/index.md43
-rw-r--r--doc/development/i18n/externalization.md9
-rw-r--r--doc/development/i18n/merging_translations.md3
-rw-r--r--doc/development/i18n/proofreader.md24
-rw-r--r--doc/development/i18n/translation.md8
-rw-r--r--doc/development/i18n_guide.md6
-rw-r--r--doc/development/img/distributed_tracing_jaeger_ui.pngbin0 -> 1032713 bytes
-rw-r--r--doc/development/img/distributed_tracing_performance_bar.pngbin0 -> 108809 bytes
-rw-r--r--doc/development/import_export.md2
-rw-r--r--doc/development/integrations/jira_connect.md41
-rw-r--r--doc/development/licensed_feature_availability.md37
-rw-r--r--doc/development/licensing.md6
-rw-r--r--doc/development/migration_style_guide.md12
-rw-r--r--doc/development/new_fe_guide/development/components.md2
-rw-r--r--doc/development/new_fe_guide/development/design_patterns.md3
-rw-r--r--doc/development/new_fe_guide/development/index.md12
-rw-r--r--doc/development/new_fe_guide/development/network_requests.md3
-rw-r--r--doc/development/new_fe_guide/development/security.md14
-rw-r--r--doc/development/new_fe_guide/event_tracking.md74
-rw-r--r--doc/development/new_fe_guide/index.md11
-rw-r--r--doc/development/new_fe_guide/initiatives.md3
-rw-r--r--doc/development/new_fe_guide/principles.md35
-rw-r--r--doc/development/new_fe_guide/style/html.md28
-rw-r--r--doc/development/new_fe_guide/style/javascript.md312
-rw-r--r--doc/development/new_fe_guide/tips.md16
-rw-r--r--doc/development/packages.md68
-rw-r--r--doc/development/performance.md1
-rw-r--r--doc/development/profiling.md10
-rw-r--r--doc/development/python_guide/index.md79
-rw-r--r--doc/development/rake_tasks.md47
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md57
-rw-r--r--doc/development/routing.md63
-rw-r--r--doc/development/session.md65
-rw-r--r--doc/development/sql.md15
-rw-r--r--doc/development/testing.md6
-rw-r--r--doc/development/testing_guide/best_practices.md75
-rw-r--r--doc/development/testing_guide/ci.md4
-rw-r--r--doc/development/testing_guide/end_to_end/best_practices.md38
-rw-r--r--doc/development/testing_guide/end_to_end/dynamic_element_validation.md113
-rw-r--r--doc/development/testing_guide/end_to_end/index.md167
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md167
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md585
-rw-r--r--doc/development/testing_guide/end_to_end/resources.md392
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md99
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md95
-rw-r--r--doc/development/testing_guide/flaky_tests.md4
-rw-r--r--doc/development/testing_guide/frontend_testing.md331
-rw-r--r--doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.pngbin0 -> 64862 bytes
-rw-r--r--doc/development/testing_guide/img/review_apps_cicd_architecture.pngbin73240 -> 136431 bytes
-rw-r--r--doc/development/testing_guide/index.md4
-rw-r--r--doc/development/testing_guide/review_apps.md186
-rw-r--r--doc/development/testing_guide/smoke.md14
-rw-r--r--doc/development/testing_guide/testing_levels.md39
-rw-r--r--doc/development/understanding_explain_plans.md47
-rw-r--r--doc/development/ux_guide/img/animation-autoscroll.gifbin302217 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/animation-dropdown.gifbin22483 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/animation-hover.gifbin247388 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/animation-quickupdate.gifbin6441 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/animation-reorder.gifbin70515 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-close--active.pngbin1120 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-close--hover.pngbin765 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-close--resting.pngbin915 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-danger--active.pngbin1117 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-danger--hover.pngbin797 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-danger--resting.pngbin932 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-info--active.pngbin1109 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-info--hover.pngbin795 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-info--resting.pngbin930 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-primary.pngbin1550 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-secondary.pngbin2683 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-spam--active.pngbin1107 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-spam--hover.pngbin777 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-spam--resting.pngbin941 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success--active.pngbin1153 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success--hover.pngbin892 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success--resting.pngbin1045 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success-secondary--active.pngbin1107 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success-secondary--hover.pngbin774 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-success-secondary--resting.pngbin945 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-warning--active.pngbin1124 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-warning--hover.pngbin790 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/button-warning--resting.pngbin925 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-blue.pngbin1751 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-green.pngbin1916 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-grey.pngbin1681 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-orange.pngbin2094 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-red.pngbin1739 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-textprimary.pngbin2553 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/color-textsecondary.pngbin2956 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-alerts.pngbin27342 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-anchorlinks.pngbin16457 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-contentblock.pngbin14190 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-counts.pngbin2438 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-coverblock.pngbin10110 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-dateexact.pngbin4157 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-daterelative.pngbin4189 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-dropdown.pngbin31760 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-fileholder.pngbin3884 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-horizontalform.pngbin4310 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-listinsidepanel.pngbin3380 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-listwithavatar.pngbin5749 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-listwithhover.pngbin2722 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-panels.pngbin21822 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referencehover.pngbin6890 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referenceissues.pngbin10002 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referencelabels.pngbin4099 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referencemilestone.pngbin2412 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referencemrs.pngbin8857 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-referencepeople.pngbin5600 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-rowcontentblock.pngbin14315 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-searchbox.pngbin3596 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-searchboxscoped.pngbin6027 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-simplelist.pngbin2776 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-table.pngbin6081 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/components-verticalform.pngbin4964 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-default.pngbin567 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-ibeam.pngbin383 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-move.pngbin276 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-panclosed.pngbin483 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-panopened.pngbin622 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/cursors-pointer.pngbin574 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/features-contextualnav.pngbin5911 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/features-emptystates.pngbin61644 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/features-filters.pngbin3924 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/features-globalnav.pngbin5780 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/harry-robison.pngbin10712 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-add.pngbin239 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-close.pngbin301 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-edit.pngbin315 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-notification.pngbin379 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-rss.pngbin528 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-spec.pngbin7523 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-subscribe.pngbin537 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/icon-trash.pngbin281 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustration-size-large-horizontal.pngbin55272 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustration-size-large-vertical.pngbin59217 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustration-size-medium.pngbin20994 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustration-size-small.pngbin43536 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-border-radius.pngbin7779 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-caps-do.pngbin3671 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-caps-don't.pngbin3624 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-color-grey.pngbin251 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-color-orange.pngbin275 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-color-purple.pngbin275 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-geometric.pngbin5057 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-palette-oragne.pngbin10439 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/illustrations-palette-purple.pngbin10002 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/james-mackey.pngbin11869 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/karolina-plaskaty.pngbin10355 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/matthieu-poirier.pngbin11483 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/modals-general-confimation-dialog.pngbin15650 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/modals-layout-for-modals.pngbin20060 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/modals-special-confimation-dialog.pngbin31419 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/modals-three-buttons.pngbin17457 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/monospacefont-sample.pngbin14282 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/nazim-ramesh.pngbin10488 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/popover-placement-above.pngbin20184 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/popover-placement-below.pngbin19967 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/skeleton-loading.gifbin1093917 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/sourcesanspro-sample.pngbin10948 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/steven-lyons.pngbin9323 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/surfaces-contentitemtitle.pngbin5139 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/surfaces-header.pngbin4095 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/surfaces-systeminformationblock.pngbin10422 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/surfaces-ux.pngbin4017 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/tooltip-placement.pngbin2066 -> 0 bytes
-rw-r--r--doc/development/ux_guide/img/tooltip-usage.pngbin5985 -> 0 bytes
-rw-r--r--doc/development/ux_guide/users.md4
-rw-r--r--doc/getting-started/subscription.md3
-rw-r--r--doc/git_hooks/git_hooks.md5
-rw-r--r--doc/gitlab-basics/README.md48
-rw-r--r--doc/gitlab-basics/add-merge-request.md2
-rw-r--r--doc/gitlab-basics/command-line-commands.md4
-rw-r--r--doc/gitlab-basics/create-group.md3
-rw-r--r--doc/gitlab-basics/create-issue.md5
-rw-r--r--doc/gitlab-basics/create-project.md11
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md42
-rw-r--r--doc/gitlab-basics/fork-project.md6
-rw-r--r--doc/gitlab-basics/img/new_issue_button.pngbin2010 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/new_issue_page.pngbin21386 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings.pngbin2842 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys.pngbin16531 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.pngbin13436 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.pngbin10534 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_title.pngbin1867 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/public_file_link.pngbin3023 -> 0 bytes
-rw-r--r--doc/gitlab-basics/start-using-git.md24
-rw-r--r--doc/gitlab-geo/README.md5
-rw-r--r--doc/gitlab-geo/after_setup.md5
-rw-r--r--doc/gitlab-geo/bring-primary-back.md5
-rw-r--r--doc/gitlab-geo/configuration.md5
-rw-r--r--doc/gitlab-geo/configuration_source.md5
-rw-r--r--doc/gitlab-geo/database.md5
-rw-r--r--doc/gitlab-geo/database_source.md5
-rw-r--r--doc/gitlab-geo/disaster-recovery.md5
-rw-r--r--doc/gitlab-geo/docker_registry.md5
-rw-r--r--doc/gitlab-geo/faq.md5
-rw-r--r--doc/gitlab-geo/ha.md5
-rw-r--r--doc/gitlab-geo/object_storage.md5
-rw-r--r--doc/gitlab-geo/planned-failover.md5
-rw-r--r--doc/gitlab-geo/security-review.md5
-rw-r--r--doc/gitlab-geo/ssh.md5
-rw-r--r--doc/gitlab-geo/troubleshooting.md5
-rw-r--r--doc/gitlab-geo/tuning.md5
-rw-r--r--doc/gitlab-geo/updating_the_geo_nodes.md5
-rw-r--r--doc/gitlab-geo/using_a_geo_server.md5
-rw-r--r--doc/hooks/custom_hooks.md4
-rw-r--r--doc/img/devops-stages.pngbin10666 -> 10654 bytes
-rw-r--r--doc/incoming_email/README.md4
-rw-r--r--doc/incoming_email/postfix.md4
-rw-r--r--doc/install/README.md7
-rw-r--r--doc/install/aws/img/add_tags.pngbin17834 -> 0 bytes
-rw-r--r--doc/install/aws/img/create_route_table.pngbin8293 -> 0 bytes
-rw-r--r--doc/install/aws/index.md36
-rw-r--r--doc/install/azure/img/azure-vm-management-settings-network-interfaces.pngbin35849 -> 0 bytes
-rw-r--r--doc/install/azure/img/azure-vm-management.pngbin35363 -> 0 bytes
-rw-r--r--doc/install/azure/index.md20
-rw-r--r--doc/install/database_mysql.md16
-rw-r--r--doc/install/digitaloceandocker.md41
-rw-r--r--doc/install/docker.md8
-rw-r--r--doc/install/google-protobuf.md29
-rw-r--r--doc/install/google_cloud_platform/img/gcp_landing.pngbin9978 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/index.md26
-rw-r--r--doc/install/installation.md221
-rw-r--r--doc/install/kubernetes/gitlab_chart.md159
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md249
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md272
-rw-r--r--doc/install/kubernetes/index.md41
-rw-r--r--doc/install/kubernetes/preparation/connect.md30
-rw-r--r--doc/install/kubernetes/preparation/eks.md48
-rw-r--r--doc/install/kubernetes/preparation/networking.md41
-rw-r--r--doc/install/kubernetes/preparation/rbac.md23
-rw-r--r--doc/install/kubernetes/preparation/tiller.md110
-rw-r--r--doc/install/kubernetes/preparation/tools_installation.md22
-rw-r--r--doc/install/ldap.md5
-rw-r--r--doc/install/openshift_and_gitlab/img/pods-overview.pngbin42617 -> 0 bytes
-rw-r--r--doc/install/openshift_and_gitlab/img/storage-volumes.pngbin20007 -> 0 bytes
-rw-r--r--doc/install/openshift_and_gitlab/index.md7
-rw-r--r--doc/install/pivotal/index.md12
-rw-r--r--doc/install/redis.md63
-rw-r--r--doc/install/relative_url.md29
-rw-r--r--doc/install/requirements.md62
-rw-r--r--doc/install/structure.md22
-rw-r--r--doc/integration/README.md6
-rw-r--r--doc/integration/chat_commands.md4
-rw-r--r--doc/integration/crowd.md4
-rw-r--r--doc/integration/elasticsearch.md520
-rw-r--r--doc/integration/external-issue-tracker.md18
-rw-r--r--doc/integration/github.md6
-rw-r--r--doc/integration/img/github_app_entry.pngbin26765 -> 26749 bytes
-rw-r--r--doc/integration/img/github_register_app.pngbin40228 -> 40225 bytes
-rw-r--r--doc/integration/img/google_app.pngbin18853 -> 0 bytes
-rw-r--r--doc/integration/img/jenkins_gitlab_plugin_config.pngbin0 -> 10054 bytes
-rw-r--r--doc/integration/img/jenkins_gitlab_service.pngbin0 -> 19235 bytes
-rw-r--r--doc/integration/img/jenkins_gitlab_service_settings.pngbin0 -> 24094 bytes
-rw-r--r--doc/integration/img/jenkins_project.pngbin0 -> 42275 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_gl_setup_1.pngbin0 -> 36145 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_jira_setup_1.pngbin0 -> 29546 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_jira_setup_2.pngbin0 -> 17374 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_jira_setup_3.pngbin0 -> 37257 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_jira_setup_4.pngbin0 -> 21592 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_jira_setup_5.pngbin0 -> 11002 bytes
-rw-r--r--doc/integration/img/jira_dev_panel_manual_refresh.pngbin0 -> 23542 bytes
-rw-r--r--doc/integration/img/limit_namespace_filter.pngbin0 -> 12777 bytes
-rw-r--r--doc/integration/img/limit_namespaces_projects_options.pngbin0 -> 22650 bytes
-rw-r--r--doc/integration/img/salesforce_app_details.pngbin0 -> 116022 bytes
-rw-r--r--doc/integration/img/salesforce_app_secret_details.pngbin0 -> 199643 bytes
-rw-r--r--doc/integration/img/salesforce_oauth_app_details.pngbin0 -> 171542 bytes
-rw-r--r--doc/integration/img/ultra_auth_credentials.pngbin0 -> 53737 bytes
-rw-r--r--doc/integration/img/ultra_auth_edit_callback_url.pngbin0 -> 39410 bytes
-rw-r--r--doc/integration/img/ultra_auth_edit_callback_url_highlighted.pngbin0 -> 68383 bytes
-rw-r--r--doc/integration/jenkins.md129
-rw-r--r--doc/integration/jenkins_deprecated.md53
-rw-r--r--doc/integration/jira.md4
-rw-r--r--doc/integration/jira_development_panel.md141
-rw-r--r--doc/integration/kerberos.md315
-rw-r--r--doc/integration/ldap.md4
-rw-r--r--doc/integration/omniauth.md7
-rw-r--r--doc/integration/salesforce.md79
-rw-r--r--doc/integration/saml.md330
-rw-r--r--doc/integration/slack.md4
-rw-r--r--doc/integration/slash_commands.md4
-rw-r--r--doc/integration/ultra_auth.md78
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md32
-rw-r--r--doc/legal/individual_contributor_license_agreement.md28
-rw-r--r--doc/license/README.md5
-rw-r--r--doc/logs/logs.md4
-rw-r--r--doc/markdown/markdown.md6
-rw-r--r--doc/monitoring/health_check.md4
-rw-r--r--doc/monitoring/performance/gitlab_configuration.md4
-rw-r--r--doc/monitoring/performance/grafana_configuration.md4
-rw-r--r--doc/monitoring/performance/img/grafana_dashboard_dropdown.pngbin7761 -> 0 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_dashboard_import.pngbin11835 -> 0 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_data_source_configuration.pngbin14695 -> 0 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_data_source_empty.pngbin11960 -> 0 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_save_icon.pngbin4598 -> 0 bytes
-rw-r--r--doc/monitoring/performance/img/metrics_gitlab_configuration_settings.pngbin21382 -> 0 bytes
-rw-r--r--doc/monitoring/performance/influxdb_configuration.md4
-rw-r--r--doc/monitoring/performance/influxdb_schema.md4
-rw-r--r--doc/monitoring/performance/introduction.md4
-rw-r--r--doc/operations/README.md4
-rw-r--r--doc/operations/cleaning_up_redis_sessions.md6
-rw-r--r--doc/operations/moving_repositories.md6
-rw-r--r--doc/operations/sidekiq_memory_killer.md6
-rw-r--r--doc/operations/unicorn.md6
-rw-r--r--doc/pages/README.md6
-rw-r--r--doc/pages/administration.md6
-rw-r--r--doc/pages/getting_started_part_one.md4
-rw-r--r--doc/pages/getting_started_part_three.md4
-rw-r--r--doc/pages/getting_started_part_two.md4
-rw-r--r--doc/permissions/permissions.md4
-rw-r--r--doc/profile/README.md4
-rw-r--r--doc/profile/preferences.md4
-rw-r--r--doc/profile/two_factor_authentication.md4
-rw-r--r--doc/project_services/bamboo.md4
-rw-r--r--doc/project_services/bugzilla.md4
-rw-r--r--doc/project_services/emails_on_push.md4
-rw-r--r--doc/project_services/hipchat.md1
-rw-r--r--doc/project_services/irker.md4
-rw-r--r--doc/project_services/jira.md4
-rw-r--r--doc/project_services/kubernetes.md4
-rw-r--r--doc/project_services/mattermost.md4
-rw-r--r--doc/project_services/mattermost_slash_commands.md4
-rw-r--r--doc/project_services/project_services.md4
-rw-r--r--doc/project_services/redmine.md4
-rw-r--r--doc/project_services/services_templates.md4
-rw-r--r--doc/project_services/slack.md4
-rw-r--r--doc/project_services/slack_slash_commands.md4
-rw-r--r--doc/push_rules/push_rules.md156
-rw-r--r--doc/raketasks/README.md1
-rw-r--r--doc/raketasks/backup_restore.md83
-rw-r--r--doc/raketasks/check.md6
-rw-r--r--doc/raketasks/cleanup.md10
-rw-r--r--doc/raketasks/import.md2
-rw-r--r--doc/raketasks/maintenance.md6
-rw-r--r--doc/security/img/two_factor_authentication_group_settings.pngbin19495 -> 0 bytes
-rw-r--r--doc/security/img/two_factor_authentication_settings.pngbin9936 -> 0 bytes
-rw-r--r--doc/security/rack_attack.md2
-rw-r--r--doc/security/two_factor_authentication.md35
-rw-r--r--doc/ssh/README.md19
-rw-r--r--doc/subscriptions/billing_table.pngbin0 -> 28321 bytes
-rw-r--r--doc/subscriptions/index.md103
-rw-r--r--doc/tools/email.md38
-rw-r--r--doc/tools/email1.pngbin0 -> 9590 bytes
-rw-r--r--doc/tools/email2.pngbin0 -> 14902 bytes
-rw-r--r--doc/topics/authentication/index.md10
-rw-r--r--doc/topics/autodevops/img/autodevops_domain_variables.pngbin8456 -> 0 bytes
-rw-r--r--doc/topics/autodevops/img/guide_connect_cluster.pngbin15225 -> 0 bytes
-rw-r--r--doc/topics/autodevops/img/guide_create_cluster.pngbin18915 -> 0 bytes
-rw-r--r--doc/topics/autodevops/img/guide_gke_apis_after.pngbin26811 -> 0 bytes
-rw-r--r--doc/topics/autodevops/img/guide_gke_apis_before.pngbin14882 -> 0 bytes
-rw-r--r--doc/topics/autodevops/img/guide_merge_request_ide.pngbin35052 -> 0 bytes
-rw-r--r--doc/topics/autodevops/index.md228
-rw-r--r--doc/topics/autodevops/quick_start_guide.md2
-rw-r--r--doc/topics/git/how_to_install_git/index.md29
-rw-r--r--doc/topics/git/index.md59
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md121
-rw-r--r--doc/topics/git/troubleshooting_git.md48
-rw-r--r--doc/university/README.md27
-rw-r--r--doc/university/glossary/README.md58
-rw-r--r--doc/university/high-availability/aws/README.md10
-rw-r--r--doc/university/high-availability/aws/img/elastic-file-system.pngbin34582 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/reference-arch2.pngbin53523 -> 53510 bytes
-rw-r--r--doc/university/support/README.md31
-rw-r--r--doc/university/training/topics/unstage.md4
-rw-r--r--doc/update/10.0-ce-to-ee.md5
-rw-r--r--doc/update/10.1-ce-to-ee.md5
-rw-r--r--doc/update/10.2-ce-to-ee.md5
-rw-r--r--doc/update/10.3-ce-to-ee.md5
-rw-r--r--doc/update/10.4-ce-to-ee.md5
-rw-r--r--doc/update/10.5-ce-to-ee.md5
-rw-r--r--doc/update/10.6-ce-to-ee.md5
-rw-r--r--doc/update/10.7-ce-to-ee.md5
-rw-r--r--doc/update/10.8-ce-to-ee.md5
-rw-r--r--doc/update/11.0-ce-to-ee.md5
-rw-r--r--doc/update/11.1-ce-to-ee.md5
-rw-r--r--doc/update/11.2-ce-to-ee.md5
-rw-r--r--doc/update/11.3-ce-to-ee.md5
-rw-r--r--doc/update/11.4-ce-to-ee.md5
-rw-r--r--doc/update/11.5-ce-to-ee.md5
-rw-r--r--doc/update/11.6-ce-to-ee.md5
-rw-r--r--doc/update/11.7-ce-to-ee.md5
-rw-r--r--doc/update/11.8-ce-to-ee.md5
-rw-r--r--doc/update/6.0-ce-to-ee.md5
-rw-r--r--doc/update/6.1-ce-to-ee.md5
-rw-r--r--doc/update/6.2-ce-to-ee.md5
-rw-r--r--doc/update/6.3-ce-to-ee.md5
-rw-r--r--doc/update/6.4-ce-to-ee.md5
-rw-r--r--doc/update/6.5-ce-to-ee.md5
-rw-r--r--doc/update/6.6-ce-to-ee.md5
-rw-r--r--doc/update/6.7-ce-to-ee.md5
-rw-r--r--doc/update/6.8-ce-to-ee.md5
-rw-r--r--doc/update/6.9-ce-to-ee.md5
-rw-r--r--doc/update/7.0-ce-to-ee.md5
-rw-r--r--doc/update/7.1-ce-to-ee.md5
-rw-r--r--doc/update/7.10-ce-to-ee.md5
-rw-r--r--doc/update/7.11-ce-to-ee.md5
-rw-r--r--doc/update/7.12-ce-to-ee.md5
-rw-r--r--doc/update/7.13-ce-to-ee.md5
-rw-r--r--doc/update/7.14-ce-to-ee.md5
-rw-r--r--doc/update/7.3-ce-to-ee.md5
-rw-r--r--doc/update/7.4-ce-to-ee.md5
-rw-r--r--doc/update/7.5-ce-to-ee.md5
-rw-r--r--doc/update/7.6-ce-to-ee.md5
-rw-r--r--doc/update/7.7-ce-to-ee.md5
-rw-r--r--doc/update/7.8-ce-to-ee.md5
-rw-r--r--doc/update/7.9-ce-to-ee.md5
-rw-r--r--doc/update/8.0-ce-to-ee.md5
-rw-r--r--doc/update/8.1-ce-to-ee.md5
-rw-r--r--doc/update/8.10-ce-to-ee.md5
-rw-r--r--doc/update/8.11-ce-to-ee.md5
-rw-r--r--doc/update/8.12-ce-to-ee.md5
-rw-r--r--doc/update/8.13-ce-to-ee.md5
-rw-r--r--doc/update/8.14-ce-to-ee.md5
-rw-r--r--doc/update/8.15-ce-to-ee.md5
-rw-r--r--doc/update/8.16-ce-to-ee.md5
-rw-r--r--doc/update/8.17-ce-to-ee.md5
-rw-r--r--doc/update/8.2-ce-to-ee.md5
-rw-r--r--doc/update/8.3-ce-to-ee.md5
-rw-r--r--doc/update/8.4-ce-to-ee.md5
-rw-r--r--doc/update/8.5-ce-to-ee.md5
-rw-r--r--doc/update/8.6-ce-to-ee.md5
-rw-r--r--doc/update/8.7-ce-to-ee.md5
-rw-r--r--doc/update/8.8-ce-to-ee.md5
-rw-r--r--doc/update/8.9-ce-to-ee.md5
-rw-r--r--doc/update/9.0-ce-to-ee.md5
-rw-r--r--doc/update/9.1-ce-to-ee.md5
-rw-r--r--doc/update/9.2-ce-to-ee.md5
-rw-r--r--doc/update/9.3-ce-to-ee.md5
-rw-r--r--doc/update/9.4-ce-to-ee.md5
-rw-r--r--doc/update/9.5-ce-to-ee.md5
-rw-r--r--doc/update/mysql_to_postgresql.md302
-rw-r--r--doc/update/patch_versions.md12
-rw-r--r--doc/update/restore_after_failure.md2
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md7
-rw-r--r--doc/update/upgrading_from_source.md89
-rw-r--r--doc/user/account/security.md4
-rw-r--r--doc/user/account/two_factor_authentication.md4
-rw-r--r--doc/user/admin_area/diff_limits.md41
-rw-r--r--doc/user/admin_area/geo_nodes.md86
-rw-r--r--doc/user/admin_area/img/admin_wrench.pngbin0 -> 3314 bytes
-rw-r--r--doc/user/admin_area/img/index_runners_search_or_filter.pngbin0 -> 22414 bytes
-rw-r--r--doc/user/admin_area/img/license_admin_area.pngbin0 -> 27826 bytes
-rw-r--r--doc/user/admin_area/img/license_details.pngbin0 -> 119290 bytes
-rw-r--r--doc/user/admin_area/img/license_upload.pngbin0 -> 10043 bytes
-rw-r--r--doc/user/admin_area/index.md205
-rw-r--r--doc/user/admin_area/labels.md22
-rw-r--r--doc/user/admin_area/license.md126
-rw-r--r--doc/user/admin_area/monitoring/health_check.md57
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md62
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md110
-rw-r--r--doc/user/admin_area/settings/email.md40
-rw-r--r--doc/user/admin_area/settings/external_authorization.md128
-rw-r--r--doc/user/admin_area/settings/img/additional_minutes.pngbin0 -> 32045 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.pngbin5360 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.pngbin4771 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_project_quota_view.pngbin0 -> 2670 bytes
-rw-r--r--doc/user/admin_area/settings/img/buy_btn.pngbin0 -> 26960 bytes
-rw-r--r--doc/user/admin_area/settings/img/buy_minutes_card.pngbin0 -> 29329 bytes
-rw-r--r--doc/user/admin_area/settings/img/classification_label_on_project_page.pngbin0 -> 19568 bytes
-rw-r--r--doc/user/admin_area/settings/img/email_settings.pngbin0 -> 53267 bytes
-rw-r--r--doc/user/admin_area/settings/img/external_authorization_service_settings.pngbin0 -> 74753 bytes
-rw-r--r--doc/user/admin_area/settings/img/file_template_admin_area.pngbin0 -> 5624 bytes
-rw-r--r--doc/user/admin_area/settings/img/file_template_user_dropdown.pngbin0 -> 8067 bytes
-rw-r--r--doc/user/admin_area/settings/img/group_pipelines_quota.pngbin0 -> 7088 bytes
-rw-r--r--doc/user/admin_area/settings/img/mirror_settings.pngbin0 -> 9966 bytes
-rw-r--r--doc/user/admin_area/settings/index.md2
-rw-r--r--doc/user/admin_area/settings/instance_template_repository.md79
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md16
-rw-r--r--doc/user/admin_area/settings/terms.md46
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md23
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md20
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md10
-rw-r--r--doc/user/application_security/container_scanning/img/container_scanning.pngbin0 -> 32549 bytes
-rw-r--r--doc/user/application_security/container_scanning/index.md204
-rw-r--r--doc/user/application_security/dast/img/dast_all.pngbin0 -> 25844 bytes
-rw-r--r--doc/user/application_security/dast/img/dast_single.pngbin0 -> 69353 bytes
-rw-r--r--doc/user/application_security/dast/index.md261
-rw-r--r--doc/user/application_security/dependency_scanning/img/dependency_scanning.pngbin0 -> 16167 bytes
-rw-r--r--doc/user/application_security/dependency_scanning/index.md243
-rw-r--r--doc/user/application_security/img/create_issue_with_list_hover.pngbin0 -> 106954 bytes
-rw-r--r--doc/user/application_security/img/dismissed_info.pngbin0 -> 20244 bytes
-rw-r--r--doc/user/application_security/img/interactive_reports.pngbin0 -> 93910 bytes
-rw-r--r--doc/user/application_security/img/issue.pngbin0 -> 4780 bytes
-rw-r--r--doc/user/application_security/img/vulnerability_solution.pngbin0 -> 3421 bytes
-rw-r--r--doc/user/application_security/index.md114
-rw-r--r--doc/user/application_security/license_management/img/license_management.pngbin0 -> 5184 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_decision.pngbin0 -> 5981 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_pipeline_tab.pngbin0 -> 12115 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_settings.pngbin0 -> 13300 bytes
-rw-r--r--doc/user/application_security/license_management/index.md264
-rw-r--r--doc/user/application_security/sast/img/sast.pngbin0 -> 24876 bytes
-rw-r--r--doc/user/application_security/sast/img/security_report.pngbin0 -> 38475 bytes
-rw-r--r--doc/user/application_security/sast/index.md254
-rw-r--r--doc/user/application_security/security_dashboard/img/dashboard.pngbin0 -> 58585 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin0 -> 126356 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md104
-rw-r--r--doc/user/clusters/applications.md290
-rw-r--r--doc/user/clusters/img/jupyter-git-extension.gifbin0 -> 2120084 bytes
-rw-r--r--doc/user/clusters/img/jupyter-gitclone.pngbin0 -> 64120 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve.pngbin0 -> 21941 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve2.pngbin0 -> 9430 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment.pngbin0 -> 11188 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment_added.pngbin0 -> 9673 bytes
-rw-r--r--doc/user/discussions/img/mr_review_start.pngbin0 -> 23491 bytes
-rw-r--r--doc/user/discussions/img/mr_review_unresolve.pngbin0 -> 11302 bytes
-rw-r--r--doc/user/discussions/img/mr_review_unresolve2.pngbin0 -> 8976 bytes
-rw-r--r--doc/user/discussions/img/multi-line-suggestion-preview.pngbin0 -> 61692 bytes
-rw-r--r--doc/user/discussions/img/multi-line-suggestion-syntax.pngbin0 -> 29753 bytes
-rw-r--r--doc/user/discussions/img/pending_review_comment.pngbin0 -> 8793 bytes
-rw-r--r--doc/user/discussions/img/reply_to_comment.gifbin0 -> 508115 bytes
-rw-r--r--doc/user/discussions/img/reply_to_comment_button.pngbin0 -> 17224 bytes
-rw-r--r--doc/user/discussions/img/review_comment_quickactions.pngbin0 -> 12392 bytes
-rw-r--r--doc/user/discussions/img/review_preview.pngbin0 -> 19769 bytes
-rw-r--r--doc/user/discussions/index.md161
-rw-r--r--doc/user/gitlab_com/index.md10
-rw-r--r--doc/user/group/clusters/index.md79
-rw-r--r--doc/user/group/contribution_analytics/img/group_stats_cal.pngbin0 -> 2029 bytes
-rw-r--r--doc/user/group/contribution_analytics/img/group_stats_graph.pngbin0 -> 35400 bytes
-rw-r--r--doc/user/group/contribution_analytics/img/group_stats_table.pngbin0 -> 8473 bytes
-rw-r--r--doc/user/group/contribution_analytics/index.md77
-rw-r--r--doc/user/group/custom_project_templates.md18
-rw-r--r--doc/user/group/dependency_proxy/img/group_dependency_proxy.pngbin0 -> 40162 bytes
-rw-r--r--doc/user/group/dependency_proxy/index.md74
-rw-r--r--doc/user/group/epics/img/button_close_epic.pngbin0 -> 13850 bytes
-rw-r--r--doc/user/group/epics/img/button_reopen_epic.pngbin0 -> 14153 bytes
-rw-r--r--doc/user/group/epics/img/child_epics_roadmap.pngbin0 -> 30149 bytes
-rw-r--r--doc/user/group/epics/img/containing_epic.pngbin0 -> 159939 bytes
-rw-r--r--doc/user/group/epics/img/epic_view.pngbin0 -> 176759 bytes
-rw-r--r--doc/user/group/epics/img/epics_list_view.pngbin0 -> 96826 bytes
-rw-r--r--doc/user/group/epics/img/epics_search.pngbin0 -> 65963 bytes
-rw-r--r--doc/user/group/epics/img/epics_sort.pngbin0 -> 71177 bytes
-rw-r--r--doc/user/group/epics/index.md233
-rw-r--r--doc/user/group/img/create_new_group_info.pngbin51072 -> 27101 bytes
-rw-r--r--doc/user/group/img/group_file_template_dropdown.pngbin0 -> 9519 bytes
-rw-r--r--doc/user/group/img/group_file_template_settings.pngbin0 -> 6217 bytes
-rw-r--r--doc/user/group/img/member_lock.pngbin0 -> 3168 bytes
-rw-r--r--doc/user/group/img/membership_lock.pngbin4820 -> 0 bytes
-rw-r--r--doc/user/group/img/new_group_form.pngbin32510 -> 0 bytes
-rw-r--r--doc/user/group/index.md307
-rw-r--r--doc/user/group/insights/img/insights_example_stacked_bar_chart.pngbin0 -> 86062 bytes
-rw-r--r--doc/user/group/insights/img/insights_group_configuration.pngbin0 -> 24107 bytes
-rw-r--r--doc/user/group/insights/img/insights_sidebar_link.pngbin0 -> 18826 bytes
-rw-r--r--doc/user/group/insights/index.md62
-rw-r--r--doc/user/group/issues_analytics/img/issues_created_per_month.pngbin0 -> 28508 bytes
-rw-r--r--doc/user/group/issues_analytics/index.md43
-rw-r--r--doc/user/group/roadmap/img/epics_state_dropdown.pngbin0 -> 3702 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_timeline_months.pngbin0 -> 7551 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_timeline_quarters.pngbin0 -> 7493 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_timeline_weeks.pngbin0 -> 8008 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_view.pngbin0 -> 49774 bytes
-rw-r--r--doc/user/group/roadmap/index.md87
-rw-r--r--doc/user/group/saml_sso/img/group_saml_configuration_information.pngbin0 -> 50435 bytes
-rw-r--r--doc/user/group/saml_sso/img/group_saml_settings.pngbin0 -> 89399 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_advanced.pngbin0 -> 21568 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_attribute_mapping.pngbin0 -> 95420 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_token.pngbin0 -> 154318 bytes
-rw-r--r--doc/user/group/saml_sso/img/unlink_group_saml.pngbin0 -> 27077 bytes
-rw-r--r--doc/user/group/saml_sso/index.md140
-rw-r--r--doc/user/group/saml_sso/scim_setup.md102
-rw-r--r--doc/user/group/security_dashboard/index.md5
-rw-r--r--doc/user/group/subgroups/index.md14
-rw-r--r--doc/user/img/color_inline_colorchip_render_gfm.pngbin4724 -> 4684 bytes
-rw-r--r--doc/user/img/mermaid_diagram_render_gfm.pngbin2202 -> 0 bytes
-rw-r--r--doc/user/img/task_list_ordered_render_gfm.pngbin2860 -> 2855 bytes
-rw-r--r--doc/user/img/unordered_check_list_render_gfm.pngbin2789 -> 2781 bytes
-rw-r--r--doc/user/index.md66
-rw-r--r--doc/user/instance/clusters/index.md23
-rw-r--r--doc/user/instance_statistics/convdev.md23
-rw-r--r--doc/user/markdown.md73
-rw-r--r--doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.pngbin0 -> 3948 bytes
-rw-r--r--doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.pngbin0 -> 30837 bytes
-rw-r--r--doc/user/operations_dashboard/index.md37
-rw-r--r--doc/user/permissions.md277
-rw-r--r--doc/user/profile/account/delete_account.md2
-rw-r--r--doc/user/profile/account/index.md3
-rw-r--r--doc/user/profile/account/two_factor_authentication.md22
-rw-r--r--doc/user/profile/active_sessions.md8
-rw-r--r--doc/user/profile/img/active_sessions_list.pngbin22266 -> 19360 bytes
-rw-r--r--doc/user/profile/img/personal_access_tokens.pngbin18553 -> 0 bytes
-rw-r--r--doc/user/profile/index.md13
-rw-r--r--doc/user/profile/personal_access_tokens.md41
-rw-r--r--doc/user/profile/preferences.md9
-rw-r--r--doc/user/project/badges.md6
-rw-r--r--doc/user/project/builds/artifacts.md4
-rw-r--r--doc/user/project/bulk_editing.md1
-rw-r--r--doc/user/project/canary_deployments.md71
-rw-r--r--doc/user/project/ci_cd_for_external_repo.md5
-rw-r--r--doc/user/project/clusters/img/k8s_cluster_monitoring.pngbin0 -> 43150 bytes
-rw-r--r--doc/user/project/clusters/img/kubernetes_pod_logs.pngbin0 -> 147319 bytes
-rw-r--r--doc/user/project/clusters/img/pod_logs_deploy_board.pngbin0 -> 13291 bytes
-rw-r--r--doc/user/project/clusters/index.md449
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md21
-rw-r--r--doc/user/project/clusters/runbooks/img/authorize-jupyter.pngbin35652 -> 35627 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/gitlab-variables.pngbin54167 -> 54154 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/helm-install.pngbin71741 -> 71705 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/ingress-install.pngbin44380 -> 44363 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/jupyterhub-install.pngbin41655 -> 41588 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/postgres-query.pngbin63480 -> 63416 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/sample-runbook.pngbin40947 -> 40941 bytes
-rw-r--r--doc/user/project/clusters/runbooks/index.md18
-rw-r--r--doc/user/project/clusters/serverless/img/function-details-loaded.pngbin0 -> 93515 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md183
-rw-r--r--doc/user/project/code_owners.md85
-rw-r--r--doc/user/project/container_registry.md10
-rw-r--r--doc/user/project/cycle_analytics.md37
-rw-r--r--doc/user/project/deploy_boards.md132
-rw-r--r--doc/user/project/deploy_tokens/index.md2
-rw-r--r--doc/user/project/description_templates.md49
-rw-r--r--doc/user/project/file_lock.md101
-rw-r--r--doc/user/project/gpg_signed_commits/index.md4
-rw-r--r--doc/user/project/img/cycle_analytics_landing_page.pngbin42114 -> 184131 bytes
-rw-r--r--doc/user/project/img/deploy_boards_canary_deployments.pngbin0 -> 17380 bytes
-rw-r--r--doc/user/project/img/deploy_boards_kubernetes_label.pngbin0 -> 43978 bytes
-rw-r--r--doc/user/project/img/deploy_boards_landing_page.pngbin0 -> 16107 bytes
-rw-r--r--doc/user/project/img/description_templates_default_settings.pngbin0 -> 26395 bytes
-rw-r--r--doc/user/project/img/file_lock.pngbin0 -> 26973 bytes
-rw-r--r--doc/user/project/img/file_lock_folders.pngbin0 -> 22900 bytes
-rw-r--r--doc/user/project/img/file_lock_list.pngbin0 -> 18243 bytes
-rw-r--r--doc/user/project/img/file_lock_merge_request_error_message.pngbin0 -> 24573 bytes
-rw-r--r--doc/user/project/img/file_lock_repository_view.pngbin0 -> 22947 bytes
-rw-r--r--doc/user/project/img/issue_board.pngbin284759 -> 0 bytes
-rw-r--r--doc/user/project/img/issue_boards_multiple.pngbin22623 -> 68373 bytes
-rw-r--r--doc/user/project/img/key_value_labels.pngbin0 -> 6208 bytes
-rw-r--r--doc/user/project/img/labels_default.pngbin22975 -> 35620 bytes
-rw-r--r--doc/user/project/img/labels_epic_sidebar.pngbin0 -> 47869 bytes
-rw-r--r--doc/user/project/img/labels_list.pngbin71323 -> 75215 bytes
-rw-r--r--doc/user/project/img/labels_sidebar_inline.pngbin11083 -> 0 bytes
-rw-r--r--doc/user/project/img/priority_sort_order.pngbin69978 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_choose_branch.pngbin7009 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_error_ui.pngbin13117 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_select_roles_and_users.pngbin0 -> 7155 bytes
-rw-r--r--doc/user/project/img/protected_branches_select_roles_and_users_list.pngbin0 -> 7408 bytes
-rw-r--r--doc/user/project/img/service_desk_confirmation_email.pngbin0 -> 24165 bytes
-rw-r--r--doc/user/project/img/service_desk_disabled.pngbin0 -> 18654 bytes
-rw-r--r--doc/user/project/img/service_desk_enabled.pngbin0 -> 33243 bytes
-rw-r--r--doc/user/project/img/service_desk_issue_tracker.pngbin0 -> 95087 bytes
-rw-r--r--doc/user/project/img/service_desk_nav_item.pngbin0 -> 30323 bytes
-rw-r--r--doc/user/project/img/service_desk_reply.pngbin0 -> 15186 bytes
-rw-r--r--doc/user/project/img/service_desk_thread.pngbin0 -> 60850 bytes
-rw-r--r--doc/user/project/import/gemnasium.md102
-rw-r--r--doc/user/project/import/github.md4
-rw-r--r--doc/user/project/import/gitlab_com.md5
-rw-r--r--doc/user/project/import/img/gemnasium/connect_github.pngbin0 -> 49966 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/create_project.pngbin0 -> 85728 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/edit_gitlab-ci.pngbin0 -> 79052 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/pipeline.pngbin0 -> 40872 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/project_connected.pngbin0 -> 21575 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/report.pngbin0 -> 144883 bytes
-rw-r--r--doc/user/project/import/img/gemnasium/select_project.pngbin0 -> 8927 bytes
-rw-r--r--doc/user/project/import/img/import_projects_from_github_select_auth_method.pngbin17611 -> 0 bytes
-rw-r--r--doc/user/project/import/index.md30
-rw-r--r--doc/user/project/import/phabricator.md29
-rw-r--r--doc/user/project/import/svn.md2
-rw-r--r--doc/user/project/index.md59
-rw-r--r--doc/user/project/insights/img/insights_example_bar_chart.pngbin0 -> 21767 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_bar_time_series_chart.pngbin0 -> 34068 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_line_chart.pngbin0 -> 120678 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_pie_chart.pngbin0 -> 10889 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_stacked_bar_chart.pngbin0 -> 81587 bytes
-rw-r--r--doc/user/project/insights/img/insights_sidebar_link.pngbin0 -> 21463 bytes
-rw-r--r--doc/user/project/insights/img/project_insights.pngbin0 -> 41210 bytes
-rw-r--r--doc/user/project/insights/index.md307
-rw-r--r--doc/user/project/integrations/custom_issue_tracker.md4
-rw-r--r--doc/user/project/integrations/github.md48
-rw-r--r--doc/user/project/integrations/gitlab_slack_application.md65
-rw-r--r--doc/user/project/integrations/hipchat.md53
-rw-r--r--doc/user/project/integrations/img/github_configuration.pngbin0 -> 12515 bytes
-rw-r--r--doc/user/project/integrations/img/github_status_check_pipeline_update.pngbin0 -> 21075 bytes
-rw-r--r--doc/user/project/integrations/img/gitlab_slack_app_landing_page.pngbin0 -> 32992 bytes
-rw-r--r--doc/user/project/integrations/img/issue_configuration.pngbin11882 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/jira_add_user_to_group.pngbin24838 -> 266180 bytes
-rw-r--r--doc/user/project/integrations/img/jira_added_user_to_group.pngbin0 -> 82473 bytes
-rw-r--r--doc/user/project/integrations/img/jira_api_token_menu.pngbin25059 -> 25056 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_group.pngbin19127 -> 262453 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_user.pngbin12625 -> 173516 bytes
-rw-r--r--doc/user/project/integrations/img/jira_group_access.pngbin19147 -> 112706 bytes
-rw-r--r--doc/user/project/integrations/img/jira_project_name.pngbin26680 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service.pngbin36976 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page.pngbin30398 -> 30395 bytes
-rw-r--r--doc/user/project/integrations/img/jira_user_management_link.pngbin23906 -> 206155 bytes
-rw-r--r--doc/user/project/integrations/img/mattermost_configuration.pngbin101151 -> 67672 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_add_metric.pngbin0 -> 53571 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_alert.pngbin0 -> 24452 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_service_alerts.pngbin0 -> 40727 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_yaml_deploy.pngbin9456 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/slack_configuration.pngbin92179 -> 64873 bytes
-rw-r--r--doc/user/project/integrations/jira.md6
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md45
-rw-r--r--doc/user/project/integrations/kubernetes.md4
-rw-r--r--doc/user/project/integrations/mattermost.md2
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md2
-rw-r--r--doc/user/project/integrations/mock_ci.md2
-rw-r--r--doc/user/project/integrations/project_services.md10
-rw-r--r--doc/user/project/integrations/prometheus.md83
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md26
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md2
-rw-r--r--doc/user/project/integrations/redmine.md4
-rw-r--r--doc/user/project/integrations/slack_slash_commands.md13
-rw-r--r--doc/user/project/integrations/webhooks.md69
-rw-r--r--doc/user/project/integrations/youtrack.md38
-rw-r--r--doc/user/project/issue_board.md24
-rw-r--r--doc/user/project/issues/confidential_issues.md7
-rw-r--r--doc/user/project/issues/create_new_issue.md22
-rw-r--r--doc/user/project/issues/crosslinking_issues.md9
-rw-r--r--doc/user/project/issues/csv_export.md77
-rw-r--r--doc/user/project/issues/csv_import.md20
-rw-r--r--doc/user/project/issues/due_dates.md7
-rw-r--r--doc/user/project/issues/img/create_issue_from_group_level_issue_tracker.pngbin0 -> 29358 bytes
-rw-r--r--doc/user/project/issues/img/csv_export_button.pngbin0 -> 7383 bytes
-rw-r--r--doc/user/project/issues/img/csv_export_modal.pngbin0 -> 16825 bytes
-rw-r--r--doc/user/project/issues/img/group_issues_list_view.pngbin46595 -> 0 bytes
-rw-r--r--doc/user/project/issues/img/issue_template.pngbin25019 -> 0 bytes
-rw-r--r--doc/user/project/issues/img/multiple_assignees.gifbin0 -> 877551 bytes
-rw-r--r--doc/user/project/issues/img/multiple_assignees_for_issues.pngbin0 -> 39710 bytes
-rw-r--r--doc/user/project/issues/img/related_issues_add.pngbin0 -> 12900 bytes
-rw-r--r--doc/user/project/issues/img/related_issues_remove.pngbin0 -> 5450 bytes
-rw-r--r--doc/user/project/issues/img/select_project_from_group_level_issue_tracker.pngbin0 -> 23706 bytes
-rw-r--r--doc/user/project/issues/img/similar_issues.pngbin25407 -> 25390 bytes
-rw-r--r--doc/user/project/issues/index.md225
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md172
-rw-r--r--doc/user/project/issues/issues_functionalities.md172
-rw-r--r--doc/user/project/issues/multiple_assignees_for_issues.md41
-rw-r--r--doc/user/project/issues/related_issues.md40
-rw-r--r--doc/user/project/labels.md65
-rw-r--r--doc/user/project/maven_packages.md5
-rw-r--r--doc/user/project/members/img/add_new_user_to_project_settings.pngbin11004 -> 0 bytes
-rw-r--r--doc/user/project/members/img/add_user_members_menu.pngbin28988 -> 0 bytes
-rw-r--r--doc/user/project/members/img/max_access_level.pngbin34710 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests.md4
-rw-r--r--doc/user/project/merge_requests/browser_performance_testing.md54
-rw-r--r--doc/user/project/merge_requests/code_quality.md82
-rw-r--r--doc/user/project/merge_requests/code_quality_diff.md6
-rw-r--r--doc/user/project/merge_requests/container_scanning.md5
-rw-r--r--doc/user/project/merge_requests/dast.md5
-rw-r--r--doc/user/project/merge_requests/dependency_scanning.md5
-rw-r--r--doc/user/project/merge_requests/img/approvals_can_override.pngbin0 -> 7634 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_mr_widget.pngbin0 -> 76524 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit.pngbin0 -> 47371 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_remove_on_push.pngbin0 -> 6551 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_starter_project_edit.pngbin0 -> 52442 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_starter_project_empty.pngbin0 -> 50820 bytes
-rw-r--r--doc/user/project/merge_requests/img/approve.pngbin0 -> 19329 bytes
-rw-r--r--doc/user/project/merge_requests/img/approve_additionally.pngbin0 -> 22700 bytes
-rw-r--r--doc/user/project/merge_requests/img/browser_performance_testing.pngbin0 -> 52100 bytes
-rw-r--r--doc/user/project/merge_requests/img/code_quality.gifbin0 -> 2617453 bytes
-rw-r--r--doc/user/project/merge_requests/img/comment-on-any-diff-line.pngbin55614 -> 55593 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_approver_merge_requests.pngbin0 -> 90764 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_wip_merge_requests.pngbin6285 -> 28572 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.pngbin6491 -> 19986 bytes
-rw-r--r--doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.pngbin0 -> 20867 bytes
-rw-r--r--doc/user/project/merge_requests/img/remove_approval.pngbin0 -> 21902 bytes
-rw-r--r--doc/user/project/merge_requests/img/revert_changes_commit.pngbin95647 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/revert_changes_mr.pngbin104954 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_blocked_accept_button.pngbin4152 -> 7141 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_mark_as_wip.pngbin7961 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_unmark_as_wip.pngbin8424 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/index.md195
-rw-r--r--doc/user/project/merge_requests/license_management.md5
-rw-r--r--doc/user/project/merge_requests/maintainer_access.md4
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md330
-rw-r--r--doc/user/project/merge_requests/merge_request_discussion_resolution.md4
-rw-r--r--doc/user/project/merge_requests/merge_when_build_succeeds.md8
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md10
-rw-r--r--doc/user/project/merge_requests/sast.md5
-rw-r--r--doc/user/project/merge_requests/sast_docker.md5
-rw-r--r--doc/user/project/merge_requests/versions.md1
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md57
-rw-r--r--doc/user/project/milestones/burndown_charts.md70
-rw-r--r--doc/user/project/milestones/img/burndown_chart.pngbin0 -> 48403 bytes
-rw-r--r--doc/user/project/milestones/index.md41
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md10
-rw-r--r--doc/user/project/operations/error_tracking.md8
-rw-r--r--doc/user/project/operations/feature_flags.md177
-rw-r--r--doc/user/project/operations/img/feature_flags_list.pngbin0 -> 14963 bytes
-rw-r--r--doc/user/project/operations/img/specs_list.pngbin0 -> 43574 bytes
-rw-r--r--doc/user/project/operations/index.md4
-rw-r--r--doc/user/project/operations/tracing.md34
-rw-r--r--doc/user/project/packages/img/maven_package_view.pngbin0 -> 16105 bytes
-rw-r--r--doc/user/project/packages/img/npm_package_view.pngbin0 -> 24443 bytes
-rw-r--r--doc/user/project/packages/maven.md5
-rw-r--r--doc/user/project/packages/maven_packages.md5
-rw-r--r--doc/user/project/packages/maven_repository.md341
-rw-r--r--doc/user/project/packages/npm_registry.md120
-rw-r--r--doc/user/project/pages/getting_started_part_four.md30
-rw-r--r--doc/user/project/pages/getting_started_part_one.md92
-rw-r--r--doc/user/project/pages/getting_started_part_three.md23
-rw-r--r--doc/user/project/pages/getting_started_part_two.md120
-rw-r--r--doc/user/project/pages/img/icons/click.pngbin4863 -> 4683 bytes
-rw-r--r--doc/user/project/pages/img/icons/fork.pngbin4562 -> 4380 bytes
-rw-r--r--doc/user/project/pages/img/icons/free.pngbin3681 -> 3563 bytes
-rw-r--r--doc/user/project/pages/img/icons/lock.pngbin3426 -> 3404 bytes
-rw-r--r--doc/user/project/pages/img/icons/monitor.pngbin2025 -> 1982 bytes
-rw-r--r--doc/user/project/pages/img/icons/terminal.pngbin1983 -> 1961 bytes
-rw-r--r--doc/user/project/pages/img/pages_create_project.pngbin6062 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_create_user_page.pngbin14435 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_dns_details.pngbin5350 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_multiple_domains.pngbin12930 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_new_domain_button.pngbin8763 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_project_templates_11-8.pngbin0 -> 69702 bytes
-rw-r--r--doc/user/project/pages/img/pages_remove.pngbin3777 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/pages_upload_cert.pngbin22888 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/remove_pages.pngbin0 -> 58035 bytes
-rw-r--r--doc/user/project/pages/index.md149
-rw-r--r--doc/user/project/pages/introduction.md462
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md10
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedule_variables.pngbin5360 -> 17292 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_new_form.pngbin23290 -> 62141 bytes
-rw-r--r--doc/user/project/pipelines/job_artifacts.md28
-rw-r--r--doc/user/project/pipelines/schedules.md124
-rw-r--r--doc/user/project/pipelines/settings.md18
-rw-r--r--doc/user/project/protected_branches.md47
-rw-r--r--doc/user/project/quick_actions.md15
-rw-r--r--doc/user/project/releases.md4
-rw-r--r--doc/user/project/repository/branches/img/branch_filter_search_box.pngbin23539 -> 23522 bytes
-rw-r--r--doc/user/project/repository/branches/index.md2
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md2
-rw-r--r--doc/user/project/repository/img/download_source_code.pngbin0 -> 61467 bytes
-rw-r--r--doc/user/project/repository/img/repository_cleanup.pngbin8117 -> 8114 bytes
-rw-r--r--doc/user/project/repository/index.md47
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md15
-rw-r--r--doc/user/project/security_dashboard.md5
-rw-r--r--doc/user/project/service_desk.md124
-rw-r--r--doc/user/project/settings/img/import_export_download_export.pngbin24397 -> 25905 bytes
-rw-r--r--doc/user/project/settings/img/import_export_export_button.pngbin24118 -> 25102 bytes
-rw-r--r--doc/user/project/settings/img/import_export_mail_link.pngbin13496 -> 7561 bytes
-rw-r--r--doc/user/project/settings/img/import_export_new_project.pngbin13082 -> 13202 bytes
-rw-r--r--doc/user/project/settings/img/import_export_select_file.pngbin13514 -> 20580 bytes
-rw-r--r--doc/user/project/settings/img/settings_edit_button.pngbin6897 -> 0 bytes
-rw-r--r--doc/user/project/settings/import_export.md28
-rw-r--r--doc/user/project/settings/index.md16
-rw-r--r--doc/user/project/slash_commands.md4
-rw-r--r--doc/user/project/web_ide/img/enable_web_ide.pngbin11364 -> 0 bytes
-rw-r--r--doc/user/project/web_ide/index.md157
-rw-r--r--doc/user/project/wiki/index.md25
-rw-r--r--doc/user/reserved_names.md1
-rw-r--r--doc/user/search/advanced_global_search.md75
-rw-r--r--doc/user/search/advanced_search_syntax.md69
-rw-r--r--doc/user/search/img/advanced_global_search.pngbin0 -> 15017 bytes
-rw-r--r--doc/user/search/img/issues_any_assignee.pngbin90455 -> 0 bytes
-rw-r--r--doc/user/search/img/issues_author.pngbin55217 -> 0 bytes
-rw-r--r--doc/user/search/img/multiple_assignees.pngbin0 -> 18897 bytes
-rw-r--r--doc/user/search/index.md23
-rw-r--r--doc/user/snippets.md2
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--doc/workflow/README.md6
-rw-r--r--doc/workflow/add-user/add-user.md4
-rw-r--r--doc/workflow/authorization_for_merge_requests.md4
-rw-r--r--doc/workflow/award_emoji.md4
-rw-r--r--doc/workflow/award_emoji.pngbin5268 -> 0 bytes
-rw-r--r--doc/workflow/cherry_pick_changes.md6
-rw-r--r--doc/workflow/ff_merge.md5
-rw-r--r--doc/workflow/git_annex.md238
-rw-r--r--doc/workflow/git_lfs.md5
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--doc/workflow/groups.md3
-rw-r--r--doc/workflow/img/copy_ssh_public_key_button.pngbin0 -> 11225 bytes
-rw-r--r--doc/workflow/img/new_branch_from_issue.pngbin33584 -> 0 bytes
-rw-r--r--doc/workflow/img/notification_global_settings.pngbin37542 -> 118914 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_pull_settings_upper.pngbin64118 -> 50084 bytes
-rw-r--r--doc/workflow/importing/README.md4
-rw-r--r--doc/workflow/importing/import_projects_from_bitbucket.md4
-rw-r--r--doc/workflow/importing/import_projects_from_fogbugz.md4
-rw-r--r--doc/workflow/importing/import_projects_from_gitea.md4
-rw-r--r--doc/workflow/importing/import_projects_from_github.md4
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md4
-rw-r--r--doc/workflow/importing/migrating_from_svn.md4
-rw-r--r--doc/workflow/issue_weight.md22
-rw-r--r--doc/workflow/issue_weight/issue.pngbin0 -> 69564 bytes
-rw-r--r--doc/workflow/labels.md4
-rw-r--r--doc/workflow/lfs/images/git-annex-branches.pngbin0 -> 32164 bytes
-rw-r--r--doc/workflow/lfs/lfs_administration.md12
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md5
-rw-r--r--doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md255
-rw-r--r--doc/workflow/merge_request_approvals.md5
-rw-r--r--doc/workflow/merge_requests.md4
-rw-r--r--doc/workflow/merge_when_build_succeeds.md4
-rw-r--r--doc/workflow/milestones.md4
-rw-r--r--doc/workflow/notifications.md35
-rw-r--r--doc/workflow/project_features.md4
-rw-r--r--doc/workflow/protected_branches.md4
-rw-r--r--doc/workflow/rebase_before_merge.md5
-rw-r--r--doc/workflow/repository_mirroring.md36
-rw-r--r--doc/workflow/revert_changes.md4
-rw-r--r--doc/workflow/share_projects_with_other_groups.md4
-rw-r--r--doc/workflow/share_with_group.md4
-rw-r--r--doc/workflow/share_with_group.pngbin50450 -> 0 bytes
-rw-r--r--doc/workflow/shortcuts.md12
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--doc/workflow/timezone.md26
-rw-r--r--doc/workflow/todos.md19
-rw-r--r--doc/workflow/web_editor.md4
-rw-r--r--doc/workflow/wip_merge_requests.md4
-rw-r--r--fixtures/emojis/index.json2
-rw-r--r--haml_lint/inline_javascript.rb25
-rw-r--r--haml_lint/linter/no_plain_nodes.rb84
-rw-r--r--jest.config.js27
-rw-r--r--lib/api/api.rb6
-rw-r--r--lib/api/branches.rb8
-rw-r--r--lib/api/circuit_breakers.rb39
-rw-r--r--lib/api/commits.rb21
-rw-r--r--lib/api/discussions.rb12
-rw-r--r--lib/api/entities.rb184
-rw-r--r--lib/api/environments.rb23
-rw-r--r--lib/api/events.rb49
-rw-r--r--lib/api/group_variables.rb2
-rw-r--r--lib/api/groups.rb39
-rw-r--r--lib/api/helpers.rb27
-rw-r--r--lib/api/helpers/custom_validators.rb13
-rw-r--r--lib/api/helpers/discussions_helpers.rb13
-rw-r--r--lib/api/helpers/events_helpers.rb31
-rw-r--r--lib/api/helpers/graphql_helpers.rb22
-rw-r--r--lib/api/helpers/groups_helpers.rb33
-rw-r--r--lib/api/helpers/internal_helpers.rb40
-rw-r--r--lib/api/helpers/issues_helpers.rb64
-rw-r--r--lib/api/helpers/members_helpers.rb19
-rw-r--r--lib/api/helpers/notes_helpers.rb20
-rw-r--r--lib/api/helpers/pagination.rb4
-rw-r--r--lib/api/helpers/projects_helpers.rb95
-rw-r--r--lib/api/helpers/protected_branches_helpers.rb13
-rw-r--r--lib/api/helpers/related_resources_helpers.rb4
-rw-r--r--lib/api/helpers/resource_label_events_helpers.rb13
-rw-r--r--lib/api/helpers/search_helpers.rb22
-rw-r--r--lib/api/helpers/services_helpers.rb767
-rw-r--r--lib/api/helpers/settings_helpers.rb19
-rw-r--r--lib/api/helpers/users_helpers.rb16
-rw-r--r--lib/api/helpers/variables_helpers.rb13
-rw-r--r--lib/api/import_github.rb4
-rw-r--r--lib/api/internal.rb42
-rw-r--r--lib/api/issuable_bulk_update.rb51
-rw-r--r--lib/api/issues.rb137
-rw-r--r--lib/api/job_artifacts.rb16
-rw-r--r--lib/api/merge_requests.rb44
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/namespaces.rb2
-rw-r--r--lib/api/notes.rb4
-rw-r--r--lib/api/pipeline_schedules.rb2
-rw-r--r--lib/api/pipelines.rb15
-rw-r--r--lib/api/project_clusters.rb3
-rw-r--r--lib/api/project_events.rb29
-rw-r--r--lib/api/project_import.rb3
-rw-r--r--lib/api/project_milestones.rb15
-rw-r--r--lib/api/project_statistics.rb23
-rw-r--r--lib/api/projects.rb93
-rw-r--r--lib/api/protected_branches.rb4
-rw-r--r--lib/api/release/links.rb2
-rw-r--r--lib/api/releases.rb16
-rw-r--r--lib/api/repositories.rb18
-rw-r--r--lib/api/resource_label_events.rb4
-rw-r--r--lib/api/runner.rb5
-rw-r--r--lib/api/runners.rb6
-rw-r--r--lib/api/search.rb41
-rw-r--r--lib/api/services.rb677
-rw-r--r--lib/api/settings.rb18
-rw-r--r--lib/api/snippets.rb25
-rw-r--r--lib/api/todos.rb32
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb7
-rw-r--r--lib/api/validations/check_assignees_count.rb32
-rw-r--r--lib/api/validations/types/labels_list.rb24
-rw-r--r--lib/api/variables.rb24
-rw-r--r--lib/api/version.rb18
-rw-r--r--lib/api/wikis.rb3
-rw-r--r--lib/backup/database.rb3
-rw-r--r--lib/backup/files.rb4
-rw-r--r--lib/backup/helper.rb8
-rw-r--r--lib/backup/manager.rb6
-rw-r--r--lib/backup/uploads.rb2
-rw-r--r--lib/banzai/color_parser.rb20
-rw-r--r--lib/banzai/commit_renderer.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb31
-rw-r--r--lib/banzai/filter/autolink_filter.rb2
-rw-r--r--lib/banzai/filter/blockquote_fence_filter.rb4
-rw-r--r--lib/banzai/filter/front_matter_filter.rb2
-rw-r--r--lib/banzai/filter/label_reference_filter.rb18
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb12
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/output_safety.rb11
-rw-r--r--lib/banzai/filter/reference_filter.rb5
-rw-r--r--lib/banzai/filter/relative_link_filter.rb2
-rw-r--r--lib/banzai/filter/spaced_link_filter.rb2
-rw-r--r--lib/banzai/filter/suggestion_filter.rb6
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb21
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb3
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb8
-rw-r--r--lib/banzai/pipeline/markup_pipeline.rb3
-rw-r--r--lib/banzai/redactor.rb7
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb2
-rw-r--r--lib/banzai/suggestions_parser.rb14
-rw-r--r--lib/bitbucket_server/collection.rb4
-rw-r--r--lib/bitbucket_server/representation/repo.rb2
-rw-r--r--lib/constraints/project_url_constrainer.rb3
-rw-r--r--lib/declarative_policy/preferred_scope.rb1
-rw-r--r--lib/declarative_policy/rule.rb6
-rw-r--r--lib/event_filter.rb2
-rw-r--r--lib/extracts_path.rb3
-rw-r--r--lib/gitlab.rb19
-rw-r--r--lib/gitlab/access.rb35
-rw-r--r--lib/gitlab/action_view_output/context.rb41
-rw-r--r--lib/gitlab/auth.rb40
-rw-r--r--lib/gitlab/auth/ldap/config.rb59
-rw-r--r--lib/gitlab/auth/ldap/person.rb4
-rw-r--r--lib/gitlab/auth/o_auth/auth_hash.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb17
-rw-r--r--lib/gitlab/auth/omniauth_identity_linker_base.rb6
-rw-r--r--lib/gitlab/auth/result.rb1
-rw-r--r--lib/gitlab/auth/saml/auth_hash.rb4
-rw-r--r--lib/gitlab/auth_logger.rb9
-rw-r--r--lib/gitlab/authorized_keys.rb149
-rw-r--r--lib/gitlab/background_migration.rb28
-rw-r--r--lib/gitlab/background_migration/archive_legacy_traces.rb9
-rw-r--r--lib/gitlab/background_migration/calculate_wiki_sizes.rb18
-rw-r--r--lib/gitlab/background_migration/encrypt_columns.rb3
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb40
-rw-r--r--lib/gitlab/background_migration/fix_cross_project_label_links.rb2
-rw-r--r--lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb2
-rw-r--r--lib/gitlab/background_migration/migrate_legacy_uploads.rb128
-rw-r--r--lib/gitlab/background_migration/migrate_stage_index.rb4
-rw-r--r--lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb12
-rw-r--r--lib/gitlab/background_migration/populate_merge_request_assignees_table.rb36
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads.rb14
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb6
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb2
-rw-r--r--lib/gitlab/background_migration/reset_merge_status.rb17
-rw-r--r--lib/gitlab/background_migration/sync_issues_state_id.rb21
-rw-r--r--lib/gitlab/background_migration/sync_merge_requests_state_id.rb23
-rw-r--r--lib/gitlab/badge/pipeline/template.rb1
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb2
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb146
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb43
-rw-r--r--lib/gitlab/checks/branch_check.rb34
-rw-r--r--lib/gitlab/checks/lfs_check.rb1
-rw-r--r--lib/gitlab/ci/ansi2html.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb8
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata/entry.rb2
-rw-r--r--lib/gitlab/ci/build/image.rb10
-rw-r--r--lib/gitlab/ci/build/policy/refs.rb12
-rw-r--r--lib/gitlab/ci/build/port.rb32
-rw-r--r--lib/gitlab/ci/build/prerequisite/base.rb27
-rw-r--r--lib/gitlab/ci/build/prerequisite/factory.rb33
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb43
-rw-r--r--lib/gitlab/ci/config.rb11
-rw-r--r--lib/gitlab/ci/config/entry/environment.rb2
-rw-r--r--lib/gitlab/ci/config/entry/global.rb3
-rw-r--r--lib/gitlab/ci/config/entry/image.rb22
-rw-r--r--lib/gitlab/ci/config/entry/include.rb23
-rw-r--r--lib/gitlab/ci/config/entry/includes.rb32
-rw-r--r--lib/gitlab/ci/config/entry/job.rb2
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb4
-rw-r--r--lib/gitlab/ci/config/entry/port.rb46
-rw-r--r--lib/gitlab/ci/config/entry/ports.rb46
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb3
-rw-r--r--lib/gitlab/ci/config/entry/service.rb7
-rw-r--r--lib/gitlab/ci/config/entry/services.rb2
-rw-r--r--lib/gitlab/ci/config/extendable/entry.rb59
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb30
-rw-r--r--lib/gitlab/ci/config/external/file/local.rb11
-rw-r--r--lib/gitlab/ci/config/external/file/project.rb7
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb36
-rw-r--r--lib/gitlab/ci/config/external/processor.rb7
-rw-r--r--lib/gitlab/ci/model.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb12
-rw-r--r--lib/gitlab/ci/pipeline/chain/limit/activity.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/limit/size.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/skip.rb6
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/repository.rb2
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/and.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/base.rb6
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/equals.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/matches.rb25
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb30
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/operator.rb20
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/or.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb17
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb23
-rw-r--r--lib/gitlab/ci/pipeline/expression/parser.rb70
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb15
-rw-r--r--lib/gitlab/ci/status/build/factory.rb4
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/status/build/failed_allowed.rb2
-rw-r--r--lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb24
-rw-r--r--lib/gitlab/ci/status/build/preparing.rb28
-rw-r--r--lib/gitlab/ci/status/preparing.rb33
-rw-r--r--lib/gitlab/ci/status/stage/factory.rb3
-rw-r--r--lib/gitlab/ci/status/stage/play_manual.rb43
-rw-r--r--lib/gitlab/ci/status/success_warning.rb2
-rw-r--r--lib/gitlab/ci/templates/.yamllint5
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml911
-rw-r--r--lib/gitlab/ci/templates/Bash.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/C++.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/Chef.gitlab-ci.yml42
-rw-r--r--lib/gitlab/ci/templates/Clojure.gitlab-ci.yml12
-rw-r--r--lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml17
-rw-r--r--lib/gitlab/ci/templates/Crystal.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Django.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Docker.gitlab-ci.yml21
-rw-r--r--lib/gitlab/ci/templates/Elixir.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Go.gitlab-ci.yml27
-rw-r--r--lib/gitlab/ci/templates/Gradle.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Grails.gitlab-ci.yml38
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml38
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml18
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml27
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml570
-rw-r--r--lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml23
-rw-r--r--lib/gitlab/ci/templates/Julia.gitlab-ci.yml17
-rw-r--r--lib/gitlab/ci/templates/Laravel.gitlab-ci.yml35
-rw-r--r--lib/gitlab/ci/templates/Maven.gitlab-ci.yml76
-rw-r--r--lib/gitlab/ci/templates/Mono.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml24
-rw-r--r--lib/gitlab/ci/templates/Packer.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml11
-rw-r--r--lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml11
-rw-r--r--lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml11
-rw-r--r--lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml12
-rw-r--r--lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml27
-rw-r--r--lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml25
-rw-r--r--lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml22
-rw-r--r--lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml28
-rw-r--r--lib/gitlab/ci/templates/Ruby.gitlab-ci.yml16
-rw-r--r--lib/gitlab/ci/templates/Rust.gitlab-ci.yml18
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml63
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml48
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml58
-rw-r--r--lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml27
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml62
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml33
-rw-r--r--lib/gitlab/ci/templates/Swift.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Terraform.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/dotNET-Core.yml117
-rw-r--r--lib/gitlab/ci/templates/dotNET.gitlab-ci.yml29
-rw-r--r--lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml30
-rw-r--r--lib/gitlab/ci/trace.rb14
-rw-r--r--lib/gitlab/ci/variables/collection.rb2
-rw-r--r--lib/gitlab/ci/variables/collection/item.rb4
-rw-r--r--lib/gitlab/cluster/lifecycle_events.rb8
-rw-r--r--lib/gitlab/config/entry/configurable.rb10
-rw-r--r--lib/gitlab/config/entry/factory.rb2
-rw-r--r--lib/gitlab/config/entry/legacy_validation_helpers.rb8
-rw-r--r--lib/gitlab/config/entry/node.rb20
-rw-r--r--lib/gitlab/config/entry/simplifiable.rb5
-rw-r--r--lib/gitlab/config/entry/validators.rb144
-rw-r--r--lib/gitlab/content_disposition.rb4
-rw-r--r--lib/gitlab/contributions_calendar.rb1
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb4
-rw-r--r--lib/gitlab/danger/helper.rb70
-rw-r--r--lib/gitlab/danger/roulette.rb84
-rw-r--r--lib/gitlab/danger/teammate.rb31
-rw-r--r--lib/gitlab/data_builder/deployment.rb25
-rw-r--r--lib/gitlab/data_builder/pipeline.rb4
-rw-r--r--lib/gitlab/data_builder/push.rb19
-rw-r--r--lib/gitlab/database.rb10
-rw-r--r--lib/gitlab/database/count/reltuples_count_strategy.rb18
-rw-r--r--lib/gitlab/database/count/tablesample_count_strategy.rb13
-rw-r--r--lib/gitlab/database/migration_helpers.rb25
-rw-r--r--lib/gitlab/database/multi_threaded_migration.rb10
-rw-r--r--lib/gitlab/database/sha_attribute.rb2
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb18
-rw-r--r--lib/gitlab/dependency_linker/composer_json_linker.rb4
-rw-r--r--lib/gitlab/dependency_linker/gemfile_linker.rb30
-rw-r--r--lib/gitlab/dependency_linker/gemspec_linker.rb2
-rw-r--r--lib/gitlab/dependency_linker/method_linker.rb10
-rw-r--r--lib/gitlab/dependency_linker/package.rb19
-rw-r--r--lib/gitlab/dependency_linker/package_json_linker.rb21
-rw-r--r--lib/gitlab/dependency_linker/parser/gemfile.rb40
-rw-r--r--lib/gitlab/dependency_linker/podfile_linker.rb11
-rw-r--r--lib/gitlab/dependency_linker/podspec_linker.rb2
-rw-r--r--lib/gitlab/diff/file.rb43
-rw-r--r--lib/gitlab/diff/suggestion.rb54
-rw-r--r--lib/gitlab/diff/suggestion_diff.rb37
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb53
-rw-r--r--lib/gitlab/discussions_diff/highlight_cache.rb13
-rw-r--r--lib/gitlab/email/reply_parser.rb2
-rw-r--r--lib/gitlab/encoding_helper.rb7
-rw-r--r--lib/gitlab/etag_caching/router.rb23
-rw-r--r--lib/gitlab/external_authorization.rb40
-rw-r--r--lib/gitlab/external_authorization/access.rb55
-rw-r--r--lib/gitlab/external_authorization/cache.rb62
-rw-r--r--lib/gitlab/external_authorization/client.rb63
-rw-r--r--lib/gitlab/external_authorization/config.rb47
-rw-r--r--lib/gitlab/external_authorization/logger.rb21
-rw-r--r--lib/gitlab/external_authorization/response.rb38
-rw-r--r--lib/gitlab/fake_application_settings.rb19
-rw-r--r--lib/gitlab/favicon.rb8
-rw-r--r--lib/gitlab/file_detector.rb1
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb2
-rw-r--r--lib/gitlab/git/blob.rb6
-rw-r--r--lib/gitlab/git/commit.rb41
-rw-r--r--lib/gitlab/git/object_pool.rb6
-rw-r--r--lib/gitlab/git/pre_receive_error.rb31
-rw-r--r--lib/gitlab/git/ref.rb1
-rw-r--r--lib/gitlab/git/repository.rb125
-rw-r--r--lib/gitlab/git/repository_cleaner.rb4
-rw-r--r--lib/gitlab/git/rugged_impl/blob.rb106
-rw-r--r--lib/gitlab/git/rugged_impl/commit.rb109
-rw-r--r--lib/gitlab/git/rugged_impl/ref.rb20
-rw-r--r--lib/gitlab/git/rugged_impl/repository.rb78
-rw-r--r--lib/gitlab/git/rugged_impl/tree.rb111
-rw-r--r--lib/gitlab/git/tree.rb8
-rw-r--r--lib/gitlab/git/wiki.rb22
-rw-r--r--lib/gitlab/git_access.rb10
-rw-r--r--lib/gitlab/git_access_result/success.rb5
-rw-r--r--lib/gitlab/git_post_receive.rb2
-rw-r--r--lib/gitlab/git_ref_validator.rb23
-rw-r--r--lib/gitlab/gitaly_client.rb121
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb6
-rw-r--r--lib/gitlab/gitaly_client/blobs_stitcher.rb20
-rw-r--r--lib/gitlab/gitaly_client/cleanup_service.rb33
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb6
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/object_pool_service.rb9
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb97
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb17
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb38
-rw-r--r--lib/gitlab/gitaly_client/storage_settings.rb8
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb34
-rw-r--r--lib/gitlab/github_import/importer/issue_importer.rb1
-rw-r--r--lib/gitlab/github_import/importer/milestones_importer.rb1
-rw-r--r--lib/gitlab/github_import/importer/pull_request_importer.rb3
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb21
-rw-r--r--lib/gitlab/github_import/parallel_importer.rb18
-rw-r--r--lib/gitlab/github_import/representation/diff_note.rb2
-rw-r--r--lib/gitlab/github_import/representation/note.rb2
-rw-r--r--lib/gitlab/gl_repository.rb49
-rw-r--r--lib/gitlab/gl_repository/repo_type.rb42
-rw-r--r--lib/gitlab/gon_helper.rb8
-rw-r--r--lib/gitlab/grape_logging/loggers/correlation_id_logger.rb2
-rw-r--r--lib/gitlab/grape_logging/loggers/perf_logger.rb5
-rw-r--r--lib/gitlab/graphql/authorize.rb17
-rw-r--r--lib/gitlab/graphql/authorize/authorize_field_service.rb117
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb17
-rw-r--r--lib/gitlab/graphql/authorize/instrumentation.rb48
-rw-r--r--lib/gitlab/graphql/connections/keyset_connection.rb16
-rw-r--r--lib/gitlab/graphql/generic_tracing.rb59
-rw-r--r--lib/gitlab/graphql/loaders/batch_model_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb23
-rw-r--r--lib/gitlab/graphql/query_analyzers/logger_analyzer.rb71
-rw-r--r--lib/gitlab/graphql/representation/tree_entry.rb31
-rw-r--r--lib/gitlab/graphql_logger.rb9
-rw-r--r--lib/gitlab/group_search_results.rb36
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb54
-rw-r--r--lib/gitlab/hashed_storage/rake_helper.rb12
-rw-r--r--lib/gitlab/health_checks/metric.rb1
-rw-r--r--lib/gitlab/health_checks/result.rb1
-rw-r--r--lib/gitlab/highlight.rb2
-rw-r--r--lib/gitlab/hook_data/issuable_builder.rb6
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb2
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb7
-rw-r--r--lib/gitlab/http.rb9
-rw-r--r--lib/gitlab/http_connection_adapter.rb44
-rw-r--r--lib/gitlab/i18n/metadata_entry.rb2
-rw-r--r--lib/gitlab/import/merge_request_helpers.rb18
-rw-r--r--lib/gitlab/import/set_async_jid.rb27
-rw-r--r--lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb7
-rw-r--r--lib/gitlab/import_export/attribute_cleaner.rb7
-rw-r--r--lib/gitlab/import_export/import_export.yml6
-rw-r--r--lib/gitlab/import_export/json_hash_builder.rb2
-rw-r--r--lib/gitlab/import_export/members_mapper.rb6
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb11
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb5
-rw-r--r--lib/gitlab/import_sources.rb3
-rw-r--r--lib/gitlab/incoming_email.rb2
-rw-r--r--lib/gitlab/json_cache.rb31
-rw-r--r--lib/gitlab/json_logger.rb2
-rw-r--r--lib/gitlab/kubernetes.rb24
-rw-r--r--lib/gitlab/kubernetes/errors.rb23
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb8
-rw-r--r--lib/gitlab/kubernetes/helm/delete_command.rb55
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb3
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb8
-rw-r--r--lib/gitlab/kubernetes/namespace.rb29
-rw-r--r--lib/gitlab/legacy_github_import/client.rb2
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb56
-rw-r--r--lib/gitlab/legacy_github_import/project_creator.rb2
-rw-r--r--lib/gitlab/legacy_github_import/release_formatter.rb1
-rw-r--r--lib/gitlab/legacy_github_import/user_formatter.rb4
-rw-r--r--lib/gitlab/lets_encrypt/challenge.rb17
-rw-r--r--lib/gitlab/lets_encrypt/client.rb95
-rw-r--r--lib/gitlab/lets_encrypt/order.rb32
-rw-r--r--lib/gitlab/lfs_token.rb42
-rw-r--r--lib/gitlab/markdown_cache.rb12
-rw-r--r--lib/gitlab/markdown_cache/active_record/extension.rb49
-rw-r--r--lib/gitlab/markdown_cache/field_data.rb35
-rw-r--r--lib/gitlab/markdown_cache/redis/extension.rb63
-rw-r--r--lib/gitlab/markdown_cache/redis/store.rb56
-rw-r--r--lib/gitlab/metrics/dashboard/base_service.rb73
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb51
-rw-r--r--lib/gitlab/metrics/dashboard/processor.rb46
-rw-r--r--lib/gitlab/metrics/dashboard/project_dashboard_service.rb47
-rw-r--r--lib/gitlab/metrics/dashboard/stages/base_stage.rb58
-rw-r--r--lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb23
-rw-r--r--lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb106
-rw-r--r--lib/gitlab/metrics/dashboard/stages/sorter.rb34
-rw-r--r--lib/gitlab/metrics/dashboard/system_dashboard_service.rb47
-rw-r--r--lib/gitlab/metrics/influx_db.rb6
-rw-r--r--lib/gitlab/metrics/metric.rb2
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb93
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb33
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb36
-rw-r--r--lib/gitlab/metrics/system.rb28
-rw-r--r--lib/gitlab/metrics/transaction.rb15
-rw-r--r--lib/gitlab/middleware/basic_health_check.rb8
-rw-r--r--lib/gitlab/middleware/correlation_id.rb2
-rw-r--r--lib/gitlab/middleware/read_only.rb2
-rw-r--r--lib/gitlab/middleware/release_env.rb1
-rw-r--r--lib/gitlab/namespaced_session_store.rb22
-rw-r--r--lib/gitlab/object_hierarchy.rb62
-rw-r--r--lib/gitlab/omniauth_initializer.rb21
-rw-r--r--lib/gitlab/optimistic_locking.rb2
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/performance_bar/peek_query_tracker.rb6
-rw-r--r--lib/gitlab/phabricator_import.rb12
-rw-r--r--lib/gitlab/phabricator_import/base_worker.rb80
-rw-r--r--lib/gitlab/phabricator_import/cache/map.rb65
-rw-r--r--lib/gitlab/phabricator_import/conduit.rb9
-rw-r--r--lib/gitlab/phabricator_import/conduit/client.rb41
-rw-r--r--lib/gitlab/phabricator_import/conduit/maniphest.rb28
-rw-r--r--lib/gitlab/phabricator_import/conduit/pagination.rb24
-rw-r--r--lib/gitlab/phabricator_import/conduit/response.rb60
-rw-r--r--lib/gitlab/phabricator_import/conduit/tasks_response.rb24
-rw-r--r--lib/gitlab/phabricator_import/import_tasks_worker.rb10
-rw-r--r--lib/gitlab/phabricator_import/importer.rb44
-rw-r--r--lib/gitlab/phabricator_import/issues/importer.rb42
-rw-r--r--lib/gitlab/phabricator_import/issues/task_importer.rb54
-rw-r--r--lib/gitlab/phabricator_import/project_creator.rb78
-rw-r--r--lib/gitlab/phabricator_import/representation/task.rb60
-rw-r--r--lib/gitlab/phabricator_import/worker_state.rb47
-rw-r--r--lib/gitlab/profiler.rb6
-rw-r--r--lib/gitlab/project_search_results.rb16
-rw-r--r--lib/gitlab/project_template.rb3
-rw-r--r--lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb8
-rw-r--r--lib/gitlab/prometheus/queries/knative_invocation_query.rb39
-rw-r--r--lib/gitlab/prometheus/query_variables.rb5
-rw-r--r--lib/gitlab/prometheus_client.rb83
-rw-r--r--lib/gitlab/proxy_http_connection_adapter.rb36
-rw-r--r--lib/gitlab/push_options.rb70
-rw-r--r--lib/gitlab/quick_actions/command_definition.rb17
-rw-r--r--lib/gitlab/quick_actions/commit_actions.rb34
-rw-r--r--lib/gitlab/quick_actions/common_actions.rb17
-rw-r--r--lib/gitlab/quick_actions/dsl.rb23
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb221
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb139
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb225
-rw-r--r--lib/gitlab/quick_actions/merge_request_actions.rb59
-rw-r--r--lib/gitlab/quick_actions/spend_time_and_date_separator.rb2
-rw-r--r--lib/gitlab/rack_timeout_observer.rb46
-rw-r--r--lib/gitlab/recaptcha.rb4
-rw-r--r--lib/gitlab/repo_path.rb28
-rw-r--r--lib/gitlab/request_context.rb8
-rw-r--r--lib/gitlab/routing.rb2
-rw-r--r--lib/gitlab/sanitizers/exif.rb156
-rw-r--r--lib/gitlab/sanitizers/svg.rb2
-rw-r--r--lib/gitlab/search_results.rb124
-rw-r--r--lib/gitlab/sentry.rb15
-rw-r--r--lib/gitlab/session.rb27
-rw-r--r--lib/gitlab/setup_helper.rb7
-rw-r--r--lib/gitlab/shell.rb141
-rw-r--r--lib/gitlab/sherlock/middleware.rb4
-rw-r--r--lib/gitlab/sherlock/query.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb8
-rw-r--r--lib/gitlab/sidekiq_middleware/correlation_injector.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/correlation_logger.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb82
-rw-r--r--lib/gitlab/sidekiq_middleware/shutdown.rb135
-rw-r--r--lib/gitlab/sidekiq_signals.rb45
-rw-r--r--lib/gitlab/slash_commands/result.rb1
-rw-r--r--lib/gitlab/sql/pattern.rb10
-rw-r--r--lib/gitlab/template/base_template.rb6
-rw-r--r--lib/gitlab/template/gitlab_ci_yml_template.rb5
-rw-r--r--lib/gitlab/tracing/common.rb69
-rw-r--r--lib/gitlab/tracing/factory.rb61
-rw-r--r--lib/gitlab/tracing/grpc_interceptor.rb54
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb97
-rw-r--r--lib/gitlab/tracing/rack_middleware.rb46
-rw-r--r--lib/gitlab/tracing/rails/action_view_subscriber.rb75
-rw-r--r--lib/gitlab/tracing/rails/active_record_subscriber.rb49
-rw-r--r--lib/gitlab/tracing/rails/rails_common.rb24
-rw-r--r--lib/gitlab/tracing/sidekiq/client_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/server_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/sidekiq_common.rb22
-rw-r--r--lib/gitlab/tree_summary.rb2
-rw-r--r--lib/gitlab/untrusted_regexp.rb22
-rw-r--r--lib/gitlab/untrusted_regexp/ruby_syntax.rb62
-rw-r--r--lib/gitlab/url_blocker.rb81
-rw-r--r--lib/gitlab/url_builder.rb4
-rw-r--r--lib/gitlab/url_helpers.rb16
-rw-r--r--lib/gitlab/url_sanitizer.rb4
-rw-r--r--lib/gitlab/usage_data.rb16
-rw-r--r--lib/gitlab/user_access.rb4
-rw-r--r--lib/gitlab/user_extractor.rb7
-rw-r--r--lib/gitlab/utils.rb6
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--lib/gitlab/workhorse.rb83
-rw-r--r--lib/google_api/cloud_platform/client.rb7
-rw-r--r--lib/haml_lint/inline_javascript.rb26
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--lib/peek/views/tracing.rb6
-rw-r--r--lib/quality/kubernetes_client.rb2
-rw-r--r--lib/quality/seeders/issues.rb58
-rw-r--r--lib/quality/test_level.rb75
-rw-r--r--lib/sentry/client.rb64
-rwxr-xr-xlib/support/init.d/gitlab37
-rw-r--r--lib/support/init.d/gitlab.default.example3
-rw-r--r--lib/system_check/app/git_user_default_ssh_config_check.rb2
-rw-r--r--lib/system_check/app/git_version_check.rb2
-rw-r--r--lib/system_check/app/ruby_version_check.rb2
-rw-r--r--lib/system_check/base_check.rb2
-rw-r--r--lib/tasks/gemojione.rake2
-rw-r--r--lib/tasks/gettext.rake1
-rw-r--r--lib/tasks/gitlab/artifacts/migrate.rake13
-rw-r--r--lib/tasks/gitlab/assets.rake6
-rw-r--r--lib/tasks/gitlab/backup.rake10
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
-rw-r--r--lib/tasks/gitlab/db.rake5
-rw-r--r--lib/tasks/gitlab/features.rake24
-rw-r--r--lib/tasks/gitlab/info.rake37
-rw-r--r--lib/tasks/gitlab/lfs/migrate.rake11
-rw-r--r--lib/tasks/gitlab/seed.rake34
-rw-r--r--lib/tasks/gitlab/shell.rake27
-rw-r--r--lib/tasks/gitlab/storage.rake63
-rw-r--r--lib/tasks/gitlab/traces.rake11
-rw-r--r--lib/tasks/gitlab/update_templates.rake2
-rw-r--r--lib/tasks/gitlab/uploads/sanitize.rake18
-rw-r--r--lib/tasks/haml-lint.rake2
-rw-r--r--lib/tasks/karma.rake7
-rw-r--r--lib/tasks/lint.rake43
-rw-r--r--lib/tasks/migrate/migrate_iids.rake42
-rw-r--r--lib/tasks/spec.rake44
-rw-r--r--lib/tasks/tokens.rake2
-rw-r--r--lib/unfold_form.rb14
-rw-r--r--locale/ar_SA/gitlab.po2737
-rw-r--r--locale/bg/gitlab.po2693
-rw-r--r--locale/bn_BD/gitlab.po13998
-rw-r--r--locale/bn_IN/gitlab.po13998
-rw-r--r--locale/ca_ES/gitlab.po2697
-rw-r--r--locale/cs_CZ/gitlab.po2709
-rw-r--r--locale/cy_GB/gitlab.po2737
-rw-r--r--locale/da_DK/gitlab.po2681
-rw-r--r--locale/de/gitlab.po2803
-rw-r--r--locale/el_GR/gitlab.po2681
-rw-r--r--locale/en/gitlab.po3
-rw-r--r--locale/eo/gitlab.po2693
-rw-r--r--locale/es/gitlab.po3947
-rw-r--r--locale/et_EE/gitlab.po2681
-rw-r--r--locale/fil_PH/gitlab.po2681
-rw-r--r--locale/fr/gitlab.po2869
-rw-r--r--locale/gitlab.pot3705
-rw-r--r--locale/gl_ES/gitlab.po2695
-rw-r--r--locale/he_IL/gitlab.po2709
-rw-r--r--locale/hi_IN/gitlab.po2681
-rw-r--r--locale/hr_HR/gitlab.po2695
-rw-r--r--locale/hu_HU/gitlab.po2681
-rw-r--r--locale/id_ID/gitlab.po2667
-rw-r--r--locale/it/gitlab.po2701
-rw-r--r--locale/ja/gitlab.po3887
-rw-r--r--locale/ka_GE/gitlab.po13998
-rw-r--r--locale/ko/gitlab.po2867
-rw-r--r--locale/mn_MN/gitlab.po2681
-rw-r--r--locale/nb_NO/gitlab.po2681
-rw-r--r--locale/nl_NL/gitlab.po2683
-rw-r--r--locale/pa_IN/gitlab.po2681
-rw-r--r--locale/pl_PL/gitlab.po2719
-rw-r--r--locale/pt_BR/gitlab.po3599
-rw-r--r--locale/pt_PT/gitlab.po2681
-rw-r--r--locale/ro_RO/gitlab.po2695
-rw-r--r--locale/ru/gitlab.po2969
-rw-r--r--locale/sk_SK/gitlab.po2709
-rw-r--r--locale/sq_AL/gitlab.po2681
-rw-r--r--locale/sr_CS/gitlab.po2695
-rw-r--r--locale/sr_SP/gitlab.po2695
-rw-r--r--locale/sv_SE/gitlab.po2681
-rw-r--r--locale/sw_KE/gitlab.po2681
-rw-r--r--locale/tr_TR/gitlab.po5337
-rw-r--r--locale/uk/gitlab.po4087
-rw-r--r--locale/zh_CN/gitlab.po4203
-rw-r--r--locale/zh_HK/gitlab.po2679
-rw-r--r--locale/zh_TW/gitlab.po2835
-rw-r--r--package.json101
-rw-r--r--qa/.gitignore1
-rw-r--r--qa/Dockerfile2
-rw-r--r--qa/Gemfile4
-rw-r--r--qa/Gemfile.lock10
-rw-r--r--qa/README.md51
-rw-r--r--qa/Rakefile28
-rw-r--r--qa/knapsack/gitlab-ce/review-qa-all_master_report.json42
-rw-r--r--qa/load/artillery.yml25
-rw-r--r--qa/qa.rb17
-rw-r--r--qa/qa/ce/strategy.rb12
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Dockerfile9
-rw-r--r--qa/qa/git/location.rb2
-rw-r--r--qa/qa/git/repository.rb7
-rw-r--r--qa/qa/page/README.md134
-rw-r--r--qa/qa/page/admin/menu.rb9
-rw-r--r--qa/qa/page/admin/settings/component/performance_bar.rb27
-rw-r--r--qa/qa/page/admin/settings/metrics_and_profiling.rb23
-rw-r--r--qa/qa/page/base.rb38
-rw-r--r--qa/qa/page/component/dropzone.rb2
-rw-r--r--qa/qa/page/component/note.rb2
-rw-r--r--qa/qa/page/component/select2.rb4
-rw-r--r--qa/qa/page/dashboard/groups.rb6
-rw-r--r--qa/qa/page/dashboard/projects.rb4
-rw-r--r--qa/qa/page/dashboard/snippet/index.rb21
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb53
-rw-r--r--qa/qa/page/dashboard/snippet/show.rb63
-rw-r--r--qa/qa/page/element.rb27
-rw-r--r--qa/qa/page/file/form.rb4
-rw-r--r--qa/qa/page/file/shared/commit_message.rb2
-rw-r--r--qa/qa/page/file/show.rb4
-rw-r--r--qa/qa/page/group/new.rb2
-rw-r--r--qa/qa/page/group/show.rb4
-rw-r--r--qa/qa/page/issuable/sidebar.rb2
-rw-r--r--qa/qa/page/label/index.rb2
-rw-r--r--qa/qa/page/label/new.rb4
-rw-r--r--qa/qa/page/layout/banner.rb2
-rw-r--r--qa/qa/page/layout/performance_bar.rb35
-rw-r--r--qa/qa/page/main/login.rb18
-rw-r--r--qa/qa/page/main/menu.rb17
-rw-r--r--qa/qa/page/main/oauth.rb2
-rw-r--r--qa/qa/page/mattermost/login.rb2
-rw-r--r--qa/qa/page/mattermost/main.rb2
-rw-r--r--qa/qa/page/merge_request/new.rb4
-rw-r--r--qa/qa/page/merge_request/show.rb20
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb4
-rw-r--r--qa/qa/page/project/activity.rb4
-rw-r--r--qa/qa/page/project/branches/show.rb18
-rw-r--r--qa/qa/page/project/fork/new.rb2
-rw-r--r--qa/qa/page/project/import/github.rb23
-rw-r--r--qa/qa/page/project/issue/index.rb4
-rw-r--r--qa/qa/page/project/issue/new.rb2
-rw-r--r--qa/qa/page/project/issue/show.rb12
-rw-r--r--qa/qa/page/project/job/show.rb6
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/page/project/milestone/index.rb2
-rw-r--r--qa/qa/page/project/milestone/new.rb4
-rw-r--r--qa/qa/page/project/new.rb23
-rw-r--r--qa/qa/page/project/operations/environments/index.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb4
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb10
-rw-r--r--qa/qa/page/project/pipeline/index.rb4
-rw-r--r--qa/qa/page/project/pipeline/show.rb8
-rw-r--r--qa/qa/page/project/settings/advanced.rb22
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb4
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb26
-rw-r--r--qa/qa/page/project/settings/common.rb10
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb6
-rw-r--r--qa/qa/page/project/settings/deploy_tokens.rb2
-rw-r--r--qa/qa/page/project/settings/main.rb20
-rw-r--r--qa/qa/page/project/settings/merge_request.rb2
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb12
-rw-r--r--qa/qa/page/project/settings/repository.rb2
-rw-r--r--qa/qa/page/project/settings/runners.rb2
-rw-r--r--qa/qa/page/project/show.rb4
-rw-r--r--qa/qa/page/project/sub_menus/operations.rb4
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb2
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb8
-rw-r--r--qa/qa/page/project/web_ide/edit.rb35
-rw-r--r--qa/qa/page/project/wiki/edit.rb12
-rw-r--r--qa/qa/page/project/wiki/new.rb2
-rw-r--r--qa/qa/page/project/wiki/show.rb2
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/page/validatable.rb22
-rw-r--r--qa/qa/page/validator.rb2
-rw-r--r--qa/qa/page/view.rb6
-rw-r--r--qa/qa/resource/README.md392
-rw-r--r--qa/qa/resource/api_fabricator.rb3
-rw-r--r--qa/qa/resource/base.rb64
-rw-r--r--qa/qa/resource/branch.rb2
-rw-r--r--qa/qa/resource/ci_variable.rb34
-rw-r--r--qa/qa/resource/deploy_key.rb2
-rw-r--r--qa/qa/resource/deploy_token.rb2
-rw-r--r--qa/qa/resource/file.rb31
-rw-r--r--qa/qa/resource/group.rb2
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb2
-rw-r--r--qa/qa/resource/label.rb4
-rw-r--r--qa/qa/resource/personal_access_token.rb4
-rw-r--r--qa/qa/resource/project.rb8
-rw-r--r--qa/qa/resource/project_imported_from_github.rb6
-rw-r--r--qa/qa/resource/project_milestone.rb2
-rw-r--r--qa/qa/resource/repository/project_push.rb2
-rw-r--r--qa/qa/resource/repository/wiki_push.rb2
-rw-r--r--qa/qa/resource/runner.rb2
-rw-r--r--qa/qa/resource/sandbox.rb4
-rw-r--r--qa/qa/resource/settings/hashed_storage.rb2
-rw-r--r--qa/qa/resource/snippet.rb30
-rw-r--r--qa/qa/resource/ssh_key.rb2
-rw-r--r--qa/qa/resource/wiki.rb2
-rw-r--r--qa/qa/runtime/address.rb9
-rw-r--r--qa/qa/runtime/api/client.rb13
-rw-r--r--qa/qa/runtime/api/request.rb2
-rw-r--r--qa/qa/runtime/browser.rb9
-rw-r--r--qa/qa/runtime/env.rb6
-rw-r--r--qa/qa/runtime/feature.rb36
-rw-r--r--qa/qa/runtime/key/base.rb2
-rw-r--r--qa/qa/runtime/key/ecdsa.rb3
-rw-r--r--qa/qa/runtime/key/ed25519.rb3
-rw-r--r--qa/qa/runtime/key/rsa.rb2
-rw-r--r--qa/qa/runtime/namespace.rb2
-rw-r--r--qa/qa/runtime/release.rb2
-rw-r--r--qa/qa/runtime/scenario.rb2
-rw-r--r--qa/qa/runtime/user.rb2
-rw-r--r--qa/qa/scenario/actable.rb2
-rw-r--r--qa/qa/scenario/bootable.rb12
-rw-r--r--qa/qa/scenario/shared_attributes.rb12
-rw-r--r--qa/qa/scenario/template.rb33
-rw-r--r--qa/qa/scenario/test/instance/all.rb3
-rw-r--r--qa/qa/scenario/test/instance/smoke.rb3
-rw-r--r--qa/qa/scenario/test/integration/github.rb2
-rw-r--r--qa/qa/scenario/test/integration/kubernetes.rb2
-rw-r--r--qa/qa/scenario/test/integration/mattermost.rb11
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/kubernetes_cluster.rb4
-rw-r--r--qa/qa/service/omnibus.rb2
-rw-r--r--qa/qa/service/runner.rb2
-rw-r--r--qa/qa/service/shellout.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb29
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb21
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb59
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb35
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb68
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb68
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb32
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb221
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb34
-rw-r--r--qa/qa/specs/helpers/quarantine.rb68
-rw-r--r--qa/qa/specs/runner.rb21
-rw-r--r--qa/qa/support/api.rb15
-rw-r--r--qa/qa/support/page/logging.rb37
-rw-r--r--qa/qa/support/waiter.rb4
-rw-r--r--qa/qa/tools/delete_subgroups.rb6
-rw-r--r--qa/qa/tools/generate_perf_testdata.rb194
-rw-r--r--qa/qa/tools/revoke_all_personal_access_tokens.rb2
-rw-r--r--qa/qa/vendor/github/page/login.rb4
-rw-r--r--qa/spec/git/location_spec.rb2
-rw-r--r--qa/spec/git/repository_spec.rb15
-rw-r--r--qa/spec/helpers/stub_env.rb2
-rw-r--r--qa/spec/page/base_spec.rb2
-rw-r--r--qa/spec/page/element_spec.rb58
-rw-r--r--qa/spec/page/logging_spec.rb34
-rw-r--r--qa/spec/page/validator_spec.rb2
-rw-r--r--qa/spec/page/view_spec.rb2
-rw-r--r--qa/spec/runtime/api/client_spec.rb2
-rw-r--r--qa/spec/runtime/api/request_spec.rb2
-rw-r--r--qa/spec/runtime/env_spec.rb30
-rw-r--r--qa/spec/runtime/feature_spec.rb41
-rw-r--r--qa/spec/runtime/key/ecdsa_spec.rb2
-rw-r--r--qa/spec/runtime/key/ed25519_spec.rb2
-rw-r--r--qa/spec/runtime/key/rsa_spec.rb2
-rw-r--r--qa/spec/runtime/release_spec.rb2
-rw-r--r--qa/spec/runtime/scenario_spec.rb10
-rw-r--r--qa/spec/scenario/actable_spec.rb2
-rw-r--r--qa/spec/scenario/bootable_spec.rb11
-rw-r--r--qa/spec/scenario/template_spec.rb28
-rw-r--r--qa/spec/scenario/test/instance/all_spec.rb2
-rw-r--r--qa/spec/scenario/test/instance/smoke_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/github_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/mattermost_spec.rb11
-rw-r--r--qa/spec/scenario/test/sanity/framework_spec.rb2
-rw-r--r--qa/spec/scenario/test/sanity/selectors_spec.rb2
-rw-r--r--qa/spec/shared_examples/scenario_shared_examples.rb40
-rw-r--r--qa/spec/spec_helper.rb62
-rw-r--r--qa/spec/spec_helper_spec.rb355
-rw-r--r--qa/spec/specs/helpers/quarantine_spec.rb271
-rw-r--r--qa/spec/specs/runner_spec.rb4
-rw-r--r--rubocop/cop/active_record_association_reload.rb21
-rw-r--r--rubocop/cop/code_reuse/active_record.rb3
-rw-r--r--rubocop/cop/gitlab/finder_with_find_by.rb4
-rw-r--r--rubocop/cop/include_action_view_context.rb31
-rw-r--r--rubocop/cop/migration/update_column_in_batches.rb2
-rw-r--r--rubocop/cop/qa/element_with_pattern.rb20
-rw-r--r--rubocop/qa_helpers.rb2
-rw-r--r--rubocop/rubocop.rb3
-rw-r--r--rubocop/spec_helpers.rb6
-rwxr-xr-xscripts/build_assets_image19
-rwxr-xr-xscripts/clean-old-cached-assets4
-rw-r--r--scripts/frontend/postinstall.js2
-rw-r--r--scripts/frontend/prettier.js4
-rw-r--r--scripts/frontend/stylelint/stylelint-duplicate-selectors.js23
-rw-r--r--scripts/frontend/stylelint/stylelint-utility-classes.js24
-rw-r--r--scripts/frontend/stylelint/stylelint-utility-map.js54
-rw-r--r--scripts/frontend/stylelint/stylelint-utils.js77
-rw-r--r--scripts/frontend/stylelint/utility-classes-map.js216
-rwxr-xr-xscripts/frontend/test.js114
-rw-r--r--scripts/gitaly_test.rb16
-rwxr-xr-xscripts/insert-rspec-profiling-data47
-rwxr-xr-xscripts/lint-doc.sh2
-rwxr-xr-xscripts/lint-rugged8
-rwxr-xr-xscripts/merge-simplecov8
-rw-r--r--scripts/prepare_build.sh11
-rwxr-xr-xscripts/review_apps/review-apps.sh397
-rwxr-xr-xscripts/trigger-build2
-rw-r--r--scripts/utils.sh136
-rw-r--r--security.txt6
-rw-r--r--spec/bin/changelog_spec.rb2
-rw-r--r--spec/config/mail_room_spec.rb2
-rw-r--r--spec/config/object_store_settings_spec.rb4
-rw-r--r--spec/config/settings_spec.rb2
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb2
-rw-r--r--spec/controllers/acme_challenges_controller_spec.rb44
-rw-r--r--spec/controllers/admin/appearances_controller_spec.rb48
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb31
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb2
-rw-r--r--spec/controllers/admin/clusters/applications_controller_spec.rb149
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb540
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb2
-rw-r--r--spec/controllers/admin/gitaly_servers_controller_spec.rb2
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb8
-rw-r--r--spec/controllers/admin/health_check_controller_spec.rb2
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/identities_controller_spec.rb2
-rw-r--r--spec/controllers/admin/impersonations_controller_spec.rb2
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb12
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb21
-rw-r--r--spec/controllers/admin/services_controller_spec.rb2
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb66
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb33
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb28
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb2
-rw-r--r--spec/controllers/concerns/checks_collaboration_spec.rb2
-rw-r--r--spec/controllers/concerns/continue_params_spec.rb2
-rw-r--r--spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb2
-rw-r--r--spec/controllers/concerns/enforces_admin_authentication_spec.rb40
-rw-r--r--spec/controllers/concerns/group_tree_spec.rb2
-rw-r--r--spec/controllers/concerns/import_url_params_spec.rb56
-rw-r--r--spec/controllers/concerns/internal_redirect_spec.rb2
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb112
-rw-r--r--spec/controllers/concerns/lfs_request_spec.rb2
-rw-r--r--spec/controllers/concerns/project_unauthorized_spec.rb53
-rw-r--r--spec/controllers/concerns/routable_actions_spec.rb156
-rw-r--r--spec/controllers/concerns/send_file_upload_spec.rb7
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb52
-rw-r--r--spec/controllers/dashboard/labels_controller_spec.rb8
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb12
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb52
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb8
-rw-r--r--spec/controllers/dashboard_controller_spec.rb35
-rw-r--r--spec/controllers/explore/groups_controller_spec.rb2
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb32
-rw-r--r--spec/controllers/google_api/authorizations_controller_spec.rb62
-rw-r--r--spec/controllers/graphql_controller_spec.rb113
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb12
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb32
-rw-r--r--spec/controllers/groups/children_controller_spec.rb14
-rw-r--r--spec/controllers/groups/clusters/applications_controller_spec.rb100
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb29
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb88
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb8
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb8
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb2
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb90
-rw-r--r--spec/controllers/groups/shared_projects_controller_spec.rb4
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb2
-rw-r--r--spec/controllers/groups/variables_controller_spec.rb36
-rw-r--r--spec/controllers/groups_controller_spec.rb184
-rw-r--r--spec/controllers/health_check_controller_spec.rb2
-rw-r--r--spec/controllers/health_controller_spec.rb2
-rw-r--r--spec/controllers/help_controller_spec.rb2
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb2
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb2
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb2
-rw-r--r--spec/controllers/import/gitea_controller_spec.rb2
-rw-r--r--spec/controllers/import/github_controller_spec.rb2
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb2
-rw-r--r--spec/controllers/import/gitlab_projects_controller_spec.rb2
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb2
-rw-r--r--spec/controllers/import/phabricator_controller_spec.rb92
-rw-r--r--spec/controllers/invites_controller_spec.rb2
-rw-r--r--spec/controllers/ldap/omniauth_callbacks_controller_spec.rb4
-rw-r--r--spec/controllers/metrics_controller_spec.rb2
-rw-r--r--spec/controllers/notification_settings_controller_spec.rb2
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb2
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb2
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb41
-rw-r--r--spec/controllers/passwords_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/emails_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/personal_access_tokens_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb2
-rw-r--r--spec/controllers/profiles_controller_spec.rb2
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/autocomplete_sources_controller_spec.rb31
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb4
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb64
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb28
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb2
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb22
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb159
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb35
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb2
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb6
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb4
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb2
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb2
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb2
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb2
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb185
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb165
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb2
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb2
-rw-r--r--spec/controllers/projects/git_http_controller_spec.rb15
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb18
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb39
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb2
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb17
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb44
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb96
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb2
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb32
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb36
-rw-r--r--spec/controllers/projects/mirrors_controller_spec.rb8
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb33
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb5
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb6
-rw-r--r--spec/controllers/projects/pipelines_settings_controller_spec.rb2
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb4
-rw-r--r--spec/controllers/projects/prometheus/metrics_controller_spec.rb2
-rw-r--r--spec/controllers/projects/protected_branches_controller_spec.rb2
-rw-r--r--spec/controllers/projects/protected_tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb4
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb6
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb2
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb2
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb2
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb101
-rw-r--r--spec/controllers/projects/services_controller_spec.rb8
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb28
-rw-r--r--spec/controllers/projects/settings/integrations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb215
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb2
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb2
-rw-r--r--spec/controllers/projects/stages_controller_spec.rb72
-rw-r--r--spec/controllers/projects/tags/releases_controller_spec.rb61
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb2
-rw-r--r--spec/controllers/projects/todos_controller_spec.rb2
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb24
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb2
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb2
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb24
-rw-r--r--spec/controllers/projects_controller_spec.rb77
-rw-r--r--spec/controllers/registrations_controller_spec.rb27
-rw-r--r--spec/controllers/root_controller_spec.rb2
-rw-r--r--spec/controllers/search_controller_spec.rb65
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb111
-rw-r--r--spec/controllers/sessions_controller_spec.rb36
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb2
-rw-r--r--spec/controllers/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/uploads_controller_spec.rb2
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb10
-rw-r--r--spec/controllers/users/terms_controller_spec.rb2
-rw-r--r--spec/controllers/users_controller_spec.rb48
-rw-r--r--spec/db/importers/common_metrics_importer_spec.rb44
-rw-r--r--spec/db/schema_spec.rb4
-rw-r--r--spec/factories/ci/builds.rb20
-rw-r--r--spec/factories/ci/group_variables.rb1
-rw-r--r--spec/factories/ci/job_artifacts.rb18
-rw-r--r--spec/factories/ci/pipeline_schedule.rb10
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb1
-rw-r--r--spec/factories/ci/pipelines.rb14
-rw-r--r--spec/factories/ci/variables.rb1
-rw-r--r--spec/factories/clusters/applications/helm.rb19
-rw-r--r--spec/factories/clusters/clusters.rb9
-rw-r--r--spec/factories/clusters/providers/gcp.rb4
-rw-r--r--spec/factories/commit_statuses.rb4
-rw-r--r--spec/factories/deployments.rb2
-rw-r--r--spec/factories/groups.rb9
-rw-r--r--spec/factories/merge_requests.rb60
-rw-r--r--spec/factories/pages_domain_acme_orders.rb17
-rw-r--r--spec/factories/pages_domains.rb8
-rw-r--r--spec/factories/pool_repositories.rb1
-rw-r--r--spec/factories/project_auto_devops.rb1
-rw-r--r--spec/factories/project_daily_statistics.rb8
-rw-r--r--spec/factories/project_metrics_settings.rb8
-rw-r--r--spec/factories/projects.rb19
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/factories/suggestions.rb6
-rw-r--r--spec/factories/uploads.rb7
-rw-r--r--spec/features/admin/admin_appearance_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb1
-rw-r--r--spec/features/admin/admin_hooks_spec.rb10
-rw-r--r--spec/features/admin/admin_runners_spec.rb50
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb29
-rw-r--r--spec/features/admin/admin_sees_projects_statistics_spec.rb20
-rw-r--r--spec/features/admin/admin_settings_spec.rb51
-rw-r--r--spec/features/admin/admin_users_spec.rb7
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb43
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb89
-rw-r--r--spec/features/commits/user_uses_quick_actions_spec.rb23
-rw-r--r--spec/features/commits_spec.rb6
-rw-r--r--spec/features/cycle_analytics_spec.rb19
-rw-r--r--spec/features/dashboard/activity_spec.rb17
-rw-r--r--spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb38
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb4
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb29
-rw-r--r--spec/features/dashboard/projects_spec.rb14
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb20
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb189
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb4
-rw-r--r--spec/features/explore/groups_list_spec.rb6
-rw-r--r--spec/features/global_search_spec.rb14
-rw-r--r--spec/features/group_variables_spec.rb2
-rw-r--r--spec/features/groups/clusters/user_spec.rb3
-rw-r--r--spec/features/groups/group_page_with_external_authorization_service_spec.rb58
-rw-r--r--spec/features/groups/group_settings_spec.rb8
-rw-r--r--spec/features/groups/members/leave_group_spec.rb26
-rw-r--r--spec/features/groups/merge_requests_spec.rb4
-rw-r--r--spec/features/groups/settings/ci_cd_spec.rb45
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/help_pages_spec.rb6
-rw-r--r--spec/features/ide/user_opens_merge_request_spec.rb21
-rw-r--r--spec/features/instance_statistics/conversational_development_index_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb2
-rw-r--r--spec/features/issuables/shortcuts_issuable_spec.rb2
-rw-r--r--spec/features/issuables/sorting_list_spec.rb28
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_emoji_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb34
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb21
-rw-r--r--spec/features/issues/form_spec.rb8
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb84
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb40
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb18
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb4
-rw-r--r--spec/features/issues/user_uses_quick_actions_spec.rb387
-rw-r--r--spec/features/issues_spec.rb29
-rw-r--r--spec/features/labels_hierarchy_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb11
-rw-r--r--spec/features/markdown/gitlab_flavored_markdown_spec.rb3
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb4
-rw-r--r--spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb116
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb6
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb15
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb18
-rw-r--r--spec/features/merge_request/user_merges_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb25
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb14
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb15
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb38
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb86
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb148
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb6
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb8
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb115
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb236
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb10
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_target_branch_spec.rb45
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb8
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb3
-rw-r--r--spec/features/milestone_spec.rb28
-rw-r--r--spec/features/oauth_login_spec.rb2
-rw-r--r--spec/features/profiles/active_sessions_spec.rb48
-rw-r--r--spec/features/profiles/user_edit_preferences_spec.rb32
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb32
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb4
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb6
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb19
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb7
-rw-r--r--spec/features/projects/blobs/edit_spec.rb13
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/classification_label_on_project_pages_spec.rb22
-rw-r--r--spec/features/projects/clusters/applications_spec.rb96
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/clusters/user_spec.rb3
-rw-r--r--spec/features/projects/clusters_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb4
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb10
-rw-r--r--spec/features/projects/environments/environments_spec.rb35
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb1
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb1
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_directory_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb3
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb5
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb2
-rw-r--r--spec/features/projects/forks/fork_list_spec.rb35
-rw-r--r--spec/features/projects/graph_spec.rb2
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb128
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb4
-rw-r--r--spec/features/projects/jobs/user_browses_jobs_spec.rb6
-rw-r--r--spec/features/projects/jobs_spec.rb119
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb20
-rw-r--r--spec/features/projects/labels/user_promotes_label_spec.rb34
-rw-r--r--spec/features/projects/labels/user_removes_labels_spec.rb5
-rw-r--r--spec/features/projects/labels/user_views_labels_spec.rb5
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb9
-rw-r--r--spec/features/projects/members/invite_group_spec.rb4
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb13
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb3
-rw-r--r--spec/features/projects/new_project_spec.rb49
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb131
-rw-r--r--spec/features/projects/pages_spec.rb40
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb8
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb246
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb147
-rw-r--r--spec/features/projects/serverless/functions_spec.rb59
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb5
-rw-r--r--spec/features/projects/services/user_activates_hipchat_spec.rb40
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb35
-rw-r--r--spec/features/projects/services/user_activates_youtrack_spec.rb89
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb3
-rw-r--r--spec/features/projects/settings/external_authorization_service_settings_spec.rb21
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb1
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb83
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb31
-rw-r--r--spec/features/projects/settings/project_settings_spec.rb43
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb31
-rw-r--r--spec/features/projects/settings/user_manages_group_links_spec.rb1
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb29
-rw-r--r--spec/features/projects/settings/user_renames_a_project_spec.rb37
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb3
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb10
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb1
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb58
-rw-r--r--spec/features/projects/snippets/user_comments_on_snippet_spec.rb4
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb1
-rw-r--r--spec/features/projects/user_creates_project_spec.rb27
-rw-r--r--spec/features/projects/user_sees_sidebar_spec.rb102
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb8
-rw-r--r--spec/features/projects/wiki/user_views_wiki_pages_spec.rb89
-rw-r--r--spec/features/projects_spec.rb19
-rw-r--r--spec/features/protected_branches_spec.rb26
-rw-r--r--spec/features/protected_tags_spec.rb13
-rw-r--r--spec/features/raven_js_spec.rb4
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb83
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb36
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb4
-rw-r--r--spec/features/security/group/private_access_spec.rb21
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb4
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb2
-rw-r--r--spec/features/task_lists_spec.rb8
-rw-r--r--spec/features/u2f_spec.rb20
-rw-r--r--spec/features/user_opens_link_to_comment.rb33
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb6
-rw-r--r--spec/features/users/login_spec.rb45
-rw-r--r--spec/features/users/overview_spec.rb6
-rw-r--r--spec/features/users/show_spec.rb22
-rw-r--r--spec/features/users/signup_spec.rb44
-rw-r--r--spec/finders/admin/runners_finder_spec.rb8
-rw-r--r--spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb66
-rw-r--r--spec/finders/autocomplete/users_finder_spec.rb16
-rw-r--r--spec/finders/cluster_ancestors_finder_spec.rb29
-rw-r--r--spec/finders/clusters/knative_services_finder_spec.rb105
-rw-r--r--spec/finders/group_projects_finder_spec.rb39
-rw-r--r--spec/finders/issues_finder_spec.rb210
-rw-r--r--spec/finders/labels_finder_spec.rb13
-rw-r--r--spec/finders/members_finder_spec.rb44
-rw-r--r--spec/finders/merge_requests_finder_spec.rb533
-rw-r--r--spec/finders/milestones_finder_spec.rb2
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb80
-rw-r--r--spec/finders/snippets_finder_spec.rb253
-rw-r--r--spec/finders/todos_finder_spec.rb7
-rw-r--r--spec/finders/users_finder_spec.rb31
-rw-r--r--spec/fixtures/api/graphql/introspection.graphql92
-rw-r--r--spec/fixtures/api/schemas/board.json3
-rw-r--r--spec/fixtures/api/schemas/cluster_status.json4
-rw-r--r--spec/fixtures/api/schemas/entities/issue.json5
-rw-r--r--spec/fixtures/api/schemas/entities/issue_board.json3
-rw-r--r--spec/fixtures/api/schemas/entities/issue_boards.json3
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_basic.json14
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar.json3
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json7
-rw-r--r--spec/fixtures/api/schemas/entities/test_case.json1
-rw-r--r--spec/fixtures/api/schemas/environment.json7
-rw-r--r--spec/fixtures/api/schemas/issue.json5
-rw-r--r--spec/fixtures/api/schemas/issues.json3
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json2
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule_variable.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/artifact.json16
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/artifact_file.json12
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/deployment.json32
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/environment.json23
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/job.json64
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/label_basic.json24
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_request.json135
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json122
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json32
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json35
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json22
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/tag_release.json12
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/releases.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/runner.json24
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/tag.json2
-rw-r--r--spec/fixtures/api/schemas/variable.json2
-rw-r--r--spec/fixtures/blockquote_fence_after.md16
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml36
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json13
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json20
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json17
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json20
-rw-r--r--spec/fixtures/passphrase_x509_certificate.crt27
-rw-r--r--spec/fixtures/passphrase_x509_certificate_pk.key54
-rw-r--r--spec/fixtures/phabricator_responses/auth_failed.json1
-rw-r--r--spec/fixtures/phabricator_responses/maniphest.search.json98
-rw-r--r--spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json422
-rw-r--r--spec/fixtures/security-reports/master/gl-dast-report.json74
-rw-r--r--spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json104
-rw-r--r--spec/fixtures/security-reports/remediations/remediation.patch180
-rw-r--r--spec/fixtures/security-reports/remediations/yarn.lock104
-rw-r--r--spec/fixtures/trace/sample_trace18
-rw-r--r--spec/fixtures/valid.po3
-rw-r--r--spec/fixtures/x509_certificate.crt27
-rw-r--r--spec/fixtures/x509_certificate_pk.key51
-rw-r--r--spec/frontend/.eslintrc.yml11
-rw-r--r--spec/frontend/__mocks__/file_mock.js1
-rw-r--r--spec/frontend/activities_spec.js70
-rw-r--r--spec/frontend/api_spec.js477
-rw-r--r--spec/frontend/autosave_spec.js151
-rw-r--r--spec/frontend/behaviors/secret_values_spec.js (renamed from spec/javascripts/behaviors/secret_values_spec.js)0
-rw-r--r--spec/frontend/blob/blob_fork_suggestion_spec.js (renamed from spec/javascripts/blob/blob_fork_suggestion_spec.js)0
-rw-r--r--spec/frontend/boards/modal_store_spec.js134
-rw-r--r--spec/frontend/boards/stores/actions_spec.js67
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js91
-rw-r--r--spec/frontend/boards/stores/state_spec.js11
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js387
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js489
-rw-r--r--spec/frontend/clusters/components/applications_spec.js327
-rw-r--r--spec/frontend/clusters/components/knative_domain_editor_spec.js141
-rw-r--r--spec/frontend/clusters/components/uninstall_application_button_spec.js32
-rw-r--r--spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js56
-rw-r--r--spec/frontend/clusters/services/application_state_machine_spec.js162
-rw-r--r--spec/frontend/clusters/services/mock_data.js136
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js179
-rw-r--r--spec/frontend/cycle_analytics/limit_warning_component_spec.js (renamed from spec/javascripts/cycle_analytics/limit_warning_component_spec.js)0
-rw-r--r--spec/frontend/diffs/components/diff_stats_spec.js (renamed from spec/javascripts/diffs/components/diff_stats_spec.js)0
-rw-r--r--spec/frontend/diffs/components/edit_button_spec.js61
-rw-r--r--spec/frontend/diffs/components/hidden_files_warning_spec.js48
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js (renamed from spec/javascripts/diffs/components/no_changes_spec.js)0
-rw-r--r--spec/frontend/environment.js67
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js118
-rw-r--r--spec/frontend/error_tracking/store/mutation_spec.js (renamed from spec/javascripts/error_tracking/store/mutation_spec.js)0
-rw-r--r--spec/frontend/filtered_search/filtered_search_token_keys_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_token_keys_spec.js)0
-rw-r--r--spec/frontend/filtered_search/services/recent_searches_service_error_spec.js18
-rw-r--r--spec/frontend/filtered_search/stores/recent_searches_store_spec.js (renamed from spec/javascripts/filtered_search/stores/recent_searches_store_spec.js)0
-rw-r--r--spec/frontend/frequent_items/store/getters_spec.js (renamed from spec/javascripts/frequent_items/store/getters_spec.js)0
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js155
-rw-r--r--spec/frontend/helpers/class_spec_helper.js9
-rw-r--r--spec/frontend/helpers/fixtures.js34
-rw-r--r--spec/frontend/helpers/jest_helpers.js24
-rw-r--r--spec/frontend/helpers/jquery.js6
-rw-r--r--spec/frontend/helpers/local_storage_helper.js41
-rw-r--r--spec/frontend/helpers/locale_helper.js11
-rw-r--r--spec/frontend/helpers/monitor_helper_spec.js45
-rw-r--r--spec/frontend/helpers/scroll_into_view_promise.js28
-rw-r--r--spec/frontend/helpers/set_timeout_promise_helper.js4
-rw-r--r--spec/frontend/helpers/text_helper.js (renamed from spec/javascripts/helpers/vue_component_helper.js)0
-rw-r--r--spec/frontend/helpers/timeout.js59
-rw-r--r--spec/frontend/helpers/user_mock_data_helper.js14
-rw-r--r--spec/frontend/helpers/vue_mount_component_helper.js38
-rw-r--r--spec/frontend/helpers/vue_resource_helper.js11
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js21
-rw-r--r--spec/frontend/helpers/vuex_action_helper.js104
-rw-r--r--spec/frontend/helpers/wait_for_attribute_change.js16
-rw-r--r--spec/frontend/helpers/wait_for_promises.js1
-rw-r--r--spec/frontend/ide/lib/common/disposable_spec.js44
-rw-r--r--spec/frontend/ide/lib/diff/diff_spec.js77
-rw-r--r--spec/frontend/ide/lib/editor_options_spec.js11
-rw-r--r--spec/frontend/ide/lib/files_spec.js78
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js73
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/file_templates/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/mutations_spec.js88
-rw-r--r--spec/frontend/ide/stores/modules/pane/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/pane/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/pane/mutations_spec.js (renamed from spec/javascripts/ide/stores/modules/pane/mutations_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/pipelines/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/pipelines/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/mutations/branch_spec.js75
-rw-r--r--spec/frontend/ide/stores/mutations/merge_request_spec.js85
-rw-r--r--spec/frontend/ide/stores/mutations/project_spec.js23
-rw-r--r--spec/frontend/image_diff/view_types_spec.js (renamed from spec/javascripts/image_diff/view_types_spec.js)0
-rw-r--r--spec/frontend/import_projects/components/import_projects_table_spec.js185
-rw-r--r--spec/frontend/import_projects/components/imported_project_table_row_spec.js58
-rw-r--r--spec/frontend/import_projects/components/provider_repo_table_row_spec.js104
-rw-r--r--spec/frontend/import_projects/store/actions_spec.js284
-rw-r--r--spec/frontend/import_projects/store/getters_spec.js (renamed from spec/javascripts/import_projects/store/getters_spec.js)0
-rw-r--r--spec/frontend/import_projects/store/mutations_spec.js34
-rw-r--r--spec/frontend/issuable_suggestions/components/app_spec.js (renamed from spec/javascripts/issuable_suggestions/components/app_spec.js)0
-rw-r--r--spec/frontend/issuable_suggestions/components/item_spec.js (renamed from spec/javascripts/issuable_suggestions/components/item_spec.js)0
-rw-r--r--spec/frontend/issuable_suggestions/mock_data.js (renamed from spec/javascripts/issuable_suggestions/mock_data.js)0
-rw-r--r--spec/frontend/jobs/components/empty_state_spec.js (renamed from spec/javascripts/jobs/components/empty_state_spec.js)0
-rw-r--r--spec/frontend/jobs/components/erased_block_spec.js (renamed from spec/javascripts/jobs/components/erased_block_spec.js)0
-rw-r--r--spec/frontend/jobs/components/sidebar_detail_row_spec.js (renamed from spec/javascripts/jobs/components/sidebar_detail_row_spec.js)0
-rw-r--r--spec/frontend/jobs/components/stuck_block_spec.js (renamed from spec/javascripts/jobs/components/stuck_block_spec.js)0
-rw-r--r--spec/frontend/jobs/store/getters_spec.js243
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js194
-rw-r--r--spec/frontend/labels_select_spec.js116
-rw-r--r--spec/frontend/lib/utils/autosave_spec.js64
-rw-r--r--spec/frontend/lib/utils/cache_spec.js (renamed from spec/javascripts/lib/utils/cache_spec.js)0
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js436
-rw-r--r--spec/frontend/lib/utils/grammar_spec.js (renamed from spec/javascripts/lib/utils/grammar_spec.js)0
-rw-r--r--spec/frontend/lib/utils/image_utility_spec.js (renamed from spec/javascripts/lib/utils/image_utility_spec.js)0
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js112
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js179
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js194
-rw-r--r--spec/frontend/locale/ensure_single_line_spec.js (renamed from spec/javascripts/locale/ensure_single_line_spec.js)0
-rw-r--r--spec/frontend/locale/sprintf_spec.js (renamed from spec/javascripts/locale/sprintf_spec.js)0
-rw-r--r--spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap95
-rw-r--r--spec/frontend/mr_popover/index_spec.js46
-rw-r--r--spec/frontend/mr_popover/mr_popover_spec.js61
-rw-r--r--spec/frontend/notebook/lib/highlight_spec.js (renamed from spec/javascripts/notebook/lib/highlight_spec.js)0
-rw-r--r--spec/frontend/notes/components/discussion_actions_spec.js104
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js139
-rw-r--r--spec/frontend/notes/components/discussion_reply_placeholder_spec.js (renamed from spec/javascripts/notes/components/discussion_reply_placeholder_spec.js)0
-rw-r--r--spec/frontend/notes/components/discussion_resolve_button_spec.js (renamed from spec/javascripts/notes/components/discussion_resolve_button_spec.js)0
-rw-r--r--spec/frontend/notes/components/note_app_spec.js322
-rw-r--r--spec/frontend/notes/components/note_attachment_spec.js (renamed from spec/javascripts/notes/components/note_attachment_spec.js)0
-rw-r--r--spec/frontend/notes/components/note_edited_text_spec.js (renamed from spec/javascripts/notes/components/note_edited_text_spec.js)0
-rw-r--r--spec/frontend/notes/old_notes_spec.js1045
-rw-r--r--spec/frontend/notes/stores/utils_spec.js17
-rw-r--r--spec/frontend/operation_settings/components/external_dashboard_spec.js166
-rw-r--r--spec/frontend/operation_settings/store/mutations_spec.js19
-rw-r--r--spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js46
-rw-r--r--spec/frontend/pages/profiles/show/emoji_menu_spec.js14
-rw-r--r--spec/frontend/performance_bar/services/performance_bar_service_spec.js (renamed from spec/javascripts/performance_bar/services/performance_bar_service_spec.js)0
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js (renamed from spec/javascripts/pipelines/blank_state_spec.js)0
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js (renamed from spec/javascripts/pipelines/empty_state_spec.js)0
-rw-r--r--spec/frontend/pipelines/pipeline_store_spec.js (renamed from spec/javascripts/pipelines/pipeline_store_spec.js)0
-rw-r--r--spec/frontend/pipelines/pipelines_store_spec.js (renamed from spec/javascripts/pipelines/pipelines_store_spec.js)0
-rw-r--r--spec/frontend/registry/getters_spec.js (renamed from spec/javascripts/registry/getters_spec.js)0
-rw-r--r--spec/frontend/reports/components/report_item_spec.js33
-rw-r--r--spec/frontend/reports/components/report_link_spec.js (renamed from spec/javascripts/reports/components/report_link_spec.js)0
-rw-r--r--spec/frontend/reports/components/report_section_spec.js240
-rw-r--r--spec/frontend/reports/store/utils_spec.js (renamed from spec/javascripts/reports/store/utils_spec.js)0
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js44
-rw-r--r--spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap35
-rw-r--r--spec/frontend/repository/components/table/index_spec.js80
-rw-r--r--spec/frontend/repository/components/table/parent_row_spec.js64
-rw-r--r--spec/frontend/repository/components/table/row_spec.js101
-rw-r--r--spec/frontend/repository/router_spec.js23
-rw-r--r--spec/frontend/repository/utils/icon_spec.js23
-rw-r--r--spec/frontend/repository/utils/title_spec.js15
-rw-r--r--spec/frontend/serverless/components/area_spec.js122
-rw-r--r--spec/frontend/serverless/components/environment_row_spec.js74
-rw-r--r--spec/frontend/serverless/components/function_details_spec.js117
-rw-r--r--spec/frontend/serverless/components/function_row_spec.js32
-rw-r--r--spec/frontend/serverless/components/functions_spec.js130
-rw-r--r--spec/frontend/serverless/components/missing_prometheus_spec.js41
-rw-r--r--spec/frontend/serverless/components/pod_box_spec.js23
-rw-r--r--spec/frontend/serverless/components/url_spec.js27
-rw-r--r--spec/frontend/serverless/mock_data.js142
-rw-r--r--spec/frontend/serverless/store/actions_spec.js90
-rw-r--r--spec/frontend/serverless/store/getters_spec.js43
-rw-r--r--spec/frontend/serverless/store/mutations_spec.js86
-rw-r--r--spec/frontend/serverless/utils.js20
-rw-r--r--spec/frontend/sidebar/confidential_edit_buttons_spec.js (renamed from spec/javascripts/sidebar/confidential_edit_buttons_spec.js)0
-rw-r--r--spec/frontend/sidebar/confidential_edit_form_buttons_spec.js (renamed from spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js)0
-rw-r--r--spec/frontend/sidebar/lock/edit_form_spec.js (renamed from spec/javascripts/sidebar/lock/edit_form_spec.js)0
-rw-r--r--spec/frontend/test_setup.js56
-rw-r--r--spec/frontend/u2f/util_spec.js (renamed from spec/javascripts/u2f/util_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js (renamed from spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js (renamed from spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js (renamed from spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js (renamed from spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js136
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js103
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap21
-rw-r--r--spec/frontend/vue_shared/components/callout_spec.js (renamed from spec/javascripts/vue_shared/components/callout_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/code_block_spec.js (renamed from spec/javascripts/vue_shared/components/code_block_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js (renamed from spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/identicon_spec.js (renamed from spec/javascripts/vue_shared/components/identicon_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_assignees_spec.js114
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_milestone_spec.js172
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js198
-rw-r--r--spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js (renamed from spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js103
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js98
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js40
-rw-r--r--spec/frontend/vue_shared/components/notes/placeholder_note_spec.js51
-rw-r--r--spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/notes/system_note_spec.js66
-rw-r--r--spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/pagination_links_spec.js (renamed from spec/javascripts/vue_shared/components/pagination_links_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart_container_spec.js64
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js36
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js86
-rw-r--r--spec/frontend/vue_shared/components/sidebar/date_picker_spec.js121
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js134
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js97
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js107
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js75
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js40
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js42
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js43
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js97
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js139
-rw-r--r--spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js32
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js (renamed from spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js)0
-rw-r--r--spec/frontend/vue_shared/droplab_dropdown_button_spec.js136
-rw-r--r--spec/frontend/vuex_shared/modules/modal/mutations_spec.js49
-rw-r--r--spec/graphql/features/authorization_spec.rb335
-rw-r--r--spec/graphql/gitlab_schema_spec.rb136
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb17
-rw-r--r--spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb8
-rw-r--r--spec/graphql/resolvers/group_resolver_spec.rb32
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb160
-rw-r--r--spec/graphql/resolvers/metadata_resolver_spec.rb11
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb69
-rw-r--r--spec/graphql/resolvers/project_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/tree_resolver_spec.rb35
-rw-r--r--spec/graphql/types/base_field_spec.rb56
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb11
-rw-r--r--spec/graphql/types/group_type_spec.rb11
-rw-r--r--spec/graphql/types/issue_type_spec.rb8
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb11
-rw-r--r--spec/graphql/types/metadata_type_spec.rb5
-rw-r--r--spec/graphql/types/milestone_type_spec.rb9
-rw-r--r--spec/graphql/types/namespace_type_spec.rb9
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb4
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb4
-rw-r--r--spec/graphql/types/project_statistics_type_spec.rb11
-rw-r--r--spec/graphql/types/project_type_spec.rb16
-rw-r--r--spec/graphql/types/query_type_spec.rb25
-rw-r--r--spec/graphql/types/repository_type_spec.rb11
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb9
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb9
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb9
-rw-r--r--spec/graphql/types/tree/tree_type_spec.rb9
-rw-r--r--spec/graphql/types/tree/type_enum_spec.rb11
-rw-r--r--spec/graphql/types/user_type_spec.rb9
-rw-r--r--spec/haml_lint/linter/no_plain_nodes_spec.rb56
-rw-r--r--spec/helpers/appearances_helper_spec.rb8
-rw-r--r--spec/helpers/auth_helper_spec.rb40
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb138
-rw-r--r--spec/helpers/blob_helper_spec.rb21
-rw-r--r--spec/helpers/clusters_helper_spec.rb33
-rw-r--r--spec/helpers/dashboard_helper_spec.rb6
-rw-r--r--spec/helpers/emails_helper_spec.rb93
-rw-r--r--spec/helpers/environments_helper_spec.rb49
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb2
-rw-r--r--spec/helpers/groups/group_members_helper_spec.rb17
-rw-r--r--spec/helpers/groups_helper_spec.rb35
-rw-r--r--spec/helpers/icons_helper_spec.rb6
-rw-r--r--spec/helpers/issuables_helper_spec.rb6
-rw-r--r--spec/helpers/labels_helper_spec.rb87
-rw-r--r--spec/helpers/markup_helper_spec.rb3
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/helpers/namespaces_helper_spec.rb72
-rw-r--r--spec/helpers/nav_helper_spec.rb12
-rw-r--r--spec/helpers/page_layout_helper_spec.rb8
-rw-r--r--spec/helpers/preferences_helper_spec.rb16
-rw-r--r--spec/helpers/projects_helper_spec.rb98
-rw-r--r--spec/helpers/search_helper_spec.rb2
-rw-r--r--spec/helpers/storage_helper_spec.rb25
-rw-r--r--spec/helpers/tracking_helper_spec.rb11
-rw-r--r--spec/helpers/version_check_helper_spec.rb8
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb45
-rw-r--r--spec/helpers/wiki_helper_spec.rb52
-rw-r--r--spec/initializers/secret_token_spec.rb4
-rw-r--r--spec/javascripts/activities_spec.js71
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js2
-rw-r--r--spec/javascripts/api_spec.js431
-rw-r--r--spec/javascripts/autosave_spec.js154
-rw-r--r--spec/javascripts/awards_handler_spec.js4
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/badges/store/actions_spec.js4
-rw-r--r--spec/javascripts/behaviors/copy_as_gfm_spec.js4
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js4
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js4
-rw-r--r--spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js8
-rw-r--r--spec/javascripts/blob/blob_file_dropzone_spec.js4
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js4
-rw-r--r--spec/javascripts/blob/pdf/index_spec.js8
-rw-r--r--spec/javascripts/blob/sketch/index_spec.js4
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js4
-rw-r--r--spec/javascripts/boards/board_card_spec.js4
-rw-r--r--spec/javascripts/boards/board_list_common_spec.js58
-rw-r--r--spec/javascripts/boards/board_list_spec.js53
-rw-r--r--spec/javascripts/boards/boards_store_spec.js91
-rw-r--r--spec/javascripts/boards/components/board_spec.js16
-rw-r--r--spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js43
-rw-r--r--spec/javascripts/boards/components/issue_due_date_spec.js4
-rw-r--r--spec/javascripts/boards/issue_card_spec.js10
-rw-r--r--spec/javascripts/boards/issue_spec.js6
-rw-r--r--spec/javascripts/boards/list_spec.js7
-rw-r--r--spec/javascripts/boards/mock_data.js9
-rw-r--r--spec/javascripts/boards/modal_store_spec.js134
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js4
-rw-r--r--spec/javascripts/breakpoints_spec.js14
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js18
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js94
-rw-r--r--spec/javascripts/ci_variable_list/native_form_variable_list_spec.js4
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js268
-rw-r--r--spec/javascripts/clusters/components/application_row_spec.js406
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js313
-rw-r--r--spec/javascripts/clusters/services/mock_data.js118
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js139
-rw-r--r--spec/javascripts/collapsed_sidebar_todo_spec.js2
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js4
-rw-r--r--spec/javascripts/diffs/components/app_spec.js405
-rw-r--r--spec/javascripts/diffs/components/changed_files_dropdown_spec.js1
-rw-r--r--spec/javascripts/diffs/components/commit_item_spec.js6
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js153
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js20
-rw-r--r--spec/javascripts/diffs/components/diff_content_spec.js16
-rw-r--r--spec/javascripts/diffs/components/diff_discussions_spec.js111
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js222
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js35
-rw-r--r--spec/javascripts/diffs/components/edit_button_spec.js1
-rw-r--r--spec/javascripts/diffs/components/hidden_files_warning_spec.js1
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js2
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js6
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js3
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js1
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js264
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js20
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js174
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js45
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_form_spec.js111
-rw-r--r--spec/javascripts/environments/confirm_rollback_modal_spec.js70
-rw-r--r--spec/javascripts/environments/environment_item_spec.js20
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js32
-rw-r--r--spec/javascripts/environments/environment_table_spec.js234
-rw-r--r--spec/javascripts/environments/environments_app_spec.js7
-rw-r--r--spec/javascripts/environments/environments_store_spec.js74
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js16
-rw-r--r--spec/javascripts/error_tracking/components/error_tracking_list_spec.js100
-rw-r--r--spec/javascripts/error_tracking_settings/components/app_spec.js63
-rw-r--r--spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js91
-rw-r--r--spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js109
-rw-r--r--spec/javascripts/error_tracking_settings/mock.js92
-rw-r--r--spec/javascripts/error_tracking_settings/store/actions_spec.js191
-rw-r--r--spec/javascripts/error_tracking_settings/store/getters_spec.js93
-rw-r--r--spec/javascripts/error_tracking_settings/store/mutation_spec.js82
-rw-r--r--spec/javascripts/error_tracking_settings/utils_spec.js29
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js2
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js339
-rw-r--r--spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js18
-rw-r--r--spec/javascripts/filtered_search/visual_token_value_spec.js383
-rw-r--r--spec/javascripts/fixtures/.gitignore3
-rw-r--r--spec/javascripts/fixtures/abuse_reports.rb3
-rw-r--r--spec/javascripts/fixtures/admin_users.rb3
-rw-r--r--spec/javascripts/fixtures/ajax_loading_spinner.html.haml2
-rw-r--r--spec/javascripts/fixtures/application_settings.rb3
-rw-r--r--spec/javascripts/fixtures/autocomplete_sources.rb39
-rw-r--r--spec/javascripts/fixtures/balsamiq.rb18
-rw-r--r--spec/javascripts/fixtures/balsamiq_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/blob.rb3
-rw-r--r--spec/javascripts/fixtures/boards.rb3
-rw-r--r--spec/javascripts/fixtures/branches.rb3
-rw-r--r--spec/javascripts/fixtures/clusters.rb3
-rw-r--r--spec/javascripts/fixtures/commit.rb3
-rw-r--r--spec/javascripts/fixtures/create_item_dropdown.html.haml13
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb3
-rw-r--r--spec/javascripts/fixtures/environments/table.html.haml11
-rw-r--r--spec/javascripts/fixtures/event_filter.html.haml25
-rw-r--r--spec/javascripts/fixtures/gl_dropdown.html.haml17
-rw-r--r--spec/javascripts/fixtures/gl_field_errors.html.haml15
-rw-r--r--spec/javascripts/fixtures/groups.rb6
-rw-r--r--spec/javascripts/fixtures/issuable_filter.html.haml8
-rw-r--r--spec/javascripts/fixtures/issue_sidebar_label.html.haml16
-rw-r--r--spec/javascripts/fixtures/issues.rb79
-rw-r--r--spec/javascripts/fixtures/jobs.rb6
-rw-r--r--spec/javascripts/fixtures/labels.rb6
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml11
-rw-r--r--spec/javascripts/fixtures/linked_tabs.html.haml13
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb43
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb15
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/mini_dropdown_graph.html.haml10
-rw-r--r--spec/javascripts/fixtures/notebook_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml6
-rw-r--r--spec/javascripts/fixtures/pdf.rb18
-rw-r--r--spec/javascripts/fixtures/pdf_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/pipeline_graph.html.haml14
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb6
-rw-r--r--spec/javascripts/fixtures/pipelines.html.haml12
-rw-r--r--spec/javascripts/fixtures/pipelines.rb3
-rw-r--r--spec/javascripts/fixtures/project_select_combo_button.html.haml6
-rw-r--r--spec/javascripts/fixtures/projects.rb15
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb3
-rw-r--r--spec/javascripts/fixtures/raw.rb27
-rw-r--r--spec/javascripts/fixtures/search.rb3
-rw-r--r--spec/javascripts/fixtures/search_autocomplete.html.haml9
-rw-r--r--spec/javascripts/fixtures/services.rb3
-rw-r--r--spec/javascripts/fixtures/sessions.rb3
-rw-r--r--spec/javascripts/fixtures/signin_tabs.html.haml5
-rw-r--r--spec/javascripts/fixtures/sketch_viewer.html.haml2
-rw-r--r--spec/javascripts/fixtures/snippet.rb3
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/fixtures/static/ajax_loading_spinner.html3
-rw-r--r--spec/javascripts/fixtures/static/balsamiq_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/create_item_dropdown.html11
-rw-r--r--spec/javascripts/fixtures/static/environments/table.html15
-rw-r--r--spec/javascripts/fixtures/static/event_filter.html44
-rw-r--r--spec/javascripts/fixtures/static/gl_dropdown.html26
-rw-r--r--spec/javascripts/fixtures/static/gl_field_errors.html22
-rw-r--r--spec/javascripts/fixtures/static/images/green_box.png (renamed from spec/javascripts/fixtures/images/green_box.png)bin1306 -> 1306 bytes
-rw-r--r--spec/javascripts/fixtures/static/images/one_white_pixel.png (renamed from spec/javascripts/fixtures/one_white_pixel.png)bin68 -> 68 bytes
-rw-r--r--spec/javascripts/fixtures/static/images/red_box.png (renamed from spec/javascripts/fixtures/images/red_box.png)bin1305 -> 1305 bytes
-rw-r--r--spec/javascripts/fixtures/static/issuable_filter.html9
-rw-r--r--spec/javascripts/fixtures/static/issue_sidebar_label.html26
-rw-r--r--spec/javascripts/fixtures/static/line_highlighter.html107
-rw-r--r--spec/javascripts/fixtures/static/linked_tabs.html20
-rw-r--r--spec/javascripts/fixtures/static/merge_requests_show.html15
-rw-r--r--spec/javascripts/fixtures/static/mini_dropdown_graph.html13
-rw-r--r--spec/javascripts/fixtures/static/notebook_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/oauth_remember_me.html6
-rw-r--r--spec/javascripts/fixtures/static/pdf_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/pipeline_graph.html24
-rw-r--r--spec/javascripts/fixtures/static/pipelines.html3
-rw-r--r--spec/javascripts/fixtures/static/project_select_combo_button.html9
-rw-r--r--spec/javascripts/fixtures/static/projects.json (renamed from spec/javascripts/fixtures/projects.json)0
-rw-r--r--spec/javascripts/fixtures/static/search_autocomplete.html15
-rw-r--r--spec/javascripts/fixtures/static/signin_tabs.html8
-rw-r--r--spec/javascripts/fixtures/static/sketch_viewer.html3
-rw-r--r--spec/javascripts/fixtures/static_fixtures.rb31
-rw-r--r--spec/javascripts/fixtures/todos.rb6
-rw-r--r--spec/javascripts/fixtures/u2f.rb6
-rw-r--r--spec/javascripts/fly_out_nav_spec.js5
-rw-r--r--spec/javascripts/frequent_items/components/app_spec.js2
-rw-r--r--spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js62
-rw-r--r--spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js25
-rw-r--r--spec/javascripts/gl_dropdown_spec.js8
-rw-r--r--spec/javascripts/gl_field_errors_spec.js4
-rw-r--r--spec/javascripts/groups/components/app_spec.js2
-rw-r--r--spec/javascripts/header_spec.js2
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js2
-rw-r--r--spec/javascripts/helpers/text_helper.js18
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js41
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js73
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js7
-rw-r--r--spec/javascripts/ide/components/error_message_spec.js2
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js2
-rw-r--r--spec/javascripts/ide/components/ide_review_spec.js2
-rw-r--r--spec/javascripts/ide/components/ide_spec.js92
-rw-r--r--spec/javascripts/ide/components/ide_tree_list_spec.js59
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_button_spec.js2
-rw-r--r--spec/javascripts/ide/components/new_dropdown/index_spec.js4
-rw-r--r--spec/javascripts/ide/components/new_dropdown/modal_spec.js67
-rw-r--r--spec/javascripts/ide/components/new_dropdown/upload_spec.js4
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js31
-rw-r--r--spec/javascripts/ide/lib/common/disposable_spec.js44
-rw-r--r--spec/javascripts/ide/lib/diff/diff_spec.js80
-rw-r--r--spec/javascripts/ide/lib/editor_options_spec.js11
-rw-r--r--spec/javascripts/ide/mock_data.js1
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js109
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js142
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js189
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js72
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js97
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js32
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js243
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js37
-rw-r--r--spec/javascripts/ide/stores/modules/commit/mutations_spec.js42
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/actions_spec.js54
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js69
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js106
-rw-r--r--spec/javascripts/ide/stores/mutations/branch_spec.js40
-rw-r--r--spec/javascripts/ide/stores/mutations/merge_request_spec.js67
-rw-r--r--spec/javascripts/ide/stores/mutations/tree_spec.js61
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js14
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js144
-rw-r--r--spec/javascripts/import_projects/components/import_projects_table_spec.js186
-rw-r--r--spec/javascripts/import_projects/components/imported_project_table_row_spec.js50
-rw-r--r--spec/javascripts/import_projects/components/provider_repo_table_row_spec.js91
-rw-r--r--spec/javascripts/import_projects/store/actions_spec.js284
-rw-r--r--spec/javascripts/import_projects/store/mutations_spec.js34
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js51
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js4
-rw-r--r--spec/javascripts/issue_show/components/fields/description_spec.js4
-rw-r--r--spec/javascripts/issue_show/components/fields/title_spec.js4
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js25
-rw-r--r--spec/javascripts/issue_spec.js11
-rw-r--r--spec/javascripts/jobs/components/artifacts_block_spec.js2
-rw-r--r--spec/javascripts/jobs/components/commit_block_spec.js3
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js41
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js16
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js189
-rw-r--r--spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js37
-rw-r--r--spec/javascripts/jobs/mock_data.js302
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js105
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js188
-rw-r--r--spec/javascripts/jobs/store/mutations_spec.js230
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js4
-rw-r--r--spec/javascripts/labels_select_spec.js52
-rw-r--r--spec/javascripts/lazy_loader_spec.js6
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js59
-rw-r--r--spec/javascripts/lib/utils/datetime_utility_spec.js416
-rw-r--r--spec/javascripts/lib/utils/higlight_spec.js43
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js90
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js154
-rw-r--r--spec/javascripts/lib/utils/url_utility_spec.js110
-rw-r--r--spec/javascripts/line_highlighter_spec.js4
-rw-r--r--spec/javascripts/matchers.js2
-rw-r--r--spec/javascripts/merge_request_spec.js10
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js6
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js4
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js93
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js28
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js316
-rw-r--r--spec/javascripts/monitoring/helpers.js8
-rw-r--r--spec/javascripts/monitoring/mock_data.js5876
-rw-r--r--spec/javascripts/monitoring/monitoring_store_spec.js35
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js158
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js92
-rw-r--r--spec/javascripts/monitoring/utils_spec.js29
-rw-r--r--spec/javascripts/new_branch_spec.js4
-rw-r--r--spec/javascripts/notes/components/discussion_filter_note_spec.js93
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js22
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js85
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js324
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js241
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js37
-rw-r--r--spec/javascripts/notes/mock_data.js6
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js175
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js8
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js17
-rw-r--r--spec/javascripts/notes_spec.js1048
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js4
-rw-r--r--spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js46
-rw-r--r--spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js2
-rw-r--r--spec/javascripts/pages/admin/users/new/index_spec.js2
-rw-r--r--spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js244
-rw-r--r--spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js4
-rw-r--r--spec/javascripts/pdf/index_spec.js10
-rw-r--r--spec/javascripts/pdf/page_spec.js10
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js22
-rw-r--r--spec/javascripts/persistent_user_callout_spec.js88
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js28
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js53
-rw-r--r--spec/javascripts/pipelines/mock_data.js1
-rw-r--r--spec/javascripts/pipelines/pipeline_triggerer_spec.js54
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js65
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js8
-rw-r--r--spec/javascripts/pipelines/stage_spec.js16
-rw-r--r--spec/javascripts/pipelines_spec.js4
-rw-r--r--spec/javascripts/project_select_combo_button_spec.js2
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js44
-rw-r--r--spec/javascripts/projects/project_new_spec.js14
-rw-r--r--spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js2
-rw-r--r--spec/javascripts/raven/index_spec.js10
-rw-r--r--spec/javascripts/raven/raven_config_spec.js10
-rw-r--r--spec/javascripts/read_more_spec.js2
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js89
-rw-r--r--spec/javascripts/related_merge_requests/store/actions_spec.js110
-rw-r--r--spec/javascripts/related_merge_requests/store/mutations_spec.js49
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js10
-rw-r--r--spec/javascripts/reports/components/modal_spec.js2
-rw-r--r--spec/javascripts/reports/components/report_section_spec.js200
-rw-r--r--spec/javascripts/reports/components/test_issue_body_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js2
-rw-r--r--spec/javascripts/search_autocomplete_spec.js4
-rw-r--r--spec/javascripts/search_spec.js2
-rw-r--r--spec/javascripts/serverless/components/environment_row_spec.js81
-rw-r--r--spec/javascripts/serverless/components/function_row_spec.js33
-rw-r--r--spec/javascripts/serverless/components/functions_spec.js68
-rw-r--r--spec/javascripts/serverless/components/url_spec.js28
-rw-r--r--spec/javascripts/serverless/mock_data.js79
-rw-r--r--spec/javascripts/serverless/stores/serverless_store_spec.js36
-rw-r--r--spec/javascripts/settings_panels_spec.js4
-rw-r--r--spec/javascripts/shortcuts_spec.js2
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js108
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js4
-rw-r--r--spec/javascripts/sidebar/todo_spec.js6
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js75
-rw-r--r--spec/javascripts/test_constants.js10
-rw-r--r--spec/javascripts/todos_spec.js4
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js4
-rw-r--r--spec/javascripts/u2f/register_spec.js4
-rw-r--r--spec/javascripts/user_popovers_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js77
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js32
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js102
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js2
-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_commits_header_spec.js110
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js4
-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.js189
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js54
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js99
-rw-r--r--spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js103
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/ci_badge_link_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js90
-rw-r--r--spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js16
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js7
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js60
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js114
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js234
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_warning_spec.js55
-rw-r--r--spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js111
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js75
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js66
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestions_spec.js109
-rw-r--r--spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js51
-rw-r--r--spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js27
-rw-r--r--spec/javascripts/vue_shared/components/notes/system_note_spec.js59
-rw-r--r--spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js110
-rw-r--r--spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js132
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js36
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js86
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js121
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js130
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js86
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js108
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js76
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js40
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js42
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js43
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js90
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js105
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js7
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js32
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js194
-rw-r--r--spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js20
-rw-r--r--spec/javascripts/vue_shared/translate_spec.js2
-rw-r--r--spec/javascripts/vuex_shared/modules/modal/mutations_spec.js49
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/api/entities/job_request/image_spec.rb31
-rw-r--r--spec/lib/api/entities/job_request/port_spec.rb22
-rw-r--r--spec/lib/api/helpers/custom_validators_spec.rb27
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb34
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb34
-rw-r--r--spec/lib/api/helpers_spec.rb12
-rw-r--r--spec/lib/backup/uploads_spec.rb18
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb4
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb43
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb25
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb21
-rw-r--r--spec/lib/banzai/filter/output_safety_spec.rb29
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb14
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb32
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb42
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb28
-rw-r--r--spec/lib/banzai/redactor_spec.rb32
-rw-r--r--spec/lib/banzai/renderer_spec.rb14
-rw-r--r--spec/lib/banzai/suggestions_parser_spec.rb32
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb4
-rw-r--r--spec/lib/event_filter_spec.rb8
-rw-r--r--spec/lib/extracts_path_spec.rb30
-rw-r--r--spec/lib/forever_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb153
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb11
-rw-r--r--spec/lib/gitlab/auth_spec.rb75
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb194
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb253
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb56
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb48
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb62
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb40
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb10
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb61
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb1
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb114
-rw-r--r--spec/lib/gitlab/checks/lfs_check_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb53
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/build/port_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb67
-rw-r--r--spec/lib/gitlab/ci/config/entry/port_spec.rb173
-rw-r--r--spec/lib/gitlab/ci/config/entry/ports_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb80
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb87
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb69
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb140
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb73
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb122
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb225
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/status/build/preparing_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb74
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb102
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb4
-rw-r--r--spec/lib/gitlab/correlation_id_spec.rb77
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb17
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb119
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb101
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb70
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb39
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb9
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb11
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb19
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb16
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb4
-rw-r--r--spec/lib/gitlab/database_spec.rb52
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb4
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb9
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb4
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb18
-rw-r--r--spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb44
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb5
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb208
-rw-r--r--spec/lib/gitlab/diff/suggestion_diff_spec.rb55
-rw-r--r--spec/lib/gitlab/diff/suggestion_spec.rb153
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb134
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb110
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb13
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb18
-rw-r--r--spec/lib/gitlab/external_authorization/access_spec.rb142
-rw-r--r--spec/lib/gitlab/external_authorization/cache_spec.rb48
-rw-r--r--spec/lib/gitlab/external_authorization/client_spec.rb97
-rw-r--r--spec/lib/gitlab/external_authorization/logger_spec.rb45
-rw-r--r--spec/lib/gitlab/external_authorization/response_spec.rb52
-rw-r--r--spec/lib/gitlab/external_authorization_spec.rb54
-rw-r--r--spec/lib/gitlab/fake_application_settings_spec.rb31
-rw-r--r--spec/lib/gitlab/favicon_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb18
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb79
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb8
-rw-r--r--spec/lib/gitlab/git/gitmodules_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb41
-rw-r--r--spec/lib/gitlab/git/pre_receive_error_spec.rb16
-rw-r--r--spec/lib/gitlab/git/repository_cleaner_spec.rb73
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb337
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb76
-rw-r--r--spec/lib/gitlab/git/wiki_spec.rb16
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb92
-rw-r--r--spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb15
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb20
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb21
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb41
-rw-r--r--spec/lib/gitlab/gitaly_client/storage_settings_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/wiki_service_spec.rb6
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb80
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb24
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb78
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb16
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb11
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb64
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb4
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb132
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb67
-rw-r--r--spec/lib/gitlab/graphql/authorize_spec.rb20
-rw-r--r--spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb5
-rw-r--r--spec/lib/gitlab/graphql/generic_tracing_spec.rb67
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb25
-rw-r--r--spec/lib/gitlab/graphql/representation/tree_entry_spec.rb20
-rw-r--r--spec/lib/gitlab/graphql_logger_spec.rb40
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb69
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb166
-rw-r--r--spec/lib/gitlab/hook_data/issuable_builder_spec.rb6
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb120
-rw-r--r--spec/lib/gitlab/http_spec.rb28
-rw-r--r--spec/lib/gitlab/import/merge_request_helpers_spec.rb73
-rw-r--r--spec/lib/gitlab/import/set_async_jid_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml17
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb16
-rw-r--r--spec/lib/gitlab/import_export/project.json46
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb33
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml27
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb13
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb12
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb82
-rw-r--r--spec/lib/gitlab/json_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/config_map_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb46
-rw-r--r--spec/lib/gitlab/kubernetes/helm/base_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/certificate_spec.rb6
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb72
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb20
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb30
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb27
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_token_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb34
-rw-r--r--spec/lib/gitlab/legacy_github_import/project_creator_spec.rb16
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb1
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb17
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb162
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb41
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb98
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb179
-rw-r--r--spec/lib/gitlab/markdown_cache/field_data_spec.rb15
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb76
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb68
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb62
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb105
-rw-r--r--spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb62
-rw-r--r--spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb32
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb123
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb30
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb25
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb24
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb229
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb29
-rw-r--r--spec/lib/gitlab/namespaced_session_store_spec.rb22
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb40
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb30
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb4
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb12
-rw-r--r--spec/lib/gitlab/phabricator_import/base_worker_spec.rb74
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb66
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/client_spec.rb59
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb39
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb79
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb27
-rw-r--r--spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb16
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb32
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb53
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb54
-rw-r--r--spec/lib/gitlab/phabricator_import/project_creator_spec.rb58
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb33
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb46
-rw-r--r--spec/lib/gitlab/profiler_spec.rb14
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb32
-rw-r--r--spec/lib/gitlab/project_template_spec.rb5
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb30
-rw-r--r--spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb26
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb28
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb107
-rw-r--r--spec/lib/gitlab/push_options_spec.rb103
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb37
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb26
-rw-r--r--spec/lib/gitlab/rack_timeout_observer_spec.rb58
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb22
-rw-r--r--spec/lib/gitlab/request_context_spec.rb27
-rw-r--r--spec/lib/gitlab/route_map_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb120
-rw-r--r--spec/lib/gitlab/search_results_spec.rb44
-rw-r--r--spec/lib/gitlab/sentry_spec.rb9
-rw-r--r--spec/lib/gitlab/session_spec.rb27
-rw-r--r--spec/lib/gitlab/shell_spec.rb595
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb74
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb88
-rw-r--r--spec/lib/gitlab/sidekiq_signals_spec.rb69
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb7
-rw-r--r--spec/lib/gitlab/tracing/factory_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/grpc_interceptor_spec.rb47
-rw-r--r--spec/lib/gitlab/tracing/jaeger_factory_spec.rb71
-rw-r--r--spec/lib/gitlab/tracing/rack_middleware_spec.rb62
-rw-r--r--spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb147
-rw-r--r--spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb73
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing_spec.rb6
-rw-r--r--spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb118
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb74
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb89
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb36
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb29
-rw-r--r--spec/lib/gitlab/user_extractor_spec.rb20
-rw-r--r--spec/lib/gitlab/utils_spec.rb18
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb8
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb102
-rw-r--r--spec/lib/gitlab_spec.rb46
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb12
-rw-r--r--spec/lib/mattermost/session_spec.rb7
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb2
-rw-r--r--spec/lib/quality/kubernetes_client_spec.rb4
-rw-r--r--spec/lib/quality/seeders/issues_spec.rb18
-rw-r--r--spec/lib/quality/test_level_spec.rb105
-rw-r--r--spec/lib/sentry/client_spec.rb129
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb25
-rw-r--r--spec/mailers/email_rejection_mailer_spec.rb16
-rw-r--r--spec/mailers/emails/auto_devops_spec.rb3
-rw-r--r--spec/mailers/emails/issues_spec.rb9
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb30
-rw-r--r--spec/mailers/notify_spec.rb207
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb7
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb2
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb24
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb22
-rw-r--r--spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb35
-rw-r--r--spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb34
-rw-r--r--spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb19
-rw-r--r--spec/migrations/delete_inconsistent_internal_id_records_spec.rb83
-rw-r--r--spec/migrations/enqueue_reset_merge_status_spec.rb49
-rw-r--r--spec/migrations/enqueue_verify_pages_domain_workers_spec.rb6
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb12
-rw-r--r--spec/migrations/generate_missing_routes_spec.rb2
-rw-r--r--spec/migrations/issues_moved_to_id_foreign_key_spec.rb15
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb6
-rw-r--r--spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb26
-rw-r--r--spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb24
-rw-r--r--spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb12
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb32
-rw-r--r--spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb20
-rw-r--r--spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb12
-rw-r--r--spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb22
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb6
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb6
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb35
-rw-r--r--spec/migrations/remove_orphaned_label_links_spec.rb6
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb46
-rw-r--r--spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb47
-rw-r--r--spec/migrations/schedule_runners_token_encryption_spec.rb2
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_spec.rb81
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb57
-rw-r--r--spec/migrations/truncate_user_fullname_spec.rb21
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/active_session_spec.rb53
-rw-r--r--spec/models/appearance_spec.rb20
-rw-r--r--spec/models/application_record_spec.rb25
-rw-r--r--spec/models/application_setting/term_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb305
-rw-r--r--spec/models/award_emoji_spec.rb2
-rw-r--r--spec/models/badge_spec.rb4
-rw-r--r--spec/models/badges/group_badge_spec.rb2
-rw-r--r--spec/models/badges/project_badge_spec.rb4
-rw-r--r--spec/models/blob_spec.rb17
-rw-r--r--spec/models/blob_viewer/base_spec.rb2
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb2
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/license_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/readme_spec.rb2
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb2
-rw-r--r--spec/models/board_group_recent_visit_spec.rb22
-rw-r--r--spec/models/board_project_recent_visit_spec.rb22
-rw-r--r--spec/models/board_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb8
-rw-r--r--spec/models/chat_name_spec.rb2
-rw-r--r--spec/models/chat_team_spec.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb2
-rw-r--r--spec/models/ci/bridge_spec.rb19
-rw-r--r--spec/models/ci/build_metadata_spec.rb2
-rw-r--r--spec/models/ci/build_runner_session_spec.rb18
-rw-r--r--spec/models/ci/build_spec.rb655
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb4
-rw-r--r--spec/models/ci/build_trace_chunks/database_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/fog_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/redis_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_name_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_spec.rb2
-rw-r--r--spec/models/ci/group_spec.rb2
-rw-r--r--spec/models/ci/group_variable_spec.rb6
-rw-r--r--spec/models/ci/job_artifact_spec.rb61
-rw-r--r--spec/models/ci/legacy_stage_spec.rb4
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb158
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb4
-rw-r--r--spec/models/ci/pipeline_spec.rb700
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb5
-rw-r--r--spec/models/ci/runner_spec.rb9
-rw-r--r--spec/models/ci/stage_spec.rb4
-rw-r--r--spec/models/ci/trigger_request_spec.rb2
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/ci/variable_spec.rb6
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb43
-rw-r--r--spec/models/clusters/applications/helm_spec.rb14
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb22
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb31
-rw-r--r--spec/models/clusters/applications/knative_spec.rb124
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb71
-rw-r--r--spec/models/clusters/applications/runner_spec.rb79
-rw-r--r--spec/models/clusters/cluster_spec.rb282
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb8
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb79
-rw-r--r--spec/models/clusters/project_spec.rb3
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb2
-rw-r--r--spec/models/commit_collection_spec.rb102
-rw-r--r--spec/models/commit_range_spec.rb2
-rw-r--r--spec/models/commit_spec.rb18
-rw-r--r--spec/models/commit_status_spec.rb29
-rw-r--r--spec/models/compare_spec.rb2
-rw-r--r--spec/models/concerns/access_requestable_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb2
-rw-r--r--spec/models/concerns/awardable_spec.rb2
-rw-r--r--spec/models/concerns/batch_destroy_dependent_associations_spec.rb2
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb442
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb2
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb2
-rw-r--r--spec/models/concerns/deployable_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb2
-rw-r--r--spec/models/concerns/deprecated_assignee_spec.rb160
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/each_batch_spec.rb2
-rw-r--r--spec/models/concerns/editable_spec.rb2
-rw-r--r--spec/models/concerns/expirable_spec.rb2
-rw-r--r--spec/models/concerns/faster_cache_keys_spec.rb2
-rw-r--r--spec/models/concerns/feature_gate_spec.rb2
-rw-r--r--spec/models/concerns/group_descendant_spec.rb2
-rw-r--r--spec/models/concerns/has_ref_spec.rb20
-rw-r--r--spec/models/concerns/has_status_spec.rb24
-rw-r--r--spec/models/concerns/has_variable_spec.rb4
-rw-r--r--spec/models/concerns/ignorable_column_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb110
-rw-r--r--spec/models/concerns/issuable_states_spec.rb30
-rw-r--r--spec/models/concerns/loaded_in_group_list_spec.rb2
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb2
-rw-r--r--spec/models/concerns/maskable_spec.rb76
-rw-r--r--spec/models/concerns/mentionable_spec.rb2
-rw-r--r--spec/models/concerns/milestoneish_spec.rb206
-rw-r--r--spec/models/concerns/noteable_spec.rb14
-rw-r--r--spec/models/concerns/participable_spec.rb2
-rw-r--r--spec/models/concerns/presentable_spec.rb2
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb55
-rw-r--r--spec/models/concerns/protected_ref_access_spec.rb14
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb46
-rw-r--r--spec/models/concerns/redactable_spec.rb2
-rw-r--r--spec/models/concerns/redis_cacheable_spec.rb2
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_discussion_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sortable_spec.rb4
-rw-r--r--spec/models/concerns/spammable_spec.rb6
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb2
-rw-r--r--spec/models/concerns/subscribable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/base_spec.rb34
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb30
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb2
-rw-r--r--spec/models/concerns/uniquify_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/conversational_development_index/metric_spec.rb2
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/cycle_analytics_spec.rb2
-rw-r--r--spec/models/deploy_key_spec.rb2
-rw-r--r--spec/models/deploy_keys_project_spec.rb2
-rw-r--r--spec/models/deploy_token_spec.rb34
-rw-r--r--spec/models/deployment_spec.rb61
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/diff_note_spec.rb20
-rw-r--r--spec/models/diff_viewer/base_spec.rb2
-rw-r--r--spec/models/diff_viewer/server_side_spec.rb2
-rw-r--r--spec/models/discussion_spec.rb2
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/environment_spec.rb51
-rw-r--r--spec/models/environment_status_spec.rb6
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb63
-rw-r--r--spec/models/event_collection_spec.rb2
-rw-r--r--spec/models/event_spec.rb6
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/fork_network_member_spec.rb2
-rw-r--r--spec/models/fork_network_spec.rb2
-rw-r--r--spec/models/generic_commit_status_spec.rb2
-rw-r--r--spec/models/global_milestone_spec.rb2
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/gpg_key_subkey_spec.rb2
-rw-r--r--spec/models/gpg_signature_spec.rb2
-rw-r--r--spec/models/group_custom_attribute_spec.rb2
-rw-r--r--spec/models/group_label_spec.rb2
-rw-r--r--spec/models/group_milestone_spec.rb2
-rw-r--r--spec/models/group_spec.rb161
-rw-r--r--spec/models/guest_spec.rb2
-rw-r--r--spec/models/hooks/active_hook_filter_spec.rb2
-rw-r--r--spec/models/hooks/project_hook_spec.rb2
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/identity_spec.rb2
-rw-r--r--spec/models/import_export_upload_spec.rb2
-rw-r--r--spec/models/instance_configuration_spec.rb13
-rw-r--r--spec/models/internal_id_spec.rb64
-rw-r--r--spec/models/issue/metrics_spec.rb8
-rw-r--r--spec/models/issue_collection_spec.rb2
-rw-r--r--spec/models/issue_spec.rb85
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_link_spec.rb2
-rw-r--r--spec/models/label_priority_spec.rb2
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/legacy_diff_discussion_spec.rb2
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/lfs_object_spec.rb2
-rw-r--r--spec/models/lfs_objects_project_spec.rb2
-rw-r--r--spec/models/license_template_spec.rb15
-rw-r--r--spec/models/list_spec.rb2
-rw-r--r--spec/models/member_spec.rb12
-rw-r--r--spec/models/members/group_member_spec.rb18
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request/metrics_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb184
-rw-r--r--spec/models/merge_request_spec.rb427
-rw-r--r--spec/models/milestone_spec.rb64
-rw-r--r--spec/models/namespace_spec.rb98
-rw-r--r--spec/models/network/graph_spec.rb4
-rw-r--r--spec/models/note_diff_file_spec.rb29
-rw-r--r--spec/models/note_spec.rb20
-rw-r--r--spec/models/notification_recipient_spec.rb42
-rw-r--r--spec/models/notification_setting_spec.rb2
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb49
-rw-r--r--spec/models/pages_domain_spec.rb41
-rw-r--r--spec/models/personal_access_token_spec.rb2
-rw-r--r--spec/models/pool_repository_spec.rb8
-rw-r--r--spec/models/programming_language_spec.rb2
-rw-r--r--spec/models/project_authorization_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb78
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb28
-rw-r--r--spec/models/project_custom_attribute_spec.rb2
-rw-r--r--spec/models/project_daily_statistic_spec.rb7
-rw-r--r--spec/models/project_deploy_token_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_label_spec.rb2
-rw-r--r--spec/models/project_metrics_setting_spec.rb55
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/assembla_service_spec.rb8
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb5
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb2
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb12
-rw-r--r--spec/models/project_services/campfire_service_spec.rb26
-rw-r--r--spec/models/project_services/chat_message/deployment_message_spec.rb153
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb2
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb2
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb409
-rw-r--r--spec/models/project_services/irker_service_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb12
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb54
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb8
-rw-r--r--spec/models/project_services/packagist_service_spec.rb2
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb78
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb18
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb61
-rw-r--r--spec/models/project_services/pushover_service_spec.rb8
-rw-r--r--spec/models/project_services/redmine_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb5
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb40
-rw-r--r--spec/models/project_snippet_spec.rb2
-rw-r--r--spec/models/project_spec.rb507
-rw-r--r--spec/models/project_statistics_spec.rb114
-rw-r--r--spec/models/project_team_spec.rb26
-rw-r--r--spec/models/project_wiki_spec.rb85
-rw-r--r--spec/models/protectable_dropdown_spec.rb2
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch_spec.rb30
-rw-r--r--spec/models/protected_tag_spec.rb2
-rw-r--r--spec/models/push_event_payload_spec.rb2
-rw-r--r--spec/models/push_event_spec.rb6
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/release_spec.rb23
-rw-r--r--spec/models/remote_mirror_spec.rb22
-rw-r--r--spec/models/repository_language_spec.rb2
-rw-r--r--spec/models/repository_spec.rb330
-rw-r--r--spec/models/resource_label_event_spec.rb4
-rw-r--r--spec/models/route_spec.rb2
-rw-r--r--spec/models/sent_notification_spec.rb2
-rw-r--r--spec/models/serverless/function_spec.rb21
-rw-r--r--spec/models/service_spec.rb6
-rw-r--r--spec/models/snippet_blob_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb2
-rw-r--r--spec/models/spam_log_spec.rb2
-rw-r--r--spec/models/ssh_host_key_spec.rb2
-rw-r--r--spec/models/subscription_spec.rb2
-rw-r--r--spec/models/suggestion_spec.rb16
-rw-r--r--spec/models/system_note_metadata_spec.rb2
-rw-r--r--spec/models/term_agreement_spec.rb2
-rw-r--r--spec/models/timelog_spec.rb2
-rw-r--r--spec/models/todo_spec.rb2
-rw-r--r--spec/models/tree_spec.rb2
-rw-r--r--spec/models/trending_project_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_callout_spec.rb2
-rw-r--r--spec/models/user_custom_attribute_spec.rb2
-rw-r--r--spec/models/user_interacted_project_spec.rb2
-rw-r--r--spec/models/user_preference_spec.rb6
-rw-r--r--spec/models/user_spec.rb75
-rw-r--r--spec/models/wiki_directory_spec.rb2
-rw-r--r--spec/models/wiki_page_spec.rb69
-rw-r--r--spec/policies/base_policy_spec.rb23
-rw-r--r--spec/policies/board_policy_spec.rb8
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb46
-rw-r--r--spec/policies/clusters/cluster_policy_spec.rb16
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb36
-rw-r--r--spec/policies/commit_policy_spec.rb55
-rw-r--r--spec/policies/global_policy_spec.rb12
-rw-r--r--spec/policies/group_member_policy_spec.rb105
-rw-r--r--spec/policies/group_policy_spec.rb203
-rw-r--r--spec/policies/identity_provider_policy_spec.rb30
-rw-r--r--spec/policies/issuable_policy_spec.rb6
-rw-r--r--spec/policies/issue_policy_spec.rb19
-rw-r--r--spec/policies/merge_request_policy_spec.rb69
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/note_policy_spec.rb94
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb31
-rw-r--r--spec/policies/project_policy_spec.rb309
-rw-r--r--spec/policies/project_snippet_policy_spec.rb109
-rw-r--r--spec/presenters/blob_presenter_spec.rb10
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb159
-rw-r--r--spec/presenters/ci/bridge_presenter_spec.rb15
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb12
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb64
-rw-r--r--spec/presenters/ci/pipeline_presenter_spec.rb134
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb68
-rw-r--r--spec/presenters/group_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/issue_presenter_spec.rb29
-rw-r--r--spec/presenters/label_presenter_spec.rb93
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb155
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/project_presenter_spec.rb19
-rw-r--r--spec/presenters/tree_entry_presenter_spec.rb16
-rw-r--r--spec/rack_servers/configs/puma.rb32
-rw-r--r--spec/rack_servers/puma_spec.rb28
-rw-r--r--spec/requests/api/badges_spec.rb6
-rw-r--r--spec/requests/api/branches_spec.rb26
-rw-r--r--spec/requests/api/circuit_breakers_spec.rb46
-rw-r--r--spec/requests/api/commit_statuses_spec.rb17
-rw-r--r--spec/requests/api/commits_spec.rb107
-rw-r--r--spec/requests/api/discussions_spec.rb4
-rw-r--r--spec/requests/api/environments_spec.rb22
-rw-r--r--spec/requests/api/events_spec.rb135
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb150
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb119
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb32
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb39
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb82
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb28
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb43
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb37
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb73
-rw-r--r--spec/requests/api/graphql_spec.rb134
-rw-r--r--spec/requests/api/group_variables_spec.rb12
-rw-r--r--spec/requests/api/groups_spec.rb3
-rw-r--r--spec/requests/api/helpers_spec.rb7
-rw-r--r--spec/requests/api/internal_spec.rb172
-rw-r--r--spec/requests/api/issuable_bulk_update_spec.rb154
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb652
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb807
-rw-r--r--spec/requests/api/issues/issues_spec.rb796
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb549
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb392
-rw-r--r--spec/requests/api/issues_spec.rb2041
-rw-r--r--spec/requests/api/jobs_spec.rb48
-rw-r--r--spec/requests/api/keys_spec.rb2
-rw-r--r--spec/requests/api/members_spec.rb15
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb4
-rw-r--r--spec/requests/api/merge_requests_spec.rb892
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb7
-rw-r--r--spec/requests/api/pipelines_spec.rb88
-rw-r--r--spec/requests/api/project_clusters_spec.rb75
-rw-r--r--spec/requests/api/project_events_spec.rb156
-rw-r--r--spec/requests/api/project_milestones_spec.rb74
-rw-r--r--spec/requests/api/project_statistics_spec.rb62
-rw-r--r--spec/requests/api/projects_spec.rb240
-rw-r--r--spec/requests/api/release/links_spec.rb16
-rw-r--r--spec/requests/api/releases_spec.rb83
-rw-r--r--spec/requests/api/runner_spec.rb165
-rw-r--r--spec/requests/api/runners_spec.rb45
-rw-r--r--spec/requests/api/search_spec.rb136
-rw-r--r--spec/requests/api/settings_spec.rb38
-rw-r--r--spec/requests/api/snippets_spec.rb76
-rw-r--r--spec/requests/api/suggestions_spec.rb3
-rw-r--r--spec/requests/api/system_hooks_spec.rb8
-rw-r--r--spec/requests/api/tags_spec.rb2
-rw-r--r--spec/requests/api/task_completion_status_spec.rb85
-rw-r--r--spec/requests/api/todos_spec.rb52
-rw-r--r--spec/requests/api/users_spec.rb60
-rw-r--r--spec/requests/api/variables_spec.rb15
-rw-r--r--spec/requests/api/version_spec.rb18
-rw-r--r--spec/requests/git_http_spec.rb185
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb26
-rw-r--r--spec/routing/api_routing_spec.rb14
-rw-r--r--spec/routing/group_routing_spec.rb18
-rw-r--r--spec/routing/import_routing_spec.rb12
-rw-r--r--spec/routing/project_routing_spec.rb84
-rw-r--r--spec/routing/uploads_routing_spec.rb22
-rw-r--r--spec/rubocop/cop/active_record_association_reload_spec.rb60
-rw-r--r--spec/rubocop/cop/code_reuse/active_record_spec.rb6
-rw-r--r--spec/rubocop/cop/include_action_view_context_spec.rb45
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb18
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb11
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb32
-rw-r--r--spec/serializers/analytics_summary_serializer_spec.rb2
-rw-r--r--spec/serializers/build_details_entity_spec.rb43
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb4
-rw-r--r--spec/serializers/deployment_entity_spec.rb41
-rw-r--r--spec/serializers/environment_entity_spec.rb4
-rw-r--r--spec/serializers/group_child_entity_spec.rb19
-rw-r--r--spec/serializers/job_artifact_report_entity_spec.rb28
-rw-r--r--spec/serializers/job_entity_spec.rb18
-rw-r--r--spec/serializers/merge_request_for_pipeline_entity_spec.rb29
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb65
-rw-r--r--spec/serializers/pipeline_entity_spec.rb88
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb46
-rw-r--r--spec/serializers/provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/stage_entity_spec.rb28
-rw-r--r--spec/serializers/stage_serializer_spec.rb31
-rw-r--r--spec/serializers/suggestion_entity_spec.rb3
-rw-r--r--spec/serializers/test_case_entity_spec.rb2
-rw-r--r--spec/services/access_token_validation_service_spec.rb2
-rw-r--r--spec/services/after_branch_delete_service_spec.rb15
-rw-r--r--spec/services/application_settings/update_service_spec.rb37
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb95
-rw-r--r--spec/services/auto_merge/base_service_spec.rb144
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb223
-rw-r--r--spec/services/auto_merge_service_spec.rb140
-rw-r--r--spec/services/base_count_service_spec.rb2
-rw-r--r--spec/services/boards/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/boards/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/create_service_spec.rb2
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb2
-rw-r--r--spec/services/boards/lists/generate_service_spec.rb2
-rw-r--r--spec/services/boards/lists/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/move_service_spec.rb2
-rw-r--r--spec/services/boards/visits/latest_service_spec.rb12
-rw-r--r--spec/services/chat_names/authorize_user_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb2
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb2
-rw-r--r--spec/services/ci/compare_test_reports_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb174
-rw-r--r--spec/services/ci/destroy_expired_job_artifacts_service_spec.rb2
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb15
-rw-r--r--spec/services/ci/ensure_stage_service_spec.rb2
-rw-r--r--spec/services/ci/expire_pipeline_cache_service_spec.rb61
-rw-r--r--spec/services/ci/extract_sections_from_build_trace_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_schedule_service_spec.rb28
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb2
-rw-r--r--spec/services/ci/play_manual_stage_service_spec.rb79
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb68
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/register_job_service_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb17
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/run_scheduled_build_service_spec.rb2
-rw-r--r--spec/services/ci/stop_environments_service_spec.rb78
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb2
-rw-r--r--spec/services/ci/update_runner_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb12
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb30
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb145
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb222
-rw-r--r--spec/services/clusters/applications/destroy_service_spec.rb63
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb80
-rw-r--r--spec/services/clusters/applications/patch_service_spec.rb80
-rw-r--r--spec/services/clusters/applications/schedule_installation_service_spec.rb77
-rw-r--r--spec/services/clusters/applications/uninstall_service_spec.rb77
-rw-r--r--spec/services/clusters/applications/update_service_spec.rb72
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb76
-rw-r--r--spec/services/clusters/build_service_spec.rb8
-rw-r--r--spec/services/clusters/create_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/fetch_operation_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/provision_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/verify_provision_status_service_spec.rb2
-rw-r--r--spec/services/clusters/refresh_service_spec.rb8
-rw-r--r--spec/services/clusters/update_service_spec.rb2
-rw-r--r--spec/services/cohorts_service_spec.rb2
-rw-r--r--spec/services/compare_service_spec.rb15
-rw-r--r--spec/services/create_branch_service_spec.rb2
-rw-r--r--spec/services/create_snippet_service_spec.rb2
-rw-r--r--spec/services/delete_branch_service_spec.rb26
-rw-r--r--spec/services/delete_merged_branches_service_spec.rb2
-rw-r--r--spec/services/deploy_keys/create_service_spec.rb2
-rw-r--r--spec/services/deploy_tokens/create_service_spec.rb12
-rw-r--r--spec/services/discussions/resolve_service_spec.rb2
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb2
-rw-r--r--spec/services/emails/confirm_service_spec.rb2
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb24
-rw-r--r--spec/services/error_tracking/list_projects_service_spec.rb30
-rw-r--r--spec/services/event_create_service_spec.rb2
-rw-r--r--spec/services/events/render_service_spec.rb2
-rw-r--r--spec/services/files/create_service_spec.rb2
-rw-r--r--spec/services/files/delete_service_spec.rb2
-rw-r--r--spec/services/files/multi_service_spec.rb18
-rw-r--r--spec/services/files/update_service_spec.rb2
-rw-r--r--spec/services/git/base_hooks_service_spec.rb90
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb347
-rw-r--r--spec/services/git/branch_push_service_spec.rb605
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb152
-rw-r--r--spec/services/git/tag_push_service_spec.rb61
-rw-r--r--spec/services/git_push_service_spec.rb837
-rw-r--r--spec/services/git_tag_push_service_spec.rb196
-rw-r--r--spec/services/gpg_keys/create_service_spec.rb2
-rw-r--r--spec/services/gravatar_service_spec.rb2
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb62
-rw-r--r--spec/services/groups/create_service_spec.rb13
-rw-r--r--spec/services/groups/destroy_service_spec.rb40
-rw-r--r--spec/services/groups/nested_create_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb113
-rw-r--r--spec/services/groups/update_service_spec.rb2
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb2
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb18
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb16
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb6
-rw-r--r--spec/services/issuable/destroy_service_spec.rb4
-rw-r--r--spec/services/issues/build_service_spec.rb82
-rw-r--r--spec/services/issues/close_service_spec.rb46
-rw-r--r--spec/services/issues/create_service_spec.rb16
-rw-r--r--spec/services/issues/duplicate_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb2
-rw-r--r--spec/services/issues/related_branches_service_spec.rb2
-rw-r--r--spec/services/issues/reopen_service_spec.rb2
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb18
-rw-r--r--spec/services/keys/create_service_spec.rb2
-rw-r--r--spec/services/keys/destroy_service_spec.rb2
-rw-r--r--spec/services/keys/last_used_service_spec.rb2
-rw-r--r--spec/services/labels/available_labels_service_spec.rb86
-rw-r--r--spec/services/labels/create_service_spec.rb2
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb2
-rw-r--r--spec/services/labels/transfer_service_spec.rb2
-rw-r--r--spec/services/labels/update_service_spec.rb2
-rw-r--r--spec/services/lfs/file_transformer_spec.rb21
-rw-r--r--spec/services/lfs/lock_file_service_spec.rb2
-rw-r--r--spec/services/lfs/locks_finder_service_spec.rb2
-rw-r--r--spec/services/lfs/unlock_file_service_spec.rb2
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb2
-rw-r--r--spec/services/members/create_service_spec.rb15
-rw-r--r--spec/services/members/destroy_service_spec.rb6
-rw-r--r--spec/services/members/request_access_service_spec.rb2
-rw-r--r--spec/services/members/update_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb34
-rw-r--r--spec/services/merge_requests/assign_issues_service_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb11
-rw-r--r--spec/services/merge_requests/close_service_spec.rb20
-rw-r--r--spec/services/merge_requests/conflicts/list_service_spec.rb2
-rw-r--r--spec/services/merge_requests/conflicts/resolve_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb4
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb71
-rw-r--r--spec/services/merge_requests/create_service_spec.rb99
-rw-r--r--spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb6
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb20
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb174
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb180
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb187
-rw-r--r--spec/services/merge_requests/migrate_external_diffs_service_spec.rb43
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb12
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb405
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb46
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb154
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb4
-rw-r--r--spec/services/merge_requests/resolved_discussion_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb55
-rw-r--r--spec/services/milestones/close_service_spec.rb2
-rw-r--r--spec/services/milestones/create_service_spec.rb2
-rw-r--r--spec/services/milestones/destroy_service_spec.rb2
-rw-r--r--spec/services/milestones/promote_service_spec.rb2
-rw-r--r--spec/services/note_summary_spec.rb12
-rw-r--r--spec/services/notes/build_service_spec.rb38
-rw-r--r--spec/services/notes/create_service_spec.rb56
-rw-r--r--spec/services/notes/destroy_service_spec.rb2
-rw-r--r--spec/services/notes/post_process_service_spec.rb2
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb27
-rw-r--r--spec/services/notes/render_service_spec.rb2
-rw-r--r--spec/services/notes/resolve_service_spec.rb2
-rw-r--r--spec/services/notes/update_service_spec.rb2
-rw-r--r--spec/services/notification_recipient_service_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb162
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb63
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb146
-rw-r--r--spec/services/preview_markdown_service_spec.rb77
-rw-r--r--spec/services/projects/after_import_service_spec.rb2
-rw-r--r--spec/services/projects/auto_devops/disable_service_spec.rb10
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/batch_open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/cleanup_service_spec.rb91
-rw-r--r--spec/services/projects/count_service_spec.rb2
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb65
-rw-r--r--spec/services/projects/destroy_service_spec.rb8
-rw-r--r--spec/services/projects/detect_repository_languages_service_spec.rb14
-rw-r--r--spec/services/projects/download_service_spec.rb2
-rw-r--r--spec/services/projects/enable_deploy_key_service_spec.rb2
-rw-r--r--spec/services/projects/fetch_statistics_increment_service_spec.rb36
-rw-r--r--spec/services/projects/fork_service_spec.rb26
-rw-r--r--spec/services/projects/forks_count_service_spec.rb2
-rw-r--r--spec/services/projects/git_deduplication_service_spec.rb90
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/create_service_spec.rb10
-rw-r--r--spec/services/projects/group_links/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb14
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb20
-rw-r--r--spec/services/projects/hashed_storage/migration_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb106
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb117
-rw-r--r--spec/services/projects/hashed_storage/rollback_service_spec.rb57
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb18
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb2
-rw-r--r--spec/services/projects/import_service_spec.rb47
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb23
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb20
-rw-r--r--spec/services/projects/lfs_pointers/lfs_import_service_spec.rb153
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb1
-rw-r--r--spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb148
-rw-r--r--spec/services/projects/move_access_service_spec.rb2
-rw-r--r--spec/services/projects/move_deploy_keys_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_forks_service_spec.rb2
-rw-r--r--spec/services/projects/move_lfs_objects_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_notification_settings_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_authorizations_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_group_links_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_members_service_spec.rb2
-rw-r--r--spec/services/projects/move_users_star_projects_service_spec.rb2
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/open_merge_requests_count_service_spec.rb2
-rw-r--r--spec/services/projects/operations/update_service_spec.rb100
-rw-r--r--spec/services/projects/overwrite_project_service_spec.rb2
-rw-r--r--spec/services/projects/participants_service_spec.rb57
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb2
-rw-r--r--spec/services/projects/repository_languages_service_spec.rb50
-rw-r--r--spec/services/projects/transfer_service_spec.rb28
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb2
-rw-r--r--spec/services/projects/update_pages_configuration_service_spec.rb2
-rw-r--r--spec/services/projects/update_pages_service_spec.rb61
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb46
-rw-r--r--spec/services/projects/update_statistics_service_spec.rb32
-rw-r--r--spec/services/prometheus/adapter_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb195
-rw-r--r--spec/services/protected_branches/create_service_spec.rb2
-rw-r--r--spec/services/protected_branches/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_branches/update_service_spec.rb2
-rw-r--r--spec/services/protected_tags/create_service_spec.rb2
-rw-r--r--spec/services/protected_tags/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_tags/update_service_spec.rb2
-rw-r--r--spec/services/push_event_payload_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb56
-rw-r--r--spec/services/releases/create_service_spec.rb12
-rw-r--r--spec/services/releases/destroy_service_spec.rb10
-rw-r--r--spec/services/releases/update_service_spec.rb2
-rw-r--r--spec/services/repair_ldap_blocked_user_service_spec.rb2
-rw-r--r--spec/services/repository_archive_clean_up_service_spec.rb2
-rw-r--r--spec/services/reset_project_cache_service_spec.rb2
-rw-r--r--spec/services/search/global_service_spec.rb2
-rw-r--r--spec/services/search/group_service_spec.rb2
-rw-r--r--spec/services/search/snippet_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb2
-rw-r--r--spec/services/service_response_spec.rb73
-rw-r--r--spec/services/spam_service_spec.rb2
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb6
-rw-r--r--spec/services/suggestions/apply_service_spec.rb232
-rw-r--r--spec/services/suggestions/create_service_spec.rb110
-rw-r--r--spec/services/suggestions/outdate_service_spec.rb102
-rw-r--r--spec/services/system_hooks_service_spec.rb14
-rw-r--r--spec/services/system_note_service_spec.rb102
-rw-r--r--spec/services/tags/create_service_spec.rb4
-rw-r--r--spec/services/tags/destroy_service_spec.rb18
-rw-r--r--spec/services/task_list_toggle_service_spec.rb21
-rw-r--r--spec/services/test_hooks/project_service_spec.rb2
-rw-r--r--spec/services/test_hooks/system_service_spec.rb2
-rw-r--r--spec/services/todo_service_spec.rb112
-rw-r--r--spec/services/todos/destroy/confidential_issue_service_spec.rb60
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb16
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/private_features_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/project_private_service_spec.rb2
-rw-r--r--spec/services/update_deployment_service_spec.rb40
-rw-r--r--spec/services/update_merge_request_metrics_service_spec.rb2
-rw-r--r--spec/services/update_snippet_service_spec.rb2
-rw-r--r--spec/services/upload_service_spec.rb2
-rw-r--r--spec/services/user_project_access_changed_service_spec.rb2
-rw-r--r--spec/services/users/activity_service_spec.rb4
-rw-r--r--spec/services/users/build_service_spec.rb2
-rw-r--r--spec/services/users/create_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb10
-rw-r--r--spec/services/users/last_push_event_service_spec.rb2
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb4
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb2
-rw-r--r--spec/services/users/respond_to_terms_service_spec.rb2
-rw-r--r--spec/services/users/update_service_spec.rb11
-rw-r--r--spec/services/verify_pages_domain_service_spec.rb187
-rw-r--r--spec/services/web_hook_service_spec.rb12
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb2
-rw-r--r--spec/spec_helper.rb20
-rw-r--r--spec/support/api/milestones_shared_examples.rb9
-rw-r--r--spec/support/api/schema_matcher.rb24
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/capybara.rb11
-rw-r--r--spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb6
-rw-r--r--spec/support/database_cleaner.rb56
-rw-r--r--spec/support/db_cleaner.rb50
-rw-r--r--spec/support/external_authorization_service_helpers.rb33
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb6
-rw-r--r--spec/support/features/issuable_quick_actions_shared_examples.rb389
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb6
-rw-r--r--spec/support/features/variable_list_shared_examples.rb163
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb2
-rw-r--r--spec/support/helpers/email_helpers.rb4
-rw-r--r--spec/support/helpers/features/notes_helpers.rb10
-rw-r--r--spec/support/helpers/file_mover_helpers.rb12
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb23
-rw-r--r--spec/support/helpers/git_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb75
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb41
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb91
-rw-r--r--spec/support/helpers/lets_encrypt_helpers.rb59
-rw-r--r--spec/support/helpers/license_helper.rb8
-rw-r--r--spec/support/helpers/login_helpers.rb16
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb43
-rw-r--r--spec/support/helpers/mobile_helpers.rb2
-rw-r--r--spec/support/helpers/notification_helpers.rb8
-rw-r--r--spec/support/helpers/policy_helpers.rb11
-rw-r--r--spec/support/helpers/project_forks_helper.rb23
-rw-r--r--spec/support/helpers/prometheus_helpers.rb14
-rw-r--r--spec/support/helpers/query_recorder.rb2
-rw-r--r--spec/support/helpers/repo_helpers.rb92
-rw-r--r--spec/support/helpers/select2_helper.rb4
-rw-r--r--spec/support/helpers/stub_configuration.rb10
-rw-r--r--spec/support/helpers/stub_object_storage.rb15
-rw-r--r--spec/support/helpers/stub_requests.rb40
-rw-r--r--spec/support/helpers/stub_worker.rb9
-rw-r--r--spec/support/helpers/test_env.rb36
-rw-r--r--spec/support/helpers/test_request_helpers.rb4
-rw-r--r--spec/support/helpers/wait_for_requests.rb14
-rw-r--r--spec/support/helpers/wait_helpers.rb20
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/matchers/access_matchers.rb35
-rw-r--r--spec/support/matchers/eq_pem.rb11
-rw-r--r--spec/support/matchers/graphql_matchers.rb4
-rw-r--r--spec/support/matchers/issuable_matchers.rb2
-rw-r--r--spec/support/matchers/not_changed_matcher.rb3
-rw-r--r--spec/support/prometheus/additional_metrics_shared_examples.rb2
-rw-r--r--spec/support/protected_branch_helpers.rb30
-rw-r--r--spec/support/protected_tag_helpers.rb18
-rw-r--r--spec/support/redis/redis_shared_examples.rb2
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb4
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb24
-rw-r--r--spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb44
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb65
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb8
-rw-r--r--spec/support/shared_contexts/merge_request_create.rb26
-rw-r--r--spec/support/shared_contexts/merge_request_edit.rb28
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb47
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb88
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb8
-rw-r--r--spec/support/shared_examples/application_setting_examples.rb291
-rw-r--r--spec/support/shared_examples/ci/stage_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb116
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/variables_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb4
-rw-r--r--spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/finders/assignees_filter_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb30
-rw-r--r--spec/support/shared_examples/helm_generated_script.rb2
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issuables_list_metadata_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/legacy_path_redirect_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/malicious_regexp_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb113
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_spec.rb79
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb248
-rw-r--r--spec/support/shared_examples/models/chat_service_spec.rb242
-rw-r--r--spec/support/shared_examples/models/ci_variable_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb30
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb65
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/update_project_statistics_shared_examples.rb77
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb231
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb88
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb88
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb103
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb83
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb83
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb95
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb82
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb82
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb85
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb92
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb120
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb102
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb93
-rw-r--r--spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb51
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb47
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb55
-rw-r--r--spec/support/shared_examples/requests/api/issues_shared_examples.rb44
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb335
-rw-r--r--spec/support/shared_examples/services/base_helm_service_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/snippet_visibility.rb322
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb306
-rw-r--r--spec/support/shared_examples/time_tracking_shared_examples.rb85
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb24
-rw-r--r--spec/support/shared_examples/views/nav_sidebar.rb11
-rw-r--r--spec/support/shared_examples/wiki_file_attachments_examples.rb2
-rw-r--r--spec/support/shoulda/matchers/rails_shim.rb27
-rw-r--r--spec/support/webmock.rb10
-rw-r--r--spec/tasks/gitlab/artifacts/migrate_rake_spec.rb55
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb18
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb108
-rw-r--r--spec/tasks/tokens_spec.rb4
-rw-r--r--spec/uploaders/file_mover_spec.rb33
-rw-r--r--spec/uploaders/import_export_uploader_spec.rb32
-rw-r--r--spec/uploaders/legacy_artifact_uploader_spec.rb61
-rw-r--r--spec/uploaders/object_storage_spec.rb12
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb60
-rw-r--r--spec/uploaders/records_uploads_spec.rb13
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb34
-rw-r--r--spec/validators/addressable_url_validator_spec.rb315
-rw-r--r--spec/validators/devise_email_validator_spec.rb94
-rw-r--r--spec/validators/public_url_validator_spec.rb8
-rw-r--r--spec/validators/sha_validator_spec.rb47
-rw-r--r--spec/validators/url_validator_spec.rb226
-rw-r--r--spec/validators/x509_certificate_credentials_validator_spec.rb87
-rw-r--r--spec/views/ci/status/_icon.html.haml_spec.rb89
-rw-r--r--spec/views/groups/_home_panel.html.haml_spec.rb15
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/help/index.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb13
-rw-r--r--spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb7
-rw-r--r--spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb13
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb54
-rw-r--r--spec/views/notify/pipeline_failed_email.text.erb_spec.rb39
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb4
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb4
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb63
-rw-r--r--spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb22
-rw-r--r--spec/views/projects/issues/show.html.haml_spec.rb27
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb10
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb6
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb13
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb8
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb11
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tags/index.html.haml_spec.rb41
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb2
-rw-r--r--spec/views/shared/_label_row.html.haml.rb32
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml.rb6
-rw-r--r--spec/views/shared/projects/_project.html.haml_spec.rb4
-rw-r--r--spec/workers/admin_email_worker_spec.rb2
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb2
-rw-r--r--spec/workers/auto_merge_process_worker_spec.rb31
-rw-r--r--spec/workers/background_migration_worker_spec.rb2
-rw-r--r--spec/workers/build_coverage_worker_spec.rb2
-rw-r--r--spec/workers/build_finished_worker_spec.rb3
-rw-r--r--spec/workers/build_hooks_worker_spec.rb2
-rw-r--r--spec/workers/build_success_worker_spec.rb3
-rw-r--r--spec/workers/build_trace_sections_worker_spec.rb2
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_prepare_worker_spec.rb30
-rw-r--r--spec/workers/ci/build_schedule_worker_spec.rb2
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb56
-rw-r--r--spec/workers/cluster_project_configure_worker_spec.rb14
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb2
-rw-r--r--spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb32
-rw-r--r--spec/workers/concerns/application_worker_spec.rb2
-rw-r--r--spec/workers/concerns/cluster_queue_spec.rb2
-rw-r--r--spec/workers/concerns/cronjob_queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_background_queue_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_queue_spec.rb2
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb2
-rw-r--r--spec/workers/concerns/repository_check_queue_spec.rb2
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb2
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb2
-rw-r--r--spec/workers/create_note_diff_file_worker_spec.rb2
-rw-r--r--spec/workers/create_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb2
-rw-r--r--spec/workers/delete_merged_branches_worker_spec.rb2
-rw-r--r--spec/workers/delete_user_worker_spec.rb2
-rw-r--r--spec/workers/deployments/finished_worker_spec.rb39
-rw-r--r--spec/workers/deployments/success_worker_spec.rb2
-rw-r--r--spec/workers/detect_repository_languages_worker_spec.rb13
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb8
-rw-r--r--spec/workers/expire_job_cache_worker_spec.rb2
-rw-r--r--spec/workers/expire_pipeline_cache_worker_spec.rb31
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb15
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb2
-rw-r--r--spec/workers/group_destroy_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/migrator_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_migrate_worker_spec.rb50
-rw-r--r--spec/workers/hashed_storage/project_rollback_worker_spec.rb50
-rw-r--r--spec/workers/hashed_storage/rollbacker_worker_spec.rb27
-rw-r--r--spec/workers/invalid_gpg_signature_update_worker_spec.rb2
-rw-r--r--spec/workers/issue_due_scheduler_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/issue_due_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/notification_service_worker_spec.rb2
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/migrate_external_diffs_worker_spec.rb25
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/new_issue_worker_spec.rb2
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/new_note_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_removal_cron_worker_spec.rb27
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb17
-rw-r--r--spec/workers/pages_domain_verification_worker_spec.rb9
-rw-r--r--spec/workers/pipeline_hooks_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb51
-rw-r--r--spec/workers/pipeline_success_worker_spec.rb24
-rw-r--r--spec/workers/pipeline_update_worker_spec.rb2
-rw-r--r--spec/workers/plugin_worker_spec.rb2
-rw-r--r--spec/workers/post_receive_spec.rb81
-rw-r--r--spec/workers/process_commit_worker_spec.rb2
-rw-r--r--spec/workers/project_cache_worker_spec.rb54
-rw-r--r--spec/workers/project_daily_statistics_worker_spec.rb35
-rw-r--r--spec/workers/project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/project_export_worker_spec.rb2
-rw-r--r--spec/workers/project_migrate_hashed_storage_worker_spec.rb48
-rw-r--r--spec/workers/propagate_service_template_worker_spec.rb2
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb2
-rw-r--r--spec/workers/prune_web_hook_logs_worker_spec.rb2
-rw-r--r--spec/workers/reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/rebase_worker_spec.rb2
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_group_links_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_members_worker_spec.rb2
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/clear_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/dispatch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb2
-rw-r--r--spec/workers/repository_cleanup_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb2
-rw-r--r--spec/workers/repository_import_worker_spec.rb2
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb2
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb2
-rw-r--r--spec/workers/run_pipeline_schedule_worker_spec.rb34
-rw-r--r--spec/workers/schedule_migrate_external_diffs_worker_spec.rb25
-rw-r--r--spec/workers/stage_update_worker_spec.rb2
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb2
-rw-r--r--spec/workers/system_hook_push_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/confidential_issue_worker_spec.rb15
-rw-r--r--spec/workers/todos_destroyer/entity_leave_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/group_private_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/private_features_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/project_private_worker_spec.rb2
-rw-r--r--spec/workers/trending_projects_worker_spec.rb2
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb6
-rw-r--r--spec/workers/update_merge_requests_worker_spec.rb2
-rw-r--r--spec/workers/update_project_statistics_worker_spec.rb17
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--spec/workers/wait_for_cluster_creation_worker_spec.rb2
-rw-r--r--vendor/assets/javascripts/jquery.atwho.js1202
-rw-r--r--vendor/assets/javascripts/jquery.caret.js436
-rwxr-xr-xvendor/assets/javascripts/pdf.js9365
-rwxr-xr-xvendor/assets/javascripts/pdf.min.js6
-rwxr-xr-xvendor/assets/javascripts/pdf.worker.js38338
-rwxr-xr-xvendor/assets/javascripts/pdf.worker.min.js19
-rw-r--r--vendor/assets/javascripts/visual_review_toolbar.js377
-rw-r--r--vendor/jupyter/values.yaml38
-rw-r--r--vendor/licenses.csv2
-rw-r--r--vendor/project_templates/android.tar.gzbin0 -> 132592 bytes
-rw-r--r--vendor/project_templates/dotnetcore.tar.gzbin44710 -> 7027 bytes
-rw-r--r--vendor/project_templates/express.tar.gzbin4894 -> 16098 bytes
-rw-r--r--vendor/project_templates/gitbook.tar.gzbin13808 -> 13068 bytes
-rw-r--r--vendor/project_templates/gomicro.tar.gzbin0 -> 5388 bytes
-rw-r--r--vendor/project_templates/hexo.tar.gzbin548020 -> 547220 bytes
-rw-r--r--vendor/project_templates/hugo.tar.gzbin1048753 -> 1047952 bytes
-rw-r--r--vendor/project_templates/iosswift.tar.gzbin0 -> 2422200 bytes
-rw-r--r--vendor/project_templates/jekyll.tar.gzbin60703 -> 60086 bytes
-rw-r--r--vendor/project_templates/nfgitbook.tar.gzbin139877 -> 122562 bytes
-rw-r--r--vendor/project_templates/nfhexo.tar.gzbin663568 -> 655028 bytes
-rw-r--r--vendor/project_templates/nfhugo.tar.gzbin1292761 -> 1159251 bytes
-rw-r--r--vendor/project_templates/nfjekyll.tar.gzbin154419 -> 132262 bytes
-rw-r--r--vendor/project_templates/nfplainhtml.tar.gzbin132509 -> 122392 bytes
-rw-r--r--vendor/project_templates/plainhtml.tar.gzbin12079 -> 11389 bytes
-rw-r--r--vendor/project_templates/rails.tar.gzbin25182 -> 25490 bytes
-rw-r--r--vendor/project_templates/spring.tar.gzbin49476 -> 49518 bytes
-rw-r--r--yarn.lock3710
6958 files changed, 325778 insertions, 130043 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 9998ddba643..2be8e63e842 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -6,35 +6,35 @@ engines:
enabled: true
config:
languages:
- - ruby
- - javascript
+ - ruby
+ - javascript
ratings:
paths:
- - Gemfile.lock
- - "**.erb"
- - "**.haml"
- - "**.rb"
- - "**.rhtml"
- - "**.slim"
- - "**.inc"
- - "**.js"
- - "**.jsx"
- - "**.module"
+ - Gemfile.lock
+ - "**.erb"
+ - "**.haml"
+ - "**.rb"
+ - "**.rhtml"
+ - "**.slim"
+ - "**.inc"
+ - "**.js"
+ - "**.jsx"
+ - "**.module"
exclude_paths:
-- config/
-- db/
-- features/
-- node_modules/
-- spec/
-- vendor/
-- .yarn-cache/
-- tmp/
-- builds/
-- coverage/
-- public/
-- shared/
-- webpack-report/
-- log/
-- backups/
-- coverage-javascript/
-- plugins/
+ - config/
+ - db/
+ - features/
+ - node_modules/
+ - spec/
+ - vendor/
+ - .yarn-cache/
+ - tmp/
+ - builds/
+ - coverage/
+ - public/
+ - shared/
+ - webpack-report/
+ - log/
+ - backups/
+ - coverage-javascript/
+ - plugins/
diff --git a/.eslintignore b/.eslintignore
index f78840e67be..9a5e15c86ae 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -9,5 +9,6 @@
/scripts/
/tmp/
/vendor/
+jest.config.js
karma.config.js
webpack.config.js
diff --git a/.eslintrc.yml b/.eslintrc.yml
index b0794bb7434..98a497aa12a 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -9,9 +9,6 @@ plugins:
- import
- html
settings:
- html/html-extensions:
- - '.html'
- - '.html.raw'
import/resolver:
webpack:
config: './config/webpack.config.js'
diff --git a/.gitignore b/.gitignore
index 0696dd217af..cb718a6939f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,8 +59,6 @@ eslint-report.html
/public/uploads.*
/public/uploads/
/shared/artifacts/
-/spec/javascripts/fixtures/blob/pdf/
-/spec/javascripts/fixtures/blob/balsamiq/
/rails_best_practices_output.html
/tags
/tmp/*
@@ -81,3 +79,4 @@ package-lock.json
/junit_*.xml
/coverage-frontend/
jsdoc/
+**/tmp/rubocop_cache/** \ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0afd59ab007..d49b620f22c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,26 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
-
-.dedicated-runner: &dedicated-runner
- retry: 1
- tags:
- - gitlab-org
-
-.default-cache: &default-cache
- key: "debian-stretch-ruby-2.5.3-node-10.x"
- paths:
- - vendor/ruby
- - .yarn-cache/
- - vendor/gitaly-ruby
-
-.push-cache: &push-cache
- cache:
- <<: *default-cache
- policy: push
-
-.pull-cache: &pull-cache
- cache:
- <<: *default-cache
- policy: pull
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
@@ -35,10 +13,8 @@ variables:
BUILD_ASSETS_IMAGE: "false"
before_script:
- - bundle --version
- date
- source scripts/utils.sh
- - date
- source scripts/prepare_build.sh
- date
@@ -50,1035 +26,22 @@ stages:
- prepare
- merge
- test
+ - review
+ - qa
- post-test
- pages
- post-cleanup
-# Predefined scopes
-.tests-metadata-state: &tests-metadata-state
- <<: *dedicated-runner
- variables:
- TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
- before_script:
- - source scripts/utils.sh
- artifacts:
- expire_in: 31d
- paths:
- - knapsack/
- - rspec_flaky/
-
-.use-pg: &use-pg
- services:
- - postgres:9.6
- - redis:alpine
-
-.use-mysql: &use-mysql
- services:
- - mysql:5.7
- - redis:alpine
-
-# Skip all jobs except the ones that begin with 'docs/'.
-# Used for commits including ONLY documentation changes.
-# https://docs.gitlab.com/ce/development/documentation/#testing
-.except-docs: &except-docs
- except:
- - /(^docs[\/-].*|.*-docs$)/
-
-.except-qa: &except-qa
- except:
- - /(^qa[\/-].*|.*-qa$)/
-
-.except-docs-and-qa: &except-docs-and-qa
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
-
-# Jobs that only need to pull cache
-.dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job
- <<: *dedicated-runner
- <<: *except-docs
- <<: *pull-cache
- dependencies:
- - setup-test-env
- stage: test
-
-# Jobs that do not need a DB
-.dedicated-no-docs-no-db-pull-cache-job: &dedicated-no-docs-no-db-pull-cache-job
- <<: *dedicated-no-docs-pull-cache-job
- variables:
- SETUP_DB: "false"
-
-.dedicated-no-docs-and-no-qa-pull-cache-job: &dedicated-no-docs-and-no-qa-pull-cache-job
- <<: *dedicated-no-docs-pull-cache-job
- <<: *except-docs-and-qa
-
-.single-script-job: &single-script-job
- image: ruby:2.5-alpine
- stage: test
- cache: {}
- dependencies: []
- variables: &single-script-job-variables
- GIT_STRATEGY: none
- before_script:
- # We don't clone the repo by using GIT_STRATEGY: none and only download the
- # single script we need here so it's much faster than cloning.
- - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
- - apk add --update openssl
- - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- - chmod 755 $(basename $SCRIPT_NAME)
-
-.rake-exec: &rake-exec
- <<: *dedicated-no-docs-no-db-pull-cache-job
- script:
- - bundle exec rake $CI_JOB_NAME
-
-.rspec-metadata: &rspec-metadata
- <<: *dedicated-runner
- <<: *except-docs-and-qa
- <<: *pull-cache
- stage: test
- script:
- - JOB_NAME=( $CI_JOB_NAME )
- - TEST_TOOL=${JOB_NAME[0]}
- - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- - export KNAPSACK_GENERATE_REPORT=true
- - export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
- - export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- - export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${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}
- - '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${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 --format RspecJunitFormatter --out junit_rspec.xml"
- artifacts:
- expire_in: 31d
- when: always
- paths:
- - coverage/
- - knapsack/
- - rspec_flaky/
- - tmp/capybara/
- reports:
- junit: junit_rspec.xml
-
-.rspec-metadata-pg: &rspec-metadata-pg
- <<: *rspec-metadata
- <<: *use-pg
-
-.rspec-metadata-mysql: &rspec-metadata-mysql
- <<: *rspec-metadata
- <<: *use-mysql
-
-.only-canonical-masters: &only-canonical-masters
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
-
-.gitlab-setup: &gitlab-setup
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- <<: *use-pg
- variables:
- SETUP_DB: "false"
- script:
- # Manually clone gitlab-test and only seed this project in
- # db/fixtures/development/04_project.rb thanks to SIZE=1 below
- - git clone https://gitlab.com/gitlab-org/gitlab-test.git
- /home/git/repositories/gitlab-org/gitlab-test.git
- - scripts/gitaly-test-spawn
- - force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
- artifacts:
- when: on_failure
- expire_in: 1d
- paths:
- - log/development.log
-
-# DB migration, rollback, and seed jobs
-.db-migrate-reset: &db-migrate-reset
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- script:
- - bundle exec rake db:migrate:reset
-
-.migration-paths: &migration-paths
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- variables:
- SETUP_DB: "false"
- script:
- - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
- - git checkout -f FETCH_HEAD
- - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile
- - bundle update google-protobuf grpc
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - cp config/gitlab.yml.example config/gitlab.yml
- - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- - date
- - git checkout -f $CI_COMMIT_SHA
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - . scripts/prepare_build.sh
- - date
- - bundle exec rake db:migrate
-
-##
-# Trigger a package build in omnibus-gitlab repository
-#
-package-and-qa:
- image: ruby:2.5-alpine
- stage: test
- before_script: []
- dependencies: []
- cache: {}
- variables:
- GIT_DEPTH: "1"
- API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
- retry: 0
- script:
- - apk add --update openssl curl jq
- - gem install gitlab --no-document
- - source ./scripts/review_apps/review-apps.sh
- - wait_for_job_to_be_done "gitlab:assets:compile"
- - ./scripts/trigger-build omnibus
- when: manual
- only:
- - //@gitlab-org/gitlab-ce
- - //@gitlab-org/gitlab-ee
-
-# Review docs base
-.review-docs: &review-docs
- <<: *dedicated-runner
- <<: *single-script-job
- variables:
- <<: *single-script-job-variables
- SCRIPT_NAME: trigger-build-docs
- environment:
- name: review-docs/$CI_COMMIT_REF_SLUG
- # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
- # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
- url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
- on_stop: review-docs-cleanup
-
-# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
-# Useful to preview the docs changes live.
-review-docs-deploy-manual:
- <<: *review-docs
- stage: build
- script:
- - gem install gitlab --no-document
- - ./$SCRIPT_NAME deploy
- when: manual
- only:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- <<: *except-docs-and-qa
-
-# Always trigger a docs build in gitlab-docs only on docs-only branches.
-# Useful to preview the docs changes live.
-review-docs-deploy:
- <<: *review-docs
- stage: post-test
- script:
- - gem install gitlab --no-document
- - ./$SCRIPT_NAME deploy
- only:
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ce
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ee
- <<: *except-qa
-
-# Cleanup remote environment of gitlab-docs
-review-docs-cleanup:
- <<: *review-docs
- stage: post-cleanup
- environment:
- name: review-docs/$CI_COMMIT_REF_SLUG
- action: stop
- script:
- - gem install gitlab --no-document
- - ./$SCRIPT_NAME cleanup
- when: manual
- only:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
-
-##
-# Trigger a docker image build in CNG (Cloud Native GitLab) repository
-#
-cloud-native-image:
- image: ruby:2.5-alpine
- before_script: []
- dependencies: []
- stage: post-test
- allow_failure: true
- variables:
- GIT_DEPTH: "1"
- cache: {}
- when: always
- script:
- - gem install gitlab --no-document
- - CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
- only:
- - tags@gitlab-org/gitlab-ce
- - tags@gitlab-org/gitlab-ee
-
-# Retrieve knapsack and rspec_flaky reports
-retrieve-tests-metadata:
- <<: *tests-metadata-state
- <<: *except-docs-and-qa
- stage: prepare
- cache:
- key: tests_metadata
- policy: pull
- script:
- - mkdir -p knapsack/${CI_PROJECT_NAME}/
- - 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
- - '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
- - mkdir -p rspec_flaky/
- - 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-tests-metadata:
- <<: *tests-metadata-state
- <<: *only-canonical-masters
- stage: post-test
- cache:
- key: tests_metadata
- paths:
- - knapsack/
- - rspec_flaky/
- policy: push
- script:
- - retry gem install fog-aws mime-types activesupport --no-document
- - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_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/all_*.json rspec_flaky/new_*.json
-
-flaky-examples-check:
- <<: *dedicated-runner
- image: ruby:2.5-alpine
- services: []
- before_script: []
- variables:
- SETUP_DB: "false"
- USE_BUNDLE_INSTALL: "false"
- NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
- stage: post-test
- allow_failure: true
- retry: 0
- only:
- - branches
- except:
- - master
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
- 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/new_*_*.json
- - scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
-
-.assets-compile-cache: &assets-compile-cache
- cache:
- key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v5"
- paths:
- - vendor/ruby/
- - .yarn-cache/
- - tmp/cache/assets/sprockets
-
-compile-assets:
- <<: *dedicated-runner
- <<: *except-docs
- <<: *use-pg
- stage: prepare
- script:
- - node --version
- - yarn install --frozen-lockfile --cache-folder .yarn-cache
- - free -m
- - bundle exec rake gitlab:assets:compile
- - scripts/clean-old-cached-assets
- variables:
- # we override the max_old_space_size to prevent OOM errors
- NODE_OPTIONS: --max_old_space_size=3584
- artifacts:
- expire_in: 7d
- paths:
- - node_modules
- - public/assets
- <<: *assets-compile-cache
-
-setup-test-env:
- <<: *dedicated-runner
- <<: *except-docs
- <<: *use-pg
- stage: prepare
- cache:
- <<: *default-cache
- script:
- - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- - scripts/gitaly-test-build # Do not use 'bundle exec' here
- artifacts:
- expire_in: 7d
- paths:
- - tmp/tests
- - config/secrets.yml
- - vendor/gitaly-ruby
-
-# GitLab Review apps
-.review-only: &review-only
- only:
- refs:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- kubernetes: active
- except:
- refs:
- - master
- - /(^docs[\/-].*|.*-docs$)/
-
-.review-base: &review-base
- <<: *dedicated-no-docs-no-db-pull-cache-job
- <<: *review-only
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: test
- cache: {}
- dependencies: []
- environment: &review-environment
- name: review/${CI_COMMIT_REF_NAME}
- url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
- before_script: []
-
-.review-docker: &review-docker
- <<: *review-base
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
- services:
- - docker:stable-dind
- tags:
- - gitlab-org
- - docker
- variables: &review-docker-variables
- GIT_DEPTH: "1"
- DOCKER_DRIVER: overlay2
- DOCKER_HOST: tcp://docker:2375
- LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
- QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
-
-build-qa-image:
- <<: *review-docker
- stage: prepare
- script:
- - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
- - echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- - time docker push ${QA_IMAGE}
-
-danger-review:
- <<: *pull-cache
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
- stage: test
- dependencies: []
- before_script: []
- only:
- variables:
- - $DANGER_GITLAB_API_TOKEN
- except:
- refs:
- - master
- variables:
- - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
- - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
- script:
- - git version
- - node --version
- - yarn install --frozen-lockfile --cache-folder .yarn-cache
- - danger --fail-on-errors=true
-
-rspec-pg:
- <<: *rspec-metadata-pg
- parallel: 50
-
-rspec-mysql:
- <<: *rspec-metadata-mysql
- parallel: 50
-
-.rspec-quarantine: &rspec-quarantine
- retry: 0
- script:
- - export CACHE_CLASSES=true
- - scripts/gitaly-test-spawn
- - bin/rspec --color --format documentation --tag quarantine spec/
-
-rspec-pg-quarantine:
- <<: *rspec-metadata-pg
- <<: *rspec-quarantine
- allow_failure: true
-
-rspec-mysql-quarantine:
- <<: *rspec-metadata-mysql
- <<: *rspec-quarantine
- allow_failure: true
-
-static-analysis:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- dependencies:
- - compile-assets
- - setup-test-env
- script:
- - scripts/static-analysis
- cache:
- key: "debian-stretch-ruby-2.5.3-node-10.x-and-rubocop"
- paths:
- - vendor/ruby
- - .yarn-cache/
- - tmp/rubocop_cache
-
-# Documentation checks:
-# - Check validity of relative links
-# - Make sure cURL examples in API docs use the full switches
-docs lint:
- <<: *dedicated-runner
- <<: *except-qa
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
- stage: test
- cache: {}
- dependencies: []
- before_script: []
- script:
- - scripts/lint-doc.sh
- - scripts/lint-changelog-yaml
- - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- - cd /tmp/gitlab-docs
- # Build HTML from Markdown
- - bundle exec nanoc
- # Check the internal links
- - bundle exec nanoc check internal_links
-
-downtime_check:
- <<: *rake-exec
- except:
- - master
- - tags
- - /^[\d-]+-stable(-ee)?$/
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
-
-ee_compat_check:
- <<: *rake-exec
- dependencies: []
- except:
- - master
- - tags
- - /[\d-]+-stable(-ee)?/
- - /^security-/
- - branches@gitlab-org/gitlab-ee
- - branches@gitlab/gitlab-ee
- retry: 0
- artifacts:
- name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
- when: always
- expire_in: 10d
- paths:
- - ee_compat_check/patches/*.patch
-
-db:migrate:reset-pg:
- <<: *db-migrate-reset
- <<: *use-pg
-
-db:migrate:reset-mysql:
- <<: *db-migrate-reset
- <<: *use-mysql
-
-db:check-schema-pg:
- <<: *db-migrate-reset
- <<: *use-pg
- script:
- - source scripts/schema_changed.sh
-
-migration:path-pg:
- <<: *migration-paths
- <<: *use-pg
-
-migration:path-mysql:
- <<: *migration-paths
- <<: *use-mysql
-
-.db-rollback: &db-rollback
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- script:
- - bundle exec rake db:migrate VERSION=20170523121229
- - bundle exec rake db:migrate
-
-db:rollback-pg:
- <<: *db-rollback
- <<: *use-pg
-
-db:rollback-mysql:
- <<: *db-rollback
- <<: *use-mysql
-
-gitlab:setup-pg:
- <<: *gitlab-setup
- <<: *use-pg
-
-gitlab:setup-mysql:
- <<: *gitlab-setup
- <<: *use-mysql
-
-# Frontend-related jobs
-gitlab:assets:compile:
- <<: *dedicated-no-docs-pull-cache-job
- image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
- dependencies:
- - setup-test-env
- services:
- - docker:stable-dind
- variables:
- NODE_ENV: "production"
- RAILS_ENV: "production"
- SETUP_DB: "false"
- SKIP_STORAGE_VALIDATION: "true"
- WEBPACK_REPORT: "true"
- # we override the max_old_space_size to prevent OOM errors
- NODE_OPTIONS: --max_old_space_size=3584
- DOCKER_DRIVER: overlay2
- DOCKER_HOST: tcp://docker:2375
- script:
- - node --version
- - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- - free -m
- - bundle exec rake gitlab:assets:compile
- - time scripts/build_assets_image
- - scripts/clean-old-cached-assets
- artifacts:
- name: webpack-report
- expire_in: 31d
- paths:
- - webpack-report/
- - public/assets/
- <<: *assets-compile-cache
- only:
- - //@gitlab-org/gitlab-ce
- - //@gitlab-org/gitlab-ee
- - //@gitlab/gitlabhq
- - //@gitlab/gitlab-ee
- tags:
- - docker
- - gitlab-org
-
-karma:
- <<: *dedicated-no-docs-pull-cache-job
- <<: *use-pg
- dependencies:
- - compile-assets
- - setup-test-env
- variables:
- # we override the max_old_space_size to prevent OOM errors
- NODE_OPTIONS: --max_old_space_size=3584
- script:
- - export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- - date
- - scripts/gitaly-test-spawn
- - date
- - bundle exec rake karma
- coverage: '/^Statements *: (\d+\.\d+%)/'
- artifacts:
- name: coverage-javascript
- expire_in: 31d
- when: always
- paths:
- - chrome_debug.log
- - coverage-javascript/
- reports:
- junit: junit_karma.xml
-
-jest:
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- <<: *use-pg
- dependencies:
- - compile-assets
- - setup-test-env
- script:
- - scripts/gitaly-test-spawn
- - date
- - bundle exec rake karma:fixtures
- - date
- - yarn jest --ci --coverage
- artifacts:
- name: coverage-frontend
- expire_in: 31d
- when: always
- paths:
- - coverage-frontend/
- - junit_jest.xml
- reports:
- junit: junit_jest.xml
- cache:
- key: jest
- paths:
- - tmp/jest/jest/
-
-code_quality:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:stable
- allow_failure: true
- # gitlab-org runners set `privileged: false` but we need to have it set to true
- # since we're using Docker in Docker
- tags: []
- before_script: []
- services:
- - docker:stable-dind
- variables:
- SETUP_DB: "false"
- DOCKER_DRIVER: overlay2
- cache: {}
- dependencies: []
- script:
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env SOURCE_CODE="$PWD"
- --volume "$PWD":/code
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
- artifacts:
- reports:
- codequality: gl-code-quality-report.json
- expire_in: 1 week
-
-sast:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:stable
- variables:
- SAST_CONFIDENCE_LEVEL: 2
- DOCKER_DRIVER: overlay2
- allow_failure: true
- tags: []
- before_script: []
- cache: {}
- dependencies: []
- services:
- - docker:stable-dind
- script:
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
- --volume "$PWD:/code"
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
- artifacts:
- reports:
- sast: gl-sast-report.json
-
-dependency_scanning:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- tags: []
- before_script: []
- cache: {}
- dependencies: []
- services:
- - docker:stable-dind
- script:
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
- --volume "$PWD:/code"
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
- artifacts:
- reports:
- dependency_scanning: gl-dependency-scanning-report.json
-
-qa:internal:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- services: []
- script:
- - cd qa/
- - bundle install
- - bundle exec rspec
-
-qa:selectors:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- services: []
- script:
- - cd qa/
- - bundle install
- - bundle exec bin/qa Test::Sanity::Selectors
-
-.qa-frontend-node: &qa-frontend-node
- <<: *dedicated-no-docs-no-db-pull-cache-job
- stage: test
- variables:
- NODE_OPTIONS: --max_old_space_size=3584
- cache:
- key: "$CI_JOB_NAME"
- paths:
- - .yarn-cache/
- dependencies: []
- before_script: []
- script:
- - date
- - yarn install --frozen-lockfile --cache-folder .yarn-cache
- - date
- - yarn run webpack-prod
-
-qa-frontend-node:8:
- <<: *qa-frontend-node
- image: node:8-alpine
-
-qa-frontend-node:10:
- <<: *qa-frontend-node
- image: node:10-alpine
-
-qa-frontend-node:latest:
- <<: *qa-frontend-node
- image: node:alpine
- allow_failure: true
-
-coverage:
- # Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
- # download artifacts from all the rspec jobs instead of from setup-test-env only
- <<: *dedicated-runner
- <<: *except-docs-and-qa
- <<: *pull-cache
- variables:
- SETUP_DB: "false"
- stage: post-test
- script:
- - bundle exec scripts/merge-simplecov
- coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
- artifacts:
- name: coverage
- expire_in: 31d
- paths:
- - coverage/index.html
- - coverage/assets/
-
-lint:javascript:report:
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- stage: post-test
- dependencies: []
- before_script: []
- script:
- - date
- - yarn run eslint-report || true # ignore exit code
- artifacts:
- name: eslint-report
- expire_in: 31d
- paths:
- - eslint-report.html
-
-jsdoc:
- <<: *dedicated-no-docs-pull-cache-job
- stage: post-test
- dependencies:
- - compile-assets
- before_script: []
- script:
- - date
- - yarn run jsdoc || true # ignore exit code
- artifacts:
- name: jsdoc
- expire_in: 31d
- paths:
- - jsdoc/
-
-pages:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- before_script: []
- stage: pages
- dependencies:
- - coverage
- - karma
- - gitlab:assets:compile
- - lint:javascript:report
- - jsdoc
- script:
- - mv public/ .public/
- - mkdir public/
- - mv coverage/ public/coverage-ruby/ || true
- - mv coverage-javascript/ public/coverage-javascript/ || true
- - mv eslint-report.html public/ || true
- - mv webpack-report/ public/webpack-report/ || true
- - cp .public/assets/application-*.css public/application.css || true
- - cp .public/assets/application-*.css.gz public/application.css.gz || true
- - mv jsdoc/ public/jsdoc/ || true
- artifacts:
- paths:
- - public
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
-
-# Insurance in case a gem needed by one of our releases gets yanked from
-# rubygems.org in the future.
-cache gems:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- script:
- - bundle package --all --all-platforms
- artifacts:
- paths:
- - vendor/cache
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - tags
-
-gitlab_git_test:
- <<: *dedicated-runner
- <<: *except-docs-and-qa
- variables:
- SETUP_DB: "false"
- before_script: []
- dependencies: []
- cache: {}
- script:
- - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
-
-no_ee_check:
- <<: *dedicated-runner
- <<: *except-docs-and-qa
- variables:
- SETUP_DB: "false"
- before_script: []
- dependencies: []
- cache: {}
- script:
- - scripts/no-ee-check
- only:
- - //@gitlab-org/gitlab-ce
-
-# GitLab Review apps
-review-build-cng:
- <<: *review-only
- image: ruby:2.5-alpine
- stage: test
- before_script: []
- dependencies: []
- cache: {}
- variables:
- GIT_DEPTH: "1"
- API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
- script:
- - apk add --update openssl curl jq
- - gem install gitlab --no-document
- - source ./scripts/review_apps/review-apps.sh
- - wait_for_job_to_be_done "gitlab:assets:compile"
- - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
-
-review-deploy:
- <<: *review-base
- retry: 2
- allow_failure: true
- variables:
- GIT_DEPTH: "1"
- HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
- DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
- GITLAB_HELM_CHART_REF: "master"
- API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
- environment:
- <<: *review-environment
- on_stop: review-stop
- before_script:
- - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- - apk update && apk add jq
- - gem install gitlab --no-document
- - source ./scripts/review_apps/review-apps.sh
- script:
- - wait_for_job_to_be_done "review-build-cng"
- - check_kube_domain
- - download_gitlab_chart
- - ensure_namespace
- - install_tiller
- - install_external_dns
- - time deploy
- - add_license
-
-.review-qa-base: &review-qa-base
- <<: *review-docker
- allow_failure: true
- variables:
- <<: *review-docker-variables
- API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
- QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
- QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
- GITLAB_USERNAME: "root"
- GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
- GITLAB_ADMIN_USERNAME: "root"
- GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
- GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
- EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
- QA_DEBUG: "true"
- artifacts:
- paths:
- - ./qa/gitlab-qa-run-*
- expire_in: 7 days
- when: always
- before_script:
- - echo "${QA_IMAGE}"
- - echo "${CI_ENVIRONMENT_URL}"
- - apk update && apk add curl jq
- - source ./scripts/review_apps/review-apps.sh
- - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
- - wait_for_job_to_be_done "review-deploy"
-
-review-qa-smoke:
- <<: *review-qa-base
- script:
- - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
-
-review-qa-all:
- <<: *review-qa-base
- script:
- - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
- when: manual
-
-review-stop:
- <<: *review-base
- <<: *single-script-job
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- allow_failure: true
- variables:
- <<: *single-script-job-variables
- SCRIPT_NAME: "review_apps/review-apps.sh"
- when: manual
- environment:
- <<: *review-environment
- action: stop
- script:
- - source $(basename "${SCRIPT_NAME}")
- - delete
- - cleanup
-
-schedule:review-cleanup:
- <<: *review-base
- stage: build
- allow_failure: true
- variables:
- GIT_DEPTH: "1"
- environment:
- name: review/auto-cleanup
- only:
- refs:
- - schedules@gitlab-org/gitlab-ce
- - schedules@gitlab-org/gitlab-ee
- kubernetes: active
- except:
- - tags
- - /(^docs[\/-].*|.*-docs$)/
- before_script:
- - gem install gitlab --no-document
- script:
- - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
+include:
+ - local: .gitlab/ci/global.gitlab-ci.yml
+ - local: .gitlab/ci/cng.gitlab-ci.yml
+ - local: .gitlab/ci/docs.gitlab-ci.yml
+ - local: .gitlab/ci/frontend.gitlab-ci.yml
+ - local: .gitlab/ci/pages.gitlab-ci.yml
+ - local: .gitlab/ci/qa.gitlab-ci.yml
+ - local: .gitlab/ci/reports.gitlab-ci.yml
+ - local: .gitlab/ci/rails.gitlab-ci.yml
+ - local: .gitlab/ci/review.gitlab-ci.yml
+ - local: .gitlab/ci/setup.gitlab-ci.yml
+ - local: .gitlab/ci/test-metadata.gitlab-ci.yml
+ - local: .gitlab/ci/yaml.gitlab-ci.yml
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
new file mode 100644
index 00000000000..d49fa4a49a0
--- /dev/null
+++ b/.gitlab/CODEOWNERS
@@ -0,0 +1,21 @@
+# Backend Maintainers are the default for all ruby files
+*.rb @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
+*.rake @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
+
+# Technical writing team are the default reviewers for everything in `doc/`
+/doc/ @axil @marcia
+
+# Frontend maintainers should see everything in `app/assets/`
+app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
+*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
+
+# Someone from the database team should review changes in `db/`
+db/ @abrandl @NikolayS
+
+# Feature specific owners
+/ee/lib/gitlab/code_owners/ @reprazent
+/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
+/lib/gitlab/auth/ldap/ @dblessing @mkozono
+/lib/gitlab/ci/templates/ @nolith @zj
+/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
+/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham
diff --git a/.gitlab/CODEOWNERS.disabled b/.gitlab/CODEOWNERS.disabled
deleted file mode 100644
index b9f886c1d47..00000000000
--- a/.gitlab/CODEOWNERS.disabled
+++ /dev/null
@@ -1,20 +0,0 @@
-# Backend Maintainers are the default for all ruby files
-*.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
-*.rake @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
-
-# Technical writing team are the default reviewers for everything in `doc/`
-/doc/ @axil @marcia
-
-# Frontend maintainers should see everything in `app/assets/`
-app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
-*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
-
-# Someone from the database team should review changes in `db/`
-db/ @abrandl @NikolayS
-
-# Feature specific owners
-/ee/lib/gitlab/code_owners/ @reprazent
-/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
-/lib/gitlab/auth/ldap/ @dblessing @mkozono
-/lib/gitlab/ci/templates/ @nolith @zj
-/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
diff --git a/.gitlab/ci/cng.gitlab-ci.yml b/.gitlab/ci/cng.gitlab-ci.yml
new file mode 100644
index 00000000000..d624e8d09f6
--- /dev/null
+++ b/.gitlab/ci/cng.gitlab-ci.yml
@@ -0,0 +1,16 @@
+cloud-native-image:
+ image: ruby:2.6-alpine
+ before_script: []
+ dependencies: []
+ stage: post-test
+ allow_failure: true
+ variables:
+ GIT_DEPTH: "1"
+ cache: {}
+ when: manual
+ script:
+ - install_gitlab_gem
+ - CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
+ only:
+ - tags@gitlab-org/gitlab-ce
+ - tags@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
new file mode 100644
index 00000000000..5aa1a856405
--- /dev/null
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -0,0 +1,76 @@
+.review-docs: &review-docs
+ extends: .single-script-job-dedicated-runner
+ variables:
+ SCRIPT_NAME: trigger-build-docs
+ environment:
+ name: review-docs/$CI_COMMIT_REF_SLUG
+ # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
+ # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
+ url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
+ on_stop: review-docs-cleanup
+
+# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
+# Useful to preview the docs changes live.
+review-docs-deploy-manual:
+ <<: *review-docs
+ stage: build
+ script:
+ - gem install gitlab --no-document
+ - ./$SCRIPT_NAME deploy
+ when: manual
+ only:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+
+# Always trigger a docs build in gitlab-docs only on docs-only branches.
+# Useful to preview the docs changes live.
+review-docs-deploy:
+ <<: *review-docs
+ stage: post-test
+ script:
+ - gem install gitlab --no-document
+ - ./$SCRIPT_NAME deploy
+ only:
+ - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ce
+ - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ee
+ except:
+ - /(^qa[\/-].*|.*-qa$)/
+
+# Cleanup remote environment of gitlab-docs
+review-docs-cleanup:
+ <<: *review-docs
+ stage: post-cleanup
+ environment:
+ name: review-docs/$CI_COMMIT_REF_SLUG
+ action: stop
+ script:
+ - gem install gitlab --no-document
+ - ./$SCRIPT_NAME cleanup
+ when: manual
+ only:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+
+docs lint:
+ extends: .dedicated-runner
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
+ stage: test
+ cache: {}
+ dependencies: []
+ before_script: []
+ script:
+ - scripts/lint-doc.sh
+ - scripts/lint-changelog-yaml
+ - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
+ - cd /tmp/gitlab-docs
+ # Build HTML from Markdown
+ - bundle exec nanoc
+ # Check the internal links
+ - bundle exec nanoc check internal_links
+ # Check the internal anchor links
+ - bundle exec nanoc check internal_anchors
+ except:
+ - /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
new file mode 100644
index 00000000000..fd5733593ef
--- /dev/null
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -0,0 +1,243 @@
+.assets-compile-cache: &assets-compile-cache
+ cache:
+ key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v5"
+ paths:
+ - vendor/ruby/
+ - .yarn-cache/
+ - tmp/cache/assets/sprockets
+ policy: pull-push
+
+.use-pg: &use-pg
+ services:
+ - name: postgres:9.6
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+
+gitlab:assets:compile:
+ <<: *assets-compile-cache
+ extends: .dedicated-no-docs-pull-cache-job
+ image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.21-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
+ dependencies:
+ - setup-test-env
+ services:
+ - docker:stable-dind
+ variables:
+ NODE_ENV: "production"
+ RAILS_ENV: "production"
+ SETUP_DB: "false"
+ SKIP_STORAGE_VALIDATION: "true"
+ WEBPACK_REPORT: "true"
+ # we override the max_old_space_size to prevent OOM errors
+ NODE_OPTIONS: --max_old_space_size=3584
+ DOCKER_DRIVER: overlay2
+ DOCKER_HOST: tcp://docker:2375
+ script:
+ - node --version
+ - retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache
+ - free -m
+ - retry bundle exec rake gitlab:assets:compile
+ - time scripts/build_assets_image
+ - scripts/clean-old-cached-assets
+ - rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here
+ # Play dependent manual jobs
+ - install_api_client_dependencies_with_apt
+ - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
+ - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
+ artifacts:
+ name: webpack-report
+ expire_in: 31d
+ paths:
+ - webpack-report/
+ - public/assets/
+ only:
+ - /.+/@gitlab-org/gitlab-ce
+ - /.+/@gitlab-org/gitlab-ee
+ - /.+/@gitlab/gitlabhq
+ - /.+/@gitlab/gitlab-ee
+ tags:
+ - docker
+ - gitlab-org
+
+compile-assets:
+ extends: .dedicated-runner
+ <<: *use-pg
+ <<: *assets-compile-cache
+ stage: prepare
+ script:
+ - node --version
+ - retry yarn install --frozen-lockfile --cache-folder .yarn-cache
+ - free -m
+ - retry bundle exec rake gitlab:assets:compile
+ - scripts/clean-old-cached-assets
+ variables:
+ # we override the max_old_space_size to prevent OOM errors
+ NODE_OPTIONS: --max_old_space_size=3584
+ artifacts:
+ expire_in: 7d
+ paths:
+ - node_modules
+ - public/assets
+ except:
+ refs:
+ - /(^docs[\/-].*|.*-docs$)/
+
+gitlab:ui:visual:
+ extends: .dedicated-runner
+ before_script: []
+ allow_failure: true
+ dependencies:
+ - compile-assets
+ script:
+ # Remove node modules from GitLab that may conflict with gitlab-ui
+ - rm -r node_modules
+ - git clone https://gitlab.com/gitlab-org/gitlab-ui.git
+ - cp public/assets/application-*.css gitlab-ui/styles/application.css
+ - cd gitlab-ui
+ - yarn install
+ - CSS_URL=./application.css yarn test
+ only:
+ changes:
+ - app/assets/stylesheets/*.scss
+ - app/assets/stylesheets/**/*.scss
+ - app/assets/stylesheets/**/**/*.scss
+ except:
+ refs:
+ - /(^docs[\/-].*|.*-docs$)/
+ - master
+ variables:
+ - $CI_COMMIT_MESSAGE =~ /\[skip visual\]/i
+ artifacts:
+ paths:
+ - gitlab-ui/tests/__image_snapshots__/
+ when: always
+
+karma:
+ extends: .dedicated-no-docs-pull-cache-job
+ <<: *use-pg
+ dependencies:
+ - compile-assets
+ - setup-test-env
+ variables:
+ # we override the max_old_space_size to prevent OOM errors
+ NODE_OPTIONS: --max_old_space_size=3584
+ script:
+ - export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
+ - date
+ - scripts/gitaly-test-spawn
+ - date
+ - bundle exec rake karma
+ coverage: '/^Statements *: (\d+\.\d+%)/'
+ artifacts:
+ name: coverage-javascript
+ expire_in: 31d
+ when: always
+ paths:
+ - chrome_debug.log
+ - coverage-javascript/
+ reports:
+ junit: junit_karma.xml
+
+jest:
+ extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+ <<: *use-pg
+ dependencies:
+ - compile-assets
+ - setup-test-env
+ script:
+ - scripts/gitaly-test-spawn
+ - date
+ - bundle exec rake karma:fixtures
+ - date
+ - yarn jest --ci --coverage
+ artifacts:
+ name: coverage-frontend
+ expire_in: 31d
+ when: always
+ paths:
+ - coverage-frontend/
+ - junit_jest.xml
+ reports:
+ junit: junit_jest.xml
+ cache:
+ key: jest
+ paths:
+ - tmp/jest/jest/
+ policy: pull-push
+
+qa:internal:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ services: []
+ script:
+ - cd qa/
+ - bundle install
+ - bundle exec rspec
+ dependencies:
+ - setup-test-env
+
+qa:selectors:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ services: []
+ script:
+ - cd qa/
+ - bundle install
+ - bundle exec bin/qa Test::Sanity::Selectors
+ dependencies:
+ - setup-test-env
+
+.qa-frontend-node: &qa-frontend-node
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ stage: test
+ cache:
+ key: "$CI_JOB_NAME"
+ paths:
+ - .yarn-cache/
+ policy: pull-push
+ dependencies: []
+ before_script: []
+ script:
+ - date
+ - yarn install --frozen-lockfile --cache-folder .yarn-cache
+ - date
+ - yarn run webpack-prod
+
+qa-frontend-node:8:
+ <<: *qa-frontend-node
+ image: node:8-alpine
+
+qa-frontend-node:10:
+ <<: *qa-frontend-node
+ image: node:10-alpine
+
+qa-frontend-node:latest:
+ <<: *qa-frontend-node
+ image: node:alpine
+ allow_failure: true
+
+lint:javascript:report:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ stage: post-test
+ dependencies: []
+ before_script: []
+ script:
+ - date
+ - yarn run eslint-report || true # ignore exit code
+ artifacts:
+ name: eslint-report
+ expire_in: 31d
+ paths:
+ - eslint-report.html
+
+jsdoc:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ stage: post-test
+ dependencies:
+ - compile-assets
+ before_script: []
+ script:
+ - date
+ - yarn run jsdoc || true # ignore exit code
+ artifacts:
+ name: jsdoc
+ expire_in: 31d
+ paths:
+ - jsdoc/
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
new file mode 100644
index 00000000000..aa25bad00e8
--- /dev/null
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -0,0 +1,62 @@
+.dedicated-runner:
+ retry:
+ max: 2 # This is confusing but this means "3 runs at max".
+ when:
+ - unknown_failure
+ - api_failure
+ - runner_system_failure
+ tags:
+ - gitlab-org
+
+.default-cache: &default-cache
+ key: "debian-stretch-ruby-2.6.3-node-10.x"
+ paths:
+ - vendor/ruby
+ - .yarn-cache/
+ - vendor/gitaly-ruby
+
+.dedicated-runner-default-cache:
+ extends: .dedicated-runner
+ cache:
+ <<: *default-cache
+
+# Jobs that only need to pull cache
+.dedicated-pull-cache-job:
+ extends: .dedicated-runner
+ cache:
+ <<: *default-cache
+ policy: pull
+ stage: test
+
+.dedicated-no-docs-pull-cache-job:
+ extends: .dedicated-pull-cache-job
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+
+.dedicated-no-docs-and-no-qa-pull-cache-job:
+ extends: .dedicated-pull-cache-job
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+
+# Jobs that do not need a DB
+.dedicated-no-docs-no-db-pull-cache-job:
+ extends: .dedicated-no-docs-pull-cache-job
+ variables:
+ SETUP_DB: "false"
+
+.single-script-job-dedicated-runner:
+ extends: .dedicated-runner
+ image: ruby:2.6-alpine
+ stage: test
+ cache: {}
+ dependencies: []
+ variables:
+ GIT_STRATEGY: none
+ before_script:
+ # We don't clone the repo by using GIT_STRATEGY: none and only download the
+ # single script we need here so it's much faster than cloning.
+ - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
+ - apk add --update openssl
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
+ - chmod 755 $(basename $SCRIPT_NAME)
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
new file mode 100644
index 00000000000..f7b18b809b4
--- /dev/null
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -0,0 +1,26 @@
+pages:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ before_script: []
+ stage: pages
+ dependencies:
+ - coverage
+ - karma
+ - gitlab:assets:compile
+ - lint:javascript:report
+ - jsdoc
+ script:
+ - mv public/ .public/
+ - mkdir public/
+ - mv coverage/ public/coverage-ruby/ || true
+ - mv coverage-javascript/ public/coverage-javascript/ || true
+ - mv eslint-report.html public/ || true
+ - mv webpack-report/ public/webpack-report/ || true
+ - cp .public/assets/application-*.css public/application.css || true
+ - cp .public/assets/application-*.css.gz public/application.css.gz || true
+ - mv jsdoc/ public/jsdoc/ || true
+ artifacts:
+ paths:
+ - public
+ only:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
new file mode 100644
index 00000000000..ee9e467886a
--- /dev/null
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -0,0 +1,17 @@
+package-and-qa:
+ image: ruby:2.6-alpine
+ stage: review # So even if review-deploy failed we can still run this
+ when: manual
+ before_script: []
+ dependencies: []
+ cache: {}
+ variables:
+ GIT_DEPTH: "1"
+ retry: 0
+ script:
+ - source scripts/utils.sh
+ - install_gitlab_gem
+ - ./scripts/trigger-build omnibus
+ only:
+ - /.+/@gitlab-org/gitlab-ce
+ - /.+/@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
new file mode 100644
index 00000000000..33e7b00784c
--- /dev/null
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -0,0 +1,349 @@
+.use-pg: &use-pg
+ services:
+ - name: postgres:9.6
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+
+.use-pg-10: &use-pg-10
+ services:
+ - name: postgres:10.7
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+
+.use-mysql: &use-mysql
+ services:
+ - mysql:5.7
+ - redis:alpine
+
+.only-schedules-master: &only-schedules-master
+ only:
+ - schedules@gitlab-org/gitlab-ce
+ - schedules@gitlab-org/gitlab-ee
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
+
+.gitlab-setup: &gitlab-setup
+ extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+ <<: *use-pg
+ variables:
+ SETUP_DB: "false"
+ script:
+ # Manually clone gitlab-test and only seed this project in
+ # db/fixtures/development/04_project.rb thanks to SIZE=1 below
+ - git clone https://gitlab.com/gitlab-org/gitlab-test.git
+ /home/git/repositories/gitlab-org/gitlab-test.git
+ - scripts/gitaly-test-spawn
+ - force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
+ artifacts:
+ when: on_failure
+ expire_in: 1d
+ paths:
+ - log/development.log
+
+.rake-exec: &rake-exec
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ script:
+ - bundle exec rake $CI_JOB_NAME
+
+.rspec-metadata: &rspec-metadata
+ extends: .dedicated-pull-cache-job
+ stage: test
+ script:
+ - JOB_NAME=( $CI_JOB_NAME )
+ - TEST_TOOL=${JOB_NAME[0]}
+ - TEST_LEVEL=${JOB_NAME[1]}
+ - DATABASE=${JOB_NAME[2]}
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_GENERATE_REPORT=true KNAPSACK_LOG_LEVEL=debug KNAPSACK_TEST_DIR=spec
+ - export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
+ - export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${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}
+ - '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
+ - '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
+ - scripts/gitaly-test-spawn
+ - date
+ - 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
+ - knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
+ - date
+ artifacts:
+ expire_in: 31d
+ when: always
+ paths:
+ - coverage/
+ - knapsack/
+ - rspec_flaky/
+ - rspec_profiling/
+ - tmp/capybara/
+ reports:
+ junit: junit_rspec.xml
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+
+.rspec-metadata-pg: &rspec-metadata-pg
+ <<: *rspec-metadata
+ <<: *use-pg
+
+.rspec-metadata-pg-10: &rspec-metadata-pg-10
+ <<: *rspec-metadata
+ <<: *use-pg-10
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29"
+
+.rspec-metadata-mysql: &rspec-metadata-mysql
+ <<: *rspec-metadata
+ <<: *use-mysql
+
+# DB migration, rollback, and seed jobs
+.db-migrate-reset: &db-migrate-reset
+ extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+ script:
+ - bundle exec rake db:migrate:reset
+ dependencies:
+ - setup-test-env
+
+.migration-paths: &migration-paths
+ extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+ variables:
+ SETUP_DB: "false"
+ script:
+ - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
+ - git checkout -f FETCH_HEAD
+ - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile
+ - sed -i "s/gem 'bootsnap', '~> 1.0.0'/gem 'bootsnap'/" Gemfile
+ - bundle update google-protobuf grpc bootsnap
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - date
+ - cp config/gitlab.yml.example config/gitlab.yml
+ - bundle exec rake db:drop db:create db:schema:load db:seed_fu
+ - date
+ - git checkout -f $CI_COMMIT_SHA
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - date
+ - . scripts/prepare_build.sh
+ - date
+ - bundle exec rake db:migrate
+ dependencies:
+ - setup-test-env
+
+setup-test-env:
+ extends: .dedicated-runner-default-cache
+ <<: *use-pg
+ stage: prepare
+ script:
+ - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
+ - scripts/gitaly-test-build # Do not use 'bundle exec' here
+ artifacts:
+ expire_in: 7d
+ paths:
+ - tmp/tests
+ - config/secrets.yml
+ - vendor/gitaly-ruby
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+
+rspec unit pg:
+ <<: *rspec-metadata-pg
+ parallel: 20
+
+rspec integration pg:
+ <<: *rspec-metadata-pg
+ parallel: 6
+
+rspec system pg:
+ <<: *rspec-metadata-pg
+ parallel: 24
+
+rspec unit pg-10:
+ <<: *rspec-metadata-pg-10
+ <<: *only-schedules-master
+ parallel: 20
+
+rspec integration pg-10:
+ <<: *rspec-metadata-pg-10
+ <<: *only-schedules-master
+ parallel: 6
+
+rspec system pg-10:
+ <<: *rspec-metadata-pg-10
+ <<: *only-schedules-master
+ parallel: 24
+
+rspec unit mysql:
+ <<: *rspec-metadata-mysql
+ <<: *only-schedules-master
+ parallel: 20
+
+rspec integration mysql:
+ <<: *rspec-metadata-mysql
+ <<: *only-schedules-master
+ parallel: 6
+
+rspec system mysql:
+ <<: *rspec-metadata-mysql
+ <<: *only-schedules-master
+ parallel: 24
+
+.rspec-mysql-on-demand: &rspec-mysql-on-demand
+ only:
+ variables:
+ - $CI_COMMIT_MESSAGE =~ /\[run mysql\]/i
+ - $CI_COMMIT_REF_NAME =~ /mysql/
+
+rspec unit mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 20
+
+rspec integration mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 6
+
+rspec system mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 24
+
+rspec-fast-spec-helper:
+ <<: *rspec-metadata-pg
+ script:
+ - bundle exec rspec spec/fast_spec_helper.rb
+
+.rspec-quarantine: &rspec-quarantine
+ <<: *only-schedules-master
+ script:
+ - export CACHE_CLASSES=true
+ - scripts/gitaly-test-spawn
+ - bin/rspec --color --format documentation --tag quarantine -- spec/
+
+rspec quarantine pg:
+ <<: *rspec-metadata-pg
+ <<: *rspec-quarantine
+ allow_failure: true
+
+rspec quarantine mysql:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-quarantine
+ <<: *only-schedules-master
+ allow_failure: true
+
+static-analysis:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ dependencies:
+ - compile-assets
+ - setup-test-env
+ script:
+ - scripts/static-analysis
+ cache:
+ key: "debian-stretch-ruby-2.6.3-node-10.x-and-rubocop"
+ paths:
+ - vendor/ruby
+ - .yarn-cache/
+ - tmp/rubocop_cache
+ policy: pull-push
+
+downtime_check:
+ <<: *rake-exec
+ except:
+ - master
+ - tags
+ - /^[\d-]+-stable(-ee)?$/
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+ dependencies:
+ - setup-test-env
+
+ee_compat_check:
+ <<: *rake-exec
+ dependencies: []
+ except:
+ - master
+ - tags
+ - /[\d-]+-stable(-ee)?/
+ - /^security-/
+ - branches@gitlab-org/gitlab-ee
+ - branches@gitlab/gitlab-ee
+ retry: 0
+ artifacts:
+ name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
+ when: always
+ expire_in: 10d
+ paths:
+ - ee_compat_check/patches/*.patch
+
+db:migrate:reset-pg:
+ <<: *db-migrate-reset
+ <<: *use-pg
+
+db:migrate:reset-mysql:
+ <<: *db-migrate-reset
+ <<: *use-mysql
+
+db:check-schema-pg:
+ <<: *db-migrate-reset
+ <<: *use-pg
+ script:
+ - source scripts/schema_changed.sh
+
+migration:path-pg:
+ <<: *migration-paths
+ <<: *use-pg
+
+migration:path-mysql:
+ <<: *migration-paths
+ <<: *use-mysql
+
+.db-rollback: &db-rollback
+ extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+ script:
+ - bundle exec rake db:migrate VERSION=20170523121229
+ - bundle exec rake db:migrate
+ dependencies:
+ - setup-test-env
+
+db:rollback-pg:
+ <<: *db-rollback
+ <<: *use-pg
+
+db:rollback-mysql:
+ <<: *db-rollback
+ <<: *use-mysql
+
+gitlab:setup-pg:
+ <<: *gitlab-setup
+ <<: *use-pg
+ dependencies:
+ - setup-test-env
+
+gitlab:setup-mysql:
+ <<: *gitlab-setup
+ <<: *use-mysql
+ dependencies:
+ - setup-test-env
+
+coverage:
+ # Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
+ # download artifacts from all the rspec jobs instead of from setup-test-env only
+ extends: .dedicated-runner-default-cache
+ cache:
+ policy: pull
+ variables:
+ SETUP_DB: "false"
+ stage: post-test
+ script:
+ - bundle exec scripts/merge-simplecov
+ coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
+ artifacts:
+ name: coverage
+ expire_in: 31d
+ paths:
+ - coverage/index.html
+ - coverage/assets/
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
new file mode 100644
index 00000000000..d0e09dbf2f8
--- /dev/null
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -0,0 +1,98 @@
+include:
+ - template: Code-Quality.gitlab-ci.yml
+
+code_quality:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ # gitlab-org runners set `privileged: false` but we need to have it set to true
+ # since we're using Docker in Docker
+ tags: []
+ before_script: []
+ cache: {}
+ dependencies: []
+ variables:
+ SETUP_DB: "false"
+
+sast:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ image: docker:stable
+ variables:
+ SAST_CONFIDENCE_LEVEL: 2
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ tags: []
+ before_script: []
+ cache: {}
+ dependencies: []
+ services:
+ - docker:stable-dind
+ script:
+ - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
+ function propagate_env_vars() {
+ CURRENT_ENV=$(printenv)
+
+ for VAR_NAME; do
+ echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
+ done
+ }
+ - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - |
+ docker run \
+ $(propagate_env_vars \
+ SAST_ANALYZER_IMAGES \
+ SAST_ANALYZER_IMAGE_PREFIX \
+ SAST_ANALYZER_IMAGE_TAG \
+ SAST_DEFAULT_ANALYZERS \
+ SAST_BRAKEMAN_LEVEL \
+ SAST_GOSEC_LEVEL \
+ SAST_FLAWFINDER_LEVEL \
+ SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
+ SAST_RUN_ANALYZER_TIMEOUT \
+ ) \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
+ artifacts:
+ reports:
+ sast: gl-sast-report.json
+
+dependency_scanning:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ tags: []
+ before_script: []
+ cache: {}
+ dependencies: []
+ services:
+ - docker:stable-dind
+ script:
+ - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
+ function propagate_env_vars() {
+ CURRENT_ENV=$(printenv)
+
+ for VAR_NAME; do
+ echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
+ done
+ }
+ - |
+ docker run \
+ $(propagate_env_vars \
+ DS_ANALYZER_IMAGES \
+ DS_ANALYZER_IMAGE_PREFIX \
+ DS_ANALYZER_IMAGE_TAG \
+ DS_DEFAULT_ANALYZERS \
+ DEP_SCAN_DISABLE_REMOTE_CHECKS \
+ DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ DS_PULL_ANALYZER_IMAGE_TIMEOUT \
+ DS_RUN_ANALYZER_TIMEOUT \
+ ) \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
+ artifacts:
+ reports:
+ dependency_scanning: gl-dependency-scanning-report.json
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
new file mode 100644
index 00000000000..9b764028be9
--- /dev/null
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -0,0 +1,235 @@
+.review-only: &review-only
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ refs:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+
+.review-schedules-only: &review-schedules-only
+ only:
+ refs:
+ - schedules@gitlab-org/gitlab-ce
+ - schedules@gitlab-org/gitlab-ee
+ kubernetes: active
+ variables:
+ - $REVIEW_APP_CLEANUP
+ except:
+ refs:
+ - tags
+ - /(^docs[\/-].*|.*-docs$)/
+
+.review-base: &review-base
+ extends: .dedicated-runner
+ <<: *review-only
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ cache: {}
+ dependencies: []
+ before_script:
+ - source scripts/utils.sh
+
+.review-docker: &review-docker
+ <<: *review-base
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
+ services:
+ - docker:stable-dind
+ tags:
+ - gitlab-org
+ - docker
+ variables: &review-docker-variables
+ DOCKER_DRIVER: overlay2
+ DOCKER_HOST: tcp://docker:2375
+ LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
+ QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
+
+build-qa-image:
+ <<: *review-docker
+ stage: test
+ script:
+ - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
+ - echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
+ - time docker push ${QA_IMAGE}
+
+.review-build-cng-base: &review-build-cng-base
+ image: ruby:2.6-alpine
+ stage: test
+ when: manual
+ before_script:
+ - source scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - install_gitlab_gem
+ dependencies: []
+ cache: {}
+ script:
+ - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+
+review-build-cng:
+ <<: *review-only
+ <<: *review-build-cng-base
+
+schedule:review-build-cng:
+ <<: *review-schedules-only
+ <<: *review-build-cng-base
+
+.review-deploy-base: &review-deploy-base
+ <<: *review-base
+ allow_failure: true
+ stage: review
+ variables:
+ HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
+ DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
+ GITLAB_HELM_CHART_REF: "master"
+ environment: &review-environment
+ name: review/${CI_COMMIT_REF_NAME}
+ url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
+ on_stop: review-stop
+ before_script:
+ - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
+ - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
+ - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
+ - echo "${CI_ENVIRONMENT_URL}" > review_app_url.txt
+ - source scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - source scripts/review_apps/review-apps.sh
+ script:
+ - perform_review_app_deployment
+ artifacts:
+ paths:
+ - review_app_url.txt
+ expire_in: 2 days
+ when: always
+
+review-deploy:
+ <<: *review-deploy-base
+
+schedule:review-deploy:
+ <<: *review-deploy-base
+ <<: *review-schedules-only
+ script:
+ - perform_review_app_deployment
+
+review-stop:
+ <<: *review-base
+ stage: review
+ when: manual
+ allow_failure: true
+ variables:
+ GIT_DEPTH: "1"
+ environment:
+ <<: *review-environment
+ action: stop
+ script:
+ - source scripts/review_apps/review-apps.sh
+ - delete
+ - cleanup
+
+.review-qa-base: &review-qa-base
+ <<: *review-docker
+ allow_failure: true
+ stage: qa
+ variables:
+ <<: *review-docker-variables
+ QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
+ QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
+ GITLAB_USERNAME: "root"
+ GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITLAB_ADMIN_USERNAME: "root"
+ GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
+ EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
+ QA_DEBUG: "true"
+ dependencies:
+ - review-deploy
+ artifacts:
+ paths:
+ - ./qa/gitlab-qa-run-*
+ expire_in: 7 days
+ when: always
+ before_script:
+ - export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
+ - echo "${CI_ENVIRONMENT_URL}"
+ - echo "${QA_IMAGE}"
+ - source scripts/utils.sh
+ - install_api_client_dependencies_with_apk
+ - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
+
+review-qa-smoke:
+ <<: *review-qa-base
+ script:
+ - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+
+review-qa-all:
+ <<: *review-qa-base
+ allow_failure: true
+ when: manual
+ parallel: 5
+ script:
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/review-qa-all_master_report.json
+ - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
+ - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+
+.review-performance-base: &review-performance-base
+ <<: *review-qa-base
+ allow_failure: true
+ before_script:
+ - export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
+ - echo "${CI_ENVIRONMENT_URL}"
+ - mkdir -p gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir -p sitespeed-results
+ script:
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
+ after_script:
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - sitespeed-results/
+ reports:
+ performance: performance.json
+
+review-performance:
+ <<: *review-performance-base
+
+schedule:review-performance:
+ <<: *review-performance-base
+ <<: *review-schedules-only
+ dependencies:
+ - schedule:review-deploy
+
+schedule:review-cleanup:
+ <<: *review-base
+ <<: *review-schedules-only
+ stage: build
+ allow_failure: true
+ environment:
+ name: review/auto-cleanup
+ action: stop
+ before_script:
+ - source scripts/utils.sh
+ - install_gitlab_gem
+ script:
+ - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
+
+danger-review:
+ extends: .dedicated-pull-cache-job
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
+ stage: test
+ dependencies: []
+ before_script: []
+ only:
+ variables:
+ - $DANGER_GITLAB_API_TOKEN
+ except:
+ refs:
+ - master
+ variables:
+ - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
+ - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
+ script:
+ - git version
+ - node --version
+ - yarn install --frozen-lockfile --cache-folder .yarn-cache
+ - danger --fail-on-errors=true
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
new file mode 100644
index 00000000000..debc90a1cb0
--- /dev/null
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -0,0 +1,43 @@
+# Insurance in case a gem needed by one of our releases gets yanked from
+# rubygems.org in the future.
+cache gems:
+ extends: .dedicated-no-docs-no-db-pull-cache-job
+ script:
+ - bundle package --all --all-platforms
+ artifacts:
+ paths:
+ - vendor/cache
+ only:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - tags
+ dependencies:
+ - setup-test-env
+
+gitlab_git_test:
+ extends: .dedicated-runner
+ variables:
+ SETUP_DB: "false"
+ before_script: []
+ dependencies: []
+ cache: {}
+ script:
+ - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+
+no_ee_check:
+ extends: .dedicated-runner
+ variables:
+ SETUP_DB: "false"
+ before_script: []
+ dependencies: []
+ cache: {}
+ script:
+ - scripts/no-ee-check
+ only:
+ - /.+/@gitlab-org/gitlab-ce
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
new file mode 100644
index 00000000000..c51f825f831
--- /dev/null
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -0,0 +1,82 @@
+.tests-metadata-state: &tests-metadata-state
+ extends: .dedicated-runner
+ variables:
+ TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
+ before_script:
+ - source scripts/utils.sh
+ artifacts:
+ expire_in: 31d
+ paths:
+ - knapsack/
+ - rspec_flaky/
+ - rspec_profiling/
+
+retrieve-tests-metadata:
+ <<: *tests-metadata-state
+ stage: prepare
+ cache:
+ key: tests_metadata
+ policy: pull
+ script:
+ - mkdir -p knapsack/${CI_PROJECT_NAME}/
+ - 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
+ - '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
+ - mkdir -p rspec_flaky/
+ - mkdir -p rspec_profiling/
+ - 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}'
+ except:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+
+update-tests-metadata:
+ <<: *tests-metadata-state
+ stage: post-test
+ cache:
+ key: tests_metadata
+ paths:
+ - knapsack/
+ - rspec_flaky/
+ policy: push
+ script:
+ - retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
+ - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_*_pg_node_*.json
+ - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
+ - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
+ - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
+ - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
+ - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
+ - rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
+ - scripts/insert-rspec-profiling-data
+ only:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
+
+flaky-examples-check:
+ extends: .dedicated-runner
+ image: ruby:2.6-alpine
+ services: []
+ before_script: []
+ variables:
+ SETUP_DB: "false"
+ USE_BUNDLE_INSTALL: "false"
+ NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
+ stage: post-test
+ allow_failure: true
+ retry: 0
+ only:
+ - branches
+ except:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+ 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/new_*_*.json
+ - scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
new file mode 100644
index 00000000000..401318d2df2
--- /dev/null
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -0,0 +1,9 @@
+# Yamllint of *.yml for .gitlab-ci.yml.
+# This uses rules from project root `.yamllint`.
+lint-ci-gitlab:
+ extends: .dedicated-runner
+ before_script: []
+ dependencies: []
+ image: sdesbure/yamllint:latest
+ script:
+ - yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 3e58d2a867e..3adea22b33a 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -27,9 +27,9 @@ and verify the issue you're about to submit isn't a duplicate.
### Example Project
-(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
+(If possible, please create an example project here on GitLab.com that exhibits the problematic behavior, and link to it here in the bug report)
-(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
+(If you are using an older version of GitLab, this will also determine whether the bug is fixed in a more recent version)
### What is the current *bug* behavior?
@@ -42,7 +42,7 @@ and verify the issue you're about to submit isn't a duplicate.
### Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output,
-logs, and code as it's very hard to read otherwise.)
+logs, and code as it's tough to read otherwise.)
### Output of checks
diff --git a/.gitlab/issue_templates/Database Reviewer.md b/.gitlab/issue_templates/Database Reviewer.md
new file mode 100644
index 00000000000..acbaf5c1965
--- /dev/null
+++ b/.gitlab/issue_templates/Database Reviewer.md
@@ -0,0 +1,34 @@
+#### Database Reviewer Checklist
+
+Thank you for becoming a ~database reviewer! Please work on the list
+below to complete your setup. For any question, reach out to #database
+an mention `@abrandl`.
+
+- [ ] Change issue title to include your name: `Database Reviewer Checklist: Your Name`
+- [ ] Review general [code review guide](https://docs.gitlab.com/ee/development/code_review.html)
+- [ ] Review [database review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html)
+- [ ] Familiarize with [migration helpers](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/database/migration_helpers.rb) and review usage in existing migrations
+- [ ] Read [database migration style guide](https://docs.gitlab.com/ee/development/migration_style_guide.html)
+- [ ] Familiarize with best practices in [database guides](https://docs.gitlab.com/ee/development/#database-guides)
+- [ ] Watch [Optimising Rails Database Queries: Episode 1](https://www.youtube.com/watch?v=79GurlaxhsI)
+- [ ] Read [Understanding EXPLAIN plans](https://docs.gitlab.com/ee/development/understanding_explain_plans.html)
+- [ ] Review [database best practices](https://docs.gitlab.com/ee/development/#best-practices)
+- [ ] Review how we use [database instances restored from a backup](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd) for testing and make sure you're set up to execute pipelines (check [README.md](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd/blob/master/README.md) and reach out to @abrandl since this is currently subject to being changed)
+- [ ] Get yourself added to [`@gl-database`](https://gitlab.com/groups/gl-database/-/group_members) group and respond to @-mentions to the group (reach out to any maintainer on the group to get added). You will get TODOs on gitlab.com for group mentions.
+- [ ] Make sure you have proper access to at least a read-only replica in staging and production
+- [ ] Indicate in `data/team.yml` your role as a database reviewer ([example MR](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/19600/diffs)). Assign MR to your manager for merge.
+- [ ] Send one MR to improve the [review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html) or the [issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Database%20Reviewer.md)
+
+Note that *approving and accepting* merge requests is *restricted* to
+Database Maintainers only. As a reviewer, pass the MR to a maintainer
+for approval.
+
+You're all set! Watch out for TODOs on GitLab.com.
+
+###### Where to go for questions?
+
+Reach out to `#database` on Slack and mention `@abrandl` for any questions.
+
+cc @abrandl
+
+/label ~meta ~database
diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md
index c0919aeeda4..67602b7b2df 100644
--- a/.gitlab/issue_templates/Documentation.md
+++ b/.gitlab/issue_templates/Documentation.md
@@ -9,7 +9,7 @@
* For information about documentation content and process, see
https://docs.gitlab.com/ee/development/documentation/ -->
-### Type of issue
+<!-- Type of issue -->
<!-- Un-comment the line for the applicable doc issue type to add its label.
Note that all text on that line is deleted upon issue creation. -->
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
new file mode 100644
index 00000000000..b7db5a33faf
--- /dev/null
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -0,0 +1,43 @@
+<!-- Title suggestion: [Feature flag] Enable description of feature -->
+
+## What
+
+Remove the `:feature_name` feature flag ...
+
+## Owners
+
+- Team: NAME_OF_TEAM
+- Most appropriate slack channel to reach out to: `#g_TEAM_NAME`
+- Best individual to reach out to: NAME
+
+## Expectations
+
+### What are we expecting to happen?
+
+### What might happen if this goes wrong?
+
+### What can we monitor to detect problems with this?
+
+<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
+
+## Beta groups/projects
+
+If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example.
+
+- `gitlab-org/gitlab-ce`/`gitlab-org/gitlab-ee` projects
+- `gitlab-org`/`gitlab-com` groups
+- ...
+
+## Roll Out Steps
+
+- [ ] Enable on staging
+- [ ] Test on staging
+- [ ] Ensure that documentation has been updated
+- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour
+- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
+- [ ] Enable on GitLab.com by running chatops command in `#production`
+- [ ] Cross post chatops slack command to `#support_gitlab-com` and in your team channel
+- [ ] Announce on the issue that the flag has been enabled
+- [ ] Remove feature flag and add changelog entry
+
+/label ~"feature flag"
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index abfbef4b2b0..8a49715e0e8 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -2,32 +2,10 @@
<!-- What problem do we solve? -->
-### Target audience
+### Intended users
-<!--- For whom are we doing this? Include a [persona](https://design.gitlab.com/research/personas)
-listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A),
-or define a specific company role, e.g. "Release Manager".
-
-Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply)
-
-- Parker, Product Manager, https://design.gitlab.com/research/personas#persona-parker
-/label ~"Persona: Product Manager"
-
-- Delaney, Development Team Lead, https://design.gitlab.com/research/personas#persona-delaney
-/label ~"Persona: Development Team Lead"
-
-- Sasha, Software Developer, https://design.gitlab.com/research/personas#persona-sasha
-/label ~"Persona: Software developer"
-
-- Devon, DevOps Engineer, https://design.gitlab.com/research/personas#persona-devon
-/label ~"Persona: DevOps Engineer"
-
-- Sidney, Systems Administrator, https://design.gitlab.com/research/personas#persona-sidney
-/label ~"Persona: Systems Administrator"
-
-- Sam, Security Analyst, https://design.gitlab.com/research/personas#persona-sam
-/label ~"Persona: Security Analyst"
--->
+<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
+Personas can be found at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/ -->
### Further details
@@ -37,11 +15,19 @@ Existing personas are: (copy relevant personas out of this comment, and delete a
<!-- How are we going to solve the problem? Try to include the user journey! https://about.gitlab.com/handbook/journeys/#user-journey -->
+### Permissions and Security
+
+<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)? -->
+
### Documentation
<!-- See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html
Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements -->
+### Testing
+
+<!-- What risks does this change pose? How might it affect the quality of the product? What additional test coverage or changes to tests will be needed? Will it require cross-browser testing? See the test engineering process for further guidelines: https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/ -->
+
### What does success look like, and how can we measure that?
<!-- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this. -->
diff --git a/.gitlab/issue_templates/Refactoring.md b/.gitlab/issue_templates/Refactoring.md
new file mode 100644
index 00000000000..cd0ce8486f0
--- /dev/null
+++ b/.gitlab/issue_templates/Refactoring.md
@@ -0,0 +1,41 @@
+## Summary
+
+<!--
+Please briefly describe what part of the code base needs to be refactored.
+-->
+
+## Improvements
+
+<!--
+Explain the benefits of refactoring this code.
+See also https://about.gitlab.com/handbook/values/index.html#say-why-not-just-what
+-->
+
+## Risks
+
+<!--
+Please list features that can break because of this refactoring and how you intend to solve that.
+-->
+
+## Involved components
+
+<!--
+List files or directories that will be changed by the refactoring.
+-->
+
+## Optional: Intended side effects
+
+<!--
+If the refactoring involves changes apart from the main improvements (such as a better UI), list them here.
+It may be a good idea to create separate issues and link them here.
+-->
+
+
+## Optional: Missing test coverage
+
+<!--
+If you are aware of tests that need to be written or adjusted apart from unit tests for the changed components,
+please list them here.
+-->
+
+/label ~backstage
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index aaa16145399..7857afb66c2 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -17,10 +17,10 @@ Set the title to: `Description of the original issue`
#### Backports
-- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases, plus the current RC if between the 7th and 22nd of the month.
+- [ ] Once the MR is ready to be merged, create MRs targeting the last 3 releases, plus the current RC if between the 7th and 22nd of the month.
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- - [ ] Create each MR targetting the stable branch `X-Y-stable`, using the "Security Release" merge request template.
+ - [ ] Create each MR targeting the stable branch `X-Y-stable`, using the "Security Release" merge request template.
- Every merge request will have its own set of TODOs, so make sure to
complete those.
- [ ] Make sure all MRs have a link in the [links section](#links)
@@ -30,6 +30,7 @@ Set the title to: `Description of the original issue`
#### Documentation and final details
- [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links)
+- [ ] Add links to this issue and your MRs in the description of the security release issue
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md
index a3c3f4a6509..3aedd5859d3 100644
--- a/.gitlab/issue_templates/Test plan.md
+++ b/.gitlab/issue_templates/Test plan.md
@@ -93,4 +93,4 @@ When adding new automated tests, please keep [testing levels](https://docs.gitla
in mind.
-->
-/label ~Quality ~"test plan"
+/label ~Quality ~"test\-plan"
diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md
index 246f2dae009..42314f9b2dd 100644
--- a/.gitlab/merge_request_templates/Security Release.md
+++ b/.gitlab/merge_request_templates/Security Release.md
@@ -7,6 +7,10 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
This merge request _must not_ close the corresponding security issue _unless_ it
targets master.
+When submitting a merge request for CE, a corresponding EE merge request is
+always required. This makes it easier to merge security merge requests, as
+manually merging CE into EE is no longer required.
+
-->
## Related issues
@@ -20,8 +24,8 @@ targets master.
- [ ] Title of this MR is the same as for all backports
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
- [ ] Add a link to this MR in the `links` section of related issue
-- [ ] Add a link to an EE MR if required
-- [ ] Assign to a reviewer
+- [ ] Set up an EE MR (always required for CE merge requests): EE_MR_LINK_HERE
+- [ ] Assign to a reviewer (that is not a release manager)
## Reviewer checklist
diff --git a/.gitlab/route-map.yml b/.gitlab/route-map.yml
index 0b37dc68f8b..cc0c2856d49 100644
--- a/.gitlab/route-map.yml
+++ b/.gitlab/route-map.yml
@@ -1,3 +1,3 @@
# Documentation
-- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md
- public: '\1.html' # doc/administration/build_artifacts.html
+- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md
+ public: '\1.html' # doc/administration/build_artifacts.html
diff --git a/.haml-lint.yml b/.haml-lint.yml
index bad918ef35d..0412b24a48c 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -1,9 +1,13 @@
+inherits_from:
+ - .haml-lint_todo.yml
# Whether to ignore frontmatter at the beginning of HAML documents for
# frameworks such as Jekyll/Middleman
skip_frontmatter: false
exclude:
- 'vendor/**/*'
- 'spec/**/*'
+require:
+ - './haml_lint/linter/no_plain_nodes.rb'
linters:
AltText:
@@ -97,6 +101,7 @@ linters:
- Cop/LineBreakAroundConditionalBlock
- Cop/ProjectPathHelper
- GitlabSecurity/PublicSend
+ - Layout/EmptyLineAfterGuardClause
- Layout/LeadingCommentSpace
- Layout/SpaceAfterColon
- Layout/SpaceAfterComma
@@ -112,11 +117,13 @@ linters:
- Lint/LiteralInInterpolation
- Lint/ParenthesesAsGroupedExpression
- Lint/RedundantWithIndex
+ - Lint/SafeNavigationConsistency
- Lint/Syntax
- Metrics/BlockNesting
- Naming/VariableName
- Performance/RedundantMatch
- Performance/StringReplacement
+ - Rails/LinkToBlank
- Rails/Presence
- Rails/RequestReferer
- Style/AndOr
@@ -134,6 +141,7 @@ linters:
- Style/TernaryParentheses
- Style/TrailingCommaInHashLiteral
- Style/UnlessElse
+ - Style/UnneededCondition
- Style/WordArray
- Style/ZeroLengthPredicate
@@ -149,7 +157,7 @@ linters:
Indentation:
enabled: true
- character: space # or tab
+ character: space # or tab
TagName:
enabled: true
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
new file mode 100644
index 00000000000..61805c2d8d2
--- /dev/null
+++ b/.haml-lint_todo.yml
@@ -0,0 +1,523 @@
+# This configuration was generated by
+# `haml-lint --auto-gen-config`
+# on 2019-05-07 19:04:08 +0100 using Haml-Lint version 0.30.0.
+# The point is for the user to remove these configuration records
+# one by one as the lints are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of Haml-Lint, may require this file to be generated again.
+
+linters:
+
+ # Offense count: 2075
+ NoPlainNodes:
+ enabled: true
+ exclude:
+ - "app/views/admin/abuse_reports/_abuse_report.html.haml"
+ - "app/views/admin/abuse_reports/index.html.haml"
+ - "app/views/admin/appearances/_form.html.haml"
+ - "app/views/admin/application_settings/_abuse.html.haml"
+ - "app/views/admin/application_settings/_diff_limits.html.haml"
+ - "app/views/admin/application_settings/_gitaly.html.haml"
+ - "app/views/admin/application_settings/_influx.html.haml"
+ - "app/views/admin/application_settings/_ip_limits.html.haml"
+ - "app/views/admin/application_settings/_logging.html.haml"
+ - "app/views/admin/application_settings/_performance.html.haml"
+ - "app/views/admin/application_settings/_plantuml.html.haml"
+ - "app/views/admin/application_settings/_prometheus.html.haml"
+ - "app/views/admin/application_settings/_realtime.html.haml"
+ - "app/views/admin/application_settings/_repository_check.html.haml"
+ - "app/views/admin/application_settings/_repository_storage.html.haml"
+ - "app/views/admin/application_settings/_signin.html.haml"
+ - "app/views/admin/application_settings/_signup.html.haml"
+ - "app/views/admin/application_settings/_spam.html.haml"
+ - "app/views/admin/application_settings/_terminal.html.haml"
+ - "app/views/admin/application_settings/_usage.html.haml"
+ - "app/views/admin/application_settings/_visibility_and_access.html.haml"
+ - "app/views/admin/applications/_delete_form.html.haml"
+ - "app/views/admin/applications/_form.html.haml"
+ - "app/views/admin/applications/edit.html.haml"
+ - "app/views/admin/applications/index.html.haml"
+ - "app/views/admin/applications/new.html.haml"
+ - "app/views/admin/applications/show.html.haml"
+ - "app/views/admin/background_jobs/show.html.haml"
+ - "app/views/admin/broadcast_messages/index.html.haml"
+ - "app/views/admin/dashboard/index.html.haml"
+ - "app/views/admin/deploy_keys/new.html.haml"
+ - "app/views/admin/groups/show.html.haml"
+ - "app/views/admin/health_check/show.html.haml"
+ - "app/views/admin/hook_logs/_index.html.haml"
+ - "app/views/admin/hook_logs/show.html.haml"
+ - "app/views/admin/hooks/_form.html.haml"
+ - "app/views/admin/hooks/edit.html.haml"
+ - "app/views/admin/hooks/index.html.haml"
+ - "app/views/admin/labels/_form.html.haml"
+ - "app/views/admin/logs/show.html.haml"
+ - "app/views/admin/projects/_projects.html.haml"
+ - "app/views/admin/projects/show.html.haml"
+ - "app/views/admin/requests_profiles/index.html.haml"
+ - "app/views/admin/runners/_runner.html.haml"
+ - "app/views/admin/runners/index.html.haml"
+ - "app/views/admin/runners/show.html.haml"
+ - "app/views/admin/services/_form.html.haml"
+ - "app/views/admin/services/index.html.haml"
+ - "app/views/admin/spam_logs/_spam_log.html.haml"
+ - "app/views/admin/spam_logs/index.html.haml"
+ - "app/views/admin/system_info/show.html.haml"
+ - "app/views/admin/users/_access_levels.html.haml"
+ - "app/views/admin/users/_form.html.haml"
+ - "app/views/admin/users/_head.html.haml"
+ - "app/views/admin/users/_profile.html.haml"
+ - "app/views/admin/users/_projects.html.haml"
+ - "app/views/admin/users/new.html.haml"
+ - "app/views/admin/users/projects.html.haml"
+ - "app/views/admin/users/show.html.haml"
+ - "app/views/clusters/clusters/_cluster.html.haml"
+ - "app/views/clusters/clusters/_form.html.haml"
+ - "app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml"
+ - "app/views/clusters/clusters/gcp/_form.html.haml"
+ - "app/views/clusters/clusters/new.html.haml"
+ - "app/views/dashboard/milestones/index.html.haml"
+ - "app/views/dashboard/projects/_blank_state_admin_welcome.html.haml"
+ - "app/views/dashboard/projects/_blank_state_welcome.html.haml"
+ - "app/views/dashboard/projects/_zero_authorized_projects.html.haml"
+ - "app/views/dashboard/snippets/index.html.haml"
+ - "app/views/dashboard/todos/_todo.html.haml"
+ - "app/views/dashboard/todos/index.html.haml"
+ - "app/views/devise/confirmations/almost_there.haml"
+ - "app/views/devise/mailer/_confirmation_instructions_account.html.haml"
+ - "app/views/devise/mailer/_confirmation_instructions_secondary.html.haml"
+ - "app/views/devise/mailer/email_changed.html.haml"
+ - "app/views/devise/mailer/password_change.html.haml"
+ - "app/views/devise/mailer/reset_password_instructions.html.haml"
+ - "app/views/devise/mailer/unlock_instructions.html.haml"
+ - "app/views/devise/passwords/edit.html.haml"
+ - "app/views/devise/sessions/_new_base.html.haml"
+ - "app/views/devise/sessions/_new_crowd.html.haml"
+ - "app/views/devise/sessions/_new_ldap.html.haml"
+ - "app/views/devise/sessions/new.html.haml"
+ - "app/views/devise/sessions/two_factor.html.haml"
+ - "app/views/devise/shared/_omniauth_box.html.haml"
+ - "app/views/devise/shared/_sign_in_link.html.haml"
+ - "app/views/devise/shared/_signup_box.html.haml"
+ - "app/views/devise/shared/_tabs_normal.html.haml"
+ - "app/views/discussions/_discussion.html.haml"
+ - "app/views/discussions/_headline.html.haml"
+ - "app/views/discussions/_notes.html.haml"
+ - "app/views/discussions/_resolve_all.html.haml"
+ - "app/views/doorkeeper/applications/_delete_form.html.haml"
+ - "app/views/doorkeeper/authorized_applications/_delete_form.html.haml"
+ - "app/views/errors/encoding.html.haml"
+ - "app/views/errors/git_not_found.html.haml"
+ - "app/views/errors/omniauth_error.html.haml"
+ - "app/views/errors/precondition_failed.html.haml"
+ - "app/views/events/_commit.html.haml"
+ - "app/views/events/_event_push.atom.haml"
+ - "app/views/events/event/_push.html.haml"
+ - "app/views/groups/_create_chat_team.html.haml"
+ - "app/views/groups/_group_admin_settings.html.haml"
+ - "app/views/groups/group_members/_new_group_member.html.haml"
+ - "app/views/groups/group_members/index.html.haml"
+ - "app/views/groups/labels/edit.html.haml"
+ - "app/views/groups/labels/new.html.haml"
+ - "app/views/groups/milestones/edit.html.haml"
+ - "app/views/groups/milestones/index.html.haml"
+ - "app/views/groups/milestones/new.html.haml"
+ - "app/views/groups/projects.html.haml"
+ - "app/views/groups/runners/edit.html.haml"
+ - "app/views/groups/settings/_advanced.html.haml"
+ - "app/views/groups/settings/_lfs.html.haml"
+ - "app/views/help/_shortcuts.html.haml"
+ - "app/views/help/index.html.haml"
+ - "app/views/help/instance_configuration.html.haml"
+ - "app/views/help/instance_configuration/_gitlab_ci.html.haml"
+ - "app/views/help/instance_configuration/_gitlab_pages.html.haml"
+ - "app/views/help/instance_configuration/_ssh_info.html.haml"
+ - "app/views/help/ui.html.haml"
+ - "app/views/import/bitbucket/status.html.haml"
+ - "app/views/import/bitbucket_server/status.html.haml"
+ - "app/views/instance_statistics/cohorts/_cohorts_table.html.haml"
+ - "app/views/instance_statistics/cohorts/_usage_ping.html.haml"
+ - "app/views/invites/show.html.haml"
+ - "app/views/layouts/_mailer.html.haml"
+ - "app/views/layouts/header/_default.html.haml"
+ - "app/views/layouts/header/_new_dropdown.haml"
+ - "app/views/layouts/mailer/devise.html.haml"
+ - "app/views/layouts/nav/sidebar/_profile.html.haml"
+ - "app/views/layouts/notify.html.haml"
+ - "app/views/notify/_failed_builds.html.haml"
+ - "app/views/notify/_reassigned_issuable_email.html.haml"
+ - "app/views/notify/_removal_notification.html.haml"
+ - "app/views/notify/autodevops_disabled_email.html.haml"
+ - "app/views/notify/changed_milestone_email.html.haml"
+ - "app/views/notify/import_issues_csv_email.html.haml"
+ - "app/views/notify/issue_moved_email.html.haml"
+ - "app/views/notify/member_access_denied_email.html.haml"
+ - "app/views/notify/member_invite_accepted_email.html.haml"
+ - "app/views/notify/member_invite_declined_email.html.haml"
+ - "app/views/notify/member_invited_email.html.haml"
+ - "app/views/notify/new_gpg_key_email.html.haml"
+ - "app/views/notify/new_mention_in_issue_email.html.haml"
+ - "app/views/notify/new_ssh_key_email.html.haml"
+ - "app/views/notify/new_user_email.html.haml"
+ - "app/views/notify/pages_domain_disabled_email.html.haml"
+ - "app/views/notify/pages_domain_enabled_email.html.haml"
+ - "app/views/notify/pages_domain_verification_failed_email.html.haml"
+ - "app/views/notify/pages_domain_verification_succeeded_email.html.haml"
+ - "app/views/notify/pipeline_failed_email.html.haml"
+ - "app/views/notify/pipeline_success_email.html.haml"
+ - "app/views/notify/project_was_exported_email.html.haml"
+ - "app/views/notify/project_was_moved_email.html.haml"
+ - "app/views/notify/project_was_not_exported_email.html.haml"
+ - "app/views/notify/push_to_merge_request_email.html.haml"
+ - "app/views/notify/remote_mirror_update_failed_email.html.haml"
+ - "app/views/notify/removed_milestone_issue_email.html.haml"
+ - "app/views/notify/removed_milestone_merge_request_email.html.haml"
+ - "app/views/notify/repository_push_email.html.haml"
+ - "app/views/peek/views/_gc.html.haml"
+ - "app/views/peek/views/_redis.html.haml"
+ - "app/views/peek/views/_sidekiq.html.haml"
+ - "app/views/profiles/_event_table.html.haml"
+ - "app/views/profiles/active_sessions/_active_session.html.haml"
+ - "app/views/profiles/active_sessions/index.html.haml"
+ - "app/views/profiles/audit_log.html.haml"
+ - "app/views/profiles/chat_names/_chat_name.html.haml"
+ - "app/views/profiles/chat_names/index.html.haml"
+ - "app/views/profiles/chat_names/new.html.haml"
+ - "app/views/profiles/emails/index.html.haml"
+ - "app/views/profiles/gpg_keys/_key.html.haml"
+ - "app/views/profiles/gpg_keys/index.html.haml"
+ - "app/views/profiles/keys/_key.html.haml"
+ - "app/views/profiles/keys/_key_details.html.haml"
+ - "app/views/profiles/keys/index.html.haml"
+ - "app/views/profiles/notifications/show.html.haml"
+ - "app/views/profiles/passwords/edit.html.haml"
+ - "app/views/profiles/personal_access_tokens/index.html.haml"
+ - "app/views/profiles/preferences/show.html.haml"
+ - "app/views/profiles/show.html.haml"
+ - "app/views/profiles/two_factor_auths/_codes.html.haml"
+ - "app/views/profiles/two_factor_auths/codes.html.haml"
+ - "app/views/profiles/two_factor_auths/create.html.haml"
+ - "app/views/profiles/two_factor_auths/show.html.haml"
+ - "app/views/projects/_bitbucket_import_modal.html.haml"
+ - "app/views/projects/_customize_workflow.html.haml"
+ - "app/views/projects/_deletion_failed.html.haml"
+ - "app/views/projects/_fork_suggestion.html.haml"
+ - "app/views/projects/_gitlab_import_modal.html.haml"
+ - "app/views/projects/_home_panel.html.haml"
+ - "app/views/projects/_import_project_pane.html.haml"
+ - "app/views/projects/_issuable_by_email.html.haml"
+ - "app/views/projects/_md_preview.html.haml"
+ - "app/views/projects/_new_project_fields.html.haml"
+ - "app/views/projects/_readme.html.haml"
+ - "app/views/projects/artifacts/_tree_file.html.haml"
+ - "app/views/projects/artifacts/browse.html.haml"
+ - "app/views/projects/blame/_age_map_legend.html.haml"
+ - "app/views/projects/blame/show.html.haml"
+ - "app/views/projects/blob/_editor.html.haml"
+ - "app/views/projects/blob/_header_content.html.haml"
+ - "app/views/projects/blob/_new_dir.html.haml"
+ - "app/views/projects/blob/_remove.html.haml"
+ - "app/views/projects/blob/_render_error.html.haml"
+ - "app/views/projects/blob/_template_selectors.html.haml"
+ - "app/views/projects/blob/_upload.html.haml"
+ - "app/views/projects/blob/edit.html.haml"
+ - "app/views/projects/blob/new.html.haml"
+ - "app/views/projects/blob/preview.html.haml"
+ - "app/views/projects/blob/viewers/_empty.html.haml"
+ - "app/views/projects/blob/viewers/_stl.html.haml"
+ - "app/views/projects/branches/_branch.html.haml"
+ - "app/views/projects/branches/_commit.html.haml"
+ - "app/views/projects/branches/_delete_protected_modal.html.haml"
+ - "app/views/projects/branches/new.html.haml"
+ - "app/views/projects/ci/builds/_build.html.haml"
+ - "app/views/projects/ci/lints/_create.html.haml"
+ - "app/views/projects/commit/_change.html.haml"
+ - "app/views/projects/commits/_commit.html.haml"
+ - "app/views/projects/commits/_inline_commit.html.haml"
+ - "app/views/projects/compare/_form.html.haml"
+ - "app/views/projects/compare/index.html.haml"
+ - "app/views/projects/cycle_analytics/_empty_stage.html.haml"
+ - "app/views/projects/cycle_analytics/_no_access.html.haml"
+ - "app/views/projects/cycle_analytics/_overview.html.haml"
+ - "app/views/projects/cycle_analytics/show.html.haml"
+ - "app/views/projects/deploy_keys/_form.html.haml"
+ - "app/views/projects/deploy_keys/_index.html.haml"
+ - "app/views/projects/deploy_keys/edit.html.haml"
+ - "app/views/projects/deploy_tokens/_revoke_modal.html.haml"
+ - "app/views/projects/deploy_tokens/_table.html.haml"
+ - "app/views/projects/deployments/_deployment.html.haml"
+ - "app/views/projects/diffs/_file_header.html.haml"
+ - "app/views/projects/diffs/_replaced_image_diff.html.haml"
+ - "app/views/projects/diffs/_stats.html.haml"
+ - "app/views/projects/empty.html.haml"
+ - "app/views/projects/environments/show.html.haml"
+ - "app/views/projects/forks/error.html.haml"
+ - "app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml"
+ - "app/views/projects/graphs/charts.html.haml"
+ - "app/views/projects/hook_logs/_index.html.haml"
+ - "app/views/projects/hook_logs/show.html.haml"
+ - "app/views/projects/hooks/_index.html.haml"
+ - "app/views/projects/hooks/edit.html.haml"
+ - "app/views/projects/imports/new.html.haml"
+ - "app/views/projects/imports/show.html.haml"
+ - "app/views/projects/issues/_issue.html.haml"
+ - "app/views/projects/issues/_new_branch.html.haml"
+ - "app/views/projects/issues/import_csv/_modal.html.haml"
+ - "app/views/projects/issues/show.html.haml"
+ - "app/views/projects/jobs/_header.html.haml"
+ - "app/views/projects/jobs/_table.html.haml"
+ - "app/views/projects/jobs/index.html.haml"
+ - "app/views/projects/labels/edit.html.haml"
+ - "app/views/projects/labels/new.html.haml"
+ - "app/views/projects/mattermosts/_no_teams.html.haml"
+ - "app/views/projects/mattermosts/_team_selection.html.haml"
+ - "app/views/projects/mattermosts/new.html.haml"
+ - "app/views/projects/merge_requests/_commits.html.haml"
+ - "app/views/projects/merge_requests/_discussion.html.haml"
+ - "app/views/projects/merge_requests/_how_to_merge.html.haml"
+ - "app/views/projects/merge_requests/_merge_request.html.haml"
+ - "app/views/projects/merge_requests/_mr_title.html.haml"
+ - "app/views/projects/merge_requests/conflicts/_commit_stats.html.haml"
+ - "app/views/projects/merge_requests/conflicts/_file_actions.html.haml"
+ - "app/views/projects/merge_requests/conflicts/_submit_form.html.haml"
+ - "app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml"
+ - "app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml"
+ - "app/views/projects/merge_requests/conflicts/show.html.haml"
+ - "app/views/projects/merge_requests/creations/_diffs.html.haml"
+ - "app/views/projects/merge_requests/creations/_new_compare.html.haml"
+ - "app/views/projects/merge_requests/creations/_new_submit.html.haml"
+ - "app/views/projects/merge_requests/diffs/_different_base.html.haml"
+ - "app/views/projects/merge_requests/diffs/_diffs.html.haml"
+ - "app/views/projects/merge_requests/diffs/_version_controls.html.haml"
+ - "app/views/projects/merge_requests/invalid.html.haml"
+ - "app/views/projects/merge_requests/widget/open/_error.html.haml"
+ - "app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml"
+ - "app/views/projects/mirrors/_ssh_host_keys.html.haml"
+ - "app/views/projects/new.html.haml"
+ - "app/views/projects/no_repo.html.haml"
+ - "app/views/projects/pages/_access.html.haml"
+ - "app/views/projects/pages/_destroy.haml"
+ - "app/views/projects/pages/_https_only.html.haml"
+ - "app/views/projects/pages/_list.html.haml"
+ - "app/views/projects/pages/_no_domains.html.haml"
+ - "app/views/projects/pages/_use.html.haml"
+ - "app/views/projects/pages/show.html.haml"
+ - "app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml"
+ - "app/views/projects/pipelines/_info.html.haml"
+ - "app/views/projects/pipelines/charts/_pipelines.haml"
+ - "app/views/projects/protected_branches/shared/_branches_list.html.haml"
+ - "app/views/projects/protected_branches/shared/_create_protected_branch.html.haml"
+ - "app/views/projects/protected_branches/shared/_dropdown.html.haml"
+ - "app/views/projects/protected_branches/shared/_index.html.haml"
+ - "app/views/projects/protected_branches/shared/_matching_branch.html.haml"
+ - "app/views/projects/protected_branches/shared/_protected_branch.html.haml"
+ - "app/views/projects/protected_branches/show.html.haml"
+ - "app/views/projects/protected_tags/shared/_create_protected_tag.html.haml"
+ - "app/views/projects/protected_tags/shared/_dropdown.html.haml"
+ - "app/views/projects/protected_tags/shared/_index.html.haml"
+ - "app/views/projects/protected_tags/shared/_matching_tag.html.haml"
+ - "app/views/projects/protected_tags/shared/_protected_tag.html.haml"
+ - "app/views/projects/protected_tags/shared/_tags_list.html.haml"
+ - "app/views/projects/protected_tags/show.html.haml"
+ - "app/views/projects/registry/repositories/_tag.html.haml"
+ - "app/views/projects/repositories/_feed.html.haml"
+ - "app/views/projects/runners/_shared_runners.html.haml"
+ - "app/views/projects/runners/edit.html.haml"
+ - "app/views/projects/services/_form.html.haml"
+ - "app/views/projects/services/_index.html.haml"
+ - "app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml"
+ - "app/views/projects/services/mattermost_slash_commands/_help.html.haml"
+ - "app/views/projects/services/prometheus/_metrics.html.haml"
+ - "app/views/projects/services/slack_slash_commands/_help.html.haml"
+ - "app/views/projects/settings/ci_cd/_badge.html.haml"
+ - "app/views/projects/settings/ci_cd/_form.html.haml"
+ - "app/views/projects/stage/_stage.html.haml"
+ - "app/views/projects/tags/index.html.haml"
+ - "app/views/projects/tags/new.html.haml"
+ - "app/views/projects/tags/releases/edit.html.haml"
+ - "app/views/projects/tree/_tree_row.html.haml"
+ - "app/views/projects/tree/_truncated_notice_tree_row.html.haml"
+ - "app/views/projects/triggers/_content.html.haml"
+ - "app/views/projects/triggers/_form.html.haml"
+ - "app/views/projects/triggers/_index.html.haml"
+ - "app/views/projects/triggers/_trigger.html.haml"
+ - "app/views/projects/triggers/edit.html.haml"
+ - "app/views/projects/wikis/_new.html.haml"
+ - "app/views/projects/wikis/_pages_wiki_page.html.haml"
+ - "app/views/projects/wikis/edit.html.haml"
+ - "app/views/projects/wikis/history.html.haml"
+ - "app/views/repository_check_mailer/notify.html.haml"
+ - "app/views/search/_form.html.haml"
+ - "app/views/search/results/_issue.html.haml"
+ - "app/views/search/results/_note.html.haml"
+ - "app/views/search/results/_snippet_blob.html.haml"
+ - "app/views/search/results/_snippet_title.html.haml"
+ - "app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml"
+ - "app/views/shared/_commit_message_container.html.haml"
+ - "app/views/shared/_confirm_modal.html.haml"
+ - "app/views/shared/_delete_label_modal.html.haml"
+ - "app/views/shared/_group_form.html.haml"
+ - "app/views/shared/_group_tips.html.haml"
+ - "app/views/shared/_milestone_expired.html.haml"
+ - "app/views/shared/_no_password.html.haml"
+ - "app/views/shared/_no_ssh.html.haml"
+ - "app/views/shared/_outdated_browser.html.haml"
+ - "app/views/shared/_personal_access_tokens_created_container.html.haml"
+ - "app/views/shared/_personal_access_tokens_table.html.haml"
+ - "app/views/shared/_ping_consent.html.haml"
+ - "app/views/shared/_project_limit.html.haml"
+ - "app/views/shared/_service_settings.html.haml"
+ - "app/views/shared/boards/components/_board.html.haml"
+ - "app/views/shared/boards/components/_sidebar.html.haml"
+ - "app/views/shared/boards/components/sidebar/_due_date.html.haml"
+ - "app/views/shared/boards/components/sidebar/_labels.html.haml"
+ - "app/views/shared/boards/components/sidebar/_milestone.html.haml"
+ - "app/views/shared/empty_states/_priority_labels.html.haml"
+ - "app/views/shared/hook_logs/_content.html.haml"
+ - "app/views/shared/issuable/_assignees.html.haml"
+ - "app/views/shared/issuable/_board_create_list_dropdown.html.haml"
+ - "app/views/shared/issuable/_bulk_update_sidebar.html.haml"
+ - "app/views/shared/issuable/_close_reopen_report_toggle.html.haml"
+ - "app/views/shared/issuable/_form.html.haml"
+ - "app/views/shared/issuable/_search_bar.html.haml"
+ - "app/views/shared/issuable/_sidebar.html.haml"
+ - "app/views/shared/issuable/form/_default_templates.html.haml"
+ - "app/views/shared/issuable/form/_issue_assignee.html.haml"
+ - "app/views/shared/issuable/form/_template_selector.html.haml"
+ - "app/views/shared/issuable/form/_title.html.haml"
+ - "app/views/shared/labels/_form.html.haml"
+ - "app/views/shared/members/_member.html.haml"
+ - "app/views/shared/milestones/_form_dates.html.haml"
+ - "app/views/shared/milestones/_issuable.html.haml"
+ - "app/views/shared/milestones/_milestone.html.haml"
+ - "app/views/shared/milestones/_sidebar.html.haml"
+ - "app/views/shared/milestones/_top.html.haml"
+ - "app/views/shared/notes/_hints.html.haml"
+ - "app/views/shared/notes/_note.html.haml"
+ - "app/views/shared/notifications/_button.html.haml"
+ - "app/views/shared/notifications/_custom_notifications.html.haml"
+ - "app/views/shared/notifications/_new_button.html.haml"
+ - "app/views/shared/notifications/_notification_dropdown.html.haml"
+ - "app/views/shared/plugins/_index.html.haml"
+ - "app/views/shared/projects/_dropdown.html.haml"
+ - "app/views/shared/projects/_list.html.haml"
+ - "app/views/shared/projects/_project.html.haml"
+ - "app/views/shared/runners/_runner_description.html.haml"
+ - "app/views/shared/runners/show.html.haml"
+ - "app/views/shared/snippets/_embed.html.haml"
+ - "app/views/shared/snippets/_header.html.haml"
+ - "app/views/shared/snippets/_snippet.html.haml"
+ - "app/views/shared/tokens/_scopes_list.html.haml"
+ - "app/views/shared/web_hooks/_form.html.haml"
+ - "app/views/shared/web_hooks/_test_button.html.haml"
+ - "app/views/u2f/_authenticate.html.haml"
+ - "app/views/u2f/_register.html.haml"
+ - "app/views/users/_deletion_guidance.html.haml"
+ - "ee/app/views/admin/_namespace_plan_info.html.haml"
+ - "ee/app/views/admin/application_settings/_elasticsearch_form.html.haml"
+ - "ee/app/views/admin/application_settings/_slack.html.haml"
+ - "ee/app/views/admin/application_settings/_snowplow.html.haml"
+ - "ee/app/views/admin/application_settings/_templates.html.haml"
+ - "ee/app/views/admin/audit_logs/index.html.haml"
+ - "ee/app/views/admin/dashboard/stats.html.haml"
+ - "ee/app/views/admin/emails/show.html.haml"
+ - "ee/app/views/admin/geo/nodes/edit.html.haml"
+ - "ee/app/views/admin/geo/nodes/new.html.haml"
+ - "ee/app/views/admin/geo/projects/_registry_failed.html.haml"
+ - "ee/app/views/admin/geo/projects/_registry_never.html.haml"
+ - "ee/app/views/admin/licenses/_breakdown.html.haml"
+ - "ee/app/views/admin/licenses/_upload_trial_license.html.haml"
+ - "ee/app/views/admin/licenses/missing.html.haml"
+ - "ee/app/views/admin/licenses/new.html.haml"
+ - "ee/app/views/admin/licenses/show.html.haml"
+ - "ee/app/views/admin/monitoring/ee/_nav.html.haml"
+ - "ee/app/views/admin/projects/_shared_runner_status.html.haml"
+ - "ee/app/views/admin/push_rules/show.html.haml"
+ - "ee/app/views/admin/users/_limits.html.haml"
+ - "ee/app/views/admin/users/_user_detail_note.html.haml"
+ - "ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml"
+ - "ee/app/views/errors/kerberos_denied.html.haml"
+ - "ee/app/views/groups/analytics/show.html.haml"
+ - "ee/app/views/groups/audit_events/index.html.haml"
+ - "ee/app/views/groups/ee/_settings_nav.html.haml"
+ - "ee/app/views/groups/epics/_epic.html.haml"
+ - "ee/app/views/groups/group_members/_ldap_sync.html.haml"
+ - "ee/app/views/groups/group_members/_sync_button.html.haml"
+ - "ee/app/views/groups/hooks/_project_hook.html.haml"
+ - "ee/app/views/groups/hooks/index.html.haml"
+ - "ee/app/views/groups/ldap_group_links/index.html.haml"
+ - "ee/app/views/groups/pipeline_quota/index.html.haml"
+ - "ee/app/views/jira_connect/subscriptions/index.html.haml"
+ - "ee/app/views/layouts/jira_connect.html.haml"
+ - "ee/app/views/layouts/nav/ee/_epic_link.html.haml"
+ - "ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml"
+ - "ee/app/views/layouts/service_desk.html.haml"
+ - "ee/app/views/ldap_group_links/_form.html.haml"
+ - "ee/app/views/ldap_group_links/_ldap_group_link.html.haml"
+ - "ee/app/views/ldap_group_links/_ldap_group_links.html.haml"
+ - "ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml"
+ - "ee/app/views/ldap_group_links/_ldap_group_links_synchronizations.html.haml"
+ - "ee/app/views/namespaces/_shared_runner_status.html.haml"
+ - "ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml"
+ - "ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml"
+ - "ee/app/views/namespaces/pipelines_quota/_list.haml"
+ - "ee/app/views/notify/approved_merge_request_email.html.haml"
+ - "ee/app/views/notify/epic_status_changed_email.html.haml"
+ - "ee/app/views/notify/issues_csv_email.html.haml"
+ - "ee/app/views/notify/new_review_email.html.haml"
+ - "ee/app/views/notify/prometheus_alert_fired_email.html.haml"
+ - "ee/app/views/notify/send_admin_notification.html.haml"
+ - "ee/app/views/notify/send_unsubscribed_notification.html.haml"
+ - "ee/app/views/notify/unapproved_merge_request_email.html.haml"
+ - "ee/app/views/oauth/geo_auth/error.html.haml"
+ - "ee/app/views/profiles/pipeline_quota/index.haml"
+ - "ee/app/views/projects/audit_events/index.html.haml"
+ - "ee/app/views/projects/blob/_owners.html.haml"
+ - "ee/app/views/projects/commits/_mirror_status.html.haml"
+ - "ee/app/views/projects/feature_flags/_configure_feature_flags_modal.html.haml"
+ - "ee/app/views/projects/issues/_issue_weight.html.haml"
+ - "ee/app/views/projects/issues/_related_issues.html.haml"
+ - "ee/app/views/projects/issues/export_csv/_modal.html.haml"
+ - "ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml"
+ - "ee/app/views/projects/merge_requests/_approvals_count.html.haml"
+ - "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml"
+ - "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml"
+ - "ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml"
+ - "ee/app/views/projects/protected_branches/ee/_dropdown.html.haml"
+ - "ee/app/views/projects/protected_branches/ee/_fallback_update_protected_branch.html.haml"
+ - "ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml"
+ - "ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml"
+ - "ee/app/views/projects/push_rules/_index.html.haml"
+ - "ee/app/views/projects/services/gitlab_slack_application/_help.html.haml"
+ - "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml"
+ - "ee/app/views/projects/services/prometheus/_metrics.html.haml"
+ - "ee/app/views/projects/settings/slacks/edit.html.haml"
+ - "ee/app/views/shared/_additional_email_text.html.haml"
+ - "ee/app/views/shared/_geo_info_modal.html.haml"
+ - "ee/app/views/shared/_mirror_update_button.html.haml"
+ - "ee/app/views/shared/_shared_runners_minutes_limit.html.haml"
+ - "ee/app/views/shared/audit_events/_event_table.html.haml"
+ - "ee/app/views/shared/boards/components/_list_weight.html.haml"
+ - "ee/app/views/shared/boards/components/sidebar/_epic.html.haml"
+ - "ee/app/views/shared/ee/_import_form.html.haml"
+ - "ee/app/views/shared/epic/_search_bar.html.haml"
+ - "ee/app/views/shared/issuable/_approvals.html.haml"
+ - "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml"
+ - "ee/app/views/shared/issuable/_filter_weight.html.haml"
+ - "ee/app/views/shared/issuable/_sidebar_item_epic.haml"
+ - "ee/app/views/shared/members/ee/_ldap_tag.html.haml"
+ - "ee/app/views/shared/members/ee/_override_member_buttons.html.haml"
+ - "ee/app/views/shared/members/ee/_sso_badge.html.haml"
+ - "ee/app/views/shared/milestones/_burndown.html.haml"
+ - "ee/app/views/shared/milestones/_weight.html.haml"
+ - "ee/app/views/shared/promotions/_promote_audit_events.html.haml"
+ - "ee/app/views/shared/promotions/_promote_burndown_charts.html.haml"
+ - "ee/app/views/shared/promotions/_promote_csv_export.html.haml"
+ - "ee/app/views/shared/promotions/_promote_issue_weights.html.haml"
+ - "ee/app/views/shared/promotions/_promote_repository_features.html.haml"
+ - "ee/app/views/shared/promotions/_promote_servicedesk.html.haml"
+ - "ee/app/views/shared/push_rules/_form.html.haml"
+ - "ee/app/views/unsubscribes/show.html.haml"
+ - "ee/app/views/admin/users/_auditor_access_level_radio.html.haml"
+ - "ee/app/views/admin/users/_auditor_user_badge.html.haml"
+ - "ee/app/views/projects/protected_branches/_update_protected_branch.html.haml"
diff --git a/.pkgr.yml b/.pkgr.yml
index 10bcd7bd4bd..2e741f41a9e 100644
--- a/.pkgr.yml
+++ b/.pkgr.yml
@@ -3,8 +3,8 @@ group: git
services:
- postgres
before_precompile: ./bin/pkgr_before_precompile.sh
-env:
- - SKIP_STORAGE_VALIDATION=true
+env:
+ - SKIP_STORAGE_VALIDATION=true
targets:
debian-7: &wheezy
build_dependencies:
diff --git a/.prettierrc b/.prettierrc
index 3384551aea5..5e2863a11f6 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,13 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
- "trailingComma": "es5",
- "overrides": [
- {
- "files": ["**/app/**/*", "**/spec/**/*"],
- "options": {
- "trailingComma": "all"
- }
- }
- ]
+ "trailingComma": "all"
}
diff --git a/.rubocop.yml b/.rubocop.yml
index bcff67ded8c..e5fe527e611 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,6 +8,7 @@ require:
- rubocop-rspec
AllCops:
+ TargetRubyVersion: 2.5
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
@@ -74,6 +75,7 @@ Naming/FileName:
- 'qa/spec/**/*'
- 'qa/qa/specs/**/*'
- 'qa/bin/*'
+ - 'ee/bin/*'
- 'config/**/*'
- 'ee/config/**/*'
- 'lib/generators/**/*'
@@ -145,6 +147,20 @@ Naming/FileName:
- XSS
- GRPC
+Rails/ApplicationRecord:
+ Enabled: true
+ Exclude:
+ # Models in database migrations should not subclass from ApplicationRecord
+ # as they need to be as decoupled from application code as possible
+ - db/**/*.rb
+ - lib/gitlab/background_migration/**/*.rb
+ - lib/gitlab/database/**/*.rb
+ - spec/**/*.rb
+ - ee/db/**/*.rb
+ - ee/lib/gitlab/background_migration/**/*.rb
+ - ee/lib/ee/gitlab/background_migration/**/*.rb
+ - ee/spec/**/*.rb
+
# GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables:
@@ -181,3 +197,17 @@ Cop/InjectEnterpriseEditionModule:
Exclude:
- 'spec/**/*'
- 'ee/spec/**/*'
+
+Style/ReturnNil:
+ Enabled: true
+
+# It isn't always safe to replace `=~` with `.match?`, especially when there are
+# nil values on the left hand side
+Performance/RegexpMatch:
+ Enabled: false
+
+ActiveRecordAssociationReload:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 97e39ce99cb..63b1685feda 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,42 +1,154 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2018-01-18 18:23:26 +0100 using RuboCop version 0.52.1.
+# on 2019-05-04 16:01:00 +0000 using RuboCop version 0.68.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 181
+# Offense count: 264
Capybara/CurrentPathExpectation:
Enabled: false
-# Offense count: 167
+# Offense count: 1097
# Cop supports --auto-correct.
-Layout/EmptyLinesAroundArguments:
+# Configuration parameters: EnforcedStyle, IndentationWidth.
+# SupportedStyles: with_first_argument, with_fixed_indentation
+Layout/AlignArguments:
Enabled: false
-# Offense count: 83
+# Offense count: 824
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
+# SupportedHashRocketStyles: key, separator, table
+# SupportedColonStyles: key, separator, table
+# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
+Layout/AlignHash:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Layout/ClosingHeredocIndentation:
+ Exclude:
+ - 'app/graphql/mutations/merge_requests/set_wip.rb'
+ - 'ee/db/geo/migrate/20180322062741_migrate_ci_job_artifacts_to_separate_registry.rb'
+ - 'ee/db/migrate/20160204190809_update_jenkins_service_category.rb'
+ - 'ee/lib/gitlab/background_migration/prune_orphaned_geo_events.rb'
+ - 'ee/lib/gitlab/geo/health_check.rb'
+ - 'lib/gitlab/background_migration/populate_untracked_uploads.rb'
+ - 'qa/qa/service/kubernetes_cluster.rb'
+ - 'spec/features/merge_request/user_sees_diff_spec.rb'
+ - 'spec/lib/gitlab/asciidoc_spec.rb'
+ - 'spec/lib/gitlab/checks/project_moved_spec.rb'
+ - 'spec/rubocop/cop/active_record_association_reload_spec.rb'
+ - 'spec/services/task_list_toggle_service_spec.rb'
+
+# Offense count: 14
+# Cop supports --auto-correct.
+Layout/ClosingParenthesisIndentation:
+ Exclude:
+ - 'db/post_migrate/20180704145007_update_project_indexes.rb'
+ - 'ee/db/geo/migrate/20180405074130_add_partial_index_project_repository_verification.rb'
+ - 'ee/db/migrate/20180308234102_add_partial_index_to_project_repository_states_checksum_columns.rb'
+ - 'ee/db/post_migrate/20180605213516_fix_partial_index_to_project_repository_states_checksum_columns.rb'
+ - 'ee/lib/ee/gitlab/usage_data.rb'
+ - 'spec/services/issues/resolve_discussions_spec.rb'
+ - 'spec/services/projects/update_service_spec.rb'
+ - 'spec/support/helpers/stub_object_storage.rb'
+ - 'spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb'
+
+# Offense count: 2
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: leading, trailing
+Layout/DotPosition:
+ Exclude:
+ - 'app/models/concerns/relative_positioning.rb'
+ - 'app/models/group.rb'
+
+# Offense count: 69
+# Cop supports --auto-correct.
+Layout/EmptyLinesAroundArguments:
+ Exclude:
+ - 'app/models/concerns/discussion_on_diff.rb'
+ - 'app/models/concerns/resolvable_discussion.rb'
+ - 'app/models/diff_discussion.rb'
+ - 'app/models/discussion.rb'
+ - 'ee/app/helpers/license_helper.rb'
+ - 'ee/spec/models/geo/project_registry_spec.rb'
+ - 'lib/banzai/pipeline/broadcast_message_pipeline.rb'
+ - 'lib/banzai/pipeline/gfm_pipeline.rb'
+ - 'lib/banzai/pipeline/single_line_pipeline.rb'
+ - 'spec/features/markdown/copy_as_gfm_spec.rb'
+
+# Offense count: 160
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_brackets
-Layout/IndentArray:
+Layout/IndentFirstArrayElement:
Enabled: false
-# Offense count: 237
+# Offense count: 631
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: special_inside_parentheses, consistent, align_braces
-Layout/IndentHash:
+Layout/IndentFirstHashElement:
Enabled: false
-# Offense count: 93
+# Offense count: 5
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, IndentationWidth.
+# SupportedStyles: consistent, align_parentheses
+Layout/IndentFirstParameter:
+ Exclude:
+ - 'app/models/ci/pipeline_schedule.rb'
+ - 'lib/gitlab/cross_project_access.rb'
+ - 'lib/gitlab/data_builder/push.rb'
+ - 'spec/support/helpers/repo_helpers.rb'
+ - 'spec/support/helpers/stub_object_storage.rb'
+
+# Offense count: 5
+# Cop supports --auto-correct.
+Layout/LeadingBlankLines:
+ Exclude:
+ - 'app/workers/update_project_statistics_worker.rb'
+ - 'db/migrate/20161007073613_create_user_activities.rb'
+ - 'ee/spec/helpers/boards_helper_spec.rb'
+ - 'lib/tasks/yarn.rake'
+ - 'spec/javascripts/fixtures/merge_requests_diffs.rb'
+
+# Offense count: 30
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, IndentationWidth.
+# SupportedStyles: aligned, indented
+Layout/MultilineOperationIndentation:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Layout/RescueEnsureAlignment:
+ Exclude:
+ - 'app/models/blob_viewer/dependency_manager.rb'
+ - 'app/models/ci/pipeline.rb'
+ - 'app/models/project.rb'
+ - 'app/services/prometheus/proxy_service.rb'
+ - 'app/workers/delete_stored_files_worker.rb'
+ - 'app/workers/reactive_caching_worker.rb'
+ - 'config/initializers/1_settings.rb'
+ - 'config/initializers/trusted_proxies.rb'
+ - 'ee/db/migrate/20151113115819_canonicalize_kerberos_identities.rb'
+ - 'lib/gitlab/background_migration/archive_legacy_traces.rb'
+ - 'lib/gitlab/highlight.rb'
+ - 'lib/tasks/gitlab/lfs/migrate.rake'
+
+# Offense count: 344
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_no_space, require_space
Layout/SpaceInLambdaLiteral:
Enabled: false
-# Offense count: 327
+# Offense count: 583
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space
@@ -44,20 +156,27 @@ Layout/SpaceInLambdaLiteral:
Layout/SpaceInsideBlockBraces:
Enabled: false
-# Offense count: 156
+# Offense count: 255
# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: space, no_space
Layout/SpaceInsideParens:
Enabled: false
-# Offense count: 26
+# Offense count: 1
+Lint/DisjunctiveAssignmentInConstructor:
+ Exclude:
+ - 'app/models/uploads/base.rb'
+
+# Offense count: 19
Lint/DuplicateMethods:
Exclude:
- - 'app/models/application_setting.rb'
- 'app/models/commit.rb'
- 'app/models/note.rb'
- - 'app/services/merge_requests/merge_service.rb'
- 'lib/bitbucket/representation/repo.rb'
- 'lib/declarative_policy/base.rb'
+ - 'lib/gitlab/auth/ldap/person.rb'
+ - 'lib/gitlab/auth/o_auth/user.rb'
- 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
- 'lib/gitlab/cycle_analytics/base_event_fetcher.rb'
- 'lib/gitlab/diff/formatters/base_formatter.rb'
@@ -65,22 +184,34 @@ Lint/DuplicateMethods:
- 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/tree.rb'
- 'lib/gitlab/git/wiki_page.rb'
- - 'lib/gitlab/auth/ldap/person.rb'
- - 'lib/gitlab/auth/o_auth/user.rb'
-# Offense count: 4
+# Offense count: 2
Lint/InterpolationCheck:
Exclude:
- 'spec/features/issues/filtered_search/filter_issues_spec.rb'
- - 'spec/features/users_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 206
+# Offense count: 326
# Configuration parameters: MaximumRangeSize.
Lint/MissingCopEnableDirective:
Enabled: false
-# Offense count: 9
+# Offense count: 2
+# Cop supports --auto-correct.
+# Configuration parameters: Whitelist.
+# Whitelist: present?, blank?, presence, try, try!
+Lint/SafeNavigationConsistency:
+ Exclude:
+ - 'lib/gitlab/gpg/commit.rb'
+
+# Offense count: 2
+# Cop supports --auto-correct.
+Lint/ToJSON:
+ Exclude:
+ - 'lib/gitlab/cycle_analytics/usage_data.rb'
+ - 'lib/gitlab/template/base_template.rb'
+
+# Offense count: 7
Lint/UriEscapeUnescape:
Exclude:
- 'app/controllers/application_controller.rb'
@@ -88,13 +219,25 @@ Lint/UriEscapeUnescape:
- 'spec/lib/google_api/auth_spec.rb'
- 'spec/requests/api/files_spec.rb'
- 'spec/requests/api/internal_spec.rb'
- - 'spec/requests/api/issues_spec.rb'
# Offense count: 1
-# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
+# Configuration parameters: CheckForMethodsWithNoSideEffects.
+Lint/Void:
+ Exclude:
+ - 'lib/gitlab/git/diff_collection.rb'
+
+# Offense count: 158
+# Cop supports --auto-correct.
+# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
- Max: 1310
+ Max: 176
+
+# Offense count: 94
+# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
+# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
+Naming/FileName:
+ Enabled: false
# Offense count: 11
# Configuration parameters: EnforcedStyle.
@@ -107,61 +250,85 @@ Naming/HeredocDelimiterCase:
- 'spec/support/helpers/repo_helpers.rb'
- 'spec/support/helpers/seed_repo.rb'
-# Offense count: 112
+# Offense count: 197
# Configuration parameters: Blacklist.
-# Blacklist: END, (?-mix:EO[A-Z]{1})
+# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$))
Naming/HeredocDelimiterNaming:
Enabled: false
+# Offense count: 125
+# Cop supports --auto-correct.
+# Configuration parameters: PreferredName.
+Naming/RescuedExceptionsVariableName:
+ Enabled: false
+
+# Offense count: 6
+# Cop supports --auto-correct.
+Performance/InefficientHashSearch:
+ Exclude:
+ - 'app/controllers/concerns/sessionless_authentication.rb'
+ - 'app/models/note.rb'
+ - 'app/models/user_preference.rb'
+ - 'ee/app/models/ee/project.rb'
+ - 'lib/gitlab/import_export/members_mapper.rb'
+ - 'qa/spec/spec_helper.rb'
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Performance/ReverseEach:
+ Exclude:
+ - 'app/models/commit.rb'
+ - 'db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb'
+ - 'lib/gitlab/profiler.rb'
-# Offense count: 3821
+# Offense count: 7081
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
RSpec/ContextWording:
Enabled: false
-# Offense count: 293
+# Offense count: 407
+# Cop supports --auto-correct.
RSpec/EmptyLineAfterFinalLet:
Enabled: false
-# Offense count: 188
+# Offense count: 232
+# Cop supports --auto-correct.
RSpec/EmptyLineAfterSubject:
Enabled: false
-# Offense count: 258
+# Offense count: 719
+# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: method_call, block
RSpec/ExpectChange:
Enabled: false
-# Offense count: 221
+# Offense count: 512
RSpec/ExpectInHook:
Enabled: false
-# Offense count: 19
+# Offense count: 10
+# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: it_behaves_like, it_should_behave_like
RSpec/ItBehavesLike:
Exclude:
- 'spec/lib/gitlab/git/commit_spec.rb'
- 'spec/lib/gitlab/git/repository_spec.rb'
- - 'spec/lib/gitlab/shell_spec.rb'
- 'spec/services/notification_service_spec.rb'
- - 'spec/workers/git_garbage_collect_worker_spec.rb'
-# Offense count: 5
+# Offense count: 3
RSpec/IteratedExpectation:
Exclude:
- 'spec/features/admin/admin_settings_spec.rb'
- - 'spec/features/merge_requests/diff_notes_resolve_spec.rb'
- - 'spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb'
- 'spec/lib/gitlab/gitlab_import/client_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/client_spec.rb'
-# Offense count: 75
+# Offense count: 68
+# Cop supports --auto-correct.
RSpec/LetBeforeExamples:
Exclude:
- - 'spec/controllers/projects/commit_controller_spec.rb'
- 'spec/lib/banzai/filter/issue_reference_filter_spec.rb'
- 'spec/lib/banzai/filter/user_reference_filter_spec.rb'
- 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb'
@@ -170,12 +337,11 @@ RSpec/LetBeforeExamples:
- 'spec/models/commit_range_spec.rb'
- 'spec/models/milestone_spec.rb'
- 'spec/models/project_services/packagist_service_spec.rb'
- - 'spec/models/repository_spec.rb'
- 'spec/rubocop/cop/migration/update_column_in_batches_spec.rb'
- 'spec/serializers/pipeline_details_entity_spec.rb'
- - 'spec/views/ci/lints/show.html.haml_spec.rb'
# Offense count: 1
+# Cop supports --auto-correct.
RSpec/MultipleSubjects:
Exclude:
- 'spec/services/merge_requests/create_from_issue_service_spec.rb'
@@ -188,94 +354,136 @@ RSpec/OverwritingSetup:
- 'spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb'
- 'spec/services/notes/quick_actions_service_spec.rb'
-# Offense count: 965
+# Offense count: 1828
+# Cop supports --auto-correct.
# Configuration parameters: Strict, EnforcedStyle.
# SupportedStyles: inflected, explicit
RSpec/PredicateMatcher:
Enabled: false
-# Offense count: 35
+# Offense count: 57
RSpec/RepeatedExample:
Enabled: false
-# Offense count: 140
+# Offense count: 474
+# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: and_return, block
RSpec/ReturnFromStub:
Enabled: false
-# Offense count: 112
+# Offense count: 188
RSpec/ScatteredLet:
Enabled: false
-# Offense count: 22
+# Offense count: 10
RSpec/ScatteredSetup:
Exclude:
- 'spec/controllers/projects/templates_controller_spec.rb'
- 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
- - 'spec/lib/gitlab/git/env_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
- 'spec/services/projects/create_service_spec.rb'
# Offense count: 1
+# Cop supports --auto-correct.
RSpec/SharedContext:
Exclude:
- 'spec/features/admin/admin_groups_spec.rb'
-# Offense count: 5
+# Offense count: 4
RSpec/VoidExpect:
Exclude:
- - 'spec/features/projects/artifacts/download_spec.rb'
- 'spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb'
- 'spec/models/ci/group_spec.rb'
- 'spec/models/ci/runner_spec.rb'
- 'spec/services/users/destroy_service_spec.rb'
-# Offense count: 41
+# Offense count: 8
+# Cop supports --auto-correct.
+Rails/BelongsTo:
+ Exclude:
+ - 'app/models/deployment.rb'
+ - 'app/models/environment.rb'
+ - 'ee/app/models/prometheus_alert.rb'
+ - 'ee/app/models/prometheus_alert_event.rb'
+
+# Offense count: 80
# Configuration parameters: Include.
# Include: db/migrate/*.rb
Rails/CreateTableWithTimestamps:
Enabled: false
-# Offense count: 155
+# Offense count: 222
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: slashes, arguments
Rails/FilePath:
Enabled: false
-# Offense count: 121
+# Offense count: 167
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasManyOrHasOneDependent:
Enabled: false
-# Offense count: 157
+# Offense count: 40
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: numeric, symbolic
+Rails/HttpStatus:
+ Enabled: false
+
+# Offense count: 2
+# Configuration parameters: Include.
+# Include: app/controllers/**/*.rb
+Rails/IgnoredSkipActionFilterOption:
+ Exclude:
+ - 'app/controllers/projects/snippets_controller.rb'
+ - 'app/controllers/snippets_controller.rb'
+
+# Offense count: 87
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/InverseOf:
Enabled: false
-# Offense count: 48
+# Offense count: 46
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb
Rails/LexicallyScopedActionFilter:
Enabled: false
-# Offense count: 14
+# Offense count: 4
+# Cop supports --auto-correct.
+Rails/LinkToBlank:
+ Exclude:
+ - 'app/helpers/projects_helper.rb'
+ - 'app/helpers/wiki_helper.rb'
+ - 'ee/app/helpers/ee/user_callouts_helper.rb'
+ - 'ee/app/helpers/license_helper.rb'
+
+# Offense count: 11
# Cop supports --auto-correct.
Rails/Presence:
Exclude:
- - 'app/controllers/projects/blob_controller.rb'
- 'app/models/ci/pipeline.rb'
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/concerns/mentionable.rb'
- - 'app/models/concerns/token_authenticatable.rb'
+ - 'app/models/project_services/hipchat_service.rb'
- 'app/models/project_services/irker_service.rb'
- 'app/models/project_services/jira_service.rb'
- 'app/models/project_services/kubernetes_service.rb'
- 'app/models/project_services/packagist_service.rb'
- 'app/models/wiki_page.rb'
- - 'lib/gitlab/git/hook.rb'
- 'lib/gitlab/github_import/importer/releases_importer.rb'
+# Offense count: 1
+# Cop supports --auto-correct.
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/RedundantAllowNil:
+ Exclude:
+ - 'app/models/application_setting.rb'
+
# Offense count: 2
# Configuration parameters: Include.
# Include: db/migrate/*.rb
@@ -283,8 +491,8 @@ Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20160824103857_drop_unused_ci_tables.rb'
-# Offense count: 446
-# Configuration parameters: Blacklist.
+# Offense count: 860
+# Configuration parameters: Blacklist, Whitelist.
# Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
Rails/SkipsModelValidations:
Enabled: false
@@ -296,21 +504,25 @@ Rails/UnknownEnv:
Exclude:
- 'db/migrate/20171124125748_populate_missing_merge_request_statuses.rb'
-# Offense count: 13
+# Offense count: 11
# Cop supports --auto-correct.
Security/YAMLLoad:
Exclude:
- - 'config/initializers/carrierwave.rb'
- 'lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb'
- 'lib/gitlab/redis/wrapper.rb'
- 'lib/system_check/incoming_email/imap_authentication_check.rb'
- 'spec/config/mail_room_spec.rb'
- 'spec/initializers/secret_token_spec.rb'
- 'spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb'
- - 'spec/models/clusters/platforms/kubernetes_spec.rb'
- 'spec/models/project_services/kubernetes_service_spec.rb'
-# Offense count: 64
+# Offense count: 34
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: inline, group
+Style/AccessModifierDeclarations:
+ Enabled: false
+
+# Offense count: 121
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: percent_q, bare_percent
@@ -323,17 +535,13 @@ Style/CommentedKeyword:
- 'lib/tasks/gitlab/backup.rake'
- 'spec/tasks/gitlab/backup_rake_spec.rb'
-# Offense count: 30
-Style/DateTime:
- Enabled: false
-
# Offense count: 1
# Cop supports --auto-correct.
Style/Dir:
Exclude:
- 'qa/qa.rb'
-# Offense count: 9
+# Offense count: 7
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
@@ -344,46 +552,42 @@ Style/EachWithObject:
- 'lib/gitlab/i18n/po_linter.rb'
- 'lib/gitlab/import_export/members_mapper.rb'
- 'lib/gitlab/import_export/relation_factory.rb'
- - 'scripts/static-analysis'
-# Offense count: 24
+# Offense count: 34
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: empty, nil, both
Style/EmptyElse:
Enabled: false
-# Offense count: 14
+# Offense count: 11
# Cop supports --auto-correct.
Style/EmptyLambdaParameter:
Exclude:
- 'app/models/ci/build.rb'
- 'app/models/ci/runner.rb'
-# Offense count: 12
+# Offense count: 9
# Cop supports --auto-correct.
Style/EmptyLiteral:
Exclude:
- - 'features/steps/project/commits/commits.rb'
- 'lib/gitlab/fogbugz_import/importer.rb'
- 'lib/gitlab/git/diff_collection.rb'
- 'lib/gitlab/gitaly_client.rb'
- - 'scripts/trigger-build'
- - 'spec/features/merge_requests/versions_spec.rb'
- 'spec/helpers/merge_requests_helper_spec.rb'
- 'spec/lib/gitlab/request_context_spec.rb'
- 'spec/lib/gitlab/workhorse_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
- 'spec/support/shared_examples/chat_slash_commands_shared_examples.rb'
-# Offense count: 102
+# Offense count: 180
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Enabled: false
-# Offense count: 23
+# Offense count: 40
# Cop supports --auto-correct.
Style/Encoding:
Enabled: false
@@ -393,52 +597,52 @@ Style/EvalWithLocation:
Exclude:
- 'app/models/service.rb'
-# Offense count: 35
+# Offense count: 203
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Enabled: false
-# Offense count: 384
+# Offense count: 669
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
-# Offense count: 22
+# Offense count: 27
Style/IfInsideElse:
Enabled: false
-# Offense count: 809
+# Offense count: 1346
# Cop supports --auto-correct.
Style/IfUnlessModifier:
Enabled: false
-# Offense count: 75
+# Offense count: 186
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: line_count_dependent, lambda, literal
Style/Lambda:
Enabled: false
-# Offense count: 11
+# Offense count: 3
# Cop supports --auto-correct.
Style/LineEndConcatenation:
Exclude:
- - 'app/helpers/tree_helper.rb'
- - 'spec/features/issuables/markdown_references_spec.rb'
- - 'spec/lib/gitlab/checks/project_moved_spec.rb'
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
- 'spec/lib/gitlab/incoming_email_spec.rb'
# Offense count: 18
-Style/MethodMissing:
+Style/MethodMissingSuper:
Enabled: false
-# Offense count: 7
+# Offense count: 18
+Style/MissingRespondToMissing:
+ Enabled: false
+
+# Offense count: 6
Style/MixinUsage:
Exclude:
- - 'features/support/env.rb'
- 'spec/factories/ci/builds.rb'
- 'spec/factories/ci/job_artifacts.rb'
- 'spec/factories/lfs_objects.rb'
@@ -446,59 +650,63 @@ Style/MixinUsage:
- 'spec/lib/gitlab/import_export/project_tree_restorer_spec.rb'
- 'spec/lib/gitlab/import_export/version_checker_spec.rb'
-# Offense count: 6
+# Offense count: 4
# Cop supports --auto-correct.
Style/MultilineIfModifier:
Exclude:
- 'app/helpers/snippets_helper.rb'
- 'app/models/project_wiki.rb'
- 'app/services/ci/process_pipeline_service.rb'
- - 'app/services/create_deployment_service.rb'
- 'lib/api/commit_statuses.rb'
- - 'lib/gitlab/ci/trace.rb'
-# Offense count: 25
+# Offense count: 72
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: literals, strict
+Style/MutableConstant:
+ Enabled: false
+
+# Offense count: 28
# Cop supports --auto-correct.
# Configuration parameters: Whitelist.
# Whitelist: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
Style/NestedParenthesizedCalls:
Enabled: false
-# Offense count: 19
+# Offense count: 31
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Enabled: false
-# Offense count: 61
+# Offense count: 67
# Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle.
# SupportedOctalStyles: zero_with_o, zero_only
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 114
+# Offense count: 186
# Cop supports --auto-correct.
-# Configuration parameters: AutoCorrect, EnforcedStyle.
+# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Enabled: false
-# Offense count: 4
+# Offense count: 2
# Cop supports --auto-correct.
Style/OrAssignment:
Exclude:
- - 'app/models/concerns/token_authenticatable.rb'
- 'lib/api/commit_statuses.rb'
- 'lib/gitlab/project_transfer.rb'
-# Offense count: 50
+# Offense count: 79
# Cop supports --auto-correct.
Style/ParallelAssignment:
Enabled: false
-# Offense count: 917
+# Offense count: 1390
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
@@ -520,25 +728,20 @@ Style/PerlBackrefs:
- 'lib/gitlab/search_results.rb'
- 'lib/gitlab/sherlock/query.rb'
-# Offense count: 87
+# Offense count: 129
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
-# Offense count: 9
+# Offense count: 3
# Cop supports --auto-correct.
Style/RedundantBegin:
Exclude:
- - 'app/controllers/projects/clusters/gcp_controller.rb'
- 'app/models/merge_request.rb'
- 'app/services/projects/import_service.rb'
- - 'lib/api/branches.rb'
- - 'lib/gitlab/current_settings.rb'
- - 'lib/gitlab/git/commit.rb'
- 'lib/gitlab/health_checks/base_abstract_check.rb'
- - 'lib/tasks/gitlab/task_helpers.rb'
# Offense count: 1
# Cop supports --auto-correct.
@@ -546,7 +749,7 @@ Style/RedundantConditional:
Exclude:
- 'lib/system_check/helpers.rb'
-# Offense count: 57
+# Offense count: 360
# Cop supports --auto-correct.
Style/RedundantFreeze:
Enabled: false
@@ -566,41 +769,36 @@ Style/RedundantReturn:
- 'lib/gitlab/utils.rb'
- 'lib/google_api/auth.rb'
-# Offense count: 460
+# Offense count: 700
# Cop supports --auto-correct.
Style/RedundantSelf:
Enabled: false
-# Offense count: 142
+# Offense count: 28
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
- Enabled: true
- EnforcedStyle: mixed
- AllowInnerSlashes: false
+ Enabled: false
-# Offense count: 36
+# Offense count: 41
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
-# Offense count: 107
+# Offense count: 197
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, explicit
Style/RescueStandardError:
Enabled: false
-# Offense count: 8
+# Offense count: 5
# Cop supports --auto-correct.
Style/SelfAssignment:
Exclude:
- 'app/models/concerns/bulk_member_access_load.rb'
- 'app/serializers/base_serializer.rb'
- - 'app/services/notification_service.rb'
- - 'lib/api/runners.rb'
- - 'spec/features/merge_requests/diff_notes_resolve_spec.rb'
- 'spec/features/projects/clusters/interchangeability_spec.rb'
- 'spec/support/import_export/configuration_helper.rb'
@@ -611,7 +809,7 @@ Style/SingleLineMethods:
Exclude:
- 'lib/gitlab/ci/ansi2html.rb'
-# Offense count: 66
+# Offense count: 91
# Cop supports --auto-correct.
# Configuration parameters: .
# SupportedStyles: use_perl_names, use_english_names
@@ -624,21 +822,21 @@ Style/StderrPuts:
Exclude:
- 'config/initializers/rspec_profiling.rb'
-# Offense count: 45
+# Offense count: 65
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Enabled: false
-# Offense count: 106
+# Offense count: 187
# Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method
Style/SymbolProc:
Enabled: false
-# Offense count: 9
+# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
@@ -646,45 +844,65 @@ Style/TernaryParentheses:
Exclude:
- 'app/finders/projects_finder.rb'
- 'app/helpers/namespaces_helper.rb'
- - 'features/support/capybara.rb'
- 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
- 'spec/requests/api/pipeline_schedules_spec.rb'
- 'spec/support/capybara.rb'
-# Offense count: 17
+# Offense count: 3
# Cop supports --auto-correct.
-# Configuration parameters: AllowNamedUnderscoreVariables.
-Style/TrailingUnderscoreVariable:
+# Configuration parameters: EnforcedStyleForMultiline.
+# SupportedStylesForMultiline: comma, consistent_comma, no_comma
+Style/TrailingCommaInArguments:
Exclude:
- - 'app/controllers/admin/background_jobs_controller.rb'
- - 'app/controllers/invites_controller.rb'
- - 'app/helpers/tab_helper.rb'
- - 'lib/backup/manager.rb'
- - 'lib/gitlab/logger.rb'
- - 'lib/gitlab/upgrader.rb'
- - 'lib/system_check/app/migrations_are_up_check.rb'
- - 'lib/system_check/incoming_email/mail_room_running_check.rb'
- - 'lib/tasks/gitlab/check.rake'
- - 'lib/tasks/gitlab/task_helpers.rb'
- - 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
- - 'spec/services/quick_actions/interpret_service_spec.rb'
+ - 'spec/features/markdown/copy_as_gfm_spec.rb'
-# Offense count: 4
+# Offense count: 10
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyleForMultiline.
+# SupportedStylesForMultiline: comma, consistent_comma, no_comma
+Style/TrailingCommaInArrayLiteral:
+ Exclude:
+ - 'ee/spec/models/project_spec.rb'
+ - 'spec/lib/gitlab/diff/position_tracer_spec.rb'
+ - 'spec/lib/gitlab/metrics/dashboard/processor_spec.rb'
+
+# Offense count: 2
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyleForMultiline.
+# SupportedStylesForMultiline: comma, consistent_comma, no_comma
+Style/TrailingCommaInHashLiteral:
+ Exclude:
+ - 'lib/gitlab/ci/ansi2html.rb'
+ - 'lib/gitlab/kubernetes.rb'
+
+# Offense count: 2
# Cop supports --auto-correct.
Style/UnlessElse:
Exclude:
- 'lib/backup/manager.rb'
- 'lib/gitlab/project_search_results.rb'
- - 'lib/tasks/gitlab/check.rake'
- - 'spec/features/issues/award_emoji_spec.rb'
-# Offense count: 31
+# Offense count: 10
+# Cop supports --auto-correct.
+Style/UnneededCondition:
+ Exclude:
+ - 'app/helpers/button_helper.rb'
+ - 'app/helpers/environment_helper.rb'
+ - 'app/models/project.rb'
+ - 'app/services/issuable/clone/base_service.rb'
+ - 'app/services/prometheus/adapter_service.rb'
+ - 'lib/gitlab/email/message/repository_push.rb'
+ - 'lib/gitlab/prometheus_client.rb'
+ - 'spec/lib/rspec_flaky/flaky_example_spec.rb'
+
+# Offense count: 73
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Enabled: false
-# Offense count: 22840
-# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
-# URISchemes: http, https
-Metrics/LineLength:
- Max: 1310
+# Offense count: 2
+# Cop supports --auto-correct.
+Style/UnneededSort:
+ Exclude:
+ - 'app/models/concerns/resolvable_discussion.rb'
+ - 'lib/gitlab/highlight.rb'
diff --git a/.ruby-version b/.ruby-version
index aedc15bb0c6..ec1cf33c3f6 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.5.3
+2.6.3
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 3df66033fa8..2a1fa27fdb5 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -1,7 +1,9 @@
# Linter Documentation:
# https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md
-scss_files: 'app/assets/stylesheets/**/*.scss'
+scss_files:
+ - 'app/assets/stylesheets/**/*.scss'
+ - 'ee/app/assets/stylesheets/**/*.scss'
exclude:
- 'app/assets/stylesheets/pages/emojis.scss'
diff --git a/.stylelintrc b/.stylelintrc
index 69de9a5dd13..b0ace93e04f 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -1,30 +1,111 @@
{
- "extends": "stylelint-config-recommended",
- "plugins": [
- "stylelint-scss"
- ],
- "rules": {
- "no-descending-specificity": null,
- "font-family-no-missing-generic-family-keyword": null,
- "at-rule-no-unknown": [ true, {
- ignoreAtRules: ["include", "each", "mixin", "extend", "if", "function", "for", "else", "return"]
- }],
- "selector-type-no-unknown": [true, {
- "ignoreTypes": ["gl-emoji"]
- }],
- "unit-no-unknown" : [true, {
- "ignoreFunctions": ["-webkit-image-set"]
- }],
- "scss/at-extend-no-missing-placeholder": null,
- "scss/at-function-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
- "scss/at-import-no-partial-leading-underscore": true,
- "scss/at-import-partial-extension-blacklist": ["scss"],
- "scss/at-mixin-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
- "scss/at-rule-no-unknown": true,
- "scss/dollar-variable-colon-space-after": "always",
- "scss/dollar-variable-colon-space-before": "never",
- "scss/dollar-variable-pattern": "^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
- "scss/percent-placeholder-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
- "scss/selector-no-redundant-nesting-selector": true,
- }
+ "plugins":[
+ "./scripts/frontend/stylelint/stylelint-duplicate-selectors.js",
+ "./scripts/frontend/stylelint/stylelint-utility-classes.js",
+ "stylelint-scss"
+ ],
+ "rules":{
+ "at-rule-blacklist":[
+ "debug"
+ ],
+ "at-rule-no-unknown":null,
+ "at-rule-no-vendor-prefix":true,
+ "block-no-empty":true,
+ "block-opening-brace-space-before":"always",
+ "color-hex-case":"lower",
+ "color-hex-length":"short",
+ "color-named":"never",
+ "color-no-invalid-hex":true,
+ "declaration-bang-space-after":"never",
+ "declaration-bang-space-before":"always",
+ "declaration-block-semicolon-newline-after":"always",
+ "declaration-block-semicolon-space-before":"never",
+ "declaration-block-single-line-max-declarations":1,
+ "declaration-block-trailing-semicolon":"always",
+ "declaration-colon-space-after":"always-single-line",
+ "declaration-colon-space-before":"never",
+ "declaration-property-value-blacklist":{
+ "border":[
+ "none"
+ ],
+ "border-top":[
+ "none"
+ ],
+ "border-right":[
+ "none"
+ ],
+ "border-bottom":[
+ "none"
+ ],
+ "border-left":[
+ "none"
+ ]
+ },
+ "function-comma-space-after":"always-single-line",
+ "function-parentheses-space-inside":"never",
+ "function-url-quotes":"always",
+ "indentation":2,
+ "length-zero-no-unit":true,
+ "max-nesting-depth":[
+ 3,
+ {
+ "ignoreAtRules":[
+ "each",
+ "media",
+ "supports",
+ "include"
+ ],
+ "severity":"warning"
+ }
+ ],
+ "media-feature-name-no-vendor-prefix":true,
+ "media-feature-parentheses-space-inside":"never",
+ "no-missing-end-of-source-newline":true,
+ "number-leading-zero":"always",
+ "number-no-trailing-zeros":true,
+ "property-no-unknown":true,
+ "property-no-vendor-prefix": [true, { "ignoreProperties": ["user-select"] }],
+ "rule-empty-line-before":[
+ "always-multi-line",
+ {
+ "except":[
+ "first-nested"
+ ],
+ "ignore":[
+ "after-comment"
+ ]
+ }
+ ],
+ "scss/at-extend-no-missing-placeholder":[true,{ "severity": "warning" }],
+ "scss/at-function-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/at-import-no-partial-leading-underscore":true,
+ "scss/at-import-partial-extension-blacklist":[
+ "scss"
+ ],
+ "scss/at-mixin-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/at-rule-no-unknown":true,
+ "scss/dollar-variable-colon-space-after":"always",
+ "scss/dollar-variable-colon-space-before":"never",
+ "scss/dollar-variable-pattern":"^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/percent-placeholder-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/selector-no-redundant-nesting-selector":true,
+ "selector-class-pattern":[
+ "^[a-z0-9\\-]+$",
+ {
+ "message":"Selector should be written in lowercase with hyphens (selector-class-pattern)",
+ "severity": "warning"
+ }
+ ],
+ "selector-list-comma-newline-after":"always",
+ "selector-max-compound-selectors":[3, { "severity": "warning" }],
+ "selector-max-id":1,
+ "selector-no-vendor-prefix":true,
+ "selector-pseudo-element-colon-notation":"double",
+ "selector-pseudo-element-no-unknown":true,
+ "shorthand-property-no-redundant-values":true,
+ "string-quotes":"single",
+ "value-no-vendor-prefix":[true, { "ignoreValues": ["sticky"] }],
+ "stylelint-gitlab/duplicate-selectors":[true,{ "severity": "warning" }],
+ "stylelint-gitlab/utility-classes":[true,{ "severity": "warning" }]
+ }
}
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 00000000000..df7cdf404bc
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,7 @@
+---
+
+extends: default
+
+rules:
+ line-length: disable
+ document-start: disable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index feda5e0835b..fd9c4df2f3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,954 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.11.2 (2019-06-04)
+
+### Fixed (7 changes)
+
+- Update SAST.gitlab-ci.yml - Add SAST_GITLEAKS_ENTROPY_LEVEL. !28607
+- Fix OmniAuth OAuth2Generic strategy not loading. !28680
+- Use source ref in pipeline webhook. !28772
+- Fix migration failure when groups are missing route. !29022
+- Stop two-step rebase from hanging when errors occur. !29068
+- Fix project settings not being able to update. !29097
+- Fix display of 'Promote to group label' button.
+
+### Other (1 change)
+
+- Fix input group height.
+
+
+## 11.11.0 (2019-05-22)
+
+### Security (1 change)
+
+- Destroy project remote mirrors instead of disabling. !27087
+
+### Fixed (74 changes, 19 of them are from the community)
+
+- Don't create a temp reference for branch comparisons within project. !24038
+- Fix some label links not appearing on group labels page and label title being a link on project labels page. !24060 (Tanya Pazitny)
+- Fix extra emails for custom notifications. !25607
+- Rewind IID on Ci::Pipelines. !26490
+- Fix duplicate merge request pipelines created by Sidekiq worker retry. !26643
+- Catch and report OpenSSL exceptions while fetching external configuration files in CI::Config. !26750 (Drew Cimino)
+- stop rendering download links for expired artifacts on the project tags page. !26753 (Drew Cimino)
+- Format extra help page text like wiki. !26782 (Bastian Blank)
+- Always show instance configuration link. !26783 (Bastian Blank)
+- Display maximum artifact size from runtime config. !26784 (Bastian Blank)
+- Resolve issue where list labels did not have the correct text color on creation. !26794 (Tucker Chapman)
+- Set release name when adding release notes to an existing tag. !26807
+- Fix the bug that the project statistics is not updated. !26854 (Hiroyuki Sato)
+- Client side changes for ListLastCommitsForTree response update. !26880
+- Fix api group visibility. !26896
+- Require all templates to use default stages. !26954
+- Remove a "reopen merge request button" on a "merged" merge request. !26965 (Hiroyuki Sato)
+- Fix misaligned image diff swipe view. !26969 (ftab)
+- Add badge-pill class on group member count. !27019
+- Remove leading / trailing spaces from heading when generating header ids. !27025 (Willian Balmant)
+- Respect updated_at attribute in notes produced by API calls. !27124 (Ben Gamari)
+- Fix GitHub project import visibility. !27133 (Daniel Wyatt)
+- Fixes actions dropdowns in environments page. !27160
+- Fixes create button background for Environments form. !27161
+- Display scoped labels in Issue Boards. !27164
+- Align UrlValidator to validate_url gem implementation. !27194 (Horatiu Eugen Vlad)
+- Resolve Web IDE template dropdown showing duplicates. !27237
+- Update GitLab Workhorse to v8.6.0. !27260
+- Only show in autocomplete when author active. !27292
+- Remove deadline for Git fsck. !27299
+- Show prioritized labels to guests. !27307
+- Properly expire all pipeline caches when pipeline is deleted. !27334
+- Replaced icon for external URL with doc-text icon. !27365
+- Add auto direction for issue title. !27378 (Ahmad Haghighi)
+- fix wiki search result links in titles. !27400 (khm)
+- Fix system notes timestamp when creating issue in the past. !27406
+- Fix approvals sometimes being reset after a merge request is rebased. !27446
+- Fix empty block in MR widget when user doesn't have permission. !27462
+- Fix wrong use of ActiveRecord in PoolRepository. !27464
+- Show proper preview for uploaded images in Web IDE. !27471
+- Resolve Renaming an image via Web IDE corrupts it. !27486
+- Clean up CarrierWave's import/export files. !27487
+- Fix autocomplete dropdown for usernames starting with period. !27533 (Jan Beckmann)
+- Disable password autocomplete in mirror repository form. !27542
+- Always use internal ID tables in development and production. !27544
+- Only show the "target branch has advanced" message when the merge request is open. !27588
+- Resolve Misalignment on suggested changes diff table. !27612
+- Update Workhorse to v8.7.0. !27630
+- Fix FE API and IDE handling of '/' relative_url_root. !27635
+- Hide ScopedBadge overflow notes. !27651
+- Fix base domain help text update. !27746
+- Upgrade letter_opener_web to support Rails 5.1. !27829
+- Fix webpack assets handling when relative url root is '/'. !27909
+- Fix IDE get file data with '/' as relative root. !27911
+- Allow a member to have an access level equal to parent group. !27913
+- Fix issuables state_id nil when importing projects from GitHub. !28027
+- Fix uploading of LFS tracked file through UI. !28052
+- Render Next badge only for gitlab.com. !28056
+- Fix update head pipeline process of Pipelines for merge requests. !28057
+- Fix visual issues in set status modal. !28147
+- Use a path for the related merge requests endpoint. !28171
+- disable SSH key validation in key details view. !28180 (Roger Meier)
+- Fix MR discussion border missing in chrome sometimes. !28185
+- Fix Error 500 when inviting user already present. !28198
+- Remove non-semantic use of `.row` in member listing controls. !28204
+- Properly handle LFS Batch API response in project import. !28223
+- Fix project visibility level validation. !28305 (Peter Marko)
+- Fix incorrect prefix used in new uploads for personal snippets. !28337
+- Fix Rugged get_tree_entries recursive flag not working. !28494
+- Fixes next badge being always visible.
+- Next badge must visible when canary flag is true.
+- Adds arrow icons to select option in CI/CD settings.
+- Vertically aligns the play button for stages.
+- Allow replying to individual notes from API.
+
+### Changed (19 changes, 3 of them are from the community)
+
+- Sort by due date and popularity in both directions for Issues and Merge requests. !25502 (Nermin Vehabovic)
+- Improve pipelines table spacing, add triggerer column. !26136
+- Allow extra arguments in helm commands when deploying the application in Auto-DevOps.gitlab-ci.yml. !26171 (tortuetorche)
+- Switch to sassc-rails for faster stylesheet compilation. !26224
+- Reorganize project merge request settings. !26834
+- Display a toast message when the Kubernetes runner has successfully upgraded. !27206
+- Allow guests users to access project releases. !27247
+- Add help texts to K8 form fields. !27274
+- Support prometheus for group level clusters. !27280
+- Include link to raw job log in plain-text emails. !27409
+- Only escape Markdown emphasis characters in autocomplete when necessary. !27457
+- Move location of charts/auto-deploy-app -> gitlab-org/charts/auto-deploy-app. !27477
+- Make canceled jobs not retryable. !27503
+- Upgrade to Gitaly v1.36.0. !27831
+- Update deployment event chat notification message. !27972
+- Upgrade to Gitaly v1.42.0. !28135
+- Resolve discussion when apply suggestion. !28160
+- Improve expanding diff to full file performance.
+- Knative version bump 0.3 -> 0.5. (Chris Baumbauer <cab@cabnetworks.net>)
+
+### Performance (5 changes)
+
+- Added list_pages method to avoid loading all wiki pages content. !22801
+- Add gitaly session id & catfile-cache feature flag. !27472
+- Add improvements to global search of issues and merge requests. !27817
+- Disable method replacement in avatar loading. !27866
+- Fix Blob.lazy always loading all previously-requested blobs when a new request is made.
+
+### Added (36 changes, 10 of them are from the community)
+
+- Add time preferences for user. !25381
+- Added write_repository scope for personal access token. !26021 (Horatiu Eugen Vlad)
+- Mark disabled pages domains for removal, but don't remove them yet. !26212
+- Remove pages domains if they weren't verified for 1 week. !26227
+- Expose pipeline variables via API. !26501 (Agustin Henze <tin@redhat.com>)
+- Download a folder from repository. !26532 (kiameisomabes)
+- Remove cleaned up OIDs from database and cache. !26555
+- Disables kubernetes resources creation if a cluster is not managed. !26565
+- Add CI_COMMIT_REF_PROTECTED CI variable. !26716 (Jason van den Hurk)
+- Add new API endpoint to expose a single environment. !26887
+- Allow Sentry configuration to be passed on gitlab.yml. !27091 (Roger Meier)
+- CI variables of type file. !27112
+- Allow linking to a private helm repository by providing credentials, and customisation of repository name. !27123 (Stuart Moore @stjm-cc)
+- Add time tracking information to Issue Boards sidebar. !27166
+- Play all manual jobs in a stage. !27188
+- Instance level kubernetes clusters. !27196
+- Adds if InfluxDB and Prometheus metrics are enabled to usage ping data. !27238
+- Autosave description in epics. !27296
+- Add deployment events to chat notification services. !27338
+- Add packages_size to ProjectStatistics. !27373
+- Added OmniAuth OpenID Connect strategy. !27383 (Horatiu Eugen Vlad)
+- Test using Git 2.21. !27418
+- Use official Gitea logo in importer. !27424 (Matti Ranta (@techknowlogick))
+- Add option to set access_level of runners upon registration. !27490 (Zelin L)
+- Add initial GraphQL query for Groups. !27492
+- Enable Sidekiq Reliable Fetcher for background jobs by default. !27530
+- Add backend support for a External Dashboard URL setting. !27550
+- Implement UI for uninstalling Cluster’s managed apps. !27559
+- Resolve Salesforce.com omniauth support. !27834
+- Leave project/group from access granted email. !27892
+- Allow Sentry client-side DSN to be passed on gitlab.yml. !27967
+- GraphQL: improve evaluation of query complexity based on arguments and query limits. !28017
+- Adds badge for Canary environment and help link.
+- Support negative matches.
+- Show category icons in user popover.
+- Added Omniauth UltraAuth strategy to GitLab. (Kartikey Tanna)
+
+### Other (29 changes, 8 of them are from the community)
+
+- Validate refs used in controllers don't have spaces. !24037
+- Migrate correlation and tracing code to LabKit. !25379
+- Update node.js to 10.15.3 in CI template for Hexo. !25943 (Takuya Noguchi)
+- Improve icons and button order in project overview. !26796
+- Add instructions on how to contribute a Built-In template for project. !26976
+- Extract DiscussionNotes component from NoteableDiscussion. !27066
+- Bump gRPC to 1.19.0 and protobuf to 3.7.1. !27086
+- Extract DiscussionActions component from NoteableDiscussion. !27227
+- Show disabled project repo mirrors in settings. !27326
+- Add backtrace to Gitaly performance bar. !27345
+- Moved EE/CE differences for dropdown_value_collapsed into CE. !27367
+- Remove "You are already signed in" banner. !27377
+- Move ee-specific code from boards/components/issue_card_inner.vue. !27394 (Roman Rodionov)
+- Upgrade to Rails 5.1. !27480 (Jasper Maes)
+- Update GitLab Runner Helm Chart to 0.4.0. !27508
+- Update GitLab Runner Helm Chart to 0.4.1. !27627
+- Refactored notes tests from Karma to Jest. !27648 (Martin Hobert)
+- refactor(issue): Refactored issue tests from Karma to Jest. !27673 (Martin Hobert)
+- Refactored Karma spec files to Jest. !27688 (Martin Hobert)
+- Add CSS fix for <wbr> elements on IE11. !27846
+- Update clair-local-scan to v2.0.8 for container scanning. !27977
+- Use PostgreSQL 10.7 in tests. !28020
+- Document EE License Auto Import During Install. !28106
+- Remove the note in the docs that multi-line suggestions are not yet available. !28119 (hardysim)
+- Update gitlab-shell to v9.1.0. !28184
+- Add EE fixtures to SeedFu list. !28241
+- Replaces CSS with BS4 utility class for pipeline schedules.
+- Creates a vendors folder for external CSS.
+- Add some frozen string to spec/**/*.rb. (gfyoung)
+
+
+## 11.10.6 (2019-06-04)
+
+### Fixed (7 changes, 1 of them is from the community)
+
+- Allow a member to have an access level equal to parent group. !27913
+- Fix uploading of LFS tracked file through UI. !28052
+- Use 3-way merge for squashing commits. !28078
+- Use a path for the related merge requests endpoint. !28171
+- Fix project visibility level validation. !28305 (Peter Marko)
+- Fix Rugged get_tree_entries recursive flag not working. !28494
+- Use source ref in pipeline webhook. !28772
+
+### Other (1 change)
+
+- Fix input group height.
+
+
+## 11.10.4 (2019-05-01)
+
+### Fixed (12 changes)
+
+- Fix MR popover on ToDos page. !27382
+- Fix 500 in general pipeline settings when passing an invalid build timeout. !27416
+- Fix bug where system note MR has no popover. !27589
+- Fix bug when project export to remote url fails. !27614
+- `on_stop` is not automatically triggered with pipelines for merge requests. !27618
+- Update Workhorse to v8.5.2. !27631
+- Show proper wiki links in search results. !27634
+- Make `CI_COMMIT_REF_NAME` and `SLUG` variable idempotent. !27663
+- Fix Kubernetes service template deployment jobs broken as of 11.10.0. !27687
+- Prevent text selection when dragging in issue boards. !27724
+- Fix pipelines for merge requests does not show pipeline page when source branch is removed. !27803
+- Fix Metrics Environments dropdown.
+
+### Performance (2 changes)
+
+- Prevent concurrent execution of PipelineScheduleWorker. !27781
+- Fix slow performance with compiling HAML templates. !27782
+
+
+## 11.10.3 (2019-04-30)
+
+### Security (1 change)
+
+- Allow to see project events only with api scope token.
+
+
+## 11.10.2 (2019-04-25)
+
+### Security (4 changes)
+
+- Loosen regex for exception sanitization. !3076
+- Resolve: moving an issue to private repo leaks namespace and project name.
+- Escape path in new merge request mail.
+- Stop sending emails to users who can't read commit.
+
+
+## 11.10.1 (2019-04-23)
+
+### Fixed (2 changes)
+
+- Upgrade Gitaly to 1.34.0. !27494
+- Fix filtering of labels from system note link. !27507
+
+### Changed (1 change)
+
+- Disable just-in-time Kubernetes resource creation for project level clusters. !27352
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+### Other (1 change)
+
+- Bump required Ruby version check to 2.5.3. !27495
+
+
+## 11.10.0 (2019-04-22)
+
+### Security (9 changes)
+
+- Update Rails to 5.0.7.2. !27022
+- Disallow guest users from accessing Releases.
+- Return cached languages if they've been detected before.
+- Added rake task for removing EXIF data from existing uploads.
+- Disallow updating namespace when updating a project.
+- Fix XSS in resolve conflicts form.
+- Hide "related branches" when user does not have permission.
+- Fix PDF.js vulnerability.
+- Use UntrustedRegexp for matching refs policy.
+
+### Fixed (81 changes, 21 of them are from the community)
+
+- Update `border-radius` of form controls and remove extra space above page titles. !24497
+- Disallow reopening of a locked merge request. !24882 (Jan Beckmann)
+- Align EmailValidator to validate_email gem implementation. !24971 (Horatiu Eugen Vlad)
+- add a uniq constraints on issues and mrs labels. !25435 (Antoine Huret)
+- Display draft when toggling replies. !25563
+- Fix markdown table header and table content borders. !25666
+- Fix authorized application count. !25715 (moyuru)
+- Added "Add List" checkbox to create label dropdown to make creation of list optional. !25716 (Tucker Chapman)
+- Makes emoji picker full width on mobile. !25883 (Jacopo Beschi @jacopo-beschi)
+- Don't cutoff letters in MR and Issue links. !25910 (gfyoung)
+- Fix unwanted character replacement on project members page caused by usage of sanitize function. !25946 (Elias Werberich)
+- Fix UI for closed MR when source project is removed. !25967 (Takuya Noguchi)
+- Keep inline as much as possible in system notes on issuable. !25968 (Takuya Noguchi)
+- Fixes long review app subdomains. !25990 (walkafwalka)
+- Fix counting of groups in admin dashboard. !26009
+- Disable inaccessible navigation links upon archiving a project. !26020 (Elias Werberich)
+- Fixed - Create project label window is cut off at the bottom. !26049
+- Fix error shown when loading links to specific comments. !26092
+- Fix group transfer selection possibilities. !26123 (Peter Marko)
+- Fix UI layout on Commits on mobile. !26133 (Takuya Noguchi)
+- Fix continuous bitbucket import loading spinner. !26175
+- Resolves Branch name is lost if I change commit mode in Web IDE. !26180
+- Fix removing remote mirror failure which leaves unnecessary refs behind. !26213
+- Fix Error 500 when user commits Wiki page with no commit message. !26247
+- Handle missing keys in sentry api response. !26264
+- Implemented whitespace-trimming for file names in Web IDE. !26270
+- Fix misalignment of group overview page buttons. !26292
+- Reject HEAD requests to info/refs endpoint. !26334
+- Prevent namespace dropdown in new project form from exceeding container. !26343
+- Fix hover animation consistency in top navbar items. !26345
+- Exclude system notes from commits in merge request discussions. !26396
+- Resolve Code in other column of side-by-side diff is highlighted when selecting code on one side. !26423
+- Prevent fade out transition on loading-button component. !26428
+- Fix merge commits being used as default squash commit messages. !26445
+- Expand resolved discussion when linking to a comment in the discussion. !26483
+- Show statistics also when repository is disabled. !26509 (Peter Marko)
+- Fix multiple series queries on metrics dashboard. !26514
+- Releases will now be automatically deleted when deleting corresponding tag. !26530
+- Make stylistic improvements to diff nav header. !26557
+- Clear pipeline status cache after destruction of pipeline. !26575
+- Update fugit which fixes a potential infinite loop. !26579
+- Fixes job link in artifacts page breadcrumb. !26592
+- Fix quick actions add label name middle word overlaps. !26602 (Jacopo Beschi @jacopo-beschi)
+- Fix Auto DevOps missing domain error handling. !26627
+- Fix jupyter rendering bug that ended in an infinite loop. !26656 (ROSPARS Benoit)
+- Use a fixed git abbrev parameter when we fetch a git revision. !26707
+- Enabled text selection highlighting in diffs in Web IDE. !26721 (Isaac Smith)
+- Remove `path` and `branch` labels from metrics. !26744
+- Resolve "Hide Kubernetes cluster warning if project has cluster related". !26749
+- Fix long label overflow on metrics dashboard. !26775
+- Group transfer now properly redirects to edit on failure. !26837
+- Only execute system hooks once when pushing tags. !26888
+- Fix UI anchor links after docs refactor. !26890
+- Fix MWPS does not work for merge request pipelines. !26906
+- Create pipelines for merge requests only when source branch is updated. !26921
+- Fix notfication emails having wrong encoding. !26931
+- Allow task lists that follow a blockquote to work correctly. !26937
+- Fix image diff swipe view on commit and compare pages. !26968 (ftab)
+- Fix IDE detection of MR from fork with same branch name. !26986
+- Fix single string values for the 'include' keyword validation of gitlab-ci.yml. !26998 (Paul Bonaud (@paulrbr))
+- Do not display Ingress IP help text when there isn’t an Ingress IP assigned. !27057
+- Fix real-time updates for projects that contain a reserved word. !27060
+- Remove duplicates from issue related merge requests. !27067
+- Add to white-space nowrap to all buttons. !27069
+- Handle possible HTTP exception for Sentry client. !27080
+- Guard against nil dereferenced_target. !27192
+- Update GitLab Workhorse to v8.5.1. !27217
+- Fix long file header names bug in diffs. !27233
+- Always return the deployment in the UpdateDeploymentService#execute method. !27322
+- Fix remove_source_branch merge request API handling. !27392
+- Fixed bug with hashes in urls in WebIDE. !54376 (Kieran Andrews)
+- Fix bug where MR popover doesn't go away on mouse leave.
+- Only consider active milestones when using the special Started milestone filter.
+- Scroll to diff file content when clicking on file header name and it is not a link to other page.
+- Remove non-functional add issue button on welcome list.
+- Fixed expand full file button showing on images.
+- Fixed Web IDE web workers not working with relative URLs.
+- Fixed Web IDE not loading merge request files.
+- Fixed duplicated diff too large error message.
+- Fixed sticky headers in merge request creation diffs.
+- Fix bug when reopening milestone from index page.
+
+### Deprecated (1 change)
+
+- Allow to use untrusted Regexp via feature flag. !26905
+
+### Changed (35 changes, 4 of them are from the community)
+
+- Create MR pipelines with `refs/merge-requests/:iid/head`. !25504
+- Create Kubernetes resources for projects when their deployment jobs run. !25586
+- Remove unnecessary folder prefix from environment name. !25600
+- Update deploy boards to additionally select on "app.gitlab.com" annotations. !25623
+- Allow failed custom hook script errors to safely appear in GitLab UI by filtering error messages by the prefix GL-HOOK-ERR:. !25625
+- Add link on two-factor authorization settings page to leave group that enforces two-factor authorization. !25731
+- Reduce height of instance system header and footer. !25752
+- Unify behaviour of 'Copy commit SHA to clipboard' to use full commit SHA. !25829 (Max Winterstein)
+- Show loading spinner while Ingress/Knative IP is being assigned. !25912
+- Hashed Storage: Prevent a migration and rollback running at the same time. !25976
+- Make time counters show 'just now' for everything under one minute. !25992 (Sergiu Marton)
+- Allow filtering labels list by one or two characters. !26012
+- Implements the creation strategy for multi-line suggestions. !26057
+- Automate base domain help text on Clusters page. !26124
+- Set user.name limit to 128 characters. !26146
+- Update gitlab-markup to 1.7.0 which requies python3. !26246
+- Update system message banner font size to 12px. !26293
+- Extend timezone dropdown. !26311
+- Upgrade to Gitaly v1.29.0. !26406
+- Automatically set Prometheus step interval. !26441
+- Knative version bump 0.2.2 -> 0.3.0. !26459 (Chris Baumbauer)
+- Display cluster form validation error messages inline. !26502
+- Split Auto-DevOps.gitlab-ci.yml into reusable templates. !26520
+- Update spinners in group list component. !26572
+- Allow removing last owner from subgroup if parent group has owners. !26718
+- Check mergeability in MergeToRefService. !26757
+- Show download diff links for closed MRs. !26772
+- Fix Container Scanning in Kubernetes Runners. !26793
+- Move "Authorize project access with external service" to Core. !26823
+- Localize notifications dropdown. !26844
+- Order labels alphabetically in issue boards. !26927
+- Upgrade to Gitaly v1.32.0. !26989
+- Upgrade to Gitaly v1.33.0. !27065
+- collapse file tree by default if the merge request changes only one file. (Riccardo Padovani <riccardo@rpadovani.com>)
+- Removes the undescriptive CI Charts header.
+
+### Performance (17 changes)
+
+- Drop legacy artifacts usage as there are no leftovers. !24294
+- Cache Repository#root_ref within a request. !25903
+- Allow ref name caching CommitService#find_commit. !26248
+- Avoid loading pipeline status in project search. !26342
+- Fix some N+1s in loading routes and counting members for groups in @-autocomplete. !26491
+- GitHub import: Run housekeeping after initial import. !26600
+- Add initial complexity limits to GraphQL queries. !26629
+- Cache FindCommit results in pipelines view. !26776
+- Fix and expand Gitaly FindCommit caching. !27018
+- Enable FindCommit caching for project and commits pages. !27048
+- Expand FindCommit caching to blob and refs. !27084
+- Enable Gitaly FindCommit caching for TreeController. !27100
+- Improve performance of PR import. !27121
+- Process at most 4 pipelines during push. !27205
+- Disable method instrumentation for diffs. !27235
+- Speed up filtering issues in a project when searching.
+- Speed up generation of avatar URLs when using object storage.
+
+### Added (35 changes, 6 of them are from the community)
+
+- Add users search results to global search. !21197 (Alexis Reigel)
+- Add target branch filter to merge requests search bar. !24380 (Hiroyuki Sato)
+- Add Knative metrics to Prometheus. !24663 (Chris Baumbauer <cab@cabnetworks.net>)
+- Support multi-line suggestions. !25211
+- Allow to sort wiki pages by date and title. !25365
+- Allow external diffs to be used conditionally. !25432
+- Add usage counts for error tracking feature. !25472
+- Enable/disable Auto DevOps at the Group level. !25533
+- Update pipeline list view to accommodate post-merge pipeline information. !25690
+- GraphQL Types can be made to always authorize access to resources of that Type. !25724
+- Update clair-local-scan to 2.0.6. !25743 (Takuya Noguchi)
+- Update pipeline block on merge request page to accommodate post-merge pipeline information. !25745
+- Support multiple queries per chart on metrics dash. !25758
+- Update pipeline detail view to accommodate post-merge pipelines. !25775
+- Update job detail sidebar to accommodate post-merge pipeline information. !25777
+- Add merge request pipeline flag to pipeline entity. !25846
+- Expose group id on home panel. !25897 (Peter Marko)
+- Move allow developers to create projects in groups to Core. !25975
+- Add two new warning messages to the MR widget about merge request pipelines. !25983
+- Support installing Group runner on group-level cluster. !26260
+- Improve the Knative installation on Clusters. !26339
+- Show error when namespace/svc account missing. !26362
+- Add select by title to milestones API. !26573
+- Implemented support for creation of new files from URL in Web IDE. !26622
+- Add control for masking variable values in runner logs. !26751
+- Allow merge requests to be created via git push options. !26752
+- Create a shortcut for a new MR in the Web IDE. !26792
+- Allow reactive caching to be used in services. !26839
+- Add a Prometheus API per environment. !26841
+- Allow merge requests to be set to merge when pipeline succeeds via git push options. !26842
+- Use gitlabktl to build and deploy GitLab Serverless Functions. !26926
+- Make touch events work on image diff swipe view and onion skin. !26971 (ftab)
+- Add extended merge request tooltip.
+- Added prometheus monitoring to GraphQL.
+- Adding highest role property to admin's user details page.
+
+### Other (29 changes, 6 of them are from the community)
+
+- Update rack-oauth2 1.2.1 -> 1.9.3. !17868
+- Merge the gitlab-shell "gitlab-keys" functionality into GitLab CE. !25598
+- Refactor all_pipelines in Merge request. !25676
+- Show error backtrace when logging errors to kubernetes.log. !25726
+- Apply recaptcha API change in 4.0. !25921 (Praveen Arimbrathodiyil)
+- Remove fake repository_path response. !25942 (Fabio Papa)
+- Use curl silent/show-error options on Auto DevOps. !25954 (Takuya Noguchi)
+- Explicitly set master_auth setting to enable basic auth and client certificate for new GKE clusters. !26018
+- Project: Improve empty repository state UI. !26024
+- Externalize strings from `/app/views/projects/pipelines`. !26035 (George Tsiolis)
+- Prepare multi-line suggestions for rendering in Markdown. !26107
+- Improve mobile UI on User Profile page. !26240 (Takuya Noguchi)
+- Update GitLab Runner Helm Chart to 0.3.0/11.9.0. !26467
+- Improve project merge request settings. !26495
+- Bump kubectl to 1.11.9 and Helm to 2.13.1 in Auto-DevOps.gitlab-ci.yml. !26534
+- Upgrade bootstrap_form Gem. !26568
+- Add API access check to Graphql. !26570
+- Change project avatar remove button to a link. !26589
+- Log Gitaly RPC duration to api_json.log and production_json.log. !26652
+- Add cluster domain to Project Cluster API. !26735
+- Move project tags to separate line. !26797
+- Changed button label at /pipelines/new. !26893 (antfobe,leonardofl)
+- Update GitLab Shell to v9.0.0. !27002
+- Migrate clusters tests to jest. !27013
+- Rewrite related MRs widget with Vue. !27027
+- Restore HipChat project service. !27172
+- Externalize admin deploy keys strings.
+- Removes EE differences for environments_table.vue.
+- Removes EE differences for environment_item.vue.
+
+
+## 11.9.12 (2019-05-30)
+
+### Security (12 changes, 1 of them is from the community)
+
+- Protect Gitlab::HTTP against DNS rebinding attack.
+- Fix project visibility level validation. (Peter Marko)
+- Update Knative version.
+- Add DNS rebinding protection settings.
+- Prevent XSS injection in note imports.
+- Prevent invalid branch for merge request.
+- Filter relative links in wiki for XSS.
+- Fix confidential issue label disclosure on milestone view.
+- Fix url redaction for issue links.
+- Resolve: Milestones leaked via search API.
+- Prevent bypass of restriction disabling web password sign in.
+- Hide confidential issue title on unsubscribe for anonymous users.
+
+
+## 11.9.10 (2019-04-26)
+
+### Security (5 changes)
+
+- Loosen regex for exception sanitization. !3077
+- Resolve: moving an issue to private repo leaks namespace and project name.
+- Escape path in new merge request mail.
+- Stop sending emails to users who can't read commit.
+- Upgrade Rails to 5.0.7.2.
+
+
+## 11.9.9 (2019-04-23)
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+
+## 11.9.8 (2019-04-11)
+
+### Deprecated (1 change)
+
+- Allow to use untrusted Regexp via feature flag. !26905
+
+### Performance (2 changes)
+
+- Improve performance of PR import. !27121
+- Disable method instrumentation for diffs. !27235
+
+### Other (1 change)
+
+- Restore HipChat project service. !27172
+
+
+## 11.9.7 (2019-04-09)
+
+- No changes.
+
+## 11.9.6 (2019-04-04)
+
+### Fixed (3 changes)
+
+- Force to recreate all MR diffs on import. !26480
+- Fix API /project/:id/branches not returning correct merge status. !26785
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Force a full GC after importing a project. !26803
+
+
+## 11.9.5 (2019-04-03)
+
+### Fixed (3 changes)
+
+- Force to recreate all MR diffs on import. !26480
+- Fix API /project/:id/branches not returning correct merge status. !26785
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Force a full GC after importing a project. !26803
+
+
+## 11.9.3 (2019-03-27)
+
+### Security (8 changes)
+
+- Disallow guest users from accessing Releases.
+- Fix PDF.js vulnerability.
+- Hide "related branches" when user does not have permission.
+- Fix XSS in resolve conflicts form.
+- Added rake task for removing EXIF data from existing uploads.
+- Return cached languages if they've been detected before.
+- Disallow updating namespace when updating a project.
+- Use UntrustedRegexp for matching refs policy.
+
+
+## 11.9.2 (2019-03-26)
+
+### Security (8 changes)
+
+- Disallow guest users from accessing Releases.
+- Fix PDF.js vulnerability.
+- Hide "related branches" when user does not have permission.
+- Fix XSS in resolve conflicts form.
+- Added rake task for removing EXIF data from existing uploads.
+- Return cached languages if they've been detected before.
+- Disallow updating namespace when updating a project.
+- Use UntrustedRegexp for matching refs policy.
+
+
+## 11.9.1 (2019-03-25)
+
+### Fixed (7 changes)
+
+- Fix issue that caused the "Show all activity" button to appear on top of the mini pipeline status dropdown on the merge request page. !26274
+- Fix duplicated bottom match line on merge request parallel diff view. !26402
+- Allow users who can push to protected branches to create protected branches via CLI. !26413
+- Add missing .gitlab-ci.yml to Android template. !26415
+- Refresh commit count after repository head changes. !26473
+- Set proper default-branch for repository on GitHub Import. !26476
+- GitHub importer: Use the project creator to create branches from forks. !26510
+
+### Changed (1 change)
+
+- Upgrade to Gitaly v1.27.1. !26533
+
+
+## 11.9.0 (2019-03-22)
+
+### Security (24 changes)
+
+- Use encrypted runner tokens. !25532
+- Stop linking to unrecognized package sources. !55518
+- Disable issue boards API when issues are disabled.
+- Forbid creating discussions for users with restricted access.
+- Fix leaking private repository information in API.
+- Fixed ability to see private groups by users not belonging to given group.
+- Prevent releases links API to leak tag existance.
+- Display the correct number of MRs a user has access to.
+- Block local URLs for Kubernetes integration.
+- Fix arbitrary file read via diffs during import.
+- Check if desired milestone for an issue is available.
+- Don't allow non-members to see private related MRs.
+- Check snippet attached file to be moved is within designated directory.
+- Fix blind SSRF in Prometheus integration by checking URL before querying.
+- Fix git clone revealing private repo's presence.
+- Remove project serialization in quick actions response.
+- Don't show new issue link after move when a user does not have permissions.
+- Limit mermaid rendering to 5K characters.
+- Show only merge requests visible to user on milestone detail page.
+- Display only information visible to current user on the Milestone page.
+- Do not display impersonated sessions under active sessions and remove ability to revoke session.
+- Validate session key when authorizing with GCP to create a cluster.
+- Do not disclose milestone titles for unauthorized users.
+- Remove the possibility to share a project with a group that a user is not a member of.
+
+### Removed (1 change)
+
+- Remove HipChat integration from GitLab. !22223
+
+### Fixed (86 changes, 21 of them are from the community)
+
+- Fixes issue with AWS V4 signatures not working with some S3 providers. !21788
+- Validate 'include' keywords in gitlab-ci.yml configuration files. !24098 (Paul Bonaud)
+- Close More Actions tooltip when menu opens. !24285
+- API: Support Jira transition ID as string. !24400 (Robert Schilling)
+- Fixed navigation sidebar flashing open on page load. !24555
+- Fix username escaping when using assign to me for issues. !24673
+- commit page info-well overflow fix #56436. !24799 (Gokhan Apaydin)
+- Fix error tracking list page. !24806
+- Fix overlapping empty-header logo. !24868 (Jonas L.)
+- Resolve Jobs tab border top in pipeline's page is 1px off. !24878
+- Require maintainer access to show pages domain settings. !24926
+- Display error message when API call to list Sentry issues fails. !24936
+- Fix rollout status for statefulsets and daemonsets. !24972 (Sergej Nikolaev <kinolaev@gmail.com>)
+- Display job names consistently on pipelines and environments list. !24984
+- Update new password breadcrumb. !25037 (George Tsiolis)
+- Fixes functions finder for upgraded Knative app. !25067
+- Provide expires_in in LFS authentication payload. !25082
+- Fix validation of certain ed25519 keys. !25115 (Merlijn B. W. Wajer)
+- Timer and action name aligned vertically for delayed jobs in pipeline actions. !25117 (Gokhan Apaydin)
+- Fix the border style of CONTRIBUTING button when it exists. !25124 (Takuya Noguchi)
+- Change badges.svg example to pipeline.svg. !25157 (Aviad Levy)
+- API: Fix docs and parameters for hangouts-chat service. !25180 (Robert Schilling)
+- API: Expose full commit title. !25189 (Robert Schilling)
+- API: Require only one parameter when updating a wiki. !25191 (Robert Schilling)
+- Hide pipeline status when pipelines are disabled on project. !25204
+- Fix alignment of dropdown icon on issuable on mobile. !25205 (Takuya Noguchi)
+- Add left margin to 1st time contributor badge. !25216 (Gokhan Apaydin)
+- Use limited counter for runner build count in admin page. !25220
+- API: Ensure that related merge requests are referenced cross-project. !25222 (Robert Schilling)
+- Ensure the base pipeline of a Merge Request belongs to its target branch. !25226
+- Fix import_jid error on project import. !25239
+- Fix commenting on commits having SHA1 starting with a large number. !25278
+- Allow empty values such as [] to be stored in reactive cache. !25283
+- Remove vertical connecting line placeholder from diff discussion notes. !25292
+- Fix hover and active state colors of award emoji button. !25295
+- Fix author layouts in issuable meta line UIs on mobile. !25332 (Takuya Noguchi)
+- Fix bug where project topics truncate. !25398
+- Fix ETag caching not being used for AJAX requests. !25400
+- Doc - fix the url of pipeline status badge. !25404 (Aviad Levy)
+- Fix pipeline status icon mismatch. !25407
+- Allow users to compare branches on a read-only instance. !25414
+- Fix 404s when C++ .gitignore template selected. !25416
+- Always fetch MR latest version when creating suggestions. !25441
+- Only show borders for markdown images in notes. !25448
+- Bring back Rugged implementation of find_commit. !25477
+- Remove duplicate units from metrics graph. !25485
+- Fix project import error importing releases. !25495
+- Remove duplicate XHR request when requesting new pipeline page. !25506
+- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
+- Fix weekday shift in issue board cards for UTC+X timezones by removing local timezone to UTC conversion. !25512 (Elias Werberich)
+- Fix large table horizontal scroll and prevent side-by-side tables. !25520 (Dany Jupille)
+- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
+- Respect the should_remove_source_branch parameter to the merge API. !25525
+- Externalize markdown toolbar buttons tooltips. !25529
+- Fix method to mark a project repository as writable. !25546
+- fix group without owner after transfer. !25573 (Peter Marko)
+- Fix pagination and duplicate requests in environments page. !25582
+- Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present. !25601
+- Add right padding to the repository mirror action buttons. !25606
+- Use 'folder-open' from sprite icons for Browse Files button in Tag page. !25635
+- Make merge to refs/merge-requests/:iid/merge not raise when FF-only enabled. !25653
+- Fixed "Copying comment with ordered list includes extraneous newlines". !25695
+- Fix bridge jobs only/except variables policy. !25710
+- Allow GraphQL requests without CSRF token. !25719
+- Skip Project validation during Hashed Storage migration or rollback. !25753
+- Resolve showing squash commit edit issue when only single commit is present. !25807
+- Fix the last-ditch memory killer pgroup SIGKILL. !25940
+- Disable timeout on merge request merging poll. !25988
+- Allow modifying squash commit message for fast-forward only merge method. !26017
+- Fix bug in BitBucket imports with SHA shorter than 40 chars. !26050
+- Fix health checks not working behind load balancers. !26055
+- Fix 500 error caused by CODEOWNERS with no matches. !26072
+- Fix notes being marked as edited after resolving. !26143
+- Fix error creating a merge request when diff includes a null byte. !26190
+- Fix undefined variable error on json project views. !26297
+- GitHub import: Create new branches as project owner. !26335
+- Gracefully handles excluded fields from attributes during serialization on JsonCache. !26368
+- Admin section finds users case-insensitively.
+- Fixes not working dropdowns in pipelines page.
+- Do not show file templates when creating a new directory in WebIDE.
+- Allow project members to see private group if the project is in the group namespace.
+- Allow maintainers to remove pages.
+- Fix inconsistent pagination styles.
+- Fixed blob editor deleting file content for certain file paths.
+- Fix upcoming milestone when there are milestones with far-future due dates.
+- Fixed alignment of changed icon in Web IDE.
+
+### Changed (31 changes, 10 of them are from the community)
+
+- Improve snippets empty state. !18348 (George Tsiolis)
+- Remove second primary button on wiki edit. !19959 (George Tsiolis)
+- Allow raw `tls_options` to be passed in LDAP configuration. !20678
+- Remove undigested token column from personal_access_tokens table from the database. !22743
+- Update activity filter for issues. !23423 (George Tsiolis)
+- Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml. !24279
+- Error tracking configuration - add a Sentry project selection dropdown. !24701
+- Move ChatOps to Core. !24780
+- Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL. !24910
+- Validate kubernetes cluster CA certificate. !24990
+- Review App Link to Changed Page if Only One Change Present. !25048
+- Show pipeline ID, commit, and branch name on modal while stopping pipeline. !25059
+- Improve empty state for starred projects. !25138
+- Capture due date when importing milestones from Github. !25182 (dstanley)
+- Add a spinner icon which is rendered using pure css. !25186
+- Make emoji picker bigger. !25187 (Jacopo Beschi @jacopo-beschi)
+- API: Sort tie breaker with id DESC. !25311 (Nermin Vehabovic)
+- Add iOS-fastlane template for .gitlab-ci.yml. !25395
+- Move language setting to preferences. !25427 (Fabian Schneider @fabsrc)
+- Resolve Create Project Template for Netlify. !25453
+- Sort labels alphabetically on issues and merge requests list. !25470
+- Add Project template for .NET Core. !25486
+- Update operations settings breadcrumb trail. !25539 (George Tsiolis)
+- Add Project template for go-micro. !25553
+- Jira: make issue links title compact. !25609 (Elan Ruusamäe @glensc)
+- Project level filtering for JupyterHub. !25684 (Amit Rathi (amit1rrr))
+- Clean up vendored templates. !25794
+- Mask all TOKEN and PASSWORD CI variables. !25868
+- Add project template for Android. !25870
+- Add iOS project template. !25872
+- Upgrade to Gitaly v1.26.0. !25890
+
+### Performance (11 changes)
+
+- Improve performance for diverging commit counts. !24287
+- Optimize Redis usage in User::ActivityService. !25005
+- Only load syntax highlight CSS of selected theme. !25232
+- Improve label select rendering. !25281
+- Enable persisted pipeline stages by default. !25347
+- Speed up group issue search counts. !25411
+- Load repository language from the database if detected before. !25518
+- Remove N+1 query for tags in /admin/runners page. !25572
+- Eliminate most N+1 queries loading UserController#calendar_activities. !25697
+- Improve Web IDE launch performance. !25700
+- Significantly reduce N+1 queries in /api/v4/todos endpoint. !25711
+
+### Added (55 changes, 18 of them are from the community)
+
+- Add a tag filter to the admin runners view. !19740 (Alexis Reigel)
+- Add project fetch statistics. !23596 (Jacopo Beschi @jacopo-beschi)
+- Hashed Storage rollback mechanism. !23955
+- Allow to recursively expand includes. !24356
+- Allow expanding a diff to display full file. !24406
+- Support `only: changes:` on MR pipelines. !24490 (Hiroyuki Sato)
+- Expose additional merge request pipeline variables. !24595 (Hiroyuki Sato)
+- Add metadata about the GitLab server to GraphQL. !24636
+- Support merge ref writing (without merging to target branch). !24692
+- Add field mergeRequests for project in GraphQL. !24805
+- API support for MR merge to temporary merge ref path. !24918
+- Ability to filter confidential issues. !24960 (Robert Schilling)
+- Allow creation of branches that match a wildcard protection, except directly through git. !24969
+- Add related merge request count to api response. !24974
+- Add realtime validation for user fullname and username on validation. !25017 (Ehsan Abdulqader @EhsanZ)
+- Allow setting feature flags per GitLab group through the API. !25022
+- Add API endpoint to get a commit's GPG signature. !25032
+- Add support for FTP assets for releases. !25071 (Robert Schilling)
+- Add Confirmation Modal to Rollback on Environment. !25110
+- add title attribute to display file name. !25154 (Satoshi Nakamatsu @satoshicano)
+- API: Expose text_color for project and group labels. !25172 (Robert Schilling)
+- Added support for ingress hostnames. !25181 (walkafwalka)
+- API: Promote project milestone to a group milestone. !25203 (Nermin Vehabovic)
+- API: Expose if the current user can merge a MR. !25207 (Robert Schilling)
+- add readme to changelogs directory. !25209 (@glensc)
+- API: Indicate if label is a project label. !25219 (Robert Schilling)
+- Expose refspecs and depth to runner. !25233
+- Port System Header and Footer feature to Core. !25241
+- Sort Environments by Last Updated. !25260
+- Accept force option to overwrite branch on commit via API. !25286
+- Add support for masking CI variables. !25293
+- Add Link from Closed (moved) Issues to Moved Issue. !25300
+- Next/previous navigation between files in MR review. !25355
+- Add YouTrack integration service. !25361 (Yauhen Kotau @bessorion)
+- Add ability to set path and name for project on fork using API. !25363
+- Add project level config for merge pipelines. !25385
+- Edit Knative domain after it has been deployed. !25386
+- Add zoom and scroll to metrics dashboard. !25388
+- Persist source sha and target sha for merge pipelines. !25417
+- Add support for toggling discussion filter from notes section. !25426
+- Resolve Move files in the Web IDE. !25431
+- Show header and footer system messages in email. !25474
+- Allow configuring POSTGRES_VERSION in Auto DevOps. !25500
+- Add Saturday to Localization first day of the week. !25509 (Ahmad Haghighi)
+- Extend the Gitlab API for deletion of job_artifacts of a single job. !25522 (rroger)
+- Simplify CI/CD configuration on serverless projects. !25523
+- Add button to start discussion from single comment. !25575
+- sidekiq: terminate child processes at shutdown. !25669
+- Expose merge request entity for pipelines. !25679
+- Link to most recent MR from a branch. !25689
+- Adds Auto DevOps build job for tags. !25718 (walkafwalka)
+- Allow all snippets to be accessed by API. !25772
+- Make file tree in merge requests resizable.
+- Make the Web IDE the default editor.
+- File uploads are deleted asynchronously when deleting a project or group.
+
+### Other (28 changes, 6 of them are from the community)
+
+- Improve GitHub and Gitea project import table UI. !24606
+- Externalize strings from `/app/views/projects/commit`. !24668 (George Tsiolis)
+- Correct non-standard unicode spaces to regular unicode. !24795 (Marcel Amirault)
+- Provide a performance bar link to the Jaeger UI. !24902
+- Remove BATCH_SIZE from WikiFileFinder. !24933
+- Use export-import svgs from gitlab-svgs. !24954
+- Fix N+1 query in Issues and MergeRequest API when issuable_metadata is present. !25042 (Alex Koval)
+- Directly inheriting from ActiveRecord::Migration is deprecated. !25066 (Jasper Maes)
+- Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively. !25072
+- Log queue duration in production_json.log. !25075
+- Extracted ResolveWithIssueButton to its own component. !25093 (Martin Hobert)
+- Add rectangular project and group avatars. !25098
+- Include note in the Rails filter_parameters configuration. !25238
+- Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively. !25268
+- Include gl_project_path in API /internal/allowed response. !25314
+- Fix incorrect Pages Domains checkbox description. !25392 (Anton Melser)
+- Update GitLab Runner Helm Chart to 0.2.0. !25493
+- Add suffix (`_event`) to merge request source. !25508
+- Creates a helper function to check if repo is EE. !25647
+- If chpst is available, make fron-source installations run sidekiq as a process group leader. !25654
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Moves EE util into the CE file. !25680
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Enable syntax highlighting to other supported markups. !25761
+- Update GitLab Shell to v8.7.1. !25801
+- Bring back Rugged implementation of commit_tree_entry. !25896
+- Removes EE differences for jobs/getters.js.
+
+
+## 11.8.10 (2019-04-30)
+
+### Security (1 change)
+
+- Allow to see project events only with api scope token.
+
+
+## 11.8.8 (2019-04-23)
+
+### Fixed (5 changes)
+
+- Bring back Rugged implementation of find_commit. !25477
+- Fix bug in BitBucket imports with SHA shorter than 40 chars. !26050
+- Fix health checks not working behind load balancers. !26055
+- Fix error creating a merge request when diff includes a null byte. !26190
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+### Other (4 changes)
+
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Bring back Rugged implementation of commit_tree_entry. !25896
+
+
+## 11.8.3 (2019-03-19)
+
+### Security (1 change)
+
+- Remove project serialization in quick actions response.
+
+
+## 11.8.2 (2019-03-13)
+
+### Security (1 change)
+
+- Fixed ability to see private groups by users not belonging to given group.
+
+### Fixed (5 changes)
+
+- Fix import_jid error on project import. !25239
+- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
+- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
+- Fix method to mark a project repository as writable. !25546
+- Allow project members to see private group if the project is in the group namespace.
+
+
## 11.8.0 (2019-02-22)
### Security (7 changes, 1 of them is from the community)
@@ -249,6 +1197,63 @@ entry.
- Creates mixin to reduce code duplication between CE and EE in graph component.
+## 11.7.12 (2019-04-23)
+
+### Fixed (2 changes)
+
+- Bring back Rugged implementation of find_commit. !25477
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+### Other (4 changes)
+
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Bring back Rugged implementation of commit_tree_entry. !25896
+
+
+## 11.7.11 (2019-04-09)
+
+- No changes.
+
+## 11.7.10 (2019-03-28)
+
+### Security (7 changes)
+
+- Disallow guest users from accessing Releases.
+- Fix PDF.js vulnerability.
+- Hide "related branches" when user does not have permission.
+- Fix XSS in resolve conflicts form.
+- Added rake task for removing EXIF data from existing uploads.
+- Disallow updating namespace when updating a project.
+- Use UntrustedRegexp for matching refs policy.
+
+
+## 11.7.8 (2019-03-26)
+
+### Security (7 changes)
+
+- Disallow guest users from accessing Releases.
+- Fix PDF.js vulnerability.
+- Hide "related branches" when user does not have permission.
+- Fix XSS in resolve conflicts form.
+- Added rake task for removing EXIF data from existing uploads.
+- Disallow updating namespace when updating a project.
+- Use UntrustedRegexp for matching refs policy.
+
+
+## 11.7.7 (2019-03-19)
+
+### Security (2 changes)
+
+- Remove project serialization in quick actions response.
+- Fixed ability to see private groups by users not belonging to given group.
+
+
## 11.7.5 (2019-02-06)
### Fixed (8 changes)
@@ -486,6 +1491,57 @@ entry.
- Update url placeholder for the sentry configuration page. !24338
+## 11.6.11 (2019-04-23)
+
+### Security (2 changes)
+
+- Fixed ability to see private groups by users not belonging to given group.
+- Fix XSS in resolve conflicts form.
+
+### Fixed (2 changes)
+
+- Bring back Rugged implementation of find_commit. !25477
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+### Other (4 changes)
+
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Bring back Rugged implementation of commit_tree_entry. !25896
+
+
+## 11.6.10 (2019-02-28)
+
+### Security (21 changes)
+
+- Stop linking to unrecognized package sources. !55518
+- Check snippet attached file to be moved is within designated directory.
+- Fix potential Addressable::URI::InvalidURIError.
+- Do not display impersonated sessions under active sessions and remove ability to revoke session.
+- Display only information visible to current user on the Milestone page.
+- Show only merge requests visible to user on milestone detail page.
+- Disable issue boards API when issues are disabled.
+- Don't show new issue link after move when a user does not have permissions.
+- Fix git clone revealing private repo's presence.
+- Fix blind SSRF in Prometheus integration by checking URL before querying.
+- Check if desired milestone for an issue is available.
+- Don't allow non-members to see private related MRs.
+- Fix arbitrary file read via diffs during import.
+- Display the correct number of MRs a user has access to.
+- Forbid creating discussions for users with restricted access.
+- Do not disclose milestone titles for unauthorized users.
+- Validate session key when authorizing with GCP to create a cluster.
+- Block local URLs for Kubernetes integration.
+- Limit mermaid rendering to 5K characters.
+- Remove the possibility to share a project with a group that a user is not a member of.
+- Fix leaking private repository information in API.
+
+
## 11.6.8 (2019-01-30)
- No changes.
@@ -830,6 +1886,25 @@ entry.
- Enable Rubocop on lib/gitlab. (gfyoung)
+## 11.5.11 (2019-04-23)
+
+### Fixed (2 changes)
+
+- Bring back Rugged implementation of find_commit. !25477
+- Avoid excessive recursive calls with Rugged TreeEntries. !26813
+
+### Performance (1 change)
+
+- Bring back Rugged implementation of ListCommitsByOid. !27441
+
+### Other (4 changes)
+
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Bring back Rugged implementation of commit_tree_entry. !25896
+
+
## 11.5.8 (2019-01-28)
### Security (21 changes)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 97dbe2f512b..e4c954448a5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,11 +18,11 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
## Contributing Documentation has been moved
As of July 2018, all the documentation for contributing to the GitLab project has been moved to a new location.
-[view the new documentation](doc/development/contributing/index.md) to find the latest information.
+[View the new documentation](https://about.gitlab.com/community/contribute/) to find the latest information.
## Contribute to GitLab
-This [documentation](doc/development/contributing/index.md#contribute-to-gitlab) has been moved.
+[View the new documentation](https://about.gitlab.com/community/contribute/) to find the latest information.
## Security vulnerability disclosure
@@ -42,7 +42,7 @@ This [documentation](doc/development/contributing/index.md#helping-others) has b
## I want to contribute!
-This [documentation](doc/development/contributing/index.md#i-want-to-contribute) has been moved.
+[View the new documentation](https://about.gitlab.com/community/contribute/) to find the latest information.
## Contribution Flow
diff --git a/Dangerfile b/Dangerfile
index 715a2bcbbae..d0a605f8d8e 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,14 +1,22 @@
+# frozen_string_literal: true
danger.import_plugin('danger/plugins/helper.rb')
-danger.import_dangerfile(path: 'danger/metadata')
-danger.import_dangerfile(path: 'danger/changes_size')
-danger.import_dangerfile(path: 'danger/changelog')
-danger.import_dangerfile(path: 'danger/specs')
-danger.import_dangerfile(path: 'danger/gemfile')
-danger.import_dangerfile(path: 'danger/database')
-danger.import_dangerfile(path: 'danger/documentation')
-danger.import_dangerfile(path: 'danger/frozen_string')
-danger.import_dangerfile(path: 'danger/commit_messages')
-danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
-danger.import_dangerfile(path: 'danger/prettier')
-danger.import_dangerfile(path: 'danger/eslint')
-danger.import_dangerfile(path: 'danger/roulette')
+danger.import_plugin('danger/plugins/roulette.rb')
+
+unless helper.release_automation?
+ danger.import_dangerfile(path: 'danger/metadata')
+ danger.import_dangerfile(path: 'danger/changes_size')
+ danger.import_dangerfile(path: 'danger/changelog')
+ danger.import_dangerfile(path: 'danger/specs')
+ danger.import_dangerfile(path: 'danger/gemfile')
+ danger.import_dangerfile(path: 'danger/database')
+ danger.import_dangerfile(path: 'danger/documentation')
+ danger.import_dangerfile(path: 'danger/frozen_string')
+ danger.import_dangerfile(path: 'danger/commit_messages')
+ danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
+ danger.import_dangerfile(path: 'danger/prettier')
+ danger.import_dangerfile(path: 'danger/eslint')
+ danger.import_dangerfile(path: 'danger/roulette')
+ danger.import_dangerfile(path: 'danger/single_codebase')
+ danger.import_dangerfile(path: 'danger/gitlab_ui_wg')
+ danger.import_dangerfile(path: 'danger/ce_ee_vue_templates')
+end
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 39893559155..0a3db35b241 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.20.0
+1.46.0
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
new file mode 100644
index 00000000000..26aaba0e866
--- /dev/null
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -0,0 +1 @@
+1.2.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index bc80560fad6..dc1e644a101 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.5.0
+1.6.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index acd405b1d62..b13d146a7b0 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.6.0
+9.3.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 56b6be4ebb2..df5119ec64e 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.3.1
+8.7.0
diff --git a/Gemfile b/Gemfile
index 67ce615c01e..53856e4fae3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,7 +1,6 @@
source 'https://rubygems.org'
-gem 'rails', '5.0.7.1'
-gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+gem 'rails', '5.1.7'
# Improves copy-on-write performance for MRI
gem 'nakayoshi_fork', '~> 0.0.4'
@@ -18,13 +17,13 @@ gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
gem 'mysql2', '~> 0.4.10', group: :mysql
gem 'pg', '~> 1.1', group: :postgres
-gem 'rugged', '~> 0.27'
-gem 'grape-path-helpers', '~> 1.0'
+gem 'rugged', '~> 0.28'
+gem 'grape-path-helpers', '~> 1.1'
gem 'faraday', '~> 0.12'
# Authentication libraries
-gem 'devise', '~> 4.4'
+gem 'devise', '~> 4.6'
gem 'doorkeeper', '~> 4.3'
gem 'doorkeeper-openid_connect', '~> 1.5'
gem 'omniauth', '~> 1.8'
@@ -42,11 +41,14 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
-gem 'rack-oauth2', '~> 1.2.1'
+gem 'omniauth_openid_connect', '~> 0.3.0'
+gem "omniauth-ultraauth", '~> 0.0.2'
+gem 'omniauth-salesforce', '~> 1.0.5'
+gem 'rack-oauth2', '~> 1.9.3'
gem 'jwt', '~> 2.1.0'
# Spam and anti-bot protection
-gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
+gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
@@ -58,6 +60,8 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
gem 'rubyzip', '~> 1.2.2', require: 'zip'
+# GitLab Pages letsencrypt support
+gem 'acme-client', '~> 2.0.2'
# Browser detection
gem 'browser', '~> 2.5'
@@ -68,7 +72,7 @@ gem 'gpgme', '~> 2.0.18'
# 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.4', require: 'omniauth-ldap'
+gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap'
# API
@@ -79,6 +83,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# GraphQL API
gem 'graphql', '~> 1.8.0'
gem 'graphiql-rails', '~> 1.4.10'
+gem 'apollo_upload_server', '~> 2.0.0.beta3'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
@@ -94,13 +99,15 @@ gem 'carrierwave', '~> 1.3'
gem 'mini_magick'
# for backups
-gem 'fog-aws', '~> 2.0.1'
-gem 'fog-core', '~> 1.44'
-gem 'fog-google', '~> 1.7.1'
-gem 'fog-local', '~> 0.3'
-gem 'fog-openstack', '~> 0.1'
+gem 'fog-aws', '~> 3.3'
+# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
+# Also see config/initializers/fog_core_patch.rb.
+gem 'fog-core', '= 2.1.0'
+gem 'fog-google', '~> 1.8'
+gem 'fog-local', '~> 0.6'
+gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
-gem 'fog-aliyun', '~> 0.2.0'
+gem 'fog-aliyun', '~> 0.3'
# for Google storage
gem 'google-api-client', '~> 0.23'
@@ -114,7 +121,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.2.0'
-gem 'gitlab-markup', '~> 1.6.5'
+gem 'gitlab-markup', '~> 1.7.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2'
@@ -126,8 +133,8 @@ gem 'asciidoctor', '~> 1.5.8'
gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.11'
-gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.10.1'
+gem 'bootstrap_form', '~> 4.2.0'
+gem 'nokogiri', '~> 1.10.3'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -137,10 +144,7 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0'
# Application server
-# The 2.0.6 version of rack requires monkeypatch to be present in
-# `config.ru`. This can be removed once a new update for Rack
-# is available that contains https://github.com/rack/rack/pull/1201.
-gem 'rack', '2.0.6'
+gem 'rack', '~> 2.0.7'
group :unicorn do
gem 'unicorn', '~> 5.4.1'
@@ -150,25 +154,26 @@ end
group :puma do
gem 'puma', '~> 3.12', require: false
gem 'puma_worker_killer', require: false
+ gem 'rack-timeout', require: false
end
# State machine
gem 'state_machines-activerecord', '~> 0.5.1'
# Issue tags
-gem 'acts-as-taggable-on', '~> 5.0'
+gem 'acts-as-taggable-on', '~> 6.0'
# Background jobs
-gem 'sidekiq', '~> 5.2.1'
+gem 'sidekiq', '~> 5.2.7'
gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser
-gem 'fugit', '~> 1.1'
+gem 'fugit', '~> 1.2.1'
# HTTP requests
-gem 'httparty', '~> 0.13.3'
+gem 'httparty', '~> 0.16.4'
# Colored output to console
gem 'rainbow', '~> 3.0'
@@ -202,6 +207,9 @@ gem 'connection_pool', '~> 2.0'
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
+# HipChat integration
+gem 'hipchat', '~> 1.5.0'
+
# JIRA integration
gem 'jira-ruby', '~> 1.4'
@@ -255,17 +263,14 @@ gem 'chronic_duration', '~> 0.10.6'
gem 'webpack-rails', '~> 0.9.10'
gem 'rack-proxy', '~> 0.6.0'
-gem 'sass-rails', '~> 5.0.6'
-gem 'sass', '~> 3.5'
+gem 'sassc-rails', '~> 2.1.0'
gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.5.2'
gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2'
-gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'request_store', '~> 1.3'
-gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
@@ -274,6 +279,9 @@ gem 'sentry-raven', '~> 2.7'
gem 'premailer-rails', '~> 1.9.7'
+# LabKit: Tracing and Correlation
+gem 'gitlab-labkit', '~> 0.2.0'
+
# I18n
gem 'ruby_parser', '~> 3.8', require: false
gem 'rails-i18n', '~> 5.1'
@@ -281,7 +289,7 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3'
gem 'gettext', '~> 3.2.2', require: false, group: :development
-gem 'batch-loader', '~> 1.2.2'
+gem 'batch-loader', '~> 1.4.0'
# Perf bar
gem 'peek', '~> 1.0.1'
@@ -301,17 +309,11 @@ group :metrics do
gem 'raindrops', '~> 0.18'
end
-group :tracing do
- # OpenTracing
- gem 'opentracing', '~> 0.4.3'
- gem 'jaeger-client', '~> 0.10.0'
-end
-
group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
- gem 'letter_opener_web', '~> 1.3.0'
+ gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler
@@ -323,7 +325,7 @@ group :development do
end
group :development, :test do
- gem 'bootsnap', '~> 1.3'
+ gem 'bootsnap', '~> 1.4'
gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET']
gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'pry-rails', '~> 0.3.4'
@@ -334,7 +336,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.7.0'
- gem 'rspec-retry', '~> 0.4.5'
+ gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized', require: false
@@ -345,21 +347,22 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.10'
- gem 'capybara', '~> 2.16.1'
- gem 'capybara-screenshot', '~> 1.0.18'
- gem 'selenium-webdriver', '~> 3.12'
+ gem 'capybara', '~> 2.18.0'
+ gem 'capybara-screenshot', '~> 1.0.22'
+ gem 'selenium-webdriver', '~> 3.141'
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
- gem 'gitlab-styles', '~> 2.4', require: false
+ gem 'gitlab-styles', '~> 2.7', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
- gem 'rubocop', '~> 0.54.0'
+ gem 'rubocop', '~> 0.69.0'
+ gem 'rubocop-performance', '~> 1.1.0'
gem 'rubocop-rspec', '~> 1.22.1'
gem 'scss_lint', '~> 0.56.0', require: false
- gem 'haml_lint', '~> 0.28.0', require: false
- gem 'simplecov', '~> 0.14.0', require: false
+ gem 'haml_lint', '~> 0.31.0', require: false
+ gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
@@ -370,6 +373,7 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false
+ gem 'derailed_benchmarks', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
@@ -377,7 +381,7 @@ group :development, :test do
end
group :test do
- gem 'shoulda-matchers', '~> 3.1.2', require: false
+ gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 3.5.1'
@@ -397,6 +401,9 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.17.0'
gem 'rbtrace', '~> 0.4', require: false
+gem 'memory_profiler', '~> 0.9', require: false
+gem 'benchmark-memory', '~> 0.1', require: false
+gem 'activerecord-explain-analyze', '~> 0.1', require: false
# OAuth
gem 'oauth2', '~> 1.4'
@@ -419,10 +426,11 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.10.0', require: 'gitaly'
-gem 'grpc', '~> 1.15.0'
+gem 'gitaly-proto', '~> 1.32.0', require: 'gitaly'
+
+gem 'grpc', '~> 1.19.0'
-gem 'google-protobuf', '~> 3.6'
+gem 'google-protobuf', '~> 3.7.1'
gem 'toml-rb', '~> 1.0.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 841f85dc7e5..c8a090e7053 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,47 +4,52 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
- actioncable (5.0.7.1)
- actionpack (= 5.0.7.1)
- nio4r (>= 1.2, < 3.0)
+ acme-client (2.0.2)
+ faraday (~> 0.9, >= 0.9.1)
+ actioncable (5.1.7)
+ actionpack (= 5.1.7)
+ nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
- actionmailer (5.0.7.1)
- actionpack (= 5.0.7.1)
- actionview (= 5.0.7.1)
- activejob (= 5.0.7.1)
+ actionmailer (5.1.7)
+ actionpack (= 5.1.7)
+ actionview (= 5.1.7)
+ activejob (= 5.1.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.0.7.1)
- actionview (= 5.0.7.1)
- activesupport (= 5.0.7.1)
+ actionpack (5.1.7)
+ actionview (= 5.1.7)
+ activesupport (= 5.1.7)
rack (~> 2.0)
- rack-test (~> 0.6.3)
+ rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.7.1)
- activesupport (= 5.0.7.1)
+ actionview (5.1.7)
+ activesupport (= 5.1.7)
builder (~> 3.1)
- erubis (~> 2.7.0)
+ erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.0.7.1)
- activesupport (= 5.0.7.1)
+ activejob (5.1.7)
+ activesupport (= 5.1.7)
globalid (>= 0.3.6)
- activemodel (5.0.7.1)
- activesupport (= 5.0.7.1)
- activerecord (5.0.7.1)
- activemodel (= 5.0.7.1)
- activesupport (= 5.0.7.1)
- arel (~> 7.0)
+ activemodel (5.1.7)
+ activesupport (= 5.1.7)
+ activerecord (5.1.7)
+ activemodel (= 5.1.7)
+ activesupport (= 5.1.7)
+ arel (~> 8.0)
+ activerecord-explain-analyze (0.1.0)
+ activerecord (>= 4)
+ pg
activerecord_sane_schema_dumper (1.0)
rails (>= 5, < 6)
- activesupport (5.0.7.1)
+ activesupport (5.1.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- acts-as-taggable-on (5.0.0)
- activerecord (>= 4.2.8)
+ acts-as-taggable-on (6.0.0)
+ activerecord (~> 5.0)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
@@ -52,7 +57,10 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.0.1)
akismet (2.0.0)
- arel (7.1.4)
+ apollo_upload_server (2.0.0.beta.3)
+ graphql (>= 1.8)
+ rails (>= 4.2)
+ arel (8.0.0)
asana (0.8.1)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
@@ -65,7 +73,7 @@ GEM
atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
- attr_required (1.0.0)
+ attr_required (1.0.1)
awesome_print (1.8.0)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
@@ -73,21 +81,25 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
base32 (0.3.2)
- batch-loader (1.2.2)
+ batch-loader (1.4.0)
bcrypt (3.1.12)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
+ benchmark-memory (0.1.2)
+ memory_profiler (~> 0.9)
better_errors (2.5.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
bindata (2.4.3)
- binding_ninja (0.2.2)
+ binding_ninja (0.2.3)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
- bootsnap (1.3.2)
+ bootsnap (1.4.1)
msgpack (~> 1.0)
- bootstrap_form (2.7.0)
+ bootstrap_form (4.2.0)
+ actionpack (>= 5.0)
+ activemodel (>= 5.0)
brakeman (4.2.1)
browser (2.5.3)
builder (3.2.3)
@@ -98,13 +110,13 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (9.1.0)
- capybara (2.16.1)
+ capybara (2.18.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
- xpath (~> 2.0)
+ xpath (>= 2.0, < 4.0)
capybara-screenshot (1.0.22)
capybara (>= 1.0, < 4)
launchy
@@ -130,9 +142,9 @@ GEM
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
- concurrent-ruby (1.1.3)
- concurrent-ruby-ext (1.1.3)
- concurrent-ruby (= 1.1.3)
+ concurrent-ruby (1.1.5)
+ concurrent-ruby-ext (1.1.5)
+ concurrent-ruby (= 1.1.5)
connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -148,10 +160,18 @@ GEM
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
+ derailed_benchmarks (1.3.5)
+ benchmark-ips (~> 2)
+ get_process_mem (~> 0)
+ heapy (~> 0)
+ memory_profiler (~> 0)
+ rack (>= 1)
+ rake (> 10, < 13)
+ thor (~> 0.19)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
- devise (4.4.3)
+ devise (4.6.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 6.0)
@@ -167,7 +187,7 @@ GEM
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
- docile (1.1.5)
+ docile (1.3.1)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
@@ -183,10 +203,9 @@ GEM
mail (~> 2.7)
encryptor (3.0.0)
equalizer (0.0.11)
- erubi (1.7.1)
- erubis (2.7.0)
+ erubi (1.8.0)
escape_utils (1.2.1)
- et-orbi (1.1.7)
+ et-orbi (1.2.1)
tzinfo
eventmachine (1.2.7)
excon (0.62.0)
@@ -218,32 +237,33 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
- fog-aliyun (0.2.0)
- fog-core (~> 1.27)
- fog-json (~> 1.0)
+ fog-aliyun (0.3.3)
+ fog-core
+ fog-json
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (2.0.1)
- fog-core (~> 1.38)
- fog-json (~> 1.0)
+ fog-aws (3.3.0)
+ fog-core (~> 2.1)
+ fog-json (~> 1.1)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-core (1.45.0)
+ fog-core (2.1.0)
builder
excon (~> 0.58)
formatador (~> 0.2)
- fog-google (1.7.1)
- fog-core
- fog-json
- fog-xml
+ mime-types
+ fog-google (1.8.2)
+ fog-core (<= 2.1.0)
+ fog-json (~> 1.2)
+ fog-xml (~> 0.1.0)
google-api-client (~> 0.23.0)
- fog-json (1.0.2)
- fog-core (~> 1.0)
+ fog-json (1.2.0)
+ fog-core
multi_json (~> 1.10)
- fog-local (0.3.1)
- fog-core (~> 1.27)
- fog-openstack (0.1.21)
- fog-core (>= 1.40)
+ fog-local (0.6.0)
+ fog-core (>= 1.27, < 3.0)
+ fog-openstack (1.0.8)
+ fog-core (~> 2.1)
fog-json (>= 1.0)
ipaddress (>= 0.8)
fog-rackspace (0.1.1)
@@ -254,20 +274,20 @@ GEM
fog-xml (0.1.3)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
- font-awesome-rails (4.7.0.1)
- railties (>= 3.2, < 5.1)
+ font-awesome-rails (4.7.0.4)
+ railties (>= 3.2, < 6.0)
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
- fugit (1.1.7)
- et-orbi (~> 1.1, >= 1.1.7)
+ fugit (1.2.1)
+ et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
gemojione (3.3.0)
json
- get_process_mem (0.2.0)
+ get_process_mem (0.2.3)
gettext (3.2.9)
locale (>= 2.0.5)
text (>= 1.3.0)
@@ -278,24 +298,31 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.10.0)
+ gitaly-proto (1.32.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
activerecord (>= 3.2.0, < 6.0)
- gitlab-markup (1.6.5)
+ gitlab-labkit (0.2.0)
+ actionpack (~> 5)
+ activesupport (~> 5)
+ grpc (~> 1.15)
+ jaeger-client (~> 0.10)
+ opentracing (~> 0.4)
+ gitlab-markup (1.7.0)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
- gitlab-styles (2.5.1)
- rubocop (~> 0.54.0)
+ gitlab-styles (2.7.0)
+ rubocop (~> 0.69.0)
rubocop-gitlab-security (~> 0.1.0)
+ rubocop-performance (~> 1.1.0)
rubocop-rspec (~> 1.19)
- gitlab_omniauth-ldap (2.0.4)
+ gitlab_omniauth-ldap (2.1.1)
net-ldap (~> 0.16)
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
- globalid (0.4.1)
+ globalid (0.4.2)
activesupport (>= 4.2.0)
gon (6.2.0)
actionpack (>= 3.0)
@@ -308,8 +335,8 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.6.1)
- googleapis-common-protos-types (1.0.2)
+ google-protobuf (3.7.1)
+ googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0)
googleauth (0.6.6)
faraday (~> 0.12)
@@ -330,8 +357,8 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
- grape-path-helpers (1.0.6)
- activesupport (>= 4, < 5.1)
+ grape-path-helpers (1.1.0)
+ activesupport
grape (~> 1.0)
rake (~> 12)
grape_logging (1.7.0)
@@ -340,13 +367,13 @@ GEM
railties
sprockets-rails
graphql (1.8.1)
- grpc (1.15.0)
+ grpc (1.19.0)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
haml (5.0.4)
temple (>= 0.8.0)
tilt
- haml_lint (0.28.0)
+ haml_lint (0.31.0)
haml (>= 4.0, < 5.1)
rainbow
rake (>= 10, < 13)
@@ -363,6 +390,10 @@ GEM
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
+ heapy (0.1.4)
+ hipchat (1.5.2)
+ httparty
+ mimemagic
html-pipeline (2.8.4)
activesupport (>= 2)
nokogiri (>= 1.4)
@@ -378,11 +409,11 @@ GEM
domain_name (~> 0.5)
http-form_data (2.1.1)
http_parser.rb (0.6.0)
- httparty (0.13.7)
- json (~> 1.8)
+ httparty (0.16.4)
+ mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
- i18n (1.2.0)
+ i18n (1.6.0)
concurrent-ruby (~> 1.0)
icalendar (2.4.1)
ice_nine (0.11.2)
@@ -393,11 +424,11 @@ GEM
jaeger-client (0.10.0)
opentracing (~> 0.3)
thrift
+ jaro_winkler (1.5.2)
jira-ruby (1.4.1)
activesupport
multipart-post
oauth (~> 0.5, >= 0.5.0)
- jquery-atwho-rails (1.3.2)
js_regex (3.1.1)
character_set (~> 1.1)
regexp_parser (~> 1.1)
@@ -431,9 +462,9 @@ GEM
rest-client (~> 2.0)
launchy (2.4.3)
addressable (~> 2.3)
- letter_opener (1.4.1)
+ letter_opener (1.7.0)
launchy (~> 2.2)
- letter_opener_web (1.3.0)
+ letter_opener_web (1.3.4)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
@@ -461,6 +492,7 @@ GEM
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
+ memory_profiler (0.9.13)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
@@ -483,7 +515,7 @@ GEM
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.10.1)
+ nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
@@ -538,6 +570,9 @@ GEM
omniauth (~> 1.9)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
+ omniauth-salesforce (1.0.5)
+ omniauth (~> 1.0)
+ omniauth-oauth2 (~> 1.0)
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
@@ -546,18 +581,34 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
+ omniauth-ultraauth (0.0.2)
+ omniauth_openid_connect (~> 0.3.0)
omniauth_crowd (2.2.3)
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
- opentracing (0.4.3)
+ omniauth_openid_connect (0.3.0)
+ addressable (~> 2.5)
+ omniauth (~> 1.3)
+ openid_connect (~> 1.1)
+ openid_connect (1.1.6)
+ activemodel
+ attr_required (>= 1.0.0)
+ json-jwt (>= 1.5.0)
+ rack-oauth2 (>= 1.6.1)
+ swd (>= 1.0.0)
+ tzinfo
+ validate_email
+ validate_url
+ webfinger (>= 1.0.1)
+ opentracing (0.5.0)
optimist (3.0.0)
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (1.0.0)
- parallel (1.12.1)
- parser (2.5.3.0)
+ parallel (1.17.0)
+ parser (2.6.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
@@ -583,10 +634,9 @@ GEM
atomic (>= 1.0.0)
peek
redis
- pg (1.1.3)
+ pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
- powerpack (0.1.1)
premailer (1.10.4)
addressable
css_parser (>= 1.4.10)
@@ -615,42 +665,41 @@ GEM
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6)
- rack (2.0.6)
+ rack (2.0.7)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
rack
rack-cors (1.0.2)
- rack-oauth2 (1.2.3)
- activesupport (>= 2.3)
- attr_required (>= 0.0.5)
- httpclient (>= 2.4)
- multi_json (>= 1.3.6)
- rack (>= 1.1)
+ rack-oauth2 (1.9.3)
+ activesupport
+ attr_required
+ httpclient
+ json-jwt (>= 1.9.0)
+ rack
rack-protection (2.0.5)
rack
rack-proxy (0.6.0)
rack
- rack-test (0.6.3)
- rack (>= 1.0)
- rails (5.0.7.1)
- actioncable (= 5.0.7.1)
- actionmailer (= 5.0.7.1)
- actionpack (= 5.0.7.1)
- actionview (= 5.0.7.1)
- activejob (= 5.0.7.1)
- activemodel (= 5.0.7.1)
- activerecord (= 5.0.7.1)
- activesupport (= 5.0.7.1)
+ rack-test (1.1.0)
+ rack (>= 1.0, < 3)
+ rack-timeout (0.5.1)
+ rails (5.1.7)
+ actioncable (= 5.1.7)
+ actionmailer (= 5.1.7)
+ actionpack (= 5.1.7)
+ actionview (= 5.1.7)
+ activejob (= 5.1.7)
+ activemodel (= 5.1.7)
+ activerecord (= 5.1.7)
+ activesupport (= 5.1.7)
bundler (>= 1.3.0)
- railties (= 5.0.7.1)
+ railties (= 5.1.7)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
actionview (~> 5.x, >= 5.0.1)
activesupport (~> 5.x)
- rails-deprecated_sanitizer (1.0.3)
- activesupport (>= 4.2.0.alpha)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
@@ -659,9 +708,9 @@ GEM
rails-i18n (5.1.1)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
- railties (5.0.7.1)
- actionpack (= 5.0.7.1)
- activesupport (= 5.0.7.1)
+ railties (5.1.7)
+ actionpack (= 5.1.7)
+ activesupport (= 5.1.7)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -679,7 +728,7 @@ GEM
optimist (>= 3.0.0)
rdoc (6.0.4)
re2 (1.1.1)
- recaptcha (3.0.0)
+ recaptcha (4.13.1)
json
recursive-open-struct (1.1.0)
redis (3.3.5)
@@ -701,7 +750,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.6.0)
redis (>= 2.2, < 5)
- regexp_parser (1.3.0)
+ regexp_parser (1.4.0)
regexp_property_values (0.3.4)
representable (3.0.4)
declarative (< 0.1.0)
@@ -735,8 +784,8 @@ GEM
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
- rspec-parameterized (0.4.1)
- binding_ninja (>= 0.2.1)
+ rspec-parameterized (0.4.2)
+ binding_ninja (>= 0.2.3)
parser
proc_to_ast
rspec (>= 2.13, < 4)
@@ -749,8 +798,8 @@ GEM
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
- rspec-retry (0.4.5)
- rspec-core
+ rspec-retry (0.6.1)
+ rspec-core (> 3.3)
rspec-set (0.1.3)
rspec-support (3.7.1)
rspec_junit_formatter (0.4.1)
@@ -760,15 +809,17 @@ GEM
pg
rails
sqlite3
- rubocop (0.54.0)
+ rubocop (0.69.0)
+ jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
- parser (>= 2.5)
- powerpack (~> 0.1)
+ parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (~> 1.0, >= 1.0.1)
+ unicode-display_width (>= 1.4.0, < 1.7)
rubocop-gitlab-security (0.1.1)
rubocop (>= 0.51)
+ rubocop-performance (1.1.0)
+ rubocop (>= 0.67.0)
rubocop-rspec (1.22.2)
rubocop (>= 0.52.1)
ruby-enum (0.7.2)
@@ -776,15 +827,15 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.17.0)
- ruby-progressbar (1.9.0)
+ ruby-progressbar (1.10.0)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
- ruby_parser (3.11.0)
+ ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rugged (0.27.5)
+ rugged (0.28.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -795,12 +846,15 @@ GEM
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
- sass-rails (5.0.6)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
+ sassc (2.0.1)
+ ffi (~> 1.9)
+ rake
+ sassc-rails (2.1.0)
+ railties (>= 4.0.0)
+ sassc (>= 2.0)
+ sprockets (> 3.0)
+ sprockets-rails
+ tilt
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
@@ -810,20 +864,18 @@ GEM
seed-fu (2.3.7)
activerecord (>= 3.1)
activesupport (>= 3.1)
- select2-rails (3.5.9.3)
- thor (~> 0.14)
- selenium-webdriver (3.12.0)
+ selenium-webdriver (3.141.0)
childprocess (~> 0.5)
- rubyzip (~> 1.2)
- sentry-raven (2.7.4)
+ rubyzip (~> 1.2, >= 1.2.2)
+ sentry-raven (2.9.0)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
- sexp_processor (4.11.0)
+ sexp_processor (4.12.0)
sham_rack (1.3.6)
rack
- shoulda-matchers (3.1.2)
- activesupport (>= 4.0.0)
- sidekiq (5.2.5)
+ shoulda-matchers (4.0.1)
+ activesupport (>= 4.2.0)
+ sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
@@ -837,11 +889,11 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_po_parser (1.1.2)
- simplecov (0.14.1)
- docile (~> 1.1.0)
+ simplecov (0.16.1)
+ docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
- simplecov-html (0.10.0)
+ simplecov-html (0.10.2)
slack-notifier (1.5.1)
spring (2.0.2)
activesupport (>= 4.2)
@@ -864,6 +916,10 @@ GEM
state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0)
+ swd (1.1.2)
+ activesupport (>= 3)
+ attr_required (>= 0.0.5)
+ httpclient (>= 2.4)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
@@ -897,7 +953,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
- unicode-display_width (1.3.2)
+ unicode-display_width (1.6.0)
unicorn (5.4.1)
kgio (~> 2.6)
raindrops (~> 0.7)
@@ -905,14 +961,20 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
- unparser (0.4.2)
+ unparser (0.4.5)
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.6)
+ parser (~> 2.6.3)
procto (~> 0.0.2)
+ validate_email (0.1.6)
+ activemodel (>= 3.0)
+ mail (>= 2.2.5)
+ validate_url (1.0.8)
+ activemodel (>= 3.0.0)
+ public_suffix
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
@@ -925,6 +987,9 @@ GEM
vmstat (2.3.0)
warden (1.2.7)
rack (>= 1.0)
+ webfinger (1.1.0)
+ activesupport
+ httpclient (>= 2.4)
webmock (3.5.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
@@ -940,8 +1005,8 @@ GEM
rinku
with_env (1.1.0)
xml-simple (1.1.5)
- xpath (2.1.0)
- nokogiri (~> 1.3)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
PLATFORMS
ruby
@@ -949,10 +1014,13 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
+ acme-client (~> 2.0.2)
+ activerecord-explain-analyze (~> 0.1)
activerecord_sane_schema_dumper (= 1.0)
- acts-as-taggable-on (~> 5.0)
+ acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
+ apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.8.1)
asciidoctor (~> 1.5.8)
asciidoctor-plantuml (= 0.0.8)
@@ -960,19 +1028,20 @@ DEPENDENCIES
awesome_print
babosa (~> 1.0.2)
base32 (~> 0.3.0)
- batch-loader (~> 1.2.2)
+ batch-loader (~> 1.4.0)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
+ benchmark-memory (~> 0.1)
better_errors (~> 2.5.0)
binding_of_caller (~> 0.8.0)
- bootsnap (~> 1.3)
- bootstrap_form (~> 2.7.0)
+ bootsnap (~> 1.4)
+ bootstrap_form (~> 4.2.0)
brakeman (~> 4.2)
browser (~> 2.5)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
- capybara (~> 2.16.1)
- capybara-screenshot (~> 1.0.18)
+ capybara (~> 2.18.0)
+ capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2)
@@ -983,8 +1052,9 @@ DEPENDENCIES
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0)
+ derailed_benchmarks
device_detector
- devise (~> 4.4)
+ devise (~> 4.6)
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
@@ -1002,64 +1072,65 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
- fog-aliyun (~> 0.2.0)
- fog-aws (~> 2.0.1)
- fog-core (~> 1.44)
- fog-google (~> 1.7.1)
- fog-local (~> 0.3)
- fog-openstack (~> 0.1)
+ fog-aliyun (~> 0.3)
+ fog-aws (~> 3.3)
+ fog-core (= 2.1.0)
+ fog-google (~> 1.8)
+ fog-local (~> 0.6)
+ fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
- fugit (~> 1.1)
+ fugit (~> 1.2.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.10.0)
+ gitaly-proto (~> 1.32.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
- gitlab-markup (~> 1.6.5)
+ gitlab-labkit (~> 0.2.0)
+ gitlab-markup (~> 1.7.0)
gitlab-sidekiq-fetcher (~> 0.4.0)
- gitlab-styles (~> 2.4)
- gitlab_omniauth-ldap (~> 2.0.4)
+ gitlab-styles (~> 2.7)
+ gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
- google-protobuf (~> 3.6)
+ google-protobuf (~> 3.7.1)
gpgme (~> 2.0.18)
grape (~> 1.1.0)
grape-entity (~> 0.7.1)
- grape-path-helpers (~> 1.0)
+ grape-path-helpers (~> 1.1)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
- grpc (~> 1.15.0)
- haml_lint (~> 0.28.0)
+ grpc (~> 1.19.0)
+ haml_lint (~> 0.31.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
+ hipchat (~> 1.5.0)
html-pipeline (~> 2.8)
html2text
- httparty (~> 0.13.3)
+ httparty (~> 0.16.4)
icalendar
influxdb (~> 0.2)
- jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
- jquery-atwho-rails (~> 1.3.2)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
kubeclient (~> 4.2.2)
- letter_opener_web (~> 1.3.0)
+ letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
mail_room (~> 0.9.1)
+ memory_profiler (~> 0.9)
method_source (~> 0.8)
mimemagic (~> 0.3.2)
mini_magick
@@ -1068,7 +1139,7 @@ DEPENDENCIES
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
- nokogiri (~> 1.10.1)
+ nokogiri (~> 1.10.3)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1082,11 +1153,13 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.6.0)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
+ omniauth-salesforce (~> 1.0.5)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
+ omniauth-ultraauth (~> 0.0.2)
omniauth_crowd (~> 2.2.0)
- opentracing (~> 0.4.3)
+ omniauth_openid_connect (~> 0.3.0)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
@@ -1101,14 +1174,14 @@ DEPENDENCIES
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
- rack (= 2.0.6)
+ rack (~> 2.0.7)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
- rack-oauth2 (~> 1.2.1)
+ rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
- rails (= 5.0.7.1)
+ rack-timeout
+ rails (= 5.1.7)
rails-controller-testing
- rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1)
rainbow (~> 3.0)
raindrops (~> 0.18)
@@ -1116,7 +1189,7 @@ DEPENDENCIES
rbtrace (~> 0.4)
rdoc (~> 6.0)
re2 (~> 1.1.1)
- recaptcha (~> 3.0)
+ recaptcha (~> 4.11)
redis (~> 3.2)
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
@@ -1126,33 +1199,32 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.7.0)
- rspec-retry (~> 0.4.5)
+ rspec-retry (~> 0.6.1)
rspec-set (~> 0.1.3)
rspec_junit_formatter
rspec_profiling (~> 0.0.5)
- rubocop (~> 0.54.0)
+ rubocop (~> 0.69.0)
+ rubocop-performance (~> 1.1.0)
rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8)
rubyzip (~> 1.2.2)
- rugged (~> 0.27)
+ rugged (~> 0.28)
sanitize (~> 4.6)
- sass (~> 3.5)
- sass-rails (~> 5.0.6)
+ sassc-rails (~> 2.1.0)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
- select2-rails (~> 3.5.9)
- selenium-webdriver (~> 3.12)
+ selenium-webdriver (~> 3.141)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
- shoulda-matchers (~> 3.1.2)
- sidekiq (~> 5.2.1)
+ shoulda-matchers (~> 4.0.1)
+ sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
- simplecov (~> 0.14.0)
+ simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1)
spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4)
diff --git a/PROCESS.md b/PROCESS.md
index 1f99cebe081..3c40f658070 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -113,7 +113,7 @@ corresponding exception request to be created.
A level of common sense should be applied when deciding whether to have a feature
behind a feature flag off or on by default.
-The following guideliness can be applied to help make this decision:
+The following guidelines can be applied to help make this decision:
* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
@@ -125,7 +125,7 @@ For more information on rolling out changes using feature flags, read [through t
In order to build the final package and present the feature for self-hosted
customers, the feature flag should be removed. This should happen before the
22nd, ideally _at least_ 2 days before. That means MRs with feature
-flags being picked at the 19th would have a quite tight schedule, so picking
+flags being picked at the 19th would have quite a tight schedule, so picking
these _earlier_ is preferable.
While rare, release managers may decide to reject picking a change into a stable
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 00000000000..2afbe5fc3f2
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,12 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+docutils = "==0.13.1"
+
+[requires]
+python_version = "3.4"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 00000000000..dc23ca89ed2
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,30 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "ec82d5e7c10fd591aeebbc9b7b62d730f7fd70dc52e4e4818834891aa4194c73"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.4"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "docutils": {
+ "hashes": [
+ "sha256:718c0f5fb677be0f34b781e04241c4067cbd9327b66bdd8e763201130f5175be",
+ "sha256:cb3ebcb09242804f84bdbf0b26504077a054da6772c6f4d625f335cc53ebf94d",
+ "sha256:de454f1015958450b72641165c08afe7023cd7e3944396448f2fb1b0ccba9d77"
+ ],
+ "index": "pypi",
+ "version": "==0.13.1"
+ }
+ },
+ "develop": {}
+}
diff --git a/VERSION b/VERSION
index 00a946e3be3..11bf1ec035c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.9.0-pre
+12.0.0-pre
diff --git a/app/assets/images/favicon-yellow.png b/app/assets/images/favicon-yellow.png
index 2d5289818b4..a80827808fc 100644
--- a/app/assets/images/favicon-yellow.png
+++ b/app/assets/images/favicon-yellow.png
Binary files differ
diff --git a/app/assets/images/select2-spinner.gif b/app/assets/images/select2-spinner.gif
new file mode 100644
index 00000000000..5b33f7e54f4
--- /dev/null
+++ b/app/assets/images/select2-spinner.gif
Binary files differ
diff --git a/app/assets/images/select2.png b/app/assets/images/select2.png
new file mode 100644
index 00000000000..1d804ffb996
--- /dev/null
+++ b/app/assets/images/select2.png
Binary files differ
diff --git a/app/assets/images/select2x2.png b/app/assets/images/select2x2.png
new file mode 100644
index 00000000000..4bdd5c961d4
--- /dev/null
+++ b/app/assets/images/select2x2.png
Binary files differ
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 85eb08cc97d..7cebb88f3a4 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
+import { joinPaths } from './lib/utils/url_utility';
const Api = {
groupsPath: '/api/:version/groups.json',
@@ -11,7 +12,8 @@ const Api = {
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
- projectLabelsPath: '/:namespace_path/:project_path/labels',
+ projectLabelsPath: '/:namespace_path/:project_path/-/labels',
+ projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
@@ -111,6 +113,22 @@ const Api = {
return axios.get(url);
},
+ /**
+ * Get all Merge Requests for a project, eventually filtering based on
+ * supplied parameters
+ * @param projectPath
+ * @param params
+ * @returns {Promise}
+ */
+ projectMergeRequests(projectPath, params = {}) {
+ const url = Api.buildUrl(Api.projectMergeRequestsPath).replace(
+ ':id',
+ encodeURIComponent(projectPath),
+ );
+
+ return axios.get(url, { params });
+ },
+
// Return Merge Request for project
projectMergeRequest(projectPath, mergeRequestId, params = {}) {
const url = Api.buildUrl(Api.projectMergeRequestPath)
@@ -322,11 +340,7 @@ const Api = {
},
buildUrl(url) {
- let urlRoot = '';
- if (gon.relative_url_root != null) {
- urlRoot = gon.relative_url_root;
- }
- return urlRoot + url.replace(':version', gon.api_version);
+ return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
};
diff --git a/app/assets/javascripts/avatar_picker.js b/app/assets/javascripts/avatar_picker.js
new file mode 100644
index 00000000000..d38e0b4abaa
--- /dev/null
+++ b/app/assets/javascripts/avatar_picker.js
@@ -0,0 +1,16 @@
+import $ from 'jquery';
+
+export default function initAvatarPicker() {
+ $('.js-choose-avatar-button').on('click', function onClickAvatar() {
+ const form = $(this).closest('form');
+ return form.find('.js-avatar-input').click();
+ });
+
+ $('.js-avatar-input').on('change', function onChangeAvatarInput() {
+ const form = $(this).closest('form');
+ const filename = $(this)
+ .val()
+ .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
+ return form.find('.js-avatar-filename').text(filename);
+ });
+}
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 73ce3e760ab..743f11625bc 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -8,6 +8,7 @@ import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
+import bp from './breakpoints';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -264,7 +265,10 @@ export class AwardsHandler {
const css = {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
- if (position === 'right') {
+ // for xs screen we position the element on center
+ if (bp.getBreakpointSize() === 'xs') {
+ css.left = '5%';
+ } else if (position === 'right') {
css.left = `${$addBtn.offset().left - $menu.outerWidth() + 20}px`;
$menu.addClass('is-aligned-right');
} else {
diff --git a/app/assets/javascripts/batch_comments/mixins/resolved_status.js b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
new file mode 100644
index 00000000000..3bbbaa86b51
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
@@ -0,0 +1,15 @@
+import { sprintf, __ } from '~/locale';
+
+export default {
+ computed: {
+ resolveButtonTitle() {
+ let title = __('Mark comment as resolved');
+
+ if (this.resolvedBy) {
+ title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
+ }
+
+ return title;
+ },
+ },
+};
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index 9a33a060c76..c3541e62568 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Clipboard from 'clipboard';
+import { sprintf, __ } from '~/locale';
function showTooltip(target, title) {
const $target = $(target);
@@ -16,7 +17,7 @@ function showTooltip(target, title) {
}
function genericSuccess(e) {
- showTooltip(e.trigger, 'Copied');
+ showTooltip(e.trigger, __('Copied'));
// Clear the selection and blur the trigger so it loses its border
e.clearSelection();
$(e.trigger).blur();
@@ -33,7 +34,7 @@ function genericError(e) {
} else {
key = 'Ctrl';
}
- showTooltip(e.trigger, `Press ${key}-C to copy`);
+ showTooltip(e.trigger, sprintf(__(`Press %{key}-C to copy`), { key }));
}
export default function initCopyToClipboard() {
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 9482a9f166d..318b7f77c7b 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -10,10 +10,10 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
- $(document).on('copy', '.md, .wiki', e => {
+ $(document).on('copy', '.md', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
});
- $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => {
+ $(document).on('copy', 'pre.code.highlight, table.code td.line_content', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection);
});
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
@@ -99,7 +99,7 @@ export class CopyAsGFM {
}
static transformGFMSelection(documentFragment) {
- const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
+ const gfmElements = documentFragment.querySelectorAll('.md');
switch (gfmElements.length) {
case 0: {
return documentFragment;
@@ -173,7 +173,9 @@ export class CopyAsGFM {
wrapEl.appendChild(node.cloneNode(true));
const doc = DOMParser.fromSchema(schema.default).parse(wrapEl);
- const res = markdownSerializer.default.serialize(doc);
+ const res = markdownSerializer.default.serialize(doc, {
+ tightLists: true,
+ });
return res;
})
.catch(() => {});
diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
index 55c68139ded..b7200150925 100644
--- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
+++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import { parseBoolean } from '~/lib/utils/common_utils';
-import GfmAutoComplete from '~/gfm_auto_complete';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
export default function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
index 20c7fa8a9ab..9a2e2c03213 100644
--- a/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this */
import { Node } from 'tiptap';
+import { __ } from '~/locale';
// Transforms generated HTML back to GFM for Banzai::Filter::TableOfContentsFilter
export default class TableOfContents extends Node {
@@ -22,7 +23,7 @@ export default class TableOfContents extends Node {
priority: 51,
},
],
- toDOM: () => ['p', { class: 'table-of-contents' }, 'Table of Contents'],
+ toDOM: () => ['p', { class: 'table-of-contents' }, __('Table of Contents')],
};
}
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index fc9286d15e6..bfb073fdcdc 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -4,6 +4,7 @@ import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import highlightCurrentUser from './highlight_current_user';
import initUserPopovers from '../../user_popovers';
+import initMRPopovers from '../../mr_popover';
// Render GitLab flavoured Markdown
//
@@ -15,6 +16,7 @@ $.fn.renderGFM = function renderGFM() {
renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.gfm-project_member').get());
+ initMRPopovers(this.find('.gfm-merge_request').get());
return this;
};
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 35380ca49fb..d0b7f3ff7a2 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -1,4 +1,5 @@
import flash from '~/flash';
+import { sprintf, __ } from '../../locale';
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
@@ -14,6 +15,9 @@ import flash from '~/flash';
// </pre>
//
+// This is an arbitrary number; Can be iterated upon when suitable.
+const MAX_CHAR_LIMIT = 5000;
+
export default function renderMermaid($els) {
if (!$els.length) return;
@@ -34,6 +38,21 @@ export default function renderMermaid($els) {
$els.each((i, el) => {
const source = el.textContent;
+ /**
+ * Restrict the rendering to a certain amount of character to
+ * prevent mermaidjs from hanging up the entire thread and
+ * causing a DoS.
+ */
+ if (source && source.length > MAX_CHAR_LIMIT) {
+ el.textContent = sprintf(
+ __(
+ 'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
+ ),
+ { charLimit: MAX_CHAR_LIMIT },
+ );
+ return;
+ }
+
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index 7adccbb062f..35874140bf9 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -22,7 +22,7 @@ function MarkdownPreview() {}
// Minimum number of users referenced before triggering a warning
MarkdownPreview.prototype.referenceThreshold = 10;
-MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
+MarkdownPreview.prototype.emptyMessage = __('Nothing to preview.');
MarkdownPreview.prototype.ajaxCache = {};
@@ -40,7 +40,7 @@ MarkdownPreview.prototype.showPreview = function($form) {
preview.text(this.emptyMessage);
this.hideReferencedUsers($form);
} else {
- preview.addClass('md-preview-loading').text('Loading...');
+ preview.addClass('md-preview-loading').text(__('Loading...'));
this.fetchMarkdownPreview(
mdText,
url,
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index c1ea67f9293..530ab0bd4d9 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import '../commons/bootstrap';
import { isInIssuePage } from '../lib/utils/common_utils';
+import { __ } from '~/locale';
// Quick Submit behavior
//
@@ -65,7 +66,9 @@ $(document).on(
}
const $this = $(this);
- const title = isMac() ? 'You can also press &#8984;-Enter' : 'You can also press Ctrl-Enter';
+ const title = isMac()
+ ? __('You can also press &#8984;-Enter')
+ : __('You can also press Ctrl-Enter');
$this.tooltip({
container: 'body',
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 680f2031409..c8eb96a625c 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -37,7 +37,7 @@ export default class ShortcutsIssuable extends Shortcuts {
}
// Sanity check: Make sure the selected text comes from a discussion : it can either contain a message...
- let foundMessage = !!documentFragment.querySelector('.md, .wiki');
+ let foundMessage = Boolean(documentFragment.querySelector('.md'));
// ... Or come from a message
if (!foundMessage) {
@@ -46,7 +46,7 @@ export default class ShortcutsIssuable extends Shortcuts {
let node = e;
do {
// Text nodes don't define the `matches` method
- if (node.matches && node.matches('.md, .wiki')) {
+ if (node.matches && node.matches('.md')) {
foundMessage = true;
}
node = node.parentNode;
diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js
index b88e69a07bf..2e537d8c000 100644
--- a/app/assets/javascripts/blob/balsamiq_viewer.js
+++ b/app/assets/javascripts/blob/balsamiq_viewer.js
@@ -1,8 +1,9 @@
import Flash from '../flash';
import BalsamiqViewer from './balsamiq/balsamiq_viewer';
+import { __ } from '~/locale';
function onError() {
- const flash = new Flash('Balsamiq file could not be loaded.');
+ const flash = new Flash(__('Balsamiq file could not be loaded.'));
return flash;
}
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index cd3251ad1ca..9010cd0c3c1 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -5,6 +5,7 @@ import Dropzone from 'dropzone';
import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
+import { sprintf, __ } from '~/locale';
Dropzone.autoDiscover = false;
@@ -73,7 +74,7 @@ export default class BlobFileDropzone {
.html(errorMessage)
.text();
$('.dropzone-alerts')
- .html(`Error uploading file: "${stripped}"`)
+ .html(sprintf(__('Error uploading file: %{stripped}'), { stripped }))
.show();
this.removeFile(file);
},
@@ -84,7 +85,7 @@ export default class BlobFileDropzone {
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert
- alert('Please select a file');
+ alert(__('Please select a file'));
return false;
}
toggleLoading(submitButton, submitButtonLoadingIcon, true);
diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js
index 57c1baa9886..dbff03dc734 100644
--- a/app/assets/javascripts/blob/sketch/index.js
+++ b/app/assets/javascripts/blob/sketch/index.js
@@ -1,5 +1,6 @@
import JSZip from 'jszip';
import JSZipUtils from 'jszip-utils';
+import { __ } from '~/locale';
export default class SketchLoader {
constructor(container) {
@@ -56,10 +57,10 @@ export default class SketchLoader {
const errorMsg = document.createElement('p');
errorMsg.className = 'prepend-top-default append-bottom-default text-center';
- errorMsg.textContent = `
+ errorMsg.textContent = __(`
Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above.
- `;
+ `);
this.container.appendChild(errorMsg);
this.removeLoadingIcon();
diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
index 4718b642617..659d57e6a6f 100644
--- a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
+++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
@@ -1,11 +1,12 @@
import FileTemplateSelector from '../file_template_selector';
+import { __ } from '~/locale';
export default class DockerfileSelector extends FileTemplateSelector {
constructor({ mediator }) {
super(mediator);
this.config = {
key: 'dockerfile',
- name: 'Dockerfile',
+ name: __('Dockerfile'),
pattern: /(Dockerfile)/,
type: 'dockerfiles',
dropdown: '.js-dockerfile-selector',
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index d0359fc5fe9..d246a1f6064 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
+import { __ } from '~/locale';
export default class BlobViewer {
constructor() {
@@ -26,7 +27,7 @@ export default class BlobViewer {
promise
.then(module => module.default(viewer))
.catch(error => {
- Flash('Error loading file viewer.');
+ Flash(__('Error loading file viewer.'));
throw error;
});
@@ -106,16 +107,19 @@ export default class BlobViewer {
if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) {
- this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
+ this.copySourceBtn.setAttribute('title', __('Copy source to clipboard'));
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute(
'title',
- 'Wait for the source to load to copy it to the clipboard',
+ __('Wait for the source to load to copy it to the clipboard'),
);
this.copySourceBtn.classList.add('disabled');
} else {
- this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
+ this.copySourceBtn.setAttribute(
+ 'title',
+ __('Switch to the source to copy it to the clipboard'),
+ );
this.copySourceBtn.classList.add('disabled');
}
@@ -158,7 +162,7 @@ export default class BlobViewer {
this.toggleCopyButtonState();
})
- .catch(() => new Flash('Error loading viewer'));
+ .catch(() => new Flash(__('Error loading viewer')));
}
static loadViewer(viewerParam) {
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 5f64175362d..6aaf5bf7296 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -13,7 +13,7 @@ export default () => {
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
const assetsPath = editBlobForm.data('assetsPrefix');
- const filePath = editBlobForm.data('blobFilename');
+ const filePath = `${editBlobForm.data('blobFilename')}`;
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
const isMarkdown = editBlobForm.data('is-markdown');
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
new file mode 100644
index 00000000000..3178bda93b8
--- /dev/null
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -0,0 +1,7 @@
+export function getMilestone() {
+ return null;
+}
+
+export default {
+ getMilestone,
+};
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index fb6e5291a61..45b9e57f9ab 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -54,7 +54,10 @@ export default Vue.extend({
return `${n__('%d issue', '%d issues', issuesSize)}`;
},
isNewIssueShown() {
- return this.list.type === 'backlog' || (!this.disabled && this.list.type !== 'closed');
+ return (
+ this.list.type === 'backlog' ||
+ (!this.disabled && this.list.type !== 'closed' && this.list.type !== 'blank')
+ );
},
},
watch: {
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index 667eea17d44..1cbd31729cd 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,6 +1,5 @@
<script>
/* global ListLabel */
-import _ from 'underscore';
import Cookies from 'js-cookie';
import boardsStore from '../stores/boards_store';
@@ -29,8 +28,6 @@ export default {
});
});
- boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
-
// Save the labels
gl.boardService
.generateDefaultLists()
@@ -60,11 +57,15 @@ export default {
</script>
<template>
- <div class="board-blank-state">
+ <div class="board-blank-state p-3">
<p>Add the following default lists to your Issue Board with one click:</p>
- <ul class="board-blank-state-list">
+ <ul class="list-unstyled board-blank-state-list">
<li v-for="(label, index) in predefinedLabels" :key="index">
- <span :style="{ backgroundColor: label.color }" class="label-color"> </span>
+ <span
+ :style="{ backgroundColor: label.color }"
+ class="label-color position-relative d-inline-block rounded"
+ >
+ </span>
{{ label.title }}
</li>
</ul>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index f569322ab70..179148b6887 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -66,7 +66,7 @@ export default {
eventHub.$emit('clearDetailIssue');
} else {
eventHub.$emit('newDetailIssue', this.issue);
- boardsStore.detail.list = this.list;
+ boardsStore.setListDetail(this.list);
}
}
},
@@ -83,7 +83,7 @@ export default {
}"
:index="index"
:data-issue-id="issue.id"
- class="board-card"
+ class="board-card p-3 rounded"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)"
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index a5f9d65e4d5..a06db359c94 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import { __ } from '~/locale';
export default Vue.extend({
props: {
@@ -13,7 +14,7 @@ export default Vue.extend({
$(this.$el).tooltip('hide');
// eslint-disable-next-line no-alert
- if (window.confirm('Are you sure you want to delete this list?')) {
+ if (window.confirm(__('Are you sure you want to delete this list?'))) {
this.list.destroy();
}
},
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index f3f341ece5c..b1a8b13f3ac 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -142,8 +142,10 @@ export default {
const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
- boardsStore.moving.list = card.list;
- boardsStore.moving.issue = boardsStore.moving.list.findIssue(+e.item.dataset.issueId);
+
+ const { list } = card;
+ const issue = list.findIssue(Number(e.item.dataset.issueId));
+ boardsStore.startMoving(list, issue);
sortableStart();
},
@@ -221,7 +223,10 @@ export default {
</script>
<template>
- <div class="board-list-component">
+ <div
+ :class="{ 'd-none': !list.isExpanded, 'd-flex flex-column': list.isExpanded }"
+ class="board-list-component position-relative h-100"
+ >
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
<gl-loading-icon />
</div>
@@ -236,7 +241,7 @@ export default {
:data-board="list.id"
:data-board-type="list.type"
:class="{ 'is-smaller': showIssueForm }"
- class="board-list js-board-list"
+ class="board-list w-100 h-100 list-unstyled mb-0 p-1 js-board-list"
>
<board-card
v-for="(issue, index) in issues"
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 28d96dab605..cc6af8e88cd 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
import { GlButton } from '@gitlab/ui';
+import { getMilestone } from 'ee_else_ce/boards/boards_util';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
@@ -51,11 +52,14 @@ export default {
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
+ const milestone = getMilestone(this.list);
+
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true,
assignees,
+ milestone,
project_id: this.selectedProject.id,
});
@@ -68,8 +72,8 @@ export default {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- boardsStore.detail.issue = issue;
- boardsStore.detail.list = this.list;
+ boardsStore.setIssueDetail(issue);
+ boardsStore.setListDetail(this.list);
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
@@ -95,7 +99,7 @@ export default {
<template>
<div class="board-new-issue-form">
- <div class="board-card">
+ <div class="board-card position-relative p-3 rounded">
<form @submit="submit($event)">
<div v-if="error" class="flash-container">
<div class="flash-alert">An error occurred. Please try again.</div>
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index e637e1f1223..c587b276fa3 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -2,19 +2,21 @@
import $ from 'jquery';
import Vue from 'vue';
-import Flash from '../../flash';
-import { sprintf, __ } from '../../locale';
-import Sidebar from '../../right_sidebar';
-import eventHub from '../../sidebar/event_hub';
-import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
-import Assignees from '../../sidebar/components/assignees/assignees.vue';
-import DueDateSelectors from '../../due_date_select';
+import Flash from '~/flash';
+import { sprintf, __ } from '~/locale';
+import Sidebar from '~/right_sidebar';
+import eventHub from '~/sidebar/event_hub';
+import DueDateSelectors from '~/due_date_select';
+import IssuableContext from '~/issuable_context';
+import LabelsSelect from '~/labels_select';
+import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
+import Assignees from '~/sidebar/components/assignees/assignees.vue';
+import Subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
+import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
+import MilestoneSelect from '~/milestone_select';
import RemoveBtn from './sidebar/remove_issue.vue';
-import IssuableContext from '../../issuable_context';
-import LabelsSelect from '../../labels_select';
-import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
-import MilestoneSelect from '../../milestone_select';
import boardsStore from '../stores/boards_store';
+import { isScopedLabel } from '~/lib/utils/common_utils';
export default Vue.extend({
components: {
@@ -22,6 +24,7 @@ export default Vue.extend({
Assignees,
RemoveBtn,
Subscriptions,
+ TimeTracker,
},
props: {
currentUser: {
@@ -42,7 +45,7 @@ export default Vue.extend({
return Object.keys(this.issue).length;
},
milestoneTitle() {
- return this.issue.milestone ? this.issue.milestone.title : 'No Milestone';
+ return this.issue.milestone ? this.issue.milestone.title : __('No Milestone');
},
canRemove() {
return !this.list.preset;
@@ -138,5 +141,11 @@ export default Vue.extend({
Flash(__('An error occurred while saving assignees'));
});
},
+ showScopedLabels(label) {
+ return boardsStore.scopedLabels.enabled && isScopedLabel(label);
+ },
+ helpLink() {
+ return boardsStore.scopedLabels.helpLink;
+ },
},
});
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 90ab3a76342..a8516f178fc 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,13 +1,16 @@
<script>
+import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import eventHub from '../eventhub';
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
+import IssueCardInnerScopedLabel from './issue_card_inner_scoped_label.vue';
+import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
components: {
@@ -16,10 +19,13 @@ export default {
TooltipOnTruncate,
IssueDueDate,
IssueTimeEstimate,
+ IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
+ IssueCardInnerScopedLabel,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [issueCardInner],
props: {
issue: {
type: Object,
@@ -92,6 +98,12 @@ export default {
const { referencePath, groupId } = this.issue;
return !groupId ? referencePath.split('#')[0] : null;
},
+ orderedLabels() {
+ return _.sortBy(this.issue.labels, 'title');
+ },
+ helpLink() {
+ return boardsStore.scopedLabels.helpLink;
+ },
},
methods: {
isIndexLessThanlimit(index) {
@@ -123,31 +135,7 @@ export default {
const labelTitle = encodeURIComponent(label.title);
const filter = `label_name[]=${labelTitle}`;
- this.applyFilter(filter);
- },
- filterByWeight(weight) {
- if (!this.updateFilters) return;
-
- const issueWeight = encodeURIComponent(weight);
- const filter = `weight=${issueWeight}`;
-
- this.applyFilter(filter);
- },
- applyFilter(filter) {
- const filterPath = boardsStore.filter.path.split('&');
- const filterIndex = filterPath.indexOf(filter);
-
- if (filterIndex === -1) {
- filterPath.push(filter);
- } else {
- filterPath.splice(filterIndex, 1);
- }
-
- boardsStore.filter.path = filterPath.join('&');
-
- boardsStore.updateFiltersUrl();
-
- eventHub.$emit('updateTokens');
+ boardsStore.toggleFilter(filter);
},
labelStyle(label) {
return {
@@ -155,12 +143,15 @@ export default {
color: label.textColor,
};
},
+ showScopedLabel(label) {
+ return boardsStore.scopedLabels.enabled && isScopedLabel(label);
+ },
},
};
</script>
<template>
<div>
- <div class="board-card-header">
+ <div class="d-flex board-card-header" dir="auto">
<h4 class="board-card-title append-bottom-0 prepend-top-0">
<icon
v-if="issue.confidential"
@@ -175,27 +166,37 @@ export default {
</h4>
</div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
- <button
- v-for="label in issue.labels"
- v-if="showLabel(label)"
- :key="label.id"
- v-gl-tooltip
- :style="labelStyle(label)"
- :title="label.description"
- class="badge color-label append-right-4 prepend-top-4"
- type="button"
- @click="filterByLabel(label)"
- >
- {{ label.title }}
- </button>
+ <template v-for="label in orderedLabels" v-if="showLabel(label)">
+ <issue-card-inner-scoped-label
+ v-if="showScopedLabel(label)"
+ :key="label.id"
+ :label="label"
+ :label-style="labelStyle(label)"
+ :scoped-labels-documentation-link="helpLink"
+ @scoped-label-click="filterByLabel($event)"
+ />
+
+ <button
+ v-else
+ :key="label.id"
+ v-gl-tooltip
+ :style="labelStyle(label)"
+ :title="label.description"
+ class="badge color-label append-right-4 prepend-top-4"
+ type="button"
+ @click="filterByLabel(label)"
+ >
+ {{ label.title }}
+ </button>
+ </template>
</div>
<div class="board-card-footer d-flex justify-content-between align-items-end">
<div
- class="d-flex align-items-start flex-wrap-reverse board-card-number-container js-board-card-number-container"
+ class="d-flex align-items-start flex-wrap-reverse board-card-number-container overflow-hidden js-board-card-number-container"
>
<span
v-if="issue.referencePath"
- class="board-card-number d-flex append-right-8 prepend-top-8"
+ class="board-card-number overflow-hidden d-flex append-right-8 prepend-top-8"
>
<tooltip-on-truncate
v-if="issueReferencePath"
@@ -209,10 +210,14 @@ export default {
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" /><issue-time-estimate
v-if="issue.timeEstimate"
:estimate="issue.timeEstimate"
+ /><issue-card-weight
+ v-if="issue.weight"
+ :weight="issue.weight"
+ @click="filterByWeight(issue.weight)"
/>
</span>
</div>
- <div class="board-card-assignee">
+ <div class="board-card-assignee d-flex">
<user-avatar-link
v-for="(assignee, index) in issue.assignees"
v-if="shouldRenderAssignee(index)"
diff --git a/app/assets/javascripts/boards/components/issue_card_inner_scoped_label.vue b/app/assets/javascripts/boards/components/issue_card_inner_scoped_label.vue
new file mode 100644
index 00000000000..fa4c68964cb
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issue_card_inner_scoped_label.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlLink, GlTooltip } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlTooltip,
+ GlLink,
+ },
+ props: {
+ label: {
+ type: Object,
+ required: true,
+ },
+ labelStyle: {
+ type: Object,
+ required: true,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <span
+ class="d-inline-block position-relative scoped-label-wrapper append-right-4 prepend-top-4 board-label"
+ >
+ <a @click="$emit('scoped-label-click', label)">
+ <span :ref="'labelTitleRef'" :style="labelStyle" class="badge label color-label">
+ {{ label.title }}
+ </span>
+ <gl-tooltip :target="() => $refs.labelTitleRef" placement="top" boundary="viewport">
+ <span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
+ ><br />
+ {{ label.description }}
+ </gl-tooltip>
+ </a>
+
+ <gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
+ ><i class="fa fa-question-circle" :style="labelStyle"></i
+ ></gl-link>
+ </span>
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 9c4c6632976..3bc7f13a9e6 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -53,7 +53,7 @@ export default {
} else if (timeDifference === -1) {
return __('Yesterday');
} else if (timeDifference > 0 && timeDifference < 7) {
- return dateFormat(issueDueDate, 'dddd', true);
+ return dateFormat(issueDueDate, 'dddd');
}
return standardDateFormat;
@@ -82,7 +82,11 @@ export default {
<template>
<span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
- <icon :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }" name="calendar" />
+ <icon
+ :class="{ 'text-danger': isPastDue }"
+ class="board-card-info-icon align-top"
+ name="calendar"
+ />
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index 5acc3025b2c..98c1d29db16 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -28,7 +28,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
- <icon name="hourglass" css-classes="board-card-info-icon" /><time
+ <icon name="hourglass" css-classes="board-card-info-icon align-top" /><time
class="board-card-info-text"
>{{ timeEstimate }}</time
>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 2a0008467c4..091700de93f 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -42,8 +42,8 @@ export default {
</script>
<template>
- <section class="empty-state">
- <div class="row">
+ <section class="empty-state d-flex mt-0 h-100">
+ <div class="row w-100 my-auto mx-0">
<div class="col-12 col-md-6 order-md-last">
<aside class="svg-content d-none d-md-block"><img :src="emptyStateSvg" /></aside>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 1f0961e02d8..1cfa6d39362 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -50,8 +50,8 @@ export default {
</script>
<template>
<div>
- <header class="add-issues-header form-actions">
- <h2>
+ <header class="add-issues-header border-top-0 form-actions">
+ <h2 class="m-0">
Add issues
<button
type="button"
@@ -65,7 +65,7 @@ export default {
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0" />
- <div v-if="showSearch" class="add-issues-search append-bottom-10">
+ <div v-if="showSearch" class="d-flex append-bottom-10">
<modal-filters :store="filter" />
<button
ref="selectAllBtn"
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 1e5761cf268..defa1f75ba2 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -124,7 +124,7 @@ export default {
data.issues.forEach(issueObj => {
const issue = new ListIssue(issueObj);
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
- issue.selected = !!foundSelectedIssue;
+ issue.selected = Boolean(foundSelectedIssue);
this.issues.push(issue);
});
@@ -143,8 +143,11 @@ export default {
};
</script>
<template>
- <div v-if="showAddIssuesModal" class="add-issues-modal">
- <div class="add-issues-container">
+ <div
+ v-if="showAddIssuesModal"
+ class="add-issues-modal d-flex position-fixed position-top-0 position-bottom-0 position-left-0 position-right-0 h-100"
+ >
+ <div class="add-issues-container d-flex flex-column m-auto rounded">
<modal-header
:project-id="projectId"
:milestone-path="milestonePath"
@@ -161,8 +164,10 @@ export default {
:new-issue-path="newIssuePath"
:empty-state-svg="emptyStateSvg"
/>
- <section v-if="loading || filterLoading" class="add-issues-list text-center">
- <div class="add-issues-list-loading"><gl-loading-icon /></div>
+ <section v-if="loading || filterLoading" class="add-issues-list d-flex h-100 text-center">
+ <div class="add-issues-list-loading w-100 align-self-center">
+ <gl-loading-icon size="md" />
+ </div>
</section>
<modal-footer />
</div>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index e9ed2de859d..28d2019af2f 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -117,7 +117,7 @@ export default {
};
</script>
<template>
- <section ref="list" class="add-issues-list add-issues-list-columns">
+ <section ref="list" class="add-issues-list add-issues-list-columns d-flex h-100">
<div
v-if="issuesCount > 0 && issues.length === 0"
class="empty-state add-issues-empty-state-filter text-center"
@@ -129,7 +129,7 @@ export default {
<div v-for="issue in group" v-if="showIssue(issue)" :key="issue.id" class="board-card-parent">
<div
:class="{ 'is-active': issue.selected }"
- class="board-card"
+ class="board-card position-relative p-3 rounded"
@click="toggleIssue($event, issue)"
>
<issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 10577da9305..c8a9cb1c296 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -2,13 +2,16 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
-import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
$(document)
.off('created.label')
- .on('created.label', (e, label) => {
+ .on('created.label', (e, label, addNewList) => {
+ if (!addNewList) {
+ return;
+ }
+
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
@@ -74,8 +77,6 @@ export default function initNewListDropdown() {
color: label.color,
},
});
-
- boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index a2b8a0af236..4ab2b17301f 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -48,7 +48,7 @@ export default Vue.extend({
list.removeIssue(issue);
});
- boardsStore.detail.issue = {};
+ boardsStore.clearDetailIssue();
},
/**
* Build the default patch request.
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index c14d69c5d18..6b54e8baefb 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,6 +1,8 @@
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
+import { isEE } from '~/lib/utils/common_utils';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@@ -8,6 +10,8 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
page: 'boards',
isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
+ isGroup: isEE(),
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
this.store = store;
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index f88e9b55988..f2f37d22b97 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,11 +1,10 @@
import $ from 'jquery';
-import _ from 'underscore';
import Vue from 'vue';
import Flash from '~/flash';
import { __ } from '~/locale';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import './models/label';
+import './models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
@@ -24,7 +23,11 @@ import BoardSidebar from './components/board_sidebar';
import initNewListDropdown from './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
-import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
+import {
+ NavigationType,
+ convertObjectPropsToCamelCase,
+ parseBoolean,
+} from '~/lib/utils/common_utils';
let issueBoardsApp;
@@ -58,6 +61,7 @@ export default () => {
state: boardsStore.state,
loading: true,
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
+ recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
listsEndpoint: $boardApp.dataset.listsEndpoint,
boardId: $boardApp.dataset.boardId,
disabled: parseBoolean($boardApp.dataset.disabled),
@@ -75,6 +79,7 @@ export default () => {
created() {
gl.boardService = new BoardService({
boardsEndpoint: this.boardsEndpoint,
+ recentBoardsEndpoint: this.recentBoardsEndpoint,
listsEndpoint: this.listsEndpoint,
bulkUpdatePath: this.bulkUpdatePath,
boardId: this.boardId,
@@ -100,24 +105,29 @@ export default () => {
gl.boardService
.all()
.then(res => res.data)
- .then(data => {
- data.forEach(board => {
- const list = boardsStore.addList(board, this.defaultAvatar);
-
- if (list.type === 'closed') {
- list.position = Infinity;
- } else if (list.type === 'backlog') {
- list.position = -1;
+ .then(lists => {
+ lists.forEach(listObj => {
+ let { position } = listObj;
+ if (listObj.list_type === 'closed') {
+ position = Infinity;
+ } else if (listObj.list_type === 'backlog') {
+ position = -1;
}
- });
- this.state.lists = _.sortBy(this.state.lists, 'position');
+ boardsStore.addList(
+ {
+ ...listObj,
+ position,
+ },
+ this.defaultAvatar,
+ );
+ });
boardsStore.addBlankState();
this.loading = false;
})
.catch(() => {
- Flash('An error occurred while fetching the board lists. Please try again.');
+ Flash(__('An error occurred while fetching the board lists. Please try again.'));
});
},
methods: {
@@ -131,9 +141,25 @@ export default () => {
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.data)
.then(data => {
+ const {
+ subscribed,
+ totalTimeSpent,
+ timeEstimate,
+ humanTimeEstimate,
+ humanTotalTimeSpent,
+ weight,
+ epic,
+ } = convertObjectPropsToCamelCase(data);
+
newIssue.setFetchingState('subscriptions', false);
newIssue.updateData({
- subscribed: data.subscribed,
+ humanTimeSpent: humanTotalTimeSpent,
+ timeSpent: totalTimeSpent,
+ humanTimeEstimate,
+ timeEstimate,
+ subscribed,
+ weight,
+ epic,
});
})
.catch(() => {
@@ -142,10 +168,10 @@ export default () => {
});
}
- boardsStore.detail.issue = newIssue;
+ boardsStore.setIssueDetail(newIssue);
},
clearDetailIssue() {
- boardsStore.detail.issue = {};
+ boardsStore.clearDetailIssue();
},
toggleSubscription(id) {
const { issue } = boardsStore.detail;
@@ -201,7 +227,7 @@ export default () => {
},
tooltipTitle() {
if (this.disabled) {
- return 'Please add a list to your board first';
+ return __('Please add a list to your board first');
}
return '';
diff --git a/app/assets/javascripts/boards/mixins/issue_card_inner.js b/app/assets/javascripts/boards/mixins/issue_card_inner.js
new file mode 100644
index 00000000000..8000237da6d
--- /dev/null
+++ b/app/assets/javascripts/boards/mixins/issue_card_inner.js
@@ -0,0 +1,5 @@
+export default {
+ methods: {
+ filterByWeight() {},
+ },
+};
diff --git a/app/assets/javascripts/vue_shared/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js
index 4a29b0d0581..4a29b0d0581 100644
--- a/app/assets/javascripts/vue_shared/models/assignee.js
+++ b/app/assets/javascripts/boards/models/assignee.js
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index dd92d3c8552..f858b162c6b 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -4,7 +4,8 @@
/* global ListAssignee */
import Vue from 'vue';
-import '~/vue_shared/models/label';
+import './label';
+import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
@@ -28,7 +29,6 @@ class ListIssue {
this.referencePath = obj.reference_path;
this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
- this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
@@ -39,6 +39,7 @@ class ListIssue {
if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
+ this.milestone_id = obj.milestone.id;
}
obj.labels.forEach(label => {
@@ -88,6 +89,19 @@ class ListIssue {
this.assignees = [];
}
+ addMilestone(milestone) {
+ const miletoneId = this.milestone ? this.milestone.id : null;
+ if (isEE && milestone.id !== miletoneId) {
+ this.milestone = new ListMilestone(milestone);
+ }
+ }
+
+ removeMilestone(removeMilestone) {
+ if (isEE && removeMilestone && removeMilestone.id === this.milestone.id) {
+ this.milestone = {};
+ }
+ }
+
getLists() {
return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
@@ -119,7 +133,17 @@ class ListIssue {
}
const projectPath = this.project ? this.project.path : '';
- return Vue.http.patch(`${this.path}.json`, data);
+ return Vue.http.patch(`${this.path}.json`, data).then(({ body = {} } = {}) => {
+ /**
+ * Since post implementation of Scoped labels, server can reject
+ * same key-ed labels. To keep the UI and server Model consistent,
+ * we're just assigning labels that server echo's back to us when we
+ * PATCH the said object.
+ */
+ if (body) {
+ this.labels = convertObjectPropsToCamelCase(body.labels, { deep: true });
+ }
+ });
}
}
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
new file mode 100644
index 00000000000..cd2a2c0137f
--- /dev/null
+++ b/app/assets/javascripts/boards/models/label.js
@@ -0,0 +1,11 @@
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export default class ListLabel {
+ constructor(obj) {
+ Object.assign(this, convertObjectPropsToCamelCase(obj, { dropKeys: ['priority'] }), {
+ priority: obj.priority !== null ? obj.priority : Infinity,
+ });
+ }
+}
+
+window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 9f6d9a853da..a9d88f19146 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -2,10 +2,11 @@
/* global ListIssue */
import { __ } from '~/locale';
-import ListLabel from '~/vue_shared/models/label';
-import ListAssignee from '~/vue_shared/models/assignee';
-import { urlParamsToObject } from '~/lib/utils/common_utils';
+import ListLabel from './label';
+import ListAssignee from './assignee';
+import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
+import ListMilestone from './milestone';
const PER_PAGE = 20;
@@ -36,8 +37,8 @@ class List {
this.type = obj.list_type;
const typeInfo = this.getTypeInfo(this.type);
- this.preset = !!typeInfo.isPreset;
- this.isExpandable = !!typeInfo.isExpandable;
+ this.preset = Boolean(typeInfo.isPreset);
+ this.isExpandable = Boolean(typeInfo.isExpandable);
this.isExpanded = true;
this.page = 1;
this.loading = true;
@@ -51,6 +52,9 @@ class List {
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
+ } else if (isEE && obj.milestone) {
+ this.milestone = new ListMilestone(obj.milestone);
+ this.title = this.milestone.title;
}
if (!typeInfo.isBlank && this.id) {
@@ -69,12 +73,14 @@ class List {
}
save() {
- const entity = this.label || this.assignee;
+ const entity = this.label || this.assignee || this.milestone;
let entityType = '';
if (this.label) {
entityType = 'label_id';
- } else {
+ } else if (this.assignee) {
entityType = 'assignee_id';
+ } else if (isEE && this.milestone) {
+ entityType = 'milestone_id';
}
return gl.boardService
@@ -84,6 +90,7 @@ class List {
this.id = data.id;
this.type = data.list_type;
this.position = data.position;
+ this.label = data.label;
return this.getIssues();
});
@@ -192,6 +199,13 @@ class List {
issue.addAssignee(this.assignee);
}
+ if (isEE && this.milestone) {
+ if (listFrom && listFrom.type === 'milestone') {
+ issue.removeMilestone(listFrom.milestone);
+ }
+ issue.addMilestone(this.milestone);
+ }
+
if (listFrom) {
this.issuesSize += 1;
diff --git a/app/assets/javascripts/boards/models/milestone.js b/app/assets/javascripts/boards/models/milestone.js
index 17d15278a74..6f81d6bc6f8 100644
--- a/app/assets/javascripts/boards/models/milestone.js
+++ b/app/assets/javascripts/boards/models/milestone.js
@@ -1,7 +1,16 @@
-class ListMilestone {
+import { isEE } from '~/lib/utils/common_utils';
+
+export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
+
+ if (isEE) {
+ this.path = obj.path;
+ this.state = obj.state;
+ this.webUrl = obj.web_url || obj.webUrl;
+ this.description = obj.description;
+ }
}
}
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 3de6eb056c2..7d463f17ab1 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -2,12 +2,13 @@ import axios from '../../lib/utils/axios_utils';
import { mergeUrlParams } from '../../lib/utils/url_utility';
export default class BoardService {
- constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
+ constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
this.boardsEndpoint = boardsEndpoint;
this.boardId = boardId;
this.listsEndpoint = listsEndpoint;
this.listsEndpointGenerate = `${listsEndpoint}/generate.json`;
this.bulkUpdatePath = bulkUpdatePath;
+ this.recentBoardsEndpoint = `${recentBoardsEndpoint}.json`;
}
generateBoardsPath(id) {
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
new file mode 100644
index 00000000000..da82b52330a
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -0,0 +1,65 @@
+const notImplemented = () => {
+ throw new Error('Not implemented!');
+};
+
+export default {
+ setEndpoints: () => {
+ notImplemented();
+ },
+
+ fetchLists: () => {
+ notImplemented();
+ },
+
+ generateDefaultLists: () => {
+ notImplemented();
+ },
+
+ createList: () => {
+ notImplemented();
+ },
+
+ updateList: () => {
+ notImplemented();
+ },
+
+ deleteList: () => {
+ notImplemented();
+ },
+
+ fetchIssuesForList: () => {
+ notImplemented();
+ },
+
+ moveIssue: () => {
+ notImplemented();
+ },
+
+ createNewIssue: () => {
+ notImplemented();
+ },
+
+ fetchBacklog: () => {
+ notImplemented();
+ },
+
+ bulkUpdateIssues: () => {
+ notImplemented();
+ },
+
+ fetchIssue: () => {
+ notImplemented();
+ },
+
+ toggleIssueSubscription: () => {
+ notImplemented();
+ },
+
+ showPage: () => {
+ notImplemented();
+ },
+
+ toggleEmptyState: () => {
+ notImplemented();
+ },
+};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 802796208c2..4b3b44574a8 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -5,14 +5,27 @@ import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import Cookies from 'js-cookie';
+import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
+import eventHub from '../eventhub';
const boardsStore = {
disabled: false,
+ scopedLabels: {
+ helpLink: '',
+ enabled: false,
+ },
filter: {
path: '',
},
- state: {},
+ state: {
+ currentBoard: {
+ labels: [],
+ },
+ currentPage: '',
+ reload: false,
+ },
detail: {
issue: {},
},
@@ -27,9 +40,13 @@ const boardsStore = {
issue: {},
};
},
+ showPage(page) {
+ this.state.reload = false;
+ this.state.currentPage = page;
+ },
addList(listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);
- this.state.lists.push(list);
+ this.state.lists = _.sortBy([...this.state.lists, list], 'position');
return list;
},
@@ -63,11 +80,9 @@ const boardsStore = {
this.addList({
id: 'blank',
list_type: 'blank',
- title: 'Welcome to your Issue Board!',
+ title: __('Welcome to your Issue Board!'),
position: 0,
});
-
- this.state.lists = _.sortBy(this.state.lists, 'position');
},
removeBlankState() {
this.removeList('blank');
@@ -95,6 +110,11 @@ const boardsStore = {
});
listFrom.update();
},
+
+ startMoving(list, issue) {
+ Object.assign(this.moving, { list, issue });
+ },
+
moveIssueToList(listFrom, listTo, issue, newIndex) {
const issueTo = listTo.findIssue(issue.id);
const issueLists = issue.getLists();
@@ -169,11 +189,43 @@ const boardsStore = {
findListByLabelId(id) {
return this.state.lists.find(list => list.type === 'label' && list.label.id === id);
},
+
+ toggleFilter(filter) {
+ const filterPath = this.filter.path.split('&');
+ const filterIndex = filterPath.indexOf(filter);
+
+ if (filterIndex === -1) {
+ filterPath.push(filter);
+ } else {
+ filterPath.splice(filterIndex, 1);
+ }
+
+ this.filter.path = filterPath.join('&');
+
+ this.updateFiltersUrl();
+
+ eventHub.$emit('updateTokens');
+ },
+
+ setListDetail(newList) {
+ this.detail.list = newList;
+ },
+
updateFiltersUrl() {
window.history.pushState(null, null, `?${this.filter.path}`);
},
+
+ clearDetailIssue() {
+ this.setIssueDetail({});
+ },
+
+ setIssueDetail(issueDetail) {
+ this.detail.issue = issueDetail;
+ },
};
+BoardsStoreEE.initEESpecific(boardsStore);
+
// hacks added in order to allow milestone_select to function properly
// TODO: remove these
diff --git a/app/assets/javascripts/boards/stores/boards_store_ee.js b/app/assets/javascripts/boards/stores/boards_store_ee.js
new file mode 100644
index 00000000000..09e3a938fbe
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/boards_store_ee.js
@@ -0,0 +1,5 @@
+// this is just to make ee_else_ce happy and will be cleaned up in https://gitlab.com/gitlab-org/gitlab-ce/issues/59807
+
+export default {
+ initEESpecific() {},
+};
diff --git a/app/assets/javascripts/boards/stores/index.js b/app/assets/javascripts/boards/stores/index.js
new file mode 100644
index 00000000000..f70395a3771
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from 'ee_else_ce/boards/stores/state';
+import actions from 'ee_else_ce/boards/stores/actions';
+import mutations from 'ee_else_ce/boards/stores/mutations';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ state,
+ actions,
+ mutations,
+ });
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
new file mode 100644
index 00000000000..fcdfa6799b6
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -0,0 +1,21 @@
+export const SET_ENDPOINTS = 'SET_ENDPOINTS';
+export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
+export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
+export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
+export const REQUEST_UPDATE_LIST = 'REQUEST_UPDATE_LIST';
+export const RECEIVE_UPDATE_LIST_SUCCESS = 'RECEIVE_UPDATE_LIST_SUCCESS';
+export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR';
+export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST';
+export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS';
+export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR';
+export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
+export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
+export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
+export const REQUEST_MOVE_ISSUE = 'REQUEST_MOVE_ISSUE';
+export const RECEIVE_MOVE_ISSUE_SUCCESS = 'RECEIVE_MOVE_ISSUE_SUCCESS';
+export const RECEIVE_MOVE_ISSUE_ERROR = 'RECEIVE_MOVE_ISSUE_ERROR';
+export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
+export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
+export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
+export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
+export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
new file mode 100644
index 00000000000..77ba68be07e
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -0,0 +1,91 @@
+import * as mutationTypes from './mutation_types';
+
+const notImplemented = () => {
+ throw new Error('Not implemented!');
+};
+
+export default {
+ [mutationTypes.SET_ENDPOINTS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_ADD_LIST]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_UPDATE_LIST]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_UPDATE_LIST_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_UPDATE_LIST_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_REMOVE_LIST]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_REMOVE_LIST_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_REMOVE_LIST_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_ADD_ISSUE]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_ADD_ISSUE_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_ADD_ISSUE_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_MOVE_ISSUE]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_MOVE_ISSUE_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_MOVE_ISSUE_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_UPDATE_ISSUE_SUCCESS]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.RECEIVE_UPDATE_ISSUE_ERROR]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.SET_CURRENT_PAGE]: () => {
+ notImplemented();
+ },
+
+ [mutationTypes.TOGGLE_EMPTY_STATE]: () => {
+ notImplemented();
+ },
+};
diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js
new file mode 100644
index 00000000000..dd16abb01a5
--- /dev/null
+++ b/app/assets/javascripts/boards/stores/state.js
@@ -0,0 +1,3 @@
+export default () => ({
+ // ...
+});
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js
index f34496f84c6..f4c3fa185d8 100644
--- a/app/assets/javascripts/branches/branches_delete_modal.js
+++ b/app/assets/javascripts/branches/branches_delete_modal.js
@@ -23,7 +23,7 @@ class DeleteModal {
const branchData = e.currentTarget.dataset;
this.branchName = branchData.branchName || '';
this.deletePath = branchData.deletePath || '';
- this.isMerged = !!branchData.isMerged;
+ this.isMerged = Boolean(branchData.isMerged);
this.updateModal();
}
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 7951348d8b2..93aacba0e8e 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -14,6 +14,9 @@ const BreakpointInstance = {
return breakpoint;
},
+ isDesktop() {
+ return ['lg', 'md'].includes(this.getBreakpointSize());
+ },
};
export default BreakpointInstance;
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 592e1fd1c31..0bba2a2e160 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -27,15 +27,24 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList {
- constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
+ constructor({
+ container,
+ saveButton,
+ errorBox,
+ formField = 'variables',
+ saveEndpoint,
+ maskableRegex,
+ }) {
this.container = container;
this.saveButton = saveButton;
this.errorBox = errorBox;
this.saveEndpoint = saveEndpoint;
+ this.maskableRegex = maskableRegex;
this.variableList = new VariableList({
container: this.container,
formField,
+ maskableRegex,
});
this.bindEvents();
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 5b20fa141cd..0303e4e51dd 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -16,9 +16,10 @@ function createEnvironmentItem(value) {
}
export default class VariableList {
- constructor({ container, formField }) {
+ constructor({ container, formField, maskableRegex }) {
this.$container = $(container);
this.formField = formField;
+ this.maskableRegex = new RegExp(maskableRegex);
this.environmentDropdownMap = new WeakMap();
this.inputMap = {
@@ -26,6 +27,10 @@ export default class VariableList {
selector: '.js-ci-variable-input-id',
default: '',
},
+ variable_type: {
+ selector: '.js-ci-variable-input-variable-type',
+ default: 'env_var',
+ },
key: {
selector: '.js-ci-variable-input-key',
default: '',
@@ -40,6 +45,12 @@ export default class VariableList {
// converted. we need the value as a string.
default: $('.js-ci-variable-input-protected').attr('data-default'),
},
+ masked: {
+ selector: '.js-ci-variable-input-masked',
+ // use `attr` instead of `data` as we don't want the value to be
+ // converted. we need the value as a string.
+ default: $('.js-ci-variable-input-masked').attr('data-default'),
+ },
environment_scope: {
// We can't use a `.js-` class here because
// gl_dropdown replaces the <input> and doesn't copy over the class
@@ -88,13 +99,16 @@ export default class VariableList {
}
});
- // Always make sure there is an empty last row
- this.$container.on('input trigger-change', inputSelector, () => {
+ this.$container.on('input trigger-change', inputSelector, e => {
+ // Always make sure there is an empty last row
const $lastRow = this.$container.find('.js-row').last();
if (this.checkIfRowTouched($lastRow)) {
this.insertRow($lastRow);
}
+
+ // If masked, validate value against regex
+ this.validateMaskability($(e.currentTarget).closest('.js-row'));
});
}
@@ -171,12 +185,32 @@ export default class VariableList {
checkIfRowTouched($row) {
return Object.keys(this.inputMap).some(name => {
+ // Row should not qualify as touched if only switches have been touched
+ if (['protected', 'masked'].includes(name)) return false;
+
const entry = this.inputMap[name];
const $el = $row.find(entry.selector);
return $el.length && $el.val() !== entry.default;
});
}
+ validateMaskability($row) {
+ const invalidInputClass = 'gl-field-error-outline';
+
+ const variableValue = $row.find(this.inputMap.secret_value.selector).val();
+ const isValueMaskable = this.maskableRegex.test(variableValue) || variableValue === '';
+ const isMaskedChecked = $row.find(this.inputMap.masked.selector).val() === 'true';
+
+ // Show a validation error if the user wants to mask an unmaskable variable value
+ $row
+ .find(this.inputMap.secret_value.selector)
+ .toggleClass(invalidInputClass, isMaskedChecked && !isValueMaskable);
+ $row
+ .find('.js-secret-value-placeholder')
+ .toggleClass(invalidInputClass, isMaskedChecked && !isValueMaskable);
+ $row.find('.masking-validation-error').toggle(isMaskedChecked && !isValueMaskable);
+ }
+
toggleEnableRow(isEnabled = true) {
this.$container.find(this.inputMap.key.selector).attr('disabled', !isEnabled);
this.$container.find('.js-row-remove-button').attr('disabled', !isEnabled);
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
index e7111c666a2..fdbefd8c313 100644
--- a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -19,6 +19,7 @@ export default function setupNativeFormVariableList({ container, formField = 'va
const isTouched = variableList.checkIfRowTouched($lastRow);
if (!isTouched) {
$lastRow.find('input, textarea').attr('name', '');
+ $lastRow.find('select').attr('name', '');
}
});
}
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 6ebd1ad109e..aacfa0d87e6 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,23 +1,21 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
-import {
- APPLICATION_STATUS,
- REQUEST_SUBMITTED,
- REQUEST_FAILURE,
- UPGRADE_REQUESTED,
- UPGRADE_REQUEST_FAILURE,
-} from './constants';
+import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX } from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
+Vue.use(GlToast);
+
/**
* Cluster page has 2 separate parts:
* Toggle button and applications section
@@ -36,6 +34,7 @@ export default class Clusters {
installRunnerPath,
installJupyterPath,
installKnativePath,
+ updateKnativePath,
installPrometheusPath,
managePrometheusPath,
hasRbac,
@@ -45,8 +44,10 @@ export default class Clusters {
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
+ clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
+ this.clusterId = clusterId;
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
@@ -62,6 +63,7 @@ export default class Clusters {
installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
+ updateKnativeEndpoint: updateKnativePath,
});
this.installApplication = this.installApplication.bind(this);
@@ -70,10 +72,18 @@ export default class Clusters {
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating');
+ this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable');
+ this.authenticationFailureContainer = document.querySelector(
+ '.js-cluster-authentication-failure',
+ );
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
+ this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
+ this.ingressDomainSnippet = this.ingressDomainHelpText.querySelector(
+ '.js-ingress-domain-snippet',
+ );
Clusters.initDismissableCallout();
initSettingsPanels();
@@ -119,24 +129,35 @@ export default class Clusters {
static initDismissableCallout() {
const callout = document.querySelector('.js-cluster-security-warning');
+ PersistentUserCallout.factory(callout);
+ }
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ addBannerCloseHandler(el, status) {
+ el.querySelector('.js-close-banner').addEventListener('click', () => {
+ el.classList.add('hidden');
+ this.setBannerDismissedState(status, true);
+ });
}
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
- eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
- eventHub.$on('upgradeFailed', appId => this.upgradeFailed(appId));
- eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
+ eventHub.$on('updateApplication', data => this.updateApplication(data));
+ eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
+ eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
+ eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
+ // Add event listener to all the banner close buttons
+ this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
+ this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
}
removeListeners() {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
- eventHub.$off('upgradeApplication', this.upgradeApplication);
- eventHub.$off('upgradeFailed', this.upgradeFailed);
- eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
+ eventHub.$off('updateApplication', this.updateApplication);
+ eventHub.$off('saveKnativeDomain');
+ eventHub.$off('setKnativeHostname');
+ eventHub.$off('uninstallApplication');
}
initPolling() {
@@ -177,6 +198,10 @@ export default class Clusters {
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
+ this.toggleIngressDomainHelpText(
+ prevApplicationMap[INGRESS],
+ this.store.state.applications[INGRESS],
+ );
}
showToken() {
@@ -195,6 +220,8 @@ export default class Clusters {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
+ this.unreachableContainer.classList.add('hidden');
+ this.authenticationFailureContainer.classList.add('hidden');
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
@@ -218,9 +245,32 @@ export default class Clusters {
}
}
+ setBannerDismissedState(status, isDismissed) {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ window.localStorage.setItem(
+ `cluster_${this.clusterId}_banner_dismissed`,
+ `${status}_${isDismissed}`,
+ );
+ }
+ }
+
+ isBannerDismissed(status) {
+ let bannerState;
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`);
+ }
+
+ return bannerState === `${status}_true`;
+ }
+
updateContainer(prevStatus, status, error) {
this.hideAll();
+ if (this.isBannerDismissed(status)) {
+ return;
+ }
+ this.setBannerDismissedState(status, false);
+
// We poll all the time but only want the `created` banner to show when newly created
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
switch (status) {
@@ -231,6 +281,12 @@ export default class Clusters {
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
+ case 'unreachable':
+ this.unreachableContainer.classList.remove('hidden');
+ break;
+ case 'authentication_failure':
+ this.authenticationFailureContainer.classList.remove('hidden');
+ break;
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
@@ -241,14 +297,14 @@ export default class Clusters {
}
}
- installApplication(data) {
- const appId = data.id;
- this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUBMITTED);
+ installApplication({ id: appId, params }) {
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
- this.service.installApplication(appId, data.params).catch(() => {
- this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
+ this.store.installApplication(appId);
+
+ return this.service.installApplication(appId, params).catch(() => {
+ this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
@@ -257,19 +313,48 @@ export default class Clusters {
});
}
- upgradeApplication(data) {
- const appId = data.id;
- this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUESTED);
- this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
- this.service.installApplication(appId, data.params).catch(() => this.upgradeFailed(appId));
+ uninstallApplication({ id: appId }) {
+ this.store.updateAppProperty(appId, 'requestReason', null);
+ this.store.updateAppProperty(appId, 'statusReason', null);
+
+ this.store.uninstallApplication(appId);
+
+ return this.service.uninstallApplication(appId).catch(() => {
+ this.store.notifyUninstallFailure(appId);
+ this.store.updateAppProperty(
+ appId,
+ 'requestReason',
+ s__('ClusterIntegration|Request to begin uninstalling failed'),
+ );
+ });
}
- upgradeFailed(appId) {
- this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUEST_FAILURE);
+ updateApplication({ id: appId, params }) {
+ this.store.updateApplication(appId);
+ this.service.installApplication(appId, params).catch(() => {
+ this.store.notifyUpdateFailure(appId);
+ });
}
- dismissUpgradeSuccess(appId) {
- this.store.updateAppProperty(appId, 'requestStatus', null);
+ toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
+ if (externalIp !== newExternalIp) {
+ this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
+ this.ingressDomainSnippet.textContent = `${newExternalIp}${INGRESS_DOMAIN_SUFFIX}`;
+ }
+ }
+
+ saveKnativeDomain(data) {
+ const appId = data.id;
+ this.store.updateApplication(appId);
+ this.service.updateApplication(appId, data.params).catch(() => {
+ this.store.notifyUpdateFailure(appId);
+ });
+ }
+
+ setKnativeHostname(data) {
+ const appId = data.id;
+ this.store.updateAppProperty(appId, 'isEditingHostName', true);
+ this.store.updateAppProperty(appId, 'hostname', data.hostname);
}
destroy() {
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 5952e93b9a7..4771090aa7e 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -1,17 +1,15 @@
<script>
/* eslint-disable vue/require-default-prop */
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlModalDirective } from '@gitlab/ui';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-import { s__, sprintf } from '../../locale';
+import { s__, __, sprintf } from '~/locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
-import {
- APPLICATION_STATUS,
- REQUEST_SUBMITTED,
- REQUEST_FAILURE,
- UPGRADE_REQUESTED,
-} from '../constants';
+import UninstallApplicationButton from './uninstall_application_button.vue';
+import UninstallApplicationConfirmationModal from './uninstall_application_confirmation_modal.vue';
+
+import { APPLICATION_STATUS } from '../constants';
export default {
components: {
@@ -19,6 +17,11 @@ export default {
identicon,
TimeagoTooltip,
GlLink,
+ UninstallApplicationButton,
+ UninstallApplicationConfirmationModal,
+ },
+ directives: {
+ GlModalDirective,
},
props: {
id: {
@@ -47,6 +50,11 @@ export default {
required: false,
default: false,
},
+ uninstallable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
status: {
type: String,
required: false,
@@ -55,13 +63,19 @@ export default {
type: String,
required: false,
},
- requestStatus: {
+ requestReason: {
type: String,
required: false,
},
- requestReason: {
- type: String,
+ installed: {
+ type: Boolean,
required: false,
+ default: false,
+ },
+ installFailed: {
+ type: Boolean,
+ required: false,
+ default: false,
},
version: {
type: String,
@@ -71,9 +85,33 @@ export default {
type: String,
required: false,
},
- upgradeAvailable: {
+ updateAvailable: {
+ type: Boolean,
+ required: false,
+ },
+ updateable: {
+ type: Boolean,
+ default: true,
+ },
+ updateSuccessful: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ updateFailed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ uninstallFailed: {
type: Boolean,
required: false,
+ default: false,
+ },
+ uninstallSuccessful: {
+ type: Boolean,
+ required: false,
+ default: false,
},
installApplicationRequestParams: {
type: Object,
@@ -89,34 +127,17 @@ export default {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
isInstalling() {
- return (
- this.status === APPLICATION_STATUS.SCHEDULED ||
- this.status === APPLICATION_STATUS.INSTALLING ||
- (this.requestStatus === REQUEST_SUBMITTED && !this.statusReason && !this.isInstalled)
- );
- },
- isInstalled() {
- return (
- this.status === APPLICATION_STATUS.INSTALLED ||
- this.status === APPLICATION_STATUS.UPDATED ||
- this.status === APPLICATION_STATUS.UPDATING ||
- this.status === APPLICATION_STATUS.UPDATE_ERRORED
- );
+ return this.status === APPLICATION_STATUS.INSTALLING;
},
canInstall() {
- if (this.isInstalling) {
- return false;
- }
-
return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
this.status === APPLICATION_STATUS.INSTALLABLE ||
- this.status === APPLICATION_STATUS.ERROR ||
this.isUnknownStatus
);
},
hasLogo() {
- return !!this.logoUrl;
+ return Boolean(this.logoUrl);
},
identiconId() {
// generate a deterministic integer id for the identicon background
@@ -125,8 +146,14 @@ export default {
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
+ displayUninstallButton() {
+ return this.installed && this.uninstallable;
+ },
+ displayInstallButton() {
+ return !this.installed || !this.uninstallable;
+ },
installButtonLoading() {
- return !this.status || this.status === APPLICATION_STATUS.SCHEDULED || this.isInstalling;
+ return !this.status || this.isInstalling;
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
@@ -142,11 +169,11 @@ export default {
installButtonLabel() {
let label;
if (this.canInstall) {
- label = s__('ClusterIntegration|Install');
+ label = __('Install');
} else if (this.isInstalling) {
- label = s__('ClusterIntegration|Installing');
- } else if (this.isInstalled) {
- label = s__('ClusterIntegration|Installed');
+ label = __('Installing');
+ } else if (this.installed) {
+ label = __('Installed');
}
return label;
@@ -155,80 +182,78 @@ export default {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
- return s__('ClusterIntegration|Manage');
+ return __('Manage');
},
hasError() {
- return (
- !this.isInstalling &&
- (this.status === APPLICATION_STATUS.ERROR || this.requestStatus === REQUEST_FAILURE)
- );
+ return this.installFailed || this.uninstallFailed;
},
generalErrorDescription() {
- return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), {
- title: this.title,
- });
- },
- versionLabel() {
- if (this.upgradeFailed) {
- return s__('ClusterIntegration|Upgrade failed');
- } else if (this.isUpgrading) {
- return s__('ClusterIntegration|Upgrading');
+ let errorDescription;
+
+ if (this.installFailed) {
+ errorDescription = s__('ClusterIntegration|Something went wrong while installing %{title}');
+ } else if (this.uninstallFailed) {
+ errorDescription = s__(
+ 'ClusterIntegration|Something went wrong while uninstalling %{title}',
+ );
}
- return s__('ClusterIntegration|Upgraded');
- },
- upgradeRequested() {
- return this.requestStatus === UPGRADE_REQUESTED;
- },
- upgradeSuccessful() {
- return this.status === APPLICATION_STATUS.UPDATED;
+ return sprintf(errorDescription, { title: this.title });
},
- upgradeFailed() {
- if (this.isUpgrading) {
- return false;
+ versionLabel() {
+ if (this.updateFailed) {
+ return __('Update failed');
+ } else if (this.isUpdating) {
+ return __('Updating');
}
- return this.status === APPLICATION_STATUS.UPDATE_ERRORED;
- },
- upgradeFailureDescription() {
- return sprintf(
- s__(
- 'ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again.',
- ),
- {
- title: this.title,
- },
- );
+ return __('Updated');
},
- upgradeSuccessDescription() {
- return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
+ updateFailureDescription() {
+ return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
+ },
+ updateSuccessDescription() {
+ return sprintf(s__('ClusterIntegration|%{title} updated successfully.'), {
title: this.title,
});
},
- upgradeButtonLabel() {
+ updateButtonLabel() {
let label;
- if (this.upgradeAvailable && !this.upgradeFailed && !this.isUpgrading) {
- label = s__('ClusterIntegration|Upgrade');
- } else if (this.isUpgrading) {
- label = s__('ClusterIntegration|Upgrading');
- } else if (this.upgradeFailed) {
- label = s__('ClusterIntegration|Retry upgrade');
+ if (this.updateAvailable && !this.updateFailed && !this.isUpdating) {
+ label = __('Update');
+ } else if (this.isUpdating) {
+ label = __('Updating');
+ } else if (this.updateFailed) {
+ label = __('Retry update');
}
return label;
},
- isUpgrading() {
+ isUpdating() {
// Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
- return (
- this.status === APPLICATION_STATUS.UPDATING ||
- (this.upgradeRequested && !this.upgradeSuccessful)
- );
+ return this.status === APPLICATION_STATUS.UPDATING;
+ },
+ shouldShowUpdateDetails() {
+ // This method only returns true when;
+ // Update was successful OR Update failed
+ // AND new update is unavailable AND version information is present.
+ return (this.updateSuccessful || this.updateFailed) && !this.updateAvailable && this.version;
+ },
+ uninstallSuccessDescription() {
+ return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
+ title: this.title,
+ });
},
},
watch: {
- status() {
- if (this.status === APPLICATION_STATUS.UPDATE_ERRORED) {
- eventHub.$emit('upgradeFailed', this.id);
+ updateSuccessful(updateSuccessful) {
+ if (updateSuccessful) {
+ this.$toast.show(this.updateSuccessDescription);
+ }
+ },
+ uninstallSuccessful(uninstallSuccessful) {
+ if (uninstallSuccessful) {
+ this.$toast.show(this.uninstallSuccessDescription);
}
},
},
@@ -239,14 +264,16 @@ export default {
params: this.installApplicationRequestParams,
});
},
- upgradeClicked() {
- eventHub.$emit('upgradeApplication', {
+ updateClicked() {
+ eventHub.$emit('updateApplication', {
id: this.id,
params: this.installApplicationRequestParams,
});
},
- dismissUpgradeSuccess() {
- eventHub.$emit('dismissUpgradeSuccess', this.id);
+ uninstallConfirmed() {
+ eventHub.$emit('uninstallApplication', {
+ id: this.id,
+ });
},
},
};
@@ -256,7 +283,7 @@ export default {
<div
:class="[
rowJsClass,
- isInstalled && 'cluster-application-installed',
+ installed && 'cluster-application-installed',
disabled && 'cluster-application-disabled',
]"
class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span"
@@ -279,16 +306,12 @@ export default {
target="blank"
rel="noopener noreferrer"
class="js-cluster-application-title"
+ >{{ title }}</a
>
- {{ title }}
- </a>
- <span v-else class="js-cluster-application-title"> {{ title }} </span>
+ <span v-else class="js-cluster-application-title">{{ title }}</span>
</strong>
<slot name="description"></slot>
- <div
- v-if="hasError || isUnknownStatus"
- class="cluster-application-error text-danger prepend-top-10"
- >
+ <div v-if="hasError" class="cluster-application-error text-danger prepend-top-10">
<p class="js-cluster-application-general-error-message append-bottom-0">
{{ generalErrorDescription }}
</p>
@@ -302,50 +325,38 @@ export default {
</ul>
</div>
- <div
- v-if="(upgradeSuccessful || upgradeFailed) && !upgradeAvailable"
- class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
- >
- {{ versionLabel }}
-
- <span v-if="upgradeSuccessful"> to</span>
-
- <gl-link
- v-if="upgradeSuccessful"
- :href="chartRepo"
- target="_blank"
- class="js-cluster-application-upgrade-version"
+ <div v-if="updateable">
+ <div
+ v-if="shouldShowUpdateDetails"
+ class="form-text text-muted label p-0 js-cluster-application-update-details"
>
- chart v{{ version }}
- </gl-link>
- </div>
+ {{ versionLabel }}
+ <span v-if="updateSuccessful">to</span>
- <div
- v-if="upgradeFailed && !isUpgrading"
- class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
- >
- {{ upgradeFailureDescription }}
- </div>
-
- <div
- v-if="upgradeRequested && upgradeSuccessful"
- class="bs-callout bs-callout-success cluster-application-banner mt-2 mb-0 p-0 pl-3"
- >
- {{ upgradeSuccessDescription }}
+ <gl-link
+ v-if="updateSuccessful"
+ :href="chartRepo"
+ target="_blank"
+ class="js-cluster-application-update-version"
+ >chart v{{ version }}</gl-link
+ >
+ </div>
- <button class="close cluster-application-banner-close" @click="dismissUpgradeSuccess">
- &times;
- </button>
+ <div
+ v-if="updateFailed && !isUpdating"
+ class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-update-details"
+ >
+ {{ updateFailureDescription }}
+ </div>
+ <loading-button
+ v-if="updateAvailable || updateFailed || isUpdating"
+ class="btn btn-primary js-cluster-application-update-button mt-2"
+ :loading="isUpdating"
+ :disabled="isUpdating"
+ :label="updateButtonLabel"
+ @click="updateClicked"
+ />
</div>
-
- <loading-button
- v-if="upgradeAvailable || upgradeFailed || isUpgrading"
- class="btn btn-primary js-cluster-application-upgrade-button mt-2"
- :loading="isUpgrading"
- :disabled="isUpgrading"
- :label="upgradeButtonLabel"
- @click="upgradeClicked"
- />
</div>
<div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
@@ -353,18 +364,30 @@ export default {
role="gridcell"
>
<div v-if="showManageButton" class="btn-group table-action-buttons">
- <a :href="manageLink" :class="{ disabled: disabled }" class="btn">
- {{ manageButtonLabel }}
- </a>
+ <a :href="manageLink" :class="{ disabled: disabled }" class="btn">{{
+ manageButtonLabel
+ }}</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
+ v-if="displayInstallButton"
:loading="installButtonLoading"
:disabled="disabled || installButtonDisabled"
:label="installButtonLabel"
class="js-cluster-application-install-button"
@click="installClicked"
/>
+ <uninstall-application-button
+ v-if="displayUninstallButton"
+ v-gl-modal-directive="'uninstall-' + id"
+ :status="status"
+ class="js-cluster-application-uninstall-button"
+ />
+ <uninstall-application-confirmation-modal
+ :application="id"
+ :application-title="title"
+ @confirm="uninstallConfirmed()"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 0cf187d4189..970f5a7b297 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import helmInstallIllustration from '@gitlab/svgs/dist/illustrations/kubernetes-installation.svg';
+import { GlLoadingIcon } from '@gitlab/ui';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
@@ -14,12 +15,18 @@ import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import eventHub from '~/clusters/event_hub';
export default {
components: {
applicationRow,
clipboardButton,
+ LoadingButton,
+ GlLoadingIcon,
+ KnativeDomainEditor,
},
props: {
type: {
@@ -86,53 +93,26 @@ export default {
ingressInstalled() {
return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
},
- ingressExternalIp() {
- return this.applications.ingress.externalIp;
+ ingressExternalEndpoint() {
+ return this.applications.ingress.externalIp || this.applications.ingress.externalHostname;
},
certManagerInstalled() {
return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
},
ingressDescription() {
- const extraCostParagraph = sprintf(
- _.escape(
- s__(
- `ClusterIntegration|%{boldNotice} This will add some extra resources
- like a load balancer, which may incur additional costs depending on
- the hosting provider your Kubernetes cluster is installed on. If you are using
- Google Kubernetes Engine, you can %{pricingLink}.`,
- ),
- ),
- {
- boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
- pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
- },
- false,
- );
-
- const externalIpParagraph = sprintf(
+ return sprintf(
_.escape(
s__(
- `ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS
- at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`,
+ `ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}.`,
),
),
{
- ingressHelpLink: `<a href="${this.ingressHelpPath}">
- ${_.escape(s__('ClusterIntegration|More information'))}
- </a>`,
+ pricingLink: `<strong><a href="https://cloud.google.com/compute/pricing#lb"
+ target="_blank" rel="noopener noreferrer">
+ ${_.escape(s__('ClusterIntegration|pricing'))}</a></strong>`,
},
false,
);
-
- return `
- <p>
- ${extraCostParagraph}
- </p>
- <p class="settings-message append-bottom-0">
- ${externalIpParagraph}
- </p>
- `;
},
certManagerDescription() {
return sprintf(
@@ -173,16 +153,27 @@ export default {
jupyterHostname() {
return this.applications.jupyter.hostname;
},
- knativeInstalled() {
- return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
- },
- knativeExternalIp() {
- return this.applications.knative.externalIp;
+ knative() {
+ return this.applications.knative;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
},
+ methods: {
+ saveKnativeDomain(hostname) {
+ eventHub.$emit('saveKnativeDomain', {
+ id: 'knative',
+ params: { hostname },
+ });
+ },
+ setKnativeHostname(hostname) {
+ eventHub.$emit('setKnativeHostname', {
+ id: 'knative',
+ hostname,
+ });
+ },
+ },
};
</script>
@@ -192,9 +183,9 @@ export default {
<p class="append-bottom-0">
{{
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
- Helm Tiller is required to install any of the following applications.`)
+ Helm Tiller is required to install any of the following applications.`)
}}
- <a :href="helpPath"> {{ __('More information') }} </a>
+ <a :href="helpPath">{{ __('More information') }}</a>
</p>
<div class="cluster-application-list prepend-top-10">
@@ -206,15 +197,20 @@ export default {
:status-reason="applications.helm.statusReason"
:request-status="applications.helm.requestStatus"
:request-reason="applications.helm.requestReason"
+ :installed="applications.helm.installed"
+ :install-failed="applications.helm.installFailed"
+ :uninstallable="applications.helm.uninstallable"
+ :uninstall-successful="applications.helm.uninstallSuccessful"
+ :uninstall-failed="applications.helm.uninstallFailed"
class="rounded-top"
title-link="https://docs.helm.sh/"
>
<div slot="description">
{{
s__(`ClusterIntegration|Helm streamlines installing
- and managing Kubernetes applications.
- Tiller runs inside of your Kubernetes Cluster,
- and manages releases of your charts.`)
+ and managing Kubernetes applications.
+ Tiller runs inside of your Kubernetes Cluster,
+ and manages releases of your charts.`)
}}
</div>
</application-row>
@@ -222,7 +218,7 @@ export default {
<div class="svg-container" v-html="helmInstallIllustration"></div>
{{
s__(`ClusterIntegration|You must first install Helm Tiller before
- installing the applications below`)
+ installing the applications below`)
}}
</div>
<application-row
@@ -233,6 +229,11 @@ export default {
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
+ :installed="applications.ingress.installed"
+ :install-failed="applications.ingress.installFailed"
+ :uninstallable="applications.ingress.uninstallable"
+ :uninstall-successful="applications.ingress.uninstallSuccessful"
+ :uninstall-failed="applications.ingress.uninstallFailed"
:disabled="!helmInstalled"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
>
@@ -240,37 +241,40 @@ export default {
<p>
{{
s__(`ClusterIntegration|Ingress gives you a way to route
- requests to services based on the request host or path,
- centralizing a number of services into a single entrypoint.`)
+ requests to services based on the request host or path,
+ centralizing a number of services into a single entrypoint.`)
}}
</p>
<template v-if="ingressInstalled">
<div class="form-group">
- <label for="ingress-ip-address">
- {{ s__('ClusterIntegration|Ingress IP Address') }}
- </label>
- <div v-if="ingressExternalIp" class="input-group">
+ <label for="ingress-endpoint">{{ s__('ClusterIntegration|Ingress Endpoint') }}</label>
+ <div v-if="ingressExternalEndpoint" class="input-group">
<input
- id="ingress-ip-address"
- :value="ingressExternalIp"
+ id="ingress-endpoint"
+ :value="ingressExternalEndpoint"
type="text"
- class="form-control js-ip-address"
+ class="form-control js-endpoint"
readonly
/>
<span class="input-group-append">
<clipboard-button
- :text="ingressExternalIp"
- :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
+ :text="ingressExternalEndpoint"
+ :title="s__('ClusterIntegration|Copy Ingress Endpoint to clipboard')"
class="input-group-text js-clipboard-btn"
/>
</span>
</div>
- <input v-else type="text" class="form-control js-ip-address" readonly value="?" />
+ <div v-else class="input-group">
+ <input type="text" class="form-control js-endpoint" readonly />
+ <gl-loading-icon
+ class="position-absolute align-self-center ml-2 js-ingress-ip-loading-icon"
+ />
+ </div>
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
- generated IP address in order to access
+ generated endpoint in order to access
your application after it has been deployed.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
@@ -279,19 +283,20 @@ export default {
</p>
</div>
- <p v-if="!ingressExternalIp" class="settings-message js-no-ip-message">
+ <p v-if="!ingressExternalEndpoint" class="settings-message js-no-endpoint-message">
{{
- s__(`ClusterIntegration|The IP address is in
+ s__(`ClusterIntegration|The endpoint is in
the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}}
-
- <a :href="ingressHelpPath" target="_blank" rel="noopener noreferrer">
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
</p>
</template>
- <div v-html="ingressDescription"></div>
+ <template v-if="!ingressInstalled">
+ <div class="bs-callout bs-callout-info" v-html="ingressDescription"></div>
+ </template>
</div>
</application-row>
<application-row
@@ -302,7 +307,12 @@ export default {
:status-reason="applications.cert_manager.statusReason"
:request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason"
+ :installed="applications.cert_manager.installed"
+ :install-failed="applications.cert_manager.installFailed"
:install-application-request-params="{ email: applications.cert_manager.email }"
+ :uninstallable="applications.cert_manager.uninstallable"
+ :uninstall-successful="applications.cert_manager.uninstallSuccessful"
+ :uninstall-failed="applications.cert_manager.uninstallFailed"
:disabled="!helmInstalled"
title-link="https://cert-manager.readthedocs.io/en/latest/#"
>
@@ -324,22 +334,20 @@ export default {
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Issuers represent a certificate authority.
- You must provide an email address for your Issuer. `)
+ You must provide an email address for your Issuer. `)
}}
<a
href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
target="_blank"
rel="noopener noreferrer"
+ >{{ __('More information') }}</a
>
- {{ __('More information') }}
- </a>
</p>
</div>
</div>
</template>
</application-row>
<application-row
- v-if="isProjectCluster"
id="prometheus"
:logo-url="prometheusLogo"
:title="applications.prometheus.title"
@@ -348,13 +356,17 @@ export default {
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
+ :installed="applications.prometheus.installed"
+ :install-failed="applications.prometheus.installFailed"
+ :uninstallable="applications.prometheus.uninstallable"
+ :uninstall-successful="applications.prometheus.uninstallSuccessful"
+ :uninstall-failed="applications.prometheus.uninstallFailed"
:disabled="!helmInstalled"
title-link="https://prometheus.io/docs/introduction/overview/"
>
<div slot="description" v-html="prometheusDescription"></div>
</application-row>
<application-row
- v-if="isProjectCluster"
id="runner"
:logo-url="gitlabLogo"
:title="applications.runner.title"
@@ -364,16 +376,23 @@ export default {
:request-reason="applications.runner.requestReason"
:version="applications.runner.version"
:chart-repo="applications.runner.chartRepo"
- :upgrade-available="applications.runner.upgradeAvailable"
+ :update-available="applications.runner.updateAvailable"
+ :installed="applications.runner.installed"
+ :install-failed="applications.runner.installFailed"
+ :update-successful="applications.runner.updateSuccessful"
+ :update-failed="applications.runner.updateFailed"
+ :uninstallable="applications.runner.uninstallable"
+ :uninstall-successful="applications.runner.uninstallSuccessful"
+ :uninstall-failed="applications.runner.uninstallFailed"
:disabled="!helmInstalled"
title-link="https://docs.gitlab.com/runner/"
>
<div slot="description">
{{
- s__(`ClusterIntegration|GitLab Runner connects to this
- project's repository and executes CI/CD jobs,
- pushing results back and deploying,
- applications to production.`)
+ s__(`ClusterIntegration|GitLab Runner connects to the
+ repository and executes CI/CD jobs,
+ pushing results back and deploying
+ applications to production.`)
}}
</div>
</application-row>
@@ -386,6 +405,11 @@ export default {
:status-reason="applications.jupyter.statusReason"
:request-status="applications.jupyter.requestStatus"
:request-reason="applications.jupyter.requestReason"
+ :installed="applications.jupyter.installed"
+ :install-failed="applications.jupyter.installFailed"
+ :uninstallable="applications.jupyter.uninstallable"
+ :uninstall-successful="applications.jupyter.uninstallSuccessful"
+ :uninstall-failed="applications.jupyter.uninstallFailed"
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled"
title-link="https://jupyterhub.readthedocs.io/en/stable/"
@@ -394,18 +418,16 @@ export default {
<p>
{{
s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
- manages, and proxies multiple instances of the single-user
- Jupyter notebook server. JupyterHub can be used to serve
- notebooks to a class of students, a corporate data science group,
- or a scientific research group.`)
+ manages, and proxies multiple instances of the single-user
+ Jupyter notebook server. JupyterHub can be used to serve
+ notebooks to a class of students, a corporate data science group,
+ or a scientific research group.`)
}}
</p>
- <template v-if="ingressExternalIp">
+ <template v-if="ingressExternalEndpoint">
<div class="form-group">
- <label for="jupyter-hostname">
- {{ s__('ClusterIntegration|Jupyter Hostname') }}
- </label>
+ <label for="jupyter-hostname">{{ s__('ClusterIntegration|Jupyter Hostname') }}</label>
<div class="input-group">
<input
@@ -445,13 +467,20 @@ export default {
:status-reason="applications.knative.statusReason"
:request-status="applications.knative.requestStatus"
:request-reason="applications.knative.requestReason"
+ :installed="applications.knative.installed"
+ :install-failed="applications.knative.installFailed"
:install-application-request-params="{ hostname: applications.knative.hostname }"
+ :uninstallable="applications.knative.uninstallable"
+ :uninstall-successful="applications.knative.uninstallSuccessful"
+ :uninstall-failed="applications.knative.uninstallFailed"
+ :updateable="false"
:disabled="!helmInstalled"
+ v-bind="applications.knative"
title-link="https://github.com/knative/docs"
>
<div slot="description">
<span v-if="!rbac">
- <p v-if="!rbac" class="bs-callout bs-callout-info append-bottom-0">
+ <p v-if="!rbac" class="rbac-notice bs-callout bs-callout-info append-bottom-0">
{{
s__(`ClusterIntegration|You must have an RBAC-enabled cluster
to install Knative.`)
@@ -465,82 +494,19 @@ export default {
<p>
{{
s__(`ClusterIntegration|Knative extends Kubernetes to provide
- a set of middleware components that are essential to build modern,
- source-centric, and container-based applications that can run
- anywhere: on premises, in the cloud, or even in a third-party data center.`)
+ a set of middleware components that are essential to build modern,
+ source-centric, and container-based applications that can run
+ anywhere: on premises, in the cloud, or even in a third-party data center.`)
}}
</p>
- <template v-if="knativeInstalled">
- <div class="form-group">
- <label for="knative-domainname">
- {{ s__('ClusterIntegration|Knative Domain Name:') }}
- </label>
- <input
- id="knative-domainname"
- v-model="applications.knative.hostname"
- type="text"
- class="form-control js-domainname"
- readonly
- />
- </div>
- </template>
- <template v-else-if="helmInstalled && rbac">
- <div class="form-group">
- <label for="knative-domainname">
- {{ s__('ClusterIntegration|Knative Domain Name:') }}
- </label>
- <input
- id="knative-domainname"
- v-model="applications.knative.hostname"
- type="text"
- class="form-control js-domainname"
- />
- </div>
- </template>
- <template v-if="knativeInstalled">
- <div class="form-group">
- <label for="knative-ip-address">
- {{ s__('ClusterIntegration|Knative IP Address:') }}
- </label>
- <div v-if="knativeExternalIp" class="input-group">
- <input
- id="knative-ip-address"
- :value="knativeExternalIp"
- type="text"
- class="form-control js-ip-address"
- readonly
- />
- <span class="input-group-append">
- <clipboard-button
- :text="knativeExternalIp"
- :title="s__('ClusterIntegration|Copy Knative IP Address to clipboard')"
- class="input-group-text js-clipboard-btn"
- />
- </span>
- </div>
- <input v-else type="text" class="form-control js-ip-address" readonly value="?" />
- </div>
-
- <p v-if="!knativeExternalIp" class="settings-message js-no-ip-message">
- {{
- s__(`ClusterIntegration|The IP address is in
- the process of being assigned. Please check your Kubernetes
- cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
- }}
- </p>
-
- <p>
- {{
- s__(`ClusterIntegration|Point a wildcard DNS to this
- generated IP address in order to access
- your application after it has been deployed.`)
- }}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
- {{ __('More information') }}
- </a>
- </p>
- </template>
+ <knative-domain-editor
+ v-if="knative.installed || (helmInstalled && rbac)"
+ :knative="knative"
+ :ingress-dns-help-path="ingressDnsHelpPath"
+ @save="saveKnativeDomain"
+ @set="setKnativeHostname"
+ />
</div>
</application-row>
</div>
diff --git a/app/assets/javascripts/clusters/components/knative_domain_editor.vue b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
new file mode 100644
index 00000000000..480228619a5
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
@@ -0,0 +1,150 @@
+<script>
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
+
+export default {
+ components: {
+ LoadingButton,
+ ClipboardButton,
+ GlLoadingIcon,
+ },
+ props: {
+ knative: {
+ type: Object,
+ required: true,
+ },
+ ingressDnsHelpPath: {
+ type: String,
+ default: '',
+ },
+ },
+ computed: {
+ saveButtonDisabled() {
+ return [UNINSTALLING, UPDATING].includes(this.knative.status);
+ },
+ saving() {
+ return [UPDATING].includes(this.knative.status);
+ },
+ saveButtonLabel() {
+ return this.saving ? this.__('Saving') : this.__('Save changes');
+ },
+ knativeInstalled() {
+ return this.knative.installed;
+ },
+ knativeExternalEndpoint() {
+ return this.knative.externalIp || this.knative.externalHostname;
+ },
+ knativeUpdateSuccessful() {
+ return this.knative.updateSuccessful;
+ },
+ knativeHostname: {
+ get() {
+ return this.knative.hostname;
+ },
+ set(hostname) {
+ this.$emit('set', hostname);
+ },
+ },
+ },
+ watch: {
+ knativeUpdateSuccessful(updateSuccessful) {
+ if (updateSuccessful) {
+ this.$toast.show(s__('ClusterIntegration|Knative domain name was updated successfully.'));
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row">
+ <div
+ v-if="knative.updateFailed"
+ class="bs-callout bs-callout-danger cluster-application-banner col-12 mt-2 mb-2 js-cluster-knative-domain-name-failure-message"
+ >
+ {{ s__('ClusterIntegration|Something went wrong while updating Knative domain name.') }}
+ </div>
+
+ <template>
+ <div
+ :class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }"
+ class="form-group col-sm-12 mb-0"
+ >
+ <label for="knative-domainname">
+ <strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
+ </label>
+ <input
+ id="knative-domainname"
+ v-model="knativeHostname"
+ type="text"
+ class="form-control js-knative-domainname"
+ />
+ </div>
+ </template>
+ <template v-if="knativeInstalled">
+ <div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
+ <label for="knative-endpoint">
+ <strong>{{ s__('ClusterIntegration|Knative Endpoint:') }}</strong>
+ </label>
+ <div v-if="knativeExternalEndpoint" class="input-group">
+ <input
+ id="knative-endpoint"
+ :value="knativeExternalEndpoint"
+ type="text"
+ class="form-control js-knative-endpoint"
+ readonly
+ />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="knativeExternalEndpoint"
+ :title="s__('ClusterIntegration|Copy Knative Endpoint to clipboard')"
+ class="input-group-text js-knative-endpoint-clipboard-btn"
+ />
+ </span>
+ </div>
+ <div v-else class="input-group">
+ <input type="text" class="form-control js-endpoint" readonly />
+ <gl-loading-icon
+ class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
+ />
+ </div>
+ </div>
+
+ <p class="form-text text-muted col-12">
+ {{
+ s__(
+ `ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
+ )
+ }}
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
+ </p>
+
+ <p
+ v-if="!knativeExternalEndpoint"
+ class="settings-message js-no-knative-endpoint-message mt-2 mr-3 mb-0 ml-3"
+ >
+ {{
+ s__(`ClusterIntegration|The endpoint is in
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
+ }}
+ </p>
+
+ <loading-button
+ class="btn-success js-knative-save-domain-button mt-3 ml-3"
+ :loading="saving"
+ :disabled="saveButtonDisabled"
+ :label="saveButtonLabel"
+ @click="$emit('save', knativeHostname)"
+ />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_button.vue b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
new file mode 100644
index 00000000000..ef4bcbe14dd
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
@@ -0,0 +1,33 @@
+<script>
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
+
+export default {
+ components: {
+ LoadingButton,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ disabled() {
+ return [UNINSTALLING, UPDATING].includes(this.status);
+ },
+ loading() {
+ return this.status === UNINSTALLING;
+ },
+ label() {
+ return this.loading ? this.__('Uninstalling') : this.__('Uninstall');
+ },
+ },
+};
+</script>
+
+<template>
+ <loading-button :label="label" :disabled="disabled" :loading="loading" />
+</template>
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
new file mode 100644
index 00000000000..65827f1cb6a
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
@@ -0,0 +1,74 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { sprintf, s__ } from '~/locale';
+import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
+import { INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
+
+const CUSTOM_APP_WARNING_TEXT = {
+ [INGRESS]: s__(
+ 'ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored.',
+ ),
+ [CERT_MANAGER]: s__(
+ 'ClusterIntegration|The associated certifcate will be deleted and cannot be restored.',
+ ),
+ [PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
+ [RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
+ [KNATIVE]: s__('ClusterIntegration|The associated IP will be deleted and cannot be restored.'),
+ [JUPYTER]: '',
+};
+
+export default {
+ components: {
+ GlModal,
+ },
+ mixins: [trackUninstallButtonClickMixin],
+ props: {
+ application: {
+ type: String,
+ required: true,
+ },
+ applicationTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ return sprintf(s__('ClusterIntegration|Uninstall %{appTitle}'), {
+ appTitle: this.applicationTitle,
+ });
+ },
+ warningText() {
+ return sprintf(
+ s__('ClusterIntegration|You are about to uninstall %{appTitle} from your cluster.'),
+ {
+ appTitle: this.applicationTitle,
+ },
+ );
+ },
+ customAppWarningText() {
+ return CUSTOM_APP_WARNING_TEXT[this.application];
+ },
+ modalId() {
+ return `uninstall-${this.application}`;
+ },
+ },
+ methods: {
+ confirmUninstall() {
+ this.trackUninstallButtonClick(this.application);
+ this.$emit('confirm');
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ ok-variant="danger"
+ cancel-variant="light"
+ :ok-title="title"
+ :modal-id="modalId"
+ :title="title"
+ @ok="confirmUninstall()"
+ >{{ warningText }} {{ customAppWarningText }}</gl-modal
+ >
+</template>
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 39022879d91..8fd752092c9 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -7,6 +7,7 @@ export const CLUSTER_TYPE = {
// These need to match what is returned from the server
export const APPLICATION_STATUS = {
+ NO_STATUS: null,
NOT_INSTALLABLE: 'not_installable',
INSTALLABLE: 'installable',
SCHEDULED: 'scheduled',
@@ -15,16 +16,35 @@ export const APPLICATION_STATUS = {
UPDATING: 'updating',
UPDATED: 'updated',
UPDATE_ERRORED: 'update_errored',
+ UNINSTALLING: 'uninstalling',
+ UNINSTALL_ERRORED: 'uninstall_errored',
ERROR: 'errored',
};
+/*
+ * The application cannot be in any of the following states without
+ * not being installed.
+ */
+export const APPLICATION_INSTALLED_STATUSES = [
+ APPLICATION_STATUS.INSTALLED,
+ APPLICATION_STATUS.UPDATING,
+ APPLICATION_STATUS.UNINSTALLING,
+];
+
// These are only used client-side
-export const REQUEST_SUBMITTED = 'request-submitted';
-export const REQUEST_FAILURE = 'request-failure';
-export const UPGRADE_REQUESTED = 'upgrade-requested';
-export const UPGRADE_REQUEST_FAILURE = 'upgrade-request-failure';
+
+export const UPDATE_EVENT = 'update';
+export const INSTALL_EVENT = 'install';
+export const UNINSTALL_EVENT = 'uninstall';
+
+export const HELM = 'helm';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
+export const PROMETHEUS = 'prometheus';
+
+export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS];
+
+export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
diff --git a/app/assets/javascripts/clusters/mixins/track_uninstall_button_click.js b/app/assets/javascripts/clusters/mixins/track_uninstall_button_click.js
new file mode 100644
index 00000000000..18f65b234d3
--- /dev/null
+++ b/app/assets/javascripts/clusters/mixins/track_uninstall_button_click.js
@@ -0,0 +1,5 @@
+export default {
+ methods: {
+ trackUninstallButtonClick: () => {},
+ },
+};
diff --git a/app/assets/javascripts/clusters/services/application_state_machine.js b/app/assets/javascripts/clusters/services/application_state_machine.js
new file mode 100644
index 00000000000..17ea4d77795
--- /dev/null
+++ b/app/assets/javascripts/clusters/services/application_state_machine.js
@@ -0,0 +1,174 @@
+import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT, UNINSTALL_EVENT } from '../constants';
+
+const {
+ NO_STATUS,
+ SCHEDULED,
+ NOT_INSTALLABLE,
+ INSTALLABLE,
+ INSTALLING,
+ INSTALLED,
+ ERROR,
+ UPDATING,
+ UPDATED,
+ UPDATE_ERRORED,
+ UNINSTALLING,
+ UNINSTALL_ERRORED,
+} = APPLICATION_STATUS;
+
+const applicationStateMachine = {
+ /* When the application initially loads, it will have `NO_STATUS`
+ * It will transition from `NO_STATUS` once the async backend call is completed
+ */
+ [NO_STATUS]: {
+ on: {
+ [SCHEDULED]: {
+ target: INSTALLING,
+ },
+ [NOT_INSTALLABLE]: {
+ target: NOT_INSTALLABLE,
+ },
+ [INSTALLABLE]: {
+ target: INSTALLABLE,
+ },
+ [INSTALLING]: {
+ target: INSTALLING,
+ },
+ [INSTALLED]: {
+ target: INSTALLED,
+ },
+ [ERROR]: {
+ target: INSTALLABLE,
+ effects: {
+ installFailed: true,
+ },
+ },
+ [UPDATING]: {
+ target: UPDATING,
+ },
+ [UPDATED]: {
+ target: INSTALLED,
+ },
+ [UPDATE_ERRORED]: {
+ target: INSTALLED,
+ effects: {
+ updateFailed: true,
+ },
+ },
+ [UNINSTALLING]: {
+ target: UNINSTALLING,
+ },
+ [UNINSTALL_ERRORED]: {
+ target: INSTALLED,
+ effects: {
+ uninstallFailed: true,
+ },
+ },
+ },
+ },
+ [NOT_INSTALLABLE]: {
+ on: {
+ [INSTALLABLE]: {
+ target: INSTALLABLE,
+ },
+ },
+ },
+ [INSTALLABLE]: {
+ on: {
+ [INSTALL_EVENT]: {
+ target: INSTALLING,
+ effects: {
+ installFailed: false,
+ },
+ },
+ // This is possible in artificial environments for E2E testing
+ [INSTALLED]: {
+ target: INSTALLED,
+ },
+ },
+ },
+ [INSTALLING]: {
+ on: {
+ [INSTALLED]: {
+ target: INSTALLED,
+ },
+ [ERROR]: {
+ target: INSTALLABLE,
+ effects: {
+ installFailed: true,
+ },
+ },
+ },
+ },
+ [INSTALLED]: {
+ on: {
+ [UPDATE_EVENT]: {
+ target: UPDATING,
+ effects: {
+ updateFailed: false,
+ updateSuccessful: false,
+ },
+ },
+ [UNINSTALL_EVENT]: {
+ target: UNINSTALLING,
+ effects: {
+ uninstallFailed: false,
+ uninstallSuccessful: false,
+ },
+ },
+ },
+ },
+ [UPDATING]: {
+ on: {
+ [UPDATED]: {
+ target: INSTALLED,
+ effects: {
+ updateSuccessful: true,
+ },
+ },
+ [UPDATE_ERRORED]: {
+ target: INSTALLED,
+ effects: {
+ updateFailed: true,
+ },
+ },
+ },
+ },
+ [UNINSTALLING]: {
+ on: {
+ [INSTALLABLE]: {
+ target: INSTALLABLE,
+ effects: {
+ uninstallSuccessful: true,
+ },
+ },
+ [UNINSTALL_ERRORED]: {
+ target: INSTALLED,
+ effects: {
+ uninstallFailed: true,
+ },
+ },
+ },
+ },
+};
+
+/**
+ * Determines an application new state based on the application current state
+ * and an event. If the application current state cannot handle a given event,
+ * the current state is returned.
+ *
+ * @param {*} application
+ * @param {*} event
+ */
+const transitionApplicationState = (application, event) => {
+ const newState = applicationStateMachine[application.status].on[event];
+
+ return newState
+ ? {
+ ...application,
+ status: newState.target,
+ ...newState.effects,
+ }
+ : application;
+};
+
+export default transitionApplicationState;
diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js
index 89dda4b7902..01f3732de7e 100644
--- a/app/assets/javascripts/clusters/services/clusters_service.js
+++ b/app/assets/javascripts/clusters/services/clusters_service.js
@@ -12,6 +12,9 @@ export default class ClusterService {
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
};
+ this.appUpdateEndpointMap = {
+ knative: this.options.updateKnativeEndpoint,
+ };
}
fetchData() {
@@ -22,6 +25,14 @@ export default class ClusterService {
return axios.post(this.appInstallEndpointMap[appId], params);
}
+ updateApplication(appId, params) {
+ return axios.patch(this.appUpdateEndpointMap[appId], params);
+ }
+
+ uninstallApplication(appId, params) {
+ return axios.delete(this.appInstallEndpointMap[appId], params);
+ }
+
static updateCluster(endpoint, data) {
return axios.put(endpoint, data);
}
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index d309678be27..f64f0ca616f 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,6 +1,31 @@
import { s__ } from '../../locale';
import { parseBoolean } from '../../lib/utils/common_utils';
-import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER, RUNNER } from '../constants';
+import {
+ INGRESS,
+ JUPYTER,
+ KNATIVE,
+ CERT_MANAGER,
+ RUNNER,
+ APPLICATION_INSTALLED_STATUSES,
+ APPLICATION_STATUS,
+ INSTALL_EVENT,
+ UPDATE_EVENT,
+ UNINSTALL_EVENT,
+} from '../constants';
+import transitionApplicationState from '../services/application_state_machine';
+
+const isApplicationInstalled = appStatus => APPLICATION_INSTALLED_STATUSES.includes(appStatus);
+
+const applicationInitialState = {
+ status: null,
+ statusReason: null,
+ requestReason: null,
+ installed: false,
+ installFailed: false,
+ uninstallable: false,
+ uninstallFailed: false,
+ uninstallSuccessful: false,
+};
export default class ClusterStore {
constructor() {
@@ -12,61 +37,47 @@ export default class ClusterStore {
statusReason: null,
applications: {
helm: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Helm Tiller'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
},
ingress: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Ingress'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
externalIp: null,
+ externalHostname: null,
},
cert_manager: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Cert-Manager'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
email: null,
},
runner: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
- upgradeAvailable: null,
+ updateAvailable: null,
+ updateSuccessful: false,
+ updateFailed: false,
},
prometheus: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Prometheus'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
},
jupyter: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|JupyterHub'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
hostname: null,
},
knative: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Knative'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
hostname: null,
+ isEditingHostName: false,
externalIp: null,
+ externalHostname: null,
+ updateSuccessful: false,
+ updateFailed: false,
},
},
};
@@ -94,6 +105,36 @@ export default class ClusterStore {
this.state.statusReason = reason;
}
+ installApplication(appId) {
+ this.handleApplicationEvent(appId, INSTALL_EVENT);
+ }
+
+ notifyInstallFailure(appId) {
+ this.handleApplicationEvent(appId, APPLICATION_STATUS.ERROR);
+ }
+
+ updateApplication(appId) {
+ this.handleApplicationEvent(appId, UPDATE_EVENT);
+ }
+
+ notifyUpdateFailure(appId) {
+ this.handleApplicationEvent(appId, APPLICATION_STATUS.UPDATE_ERRORED);
+ }
+
+ uninstallApplication(appId) {
+ this.handleApplicationEvent(appId, UNINSTALL_EVENT);
+ }
+
+ notifyUninstallFailure(appId) {
+ this.handleApplicationEvent(appId, APPLICATION_STATUS.UNINSTALL_ERRORED);
+ }
+
+ handleApplicationEvent(appId, event) {
+ const currentAppState = this.state.applications[appId];
+
+ this.state.applications[appId] = transitionApplicationState(currentAppState, event);
+ }
+
updateAppProperty(appId, prop, value) {
this.state.applications[appId][prop] = value;
}
@@ -108,17 +149,23 @@ export default class ClusterStore {
status,
status_reason: statusReason,
version,
- update_available: upgradeAvailable,
+ update_available: updateAvailable,
+ can_uninstall: uninstallable,
} = serverAppEntry;
+ const currentApplicationState = this.state.applications[appId] || {};
+ const nextApplicationState = transitionApplicationState(currentApplicationState, status);
this.state.applications[appId] = {
- ...(this.state.applications[appId] || {}),
- status,
+ ...currentApplicationState,
+ ...nextApplicationState,
statusReason,
+ installed: isApplicationInstalled(nextApplicationState.status),
+ uninstallable,
};
if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
+ this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
@@ -129,13 +176,17 @@ export default class ClusterStore {
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: '');
} else if (appId === KNATIVE) {
- this.state.applications.knative.hostname =
- serverAppEntry.hostname || this.state.applications.knative.hostname;
+ if (!this.state.applications.knative.isEditingHostName) {
+ this.state.applications.knative.hostname =
+ serverAppEntry.hostname || this.state.applications.knative.hostname;
+ }
this.state.applications.knative.externalIp =
serverAppEntry.external_ip || this.state.applications.knative.externalIp;
+ this.state.applications.knative.externalHostname =
+ serverAppEntry.external_hostname || this.state.applications.knative.externalHostname;
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
- this.state.applications.runner.upgradeAvailable = upgradeAvailable;
+ this.state.applications.runner.updateAvailable = updateAvailable;
}
});
}
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index d4ecfa4aa93..bc666aef54b 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -71,29 +71,39 @@ export default class ImageFile {
// eslint-disable-next-line class-methods-use-this
initDraggable($el, padding, callback) {
var dragging = false;
- var $body = $('body');
- var $offsetEl = $el.parent();
-
- $el.off('mousedown').on('mousedown', function() {
+ const $body = $('body');
+ const $offsetEl = $el.parent();
+ const dragStart = function() {
dragging = true;
$body.css('user-select', 'none');
- });
+ };
+ const dragStop = function() {
+ dragging = false;
+ $body.css('user-select', '');
+ };
+ const dragMove = function(e) {
+ const moveX = e.pageX || e.touches[0].pageX;
+ const left = moveX - ($offsetEl.offset().left + padding);
+ if (!dragging) return;
+
+ callback(e, left);
+ };
+
+ $el
+ .off('mousedown')
+ .off('touchstart')
+ .on('mousedown', dragStart)
+ .on('touchstart', dragStart);
$body
.off('mouseup')
.off('mousemove')
- .on('mouseup', function() {
- dragging = false;
- $body.css('user-select', '');
- })
- .on('mousemove', function(e) {
- var left;
- if (!dragging) return;
-
- left = e.pageX - ($offsetEl.offset().left + padding);
-
- callback(e, left);
- });
+ .off('touchend')
+ .off('touchmove')
+ .on('mouseup', dragStop)
+ .on('touchend', dragStop)
+ .on('mousemove', dragMove)
+ .on('touchmove', dragMove);
}
prepareFrames(view) {
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index fba30aea9ae..e5e1cbb1e62 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -16,3 +16,63 @@ $.fn.extend({
.removeClass('disabled');
},
});
+
+/*
+ Starting with bootstrap 4.3.1, bootstrap sanitizes html used for tooltips / popovers.
+ This extends the default whitelists with more elements / attributes:
+ https://getbootstrap.com/docs/4.3/getting-started/javascript/#sanitizer
+ */
+const whitelist = $.fn.tooltip.Constructor.Default.whiteList;
+
+const inputAttributes = ['value', 'type'];
+
+const dataAttributes = [
+ 'data-toggle',
+ 'data-placement',
+ 'data-container',
+ 'data-title',
+ 'data-class',
+ 'data-clipboard-text',
+ 'data-placement',
+];
+
+// Whitelisting data attributes
+whitelist['*'] = [
+ ...whitelist['*'],
+ ...dataAttributes,
+ 'title',
+ 'width height',
+ 'abbr',
+ 'datetime',
+ 'name',
+ 'width',
+ 'height',
+];
+
+// Whitelist missing elements:
+whitelist.label = ['for'];
+whitelist.button = [...inputAttributes];
+whitelist.input = [...inputAttributes];
+
+whitelist.tt = [];
+whitelist.samp = [];
+whitelist.kbd = [];
+whitelist.var = [];
+whitelist.dfn = [];
+whitelist.cite = [];
+whitelist.big = [];
+whitelist.address = [];
+whitelist.dl = [];
+whitelist.dt = [];
+whitelist.dd = [];
+whitelist.abbr = [];
+whitelist.acronym = [];
+whitelist.blockquote = [];
+whitelist.del = [];
+whitelist.ins = [];
+whitelist['gl-emoji'] = [];
+
+// Whitelisting SVG tags and attributes
+whitelist.svg = ['viewBox'];
+whitelist.use = ['xlink:href'];
+whitelist.path = ['d'];
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
index 009153d0703..2f268419bff 100644
--- a/app/assets/javascripts/commons/jquery.js
+++ b/app/assets/javascripts/commons/jquery.js
@@ -3,7 +3,7 @@ import 'jquery';
// common jQuery plugins
import 'jquery-ujs';
import 'vendor/jquery.endless-scroll';
-import 'vendor/jquery.caret';
-import 'vendor/jquery.atwho';
+import 'jquery.caret'; // must be imported before at.js
+import 'at.js';
import 'vendor/jquery.scrollTo';
import 'jquery.waitforimages';
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index bffc025ced3..d0cc4897aeb 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -1,18 +1,21 @@
// ECMAScript polyfills
-import 'core-js/fn/array/fill';
-import 'core-js/fn/array/find';
-import 'core-js/fn/array/find-index';
-import 'core-js/fn/array/from';
-import 'core-js/fn/array/includes';
-import 'core-js/fn/object/assign';
-import 'core-js/fn/object/values';
-import 'core-js/fn/promise';
-import 'core-js/fn/string/code-point-at';
-import 'core-js/fn/string/from-code-point';
-import 'core-js/fn/string/includes';
-import 'core-js/fn/symbol';
-import 'core-js/es6/map';
-import 'core-js/es6/weak-map';
+import 'core-js/es/array/fill';
+import 'core-js/es/array/find';
+import 'core-js/es/array/find-index';
+import 'core-js/es/array/from';
+import 'core-js/es/array/includes';
+import 'core-js/es/object/assign';
+import 'core-js/es/object/values';
+import 'core-js/es/object/entries';
+import 'core-js/es/promise';
+import 'core-js/es/promise/finally';
+import 'core-js/es/string/code-point-at';
+import 'core-js/es/string/from-code-point';
+import 'core-js/es/string/includes';
+import 'core-js/es/symbol';
+import 'core-js/es/map';
+import 'core-js/es/weak-map';
+import 'core-js/modules/web.url';
// Browser polyfills
import 'formdata-polyfill';
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 37a3ceb5341..5bfe158ceda 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
},
selectable: true,
filterable: true,
- filterRemote: !!$dropdown.data('refsUrl'),
+ filterRemote: Boolean($dropdown.data('refsUrl')),
fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 50efecb3475..b62ec8a651b 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -4,6 +4,12 @@ import _ from 'underscore';
import bp from './breakpoints';
import { parseBoolean } from '~/lib/utils/common_utils';
+// NOTE: at 1200px nav sidebar should not overlap the content
+// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24555#note_134136110
+const NAV_SIDEBAR_BREAKPOINT = 1200;
+
+export const SIDEBAR_COLLAPSED_CLASS = 'js-sidebar-collapsed';
+
export default class ContextualSidebar {
constructor() {
this.initDomElements();
@@ -26,44 +32,58 @@ export default class ContextualSidebar {
bindEvents() {
if (!this.$sidebar.length) return;
- document.addEventListener('click', e => {
- if (
- !e.target.closest('.nav-sidebar') &&
- (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')
- ) {
- this.toggleCollapsedSidebar(true, true);
- }
- });
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-collapsed-desktop');
- this.toggleCollapsedSidebar(value, true);
+ if (!ContextualSidebar.isDesktopBreakpoint()) {
+ this.toggleSidebarNav(!this.$sidebar.hasClass('sidebar-expanded-mobile'));
+ } else {
+ const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
+ this.toggleCollapsedSidebar(value, true);
+ }
+ });
+ this.$page.on('transitionstart transitionend', () => {
+ $(document).trigger('content.resize');
});
$(window).on('resize', () => _.debounce(this.render(), 100));
}
+ // TODO: use the breakpoints from breakpoints.js once they have been updated for bootstrap 4
+ // See documentation: https://design.gitlab.com/regions/navigation#contextual-navigation
+ static isDesktopBreakpoint = () => bp.windowWidth() >= NAV_SIDEBAR_BREAKPOINT;
static setCollapsedCookie(value) {
- if (bp.getBreakpointSize() !== 'lg') {
+ if (!ContextualSidebar.isDesktopBreakpoint()) {
return;
}
Cookies.set('sidebar_collapsed', value, { expires: 365 * 10 });
}
toggleSidebarNav(show) {
- this.$sidebar.toggleClass('sidebar-expanded-mobile', show);
- this.$overlay.toggleClass('mobile-nav-open', show);
+ const breakpoint = bp.getBreakpointSize();
+ const dbp = ContextualSidebar.isDesktopBreakpoint();
+
+ this.$sidebar.toggleClass(SIDEBAR_COLLAPSED_CLASS, !show);
+ this.$sidebar.toggleClass('sidebar-expanded-mobile', !dbp ? show : false);
+ this.$overlay.toggleClass(
+ 'mobile-nav-open',
+ breakpoint === 'xs' || breakpoint === 'sm' ? show : false,
+ );
this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
toggleCollapsedSidebar(collapsed, saveCookie) {
const breakpoint = bp.getBreakpointSize();
+ const dbp = ContextualSidebar.isDesktopBreakpoint();
if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
- this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
+ this.$sidebar.toggleClass('sidebar-expanded-mobile', !dbp ? !collapsed : false);
+ this.$page.toggleClass(
+ 'page-with-icon-sidebar',
+ breakpoint === 'xs' || breakpoint === 'sm' ? true : collapsed,
+ );
}
if (saveCookie) {
@@ -84,13 +104,11 @@ export default class ContextualSidebar {
render() {
if (!this.$sidebar.length) return;
- const breakpoint = bp.getBreakpointSize();
-
- if (breakpoint === 'sm' || breakpoint === 'md') {
- this.toggleCollapsedSidebar(true, false);
- } else if (breakpoint === 'lg') {
+ if (!ContextualSidebar.isDesktopBreakpoint()) {
+ this.toggleSidebarNav(false);
+ } else {
const collapse = parseBoolean(Cookies.get('sidebar_collapsed'));
- this.toggleCollapsedSidebar(collapse, false);
+ this.toggleCollapsedSidebar(collapse, true);
}
}
}
diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js
index 916b190f469..fa0f04c7d82 100644
--- a/app/assets/javascripts/create_item_dropdown.js
+++ b/app/assets/javascripts/create_item_dropdown.js
@@ -12,7 +12,7 @@ export default class CreateItemDropdown {
this.fieldName = options.fieldName;
this.onSelect = options.onSelect || (() => {});
this.getDataOption = options.getData;
- this.getDataRemote = !!options.filterRemote;
+ this.getDataRemote = Boolean(options.filterRemote);
this.createNewItemFromValueOption = options.createNewItemFromValue;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index 28ca7d97314..eac0e37bcaa 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -14,6 +14,7 @@ export default class CreateLabelDropdown {
this.$newLabelField = $('#new_label_name', this.$el);
this.$newColorField = $('#new_label_color', this.$el);
this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
+ this.$addList = $('.js-add-list', this.$el);
this.$newLabelError = $('.js-label-error', this.$el);
this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
@@ -21,6 +22,8 @@ export default class CreateLabelDropdown {
this.$newLabelError.hide();
this.$newLabelCreateButton.disable();
+ this.addListDefault = this.$addList.is(':checked');
+
this.cleanBinding();
this.addBinding();
}
@@ -83,6 +86,8 @@ export default class CreateLabelDropdown {
this.$newColorField.val('').trigger('change');
+ this.$addList.prop('checked', this.addListDefault);
+
this.$colorPreview
.css('background-color', '')
.parent()
@@ -116,9 +121,9 @@ export default class CreateLabelDropdown {
this.$newLabelError.html(errors).show();
} else {
+ const addNewList = this.$addList.is(':checked');
this.$dropdownBack.trigger('click');
-
- $(document).trigger('created.label', label);
+ $(document).trigger('created.label', [label, addNewList]);
}
},
);
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index 02aa507ba03..8f5cece0788 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -118,7 +118,7 @@ export default class CreateMergeRequestDropdown {
this.branchCreated = true;
window.location.href = data.url;
})
- .catch(() => Flash('Failed to create a branch for this issue. Please try again.'));
+ .catch(() => Flash(__('Failed to create a branch for this issue. Please try again.')));
}
createMergeRequest() {
@@ -130,7 +130,7 @@ export default class CreateMergeRequestDropdown {
this.mergeRequestCreated = true;
window.location.href = data.url;
})
- .catch(() => Flash('Failed to create Merge Request. Please try again.'));
+ .catch(() => Flash(__('Failed to create Merge Request. Please try again.')));
}
disable() {
@@ -227,7 +227,7 @@ export default class CreateMergeRequestDropdown {
.catch(() => {
this.unavailable();
this.disable();
- new Flash('Failed to get ref.');
+ new Flash(__('Failed to get ref.'));
this.isGettingRef = false;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 4de425b48e7..3f0a9f2602c 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -12,6 +12,7 @@ import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue';
import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store';
+import { __ } from '~/locale';
Vue.use(Translate);
@@ -61,7 +62,7 @@ export default () => {
methods: {
handleError() {
this.store.setErrorState(true);
- return new Flash('There was an error while fetching cycle analytics data.');
+ return new Flash(__('There was an error while fetching cycle analytics data.'));
},
initDropdown() {
const $dropdown = $('.js-ca-dropdown');
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index 4ae4ceabc21..f66e07ba31a 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
@@ -3,6 +3,7 @@
import $ from 'jquery';
import Vue from 'vue';
+import { __ } from '~/locale';
const CommentAndResolveBtn = Vue.extend({
props: {
@@ -31,15 +32,15 @@ const CommentAndResolveBtn = Vue.extend({
buttonText: function() {
if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) {
- return 'Unresolve discussion';
+ return __('Unresolve discussion');
} else {
- return 'Comment & unresolve discussion';
+ return __('Comment & unresolve discussion');
}
} else {
if (this.textareaIsEmpty) {
- return 'Resolve discussion';
+ return __('Resolve discussion');
} else {
- return 'Comment & resolve discussion';
+ return __('Comment & resolve discussion');
}
}
},
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 5bdeaaade68..b5a781cbc92 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -5,6 +5,7 @@ import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
import Notes from '../../notes';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import { n__ } from '~/locale';
const DiffNoteAvatars = Vue.extend({
components: {
@@ -44,7 +45,7 @@ const DiffNoteAvatars = Vue.extend({
if (this.discussion) {
const extra = this.discussion.notesCount() - this.shownAvatars;
- return `${extra} more comment${extra > 1 ? 's' : ''}`;
+ return n__('%d more comment', '%d more comments', extra);
}
return '';
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 8542a6e718a..fe4088cadda 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -3,6 +3,7 @@
import $ from 'jquery';
import Vue from 'vue';
+import { __ } from '~/locale';
import DiscussionMixins from '../mixins/discussion';
@@ -23,9 +24,9 @@ const JumpToDiscussion = Vue.extend({
computed: {
buttonText: function() {
if (this.discussionId) {
- return 'Jump to next unresolved discussion';
+ return __('Jump to next unresolved discussion');
} else {
- return 'Jump to first unresolved discussion';
+ return __('Jump to first unresolved discussion');
}
},
allResolved: function() {
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index a69b34b0db8..87e7dd18e0c 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -4,6 +4,7 @@
import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
+import { sprintf, __ } from '~/locale';
const ResolveBtn = Vue.extend({
props: {
@@ -55,12 +56,14 @@ const ResolveBtn = Vue.extend({
},
buttonText() {
if (this.isResolved) {
- return `Resolved by ${this.resolvedByName}`;
+ return sprintf(__('Resolved by %{resolvedByName}'), {
+ resolvedByName: this.resolvedByName,
+ });
} else if (this.canResolve) {
- return 'Mark as resolved';
+ return __('Mark as resolved');
}
- return 'Unable to resolve';
+ return __('Unable to resolve');
},
isResolved() {
if (this.note) {
@@ -132,7 +135,8 @@ const ResolveBtn = Vue.extend({
this.updateTooltip();
})
.catch(
- () => new Flash('An error occurred when trying to resolve a comment. Please try again.'),
+ () =>
+ new Flash(__('An error occurred when trying to resolve a comment. Please try again.')),
);
},
},
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
index 6fcad187b35..4b204fdfeb0 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
@@ -3,6 +3,7 @@
/* global ResolveService */
import Vue from 'vue';
+import { __ } from '~/locale';
const ResolveDiscussionBtn = Vue.extend({
props: {
@@ -41,9 +42,9 @@ const ResolveDiscussionBtn = Vue.extend({
},
buttonText: function() {
if (this.isDiscussionResolved) {
- return 'Unresolve discussion';
+ return __('Unresolve discussion');
} else {
- return 'Resolve discussion';
+ return __('Resolve discussion');
}
},
loading: function() {
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index e69eaad4423..0687028ca54 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -3,6 +3,7 @@
import Vue from 'vue';
import Flash from '../../flash';
import '../../vue_shared/vue_resource_interceptor';
+import { __ } from '~/locale';
window.gl = window.gl || {};
@@ -49,7 +50,8 @@ class ResolveServiceClass {
discussion.updateHeadline(data);
})
.catch(
- () => new Flash('An error occurred when trying to resolve a discussion. Please try again.'),
+ () =>
+ new Flash(__('An error occurred when trying to resolve a discussion. Please try again.')),
);
}
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 8f47931d14a..11d6672cacf 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import createFlash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
+import Mousetrap from 'mousetrap';
import eventHub from '../../notes/event_hub';
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
@@ -18,6 +19,8 @@ import {
MIN_TREE_WIDTH,
MAX_TREE_WIDTH,
TREE_HIDE_STATS_WIDTH,
+ MR_TREE_SHOW_KEY,
+ CENTERED_LIMITED_CONTAINER_CLASSES,
} from '../constants';
export default {
@@ -61,6 +64,11 @@ export default {
required: false,
default: '',
},
+ isFluidLayout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const treeWidth =
@@ -87,7 +95,7 @@ export default {
emailPatchPath: state => state.diffs.emailPatchPath,
}),
...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']),
- ...mapGetters('diffs', ['isParallelView']),
+ ...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
targetBranch() {
return {
@@ -112,6 +120,9 @@ export default {
hideFileStats() {
return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
},
+ isLimitedContainer() {
+ return !this.showTreeList && !this.isParallelView && !this.isFluidLayout;
+ },
},
watch: {
diffViewType() {
@@ -146,9 +157,13 @@ export default {
this.adjustView();
eventHub.$once('fetchedNotesData', this.setDiscussions);
eventHub.$once('fetchDiffData', this.fetchData);
+ eventHub.$on('refetchDiffData', this.refetchDiffData);
+ this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
},
beforeDestroy() {
eventHub.$off('fetchDiffData', this.fetchData);
+ eventHub.$off('refetchDiffData', this.refetchDiffData);
+ this.removeEventListeners();
},
methods: {
...mapActions(['startTaskList']),
@@ -159,10 +174,20 @@ export default {
'assignDiscussionsToDiff',
'setHighlightedRow',
'cacheTreeListWidth',
+ 'scrollToFile',
+ 'toggleShowTreeList',
]),
- fetchData() {
+ refetchDiffData() {
+ this.assignedDiscussions = false;
+ this.fetchData(false);
+ },
+ fetchData(toggleTree = true) {
this.fetchDiffFiles()
.then(() => {
+ if (toggleTree) {
+ this.hideTreeListIfJustOneFile();
+ }
+
requestIdleCallback(
() => {
this.setDiscussions();
@@ -195,9 +220,42 @@ export default {
adjustView() {
if (this.shouldShow) {
this.$nextTick(() => {
- window.mrTabs.resetViewContainer();
- window.mrTabs.expandViewContainer(this.showTreeList);
+ this.setEventListeners();
});
+ } else {
+ this.removeEventListeners();
+ }
+ },
+ setEventListeners() {
+ Mousetrap.bind(['[', 'k', ']', 'j'], (e, combo) => {
+ switch (combo) {
+ case '[':
+ case 'k':
+ this.jumpToFile(-1);
+ break;
+ case ']':
+ case 'j':
+ this.jumpToFile(+1);
+ break;
+ default:
+ break;
+ }
+ });
+ },
+ removeEventListeners() {
+ Mousetrap.unbind(['[', 'k', ']', 'j']);
+ },
+ jumpToFile(step) {
+ const targetIndex = this.currentDiffIndex + step;
+ if (targetIndex >= 0 && targetIndex < this.diffFiles.length) {
+ this.scrollToFile(this.diffFiles[targetIndex].file_path);
+ }
+ },
+ hideTreeListIfJustOneFile() {
+ const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
+
+ if ((storedTreeShow === null && this.diffFiles.length <= 1) || storedTreeShow === 'false') {
+ this.toggleShowTreeList(false);
}
},
},
@@ -214,6 +272,7 @@ export default {
:merge-request-diffs="mergeRequestDiffs"
:merge-request-diff="mergeRequestDiff"
:target-branch="targetBranch"
+ :is-limited-container="isLimitedContainer"
/>
<hidden-files-warning
@@ -243,7 +302,12 @@ export default {
/>
<tree-list :hide-file-stats="hideFileStats" />
</div>
- <div class="diff-files-holder">
+ <div
+ class="diff-files-holder"
+ :class="{
+ [CENTERED_LIMITED_CONTAINER_CLASSES]: isLimitedContainer,
+ }"
+ >
<commit-widget v-if="commit" :commit="commit" />
<template v-if="renderDiffFiles">
<diff-file
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index c02a8740a42..bd7259ce3ee 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -69,7 +69,7 @@ export default {
:link-href="authorUrl"
:img-src="authorAvatar"
:img-alt="authorName"
- :img-size="36"
+ :img-size="40"
class="avatar-cell d-none d-sm-block"
/>
<div class="commit-detail flex-list">
@@ -113,9 +113,10 @@ export default {
<commit-pipeline-status
v-if="commit.pipeline_status_path"
:endpoint="commit.pipeline_status_path"
+ class="d-inline-flex"
/>
<div class="commit-sha-group">
- <div class="label label-monospace" v-text="commit.short_id"></div>
+ <div class="label label-monospace monospace" v-text="commit.short_id"></div>
<clipboard-button
:text="commit.id"
:title="__('Copy commit SHA to clipboard')"
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 0bf2dde8b96..363ebad1594 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -7,6 +7,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
import SettingsDropdown from './settings_dropdown.vue';
import DiffStats from './diff_stats.vue';
+import { CENTERED_LIMITED_CONTAINER_CLASSES } from '../constants';
export default {
components: {
@@ -35,6 +36,11 @@ export default {
required: false,
default: null,
},
+ isLimitedContainer: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
...mapGetters('diffs', ['hasCollapsedFile', 'diffFilesLength']),
@@ -62,6 +68,9 @@ export default {
return this.mergeRequestDiff.base_version_path;
},
},
+ created() {
+ this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
+ },
mounted() {
polyfillSticky(this.$el);
},
@@ -77,8 +86,13 @@ export default {
</script>
<template>
- <div class="mr-version-controls" :class="{ 'is-fileTreeOpen': showTreeList }">
- <div class="mr-version-menus-container content-block">
+ <div class="mr-version-controls border-top border-bottom">
+ <div
+ class="mr-version-menus-container content-block"
+ :class="{
+ [CENTERED_LIMITED_CONTAINER_CLASSES]: isLimitedContainer,
+ }"
+ >
<button
v-gl-tooltip.hover
type="button"
@@ -125,9 +139,9 @@ export default {
>
{{ __('Show latest version') }}
</gl-button>
- <a v-show="hasCollapsedFile" class="btn btn-default append-right-8" @click="expandAllFiles">
+ <gl-button v-show="hasCollapsedFile" class="append-right-8" @click="expandAllFiles">
{{ __('Expand all') }}
- </a>
+ </gl-button>
<settings-dropdown />
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index cb92093db32..d59b1136677 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -1,10 +1,14 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
+import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import NoteForm from '../../notes/components/note_form.vue';
import ImageDiffOverlay from './image_diff_overlay.vue';
import DiffDiscussions from './diff_discussions.vue';
@@ -14,6 +18,7 @@ import { diffViewerModes } from '~/ide/constants';
export default {
components: {
+ GlLoadingIcon,
InlineDiffView,
ParallelDiffView,
DiffViewer,
@@ -22,7 +27,10 @@ export default {
ImageDiffOverlay,
NotDiffableViewer,
NoPreviewViewer,
+ userAvatarLink,
+ DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'),
},
+ mixins: [diffLineNoteFormMixin, draftCommentsMixin],
props: {
diffFile: {
type: Object,
@@ -41,7 +49,7 @@ export default {
}),
...mapGetters('diffs', ['isInlineView', 'isParallelView']),
...mapGetters('diffs', ['getCommentFormForDiffFile']),
- ...mapGetters(['getNoteableData', 'noteableType']),
+ ...mapGetters(['getNoteableData', 'noteableType', 'getUserData']),
diffMode() {
return getDiffMode(this.diffFile);
},
@@ -58,10 +66,16 @@ export default {
return this.diffViewerMode === diffViewerModes.not_diffable;
},
diffFileCommentForm() {
- return this.getCommentFormForDiffFile(this.diffFile.file_hash);
+ return this.getCommentFormForDiffFile(this.diffFileHash);
},
showNotesContainer() {
- return this.diffFile.discussions.length || this.diffFileCommentForm;
+ return this.imageDiscussions.length || this.diffFileCommentForm;
+ },
+ diffFileHash() {
+ return this.diffFile.file_hash;
+ },
+ author() {
+ return this.getUserData;
},
},
methods: {
@@ -101,6 +115,7 @@ export default {
:diff-lines="diffFile.parallel_diff_lines || []"
:help-page-path="helpPagePath"
/>
+ <gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
</template>
<not-diffable-viewer v-else-if="notDiffable" />
<no-preview-viewer v-else-if="noPreview" />
@@ -112,18 +127,26 @@ export default {
:new-sha="diffFile.diff_refs.head_sha"
:old-path="diffFile.old_path"
:old-sha="diffFile.diff_refs.base_sha"
- :file-hash="diffFile.file_hash"
+ :file-hash="diffFileHash"
:project-path="projectPath"
:a-mode="diffFile.a_mode"
:b-mode="diffFile.b_mode"
>
<image-diff-overlay
slot="image-overlay"
- :discussions="diffFile.discussions"
- :file-hash="diffFile.file_hash"
+ :discussions="imageDiscussions"
+ :file-hash="diffFileHash"
:can-comment="getNoteableData.current_user.can_create_note"
/>
<div v-if="showNotesContainer" class="note-container">
+ <user-avatar-link
+ v-if="diffFileCommentForm && author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block new-comment"
+ />
<diff-discussions
v-if="diffFile.discussions.length"
class="diff-file-discussions"
@@ -131,14 +154,16 @@ export default {
:should-collapse-discussions="true"
:render-avatar-badge="true"
/>
+ <diff-file-drafts :file-hash="diffFileHash" class="diff-file-discussions" />
<note-form
v-if="diffFileCommentForm"
ref="noteForm"
:is-editing="false"
:save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container"
+ @handleFormUpdateAddToReview="addToReview"
@handleFormUpdate="handleSaveNote"
- @cancelForm="closeDiffFileCommentForm(diffFile.file_hash)"
+ @cancelForm="closeDiffFileCommentForm(diffFileHash)"
/>
</div>
</diff-viewer>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 1141a197c6a..f5876a73eff 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -73,13 +73,23 @@ export default {
if (!newVal && oldVal && !this.hasDiffLines) {
this.handleLoadCollapsedDiff();
}
+
+ this.setFileCollapsed({ filePath: this.file.file_path, collapsed: newVal });
+ },
+ 'file.viewer.collapsed': function setIsCollapsed(newVal) {
+ this.isCollapsed = newVal;
},
},
created() {
eventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.handleLoadCollapsedDiff);
},
methods: {
- ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff', 'setRenderIt']),
+ ...mapActions('diffs', [
+ 'loadCollapsedDiff',
+ 'assignDiscussionsToDiff',
+ 'setRenderIt',
+ 'setFileCollapsed',
+ ]),
handleToggle() {
if (!this.hasDiffLines) {
this.handleLoadCollapsedDiff();
@@ -160,26 +170,24 @@ export default {
</div>
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
<template v-else>
- <div v-if="errorMessage" class="diff-viewer">
- <div class="nothing-here-block" v-html="errorMessage"></div>
+ <div :id="`diff-content-${file.file_hash}`">
+ <div v-if="errorMessage" class="diff-viewer">
+ <div class="nothing-here-block" v-html="errorMessage"></div>
+ </div>
+ <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
+ {{ __('This diff is collapsed.') }}
+ <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
+ __('Click to expand it.')
+ }}</a>
+ </div>
+ <diff-content
+ v-else
+ :class="{ hidden: isCollapsed || isFileTooLarge }"
+ :diff-file="file"
+ :help-page-path="helpPagePath"
+ />
</div>
- <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
- {{ __('This diff is collapsed.') }}
- <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
- __('Click to expand it.')
- }}</a>
- </div>
- <diff-content
- v-else
- :class="{ hidden: isCollapsed || isFileTooLarge }"
- :diff-file="file"
- :help-page-path="helpPagePath"
- />
</template>
- <div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff">
- {{ __('This source diff could not be displayed because it is too large.') }}
- <span v-html="viewBlobLink"></span>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 2b801898345..eb9f1465945 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -1,19 +1,23 @@
<script>
import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
-import { polyfillSticky } from '~/lib/utils/sticky';
+import { polyfillSticky, stickyMonitor } from '~/lib/utils/sticky';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlButton, GlTooltipDirective, GlTooltip, GlLoadingIcon } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
import { diffViewerModes } from '~/ide/constants';
import EditButton from './edit_button.vue';
import DiffStats from './diff_stats.vue';
+import { scrollToElement, contentTop } from '~/lib/utils/common_utils';
export default {
components: {
+ GlTooltip,
+ GlLoadingIcon,
+ GlButton,
ClipboardButton,
EditButton,
Icon,
@@ -63,6 +67,9 @@ export default {
hasExpandedDiscussions() {
return this.diffHasExpandedDiscussions(this.diffFile);
},
+ diffContentIDSelector() {
+ return `#diff-content-${this.diffFile.file_hash}`;
+ },
icon() {
if (this.diffFile.submodule) {
return 'archive';
@@ -74,6 +81,11 @@ export default {
if (this.diffFile.submodule) {
return this.diffFile.submodule_tree_url || this.diffFile.submodule_link;
}
+
+ if (!this.discussionPath) {
+ return this.diffContentIDSelector;
+ }
+
return this.discussionPath;
},
filePath() {
@@ -100,9 +112,7 @@ export default {
const truncatedContentSha = _.escape(truncateSha(this.diffFile.content_sha));
return sprintf(
s__('MergeRequests|View file @ %{commitId}'),
- {
- commitId: `<span class="commit-sha">${truncatedContentSha}</span>`,
- },
+ { commitId: truncatedContentSha },
false,
);
},
@@ -125,12 +135,23 @@ export default {
isModeChanged() {
return this.diffFile.viewer.name === diffViewerModes.mode_changed;
},
+ showExpandDiffToFullFileEnabled() {
+ return gon.features.expandDiffFullFile && !this.diffFile.is_fully_expanded;
+ },
+ expandDiffToFullFileTitle() {
+ if (this.diffFile.isShowingFullFile) {
+ return s__('MRDiff|Show changes only');
+ }
+ return s__('MRDiff|Show full file');
+ },
},
mounted() {
polyfillSticky(this.$refs.header);
+ const fileHeaderHeight = this.$refs.header.clientHeight;
+ stickyMonitor(this.$refs.header, contentTop() - fileHeaderHeight - 1, false);
},
methods: {
- ...mapActions('diffs', ['toggleFileDiscussions']),
+ ...mapActions('diffs', ['toggleFileDiscussions', 'toggleFullDiff']),
handleToggleFile(e, checkTarget) {
if (
!checkTarget ||
@@ -146,6 +167,18 @@ export default {
handleToggleDiscussions() {
this.toggleFileDiscussions(this.diffFile);
},
+ handleFileNameClick(e) {
+ const isLinkToOtherPage =
+ this.diffFile.submodule_tree_url || this.diffFile.submodule_link || this.discussionPath;
+
+ if (!isLinkToOtherPage) {
+ e.preventDefault();
+ const selector = this.diffContentIDSelector;
+
+ scrollToElement(document.querySelector(selector));
+ window.location.hash = selector;
+ }
+ },
},
};
</script>
@@ -165,7 +198,14 @@ export default {
class="diff-toggle-caret append-right-5"
@click.stop="handleToggle"
/>
- <a v-once ref="titleWrapper" :href="titleLink" class="append-right-4 js-title-wrapper">
+ <a
+ v-once
+ id="diffFile.file_path"
+ ref="titleWrapper"
+ class="append-right-4 js-title-wrapper"
+ :href="titleLink"
+ @click="handleFileNameClick"
+ >
<file-icon
:file-name="filePath"
:size="18"
@@ -200,7 +240,7 @@ export default {
css-class="btn-default btn-transparent btn-clipboard"
/>
- <small v-if="isModeChanged" ref="fileMode">
+ <small v-if="isModeChanged" ref="fileMode" class="mr-1">
{{ diffFile.a_mode }} → {{ diffFile.b_mode }}
</small>
@@ -212,48 +252,71 @@ export default {
class="file-actions d-none d-sm-block"
>
<diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
- <template v-if="diffFile.blob && diffFile.blob.readable_text">
- <button
- :disabled="!diffHasDiscussions(diffFile)"
- :class="{ active: hasExpandedDiscussions }"
- :title="s__('MergeRequests|Toggle comments for this file')"
- class="js-btn-vue-toggle-comments btn"
- type="button"
- @click="handleToggleDiscussions"
- >
- <icon name="comment" />
- </button>
+ <div class="btn-group" role="group">
+ <template v-if="diffFile.blob && diffFile.blob.readable_text">
+ <span v-gl-tooltip.hover :title="s__('MergeRequests|Toggle comments for this file')">
+ <gl-button
+ :disabled="!diffHasDiscussions(diffFile)"
+ :class="{ active: hasExpandedDiscussions }"
+ class="js-btn-vue-toggle-comments btn"
+ type="button"
+ @click="handleToggleDiscussions"
+ >
+ <icon name="comment" />
+ </gl-button>
+ </span>
- <edit-button
- v-if="!diffFile.deleted_file"
- :can-current-user-fork="canCurrentUserFork"
- :edit-path="diffFile.edit_path"
- :can-modify-blob="diffFile.can_modify_blob"
- @showForkMessage="showForkMessage"
- />
- </template>
+ <edit-button
+ v-if="!diffFile.deleted_file"
+ :can-current-user-fork="canCurrentUserFork"
+ :edit-path="diffFile.edit_path"
+ :can-modify-blob="diffFile.can_modify_blob"
+ @showForkMessage="showForkMessage"
+ />
+ </template>
- <a
- v-if="diffFile.replaced_view_path"
- :href="diffFile.replaced_view_path"
- class="btn view-file js-view-file"
- v-html="viewReplacedFileButtonText"
- >
- </a>
- <a :href="diffFile.view_path" class="btn view-file js-view-file" v-html="viewFileButtonText">
- </a>
+ <a
+ v-if="diffFile.replaced_view_path"
+ :href="diffFile.replaced_view_path"
+ class="btn view-file js-view-replaced-file"
+ v-html="viewReplacedFileButtonText"
+ >
+ </a>
+ <gl-button
+ v-if="!diffFile.is_fully_expanded"
+ ref="expandDiffToFullFileButton"
+ v-gl-tooltip.hover
+ :title="expandDiffToFullFileTitle"
+ class="expand-file js-expand-file"
+ @click="toggleFullDiff(diffFile.file_path)"
+ >
+ <gl-loading-icon v-if="diffFile.isLoadingFullFile" color="dark" inline />
+ <icon v-else-if="diffFile.isShowingFullFile" name="doc-changes" />
+ <icon v-else name="doc-expand" />
+ </gl-button>
+ <gl-button
+ ref="viewButton"
+ v-gl-tooltip.hover
+ :href="diffFile.view_path"
+ target="blank"
+ class="view-file js-view-file-button"
+ :title="viewFileButtonText"
+ >
+ <icon name="doc-text" />
+ </gl-button>
- <a
- v-if="diffFile.external_url"
- v-gl-tooltip.hover
- :href="diffFile.external_url"
- :title="`View on ${diffFile.formatted_external_url}`"
- target="_blank"
- rel="noopener noreferrer"
- class="btn btn-file-option"
- >
- <icon name="external-link" />
- </a>
+ <a
+ v-if="diffFile.external_url"
+ v-gl-tooltip.hover
+ :href="diffFile.external_url"
+ :title="`View on ${diffFile.formatted_external_url}`"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="btn btn-file-option js-external-url"
+ >
+ <icon name="external-link" />
+ </a>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 0c0a0faa59d..7cf3d90d468 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -86,7 +86,6 @@ export default {
:key="note.id"
:img-src="note.author.avatar_url"
:tooltip-text="getTooltipText(note)"
- :size="19"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="toggleDiscussions"
/>
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index 6709df48637..1281f9b17ef 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -84,8 +84,6 @@ export default {
},
shouldShowCommentButton() {
return (
- this.isLoggedIn &&
- this.showCommentButton &&
this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
@@ -102,6 +100,9 @@ export default {
}
return this.showCommentButton && this.hasDiscussions;
},
+ shouldRenderCommentButton() {
+ return this.isLoggedIn && this.showCommentButton;
+ },
},
methods: {
...mapActions('diffs', ['loadMoreLines', 'showCommentForm', 'setHighlightedRow']),
@@ -167,6 +168,7 @@ export default {
>
<template v-else>
<button
+ v-if="shouldRenderCommentButton"
v-show="shouldShowCommentButton"
type="button"
class="add-diff-note js-add-diff-note-button qa-diff-comment"
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 18edbe286ba..c209b857652 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,15 +1,18 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { DIFF_NOTE_TYPE } from '../constants';
export default {
components: {
noteForm,
+ userAvatarLink,
},
- mixins: [autosave],
+ mixins: [autosave, diffLineNoteFormMixin],
props: {
diffFileHash: {
type: String,
@@ -40,17 +43,29 @@ export default {
diffViewType: state => state.diffs.diffViewType,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
- ...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']),
+ ...mapGetters([
+ 'isLoggedIn',
+ 'noteableType',
+ 'getNoteableData',
+ 'getNotesDataByProp',
+ 'getUserData',
+ ]),
+ author() {
+ return this.getUserData;
+ },
formData() {
return {
noteableData: this.noteableData,
noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType,
- diffFile: this.getDiffFileByHash(this.diffFileHash),
+ diffFile: this.diffFile,
linePosition: this.linePosition,
};
},
+ diffFile() {
+ return this.getDiffFileByHash(this.diffFileHash);
+ },
},
mounted() {
if (this.isLoggedIn) {
@@ -95,14 +110,24 @@ export default {
<template>
<div class="content discussion-form discussion-form-container discussion-notes">
+ <user-avatar-link
+ v-if="author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
<note-form
ref="noteForm"
:is-editing="true"
:line-code="line.line_code"
:line="line"
:help-page-path="helpPagePath"
+ :diff-file="diffFile"
save-button-title="Comment"
class="diff-comment-form"
+ @handleFormUpdateAddToReview="addToReview"
@cancelForm="handleCancelCommentForm"
@handleFormUpdate="handleSaveNote"
/>
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index d174b13e133..0f3e9208d21 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -89,17 +89,19 @@ export default {
classNameMap() {
const { type } = this.line;
- return {
- hll: this.isHighlighted,
- [type]: type,
- [LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
- [LINE_HOVER_CLASS_NAME]:
- this.isLoggedIn &&
- this.isHover &&
- !this.isMatchLine &&
- !this.isContextLine &&
- !this.isMetaLine,
- };
+ return [
+ type,
+ {
+ hll: this.isHighlighted,
+ [LINE_UNFOLD_CLASS_NAME]: this.isMatchLine,
+ [LINE_HOVER_CLASS_NAME]:
+ this.isLoggedIn &&
+ this.isHover &&
+ !this.isMatchLine &&
+ !this.isContextLine &&
+ !this.isMetaLine,
+ },
+ ];
},
lineNumber() {
return this.lineType === OLD_LINE_TYPE ? this.line.old_line : this.line.new_line;
diff --git a/app/assets/javascripts/diffs/components/edit_button.vue b/app/assets/javascripts/diffs/components/edit_button.vue
index 5d38d545ce8..dcb79cd5e16 100644
--- a/app/assets/javascripts/diffs/components/edit_button.vue
+++ b/app/assets/javascripts/diffs/components/edit_button.vue
@@ -1,5 +1,15 @@
<script>
+import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+
export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
editPath: {
type: String,
@@ -17,12 +27,7 @@ export default {
},
methods: {
handleEditClick(evt) {
- if (!this.canCurrentUserFork || this.canModifyBlob) {
- // if we can Edit, do default Edit button behavior
- return;
- }
-
- if (this.canCurrentUserFork) {
+ if (this.canCurrentUserFork && !this.canModifyBlob) {
evt.preventDefault();
this.$emit('showForkMessage');
}
@@ -32,5 +37,13 @@ export default {
</script>
<template>
- <a :href="editPath" class="btn btn-default js-edit-blob" @click="handleEditClick"> Edit </a>
+ <gl-button
+ v-gl-tooltip.top
+ :href="editPath"
+ :title="__('Edit file')"
+ class="js-edit-blob"
+ @click.native="handleEditClick"
+ >
+ <icon name="pencil" />
+ </gl-button>
</template>
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index 4a83c5a72a5..703a281308e 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -1,6 +1,7 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore';
+import imageDiffMixin from 'ee_else_ce/diffs/mixins/image_diff';
import Icon from '~/vue_shared/components/icon.vue';
export default {
@@ -8,6 +9,7 @@ export default {
components: {
Icon,
},
+ mixins: [imageDiffMixin],
props: {
discussions: {
type: [Array, Object],
@@ -48,7 +50,6 @@ export default {
},
},
methods: {
- ...mapActions(['toggleDiscussion']),
...mapActions('diffs', ['openDiffFileCommentForm']),
getImageDimensions() {
return {
@@ -105,15 +106,15 @@ export default {
v-for="(discussion, index) in allDiscussions"
:key="discussion.id"
:style="getPosition(discussion)"
- :class="badgeClass"
+ :class="[badgeClass, { 'is-draft': discussion.isDraft }]"
:disabled="!shouldToggleDiscussion"
class="js-image-badge"
type="button"
- @click="toggleDiscussion({ discussionId: discussion.id })"
+ @click="clickedToggle(discussion)"
>
<icon v-if="showCommentIcon" name="image-comment-dark" />
<template v-else>
- {{ index + 1 }}
+ {{ toggleText(discussion, index) }}
</template>
</button>
<button
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index 69146f1f6fd..1faa0493e79 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -41,7 +41,7 @@ export default {
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
- <td class="notes_content" colspan="3">
+ <td class="notes-content" colspan="3">
<div class="content">
<diff-discussions
v-if="line.discussions.length"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index c764cbeb8e0..2d5262baeec 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -1,12 +1,11 @@
<script>
-import { mapGetters, mapActions, mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
CONTEXT_LINE_CLASS_NAME,
- PARALLEL_DIFF_VIEW_TYPE,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
} from '../constants';
@@ -45,16 +44,16 @@ export default {
return this.line.line_code !== null && this.line.line_code === state.diffs.highlightedRow;
},
}),
- ...mapGetters('diffs', ['isInlineView']),
isContextLine() {
return this.line.type === CONTEXT_LINE_TYPE;
},
classNameMap() {
- return {
- [this.line.type]: this.line.type,
- [CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
- [PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
- };
+ return [
+ this.line.type,
+ {
+ [CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
+ },
+ ];
},
inlineRowId() {
return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`;
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index e781397214d..8c76a555b62 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -1,5 +1,6 @@
<script>
import { mapGetters } from 'vuex';
+import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
@@ -7,7 +8,10 @@ export default {
components: {
inlineDiffCommentRow,
inlineDiffTableRow,
+ InlineDraftCommentRow: () =>
+ import('ee_component/batch_comments/components/inline_draft_comment_row.vue'),
},
+ mixins: [draftCommentsMixin],
props: {
diffFile: {
type: Object,
@@ -54,6 +58,11 @@ export default {
:line="line"
:help-page-path="helpPagePath"
/>
+ <inline-draft-comment-row
+ v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
+ :key="`draft_${index}`"
+ :draft="draftForLine(diffFile.file_hash, line)"
+ />
</template>
</tbody>
</table>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index 370cb6e339a..d2e54edca85 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -87,7 +87,7 @@ export default {
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
- <td class="notes_content parallel old" colspan="2">
+ <td class="notes-content parallel old" colspan="2">
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
<diff-discussions
v-if="line.left.discussions.length"
@@ -105,7 +105,7 @@ export default {
line-position="left"
/>
</td>
- <td class="notes_content parallel new" colspan="2">
+ <td class="notes-content parallel new" colspan="2">
<div v-if="shouldRenderDiscussionsOnRight" class="content">
<diff-discussions
v-if="line.right.discussions.length"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index caf0df8a4e3..c60246bf8ef 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -140,7 +140,7 @@ export default {
:id="line.left.line_code"
:class="parallelViewLeftLineType"
class="line_content parallel left-side"
- @mousedown.native="handleParallelLineMouseDown"
+ @mousedown="handleParallelLineMouseDown"
v-html="line.left.rich_text"
></td>
</template>
@@ -171,7 +171,7 @@ export default {
},
]"
class="line_content parallel right-side"
- @mousedown.native="handleParallelLineMouseDown"
+ @mousedown="handleParallelLineMouseDown"
v-html="line.right.rich_text"
></td>
</template>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 1bf693380db..41a80d99850 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -1,5 +1,6 @@
<script>
import { mapGetters } from 'vuex';
+import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
@@ -7,7 +8,10 @@ export default {
components: {
parallelDiffTableRow,
parallelDiffCommentRow,
+ ParallelDraftCommentRow: () =>
+ import('ee_component/batch_comments/components/parallel_draft_comment_row.vue'),
},
+ mixins: [draftCommentsMixin],
props: {
diffFile: {
type: Object,
@@ -34,30 +38,34 @@ export default {
</script>
<template>
- <div
+ <table
:class="$options.userColorScheme"
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file"
>
- <table>
- <tbody>
- <template v-for="(line, index) in diffLines">
- <parallel-diff-table-row
- :key="line.line_code"
- :file-hash="diffFile.file_hash"
- :context-lines-path="diffFile.context_lines_path"
- :line="line"
- :is-bottom="index + 1 === diffLinesLength"
- />
- <parallel-diff-comment-row
- :key="`dcr-${line.line_code || index}`"
- :line="line"
- :diff-file-hash="diffFile.file_hash"
- :line-index="index"
- :help-page-path="helpPagePath"
- />
- </template>
- </tbody>
- </table>
- </div>
+ <tbody>
+ <template v-for="(line, index) in diffLines">
+ <parallel-diff-table-row
+ :key="line.line_code"
+ :file-hash="diffFile.file_hash"
+ :context-lines-path="diffFile.context_lines_path"
+ :line="line"
+ :is-bottom="index + 1 === diffLinesLength"
+ />
+ <parallel-diff-comment-row
+ :key="`dcr-${line.line_code || index}`"
+ :line="line"
+ :diff-file-hash="diffFile.file_hash"
+ :line-index="index"
+ :help-page-path="helpPagePath"
+ />
+ <parallel-draft-comment-row
+ v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
+ :key="`drafts-${index}`"
+ :line="line"
+ :diff-file-content-sha="diffFile.file_hash"
+ />
+ </template>
+ </tbody>
+ </table>
</template>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 8fc3af15bea..30be2e68e76 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,6 +1,7 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
@@ -30,8 +31,9 @@ export default {
filteredTreeList() {
const search = this.search.toLowerCase().trim();
- if (search === '' || this.$options.fuzzyFileFinderEnabled)
+ if (search === '') {
return this.renderTreeList ? this.tree : this.allBlobs;
+ }
return this.allBlobs.reduce((acc, folder) => {
const tree = folder.tree.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
@@ -51,13 +53,14 @@ export default {
},
},
methods: {
- ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile', 'toggleFileFinder']),
+ ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
},
},
- shortcutKeyCharacter: `${/Mac/i.test(navigator.userAgent) ? '&#8984;' : 'Ctrl'}+P`,
- diffTreeFiltering: gon.features && gon.features.diffTreeFiltering,
+ searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), {
+ modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl',
+ }),
};
</script>
@@ -66,36 +69,24 @@ export default {
<div class="append-bottom-8 position-relative tree-list-search d-flex">
<div class="flex-fill d-flex">
<icon name="search" class="position-absolute tree-list-icon" />
- <template v-if="$options.diffTreeFiltering">
- <input
- v-model="search"
- :placeholder="s__('MergeRequest|Filter files')"
- type="search"
- class="form-control"
- />
- <button
- v-show="search"
- :aria-label="__('Clear search')"
- type="button"
- class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
- @click="clearSearch"
- >
- <icon name="close" />
- </button>
- </template>
- <template v-else>
- <button
- type="button"
- class="form-control text-left text-secondary"
- @click="toggleFileFinder(true)"
- >
- {{ s__('MergeRequest|Search files') }}
- </button>
- <span
- class="position-absolute text-secondary diff-tree-search-shortcut"
- v-html="$options.shortcutKeyCharacter"
- ></span>
- </template>
+ <label for="diff-tree-search" class="sr-only">{{ $options.searchPlaceholder }}</label>
+ <input
+ id="diff-tree-search"
+ v-model="search"
+ :placeholder="$options.searchPlaceholder"
+ type="search"
+ name="diff-tree-search"
+ class="form-control"
+ />
+ <button
+ v-show="search"
+ :aria-label="__('Clear search')"
+ type="button"
+ class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
+ @click="clearSearch"
+ >
+ <icon name="close" />
+ </button>
</div>
</div>
<div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 7002655ea49..d84e1af11f3 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -42,3 +42,18 @@ export const INITIAL_TREE_WIDTH = 320;
export const MIN_TREE_WIDTH = 240;
export const MAX_TREE_WIDTH = 400;
export const TREE_HIDE_STATS_WIDTH = 260;
+
+export const OLD_LINE_KEY = 'old_line';
+export const NEW_LINE_KEY = 'new_line';
+export const TYPE_KEY = 'type';
+export const LEFT_LINE_KEY = 'left';
+
+export const CENTERED_LIMITED_CONTAINER_CLASSES =
+ 'container-limited limit-container-width mx-lg-auto px-3';
+
+export const MAX_RENDERING_DIFF_LINES = 500;
+export const MAX_RENDERING_BULK_ROWS = 30;
+export const MIN_RENDERING_MS = 2;
+export const START_RENDERING_INDEX = 200;
+export const INLINE_DIFF_LINES_KEY = 'highlighted_diff_lines';
+export const PARALLEL_DIFF_LINES_KEY = 'parallel_diff_lines';
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 63954d9d412..1d897bca1dd 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -71,6 +71,7 @@ export default function initDiffsApp(store) {
helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,
+ isFluidLayout: parseBoolean(dataset.isFluidLayout),
};
},
computed: {
@@ -97,6 +98,7 @@ export default function initDiffsApp(store) {
helpPagePath: this.helpPagePath,
shouldShow: this.activeTab === 'diffs',
changesEmptyStateIllustration: this.changesEmptyStateIllustration,
+ isFluidLayout: this.isFluidLayout,
},
});
},
diff --git a/app/assets/javascripts/diffs/mixins/draft_comments.js b/app/assets/javascripts/diffs/mixins/draft_comments.js
new file mode 100644
index 00000000000..dfb71bf38ce
--- /dev/null
+++ b/app/assets/javascripts/diffs/mixins/draft_comments.js
@@ -0,0 +1,10 @@
+export default {
+ computed: {
+ shouldRenderDraftRow: () => () => false,
+ shouldRenderParallelDraftRow: () => () => false,
+ draftForLine: () => () => ({}),
+ imageDiscussions() {
+ return this.diffFile.discussions;
+ },
+ },
+};
diff --git a/app/assets/javascripts/diffs/mixins/image_diff.js b/app/assets/javascripts/diffs/mixins/image_diff.js
new file mode 100644
index 00000000000..9067ea6f8b3
--- /dev/null
+++ b/app/assets/javascripts/diffs/mixins/image_diff.js
@@ -0,0 +1,13 @@
+import { mapActions } from 'vuex';
+
+export default {
+ methods: {
+ ...mapActions(['toggleDiscussion']),
+ clickedToggle(discussion) {
+ this.toggleDiscussion({ discussionId: discussion.id });
+ },
+ toggleText(discussion, index) {
+ return index + 1;
+ },
+ },
+};
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 82ff2e3be76..479afc50113 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -7,7 +7,12 @@ import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/uti
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
import TreeWorker from '../workers/tree_worker';
import eventHub from '../../notes/event_hub';
-import { getDiffPositionByLineCode, getNoteFormData } from './utils';
+import {
+ getDiffPositionByLineCode,
+ getNoteFormData,
+ convertExpandLines,
+ idleCallback,
+} from './utils';
import * as types from './mutation_types';
import {
PARALLEL_DIFF_VIEW_TYPE,
@@ -17,6 +22,16 @@ import {
TREE_LIST_STORAGE_KEY,
WHITESPACE_STORAGE_KEY,
TREE_LIST_WIDTH_STORAGE_KEY,
+ OLD_LINE_KEY,
+ NEW_LINE_KEY,
+ TYPE_KEY,
+ LEFT_LINE_KEY,
+ MAX_RENDERING_DIFF_LINES,
+ MAX_RENDERING_BULK_ROWS,
+ MIN_RENDERING_MS,
+ START_RENDERING_INDEX,
+ INLINE_DIFF_LINES_KEY,
+ PARALLEL_DIFF_LINES_KEY,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
@@ -37,7 +52,7 @@ export const fetchDiffFiles = ({ state, commit }) => {
});
return axios
- .get(state.endpoint, { params: { w: state.showWhitespace ? null : '1' } })
+ .get(mergeUrlParams({ w: state.showWhitespace ? '0' : '1' }, state.endpoint))
.then(res => {
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
@@ -52,7 +67,9 @@ export const fetchDiffFiles = ({ state, commit }) => {
};
export const setHighlightedRow = ({ commit }, lineCode) => {
+ const fileHash = lineCode.split('_')[0];
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
};
// This is adding line discussions to the actual lines in the diff tree
@@ -108,7 +125,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
new Promise(resolve => {
const nextFile = state.diffFiles.find(
file =>
- !file.renderIt && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text),
+ !file.renderIt &&
+ (file.viewer && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text)),
);
if (nextFile) {
@@ -193,11 +211,12 @@ export const scrollToLineIfNeededParallel = (_, line) => {
}
};
-export const loadCollapsedDiff = ({ commit, getters }, file) =>
+export const loadCollapsedDiff = ({ commit, getters, state }, file) =>
axios
.get(file.load_collapsed_diff_url, {
params: {
commit_id: getters.commitId,
+ w: state.showWhitespace ? '0' : '1',
},
})
.then(res => {
@@ -262,13 +281,14 @@ export const scrollToFile = ({ state, commit }, path) => {
document.location.hash = fileHash;
commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
-
- setTimeout(() => commit(types.UPDATE_CURRENT_DIFF_FILE_ID, ''), 1000);
};
-export const toggleShowTreeList = ({ commit, state }) => {
+export const toggleShowTreeList = ({ commit, state }, saving = true) => {
commit(types.TOGGLE_SHOW_TREE_LIST);
- localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList);
+
+ if (saving) {
+ localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList);
+ }
};
export const openDiffFileCommentForm = ({ commit, getters }, formData) => {
@@ -297,8 +317,10 @@ export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = fals
localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace);
if (pushState) {
- historyPushState(showWhitespace ? '?w=0' : '?w=1');
+ historyPushState(mergeUrlParams({ w: showWhitespace ? '0' : '1' }, window.location.href));
}
+
+ eventHub.$emit('refetchDiffData');
};
export const toggleFileFinder = ({ commit }, visible) => {
@@ -309,5 +331,129 @@ export const cacheTreeListWidth = (_, size) => {
localStorage.setItem(TREE_LIST_WIDTH_STORAGE_KEY, size);
};
+export const requestFullDiff = ({ commit }, filePath) => commit(types.REQUEST_FULL_DIFF, filePath);
+export const receiveFullDiffSucess = ({ commit }, { filePath }) =>
+ commit(types.RECEIVE_FULL_DIFF_SUCCESS, { filePath });
+export const receiveFullDiffError = ({ commit }, filePath) => {
+ commit(types.RECEIVE_FULL_DIFF_ERROR, filePath);
+ createFlash(s__('MergeRequest|Error loading full diff. Please try again.'));
+};
+
+export const setExpandedDiffLines = ({ commit, state }, { file, data }) => {
+ const expandedDiffLines = {
+ highlighted_diff_lines: convertExpandLines({
+ diffLines: file.highlighted_diff_lines,
+ typeKey: TYPE_KEY,
+ oldLineKey: OLD_LINE_KEY,
+ newLineKey: NEW_LINE_KEY,
+ data,
+ mapLine: ({ line, oldLine, newLine }) =>
+ Object.assign(line, {
+ old_line: oldLine,
+ new_line: newLine,
+ line_code: `${file.file_hash}_${oldLine}_${newLine}`,
+ }),
+ }),
+ parallel_diff_lines: convertExpandLines({
+ diffLines: file.parallel_diff_lines,
+ typeKey: [LEFT_LINE_KEY, TYPE_KEY],
+ oldLineKey: [LEFT_LINE_KEY, OLD_LINE_KEY],
+ newLineKey: [LEFT_LINE_KEY, NEW_LINE_KEY],
+ data,
+ mapLine: ({ line, oldLine, newLine }) => ({
+ left: {
+ ...line,
+ old_line: oldLine,
+ line_code: `${file.file_hash}_${oldLine}_${newLine}`,
+ },
+ right: {
+ ...line,
+ new_line: newLine,
+ line_code: `${file.file_hash}_${newLine}_${oldLine}`,
+ },
+ }),
+ }),
+ };
+ const currentDiffLinesKey =
+ state.diffViewType === INLINE_DIFF_VIEW_TYPE ? INLINE_DIFF_LINES_KEY : PARALLEL_DIFF_LINES_KEY;
+ const hiddenDiffLinesKey =
+ state.diffViewType === INLINE_DIFF_VIEW_TYPE ? PARALLEL_DIFF_LINES_KEY : INLINE_DIFF_LINES_KEY;
+
+ commit(types.SET_HIDDEN_VIEW_DIFF_FILE_LINES, {
+ filePath: file.file_path,
+ lines: expandedDiffLines[hiddenDiffLinesKey],
+ });
+
+ if (expandedDiffLines[currentDiffLinesKey].length > MAX_RENDERING_DIFF_LINES) {
+ let index = START_RENDERING_INDEX;
+ commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, {
+ filePath: file.file_path,
+ lines: expandedDiffLines[currentDiffLinesKey].slice(0, index),
+ });
+ commit(types.TOGGLE_DIFF_FILE_RENDERING_MORE, file.file_path);
+
+ const idleCb = t => {
+ const startIndex = index;
+
+ while (
+ t.timeRemaining() >= MIN_RENDERING_MS &&
+ index !== expandedDiffLines[currentDiffLinesKey].length &&
+ index - startIndex !== MAX_RENDERING_BULK_ROWS
+ ) {
+ const line = expandedDiffLines[currentDiffLinesKey][index];
+
+ if (line) {
+ commit(types.ADD_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: file.file_path, line });
+ index += 1;
+ }
+ }
+
+ if (index !== expandedDiffLines[currentDiffLinesKey].length) {
+ idleCallback(idleCb);
+ } else {
+ commit(types.TOGGLE_DIFF_FILE_RENDERING_MORE, file.file_path);
+ }
+ };
+
+ idleCallback(idleCb);
+ } else {
+ commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, {
+ filePath: file.file_path,
+ lines: expandedDiffLines[currentDiffLinesKey],
+ });
+ }
+};
+
+export const fetchFullDiff = ({ dispatch }, file) =>
+ axios
+ .get(file.context_lines_path, {
+ params: {
+ full: true,
+ from_merge_request: true,
+ },
+ })
+ .then(({ data }) => {
+ dispatch('receiveFullDiffSucess', { filePath: file.file_path });
+ dispatch('setExpandedDiffLines', { file, data });
+ })
+ .catch(() => dispatch('receiveFullDiffError', file.file_path));
+
+export const toggleFullDiff = ({ dispatch, getters, state }, filePath) => {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+
+ dispatch('requestFullDiff', filePath);
+
+ if (file.isShowingFullFile) {
+ dispatch('loadCollapsedDiff', file)
+ .then(() => dispatch('assignDiscussionsToDiff', getters.getDiffFileDiscussions(file)))
+ .catch(() => dispatch('receiveFullDiffError', filePath));
+ } else {
+ dispatch('fetchFullDiff', file);
+ }
+};
+
+export const setFileCollapsed = ({ commit }, { filePath, collapsed }) =>
+ commit(types.SET_FILE_COLLAPSED, { filePath, collapsed });
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 4e7e5306995..bc27e263bff 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -100,5 +100,12 @@ export const diffFilesLength = state => state.diffFiles.length;
export const getCommentFormForDiffFile = state => fileHash =>
state.commentForms.find(form => form.fileHash === fileHash);
+/**
+ * Returns index of a currently selected diff in diffFiles
+ * @returns {number}
+ */
+export const currentDiffIndex = state =>
+ Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId));
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 47f78a5db54..cf4dd93dbfb 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,13 +1,10 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
-import bp from '~/breakpoints';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
+import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
-const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
export default () => ({
isLoading: true,
@@ -23,8 +20,7 @@ export default () => ({
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
- showTreeList:
- storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : parseBoolean(storedTreeShow),
+ showTreeList: true,
currentDiffFileId: '',
projectPath: '',
commentForms: [],
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 71ad108ce88..6bb24c97139 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -23,3 +23,13 @@ export const SET_TREE_DATA = 'SET_TREE_DATA';
export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE';
+
+export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF';
+export const RECEIVE_FULL_DIFF_SUCCESS = 'RECEIVE_FULL_DIFF_SUCCESS';
+export const RECEIVE_FULL_DIFF_ERROR = 'RECEIVE_FULL_DIFF_ERROR';
+export const SET_FILE_COLLAPSED = 'SET_FILE_COLLAPSED';
+
+export const SET_HIDDEN_VIEW_DIFF_FILE_LINES = 'SET_HIDDEN_VIEW_DIFF_FILE_LINES';
+export const SET_CURRENT_VIEW_DIFF_FILE_LINES = 'SET_CURRENT_VIEW_DIFF_FILE_LINES';
+export const ADD_CURRENT_VIEW_DIFF_FILE_LINES = 'ADD_CURRENT_VIEW_DIFF_FILE_LINES';
+export const TOGGLE_DIFF_FILE_RENDERING_MORE = 'TOGGLE_DIFF_FILE_RENDERING_MORE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 5a27388863c..67bc1724738 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -102,7 +102,10 @@ export default {
[types.EXPAND_ALL_FILES](state) {
state.diffFiles = state.diffFiles.map(file => ({
...file,
- collapsed: false,
+ viewer: {
+ ...file.viewer,
+ collapsed: false,
+ },
}));
},
@@ -155,7 +158,9 @@ export default {
}
if (!file.parallel_diff_lines || !file.highlighted_diff_lines) {
- file.discussions = (file.discussions || []).concat(discussion);
+ file.discussions = (file.discussions || [])
+ .filter(d => d.id !== discussion.id)
+ .concat(discussion);
}
return file;
@@ -248,4 +253,53 @@ export default {
[types.TOGGLE_FILE_FINDER_VISIBLE](state, visible) {
state.fileFinderVisible = visible;
},
+ [types.REQUEST_FULL_DIFF](state, filePath) {
+ const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+
+ file.isLoadingFullFile = true;
+ },
+ [types.RECEIVE_FULL_DIFF_ERROR](state, filePath) {
+ const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+
+ file.isLoadingFullFile = false;
+ },
+ [types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath }) {
+ const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+
+ file.isShowingFullFile = true;
+ file.isLoadingFullFile = false;
+ },
+ [types.SET_FILE_COLLAPSED](state, { filePath, collapsed }) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+
+ if (file && file.viewer) {
+ file.viewer.collapsed = collapsed;
+ }
+ },
+ [types.SET_HIDDEN_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+ const hiddenDiffLinesKey =
+ state.diffViewType === 'inline' ? 'parallel_diff_lines' : 'highlighted_diff_lines';
+
+ file[hiddenDiffLinesKey] = lines;
+ },
+ [types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+ const currentDiffLinesKey =
+ state.diffViewType === 'inline' ? 'highlighted_diff_lines' : 'parallel_diff_lines';
+
+ file[currentDiffLinesKey] = lines;
+ },
+ [types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, line }) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+ const currentDiffLinesKey =
+ state.diffViewType === 'inline' ? 'highlighted_diff_lines' : 'parallel_diff_lines';
+
+ file[currentDiffLinesKey].push(line);
+ },
+ [types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, filePath) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+
+ file.renderingLines = !file.renderingLines;
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 247d1e65fea..71956255eef 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -15,8 +15,8 @@ import {
TREE_TYPE,
} from '../constants';
-export function findDiffFile(files, hash) {
- return files.filter(file => file.file_hash === hash)[0];
+export function findDiffFile(files, match, matchKey = 'file_hash') {
+ return files.find(file => file[matchKey] === match);
}
export const getReversePosition = linePosition => {
@@ -250,7 +250,10 @@ export function prepareDiffData(diffData) {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
collapsed:
file.viewer.name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ isShowingFullFile: false,
+ isLoadingFullFile: false,
discussions: [],
+ renderingLines: false,
});
}
}
@@ -411,3 +414,43 @@ export const getDiffMode = diffFile => {
diffModes.replaced
);
};
+
+export const convertExpandLines = ({
+ diffLines,
+ data,
+ typeKey,
+ oldLineKey,
+ newLineKey,
+ mapLine,
+}) => {
+ const dataLength = data.length;
+ const lines = [];
+
+ for (let i = 0, diffLinesLength = diffLines.length; i < diffLinesLength; i += 1) {
+ const line = diffLines[i];
+
+ if (_.property(typeKey)(line) === 'match') {
+ const beforeLine = diffLines[i - 1];
+ const afterLine = diffLines[i + 1];
+ const newLineProperty = _.property(newLineKey);
+ const beforeLineIndex = newLineProperty(beforeLine) || 0;
+ const afterLineIndex = newLineProperty(afterLine) - 1 || dataLength;
+
+ lines.push(
+ ...data.slice(beforeLineIndex, afterLineIndex).map((l, index) =>
+ mapLine({
+ line: Object.assign(l, { hasForm: false, discussions: [] }),
+ oldLine: (_.property(oldLineKey)(beforeLine) || 0) + index + 1,
+ newLine: (newLineProperty(beforeLine) || 0) + index + 1,
+ }),
+ ),
+ );
+ } else {
+ lines.push(line);
+ }
+ }
+
+ return lines;
+};
+
+export const idleCallback = cb => requestIdleCallback(cb);
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
index 534d737c77e..415c463fd19 100644
--- a/app/assets/javascripts/diffs/workers/tree_worker.js
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -4,6 +4,11 @@ import { generateTreeList } from '../store/utils';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data } = e;
+
+ if (data === undefined) {
+ return;
+ }
+
const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
index 00e41dd0301..0fcaec9531c 100644
--- a/app/assets/javascripts/dirty_submit/dirty_submit_form.js
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import $ from 'jquery';
class DirtySubmitForm {
constructor(form) {
@@ -20,12 +21,18 @@ class DirtySubmitForm {
}
registerListeners() {
- const throttledUpdateDirtyInput = _.throttle(
- event => this.updateDirtyInput(event),
- DirtySubmitForm.THROTTLE_DURATION,
+ const getThrottledHandlerForInput = _.memoize(() =>
+ _.throttle(event => this.updateDirtyInput(event), DirtySubmitForm.THROTTLE_DURATION),
);
+
+ const throttledUpdateDirtyInput = event => {
+ const throttledHandler = getThrottledHandlerForInput(event.target.name);
+ throttledHandler(event);
+ };
+
this.form.addEventListener('input', throttledUpdateDirtyInput);
this.form.addEventListener('change', throttledUpdateDirtyInput);
+ $(this.form).on('change.select2', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 9987fbcb6a7..0ff26445a6a 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -4,6 +4,7 @@ import _ from 'underscore';
import './behaviors/preview_markdown';
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';
+import { n__, __ } from '~/locale';
Dropzone.autoDiscover = false;
@@ -90,7 +91,7 @@ export default function dropzoneInput(form) {
if (!processingFileCount) $attachButton.removeClass('hide');
addFileToForm(response.link.url);
},
- error: (file, errorMessage = 'Attaching the file failed.', xhr) => {
+ error: (file, errorMessage = __('Attaching the file failed.'), xhr) => {
// If 'error' event is fired by dropzone, the second parameter is error message.
// If the 'errorMessage' parameter is empty, the default error message is set.
// If the 'error' event is fired by backend (xhr) error response, the third parameter is
@@ -273,19 +274,11 @@ export default function dropzoneInput(form) {
};
updateAttachingMessage = (files, messageContainer) => {
- let attachingMessage;
const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued')
.length;
+ const attachingMessage = n__('Attaching a file', 'Attaching %d files', filesCount);
- // Dinamycally change uploading files text depending on files number in
- // dropzone files queue.
- if (filesCount > 1) {
- attachingMessage = `Attaching ${filesCount} files -`;
- } else {
- attachingMessage = 'Attaching a file -';
- }
-
- messageContainer.text(attachingMessage);
+ messageContainer.text(`${attachingMessage} -`);
};
form.find('.markdown-selector').click(function onMarkdownClick(e) {
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index cb1b1173190..3c650397a19 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -104,7 +104,7 @@ class DueDateSelect {
const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
this.displayedDate = dateFormat(dateObj, 'mmm d, yyyy');
} else {
- this.displayedDate = 'No due date';
+ this.displayedDate = __('None');
}
}
@@ -132,7 +132,7 @@ class DueDateSelect {
submitSelectedDate(isDropdown) {
const selectedDateValue = this.datePayload[this.abilityName].due_date;
- const hasDueDate = this.displayedDate !== 'No due date';
+ const hasDueDate = this.displayedDate !== __('None');
const displayedDateStyle = hasDueDate ? 'bold' : 'no-value';
this.$loading.removeClass('hidden').fadeIn();
diff --git a/app/assets/javascripts/emoji/no_emoji_validator.js b/app/assets/javascripts/emoji/no_emoji_validator.js
index 0fd4dd74953..384d62a133a 100644
--- a/app/assets/javascripts/emoji/no_emoji_validator.js
+++ b/app/assets/javascripts/emoji/no_emoji_validator.js
@@ -1,10 +1,11 @@
import { __ } from '~/locale';
import emojiRegex from 'emoji-regex';
+import InputValidator from '../validators/input_validator';
-const invalidInputClass = 'gl-field-error-outline';
-
-export default class NoEmojiValidator {
+export default class NoEmojiValidator extends InputValidator {
constructor(opts = {}) {
+ super();
+
const container = opts.container || '';
this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
@@ -19,45 +20,14 @@ export default class NoEmojiValidator {
const { value } = this.inputDomElement;
+ this.errorMessage = __('Invalid input, please avoid emojis');
+
this.validatePattern(value);
this.setValidationStateAndMessage();
}
validatePattern(value) {
const pattern = emojiRegex();
- this.hasEmojis = new RegExp(pattern).test(value);
-
- if (this.hasEmojis) {
- this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis'));
- } else {
- this.inputDomElement.setCustomValidity('');
- }
- }
-
- setValidationStateAndMessage() {
- if (!this.inputDomElement.checkValidity()) {
- this.setInvalidState();
- } else {
- this.clearFieldValidationState();
- }
- }
-
- clearFieldValidationState() {
- this.inputDomElement.classList.remove(invalidInputClass);
- this.inputErrorMessage.classList.add('hide');
- }
-
- setInvalidState() {
- this.inputDomElement.classList.add(invalidInputClass);
- this.setErrorMessage();
- }
-
- setErrorMessage() {
- if (this.hasEmojis) {
- this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage;
- } else {
- this.inputErrorMessage.innerHTML = this.inputDomElement.title;
- }
- this.inputErrorMessage.classList.remove('hide');
+ this.invalidInput = new RegExp(pattern).test(value);
}
}
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
new file mode 100644
index 00000000000..70b5c6b0094
--- /dev/null
+++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
@@ -0,0 +1,108 @@
+<script>
+/**
+ * Render modal to confirm rollback/redeploy.
+ */
+
+import _ from 'underscore';
+import { GlModal } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+
+import eventHub from '../event_hub';
+
+export default {
+ name: 'ConfirmRollbackModal',
+
+ components: {
+ GlModal,
+ },
+
+ props: {
+ environment: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ modalTitle() {
+ const title = this.environment.isLastDeployment
+ ? s__('Environments|Re-deploy environment %{name}?')
+ : s__('Environments|Rollback environment %{name}?');
+
+ return sprintf(title, {
+ name: _.escape(this.environment.name),
+ });
+ },
+
+ commitShortSha() {
+ const { last_deployment } = this.environment;
+ return this.commitData(last_deployment, 'short_id');
+ },
+
+ commitUrl() {
+ const { last_deployment } = this.environment;
+ return this.commitData(last_deployment, 'commit_path');
+ },
+
+ commitTitle() {
+ const { last_deployment } = this.environment;
+ return this.commitData(last_deployment, 'title');
+ },
+
+ modalText() {
+ const linkStart = `<a class="commit-sha mr-0" href="${_.escape(this.commitUrl)}">`;
+ const commitId = _.escape(this.commitShortSha);
+ const linkEnd = '</a>';
+ const name = _.escape(this.name);
+ const body = this.environment.isLastDeployment
+ ? s__(
+ 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?',
+ )
+ : s__(
+ 'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?',
+ );
+ return sprintf(
+ body,
+ {
+ commitId,
+ linkStart,
+ linkEnd,
+ name,
+ },
+ false,
+ );
+ },
+
+ modalActionText() {
+ return this.environment.isLastDeployment
+ ? s__('Environments|Re-deploy')
+ : s__('Environments|Rollback');
+ },
+ },
+
+ methods: {
+ onOk() {
+ eventHub.$emit('rollbackEnvironment', this.environment);
+ },
+
+ commitData(lastDeployment, key) {
+ if (lastDeployment && lastDeployment.commit) {
+ return lastDeployment.commit[key];
+ }
+
+ return '';
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ :title="modalTitle"
+ modal-id="confirm-rollback-modal"
+ :ok-title="modalActionText"
+ ok-variant="danger"
+ @ok="onOk"
+ >
+ <p v-html="modalText"></p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 6ece8b92a30..be80661223c 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -1,14 +1,16 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
-import tablePagination from '../../vue_shared/components/table_pagination.vue';
-import environmentTable from '../components/environments_table.vue';
+import TablePagination from '~/vue_shared/components/table_pagination.vue';
+import containerMixin from 'ee_else_ce/environments/mixins/container_mixin';
+import EnvironmentTable from '../components/environments_table.vue';
export default {
components: {
- environmentTable,
- tablePagination,
+ EnvironmentTable,
+ TablePagination,
GlLoadingIcon,
},
+ mixins: [containerMixin],
props: {
isLoading: {
type: Boolean,
@@ -47,7 +49,15 @@ export default {
<slot name="emptyState"></slot>
<div v-if="!isLoading && environments.length > 0" class="table-holder">
- <environment-table :environments="environments" :can-read-environment="canReadEnvironment" />
+ <environment-table
+ :environments="environments"
+ :can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
+ />
<table-pagination
v-if="pagination && pagination.totalPages > 1"
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 503c1b38f71..f0e80cba753 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -3,8 +3,8 @@ import Timeago from 'timeago.js';
import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import { humanize } from '~/lib/utils/text_utility';
import Icon from '~/vue_shared/components/icon.vue';
+import environmentItemMixin from 'ee_else_ce/environments/mixins/environment_item_mixin';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
@@ -35,10 +35,10 @@ export default {
TerminalButtonComponent,
MonitoringButtonComponent,
},
-
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [environmentItemMixin],
props: {
model: {
@@ -156,7 +156,7 @@ export default {
const combinedActions = (manualActions || []).concat(scheduledActions || []);
return combinedActions.map(action => ({
...action,
- name: humanize(action.name),
+ name: action.name,
}));
},
@@ -459,19 +459,37 @@ export default {
class="gl-responsive-table-row"
role="row"
>
- <div
- v-gl-tooltip
- :title="model.name"
- class="table-section section-wrap section-15 text-truncate"
- role="gridcell"
- >
+ <div class="table-section section-wrap section-15 text-truncate" role="gridcell">
<div v-if="!model.isFolder" class="table-mobile-header" role="rowheader">
{{ s__('Environments|Environment') }}
</div>
- <span v-if="!model.isFolder" class="environment-name table-mobile-content">
- <a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a>
+
+ <span v-if="shouldRenderDeployBoard" class="deploy-board-icon" @click="toggleDeployBoard">
+ <icon :name="deployIconName" />
+ </span>
+
+ <span
+ v-if="!model.isFolder"
+ v-gl-tooltip
+ :title="model.name"
+ class="environment-name table-mobile-content"
+ >
+ <a class="qa-environment-link" :href="environmentPath">
+ <span v-if="model.size === 1">{{ model.name }}</span>
+ <span v-else>{{ model.name_without_type }}</span>
+ </a>
+ <span v-if="isProtected" class="badge badge-success">
+ {{ s__('Environments|protected') }}
+ </span>
</span>
- <span v-else class="folder-name" role="button" @click="onClickFolder">
+ <span
+ v-else
+ v-gl-tooltip
+ :title="model.folderName"
+ class="folder-name"
+ role="button"
+ @click="onClickFolder"
+ >
<icon :name="folderIconName" class="folder-icon" />
<icon name="folder" class="folder-icon" />
@@ -486,22 +504,28 @@ export default {
class="table-section section-10 deployment-column d-none d-sm-none d-md-block"
role="gridcell"
>
- <span v-if="shouldRenderDeploymentID"> {{ deploymentInternalId }} </span>
+ <span v-if="shouldRenderDeploymentID" class="text-break-word">
+ {{ deploymentInternalId }}
+ </span>
- <span v-if="!model.isFolder && deploymentHasUser">
+ <span v-if="!model.isFolder && deploymentHasUser" class="text-break-word">
by
<user-avatar-link
:link-href="deploymentUser.web_url"
:img-src="deploymentUser.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="deploymentUser.username"
- class="js-deploy-user-container"
+ class="js-deploy-user-container float-none"
/>
</span>
</div>
<div class="table-section section-15 d-none d-sm-none d-md-block" role="gridcell">
- <a v-if="shouldRenderBuildName" :href="buildPath" class="build-link flex-truncate-parent">
+ <a
+ v-if="shouldRenderBuildName"
+ :href="buildPath"
+ class="build-link cgray flex-truncate-parent"
+ >
<span class="flex-truncate-child">{{ buildName }}</span>
</a>
</div>
@@ -556,6 +580,7 @@ export default {
<rollback-component
v-if="canRetry"
+ :environment="model"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
/>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index 50c86af057c..bafbc00597e 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -5,29 +5,38 @@
*
* Makes a post request when the button is clicked.
*/
+import { GlTooltipDirective, GlLoadingIcon, GlModalDirective, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
-import { GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import eventHub from '../event_hub';
export default {
components: {
Icon,
GlLoadingIcon,
+ GlButton,
+ ConfirmRollbackModal,
},
directives: {
GlTooltip: GlTooltipDirective,
+ GlModal: GlModalDirective,
},
props: {
- retryUrl: {
- type: String,
- default: '',
- },
-
isLastDeployment: {
type: Boolean,
default: true,
},
+
+ environment: {
+ type: Object,
+ required: true,
+ },
+
+ retryUrl: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -45,23 +54,30 @@ export default {
methods: {
onClick() {
- this.isLoading = true;
-
- eventHub.$emit('postAction', { endpoint: this.retryUrl });
+ eventHub.$emit('requestRollbackEnvironment', {
+ ...this.environment,
+ retryUrl: this.retryUrl,
+ isLastDeployment: this.isLastDeployment,
+ });
+ eventHub.$on('rollbackEnvironment', environment => {
+ if (environment.id === this.environment.id) {
+ this.isLoading = true;
+ }
+ });
},
},
};
</script>
<template>
- <button
+ <gl-button
v-gl-tooltip
+ v-gl-modal.confirm-rollback-modal
:disabled="isLoading"
:title="title"
- type="button"
- class="btn d-none d-sm-none d-md-block"
+ class="d-none d-md-block text-secondary"
@click="onClick"
>
<icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" />
<gl-loading-icon v-if="isLoading" />
- </button>
+ </gl-button>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 6d74d136a94..13195d32cc4 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -39,7 +39,7 @@ export default {
:aria-label="title"
:href="terminalPath"
:class="{ disabled: disabled }"
- class="btn terminal-button d-none d-sm-none d-md-block"
+ class="btn terminal-button d-none d-sm-none d-md-block text-secondary"
>
<icon name="terminal" />
</a>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index aa2417d3194..ec78240217b 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -1,4 +1,5 @@
<script>
+import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin';
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
@@ -6,14 +7,16 @@ import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from './stop_environment_modal.vue';
+import ConfirmRollbackModal from './confirm_rollback_modal.vue';
export default {
components: {
emptyState,
StopEnvironmentModal,
+ ConfirmRollbackModal,
},
- mixins: [CIPaginationMixin, environmentsMixin],
+ mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin],
props: {
endpoint: {
@@ -87,14 +90,15 @@ export default {
<template>
<div :class="cssContainerClass">
<stop-environment-modal :environment="environmentInStopModal" />
+ <confirm-rollback-modal :environment="environmentInRollbackModal" />
<div class="top-area">
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
<div v-if="canCreateEnvironment && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-success">{{
- s__('Environments|New environment')
- }}</a>
+ <a :href="newEnvironmentPath" class="btn btn-success">
+ {{ s__('Environments|New environment') }}
+ </a>
</div>
</div>
@@ -103,6 +107,11 @@ export default {
:environments="state.environments"
:pagination="state.paginationInformation"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
>
<empty-state
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index e2c304de00a..55613d815ce 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -3,27 +3,40 @@
* Render environments table.
*/
import { GlLoadingIcon } from '@gitlab/ui';
-import environmentItem from './environment_item.vue';
+import _ from 'underscore';
+import environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin';
+import EnvironmentItem from './environment_item.vue';
export default {
components: {
- environmentItem,
+ EnvironmentItem,
GlLoadingIcon,
+ DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'),
+ CanaryDeploymentCallout: () =>
+ import('ee_component/environments/components/canary_deployment_callout.vue'),
},
-
+ mixins: [environmentTableMixin],
props: {
environments: {
type: Array,
required: true,
default: () => [],
},
-
canReadEnvironment: {
type: Boolean,
required: false,
default: false,
},
},
+ computed: {
+ sortedEnvironments() {
+ return this.sortEnvironments(this.environments).map(env =>
+ this.shouldRenderFolderContent(env)
+ ? { ...env, children: this.sortEnvironments(env.children) }
+ : env,
+ );
+ },
+ },
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
@@ -31,6 +44,30 @@ export default {
shouldRenderFolderContent(env) {
return env.isFolder && env.isOpen && env.children && env.children.length > 0;
},
+ sortEnvironments(environments) {
+ /*
+ * The sorting algorithm should sort in the following priorities:
+ *
+ * 1. folders first,
+ * 2. last updated descending,
+ * 3. by name ascending,
+ *
+ * the sorting algorithm must:
+ *
+ * 1. Sort by name ascending,
+ * 2. Reverse (sort by name descending),
+ * 3. Sort by last deployment ascending,
+ * 4. Reverse (last deployment descending, name ascending),
+ * 5. Put folders first.
+ */
+ return _.chain(environments)
+ .sortBy(env => (env.isFolder ? env.folderName : env.name))
+ .reverse()
+ .sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000'))
+ .reverse()
+ .sortBy(env => (env.isFolder ? -1 : 1))
+ .value();
+ },
},
};
</script>
@@ -53,7 +90,7 @@ export default {
{{ s__('Environments|Updated') }}
</div>
</div>
- <template v-for="(model, i) in environments" :model="model">
+ <template v-for="(model, i) in sortedEnvironments" :model="model">
<div
is="environment-item"
:key="`environment-item-${i}`"
@@ -61,6 +98,21 @@ export default {
:can-read-environment="canReadEnvironment"
/>
+ <div
+ v-if="shouldRenderDeployBoard(model)"
+ :key="`deploy-board-row-${i}`"
+ class="js-deploy-board-row"
+ >
+ <div class="deploy-board-container">
+ <deploy-board
+ :deploy-board-data="model.deployBoardData"
+ :is-loading="model.isLoadingDeployBoard"
+ :is-empty="model.isEmptyDeployBoard"
+ :logs-path="model.logs_path"
+ />
+ </div>
+ </div>
+
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
<gl-loading-icon :size="2" class="prepend-top-16" />
@@ -77,13 +129,24 @@ export default {
<div :key="`sub-div-${i}`">
<div class="text-center prepend-top-10">
- <a :href="folderUrl(model)" class="btn btn-default">{{
- s__('Environments|Show all')
- }}</a>
+ <a :href="folderUrl(model)" class="btn btn-default">
+ {{ s__('Environments|Show all') }}
+ </a>
</div>
</div>
</template>
</template>
+
+ <template v-if="shouldShowCanaryCallout(model)">
+ <canary-deployment-callout
+ :key="`canary-promo-${i}`"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
+ :data-js-canary-promo-key="i"
+ />
+ </template>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 56e7f69cad6..c1bfe8d05fe 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsFolderApp from './environments_folder_view.vue';
import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
@@ -11,6 +12,7 @@ export default () =>
components: {
environmentsFolderApp,
},
+ mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -28,6 +30,7 @@ export default () =>
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canReadEnvironment: this.canReadEnvironment,
+ ...this.canaryCalloutProps,
},
});
},
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 80f0e00400b..6fd0561f682 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -1,4 +1,5 @@
<script>
+import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view_mixin';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
@@ -8,7 +9,7 @@ export default {
StopEnvironmentModal,
},
- mixins: [environmentsMixin, CIPaginationMixin],
+ mixins: [environmentsMixin, CIPaginationMixin, folderMixin],
props: {
endpoint: {
@@ -41,7 +42,8 @@ export default {
<div v-if="!isLoading" class="top-area">
<h4 class="js-folder-name environments-folder-name">
- {{ s__('Environments|Environments') }} / <b>{{ folderName }}</b>
+ {{ s__('Environments|Environments') }} /
+ <b>{{ folderName }}</b>
</h4>
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
@@ -52,6 +54,11 @@ export default {
:environments="state.environments"
:pagination="state.paginationInformation"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
/>
</div>
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index 6af66d0f86e..b53d42f202b 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
@@ -11,6 +12,7 @@ export default () =>
components: {
environmentsComponent,
},
+ mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -32,6 +34,7 @@ export default () =>
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canReadEnvironment: this.canReadEnvironment,
+ ...this.canaryCalloutProps,
},
});
},
diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
new file mode 100644
index 00000000000..f6d3d67b777
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
@@ -0,0 +1,5 @@
+export default {
+ computed: {
+ canaryCalloutProps() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/container_mixin.js b/app/assets/javascripts/environments/mixins/container_mixin.js
new file mode 100644
index 00000000000..f2907c120f8
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/container_mixin.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environment_item_mixin.js b/app/assets/javascripts/environments/mixins/environment_item_mixin.js
new file mode 100644
index 00000000000..2dfed36ec99
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environment_item_mixin.js
@@ -0,0 +1,13 @@
+export default {
+ computed: {
+ deployIconName() {
+ return '';
+ },
+ shouldRenderDeployBoard() {
+ return false;
+ },
+ },
+ methods: {
+ toggleDeployBoard() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_app_mixin.js b/app/assets/javascripts/environments/mixins/environments_app_mixin.js
new file mode 100644
index 00000000000..fc805b9235a
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_app_mixin.js
@@ -0,0 +1,32 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ metods: {
+ toggleDeployBoard() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js
new file mode 100644
index 00000000000..e793a7cadf2
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index e81a1525df0..a5812b173dc 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -3,13 +3,13 @@
*/
import _ from 'underscore';
import Visibility from 'visibilityjs';
+import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
-import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
@@ -36,6 +36,7 @@ export default {
page: getParameterByName('page') || '1',
requestData: {},
environmentInStopModal: {},
+ environmentInRollbackModal: {},
};
},
@@ -43,7 +44,11 @@ export default {
saveData(resp) {
this.isLoading = false;
- if (_.isEqual(resp.config.params, this.requestData)) {
+ // Prevent the absence of the nested flag from causing mismatches
+ const response = this.filterNilValues(resp.config.params);
+ const request = this.filterNilValues(this.requestData);
+
+ if (_.isEqual(response, request)) {
this.store.storeAvailableCount(resp.data.available_count);
this.store.storeStoppedCount(resp.data.stopped_count);
this.store.storeEnvironments(resp.data.environments);
@@ -51,6 +56,10 @@ export default {
}
},
+ filterNilValues(obj) {
+ return _.omit(obj, value => _.isUndefined(value) || _.isNull(value));
+ },
+
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
@@ -64,10 +73,9 @@ export default {
// fetch new data
return this.service
.fetchEnvironments(this.requestData)
- .then(response => this.successCallback(response))
- .then(() => {
- // restart polling
- this.poll.restart({ data: this.requestData });
+ .then(response => {
+ this.successCallback(response);
+ this.poll.enable({ data: this.requestData, response });
})
.catch(() => {
this.errorCallback();
@@ -109,6 +117,10 @@ export default {
this.environmentInStopModal = environment;
},
+ updateRollbackModal(environment) {
+ this.environmentInRollbackModal = environment;
+ },
+
stopEnvironment(environment) {
const endpoint = environment.stop_path;
const errorMessage = s__(
@@ -116,6 +128,16 @@ export default {
);
this.postAction({ endpoint, errorMessage });
},
+
+ rollbackEnvironment(environment) {
+ const { retryUrl, isLastDeployment } = environment;
+ const errorMessage = isLastDeployment
+ ? s__('Environments|An error occurred while re-deploying the environment, please try again')
+ : s__(
+ 'Environments|An error occurred while rolling back the environment, please try again',
+ );
+ this.postAction({ endpoint: retryUrl, errorMessage });
+ },
},
computed: {
@@ -174,11 +196,17 @@ export default {
eventHub.$on('postAction', this.postAction);
eventHub.$on('requestStopEnvironment', this.updateStopModal);
eventHub.$on('stopEnvironment', this.stopEnvironment);
+
+ eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal);
+ eventHub.$on('rollbackEnvironment', this.rollbackEnvironment);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('requestStopEnvironment', this.updateStopModal);
eventHub.$off('stopEnvironment', this.stopEnvironment);
+
+ eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal);
+ eventHub.$off('rollbackEnvironment', this.rollbackEnvironment);
},
};
diff --git a/app/assets/javascripts/environments/mixins/environments_table_mixin.js b/app/assets/javascripts/environments/mixins/environments_table_mixin.js
new file mode 100644
index 00000000000..208f1a7373d
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_table_mixin.js
@@ -0,0 +1,10 @@
+export default {
+ methods: {
+ shouldShowCanaryCallout() {
+ return false;
+ },
+ shouldRenderDeployBoard() {
+ return false;
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index ac9a31c202c..5fb420e9da5 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,4 +1,6 @@
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers';
+
/**
* Environments Store.
*
@@ -31,6 +33,14 @@ export default class EnvironmentsStore {
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
*
+ * Top level environments - when the size is 1 - with `rollout_status`
+ * can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData`
+ * keys to those environments.
+ * The first key will let's us know if we should or not render the deploy board.
+ * It will be toggled when the user clicks to seee the deploy board.
+ *
+ * The second key will allow us to update the environment with the received deploy board data.
+ *
* @param {Array} environments
* @returns {Array}
*/
@@ -63,6 +73,7 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
+ filtered = setDeployBoard(oldEnvironmentState, filtered);
return filtered;
});
@@ -71,6 +82,20 @@ export default class EnvironmentsStore {
return filteredEnvironments;
}
+ /**
+ * Stores the pagination information needed to render the pagination for the
+ * table.
+ *
+ * Normalizes the headers to uppercase since they can be provided either
+ * in uppercase or lowercase.
+ *
+ * Parses to an integer the normalized ones needed for the pagination component.
+ *
+ * Stores the normalized and parsed information.
+ *
+ * @param {Object} pagination = {}
+ * @return {Object}
+ */
setPagination(pagination = {}) {
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
diff --git a/app/assets/javascripts/environments/stores/helpers.js b/app/assets/javascripts/environments/stores/helpers.js
new file mode 100644
index 00000000000..8eba6c00601
--- /dev/null
+++ b/app/assets/javascripts/environments/stores/helpers.js
@@ -0,0 +1,8 @@
+/**
+ * Deploy boards are EE only.
+ *
+ * @param {Object} environment
+ * @returns {Object}
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const setDeployBoard = (oldEnvironmentState, environment) => environment;
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 6981afe1ead..43ae54133af 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -48,7 +48,7 @@ export default {
}
},
methods: {
- ...mapActions(['startPolling']),
+ ...mapActions(['startPolling', 'restartPolling']),
},
};
</script>
@@ -56,19 +56,17 @@ export default {
<template>
<div>
<div v-if="errorTrackingEnabled">
- <div v-if="loading" class="py-3"><gl-loading-icon :size="3" /></div>
+ <div v-if="loading" class="py-3">
+ <gl-loading-icon :size="3" />
+ </div>
<div v-else>
<div class="d-flex justify-content-end">
- <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank"
- >View in Sentry <icon name="external-link" />
+ <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank">
+ {{ __('View in Sentry') }}
+ <icon name="external-link" />
</gl-button>
</div>
- <gl-table
- :items="errors"
- :fields="$options.fields"
- :show-empty="true"
- :empty-text="__('No errors to display')"
- >
+ <gl-table :items="errors" :fields="$options.fields" :show-empty="true">
<template slot="HEAD_events" slot-scope="data">
<div class="text-right">{{ data.label }}</div>
</template>
@@ -102,6 +100,14 @@ export default {
<time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div>
</template>
+ <template slot="empty">
+ <div ref="empty">
+ {{ __('No errors to display.') }}
+ <gl-link class="js-try-again" @click="restartPolling">
+ {{ __('Check again') }}
+ </gl-link>
+ </div>
+ </template>
</gl-table>
</div>
</div>
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
index 11aec312368..1e754a4f54f 100644
--- a/app/assets/javascripts/error_tracking/store/actions.js
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -6,7 +6,7 @@ import { __, sprintf } from '~/locale';
let eTagPoll;
-export function startPolling({ commit }, endpoint) {
+export function startPolling({ commit, dispatch }, endpoint) {
eTagPoll = new Poll({
resource: Service,
method: 'getErrorList',
@@ -18,8 +18,9 @@ export function startPolling({ commit }, endpoint) {
commit(types.SET_ERRORS, data.errors);
commit(types.SET_EXTERNAL_URL, data.external_url);
commit(types.SET_LOADING, false);
+ dispatch('stopPolling');
},
- errorCallback: response => {
+ errorCallback: ({ response }) => {
let errorMessage = '';
if (response && response.data && response.data.message) {
errorMessage = response.data.message;
@@ -36,4 +37,16 @@ export function startPolling({ commit }, endpoint) {
eTagPoll.makeRequest();
}
+export const stopPolling = () => {
+ if (eTagPoll) eTagPoll.stop();
+};
+
+export function restartPolling({ commit }) {
+ commit(types.SET_ERRORS, []);
+ commit(types.SET_EXTERNAL_URL, '');
+ commit(types.SET_LOADING, true);
+
+ if (eTagPoll) eTagPoll.restart();
+}
+
export default () => {};
diff --git a/app/assets/javascripts/error_tracking_settings/components/app.vue b/app/assets/javascripts/error_tracking_settings/components/app.vue
new file mode 100644
index 00000000000..50eb3e63b7c
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/app.vue
@@ -0,0 +1,129 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlButton } from '@gitlab/ui';
+import ProjectDropdown from './project_dropdown.vue';
+import ErrorTrackingForm from './error_tracking_form.vue';
+
+export default {
+ components: { ProjectDropdown, ErrorTrackingForm, GlButton },
+ props: {
+ initialApiHost: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialEnabled: {
+ type: String,
+ required: true,
+ },
+ initialProject: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ initialToken: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ listProjectsEndpoint: {
+ type: String,
+ required: true,
+ },
+ operationsSettingsEndpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters([
+ 'dropdownLabel',
+ 'hasProjects',
+ 'invalidProjectLabel',
+ 'isProjectInvalid',
+ 'projectSelectionLabel',
+ ]),
+ ...mapState([
+ 'apiHost',
+ 'connectError',
+ 'connectSuccessful',
+ 'enabled',
+ 'projects',
+ 'selectedProject',
+ 'settingsLoading',
+ 'token',
+ ]),
+ },
+ created() {
+ this.setInitialState({
+ apiHost: this.initialApiHost,
+ enabled: this.initialEnabled,
+ project: this.initialProject,
+ token: this.initialToken,
+ listProjectsEndpoint: this.listProjectsEndpoint,
+ operationsSettingsEndpoint: this.operationsSettingsEndpoint,
+ });
+ },
+ methods: {
+ ...mapActions([
+ 'fetchProjects',
+ 'setInitialState',
+ 'updateApiHost',
+ 'updateEnabled',
+ 'updateSelectedProject',
+ 'updateSettings',
+ 'updateToken',
+ ]),
+ handleSubmit() {
+ this.updateSettings();
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="form-check form-group">
+ <input
+ id="error-tracking-enabled"
+ :checked="enabled"
+ class="form-check-input"
+ type="checkbox"
+ @change="updateEnabled($event.target.checked)"
+ />
+ <label class="form-check-label" for="error-tracking-enabled">{{
+ s__('ErrorTracking|Active')
+ }}</label>
+ </div>
+ <error-tracking-form
+ :api-host="apiHost"
+ :connect-error="connectError"
+ :connect-successful="connectSuccessful"
+ :token="token"
+ @handle-connect="fetchProjects"
+ @update-api-host="updateApiHost"
+ @update-token="updateToken"
+ />
+ <div class="form-group">
+ <project-dropdown
+ :has-projects="hasProjects"
+ :invalid-project-label="invalidProjectLabel"
+ :is-project-invalid="isProjectInvalid"
+ :dropdown-label="dropdownLabel"
+ :project-selection-label="projectSelectionLabel"
+ :projects="projects"
+ :selected-project="selectedProject"
+ :token="token"
+ @select-project="updateSelectedProject"
+ />
+ </div>
+ <gl-button
+ :disabled="settingsLoading"
+ class="js-error-tracking-button"
+ variant="success"
+ @click="handleSubmit"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
new file mode 100644
index 00000000000..060d8e25227
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: { GlButton, GlFormInput, Icon },
+ props: {
+ apiHost: {
+ type: String,
+ required: true,
+ },
+ connectError: {
+ type: Boolean,
+ required: true,
+ },
+ connectSuccessful: {
+ type: Boolean,
+ required: true,
+ },
+ token: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ tokenInputState() {
+ return this.connectError ? false : null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="form-group">
+ <label class="label-bold" for="error-tracking-api-host">{{ __('Sentry API URL') }}</label>
+ <div class="row">
+ <div class="col-8 col-md-9 gl-pr-0">
+ <gl-form-input
+ id="error-tracking-api-host"
+ :value="apiHost"
+ placeholder="https://mysentryserver.com"
+ @input="$emit('update-api-host', $event)"
+ />
+ </div>
+ </div>
+ <p class="form-text text-muted">
+ {{ s__('ErrorTracking|Find your hostname in your Sentry account settings page') }}
+ </p>
+ </div>
+ <div class="form-group" :class="{ 'gl-show-field-errors': connectError }">
+ <label class="label-bold" for="error-tracking-token">{{
+ s__('ErrorTracking|Auth Token')
+ }}</label>
+ <div class="row">
+ <div class="col-8 col-md-9 gl-pr-0">
+ <gl-form-input
+ id="error-tracking-token"
+ :value="token"
+ :state="tokenInputState"
+ @input="$emit('update-token', $event)"
+ />
+ </div>
+ <div class="col-4 col-md-3 gl-pl-0">
+ <gl-button
+ class="js-error-tracking-connect prepend-left-5"
+ @click="$emit('handle-connect')"
+ >
+ {{ __('Connect') }}
+ </gl-button>
+ <icon
+ v-show="connectSuccessful"
+ class="js-error-tracking-connect-success prepend-left-5 text-success align-middle"
+ :aria-label="__('Projects Successfully Retrieved')"
+ name="check-circle"
+ />
+ </div>
+ </div>
+ <p v-if="connectError" class="gl-field-error">
+ {{ s__('ErrorTracking|Connection has failed. Re-check Auth Token and try again.') }}
+ </p>
+ <p v-else class="form-text text-muted">
+ {{
+ s__(
+ "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects",
+ )
+ }}
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue b/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue
new file mode 100644
index 00000000000..82df02afafd
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue
@@ -0,0 +1,82 @@
+<script>
+import { GlDropdown, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { getDisplayName } from '../utils';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownHeader,
+ GlDropdownItem,
+ Icon,
+ },
+ props: {
+ dropdownLabel: {
+ type: String,
+ required: true,
+ },
+ hasProjects: {
+ type: Boolean,
+ required: true,
+ },
+ invalidProjectLabel: {
+ type: String,
+ required: true,
+ },
+ isProjectInvalid: {
+ type: Boolean,
+ required: true,
+ },
+ projects: {
+ type: Array,
+ required: true,
+ },
+ selectedProject: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ projectSelectionLabel: {
+ type: String,
+ required: true,
+ },
+ token: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ getDisplayName,
+ },
+};
+</script>
+
+<template>
+ <div :class="{ 'gl-show-field-errors': isProjectInvalid }">
+ <label class="label-bold" for="project-dropdown">{{ __('Project') }}</label>
+ <div class="row">
+ <gl-dropdown
+ id="project-dropdown"
+ class="col-8 col-md-9 gl-pr-0"
+ :disabled="!hasProjects"
+ menu-class="w-100 mw-100"
+ toggle-class="dropdown-menu-toggle w-100 gl-field-error-outline"
+ :text="dropdownLabel"
+ >
+ <gl-dropdown-item
+ v-for="project in projects"
+ :key="`${project.organizationSlug}.${project.slug}`"
+ class="w-100"
+ @click="$emit('select-project', project)"
+ >{{ getDisplayName(project) }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ <p v-if="isProjectInvalid" class="js-project-dropdown-error gl-field-error">
+ {{ invalidProjectLabel }}
+ </p>
+ <p v-else-if="!hasProjects" class="js-project-dropdown-label form-text text-muted">
+ {{ projectSelectionLabel }}
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/index.js b/app/assets/javascripts/error_tracking_settings/index.js
new file mode 100644
index 00000000000..ce315963723
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/index.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import ErrorTrackingSettings from './components/app.vue';
+import createStore from './store';
+
+export default () => {
+ const formContainerEl = document.querySelector('.js-error-tracking-form');
+ const {
+ dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
+ } = formContainerEl;
+
+ return new Vue({
+ el: formContainerEl,
+ store: createStore(),
+ render(createElement) {
+ return createElement(ErrorTrackingSettings, {
+ props: {
+ initialApiHost: apiHost,
+ initialEnabled: enabled,
+ initialProject: project,
+ initialToken: token,
+ listProjectsEndpoint,
+ operationsSettingsEndpoint,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/error_tracking_settings/store/actions.js b/app/assets/javascripts/error_tracking_settings/store/actions.js
new file mode 100644
index 00000000000..95105797807
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/actions.js
@@ -0,0 +1,91 @@
+import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import createFlash from '~/flash';
+import { transformFrontendSettings } from '../utils';
+import * as types from './mutation_types';
+
+export const requestProjects = ({ commit }) => {
+ commit(types.RESET_CONNECT);
+};
+
+export const receiveProjectsSuccess = ({ commit }, projects) => {
+ commit(types.UPDATE_CONNECT_SUCCESS);
+ commit(types.RECEIVE_PROJECTS, projects);
+};
+
+export const receiveProjectsError = ({ commit }) => {
+ commit(types.UPDATE_CONNECT_ERROR);
+ commit(types.CLEAR_PROJECTS);
+};
+
+export const fetchProjects = ({ dispatch, state }) => {
+ dispatch('requestProjects');
+ return axios
+ .post(state.listProjectsEndpoint, {
+ error_tracking_setting: {
+ api_host: state.apiHost,
+ token: state.token,
+ },
+ })
+ .then(({ data: { projects } }) => {
+ dispatch('receiveProjectsSuccess', projects);
+ })
+ .catch(() => {
+ dispatch('receiveProjectsError');
+ });
+};
+
+export const requestSettings = ({ commit }) => {
+ commit(types.UPDATE_SETTINGS_LOADING, true);
+};
+
+export const receiveSettingsError = ({ commit }, { response = {} }) => {
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
+ commit(types.UPDATE_SETTINGS_LOADING, false);
+};
+
+export const updateSettings = ({ dispatch, state }) => {
+ dispatch('requestSettings');
+ return axios
+ .patch(state.operationsSettingsEndpoint, {
+ project: {
+ error_tracking_setting_attributes: {
+ ...transformFrontendSettings(state),
+ },
+ },
+ })
+ .then(() => {
+ refreshCurrentPage();
+ })
+ .catch(err => {
+ dispatch('receiveSettingsError', err);
+ });
+};
+
+export const updateApiHost = ({ commit }, apiHost) => {
+ commit(types.UPDATE_API_HOST, apiHost);
+ commit(types.RESET_CONNECT);
+};
+
+export const updateEnabled = ({ commit }, enabled) => {
+ commit(types.UPDATE_ENABLED, enabled);
+};
+
+export const updateToken = ({ commit }, token) => {
+ commit(types.UPDATE_TOKEN, token);
+ commit(types.RESET_CONNECT);
+};
+
+export const updateSelectedProject = ({ commit }, selectedProject) => {
+ commit(types.UPDATE_SELECTED_PROJECT, selectedProject);
+};
+
+export const setInitialState = ({ commit }, data) => {
+ commit(types.SET_INITIAL_STATE, data);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking_settings/store/getters.js b/app/assets/javascripts/error_tracking_settings/store/getters.js
new file mode 100644
index 00000000000..d77e5f15469
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/getters.js
@@ -0,0 +1,44 @@
+import _ from 'underscore';
+import { __, s__, sprintf } from '~/locale';
+import { getDisplayName } from '../utils';
+
+export const hasProjects = state => Boolean(state.projects) && state.projects.length > 0;
+
+export const isProjectInvalid = (state, getters) =>
+ Boolean(state.selectedProject) &&
+ getters.hasProjects &&
+ !state.projects.some(project => _.isMatch(state.selectedProject, project));
+
+export const dropdownLabel = (state, getters) => {
+ if (state.selectedProject !== null) {
+ return getDisplayName(state.selectedProject);
+ }
+ if (!getters.hasProjects) {
+ return s__('ErrorTracking|No projects available');
+ }
+ return s__('ErrorTracking|Select project');
+};
+
+export const invalidProjectLabel = state => {
+ if (state.selectedProject) {
+ return sprintf(
+ __('Project "%{name}" is no longer available. Select another project to continue.'),
+ {
+ name: state.selectedProject.name,
+ },
+ );
+ }
+ return '';
+};
+
+export const projectSelectionLabel = state => {
+ if (state.token) {
+ return s__(
+ "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.",
+ );
+ }
+ return s__('ErrorTracking|To enable project selection, enter a valid Auth Token');
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking_settings/store/index.js b/app/assets/javascripts/error_tracking_settings/store/index.js
new file mode 100644
index 00000000000..560f265a2ea
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ state: createState(),
+ actions,
+ getters,
+ mutations,
+ });
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutation_types.js b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
new file mode 100644
index 00000000000..b4f8a237947
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
@@ -0,0 +1,11 @@
+export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
+export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
+export const RECEIVE_PROJECTS = 'RECEIVE_PROJECTS';
+export const RESET_CONNECT = 'RESET_CONNECT';
+export const UPDATE_API_HOST = 'UPDATE_API_HOST';
+export const UPDATE_CONNECT_ERROR = 'UPDATE_CONNECT_ERROR';
+export const UPDATE_CONNECT_SUCCESS = 'UPDATE_CONNECT_SUCCESS';
+export const UPDATE_ENABLED = 'UPDATE_ENABLED';
+export const UPDATE_SELECTED_PROJECT = 'UPDATE_SELECTED_PROJECT';
+export const UPDATE_SETTINGS_LOADING = 'UPDATE_SETTINGS_LOADING';
+export const UPDATE_TOKEN = 'UPDATE_TOKEN';
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutations.js b/app/assets/javascripts/error_tracking_settings/store/mutations.js
new file mode 100644
index 00000000000..4089d1ee94e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/mutations.js
@@ -0,0 +1,61 @@
+import _ from 'underscore';
+import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
+import * as types from './mutation_types';
+import { projectKeys } from '../utils';
+
+export default {
+ [types.CLEAR_PROJECTS](state) {
+ state.projects = [];
+ },
+ [types.RECEIVE_PROJECTS](state, projects) {
+ state.projects = projects
+ .map(convertObjectPropsToCamelCase)
+ // The `pick` strips out extra properties returned from Sentry.
+ // Such properties could be problematic later, e.g. when checking whether `projects` contains `selectedProject`
+ .map(project => _.pick(project, projectKeys));
+ },
+ [types.RESET_CONNECT](state) {
+ state.connectSuccessful = false;
+ state.connectError = false;
+ },
+ [types.SET_INITIAL_STATE](
+ state,
+ { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
+ ) {
+ state.enabled = parseBoolean(enabled);
+ state.apiHost = apiHost;
+ state.token = token;
+ state.listProjectsEndpoint = listProjectsEndpoint;
+ state.operationsSettingsEndpoint = operationsSettingsEndpoint;
+
+ if (project) {
+ state.selectedProject = _.pick(
+ convertObjectPropsToCamelCase(JSON.parse(project)),
+ projectKeys,
+ );
+ }
+ },
+ [types.UPDATE_API_HOST](state, apiHost) {
+ state.apiHost = apiHost;
+ },
+ [types.UPDATE_ENABLED](state, enabled) {
+ state.enabled = enabled;
+ },
+ [types.UPDATE_TOKEN](state, token) {
+ state.token = token;
+ },
+ [types.UPDATE_SELECTED_PROJECT](state, selectedProject) {
+ state.selectedProject = selectedProject;
+ },
+ [types.UPDATE_SETTINGS_LOADING](state, settingsLoading) {
+ state.settingsLoading = settingsLoading;
+ },
+ [types.UPDATE_CONNECT_SUCCESS](state) {
+ state.connectSuccessful = true;
+ state.connectError = false;
+ },
+ [types.UPDATE_CONNECT_ERROR](state) {
+ state.connectSuccessful = false;
+ state.connectError = true;
+ },
+};
diff --git a/app/assets/javascripts/error_tracking_settings/store/state.js b/app/assets/javascripts/error_tracking_settings/store/state.js
new file mode 100644
index 00000000000..98219d33f4d
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/state.js
@@ -0,0 +1,12 @@
+export default () => ({
+ apiHost: '',
+ enabled: false,
+ token: '',
+ projects: [],
+ selectedProject: null,
+ settingsLoading: false,
+ connectSuccessful: false,
+ connectError: false,
+ listProjectsEndpoint: '',
+ operationsSettingsEndpoint: '',
+});
diff --git a/app/assets/javascripts/error_tracking_settings/utils.js b/app/assets/javascripts/error_tracking_settings/utils.js
new file mode 100644
index 00000000000..6613e04ee0e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/utils.js
@@ -0,0 +1,18 @@
+export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug'];
+
+export const transformFrontendSettings = ({ apiHost, enabled, token, selectedProject }) => {
+ const project = selectedProject
+ ? {
+ slug: selectedProject.slug,
+ name: selectedProject.name,
+ organization_name: selectedProject.organizationName,
+ organization_slug: selectedProject.organizationSlug,
+ }
+ : null;
+
+ return { api_host: apiHost || null, enabled, token: token || null, project };
+};
+
+export const getDisplayName = project => `${project.organizationName} | ${project.name}`;
+
+export default () => {};
diff --git a/app/assets/javascripts/event_tracking/notes.js b/app/assets/javascripts/event_tracking/notes.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/event_tracking/notes.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
new file mode 100644
index 00000000000..e020628a473
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
@@ -0,0 +1,30 @@
+import { __ } from '~/locale';
+
+export default IssuableTokenKeys => {
+ const wipToken = {
+ key: 'wip',
+ type: 'string',
+ param: '',
+ symbol: '',
+ icon: 'admin',
+ tag: __('Yes or No'),
+ lowercaseValueOnSubmit: true,
+ uppercaseTokenName: true,
+ capitalizeTokenValue: true,
+ };
+
+ IssuableTokenKeys.tokenKeys.push(wipToken);
+ IssuableTokenKeys.tokenKeysWithAlternative.push(wipToken);
+
+ const targetBranchToken = {
+ key: 'target-branch',
+ type: 'string',
+ param: '',
+ symbol: '',
+ icon: 'arrow-right',
+ tag: 'branch',
+ };
+
+ IssuableTokenKeys.tokenKeys.push(targetBranchToken);
+ IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken);
+};
diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
index 934375023ba..691d165c585 100644
--- a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
@@ -17,6 +17,14 @@ const tokenKeys = [
icon: 'cube',
tag: 'type',
},
+ {
+ key: 'tag',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'tag',
+ tag: '~tag',
+ },
];
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
new file mode 100644
index 00000000000..be867a3838d
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
@@ -0,0 +1,164 @@
+import DropdownHint from './dropdown_hint';
+import DropdownUser from './dropdown_user';
+import DropdownNonUser from './dropdown_non_user';
+import DropdownEmoji from './dropdown_emoji';
+import NullDropdown from './null_dropdown';
+import DropdownAjaxFilter from './dropdown_ajax_filter';
+import DropdownUtils from './dropdown_utils';
+import { mergeUrlParams } from '../lib/utils/url_utility';
+
+export default class AvailableDropdownMappings {
+ constructor(container, baseEndpoint, groupsOnly, includeAncestorGroups, includeDescendantGroups) {
+ this.container = container;
+ this.baseEndpoint = baseEndpoint;
+ this.groupsOnly = groupsOnly;
+ this.includeAncestorGroups = includeAncestorGroups;
+ this.includeDescendantGroups = includeDescendantGroups;
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
+ }
+
+ getAllowedMappings(supportedTokens) {
+ return this.buildMappings(supportedTokens, this.getMappings());
+ }
+
+ buildMappings(supportedTokens, availableMappings) {
+ const allowedMappings = {
+ hint: {
+ reference: null,
+ gl: DropdownHint,
+ element: this.container.querySelector('#js-dropdown-hint'),
+ },
+ };
+
+ supportedTokens.forEach(type => {
+ if (availableMappings[type]) {
+ allowedMappings[type] = availableMappings[type];
+ }
+ });
+
+ return allowedMappings;
+ }
+
+ getMappings() {
+ return {
+ author: {
+ reference: null,
+ gl: DropdownUser,
+ element: this.container.querySelector('#js-dropdown-author'),
+ },
+ assignee: {
+ reference: null,
+ gl: DropdownUser,
+ element: this.container.querySelector('#js-dropdown-assignee'),
+ },
+ milestone: {
+ reference: null,
+ gl: DropdownNonUser,
+ extraArguments: {
+ endpoint: this.getMilestoneEndpoint(),
+ symbol: '%',
+ },
+ element: this.container.querySelector('#js-dropdown-milestone'),
+ },
+ label: {
+ reference: null,
+ gl: DropdownNonUser,
+ extraArguments: {
+ endpoint: this.getLabelsEndpoint(),
+ symbol: '~',
+ preprocessing: DropdownUtils.duplicateLabelPreprocessing,
+ },
+ element: this.container.querySelector('#js-dropdown-label'),
+ },
+ 'my-reaction': {
+ reference: null,
+ gl: DropdownEmoji,
+ element: this.container.querySelector('#js-dropdown-my-reaction'),
+ },
+ wip: {
+ reference: null,
+ gl: DropdownNonUser,
+ element: this.container.querySelector('#js-dropdown-wip'),
+ },
+ confidential: {
+ reference: null,
+ gl: DropdownNonUser,
+ element: this.container.querySelector('#js-dropdown-confidential'),
+ },
+ status: {
+ reference: null,
+ gl: NullDropdown,
+ element: this.container.querySelector('#js-dropdown-admin-runner-status'),
+ },
+ type: {
+ reference: null,
+ gl: NullDropdown,
+ element: this.container.querySelector('#js-dropdown-admin-runner-type'),
+ },
+ tag: {
+ reference: null,
+ gl: DropdownAjaxFilter,
+ extraArguments: {
+ endpoint: this.getRunnerTagsEndpoint(),
+ symbol: '~',
+ },
+ element: this.container.querySelector('#js-dropdown-runner-tag'),
+ },
+ 'target-branch': {
+ reference: null,
+ gl: DropdownNonUser,
+ extraArguments: {
+ endpoint: this.getMergeRequestTargetBranchesEndpoint(),
+ symbol: '',
+ },
+ element: this.container.querySelector('#js-dropdown-target-branch'),
+ },
+ };
+ }
+
+ getMilestoneEndpoint() {
+ return `${this.baseEndpoint}/milestones.json`;
+ }
+
+ getLabelsEndpoint() {
+ let endpoint = `${this.baseEndpoint}/labels.json?`;
+
+ if (this.groupsOnly) {
+ endpoint = `${endpoint}only_group_labels=true&`;
+ }
+
+ if (this.includeAncestorGroups) {
+ endpoint = `${endpoint}include_ancestor_groups=true&`;
+ }
+
+ if (this.includeDescendantGroups) {
+ endpoint = `${endpoint}include_descendant_groups=true`;
+ }
+
+ return endpoint;
+ }
+
+ getRunnerTagsEndpoint() {
+ return `${this.baseEndpoint}/admin/runners/tag_list.json`;
+ }
+
+ getMergeRequestTargetBranchesEndpoint() {
+ const endpoint = `${gon.relative_url_root ||
+ ''}/autocomplete/merge_request_target_branches.json`;
+
+ const params = {
+ group_id: this.getGroupId(),
+ project_id: this.getProjectId(),
+ };
+
+ return mergeUrlParams(params, endpoint);
+ }
+
+ getGroupId() {
+ return this.filteredSearchInput.getAttribute('data-group-id') || '';
+ }
+
+ getProjectId() {
+ return this.filteredSearchInput.getAttribute('data-project-id') || '';
+ }
+}
diff --git a/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js b/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js
new file mode 100644
index 00000000000..b27bb63c220
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js
@@ -0,0 +1,68 @@
+import createFlash from '../flash';
+import AjaxFilter from '../droplab/plugins/ajax_filter';
+import FilteredSearchDropdown from './filtered_search_dropdown';
+import DropdownUtils from './dropdown_utils';
+import FilteredSearchTokenizer from './filtered_search_tokenizer';
+import { __ } from '~/locale';
+
+export default class DropdownAjaxFilter extends FilteredSearchDropdown {
+ constructor(options = {}) {
+ const { tokenKeys, endpoint, symbol } = options;
+
+ super(options);
+
+ this.tokenKeys = tokenKeys;
+ this.endpoint = endpoint;
+ this.symbol = symbol;
+
+ this.config = {
+ AjaxFilter: this.ajaxFilterConfig(),
+ };
+ }
+
+ ajaxFilterConfig() {
+ return {
+ endpoint: `${gon.relative_url_root || ''}${this.endpoint}`,
+ searchKey: 'search',
+ searchValueFunction: this.getSearchInput.bind(this),
+ loadingTemplate: this.loadingTemplate,
+ onError() {
+ createFlash(__('An error occurred fetching the dropdown data.'));
+ },
+ };
+ }
+
+ itemClicked(e) {
+ super.itemClicked(e, selected =>
+ selected.querySelector('.dropdown-light-content').innerText.trim(),
+ );
+ }
+
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
+ super.renderContent(forceShowList);
+ }
+
+ getSearchInput() {
+ const query = DropdownUtils.getSearchInput(this.input);
+ const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
+
+ let value = lastToken || '';
+
+ if (value[0] === this.symbol) {
+ value = value.slice(1);
+ }
+
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if (value[0] === '"' || value[0] === "'") {
+ value = value.slice(1);
+ }
+
+ return value;
+ }
+
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
+ }
+}
diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js
index d9a4d06b549..dad188f6f98 100644
--- a/app/assets/javascripts/filtered_search/dropdown_emoji.js
+++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js
@@ -3,6 +3,7 @@ import Ajax from '../droplab/plugins/ajax';
import Filter from '../droplab/plugins/filter';
import FilteredSearchDropdown from './filtered_search_dropdown';
import DropdownUtils from './dropdown_utils';
+import { __ } from '~/locale';
export default class DropdownEmoji extends FilteredSearchDropdown {
constructor(options = {}) {
@@ -14,7 +15,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
- new Flash('An error occurred fetching the dropdown data.');
+ new Flash(__('An error occurred fetching the dropdown data.'));
/* eslint-enable no-new */
},
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 0264f934914..a2312de289d 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -3,6 +3,7 @@ import Ajax from '../droplab/plugins/ajax';
import Filter from '../droplab/plugins/filter';
import FilteredSearchDropdown from './filtered_search_dropdown';
import DropdownUtils from './dropdown_utils';
+import { __ } from '~/locale';
export default class DropdownNonUser extends FilteredSearchDropdown {
constructor(options = {}) {
@@ -17,7 +18,7 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
preprocessing,
onError() {
/* eslint-disable no-new */
- new Flash('An error occurred fetching the dropdown data.');
+ new Flash(__('An error occurred fetching the dropdown data.'));
/* eslint-enable no-new */
},
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index d5027590bb7..a65c0012b4d 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -1,54 +1,35 @@
-import Flash from '../flash';
-import AjaxFilter from '../droplab/plugins/ajax_filter';
-import FilteredSearchDropdown from './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
-import DropdownUtils from './dropdown_utils';
-import FilteredSearchTokenizer from './filtered_search_tokenizer';
+import DropdownAjaxFilter from './dropdown_ajax_filter';
-export default class DropdownUser extends FilteredSearchDropdown {
+export default class DropdownUser extends DropdownAjaxFilter {
constructor(options = {}) {
- const { tokenKeys } = options;
- super(options);
- this.config = {
- AjaxFilter: {
- endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
- searchKey: 'search',
- params: {
- active: true,
- group_id: this.getGroupId(),
- project_id: this.getProjectId(),
- current_user: true,
- },
- searchValueFunction: this.getSearchInput.bind(this),
- loadingTemplate: this.loadingTemplate,
- onLoadingFinished: () => {
- this.hideCurrentUser();
- },
- onError() {
- /* eslint-disable no-new */
- new Flash('An error occurred fetching the dropdown data.');
- /* eslint-enable no-new */
- },
+ super({
+ ...options,
+ endpoint: '/autocomplete/users.json',
+ symbol: '@',
+ });
+ }
+
+ ajaxFilterConfig() {
+ return {
+ ...super.ajaxFilterConfig(),
+ params: {
+ active: true,
+ group_id: this.getGroupId(),
+ project_id: this.getProjectId(),
+ current_user: true,
+ ...this.projectOrGroupId(),
+ },
+ onLoadingFinished: () => {
+ this.hideCurrentUser();
},
};
- this.tokenKeys = tokenKeys;
}
hideCurrentUser() {
addClassIfElementExists(this.dropdown.querySelector('.js-current-user'), 'hidden');
}
- itemClicked(e) {
- super.itemClicked(e, selected =>
- selected.querySelector('.dropdown-light-content').innerText.trim(),
- );
- }
-
- renderContent(forceShowList = false) {
- this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
- super.renderContent(forceShowList);
- }
-
getGroupId() {
return this.input.getAttribute('data-group-id');
}
@@ -57,26 +38,16 @@ export default class DropdownUser extends FilteredSearchDropdown {
return this.input.getAttribute('data-project-id');
}
- getSearchInput() {
- const query = DropdownUtils.getSearchInput(this.input);
- const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
-
- let value = lastToken || '';
-
- if (value[0] === '@') {
- value = value.slice(1);
+ projectOrGroupId() {
+ const projectId = this.getProjectId();
+ const groupId = this.getGroupId();
+ if (groupId) {
+ return {
+ group_id: groupId,
+ };
}
-
- // Removes the first character if it is a quotation so that we can search
- // with multiple words
- if (value[0] === '"' || value[0] === "'") {
- value = value.slice(1);
- }
-
- return value;
- }
-
- init() {
- this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
+ return {
+ project_id: projectId,
+ };
}
}
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 4d05f46ed17..cb0a84b490b 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -1,13 +1,9 @@
+import AvailableDropdownMappings from 'ee_else_ce/filtered_search/available_dropdown_mappings';
import _ from 'underscore';
import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
import FilteredSearchTokenKeys from './filtered_search_token_keys';
import DropdownUtils from './dropdown_utils';
-import DropdownHint from './dropdown_hint';
-import DropdownEmoji from './dropdown_emoji';
-import DropdownNonUser from './dropdown_non_user';
-import DropdownUser from './dropdown_user';
-import NullDropdown from './null_dropdown';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
@@ -49,101 +45,15 @@ export default class FilteredSearchDropdownManager {
setupMapping() {
const supportedTokens = this.filteredSearchTokenKeys.getKeys();
- const allowedMappings = {
- hint: {
- reference: null,
- gl: DropdownHint,
- element: this.container.querySelector('#js-dropdown-hint'),
- },
- };
- const availableMappings = {
- author: {
- reference: null,
- gl: DropdownUser,
- element: this.container.querySelector('#js-dropdown-author'),
- },
- assignee: {
- reference: null,
- gl: DropdownUser,
- element: this.container.querySelector('#js-dropdown-assignee'),
- },
- milestone: {
- reference: null,
- gl: DropdownNonUser,
- extraArguments: {
- endpoint: this.getMilestoneEndpoint(),
- symbol: '%',
- },
- element: this.container.querySelector('#js-dropdown-milestone'),
- },
- label: {
- reference: null,
- gl: DropdownNonUser,
- extraArguments: {
- endpoint: this.getLabelsEndpoint(),
- symbol: '~',
- preprocessing: DropdownUtils.duplicateLabelPreprocessing,
- },
- element: this.container.querySelector('#js-dropdown-label'),
- },
- 'my-reaction': {
- reference: null,
- gl: DropdownEmoji,
- element: this.container.querySelector('#js-dropdown-my-reaction'),
- },
- wip: {
- reference: null,
- gl: DropdownNonUser,
- element: this.container.querySelector('#js-dropdown-wip'),
- },
- confidential: {
- reference: null,
- gl: DropdownNonUser,
- element: this.container.querySelector('#js-dropdown-confidential'),
- },
- status: {
- reference: null,
- gl: NullDropdown,
- element: this.container.querySelector('#js-dropdown-admin-runner-status'),
- },
- type: {
- reference: null,
- gl: NullDropdown,
- element: this.container.querySelector('#js-dropdown-admin-runner-type'),
- },
- };
-
- supportedTokens.forEach(type => {
- if (availableMappings[type]) {
- allowedMappings[type] = availableMappings[type];
- }
- });
-
- this.mapping = allowedMappings;
- }
-
- getMilestoneEndpoint() {
- const endpoint = `${this.baseEndpoint}/milestones.json`;
-
- return endpoint;
- }
-
- getLabelsEndpoint() {
- let endpoint = `${this.baseEndpoint}/labels.json?`;
-
- if (this.groupsOnly) {
- endpoint = `${endpoint}only_group_labels=true&`;
- }
-
- if (this.includeAncestorGroups) {
- endpoint = `${endpoint}include_ancestor_groups=true&`;
- }
-
- if (this.includeDescendantGroups) {
- endpoint = `${endpoint}include_descendant_groups=true`;
- }
+ const availableMappings = new AvailableDropdownMappings(
+ this.container,
+ this.baseEndpoint,
+ this.groupsOnly,
+ this.includeAncestorGroups,
+ this.includeDescendantGroups,
+ );
- return endpoint;
+ this.mapping = availableMappings.getAllowedMappings(supportedTokens);
}
static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 33c82778c79..78fbb3696cc 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,6 +1,7 @@
import _ from 'underscore';
import { getParameterByName, getUrlParamsArray } from '~/lib/utils/common_utils';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
@@ -13,6 +14,7 @@ import FilteredSearchTokenizer from './filtered_search_tokenizer';
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
import DropdownUtils from './dropdown_utils';
+import { __ } from '~/locale';
export default class FilteredSearchManager {
constructor({
@@ -36,10 +38,11 @@ export default class FilteredSearchManager {
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector;
- this.recentsStorageKeyNames = {
- issues: 'issue-recent-searches',
- merge_requests: 'merge-request-recent-searches',
- };
+
+ const { multipleAssignees } = this.filteredSearchInput.dataset;
+ if (multipleAssignees && this.filteredSearchTokenKeys.enableMultipleAssignees) {
+ this.filteredSearchTokenKeys.enableMultipleAssignees();
+ }
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
@@ -51,7 +54,7 @@ export default class FilteredSearchManager {
const fullPath = this.searchHistoryDropdownElement
? this.searchHistoryDropdownElement.dataset.fullPath
: 'project';
- const recentSearchesKey = `${fullPath}-${this.recentsStorageKeyNames[this.page]}`;
+ const recentSearchesKey = `${fullPath}-${recentSearchesStorageKeys[this.page]}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
@@ -62,7 +65,7 @@ export default class FilteredSearchManager {
.catch(error => {
if (error.name === 'RecentSearchesServiceError') return undefined;
// eslint-disable-next-line no-new
- new Flash('An error occurred while parsing recent searches');
+ new Flash(__('An error occurred while parsing recent searches'));
// Gracefully fail to empty array
return [];
})
@@ -338,7 +341,7 @@ export default class FilteredSearchManager {
handleInputPlaceholder() {
const query = DropdownUtils.getSearchQuery();
- const placeholder = 'Search or filter results...';
+ const placeholder = __('Search or filter results...');
const currentPlaceholder = this.filteredSearchInput.placeholder;
if (query.length === 0 && currentPlaceholder !== placeholder) {
@@ -504,14 +507,7 @@ export default class FilteredSearchManager {
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
- // Use lastIndexOf because the token key is allowed to contain underscore
- // e.g. 'my_reaction' is the token key of 'my_reaction_emoji'
- const lastIndexOf = keyParam.lastIndexOf('_');
- let sanitizedKey = lastIndexOf !== -1 ? keyParam.slice(0, lastIndexOf) : keyParam;
- // Replace underscore with hyphen in the sanitizedkey.
- // e.g. 'my_reaction' => 'my-reaction'
- sanitizedKey = sanitizedKey.replace('_', '-');
- const { symbol } = match;
+ const { key, symbol } = match;
let quotationsToUse = '';
if (sanitizedValue.indexOf(' ') !== -1) {
@@ -520,10 +516,10 @@ export default class FilteredSearchManager {
}
hasFilteredSearch = true;
- const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
+ const canEdit = this.canEdit && this.canEdit(key, sanitizedValue);
const { uppercaseTokenName, capitalizeTokenValue } = match;
FilteredSearchVisualTokens.addFilterVisualToken(
- sanitizedKey,
+ key,
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
{
canEdit,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index 48534bdf815..0a9579bf491 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default class FilteredSearchTokenKeys {
constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) {
this.tokenKeys = tokenKeys;
@@ -79,7 +81,7 @@ export default class FilteredSearchTokenKeys {
param: '',
symbol: '',
icon: 'eye-slash',
- tag: 'Yes or No',
+ tag: __('Yes or No'),
lowercaseValueOnSubmit: true,
uppercaseTokenName: false,
capitalizeTokenValue: true,
@@ -88,21 +90,4 @@ export default class FilteredSearchTokenKeys {
this.tokenKeys.push(confidentialToken);
this.tokenKeysWithAlternative.push(confidentialToken);
}
-
- addExtraTokensForMergeRequests() {
- const wipToken = {
- key: 'wip',
- type: 'string',
- param: '',
- symbol: '',
- icon: 'admin',
- tag: 'Yes or No',
- lowercaseValueOnSubmit: true,
- uppercaseTokenName: true,
- capitalizeTokenValue: true,
- };
-
- this.tokenKeys.push(wipToken);
- this.tokenKeysWithAlternative.push(wipToken);
- }
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 5090b0bdc3c..315cd6f64da 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,10 +1,6 @@
-import _ from 'underscore';
-import AjaxCache from '~/lib/utils/ajax_cache';
+import VisualTokenValue from 'ee_else_ce/filtered_search/visual_token_value';
import { objectToQueryString } from '~/lib/utils/common_utils';
-import Flash from '../flash';
import FilteredSearchContainer from './container';
-import UsersCache from '../lib/utils/users_cache';
-import DropdownUtils from './dropdown_utils';
export default class FilteredSearchVisualTokens {
static getLastVisualTokenBeforeInput() {
@@ -20,21 +16,6 @@ export default class FilteredSearchVisualTokens {
};
}
- /**
- * Returns a computed API endpoint
- * and query string composed of values from endpointQueryParams
- * @param {String} endpoint
- * @param {String} endpointQueryParams
- */
- static getEndpointWithQueryParams(endpoint, endpointQueryParams) {
- if (!endpointQueryParams) {
- return endpoint;
- }
-
- const queryString = objectToQueryString(JSON.parse(endpointQueryParams));
- return `${endpoint}?${queryString}`;
- }
-
static unselectTokens() {
const otherTokens = FilteredSearchContainer.container.querySelectorAll(
'.js-visual-token .selectable.selected',
@@ -76,122 +57,33 @@ export default class FilteredSearchVisualTokens {
`;
}
- static setTokenStyle(tokenContainer, backgroundColor, textColor) {
- const token = tokenContainer;
-
- token.style.backgroundColor = backgroundColor;
- token.style.color = textColor;
-
- if (textColor === '#FFFFFF') {
- const removeToken = token.querySelector('.remove-token');
- removeToken.classList.add('inverted');
- }
-
- return token;
- }
-
- static updateLabelTokenColor(tokenValueContainer, tokenValue) {
- const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
- const { baseEndpoint } = filteredSearchInput.dataset;
- const labelsEndpoint = FilteredSearchVisualTokens.getEndpointWithQueryParams(
- `${baseEndpoint}/labels.json`,
- filteredSearchInput.dataset.endpointQueryParams,
- );
-
- return AjaxCache.retrieve(labelsEndpoint)
- .then(labels => {
- const matchingLabel = (labels || []).find(
- label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
- );
-
- if (!matchingLabel) {
- return;
- }
-
- FilteredSearchVisualTokens.setTokenStyle(
- tokenValueContainer,
- matchingLabel.color,
- matchingLabel.text_color,
- );
- })
- .catch(() => new Flash('An error occurred while fetching label colors.'));
- }
-
- static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
- const username = tokenValue.replace(/^@/, '');
- return (
- UsersCache.retrieve(username)
- .then(user => {
- if (!user) {
- return;
- }
-
- /* eslint-disable no-param-reassign */
- tokenValueContainer.dataset.originalValue = tokenValue;
- tokenValueElement.innerHTML = `
- <img class="avatar s20" src="${user.avatar_url}" alt="">
- ${_.escape(user.name)}
- `;
- /* eslint-enable no-param-reassign */
- })
- // ignore error and leave username in the search bar
- .catch(() => {})
- );
- }
-
- static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
- const container = tokenValueContainer;
- const element = tokenValueElement;
-
- return (
- import(/* webpackChunkName: 'emoji' */ '../emoji')
- .then(Emoji => {
- if (!Emoji.isEmojiNameValid(tokenValue)) {
- return;
- }
-
- container.dataset.originalValue = tokenValue;
- element.innerHTML = Emoji.glEmojiTag(tokenValue);
- })
- // ignore error and leave emoji name in the search bar
- .catch(() => {})
- );
- }
-
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
+ const tokenType = tokenName.toLowerCase();
const tokenValueContainer = parentElement.querySelector('.value-container');
const tokenValueElement = tokenValueContainer.querySelector('.value');
tokenValueElement.innerText = tokenValue;
- if (['none', 'any'].includes(tokenValue.toLowerCase())) {
- return;
- }
-
- const tokenType = tokenName.toLowerCase();
+ const visualTokenValue = new VisualTokenValue(tokenValue, tokenType);
- if (tokenType === 'label') {
- FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue);
- } else if (tokenType === 'author' || tokenType === 'assignee') {
- FilteredSearchVisualTokens.updateUserTokenAppearance(
- tokenValueContainer,
- tokenValueElement,
- tokenValue,
- );
- } else if (tokenType === 'my-reaction') {
- FilteredSearchVisualTokens.updateEmojiTokenAppearance(
- tokenValueContainer,
- tokenValueElement,
- tokenValue,
- );
- }
+ visualTokenValue.render(tokenValueContainer, tokenValueElement);
}
static addVisualTokenElement(name, value, options = {}) {
- const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
+ const {
+ isSearchTerm = false,
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ tokenClass = `search-token-${name.toLowerCase()}`,
+ } = options;
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
+ if (!isSearchTerm) {
+ li.classList.add(tokenClass);
+ }
+
if (value) {
li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML({
canEdit,
@@ -318,6 +210,21 @@ export default class FilteredSearchVisualTokens {
}
}
+ /**
+ * Returns a computed API endpoint
+ * and query string composed of values from endpointQueryParams
+ * @param {String} endpoint
+ * @param {String} endpointQueryParams
+ */
+ static getEndpointWithQueryParams(endpoint, endpointQueryParams) {
+ if (!endpointQueryParams) {
+ return endpoint;
+ }
+
+ const queryString = objectToQueryString(JSON.parse(endpointQueryParams));
+ return `${endpoint}?${queryString}`;
+ }
+
static editToken(token) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index fd61030eb13..6c3d9e33420 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -1,4 +1,5 @@
import FilteredSearchTokenKeys from './filtered_search_token_keys';
+import { __ } from '~/locale';
export const tokenKeys = [
{
@@ -60,52 +61,52 @@ export const conditions = [
{
url: 'assignee_id=None',
tokenKey: 'assignee',
- value: 'None',
+ value: __('None'),
},
{
url: 'assignee_id=Any',
tokenKey: 'assignee',
- value: 'Any',
+ value: __('Any'),
},
{
url: 'milestone_title=None',
tokenKey: 'milestone',
- value: 'None',
+ value: __('None'),
},
{
url: 'milestone_title=Any',
tokenKey: 'milestone',
- value: 'Any',
+ value: __('Any'),
},
{
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
- value: 'Upcoming',
+ value: __('Upcoming'),
},
{
url: 'milestone_title=%23started',
tokenKey: 'milestone',
- value: 'Started',
+ value: __('Started'),
},
{
url: 'label_name[]=None',
tokenKey: 'label',
- value: 'None',
+ value: __('None'),
},
{
url: 'label_name[]=Any',
tokenKey: 'label',
- value: 'Any',
+ value: __('Any'),
},
{
url: 'my_reaction_emoji=None',
tokenKey: 'my-reaction',
- value: 'None',
+ value: __('None'),
},
{
url: 'my_reaction_emoji=Any',
tokenKey: 'my-reaction',
- value: 'Any',
+ value: __('Any'),
},
];
diff --git a/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
new file mode 100644
index 00000000000..7e9b809e9b2
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
@@ -0,0 +1,4 @@
+export default {
+ issues: 'issue-recent-searches',
+ merge_requests: 'merge-request-recent-searches',
+};
diff --git a/app/assets/javascripts/filtered_search/services/recent_searches_service_error.js b/app/assets/javascripts/filtered_search/services/recent_searches_service_error.js
index 5917b223d63..011b37e218d 100644
--- a/app/assets/javascripts/filtered_search/services/recent_searches_service_error.js
+++ b/app/assets/javascripts/filtered_search/services/recent_searches_service_error.js
@@ -1,7 +1,9 @@
+import { __ } from '~/locale';
+
class RecentSearchesServiceError {
constructor(message) {
this.name = 'RecentSearchesServiceError';
- this.message = message || 'Recent Searches Service is unavailable';
+ this.message = message || __('Recent Searches Service is unavailable');
}
}
diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js
new file mode 100644
index 00000000000..38327472cb3
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/visual_token_value.js
@@ -0,0 +1,117 @@
+import _ from 'underscore';
+import FilteredSearchContainer from '~/filtered_search/container';
+import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import DropdownUtils from '~/filtered_search/dropdown_utils';
+import Flash from '~/flash';
+import UsersCache from '~/lib/utils/users_cache';
+import { __ } from '~/locale';
+
+export default class VisualTokenValue {
+ constructor(tokenValue, tokenType) {
+ this.tokenValue = tokenValue;
+ this.tokenType = tokenType;
+ }
+
+ render(tokenValueContainer, tokenValueElement) {
+ const { tokenType, tokenValue } = this;
+
+ if (['none', 'any'].includes(tokenValue.toLowerCase())) {
+ return;
+ }
+
+ if (tokenType === 'label') {
+ this.updateLabelTokenColor(tokenValueContainer);
+ } else if (tokenType === 'author' || tokenType === 'assignee') {
+ this.updateUserTokenAppearance(tokenValueContainer, tokenValueElement);
+ } else if (tokenType === 'my-reaction') {
+ this.updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement);
+ }
+ }
+
+ updateUserTokenAppearance(tokenValueContainer, tokenValueElement) {
+ const { tokenValue } = this;
+ const username = this.tokenValue.replace(/^@/, '');
+
+ return (
+ UsersCache.retrieve(username)
+ .then(user => {
+ if (!user) {
+ return;
+ }
+
+ /* eslint-disable no-param-reassign */
+ tokenValueContainer.dataset.originalValue = tokenValue;
+ tokenValueElement.innerHTML = `
+ <img class="avatar s20" src="${user.avatar_url}" alt="">
+ ${_.escape(user.name)}
+ `;
+ /* eslint-enable no-param-reassign */
+ })
+ // ignore error and leave username in the search bar
+ .catch(() => {})
+ );
+ }
+
+ updateLabelTokenColor(tokenValueContainer) {
+ const { tokenValue } = this;
+ const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
+ const { baseEndpoint } = filteredSearchInput.dataset;
+ const labelsEndpoint = FilteredSearchVisualTokens.getEndpointWithQueryParams(
+ `${baseEndpoint}/labels.json`,
+ filteredSearchInput.dataset.endpointQueryParams,
+ );
+
+ return AjaxCache.retrieve(labelsEndpoint)
+ .then(labels => {
+ const matchingLabel = (labels || []).find(
+ label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
+ );
+
+ if (!matchingLabel) {
+ return;
+ }
+
+ VisualTokenValue.setTokenStyle(
+ tokenValueContainer,
+ matchingLabel.color,
+ matchingLabel.text_color,
+ );
+ })
+ .catch(() => new Flash(__('An error occurred while fetching label colors.')));
+ }
+
+ static setTokenStyle(tokenValueContainer, backgroundColor, textColor) {
+ const token = tokenValueContainer;
+
+ token.style.backgroundColor = backgroundColor;
+ token.style.color = textColor;
+
+ if (textColor === '#FFFFFF') {
+ const removeToken = token.querySelector('.remove-token');
+ removeToken.classList.add('inverted');
+ }
+
+ return token;
+ }
+
+ updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement) {
+ const container = tokenValueContainer;
+ const element = tokenValueElement;
+ const value = this.tokenValue;
+
+ return (
+ import(/* webpackChunkName: 'emoji' */ '../emoji')
+ .then(Emoji => {
+ if (!Emoji.isEmojiNameValid(value)) {
+ return;
+ }
+
+ container.dataset.originalValue = value;
+ element.innerHTML = Emoji.glEmojiTag(value);
+ })
+ // ignore error and leave emoji name in the search bar
+ .catch(() => {})
+ );
+ }
+}
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 2b6af9060d1..2566ed6b47c 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -1,4 +1,5 @@
import bp from './breakpoints';
+import { SIDEBAR_COLLAPSED_CLASS } from './contextual_sidebar';
const HIDE_INTERVAL_TIMEOUT = 300;
const IS_OVER_CLASS = 'is-over';
@@ -29,7 +30,7 @@ const setHeaderHeight = () => {
};
export const isSidebarCollapsed = () =>
- sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
+ sidebar && sidebar.classList.contains(SIDEBAR_COLLAPSED_CLASS);
export const canShowActiveSubItems = el => {
if (el.classList.contains('active') && !isSidebarCollapsed()) {
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 42d14b65b3a..92c3bcb5012 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,6 +1,9 @@
<script>
/* eslint-disable vue/require-default-prop */
-import Identicon from '../../vue_shared/components/identicon.vue';
+import _ from 'underscore';
+import Identicon from '~/vue_shared/components/identicon.vue';
+import highlight from '~/lib/utils/highlight';
+import { truncateNamespace } from '~/lib/utils/text_utility';
export default {
components: {
@@ -36,43 +39,13 @@ export default {
},
computed: {
hasAvatar() {
- return this.avatarUrl !== null;
+ return _.isString(this.avatarUrl) && !_.isEmpty(this.avatarUrl);
},
- highlightedItemName() {
- if (this.matcher) {
- const matcherRegEx = new RegExp(this.matcher, 'gi');
- const matches = this.itemName.match(matcherRegEx);
-
- if (matches && matches.length > 0) {
- return this.itemName.replace(matches[0], `<b>${matches[0]}</b>`);
- }
- }
- return this.itemName;
- },
- /**
- * Smartly truncates item namespace by doing two things;
- * 1. Only include Group names in path by removing item name
- * 2. Only include first and last group names in the path
- * when namespace has more than 2 groups present
- *
- * First part (removal of item name from namespace) can be
- * done from backend but doing so involves migration of
- * existing item namespaces which is not wise thing to do.
- */
truncatedNamespace() {
- if (!this.namespace) {
- return null;
- }
- const namespaceArr = this.namespace.split(' / ');
-
- namespaceArr.splice(-1, 1);
- let namespace = namespaceArr.join(' / ');
-
- if (namespaceArr.length > 2) {
- namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
- }
-
- return namespace;
+ return truncateNamespace(this.namespace);
+ },
+ highlightedItemName() {
+ return highlight(this.itemName, this.matcher);
},
},
};
@@ -92,8 +65,16 @@ export default {
/>
</div>
<div class="frequent-items-item-metadata-container">
- <div :title="itemName" class="frequent-items-item-title" v-html="highlightedItemName"></div>
- <div v-if="truncatedNamespace" :title="namespace" class="frequent-items-item-namespace">
+ <div
+ :title="itemName"
+ class="frequent-items-item-title js-frequent-items-item-title"
+ v-html="highlightedItemName"
+ ></div>
+ <div
+ v-if="namespace"
+ :title="namespace"
+ class="frequent-items-item-namespace js-frequent-items-item-namespace"
+ >
{{ truncatedNamespace }}
</div>
</div>
diff --git a/app/assets/javascripts/frequent_items/store/actions.js b/app/assets/javascripts/frequent_items/store/actions.js
index 3dd89a82a42..ba62ab67e50 100644
--- a/app/assets/javascripts/frequent_items/store/actions.js
+++ b/app/assets/javascripts/frequent_items/store/actions.js
@@ -51,7 +51,7 @@ export const fetchSearchedItems = ({ state, dispatch }, searchQuery) => {
const params = {
simple: true,
per_page: 20,
- membership: !!gon.current_user_id,
+ membership: Boolean(gon.current_user_id),
};
if (state.namespace === 'projects') {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index c81e754df4c..0af9aabd8cf 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import 'at.js';
import _ from 'underscore';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
@@ -461,7 +462,10 @@ class GfmAutoComplete {
// We can ignore this for quick actions because they are processed
// before Markdown.
if (!this.setting.skipMarkdownCharacterTest) {
- withoutAt = withoutAt.replace(/([~\-_*`])/g, '\\$&');
+ withoutAt = withoutAt
+ .replace(/(~~|`|\*)/g, '\\$1')
+ .replace(/(\b)(_+)/g, '$1\\$2') // only escape underscores at the start
+ .replace(/(_+)(\b)/g, '\\$1$2'); // or end of words
}
return `${at}${withoutAt}`;
@@ -474,6 +478,16 @@ class GfmAutoComplete {
}
return null;
},
+ highlighter(li, query) {
+ // override default behaviour to escape dot character
+ // see https://github.com/ichord/At.js/pull/576
+ if (!query) {
+ return li;
+ }
+ const escapedQuery = query.replace(/[.+]/, '\\$&');
+ const regexp = new RegExp(`>\\s*([^<]*?)(${escapedQuery})([^<]*)\\s*<`, 'ig');
+ return li.replace(regexp, (str, $1, $2, $3) => `> ${$1}<strong>${$2}</strong>${$3} <`);
+ },
};
}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index a8ac2f510a4..05f34391323 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -307,8 +307,8 @@ GitLabDropdown = (function() {
// 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.icon = !!this.options.icon;
+ this.highlight = Boolean(this.options.highlight);
+ this.icon = Boolean(this.options.icon);
this.filterInputBlur =
this.options.filterInputBlur != null ? this.options.filterInputBlur : true;
// If no input is passed create a default one
@@ -335,6 +335,10 @@ GitLabDropdown = (function() {
_this.fullData = data;
_this.parseData(_this.fullData);
_this.focusTextInput();
+
+ // Update dropdown position since remote data may have changed dropdown size
+ _this.dropdown.find('.dropdown-menu-toggle').dropdown('update');
+
if (
_this.options.filterable &&
_this.filter &&
@@ -561,10 +565,14 @@ GitLabDropdown = (function() {
!$target.data('isLink')
) {
e.stopPropagation();
- return false;
- } else {
- return true;
+
+ // This prevents automatic scrolling to the top
+ if ($target.is('a')) {
+ return false;
+ }
}
+
+ return true;
}
};
@@ -656,23 +664,7 @@ GitLabDropdown = (function() {
if (this.options.renderMenu) {
return this.options.renderMenu(html);
} else {
- var ul = document.createElement('ul');
-
- for (var i = 0; i < html.length; i += 1) {
- var el = html[i];
-
- if (el instanceof $) {
- el = el.get(0);
- }
-
- if (typeof el === 'string') {
- ul.innerHTML += el;
- } else {
- ul.appendChild(el);
- }
- }
-
- return ul;
+ return $('<ul>').append(html);
}
};
@@ -719,6 +711,10 @@ GitLabDropdown = (function() {
}
html = document.createElement('li');
+ if (rowHidden) {
+ html.style.display = 'none';
+ }
+
if (data === 'divider' || data === 'separator') {
html.className = data;
return html;
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index a5b8c357e8a..04301c9ce12 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { __ } from '~/locale';
/**
* This class overrides the browser's validation error bubbles, displaying custom
@@ -61,7 +62,7 @@ export default class GlFieldError {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
- this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
+ this.errorMessage = this.inputElement.attr('title') || __('This field is required.');
this.fieldErrorElement = $(`<p class='${errorMessageClass} hidden'>${this.errorMessage}</p>`);
this.state = {
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index d5d5954ce6a..c4fd719c8d0 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -15,7 +15,7 @@ export default class GlFieldErrors {
initValidators() {
// register selectors here as needed
- const validateSelectors = [':text', ':password', '[type=email]']
+ const validateSelectors = [':text', ':password', '[type=email]', '[type=url]', '[type=number]']
.map(selector => `input${selector}`)
.join(',');
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index f5e2e46237f..a66555838ba 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import autosize from 'autosize';
-import GfmAutoComplete, * as GFMConfig from './gfm_auto_complete';
+import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
@@ -8,12 +8,12 @@ export default class GLForm {
constructor(form, enableGFM = {}) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
- this.enableGFM = Object.assign({}, GFMConfig.defaultAutocompleteConfig, enableGFM);
+ this.enableGFM = Object.assign({}, defaultAutocompleteConfig, enableGFM);
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
if (item !== 'emojis') {
- this.enableGFM[item] = !!dataSources[item];
+ this.enableGFM[item] = Boolean(dataSources[item]);
}
});
// Before we start, we should clean up any previous data for this form
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
index efba6fc1aff..96051b612b5 100644
--- a/app/assets/javascripts/gpg_badges.js
+++ b/app/assets/javascripts/gpg_badges.js
@@ -20,7 +20,7 @@ export default class GpgBadges {
const endpoint = tag.data('signaturesPath');
if (!endpoint) {
displayError();
- return Promise.reject(new Error('Missing commit signatures endpoint!'));
+ return Promise.reject(new Error(__('Missing commit signatures endpoint!')));
}
const params = parseQueryStringIntoObject(tag.serialize());
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
deleted file mode 100644
index dcda625f587..00000000000
--- a/app/assets/javascripts/group_avatar.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import $ from 'jquery';
-
-export default function groupAvatar() {
- $('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
- const form = $(this).closest('form');
- return form.find('.js-group-avatar-input').click();
- });
- $('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
- const form = $(this).closest('form');
- const filename = $(this)
- .val()
- .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
- return form.find('.js-avatar-filename').text(filename);
- });
-}
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 29dc2d6a8a3..aa50fd8ff62 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -244,7 +244,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
:label="s__('GroupsTree|Loading groups')"
- :size="2"
+ size="md"
class="loading-animation prepend-top-20"
/>
<groups-component
diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js
index 26510fcdb2a..ce0c9256148 100644
--- a/app/assets/javascripts/groups/transfer_dropdown.js
+++ b/app/assets/javascripts/groups/transfer_dropdown.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { __ } from '~/locale';
export default class TransferDropdown {
constructor() {
@@ -13,7 +14,7 @@ export default class TransferDropdown {
}
buildDropdown() {
- const extraOptions = [{ id: '', text: 'No parent group' }, 'divider'];
+ const extraOptions = [{ id: '', text: __('No parent group') }, 'divider'];
this.groupDropdown.glDropdown({
selectable: true,
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index bdadbb1bb2a..a1263d1cdab 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Api from './api';
import { normalizeHeaders } from './lib/utils/common_utils';
+import { __ } from '~/locale';
export default function groupsSelect() {
import(/* webpackChunkName: 'select2' */ 'select2/select2')
@@ -18,7 +19,7 @@ export default function groupsSelect() {
: Api.groupsPath;
$select.select2({
- placeholder: 'Search for a group',
+ placeholder: __('Search for a group'),
allowClear: $select.hasClass('allowClear'),
multiple: $select.hasClass('multiselect'),
minimumInputLength: 0,
diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js
new file mode 100644
index 00000000000..2c2a04d5b5e
--- /dev/null
+++ b/app/assets/javascripts/helpers/monitor_helper.js
@@ -0,0 +1,17 @@
+/* eslint-disable import/prefer-default-export */
+
+export const makeDataSeries = (queryResults, defaultConfig) =>
+ queryResults.reduce((acc, result) => {
+ const data = result.values.filter(([, value]) => !Number.isNaN(value));
+ if (!data.length) {
+ return acc;
+ }
+ const relevantMetric = defaultConfig.name.toLowerCase().replace(' ', '_');
+ const name = result.metric[relevantMetric];
+ const series = { data };
+ if (name) {
+ series.name = `${defaultConfig.name}: ${name}`;
+ }
+
+ return acc.concat({ ...defaultConfig, ...series });
+ }, []);
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index 7c769ab7fa0..7b4e03be8eb 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -78,7 +78,7 @@ export default {
data-container="body"
data-placement="right"
type="button"
- class="ide-sidebar-link js-ide-commit-mode"
+ class="ide-sidebar-link js-ide-commit-mode qa-commit-mode-tab"
@click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
>
<icon name="commit" />
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index d360dc42cd3..685d8a6b245 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -1,17 +1,24 @@
<script>
import _ from 'underscore';
-import { mapActions, mapState, mapGetters } from 'vuex';
+import { mapState, mapGetters, createNamespacedHelpers } from 'vuex';
import { sprintf, __ } from '~/locale';
-import * as consts from '../../stores/modules/commit/constants';
+import consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue';
+import NewMergeRequestOption from './new_merge_request_option.vue';
+
+const { mapState: mapCommitState, mapActions: mapCommitActions } = createNamespacedHelpers(
+ 'commit',
+);
export default {
components: {
RadioGroup,
+ NewMergeRequestOption,
},
computed: {
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
- ...mapGetters(['currentProject', 'currentBranch']),
+ ...mapCommitState(['commitAction']),
+ ...mapGetters(['currentBranch']),
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
@@ -19,12 +26,12 @@ export default {
false,
);
},
- disableMergeRequestRadio() {
+ containsStagedChanges() {
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
},
},
watch: {
- disableMergeRequestRadio() {
+ containsStagedChanges() {
this.updateSelectedCommitAction();
},
},
@@ -32,18 +39,17 @@ export default {
this.updateSelectedCommitAction();
},
methods: {
- ...mapActions('commit', ['updateCommitAction']),
+ ...mapCommitActions(['updateCommitAction']),
updateSelectedCommitAction() {
if (this.currentBranch && !this.currentBranch.can_push) {
this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
- } else if (this.disableMergeRequestRadio) {
+ } else if (this.containsStagedChanges) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
},
},
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
- commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
currentBranchPermissionsTooltip: __(
"This option is disabled as you don't have write permissions for the current branch",
),
@@ -51,7 +57,7 @@ export default {
</script>
<template>
- <div class="append-bottom-15 ide-commit-radios">
+ <div class="append-bottom-15 ide-commit-options">
<radio-group
:value="$options.commitToCurrentBranch"
:disabled="currentBranch && !currentBranch.can_push"
@@ -64,13 +70,6 @@ export default {
:label="__('Create a new branch')"
:show-input="true"
/>
- <radio-group
- v-if="currentProject.merge_requests_enabled"
- :value="$options.commitToNewBranchMR"
- :label="__('Create a new branch and merge request')"
- :title="__('This option is disabled while you still have unstaged changes')"
- :show-input="true"
- :disabled="disableMergeRequestRadio"
- />
+ <new-merge-request-option />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
index 00b2d236da3..6b0aa5b2b2b 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -108,6 +108,7 @@ export default {
:placeholder="placeholder"
:value="text"
class="note-textarea ide-commit-message-textarea"
+ dir="auto"
name="commit-message"
@scroll="handleScroll"
@input="onInput"
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
new file mode 100644
index 00000000000..b2e7b15089c
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -0,0 +1,43 @@
+<script>
+import { mapGetters, createNamespacedHelpers } from 'vuex';
+
+const {
+ mapState: mapCommitState,
+ mapGetters: mapCommitGetters,
+ mapActions: mapCommitActions,
+} = createNamespacedHelpers('commit');
+
+export default {
+ computed: {
+ ...mapCommitState(['shouldCreateMR']),
+ ...mapCommitGetters(['isCommittingToCurrentBranch', 'isCommittingToDefaultBranch']),
+ ...mapGetters(['hasMergeRequest', 'isOnDefaultBranch']),
+ currentBranchHasMr() {
+ return this.hasMergeRequest && this.isCommittingToCurrentBranch;
+ },
+ showNewMrOption() {
+ return (
+ this.isCommittingToDefaultBranch || !this.currentBranchHasMr || this.isCommittingToNewBranch
+ );
+ },
+ },
+ mounted() {
+ this.setShouldCreateMR();
+ },
+ methods: {
+ ...mapCommitActions(['toggleShouldCreateMR', 'setShouldCreateMR']),
+ },
+};
+</script>
+
+<template>
+ <div v-if="showNewMrOption">
+ <hr class="my-2" />
+ <label class="mb-0">
+ <input :checked="shouldCreateMR" type="checkbox" @change="toggleShouldCreateMR" />
+ <span class="prepend-left-10">
+ {{ __('Start a new merge request') }}
+ </span>
+ </label>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 2b44438f849..9161eb3d9b1 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -38,8 +38,8 @@ export default {
},
},
computed: {
- ...mapState('commit', ['commitAction']),
- ...mapGetters('commit', ['newBranchName']),
+ ...mapState('commit', ['commitAction', 'newBranchName']),
+ ...mapGetters('commit', ['placeholderBranchName']),
tooltipTitle() {
return this.disabled ? this.title : '';
},
@@ -73,7 +73,8 @@ export default {
</label>
<div v-if="commitAction === value && showInput" class="ide-commit-new-branch">
<input
- :placeholder="newBranchName"
+ :placeholder="placeholderBranchName"
+ :value="newBranchName"
type="text"
class="form-control monospace"
@input="updateBranchName($event.target.value)"
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index d6673cf0421..80a6ab9598a 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -23,7 +23,7 @@ export default {
type: Object,
required: true,
},
- mouseOver: {
+ dropdownOpen: {
type: Boolean,
required: true,
},
@@ -92,8 +92,9 @@ export default {
<new-dropdown
:type="file.type"
:path="file.path"
- :mouse-over="mouseOver"
+ :is-open="dropdownOpen"
class="prepend-left-8"
+ v-on="$listeners"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 9894ebb0624..e41b1530226 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,6 +1,7 @@
<script>
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import NewModal from './new_dropdown/modal.vue';
@@ -22,6 +23,8 @@ export default {
FindFile,
ErrorMessage,
CommitEditorHeader,
+ GlButton,
+ GlLoadingIcon,
},
props: {
rightPaneComponent: {
@@ -47,13 +50,15 @@ export default {
'someUncommittedChanges',
'isCommitModeActive',
'allBlobs',
+ 'emptyRepo',
+ 'currentTree',
]),
},
mounted() {
window.onbeforeunload = e => this.onBeforeUnload(e);
},
methods: {
- ...mapActions(['toggleFileFinder']),
+ ...mapActions(['toggleFileFinder', 'openNewEntryModal']),
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
@@ -98,17 +103,40 @@ export default {
<repo-editor :file="activeFile" class="multi-file-edit-pane-content" />
</template>
<template v-else>
- <div v-once class="ide-empty-state">
+ <div class="ide-empty-state">
<div class="row js-empty-state">
<div class="col-12">
<div class="svg-content svg-250"><img :src="emptyStateSvgPath" /></div>
</div>
<div class="col-12">
<div class="text-content text-center">
- <h4>Welcome to the GitLab IDE</h4>
- <p>
- Select a file from the left sidebar to begin editing. Afterwards, you'll be able
- to commit your changes.
+ <h4>
+ {{ __('Make and review changes in the browser with the Web IDE') }}
+ </h4>
+ <template v-if="emptyRepo">
+ <p>
+ {{
+ __(
+ "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes.",
+ )
+ }}
+ </p>
+ <gl-button
+ variant="success"
+ :title="__('New file')"
+ :aria-label="__('New file')"
+ @click="openNewEntryModal({ type: 'blob' })"
+ >
+ {{ __('New file') }}
+ </gl-button>
+ </template>
+ <gl-loading-icon v-else-if="!currentTree || currentTree.loading" size="md" />
+ <p v-else>
+ {{
+ __(
+ "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
+ )
+ }}
</p>
</div>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 81374f26645..95782b2c88a 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -54,14 +54,17 @@ export default {
<slot name="header"></slot>
</header>
<div class="ide-tree-body h-100">
- <file-row
- v-for="file in currentTree.tree"
- :key="file.key"
- :file="file"
- :level="0"
- :extra-component="$options.FileRowExtra"
- @toggleTreeOpen="toggleTreeOpen"
- />
+ <template v-if="currentTree.tree.length">
+ <file-row
+ v-for="file in currentTree.tree"
+ :key="file.key"
+ :file="file"
+ :level="0"
+ :extra-component="$options.FileRowExtra"
+ @toggleTreeOpen="toggleTreeOpen"
+ />
+ </template>
+ <div v-else class="file-row">{{ __('No files') }}</div>
</div>
</template>
</div>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index d7a7b1b4d78..27d24fa5e1d 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -1,7 +1,6 @@
<script>
import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
-import newModal from './modal.vue';
import upload from './upload.vue';
import ItemButton from './button.vue';
import { modalTypes } from '../../constants';
@@ -9,7 +8,6 @@ import { modalTypes } from '../../constants';
export default {
components: {
icon,
- newModal,
upload,
ItemButton,
},
@@ -23,38 +21,29 @@ export default {
required: false,
default: '',
},
- mouseOver: {
+ isOpen: {
type: Boolean,
- required: true,
+ required: false,
+ default: false,
},
},
- data() {
- return {
- dropdownOpen: false,
- };
- },
watch: {
- dropdownOpen() {
+ isOpen() {
this.$nextTick(() => {
this.$refs.dropdownMenu.scrollIntoView({
block: 'nearest',
});
});
},
- mouseOver() {
- if (!this.mouseOver) {
- this.dropdownOpen = false;
- }
- },
},
methods: {
...mapActions(['createTempEntry', 'openNewEntryModal', 'deleteEntry']),
createNewItem(type) {
this.openNewEntryModal({ type, path: this.path });
- this.dropdownOpen = false;
+ this.$emit('toggle', false);
},
openDropdown() {
- this.dropdownOpen = !this.dropdownOpen;
+ this.$emit('toggle', !this.isOpen);
},
},
modalTypes,
@@ -65,7 +54,7 @@ export default {
<div class="ide-new-btn">
<div
:class="{
- show: dropdownOpen,
+ show: isOpen,
}"
class="dropdown d-flex"
>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index c9c4e9e86f8..f67666f1fbf 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import flash from '~/flash';
+import { __, sprintf, s__ } from '~/locale';
import { mapActions, mapState, mapGetters } from 'vuex';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { modalTypes } from '../../constants';
@@ -15,18 +16,20 @@ export default {
};
},
computed: {
- ...mapState(['entryModal']),
+ ...mapState(['entries', 'entryModal']),
...mapGetters('fileTemplates', ['templateTypes']),
entryName: {
get() {
+ const entryPath = this.entryModal.entry.path;
+
if (this.entryModal.type === modalTypes.rename) {
- return this.name || this.entryModal.entry.name;
+ return this.name || entryPath;
}
- return this.name || (this.entryModal.path !== '' ? `${this.entryModal.path}/` : '');
+ return this.name || (entryPath ? `${entryPath}/` : '');
},
set(val) {
- this.name = val;
+ this.name = val.trim();
},
},
modalTitle() {
@@ -62,10 +65,40 @@ export default {
...mapActions(['createTempEntry', 'renameEntry']),
submitForm() {
if (this.entryModal.type === modalTypes.rename) {
- this.renameEntry({
- path: this.entryModal.entry.path,
- name: this.entryName,
- });
+ if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
+ flash(
+ sprintf(s__('The name %{entryName} is already taken in this directory.'), {
+ entryName: this.entryName,
+ }),
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+ } else {
+ let parentPath = this.entryName.split('/');
+ const entryName = parentPath.pop();
+ parentPath = parentPath.join('/');
+
+ const createPromise =
+ parentPath && !this.entries[parentPath]
+ ? this.createTempEntry({ name: parentPath, type: 'tree' })
+ : Promise.resolve();
+
+ createPromise
+ .then(() =>
+ this.renameEntry({
+ path: this.entryModal.entry.path,
+ name: entryName,
+ entryPath: null,
+ parentPath,
+ }),
+ )
+ .catch(() =>
+ flash(__('Error creating a new path'), 'alert', document, null, false, true),
+ );
+ }
} else {
this.createTempEntry({
name: this.name,
@@ -82,7 +115,14 @@ export default {
$('#ide-new-entry').modal('toggle');
},
focusInput() {
+ const name = this.entries[this.entryName] ? this.entries[this.entryName].name : null;
+ const inputValue = this.$refs.fieldName.value;
+
this.$refs.fieldName.focus();
+
+ if (name) {
+ this.$refs.fieldName.setSelectionRange(inputValue.indexOf(name), inputValue.length);
+ }
},
closedModal() {
this.name = '';
@@ -94,6 +134,7 @@ export default {
<template>
<gl-modal
id="ide-new-entry"
+ class="qa-new-file-modal"
:header-title-text="modalTitle"
:footer-primary-button-text="buttonLabel"
footer-primary-button-variant="success"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index ec759043efc..188518dd419 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -57,6 +57,8 @@ export default {
type: 'blob',
content: result,
base64: !isText,
+ binary: !isText,
+ rawPath: !isText ? target.result : '',
});
},
readFile(file) {
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 451c8030e16..5ae73b2fc9c 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -24,7 +24,13 @@ export default {
...mapState(['pipelinesEmptyStateSvgPath', 'links']),
...mapGetters(['currentProject']),
...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']),
- ...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']),
+ ...mapState('pipelines', [
+ 'isLoadingPipeline',
+ 'hasLoadedPipeline',
+ 'latestPipeline',
+ 'stages',
+ 'isLoadingJobs',
+ ]),
ciLintText() {
return sprintf(
__('You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}.'),
@@ -36,7 +42,7 @@ export default {
);
},
showLoadingIcon() {
- return this.isLoadingPipeline && this.latestPipeline === null;
+ return this.isLoadingPipeline && !this.hasLoadedPipeline;
},
},
created() {
@@ -51,7 +57,7 @@ export default {
<template>
<div class="ide-pipeline">
<gl-loading-icon v-if="showLoadingIcon" :size="2" class="prepend-top-default" />
- <template v-else-if="latestPipeline !== null">
+ <template v-else-if="hasLoadedPipeline">
<header v-if="latestPipeline" class="ide-tree-header ide-pipeline-header">
<ci-icon :status="latestPipeline.details.status" :size="24" />
<span class="prepend-left-8">
@@ -62,7 +68,7 @@ export default {
</span>
</header>
<empty-state
- v-if="latestPipeline === false"
+ v-if="!latestPipeline"
:help-page-path="links.ciHelpPagePath"
:empty-state-svg-path="pipelinesEmptyStateSvgPath"
:can-set-ci="true"
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index c98dda00817..6999746f115 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -105,7 +105,7 @@ export default {
.then(() => {
this.initManager('#ide-preview', this.sandboxOpts, {
fileResolver: {
- isFile: p => Promise.resolve(!!this.entries[createPathWithExt(p)]),
+ isFile: p => Promise.resolve(Boolean(this.entries[createPathWithExt(p)])),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
});
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 8dd88f187d4..5201c33b1b4 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -5,7 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import CommitFilesList from './commit_sidebar/list.vue';
import EmptyState from './commit_sidebar/empty_state.vue';
-import * as consts from '../stores/modules/commit/constants';
+import consts from '../stores/modules/commit/constants';
import { activityBarViews, stageKeys } from '../constants';
export default {
@@ -30,7 +30,7 @@ export default {
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommittedChanges', 'activeFile']),
...mapGetters('commit', ['discardDraftButtonDisabled']),
showStageUnstageArea() {
- return !!(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal);
+ return Boolean(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal);
},
activeFileKey() {
return this.activeFile ? this.activeFile.key : null;
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 94a9e87369c..b0c4969c5e4 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -1,5 +1,6 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
+import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import flash from '~/flash';
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
@@ -35,7 +36,7 @@ export default {
]),
...mapGetters('fileTemplates', ['showFileTemplatesBar']),
shouldHideEditor() {
- return this.file && this.file.binary && !this.file.content;
+ return this.file && this.file.binary;
},
showContentViewer() {
return (
@@ -56,6 +57,10 @@ export default {
active: this.file.viewMode === 'preview',
};
},
+ fileType() {
+ const info = viewerInformationForPath(this.file.path);
+ return (info && info.id) || '';
+ },
},
watch: {
file(newVal, oldVal) {
@@ -120,6 +125,7 @@ export default {
'setFileEOL',
'updateViewer',
'removePendingTab',
+ 'triggerFilesChange',
]),
initEditor() {
if (this.shouldHideEditor) return;
@@ -251,6 +257,7 @@ export default {
'is-added': file.tempFile,
}"
class="multi-file-editor-holder"
+ @focusout="triggerFilesChange"
></div>
<content-viewer
v-if="showContentViewer"
@@ -258,6 +265,7 @@ export default {
:path="file.rawPath || file.path"
:file-size="file.size"
:project-path="file.projectId"
+ :type="fileType"
/>
<diff-viewer
v-if="showDiffViewer"
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 7c560c89695..e30670e119f 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -72,4 +72,11 @@ export const modalTypes = {
tree: 'tree',
};
+export const commitActionTypes = {
+ move: 'move',
+ delete: 'delete',
+ create: 'create',
+ update: 'update',
+};
+
export const packageJsonPath = 'package.json';
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index 229ef168926..8c84b98a108 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -1,8 +1,9 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
-import { join as joinPath } from 'path';
+import { joinPaths } from '~/lib/utils/url_utility';
import flash from '~/flash';
import store from './stores';
+import { __ } from '~/locale';
Vue.use(VueRouter);
@@ -34,7 +35,7 @@ const EmptyRouterComponent = {
const router = new VueRouter({
mode: 'history',
- base: `${gon.relative_url_root}/-/ide/`,
+ base: joinPaths(gon.relative_url_root || '', '/-/ide/'),
routes: [
{
path: '/project/:namespace+/:project',
@@ -46,11 +47,11 @@ const router = new VueRouter({
},
{
path: ':targetmode(edit|tree|blob)/:branchid+/',
- redirect: to => joinPath(to.path, '/-/'),
+ redirect: to => joinPaths(to.path, '/-/'),
},
{
path: ':targetmode(edit|tree|blob)',
- redirect: to => joinPath(to.path, '/master/-/'),
+ redirect: to => joinPaths(to.path, '/master/-/'),
},
{
path: 'merge_requests/:mrid',
@@ -58,7 +59,7 @@ const router = new VueRouter({
},
{
path: '',
- redirect: to => joinPath(to.path, '/edit/master/-/'),
+ redirect: to => joinPaths(to.path, '/edit/master/-/'),
},
],
},
@@ -94,7 +95,7 @@ router.beforeEach((to, from, next) => {
})
.catch(e => {
flash(
- 'Error while loading the project data. Please try again.',
+ __('Error while loading the project data. Please try again.'),
'alert',
document,
null,
diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index e35595ab1fd..dac2a8e8b51 100644
--- a/app/assets/javascripts/ide/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
@@ -11,7 +11,7 @@ export const defaultEditorOptions = {
export default [
{
- readOnly: model => !!model.file.file_lock,
+ readOnly: model => Boolean(model.file.file_lock),
quickSuggestions: model => !(model.language === 'markdown'),
},
];
diff --git a/app/assets/javascripts/ide/lib/files.js b/app/assets/javascripts/ide/lib/files.js
new file mode 100644
index 00000000000..b8abaa41f23
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/files.js
@@ -0,0 +1,119 @@
+import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
+import { decorateData, sortTree } from '../stores/utils';
+
+export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/');
+
+export const splitParent = path => {
+ const idx = path.lastIndexOf('/');
+
+ return {
+ parent: idx >= 0 ? path.substring(0, idx) : null,
+ name: idx >= 0 ? path.substring(idx + 1) : path,
+ };
+};
+
+/**
+ * Create file objects from a list of file paths.
+ */
+export const decorateFiles = ({
+ data,
+ projectId,
+ branchId,
+ tempFile = false,
+ content = '',
+ base64 = false,
+ binary = false,
+ rawPath = '',
+}) => {
+ const treeList = [];
+ const entries = {};
+
+ // These mutable variable references end up being exported and used by `createTempEntry`
+ let file;
+ let parentPath;
+
+ const insertParent = path => {
+ if (!path) {
+ return null;
+ } else if (entries[path]) {
+ return entries[path];
+ }
+
+ const { parent, name } = splitParent(path);
+ const parentFolder = parent && insertParent(parent);
+ parentPath = parentFolder && parentFolder.path;
+
+ const tree = decorateData({
+ projectId,
+ branchId,
+ id: path,
+ name,
+ path,
+ url: `/${projectId}/tree/${branchId}/-/${escapeFileUrl(path)}/`,
+ type: 'tree',
+ parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
+ tempFile,
+ changed: tempFile,
+ opened: tempFile,
+ parentPath,
+ });
+
+ Object.assign(entries, {
+ [path]: tree,
+ });
+
+ if (parentFolder) {
+ parentFolder.tree.push(tree);
+ } else {
+ treeList.push(tree);
+ }
+
+ return tree;
+ };
+
+ data.forEach(path => {
+ const { parent, name } = splitParent(path);
+
+ const fileFolder = parent && insertParent(parent);
+
+ if (name) {
+ parentPath = fileFolder && fileFolder.path;
+
+ file = decorateData({
+ projectId,
+ branchId,
+ id: path,
+ name,
+ path,
+ url: `/${projectId}/blob/${branchId}/-/${escapeFileUrl(path)}`,
+ type: 'blob',
+ parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
+ tempFile,
+ changed: tempFile,
+ content,
+ base64,
+ binary,
+ rawPath,
+ previewMode: viewerInformationForPath(name),
+ parentPath,
+ });
+
+ Object.assign(entries, {
+ [path]: file,
+ });
+
+ if (fileFolder) {
+ fileFolder.tree.push(file);
+ } else {
+ treeList.push(file);
+ }
+ }
+ });
+
+ return {
+ entries,
+ treeList: sortTree(treeList),
+ file,
+ parentPath,
+ };
+};
diff --git a/app/assets/javascripts/ide/lib/keymap.json b/app/assets/javascripts/ide/lib/keymap.json
index 131abfebbed..2db87c07dde 100644
--- a/app/assets/javascripts/ide/lib/keymap.json
+++ b/app/assets/javascripts/ide/lib/keymap.json
@@ -7,5 +7,13 @@
"name": "toggleFileFinder",
"params": true
}
+ },
+ {
+ "id": "save-files",
+ "label": "Save files",
+ "bindings": ["CtrlCmd+KEY_S"],
+ "action": {
+ "name": "triggerFilesChange"
+ }
}
]
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 13449592e62..ba33b6826d6 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -40,6 +40,9 @@ export default {
getProjectData(namespace, project) {
return Api.project(`${namespace}/${project}`);
},
+ getProjectMergeRequests(projectId, params = {}) {
+ return Api.projectMergeRequests(projectId, params);
+ },
getProjectMergeRequestData(projectId, mergeRequestId, params = {}) {
return Api.projectMergeRequest(projectId, mergeRequestId, params);
},
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index e10a132ab4b..5429b834708 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -1,12 +1,15 @@
import $ from 'jquery';
import Vue from 'vue';
+import { __, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
+import _ from 'underscore';
import * as types from './mutation_types';
-import FilesDecoratorWorker from './workers/files_decorator_worker';
+import { decorateFiles } from '../lib/files';
import { stageKeys } from '../constants';
+import service from '../services';
-export const redirectToUrl = (_, url) => visitUrl(url);
+export const redirectToUrl = (self, url) => visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
@@ -53,10 +56,9 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const createTempEntry = (
{ state, commit, dispatch },
- { name, type, content = '', base64 = false },
+ { name, type, content = '', base64 = false, binary = false, rawPath = '' },
) =>
new Promise(resolve => {
- const worker = new FilesDecoratorWorker();
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
if (state.entries[name]) {
@@ -74,40 +76,38 @@ export const createTempEntry = (
return null;
}
- worker.addEventListener('message', ({ data }) => {
- const { file, parentPath } = data;
-
- worker.terminate();
-
- commit(types.CREATE_TMP_ENTRY, {
- data,
- projectId: state.currentProjectId,
- branchId: state.currentBranchId,
- });
-
- if (type === 'blob') {
- commit(types.TOGGLE_FILE_OPEN, file.path);
- commit(types.ADD_FILE_TO_CHANGED, file.path);
- dispatch('setFileActive', file.path);
- }
-
- if (parentPath && !state.entries[parentPath].opened) {
- commit(types.TOGGLE_TREE_OPEN, parentPath);
- }
-
- resolve(file);
- });
-
- worker.postMessage({
+ const data = decorateFiles({
data: [fullName],
projectId: state.currentProjectId,
branchId: state.currentBranchId,
type,
tempFile: true,
- base64,
content,
+ base64,
+ binary,
+ rawPath,
+ });
+ const { file, parentPath } = data;
+
+ commit(types.CREATE_TMP_ENTRY, {
+ data,
+ projectId: state.currentProjectId,
+ branchId: state.currentBranchId,
});
+ if (type === 'blob') {
+ commit(types.TOGGLE_FILE_OPEN, file.path);
+ commit(types.ADD_FILE_TO_CHANGED, file.path);
+ dispatch('setFileActive', file.path);
+ dispatch('triggerFilesChange');
+ }
+
+ if (parentPath && !state.entries[parentPath].opened) {
+ commit(types.TOGGLE_TREE_OPEN, parentPath);
+ }
+
+ resolve(file);
+
return null;
});
@@ -211,26 +211,89 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
if (entry.parentPath && state.entries[entry.parentPath].tree.length === 0) {
dispatch('deleteEntry', entry.parentPath);
}
+
+ dispatch('triggerFilesChange');
};
export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
-export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath = null }) => {
+export const renameEntry = (
+ { dispatch, commit, state },
+ { path, name, entryPath = null, parentPath },
+) => {
const entry = state.entries[entryPath || path];
- commit(types.RENAME_ENTRY, { path, name, entryPath });
+ commit(types.RENAME_ENTRY, { path, name, entryPath, parentPath });
if (entry.type === 'tree') {
- state.entries[entryPath || path].tree.forEach(f =>
- dispatch('renameEntry', { path, name, entryPath: f.path }),
- );
+ const slashedParentPath = parentPath ? `${parentPath}/` : '';
+ const targetEntry = entryPath ? entryPath.split('/').pop() : name;
+ const newParentPath = `${slashedParentPath}${targetEntry}`;
+
+ state.entries[entryPath || path].tree.forEach(f => {
+ dispatch('renameEntry', {
+ path,
+ name,
+ entryPath: f.path,
+ parentPath: newParentPath,
+ });
+ });
}
if (!entryPath && !entry.tempFile) {
dispatch('deleteEntry', path);
}
+
+ dispatch('triggerFilesChange');
};
+export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
+ new Promise((resolve, reject) => {
+ const currentProject = state.projects[projectId];
+ if (!currentProject || !currentProject.branches[branchId] || force) {
+ service
+ .getBranchData(projectId, branchId)
+ .then(({ data }) => {
+ const { id } = data.commit;
+ commit(types.SET_BRANCH, {
+ projectPath: projectId,
+ branchName: branchId,
+ branch: data,
+ });
+ commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+ resolve(data);
+ })
+ .catch(e => {
+ if (e.response.status === 404) {
+ reject(e);
+ } else {
+ flash(
+ __('Error loading branch data. Please try again.'),
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+
+ reject(
+ new Error(
+ sprintf(
+ __('Branch not loaded - %{branchId}'),
+ {
+ branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`,
+ },
+ false,
+ ),
+ ),
+ );
+ }
+ });
+ } else {
+ resolve(currentProject.branches[branchId]);
+ }
+ });
+
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index e74b880e02c..dc40a1fa6a2 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -1,5 +1,6 @@
-import { __ } from '../../../locale';
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
import eventHub from '../../eventhub';
import service from '../../services';
import * as types from '../mutation_types';
@@ -69,7 +70,7 @@ export const getFileData = (
const url = file.prevPath ? file.url.replace(file.path, file.prevPath) : file.url;
return service
- .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${url.replace('/-/', '/')}`)
+ .getFileData(joinPaths(gon.relative_url_root || '', url.replace('/-/', '/')))
.then(({ data, headers }) => {
const normalizedHeaders = normalizeHeaders(headers);
setPageTitle(decodeURI(normalizedHeaders['PAGE-TITLE']));
@@ -264,3 +265,8 @@ export const removePendingTab = ({ commit }, file) => {
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
+
+export const triggerFilesChange = () => {
+ // Used in EE for file mirroring
+ eventHub.$emit('ide.files.change');
+};
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 18c24369996..1273e375859 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -4,6 +4,39 @@ import service from '../../services';
import * as types from '../mutation_types';
import { activityBarViews } from '../../constants';
+export const getMergeRequestsForBranch = ({ commit, state }, { projectId, branchId } = {}) =>
+ service
+ .getProjectMergeRequests(`${projectId}`, {
+ source_branch: branchId,
+ source_project_id: state.projects[projectId].id,
+ order_by: 'created_at',
+ per_page: 1,
+ })
+ .then(({ data }) => {
+ if (data.length > 0) {
+ const currentMR = data[0];
+
+ commit(types.SET_MERGE_REQUEST, {
+ projectPath: projectId,
+ mergeRequestId: currentMR.iid,
+ mergeRequest: currentMR,
+ });
+
+ commit(types.SET_CURRENT_MERGE_REQUEST, `${currentMR.iid}`);
+ }
+ })
+ .catch(e => {
+ flash(
+ __(`Error fetching merge requests for ${branchId}`),
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+ throw e;
+ });
+
export const getMergeRequestData = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index b65f631c99c..dd8f17e4f3a 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -35,48 +35,6 @@ export const getProjectData = ({ commit, state }, { namespace, projectId, force
}
});
-export const getBranchData = (
- { commit, dispatch, state },
- { projectId, branchId, force = false } = {},
-) =>
- new Promise((resolve, reject) => {
- if (
- typeof state.projects[`${projectId}`] === 'undefined' ||
- !state.projects[`${projectId}`].branches[branchId] ||
- force
- ) {
- service
- .getBranchData(`${projectId}`, branchId)
- .then(({ data }) => {
- const { id } = data.commit;
- commit(types.SET_BRANCH, {
- projectPath: `${projectId}`,
- branchName: branchId,
- branch: data,
- });
- commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
- resolve(data);
- })
- .catch(e => {
- if (e.response.status === 404) {
- dispatch('showBranchNotFoundError', branchId);
- } else {
- flash(
- __('Error loading branch data. Please try again.'),
- 'alert',
- document,
- null,
- false,
- true,
- );
- }
- reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
- });
- } else {
- resolve(state.projects[`${projectId}`].branches[branchId]);
- }
- });
-
export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) =>
service
.getBranchData(projectId, branchId)
@@ -125,28 +83,66 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
});
};
-export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath }) => {
- dispatch('setCurrentBranchId', branchId);
-
- dispatch('getBranchData', {
- projectId,
- branchId,
+export const showEmptyState = ({ commit, state }, { projectId, branchId }) => {
+ const treePath = `${projectId}/${branchId}`;
+ commit(types.CREATE_TREE, { treePath });
+ commit(types.TOGGLE_LOADING, {
+ entry: state.trees[treePath],
+ forceValue: false,
});
+};
- return dispatch('getFiles', {
+export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, basePath }) => {
+ dispatch('setCurrentBranchId', branchId);
+
+ if (getters.emptyRepo) {
+ return dispatch('showEmptyState', { projectId, branchId });
+ }
+ return dispatch('getBranchData', {
projectId,
branchId,
- }).then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(state.entries).find(
- key => key === path && !state.entries[key].pending,
- );
- const treeEntry = state.entries[treeEntryKey];
+ })
+ .then(() => {
+ dispatch('getMergeRequestsForBranch', {
+ projectId,
+ branchId,
+ });
+ dispatch('getFiles', {
+ projectId,
+ branchId,
+ })
+ .then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
- if (treeEntry) {
- dispatch('handleTreeEntryAction', treeEntry);
- }
- }
- });
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
+ } else {
+ dispatch('createTempEntry', {
+ name: path,
+ type: 'blob',
+ });
+ }
+ }
+ })
+ .catch(
+ () =>
+ new Error(
+ sprintf(
+ __('An error occurred whilst getting files for - %{branchId}'),
+ {
+ branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`,
+ },
+ false,
+ ),
+ ),
+ );
+ })
+ .catch(() => {
+ dispatch('showBranchNotFoundError', branchId);
+ });
};
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index de5f6050074..75511574d3e 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -1,7 +1,8 @@
+import _ from 'underscore';
import { __ } from '../../../locale';
import service from '../../services';
import * as types from '../mutation_types';
-import FilesDecoratorWorker from '../workers/files_decorator_worker';
+import { decorateFiles } from '../../lib/files';
export const toggleTreeOpen = ({ commit }, path) => {
commit(types.TOGGLE_TREE_OPEN, path);
@@ -32,6 +33,19 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
dispatch('showTreeEntry', row.path);
};
+export const setDirectoryData = ({ state, commit }, { projectId, branchId, treeList }) => {
+ const selectedTree = state.trees[`${projectId}/${branchId}`];
+
+ commit(types.SET_DIRECTORY_DATA, {
+ treePath: `${projectId}/${branchId}`,
+ data: treeList,
+ });
+ commit(types.TOGGLE_LOADING, {
+ entry: selectedTree,
+ forceValue: false,
+ });
+};
+
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => {
if (
@@ -45,44 +59,28 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } =
service
.getFiles(selectedProject.web_url, branchId)
.then(({ data }) => {
- const worker = new FilesDecoratorWorker();
- worker.addEventListener('message', e => {
- const { entries, treeList } = e.data;
- const selectedTree = state.trees[`${projectId}/${branchId}`];
-
- commit(types.SET_ENTRIES, entries);
- commit(types.SET_DIRECTORY_DATA, {
- treePath: `${projectId}/${branchId}`,
- data: treeList,
- });
- commit(types.TOGGLE_LOADING, {
- entry: selectedTree,
- forceValue: false,
- });
-
- worker.terminate();
-
- resolve();
- });
-
- worker.postMessage({
+ const { entries, treeList } = decorateFiles({
data,
projectId,
branchId,
});
+
+ commit(types.SET_ENTRIES, entries);
+
+ // Defer setting the directory data because this triggers some intense rendering.
+ // The entries is all we need to load the file editor.
+ _.defer(() => dispatch('setDirectoryData', { projectId, branchId, treeList }));
+
+ resolve();
})
.catch(e => {
- if (e.response.status === 404) {
- dispatch('showBranchNotFoundError', branchId);
- } else {
- dispatch('setErrorMessage', {
- text: __('An error occurred whilst loading all the files.'),
- action: payload =>
- dispatch('getFiles', payload).then(() => dispatch('setErrorMessage', null)),
- actionText: __('Please try again'),
- actionPayload: { projectId, branchId },
- });
- }
+ dispatch('setErrorMessage', {
+ text: __('An error occurred whilst loading all the files.'),
+ action: payload =>
+ dispatch('getFiles', payload).then(() => dispatch('setErrorMessage', null)),
+ actionText: __('Please try again'),
+ actionPayload: { projectId, branchId },
+ });
reject(e);
});
} else {
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 8ad85074d6b..406903129db 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -25,7 +25,10 @@ export const projectsWithTrees = state =>
});
export const currentMergeRequest = state => {
- if (state.projects[state.currentProjectId]) {
+ if (
+ state.projects[state.currentProjectId] &&
+ state.projects[state.currentProjectId].mergeRequests
+ ) {
return state.projects[state.currentProjectId].mergeRequests[state.currentMergeRequestId];
}
return null;
@@ -33,12 +36,16 @@ export const currentMergeRequest = state => {
export const currentProject = state => state.projects[state.currentProjectId];
+export const emptyRepo = state =>
+ state.projects[state.currentProjectId] && state.projects[state.currentProjectId].empty_repo;
+
export const currentTree = state =>
state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
-export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length;
+export const hasChanges = state =>
+ Boolean(state.changedFiles.length) || Boolean(state.stagedFiles.length);
-export const hasMergeRequest = state => !!state.currentMergeRequestId;
+export const hasMergeRequest = state => Boolean(state.currentMergeRequestId);
export const allBlobs = state =>
Object.keys(state.entries)
@@ -64,7 +71,7 @@ export const isCommitModeActive = state => state.currentActivityView === activit
export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review;
export const someUncommittedChanges = state =>
- !!(state.changedFiles.length || state.stagedFiles.length);
+ Boolean(state.changedFiles.length || state.stagedFiles.length);
export const getChangesInFolder = state => path => {
const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f.path, path)).length;
@@ -90,7 +97,12 @@ export const lastCommit = (state, getters) => {
export const currentBranch = (state, getters) =>
getters.currentProject && getters.currentProject.branches[state.currentBranchId];
+export const branchName = (_state, getters) => getters.currentBranch && getters.currentBranch.name;
+
export const packageJson = state => state.entries[packageJsonPath];
+export const isOnDefaultBranch = (_state, getters) =>
+ getters.currentProject && getters.currentProject.default_branch === getters.branchName;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 24c2f71ae2b..51062f092ad 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -6,7 +6,7 @@ import { createCommitPayload, createNewMergeRequestUrl } from '../../utils';
import router from '../../../ide_router';
import service from '../../../services';
import * as types from './mutation_types';
-import * as consts from './constants';
+import consts from './constants';
import { activityBarViews } from '../../../constants';
import eventHub from '../../../eventhub';
@@ -18,16 +18,42 @@ export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
-export const updateCommitAction = ({ commit }, commitAction) => {
- commit(types.UPDATE_COMMIT_ACTION, commitAction);
+export const updateCommitAction = ({ commit, dispatch }, commitAction) => {
+ commit(types.UPDATE_COMMIT_ACTION, {
+ commitAction,
+ });
+ dispatch('setShouldCreateMR');
+};
+
+export const toggleShouldCreateMR = ({ commit }) => {
+ commit(types.TOGGLE_SHOULD_CREATE_MR);
+ commit(types.INTERACT_WITH_NEW_MR);
+};
+
+export const setShouldCreateMR = ({
+ commit,
+ getters,
+ rootGetters,
+ state: { interactedWithNewMR },
+}) => {
+ const committingToExistingMR =
+ getters.isCommittingToCurrentBranch &&
+ rootGetters.hasMergeRequest &&
+ !rootGetters.isOnDefaultBranch;
+
+ if ((getters.isCommittingToDefaultBranch && !interactedWithNewMR) || committingToExistingMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, false);
+ } else if (!interactedWithNewMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, true);
+ }
};
export const updateBranchName = ({ commit }, branchName) => {
commit(types.UPDATE_NEW_BRANCH_NAME, branchName);
};
-export const setLastCommitMessage = ({ rootState, commit }, data) => {
- const currentProject = rootState.projects[rootState.currentProjectId];
+export const setLastCommitMessage = ({ commit, rootGetters }, data) => {
+ const { currentProject } = rootGetters;
const commitStats = data.stats
? sprintf(__('with %{additions} additions, %{deletions} deletions.'), {
additions: data.stats.additions,
@@ -48,8 +74,8 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => {
commit(rootTypes.SET_LAST_COMMIT_MSG, commitMsg, { root: true });
};
-export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }) => {
- const selectedProject = rootState.projects[rootState.currentProjectId];
+export const updateFilesAfterCommit = ({ commit, dispatch, rootState, rootGetters }, { data }) => {
+ const selectedProject = rootGetters.currentProject;
const lastCommit = {
commit_path: `${selectedProject.web_url}/commit/${data.id}`,
commit: {
@@ -95,7 +121,7 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }
eventHub.$emit(`editor.update.model.content.${file.key}`, {
content: file.content,
- changed: !!changedFile,
+ changed: Boolean(changedFile),
});
});
};
@@ -128,6 +154,17 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
return null;
}
+ if (!data.parent_ids.length) {
+ commit(
+ rootTypes.TOGGLE_EMPTY_STATE,
+ {
+ projectPath: rootState.currentProjectId,
+ value: false,
+ },
+ { root: true },
+ );
+ }
+
dispatch('setLastCommitMessage', data);
dispatch('updateCommitMessage', '');
return dispatch('updateFilesAfterCommit', {
@@ -135,14 +172,15 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
branch: getters.branchName,
})
.then(() => {
- if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
+ if (state.shouldCreateMR) {
+ const { currentProject } = rootGetters;
+ const targetBranch = getters.isCreatingNewBranch
+ ? rootState.currentBranchId
+ : currentProject.default_branch;
+
dispatch(
'redirectToUrl',
- createNewMergeRequestUrl(
- rootState.projects[rootState.currentProjectId].web_url,
- getters.branchName,
- rootState.currentBranchId,
- ),
+ createNewMergeRequestUrl(currentProject.web_url, getters.branchName, targetBranch),
{ root: true },
);
}
diff --git a/app/assets/javascripts/ide/stores/modules/commit/constants.js b/app/assets/javascripts/ide/stores/modules/commit/constants.js
index 230b0a3d9b5..c6c3701effe 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/constants.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/constants.js
@@ -1,3 +1,7 @@
-export const COMMIT_TO_CURRENT_BRANCH = '1';
-export const COMMIT_TO_NEW_BRANCH = '2';
-export const COMMIT_TO_NEW_BRANCH_MR = '3';
+const COMMIT_TO_CURRENT_BRANCH = '1';
+const COMMIT_TO_NEW_BRANCH = '2';
+
+export default {
+ COMMIT_TO_CURRENT_BRANCH,
+ COMMIT_TO_NEW_BRANCH,
+};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 03777e6c10b..64779e9e4df 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -1,5 +1,5 @@
import { sprintf, n__, __ } from '../../../../locale';
-import * as consts from './constants';
+import consts from './constants';
const BRANCH_SUFFIX_COUNT = 5;
const createTranslatedTextForFiles = (files, text) => {
@@ -14,18 +14,15 @@ const createTranslatedTextForFiles = (files, text) => {
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
-export const newBranchName = (state, _, rootState) =>
+export const placeholderBranchName = (state, _, rootState) =>
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
-BRANCH_SUFFIX_COUNT,
)}`;
export const branchName = (state, getters, rootState) => {
- if (
- state.commitAction === consts.COMMIT_TO_NEW_BRANCH ||
- state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR
- ) {
+ if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
if (state.newBranchName === '') {
- return getters.newBranchName;
+ return getters.placeholderBranchName;
}
return state.newBranchName;
@@ -49,5 +46,13 @@ export const preBuiltCommitMessage = (state, _, rootState) => {
.join('\n');
};
+export const isCreatingNewBranch = state => state.commitAction === consts.COMMIT_TO_NEW_BRANCH;
+
+export const isCommittingToCurrentBranch = state =>
+ state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
+
+export const isCommittingToDefaultBranch = (_state, getters, _rootState, rootGetters) =>
+ getters.isCommittingToCurrentBranch && rootGetters.isOnDefaultBranch;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
index 9221f054e9f..b81918156b0 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
@@ -2,3 +2,5 @@ export const UPDATE_COMMIT_MESSAGE = 'UPDATE_COMMIT_MESSAGE';
export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
export const UPDATE_LOADING = 'UPDATE_LOADING';
+export const TOGGLE_SHOULD_CREATE_MR = 'TOGGLE_SHOULD_CREATE_MR';
+export const INTERACT_WITH_NEW_MR = 'INTERACT_WITH_NEW_MR';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
index 797357e3df9..14957d283bb 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -6,10 +6,8 @@ export default {
commitMessage,
});
},
- [types.UPDATE_COMMIT_ACTION](state, commitAction) {
- Object.assign(state, {
- commitAction,
- });
+ [types.UPDATE_COMMIT_ACTION](state, { commitAction }) {
+ Object.assign(state, { commitAction });
},
[types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
Object.assign(state, {
@@ -21,4 +19,12 @@ export default {
submitCommitLoading,
});
},
+ [types.TOGGLE_SHOULD_CREATE_MR](state, shouldCreateMR) {
+ Object.assign(state, {
+ shouldCreateMR: shouldCreateMR === undefined ? !state.shouldCreateMR : shouldCreateMR,
+ });
+ },
+ [types.INTERACT_WITH_NEW_MR](state) {
+ Object.assign(state, { interactedWithNewMR: true });
+ },
};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/state.js b/app/assets/javascripts/ide/stores/modules/commit/state.js
index 8dae50961b0..53647a7e3e3 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/state.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/state.js
@@ -3,4 +3,6 @@ export default () => ({
commitAction: '1',
newBranchName: '',
submitCommitLoading: false,
+ shouldCreateMR: false,
+ interactedWithNewMR: false,
});
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
index b7090e09daf..59ead8a3dcf 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
@@ -23,22 +23,27 @@ export const receiveTemplateTypesError = ({ commit, dispatch }) => {
export const receiveTemplateTypesSuccess = ({ commit }, templates) =>
commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates);
-export const fetchTemplateTypes = ({ dispatch, state, rootState }, page = 1) => {
+export const fetchTemplateTypes = ({ dispatch, state, rootState }) => {
if (!Object.keys(state.selectedTemplateType).length) return Promise.reject();
dispatch('requestTemplateTypes');
- return Api.projectTemplates(rootState.currentProjectId, state.selectedTemplateType.key, { page })
- .then(({ data, headers }) => {
- const nextPage = parseInt(normalizeHeaders(headers)['X-NEXT-PAGE'], 10);
+ const fetchPages = (page = 1, prev = []) =>
+ Api.projectTemplates(rootState.currentProjectId, state.selectedTemplateType.key, {
+ page,
+ per_page: 100,
+ })
+ .then(({ data, headers }) => {
+ const nextPage = parseInt(normalizeHeaders(headers)['X-NEXT-PAGE'], 10);
+ const nextData = prev.concat(data);
- dispatch('receiveTemplateTypesSuccess', data);
+ dispatch('receiveTemplateTypesSuccess', nextData);
- if (nextPage) {
- dispatch('fetchTemplateTypes', nextPage);
- }
- })
- .catch(() => dispatch('receiveTemplateTypesError'));
+ return nextPage ? fetchPages(nextPage, nextData) : nextData;
+ })
+ .catch(() => dispatch('receiveTemplateTypesError'));
+
+ return fetchPages();
};
export const setSelectedTemplateType = ({ commit, dispatch, rootGetters }, type) => {
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
index 628babe6a01..f10891a8e5b 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
@@ -1,4 +1,5 @@
import { activityBarViews } from '../../../constants';
+import { __ } from '~/locale';
export const templateTypes = () => [
{
@@ -10,11 +11,11 @@ export const templateTypes = () => [
key: 'gitignores',
},
{
- name: 'LICENSE',
+ name: __('LICENSE'),
key: 'licenses',
},
{
- name: 'Dockerfile',
+ name: __('Dockerfile'),
key: 'dockerfiles',
},
];
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
index 25a65b047f1..7fc1c9134a7 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -3,13 +3,14 @@ import * as types from './mutation_types';
export default {
[types.REQUEST_TEMPLATE_TYPES](state) {
state.isLoading = true;
+ state.templates = [];
},
[types.RECEIVE_TEMPLATE_TYPES_ERROR](state) {
state.isLoading = false;
},
[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, templates) {
state.isLoading = false;
- state.templates = state.templates.concat(templates);
+ state.templates = templates;
},
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
state.selectedTemplateType = type;
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
index ef7cd4ff8e8..1d127d915d7 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
@@ -1,6 +1,6 @@
import { states } from './constants';
-export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline;
+export const hasLatestPipeline = state => !state.isLoadingPipeline && Boolean(state.latestPipeline);
export const pipelineFailed = state =>
state.latestPipeline && state.latestPipeline.details.status.text === states.failed;
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
index b4be100cb07..eaaa82cb339 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
@@ -10,6 +10,7 @@ export default {
},
[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](state, pipeline) {
state.isLoadingPipeline = false;
+ state.hasLoadedPipeline = true;
if (pipeline) {
state.latestPipeline = {
@@ -34,7 +35,7 @@ export default {
};
});
} else {
- state.latestPipeline = false;
+ state.latestPipeline = null;
}
},
[types.REQUEST_JOBS](state, id) {
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/state.js b/app/assets/javascripts/ide/stores/modules/pipelines/state.js
index 8651e267b53..8dfa0ec491f 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/state.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/state.js
@@ -1,5 +1,6 @@
export default () => ({
isLoadingPipeline: true,
+ hasLoadedPipeline: false,
isLoadingJobs: false,
latestPipeline: null,
stages: [],
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index a5f8098dc17..86ab76136df 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -12,6 +12,7 @@ export const SET_LINKS = 'SET_LINKS';
export const SET_PROJECT = 'SET_PROJECT';
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
+export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
// Merge Request Mutation Types
export const SET_MERGE_REQUEST = 'SET_MERGE_REQUEST';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 78cdfda74f0..ae42b87c9a7 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -142,7 +142,7 @@ export default {
Object.assign(state.entries[file.path], {
raw: file.content,
- changed: !!changedFile,
+ changed: Boolean(changedFile),
staged: false,
prevPath: '',
moved: false,
@@ -206,19 +206,17 @@ export default {
}
}
},
- [types.RENAME_ENTRY](state, { path, name, entryPath = null }) {
+ [types.RENAME_ENTRY](state, { path, name, entryPath = null, parentPath }) {
const oldEntry = state.entries[entryPath || path];
- const nameRegex =
- !entryPath && oldEntry.type === 'blob'
- ? new RegExp(`${oldEntry.name}$`)
- : new RegExp(`^${path}`);
- const newPath = oldEntry.path.replace(nameRegex, name);
- const parentPath = oldEntry.parentPath ? oldEntry.parentPath.replace(nameRegex, name) : '';
+ const slashedParentPath = parentPath ? `${parentPath}/` : '';
+ const newPath = entryPath
+ ? `${slashedParentPath}${oldEntry.name}`
+ : `${slashedParentPath}${name}`;
- state.entries[newPath] = {
+ Vue.set(state.entries, newPath, {
...oldEntry,
id: newPath,
- key: `${name}-${oldEntry.type}-${oldEntry.id}`,
+ key: `${newPath}-${oldEntry.type}-${oldEntry.id}`,
path: newPath,
name: entryPath ? oldEntry.name : name,
tempFile: true,
@@ -227,7 +225,8 @@ export default {
tree: [],
parentPath,
raw: '',
- };
+ });
+
oldEntry.moved = true;
oldEntry.movedPath = newPath;
@@ -256,6 +255,7 @@ export default {
Vue.delete(state.entries, oldEntry.path);
}
},
+
...projectMutations,
...mergeRequestMutation,
...fileMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
index e09f88878f4..6afd8de2aa4 100644
--- a/app/assets/javascripts/ide/stores/mutations/branch.js
+++ b/app/assets/javascripts/ide/stores/mutations/branch.js
@@ -19,6 +19,12 @@ export default {
});
},
[types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
+ if (!state.projects[projectId].branches[branchId]) {
+ Object.assign(state.projects[projectId].branches, {
+ [branchId]: {},
+ });
+ }
+
Object.assign(state.projects[projectId].branches[branchId], {
workingReference: reference,
});
diff --git a/app/assets/javascripts/ide/stores/mutations/merge_request.js b/app/assets/javascripts/ide/stores/mutations/merge_request.js
index 334819fe702..e5b5107bc93 100644
--- a/app/assets/javascripts/ide/stores/mutations/merge_request.js
+++ b/app/assets/javascripts/ide/stores/mutations/merge_request.js
@@ -7,6 +7,8 @@ export default {
});
},
[types.SET_MERGE_REQUEST](state, { projectPath, mergeRequestId, mergeRequest }) {
+ const existingMergeRequest = state.projects[projectPath].mergeRequests[mergeRequestId] || {};
+
Object.assign(state.projects[projectPath], {
mergeRequests: {
[mergeRequestId]: {
@@ -15,6 +17,7 @@ export default {
changes: [],
versions: [],
baseCommitSha: null,
+ ...existingMergeRequest,
},
},
});
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
index 284b39a2c72..9230f3839c1 100644
--- a/app/assets/javascripts/ide/stores/mutations/project.js
+++ b/app/assets/javascripts/ide/stores/mutations/project.js
@@ -21,4 +21,9 @@ export default {
}),
});
},
+ [types.TOGGLE_EMPTY_STATE](state, { projectPath, value }) {
+ Object.assign(state.projects[projectPath], {
+ empty_repo: value,
+ });
+ },
};
diff --git a/app/assets/javascripts/ide/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
index eac7441ee54..359943b4ab7 100644
--- a/app/assets/javascripts/ide/stores/mutations/tree.js
+++ b/app/assets/javascripts/ide/stores/mutations/tree.js
@@ -1,5 +1,5 @@
import * as types from '../mutation_types';
-import { sortTree } from '../utils';
+import { sortTree, mergeTrees } from '../utils';
export default {
[types.TOGGLE_TREE_OPEN](state, path) {
@@ -23,9 +23,15 @@ export default {
});
},
[types.SET_DIRECTORY_DATA](state, { data, treePath }) {
- Object.assign(state.trees[treePath], {
- tree: data,
- });
+ const selectedTree = state.trees[treePath];
+
+ // If we opened files while loading the tree, we need to merge them
+ // Otherwise, simply overwrite the tree
+ const tree = !selectedTree.tree.length
+ ? data
+ : selectedTree.loading && mergeTrees(selectedTree.tree, data);
+
+ Object.assign(selectedTree, { tree });
},
[types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
Object.assign(tree, {
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 0ede76fd1e0..bcc9ca60d9b 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,3 +1,5 @@
+import { commitActionTypes } from '../constants';
+
export const dataStructure = () => ({
id: '',
// Key will contain a mixture of ID and path
@@ -69,14 +71,15 @@ export const decorateData = entity => {
changed = false,
parentTreeUrl = '',
base64 = false,
+ binary = false,
+ rawPath = '',
previewMode,
file_lock,
html,
parentPath = '',
} = entity;
- return {
- ...dataStructure(),
+ return Object.assign(dataStructure(), {
id,
projectId,
branchId,
@@ -93,11 +96,13 @@ export const decorateData = entity => {
renderError,
content,
base64,
+ binary,
+ rawPath,
previewMode,
file_lock,
html,
parentPath,
- };
+ });
};
export const findEntry = (tree, type, name, prop = 'name') =>
@@ -111,14 +116,14 @@ export const setPageTitle = title => {
export const commitActionForFile = file => {
if (file.prevPath) {
- return 'move';
+ return commitActionTypes.move;
} else if (file.deleted) {
- return 'delete';
+ return commitActionTypes.delete;
} else if (file.tempFile) {
- return 'create';
+ return commitActionTypes.create;
}
- return 'update';
+ return commitActionTypes.update;
};
export const getCommitFiles = stagedFiles =>
@@ -171,3 +176,31 @@ export const filePathMatches = (filePath, path) => filePath.indexOf(`${path}/`)
export const getChangesCountForFiles = (files, path) =>
files.filter(f => filePathMatches(f.path, path)).length;
+
+export const mergeTrees = (fromTree, toTree) => {
+ if (!fromTree || !fromTree.length) {
+ return toTree;
+ }
+
+ const recurseTree = (n, t) => {
+ if (!n) {
+ return t;
+ }
+ const existingTreeNode = t.find(el => el.path === n.path);
+
+ if (existingTreeNode && n.tree.length > 0) {
+ existingTreeNode.opened = true;
+ recurseTree(n.tree[0], existingTreeNode.tree);
+ } else if (!existingTreeNode) {
+ const sorted = sortTree(t.concat(n));
+ t.splice(0, t.length + 1, ...sorted);
+ }
+ return t;
+ };
+
+ for (let i = 0, l = fromTree.length; i < l; i += 1) {
+ recurseTree(fromTree[i], toTree);
+ }
+
+ return toTree;
+};
diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
deleted file mode 100644
index fa35c215880..00000000000
--- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
-import { decorateData, sortTree } from '../utils';
-
-// eslint-disable-next-line no-restricted-globals
-self.addEventListener('message', e => {
- const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
-
- const treeList = [];
- let file;
- let parentPath;
- const entries = data.reduce((acc, path) => {
- const pathSplit = path.split('/');
- const blobName = pathSplit.pop().trim();
-
- if (pathSplit.length > 0) {
- pathSplit.reduce((pathAcc, folderName) => {
- const parentFolder = acc[pathAcc[pathAcc.length - 1]];
- const folderPath = `${parentFolder ? `${parentFolder.path}/` : ''}${folderName}`;
- const foundEntry = acc[folderPath];
-
- if (!foundEntry) {
- parentPath = parentFolder ? parentFolder.path : null;
-
- const tree = decorateData({
- projectId,
- branchId,
- id: folderPath,
- name: folderName,
- path: folderPath,
- url: `/${projectId}/tree/${branchId}/-/${folderPath}/`,
- type: 'tree',
- parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
- tempFile,
- changed: tempFile,
- opened: tempFile,
- parentPath,
- });
-
- Object.assign(acc, {
- [folderPath]: tree,
- });
-
- if (parentFolder) {
- parentFolder.tree.push(tree);
- } else {
- treeList.push(tree);
- }
-
- pathAcc.push(tree.path);
- } else {
- pathAcc.push(foundEntry.path);
- }
-
- return pathAcc;
- }, []);
- }
-
- if (blobName !== '') {
- const fileFolder = acc[pathSplit.join('/')];
- parentPath = fileFolder ? fileFolder.path : null;
-
- file = decorateData({
- projectId,
- branchId,
- id: path,
- name: blobName,
- path,
- url: `/${projectId}/blob/${branchId}/-/${path}`,
- type: 'blob',
- parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
- tempFile,
- changed: tempFile,
- content,
- base64,
- previewMode: viewerInformationForPath(blobName),
- parentPath,
- });
-
- Object.assign(acc, {
- [path]: file,
- });
-
- if (fileFolder) {
- fileFolder.tree.push(file);
- } else {
- treeList.push(file);
- }
- }
-
- return acc;
- }, {});
-
- // eslint-disable-next-line no-restricted-globals
- self.postMessage({
- entries,
- treeList: sortTree(treeList),
- file,
- parentPath,
- });
-});
diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
index 05000c73052..7051a968dac 100644
--- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
@@ -14,7 +14,7 @@ export function addCommentIndicator(containerEl, { x, y }) {
export function removeCommentIndicator(imageFrameEl) {
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
const imageEl = imageFrameEl.querySelector('img');
- const willRemove = !!commentIndicatorEl;
+ const willRemove = Boolean(commentIndicatorEl);
let meta = {};
if (willRemove) {
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index 3587f073a00..26c1b0ec7be 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -6,8 +6,8 @@ import { isImageLoaded } from '../lib/utils/image_utility';
export default class ImageDiff {
constructor(el, options) {
this.el = el;
- this.canCreateNote = !!(options && options.canCreateNote);
- this.renderCommentBadge = !!(options && options.renderCommentBadge);
+ this.canCreateNote = Boolean(options && options.canCreateNote);
+ this.renderCommentBadge = Boolean(options && options.renderCommentBadge);
this.$noteContainer = $('.note-container', this.el);
this.imageBadges = [];
}
diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js
index ab0a595571f..1a5123de220 100644
--- a/app/assets/javascripts/image_diff/view_types.js
+++ b/app/assets/javascripts/image_diff/view_types.js
@@ -5,5 +5,5 @@ export const viewTypes = {
};
export function isValidViewType(validate) {
- return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
+ return Boolean(Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate));
}
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue
index 777f8fa6691..00eb0afb3bf 100644
--- a/app/assets/javascripts/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue
@@ -74,7 +74,7 @@ export default {
<gl-loading-icon
v-if="isLoadingRepos"
class="js-loading-button-icon import-projects-loading-icon"
- :size="4"
+ size="md"
/>
<div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive">
<table class="table import-table">
diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
index 7cc29fa1b91..3c6c9c71b8c 100644
--- a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
@@ -41,7 +41,7 @@ export default {
return {
data: this.namespaceSelectOptions,
containerCssClass:
- 'import-namespace-select js-namespace-select qa-project-namespace-select',
+ 'import-namespace-select js-namespace-select qa-project-namespace-select w-auto',
};
},
diff --git a/app/assets/javascripts/import_projects/index.js b/app/assets/javascripts/import_projects/index.js
index 5c77484aee1..2d99d716609 100644
--- a/app/assets/javascripts/import_projects/index.js
+++ b/app/assets/javascripts/import_projects/index.js
@@ -3,7 +3,7 @@ import { mapActions } from 'vuex';
import Translate from '../vue_shared/translate';
import ImportProjectsTable from './components/import_projects_table.vue';
import { parseBoolean } from '../lib/utils/common_utils';
-import store from './store';
+import createStore from './store';
Vue.use(Translate);
@@ -20,6 +20,7 @@ export default function mountImportProjectsTable(mountElement) {
ciCdOnly,
} = mountElement.dataset;
+ const store = createStore();
return new Vue({
el: mountElement,
store,
diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js
index f03474a8404..727b80765bd 100644
--- a/app/assets/javascripts/import_projects/store/getters.js
+++ b/app/assets/javascripts/import_projects/store/getters.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export const namespaceSelectOptions = state => {
const serializedNamespaces = state.namespaces.map(({ fullPath }) => ({
id: fullPath,
@@ -5,9 +7,9 @@ export const namespaceSelectOptions = state => {
}));
return [
- { text: 'Groups', children: serializedNamespaces },
+ { text: __('Groups'), children: serializedNamespaces },
{
- text: 'Users',
+ text: __('Users'),
children: [{ id: state.defaultTargetNamespace, text: state.defaultTargetNamespace }],
},
];
diff --git a/app/assets/javascripts/import_projects/store/index.js b/app/assets/javascripts/import_projects/store/index.js
index 6ac9bfd8189..ff1fd1e598e 100644
--- a/app/assets/javascripts/import_projects/store/index.js
+++ b/app/assets/javascripts/import_projects/store/index.js
@@ -7,9 +7,12 @@ import mutations from './mutations';
Vue.use(Vuex);
-export default new Vuex.Store({
- state: state(),
- actions,
- mutations,
- getters,
-});
+export { state, actions, getters, mutations };
+
+export default () =>
+ new Vuex.Store({
+ state: state(),
+ actions,
+ mutations,
+ getters,
+ });
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 08b858305ab..a7746bb3a0b 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
+import { __ } from '~/locale';
export default class IntegrationSettingsForm {
constructor(formSelector) {
@@ -65,10 +66,10 @@ export default class IntegrationSettingsForm {
* Toggle Submit button label based on Integration status and ability to test service
*/
toggleSubmitBtnLabel(serviceActive) {
- let btnLabel = 'Save changes';
+ let btnLabel = __('Save changes');
if (serviceActive && this.canTestService) {
- btnLabel = 'Test settings and save changes';
+ btnLabel = __('Test settings and save changes');
}
this.$submitBtnLabel.text(btnLabel);
@@ -105,7 +106,7 @@ export default class IntegrationSettingsForm {
if (data.test_failed) {
flashActions = {
- title: 'Save anyway',
+ title: __('Save anyway'),
clickHandler: e => {
e.preventDefault();
this.$form.submit();
@@ -121,7 +122,7 @@ export default class IntegrationSettingsForm {
this.toggleSubmitBtnState(false);
})
.catch(() => {
- flash('Something went wrong on our end.');
+ flash(__('Something went wrong on our end.'));
this.toggleSubmitBtnState(false);
});
}
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index b844e4c5e5b..bc9d7fcf30d 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -4,6 +4,7 @@ import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
+import { __ } from './locale';
export default {
init({ container, form, issues, prefixId } = {}) {
@@ -32,7 +33,7 @@ export default {
onFormSubmitFailure() {
this.form.find('[type="submit"]').enable();
- return new Flash('Issue update failed');
+ return new Flash(__('Issue update failed'));
},
getSelectedIssues() {
@@ -81,9 +82,6 @@ export default {
const formData = {
update: {
state_event: this.form.find('input[name="update[state_event]"]').val(),
- // For Merge Requests
- assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
- // For Issues
assignee_ids: [this.form.find('input[name="update[assignee_ids][]"]').val()],
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 9336b71cfd7..7576d36f27d 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import Pikaday from 'pikaday';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import Autosave from './autosave';
import UsersSelect from './users_select';
-import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index ffcbd7cf28c..16f88cddce3 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
-import { __ } from './locale';
+import { s__, __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
@@ -12,7 +12,7 @@ export default class IssuableIndex {
}
initBulkUpdate(pagePrefix) {
const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
- const alreadyInitialized = !!this.bulkUpdateSidebar;
+ const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (userCanBulkUpdate && !alreadyInitialized) {
IssuableBulkUpdateActions.init({
@@ -29,7 +29,7 @@ export default class IssuableIndex {
$resetToken.on('click', e => {
e.preventDefault();
- $resetToken.text('resetting...');
+ $resetToken.text(s__('EmailToken|resetting...'));
axios
.put($resetToken.attr('href'))
@@ -38,12 +38,12 @@ export default class IssuableIndex {
.val(data.new_address)
.focus();
- $resetToken.text('reset it');
+ $resetToken.text(s__('EmailToken|reset it'));
})
.catch(() => {
flash(__('There was an error when reseting email token.'));
- $resetToken.text('reset it');
+ $resetToken.text(s__('EmailToken|reset it'));
});
});
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 94b78907d9a..db4607ca58d 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -7,6 +7,7 @@ import flash from './flash';
import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
+import { __ } from './locale';
export default class Issue {
constructor() {
@@ -15,8 +16,9 @@ export default class Issue {
Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
- Issue.initMergeRequests();
- Issue.initRelatedBranches();
+ if (document.querySelector('#related-branches')) {
+ Issue.initRelatedBranches();
+ }
this.closeButtons = $('a.btn-close');
this.reopenButtons = $('a.btn-reopen');
@@ -43,7 +45,11 @@ export default class Issue {
* @param {Array} data
* @param {String} issueFailMessage
*/
- updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') {
+ updateTopState(
+ isClosed,
+ data,
+ issueFailMessage = __('Unable to update this issue at this time.'),
+ ) {
if ('id' in data) {
const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
@@ -80,7 +86,7 @@ export default class Issue {
}
initIssueBtnEventListeners() {
- const issueFailMessage = 'Unable to update this issue at this time.';
+ const issueFailMessage = __('Unable to update this issue at this time.');
return $(document).on(
'click',
@@ -141,19 +147,6 @@ export default class Issue {
}
}
- static initMergeRequests() {
- var $container;
- $container = $('#merge-requests');
- return axios
- .get($container.data('url'))
- .then(({ data }) => {
- if ('html' in data) {
- $container.html(data.html);
- }
- })
- .catch(() => flash('Failed to load referenced merge requests'));
- }
-
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
@@ -164,6 +157,6 @@ export default class Issue {
$container.html(data.html);
}
})
- .catch(() => flash('Failed to load related branches'));
+ .catch(() => flash(__('Failed to load related branches')));
}
}
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index bd757a76ee7..e88ca4747c5 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -156,12 +156,26 @@ export default {
return this.store.formState;
},
hasUpdated() {
- return !!this.state.updatedAt;
+ return Boolean(this.state.updatedAt);
},
issueChanged() {
- const descriptionChanged = this.initialDescriptionText !== this.store.formState.description;
- const titleChanged = this.initialTitleText !== this.store.formState.title;
- return descriptionChanged || titleChanged;
+ const {
+ store: {
+ formState: { description, title },
+ },
+ initialDescriptionText,
+ initialTitleText,
+ } = this;
+
+ if (initialDescriptionText || description) {
+ return initialDescriptionText !== description;
+ }
+
+ if (initialTitleText || title) {
+ return initialTitleText !== title;
+ }
+
+ return false;
},
defaultErrorMessage() {
return sprintf(s__('Error updating %{issuableType}'), { issuableType: this.issuableType });
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 58f14bac8c8..f2462e50093 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -140,14 +140,16 @@ export default {
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation,
}"
- class="wiki"
+ class="md"
v-html="descriptionHtml"
></div>
<textarea
v-if="descriptionText"
+ ref="textarea"
v-model="descriptionText"
:data-update-url="updateUrl"
class="hidden js-task-list-field"
+ dir="auto"
>
</textarea>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 299130e56ae..d27dd873125 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -53,6 +53,7 @@ export default {
v-model="formState.description"
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
+ dir="auto"
data-supports-quick-actions="false"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
index c3d7ba4907f..ce4baf17d09 100644
--- a/app/assets/javascripts/issue_show/components/fields/title.vue
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -17,8 +17,10 @@ export default {
<label class="sr-only" for="issuable-title"> Title </label>
<input
id="issuable-title"
+ ref="input"
v-model="formState.title"
class="form-control qa-title-input"
+ dir="auto"
type="text"
placeholder="Title"
aria-label="Title"
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index eade31f1d14..528ccb77efc 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -1,9 +1,12 @@
<script>
+import $ from 'jquery';
import lockedWarning from './locked_warning.vue';
import titleField from './fields/title.vue';
import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue';
+import Autosave from '~/autosave';
+import eventHub from '../event_hub';
export default {
components: {
@@ -68,6 +71,47 @@ export default {
return this.issuableTemplates.length;
},
},
+ created() {
+ eventHub.$on('delete.issuable', this.resetAutosave);
+ eventHub.$on('update.issuable', this.resetAutosave);
+ eventHub.$on('close.form', this.resetAutosave);
+ },
+ mounted() {
+ this.initAutosave();
+ },
+ beforeDestroy() {
+ eventHub.$off('delete.issuable', this.resetAutosave);
+ eventHub.$off('update.issuable', this.resetAutosave);
+ eventHub.$off('close.form', this.resetAutosave);
+ },
+ methods: {
+ initAutosave() {
+ const {
+ description: {
+ $refs: { textarea },
+ },
+ title: {
+ $refs: { input },
+ },
+ } = this.$refs;
+
+ this.autosaveDescription = new Autosave($(textarea), [
+ document.location.pathname,
+ document.location.search,
+ 'description',
+ ]);
+
+ this.autosaveTitle = new Autosave($(input), [
+ document.location.pathname,
+ document.location.search,
+ 'title',
+ ]);
+ },
+ resetAutosave() {
+ this.autosaveDescription.reset();
+ this.autosaveTitle.reset();
+ },
+ },
};
</script>
@@ -89,10 +133,11 @@ export default {
'col-12': !hasIssuableTemplates,
}"
>
- <title-field :form-state="formState" :issuable-templates="issuableTemplates" />
+ <title-field ref="title" :form-state="formState" :issuable-templates="issuableTemplates" />
</div>
</div>
<description-field
+ ref="description"
:form-state="formState"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index 3b5c95ccded..1e1dce5f4fc 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -71,7 +71,8 @@ export default {
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation,
}"
- class="title"
+ class="title qa-title"
+ dir="auto"
v-html="titleHtml"
></h2>
<button
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index d08e8ba0c4b..529b6386221 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,12 +1,9 @@
import Vue from 'vue';
-import sanitize from 'sanitize-html';
import issuableApp from './components/app.vue';
+import { parseIssuableData } from './utils/parse_data';
import '../vue_shared/vue_resource_interceptor';
export default function initIssueableApp() {
- const initialDataEl = document.getElementById('js-issuable-app-initial-data');
- const props = JSON.parse(sanitize(initialDataEl.textContent).replace(/&quot;/g, '"'));
-
return new Vue({
el: document.getElementById('js-issuable-app'),
components: {
@@ -14,7 +11,7 @@ export default function initIssueableApp() {
},
render(createElement) {
return createElement('issuable-app', {
- props,
+ props: parseIssuableData(),
});
},
});
diff --git a/app/assets/javascripts/issue_show/utils/parse_data.js b/app/assets/javascripts/issue_show/utils/parse_data.js
new file mode 100644
index 00000000000..05e384adad3
--- /dev/null
+++ b/app/assets/javascripts/issue_show/utils/parse_data.js
@@ -0,0 +1,15 @@
+import sanitize from 'sanitize-html';
+
+export const parseIssuableData = () => {
+ try {
+ const initialDataEl = document.getElementById('js-issuable-app-initial-data');
+
+ return JSON.parse(sanitize(initialDataEl.textContent).replace(/&quot;/g, '"'));
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+
+ return {};
+ }
+};
+
+export default {};
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index c14803c80e7..75edff41a89 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { __ } from './locale';
export default function issueStatusSelect() {
$('.js-issue-status').each((i, el) => {
@@ -7,7 +8,7 @@ export default function issueStatusSelect() {
selectable: true,
fieldName,
toggleLabel(selected, element, instance) {
- let label = 'Author';
+ let label = __('Author');
const $item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 7076a79dd5d..b651a6e4bfb 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -39,7 +39,7 @@ export default {
</gl-link>
<clipboard-button
- :text="commit.short_id"
+ :text="commit.id"
:title="__('Copy commit SHA to clipboard')"
css-class="btn btn-clipboard btn-transparent"
/>
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index 668fcf3d673..04f910b6b80 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -49,7 +49,7 @@ export default {
<div class="text-content">
<h4 class="js-job-empty-state-title text-center">{{ title }}</h4>
- <p v-if="content" class="js-job-empty-state-content">{{ content }}</p>
+ <p v-if="content" class="js-job-empty-state-content text-center">{{ content }}</p>
<div v-if="action" class="text-center">
<gl-link
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index d473d6a482d..79fb67d38cd 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -15,6 +15,7 @@ import ErasedBlock from './erased_block.vue';
import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
+import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
@@ -32,8 +33,10 @@ export default {
Log,
LogTopBar,
StuckBlock,
+ UnmetPrerequisitesBlock,
Sidebar,
GlLoadingIcon,
+ SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'),
},
mixins: [delayedJobMixin],
props: {
@@ -47,6 +50,11 @@ export default {
required: false,
default: null,
},
+ deploymentHelpUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
endpoint: {
type: String,
required: true,
@@ -78,12 +86,15 @@ export default {
'isScrollTopDisabled',
'isScrolledToBottomBeforeReceivingTrace',
'hasError',
+ 'selectedStage',
]),
...mapGetters([
'headerTime',
+ 'hasUnmetPrerequisitesFailure',
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
'hasEnvironment',
+ 'shouldRenderSharedRunnerLimitWarning',
'hasTrace',
'emptyStateIllustration',
'isScrollingDown',
@@ -111,7 +122,13 @@ export default {
// fetch the stages for the dropdown on the sidebar
job(newVal, oldVal) {
if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
- this.fetchStages();
+ const stages = this.job.pipeline.details.stages || [];
+
+ const defaultStage = stages.find(stage => stage && stage.name === this.selectedStage);
+
+ if (defaultStage) {
+ this.fetchJobsForStage(defaultStage);
+ }
}
if (newVal.archived) {
@@ -150,7 +167,7 @@ export default {
'setJobEndpoint',
'setTraceOptions',
'fetchJob',
- 'fetchStages',
+ 'fetchJobsForStage',
'hideSidebar',
'showSidebar',
'toggleSidebar',
@@ -208,7 +225,10 @@ export default {
/>
</div>
- <callout v-if="shouldRenderCalloutMessage" :message="job.callout_message" />
+ <callout
+ v-if="shouldRenderCalloutMessage && !hasUnmetPrerequisitesFailure"
+ :message="job.callout_message"
+ />
</header>
<!-- EO Header Section -->
@@ -221,6 +241,20 @@ export default {
:runners-path="runnerSettingsUrl"
/>
+ <unmet-prerequisites-block
+ v-if="hasUnmetPrerequisitesFailure"
+ class="js-job-failed"
+ :help-path="deploymentHelpUrl"
+ />
+
+ <shared-runner
+ v-if="shouldRenderSharedRunnerLimitWarning"
+ class="js-shared-runner-limit"
+ :quota-used="job.runners.quota.used"
+ :quota-limit="job.runners.quota.limit"
+ :runners-path="runnerHelpUrl"
+ />
+
<environments-block
v-if="hasEnvironment"
class="js-job-environment"
@@ -242,13 +276,12 @@ export default {
:class="{ 'sticky-top border-bottom-0': hasTrace }"
>
<icon name="lock" class="align-text-bottom" />
-
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!-- job log -->
<div
v-if="hasTrace"
- class="build-trace-container"
+ class="build-trace-container position-relative"
:class="{ 'prepend-top-default': !job.archived }"
>
<log-top-bar
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
index 845699a90b5..a55dffbe488 100644
--- a/app/assets/javascripts/jobs/components/job_container_item.vue
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="build-job"
+ class="build-job position-relative"
:class="{
retried: job.retried,
active: isActive,
@@ -56,7 +56,11 @@ export default {
data-boundary="viewport"
class="js-job-link"
>
- <icon v-if="isActive" name="arrow-right" class="js-arrow-right icon-arrow-right" />
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right position-absolute d-block"
+ />
<ci-icon :status="job.status" />
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 52e14f954ee..607b2bd1c74 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -75,7 +75,11 @@ export default {
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
- <gl-link v-if="rawPath" :href="rawPath" class="js-raw-link raw-link">
+ <gl-link
+ v-if="rawPath"
+ :href="rawPath"
+ class="js-raw-link text-plain text-underline prepend-left-5"
+ >
{{ s__('Job|Complete Raw') }}
</gl-link>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 1691ac62100..24276c06486 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -34,7 +34,7 @@ export default {
},
},
computed: {
- ...mapState(['job', 'stages', 'jobs', 'selectedStage', 'isLoadingStages']),
+ ...mapState(['job', 'stages', 'jobs', 'selectedStage']),
coverage() {
return `${this.job.coverage}%`;
},
@@ -208,7 +208,6 @@ export default {
/>
<stages-dropdown
- v-if="!isLoadingStages"
:stages="stages"
:pipeline="job.pipeline"
:selected-stage="selectedStage"
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index c5076d65ff9..cb073a9b04d 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,12 +1,16 @@
<script>
import _ from 'underscore';
+import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
CiIcon,
Icon,
+ GlLink,
+ PipelineLink,
},
props: {
pipeline: {
@@ -26,6 +30,12 @@ export default {
hasRef() {
return !_.isEmpty(this.pipeline.ref);
},
+ isTriggeredByMergeRequest() {
+ return Boolean(this.pipeline.merge_request);
+ },
+ isMergeRequestPipeline() {
+ return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
+ },
},
methods: {
onStageClick(stage) {
@@ -36,16 +46,44 @@ export default {
</script>
<template>
<div class="block-last dropdown">
- <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
+ <div class="js-pipeline-info">
+ <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
- <span class="font-weight-bold">{{ __('Pipeline') }}</span>
- <a :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
- >#{{ pipeline.id }}</a
- >
- <template v-if="hasRef">
- {{ __('from') }}
- <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
- </template>
+ <span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span>
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="js-pipeline-path link-commit qa-pipeline-path"
+ />
+ <template v-if="hasRef">
+ {{ s__('Job|for') }}
+
+ <template v-if="isTriggeredByMergeRequest">
+ <gl-link :href="pipeline.merge_request.path" class="link-commit ref-name js-mr-link"
+ >!{{ pipeline.merge_request.iid }}</gl-link
+ >
+ {{ s__('Job|with') }}
+ <gl-link
+ :href="pipeline.merge_request.source_branch_path"
+ class="link-commit ref-name js-source-branch-link"
+ >{{ pipeline.merge_request.source_branch }}</gl-link
+ >
+
+ <template v-if="isMergeRequestPipeline">
+ {{ s__('Job|into') }}
+ <gl-link
+ :href="pipeline.merge_request.target_branch_path"
+ class="link-commit ref-name js-target-branch-link"
+ >{{ pipeline.merge_request.target_branch }}</gl-link
+ >
+ </template>
+ </template>
+ <gl-link v-else :href="pipeline.ref.path" class="link-commit ref-name">{{
+ pipeline.ref.name
+ }}</gl-link>
+ </template>
+ </div>
<button
type="button"
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 997737b3e23..922f64d93fe 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -52,7 +52,7 @@ export default {
</p>
<template v-if="hasVariables">
- <p class="trigger-variables-btn-container">
+ <p class="trigger-variables-btn-container d-flex">
<span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
<gl-button
diff --git a/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue
new file mode 100644
index 00000000000..25a8da84873
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue
@@ -0,0 +1,30 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+/**
+ * Renders Unmet Prerequisites block for job's view.
+ */
+export default {
+ components: {
+ GlLink,
+ },
+ props: {
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="bs-callout bs-callout-danger">
+ <p class="js-failed-unmet-prerequisites append-bottom-0">
+ {{
+ s__(`Job|This job failed because the necessary resources were not successfully created.`)
+ }}
+
+ <gl-link :href="helpPath" class="js-help-path">
+ <strong> {{ __('More information') }} </strong>
+ </gl-link>
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index a32e945627c..25132449458 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -12,6 +12,7 @@ export default () => {
render(createElement) {
return createElement('job-app', {
props: {
+ deploymentHelpUrl: element.dataset.deploymentHelpUrl,
runnerHelpUrl: element.dataset.runnerHelpUrl,
runnerSettingsUrl: element.dataset.runnerSettingsUrl,
endpoint: element.dataset.endpoint,
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 8045f6dc3ff..12d67a43599 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -179,37 +179,13 @@ export const receiveTraceError = ({ commit }) => {
};
/**
- * Stages dropdown on sidebar
- */
-export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
-export const fetchStages = ({ state, dispatch }) => {
- dispatch('requestStages');
-
- axios
- .get(`${state.job.pipeline.path}.json`)
- .then(({ data }) => {
- // Set selected stage
- dispatch('receiveStagesSuccess', data.details.stages);
- const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
- dispatch('fetchJobsForStage', selectedStage);
- })
- .catch(() => dispatch('receiveStagesError'));
-};
-export const receiveStagesSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_STAGES_SUCCESS, data);
-export const receiveStagesError = ({ commit }) => {
- commit(types.RECEIVE_STAGES_ERROR);
- flash(__('An error occurred while fetching stages.'));
-};
-
-/**
* Jobs list on sidebar - depend on stages dropdown
*/
export const requestJobsForStage = ({ commit }, stage) =>
commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job
-export const fetchJobsForStage = ({ dispatch }, stage) => {
+export const fetchJobsForStage = ({ dispatch }, stage = {}) => {
dispatch('requestJobsForStage', stage);
axios
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 98911717381..406b1a2e375 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -3,6 +3,9 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
+export const hasUnmetPrerequisitesFailure = state =>
+ state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites';
+
export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
@@ -28,6 +31,17 @@ export const emptyStateIllustration = state =>
export const emptyStateAction = state =>
(state.job && state.job.status && state.job.status.action) || null;
+/**
+ * Shared runners limit is only rendered when
+ * used quota is bigger or equal than the limit
+ *
+ * @returns {Boolean}
+ */
+export const shouldRenderSharedRunnerLimitWarning = state =>
+ !_.isEmpty(state.job.runners) &&
+ !_.isEmpty(state.job.runners.quota) &&
+ state.job.runners.quota.used >= state.job.runners.quota.limit;
+
export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
export const hasRunnersForProject = state =>
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
index fd098f13e90..39146b2eefd 100644
--- a/app/assets/javascripts/jobs/store/mutation_types.js
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -24,10 +24,6 @@ export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
-export const REQUEST_STAGES = 'REQUEST_STAGES';
-export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
-export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
-
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE';
export const RECEIVE_JOBS_FOR_STAGE_SUCCESS = 'RECEIVE_JOBS_FOR_STAGE_SUCCESS';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index cd440d21c1f..ad08f27b147 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -65,6 +65,11 @@ export default {
state.isLoading = false;
state.job = job;
+ state.stages =
+ job.pipeline && job.pipeline.details && job.pipeline.details.stages
+ ? job.pipeline.details.stages
+ : [];
+
/**
* We only update it on the first request
* The dropdown can be changed by the user
@@ -101,19 +106,7 @@ export default {
state.isScrolledToBottomBeforeReceivingTrace = toggle;
},
- [types.REQUEST_STAGES](state) {
- state.isLoadingStages = true;
- },
- [types.RECEIVE_STAGES_SUCCESS](state, stages) {
- state.isLoadingStages = false;
- state.stages = stages;
- },
- [types.RECEIVE_STAGES_ERROR](state) {
- state.isLoadingStages = false;
- state.stages = [];
- },
-
- [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
+ [types.REQUEST_JOBS_FOR_STAGE](state, stage = {}) {
state.isLoadingJobs = true;
state.selectedStage = stage.name;
},
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index 04825187c99..6019214e62c 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -25,7 +25,6 @@ export default () => ({
traceState: null,
// sidebar dropdown & list of jobs
- isLoadingStages: false,
isLoadingJobs: false,
selectedStage: '',
stages: [],
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index f134a54dd53..7064731a5ea 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -5,22 +5,26 @@ import Sortable from 'sortablejs';
import flash from './flash';
import axios from './lib/utils/axios_utils';
+import { __ } from './locale';
export default class LabelManager {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.otherLabels = otherLabels || $('.js-other-labels');
- this.errorMessage = 'Unable to update label prioritization at this time';
+ this.errorMessage = __('Unable to update label prioritization at this time');
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.$badgeItemTemplate = $('#js-badge-item-template');
- this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
- filter: '.empty-message',
- forceFallback: true,
- fallbackClass: 'is-dragging',
- dataIdAttr: 'data-id',
- onUpdate: this.onPrioritySortUpdate.bind(this),
- });
+
+ if ('sortable' in this.prioritizedLabels.data()) {
+ Sortable.create(this.prioritizedLabels.get(0), {
+ filter: '.empty-message',
+ forceFallback: true,
+ fallbackClass: 'is-dragging',
+ dataIdAttr: 'data-id',
+ onUpdate: this.onPrioritySortUpdate.bind(this),
+ });
+ }
this.bindEvents();
}
@@ -49,7 +53,7 @@ export default class LabelManager {
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle(
'hidden',
- !!this.prioritizedLabels[0].querySelector(':scope > li'),
+ Boolean(this.prioritizedLabels[0].querySelector(':scope > li')),
);
}
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index f7a611fbca0..3f954b43ee3 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,13 +4,14 @@
import $ from 'jquery';
import _ from 'underscore';
-import { sprintf, __ } from './locale';
+import { sprintf, s__, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
import boardsStore from './boards/stores/boards_store';
+import { isEE, isScopedLabel } from '~/lib/utils/common_utils';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -86,8 +87,9 @@ export default class LabelsSelect {
return this.value;
})
.get();
+ const scopedLabels = $dropdown.data('scopedLabels');
+ const scopedLabelsDocumentationLink = $dropdown.data('scopedLabelsDocumentationLink');
const { handleClick } = options;
-
$sidebarLabelTooltip.tooltip();
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
@@ -132,10 +134,51 @@ export default class LabelsSelect {
template = LabelsSelect.getLabelTemplate({
labels: data.labels,
issueUpdateURL,
+ enableScopedLabels: scopedLabels,
+ scopedLabelsDocumentationLink,
});
labelCount = data.labels.length;
+
+ // EE Specific
+ if (isEE) {
+ /**
+ * For Scoped labels, the last label selected with the
+ * same key will be applied to the current issueable.
+ *
+ * If these are the labels - priority::1, priority::2; and if
+ * we apply them in the same order, only priority::2 will stick
+ * with the issuable.
+ *
+ * In the current dropdown implementation, we keep track of all
+ * the labels selected via a hidden DOM element. Since a User
+ * can select priority::1 and priority::2 at the same time, the
+ * DOM will have 2 hidden input and the dropdown will show both
+ * the items selected but in reality server only applied
+ * priority::2.
+ *
+ * We find all the labels then find all the labels server accepted
+ * and then remove the excess ones.
+ */
+ const toRemoveIds = Array.from(
+ $form.find(`input[type="hidden"][name="${fieldName}"]`),
+ )
+ .map(el => el.value)
+ .map(Number);
+
+ data.labels.forEach(label => {
+ const index = toRemoveIds.indexOf(label.id);
+ toRemoveIds.splice(index, 1);
+ });
+
+ toRemoveIds.forEach(id => {
+ $form
+ .find(`input[type="hidden"][name="${fieldName}"][value="${id}"]`)
+ .last()
+ .remove();
+ });
+ }
} else {
- template = '<span class="no-value">None</span>';
+ template = `<span class="no-value">${__('None')}</span>`;
}
$value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount);
@@ -147,7 +190,9 @@ export default class LabelsSelect {
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
- labelTitles.push('and ' + (data.labels.length - 5) + ' more');
+ labelTitles.push(
+ sprintf(s__('Labels|and %{count} more'), { count: data.labels.length - 5 }),
+ );
}
labelTooltipTitle = labelTitles.join(', ');
@@ -176,13 +221,13 @@ export default class LabelsSelect {
if (showNo) {
extraData.unshift({
id: 0,
- title: 'No Label',
+ title: __('No Label'),
});
}
if (showAny) {
extraData.unshift({
isAny: true,
- title: 'Any Label',
+ title: __('Any Label'),
});
}
if (extraData.length) {
@@ -199,8 +244,8 @@ export default class LabelsSelect {
.catch(() => flash(__('Error fetching labels.')));
},
renderRow: function(label, instance) {
- var $a,
- $li,
+ var linkEl,
+ listItemEl,
color,
colorEl,
indeterminate,
@@ -209,12 +254,11 @@ export default class LabelsSelect {
spacing,
i,
marked,
- dropdownName,
dropdownValue;
- $li = $('<li>');
- $a = $('<a href="#">');
+
selectedClass = [];
removesAll = label.id <= 0 || label.id == null;
+
if ($dropdown.hasClass('js-filter-bulk-update')) {
indeterminate = $dropdown.data('indeterminate') || [];
marked = $dropdown.data('marked') || [];
@@ -233,7 +277,6 @@ export default class LabelsSelect {
}
} else {
if (this.id(label)) {
- dropdownName = $dropdown.data('fieldName');
dropdownValue = this.id(label)
.toString()
.replace(/'/g, "\\'");
@@ -241,7 +284,7 @@ export default class LabelsSelect {
if (
$form.find(
"input[type='hidden'][name='" +
- dropdownName +
+ this.fieldName +
"'][value='" +
dropdownValue +
"']",
@@ -251,24 +294,34 @@ export default class LabelsSelect {
}
}
- if ($dropdown.hasClass('js-multiselect') && removesAll) {
+ if (this.multiSelect && removesAll) {
selectedClass.push('dropdown-clear-active');
}
}
+
if (label.color) {
colorEl =
"<span class='dropdown-label-box' style='background: " + label.color + "'></span>";
} else {
colorEl = '';
}
+
+ linkEl = document.createElement('a');
+ linkEl.href = '#';
+
// We need to identify which items are actually labels
if (label.id) {
selectedClass.push('label-item');
- $a.attr('data-label-id', label.id);
+ linkEl.dataset.labelId = label.id;
}
- $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`);
- // Return generated html
- return $li.html($a).prop('outerHTML');
+
+ linkEl.className = selectedClass.join(' ');
+ linkEl.innerHTML = `${colorEl} ${_.escape(label.title)}`;
+
+ listItemEl = document.createElement('li');
+ listItemEl.appendChild(linkEl);
+
+ return listItemEl;
},
search: {
fields: ['title'],
@@ -290,7 +343,7 @@ export default class LabelsSelect {
if (selected && selected.id === 0) {
this.selected = [];
- return 'No Label';
+ return __('No Label');
} else if (isSelected) {
this.selected.push(title);
} else if (!isSelected && title) {
@@ -350,6 +403,7 @@ export default class LabelsSelect {
} else {
if (!$dropdown.hasClass('js-filter-bulk-update')) {
saveLabelData();
+ $dropdown.data('glDropdown').clearMenu();
}
}
}
@@ -463,19 +517,60 @@ export default class LabelsSelect {
// so best approach is to use traditional way of
// concatenation
// see: http://2ality.com/2016/05/template-literal-whitespace.html#joining-arrays
- const tpl = _.template(
+
+ const labelTemplate = _.template(
[
- '<% _.each(labels, function(label){ %>',
'<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">',
- '<span class="badge label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">',
+ '<span class="badge label has-tooltip color-label" <%= linkAttrs %> title="<%= tooltipTitleTemplate({ label, isScopedLabel, enableScopedLabels, escapeStr }) %>" style="background-color: <%= escapeStr(label.color) %>; color: <%= escapeStr(label.text_color) %>;">',
'<%- label.title %>',
'</span>',
'</a>',
+ ].join(''),
+ );
+
+ const infoIconTemplate = _.template(
+ [
+ '<a href="<%= scopedLabelsDocumentationLink %>" class="label scoped-label" target="_blank" rel="noopener">',
+ '<i class="fa fa-question-circle" style="background-color: <%= escapeStr(label.color) %>; color: <%= escapeStr(label.text_color) %>;"></i>',
+ '</a>',
+ ].join(''),
+ );
+
+ const tooltipTitleTemplate = _.template(
+ [
+ '<% if (isScopedLabel(label) && enableScopedLabels) { %>',
+ "<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span>",
+ '<br />',
+ '<%= escapeStr(label.description) %>',
+ '<% } else { %>',
+ '<%= escapeStr(label.description) %>',
+ '<% } %>',
+ ].join(''),
+ );
+
+ const tpl = _.template(
+ [
+ '<% _.each(labels, function(label){ %>',
+ '<% if (isScopedLabel(label) && enableScopedLabels) { %>',
+ '<span class="d-inline-block position-relative scoped-label-wrapper">',
+ '<%= labelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, tooltipTitleTemplate, escapeStr, linkAttrs: \'data-html="true"\' }) %>',
+ '<%= infoIconTemplate({ label, scopedLabelsDocumentationLink, escapeStr }) %>',
+ '</span>',
+ '<% } else { %>',
+ '<%= labelTemplate({ label, issueUpdateURL, isScopedLabel, enableScopedLabels, tooltipTitleTemplate, escapeStr, linkAttrs: "" }) %>',
+ '<% } %>',
'<% }); %>',
].join(''),
);
- return tpl(tplData);
+ return tpl({
+ ...tplData,
+ labelTemplate,
+ infoIconTemplate,
+ tooltipTitleTemplate,
+ isScopedLabel,
+ escapeStr: _.escape,
+ });
}
bindEvents() {
@@ -486,7 +581,7 @@ export default class LabelsSelect {
if ($('.selected-issuable:checked').length) {
return;
}
- return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label');
+ return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text(__('Label'));
}
// eslint-disable-next-line class-methods-use-this
enableBulkLabelDropdown() {
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 64e4e899f44..5857f9e22ae 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -1,11 +1,32 @@
-import ApolloClient from 'apollo-boost';
+import { ApolloClient } from 'apollo-client';
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import { createUploadLink } from 'apollo-upload-client';
+import { ApolloLink } from 'apollo-link';
+import { BatchHttpLink } from 'apollo-link-batch-http';
import csrf from '~/lib/utils/csrf';
-export default (clientState = {}) =>
- new ApolloClient({
- uri: `${gon.relative_url_root}/api/graphql`,
+export default (resolvers = {}, config = {}) => {
+ let uri = `${gon.relative_url_root}/api/graphql`;
+
+ if (config.baseUrl) {
+ // Prepend baseUrl and ensure that `///` are replaced with `/`
+ uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/');
+ }
+
+ const httpOptions = {
+ uri,
headers: {
[csrf.headerKey]: csrf.token,
},
- clientState,
+ };
+
+ return new ApolloClient({
+ link: ApolloLink.split(
+ operation => operation.getContext().hasUpload,
+ createUploadLink(httpOptions),
+ new BatchHttpLink(httpOptions),
+ ),
+ cache: new InMemoryCache(config.cacheConfig),
+ resolvers,
});
+};
diff --git a/app/assets/javascripts/lib/utils/accessor.js b/app/assets/javascripts/lib/utils/accessor.js
index 1d18992af63..39cffedcac6 100644
--- a/app/assets/javascripts/lib/utils/accessor.js
+++ b/app/assets/javascripts/lib/utils/accessor.js
@@ -2,7 +2,7 @@ function isPropertyAccessSafe(base, property) {
let safe;
try {
- safe = !!base[property];
+ safe = Boolean(base[property]);
} catch (error) {
safe = false;
}
diff --git a/app/assets/javascripts/lib/utils/autosave.js b/app/assets/javascripts/lib/utils/autosave.js
new file mode 100644
index 00000000000..023c336db02
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/autosave.js
@@ -0,0 +1,32 @@
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+export const clearDraft = autosaveKey => {
+ try {
+ window.localStorage.removeItem(`autosave/${autosaveKey}`);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+};
+
+export const getDraft = autosaveKey => {
+ try {
+ return window.localStorage.getItem(`autosave/${autosaveKey}`);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ return null;
+ }
+};
+
+export const updateDraft = (autosaveKey, text) => {
+ try {
+ window.localStorage.setItem(`autosave/${autosaveKey}`, text);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+};
+
+export const getDiscussionReplyKey = (noteableType, discussionId) =>
+ ['Note', capitalizeFirstCharacter(noteableType), discussionId, 'Reply'].join('/');
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index a24c71aeab1..28a7ebfdc69 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -51,6 +51,7 @@ export default class LinkedTabs {
this.defaultAction = this.options.defaultAction;
this.action = this.options.action || this.defaultAction;
+ this.hashedTabs = this.options.hashedTabs || false;
if (this.action === 'show') {
this.action = this.defaultAction;
@@ -58,6 +59,10 @@ export default class LinkedTabs {
this.currentLocation = window.location;
+ if (this.hashedTabs) {
+ this.action = this.currentLocation.hash || this.action;
+ }
+
const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
// since this is a custom event we need jQuery :(
@@ -91,7 +96,9 @@ export default class LinkedTabs {
copySource.replace(/\/+$/, '');
- const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
+ const newState = this.hashedTabs
+ ? copySource
+ : `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
window.history.replaceState(
{
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index a73cdb73690..cc5e12aa467 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -7,6 +7,7 @@ import axios from './axios_utils';
import { getLocationHash } from './url_utility';
import { convertToCamelCase } from './text_utility';
import { isObject } from './type_utility';
+import breakpointInstance from '../../breakpoints';
export const getPagePath = (index = 0) => {
const page = $('body').attr('data-page') || '';
@@ -93,6 +94,8 @@ export const handleLocationHash = () => {
const fixedNav = document.querySelector('.navbar-gitlab');
const performanceBar = document.querySelector('#js-peek');
const topPadding = 8;
+ const diffFileHeader = document.querySelector('.js-file-title');
+ const versionMenusContainer = document.querySelector('.mr-version-menus-container');
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -113,6 +116,14 @@ export const handleLocationHash = () => {
adjustment -= performanceBar.offsetHeight;
}
+ if (diffFileHeader) {
+ adjustment -= diffFileHeader.offsetHeight;
+ }
+
+ if (versionMenusContainer) {
+ adjustment -= versionMenusContainer.offsetHeight;
+ }
+
if (isInMRPage()) {
adjustment -= topPadding;
}
@@ -193,16 +204,23 @@ export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
export const contentTop = () => {
- const perfBar = $('#js-peek').height() || 0;
- const mrTabsHeight = $('.merge-request-tabs').height() || 0;
- const headerHeight = $('.navbar-gitlab').height() || 0;
- const diffFilesChanged = $('.js-diff-files-changed').height() || 0;
- const diffFileLargeEnoughScreen =
- 'matchMedia' in window ? window.matchMedia('min-width: 768') : true;
+ const perfBar = $('#js-peek').outerHeight() || 0;
+ const mrTabsHeight = $('.merge-request-tabs').outerHeight() || 0;
+ const headerHeight = $('.navbar-gitlab').outerHeight() || 0;
+ const diffFilesChanged = $('.js-diff-files-changed').outerHeight() || 0;
+ const isDesktop = breakpointInstance.isDesktop();
const diffFileTitleBar =
- (diffFileLargeEnoughScreen && $('.diff-file .file-title-flex-parent:visible').height()) || 0;
+ (isDesktop && $('.diff-file .file-title-flex-parent:visible').outerHeight()) || 0;
+ const compareVersionsHeaderHeight = (isDesktop && $('.mr-version-controls').outerHeight()) || 0;
- return perfBar + mrTabsHeight + headerHeight + diffFilesChanged + diffFileTitleBar;
+ return (
+ perfBar +
+ mrTabsHeight +
+ headerHeight +
+ diffFilesChanged +
+ diffFileTitleBar +
+ compareVersionsHeaderHeight
+ );
};
export const scrollToElement = element => {
@@ -708,6 +726,26 @@ export const NavigationType = {
TYPE_RESERVED: 255,
};
+/**
+ * Returns the value of `gon.ee`
+ * Used to check if it's the EE codebase or the CE one.
+ *
+ * @returns Boolean
+ */
+export const isEE = () => window.gon && window.gon.ee;
+
+/**
+ * Checks if the given Label has a special syntax `::` in
+ * it's title.
+ *
+ * Expected Label to be an Object with `title` as a key:
+ * { title: 'LabelTitle', ...otherProperties };
+ *
+ * @param {Object} label
+ * @returns Boolean
+ */
+export const isScopedLabel = ({ title = '' }) => title.indexOf('::') !== -1;
+
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d3fe8f77bd4..d521c462ad8 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -3,7 +3,7 @@ import _ from 'underscore';
import timeago from 'timeago.js';
import dateFormat from 'dateformat';
import { pluralize } from './text_utility';
-import { languageCode, s__ } from '../../locale';
+import { languageCode, s__, __ } from '../../locale';
window.timeago = timeago;
@@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len);
* @returns {String}
*/
export const getDayName = date =>
- ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
+ [
+ __('Sunday'),
+ __('Monday'),
+ __('Tuesday'),
+ __('Wednesday'),
+ __('Thursday'),
+ __('Friday'),
+ __('Saturday'),
+ ][date.getDay()];
/**
* @example
@@ -71,7 +79,12 @@ export const getDayName = date =>
* @param {date} datetime
* @returns {String}
*/
-export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+export const formatDate = datetime => {
+ if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
+ throw new Error(__('Invalid date'));
+ }
+ return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+};
/**
* Timeago uses underscores instead of dashes to separate language from country code.
@@ -92,7 +105,7 @@ export const getTimeago = () => {
const timeAgoLocaleRemaining = [
() => [s__('Timeago|just now'), s__('Timeago|right now')],
- () => [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
+ () => [s__('Timeago|just now'), s__('Timeago|%s seconds remaining')],
() => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
() => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
() => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
@@ -121,7 +134,7 @@ export const getTimeago = () => {
const timeAgoLocale = [
() => [s__('Timeago|just now'), s__('Timeago|right now')],
- () => [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
+ () => [s__('Timeago|just now'), s__('Timeago|in %s seconds')],
() => [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
() => [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
() => [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
@@ -160,7 +173,11 @@ export const getTimeago = () => {
* @param {Boolean} setTimeago
*/
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
- getTimeago().render($timeagoEls, timeagoLanguageCode);
+ getTimeago();
+
+ $timeagoEls.each((i, el) => {
+ $(el).text(timeagoInstance.format($(el).attr('datetime'), timeagoLanguageCode));
+ });
if (!setTimeago) {
return;
@@ -316,13 +333,13 @@ export const getSundays = date => {
}
const daysToSunday = [
- 'Saturday',
- 'Friday',
- 'Thursday',
- 'Wednesday',
- 'Tuesday',
- 'Monday',
- 'Sunday',
+ __('Saturday'),
+ __('Friday'),
+ __('Thursday'),
+ __('Wednesday'),
+ __('Tuesday'),
+ __('Monday'),
+ __('Sunday'),
];
const month = date.getMonth();
@@ -332,7 +349,7 @@ export const getSundays = date => {
while (dateOfMonth.getMonth() === month) {
const dayName = getDayName(dateOfMonth);
- if (dayName === 'Sunday') {
+ if (dayName === __('Sunday')) {
sundays.push(new Date(dateOfMonth.getTime()));
}
@@ -496,7 +513,7 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => {
const reducedTime = _.reduce(
timeObject,
(memo, unitValue, unitName) => {
- const isNonZero = !!unitValue;
+ const isNonZero = Boolean(unitValue);
if (fullNameFormat && isNonZero) {
// Remove traling 's' if unit value is singular
diff --git a/app/assets/javascripts/lib/utils/highlight.js b/app/assets/javascripts/lib/utils/highlight.js
new file mode 100644
index 00000000000..8f0afa3467d
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/highlight.js
@@ -0,0 +1,44 @@
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import _ from 'underscore';
+import sanitize from 'sanitize-html';
+
+/**
+ * Wraps substring matches with HTML `<span>` elements.
+ * Inputs are sanitized before highlighting, so this
+ * filter is safe to use with `v-html` (as long as `matchPrefix`
+ * and `matchSuffix` are not being dynamically generated).
+ *
+ * Note that this function can't be used inside `v-html` as a filter
+ * (Vue filters cannot be used inside `v-html`).
+ *
+ * @param {String} string The string to highlight
+ * @param {String} match The substring match to highlight in the string
+ * @param {String} matchPrefix The string to insert at the beginning of a match
+ * @param {String} matchSuffix The string to insert at the end of a match
+ */
+export default function highlight(string, match = '', matchPrefix = '<b>', matchSuffix = '</b>') {
+ if (_.isUndefined(string) || _.isNull(string)) {
+ return '';
+ }
+
+ if (_.isUndefined(match) || _.isNull(match) || match === '') {
+ return string;
+ }
+
+ const sanitizedValue = sanitize(string.toString(), { allowedTags: [] });
+
+ // occurrences is an array of character indices that should be
+ // highlighted in the original string, i.e. [3, 4, 5, 7]
+ const occurrences = fuzzaldrinPlus.match(sanitizedValue, match.toString());
+
+ return sanitizedValue
+ .split('')
+ .map((character, i) => {
+ if (_.contains(occurrences, i)) {
+ return `${matchPrefix}${character}${matchSuffix}`;
+ }
+
+ return character;
+ })
+ .join('');
+}
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 14c02218990..37ad1676f7a 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -16,6 +16,7 @@ const httpStatusCodes = {
IM_USED: 226,
MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400,
+ UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
UNPROCESSABLE_ENTITY: 422,
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 2ccc51c35f7..61c8b8803d7 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -1,4 +1,5 @@
import { BYTES_IN_KIB } from './constants';
+import { sprintf, __ } from '~/locale';
/**
* Function that allows a number with an X amount of decimals
@@ -72,11 +73,36 @@ export function bytesToGiB(number) {
*/
export function numberToHumanSize(size) {
if (size < BYTES_IN_KIB) {
- return `${size} bytes`;
+ return sprintf(__('%{size} bytes'), { size });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB) {
- return `${bytesToKiB(size).toFixed(2)} KiB`;
+ return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) {
- return `${bytesToMiB(size).toFixed(2)} MiB`;
+ return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
}
- return `${bytesToGiB(size).toFixed(2)} GiB`;
+ return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });
}
+
+/**
+ * A simple method that returns the value of a + b
+ * It seems unessesary, but when combined with a reducer it
+ * adds up all the values in an array.
+ *
+ * e.g. `[1, 2, 3, 4, 5].reduce(sum) // => 15`
+ *
+ * @param {Float} a
+ * @param {Float} b
+ * @example
+ * // return 15
+ * [1, 2, 3, 4, 5].reduce(sum);
+ *
+ * // returns 6
+ * Object.values([{a: 1, b: 2, c: 3].reduce(sum);
+ * @returns {Float} The summed value
+ */
+export const sum = (a = 0, b = 0) => a + b;
+
+/**
+ * Checks if the provided number is odd
+ * @param {Int} number
+ */
+export const isOdd = (number = 0) => number % 2;
diff --git a/app/assets/javascripts/lib/utils/simple_poll.js b/app/assets/javascripts/lib/utils/simple_poll.js
index 473f179ad86..576a9ec880c 100644
--- a/app/assets/javascripts/lib/utils/simple_poll.js
+++ b/app/assets/javascripts/lib/utils/simple_poll.js
@@ -1,10 +1,10 @@
-export default (fn, interval = 2000, timeout = 60000) => {
+export default (fn, { interval = 2000, timeout = 60000 } = {}) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
- if (Date.now() - startTime < timeout) {
+ if (timeout === 0 || Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
} else {
reject(new Error('SIMPLE_POLL_TIMEOUT'));
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 84a617acb42..b7922e29bb0 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -223,9 +223,9 @@ export function insertMarkdownText({
return tag.replace(textPlaceholder, val);
}
if (val.indexOf(tag) === 0) {
- return '' + val.replace(tag, '');
+ return String(val.replace(tag, ''));
} else {
- return '' + tag + val;
+ return String(tag) + val;
}
})
.join('\n');
@@ -233,7 +233,7 @@ export function insertMarkdownText({
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else {
- textToInsert = '' + startChar + tag + selected + (wrap ? tag : ' ');
+ textToInsert = String(startChar) + tag + selected + (wrap ? tag : ' ');
}
if (removedFirstNewLine) {
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index c49b1bb5a2f..cc1d85fd97d 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,3 +1,5 @@
+import _ from 'underscore';
+
/**
* Adds a , to a string composed by numbers, at every 3 chars.
*
@@ -42,18 +44,18 @@ export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' :
export const dasherize = str => str.replace(/[_\s]+/g, '-');
/**
- * Removes accents and converts to lower case
+ * Replaces whitespaces with hyphens and converts to lower case
* @param {String} str
* @returns {String}
*/
-export const slugify = str => str.trim().toLowerCase();
+export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
/**
- * Replaces whitespaces with hyphens and converts to lower case
+ * Replaces whitespaces with underscore and converts to lower case
* @param {String} str
* @returns {String}
*/
-export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
+export const slugifyWithUnderscore = str => str.toLowerCase().replace(/\s+/g, '_');
/**
* Truncates given text
@@ -160,3 +162,33 @@ export const splitCamelCase = string =>
.replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
.trim();
+
+/**
+ * Intelligently truncates an item's namespace by doing two things:
+ * 1. Only include group names in path by removing the item name
+ * 2. Only include the first and last group names in the path
+ * when the namespace includes more than 2 groups
+ *
+ * @param {String} string A string namespace,
+ * i.e. "My Group / My Subgroup / My Project"
+ */
+export const truncateNamespace = (string = '') => {
+ if (_.isNull(string) || !_.isString(string)) {
+ return '';
+ }
+
+ const namespaceArray = string.split(' / ');
+
+ if (namespaceArray.length === 1) {
+ return string;
+ }
+
+ namespaceArray.splice(-1, 1);
+ let namespace = namespaceArray.join(' / ');
+
+ if (namespaceArray.length > 2) {
+ namespace = `${namespaceArray[0]} / ... / ${namespaceArray.pop()}`;
+ }
+
+ return namespace;
+};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 4ba84589705..b5474fc5c71 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -120,3 +120,41 @@ export function webIDEUrl(route = undefined) {
}
return returnUrl;
}
+
+/**
+ * Returns current base URL
+ */
+export function getBaseURL() {
+ const { protocol, host } = window.location;
+ return `${protocol}//${host}`;
+}
+
+/**
+ * Returns true if url is an absolute or root-relative URL
+ *
+ * @param {String} url
+ */
+export function isAbsoluteOrRootRelative(url) {
+ return /^(https?:)?\//.test(url);
+}
+
+/**
+ * Checks if the provided URL is a safe URL (absolute http(s) or root-relative URL)
+ *
+ * @param {String} url that will be checked
+ * @returns {Boolean}
+ */
+export function isSafeURL(url) {
+ if (!isAbsoluteOrRootRelative(url)) {
+ return false;
+ }
+
+ try {
+ const parsedUrl = new URL(url, getBaseURL());
+ return ['http:', 'https:'].includes(parsedUrl.protocol);
+ } catch (e) {
+ return false;
+ }
+}
+
+export { join as joinPaths } from 'path';
diff --git a/app/assets/javascripts/lib/utils/webpack.js b/app/assets/javascripts/lib/utils/webpack.js
index 308ad9784e4..37b17f0fe23 100644
--- a/app/assets/javascripts/lib/utils/webpack.js
+++ b/app/assets/javascripts/lib/utils/webpack.js
@@ -1,3 +1,5 @@
+import { joinPaths } from '~/lib/utils/url_utility';
+
// tell webpack to load assets from origin so that web workers don't break
// eslint-disable-next-line import/prefer-default-export
export function resetServiceWorkersPublicPath() {
@@ -5,6 +7,12 @@ export function resetServiceWorkersPublicPath() {
// the webpack publicPath setting at runtime.
// see: https://webpack.js.org/guides/public-path/
const relativeRootPath = (gon && gon.relative_url_root) || '';
- const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
+ const webpackAssetPath = joinPaths(relativeRootPath, '/assets/webpack/');
__webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
+
+ // monaco-editor-webpack-plugin currently (incorrectly) references the
+ // public path as a property of `window`. Once this is fixed upstream we
+ // can remove this line
+ // see: https://github.com/Microsoft/monaco-editor-webpack-plugin/pull/63
+ window.__webpack_public_path__ = webpackAssetPath; // eslint-disable-line
}
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index 1ae3362c4bc..41aa0f4ddb9 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -11,7 +11,7 @@ delete window.translations;
@param text The text to be translated
@returns {String} The translated text
*/
-const gettext = text => locale.gettext.bind(locale)(ensureSingleLine(text));
+const gettext = text => locale.gettext(ensureSingleLine(text));
/**
Translate the text with a number
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 1b722c0505a..9f30a989295 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -31,6 +31,7 @@ import initPerformanceBar from './performance_bar';
import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
+import { __ } from './locale';
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
@@ -135,6 +136,24 @@ function deferredInitialisation() {
});
loadAwardsHandler();
+
+ /**
+ * Toggle Canary Badge
+ *
+ * For GitLab.com only, when the user is using canary
+ * we render a Next badge and hide the option to switch
+ * to canay
+ */
+ if (Cookies.get('gitlab_canary') && Cookies.get('gitlab_canary') === 'true') {
+ const canaryBadge = document.querySelector('.js-canary-badge');
+ const canaryLink = document.querySelector('.js-canary-link');
+ if (canaryBadge) {
+ canaryBadge.classList.remove('hidden');
+ }
+ if (canaryLink) {
+ canaryLink.classList.add('hidden');
+ }
+ }
}
document.addEventListener('DOMContentLoaded', () => {
@@ -201,9 +220,9 @@ document.addEventListener('DOMContentLoaded', () => {
const ref = xhrObj.status;
if (ref === 401) {
- Flash('You need to be logged in.');
+ Flash(__('You need to be logged in.'));
} else if (ref === 404 || ref === 500) {
- Flash('Something went wrong on our end.');
+ Flash(__('Something went wrong on our end.'));
}
});
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index bd263c75a3d..af2697444f2 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -16,25 +16,33 @@ export default class Members {
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
+ dropdownClicked(options) {
+ this.formSubmit(null, options.$el);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ dropdownToggleLabel(selected, $el) {
+ return $el.text();
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ dropdownIsSelectable(selected, $el) {
+ return !$el.hasClass('is-active');
+ }
+
initGLDropdown() {
$('.js-member-permissions-dropdown').each((i, btn) => {
const $btn = $(btn);
$btn.glDropdown({
selectable: true,
- isSelectable(selected, $el) {
- return !$el.hasClass('is-active');
- },
+ isSelectable: (selected, $el) => this.dropdownIsSelectable(selected, $el),
fieldName: $btn.data('fieldName'),
id(selected, $el) {
return $el.data('id');
},
- toggleLabel(selected, $el) {
- return $el.text();
- },
- clicked: options => {
- this.formSubmit(null, options.$el);
- },
+ toggleLabel: (selected, $el) => this.dropdownToggleLabel(selected, $el, $btn),
+ clicked: options => this.dropdownClicked(options),
});
});
}
@@ -55,6 +63,7 @@ export default class Members {
$toggle.enable();
$dateInput.enable();
}
+
// eslint-disable-next-line class-methods-use-this
getMemberListItems($el) {
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('elId')}`);
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
index c2de0379d23..3cb406b819d 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
@@ -16,7 +16,7 @@ import utilsMixin from '../mixins/line_conflict_utils';
},
},
template: `
- <table>
+ <table class="diff-wrap-lines code js-syntax-highlight">
<tr class="line_holder parallel" v-for="section in file.parallelLines">
<template v-for="line in section">
<td class="diff-line-num header" :class="lineCssClass(line)" v-if="line.isHeader"></td>
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 0333335de06..88bc0940741 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -3,15 +3,16 @@
import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
+import { s__ } from '~/locale';
(global => {
global.mergeConflicts = global.mergeConflicts || {};
const diffViewType = Cookies.get('diff_view');
- const HEAD_HEADER_TEXT = 'HEAD//our changes';
- const ORIGIN_HEADER_TEXT = 'origin//their changes';
- const HEAD_BUTTON_TITLE = 'Use ours';
- const ORIGIN_BUTTON_TITLE = 'Use theirs';
+ const HEAD_HEADER_TEXT = s__('MergeConflict|HEAD//our changes');
+ const ORIGIN_HEADER_TEXT = s__('MergeConflict|origin//their changes');
+ const HEAD_BUTTON_TITLE = s__('MergeConflict|Use ours');
+ const ORIGIN_BUTTON_TITLE = s__('MergeConflict|Use theirs');
const INTERACTIVE_RESOLVE_MODE = 'interactive';
const EDIT_RESOLVE_MODE = 'edit';
const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
@@ -173,7 +174,7 @@ import Cookies from 'js-cookie';
getConflictsCountText() {
const count = this.getConflictsCount();
- const text = count > 1 ? 'conflicts' : 'conflict';
+ const text = count > 1 ? s__('MergeConflict|conflicts') : s__('MergeConflict|conflict');
return `${count} ${text}`;
},
@@ -348,8 +349,8 @@ import Cookies from 'js-cookie';
},
getCommitButtonText() {
- const initial = 'Commit to source branch';
- const inProgress = 'Committing...';
+ const initial = s__('MergeConflict|Commit to source branch');
+ const inProgress = s__('MergeConflict|Committing...');
return this.state ? (this.state.isSubmitting ? inProgress : initial) : initial;
},
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 7badd68089c..d8d203e0616 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -8,6 +8,7 @@ import './components/diff_file_editor';
import './components/inline_conflict_lines';
import './components/parallel_conflict_lines';
import syntaxHighlight from '../syntax_highlight';
+import { __ } from '~/locale';
export default function initMergeConflicts() {
const INTERACTIVE_RESOLVE_MODE = 'interactive';
@@ -92,7 +93,7 @@ export default function initMergeConflicts() {
})
.catch(() => {
mergeConflictsStore.setSubmitState(false);
- createFlash('Failed to save merge conflicts resolutions. Please try again!');
+ createFlash(__('Failed to save merge conflicts resolutions. Please try again!'));
});
},
},
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 2f15da42271..e5cf43e8289 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -21,6 +21,7 @@ import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
import Notes from './notes';
import { polyfillSticky } from './lib/utils/sticky';
+import { __ } from './locale';
// MergeRequestTabs
//
@@ -326,7 +327,7 @@ export default class MergeRequestTabs {
})
.catch(() => {
this.toggleLoading(false);
- flash('An error occurred while fetching this tab.');
+ flash(__('An error occurred while fetching this tab.'));
});
}
@@ -398,7 +399,7 @@ export default class MergeRequestTabs {
const hash = getLocationHash();
const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) {
- const notesContent = anchor.closest('.notes_content');
+ const notesContent = anchor.closest('.notes-content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
Notes.instance.toggleDiffNote({
target: anchor,
@@ -416,7 +417,7 @@ export default class MergeRequestTabs {
})
.catch(() => {
this.toggleLoading(false);
- flash('An error occurred while fetching this tab.');
+ flash(__('An error occurred while fetching this tab.'));
});
}
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index f211632cf24..6aaba4e7c74 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
+import { __ } from './locale';
export default class Milestone {
constructor() {
@@ -42,7 +43,7 @@ export default class Milestone {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
})
- .catch(() => flash('Error loading milestone tab'));
+ .catch(() => flash(__('Error loading milestone tab')));
}
}
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 75c18a9b6a0..43949d5cc86 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -56,14 +56,15 @@ export default class MilestoneSelect {
const $value = $block.find('.value');
const $loading = $block.find('.block-loading').fadeOut();
selectedMilestoneDefault = showAny ? '' : null;
- selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault;
+ selectedMilestoneDefault =
+ showNo && defaultNo ? __('No Milestone') : selectedMilestoneDefault;
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
if (issueUpdateURL) {
milestoneLinkTemplate = _.template(
'<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>',
);
- milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
+ milestoneLinkNoneTemplate = `<span class="no-value">${__('None')}</span>`;
}
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
@@ -74,28 +75,28 @@ export default class MilestoneSelect {
extraOptions.push({
id: null,
name: null,
- title: 'Any Milestone',
+ title: __('Any Milestone'),
});
}
if (showNo) {
extraOptions.push({
id: -1,
- name: 'No Milestone',
- title: 'No Milestone',
+ name: __('No Milestone'),
+ title: __('No Milestone'),
});
}
if (showUpcoming) {
extraOptions.push({
id: -2,
name: '#upcoming',
- title: 'Upcoming',
+ title: __('Upcoming'),
});
}
if (showStarted) {
extraOptions.push({
id: -3,
name: '#started',
- title: 'Started',
+ title: __('Started'),
});
}
if (extraOptions.length) {
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index 81ab9d8be4b..b39ad764f01 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import flash from './flash';
import axios from './lib/utils/axios_utils';
+import { __ } from './locale';
/**
* In each pipelines table we have a mini pipeline graph for each pipeline.
@@ -98,7 +99,7 @@ export default class MiniPipelineGraph {
) {
$(button).dropdown('toggle');
}
- flash('An error occurred while fetching the builds.', 'alert');
+ flash(__('An error occurred while fetching the builds.'), 'alert');
});
}
diff --git a/app/assets/javascripts/mirrors/mirror_repos.js b/app/assets/javascripts/mirrors/mirror_repos.js
index 196b84621b6..33e9b1c4e46 100644
--- a/app/assets/javascripts/mirrors/mirror_repos.js
+++ b/app/assets/javascripts/mirrors/mirror_repos.js
@@ -87,7 +87,7 @@ export default class MirrorRepos {
project: {
remote_mirrors_attributes: {
id: $target.data('mirrorId'),
- enabled: 0,
+ _destroy: 1,
},
},
};
diff --git a/app/assets/javascripts/mirrors/ssh_mirror.js b/app/assets/javascripts/mirrors/ssh_mirror.js
index 5bdf5d6277a..bb5ae6ce2d1 100644
--- a/app/assets/javascripts/mirrors/ssh_mirror.js
+++ b/app/assets/javascripts/mirrors/ssh_mirror.js
@@ -20,15 +20,10 @@ export default class SSHMirror {
this.$btnDetectHostKeys = this.$form.find('.js-detect-host-keys');
this.$btnSSHHostsShowAdvanced = this.$form.find('.btn-show-advanced');
this.$dropdownAuthType = this.$form.find('.js-mirror-auth-type');
+ this.$hiddenAuthType = this.$form.find('.js-hidden-mirror-auth-type');
this.$wellAuthTypeChanging = this.$form.find('.js-well-changing-auth');
this.$wellPasswordAuth = this.$form.find('.js-well-password-auth');
- this.$wellSSHAuth = this.$form.find('.js-well-ssh-auth');
- this.$sshPublicKeyWrap = this.$form.find('.js-ssh-public-key-wrap');
- this.$regeneratePublicSshKeyButton = this.$wellSSHAuth.find('.js-btn-regenerate-ssh-key');
- this.$regeneratePublicSshKeyModal = this.$wellSSHAuth.find(
- '.js-regenerate-public-ssh-key-confirm-modal',
- );
}
init() {
@@ -39,15 +34,6 @@ export default class SSHMirror {
this.$dropdownAuthType.on('change', e => this.handleAuthTypeChange(e));
this.$btnDetectHostKeys.on('click', e => this.handleDetectHostKeys(e));
this.$btnSSHHostsShowAdvanced.on('click', e => this.handleSSHHostsAdvanced(e));
- this.$regeneratePublicSshKeyButton.on('click', () =>
- this.$regeneratePublicSshKeyModal.toggle(true),
- );
- $('.js-confirm', this.$regeneratePublicSshKeyModal).on('click', e =>
- this.regeneratePublicSshKey(e),
- );
- $('.js-cancel', this.$regeneratePublicSshKeyModal).on('click', () =>
- this.$regeneratePublicSshKeyModal.toggle(false),
- );
}
/**
@@ -161,53 +147,11 @@ export default class SSHMirror {
* Authentication method dropdown change event listener
*/
handleAuthTypeChange() {
- const projectMirrorAuthTypeEndpoint = `${this.$form.attr('action')}.json`;
- const $sshPublicKey = this.$sshPublicKeyWrap.find('.ssh-public-key');
const selectedAuthType = this.$dropdownAuthType.val();
this.$wellPasswordAuth.collapse('hide');
- this.$wellSSHAuth.collapse('hide');
-
- // This request should happen only if selected Auth type was SSH
- // and SSH Public key was not present on page load
- if (selectedAuthType === AUTH_METHOD.SSH && !$sshPublicKey.text().trim()) {
- if (!this.$wellSSHAuth.length) return;
-
- // Construct request body
- const authTypeData = {
- project: {
- ...this.$regeneratePublicSshKeyButton.data().projectData,
- },
- };
-
- this.$wellAuthTypeChanging.collapse('show');
- this.$dropdownAuthType.disable();
-
- axios
- .put(projectMirrorAuthTypeEndpoint, JSON.stringify(authTypeData), {
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- })
- .then(({ data }) => {
- // Show SSH public key container and fill in public key
- this.toggleAuthWell(selectedAuthType);
- this.toggleSSHAuthWellMessage(true);
- this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
-
- this.$wellAuthTypeChanging.collapse('hide');
- this.$dropdownAuthType.enable();
- })
- .catch(() => {
- Flash(__('Something went wrong on our end.'));
-
- this.$wellAuthTypeChanging.collapse('hide');
- this.$dropdownAuthType.enable();
- });
- } else {
- this.toggleAuthWell(selectedAuthType);
- this.$wellSSHAuth.find('.js-ssh-public-key-present').collapse('show');
- }
+ this.updateHiddenAuthType(selectedAuthType);
+ this.toggleAuthWell(selectedAuthType);
}
/**
@@ -233,57 +177,12 @@ export default class SSHMirror {
*/
toggleAuthWell(authType) {
this.$wellPasswordAuth.collapse(authType === AUTH_METHOD.PASSWORD ? 'show' : 'hide');
- this.$wellSSHAuth.collapse(authType === AUTH_METHOD.SSH ? 'show' : 'hide');
+ this.updateHiddenAuthType(authType);
}
- /**
- * Toggle SSH auth information message
- */
- toggleSSHAuthWellMessage(sshKeyPresent) {
- this.$sshPublicKeyWrap.collapse(sshKeyPresent ? 'show' : 'hide');
- this.$wellSSHAuth.find('.js-ssh-public-key-present').collapse(sshKeyPresent ? 'show' : 'hide');
- this.$regeneratePublicSshKeyButton.collapse(sshKeyPresent ? 'show' : 'hide');
- this.$wellSSHAuth.find('.js-ssh-public-key-pending').collapse(sshKeyPresent ? 'hide' : 'show');
- }
-
- /**
- * Sets SSH Public key to Clipboard button and shows it on UI.
- */
- setSSHPublicKey(sshPublicKey) {
- this.$sshPublicKeyWrap.find('.ssh-public-key').text(sshPublicKey);
- this.$sshPublicKeyWrap
- .find('.btn-copy-ssh-public-key')
- .attr('data-clipboard-text', sshPublicKey);
- }
-
- regeneratePublicSshKey(event) {
- event.preventDefault();
-
- this.$regeneratePublicSshKeyModal.toggle(false);
-
- const button = this.$regeneratePublicSshKeyButton;
- const spinner = $('.js-spinner', button);
- const endpoint = button.data('endpoint');
- const authTypeData = {
- project: {
- ...this.$regeneratePublicSshKeyButton.data().projectData,
- },
- };
-
- button.attr('disabled', 'disabled');
- spinner.removeClass('d-none');
-
- axios
- .patch(endpoint, authTypeData)
- .then(({ data }) => {
- button.removeAttr('disabled');
- spinner.addClass('d-none');
-
- this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
- })
- .catch(() => {
- Flash(_('Unable to regenerate public ssh key.'));
- });
+ updateHiddenAuthType(authType) {
+ this.$hiddenAuthType.val(authType);
+ this.$hiddenAuthType.prop('disabled', authType === AUTH_METHOD.SSH);
}
destroy() {
@@ -292,8 +191,5 @@ export default class SSHMirror {
this.$dropdownAuthType.off('change');
this.$btnDetectHostKeys.off('click');
this.$btnSSHHostsShowAdvanced.off('click');
- this.$regeneratePublicSshKeyButton.off('click');
- $('.js-confirm', this.$regeneratePublicSshKeyModal).off('click');
- $('.js-cancel', this.$regeneratePublicSshKeyModal).off('click');
}
}
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 9e031b03579..c43791f2426 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -1,15 +1,18 @@
<script>
-import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
+import { chartHeight, graphTypes, lineTypes } from '../../constants';
+import { makeDataSeries } from '~/helpers/monitor_helper';
let debouncedResize;
export default {
components: {
GlAreaChart,
+ GlChartSeriesLabel,
Icon,
},
inheritAttrs: false,
@@ -19,7 +22,6 @@ export default {
required: true,
validator(data) {
return (
- data.queries &&
Array.isArray(data.queries) &&
data.queries.filter(query => {
if (Array.isArray(query.result)) {
@@ -41,31 +43,58 @@ export default {
required: false,
default: () => [],
},
- alertData: {
- type: Object,
+ thresholds: {
+ type: Array,
required: false,
- default: () => ({}),
+ default: () => [],
},
},
data() {
return {
tooltip: {
title: '',
- content: '',
+ content: [],
isDeployment: false,
sha: '',
},
width: 0,
- height: 0,
- scatterSymbol: undefined,
+ height: chartHeight,
+ svgs: {},
+ primaryColor: null,
};
},
computed: {
chartData() {
- return this.graphData.queries.reduce((accumulator, query) => {
- accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []);
- return accumulator;
- }, {});
+ // Transforms & supplements query data to render appropriate labels & styles
+ // Input: [{ queryAttributes1 }, { queryAttributes2 }]
+ // Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
+ return this.graphData.queries.reduce((acc, query) => {
+ const { appearance } = query;
+ const lineType =
+ appearance && appearance.line && appearance.line.type
+ ? appearance.line.type
+ : lineTypes.default;
+ const lineWidth =
+ appearance && appearance.line && appearance.line.width
+ ? appearance.line.width
+ : undefined;
+
+ const series = makeDataSeries(query.result, {
+ name: this.formatLegendLabel(query),
+ lineStyle: {
+ type: lineType,
+ width: lineWidth,
+ },
+ areaStyle: {
+ opacity:
+ appearance && appearance.area && typeof appearance.area.opacity === 'number'
+ ? appearance.area.opacity
+ : undefined,
+ },
+ });
+
+ return acc.concat(series);
+ }, []);
},
chartOptions() {
return {
@@ -78,37 +107,40 @@ export default {
axisPointer: {
snap: true,
},
- nameTextStyle: {
- padding: [18, 0, 0, 0],
- },
},
yAxis: {
name: this.yAxisLabel,
axisLabel: {
formatter: value => value.toFixed(3),
},
- nameTextStyle: {
- padding: [0, 0, 36, 0],
- },
- },
- legend: {
- formatter: this.xAxisLabel,
},
series: this.scatterSeries,
+ dataZoom: this.dataZoomConfig,
};
},
+ dataZoomConfig() {
+ const handleIcon = this.svgs['scroll-handle'];
+
+ return handleIcon ? { handleIcon } : {};
+ },
earliestDatapoint() {
- return Object.values(this.chartData).reduce((acc, data) => {
- const [[timestamp]] = data.sort(([a], [b]) => {
- if (a < b) {
- return -1;
- }
- return a > b ? 1 : 0;
- });
+ return this.chartData.reduce((acc, series) => {
+ const { data } = series;
+ const { length } = data;
+ if (!length) {
+ return acc;
+ }
+
+ const [first] = data[0];
+ const [last] = data[length - 1];
+ const seriesEarliest = first < last ? first : last;
- return timestamp < acc || acc === null ? timestamp : acc;
+ return seriesEarliest < acc || acc === null ? seriesEarliest : acc;
}, null);
},
+ isMultiSeries() {
+ return this.tooltip.content.length > 1;
+ },
recentDeployments() {
return this.deploymentData.reduce((acc, deployment) => {
if (deployment.created_at >= this.earliestDatapoint) {
@@ -129,15 +161,15 @@ export default {
},
scatterSeries() {
return {
- type: 'scatter',
+ type: graphTypes.deploymentData,
data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
- symbol: this.scatterSymbol,
+ symbol: this.svgs.rocket,
symbolSize: 14,
+ itemStyle: {
+ color: this.primaryColor,
+ },
};
},
- xAxisLabel() {
- return this.graphData.queries.map(query => query.label).join(', ');
- },
yAxisLabel() {
return `${this.graphData.y_label}`;
},
@@ -151,35 +183,52 @@ export default {
created() {
debouncedResize = debounceByAnimationFrame(this.onResize);
window.addEventListener('resize', debouncedResize);
- this.getScatterSymbol();
+ this.setSvg('rocket');
+ this.setSvg('scroll-handle');
},
methods: {
+ formatLegendLabel(query) {
+ return `${query.label}`;
+ },
formatTooltipText(params) {
- const [seriesData] = params.seriesData;
- this.tooltip.isDeployment = seriesData.componentSubType === 'scatter';
this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
- if (this.tooltip.isDeployment) {
- const [deploy] = this.recentDeployments.filter(
- deployment => deployment.createdAt === seriesData.value[0],
- );
- this.tooltip.sha = deploy.sha.substring(0, 8);
- } else {
- this.tooltip.content = `${this.yAxisLabel} ${seriesData.value[1].toFixed(3)}`;
- }
+ this.tooltip.content = [];
+ params.seriesData.forEach(seriesData => {
+ if (seriesData.componentSubType === graphTypes.deploymentData) {
+ this.tooltip.isDeployment = true;
+ const [deploy] = this.recentDeployments.filter(
+ deployment => deployment.createdAt === seriesData.value[0],
+ );
+ this.tooltip.sha = deploy.sha.substring(0, 8);
+ } else {
+ const { seriesName, color } = seriesData;
+ // seriesData.value contains the chart's [x, y] value pair
+ // seriesData.value[1] is threfore the chart y value
+ const value = seriesData.value[1].toFixed(3);
+
+ this.tooltip.content.push({
+ name: seriesName,
+ value,
+ color,
+ });
+ }
+ });
},
- getScatterSymbol() {
- getSvgIconPathContent('rocket')
+ setSvg(name) {
+ getSvgIconPathContent(name)
.then(path => {
if (path) {
- this.scatterSymbol = `path://${path}`;
+ this.$set(this.svgs, name, `path://${path}`);
}
})
.catch(() => {});
},
+ onChartUpdated(chart) {
+ [this.primaryColor] = chart.getOption().color;
+ },
onResize() {
- const { width, height } = this.$refs.areaChart.$el.getBoundingClientRect();
+ const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
this.width = width;
- this.height = height;
},
},
};
@@ -197,23 +246,39 @@ export default {
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
- :thresholds="alertData"
+ :thresholds="thresholds"
:width="width"
:height="height"
+ @updated="onChartUpdated"
>
- <template slot="tooltipTitle">
- <div v-if="tooltip.isDeployment">
+ <template v-if="tooltip.isDeployment">
+ <template slot="tooltipTitle">
{{ __('Deployed') }}
- </div>
- {{ tooltip.title }}
- </template>
- <template slot="tooltipContent">
- <div v-if="tooltip.isDeployment" class="d-flex align-items-center">
+ </template>
+ <div slot="tooltipContent" class="d-flex align-items-center">
<icon name="commit" class="mr-2" />
{{ tooltip.sha }}
</div>
- <template v-else>
- {{ tooltip.content }}
+ </template>
+ <template v-else>
+ <template slot="tooltipTitle">
+ <div class="text-nowrap">
+ {{ tooltip.title }}
+ </div>
+ </template>
+ <template slot="tooltipContent">
+ <div
+ v-for="(content, key) in tooltip.content"
+ :key="key"
+ class="d-flex justify-content-between"
+ >
+ <gl-chart-series-label :color="isMultiSeries ? content.color : ''">
+ {{ content.name }}
+ </gl-chart-series-label>
+ <div class="prepend-left-32">
+ {{ content.value }}
+ </div>
+ </div>
</template>
</template>
</gl-area-chart>
diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
new file mode 100644
index 00000000000..b03a6ca1806
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
@@ -0,0 +1,37 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+
+export default {
+ components: {
+ GlSingleStat,
+ },
+ inheritAttrs: false,
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: Number,
+ required: true,
+ },
+ unit: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ valueWithUnit() {
+ return `${this.value}${this.unit}`;
+ },
+ },
+};
+</script>
+<template>
+ <div class="prometheus-graph col-12 col-lg-6">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ title }}</h5>
+ </div>
+ <gl-single-stat :value="valueWithUnit" :title="title" variant="success" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 895a57785bc..2314f7b80cf 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,12 +1,23 @@
<script>
+import {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlModalDirective,
+ GlLink,
+} from '@gitlab/ui';
+import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import Flash from '../../flash';
-import MonitoringService from '../services/monitoring_service';
+import '~/vue_shared/mixins/is_ee';
+import { getParameterValues } from '~/lib/utils/url_utility';
import MonitorAreaChart from './charts/area.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import MonitoringStore from '../stores/monitoring_store';
+import { timeWindows, timeWindowsKeyNames } from '../constants';
+import { getTimeDiff } from '../utils';
const sidebarAnimationDuration = 150;
let sidebarMutationObserver;
@@ -17,8 +28,21 @@ export default {
GraphGroup,
EmptyState,
Icon,
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlLink,
+ GlModal,
+ },
+ directives: {
+ GlModalDirective,
},
props: {
+ externalDashboardUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
hasMetrics: {
type: Boolean,
required: false,
@@ -82,21 +106,58 @@ export default {
type: String,
required: true,
},
+ customMetricsAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ customMetricsPath: {
+ type: String,
+ required: true,
+ },
+ validateQueryPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
- store: new MonitoringStore(),
state: 'gettingStarted',
- showEmptyState: true,
elWidth: 0,
+ selectedTimeWindow: '',
+ selectedTimeWindowKey: '',
+ formIsValid: null,
};
},
+ computed: {
+ canAddMetrics() {
+ return this.customMetricsAvailable && this.customMetricsPath.length;
+ },
+ ...mapState('monitoringDashboard', [
+ 'groups',
+ 'emptyState',
+ 'showEmptyState',
+ 'environments',
+ 'deploymentData',
+ ]),
+ },
created() {
- this.service = new MonitoringService({
+ this.setEndpoints({
metricsEndpoint: this.metricsEndpoint,
- deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
+ deploymentsEndpoint: this.deploymentEndpoint,
});
+
+ this.timeWindows = timeWindows;
+ this.selectedTimeWindowKey =
+ _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;
+
+ // Set default time window if the selectedTimeWindowKey is bogus
+ if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) {
+ this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours;
+ }
+
+ this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
},
beforeDestroy() {
if (sidebarMutationObserver) {
@@ -105,9 +166,10 @@ export default {
},
mounted() {
if (!this.hasMetrics) {
- this.state = 'gettingStarted';
+ this.setGettingStartedEmptyState();
} else {
- this.getGraphsData();
+ this.fetchData(getTimeDiff(this.selectedTimeWindow));
+
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
@@ -117,71 +179,135 @@ export default {
}
},
methods: {
- getGraphAlerts(graphId) {
- return this.alertData ? this.alertData[graphId] || {} : {};
- },
- getGraphsData() {
- this.state = 'loading';
- Promise.all([
- this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
- this.service
- .getDeploymentData()
- .then(data => this.store.storeDeploymentData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
- this.service
- .getEnvironmentsData()
- .then(data => this.store.storeEnvironmentsData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
- ])
- .then(() => {
- if (this.store.groups.length < 1) {
- this.state = 'noData';
- return;
- }
-
- this.showEmptyState = false;
- })
- .catch(() => {
- this.state = 'unableToConnect';
- });
+ ...mapActions('monitoringDashboard', [
+ 'fetchData',
+ 'setGettingStartedEmptyState',
+ 'setEndpoints',
+ ]),
+ getGraphAlerts(queries) {
+ if (!this.allAlerts) return {};
+ const metricIdsForChart = queries.map(q => q.metricId);
+ return _.pick(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId));
+ },
+ getGraphAlertValues(queries) {
+ return Object.values(this.getGraphAlerts(queries));
+ },
+ hideAddMetricModal() {
+ this.$refs.addMetricModal.hide();
},
onSidebarMutation() {
setTimeout(() => {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
},
+ setFormValidity(isValid) {
+ this.formIsValid = isValid;
+ },
+ submitCustomMetricsForm() {
+ this.$refs.customMetricsForm.submit();
+ },
+ activeTimeWindow(key) {
+ return this.timeWindows[key] === this.selectedTimeWindow;
+ },
+ setTimeWindowParameter(key) {
+ return `?time_window=${key}`;
+ },
+ },
+ addMetric: {
+ title: s__('Metrics|Add metric'),
+ modalId: 'add-metric',
},
};
</script>
<template>
- <div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default">
- <div class="environments d-flex align-items-center">
- {{ s__('Metrics|Environment') }}
- <div class="dropdown prepend-left-10">
- <button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
- <span>{{ currentEnvironmentName }}</span>
- <icon name="chevron-down" />
- </button>
- <div
- v-if="store.environmentsData.length > 0"
- class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"
- >
- <ul>
- <li v-for="environment in store.environmentsData" :key="environment.id">
- <a
- :href="environment.metrics_path"
- :class="{ 'is-active': environment.name == currentEnvironmentName }"
- class="dropdown-item"
- >{{ environment.name }}</a
+ <div v-if="!showEmptyState" class="prometheus-graphs">
+ <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between">
+ <div
+ v-if="environmentsEndpoint"
+ class="dropdowns d-flex align-items-center justify-content-between"
+ >
+ <div class="d-flex align-items-center">
+ <strong>{{ s__('Metrics|Environment') }}</strong>
+ <gl-dropdown
+ class="prepend-left-10 js-environments-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="currentEnvironmentName"
+ :disabled="environments.length === 0"
+ >
+ <gl-dropdown-item
+ v-for="environment in environments"
+ :key="environment.id"
+ :active="environment.name === currentEnvironmentName"
+ active-class="is-active"
+ >{{ environment.name }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ <div class="d-flex align-items-center prepend-left-8">
+ <strong>{{ s__('Metrics|Show last') }}</strong>
+ <gl-dropdown
+ class="prepend-left-10 js-time-window-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="selectedTimeWindow"
+ >
+ <gl-dropdown-item
+ v-for="(value, key) in timeWindows"
+ :key="key"
+ :active="activeTimeWindow(key)"
+ ><gl-link :href="setTimeWindowParameter(key)">{{ value }}</gl-link></gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ </div>
+ <div class="d-flex">
+ <div v-if="isEE && canAddMetrics">
+ <gl-button
+ v-gl-modal-directive="$options.addMetric.modalId"
+ class="js-add-metric-button text-success border-success"
+ >
+ {{ $options.addMetric.title }}
+ </gl-button>
+ <gl-modal
+ ref="addMetricModal"
+ :modal-id="$options.addMetric.modalId"
+ :title="$options.addMetric.title"
+ >
+ <form ref="customMetricsForm" :action="customMetricsPath" method="post">
+ <custom-metrics-form-fields
+ :validate-query-path="validateQueryPath"
+ form-operation="post"
+ @formValidation="setFormValidity"
+ />
+ </form>
+ <div slot="modal-footer">
+ <gl-button @click="hideAddMetricModal">
+ {{ __('Cancel') }}
+ </gl-button>
+ <gl-button
+ :disabled="!formIsValid"
+ variant="success"
+ @click="submitCustomMetricsForm"
>
- </li>
- </ul>
+ {{ __('Save changes') }}
+ </gl-button>
+ </div>
+ </gl-modal>
</div>
+ <gl-button
+ v-if="externalDashboardUrl.length"
+ class="js-external-dashboard-link prepend-left-8"
+ variant="primary"
+ :href="externalDashboardUrl"
+ target="_blank"
+ >
+ {{ __('View full dashboard') }}
+ <icon name="external-link" />
+ </gl-button>
</div>
</div>
<graph-group
- v-for="(groupData, index) in store.groups"
+ v-for="(groupData, index) in groups"
:key="index"
:name="groupData.group"
:show-panels="showPanels"
@@ -190,16 +316,24 @@ export default {
v-for="(graphData, graphIndex) in groupData.metrics"
:key="graphIndex"
:graph-data="graphData"
- :deployment-data="store.deploymentData"
- :alert-data="getGraphAlerts(graphData.id)"
+ :deployment-data="deploymentData"
+ :thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth"
group-id="monitor-area-chart"
- />
+ >
+ <alert-widget
+ v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
</graph-group>
</div>
<empty-state
v-else
- :selected-state="state"
+ :selected-state="emptyState"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
new file mode 100644
index 00000000000..26f1bf3f68d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -0,0 +1,29 @@
+import { __ } from '~/locale';
+
+export const chartHeight = 300;
+
+export const graphTypes = {
+ deploymentData: 'scatter',
+};
+
+export const lineTypes = {
+ default: 'solid',
+};
+
+export const timeWindows = {
+ thirtyMinutes: __('30 minutes'),
+ threeHours: __('3 hours'),
+ eightHours: __('8 hours'),
+ oneDay: __('1 day'),
+ threeDays: __('3 days'),
+ oneWeek: __('1 week'),
+};
+
+export const timeWindowsKeyNames = {
+ thirtyMinutes: 'thirtyMinutes',
+ threeHours: 'threeHours',
+ eightHours: 'eightHours',
+ oneDay: 'oneDay',
+ threeDays: 'threeDays',
+ oneWeek: 'oneWeek',
+};
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 9d78b5ea110..62c0f44c1e6 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,19 +1,22 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-import Dashboard from './components/dashboard.vue';
+import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
+import store from './stores';
-export default () => {
+export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
if (el && el.dataset) {
// eslint-disable-next-line no-new
new Vue({
el,
+ store,
render(createElement) {
return createElement(Dashboard, {
props: {
...el.dataset,
hasMetrics: parseBoolean(el.dataset.hasMetrics),
+ ...props,
},
});
},
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
deleted file mode 100644
index 24b4acaf6da..00000000000
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import axios from '../../lib/utils/axios_utils';
-import statusCodes from '../../lib/utils/http_status';
-import { backOff } from '../../lib/utils/common_utils';
-import { s__ } from '../../locale';
-
-const MAX_REQUESTS = 3;
-
-function backOffRequest(makeRequestCallback) {
- let requestCounter = 0;
- return backOff((next, stop) => {
- makeRequestCallback()
- .then(resp => {
- if (resp.status === statusCodes.NO_CONTENT) {
- requestCounter += 1;
- if (requestCounter < MAX_REQUESTS) {
- next();
- } else {
- stop(new Error('Failed to connect to the prometheus server'));
- }
- } else {
- stop(resp);
- }
- })
- .catch(stop);
- });
-}
-
-export default class MonitoringService {
- constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
- this.metricsEndpoint = metricsEndpoint;
- this.deploymentEndpoint = deploymentEndpoint;
- this.environmentsEndpoint = environmentsEndpoint;
- }
-
- getGraphsData() {
- return backOffRequest(() => axios.get(this.metricsEndpoint))
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.data) {
- throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
- }
- return response.data;
- });
- }
-
- getDeploymentData() {
- if (!this.deploymentEndpoint) {
- return Promise.resolve([]);
- }
- return backOffRequest(() => axios.get(this.deploymentEndpoint))
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.deployments) {
- throw new Error(
- s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
- );
- }
- return response.deployments;
- });
- }
-
- getEnvironmentsData() {
- return axios
- .get(this.environmentsEndpoint)
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.environments) {
- throw new Error(
- s__('Metrics|There was an error fetching the environments data, please try again'),
- );
- }
- return response.environments;
- });
- }
-}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
new file mode 100644
index 00000000000..63c23e8449d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -0,0 +1,117 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import statusCodes from '../../lib/utils/http_status';
+import { backOff } from '../../lib/utils/common_utils';
+import { s__, __ } from '../../locale';
+
+const MAX_REQUESTS = 3;
+
+function backOffRequest(makeRequestCallback) {
+ let requestCounter = 0;
+ return backOff((next, stop) => {
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ requestCounter += 1;
+ if (requestCounter < MAX_REQUESTS) {
+ next();
+ } else {
+ stop(new Error(__('Failed to connect to the prometheus server')));
+ }
+ } else {
+ stop(resp);
+ }
+ })
+ .catch(stop);
+ });
+}
+
+export const setGettingStartedEmptyState = ({ commit }) => {
+ commit(types.SET_GETTING_STARTED_EMPTY_STATE);
+};
+
+export const setEndpoints = ({ commit }, endpoints) => {
+ commit(types.SET_ENDPOINTS, endpoints);
+};
+
+export const requestMetricsData = ({ commit }) => commit(types.REQUEST_METRICS_DATA);
+export const receiveMetricsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_METRICS_DATA_SUCCESS, data);
+export const receiveMetricsDataFailure = ({ commit }, error) =>
+ commit(types.RECEIVE_METRICS_DATA_FAILURE, error);
+export const receiveDeploymentsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data);
+export const receiveDeploymentsDataFailure = ({ commit }) =>
+ commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE);
+export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data);
+export const receiveEnvironmentsDataFailure = ({ commit }) =>
+ commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
+
+export const fetchData = ({ dispatch }, params) => {
+ dispatch('fetchMetricsData', params);
+ dispatch('fetchDeploymentsData');
+ dispatch('fetchEnvironmentsData');
+};
+
+export const fetchMetricsData = ({ state, dispatch }, params) => {
+ dispatch('requestMetricsData');
+
+ return backOffRequest(() => axios.get(state.metricsEndpoint, { params }))
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.data || !response.success) {
+ dispatch('receiveMetricsDataFailure', null);
+ createFlash(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
+ }
+ dispatch('receiveMetricsDataSuccess', response.data);
+ })
+ .catch(error => {
+ dispatch('receiveMetricsDataFailure', error);
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ });
+};
+
+export const fetchDeploymentsData = ({ state, dispatch }) => {
+ if (!state.deploymentEndpoint) {
+ return Promise.resolve([]);
+ }
+ return backOffRequest(() => axios.get(state.deploymentEndpoint))
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.deployments) {
+ createFlash(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
+ }
+
+ dispatch('receiveDeploymentsDataSuccess', response.deployments);
+ })
+ .catch(() => {
+ dispatch('receiveDeploymentsDataFailure');
+ createFlash(s__('Metrics|There was an error getting deployment information.'));
+ });
+};
+
+export const fetchEnvironmentsData = ({ state, dispatch }) => {
+ if (!state.environmentsEndpoint) {
+ return Promise.resolve([]);
+ }
+ return axios
+ .get(state.environmentsEndpoint)
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.environments) {
+ createFlash(
+ s__('Metrics|There was an error fetching the environments data, please try again'),
+ );
+ }
+ dispatch('receiveEnvironmentsDataSuccess', response.environments);
+ })
+ .catch(() => {
+ dispatch('receiveEnvironmentsDataFailure');
+ createFlash(s__('Metrics|There was an error getting environments information.'));
+ });
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/index.js b/app/assets/javascripts/monitoring/stores/index.js
new file mode 100644
index 00000000000..d58398c54ae
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ modules: {
+ monitoringDashboard: {
+ namespaced: true,
+ actions,
+ mutations,
+ state,
+ },
+ },
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
deleted file mode 100644
index 70635059bd9..00000000000
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import _ from 'underscore';
-
-function sortMetrics(metrics) {
- return _.chain(metrics)
- .sortBy('title')
- .sortBy('weight')
- .value();
-}
-
-function checkQueryEmptyData(query) {
- return {
- ...query,
- result: query.result.filter(timeSeries => {
- const newTimeSeries = timeSeries;
- const hasValue = series =>
- !Number.isNaN(series[1]) && (series[1] !== null || series[1] !== undefined);
- const hasNonNullValue = timeSeries.values.find(hasValue);
-
- newTimeSeries.values = hasNonNullValue ? newTimeSeries.values : [];
-
- return newTimeSeries.values.length > 0;
- }),
- };
-}
-
-function removeTimeSeriesNoData(queries) {
- return queries.reduce((series, query) => series.concat(checkQueryEmptyData(query)), []);
-}
-
-function normalizeMetrics(metrics) {
- return metrics.map(metric => {
- const queries = metric.queries.map(query => ({
- ...query,
- result: query.result.map(result => ({
- ...result,
- values: result.values.map(([timestamp, value]) => [
- new Date(timestamp * 1000).toISOString(),
- Number(value),
- ]),
- })),
- }));
-
- return {
- ...metric,
- queries: removeTimeSeriesNoData(queries),
- };
- });
-}
-
-export default class MonitoringStore {
- constructor() {
- this.groups = [];
- this.deploymentData = [];
- this.environmentsData = [];
- }
-
- storeMetrics(groups = []) {
- this.groups = groups.map(group => ({
- ...group,
- metrics: normalizeMetrics(sortMetrics(group.metrics)),
- }));
- }
-
- storeDeploymentData(deploymentData = []) {
- this.deploymentData = deploymentData;
- }
-
- storeEnvironmentsData(environmentsData = []) {
- this.environmentsData = environmentsData.filter(environment => !!environment.last_deployment);
- }
-
- getMetricsCount() {
- return this.groups.reduce((count, group) => count + group.metrics.length, 0);
- }
-}
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
new file mode 100644
index 00000000000..3fd9e07fa8b
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -0,0 +1,12 @@
+export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA';
+export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS';
+export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE';
+export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
+export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
+export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE';
+export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
+export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
+export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE';
+export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
+export const SET_ENDPOINTS = 'SET_ENDPOINTS';
+export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
new file mode 100644
index 00000000000..c1779333d75
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -0,0 +1,45 @@
+import * as types from './mutation_types';
+import { normalizeMetrics, sortMetrics } from './utils';
+
+export default {
+ [types.REQUEST_METRICS_DATA](state) {
+ state.emptyState = 'loading';
+ state.showEmptyState = true;
+ },
+ [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
+ state.groups = groupData.map(group => ({
+ ...group,
+ metrics: normalizeMetrics(sortMetrics(group.metrics)),
+ }));
+
+ if (!state.groups.length) {
+ state.emptyState = 'noData';
+ } else {
+ state.showEmptyState = false;
+ }
+ },
+ [types.RECEIVE_METRICS_DATA_FAILURE](state, error) {
+ state.emptyState = error ? 'unableToConnect' : 'noData';
+ state.showEmptyState = true;
+ },
+ [types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](state, deployments) {
+ state.deploymentData = deployments;
+ },
+ [types.RECEIVE_DEPLOYMENTS_DATA_FAILURE](state) {
+ state.deploymentData = [];
+ },
+ [types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, environments) {
+ state.environments = environments;
+ },
+ [types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
+ state.environments = [];
+ },
+ [types.SET_ENDPOINTS](state, endpoints) {
+ state.metricsEndpoint = endpoints.metricsEndpoint;
+ state.environmentsEndpoint = endpoints.environmentsEndpoint;
+ state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
+ },
+ [types.SET_GETTING_STARTED_EMPTY_STATE](state) {
+ state.emptyState = 'gettingStarted';
+ },
+};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
new file mode 100644
index 00000000000..5103122612a
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -0,0 +1,12 @@
+export default () => ({
+ hasMetrics: false,
+ showPanels: true,
+ metricsEndpoint: null,
+ environmentsEndpoint: null,
+ deploymentsEndpoint: null,
+ emptyState: 'gettingStarted',
+ showEmptyState: true,
+ groups: [],
+ deploymentData: [],
+ environments: [],
+});
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
new file mode 100644
index 00000000000..9216554ecbf
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -0,0 +1,83 @@
+import _ from 'underscore';
+
+function checkQueryEmptyData(query) {
+ return {
+ ...query,
+ result: query.result.filter(timeSeries => {
+ const newTimeSeries = timeSeries;
+ const hasValue = series =>
+ !Number.isNaN(series[1]) && (series[1] !== null || series[1] !== undefined);
+ const hasNonNullValue = timeSeries.values.find(hasValue);
+
+ newTimeSeries.values = hasNonNullValue ? newTimeSeries.values : [];
+
+ return newTimeSeries.values.length > 0;
+ }),
+ };
+}
+
+function removeTimeSeriesNoData(queries) {
+ return queries.reduce((series, query) => series.concat(checkQueryEmptyData(query)), []);
+}
+
+// Metrics and queries are currently stored 1:1, so `queries` is an array of length one.
+// We want to group queries onto a single chart by title & y-axis label.
+// This function will no longer be required when metrics:queries are 1:many,
+// though there is no consequence if the function stays in use.
+// @param metrics [Array<Object>]
+// Ex) [
+// { id: 1, title: 'title', y_label: 'MB', queries: [{ ...query1Attrs }] },
+// { id: 2, title: 'title', y_label: 'MB', queries: [{ ...query2Attrs }] },
+// { id: 3, title: 'new title', y_label: 'MB', queries: [{ ...query3Attrs }] }
+// ]
+// @return [Array<Object>]
+// Ex) [
+// { title: 'title', y_label: 'MB', queries: [{ metricId: 1, ...query1Attrs },
+// { metricId: 2, ...query2Attrs }] },
+// { title: 'new title', y_label: 'MB', queries: [{ metricId: 3, ...query3Attrs }]}
+// ]
+function groupQueriesByChartInfo(metrics) {
+ const metricsByChart = metrics.reduce((accumulator, metric) => {
+ const { queries, ...chart } = metric;
+ const metricId = chart.id ? chart.id.toString() : null;
+
+ const chartKey = `${chart.title}|${chart.y_label}`;
+ accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] };
+
+ queries.forEach(queryAttrs => accumulator[chartKey].queries.push({ metricId, ...queryAttrs }));
+
+ return accumulator;
+ }, {});
+
+ return Object.values(metricsByChart);
+}
+
+export const sortMetrics = metrics =>
+ _.chain(metrics)
+ .sortBy('title')
+ .sortBy('weight')
+ .value();
+
+export const normalizeMetrics = metrics => {
+ const groupedMetrics = groupQueriesByChartInfo(metrics);
+
+ return groupedMetrics.map(metric => {
+ const queries = metric.queries.map(query => ({
+ ...query,
+ // custom metrics do not require a label, so we should ensure this attribute is defined
+ label: query.label || metric.y_label,
+ result: query.result.map(result => ({
+ ...result,
+ values: result.values.map(([timestamp, value]) => [
+ new Date(timestamp * 1000).toISOString(),
+ Number(value),
+ ]),
+ })),
+ }));
+
+ return {
+ ...metric,
+ queries: removeTimeSeriesNoData(queries),
+ };
+ });
+};
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
new file mode 100644
index 00000000000..ef309c8a398
--- /dev/null
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -0,0 +1,33 @@
+import { timeWindows } from './constants';
+
+/**
+ * method that converts a predetermined time window to minutes
+ * defaults to 8 hours as the default option
+ * @param {String} timeWindow - The time window to convert to minutes
+ * @returns {number} The time window in minutes
+ */
+const getTimeDifferenceSeconds = timeWindow => {
+ switch (timeWindow) {
+ case timeWindows.thirtyMinutes:
+ return 60 * 30;
+ case timeWindows.threeHours:
+ return 60 * 60 * 3;
+ case timeWindows.oneDay:
+ return 60 * 60 * 24 * 1;
+ case timeWindows.threeDays:
+ return 60 * 60 * 24 * 3;
+ case timeWindows.oneWeek:
+ return 60 * 60 * 24 * 7 * 1;
+ default:
+ return 60 * 60 * 8;
+ }
+};
+
+export const getTimeDiff = selectedTimeWindow => {
+ const end = Date.now() / 1000; // convert milliseconds to seconds
+ const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
+
+ return { start, end };
+};
+
+export default {};
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 9e99aa4f724..8eccba07c38 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -1,11 +1,9 @@
-import $ from 'jquery';
import Vue from 'vue';
-import { mapActions, mapState, mapGetters } from 'vuex';
+import store from 'ee_else_ce/mr_notes/stores';
+import initNotesApp from './init_notes';
import initDiffsApp from '../diffs';
-import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
import initDiscussionFilters from '../notes/discussion_filters';
-import store from './stores';
import MergeRequest from '../merge_request';
import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
@@ -18,68 +16,7 @@ export default function initMrNotes() {
action: mrShowNode.dataset.mrAction,
});
- // eslint-disable-next-line no-new
- new Vue({
- el: '#js-vue-mr-discussions',
- name: 'MergeRequestDiscussions',
- components: {
- notesApp,
- },
- store,
- data() {
- const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
- const noteableData = JSON.parse(notesDataset.noteableData);
- noteableData.noteableType = notesDataset.noteableType;
- noteableData.targetType = notesDataset.targetType;
-
- return {
- noteableData,
- currentUserData: JSON.parse(notesDataset.currentUserData),
- notesData: JSON.parse(notesDataset.notesData),
- helpPagePath: notesDataset.helpPagePath,
- };
- },
- computed: {
- ...mapGetters(['discussionTabCounter']),
- ...mapState({
- activeTab: state => state.page.activeTab,
- }),
- },
- watch: {
- discussionTabCounter() {
- this.updateDiscussionTabCounter();
- },
- },
- created() {
- this.setActiveTab(window.mrTabs.getCurrentAction());
- },
- mounted() {
- this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge');
- $(document).on('visibilitychange', this.updateDiscussionTabCounter);
- window.mrTabs.eventHub.$on('MergeRequestTabChange', this.setActiveTab);
- },
- beforeDestroy() {
- $(document).off('visibilitychange', this.updateDiscussionTabCounter);
- window.mrTabs.eventHub.$off('MergeRequestTabChange', this.setActiveTab);
- },
- methods: {
- ...mapActions(['setActiveTab']),
- updateDiscussionTabCounter() {
- this.notesCountBadge.text(this.discussionTabCounter);
- },
- },
- render(createElement) {
- return createElement('notes-app', {
- props: {
- noteableData: this.noteableData,
- notesData: this.notesData,
- userData: this.currentUserData,
- shouldShow: this.activeTab === 'show',
- helpPagePath: this.helpPagePath,
- },
- });
- },
- });
+ initNotesApp();
// eslint-disable-next-line no-new
new Vue({
diff --git a/app/assets/javascripts/mr_notes/init_notes.js b/app/assets/javascripts/mr_notes/init_notes.js
new file mode 100644
index 00000000000..842a209a545
--- /dev/null
+++ b/app/assets/javascripts/mr_notes/init_notes.js
@@ -0,0 +1,70 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import store from 'ee_else_ce/mr_notes/stores';
+import notesApp from '../notes/components/notes_app.vue';
+
+export default () => {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#js-vue-mr-discussions',
+ name: 'MergeRequestDiscussions',
+ components: {
+ notesApp,
+ },
+ store,
+ data() {
+ const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
+ const noteableData = JSON.parse(notesDataset.noteableData);
+ noteableData.noteableType = notesDataset.noteableType;
+ noteableData.targetType = notesDataset.targetType;
+
+ return {
+ noteableData,
+ currentUserData: JSON.parse(notesDataset.currentUserData),
+ notesData: JSON.parse(notesDataset.notesData),
+ helpPagePath: notesDataset.helpPagePath,
+ };
+ },
+ computed: {
+ ...mapGetters(['discussionTabCounter']),
+ ...mapState({
+ activeTab: state => state.page.activeTab,
+ }),
+ },
+ watch: {
+ discussionTabCounter() {
+ this.updateDiscussionTabCounter();
+ },
+ },
+ created() {
+ this.setActiveTab(window.mrTabs.getCurrentAction());
+ },
+ mounted() {
+ this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge');
+ $(document).on('visibilitychange', this.updateDiscussionTabCounter);
+ window.mrTabs.eventHub.$on('MergeRequestTabChange', this.setActiveTab);
+ },
+ beforeDestroy() {
+ $(document).off('visibilitychange', this.updateDiscussionTabCounter);
+ window.mrTabs.eventHub.$off('MergeRequestTabChange', this.setActiveTab);
+ },
+ methods: {
+ ...mapActions(['setActiveTab']),
+ updateDiscussionTabCounter() {
+ this.notesCountBadge.text(this.discussionTabCounter);
+ },
+ },
+ render(createElement) {
+ return createElement('notes-app', {
+ props: {
+ noteableData: this.noteableData,
+ notesData: this.notesData,
+ userData: this.currentUserData,
+ shouldShow: this.activeTab === 'show',
+ helpPagePath: this.helpPagePath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/mr_notes/stores/getters.js b/app/assets/javascripts/mr_notes/stores/getters.js
index b10e9f9f9f1..e48cfcd9564 100644
--- a/app/assets/javascripts/mr_notes/stores/getters.js
+++ b/app/assets/javascripts/mr_notes/stores/getters.js
@@ -1,5 +1,5 @@
export default {
isLoggedIn(state, getters) {
- return !!getters.getUserData.id;
+ return Boolean(getters.getUserData.id);
},
};
diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue
new file mode 100644
index 00000000000..8e2d8fa816a
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue
@@ -0,0 +1,110 @@
+<script>
+import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import Icon from '../../vue_shared/components/icon.vue';
+import CiIcon from '../../vue_shared/components/ci_icon.vue';
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import query from '../queries/merge_request.graphql';
+import { mrStates, humanMRStates } from '../constants';
+
+export default {
+ name: 'MRPopover',
+ components: {
+ GlPopover,
+ GlSkeletonLoading,
+ Icon,
+ CiIcon,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ mergeRequestIID: {
+ type: String,
+ required: true,
+ },
+ mergeRequestTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ mergeRequest: {},
+ };
+ },
+ computed: {
+ detailedStatus() {
+ return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus;
+ },
+ formattedTime() {
+ return this.timeFormated(this.mergeRequest.createdAt);
+ },
+ statusBoxClass() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return 'status-box-mr-merged';
+ case mrStates.closed:
+ return 'status-box-closed';
+ default:
+ return 'status-box-open';
+ }
+ },
+ stateHumanName() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return humanMRStates.merged;
+ case mrStates.closed:
+ return humanMRStates.closed;
+ default:
+ return humanMRStates.open;
+ }
+ },
+ showDetails() {
+ return Object.keys(this.mergeRequest).length > 0;
+ },
+ },
+ apollo: {
+ mergeRequest: {
+ query,
+ update: data => data.project.mergeRequest,
+ variables() {
+ const { projectPath, mergeRequestIID } = this;
+
+ return {
+ projectPath,
+ mergeRequestIID,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <div class="mr-popover">
+ <div v-if="$apollo.loading">
+ <gl-skeleton-loading :lines="1" class="animation-container-small mt-1" />
+ </div>
+ <div v-else-if="showDetails" class="d-flex align-items-center justify-content-between">
+ <div class="d-inline-flex align-items-center">
+ <div :class="`issuable-status-box status-box ${statusBoxClass}`">
+ {{ stateHumanName }}
+ </div>
+ <span class="text-secondary">Opened <time v-text="formattedTime"></time></span>
+ </div>
+ <ci-icon v-if="detailedStatus" :status="detailedStatus" />
+ </div>
+ <h5 class="my-2">{{ mergeRequestTitle }}</h5>
+ <div class="text-secondary">
+ {{ `${projectPath}!${mergeRequestIID}` }}
+ </div>
+ </div>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/mr_popover/constants.js b/app/assets/javascripts/mr_popover/constants.js
new file mode 100644
index 00000000000..c13c417cc18
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/constants.js
@@ -0,0 +1,12 @@
+import { __ } from '~/locale';
+
+export const mrStates = {
+ merged: 'merged',
+ closed: 'closed',
+};
+
+export const humanMRStates = {
+ merged: __('Merged'),
+ closed: __('Closed'),
+ open: __('Open'),
+};
diff --git a/app/assets/javascripts/mr_popover/index.js b/app/assets/javascripts/mr_popover/index.js
new file mode 100644
index 00000000000..18c0e201300
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/index.js
@@ -0,0 +1,67 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import MRPopover from './components/mr_popover.vue';
+import createDefaultClient from '~/lib/graphql';
+
+let renderedPopover;
+let renderFn;
+
+const handleUserPopoverMouseOut = ({ target }) => {
+ target.removeEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ if (renderFn) {
+ clearTimeout(renderFn);
+ }
+ if (renderedPopover) {
+ renderedPopover.$destroy();
+ renderedPopover = null;
+ }
+};
+
+/**
+ * Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes.
+ * loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover
+ */
+const handleMRPopoverMount = ({ apolloProvider, projectPath, mrTitle, iid }) => ({ target }) => {
+ // Add listener to actually remove it again
+ target.addEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ renderFn = setTimeout(() => {
+ const MRPopoverComponent = Vue.extend(MRPopover);
+ renderedPopover = new MRPopoverComponent({
+ propsData: {
+ target,
+ projectPath,
+ mergeRequestIID: iid,
+ mergeRequestTitle: mrTitle,
+ },
+ apolloProvider,
+ });
+
+ renderedPopover.$mount();
+ }, 200); // 200ms delay so not every mouseover triggers Popover + API Call
+};
+
+export default elements => {
+ const mrLinks = elements || [...document.querySelectorAll('.gfm-merge_request')];
+ if (mrLinks.length > 0) {
+ Vue.use(VueApollo);
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+ const listenerAddedAttr = 'data-mr-listener-added';
+
+ mrLinks.forEach(el => {
+ const { projectPath, mrTitle, iid } = el.dataset;
+
+ if (!el.getAttribute(listenerAddedAttr) && projectPath && mrTitle && iid) {
+ el.addEventListener(
+ 'mouseenter',
+ handleMRPopoverMount({ apolloProvider, projectPath, mrTitle, iid }),
+ );
+ el.setAttribute(listenerAddedAttr, true);
+ }
+ });
+ }
+};
diff --git a/app/assets/javascripts/mr_popover/queries/merge_request.graphql b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
new file mode 100644
index 00000000000..0bb9bc03bc7
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
@@ -0,0 +1,14 @@
+query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $mergeRequestIID) {
+ createdAt
+ state
+ headPipeline {
+ detailedStatus {
+ icon
+ group
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index ee1a5274ff7..03d349ac714 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -4,6 +4,7 @@ import $ from 'jquery';
import Api from './api';
import { mergeUrlParams } from './lib/utils/url_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
+import { __ } from './locale';
export default class NamespaceSelect {
constructor(opts) {
@@ -29,7 +30,7 @@ export default class NamespaceSelect {
return Api.namespaces(term, function(namespaces) {
if (isFilter) {
const anyNamespace = {
- text: 'Any namespace',
+ text: __('Any namespace'),
id: null,
};
namespaces.unshift(anyNamespace);
diff --git a/app/assets/javascripts/namespaces/leave_by_url.js b/app/assets/javascripts/namespaces/leave_by_url.js
new file mode 100644
index 00000000000..b817d38960c
--- /dev/null
+++ b/app/assets/javascripts/namespaces/leave_by_url.js
@@ -0,0 +1,22 @@
+import Flash from '~/flash';
+import { __, sprintf } from '~/locale';
+import { getParameterByName } from '~/lib/utils/common_utils';
+
+const PARAMETER_NAME = 'leave';
+const LEAVE_LINK_SELECTOR = '.js-leave-link';
+
+export default function leaveByUrl(namespaceType) {
+ if (!namespaceType) throw new Error('namespaceType not provided');
+
+ const param = getParameterByName(PARAMETER_NAME);
+ if (!param) return;
+
+ const leaveLink = document.querySelector(LEAVE_LINK_SELECTOR);
+ if (leaveLink) {
+ leaveLink.click();
+ } else {
+ Flash(
+ sprintf(__('You do not have permission to leave this %{namespaceType}.'), { namespaceType }),
+ );
+ }
+}
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index c5ae7e7ee10..b59ddd0d57a 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -20,12 +20,20 @@ export default {
required: true,
},
},
- data() {
- return {
- outputType: '',
- };
- },
methods: {
+ outputType(output) {
+ if (output.text) {
+ return 'text/plain';
+ } else if (output.data['image/png']) {
+ return 'image/png';
+ } else if (output.data['text/html']) {
+ return 'text/html';
+ } else if (output.data['image/svg+xml']) {
+ return 'image/svg+xml';
+ }
+
+ return 'text/plain';
+ },
dataForType(output, type) {
let data = output.data[type];
@@ -39,20 +47,13 @@ export default {
if (output.text) {
return CodeOutput;
} else if (output.data['image/png']) {
- this.outputType = 'image/png';
-
return ImageOutput;
} else if (output.data['text/html']) {
- this.outputType = 'text/html';
-
return HtmlOutput;
} else if (output.data['image/svg+xml']) {
- this.outputType = 'image/svg+xml';
-
return HtmlOutput;
}
- this.outputType = 'text/plain';
return CodeOutput;
},
rawCode(output) {
@@ -60,7 +61,7 @@ export default {
return output.text.join('');
}
- return this.dataForType(output, this.outputType);
+ return this.dataForType(output, this.outputType(output));
},
},
};
@@ -73,7 +74,7 @@ export default {
v-for="(output, index) in outputs"
:key="index"
type="output"
- :output-type="outputType"
+ :output-type="outputType(output)"
:count="count"
:index="index"
:raw-code="rawCode(output)"
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index c9c01354333..a7156bd2406 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -7,12 +7,16 @@ no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
+/*
+old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
+ */
+
import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import Autosize from 'autosize';
-import 'vendor/jquery.caret'; // required by jquery.atwho
-import 'vendor/jquery.atwho';
+import 'jquery.caret'; // required by at.js
+import 'at.js';
import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight';
@@ -35,6 +39,7 @@ import {
} from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
+import { sprintf, s__, __ } from './locale';
window.autosize = Autosize;
@@ -253,7 +258,7 @@ export default class Notes {
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) {
if ($textarea.val() !== '') {
- if (!window.confirm('Are you sure you want to cancel creating this comment?')) {
+ if (!window.confirm(__('Are you sure you want to cancel creating this comment?'))) {
return;
}
}
@@ -265,7 +270,7 @@ export default class Notes {
originalText = $textarea.closest('form').data('originalNote');
newText = $textarea.val();
if (originalText !== newText) {
- if (!window.confirm('Are you sure you want to cancel editing this comment?')) {
+ if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) {
return;
}
}
@@ -506,7 +511,7 @@ export default class Notes {
var contentContainerClass =
'.' +
$notes
- .closest('.notes_content')
+ .closest('.notes-content')
.attr('class')
.split(' ')
.join('.');
@@ -636,7 +641,7 @@ export default class Notes {
this.glForm = new GLForm(form, enableGFM);
textarea = form.find('.js-note-text');
key = [
- 'Note',
+ s__('NoteForm|Note'),
form.find('#note_noteable_type').val(),
form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(),
@@ -670,7 +675,9 @@ export default class Notes {
formParentTimeline = $form.closest('.discussion-notes').find('.notes');
}
return this.addFlash(
- 'Your comment could not be submitted! Please check your network connection and try again.',
+ __(
+ 'Your comment could not be submitted! Please check your network connection and try again.',
+ ),
'alert',
formParentTimeline.get(0),
);
@@ -679,7 +686,7 @@ export default class Notes {
updateNoteError($parentTimeline) {
// eslint-disable-next-line no-new
new Flash(
- 'Your comment could not be updated! Please check your network connection and try again.',
+ __('Your comment could not be updated! Please check your network connection and try again.'),
);
}
@@ -983,6 +990,14 @@ export default class Notes {
form.find('#note_position').val(dataHolder.attr('data-position'));
form
+ .prepend(
+ `<div class="avatar-note-form-holder"><div class="content"><a href="${escape(
+ gon.current_username,
+ )}" class="user-avatar-link d-none d-sm-block"><img class="avatar s40" src="${encodeURI(
+ gon.current_user_avatar_url,
+ )}" alt="${escape(gon.current_user_fullname)}" /></a></div></div>`,
+ )
+ .append('</div>')
.find('.js-close-discussion-note-form')
.show()
.removeClass('hide');
@@ -1018,6 +1033,9 @@ export default class Notes {
target: $link,
lineType: link.dataset.lineType,
showReplyInput,
+ currentUsername: gon.current_username,
+ currentUserAvatar: gon.current_user_avatar_url,
+ currentUserFullname: gon.current_user_fullname,
});
}
@@ -1046,7 +1064,15 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
- toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
+ toggleDiffNote({
+ target,
+ lineType,
+ forceShow,
+ showReplyInput = false,
+ currentUsername,
+ currentUserAvatar,
+ currentUserFullname,
+ }) {
var $link,
addForm,
hasNotes,
@@ -1069,14 +1095,14 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_content" colspan="3"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes-content" colspan="3"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes-content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes-content parallel new"><div class="content"></div></td></tr>';
}
- const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
+ const notesContentSelector = `.notes-content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
if (hasNotes && showReplyInput) {
@@ -1258,12 +1284,19 @@ export default class Notes {
putConflictEditWarningInPlace(noteEntity, $note) {
if ($note.find('.js-conflict-edit-warning').length === 0) {
+ const open_link = `<a href="#note_${
+ noteEntity.id
+ }" target="_blank" rel="noopener noreferrer">`;
const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
- This comment has changed since you started editing, please review the
- <a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
- updated comment
- </a>
- to ensure information is not lost
+ ${sprintf(
+ s__(
+ 'Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost',
+ ),
+ {
+ open_link,
+ close_link: '</a>',
+ },
+ )}
</div>`);
$alert.insertAfter($note.find('.note-text'));
}
@@ -1491,13 +1524,15 @@ export default class Notes {
if (executedCommands && executedCommands.length) {
if (executedCommands.length > 1) {
- tempFormContent = 'Applying multiple commands';
+ tempFormContent = __('Applying multiple commands');
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
- tempFormContent = `Applying command to ${commandDescription}`;
+ tempFormContent = sprintf(__('Applying command to %{commandDescription}'), {
+ commandDescription,
+ });
}
} else {
- tempFormContent = 'Applying command';
+ tempFormContent = __('Applying command');
}
return tempFormContent;
@@ -1530,7 +1565,9 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
- <span class="d-none d-sm-inline-block">${_.escape(currentUsername)}</span>
+ <span class="d-none d-sm-inline-block bold">${_.escape(
+ currentUsername,
+ )}</span>
<span class="note-headline-light">${_.escape(currentUsername)}</span>
</a>
</div>
@@ -1817,7 +1854,9 @@ export default class Notes {
$editingNote
.find('.note-headline-meta a')
.html(
- '<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>',
+ `<i class="fa fa-spinner fa-spin" aria-label="${__(
+ 'Comment is being updated',
+ )}" aria-hidden="true"></i>`,
);
// Make request to update comment on server
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 1d6cb9485f7..075c28e8d07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -11,6 +11,7 @@ import {
capitalizeFirstCharacter,
convertToCamelCase,
splitCamelCase,
+ slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
@@ -115,8 +116,11 @@ export default {
author() {
return this.getUserData;
},
- canUpdateIssue() {
- return this.getNoteableData.current_user.can_update;
+ canToggleIssueState() {
+ return (
+ this.getNoteableData.current_user.can_update &&
+ this.getNoteableData.state !== constants.MERGED
+ );
},
endpoint() {
return this.getNoteableData.create_note_path;
@@ -126,6 +130,9 @@ export default {
? 'merge request'
: 'issue';
},
+ trackingLabel() {
+ return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
+ },
},
watch: {
note(newNote) {
@@ -330,6 +337,8 @@ Please check your network connection and try again.`;
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
@@ -344,6 +353,7 @@ Please check your network connection and try again.`;
ref="textarea"
slot="textarea"
v-model="note"
+ dir="auto"
:disabled="isSubmitting"
name="note[note]"
class="note-textarea js-vue-comment-form js-note-text
@@ -367,6 +377,8 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
+ :data-track-label="trackingLabel"
+ data-track-event="click_button"
@click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
@@ -415,7 +427,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</div>
<loading-button
- v-if="canUpdateIssue"
+ v-if="canToggleIssueState"
:loading="isToggleStateButtonLoading"
:container-class="[
actionButtonClassNames,
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index d8947e8ca50..b95835ed10a 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -72,8 +72,8 @@ export default {
:can-current-user-fork="false"
:expanded="!discussion.diff_file.viewer.collapsed"
/>
- <div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code">
- <table>
+ <div v-if="isTextFile" class="diff-content">
+ <table class="code js-syntax-highlight" :class="$options.userColorSchemeClass">
<template v-if="hasTruncatedDiffLines">
<tr
v-for="line in discussion.truncated_diff_lines"
@@ -81,8 +81,8 @@ export default {
:key="line.line_code"
class="line_holder"
>
- <td class="diff-line-num old_line">{{ line.old_line }}</td>
- <td class="diff-line-num new_line">{{ line.new_line }}</td>
+ <td :class="line.type" class="diff-line-num old_line">{{ line.old_line }}</td>
+ <td :class="line.type" class="diff-line-num new_line">{{ line.new_line }}</td>
<td :class="line.type" class="line_content" v-html="line.rich_text"></td>
</tr>
</template>
@@ -105,7 +105,7 @@ export default {
</td>
</tr>
<tr class="notes_holder">
- <td class="notes_content" colspan="3"><slot></slot></td>
+ <td class="notes-content" colspan="3"><slot></slot></td>
</tr>
</table>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
new file mode 100644
index 00000000000..22cca756ef6
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -0,0 +1,58 @@
+<script>
+import ReplyPlaceholder from './discussion_reply_placeholder.vue';
+import ResolveDiscussionButton from './discussion_resolve_button.vue';
+import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
+import JumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
+
+export default {
+ name: 'DiscussionActions',
+ components: {
+ ReplyPlaceholder,
+ ResolveDiscussionButton,
+ ResolveWithIssueButton,
+ JumpToNextDiscussionButton,
+ },
+ props: {
+ discussion: {
+ type: Object,
+ required: true,
+ },
+ isResolving: {
+ type: Boolean,
+ required: true,
+ },
+ resolveButtonTitle: {
+ type: String,
+ required: true,
+ },
+ resolveWithIssuePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ shouldShowJumpToNextDiscussion: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="discussion-with-resolve-btn">
+ <reply-placeholder class="qa-discussion-reply" @onClick="$emit('showReplyForm')" />
+ <resolve-discussion-button
+ v-if="discussion.resolvable"
+ :is-resolving="isResolving"
+ :button-title="resolveButtonTitle"
+ @onClick="$emit('resolve')"
+ />
+ <div v-if="discussion.resolvable" class="btn-group discussion-actions ml-sm-2" role="group">
+ <resolve-with-issue-button v-if="resolveWithIssuePath" :url="resolveWithIssuePath" />
+ <jump-to-next-discussion-button
+ v-if="shouldShowJumpToNextDiscussion"
+ @onClick="$emit('jumpToNextDiscussion')"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index c7cfc0f0f3b..efd84f5722c 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -49,22 +49,26 @@ export default {
</script>
<template>
- <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container prepend-top-8">
- <div>
+ <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container full-width-mobile">
+ <div class="full-width-mobile d-flex d-sm-block">
<div :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all">
<span
:class="{ 'is-active': allResolved }"
class="line-resolve-btn is-disabled"
type="button"
>
- <icon name="check-circle" />
+ <icon :name="allResolved ? 'check-circle-filled' : 'check-circle'" />
</span>
<span class="line-resolve-text">
{{ resolvedDiscussionsCount }}/{{ resolvableDiscussionsCount }}
{{ n__('discussion resolved', 'discussions resolved', resolvableDiscussionsCount) }}
</span>
</div>
- <div v-if="resolveAllDiscussionsIssuePath && !allResolved" class="btn-group" role="group">
+ <div
+ v-if="resolveAllDiscussionsIssuePath && !allResolved"
+ class="btn-group btn-group-sm"
+ role="group"
+ >
<a
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
@@ -74,7 +78,7 @@ export default {
<icon name="issue-new" />
</a>
</div>
- <div v-if="isLoggedIn && !allResolved" class="btn-group" role="group">
+ <div v-if="isLoggedIn && !allResolved" class="btn-group btn-group-sm" role="group">
<button
v-gl-tooltip
title="Jump to first unresolved discussion"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index e03d6e9cd02..eb3fbbe1385 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -7,7 +7,9 @@ import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
HISTORY_ONLY_FILTER_VALUE,
DISCUSSION_TAB_LABEL,
+ DISCUSSION_FILTER_TYPES,
} from '../constants';
+import notesEventHub from '../event_hub';
export default {
components: {
@@ -20,7 +22,7 @@ export default {
},
selectedValue: {
type: Number,
- default: null,
+ default: DISCUSSION_FILTERS_DEFAULT_VALUE,
required: false,
},
},
@@ -46,6 +48,7 @@ export default {
this.toggleFilters(currentTab);
}
+ notesEventHub.$on('dropdownSelect', this.selectFilter);
window.addEventListener('hashchange', this.handleLocationHash);
this.handleLocationHash();
},
@@ -53,6 +56,7 @@ export default {
this.toggleCommentsForm();
},
destroyed() {
+ notesEventHub.$off('dropdownSelect', this.selectFilter);
window.removeEventListener('hashchange', this.handleLocationHash);
},
methods: {
@@ -86,28 +90,44 @@ export default {
this.setTargetNoteHash(hash);
}
},
+ filterType(value) {
+ if (value === 0) {
+ return DISCUSSION_FILTER_TYPES.ALL;
+ } else if (value === 1) {
+ return DISCUSSION_FILTER_TYPES.COMMENTS;
+ }
+ return DISCUSSION_FILTER_TYPES.HISTORY;
+ },
},
};
</script>
<template>
- <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom">
+ <div
+ v-if="displayFilters"
+ class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom full-width-mobile"
+ >
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
- class="btn btn-default qa-discussion-filter"
+ class="btn btn-sm qa-discussion-filter"
data-toggle="dropdown"
aria-expanded="false"
>
{{ currentFilter.title }} <icon name="chevron-down" />
</button>
<div
+ ref="dropdownMenu"
class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"
aria-labelledby="discussion-filter-dropdown"
>
<div class="dropdown-content">
<ul>
- <li v-for="filter in filters" :key="filter.value">
+ <li
+ v-for="filter in filters"
+ :key="filter.value"
+ :data-filter-type="filterType(filter.value)"
+ >
<button
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
new file mode 100644
index 00000000000..889731df180
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
+
+import notesEventHub from '../event_hub';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+ computed: {
+ timelineContent() {
+ return sprintf(
+ __(
+ "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options.",
+ ),
+ {
+ startTag: `<b>`,
+ endTag: `</b>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ selectFilter(value) {
+ notesEventHub.$emit('dropdownSelect', value);
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note">
+ <div class="timeline-icon d-none d-lg-flex">
+ <icon name="comment" />
+ </div>
+ <div class="timeline-content">
+ <div v-html="timelineContent"></div>
+ <div class="discussion-filter-actions mt-2">
+ <gl-button variant="default" @click="selectFilter(0)">
+ {{ __('Show all activity') }}
+ </gl-button>
+ <gl-button variant="default" @click="selectFilter(1)">
+ {{ __('Show comments only') }}
+ </gl-button>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index c469a6b7bcd..53f509185a8 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -1,12 +1,24 @@
<script>
+import { GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
import Issuable from '~/vue_shared/mixins/issuable';
+import issuableStateMixin from '../mixins/issuable_state';
export default {
components: {
Icon,
+ GlLink,
+ },
+ mixins: [Issuable, issuableStateMixin],
+ computed: {
+ lockedIssueWarning() {
+ return sprintf(
+ __('This %{issuableDisplayName} is locked. Only project members can comment.'),
+ { issuableDisplayName: this.issuableDisplayName },
+ );
+ },
},
- mixins: [Issuable],
};
</script>
@@ -15,7 +27,11 @@ export default {
<span class="issuable-note-warning inline">
<icon :size="16" name="lock" class="icon" />
<span>
- This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
+ {{ lockedIssueWarning }}
+
+ <gl-link :href="lockedIssueDocsPath" target="_blank" class="learn-more">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
new file mode 100644
index 00000000000..228bb652597
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -0,0 +1,155 @@
+<script>
+import { mapGetters } from 'vuex';
+import { SYSTEM_NOTE } from '../constants';
+import { __ } from '~/locale';
+import NoteableNote from './noteable_note.vue';
+import PlaceholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
+import PlaceholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
+import SystemNote from '~/vue_shared/components/notes/system_note.vue';
+import ToggleRepliesWidget from './toggle_replies_widget.vue';
+import NoteEditedText from './note_edited_text.vue';
+
+export default {
+ name: 'DiscussionNotes',
+ components: {
+ ToggleRepliesWidget,
+ NoteEditedText,
+ },
+ props: {
+ discussion: {
+ type: Object,
+ required: true,
+ },
+ isExpanded: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ diffLine: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ shouldGroupReplies: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ ...mapGetters(['userCanReply']),
+ hasReplies() {
+ return Boolean(this.replies.length);
+ },
+ replies() {
+ return this.discussion.notes.slice(1);
+ },
+ firstNote() {
+ return this.discussion.notes.slice(0, 1)[0];
+ },
+ resolvedText() {
+ return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
+ },
+ commit() {
+ if (!this.discussion.for_commit) {
+ return null;
+ }
+
+ return {
+ id: this.discussion.commit_id,
+ url: this.discussion.discussion_path,
+ };
+ },
+ },
+ methods: {
+ componentName(note) {
+ if (note.isPlaceholderNote) {
+ if (note.placeholderType === SYSTEM_NOTE) {
+ return PlaceholderSystemNote;
+ }
+
+ return PlaceholderNote;
+ }
+
+ if (note.system) {
+ return SystemNote;
+ }
+
+ return NoteableNote;
+ },
+ componentData(note) {
+ return note.isPlaceholderNote ? note.notes[0] : note;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="discussion-notes">
+ <ul class="notes">
+ <template v-if="shouldGroupReplies">
+ <component
+ :is="componentName(firstNote)"
+ :note="componentData(firstNote)"
+ :line="line"
+ :commit="commit"
+ :help-page-path="helpPagePath"
+ :show-reply-button="userCanReply"
+ @handle-delete-note="$emit('deleteNote')"
+ @start-replying="$emit('startReplying')"
+ >
+ <note-edited-text
+ v-if="discussion.resolved"
+ slot="discussion-resolved-text"
+ :edited-at="discussion.resolved_at"
+ :edited-by="discussion.resolved_by"
+ :action-text="resolvedText"
+ class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
+ />
+ <slot slot="avatar-badge" name="avatar-badge"></slot>
+ </component>
+ <toggle-replies-widget
+ v-if="hasReplies"
+ :collapsed="!isExpanded"
+ :replies="replies"
+ @toggle="$emit('toggleDiscussion')"
+ />
+ <template v-if="isExpanded">
+ <component
+ :is="componentName(note)"
+ v-for="note in replies"
+ :key="note.id"
+ :note="componentData(note)"
+ :help-page-path="helpPagePath"
+ :line="line"
+ @handle-delete-note="$emit('deleteNote')"
+ />
+ </template>
+ </template>
+ <template v-else>
+ <component
+ :is="componentName(note)"
+ v-for="(note, index) in discussion.notes"
+ :key="note.id"
+ :note="componentData(note)"
+ :help-page-path="helpPagePath"
+ :line="diffLine"
+ @handle-delete-note="$emit('deleteNote')"
+ >
+ <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
+ </component>
+ </template>
+ </ul>
+ <slot :show-replies="isExpanded || !hasReplies" name="footer"></slot>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index de1ea0f58d6..844d0c3e376 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -2,6 +2,7 @@
import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import resolvedStatusMixin from 'ee_else_ce/batch_comments/mixins/resolved_status';
import ReplyButton from './note_actions/reply_button.vue';
export default {
@@ -14,6 +15,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [resolvedStatusMixin],
props: {
authorId: {
type: Number,
@@ -86,9 +88,6 @@ export default {
},
computed: {
...mapGetters(['getUserDataByProp']),
- showReplyButton() {
- return gon.features && gon.features.replyToIndividualNotes && this.showReply;
- },
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
@@ -101,15 +100,6 @@ export default {
currentUserId() {
return this.getUserDataByProp('id');
},
- resolveButtonTitle() {
- let title = 'Mark as resolved';
-
- if (this.resolvedBy) {
- title = `Resolved by ${this.resolvedBy.name}`;
- }
-
- return title;
- },
},
methods: {
onEdit() {
@@ -145,7 +135,7 @@ export default {
@click="onResolve"
>
<template v-if="!isResolving">
- <icon name="check-circle" />
+ <icon :name="isResolved ? 'check-circle-filled' : 'check-circle'" />
</template>
<gl-loading-icon v-else inline />
</button>
@@ -157,18 +147,15 @@ export default {
class="note-action-button note-emoji-button js-add-award js-note-emoji"
href="#"
title="Add reaction"
+ data-position="right"
>
- <gl-loading-icon inline />
- <icon
- css-classes="link-highlight award-control-icon-neutral"
- name="emoji_slightly_smiling_face"
- />
- <icon css-classes="link-highlight award-control-icon-positive" name="emoji_smiley" />
- <icon css-classes="link-highlight award-control-icon-super-positive" name="emoji_smiley" />
+ <icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" />
+ <icon css-classes="link-highlight award-control-icon-positive" name="smiley" />
+ <icon css-classes="link-highlight award-control-icon-super-positive" name="smiley" />
</a>
</div>
<reply-button
- v-if="showReplyButton"
+ v-if="showReply"
ref="replyButton"
class="js-reply-button"
@startReplying="$emit('startReplying')"
@@ -208,7 +195,7 @@ export default {
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
- <a :href="reportAbusePath">{{ __('Report abuse to GitLab') }}</a>
+ <a :href="reportAbusePath">{{ __('Report abuse to admin') }}</a>
</li>
<li v-if="noteUrl">
<button
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index f50cab81efe..be8e42af9ea 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -18,7 +18,7 @@ export default {
<div class="note-actions-item">
<gl-button
ref="button"
- v-gl-tooltip.bottom
+ v-gl-tooltip
class="note-action-button"
variant="transparent"
:title="__('Reply to comment')"
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 17e5fcab5b7..941b6d5cab3 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -189,13 +189,13 @@ export default {
type="button"
>
<span class="award-control-icon award-control-icon-neutral">
- <icon name="emoji_slightly_smiling_face" />
+ <icon name="slight-smile" />
</span>
<span class="award-control-icon award-control-icon-positive">
- <icon name="emoji_smiley" />
+ <icon name="smiley" />
</span>
<span class="award-control-icon award-control-icon-super-positive">
- <icon name="emoji_smiley" />
+ <icon name="smiley" />
</span>
<i
aria-hidden="true"
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index fb1d98355b3..88454c3fb4c 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,6 +1,7 @@
<script>
import { mapActions } from 'vuex';
import $ from 'jquery';
+import getDiscussion from 'ee_else_ce/notes/mixins/get_discussion';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
@@ -16,7 +17,7 @@ export default {
noteForm,
Suggestions,
},
- mixins: [autosave],
+ mixins: [autosave, getDiscussion],
props: {
note: {
type: Object,
@@ -76,16 +77,18 @@ export default {
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
- handleFormUpdate(note, parentElement, callback) {
- this.$emit('handleFormUpdate', note, parentElement, callback);
+ handleFormUpdate(note, parentElement, callback, resolveDiscussion) {
+ this.$emit('handleFormUpdate', note, parentElement, callback, resolveDiscussion);
},
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelForm', shouldConfirm, isDirty);
},
- applySuggestion({ suggestionId, flashContainer, callback }) {
+ applySuggestion({ suggestionId, flashContainer, callback = () => {} }) {
const { discussion_id: discussionId, id: noteId } = this.note;
- this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer, callback });
+ return this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer }).then(
+ callback,
+ );
},
},
};
@@ -95,7 +98,6 @@ export default {
<div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body">
<suggestions
v-if="hasSuggestion && !isEditing"
- class="note-text md"
:suggestions="note.suggestions"
:note-html="note.note_html"
:line-type="lineType"
@@ -112,6 +114,8 @@ export default {
:line="line"
:note="note"
:help-page-path="helpPagePath"
+ :discussion="discussion"
+ :resolve-discussion="note.resolve_discussion"
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
/>
@@ -120,6 +124,7 @@ export default {
v-model="note.note"
:data-update-url="note.path"
class="hidden js-task-list-field"
+ dir="auto"
></textarea>
<note-edited-text
v-if="note.last_edited_at"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 92258a25438..09ecb695214 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -7,6 +7,8 @@ import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
import { __ } from '~/locale';
+import { getDraft, updateDraft } from '~/lib/utils/autosave';
+import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
export default {
name: 'NoteForm',
@@ -14,7 +16,7 @@ export default {
issueWarning,
markdownField,
},
- mixins: [issuableStateMixin, resolvable],
+ mixins: [issuableStateMixin, resolvable, noteFormMixin],
props: {
noteBody: {
type: String,
@@ -60,15 +62,31 @@ export default {
required: false,
default: null,
},
+ diffFile: {
+ type: Object,
+ required: false,
+ default: null,
+ },
helpPagePath: {
type: String,
required: false,
default: '',
},
+ autosaveKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
+ let updatedNoteBody = this.noteBody;
+
+ if (!updatedNoteBody && this.autosaveKey) {
+ updatedNoteBody = getDraft(this.autosaveKey) || '';
+ }
+
return {
- updatedNoteBody: this.noteBody,
+ updatedNoteBody,
conflictWhileEditing: false,
isSubmitting: false,
isResolving: this.resolveDiscussion,
@@ -90,9 +108,42 @@ export default {
}
return '#';
},
+ diffParams() {
+ if (this.diffFile) {
+ return {
+ filePath: this.diffFile.file_path,
+ refs: this.diffFile.diff_refs,
+ };
+ } else if (this.note && this.note.position) {
+ return {
+ filePath: this.note.position.new_path,
+ refs: this.note.position,
+ };
+ } else if (this.discussion && this.discussion.diff_file) {
+ return {
+ filePath: this.discussion.diff_file.file_path,
+ refs: this.discussion.diff_file.diff_refs,
+ };
+ }
+
+ return null;
+ },
markdownPreviewPath() {
const notable = this.getNoteableDataByProp('preview_note_path');
- return mergeUrlParams({ preview_suggestions: true }, notable);
+
+ const previewSuggestions = this.line && this.diffParams;
+ const params = previewSuggestions
+ ? {
+ preview_suggestions: previewSuggestions,
+ line: this.line.new_line,
+ file_path: this.diffParams.filePath,
+ base_sha: this.diffParams.refs.base_sha,
+ start_sha: this.diffParams.refs.start_sha,
+ head_sha: this.diffParams.refs.head_sha,
+ }
+ : {};
+
+ return mergeUrlParams(params, notable);
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
@@ -145,21 +196,6 @@ export default {
return shouldResolve || shouldToggleState;
},
- handleKeySubmit() {
- this.handleUpdate();
- },
- handleUpdate(shouldResolve) {
- const beforeSubmitDiscussionState = this.discussionResolved;
- this.isSubmitting = true;
-
- this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
- this.isSubmitting = false;
-
- if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
- this.resolveHandler(beforeSubmitDiscussionState);
- }
- });
- },
editMyLastNote() {
if (this.updatedNoteBody === '') {
const lastNoteInDiscussion = this.getDiscussionLastNote(this.discussion);
@@ -175,6 +211,12 @@ export default {
// Sends information about confirm message and if the textarea has changed
this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
},
+ onInput() {
+ if (this.autosaveKey) {
+ const { autosaveKey, updatedNoteBody: text } = this;
+ updateDraft(autosaveKey, text);
+ }
+ },
},
};
</script>
@@ -192,6 +234,8 @@ export default {
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
@@ -212,37 +256,85 @@ export default {
:data-supports-quick-actions="!isEditing"
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
+ dir="auto"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
@keydown.meta.enter="handleKeySubmit()"
@keydown.ctrl.enter="handleKeySubmit()"
- @keydown.up="editMyLastNote()"
- @keydown.esc="cancelHandler(true)"
+ @keydown.exact.up="editMyLastNote()"
+ @keydown.exact.esc="cancelHandler(true)"
+ @input="onInput"
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
- <button
- :disabled="isDisabled"
- type="button"
- class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
- @click="handleUpdate()"
- >
- {{ saveButtonTitle }}
- </button>
- <button
- v-if="discussion.resolvable"
- class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
- @click.prevent="handleUpdate(true)"
- >
- {{ resolveButtonTitle }}
- </button>
- <button
- class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
- type="button"
- @click="cancelHandler()"
- >
- Cancel
- </button>
+ <template v-if="showBatchCommentsActions">
+ <p v-if="showResolveDiscussionToggle">
+ <label>
+ <template v-if="discussionResolved">
+ <input
+ v-model="isUnresolving"
+ type="checkbox"
+ class="qa-unresolve-review-discussion"
+ />
+ {{ __('Unresolve discussion') }}
+ </template>
+ <template v-else>
+ <input v-model="isResolving" type="checkbox" class="qa-resolve-review-discussion" />
+ {{ __('Resolve discussion') }}
+ </template>
+ </label>
+ </p>
+ <div>
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="btn btn-success qa-start-review"
+ @click="handleAddToReview"
+ >
+ <template v-if="hasDrafts">{{ __('Add to review') }}</template>
+ <template v-else>{{ __('Start a review') }}</template>
+ </button>
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="btn qa-comment-now"
+ @click="handleUpdate()"
+ >
+ {{ __('Add comment now') }}
+ </button>
+ <button
+ class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
+ type="button"
+ @click="cancelHandler()"
+ >
+ {{ __('Cancel') }}
+ </button>
+ </div>
+ </template>
+ <template v-else>
+ <button
+ :disabled="isDisabled"
+ type="button"
+ class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
+ @click="handleUpdate()"
+ >
+ {{ saveButtonTitle }}
+ </button>
+ <button
+ v-if="discussion.resolvable"
+ class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
+ @click.prevent="handleUpdate(true)"
+ >
+ {{ resolveButtonTitle }}
+ </button>
+ <button
+ class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
+ type="button"
+ @click="cancelHandler()"
+ >
+ Cancel
+ </button>
+ </template>
</div>
</form>
</div>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 7b39901024d..fbf82fab9e9 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -69,7 +69,7 @@ export default {
type="button"
@click="handleToggle"
>
- <i :class="toggleChevronClass" class="fa" aria-hidden="true"> </i>
+ <i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle discussion') }}
</button>
</div>
@@ -81,35 +81,31 @@ export default {
:data-user-id="author.id"
:data-username="author.username"
>
- <span class="note-header-author-name">{{ author.name }}</span>
+ <slot name="note-header-info"></slot>
+ <span class="note-header-author-name bold">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
- <span class="note-headline-light"> @{{ author.username }} </span>
+ <span class="note-headline-light">@{{ author.username }}</span>
</a>
- <span v-else> {{ __('A deleted user') }} </span>
- <span class="note-headline-light">
- <span class="note-headline-meta">
- <span class="system-note-message"> <slot></slot> </span>
- <template v-if="createdAt">
- <span class="system-note-separator">
- <template v-if="actionText">
- {{ actionText }}
- </template>
- </span>
- <a
- :href="noteTimestampLink"
- class="note-timestamp system-note-separator"
- @click="updateTargetNoteHash"
- >
- <time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
- </a>
- </template>
- <i
- class="fa fa-spinner fa-spin editing-spinner"
- aria-label="Comment is being updated"
- aria-hidden="true"
+ <span v-else>{{ __('A deleted user') }}</span>
+ <span class="note-headline-light note-headline-meta">
+ <span class="system-note-message"> <slot></slot> </span>
+ <template v-if="createdAt">
+ <span class="system-note-separator">
+ <template v-if="actionText">{{ actionText }}</template>
+ </span>
+ <a
+ :href="noteTimestampLink"
+ class="note-timestamp system-note-separator"
+ @click="updateTargetNoteHash"
>
- </i>
- </span>
+ <time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
+ </a>
+ </template>
+ <i
+ class="fa fa-spinner fa-spin editing-spinner"
+ aria-label="Comment is being updated"
+ aria-hidden="true"
+ ></i>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 3894dc8c677..eb6a4a67fff 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -4,55 +4,42 @@ import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
-import systemNote from '~/vue_shared/components/notes/system_note.vue';
+import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
-import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue';
-import resolveDiscussionButton from './discussion_resolve_button.vue';
-import toggleRepliesWidget from './toggle_replies_widget.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
-import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
-import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
-import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
-import ReplyPlaceholder from './discussion_reply_placeholder.vue';
-import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
-import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
import eventHub from '../event_hub';
+import DiscussionNotes from './discussion_notes.vue';
+import DiscussionActions from './discussion_actions.vue';
export default {
name: 'NoteableDiscussion',
components: {
icon,
- noteableNote,
userAvatarLink,
noteHeader,
noteSignedOutWidget,
noteEditedText,
noteForm,
- resolveDiscussionButton,
- jumpToNextDiscussionButton,
- toggleRepliesWidget,
- ReplyPlaceholder,
- placeholderNote,
- placeholderSystemNote,
- ResolveWithIssueButton,
- systemNote,
+ DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
TimelineEntryItem,
+ DiscussionNotes,
+ DiscussionActions,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [autosave, noteable, resolvable, discussionNavigation],
+ mixins: [noteable, resolvable, discussionNavigation, diffLineNoteFormMixin],
props: {
discussion: {
type: Object,
@@ -85,42 +72,38 @@ export default {
},
},
data() {
- const { diff_discussion: isDiffDiscussion, resolved } = this.discussion;
-
return {
isReplying: false,
isResolving: false,
resolveAsThread: true,
- isRepliesCollapsed: Boolean(!isDiffDiscussion && resolved),
};
},
computed: {
...mapGetters([
'convertedDisscussionIds',
'getNoteableData',
+ 'userCanReply',
'nextUnresolvedDiscussionId',
'unresolvedDiscussionsCount',
'hasUnresolvedDiscussions',
'showJumpToNextDiscussion',
+ 'getUserData',
]),
+ currentUser() {
+ return this.getUserData;
+ },
author() {
- return this.initialDiscussion.author;
+ return this.firstNote.author;
},
- canReply() {
- return this.getNoteableData.current_user.can_create_note;
+ autosaveKey() {
+ return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
},
newNotePath() {
return this.getNoteableData.create_note_path;
},
- hasReplies() {
- return this.discussion.notes.length > 1;
- },
- initialDiscussion() {
+ firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
- replies() {
- return this.discussion.notes.slice(1);
- },
lastUpdatedBy() {
const { notes } = this.discussion;
@@ -173,11 +156,11 @@ export default {
return '';
},
- shouldShowDiscussions() {
- const { expanded, resolved } = this.discussion;
- const isResolvedNonDiffDiscussion = !this.discussion.diff_discussion && resolved;
-
- return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
+ isExpanded() {
+ return this.discussion.expanded || this.alwaysExpanded;
+ },
+ shouldHideDiscussionBody() {
+ return this.shouldRenderDiffs && !this.isExpanded;
},
actionText() {
const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
@@ -226,30 +209,8 @@ export default {
return null;
},
- commit() {
- if (!this.discussion.for_commit) {
- return null;
- }
-
- return {
- id: this.discussion.commit_id,
- url: this.discussion.discussion_path,
- };
- },
resolveWithIssuePath() {
- return !this.discussionResolved && this.discussion.resolve_with_issue_path;
- },
- },
- watch: {
- isReplying() {
- if (this.isReplying) {
- this.$nextTick(() => {
- // Pass an extra key to separate reply and note edit forms
- this.initAutoSave({ ...this.initialDiscussion, ...this.discussion }, ['Reply']);
- });
- } else {
- this.disposeAutoSave();
- }
+ return !this.discussionResolved ? this.discussion.resolve_with_issue_path : '';
},
},
created() {
@@ -268,30 +229,9 @@ export default {
'removeConvertedDiscussion',
]),
truncateSha,
- componentName(note) {
- if (note.isPlaceholderNote) {
- if (note.placeholderType === SYSTEM_NOTE) {
- return placeholderSystemNote;
- }
-
- return placeholderNote;
- }
-
- if (note.system) {
- return systemNote;
- }
-
- return noteableNote;
- },
- componentData(note) {
- return note.isPlaceholderNote ? note.notes[0] : note;
- },
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
},
- toggleReplies() {
- this.isRepliesCollapsed = !this.isRepliesCollapsed;
- },
showReplyForm() {
this.isReplying = true;
},
@@ -310,7 +250,7 @@ export default {
}
this.isReplying = false;
- this.resetAutoSave();
+ clearDraft(this.autosaveKey);
},
saveReply(noteText, form, callback) {
const postData = {
@@ -336,7 +276,7 @@ export default {
this.isReplying = false;
this.saveNote(replyData)
.then(() => {
- this.resetAutoSave();
+ clearDraft(this.autosaveKey);
callback();
})
.catch(err => {
@@ -388,8 +328,8 @@ Please check your network connection and try again.`;
<div class="timeline-content">
<note-header
:author="author"
- :created-at="initialDiscussion.created_at"
- :note-id="initialDiscussion.id"
+ :created-at="firstNote.created_at"
+ :note-id="firstNote.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
@@ -412,110 +352,79 @@ Please check your network connection and try again.`;
/>
</div>
</div>
- <div v-if="shouldShowDiscussions" class="discussion-body">
+ <div v-if="!shouldHideDiscussionBody" class="discussion-body">
<component
:is="wrapperComponent"
v-bind="wrapperComponentProps"
class="card discussion-wrapper"
>
- <div class="discussion-notes">
- <ul class="notes">
- <template v-if="shouldGroupReplies">
- <component
- :is="componentName(initialDiscussion)"
- :note="componentData(initialDiscussion)"
- :line="line"
- :commit="commit"
- :help-page-path="helpPagePath"
- :show-reply-button="canReply"
- @handleDeleteNote="deleteNoteHandler"
- @startReplying="showReplyForm"
- >
- <note-edited-text
- v-if="discussion.resolved"
- slot="discussion-resolved-text"
- :edited-at="discussion.resolved_at"
- :edited-by="discussion.resolved_by"
- :action-text="resolvedText"
- class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
- />
- <slot slot="avatar-badge" name="avatar-badge"></slot>
- </component>
- <toggle-replies-widget
- v-if="hasReplies"
- :collapsed="isRepliesCollapsed"
- :replies="replies"
- @toggle="toggleReplies"
+ <discussion-notes
+ :discussion="discussion"
+ :diff-line="diffLine"
+ :help-page-path="helpPagePath"
+ :is-expanded="isExpanded"
+ :line="line"
+ :should-group-replies="shouldGroupReplies"
+ @startReplying="showReplyForm"
+ @toggleDiscussion="toggleDiscussionHandler"
+ @deleteNote="deleteNoteHandler"
+ >
+ <slot slot="avatar-badge" name="avatar-badge"></slot>
+ <template #footer="{ showReplies }">
+ <draft-note
+ v-if="showDraft(discussion.reply_id)"
+ :key="`draft_${discussion.id}`"
+ :draft="draftForDiscussion(discussion.reply_id)"
+ />
+ <div
+ v-else-if="showReplies"
+ :class="{ 'is-replying': isReplying }"
+ class="discussion-reply-holder"
+ >
+ <user-avatar-link
+ v-if="!isReplying && currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
+ <discussion-actions
+ v-if="!isReplying && userCanReply"
+ :discussion="discussion"
+ :is-resolving="isResolving"
+ :resolve-button-title="resolveButtonTitle"
+ :resolve-with-issue-path="resolveWithIssuePath"
+ :should-show-jump-to-next-discussion="shouldShowJumpToNextDiscussion"
+ @showReplyForm="showReplyForm"
+ @resolve="resolveHandler"
+ @jumpToNextDiscussion="jumpToNextDiscussion"
/>
- <template v-if="!isRepliesCollapsed">
- <component
- :is="componentName(note)"
- v-for="note in replies"
- :key="note.id"
- :note="componentData(note)"
- :help-page-path="helpPagePath"
- :line="line"
- @handleDeleteNote="deleteNoteHandler"
+ <div v-if="isReplying" class="avatar-note-form-holder">
+ <user-avatar-link
+ v-if="currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
/>
- </template>
- </template>
- <template v-else>
- <component
- :is="componentName(note)"
- v-for="(note, index) in discussion.notes"
- :key="note.id"
- :note="componentData(note)"
- :help-page-path="helpPagePath"
- :line="diffLine"
- @handleDeleteNote="deleteNoteHandler"
- >
- <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
- </component>
- </template>
- </ul>
- <div
- v-if="!isRepliesCollapsed || !hasReplies"
- :class="{ 'is-replying': isReplying }"
- class="discussion-reply-holder"
- >
- <template v-if="!isReplying && canReply">
- <div class="discussion-with-resolve-btn">
- <reply-placeholder class="qa-discussion-reply" @onClick="showReplyForm" />
- <resolve-discussion-button
- v-if="discussion.resolvable"
- :is-resolving="isResolving"
- :button-title="resolveButtonTitle"
- @onClick="resolveHandler"
+ <note-form
+ ref="noteForm"
+ :discussion="discussion"
+ :is-editing="false"
+ :line="diffLine"
+ save-button-title="Comment"
+ :autosave-key="autosaveKey"
+ @handleFormUpdateAddToReview="addReplyToReview"
+ @handleFormUpdate="saveReply"
+ @cancelForm="cancelReplyForm"
/>
- <div
- v-if="discussion.resolvable"
- class="btn-group discussion-actions ml-sm-2"
- role="group"
- >
- <resolve-with-issue-button
- v-if="resolveWithIssuePath"
- :url="resolveWithIssuePath"
- />
- <jump-to-next-discussion-button
- v-if="shouldShowJumpToNextDiscussion"
- @onClick="jumpToNextDiscussion"
- />
- </div>
</div>
- </template>
- <note-form
- v-if="isReplying"
- ref="noteForm"
- :discussion="discussion"
- :is-editing="false"
- :line="diffLine"
- save-button-title="Comment"
- @handleFormUpdate="saveReply"
- @cancelForm="cancelReplyForm"
- />
- <note-signed-out-widget v-if="!canReply" />
- </div>
- </div>
+ <note-signed-out-widget v-if="!userCanReply" />
+ </div>
+ </template>
+ </discussion-notes>
</component>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 04e74a43acc..aa80e25a3e0 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -4,12 +4,13 @@ import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
-import noteBody from './note_body.vue';
+import NoteBody from './note_body.vue';
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
@@ -20,10 +21,10 @@ export default {
userAvatarLink,
noteHeader,
noteActions,
- noteBody,
+ NoteBody,
TimelineEntryItem,
},
- mixins: [noteable, resolvable],
+ mixins: [noteable, resolvable, draftMixin],
props: {
note: {
type: Object,
@@ -73,11 +74,8 @@ export default {
'is-editable': this.note.current_user.can_edit,
};
},
- canResolve() {
- return this.note.resolvable && !!this.getUserData.id;
- },
canReportAsAbuse() {
- return !!this.note.report_abuse_path && this.author.id !== this.getUserData.id;
+ return Boolean(this.note.report_abuse_path) && this.author.id !== this.getUserData.id;
},
noteAnchorId() {
return `note_${this.note.id}`;
@@ -96,7 +94,7 @@ export default {
return '';
}
- // We need to do this to ensure we have the currect sentence order
+ // We need to do this to ensure we have the correct sentence order
// when translating this as the sentence order may change from one
// language to the next. See:
// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24427#note_133713771
@@ -156,12 +154,16 @@ export default {
this.$refs.noteBody.resetAutoSave();
this.$emit('updateSuccess');
},
- formUpdateHandler(noteText, parentElement, callback) {
+ formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
+ resolveDiscussion,
callback: () => this.updateSuccess(),
});
+
+ if (this.isDraft) return;
+
const data = {
endpoint: this.note.path,
note: {
@@ -207,7 +209,10 @@ export default {
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
- this.$refs.noteBody.note.note = noteText;
+ const { noteBody } = this.$refs;
+ if (noteBody) {
+ noteBody.note.note = noteText;
+ }
},
},
};
@@ -219,7 +224,7 @@ export default {
:class="classNameBindings"
:data-award-url="note.toggle_award_path"
:data-note-id="note.id"
- class="note note-wrapper"
+ class="note note-wrapper qa-noteable-note-item"
>
<div v-once class="timeline-icon">
<user-avatar-link
@@ -234,6 +239,7 @@ export default {
<div class="timeline-content">
<div class="note-header">
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
+ <slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span>
<span v-else class="d-none d-sm-inline">&middot;</span>
</note-header>
@@ -247,12 +253,15 @@ export default {
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
- :can-resolve="note.current_user.can_resolve"
+ :can-resolve="canResolve"
:report-abuse-path="note.report_abuse_path"
- :resolvable="note.resolvable"
- :is-resolved="note.resolved"
+ :resolvable="note.resolvable || note.isDraft"
+ :is-resolved="note.resolved || note.resolve_discussion"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
+ :is-draft="note.isDraft"
+ :resolve-discussion="note.isDraft && note.resolve_discussion"
+ :discussion-id="discussionId"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 8d3f6d902f8..4d00e957973 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -6,6 +6,7 @@ import * as constants from '../constants';
import eventHub from '../event_hub';
import noteableNote from './noteable_note.vue';
import noteableDiscussion from './noteable_discussion.vue';
+import discussionFilterNote from './discussion_filter_note.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue';
import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
@@ -24,6 +25,7 @@ export default {
placeholderNote,
placeholderSystemNote,
skeletonLoadingContainer,
+ discussionFilterNote,
},
props: {
noteableData: {
@@ -65,6 +67,7 @@ export default {
'isLoading',
'commentsDisabled',
'getNoteableData',
+ 'userCanReply',
]),
noteableType() {
return this.noteableData.noteableType;
@@ -81,7 +84,7 @@ export default {
return this.discussions;
},
canReply() {
- return this.getNoteableData.current_user.can_create_note && !this.commentsDisabled;
+ return this.userCanReply && !this.commentsDisabled;
},
},
watch: {
@@ -124,6 +127,9 @@ export default {
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
});
},
+ beforeDestroy() {
+ this.stopPolling();
+ },
methods: {
...mapActions([
'setLoadingState',
@@ -141,6 +147,7 @@ export default {
'expandDiscussion',
'startTaskList',
'convertToDiscussion',
+ 'stopPolling',
]),
fetchNotes() {
if (this.isFetching) return null;
@@ -235,6 +242,7 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
+ <discussion-filter-note v-show="commentsDisabled" />
</ul>
<comment-form v-if="!commentsDisabled" :noteable-type="noteableType" />
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 78d365fe94b..bdfb6b8f105 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -7,6 +7,7 @@ export const COMMENT = 'comment';
export const OPENED = 'opened';
export const REOPENED = 'reopened';
export const CLOSED = 'closed';
+export const MERGED = 'merged';
export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown';
export const ISSUE_NOTEABLE_TYPE = 'issue';
@@ -24,3 +25,9 @@ export const NOTEABLE_TYPE_MAPPING = {
MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE,
Epic: EPIC_NOTEABLE_TYPE,
};
+
+export const DISCUSSION_FILTER_TYPES = {
+ ALL: 'all',
+ COMMENTS: 'comments',
+ HISTORY: 'history',
+};
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
index 5c5f38a3fb0..cdf9a46c5aa 100644
--- a/app/assets/javascripts/notes/discussion_filters.js
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -6,12 +6,16 @@ export default store => {
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
- const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
value: filterValues[entry],
}));
+ const props = { filters };
+
+ if (defaultFilter) {
+ props.selectedValue = parseInt(defaultFilter, 10);
+ }
return new Vue({
el: discussionFilterEl,
@@ -21,12 +25,7 @@ export default store => {
},
store,
render(createElement) {
- return createElement('discussion-filter', {
- props: {
- filters,
- selectedValue,
- },
- });
+ return createElement('discussion-filter', { props });
},
});
}
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 4883266dae5..57dd1c5cab2 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import { isEE } from '~/lib/utils/common_utils';
+import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
@@ -6,9 +8,8 @@ import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
const store = createStore();
- initDiscussionFilters(store);
-
- return new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#js-vue-notes',
components: {
notesApp,
@@ -39,6 +40,11 @@ document.addEventListener('DOMContentLoaded', () => {
notesData: JSON.parse(notesDataset.notesData),
};
},
+ mounted() {
+ if (isEE) {
+ initNoteStats();
+ }
+ },
render(createElement) {
return createElement('notes-app', {
props: {
@@ -49,4 +55,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
+
+ initDiscussionFilters(store);
});
diff --git a/app/assets/javascripts/notes/mixins/autosave.js b/app/assets/javascripts/notes/mixins/autosave.js
index 4f45f912479..b161773f5f1 100644
--- a/app/assets/javascripts/notes/mixins/autosave.js
+++ b/app/assets/javascripts/notes/mixins/autosave.js
@@ -1,12 +1,13 @@
import $ from 'jquery';
import Autosave from '../../autosave';
import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
+import { s__ } from '~/locale';
export default {
methods: {
initAutoSave(noteable, extraKeys = []) {
let keys = [
- 'Note',
+ s__('Autosave|Note'),
capitalizeFirstCharacter(noteable.noteable_type || noteable.noteableType),
noteable.id,
];
diff --git a/app/assets/javascripts/notes/mixins/diff_line_note_form.js b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
new file mode 100644
index 00000000000..188556e8921
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
@@ -0,0 +1,10 @@
+export default {
+ computed: {
+ draftForDiscussion: () => () => ({}),
+ },
+ methods: {
+ showDraft: () => false,
+ addReplyToReview: () => {},
+ addToReview: () => {},
+ },
+};
diff --git a/app/assets/javascripts/notes/mixins/draft.js b/app/assets/javascripts/notes/mixins/draft.js
new file mode 100644
index 00000000000..1370f3978df
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/draft.js
@@ -0,0 +1,8 @@
+export default {
+ computed: {
+ isDraft: () => false,
+ canResolve() {
+ return this.note.current_user.can_resolve;
+ },
+ },
+};
diff --git a/app/assets/javascripts/notes/mixins/get_discussion.js b/app/assets/javascripts/notes/mixins/get_discussion.js
new file mode 100644
index 00000000000..b5d820fe083
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/get_discussion.js
@@ -0,0 +1,7 @@
+export default {
+ computed: {
+ discussion() {
+ return {};
+ },
+ },
+};
diff --git a/app/assets/javascripts/notes/mixins/issuable_state.js b/app/assets/javascripts/notes/mixins/issuable_state.js
index 97f3ea0d5de..d97d9f6850a 100644
--- a/app/assets/javascripts/notes/mixins/issuable_state.js
+++ b/app/assets/javascripts/notes/mixins/issuable_state.js
@@ -1,11 +1,22 @@
+import { mapGetters } from 'vuex';
+
export default {
+ computed: {
+ ...mapGetters(['getNoteableDataByProp']),
+ lockedIssueDocsPath() {
+ return this.getNoteableDataByProp('locked_discussion_docs_path');
+ },
+ confidentialIssueDocsPath() {
+ return this.getNoteableDataByProp('confidential_issues_docs_path');
+ },
+ },
methods: {
isConfidential(issue) {
- return !!issue.confidential;
+ return Boolean(issue.confidential);
},
isLocked(issue) {
- return !!issue.discussion_locked;
+ return Boolean(issue.discussion_locked);
},
hasWarning(issue) {
diff --git a/app/assets/javascripts/notes/mixins/note_form.js b/app/assets/javascripts/notes/mixins/note_form.js
new file mode 100644
index 00000000000..b74879f2256
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/note_form.js
@@ -0,0 +1,24 @@
+export default {
+ data() {
+ return {
+ showBatchCommentsActions: false,
+ };
+ },
+ methods: {
+ handleKeySubmit() {
+ this.handleUpdate();
+ },
+ handleUpdate(shouldResolve) {
+ const beforeSubmitDiscussionState = this.discussionResolved;
+ this.isSubmitting = true;
+
+ this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
+ this.isSubmitting = false;
+
+ if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
+ this.resolveHandler(beforeSubmitDiscussionState);
+ }
+ });
+ },
+ },
+};
diff --git a/app/assets/javascripts/notes/mixins/resolvable.js b/app/assets/javascripts/notes/mixins/resolvable.js
index 8edf3d088bb..2329727bca2 100644
--- a/app/assets/javascripts/notes/mixins/resolvable.js
+++ b/app/assets/javascripts/notes/mixins/resolvable.js
@@ -31,6 +31,10 @@ export default {
},
methods: {
resolveHandler(resolvedState = false) {
+ if (this.note && this.note.isDraft) {
+ return this.$emit('toggleResolveStatus');
+ }
+
this.isResolving = true;
const isResolved = this.discussionResolved || resolvedState;
const discussion = this.resolveAsThread;
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 1a0dba69a7c..63658d49a05 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -142,6 +142,23 @@ export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
+export const resolveDiscussion = ({ state, dispatch, getters }, { discussionId }) => {
+ const discussion = utils.findNoteObjectById(state.discussions, discussionId);
+ const isResolved = getters.isDiscussionResolved(discussionId);
+
+ if (!discussion) {
+ return Promise.reject();
+ } else if (isResolved) {
+ return Promise.resolve();
+ }
+
+ return dispatch('toggleResolveNote', {
+ endpoint: discussion.resolve_path,
+ isResolved,
+ discussion: true,
+ });
+};
+
export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) =>
service
.toggleResolveNote(endpoint, isResolved)
@@ -251,11 +268,20 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const { errors } = res;
const commandsChanges = res.commands_changes;
- if (hasQuickActions && errors && Object.keys(errors).length) {
- eTagPoll.makeRequest();
+ if (errors && Object.keys(errors).length) {
+ /*
+ The following reply means that quick actions have been successfully applied:
+
+ {"commands_changes":{},"valid":false,"errors":{"commands_only":["Commands applied"]}}
+ */
+ if (hasQuickActions) {
+ eTagPoll.makeRequest();
- $('.js-gfm-input').trigger('clear-commands-cache.atwho');
- Flash('Commands applied', 'notice', noteData.flashContainer);
+ $('.js-gfm-input').trigger('clear-commands-cache.atwho');
+ Flash(__('Commands applied'), 'notice', noteData.flashContainer);
+ } else {
+ throw new Error(__('Failed to save comment!'));
+ }
}
if (commandsChanges) {
@@ -269,7 +295,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
})
.catch(() => {
Flash(
- 'Something went wrong while adding your award. Please try again.',
+ __('Something went wrong while adding your award. Please try again.'),
'alert',
noteData.flashContainer,
);
@@ -311,7 +337,7 @@ export const poll = ({ commit, state, getters, dispatch }) => {
data: state,
successCallback: resp =>
resp.json().then(data => pollSuccessCallBack(data, commit, state, getters, dispatch)),
- errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
+ errorCallback: () => Flash(__('Something went wrong while fetching latest comments.')),
});
if (!Visibility.hidden()) {
@@ -347,7 +373,7 @@ export const fetchData = ({ commit, state, getters }) => {
.poll(requestData)
.then(resp => resp.json)
.then(data => pollSuccessCallBack(data, commit, state, getters))
- .catch(() => Flash('Something went wrong while fetching latest comments.'));
+ .catch(() => Flash(__('Something went wrong while fetching latest comments.')));
};
export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
@@ -420,15 +446,13 @@ export const updateResolvableDiscussonsCounts = ({ commit }) =>
commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS);
export const submitSuggestion = (
- { commit },
- { discussionId, noteId, suggestionId, flashContainer, callback },
-) => {
+ { commit, dispatch },
+ { discussionId, noteId, suggestionId, flashContainer },
+) =>
service
.applySuggestion(suggestionId)
- .then(() => {
- commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId });
- callback();
- })
+ .then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }))
+ .then(() => dispatch('resolveDiscussion', { discussionId }).catch(() => {}))
.catch(err => {
const defaultMessage = __(
'Something went wrong while applying the suggestion. Please try again.',
@@ -436,9 +460,7 @@ export const submitSuggestion = (
const flashMessage = err.response.data ? `${err.response.data.message}.` : defaultMessage;
Flash(__(flashMessage), 'alert', flashContainer);
- callback();
});
-};
export const convertToDiscussion = ({ commit }, noteId) =>
commit(types.CONVERT_TO_DISCUSSION, noteId);
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 5026c13dab5..d7982be3e4b 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -20,6 +20,8 @@ export const getNoteableData = state => state.noteableData;
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
+export const userCanReply = state => Boolean(state.noteableData.current_user.can_create_note);
+
export const openState = state => state.noteableData.state;
export const getUserData = state => state.userData || {};
@@ -191,6 +193,9 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
return getters.unresolvedDiscussionsIdsByDate[0];
};
+export const getDiscussion = state => discussionId =>
+ state.discussions.find(discussion => discussion.id === discussionId);
+
export const commentsDisabled = state => state.commentsDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index ae6f8b7790a..fa44ef2d057 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -193,6 +193,10 @@ export default {
const noteObj = utils.findNoteObjectById(state.discussions, note.discussion_id);
if (noteObj.individual_note) {
+ if (note.type === constants.DISCUSSION_NOTE) {
+ noteObj.individual_note = false;
+ }
+
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 4b0feb0f94d..ed4cef4a917 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -1,12 +1,14 @@
import AjaxCache from '~/lib/utils/ajax_cache';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
+import { sprintf, __ } from '~/locale';
-const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
+// factory function because global flag makes RegExp stateful
+const createQuickActionsRegex = () => /^\/\w+.*$/gm;
export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
export const getQuickActionText = note => {
- let text = 'Applying command';
+ let text = __('Applying command');
const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter(command => {
@@ -16,19 +18,19 @@ export const getQuickActionText = note => {
if (executedCommands && executedCommands.length) {
if (executedCommands.length > 1) {
- text = 'Applying multiple commands';
+ text = __('Applying multiple commands');
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
- text = `Applying command to ${commandDescription}`;
+ text = sprintf(__('Applying command to %{commandDescription}', { commandDescription }));
}
}
return text;
};
-export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
+export const hasQuickActions = note => createQuickActionsRegex().test(note);
-export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
+export const stripQuickActions = note => note.replace(createQuickActionsRegex(), '').trim();
export const prepareDiffLines = diffLines =>
diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) }));
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index e7fa05faa8a..08545dcea46 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -1,9 +1,11 @@
import $ from 'jquery';
import Flash from './flash';
+import { __ } from '~/locale';
export default function notificationsDropdown() {
$(document).on('click', '.update-notification', function updateNotificationCallback(e) {
e.preventDefault();
+
if ($(this).is('.is-active') && $(this).data('notificationLevel') === 'custom') {
return;
}
@@ -26,7 +28,7 @@ export default function notificationsDropdown() {
.closest('.js-notification-dropdown')
.replaceWith(data.html);
} else {
- Flash('Failed to save new settings', 'alert');
+ Flash(__('Failed to save new settings'), 'alert');
}
});
}
diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
new file mode 100644
index 00000000000..ed518611d0b
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
@@ -0,0 +1,67 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlButton,
+ GlFormGroup,
+ GlFormInput,
+ GlLink,
+ },
+ computed: {
+ ...mapState([
+ 'externalDashboardHelpPagePath',
+ 'externalDashboardUrl',
+ 'operationsSettingsEndpoint',
+ ]),
+ userDashboardUrl: {
+ get() {
+ return this.externalDashboardUrl;
+ },
+ set(url) {
+ this.setExternalDashboardUrl(url);
+ },
+ },
+ },
+ methods: {
+ ...mapActions(['setExternalDashboardUrl', 'updateExternalDashboardUrl']),
+ },
+};
+</script>
+
+<template>
+ <section class="settings no-animate">
+ <div class="settings-header">
+ <h4 class="js-section-header">
+ {{ s__('ExternalMetrics|External Dashboard') }}
+ </h4>
+ <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
+ <p class="js-section-sub-header">
+ {{
+ s__(
+ 'ExternalMetrics|Add a button to the metrics dashboard linking directly to your existing external dashboards.',
+ )
+ }}
+ <gl-link :href="externalDashboardHelpPagePath">{{ __('Learn more') }}</gl-link>
+ </p>
+ </div>
+ <div class="settings-content">
+ <form>
+ <gl-form-group
+ :label="s__('ExternalMetrics|Full dashboard URL')"
+ :description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')"
+ >
+ <gl-form-input
+ v-model="userDashboardUrl"
+ placeholder="https://my-org.gitlab.io/my-dashboards"
+ @keydown.enter.native.prevent="updateExternalDashboardUrl"
+ />
+ </gl-form-group>
+ <gl-button variant="success" @click="updateExternalDashboardUrl">
+ {{ __('Save Changes') }}
+ </gl-button>
+ </form>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/operation_settings/index.js b/app/assets/javascripts/operation_settings/index.js
new file mode 100644
index 00000000000..6946578e6d2
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import store from './store';
+import ExternalDashboardForm from './components/external_dashboard.vue';
+
+export default () => {
+ /**
+ * This check can be removed when we remove
+ * the :grafana_dashboard_link feature flag
+ */
+ if (!gon.features.grafanaDashboardLink) {
+ return null;
+ }
+
+ const el = document.querySelector('.js-operation-settings');
+
+ return new Vue({
+ el,
+ store: store(el.dataset),
+ render(createElement) {
+ return createElement(ExternalDashboardForm);
+ },
+ });
+};
diff --git a/app/assets/javascripts/operation_settings/store/actions.js b/app/assets/javascripts/operation_settings/store/actions.js
new file mode 100644
index 00000000000..ec05b0c76cf
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/actions.js
@@ -0,0 +1,38 @@
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import * as mutationTypes from './mutation_types';
+
+export const setExternalDashboardUrl = ({ commit }, url) =>
+ commit(mutationTypes.SET_EXTERNAL_DASHBOARD_URL, url);
+
+export const updateExternalDashboardUrl = ({ state, dispatch }) =>
+ axios
+ .patch(state.operationsSettingsEndpoint, {
+ project: {
+ metrics_setting_attributes: {
+ external_dashboard_url: state.externalDashboardUrl,
+ },
+ },
+ })
+ .then(() => dispatch('receiveExternalDashboardUpdateSuccess'))
+ .catch(error => dispatch('receiveExternalDashboardUpdateError', error));
+
+export const receiveExternalDashboardUpdateSuccess = () => {
+ /**
+ * The operations_controller currently handles successful requests
+ * by creating a flash banner messsage to notify the user.
+ */
+ refreshCurrentPage();
+};
+
+export const receiveExternalDashboardUpdateError = (_, error) => {
+ const { response } = error;
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/operation_settings/store/index.js b/app/assets/javascripts/operation_settings/store/index.js
new file mode 100644
index 00000000000..e96bb1e8aad
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export const createStore = initialState =>
+ new Vuex.Store({
+ state: createState(initialState),
+ actions,
+ mutations,
+ });
+
+export default createStore;
diff --git a/app/assets/javascripts/operation_settings/store/mutation_types.js b/app/assets/javascripts/operation_settings/store/mutation_types.js
new file mode 100644
index 00000000000..237d2b6122f
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/mutation_types.js
@@ -0,0 +1,3 @@
+/* eslint-disable import/prefer-default-export */
+
+export const SET_EXTERNAL_DASHBOARD_URL = 'SET_EXTERNAL_DASHBOARD_URL';
diff --git a/app/assets/javascripts/operation_settings/store/mutations.js b/app/assets/javascripts/operation_settings/store/mutations.js
new file mode 100644
index 00000000000..64bb33bb89f
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_EXTERNAL_DASHBOARD_URL](state, url) {
+ state.externalDashboardUrl = url;
+ },
+};
diff --git a/app/assets/javascripts/operation_settings/store/state.js b/app/assets/javascripts/operation_settings/store/state.js
new file mode 100644
index 00000000000..72167141c48
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/state.js
@@ -0,0 +1,5 @@
+export default (initialState = {}) => ({
+ externalDashboardUrl: initialState.externalDashboardUrl || '',
+ operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
+ externalDashboardHelpPagePath: initialState.externalDashboardHelpPagePath,
+});
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index d5ded3f9a79..6e00e31b828 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -22,7 +22,7 @@ export default () => {
_.debounce(function onMessageInput() {
const message = $(this).val();
if (message === '') {
- $('.js-broadcast-message-preview').text('Your message here');
+ $('.js-broadcast-message-preview').text(__('Your message here'));
} else {
axios
.post(previewPath, {
diff --git a/app/assets/javascripts/pages/admin/clusters/destroy/index.js b/app/assets/javascripts/pages/admin/clusters/destroy/index.js
new file mode 100644
index 00000000000..8001d2dd1da
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/clusters/destroy/index.js
@@ -0,0 +1,5 @@
+import ClustersBundle from '~/clusters/clusters_bundle';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ClustersBundle(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/admin/clusters/edit/index.js b/app/assets/javascripts/pages/admin/clusters/edit/index.js
new file mode 100644
index 00000000000..8001d2dd1da
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/clusters/edit/index.js
@@ -0,0 +1,5 @@
+import ClustersBundle from '~/clusters/clusters_bundle';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ClustersBundle(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/admin/clusters/index.js b/app/assets/javascripts/pages/admin/clusters/index.js
new file mode 100644
index 00000000000..d0c9ae66c6a
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/clusters/index.js
@@ -0,0 +1,21 @@
+import PersistentUserCallout from '~/persistent_user_callout';
+import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+
+function initGcpSignupCallout() {
+ const callout = document.querySelector('.gcp-signup-offer');
+ PersistentUserCallout.factory(callout);
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const { page } = document.body.dataset;
+ const newClusterViews = [
+ 'admin:clusters:new',
+ 'admin:clusters:create_gcp',
+ 'admin:clusters:create_user',
+ ];
+
+ if (newClusterViews.indexOf(page) > -1) {
+ initGcpSignupCallout();
+ initGkeDropdowns();
+ }
+});
diff --git a/app/assets/javascripts/pages/admin/clusters/index/index.js b/app/assets/javascripts/pages/admin/clusters/index/index.js
new file mode 100644
index 00000000000..30d519d0e37
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/clusters/index/index.js
@@ -0,0 +1,6 @@
+import PersistentUserCallout from '~/persistent_user_callout';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const callout = document.querySelector('.gcp-signup-offer');
+ PersistentUserCallout.factory(callout);
+});
diff --git a/app/assets/javascripts/pages/admin/clusters/show/index.js b/app/assets/javascripts/pages/admin/clusters/show/index.js
new file mode 100644
index 00000000000..8001d2dd1da
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/clusters/show/index.js
@@ -0,0 +1,5 @@
+import ClustersBundle from '~/clusters/clusters_bundle';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ClustersBundle(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/admin/groups/edit/index.js b/app/assets/javascripts/pages/admin/groups/edit/index.js
index d3d125a1859..ad7276132b9 100644
--- a/app/assets/javascripts/pages/admin/groups/edit/index.js
+++ b/app/assets/javascripts/pages/admin/groups/edit/index.js
@@ -1,3 +1,3 @@
-import groupAvatar from '~/group_avatar';
+import initAvatarPicker from '~/avatar_picker';
-document.addEventListener('DOMContentLoaded', groupAvatar);
+document.addEventListener('DOMContentLoaded', initAvatarPicker);
diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js
index 21f1ce222ac..6de740ee9ce 100644
--- a/app/assets/javascripts/pages/admin/groups/new/index.js
+++ b/app/assets/javascripts/pages/admin/groups/new/index.js
@@ -1,9 +1,9 @@
import BindInOut from '../../../../behaviors/bind_in_out';
import Group from '../../../../group';
-import groupAvatar from '../../../../group_avatar';
+import initAvatarPicker from '~/avatar_picker';
document.addEventListener('DOMContentLoaded', () => {
BindInOut.initAll();
new Group(); // eslint-disable-line no-new
- groupAvatar();
+ initAvatarPicker();
});
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
index 260484726f3..ff758fcb4fe 100644
--- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -1,10 +1,11 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
- IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests();
+ addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js
index 21efc4f6d00..30d519d0e37 100644
--- a/app/assets/javascripts/pages/groups/clusters/index/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/index/index.js
@@ -2,6 +2,5 @@ import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ PersistentUserCallout.factory(callout);
});
diff --git a/app/assets/javascripts/pages/groups/details/index.js b/app/assets/javascripts/pages/groups/details/index.js
new file mode 100644
index 00000000000..3bcaa0f0232
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/details/index.js
@@ -0,0 +1,5 @@
+import initGroupDetails from '../shared/group_details';
+
+document.addEventListener('DOMContentLoaded', () => {
+ initGroupDetails('details');
+});
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 01ef445c901..d036ff07d89 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -1,4 +1,4 @@
-import groupAvatar from '~/group_avatar';
+import initAvatarPicker from '~/avatar_picker';
import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels';
@@ -9,7 +9,7 @@ import groupsSelect from '~/groups_select';
import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
- groupAvatar();
+ initAvatarPicker();
new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal();
initSettingsPanels();
diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js
index c22a164cd4e..e4f4c3b574e 100644
--- a/app/assets/javascripts/pages/groups/group_members/index/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index/index.js
@@ -1,7 +1,7 @@
/* eslint-disable no-new */
import memberExpirationDate from '~/member_expiration_date';
-import Members from '~/members';
+import Members from 'ee_else_ce/members';
import UsersSelect from '~/users_select';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js
index a63a0dbc6b1..451be6497de 100644
--- a/app/assets/javascripts/pages/groups/index.js
+++ b/app/assets/javascripts/pages/groups/index.js
@@ -3,8 +3,7 @@ import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
function initGcpSignupCallout() {
const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ PersistentUserCallout.factory(callout);
}
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 21ec3f9f9ba..35d4b034654 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,7 +1,7 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js
index fa81ad914ba..83d6ac9fd14 100644
--- a/app/assets/javascripts/pages/groups/labels/edit/index.js
+++ b/app/assets/javascripts/pages/groups/labels/edit/index.js
@@ -1,3 +1,3 @@
-import Labels from '~/labels';
+import Labels from 'ee_else_ce/labels';
document.addEventListener('DOMContentLoaded', () => new Labels());
diff --git a/app/assets/javascripts/pages/groups/labels/new/index.js b/app/assets/javascripts/pages/groups/labels/new/index.js
index fa81ad914ba..83d6ac9fd14 100644
--- a/app/assets/javascripts/pages/groups/labels/new/index.js
+++ b/app/assets/javascripts/pages/groups/labels/new/index.js
@@ -1,3 +1,3 @@
-import Labels from '~/labels';
+import Labels from 'ee_else_ce/labels';
document.addEventListener('DOMContentLoaded', () => new Labels());
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 339ce67438a..12a26fd88fa 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -1,10 +1,11 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
- IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests();
+ addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
index b2f275dc5ea..57b53eb9e5d 100644
--- a/app/assets/javascripts/pages/groups/new/index.js
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -1,9 +1,9 @@
import BindInOut from '~/behaviors/bind_in_out';
import Group from '~/group';
-import groupAvatar from '~/group_avatar';
+import initAvatarPicker from '~/avatar_picker';
document.addEventListener('DOMContentLoaded', () => {
BindInOut.initAll();
new Group(); // eslint-disable-line no-new
- groupAvatar();
+ initAvatarPicker();
});
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index ae0a8c74964..8a5300c9266 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -12,5 +12,6 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
});
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
new file mode 100644
index 00000000000..01ef3f1db2b
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -0,0 +1,31 @@
+/* eslint-disable no-new */
+
+import { getPagePath } from '~/lib/utils/common_utils';
+import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
+import NewGroupChild from '~/groups/new_group_child';
+import notificationsDropdown from '~/notifications_dropdown';
+import NotificationsForm from '~/notifications_form';
+import ProjectsList from '~/projects_list';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import GroupTabs from './group_tabs';
+
+export default function initGroupDetails(actionName = 'show') {
+ const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
+ const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
+ const paths = window.location.pathname.split('/');
+ const subpath = paths[paths.length - 1];
+ let action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
+ if (actionName && action === actionName) {
+ action = 'show'; // 'show' resets GroupTabs to default action through base class
+ }
+
+ new GroupTabs({ parentEl: '.groups-listing', action });
+ new ShortcutsNavigation();
+ new NotificationsForm();
+ notificationsDropdown();
+ new ProjectsList();
+
+ if (newGroupChildWrapper) {
+ new NewGroupChild(newGroupChildWrapper);
+ }
+}
diff --git a/app/assets/javascripts/pages/groups/show/group_tabs.js b/app/assets/javascripts/pages/groups/shared/group_tabs.js
index c6fe61d2bd9..c6fe61d2bd9 100644
--- a/app/assets/javascripts/pages/groups/show/group_tabs.js
+++ b/app/assets/javascripts/pages/groups/shared/group_tabs.js
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index 3a45fd70d02..82ee5ead83d 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -1,28 +1,7 @@
-/* eslint-disable no-new */
-
-import { getPagePath } from '~/lib/utils/common_utils';
-import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
-import NewGroupChild from '~/groups/new_group_child';
-import notificationsDropdown from '~/notifications_dropdown';
-import NotificationsForm from '~/notifications_form';
-import ProjectsList from '~/projects_list';
-import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
-import GroupTabs from './group_tabs';
+import leaveByUrl from '~/namespaces/leave_by_url';
+import initGroupDetails from '../shared/group_details';
document.addEventListener('DOMContentLoaded', () => {
- const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
- const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
- const paths = window.location.pathname.split('/');
- const subpath = paths[paths.length - 1];
- const action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
-
- new GroupTabs({ parentEl: '.groups-listing', action });
- new ShortcutsNavigation();
- new NotificationsForm();
- notificationsDropdown();
- new ProjectsList();
-
- if (newGroupChildWrapper) {
- new NewGroupChild(newGroupChildWrapper);
- }
+ leaveByUrl('group');
+ initGroupDetails();
});
diff --git a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
index a79ef07f1c5..c563514d36b 100644
--- a/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
+++ b/app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
@@ -33,8 +33,7 @@ export default {
text() {
return sprintf(
s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
- Existing project milestones with the same title will be merged.
- This action cannot be reversed.`),
+ Existing project milestones with the same title will be merged.`),
{ milestoneTitle: this.milestoneTitle, groupName: this.groupName },
);
},
@@ -72,6 +71,9 @@ export default {
<template slot="title">
{{ title }}
</template>
- {{ text }}
+ <div>
+ <p>{{ text }}</p>
+ <p>{{ s__('Milestones|This action cannot be reversed.') }}</p>
+ </div>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/pages/profiles/keys/index.js b/app/assets/javascripts/pages/profiles/keys/index.js
index 1cd3ee1dfdb..d3dcd21f456 100644
--- a/app/assets/javascripts/pages/profiles/keys/index.js
+++ b/app/assets/javascripts/pages/profiles/keys/index.js
@@ -2,6 +2,8 @@ import AddSshKeyValidation from '~/profile/add_ssh_key_validation';
document.addEventListener('DOMContentLoaded', () => {
const input = document.querySelector('.js-add-ssh-key-validation-input');
+ if (!input) return;
+
const warning = document.querySelector('.js-add-ssh-key-validation-warning');
const originalSubmit = input.form.querySelector('.js-add-ssh-key-validation-original-submit');
const confirmSubmit = warning.querySelector('.js-add-ssh-key-validation-confirm-submit');
diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js
index 0dd0d5336fc..13cb0d6f74b 100644
--- a/app/assets/javascripts/pages/profiles/show/index.js
+++ b/app/assets/javascripts/pages/profiles/show/index.js
@@ -1,8 +1,9 @@
import $ from 'jquery';
import createFlash from '~/flash';
-import GfmAutoComplete from '~/gfm_auto_complete';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import emojiRegex from 'emoji-regex';
import EmojiMenu from './emoji_menu';
+import { __ } from '~/locale';
const defaultStatusEmoji = 'speech_balloon';
@@ -48,7 +49,7 @@ document.addEventListener('DOMContentLoaded', () => {
const EMOJI_REGEX = emojiRegex();
if (EMOJI_REGEX.test(userNameInput.value)) {
// set field to invalid so it gets detected by GlFieldErrors
- userNameInput.setCustomValidity('Invalid field');
+ userNameInput.setCustomValidity(__('Invalid field'));
} else {
userNameInput.setCustomValidity('');
}
@@ -81,5 +82,5 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
})
- .catch(() => createFlash('Failed to load emoji list.'));
+ .catch(() => createFlash(__('Failed to load emoji list.')));
});
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
index 21efc4f6d00..30d519d0e37 100644
--- a/app/assets/javascripts/pages/projects/clusters/index/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -2,6 +2,5 @@ import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ PersistentUserCallout.factory(callout);
});
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 899d5925956..92ed6a652d7 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -3,17 +3,24 @@ import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
-import fileUpload from '~/lib/utils/file_upload';
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
+import initAvatarPicker from '~/avatar_picker';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
- initProjectLoadingSpinner();
- setupProjectEdit();
- // Initialize expandable settings panels
- initSettingsPanels();
- fileUpload('.js-choose-project-avatar-button', '.js-project-avatar-input');
- initProjectPermissionsSettings();
+ initAvatarPicker();
initConfirmDangerModal();
+ initSettingsPanels();
mountBadgeSettings(PROJECT_BADGE);
+
+ initProjectLoadingSpinner();
+ initProjectPermissionsSettings();
+ setupProjectEdit();
+
+ dirtySubmitFactory(
+ document.querySelectorAll(
+ '.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form',
+ ),
+ );
});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index b0345b4e50d..d4bd02c14e9 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (newClusterViews.indexOf(page) > -1) {
const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ PersistentUserCallout.factory(callout);
initGkeDropdowns();
}
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
index ffc84dc106b..aecc6484b26 100644
--- a/app/assets/javascripts/pages/projects/issues/edit/index.js
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -1,3 +1,3 @@
-import initForm from '../form';
+import initForm from 'ee_else_ce/pages/projects/issues/form';
document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index f99023ad8e7..941c4552579 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -2,7 +2,7 @@
import $ from 'jquery';
import GLForm from '~/gl_form';
-import IssuableForm from '~/issuable_form';
+import IssuableForm from 'ee_else_ce/issuable_form';
import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index bb91e38cb64..c34aff02111 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -4,9 +4,9 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
index ffc84dc106b..aecc6484b26 100644
--- a/app/assets/javascripts/pages/projects/issues/new/index.js
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -1,3 +1,3 @@
-import initForm from '../form';
+import initForm from 'ee_else_ce/pages/projects/issues/form';
document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 8987c8e3f47..0447d1f79fb 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -4,9 +4,11 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
import initIssueableApp from '~/issue_show';
+import initRelatedMergeRequestsApp from '~/related_merge_requests';
export default function() {
initIssueableApp();
+ initRelatedMergeRequestsApp();
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/labels/edit/index.js b/app/assets/javascripts/pages/projects/labels/edit/index.js
index fa81ad914ba..83d6ac9fd14 100644
--- a/app/assets/javascripts/pages/projects/labels/edit/index.js
+++ b/app/assets/javascripts/pages/projects/labels/edit/index.js
@@ -1,3 +1,3 @@
-import Labels from '~/labels';
+import Labels from 'ee_else_ce/labels';
document.addEventListener('DOMContentLoaded', () => new Labels());
diff --git a/app/assets/javascripts/pages/projects/labels/new/index.js b/app/assets/javascripts/pages/projects/labels/new/index.js
index fa81ad914ba..83d6ac9fd14 100644
--- a/app/assets/javascripts/pages/projects/labels/new/index.js
+++ b/app/assets/javascripts/pages/projects/labels/new/index.js
@@ -1,3 +1,3 @@
-import Labels from '~/labels';
+import Labels from 'ee_else_ce/labels';
document.addEventListener('DOMContentLoaded', () => new Labels());
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index ec39db12e74..0bcca22e40f 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -2,12 +2,13 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
- IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests();
+ addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index e3971618da5..8f0dc8554e2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import Diff from '~/diff';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GLForm from '~/gl_form';
-import IssuableForm from '~/issuable_form';
+import IssuableForm from 'ee_else_ce/issuable_form';
import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
diff --git a/app/assets/javascripts/pages/projects/pages_domains/edit/index.js b/app/assets/javascripts/pages/projects/pages_domains/edit/index.js
new file mode 100644
index 00000000000..27e4433ad4d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/pages_domains/form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pages_domains/form.js b/app/assets/javascripts/pages/projects/pages_domains/form.js
new file mode 100644
index 00000000000..1d0dbfe0406
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/form.js
@@ -0,0 +1,43 @@
+import setupToggleButtons from '~/toggle_buttons';
+
+export default () => {
+ const toggleContainer = document.querySelector('.js-auto-ssl-toggle-container');
+
+ if (toggleContainer) {
+ const onToggleButtonClicked = isAutoSslEnabled => {
+ Array.from(document.querySelectorAll('.js-shown-if-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.classList.remove('d-none');
+ } else {
+ el.classList.add('d-none');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-shown-unless-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.classList.add('d-none');
+ } else {
+ el.classList.remove('d-none');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-enabled-if-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.removeAttribute('disabled');
+ } else {
+ el.setAttribute('disabled', 'disabled');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-enabled-unless-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.setAttribute('disabled', 'disabled');
+ } else {
+ el.removeAttribute('disabled');
+ }
+ });
+ };
+
+ setupToggleButtons(toggleContainer, onToggleButtonClicked);
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/pages_domains/new/index.js b/app/assets/javascripts/pages/projects/pages_domains/new/index.js
new file mode 100644
index 00000000000..27e4433ad4d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/pages_domains/form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index bd4309e47ad..bb490919a9a 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -29,7 +29,7 @@ export default {
// The text input is editable when there's a custom interval, or when it's
// a preset interval and the user clicks the 'custom' radio button
isEditable() {
- return !!(this.customInputEnabled || !this.intervalIsPreset);
+ return Boolean(this.customInputEnabled || !this.intervalIsPreset);
},
},
watch: {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index 95b57d5e048..a20a0526f12 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
@@ -1,15 +1,42 @@
-/* eslint-disable class-methods-use-this */
+const defaultTimezone = { name: 'UTC', offset: 0 };
+const defaults = {
+ $inputEl: null,
+ $dropdownEl: null,
+ onSelectTimezone: null,
+ displayFormat: item => item.name,
+};
-import $ from 'jquery';
+export const formatUtcOffset = offset => {
+ const parsed = parseInt(offset, 10);
+ if (Number.isNaN(parsed) || parsed === 0) {
+ return `0`;
+ }
+ const prefix = offset > 0 ? '+' : '-';
+ return `${prefix} ${Math.abs(offset / 3600)}`;
+};
+
+export const formatTimezone = item => `[UTC ${formatUtcOffset(item.offset)}] ${item.name}`;
-const defaultTimezone = 'UTC';
+export const findTimezoneByIdentifier = (tzList = [], identifier = null) => {
+ if (tzList && tzList.length && identifier && identifier.length) {
+ return tzList.find(tz => tz.identifier === identifier) || null;
+ }
+ return null;
+};
export default class TimezoneDropdown {
- constructor() {
- this.$dropdown = $('.js-timezone-dropdown');
+ constructor({ $dropdownEl, $inputEl, onSelectTimezone, displayFormat } = defaults) {
+ this.$dropdown = $dropdownEl;
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
- this.$input = $('#schedule_cron_timezone');
+ this.$input = $inputEl;
this.timezoneData = this.$dropdown.data('data');
+
+ this.onSelectTimezone = onSelectTimezone;
+ this.displayFormat = displayFormat || defaults.displayFormat;
+
+ this.initialTimezone =
+ findTimezoneByIdentifier(this.timezoneData, this.$input.val()) || defaultTimezone;
+
this.initDefaultTimezone();
this.initDropdown();
}
@@ -19,50 +46,32 @@ export default class TimezoneDropdown {
data: this.timezoneData,
filterable: true,
selectable: true,
- toggleLabel: item => item.name,
+ toggleLabel: this.displayFormat,
search: {
fields: ['name'],
},
clicked: cfg => this.updateInputValue(cfg),
- text: item => this.formatTimezone(item),
+ text: item => formatTimezone(item),
});
- this.setDropdownToggle();
- }
-
- formatUtcOffset(offset) {
- let prefix = '';
-
- if (offset > 0) {
- prefix = '+';
- } else if (offset < 0) {
- prefix = '-';
- }
-
- return `${prefix} ${Math.abs(offset / 3600)}`;
- }
-
- formatTimezone(item) {
- return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`;
+ this.setDropdownToggle(this.displayFormat(this.initialTimezone));
}
initDefaultTimezone() {
- const initialValue = this.$input.val();
-
- if (!initialValue) {
- this.$input.val(defaultTimezone);
+ if (!this.$input.val()) {
+ this.$input.val(defaultTimezone.name);
}
}
- setDropdownToggle() {
- const initialValue = this.$input.val();
-
- this.$dropdownToggle.text(initialValue);
+ setDropdownToggle(dropdownText) {
+ this.$dropdownToggle.text(dropdownText);
}
updateInputValue({ selectedObj, e }) {
e.preventDefault();
this.$input.val(selectedObj.identifier);
- gl.pipelineScheduleFieldErrors.updateFormValidityState();
+ if (this.onSelectTimezone) {
+ this.onSelectTimezone({ selectedObj, e });
+ }
}
}
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index 4d494efef6c..dc6df27f1c7 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -41,7 +41,13 @@ export default () => {
const formElement = document.getElementById('new-pipeline-schedule-form');
- gl.timezoneDropdown = new TimezoneDropdown();
+ gl.timezoneDropdown = new TimezoneDropdown({
+ $dropdownEl: $('.js-timezone-dropdown'),
+ $inputEl: $('#schedule_cron_timezone'),
+ onSelectTimezone: () => {
+ gl.pipelineScheduleFieldErrors.updateFormValidityState();
+ },
+ });
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index b288989b252..f0d529758d5 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -39,6 +39,11 @@ export default class Project {
$label.text(activeText);
});
+ $('#modal-geo-info').data({
+ cloneUrlSecondary: $this.attr('href'),
+ cloneUrlPrimary: $this.data('primaryUrl') || '',
+ });
+
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
@@ -67,6 +72,13 @@ export default class Project {
.remove();
return e.preventDefault();
});
+ $('.hide-shared-runner-limit-message').on('click', function(e) {
+ var $alert = $(this).parents('.shared-runner-quota-message');
+ var scope = $alert.data('scope');
+ Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope });
+ $alert.remove();
+ e.preventDefault();
+ });
$('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index adbe744290a..f39765818e7 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -1,7 +1,7 @@
+import Members from 'ee_else_ce/members';
import memberExpirationDate from '../../../member_expiration_date';
import UsersSelect from '../../../users_select';
import groupsSelect from '../../../groups_select';
-import Members from '../../../members';
document.addEventListener('DOMContentLoaded', () => {
memberExpirationDate('.js-access-expiration-date-groups');
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 15c6fb550c1..885247335a4 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
// hide extra auto devops settings based checkbox state
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
new file mode 100644
index 00000000000..98e19705976
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -0,0 +1,9 @@
+import mountErrorTrackingForm from '~/error_tracking_settings';
+import mountOperationSettings from '~/operation_settings';
+import initSettingsPanels from '~/settings_panels';
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountErrorTrackingForm();
+ mountOperationSettings();
+ initSettingsPanels();
+});
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 19d9903c988..dea7c586868 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -175,11 +175,6 @@ export default {
if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
},
-
- buildsAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
- },
},
methods: {
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index bc5c29d12b5..ac0dca31c37 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export const visibilityOptions = {
PRIVATE: 0,
INTERNAL: 10,
@@ -5,9 +7,11 @@ export const visibilityOptions = {
};
export const visibilityLevelDescriptions = {
- [visibilityOptions.PRIVATE]:
+ [visibilityOptions.PRIVATE]: __(
'The project is accessible only by members of the project. Access must be granted explicitly to each user.',
- [visibilityOptions.INTERNAL]: 'The project can be accessed by any user who is logged in.',
- [visibilityOptions.PUBLIC]:
+ ),
+ [visibilityOptions.INTERNAL]: __('The project can be accessed by any user who is logged in.'),
+ [visibilityOptions.PUBLIC]: __(
'The project can be accessed by anyone, regardless of authentication.',
+ ),
};
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 7302c1ab202..6aa41d0825b 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -9,6 +9,7 @@ import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
import GpgBadges from '~/gpg_badges';
import initReadMore from '~/read_more';
+import leaveByUrl from '~/namespaces/leave_by_url';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
@@ -44,4 +45,13 @@ document.addEventListener('DOMContentLoaded', () => {
});
GpgBadges.fetch();
+ leaveByUrl('project');
+
+ if (document.getElementById('js-tree-list')) {
+ import('~/repository')
+ .then(m => m.default())
+ .catch(e => {
+ throw e;
+ });
+ }
});
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 400aed35e32..7b90a3a4f6e 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -40,4 +40,12 @@ document.addEventListener('DOMContentLoaded', () => {
}
GpgBadges.fetch();
+
+ if (document.getElementById('js-tree-list')) {
+ import('~/repository')
+ .then(m => m.default())
+ .catch(e => {
+ throw e;
+ });
+ }
});
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index 0c896c8599e..d5a8e712d6b 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import Flash from '~/flash';
import Api from '~/api';
+import { __ } from '~/locale';
export default class Search {
constructor() {
@@ -24,7 +25,7 @@ export default class Search {
data(term, callback) {
return Api.groups(term, {}, data => {
data.unshift({
- full_name: 'Any',
+ full_name: __('Any'),
});
data.splice(1, 0, 'divider');
return callback(data);
@@ -54,14 +55,14 @@ export default class Search {
this.getProjectsData(term)
.then(data => {
data.unshift({
- name_with_namespace: 'Any',
+ name_with_namespace: __('Any'),
});
data.splice(1, 0, 'divider');
return data;
})
.then(data => callback(data))
- .catch(() => new Flash('Error fetching projects'));
+ .catch(() => new Flash(__('Error fetching projects')));
},
id(obj) {
return obj.id;
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index e1a3f42a71f..3f5a3e15c2c 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import LengthValidator from './length_validator';
import UsernameValidator from './username_validator';
import NoEmojiValidator from '../../../emoji/no_emoji_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
@@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => {
+ new LengthValidator(); // eslint-disable-line no-new
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
new file mode 100644
index 00000000000..3d687ca08cc
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -0,0 +1,32 @@
+import InputValidator from '../../../validators/input_validator';
+
+const errorMessageClass = 'gl-field-error';
+
+export default class LengthValidator extends InputValidator {
+ constructor(opts = {}) {
+ super();
+
+ const container = opts.container || '';
+ const validateLengthElements = document.querySelectorAll(`${container} .js-validate-length`);
+
+ validateLengthElements.forEach(element =>
+ element.addEventListener('input', this.eventHandler.bind(this)),
+ );
+ }
+
+ eventHandler(event) {
+ this.inputDomElement = event.target;
+ this.inputErrorMessage = this.inputDomElement.parentElement.querySelector(
+ `.${errorMessageClass}`,
+ );
+
+ const { value } = this.inputDomElement;
+ const { maxLengthMessage, maxLength } = this.inputDomElement.dataset;
+
+ this.errorMessage = maxLengthMessage;
+
+ this.invalidInput = value.length > parseInt(maxLength, 10);
+
+ this.setValidationStateAndMessage();
+ }
+}
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index afa099d0e0b..693125f8a38 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -6,10 +6,16 @@ import dateFormat from 'dateformat';
import { getDayName, getDayDifference } from '~/lib/utils/datetime_utility';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
-import { __ } from '~/locale';
+import { n__, s__, __ } from '~/locale';
const d3 = { select, scaleLinear, scaleThreshold };
+const firstDayOfWeekChoices = Object.freeze({
+ sunday: 0,
+ monday: 1,
+ saturday: 6,
+});
+
const LOADING_HTML = `
<div class="text-center">
<i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i>
@@ -29,9 +35,9 @@ function formatTooltipText({ date, count }) {
const dateDayName = getDayName(dateObject);
const dateText = dateFormat(dateObject, 'mmm d, yyyy');
- let contribText = 'No contributions';
+ let contribText = __('No contributions');
if (count > 0) {
- contribText = `${count} contribution${count > 1 ? 's' : ''}`;
+ contribText = n__('%d contribution', '%d contributions', count);
}
return `${contribText}<br />${dateDayName} ${dateText}`;
}
@@ -49,7 +55,7 @@ export default class ActivityCalendar {
timestamps,
calendarActivitiesPath,
utcOffset = 0,
- firstDayOfWeek = 0,
+ firstDayOfWeek = firstDayOfWeekChoices.sunday,
monthsAgo = 12,
) {
this.calendarActivitiesPath = calendarActivitiesPath;
@@ -59,18 +65,18 @@ export default class ActivityCalendar {
this.daySize = 15;
this.daySizeWithSpace = this.daySize + this.daySpace * 2;
this.monthNames = [
- 'Jan',
- 'Feb',
- 'Mar',
- 'Apr',
- 'May',
- 'Jun',
- 'Jul',
- 'Aug',
- 'Sep',
- 'Oct',
- 'Nov',
- 'Dec',
+ __('Jan'),
+ __('Feb'),
+ __('Mar'),
+ __('Apr'),
+ __('May'),
+ __('Jun'),
+ __('Jul'),
+ __('Aug'),
+ __('Sep'),
+ __('Oct'),
+ __('Nov'),
+ __('Dec'),
];
this.months = [];
this.firstDayOfWeek = firstDayOfWeek;
@@ -193,24 +199,29 @@ export default class ActivityCalendar {
renderDayTitles() {
const days = [
{
- text: 'M',
+ text: s__('DayTitle|M'),
y: 29 + this.dayYPos(1),
},
{
- text: 'W',
+ text: s__('DayTitle|W'),
y: 29 + this.dayYPos(3),
},
{
- text: 'F',
+ text: s__('DayTitle|F'),
y: 29 + this.dayYPos(5),
},
];
- if (this.firstDayOfWeek === 1) {
+ if (this.firstDayOfWeek === firstDayOfWeekChoices.monday) {
days.push({
- text: 'S',
+ text: s__('DayTitle|S'),
y: 29 + this.dayYPos(7),
});
+ } else if (this.firstDayOfWeek === firstDayOfWeekChoices.saturday) {
+ days.push({
+ text: s__('DayTitle|S'),
+ y: 29 + this.dayYPos(6),
+ });
}
this.svg
@@ -242,11 +253,11 @@ export default class ActivityCalendar {
renderKey() {
const keyValues = [
- 'no contributions',
- '1-9 contributions',
- '10-19 contributions',
- '20-29 contributions',
- '30+ contributions',
+ __('no contributions'),
+ __('1-9 contributions'),
+ __('10-19 contributions'),
+ __('20-29 contributions'),
+ __('30+ contributions'),
];
const keyColors = [
'#ededed',
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 636308c5401..7f800d20835 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -91,6 +91,7 @@ export default class UserTabs {
this.actions = Object.keys(this.loaded);
this.bindEvents();
+ // TODO: refactor to make this configurable via constructor params with a default value of 'show'
if (this.action === 'show') {
this.action = this.defaultAction;
}
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index cdf1257b4e3..6d39abd4a1f 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -1,6 +1,6 @@
<script>
-import pdfjsLib from 'vendor/pdf';
-import workerSrc from 'vendor/pdf.worker.min';
+import pdfjsLib from 'pdfjs-dist/build/pdf';
+import workerSrc from 'pdfjs-dist/build/pdf.worker.min';
import page from './page/index.vue';
@@ -28,7 +28,7 @@ export default {
},
watch: { pdf: 'load' },
mounted() {
- pdfjsLib.PDFJS.workerSrc = workerSrc;
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
methods: {
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index c729198c1d3..8f3ba9779fb 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -1,9 +1,11 @@
<script>
import GlModal from '~/vue_shared/components/gl_modal.vue';
+import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
GlModal,
+ Icon,
},
props: {
currentRequest: {
@@ -38,7 +40,11 @@ export default {
};
</script>
<template>
- <div v-if="currentRequest.details" :id="`peek-view-${metric}`" class="view">
+ <div
+ v-if="currentRequest.details"
+ :id="`peek-view-${metric}`"
+ class="view qa-performance-bar-detailed-metric"
+ >
<button
:data-target="`#modal-peek-${metric}-details`"
class="btn-blank btn-link bold"
@@ -57,9 +63,31 @@ export default {
<template v-if="detailsList.length">
<tr v-for="(item, index) in detailsList" :key="index">
<td>
- <strong>{{ item.duration }}ms</strong>
+ <span>{{ item.duration }}ms</span>
+ </td>
+ <td>
+ <div class="js-toggle-container">
+ <div
+ v-for="(key, keyIndex) in keys"
+ :key="key"
+ class="break-word"
+ :class="{ 'mb-3 bold': keyIndex == 0 }"
+ >
+ {{ item[key] }}
+ <button
+ v-if="keyIndex == 0 && item.backtrace"
+ class="text-expander js-toggle-button"
+ type="button"
+ :aria-label="__('Toggle backtrace')"
+ >
+ <icon :size="12" name="ellipsis_h" />
+ </button>
+ </div>
+ <pre v-if="item.backtrace" class="backtrace-row js-toggle-content mt-2">{{
+ item.backtrace
+ }}</pre>
+ </div>
</td>
- <td v-for="key in keys" :key="key" class="break-word">{{ item[key] }}</td>
</tr>
</template>
<template v-else>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 1ec2784cc5a..48515cf785c 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -92,7 +92,7 @@ export default {
</script>
<template>
<div id="js-peek" :class="env">
- <div v-if="currentRequest" class="d-flex container-fluid container-limited">
+ <div v-if="currentRequest" class="d-flex container-fluid container-limited qa-performance-bar">
<div id="peek-view-host" class="view">
<span
v-if="hasHost"
diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue
index fdb5c0d6939..297507b85af 100644
--- a/app/assets/javascripts/performance_bar/components/request_selector.vue
+++ b/app/assets/javascripts/performance_bar/components/request_selector.vue
@@ -37,7 +37,12 @@ export default {
<template>
<div id="peek-request-selector">
<select v-model="currentRequestId">
- <option v-for="request in requests" :key="request.id" :value="request.id">
+ <option
+ v-for="request in requests"
+ :key="request.id"
+ :value="request.id"
+ class="qa-performance-bar-request"
+ >
{{ truncatedUrl(request.url) }}
</option>
</select>
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
index 1e34e74a152..4a08e158f6b 100644
--- a/app/assets/javascripts/persistent_user_callout.js
+++ b/app/assets/javascripts/persistent_user_callout.js
@@ -31,4 +31,12 @@ export default class PersistentUserCallout {
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
});
}
+
+ static factory(container) {
+ if (!container) {
+ return undefined;
+ }
+
+ return new PersistentUserCallout(container);
+ }
}
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index 8ca539351a7..3c85bb61ce8 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import { GlTooltipDirective, GlButton, GlLoadingIcon } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { dasherize } from '~/lib/utils/text_utility';
import { __ } from '~/locale';
@@ -20,6 +20,7 @@ export default {
components: {
Icon,
GlButton,
+ GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -41,6 +42,7 @@ export default {
data() {
return {
isDisabled: false,
+ isLoading: false,
};
},
computed: {
@@ -59,15 +61,19 @@ export default {
onClickAction() {
this.$root.$emit('bv::hide::tooltip', `js-ci-action-${this.link}`);
this.isDisabled = true;
+ this.isLoading = true;
axios
.post(`${this.link}.json`)
.then(() => {
this.isDisabled = false;
+ this.isLoading = false;
+
this.$emit('pipelineActionRequestComplete');
})
.catch(() => {
this.isDisabled = false;
+ this.isLoading = false;
createFlash(__('An error occurred while making the request.'));
});
@@ -82,10 +88,10 @@ export default {
:title="tooltipText"
:class="cssClass"
:disabled="isDisabled"
- class="js-ci-action btn btn-blank
-btn-transparent ci-action-icon-container ci-action-icon-wrapper"
+ class="js-ci-action btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper"
@click="onClickAction"
>
- <icon :name="actionIcon" />
+ <gl-loading-icon v-if="isLoading" class="js-action-icon-loading" />
+ <icon v-else :name="actionIcon" />
</gl-button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index a49dc311bd0..ba0dea626dc 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -24,6 +24,7 @@ export default {
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
+ :action="stage.status.action"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
index 482898b80c4..ebd7a17040a 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
@@ -69,7 +69,9 @@ export default {
>
<ci-icon :status="group.status" />
- <span class="ci-status-text"> {{ group.name }} </span>
+ <span class="ci-status-text text-truncate mw-70p gl-pl-1 d-inline-block align-bottom">
+ {{ group.name }}
+ </span>
<span class="dropdown-counter-badge"> {{ group.size }} </span>
</button>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index 2b32a6e4a98..0d5afe04e8e 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -57,6 +57,9 @@ export default {
},
},
computed: {
+ boundary() {
+ return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
+ },
status() {
return this.job && this.job.status ? this.job.status : {};
},
@@ -104,7 +107,7 @@ export default {
<div class="ci-job-component">
<gl-link
v-if="status.has_details"
- v-gl-tooltip
+ v-gl-tooltip="{ boundary, placement: 'bottom' }"
:href="status.details_path"
:title="tooltipText"
:class="cssClassJobName"
@@ -115,7 +118,7 @@ export default {
<div
v-else
- v-gl-tooltip
+ v-gl-tooltip="{ boundary, placement: 'bottom' }"
:title="tooltipText"
:class="cssClassJobName"
class="js-job-component-tooltip non-details-job-component"
diff --git a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
index 1bfab2a7fc0..02451839330 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
@@ -27,7 +27,8 @@ export default {
<template>
<span class="ci-job-name-component">
<ci-icon :status="status" />
-
- <span class="ci-status-text"> {{ name }} </span>
+ <span class="ci-status-text text-truncate mw-70p gl-pl-1 d-inline-block align-bottom">
+ {{ name }}
+ </span>
</span>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 09a50d25020..d5c124dc0ca 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,13 +1,17 @@
<script>
import _ from 'underscore';
+import stageColumnMixin from 'ee_else_ce/pipelines/mixins/stage_column_mixin';
import JobItem from './job_item.vue';
import JobGroupDropdown from './job_group_dropdown.vue';
+import ActionComponent from './action_component.vue';
export default {
components: {
JobItem,
JobGroupDropdown,
+ ActionComponent,
},
+ mixins: [stageColumnMixin],
props: {
title: {
type: String,
@@ -27,14 +31,21 @@ export default {
required: false,
default: '',
},
+ action: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ hasAction() {
+ return !_.isEmpty(this.action);
+ },
},
methods: {
groupId(group) {
return `ci-badge-${_.escape(group.name)}`;
},
- buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
- },
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
},
@@ -43,7 +54,18 @@ export default {
</script>
<template>
<li :class="stageConnectorClass" class="stage-column">
- <div class="stage-name">{{ title }}</div>
+ <div class="stage-name position-relative">
+ {{ title }}
+ <action-component
+ v-if="hasAction"
+ :action-icon="action.icon"
+ :tooltip-text="action.title"
+ :link="action.path"
+ class="js-stage-action stage-action position-absolute position-top-0 rounded"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
+ />
+ </div>
+
<div class="builds-container">
<ul>
<li
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index b2e365e5cde..f3a71ee434c 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -83,6 +83,8 @@ export default {
v-if="shouldRenderContent"
:status="status"
:item-id="pipeline.id"
+ :item-iid="pipeline.iid"
+ :item-id-tooltip="__('Pipeline ID (IID)')"
:time="pipeline.created_at"
:user="pipeline.user"
:actions="actions"
diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
new file mode 100644
index 00000000000..4cafd147511
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
@@ -0,0 +1,97 @@
+<script>
+import _ from 'underscore';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { GlLink } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { s__, sprintf } from '~/locale';
+
+/**
+ * Pipeline Stop Modal.
+ *
+ * Renders the modal used to confirm stopping a pipeline.
+ */
+export default {
+ components: {
+ GlModal,
+ GlLink,
+ ClipboardButton,
+ CiIcon,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ deep: true,
+ },
+ },
+ computed: {
+ modalTitle() {
+ return sprintf(
+ s__('Pipeline|Stop pipeline #%{pipelineId}?'),
+ {
+ pipelineId: `${this.pipeline.id}`,
+ },
+ false,
+ );
+ },
+ modalText() {
+ return sprintf(
+ s__(`Pipeline|You’re about to stop pipeline %{pipelineId}.`),
+ {
+ pipelineId: `<strong>#${this.pipeline.id}</strong>`,
+ },
+ false,
+ );
+ },
+ hasRef() {
+ return !_.isEmpty(this.pipeline.ref);
+ },
+ },
+ methods: {
+ emitSubmit(event) {
+ this.$emit('submit', event);
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ id="confirmation-modal"
+ :header-title-text="modalTitle"
+ :footer-primary-button-text="s__('Pipeline|Stop pipeline')"
+ footer-primary-button-variant="danger"
+ @submit="emitSubmit($event)"
+ >
+ <p v-html="modalText"></p>
+
+ <p v-if="pipeline">
+ <ci-icon
+ v-if="pipeline.details"
+ :status="pipeline.details.status"
+ class="vertical-align-middle"
+ />
+
+ <span class="font-weight-bold">{{ __('Pipeline') }}</span>
+
+ <a :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
+ >#{{ pipeline.id }}</a
+ >
+ <template v-if="hasRef">
+ {{ __('from') }}
+ <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
+ </template>
+ </p>
+
+ <template v-if="pipeline.commit">
+ <p>
+ <span class="font-weight-bold">{{ __('Commit') }}</span>
+
+ <gl-link :href="pipeline.commit.commit_path" class="js-commit-sha commit-sha link-commit">
+ {{ pipeline.commit.short_id }}
+ </gl-link>
+ </p>
+ <p>{{ pipeline.commit.title }}</p>
+ </template>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
new file mode 100644
index 00000000000..740b54cd8e0
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
@@ -0,0 +1,35 @@
+<script>
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+export default {
+ components: {
+ UserAvatarLink,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ user() {
+ return this.pipeline.user;
+ },
+ },
+};
+</script>
+<template>
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-triggerer">
+ <user-avatar-link
+ v-if="user"
+ :link-href="user.path"
+ :img-src="user.avatar_url"
+ :img-size="26"
+ :tooltip-text="user.name"
+ class="prepend-left-default js-pipeline-url-user"
+ />
+ <span v-else class="prepend-left-default js-pipeline-url-api api">
+ {{ s__('Pipelines|API') }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 918622ef8dc..00c02e15562 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -2,6 +2,7 @@
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import popover from '~/vue_shared/directives/popover';
@@ -19,6 +20,7 @@ export default {
components: {
UserAvatarLink,
GlLink,
+ PipelineLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -59,19 +61,13 @@ export default {
};
</script>
<template>
- <div class="table-section section-15 d-none d-sm-none d-md-block pipeline-tags">
- <gl-link :href="pipeline.path" class="js-pipeline-url-link">
- <span class="pipeline-id">#{{ pipeline.id }}</span>
- </gl-link>
- <span>by</span>
- <user-avatar-link
- v-if="user"
- :link-href="user.path"
- :img-src="user.avatar_url"
- :tooltip-text="user.name"
- class="js-pipeline-url-user"
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags section-wrap">
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="js-pipeline-url-link"
/>
- <span v-if="!user" class="js-pipeline-url-api api"> API </span>
<div class="label-container">
<span
v-if="pipeline.flags.latest"
@@ -110,12 +106,12 @@ export default {
{{ __('stuck') }}
</span>
<span
- v-if="pipeline.flags.merge_request"
+ v-if="pipeline.flags.detached_merge_request_pipeline"
v-gl-tooltip
- :title="__('This pipeline is run in a merge request context')"
- class="js-pipeline-url-mergerequest badge badge-info"
+ :title="__('This pipeline is run on the source branch')"
+ class="js-pipeline-url-detached badge badge-info"
>
- {{ __('merge request') }}
+ {{ __('detached') }}
</span>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 1c60ae6a152..03d332cd430 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -1,7 +1,7 @@
<script>
-import Modal from '~/vue_shared/components/gl_modal.vue';
-import { s__, sprintf } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
+import PipelineStopModal from './pipeline_stop_modal.vue';
import eventHub from '../event_hub';
/**
@@ -12,7 +12,10 @@ import eventHub from '../event_hub';
export default {
components: {
PipelinesTableRowComponent,
- Modal,
+ PipelineStopModal,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
props: {
pipelines: {
@@ -36,30 +39,11 @@ export default {
data() {
return {
pipelineId: 0,
+ pipeline: {},
endpoint: '',
cancelingPipeline: null,
};
},
- computed: {
- modalTitle() {
- return sprintf(
- s__('Pipeline|Stop pipeline #%{pipelineId}?'),
- {
- pipelineId: `${this.pipelineId}`,
- },
- false,
- );
- },
- modalText() {
- return sprintf(
- s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'),
- {
- pipelineId: `<strong>#${this.pipelineId}</strong>`,
- },
- false,
- );
- },
- },
created() {
eventHub.$on('openConfirmationModal', this.setModalData);
},
@@ -68,7 +52,8 @@ export default {
},
methods: {
setModalData(data) {
- this.pipelineId = data.pipelineId;
+ this.pipelineId = data.pipeline.id;
+ this.pipeline = data.pipeline;
this.endpoint = data.endpoint;
},
onSubmit() {
@@ -81,16 +66,19 @@ export default {
<template>
<div class="ci-table">
<div class="gl-responsive-table-row table-row-header" role="row">
- <div class="table-section section-10 js-pipeline-status pipeline-status" role="rowheader">
+ <div class="table-section section-10 js-pipeline-status" role="rowheader">
{{ s__('Pipeline|Status') }}
</div>
- <div class="table-section section-15 js-pipeline-info pipeline-info" role="rowheader">
+ <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
{{ s__('Pipeline|Pipeline') }}
</div>
+ <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
+ {{ s__('Pipeline|Triggerer') }}
+ </div>
<div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
{{ s__('Pipeline|Commit') }}
</div>
- <div class="table-section section-20 js-pipeline-stages pipeline-stages" role="rowheader">
+ <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
{{ s__('Pipeline|Stages') }}
</div>
</div>
@@ -103,15 +91,6 @@ export default {
:view-type="viewType"
:canceling-pipeline="cancelingPipeline"
/>
-
- <modal
- id="confirmation-modal"
- :header-title-text="modalTitle"
- :footer-primary-button-text="s__('Pipeline|Stop pipeline')"
- footer-primary-button-variant="danger"
- @submit="onSubmit"
- >
- <span v-html="modalText"></span>
- </modal>
+ <pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index da42698c255..e32e2f785bd 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -5,6 +5,7 @@ import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
+import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelinesTimeago from './time_ago.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -23,6 +24,7 @@ export default {
CommitComponent,
PipelineStage,
PipelineUrl,
+ PipelineTriggerer,
CiBadge,
PipelinesTimeago,
LoadingButton,
@@ -243,7 +245,7 @@ export default {
methods: {
handleCancelClick() {
eventHub.$emit('openConfirmationModal', {
- pipelineId: this.pipeline.id,
+ pipeline: this.pipeline,
endpoint: this.pipeline.cancel_path,
});
},
@@ -264,23 +266,25 @@ export default {
</div>
<pipeline-url :pipeline="pipeline" :auto-devops-help-path="autoDevopsHelpPath" />
+ <pipeline-triggerer :pipeline="pipeline" />
- <div class="table-section section-20">
+ <div class="table-section section-wrap section-20">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Commit') }}</div>
<div class="table-mobile-content">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
- :show-branch="!isChildView"
+ :show-ref-info="!isChildView"
/>
</div>
</div>
- <div class="table-section section-wrap section-20 stage-cell">
+ <div class="table-section section-wrap section-15 stage-cell">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
<div class="table-mobile-content">
<template v-if="pipeline.details.stages.length > 0">
diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
new file mode 100644
index 00000000000..dd79ade5bc9
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
@@ -0,0 +1,16 @@
+import Flash from '~/flash';
+import { __ } from '~/locale';
+
+export default {
+ methods: {
+ clickTriggeredByPipeline() {},
+ clickTriggeredPipeline() {},
+ requestRefreshPipelineGraph() {
+ // When an action is clicked
+ // (wether in the dropdown or in the main nodes, we refresh the big graph)
+ this.mediator
+ .refreshPipeline()
+ .catch(() => Flash(__('An error occurred while making the request.')));
+ },
+ },
+};
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 74ca3071364..3cc9d0a3a4e 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -27,11 +27,7 @@ export default {
},
computed: {
shouldRenderPagination() {
- return (
- !this.isLoading &&
- this.state.pipelines.length &&
- this.state.pageInfo.total > this.state.pageInfo.perPage
- );
+ return !this.isLoading;
},
},
beforeMount() {
diff --git a/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js b/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js
new file mode 100644
index 00000000000..64283ed0e58
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js
@@ -0,0 +1,7 @@
+export default {
+ methods: {
+ buildConnnectorClass(index) {
+ return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ },
+ },
+};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index dc9befe6349..b8976f77bac 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -2,8 +2,9 @@ import Vue from 'vue';
import Flash from '~/flash';
import Translate from '~/vue_shared/translate';
import { __ } from '~/locale';
+import pipelineGraph from 'ee_else_ce/pipelines/components/graph/graph_component.vue';
+import GraphEEMixin from 'ee_else_ce/pipelines/mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
-import pipelineGraph from './components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
@@ -22,28 +23,25 @@ export default () => {
components: {
pipelineGraph,
},
+ mixins: [GraphEEMixin],
data() {
return {
mediator,
};
},
- methods: {
- requestRefreshPipelineGraph() {
- // When an action is clicked
- // (wether in the dropdown or in the main nodes, we refresh the big graph)
- this.mediator
- .refreshPipeline()
- .catch(() => Flash(__('An error occurred while making the request.')));
- },
- },
render(createElement) {
return createElement('pipeline-graph', {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
+ mediator: this.mediator,
},
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
+ onClickTriggeredBy: (parentPipeline, pipeline) =>
+ this.clickTriggeredByPipeline(parentPipeline, pipeline),
+ onClickTriggered: (parentPipeline, pipeline) =>
+ this.clickTriggeredPipeline(parentPipeline, pipeline),
},
});
},
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index bd1e1895660..d67d88c4dba 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -19,6 +19,7 @@ export default class pipelinesMediator {
this.poll = new Poll({
resource: this.service,
method: 'getPipeline',
+ data: this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
successCallback: this.successCallback.bind(this),
errorCallback: this.errorCallback.bind(this),
});
@@ -56,6 +57,19 @@ export default class pipelinesMediator {
.getPipeline()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback())
- .finally(() => this.poll.restart());
+ .finally(() =>
+ this.poll.restart(
+ this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
+ ),
+ );
+ }
+
+ /**
+ * Backend expects paramets in the following format: `expanded[]=id&expanded[]=id`
+ */
+ getExpandedParameters() {
+ return {
+ expanded: this.store.state.expandedPipelines,
+ };
}
}
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
index a53a9cc8365..e44eb9cdfd1 100644
--- a/app/assets/javascripts/pipelines/services/pipeline_service.js
+++ b/app/assets/javascripts/pipelines/services/pipeline_service.js
@@ -5,8 +5,8 @@ export default class PipelineService {
this.pipeline = endpoint;
}
- getPipeline() {
- return axios.get(this.pipeline);
+ getPipeline(params) {
+ return axios.get(this.pipeline, { params });
}
// eslint-disable-next-line class-methods-use-this
diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js
index 052e34a8aef..259278b6410 100644
--- a/app/assets/javascripts/pipelines/stores/pipeline_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipeline_store.js
@@ -1,7 +1,6 @@
export default class PipelineStore {
constructor() {
this.state = {};
-
this.state.pipeline = {};
}
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index 59c13e1a042..f0d9642a2b2 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -35,7 +35,7 @@ export default () => {
return createElement('delete-account-modal', {
props: {
actionUrl: deleteAccountModalEl.dataset.actionUrl,
- confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword,
+ confirmWithPassword: Boolean(deleteAccountModalEl.dataset.confirmWithPassword),
username: deleteAccountModalEl.dataset.username,
},
});
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index deacff5abe7..8dd37aee7e1 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -2,6 +2,9 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import flash from '../flash';
import { parseBoolean } from '~/lib/utils/common_utils';
+import TimezoneDropdown, {
+ formatTimezone,
+} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown';
export default class Profile {
constructor({ form } = {}) {
@@ -10,6 +13,14 @@ export default class Profile {
this.setRepoRadio();
this.bindEvents();
this.initAvatarGlCrop();
+
+ this.$inputEl = $('#user_timezone');
+
+ this.timezoneDropdown = new TimezoneDropdown({
+ $inputEl: this.$inputEl,
+ $dropdownEl: $('.js-timezone-dropdown'),
+ displayFormat: selectedItem => formatTimezone(selectedItem),
+ });
}
initAvatarGlCrop() {
@@ -28,6 +39,7 @@ export default class Profile {
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
+ $('.js-group-notification-email').on('change', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
this.form.on('submit', this.onSubmitForm);
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index d3c604dcee1..5395e14cc79 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -38,9 +38,9 @@ export default class ProjectLabelSubscription {
let newAction;
if (oldStatus === 'unsubscribed') {
- [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
+ [newStatus, newAction] = ['subscribed', __('Unsubscribe')];
} else {
- [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
+ [newStatus, newAction] = ['unsubscribed', __('Subscribe')];
}
$btn.removeClass('disabled');
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 5ee510eb11d..dbe354a547b 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -3,6 +3,7 @@
import $ from 'jquery';
import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
+import { s__ } from './locale';
export default function projectSelect() {
import(/* webpackChunkName: 'select2' */ 'select2/select2')
@@ -21,9 +22,9 @@ export default function projectSelect() {
this.includeProjectsInSubgroups = $(select).data('includeProjectsInSubgroups') || false;
this.allowClear = $(select).data('allowClear') || false;
- placeholder = 'Search for project';
+ placeholder = s__('ProjectSelect|Search for project');
if (this.includeGroups) {
- placeholder += ' or group';
+ placeholder += s__('ProjectSelect| or group');
}
$(select).select2({
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
index 5f8a4946f4a..fd5d5f86401 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
@@ -34,7 +34,7 @@ export default {
},
errorMessage() {
return sprintf(
- s__('ClusterIntegration|An error occured while trying to fetch project zones: %{error}'),
+ s__('ClusterIntegration|An error occurred while trying to fetch project zones: %{error}'),
{ error: this.gapiError },
);
},
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
index 4834a856271..f05ad7773a2 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
@@ -57,7 +57,7 @@ export const validateProjectBilling = ({ dispatch, commit, state }) =>
resp => {
const { billingEnabled } = resp.result;
- commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled);
+ commit(types.SET_PROJECT_BILLING_STATUS, Boolean(billingEnabled));
dispatch('setIsValidatingProjectBilling', false);
resolve();
},
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
index e39f02d0894..f9e2e2f74fb 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
@@ -1,3 +1,3 @@
-export const hasProject = state => !!state.selectedProject.projectId;
-export const hasZone = state => !!state.selectedZone;
-export const hasMachineType = state => !!state.selectedMachineType;
+export const hasProject = state => Boolean(state.selectedProject.projectId);
+export const hasZone = state => Boolean(state.selectedZone);
+export const hasMachineType = state => Boolean(state.selectedMachineType);
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 6fb25622a05..ea82ff4e340 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
import { slugifyWithHyphens } from '../lib/utils/text_utility';
+import { s__ } from '~/locale';
let hasUserDefinedProjectPath = false;
@@ -114,59 +115,71 @@ const bindEvents = () => {
const value = $(this).val();
const templates = {
rails: {
- text: 'Ruby on Rails',
+ text: s__('ProjectTemplates|Ruby on Rails'),
icon: '.template-option .icon-rails',
},
express: {
- text: 'NodeJS Express',
+ text: s__('ProjectTemplates|NodeJS Express'),
icon: '.template-option .icon-express',
},
spring: {
- text: 'Spring',
+ text: s__('ProjectTemplates|Spring'),
icon: '.template-option .icon-spring',
},
+ iosswift: {
+ text: s__('ProjectTemplates|iOS (Swift)'),
+ icon: '.template-option svg.icon-gitlab',
+ },
dotnetcore: {
- text: '.NET Core',
+ text: s__('ProjectTemplates|.NET Core'),
icon: '.template-option .icon-dotnet',
},
+ android: {
+ text: s__('ProjectTemplates|Android'),
+ icon: '.template-option svg.icon-android',
+ },
+ gomicro: {
+ text: s__('ProjectTemplates|Go Micro'),
+ icon: '.template-option .icon-gomicro',
+ },
hugo: {
- text: 'Pages/Hugo',
+ text: s__('ProjectTemplates|Pages/Hugo'),
icon: '.template-option .icon-hugo',
},
jekyll: {
- text: 'Pages/Jekyll',
+ text: s__('ProjectTemplates|Pages/Jekyll'),
icon: '.template-option .icon-jekyll',
},
plainhtml: {
- text: 'Pages/Plain HTML',
+ text: s__('ProjectTemplates|Pages/Plain HTML'),
icon: '.template-option .icon-plainhtml',
},
gitbook: {
- text: 'Pages/GitBook',
+ text: s__('ProjectTemplates|Pages/GitBook'),
icon: '.template-option .icon-gitbook',
},
hexo: {
- text: 'Pages/Hexo',
+ text: s__('ProjectTemplates|Pages/Hexo'),
icon: '.template-option .icon-hexo',
},
nfhugo: {
- text: 'Netlify/Hugo',
+ text: s__('ProjectTemplates|Netlify/Hugo'),
icon: '.template-option .icon-netlify',
},
nfjekyll: {
- text: 'Netlify/Jekyll',
+ text: s__('ProjectTemplates|Netlify/Jekyll'),
icon: '.template-option .icon-netlify',
},
nfplainhtml: {
- text: 'Netlify/Plain HTML',
+ text: s__('ProjectTemplates|Netlify/Plain HTML'),
icon: '.template-option .icon-netlify',
},
nfgitbook: {
- text: 'Netlify/GitBook',
+ text: s__('ProjectTemplates|Netlify/GitBook'),
icon: '.template-option .icon-netlify',
},
nfhexo: {
- text: 'Netlify/Hexo',
+ text: s__('ProjectTemplates|Netlify/Hexo'),
icon: '.template-option .icon-netlify',
},
};
@@ -205,6 +218,12 @@ const bindEvents = () => {
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
+ $('.js-import-git-toggle-button').on('click', () => {
+ const $projectMirror = $('#project_mirror');
+
+ $projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
+ });
+
$projectName.on('keyup change', () => {
onProjectNameChange($projectName, $projectPath);
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
index 40a873833e1..41e295387ae 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default class ProtectedBranchAccessDropdown {
constructor(options) {
this.options = options;
@@ -15,7 +17,7 @@ export default class ProtectedBranchAccessDropdown {
if ($el.is('.is-active')) {
return item.text;
}
- return 'Select';
+ return __('Select');
},
clicked(options) {
options.e.preventDefault();
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 48343c8ba0a..16ecd5523d6 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor';
+import { __ } from '~/locale';
export default class ProtectedBranchCreate {
constructor() {
@@ -35,7 +36,7 @@ export default class ProtectedBranchCreate {
this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown,
- defaultToggleLabel: 'Protected Branch',
+ defaultToggleLabel: __('Protected Branch'),
fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
getData: ProtectedBranchCreate.getProtectedBranches,
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js
index 5bc08f60d16..08d8c9919dd 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js
@@ -1,6 +1,7 @@
import flash from '../flash';
import axios from '../lib/utils/axios_utils';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
+import { __ } from '~/locale';
export default class ProtectedBranchEdit {
constructor(options) {
@@ -68,7 +69,7 @@ export default class ProtectedBranchEdit {
this.$allowedToPushDropdown.enable();
flash(
- 'Failed to update branch!',
+ __('Failed to update branch!'),
'alert',
document.querySelector('.js-protected-branches-list'),
);
diff --git a/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js b/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js
index b803da798d5..def2f091947 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default class ProtectedTagAccessDropdown {
constructor(options) {
this.options = options;
@@ -15,7 +17,7 @@ export default class ProtectedTagAccessDropdown {
if ($el.is('.is-active')) {
return item.text;
}
- return 'Select';
+ return __('Select');
},
clicked(options) {
options.e.preventDefault();
diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js
index fddf2674cbb..03a5fe6b353 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_create.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_create.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
+import { __ } from '~/locale';
export default class ProtectedTagCreate {
constructor() {
@@ -27,7 +28,7 @@ export default class ProtectedTagCreate {
// Protected tag dropdown
this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'),
- defaultToggleLabel: 'Protected Tag',
+ defaultToggleLabel: __('Protected Tag'),
fieldName: 'protected_tag[name]',
onSelect: this.onSelectCallback,
getData: ProtectedTagCreate.getProtectedTags,
diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js
index c52497e62f2..70bfd71abce 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_edit.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js
@@ -1,6 +1,7 @@
import flash from '../flash';
import axios from '../lib/utils/axios_utils';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
+import { __ } from '~/locale';
export default class ProtectedTagEdit {
constructor(options) {
@@ -47,7 +48,11 @@ export default class ProtectedTagEdit {
.catch(() => {
this.$allowedToCreateDropdownButton.enable();
- flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
+ flash(
+ __('Failed to update tag!'),
+ 'alert',
+ document.querySelector('.js-protected-tags-list'),
+ );
});
}
}
diff --git a/app/assets/javascripts/raven/index.js b/app/assets/javascripts/raven/index.js
index edc2293915f..4dd0175e528 100644
--- a/app/assets/javascripts/raven/index.js
+++ b/app/assets/javascripts/raven/index.js
@@ -4,8 +4,11 @@ const index = function index() {
RavenConfig.init({
sentryDsn: gon.sentry_dsn,
currentUserId: gon.current_user_id,
- whitelistUrls: [gon.gitlab_url],
- isProduction: process.env.NODE_ENV,
+ whitelistUrls:
+ process.env.NODE_ENV === 'production'
+ ? [gon.gitlab_url]
+ : [gon.gitlab_url, 'webpack-internal://'],
+ environment: gon.sentry_environment,
release: gon.revision,
tags: {
revision: gon.revision,
diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js
index 338006ce2b9..7259e0df104 100644
--- a/app/assets/javascripts/raven/raven_config.js
+++ b/app/assets/javascripts/raven/raven_config.js
@@ -1,5 +1,6 @@
import Raven from 'raven-js';
import $ from 'jquery';
+import { __ } from '~/locale';
const IGNORE_ERRORS = [
// Random plugins/extensions
@@ -9,9 +10,9 @@ const IGNORE_ERRORS = [
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
- "Can't find variable: ZiteReader",
- 'jigsaw is not defined',
- 'ComboSearch is not defined',
+ __("Can't find variable: ZiteReader"),
+ __('jigsaw is not defined'),
+ __('ComboSearch is not defined'),
'http://loading.retry.widdit.com/',
'atomicFindClose',
// Facebook borked
@@ -61,7 +62,7 @@ const RavenConfig = {
release: this.options.release,
tags: this.options.tags,
whitelistUrls: this.options.whitelistUrls,
- environment: this.options.isProduction ? 'production' : 'development',
+ environment: this.options.environment,
ignoreErrors: this.IGNORE_ERRORS,
ignoreUrls: this.IGNORE_URLS,
shouldSendCallback: this.shouldSendSample.bind(this),
@@ -80,7 +81,7 @@ const RavenConfig = {
handleRavenErrors(event, req, config, err) {
const error = err || req.statusText;
- const responseText = req.responseText || 'Unknown response text';
+ const responseText = req.responseText || __('Unknown response text');
Raven.captureMessage(error, {
extra: {
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
index 1ac699c538f..8ace6657ad1 100644
--- a/app/assets/javascripts/registry/stores/mutations.js
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -9,7 +9,7 @@ export default {
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
- canDelete: !!el.destroy_path,
+ canDelete: Boolean(el.destroy_path),
destroyPath: el.destroy_path,
id: el.id,
isLoading: false,
@@ -42,7 +42,7 @@ export default {
location: element.location,
createdAt: element.created_at,
destroyPath: element.destroy_path,
- canDelete: !!element.destroy_path,
+ canDelete: Boolean(element.destroy_path),
}));
},
diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
new file mode 100644
index 00000000000..6d908524da9
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
@@ -0,0 +1,118 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { sprintf, n__, s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
+import { parseIssuableData } from '../../issue_show/utils/parse_data';
+
+export default {
+ name: 'RelatedMergeRequests',
+ components: {
+ Icon,
+ GlLoadingIcon,
+ RelatedIssuableItem,
+ },
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['isFetchingMergeRequests', 'mergeRequests', 'totalCount']),
+ closingMergeRequestsText() {
+ if (!this.hasClosingMergeRequest) {
+ return '';
+ }
+
+ const mrText = n__(
+ 'When this merge request is accepted',
+ 'When these merge requests are accepted',
+ this.totalCount,
+ );
+
+ return sprintf(s__('%{mrText}, this issue will be closed automatically.'), { mrText });
+ },
+ },
+ mounted() {
+ this.setInitialState({ apiEndpoint: this.endpoint });
+ this.fetchMergeRequests();
+ },
+ created() {
+ this.hasClosingMergeRequest = parseIssuableData().hasClosingMergeRequest;
+ },
+ methods: {
+ ...mapActions(['setInitialState', 'fetchMergeRequests']),
+ getAssignees(mr) {
+ if (mr.assignees) {
+ return mr.assignees;
+ }
+
+ return mr.assignee ? [mr.assignee] : [];
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)">
+ <div id="merge-requests" class="card-slim mt-3">
+ <div class="card-header">
+ <div class="card-title mt-0 mb-0 h5 merge-requests-title">
+ <span class="mr-1">
+ {{ __('Related merge requests') }}
+ </span>
+ <div v-if="totalCount" class="d-inline-flex lh-100 align-middle">
+ <div class="mr-count-badge">
+ <div class="mr-count-badge-count">
+ <svg class="s16 mr-1 text-secondary">
+ <icon name="merge-request" class="mr-1 text-secondary" />
+ </svg>
+ <span class="js-items-count">{{ totalCount }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div v-if="isFetchingMergeRequests" class="qa-related-merge-requests-loading-icon">
+ <gl-loading-icon label="Fetching related merge requests" class="py-2" />
+ </div>
+ <ul v-else class="content-list related-items-list">
+ <li v-for="mr in mergeRequests" :key="mr.id" class="list-item pt-0 pb-0">
+ <related-issuable-item
+ :id-key="mr.id"
+ :display-reference="mr.reference"
+ :title="mr.title"
+ :milestone="mr.milestone"
+ :assignees="getAssignees(mr)"
+ :created-at="mr.created_at"
+ :closed-at="mr.closed_at"
+ :merged-at="mr.merged_at"
+ :path="mr.web_url"
+ :state="mr.state"
+ :is-merge-request="true"
+ :pipeline-status="mr.head_pipeline && mr.head_pipeline.detailed_status"
+ path-id-separator="!"
+ />
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div
+ v-if="hasClosingMergeRequest && !isFetchingMergeRequests"
+ class="issue-closed-by-widget second-block"
+ >
+ {{ closingMergeRequestsText }}
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/related_merge_requests/index.js b/app/assets/javascripts/related_merge_requests/index.js
new file mode 100644
index 00000000000..092ff1df00f
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import RelatedMergeRequests from './components/related_merge_requests.vue';
+import createStore from './store';
+
+export default function initRelatedMergeRequests() {
+ const relatedMergeRequestsElement = document.querySelector('#js-related-merge-requests');
+
+ if (relatedMergeRequestsElement) {
+ const { endpoint, projectPath, projectNamespace } = relatedMergeRequestsElement.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: relatedMergeRequestsElement,
+ components: {
+ RelatedMergeRequests,
+ },
+ store: createStore(),
+ render: createElement =>
+ createElement('related-merge-requests', {
+ props: { endpoint, projectNamespace, projectPath },
+ }),
+ });
+ }
+}
diff --git a/app/assets/javascripts/related_merge_requests/store/actions.js b/app/assets/javascripts/related_merge_requests/store/actions.js
new file mode 100644
index 00000000000..69abeaaf7db
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/store/actions.js
@@ -0,0 +1,37 @@
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
+import * as types from './mutation_types';
+
+const REQUEST_PAGE_COUNT = 100;
+
+export const setInitialState = ({ commit }, props) => {
+ commit(types.SET_INITIAL_STATE, props);
+};
+
+export const requestData = ({ commit }) => commit(types.REQUEST_DATA);
+
+export const receiveDataSuccess = ({ commit }, data) => commit(types.RECEIVE_DATA_SUCCESS, data);
+
+export const receiveDataError = ({ commit }) => commit(types.RECEIVE_DATA_ERROR);
+
+export const fetchMergeRequests = ({ state, dispatch }) => {
+ dispatch('requestData');
+
+ return axios
+ .get(`${state.apiEndpoint}?per_page=${REQUEST_PAGE_COUNT}`)
+ .then(res => {
+ const { headers, data } = res;
+ const total = Number(normalizeHeaders(headers)['X-TOTAL']) || 0;
+
+ dispatch('receiveDataSuccess', { data, total });
+ })
+ .catch(() => {
+ dispatch('receiveDataError');
+ createFlash(s__('Something went wrong while fetching related merge requests.'));
+ });
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/related_merge_requests/store/index.js b/app/assets/javascripts/related_merge_requests/store/index.js
new file mode 100644
index 00000000000..dcb70c22bcb
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/store/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ state: createState(),
+ actions,
+ mutations,
+ });
diff --git a/app/assets/javascripts/related_merge_requests/store/mutation_types.js b/app/assets/javascripts/related_merge_requests/store/mutation_types.js
new file mode 100644
index 00000000000..31d4fe032e1
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/store/mutation_types.js
@@ -0,0 +1,4 @@
+export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
+export const REQUEST_DATA = 'REQUEST_DATA';
+export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS';
+export const RECEIVE_DATA_ERROR = 'RECEIVE_DATA_ERROR';
diff --git a/app/assets/javascripts/related_merge_requests/store/mutations.js b/app/assets/javascripts/related_merge_requests/store/mutations.js
new file mode 100644
index 00000000000..11ca28a5fb9
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/store/mutations.js
@@ -0,0 +1,19 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_INITIAL_STATE](state, { apiEndpoint }) {
+ state.apiEndpoint = apiEndpoint;
+ },
+ [types.REQUEST_DATA](state) {
+ state.isFetchingMergeRequests = true;
+ },
+ [types.RECEIVE_DATA_SUCCESS](state, { data, total }) {
+ state.isFetchingMergeRequests = false;
+ state.mergeRequests = data;
+ state.totalCount = total;
+ },
+ [types.RECEIVE_DATA_ERROR](state) {
+ state.isFetchingMergeRequests = false;
+ state.hasErrorFetchingMergeRequests = true;
+ },
+};
diff --git a/app/assets/javascripts/related_merge_requests/store/state.js b/app/assets/javascripts/related_merge_requests/store/state.js
new file mode 100644
index 00000000000..bc3468a025b
--- /dev/null
+++ b/app/assets/javascripts/related_merge_requests/store/state.js
@@ -0,0 +1,7 @@
+export default () => ({
+ apiEndpoint: '',
+ isFetchingMergeRequests: false,
+ hasErrorFetchingMergeRequests: false,
+ mergeRequests: [],
+ totalCount: 0,
+});
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 7ed1b407ddd..0958b9fa926 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -86,7 +86,7 @@ export default {
</div>
<div
- v-if="assets.links.length || assets.sources.length"
+ v-if="assets.links.length || (assets.sources && assets.sources.length)"
class="card-text prepend-top-default"
>
<b>
@@ -103,7 +103,7 @@ export default {
</li>
</ul>
- <div v-if="assets.sources.length" class="dropdown">
+ <div v-if="assets.sources && assets.sources.length" class="dropdown">
<button
type="button"
class="btn btn-link"
diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js
index b5c4d54ac33..e0a922d5ef6 100644
--- a/app/assets/javascripts/releases/store/actions.js
+++ b/app/assets/javascripts/releases/store/actions.js
@@ -30,7 +30,7 @@ export const receiveReleasesSuccess = ({ commit }, data) =>
export const receiveReleasesError = ({ commit }) => {
commit(types.RECEIVE_RELEASES_ERROR);
- createFlash(__('An error occured while fetching the releases. Please try again.'));
+ createFlash(__('An error occurred while fetching the releases. Please try again.'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/reports/components/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index 2946fbc6a1f..04fba43b2f3 100644
--- a/app/assets/javascripts/reports/components/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -13,6 +13,11 @@ export default {
type: String,
required: true,
},
+ statusIconSize: {
+ type: Number,
+ required: false,
+ default: 32,
+ },
},
computed: {
iconName() {
@@ -45,6 +50,6 @@ export default {
}"
class="report-block-list-icon"
>
- <icon :name="iconName" :size="32" />
+ <icon :name="iconName" :size="statusIconSize" />
</div>
</template>
diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index f4243522ef8..ee07efea3b0 100644
--- a/app/assets/javascripts/reports/components/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -52,6 +52,21 @@ export default {
required: false,
default: '',
},
+ showReportSectionStatusIcon: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
issuesWithState() {
@@ -62,6 +77,9 @@ export default {
...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
];
},
+ wclass() {
+ return `report-block-list ${this.issuesUlElementClass}`;
+ },
},
};
</script>
@@ -72,7 +90,7 @@ export default {
:size="$options.typicalReportItemHeight"
class="report-block-container"
wtag="ul"
- wclass="report-block-list"
+ :wclass="wclass"
>
<report-item
v-for="(wrapped, index) in issuesWithState"
@@ -81,6 +99,8 @@ export default {
:status="wrapped.status"
:component="component"
:is-new="wrapped.isNew"
+ :show-report-section-status-icon="showReportSectionStatusIcon"
+ :class="issueItemClass"
/>
</smart-virtual-list>
</template>
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 839e86bdf17..01a30809e1a 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -24,17 +24,32 @@ export default {
type: String,
required: true,
},
+ statusIconSize: {
+ type: Number,
+ required: false,
+ default: 32,
+ },
isNew: {
type: Boolean,
required: false,
default: false,
},
+ showReportSectionStatusIcon: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
};
</script>
<template>
<li :class="{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue">
- <issue-status-icon :status="status" class="append-right-5" />
+ <issue-status-icon
+ v-if="showReportSectionStatusIcon"
+ :status="status"
+ :status-icon-size="statusIconSize"
+ class="append-right-5"
+ />
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
</li>
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index d6483e95278..3d576caaf8f 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -3,10 +3,7 @@ import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
-
-const LOADING = 'LOADING';
-const ERROR = 'ERROR';
-const SUCCESS = 'SUCCESS';
+import { status } from '../constants';
export default {
name: 'ReportSection',
@@ -42,7 +39,8 @@ export default {
},
successText: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
unresolvedIssues: {
type: Array,
@@ -73,6 +71,26 @@ export default {
default: () => ({}),
required: false,
},
+ showReportSectionStatusIcon: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issuesListContainerClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
@@ -86,13 +104,13 @@ export default {
return this.isCollapsed ? __('Expand') : __('Collapse');
},
isLoading() {
- return this.status === LOADING;
+ return this.status === status.LOADING;
},
loadingFailed() {
- return this.status === ERROR;
+ return this.status === status.ERROR;
},
isSuccess() {
- return this.status === SUCCESS;
+ return this.status === status.SUCCESS;
},
isCollapsible() {
return !this.alwaysOpen && this.hasIssues;
@@ -127,6 +145,15 @@ export default {
hasPopover() {
return Object.keys(this.popoverOptions).length > 0;
},
+ slotName() {
+ if (this.isSuccess) {
+ return 'success';
+ } else if (this.isLoading) {
+ return 'loading';
+ }
+
+ return 'error';
+ },
},
methods: {
toggleCollapsed() {
@@ -142,6 +169,7 @@ export default {
<div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text">
{{ headerText }}
+ <slot :name="slotName"></slot>
<popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
</span>
@@ -151,7 +179,7 @@ export default {
<button
v-if="isCollapsible"
type="button"
- class="js-collapse-btn btn float-right btn-sm"
+ class="js-collapse-btn btn float-right btn-sm qa-expand-report-button"
@click="toggleCollapsed"
>
{{ collapseText }}
@@ -166,6 +194,10 @@ export default {
:resolved-issues="resolvedIssues"
:neutral-issues="neutralIssues"
:component="component"
+ :show-report-section-status-icon="showReportSectionStatusIcon"
+ :issues-ul-element-class="issuesUlElementClass"
+ :class="issuesListContainerClass"
+ :issue-item-class="issueItemClass"
/>
</slot>
</div>
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js
index c323dc543f3..66ac1af062b 100644
--- a/app/assets/javascripts/reports/constants.js
+++ b/app/assets/javascripts/reports/constants.js
@@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral';
export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
+
+export const status = {
+ LOADING: 'LOADING',
+ ERROR: 'ERROR',
+ SUCCESS: 'SUCCESS',
+};
diff --git a/app/assets/javascripts/reports/store/state.js b/app/assets/javascripts/reports/store/state.js
index 5484900276c..25f9f70d095 100644
--- a/app/assets/javascripts/reports/store/state.js
+++ b/app/assets/javascripts/reports/store/state.js
@@ -40,6 +40,11 @@ export default () => ({
text: s__('Reports|Class'),
type: fieldTypes.link,
},
+ classname: {
+ value: null,
+ text: s__('Reports|Classname'),
+ type: fieldTypes.text,
+ },
execution_time: {
value: null,
text: s__('Reports|Execution time'),
diff --git a/app/assets/javascripts/reports/store/utils.js b/app/assets/javascripts/reports/store/utils.js
index 35632218269..10560d0ae8e 100644
--- a/app/assets/javascripts/reports/store/utils.js
+++ b/app/assets/javascripts/reports/store/utils.js
@@ -1,4 +1,4 @@
-import { sprintf, n__, s__ } from '~/locale';
+import { sprintf, n__, s__, __ } from '~/locale';
import {
STATUS_FAILED,
STATUS_SUCCESS,
@@ -38,12 +38,12 @@ const textBuilder = results => {
export const summaryTextBuilder = (name = '', results = {}) => {
const resultsString = textBuilder(results);
- return `${name} contained ${resultsString}`;
+ return sprintf(__('%{name} contained %{resultsString}'), { name, resultsString });
};
export const reportTextBuilder = (name = '', results = {}) => {
const resultsString = textBuilder(results);
- return `${name} found ${resultsString}`;
+ return sprintf(__('%{name} found %{resultsString}'), { name, resultsString });
};
export const statusIcon = status => {
diff --git a/app/assets/javascripts/repository/components/app.vue b/app/assets/javascripts/repository/components/app.vue
new file mode 100644
index 00000000000..98240aef810
--- /dev/null
+++ b/app/assets/javascripts/repository/components/app.vue
@@ -0,0 +1,3 @@
+<template>
+ <router-view />
+</template>
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
new file mode 100644
index 00000000000..6eca015036f
--- /dev/null
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -0,0 +1,61 @@
+<script>
+import getRefMixin from '../mixins/get_ref';
+import getProjectShortPath from '../queries/getProjectShortPath.graphql';
+
+export default {
+ apollo: {
+ projectShortPath: {
+ query: getProjectShortPath,
+ },
+ },
+ mixins: [getRefMixin],
+ props: {
+ currentPath: {
+ type: String,
+ required: false,
+ default: '/',
+ },
+ },
+ data() {
+ return {
+ projectShortPath: '',
+ };
+ },
+ computed: {
+ pathLinks() {
+ return this.currentPath
+ .split('/')
+ .filter(p => p !== '')
+ .reduce(
+ (acc, name, i) => {
+ const path = `${i > 0 ? acc[i].path : ''}/${name}`;
+
+ return acc.concat({
+ name,
+ path,
+ to: `/tree/${this.ref}${path}`,
+ });
+ },
+ [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}` }],
+ );
+ },
+ },
+ methods: {
+ isLast(i) {
+ return i === this.pathLinks.length - 1;
+ },
+ },
+};
+</script>
+
+<template>
+ <nav :aria-label="__('Files breadcrumb')">
+ <ol class="breadcrumb repo-breadcrumb">
+ <li v-for="(link, i) in pathLinks" :key="i" class="breadcrumb-item">
+ <router-link :to="link.to" :aria-current="isLast(i) ? 'page' : null">
+ {{ link.name }}
+ </router-link>
+ </li>
+ </ol>
+ </nav>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/header.vue b/app/assets/javascripts/repository/components/table/header.vue
new file mode 100644
index 00000000000..9d30aa88155
--- /dev/null
+++ b/app/assets/javascripts/repository/components/table/header.vue
@@ -0,0 +1,9 @@
+<template>
+ <thead>
+ <tr>
+ <th id="name" scope="col">{{ s__('ProjectFileTree|Name') }}</th>
+ <th id="last-commit" scope="col" class="d-none d-sm-table-cell">{{ __('Last commit') }}</th>
+ <th id="last-update" scope="col" class="text-right">{{ __('Last update') }}</th>
+ </tr>
+ </thead>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
new file mode 100644
index 00000000000..d2198bcccfe
--- /dev/null
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -0,0 +1,145 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { sprintf, __ } from '../../../locale';
+import getRefMixin from '../../mixins/get_ref';
+import getFiles from '../../queries/getFiles.graphql';
+import getProjectPath from '../../queries/getProjectPath.graphql';
+import TableHeader from './header.vue';
+import TableRow from './row.vue';
+import ParentRow from './parent_row.vue';
+
+const PAGE_SIZE = 100;
+
+export default {
+ components: {
+ GlLoadingIcon,
+ TableHeader,
+ TableRow,
+ ParentRow,
+ },
+ mixins: [getRefMixin],
+ apollo: {
+ projectPath: {
+ query: getProjectPath,
+ },
+ },
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ projectPath: '',
+ nextPageCursor: '',
+ entries: {
+ trees: [],
+ submodules: [],
+ blobs: [],
+ },
+ isLoadingFiles: false,
+ };
+ },
+ computed: {
+ tableCaption() {
+ return sprintf(
+ __('Files, directories, and submodules in the path %{path} for commit reference %{ref}'),
+ { path: this.path, ref: this.ref },
+ );
+ },
+ showParentRow() {
+ return !this.isLoadingFiles && ['', '/'].indexOf(this.path) === -1;
+ },
+ },
+ watch: {
+ $route: function routeChange() {
+ this.entries.trees = [];
+ this.entries.submodules = [];
+ this.entries.blobs = [];
+ this.nextPageCursor = '';
+ this.fetchFiles();
+ },
+ },
+ mounted() {
+ // We need to wait for `ref` and `projectPath` to be set
+ this.$nextTick(() => this.fetchFiles());
+ },
+ methods: {
+ fetchFiles() {
+ this.isLoadingFiles = true;
+
+ return this.$apollo
+ .query({
+ query: getFiles,
+ variables: {
+ projectPath: this.projectPath,
+ ref: this.ref,
+ path: this.path,
+ nextPageCursor: this.nextPageCursor,
+ pageSize: PAGE_SIZE,
+ },
+ })
+ .then(({ data }) => {
+ if (!data) return;
+
+ const pageInfo = this.hasNextPage(data.project.repository.tree);
+
+ this.isLoadingFiles = false;
+ this.entries = Object.keys(this.entries).reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: this.normalizeData(key, data.project.repository.tree[key].edges),
+ }),
+ {},
+ );
+
+ if (pageInfo && pageInfo.hasNextPage) {
+ this.nextPageCursor = pageInfo.endCursor;
+ this.fetchFiles();
+ }
+ })
+ .catch(() => createFlash(__('An error occurred while fetching folder content.')));
+ },
+ normalizeData(key, data) {
+ return this.entries[key].concat(data.map(({ node }) => node));
+ },
+ hasNextPage(data) {
+ return []
+ .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
+ .find(({ hasNextPage }) => hasNextPage);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="tree-content-holder">
+ <div class="table-holder bordered-box">
+ <table class="table tree-table qa-file-tree" aria-live="polite">
+ <caption class="sr-only">
+ {{
+ tableCaption
+ }}
+ </caption>
+ <table-header v-once />
+ <tbody>
+ <parent-row v-show="showParentRow" :commit-ref="ref" :path="path" />
+ <template v-for="val in entries">
+ <table-row
+ v-for="entry in val"
+ :id="entry.id"
+ :key="`${entry.flatPath}-${entry.id}`"
+ :current-path="path"
+ :path="entry.flatPath"
+ :type="entry.type"
+ :url="entry.webUrl"
+ />
+ </template>
+ </tbody>
+ </table>
+ <gl-loading-icon v-show="isLoadingFiles" class="my-3" size="md" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue
new file mode 100644
index 00000000000..3c39f404226
--- /dev/null
+++ b/app/assets/javascripts/repository/components/table/parent_row.vue
@@ -0,0 +1,37 @@
+<script>
+export default {
+ props: {
+ commitRef: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ parentRoute() {
+ const splitArray = this.path.split('/');
+ splitArray.pop();
+
+ return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` };
+ },
+ },
+ methods: {
+ clickRow() {
+ this.$router.push(this.parentRoute);
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="tree-item">
+ <td colspan="3" class="tree-item-file-name" @click.self="clickRow">
+ <router-link :to="parentRoute" :aria-label="__('Go to parent')">
+ ..
+ </router-link>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
new file mode 100644
index 00000000000..764882a7936
--- /dev/null
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -0,0 +1,77 @@
+<script>
+import { getIconName } from '../../utils/icon';
+import getRefMixin from '../../mixins/get_ref';
+
+export default {
+ mixins: [getRefMixin],
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ currentPath: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ type: {
+ type: String,
+ required: true,
+ },
+ url: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ routerLinkTo() {
+ return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null;
+ },
+ iconName() {
+ return `fa-${getIconName(this.type, this.path)}`;
+ },
+ isFolder() {
+ return this.type === 'tree';
+ },
+ isSubmodule() {
+ return this.type === 'commit';
+ },
+ linkComponent() {
+ return this.isFolder ? 'router-link' : 'a';
+ },
+ fullPath() {
+ return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
+ },
+ shortSha() {
+ return this.id.slice(0, 8);
+ },
+ },
+ methods: {
+ openRow() {
+ if (this.isFolder) {
+ this.$router.push(this.routerLinkTo);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow">
+ <td class="tree-item-file-name">
+ <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
+ <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated">
+ {{ fullPath }}
+ </component>
+ <template v-if="isSubmodule">
+ @ <a href="#" class="commit-sha">{{ shortSha }}</a>
+ </template>
+ </td>
+ <td class="d-none d-sm-table-cell tree-commit"></td>
+ <td class="tree-time-ago text-right"></td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/repository/fragmentTypes.json b/app/assets/javascripts/repository/fragmentTypes.json
new file mode 100644
index 00000000000..949ebca432b
--- /dev/null
+++ b/app/assets/javascripts/repository/fragmentTypes.json
@@ -0,0 +1 @@
+{"__schema":{"types":[{"kind":"INTERFACE","name":"Entry","possibleTypes":[{"name":"Blob"},{"name":"Submodule"},{"name":"TreeEntry"}]}]}}
diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js
new file mode 100644
index 00000000000..c64d16ef02a
--- /dev/null
+++ b/app/assets/javascripts/repository/graphql.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import createDefaultClient from '~/lib/graphql';
+import introspectionQueryResultData from './fragmentTypes.json';
+
+Vue.use(VueApollo);
+
+// We create a fragment matcher so that we can create a fragment from an interface
+// Without this, Apollo throws a heuristic fragment matcher warning
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
+
+const defaultClient = createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ dataIdFromObject: obj => {
+ // eslint-disable-next-line no-underscore-dangle
+ switch (obj.__typename) {
+ // We need to create a dynamic ID for each entry
+ // Each entry can have the same ID as the ID is a commit ID
+ // So we create a unique cache ID with the path and the ID
+ case 'TreeEntry':
+ case 'Submodule':
+ case 'Blob':
+ return `${obj.flatPath}-${obj.id}`;
+ default:
+ // If the type doesn't match any of the above we fallback
+ // to using the default Apollo ID
+ // eslint-disable-next-line no-underscore-dangle
+ return obj.id || obj._id;
+ }
+ },
+ },
+ },
+);
+
+export default new VueApollo({
+ defaultClient,
+});
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
new file mode 100644
index 00000000000..52f53be045b
--- /dev/null
+++ b/app/assets/javascripts/repository/index.js
@@ -0,0 +1,59 @@
+import Vue from 'vue';
+import createRouter from './router';
+import App from './components/app.vue';
+import Breadcrumbs from './components/breadcrumbs.vue';
+import apolloProvider from './graphql';
+import { setTitle } from './utils/title';
+
+export default function setupVueRepositoryList() {
+ const el = document.getElementById('js-tree-list');
+ const { projectPath, projectShortPath, ref, fullName } = el.dataset;
+ const router = createRouter(projectPath, ref);
+
+ apolloProvider.clients.defaultClient.cache.writeData({
+ data: {
+ projectPath,
+ projectShortPath,
+ ref,
+ },
+ });
+
+ router.afterEach(({ params: { pathMatch } }) => {
+ const isRoot = pathMatch === undefined || pathMatch === '/';
+
+ setTitle(pathMatch, ref, fullName);
+
+ if (!isRoot) {
+ document
+ .querySelectorAll('.js-keep-hidden-on-navigation')
+ .forEach(elem => elem.classList.add('hidden'));
+ }
+
+ document
+ .querySelectorAll('.js-hide-on-navigation')
+ .forEach(elem => elem.classList.toggle('hidden', !isRoot));
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: document.getElementById('js-repo-breadcrumb'),
+ router,
+ apolloProvider,
+ render(h) {
+ return h(Breadcrumbs, {
+ props: {
+ currentPath: this.$route.params.pathMatch,
+ },
+ });
+ },
+ });
+
+ return new Vue({
+ el,
+ router,
+ apolloProvider,
+ render(h) {
+ return h(App);
+ },
+ });
+}
diff --git a/app/assets/javascripts/repository/mixins/get_ref.js b/app/assets/javascripts/repository/mixins/get_ref.js
new file mode 100644
index 00000000000..b06087d6f42
--- /dev/null
+++ b/app/assets/javascripts/repository/mixins/get_ref.js
@@ -0,0 +1,14 @@
+import getRef from '../queries/getRef.graphql';
+
+export default {
+ apollo: {
+ ref: {
+ query: getRef,
+ },
+ },
+ data() {
+ return {
+ ref: '',
+ };
+ },
+};
diff --git a/app/assets/javascripts/repository/pages/index.vue b/app/assets/javascripts/repository/pages/index.vue
new file mode 100644
index 00000000000..2d92e9174ca
--- /dev/null
+++ b/app/assets/javascripts/repository/pages/index.vue
@@ -0,0 +1,18 @@
+<script>
+import FileTable from '../components/table/index.vue';
+
+export default {
+ components: {
+ FileTable,
+ },
+ data() {
+ return {
+ ref: '',
+ };
+ },
+};
+</script>
+
+<template>
+ <file-table path="/" />
+</template>
diff --git a/app/assets/javascripts/repository/pages/tree.vue b/app/assets/javascripts/repository/pages/tree.vue
new file mode 100644
index 00000000000..3b898d1aa91
--- /dev/null
+++ b/app/assets/javascripts/repository/pages/tree.vue
@@ -0,0 +1,20 @@
+<script>
+import FileTable from '../components/table/index.vue';
+
+export default {
+ components: {
+ FileTable,
+ },
+ props: {
+ path: {
+ type: String,
+ required: false,
+ default: '/',
+ },
+ },
+};
+</script>
+
+<template>
+ <file-table :path="path" />
+</template>
diff --git a/app/assets/javascripts/repository/queries/getFiles.graphql b/app/assets/javascripts/repository/queries/getFiles.graphql
new file mode 100644
index 00000000000..7d92bc46455
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getFiles.graphql
@@ -0,0 +1,57 @@
+fragment TreeEntry on Entry {
+ id
+ flatPath
+ type
+}
+
+fragment PageInfo on PageInfo {
+ hasNextPage
+ endCursor
+}
+
+query getFiles(
+ $projectPath: ID!
+ $path: String
+ $ref: String!
+ $pageSize: Int!
+ $nextPageCursor: String
+) {
+ project(fullPath: $projectPath) {
+ repository {
+ tree(path: $path, ref: $ref) {
+ trees(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ webUrl
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ submodules(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ blobs(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ webUrl
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/repository/queries/getProjectPath.graphql b/app/assets/javascripts/repository/queries/getProjectPath.graphql
new file mode 100644
index 00000000000..74e73e07577
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getProjectPath.graphql
@@ -0,0 +1,3 @@
+query getProjectPath {
+ projectPath
+}
diff --git a/app/assets/javascripts/repository/queries/getProjectShortPath.graphql b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql
new file mode 100644
index 00000000000..34eb26598c2
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql
@@ -0,0 +1,3 @@
+query getProjectShortPath {
+ projectShortPath @client
+}
diff --git a/app/assets/javascripts/repository/queries/getRef.graphql b/app/assets/javascripts/repository/queries/getRef.graphql
new file mode 100644
index 00000000000..58c09844c3f
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getRef.graphql
@@ -0,0 +1,3 @@
+query getRef {
+ ref @client
+}
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
new file mode 100644
index 00000000000..9322c81ab97
--- /dev/null
+++ b/app/assets/javascripts/repository/router.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import { joinPaths } from '../lib/utils/url_utility';
+import IndexPage from './pages/index.vue';
+import TreePage from './pages/tree.vue';
+
+Vue.use(VueRouter);
+
+export default function createRouter(base, baseRef) {
+ return new VueRouter({
+ mode: 'history',
+ base: joinPaths(gon.relative_url_root || '', base),
+ routes: [
+ {
+ path: `/tree/${baseRef}(/.*)?`,
+ name: 'treePath',
+ component: TreePage,
+ props: route => ({
+ path: route.params.pathMatch && route.params.pathMatch.replace(/^\//, ''),
+ }),
+ },
+ {
+ path: '/',
+ name: 'projectRoot',
+ component: IndexPage,
+ },
+ ],
+ });
+}
diff --git a/app/assets/javascripts/repository/utils/icon.js b/app/assets/javascripts/repository/utils/icon.js
new file mode 100644
index 00000000000..661ebb6edfc
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/icon.js
@@ -0,0 +1,99 @@
+const entryTypeIcons = {
+ tree: 'folder',
+ commit: 'archive',
+};
+
+const fileTypeIcons = [
+ { extensions: ['pdf'], name: 'file-pdf-o' },
+ {
+ extensions: [
+ 'jpg',
+ 'jpeg',
+ 'jif',
+ 'jfif',
+ 'jp2',
+ 'jpx',
+ 'j2k',
+ 'j2c',
+ 'png',
+ 'gif',
+ 'tif',
+ 'tiff',
+ 'svg',
+ 'ico',
+ 'bmp',
+ ],
+ name: 'file-image-o',
+ },
+ {
+ extensions: ['zip', 'zipx', 'tar', 'gz', 'bz', 'bzip', 'xz', 'rar', '7z'],
+ name: 'file-archive-o',
+ },
+ { extensions: ['mp3', 'wma', 'ogg', 'oga', 'wav', 'flac', 'aac'], name: 'file-audio-o' },
+ {
+ extensions: [
+ 'mp4',
+ 'm4p',
+ 'm4v',
+ 'mpg',
+ 'mp2',
+ 'mpeg',
+ 'mpe',
+ 'mpv',
+ 'm2v',
+ 'avi',
+ 'mkv',
+ 'flv',
+ 'ogv',
+ 'mov',
+ '3gp',
+ '3g2',
+ ],
+ name: 'file-video-o',
+ },
+ { extensions: ['doc', 'dot', 'docx', 'docm', 'dotx', 'dotm', 'docb'], name: 'file-word-o' },
+ {
+ extensions: [
+ 'xls',
+ 'xlt',
+ 'xlm',
+ 'xlsx',
+ 'xlsm',
+ 'xltx',
+ 'xltm',
+ 'xlsb',
+ 'xla',
+ 'xlam',
+ 'xll',
+ 'xlw',
+ ],
+ name: 'file-excel-o',
+ },
+ {
+ extensions: [
+ 'ppt',
+ 'pot',
+ 'pps',
+ 'pptx',
+ 'pptm',
+ 'potx',
+ 'potm',
+ 'ppam',
+ 'ppsx',
+ 'ppsm',
+ 'sldx',
+ 'sldm',
+ ],
+ name: 'file-powerpoint-o',
+ },
+];
+
+// eslint-disable-next-line import/prefer-default-export
+export const getIconName = (type, path) => {
+ if (entryTypeIcons[type]) return entryTypeIcons[type];
+
+ const extension = path.split('.').pop();
+ const file = fileTypeIcons.find(t => t.extensions.some(ext => ext === extension));
+
+ return file ? file.name : 'file-text-o';
+};
diff --git a/app/assets/javascripts/repository/utils/title.js b/app/assets/javascripts/repository/utils/title.js
new file mode 100644
index 00000000000..4e194640e92
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/title.js
@@ -0,0 +1,9 @@
+// eslint-disable-next-line import/prefer-default-export
+export const setTitle = (pathMatch, ref, project) => {
+ if (!pathMatch) return;
+
+ const path = pathMatch.replace(/^\//, '');
+ const isEmpty = path === '';
+
+ document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project}`;
+};
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 9a0cdc02952..930c0d5e958 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -5,7 +5,7 @@ import _ from 'underscore';
import Cookies from 'js-cookie';
import flash from './flash';
import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
+import { sprintf, s__, __ } from './locale';
function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this);
@@ -82,9 +82,9 @@ Sidebar.prototype.toggleTodo = function(e) {
ajaxType = $this.data('deletePath') ? 'delete' : 'post';
if ($this.data('deletePath')) {
- url = '' + $this.data('deletePath');
+ url = String($this.data('deletePath'));
} else {
- url = '' + $this.data('createPath');
+ url = String($this.data('createPath'));
}
$this.tooltip('hide');
@@ -101,7 +101,10 @@ Sidebar.prototype.toggleTodo = function(e) {
this.todoUpdateDone(data);
})
.catch(() =>
- flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`),
+ flash(sprintf(__('There was an error %{message} todo.')), {
+ message:
+ ajaxType === 'post' ? s__('RightSidebar|adding a') : s__('RightSidebar|deleting the'),
+ }),
);
};
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 0a4583b5861..6aca4067ba7 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -2,7 +2,7 @@
import $ from 'jquery';
import { escape, throttle } from 'underscore';
-import { s__, sprintf } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
import axios from './lib/utils/axios_utils';
import DropdownUtils from './filtered_search/dropdown_utils';
@@ -379,7 +379,7 @@ export class SearchAutocomplete {
}
}
}
- this.wrap.toggleClass('has-value', !!e.target.value);
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
}
onSearchInputFocus() {
@@ -396,7 +396,7 @@ export class SearchAutocomplete {
onClearInputClick(e) {
e.preventDefault();
- this.wrap.toggleClass('has-value', !!e.target.value);
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
return this.searchInput.val('').focus();
}
@@ -405,8 +405,9 @@ export class SearchAutocomplete {
this.wrap.removeClass('search-active');
// If input is blank then restore state
if (this.searchInput.val() === '') {
- return this.restoreOriginalState();
+ this.restoreOriginalState();
}
+ this.dropdownMenu.removeClass('show');
}
restoreOriginalState() {
@@ -439,7 +440,7 @@ export class SearchAutocomplete {
restoreMenu() {
var html;
- html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
+ html = `<ul><li class="dropdown-menu-empty-item"><a>${__('Loading...')}</a></li></ul>`;
return this.dropdownContent.html(html);
}
diff --git a/app/assets/javascripts/serverless/components/area.vue b/app/assets/javascripts/serverless/components/area.vue
new file mode 100644
index 00000000000..32c9d6eccb8
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/area.vue
@@ -0,0 +1,146 @@
+<script>
+import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
+import dateFormat from 'dateformat';
+import { X_INTERVAL } from '../constants';
+import { validateGraphData } from '../utils';
+
+let debouncedResize;
+
+export default {
+ components: {
+ GlAreaChart,
+ },
+ inheritAttrs: false,
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ validator: validateGraphData,
+ },
+ containerWidth: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ tooltipPopoverTitle: '',
+ tooltipPopoverContent: '',
+ width: this.containerWidth,
+ };
+ },
+ computed: {
+ chartData() {
+ return this.graphData.queries.reduce((accumulator, query) => {
+ accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []);
+ return accumulator;
+ }, {});
+ },
+ extractTimeData() {
+ return this.chartData.requests.map(data => data.time);
+ },
+ generateSeries() {
+ return {
+ name: 'Invocations',
+ type: 'line',
+ data: this.chartData.requests.map(data => [data.time, data.value]),
+ symbolSize: 0,
+ };
+ },
+ getInterval() {
+ const { result } = this.graphData.queries[0];
+
+ if (result.length === 0) {
+ return 1;
+ }
+
+ const split = result[0].values.reduce(
+ (acc, pair) => (pair.value > acc ? pair.value : acc),
+ 1,
+ );
+
+ return split < X_INTERVAL ? split : X_INTERVAL;
+ },
+ chartOptions() {
+ return {
+ xAxis: {
+ name: 'time',
+ type: 'time',
+ axisLabel: {
+ formatter: date => dateFormat(date, 'h:MM TT'),
+ },
+ data: this.extractTimeData,
+ nameTextStyle: {
+ padding: [18, 0, 0, 0],
+ },
+ },
+ yAxis: {
+ name: this.yAxisLabel,
+ nameTextStyle: {
+ padding: [0, 0, 36, 0],
+ },
+ splitNumber: this.getInterval,
+ },
+ legend: {
+ formatter: this.xAxisLabel,
+ },
+ series: this.generateSeries,
+ };
+ },
+ xAxisLabel() {
+ return this.graphData.queries.map(query => query.label).join(', ');
+ },
+ yAxisLabel() {
+ const [query] = this.graphData.queries;
+ return `${this.graphData.y_label} (${query.unit})`;
+ },
+ },
+ watch: {
+ containerWidth: 'onResize',
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', debouncedResize);
+ },
+ created() {
+ debouncedResize = debounceByAnimationFrame(this.onResize);
+ window.addEventListener('resize', debouncedResize);
+ },
+ methods: {
+ formatTooltipText(params) {
+ const [seriesData] = params.seriesData;
+ this.tooltipPopoverTitle = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
+ this.tooltipPopoverContent = `${this.yAxisLabel}: ${seriesData.value[1]}`;
+ },
+ onResize() {
+ const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
+ this.width = width;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="prometheus-graph">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
+ </div>
+ <gl-area-chart
+ ref="areaChart"
+ v-bind="$attrs"
+ :data="[]"
+ :option="chartOptions"
+ :format-tooltip-text="formatTooltipText"
+ :width="width"
+ :include-legend-avg-max="false"
+ >
+ <template slot="tooltipTitle">
+ {{ tooltipPopoverTitle }}
+ </template>
+ <template slot="tooltipContent">
+ {{ tooltipPopoverContent }}
+ </template>
+ </gl-area-chart>
+ </div>
+</template>
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
index 4f89ad69129..b8906cfca4e 100644
--- a/app/assets/javascripts/serverless/components/function_details.vue
+++ b/app/assets/javascripts/serverless/components/function_details.vue
@@ -1,39 +1,77 @@
<script>
+import _ from 'underscore';
+import { mapState, mapActions, mapGetters } from 'vuex';
import PodBox from './pod_box.vue';
import Url from './url.vue';
+import AreaChart from './area.vue';
+import MissingPrometheus from './missing_prometheus.vue';
export default {
components: {
PodBox,
Url,
+ AreaChart,
+ MissingPrometheus,
},
props: {
func: {
type: Object,
required: true,
},
+ hasPrometheus: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ clustersPath: {
+ type: String,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ elWidth: 0,
+ };
},
computed: {
name() {
return this.func.name;
},
description() {
- return this.func.description;
+ return _.isString(this.func.description) ? this.func.description : '';
},
funcUrl() {
return this.func.url;
},
podCount() {
- return this.func.podcount || 0;
+ return Number(this.func.podcount) || 0;
},
+ ...mapState(['graphData', 'hasPrometheusData']),
+ ...mapGetters(['hasPrometheusMissingData']),
+ },
+ created() {
+ this.fetchMetrics({
+ metricsPath: this.func.metricsUrl,
+ hasPrometheus: this.hasPrometheus,
+ });
+ },
+ mounted() {
+ this.elWidth = this.$el.clientWidth;
+ },
+ methods: {
+ ...mapActions(['fetchMetrics']),
},
};
</script>
<template>
<section id="serverless-function-details">
- <h3>{{ name }}</h3>
- <div class="append-bottom-default">
+ <h3 class="serverless-function-name">{{ name }}</h3>
+ <div class="append-bottom-default serverless-function-description">
<div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div>
</div>
<url :uri="funcUrl" />
@@ -52,5 +90,13 @@ export default {
</p>
</div>
<div v-else><p>No pods loaded at this time.</p></div>
+
+ <area-chart v-if="hasPrometheusData" :graph-data="graphData" :container-width="elWidth" />
+ <missing-prometheus
+ v-if="!hasPrometheus || hasPrometheusMissingData"
+ :help-path="helpPath"
+ :clusters-path="clustersPath"
+ :missing-data="hasPrometheusMissingData"
+ />
</section>
</template>
diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue
index 773d18781fd..4b3bb078eae 100644
--- a/app/assets/javascripts/serverless/components/function_row.vue
+++ b/app/assets/javascripts/serverless/components/function_row.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import Url from './url.vue';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -19,6 +20,10 @@ export default {
return this.func.name;
},
description() {
+ if (!_.isString(this.func.description)) {
+ return '';
+ }
+
const desc = this.func.description.split('\n');
if (desc.length > 1) {
return desc[1];
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index 4bde409f906..94341050b86 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -1,26 +1,19 @@
<script>
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
import FunctionRow from './function_row.vue';
import EnvironmentRow from './environment_row.vue';
import EmptyState from './empty_state.vue';
+import { CHECKING_INSTALLED } from '../constants';
export default {
components: {
EnvironmentRow,
FunctionRow,
EmptyState,
- GlSkeletonLoading,
+ GlLoadingIcon,
},
props: {
- functions: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- installed: {
- type: Boolean,
- required: true,
- },
clustersPath: {
type: String,
required: true,
@@ -29,32 +22,48 @@ export default {
type: String,
required: true,
},
- loadingData: {
- type: Boolean,
- required: false,
- default: true,
+ statusPath: {
+ type: String,
+ required: true,
},
- hasFunctionData: {
- type: Boolean,
- required: false,
- default: true,
+ },
+ computed: {
+ ...mapState(['installed', 'isLoading', 'hasFunctionData']),
+ ...mapGetters(['getFunctions']),
+
+ checkingInstalled() {
+ return this.installed === CHECKING_INSTALLED;
+ },
+ isInstalled() {
+ return this.installed === true;
},
},
+ created() {
+ this.fetchFunctions({
+ functionsPath: this.statusPath,
+ });
+ },
+ methods: {
+ ...mapActions(['fetchFunctions']),
+ },
};
</script>
<template>
<section id="serverless-functions">
- <div v-if="installed">
+ <gl-loading-icon
+ v-if="checkingInstalled"
+ :size="2"
+ class="prepend-top-default append-bottom-default"
+ />
+
+ <div v-else-if="isInstalled">
<div v-if="hasFunctionData">
- <template v-if="loadingData">
- <div v-for="j in 3" :key="j" class="gl-responsive-table-row"><gl-skeleton-loading /></div>
- </template>
- <template v-else>
- <div class="groups-list-tree-container">
+ <template>
+ <div class="groups-list-tree-container js-functions-wrapper">
<ul class="content-list group-list-tree">
<environment-row
- v-for="(env, index) in functions"
+ v-for="(env, index) in getFunctions"
:key="index"
:env="env"
:env-name="index"
@@ -62,6 +71,11 @@ export default {
</ul>
</div>
</template>
+ <gl-loading-icon
+ v-if="isLoading"
+ :size="2"
+ class="prepend-top-default append-bottom-default js-functions-loader"
+ />
</div>
<div v-else class="empty-state js-empty-state">
<div class="text-content">
diff --git a/app/assets/javascripts/serverless/components/missing_prometheus.vue b/app/assets/javascripts/serverless/components/missing_prometheus.vue
new file mode 100644
index 00000000000..6c19434f202
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/missing_prometheus.vue
@@ -0,0 +1,63 @@
+<script>
+import { GlButton, GlLink } from '@gitlab/ui';
+import { s__ } from '../../locale';
+
+export default {
+ components: {
+ GlButton,
+ GlLink,
+ },
+ props: {
+ clustersPath: {
+ type: String,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ missingData: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ missingStateClass() {
+ return this.missingData ? 'missing-prometheus-state' : 'empty-prometheus-state';
+ },
+ prometheusHelpPath() {
+ return `${this.helpPath}#prometheus-support`;
+ },
+ description() {
+ return this.missingData
+ ? s__(`ServerlessDetails|Invocation metrics loading or not available at this time.`)
+ : s__(
+ `ServerlessDetails|Function invocation metrics require Prometheus to be installed first.`,
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row" :class="missingStateClass">
+ <div class="col-12">
+ <div class="text-content">
+ <h4 class="state-title text-left">{{ s__(`ServerlessDetails|Invocations`) }}</h4>
+ <p class="state-description">
+ {{ description }}
+ <gl-link :href="prometheusHelpPath">{{
+ s__(`ServerlessDetails|More information`)
+ }}</gl-link
+ >.
+ </p>
+
+ <div v-if="!missingData" class="text-left">
+ <gl-button :href="clustersPath" variant="success">
+ {{ s__('ServerlessDetails|Install Prometheus') }}
+ </gl-button>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/serverless/components/url.vue b/app/assets/javascripts/serverless/components/url.vue
index ca53bf6c52a..e47a03f1939 100644
--- a/app/assets/javascripts/serverless/components/url.vue
+++ b/app/assets/javascripts/serverless/components/url.vue
@@ -20,7 +20,7 @@ export default {
<template>
<div class="clipboard-group">
- <div class="url-text-field label label-monospace">{{ uri }}</div>
+ <div class="url-text-field label label-monospace monospace">{{ uri }}</div>
<clipboard-button
:text="uri"
:title="s__('ServerlessURL|Copy URL to clipboard')"
diff --git a/app/assets/javascripts/serverless/constants.js b/app/assets/javascripts/serverless/constants.js
new file mode 100644
index 00000000000..2fa15e56ccb
--- /dev/null
+++ b/app/assets/javascripts/serverless/constants.js
@@ -0,0 +1,7 @@
+export const MAX_REQUESTS = 3; // max number of times to retry
+
+export const X_INTERVAL = 5; // Reflects the number of verticle bars on the x-axis
+
+export const CHECKING_INSTALLED = 'checking'; // The backend is still determining whether or not Knative is installed
+
+export const TIMEOUT = 'timeout';
diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js
index 47a510d5fb5..ed3b633d766 100644
--- a/app/assets/javascripts/serverless/serverless_bundle.js
+++ b/app/assets/javascripts/serverless/serverless_bundle.js
@@ -1,13 +1,7 @@
-import Visibility from 'visibilityjs';
import Vue from 'vue';
-import { s__ } from '../locale';
-import Flash from '../flash';
-import Poll from '../lib/utils/poll';
-import ServerlessStore from './stores/serverless_store';
-import ServerlessDetailsStore from './stores/serverless_details_store';
-import GetFunctionsService from './services/get_functions_service';
import Functions from './components/functions.vue';
import FunctionDetails from './components/function_details.vue';
+import { createStore } from './store';
export default class Serverless {
constructor() {
@@ -19,10 +13,12 @@ export default class Serverless {
serviceUrl,
serviceNamespace,
servicePodcount,
+ serviceMetricsUrl,
+ prometheus,
+ clustersPath,
+ helpPath,
} = document.querySelector('.js-serverless-function-details-page').dataset;
const el = document.querySelector('#js-serverless-function-details');
- this.store = new ServerlessDetailsStore();
- const { store } = this;
const service = {
name: serviceName,
@@ -31,118 +27,48 @@ export default class Serverless {
url: serviceUrl,
namespace: serviceNamespace,
podcount: servicePodcount,
+ metricsUrl: serviceMetricsUrl,
};
- this.store.updateDetailedFunction(service);
this.functionDetails = new Vue({
el,
- data() {
- return {
- state: store.state,
- };
- },
+ store: createStore(),
render(createElement) {
return createElement(FunctionDetails, {
props: {
- func: this.state.functionDetail,
+ func: service,
+ hasPrometheus: prometheus !== undefined,
+ clustersPath,
+ helpPath,
},
});
},
});
} else {
- const { statusPath, clustersPath, helpPath, installed } = document.querySelector(
+ const { statusPath, clustersPath, helpPath } = document.querySelector(
'.js-serverless-functions-page',
).dataset;
- this.service = new GetFunctionsService(statusPath);
- this.knativeInstalled = installed !== undefined;
- this.store = new ServerlessStore(this.knativeInstalled, clustersPath, helpPath);
- this.initServerless();
- this.functionLoadCount = 0;
-
- if (statusPath && this.knativeInstalled) {
- this.initPolling();
- }
- }
- }
-
- initServerless() {
- const { store } = this;
- const el = document.querySelector('#js-serverless-functions');
-
- this.functions = new Vue({
- el,
- data() {
- return {
- state: store.state,
- };
- },
- render(createElement) {
- return createElement(Functions, {
- props: {
- functions: this.state.functions,
- installed: this.state.installed,
- clustersPath: this.state.clustersPath,
- helpPath: this.state.helpPath,
- loadingData: this.state.loadingData,
- hasFunctionData: this.state.hasFunctionData,
- },
- });
- },
- });
- }
-
- initPolling() {
- this.poll = new Poll({
- resource: this.service,
- method: 'fetchData',
- successCallback: data => this.handleSuccess(data),
- errorCallback: () => Serverless.handleError(),
- });
-
- if (!Visibility.hidden()) {
- this.poll.makeRequest();
- } else {
- this.service
- .fetchData()
- .then(data => this.handleSuccess(data))
- .catch(() => Serverless.handleError());
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden() && !this.destroyed) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
- }
-
- handleSuccess(data) {
- if (data.status === 200) {
- this.store.updateFunctionsFromServer(data.data);
- this.store.updateLoadingState(false);
- } else if (data.status === 204) {
- /* Time out after 3 attempts to retrieve data */
- this.functionLoadCount += 1;
- if (this.functionLoadCount === 3) {
- this.poll.stop();
- this.store.toggleNoFunctionData();
- }
+ const el = document.querySelector('#js-serverless-functions');
+ this.functions = new Vue({
+ el,
+ store: createStore(),
+ render(createElement) {
+ return createElement(Functions, {
+ props: {
+ clustersPath,
+ helpPath,
+ statusPath,
+ },
+ });
+ },
+ });
}
}
- static handleError() {
- Flash(s__('Serverless|An error occurred while retrieving serverless components'));
- }
-
destroy() {
this.destroyed = true;
- if (this.poll) {
- this.poll.stop();
- }
-
this.functions.$destroy();
this.functionDetails.$destroy();
}
diff --git a/app/assets/javascripts/serverless/services/get_functions_service.js b/app/assets/javascripts/serverless/services/get_functions_service.js
deleted file mode 100644
index 303b42dc66c..00000000000
--- a/app/assets/javascripts/serverless/services/get_functions_service.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-
-export default class GetFunctionsService {
- constructor(endpoint) {
- this.endpoint = endpoint;
- }
-
- fetchData() {
- return axios.get(this.endpoint);
- }
-}
diff --git a/app/assets/javascripts/serverless/store/actions.js b/app/assets/javascripts/serverless/store/actions.js
new file mode 100644
index 00000000000..a0a9fdf7ace
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/actions.js
@@ -0,0 +1,128 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import statusCodes from '~/lib/utils/http_status';
+import { backOff } from '~/lib/utils/common_utils';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import { MAX_REQUESTS, CHECKING_INSTALLED, TIMEOUT } from '../constants';
+
+export const requestFunctionsLoading = ({ commit }) => commit(types.REQUEST_FUNCTIONS_LOADING);
+export const receiveFunctionsSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_SUCCESS, data);
+export const receiveFunctionsPartial = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_PARTIAL, data);
+export const receiveFunctionsTimeout = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_TIMEOUT, data);
+export const receiveFunctionsNoDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_NODATA_SUCCESS, data);
+export const receiveFunctionsError = ({ commit }, error) =>
+ commit(types.RECEIVE_FUNCTIONS_ERROR, error);
+
+export const receiveMetricsSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_METRICS_SUCCESS, data);
+export const receiveMetricsNoPrometheus = ({ commit }) =>
+ commit(types.RECEIVE_METRICS_NO_PROMETHEUS);
+export const receiveMetricsNoDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_METRICS_NODATA_SUCCESS, data);
+export const receiveMetricsError = ({ commit }, error) =>
+ commit(types.RECEIVE_METRICS_ERROR, error);
+
+export const fetchFunctions = ({ dispatch }, { functionsPath }) => {
+ let retryCount = 0;
+
+ const functionsPartiallyFetched = data => {
+ if (data.functions !== null && data.functions.length) {
+ dispatch('receiveFunctionsPartial', data);
+ }
+ };
+
+ dispatch('requestFunctionsLoading');
+
+ backOff((next, stop) => {
+ axios
+ .get(functionsPath)
+ .then(response => {
+ if (response.data.knative_installed === CHECKING_INSTALLED) {
+ retryCount += 1;
+ if (retryCount < MAX_REQUESTS) {
+ functionsPartiallyFetched(response.data);
+ next();
+ } else {
+ stop(TIMEOUT);
+ }
+ } else {
+ stop(response.data);
+ }
+ })
+ .catch(stop);
+ })
+ .then(data => {
+ if (data === TIMEOUT) {
+ dispatch('receiveFunctionsTimeout');
+ createFlash(__('Loading functions timed out. Please reload the page to try again.'));
+ } else if (data.functions !== null && data.functions.length) {
+ dispatch('receiveFunctionsSuccess', data);
+ } else {
+ dispatch('receiveFunctionsNoDataSuccess', data);
+ }
+ })
+ .catch(error => {
+ dispatch('receiveFunctionsError', error);
+ createFlash(error);
+ });
+};
+
+export const fetchMetrics = ({ dispatch }, { metricsPath, hasPrometheus }) => {
+ let retryCount = 0;
+
+ if (!hasPrometheus) {
+ dispatch('receiveMetricsNoPrometheus');
+ return;
+ }
+
+ backOff((next, stop) => {
+ axios
+ .get(metricsPath)
+ .then(response => {
+ if (response.status === statusCodes.NO_CONTENT) {
+ retryCount += 1;
+ if (retryCount < MAX_REQUESTS) {
+ next();
+ } else {
+ dispatch('receiveMetricsNoDataSuccess');
+ stop(null);
+ }
+ } else {
+ stop(response.data);
+ }
+ })
+ .catch(stop);
+ })
+ .then(data => {
+ if (data === null) {
+ return;
+ }
+
+ const updatedMetric = data.metrics;
+ const queries = data.metrics.queries.map(query => ({
+ ...query,
+ result: query.result.map(result => ({
+ ...result,
+ values: result.values.map(([timestamp, value]) => ({
+ time: new Date(timestamp * 1000).toISOString(),
+ value: Number(value),
+ })),
+ })),
+ }));
+
+ updatedMetric.queries = queries;
+ dispatch('receiveMetricsSuccess', updatedMetric);
+ })
+ .catch(error => {
+ dispatch('receiveMetricsError', error);
+ createFlash(error);
+ });
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/serverless/store/getters.js b/app/assets/javascripts/serverless/store/getters.js
new file mode 100644
index 00000000000..071f663d9d2
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/getters.js
@@ -0,0 +1,10 @@
+import { translate } from '../utils';
+
+export const hasPrometheusMissingData = state => state.hasPrometheus && !state.hasPrometheusData;
+
+// Convert the function list into a k/v grouping based on the environment scope
+
+export const getFunctions = state => translate(state.functions);
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/serverless/store/index.js b/app/assets/javascripts/serverless/store/index.js
new file mode 100644
index 00000000000..5f72060633e
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import createState from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: createState(),
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/serverless/store/mutation_types.js b/app/assets/javascripts/serverless/store/mutation_types.js
new file mode 100644
index 00000000000..b8fa9ea1a01
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/mutation_types.js
@@ -0,0 +1,11 @@
+export const REQUEST_FUNCTIONS_LOADING = 'REQUEST_FUNCTIONS_LOADING';
+export const RECEIVE_FUNCTIONS_SUCCESS = 'RECEIVE_FUNCTIONS_SUCCESS';
+export const RECEIVE_FUNCTIONS_PARTIAL = 'RECEIVE_FUNCTIONS_PARTIAL';
+export const RECEIVE_FUNCTIONS_TIMEOUT = 'RECEIVE_FUNCTIONS_TIMEOUT';
+export const RECEIVE_FUNCTIONS_NODATA_SUCCESS = 'RECEIVE_FUNCTIONS_NODATA_SUCCESS';
+export const RECEIVE_FUNCTIONS_ERROR = 'RECEIVE_FUNCTIONS_ERROR';
+
+export const RECEIVE_METRICS_NO_PROMETHEUS = 'RECEIVE_METRICS_NO_PROMETHEUS';
+export const RECEIVE_METRICS_SUCCESS = 'RECEIVE_METRICS_SUCCESS';
+export const RECEIVE_METRICS_NODATA_SUCCESS = 'RECEIVE_METRICS_NODATA_SUCCESS';
+export const RECEIVE_METRICS_ERROR = 'RECEIVE_METRICS_ERROR';
diff --git a/app/assets/javascripts/serverless/store/mutations.js b/app/assets/javascripts/serverless/store/mutations.js
new file mode 100644
index 00000000000..2685a5b11ff
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/mutations.js
@@ -0,0 +1,49 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.REQUEST_FUNCTIONS_LOADING](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_FUNCTIONS_SUCCESS](state, data) {
+ state.functions = data.functions;
+ state.installed = data.knative_installed;
+ state.isLoading = false;
+ state.hasFunctionData = true;
+ },
+ [types.RECEIVE_FUNCTIONS_PARTIAL](state, data) {
+ state.functions = data.functions;
+ state.installed = true;
+ state.isLoading = true;
+ state.hasFunctionData = true;
+ },
+ [types.RECEIVE_FUNCTIONS_TIMEOUT](state) {
+ state.isLoading = false;
+ },
+ [types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, data) {
+ state.isLoading = false;
+ state.installed = data.knative_installed;
+ state.hasFunctionData = false;
+ },
+ [types.RECEIVE_FUNCTIONS_ERROR](state, error) {
+ state.error = error;
+ state.hasFunctionData = false;
+ state.isLoading = false;
+ },
+ [types.RECEIVE_METRICS_SUCCESS](state, data) {
+ state.isLoading = false;
+ state.hasPrometheusData = true;
+ state.graphData = data;
+ },
+ [types.RECEIVE_METRICS_NODATA_SUCCESS](state) {
+ state.isLoading = false;
+ state.hasPrometheusData = false;
+ },
+ [types.RECEIVE_METRICS_ERROR](state, error) {
+ state.hasPrometheusData = false;
+ state.error = error;
+ },
+ [types.RECEIVE_METRICS_NO_PROMETHEUS](state) {
+ state.hasPrometheusData = false;
+ state.hasPrometheus = false;
+ },
+};
diff --git a/app/assets/javascripts/serverless/store/state.js b/app/assets/javascripts/serverless/store/state.js
new file mode 100644
index 00000000000..fdd29299749
--- /dev/null
+++ b/app/assets/javascripts/serverless/store/state.js
@@ -0,0 +1,14 @@
+export default () => ({
+ error: null,
+ installed: 'checking',
+ isLoading: true,
+
+ // functions
+ functions: [],
+ hasFunctionData: true,
+
+ // function_details
+ hasPrometheus: true,
+ hasPrometheusData: false,
+ graphData: {},
+});
diff --git a/app/assets/javascripts/serverless/stores/serverless_details_store.js b/app/assets/javascripts/serverless/stores/serverless_details_store.js
deleted file mode 100644
index 5394d2cded1..00000000000
--- a/app/assets/javascripts/serverless/stores/serverless_details_store.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export default class ServerlessDetailsStore {
- constructor() {
- this.state = {
- functionDetail: {},
- };
- }
-
- updateDetailedFunction(func) {
- this.state.functionDetail = func;
- }
-}
diff --git a/app/assets/javascripts/serverless/stores/serverless_store.js b/app/assets/javascripts/serverless/stores/serverless_store.js
deleted file mode 100644
index 816d55a03f9..00000000000
--- a/app/assets/javascripts/serverless/stores/serverless_store.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export default class ServerlessStore {
- constructor(knativeInstalled = false, clustersPath, helpPath) {
- this.state = {
- functions: {},
- hasFunctionData: true,
- loadingData: true,
- installed: knativeInstalled,
- clustersPath,
- helpPath,
- };
- }
-
- updateFunctionsFromServer(upstreamFunctions = []) {
- this.state.functions = upstreamFunctions.reduce((rv, func) => {
- const envs = rv;
- envs[func.environment_scope] = (rv[func.environment_scope] || []).concat([func]);
-
- return envs;
- }, {});
- }
-
- updateLoadingState(loadingData) {
- this.state.loadingData = loadingData;
- }
-
- toggleNoFunctionData() {
- this.state.hasFunctionData = false;
- }
-}
diff --git a/app/assets/javascripts/serverless/utils.js b/app/assets/javascripts/serverless/utils.js
new file mode 100644
index 00000000000..8b9e96ce9aa
--- /dev/null
+++ b/app/assets/javascripts/serverless/utils.js
@@ -0,0 +1,23 @@
+// Validate that the object coming in has valid query details and results
+export const validateGraphData = data =>
+ data.queries &&
+ Array.isArray(data.queries) &&
+ data.queries.filter(query => {
+ if (Array.isArray(query.result)) {
+ return query.result.filter(res => Array.isArray(res.values)).length === query.result.length;
+ }
+
+ return false;
+ }).length === data.queries.length;
+
+export const translate = functions =>
+ functions.reduce(
+ (acc, func) =>
+ Object.assign(acc, {
+ [func.environment_scope]: (acc[func.environment_scope] || []).concat([func]),
+ }),
+ {},
+ );
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 7f86741ed29..35eba266625 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -2,7 +2,7 @@
import $ from 'jquery';
import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
-import GfmAutoComplete from '~/gfm_auto_complete';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import { __, s__ } from '~/locale';
import Api from '~/api';
import { GlModal, GlTooltipDirective } from '@gitlab/ui';
@@ -178,7 +178,7 @@ export default {
/>
<div ref="userStatusForm" class="form-group position-relative m-0">
<div class="input-group">
- <span class="input-group-btn">
+ <span class="input-group-prepend">
<button
ref="toggleEmojiMenuButton"
v-gl-tooltip.bottom
@@ -194,9 +194,9 @@ export default {
v-show="noEmoji"
class="js-no-emoji-placeholder no-emoji-placeholder position-relative"
>
- <icon name="emoji_slightly_smiling_face" css-classes="award-control-icon-neutral" />
- <icon name="emoji_smiley" css-classes="award-control-icon-positive" />
- <icon name="emoji_smile" css-classes="award-control-icon-super-positive" />
+ <icon name="slight-smile" css-classes="award-control-icon-neutral" />
+ <icon name="smiley" css-classes="award-control-icon-positive" />
+ <icon name="smile" css-classes="award-control-icon-super-positive" />
</span>
</button>
</span>
@@ -211,7 +211,7 @@ export default {
@keyup.enter.prevent
@click="hideEmojiMenu"
/>
- <span v-show="isDirty" class="input-group-btn">
+ <span v-show="isDirty" class="input-group-append">
<button
v-gl-tooltip.bottom
:title="s__('SetStatusModal|Clear status')"
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index d1a396182b3..0074d7099dc 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -74,8 +74,7 @@ export default {
}
if (!this.users.length) {
- const emptyTooltipLabel =
- this.issuableType === 'issue' ? __('Assignee(s)') : __('Assignee');
+ const emptyTooltipLabel = __('Assignee(s)');
names.push(emptyTooltipLabel);
}
@@ -90,6 +89,27 @@ export default {
return counter;
},
+ mergeNotAllowedTooltipMessage() {
+ const assigneesCount = this.users.length;
+
+ if (this.issuableType !== 'merge_request' || assigneesCount === 0) {
+ return null;
+ }
+
+ const cannotMergeCount = this.users.filter(u => u.can_merge === false).length;
+ const canMergeCount = assigneesCount - cannotMergeCount;
+
+ if (canMergeCount === assigneesCount) {
+ // Everyone can merge
+ return null;
+ } else if (cannotMergeCount === assigneesCount && assigneesCount > 1) {
+ return 'No one can merge';
+ } else if (assigneesCount === 1) {
+ return 'Cannot merge';
+ }
+
+ return `${canMergeCount}/${assigneesCount} can merge`;
+ },
},
methods: {
assignSelf() {
@@ -133,7 +153,7 @@ export default {
data-placement="left"
data-boundary="viewport"
>
- <i v-if="hasNoUsers" aria-label="No Assignee" class="fa fa-user"> </i>
+ <i v-if="hasNoUsers" aria-label="None" class="fa fa-user"> </i>
<button
v-for="(user, index) in users"
v-if="shouldRenderCollapsedAssignee(index)"
@@ -154,9 +174,18 @@ export default {
</button>
</div>
<div class="value hide-collapsed">
+ <span
+ v-if="mergeNotAllowedTooltipMessage"
+ v-tooltip
+ :title="mergeNotAllowedTooltipMessage"
+ data-placement="left"
+ class="float-right cannot-be-merged"
+ >
+ <i aria-hidden="true" data-hidden="true" class="fa fa-exclamation-triangle"></i>
+ </span>
<template v-if="hasNoUsers">
- <span class="assign-yourself no-value">
- No assignee
+ <span class="assign-yourself no-value qa-assign-yourself">
+ None
<template v-if="editable">
- <button type="button" class="btn-link" @click="assignSelf">assign yourself</button>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index c03b2a68c78..d84d5344935 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -49,10 +49,10 @@ export default {
},
computed: {
hasTimeSpent() {
- return !!this.timeSpent;
+ return Boolean(this.timeSpent);
},
hasTimeEstimate() {
- return !!this.timeEstimate;
+ return Boolean(this.timeEstimate);
},
showComparisonState() {
return this.hasTimeEstimate && this.hasTimeSpent;
@@ -67,7 +67,7 @@ export default {
return !this.hasTimeEstimate && !this.hasTimeSpent;
},
showHelpState() {
- return !!this.showHelp;
+ return Boolean(this.showHelp);
},
},
created() {
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
index 706e6ca19c3..57125c78cf6 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
@@ -50,6 +50,9 @@ export default {
buttonLabel() {
return this.isTodo ? MARK_TEXT : TODO_TEXT;
},
+ buttonTooltip() {
+ return !this.collapsed ? undefined : this.buttonLabel;
+ },
collapsedButtonIconClasses() {
return this.isTodo ? 'todo-undone' : '';
},
@@ -69,7 +72,7 @@ export default {
<button
v-tooltip
:class="buttonClasses"
- :title="buttonLabel"
+ :title="buttonTooltip"
:aria-label="buttonLabel"
:data-issuable-id="issuableId"
:data-issuable-type="issuableType"
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
index 225ebb61195..110175a6779 100644
--- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
+++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import _ from 'underscore';
+import { __ } from '~/locale';
function isValidProjectId(id) {
return id > 0;
@@ -40,7 +41,9 @@ class SidebarMoveIssue {
this.mediator
.fetchAutocompleteProjects(searchTerm)
.then(callback)
- .catch(() => new window.Flash('An error occurred while fetching projects autocomplete.'));
+ .catch(
+ () => new window.Flash(__('An error occurred while fetching projects autocomplete.')),
+ );
},
renderRow: project => `
<li>
@@ -72,7 +75,7 @@ class SidebarMoveIssue {
this.$confirmButton.disable().addClass('is-loading');
this.mediator.moveIssue().catch(() => {
- window.Flash('An error occurred while moving the issue.');
+ window.Flash(__('An error occurred while moving the issue.'));
this.$confirmButton.enable().removeClass('is-loading');
});
}
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 3e040ec8428..22ac8df9699 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -2,6 +2,7 @@ import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import Service from './services/sidebar_service';
import Store from './stores/sidebar_store';
+import { __ } from '~/locale';
export default class SidebarMediator {
constructor(options) {
@@ -45,7 +46,7 @@ export default class SidebarMediator {
.then(data => {
this.processFetchedData(data);
})
- .catch(() => new Flash('Error occurred when fetching sidebar data'));
+ .catch(() => new Flash(__('Error occurred when fetching sidebar data')));
}
processFetchedData(data) {
diff --git a/app/assets/javascripts/snippet/snippet_embed.js b/app/assets/javascripts/snippet/snippet_embed.js
index 873a506a92f..fe08d2c7ebb 100644
--- a/app/assets/javascripts/snippet/snippet_embed.js
+++ b/app/assets/javascripts/snippet/snippet_embed.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default () => {
const { protocol, host, pathname } = window.location;
const shareBtn = document.querySelector('.js-share-btn');
@@ -10,7 +12,7 @@ export default () => {
shareBtn.classList.add('is-active');
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
- embedAction.innerText = 'Share';
+ embedAction.innerText = __('Share');
});
embedBtn.addEventListener('click', () => {
@@ -18,6 +20,6 @@ export default () => {
shareBtn.classList.remove('is-active');
const scriptTag = `<script src="${url}.js"></script>`;
snippetUrlArea.value = scriptTag;
- embedAction.innerText = 'Embed';
+ embedAction.innerText = __('Embed');
});
};
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 7404dfbf22a..70f89152f70 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -31,7 +31,7 @@ export default class Star {
$this.prepend(spriteIcon('star', iconClasses));
}
})
- .catch(() => Flash('Star toggle failed. Try again later.'));
+ .catch(() => Flash(__('Star toggle failed. Try again later.')));
});
}
}
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index ebe1c6dd02d..7206bbd7109 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { __ } from './locale';
export default function subscriptionSelect() {
$('.js-subscription-event').each((i, element) => {
@@ -8,7 +9,7 @@ export default function subscriptionSelect() {
selectable: true,
fieldName,
toggleLabel(selected, el, instance) {
- let label = 'Subscription';
+ let label = __('Subscription');
const $item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 6065770e68d..78609ce0610 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -3,6 +3,7 @@
import $ from 'jquery';
import Api from '../api';
import TemplateSelector from '../blob/template_selector';
+import { __ } from '~/locale';
export default class IssuableTemplateSelector extends TemplateSelector {
constructor(...args) {
@@ -25,7 +26,7 @@ export default class IssuableTemplateSelector extends TemplateSelector {
$('.no-template', this.dropdown.parent()).on('click', () => {
this.currentTemplate.content = '';
this.setInputValueToTemplateContent();
- $('.dropdown-toggle-text', this.dropdown).text('Choose a template');
+ $('.dropdown-toggle-text', this.dropdown).text(__('Choose a template'));
});
}
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index e5dd7a465ea..9c7c10d9864 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -4,6 +4,7 @@ import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
import * as webLinks from 'xterm/lib/addons/webLinks/webLinks';
import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+import { __ } from '~/locale';
const SCROLL_MARGIN = 5;
@@ -78,7 +79,8 @@ export default class GLTerminal {
}
handleSocketFailure() {
- this.terminal.write('\r\nConnection failure');
+ this.terminal.write('\r\n');
+ this.terminal.write(__('Connection failure'));
}
addScrollListener(onScrollLimit) {
diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js
index a55a338eea8..1e75ee60671 100644
--- a/app/assets/javascripts/test_utils/index.js
+++ b/app/assets/javascripts/test_utils/index.js
@@ -1,5 +1,5 @@
-import 'core-js/es6/map';
-import 'core-js/es6/set';
+import 'core-js/es/map';
+import 'core-js/es/set';
import simulateDrag from './simulate_drag';
import simulateInput from './simulate_input';
diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js
index 1a98564ff55..ca0fc0700ad 100644
--- a/app/assets/javascripts/u2f/error.js
+++ b/app/assets/javascripts/u2f/error.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default class U2FError {
constructor(errorCode, u2fFlowType) {
this.errorCode = errorCode;
@@ -8,15 +10,17 @@ export default class U2FError {
message() {
if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
- return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.';
+ return __(
+ 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.',
+ );
} else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) {
if (this.u2fFlowType === 'authenticate') {
- return 'This device has not been registered with us.';
+ return __('This device has not been registered with us.');
}
if (this.u2fFlowType === 'register') {
- return 'This device has already been registered with us.';
+ return __('This device has already been registered with us.');
}
}
- return 'There was a problem communicating with your device.';
+ return __('There was a problem communicating with your device.');
}
}
diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js
index d3d745a3c11..1e7a5fb19c2 100644
--- a/app/assets/javascripts/usage_ping_consent.js
+++ b/app/assets/javascripts/usage_ping_consent.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Flash, { hideFlash } from './flash';
import { parseBoolean } from './lib/utils/common_utils';
+import { __ } from './locale';
export default () => {
$('body').on('click', '.js-usage-consent-action', e => {
@@ -25,7 +26,7 @@ export default () => {
})
.catch(() => {
hideConsentMessage();
- Flash('Something went wrong. Try again later.');
+ Flash(__('Something went wrong. Try again later.'));
});
});
};
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 4017630d6ef..7e6f02b10af 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -5,7 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
+import { s__, __, sprintf } from './locale';
import ModalStore from './boards/stores/modal_store';
// TODO: remove eventHub hack after code splitting refactor
@@ -93,23 +93,22 @@ function UsersSelect(currentUser, els, options = {}) {
}
// Save current selected user to the DOM
- const input = document.createElement('input');
- input.type = 'hidden';
- input.name = $dropdown.data('fieldName');
-
- const currentUserInfo = $dropdown.data('currentUserInfo');
-
- if (currentUserInfo) {
- input.value = currentUserInfo.id;
- input.dataset.meta = _.escape(currentUserInfo.name);
- } else if (_this.currentUser) {
- input.value = _this.currentUser.id;
- }
+ const currentUserInfo = $dropdown.data('currentUserInfo') || {};
+ const currentUser = _this.currentUser || {};
+ const fieldName = $dropdown.data('fieldName');
+ const userName = currentUserInfo.name;
+ const userId = currentUserInfo.id || currentUser.id;
+
+ const inputHtmlString = _.template(`
+ <input type="hidden" name="<%- fieldName %>"
+ data-meta="<%- userName %>"
+ value="<%- userId %>" />
+ `)({ fieldName, userName, userId });
if ($selectbox) {
- $dropdown.parent().before(input);
+ $dropdown.parent().before(inputHtmlString);
} else {
- $dropdown.after(input);
+ $dropdown.after(inputHtmlString);
}
};
@@ -158,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) {
.get(0);
if (selectedUsers.length === 0) {
- return 'Unassigned';
+ return s__('UsersSelect|Unassigned');
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
- return `${selectedUser.name} + ${otherSelected.length} more`;
+ return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
+ name: selectedUser.name,
+ length: otherSelected.length,
+ });
} else {
- return `${firstUser.name} + ${selectedUsers.length - 1} more`;
+ return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
+ name: firstUser.name,
+ length: selectedUsers.length - 1,
+ });
}
};
@@ -219,11 +224,11 @@ function UsersSelect(currentUser, els, options = {}) {
tooltipTitle = _.escape(user.name);
} else {
user = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
username: '',
avatar: '',
};
- tooltipTitle = __('Assignee');
+ tooltipTitle = s__('UsersSelect|Assignee');
}
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
@@ -234,7 +239,11 @@ function UsersSelect(currentUser, els, options = {}) {
'<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
);
assigneeTemplate = _.template(
- '<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>',
+ `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
+ ${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
+ openingTag: '<a href="#" class="js-assign-yourself">',
+ closingTag: '</a>',
+ })}</span> <% } %>`,
);
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
@@ -303,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
users.unshift({
beforeDivider: true,
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
id: 0,
});
}
@@ -311,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
name = showAnyUser;
if (name === true) {
- name = 'Any User';
+ name = s__('UsersSelect|Any User');
}
anyUser = {
beforeDivider: true,
@@ -597,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) {
showEmailUser = $(select).data('emailUser');
firstUser = $(select).data('firstUser');
return $(select).select2({
- placeholder: 'Search for a user',
+ placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
@@ -622,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
if (showNullUser) {
nullUser = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
id: 0,
};
data.results.unshift(nullUser);
@@ -630,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showAnyUser) {
name = showAnyUser;
if (name === true) {
- name = 'Any User';
+ name = s__('UsersSelect|Any User');
}
anyUser = {
name: name,
@@ -646,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) {
) {
var trimmed = query.term.trim();
emailUser = {
- name: 'Invite "' + trimmed + '" by email',
+ name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed,
id: trimmed,
invite: true,
@@ -689,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) {
id = $(element).val();
if (id === '0') {
nullUser = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
};
return callback(nullUser);
} else if (id !== '') {
diff --git a/app/assets/javascripts/validators/input_validator.js b/app/assets/javascripts/validators/input_validator.js
new file mode 100644
index 00000000000..f37373977b8
--- /dev/null
+++ b/app/assets/javascripts/validators/input_validator.js
@@ -0,0 +1,34 @@
+const invalidInputClass = 'gl-field-error-outline';
+
+export default class InputValidator {
+ constructor() {
+ this.inputDomElement = {};
+ this.inputErrorMessage = {};
+ this.errorMessage = null;
+ this.invalidInput = null;
+ }
+
+ setValidationStateAndMessage() {
+ this.setValidationMessage();
+
+ const isInvalidInput = !this.inputDomElement.checkValidity();
+ this.inputDomElement.classList.toggle(invalidInputClass, isInvalidInput);
+ this.inputErrorMessage.classList.toggle('hide', !isInvalidInput);
+ }
+
+ setValidationMessage() {
+ if (this.invalidInput) {
+ this.inputDomElement.setCustomValidity(this.errorMessage);
+ this.inputErrorMessage.innerHTML = this.errorMessage;
+ } else {
+ this.resetValidationMessage();
+ }
+ }
+
+ resetValidationMessage() {
+ if (this.inputDomElement.validationMessage === this.errorMessage) {
+ this.inputDomElement.setCustomValidity('');
+ this.inputErrorMessage.innerHTML = this.inputDomElement.title;
+ }
+ }
+}
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
new file mode 100644
index 00000000000..91d0382feac
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/index.js
@@ -0,0 +1,2 @@
+import './styles/toolbar.css';
+import 'vendor/visual_review_toolbar';
diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
new file mode 100644
index 00000000000..342b3599a44
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
@@ -0,0 +1,149 @@
+/*
+ As a standalone script, the toolbar has its own css
+ */
+
+#gitlab-collapse > * {
+ pointer-events: none;
+}
+
+#gitlab-form-wrapper {
+ display: flex;
+ flex-direction: column;
+ width: 100%
+}
+
+#gitlab-review-container {
+ max-width: 22rem;
+ max-height: 22rem;
+ overflow: scroll;
+ position: fixed;
+ bottom: 1rem;
+ right: 1rem;
+ display: flex;
+ flex-direction: row-reverse;
+ padding: 1rem;
+ background-color: #fff;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
+ 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+ 'Noto Color Emoji';
+ font-size: .8rem;
+ font-weight: 400;
+ color: #2e2e2e;
+}
+
+.gitlab-open-wrapper {
+ max-width: 22rem;
+ max-height: 22rem;
+}
+
+.gitlab-closed-wrapper {
+ max-width: 3.4rem;
+ max-height: 3.4rem;
+}
+
+.gitlab-button {
+ cursor: pointer;
+ transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear;
+}
+
+.gitlab-button-secondary {
+ background: none #fff;
+ margin: 0 .5rem;
+ border: 1px solid #e3e3e3;
+}
+
+.gitlab-button-secondary:hover {
+ background-color: #f0f0f0;
+ border-color: #e3e3e3;
+ color: #2e2e2e;
+}
+
+.gitlab-button-secondary:active {
+ color: #2e2e2e;
+ background-color: #e1e1e1;
+ border-color: #dadada;
+}
+
+.gitlab-button-success:hover {
+ color: #fff;
+ background-color: #137e3f;
+ border-color: #127339;
+}
+
+.gitlab-button-success:active {
+ background-color: #168f48;
+ border-color: #12753a;
+ color: #fff;
+}
+
+.gitlab-button-success {
+ background-color: #1aaa55;
+ border: 1px solid #168f48;
+ color: #fff;
+}
+
+.gitlab-button-wide {
+ width: 100%;
+}
+
+.gitlab-button-wrapper {
+ margin-top: 1rem;
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-end;
+}
+
+.gitlab-collapse {
+ width: 2.4rem;
+ height: 2.2rem;
+ margin-left: 1rem;
+ padding: .5rem;
+}
+
+.gitlab-collapse-closed {
+ align-self: center;
+}
+
+.gitlab-checkbox-label {
+ padding: 0 .2rem;
+}
+
+.gitlab-checkbox-wrapper {
+ display: flex;
+ align-items: baseline;
+}
+
+.gitlab-label {
+ font-weight: 600;
+ display: inline-block;
+ width: 100%;
+}
+
+.gitlab-link {
+ color: #1b69b6;
+ text-decoration: none;
+ background-color: transparent;
+ background-image: none;
+}
+
+.gitlab-message {
+ padding: .25rem 0;
+ margin: 0;
+ line-height: 1.2rem;
+}
+
+.gitlab-metadata-note {
+ font-size: .7rem;
+ line-height: 1rem;
+ color: #666;
+ margin-bottom: 0;
+}
+
+.gitlab-input {
+ width: 100%;
+ border: 1px solid #dfdfdf;
+ border-radius: 4px;
+ padding: .1rem .2rem;
+ min-height: 2rem;
+ max-width: 17rem;
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index da0a9483f8e..abe5bdd2901 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -23,6 +23,8 @@ export default {
TooltipOnTruncate,
FilteredSearchDropdown,
ReviewAppLink,
+ VisualReviewAppLink: () =>
+ import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -37,6 +39,20 @@ export default {
type: Boolean,
required: true,
},
+ showVisualReviewApp: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ visualReviewAppMeta: {
+ type: Object,
+ required: false,
+ default: () => ({
+ sourceProjectId: '',
+ mergeRequestId: '',
+ appUrl: '',
+ }),
+ },
},
deployedTextMap: {
running: __('Deploying to'),
@@ -61,16 +77,16 @@ export default {
return this.deployment.external_url;
},
hasExternalUrls() {
- return !!(this.deployment.external_url && this.deployment.external_url_formatted);
+ return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasDeploymentTime() {
- return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
+ return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
},
hasDeploymentMeta() {
- return !!(this.deployment.url && this.deployment.name);
+ return Boolean(this.deployment.url && this.deployment.name);
},
hasMetrics() {
- return !!this.deployment.metrics_url;
+ return Boolean(this.deployment.metrics_url);
},
deployedText() {
return this.$options.deployedTextMap[this.deployment.status];
@@ -168,6 +184,11 @@ export default {
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
+ <visual-review-app-link
+ v-if="showVisualReviewApp"
+ :link="deploymentExternalUrl"
+ :app-metadata="visualReviewAppMeta"
+ />
</template>
<template slot="result" slot-scope="slotProps">
@@ -187,11 +208,17 @@ export default {
</a>
</template>
</filtered-search-dropdown>
- <review-app-link
- v-else
- :link="deploymentExternalUrl"
- css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin"
- />
+ <template v-else>
+ <review-app-link
+ :link="deploymentExternalUrl"
+ css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ />
+ <visual-review-app-link
+ v-if="showVisualReviewApp"
+ :link="deploymentExternalUrl"
+ :app-metadata="visualReviewAppMeta"
+ />
+ </template>
</template>
<span
v-if="deployment.stop_url"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
new file mode 100644
index 00000000000..19a222462b3
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
@@ -0,0 +1,46 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { WARNING, DANGER, WARNING_MESSAGE_CLASS, DANGER_MESSAGE_CLASS } from '../constants';
+
+export default {
+ name: 'MrWidgetAlertMessage',
+ components: {
+ GlLink,
+ Icon,
+ },
+ props: {
+ type: {
+ type: String,
+ required: false,
+ default: DANGER,
+ validator: value => [WARNING, DANGER].includes(value),
+ },
+ helpPath: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ },
+ computed: {
+ messageClass() {
+ if (this.type === WARNING) {
+ return WARNING_MESSAGE_CLASS;
+ } else if (this.type === DANGER) {
+ return DANGER_MESSAGE_CLASS;
+ }
+
+ return '';
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="m-3 ml-7" :class="messageClass">
+ <slot></slot>
+ <gl-link v-if="helpPath" :href="helpPath" target="_blank">
+ <icon :size="16" name="question-o" class="align-middle" />
+ </gl-link>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 3b9fc2661ef..361441640e1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -109,33 +109,35 @@ export default {
></div>
</div>
- <div v-if="mr.isOpen" class="branch-actions d-flex">
- <a
- v-if="!mr.sourceBranchRemoved"
- v-tooltip
- :href="webIdePath"
- :title="ideButtonTitle"
- :class="{ disabled: !mr.canPushToSourceBranch }"
- class="btn btn-default js-web-ide d-none d-md-inline-block append-right-8"
- data-placement="bottom"
- tabindex="0"
- role="button"
- >
- {{ s__('mrWidget|Open in Web IDE') }}
- </a>
- <button
- :disabled="mr.sourceBranchRemoved"
- data-target="#modal_merge_info"
- data-toggle="modal"
- class="btn btn-default js-check-out-branch append-right-default"
- type="button"
- >
- {{ s__('mrWidget|Check out branch') }}
- </button>
+ <div class="branch-actions d-flex">
+ <template v-if="mr.isOpen">
+ <a
+ v-if="!mr.sourceBranchRemoved"
+ v-tooltip
+ :href="webIdePath"
+ :title="ideButtonTitle"
+ :class="{ disabled: !mr.canPushToSourceBranch }"
+ class="btn btn-default js-web-ide d-none d-md-inline-block append-right-8"
+ data-placement="bottom"
+ tabindex="0"
+ role="button"
+ >
+ {{ s__('mrWidget|Open in Web IDE') }}
+ </a>
+ <button
+ :disabled="mr.sourceBranchRemoved"
+ data-target="#modal_merge_info"
+ data-toggle="modal"
+ class="btn btn-default js-check-out-branch append-right-default"
+ type="button"
+ >
+ {{ s__('mrWidget|Check out branch') }}
+ </button>
+ </template>
<span class="dropdown">
<button
type="button"
- class="btn dropdown-toggle"
+ class="btn dropdown-toggle qa-dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
aria-haspopup="true"
@@ -145,12 +147,20 @@ export default {
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
- <a :href="mr.emailPatchesPath" class="js-download-email-patches" download>
+ <a
+ :href="mr.emailPatchesPath"
+ class="js-download-email-patches qa-download-email-patches"
+ download
+ >
{{ s__('mrWidget|Email patches') }}
</a>
</li>
<li>
- <a :href="mr.plainDiffPath" class="js-download-plain-diff" download>
+ <a
+ :href="mr.plainDiffPath"
+ class="js-download-plain-diff qa-download-plain-diff"
+ download
+ >
{{ s__('mrWidget|Plain diff') }}
</a>
</li>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index f11cf21b0ca..c377c16fb13 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -1,10 +1,13 @@
<script>
/* eslint-disable vue/require-default-prop */
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
export default {
name: 'MRWidgetPipeline',
@@ -13,7 +16,15 @@ export default {
CiIcon,
Icon,
TooltipOnTruncate,
+ GlLink,
+ PipelineLink,
+ LinkedPipelinesMiniList: () =>
+ import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [mrWidgetPipelineMixin],
props: {
pipeline: {
type: Object,
@@ -74,16 +85,21 @@ export default {
false,
);
},
+ isTriggeredByMergeRequest() {
+ return Boolean(this.pipeline.merge_request);
+ },
+ isMergeRequestPipeline() {
+ return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
+ },
},
};
</script>
<template>
- <div v-if="hasPipeline || hasCIError" class="ci-widget media">
- <template v-if="hasCIError">
+ <div class="ci-widget media js-ci-widget">
+ <template v-if="!hasPipeline || hasCIError">
<div
- class="add-border ci-status-icon ci-status-icon-failed ci-error
- js-ci-error append-right-default"
+ class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default"
>
<icon :size="32" name="status_failed_borderless" />
</div>
@@ -96,24 +112,61 @@ export default {
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
<div class="media-body">
- <div class="font-weight-bold">
- Pipeline
- <a :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number"
- >#{{ pipeline.id }}</a
- >
-
+ <div class="font-weight-bold js-pipeline-info-container">
+ {{ s__('Pipeline|Pipeline') }}
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="pipeline-id pipeline-iid font-weight-normal"
+ />
{{ pipeline.details.status.label }}
-
<template v-if="hasCommitInfo">
- for
- <a
+ {{ s__('Pipeline|for') }}
+ <gl-link
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link font-weight-normal"
+ >{{ pipeline.commit.short_id }}</gl-link
>
- {{ pipeline.commit.short_id }}</a
- >
- on
+ {{ s__('Pipeline|on') }}
+ <template v-if="isTriggeredByMergeRequest">
+ <gl-link
+ v-gl-tooltip
+ :href="pipeline.merge_request.path"
+ :title="pipeline.merge_request.title"
+ class="font-weight-normal"
+ >!{{ pipeline.merge_request.iid }}</gl-link
+ >
+ {{ s__('Pipeline|with') }}
+ <tooltip-on-truncate
+ :title="pipeline.merge_request.source_branch"
+ truncate-target="child"
+ class="label-branch label-truncate"
+ >
+ <gl-link
+ :href="pipeline.merge_request.source_branch_path"
+ class="font-weight-normal"
+ >{{ pipeline.merge_request.source_branch }}</gl-link
+ >
+ </tooltip-on-truncate>
+
+ <template v-if="isMergeRequestPipeline">
+ {{ s__('Pipeline|into') }}
+ <tooltip-on-truncate
+ :title="pipeline.merge_request.target_branch"
+ truncate-target="child"
+ class="label-branch label-truncate"
+ >
+ <gl-link
+ :href="pipeline.merge_request.target_branch_path"
+ class="font-weight-normal"
+ >{{ pipeline.merge_request.target_branch }}</gl-link
+ >
+ </tooltip-on-truncate>
+ </template>
+ </template>
<tooltip-on-truncate
+ v-else
:title="sourceBranch"
truncate-target="child"
class="label-branch label-truncate"
@@ -121,20 +174,29 @@ export default {
/>
</template>
</div>
- <div v-if="pipeline.coverage" class="coverage">Coverage {{ pipeline.coverage }}%</div>
+ <div v-if="pipeline.coverage" class="coverage">
+ {{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
+ </div>
</div>
</div>
<div>
<span class="mr-widget-pipeline-graph">
- <span v-if="hasStages" class="stage-cell">
- <div
- v-for="(stage, i) in pipeline.details.stages"
- :key="i"
- class="stage-container dropdown js-mini-pipeline-graph mr-widget-pipeline-stages"
- >
- <pipeline-stage :stage="stage" />
- </div>
+ <span class="stage-cell">
+ <linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" />
+ <template v-if="hasStages">
+ <div
+ v-for="(stage, i) in pipeline.details.stages"
+ :key="i"
+ :class="{
+ 'has-downstream': hasDownstream(i),
+ }"
+ class="stage-container dropdown js-mini-pipeline-graph mr-widget-pipeline-stages"
+ >
+ <pipeline-stage :stage="stage" />
+ </div>
+ </template>
</span>
+ <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" />
</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 5f5fe67b3c1..03a15ba81ed 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -30,9 +30,6 @@ export default {
},
},
computed: {
- pipeline() {
- return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
- },
branch() {
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch;
},
@@ -48,6 +45,19 @@ export default {
hasDeploymentMetrics() {
return this.isPostMerge;
},
+ visualReviewAppMeta() {
+ return {
+ appUrl: this.mr.appUrl,
+ mergeRequestId: this.mr.iid,
+ sourceProjectId: this.mr.sourceProjectId,
+ };
+ },
+ pipeline() {
+ return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
+ },
+ showVisualReviewAppLink() {
+ return Boolean(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable);
+ },
},
};
</script>
@@ -61,14 +71,18 @@ export default {
:source-branch-link="branchLink"
:troubleshooting-docs-path="mr.troubleshootingDocsPath"
/>
- <div v-if="deployments.length" slot="footer" class="mr-widget-extension">
- <deployment
- v-for="deployment in deployments"
- :key="deployment.id"
- :class="deploymentClass"
- :deployment="deployment"
- :show-metrics="hasDeploymentMetrics"
- />
- </div>
+ <template v-slot:footer>
+ <div v-if="deployments.length" class="mr-widget-extension">
+ <deployment
+ v-for="deployment in deployments"
+ :key="deployment.id"
+ :class="deploymentClass"
+ :deployment="deployment"
+ :show-metrics="hasDeploymentMetrics"
+ :show-visual-review-app="true"
+ :visual-review-app-meta="visualReviewAppMeta"
+ />
+ </div>
+ </template>
</mr-widget-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 780ced4d382..392eb6fb425 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -33,7 +33,7 @@ export default {
</script>
<template>
<div class="space-children d-flex append-right-10 widget-status-icon">
- <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon /></div>
+ <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="md" /></div>
<ci-icon v-else :status="statusObj" :size="24" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
index de9c122f268..457a71cab95 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
@@ -19,6 +19,6 @@ export default {
</script>
<template>
<a :href="link" target="_blank" rel="noopener noreferrer nofollow" :class="cssClass">
- {{ __('View app') }} <icon name="external-link" />
+ {{ __('View app') }} <icon css-classes="fgray" name="external-link" />
</a>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index 780ecdcdac4..6aad2a26a53 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -14,7 +14,7 @@ export default {
</script>
<template>
- <p v-once class="mr-info-list mr-links source-branch-removal-status append-bottom-0">
+ <p v-once class="mr-info-list mr-links append-bottom-0">
<span class="status-text" v-html="removesBranchText"> </span>
<i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle">
</i>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
index a38f25cce35..acd8037cfb2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
@@ -30,6 +30,7 @@ export default {
:id="inputId"
:value="value"
class="form-control js-gfm-input append-bottom-default commit-message-edit"
+ dir="auto"
required="required"
rows="7"
@input="$emit('input', $event.target.value)"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
index b3c1c0e329d..b6722de5277 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
@@ -20,7 +20,6 @@ export default {
<div>
<gl-dropdown
right
- no-caret
text="Use an existing commit message"
variant="link"
class="mr-commit-dropdown"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index 33963d5e1e6..0312b147b62 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -14,6 +14,10 @@ export default {
type: Boolean,
required: true,
},
+ isFastForwardEnabled: {
+ type: Boolean,
+ required: true,
+ },
commitsCount: {
type: Number,
required: false,
@@ -37,16 +41,22 @@ export default {
return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
},
modifyLinkMessage() {
- return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
+ if (this.isFastForwardEnabled) return __('Modify commit message');
+ else if (this.isSquashEnabled) return __('Modify commit messages');
+ return __('Modify merge commit');
},
ariaLabel() {
return this.expanded ? __('Collapse') : __('Expand');
},
message() {
+ const message = this.isFastForwardEnabled
+ ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
+ : s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
+ );
+
return sprintf(
- s__(
- 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
- ),
+ message,
{
commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index a3a44dd8e99..83e7d6db9fa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -35,9 +35,7 @@ export default {
<status-icon status="warning" />
<div class="media-body space-children">
<span class="bold">
- <template v-if="mr.mergeError"
- >{{ mr.mergeError }}.</template
- >
+ <template v-if="mr.mergeError">{{ mr.mergeError }}</template>
{{ s__('mrWidget|This merge request failed to be merged automatically') }}
</span>
<button
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
index 2a4dff71d9b..11bc8c73ee9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
@@ -80,7 +80,7 @@ export default {
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span class="bold">
- <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }}. </span>
+ <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }} </span>
<span v-else> {{ s__('mrWidget|Merge failed.') }} </span>
<span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span>
</span>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index 1b3af2fccf2..88e1ccbaf35 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -57,7 +57,7 @@ export default {
removeSourceBranch() {
const options = {
sha: this.mr.sha,
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true,
};
@@ -85,7 +85,7 @@ export default {
<h4 class="d-flex align-items-start">
<span class="append-right-10">
{{ s__('mrWidget|Set by') }}
- <mr-widget-author :author="mr.setToMWPSBy" />
+ <mr-widget-author :author="mr.setToAutoMergeBy" />
{{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }}
</span>
<a
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index ce4207864ea..615d59a7b8e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -3,6 +3,7 @@ import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
+import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -21,6 +22,7 @@ export default {
CommitEdit,
CommitMessageDropdown,
},
+ mixins: [readyToMergeMixin],
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
@@ -29,7 +31,7 @@ export default {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
- setToMergeWhenPipelineSucceeds: false,
+ autoMergeStrategy: undefined,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
@@ -40,7 +42,7 @@ export default {
};
},
computed: {
- shouldShowMergeWhenPipelineSucceedsText() {
+ shouldShowAutoMergeText() {
return this.mr.isPipelineActive;
},
status() {
@@ -85,7 +87,7 @@ export default {
mergeButtonText() {
if (this.isMergingImmediately) {
return __('Merge in progress');
- } else if (this.shouldShowMergeWhenPipelineSucceedsText) {
+ } else if (this.shouldShowAutoMergeText) {
return __('Merge when pipeline succeeds');
}
@@ -94,15 +96,6 @@ export default {
shouldShowMergeOptionsDropdown() {
return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
},
- isMergeButtonDisabled() {
- const { commitMessage } = this;
- return Boolean(
- !commitMessage.length ||
- !this.shouldShowMergeControls ||
- this.isMakingRequest ||
- this.mr.preventMerge,
- );
- },
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
},
@@ -111,7 +104,13 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
shouldShowMergeControls() {
- return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
+ return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
+ },
+ shouldShowSquashEdit() {
+ return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
+ },
+ shouldShowMergeEdit() {
+ return !this.mr.ffOnlyEnabled;
},
},
methods: {
@@ -127,12 +126,12 @@ export default {
this.isMergingImmediately = true;
}
- this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true;
+ this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;
const options = {
sha: this.mr.sha,
commit_message: this.commitMessage,
- merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
+ auto_merge_strategy: this.autoMergeStrategy,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
squash_commit_message: this.squashCommitMessage,
@@ -159,9 +158,12 @@ export default {
});
},
initiateMergePolling() {
- simplePoll((continuePolling, stopPolling) => {
- this.handleMergePolling(continuePolling, stopPolling);
- });
+ simplePoll(
+ (continuePolling, stopPolling) => {
+ this.handleMergePolling(continuePolling, stopPolling);
+ },
+ { timeout: 0 },
+ );
},
handleMergePolling(continuePolling, stopPolling) {
this.service
@@ -192,6 +194,7 @@ export default {
})
.catch(() => {
new Flash(__('Something went wrong while merging this merge request. Please try again.')); // eslint-disable-line
+ stopPolling();
});
},
initiateRemoveSourceBranchPolling() {
@@ -321,43 +324,45 @@ export default {
<div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
{{ __('Fast-forward merge without a merge commit') }}
</div>
- <template v-else>
- <commits-header
- :is-squash-enabled="squashBeforeMerge"
- :commits-count="mr.commitsCount"
- :target-branch="mr.targetBranch"
- >
- <ul class="border-top content-list commits-list flex-list">
- <commit-edit
- v-if="squashBeforeMerge"
+ <commits-header
+ v-if="shouldShowSquashEdit || shouldShowMergeEdit"
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="mr.commitsCount"
+ :target-branch="mr.targetBranch"
+ :is-fast-forward-enabled="mr.ffOnlyEnabled"
+ :class="{ 'border-bottom': mr.mergeError }"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="shouldShowSquashEdit"
+ v-model="squashCommitMessage"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
v-model="squashCommitMessage"
- :label="__('Squash commit message')"
- input-id="squash-message-edit"
- squash
- >
- <commit-message-dropdown
- slot="header"
- v-model="squashCommitMessage"
- :commits="mr.commits"
+ :commits="mr.commits"
+ />
+ </commit-edit>
+ <commit-edit
+ v-if="shouldShowMergeEdit"
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
/>
- </commit-edit>
- <commit-edit
- v-model="commitMessage"
- :label="__('Merge commit message')"
- input-id="merge-message-edit"
- >
- <label slot="checkbox">
- <input
- id="include-description"
- type="checkbox"
- @change="updateMergeCommitMessage($event.target.checked)"
- />
- {{ __('Include merge request description') }}
- </label>
- </commit-edit>
- </ul>
- </commits-header>
- </template>
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index b1f5655a15a..accb9d9fef1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -29,8 +29,8 @@ export default {
</script>
<template>
- <div class="accept-control inline">
- <label class="merge-param-checkbox">
+ <div class="inline">
+ <label>
<input
:checked="value"
:disabled="isDisabled"
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
new file mode 100644
index 00000000000..0a29d55fbd6
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -0,0 +1,5 @@
+export const WARNING = 'warning';
+export const DANGER = 'danger';
+
+export const WARNING_MESSAGE_CLASS = 'warning_message';
+export const DANGER_MESSAGE_CLASS = 'danger_message';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/mixins/mr_widget_pipeline.js
new file mode 100644
index 00000000000..96e8bb45e34
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/mr_widget_pipeline.js
@@ -0,0 +1,15 @@
+export default {
+ computed: {
+ triggered() {
+ return [];
+ },
+ triggeredBy() {
+ return [];
+ },
+ },
+ methods: {
+ hasDownstream() {
+ return false;
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
new file mode 100644
index 00000000000..b2e64506472
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
@@ -0,0 +1,13 @@
+export default {
+ computed: {
+ isMergeButtonDisabled() {
+ const { commitMessage } = this;
+ return Boolean(
+ !commitMessage.length ||
+ !this.shouldShowMergeControls ||
+ this.isMakingRequest ||
+ this.mr.preventMerge,
+ );
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 57c4dfbe3b7..d02bb2f341d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import { __ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
@@ -12,6 +12,7 @@ import WidgetMergeHelp from './components/mr_widget_merge_help.vue';
import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue';
import Deployment from './components/deployment.vue';
import WidgetRelatedLinks from './components/mr_widget_related_links.vue';
+import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
import MergedState from './components/states/mr_widget_merged.vue';
import ClosedState from './components/states/mr_widget_closed.vue';
import MergingState from './components/states/mr_widget_merging.vue';
@@ -46,6 +47,7 @@ export default {
MrWidgetPipelineContainer,
Deployment,
'mr-widget-related-links': WidgetRelatedLinks,
+ MrWidgetAlertMessage,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
'mr-widget-merging': MergingState,
@@ -95,7 +97,7 @@ export default {
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
- return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
+ return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
},
shouldRenderSourceBranchRemovalStatus() {
return (
@@ -110,6 +112,24 @@ export default {
shouldRenderMergedPipeline() {
return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
},
+ showMergePipelineForkWarning() {
+ return Boolean(
+ this.mr.mergePipelinesEnabled && this.mr.sourceProjectId !== this.mr.targetProjectId,
+ );
+ },
+ showTargetBranchAdvancedError() {
+ return Boolean(
+ this.mr.isOpen &&
+ this.mr.pipeline &&
+ this.mr.pipeline.target_sha &&
+ this.mr.pipeline.target_sha !== this.mr.targetBranchSha,
+ );
+ },
+ mergeError() {
+ return sprintf(s__('mrWidget|Merge failed: %{mergeError}. Please try again.'), {
+ mergeError: this.mr.mergeError,
+ });
+ },
},
watch: {
state(newVal, oldVal) {
@@ -318,17 +338,49 @@ export default {
<div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
- <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
- {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
- </section>
+ <div class="mr-widget-info">
+ <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
+ <p>
+ {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
+ </p>
+ </section>
+
+ <mr-widget-related-links
+ v-if="shouldRenderRelatedLinks"
+ :state="mr.state"
+ :related-links="mr.relatedLinks"
+ />
+
+ <mr-widget-alert-message
+ v-if="showMergePipelineForkWarning"
+ type="warning"
+ :help-path="mr.mergeRequestPipelinesHelpPath"
+ >
+ {{
+ s__(
+ 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
+ )
+ }}
+ </mr-widget-alert-message>
+
+ <mr-widget-alert-message
+ v-if="showTargetBranchAdvancedError"
+ type="danger"
+ :help-path="mr.mergeRequestPipelinesHelpPath"
+ >
+ {{
+ s__(
+ 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
+ )
+ }}
+ </mr-widget-alert-message>
- <mr-widget-related-links
- v-if="shouldRenderRelatedLinks"
- :state="mr.state"
- :related-links="mr.relatedLinks"
- />
+ <mr-widget-alert-message v-if="mr.mergeError" type="danger">
+ {{ mergeError }}
+ </mr-widget-alert-message>
- <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
+ <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
+ </div>
</div>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
index 0cc4fd59f5e..3ab229567f6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
@@ -23,8 +23,8 @@ export default function deviseState(data) {
return stateKey.pipelineBlocked;
} else if (this.isSHAMismatch) {
return stateKey.shaMismatch;
- } else if (this.mergeWhenPipelineSucceeds) {
- return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
+ } else if (this.autoMergeEnabled) {
+ return this.mergeError ? stateKey.autoMergeFailed : stateKey.autoMergeEnabled;
} else if (!this.canMerge) {
return stateKey.notAllowedToMerge;
} else if (this.canBeMerged) {
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 58363f632a9..32badb0fb08 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
@@ -28,9 +28,11 @@ export default class MergeRequestStore {
this.iid = data.iid;
this.title = data.title;
this.targetBranch = data.target_branch;
+ this.targetBranchSha = data.target_branch_sha;
this.sourceBranch = data.source_branch;
this.sourceBranchProtected = data.source_branch_protected;
this.conflictsDocsPath = data.conflicts_docs_path;
+ this.mergeRequestPipelinesHelpPath = data.merge_request_pipelines_docs_path;
this.mergeStatus = data.merge_status;
this.commitMessage = data.default_merge_commit_message;
this.shortMergeCommitSha = data.short_merge_commit_sha;
@@ -59,7 +61,7 @@ export default class MergeRequestStore {
this.updatedAt = data.updated_at;
this.metrics = MergeRequestStore.buildMetrics(data.metrics);
- this.setToMWPSBy = MergeRequestStore.formatUserObject(data.merge_user || {});
+ this.setToAutoMergeBy = MergeRequestStore.formatUserObject(data.merge_user || {});
this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id;
this.sourceBranchPath = data.source_branch_path;
@@ -68,15 +70,16 @@ export default class MergeRequestStore {
this.targetBranchPath = data.target_branch_commits_path;
this.targetBranchTreePath = data.target_branch_tree_path;
this.conflictResolutionPath = data.conflict_resolution_path;
- this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
+ this.cancelAutoMergePath = data.cancel_auto_merge_path;
this.removeWIPPath = data.remove_wip_path;
this.sourceBranchRemoved = !data.source_branch_exists;
this.shouldRemoveSourceBranch = data.remove_source_branch || false;
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
- this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
+ this.autoMergeEnabled = Boolean(data.auto_merge_enabled);
+ this.autoMergeStrategy = data.auto_merge_strategy;
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
- this.shouldBeRebased = !!data.should_be_rebased;
+ this.shouldBeRebased = Boolean(data.should_be_rebased);
this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
@@ -89,9 +92,9 @@ export default class MergeRequestStore {
this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
- this.canMerge = !!data.merge_path;
+ this.canMerge = Boolean(data.merge_path);
this.canCreateIssue = currentUser.can_create_issue || false;
- this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
+ this.canCancelAutomaticMerge = Boolean(data.cancel_auto_merge_path);
this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false;
@@ -99,6 +102,9 @@ export default class MergeRequestStore {
this.allowCollaboration = data.allow_collaboration;
this.targetProjectFullPath = data.target_project_full_path;
this.sourceProjectFullPath = data.source_project_full_path;
+ this.sourceProjectId = data.source_project_id;
+ this.targetProjectId = data.target_project_id;
+ this.mergePipelinesEnabled = data.merge_pipelines_enabled;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
@@ -112,7 +118,7 @@ export default class MergeRequestStore {
this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
this.isPipelinePassing =
- this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
+ this.ciStatus === 'success' || this.ciStatus === 'success-with-warnings';
this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
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 e080ce5c229..48bc6a867f4 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
@@ -13,7 +13,7 @@ const stateToComponentMap = {
unresolvedDiscussions: 'mr-widget-unresolved-discussions',
pipelineBlocked: 'mr-widget-pipeline-blocked',
pipelineFailed: 'mr-widget-pipeline-failed',
- mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
+ autoMergeEnabled: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'sha-mismatch',
@@ -45,7 +45,7 @@ export const stateKey = {
pipelineBlocked: 'pipelineBlocked',
shaMismatch: 'shaMismatch',
autoMergeFailed: 'autoMergeFailed',
- mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds',
+ autoMergeEnabled: 'autoMergeEnabled',
notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge',
rebase: 'rebase',
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 2f498c4fa2a..25f80219993 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -21,6 +21,8 @@ import Icon from '../../vue_shared/components/icon.vue';
* - Jobs table
* - Jobs show view header
* - Jobs show view sidebar
+ * - Linked pipelines
+ * - Extended MR Popover
*/
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
diff --git a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
new file mode 100644
index 00000000000..eae4c06467c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
@@ -0,0 +1,32 @@
+<script>
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ href: {
+ type: String,
+ required: true,
+ },
+ pipelineId: {
+ type: Number,
+ required: true,
+ },
+ pipelineIid: {
+ type: Number,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <gl-link v-gl-tooltip :href="href" :title="__('Pipeline ID (IID)')">
+ <span class="pipeline-id">#{{ pipelineId }}</span>
+ <span class="pipeline-iid">(#{{ pipelineIid }})</span>
+ </gl-link>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 671b4909839..a620f560b52 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -7,7 +7,7 @@
*
* @example
* <clipboard-button
- * title="Copy to clipbard"
+ * title="Copy to clipboard"
* text="Content to be copied"
* css-class="btn-transparent"
* />
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ee685a4b8cd..3ba946e6447 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,5 +1,6 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import _ from 'underscore';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@@ -10,6 +11,7 @@ export default {
components: {
UserAvatarLink,
Icon,
+ GlLink,
},
props: {
/**
@@ -33,6 +35,27 @@ export default {
required: false,
default: () => ({}),
},
+
+ /**
+ * If provided, is used the render the MR IID and link
+ * in place of the branch name. Must contains the
+ * following properties:
+ * - iid (number)
+ * - path (non-empty string)
+ *
+ * May optionally contain the following properties:
+ * - title (string): used in a tooltip if provided
+ *
+ * Any additional properties are ignored.
+ */
+ mergeRequestRef: {
+ type: Object,
+ required: false,
+ default: undefined,
+ validator: ref =>
+ _.isUndefined(ref) || (_.isFinite(ref.iid) && _.isString(ref.path) && !_.isEmpty(ref.path)),
+ },
+
/**
* Used to link to the commit sha.
*/
@@ -70,7 +93,11 @@ export default {
required: false,
default: () => ({}),
},
- showBranch: {
+
+ /**
+ * Indicates whether or not to show the branch/MR ref info
+ */
+ showRefInfo: {
type: Boolean,
required: false,
default: true,
@@ -78,14 +105,12 @@ export default {
},
computed: {
/**
- * Used to verify if all the properties needed to render the commit
- * ref section were provided.
- *
- * @returns {Boolean}
+ * Determines if we shoud render the ref info section based
*/
- hasCommitRef() {
- return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
+ shouldShowRefInfo() {
+ return this.showRefInfo && (this.commitRef || this.mergeRequestRef);
},
+
/**
* Used to verify if all the properties needed to render the commit
* author section were provided.
@@ -108,19 +133,36 @@ export default {
};
</script>
<template>
- <div class="branch-commit">
- <template v-if="hasCommitRef && showBranch">
+ <div class="branch-commit cgray">
+ <template v-if="shouldShowRefInfo">
<div class="icon-container">
- <i v-if="tag" class="fa fa-tag" aria-hidden="true"> </i> <icon v-if="!tag" name="fork" />
+ <icon v-if="tag" name="tag" />
+ <icon v-else-if="mergeRequestRef" name="git-merge" />
+ <icon v-else name="branch" />
</div>
- <a v-gl-tooltip :href="commitRef.ref_url" :title="commitRef.name" class="ref-name">
+ <gl-link
+ v-if="mergeRequestRef"
+ v-gl-tooltip
+ :href="mergeRequestRef.path"
+ :title="mergeRequestRef.title"
+ class="ref-name"
+ >
+ {{ mergeRequestRef.iid }}
+ </gl-link>
+ <gl-link
+ v-else
+ v-gl-tooltip
+ :href="commitRef.ref_url"
+ :title="commitRef.name"
+ class="ref-name"
+ >
{{ commitRef.name }}
- </a>
+ </gl-link>
</template>
<icon name="commit" class="commit-icon js-commit-icon" />
- <a :href="commitUrl" class="commit-sha"> {{ shortSha }} </a>
+ <gl-link :href="commitUrl" class="commit-sha mr-0"> {{ shortSha }} </gl-link>
<div class="commit-title flex-truncate-parent">
<span v-if="title" class="flex-truncate-child">
@@ -132,7 +174,7 @@ export default {
:tooltip-text="author.username"
class="avatar-image-container"
/>
- <a :href="commitUrl" class="commit-row-message"> {{ title }} </a>
+ <gl-link :href="commitUrl" class="commit-row-message cgray"> {{ title }} </gl-link>
</span>
<span v-else> Can't find HEAD commit for this branch </span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
index 4155e1bab9c..1e6f4c376c1 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -1,5 +1,4 @@
<script>
-import { viewerInformationForPath } from './lib/viewer_utils';
import MarkdownViewer from './viewers/markdown_viewer.vue';
import ImageViewer from './viewers/image_viewer.vue';
import DownloadViewer from './viewers/download_viewer.vue';
@@ -24,15 +23,18 @@ export default {
required: false,
default: '',
},
+ type: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
viewer() {
if (!this.path) return null;
+ if (!this.type) return DownloadViewer;
- const previewInfo = viewerInformationForPath(this.path);
- if (!previewInfo) return DownloadViewer;
-
- switch (previewInfo.id) {
+ switch (this.type) {
case 'markdown':
return MarkdownViewer;
case 'image':
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
index f01a51da0b3..ba63683f5c0 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -1,10 +1,12 @@
+import { __ } from '~/locale';
+
const viewers = {
image: {
id: 'image',
},
markdown: {
id: 'markdown',
- previewTitle: 'Preview Markdown',
+ previewTitle: __('Preview Markdown'),
},
};
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index c9915f7d685..5fdc915fffb 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -78,8 +78,8 @@ export default {
</script>
<template>
- <div ref="markdown-preview" class="md md-previewer">
+ <div ref="markdown-preview" class="md-previewer">
<gl-skeleton-loading v-if="isLoading" />
- <div v-else v-html="previewContent"></div>
+ <div v-else class="md" v-html="previewContent"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue
index f085ef35ccc..2b5b2269ec8 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/onion_skin_viewer.vue
@@ -40,12 +40,15 @@ export default {
},
beforeDestroy() {
document.body.removeEventListener('mouseup', this.stopDrag);
- this.$refs.dragger.removeEventListener('mousedown', this.startDrag);
+ document.body.removeEventListener('touchend', this.stopDrag);
+ document.body.removeEventListener('mousemove', this.dragMove);
+ document.body.removeEventListener('touchmove', this.dragMove);
},
methods: {
dragMove(e) {
if (!this.dragging) return;
- const left = e.pageX - this.$refs.dragTrack.getBoundingClientRect().left;
+ const moveX = e.pageX || e.touches[0].pageX;
+ const left = moveX - this.$refs.dragTrack.getBoundingClientRect().left;
const dragTrackWidth =
this.$refs.dragTrack.clientWidth - this.$refs.dragger.clientWidth || 100;
@@ -60,11 +63,13 @@ export default {
this.dragging = true;
document.body.style.userSelect = 'none';
document.body.addEventListener('mousemove', this.dragMove);
+ document.body.addEventListener('touchmove', this.dragMove);
},
stopDrag() {
this.dragging = false;
document.body.style.userSelect = '';
document.body.removeEventListener('mousemove', this.dragMove);
+ document.body.removeEventListener('touchmove', this.dragMove);
},
prepareOnionSkin() {
if (this.onionOldImgInfo && this.onionNewImgInfo) {
@@ -82,6 +87,7 @@ export default {
this.$refs.dragTrack.clientWidth - this.$refs.dragger.clientWidth || 100;
document.body.addEventListener('mouseup', this.stopDrag);
+ document.body.addEventListener('touchend', this.stopDrag);
}
},
onionNewImgLoaded(imgInfo) {
@@ -102,7 +108,7 @@ export default {
:style="{
width: onionMaxPixelWidth,
height: onionMaxPixelHeight,
- 'user-select': dragging === true ? 'none' : '',
+ 'user-select': dragging ? 'none' : null,
}"
class="onion-skin-frame"
>
@@ -140,7 +146,14 @@ export default {
</div>
<div class="controls">
<div class="transparent"></div>
- <div ref="dragTrack" class="drag-track" @mousedown="startDrag" @mouseup="stopDrag">
+ <div
+ ref="dragTrack"
+ class="drag-track"
+ @mousedown="startDrag"
+ @mouseup="stopDrag"
+ @touchstart="startDrag"
+ @touchend="stopDrag"
+ >
<div ref="dragger" :style="{ left: onionDraggerPixelPos }" class="dragger"></div>
</div>
<div class="opaque"></div>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue
index 1c970b72a66..8d77b156aa4 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff/swipe_viewer.vue
@@ -46,6 +46,8 @@ export default {
window.removeEventListener('resize', this.resizeThrottled, false);
document.body.removeEventListener('mouseup', this.stopDrag);
document.body.removeEventListener('mousemove', this.dragMove);
+ document.body.removeEventListener('touchend', this.stopDrag);
+ document.body.removeEventListener('touchmove', this.dragMove);
},
mounted() {
window.addEventListener('resize', this.resize, false);
@@ -54,13 +56,13 @@ export default {
dragMove(e) {
if (!this.dragging) return;
- let leftValue = e.pageX - this.$refs.swipeFrame.getBoundingClientRect().left;
- const spaceLeft = 20;
+ const moveX = e.pageX || e.touches[0].pageX;
+ let leftValue = moveX - this.$refs.swipeFrame.getBoundingClientRect().left;
const { clientWidth } = this.$refs.swipeFrame;
if (leftValue <= 0) {
leftValue = 0;
- } else if (leftValue > clientWidth - spaceLeft) {
- leftValue = clientWidth - spaceLeft;
+ } else if (leftValue > clientWidth) {
+ leftValue = clientWidth;
}
this.swipeWrapWidth = (leftValue / clientWidth) * 100;
@@ -68,16 +70,16 @@ export default {
},
startDrag() {
this.dragging = true;
- document.body.style.userSelect = 'none';
document.body.addEventListener('mousemove', this.dragMove);
+ document.body.addEventListener('touchmove', this.dragMove);
},
stopDrag() {
this.dragging = false;
- document.body.style.userSelect = '';
document.body.removeEventListener('mousemove', this.dragMove);
+ document.body.removeEventListener('touchmove', this.dragMove);
},
prepareSwipe() {
- if (this.swipeOldImgInfo && this.swipeNewImgInfo) {
+ if (this.swipeOldImgInfo && this.swipeNewImgInfo && this.swipeOldImgInfo.renderedWidth > 0) {
// Add 2 for border width
this.swipeMaxWidth =
Math.max(this.swipeOldImgInfo.renderedWidth, this.swipeNewImgInfo.renderedWidth) + 2;
@@ -85,6 +87,7 @@ export default {
Math.max(this.swipeOldImgInfo.renderedHeight, this.swipeNewImgInfo.renderedHeight) + 2;
document.body.addEventListener('mouseup', this.stopDrag);
+ document.body.addEventListener('touchend', this.stopDrag);
}
},
swipeNewImgLoaded(imgInfo) {
@@ -97,6 +100,8 @@ export default {
},
resize: _.throttle(function throttledResize() {
this.swipeBarPos = 0;
+ this.swipeWrapWidth = 0;
+ this.prepareSwipe();
}, 400),
},
};
@@ -104,7 +109,15 @@ export default {
<template>
<div class="swipe view">
- <div ref="swipeFrame" class="swipe-frame">
+ <div
+ ref="swipeFrame"
+ :style="{
+ width: swipeMaxPixelWidth,
+ height: swipeMaxPixelHeight,
+ 'user-select': dragging ? 'none' : null,
+ }"
+ class="swipe-frame"
+ >
<image-viewer
key="swipeOldImg"
ref="swipeOldImg"
@@ -139,6 +152,8 @@ export default {
class="swipe-bar"
@mousedown="startDrag"
@mouseup="stopDrag"
+ @touchstart="startDrag"
+ @touchend="stopDrag"
>
<span class="top-handle"></span> <span class="bottom-handle"></span>
</span>
diff --git a/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
new file mode 100644
index 00000000000..7d49c87271d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from './icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ primaryButtonClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ dropdownClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actions: {
+ type: Array,
+ required: true,
+ },
+ defaultAction: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selectedAction: this.defaultAction,
+ };
+ },
+ computed: {
+ selectedActionTitle() {
+ return this.actions[this.selectedAction].title;
+ },
+ buttonSizeClass() {
+ return `btn-${this.size}`;
+ },
+ },
+ methods: {
+ handlePrimaryActionClick() {
+ this.$emit('onActionClick', this.actions[this.selectedAction]);
+ },
+ handleActionClick(selectedAction) {
+ this.selectedAction = selectedAction;
+ this.$emit('onActionSelect', selectedAction);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="btn-group droplab-dropdown comment-type-dropdown">
+ <gl-button :class="primaryButtonClass" :size="size" @click.prevent="handlePrimaryActionClick">
+ {{ selectedActionTitle }}
+ </gl-button>
+ <button
+ :class="buttonSizeClass"
+ type="button"
+ class="btn dropdown-toggle pl-2 pr-2"
+ data-display="static"
+ data-toggle="dropdown"
+ >
+ <icon name="arrow-down" aria-label="toggle dropdown" />
+ </button>
+ <ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
+ <template v-for="(action, index) in actions">
+ <li :key="index" :class="{ 'droplab-item-selected': selectedAction === index }">
+ <gl-button class="btn-transparent" @click.prevent="handleActionClick(index)">
+ <i aria-hidden="true" class="fa fa-check icon"> </i>
+ <div class="description">
+ <strong>{{ action.title }}</strong>
+ <p>{{ action.description }}</p>
+ </div>
+ </gl-button>
+ </li>
+ <li v-if="index === 0" :key="`${index}-separator`" class="divider droplab-item-ignore"></li>
+ </template>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/empty_component.js b/app/assets/javascripts/vue_shared/components/empty_component.js
new file mode 100644
index 00000000000..e4402020096
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/empty_component.js
@@ -0,0 +1,3 @@
+export default {
+ render: () => null,
+};
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 0cbcdbf2eb4..1bfa91500cb 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -39,7 +39,7 @@ export default {
},
data() {
return {
- mouseOver: false,
+ dropdownOpen: false,
};
},
computed: {
@@ -123,8 +123,8 @@ export default {
return this.$router.currentRoute.path === `/project${this.file.url}`;
},
- toggleHover(over) {
- this.mouseOver = over;
+ toggleDropdown(val) {
+ this.dropdownOpen = val;
},
},
};
@@ -140,8 +140,7 @@ export default {
class="file-row"
role="button"
@click="clickFile"
- @mouseover="toggleHover(true)"
- @mouseout="toggleHover(false)"
+ @mouseleave="toggleDropdown(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
@@ -160,7 +159,8 @@ export default {
:is="extraComponent"
v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="file"
- :mouse-over="mouseOver"
+ :dropdown-open="dropdownOpen"
+ @toggle="toggleDropdown($event)"
/>
</div>
</div>
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 3f45dc7853b..0bac63b1062 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -37,6 +37,16 @@ export default {
type: Number,
required: true,
},
+ itemIid: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ itemIdTooltip: {
+ type: String,
+ required: false,
+ default: '',
+ },
time: {
type: String,
required: true,
@@ -85,7 +95,12 @@ export default {
<section class="header-main-content">
<ci-icon-badge :status="status" />
- <strong> {{ itemName }} #{{ itemId }} </strong>
+ <strong v-gl-tooltip :title="itemIdTooltip">
+ {{ itemName }} #{{ itemId }}
+ <template v-if="itemIid"
+ >(#{{ itemIid }})</template
+ >
+ </strong>
<template v-if="shouldRenderTriggeredLabel">
triggered
@@ -96,9 +111,8 @@ export default {
<timeago-tooltip :time="time" />
- by
-
<template v-if="user">
+ by
<gl-link
v-gl-tooltip
:href="user.path"
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
index 7e79e63aa1e..715cf97f0ac 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -62,6 +62,15 @@ export default {
assigneeName: assignee.name,
});
},
+ // This method is for backward compat
+ // since Graph query would return camelCase
+ // props while Rails would return snake_case
+ webUrl(assignee) {
+ return assignee.web_url || assignee.webUrl;
+ },
+ avatarUrl(assignee) {
+ return assignee.avatar_url || assignee.avatarUrl;
+ },
},
};
</script>
@@ -70,9 +79,9 @@ export default {
<user-avatar-link
v-for="assignee in assigneesToShow"
:key="assignee.id"
- :link-href="assignee.web_url"
+ :link-href="webUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
- :img-src="assignee.avatar_url"
+ :img-src="avatarUrl(assignee)"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
index d5d967e25bf..9b2ee5062b1 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
@@ -17,15 +17,17 @@ export default {
required: true,
},
},
- data() {
- return {
- milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
- milestoneStart: this.milestone.start_date
- ? parsePikadayDate(this.milestone.start_date)
- : null,
- };
- },
computed: {
+ milestoneDue() {
+ const dueDate = this.milestone.due_date || this.milestone.dueDate;
+
+ return dueDate ? parsePikadayDate(dueDate) : null;
+ },
+ milestoneStart() {
+ const startDate = this.milestone.start_date || this.milestone.startDate;
+
+ return startDate ? parsePikadayDate(startDate) : null;
+ },
isMilestoneStarted() {
if (!this.milestoneStart) {
return false;
@@ -72,7 +74,7 @@ export default {
<template>
<div ref="milestoneDetails" class="issue-milestone-details">
<icon :size="16" class="inline icon" name="clock" />
- <span class="milestone-title">{{ milestone.title }}</span>
+ <span class="milestone-title d-inline-block">{{ milestone.title }}</span>
<gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
<span class="bold">{{ __('Milestone') }}</span> <br />
<span>{{ milestone.title }}</span> <br />
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index e92babc499b..e438ff16a41 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,9 +1,17 @@
<script>
+import { GlLink } from '@gitlab/ui';
+import _ from 'underscore';
+import { sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
+function buildDocsLinkStart(path) {
+ return `<a href="${_.escape(path)}" target="_blank" rel="noopener noreferrer">`;
+}
+
export default {
components: {
icon,
+ GlLink,
},
props: {
isLocked: {
@@ -16,6 +24,16 @@ export default {
default: false,
required: false,
},
+ lockedIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidentialIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
warningIcon() {
@@ -27,6 +45,17 @@ export default {
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
+ confidentialAndLockedDiscussionText() {
+ return sprintf(
+ 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ {
+ confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
+ lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
},
};
</script>
@@ -35,20 +64,26 @@ export default {
<icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
<span v-if="isLockedAndConfidential">
- {{ __('This issue is confidential and locked.') }}
+ <span v-html="confidentialAndLockedDiscussionText"></span>
{{
- __(`People without permission will never
-get a notification and won't be able to comment.`)
+ __(`People without permission will never get a notification and won't be able to comment.`)
}}
</span>
<span v-else-if="isConfidential">
{{ __('This is a confidential issue.') }}
- {{ __('Your comment will not be visible to the public.') }}
+ {{ __('People without permission will never get a notification.') }}
+ <gl-link :href="confidentialIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
<span v-else-if="isLocked">
- {{ __('This issue is locked.') }} {{ __('Only project members can comment.') }}
+ {{ __('This issue is locked.') }}
+ {{ __('Only project members can comment.') }}
+ <gl-link :href="lockedIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
new file mode 100644
index 00000000000..05ad7710a62
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -0,0 +1,141 @@
+<script>
+import '~/commons/bootstrap';
+import { GlTooltipDirective } from '@gitlab/ui';
+import { sprintf } from '~/locale';
+import IssueMilestone from '../../components/issue/issue_milestone.vue';
+import IssueAssignees from '../../components/issue/issue_assignees.vue';
+import relatedIssuableMixin from '../../mixins/related_issuable_mixin';
+import CiIcon from '../ci_icon.vue';
+
+export default {
+ name: 'IssueItem',
+ components: {
+ IssueMilestone,
+ IssueAssignees,
+ CiIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [relatedIssuableMixin],
+ props: {
+ canReorder: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ greyLinkWhenMerged: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ stateTitle() {
+ return sprintf(
+ '<span class="bold">%{state}</span> %{timeInWords}<br/><span class="text-tertiary">%{timestamp}</span>',
+ {
+ state: this.stateText,
+ timeInWords: this.stateTimeInWords,
+ timestamp: this.stateTimestamp,
+ },
+ );
+ },
+ issueableLinkClass() {
+ return this.greyLinkWhenMerged
+ ? `sortable-link ${this.state === 'merged' ? ' text-secondary' : ''}`
+ : 'sortable-link';
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ :class="{
+ 'issuable-info-container': !canReorder,
+ 'card-body': canReorder,
+ }"
+ class="item-body d-flex align-items-center p-2 p-lg-3 p-xl-2 pl-xl-3"
+ >
+ <div class="item-contents d-flex align-items-center flex-wrap flex-grow-1 flex-xl-nowrap">
+ <div class="item-title d-flex align-items-center mb-1 mb-xl-0">
+ <icon
+ v-if="hasState"
+ v-tooltip
+ :css-classes="iconClass"
+ :name="iconName"
+ :size="16"
+ :title="stateTitle"
+ :aria-label="state"
+ data-html="true"
+ />
+ <icon
+ v-if="confidential"
+ v-gl-tooltip
+ name="eye-slash"
+ :size="16"
+ :title="__('Confidential')"
+ class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0"
+ :aria-label="__('Confidential')"
+ />
+ <a :href="computedPath" :class="issueableLinkClass">{{ title }}</a>
+ </div>
+ <div class="item-meta d-flex flex-wrap mt-xl-0 justify-content-xl-end flex-xl-nowrap">
+ <div
+ class="d-flex align-items-center item-path-id order-md-0 mt-md-0 mt-1 ml-xl-2 mr-xl-auto"
+ >
+ <icon
+ v-if="hasState"
+ v-tooltip
+ :css-classes="iconClass"
+ :name="iconName"
+ :size="16"
+ :title="stateTitle"
+ :aria-label="state"
+ data-html="true"
+ class="d-xl-none"
+ />
+ <span v-tooltip :title="itemPath" class="path-id-text d-inline-block">{{
+ itemPath
+ }}</span>
+ {{ pathIdSeparator }}{{ itemId }}
+ </div>
+ <div
+ class="item-meta-child d-flex align-items-center order-0 flex-wrap mr-md-1 ml-md-auto ml-xl-2 flex-xl-nowrap"
+ >
+ <span v-if="hasPipeline" class="mr-ci-status pr-2">
+ <a :href="pipelineStatus.details_path">
+ <ci-icon v-gl-tooltip :status="pipelineStatus" :title="pipelineStatusTooltip" />
+ </a>
+ </span>
+ <issue-milestone
+ v-if="hasMilestone"
+ :milestone="milestone"
+ class="d-flex align-items-center item-milestone"
+ />
+ <slot name="dueDate"></slot>
+ <slot name="weight"></slot>
+ </div>
+ <issue-assignees
+ v-if="assignees.length"
+ :assignees="assignees"
+ class="item-assignees d-inline-flex align-items-center align-self-end ml-auto ml-md-0 mb-md-0 order-2 flex-xl-grow-0 mt-xl-0 mr-xl-1"
+ />
+ </div>
+ </div>
+ <button
+ v-if="canRemove"
+ ref="removeButton"
+ v-tooltip
+ :disabled="removeDisabled"
+ type="button"
+ class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button qa-remove-issue-button mr-xl-0 align-self-xl-center"
+ title="Remove"
+ aria-label="Remove"
+ @click="onRemoveRequest"
+ >
+ <icon :size="16" class="btn-item-remove-icon" name="close" />
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js
new file mode 100644
index 00000000000..d1aba99ac22
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js
@@ -0,0 +1,20 @@
+/* eslint-disable import/prefer-default-export */
+
+function trimFirstCharOfLineContent(text) {
+ if (!text) {
+ return text;
+ }
+
+ return text.replace(/^( |\+|-)/, '');
+}
+
+function cleanSuggestionLine(line = {}) {
+ return {
+ ...line,
+ text: trimFirstCharOfLineContent(line.text),
+ };
+}
+
+export function selectDiffLines(lines) {
+ return lines.filter(line => line.type !== 'match').map(line => cleanSuggestionLine(line));
+}
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 7a53d053eec..216f6c62e69 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -53,7 +53,7 @@ export default {
<template>
<button :class="containerClass" :disabled="loading || disabled" type="button" @click="onClick">
- <transition name="fade">
+ <transition name="fade-in">
<gl-loading-icon
v-if="loading"
:inline="true"
@@ -63,7 +63,7 @@ export default {
class="js-loading-button-icon"
/>
</transition>
- <transition name="fade">
+ <transition name="fade-in">
<slot>
<span v-if="label" class="js-loading-button-label"> {{ label }} </span>
</slot>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 3f607aa2a0a..0f3b3568414 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -76,6 +76,7 @@ export default {
hasSuggestion: false,
markdownPreviewLoading: false,
previewMarkdown: false,
+ suggestions: this.note.suggestions || [],
};
},
computed: {
@@ -109,9 +110,6 @@ export default {
}
return lineNumber;
},
- suggestions() {
- return this.note.suggestions || [];
- },
lineType() {
return this.line ? this.line.type : '';
},
@@ -175,6 +173,7 @@ export default {
this.referencedCommands = data.references.commands;
this.referencedUsers = data.references.users;
this.hasSuggestion = data.references.suggestions && data.references.suggestions.length;
+ this.suggestions = data.references.suggestions;
}
this.$nextTick()
@@ -189,7 +188,7 @@ export default {
<div
ref="gl-form"
:class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }"
- class="md-area js-vue-markdown-field"
+ class="js-vue-markdown-field md-area position-relative"
>
<markdown-header
:preview-markdown="previewMarkdown"
@@ -215,7 +214,7 @@ export default {
<div
v-show="previewMarkdown"
ref="markdown-preview"
- class="md-preview js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md-preview-holder"
>
<suggestions
v-if="hasSuggestion"
@@ -233,7 +232,7 @@ export default {
<div
v-show="previewMarkdown"
ref="markdown-preview"
- class="md-preview js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md md-preview-holder"
v-html="markdownPreview"
></div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index dbfa32cd0ce..a5a5b2ef415 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -38,7 +38,7 @@ export default {
].join('\n');
},
mdSuggestion() {
- return ['```suggestion', `{text}`, '```'].join('\n');
+ return ['```suggestion:-0+0', `{text}`, '```'].join('\n');
},
},
mounted() {
@@ -79,7 +79,7 @@ export default {
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
<button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
- Write
+ {{ __('Write') }}
</button>
</li>
<li :class="{ active: previewMarkdown }" class="md-header-tab">
@@ -89,36 +89,41 @@ export default {
type="button"
@click="previewMarkdownTab($event)"
>
- Preview
+ {{ __('Preview') }}
</button>
</li>
<li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
- <toolbar-button tag="**" button-title="Add bold text" icon="bold" />
- <toolbar-button tag="*" button-title="Add italic text" icon="italic" />
- <toolbar-button :prepend="true" tag="> " button-title="Insert a quote" icon="quote" />
- <toolbar-button tag="`" tag-block="```" button-title="Insert code" icon="code" />
+ <toolbar-button tag="**" :button-title="__('Add bold text')" icon="bold" />
+ <toolbar-button tag="*" :button-title="__('Add italic text')" icon="italic" />
+ <toolbar-button
+ :prepend="true"
+ tag="> "
+ :button-title="__('Insert a quote')"
+ icon="quote"
+ />
+ <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
<toolbar-button
tag="[{text}](url)"
tag-select="url"
- button-title="Add a link"
+ :button-title="__('Add a link')"
icon="link"
/>
<toolbar-button
:prepend="true"
tag="* "
- button-title="Add a bullet list"
+ :button-title="__('Add a bullet list')"
icon="list-bulleted"
/>
<toolbar-button
:prepend="true"
tag="1. "
- button-title="Add a numbered list"
+ :button-title="__('Add a numbered list')"
icon="list-numbered"
/>
<toolbar-button
:prepend="true"
tag="* [ ] "
- button-title="Add a task list"
+ :button-title="__('Add a task list')"
icon="task-done"
/>
<toolbar-button
@@ -139,11 +144,11 @@ export default {
/>
<button
v-gl-tooltip
- aria-label="Go full screen"
+ :aria-label="__('Go full screen')"
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
data-container="body"
tabindex="-1"
- title="Go full screen"
+ :title="__('Go full screen')"
type="button"
>
<icon name="screen-full" />
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index a351ca62c94..2eb4ec12a4a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -1,24 +1,14 @@
<script>
import SuggestionDiffHeader from './suggestion_diff_header.vue';
+import SuggestionDiffRow from './suggestion_diff_row.vue';
+import { selectDiffLines } from '../lib/utils/diff_utils';
export default {
components: {
SuggestionDiffHeader,
+ SuggestionDiffRow,
},
props: {
- newLines: {
- type: Array,
- required: true,
- },
- fromContent: {
- type: String,
- required: false,
- default: '',
- },
- fromLine: {
- type: Number,
- required: true,
- },
suggestion: {
type: Object,
required: true,
@@ -33,6 +23,11 @@ export default {
required: true,
},
},
+ computed: {
+ lines() {
+ return selectDiffLines(this.suggestion.diff_lines);
+ },
+ },
methods: {
applySuggestion(callback) {
this.$emit('apply', { suggestionId: this.suggestion.id, callback });
@@ -52,22 +47,11 @@ export default {
/>
<table class="mb-3 md-suggestion-diff js-syntax-highlight code">
<tbody>
- <!-- Old Line -->
- <tr class="line_holder old">
- <td class="diff-line-num old_line qa-old-diff-line-number old">{{ fromLine }}</td>
- <td class="diff-line-num new_line old"></td>
- <td class="line_content old">
- <span>{{ fromContent }}</span>
- </td>
- </tr>
- <!-- New Line(s) -->
- <tr v-for="(line, key) of newLines" :key="key" class="line_holder new">
- <td class="diff-line-num old_line new"></td>
- <td class="diff-line-num new_line qa-new-diff-line-number new">{{ line.lineNumber }}</td>
- <td class="line_content new">
- <span>{{ line.content }}</span>
- </td>
- </tr>
+ <suggestion-diff-row
+ v-for="(line, index) of lines"
+ :key="`${index}-${line.text}`"
+ :line="line"
+ />
</tbody>
</table>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index c5a2aa1f2af..32783b85df4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -1,8 +1,10 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
export default {
- components: { Icon },
+ components: { Icon, GlButton, GlLoadingIcon },
+ directives: { 'gl-tooltip': GlTooltipDirective },
props: {
canApply: {
type: Boolean,
@@ -21,7 +23,6 @@ export default {
},
data() {
return {
- isAppliedSuccessfully: false,
isApplying: false,
};
},
@@ -47,14 +48,19 @@ export default {
</a>
</div>
<span v-if="isApplied" class="badge badge-success">{{ __('Applied') }}</span>
- <button
- v-if="canApply"
- type="button"
- class="btn qa-apply-btn"
+ <div v-if="isApplying" class="d-flex align-items-center text-secondary">
+ <gl-loading-icon class="d-flex-center mr-2" />
+ <span>{{ __('Applying suggestion') }}</span>
+ </div>
+ <gl-button
+ v-else-if="canApply"
+ v-gl-tooltip.viewport="__('This also resolves the discussion')"
+ class="btn-inverted qa-apply-btn"
:disabled="isApplying"
+ variant="success"
@click="applySuggestion"
>
{{ __('Apply suggestion') }}
- </button>
+ </gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
new file mode 100644
index 00000000000..c09bdfec250
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_row.vue
@@ -0,0 +1,32 @@
+<script>
+export default {
+ name: 'SuggestionDiffRow',
+ props: {
+ line: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ lineType() {
+ return this.line.type;
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="line_holder" :class="lineType">
+ <td class="diff-line-num old_line border-top-0 border-bottom-0" :class="lineType">
+ {{ line.old_line }}
+ </td>
+ <td class="diff-line-num new_line border-top-0 border-bottom-0" :class="lineType">
+ {{ line.new_line }}
+ </td>
+ <td class="line_content" :class="lineType">
+ <span v-if="line.text">{{ line.text }}</span>
+ <!-- TODO: replace this hack with zero-width whitespace when we have rich_text from BE -->
+ <span v-else>&#8203;</span>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index dcda701f049..8d3705e1e4a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -6,16 +6,6 @@ import Flash from '~/flash';
export default {
components: { SuggestionDiff },
props: {
- fromLine: {
- type: Number,
- required: false,
- default: 0,
- },
- fromContent: {
- type: String,
- required: false,
- default: '',
- },
lineType: {
type: String,
required: false,
@@ -71,41 +61,19 @@ export default {
suggestionElements.forEach((suggestionEl, i) => {
const suggestionParentEl = suggestionEl.parentElement;
- const newLines = this.extractNewLines(suggestionParentEl);
- const diffComponent = this.generateDiff(newLines, i);
+ const diffComponent = this.generateDiff(i);
diffComponent.$mount(suggestionParentEl);
});
this.isRendered = true;
},
- extractNewLines(suggestionEl) {
- // extracts the suggested lines from the markdown
- // calculates a line number for each line
-
- const newLines = suggestionEl.querySelectorAll('.line');
- const fromLine = this.suggestions.length ? this.suggestions[0].from_line : this.fromLine;
- const lines = [];
-
- newLines.forEach((line, i) => {
- const content = `${line.innerText}\n`;
- const lineNumber = fromLine + i;
- lines.push({ content, lineNumber });
- });
-
- return lines;
- },
- generateDiff(newLines, suggestionIndex) {
- // generates the diff <suggestion-diff /> component
- // all `suggestion` markdown will be swapped out by this component
-
+ generateDiff(suggestionIndex) {
const { suggestions, disabled, helpPagePath } = this;
const suggestion =
suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {};
- const fromContent = suggestion.from_content || this.fromContent;
- const fromLine = suggestion.from_line || this.fromLine;
const SuggestionDiffComponent = Vue.extend(SuggestionDiff);
const suggestionDiff = new SuggestionDiffComponent({
- propsData: { newLines, fromLine, fromContent, disabled, suggestion, helpPagePath },
+ propsData: { disabled, suggestion, helpPagePath },
});
suggestionDiff.$on('apply', ({ suggestionId, callback }) => {
@@ -130,6 +98,6 @@ export default {
<template>
<div>
<div class="flash-container js-suggestions-flash"></div>
- <div v-show="isRendered" ref="container" v-html="noteHtml"></div>
+ <div v-show="isRendered" ref="container" class="md" v-html="noteHtml"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 3b57b5e8da4..d6c398c8946 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -33,37 +33,36 @@ export default {
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">
- Markdown is supported
- </gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"
+ >Markdown is supported</gl-link
+ >
</template>
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"> Markdown </gl-link>
- and
- <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">
- quick actions
- </gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">Markdown</gl-link> and
+ <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">quick actions</gl-link>
are supported
</template>
</div>
<span v-if="canAttachFile" class="uploading-container">
<span class="uploading-progress-container hide">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i>
- <span class="attaching-file-message"></span> <span class="uploading-progress">0%</span>
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
+ <span class="attaching-file-message"></span>
+ <span class="uploading-progress">0%</span>
<span class="uploading-spinner">
- <i class="fa fa-spinner fa-spin toolbar-button-icon" aria-hidden="true"> </i>
+ <i class="fa fa-spinner fa-spin toolbar-button-icon" aria-hidden="true"></i>
</span>
</span>
<span class="uploading-error-container hide">
<span class="uploading-error-icon">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i>
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
</span>
<span class="uploading-error-message"></span>
<button class="retry-uploading-link" type="button">Try again</button> or
<button class="attach-new-file markdown-selector" type="button">attach a new file</button>
</span>
- <button class="markdown-selector button-attach-file" tabindex="-1" type="button">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i> Attach a file
+ <button class="markdown-selector button-attach-file btn-link" tabindex="-1" type="button">
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i
+ ><span class="text-attach-file">Attach a file</span>
</button>
<button class="btn btn-default btn-sm hide button-cancel-uploading-files" type="button">
Cancel
diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
new file mode 100644
index 00000000000..bf59a6abf3f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
@@ -0,0 +1,121 @@
+<script>
+import $ from 'jquery';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import Clipboard from 'clipboard';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+
+ props: {
+ text: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ container: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ modalId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ target: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ tooltipContainer: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+
+ copySuccessText: __('Copied'),
+
+ computed: {
+ modalDomId() {
+ return this.modalId ? `#${this.modalId}` : '';
+ },
+ },
+
+ mounted() {
+ this.$nextTick(() => {
+ this.clipboard = new Clipboard(this.$el, {
+ container:
+ document.querySelector(`${this.modalDomId} div.modal-content`) ||
+ document.getElementById(this.container) ||
+ document.body,
+ });
+ this.clipboard
+ .on('success', e => {
+ this.updateTooltip(e.trigger);
+ this.$emit('success', e);
+ // Clear the selection and blur the trigger so it loses its border
+ e.clearSelection();
+ $(e.trigger).blur();
+ })
+ .on('error', e => this.$emit('error', e));
+ });
+ },
+
+ destroyed() {
+ if (this.clipboard) {
+ this.clipboard.destroy();
+ }
+ },
+
+ methods: {
+ updateTooltip(target) {
+ const $target = $(target);
+ const originalTitle = $target.data('originalTitle');
+
+ if ($target.tooltip) {
+ /**
+ * The original tooltip will continue staying there unless we remove it by hand.
+ * $target.tooltip('hide') isn't working.
+ */
+ $('.tooltip').remove();
+ $target.attr('title', this.$options.copySuccessText);
+ $target.tooltip('_fixTitle');
+ $target.tooltip('show');
+ $target.attr('title', originalTitle);
+ $target.tooltip('_fixTitle');
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-button
+ v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }"
+ :data-clipboard-target="target"
+ :data-clipboard-text="text"
+ :title="title"
+ >
+ <slot>
+ <icon name="duplicate" />
+ </slot>
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index 8d3a3009c55..baed26a157c 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -51,13 +51,13 @@ export default {
<div class="note-header">
<div class="note-header-info">
<a :href="getUserData.path">
- <span class="d-none d-sm-inline-block">{{ getUserData.name }}</span>
+ <span class="d-none d-sm-inline-block bold">{{ getUserData.name }}</span>
<span class="note-headline-light">@{{ getUserData.username }}</span>
</a>
</div>
</div>
<div class="note-body">
- <div class="note-text">
+ <div class="note-text md">
<p>{{ note.body }}</p>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index b0af8399955..3c86b7e4c61 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -22,6 +22,7 @@ import noteHeader from '~/notes/components/note_header.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from './timeline_entry_item.vue';
import { spriteIcon } from '../../../lib/utils/common_utils';
+import initMRPopovers from '~/mr_popover/';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
@@ -71,6 +72,9 @@ export default {
);
},
},
+ mounted() {
+ initMRPopovers(this.$el.querySelectorAll('.gfm-merge_request'));
+ },
};
</script>
@@ -93,7 +97,7 @@ export default {
'system-note-commit-list': hasMoreCommits,
'hide-shade': expanded,
}"
- class="note-text"
+ class="note-text md"
v-html="note.note_html"
></div>
<div v-if="hasMoreCommits" class="flex-list">
diff --git a/app/assets/javascripts/vue_shared/components/notes/timeline_entry_item.vue b/app/assets/javascripts/vue_shared/components/notes/timeline_entry_item.vue
index 06974a12aed..f316c4fe112 100644
--- a/app/assets/javascripts/vue_shared/components/notes/timeline_entry_item.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/timeline_entry_item.vue
@@ -1,9 +1,3 @@
-<script>
-export default {
- name: 'TimelineEntryItem',
-};
-</script>
-
<template>
<li class="timeline-entry">
<div class="timeline-entry-inner"><slot></slot></div>
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index fa502b9beb9..8104d919bf6 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -34,7 +34,7 @@ export default {
format: 'yyyy-mm-dd',
container: this.$el,
defaultDate: this.selectedDate,
- setDefaultDate: !!this.selectedDate,
+ setDefaultDate: Boolean(this.selectedDate),
minDate: this.minDate,
maxDate: this.maxDate,
parse: dateString => parsePikadayDate(dateString),
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
new file mode 100644
index 00000000000..071bae7f665
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
@@ -0,0 +1,74 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
+import highlight from '~/lib/utils/highlight';
+import { truncateNamespace } from '~/lib/utils/text_utility';
+import _ from 'underscore';
+
+export default {
+ name: 'ProjectListItem',
+ components: {
+ Icon,
+ ProjectAvatar,
+ GlButton,
+ },
+ props: {
+ project: {
+ type: Object,
+ required: true,
+ validator: p => _.isFinite(p.id) && _.isString(p.name) && _.isString(p.name_with_namespace),
+ },
+ selected: {
+ type: Boolean,
+ required: true,
+ },
+ matcher: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ truncatedNamespace() {
+ return truncateNamespace(this.project.name_with_namespace);
+ },
+ highlightedProjectName() {
+ return highlight(this.project.name, this.matcher);
+ },
+ },
+ methods: {
+ onClick() {
+ this.$emit('click');
+ },
+ },
+};
+</script>
+<template>
+ <gl-button
+ class="d-flex align-items-center btn pt-1 pb-1 border-0 project-list-item"
+ @click="onClick"
+ >
+ <icon
+ class="prepend-left-10 append-right-10 flex-shrink-0 position-top-0 js-selected-icon"
+ :class="{ 'js-selected visible': selected, 'js-unselected invisible': !selected }"
+ name="mobile-issue-close"
+ />
+ <project-avatar class="flex-shrink-0 js-project-avatar" :project="project" :size="32" />
+ <div class="d-flex flex-wrap project-namespace-name-container">
+ <div
+ v-if="truncatedNamespace"
+ :title="project.name_with_namespace"
+ class="text-secondary text-truncate js-project-namespace"
+ >
+ {{ truncatedNamespace }}
+ <span v-if="truncatedNamespace" class="text-secondary">/&nbsp;</span>
+ </div>
+ <div
+ :title="project.name"
+ class="js-project-name text-truncate"
+ v-html="highlightedProjectName"
+ ></div>
+ </div>
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
new file mode 100644
index 00000000000..596fd48f96a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
@@ -0,0 +1,103 @@
+<script>
+import _ from 'underscore';
+import { GlLoadingIcon } from '@gitlab/ui';
+import ProjectListItem from './project_list_item.vue';
+
+const SEARCH_INPUT_TIMEOUT_MS = 500;
+
+export default {
+ name: 'ProjectSelector',
+ components: {
+ GlLoadingIcon,
+ ProjectListItem,
+ },
+ props: {
+ projectSearchResults: {
+ type: Array,
+ required: true,
+ },
+ selectedProjects: {
+ type: Array,
+ required: true,
+ },
+ showNoResultsMessage: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ showMinimumSearchQueryMessage: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ showLoadingIndicator: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ showSearchErrorMessage: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ searchQuery: '',
+ };
+ },
+ methods: {
+ projectClicked(project) {
+ this.$emit('projectClicked', project);
+ },
+ isSelected(project) {
+ return Boolean(_.findWhere(this.selectedProjects, { id: project.id }));
+ },
+ focusSearchInput() {
+ this.$refs.searchInput.focus();
+ },
+ onInput: _.debounce(function debouncedOnInput() {
+ this.$emit('searched', this.searchQuery);
+ }, SEARCH_INPUT_TIMEOUT_MS),
+ },
+};
+</script>
+<template>
+ <div>
+ <input
+ ref="searchInput"
+ v-model="searchQuery"
+ :placeholder="__('Search your projects')"
+ type="search"
+ class="form-control mb-3 js-project-selector-input"
+ autofocus
+ @input="onInput"
+ />
+ <div class="d-flex flex-column">
+ <gl-loading-icon v-if="showLoadingIndicator" :size="2" class="py-2 px-4" />
+ <div v-if="!showLoadingIndicator" class="d-flex flex-column">
+ <project-list-item
+ v-for="project in projectSearchResults"
+ :key="project.id"
+ :selected="isSelected(project)"
+ :project="project"
+ :matcher="searchQuery"
+ class="js-project-list-item"
+ @click="projectClicked(project)"
+ />
+ </div>
+ <div v-if="showNoResultsMessage" class="text-muted ml-2 js-no-results-message">
+ {{ __('Sorry, no projects matched your search') }}
+ </div>
+ <div
+ v-if="showMinimumSearchQueryMessage"
+ class="text-muted ml-2 js-minimum-search-query-message"
+ >
+ {{ __('Enter at least three characters to search') }}
+ </div>
+ <div v-if="showSearchErrorMessage" class="text-danger ml-2 js-search-error-message">
+ {{ __('Something went wrong, unable to search projects') }}
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue b/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue
new file mode 100644
index 00000000000..1f3d248e991
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue
@@ -0,0 +1,40 @@
+<script>
+import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
+import $ from 'jquery';
+
+export default {
+ data() {
+ return {
+ width: 0,
+ height: 0,
+ };
+ },
+ beforeDestroy() {
+ this.contentResizeHandler.off('content.resize', this.debouncedResize);
+ window.removeEventListener('resize', this.debouncedResize);
+ },
+ created() {
+ this.debouncedResize = debounceByAnimationFrame(this.onResize);
+
+ // Handle when we explicictly trigger a custom resize event
+ this.contentResizeHandler = $(document).on('content.resize', this.debouncedResize);
+
+ // Handle window resize
+ window.addEventListener('resize', this.debouncedResize);
+ },
+ methods: {
+ onResize() {
+ // Slot dimensions
+ const { clientWidth, clientHeight } = this.$refs.chartWrapper;
+ this.width = clientWidth;
+ this.height = clientHeight;
+ },
+ },
+};
+</script>
+
+<template>
+ <div ref="chartWrapper">
+ <slot :width="width" :height="height"> </slot>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue
index 3074ea859cc..6d2612556ff 100644
--- a/app/assets/javascripts/vue_shared/components/select2_select.vue
+++ b/app/assets/javascripts/vue_shared/components/select2_select.vue
@@ -1,6 +1,6 @@
<script>
import $ from 'jquery';
-import 'select2/select2';
+import 'select2';
export default {
name: 'Select2Select',
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
index f66e81b1e08..9c258c4651f 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
@@ -75,6 +75,16 @@ export default {
required: false,
default: false,
},
+ enableScopedLabels: {
+ type: Boolean,
+ require: false,
+ default: false,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ require: false,
+ default: '#',
+ },
},
computed: {
hiddenInputName() {
@@ -123,7 +133,12 @@ export default {
@onValueClick="handleCollapsedValueClick"
/>
<dropdown-title :can-edit="canEdit" />
- <dropdown-value :labels="context.labels" :label-filter-base-path="labelFilterBasePath">
+ <dropdown-value
+ :labels="context.labels"
+ :label-filter-base-path="labelFilterBasePath"
+ :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
+ :enable-scoped-labels="enableScopedLabels"
+ >
<slot></slot>
</dropdown-value>
<div v-if="canEdit" class="selectbox js-selectbox" style="display: none;">
@@ -142,6 +157,8 @@ export default {
:namespace="namespace"
:labels="context.labels"
:show-extra-options="!showCreate"
+ :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
+ :enable-scoped-labels="enableScopedLabels"
/>
<div
class="dropdown-menu dropdown-select dropdown-menu-paging
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
index 498b507d11d..1eed8907bb7 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue
@@ -31,6 +31,16 @@ export default {
type: Boolean,
required: true,
},
+ enableScopedLabels: {
+ type: Boolean,
+ require: false,
+ default: false,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ require: false,
+ default: '#',
+ },
},
computed: {
dropdownToggleText() {
@@ -61,6 +71,8 @@ export default {
:data-labels="labelsPath"
:data-namespace-path="namespace"
:data-show-any="showExtraOptions"
+ :data-scoped-labels="enableScopedLabels"
+ :data-scoped-labels-documentation-link="scopedLabelsDocumentationLink"
type="button"
class="dropdown-menu-toggle wide js-label-select js-multiselect js-context-config-modal"
data-toggle="dropdown"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
index 6faf3fafad1..4abf7c478ee 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue
@@ -1,9 +1,12 @@
<script>
-import tooltip from '~/vue_shared/directives/tooltip';
+import DropdownValueScopedLabel from './dropdown_value_scoped_label.vue';
+import DropdownValueRegularLabel from './dropdown_value_regular_label.vue';
+import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
- directives: {
- tooltip,
+ components: {
+ DropdownValueScopedLabel,
+ DropdownValueRegularLabel,
},
props: {
labels: {
@@ -14,6 +17,16 @@ export default {
type: String,
required: true,
},
+ enableScopedLabels: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ required: false,
+ default: '#',
+ },
},
computed: {
isEmpty() {
@@ -30,6 +43,12 @@ export default {
backgroundColor: label.color,
};
},
+ scopedLabelsDescription({ description = '' }) {
+ return `<span class="font-weight-bold scoped-label-tooltip-title">Scoped label</span><br />${description}`;
+ },
+ showScopedLabels(label) {
+ return this.enableScopedLabels && isScopedLabel(label);
+ },
},
};
</script>
@@ -44,17 +63,24 @@ export default {
<span v-if="isEmpty" class="text-secondary">
<slot>{{ __('None') }}</slot>
</span>
- <a v-for="label in labels" v-else :key="label.id" :href="labelFilterUrl(label)">
- <span
- v-tooltip
- :style="labelStyle(label)"
- :title="label.description"
- class="badge color-label"
- data-placement="bottom"
- data-container="body"
- >
- {{ label.title }}
- </span>
- </a>
+
+ <template v-for="label in labels" v-else>
+ <dropdown-value-scoped-label
+ v-if="showScopedLabels(label)"
+ :key="label.id"
+ :label="label"
+ :label-filter-url="labelFilterUrl(label)"
+ :label-style="labelStyle(label)"
+ :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
+ />
+
+ <dropdown-value-regular-label
+ v-else
+ :key="label.id"
+ :label="label"
+ :label-filter-url="labelFilterUrl(label)"
+ :label-style="labelStyle(label)"
+ />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
index 373794fb1f2..05446903286 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -14,10 +14,12 @@ export default {
},
computed: {
labelsList() {
- const labelsString = this.labels
- .slice(0, 5)
- .map(label => label.title)
- .join(', ');
+ const labelsString = this.labels.length
+ ? this.labels
+ .slice(0, 5)
+ .map(label => label.title)
+ .join(', ')
+ : s__('LabelSelect|Labels');
if (this.labels.length > 5) {
return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_regular_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_regular_label.vue
new file mode 100644
index 00000000000..282b181f11e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_regular_label.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlLink, GlTooltip } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlTooltip,
+ GlLink,
+ },
+ props: {
+ label: {
+ type: Object,
+ required: true,
+ },
+ labelStyle: {
+ type: Object,
+ required: true,
+ },
+ labelFilterUrl: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <a ref="regularLabelRef" :href="labelFilterUrl">
+ <span :style="labelStyle" class="badge color-label">
+ {{ label.title }}
+ </span>
+ <gl-tooltip :target="() => $refs.regularLabelRef" placement="top" boundary="viewport">
+ {{ label.description }}
+ </gl-tooltip>
+ </a>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_scoped_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_scoped_label.vue
new file mode 100644
index 00000000000..ad5a86de166
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_scoped_label.vue
@@ -0,0 +1,47 @@
+<script>
+import { GlLink, GlTooltip } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlTooltip,
+ GlLink,
+ },
+ props: {
+ label: {
+ type: Object,
+ required: true,
+ },
+ labelStyle: {
+ type: Object,
+ required: true,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ required: true,
+ },
+ labelFilterUrl: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <span class="d-inline-block position-relative scoped-label-wrapper">
+ <a :href="labelFilterUrl">
+ <span :ref="`labelTitleRef`" :style="labelStyle" class="badge color-label label">
+ {{ label.title }}
+ </span>
+ <gl-tooltip :target="() => $refs.labelTitleRef" placement="top" boundary="viewport">
+ <span class="font-weight-bold scoped-label-tooltip-title">{{ __('Scoped label') }}</span
+ ><br />
+ {{ label.description }}
+ </gl-tooltip>
+ </a>
+
+ <gl-link :href="scopedLabelsDocumentationLink" target="_blank" class="label scoped-label"
+ ><i class="fa fa-question-circle" :style="labelStyle"></i
+ ></gl-link>
+ </span>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
index cca90af275e..5ce45d492f9 100644
--- a/app/assets/javascripts/vue_shared/components/svg_gradient.vue
+++ b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
@@ -4,10 +4,16 @@ export default {
colors: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
opacity: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
identifierName: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 2a34b4630f2..9cce9a4e542 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -54,15 +54,14 @@ export default {
return this.pageInfo.nextPage;
},
getItems() {
- const total = this.pageInfo.totalPages;
- const { page } = this.pageInfo;
+ const { totalPages, nextPage, previousPage, page } = this.pageInfo;
const items = [];
if (page > 1) {
items.push({ title: FIRST, first: true });
}
- if (page > 1) {
+ if (previousPage) {
items.push({ title: PREV, prev: true });
} else {
items.push({ title: PREV, disabled: true, prev: true });
@@ -70,32 +69,34 @@ export default {
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
- const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
- const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
+ if (totalPages) {
+ const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
+ const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, totalPages);
- for (let i = start; i <= end; i += 1) {
- const isActive = i === page;
- items.push({ title: i, active: isActive, page: true });
- }
+ for (let i = start; i <= end; i += 1) {
+ const isActive = i === page;
+ items.push({ title: i, active: isActive, page: true });
+ }
- if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
- items.push({ title: SPREAD, separator: true, page: true });
+ if (totalPages - page > PAGINATION_UI_BUTTON_LIMIT) {
+ items.push({ title: SPREAD, separator: true, page: true });
+ }
}
- if (page === total) {
- items.push({ title: NEXT, disabled: true, next: true });
- } else if (total - page >= 1) {
+ if (nextPage) {
items.push({ title: NEXT, next: true });
+ } else {
+ items.push({ title: NEXT, disabled: true, next: true });
}
- if (total - page >= 1) {
+ if (totalPages && totalPages - page >= 1) {
items.push({ title: LAST, last: true });
}
return items;
},
showPagination() {
- return this.pageInfo.totalPages > 1;
+ return this.pageInfo.nextPage || this.pageInfo.previousPage;
},
},
methods: {
@@ -120,7 +121,7 @@ export default {
this.change(1);
break;
default:
- this.change(+text);
+ this.change(Number(text));
break;
}
},
@@ -149,9 +150,9 @@ export default {
}"
class="page-item"
>
- <a class="page-link" @click.prevent="changePage(item.title, item.disabled)">
+ <button type="button" class="page-link" @click="changePage(item.title, item.disabled)">
{{ item.title }}
- </a>
+ </button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index f9773622001..a60d5eb491e 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -1,11 +1,13 @@
<script>
import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
export default {
name: 'UserPopover',
components: {
+ Icon,
GlPopover,
GlSkeletonLoading,
UserAvatarImage,
@@ -68,16 +70,31 @@ export default {
<gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
</div>
<div class="text-secondary">
- <div v-if="user.bio" class="js-bio">{{ user.bio }}</div>
- <div v-if="user.organization" class="js-organization">{{ user.organization }}</div>
+ <div v-if="user.bio" class="js-bio d-flex mb-1">
+ <icon name="profile" css-classes="category-icon flex-shrink-0" />
+ <span class="ml-1">{{ user.bio }}</span>
+ </div>
+ <div v-if="user.organization" class="js-organization d-flex mb-1">
+ <icon
+ v-show="!jobInfoIsLoading"
+ name="work"
+ css-classes="category-icon flex-shrink-0"
+ />
+ <span class="ml-1">{{ user.organization }}</span>
+ </div>
<gl-skeleton-loading
v-if="jobInfoIsLoading"
:lines="1"
class="animation-container-small mb-1"
/>
</div>
- <div class="text-secondary">
- {{ user.location }}
+ <div class="js-location text-secondary d-flex">
+ <icon
+ v-show="!locationIsLoading && user.location"
+ name="location"
+ css-classes="category-icon flex-shrink-0"
+ />
+ <span class="ml-1">{{ user.location }}</span>
<gl-skeleton-loading
v-if="locationIsLoading"
:lines="1"
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
index 549d27e96d9..2d1f7a1cfd0 100644
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import '~/commons/bootstrap';
export default {
bind(el) {
diff --git a/app/assets/javascripts/vue_shared/mixins/is_ee.js b/app/assets/javascripts/vue_shared/mixins/is_ee.js
new file mode 100644
index 00000000000..8e00d93ef18
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/is_ee.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import { isEE } from '~/lib/utils/common_utils';
+
+Vue.mixin({
+ computed: {
+ isEE() {
+ return isEE();
+ },
+ },
+});
diff --git a/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js b/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js
new file mode 100644
index 00000000000..8e0e4baa75a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js
@@ -0,0 +1,217 @@
+import _ from 'underscore';
+import { sprintf, __ } from '~/locale';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import tooltip from '~/vue_shared/directives/tooltip';
+import icon from '~/vue_shared/components/icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+const mixins = {
+ data() {
+ return {
+ removeDisabled: false,
+ };
+ },
+ props: {
+ idKey: {
+ type: Number,
+ required: true,
+ },
+ displayReference: {
+ type: String,
+ required: true,
+ },
+ pathIdSeparator: {
+ type: String,
+ required: true,
+ },
+ eventNamespace: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ path: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ state: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ createdAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ closedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ mergedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ milestone: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ dueDate: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ assignees: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ weight: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ canRemove: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isMergeRequest: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ pipelineStatus: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [timeagoMixin],
+ computed: {
+ hasState() {
+ return this.state && this.state.length > 0;
+ },
+ hasPipeline() {
+ return this.isMergeRequest && this.pipelineStatus && Object.keys(this.pipelineStatus).length;
+ },
+ isOpen() {
+ return this.state === 'opened';
+ },
+ isClosed() {
+ return this.state === 'closed';
+ },
+ isMerged() {
+ return this.state === 'merged';
+ },
+ hasTitle() {
+ return this.title.length > 0;
+ },
+ hasMilestone() {
+ return !_.isEmpty(this.milestone);
+ },
+ iconName() {
+ if (this.isMergeRequest && this.isMerged) {
+ return 'merge';
+ }
+
+ return this.isOpen ? 'issue-open-m' : 'issue-close';
+ },
+ iconClass() {
+ if (this.isMergeRequest && this.isClosed) {
+ return 'merge-request-status closed issue-token-state-icon-closed';
+ }
+
+ return this.isOpen ? 'issue-token-state-icon-open' : 'issue-token-state-icon-closed';
+ },
+ computedLinkElementType() {
+ return this.path.length > 0 ? 'a' : 'span';
+ },
+ computedPath() {
+ return this.path.length ? this.path : null;
+ },
+ itemPath() {
+ return this.displayReference.split(this.pathIdSeparator)[0];
+ },
+ itemId() {
+ return this.displayReference.split(this.pathIdSeparator).pop();
+ },
+ createdAtInWords() {
+ return this.createdAt ? this.timeFormated(this.createdAt) : '';
+ },
+ createdAtTimestamp() {
+ return this.createdAt ? formatDate(new Date(this.createdAt)) : '';
+ },
+ mergedAtTimestamp() {
+ return this.mergedAt ? formatDate(new Date(this.mergedAt)) : '';
+ },
+ mergedAtInWords() {
+ return this.mergedAt ? this.timeFormated(this.mergedAt) : '';
+ },
+ closedAtInWords() {
+ return this.closedAt ? this.timeFormated(this.closedAt) : '';
+ },
+ closedAtTimestamp() {
+ return this.closedAt ? formatDate(new Date(this.closedAt)) : '';
+ },
+ stateText() {
+ if (this.isMerged) {
+ return __('Merged');
+ }
+
+ return this.isOpen ? __('Opened') : __('Closed');
+ },
+ stateTimeInWords() {
+ if (this.isMerged) {
+ return this.mergedAtInWords;
+ }
+
+ return this.isOpen ? this.createdAtInWords : this.closedAtInWords;
+ },
+ stateTimestamp() {
+ if (this.isMerged) {
+ return this.mergedAtTimestamp;
+ }
+
+ return this.isOpen ? this.createdAtTimestamp : this.closedAtTimestamp;
+ },
+ pipelineStatusTooltip() {
+ return this.hasPipeline
+ ? sprintf(__('Pipeline: %{status}'), { status: this.pipelineStatus.label })
+ : '';
+ },
+ },
+ methods: {
+ onRemoveRequest() {
+ let namespacePrefix = '';
+ if (this.eventNamespace && this.eventNamespace.length > 0) {
+ namespacePrefix = `${this.eventNamespace}`;
+ }
+
+ this.$emit(`${namespacePrefix}RemoveRequest`, this.idKey);
+
+ this.removeDisabled = true;
+ },
+ },
+};
+
+export default mixins;
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
deleted file mode 100644
index 2d2732d0661..00000000000
--- a/app/assets/javascripts/vue_shared/models/label.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class ListLabel {
- constructor(obj) {
- this.id = obj.id;
- this.title = obj.title;
- this.type = obj.type;
- this.color = obj.color;
- this.textColor = obj.text_color;
- this.description = obj.description;
- this.priority = obj.priority !== null ? obj.priority : Infinity;
- }
-}
-
-window.ListLabel = ListLabel;
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 83ad8766cb5..a2f518cd24e 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,44 +2,36 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery.atwho
- *= require select2
*= require_self
*= require cropper.css
*/
-/*
- * Welcome to GitLab css!
- * If you need to add or modify UI component that is common for many pages
- * like a table or typography then make changes in the framework/ directory.
- * If you need to add unique style that should affect only one page - use pages/
- * directory.
- */
-
+// Welcome to GitLab css!
+// If you need to add or modify UI component that is common for many pages
+// like a table or typography then make changes in the framework/ directory.
+// If you need to add unique style that should affect only one page - use pages/
+// directory.
+@import "../../../node_modules/at.js/dist/css/jquery.atwho";
@import "../../../node_modules/pikaday/scss/pikaday";
@import "../../../node_modules/dropzone/dist/basic";
+@import "../../../node_modules/select2/select2";
-/*
- * GitLab UI framework
- */
+// GitLab UI framework
@import "framework";
-/*
- * Font icons
- */
+// Font icons
@import "font-awesome";
-/*
- * Page specific styles (issues, projects etc):
- */
+// Page specific styles (issues, projects etc):
@import "pages/**/*";
-/*
- * Component specific styles, will be moved to gitlab-ui
- */
+// Component specific styles, will be moved to gitlab-ui
@import "components/**/*";
-/*
- * Styles for JS behaviors.
- */
+// Vendors specific styles
+@import "vendors/**/*";
+
+// Styles for JS behaviors.
@import "behaviors";
+
+@import "utilities";
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c8357f7751c..7f6384f4eea 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -22,7 +22,9 @@ body,
.form-control,
.search form {
// Override default font size used in non-csslab UI
- font-size: 14px;
+ // Use rem to keep default font-size at 14px on body so 1rem still
+ // fits 8px grid, but also allow users to change browser font size
+ font-size: .875rem;
}
legend {
@@ -343,16 +345,6 @@ input[type=color].form-control {
}
}
-// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
-.input-group-btn:first-child {
- @extend .input-group-prepend;
-}
-
-// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
-.input-group-btn:last-child {
- @extend .input-group-append;
-}
-
/*
Bootstrap 4.1.2 introduced a new default vertical alignment which breaks our icons,
so we need to reset the vertical alignment to the default value. See:
diff --git a/app/assets/stylesheets/components/avatar.scss b/app/assets/stylesheets/components/avatar.scss
new file mode 100644
index 00000000000..1afa5ed90f4
--- /dev/null
+++ b/app/assets/stylesheets/components/avatar.scss
@@ -0,0 +1,202 @@
+$avatar-sizes: (
+ 16: (
+ font-size: 10px,
+ line-height: 16px,
+ border-radius: $border-radius-small
+ ),
+ 18: (
+ border-radius: $border-radius-small
+ ),
+ 20: (
+ border-radius: $border-radius-small
+ ),
+ 24: (
+ font-size: 12px,
+ line-height: 24px,
+ border-radius: $border-radius-default
+ ),
+ 26: (
+ font-size: 20px,
+ line-height: 1.33,
+ border-radius: $border-radius-default
+ ),
+ 32: (
+ font-size: 14px,
+ line-height: 32px,
+ border-radius: $border-radius-default
+ ),
+ 40: (
+ font-size: 16px,
+ line-height: 38px,
+ border-radius: $border-radius-default
+ ),
+ 48: (
+ font-size: 20px,
+ line-height: 48px,
+ border-radius: $border-radius-large
+ ),
+ 60: (
+ font-size: 32px,
+ line-height: 58px,
+ border-radius: $border-radius-large
+ ),
+ 64: (
+ font-size: 28px,
+ line-height: 64px,
+ border-radius: $border-radius-large
+ ),
+ 90: (
+ font-size: 36px,
+ line-height: 88px,
+ border-radius: $border-radius-large
+ ),
+ 100: (
+ font-size: 36px,
+ line-height: 98px,
+ border-radius: $border-radius-large
+ ),
+ 160: (
+ font-size: 96px,
+ line-height: 158px,
+ border-radius: $border-radius-large
+ )
+);
+
+$identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal,
+ $identicon-orange, $gray-darker;
+
+.avatar-circle {
+ float: left;
+ margin-right: $gl-padding;
+ border-radius: $avatar-radius;
+ border: 1px solid $gray-normal;
+
+ @each $size, $size-config in $avatar-sizes {
+ &.s#{$size} {
+ @include avatar-size(#{$size}px, if($size < 48, 8px, 16px));
+ }
+ }
+}
+
+.avatar {
+ @extend .avatar-circle;
+ transition-property: none;
+
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ background: $gray-lightest;
+ overflow: hidden;
+ border-color: rgba($black, $gl-avatar-border-opacity);
+
+ &.avatar-inline {
+ float: none;
+ display: inline-block;
+ margin-left: 2px;
+ flex-shrink: 0;
+
+ &.s16 {
+ margin-right: 4px;
+ }
+
+ &.s24 {
+ margin-right: 4px;
+ }
+ }
+
+ &.center {
+ font-size: 14px;
+ line-height: 1.8em;
+ text-align: center;
+ }
+
+ &.avatar-tile {
+ border-radius: 0;
+ border: 0;
+ }
+
+ &.avatar-placeholder {
+ border: 0;
+ }
+}
+
+.identicon {
+ text-align: center;
+ vertical-align: top;
+ color: $gray-800;
+ background-color: $gray-darker;
+
+ // Sizes
+ @each $size, $size-config in $avatar-sizes {
+ $keys: map-keys($size-config);
+
+ &.s#{$size} {
+ @each $key in $keys {
+ // We don't want `border-radius` to be included here.
+ @if ($key != 'border-radius') {
+ #{$key}: map-get($size-config, #{$key});
+ }
+ }
+ }
+ }
+
+ // Background colors
+ @for $i from 1 through length($identicon-backgrounds) {
+ &.bg#{$i} {
+ background-color: nth($identicon-backgrounds, $i);
+ }
+ }
+}
+
+.avatar-container {
+ @extend .avatar-circle;
+ overflow: hidden;
+ display: flex;
+
+ a {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ text-decoration: none;
+ }
+
+ .avatar {
+ border-radius: 0;
+ border: 0;
+ height: auto;
+ width: 100%;
+ margin: 0;
+ align-self: center;
+ }
+
+ &.s40 {
+ min-width: 40px;
+ min-height: 40px;
+ }
+
+ &.s64 {
+ min-width: 64px;
+ min-height: 64px;
+ }
+}
+
+.rect-avatar {
+ border-radius: $border-radius-small;
+
+ @each $size, $size-config in $avatar-sizes {
+ &.s#{$size} {
+ border-radius: map-get($size-config, 'border-radius');
+ }
+ }
+}
+
+.avatar-counter {
+ background-color: $gray-darkest;
+ color: $white-light;
+ border: 1px solid $gray-normal;
+ border-radius: 1em;
+ font-family: $regular-font;
+ font-size: 9px;
+ line-height: 16px;
+ text-align: center;
+}
diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/components/dashboard_skeleton.scss
new file mode 100644
index 00000000000..a104d035a9a
--- /dev/null
+++ b/app/assets/stylesheets/components/dashboard_skeleton.scss
@@ -0,0 +1,77 @@
+.dashboard-cards {
+ margin-right: -$gl-padding-8;
+ margin-left: -$gl-padding-8;
+}
+
+.dashboard-card {
+ &-header {
+ &-warning {
+ background-color: $orange-100;
+ }
+ }
+
+ &-body {
+ min-height: 120px;
+
+ &-warning {
+ background-color: $orange-50;
+ }
+
+ &-failed {
+ background-color: $red-50;
+ }
+ }
+
+ &-icon {
+ color: $gray-500;
+ }
+
+ &-footer {
+ border-radius: $gl-padding;
+ height: $gl-padding-32;
+
+ &-arrow {
+ color: $gray-300;
+ }
+
+ &-downstream {
+ margin-right: -$gl-padding-8;
+ }
+
+ &-extra {
+ background-color: $gray-400;
+ font-size: 10px;
+ line-height: $gl-line-height;
+ width: $gl-padding;
+ }
+ }
+
+ &-header,
+ &-footer {
+ &-failed {
+ background-color: $red-100;
+ }
+ }
+
+ &-skeleton-info {
+ border-radius: $gl-padding;
+ height: $gl-padding;
+ overflow: hidden;
+
+ &::after {
+ content: ' ';
+ display: block;
+ animation: blockTextShine 1s linear infinite forwards;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-image: linear-gradient(to right,
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%);
+ border-radius: $gl-padding;
+ height: $gl-padding;
+ margin-top: -$gl-padding-8;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 2f4d30fe923..774be9ef588 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -5,5 +5,68 @@
padding: $gl-padding-8;
font-size: $gl-font-size-small;
line-height: $gl-line-height;
+
+ .category-icon {
+ color: $gray-600;
+ }
+ }
+
+ &.blue {
+ background-color: $blue-600;
+
+ .popover-body {
+ color: $white-light;
+ }
+
+ &.bs-popover-bottom {
+ .arrow::after {
+ border-bottom-color: $blue-600;
+ }
+ }
+
+ &.bs-popover-top {
+ .arrow::after {
+ border-top-color: $blue-600;
+ }
+ }
+ }
+}
+
+.mr-popover {
+ .text-secondary {
+ font-size: 12px;
+ line-height: 1.33;
+ }
+}
+
+.onboarding-popover {
+ box-shadow: 0 2px 4px $dropdown-shadow-color;
+
+ .popover-body {
+ font-size: $gl-font-size;
+ line-height: $gl-line-height;
+ padding: $gl-padding;
+ }
+
+ .popover-header {
+ display: none;
+ }
+
+ .accept-mr-label {
+ background-color: $accepting-mr-label-color;
+ color: $white-light;
+ }
+}
+
+.onboarding-welcome-page {
+ .popover {
+ min-width: auto;
+ max-width: 40%;
+
+ .popover-body {
+ padding-top: $gl-padding;
+ padding-bottom: $gl-padding;
+ font-size: $gl-font-size-small;
+ }
}
}
diff --git a/app/assets/stylesheets/components/project_list_item.scss b/app/assets/stylesheets/components/project_list_item.scss
new file mode 100644
index 00000000000..8e7c2c4398c
--- /dev/null
+++ b/app/assets/stylesheets/components/project_list_item.scss
@@ -0,0 +1,24 @@
+.project-list-item {
+ &:not(:disabled):not(.disabled) {
+ &:focus,
+ &:active,
+ &:focus:active {
+ outline: none;
+ box-shadow: none;
+ }
+ }
+}
+
+// When housed inside a modal, the edge of each item
+// should extend to the edge of the modal.
+.modal-body {
+ .project-list-item {
+ border-radius: 0;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+
+ .project-namespace-name-container {
+ overflow: hidden;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
index 048a5c0300c..7f9cf1266b1 100644
--- a/app/assets/stylesheets/components/related_items_list.scss
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -11,18 +11,24 @@ $item-weight-max-width: 48px;
}
}
+.sortable-link {
+ max-width: 85%;
+}
+
.item-body {
- display: flex;
position: relative;
- align-items: center;
- padding: $gl-padding-8;
line-height: $gl-line-height;
- .item-contents {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- flex-grow: 1;
+ .issue-token-state-icon-open {
+ color: $green-500;
+ }
+
+ .issue-token-state-icon-closed {
+ color: $blue-500;
+ }
+
+ .merge-request-status.closed {
+ color: $red-500;
}
.issue-token-state-icon-open,
@@ -40,235 +46,185 @@ $item-weight-max-width: 48px;
}
.confidential-icon {
- align-self: baseline;
color: $orange-600;
- margin-right: $gl-padding-4;
}
.item-title {
flex-basis: 100%;
- margin-bottom: $gl-padding-8;
font-size: $gl-font-size-small;
&.mr-title {
font-weight: $gl-font-weight-bold;
}
- .sortable-link {
- max-width: 85%;
- }
-
.issue-token-state-icon-open,
.issue-token-state-icon-closed {
display: none;
}
}
- .item-meta {
- display: flex;
- flex-wrap: wrap;
- flex-basis: 100%;
- font-size: $gl-font-size-small;
+ .item-path-id .path-id-text,
+ .item-milestone .milestone-title,
+ .item-due-date,
+ .item-weight .board-card-info-text {
color: $gl-text-color-secondary;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+}
- .item-meta-child {
- order: 0;
- display: flex;
- flex-wrap: wrap;
- flex-basis: 100%;
-
- .item-due-date,
- .item-weight {
- margin-left: $gl-padding-8;
- }
+.item-meta {
+ flex-basis: 100%;
+ font-size: $gl-font-size-small;
+ color: $gl-text-color-secondary;
- .item-milestone,
- .item-weight {
- cursor: help;
- }
+ .item-meta-child {
+ flex-basis: 100%;
+ }
- .item-milestone {
- text-decoration: none;
- max-width: $item-milestone-max-width;
- }
+ .item-milestone,
+ .item-weight {
+ cursor: help;
+ }
- .item-due-date {
- margin-right: 0;
- }
+ .item-milestone {
+ text-decoration: none;
+ max-width: $item-milestone-max-width;
- .item-weight {
- margin-right: 0;
- max-width: $item-weight-max-width;
- }
+ .ic-clock {
+ color: $gl-text-color-tertiary;
+ margin-right: $gl-padding-4;
}
+ }
- .item-path-id .path-id-text,
- .item-milestone .milestone-title,
- .item-due-date,
- .item-weight .board-card-info-text {
- color: $gl-text-color-secondary;
- display: inline-block;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
+ .item-weight {
+ max-width: $item-weight-max-width;
+ }
- .item-path-id {
- margin-top: $gl-padding-4;
- font-size: $gl-font-size-xs;
- white-space: nowrap;
+ .item-assignees {
+ .user-avatar-link {
+ margin-right: -$gl-padding-4;
- .path-id-text {
- font-weight: $gl-font-weight-bold;
- max-width: $item-path-max-width;
+ &:nth-of-type(1) {
+ z-index: 2;
}
- .issue-token-state-icon-open,
- .issue-token-state-icon-closed {
- display: block;
+ &:nth-of-type(2) {
+ z-index: 1;
}
- &:not(.mr-item-path) {
- order: 1;
+ &:last-child {
+ margin-right: 0;
}
}
- .item-milestone .ic-clock {
- color: $gl-text-color-tertiary;
- margin-right: $gl-padding-4;
+ .avatar {
+ height: $gl-padding;
+ width: $gl-padding;
+ margin-right: 0;
+ vertical-align: bottom;
}
- .item-assignees {
- order: 2;
- align-self: flex-end;
- align-items: center;
- margin-left: auto;
-
- .user-avatar-link {
- margin-right: -$gl-padding-4;
-
- &:nth-of-type(1) {
- z-index: 2;
- }
+ .avatar-counter {
+ height: $gl-padding;
+ border: 1px solid transparent;
+ background-color: $gl-text-color-tertiary;
+ font-weight: $gl-font-weight-bold;
+ padding: 0 $gl-padding-4;
+ line-height: $gl-padding;
+ }
+ }
+}
- &:nth-of-type(2) {
- z-index: 1;
- }
+.item-path-id {
+ font-size: $gl-font-size-xs;
+ white-space: nowrap;
- &:last-child {
- margin-right: 0;
- }
- }
+ .path-id-text {
+ font-weight: $gl-font-weight-bold;
+ max-width: $item-path-max-width;
+ }
- .avatar {
- height: $gl-padding;
- width: $gl-padding;
- margin-right: 0;
- vertical-align: bottom;
- }
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ }
- .avatar-counter {
- height: $gl-padding;
- border: 1px solid transparent;
- background-color: $gl-text-color-tertiary;
- font-weight: $gl-font-weight-bold;
- padding: 0 $gl-padding-4;
- line-height: $gl-padding;
- }
+ @include media-breakpoint-down(sm) {
+ &:not(.mr-item-path) {
+ order: 1;
}
}
+}
- .btn-item-remove {
- position: absolute;
- right: 0;
- top: $gl-padding-4 / 2;
- padding: $gl-padding-4;
- margin-right: $gl-padding-4 / 2;
- line-height: 0;
- border-color: transparent;
- color: $gl-text-color-secondary;
+.btn-item-remove {
+ position: absolute;
+ right: 0;
+ top: $gl-padding-4 / 2;
+ padding: $gl-padding-4;
+ margin-right: $gl-padding-4 / 2;
+ line-height: 0;
+ border-color: transparent;
+ color: $gl-text-color-secondary;
- &:hover {
- color: $gl-text-color;
- }
+ &:hover {
+ color: $gl-text-color;
}
}
.mr-status-wrapper,
-.mr-ci-status
- {
+.mr-ci-status {
line-height: 0;
}
@include media-breakpoint-up(sm) {
- .item-body {
- .item-contents .item-title {
- .mr-title-link,
- .sortable-link {
- max-width: 90%;
- }
- }
+ .sortable-link {
+ max-width: 90%;
}
}
/* Small devices (landscape phones, 768px and up) */
@include media-breakpoint-up(md) {
+ .sortable-link {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 100%;
+ }
+
.item-body {
.item-contents {
min-width: 0;
+ }
- .item-title {
- flex-basis: unset;
- // 95% because we compensate
- // for remove button which is
- // positioned absolutely
- width: 95%;
- margin-bottom: $gl-padding-4;
-
- .mr-title-link,
- .sortable-link {
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- max-width: 100%;
- }
- }
-
- .item-meta {
- .item-path-id {
- order: 0;
- margin-top: 0;
- }
-
- .item-meta-child {
- flex-basis: unset;
- margin-left: auto;
- margin-right: $gl-padding-4;
-
- ~ .item-assignees {
- margin-left: $gl-padding-4;
- }
- }
-
- .item-assignees {
- margin-bottom: 0;
- margin-left: 0;
- order: 2;
- }
- }
+ .item-title {
+ flex-basis: unset;
+ // 95% because we compensate
+ // for remove button which is
+ // positioned absolutely
+ width: 95%;
}
.btn-item-remove {
order: 1;
}
}
+
+ .item-meta {
+ .item-meta-child {
+ flex-basis: unset;
+
+ ~ .item-assignees {
+ margin-left: $gl-padding-4;
+ }
+ }
+ }
}
/* Medium devices (desktops, 992px and up) */
@include media-breakpoint-up(lg) {
.item-body {
- padding: $gl-padding;
-
.item-title {
font-size: $gl-font-size;
}
@@ -276,106 +232,60 @@ $item-weight-max-width: 48px;
.item-meta .item-path-id {
font-size: inherit; // Base size given to `item-meta` is `$gl-font-size-small`
}
-
- .issue-token-state-icon-open,
- .issue-token-state-icon-closed {
- margin-right: $gl-padding-4;
- }
}
}
/* Large devices (large desktops, 1200px and up) */
@include media-breakpoint-up(xl) {
.item-body {
- padding: $gl-padding-8;
- padding-left: $gl-padding;
+ .item-title {
+ min-width: 0;
+ width: auto;
+ flex-basis: unset;
+ font-weight: $gl-font-weight-normal;
- .item-contents {
- flex-wrap: nowrap;
- overflow: hidden;
-
- .item-title {
- display: flex;
- margin-bottom: 0;
- min-width: 0;
- width: auto;
- flex-basis: unset;
- font-weight: $gl-font-weight-normal;
-
- .mr-title-link,
- .sortable-link {
- display: block;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- }
-
- .issue-token-state-icon-open,
- .issue-token-state-icon-closed {
- display: block;
- margin-right: $gl-padding-8;
- }
-
- .confidential-icon {
- align-self: auto;
- margin-top: 0;
- }
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ margin-right: $gl-padding-8;
}
+ }
+ }
- .item-meta {
- margin-top: 0;
- justify-content: flex-end;
- flex: 1;
- flex-wrap: nowrap;
-
- .item-path-id {
- order: 0;
- margin-top: 0;
- margin-left: $gl-padding-8;
- margin-right: auto;
-
- .issue-token-state-icon-open,
- .issue-token-state-icon-closed {
- display: none;
- }
- }
-
- .item-meta-child {
- margin-left: $gl-padding-8;
- flex-wrap: nowrap;
- }
-
- .item-assignees {
- flex-grow: 0;
- margin-top: 0;
- margin-right: $gl-padding-4;
-
- .avatar {
- height: $gl-padding-24;
- width: $gl-padding-24;
- }
-
- .avatar-counter {
- height: $gl-padding-24;
- min-width: $gl-padding-24;
- line-height: $gl-padding-24;
- border-radius: $gl-padding-24;
- }
- }
- }
+ .item-contents {
+ overflow: hidden;
+ }
+
+ .item-meta {
+ flex: 1;
+ }
+
+ .item-assignees {
+ .avatar {
+ height: $gl-padding-24;
+ width: $gl-padding-24;
}
- .btn-item-remove {
- position: relative;
- align-self: center;
- top: initial;
- right: 0;
- margin-right: 0;
- padding: $btn-sm-side-margin;
+ .avatar-counter {
+ height: $gl-padding-24;
+ min-width: $gl-padding-24;
+ line-height: $gl-padding-24;
+ border-radius: $gl-padding-24;
+ }
+ }
- &:hover {
- border-color: $border-color;
- }
+ .btn-item-remove {
+ position: relative;
+ top: initial;
+ right: 0;
+ padding: $btn-sm-side-margin;
+
+ &:hover {
+ border-color: $border-color;
}
}
+
+ .sortable-link {
+ line-height: 1.3;
+ }
}
diff --git a/app/assets/stylesheets/components/toast.scss b/app/assets/stylesheets/components/toast.scss
new file mode 100644
index 00000000000..33e1c4e5349
--- /dev/null
+++ b/app/assets/stylesheets/components/toast.scss
@@ -0,0 +1,53 @@
+/*
+* These styles are specific to the gl-toast component.
+* Documentation: https://design.gitlab.com/components/toasts
+* Note: Styles below are nested in order to override some of vue-toasted's default styling
+*/
+.toasted-container {
+
+ max-width: $toast-max-width;
+
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+ padding-right: $toast-padding-right;
+ }
+
+ .toasted.gl-toast {
+ border-radius: $border-radius-default;
+ font-size: $gl-font-size;
+ padding: $gl-padding-8 $gl-padding-24;
+ margin-top: $toast-default-margin;
+ line-height: $gl-line-height;
+ background-color: rgba($gray-900, $toast-background-opacity);
+
+ @include media-breakpoint-down(xs) {
+ .action:first-child {
+ // Ensures actions buttons are right aligned on mobile
+ margin-left: auto;
+ }
+ }
+
+ .action {
+ color: $blue-300;
+ margin: 0 0 0 $toast-action-margin-left;
+ text-transform: none;
+ font-size: $gl-font-size;
+
+ &:first-child {
+ padding-right: 0;
+ }
+ }
+
+ .toast-close {
+ font-size: $default-icon-size;
+ margin-left: $toast-default-margin;
+ padding-left: $gl-padding;
+ }
+ }
+}
+
+// Overrides the default positioning of toasts
+body .toasted-container.bottom-left {
+ bottom: $toast-offset;
+ left: $toast-offset;
+}
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index 658e0ff638e..8c32b6c8985 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -17,7 +17,7 @@ body {
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
- font-size: 14px;
+ font-size: .875rem;
}
h1 {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 555ea276c6c..9b0d19b0ef0 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -8,7 +8,6 @@
@import 'framework/animations';
@import 'framework/vue_transitions';
-@import 'framework/avatar';
@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
@@ -60,9 +59,11 @@
@import 'framework/memory_graph';
@import 'framework/responsive_tables';
@import 'framework/stacked_progress_bar';
+@import 'framework/sortable';
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
@import 'framework/read_more';
@import 'framework/flex_grid';
@import 'framework/system_messages';
+@import "framework/spinner";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 70d50c74ca9..6f5a2e561af 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -27,7 +27,7 @@
&.flipOutY,
&.bounceIn,
&.bounceOut {
- @include webkit-prefix(animation-duration, .75s);
+ @include webkit-prefix(animation-duration, 0.75s);
}
&.short {
@@ -73,22 +73,10 @@
@mixin disable-all-animation {
/*CSS transitions*/
- -o-transition-property: none !important;
- -moz-transition-property: none !important;
- -ms-transition-property: none !important;
- -webkit-transition-property: none !important;
transition-property: none !important;
/*CSS transforms*/
- -o-transform: none !important;
- -moz-transform: none !important;
- -ms-transform: none !important;
- -webkit-transform: none !important;
transform: none !important;
/*CSS animations*/
- -webkit-animation: none !important;
- -moz-animation: none !important;
- -o-animation: none !important;
- -ms-animation: none !important;
animation: none !important;
}
@@ -202,7 +190,7 @@ a {
}
}
- [class^="skeleton-line-"] {
+ [class^='skeleton-line-'] {
position: relative;
background-color: $gray-100;
height: 10px;
@@ -218,13 +206,11 @@ a {
animation: blockTextShine 1s linear infinite forwards;
background-repeat: no-repeat;
background-size: cover;
- background-image: linear-gradient(
- to right,
- $gray-100 0%,
- $gray-50 20%,
- $gray-100 40%,
- $gray-100 100%
- );
+ background-image: linear-gradient(to right,
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%);
height: 10px;
}
}
@@ -282,3 +268,27 @@ $skeleton-line-widths: (
@include webkit-prefix(animation-duration, 1s);
transform-origin: 50% 50%;
}
+
+/* ----------------------------------------------
+ * Generated by Animista on 2019-4-26 17:40:41
+ * w: http://animista.net, t: @cssanimista
+ * ---------------------------------------------- */
+@keyframes slide-in-fwd-bottom {
+ 0% {
+ transform: translateZ(-1400px) translateY(800px);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: translateZ(0) translateY(0);
+ opacity: 1;
+ }
+}
+
+.slide-in-fwd-bottom-enter-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
+}
+
+.slide-in-fwd-bottom-leave-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both reverse;
+}
diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss
index 62493c32833..1586265d40e 100644
--- a/app/assets/stylesheets/framework/asciidoctor.scss
+++ b/app/assets/stylesheets/framework/asciidoctor.scss
@@ -1,7 +1,7 @@
.admonitionblock td.icon {
width: 1%;
- [class^="fa icon-"] {
+ [class^='fa icon-'] {
@extend .fa-2x;
}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
deleted file mode 100644
index bfd3d776bd4..00000000000
--- a/app/assets/stylesheets/framework/avatar.scss
+++ /dev/null
@@ -1,160 +0,0 @@
-@mixin avatar-size($size, $margin-right) {
- width: $size;
- height: $size;
- margin-right: $margin-right;
-}
-
-.avatar-circle {
- float: left;
- margin-right: 15px;
- border-radius: $avatar-radius;
- border: 1px solid $gray-normal;
- &.s16 { @include avatar-size(16px, 6px); }
- &.s18 { @include avatar-size(18px, 6px); }
- &.s19 { @include avatar-size(19px, 6px); }
- &.s20 { @include avatar-size(20px, 7px); }
- &.s24 { @include avatar-size(24px, 8px); }
- &.s26 { @include avatar-size(26px, 8px); }
- &.s32 { @include avatar-size(32px, 10px); }
- &.s36 { @include avatar-size(36px, 10px); }
- &.s40 { @include avatar-size(40px, 10px); }
- &.s46 { @include avatar-size(46px, 15px); }
- &.s48 { @include avatar-size(48px, 10px); }
- &.s60 { @include avatar-size(60px, 12px); }
- &.s64 { @include avatar-size(64px, 14px); }
- &.s70 { @include avatar-size(70px, 14px); }
- &.s90 { @include avatar-size(90px, 15px); }
- &.s100 { @include avatar-size(100px, 15px); }
- &.s110 { @include avatar-size(110px, 15px); }
- &.s140 { @include avatar-size(140px, 15px); }
- &.s160 { @include avatar-size(160px, 20px); }
-}
-
-.avatar {
- @extend .avatar-circle;
- transition-property: none;
-
- width: 40px;
- height: 40px;
- padding: 0;
- background: $gray-lightest;
- overflow: hidden;
-
- &.avatar-inline {
- float: none;
- display: inline-block;
- margin-left: 2px;
- flex-shrink: 0;
- -webkit-flex-shrink: 0;
-
- &.s16 { margin-right: 4px; }
- &.s24 { margin-right: 4px; }
- }
-
- &.center {
- font-size: 14px;
- line-height: 1.8em;
- text-align: center;
- }
-
- &.avatar-tile {
- border-radius: 0;
- border: 0;
- }
-
- &:not([href]):hover {
- border-color: darken($gray-normal, 10%);
- }
-}
-
-.identicon {
- text-align: center;
- vertical-align: top;
- color: $gl-gray-700;
- background-color: $gray-darker;
-
- // Sizes
- &.s16 { font-size: 12px; line-height: 1.33; }
- &.s24 { font-size: 13px; line-height: 1.8; }
- &.s26 { font-size: 20px; line-height: 1.33; }
- &.s32 { font-size: 20px; line-height: 30px; }
- &.s40 { font-size: 16px; line-height: 38px; }
- &.s48 { font-size: 20px; line-height: 46px; }
- &.s60 { font-size: 32px; line-height: 58px; }
- &.s64 { font-size: 32px; line-height: 64px; }
- &.s70 { font-size: 34px; line-height: 70px; }
- &.s90 { font-size: 36px; line-height: 88px; }
- &.s100 { font-size: 36px; line-height: 98px; }
- &.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; }
- &.s140 { font-size: 72px; line-height: 138px; }
- &.s160 { font-size: 96px; line-height: 158px; }
-
- // Background colors
- &.bg1 { background-color: $identicon-red; }
- &.bg2 { background-color: $identicon-purple; }
- &.bg3 { background-color: $identicon-indigo; }
- &.bg4 { background-color: $identicon-blue; }
- &.bg5 { background-color: $identicon-teal; }
- &.bg6 { background-color: $identicon-orange; }
- &.bg7 { background-color: $gray-darker; }
-}
-
-.avatar-container {
- @extend .avatar-circle;
- overflow: hidden;
- display: flex;
-
- a {
- width: 100%;
- height: 100%;
- display: flex;
- text-decoration: none;
- }
-
- .avatar {
- border-radius: 0;
- border: 0;
- height: auto;
- width: 100%;
- margin: 0;
- align-self: center;
- }
-
- &.s40 { min-width: 40px; min-height: 40px; }
- &.s64 { min-width: 64px; min-height: 64px; }
-}
-
-.rect-avatar {
- border-radius: $border-radius-small;
- &.s16 { border-radius: $border-radius-small; }
- &.s18 { border-radius: $border-radius-small; }
- &.s19 { border-radius: $border-radius-small; }
- &.s20 { border-radius: $border-radius-small; }
- &.s24 { border-radius: $border-radius-default; }
- &.s26 { border-radius: $border-radius-default; }
- &.s32 { border-radius: $border-radius-default; }
- &.s36 { border-radius: $border-radius-default; }
- &.s40 { border-radius: $border-radius-default; }
- &.s46 { border-radius: $border-radius-default; }
- &.s48 { border-radius: $border-radius-large; }
- &.s60 { border-radius: $border-radius-large; }
- &.s64 { border-radius: $border-radius-large; }
- &.s70 { border-radius: $border-radius-large; }
- &.s90 { border-radius: $border-radius-large; }
- &.s96 { border-radius: $border-radius-large; }
- &.s100 { border-radius: $border-radius-large; }
- &.s110 { border-radius: $border-radius-large; }
- &.s140 { border-radius: $border-radius-large; }
- &.s160 { border-radius: $border-radius-large; }
-}
-
-.avatar-counter {
- background-color: $gray-darkest;
- color: $white-light;
- border: 1px solid $gray-normal;
- border-radius: 1em;
- font-family: $regular-font;
- font-size: 9px;
- line-height: 16px;
- text-align: center;
-}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 5cfd5bbd4f5..7760c48cb92 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -23,9 +23,9 @@
box-shadow: 0 6px 12px $award-emoji-menu-shadow;
pointer-events: none;
opacity: 0;
- transform: scale(.2);
+ transform: scale(0.2);
transform-origin: 0 -45px;
- transition: .3s cubic-bezier(.67, .06, .19, 1.44);
+ transition: 0.3s cubic-bezier(0.67, 0.06, 0.19, 1.44);
transition-property: transform, opacity;
&.is-rendered {
@@ -62,7 +62,7 @@
}
.emoji-search {
- background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
@@ -90,7 +90,7 @@
background: none;
border: 0;
border-radius: $border-radius-base;
- transition: transform .15s cubic-bezier(.3, 0, .2, 2);
+ transition: transform 0.15s cubic-bezier(0.3, 0, 0.2, 2);
&:hover {
background-color: transparent;
@@ -151,8 +151,7 @@
outline: 0;
.award-control-icon svg {
- background: $award-emoji-positive-add-bg;
- fill: $award-emoji-positive-add-lines;
+ fill: $blue-500;
}
.award-control-icon-neutral {
@@ -233,10 +232,7 @@
height: $default-icon-size;
width: $default-icon-size;
border-radius: 50%;
- }
-
- path {
- fill: $border-gray-normal;
+ fill: $gray-700;
}
}
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index 91dbb2a6365..cbd390e7145 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -69,6 +69,7 @@
@include media-breakpoint-up(sm) {
display: flex;
+ height: 100%;
align-items: center;
padding: 50px 30px;
}
@@ -99,3 +100,30 @@
}
}
}
+
+@include media-breakpoint-up(lg) {
+ .column-large {
+ flex: 2;
+ }
+
+ .column-small {
+ flex: 1;
+ margin-bottom: 15px;
+
+ .blank-state {
+ max-width: 400px;
+ flex-wrap: wrap;
+ margin-left: 15px;
+ }
+
+ .blank-state-icon {
+ margin-bottom: 30px;
+ }
+ }
+}
+
+@include media-breakpoint-down(xs) {
+ .blank-state-icon svg {
+ width: 315px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 43b7c26b272..65c0ee74c60 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -22,6 +22,10 @@
}
}
+.oneline {
+ line-height: 35px;
+}
+
.row-content-block {
margin-top: 0;
background-color: $gray-light;
@@ -77,20 +81,13 @@
color: $gl-text-color;
}
- .oneline {
- line-height: 35px;
- }
-
> p:last-child {
margin-bottom: 0;
}
.block-controls {
- display: -webkit-flex;
display: flex;
- -webkit-justify-content: flex-end;
justify-content: flex-end;
- -webkit-flex: 1;
flex: 1;
.control {
@@ -111,10 +108,6 @@
padding: 11px 0;
margin-bottom: 11px;
- .oneline {
- line-height: 35px;
- }
-
&.no-bottom-space {
border-bottom: 0;
margin-bottom: 0;
@@ -153,7 +146,7 @@
display: inline-block;
margin-left: 5px;
font-size: 18px;
- color: color("gray");
+ color: color('gray');
}
p {
@@ -163,8 +156,6 @@
}
.cover-desc {
- color: $gl-text-color;
-
&.username:last-child {
padding-bottom: $gl-padding;
}
@@ -208,6 +199,7 @@
&.user-cover-block {
padding: 24px 0 0;
+ border-bottom: 1px solid $border-color;
.nav-links {
width: 100%;
@@ -228,7 +220,6 @@
}
.group-info {
-
h1 {
display: inline;
font-weight: $gl-font-weight-normal;
@@ -242,14 +233,6 @@
margin-top: -1px;
}
-.nav-block {
- .controls {
- float: right;
- margin-top: 8px;
- padding-bottom: 8px;
- }
-}
-
.content-block {
padding: $gl-padding 0;
border-bottom: 1px solid $white-dark;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index cb2c8879c5f..767832e242c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,12 +1,12 @@
@mixin btn-comment-icon {
border-radius: 50%;
background: $white-light;
- padding: 1px 5px;
+ padding: 1px;
font-size: 12px;
color: $blue-500;
+ border: 1px solid $blue-500;
width: 24px;
height: 24px;
- border: 1px solid $blue-500;
&:hover,
&.inverted {
@@ -21,7 +21,7 @@
}
@mixin btn-default {
- border-radius: 3px;
+ border-radius: $border-radius-default;
font-size: $gl-font-size;
font-weight: $gl-font-weight-normal;
padding: $gl-vert-padding $gl-btn-padding;
@@ -37,7 +37,7 @@
@include btn-default;
}
-@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) {
+@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border, $active-text) {
background-color: $background;
color: $text;
border-color: $border;
@@ -61,13 +61,22 @@
}
}
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
+
&:active {
background-color: $active-background;
border-color: $active-border;
- color: $hover-text;
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
+ color: $active-text;
> .icon {
- color: $hover-text;
+ color: $active-text;
+ }
+
+ &:focus {
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
}
}
}
@@ -139,6 +148,7 @@
@include btn-white;
color: $gl-text-color;
+ white-space: nowrap;
&:focus:active {
outline: 0;
@@ -163,21 +173,21 @@
&.btn-inverted {
&.btn-success {
- @include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700);
+ @include btn-outline($white-light, $green-600, $green-500, $green-100, $green-700, $green-500, $green-200, $green-600, $green-800);
}
&.btn-remove,
&.btn-danger {
- @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
+ @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
}
&.btn-warning {
- @include btn-outline($white-light, $orange-500, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
+ @include btn-outline($white-light, $orange-500, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
}
&.btn-primary,
&.btn-info {
- @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
}
}
@@ -192,11 +202,11 @@
&.btn-close,
&.btn-close-color {
- @include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
+ @include btn-outline($white-light, $orange-600, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
}
&.btn-spam {
- @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
+ @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
}
&.btn-danger,
@@ -239,7 +249,7 @@
padding: 6px 16px;
border-color: $border-color;
color: $gray-darkest;
- background-color: $gray-light;
+ background-color: $white-light;
&:hover,
&:active,
@@ -248,7 +258,6 @@
box-shadow: none;
border-color: lighten($blue-300, 20%);
color: $gray-darkest;
- background-color: $gray-light;
}
}
@@ -329,6 +338,8 @@
svg {
top: auto;
+ width: 16px;
+ height: 16px;
}
}
@@ -395,15 +406,13 @@
cursor: default;
&:active {
- -moz-box-shadow: inset 0 0 0 $white-light;
- -webkit-box-shadow: inset 0 0 0 $white-light;
box-shadow: inset 0 0 0 $white-light;
}
}
.btn-inverted {
&-secondary {
- @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
}
}
@@ -445,7 +454,8 @@
border-color: transparent;
}
- &.btn-secondary-hover-link {
+ &.btn-secondary-hover-link,
+ &.btn-default-hover-link {
color: $gl-text-color-secondary;
&:hover,
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index 0d8e4afa76f..643b20c56bc 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -28,6 +28,10 @@
background-color: $red-100;
border-color: $red-200;
color: $red-700;
+
+ a {
+ color: $red-700;
+ }
}
.bs-callout-warning {
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
index 7207e5119ce..28d7492b99c 100644
--- a/app/assets/stylesheets/framework/ci_variable_list.scss
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -47,6 +47,7 @@
display: flex;
align-items: flex-start;
width: 100%;
+ padding-bottom: $gl-padding;
@include media-breakpoint-down(xs) {
display: block;
@@ -66,6 +67,7 @@
}
}
+.ci-variable-masked-item,
.ci-variable-protected-item {
flex: 0 1 auto;
display: flex;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index fa424532879..db09118ba15 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -5,6 +5,9 @@
.cgreen { color: $green-600; }
.cdark { color: $common-gray-dark; }
+.fwhite { fill: $white-light; }
+.fgray { fill: $gray-700; }
+
.text-plain,
.text-plain:hover {
color: $gl-text-color;
@@ -48,6 +51,10 @@
color: $brand-info;
}
+.bg-gray-light {
+ background-color: $gray-light;
+}
+
.text-break-word {
word-break: break-all;
}
@@ -57,7 +64,11 @@
text-decoration: underline;
}
-.hint { font-style: italic; color: $gl-gray-400; }
+.hint {
+ font-style: italic;
+ color: $gl-gray-400;
+}
+
.light { color: $gl-text-color; }
.slead {
@@ -116,7 +127,7 @@ hr {
text-overflow: ellipsis;
white-space: nowrap;
- > div,
+ > div:not(.block),
.str-truncated {
display: inline;
}
@@ -158,13 +169,14 @@ p.time {
text-shadow: none;
}
-.thin_area {
+.thin-area {
height: 150px;
}
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
img { max-width: 100%; }
+
.note-title {
li {
border-bottom: 0 !important;
@@ -183,11 +195,6 @@ li.note {
background-color: inherit;
}
-.show-suppressed-diff,
-.show-all-commits {
- cursor: pointer;
-}
-
.error-message {
padding: 10px;
background: $red-400;
@@ -200,12 +207,12 @@ li.note {
}
}
-.warning_message {
- border-left: 4px solid $orange-200;
- color: $orange-700;
+@mixin message($background-color, $border-color, $text-color) {
+ border-left: 4px solid $border-color;
+ color: $text-color;
padding: 10px;
margin-bottom: 10px;
- background: $orange-100;
+ background: $background-color;
padding-left: 20px;
&.centered {
@@ -213,6 +220,14 @@ li.note {
}
}
+.warning_message {
+ @include message($orange-100, $orange-200, $orange-700);
+}
+
+.danger_message {
+ @include message($red-100, $red-200, $red-900);
+}
+
.gitlab-promo {
a {
color: $gl-gray-350;
@@ -335,7 +350,7 @@ img.emoji {
.disabled-content {
pointer-events: none;
- opacity: .5;
+ opacity: 0.5;
}
.break-word {
@@ -371,18 +386,23 @@ img.emoji {
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-16 { margin-top: 16px; }
.prepend-top-20 { margin-top: 20px; }
+.prepend-top-32 { margin-top: 32px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-8 { margin-left: 8px; }
.prepend-left-10 { margin-left: 10px; }
+.prepend-left-15 { margin-left: 15px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
+.prepend-left-32 { margin-left: 32px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
+.append-right-15 { margin-right: 15px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
+.prepend-right-32 { margin-right: 32px; }
.append-bottom-0 { margin-bottom: 0; }
.append-bottom-4 { margin-bottom: $gl-padding-4; }
.append-bottom-5 { margin-bottom: 5px; }
@@ -391,15 +411,20 @@ img.emoji {
.append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; }
.append-bottom-default { margin-bottom: $gl-padding; }
+.prepend-bottom-32 { margin-bottom: 32px; }
.inline { display: inline-block; }
.center { text-align: center; }
+.block { display: block; }
+.flex { display: flex; }
.vertical-align-middle { vertical-align: middle; }
.vertical-align-sub { vertical-align: sub; }
.flex-align-self-center { align-self: center; }
.flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
+.ws-normal { white-space: normal; }
.overflow-auto { overflow: auto; }
+
.d-flex-center {
display: flex;
align-items: center;
@@ -413,27 +438,24 @@ img.emoji {
.mw-460 { max-width: 460px; }
.mw-6em { max-width: 6em; }
+.mw-70p { max-width: 70%; }
.min-height-0 { min-height: 0; }
-.w-3 { width: #{3 * $grid-size}; }
-
-.h-3 { width: #{3 * $grid-size}; }
+.svg-w-100 {
+ svg {
+ width: 100%;
+ }
+}
/** COMMON SPACING CLASSES **/
-.gl-pl-0 { padding-left: 0; }
-.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
-.gl-pl-2 { padding-left: $grid-size; }
-.gl-pl-3 { padding-left: #{2 * $grid-size}; }
-.gl-pl-4 { padding-left: #{3 * $grid-size}; }
-.gl-pl-5 { padding-left: #{4 * $grid-size}; }
-
-.gl-pr-0 { padding-right: 0; }
-.gl-pr-1 { padding-right: #{0.5 * $grid-size}; }
-.gl-pr-2 { padding-right: $grid-size; }
-.gl-pr-3 { padding-right: #{2 * $grid-size}; }
-.gl-pr-4 { padding-right: #{3 * $grid-size}; }
-.gl-pr-5 { padding-right: #{4 * $grid-size}; }
+@each $index, $padding in $spacing-scale {
+ #{'.gl-p-#{$index}'} { padding: $padding; }
+ #{'.gl-pl-#{$index}'} { padding-left: $padding; }
+ #{'.gl-pr-#{$index}'} { padding-right: $padding; }
+ #{'.gl-pt-#{$index}'} { padding-top: $padding; }
+ #{'.gl-pb-#{$index}'} { padding-bottom: $padding; }
+}
/**
* Removes browser specific clear icon from input fields in
@@ -447,10 +469,10 @@ img.emoji {
}
/** COMMON POSITIONING CLASSES */
-.position-bottom-0 { bottom: 0; }
-.position-left-0 { left: 0; }
-.position-right-0 { right: 0; }
-.position-top-0 { top: 0; }
+.position-bottom-0 { bottom: 0 !important; }
+.position-left-0 { left: 0 !important; }
+.position-right-0 { right: 0 !important; }
+.position-top-0 { top: 0 !important; }
.drag-handle {
width: 4px;
@@ -463,3 +485,54 @@ img.emoji {
background-color: $gray-600;
}
}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+// Make buttons/dropdowns full-width on mobile
+.full-width-mobile {
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+
+ > .dropdown-menu,
+ > .btn {
+ width: 100%;
+ }
+ }
+}
+
+.onboarding-helper-container {
+ bottom: 40px;
+ right: 40px;
+ font-size: $gl-font-size-small;
+ background: $gray-100;
+ width: 200px;
+ border-radius: 24px;
+ box-shadow: 0 2px 4px $issue-boards-card-shadow;
+ z-index: 10000;
+
+ .collapsible {
+ max-height: 0;
+ transition: max-height 0.5s cubic-bezier(0, 1, 0, 1);
+ }
+
+ &.expanded {
+ border-bottom-right-radius: $border-radius-default;
+ border-bottom-left-radius: $border-radius-default;
+
+ .collapsible {
+ max-height: 1000px;
+ transition: max-height 1s ease-in-out;
+ }
+ }
+
+ .avatar {
+ border-color: darken($gray-normal, 10%);
+
+ img {
+ width: 32px;
+ height: 32px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 8b6a7017c47..3238b01c6c0 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -5,7 +5,7 @@
padding-left: $contextual-sidebar-collapsed-width;
}
- @include media-breakpoint-up(lg) {
+ @include media-breakpoint-up(xl) {
padding-left: $contextual-sidebar-width;
}
@@ -15,7 +15,7 @@
}
.page-with-icon-sidebar {
- @include media-breakpoint-up(sm) {
+ @include media-breakpoint-up(md) {
padding-left: $contextual-sidebar-collapsed-width;
}
}
@@ -71,6 +71,44 @@
}
}
+@mixin collapse-contextual-sidebar-content {
+ .context-header {
+ height: 60px;
+ width: $contextual-sidebar-collapsed-width;
+
+ a {
+ padding: 10px 4px;
+ }
+ }
+
+ .sidebar-top-level-items > li {
+ .sidebar-sub-level-items {
+ &:not(.flyout-list) {
+ display: none;
+ }
+ }
+ }
+
+ .nav-icon-container {
+ margin-right: 0;
+ }
+
+ .toggle-sidebar-button {
+ padding: 16px;
+ width: $contextual-sidebar-collapsed-width - 1px;
+
+ .collapse-text,
+ .icon-angle-double-left {
+ display: none;
+ }
+
+ .icon-angle-double-right {
+ display: block;
+ margin: 0;
+ }
+ }
+}
+
.nav-sidebar {
transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
position: fixed;
@@ -89,7 +127,7 @@
}
}
- &.sidebar-collapsed-desktop {
+ @mixin collapse-contextual-sidebar {
width: $contextual-sidebar-collapsed-width;
.nav-sidebar-inner-scroll {
@@ -115,6 +153,10 @@
}
}
+ &.sidebar-collapsed-desktop {
+ @include collapse-contextual-sidebar;
+ }
+
&.sidebar-expanded-mobile {
left: 0;
}
@@ -150,7 +192,7 @@
}
}
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
left: (-$contextual-sidebar-width);
}
@@ -167,16 +209,19 @@
height: 16px;
width: 16px;
}
+
+ @media (min-width: map-get($grid-breakpoints, md)) and (max-width: map-get($grid-breakpoints, xl) - 1px) {
+ &:not(.sidebar-expanded-mobile) {
+ @include collapse-contextual-sidebar;
+ @include collapse-contextual-sidebar-content;
+ }
+ }
}
.nav-sidebar-inner-scroll {
height: 100%;
width: 100%;
overflow: auto;
-
- @include media-breakpoint-up(sm) {
- overflow: hidden;
- }
}
.with-performance-bar .nav-sidebar {
@@ -346,53 +391,13 @@
}
}
-.toggle-sidebar-button {
- @include media-breakpoint-down(xs) {
- display: none;
- }
-}
-
.collapse-text {
white-space: nowrap;
overflow: hidden;
}
.sidebar-collapsed-desktop {
- .context-header {
- height: 60px;
- width: $contextual-sidebar-collapsed-width;
-
- a {
- padding: 10px 4px;
- }
- }
-
- .sidebar-top-level-items > li {
- .sidebar-sub-level-items {
- &:not(.flyout-list) {
- display: none;
- }
- }
- }
-
- .nav-icon-container {
- margin-right: 0;
- }
-
- .toggle-sidebar-button {
- padding: 16px;
- width: $contextual-sidebar-collapsed-width - 1px;
-
- .collapse-text,
- .icon-angle-double-left {
- display: none;
- }
-
- .icon-angle-double-right {
- display: block;
- margin: 0;
- }
- }
+ @include collapse-contextual-sidebar-content;
}
.fly-out-top-item {
@@ -428,16 +433,14 @@
color: $gl-text-color-secondary;
}
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
display: flex;
align-items: center;
i {
font-size: 18px;
}
- }
- @include media-breakpoint-down(xs) {
+ .breadcrumbs-links {
padding-left: $gl-padding;
border-left: 1px solid $gl-text-color-quaternary;
@@ -445,21 +448,25 @@
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.close-nav-button {
display: flex;
}
-}
-.mobile-overlay {
- display: none;
+ .toggle-sidebar-button {
+ display: none;
+ }
- &.mobile-nav-open {
- display: block;
- position: fixed;
- background-color: $black-transparent;
- height: 100%;
- width: 100%;
- z-index: 300;
+ .mobile-overlay {
+ display: none;
+
+ &.mobile-nav-open {
+ display: block;
+ position: fixed;
+ background-color: $black-transparent;
+ height: 100%;
+ width: 100%;
+ z-index: 300;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index b90db135b4a..cd951f67293 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -351,6 +351,10 @@
// Expects up to 3 digits on the badge
margin-right: 40px;
}
+
+ .dropdown-menu-content {
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
+ }
}
.droplab-dropdown {
@@ -566,10 +570,10 @@
}
.dropdown-menu-close {
- right: 5px;
+ top: $gl-padding-4;
+ right: $gl-padding-8;
width: 20px;
height: 20px;
- top: -1px;
}
.dropdown-menu-close-icon {
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index be85e03430e..13c5541da92 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -2,7 +2,7 @@ gl-emoji {
font-style: normal;
display: inline-flex;
vertical-align: middle;
- font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
font-size: 1.4em;
line-height: 1em;
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 6108eaa1ad0..536a26a6ffe 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -25,10 +25,6 @@
}
}
- table {
- @extend .table;
- }
-
.file-title {
position: relative;
background-color: $gray-light;
@@ -123,7 +119,7 @@
}
}
- &.wiki {
+ &.md {
padding: $gl-padding;
@include media-breakpoint-up(md) {
@@ -245,6 +241,7 @@
*/
&.code {
padding: 0;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
}
.list-inline.previews {
@@ -332,9 +329,13 @@ span.idiff {
background-color: $gray-light;
border-bottom: 1px solid $border-color;
border-top: 1px solid $border-color;
- padding: 5px $gl-padding;
+ padding: $gl-padding-8 $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
+
+ &.is-stuck {
+ border-radius: 0;
+ }
}
.file-header-content {
@@ -365,10 +366,6 @@ span.idiff {
color: $gl-text-color;
}
- small {
- margin: 0 10px 0 0;
- }
-
.file-actions .btn {
padding: 0 10px;
font-size: 13px;
@@ -456,6 +453,28 @@ span.idiff {
}
}
+ .note-container {
+ .user-avatar-link.new-comment {
+ position: absolute;
+ margin: 40px $gl-padding 0 116px;
+
+ ~ .note-edit-form form.edit-note {
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
+ }
+ }
+
+ .diff-discussions:not(:last-child) .discussion .discussion-body {
+ padding-bottom: $gl-padding;
+
+ .discussion-reply-holder {
+ border-bottom: 1px solid $gray-100;
+ border-radius: 0;
+ }
+ }
+
.md-previewer {
padding: $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index f48b3ddc912..26cbb7f5c13 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -50,19 +50,15 @@
}
.filtered-search-wrapper {
- display: -webkit-flex;
display: flex;
@include media-breakpoint-down(xs) {
- -webkit-flex-direction: column;
flex-direction: column;
}
.tokens-container {
- display: -webkit-flex;
display: flex;
flex: 1;
- -webkit-flex: 1;
padding-left: 12px;
position: relative;
margin-bottom: 0;
@@ -82,21 +78,18 @@
.input-token:only-child,
.input-token:last-child {
flex: 1;
- -webkit-flex: 1;
max-width: inherit;
}
}
.filtered-search-token,
.filtered-search-term {
- display: -webkit-flex;
display: flex;
flex-shrink: 0;
margin-top: 4px;
margin-bottom: 4px;
.selectable {
- display: -webkit-flex;
display: flex;
}
@@ -115,6 +108,8 @@
}
.value-container {
+ display: flex;
+ align-items: center;
background-color: $white-normal;
color: $filter-value-text-color;
border-radius: 0 2px 2px 0;
@@ -128,7 +123,7 @@
.remove-token {
display: inline-block;
- padding-left: 4px;
+ padding-left: 8px;
padding-right: 0;
.fa-close {
@@ -176,7 +171,6 @@
}
.scroll-container {
- display: -webkit-flex;
display: flex;
overflow-x: auto;
white-space: nowrap;
@@ -186,7 +180,6 @@
.filtered-search-box {
position: relative;
flex: 1;
- display: -webkit-flex;
display: flex;
width: 100%;
min-width: 0;
@@ -194,7 +187,6 @@
background-color: $white-light;
@include media-breakpoint-down(xs) {
- -webkit-flex: 1 1 auto;
flex: 1 1 auto;
margin-bottom: 10px;
}
@@ -226,7 +218,7 @@
min-width: 200px;
padding-right: 25px;
padding-left: 0;
- height: $input-height;
+ height: $input-height - 2;
line-height: inherit;
border-color: transparent;
@@ -349,7 +341,6 @@
}
.filter-dropdown-container {
- display: -webkit-flex;
display: flex;
.dropdown-toggle {
@@ -423,3 +414,10 @@
padding: 8px 16px;
text-align: center;
}
+
+.search-token-target-branch {
+ .value {
+ font-family: $monospace-font;
+ font-size: 13px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index afa85f0e4ae..e3dd127366d 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -6,6 +6,19 @@
position: relative;
z-index: 1;
+ .flash-notice,
+ .flash-alert,
+ .flash-success,
+ .flash-warning {
+ border-radius: $border-radius-default;
+ color: $white-light;
+
+ .container-fluid,
+ .container-fluid.container-limited {
+ background: transparent;
+ }
+ }
+
.flash-notice {
@extend .alert;
background-color: $blue-500;
@@ -28,7 +41,8 @@
.flash-warning {
@extend .alert;
- background-color: $orange-500;
+ background-color: $orange-100;
+ color: $orange-900;
margin: 0;
}
@@ -60,19 +74,6 @@
margin: 0;
}
- .flash-notice,
- .flash-alert,
- .flash-success,
- .flash-warning {
- border-radius: $border-radius-default;
- color: $white-light;
-
- .container-fluid,
- .container-fluid.container-limited {
- background: transparent;
- }
- }
-
&.flash-container-page {
margin-bottom: 0;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index cbf9ee24ec5..2a601afff53 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -27,10 +27,16 @@ input[type='text'].danger {
}
label {
+ font-weight: $gl-font-weight-bold;
+
&.inline-label {
margin: 0;
}
+ &.form-check-label {
+ font-weight: $gl-font-weight-normal;
+ }
+
&.label-bold {
font-weight: $gl-font-weight-bold;
}
@@ -41,14 +47,6 @@ label {
margin: 0;
}
-.form-label {
- @extend label;
-}
-
-.form-control-label {
- @extend .col-md-2;
-}
-
.inline-input-group {
width: 250px;
}
@@ -81,44 +79,14 @@ label {
margin-left: 0;
margin-right: 0;
- .form-control-label {
- font-weight: $gl-font-weight-bold;
- padding-top: 4px;
- }
-
.form-control {
height: 29px;
background: $white-light;
font-family: $monospace-font;
}
- .input-group-prepend .btn,
- .input-group-append .btn {
- padding: 3px $gl-btn-padding;
- background-color: $gray-light;
- border: 1px solid $border-color;
- }
-
- .text-block {
- line-height: 0.8;
- padding-top: 9px;
-
- code {
- line-height: 1.8;
- }
-
- img {
- margin-right: $gl-padding;
- }
- }
-
@include media-breakpoint-down(xs) {
padding: 0 $gl-padding;
-
- .form-control-label,
- .text-block {
- padding-left: 0;
- }
}
}
@@ -128,7 +96,7 @@ label {
.form-control {
@include box-shadow(none);
- border-radius: 2px;
+ border-radius: $border-radius-default;
padding: $gl-vert-padding $gl-input-padding;
&.input-short {
@@ -140,25 +108,14 @@ label {
}
}
-.select-wrapper {
- position: relative;
-
- .fa-chevron-down {
- position: absolute;
- font-size: 10px;
- right: 10px;
- top: 12px;
- color: $gray-darkest;
- pointer-events: none;
- }
-}
-
.select-control {
padding-left: 10px;
padding-right: 10px;
+ appearance: none;
+ /* stylelint-disable property-no-vendor-prefix */
-webkit-appearance: none;
-moz-appearance: none;
- appearance: none;
+ /* stylelint-enable property-no-vendor-prefix */
&::-ms-expand {
display: none;
@@ -173,12 +130,7 @@ label {
margin-top: 35px;
}
-.form-group .form-control-label,
-.form-group .form-control-label-full-width {
- font-weight: $gl-font-weight-normal;
-}
-
-.form-control::-webkit-input-placeholder {
+.form-control::placeholder {
color: $gl-text-color-tertiary;
}
@@ -203,10 +155,13 @@ label {
.form-text.text-muted {
margin-bottom: 0;
margin-top: #{$grid-size / 2};
+ font-size: $gl-font-size;
}
-.gl-field-error {
+.gl-field-error,
+.invalid-feedback {
color: $red-500;
+ font-size: $gl-font-size;
}
.gl-show-field-errors {
@@ -218,7 +173,8 @@ label {
border: 1px solid $green-600;
&:focus {
- box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-600;
+ box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset,
+ 0 0 4px 0 $green-600;
border: 0 none;
}
}
@@ -227,7 +183,8 @@ label {
border: 1px solid $red-500;
&:focus {
- box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error;
+ box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset,
+ 0 0 4px 0 $gl-field-focus-shadow-error;
border: 0 none;
}
}
@@ -253,16 +210,26 @@ label {
}
}
-.input-icon-wrapper {
+.input-icon-wrapper,
+.select-wrapper {
position: relative;
+}
- .input-icon-right {
- position: absolute;
- right: 0.8em;
- top: 50%;
- transform: translateY(-50%);
- color: $gray-600;
- }
+.select-wrapper > .fa-chevron-down {
+ position: absolute;
+ font-size: 10px;
+ right: 10px;
+ top: 12px;
+ color: $gray-darkest;
+ pointer-events: none;
+}
+
+.input-icon-wrapper > .input-icon-right {
+ position: absolute;
+ right: 0.8em;
+ top: 50%;
+ transform: translateY(-50%);
+ color: $gray-600;
}
.input-md {
@@ -274,3 +241,21 @@ label {
max-width: $input-lg-width;
width: 100%;
}
+
+.input-group-text {
+ max-height: $input-height;
+}
+
+.gl-form-checkbox {
+ align-items: baseline;
+
+ &.form-check-inline .form-check-input {
+ align-self: flex-start;
+ margin-right: $gl-padding-8;
+ height: 1.5 * $gl-font-size;
+ }
+
+ .help-text {
+ margin-bottom: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 50d4298d418..6943bfbc3d0 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -32,7 +32,7 @@
height: $chip-size;
background: $white-light;
background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
- linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
+ linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
background-size: $bg-size $bg-size;
background-position: 0 0, $bg-pos $bg-pos;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 23dcc1817b1..1bc597bd4ae 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -39,7 +39,6 @@
.header-content {
width: 100%;
- display: -webkit-flex;
display: flex;
justify-content: space-between;
position: relative;
@@ -47,11 +46,8 @@
padding-left: 0;
.title-container {
- display: -webkit-flex;
display: flex;
- -webkit-align-items: stretch;
align-items: stretch;
- -webkit-flex: 1 1 auto;
flex: 1 1 auto;
padding-top: 0;
overflow: visible;
@@ -60,7 +56,6 @@
.title {
padding-right: 0;
color: currentColor;
- display: -webkit-flex;
display: flex;
position: relative;
margin: 0;
@@ -85,7 +80,6 @@
}
a {
- display: -webkit-flex;
display: flex;
align-items: center;
padding: 2px 8px;
@@ -173,7 +167,6 @@
.navbar-nav {
@include media-breakpoint-down(xs) {
- display: -webkit-flex;
display: flex;
padding-right: 10px;
flex-direction: row;
@@ -258,7 +251,6 @@
> li {
> a,
> button {
- display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
@@ -294,7 +286,6 @@
}
.navbar-sub-nav {
- display: -webkit-flex;
display: flex;
margin: 0 0 0 6px;
@@ -313,7 +304,9 @@
}
}
-.caret-down {
+.caret-down,
+.btn .caret-down {
+ top: 0;
height: 11px;
width: 11px;
margin-left: 4px;
@@ -326,14 +319,12 @@
}
.breadcrumbs {
- display: -webkit-flex;
display: flex;
min-height: $breadcrumb-min-height;
color: $gl-text-color;
}
.breadcrumbs-container {
- display: -webkit-flex;
display: flex;
width: 100%;
position: relative;
@@ -344,7 +335,6 @@
}
.breadcrumbs-links {
- -webkit-flex: 1;
flex: 1;
min-width: 0;
align-self: center;
@@ -379,7 +369,6 @@
}
.breadcrumbs-list {
- display: -webkit-flex;
display: flex;
margin-bottom: 0;
line-height: 16px;
@@ -430,7 +419,6 @@
}
.breadcrumbs-extra {
- display: -webkit-flex;
display: flex;
flex: 0 0 auto;
margin-left: auto;
@@ -459,29 +447,44 @@
}
}
+.title-container,
.navbar-nav {
- li {
- .badge.badge-pill {
- position: inherit;
- font-weight: $gl-font-weight-normal;
- margin-left: -6px;
- font-size: 11px;
- color: $white-light;
- padding: 0 5px;
- line-height: 12px;
- border-radius: 7px;
- box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
-
- &.issues-count {
- background-color: $green-500;
- }
+ .badge.badge-pill {
+ position: inherit;
+ font-weight: $gl-font-weight-normal;
+ margin-left: -6px;
+ font-size: 11px;
+ color: $white-light;
+ padding: 0 5px;
+ line-height: 12px;
+ border-radius: 7px;
+ box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
+
+ &.green-badge {
+ background-color: $green-500;
+ }
- &.merge-requests-count {
- background-color: $orange-600;
- }
+ &.merge-requests-count {
+ background-color: $orange-600;
+ }
+
+ &.todos-count {
+ background-color: $blue-500;
+ }
+ }
+
+ .canary-badge {
+ .badge {
+ font-size: $gl-font-size-small;
+ line-height: $gl-line-height;
+ padding: 0 $grid-size;
+ }
- &.todos-count {
- background-color: $blue-500;
+ &:hover {
+ text-decoration: none;
+
+ .badge {
+ text-decoration: none;
}
}
}
@@ -594,10 +597,15 @@
.emoji-menu-toggle-button {
@include emoji-menu-toggle-button;
+ padding: $gl-vert-padding $gl-btn-padding;
}
.input-group {
- height: 34px;
+ &,
+ .input-group-prepend,
+ .input-group-append {
+ height: $input-height;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 946f575ac13..741f92110c3 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -8,7 +8,7 @@
pre {
padding: 10px 0;
border: 0;
- border-radius: 0;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
font-family: $monospace-font;
font-size: $code-font-size;
line-height: 19px;
@@ -42,6 +42,7 @@
padding: 10px;
text-align: right;
float: left;
+ border-bottom-left-radius: $border-radius-default;
a {
font-family: $monospace-font;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 49b9b7014ae..1be5ef276fd 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -20,8 +20,8 @@
}
.ci-status-icon-pending,
-.ci-status-icon-failed_with_warnings,
-.ci-status-icon-success_with_warnings {
+.ci-status-icon-failed-with-warnings,
+.ci-status-icon-success-with-warnings {
svg {
fill: $orange-500;
}
@@ -31,6 +31,7 @@
}
}
+.ci-status-icon-preparing,
.ci-status-icon-running {
svg {
fill: $blue-400;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index d9d4a210f5f..555a3fe0dc7 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -15,7 +15,7 @@
word-wrap: break-word;
&::after {
- content: " ";
+ content: ' ';
display: table;
clear: both;
}
@@ -156,6 +156,12 @@ ul.content-list {
margin-top: 3px;
margin-bottom: 4px;
+ &.btn-ldap-override {
+ @include media-breakpoint-up(sm) {
+ margin-bottom: 0;
+ }
+ }
+
&.has-tooltip,
&:last-child {
margin-right: 0;
@@ -167,15 +173,7 @@ ul.content-list {
}
.no-comments {
- opacity: .5;
- }
- }
-
- .member-controls {
- float: none;
-
- @include media-breakpoint-up(sm) {
- float: right;
+ opacity: 0.5;
}
}
@@ -196,8 +194,6 @@ ul.content-list {
// Content list using flexbox
.flex-list {
.flex-row {
- display: -webkit-flex;
- display: -ms-flexbox;
display: flex;
align-items: center;
white-space: nowrap;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
index 429cfbe7235..c5feefb8c54 100644
--- a/app/assets/stylesheets/framework/logo.scss
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -9,7 +9,6 @@
}
.tanuki-logo {
-
.tanuki-left-ear,
.tanuki-right-ear,
.tanuki-nose {
@@ -34,7 +33,9 @@
.tanuki-left-cheek {
@include include-keyframes(animate-tanuki-left-cheek) {
- 0%, 10%, 100% {
+ 0%,
+ 10%,
+ 100% {
fill: lighten($tanuki-yellow, 25%);
}
@@ -46,11 +47,13 @@
.tanuki-left-eye {
@include include-keyframes(animate-tanuki-left-eye) {
- 10%, 80% {
+ 10%,
+ 80% {
fill: $tanuki-orange;
}
- 20%, 90% {
+ 20%,
+ 90% {
fill: lighten($tanuki-orange, 25%);
}
}
@@ -58,11 +61,13 @@
.tanuki-left-ear {
@include include-keyframes(animate-tanuki-left-ear) {
- 10%, 80% {
+ 10%,
+ 80% {
fill: $tanuki-red;
}
- 20%, 90% {
+ 20%,
+ 90% {
fill: lighten($tanuki-red, 25%);
}
}
@@ -70,11 +75,13 @@
.tanuki-nose {
@include include-keyframes(animate-tanuki-nose) {
- 20%, 70% {
+ 20%,
+ 70% {
fill: $tanuki-red;
}
- 30%, 80% {
+ 30%,
+ 80% {
fill: lighten($tanuki-red, 25%);
}
}
@@ -82,11 +89,13 @@
.tanuki-right-eye {
@include include-keyframes(animate-tanuki-right-eye) {
- 30%, 60% {
+ 30%,
+ 60% {
fill: $tanuki-orange;
}
- 40%, 70% {
+ 40%,
+ 70% {
fill: lighten($tanuki-orange, 25%);
}
}
@@ -94,11 +103,13 @@
.tanuki-right-ear {
@include include-keyframes(animate-tanuki-right-ear) {
- 30%, 60% {
+ 30%,
+ 60% {
fill: $tanuki-red;
}
- 40%, 70% {
+ 40%,
+ 70% {
fill: lighten($tanuki-red, 25%);
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index d6c4e68f68f..bfd96a4bc05 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -50,10 +50,6 @@
transition: opacity 200ms ease-in-out;
}
-.md-area {
- position: relative;
-}
-
.md-header {
.nav-links {
a {
@@ -61,6 +57,10 @@
padding-top: 0;
line-height: 19px;
+ &.btn.btn-sm {
+ padding: 2px 5px;
+ }
+
&:focus {
margin-top: -10px;
padding-top: 10px;
@@ -131,30 +131,6 @@
width: 100%;
}
-.md:not(.use-csslab) {
- &.md-preview-holder {
- // Reset ul style types since we're nested inside a ul already
- @include bulleted-list;
- }
-
- // On diffs code should wrap nicely and not overflow
- code {
- white-space: pre-wrap;
- word-break: keep-all;
- }
-
- hr {
- // Darken 'whitesmoke' a bit to make it more visible in note bodies
- border-color: darken($gray-normal, 8%);
- margin: 10px 0;
- }
-
-
- table:not(.js-syntax-highlight) {
- @include markdown-table;
- }
-}
-
.toolbar-btn {
float: left;
padding: 0 7px;
@@ -187,88 +163,6 @@
}
}
-.atwho-view {
- overflow-y: auto;
- overflow-x: hidden;
-
- .name,
- small.aliases,
- small.params {
- float: left;
- }
-
- small.aliases,
- small.params {
- padding: 2px 5px;
- }
-
- small.description {
- float: right;
- padding: 3px 5px;
- }
-
- .avatar-inline {
- margin-bottom: 0;
- }
-
- .has-warning {
- .name,
- .description {
- color: $orange-700;
- }
- }
-
- .cur {
- .avatar {
- @include disable-all-animation;
- border: 1px solid $white-light;
- }
- }
-
- ul > li {
- @include clearfix;
- white-space: nowrap;
- }
-
- // TODO: fallback to global style
- .atwho-view-ul {
- padding: 8px 1px;
-
- li {
- padding: 8px 16px;
- border: 0;
-
- &.cur {
- background-color: $gray-darker;
- color: $gl-text-color;
-
- small {
- color: inherit;
- }
-
- &.has-warning {
- color: $orange-700;
- background-color: $orange-100;
- }
- }
-
- div.avatar {
- display: inline-flex;
- justify-content: center;
- align-items: center;
-
- .center {
- line-height: 14px;
- }
- }
-
- strong {
- color: $gl-text-color;
- }
- }
- }
-}
-
.md-suggestion-diff {
display: table !important;
border: 1px solid $border-color !important;
@@ -293,15 +187,6 @@
}
@include media-breakpoint-down(xs) {
- .atwho-view-ul {
- width: 350px;
- }
-
- .atwho-view ul li {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
.referenced-users {
margin-right: 0;
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 3b0869e31a9..e7278554e6e 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -22,32 +22,6 @@
}
/*
- * Mixin for markdown tables
- */
-@mixin markdown-table {
- width: auto;
- display: inline-block;
- overflow-x: auto;
- border: 0;
- border-color: $gl-gray-100;
-
- @supports (width: fit-content) {
- display: block;
- width: fit-content;
- }
-
- tr {
- th {
- border-bottom: solid 2px $gl-gray-100;
- }
-
- td {
- border-color: $gl-gray-100;
- }
- }
-}
-
-/*
* Base mixin for lists in GitLab
*/
@mixin basic-list {
@@ -99,20 +73,6 @@
}
}
-@mixin bulleted-list {
- > ul {
- list-style-type: disc;
-
- ul {
- list-style-type: circle;
-
- ul {
- list-style-type: square;
- }
- }
- }
-}
-
@mixin webkit-prefix($property, $value) {
#{'-webkit-' + $property}: $value;
#{$property}: $value;
@@ -120,16 +80,13 @@
/* http://phrappe.com/css/conditional-css-for-webkit-based-browsers/ */
@mixin on-webkit-only {
+ /* stylelint-disable-next-line media-feature-name-no-vendor-prefix */
@media screen and (-webkit-min-device-pixel-ratio: 0) {
@content;
}
}
@mixin keyframes($animation-name) {
- @-webkit-keyframes #{$animation-name} {
- @content;
- }
-
@keyframes #{$animation-name} {
@content;
}
@@ -169,12 +126,10 @@
width: 43px;
height: 30px;
transition-duration: 0.3s;
- -webkit-transform: translateZ(0);
- background: linear-gradient(
- to $gradient-direction,
- $gradient-color 45%,
- rgba($gradient-color, 0.4)
- );
+ transform: translateZ(0);
+ background: linear-gradient(to $gradient-direction,
+ $gradient-color 45%,
+ rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
@@ -263,16 +218,22 @@
}
}
-@mixin build-trace-top-bar($height) {
+// Used in EE for Web Terminal
+@mixin build-trace-bar($height) {
height: $height;
min-height: $height;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
- position: sticky;
+ padding: $grid-size;
+}
+
+@mixin build-trace-top-bar($height) {
+ @include build-trace-bar($height);
+
position: -webkit-sticky;
+ position: sticky;
top: $header-height;
- padding: $grid-size;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
@@ -370,8 +331,8 @@
line-height: 1;
padding: 0;
min-width: 16px;
- color: $gray-darkest;
- fill: $gray-darkest;
+ color: $gray-600;
+ fill: $gray-600;
.fa {
position: relative;
@@ -421,3 +382,17 @@
}
}
}
+
+/*
+* Mixin that handles the size and right margin of avatars.
+*/
+@mixin avatar-size($size, $margin-right) {
+ width: $size;
+ height: $size;
+ margin-right: $margin-right;
+}
+
+@mixin code-icon-size() {
+ width: $gl-font-size * $code-line-height * 0.9;
+ height: $gl-font-size * $code-line-height * 0.9;
+}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 3703b7568c8..f75e5b55506 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -34,10 +34,10 @@
.modal-body {
background-color: $modal-body-bg;
line-height: $line-height-base;
- min-height: $modal-body-height;
position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size};
text-align: left;
+ white-space: normal;
.form-actions {
margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
@@ -52,24 +52,22 @@
display: flex;
flex-direction: row;
- .btn + .btn {
+ .btn + .btn:not(.dropdown-toggle-split),
+ .btn + .btn-group,
+ .btn-group + .btn {
margin-left: $grid-size;
}
@include media-breakpoint-down(xs) {
flex-direction: column;
- .btn + .btn {
+ .btn + .btn:not(.dropdown-toggle-split),
+ .btn + .btn-group,
+ .btn-group + .btn {
margin-left: 0;
margin-top: $grid-size;
}
}
-
- @include media-breakpoint-up(sm) {
- .btn:nth-child(1) {
- margin-left: auto;
- }
- }
}
body.modal-open {
diff --git a/app/assets/stylesheets/framework/notes.scss b/app/assets/stylesheets/framework/notes.scss
index d349e3fad9c..85ddf11d6fe 100644
--- a/app/assets/stylesheets/framework/notes.scss
+++ b/app/assets/stylesheets/framework/notes.scss
@@ -4,7 +4,7 @@
}
// Diff is side by side
- .notes_content.parallel & {
+ .notes-content.parallel & {
// We hide at double what we normally hide at because
// there are two columns of notes
@media (#{$condition}-width: (2 * $breakpoint-width)) {
diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss
index e8302953a63..c77e2be8e5a 100644
--- a/app/assets/stylesheets/framework/page_title.scss
+++ b/app/assets/stylesheets/framework/page_title.scss
@@ -1,8 +1,4 @@
.page-title-holder {
- @extend .d-flex;
- @extend .align-items-center;
-
- padding-top: $gl-padding-top;
border-bottom: 1px solid $border-color;
.page-title {
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 3a117106cff..cd3d6f8297e 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -7,7 +7,6 @@
margin-bottom: $gl-vert-padding;
}
-
.card-header {
padding: $gl-vert-padding $gl-padding;
line-height: 36px;
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 19640ab5986..ada8f2fe1a6 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -13,8 +13,8 @@
a,
button {
- padding: $gl-btn-padding;
- padding-bottom: 11px;
+ padding: $gl-padding-8;
+ padding-bottom: $gl-padding-8 + 1;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
@@ -58,8 +58,12 @@
}
.top-area {
- @include clearfix;
border-bottom: 1px solid $border-color;
+ display: flex;
+
+ @include media-breakpoint-down(md) {
+ flex-flow: column-reverse wrap;
+ }
.nav-text {
padding-top: 16px;
@@ -75,9 +79,8 @@
}
.nav-links {
- margin-bottom: 0;
border-bottom: 0;
- float: left;
+ flex: 1;
&.wide {
width: 100%;
@@ -98,16 +101,23 @@
&.mobile-separator {
border-bottom: 1px solid $border-color;
+ margin-bottom: $gl-padding-8;
}
}
}
.nav-controls {
display: inline-block;
- float: right;
text-align: right;
- padding: $gl-padding-8 0;
- margin-bottom: 0;
+
+ @include media-breakpoint-down(sm) {
+ margin-top: $gl-padding-8;
+ }
+
+ @include media-breakpoint-up(md) {
+ display: flex;
+ align-items: center;
+ }
> .btn,
> .btn-container,
@@ -115,8 +125,6 @@
> input,
> form {
margin-right: $gl-padding-top;
- display: inline-block;
- vertical-align: top;
&:last-child {
margin-right: 0;
@@ -143,7 +151,7 @@
@include media-breakpoint-up(lg) { width: 250px; }
}
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
padding-bottom: 0;
width: 100%;
@@ -153,7 +161,7 @@
.dropdown-toggle,
.dropdown-menu-toggle,
.form-control {
- margin: 0 0 10px;
+ margin: 0 0 $gl-padding-8;
display: block;
width: 100%;
}
@@ -165,7 +173,7 @@
form {
display: block;
height: auto;
- margin-bottom: 14px;
+ margin-bottom: $gl-padding-8;
input {
width: 100%;
@@ -181,6 +189,33 @@
margin: 0;
width: 100%;
}
+
+ &.inline {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: space-between;
+
+ > .btn,
+ > .btn-container,
+ > .dropdown,
+ > input,
+ > form {
+ flex: 1 1 auto;
+ margin: 0 0 10px;
+ margin-left: $gl-padding-top;
+ width: auto;
+
+ &:first-child {
+ margin-left: 0;
+ float: none;
+ }
+ }
+
+ .btn-full {
+ flex: 1 1 100%;
+ margin-left: 0;
+ }
+ }
}
}
@@ -209,20 +244,11 @@
width: 100%;
}
- @include media-breakpoint-down(xs) {
- flex-flow: row wrap;
-
+ @include media-breakpoint-down(md) {
.nav-controls {
$controls-margin: $btn-margin-5 - 2px;
flex: 0 0 100%;
-
- &.controls-flex {
- display: flex;
- flex-flow: row wrap;
- align-items: center;
- justify-content: center;
- padding: 0 0 $gl-padding-top;
- }
+ margin-top: $gl-padding-8;
.controls-item,
.controls-item-full,
@@ -299,8 +325,8 @@
.fade-right,
.fade-left {
- top: 16px;
- bottom: auto;
+ bottom: $gl-padding;
+ top: auto;
}
&.is-smaller {
@@ -340,6 +366,7 @@
display: flex;
border-bottom: 1px solid $border-color;
overflow: hidden;
+ align-items: center;
.nav-links {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index bcd601e198a..81ccea1e01f 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -32,7 +32,7 @@
}
&::after {
- content: "\f078";
+ content: '\f078';
position: absolute;
z-index: 1;
text-align: center;
@@ -264,6 +264,16 @@
}
}
+.project-result {
+ .project-name {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .project-path {
+ color: $gl-gray-400;
+ }
+}
+
.user-result {
min-height: 24px;
display: flex;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c4dbcf2ddc9..43d0e51e4c9 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -157,3 +157,55 @@
.sidebar-collapsed-icon .sidebar-collapsed-value {
font-size: 12px;
}
+
+.ancestor-tree {
+ .vertical-timeline {
+ position: relative;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ &::before {
+ content: '';
+ border-left: 1px solid $gray-500;
+ position: absolute;
+ top: $gl-padding;
+ bottom: $gl-padding;
+ left: map-get($spacers, 2) - 1px;
+ }
+
+ &-row {
+ margin-top: map-get($spacers, 3);
+
+ &:nth-child(1) {
+ margin-top: 0;
+ }
+ }
+
+ &-icon {
+ /**
+ * 2px extra is to give a little more height than needed
+ * to hide timeline line before/after the element starts/ends
+ */
+ height: map-get($spacers, 4) + 2px;
+ z-index: 1;
+ position: relative;
+ top: -3px;
+ padding: $gl-padding-4 0;
+ background-color: $gray-light;
+
+ &.opened {
+ color: $green-500;
+ }
+
+ &.closed {
+ color: $blue-500;
+ }
+ }
+
+ &-content {
+ line-height: initial;
+ margin-left: $gl-padding-8;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index 36ab38f1c9d..3ab83f4c8e6 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -22,6 +22,10 @@
.snippet-file-content {
border-radius: 3px;
+
+ .file-title-flex-parent .btn-clipboard {
+ line-height: 28px;
+ }
}
.snippet-header {
diff --git a/app/assets/stylesheets/framework/sortable.scss b/app/assets/stylesheets/framework/sortable.scss
new file mode 100644
index 00000000000..8c070200135
--- /dev/null
+++ b/app/assets/stylesheets/framework/sortable.scss
@@ -0,0 +1,92 @@
+.sortable-container {
+ background-color: $gray-light;
+
+ .flex-list {
+ padding: 5px;
+ margin-bottom: 0;
+ }
+}
+
+.sortable-row {
+ .flex-row {
+ display: flex;
+
+ &.issuable-info-container {
+ padding-right: 0;
+ }
+ }
+
+ .sortable-link {
+ color: $black;
+ }
+}
+
+.gl-sortable {
+ .header {
+ user-select: none;
+
+ &:hover {
+ cursor: pointer;
+ background-color: $gray-100;
+ }
+
+ &:focus {
+ outline: 1px solid $blue-300;
+ }
+ }
+}
+
+.related-issues-list-item {
+ .card-body,
+ .issuable-info-container {
+ padding: $gl-padding-4 $gl-padding-4 $gl-padding-4 $gl-padding;
+
+ .block-truncated {
+ padding: $gl-padding-8 0;
+ line-height: $gl-btn-line-height;
+ }
+
+ @include media-breakpoint-down(md) {
+ padding-left: $gl-padding;
+
+ .block-truncated {
+ flex-direction: column-reverse;
+ padding: $gl-padding-4 0;
+
+ .text-secondary {
+ margin-top: $gl-padding-4;
+ }
+
+ .issue-token-title-text {
+ display: block;
+ }
+ }
+
+ .issue-item-remove-button {
+ align-self: baseline;
+ }
+ }
+
+ @include media-breakpoint-only(md) {
+ .block-truncated .issue-token-title-text {
+ white-space: nowrap;
+ }
+
+ .issue-item-remove-button {
+ align-self: center;
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+ padding-left: $gl-padding-8;
+
+ .block-truncated .issue-token-title-text {
+ white-space: normal;
+ }
+ }
+ }
+
+ &.is-dragging {
+ padding: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/spinner.scss b/app/assets/stylesheets/framework/spinner.scss
new file mode 100644
index 00000000000..91fe75075dc
--- /dev/null
+++ b/app/assets/stylesheets/framework/spinner.scss
@@ -0,0 +1,51 @@
+@mixin spinner-color($color) {
+ border-color: rgba($color, 0.25);
+ border-top-color: $color;
+}
+
+@mixin spinner-size($size, $border-width) {
+ width: $size;
+ height: $size;
+ border-width: $border-width;
+ @include webkit-prefix(transform-origin, 50% 50% calc((#{$size} / 2) + #{$border-width}));
+}
+
+@keyframes spinner-rotate {
+ 0% {
+ transform: rotate(0);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.spinner {
+ border-radius: 50%;
+ position: relative;
+ margin: 0 auto;
+ animation-name: spinner-rotate;
+ animation-duration: 0.6s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ border-style: solid;
+ display: inline-flex;
+ @include spinner-size(16px, 2px);
+ @include spinner-color($orange-600);
+
+ &.spinner-md {
+ @include spinner-size(32px, 3px);
+ }
+
+ &.spinner-lg {
+ @include spinner-size(64px, 4px);
+ }
+
+ &.spinner-dark {
+ @include spinner-color($gray-700);
+ }
+
+ &.spinner-light {
+ @include spinner-color($white);
+ }
+}
diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss
index 3d66136938f..6205ccaa52f 100644
--- a/app/assets/stylesheets/framework/system_messages.scss
+++ b/app/assets/stylesheets/framework/system_messages.scss
@@ -12,8 +12,9 @@
p {
@include str-truncated(100%);
- margin-top: 0;
+ margin-top: -1px;
margin-bottom: 0;
+ font-size: $gl-font-size-small;
}
}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 295a5b5ee7a..ba406bac50b 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -161,4 +161,3 @@ table {
border-top: 0;
}
}
-
diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss
index 3f4be8829d7..b07d6023127 100644
--- a/app/assets/stylesheets/framework/terms.scss
+++ b/app/assets/stylesheets/framework/terms.scss
@@ -13,7 +13,6 @@
.card {
.card-header {
- display: -webkit-flex;
display: flex;
align-items: center;
justify-content: space-between;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 3d5208c3db5..42a739e88f7 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -42,8 +42,8 @@
}
}
- .avatar {
- margin-right: 15px;
+ img.avatar {
+ margin-right: $gl-padding;
}
.controls {
@@ -55,4 +55,5 @@
.discussion .timeline-entry {
margin: 0;
border-right: 0;
+ border-radius: $border-radius-default $border-radius-default 0 0;
}
diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
index 8258da07e4d..5f8ac3b7e37 100644
--- a/app/assets/stylesheets/framework/toggle.scss
+++ b/app/assets/stylesheets/framework/toggle.scss
@@ -34,7 +34,7 @@
background: $gl-gray-400;
border-radius: 12px;
padding: 3px;
- transition: all .4s ease;
+ transition: all 0.4s ease;
&::selection,
&::before::selection,
@@ -52,7 +52,7 @@
left: 0;
border-radius: 9px;
background: $feature-toggle-color;
- transition: all .2s ease;
+ transition: all 0.2s ease;
&,
.toggle-icon-svg {
@@ -135,12 +135,18 @@
}
@keyframes animate-enabled {
- 0%, 35% { opacity: 0; }
+ 0%,
+
+ 35% { opacity: 0; }
+
100% { opacity: 1; }
}
@keyframes animate-disabled {
- 0%, 35% { opacity: 0; }
+ 0%,
+
+ 35% { opacity: 0; }
+
100% { opacity: 1; }
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 1b36c1f4862..7c152efd9c7 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -1,13 +1,44 @@
-@mixin md-typography {
+/**
+ * Apply Markdown typography
+ *
+ */
+.md:not(.use-csslab) {
color: $gl-text-color;
word-wrap: break-word;
- [dir="auto"] {
+ [dir='auto'] {
text-align: initial;
}
+ *:first-child {
+ margin-top: 0;
+ }
+
+ > :last-child {
+ margin-bottom: 0;
+ }
+
+ p {
+ color: $gl-text-color;
+ margin: 0 0 16px;
+
+ > code {
+ font-weight: inherit;
+ }
+
+ a:not(.no-attachment-icon) img {
+ // Remove bottom padding because
+ // <p> already has $gl-padding bottom
+ margin-bottom: 0;
+ }
+ }
+
a {
color: $blue-600;
+
+ > code {
+ color: $blue-600;
+ }
}
img:not(.emoji) {
@@ -28,18 +59,12 @@
max-width: 100%;
}
- p a:not(.no-attachment-icon) img {
- // Remove bottom padding because
- // <p> already has $gl-padding bottom
- margin-bottom: 0;
- }
-
- *:first-child {
- margin-top: 0;
- }
-
- > :last-child {
- margin-bottom: 0;
+ &:not(.md-file) img:not(.emoji) {
+ border: 1px solid $white-normal;
+ padding: 5px;
+ margin: 5px 0;
+ // Ensure that image does not exceed viewport
+ max-height: calc(100vh - 100px);
}
// Single code lines should wrap
@@ -47,6 +72,7 @@
font-family: $monospace-font;
white-space: pre-wrap;
word-wrap: normal;
+ word-break: keep-all;
}
kbd {
@@ -131,20 +157,34 @@
}
}
- p {
- color: $gl-text-color;
- margin: 0 0 16px;
+ hr {
+ // Darken 'whitesmoke' a bit to make it more visible in note bodies
+ border-color: darken($gray-normal, 8%);
+ margin: 10px 0;
}
- table:not(.js-syntax-highlight) {
+ table:not(.code) {
@extend .table;
@extend .table-bordered;
margin: 16px 0;
color: $gl-text-color;
border: 0;
+ width: auto;
+ display: block;
+ overflow-x: auto;
- th {
- background: $label-gray-bg;
+ tbody {
+ background-color: $white-light;
+ }
+
+ tr {
+ th {
+ border-bottom: solid 2px $gl-gray-200;
+ }
+
+ td {
+ border-color: $gl-gray-200;
+ }
}
}
@@ -173,14 +213,6 @@
}
}
- p > code {
- font-weight: inherit;
- }
-
- a > code {
- color: $blue-600;
- }
-
dd {
margin-left: $gl-padding;
}
@@ -196,6 +228,18 @@
margin: 3px 28px 3px 0 !important;
}
+ > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+
li {
line-height: 1.6em;
margin-left: 25px;
@@ -224,8 +268,8 @@
}
}
- a[href*="/uploads/"],
- a[href*="storage.googleapis.com/google-code-attachments/"] {
+ a[href*='/uploads/'],
+ a[href*='storage.googleapis.com/google-code-attachments/'] {
&::before {
margin-right: 4px;
@@ -233,17 +277,17 @@
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
- content: "\f0c6";
+ content: '\f0c6';
}
&:hover::before {
text-decoration: none;
}
- }
- a.no-attachment-icon {
- &::before {
- display: none;
+ &.no-attachment-icon {
+ &::before {
+ display: none;
+ }
}
}
@@ -362,28 +406,6 @@ code {
}
/**
- * Apply Markdown typography
- *
- */
-.wiki:not(.use-csslab) {
- @include md-typography;
-}
-
-.md:not(.use-csslab) {
- @include md-typography;
-
- &:not(.wiki) {
- img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- margin: 5px 0;
- // Ensure that image does not exceed viewport
- max-height: calc(100vh - 100px);
- }
- }
-}
-
-/**
* Textareas intended for GFM
*
*/
@@ -423,6 +445,7 @@ h4 {
/**
* form text input i.e. search bar, comments, forms, etc.
*/
+/* stylelint-disable selector-no-vendor-prefix */
input,
textarea {
&::-webkit-input-placeholder {
@@ -447,5 +470,10 @@ textarea {
color: $gl-text-color-tertiary;
}
}
+/* stylelint-enable */
.lh-100 { line-height: 1; }
+
+wbr {
+ display: inline-block;
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 25b272ab3a9..dc451a97e17 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -11,6 +11,14 @@ $default-transition-duration: 0.15s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
$toggle-sidebar-height: 48px;
+$spacing-scale: (
+ 0: 0,
+ 1: #{0.5 * $grid-size},
+ 2: $grid-size,
+ 3: #{2 * $grid-size},
+ 4: #{3 * $grid-size},
+ 5: #{4 * $grid-size}
+);
/*
* Color schema
@@ -23,6 +31,7 @@ $darken-border-dashed-factor: 25%;
$white-light: #fff;
$white-normal: #f0f0f0;
$white-dark: #eaeaea;
+$white-transparent: rgba(255, 255, 255, 0.8);
$gray-lightest: #fdfdfd;
$gray-light: #fafafa;
@@ -41,13 +50,13 @@ $t-gray-a-04: rgba($black, 0.04);
$t-gray-a-06: rgba($black, 0.06);
$t-gray-a-08: rgba($black, 0.08);
-$gl-gray-100: #dddddd;
-$gl-gray-200: #cccccc;
-$gl-gray-350: #aaaaaa;
-$gl-gray-400: #999999;
-$gl-gray-500: #777777;
-$gl-gray-600: #666666;
-$gl-gray-700: #555555;
+$gl-gray-100: #ddd;
+$gl-gray-200: #ccc;
+$gl-gray-350: #aaa;
+$gl-gray-400: #999;
+$gl-gray-500: #777;
+$gl-gray-600: #666;
+$gl-gray-700: #555;
$green-50: #f1fdf6;
$green-100: #dcf5e7;
@@ -100,7 +109,7 @@ $red-950: #4b140b;
$gray-50: #fafafa;
$gray-100: #f2f2f2;
$gray-200: #dfdfdf;
-$gray-300: #cccccc;
+$gray-300: #ccc;
$gray-400: #bababa;
$gray-500: #a7a7a7;
$gray-600: #919191;
@@ -109,6 +118,84 @@ $gray-800: #4f4f4f;
$gray-900: #2e2e2e;
$gray-950: #1f1f1f;
+$greens: (
+ '50': $green-50,
+ '100': $green-100,
+ '200': $green-200,
+ '300': $green-300,
+ '400': $green-400,
+ '500': $green-500,
+ '600': $green-600,
+ '700': $green-700,
+ '800': $green-800,
+ '900': $green-900,
+ '950': $green-950
+);
+
+$blues: (
+ '50': $blue-50,
+ '100': $blue-100,
+ '200': $blue-200,
+ '300': $blue-300,
+ '400': $blue-400,
+ '500': $blue-500,
+ '600': $blue-600,
+ '700': $blue-700,
+ '800': $blue-800,
+ '900': $blue-900,
+ '950': $blue-950
+);
+
+$oranges: (
+ '50': $orange-50,
+ '100': $orange-100,
+ '200': $orange-200,
+ '300': $orange-300,
+ '400': $orange-400,
+ '500': $orange-500,
+ '600': $orange-600,
+ '700': $orange-700,
+ '800': $orange-800,
+ '900': $orange-900,
+ '950': $orange-950
+);
+
+$reds: (
+ '50': $red-50,
+ '100': $red-100,
+ '200': $red-200,
+ '300': $red-300,
+ '400': $red-400,
+ '500': $red-500,
+ '600': $red-600,
+ '700': $red-700,
+ '800': $red-800,
+ '900': $red-900,
+ '950': $red-950
+);
+
+$grays: (
+ '50': $gray-50,
+ '100': $gray-100,
+ '200': $gray-200,
+ '300': $gray-300,
+ '400': $gray-400,
+ '500': $gray-500,
+ '600': $gray-600,
+ '700': $gray-700,
+ '800': $gray-800,
+ '900': $gray-900,
+ '950': $gray-950
+);
+
+$color-ranges: (
+ 'primary': $blues,
+ 'secondary': $grays,
+ 'success': $greens,
+ 'warning': $oranges,
+ 'danger': $reds
+);
+
// GitLab themes
$indigo-50: #f7f7ff;
@@ -218,6 +305,15 @@ $gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
+$type-scale: (
+ 1: 12px,
+ 2: 14px,
+ 3: 16px,
+ 4: 20px,
+ 5: 28px,
+ 6: 42px
+);
+
/*
* Lists
*/
@@ -277,7 +373,7 @@ $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
-$system-header-height: 35px;
+$system-header-height: 16px;
$system-footer-height: $system-header-height;
$flash-height: 52px;
$context-header-height: 60px;
@@ -285,9 +381,13 @@ $breadcrumb-min-height: 48px;
$home-panel-title-row-height: 64px;
$home-panel-avatar-mobile-size: 24px;
$gl-line-height: 16px;
+$gl-line-height-20: 20px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
+$issue-box-upcoming-bg: #8f8f8f;
+$pages-group-name-color: #4c4e54;
+
/*
* Common component specific colors
*/
@@ -323,8 +423,8 @@ $line-select-yellow: #fcf8e7;
$line-select-yellow-dark: #f0e2bd;
$dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
-$diff-image-info-color: gray;
-$diff-view-modes-color: gray;
+$diff-image-info-color: #808080;
+$diff-view-modes-color: #808080;
$diff-view-modes-border: #c1c1c1;
$diff-jagged-border-gradient-color: darken($white-normal, 8%);
@@ -333,7 +433,7 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
*/
$monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono',
'Courier New', 'andale mono', 'lucida console', monospace;
-$regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
+$regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', Ubuntu, Cantarell,
'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
@@ -343,6 +443,7 @@ $regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-San
$dropdown-width: 300px;
$dropdown-min-height: 40px;
$dropdown-max-height: 312px;
+$dropdown-max-height-lg: 445px;
$dropdown-vertical-offset: 4px;
$dropdown-empty-row-bg: rgba(#000, 0.04);
$dropdown-shadow-color: rgba(#000, 0.1);
@@ -398,6 +499,17 @@ $pagination-line-height: 20px;
$pagination-disabled-color: #cdcdcd;
/*
+* Toasts
+*/
+$toast-offset: 24px;
+$toast-height: 48px;
+$toast-max-width: 586px;
+$toast-padding-right: 42px;
+$toast-default-margin: 8px;
+$toast-action-margin-left: 16px;
+$toast-background-opacity: 0.95;
+
+/*
* Status icons
*/
$status-icon-size: 22px;
@@ -409,7 +521,7 @@ $award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
$award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13;
$award-emoji-width: 376px;
-$award-emoji-width-xs: 300px;
+$award-emoji-width-xs: 90%;
/*
* Search Box
@@ -478,6 +590,7 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards
*/
$avatar-radius: 50%;
$gl-avatar-size: 40px;
+$gl-avatar-border-opacity: 0.1;
/*
* Blame
@@ -528,6 +641,7 @@ $input-lg-width: 320px;
*/
$document-index-color: #888;
$help-shortcut-header-color: #333;
+$accepting-mr-label-color: #69d100;
/*
* Issues
@@ -570,6 +684,11 @@ $feature-toggle-text-color: #fff;
$feature-toggle-color-enabled: #4a8bee;
/*
+ * Monitor Charts
+ */
+$chart-tooltip-max-width: 512px;
+
+/*
Stat Graph
*/
$stat-graph-common-bg: #f3f3f3;
@@ -625,6 +744,18 @@ Animation Functions
$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/*
+GitLab Plans
+*/
+$gl-gold-plan: #d4af37;
+$gl-silver-plan: #91a1ab;
+$gl-bronze-plan: #cd7f32;
+
+/*
+Cross-project Pipelines
+ */
+$linked-project-column-margin: 60px;
+
+/*
Performance Bar
*/
$perf-bar-production: #222;
@@ -648,6 +779,17 @@ $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 12;
/*
+Add GitLab Slack Application
+*/
+$add-to-slack-popup-max-width: 400px;
+$add-to-slack-gif-max-width: 850px;
+$add-to-slack-well-max-width: 750px;
+$add-to-slack-logo-size: 100px;
+$double-headed-arrow-width: 100px;
+$double-headed-arrow-height: 25px;
+$right-arrow-size: 16px;
+
+/*
Popup
*/
$popup-triangle-size: 15px;
@@ -682,3 +824,16 @@ $mr-version-controls-height: 56px;
Compare Branches
*/
$compare-branches-sticky-header-height: 68px;
+
+/**
+ Bootstrap 4.2.0 introduced new icons for validating forms.
+ Our design system does not use those, so we are disabling them for now:
+ - Docs: https://getbootstrap.com/docs/4.3/components/forms/#server-side
+ - Issue: https://gitlab.com/gitlab-org/design.gitlab.com/issues/242
+ */
+$enable-validation-icons: false;
+
+/*
+Licenses
+*/
+$license-header-cell-width: 150px;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 1dfe2a69a2f..ea96381a098 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -7,9 +7,9 @@ $secondary: $gray-light;
$input-disabled-bg: $gray-light;
$input-border-color: $gray-200;
$input-color: $gl-text-color;
+$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
$btn-line-height: 20px;
$table-accent-bg: $gray-light;
$card-border-color: $border-color;
@@ -37,9 +37,14 @@ $h6-font-size: 14px;
$spacer: $grid-size;
$spacers: (
0: 0,
- 1: ($spacer * .5),
+ 1: ($spacer * 0.5),
2: ($spacer),
3: ($spacer * 2),
4: ($spacer * 3),
- 5: ($spacer * 4)
+ 5: ($spacer * 4),
+ 6: ($spacer * 5),
+ 7: ($spacer * 6),
+ 8: ($spacer * 7),
+ 9: ($spacer * 8)
);
+$pagination-color: $gl-text-color;
diff --git a/app/assets/stylesheets/framework/vue_transitions.scss b/app/assets/stylesheets/framework/vue_transitions.scss
index e07a177e153..e3bdc0b0199 100644
--- a/app/assets/stylesheets/framework/vue_transitions.scss
+++ b/app/assets/stylesheets/framework/vue_transitions.scss
@@ -1,9 +1,13 @@
.fade-enter-active,
-.fade-leave-active {
+.fade-leave-active,
+.fade-in-enter-active,
+.fade-out-leave-active {
transition: opacity $sidebar-transition-duration $general-hover-transition-curve;
}
.fade-enter,
+.fade-in-enter,
+.fade-out-leave-to,
.fade-leave-to {
opacity: 0;
}
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 161943766d4..434cbd6d21c 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -12,6 +12,10 @@
border-bottom: 1px solid $well-inner-border;
}
+ &.borderless {
+ border-bottom: 0;
+ }
+
&.branch-info {
.commit-sha,
.commit-info {
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
index 2b0794759d5..ac3214a07d9 100644
--- a/app/assets/stylesheets/highlight/common.scss
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -1,4 +1,4 @@
-@import "../framework/variables";
+@import '../framework/variables';
@mixin diff-background($background, $idiff, $border) {
background: $background;
diff --git a/app/assets/stylesheets/highlight/embedded.scss b/app/assets/stylesheets/highlight/embedded.scss
index 44c8a1d39ec..74364ee4ddb 100644
--- a/app/assets/stylesheets/highlight/embedded.scss
+++ b/app/assets/stylesheets/highlight/embedded.scss
@@ -1,3 +1,3 @@
.code {
- @import "white_base";
+ @import 'white_base';
}
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index 23ec3380ce9..ee0ec94c636 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -1,6 +1,6 @@
/* https://github.com/aahan/pygments-github-style */
-@import "./common";
+@import './common';
/*
* White Syntax Colors
@@ -37,16 +37,16 @@ $white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
-$white-na: teal;
+$white-na: #008080;
$white-nb: #0086b3;
$white-nc: #458;
-$white-no: teal;
-$white-ni: purple;
+$white-no: #008080;
+$white-ni: #800080;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
-$white-nt: navy;
-$white-nv: teal;
+$white-nt: #000080;
+$white-nv: #008080;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
@@ -64,9 +64,9 @@ $white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
-$white-vc: teal;
-$white-vg: teal;
-$white-vi: teal;
+$white-vc: #008080;
+$white-vg: #008080;
+$white-vi: #008080;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@@ -77,7 +77,7 @@ $white-gc-bg: #eaf2f5;
background-color: $gray-light;
}
- // Line numbers
+// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
@@ -103,7 +103,6 @@ pre.code,
// Diff line
.line_holder {
-
&.match .line_content,
.new-nonewline.line_content,
.old-nonewline.line_content {
@@ -201,25 +200,38 @@ pre .hll {
background-color: $white-pre-hll-bg !important;
}
- // Search result highlight
+// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
- // Links to URLs, emails, or dependencies
+// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
-.c { color: $white-c; font-style: italic; }
-.err { color: $white-err; background-color: $white-err-bg; }
+
+.c { color: $white-c;
+ font-style: italic; }
+
+.err { color: $white-err;
+ background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
-.cm { color: $white-cm; font-style: italic; }
-.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
-.c1 { color: $white-c1; font-style: italic; }
-.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.cm { color: $white-cm;
+ font-style: italic; }
+
+.cp { color: $white-cp;
+ font-weight: $gl-font-weight-bold; }
+
+.c1 { color: $white-c1;
+ font-style: italic; }
+
+.cs { color: $white-cs;
+ font-weight: $gl-font-weight-bold;
+ font-style: italic; }
.gd {
color: $white-gd;
@@ -248,24 +260,34 @@ span.highlight_word {
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
-.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
+
+.gu { color: $white-gu;
+ font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
-.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
+
+.kt { color: $white-kt;
+ font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
-.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
+
+.nc { color: $white-nc;
+ font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
-.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
-.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
+
+.ne { color: $white-ne;
+ font-weight: $gl-font-weight-bold; }
+
+.nf { color: $white-nf;
+ font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
@@ -291,4 +313,6 @@ span.highlight_word {
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
-.gc { color: $white-gc-color; background-color: $white-gc-bg; }
+
+.gc { color: $white-gc-color;
+ background-color: $white-gc-bg; }
diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
index 8b234a5a656..33c114838c2 100644
--- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss
+++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
@@ -1,4 +1,4 @@
-@import "framework/variables";
+@import 'framework/variables';
// This file is largely copied from `highlight/white.scss`, but modified to
// avoid all descendant selectors (`table td`). This is because the CSS inlining
@@ -40,16 +40,16 @@ $highlighted-kt: #458;
$highlighted-m: #099;
$highlighted-s: #d14;
$highlighted-n: #333;
-$highlighted-na: teal;
+$highlighted-na: #008080;
$highlighted-nb: #0086b3;
$highlighted-nc: #458;
-$highlighted-no: teal;
-$highlighted-ni: purple;
+$highlighted-no: #008080;
+$highlighted-ni: #800080;
$highlighted-ne: #900;
$highlighted-nf: #900;
$highlighted-nn: #555;
-$highlighted-nt: navy;
-$highlighted-nv: teal;
+$highlighted-nt: #000080;
+$highlighted-nv: #008080;
$highlighted-w: #bbb;
$highlighted-mf: #099;
$highlighted-mh: #099;
@@ -67,9 +67,9 @@ $highlighted-sr: #009926;
$highlighted-s1: #d14;
$highlighted-ss: #990073;
$highlighted-bp: #999;
-$highlighted-vc: teal;
-$highlighted-vg: teal;
-$highlighted-vi: teal;
+$highlighted-vc: #008080;
+$highlighted-vg: #008080;
+$highlighted-vi: #008080;
$highlighted-il: #099;
$highlighted-gc: #999;
$highlighted-gc-bg: #eaf2f5;
@@ -151,14 +151,27 @@ span.highlight_word {
}
.hll { background-color: $highlighted-hll-bg; }
-.c { color: $highlighted-c; font-style: italic; }
-.err { color: $highlighted-err; background-color: $highlighted-err-bg; }
+
+.c { color: $highlighted-c;
+ font-style: italic; }
+
+.err { color: $highlighted-err;
+ background-color: $highlighted-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
-.cm { color: $highlighted-cm; font-style: italic; }
-.cp { color: $highlighted-cp; font-weight: $gl-font-weight-bold; }
-.c1 { color: $highlighted-c1; font-style: italic; }
-.cs { color: $highlighted-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.cm { color: $highlighted-cm;
+ font-style: italic; }
+
+.cp { color: $highlighted-cp;
+ font-weight: $gl-font-weight-bold; }
+
+.c1 { color: $highlighted-c1;
+ font-style: italic; }
+
+.cs { color: $highlighted-cs;
+ font-weight: $gl-font-weight-bold;
+ font-style: italic; }
.gd {
color: $highlighted-gd;
@@ -187,24 +200,34 @@ span.highlight_word {
.go { color: $highlighted-go; }
.gp { color: $highlighted-gp; }
.gs { font-weight: $gl-font-weight-bold; }
-.gu { color: $highlighted-gu; font-weight: $gl-font-weight-bold; }
+
+.gu { color: $highlighted-gu;
+ font-weight: $gl-font-weight-bold; }
.gt { color: $highlighted-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
-.kt { color: $highlighted-kt; font-weight: $gl-font-weight-bold; }
+
+.kt { color: $highlighted-kt;
+ font-weight: $gl-font-weight-bold; }
.m { color: $highlighted-m; }
.s { color: $highlighted-s; }
.n { color: $highlighted-n; }
.na { color: $highlighted-na; }
.nb { color: $highlighted-nb; }
-.nc { color: $highlighted-nc; font-weight: $gl-font-weight-bold; }
+
+.nc { color: $highlighted-nc;
+ font-weight: $gl-font-weight-bold; }
.no { color: $highlighted-no; }
.ni { color: $highlighted-ni; }
-.ne { color: $highlighted-ne; font-weight: $gl-font-weight-bold; }
-.nf { color: $highlighted-nf; font-weight: $gl-font-weight-bold; }
+
+.ne { color: $highlighted-ne;
+ font-weight: $gl-font-weight-bold; }
+
+.nf { color: $highlighted-nf;
+ font-weight: $gl-font-weight-bold; }
.nn { color: $highlighted-nn; }
.nt { color: $highlighted-nt; }
.nv { color: $highlighted-nv; }
@@ -230,4 +253,6 @@ span.highlight_word {
.vg { color: $highlighted-vg; }
.vi { color: $highlighted-vi; }
.il { color: $highlighted-il; }
-.gc { color: $highlighted-gc; background-color: $highlighted-gc-bg; }
+
+.gc { color: $highlighted-gc;
+ background-color: $highlighted-gc-bg; }
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index f24c80bd81c..d77b7dfad68 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -1,4 +1,4 @@
-@import "framework/variables";
+@import 'framework/variables';
img {
max-width: 100%;
diff --git a/app/assets/stylesheets/page_bundles/_ide_mixins.scss b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
index 896a3466cb4..9465dd5bed6 100644
--- a/app/assets/stylesheets/page_bundles/_ide_mixins.scss
+++ b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
@@ -2,17 +2,17 @@
display: flex;
flex-direction: column;
height: 100%;
- margin-top: -$grid-size;
- margin-bottom: -$grid-size;
- &.build-page .top-bar {
+ .top-bar {
+ @include build-trace-bar(35px);
+
top: 0;
- height: auto;
font-size: 12px;
border-top-right-radius: $border-radius-default;
- }
-
- .top-bar {
margin-left: -$gl-padding;
+
+ .controllers {
+ @include build-controllers(15px, center, false, 0, inline, 0);
+ }
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index a80158943c6..f08fa80495d 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -179,6 +179,14 @@ $ide-commit-header-height: 48px;
display: none;
}
+ .monaco-editor .selected-text {
+ z-index: 1;
+ }
+
+ .monaco-editor .view-lines {
+ z-index: 2;
+ }
+
.is-readonly,
.editor.original {
.view-lines {
@@ -711,7 +719,7 @@ $ide-commit-header-height: 48px;
border: 1px solid $white-dark;
}
-.ide-commit-radios {
+.ide-commit-options {
label {
font-weight: normal;
diff --git a/app/assets/stylesheets/page_bundles/xterm.scss b/app/assets/stylesheets/page_bundles/xterm.scss
index 7f040ac9b96..de3f2a1177d 100644
--- a/app/assets/stylesheets/page_bundles/xterm.scss
+++ b/app/assets/stylesheets/page_bundles/xterm.scss
@@ -6,11 +6,11 @@
$black: #000;
$red: #ea1010;
- $green: #009900;
- $yellow: #999900;
+ $green: #090;
+ $yellow: #990;
$blue: #0073e6;
$magenta: #d411d4;
- $cyan: #009999;
+ $cyan: #099;
$white: #ccc;
$l-black: #373b41;
$l-red: #ff6161;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index a9324ba2ed0..5e3652db48f 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -1,9 +1,4 @@
-[v-cloak] {
- display: none;
-}
-
.user-can-drag {
- cursor: -webkit-grab;
cursor: grab;
}
@@ -17,13 +12,13 @@
-ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging
- cursor: -webkit-grabbing !important;
cursor: grabbing !important;
}
}
.is-ghost {
opacity: 0.3;
+ pointer-events: none;
}
.dropdown-projects {
@@ -36,19 +31,15 @@
width: 320px;
.dropdown-content {
- max-height: 162px;
+ max-height: 140px;
}
}
.issue-board-dropdown-content {
- margin: 0 8px 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid $dropdown-divider-bg;
-
- > p {
- margin: 0;
- font-size: 14px;
- }
+ margin: 0;
+ padding: $gl-padding-4 $gl-padding $gl-padding;
+ border-bottom: 0;
+ color: $gl-text-color-secondary;
}
.issue-boards-page {
@@ -58,8 +49,6 @@
}
.boards-app {
- position: relative;
-
@include media-breakpoint-up(sm) {
transition: width $sidebar-transition-duration;
width: 100%;
@@ -70,17 +59,9 @@
}
}
-.boards-app-loading {
- width: 100%;
- font-size: 34px;
-}
-
.boards-list {
height: calc(100vh - #{$issue-board-list-difference-xs});
- width: 100%;
- padding: $gl-padding ($gl-padding / 2);
overflow-x: scroll;
- white-space: nowrap;
min-height: 200px;
@include media-breakpoint-only(sm) {
@@ -105,13 +86,7 @@
}
.board {
- display: inline-block;
width: calc(85vw - 15px);
- height: 100%;
- padding-right: ($gl-padding / 2);
- padding-left: ($gl-padding / 2);
- white-space: normal;
- vertical-align: top;
@include media-breakpoint-up(sm) {
width: 400px;
@@ -126,23 +101,7 @@
&.is-collapsed {
width: 50px;
- .board-header {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
-
- button {
- display: none;
- }
- }
-
.board-title {
- padding: 0;
- border-bottom: 0;
- justify-content: center;
-
> span {
width: 100%;
margin-top: -12px;
@@ -158,34 +117,16 @@
left: 50%;
margin-left: -10px;
}
-
- .board-list-component,
- .issue-count-badge {
- display: none;
- }
- }
-
- &:not(.is-collapsed) {
- .board-list-component {
- display: flex;
- flex-direction: column;
- }
}
}
.board-inner {
- position: relative;
- height: 100%;
font-size: $issue-boards-font-size;
background: $gray-light;
border: 1px solid $border-color;
- border-radius: $border-radius-default;
- flex: 1;
}
.board-header {
- position: relative;
-
&.has-border::before {
border-top: 3px solid;
border-color: inherit;
@@ -210,30 +151,19 @@
}
}
-.board-inner-container {
- border-bottom: 1px solid $border-color;
- padding: $gl-padding;
-}
-
.board-title {
- margin: 0;
- padding: $gl-padding-8 $gl-padding;
font-size: 1em;
border-bottom: 1px solid $border-color;
- display: flex;
- align-items: center;
}
.board-title-text {
- margin-right: auto;
+ margin: $gl-vert-padding auto $gl-vert-padding 0;
}
.board-delete {
margin-right: 10px;
- padding: 0;
color: $gray-darkest;
background-color: transparent;
- border: 0;
outline: 0;
&:hover {
@@ -241,8 +171,8 @@
}
}
-.board-blank-state {
- padding: $gl-padding;
+.board-blank-state,
+.board-promotion-state {
background-color: $white-light;
flex: 1;
overflow-y: auto;
@@ -250,35 +180,23 @@
}
.board-blank-state-list {
- list-style: none;
-
> li:not(:last-child) {
margin-bottom: 8px;
}
.label-color {
- position: relative;
top: 2px;
- display: inline-block;
width: 16px;
height: 16px;
margin-right: 3px;
- border-radius: $border-radius-default;
}
}
.board-list-component {
- position: relative;
- flex: 1;
min-height: 0; // firefox fix
}
.board-list {
- height: 100%;
- width: 100%;
- margin-bottom: 0;
- padding: $gl-padding-4;
- list-style: none;
overflow-y: auto;
overflow-x: hidden;
}
@@ -289,14 +207,11 @@
}
.board-card {
- position: relative;
- padding: $gl-padding;
background: $white-light;
- border-radius: $border-radius-default;
border: 1px solid $gray-200;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
- list-style: none;
line-height: $gl-padding;
+ list-style: none;
&:not(:last-child) {
margin-bottom: $gl-padding-8;
@@ -323,10 +238,6 @@
}
}
- svg {
- vertical-align: top;
- }
-
.confidential-icon {
color: $orange-600;
cursor: help;
@@ -351,11 +262,10 @@
}
.board-card-header {
- display: flex;
+ text-align: initial;
}
.board-card-assignee {
- display: flex;
margin-top: -$gl-padding-4;
margin-bottom: -$gl-padding-4;
@@ -415,34 +325,16 @@
.board-card-number {
font-size: $gl-font-size-xs;
color: $gl-text-color-secondary;
- overflow: hidden;
@include media-breakpoint-up(md) {
font-size: $label-font-size;
}
}
-.board-card-number-container {
- overflow: hidden;
-}
-
-.issue-boards-search {
- width: 395px;
-
- .form-control {
- display: inline-block;
- width: 210px;
- }
-}
-
.board-list-count {
padding: 10px 0;
color: $gl-text-color-secondary;
font-size: 13px;
-
- > .fa {
- margin-right: 5px;
- }
}
.board-new-issue-form {
@@ -450,16 +342,9 @@
margin: 5px;
}
-.page-with-contextual-sidebar.layout-page .issue-boards-sidebar {
- .issuable-sidebar-header {
- position: relative;
- }
-
+.issue-boards-sidebar {
.gutter-toggle {
- position: absolute;
- top: 0;
bottom: 15px;
- right: 0;
width: 22px;
color: $gray-darkest;
@@ -479,10 +364,6 @@
.issuable-header-text {
@include overflow-break-word();
padding-right: 35px;
-
- > strong {
- font-weight: $gl-font-weight-bold;
- }
}
}
@@ -501,51 +382,25 @@
}
.add-issues-modal {
- display: -webkit-flex;
- display: flex;
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
background-color: rgba($black, 0.3);
z-index: 9999;
}
.add-issues-container {
- display: -webkit-flex;
- display: flex;
- -webkit-flex-direction: column;
- flex-direction: column;
width: 90vw;
height: 85vh;
max-width: 1100px;
min-height: 500px;
- margin: auto;
padding: 25px 15px 0;
background-color: $white-light;
- border-radius: $border-radius-default;
box-shadow: 0 2px 12px rgba($black, 0.5);
.empty-state {
- display: -webkit-flex;
- display: flex;
- -webkit-flex: 1;
- flex: 1;
- margin-top: 0;
-
&.add-issues-empty-state-filter {
- -webkit-flex-direction: column;
flex-direction: column;
- -webkit-justify-content: center;
justify-content: center;
}
- > .row {
- width: 100%;
- margin: auto 0;
- }
-
.svg-content {
margin-top: -40px;
}
@@ -554,27 +409,15 @@
.add-issues-header {
margin: -25px -15px -5px;
- border-top: 0;
border-bottom: 1px solid $border-color;
border-top-right-radius: $border-radius-default;
border-top-left-radius: $border-radius-default;
> h2 {
- margin: 0;
font-size: 18px;
}
}
-.add-issues-search {
- display: -webkit-flex;
- display: flex;
-
- .issues-filters {
- -webkit-flex: 1;
- flex: 1;
- }
-}
-
.add-issues-list-column {
width: 100%;
@@ -588,10 +431,6 @@
}
.add-issues-list {
- display: -webkit-flex;
- display: flex;
- -webkit-flex: 1;
- flex: 1;
padding-top: 3px;
margin-left: -$gl-vert-padding;
margin-right: -$gl-vert-padding;
@@ -608,15 +447,6 @@
}
}
-.add-issues-list-loading {
- -webkit-align-self: center;
- align-self: center;
- width: 100%;
- padding-left: $gl-vert-padding;
- padding-right: $gl-vert-padding;
- font-size: 35px;
-}
-
.add-issues-footer {
margin: auto -15px 0;
padding-left: 15px;
@@ -644,27 +474,6 @@
border-radius: 50%;
}
-.modal-filters {
- display: flex;
-
- > .dropdown {
- display: none;
- margin-right: 10px;
-
- @include media-breakpoint-up(sm) {
- display: block;
- }
- }
-
- .dropdown-menu-toggle {
- width: 100px;
-
- @include media-breakpoint-up(md) {
- width: 140px;
- }
- }
-}
-
.board-card-info {
color: $gl-text-color-secondary;
white-space: nowrap;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index fa5a182243c..6fc742871e7 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -46,11 +46,6 @@
}
.build-page {
- .build-trace-container {
- position: relative;
- }
-
-
.build-trace {
@include build-trace();
}
@@ -105,18 +100,6 @@
top: 0;
}
- .truncated-info {
- .truncated-info-size {
- margin: 0 5px;
- }
-
- .raw-link {
- color: $gl-text-color;
- margin-left: 5px;
- text-decoration: underline;
- }
- }
-
.controllers {
@include build-controllers(15px, center, false, 0, inline, 0);
}
@@ -143,12 +126,6 @@
}
}
-.with-performance-bar .build-page {
- .top-bar.affix {
- top: $header-height + $performance-bar-height;
- }
-}
-
.build-header {
.ci-header-container,
.header-action-buttons {
@@ -234,7 +211,6 @@
}
.trigger-variables-btn-container {
- @extend .d-flex;
justify-content: space-between;
align-items: center;
@@ -278,12 +254,6 @@
.retry-link {
display: block;
- .btn {
- i {
- margin-left: 5px;
- }
- }
-
.btn-inverted-secondary {
color: $blue-500;
@@ -330,16 +300,12 @@
}
}
- .build-job {
- position: relative;
-
- .icon-arrow-right {
- position: absolute;
- left: 15px;
- top: 20px;
- display: block;
- }
+ .icon-arrow-right {
+ left: 15px;
+ top: 20px;
+ }
+ .build-job {
&.active {
font-weight: $gl-font-weight-bold;
}
@@ -351,10 +317,6 @@
&:hover {
background-color: $gray-darker;
}
-
- .icon-retry {
- margin-left: 3px;
- }
}
}
@@ -392,3 +354,14 @@
right: 0;
margin-top: -17px;
}
+
+@include media-breakpoint-down(sm) {
+ .top-bar {
+ .truncated-info {
+ white-space: nowrap;
+ overflow: hidden;
+ max-width: 220px;
+ text-overflow: ellipsis;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 809ba6d4953..255383d89c8 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -69,6 +69,8 @@
align-self: flex-start;
font-weight: 500;
font-size: 20px;
+ color: $orange-900;
+ opacity: 1;
margin: $gl-padding-8 14px 0 0;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 11966931a6c..77a36e59b03 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -58,14 +58,6 @@
display: inline-block;
vertical-align: middle;
- .stage-cell .stage-container {
- margin: 0 3px 3px 0;
- }
-
- .stage-container:last-child {
- margin-right: 0;
- }
-
.dropdown-menu {
margin-top: 11px;
}
@@ -128,18 +120,9 @@
}
.commit-row-title {
- .notes_count {
- float: right;
- margin-right: 10px;
- }
-
.str-truncated {
max-width: 70%;
}
-
- .commit-row-message {
- color: $gl-text-color;
- }
}
.text-expander {
@@ -171,8 +154,6 @@
}
.avatar-cell {
- width: 46px;
-
img {
margin-right: 0;
}
@@ -185,7 +166,7 @@
flex-grow: 1;
min-width: 0;
- .project_namespace {
+ .project-namespace {
color: $gl-text-color-secondary;
}
}
@@ -208,10 +189,6 @@
}
}
- .ci-status-link {
- display: inline-flex;
- }
-
.ci-status-icon svg {
vertical-align: text-bottom;
}
@@ -239,7 +216,6 @@
}
.label-monospace {
- @extend .monospace;
user-select: text;
color: $gl-text-color;
background-color: $gray-light;
@@ -266,7 +242,7 @@
}
.commit,
-.generic_commit_status {
+.generic-commit-status {
a,
button {
vertical-align: baseline;
@@ -278,37 +254,22 @@
&.autodevops-badge {
color: $white-light;
}
-
- &.autodevops-link {
- color: $blue-600;
- }
}
.commit-row-description {
@extend %commit-description-base;
display: none;
flex: 1;
-
- a {
- color: $gl-text-color;
- }
}
&.inline-commit {
.commit-row-title {
font-size: 13px;
}
-
- .committed_ago {
- @extend .cgray;
- float: right;
- }
}
}
.branch-commit {
- color: $gl-text-color;
-
.commit-icon {
text-align: center;
display: inline-block;
@@ -320,14 +281,15 @@
fill: $gl-text-color-secondary;
}
}
+}
+.commit,
+.generic-commit-status,
+.branch-commit {
+ .autodevops-link,
.commit-sha {
color: $blue-600;
}
-
- .commit-row-message {
- color: $gl-text-color;
- }
}
.gpg-status-box {
@@ -342,11 +304,11 @@
}
&.invalid {
- @include status-color($gray-dark, color("gray"), $gray-darkest);
+ @include status-color($gray-dark, color('gray'), $gray-darkest);
border-color: $gray-darkest;
&:not(span):hover {
- color: color("gray");
+ color: color('gray');
}
}
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index ec2108b15be..2b932d164a5 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -330,7 +330,6 @@
// Custom Styles for stage items
.item-build-component {
-
.item-title {
.icon-build-status {
float: left;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index cb5f1a84005..c386493231c 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -21,7 +21,6 @@
.detail-page-header-body {
position: relative;
- line-height: 35px;
display: flex;
flex: 1 1;
min-width: 0;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index d001dff7986..3b0d740def3 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -7,7 +7,9 @@
cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) {
- $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height;
+ // The `-1` below is to prevent two borders from clashing up against eachother -
+ // the bottom of the compare-versions header and the top of the file header
+ $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height - 1;
position: -webkit-sticky;
position: sticky;
@@ -54,6 +56,11 @@
background-color: $gray-normal;
}
+ a,
+ button {
+ color: $gray-700;
+ }
+
svg {
vertical-align: middle;
top: -1px;
@@ -85,138 +92,6 @@
}
}
- .note-text {
- table {
- font-family: $font-family-sans-serif;
- }
- }
-
- table {
- width: 100%;
- font-family: $monospace-font;
- border: 0;
- border-collapse: separate;
- margin: 0;
- padding: 0;
- table-layout: fixed;
- border-radius: 0 0 $border-radius-default $border-radius-default;
-
- .diff-line-num {
- width: 50px;
- position: relative;
-
- a {
- transition: none;
- }
- }
-
- .line_holder td {
- line-height: $code-line-height;
- font-size: $code-font-size;
- vertical-align: top;
-
- &.noteable_line {
- position: relative;
- }
-
- span {
- white-space: pre-wrap;
-
- &.context-cell {
- display: inline-block;
- width: 100%;
- height: 100%;
- }
- }
-
- .line {
- word-wrap: break-word;
- }
- }
-
- &.left-side-selected {
- td.line_content.parallel.right-side {
- user-select: none;
- }
- }
-
- &.right-side-selected {
- td.line_content.parallel.left-side {
- user-select: none;
- }
- }
- }
-
- tr.line_holder.parallel {
- td.line_content.parallel {
- width: 46%;
- }
-
- .add-diff-note {
- margin-left: -55px;
- }
- }
-
- .old_line,
- .new_line {
- user-select: none;
- margin: 0;
- border: 0;
- padding: 0 5px;
- border-right: 1px solid;
- text-align: right;
- min-width: 35px;
- max-width: 50px;
- width: 35px;
-
- a {
- float: left;
- width: 35px;
- font-weight: $gl-font-weight-normal;
-
- &[disabled] {
- cursor: default;
-
- &:hover,
- &:active {
- text-decoration: none;
- }
- }
- }
- }
-
- .line_content {
- display: block;
- margin: 0;
- padding: 0 1.5em;
- border: 0;
- position: relative;
-
- &.parallel {
- display: table-cell;
-
- span {
- word-break: break-all;
- }
- }
-
- &.old {
- &::before {
- content: '-';
- position: absolute;
- left: 0.5em;
- }
- }
-
- &.new {
- &::before {
- content: '+';
- position: absolute;
- left: 0.5em;
- }
- }
- }
-
.diff-loading-error-block {
padding: $gl-padding * 2 $gl-padding;
text-align: center;
@@ -239,22 +114,18 @@
img {
border: 1px solid $white-light;
- background-image: linear-gradient(
- 45deg,
- $border-color 25%,
- transparent 25%,
- transparent 75%,
- $border-color 75%,
- $border-color 100%
- ),
- linear-gradient(
- 45deg,
- $border-color 25%,
- transparent 25%,
- transparent 75%,
- $border-color 75%,
- $border-color 100%
- );
+ background-image: linear-gradient(45deg,
+ $border-color 25%,
+ transparent 25%,
+ transparent 75%,
+ $border-color 75%,
+ $border-color 100%),
+ linear-gradient(45deg,
+ $border-color 25%,
+ transparent 25%,
+ transparent 75%,
+ $border-color 75%,
+ $border-color 100%);
background-size: 10px 10px;
background-position: 0 0, 5px 5px;
max-width: 100%;
@@ -286,11 +157,34 @@
.swipe-wrap {
overflow: hidden;
- border-left: 1px solid $gl-gray-400;
+ border-right: 1px solid $gl-gray-400;
position: absolute;
display: block;
top: 13px;
right: 7px;
+
+ &.left-oriented {
+ /* only for commit view (different swipe viewer) */
+ border-right: 0;
+ border-left: 1px solid $gl-gray-400;
+ }
+ }
+
+ .frame {
+ top: 0;
+ right: 0;
+
+ &.old-diff {
+ /* only for commit / compare view */
+ position: absolute;
+ }
+
+ &.deleted {
+ margin: 0;
+ display: block;
+ top: 13px;
+ right: 7px;
+ }
}
.swipe-bar {
@@ -443,10 +337,6 @@
}
}
- .line_content {
- white-space: pre-wrap;
- }
-
.diff-file-container {
.frame.deleted {
border: 1px solid $deleted;
@@ -508,12 +398,126 @@
}
}
+table.code {
+ width: 100%;
+ font-family: $monospace-font;
+ border: 0;
+ border-collapse: separate;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
+
+ tr.line_holder td {
+ line-height: $code-line-height;
+ font-size: $code-font-size;
+ vertical-align: top;
+
+ span {
+ white-space: pre-wrap;
+
+ &.context-cell {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ }
+
+ &.line {
+ word-wrap: break-word;
+ }
+ }
+
+ &.diff-line-num {
+ user-select: none;
+ margin: 0;
+ padding: 0 10px 0 5px;
+ border-right-width: 1px;
+ border-right-style: solid;
+ text-align: right;
+ width: 50px;
+ position: relative;
+
+ a {
+ transition: none;
+ float: left;
+ width: 100%;
+ font-weight: $gl-font-weight-normal;
+
+ &[disabled] {
+ cursor: default;
+
+ &:hover,
+ &:active {
+ text-decoration: none;
+ }
+ }
+ }
+
+ &:not(.js-unfold-bottom) a::before {
+ content: attr(data-linenumber);
+ }
+ }
+
+ &.line_content {
+ display: block;
+ margin: 0;
+ padding: 0 1.5em;
+ border: 0;
+ position: relative;
+ white-space: pre-wrap;
+
+ &.parallel {
+ display: table-cell;
+ width: 46%;
+
+ span {
+ word-break: break-all;
+ }
+ }
+
+ &.old {
+ &::before {
+ content: '-';
+ position: absolute;
+ left: 0.5em;
+ }
+ }
+
+ &.new {
+ &::before {
+ content: '+';
+ position: absolute;
+ left: 0.5em;
+ }
+ }
+ }
+ }
+
+ .line_holder:last-of-type {
+ td:first-child {
+ border-bottom-left-radius: $border-radius-default;
+ }
+ }
+
+ &.left-side-selected {
+ td.line_content.parallel.right-side {
+ user-select: none;
+ }
+ }
+
+ &.right-side-selected {
+ td.line_content.parallel.left-side {
+ user-select: none;
+ }
+ }
+}
+
.diff-stats {
align-items: center;
- padding: 0 .25rem;
+ padding: 0 0.25rem;
.diff-stats-group {
- padding: 0 .25rem;
+ padding: 0 0.25rem;
}
svg.diff-stats-icon {
@@ -522,7 +526,7 @@
&.is-compare-versions-header {
.diff-stats-group {
- padding: 0 .5rem;
+ padding: 0 0.5rem;
}
}
}
@@ -608,22 +612,11 @@
}
}
-.file-holder {
- .diff-line-num:not(.js-unfold-bottom) {
- a {
- &::before {
- content: attr(data-linenumber);
- }
- }
- }
-}
-
.diff-comment-avatar-holders {
position: absolute;
- height: 19px;
- width: 19px;
- margin-left: -15px;
+ margin-left: -$gl-padding;
z-index: 100;
+ @include code-icon-size();
&:hover {
.diff-comment-avatar,
@@ -657,26 +650,28 @@
.diff-comments-more-count {
position: absolute;
left: 0;
- width: 19px;
- height: 19px;
margin-right: 0;
border-color: $white-light;
cursor: pointer;
transition: all 0.1s ease-out;
+ @include code-icon-size();
@for $i from 1 through 4 {
&:nth-child(#{$i}) {
z-index: (4 - $i);
}
}
+
+ .avatar {
+ @include code-icon-size();
+ }
}
.diff-comments-more-count {
- width: 19px;
- min-width: 19px;
padding-left: 0;
padding-right: 0;
overflow: hidden;
+ @include code-icon-size();
}
.diff-comments-more-count,
@@ -685,12 +680,15 @@
}
.diff-notes-collapse {
- width: 24px;
- height: 24px;
+ border: 0;
border-radius: 50%;
padding: 0;
transition: transform 0.1s ease-out;
z-index: 100;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ @include code-icon-size();
.collapse-icon {
height: 50%;
@@ -834,34 +832,26 @@
width: 100%;
height: 10px;
background-color: $white-light;
- background-image: linear-gradient(
- 45deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- 225deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- 135deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- -45deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- );
+ background-image: linear-gradient(45deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(225deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(135deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(-45deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%);
background-position: 5px 5px, 0 5px, 0 5px, 5px 5px;
background-size: 10px 10px;
background-repeat: repeat;
@@ -904,7 +894,7 @@
}
}
-.files:not([data-can-create-note="true"]) .frame {
+.files:not([data-can-create-note='true']) .frame {
cursor: auto;
}
@@ -913,15 +903,14 @@
.btn-transparent.image-diff-overlay-add-comment {
position: relative;
cursor: image-url('illustrations/image_comment_light_cursor.svg')
- $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
auto;
// Retina cursor
- cursor: -webkit-image-set(
- image-url('illustrations/image_comment_light_cursor.svg') 1x,
- image-url('illustrations/image_comment_light_cursor@2x.svg') 2x
- )
- $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
+ // scss-lint:disable DuplicateProperty
+ cursor: image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x,
+ image-url('illustrations/image_comment_light_cursor@2x.svg') 2x)
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
auto;
.comment-indicator {
@@ -1019,6 +1008,10 @@
display: block;
}
}
+
+ .note-edit-form {
+ margin-left: $note-icon-gutter-width;
+ }
}
.discussion-body .image .frame {
@@ -1106,7 +1099,10 @@
flex-direction: column;
.diff-tree-list {
- width: 100%;
+ position: relative;
+ top: 0;
+ // !important is required to override inline styles of resizable sidebar
+ width: 100% !important;
}
.tree-list-holder {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 61ecf133b02..93dffb5ff09 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -12,34 +12,6 @@
.environments-container {
.ci-table {
- .deployment-column {
- > span {
- word-break: break-all;
- }
-
- .avatar {
- float: none;
- }
- }
-
- .btn-group {
- > .btn:not(.btn-danger) {
- color: $gl-text-color-secondary;
- }
-
- svg path {
- fill: $gl-text-color-secondary;
- }
-
- .dropdown {
- outline: none;
- }
- }
-
- .btn .text-center {
- display: inline;
- }
-
.commit-title {
margin: 0;
}
@@ -49,47 +21,16 @@
color: $gl-text-color-secondary;
}
- .dropdown-menu {
- .fa {
- margin-right: 6px;
- color: $gl-text-color-secondary;
- }
- }
-
.build-link,
.ref-name {
color: $gl-text-color;
}
- .stop-env-link,
- .external-url {
- color: $gl-text-color-secondary;
-
- .stop-env-icon {
- font-size: 14px;
- }
- }
-
- .deployment .build-column {
- .build-link {
- color: $gl-text-color;
- }
-
- .avatar {
- float: none;
- margin-right: 0;
- }
- }
-
.folder-icon {
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
vertical-align: text-top;
-
- .fa:nth-child(1) {
- margin-right: 3px;
- }
}
.folder-name {
@@ -103,12 +44,6 @@
text-align: center;
}
- .branch-commit {
- .commit-sha {
- margin-right: 0;
- }
- }
-
.no-btn {
border: 0;
background: none;
@@ -168,11 +103,6 @@
opacity: 0.25;
}
-.prometheus-graph-overlay {
- fill: none;
- opacity: 0;
- pointer-events: all;
-}
.rect-text-metric {
fill: $white-light;
@@ -203,274 +133,10 @@
stroke: $gray-darkest;
}
-.prometheus-graphs {
- .environments {
- .dropdown-menu-toggle {
- svg {
- position: absolute;
- right: 5%;
- top: 25%;
- }
- }
-
- .dropdown-menu-toggle,
- .dropdown-menu {
- width: 240px;
- }
- }
-}
-
.environments-actions {
.external-url,
.monitoring-url,
- .terminal-button,
- .stop-env-link {
+ .terminal-button {
width: 38px;
}
}
-
-.prometheus-panel {
- margin-top: 20px;
-}
-
-.prometheus-graph-group {
- display: flex;
- flex-wrap: wrap;
- padding: $gl-padding / 2;
-}
-
-.prometheus-graph {
- padding: $gl-padding / 2;
-}
-
-.prometheus-graph-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: $gl-padding-8;
-
- h5 {
- font-size: $gl-font-size-large;
- margin: 0;
- }
-}
-
-.prometheus-graph-cursor {
- position: absolute;
- background: $gray-600;
- width: 1px;
-}
-
-.prometheus-graph-flag {
- display: block;
- min-width: 160px;
- border: 0;
- box-shadow: 0 1px 4px 0 $black-transparent;
-
- h5 {
- padding: 0;
- margin: 0;
- font-size: 14px;
- line-height: 1.2;
- }
-
- .deploy-meta-content {
- border-bottom: 1px solid $white-dark;
-
- svg {
- height: 15px;
- vertical-align: bottom;
- }
- }
-
- &.popover {
- padding: 0;
-
- &.left {
- left: auto;
- right: 0;
- margin-right: 10px;
-
- > .arrow {
- right: -14px;
- border-left-color: $border-color;
- }
-
- > .arrow::after {
- border-top: 6px solid transparent;
- border-bottom: 6px solid transparent;
- border-left: 4px solid $gray-50;
- }
-
- .arrow-shadow {
- right: -3px;
- box-shadow: 1px 0 9px 0 $black-transparent;
- }
- }
-
- &.right {
- left: 0;
- right: auto;
- margin-left: 10px;
-
- > .arrow {
- left: -7px;
- border-right-color: $border-color;
- }
-
- > .arrow::after {
- border-top: 6px solid transparent;
- border-bottom: 6px solid transparent;
- border-right: 4px solid $gray-50;
- }
-
- .arrow-shadow {
- left: -3px;
- box-shadow: 1px 0 8px 0 $black-transparent;
- }
- }
-
- > .arrow {
- top: 10px;
- margin: 0;
- }
-
- .arrow-shadow {
- content: '';
- position: absolute;
- width: 7px;
- height: 7px;
- background-color: transparent;
- transform: rotate(45deg);
- top: 13px;
- }
-
- > .popover-title,
- > .popover-content {
- padding: 8px;
- font-size: 12px;
- white-space: nowrap;
- position: relative;
- }
-
- > .popover-title {
- background-color: $gray-50;
- border-radius: $border-radius-default $border-radius-default 0 0;
- }
- }
-
- strong {
- font-weight: 600;
- }
-}
-
-.prometheus-table {
- border-collapse: collapse;
- padding: 0;
- margin: 0;
-
- td {
- vertical-align: middle;
-
- + td {
- padding-left: 8px;
- vertical-align: top;
- }
- }
-
- .legend-metric-title {
- font-size: 12px;
- vertical-align: middle;
- }
-}
-
-.prometheus-svg-container {
- position: relative;
- height: 0;
- width: 100%;
- padding: 0;
- padding-bottom: 100%;
-
- .text-metric-usage {
- fill: $black;
- font-weight: $gl-font-weight-normal;
- font-size: 12px;
- }
-
- > svg {
- position: absolute;
- height: 100%;
- width: 100%;
- left: 0;
- top: 0;
-
- text {
- fill: $gl-text-color;
- stroke-width: 0;
- }
-
- .text-metric-bold {
- font-weight: $gl-font-weight-bold;
- }
-
- .label-axis-text {
- fill: $black;
- font-weight: $gl-font-weight-normal;
- font-size: 10px;
- }
-
- .legend-axis-text {
- fill: $black;
- }
-
- .tick {
- > line {
- stroke: $gray-darker;
- }
-
- > text {
- fill: $gray-600;
- font-size: 10px;
- }
- }
-
- .y-label-text,
- .x-label-text {
- fill: $gray-darkest;
- }
-
- .axis-tick {
- stroke: $gray-darker;
- }
-
- .deploy-info-text {
- dominant-baseline: text-before-edge;
- font-size: 12px;
- }
-
- .deploy-info-text-link {
- font-family: $monospace-font;
- fill: $blue-600;
-
- &:hover {
- fill: $blue-800;
- }
- }
-
- @include media-breakpoint-down(sm) {
- .label-axis-text,
- .text-metric-usage,
- .legend-axis-text {
- font-size: 8px;
- }
-
- .tick > text {
- font-size: 8px;
- }
- }
- }
-}
-
-.prometheus-table-row-highlight {
- background-color: $gray-100;
-}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 618f23d81b1..500f5816d38 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -8,7 +8,7 @@
border-bottom: 1px solid $white-normal;
color: $gl-text-color-secondary;
position: relative;
- line-height: $gl-line-height;
+ line-height: $gl-line-height-20;
.system-note-image {
position: absolute;
@@ -48,7 +48,7 @@
}
.event-user-info {
- margin-bottom: $gl-padding-8;
+ margin-bottom: $gl-padding-4;
.author_name {
a {
@@ -67,7 +67,7 @@
}
.event-body {
- margin-top: $gl-padding-8;
+ margin-top: $gl-padding-4;
margin-right: 174px;
color: $gl-text-color;
@@ -156,6 +156,10 @@
&:hover {
background: none;
}
+
+ a {
+ color: $blue-600;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 83b1680512d..3febf4cf826 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -71,12 +71,10 @@
.svg-graph-container-with-grab {
cursor: grab;
- cursor: -webkit-grab;
}
.svg-graph-container-grabbed {
cursor: grabbing;
- cursor: -webkit-grabbing;
}
@keyframes flickerAnimation {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 8ade995525a..656202f4e58 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -15,6 +15,11 @@
word-wrap: nowrap;
}
+.content-list .group-name {
+ font-weight: $gl-font-weight-bold;
+ color: $pages-group-name-color;
+}
+
.group-row {
@include basic-list-stats;
@@ -30,9 +35,6 @@
}
.group-nav-container .nav-controls {
- align-items: flex-start;
- padding: $gl-padding-top 0 0;
-
.group-filter-form {
flex: 1 1 auto;
margin-right: $gl-padding-8;
@@ -172,6 +174,50 @@
}
}
+.card {
+ .shared_runners_limit_under_quota {
+ color: $green-500;
+ }
+
+ .shared_runners_limit_over_quota {
+ color: $red-500;
+ }
+}
+
+.pipeline-quota {
+ border-top: 1px solid $table-border-color;
+ border-bottom: 1px solid $table-border-color;
+ margin: 0 0 $gl-padding;
+
+ .row {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+
+ .right {
+ text-align: right;
+ }
+
+ .progress {
+ height: 6px;
+ width: 100%;
+ margin-bottom: 0;
+ margin-top: 4px;
+ }
+}
+
+.user-settings-pipeline-quota {
+ margin-top: $gl-padding;
+
+ .pipeline-quota {
+ border-top: 0;
+ }
+}
+
+table.pipeline-project-metrics tr td {
+ padding: $gl-padding;
+}
+
.mattermost-icon svg {
width: 16px;
height: 16px;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 2c23f31c240..7610c5cf6f3 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -30,19 +30,11 @@
.key {
@extend .badge.badge-pill;
background-color: $label-inverse-bg;
- font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace;
padding: 3px 5px;
}
}
.documentation {
padding: 7px;
-
- // Border around images in the help pages.
- img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- margin: 5px;
- max-height: calc(100vh - 100px);
- }
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 7f800367cad..74f80a11471 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -18,8 +18,6 @@
}
.import-namespace-select {
- width: auto !important;
-
> .select2-choice {
border-radius: $border-radius-default 0 0 $border-radius-default;
position: relative;
@@ -49,3 +47,15 @@
.import-projects-loading-icon {
margin-top: $gl-padding-32;
}
+
+.btn-import {
+ .loading-icon {
+ display: none;
+ }
+
+ &.is-loading {
+ .loading-icon {
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e0bdc1341b1..79282f9043c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,8 +1,3 @@
-// Limit MR description for side-by-side diff view
-.fixed-width-container {
- @include fixed-width-container;
-}
-
.issuable-warning-icon {
background-color: $orange-100;
border-radius: $border-radius-default;
@@ -22,11 +17,12 @@
.detail-page-header,
.page-content-header,
.commit-box,
+ .info-well,
.commit-ci-menu,
.files-changed-inner,
.limited-header-width,
.limited-width-notes {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
.issuable-details {
@@ -34,13 +30,13 @@
.mr-source-target,
.mr-state-widget,
.merge-manually {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
.merge-request-details {
.emoji-list-container {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
}
@@ -64,6 +60,7 @@
overflow-wrap: break-word;
min-width: 0;
width: 100%;
+ text-align: initial;
}
.btn-edit {
@@ -71,16 +68,12 @@
height: $gl-padding * 2;
}
- // Border around images in issue and MR descriptions.
- .description img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- max-height: calc(100vh - 100px);
- max-width: 100%;
- }
-
.emoji-block {
- padding: 10px 0;
+ padding: $gl-padding-4 0;
+
+ @include media-breakpoint-down(md) {
+ padding: $gl-padding-8 0;
+ }
}
}
@@ -117,6 +110,20 @@
font-size: 0;
margin-bottom: -5px;
}
+
+ .scoped-label-wrapper {
+ > a {
+ max-width: 100%;
+ }
+
+ .color-label {
+ padding-right: $gl-padding-24;
+ }
+
+ .scoped-label {
+ right: 12px;
+ }
+ }
}
.right-sidebar {
@@ -129,6 +136,10 @@
z-index: 200;
overflow: hidden;
+ @include media-breakpoint-down(sm) {
+ z-index: 251;
+ }
+
a:not(.btn) {
color: inherit;
@@ -136,7 +147,7 @@
color: $blue-800;
.avatar {
- border-color: rgba($gray-normal, .2);
+ border-color: rgba($gray-normal, 0.2);
}
}
@@ -215,7 +226,7 @@
.title {
color: $gl-text-color;
- margin-bottom: 10px;
+ margin-bottom: $gl-padding-8;
line-height: 1;
.avatar {
@@ -223,7 +234,7 @@
}
a.edit-link:not([href]):hover {
- color: rgba($gray-normal, .2);
+ color: rgba($gray-normal, 0.2);
}
.lock-edit, // uses same style, different js behaviour
@@ -263,6 +274,10 @@
.selectbox {
display: none;
+
+ &.show {
+ display: block;
+ }
}
.btn-clipboard:hover {
@@ -316,6 +331,7 @@
}
.no-value,
+ .btn-default-hover-link,
.btn-secondary-hover-link {
color: $gl-text-color-secondary;
}
@@ -596,10 +612,8 @@
.participants-list {
display: flex;
flex-wrap: wrap;
- margin: -7px;
}
-
.user-list {
display: flex;
flex-wrap: wrap;
@@ -607,7 +621,7 @@
.participants-author {
display: inline-block;
- padding: 7px;
+ padding: 0 $gl-padding-8 $gl-padding-8 0;
&:nth-of-type(7n) {
padding-right: 0;
@@ -634,7 +648,6 @@
.participants-more,
.user-list-more {
- margin-top: 5px;
margin-left: 5px;
a,
@@ -711,14 +724,11 @@
.issuable-list {
li {
-
.issue-box {
- display: -webkit-flex;
display: flex;
}
.issuable-info-container {
- -webkit-flex: 1;
flex: 1;
display: flex;
padding-right: $gl-padding;
@@ -726,6 +736,7 @@
.issuable-main-info {
flex: 1 auto;
margin-right: 10px;
+ min-width: 0;
.issue-weight-icon {
vertical-align: sub;
@@ -787,6 +798,7 @@
@media(max-width: map-get($grid-breakpoints, lg)-1) {
.task-status,
.issuable-due-date,
+ .issuable-weight,
.project-ref-path {
display: none;
}
@@ -813,7 +825,6 @@
.sidebar-collapsed-icon {
-
> .stopwatch-svg {
display: inline-block;
}
@@ -871,11 +882,11 @@
}
.help-state-toggle-enter-active {
- transition: all .8s ease;
+ transition: all 0.8s ease;
}
.help-state-toggle-leave-active {
- transition: all .5s ease;
+ transition: all 0.5s ease;
}
.help-state-toggle-enter,
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0037364978c..48289c8f381 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -58,8 +58,6 @@ form.edit-issue {
}
ul.related-merge-requests > li {
- display: -ms-flexbox;
- display: -webkit-flex;
display: flex;
align-items: center;
@@ -147,6 +145,11 @@ ul.related-merge-requests > li {
}
}
+.issues-footer {
+ padding-top: $gl-padding;
+ padding-bottom: 37px;
+}
+
.issues-nav-controls {
font-size: 0;
@@ -255,8 +258,15 @@ ul.related-merge-requests > li {
}
}
-.discussion-reply-holder .note-edit-form {
- display: block;
+.discussion-reply-holder {
+ .avatar-note-form-holder .note-edit-form {
+ display: block;
+ margin-left: $note-icon-gutter-width;
+
+ @include media-breakpoint-down(xs) {
+ margin-left: 0;
+ }
+ }
}
.issue-sort-dropdown {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 2372640277e..11e8a32389f 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -34,7 +34,7 @@
.dropdown-new-label {
.dropdown-content {
- max-height: 136px;
+ max-height: initial;
}
}
@@ -75,7 +75,7 @@
padding: 0;
margin-bottom: 0;
- > li:not(.empty-message):not(.is-not-draggable) {
+ > li:not(.empty-message):not(.no-border) {
background-color: $white-light;
margin-bottom: 5px;
display: flex;
@@ -92,16 +92,14 @@
opacity: 0.3;
}
- .prioritized-labels & {
+ .prioritized-labels:not(.is-not-draggable) & {
box-shadow: 0 1px 2px $issue-boards-card-shadow;
cursor: move;
- cursor: -webkit-grab;
- cursor: -moz-grab;
+ cursor: grab;
border: 0;
&:active {
- cursor: -webkit-grabbing;
- cursor: -moz-grabbing;
+ cursor: grabbing;
}
}
}
@@ -355,7 +353,7 @@
@media (max-width: map-get($grid-breakpoints, md)-1) {
.manage-labels-list {
- > li:not(.empty-message):not(.is-not-draggable) {
+ > li:not(.empty-message):not(.no-border) {
flex-wrap: wrap;
}
@@ -404,3 +402,67 @@
.priority-labels-empty-state .svg-content img {
max-width: $priority-label-empty-state-width;
}
+
+.scoped-label-tooltip-title {
+ color: $indigo-300;
+}
+
+.scoped-label-wrapper {
+ max-width: 100%;
+ vertical-align: top;
+
+ .badge {
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ }
+
+ &.label-link .color-label a {
+ color: inherit;
+ }
+
+ .color-label {
+ padding-right: $gl-padding-24;
+ max-width: 100%;
+ }
+
+ .scoped-label {
+ position: absolute;
+ top: 4px;
+ right: 8px;
+ padding: 0;
+ margin: 0;
+ line-height: $gl-line-height;
+ }
+
+ &.board-label {
+ .scoped-label {
+ top: 1px;
+ }
+ }
+}
+
+// Label inside title of Delete Label Modal
+.modal-header .page-title {
+ .scoped-label-wrapper {
+ .scoped-label {
+ line-height: 20px;
+ }
+
+ span.color-label {
+ padding-right: $gl-padding-24;
+ }
+ }
+}
+
+// Don't hide the overflow in system messages
+.system-note-message,
+.issuable-details,
+.md-preview-holder,
+.referenced-commands,
+.note-body {
+ .scoped-label-wrapper {
+ .badge {
+ overflow: initial;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 67d7a8175ac..d8aabecc036 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -21,13 +21,6 @@
color: $login-brand-holder-color;
}
- h1:first-child {
- font-weight: $gl-font-weight-normal;
- margin-bottom: 0.68em;
- margin-top: 0;
- font-size: 34px;
- }
-
h3 {
font-size: 22px;
}
@@ -49,8 +42,8 @@
.login-box,
.omniauth-container {
box-shadow: 0 0 0 1px $border-color;
- border-bottom-right-radius: $border-radius-small;
- border-bottom-left-radius: $border-radius-small;
+ border-bottom-right-radius: $border-radius;
+ border-bottom-left-radius: $border-radius;
padding: 15px;
.login-heading h3 {
@@ -80,7 +73,8 @@
.login-body {
font-size: 13px;
- input + p {
+ input + p,
+ input ~ p.field-validation {
margin-top: 5px;
}
@@ -95,7 +89,7 @@
}
.omniauth-container {
- border-radius: $border-radius-small;
+ border-radius: $border-radius;
font-size: 13px;
p {
@@ -120,7 +114,6 @@
}
.new-session-tabs {
- display: -webkit-flex;
display: flex;
box-shadow: 0 0 0 1px $border-color;
border-top-right-radius: $border-radius-default;
@@ -190,7 +183,7 @@
margin-top: 16px;
}
- input[type="submit"] {
+ input[type='submit'] {
@extend .btn-block;
margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 99609a96976..68af01f9ccc 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -14,20 +14,14 @@
}
.member {
- .list-item-name {
- @include media-breakpoint-up(sm) {
- float: left;
- width: 50%;
- }
-
- strong {
- font-weight: $gl-font-weight-bold;
+ &.is-overridden {
+ .btn-ldap-override {
+ display: none !important;
}
}
.controls {
@include media-breakpoint-up(sm) {
- display: -webkit-flex;
display: flex;
}
@@ -38,10 +32,11 @@
.form-group {
margin-bottom: 0;
+ }
- @include media-breakpoint-down(sm) {
- display: block;
- margin-left: 5px;
+ .member-controls {
+ .fa {
+ line-height: inherit;
}
}
@@ -61,23 +56,12 @@
}
.member-form-control {
- @include media-breakpoint-down(sm) {
- width: $dropdown-member-form-control-width;
- margin-left: 0;
- padding-bottom: 5px;
- }
-
@include media-breakpoint-down(xs) {
margin-right: 0;
width: auto;
}
}
-.member-access-text {
- margin-left: auto;
- line-height: 43px;
-}
-
.member-search-form {
position: relative;
@@ -123,6 +107,46 @@
outline: 0;
}
+.members-ldap {
+ align-self: center;
+}
+
+.alert-member-ldap {
+ background-color: $orange-50;
+
+ @include media-breakpoint-up(sm) {
+ line-height: 40px;
+ }
+
+ > p {
+ float: left;
+ margin-bottom: 10px;
+ color: $orange-600;
+
+ @include media-breakpoint-up(sm) {
+ padding-left: 55px;
+ margin-bottom: 0;
+ }
+ }
+
+ .controls {
+ width: 100%;
+
+ @include media-breakpoint-up(sm) {
+ width: auto;
+ }
+ }
+}
+
+.btn-ldap-override {
+ width: 100%;
+
+ @include media-breakpoint-up(sm) {
+ margin-left: 10px;
+ width: auto;
+ }
+}
+
.flex-project-members-panel {
display: flex;
flex-direction: row;
@@ -176,9 +200,6 @@
}
.content-list.members-list li {
- display: flex;
- justify-content: space-between;
-
.list-item-name {
float: none;
display: flex;
@@ -207,33 +228,24 @@
align-self: flex-start;
}
+ @include media-breakpoint-down(sm) {
+ .member-access-text {
+ margin: 0 0 $gl-padding-4 ($grid-size * 6);
+ }
+ }
+
@include media-breakpoint-down(xs) {
display: block;
- .controls > .btn {
- margin-left: 0;
- margin-right: 0;
+ .controls > .btn,
+ .controls .member-form-control {
+ margin: 0 0 $gl-padding-8;
display: block;
}
- .controls > .btn:last-child {
- margin-left: 5px;
- margin-right: 5px;
- width: auto;
- }
-
.form-control {
width: 100%;
}
-
- .member-access-text {
- line-height: 0;
- margin-left: 50px;
- }
-
- .member-controls {
- margin-top: 5px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index e0f7d075fc7..278a9014458 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -20,81 +20,81 @@ $colors: (
white-header-not-chosen : #f0f0f0,
white-line-not-chosen : $gray-light,
- dark-header-head-neutral : rgba(#3f3, .2),
- dark-line-head-neutral : rgba(#3f3, .1),
+ dark-header-head-neutral : rgba(#3f3, 0.2),
+ dark-line-head-neutral : rgba(#3f3, 0.1),
dark-button-head-neutral : #40874f,
- dark-header-head-chosen : rgba(#3f3, .33),
- dark-line-head-chosen : rgba(#3f3, .2),
+ dark-header-head-chosen : rgba(#3f3, 0.33),
+ dark-line-head-chosen : rgba(#3f3, 0.2),
dark-button-head-chosen : #258537,
- dark-header-origin-neutral : rgba(#2878c9, .4),
- dark-line-origin-neutral : rgba(#2878c9, .3),
+ dark-header-origin-neutral : rgba(#2878c9, 0.4),
+ dark-line-origin-neutral : rgba(#2878c9, 0.3),
dark-button-origin-neutral : #2a5c8c,
- dark-header-origin-chosen : rgba(#2878c9, .6),
- dark-line-origin-chosen : rgba(#2878c9, .4),
+ dark-header-origin-chosen : rgba(#2878c9, 0.6),
+ dark-line-origin-chosen : rgba(#2878c9, 0.4),
dark-button-origin-chosen : #1d6cbf,
- dark-header-not-chosen : rgba(#fff, .25),
- dark-line-not-chosen : rgba(#fff, .1),
+ dark-header-not-chosen : rgba(#fff, 0.25),
+ dark-line-not-chosen : rgba(#fff, 0.1),
- monokai-header-head-neutral : rgba(#a6e22e, .25),
- monokai-line-head-neutral : rgba(#a6e22e, .1),
+ monokai-header-head-neutral : rgba(#a6e22e, 0.25),
+ monokai-line-head-neutral : rgba(#a6e22e, 0.1),
monokai-button-head-neutral : #376b20,
- monokai-header-head-chosen : rgba(#a6e22e, .4),
- monokai-line-head-chosen : rgba(#a6e22e, .25),
+ monokai-header-head-chosen : rgba(#a6e22e, 0.4),
+ monokai-line-head-chosen : rgba(#a6e22e, 0.25),
monokai-button-head-chosen : #39800d,
- monokai-header-origin-neutral : rgba(#60d9f1, .35),
- monokai-line-origin-neutral : rgba(#60d9f1, .15),
+ monokai-header-origin-neutral : rgba(#60d9f1, 0.35),
+ monokai-line-origin-neutral : rgba(#60d9f1, 0.15),
monokai-button-origin-neutral : #38848c,
- monokai-header-origin-chosen : rgba(#60d9f1, .5),
- monokai-line-origin-chosen : rgba(#60d9f1, .35),
+ monokai-header-origin-chosen : rgba(#60d9f1, 0.5),
+ monokai-line-origin-chosen : rgba(#60d9f1, 0.35),
monokai-button-origin-chosen : #3ea4b2,
- monokai-header-not-chosen : rgba(#76715d, .24),
- monokai-line-not-chosen : rgba(#76715d, .1),
+ monokai-header-not-chosen : rgba(#76715d, 0.24),
+ monokai-line-not-chosen : rgba(#76715d, 0.1),
- solarized-light-header-head-neutral : rgba(#859900, .37),
- solarized-light-line-head-neutral : rgba(#859900, .2),
+ solarized-light-header-head-neutral : rgba(#859900, 0.37),
+ solarized-light-line-head-neutral : rgba(#859900, 0.2),
solarized-light-button-head-neutral : #afb262,
- solarized-light-header-head-chosen : rgba(#859900, .5),
- solarized-light-line-head-chosen : rgba(#859900, .37),
+ solarized-light-header-head-chosen : rgba(#859900, 0.5),
+ solarized-light-line-head-chosen : rgba(#859900, 0.37),
solarized-light-button-head-chosen : #94993d,
- solarized-light-header-origin-neutral : rgba(#2878c9, .37),
- solarized-light-line-origin-neutral : rgba(#2878c9, .15),
+ solarized-light-header-origin-neutral : rgba(#2878c9, 0.37),
+ solarized-light-line-origin-neutral : rgba(#2878c9, 0.15),
solarized-light-button-origin-neutral : #60a1bf,
- solarized-light-header-origin-chosen : rgba(#2878c9, .6),
- solarized-light-line-origin-chosen : rgba(#2878c9, .37),
+ solarized-light-header-origin-chosen : rgba(#2878c9, 0.6),
+ solarized-light-line-origin-chosen : rgba(#2878c9, 0.37),
solarized-light-button-origin-chosen : #2482b2,
- solarized-light-header-not-chosen : rgba(#839496, .37),
- solarized-light-line-not-chosen : rgba(#839496, .2),
+ solarized-light-header-not-chosen : rgba(#839496, 0.37),
+ solarized-light-line-not-chosen : rgba(#839496, 0.2),
- solarized-dark-header-head-neutral : rgba(#859900, .35),
- solarized-dark-line-head-neutral : rgba(#859900, .15),
+ solarized-dark-header-head-neutral : rgba(#859900, 0.35),
+ solarized-dark-line-head-neutral : rgba(#859900, 0.15),
solarized-dark-button-head-neutral : #376b20,
- solarized-dark-header-head-chosen : rgba(#859900, .5),
- solarized-dark-line-head-chosen : rgba(#859900, .35),
+ solarized-dark-header-head-chosen : rgba(#859900, 0.5),
+ solarized-dark-line-head-chosen : rgba(#859900, 0.35),
solarized-dark-button-head-chosen : #39800d,
- solarized-dark-header-origin-neutral : rgba(#2878c9, .35),
- solarized-dark-line-origin-neutral : rgba(#2878c9, .15),
+ solarized-dark-header-origin-neutral : rgba(#2878c9, 0.35),
+ solarized-dark-line-origin-neutral : rgba(#2878c9, 0.15),
solarized-dark-button-origin-neutral : #086799,
- solarized-dark-header-origin-chosen : rgba(#2878c9, .6),
- solarized-dark-line-origin-chosen : rgba(#2878c9, .35),
+ solarized-dark-header-origin-chosen : rgba(#2878c9, 0.6),
+ solarized-dark-line-origin-chosen : rgba(#2878c9, 0.35),
solarized-dark-button-origin-chosen : #0082cc,
- solarized_dark_header_not_chosen : rgba(#839496, .25),
- solarized_dark_line_not_chosen : rgba(#839496, .15),
+ solarized_dark_header_not_chosen : rgba(#839496, 0.25),
+ solarized_dark_line_not_chosen : rgba(#839496, 0.15),
none_header_head_neutral : $gray-normal,
none_line_head_neutral : $gray-normal,
@@ -210,26 +210,20 @@ $colors: (
}
#conflicts {
-
.white {
- @include color-scheme('white')
- }
+ @include color-scheme('white'); }
.dark {
- @include color-scheme('dark')
- }
+ @include color-scheme('dark'); }
.monokai {
- @include color-scheme('monokai')
- }
+ @include color-scheme('monokai'); }
.solarized-light {
- @include color-scheme('solarized-light')
- }
+ @include color-scheme('solarized-light'); }
.solarized-dark {
- @include color-scheme('solarized-dark')
- }
+ @include color-scheme('solarized-dark'); }
.diff-wrap-lines .line_content {
white-space: normal;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index cfd3faab122..8cb3fab74e0 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -87,6 +87,11 @@
padding: $gl-padding;
}
+.mr-widget-info {
+ padding-left: $gl-padding-50 - $gl-padding-32;
+ padding-right: $gl-padding;
+}
+
.mr-state-widget {
color: $gl-text-color;
@@ -166,6 +171,7 @@
float: left;
.accept-merge-request {
+ &.ci-preparing,
&.ci-pending,
&.ci-running {
@include btn-blue;
@@ -179,46 +185,6 @@
}
}
}
-
- .accept-control {
- display: inline-block;
- float: left;
- margin: 0;
- margin-left: 20px;
- padding: 5px;
- padding-top: 8px;
- line-height: 20px;
-
- &.right {
- float: right;
- padding-right: 0;
- }
-
- .modify-merge-commit-link {
- padding: 0;
- background-color: transparent;
- border: 0;
- color: $gl-text-color;
-
- &:hover,
- &:focus {
- text-decoration: underline;
- }
- }
-
- .merge-param-checkbox {
- margin: 0;
- }
-
- a .fa-question-circle {
- color: $gl-text-color-secondary;
-
- &:hover,
- &:focus {
- color: $link-hover-color;
- }
- }
- }
}
.ci-widget {
@@ -401,12 +367,6 @@
width: 100%;
text-align: center;
}
-
- .accept-control {
- width: 100%;
- text-align: center;
- margin: 0;
- }
}
.commit-message-editor {
@@ -491,14 +451,22 @@
.merge-request {
padding: 10px 0 10px 15px;
position: relative;
- display: -webkit-flex;
display: flex;
.issuable-info-container {
- -webkit-flex: 1;
flex: 1;
}
+ .issuable-meta {
+ .author-link {
+ display: inline-block;
+ }
+
+ .issuable-comments {
+ height: 18px;
+ }
+ }
+
.merge-request-title {
margin-bottom: 2px;
@@ -551,6 +519,10 @@
.mr-links {
padding-left: $status-icon-size + $gl-btn-padding;
+
+ &:last-child {
+ padding-bottom: $gl-padding;
+ }
}
.mr-info-list {
@@ -596,7 +568,6 @@
color: $gl-text-color;
}
-
.git-merge-container {
justify-content: space-between;
flex: 1;
@@ -738,7 +709,6 @@
background: $gray-light;
color: $gl-text-color;
margin-top: -1px;
- border-top: 1px solid $border-color;
.mr-version-menus-container {
display: flex;
@@ -761,6 +731,7 @@
.content-block {
padding: $gl-padding-top $gl-padding;
+ border-bottom: 0;
}
.comments-disabled-notif {
@@ -785,16 +756,18 @@
padding-right: 5px;
}
+ // Shortening button height by 1px to make compare-versions
+ // header 56px and fit into our 8px design grid
+ button {
+ height: 34px;
+ }
+
@include media-breakpoint-up(md) {
position: -webkit-sticky;
position: sticky;
top: $header-height + $mr-tabs-height;
- width: 100%;
-
- &.is-fileTreeOpen {
- margin-left: -16px;
- width: calc(100% + 32px);
- }
+ margin-left: -16px;
+ width: calc(100% + 32px);
.mr-version-menus-container {
flex-wrap: nowrap;
@@ -806,15 +779,16 @@
}
}
-.merge-request-tabs-holder {
+.merge-request-tabs-holder,
+.epic-tabs-holder {
top: $header-height;
- z-index: 300;
+ z-index: 250;
background-color: $white-light;
border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
- position: sticky;
position: -webkit-sticky;
+ position: sticky;
}
&.affix {
@@ -824,11 +798,6 @@
@include media-breakpoint-down(xs) {
right: 0;
}
-
- .merge-request-tabs-container {
- padding-left: $gl-padding;
- padding-right: $gl-padding;
- }
}
.nav-links {
@@ -836,11 +805,21 @@
}
}
-.with-performance-bar .merge-request-tabs-holder {
- top: $header-height + $performance-bar-height;
+.merge-request-tabs-holder.affix .merge-request-tabs-container,
+.epic-tabs-holder.affix .epic-tabs-container {
+ padding-left: $gl-padding;
+ padding-right: $gl-padding;
+}
+
+.with-performance-bar {
+ .merge-request-tabs-holder,
+ .epic-tabs-holder {
+ top: $header-height + $performance-bar-height;
+ }
}
-.merge-request-tabs {
+.merge-request-tabs,
+.epic-tabs {
display: flex;
flex-wrap: nowrap;
margin-bottom: 0;
@@ -848,7 +827,8 @@
}
.limit-container-width {
- .merge-request-tabs-container {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
@@ -861,31 +841,56 @@
}
}
-.merge-request-tabs-container {
+.merge-request-tabs-container,
+.epic-tabs-container {
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(sm) {
- flex-direction: column-reverse;
+ @include media-breakpoint-down(xs) {
+ .discussion-filter-container,
+ .line-resolve-all-container {
+ margin-bottom: $gl-padding-4;
+ }
}
.discussion-filter-container {
- margin-top: $gl-padding-8;
-
&:not(:only-child) {
- padding-right: $gl-padding-8;
+ margin: $gl-padding-4;
}
}
+
+ .merge-request-tabs {
+ height: $grid-size * 6;
+ }
}
-.limit-container-width:not(.container-limited) {
- .merge-request-tabs-holder:not(.affix) {
- .merge-request-tabs-container {
- max-width: $limited-layout-width - ($gl-padding * 2);
+// Wrap MR tabs/buttons so you don't have to scroll on desktop
+@include media-breakpoint-down(md) {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ padding-top: $gl-padding-8;
+ }
+}
+
+@include media-breakpoint-down(lg) {
+ .right-sidebar-expanded {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ padding-top: $gl-padding-8;
}
}
}
+.limit-container-width:not(.container-limited) {
+ .merge-request-tabs-holder:not(.affix) .merge-request-tabs-container,
+ .epic-tabs-holder:not(.affix) .epic-tabs-container {
+ max-width: $limited-layout-width - ($gl-padding * 2);
+ }
+}
+
.mr-memory-usage {
width: 100%;
@@ -955,10 +960,6 @@
}
}
- .btn svg {
- fill: $gray-700;
- }
-
.dropdown-menu {
width: 400px;
}
@@ -1017,7 +1018,12 @@
background: $black-transparent;
}
-.source-branch-removal-status {
- padding-left: 50px;
- padding-bottom: $gl-padding;
+.mr-compare {
+ .diff-file .file-title-flex-parent {
+ top: $header-height + 51px;
+
+ .with-performance-bar & {
+ top: $performance-bar-height + $header-height + 51px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 15f3a2ef4a8..49608a3964f 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -67,18 +67,14 @@ $status-box-line-height: 26px;
.card-header {
line-height: $line-height-base;
padding: 14px 16px;
- display: -webkit-flex;
display: flex;
.title {
- -webkit-flex: 1;
- -webkit-flex-grow: 1;
flex: 1;
flex-grow: 2;
}
.counter {
- -webkit-flex: 1;
flex: 0;
padding-left: 16px;
}
@@ -239,6 +235,7 @@ $status-box-line-height: 26px;
padding: 0;
}
+ .popover-body,
.popover-content {
padding: 0;
}
diff --git a/app/assets/stylesheets/pages/monitor.scss b/app/assets/stylesheets/pages/monitor.scss
new file mode 100644
index 00000000000..25ff5abd774
--- /dev/null
+++ b/app/assets/stylesheets/pages/monitor.scss
@@ -0,0 +1,5 @@
+.chart-tooltip > .popover {
+ min-width: 0;
+ width: max-content;
+ max-width: $chart-tooltip-max-width;
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 51f755c67af..c6bac33e888 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -58,7 +58,8 @@
border: 1px solid $border-color;
border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
- box-shadow ease-in-out 0.15s;
+ box-shadow ease-in-out 0.15s;
+ background-color: $white-light;
&.is-focused {
@extend .form-control:focus;
@@ -72,7 +73,7 @@
&.is-dropzone-hover {
border-color: $green-500;
box-shadow: 0 0 2px $black-transparent,
- 0 0 4px $green-500-focus;
+ 0 0 4px $green-500-focus;
.comment-toolbar,
.nav-links {
@@ -84,9 +85,7 @@
.md-header .nav-links {
display: flex;
- display: -webkit-flex;
flex-flow: row wrap;
- -webkit-flex-flow: row wrap;
width: 100%;
.float-right {
@@ -105,6 +104,11 @@
margin: auto;
align-items: center;
+ a {
+ color: $orange-600;
+ text-decoration: underline;
+ }
+
.icon {
margin-right: $issuable-warning-icon-margin;
vertical-align: text-bottom;
@@ -170,6 +174,16 @@
.discussion-form {
background-color: $white-light;
+
+ @include media-breakpoint-down(xs) {
+ .user-avatar-link {
+ display: none;
+ }
+
+ .note-edit-form {
+ margin-left: 0;
+ }
+ }
}
table {
@@ -236,13 +250,25 @@ table {
.diff-file,
.commit-diff {
.discussion-reply-holder {
- background-color: $white-light;
+ background-color: $gray-light;
border-radius: 0 0 3px 3px;
padding: $gl-padding;
+ border-top: 1px solid $gray-100;
+
+ + .new-note {
+ background-color: $gray-light;
+ border-top: 1px solid $gray-100;
+ }
&.is-replying {
padding-bottom: $gl-padding;
}
+
+ .user-avatar-link {
+ img {
+ margin-top: -3px;
+ }
+ }
}
}
@@ -336,7 +362,7 @@ table {
.toolbar-button-icon {
position: relative;
top: 1px;
- margin-right: 3px;
+ margin-right: $gl-padding-4;
color: inherit;
font-size: 16px;
}
@@ -444,7 +470,7 @@ table {
.uploading-error-message {
@include media-breakpoint-down(xs) {
&::after {
- content: "\a";
+ content: '\a';
white-space: pre;
}
}
@@ -463,6 +489,15 @@ table {
border: 0;
font-size: 14px;
line-height: 16px;
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+
+ .text-attach-file {
+ text-decoration: underline;
+ }
+ }
}
.markdown-selector {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 7e7eff1346a..69dd583bc5b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -68,7 +68,7 @@ $note-form-margin-left: 72px;
}
}
- .notes_content {
+ .notes-content {
border: 0;
border-top: 1px solid $border-color;
}
@@ -80,21 +80,17 @@ $note-form-margin-left: 72px;
}
}
- li.note {
- border-bottom: 1px solid $border-color;
- }
-
.replies-toggle {
background-color: $gray-light;
padding: $gl-padding-8 $gl-padding;
+ border-top: 1px solid $gray-100;
+ border-bottom: 1px solid $gray-100;
.collapse-replies-btn:hover {
color: $blue-600;
}
&.expanded {
- border-bottom: 1px solid $border-color;
-
span {
cursor: pointer;
}
@@ -107,6 +103,7 @@ $note-form-margin-left: 72px;
&.collapsed {
color: $gl-text-color-secondary;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
svg {
float: left;
@@ -210,8 +207,13 @@ $note-form-margin-left: 72px;
display: none;
}
+ .user-avatar-link img {
+ margin-top: $gl-padding-8;
+ }
+
.note-edit-form {
display: block;
+ margin-left: 0;
&.current-note-edit-form + .note-awards {
display: none;
@@ -224,14 +226,7 @@ $note-form-margin-left: 72px;
overflow-y: hidden;
.note-text {
- @include md-typography;
- // Reset ul style types since we're nested inside a ul already
- @include bulleted-list;
word-wrap: break-word;
-
- table {
- @include markdown-table;
- }
}
}
@@ -270,8 +265,8 @@ $note-form-margin-left: 72px;
}
.system-note {
- padding: 6px 21px;
- margin: $gl-padding-24 0;
+ padding: $gl-padding-4 20px;
+ margin: $gl-padding 0;
background-color: transparent;
.note-header-info {
@@ -283,8 +278,6 @@ $note-form-margin-left: 72px;
}
.system-note-message {
- display: inline;
-
&::first-letter {
text-transform: lowercase;
}
@@ -303,26 +296,6 @@ $note-form-margin-left: 72px;
}
}
- .timeline-icon {
- float: left;
- display: flex;
- align-items: center;
- background-color: $white-light;
- width: $system-note-icon-size;
- height: $system-note-icon-size;
- border: 1px solid $border-color;
- border-radius: $system-note-icon-size;
- margin: -6px $gl-padding 0 0;
-
- svg {
- width: $system-note-svg-size;
- height: $system-note-svg-size;
- fill: $gray-darkest;
- display: block;
- margin: 0 auto;
- }
- }
-
.timeline-content {
@include notes-media('min', map-get($grid-breakpoints, sm)) {
margin-left: 30px;
@@ -380,6 +353,37 @@ $note-form-margin-left: 72px;
}
}
}
+
+ .system-note,
+ .discussion-filter-note {
+ .timeline-icon {
+ float: left;
+ display: flex;
+ align-items: center;
+ background-color: $white-light;
+ width: $system-note-icon-size;
+ height: $system-note-icon-size;
+ border: 1px solid $border-color;
+ border-radius: $system-note-icon-size;
+ margin: -6px 20px 0 0;
+
+ svg {
+ width: $system-note-svg-size;
+ height: $system-note-svg-size;
+ fill: $gray-darkest;
+ display: block;
+ margin: 0 auto;
+ }
+ }
+ }
+
+ .discussion-filter-note {
+ .timeline-icon {
+ width: $system-note-icon-size + 6;
+ height: $system-note-icon-size + 6;
+ margin-top: -8px;
+ }
+ }
}
// Diff code in discussion view
@@ -434,7 +438,9 @@ $note-form-margin-left: 72px;
.diff-file {
.is-over {
.add-diff-note {
- display: inline-block;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
}
}
@@ -451,7 +457,7 @@ $note-form-margin-left: 72px;
// Merge request notes in diffs
// Diff is inline
- .notes_content .note-header .note-headline-light {
+ .notes-content .note-header .note-headline-light {
display: inline-block;
position: relative;
}
@@ -463,12 +469,17 @@ $note-form-margin-left: 72px;
border: 1px solid $border-color;
border-left: 0;
- &.notes_content {
+ &.notes-content {
border-width: 1px 0;
padding: 0;
vertical-align: top;
white-space: normal;
+ // Fixes subpixel rounding issue https://gitlab.com/gitlab-org/gitlab-ce/issues/53973
+ // background-color is needed for dark code preference
+ padding-bottom: 1px;
+ background-color: $white-light;
+
&.parallel {
border-width: 1px;
@@ -509,12 +520,30 @@ $note-form-margin-left: 72px;
}
}
-.commit-diff {
- .notes_content {
- background-color: $white-light;
+.code-commit .notes-content,
+.diff-viewer > .image ~ .note-container {
+ background-color: $white-light;
+
+ .avatar-note-form-holder {
+ .user-avatar-link img {
+ margin: 13px $gl-padding $gl-padding;
+ }
+
+ form,
+ ~ .discussion-form-container {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
}
}
+.diff-viewer > .image ~ .note-container form.new-note {
+ margin-left: 0;
+}
+
.discussion-header,
.note-header-info {
a {
@@ -540,7 +569,7 @@ $note-form-margin-left: 72px;
}
.discussion-header {
- min-height: 72px;
+ min-height: 74px;
.note-header-info {
padding-bottom: 0;
@@ -553,8 +582,10 @@ $note-form-margin-left: 72px;
}
.unresolved {
- .note-header-info {
- margin-top: $gl-padding-8;
+ .discussion-header {
+ .note-header-info {
+ margin-top: $gl-padding-8;
+ }
}
}
@@ -596,12 +627,6 @@ $note-form-margin-left: 72px;
}
.note-headline-meta {
- display: inline-block;
-
- .system-note-message {
- white-space: normal;
- }
-
.system-note-separator {
color: $gl-text-color-disabled;
}
@@ -643,7 +668,7 @@ $note-form-margin-left: 72px;
display: inline-flex;
align-items: center;
margin-left: 10px;
- color: $gray-darkest;
+ color: $gray-600;
@include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
float: none;
@@ -739,7 +764,7 @@ $note-form-margin-left: 72px;
.add-diff-note {
@include btn-comment-icon;
opacity: 0;
- margin-left: -55px;
+ margin-left: -52px;
position: absolute;
top: 50%;
transform: translateY(-50%);
@@ -758,15 +783,13 @@ $note-form-margin-left: 72px;
background-color: $white-light;
}
- a {
+ a:not(.learn-more) {
color: $blue-600;
}
}
.line-resolve-all-container {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-right: 0;
- }
+ margin: $gl-padding-4;
> div {
white-space: nowrap;
@@ -782,6 +805,8 @@ $note-form-margin-left: 72px;
}
.btn {
+ line-height: $gl-line-height;
+
svg {
fill: $gray-darkest;
}
@@ -807,10 +832,11 @@ $note-form-margin-left: 72px;
.line-resolve-all {
vertical-align: middle;
display: inline-block;
- padding: 6px 10px;
+ padding: $gl-padding-4 10px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ font-size: $gl-btn-small-font-size;
&.has-next-btn {
border-top-right-radius: 0;
@@ -820,11 +846,16 @@ $note-form-margin-left: 72px;
.line-resolve-btn {
margin-right: 5px;
+ color: $gray-darkest;
svg {
vertical-align: middle;
}
}
+
+ @include media-breakpoint-down(xs) {
+ flex: 1;
+ }
}
.line-resolve-btn {
@@ -834,7 +865,6 @@ $note-form-margin-left: 72px;
background-color: transparent;
border: 0;
outline: 0;
- color: $gray-darkest;
transition: color $general-hover-transition-duration $general-hover-transition-curve;
&.is-disabled {
@@ -898,14 +928,9 @@ $note-form-margin-left: 72px;
.diff-comment-form {
display: block;
}
-
- .add-diff-note svg {
- margin-top: 4px;
- }
}
.discussion-filter-container {
-
.btn > svg {
width: $gl-col-padding;
height: $gl-col-padding;
@@ -927,7 +952,6 @@ $note-form-margin-left: 72px;
//This needs to be deleted when Snippet/Commit comments are convered to Vue
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/53918#note_117038785
.unstyled-comments {
-
.discussion-header {
padding: $gl-padding;
border-bottom: 1px solid $border-color;
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index e98cb444f0a..e1cbf0e6654 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -4,6 +4,34 @@
.dropdown-menu {
@extend .dropdown-menu-right;
}
+
+ @include media-breakpoint-down(sm) {
+ .notification-dropdown {
+ width: 100%;
+ }
+
+ .notification-form {
+ display: block;
+ }
+
+ .notifications-btn,
+ .btn-group {
+ width: 100%;
+ }
+
+ .table-section {
+ border-top: 0;
+ min-height: unset;
+
+ &:not(:first-child) {
+ padding-top: 0;
+ }
+ }
+
+ .update-notifications {
+ width: 100%;
+ }
+ }
}
.notification {
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 617b3db2fae..85c4902eee2 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -1,9 +1,4 @@
-.js-pipeline-schedule-form {
- .dropdown-select,
- .dropdown-menu-toggle {
- width: 100% !important;
- }
-
+.pipeline-schedule-form {
.gl-field-error {
margin: 10px 0 0;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index e676d48c1f4..aa6bbc8e473 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -341,13 +341,15 @@
&.builds .ci-table tr {
height: 71px;
}
-}
-.build-failures {
- th {
- border-top: 0;
+ .ci-table {
+ thead th {
+ border-top: 0;
+ }
}
+}
+.build-failures {
.build-state {
padding: 20px 2px;
@@ -496,7 +498,8 @@
list-style: none;
}
- &:last-child {
+ // when downstream pipelines are present, the last stage isn't the last column
+ &:last-child:not(.has-downstream) {
.build {
// Remove right connecting horizontal line from first build in last stage
&:first-child::after {
@@ -513,7 +516,8 @@
}
}
- &:first-child {
+ // when upstream pipelines are present, the first stage isn't the first column
+ &:first-child:not(.has-upstream) {
.build {
// Remove left curved connectors from all builds in first stage
&:not(:first-child)::before {
@@ -561,6 +565,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ line-height: 2.2em;
}
.build {
@@ -697,6 +702,11 @@
}
}
}
+
+ .stage-action svg {
+ left: 1px;
+ top: -2px;
+ }
}
// Triggers the dropdown in the big pipeline graph
@@ -708,15 +718,9 @@
top: 8px;
}
+.ci-build-text,
.ci-status-text {
- max-width: 110px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: bottom;
- display: inline-block;
- position: relative;
- font-weight: $gl-font-weight-normal;
+ font-weight: 200;
}
@mixin mini-pipeline-graph-color(
@@ -787,10 +791,11 @@
}
&.ci-status-icon-pending,
- &.ci-status-icon-success_with_warnings {
+ &.ci-status-icon-success-with-warnings {
@include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
+ &.ci-status-icon-preparing,
&.ci-status-icon-running {
@include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
}
@@ -898,7 +903,7 @@ button.mini-pipeline-graph-dropdown-toggle {
// Match dropdown.scss for all `a` tags
&.non-details-job-component {
- padding: 8px 16px;
+ padding: $gl-padding-8 $gl-btn-horz-padding;
}
.ci-job-name-component {
@@ -907,26 +912,6 @@ button.mini-pipeline-graph-dropdown-toggle {
flex: 1;
}
- // build name
- .ci-build-text,
- .ci-status-text {
- font-weight: 200;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 70%;
- margin-left: 2px;
- display: inline-block;
-
- &::after {
- content: '';
- display: block;
- }
-
- @include media-breakpoint-down(xs) {
- max-width: 60%;
- }
- }
.ci-status-icon {
@extend .append-right-8;
@@ -994,7 +979,6 @@ button.mini-pipeline-graph-dropdown-toggle {
* Top arrow in the dropdown in the mini pipeline graph
*/
.mini-pipeline-graph-dropdown-menu {
-
&::before,
&::after {
content: '';
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index a1e847009fc..87cef43b923 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -266,23 +266,6 @@
padding-top: 20px;
}
- .cover-controls {
- position: static;
- padding: 0 16px;
- margin-bottom: 20px;
- display: -webkit-flex;
- display: flex;
-
- .btn {
- -webkit-flex-grow: 1;
- flex-grow: 1;
-
- &:first-child {
- margin-left: 0;
- }
- }
- }
-
.user-profile-nav {
a {
margin-right: 0;
@@ -322,6 +305,7 @@ table.u2f-registrations {
margin: 20px -5px 0;
.bordered-box {
+ padding: 32px;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-50;
@@ -455,8 +439,17 @@ table.u2f-registrations {
}
}
+ .form-group > label {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .form-group > .form-text {
+ font-size: $gl-font-size;
+ }
+
.emoji-menu-toggle-button {
@include emoji-menu-toggle-button;
+ padding: 6px 10px;
.no-emoji-placeholder {
position: relative;
@@ -478,3 +471,41 @@ table.u2f-registrations {
.help-block {
color: $gl-text-color-secondary;
}
+
+.gitlab-slack-gif {
+ width: 100%;
+ max-width: $add-to-slack-gif-max-width;
+}
+
+.gitlab-slack-well {
+ background-color: $white-light;
+ box-shadow: none;
+ max-width: $add-to-slack-well-max-width;
+}
+
+.gitlab-slack-logo {
+ width: $add-to-slack-logo-size;
+ height: $add-to-slack-logo-size;
+}
+
+.gitlab-slack-popup {
+ width: 100%;
+ max-width: $add-to-slack-popup-max-width;
+}
+
+.gitlab-slack-right-arrow svg {
+ fill: $white-dark;
+ width: $right-arrow-size;
+ height: $right-arrow-size;
+ vertical-align: text-bottom;
+}
+
+.gitlab-slack-double-headed-arrow {
+ vertical-align: text-top;
+
+ svg {
+ fill: $gray-darker;
+ width: $double-headed-arrow-width;
+ height: $double-headed-arrow-height;
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 277030ad3af..151af843c95 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -18,12 +18,9 @@
}
.input-group {
- display: flex;
-
.select2-container {
display: unset;
max-width: unset;
- width: unset !important;
flex-grow: 1;
}
@@ -70,6 +67,10 @@
}
}
+.classification-label {
+ background-color: $red-500;
+}
+
.toggle-wrapper {
margin-top: 5px;
}
@@ -212,8 +213,7 @@
}
}
- .access-request-link,
- .home-panel-topic-list {
+ .access-request-link {
padding-left: $gl-padding-8;
border-left: 1px solid $gl-text-color-secondary;
}
@@ -571,9 +571,7 @@
.import-buttons {
padding-left: 0;
- display: -webkit-flex;
display: flex;
- -webkit-flex-wrap: wrap;
flex-wrap: wrap;
.btn {
@@ -695,10 +693,6 @@
}
}
-.project-empty-note-panel {
- border-bottom: 1px solid $border-color;
-}
-
.project-stats,
.project-buttons {
.scrolling-tabs-container {
@@ -1168,6 +1162,8 @@ pre.light-well {
.cannot-be-merged:hover {
color: $red-500;
margin-top: 2px;
+ position: relative;
+ z-index: 2;
}
.private-forks-notice .private-fork-icon {
@@ -1450,3 +1446,86 @@ pre.light-well {
}
}
}
+
+.project-filters {
+ .btn svg {
+ color: $gl-gray-700;
+ }
+
+ .button-filter-group {
+ .btn {
+ width: 96px;
+ }
+
+ a {
+ color: $black;
+ }
+
+ .active {
+ background: $btn-active-gray;
+ }
+ }
+
+ .filtered-search-dropdown-label {
+ min-width: 68px;
+
+ @include media-breakpoint-down(xs) {
+ min-width: 60px;
+ }
+ }
+
+ .filtered-search {
+ min-width: 30%;
+ flex-basis: 0;
+
+ .project-filter-form .project-filter-form-field {
+ padding-right: $gl-padding-8;
+ }
+
+ .filtered-search,
+ .filtered-search-nav,
+ .filtered-search-dropdown {
+ flex-basis: 0;
+ }
+
+ @include media-breakpoint-down(lg) {
+ min-width: 15%;
+
+ .project-filter-form-field {
+ min-width: 150px;
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+ min-width: 30%;
+ }
+ }
+
+ .filtered-search-box {
+ border-radius: 3px 0 0 3px;
+ }
+
+ .dropdown-menu-toggle {
+ margin-left: $gl-padding-8;
+ }
+
+ @include media-breakpoint-down(md) {
+ .extended-filtered-search-box {
+ min-width: 55%;
+ }
+
+ .filtered-search-dropdown {
+ width: 50%;
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .filtered-search-dropdown {
+ width: 100%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
new file mode 100644
index 00000000000..c03554b287f
--- /dev/null
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -0,0 +1,270 @@
+.prometheus-graphs {
+ .dropdowns {
+ .dropdown-menu-toggle {
+ svg {
+ position: absolute;
+ right: 5%;
+ top: 25%;
+ }
+ }
+
+ .dropdown-menu-toggle,
+ .dropdown-menu {
+ width: 240px;
+ }
+ }
+}
+
+.prometheus-panel {
+ margin-top: 20px;
+}
+
+.prometheus-graph-group {
+ display: flex;
+ flex-wrap: wrap;
+ padding: $gl-padding / 2;
+}
+
+.prometheus-graph {
+ padding: $gl-padding / 2;
+}
+
+.prometheus-graph-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: $gl-padding-8;
+
+ h5 {
+ font-size: $gl-font-size-large;
+ margin: 0;
+ }
+}
+
+.prometheus-graph-cursor {
+ position: absolute;
+ background: $gray-600;
+ width: 1px;
+}
+
+.prometheus-graph-flag {
+ display: block;
+ min-width: 160px;
+ border: 0;
+ box-shadow: 0 1px 4px 0 $black-transparent;
+
+ h5 {
+ padding: 0;
+ margin: 0;
+ font-size: 14px;
+ line-height: 1.2;
+ }
+
+ .deploy-meta-content {
+ border-bottom: 1px solid $white-dark;
+
+ svg {
+ height: 15px;
+ vertical-align: bottom;
+ }
+ }
+
+ &.popover {
+ padding: 0;
+
+ &.left {
+ left: auto;
+ right: 0;
+ margin-right: 10px;
+
+ > .arrow {
+ right: -14px;
+ border-left-color: $border-color;
+ }
+
+ > .arrow::after {
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ border-left: 4px solid $gray-50;
+ }
+
+ .arrow-shadow {
+ right: -3px;
+ box-shadow: 1px 0 9px 0 $black-transparent;
+ }
+ }
+
+ &.right {
+ left: 0;
+ right: auto;
+ margin-left: 10px;
+
+ > .arrow {
+ left: -7px;
+ border-right-color: $border-color;
+ }
+
+ > .arrow::after {
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ border-right: 4px solid $gray-50;
+ }
+
+ .arrow-shadow {
+ left: -3px;
+ box-shadow: 1px 0 8px 0 $black-transparent;
+ }
+ }
+
+ > .arrow {
+ top: 10px;
+ margin: 0;
+ }
+
+ .arrow-shadow {
+ content: '';
+ position: absolute;
+ width: 7px;
+ height: 7px;
+ background-color: transparent;
+ transform: rotate(45deg);
+ top: 13px;
+ }
+
+ > .popover-title,
+ > .popover-content,
+ > .popover-header,
+ > .popover-body {
+ padding: 8px;
+ font-size: 12px;
+ white-space: nowrap;
+ position: relative;
+ }
+
+ > .popover-title {
+ background-color: $gray-50;
+ border-radius: $border-radius-default $border-radius-default 0 0;
+ }
+ }
+
+ strong {
+ font-weight: 600;
+ }
+}
+
+.prometheus-table {
+ border-collapse: collapse;
+ padding: 0;
+ margin: 0;
+
+ td {
+ vertical-align: middle;
+
+ + td {
+ padding-left: 8px;
+ vertical-align: top;
+ }
+ }
+
+ .legend-metric-title {
+ font-size: 12px;
+ vertical-align: middle;
+ }
+}
+
+.prometheus-svg-container {
+ position: relative;
+ height: 0;
+ width: 100%;
+ padding: 0;
+ padding-bottom: 100%;
+
+ .text-metric-usage {
+ fill: $black;
+ font-weight: $gl-font-weight-normal;
+ font-size: 12px;
+ }
+
+ > svg {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
+
+ text {
+ fill: $gl-text-color;
+ stroke-width: 0;
+ }
+
+ .text-metric-bold {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .label-axis-text {
+ fill: $black;
+ font-weight: $gl-font-weight-normal;
+ font-size: 10px;
+ }
+
+ .legend-axis-text {
+ fill: $black;
+ }
+
+ .tick {
+ > line {
+ stroke: $gray-darker;
+ }
+
+ > text {
+ fill: $gray-600;
+ font-size: 10px;
+ }
+ }
+
+ .y-label-text,
+ .x-label-text {
+ fill: $gray-darkest;
+ }
+
+ .axis-tick {
+ stroke: $gray-darker;
+ }
+
+ .deploy-info-text {
+ dominant-baseline: text-before-edge;
+ font-size: 12px;
+ }
+
+ .deploy-info-text-link {
+ font-family: $monospace-font;
+ fill: $blue-600;
+
+ &:hover {
+ fill: $blue-800;
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+ .label-axis-text,
+ .text-metric-usage,
+ .legend-axis-text {
+ font-size: 8px;
+ }
+
+ .tick > text {
+ font-size: 8px;
+ }
+ }
+ }
+}
+
+.prometheus-table-row-highlight {
+ background-color: $gray-100;
+}
+
+.prometheus-graph-overlay {
+ fill: none;
+ opacity: 0;
+ pointer-events: all;
+}
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index f7619ccbd20..94da72622af 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -52,11 +52,6 @@
.report-block-list-icon .loading-container {
position: relative;
left: -2px;
- // needed to make the next element align with the
- // elements below that have a svg with 16px width
- .fa-spinner {
- width: 16px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 149c3254d84..dbf600df9d6 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -75,6 +75,8 @@ input[type='checkbox']:hover {
}
.search-input-wrap {
+ width: 100%;
+
.search-icon,
.clear-icon {
position: absolute;
@@ -87,6 +89,7 @@ input[type='checkbox']:hover {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
+ user-select: none;
}
.clear-icon {
@@ -185,13 +188,11 @@ input[type='checkbox']:hover {
.search-holder {
@include media-breakpoint-up(sm) {
- display: -webkit-flex;
display: flex;
}
.search-field-holder,
.project-filter-form {
- -webkit-flex: 1 0 auto;
flex: 1 0 auto;
position: relative;
margin-right: 0;
@@ -260,3 +261,13 @@ input[type='checkbox']:hover {
color: $blue-600;
}
}
+
+// Disable webkit input icons, link to solution: https://stackoverflow.com/questions/9421551/how-do-i-remove-all-default-webkit-search-field-styling
+/* stylelint-disable property-no-vendor-prefix */
+input[type='search']::-webkit-search-decoration,
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-results-button,
+input[type='search']::-webkit-search-results-decoration {
+ -webkit-appearance: none;
+}
+/* stylelint-enable */
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 811cc310a8f..0a9c56f5625 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -23,7 +23,10 @@
}
.settings {
- border-bottom: 1px solid $gray-darker;
+ // border-top for each item except the top one
+ + .settings {
+ border-top: 1px solid $border-color;
+ }
&:first-of-type {
margin-top: 10px;
@@ -36,7 +39,7 @@
.settings-header {
position: relative;
- padding: 20px 110px 10px 0;
+ padding: 20px 110px 0 0;
h4 {
margin-top: 0;
@@ -213,6 +216,31 @@
}
}
+.nested-settings {
+ padding-left: 20px;
+}
+
+.input-btn-group {
+ display: flex;
+
+ .input-large {
+ flex: 1;
+ }
+
+ .btn {
+ margin-left: 10px;
+ }
+}
+
+.content-list > .settings-flex-row {
+ display: flex;
+ align-items: center;
+
+ .float-right {
+ margin-left: auto;
+ }
+}
+
.prometheus-metrics-monitoring {
.card {
.card-toggle {
@@ -243,6 +271,27 @@
}
}
+ .custom-monitored-metrics {
+ .card-title {
+ display: flex;
+ align-items: center;
+
+ > .btn-success {
+ margin-left: auto;
+ }
+ }
+
+ .custom-metric {
+ display: flex;
+ align-items: center;
+ }
+
+ .custom-metric-link-bold {
+ font-weight: $gl-font-weight-bold;
+ text-decoration: none;
+ }
+ }
+
.loading-metrics,
.empty-metrics {
padding: 30px 10px;
@@ -277,6 +326,12 @@
}
}
+.saml-settings.info-well {
+ .form-control[readonly] {
+ background: $white-light;
+ }
+}
+
.modal-doorkeepr-auth {
.modal-body {
padding: $gl-padding;
@@ -316,8 +371,4 @@
.push-pull-table {
margin-top: 1em;
-
- .mirror-action-buttons {
- padding-right: 0;
- }
}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index d331edaa302..31ccdacbc02 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -18,21 +18,15 @@
@include make-col-ready();
@include make-col(12);
}
-
- svg {
- width: 100%;
- }
}
#contributors {
+ flex: 1;
+
.contributors-list {
margin: 0 0 10px;
list-style: none;
padding: 0;
-
- svg {
- width: 100%;
- }
}
.person {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 7d59dd3b5d1..613f643af3a 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -33,17 +33,18 @@
border-color: $gl-text-color;
&:not(span):hover {
- background-color: rgba($gl-text-color, .07);
+ background-color: rgba($gl-text-color, 0.07);
}
}
&.ci-pending,
- &.ci-failed_with_warnings,
- &.ci-success_with_warnings {
+ &.ci-failed-with-warnings,
+ &.ci-success-with-warnings {
@include status-color($orange-100, $orange-500, $orange-700);
}
&.ci-info,
+ &.ci-preparing,
&.ci-running {
@include status-color($blue-100, $blue-500, $blue-600);
}
@@ -54,7 +55,7 @@
border-color: $gl-text-color-secondary;
&:not(span):hover {
- background-color: rgba($gl-text-color-secondary, .07);
+ background-color: rgba($gl-text-color-secondary, 0.07);
}
}
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 3fc37e20c36..586365eb1ce 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,9 +6,7 @@
.todos-list > .todo {
// workaround because we cannot use border-colapse
border-top: 1px solid transparent;
- display: -webkit-flex;
display: flex;
- -webkit-flex-direction: row;
flex-direction: row;
&:hover {
@@ -29,23 +27,18 @@
.todo-avatar,
.todo-actions {
@include transition(opacity);
- -webkit-flex: 0 0 auto;
flex: 0 0 auto;
}
.todo-actions {
- display: -webkit-flex;
display: flex;
- -webkit-justify-content: center;
justify-content: center;
- -webkit-flex-direction: column;
flex-direction: column;
margin-left: 10px;
min-width: 55px;
}
.todo-item {
- -webkit-flex: 0 1 100%;
flex: 0 1 100%;
min-width: 0;
}
@@ -60,13 +53,13 @@
.todo-avatar,
.todo-item {
- opacity: .6;
+ opacity: 0.6;
}
}
.todo-avatar,
.todo-item {
- opacity: .2;
+ opacity: 0.2;
}
.btn {
@@ -82,7 +75,6 @@
display: flex;
> .title-item {
- -webkit-flex: 0 0 auto;
flex: 0 0 auto;
margin: 0 2px;
@@ -96,7 +88,6 @@
}
.todo-label {
- -webkit-flex: 0 1 auto;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
@@ -119,45 +110,38 @@
}
.todo-body {
- .todo-note {
- word-wrap: break-word;
-
- .md {
- color: $gl-grayish-blue;
- font-size: $gl-font-size;
-
- .badge.badge-pill {
- color: $gl-text-color;
- }
+ .badge.badge-pill,
+ p {
+ color: $gl-text-color;
+ }
- p {
- color: $gl-text-color;
- }
- }
+ .md {
+ color: $gl-grayish-blue;
+ font-size: $gl-font-size;
+ }
- code {
- white-space: pre-wrap;
- }
+ code {
+ white-space: pre-wrap;
+ }
- pre {
- border: 0;
- background: $gray-light;
- border-radius: 0;
- color: $gl-gray-500;
- margin: 0 20px;
- overflow: hidden;
- }
+ pre {
+ border: 0;
+ background: $gray-light;
+ border-radius: 0;
+ color: $gl-gray-500;
+ margin: 0 20px;
+ overflow: hidden;
+ }
- .note-image-attach {
- margin-top: 4px;
- margin-left: 0;
- max-width: 200px;
- float: none;
- }
+ .note-image-attach {
+ margin-top: 4px;
+ margin-left: 0;
+ max-width: 200px;
+ float: none;
+ }
- p:last-child {
- margin-bottom: 0;
- }
+ p:last-child {
+ margin-bottom: 0;
}
}
}
@@ -222,23 +206,19 @@
}
.todos-empty {
- display: -webkit-flex;
display: flex;
- -webkit-flex-direction: column;
flex-direction: column;
max-width: 900px;
margin-left: auto;
margin-right: auto;
@include media-breakpoint-up(sm) {
- -webkit-flex-direction: row;
flex-direction: row;
padding-top: 80px;
}
}
.todos-empty-content {
- -webkit-align-self: center;
align-self: center;
max-width: 480px;
margin-right: 20px;
@@ -252,7 +232,6 @@
@include media-breakpoint-up(sm) {
width: 300px;
margin-right: 0;
- -webkit-order: 2;
order: 2;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index a46b8679a42..5664f46484e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -172,26 +172,6 @@
text-decoration: inherit;
}
}
-
- .tree_commit {
- max-width: 320px;
-
- .str-truncated {
- max-width: 100%;
- }
- }
-
- .tree_time_ago {
- min-width: 135px;
- }
- }
-
- .tree_author {
- padding-right: 8px;
-
- .commit-author-name {
- color: $gl-text-color;
- }
}
.tree-truncated-warning {
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 84c617c7ec0..7744fd814d0 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -10,7 +10,7 @@
margin-bottom: 15px;
&::before {
- content: "Example";
+ content: 'Example';
color: $ui-dev-kit-example-color;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 82e887aa62a..3260aed143e 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -179,9 +179,3 @@ ul.wiki-pages-list.content-list {
}
}
}
-
-.wiki:not(.use-csslab) {
- table {
- @include markdown-table;
- }
-}
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 9c01a2f8bda..5a8940ffd6d 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -79,8 +79,12 @@
table {
color: $black;
- strong {
- color: $black;
+ td {
+ vertical-align: top;
+ }
+
+ .backtrace-row {
+ display: none;
}
}
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index bb10928a037..9ed1600419d 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -1,21 +1,21 @@
-.wiki h1,
-.wiki h2,
-.wiki h3,
-.wiki h4,
-.wiki h5,
-.wiki h6 {
+.md h1,
+.md h2,
+.md h3,
+.md h4,
+.md h5,
+.md h6 {
margin-top: 17px;
}
-.wiki h1 {
+.md h1 {
font-size: 30px;
}
-.wiki h2 {
+.md h2 {
font-size: 22px;
}
-.wiki h3 {
+.md h3 {
font-size: 18px;
font-weight: 600;
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
new file mode 100644
index 00000000000..3648ec5e239
--- /dev/null
+++ b/app/assets/stylesheets/utilities.scss
@@ -0,0 +1,17 @@
+@each $variant, $range in $color-ranges {
+ @each $suffix, $color in $range {
+ #{'.bg-#{$variant}-#{$suffix}'} {
+ background-color: $color;
+ }
+
+ #{'.text-#{$variant}-#{$suffix}'} {
+ color: $color;
+ }
+ }
+}
+
+@each $index, $size in $type-scale {
+ #{'.text-#{$index}'} {
+ font-size: $size;
+ }
+}
diff --git a/app/assets/stylesheets/vendors/atwho.scss b/app/assets/stylesheets/vendors/atwho.scss
new file mode 100644
index 00000000000..ccf3824ea56
--- /dev/null
+++ b/app/assets/stylesheets/vendors/atwho.scss
@@ -0,0 +1,92 @@
+.atwho-view {
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ .name,
+ small.aliases,
+ small.params {
+ float: left;
+ }
+
+ small.aliases,
+ small.params {
+ padding: 2px 5px;
+ }
+
+ small.description {
+ float: right;
+ padding: 3px 5px;
+ }
+
+ .avatar-inline {
+ margin-bottom: 0;
+ }
+
+ .has-warning {
+ .name,
+ .description {
+ color: $orange-700;
+ }
+ }
+
+ .cur {
+ .avatar {
+ @include disable-all-animation;
+ border: 1px solid $white-light;
+ }
+ }
+
+ ul > li {
+ @include clearfix;
+ white-space: nowrap;
+ }
+
+ // TODO: fallback to global style
+ .atwho-view-ul {
+ padding: 8px 1px;
+
+ li {
+ padding: 8px 16px;
+ border: 0;
+
+ &.cur {
+ background-color: $gray-darker;
+ color: $gl-text-color;
+
+ small {
+ color: inherit;
+ }
+
+ &.has-warning {
+ color: $orange-700;
+ background-color: $orange-100;
+ }
+ }
+
+ div.avatar {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ .center {
+ line-height: 14px;
+ }
+ }
+
+ strong {
+ color: $gl-text-color;
+ }
+ }
+ }
+}
+
+@include media-breakpoint-down(xs) {
+ .atwho-view-ul {
+ width: 350px;
+ }
+
+ .atwho-view ul li {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 68e14f0c2e5..7d8016f763d 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -16,7 +16,7 @@ class AbuseReportsController < ApplicationController
if @abuse_report.save
@abuse_report.notify
- message = "Thank you for your report. A GitLab administrator will look into it shortly."
+ message = _("Thank you for your report. A GitLab administrator will look into it shortly.")
redirect_to @abuse_report.user, notice: message
else
render :new
@@ -37,9 +37,9 @@ class AbuseReportsController < ApplicationController
@user = User.find_by(id: params[:user_id])
if @user.nil?
- redirect_to root_path, alert: "Cannot create the abuse report. The user has been deleted."
+ redirect_to root_path, alert: _("Cannot create the abuse report. The user has been deleted.")
elsif @user.blocked?
- redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked."
+ redirect_to @user, alert: _("Cannot create the abuse report. This user has been blocked.")
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/acme_challenges_controller.rb b/app/controllers/acme_challenges_controller.rb
new file mode 100644
index 00000000000..67a39d8870b
--- /dev/null
+++ b/app/controllers/acme_challenges_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AcmeChallengesController < ActionController::Base
+ def show
+ if acme_order
+ render plain: acme_order.challenge_file_content, content_type: 'text/plain'
+ else
+ head :not_found
+ end
+ end
+
+ private
+
+ def acme_order
+ @acme_order ||= PagesDomainAcmeOrder.find_by_domain_and_token(params[:domain], params[:token])
+ end
+end
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 2b9cae21da2..383ec2a7d16 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -14,7 +14,7 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance = Appearance.new(appearance_params)
if @appearance.save
- redirect_to admin_appearances_path, notice: 'Appearance was successfully created.'
+ redirect_to admin_appearances_path, notice: _('Appearance was successfully created.')
else
render action: 'show'
end
@@ -22,7 +22,7 @@ class Admin::AppearancesController < Admin::ApplicationController
def update
if @appearance.update(appearance_params)
- redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.'
+ redirect_to admin_appearances_path, notice: _('Appearance was successfully updated.')
else
render action: 'show'
end
@@ -33,21 +33,21 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance.save
- redirect_to admin_appearances_path, notice: 'Logo was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Logo was successfully removed.')
end
def header_logos
@appearance.remove_header_logo!
@appearance.save
- redirect_to admin_appearances_path, notice: 'Header logo was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Header logo was successfully removed.')
end
def favicon
@appearance.remove_favicon!
@appearance.save
- redirect_to admin_appearances_path, notice: 'Favicon was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Favicon was successfully removed.')
end
private
@@ -78,6 +78,7 @@ class Admin::AppearancesController < Admin::ApplicationController
footer_message
message_background_color
message_font_color
+ email_header_and_footer_enabled
]
end
end
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index ef182b981f1..b742b7e19cf 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -4,10 +4,7 @@
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController
- before_action :authenticate_admin!
- layout 'admin'
+ include EnforcesAdminAuthentication
- def authenticate_admin!
- render_404 unless current_user.admin?
- end
+ layout 'admin'
end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 8f267eccc8a..d5bc723aa8c 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -48,7 +48,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
respond_to do |format|
if successful
format.json { head :ok }
- format.html { redirect_to redirect_path, notice: 'Application settings saved successfully' }
+ format.html { redirect_to redirect_path, notice: _('Application settings saved successfully') }
else
format.json { head :bad_request }
format.html { render :show }
@@ -70,13 +70,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def reset_registration_token
@application_setting.reset_runners_registration_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('New runners registration token has been generated!')
redirect_to admin_runners_path
end
def reset_health_check_token
@application_setting.reset_health_check_access_token!
- flash[:notice] = 'New health check access token has been generated!'
+ flash[:notice] = _('New health check access token has been generated!')
redirect_back_or_default
end
@@ -85,10 +85,17 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to(
admin_application_settings_path,
- notice: 'Started asynchronous removal of all repository check states.'
+ notice: _('Started asynchronous removal of all repository check states.')
)
end
+ # Getting ToS url requires `directory` api call to Let's Encrypt
+ # which could result in 500 error/slow rendering on settings page
+ # Because of that we use separate controller action
+ def lets_encrypt_terms_of_service
+ redirect_to ::Gitlab::LetsEncrypt.terms_of_service_url
+ end
+
private
def set_application_setting
@@ -124,7 +131,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def visible_application_setting_attributes
- ApplicationSettingsHelper.visible_attributes + [
+ [
+ *::ApplicationSettingsHelper.visible_attributes,
+ *::ApplicationSettingsHelper.external_authorization_service_attributes,
+ *lets_encrypt_visible_attributes,
:domain_blacklist_file,
disabled_oauth_sign_in_sources: [],
import_sources: [],
@@ -132,4 +142,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
restricted_visibility_levels: []
]
end
+
+ def lets_encrypt_visible_attributes
+ return [] unless Feature.enabled?(:pages_auto_ssl)
+
+ [
+ :lets_encrypt_notification_email,
+ :lets_encrypt_terms_of_service_accepted
+ ]
+ end
end
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 6fc336714b6..3648c8be426 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -34,7 +34,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
def update
if @application.update(application_params)
- redirect_to admin_application_path(@application), notice: 'Application was successfully updated.'
+ redirect_to admin_application_path(@application), notice: _('Application was successfully updated.')
else
render :edit
end
@@ -42,7 +42,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
def destroy
@application.destroy
- redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.'
+ redirect_to admin_applications_url, status: 302, notice: _('Application was successfully destroyed.')
end
private
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index a91d9a534cd..6e5dd1a1f55 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
@broadcast_message = BroadcastMessage.new(broadcast_message_params)
if @broadcast_message.save
- redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.'
+ redirect_to admin_broadcast_messages_path, notice: _('Broadcast Message was successfully created.')
else
render :index
end
@@ -27,7 +27,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
def update
if @broadcast_message.update(broadcast_message_params)
- redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully updated.'
+ redirect_to admin_broadcast_messages_path, notice: _('Broadcast Message was successfully updated.')
else
render :edit
end
diff --git a/app/controllers/admin/clusters/applications_controller.rb b/app/controllers/admin/clusters/applications_controller.rb
new file mode 100644
index 00000000000..7400cc16175
--- /dev/null
+++ b/app/controllers/admin/clusters/applications_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class Admin::Clusters::ApplicationsController < Clusters::ApplicationsController
+ include EnforcesAdminAuthentication
+
+ private
+
+ def clusterable
+ @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
+ end
+end
diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb
new file mode 100644
index 00000000000..f54933de10f
--- /dev/null
+++ b/app/controllers/admin/clusters_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Admin::ClustersController < Clusters::ClustersController
+ include EnforcesAdminAuthentication
+
+ layout 'admin'
+
+ private
+
+ def clusterable
+ @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
+ end
+end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index 49ce275ad14..180f7d4c803 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -25,7 +25,7 @@ class Admin::DeployKeysController < Admin::ApplicationController
def update
if deploy_key.update(update_params)
- flash[:notice] = 'Deploy key was successfully updated.'
+ flash[:notice] = _('Deploy key was successfully updated.')
redirect_to admin_deploy_keys_path
else
render 'edit'
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 46e85e1424f..15f7ef881c8 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -36,7 +36,7 @@ class Admin::GroupsController < Admin::ApplicationController
if @group.save
@group.add_owner(current_user)
- redirect_to [:admin, @group], notice: "Group '#{@group.name}' was successfully created."
+ redirect_to [:admin, @group], notice: _('Group %{group_name} was successfully created.') % { group_name: @group.name }
else
render "new"
end
@@ -44,7 +44,7 @@ class Admin::GroupsController < Admin::ApplicationController
def update
if @group.update(group_params)
- redirect_to [:admin, @group], notice: 'Group was successfully updated.'
+ redirect_to [:admin, @group], notice: _('Group was successfully updated.')
else
render "edit"
end
@@ -55,7 +55,7 @@ class Admin::GroupsController < Admin::ApplicationController
result = Members::CreateService.new(current_user, member_params.merge(limit: -1)).execute(@group)
if result[:status] == :success
- redirect_to [:admin, @group], notice: 'Users were successfully added.'
+ redirect_to [:admin, @group], notice: _('Users were successfully added.')
else
redirect_to [:admin, @group], alert: result[:message]
end
@@ -66,7 +66,7 @@ class Admin::GroupsController < Admin::ApplicationController
redirect_to admin_groups_path,
status: 302,
- alert: "Group '#{@group.name}' was scheduled for deletion."
+ alert: _('Group %{group_name} was scheduled for deletion.') % { group_name: @group.name }
end
private
@@ -89,7 +89,8 @@ class Admin::GroupsController < Admin::ApplicationController
:request_access_enabled,
:visibility_level,
:require_two_factor_authentication,
- :two_factor_grace_period
+ :two_factor_grace_period,
+ :project_creation_level
]
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index d0abdec50ae..51b0f45c5be 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -14,7 +14,7 @@ class Admin::HooksController < Admin::ApplicationController
@hook = SystemHook.new(hook_params.to_h)
if @hook.save
- redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
+ redirect_to admin_hooks_path, notice: _('Hook was successfully created.')
else
@hooks = SystemHook.all
render :index
@@ -26,7 +26,7 @@ class Admin::HooksController < Admin::ApplicationController
def update
if hook.update(hook_params)
- flash[:notice] = 'System hook was successfully updated.'
+ flash[:notice] = _('System hook was successfully updated.')
redirect_to admin_hooks_path
else
render 'edit'
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index b51c2f678ca..f518f7a657f 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -13,7 +13,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
@identity.user_id = user.id
if @identity.save
- redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
+ redirect_to admin_user_identities_path(@user), notice: _('User identity was successfully created.')
else
render :new
end
@@ -29,7 +29,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
def update
if @identity.update(identity_params)
RepairLdapBlockedUserService.new(@user).execute
- redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.'
+ redirect_to admin_user_identities_path(@user), notice: _('User identity was successfully updated.')
else
render :edit
end
@@ -38,9 +38,9 @@ class Admin::IdentitiesController < Admin::ApplicationController
def destroy
if @identity.destroy
RepairLdapBlockedUserService.new(@user).execute
- redirect_to admin_user_identities_path(@user), status: 302, notice: 'User identity was successfully removed.'
+ redirect_to admin_user_identities_path(@user), status: 302, notice: _('User identity was successfully removed.')
else
- redirect_to admin_user_identities_path(@user), status: 302, alert: 'Failed to remove user identity.'
+ redirect_to admin_user_identities_path(@user), status: 302, alert: _('Failed to remove user identity.')
end
end
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index 706bcc1e549..c35619a944e 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -12,7 +12,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
if @impersonation_token.save
PersonalAccessToken.redis_store!(current_user.id, @impersonation_token.token)
- redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
+ redirect_to admin_user_impersonation_tokens_path, notice: _("A new impersonation token has been created.")
else
set_index_vars
render :index
@@ -23,9 +23,9 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@impersonation_token = finder.find(params[:id])
if @impersonation_token.revoke!
- flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!"
+ flash[:notice] = _("Revoked impersonation token %{token_name}!") % { token_name: @impersonation_token.name }
else
- flash[:alert] = "Could not revoke impersonation token #{@impersonation_token.name}."
+ flash[:alert] = _("Could not revoke impersonation token %{token_name}.") % { token_name: @impersonation_token.name }
end
redirect_to admin_user_impersonation_tokens_path
@@ -49,7 +49,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
- @scopes = Gitlab::Auth.available_scopes(current_user)
+ @scopes = Gitlab::Auth.available_scopes_for(current_user)
@impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index 4e9262ccc96..340eecd7632 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -17,9 +17,9 @@ class Admin::KeysController < Admin::ApplicationController
respond_to do |format|
if key.destroy
- format.html { redirect_to keys_admin_user_path(user), status: 302, notice: 'User key was successfully removed.' }
+ format.html { redirect_to keys_admin_user_path(user), status: 302, notice: _('User key was successfully removed.') }
else
- format.html { redirect_to keys_admin_user_path(user), status: 302, alert: 'Failed to remove user key.' }
+ format.html { redirect_to keys_admin_user_path(user), status: 302, alert: _('Failed to remove user key.') }
end
end
end
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index aa5eae7a474..90c1694fd2e 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -21,7 +21,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::CreateService.new(label_params).execute(template: true)
if @label.persisted?
- redirect_to admin_labels_url, notice: "Label was created"
+ redirect_to admin_labels_url, notice: _("Label was created")
else
render :new
end
@@ -31,7 +31,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
- redirect_to admin_labels_path, notice: 'Label was successfully updated.'
+ redirect_to admin_labels_path, notice: _('Label was successfully updated.')
else
render :edit
end
@@ -43,7 +43,7 @@ class Admin::LabelsController < Admin::ApplicationController
respond_to do |format|
format.html do
- redirect_to admin_labels_path, status: 302, notice: 'Label was removed'
+ redirect_to admin_labels_path, status: 302, notice: _('Label was removed')
end
format.js
end
diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb
index 06b0e6a15a3..704e727b1da 100644
--- a/app/controllers/admin/logs_controller.rb
+++ b/app/controllers/admin/logs_controller.rb
@@ -15,7 +15,8 @@ class Admin::LogsController < Admin::ApplicationController
Gitlab::EnvironmentLogger,
Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger,
- Gitlab::ProjectServiceLogger
+ Gitlab::ProjectServiceLogger,
+ Gitlab::Kubernetes::Logger
]
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 550f29a58d2..70db15916b9 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -3,7 +3,7 @@
class Admin::ProjectsController < Admin::ApplicationController
include MembersPresentation
- before_action :project, only: [:show, :transfer, :repository_check]
+ before_action :project, only: [:show, :transfer, :repository_check, :destroy]
before_action :group, only: [:show, :transfer]
def index
@@ -15,7 +15,7 @@ class Admin::ProjectsController < Admin::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("admin/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("admin/projects/_projects", projects: @projects)
}
end
end
@@ -35,12 +35,21 @@ class Admin::ProjectsController < Admin::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ def destroy
+ ::Projects::DestroyService.new(@project, current_user, {}).async_execute
+ flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
+
+ redirect_to admin_projects_path, status: :found
+ rescue Projects::DestroyService::DestroyError => ex
+ redirect_to admin_projects_path, status: 302, alert: ex.message
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def transfer
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
- @project.reload
+ @project.reset
redirect_to admin_project_path(@project)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -50,7 +59,7 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to(
admin_project_path(@project),
- notice: 'Repository check was triggered.'
+ notice: _('Repository check was triggered.')
)
end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 0b6ff491c66..783c59822f1 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::RunnersController < Admin::ApplicationController
- before_action :runner, except: :index
+ before_action :runner, except: [:index, :tag_list]
def index
finder = Admin::RunnersFinder.new(params: params)
@@ -34,20 +34,26 @@ class Admin::RunnersController < Admin::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ redirect_to admin_runners_path, notice: _('Runner was successfully updated.')
else
- redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ redirect_to admin_runners_path, alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ redirect_to admin_runners_path, notice: _('Runner was successfully updated.')
else
- redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ redirect_to admin_runners_path, alert: _('Runner was not updated.')
end
end
+ def tag_list
+ tags = Autocomplete::ActsAsTaggableOn::TagsFinder.new(params: params).execute
+
+ render json: ActsAsTaggableOn::TagSerializer.new.represent(tags)
+ end
+
private
def runner
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 18d22c95b61..45cf0d3207e 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -14,7 +14,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log.remove_user(deleted_by: current_user)
redirect_to admin_spam_logs_path,
status: 302,
- notice: "User #{spam_log.user.username} was successfully removed."
+ notice: _('User %{username} was successfully removed.') % { username: spam_log.user.username }
else
spam_log.destroy
head :ok
@@ -25,9 +25,9 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log = SpamLog.find(params[:id])
if HamService.new(spam_log).mark_as_ham!
- redirect_to admin_spam_logs_path, notice: 'Spam log successfully submitted as ham.'
+ redirect_to admin_spam_logs_path, notice: _('Spam log successfully submitted as ham.')
else
- redirect_to admin_spam_logs_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to admin_spam_logs_path, alert: _('Error with Akismet. Please check the logs for more info.')
end
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index bfa7c7d0109..a02d0843615 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -39,19 +39,19 @@ class Admin::UsersController < Admin::ApplicationController
warden.set_user(user, scope: :user)
- Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}")
+ Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
- flash[:alert] = "You are now impersonating #{user.username}"
+ flash[:alert] = _("You are now impersonating %{username}") % { username: user.username }
redirect_to root_path
else
flash[:alert] =
if user.blocked?
- "You cannot impersonate a blocked user"
+ _("You cannot impersonate a blocked user")
elsif user.internal?
- "You cannot impersonate an internal user"
+ _("You cannot impersonate an internal user")
else
- "You cannot impersonate a user who cannot log in"
+ _("You cannot impersonate a user who cannot log in")
end
redirect_to admin_user_path(user)
@@ -60,35 +60,35 @@ class Admin::UsersController < Admin::ApplicationController
def block
if update_user { |user| user.block }
- redirect_back_or_admin_user(notice: "Successfully blocked")
+ redirect_back_or_admin_user(notice: _("Successfully blocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not blocked"))
end
end
def unblock
if user.ldap_blocked?
- redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab")
+ redirect_back_or_admin_user(alert: _("This user cannot be unlocked manually from GitLab"))
elsif update_user { |user| user.activate }
- redirect_back_or_admin_user(notice: "Successfully unblocked")
+ redirect_back_or_admin_user(notice: _("Successfully unblocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not unblocked"))
end
end
def unlock
if update_user { |user| user.unlock_access! }
- redirect_back_or_admin_user(alert: "Successfully unlocked")
+ redirect_back_or_admin_user(alert: _("Successfully unlocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not unlocked"))
end
end
def confirm
if update_user { |user| user.confirm }
- redirect_back_or_admin_user(notice: "Successfully confirmed")
+ redirect_back_or_admin_user(notice: _("Successfully confirmed"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not confirmed"))
end
end
@@ -96,7 +96,7 @@ class Admin::UsersController < Admin::ApplicationController
update_user { |user| user.disable_two_factor! }
redirect_to admin_user_path(user),
- notice: 'Two-factor Authentication has been disabled for this user'
+ notice: _('Two-factor Authentication has been disabled for this user')
end
def create
@@ -109,7 +109,7 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
if @user.persisted?
- format.html { redirect_to [:admin, @user], notice: 'User was successfully created.' }
+ format.html { redirect_to [:admin, @user], notice: _('User was successfully created.') }
format.json { render json: @user, status: :created, location: @user }
else
format.html { render "new" }
@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
end
if result[:status] == :success
- format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
+ format.html { redirect_to [:admin, user], notice: _('User was successfully updated.') }
format.json { head :ok }
else
# restore username to keep form action url.
@@ -153,7 +153,7 @@ class Admin::UsersController < Admin::ApplicationController
user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete))
respond_to do |format|
- format.html { redirect_to admin_users_path, status: 302, notice: "The user is being deleted." }
+ format.html { redirect_to admin_users_path, status: 302, notice: _("The user is being deleted.") }
format.json { head :ok }
end
end
@@ -164,11 +164,11 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
if success
- format.html { redirect_back_or_admin_user(notice: 'Successfully removed email.') }
+ format.html { redirect_back_or_admin_user(notice: _('Successfully removed email.')) }
format.json { head :ok }
else
- format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') }
- format.json { render json: 'There was an error removing the e-mail.', status: :bad_request }
+ format.html { redirect_back_or_admin_user(alert: _('There was an error removing the e-mail.')) }
+ format.json { render json: _('There was an error removing the e-mail.'), status: :bad_request }
end
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index af0b0c64814..7321f719deb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -27,6 +27,7 @@ class ApplicationController < ActionController::Base
before_action :check_impersonation_availability
around_action :set_locale
+ around_action :set_session_storage
after_action :set_page_title_header, if: :json_request?
after_action :limit_unauthenticated_session_times
@@ -41,9 +42,12 @@ class ApplicationController < ActionController::Base
:bitbucket_server_import_enabled?,
:google_code_import_enabled?, :fogbugz_import_enabled?,
:git_import_enabled?, :gitlab_project_import_enabled?,
- :manifest_import_enabled?
+ :manifest_import_enabled?, :phabricator_import_enabled?
+ # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
+ # concerns due to caching private data.
DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze
+ DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache".freeze
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -125,7 +129,7 @@ class ApplicationController < ActionController::Base
payload[:ua] = request.env["HTTP_USER_AGENT"]
payload[:remote_ip] = request.remote_ip
- payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
+ payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
logged_user = auth_user
@@ -235,9 +239,9 @@ class ApplicationController < ActionController::Base
end
def no_cache_headers
- response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
- response.headers["Pragma"] = "no-cache"
- response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
+ headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE
+ headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
+ headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
end
def default_headers
@@ -247,10 +251,16 @@ class ApplicationController < ActionController::Base
headers['X-Content-Type-Options'] = 'nosniff'
if current_user
- # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
- # concerns due to caching private data.
- headers['Cache-Control'] = DEFAULT_GITLAB_CACHE_CONTROL
- headers["Pragma"] = "no-cache" # HTTP 1.0 compatibility
+ headers['Cache-Control'] = default_cache_control
+ headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
+ end
+ end
+
+ def default_cache_control
+ if request.xhr?
+ ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL
+ else
+ DEFAULT_GITLAB_CACHE_CONTROL
end
end
@@ -284,7 +294,7 @@ class ApplicationController < ActionController::Base
unless Gitlab::Auth::LDAP::Access.allowed?(current_user)
sign_out current_user
- flash[:alert] = "Access denied for your LDAP account."
+ flash[:alert] = _("Access denied for your LDAP account.")
redirect_to new_user_session_path
end
end
@@ -331,7 +341,7 @@ class ApplicationController < ActionController::Base
def require_email
if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil?
- return redirect_to profile_path, notice: 'Please complete your profile with email address'
+ return redirect_to profile_path, notice: _('Please complete your profile with email address')
end
end
@@ -414,6 +424,10 @@ class ApplicationController < ActionController::Base
Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end
+ def phabricator_import_enabled?
+ Gitlab::PhabricatorImport.available?
+ end
+
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
@@ -425,6 +439,12 @@ class ApplicationController < ActionController::Base
Gitlab::I18n.with_user_locale(current_user, &block)
end
+ def set_session_storage(&block)
+ return yield if sessionless_user?
+
+ Gitlab::Session.with_session(session, &block)
+ end
+
def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 0d5c8657c9e..091327931c2 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AutocompleteController < ApplicationController
- skip_before_action :authenticate_user!, only: [:users, :award_emojis]
+ skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches]
def users
project = Autocomplete::ProjectFinder
@@ -38,4 +38,11 @@ class AutocompleteController < ApplicationController
def award_emojis
render json: AwardedEmojiFinder.new(current_user).execute
end
+
+ def merge_request_target_branches
+ merge_requests = MergeRequestsFinder.new(current_user, params).execute
+ target_branches = merge_requests.recent_target_branches
+
+ render json: target_branches.map { |target_branch| { title: target_branch } }
+ end
end
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
index c4e7fc950f9..16c2365f85d 100644
--- a/app/controllers/clusters/applications_controller.rb
+++ b/app/controllers/clusters/applications_controller.rb
@@ -3,26 +3,54 @@
class Clusters::ApplicationsController < Clusters::BaseController
before_action :cluster
before_action :authorize_create_cluster!, only: [:create]
+ before_action :authorize_update_cluster!, only: [:update]
+ before_action :authorize_admin_cluster!, only: [:destroy]
def create
- Clusters::Applications::CreateService
- .new(@cluster, current_user, create_cluster_application_params)
- .execute(request)
+ request_handler do
+ Clusters::Applications::CreateService
+ .new(@cluster, current_user, cluster_application_params)
+ .execute(request)
+ end
+ end
+
+ def update
+ request_handler do
+ Clusters::Applications::UpdateService
+ .new(@cluster, current_user, cluster_application_params)
+ .execute(request)
+ end
+ end
+
+ def destroy
+ request_handler do
+ Clusters::Applications::DestroyService
+ .new(@cluster, current_user, cluster_application_destroy_params)
+ .execute(request)
+ end
+ end
+
+ private
+
+ def request_handler
+ yield
head :no_content
- rescue Clusters::Applications::CreateService::InvalidApplicationError
+ rescue Clusters::Applications::BaseService::InvalidApplicationError
render_404
rescue StandardError
head :bad_request
end
- private
-
def cluster
@cluster ||= clusterable.clusters.find(params[:id]) || render_404
end
- def create_cluster_application_params
+ def cluster_application_params
params.permit(:application, :hostname, :email)
end
+
+ def cluster_application_destroy_params
+ params.permit(:application)
+ end
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 68a2a83f0de..80ee7c35906 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -123,25 +123,25 @@ class Clusters::ClustersController < Clusters::BaseController
private
def update_params
- if cluster.managed?
+ if cluster.provided_by_user?
params.require(:cluster).permit(
:enabled,
+ :name,
:environment_scope,
:base_domain,
platform_kubernetes_attributes: [
+ :api_url,
+ :token,
+ :ca_cert,
:namespace
]
)
else
params.require(:cluster).permit(
:enabled,
- :name,
:environment_scope,
:base_domain,
platform_kubernetes_attributes: [
- :api_url,
- :token,
- :ca_cert,
:namespace
]
)
@@ -153,6 +153,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
+ :managed,
provider_gcp_attributes: [
:gcp_project_id,
:zone,
@@ -171,6 +172,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
+ :managed,
platform_kubernetes_attributes: [
:namespace,
:api_url,
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 5507328f8ae..4926062f9ca 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -8,13 +8,6 @@
module AuthenticatesWithTwoFactor
extend ActiveSupport::Concern
- included do
- # This action comes from DeviseController, but because we call `sign_in`
- # manually, not skipping this action would cause a "You are already signed
- # in." error message to be shown upon successful login.
- skip_before_action :require_no_authentication, only: [:create], raise: false
- end
-
# Store the user's ID in the session for later retrieval and render the
# two factor code prompt
#
@@ -36,7 +29,7 @@ module AuthenticatesWithTwoFactor
end
def locked_user_redirect(user)
- flash.now[:alert] = 'Invalid Login or password'
+ flash.now[:alert] = _('Invalid Login or password')
render 'devise/sessions/new'
end
@@ -66,7 +59,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
- flash.now[:alert] = 'Invalid two-factor code.'
+ flash.now[:alert] = _('Invalid two-factor code.')
prompt_for_two_factor(user)
end
end
@@ -83,7 +76,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
- flash.now[:alert] = 'Authentication via U2F device failed.'
+ flash.now[:alert] = _('Authentication via U2F device failed.')
prompt_for_two_factor(user)
end
end
diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb
new file mode 100644
index 00000000000..ed7ea2f0e04
--- /dev/null
+++ b/app/controllers/concerns/boards_actions.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module BoardsActions
+ include Gitlab::Utils::StrongMemoize
+ extend ActiveSupport::Concern
+
+ included do
+ include BoardsResponses
+
+ before_action :boards, only: :index
+ before_action :board, only: :show
+ end
+
+ def index
+ respond_with_boards
+ end
+
+ def show
+ # Add / update the board in the recent visits table
+ Boards::Visits::CreateService.new(parent, current_user).execute(board) if request.format.html?
+
+ respond_with_board
+ end
+
+ private
+
+ def boards
+ strong_memoize(:boards) do
+ Boards::ListService.new(parent, current_user).execute
+ end
+ end
+
+ def board
+ strong_memoize(:board) do
+ boards.find(params[:id])
+ end
+ end
+end
diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb
index f0e6adf4dec..54c0510497f 100644
--- a/app/controllers/concerns/continue_params.rb
+++ b/app/controllers/concerns/continue_params.rb
@@ -6,7 +6,7 @@ module ContinueParams
def continue_params
continue_params = params[:continue]
- return nil unless continue_params
+ return unless continue_params
continue_params = continue_params.permit(:to, :notice, :notice_now)
continue_params[:to] = safe_redirect_path(continue_params[:to])
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b3777fd2b0f..e8e681ce649 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -31,7 +31,7 @@ module CreatesCommit
respond_to do |format|
format.html { redirect_to success_path }
- format.json { render json: { message: "success", filePath: success_path } }
+ format.json { render json: { message: _("success"), filePath: success_path } }
end
else
flash[:alert] = result[:message]
@@ -45,7 +45,7 @@ module CreatesCommit
redirect_to failure_path
end
end
- format.json { render json: { message: "failed", filePath: failure_path } }
+ format.json { render json: { message: _("failed"), filePath: failure_path } }
end
end
end
@@ -60,15 +60,22 @@ module CreatesCommit
private
def update_flash_notice(success_notice)
- flash[:notice] = success_notice || "Your changes have been successfully committed."
+ flash[:notice] = success_notice || _("Your changes have been successfully committed.")
if create_merge_request?
- if merge_request_exists?
- flash[:notice] = nil
- else
- target = different_project? ? "project" : "branch"
- flash[:notice] = flash[:notice] + " You can now submit a merge request to get this change into the original #{target}."
- end
+ flash[:notice] =
+ if merge_request_exists?
+ nil
+ else
+ mr_message =
+ if different_project?
+ _("You can now submit a merge request to get this change into the original project.")
+ else
+ _("You can now submit a merge request to get this change into the original branch.")
+ end
+
+ flash[:notice] += " " + mr_message
+ end
end
end
diff --git a/app/controllers/concerns/enforces_admin_authentication.rb b/app/controllers/concerns/enforces_admin_authentication.rb
new file mode 100644
index 00000000000..3ef92730df6
--- /dev/null
+++ b/app/controllers/concerns/enforces_admin_authentication.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# == EnforcesAdminAuthentication
+#
+# Controller concern to enforce that users are authenticated as admins
+#
+# Upon inclusion, adds `authenticate_admin!` as a before_action
+#
+module EnforcesAdminAuthentication
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authenticate_admin!
+ end
+
+ def authenticate_admin!
+ render_404 unless current_user.admin?
+ end
+end
diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb
index 71bdef8ce03..0fddf15d197 100644
--- a/app/controllers/concerns/enforces_two_factor_authentication.rb
+++ b/app/controllers/concerns/enforces_two_factor_authentication.rb
@@ -16,7 +16,7 @@ module EnforcesTwoFactorAuthentication
end
def check_two_factor_requirement
- if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor?
+ if two_factor_authentication_required? && current_user && !current_user.temp_oauth_email? && !current_user.two_factor_enabled? && !skip_two_factor?
redirect_to profile_two_factor_auth_path
end
end
diff --git a/app/controllers/concerns/import_url_params.rb b/app/controllers/concerns/import_url_params.rb
new file mode 100644
index 00000000000..e51e4157f50
--- /dev/null
+++ b/app/controllers/concerns/import_url_params.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ImportUrlParams
+ def import_url_params
+ return {} unless params.dig(:project, :import_url).present?
+
+ { import_url: import_params_to_full_url(params[:project]) }
+ end
+
+ def import_params_to_full_url(params)
+ Gitlab::UrlSanitizer.new(
+ params[:import_url],
+ credentials: {
+ user: params[:import_url_user],
+ password: params[:import_url_password]
+ }
+ ).full_url
+ end
+end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index cd3fa641e89..065d2d3a4ec 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -8,7 +8,7 @@ module IssuableActions
before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
before_action only: :show do
- push_frontend_feature_flag(:reply_to_individual_notes)
+ push_frontend_feature_flag(:scoped_labels, default_enabled: true)
end
end
@@ -192,12 +192,7 @@ module IssuableActions
def bulk_update_params
permitted_keys_array = permitted_keys.dup
-
- if resource_name == 'issue'
- permitted_keys_array << { assignee_ids: [] }
- else
- permitted_keys_array.unshift(:assignee_id)
- end
+ permitted_keys_array << { assignee_ids: [] }
params.require(:update).permit(permitted_keys_array)
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index c529aabf797..9cf25915e92 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -41,6 +41,7 @@ module IssuableCollections
return if pagination_disabled?
@issuables = @issuables.page(params[:page])
+ @issuables = per_page_for_relative_position if params[:sort] == 'relative_position'
@issuable_meta_data = issuable_meta_data(@issuables, collection_type)
@total_pages = issuable_page_count
end
@@ -80,6 +81,11 @@ module IssuableCollections
(row_count.to_f / limit).ceil
end
+ # manual / relative_position sorting allows for 100 items on the page
+ def per_page_for_relative_position
+ @issuables.per(100) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
def issuable_finder_for(finder_class)
finder_class.new(current_user, finder_options)
end
@@ -100,6 +106,7 @@ module IssuableCollections
if @project
options[:project_id] = @project.id
+ options[:attempt_project_search_optimizations] = true
elsif @group
options[:group_id] = @group.id
options[:include_subgroups] = true
@@ -189,15 +196,15 @@ module IssuableCollections
end
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def preload_for_collection
+ common_attributes = [:author, :assignees, :labels, :milestone]
@preload_for_collection ||= case collection_type
when 'Issue'
- [:project, :author, :assignees, :labels, :milestone, project: :namespace]
+ common_attributes + [:project, project: :namespace]
when 'MergeRequest'
- [
- :target_project, :author, :assignee, :labels, :milestone,
- source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits
- ]
+ common_attributes + [:target_project, source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits]
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 57e444319e0..f7137a04437 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -26,7 +26,7 @@ module LfsRequest
render(
json: {
- message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
+ message: _('Git LFS is not enabled on this GitLab server, contact your admin.'),
documentation_url: help_url
},
status: :not_implemented
@@ -51,7 +51,7 @@ module LfsRequest
def render_lfs_forbidden
render(
json: {
- message: 'Access forbidden. Check your access level.',
+ message: _('Access forbidden. Check your access level.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
@@ -62,7 +62,7 @@ module LfsRequest
def render_lfs_not_found
render(
json: {
- message: 'Not found.',
+ message: _('Not found.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 6402e01ddc0..0b2756c0c6a 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -9,7 +9,7 @@ module MembershipActions
result = Members::CreateService.new(current_user, create_params).execute(membershipable)
if result[:status] == :success
- redirect_to members_page_url, notice: 'Users were successfully added.'
+ redirect_to members_page_url, notice: _('Users were successfully added.')
else
redirect_to members_page_url, alert: result[:message]
end
@@ -35,9 +35,16 @@ module MembershipActions
respond_to do |format|
format.html do
- source = source_type == 'group' ? 'group and any subresources' : source_type
+ message =
+ begin
+ case membershipable
+ when Namespace
+ _("User was successfully removed from group and any subresources.")
+ else
+ _("User was successfully removed from project.")
+ end
+ end
- message = "User was successfully removed from #{source}."
redirect_to members_page_url, notice: message
end
@@ -49,7 +56,7 @@ module MembershipActions
membershipable.request_access(current_user)
redirect_to polymorphic_path(membershipable),
- notice: 'Your request for access has been queued for review.'
+ notice: _('Your request for access has been queued for review.')
end
def approve_access_request
@@ -68,9 +75,9 @@ module MembershipActions
notice =
if member.request?
- "Your access request to the #{source_type} has been withdrawn."
+ _("Your access request to the %{source_type} has been withdrawn.") % { source_type: source_type }
else
- "You left the \"#{membershipable.human_name}\" #{source_type}."
+ _("You left the \"%{membershipable_human_name}\" %{source_type}.") % { membershipable_human_name: membershipable.human_name, source_type: source_type }
end
respond_to do |format|
@@ -90,9 +97,9 @@ module MembershipActions
if member.invite?
member.resend_invite
- redirect_to members_page_url, notice: 'The invitation was successfully resent.'
+ redirect_to members_page_url, notice: _('The invitation was successfully resent.')
else
- redirect_to members_page_url, alert: 'The invitation has already been accepted.'
+ redirect_to members_page_url, alert: _('The invitation has already been accepted.')
end
end
@@ -125,6 +132,16 @@ module MembershipActions
end
def source_type
- @source_type ||= membershipable.class.to_s.humanize(capitalize: false)
+ @source_type ||=
+ begin
+ case membershipable
+ when Namespace
+ _("group")
+ when Project
+ _("project")
+ else
+ raise "Unknown membershipable type: #{membershipable}!"
+ end
+ end
end
end
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index eccbe35577b..8b8b7db72f8 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -8,7 +8,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", {
- merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: true
})
end
@@ -26,16 +26,22 @@ module MilestoneActions
end
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def labels
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
+ milestone_labels = @milestone.issue_labels_visible_by_user(current_user)
+
render json: tabs_json("shared/milestones/_labels_tab", {
- labels: @milestone.labels # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ labels: milestone_labels.map do |label|
+ label.present(issuable_subject: @milestone.parent)
+ end
})
end
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
private
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index b4fee93713b..f96d1821095 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -48,7 +48,7 @@ module NotesActions
respond_to do |format|
format.json do
json = {
- commands_changes: @note.commands_changes
+ commands_changes: @note.commands_changes&.slice(:emoji_award, :time_estimate, :spend_time)
}
if @note.persisted? && return_discussion?
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index f72d25fc54c..2a9729b6ffd 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -20,7 +20,7 @@ module PreviewMarkdown
body: view_context.markdown(result[:text], markdown_params),
references: {
users: result[:users],
- suggestions: result[:suggestions],
+ suggestions: SuggestionSerializer.new.represent_diff(result[:suggestions]),
commands: view_context.markdown(result[:commands])
}
}
diff --git a/app/controllers/concerns/project_unauthorized.rb b/app/controllers/concerns/project_unauthorized.rb
index f59440dbc59..7238840440f 100644
--- a/app/controllers/concerns/project_unauthorized.rb
+++ b/app/controllers/concerns/project_unauthorized.rb
@@ -1,10 +1,21 @@
# frozen_string_literal: true
module ProjectUnauthorized
- extend ActiveSupport::Concern
+ module ControllerActions
+ def self.on_routable_not_found
+ lambda do |routable|
+ return unless routable.is_a?(Project)
- # EE would override this
- def project_unauthorized_proc
- # no-op
+ label = routable.external_authorization_classification_label
+ rejection_reason = nil
+
+ unless ::Gitlab::ExternalAuthorization.access_allowed?(current_user, label)
+ rejection_reason = ::Gitlab::ExternalAuthorization.rejection_reason(current_user, label)
+ rejection_reason ||= _('External authorization denied access to this project')
+ end
+
+ access_denied!(rejection_reason) if rejection_reason
+ end
+ end
end
end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index ce36da6b715..18015b1de88 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -16,7 +16,7 @@ module RendersNotes
private
def preload_max_access_for_authors(notes, project)
- return nil unless project
+ return unless project
user_ids = notes.map(&:author_id)
project.team.max_member_access_for_user_ids(user_ids)
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 5624eb3aa45..ff9b0332c97 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -3,15 +3,13 @@
module RoutableActions
extend ActiveSupport::Concern
- def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil, not_found_or_authorized_proc: nil)
+ def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil)
routable = routable_klass.find_by_full_path(requested_full_path, follow_redirects: request.get?)
if routable_authorized?(routable, extra_authorization_proc)
ensure_canonical_path(routable, requested_full_path)
routable
else
- if not_found_or_authorized_proc
- not_found_or_authorized_proc.call(routable)
- end
+ perform_not_found_actions(routable, not_found_actions)
route_not_found unless performed?
@@ -19,6 +17,18 @@ module RoutableActions
end
end
+ def not_found_actions
+ [ProjectUnauthorized::ControllerActions.on_routable_not_found]
+ end
+
+ def perform_not_found_actions(routable, actions)
+ actions.each do |action|
+ break if performed?
+
+ instance_exec(routable, &action)
+ end
+ end
+
def routable_authorized?(routable, extra_authorization_proc)
return false unless routable
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index c3a1b12af84..a8ffa33f1c7 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -12,9 +12,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+ redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
else
- redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
end
end
@@ -33,7 +33,7 @@ module SpammableActions
ensure_spam_config_loaded!
if params[:recaptcha_verification]
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
end
respond_to do |format|
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 4ec0e94df9a..59f6d3452a3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -16,7 +16,7 @@ module UploadsActions
end
else
format.json do
- render json: 'Invalid file.', status: :unprocessable_entity
+ render json: _('Invalid file.'), status: :unprocessable_entity
end
end
end
@@ -57,7 +57,7 @@ module UploadsActions
render json: authorized
rescue SocketError
- render json: "Error uploading file", status: :internal_server_error
+ render json: _("Error uploading file"), status: :internal_server_error
end
private
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 2c4aab67448..2ae500a2fdf 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -22,7 +22,7 @@ class ConfirmationsController < Devise::ConfirmationsController
after_sign_in(resource)
else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
- flash[:notice] = flash[:notice] + " Please sign in."
+ flash[:notice] = flash[:notice] + _(" Please sign in.")
new_session_path(:user, anchor: 'login-pane')
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index b1d224d026f..65d14781d92 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -6,21 +6,22 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
+ before_action :projects, only: [:index]
before_action :default_sorting
skip_cross_project_access_check :index, :starred
def index
- @projects = load_projects(params.merge(non_public: true))
-
respond_to do |format|
- format.html
+ format.html do
+ render_projects
+ end
format.atom do
load_events
render layout: 'xml.atom'
end
format.json do
render json: {
- html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("dashboard/projects/_projects", projects: @projects)
}
end
end
@@ -37,7 +38,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("dashboard/projects/_projects", projects: @projects)
}
end
end
@@ -46,6 +47,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
+ def projects
+ @projects ||= load_projects(params.merge(non_public: true))
+ end
+
+ def render_projects
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40260
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ render
+ end
+ end
+
def default_sorting
params[:sort] ||= 'latest_activity_desc'
@sort = params[:sort]
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 3fa582cf25b..f173c263474 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -21,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
format.html do
redirect_to dashboard_todos_path,
status: 302,
- notice: 'Todo was successfully marked as done.'
+ notice: _('Todo was successfully marked as done.')
end
format.js { head :ok }
format.json { render json: todos_counts }
@@ -32,7 +32,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, status: 302, notice: 'All todos were marked as done.' }
+ format.html { redirect_to dashboard_todos_path, status: 302, notice: _('All todos were marked as done.') }
format.js { head :ok }
format.json { render json: todos_counts.merge(updated_ids: updated_ids) }
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 75329b05a6f..1a97b39d3ae 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -46,7 +46,10 @@ class DashboardController < Dashboard::ApplicationController
end
def check_filters_presence!
- @no_filters_set = finder_type.scalar_params.none? { |k| params.key?(k) }
+ no_scalar_filters_set = finder_type.scalar_params.none? { |k| params.key?(k) }
+ no_array_filters_set = finder_type.array_params.none? { |k, _| params.key?(k) }
+
+ @no_filters_set = no_scalar_filters_set && no_array_filters_set
return unless @no_filters_set
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index f3d76c5a478..ef86d5f981a 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -15,7 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
@@ -30,7 +30,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
@@ -44,7 +44,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
diff --git a/app/controllers/google_api/authorizations_controller.rb b/app/controllers/google_api/authorizations_controller.rb
index dd9f5af61b3..ed0995e7ffd 100644
--- a/app/controllers/google_api/authorizations_controller.rb
+++ b/app/controllers/google_api/authorizations_controller.rb
@@ -2,6 +2,10 @@
module GoogleApi
class AuthorizationsController < ApplicationController
+ include Gitlab::Utils::StrongMemoize
+
+ before_action :validate_session_key!
+
def callback
token, expires_at = GoogleApi::CloudPlatform::Client
.new(nil, callback_google_api_auth_url)
@@ -11,21 +15,27 @@ module GoogleApi
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
expires_at.to_s
- state_redirect_uri = redirect_uri_from_session_key(params[:state])
-
- if state_redirect_uri
- redirect_to state_redirect_uri
- else
- redirect_to root_path
- end
+ redirect_to redirect_uri_from_session
end
private
- def redirect_uri_from_session_key(state)
- key = GoogleApi::CloudPlatform::Client
- .session_key_for_redirect_uri(params[:state])
- session[key] if key
+ def validate_session_key!
+ access_denied! unless redirect_uri_from_session.present?
+ end
+
+ def redirect_uri_from_session
+ strong_memoize(:redirect_uri_from_session) do
+ if params[:state].present?
+ session[session_key_for_redirect_uri(params[:state])]
+ else
+ nil
+ end
+ end
+ end
+
+ def session_key_for_redirect_uri(state)
+ GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
end
end
end
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 3ef03bc9622..1ce0afac83b 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -3,18 +3,21 @@
class GraphqlController < ApplicationController
# Unauthenticated users have access to the API for public data
skip_before_action :authenticate_user!
- prepend_before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
+
+ # Allow missing CSRF tokens, this would mean that if a CSRF is invalid or missing,
+ # the user won't be authenticated but can proceed as an anonymous user.
+ #
+ # If a CSRF is valid, the user is authenticated. This makes it easier to play
+ # around in GraphiQL.
+ protect_from_forgery with: :null_session, only: :execute
before_action :check_graphql_feature_flag!
+ before_action :authorize_access_api!
+ before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
def execute
- variables = Gitlab::Graphql::Variables.new(params[:variables]).to_h
- query = params[:query]
- operation_name = params[:operationName]
- context = {
- current_user: current_user
- }
- result = GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
+ result = multiplex? ? execute_multiplex : execute_query
+
render json: result
end
@@ -30,6 +33,48 @@ class GraphqlController < ApplicationController
private
+ def execute_multiplex
+ GitlabSchema.multiplex(multiplex_queries, context: context)
+ end
+
+ def execute_query
+ variables = build_variables(params[:variables])
+ operation_name = params[:operationName]
+
+ GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
+ end
+
+ def query
+ params[:query]
+ end
+
+ def multiplex_queries
+ params[:_json].map do |single_query_info|
+ {
+ query: single_query_info[:query],
+ variables: build_variables(single_query_info[:variables]),
+ operation_name: single_query_info[:operationName],
+ context: context
+ }
+ end
+ end
+
+ def context
+ @context ||= { current_user: current_user }
+ end
+
+ def build_variables(variable_info)
+ Gitlab::Graphql::Variables.new(variable_info).to_h
+ end
+
+ def multiplex?
+ params[:_json].present?
+ end
+
+ def authorize_access_api!
+ access_denied!("API not accessible for user.") unless can?(current_user, :access_api)
+ end
+
# Overridden from the ApplicationController to make the response look like
# a GraphQL response. That is nicely picked up in Graphiql.
def render_404
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 51fdb6c05fb..40b8d5ed72c 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -1,53 +1,16 @@
# frozen_string_literal: true
class Groups::BoardsController < Groups::ApplicationController
- include BoardsResponses
+ include BoardsActions
include RecordUserLastActivity
before_action :assign_endpoint_vars
- before_action :boards, only: :index
- before_action :redirect_to_recent_board, only: :index
-
- def index
- respond_with_boards
- end
-
- def show
- @board = boards.find(params[:id])
-
- # add/update the board in the recent visited table
- Boards::Visits::CreateService.new(@board.group, current_user).execute(@board) if request.format.html?
-
- respond_with_board
- end
private
- def boards
- @boards ||= Boards::ListService.new(group, current_user).execute
- end
-
def assign_endpoint_vars
@boards_endpoint = group_boards_url(group)
@namespace_path = group.to_param
@labels_endpoint = group_labels_url(group)
end
-
- def serialize_as_json(resource)
- resource.as_json(only: [:id])
- end
-
- def includes_board?(board_id)
- boards.any? { |board| board.id == board_id }
- end
-
- def redirect_to_recent_board
- return if request.format.json?
-
- recently_visited = Boards::Visits::LatestService.new(group, current_user).execute
-
- if recently_visited && includes_board?(recently_visited.board_id)
- redirect_to(group_board_path(id: recently_visited.board_id), status: :found)
- end
- end
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 0bc082246a1..f1d6fb00cfc 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -12,6 +12,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
+ skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite,
:override
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index dd8fbf7a029..f8e32451b02 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -16,7 +16,7 @@ class Groups::RunnersController < Groups::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to group_runner_path(@group, @runner), notice: 'Runner was successfully updated.'
+ redirect_to group_runner_path(@group, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -30,17 +30,17 @@ class Groups::RunnersController < Groups::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index f476f428fdb..c465e622de0 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -13,7 +13,17 @@ module Groups
def reset_registration_token
@group.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('GroupSettings|New runners registration token has been generated!')
+ redirect_to group_settings_ci_cd_path
+ end
+
+ def update_auto_devops
+ if auto_devops_service.execute
+ flash[:notice] = s_('GroupSettings|Auto DevOps pipeline was updated for the group')
+ else
+ flash[:alert] = s_("GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." % { error_messages: group.errors.full_messages })
+ end
+
redirect_to group_settings_ci_cd_path
end
@@ -29,6 +39,14 @@ module Groups
def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, group)
end
+
+ def auto_devops_params
+ params.require(:group).permit(:auto_devops_enabled)
+ end
+
+ def auto_devops_service
+ Groups::AutoDevopsService.new(group, current_user, auto_devops_params)
+ end
end
end
end
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
index 4f641de0357..11e3cfb01e4 100644
--- a/app/controllers/groups/variables_controller.rb
+++ b/app/controllers/groups/variables_controller.rb
@@ -41,7 +41,7 @@ module Groups
end
def variable_params_attributes
- %i[id key secret_value protected _destroy]
+ %i[id variable_type key secret_value protected masked _destroy]
end
def authorize_admin_build!
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 4e50106398a..e936d771502 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -58,11 +58,24 @@ class GroupsController < Groups::ApplicationController
def show
respond_to do |format|
- format.html
+ format.html do
+ render_show_html
+ end
format.atom do
- load_events
- render layout: 'xml.atom'
+ render_details_view_atom
+ end
+ end
+ end
+
+ def details
+ respond_to do |format|
+ format.html do
+ render_details_html
+ end
+
+ format.atom do
+ render_details_view_atom
end
end
end
@@ -111,14 +124,27 @@ class GroupsController < Groups::ApplicationController
flash[:notice] = "Group '#{@group.name}' was successfully transferred."
redirect_to group_path(@group)
else
- flash.now[:alert] = service.error
- render :edit
+ flash[:alert] = service.error
+ redirect_to edit_group_path(@group)
end
end
# rubocop: enable CodeReuse/ActiveRecord
protected
+ def render_show_html
+ render 'groups/show'
+ end
+
+ def render_details_html
+ render 'groups/show'
+ end
+
+ def render_details_view_atom
+ load_events
+ render layout: 'xml.atom', template: 'groups/show'
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def authorize_create_group!
allowed = if params[:parent_id].present?
@@ -161,7 +187,8 @@ class GroupsController < Groups::ApplicationController
:create_chat_team,
:chat_team_name,
:require_two_factor_authentication,
- :two_factor_grace_period
+ :two_factor_grace_period,
+ :project_creation_level
]
end
@@ -178,8 +205,8 @@ class GroupsController < Groups::ApplicationController
.includes(:namespace)
@events = EventCollection
- .new(@projects, offset: params[:offset].to_i, filter: event_filter)
- .to_a
+ .new(@projects, offset: params[:offset].to_i, filter: event_filter)
+ .to_a
Events::RenderService
.new(current_user)
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index a9d6addd4a4..837c26c630a 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -7,7 +7,7 @@ class HelpController < ApplicationController
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
- YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
+ YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
def index
# Remove YAML frontmatter so that it doesn't look weird
@@ -22,7 +22,7 @@ class HelpController < ApplicationController
end
def show
- @path = clean_path_info(path_params[:path])
+ @path = Rack::Utils.clean_path_info(path_params[:path])
respond_to do |format|
format.any(:markdown, :md, :html) do
@@ -75,35 +75,4 @@ class HelpController < ApplicationController
params
end
-
- PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
-
- # Taken from ActionDispatch::FileHandler
- # Cleans up the path, to prevent directory traversal outside the doc folder.
- def clean_path_info(path_info)
- parts = path_info.split(PATH_SEPS)
-
- clean = []
-
- # Walk over each part of the path
- parts.each do |part|
- # Turn `one//two` or `one/./two` into `one/two`.
- next if part.empty? || part == '.'
-
- if part == '..'
- # Turn `one/two/../` into `one`
- clean.pop
- else
- # Add simple folder names to the clean path.
- clean << part
- end
- end
-
- # If the path was an absolute path (i.e. `/` or `/one/two`),
- # add `/` to the front of the clean path.
- clean.unshift '/' if parts.empty? || parts.first.empty?
-
- # Join all the clean path parts by the path separator.
- ::File.join(*clean)
- end
end
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 2b1395f364f..293d76ea765 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index f333e43b892..f71ea8642cd 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -15,8 +15,8 @@ class Import::BitbucketServerController < Import::BaseController
# (https://community.atlassian.com/t5/Answers-Developer-Questions/stash-repository-names/qaq-p/499054)
#
# Bitbucket Server starts personal project names with a tilde.
- VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/
- VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/
+ VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/.freeze
+ VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/.freeze
def new
end
@@ -25,7 +25,7 @@ class Import::BitbucketServerController < Import::BaseController
repo = bitbucket_client.repo(@project_key, @repo_slug)
unless repo
- return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity
+ return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
end
project_name = params[:new_name].presence || repo.name
@@ -41,10 +41,10 @@ class Import::BitbucketServerController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
- rescue BitbucketServer::Connection::ConnectionError => e
- render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
+ rescue BitbucketServer::Connection::ConnectionError => error
+ render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity
end
def configure
@@ -65,8 +65,8 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- rescue BitbucketServer::Connection::ConnectionError => e
- flash[:alert] = "Unable to connect to server: #{e}"
+ rescue BitbucketServer::Connection::ConnectionError => error
+ flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data
redirect_to new_import_bitbucket_server_path
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 5a439e6de78..a37ba682b91 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -14,7 +14,7 @@ class Import::FogbugzController < Import::BaseController
res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
rescue
# If the URI is invalid various errors can occur
- return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL'
+ return redirect_to new_import_fogbugz_path, alert: _('Could not connect to FogBugz, check your URL')
end
session[:fogbugz_token] = res.get_token
session[:fogbugz_uri] = params[:uri]
@@ -29,14 +29,14 @@ class Import::FogbugzController < Import::BaseController
user_map = params[:users]
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
- flash.now[:alert] = 'All users must have a name.'
+ flash.now[:alert] = _('All users must have a name.')
return render 'new_user_map'
end
session[:fogbugz_user_map] = user_map
- flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
+ flash[:notice] = _('The user map has been saved. Continue by selecting the projects you want to import.')
redirect_to status_import_fogbugz_path
end
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index 68ad8650dba..a23b2f8139e 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -46,7 +46,7 @@ class Import::GiteaController < Import::GithubController
def provider_auth
if session[access_token_key].blank? || provider_url.blank?
redirect_to new_import_gitea_url,
- alert: 'You need to specify both an Access Token and a Host URL.'
+ alert: _('You need to specify both an Access Token and a Host URL.')
end
end
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 498de0b07b8..5ec8e9e6fc5 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -42,7 +42,7 @@ class Import::GitlabController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 354fba5d204..89889141be6 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -13,7 +13,7 @@ class Import::GitlabProjectsController < Import::BaseController
def create
unless file_is_valid?
- return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive (ending in .gz)." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a GitLab project export archive (ending in .gz).") })
end
@project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
@@ -21,7 +21,7 @@ class Import::GitlabProjectsController < Import::BaseController
if @project.saved?
redirect_to(
project_path(@project),
- notice: "Project '#{@project.name}' is being imported."
+ notice: _("Project '%{project_name}' is being imported.") % { project_name: @project.name }
)
else
redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" })
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 331f06c3dd6..4dddfbcd20d 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -11,18 +11,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
- return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a Google Takeout archive.") })
end
begin
dump = JSON.parse(dump_file.read)
rescue
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
session[:google_code_dump] = dump
@@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController
begin
user_map = JSON.parse(user_map_json)
rescue
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
@@ -62,7 +62,7 @@ class Import::GoogleCodeController < Import::BaseController
session[:google_code_user_map] = user_map
- flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import."
+ flash[:notice] = _("The user map has been saved. Continue by selecting the projects you want to import.")
redirect_to status_import_google_code_path
end
diff --git a/app/controllers/import/phabricator_controller.rb b/app/controllers/import/phabricator_controller.rb
new file mode 100644
index 00000000000..d1c04817689
--- /dev/null
+++ b/app/controllers/import/phabricator_controller.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class Import::PhabricatorController < Import::BaseController
+ include ImportHelper
+
+ before_action :verify_import_enabled
+
+ def new
+ end
+
+ def create
+ @project = Gitlab::PhabricatorImport::ProjectCreator
+ .new(current_user, import_params).execute
+
+ if @project&.persisted?
+ redirect_to @project
+ else
+ @name = params[:name]
+ @path = params[:path]
+ @errors = @project&.errors&.full_messages || [_("Invalid import params")]
+
+ render :new
+ end
+ end
+
+ def verify_import_enabled
+ render_404 unless phabricator_import_enabled?
+ end
+
+ private
+
+ def import_params
+ params.permit(:path, :phabricator_server_url, :api_token, :name, :namespace_id)
+ end
+end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 315d1375e02..a78d87eceea 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -13,9 +13,9 @@ class InvitesController < ApplicationController
if member.accept_invite!(current_user)
label, path = source_info(member.source)
- redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
+ redirect_to path, notice: _("You have been granted %{member_human_access} access to %{label}.") % { member_human_access: member.human_access, label: label }
else
- redirect_back_or_default(options: { alert: "The invitation could not be accepted." })
+ redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") })
end
end
@@ -30,9 +30,9 @@ class InvitesController < ApplicationController
new_user_session_path
end
- redirect_to path, notice: "You have declined the invitation to join #{label}."
+ redirect_to path, notice: _("You have declined the invitation to join %{label}.") % { label: label }
else
- redirect_back_or_default(options: { alert: "The invitation could not be declined." })
+ redirect_back_or_default(options: { alert: _("The invitation could not be declined.") })
end
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index f9008a5b67e..5ecf4f114cf 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -22,7 +22,7 @@ class JwtController < ApplicationController
private
def authenticate_project_or_user
- @authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_authentication_abilities)
+ @authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_only_authentication_abilities)
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
@@ -39,9 +39,9 @@ class JwtController < ApplicationController
render json: {
errors: [
{ code: 'UNAUTHORIZED',
- message: "HTTP Basic: Access denied\n" \
- "You must use a personal access token with 'api' scope for Git over HTTP.\n" \
- "You can generate one at #{profile_personal_access_tokens_url}" }
+ message: _('HTTP Basic: Access denied\n' \
+ 'You must use a personal access token with \'api\' scope for Git over HTTP.\n' \
+ 'You can generate one at %{profile_personal_access_tokens_url}') % { profile_personal_access_tokens_url: profile_personal_access_tokens_url } }
]
}, status: :unauthorized
end
diff --git a/app/controllers/ldap/omniauth_callbacks_controller.rb b/app/controllers/ldap/omniauth_callbacks_controller.rb
index 5e872804448..9a5a45939e0 100644
--- a/app/controllers/ldap/omniauth_callbacks_controller.rb
+++ b/app/controllers/ldap/omniauth_callbacks_controller.rb
@@ -26,7 +26,7 @@ class Ldap::OmniauthCallbacksController < OmniauthCallbacksController
override :fail_login
def fail_login(user)
- flash[:alert] = 'Access denied for your LDAP account.'
+ flash[:alert] = _('Access denied for your LDAP account.')
redirect_to new_user_session_path
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index cc2bb99f55b..2a8dd997d04 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -3,6 +3,7 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
+ include AuthHelper
protect_from_forgery except: [:kerberos, :saml, :cas3, :failure], with: :exception, prepend: true
@@ -80,11 +81,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
if current_user
+ return render_403 unless link_provider_allowed?(oauth['provider'])
+
log_audit_event(current_user, with: oauth['provider'])
identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth)
- identity_linker.link
+ link_identity(identity_linker)
if identity_linker.changed?
redirect_identity_linked
@@ -98,16 +101,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
+ def link_identity(identity_linker)
+ identity_linker.link
+ end
+
def redirect_identity_exists
redirect_to after_sign_in_path_for(current_user)
end
def redirect_identity_link_failed(error_message)
- redirect_to profile_account_path, notice: "Authentication failed: #{error_message}"
+ redirect_to profile_account_path, notice: _("Authentication failed: %{error_message}") % { error_message: error_message }
end
def redirect_identity_linked
- redirect_to profile_account_path, notice: 'Authentication method updated'
+ redirect_to profile_account_path, notice: _('Authentication method updated')
end
def handle_service_ticket(provider, ticket)
@@ -145,10 +152,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_signup_error
label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
- message = ["Signing in using your #{label} account without a pre-existing GitLab account is not allowed."]
+ message = [_("Signing in using your %{label} account without a pre-existing GitLab account is not allowed.") % { label: label }]
if Gitlab::CurrentSettings.allow_signup?
- message << "Create a GitLab account first, and then connect it to your #{label} account."
+ message << _("Create a GitLab account first, and then connect it to your %{label} account.") % { label: label }
end
flash[:notice] = message.join(' ')
@@ -166,14 +173,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
def fail_auth0_login
- flash[:alert] = 'Wrong extern UID provided. Make sure Auth0 is configured correctly.'
+ flash[:alert] = _('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
redirect_to new_user_session_path
end
def handle_disabled_provider
label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
- flash[:alert] = "Signing in using #{label} has been disabled"
+ flash[:alert] = _("Signing in using %{label} has been disabled") % { label: label }
redirect_to new_user_session_path
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 28f113b5cbe..77de5cb45c9 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -22,7 +22,7 @@ class PasswordsController < Devise::PasswordsController
).first_or_initialize
unless user.reset_password_period_valid?
- flash[:alert] = 'Your password reset token has expired.'
+ flash[:alert] = _('Your password reset token has expired.')
redirect_to(new_user_password_url(user_email: user['email']))
end
end
@@ -52,7 +52,7 @@ class PasswordsController < Devise::PasswordsController
end
redirect_to after_sending_reset_password_instructions_path_for(resource_name),
- alert: "Password authentication is unavailable."
+ alert: _("Password authentication is unavailable.")
end
def throttle_reset
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index b0d65f284af..b03f4b7435f 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -14,10 +14,10 @@ class Profiles::AccountsController < Profiles::ApplicationController
return render_404 unless identity
- if unlink_allowed?(provider)
+ if unlink_provider_allowed?(provider)
identity.destroy
else
- flash[:alert] = "You are not allowed to unlink your primary login account"
+ flash[:alert] = _("You are not allowed to unlink your primary login account")
end
redirect_to profile_account_path
diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb
index efe7ede5efa..c473023cacb 100644
--- a/app/controllers/profiles/active_sessions_controller.rb
+++ b/app/controllers/profiles/active_sessions_controller.rb
@@ -2,15 +2,6 @@
class Profiles::ActiveSessionsController < Profiles::ApplicationController
def index
- @sessions = ActiveSession.list(current_user)
- end
-
- def destroy
- ActiveSession.destroy(current_user, params[:id])
-
- respond_to do |format|
- format.html { redirect_to profile_active_sessions_url, status: :found }
- format.js { head :ok }
- end
+ @sessions = ActiveSession.list(current_user).reject(&:is_impersonated)
end
end
diff --git a/app/controllers/profiles/chat_names_controller.rb b/app/controllers/profiles/chat_names_controller.rb
index 2e78b9e6dc7..80b8279e91e 100644
--- a/app/controllers/profiles/chat_names_controller.rb
+++ b/app/controllers/profiles/chat_names_controller.rb
@@ -15,9 +15,9 @@ class Profiles::ChatNamesController < Profiles::ApplicationController
new_chat_name = current_user.chat_names.new(chat_name_params)
if new_chat_name.save
- flash[:notice] = "Authorized #{new_chat_name.chat_name}"
+ flash[:notice] = _("Authorized %{new_chat_name}") % { new_chat_name: new_chat_name.chat_name }
else
- flash[:alert] = "Could not authorize chat nickname. Try again!"
+ flash[:alert] = _("Could not authorize chat nickname. Try again!")
end
delete_chat_name_token
@@ -27,7 +27,7 @@ class Profiles::ChatNamesController < Profiles::ApplicationController
def deny
delete_chat_name_token
- flash[:notice] = "Denied authorization of chat nickname #{chat_name_params[:user_name]}."
+ flash[:notice] = _("Denied authorization of chat nickname %{user_name}.") % { user_name: chat_name_params[:user_name] }
redirect_to profile_chat_names_path
end
@@ -36,9 +36,9 @@ class Profiles::ChatNamesController < Profiles::ApplicationController
@chat_name = chat_names.find(params[:id])
if @chat_name.destroy
- flash[:notice] = "Deleted chat nickname: #{@chat_name.chat_name}!"
+ flash[:notice] = _("Deleted chat nickname: %{chat_name}!") % { chat_name: @chat_name.chat_name }
else
- flash[:alert] = "Could not delete chat nickname #{@chat_name.chat_name}."
+ flash[:alert] = _("Could not delete chat nickname %{chat_name}.") % { chat_name: @chat_name.chat_name }
end
redirect_to profile_chat_names_path, status: :found
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
new file mode 100644
index 00000000000..c755bcb718a
--- /dev/null
+++ b/app/controllers/profiles/groups_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class Profiles::GroupsController < Profiles::ApplicationController
+ include RoutableActions
+
+ def update
+ group = find_routable!(Group, params[:id])
+ notification_setting = current_user.notification_settings.find_by(source: group) # rubocop: disable CodeReuse/ActiveRecord
+
+ if notification_setting.update(update_params)
+ flash[:notice] = "Notification settings for #{group.name} saved"
+ else
+ flash[:alert] = "Failed to save new settings for #{group.name}"
+ end
+
+ redirect_back_or_default(default: profile_notifications_path)
+ end
+
+ private
+
+ def update_params
+ params.require(:notification_setting).permit(:notification_email)
+ end
+end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index b719b70c56e..617e5bb7cb3 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -14,9 +14,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController
result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
if result[:status] == :success
- flash[:notice] = "Notification settings saved"
+ flash[:notice] = _("Notification settings saved")
else
- flash[:alert] = "Failed to save new settings"
+ flash[:alert] = _("Failed to save new settings")
end
redirect_back_or_default(default: profile_notifications_path)
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index a0391d677c4..d2787c2e450 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -14,7 +14,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
def create
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to new_profile_password_path, alert: _('You must provide a valid current password')
return
end
@@ -29,7 +29,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
if result[:status] == :success
Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute
- redirect_to root_path, notice: 'Password successfully changed'
+ redirect_to root_path, notice: _('Password successfully changed')
else
render :new
end
@@ -45,24 +45,24 @@ class Profiles::PasswordsController < Profiles::ApplicationController
password_attributes[:password_automatically_set] = false
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to edit_profile_password_path, alert: _('You must provide a valid current password')
return
end
result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
- flash[:notice] = "Password was successfully updated. Please login with it"
+ flash[:notice] = _('Password was successfully updated. Please login with it')
redirect_to new_user_session_path
else
- @user.reload
+ @user.reset
render 'edit'
end
end
def reset
current_user.send_reset_password_instructions
- redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions'
+ redirect_to edit_profile_password_path, notice: _('We sent you an email with reset password instructions')
end
private
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 4b6ec2697b7..f1c07cd9a1d 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -11,7 +11,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
if @personal_access_token.save
PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
- redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
+ redirect_to profile_personal_access_tokens_path, notice: _("Your new personal access token has been created.")
else
set_index_vars
render :index
@@ -22,9 +22,9 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = finder.find(params[:id])
if @personal_access_token.revoke!
- flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
+ flash[:notice] = _("Revoked personal access token %{personal_access_token_name}!") % { personal_access_token_name: @personal_access_token.name }
else
- flash[:alert] = "Could not revoke personal access token #{@personal_access_token.name}."
+ flash[:alert] = _("Could not revoke personal access token %{personal_access_token_name}.") % { personal_access_token_name: @personal_access_token.name }
end
redirect_to profile_personal_access_tokens_path
@@ -42,7 +42,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
- @scopes = Gitlab::Auth.available_scopes(current_user)
+ @scopes = Gitlab::Auth.available_scopes_for(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 0227af2c266..62f98d9e549 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -11,13 +11,13 @@ class Profiles::PreferencesController < Profiles::ApplicationController
result = Users::UpdateService.new(current_user, preferences_params.merge(user: user)).execute
if result[:status] == :success
- flash[:notice] = 'Preferences saved.'
+ flash[:notice] = _('Preferences saved.')
else
- flash[:alert] = 'Failed to save preferences.'
+ flash[:alert] = _('Failed to save preferences.')
end
rescue ArgumentError => e
# Raised when `dashboard` is given an invalid value.
- flash[:alert] = "Failed to save preferences (#{e.message})."
+ flash[:alert] = _("Failed to save preferences (%{error_message}).") % { error_message: e.message }
end
respond_to do |format|
@@ -44,7 +44,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:project_view,
:theme_id,
:first_day_of_week,
- :preferred_language
+ :preferred_language,
+ :time_display_relative,
+ :time_format_in_24h
]
end
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index ba94196b2f9..95b9344c551 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -18,21 +18,16 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
two_factor_authentication_reason(
global: lambda do
flash.now[:alert] =
- 'The global settings require you to enable Two-Factor Authentication for your account.'
+ _('The global settings require you to enable Two-Factor Authentication for your account.')
end,
group: lambda do |groups|
- group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence
-
- flash.now[:alert] = %{
- The group settings for #{group_links} require you to enable
- Two-Factor Authentication for your account.
- }.html_safe
+ flash.now[:alert] = groups_notification(groups)
end
)
unless two_factor_grace_period_expired?
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = flash.now[:alert] + " You need to do this before #{l(grace_period_deadline)}."
+ flash.now[:alert] = flash.now[:alert] + _(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
end
end
@@ -49,7 +44,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
render 'create'
else
- @error = 'Invalid pin code'
+ @error = _('Invalid pin code')
@qr_code = build_qr_code
setup_u2f_registration
render 'show'
@@ -63,7 +58,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
if @u2f_registration.persisted?
session.delete(:challenges)
- redirect_to profile_two_factor_auth_path, notice: "Your U2F device was registered!"
+ redirect_to profile_two_factor_auth_path, notice: s_("Your U2F device was registered!")
else
@qr_code = build_qr_code
setup_u2f_registration
@@ -85,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def skip
if two_factor_grace_period_expired?
- redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
+ redirect_to new_profile_two_factor_auth_path, alert: s_('Cannot skip two factor authentication setup')
else
session[:skip_two_factor] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
redirect_to root_path
@@ -126,4 +121,12 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def u2f_registration_params
params.require(:u2f_registration).permit(:device_response, :name)
end
+
+ def groups_notification(groups)
+ group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence
+ leave_group_links = groups.map { |group| view_context.link_to (s_("leave %{group_name}") % { group_name: group.full_name }), leave_group_members_path(group), remote: false, method: :delete}.to_sentence
+
+ s_(%{The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}.})
+ .html_safe % { group_links: group_links.html_safe, leave_group_links: leave_group_links.html_safe }
+ end
end
diff --git a/app/controllers/profiles/u2f_registrations_controller.rb b/app/controllers/profiles/u2f_registrations_controller.rb
index e6a154fb6aa..866c4dee6e2 100644
--- a/app/controllers/profiles/u2f_registrations_controller.rb
+++ b/app/controllers/profiles/u2f_registrations_controller.rb
@@ -4,6 +4,6 @@ class Profiles::U2fRegistrationsController < Profiles::ApplicationController
def destroy
u2f_registration = current_user.u2f_registrations.find(params[:id])
u2f_registration.destroy
- redirect_to profile_two_factor_auth_path, status: 302, notice: "Successfully deleted U2F device."
+ redirect_to profile_two_factor_auth_path, status: 302, notice: _("Successfully deleted U2F device.")
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index b9c52618d4b..1d16ddb1608 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -15,7 +15,7 @@ class ProfilesController < Profiles::ApplicationController
result = Users::UpdateService.new(current_user, user_params.merge(user: @user)).execute
if result[:status] == :success
- message = "Profile was successfully updated"
+ message = s_("Profiles|Profile was successfully updated")
format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
format.json { render json: { message: message } }
@@ -31,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController
user.reset_incoming_email_token!
end
- flash[:notice] = "Incoming email token was successfully reset"
+ flash[:notice] = s_("Profiles|Incoming email token was successfully reset")
redirect_to profile_personal_access_tokens_path
end
@@ -41,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController
user.reset_feed_token!
end
- flash[:notice] = 'Feed token was successfully reset'
+ flash[:notice] = s_('Profiles|Feed token was successfully reset')
redirect_to profile_personal_access_tokens_path
end
@@ -106,6 +106,7 @@ class ProfilesController < Profiles::ApplicationController
:organization,
:private_profile,
:include_private_contributions,
+ :timezone,
status: [:emoji, :message]
)
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index e0677ce3fbc..80e4f54bbf4 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -3,7 +3,6 @@
class Projects::ApplicationController < ApplicationController
include CookiesHelper
include RoutableActions
- include ProjectUnauthorized
include ChecksCollaboration
skip_before_action :authenticate_user!
@@ -17,12 +16,12 @@ class Projects::ApplicationController < ApplicationController
def project
return @project if @project
- return nil unless params[:project_id] || params[:id]
+ return unless params[:project_id] || params[:id]
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
- @project = find_routable!(Project, path, extra_authorization_proc: auth_proc, not_found_or_authorized_proc: project_unauthorized_proc)
+ @project = find_routable!(Project, path, extra_authorization_proc: auth_proc)
end
def build_canonical_path(project)
@@ -88,4 +87,10 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
+
+ def allow_gitaly_ref_name_caching
+ ::Gitlab::GitalyClient.allow_ref_name_caching do
+ yield
+ end
+ end
end
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index 9c130af8394..0e3f13045ce 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Projects::AutocompleteSourcesController < Projects::ApplicationController
+ before_action :authorize_read_milestone!, only: :milestones
+
def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 77672e7d9fc..b04ffe80db4 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,6 +9,8 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
prepend_before_action :authenticate_user!, only: [:edit]
+ around_action :allow_gitaly_ref_name_caching, only: [:show]
+
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
@@ -29,7 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
- create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
+ create_commit(Files::CreateService, success_notice: _("The file has been successfully created."),
success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
failure_path: project_new_blob_path(@project, @ref))
@@ -81,7 +83,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
+ create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."),
success_path: -> { after_delete_path },
failure_view: :show,
failure_path: project_blob_path(@project, @id))
@@ -90,65 +92,21 @@ class Projects::BlobController < Projects::ApplicationController
def diff
apply_diff_view_cookie!
- @blob.load_all_data!
- @lines = @blob.present.highlight.lines
-
- @form = UnfoldForm.new(params.to_unsafe_h)
-
- @lines = @lines[@form.since - 1..@form.to - 1].map(&:html_safe)
-
- if @form.bottom?
- @match_line = ''
- else
- lines_length = @lines.length - 1
- line = [@form.since, lines_length].join(',')
- @match_line = "@@ -#{line}+#{line} @@"
- end
+ @form = Blobs::UnfoldPresenter.new(blob, params.to_unsafe_h)
- # We can keep only 'render_diff_lines' from this conditional when
+ # keep only json rendering when
# https://gitlab.com/gitlab-org/gitlab-ce/issues/44988 is done
if rendered_for_merge_request?
- render_diff_lines
+ render json: DiffLineSerializer.new.represent(@form.diff_lines)
else
+ @lines = @form.lines
+ @match_line = @form.match_line_text
render layout: false
end
end
private
- # Converts a String array to Gitlab::Diff::Line array
- def render_diff_lines
- @lines.map! do |line|
- # These are marked as context lines but are loaded from blobs.
- # We also have context lines loaded from diffs in other places.
- diff_line = Gitlab::Diff::Line.new(line, nil, nil, nil, nil)
- diff_line.rich_text = line
- diff_line
- end
-
- add_match_line
-
- render json: DiffLineSerializer.new.represent(@lines)
- end
-
- def add_match_line
- return unless @form.unfold?
-
- if @form.bottom? && @form.to < @blob.lines.size
- old_pos = @form.to - @form.offset
- new_pos = @form.to
- elsif @form.since != 1
- old_pos = new_pos = @form.since
- end
-
- # Match line is not needed when it reaches the top limit or bottom limit of the file.
- return unless new_pos
-
- @match_line = Gitlab::Diff::Line.new(@match_line, 'match', nil, old_pos, new_pos)
-
- @form.bottom? ? @lines.push(@match_line) : @lines.unshift(@match_line)
- end
-
def blob
@blob ||= @repository.blob_at(@commit.id, @path)
@@ -216,8 +174,7 @@ class Projects::BlobController < Projects::ApplicationController
end
if params[:file].present?
- params[:content] = Base64.encode64(params[:file].read)
- params[:encoding] = 'base64'
+ params[:content] = params[:file]
end
@commit_params = {
@@ -231,6 +188,8 @@ class Projects::BlobController < Projects::ApplicationController
end
def validate_diff_params
+ return if params[:full]
+
if [:since, :to, :offset].any? { |key| params[key].blank? }
head :ok
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 8189b5d182a..95897aaf980 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,34 +1,15 @@
# frozen_string_literal: true
class Projects::BoardsController < Projects::ApplicationController
- include BoardsResponses
+ include BoardsActions
include IssuableCollections
before_action :check_issues_available!
before_action :authorize_read_board!, only: [:index, :show]
- before_action :boards, only: :index
before_action :assign_endpoint_vars
- before_action :redirect_to_recent_board, only: :index
-
- def index
- respond_with_boards
- end
-
- def show
- @board = boards.find(params[:id])
-
- # add/update the board in the recent visited table
- Boards::Visits::CreateService.new(@board.project, current_user).execute(@board) if request.format.html?
-
- respond_with_board
- end
private
- def boards
- @boards ||= Boards::ListService.new(project, current_user).execute
- end
-
def assign_endpoint_vars
@boards_endpoint = project_boards_path(project)
@bulk_issues_path = bulk_update_project_issues_path(project)
@@ -39,22 +20,4 @@ class Projects::BoardsController < Projects::ApplicationController
def authorize_read_board!
access_denied! unless can?(current_user, :read_board, project)
end
-
- def serialize_as_json(resource)
- resource.as_json(only: [:id])
- end
-
- def includes_board?(board_id)
- boards.any? { |board| board.id == board_id }
- end
-
- def redirect_to_recent_board
- return if request.format.json?
-
- recently_visited = Boards::Visits::LatestService.new(project, current_user).execute
-
- if recently_visited && includes_board?(recently_visited.board_id)
- redirect_to(namespace_project_board_path(id: recently_visited.board_id), status: :found)
- end
- end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 32b7f3207ef..fc708400657 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -25,7 +25,7 @@ class Projects::BranchesController < Projects::ApplicationController
@refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
- # n+1: https://gitlab.com/gitlab-org/gitaly/issues/992
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/48097
Gitlab::GitalyClient.allow_n_plus_1_calls do
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
@@ -53,7 +53,7 @@ class Projects::BranchesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def create
- branch_name = sanitize(strip_tags(params[:branch_name]))
+ branch_name = strip_tags(sanitize(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
redirect_to_autodeploy = project.empty_repo? && project.deployment_platform.present?
@@ -100,14 +100,14 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html do
- flash_type = result[:status] == :error ? :alert : :notice
- flash[flash_type] = result[:message]
+ flash_type = result.error? ? :alert : :notice
+ flash[flash_type] = result.message
redirect_to project_branches_path(@project), status: :see_other
end
- format.js { head result[:return_code] }
- format.json { render json: { message: result[:message] }, status: result[:return_code] }
+ format.js { head result.http_status }
+ format.json { render json: { message: result.message }, status: result.http_status }
end
end
@@ -115,14 +115,14 @@ class Projects::BranchesController < Projects::ApplicationController
DeleteMergedBranchesService.new(@project, current_user).async_execute
redirect_to project_branches_path(@project),
- notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ notice: _('Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.')
end
private
def ref
if params[:ref]
- ref_escaped = sanitize(strip_tags(params[:ref]))
+ ref_escaped = strip_tags(sanitize(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
@project.default_branch || 'master'
@@ -143,7 +143,7 @@ class Projects::BranchesController < Projects::ApplicationController
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
- redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
+ redirect_to project_branches_filtered_path(@project, state: 'all'), notice: _('Update your bookmarked URLs as filtered/sorted branches URL has been changed.')
end
end
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index c7b6218d007..2a04b007304 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class Projects::Clusters::ApplicationsController < Clusters::ApplicationsController
- include ProjectUnauthorized
-
prepend_before_action :project
private
@@ -12,6 +10,6 @@ class Projects::Clusters::ApplicationsController < Clusters::ApplicationsControl
end
def project
- @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]))
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index feda6deeaa6..98cd66cf6f9 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -1,11 +1,13 @@
# frozen_string_literal: true
class Projects::ClustersController < Clusters::ClustersController
- include ProjectUnauthorized
-
prepend_before_action :project
before_action :repository
+ before_action do
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ end
+
layout 'project'
private
@@ -15,7 +17,7 @@ class Projects::ClustersController < Clusters::ClustersController
end
def project
- @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]))
end
def repository
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index b13c0ae3967..939a09d4fd2 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def merge_requests
- @merge_requests = @commit.merge_requests.map do |mr|
+ @merge_requests = MergeRequestsFinder.new(
+ current_user,
+ project_id: @project.id,
+ commit_sha: @commit.sha
+ ).execute.map do |mr|
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title }
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 2510a31c9b3..f540ccee386 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -7,6 +7,7 @@ class Projects::CommitsController < Projects::ApplicationController
include RendersCommits
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
+ around_action :allow_gitaly_ref_name_caching
before_action :whitelist_query_limiting, except: :commits_root
before_action :require_non_empty_project
before_action :assign_ref_vars, except: :commits_root
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 6824a07dc76..514b03e23b5 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -38,7 +38,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def update
if deploy_key.update(update_params)
- flash[:notice] = 'Deploy key was successfully updated.'
+ flash[:notice] = _('Deploy key was successfully updated.')
redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
else
render 'edit'
diff --git a/app/controllers/projects/environments/prometheus_api_controller.rb b/app/controllers/projects/environments/prometheus_api_controller.rb
new file mode 100644
index 00000000000..f8ef23cd83e
--- /dev/null
+++ b/app/controllers/projects/environments/prometheus_api_controller.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+class Projects::Environments::PrometheusApiController < Projects::ApplicationController
+ before_action :authorize_read_prometheus!
+ before_action :environment
+
+ def proxy
+ result = Prometheus::ProxyService.new(
+ environment,
+ proxy_method,
+ proxy_path,
+ proxy_params
+ ).execute
+
+ if result.nil?
+ return render status: :accepted, json: {
+ status: _('processing'),
+ message: _('Not ready yet. Try again later.')
+ }
+ end
+
+ if result[:status] == :success
+ render status: result[:http_status], json: result[:body]
+ else
+ render(
+ status: result[:http_status] || :bad_request,
+ json: { status: result[:status], message: result[:message] }
+ )
+ end
+ end
+
+ private
+
+ def query_context
+ Gitlab::Prometheus::QueryVariables.call(environment)
+ end
+
+ def environment
+ @environment ||= project.environments.find(params[:id])
+ end
+
+ def proxy_method
+ request.method
+ end
+
+ def proxy_path
+ params[:proxy_path]
+ end
+
+ def proxy_params
+ substitute_query_variables(params).permit!
+ end
+
+ def substitute_query_variables(params)
+ query = params[:query]
+ return params unless query
+
+ params.merge(query: query % query_context)
+ end
+end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index e9cd475a199..e002a4d349b 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -10,6 +10,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
+ before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
+ push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
+ push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
+ push_frontend_feature_flag(:grafana_dashboard_link)
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ end
def index
@environments = project.environments
@@ -114,7 +120,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
terminal = environment.terminals.try(:first)
if terminal
set_workhorse_internal_api_content_type
- render json: Gitlab::Workhorse.terminal_websocket(terminal)
+ render json: Gitlab::Workhorse.channel_websocket(terminal)
else
render html: 'Not found', status: :not_found
end
@@ -131,13 +137,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics
- # Currently, this acts as a hint to load the metrics details into the cache
- # if they aren't there already
- @metrics = environment.metrics || {}
-
respond_to do |format|
format.html
format.json do
+ # Currently, this acts as a hint to load the metrics details into the cache
+ # if they aren't there already
+ @metrics = environment.metrics || {}
+
render json: @metrics, status: @metrics.any? ? :ok : :no_content
end
end
@@ -146,13 +152,40 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def additional_metrics
respond_to do |format|
format.json do
- additional_metrics = environment.additional_metrics || {}
+ additional_metrics = environment.additional_metrics(*metrics_params) || {}
render json: additional_metrics, status: additional_metrics.any? ? :ok : :no_content
end
end
end
+ def metrics_dashboard
+ return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, project)
+
+ if Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
+ result = dashboard_finder.find(project, current_user, environment, params[:dashboard])
+
+ result[:all_dashboards] = project.repository.metrics_dashboard_paths
+ else
+ result = dashboard_finder.find(project, current_user, environment)
+ end
+
+ respond_to do |format|
+ if result[:status] == :success
+ format.json do
+ render status: :ok, json: result.slice(:all_dashboards, :dashboard, :status)
+ end
+ else
+ format.json do
+ render(
+ status: result[:http_status],
+ json: result.slice(:all_dashboards, :message, :status)
+ )
+ end
+ end
+ end
+ end
+
def search
respond_to do |format|
format.json do
@@ -186,6 +219,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment ||= project.environments.find(params[:id])
end
+ def metrics_params
+ params.require([:start, :end])
+ end
+
+ def dashboard_finder
+ Gitlab::Metrics::Dashboard::Finder
+ end
+
def search_environment_names
return [] unless params[:query]
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index d439db97252..956093b972b 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -15,6 +15,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
+ skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
@@ -78,24 +79,28 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def parse_repo_path
- @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
+ @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \
- "You must use a personal access token with 'api' scope for Git over HTTP.\n" \
+ "You must use a personal access token with 'read_repository' or 'write_repository' scope for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: :unauthorized
end
def repository
- wiki? ? project.wiki.repository : project.repository
+ repo_type.repository_for(project)
end
def wiki?
- parse_repo_path unless defined?(@wiki)
+ repo_type.wiki?
+ end
+
+ def repo_type
+ parse_repo_path unless defined?(@repo_type)
- @wiki
+ @repo_type
end
def handle_basic_authentication(login, password)
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 30e436365de..e519cc1f158 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -4,6 +4,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
include WorkhorseRequest
before_action :access_check
+ prepend_before_action :deny_head_requests, only: [:info_refs]
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404
@@ -20,6 +21,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
# POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack
+ enqueue_fetch_statistics_update
+
render_ok
end
@@ -30,6 +33,10 @@ class Projects::GitHttpController < Projects::GitHttpClientController
private
+ def deny_head_requests
+ head :forbidden if request.head?
+ end
+
def download_request?
upload_pack?
end
@@ -48,7 +55,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def render_ok
set_workhorse_internal_api_content_type
- render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name)
+ render json: Gitlab::Workhorse.git_http_ok(repository, repo_type, user, action_name)
end
def render_403(exception)
@@ -67,6 +74,13 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :service_unavailable
end
+ def enqueue_fetch_statistics_update
+ return if wiki?
+ return unless project.daily_statistics_enabled?
+
+ ProjectDailyStatisticsWorker.perform_async(project.id)
+ end
+
def access
@access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities,
@@ -85,7 +99,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access_klass
- @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ @access_klass ||= repo_type.access_checker_class
end
def project_path
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index c80fce513f6..67d3f49af18 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -46,12 +46,8 @@ class Projects::GraphsController < Projects::ApplicationController
def get_languages
@languages =
- if @project.repository_languages.present?
- @project.repository_languages.map do |lang|
- { value: lang.share, label: lang.name, color: lang.color, highlight: lang.color }
- end
- else
- @project.repository.languages
+ ::Projects::RepositoryLanguagesService.new(@project, current_user).execute.map do |lang|
+ { value: lang.share, label: lang.name, color: lang.color, highlight: lang.color }
end
end
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 7c713c19762..dc65f9959db 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -13,11 +13,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
if group
- return render_404 unless can?(current_user, :read_group, group)
+ result = Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
+ return render_404 if result[:http_status] == 404
- Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
+ flash[:alert] = result[:message] if result[:http_status] == 409
else
- flash[:alert] = 'Please select a group.'
+ flash[:alert] = _('Please select a group.')
end
redirect_to project_project_members_path(project)
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index bc84418b79f..5fa0339f44d 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -32,7 +32,7 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update(hook_params)
- flash[:notice] = 'Hook was successfully updated.'
+ flash[:notice] = _('Hook was successfully updated.')
redirect_to project_settings_integrations_path(@project)
else
render 'edit'
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 8b33fa85c1e..afbf9fd7720 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -2,6 +2,7 @@
class Projects::ImportsController < Projects::ApplicationController
include ContinueParams
+ include ImportUrlParams
# Authorize
before_action :authorize_admin_project!
@@ -14,7 +15,7 @@ class Projects::ImportsController < Projects::ApplicationController
def create
if @project.update(import_params)
- @project.import_state.reload.schedule
+ @project.import_state.reset.schedule
end
redirect_to project_import_path(@project)
@@ -42,9 +43,9 @@ class Projects::ImportsController < Projects::ApplicationController
def finished_notice
if @project.forked?
- 'The project was successfully forked.'
+ _('The project was successfully forked.')
else
- 'The project was successfully imported.'
+ _('The project was successfully imported.')
end
end
@@ -67,10 +68,12 @@ class Projects::ImportsController < Projects::ApplicationController
end
def import_params_attributes
- [:import_url]
+ []
end
def import_params
- params.require(:project).permit(import_params_attributes)
+ params.require(:project)
+ .permit(import_params_attributes)
+ .merge(import_url_params)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b9d02a62fc3..b4d89db20c5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,11 +10,11 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
include RecordUserLastActivity
- def self.issue_except_actions
+ def issue_except_actions
%i[index calendar new create bulk_update import_csv]
end
- def self.set_issuables_index_only_actions
+ def set_issuables_index_only_actions
%i[index calendar]
end
@@ -25,9 +25,9 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
- before_action :issue, except: issue_except_actions
+ before_action :issue, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
- before_action :set_issuables_index, only: set_issuables_index_only_actions
+ before_action :set_issuables_index, if: ->(c) { c.set_issuables_index_only_actions.include?(c.action_name.to_sym) }
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -39,6 +39,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
before_action :authorize_import_issues!, only: [:import_csv]
+ before_action :authorize_download_code!, only: [:related_branches]
before_action :set_suggested_issues_feature_flags, only: [:new]
@@ -95,9 +96,9 @@ class Projects::IssuesController < Projects::ApplicationController
if service.discussions_to_resolve.count(&:resolved?) > 0
flash[:notice] = if service.discussion_to_resolve_id
- "Resolved 1 discussion."
+ _("Resolved 1 discussion.")
else
- "Resolved all discussions."
+ _("Resolved all discussions.")
end
end
@@ -131,18 +132,6 @@ class Projects::IssuesController < Projects::ApplicationController
render_conflict_response
end
- def referenced_merge_requests
- @merge_requests, @closed_by_merge_requests = ::Issues::ReferencedMergeRequestsService.new(project, current_user).execute(issue)
-
- respond_to do |format|
- format.json do
- render json: {
- html: view_to_html_string('projects/issues/_merge_requests')
- }
- end
- end
- end
-
def related_branches
@related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue)
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index d5ce790e2d9..2a4933e7bc2 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -122,7 +122,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),
- notice: "Job has been successfully erased!"
+ notice: _("Job has been successfully erased!")
else
respond_422
end
@@ -157,7 +157,7 @@ class Projects::JobsController < Projects::ApplicationController
# GET .../terminal.ws : implemented in gitlab-workhorse
def terminal_websocket_authorize
set_workhorse_internal_api_content_type
- render json: Gitlab::Workhorse.terminal_websocket(@build.terminal_specification)
+ render json: Gitlab::Workhorse.channel_websocket(@build.terminal_specification)
end
private
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 640038818f2..386a1f00bd2 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -132,7 +132,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to(project_labels_path(@project),
- notice: 'Failed to promote label due to internal error. Please contact administrators.')
+ notice: _('Failed to promote label due to internal error. Please contact administrators.'))
end
format.js
end
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index be40077d389..42c415757f9 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -26,7 +26,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def deprecated
render(
json: {
- message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
+ message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
status: :not_implemented
@@ -62,7 +62,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
else
object[:error] = {
code: 404,
- message: "Object does not exist on the server or you don't have permissions to access it"
+ message: _("Object does not exist on the server or you don't have permissions to access it")
}
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 6045ee4e171..f2a6268b3e9 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -7,11 +7,15 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
private
- # rubocop: disable CodeReuse/ActiveRecord
def merge_request
- @issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id])
+ @issuable =
+ @merge_request ||=
+ merge_request_includes(@project.merge_requests).find_by_iid!(params[:id])
+ end
+
+ def merge_request_includes(association)
+ association.includes(:metrics, :assignees, author: :status) # rubocop:disable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes)
@@ -20,7 +24,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
def merge_request_params_attributes
[
:allow_collaboration,
- :assignee_id,
:description,
:force_remove_source_branch,
:lock_version,
@@ -35,6 +38,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:title,
:discussion_locked,
label_ids: [],
+ assignee_ids: [],
update_task: [:index, :checked, :line_number, :line_source]
]
end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index 045a4e974fe..011ac9a42f8 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -16,12 +16,12 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
render json: @conflicts_list
elsif @merge_request.can_be_merged?
render json: {
- message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
+ message: _('The merge conflicts for this merge request have already been resolved. Please return to the merge request.'),
type: 'error'
}
else
render json: {
- message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
+ message: _('The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.'),
type: 'error'
}
end
@@ -43,7 +43,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
return render_404 unless @conflicts_list.can_be_resolved_in_ui?
if @merge_request.can_be_merged?
- render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
+ render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') }
return
end
@@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
.new(merge_request)
.execute(current_user, params)
- flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+ flash[:notice] = _('All merge conflicts were resolved. The merge request can now be merged.')
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 518d41bd3fb..456d2c34768 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -46,8 +46,8 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
# rubocop: disable CodeReuse/ActiveRecord
def commit
- return nil unless commit_id = params[:commit_id].presence
- return nil unless @merge_request.all_commits.exists?(sha: commit_id)
+ return unless commit_id = params[:commit_id].presence
+ return unless @merge_request.all_commits.exists?(sha: commit_id)
@commit ||= @project.commit(commit_id)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 46a44841c31..135117926be 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -16,9 +16,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
- before_action only: [:show] do
- push_frontend_feature_flag(:diff_tree_filtering, default_enabled: true)
- end
+ around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
def index
@merge_requests = @issuables
@@ -35,7 +33,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def show
close_merge_request_if_no_source_project
- mark_merge_request_mergeable
+ @merge_request.check_mergeability
respond_to do |format|
format.html do
@@ -100,20 +98,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def test_reports
- result = @merge_request.compare_test_reports
-
- case result[:status]
- when :parsing
- Gitlab::PollingInterval.set_header(response, interval: 3000)
-
- render json: '', status: :no_content
- when :parsed
- render json: result[:data].to_json, status: :ok
- when :error
- render json: { status_reason: result[:status_reason] }, status: :bad_request
- else
- render json: { status_reason: 'Unknown error' }, status: :internal_server_error
- end
+ reports_response(@merge_request.compare_test_reports)
end
def edit
@@ -160,14 +145,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render partial: 'projects/merge_requests/widget/commit_change_content', layout: false
end
- def cancel_merge_when_pipeline_succeeds
- unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
+ def cancel_auto_merge
+ unless @merge_request.can_cancel_auto_merge?(current_user)
return access_denied!
end
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(@project, current_user)
- .cancel(@merge_request)
+ AutoMergeService.new(project, current_user).cancel(@merge_request)
render json: serialize_widget(@merge_request)
end
@@ -244,12 +227,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge_params_attributes
- [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash]
+ [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash, :auto_merge_strategy]
end
- def merge_when_pipeline_succeeds_active?
- params[:merge_when_pipeline_succeeds].present? &&
- @merge_request.head_pipeline && @merge_request.head_pipeline.active?
+ def auto_merge_requested?
+ # Support params[:merge_when_pipeline_succeeds] during the transition period
+ params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end
def close_merge_request_if_no_source_project
@@ -268,14 +251,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.has_no_commits? && !@merge_request.target_branch_exists?
end
- def mark_merge_request_mergeable
- @merge_request.check_if_can_be_merged
- end
-
def merge!
- # Disable the CI check if merge_when_pipeline_succeeds is enabled since we have
+ # Disable the CI check if auto_merge_strategy is specified since we have
# to wait until CI completes to know
- unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?)
+ unless @merge_request.mergeable?(skip_ci_check: auto_merge_requested?)
return :failed
end
@@ -289,24 +268,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
- if params[:merge_when_pipeline_succeeds].present?
- return :failed unless @merge_request.actual_head_pipeline
-
- if @merge_request.actual_head_pipeline.active?
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(@project, current_user, merge_params)
- .execute(@merge_request)
-
- :merge_when_pipeline_succeeds
- elsif @merge_request.actual_head_pipeline.success?
- # This can be triggered when a user clicks the auto merge button while
- # the tests finish at about the same time
- @merge_request.merge_async(current_user.id, merge_params)
-
- :success
- else
- :failed
- end
+ if auto_merge_requested?
+ AutoMergeService.new(project, current_user, merge_params)
+ .execute(merge_request,
+ params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
@merge_request.merge_async(current_user.id, merge_params)
@@ -355,4 +320,19 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
end
+
+ def reports_response(report_comparison)
+ case report_comparison[:status]
+ when :parsing
+ ::Gitlab::PollingInterval.set_header(response, interval: 3000)
+
+ render json: '', status: :no_content
+ when :parsed
+ render json: report_comparison[:data].to_json, status: :ok
+ when :error
+ render json: { status_reason: report_comparison[:status_reason] }, status: :bad_request
+ else
+ render json: { status_reason: 'Unknown error' }, status: :internal_server_error
+ end
+ end
end
diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb
index ab7ab13657a..6c6adc233b7 100644
--- a/app/controllers/projects/mirrors_controller.rb
+++ b/app/controllers/projects/mirrors_controller.rb
@@ -18,7 +18,7 @@ class Projects::MirrorsController < Projects::ApplicationController
result = ::Projects::UpdateService.new(project, current_user, mirror_params).execute
if result[:status] == :success
- flash[:notice] = 'Mirroring settings were successfully updated.'
+ flash[:notice] = _('Mirroring settings were successfully updated.')
else
flash[:alert] = project.errors.full_messages.join(', ').html_safe
end
@@ -38,7 +38,7 @@ class Projects::MirrorsController < Projects::ApplicationController
def update_now
if params[:sync_remote]
project.update_remote_mirrors
- flash[:notice] = "The remote repository is being updated..."
+ flash[:notice] = _("The remote repository is being updated...")
end
redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
@@ -81,6 +81,7 @@ class Projects::MirrorsController < Projects::ApplicationController
password
ssh_known_hosts
regenerate_ssh_private_key
+ _destroy
]
]
end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index 58b1bc54181..89f21d8dadb 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -65,11 +65,11 @@ class Projects::PagesDomainsController < Projects::ApplicationController
private
def create_params
- params.require(:pages_domain).permit(:key, :certificate, :domain)
+ params.require(:pages_domain).permit(:key, :certificate, :domain, :auto_ssl_enabled)
end
def update_params
- params.require(:pages_domain).permit(:key, :certificate)
+ params.require(:pages_domain).permit(:key, :certificate, :auto_ssl_enabled)
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index acf56f0eb6a..72e939a3310 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -50,9 +50,11 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
if job_id
- flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe
+ pipelines_link_start = "<a href=\"#{project_pipelines_path(@project)}\">"
+ message = _("Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details.") % { pipelines_link_start: pipelines_link_start, pipelines_link_end: "</a>" }
+ flash[:notice] = message.html_safe
else
- flash[:alert] = 'Unable to schedule a pipeline to run immediately'
+ flash[:alert] = _('Unable to schedule a pipeline to run immediately')
end
redirect_to pipeline_schedules_path(@project)
@@ -85,7 +87,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
return unless limiter.throttled?([current_user, schedule], 1)
- flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
+ flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
redirect_to pipeline_schedules_path(@project)
end
@@ -96,7 +98,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def schedule_params
params.require(:schedule)
.permit(:description, :cron, :cron_timezone, :ref, :active,
- variables_attributes: [:id, :key, :secret_value, :_destroy] )
+ variables_attributes: [:id, :variable_type, :key, :secret_value, :_destroy] )
end
def authorize_play_pipeline_schedule!
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 6a86f8ca729..db3b7c8b177 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -8,6 +8,8 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
+ around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
+
wrap_parameters Ci::Pipeline
POLLING_INTERVAL = 10_000
@@ -31,10 +33,7 @@ class Projects::PipelinesController < Projects::ApplicationController
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
render json: {
- pipelines: PipelineSerializer
- .new(project: @project, current_user: @current_user)
- .with_pagination(request, response)
- .represent(@pipelines, disable_coverage: true, preload: true),
+ pipelines: serialize_pipelines,
count: {
all: @pipelines_count,
running: @running_count,
@@ -150,6 +149,13 @@ class Projects::PipelinesController < Projects::ApplicationController
private
+ def serialize_pipelines
+ PipelineSerializer
+ .new(project: @project, current_user: @current_user)
+ .with_pagination(request, response)
+ .represent(@pipelines, disable_coverage: true, preload: true)
+ end
+
def render_show
respond_to do |format|
format.html do
@@ -163,7 +169,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def create_params
- params.require(:pipeline).permit(:ref, variables_attributes: %i[key secret_value])
+ params.require(:pipeline).permit(:ref, variables_attributes: %i[key variable_type secret_value])
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index b97fbe19bbf..b3447812ef2 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -4,6 +4,8 @@ class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
include TreeHelper
+ around_action :allow_gitaly_ref_name_caching, only: [:logs_tree]
+
before_action :require_non_empty_project
before_action :validate_ref_id
before_action :assign_ref_vars
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 4eeaeb860ee..3b4215b766e 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -23,7 +23,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
append_sha = false if @filename == shortname
end
- send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
+ send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha
rescue => ex
logger.error("#{self.class.name}: #{ex}")
git_not_found!
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 91f40b90aa8..ca62f54813b 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -15,7 +15,7 @@ class Projects::RunnersController < Projects::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
+ redirect_to project_runner_path(@project, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -31,17 +31,17 @@ class Projects::RunnersController < Projects::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
index 39eca10134f..4b0d001fca6 100644
--- a/app/controllers/projects/serverless/functions_controller.rb
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -3,29 +3,20 @@
module Projects
module Serverless
class FunctionsController < Projects::ApplicationController
- include ProjectUnauthorized
-
before_action :authorize_read_cluster!
- INDEX_PRIMING_INTERVAL = 15_000
- INDEX_POLLING_INTERVAL = 60_000
-
def index
respond_to do |format|
format.json do
functions = finder.execute
- if functions.any?
- Gitlab::PollingInterval.set_header(response, interval: INDEX_POLLING_INTERVAL)
- render json: serialize_function(functions)
- else
- Gitlab::PollingInterval.set_header(response, interval: INDEX_PRIMING_INTERVAL)
- head :no_content
- end
+ render json: {
+ knative_installed: finder.knative_installed,
+ functions: serialize_function(functions)
+ }.to_json
end
format.html do
- @installed = finder.installed?
render
end
end
@@ -33,6 +24,8 @@ module Projects
def show
@service = serialize_function(finder.service(params[:environment_id], params[:id]))
+ @prometheus = finder.has_prometheus?(params[:environment_id])
+
return not_found if @service.nil?
respond_to do |format|
@@ -44,10 +37,24 @@ module Projects
end
end
+ def metrics
+ respond_to do |format|
+ format.json do
+ metrics = finder.invocation_metrics(params[:environment_id], params[:id])
+
+ if metrics.nil?
+ head :no_content
+ else
+ render json: metrics
+ end
+ end
+ end
+ end
+
private
def finder
- Projects::Serverless::FunctionsFinder.new(project.clusters)
+ Projects::Serverless::FunctionsFinder.new(project)
end
def serialize_function(function)
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index f1c9d0d0f77..e0df51590ae 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -43,20 +43,20 @@ class Projects::ServicesController < Projects::ApplicationController
if outcome[:success]
{}
else
- { error: true, message: 'Test failed.', service_response: outcome[:result].to_s, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true }
end
else
- { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(','), test_failed: false }
+ { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false }
end
rescue Gitlab::HTTP::BlockedUrlError => e
- { error: true, message: 'Test failed.', service_response: e.message, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true }
end
def success_message
if @service.active?
- "#{@service.title} activated."
+ _("%{service_title} activated.") % { service_title: @service.title }
else
- "#{@service.title} settings saved, but not activated."
+ _("%{service_title} settings saved, but not activated.") % { service_title: @service.title }
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index f2f63e986bb..1b8d479209b 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -13,13 +13,13 @@ module Projects
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
result = service.execute
if result[:status] == :success
- flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
+ flash[:notice] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name }
run_autodevops_pipeline(service)
redirect_to project_settings_ci_cd_path(@project)
else
- render 'show'
+ redirect_to project_settings_ci_cd_path(@project), alert: result[:message]
end
end
end
@@ -39,7 +39,7 @@ module Projects
def reset_registration_token
@project.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('New runners registration token has been generated!')
redirect_to namespace_project_settings_ci_cd_path
end
@@ -50,7 +50,8 @@ module Projects
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
- auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy]
+ auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy],
+ ci_cd_settings_attributes: [:default_git_depth]
)
end
@@ -58,7 +59,7 @@ module Projects
return unless service.run_auto_devops_pipeline?
if @project.empty_repo?
- flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+ flash[:warning] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.")
return
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 521ec2acebb..b5c77e5bbf4 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -3,9 +3,12 @@
module Projects
module Settings
class OperationsController < Projects::ApplicationController
- before_action :check_license
before_action :authorize_update_environment!
+ before_action do
+ push_frontend_feature_flag(:grafana_dashboard_link)
+ end
+
helper_method :error_tracking_setting
def show
@@ -14,16 +17,37 @@ module Projects
def update
result = ::Projects::Operations::UpdateService.new(project, current_user, update_params).execute
+ render_update_response(result)
+ end
+
+ private
+
+ # overridden in EE
+ def render_update_response(result)
+ respond_to do |format|
+ format.json do
+ render_update_json_response(result)
+ end
+ end
+ end
+
+ def render_update_json_response(result)
if result[:status] == :success
flash[:notice] = _('Your changes have been saved')
- redirect_to project_settings_operations_path(@project)
+ render json: {
+ status: result[:status]
+ }
else
- render 'show'
+ render(
+ status: result[:http_status] || :bad_request,
+ json: {
+ status: result[:status],
+ message: result[:message]
+ }
+ )
end
end
- private
-
def error_tracking_setting
@error_tracking_setting ||= project.error_tracking_setting ||
project.build_error_tracking_setting
@@ -35,11 +59,16 @@ module Projects
# overridden in EE
def permitted_project_params
- { error_tracking_setting_attributes: [:enabled, :api_url, :token] }
- end
+ {
+ metrics_setting_attributes: [:external_dashboard_url],
- def check_license
- render_404 unless helpers.settings_operations_available?
+ error_tracking_setting_attributes: [
+ :enabled,
+ :api_host,
+ :token,
+ project: [:slug, :name, :organization_slug, :organization_name]
+ ]
+ }
end
end
end
diff --git a/app/controllers/projects/stages_controller.rb b/app/controllers/projects/stages_controller.rb
new file mode 100644
index 00000000000..c8db5b1277f
--- /dev/null
+++ b/app/controllers/projects/stages_controller.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class Projects::StagesController < Projects::PipelinesController
+ before_action :authorize_update_pipeline!
+
+ def play_manual
+ ::Ci::PlayManualStageService
+ .new(@project, current_user, pipeline: pipeline)
+ .execute(stage)
+
+ respond_to do |format|
+ format.json do
+ render json: StageSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(stage)
+ end
+ end
+ end
+
+ private
+
+ def stage
+ @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name])
+ end
+end
diff --git a/app/controllers/projects/tags/releases_controller.rb b/app/controllers/projects/tags/releases_controller.rb
index 334e1847cc8..5e4c601a693 100644
--- a/app/controllers/projects/tags/releases_controller.rb
+++ b/app/controllers/projects/tags/releases_controller.rb
@@ -12,16 +12,13 @@ class Projects::Tags::ReleasesController < Projects::ApplicationController
end
def update
- # Release belongs to Tag which is not active record object,
- # it exists only to save a description to each Tag.
- # If description is empty we should destroy the existing record.
if release_params[:description].present?
release.update(release_params)
else
release.destroy
end
- redirect_to project_tag_path(@project, @tag.name)
+ redirect_to project_tag_path(@project, tag.name)
end
private
@@ -30,11 +27,10 @@ class Projects::Tags::ReleasesController < Projects::ApplicationController
@tag ||= @repository.find_tag(params[:tag_id])
end
- # rubocop: disable CodeReuse/ActiveRecord
def release
- @release ||= @project.releases.find_or_initialize_by(tag: @tag.name)
+ @release ||= Releases::CreateService.new(project, current_user, tag: @tag.name)
+ .find_or_build_release
end
- # rubocop: enable CodeReuse/ActiveRecord
def release_params
params.require(:release).permit(:description)
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index edebfc55c17..7509cc29a76 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -6,6 +6,8 @@ class Projects::TreeController < Projects::ApplicationController
include CreatesCommit
include ActionView::Helpers::SanitizeHelper
+ around_action :allow_gitaly_ref_name_caching, only: [:show]
+
before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars
before_action :assign_dir_vars, only: [:create_dir]
@@ -37,7 +39,7 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir
return render_404 unless @commit_params.values.all?
- create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
+ create_commit(Files::CreateDirService, success_notice: _("The directory has been successfully created."),
success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
failure_path: project_tree_path(@project, @ref))
end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index c7b4ebb2b24..284e119ca06 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -16,9 +16,9 @@ class Projects::TriggersController < Projects::ApplicationController
@trigger = project.triggers.create(trigger_params.merge(owner: current_user))
if @trigger.valid?
- flash[:notice] = 'Trigger was created successfully.'
+ flash[:notice] = _('Trigger was created successfully.')
else
- flash[:alert] = 'You could not create a new trigger.'
+ flash[:alert] = _('You could not create a new trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -26,9 +26,9 @@ class Projects::TriggersController < Projects::ApplicationController
def take_ownership
if trigger.update(owner: current_user)
- flash[:notice] = 'Trigger was re-assigned.'
+ flash[:notice] = _('Trigger was re-assigned.')
else
- flash[:alert] = 'You could not take ownership of trigger.'
+ flash[:alert] = _('You could not take ownership of trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -39,7 +39,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: _('Trigger was successfully updated.')
else
render action: "edit"
end
@@ -47,9 +47,9 @@ class Projects::TriggersController < Projects::ApplicationController
def destroy
if trigger.destroy
- flash[:notice] = "Trigger removed."
+ flash[:notice] = _("Trigger removed.")
else
- flash[:alert] = "Could not remove the trigger."
+ flash[:alert] = _("Could not remove the trigger.")
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index bb658bfcc19..646728e8167 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -38,6 +38,6 @@ class Projects::VariablesController < Projects::ApplicationController
end
def variable_params_attributes
- %i[id key secret_value protected _destroy]
+ %i[id variable_type key secret_value protected masked _destroy]
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 88dd111132b..fa5bdbc7d49 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -16,7 +16,10 @@ class Projects::WikisController < Projects::ApplicationController
end
def pages
- @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
+ @wiki_pages = Kaminari.paginate_array(
+ @project_wiki.list_pages(sort: params[:sort], direction: params[:direction])
+ ).page(params[:page])
+
@wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end
@@ -49,7 +52,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render 'edit'
@@ -65,7 +68,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render action: "edit"
@@ -85,7 +88,7 @@ class Projects::WikisController < Projects::ApplicationController
else
redirect_to(
project_wiki_path(@project, :home),
- notice: "Page not found"
+ notice: _("Page not found")
)
end
end
@@ -95,7 +98,7 @@ class Projects::WikisController < Projects::ApplicationController
redirect_to project_wiki_path(@project, :home),
status: 302,
- notice: "Page was successfully deleted"
+ notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
@@ -115,10 +118,10 @@ class Projects::WikisController < Projects::ApplicationController
@sidebar_page = @project_wiki.find_sidebar(params[:version_id])
unless @sidebar_page # Fallback to default sidebar
- @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
+ @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.list_pages(limit: 15))
end
rescue ProjectWiki::CouldNotCreateWikiError
- flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
+ flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
redirect_to project_path(@project)
false
end
@@ -155,7 +158,7 @@ class Projects::WikisController < Projects::ApplicationController
end
def set_encoding_error
- flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+ flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
end
def file_blob
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 33c6608d321..12db493978b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -7,9 +7,12 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown
include SendFileUpload
include RecordUserLastActivity
+ include ImportUrlParams
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
+ around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
+
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve]
before_action :redirect_git_extension, only: [:show]
@@ -34,10 +37,10 @@ class ProjectsController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def new
- namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
- return access_denied! if namespace && !can?(current_user, :create_projects, namespace)
+ @namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
+ return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace)
- @project = Project.new(namespace_id: namespace&.id)
+ @project = Project.new(namespace_id: @namespace&.id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -47,7 +50,7 @@ class ProjectsController < Projects::ApplicationController
end
def create
- @project = ::Projects::CreateService.new(current_user, project_params).execute
+ @project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute
if @project.saved?
cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) }
@@ -235,7 +238,7 @@ class ProjectsController < Projects::ApplicationController
def toggle_star
current_user.toggle_star(@project)
- @project.reload
+ @project.reset
render json: {
star_count: @project.star_count
@@ -328,9 +331,10 @@ class ProjectsController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
- def project_params
+ def project_params(attributes: [])
params.require(:project)
- .permit(project_params_attributes)
+ .permit(project_params_attributes + attributes)
+ .merge(import_url_params)
end
def project_params_attributes
@@ -343,17 +347,17 @@ class ProjectsController < Projects::ApplicationController
:container_registry_enabled,
:default_branch,
:description,
+ :external_authorization_classification_label,
:import_url,
:issues_tracker,
:issues_tracker_id,
:last_activity_at,
:lfs_enabled,
:name,
- :namespace_id,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
- :printing_merge_request_link_enabled,
:path,
+ :printing_merge_request_link_enabled,
:public_builds,
:request_access_enabled,
:runners_token,
@@ -375,6 +379,10 @@ class ProjectsController < Projects::ApplicationController
]
end
+ def project_params_create_attributes
+ [:namespace_id]
+ end
+
def custom_import_params
{}
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 8b8d87524a8..07b38371ab9 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -4,6 +4,7 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
include AcceptsPendingInvitations
+ prepend_before_action :check_captcha, only: :create
before_action :whitelist_query_limiting, only: [:destroy]
before_action :ensure_terms_accepted,
if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms? },
@@ -21,15 +22,10 @@ class RegistrationsController < Devise::RegistrationsController
params[resource_name] = params.delete(:"new_#{resource_name}")
end
- if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
- accept_pending_invitations
- super do |new_user|
- persist_accepted_terms_if_required(new_user)
- end
- else
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
- flash.delete :recaptcha_error
- render action: 'new'
+ accept_pending_invitations
+
+ super do |new_user|
+ persist_accepted_terms_if_required(new_user)
end
rescue Gitlab::Access::AccessDeniedError
redirect_to(new_user_session_path)
@@ -89,6 +85,17 @@ class RegistrationsController < Devise::RegistrationsController
private
+ def check_captcha
+ return unless Feature.enabled?(:registrations_recaptcha, default_enabled: true)
+ return unless Gitlab::Recaptcha.load_configurations!
+
+ return if verify_recaptcha
+
+ flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
+ flash.delete :recaptcha_error
+ render action: 'new'
+ end
+
def sign_up_params
params.require(:user).permit(:username, :email, :email_confirmation, :name, :password)
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 7b6657e1196..f1b39125a48 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -15,7 +15,7 @@ class RootController < Dashboard::ProjectsController
before_action :redirect_logged_user, if: -> { current_user.present? }
def index
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40260
Gitlab::GitalyClient.allow_n_plus_1_calls do
super
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 1b22907c10f..cb25548c83f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -14,8 +14,6 @@ class SearchController < ApplicationController
layout 'search'
def show
- search_service = SearchService.new(current_user, params)
-
@project = search_service.project
@group = search_service.group
@@ -27,8 +25,10 @@ class SearchController < ApplicationController
@show_snippets = search_service.show_snippets?
@search_results = search_service.search_results
@search_objects = search_service.search_objects
+ @display_options = search_service.display_options
render_commits if @scope == 'commits'
+ eager_load_user_status if @scope == 'users'
check_single_commit_result
end
@@ -54,6 +54,12 @@ class SearchController < ApplicationController
@search_objects = prepare_commits_for_rendering(@search_objects)
end
+ def eager_load_user_status
+ return if Feature.disabled?(:users_search, default_enabled: true)
+
+ @search_objects = @search_objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def check_single_commit_result
if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first
diff --git a/app/controllers/sent_notifications_controller.rb b/app/controllers/sent_notifications_controller.rb
index 2b76921ebd8..77757c4a3ef 100644
--- a/app/controllers/sent_notifications_controller.rb
+++ b/app/controllers/sent_notifications_controller.rb
@@ -16,7 +16,7 @@ class SentNotificationsController < ApplicationController
noteable = @sent_notification.noteable
noteable.unsubscribe(@sent_notification.recipient, @sent_notification.project)
- flash[:notice] = "You have been unsubscribed from this thread."
+ flash[:notice] = _("You have been unsubscribed from this thread.")
if current_user
redirect_to noteable_path(noteable)
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 4bd7d71e264..a841859621e 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -8,6 +8,8 @@ class SessionsController < Devise::SessionsController
include Recaptcha::Verify
skip_before_action :check_two_factor_requirement, only: [:destroy]
+ # replaced with :require_no_authentication_without_flash
+ skip_before_action :require_no_authentication, only: [:new, :create]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
@@ -15,6 +17,9 @@ class SessionsController < Devise::SessionsController
prepend_before_action :check_captcha, only: [:create]
prepend_before_action :store_redirect_uri, only: [:new]
prepend_before_action :ldap_servers, only: [:new, :create]
+ prepend_before_action :require_no_authentication_without_flash, only: [:new, :create]
+ prepend_before_action :ensure_password_authentication_enabled!, if: :password_based_login?, only: [:create]
+
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
@@ -54,6 +59,14 @@ class SessionsController < Devise::SessionsController
private
+ def require_no_authentication_without_flash
+ require_no_authentication
+
+ if flash[:alert] == I18n.t('devise.failure.already_authenticated')
+ flash[:alert] = nil
+ end
+ end
+
def captcha_enabled?
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
end
@@ -70,7 +83,7 @@ class SessionsController < Devise::SessionsController
increment_failed_login_captcha_counter
self.resource = resource_class.new
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
flash.delete :recaptcha_error
respond_with_navigational(resource) { render :new }
@@ -122,10 +135,18 @@ class SessionsController < Devise::SessionsController
end
redirect_to edit_user_password_path(reset_password_token: @token),
- notice: "Please create a password for your new account."
+ notice: _("Please create a password for your new account.")
end
# rubocop: enable CodeReuse/ActiveRecord
+ def ensure_password_authentication_enabled!
+ render_403 unless Gitlab::CurrentSettings.password_authentication_enabled_for_web?
+ end
+
+ def password_based_login?
+ user_params[:login].present? || user_params[:password].present?
+ end
+
def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt, :device_response)
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 519e7439205..5d28635232b 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -28,13 +28,13 @@ class UploadsController < ApplicationController
end
def find_model
- return nil unless params[:id]
+ return unless params[:id]
upload_model_class.find(params[:id])
end
def authorize_access!
- return nil unless model
+ return unless model
authorized =
case model
@@ -45,7 +45,7 @@ class UploadsController < ApplicationController
when Appearance
true
else
- permission = "read_#{model.class.to_s.underscore}".to_sym
+ permission = "read_#{model.class.underscore}".to_sym
can?(current_user, permission, model)
end
@@ -54,10 +54,11 @@ class UploadsController < ApplicationController
end
def authorize_create_access!
- return nil unless model
+ return unless model
- # for now we support only personal snippets comments
- authorized = can?(current_user, :comment_personal_snippet, model)
+ # for now we support only personal snippets comments. Only personal_snippet
+ # is allowed as a model to #create through routing.
+ authorized = can?(current_user, :create_note, model)
render_unauthorized unless authorized
end
diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb
index fbb1cfc5c66..b2799565f57 100644
--- a/app/finders/admin/runners_finder.rb
+++ b/app/finders/admin/runners_finder.rb
@@ -11,10 +11,11 @@ class Admin::RunnersFinder < UnionFinder
search!
filter_by_status!
filter_by_runner_type!
+ filter_by_tag_list!
sort!
paginate!
- @runners
+ @runners.with_tags
end
def sort_key
@@ -44,6 +45,14 @@ class Admin::RunnersFinder < UnionFinder
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
end
+ def filter_by_tag_list!
+ tag_list = @params[:tag_name].presence
+
+ if tag_list
+ @runners = @runners.tagged_with(tag_list)
+ end
+ end
+
def sort!
@runners = @runners.order_by(sort_key)
end
diff --git a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
new file mode 100644
index 00000000000..f38c187799c
--- /dev/null
+++ b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ module ActsAsTaggableOn
+ class TagsFinder
+ LIMIT = 20
+
+ def initialize(params:)
+ @params = params
+ end
+
+ def execute
+ tags = all_tags
+ tags = filter_by_name(tags)
+ limit(tags)
+ end
+
+ private
+
+ def all_tags
+ ::ActsAsTaggableOn::Tag.all
+ end
+
+ def filter_by_name(tags)
+ return tags unless search
+ return tags.none if search.empty?
+
+ if search.length >= Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING
+ tags.named_like(search)
+ else
+ tags.named(search)
+ end
+ end
+
+ def limit(tags)
+ tags.limit(LIMIT) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def search
+ @params[:search]
+ end
+ end
+ end
+end
diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb
index 45955783be9..ce7d0b8699c 100644
--- a/app/finders/autocomplete/users_finder.rb
+++ b/app/finders/autocomplete/users_finder.rb
@@ -2,6 +2,8 @@
module Autocomplete
class UsersFinder
+ include Gitlab::Utils::StrongMemoize
+
# The number of users to display in the results is hardcoded to 20, and
# pagination is not supported. This ensures that performance remains
# consistent and removes the need for implementing keyset pagination to
@@ -31,7 +33,7 @@ module Autocomplete
# Include current user if available to filter by "Me"
items.unshift(current_user) if prepend_current_user?
- if prepend_author? && (author = User.find_by_id(author_id))
+ if prepend_author? && author&.active?
items.unshift(author)
end
end
@@ -41,6 +43,12 @@ module Autocomplete
private
+ def author
+ strong_memoize(:author) do
+ User.find_by_id(author_id)
+ end
+ end
+
# Returns the users based on the input parameters, as an Array.
#
# This method is separate so it is easier to extend in EE.
diff --git a/app/finders/clusters/knative_services_finder.rb b/app/finders/clusters/knative_services_finder.rb
new file mode 100644
index 00000000000..7d3b53ef663
--- /dev/null
+++ b/app/finders/clusters/knative_services_finder.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+module Clusters
+ class KnativeServicesFinder
+ include ReactiveCaching
+ include Gitlab::Utils::StrongMemoize
+
+ KNATIVE_STATES = {
+ 'checking' => 'checking',
+ 'installed' => 'installed',
+ 'not_found' => 'not_found'
+ }.freeze
+
+ self.reactive_cache_key = ->(finder) { finder.model_name }
+ self.reactive_cache_worker_finder = ->(_id, *cache_args) { from_cache(*cache_args) }
+
+ attr_reader :cluster, :project
+
+ def initialize(cluster, project)
+ @cluster = cluster
+ @project = project
+ end
+
+ def with_reactive_cache_memoized(*cache_args, &block)
+ strong_memoize(:reactive_cache) do
+ with_reactive_cache(*cache_args, &block)
+ end
+ end
+
+ def clear_cache!
+ clear_reactive_cache!(*cache_args)
+ end
+
+ def self.from_cache(cluster_id, project_id)
+ cluster = Clusters::Cluster.find(cluster_id)
+ project = ::Project.find(project_id)
+
+ new(cluster, project)
+ end
+
+ def calculate_reactive_cache(*)
+ # read_services calls knative_client.discover implicitily. If we stop
+ # detecting services but still want to detect knative, we'll need to
+ # explicitily call: knative_client.discover
+ #
+ # We didn't create it separately to avoid 2 cluster requests.
+ ksvc = read_services
+ pods = knative_client.discovered ? read_pods : []
+ { services: ksvc, pods: pods, knative_detected: knative_client.discovered }
+ end
+
+ def services
+ return [] unless search_namespace
+
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+ cached_data.to_h.fetch(:services, [])
+ end
+
+ def cache_args
+ [cluster.id, project.id]
+ end
+
+ def service_pod_details(service)
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+ cached_data.to_h.fetch(:pods, []).select do |pod|
+ filter_pods(pod, service)
+ end
+ end
+
+ def knative_detected
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+
+ knative_state = cached_data.to_h[:knative_detected]
+
+ return KNATIVE_STATES['checking'] if knative_state.nil?
+ return KNATIVE_STATES['installed'] if knative_state
+
+ KNATIVE_STATES['uninstalled']
+ end
+
+ def model_name
+ self.class.name.underscore.tr('/', '_')
+ end
+
+ private
+
+ def search_namespace
+ @search_namespace ||= cluster.kubernetes_namespace_for(project)
+ end
+
+ def knative_client
+ cluster.kubeclient.knative_client
+ end
+
+ def filter_pods(pod, service)
+ pod["metadata"]["labels"]["serving.knative.dev/service"] == service
+ end
+
+ def read_services
+ knative_client.get_services(namespace: search_namespace).as_json
+ rescue Kubeclient::ResourceNotFoundError
+ []
+ end
+
+ def read_pods
+ cluster.kubeclient.core_client.get_pods(namespace: search_namespace).as_json
+ end
+
+ def id
+ nil
+ end
+ end
+end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 0080123407d..7d419103b1c 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -11,6 +11,7 @@
# parent: Group
# all_available: boolean (defaults to true)
# min_access_level: integer
+# exclude_group_ids: array of integers
#
# Users with full private access can see all groups. The `owned` and `parent`
# params can be used to restrict the groups that are returned.
@@ -29,6 +30,7 @@ class GroupsFinder < UnionFinder
items = all_groups.map do |item|
item = by_parent(item)
item = by_custom_attributes(item)
+ item = exclude_group_ids(item)
item
end
@@ -72,6 +74,12 @@ class GroupsFinder < UnionFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ def exclude_group_ids(groups)
+ return groups unless params[:exclude_group_ids]
+
+ groups.id_not_in(params[:exclude_group_ids])
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_parent(groups)
return groups unless params[:parent]
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 5870f158690..50e9418677c 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -29,6 +29,7 @@
# updated_after: datetime
# updated_before: datetime
# attempt_group_search_optimizations: boolean
+# attempt_project_search_optimizations: boolean
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
@@ -78,19 +79,20 @@ class IssuableFinder
items = init_collection
items = filter_items(items)
- # This has to be last as we may use a CTE as an optimization fence
- # by passing the attempt_group_search_optimizations param and
- # enabling the use_cte_for_group_issues_search feature flag
+ # This has to be last as we use a CTE as an optimization fence
+ # for counts by passing the force_cte param and enabling the
+ # attempt_group_search_optimizations feature flag
# https://www.postgresql.org/docs/current/static/queries-with.html
items = by_search(items)
- sort(items)
+ items = sort(items)
+
+ items
end
def filter_items(items)
items = by_project(items)
items = by_group(items)
- items = by_subquery(items)
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
@@ -117,8 +119,9 @@ class IssuableFinder
#
# rubocop: disable CodeReuse/ActiveRecord
def count_by_state
- count_params = params.merge(state: nil, sort: nil)
+ count_params = params.merge(state: nil, sort: nil, force_cte: true)
finder = self.class.new(current_user, count_params)
+
counts = Hash.new(0)
# Searching by label includes a GROUP BY in the query, but ours will be last
@@ -128,6 +131,11 @@ class IssuableFinder
#
# This does not apply when we are using a CTE for the search, as the labels
# GROUP BY is inside the subquery in that case, so we set labels_count to 1.
+ #
+ # Groups and projects have separate feature flags to suggest the use
+ # of a CTE. The CTE will not be used if the sort doesn't support it,
+ # but will always be used for the counts here as we ignore sorting
+ # anyway.
labels_count = label_names.any? ? label_names.count : 1
labels_count = 1 if use_cte_for_search?
@@ -177,7 +185,6 @@ class IssuableFinder
@project = project
end
- # rubocop: disable CodeReuse/ActiveRecord
def projects
return @projects if defined?(@projects)
@@ -185,17 +192,25 @@ class IssuableFinder
projects =
if current_user && params[:authorized_only].presence && !current_user_related?
- current_user.authorized_projects
+ current_user.authorized_projects(min_access_level)
elsif group
- finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
- GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute # rubocop: disable CodeReuse/Finder
+ find_group_projects
else
- ProjectsFinder.new(current_user: current_user).execute # rubocop: disable CodeReuse/Finder
+ Project.public_or_visible_to_user(current_user, min_access_level)
end
- @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
+ @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def find_group_projects
+ return Project.none unless group
+
+ if params[:include_subgroups]
+ Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord
+ else
+ group.projects
+ end.public_or_visible_to_user(current_user, min_access_level)
end
- # rubocop: enable CodeReuse/ActiveRecord
def search
params[:search].presence
@@ -303,29 +318,35 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
- def use_subquery_for_search?
- strong_memoize(:use_subquery_for_search) do
- attempt_group_search_optimizations? &&
- Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: true)
- end
- end
-
def use_cte_for_search?
strong_memoize(:use_cte_for_search) do
- attempt_group_search_optimizations? &&
- !use_subquery_for_search? &&
- Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
+ next false unless search
+ next false unless Gitlab::Database.postgresql?
+ # Only simple unsorted & simple sorts can use CTE
+ next false if params[:sort].present? && !params[:sort].in?(klass.simple_sorts.keys)
+
+ attempt_group_search_optimizations? || attempt_project_search_optimizations?
end
end
private
+ def force_cte?
+ !!params[:force_cte]
+ end
+
def init_collection
klass.all
end
def attempt_group_search_optimizations?
- search && Gitlab::Database.postgresql? && params[:attempt_group_search_optimizations]
+ params[:attempt_group_search_optimizations] &&
+ Feature.enabled?(:attempt_group_search_optimizations, default_enabled: true)
+ end
+
+ def attempt_project_search_optimizations?
+ params[:attempt_project_search_optimizations] &&
+ Feature.enabled?(:attempt_project_search_optimizations, default_enabled: true)
end
def count_key(value)
@@ -398,15 +419,6 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
- # Wrap projects and groups in a subquery if the conditions are met.
- def by_subquery(items)
- if use_subquery_for_search?
- klass.where(id: items.select(:id)) # rubocop: disable CodeReuse/ActiveRecord
- else
- items
- end
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def by_search(items)
return items unless search
@@ -436,22 +448,6 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- def by_assignee(items)
- if filter_by_no_assignee?
- items.where(assignee_id: nil)
- elsif filter_by_any_assignee?
- items.where('assignee_id IS NOT NULL')
- elsif assignee
- items.where(assignee_id: assignee.id)
- elsif assignee_id? || assignee_username? # assignee not found
- items.none
- else
- items
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def filter_by_no_assignee?
# Assignee_id takes precedence over assignee_username
[NONE, FILTER_NONE].include?(params[:assignee_id].to_s.downcase) || params[:assignee_username].to_s == NONE
@@ -475,6 +471,20 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ def by_assignee(items)
+ if filter_by_no_assignee?
+ items.unassigned
+ elsif filter_by_any_assignee?
+ items.assigned
+ elsif assignee
+ items.assigned_to(assignee)
+ elsif assignee_id? || assignee_username? # assignee not found
+ items.none
+ else
+ items
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_milestone(items)
if milestones?
@@ -486,7 +496,7 @@ class IssuableFinder
upcoming_ids = Milestone.upcoming_ids(projects, related_groups)
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
elsif filter_by_started_milestone?
- items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
+ items = items.left_joins_milestones.merge(Milestone.started)
else
items = items.with_milestone(params[:milestone_title])
end
@@ -568,4 +578,8 @@ class IssuableFinder
scope = params[:scope]
scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me'
end
+
+ def min_access_level
+ ProjectFeature.required_minimum_access_level(klass)
+ end
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index cb44575d6f1..58a01d598ba 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -48,9 +48,9 @@ class IssuesFinder < IssuableFinder
OR (issues.confidential = TRUE
AND (issues.author_id = :user_id
OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
- OR issues.project_id IN(:project_ids)))',
+ OR EXISTS (:authorizations)))',
user_id: current_user.id,
- project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id))
+ authorizations: current_user.authorizations_for_projects(min_access_level: CONFIDENTIAL_ACCESS_LEVEL, related_project_column: "issues.project_id"))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -144,18 +144,4 @@ class IssuesFinder < IssuableFinder
current_user.blank?
end
-
- def by_assignee(items)
- if filter_by_no_assignee?
- items.unassigned
- elsif filter_by_any_assignee?
- items.assigned
- elsif assignee
- items.assigned_to(assignee)
- elsif assignee_id? || assignee_username? # assignee not found
- items.none
- else
- items
- end
- end
end
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index f90a7868102..917de249104 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -9,25 +9,18 @@ class MembersFinder
@group = project.group
end
- # rubocop: disable CodeReuse/ActiveRecord
- def execute(include_descendants: false)
+ def execute(include_descendants: false, include_invited_groups_members: false)
project_members = project.project_members
project_members = project_members.non_invite unless can?(current_user, :admin_project, project)
- if group
- group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants) # rubocop: disable CodeReuse/Finder
- group_members = group_members.non_invite
+ union_members = group_union_members(include_descendants, include_invited_groups_members)
- union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false) # rubocop: disable Gitlab/Union
-
- sql = distinct_on(union)
-
- Member.includes(:user).from("(#{sql}) AS #{Member.table_name}")
+ if union_members.any?
+ distinct_union_of_members(union_members << project_members)
else
project_members
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def can?(*args)
Ability.allowed?(*args)
@@ -35,6 +28,34 @@ class MembersFinder
private
+ def group_union_members(include_descendants, include_invited_groups_members)
+ [].tap do |members|
+ members << direct_group_members(include_descendants) if group
+ members << project_invited_groups_members if include_invited_groups_members
+ end
+ end
+
+ def direct_group_members(include_descendants)
+ GroupMembersFinder.new(group).execute(include_descendants: include_descendants).non_invite # rubocop: disable CodeReuse/Finder
+ end
+
+ def project_invited_groups_members
+ invited_groups_ids_including_ancestors = Gitlab::ObjectHierarchy
+ .new(project.invited_groups)
+ .base_and_ancestors
+ .public_or_visible_to_user(current_user)
+ .select(:id)
+
+ GroupMember.with_source_id(invited_groups_ids_including_ancestors)
+ end
+
+ def distinct_union_of_members(union_members)
+ union = Gitlab::SQL::Union.new(union_members, remove_duplicates: false) # rubocop: disable Gitlab/Union
+ sql = distinct_on(union)
+
+ Member.includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
def distinct_on(union)
# We're interested in a list of members without duplicates by user_id.
# We prefer project members over group members, project members should go first.
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index b645011a3c5..29947bc94d5 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -29,7 +29,7 @@
#
class MergeRequestsFinder < IssuableFinder
def self.scalar_params
- @scalar_params ||= super + [:wip]
+ @scalar_params ||= super + [:wip, :target_branch]
end
def klass
@@ -37,13 +37,21 @@ class MergeRequestsFinder < IssuableFinder
end
def filter_items(_items)
- items = by_source_branch(super)
+ items = by_commit(super)
+ items = by_source_branch(items)
items = by_wip(items)
- by_target_branch(items)
+ items = by_target_branch(items)
+ by_source_project_id(items)
end
private
+ def by_commit(items)
+ return items unless params[:commit_sha].presence
+
+ items.by_commit_sha(params[:commit_sha])
+ end
+
def source_branch
@source_branch ||= params[:source_branch].presence
end
@@ -67,6 +75,16 @@ class MergeRequestsFinder < IssuableFinder
items.where(target_branch: target_branch)
end
+ def source_project_id
+ @source_project_id ||= params[:source_project_id].presence
+ end
+
+ def by_source_project_id(items)
+ return items unless source_project_id
+
+ items.where(source_project_id: source_project_id)
+ end
+
def by_wip(items)
if params[:wip] == 'yes'
items.where(wip_match(items.arel_table))
diff --git a/app/finders/projects/daily_statistics_finder.rb b/app/finders/projects/daily_statistics_finder.rb
new file mode 100644
index 00000000000..912c23107bc
--- /dev/null
+++ b/app/finders/projects/daily_statistics_finder.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Projects
+ class DailyStatisticsFinder
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def fetches
+ ProjectDailyStatistic.of_project(project)
+ .of_last_30_days
+ .sorted_by_date_desc
+ end
+
+ def total_fetch_count
+ fetches.sum_fetch_count
+ end
+ end
+end
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index 2f2816a4a08..ebe50806ca1 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -3,29 +3,59 @@
module Projects
module Serverless
class FunctionsFinder
- def initialize(clusters)
- @clusters = clusters
+ attr_reader :project
+
+ def initialize(project)
+ @clusters = project.clusters
+ @project = project
end
def execute
knative_services.flatten.compact
end
- def installed?
- clusters_with_knative_installed.exists?
+ # Possible return values: Clusters::KnativeServicesFinder::KNATIVE_STATE
+ def knative_installed
+ states = @clusters.map do |cluster|
+ cluster.application_knative
+ cluster.knative_services_finder(project).knative_detected.tap do |state|
+ return state if state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] # rubocop:disable Cop/AvoidReturnFromBlocks
+ end
+ end
+
+ states.any? { |state| state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['installed'] }
end
def service(environment_scope, name)
knative_service(environment_scope, name)&.first
end
+ def invocation_metrics(environment_scope, name)
+ return unless prometheus_adapter&.can_query?
+
+ cluster = @clusters.find do |c|
+ environment_scope == c.environment_scope
+ end
+
+ func = ::Serverless::Function.new(project, name, cluster.kubernetes_namespace_for(project))
+ prometheus_adapter.query(:knative_invocation, func)
+ end
+
+ def has_prometheus?(environment_scope)
+ @clusters.any? do |cluster|
+ environment_scope == cluster.environment_scope && cluster.application_prometheus_available?
+ end
+ end
+
private
def knative_service(environment_scope, name)
- clusters_with_knative_installed.preload_knative.map do |cluster|
+ @clusters.map do |cluster|
next if environment_scope != cluster.environment_scope
- services = cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ services = cluster
+ .knative_services_finder(project)
+ .services
.select { |svc| svc["metadata"]["name"] == name }
add_metadata(cluster, services).first unless services.nil?
@@ -33,8 +63,11 @@ module Projects
end
def knative_services
- clusters_with_knative_installed.preload_knative.map do |cluster|
- services = cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ @clusters.map do |cluster|
+ services = cluster
+ .knative_services_finder(project)
+ .services
+
add_metadata(cluster, services) unless services.nil?
end
end
@@ -45,16 +78,19 @@ module Projects
s["cluster_id"] = cluster.id
if services.length == 1
- s["podcount"] = cluster.application_knative.service_pod_details(
- cluster.platform_kubernetes&.actual_namespace,
- s["metadata"]["name"]).length
+ s["podcount"] = cluster
+ .knative_services_finder(project)
+ .service_pod_details(s["metadata"]["name"])
+ .length
end
end
end
- def clusters_with_knative_installed
- @clusters.with_knative_installed
+ # rubocop: disable CodeReuse/ServiceClass
+ def prometheus_adapter
+ @prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter
end
+ # rubocop: enable CodeReuse/ServiceClass
end
end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 93d3c991846..23b731b1aed 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -62,7 +62,7 @@ class ProjectsFinder < UnionFinder
collection = by_personal(collection)
collection = by_starred(collection)
collection = by_trending(collection)
- collection = by_visibilty_level(collection)
+ collection = by_visibility_level(collection)
collection = by_tags(collection)
collection = by_search(collection)
collection = by_archived(collection)
@@ -71,12 +71,11 @@ class ProjectsFinder < UnionFinder
collection
end
- # rubocop: disable CodeReuse/ActiveRecord
def collection_with_user
if owned_projects?
current_user.owned_projects
elsif min_access_level?
- current_user.authorized_projects.where('project_authorizations.access_level >= ?', params[:min_access_level])
+ current_user.authorized_projects(params[:min_access_level])
else
if private_only?
current_user.authorized_projects
@@ -85,7 +84,6 @@ class ProjectsFinder < UnionFinder
end
end
end
- # rubocop: enable CodeReuse/ActiveRecord
# Builds a collection for an anonymous user.
def collection_without_user
@@ -131,7 +129,7 @@ class ProjectsFinder < UnionFinder
end
# rubocop: disable CodeReuse/ActiveRecord
- def by_visibilty_level(items)
+ def by_visibility_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index d3774746cb8..bf29f15642d 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -69,6 +69,8 @@ class SnippetsFinder < UnionFinder
base.with_optional_visibility(visibility_from_scope).fresh
end
+ private
+
# Produces a query that retrieves snippets from multiple projects.
#
# The resulting query will, depending on the user's permissions, include the
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 06d26309b5b..2e5bdbd79c8 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -1,13 +1,97 @@
# frozen_string_literal: true
class GitlabSchema < GraphQL::Schema
+ # Currently an IntrospectionQuery has a complexity of 179.
+ # These values will evolve over time.
+ DEFAULT_MAX_COMPLEXITY = 200
+ AUTHENTICATED_COMPLEXITY = 250
+ ADMIN_COMPLEXITY = 300
+
+ DEFAULT_MAX_DEPTH = 10
+ AUTHENTICATED_MAX_DEPTH = 15
+
use BatchLoader::GraphQL
use Gitlab::Graphql::Authorize
use Gitlab::Graphql::Present
use Gitlab::Graphql::Connections
+ use Gitlab::Graphql::GenericTracing
+
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new
query(Types::QueryType)
default_max_page_size 100
+
+ max_complexity DEFAULT_MAX_COMPLEXITY
+ max_depth DEFAULT_MAX_DEPTH
+
mutation(Types::MutationType)
+
+ class << self
+ def multiplex(queries, **kwargs)
+ kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
+
+ queries.each do |query|
+ query[:max_depth] = max_query_depth(kwargs[:context])
+ end
+
+ super(queries, **kwargs)
+ end
+
+ def execute(query_str = nil, **kwargs)
+ kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
+ kwargs[:max_depth] ||= max_query_depth(kwargs[:context])
+
+ super(query_str, **kwargs)
+ end
+
+ def id_from_object(object)
+ unless object.respond_to?(:to_global_id)
+ # This is an error in our schema and needs to be solved. So raise a
+ # more meaningfull error message
+ raise "#{object} does not implement `to_global_id`. "\
+ "Include `GlobalID::Identification` into `#{object.class}"
+ end
+
+ object.to_global_id
+ end
+
+ def object_from_id(global_id)
+ gid = GlobalID.parse(global_id)
+
+ unless gid
+ raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id."
+ end
+
+ if gid.model_class < ApplicationRecord
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
+ else
+ gid.find
+ end
+ end
+
+ private
+
+ def max_query_complexity(ctx)
+ current_user = ctx&.fetch(:current_user, nil)
+
+ if current_user&.admin
+ ADMIN_COMPLEXITY
+ elsif current_user
+ AUTHENTICATED_COMPLEXITY
+ else
+ DEFAULT_MAX_COMPLEXITY
+ end
+ end
+
+ def max_query_depth(ctx)
+ current_user = ctx&.fetch(:current_user, nil)
+
+ if current_user
+ AUTHENTICATED_MAX_DEPTH
+ else
+ DEFAULT_MAX_DEPTH
+ end
+ end
+ end
end
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index 7d0cb777ad1..e85d16fc2c5 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -10,7 +10,7 @@ module Mutations
required: true,
description: "The project the merge request to mutate is in"
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the merge request to mutate"
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 063def75d38..5b7eb57841c 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -9,5 +9,24 @@ module Resolvers
end
end
end
+
+ def self.resolver_complexity(args, child_complexity:)
+ complexity = 1
+ complexity += 1 if args[:sort]
+ complexity += 5 if args[:search]
+
+ complexity
+ end
+
+ def self.complexity_multiplier(args)
+ # When fetching many items, additional complexity is added to the field
+ # depending on how many items is fetched. For each item we add 1% of the
+ # original complexity - this means that loading 100 items (our default
+ # maxp_age_size limit) doubles the original complexity.
+ #
+ # Complexity is not increased when searching by specific ID(s), because
+ # complexity difference is minimal in this case.
+ [args[:iid], args[:iids]].any? ? 0 : 0.01
+ end
end
end
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index 8fd26d85994..a6f82cc8505 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -19,6 +19,16 @@ module ResolvesPipelines
description: "Filter pipelines by the sha of the commit they are run for"
end
+ class_methods do
+ def resolver_complexity(args, child_complexity:)
+ complexity = super
+ complexity += 2 if args[:sha]
+ complexity += 2 if args[:ref]
+
+ complexity
+ end
+ end
+
def resolve_pipelines(project, params = {})
PipelinesFinder.new(project, context[:current_user], params).execute
end
diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb
index 0f1a64b6c58..972f318c806 100644
--- a/app/graphql/resolvers/full_path_resolver.rb
+++ b/app/graphql/resolvers/full_path_resolver.rb
@@ -7,14 +7,14 @@ module Resolvers
prepended do
argument :full_path, GraphQL::ID_TYPE,
required: true,
- description: 'The full path of the project or namespace, e.g., "gitlab-org/gitlab-ce"'
+ description: 'The full path of the project, group or namespace, e.g., "gitlab-org/gitlab-ce"'
end
def model_by_full_path(model, full_path)
BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args|
# `with_route` avoids an N+1 calculating full_path
- args[:key].where_full_path_in(full_paths).with_route.each do |project|
- loader.call(project.full_path, project)
+ args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
+ loader.call(model_instance.full_path, model_instance)
end
end
end
diff --git a/app/graphql/resolvers/group_resolver.rb b/app/graphql/resolvers/group_resolver.rb
new file mode 100644
index 00000000000..4260e18829e
--- /dev/null
+++ b/app/graphql/resolvers/group_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class GroupResolver < BaseResolver
+ prepend FullPathResolver
+
+ type Types::GroupType, null: true
+
+ def resolve(full_path:)
+ model_by_full_path(Group, full_path)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index b98d8bd1fff..6988b451ec3 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -2,11 +2,11 @@
module Resolvers
class IssuesResolver < BaseResolver
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'The IID of the issue, e.g., "1"'
- argument :iids, [GraphQL::ID_TYPE],
+ argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'The list of IIDs of issues, e.g., [1, 2]'
argument :state, Types::IssuableStateEnum,
@@ -44,6 +44,12 @@ module Resolvers
alias_method :project, :object
def resolve(**args)
+ # The project could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` of the project to query for issues, so
+ # make sure it's loaded and not `nil` before continuing.
+ project.sync if project.respond_to?(:sync)
+ return Issue.none if project.nil?
+
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54520
args[:project_id] = project.id
@@ -51,5 +57,12 @@ module Resolvers
IssuesFinder.new(context[:current_user], args).execute
end
+
+ def self.resolver_complexity(args, child_complexity:)
+ complexity = super
+ complexity += 2 if args[:labelName]
+
+ complexity
+ end
end
end
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index 90795c797ac..b84e60066e1 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -2,11 +2,11 @@
module Resolvers
class MergeRequestsResolver < BaseResolver
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'The IID of the merge request, e.g., "1"'
- argument :iids, [GraphQL::ID_TYPE],
+ argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'The list of IIDs of issues, e.g., [1, 2]'
diff --git a/app/graphql/resolvers/metadata_resolver.rb b/app/graphql/resolvers/metadata_resolver.rb
new file mode 100644
index 00000000000..3a79e6434fb
--- /dev/null
+++ b/app/graphql/resolvers/metadata_resolver.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class MetadataResolver < BaseResolver
+ type Types::MetadataType, null: false
+
+ def resolve(**args)
+ { version: Gitlab::VERSION, revision: Gitlab.revision }
+ end
+ end
+end
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
new file mode 100644
index 00000000000..677ea808aeb
--- /dev/null
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class NamespaceProjectsResolver < BaseResolver
+ argument :include_subgroups, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ default_value: false,
+ description: 'Include also subgroup projects'
+
+ type Types::ProjectType, null: true
+
+ alias_method :namespace, :object
+
+ def resolve(include_subgroups:)
+ # The namespace could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` or the `full_path` of the namespace
+ # to query for projects, so make sure it's loaded and not `nil` before continuing.
+ namespace.sync if namespace.respond_to?(:sync)
+ return Project.none if namespace.nil?
+
+ if include_subgroups
+ namespace.all_projects.with_route
+ else
+ namespace.projects.with_route
+ end
+ end
+
+ def self.resolver_complexity(args, child_complexity:)
+ complexity = super
+ complexity + 10
+ end
+ end
+end
diff --git a/app/graphql/resolvers/namespace_resolver.rb b/app/graphql/resolvers/namespace_resolver.rb
new file mode 100644
index 00000000000..17b3800d151
--- /dev/null
+++ b/app/graphql/resolvers/namespace_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class NamespaceResolver < BaseResolver
+ prepend FullPathResolver
+
+ type Types::NamespaceType, null: true
+
+ def resolve(full_path:)
+ model_by_full_path(Namespace, full_path)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/project_resolver.rb b/app/graphql/resolvers/project_resolver.rb
index ac7c9b0ce2e..2132447da5e 100644
--- a/app/graphql/resolvers/project_resolver.rb
+++ b/app/graphql/resolvers/project_resolver.rb
@@ -9,5 +9,9 @@ module Resolvers
def resolve(full_path:)
model_by_full_path(Project, full_path)
end
+
+ def self.complexity_multiplier(args)
+ 0
+ end
end
end
diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb
new file mode 100644
index 00000000000..5aad1c71b40
--- /dev/null
+++ b/app/graphql/resolvers/tree_resolver.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class TreeResolver < BaseResolver
+ argument :path, GraphQL::STRING_TYPE,
+ required: false,
+ default_value: '',
+ description: 'The path to get the tree for. Default value is the root of the repository'
+ argument :ref, GraphQL::STRING_TYPE,
+ required: false,
+ default_value: :head,
+ description: 'The commit ref to get the tree for. Default value is HEAD'
+ argument :recursive, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ default_value: false,
+ description: 'Used to get a recursive tree. Default is false'
+
+ alias_method :repository, :object
+
+ def resolve(**args)
+ return unless repository.exists?
+
+ repository.tree(args[:ref], args[:path], recursive: args[:recursive])
+ end
+ end
+end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 2b2ea64c00b..dd0d9105df6 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -3,5 +3,47 @@
module Types
class BaseField < GraphQL::Schema::Field
prepend Gitlab::Graphql::Authorize
+
+ DEFAULT_COMPLEXITY = 1
+
+ def initialize(*args, **kwargs, &block)
+ kwargs[:complexity] ||= field_complexity(kwargs[:resolver_class])
+
+ super(*args, **kwargs, &block)
+ end
+
+ private
+
+ def field_complexity(resolver_class)
+ if resolver_class
+ field_resolver_complexity
+ else
+ DEFAULT_COMPLEXITY
+ end
+ end
+
+ def field_resolver_complexity
+ # Complexity can be either integer or proc. If proc is used then it's
+ # called when computing a query complexity and context and query
+ # arguments are available for computing complexity. For resolvers we use
+ # proc because we set complexity depending on arguments and number of
+ # items which can be loaded.
+ proc do |ctx, args, child_complexity|
+ # Resolvers may add extra complexity depending on used arguments
+ complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
+
+ field_defn = to_graphql
+
+ if field_defn.connection?
+ # Resolvers may add extra complexity depending on number of items being loaded.
+ page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
+ limit_value = [args[:first], args[:last], page_size].compact.min
+ multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
+ complexity += complexity * limit_value * multiplier
+ end
+
+ complexity.to_i
+ end
+ end
end
end
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index 82b78abd573..e40059c46bb 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -6,5 +6,10 @@ module Types
prepend Gitlab::Graphql::ExposePermissions
field_class Types::BaseField
+
+ # All graphql fields exposing an id, should expose a global id.
+ def id
+ GitlabSchema.id_from_object(object)
+ end
end
end
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
new file mode 100644
index 00000000000..2987354b556
--- /dev/null
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+module Types
+ module Ci
+ class DetailedStatusType < BaseObject
+ graphql_name 'DetailedStatus'
+
+ field :group, GraphQL::STRING_TYPE, null: false
+ field :icon, GraphQL::STRING_TYPE, null: false
+ field :favicon, GraphQL::STRING_TYPE, null: false
+ field :details_path, GraphQL::STRING_TYPE, null: false
+ field :has_details, GraphQL::BOOLEAN_TYPE, null: false, method: :has_details?
+ field :label, GraphQL::STRING_TYPE, null: false
+ field :text, GraphQL::STRING_TYPE, null: false
+ field :tooltip, GraphQL::STRING_TYPE, null: false, method: :status_tooltip
+ end
+ end
+end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index 2bbffad4563..cff81e5670b 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -3,16 +3,22 @@
module Types
module Ci
class PipelineType < BaseObject
- expose_permissions Types::PermissionTypes::Ci::Pipeline
-
graphql_name 'Pipeline'
+ authorize :read_pipeline
+
+ expose_permissions Types::PermissionTypes::Ci::Pipeline
+
field :id, GraphQL::ID_TYPE, null: false
- field :iid, GraphQL::ID_TYPE, null: false
+ field :iid, GraphQL::STRING_TYPE, null: false
field :sha, GraphQL::STRING_TYPE, null: false
field :before_sha, GraphQL::STRING_TYPE, null: true
field :status, PipelineStatusEnum, null: false
+ field :detailed_status,
+ Types::Ci::DetailedStatusType,
+ null: false,
+ resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
field :duration,
GraphQL::INT_TYPE,
null: true,
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
new file mode 100644
index 00000000000..530aecc2bf9
--- /dev/null
+++ b/app/graphql/types/group_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ class GroupType < NamespaceType
+ graphql_name 'Group'
+
+ authorize :read_group
+
+ expose_permissions Types::PermissionTypes::Group
+
+ field :web_url, GraphQL::STRING_TYPE, null: false
+
+ field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do
+ group.avatar_url(only_path: false)
+ end
+
+ if ::Group.supports_nested_objects?
+ field :parent, GroupType,
+ null: true,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
+ end
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 87f6b1f8278..dd5133189dc 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -2,10 +2,12 @@
module Types
class IssueType < BaseObject
- expose_permissions Types::PermissionTypes::Issue
-
graphql_name 'Issue'
+ authorize :read_issue
+
+ expose_permissions Types::PermissionTypes::Issue
+
present_using IssuePresenter
field :iid, GraphQL::ID_TYPE, null: false
@@ -13,20 +15,22 @@ module Types
field :description, GraphQL::STRING_TYPE, null: true
field :state, IssueStateEnum, null: false
+ field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference do
+ argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false
+ end
+
field :author, Types::UserType,
null: false,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do
- authorize :read_user
- end
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
- field :assignees, Types::UserType.connection_type, null: true
+ # Remove complexity when BatchLoader is used
+ field :assignees, Types::UserType.connection_type, null: true, complexity: 5
- field :labels, Types::LabelType.connection_type, null: true
+ # Remove complexity when BatchLoader is used
+ field :labels, Types::LabelType.connection_type, null: true, complexity: 5
field :milestone, Types::MilestoneType,
null: true,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do
- authorize :read_milestone
- end
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :due_date, Types::TimeType, null: true
field :confidential, GraphQL::BOOLEAN_TYPE, null: false
@@ -37,7 +41,9 @@ module Types
field :upvotes, GraphQL::INT_TYPE, null: false
field :downvotes, GraphQL::INT_TYPE, null: false
field :user_notes_count, GraphQL::INT_TYPE, null: false
+ field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path
field :web_url, GraphQL::STRING_TYPE, null: false
+ field :relative_position, GraphQL::INT_TYPE, null: true
field :closed_at, Types::TimeType, null: true
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 7827b6e3717..85ac3102442 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -2,14 +2,16 @@
module Types
class MergeRequestType < BaseObject
+ graphql_name 'MergeRequest'
+
+ authorize :read_merge_request
+
expose_permissions Types::PermissionTypes::MergeRequest
present_using MergeRequestPresenter
- graphql_name 'MergeRequest'
-
field :id, GraphQL::ID_TYPE, null: false
- field :iid, GraphQL::ID_TYPE, null: false
+ field :iid, GraphQL::STRING_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
field :description, GraphQL::STRING_TYPE, null: true
field :state, MergeRequestStateEnum, null: false
@@ -48,9 +50,7 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false
- field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline do
- authorize :read_pipeline
- end
+ field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline
field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver
end
diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb
new file mode 100644
index 00000000000..2d8bad0614b
--- /dev/null
+++ b/app/graphql/types/metadata_type.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ class MetadataType < ::Types::BaseObject
+ graphql_name 'Metadata'
+
+ field :version, GraphQL::STRING_TYPE, null: false
+ field :revision, GraphQL::STRING_TYPE, null: false
+ end
+end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index af31b572c9a..2772fbec86f 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -4,6 +4,8 @@ module Types
class MilestoneType < BaseObject
graphql_name 'Milestone'
+ authorize :read_milestone
+
field :description, GraphQL::STRING_TYPE, null: true
field :title, GraphQL::STRING_TYPE, null: false
field :state, GraphQL::STRING_TYPE, null: false
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
new file mode 100644
index 00000000000..f6d91320e50
--- /dev/null
+++ b/app/graphql/types/namespace_type.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Types
+ class NamespaceType < BaseObject
+ graphql_name 'Namespace'
+
+ field :id, GraphQL::ID_TYPE, null: false
+
+ field :name, GraphQL::STRING_TYPE, null: false
+ field :path, GraphQL::STRING_TYPE, null: false
+ field :full_name, GraphQL::STRING_TYPE, null: false
+ field :full_path, GraphQL::ID_TYPE, null: false
+
+ field :description, GraphQL::STRING_TYPE, null: true
+ field :visibility, GraphQL::STRING_TYPE, null: true
+ field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?
+ field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
+
+ field :projects,
+ Types::ProjectType.connection_type,
+ null: false,
+ resolver: ::Resolvers::NamespaceProjectsResolver
+ end
+end
diff --git a/app/graphql/types/permission_types/group.rb b/app/graphql/types/permission_types/group.rb
new file mode 100644
index 00000000000..29833993ce6
--- /dev/null
+++ b/app/graphql/types/permission_types/group.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Group < BasePermissionType
+ graphql_name 'GroupPermissions'
+
+ abilities :read_group
+ end
+ end
+end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
new file mode 100644
index 00000000000..62537361918
--- /dev/null
+++ b/app/graphql/types/project_statistics_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class ProjectStatisticsType < BaseObject
+ graphql_name 'ProjectStatistics'
+
+ field :commit_count, GraphQL::INT_TYPE, null: false
+
+ field :storage_size, GraphQL::INT_TYPE, null: false
+ field :repository_size, GraphQL::INT_TYPE, null: false
+ field :lfs_objects_size, GraphQL::INT_TYPE, null: false
+ field :build_artifacts_size, GraphQL::INT_TYPE, null: false
+ field :packages_size, GraphQL::INT_TYPE, null: false
+ field :wiki_size, GraphQL::INT_TYPE, null: true
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index d25c8c8bd90..2236ffa394d 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -2,10 +2,12 @@
module Types
class ProjectType < BaseObject
- expose_permissions Types::PermissionTypes::Project
-
graphql_name 'Project'
+ authorize :read_project
+
+ expose_permissions Types::PermissionTypes::Project
+
field :id, GraphQL::ID_TYPE, null: false
field :full_path, GraphQL::ID_TYPE, null: false
@@ -16,7 +18,6 @@ module Types
field :description, GraphQL::STRING_TYPE, null: true
- field :default_branch, GraphQL::STRING_TYPE, null: true
field :tag_list, GraphQL::STRING_TYPE, null: true
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true
@@ -59,26 +60,30 @@ module Types
end
field :import_status, GraphQL::STRING_TYPE, null: true
- field :ci_config_path, GraphQL::STRING_TYPE, null: true
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true
+ field :namespace, Types::NamespaceType, null: false
+ field :group, Types::GroupType, null: true
+
+ field :statistics, Types::ProjectStatisticsType,
+ null: false,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
+
+ field :repository, Types::RepositoryType, null: false
+
field :merge_requests,
Types::MergeRequestType.connection_type,
null: true,
- resolver: Resolvers::MergeRequestsResolver do
- authorize :read_merge_request
- end
+ resolver: Resolvers::MergeRequestsResolver
field :merge_request,
Types::MergeRequestType,
null: true,
- resolver: Resolvers::MergeRequestsResolver.single do
- authorize :read_merge_request
- end
+ resolver: Resolvers::MergeRequestsResolver.single
field :issues,
Types::IssueType.connection_type,
@@ -92,7 +97,7 @@ module Types
field :pipelines,
Types::Ci::PipelineType.connection_type,
- null: false,
+ null: true,
resolver: Resolvers::ProjectPipelinesResolver
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 7c41716b82a..536bdb077ad 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -1,14 +1,30 @@
# frozen_string_literal: true
module Types
- class QueryType < BaseObject
+ class QueryType < ::Types::BaseObject
graphql_name 'Query'
field :project, Types::ProjectType,
null: true,
resolver: Resolvers::ProjectResolver,
- description: "Find a project" do
- authorize :read_project
+ description: "Find a project"
+
+ field :group, Types::GroupType,
+ null: true,
+ resolver: Resolvers::GroupResolver,
+ description: "Find a group"
+
+ field :namespace, Types::NamespaceType,
+ null: true,
+ resolver: Resolvers::NamespaceResolver,
+ description: "Find a namespace"
+
+ field :metadata, Types::MetadataType,
+ null: true,
+ resolver: Resolvers::MetadataResolver,
+ description: 'Metadata about GitLab' do |*args|
+
+ authorize :read_instance_metadata
end
field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb
new file mode 100644
index 00000000000..5987467e1ea
--- /dev/null
+++ b/app/graphql/types/repository_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class RepositoryType < BaseObject
+ graphql_name 'Repository'
+
+ authorize :download_code
+
+ field :root_ref, GraphQL::STRING_TYPE, null: true
+ field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?
+ field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists?
+ field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver
+ end
+end
diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb
new file mode 100644
index 00000000000..f2b7d5df2b2
--- /dev/null
+++ b/app/graphql/types/tree/blob_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+module Types
+ module Tree
+ class BlobType < BaseObject
+ implements Types::Tree::EntryType
+
+ present_using BlobPresenter
+
+ graphql_name 'Blob'
+
+ field :web_url, GraphQL::STRING_TYPE, null: true
+ end
+ end
+end
diff --git a/app/graphql/types/tree/entry_type.rb b/app/graphql/types/tree/entry_type.rb
new file mode 100644
index 00000000000..d8e8642ddb8
--- /dev/null
+++ b/app/graphql/types/tree/entry_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+module Types
+ module Tree
+ module EntryType
+ include Types::BaseInterface
+
+ field :id, GraphQL::ID_TYPE, null: false
+ field :name, GraphQL::STRING_TYPE, null: false
+ field :type, Tree::TypeEnum, null: false
+ field :path, GraphQL::STRING_TYPE, null: false
+ field :flat_path, GraphQL::STRING_TYPE, null: false
+ end
+ end
+end
diff --git a/app/graphql/types/tree/submodule_type.rb b/app/graphql/types/tree/submodule_type.rb
new file mode 100644
index 00000000000..cea76dbfd2a
--- /dev/null
+++ b/app/graphql/types/tree/submodule_type.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Types
+ module Tree
+ class SubmoduleType < BaseObject
+ implements Types::Tree::EntryType
+
+ graphql_name 'Submodule'
+ end
+ end
+end
diff --git a/app/graphql/types/tree/tree_entry_type.rb b/app/graphql/types/tree/tree_entry_type.rb
new file mode 100644
index 00000000000..23ec2ef0ec2
--- /dev/null
+++ b/app/graphql/types/tree/tree_entry_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Types
+ module Tree
+ class TreeEntryType < BaseObject
+ implements Types::Tree::EntryType
+
+ present_using TreeEntryPresenter
+
+ graphql_name 'TreeEntry'
+ description 'Represents a directory'
+
+ field :web_url, GraphQL::STRING_TYPE, null: true
+ end
+ end
+end
diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb
new file mode 100644
index 00000000000..1ee93ed9542
--- /dev/null
+++ b/app/graphql/types/tree/tree_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+module Types
+ module Tree
+ class TreeType < BaseObject
+ graphql_name 'Tree'
+
+ field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
+ end
+
+ field :submodules, Types::Tree::SubmoduleType.connection_type, null: false
+
+ field :blobs, Types::Tree::BlobType.connection_type, null: false, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/tree/type_enum.rb b/app/graphql/types/tree/type_enum.rb
new file mode 100644
index 00000000000..6560d91e9e5
--- /dev/null
+++ b/app/graphql/types/tree/type_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module Tree
+ class TypeEnum < BaseEnum
+ graphql_name 'EntryType'
+ description 'Type of a tree entry'
+
+ value 'tree', value: :tree
+ value 'blob', value: :blob
+ value 'commit', value: :commit
+ end
+ end
+end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index a13e65207df..6b53554314b 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -4,6 +4,8 @@ module Types
class UserType < BaseObject
graphql_name 'User'
+ authorize :read_user
+
present_using UserPresenter
field :name, GraphQL::STRING_TYPE, null: false
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 023e44258b7..c0db9910143 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module AppearancesHelper
+ include MarkupHelper
+
def brand_title
current_appearance&.title.presence || default_brand_title
end
@@ -47,7 +49,7 @@ module AppearancesHelper
class_names = []
class_names << 'with-performance-bar' if performance_bar_enabled?
- render_message(:header_message, class_names)
+ render_message(:header_message, class_names: class_names)
end
def footer_message
@@ -58,10 +60,10 @@ module AppearancesHelper
private
- def render_message(field_sym, class_names = [])
+ def render_message(field_sym, class_names: [], style: message_style)
class_names << field_sym.to_s.dasherize
- content_tag :div, class: class_names, style: message_style do
+ content_tag :div, class: class_names, style: style do
markdown_field(current_appearance, field_sym)
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index e635f608237..4469118f065 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -119,6 +119,39 @@ module ApplicationSettingsHelper
options_for_select(options, selected)
end
+ def external_authorization_description
+ _("If enabled, access to projects will be validated on an external service"\
+ " using their classification label.")
+ end
+
+ def external_authorization_timeout_help_text
+ _("Time in seconds GitLab will wait for a response from the external "\
+ "service. When the service does not respond in time, access will be "\
+ "denied.")
+ end
+
+ def external_authorization_url_help_text
+ _("When leaving the URL blank, classification labels can still be "\
+ "specified without disabling cross project features or performing "\
+ "external authorization checks.")
+ end
+
+ def external_authorization_client_certificate_help_text
+ _("The X509 Certificate to use when mutual TLS is required to communicate "\
+ "with the external authorization service. If left blank, the server "\
+ "certificate is still validated when accessing over HTTPS.")
+ end
+
+ def external_authorization_client_key_help_text
+ _("The private key to use when a client certificate is provided. This value "\
+ "is encrypted at rest.")
+ end
+
+ def external_authorization_client_pass_help_text
+ _("The passphrase required to decrypt the private key. This is optional "\
+ "and the value is encrypted at rest.")
+ end
+
def visible_attributes
[
:admin_notification_email,
@@ -127,6 +160,7 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
+ :dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
@@ -137,6 +171,7 @@ module ApplicationSettingsHelper
:default_artifacts_expire_in,
:default_branch_protection,
:default_group_visibility,
+ :default_project_creation,
:default_project_visibility,
:default_projects_limit,
:default_snippet_visibility,
@@ -237,7 +272,23 @@ module ApplicationSettingsHelper
]
end
+ def external_authorization_service_attributes
+ [
+ :external_auth_client_cert,
+ :external_auth_client_key,
+ :external_auth_client_key_pass,
+ :external_authorization_service_default_label,
+ :external_authorization_service_enabled,
+ :external_authorization_service_timeout,
+ :external_authorization_service_url
+ ]
+ end
+
def expanded_by_default?
Rails.env.test?
end
+
+ def instance_clusters_enabled?
+ can?(current_user, :read_cluster, Clusters::Instance.new)
+ end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2b1d6f49878..076976175a9 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -2,7 +2,7 @@
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
- LDAP_PROVIDER = /\Aldap/
+ LDAP_PROVIDER = /\Aldap/.freeze
def ldap_enabled?
Gitlab::Auth::LDAP::Config.enabled?
@@ -100,8 +100,12 @@ module AuthHelper
end
# rubocop: enable CodeReuse/ActiveRecord
- def unlink_allowed?(provider)
- %w(saml cas3).exclude?(provider.to_s)
+ def unlink_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:unlink)
+ end
+
+ def link_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:link)
end
extend self
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 67e7e475920..0f0d5350df6 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -9,4 +9,17 @@ module AutoDevopsHelper
!project.repository.gitlab_ci_yml &&
!project.ci_service
end
+
+ def badge_for_auto_devops_scope(auto_devops_receiver)
+ return unless auto_devops_receiver.auto_devops_enabled?
+
+ case auto_devops_receiver.first_auto_devops_config[:scope]
+ when :project
+ nil
+ when :group
+ s_('CICD|group enabled')
+ when :instance
+ s_('CICD|instance enabled')
+ end
+ end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 23d6684a8e6..0d6a6496993 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -19,10 +19,14 @@ module BlobHelper
def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
segments = [ide_path, 'project', project.full_path, 'edit', ref]
- segments.concat(['-', path]) if path.present?
+ segments.concat(['-', encode_ide_path(path)]) if path.present?
File.join(segments)
end
+ def encode_ide_path(path)
+ url_encode(path).gsub('%2F', '/')
+ end
+
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
return unless blob = readable_blob(options, path, project, ref)
@@ -31,12 +35,13 @@ module BlobHelper
edit_button_tag(blob,
common_classes,
_('Edit'),
- edit_blob_path(project, ref, path, options),
+ Feature.enabled?(:web_ide_default) ? ide_edit_path(project, ref, path, options) : edit_blob_path(project, ref, path, options),
project,
ref)
end
def ide_edit_button(project = @project, ref = @ref, path = @path, options = {})
+ return if Feature.enabled?(:web_ide_default)
return unless blob = readable_blob(options, path, project, ref)
edit_button_tag(blob,
@@ -72,7 +77,7 @@ module BlobHelper
project,
ref,
path,
- label: "Replace",
+ label: _("Replace"),
action: "replace",
btn_class: "default",
modal_type: "upload"
@@ -84,7 +89,7 @@ module BlobHelper
project,
ref,
path,
- label: "Delete",
+ label: _("Delete"),
action: "delete",
btn_class: "remove",
modal_type: "remove"
@@ -96,14 +101,14 @@ module BlobHelper
end
def leave_edit_message
- "Leave edit mode?\nAll unsaved changes will be lost."
+ _("Leave edit mode? All unsaved changes will be lost.")
end
def editing_preview_title(filename)
if Gitlab::MarkupHelper.previewable?(filename)
- 'Preview'
+ _('Preview')
else
- 'Preview changes'
+ _('Preview changes')
end
end
@@ -183,7 +188,7 @@ module BlobHelper
end
def copy_file_path_button(file_path)
- clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
+ clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent', title: 'Copy file path to clipboard')
end
def copy_blob_source_button(blob)
@@ -196,14 +201,14 @@ module BlobHelper
return if blob.empty?
return if blob.binary? || blob.stored_externally?
- title = 'Open raw'
+ title = _('Open raw')
link_to icon('file-code-o'), blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
end
def download_blob_button(blob)
return if blob.empty?
- title = 'Download'
+ title = _('Download')
link_to sprite_icon('download'), blob_raw_path(inline: false), download: @path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index be1e7016a1e..1640f4fc93f 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -69,7 +69,7 @@ module BoardsHelper
end
def board_sidebar_user_data
- dropdown_options = issue_assignees_dropdown_options
+ dropdown_options = assignees_dropdown_options('issue')
{
toggle: 'dropdown',
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 289cb44f1e8..495c29d3e24 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -4,7 +4,7 @@ module BroadcastMessagesHelper
def broadcast_message(message)
return unless message.present?
- content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
+ content_tag :div, dir: 'auto', class: 'broadcast-message', style: broadcast_message_style(message) do
icon('bullhorn') << ' ' << render_broadcast_message(message)
end
end
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 3c8caec3fe5..a5fe6bb8f07 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -4,12 +4,12 @@ module BuildsHelper
def build_summary(build, skip: false)
if build.has_trace?
if skip
- link_to "View job trace", pipeline_job_url(build.pipeline, build)
+ link_to _("View job trace"), pipeline_job_url(build.pipeline, build)
else
build.trace.html(last_lines: 10).html_safe
end
else
- "No job trace"
+ _("No job trace")
end
end
@@ -31,7 +31,7 @@ module BuildsHelper
def build_failed_issue_options
{
- title: "Job Failed ##{@build.id}",
+ title: _("Job Failed #%{build_id}") % { build_id: @build.id },
description: project_job_url(@project, @build)
}
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 494c754e7d5..03adbfa204f 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -21,7 +21,7 @@ module ButtonHelper
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
- title = data[:title] || 'Copy to clipboard'
+ title = data[:title] || _('Copy to clipboard')
button_text = data[:button_text] || ''
hide_tooltip = data[:hide_tooltip] || false
hide_button_icon = data[:hide_button_icon] || false
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 923a06a0512..f2b5b82b013 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -16,7 +16,7 @@ module CiStatusHelper
label = case status
when 'success'
'passed'
- when 'success_with_warnings'
+ when 'success-with-warnings'
'passed with warnings'
when 'manual'
'waiting for manual action'
@@ -37,7 +37,7 @@ module CiStatusHelper
case status
when 'success'
s_('CiStatusText|passed')
- when 'success_with_warnings'
+ when 'success-with-warnings'
s_('CiStatusText|passed')
when 'manual'
s_('CiStatusText|blocked')
@@ -71,7 +71,7 @@ module CiStatusHelper
case status
when 'success'
'status_success'
- when 'success_with_warnings'
+ when 'success-with-warnings'
'status_warning'
when 'failed'
'status_failed'
@@ -100,17 +100,6 @@ module CiStatusHelper
"pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}"
end
- def render_project_pipeline_status(pipeline_status, tooltip_placement: 'left')
- project = pipeline_status.project
- path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
-
- render_status_with_link(
- 'commit',
- pipeline_status.status,
- path,
- tooltip_placement: tooltip_placement)
- end
-
def render_commit_status(commit, ref: nil, tooltip_placement: 'left')
project = commit.project
path = pipelines_project_commit_path(project, commit, ref: ref)
@@ -123,14 +112,8 @@ module CiStatusHelper
icon_size: 24)
end
- def render_pipeline_status(pipeline, tooltip_placement: 'left')
- project = pipeline.project
- path = project_pipeline_path(project, pipeline)
- render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
- end
-
def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
- klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
+ klass = "ci-status-link ci-status-icon-#{status.dasherize} d-inline-flex #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb
index e3728804c2a..fc51f00d052 100644
--- a/app/helpers/ci_variables_helper.rb
+++ b/app/helpers/ci_variables_helper.rb
@@ -12,4 +12,23 @@ module CiVariablesHelper
ci_variable_protected_by_default?
end
end
+
+ def ci_variable_masked?(variable, only_key_value)
+ if variable && !only_key_value
+ variable.masked
+ else
+ false
+ end
+ end
+
+ def ci_variable_type_options
+ [
+ %w(Variable env_var),
+ %w(File file)
+ ]
+ end
+
+ def ci_variable_maskable_regex
+ Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
+ end
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 916dcb1a308..769f75f57c4 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -14,4 +14,10 @@ module ClustersHelper
render 'clusters/clusters/gcp_signup_offer_banner'
end
end
+
+ def has_rbac_enabled?(cluster)
+ return cluster.platform_kubernetes_rbac? if cluster.platform_kubernetes
+
+ !cluster.provider.legacy_abac?
+ end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index d90ef8903a7..42732eb93dd 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -21,6 +21,10 @@ module DashboardHelper
links.any? { |link| dashboard_nav_link?(link) }
end
+ def has_start_trial?
+ false
+ end
+
private
def get_dashboard_nav_links
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index dedc58f482b..36122d3a22a 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -57,12 +57,6 @@ module EmailsHelper
pluralize(valid_length, unit)
end
- def reset_token_expire_message
- link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
- "This link is valid for #{password_reset_token_valid_time}. " \
- "After it expires, you can #{link_tag}."
- end
-
def header_logo
if current_appearance&.header_logo?
image_tag(
@@ -91,6 +85,29 @@ module EmailsHelper
].join(';')
end
+ def closure_reason_text(closed_via, format: nil)
+ case closed_via
+ when MergeRequest
+ merge_request = MergeRequest.find(closed_via[:id]).present
+
+ case format
+ when :html
+ merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
+ _("via merge request %{link}").html_safe % { link: merge_request_link }
+ else
+ # If it's not HTML nor text then assume it's text to be safe
+ _("via merge request %{link}") % { link: "#{merge_request.to_reference} (#{merge_request.web_url})" }
+ end
+ when String
+ # Technically speaking this should be Commit but per
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
+ # we can't deserialize Commit without custom serializer for ActiveJob
+ _("via %{closed_via}") % { closed_via: closed_via }
+ else
+ ""
+ end
+ end
+
# "You are receiving this email because #{reason}"
def notification_reason_text(reason)
string = case reason
@@ -131,4 +148,42 @@ module EmailsHelper
project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host
end
+
+ def html_header_message
+ return unless show_header?
+
+ render_message(:header_message, style: '')
+ end
+
+ def html_footer_message
+ return unless show_footer?
+
+ render_message(:footer_message, style: '')
+ end
+
+ def text_header_message
+ return unless show_header?
+
+ strip_tags(render_message(:header_message, style: ''))
+ end
+
+ def text_footer_message
+ return unless show_footer?
+
+ strip_tags(render_message(:footer_message, style: ''))
+ end
+
+ private
+
+ def show_footer?
+ email_header_and_footer_enabled? && current_appearance&.show_footer?
+ end
+
+ def show_header?
+ email_header_and_footer_enabled? && current_appearance&.show_header?
+ end
+
+ def email_header_and_footer_enabled?
+ current_appearance&.email_header_and_footer_enabled?
+ end
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 365b94f5a3e..8002eb08ada 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -30,7 +30,8 @@ module EnvironmentsHelper
"environments-endpoint": project_environments_path(project, format: :json),
"project-path" => project_path(project),
"tags-path" => project_tags_path(project),
- "has-metrics" => "#{environment.has_metrics?}"
+ "has-metrics" => "#{environment.has_metrics?}",
+ "external-dashboard-url" => project.metrics_setting_external_dashboard_url
}
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 1371e9993b4..e990e425cb6 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -68,7 +68,7 @@ module EventsHelper
end
def event_preposition(event)
- if event.push? || event.commented? || event.target
+ if event.push_action? || event.commented_action? || event.target
"at"
elsif event.milestone?
"in"
@@ -80,11 +80,11 @@ module EventsHelper
words << event.author_name
words << event_action_name(event)
- if event.push?
+ if event.push_action?
words << event.ref_type
words << event.ref_name
words << "at"
- elsif event.commented?
+ elsif event.commented_action?
words << event.note_target_reference
words << "at"
elsif event.milestone?
@@ -121,9 +121,9 @@ module EventsHelper
if event.note_target
event_note_target_url(event)
end
- elsif event.push?
+ elsif event.push_action?
push_event_feed_url(event)
- elsif event.created_project?
+ elsif event.created_project_action?
project_url(event.project)
end
end
@@ -147,7 +147,7 @@ module EventsHelper
def event_feed_summary(event)
if event.issue?
render "events/event_issue", issue: event.issue
- elsif event.push?
+ elsif event.push_action?
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 5705ee54cee..f7c7f37cc38 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -4,8 +4,7 @@ module FormHelper
def form_errors(model, type: 'form')
return unless model.errors.any?
- pluralized = 'error'.pluralize(model.errors.count)
- headline = "The #{type} contains the following #{pluralized}:"
+ headline = n_('The %{type} contains the following error:', 'The %{type} contains the following errors:', model.errors.count) % { type: type }
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
@@ -18,19 +17,19 @@ module FormHelper
end
end
- def issue_assignees_dropdown_options
- {
+ def assignees_dropdown_options(issuable_type)
+ dropdown_data = {
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee',
filter: true,
dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee',
- placeholder: 'Search users',
+ placeholder: _('Search users'),
data: {
first_user: current_user&.username,
null_user: true,
current_user: true,
- project_id: @project&.id,
- field_name: 'issue[assignee_ids][]',
+ project_id: (@target_project || @project)&.id,
+ field_name: "#{issuable_type}[assignee_ids][]",
default_label: 'Unassigned',
'max-select': 1,
'dropdown-header': 'Assignee',
@@ -40,5 +39,36 @@ module FormHelper
current_user_info: UserSerializer.new.represent(current_user)
}
}
+
+ type = issuable_type.to_s
+
+ if type == 'issue' && issue_supports_multiple_assignees? ||
+ type == 'merge_request' && merge_request_supports_multiple_assignees?
+ dropdown_data = multiple_assignees_dropdown_options(dropdown_data)
+ end
+
+ dropdown_data
+ end
+
+ # Overwritten
+ def issue_supports_multiple_assignees?
+ false
+ end
+
+ # Overwritten
+ def merge_request_supports_multiple_assignees?
+ false
+ end
+
+ private
+
+ def multiple_assignees_dropdown_options(options)
+ new_options = options.dup
+
+ new_options[:title] = 'Select assignee(s)'
+ new_options[:data][:'dropdown-header'] = 'Assignee(s)'
+ new_options[:data].delete(:'max-select')
+
+ new_options
end
end
diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb
new file mode 100644
index 00000000000..a5d2f76820f
--- /dev/null
+++ b/app/helpers/groups/group_members_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Groups::GroupMembersHelper
+ def group_member_select_options
+ { multiple: true, class: 'input-clamp', scope: :all, email_user: true }
+ end
+end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 4a9ed123161..a3f53ca8dd6 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -4,6 +4,7 @@ module GroupsHelper
def group_overview_nav_link_paths
%w[
groups#show
+ groups#details
groups#activity
groups#subgroups
analytics#show
@@ -98,7 +99,7 @@ module GroupsHelper
end
def remove_group_message(group)
- _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
+ _("You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name }
end
@@ -117,11 +118,12 @@ module GroupsHelper
end
def parent_group_options(current_group)
- groups = current_user.owned_groups.sort_by(&:human_name).map do |group|
+ exclude_groups = current_group.self_and_descendants.pluck_primary_key
+ exclude_groups << current_group.parent_id if current_group.parent_id
+ groups = GroupsFinder.new(current_user, min_access_level: Gitlab::Access::OWNER, exclude_group_ids: exclude_groups).execute.sort_by(&:human_name).map do |group|
{ id: group.id, text: group.human_name }
end
- groups.delete_if { |group| group[:id] == current_group.id }
groups.to_json
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index af28e6fcb93..9a12db258d5 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -15,11 +15,14 @@ module IssuablesHelper
sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
end
- def sidebar_assignee_tooltip_label(issuable)
- if issuable.assignee
- issuable.assignee.name
+ def assignees_label(issuable, include_value: true)
+ label = 'Assignee'.pluralize(issuable.assignees.count)
+
+ if include_value
+ sanitized_list = sanitize_name(issuable.assignee_list)
+ "#{label}: #{sanitized_list}"
else
- issuable.allows_multiple_assignees? ? _('Assignee(s)') : _('Assignee')
+ label
end
end
@@ -191,7 +194,7 @@ module IssuablesHelper
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline")
- author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
+ author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none")
if status = user_status(issuable.author)
author_output << "#{status}".html_safe
@@ -277,6 +280,8 @@ module IssuablesHelper
initialTaskStatus: issuable.task_status
}
+ data[:hasClosingMergeRequest] = issuable.merge_requests_count != 0 if issuable.is_a?(Issue)
+
if parent.is_a?(Group)
data[:groupPath] = parent.path
else
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index bd53add80ca..db4f29cd996 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -5,7 +5,7 @@ module LabelsHelper
include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
- return true if label.is_a?(GroupLabel)
+ return true unless label.project_label?
return true unless project
project.feature_available?(issuables_type, current_user)
@@ -13,9 +13,7 @@ module LabelsHelper
# Link to a Label
#
- # label - Label object to link to
- # subject - Project/Group object which will be used as the context for the
- # label's link. If omitted, defaults to the label's own group/project.
+ # label - LabelPresenter object to link to
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the
@@ -40,81 +38,77 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
- def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block)
- link = label_filter_path(subject || label.subject, label, type: type)
+ def link_to_label(label, type: :issue, tooltip: true, css_class: nil, &block)
+ link = label.filter_path(type: type)
if block_given?
link_to link, class: css_class, &block
else
- link_to render_colored_label(label, tooltip: tooltip), link, class: css_class
+ render_label(label, tooltip: tooltip, link: link, css: css_class)
end
end
- def label_filter_path(subject, label, type: :issue)
- case subject
- when Group
- 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", # rubocop:disable GitlabSecurity/PublicSend
- subject.namespace,
- subject,
- label_name: [label.name])
- end
- end
-
- def edit_label_path(label)
- case label
- when GroupLabel then edit_group_label_path(label.group, label)
- when ProjectLabel then edit_project_label_path(label.project, label)
- end
- end
+ def render_label(label, tooltip: true, link: nil, css: nil)
+ # if scoped label is used then EE wraps label tag with scoped label
+ # doc link
+ html = render_colored_label(label, tooltip: tooltip)
+ html = link_to(html, link, class: css) if link
- def destroy_label_path(label)
- case label
- when GroupLabel then group_label_path(label.group, label)
- when ProjectLabel then project_label_path(label.project, label)
- end
+ html
end
- def render_colored_label(label, label_suffix = '', tooltip: true)
+ def render_colored_label(label, label_suffix: '', tooltip: true, title: nil)
text_color = text_color_for_bg(label.color)
+ title ||= tooltip ? label_tooltip_title(label) : label.name
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="badge color-label #{"has-tooltip" if tooltip}" ) +
- %(style="background-color: #{label.color}; color: #{text_color}" ) +
- %(title="#{escape_once(label.description)}" data-container="body">) +
+ %(data-html="true" style="background-color: #{label.color}; color: #{text_color}" ) +
+ %(title="#{escape_once(title)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
end
+ def label_tooltip_title(label)
+ label.description
+ end
+
def suggested_colors
- [
- '#0033CC',
- '#428BCA',
- '#44AD8E',
- '#A8D695',
- '#5CB85C',
- '#69D100',
- '#004E00',
- '#34495E',
- '#7F8C8D',
- '#A295D6',
- '#5843AD',
- '#8E44AD',
- '#FFECDB',
- '#AD4363',
- '#D10069',
- '#CC0033',
- '#FF0000',
- '#D9534F',
- '#D1D100',
- '#F0AD4E',
- '#AD8D43'
- ]
+ {
+ '#0033CC' => s_('SuggestedColors|UA blue'),
+ '#428BCA' => s_('SuggestedColors|Moderate blue'),
+ '#44AD8E' => s_('SuggestedColors|Lime green'),
+ '#A8D695' => s_('SuggestedColors|Feijoa'),
+ '#5CB85C' => s_('SuggestedColors|Slightly desaturated green'),
+ '#69D100' => s_('SuggestedColors|Bright green'),
+ '#004E00' => s_('SuggestedColors|Very dark lime green'),
+ '#34495E' => s_('SuggestedColors|Very dark desaturated blue'),
+ '#7F8C8D' => s_('SuggestedColors|Dark grayish cyan'),
+ '#A295D6' => s_('SuggestedColors|Slightly desaturated blue'),
+ '#5843AD' => s_('SuggestedColors|Dark moderate blue'),
+ '#8E44AD' => s_('SuggestedColors|Dark moderate violet'),
+ '#FFECDB' => s_('SuggestedColors|Very pale orange'),
+ '#AD4363' => s_('SuggestedColors|Dark moderate pink'),
+ '#D10069' => s_('SuggestedColors|Strong pink'),
+ '#CC0033' => s_('SuggestedColors|Strong red'),
+ '#FF0000' => s_('SuggestedColors|Pure red'),
+ '#D9534F' => s_('SuggestedColors|Soft red'),
+ '#D1D100' => s_('SuggestedColors|Strong yellow'),
+ '#F0AD4E' => s_('SuggestedColors|Soft orange'),
+ '#AD8D43' => s_('SuggestedColors|Dark moderate orange')
+ }
+ end
+
+ def render_suggested_colors
+ colors_html = suggested_colors.map do |color_hex_value, color_name|
+ link_to('', '#', class: "has-tooltip", style: "background-color: #{color_hex_value}", data: { color: color_hex_value }, title: color_name)
+ end
+
+ content_tag(:div, class: 'suggest-colors') do
+ colors_html.join.html_safe
+ end
end
def text_color_for_bg(bg_color)
@@ -154,10 +148,6 @@ module LabelsHelper
end
end
- def can_subscribe_to_label_in_different_levels?(label)
- defined?(@project) && label.is_a?(GroupLabel)
- end
-
def label_subscription_status(label, project)
return 'group-level' if label.subscribed?(current_user)
return 'project-level' if label.subscribed?(current_user, project)
@@ -179,13 +169,6 @@ module LabelsHelper
label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe'
end
- def label_deletion_confirm_text(label)
- case label
- when GroupLabel then 'Remove this label? This will affect all projects within the group. Are you sure?'
- when ProjectLabel then 'Remove this label? Are you sure?'
- end
- end
-
def create_label_title(subject)
case subject
when Group
@@ -220,17 +203,52 @@ module LabelsHelper
end
def label_status_tooltip(label, status)
- type = label.is_a?(ProjectLabel) ? 'project' : 'group'
+ type = label.project_label? ? 'project' : 'group'
level = status.unsubscribed? ? type : status.sub('-level', '')
action = status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'
"#{action} at #{level} level"
end
- def labels_sorted_by_title(labels)
- labels.sort_by(&:title)
+ def presented_labels_sorted_by_title(labels, subject)
+ labels.sort_by(&:title).map { |label| label.present(issuable_subject: subject) }
+ end
+
+ def label_dropdown_data(project, opts = {})
+ {
+ toggle: "dropdown",
+ field_name: opts[:field_name] || "label_name[]",
+ show_no: "true",
+ show_any: "true",
+ project_id: project&.try(:id),
+ namespace_path: project&.try(:namespace)&.try(:full_path),
+ project_path: project&.try(:path)
+ }.merge(opts)
+ end
+
+ def sidebar_label_dropdown_data(issuable_type, issuable_sidebar)
+ label_dropdown_data(nil, {
+ default_label: "Labels",
+ field_name: "#{issuable_type}[label_names][]",
+ ability_name: issuable_type,
+ namespace_path: issuable_sidebar[:namespace_path],
+ project_path: issuable_sidebar[:project_path],
+ issue_update: issuable_sidebar[:issuable_json_path],
+ labels: issuable_sidebar[:project_labels_path],
+ display: 'static'
+ })
+ end
+
+ def label_from_hash(hash)
+ klass = hash[:group_id] ? GroupLabel : ProjectLabel
+
+ klass.new(hash.slice(:color, :description, :title, :group_id, :project_id))
+ end
+
+ def issuable_types
+ ['issues', 'merge requests']
end
# Required for Banzai::Filter::LabelReferenceFilter
- module_function :render_colored_label, :text_color_for_bg, :escape_once
+ module_function :render_colored_label, :text_color_for_bg, :escape_once, :label_tooltip_title
end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 66f4b7b3f30..dce4168ad7b 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -4,7 +4,7 @@ require 'nokogiri'
module MarkupHelper
include ActionView::Helpers::TagHelper
- include ActionView::Context
+ include ::Gitlab::ActionViewOutput::Context
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
@@ -74,7 +74,7 @@ module MarkupHelper
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
md = markdown_field(object, attribute, options)
- return nil unless md.present?
+ return unless md.present?
tags = %w(a gl-emoji b pre code p span)
tags << 'img' if options[:allow_images]
@@ -83,7 +83,8 @@ module MarkupHelper
text = sanitize(
text,
tags: tags,
- attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
+ attributes: Rails::Html::WhiteListSanitizer.allowed_attributes +
+ %w(style data-src data-name data-unicode-version data-iid data-project-path data-mr-title)
)
# since <img> tags are stripped, this can leave empty <a> tags hanging around
@@ -241,9 +242,7 @@ module MarkupHelper
node.remove if node.name == 'a' && node.content.blank?
end
- # Use `Loofah` directly instead of `sanitize`
- # as we still use the `rails-deprecated_sanitizer` gem
- Loofah.fragment(text).scrub!(scrubber).to_s
+ sanitize text, scrubber: scrubber
end
def markdown_toolbar_button(options = {})
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 23d7aa427bb..2de4e92e33e 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -29,7 +29,7 @@ module MergeRequestsHelper
def ci_build_details_path(merge_request)
build_url = merge_request.source_project.ci_service.build_page(merge_request.diff_head_sha, merge_request.source_branch)
- return nil unless build_url
+ return unless build_url
parsed_url = URI.parse(build_url)
@@ -92,7 +92,7 @@ module MergeRequestsHelper
end
def version_index(merge_request_diff)
- return nil if @merge_request_diffs.empty?
+ return if @merge_request_diffs.empty?
@merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
end
@@ -103,7 +103,7 @@ module MergeRequestsHelper
def merge_params(merge_request)
{
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
should_remove_source_branch: true,
sha: merge_request.diff_head_sha,
squash: merge_request.squash
@@ -149,7 +149,7 @@ module MergeRequestsHelper
def merge_request_source_project_for_project(project = @project)
unless can?(current_user, :create_merge_request_in, project)
- return nil
+ return
end
if can?(current_user, :create_merge_request_from, project)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 50aec83b867..c1a04640688 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -45,7 +45,7 @@ module MilestonesHelper
when :closed
issues.closed
else
- raise ArgumentError, "invalid milestone state `#{state}`"
+ raise ArgumentError, _("invalid milestone state `%{state}`") % { state: state }
end
issues.size
@@ -145,8 +145,13 @@ module MilestonesHelper
content = []
- content << n_("1 open issue", "%d open issues", issues["opened"]) % issues["opened"] if issues["opened"]
- content << n_("1 closed issue", "%d closed issues", issues["closed"]) % issues["closed"] if issues["closed"]
+ if issues["opened"]
+ content << n_("1 open issue", "%{issues} open issues", issues["opened"]) % { issues: issues["opened"] }
+ end
+
+ if issues["closed"]
+ content << n_("1 closed issue", "%{issues} closed issues", issues["closed"]) % { issues: issues["closed"] }
+ end
content.join('<br />').html_safe
end
@@ -158,9 +163,9 @@ module MilestonesHelper
content = []
- content << n_("1 open merge request", "%d open merge requests", merge_requests.opened.count) % merge_requests.opened.count if merge_requests.opened.any?
- content << n_("1 closed merge request", "%d closed merge requests", merge_requests.closed.count) % merge_requests.closed.count if merge_requests.closed.any?
- content << n_("1 merged merge request", "%d merged merge requests", merge_requests.merged.count) % merge_requests.merged.count if merge_requests.merged.any?
+ content << n_("1 open merge request", "%{merge_requests} open merge requests", merge_requests.opened.count) % { merge_requests: merge_requests.opened.count } if merge_requests.opened.any?
+ content << n_("1 closed merge request", "%{merge_requests} closed merge requests", merge_requests.closed.count) % { merge_requests: merge_requests.closed.count } if merge_requests.closed.any?
+ content << n_("1 merged merge request", "%{merge_requests} merged merge requests", merge_requests.merged.count) % { merge_requests: merge_requests.merged.count } if merge_requests.merged.any?
content.join('<br />').html_safe
end
@@ -178,15 +183,15 @@ module MilestonesHelper
"#{milestone.start_date.to_s(:medium)}–#{milestone.due_date.to_s(:medium)}"
elsif milestone.due_date
if milestone.due_date.past?
- "expired on #{milestone.due_date.to_s(:medium)}"
+ _("expired on %{milestone_due_date}") % { milestone_due_date: milestone.due_date.strftime('%b %-d, %Y') }
else
- "expires on #{milestone.due_date.to_s(:medium)}"
+ _("expires on %{milestone_due_date}") % { milestone_due_date: milestone.due_date.strftime('%b %-d, %Y') }
end
elsif milestone.start_date
if milestone.start_date.past?
- "started on #{milestone.start_date.to_s(:medium)}"
+ _("started on %{milestone_start_date}") % { milestone_start_date: milestone.start_date.strftime('%b %-d, %Y') }
else
- "starts on #{milestone.start_date.to_s(:medium)}"
+ _("starts on %{milestone_start_date}") % { milestone_start_date: milestone.start_date.strftime('%b %-d, %Y') }
end
end
end
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
index 65c7cd82832..921c79ab771 100644
--- a/app/helpers/mirror_helper.rb
+++ b/app/helpers/mirror_helper.rb
@@ -7,4 +7,8 @@ module MirrorHelper
project_mirror_endpoint: project_mirror_path(@project, :json)
}
end
+
+ def mirror_lfs_sync_message
+ _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
+ end
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index ea3bcfc791a..572d68cb4a3 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -49,6 +49,13 @@ module NamespacesHelper
end
end
+ def namespaces_options_with_developer_maintainer_access(options = {})
+ selected = options.delete(:selected) || :current_user
+ options[:groups] = current_user.manageable_groups_with_routes(include_groups_with_developer_maintainer_access: true)
+
+ namespaces_options(selected, options)
+ end
+
private
# Many importers create a temporary Group, so use the real
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 05da5ebdb22..a57ba5f3a4f 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -58,6 +58,14 @@ module NavHelper
current_path?('milestones#show')
end
+ def admin_monitoring_nav_links
+ %w(system_info background_jobs logs health_check requests_profiles)
+ end
+
+ def group_issues_sub_menu_items
+ %w(groups#issues labels#index milestones#index boards#index boards#show)
+ end
+
private
def get_header_links
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index aaf38cbfe70..2e31a5e2ed4 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -122,21 +122,15 @@ module NotesHelper
end
def new_form_url
- return nil unless @snippet.is_a?(PersonalSnippet)
+ return unless @snippet.is_a?(PersonalSnippet)
snippet_notes_path(@snippet)
end
def can_create_note?
- issuable = @issue || @merge_request
+ noteable = @issue || @merge_request || @snippet || @project
- if @snippet.is_a?(PersonalSnippet)
- can?(current_user, :comment_personal_snippet, @snippet)
- elsif issuable
- can?(current_user, :create_note, issuable)
- else
- can?(current_user, :create_note, @project)
- end
+ can?(current_user, :create_note, noteable)
end
def initial_notes_data(autocomplete)
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 5318ab4ddef..11b9cf22142 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -93,4 +93,15 @@ module NotificationsHelper
s_(event.to_s.humanize)
end
end
+
+ def notification_setting_icon(notification_setting)
+ sprite_icon(
+ notification_setting.disabled? ? "notifications-off" : "notifications",
+ css_class: "icon notifications-icon js-notifications-icon"
+ )
+ end
+
+ def show_unsubscribe_title?(noteable)
+ can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
+ end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 5038dcf9746..ec1d8577f36 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
# frozen_string_literal: true
module PageLayoutHelper
@@ -36,7 +37,7 @@ module PageLayoutHelper
if description.present?
@page_description = description.squish
elsif @page_description.present?
- sanitize(@page_description, tags: []).truncate_words(30)
+ sanitize(@page_description.truncate_words(30), tags: [])
end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index eed529f93db..766508b6609 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -46,7 +46,8 @@ module PreferencesHelper
def first_day_of_week_choices
[
[_('Sunday'), 0],
- [_('Monday'), 1]
+ [_('Monday'), 1],
+ [_('Saturday'), 6]
]
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c400302cda3..8dee842a22d 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -169,7 +169,7 @@ module ProjectsHelper
translation.html_safe
end
- def project_list_cache_key(project)
+ def project_list_cache_key(project, pipeline_status: true)
key = [
project.route.cache_key,
project.cache_key,
@@ -179,10 +179,11 @@ module ProjectsHelper
Gitlab::CurrentSettings.cache_key,
"cross-project:#{can?(current_user, :read_cross_project)}",
max_project_member_access_cache_key(project),
+ pipeline_status,
'v2.6'
]
- key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
+ key << pipeline_status_cache_key(project.pipeline_status) if pipeline_status && project.pipeline_status.has_status?
key
end
@@ -238,8 +239,11 @@ module ProjectsHelper
end
# rubocop: enable CodeReuse/ActiveRecord
+ # TODO: Remove this method when removing the feature flag
+ # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234863
+ # make sure to remove from the EE specific controller as well: ee/app/controllers/ee/dashboard/projects_controller.rb
def show_projects?(projects, params)
- !!(params[:personal] || params[:name] || any_projects?(projects))
+ Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects))
end
def push_to_create_project_command(user = current_user)
@@ -284,13 +288,74 @@ module ProjectsHelper
can?(current_user, :read_environment, @project)
end
+ def error_tracking_setting_project_json
+ setting = @project.error_tracking_setting
+
+ return if setting.blank? || setting.project_slug.blank? ||
+ setting.organization_slug.blank?
+
+ {
+ name: setting.project_name,
+ organization_name: setting.organization_name,
+ organization_slug: setting.organization_slug,
+ slug: setting.project_slug
+ }.to_json
+ end
+
+ def directory?
+ @path.present?
+ end
+
+ def external_classification_label_help_message
+ default_label = ::Gitlab::CurrentSettings.current_application_settings
+ .external_authorization_service_default_label
+
+ s_(
+ "ExternalAuthorizationService|When no classification label is set the "\
+ "default label `%{default_label}` will be used."
+ ) % { default_label: default_label }
+ end
+
+ def can_import_members?
+ Ability.allowed?(current_user, :admin_project_member, @project)
+ end
+
+ def project_can_be_shared?
+ !membership_locked? || @project.allowed_to_share_with_group?
+ end
+
+ def membership_locked?
+ false
+ end
+
+ def share_project_description(project)
+ share_with_group = project.allowed_to_share_with_group?
+ share_with_members = !membership_locked?
+
+ description =
+ if share_with_group && share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.")
+ elsif share_with_group
+ _("You can invite another group to <strong>%{project_name}</strong>.")
+ elsif share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong>.")
+ end
+
+ description.html_safe % { project_name: project.name }
+ end
+
+ def metrics_external_dashboard_url
+ @project.metrics_setting_external_dashboard_url
+ end
+
private
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
- if !project.empty_repo? && can?(current_user, :download_code, project)
- nav_tabs << [:files, :commits, :network, :graphs, :forks, :releases]
+ unless project.empty_repo?
+ nav_tabs << [:files, :commits, :network, :graphs, :forks] if can?(current_user, :download_code, project)
+ nav_tabs << :releases if can?(current_user, :read_release, project)
end
if project.repo_exists? && can?(current_user, :read_merge_request, project)
@@ -350,7 +415,8 @@ module ProjectsHelper
blobs: :download_code,
commits: :download_code,
merge_requests: :read_merge_request,
- notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet]
+ notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet],
+ members: :read_project_member
)
end
@@ -594,4 +660,8 @@ module ProjectsHelper
project.builds_enabled? &&
!project.repository.gitlab_ci_yml
end
+
+ def vue_file_list_enabled?
+ Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project)
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 0ee76a51f7d..4594f5a31b9 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -30,13 +30,18 @@ module SearchHelper
to = collection.offset_value + collection.to_a.size
count = collection.total_count
- "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
+ s_("SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"") % { from: from, to: to, count: count, scope: scope.humanize(capitalize: false), term: term }
end
- def find_project_for_result_blob(result)
+ def find_project_for_result_blob(projects, result)
@project
end
+ # Used in EE
+ def blob_projects(results)
+ nil
+ end
+
def parse_search_result(result)
result
end
@@ -45,36 +50,40 @@ module SearchHelper
filename
end
+ def search_service
+ @search_service ||= ::SearchService.new(current_user, params)
+ end
+
private
# Autocomplete results for various settings pages
def default_autocomplete
[
- { category: "Settings", label: "User settings", url: profile_path },
- { category: "Settings", label: "SSH Keys", url: profile_keys_path },
- { category: "Settings", label: "Dashboard", url: root_path }
+ { category: "Settings", label: _("User settings"), url: profile_path },
+ { category: "Settings", label: _("SSH Keys"), url: profile_keys_path },
+ { category: "Settings", label: _("Dashboard"), url: root_path }
]
end
# Autocomplete results for settings pages, for admins
def default_autocomplete_admin
[
- { category: "Settings", label: "Admin Section", url: admin_root_path }
+ { category: "Settings", label: _("Admin Section"), url: admin_root_path }
]
end
# Autocomplete results for internal help pages
def help_autocomplete
[
- { category: "Help", label: "API Help", url: help_page_path("api/README") },
- { category: "Help", label: "Markdown Help", url: help_page_path("user/markdown") },
- { category: "Help", label: "Permissions Help", url: help_page_path("user/permissions") },
- { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") },
- { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") },
- { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") },
- { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") },
- { category: "Help", label: "Webhooks Help", url: help_page_path("user/project/integrations/webhooks") },
- { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") }
+ { category: "Help", label: _("API Help"), url: help_page_path("api/README") },
+ { category: "Help", label: _("Markdown Help"), url: help_page_path("user/markdown") },
+ { category: "Help", label: _("Permissions Help"), url: help_page_path("user/permissions") },
+ { category: "Help", label: _("Public Access Help"), url: help_page_path("public_access/public_access") },
+ { category: "Help", label: _("Rake Tasks Help"), url: help_page_path("raketasks/README") },
+ { category: "Help", label: _("SSH Keys Help"), url: help_page_path("ssh/README") },
+ { category: "Help", label: _("System Hooks Help"), url: help_page_path("system_hooks/system_hooks") },
+ { category: "Help", label: _("Webhooks Help"), url: help_page_path("user/project/integrations/webhooks") },
+ { category: "Help", label: _("Workflow Help"), url: help_page_path("workflow/README") }
]
end
@@ -84,16 +93,16 @@ module SearchHelper
ref = @ref || @project.repository.root_ref
[
- { category: "In this project", label: "Files", url: project_tree_path(@project, ref) },
- { category: "In this project", label: "Commits", url: project_commits_path(@project, ref) },
- { category: "In this project", label: "Network", url: project_network_path(@project, ref) },
- { category: "In this project", label: "Graph", url: project_graph_path(@project, ref) },
- { category: "In this project", label: "Issues", url: project_issues_path(@project) },
- { category: "In this project", label: "Merge Requests", url: project_merge_requests_path(@project) },
- { category: "In this project", label: "Milestones", url: project_milestones_path(@project) },
- { category: "In this project", label: "Snippets", url: project_snippets_path(@project) },
- { category: "In this project", label: "Members", url: project_project_members_path(@project) },
- { category: "In this project", label: "Wiki", url: project_wikis_path(@project) }
+ { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
+ { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) },
+ { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
+ { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) },
+ { category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
+ { category: "In this project", label: _("Merge Requests"), url: project_merge_requests_path(@project) },
+ { category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
+ { category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
+ { category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
+ { category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
]
else
[]
@@ -119,7 +128,7 @@ module SearchHelper
# rubocop: disable CodeReuse/ActiveRecord
def projects_autocomplete(term, limit = 5)
current_user.authorized_projects.order_id_desc.search_by_title(term)
- .sorted_by_stars.non_archived.limit(limit).map do |p|
+ .sorted_by_stars_desc.non_archived.limit(limit).map do |p|
{
category: "Projects",
id: p.id,
@@ -153,7 +162,7 @@ module SearchHelper
opts =
{
id: "filtered-search-#{type}",
- placeholder: 'Search or filter results...',
+ placeholder: _('Search or filter results...'),
data: {
'username-params' => UserSerializer.new.represent(@users)
},
@@ -201,4 +210,14 @@ module SearchHelper
def limited_count(count, limit = 1000)
count > limit ? "#{limit}+" : count
end
+
+ def search_tabs?(tab)
+ return false if Feature.disabled?(:users_search, default_enabled: true)
+
+ if @project
+ project_search_tabs?(:members)
+ else
+ can?(current_user, :read_users_list)
+ end
+ end
end
diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb
index 32bf3526571..6326d98461e 100644
--- a/app/helpers/sidekiq_helper.rb
+++ b/app/helpers/sidekiq_helper.rb
@@ -8,7 +8,7 @@ module SidekiqHelper
(?<state>[DIEKNRSTVWXZNLpsl\+<>/\d]+)\s+
(?<start>.+?)\s+
(?<command>(?:ruby\d+:\s+)?sidekiq.*\].*)
- \z}x
+ \z}x.freeze
def parse_sidekiq_ps(line)
match = line.strip.match(SIDEKIQ_PS_REGEXP)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 07ec129dea3..26692934456 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -3,40 +3,48 @@
module SortingHelper
def sort_options_hash
{
- sort_value_created_date => sort_title_created_date,
- sort_value_downvotes => sort_title_downvotes,
- sort_value_due_date => sort_title_due_date,
- sort_value_due_date_later => sort_title_due_date_later,
- sort_value_due_date_soon => sort_title_due_date_soon,
- sort_value_label_priority => sort_title_label_priority,
- sort_value_largest_group => sort_title_largest_group,
- sort_value_largest_repo => sort_title_largest_repo,
- sort_value_milestone => sort_title_milestone,
- sort_value_milestone_later => sort_title_milestone_later,
- sort_value_milestone_soon => sort_title_milestone_soon,
- sort_value_name => sort_title_name,
- sort_value_name_desc => sort_title_name_desc,
- sort_value_oldest_created => sort_title_oldest_created,
- sort_value_oldest_signin => sort_title_oldest_signin,
- sort_value_oldest_updated => sort_title_oldest_updated,
- sort_value_recently_created => sort_title_recently_created,
- sort_value_recently_signin => sort_title_recently_signin,
- sort_value_recently_updated => sort_title_recently_updated,
- sort_value_popularity => sort_title_popularity,
- sort_value_priority => sort_title_priority,
- sort_value_upvotes => sort_title_upvotes,
- sort_value_contacted_date => sort_title_contacted_date
+ sort_value_created_date => sort_title_created_date,
+ sort_value_downvotes => sort_title_downvotes,
+ sort_value_due_date => sort_title_due_date,
+ sort_value_due_date_later => sort_title_due_date_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_label_priority => sort_title_label_priority,
+ sort_value_largest_group => sort_title_largest_group,
+ sort_value_largest_repo => sort_title_largest_repo,
+ sort_value_milestone => sort_title_milestone,
+ sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_milestone_soon => sort_title_milestone_soon,
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_oldest_signin => sort_title_oldest_signin,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_recently_signin => sort_title_recently_signin,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_popularity => sort_title_popularity,
+ sort_value_priority => sort_title_priority,
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_contacted_date => sort_title_contacted_date,
+ sort_value_relative_position => sort_title_relative_position
}
end
def projects_sort_options_hash
+ Feature.enabled?(:project_list_filter_bar) && !current_controller?('admin/projects') ? projects_sort_common_options_hash : old_projects_sort_options_hash
+ end
+
+ # TODO: Simplify these sorting options
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
+ # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234858
+ def old_projects_sort_options_hash
options = {
sort_value_latest_activity => sort_title_latest_activity,
sort_value_name => sort_title_name,
sort_value_oldest_activity => sort_title_oldest_activity,
sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_created => sort_title_recently_created,
- sort_value_most_stars => sort_title_most_stars
+ sort_value_stars_desc => sort_title_most_stars
}
if current_controller?('admin/projects')
@@ -46,6 +54,41 @@ module SortingHelper
options
end
+ def projects_sort_common_options_hash
+ {
+ sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_recently_created => sort_title_created_date,
+ sort_value_name => sort_title_name,
+ sort_value_stars_desc => sort_title_stars
+ }
+ end
+
+ def projects_sort_option_titles
+ {
+ sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_recently_created => sort_title_created_date,
+ sort_value_name => sort_title_name,
+ sort_value_stars_desc => sort_title_stars,
+ sort_value_oldest_activity => sort_title_latest_activity,
+ sort_value_oldest_created => sort_title_created_date,
+ sort_value_name_desc => sort_title_name,
+ sort_value_stars_asc => sort_title_stars
+ }
+ end
+
+ def projects_reverse_sort_options_hash
+ {
+ sort_value_latest_activity => sort_value_oldest_activity,
+ sort_value_recently_created => sort_value_oldest_created,
+ sort_value_name => sort_value_name_desc,
+ sort_value_stars_desc => sort_value_stars_asc,
+ sort_value_oldest_activity => sort_value_latest_activity,
+ sort_value_oldest_created => sort_value_recently_created,
+ sort_value_name_desc => sort_value_name,
+ sort_value_stars_asc => sort_value_stars_desc
+ }
+ end
+
def groups_sort_options_hash
{
sort_value_name => sort_title_name,
@@ -59,7 +102,7 @@ module SortingHelper
def subgroups_sort_options_hash
groups_sort_options_hash.merge(
- sort_value_most_stars => sort_title_most_stars
+ sort_value_stars_desc => sort_title_most_stars
)
end
@@ -142,7 +185,9 @@ module SortingHelper
{
sort_value_oldest_created => sort_value_created_date,
sort_value_oldest_updated => sort_value_recently_updated,
- sort_value_milestone_later => sort_value_milestone
+ sort_value_milestone_later => sort_value_milestone,
+ sort_value_due_date_later => sort_value_due_date,
+ sort_value_least_popular => sort_value_popularity
}
end
@@ -151,7 +196,11 @@ module SortingHelper
sort_value_created_date => sort_value_oldest_created,
sort_value_recently_created => sort_value_oldest_created,
sort_value_recently_updated => sort_value_oldest_updated,
- sort_value_milestone => sort_value_milestone_later
+ sort_value_milestone => sort_value_milestone_later,
+ sort_value_due_date => sort_value_due_date_later,
+ sort_value_due_date_soon => sort_value_due_date_later,
+ sort_value_popularity => sort_value_least_popular,
+ sort_value_most_popular => sort_value_least_popular
}.merge(issuable_sort_option_overrides)
end
@@ -170,6 +219,8 @@ module SortingHelper
end
end
+ # TODO: dedupicate issuable and project sort direction
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
def issuable_sort_direction_button(sort_value)
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
reverse_sort = issuable_reverse_sort_order_hash[sort_value]
@@ -181,7 +232,23 @@ module SortingHelper
link_class += ' disabled'
end
- link_to(reverse_url, type: 'button', class: link_class, title: 'Sort direction') do
+ link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
+ sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
+ end
+ end
+
+ def project_sort_direction_button(sort_value)
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ reverse_sort = projects_reverse_sort_options_hash[sort_value]
+
+ if reverse_sort
+ reverse_url = filter_projects_path(sort: reverse_sort)
+ else
+ reverse_url = '#'
+ link_class += ' disabled'
+ end
+
+ link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
end
end
@@ -319,6 +386,10 @@ module SortingHelper
s_('SortOptions|Most stars')
end
+ def sort_title_stars
+ s_('SortOptions|Stars')
+ end
+
def sort_title_oldest_last_activity
s_('SortOptions|Oldest last activity')
end
@@ -327,6 +398,10 @@ module SortingHelper
s_('SortOptions|Recent last activity')
end
+ def sort_title_relative_position
+ s_('SortOptions|Manual')
+ end
+
# Values.
def sort_value_access_level_asc
'access_level_asc'
@@ -420,6 +495,14 @@ module SortingHelper
'popularity'
end
+ def sort_value_most_popular
+ 'popularity_desc'
+ end
+
+ def sort_value_least_popular
+ 'popularity_asc'
+ end
+
def sort_value_priority
'priority'
end
@@ -452,10 +535,14 @@ module SortingHelper
'contacted_asc'
end
- def sort_value_most_stars
+ def sort_value_stars_desc
'stars_desc'
end
+ def sort_value_stars_asc
+ 'stars_asc'
+ end
+
def sort_value_oldest_last_activity
'last_activity_on_asc'
end
@@ -463,4 +550,8 @@ module SortingHelper
def sort_value_recently_last_activity
'last_activity_on_desc'
end
+
+ def sort_value_relative_position
+ 'relative_position'
+ end
end
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index be8761db562..ecf37bae6b3 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -2,8 +2,21 @@
module StorageHelper
def storage_counter(size_in_bytes)
+ return s_('StorageSize|Unknown') unless size_in_bytes
+
precision = size_in_bytes < 1.megabyte ? 0 : 1
number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false)
end
+
+ def storage_counters_details(statistics)
+ counters = {
+ counter_repositories: storage_counter(statistics.repository_size),
+ counter_wikis: storage_counter(statistics.wiki_size),
+ counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
+ counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
+ }
+
+ _("%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters
+ end
end
diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb
new file mode 100644
index 00000000000..51ea79d1ddd
--- /dev/null
+++ b/app/helpers/tracking_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module TrackingHelper
+ def tracking_attrs(label, event, property)
+ {} # CE has no tracking features
+ end
+end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index e2879bfdcf1..4690b6ffbe1 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -86,17 +86,17 @@ module TreeHelper
end
def edit_in_new_fork_notice_now
- "You're not allowed to make changes to this project directly." +
- " A fork of this project is being created that you can make changes in, so you can submit a merge request."
+ _("You're not allowed to make changes to this project directly. "\
+ "A fork of this project is being created that you can make changes in, so you can submit a merge request.")
end
def edit_in_new_fork_notice
- "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."
+ _("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
def edit_in_new_fork_notice_action(action)
- edit_in_new_fork_notice + " Try to #{action} this file again."
+ edit_in_new_fork_notice + _(" Try to %{action} this file again.") % { action: action }
end
def commit_in_fork_help
@@ -136,18 +136,9 @@ module TreeHelper
end
# returns the relative path of the first subdir that doesn't have only one directory descendant
- # rubocop: disable CodeReuse/ActiveRecord
def flatten_tree(root_path, tree)
- return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
-
- subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
- if subtree.count == 1 && subtree.first.dir?
- return tree_join(tree.name, flatten_tree(root_path, subtree.first))
- else
- return tree.name
- end
+ tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '')
end
- # rubocop: enable CodeReuse/ActiveRecord
def selected_branch
@branch_name || tree_edit_branch
diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb
index 1ad7bb81784..5d658d35107 100644
--- a/app/helpers/user_callouts_helper.rb
+++ b/app/helpers/user_callouts_helper.rb
@@ -17,6 +17,9 @@ module UserCalloutsHelper
render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
end
+ def render_dashboard_gold_trial(user)
+ end
+
private
def user_dismissed?(feature_name)
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 712f0f808dd..b318b27992a 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -42,11 +42,11 @@ module VisibilityLevelHelper
def group_visibility_level_description(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
- "The group and its projects can only be viewed by members."
+ _("The group and its projects can only be viewed by members.")
when Gitlab::VisibilityLevel::INTERNAL
- "The group and any internal projects can be viewed by any logged in user."
+ _("The group and any internal projects can be viewed by any logged in user.")
when Gitlab::VisibilityLevel::PUBLIC
- "The group and any public projects can be viewed without any authentication."
+ _("The group and any public projects can be viewed without any authentication.")
end
end
@@ -54,20 +54,20 @@ module VisibilityLevelHelper
case level
when Gitlab::VisibilityLevel::PRIVATE
if snippet.is_a? ProjectSnippet
- "The snippet is visible only to project members."
+ _("The snippet is visible only to project members.")
else
- "The snippet is visible only to me."
+ _("The snippet is visible only to me.")
end
when Gitlab::VisibilityLevel::INTERNAL
- "The snippet is visible to any logged in user."
+ _("The snippet is visible to any logged in user.")
when Gitlab::VisibilityLevel::PUBLIC
- "The snippet can be accessed without any authentication."
+ _("The snippet can be accessed without any authentication.")
end
end
def restricted_visibility_level_description(level)
level_name = Gitlab::VisibilityLevel.level_name(level)
- "#{level_name.capitalize} visibility has been restricted by the administrator."
+ _("%{level_name} visibility has been restricted by the administrator.") % { level_name: level_name.capitalize }
end
def disallowed_visibility_level_description(level, form_model)
@@ -165,8 +165,46 @@ module VisibilityLevelHelper
!form_model.visibility_level_allowed?(level)
end
+ # Visibility level can be restricted in two ways:
+ #
+ # 1. The group permissions (e.g. a subgroup is private, which requires
+ # all projects to be private)
+ # 2. The global allowed visibility settings, set by the admin
+ def selected_visibility_level(form_model, requested_level)
+ requested_level =
+ if requested_level.present?
+ requested_level.to_i
+ else
+ default_project_visibility
+ end
+
+ [requested_level, max_allowed_visibility_level(form_model)].min
+ end
+
private
+ def max_allowed_visibility_level(form_model)
+ # First obtain the maximum visibility for the project or group
+ current_level = max_allowed_visibility_level_by_model(form_model)
+
+ # Now limit this by the global setting
+ Gitlab::VisibilityLevel.closest_allowed_level(current_level)
+ end
+
+ def max_allowed_visibility_level_by_model(form_model)
+ current_level = Gitlab::VisibilityLevel::PRIVATE
+
+ Gitlab::VisibilityLevel.values.sort.each do |value|
+ if disallowed_visibility_level?(form_model, value)
+ break
+ else
+ current_level = value
+ end
+ end
+
+ current_level
+ end
+
def visibility_level_errors_for_group(group, level_name)
group_name = link_to group.name, group_path(group)
change_visiblity = link_to 'change the visibility', edit_group_path(group)
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 647f34e57ed..edd48f82729 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -47,4 +47,24 @@ module WikiHelper
def wiki_attachment_upload_url
expose_url(api_v4_projects_wikis_attachments_path(id: @project.id))
end
+
+ def wiki_sort_controls(project, sort, direction)
+ sort ||= ProjectWiki::TITLE_ORDER
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ reversed_direction = direction == 'desc' ? 'asc' : 'desc'
+ icon_class = direction == 'desc' ? 'highest' : 'lowest'
+
+ link_to(project_wikis_pages_path(project, sort: sort, direction: reversed_direction),
+ type: 'button', class: link_class, title: _('Sort direction')) do
+ sprite_icon("sort-#{icon_class}", size: 16)
+ end
+ end
+
+ def wiki_sort_title(key)
+ if key == ProjectWiki::CREATED_AT_ORDER
+ s_("Wiki|Created date")
+ else
+ s_("Wiki|Title")
+ end
+ end
end
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index e032f568913..e0aa66e6de3 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class AbuseReportMailer < BaseMailer
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def notify(abuse_report_id)
return unless deliverable?
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index 7aa75ee30e6..cbaf53fced1 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -7,6 +7,7 @@ class DeviseMailer < Devise::Mailer
layout 'mailer/devise'
helper EmailsHelper
+ helper ApplicationHelper
protected
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 45fc5a6c383..d743533b1bc 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class EmailRejectionMailer < BaseMailer
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def rejection(reason, original_raw, can_retry = false)
@reason = reason
@original_message = Mail::Message.new(original_raw)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 654ae211310..f3a3203f7ad 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -30,8 +30,8 @@ module Emails
end
# rubocop: enable CodeReuse/ActiveRecord
- def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
- setup_issue_mail(issue_id, recipient_id)
+ def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason: nil, closed_via: nil)
+ setup_issue_mail(issue_id, recipient_id, closed_via: closed_via)
@updated_by = User.find(updated_by_user_id)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
@@ -74,6 +74,7 @@ module Emails
@new_issue = new_issue
@new_project = new_issue.project
+ @can_access_project = recipient.can?(:read_project, @new_project)
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end
@@ -82,7 +83,7 @@ module Emails
@project = Project.find(project_id)
@results = results
- mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
+ mail(to: recipient(@user.id, @project.group), subject: subject('Imported issues')) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
@@ -90,10 +91,11 @@ module Emails
private
- def setup_issue_mail(issue_id, recipient_id)
+ def setup_issue_mail(issue_id, recipient_id, closed_via: nil)
@issue = Issue.find(issue_id)
@project = @issue.project
@target_url = project_issue_url(@project, @issue)
+ @closed_via = closed_via
@sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
end
@@ -101,7 +103,7 @@ module Emails
def issue_thread_options(sender_id, recipient_id, reason)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@issue.title} (##{@issue.iid})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 91dfdf58982..2bfa59774d7 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -58,9 +58,8 @@ module Emails
@member_source_type = member_source_type
@member_source = member_source_class.find(source_id)
@invite_email = invite_email
- inviter = User.find(created_by_id)
- mail(to: inviter.notification_email,
+ mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source),
subject: subject('Invitation declined'))
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 9ba8f92fcbf..864f9e2975a 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -24,10 +24,12 @@ module Emails
end
# rubocop: disable CodeReuse/ActiveRecord
- def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil)
+ def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_ids, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
- @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
+ @previous_assignees = []
+ @previous_assignees = User.where(id: previous_assignee_ids) if previous_assignee_ids.any?
+
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -56,14 +58,14 @@ module Emails
}))
end
- def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
+ def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason: nil, closed_via: nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@updated_by = User.find(updated_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
- def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
+ def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason: nil, closed_via: nil)
setup_merge_request_mail(merge_request_id, recipient_id)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
@@ -108,7 +110,7 @@ module Emails
def merge_request_thread_options(sender_id, recipient_id, reason = nil)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 1b3c1f9a8a9..70d296fe3b8 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -51,7 +51,7 @@ module Emails
def note_thread_options(recipient_id)
{
from: sender(@note.author_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @group),
subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})")
}
end
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index ce449237ef6..2d390666f65 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -7,7 +7,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been enabled")
)
end
@@ -17,7 +17,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been disabled")
)
end
@@ -27,7 +27,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("Verification succeeded for GitLab Pages domain '#{domain.domain}'")
)
end
@@ -37,7 +37,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'")
)
end
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 31e183640ad..fb57c0da34d 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -15,7 +15,7 @@ module Emails
def pipeline_mail(pipeline, recipients, status)
@project = pipeline.project
@pipeline = pipeline
- @merge_request = pipeline.merge_requests.first
+ @merge_request = pipeline.merge_requests_as_head_pipeline.first
add_headers
# We use bcc here because we don't want to generate this emails for a
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 2500622caa7..f81f76f67f7 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -7,20 +7,20 @@ module Emails
@project = Project.find project_id
@target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
- mail(to: @user.notification_email,
+ mail(to: recipient(user_id, @project.group),
subject: subject("Project was moved"))
end
def project_was_exported_email(current_user, project)
@project = project
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, project.group),
subject: subject("Project was exported"))
end
def project_was_not_exported_email(current_user, project, errors)
@project = project
@errors = errors
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, @project.group),
subject: subject("Project export error"))
end
@@ -28,7 +28,7 @@ module Emails
@project = project
@user = user
- mail(to: user.notification_email, subject: subject("Project cleanup has completed"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup has completed"))
end
def repository_cleanup_failure_email(project, user, error)
@@ -36,7 +36,7 @@ module Emails
@user = user
@error = error
- mail(to: user.notification_email, subject: subject("Project cleanup failure"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup failure"))
end
def repository_push_email(project_id, opts = {})
diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb
index 2018eb7260b..2d8137843ec 100644
--- a/app/mailers/emails/remote_mirrors.rb
+++ b/app/mailers/emails/remote_mirrors.rb
@@ -6,7 +6,7 @@ module Emails
@remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
@project = @remote_mirror.project
- mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed'))
+ mail(to: recipient(recipient_id, @project.group), subject: subject('Remote mirror update failed'))
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index efa1233b434..576caea4c10 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -4,6 +4,7 @@ class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
include EmailsHelper
+ include IssuablesHelper
include Emails::Issues
include Emails::MergeRequests
@@ -24,6 +25,7 @@ class Notify < BaseMailer
helper MembersHelper
helper AvatarsHelper
helper GitlabRoutingHelper
+ helper IssuablesHelper
def test_email(recipient_email, subject, body)
mail(to: recipient_email,
@@ -71,12 +73,22 @@ class Notify < BaseMailer
# Look up a User by their ID and return their email address
#
- # recipient_id - User ID
+ # recipient_id - User ID
+ # notification_group - The parent group of the notification
#
# Returns a String containing the User's email address.
- def recipient(recipient_id)
+ def recipient(recipient_id, notification_group = nil)
@current_user = User.find(recipient_id)
- @current_user.notification_email
+ group_notification_email = nil
+
+ if notification_group
+ notification_settings = notification_group.notification_settings_for(@current_user, hierarchy_order: :asc)
+ group_notification_email = notification_settings.find { |n| n.notification_email.present? }&.notification_email
+ end
+
+ # Return group-specific email address if present, otherwise return global
+ # email address
+ group_notification_email || @current_user.notification_email
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 145169be8a6..a24d3476d0e 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -2,6 +2,10 @@
class RepositoryCheckMailer < BaseMailer
# rubocop: disable CodeReuse/ActiveRecord
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def notify(failed_count)
@message =
if failed_count == 1
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 1b78fd04ebb..a3a1748142f 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AbuseReport < ActiveRecord::Base
+class AbuseReport < ApplicationRecord
include CacheMarkdownField
cache_markdown_field :message, pipeline: :single_line
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index 0d9c6a4a1f0..f355b02c428 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -5,7 +5,8 @@ class ActiveSession
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
- :browser, :os, :device_name, :device_type
+ :browser, :os, :device_name, :device_type,
+ :is_impersonated
def current?(session)
return false if session_id.nil? || session.id.nil?
@@ -31,7 +32,8 @@ class ActiveSession
device_type: client.device_type,
created_at: user.current_sign_in_at || timestamp,
updated_at: timestamp,
- session_id: session_id
+ session_id: session_id,
+ is_impersonated: request.session[:impersonator_id].present?
)
redis.pipelined do
@@ -51,7 +53,7 @@ class ActiveSession
def self.list(user)
Gitlab::Redis::SharedState.with do |redis|
- cleaned_up_lookup_entries(redis, user.id).map do |entry|
+ cleaned_up_lookup_entries(redis, user).map do |entry|
# rubocop:disable Security/MarshalLoad
Marshal.load(entry)
# rubocop:enable Security/MarshalLoad
@@ -76,7 +78,7 @@ class ActiveSession
def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
- cleaned_up_lookup_entries(redis, user.id)
+ cleaned_up_lookup_entries(redis, user)
end
end
@@ -88,25 +90,52 @@ class ActiveSession
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
end
- def self.cleaned_up_lookup_entries(redis, user_id)
- lookup_key = lookup_key_name(user_id)
+ def self.list_sessions(user)
+ sessions_from_ids(session_ids_for_user(user))
+ end
- session_ids = redis.smembers(lookup_key)
+ def self.session_ids_for_user(user)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.smembers(lookup_key_name(user.id))
+ end
+ end
- entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
- return [] if entry_keys.empty?
+ def self.sessions_from_ids(session_ids)
+ return [] if session_ids.empty?
- entries = redis.mget(entry_keys)
+ Gitlab::Redis::SharedState.with do |redis|
+ session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
- session_ids_and_entries = session_ids.zip(entries)
+ redis.mget(session_keys).compact.map do |raw_session|
+ # rubocop:disable Security/MarshalLoad
+ Marshal.load(raw_session)
+ # rubocop:enable Security/MarshalLoad
+ end
+ end
+ end
+
+ def self.raw_active_session_entries(session_ids, user_id)
+ return [] if session_ids.empty?
+
+ Gitlab::Redis::SharedState.with do |redis|
+ entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
+
+ redis.mget(entry_keys)
+ end
+ end
+
+ def self.cleaned_up_lookup_entries(redis, user)
+ session_ids = session_ids_for_user(user)
+ entries = raw_active_session_entries(session_ids, user.id)
# remove expired keys.
# only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually.
+ session_ids_and_entries = session_ids.zip(entries)
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
- redis.srem(lookup_key, session_id)
+ redis.srem(lookup_key_name(user.id), session_id)
end
- session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry }
+ entries.compact
end
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index b9ad676ca47..2815a117f7f 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Appearance < ActiveRecord::Base
+class Appearance < ApplicationRecord
include CacheableAttributes
include CacheMarkdownField
include ObjectStorage::BackgroundMove
@@ -20,6 +20,7 @@ class Appearance < ActiveRecord::Base
default_value_for :message_background_color, '#E75E40'
default_value_for :message_font_color, '#FFFFFF'
+ default_value_for :email_header_and_footer_enabled, false
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index a3d662d8250..0979d03f6e6 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -3,10 +3,33 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
+ alias_method :reset, :reload
+
def self.id_in(ids)
where(id: ids)
end
+ def self.id_not_in(ids)
+ where.not(id: ids)
+ end
+
+ def self.pluck_primary_key
+ where(nil).pluck(self.primary_key)
+ end
+
+ def self.safe_ensure_unique(retries: 0)
+ transaction(requires_new: true) do
+ yield
+ end
+ rescue ActiveRecord::RecordNotUnique
+ if retries > 0
+ retries -= 1
+ retry
+ end
+
+ false
+ end
+
def self.safe_find_or_create_by!(*args)
safe_find_or_create_by(*args).tap do |record|
record.validate! unless record.persisted?
@@ -14,10 +37,12 @@ class ApplicationRecord < ActiveRecord::Base
end
def self.safe_find_or_create_by(*args)
- transaction(requires_new: true) do
+ safe_ensure_unique(retries: 1) do
find_or_create_by(*args)
end
- rescue ActiveRecord::RecordNotUnique
- retry
+ end
+
+ def self.underscore
+ Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { self.to_s.underscore }
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index daadf9427ba..bbe2d2e8fd4 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -1,26 +1,20 @@
# frozen_string_literal: true
-class ApplicationSetting < ActiveRecord::Base
+class ApplicationSetting < ApplicationRecord
include CacheableAttributes
include CacheMarkdownField
include TokenAuthenticatable
include IgnorableColumn
include ChronicDurationAttribute
- add_authentication_token_field :runners_registration_token, encrypted: true, fallback: true
+ add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
add_authentication_token_field :health_check_access_token
- DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
- | # or
- \s # any whitespace character
- | # or
- [\r\n] # any number of newline characters
- }x
-
- # Setting a key restriction to `-1` means that all keys of this type are
- # forbidden.
- FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
- SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
+ # Include here so it can override methods from
+ # `add_authentication_token_field`
+ # We don't prepend for now because otherwise we'll need to
+ # fix a lot of tests using allow_any_instance_of
+ include ApplicationSettingImplementation
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
@@ -42,8 +36,6 @@ class ApplicationSetting < ActiveRecord::Base
cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
cache_markdown_field :after_sign_up_text
- attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
-
default_value_for :id, 1
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
@@ -56,20 +48,20 @@ class ApplicationSetting < ActiveRecord::Base
validates :home_page_url,
allow_blank: true,
- url: true,
+ addressable_url: true,
if: :home_page_url_column_exists?
validates :help_page_support_url,
allow_blank: true,
- url: true,
+ addressable_url: true,
if: :help_page_support_url_column_exists?
validates :after_sign_out_path,
allow_blank: true,
- url: true
+ addressable_url: true
validates :admin_notification_email,
- email: true,
+ devise_email: true,
allow_blank: true
validates :two_factor_grace_period,
@@ -206,7 +198,7 @@ class ApplicationSetting < ActiveRecord::Base
validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level|
unless Gitlab::VisibilityLevel.options.value?(level)
- record.errors.add(attr, "'#{level}' is not a valid visibility level")
+ record.errors.add(attr, _("'%{level}' is not a valid visibility level") % { level: level })
end
end
end
@@ -214,13 +206,63 @@ class ApplicationSetting < ActiveRecord::Base
validates_each :import_sources do |record, attr, value|
value&.each do |source|
unless Gitlab::ImportSources.options.value?(source)
- record.errors.add(attr, "'#{source}' is not a import source")
+ record.errors.add(attr, _("'%{source}' is not a import source") % { source: source })
end
end
end
validate :terms_exist, if: :enforce_terms?
+ validates :external_authorization_service_default_label,
+ presence: true,
+ if: :external_authorization_service_enabled
+
+ validates :external_authorization_service_url,
+ addressable_url: true, allow_blank: true,
+ if: :external_authorization_service_enabled
+
+ validates :external_authorization_service_timeout,
+ numericality: { greater_than: 0, less_than_or_equal_to: 10 },
+ if: :external_authorization_service_enabled
+
+ validates :external_auth_client_key,
+ presence: true,
+ if: -> (setting) { setting.external_auth_client_cert.present? }
+
+ validates :lets_encrypt_notification_email,
+ devise_email: true,
+ format: { without: /@example\.(com|org|net)\z/,
+ message: N_("Let's Encrypt does not accept emails on example.com") },
+ allow_blank: true
+
+ validates :lets_encrypt_notification_email,
+ presence: true,
+ if: :lets_encrypt_terms_of_service_accepted?
+
+ validates_with X509CertificateCredentialsValidator,
+ certificate: :external_auth_client_cert,
+ pkey: :external_auth_client_key,
+ pass: :external_auth_client_key_pass,
+ if: -> (setting) { setting.external_auth_client_cert.present? }
+
+ attr_encrypted :external_auth_client_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ attr_encrypted :external_auth_client_key_pass,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ attr_encrypted :lets_encrypt_private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
before_validation :ensure_uuid!
before_validation :strip_sentry_values
@@ -232,265 +274,12 @@ class ApplicationSetting < ActiveRecord::Base
end
after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
- def self.defaults
- {
- after_sign_up_text: nil,
- akismet_enabled: false,
- allow_local_requests_from_hooks_and_services: false,
- authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
- container_registry_token_expire_delay: 5,
- default_artifacts_expire_in: '30 days',
- default_branch_protection: Settings.gitlab['default_branch_protection'],
- default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- default_projects_limit: Settings.gitlab['default_projects_limit'],
- default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- disabled_oauth_sign_in_sources: [],
- domain_whitelist: Settings.gitlab['domain_whitelist'],
- dsa_key_restriction: 0,
- ecdsa_key_restriction: 0,
- ed25519_key_restriction: 0,
- first_day_of_week: 0,
- gitaly_timeout_default: 55,
- gitaly_timeout_fast: 10,
- gitaly_timeout_medium: 30,
- gravatar_enabled: Settings.gravatar['enabled'],
- help_page_hide_commercial_content: false,
- help_page_text: nil,
- hide_third_party_offers: false,
- housekeeping_bitmaps_enabled: true,
- housekeeping_enabled: true,
- housekeeping_full_repack_period: 50,
- housekeeping_gc_period: 200,
- housekeeping_incremental_repack_period: 10,
- import_sources: Settings.gitlab['import_sources'],
- max_artifacts_size: Settings.artifacts['max_size'],
- max_attachment_size: Settings.gitlab['max_attachment_size'],
- mirror_available: true,
- password_authentication_enabled_for_git: true,
- password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
- performance_bar_allowed_group_id: nil,
- rsa_key_restriction: 0,
- plantuml_enabled: false,
- plantuml_url: nil,
- polling_interval_multiplier: 1,
- project_export_enabled: true,
- recaptcha_enabled: false,
- repository_checks_enabled: true,
- repository_storages: ['default'],
- require_two_factor_authentication: false,
- restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
- session_expire_delay: Settings.gitlab['session_expire_delay'],
- send_user_confirmation_email: false,
- shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- shared_runners_text: nil,
- sign_in_text: nil,
- signup_enabled: Settings.gitlab['signup_enabled'],
- terminal_max_session_time: 0,
- throttle_authenticated_api_enabled: false,
- throttle_authenticated_api_period_in_seconds: 3600,
- throttle_authenticated_api_requests_per_period: 7200,
- throttle_authenticated_web_enabled: false,
- throttle_authenticated_web_period_in_seconds: 3600,
- throttle_authenticated_web_requests_per_period: 7200,
- throttle_unauthenticated_enabled: false,
- throttle_unauthenticated_period_in_seconds: 3600,
- throttle_unauthenticated_requests_per_period: 3600,
- two_factor_grace_period: 48,
- unique_ips_limit_enabled: false,
- unique_ips_limit_per_user: 10,
- unique_ips_limit_time_window: 3600,
- usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
- instance_statistics_visibility_private: false,
- user_default_external: false,
- user_default_internal_regex: nil,
- user_show_add_ssh_key_message: true,
- usage_stats_set_by_user_id: nil,
- diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
- commit_email_hostname: default_commit_email_hostname,
- protected_ci_variables: false,
- local_markdown_version: 0
- }
- end
-
- def self.default_commit_email_hostname
- "users.noreply.#{Gitlab.config.gitlab.host}"
- end
-
def self.create_from_defaults
- build_from_defaults.tap(&:save)
- end
-
- def self.human_attribute_name(attr, _options = {})
- if attr == :default_artifacts_expire_in
- 'Default artifacts expiration'
- else
+ transaction(requires_new: true) do
super
end
- end
-
- def home_page_url_column_exists?
- ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
- end
-
- def help_page_support_url_column_exists?
- ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
- end
-
- def disabled_oauth_sign_in_sources=(sources)
- sources = (sources || []).map(&:to_s) & Devise.omniauth_providers.map(&:to_s)
- super(sources)
- end
-
- def domain_whitelist_raw
- self.domain_whitelist&.join("\n")
- end
-
- def domain_blacklist_raw
- self.domain_blacklist&.join("\n")
- end
-
- def domain_whitelist_raw=(values)
- self.domain_whitelist = []
- self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_whitelist.reject! { |d| d.empty? }
- self.domain_whitelist
- end
-
- def domain_blacklist_raw=(values)
- self.domain_blacklist = []
- self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_blacklist.reject! { |d| d.empty? }
- self.domain_blacklist
- end
-
- def domain_blacklist_file=(file)
- self.domain_blacklist_raw = file.read
- end
-
- def repository_storages
- Array(read_attribute(:repository_storages))
- end
-
- def commit_email_hostname
- super.presence || self.class.default_commit_email_hostname
- end
-
- def default_project_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def default_snippet_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def default_group_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def restricted_visibility_levels=(levels)
- super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) })
- end
-
- def strip_sentry_values
- sentry_dsn.strip! if sentry_dsn.present?
- clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
- end
-
- def performance_bar_allowed_group
- Group.find_by_id(performance_bar_allowed_group_id)
- end
-
- # Return true if the Performance Bar is enabled for a given group
- def performance_bar_enabled
- performance_bar_allowed_group_id.present?
- end
-
- # Choose one of the available repository storage options. Currently all have
- # equal weighting.
- def pick_repository_storage
- repository_storages.sample
- end
-
- def runners_registration_token
- ensure_runners_registration_token!
- end
-
- def health_check_access_token
- ensure_health_check_access_token!
- end
-
- def usage_ping_can_be_configured?
- Settings.gitlab.usage_ping_enabled
- end
-
- def usage_ping_enabled
- usage_ping_can_be_configured? && super
- end
-
- def allowed_key_types
- SUPPORTED_KEY_TYPES.select do |type|
- key_restriction_for(type) != FORBIDDEN_KEY_VALUE
- end
- end
-
- def key_restriction_for(type)
- attr_name = "#{type}_key_restriction"
-
- has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def allow_signup?
- signup_enabled? && password_authentication_enabled_for_web?
- end
-
- def password_authentication_enabled?
- password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
- end
-
- def user_default_internal_regex_enabled?
- user_default_external? && user_default_internal_regex.present?
- end
-
- def user_default_internal_regex_instance
- Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
- end
-
- delegate :terms, to: :latest_terms, allow_nil: true
- def latest_terms
- @latest_terms ||= Term.latest
- end
-
- def reset_memoized_terms
- @latest_terms = nil
- latest_terms
- end
-
- def archive_builds_older_than
- archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
- end
-
- private
-
- def ensure_uuid!
- return if uuid?
-
- self.uuid = SecureRandom.uuid
- end
-
- def check_repository_storages
- invalid = repository_storages - Gitlab.config.repositories.storages.keys
- errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
- invalid.empty?
- end
-
- def terms_exist
- return unless enforce_terms?
-
- errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
- end
-
- def expire_performance_bar_allowed_user_ids_cache
- Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ rescue ActiveRecord::RecordNotUnique
+ # We already have an ApplicationSetting record, so just return it.
+ current_without_cache
end
end
diff --git a/app/models/application_setting/term.rb b/app/models/application_setting/term.rb
index 498701ba22b..723540c9b91 100644
--- a/app/models/application_setting/term.rb
+++ b/app/models/application_setting/term.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ApplicationSetting
- class Term < ActiveRecord::Base
+ class Term < ApplicationRecord
include CacheMarkdownField
has_many :term_agreements
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
new file mode 100644
index 00000000000..904d650ef96
--- /dev/null
+++ b/app/models/application_setting_implementation.rb
@@ -0,0 +1,299 @@
+# frozen_string_literal: true
+
+module ApplicationSettingImplementation
+ extend ActiveSupport::Concern
+
+ DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
+ | # or
+ \s # any whitespace character
+ | # or
+ [\r\n] # any number of newline characters
+ }x.freeze
+
+ # Setting a key restriction to `-1` means that all keys of this type are
+ # forbidden.
+ FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
+ SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
+
+ class_methods do
+ def defaults
+ {
+ after_sign_up_text: nil,
+ akismet_enabled: false,
+ allow_local_requests_from_hooks_and_services: false,
+ dns_rebinding_protection_enabled: true,
+ authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
+ container_registry_token_expire_delay: 5,
+ default_artifacts_expire_in: '30 days',
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
+ default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_project_creation: Settings.gitlab['default_project_creation'],
+ default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
+ default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ disabled_oauth_sign_in_sources: [],
+ domain_whitelist: Settings.gitlab['domain_whitelist'],
+ dsa_key_restriction: 0,
+ ecdsa_key_restriction: 0,
+ ed25519_key_restriction: 0,
+ first_day_of_week: 0,
+ gitaly_timeout_default: 55,
+ gitaly_timeout_fast: 10,
+ gitaly_timeout_medium: 30,
+ gravatar_enabled: Settings.gravatar['enabled'],
+ help_page_hide_commercial_content: false,
+ help_page_text: nil,
+ hide_third_party_offers: false,
+ housekeeping_bitmaps_enabled: true,
+ housekeeping_enabled: true,
+ housekeeping_full_repack_period: 50,
+ housekeeping_gc_period: 200,
+ housekeeping_incremental_repack_period: 10,
+ import_sources: Settings.gitlab['import_sources'],
+ max_artifacts_size: Settings.artifacts['max_size'],
+ max_attachment_size: Settings.gitlab['max_attachment_size'],
+ mirror_available: true,
+ password_authentication_enabled_for_git: true,
+ password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
+ performance_bar_allowed_group_id: nil,
+ rsa_key_restriction: 0,
+ plantuml_enabled: false,
+ plantuml_url: nil,
+ polling_interval_multiplier: 1,
+ project_export_enabled: true,
+ recaptcha_enabled: false,
+ repository_checks_enabled: true,
+ repository_storages: ['default'],
+ require_two_factor_authentication: false,
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
+ session_expire_delay: Settings.gitlab['session_expire_delay'],
+ send_user_confirmation_email: false,
+ shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
+ shared_runners_text: nil,
+ sign_in_text: nil,
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ terminal_max_session_time: 0,
+ throttle_authenticated_api_enabled: false,
+ throttle_authenticated_api_period_in_seconds: 3600,
+ throttle_authenticated_api_requests_per_period: 7200,
+ throttle_authenticated_web_enabled: false,
+ throttle_authenticated_web_period_in_seconds: 3600,
+ throttle_authenticated_web_requests_per_period: 7200,
+ throttle_unauthenticated_enabled: false,
+ throttle_unauthenticated_period_in_seconds: 3600,
+ throttle_unauthenticated_requests_per_period: 3600,
+ two_factor_grace_period: 48,
+ unique_ips_limit_enabled: false,
+ unique_ips_limit_per_user: 10,
+ unique_ips_limit_time_window: 3600,
+ usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
+ instance_statistics_visibility_private: false,
+ user_default_external: false,
+ user_default_internal_regex: nil,
+ user_show_add_ssh_key_message: true,
+ usage_stats_set_by_user_id: nil,
+ diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
+ commit_email_hostname: default_commit_email_hostname,
+ protected_ci_variables: false,
+ local_markdown_version: 0
+ }
+ end
+
+ def default_commit_email_hostname
+ "users.noreply.#{Gitlab.config.gitlab.host}"
+ end
+
+ def create_from_defaults
+ build_from_defaults.tap(&:save)
+ end
+
+ def human_attribute_name(attr, _options = {})
+ if attr == :default_artifacts_expire_in
+ 'Default artifacts expiration'
+ else
+ super
+ end
+ end
+ end
+
+ def home_page_url_column_exists?
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
+ end
+
+ def help_page_support_url_column_exists?
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
+ end
+
+ def disabled_oauth_sign_in_sources=(sources)
+ sources = (sources || []).map(&:to_s) & Devise.omniauth_providers.map(&:to_s)
+ super(sources)
+ end
+
+ def domain_whitelist_raw
+ self.domain_whitelist&.join("\n")
+ end
+
+ def domain_blacklist_raw
+ self.domain_blacklist&.join("\n")
+ end
+
+ def domain_whitelist_raw=(values)
+ self.domain_whitelist = []
+ self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
+ self.domain_whitelist.reject! { |d| d.empty? }
+ self.domain_whitelist
+ end
+
+ def domain_blacklist_raw=(values)
+ self.domain_blacklist = []
+ self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
+ self.domain_blacklist.reject! { |d| d.empty? }
+ self.domain_blacklist
+ end
+
+ def domain_blacklist_file=(file)
+ self.domain_blacklist_raw = file.read
+ end
+
+ def repository_storages
+ Array(read_attribute(:repository_storages))
+ end
+
+ def commit_email_hostname
+ super.presence || self.class.default_commit_email_hostname
+ end
+
+ def default_project_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def default_snippet_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def default_group_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def restricted_visibility_levels=(levels)
+ super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) })
+ end
+
+ def strip_sentry_values
+ sentry_dsn.strip! if sentry_dsn.present?
+ clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
+ end
+
+ def sentry_enabled
+ Gitlab.config.sentry.enabled || read_attribute(:sentry_enabled)
+ end
+
+ def sentry_dsn
+ Gitlab.config.sentry.dsn || read_attribute(:sentry_dsn)
+ end
+
+ def clientside_sentry_enabled
+ Gitlab.config.sentry.enabled || read_attribute(:clientside_sentry_enabled)
+ end
+
+ def clientside_sentry_dsn
+ Gitlab.config.sentry.clientside_dsn || read_attribute(:clientside_sentry_dsn)
+ end
+
+ def performance_bar_allowed_group
+ Group.find_by_id(performance_bar_allowed_group_id)
+ end
+
+ # Return true if the Performance Bar is enabled for a given group
+ def performance_bar_enabled
+ performance_bar_allowed_group_id.present?
+ end
+
+ # Choose one of the available repository storage options. Currently all have
+ # equal weighting.
+ def pick_repository_storage
+ repository_storages.sample
+ end
+
+ def runners_registration_token
+ ensure_runners_registration_token!
+ end
+
+ def health_check_access_token
+ ensure_health_check_access_token!
+ end
+
+ def usage_ping_can_be_configured?
+ Settings.gitlab.usage_ping_enabled
+ end
+
+ def usage_ping_enabled
+ usage_ping_can_be_configured? && super
+ end
+
+ def allowed_key_types
+ SUPPORTED_KEY_TYPES.select do |type|
+ key_restriction_for(type) != FORBIDDEN_KEY_VALUE
+ end
+ end
+
+ def key_restriction_for(type)
+ attr_name = "#{type}_key_restriction"
+
+ has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def allow_signup?
+ signup_enabled? && password_authentication_enabled_for_web?
+ end
+
+ def password_authentication_enabled?
+ password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
+ end
+
+ def user_default_internal_regex_enabled?
+ user_default_external? && user_default_internal_regex.present?
+ end
+
+ def user_default_internal_regex_instance
+ Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
+ end
+
+ delegate :terms, to: :latest_terms, allow_nil: true
+ def latest_terms
+ @latest_terms ||= ApplicationSetting::Term.latest
+ end
+
+ def reset_memoized_terms
+ @latest_terms = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ latest_terms
+ end
+
+ def archive_builds_older_than
+ archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
+ end
+
+ private
+
+ def ensure_uuid!
+ return if uuid?
+
+ self.uuid = SecureRandom.uuid
+ end
+
+ def check_repository_storages
+ invalid = repository_storages - Gitlab.config.repositories.storages.keys
+ errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
+ invalid.empty?
+ end
+
+ def terms_exist
+ return unless enforce_terms?
+
+ errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
+ end
+
+ def expire_performance_bar_allowed_user_ids_cache
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 8508c88d406..6ef2914ac11 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AuditEvent < ActiveRecord::Base
+class AuditEvent < ApplicationRecord
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index ddc516ccb60..e26162f6151 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AwardEmoji < ActiveRecord::Base
+class AwardEmoji < ApplicationRecord
DOWNVOTE_NAME = "thumbsdown".freeze
UPVOTE_NAME = "thumbsup".freeze
diff --git a/app/models/badge.rb b/app/models/badge.rb
index f016654206b..50299cd6652 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Badge < ActiveRecord::Base
+class Badge < ApplicationRecord
include FromUnion
# This structure sets the placeholders that the urls
@@ -22,7 +22,7 @@ class Badge < ActiveRecord::Base
scope :order_created_at_asc, -> { reorder(created_at: :asc) }
- validates :link_url, :image_url, url: { protocols: %w(http https) }
+ validates :link_url, :image_url, addressable_url: true
validates :type, presence: true
def rendered_link_url(project = nil)
diff --git a/app/models/blob.rb b/app/models/blob.rb
index c5766eb0327..d528bef8b19 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -110,7 +110,7 @@ class Blob < SimpleDelegator
end
def load_all_data!
- # Endpoint needed: gitlab-org/gitaly#756
+ # Endpoint needed: https://gitlab.com/gitlab-org/gitaly/issues/756
Gitlab::GitalyClient.allow_n_plus_1_calls do
super(project.repository) if project
end
diff --git a/app/models/board.rb b/app/models/board.rb
index 758a71d6903..e08db764f65 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Board < ActiveRecord::Base
+class Board < ApplicationRecord
belongs_to :group
belongs_to :project
diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb
index 92abbb67222..2f1cd830791 100644
--- a/app/models/board_group_recent_visit.rb
+++ b/app/models/board_group_recent_visit.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Tracks which boards in a specific group a user has visited
-class BoardGroupRecentVisit < ActiveRecord::Base
+class BoardGroupRecentVisit < ApplicationRecord
belongs_to :user
belongs_to :group
belongs_to :board
@@ -10,7 +10,7 @@ class BoardGroupRecentVisit < ActiveRecord::Base
validates :group, presence: true
validates :board, presence: true
- scope :by_user_group, -> (user, group) { where(user: user, group: group).order(:updated_at) }
+ scope :by_user_group, -> (user, group) { where(user: user, group: group) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, group: board.group, board: board)
@@ -19,7 +19,10 @@ class BoardGroupRecentVisit < ActiveRecord::Base
retry
end
- def self.latest(user, group)
- by_user_group(user, group).last
+ def self.latest(user, group, count: nil)
+ visits = by_user_group(user, group).order(updated_at: :desc)
+ visits = visits.preload(:board) if count && count > 1
+
+ visits.first(count)
end
end
diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb
index 7cffff906d8..236d88e909c 100644
--- a/app/models/board_project_recent_visit.rb
+++ b/app/models/board_project_recent_visit.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Tracks which boards in a specific project a user has visited
-class BoardProjectRecentVisit < ActiveRecord::Base
+class BoardProjectRecentVisit < ApplicationRecord
belongs_to :user
belongs_to :project
belongs_to :board
@@ -10,7 +10,7 @@ class BoardProjectRecentVisit < ActiveRecord::Base
validates :project, presence: true
validates :board, presence: true
- scope :by_user_project, -> (user, project) { where(user: user, project: project).order(:updated_at) }
+ scope :by_user_project, -> (user, project) { where(user: user, project: project) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, project: board.project, board: board)
@@ -19,7 +19,10 @@ class BoardProjectRecentVisit < ActiveRecord::Base
retry
end
- def self.latest(user, project)
- by_user_project(user, project).last
+ def self.latest(user, project, count: nil)
+ visits = by_user_project(user, project).order(updated_at: :desc)
+ visits = visits.preload(:board) if count && count > 1
+
+ visits.first(count)
end
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 2d237383e60..18fe2a9624f 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-class BroadcastMessage < ActiveRecord::Base
+class BroadcastMessage < ApplicationRecord
include CacheMarkdownField
include Sortable
- cache_markdown_field :message, pipeline: :broadcast_message
+ cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true
validates :message, presence: true
validates :starts_at, presence: true
diff --git a/app/models/chat_name.rb b/app/models/chat_name.rb
index 03b0af53046..0041595baba 100644
--- a/app/models/chat_name.rb
+++ b/app/models/chat_name.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ChatName < ActiveRecord::Base
+class ChatName < ApplicationRecord
LAST_USED_AT_INTERVAL = 1.hour
belongs_to :service
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 4e724f9adf7..52b5a7b4a91 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ChatTeam < ActiveRecord::Base
+class ChatTeam < ApplicationRecord
validates :team_id, presence: true
validates :namespace, uniqueness: true
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 5450d40ea95..644716ba8e7 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -3,8 +3,11 @@
module Ci
class Bridge < CommitStatus
include Ci::Processable
+ include Ci::Contextable
+ include Ci::PipelineDelegator
include Importable
include AfterCommitQueue
+ include HasRef
include Gitlab::Utils::StrongMemoize
belongs_to :project
@@ -37,11 +40,11 @@ module Ci
false
end
- def expanded_environment_name
+ def runnable?
+ false
end
- def predefined_variables
- raise NotImplementedError
+ def expanded_environment_name
end
def execute_hooks
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index c902e49ee6d..89cc082d0bc 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -2,9 +2,10 @@
module Ci
class Build < CommitStatus
- prepend ArtifactMigratable
include Ci::Processable
include Ci::Metadatable
+ include Ci::Contextable
+ include Ci::PipelineDelegator
include TokenAuthenticatable
include AfterCommitQueue
include ObjectStorage::BackgroundMove
@@ -18,6 +19,11 @@ module Ci
BuildArchivedError = Class.new(StandardError)
ignore_column :commands
+ ignore_column :artifacts_file
+ ignore_column :artifacts_metadata
+ ignore_column :artifacts_file_store
+ ignore_column :artifacts_metadata_store
+ ignore_column :artifacts_size
belongs_to :project, inverse_of: :builds
belongs_to :runner
@@ -25,7 +31,8 @@ module Ci
belongs_to :erased_by, class_name: 'User'
RUNNER_FEATURES = {
- upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }
+ upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
+ refspecs: -> (build) { build.merge_request_ref? }
}.freeze
has_one :deployment, as: :deployable, class_name: 'Deployment'
@@ -46,7 +53,6 @@ module Ci
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
- delegate :merge_request?, to: :pipeline
##
# Since Gitlab 11.5, deployments records started being created right after
@@ -80,8 +86,7 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts_archive, ->() do
- where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
- '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
+ where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
scope :with_existing_job_artifacts, ->(query) do
@@ -96,15 +101,15 @@ module Ci
where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
end
- scope :with_test_reports, ->() do
- with_existing_job_artifacts(Ci::JobArtifact.test_reports)
+ scope :with_reports, ->(reports_scope) do
+ with_existing_job_artifacts(reports_scope)
.eager_load_job_artifacts
end
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
- scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
- scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+ scope :with_artifacts_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.archive.with_files_stored_locally) }
+ scope :with_archived_trace_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.trace.with_files_stored_locally) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
@@ -132,14 +137,12 @@ module Ci
where("EXISTS (?)", matcher)
end
- mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file
- mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata
+ scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
acts_as_taggable
- add_authentication_token_field :token, encrypted: true, fallback: true
+ add_authentication_token_field :token, encrypted: :optional
- before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
before_destroy { unscoped_project }
@@ -147,9 +150,6 @@ module Ci
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
- after_save :update_project_statistics_after_save, if: :artifacts_size_changed?
- after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
-
class << self
# This is needed for url_for to work,
# as the controller is JobsController
@@ -171,6 +171,10 @@ module Ci
end
state_machine :status do
+ event :enqueue do
+ transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
+ end
+
event :actionize do
transition created: :manual
end
@@ -184,8 +188,12 @@ module Ci
end
event :enqueue_scheduled do
+ transition scheduled: :preparing, if: ->(build) do
+ build.scheduled_at&.past? && build.any_unmet_prerequisites?
+ end
+
transition scheduled: :pending, if: ->(build) do
- build.scheduled_at && build.scheduled_at < Time.now
+ build.scheduled_at&.past? && !build.any_unmet_prerequisites?
end
end
@@ -203,6 +211,12 @@ module Ci
end
end
+ after_transition any => [:preparing] do |build|
+ build.run_after_commit do
+ Ci::BuildPrepareWorker.perform_async(id)
+ end
+ end
+
after_transition any => [:pending] do |build|
build.run_after_commit do
BuildQueueWorker.perform_async(id)
@@ -289,6 +303,10 @@ module Ci
self.name == 'pages'
end
+ def runnable?
+ true
+ end
+
def archived?
return true if degenerated?
@@ -350,6 +368,14 @@ module Ci
!retried?
end
+ def any_unmet_prerequisites?
+ prerequisites.present?
+ end
+
+ def prerequisites
+ Gitlab::Ci::Build::Prerequisite::Factory.new(self).unmet
+ end
+
def expanded_environment_name
return unless has_environment?
@@ -398,46 +424,6 @@ module Ci
options&.dig(:environment, :on_stop)
end
- # A slugified version of the build ref, suitable for inclusion in URLs and
- # domain names. Rules:
- #
- # * Lowercased
- # * Anything not matching [a-z0-9-] is replaced with a -
- # * Maximum length is 63 bytes
- # * First/Last Character is not a hyphen
- def ref_slug
- Gitlab::Utils.slugify(ref.to_s)
- end
-
- ##
- # Variables in the environment name scope.
- #
- def scoped_variables(environment: expanded_environment_name)
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.concat(predefined_variables)
- variables.concat(project.predefined_variables)
- variables.concat(pipeline.predefined_variables)
- variables.concat(runner.predefined_variables) if runner
- variables.concat(project.deployment_variables(environment: environment)) if environment
- variables.concat(yaml_variables)
- variables.concat(user_variables)
- variables.concat(secret_group_variables)
- variables.concat(secret_project_variables(environment: environment))
- variables.concat(trigger_request.user_variables) if trigger_request
- variables.concat(pipeline.variables)
- variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
- end
- end
-
- ##
- # Variables that do not depend on the environment name.
- #
- def simple_variables
- strong_memoize(:simple_variables) do
- scoped_variables(environment: nil).to_runner_variables
- end
- end
-
##
# All variables, including persisted environment variables.
#
@@ -451,12 +437,46 @@ module Ci
end
end
- ##
- # Regular Ruby hash of scoped variables, without duplicates that are
- # possible to be present in an array of hashes returned from `variables`.
- #
- def scoped_variables_hash
- scoped_variables.to_hash
+ CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
+
+ def persisted_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless persisted?
+
+ variables
+ .concat(pipeline.persisted_variables)
+ .append(key: 'CI_JOB_ID', value: id.to_s)
+ .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self))
+ .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false, masked: true)
+ .append(key: 'CI_BUILD_ID', value: id.to_s)
+ .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false, masked: true)
+ .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
+ .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false, masked: true)
+ .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
+ .concat(deploy_token_variables)
+ end
+ end
+
+ def persisted_environment_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless persisted? && persisted_environment.present?
+
+ variables.concat(persisted_environment.predefined_variables)
+
+ # Here we're passing unexpanded environment_url for runner to expand,
+ # and we need to make sure that CI_ENVIRONMENT_NAME and
+ # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
+ variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
+ end
+ end
+
+ def deploy_token_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless gitlab_deploy_token
+
+ variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username)
+ variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false, masked: true)
+ end
end
def features
@@ -511,6 +531,26 @@ module Ci
trace.exist?
end
+ def artifacts_file
+ job_artifacts_archive&.file
+ end
+
+ def artifacts_size
+ job_artifacts_archive&.size
+ end
+
+ def artifacts_metadata
+ job_artifacts_metadata&.file
+ end
+
+ def artifacts?
+ !artifacts_expired? && artifacts_file&.exists?
+ end
+
+ def artifacts_metadata?
+ artifacts? && artifacts_metadata&.exists?
+ end
+
def has_job_artifacts?
job_artifacts.any?
end
@@ -579,14 +619,12 @@ module Ci
# and use that for `ExpireBuildInstanceArtifactsWorker`?
def erase_erasable_artifacts!
job_artifacts.erasable.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
end
def erase(opts = {})
return false unless erasable?
job_artifacts.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
erase_trace!
update_erased!(opts[:erased_by])
end
@@ -624,37 +662,13 @@ module Ci
end
def artifacts_file_for_type(type)
- file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
- # TODO: to be removed once legacy artifacts is removed
- file ||= legacy_artifacts_file if type == :archive
- file
+ job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
end
def coverage_regex
super || project.try(:build_coverage_regex)
end
- def user_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables if user.blank?
-
- variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
- variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
- variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
- variables.append(key: 'GITLAB_USER_NAME', value: user.name)
- end
- end
-
- def secret_group_variables
- return [] unless project.group
-
- project.group.ci_variables_for(git_ref, project)
- end
-
- def secret_project_variables(environment: persisted_environment)
- project.ci_variables_for(ref: git_ref, environment: environment)
- end
-
def steps
[Gitlab::Ci::Build::Step.from_commands(self),
Gitlab::Ci::Build::Step.from_after_script(self)].compact
@@ -755,9 +769,13 @@ module Ci
end
end
+ def report_artifacts
+ job_artifacts.with_reports
+ end
+
# Virtual deployment status depending on the environment status.
def deployment_status
- return nil unless starts_environment?
+ return unless starts_environment?
if success?
return successful_deployment_status
@@ -770,13 +788,6 @@ module Ci
private
- def erase_old_artifacts!
- # TODO: To be removed once we get rid of
- remove_artifacts_file!
- remove_artifacts_metadata!
- save
- end
-
def successful_deployment_status
if deployment&.last?
:last
@@ -798,10 +809,6 @@ module Ci
job_artifacts.select { |artifact| artifact.file_type.in?(report_types) }
end
- def update_artifacts_size
- self.artifacts_size = legacy_artifacts_file&.size
- end
-
def erase_trace!
trace.erase!
end
@@ -814,89 +821,6 @@ module Ci
@unscoped_project ||= Project.unscoped.find_by(id: project_id)
end
- CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
-
- def persisted_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables unless persisted?
-
- variables
- .concat(pipeline.persisted_variables)
- .append(key: 'CI_JOB_ID', value: id.to_s)
- .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self))
- .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false)
- .append(key: 'CI_BUILD_ID', value: id.to_s)
- .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false)
- .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
- .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false)
- .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
- .concat(deploy_token_variables)
- end
- end
-
- def predefined_variables # rubocop:disable Metrics/AbcSize
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.append(key: 'CI', value: 'true')
- variables.append(key: 'GITLAB_CI', value: 'true')
- variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
- variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
- variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
- variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
- variables.append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s)
- variables.append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s)
- variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision)
- variables.append(key: 'CI_JOB_NAME', value: name)
- variables.append(key: 'CI_JOB_STAGE', value: stage)
- variables.append(key: 'CI_COMMIT_SHA', value: sha)
- variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
- variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
- variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
- variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
- variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
- variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
- variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
- variables.append(key: "CI_NODE_INDEX", value: self.options[:instance].to_s) if self.options&.include?(:instance)
- variables.append(key: "CI_NODE_TOTAL", value: (self.options&.dig(:parallel) || 1).to_s)
- variables.concat(legacy_variables)
- end
- end
-
- def legacy_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.append(key: 'CI_BUILD_REF', value: sha)
- variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
- variables.append(key: 'CI_BUILD_REF_NAME', value: ref)
- variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug)
- variables.append(key: 'CI_BUILD_NAME', value: name)
- variables.append(key: 'CI_BUILD_STAGE', value: stage)
- variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
- variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request
- variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action?
- end
- end
-
- def persisted_environment_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables unless persisted? && persisted_environment.present?
-
- variables.concat(persisted_environment.predefined_variables)
-
- # Here we're passing unexpanded environment_url for runner to expand,
- # and we need to make sure that CI_ENVIRONMENT_NAME and
- # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
- variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
- end
- end
-
- def deploy_token_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables unless gitlab_deploy_token
-
- variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username)
- variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false)
- end
- end
-
def environment_url
options&.dig(:environment, :url) || persisted_environment&.external_url
end
@@ -919,21 +843,5 @@ module Ci
pipeline.config_processor.build_attributes(name)
end
-
- def update_project_statistics_after_save
- update_project_statistics(read_attribute(:artifacts_size).to_i - artifacts_size_was.to_i)
- end
-
- def update_project_statistics_after_destroy
- update_project_statistics(-artifacts_size)
- end
-
- def update_project_statistics(difference)
- ProjectStatistics.increment_statistic(project_id, :build_artifacts_size, difference)
- end
-
- def project_destroyed?
- project.pending_delete?
- end
end
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index cd8eb774cf5..f281cbd1d6f 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -3,7 +3,7 @@
module Ci
# The purpose of this class is to store Build related data that can be disposed.
# Data that should be persisted forever, should be stored with Ci::Build model.
- class BuildMetadata < ActiveRecord::Base
+ class BuildMetadata < ApplicationRecord
extend Gitlab::Ci::Model
include Presentable
include ChronicDurationAttribute
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index 457d7eeab6a..997bf298025 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -3,22 +3,34 @@
module Ci
# The purpose of this class is to store Build related runner session.
# Data will be removed after transitioning from running to any state.
- class BuildRunnerSession < ActiveRecord::Base
+ class BuildRunnerSession < ApplicationRecord
extend Gitlab::Ci::Model
+ TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'.freeze
+
self.table_name = 'ci_builds_runner_session'
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true
- validates :url, url: { protocols: %w(https) }
+ validates :url, addressable_url: { schemes: %w(https) }
def terminal_specification
- return {} unless url.present?
+ wss_url = Gitlab::UrlHelpers.as_wss(self.url)
+ return {} unless wss_url.present?
+
+ wss_url = "#{wss_url}/exec"
+ channel_specification(wss_url, TERMINAL_SUBPROTOCOL)
+ end
+
+ private
+
+ def channel_specification(url, subprotocol)
+ return {} if subprotocol.blank? || url.blank?
{
- subprotocols: ['terminal.gitlab.com'].freeze,
- url: "#{url}/exec".sub("https://", "wss://"),
+ subprotocols: Array(subprotocol),
+ url: url,
headers: { Authorization: [authorization.presence] }.compact,
ca_pem: certificate.presence
}
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 33e61cd2111..0a7a0e0772b 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceChunk < ActiveRecord::Base
+ class BuildTraceChunk < ApplicationRecord
include FastDestroyAll
include ::Gitlab::ExclusiveLeaseHelpers
extend Gitlab::Ci::Model
@@ -115,7 +115,7 @@ module Ci
current_data = get_data
unless current_data&.bytesize.to_i == CHUNK_SIZE
- raise FailedToPersistDataError, 'Data is not fullfilled in a bucket'
+ raise FailedToPersistDataError, 'Data is not fulfilled in a bucket'
end
old_store_class = self.class.get_store_class(data_store)
diff --git a/app/models/ci/build_trace_section.rb b/app/models/ci/build_trace_section.rb
index a4bee59c83b..8be42eb48d6 100644
--- a/app/models/ci/build_trace_section.rb
+++ b/app/models/ci/build_trace_section.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceSection < ActiveRecord::Base
+ class BuildTraceSection < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :build, class_name: 'Ci::Build'
diff --git a/app/models/ci/build_trace_section_name.rb b/app/models/ci/build_trace_section_name.rb
index cbdf3c4b673..c065cfea14e 100644
--- a/app/models/ci/build_trace_section_name.rb
+++ b/app/models/ci/build_trace_section_name.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceSectionName < ActiveRecord::Base
+ class BuildTraceSectionName < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :project
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
index 492d1d0329e..0e50265c7ba 100644
--- a/app/models/ci/group_variable.rb
+++ b/app/models/ci/group_variable.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
module Ci
- class GroupVariable < ActiveRecord::Base
+ class GroupVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Presentable
+ include Maskable
belongs_to :group, class_name: "::Group"
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 789bb293811..f80e98e5bca 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
module Ci
- class JobArtifact < ActiveRecord::Base
+ class JobArtifact < ApplicationRecord
include AfterCommitQueue
include ObjectStorage::BackgroundMove
+ include UpdateProjectStatistics
extend Gitlab::Ci::Model
NotSupportedAdapterError = Class.new(StandardError)
@@ -21,14 +22,19 @@ module Ci
container_scanning: 'gl-container-scanning-report.json',
dast: 'gl-dast-report.json',
license_management: 'gl-license-management-report.json',
- performance: 'performance.json'
+ performance: 'performance.json',
+ metrics: 'metrics.txt'
}.freeze
- TYPE_AND_FORMAT_PAIRS = {
+ INTERNAL_TYPES = {
archive: :zip,
metadata: :gzip,
- trace: :raw,
+ trace: :raw
+ }.freeze
+
+ REPORT_TYPES = {
junit: :gzip,
+ metrics: :gzip,
# All these file formats use `raw` as we need to store them uncompressed
# for Frontend to fetch the files and do analysis
@@ -42,6 +48,8 @@ module Ci
performance: :raw
}.freeze
+ TYPE_AND_FORMAT_PAIRS = INTERNAL_TYPES.merge(REPORT_TYPES).freeze
+
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
@@ -50,10 +58,10 @@ module Ci
validates :file_format, presence: true, unless: :trace?, on: :create
validate :valid_file_format?, unless: :trace?, on: :create
before_save :set_size, if: :file_changed?
- after_save :update_project_statistics_after_save, if: :size_changed?
- after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
- after_save :update_file_store, if: :file_changed?
+ update_project_statistics project_statistics_name: :build_artifacts_size
+
+ after_save :update_file_store, if: :saved_change_to_file?
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
@@ -63,6 +71,10 @@ module Ci
where(file_type: types)
end
+ scope :with_reports, -> do
+ with_file_types(REPORT_TYPES.keys.map(&:to_s))
+ end
+
scope :test_reports, -> do
with_file_types(TEST_REPORT_FILE_TYPES)
end
@@ -88,14 +100,15 @@ module Ci
dast: 8, ## EE-specific
codequality: 9, ## EE-specific
license_management: 10, ## EE-specific
- performance: 11 ## EE-specific
+ performance: 11, ## EE-specific
+ metrics: 12 ## EE-specific
}
enum file_format: {
raw: 1,
zip: 2,
gzip: 3
- }
+ }, _suffix: true
# `file_location` indicates where actual files are stored.
# Ideally, actual files should be stored in the same directory, and use the same
@@ -173,18 +186,6 @@ module Ci
self.size = file.size
end
- def update_project_statistics_after_save
- update_project_statistics(size.to_i - size_was.to_i)
- end
-
- def update_project_statistics_after_destroy
- update_project_statistics(-self.size.to_i)
- end
-
- def update_project_statistics(difference)
- ProjectStatistics.increment_statistic(project_id, :build_artifacts_size, difference)
- end
-
def project_destroyed?
# Use job.project to avoid extra DB query for project
job.project.pending_delete?
diff --git a/app/models/ci/legacy_stage.rb b/app/models/ci/legacy_stage.rb
index 96dbc7b6895..930c8a71453 100644
--- a/app/models/ci/legacy_stage.rb
+++ b/app/models/ci/legacy_stage.rb
@@ -58,5 +58,9 @@ module Ci
statuses.latest.failed_but_allowed.any?
end
end
+
+ def manual_playable?
+ %[manual scheduled skipped].include?(status.to_s)
+ end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index eb15347b4e1..3727a9861aa 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Pipeline < ActiveRecord::Base
+ class Pipeline < ApplicationRecord
extend Gitlab::Ci::Model
include HasStatus
include Importable
@@ -12,6 +12,11 @@ module Ci
include AtomicInternalId
include EnumWithNil
include HasRef
+ include ShaAttribute
+ include FromUnion
+
+ sha_attribute :source_sha
+ sha_attribute :target_sha
belongs_to :project, inverse_of: :all_pipelines
belongs_to :user
@@ -35,7 +40,7 @@ module Ci
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
- has_many :merge_requests, foreign_key: "head_pipeline_id"
+ has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
@@ -56,9 +61,9 @@ module Ci
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
- validates :merge_request, presence: { if: :merge_request? }
- validates :merge_request, absence: { unless: :merge_request? }
- validates :tag, inclusion: { in: [false], if: :merge_request? }
+ validates :merge_request, presence: { if: :merge_request_event? }
+ validates :merge_request, absence: { unless: :merge_request_event? }
+ validates :tag, inclusion: { in: [false], if: :merge_request_event? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
@@ -77,10 +82,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition [:created, :skipped, :scheduled] => :pending
+ transition [:created, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -113,7 +122,7 @@ module Ci
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
- before_transition [:created, :pending] => :running do |pipeline|
+ before_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
@@ -136,7 +145,7 @@ module Ci
end
end
- after_transition [:created, :pending] => :running do |pipeline|
+ after_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
@@ -144,7 +153,7 @@ module Ci
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
- after_transition [:created, :pending, :running] => :success do |pipeline|
+ after_transition [:created, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
@@ -157,6 +166,16 @@ module Ci
end
end
+ after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
+ pipeline.run_after_commit do
+ pipeline.all_merge_requests.each do |merge_request|
+ next unless merge_request.auto_merge_enabled?
+
+ AutoMergeProcessWorker.perform_async(merge_request.id)
+ end
+ end
+ end
+
after_transition any => [:success, :failed] do |pipeline|
pipeline.run_after_commit do
PipelineNotificationWorker.perform_async(pipeline.id)
@@ -175,20 +194,34 @@ module Ci
scope :sort_by_merge_request_pipelines, -> do
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
- query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request]]) # rubocop:disable GitlabSecurity/PublicSend
+ query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
order(query)
end
scope :for_user, -> (user) { where(user: user) }
+ scope :for_sha, -> (sha) { where(sha: sha) }
+ scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
+ scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
+
+ scope :triggered_by_merge_request, -> (merge_request) do
+ where(source: :merge_request_event, merge_request: merge_request)
+ end
+
+ scope :detached_merge_request_pipelines, -> (merge_request, sha) do
+ triggered_by_merge_request(merge_request).for_sha(sha)
+ end
+
+ scope :merge_request_pipelines, -> (merge_request, source_sha) do
+ triggered_by_merge_request(merge_request).for_source_sha(source_sha)
+ end
- scope :for_merge_request, -> (merge_request, ref, sha) do
- ##
- # We have to filter out unrelated MR pipelines.
- # When merge request is empty, it selects general pipelines, such as push sourced pipelines.
- # When merge request is matched, it selects MR pipelines.
- where(merge_request: [nil, merge_request], ref: ref, sha: sha)
- .sort_by_merge_request_pipelines
+ scope :triggered_for_branch, -> (ref) do
+ where(source: branch_pipeline_sources).where(ref: ref, tag: false)
+ end
+
+ scope :with_reports, -> (reports_scope) do
+ where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1))
end
# Returns the pipelines in descending order (= newest first), optionally
@@ -278,8 +311,8 @@ module Ci
sources.reject { |source| source == "external" }.values
end
- def self.latest_for_merge_request(merge_request, ref, sha)
- for_merge_request(merge_request, ref, sha).first
+ def self.branch_pipeline_sources
+ @branch_pipeline_sources ||= sources.reject { |source| source == 'merge_request_event' }.values
end
def self.ci_sources_values
@@ -397,10 +430,6 @@ module Ci
@commit ||= Commit.lazy(project, sha)
end
- def branch?
- super && !merge_request?
- end
-
def stuck?
pending_builds.any?(&:stuck?)
end
@@ -446,9 +475,9 @@ module Ci
end
def latest?
- return false unless ref && commit.present?
+ return false unless git_ref && commit.present?
- project.commit(ref) == commit
+ project.commit(git_ref) == commit
end
def retried
@@ -582,6 +611,7 @@ module Ci
retry_optimistic_lock(self) do
case latest_builds_status.to_s
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
@@ -622,8 +652,11 @@ module Ci
variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
+ variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s)
- if merge_request? && merge_request
+ if merge_request_event? && merge_request
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
variables.concat(merge_request.predefined_variables)
end
end
@@ -651,10 +684,10 @@ module Ci
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||=
- if merge_request?
- project.merge_requests.where(id: merge_request_id)
+ if merge_request_event?
+ MergeRequest.where(id: merge_request_id)
else
- project.merge_requests.where(source_branch: ref)
+ MergeRequest.where(source_project_id: project_id, source_branch: ref)
end
end
@@ -668,16 +701,16 @@ module Ci
# We purposely cast the builds to an Array here. Because we always use the
# rows if there are more than 0 this prevents us from having to run two
# queries: one to get the count and one to get the rows.
- @latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
+ @latest_builds_with_artifacts ||= builds.latest.with_artifacts_not_expired.to_a
end
- def has_test_reports?
- complete? && builds.latest.with_test_reports.any?
+ def has_reports?(reports_scope)
+ complete? && builds.latest.with_reports(reports_scope).exists?
end
def test_reports
Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
- builds.latest.with_test_reports.each do |build|
+ builds.latest.with_reports(Ci::JobArtifact.test_reports).each do |build|
build.collect_test_reports!(test_reports)
end
end
@@ -696,7 +729,7 @@ module Ci
# * nil: Modified path can not be evaluated
def modified_paths
strong_memoize(:modified_paths) do
- if merge_request?
+ if merge_request_event?
merge_request.modified_paths
elsif branch_updated?
push_details.modified_paths
@@ -708,6 +741,50 @@ module Ci
ref == project.default_branch
end
+ def triggered_by_merge_request?
+ merge_request_event? && merge_request_id.present?
+ end
+
+ def detached_merge_request_pipeline?
+ triggered_by_merge_request? && target_sha.nil?
+ end
+
+ def legacy_detached_merge_request_pipeline?
+ detached_merge_request_pipeline? && !merge_request_ref?
+ end
+
+ def merge_request_pipeline?
+ triggered_by_merge_request? && target_sha.present?
+ end
+
+ def merge_request_ref?
+ MergeRequest.merge_request_ref?(ref)
+ end
+
+ def matches_sha_or_source_sha?(sha)
+ self.sha == sha || self.source_sha == sha
+ end
+
+ def triggered_by?(current_user)
+ user == current_user
+ end
+
+ def source_ref
+ if triggered_by_merge_request?
+ merge_request.source_branch
+ else
+ ref
+ end
+ end
+
+ def source_ref_slug
+ Gitlab::Utils.slugify(source_ref.to_s)
+ end
+
+ def find_stage_by_name!(name)
+ stages.find_by!(name: name)
+ end
+
private
def ci_yaml_from_repo
@@ -739,16 +816,18 @@ module Ci
end
def git_ref
- if merge_request?
- ##
- # In the future, we're going to change this ref to
- # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
- # In order to do that, we have to update GitLab-Runner's source pulling
- # logic.
- # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
- Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
- else
- super
+ strong_memoize(:git_ref) do
+ if merge_request_event?
+ ##
+ # In the future, we're going to change this ref to
+ # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
+ # In order to do that, we have to update GitLab-Runner's source pulling
+ # logic.
+ # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
+ Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
+ else
+ super
+ end
end
end
diff --git a/app/models/ci/pipeline_chat_data.rb b/app/models/ci/pipeline_chat_data.rb
index 8d37500fec5..65466a8c6f8 100644
--- a/app/models/ci/pipeline_chat_data.rb
+++ b/app/models/ci/pipeline_chat_data.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineChatData < ActiveRecord::Base
+ class PipelineChatData < ApplicationRecord
self.table_name = 'ci_pipeline_chat_data'
belongs_to :chat_name
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 4be4fdb1ff2..571c4271475 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -23,7 +23,7 @@ module Ci
api: 5,
external: 6,
chat: 8,
- merge_request: 10
+ merge_request_event: 10
}
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 1c1f203bdb2..c40ad39be61 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
module Ci
- class PipelineSchedule < ActiveRecord::Base
+ class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
include IgnorableColumn
+ include StripAttribute
ignore_column :deleted_at
@@ -22,11 +23,17 @@ module Ci
before_save :set_next_run_at
+ strip_attributes :cron
+
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
+ scope :runnable_schedules, -> { active.where("next_run_at < ?", Time.now) }
+ scope :preloaded, -> { preload(:owner, :project) }
accepts_nested_attributes_for :variables, allow_destroy: true
+ alias_attribute :real_next_run, :next_run_at
+
def owned_by?(current_user)
owner == current_user
end
@@ -43,8 +50,14 @@ module Ci
update_attribute(:active, false)
end
+ ##
+ # The `next_run_at` column is set to the actual execution date of `PipelineScheduleWorker`.
+ # This way, a schedule like `*/1 * * * *` won't be triggered in a short interval
+ # when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer.
def set_next_run_at
- self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
+ self.next_run_at = Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'],
+ Time.zone.name)
+ .next_time_from(ideal_next_run_at)
end
def schedule_next_run!
@@ -53,15 +66,14 @@ module Ci
update_attribute(:next_run_at, nil) # update without validation
end
- def real_next_run(
- worker_cron: Settings.cron_jobs['pipeline_schedule_worker']['cron'],
- worker_time_zone: Time.zone.name)
- Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone)
- .next_time_from(next_run_at)
- end
-
def job_variables
variables&.map(&:to_runner_variable) || []
end
+
+ private
+
+ def ideal_next_run_at
+ Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
+ end
end
end
diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb
index fbb9987cab2..be6e5e76c31 100644
--- a/app/models/ci/pipeline_schedule_variable.rb
+++ b/app/models/ci/pipeline_schedule_variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineScheduleVariable < ActiveRecord::Base
+ class PipelineScheduleVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb
index 08514d6af4e..51a6272e1ff 100644
--- a/app/models/ci/pipeline_variable.rb
+++ b/app/models/ci/pipeline_variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineVariable < ActiveRecord::Base
+ class PipelineVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 5aae31de6e2..07d00503861 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Runner < ActiveRecord::Base
+ class Runner < ApplicationRecord
extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern
include IgnorableColumn
@@ -10,7 +10,7 @@ module Ci
include FromUnion
include TokenAuthenticatable
- add_authentication_token_field :token, encrypted: true, migrating: true
+ add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
enum access_level: {
not_protected: 0,
@@ -97,6 +97,7 @@ module Ci
scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
scope :order_created_at_desc, -> { order(created_at: :desc) }
+ scope :with_tags, -> { preload(:tags) }
validate :tag_constraints
validates :access_level, presence: true
diff --git a/app/models/ci/runner_namespace.rb b/app/models/ci/runner_namespace.rb
index 22b80b98551..6903e8a21a1 100644
--- a/app/models/ci/runner_namespace.rb
+++ b/app/models/ci/runner_namespace.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class RunnerNamespace < ActiveRecord::Base
+ class RunnerNamespace < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :runner, inverse_of: :runner_namespaces, validate: true
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 1a718d24141..f5bd50dc5a3 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class RunnerProject < ActiveRecord::Base
+ class RunnerProject < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :runner, inverse_of: :runner_projects
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 0389945191e..d90339d90dc 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Stage < ActiveRecord::Base
+ class Stage < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
include HasStatus
@@ -39,10 +39,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition created: :pending
+ transition [:created, :preparing] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -76,6 +80,7 @@ module Ci
retry_optimistic_lock(self) do
case statuses.latest.status
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
@@ -115,5 +120,9 @@ module Ci
.new(self, current_user)
.fabricate!
end
+
+ def manual_playable?
+ blocked? || skipped?
+ end
end
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 637148c4ce4..8927bb9bc18 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Trigger < ActiveRecord::Base
+ class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
include IgnorableColumn
include Presentable
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 0b52c690e93..5daf3dd192d 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class TriggerRequest < ActiveRecord::Base
+ class TriggerRequest < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :trigger
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 524d79014f8..a77bbef0fca 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
module Ci
- class Variable < ActiveRecord::Base
+ class Variable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Presentable
+ include Maskable
belongs_to :project
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index c758577815a..d6a7d1d2bdd 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class CertManager < ActiveRecord::Base
+ class CertManager < ApplicationRecord
VERSION = 'v0.5.2'.freeze
self.table_name = 'clusters_applications_cert_managers'
@@ -24,6 +24,12 @@ module Clusters
'stable/cert-manager'
end
+ # We will implement this in future MRs.
+ # Need to reverse postinstall step
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'certmanager',
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index 423071ec024..a83d06c4b00 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -4,7 +4,7 @@ require 'openssl'
module Clusters
module Applications
- class Helm < ActiveRecord::Base
+ class Helm < ApplicationRecord
self.table_name = 'clusters_applications_helm'
attr_encrypted :ca_key,
@@ -29,6 +29,13 @@ module Clusters
self.status = 'installable' if cluster&.platform_kubernetes_active?
end
+ # We will implement this in future MRs.
+ # Basically we need to check all other applications are not installed
+ # first.
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InitCommand.new(
name: name,
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 7c15aaa4825..a1023f44049 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Ingress < ActiveRecord::Base
+ class Ingress < ApplicationRecord
VERSION = '1.1.2'.freeze
self.table_name = 'clusters_applications_ingress'
@@ -35,6 +35,13 @@ module Clusters
'stable/nginx-ingress'
end
+ # We will implement this in future MRs.
+ # Basically we need to check all dependent applications are not installed
+ # first.
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -48,6 +55,7 @@ module Clusters
def schedule_status_update
return unless installed?
return if external_ip
+ return if external_hostname
ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 421a923d386..4aaa1f941e5 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
+require 'securerandom'
+
module Clusters
module Applications
- class Jupyter < ActiveRecord::Base
- VERSION = 'v0.6'.freeze
+ class Jupyter < ApplicationRecord
+ VERSION = '0.9-174bbd5'.freeze
self.table_name = 'clusters_applications_jupyter'
@@ -18,8 +20,10 @@ module Clusters
def set_initial_status
return unless not_installable?
+ return unless cluster&.application_ingress_available?
- if cluster&.application_ingress_available? && cluster.application_ingress.external_ip
+ ingress = cluster.application_ingress
+ if ingress.external_ip || ingress.external_hostname
self.status = 'installable'
end
end
@@ -36,6 +40,12 @@ module Clusters
content_values.to_yaml
end
+ # Will be addressed in future MRs
+ # We need to investigate and document what will be permanently deleted.
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -51,6 +61,10 @@ module Clusters
"http://#{hostname}/hub/oauth_callback"
end
+ def oauth_scopes
+ 'api read_repository write_repository'
+ end
+
private
def specification
@@ -72,24 +86,41 @@ module Clusters
"secretToken" => secret_token
},
"auth" => {
+ "state" => {
+ "cryptoKey" => crypto_key
+ },
"gitlab" => {
"clientId" => oauth_application.uid,
"clientSecret" => oauth_application.secret,
- "callbackUrl" => callback_url
+ "callbackUrl" => callback_url,
+ "gitlabProjectIdWhitelist" => [project_id]
}
},
"singleuser" => {
"extraEnv" => {
- "GITLAB_CLUSTER_ID" => cluster.id
+ "GITLAB_CLUSTER_ID" => cluster.id.to_s,
+ "GITLAB_HOST" => gitlab_host
}
}
}
end
+ def crypto_key
+ @crypto_key ||= SecureRandom.hex(32)
+ end
+
+ def project_id
+ cluster&.project&.id
+ end
+
def gitlab_url
Gitlab.config.gitlab.url
end
+ def gitlab_host
+ Gitlab.config.gitlab.host
+ end
+
def content_values
YAML.load_file(chart_values_file).deep_merge!(specification)
end
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 8d79b041b64..d5a3bd62e3d 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -2,8 +2,8 @@
module Clusters
module Applications
- class Knative < ActiveRecord::Base
- VERSION = '0.2.2'.freeze
+ class Knative < ApplicationRecord
+ VERSION = '0.5.0'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
@@ -15,9 +15,6 @@ module Clusters
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
- include ReactiveCaching
-
- self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] }
def set_initial_status
return unless not_installable?
@@ -41,8 +38,6 @@ module Clusters
scope :for_cluster, -> (cluster) { where(cluster: cluster) }
- after_save :clear_reactive_cache!
-
def chart
'knative/knative'
end
@@ -51,6 +46,12 @@ module Clusters
{ "domain" => hostname }.to_yaml
end
+ # Handled in a new issue:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/59369
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -66,59 +67,17 @@ module Clusters
def schedule_status_update
return unless installed?
return if external_ip
+ return if external_hostname
ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
- def client
- cluster.kubeclient.knative_client
- end
-
- def services
- with_reactive_cache do |data|
- data[:services]
- end
- end
-
- def calculate_reactive_cache
- { services: read_services, pods: read_pods }
- end
-
def ingress_service
- cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system')
- end
-
- def services_for(ns: namespace)
- return [] unless services
- return [] unless ns
-
- services.select do |service|
- service.dig('metadata', 'namespace') == ns
- end
- end
-
- def service_pod_details(ns, service)
- with_reactive_cache do |data|
- data[:pods].select { |pod| filter_pods(pod, ns, service) }
- end
+ cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
end
private
- def read_pods
- cluster.kubeclient.core_client.get_pods.as_json
- end
-
- def filter_pods(pod, namespace, service)
- pod["metadata"]["namespace"] == namespace && pod["metadata"]["labels"]["serving.knative.dev/service"] == service
- end
-
- def read_services
- client.get_services.as_json
- rescue Kubeclient::ResourceNotFoundError
- []
- end
-
def install_knative_metrics
["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available?
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index fa7ce363531..a6b7617b830 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Prometheus < ActiveRecord::Base
+ class Prometheus < ApplicationRecord
include PrometheusAdapter
VERSION = '6.7.3'
@@ -16,10 +16,12 @@ module Clusters
default_value_for :version, VERSION
+ after_destroy :disable_prometheus_integration
+
state_machine :status do
after_transition any => [:installed] do |application|
application.cluster.projects.each do |project|
- project.find_or_initialize_service('prometheus').update(active: true)
+ project.find_or_initialize_service('prometheus').update!(active: true)
end
end
end
@@ -47,6 +49,14 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files
+ )
+ end
+
def upgrade_command(values)
::Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -82,6 +92,12 @@ module Clusters
private
+ def disable_prometheus_integration
+ cluster.projects.each do |project|
+ project.prometheus_service&.update!(active: false)
+ end
+ end
+
def kube_client
cluster&.kubeclient&.core_client
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 941551dadaa..db7fd8524c2 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -2,8 +2,8 @@
module Clusters
module Applications
- class Runner < ActiveRecord::Base
- VERSION = '0.2.0'.freeze
+ class Runner < ApplicationRecord
+ VERSION = '0.5.2'.freeze
self.table_name = 'clusters_applications_runners'
@@ -13,7 +13,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationData
belongs_to :runner, class_name: 'Ci::Runner', foreign_key: :runner_id
- delegate :project, to: :cluster
+ delegate :project, :group, to: :cluster
default_value_for :version, VERSION
@@ -29,6 +29,13 @@ module Clusters
content_values.to_yaml
end
+ # Need to investigate if pipelines run by this runner will stop upon the
+ # executor pod stopping
+ # I.e.run a pipeline, and uninstall runner while pipeline is running
+ def allowed_to_uninstall?
+ false
+ end
+
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -55,12 +62,19 @@ module Clusters
end
def runner_create_params
- {
+ attributes = {
name: 'kubernetes-cluster',
- runner_type: :project_type,
- tag_list: %w(kubernetes cluster),
- projects: [project]
+ runner_type: cluster.cluster_type,
+ tag_list: %w[kubernetes cluster]
}
+
+ if cluster.group_type?
+ attributes[:groups] = [group]
+ elsif cluster.project_type?
+ attributes[:projects] = [project]
+ end
+
+ attributes
end
def gitlab_url
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index be3e6a05e1e..e1d6b2a802b 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -1,22 +1,26 @@
# frozen_string_literal: true
module Clusters
- class Cluster < ActiveRecord::Base
+ class Cluster < ApplicationRecord
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
+ include ReactiveCaching
self.table_name = 'clusters'
+ self.reactive_cache_key = -> (cluster) { [cluster.class.model_name.singular, cluster.id] }
+ PROJECT_ONLY_APPLICATIONS = {
+ Applications::Jupyter.application_name => Applications::Jupyter,
+ Applications::Knative.application_name => Applications::Knative
+ }.freeze
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager,
- Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
- Applications::Jupyter.application_name => Applications::Jupyter,
- Applications::Knative.application_name => Applications::Knative
- }.freeze
+ Applications::Prometheus.application_name => Applications::Prometheus
+ }.merge(PROJECT_ONLY_APPLICATIONS).freeze
DEFAULT_ENVIRONMENT = '*'.freeze
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
@@ -43,7 +47,6 @@ module Clusters
has_one :application_knative, class_name: 'Clusters::Applications::Knative'
has_many :kubernetes_namespaces
- has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
@@ -56,6 +59,8 @@ module Clusters
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
+ after_save :clear_reactive_cache!
+
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@@ -67,8 +72,10 @@ module Clusters
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
+ delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true
alias_attribute :base_domain, :domain
+ alias_attribute :provided_by_user?, :user?
enum cluster_type: {
instance_type: 1,
@@ -90,6 +97,7 @@ module Clusters
scope :user_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:user]) }
scope :gcp_provided, -> { where(provider_type: ::Clusters::Cluster.provider_types[:gcp]) }
scope :gcp_installed, -> { gcp_provided.includes(:provider_gcp).where(cluster_providers_gcp: { status: ::Clusters::Providers::Gcp.state_machines[:status].states[:created].value }) }
+ scope :managed, -> { where(managed: true) }
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
@@ -103,29 +111,35 @@ module Clusters
scope :preload_knative, -> {
preload(
- :kubernetes_namespace,
+ :kubernetes_namespaces,
:platform_kubernetes,
:application_knative
)
}
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
+ return [] if clusterable.is_a?(Instance)
+
hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
- hierarchy_groups.flat_map(&:clusters)
+ hierarchy_groups.flat_map(&:clusters) + Instance.new.clusters
end
def status_name
- if provider
- provider.status_name
- else
- :created
+ provider&.status_name || connection_status.presence || :created
+ end
+
+ def connection_status
+ with_reactive_cache do |data|
+ data[:connection_status]
end
end
- def created?
- status_name == :created
+ def calculate_reactive_cache
+ return unless enabled?
+
+ { connection_status: retrieve_connection_status }
end
def applications
@@ -148,10 +162,6 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def managed?
- !user?
- end
-
def all_projects
if project_type?
projects
@@ -176,20 +186,24 @@ module Clusters
end
alias_method :group, :first_group
+ def instance
+ Instance.new if instance_type?
+ end
+
def kubeclient
platform_kubernetes.kubeclient if kubernetes?
end
+ def kubernetes_namespace_for(project)
+ find_or_initialize_kubernetes_namespace_for_project(project).namespace
+ end
+
def find_or_initialize_kubernetes_namespace_for_project(project)
- if project_type?
- kubernetes_namespaces.find_or_initialize_by(
- project: project,
- cluster_project: cluster_project
- )
- else
- kubernetes_namespaces.find_or_initialize_by(
- project: project
- )
+ attributes = { project: project }
+ attributes[:cluster_project] = cluster_project if project_type?
+
+ kubernetes_namespaces.find_or_initialize_by(attributes).tap do |namespace|
+ namespace.set_defaults
end
end
@@ -198,7 +212,7 @@ module Clusters
end
def kube_ingress_domain
- @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
+ @kube_ingress_domain ||= domain.presence || instance_domain
end
def predefined_variables
@@ -209,12 +223,43 @@ module Clusters
end
end
+ def knative_services_finder(project)
+ @knative_services_finder ||= KnativeServicesFinder.new(self, project)
+ end
+
private
def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
+ def retrieve_connection_status
+ kubeclient.core_client.discover
+ rescue *Gitlab::Kubernetes::Errors::CONNECTION
+ :unreachable
+ rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
+ :authentication_failure
+ rescue Kubeclient::HttpError => e
+ kubeclient_error_status(e.message)
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
+
+ :unknown_failure
+ else
+ :connected
+ end
+
+ # KubeClient uses the same error class
+ # For connection errors (eg. timeout) and
+ # for Kubernetes errors.
+ def kubeclient_error_status(message)
+ if message&.match?(/timed out|timeout/i)
+ :unreachable
+ else
+ :authentication_failure
+ end
+ end
+
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index 683b45331f6..4514498b84b 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -18,6 +18,16 @@ module Clusters
self.status = 'installable' if cluster&.application_helm_available?
end
+ def can_uninstall?
+ allowed_to_uninstall?
+ end
+
+ # All new applications should uninstall by default
+ # Override if there's dependencies that needs to be uninstalled first
+ def allowed_to_uninstall?
+ true
+ end
+
def self.application_name
self.to_s.demodulize.underscore
end
@@ -30,6 +40,12 @@ module Clusters
# Override if you need extra data synchronized
# from K8s after installation
end
+
+ def update_command
+ install_command.tap do |command|
+ command.version = version
+ end
+ end
end
end
end
diff --git a/app/models/clusters/concerns/application_data.rb b/app/models/clusters/concerns/application_data.rb
index 52498f123ff..3479fea415e 100644
--- a/app/models/clusters/concerns/application_data.rb
+++ b/app/models/clusters/concerns/application_data.rb
@@ -3,48 +3,52 @@
module Clusters
module Concerns
module ApplicationData
- extend ActiveSupport::Concern
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files
+ )
+ end
- included do
- def repository
- nil
- end
+ def repository
+ nil
+ end
- def values
- File.read(chart_values_file)
- end
+ def values
+ File.read(chart_values_file)
+ end
- def files
- @files ||= begin
- files = { 'values.yaml': values }
+ def files
+ @files ||= begin
+ files = { 'values.yaml': values }
- files.merge!(certificate_files) if cluster.application_helm.has_ssl?
+ files.merge!(certificate_files) if cluster.application_helm.has_ssl?
- files
- end
+ files
end
+ end
- private
+ private
- def certificate_files
- {
- 'ca.pem': ca_cert,
- 'cert.pem': helm_cert.cert_string,
- 'key.pem': helm_cert.key_string
- }
- end
+ def certificate_files
+ {
+ 'ca.pem': ca_cert,
+ 'cert.pem': helm_cert.cert_string,
+ 'key.pem': helm_cert.key_string
+ }
+ end
- def ca_cert
- cluster.application_helm.ca_cert
- end
+ def ca_cert
+ cluster.application_helm.ca_cert
+ end
- def helm_cert
- @helm_cert ||= cluster.application_helm.issue_client_cert
- end
+ def helm_cert
+ @helm_cert ||= cluster.application_helm.issue_client_cert
+ end
- def chart_values_file
- "#{Rails.root}/vendor/#{name}/values.yaml"
- end
+ def chart_values_file
+ "#{Rails.root}/vendor/#{name}/values.yaml"
end
end
end
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 1273ed83abe..54a3dda6d75 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -25,9 +25,11 @@ module Clusters
state :updating, value: 4
state :updated, value: 5
state :update_errored, value: 6
+ state :uninstalling, value: 7
+ state :uninstall_errored, value: 8
event :make_scheduled do
- transition [:installable, :errored, :installed, :updated, :update_errored] => :scheduled
+ transition [:installable, :errored, :installed, :updated, :update_errored, :uninstall_errored] => :scheduled
end
event :make_installing do
@@ -40,8 +42,9 @@ module Clusters
end
event :make_errored do
- transition any - [:updating] => :errored
+ transition any - [:updating, :uninstalling] => :errored
transition [:updating] => :update_errored
+ transition [:uninstalling] => :uninstall_errored
end
event :make_updating do
@@ -52,6 +55,10 @@ module Clusters
transition any => :update_errored
end
+ event :make_uninstalling do
+ transition [:scheduled] => :uninstalling
+ end
+
before_transition any => [:scheduled] do |app_status, _|
app_status.status_reason = nil
end
@@ -65,7 +72,7 @@ module Clusters
app_status.status_reason = nil
end
- before_transition any => [:update_errored] do |app_status, transition|
+ before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition|
status_reason = transition.args.first
app_status.status_reason = status_reason if status_reason
end
diff --git a/app/models/clusters/group.rb b/app/models/clusters/group.rb
index 2b08a9e47f0..27f39b53579 100644
--- a/app/models/clusters/group.rb
+++ b/app/models/clusters/group.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Clusters
- class Group < ActiveRecord::Base
+ class Group < ApplicationRecord
self.table_name = 'cluster_groups'
belongs_to :cluster, class_name: 'Clusters::Cluster'
diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb
new file mode 100644
index 00000000000..d8a888d53ba
--- /dev/null
+++ b/app/models/clusters/instance.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Clusters
+ class Instance
+ def clusters
+ Clusters::Cluster.instance_type
+ end
+
+ def feature_available?(feature)
+ ::Feature.enabled?(feature, default_enabled: true)
+ end
+
+ def self.enabled?
+ ::Feature.enabled?(:instance_clusters, default_enabled: true)
+ end
+ end
+end
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index 73da6cb37d7..b0c4900546e 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Clusters
- class KubernetesNamespace < ActiveRecord::Base
+ class KubernetesNamespace < ApplicationRecord
include Gitlab::Kubernetes
self.table_name = 'clusters_kubernetes_namespaces'
@@ -37,7 +37,7 @@ module Clusters
variables
.append(key: 'KUBE_SERVICE_ACCOUNT', value: service_account_name.to_s)
.append(key: 'KUBE_NAMESPACE', value: namespace.to_s)
- .append(key: 'KUBE_TOKEN', value: service_account_token.to_s, public: false)
+ .append(key: 'KUBE_TOKEN', value: service_account_token.to_s, public: false, masked: true)
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 46d0898014e..9b951578aee 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -2,7 +2,7 @@
module Clusters
module Platforms
- class Kubernetes < ActiveRecord::Base
+ class Kubernetes < ApplicationRecord
include Gitlab::Kubernetes
include ReactiveCaching
include EnumWithNil
@@ -41,7 +41,7 @@ module Clusters
validate :no_namespace, unless: :allow_user_defined_namespace?
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
- validates :api_url, url: true, presence: true
+ validates :api_url, public_url: true, presence: true
validates :token, presence: true
validates :ca_cert, certificate: true, allow_blank: true, if: :ca_cert_changed?
@@ -52,11 +52,14 @@ module Clusters
alias_attribute :ca_pem, :ca_cert
- delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
- delegate :managed?, to: :cluster, allow_nil: true
+ delegate :provided_by_user?, to: :cluster, allow_nil: true
delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true
- delegate :kubernetes_namespace, to: :cluster
+
+ # This is just to maintain compatibility with KubernetesService, which
+ # will be removed in https://gitlab.com/gitlab-org/gitlab-ce/issues/39217.
+ # It can be removed once KubernetesService is gone.
+ delegate :kubernetes_namespace_for, to: :cluster, allow_nil: true
alias_method :active?, :enabled?
@@ -68,14 +71,6 @@ module Clusters
default_value_for :authorization_type, :rbac
- def actual_namespace
- if namespace.present?
- namespace
- else
- default_namespace
- end
- end
-
def predefined_variables(project:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'KUBE_URL', value: api_url)
@@ -88,16 +83,19 @@ module Clusters
if kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project)
variables.concat(kubernetes_namespace.predefined_variables)
- elsif cluster.project_type?
- # From 11.5, every Clusters::Project should have at least one
- # Clusters::KubernetesNamespace, so once migration has been completed,
- # this 'else' branch will be removed. For more information, please see
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433
+ elsif cluster.project_type? || !cluster.managed?
+ # As of 11.11 a user can create a cluster that they manage themselves,
+ # which replicates the existing project-level cluster behaviour.
+ # Once we have marked all project-level clusters that make use of this
+ # behaviour as "unmanaged", we can remove the `cluster.project_type?`
+ # check here.
+ project_namespace = cluster.kubernetes_namespace_for(project)
+
variables
.append(key: 'KUBE_URL', value: api_url)
- .append(key: 'KUBE_TOKEN', value: token, public: false)
- .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
- .append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
+ .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true)
+ .append(key: 'KUBE_NAMESPACE', value: project_namespace)
+ .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true)
end
variables.concat(cluster.predefined_variables)
@@ -110,8 +108,10 @@ module Clusters
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
+ project = environment.project
+
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, cluster.kubernetes_namespace_for(project), pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
@@ -119,7 +119,7 @@ module Clusters
# Caches resources in the namespace so other calls don't need to block on
# network access
def calculate_reactive_cache
- return unless enabled? && project && !project.pending_delete?
+ return unless enabled?
# We may want to cache extra things in the future
{ pods: read_pods }
@@ -131,33 +131,16 @@ module Clusters
private
- def kubeconfig
+ def kubeconfig(namespace)
to_kubeconfig(
url: api_url,
- namespace: actual_namespace,
+ namespace: namespace,
token: token,
ca_pem: ca_pem)
end
- def default_namespace
- kubernetes_namespace&.namespace.presence || fallback_default_namespace
- end
-
- # DEPRECATED
- #
- # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to
- # have multiple namespaces per project. This method will be removed after migration
- # has been completed.
- def fallback_default_namespace
- return unless project
-
- slug = "#{project.path}-#{project.id}".downcase
- Gitlab::NamespaceSanitizer.sanitize(slug)
- end
-
def build_kube_client!
raise "Incomplete settings" unless api_url
- raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace
unless (username && password) || token
raise "Either username/password or token is required to access API"
@@ -173,9 +156,13 @@ module Clusters
# Returns a hash of all pods in the namespace
def read_pods
- kubeclient = build_kube_client!
+ # TODO: The project lookup here should be moved (to environment?),
+ # which will enable reading pods from the correct namespace for group
+ # and instance clusters.
+ # This will be done in https://gitlab.com/gitlab-org/gitlab-ce/issues/61156
+ return [] unless cluster.project_type?
- kubeclient.get_pods(namespace: actual_namespace).as_json
+ kubeclient.get_pods(namespace: cluster.kubernetes_namespace_for(cluster.first_project)).as_json
rescue Kubeclient::ResourceNotFoundError
[]
end
@@ -219,7 +206,7 @@ module Clusters
end
def prevent_modification
- return unless managed?
+ return if provided_by_user?
if api_url_changed? || token_changed? || ca_pem_changed?
errors.add(:base, _('Cannot modify managed Kubernetes cluster'))
@@ -230,7 +217,7 @@ module Clusters
end
def update_kubernetes_namespace
- return unless namespace_changed?
+ return unless saved_change_to_namespace?
run_after_commit do
ClusterConfigureWorker.perform_async(cluster_id)
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 15092b1c9d2..e0bf60164ba 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -1,13 +1,12 @@
# frozen_string_literal: true
module Clusters
- class Project < ActiveRecord::Base
+ class Project < ApplicationRecord
self.table_name = 'cluster_projects'
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
- has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
end
end
diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb
index 16b59cd9d14..390748bf252 100644
--- a/app/models/clusters/providers/gcp.rb
+++ b/app/models/clusters/providers/gcp.rb
@@ -2,7 +2,7 @@
module Clusters
module Providers
- class Gcp < ActiveRecord::Base
+ class Gcp < ApplicationRecord
self.table_name = 'cluster_providers_gcp'
belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster'
diff --git a/app/models/commit.rb b/app/models/commit.rb
index f412d252e5c..fa0bf36ba49 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -13,6 +13,7 @@ class Commit
include StaticModel
include Presentable
include ::Gitlab::Utils::StrongMemoize
+ include CacheMarkdownField
attr_mentionable :safe_message, pipeline: :single_line
@@ -37,13 +38,9 @@ class Commit
# Used by GFM to match and present link extensions on node texts and hrefs.
LINK_EXTENSION_PATTERN = /(patch)/.freeze
- def banzai_render_context(field)
- pipeline = field == :description ? :commit_description : :single_line
- context = { pipeline: pipeline, project: self.project }
- context[:author] = self.author if self.author
-
- context
- end
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :full_title, pipeline: :single_line
+ cache_markdown_field :description, pipeline: :commit_description
class << self
def decorate(commits, project)
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index a9a2e9c81eb..e8df46e1cc3 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -20,18 +20,51 @@ class CommitCollection
commits.each(&block)
end
- def authors
- emails = without_merge_commits.map(&:author_email).uniq
+ def committers
+ emails = without_merge_commits.map(&:committer_email).uniq
User.by_any_email(emails)
end
def without_merge_commits
strong_memoize(:without_merge_commits) do
- commits.reject(&:merge_commit?)
+ # `#enrich!` the collection to ensure all commits contain
+ # the necessary parent data
+ enrich!.commits.reject(&:merge_commit?)
end
end
+ def unenriched
+ commits.reject(&:gitaly_commit?)
+ end
+
+ def fully_enriched?
+ unenriched.empty?
+ end
+
+ # Batch load any commits that are not backed by full gitaly data, and
+ # replace them in the collection.
+ def enrich!
+ # A project is needed in order to fetch data from gitaly. Projects
+ # can be absent from commits in certain rare situations (like when
+ # viewing a MR of a deleted fork). In these cases, assume that the
+ # enriched data is not needed.
+ return self if project.blank? || fully_enriched?
+
+ # Batch load full Commits from the repository
+ # and map to a Hash of id => Commit
+ replacements = Hash[unenriched.map do |c|
+ [c.id, Commit.lazy(project, c.id)]
+ end.compact]
+
+ # Replace the commits, keeping the same order
+ @commits = @commits.map do |c|
+ replacements.fetch(c.id, c)
+ end
+
+ self
+ end
+
# Sets the pipeline status for every commit.
#
# Setting this status ahead of time removes the need for running a query for
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 094747ee48d..08ca86bc902 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -28,12 +28,12 @@ class CommitRange
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
- REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
- PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
+ REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/.freeze
+ PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/.freeze
# In text references, the beginning and ending refs can only be SHAs
# between 7 and 40 hex characters.
- STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
+ STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/.freeze
def self.reference_prefix
'@'
@@ -134,25 +134,25 @@ class CommitRange
end
def sha_from
- return nil unless @commit_from
+ return unless @commit_from
@commit_from.id
end
def sha_to
- return nil unless @commit_to
+ return unless @commit_to
@commit_to.id
end
def sha_start
- return nil unless sha_from
+ return unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end
def commit_start
- return nil unless sha_start
+ return unless sha_start
if exclude_start?
@commit_start ||= project.commit(sha_start)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 7f6562b63e5..be6f3e9c5b0 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CommitStatus < ActiveRecord::Base
+class CommitStatus < ApplicationRecord
include HasStatus
include Importable
include AfterCommitQueue
@@ -66,7 +66,10 @@ class CommitStatus < ActiveRecord::Base
end
event :enqueue do
- transition [:created, :skipped, :manual, :scheduled] => :pending
+ # A CommitStatus will never have prerequisites, but this event
+ # is shared by Ci::Build, which cannot progress unless prerequisites
+ # are satisfied.
+ transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending, unless: :any_unmet_prerequisites?
end
event :run do
@@ -74,26 +77,26 @@ class CommitStatus < ActiveRecord::Base
end
event :skip do
- transition [:created, :pending] => :skipped
+ transition [:created, :preparing, :pending] => :skipped
end
event :drop do
- transition [:created, :pending, :running, :scheduled] => :failed
+ transition [:created, :preparing, :pending, :running, :scheduled] => :failed
end
event :success do
- transition [:created, :pending, :running] => :success
+ transition [:created, :preparing, :pending, :running] => :success
end
event :cancel do
- transition [:created, :pending, :running, :manual, :scheduled] => :canceled
+ transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
end
- before_transition [:created, :skipped, :manual, :scheduled] => :pending do |commit_status|
+ before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
- before_transition [:created, :pending] => :running do |commit_status|
+ before_transition [:created, :preparing, :pending] => :running do |commit_status|
commit_status.started_at = Time.now
end
@@ -137,7 +140,7 @@ class CommitStatus < ActiveRecord::Base
end
def locking_enabled?
- status_changed?
+ will_save_change_to_status?
end
def before_sha
@@ -180,6 +183,10 @@ class CommitStatus < ActiveRecord::Base
false
end
+ def any_unmet_prerequisites?
+ false
+ end
+
def auto_canceled?
canceled? && auto_canceled_by_id?
end
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index 152105d9429..45e08fa18fe 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -14,7 +14,8 @@ module CommitStatusEnums
runner_unsupported: 6,
stale_schedule: 7,
job_execution_timeout: 8,
- archived_failure: 9
+ archived_failure: 9,
+ unmet_prerequisites: 10
}
end
end
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
deleted file mode 100644
index cbd63ba8876..00000000000
--- a/app/models/concerns/artifact_migratable.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# Adapter class to unify the interface between mounted uploaders and the
-# Ci::Artifact model
-# Meant to be prepended so the interface can stay the same
-module ArtifactMigratable
- def artifacts_file
- job_artifacts_archive&.file || legacy_artifacts_file
- end
-
- def artifacts_metadata
- job_artifacts_metadata&.file || legacy_artifacts_metadata
- end
-
- def artifacts?
- !artifacts_expired? && artifacts_file.exists?
- end
-
- def artifacts_metadata?
- artifacts? && artifacts_metadata.exists?
- end
-
- def artifacts_file_changed?
- job_artifacts_archive&.file_changed? || attribute_changed?(:artifacts_file)
- end
-
- def remove_artifacts_file!
- if job_artifacts_archive
- job_artifacts_archive.destroy
- else
- remove_legacy_artifacts_file!
- end
- end
-
- def remove_artifacts_metadata!
- if job_artifacts_metadata
- job_artifacts_metadata.destroy
- else
- remove_legacy_artifacts_metadata!
- end
- end
-
- def artifacts_size
- read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
- end
-end
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 4e15b60ccd1..dc1735a7e48 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -7,7 +7,7 @@
#
# For example, let's generate internal ids for Issue per Project:
# ```
-# class Issue < ActiveRecord::Base
+# class Issue < ApplicationRecord
# has_internal_id :iid, scope: :project, init: ->(s) { s.project.issues.maximum(:iid) }
# end
# ```
@@ -53,6 +53,20 @@ module AtomicInternalId
value
end
+
+ define_method("reset_#{scope}_#{column}") do
+ if value = read_attribute(column)
+ scope_value = association(scope).reader
+ scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
+ usage = self.class.table_name.to_sym
+
+ if InternalId.reset(self, scope_attrs, usage, value)
+ write_attribute(column, nil)
+ end
+ end
+
+ read_attribute(column)
+ end
end
end
end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 4687ec7d166..80278e07e65 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -91,7 +91,8 @@ module Avatarable
private
def retrieve_upload_from_batch(identifier)
- BatchLoader.for(identifier: identifier, model: self).batch(key: self.class) do |upload_params, loader, args|
+ BatchLoader.for(identifier: identifier, model: self)
+ .batch(key: self.class, cache: true, replace_methods: false) do |upload_params, loader, args|
model_class = args[:key]
paths = upload_params.flat_map do |params|
params[:model].upload_paths(params[:identifier])
diff --git a/app/models/concerns/blob_language_from_git_attributes.rb b/app/models/concerns/blob_language_from_git_attributes.rb
index 70213d22147..56e1276a220 100644
--- a/app/models/concerns/blob_language_from_git_attributes.rb
+++ b/app/models/concerns/blob_language_from_git_attributes.rb
@@ -5,7 +5,7 @@ module BlobLanguageFromGitAttributes
extend ActiveSupport::Concern
def language_from_gitattributes
- return nil unless project
+ return unless project
repository = project.repository
repository.gitattribute(path, 'gitlab-language')
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 1a8570b80c3..42203a5f214 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -7,40 +7,15 @@
# cache_markdown_field :foo
# cache_markdown_field :bar
# cache_markdown_field :baz, pipeline: :single_line
+# cache_markdown_field :baz, whitelisted: true
#
# Corresponding foo_html, bar_html and baz_html fields should exist.
module CacheMarkdownField
extend ActiveSupport::Concern
- # Increment this number every time the renderer changes its output
- CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 14
-
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
- # Knows about the relationship between markdown and html field names, and
- # stores the rendering contexts for the latter
- class FieldData
- def initialize
- @data = {}
- end
-
- delegate :[], :[]=, to: :@data
-
- def markdown_fields
- @data.keys
- end
-
- def html_field(markdown_field)
- "#{markdown_field}_html"
- end
-
- def html_fields
- markdown_fields.map {|field| html_field(field) }
- end
- end
-
def skip_project_check?
false
end
@@ -76,24 +51,22 @@ module CacheMarkdownField
end.to_h
updates['cached_markdown_version'] = latest_cached_markdown_version
- updates.each {|html_field, data| write_attribute(html_field, data) }
+ updates.each { |field, data| write_markdown_field(field, data) }
end
def refresh_markdown_cache!
updates = refresh_markdown_cache
- return unless persisted? && Gitlab::Database.read_write?
-
- update_columns(updates)
+ save_markdown(updates)
end
def cached_html_up_to_date?(markdown_field)
- html_field = cached_markdown_fields.html_field(markdown_field)
+ return false if cached_html_for(markdown_field).nil? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
- return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
+ html_field = cached_markdown_fields.html_field(markdown_field)
- markdown_changed = attribute_changed?(markdown_field) || false
- html_changed = attribute_changed?(html_field) || false
+ markdown_changed = markdown_field_changed?(markdown_field)
+ html_changed = markdown_field_changed?(html_field)
latest_cached_markdown_version == cached_markdown_version &&
(html_changed || markdown_changed == html_changed)
@@ -108,21 +81,21 @@ module CacheMarkdownField
end
def cached_html_for(markdown_field)
- raise ArgumentError.new("Unknown field: #{field}") unless
+ raise ArgumentError.new("Unknown field: #{markdown_field}") unless
cached_markdown_fields.markdown_fields.include?(markdown_field)
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
def latest_cached_markdown_version
- @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version
+ @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version
end
def local_version
# because local_markdown_version is stored in application_settings which
# uses cached_markdown_version too, we check explicitly to avoid
# endless loop
- return local_markdown_version if has_attribute?(:local_markdown_version)
+ return local_markdown_version if respond_to?(:has_attribute?) && has_attribute?(:local_markdown_version)
settings = Gitlab::CurrentSettings.current_application_settings
@@ -141,27 +114,14 @@ module CacheMarkdownField
included do
cattr_reader :cached_markdown_fields do
- FieldData.new
+ Gitlab::MarkdownCache::FieldData.new
end
- # Always exclude _html fields from attributes (including serialization).
- # They contain unredacted HTML, which would be a security issue
- alias_method :attributes_before_markdown_cache, :attributes
- def attributes
- attrs = attributes_before_markdown_cache
-
- attrs.delete('cached_markdown_version')
-
- cached_markdown_fields.html_fields.each do |field|
- attrs.delete(field)
- end
-
- attrs
+ if self < ActiveRecord::Base
+ include Gitlab::MarkdownCache::ActiveRecord::Extension
+ else
+ prepend Gitlab::MarkdownCache::Redis::Extension
end
-
- # Using before_update here conflicts with elasticsearch-model somehow
- before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
- before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
end
class_methods do
@@ -179,10 +139,8 @@ module CacheMarkdownField
# The HTML becomes invalid if any dependent fields change. For now, assume
# author and project invalidate the cache in all circumstances.
define_method(invalidation_method) do
- changed_fields = changed_attributes.keys
- invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY]
- invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html")
-
+ invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY]
+ invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html")
!invalidations.empty? || !cached_html_up_to_date?(markdown_field)
end
end
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
new file mode 100644
index 00000000000..e1d5ce7f7d4
--- /dev/null
+++ b/app/models/concerns/ci/contextable.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+module Ci
+ ##
+ # This module implements methods that provide context in form of
+ # essential CI/CD variables that can be used by a build / bridge job.
+ #
+ module Contextable
+ ##
+ # Variables in the environment name scope.
+ #
+ def scoped_variables(environment: expanded_environment_name)
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.concat(predefined_variables)
+ variables.concat(project.predefined_variables)
+ variables.concat(pipeline.predefined_variables)
+ variables.concat(runner.predefined_variables) if runnable? && runner
+ variables.concat(project.deployment_variables(environment: environment)) if environment
+ variables.concat(yaml_variables)
+ variables.concat(user_variables)
+ variables.concat(secret_group_variables)
+ variables.concat(secret_project_variables(environment: environment))
+ variables.concat(trigger_request.user_variables) if trigger_request
+ variables.concat(pipeline.variables)
+ variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
+ end
+ end
+
+ ##
+ # Regular Ruby hash of scoped variables, without duplicates that are
+ # possible to be present in an array of hashes returned from `variables`.
+ #
+ def scoped_variables_hash
+ scoped_variables.to_hash
+ end
+
+ ##
+ # Variables that do not depend on the environment name.
+ #
+ def simple_variables
+ strong_memoize(:simple_variables) do
+ scoped_variables(environment: nil).to_runner_variables
+ end
+ end
+
+ def user_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables if user.blank?
+
+ variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
+ variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
+ variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
+ variables.append(key: 'GITLAB_USER_NAME', value: user.name)
+ end
+ end
+
+ def predefined_variables # rubocop:disable Metrics/AbcSize
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI', value: 'true')
+ variables.append(key: 'GITLAB_CI', value: 'true')
+ variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
+ variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
+ variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
+ variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
+ variables.append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s)
+ variables.append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s)
+ variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision)
+ variables.append(key: 'CI_JOB_NAME', value: name)
+ variables.append(key: 'CI_JOB_STAGE', value: stage)
+ variables.append(key: 'CI_COMMIT_SHA', value: sha)
+ variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
+ variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
+ variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
+ variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
+ variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
+ variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
+ variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
+ variables.append(key: "CI_NODE_INDEX", value: self.options[:instance].to_s) if self.options&.include?(:instance)
+ variables.append(key: "CI_NODE_TOTAL", value: (self.options&.dig(:parallel) || 1).to_s)
+ variables.concat(legacy_variables)
+ end
+ end
+
+ def legacy_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_BUILD_REF', value: sha)
+ variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
+ variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
+ variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
+ variables.append(key: 'CI_BUILD_NAME', value: name)
+ variables.append(key: 'CI_BUILD_STAGE', value: stage)
+ variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
+ variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request
+ variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action?
+ end
+ end
+
+ def secret_group_variables
+ return [] unless project.group
+
+ project.group.ci_variables_for(git_ref, project)
+ end
+
+ def secret_project_variables(environment: persisted_environment)
+ project.ci_variables_for(ref: git_ref, environment: environment)
+ end
+ end
+end
diff --git a/app/models/concerns/ci/pipeline_delegator.rb b/app/models/concerns/ci/pipeline_delegator.rb
new file mode 100644
index 00000000000..dbc5ed1bc9a
--- /dev/null
+++ b/app/models/concerns/ci/pipeline_delegator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+##
+# This module is mainly used by child associations of `Ci::Pipeline` that needs to look up
+# single source of truth. For example, `Ci::Build` has `git_ref` method, which behaves
+# slightly different from `Ci::Pipeline`'s `git_ref`. This is very confusing as
+# the system could behave differently time to time.
+# We should have a single interface in `Ci::Pipeline` and access the method always.
+module Ci
+ module PipelineDelegator
+ extend ActiveSupport::Concern
+
+ included do
+ delegate :merge_request_event?,
+ :merge_request_ref?,
+ :source_ref,
+ :source_ref_slug,
+ :legacy_detached_merge_request_pipeline?, to: :pipeline
+ end
+ end
+end
diff --git a/app/models/concerns/ci/processable.rb b/app/models/concerns/ci/processable.rb
index 1c78b1413a8..268fa8ec692 100644
--- a/app/models/concerns/ci/processable.rb
+++ b/app/models/concerns/ci/processable.rb
@@ -23,5 +23,9 @@ module Ci
def expanded_environment_name
raise NotImplementedError
end
+
+ def scoped_variables_hash
+ raise NotImplementedError
+ end
end
end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 0107af5f8ec..9ac0d612db3 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -14,6 +14,7 @@ module DeploymentPlatform
def find_deployment_platform(environment)
find_cluster_platform_kubernetes(environment: environment) ||
find_group_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
+ find_instance_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end
@@ -36,6 +37,18 @@ module DeploymentPlatform
.first&.platform_kubernetes
end
+ def find_instance_cluster_platform_kubernetes_with_feature_guard(environment: nil)
+ return unless Clusters::Instance.enabled?
+
+ find_instance_cluster_platform_kubernetes(environment: environment)
+ end
+
+ # EE would override this and utilize environment argument
+ def find_instance_cluster_platform_kubernetes(environment: nil)
+ Clusters::Instance.new.clusters.enabled.default_environment
+ .first&.platform_kubernetes
+ end
+
def find_kubernetes_service_integration
services.deployment.reorder(nil).find_by(active: true)
end
diff --git a/app/models/concerns/deprecated_assignee.rb b/app/models/concerns/deprecated_assignee.rb
new file mode 100644
index 00000000000..7f12ce39c96
--- /dev/null
+++ b/app/models/concerns/deprecated_assignee.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+# This module handles backward compatibility for import/export of Merge Requests after
+# multiple assignees feature was introduced. Also, it handles the scenarios where
+# the #26496 background migration hasn't finished yet.
+# Ideally, most of this code should be removed at #59457.
+module DeprecatedAssignee
+ extend ActiveSupport::Concern
+
+ def assignee_ids=(ids)
+ nullify_deprecated_assignee
+ super
+ end
+
+ def assignees=(users)
+ nullify_deprecated_assignee
+ super
+ end
+
+ def assignee_id=(id)
+ self.assignee_ids = Array(id)
+ end
+
+ def assignee=(user)
+ self.assignees = Array(user)
+ end
+
+ def assignee
+ assignees.first
+ end
+
+ def assignee_id
+ assignee_ids.first
+ end
+
+ def assignee_ids
+ if Gitlab::Database.read_only? && pending_assignees_population?
+ return Array(deprecated_assignee_id)
+ end
+
+ update_assignees_relation
+ super
+ end
+
+ def assignees
+ if Gitlab::Database.read_only? && pending_assignees_population?
+ return User.where(id: deprecated_assignee_id)
+ end
+
+ update_assignees_relation
+ super
+ end
+
+ private
+
+ # This will make the background migration process quicker (#26496) as it'll have less
+ # assignee_id rows to look through.
+ def nullify_deprecated_assignee
+ return unless persisted? && Gitlab::Database.read_only?
+
+ update_column(:assignee_id, nil)
+ end
+
+ # This code should be removed in the clean-up phase of the
+ # background migration (#59457).
+ def pending_assignees_population?
+ persisted? && deprecated_assignee_id && merge_request_assignees.empty?
+ end
+
+ # If there's an assignee_id and no relation, it means the background
+ # migration at #26496 didn't reach this merge request yet.
+ # This code should be removed in the clean-up phase of the
+ # background migration (#59457).
+ def update_assignees_relation
+ if pending_assignees_population?
+ transaction do
+ merge_request_assignees.create!(user_id: deprecated_assignee_id, merge_request_id: id)
+ update_column(:assignee_id, nil)
+ end
+ end
+ end
+
+ def deprecated_assignee_id
+ read_attribute(:assignee_id)
+ end
+end
diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb
index 3f84de54ad5..bb095f113e2 100644
--- a/app/models/concerns/feature_gate.rb
+++ b/app/models/concerns/feature_gate.rb
@@ -2,7 +2,7 @@
module FeatureGate
def flipper_id
- return nil if new_record?
+ return if new_record?
"#{self.class.name}:#{id}"
end
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index 05cd4265133..cfffd845e43 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -22,7 +22,7 @@ module GroupDescendant
return [] if descendants.empty?
unless descendants.all? { |hierarchy| hierarchy.is_a?(GroupDescendant) }
- raise ArgumentError.new('element is not a hierarchy')
+ raise ArgumentError.new(_('element is not a hierarchy'))
end
all_hierarchies = descendants.map do |descendant|
@@ -56,7 +56,7 @@ module GroupDescendant
end
if parent.nil? && hierarchy_top.present?
- raise ArgumentError.new('specified top is not part of the tree')
+ raise ArgumentError.new(_('specified top is not part of the tree'))
end
if parent && parent != hierarchy_top
diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb
index d7089294efc..fa0cf5ddfd2 100644
--- a/app/models/concerns/has_ref.rb
+++ b/app/models/concerns/has_ref.rb
@@ -1,10 +1,13 @@
# frozen_string_literal: true
+##
+# We will disable `ref` and `sha` attributes in `Ci::Build` in the future
+# and remove this module in favor of Ci::PipelineDelegator.
module HasRef
extend ActiveSupport::Concern
def branch?
- !tag?
+ !tag? && !merge_request_event?
end
def git_ref
@@ -14,4 +17,15 @@ module HasRef
Gitlab::Git::TAG_REF_PREFIX + ref.to_s
end
end
+
+ # A slugified version of the build ref, suitable for inclusion in URLs and
+ # domain names. Rules:
+ #
+ # * Lowercased
+ # * Anything not matching [a-z0-9-] is replaced with a -
+ # * Maximum length is 63 bytes
+ # * First/Last Character is not a hyphen
+ def ref_slug
+ Gitlab::Utils.slugify(ref.to_s)
+ end
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 0d2be4c61ab..78bcce2f592 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -5,14 +5,14 @@ module HasStatus
DEFAULT_STATUS = 'created'.freeze
BLOCKED_STATUS = %w[manual scheduled].freeze
- AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped manual scheduled].freeze
+ AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
- ACTIVE_STATUSES = %w[pending running].freeze
+ ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[failed pending running manual scheduled canceled success skipped created].freeze
+ ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8 }.freeze
+ scheduled: 8, preparing: 9 }.freeze
UnknownStatusError = Class.new(StandardError)
@@ -26,6 +26,7 @@ module HasStatus
success = scope_relevant.success.select('count(*)').to_sql
manual = scope_relevant.manual.select('count(*)').to_sql
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
+ preparing = scope_relevant.preparing.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
@@ -37,12 +38,14 @@ module HasStatus
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
+ WHEN (#{builds})=(#{preparing}) THEN 'preparing'
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
WHEN (#{scheduled})>0 THEN 'scheduled'
+ WHEN (#{preparing})>0 THEN 'preparing'
WHEN (#{created})>0 THEN 'running'
ELSE 'failed'
END)"
@@ -63,6 +66,10 @@ module HasStatus
def all_state_names
state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
end
+
+ def completed_statuses
+ COMPLETED_STATUSES.map(&:to_sym)
+ end
end
included do
@@ -70,6 +77,7 @@ module HasStatus
state_machine :status, initial: :created do
state :created, value: 'created'
+ state :preparing, value: 'preparing'
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
@@ -81,6 +89,7 @@ module HasStatus
end
scope :created, -> { where(status: 'created') }
+ scope :preparing, -> { where(status: 'preparing') }
scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) }
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
@@ -90,14 +99,14 @@ module HasStatus
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :scheduled, -> { where(status: 'scheduled') }
- scope :alive, -> { where(status: [:created, :pending, :running]) }
+ scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do
- where(status: [:running, :pending, :created, :scheduled])
+ where(status: [:running, :preparing, :pending, :created, :scheduled])
end
end
diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb
index dfbe413a878..b4e99569071 100644
--- a/app/models/concerns/has_variable.rb
+++ b/app/models/concerns/has_variable.rb
@@ -4,6 +4,11 @@ module HasVariable
extend ActiveSupport::Concern
included do
+ enum variable_type: {
+ env_var: 1,
+ file: 2
+ }
+
validates :key,
presence: true,
length: { maximum: 255 },
@@ -21,9 +26,9 @@ module HasVariable
def key=(new_key)
super(new_key.to_s.strip)
end
+ end
- def to_runner_variable
- { key: key, value: value, public: false }
- end
+ def to_runner_variable
+ { key: key, value: value, public: false, file: file? }
end
end
diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb
index 5c1f7dfcd2a..3bec44dc79b 100644
--- a/app/models/concerns/ignorable_column.rb
+++ b/app/models/concerns/ignorable_column.rb
@@ -5,7 +5,7 @@
#
# Example:
#
-# class User < ActiveRecord::Base
+# class User < ApplicationRecord
# include IgnorableColumn
#
# ignore_column :updated_at
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 429a63f83cc..127430cc68f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -23,12 +23,13 @@ module Issuable
include Sortable
include CreatedAtFilterable
include UpdatedAtFilterable
+ include IssuableStates
include ClosedAtFilterable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance.
- IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count)
+ IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :merge_requests_count)
included do
cache_markdown_field :title, pipeline: :single_line
@@ -36,8 +37,8 @@ module Issuable
redact_field :description
- belongs_to :author, class_name: "User"
- belongs_to :updated_by, class_name: "User"
+ belongs_to :author, class_name: 'User'
+ belongs_to :updated_by, class_name: 'User'
belongs_to :last_edited_by, class_name: 'User'
belongs_to :milestone
@@ -66,15 +67,9 @@ module Issuable
allow_nil: true,
prefix: true
- delegate :name,
- :email,
- :public_email,
- to: :assignee,
- allow_nil: true,
- prefix: true
-
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
+ validate :milestone_is_valid
scope :authored, ->(user) { where(author_id: user) }
scope :recent, -> { reorder(id: :desc) }
@@ -86,6 +81,19 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
+ # rubocop:disable GitlabSecurity/SqlInjection
+ # The `to_ability_name` method is not an user input.
+ scope :assigned, -> do
+ where("EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ end
+ scope :unassigned, -> do
+ where("NOT EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ end
+ scope :assigned_to, ->(u) do
+ where("EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE user_id = ? AND #{to_ability_name}_id = #{to_ability_name}s.id)", u.id)
+ end
+ # rubocop:enable GitlabSecurity/SqlInjection
+
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
scope :order_milestone_due_desc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC') }
scope :order_milestone_due_asc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date ASC') }
@@ -102,13 +110,14 @@ module Issuable
participant :author
participant :notes_with_associations
+ participant :assignees
strip_attributes :title
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
- title_changed? || description_changed?
+ will_save_change_to_title? || will_save_change_to_description?
end
def allows_multiple_assignees?
@@ -118,6 +127,12 @@ module Issuable
def has_multiple_assignees?
assignees.count > 1
end
+
+ private
+
+ def milestone_is_valid
+ errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available?
+ end
end
class_methods do
@@ -132,6 +147,15 @@ module Issuable
fuzzy_search(query, [:title])
end
+ # Available state values persisted in state_id column using state machine
+ #
+ # Override this on subclasses if different states are needed
+ #
+ # Check MergeRequest.available_states for example
+ def available_states
+ @available_states ||= { opened: 1, closed: 2 }.with_indifferent_access
+ end
+
# Searches for records with a matching title or description.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -151,6 +175,10 @@ module Issuable
fuzzy_search(query, matched_columns)
end
+ def simple_sorts
+ super.except('name_asc', 'name_desc')
+ end
+
def sort_by_attribute(method, excluded_labels: [])
sorted =
case method.to_s
@@ -245,6 +273,14 @@ module Issuable
end
end
+ def milestone_available?
+ project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
+ end
+
+ def assignee_or_author?(user)
+ author_id == user.id || assignees.exists?(user.id)
+ end
+
def today?
Date.today == created_at.to_date
end
@@ -289,11 +325,7 @@ module Issuable
end
if old_assignees != assignees
- if self.is_a?(Issue)
- changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
- else
- changes[:assignee] = [old_assignees&.first&.hook_attrs, assignee&.hook_attrs]
- end
+ changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
end
if self.respond_to?(:total_time_spent)
@@ -330,10 +362,18 @@ module Issuable
def card_attributes
{
'Author' => author.try(:name),
- 'Assignee' => assignee.try(:name)
+ 'Assignee' => assignee_list
}
end
+ def assignee_list
+ assignees.map(&:name).to_sentence
+ end
+
+ def assignee_username_list
+ assignees.map(&:username).to_sentence
+ end
+
def notes_with_associations
# If A has_many Bs, and B has_many Cs, and you do
# `A.includes(b: :c).each { |a| a.b.includes(:c) }`, sadly ActiveRecord
diff --git a/app/models/concerns/issuable_states.rb b/app/models/concerns/issuable_states.rb
new file mode 100644
index 00000000000..b722c541580
--- /dev/null
+++ b/app/models/concerns/issuable_states.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module IssuableStates
+ extend ActiveSupport::Concern
+
+ # The state:string column is being migrated to state_id:integer column
+ # This is a temporary hook to populate state_id column with new values
+ # and should be removed after the state column is removed.
+ # Check https://gitlab.com/gitlab-org/gitlab-ce/issues/51789 for more information
+ included do
+ before_save :set_state_id
+ end
+
+ def set_state_id
+ return if state.nil? || state.empty?
+
+ # Needed to prevent breaking some migration specs that
+ # rollback database to a point where state_id does not exist.
+ # We can use this guard clause for now since this file will
+ # be removed in the next release.
+ return unless self.has_attribute?(:state_id)
+
+ self.state_id = self.class.available_states[state]
+ end
+end
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
new file mode 100644
index 00000000000..e0f2c41b836
--- /dev/null
+++ b/app/models/concerns/maskable.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Maskable
+ extend ActiveSupport::Concern
+
+ # * Single line
+ # * No escape characters
+ # * No variables
+ # * No spaces
+ # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
+ # * Absolutely no fun is allowed
+ REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
+
+ included do
+ validates :masked, inclusion: { in: [true, false] }
+ validates :value, format: { with: REGEX }, if: :masked?
+ end
+
+ def to_runner_variable
+ super.merge(masked: masked?)
+ end
+end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 055ffe04646..3deb86da6cf 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,28 +1,20 @@
# frozen_string_literal: true
module Milestoneish
- def closed_items_count(user)
- memoize_per_user(user, :closed_items_count) do
- (count_issues_by_state(user)['closed'] || 0) + merge_requests.closed_and_merged.size
- end
- end
-
- def total_items_count(user)
- memoize_per_user(user, :total_items_count) do
- total_issues_count(user) + merge_requests.size
- end
- end
-
def total_issues_count(user)
count_issues_by_state(user).values.sum
end
+ def closed_issues_count(user)
+ count_issues_by_state(user)['closed'].to_i
+ end
+
def complete?(user)
- total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
+ total_issues_count(user) > 0 && total_issues_count(user) == closed_issues_count(user)
end
def percent_complete(user)
- ((closed_items_count(user) * 100) / total_items_count(user)).abs
+ closed_issues_count(user) * 100 / total_issues_count(user)
rescue ZeroDivisionError
0
end
@@ -46,12 +38,31 @@ module Milestoneish
end
end
+ def issue_participants_visible_by_user(user)
+ User.joins(:issue_assignees)
+ .where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id))
+ .distinct
+ end
+
+ def issue_labels_visible_by_user(user)
+ Label.joins(:label_links)
+ .where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue')
+ .distinct
+ end
+
def sorted_issues(user)
issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
end
- def sorted_merge_requests
- merge_requests.sort_by_attribute('label_priority')
+ def sorted_merge_requests(user)
+ merge_requests_visible_to_user(user).sort_by_attribute('label_priority')
+ end
+
+ def merge_requests_visible_to_user(user)
+ memoize_per_user(user, :merge_requests_visible_to_user) do
+ MergeRequestsFinder.new(user, issues_finder_params)
+ .execute.where(milestone_id: milestoneish_id)
+ end
end
def upcoming?
diff --git a/app/models/concerns/mirror_authentication.rb b/app/models/concerns/mirror_authentication.rb
index e3e1a0441f8..948094221e5 100644
--- a/app/models/concerns/mirror_authentication.rb
+++ b/app/models/concerns/mirror_authentication.rb
@@ -79,7 +79,7 @@ module MirrorAuthentication
end
def ssh_public_key
- return nil if ssh_private_key.blank?
+ return if ssh_private_key.blank?
comment = "git@#{::Gitlab.config.gitlab.host}"
::SSHKey.new(ssh_private_key, comment: comment).ssh_public_key
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 3c74034b527..4b428b0af83 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -3,16 +3,26 @@
module Noteable
extend ActiveSupport::Concern
- # `Noteable` class names that support resolvable notes.
- RESOLVABLE_TYPES = %w(MergeRequest).freeze
-
class_methods do
# `Noteable` class names that support replying to individual notes.
def replyable_types
%w(Issue MergeRequest)
end
+
+ # `Noteable` class names that support resolvable notes.
+ def resolvable_types
+ %w(MergeRequest)
+ end
end
+ # The timestamp of the note (e.g. the :created_at or :updated_at attribute if provided via
+ # API call)
+ def system_note_timestamp
+ @system_note_timestamp || Time.now # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ attr_writer :system_note_timestamp
+
def base_class_name
self.class.base_class.name
end
@@ -28,7 +38,7 @@ module Noteable
end
def supports_resolvable_notes?
- RESOLVABLE_TYPES.include?(base_class_name)
+ self.class.resolvable_types.include?(base_class_name)
end
def supports_discussions?
@@ -123,3 +133,5 @@ module Noteable
)
end
end
+
+Noteable.extend(Noteable::ClassMethods)
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 614c3242874..b140fca9b83 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -7,7 +7,7 @@
#
# Usage:
#
-# class Issue < ActiveRecord::Base
+# class Issue < ApplicationRecord
# include Participable
#
# # ...
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index a29e80fe0c1..258c819f243 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -36,7 +36,7 @@ module PrometheusAdapter
def calculate_reactive_cache(query_class_name, *args)
return unless prometheus_client
- data = Kernel.const_get(query_class_name).new(prometheus_client_wrapper).query(*args)
+ data = Object.const_get(query_class_name, false).new(prometheus_client_wrapper).query(*args)
{
success: true,
data: data,
@@ -51,7 +51,7 @@ module PrometheusAdapter
end
def build_query_args(*args)
- args.map(&:id)
+ args.map { |arg| arg.respond_to?(:id) ? arg.id : arg }
end
end
end
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index de77ca3e963..1e09cd89550 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -7,7 +7,7 @@
#
# Example of use:
#
-# class Foo < ActiveRecord::Base
+# class Foo < ApplicationRecord
# include ReactiveCaching
#
# self.reactive_cache_key = ->(thing) { ["foo", thing.id] }
@@ -29,6 +29,40 @@
# However, it will enqueue a background worker to call `#calculate_reactive_cache`
# and set an initial cache lifetime of ten minutes.
#
+# The background worker needs to find or generate the object on which
+# `with_reactive_cache` was called.
+# The default behaviour can be overridden by defining a custom
+# `reactive_cache_worker_finder`.
+# Otherwise the background worker will use the class name and primary key to get
+# the object using the ActiveRecord find_by method.
+#
+# class Bar
+# include ReactiveCaching
+#
+# self.reactive_cache_key = ->() { ["bar", "thing"] }
+# self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+#
+# def self.from_cache(var1, var2)
+# # This method will be called by the background worker with "bar1" and
+# # "bar2" as arguments.
+# new(var1, var2)
+# end
+#
+# def initialize(var1, var2)
+# # ...
+# end
+#
+# def calculate_reactive_cache
+# # Expensive operation here. The return value of this method is cached
+# end
+#
+# def result
+# with_reactive_cache("bar1", "bar2") do |data|
+# # ...
+# end
+# end
+# end
+#
# Each time the background job completes, it stores the return value of
# `#calculate_reactive_cache`. It is also re-enqueued to run again after
# `reactive_cache_refresh_interval`, so keeping the stored value up to date.
@@ -52,6 +86,7 @@ module ReactiveCaching
class_attribute :reactive_cache_key
class_attribute :reactive_cache_lifetime
class_attribute :reactive_cache_refresh_interval
+ class_attribute :reactive_cache_worker_finder
# defaults
self.reactive_cache_lease_timeout = 2.minutes
@@ -59,6 +94,10 @@ module ReactiveCaching
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
+ self.reactive_cache_worker_finder = ->(id, *_args) do
+ find_by(primary_key => id)
+ end
+
def calculate_reactive_cache(*args)
raise NotImplementedError
end
@@ -69,7 +108,7 @@ module ReactiveCaching
def with_reactive_cache(*args, &blk)
unless within_reactive_cache_lifetime?(*args)
refresh_reactive_cache!(*args)
- return nil
+ return
end
keep_alive_reactive_cache!(*args)
diff --git a/app/models/concerns/redactable.rb b/app/models/concerns/redactable.rb
index 5ad96d6cc46..53ae300ee2d 100644
--- a/app/models/concerns/redactable.rb
+++ b/app/models/concerns/redactable.rb
@@ -10,7 +10,7 @@
module Redactable
extend ActiveSupport::Concern
- UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}
+ UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}.freeze
class_methods do
def redact_field(field)
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 58143a32fdc..4a506146de3 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -73,6 +73,7 @@ module Referable
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
+ (?:\/\-)?
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 16ea330701d..2d2d5fb7168 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -12,7 +12,7 @@ module ResolvableNote
validates :resolved_by, presence: true, if: :resolved?
# Keep this scope in sync with `#potentially_resolvable?`
- scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable::RESOLVABLE_TYPES) }
+ scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable.resolvable_types) }
# Keep this scope in sync with `#resolvable?`
scope :resolvable, -> { potentially_resolvable.user }
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index e51b4e22c96..70ac873a030 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -16,6 +16,8 @@ module ShaAttribute
# the column is the correct type. In production it should behave like any other attribute.
# See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5502 for more discussion
def validate_binary_column_exists!(name)
+ return unless database_exists?
+
unless table_exists?
warn "WARNING: sha_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations"
return
@@ -35,5 +37,13 @@ module ShaAttribute
Gitlab::AppLogger.error "ShaAttribute initialization: #{error.message}"
raise
end
+
+ def database_exists?
+ ApplicationRecord.connection
+
+ true
+ rescue
+ false
+ end
end
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 29e48f0c5f7..df1a9e3fe6e 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -21,19 +21,21 @@ module Sortable
class_methods do
def order_by(method)
- case method.to_s
- when 'created_asc' then order_created_asc
- when 'created_date' then order_created_desc
- when 'created_desc' then order_created_desc
- when 'id_asc' then order_id_asc
- when 'id_desc' then order_id_desc
- when 'name_asc' then order_name_asc
- when 'name_desc' then order_name_desc
- when 'updated_asc' then order_updated_asc
- when 'updated_desc' then order_updated_desc
- else
- all
- end
+ simple_sorts.fetch(method.to_s, -> { all }).call
+ end
+
+ def simple_sorts
+ {
+ 'created_asc' => -> { order_created_asc },
+ 'created_date' => -> { order_created_desc },
+ 'created_desc' => -> { order_created_desc },
+ 'id_asc' => -> { order_id_asc },
+ 'id_desc' => -> { order_id_desc },
+ 'name_asc' => -> { order_name_asc },
+ 'name_desc' => -> { order_name_desc },
+ 'updated_asc' => -> { order_updated_asc },
+ 'updated_desc' => -> { order_updated_desc }
+ }
end
private
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 498996f4f80..a15dc19e07a 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -13,20 +13,20 @@ module Storage
raise Gitlab::UpdatePathError.new("Namespace #{name} (#{id}) cannot be moved because at least one project (e.g. #{proj_with_tags.name} (#{proj_with_tags.id})) has tags in container registry")
end
- parent_was = if parent_changed? && parent_id_was.present?
- Namespace.find(parent_id_was) # raise NotFound early if needed
+ parent_was = if saved_change_to_parent? && parent_id_before_last_save.present?
+ Namespace.find(parent_id_before_last_save) # raise NotFound early if needed
end
move_repositories
- if parent_changed?
+ if saved_change_to_parent?
former_parent_full_path = parent_was&.full_path
parent_full_path = parent&.full_path
Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
else
- Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
- Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
+ Gitlab::UploadsTransfer.new.rename_namespace(full_path_before_last_save, full_path)
+ Gitlab::PagesTransfer.new.rename_namespace(full_path_before_last_save, full_path)
end
# If repositories moved successfully we need to
@@ -38,7 +38,7 @@ module Storage
write_projects_repository_config
rescue => e
# Raise if development/test environment, else just notify Sentry
- Gitlab::Sentry.track_exception(e, extra: { full_path_was: full_path_was, full_path: full_path, action: 'move_dir' })
+ Gitlab::Sentry.track_exception(e, extra: { full_path_before_last_save: full_path_before_last_save, full_path: full_path, action: 'move_dir' })
end
true # false would cancel later callbacks but not rollback
@@ -57,14 +57,14 @@ module Storage
# Move the namespace directory in all storages used by member projects
repository_storages.each do |repository_storage|
# Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage, full_path_was)
+ gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
# Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
- unless gitlab_shell.mv_namespace(repository_storage, full_path_was, full_path)
+ unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
- Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_was} to #{full_path}"
+ Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
@@ -104,11 +104,5 @@ module Storage
end
end
end
-
- def remove_legacy_exports!
- legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
-
- FileUtils.rm_rf(legacy_export_path)
- end
end
end
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index c9f5ba7793d..8f6a6244dd3 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -6,7 +6,7 @@
#
# Usage:
#
-# class Milestone < ActiveRecord::Base
+# class Milestone < ApplicationRecord
# strip_attributes :title
# end
#
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index f147ce8ad6b..b42adad94ba 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -19,7 +19,7 @@ module Taskable
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text.
- }x
+ }x.freeze
def self.get_tasks(content)
content.to_s.scan(ITEM_PATTERN).map do |checkbox, label|
@@ -75,4 +75,11 @@ module Taskable
def task_status_short
task_status(short: true)
end
+
+ def task_completion_status
+ @task_completion_status ||= {
+ count: tasks.summary.item_count,
+ completed_count: tasks.summary.complete_count
+ }
+ end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index f5bb559ceda..8c769be0489 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -26,34 +26,41 @@ module TokenAuthenticatable
end
end
- define_method(token_field) do
+ mod = token_authenticatable_module
+
+ mod.define_method(token_field) do
strategy.get_token(self)
end
- define_method("set_#{token_field}") do |token|
+ mod.define_method("set_#{token_field}") do |token|
strategy.set_token(self, token)
end
- define_method("ensure_#{token_field}") do
+ mod.define_method("ensure_#{token_field}") do
strategy.ensure_token(self)
end
# Returns a token, but only saves when the database is in read & write mode
- define_method("ensure_#{token_field}!") do
+ mod.define_method("ensure_#{token_field}!") do
strategy.ensure_token!(self)
end
# Resets the token, but only saves when the database is in read & write mode
- define_method("reset_#{token_field}!") do
+ mod.define_method("reset_#{token_field}!") do
strategy.reset_token!(self)
end
- define_method("#{token_field}_matches?") do |other_token|
+ mod.define_method("#{token_field}_matches?") do |other_token|
token = read_attribute(token_field)
token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(other_token, token)
end
end
+ def token_authenticatable_module
+ @token_authenticatable_module ||=
+ const_set(:TokenAuthenticatable, Module.new).tap(&method(:include))
+ end
+
def token_authenticatable_fields
@token_authenticatable_fields ||= []
end
diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb
index 01fb194281a..aafd0b538a3 100644
--- a/app/models/concerns/token_authenticatable_strategies/base.rb
+++ b/app/models/concerns/token_authenticatable_strategies/base.rb
@@ -39,25 +39,9 @@ module TokenAuthenticatableStrategies
instance.save! if Gitlab::Database.read_write?
end
- def fallback?
- unless options[:fallback].in?([true, false, nil])
- raise ArgumentError, 'fallback: needs to be a boolean value!'
- end
-
- options[:fallback] == true
- end
-
- def migrating?
- unless options[:migrating].in?([true, false, nil])
- raise ArgumentError, 'migrating: needs to be a boolean value!'
- end
-
- options[:migrating] == true
- end
-
def self.fabricate(model, field, options)
if options[:digest] && options[:encrypted]
- raise ArgumentError, 'Incompatible options set!'
+ raise ArgumentError, _('Incompatible options set!')
end
if options[:digest]
diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb
index 152491aa6e9..4728cb658dc 100644
--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb
+++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb
@@ -2,28 +2,18 @@
module TokenAuthenticatableStrategies
class Encrypted < Base
- def initialize(*)
- super
-
- if migrating? && fallback?
- raise ArgumentError, '`fallback` and `migrating` options are not compatible!'
- end
- end
-
def find_token_authenticatable(token, unscoped = false)
return if token.blank?
- if fully_encrypted?
- return find_by_encrypted_token(token, unscoped)
- end
-
- if fallback?
+ if required?
+ find_by_encrypted_token(token, unscoped)
+ elsif optional?
find_by_encrypted_token(token, unscoped) ||
find_by_plaintext_token(token, unscoped)
elsif migrating?
find_by_plaintext_token(token, unscoped)
else
- raise ArgumentError, 'Unknown encryption phase!'
+ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy }
end
end
@@ -41,8 +31,8 @@ module TokenAuthenticatableStrategies
return super if instance.has_attribute?(encrypted_field)
- if fully_encrypted?
- raise ArgumentError, 'Using encrypted strategy when encrypted field is missing!'
+ if required?
+ raise ArgumentError, _('Using required encryption strategy when encrypted field is missing!')
else
insecure_strategy.ensure_token(instance)
end
@@ -53,8 +43,7 @@ module TokenAuthenticatableStrategies
encrypted_token = instance.read_attribute(encrypted_field)
token = Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token)
-
- token || (insecure_strategy.get_token(instance) if fallback?)
+ token || (insecure_strategy.get_token(instance) if optional?)
end
def set_token(instance, token)
@@ -62,16 +51,35 @@ module TokenAuthenticatableStrategies
instance[encrypted_field] = Gitlab::CryptoHelper.aes256_gcm_encrypt(token)
instance[token_field] = token if migrating?
- instance[token_field] = nil if fallback?
+ instance[token_field] = nil if optional?
token
end
- def fully_encrypted?
- !migrating? && !fallback?
+ def required?
+ encrypted_strategy == :required
+ end
+
+ def migrating?
+ encrypted_strategy == :migrating
+ end
+
+ def optional?
+ encrypted_strategy == :optional
end
protected
+ def encrypted_strategy
+ value = options[:encrypted]
+ value = value.call if value.is_a?(Proc)
+
+ unless value.in?([:required, :optional, :migrating])
+ raise ArgumentError, _('encrypted: needs to be a :required, :optional or :migrating!')
+ end
+
+ value
+ end
+
def find_by_plaintext_token(token, unscoped)
insecure_strategy.find_token_authenticatable(token, unscoped)
end
@@ -89,7 +97,7 @@ module TokenAuthenticatableStrategies
def token_set?(instance)
raw_token = instance.read_attribute(encrypted_field)
- unless fully_encrypted?
+ unless required?
raw_token ||= insecure_strategy.get_token(instance)
end
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
new file mode 100644
index 00000000000..1f881249322
--- /dev/null
+++ b/app/models/concerns/update_project_statistics.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+# This module is providing helpers for updating `ProjectStatistics` with `after_save` and `before_destroy` hooks.
+#
+# It deals with `ProjectStatistics.increment_statistic` making sure not to update statistics on a cascade delete from the
+# project, and keeping track of value deltas on each save. It updates the DB only when a change is needed.
+#
+# Example:
+#
+# module Ci
+# class JobArtifact < ApplicationRecord
+# include UpdateProjectStatistics
+#
+# update_project_statistics project_statistics_name: :build_artifacts_size
+# end
+# end
+#
+# Expectation:
+#
+# - `statistic_attribute` must be an ActiveRecord attribute
+# - The model must implement `project` and `project_id`. i.e. direct Project relationship or delegation
+#
+module UpdateProjectStatistics
+ extend ActiveSupport::Concern
+
+ class_methods do
+ attr_reader :project_statistics_name, :statistic_attribute
+
+ # Configure the model to update `project_statistics_name` on ProjectStatistics,
+ # when `statistic_attribute` changes
+ #
+ # - project_statistics_name: A column of `ProjectStatistics` to update
+ # - statistic_attribute: An attribute of the current model, default to `size`
+ #
+ def update_project_statistics(project_statistics_name:, statistic_attribute: :size)
+ @project_statistics_name = project_statistics_name
+ @statistic_attribute = statistic_attribute
+
+ after_save(:update_project_statistics_after_save, if: :update_project_statistics_attribute_changed?)
+ after_destroy(:update_project_statistics_after_destroy, unless: :project_destroyed?)
+ end
+
+ private :update_project_statistics
+ end
+
+ included do
+ private
+
+ def update_project_statistics_after_save
+ attr = self.class.statistic_attribute
+ delta = read_attribute(attr).to_i - attribute_before_last_save(attr).to_i
+
+ update_project_statistics(delta)
+ end
+
+ def update_project_statistics_attribute_changed?
+ saved_change_to_attribute?(self.class.statistic_attribute)
+ end
+
+ def update_project_statistics_after_destroy
+ update_project_statistics(-read_attribute(self.class.statistic_attribute).to_i)
+ end
+
+ def project_destroyed?
+ project.pending_delete?
+ end
+
+ def update_project_statistics(delta)
+ ProjectStatistics.increment_statistic(project_id, self.class.project_statistics_name, delta)
+ end
+ end
+end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index cf057d774cf..39e12ac2b06 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ContainerRepository < ActiveRecord::Base
+class ContainerRepository < ApplicationRecord
include Gitlab::Utils::StrongMemoize
belongs_to :project
diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb
index c54537572d6..b91123be87e 100644
--- a/app/models/conversational_development_index/metric.rb
+++ b/app/models/conversational_development_index/metric.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module ConversationalDevelopmentIndex
- class Metric < ActiveRecord::Base
+ class Metric < ApplicationRecord
include Presentable
self.table_name = 'conversational_development_index_metrics'
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 71fd02fac86..15906ed8e06 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class DeployKeysProject < ActiveRecord::Base
+class DeployKeysProject < ApplicationRecord
belongs_to :project
belongs_to :deploy_key, inverse_of: :deploy_keys_projects
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index e3524305346..b0e570f52ba 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class DeployToken < ActiveRecord::Base
+class DeployToken < ApplicationRecord
include Expirable
include TokenAuthenticatable
include PolicyActor
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 811e623b7f7..92c7311014a 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Deployment < ActiveRecord::Base
+class Deployment < ApplicationRecord
include AtomicInternalId
include IidRoutes
include AfterCommitQueue
@@ -47,6 +47,12 @@ class Deployment < ActiveRecord::Base
Deployments::SuccessWorker.perform_async(id)
end
end
+
+ after_transition any => [:success, :failed, :canceled] do |deployment|
+ deployment.run_after_commit do
+ Deployments::FinishedWorker.perform_async(id)
+ end
+ end
end
enum status: {
@@ -78,6 +84,19 @@ class Deployment < ActiveRecord::Base
Commit.truncate_sha(sha)
end
+ def cluster
+ platform = project.deployment_platform(environment: environment.name)
+
+ if platform.present? && platform.respond_to?(:cluster)
+ platform.cluster
+ end
+ end
+
+ def execute_hooks
+ deployment_data = Gitlab::DataBuilder::Deployment.build(self)
+ project.execute_services(deployment_data, :deployment_hooks)
+ end
+
def last?
self == environment.last_deployment
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 279603496b0..f75c32633b1 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -15,7 +15,9 @@ class DiffNote < Note
validates :original_position, presence: true
validates :position, presence: true
validates :line_code, presence: true, line_code: true, if: :on_text?
- validates :noteable_type, inclusion: { in: noteable_types }
+ # We need to evaluate the `noteable` types when running the validation since
+ # EE might have added a type when the module was prepended
+ validates :noteable_type, inclusion: { in: -> (_note) { noteable_types } }
validate :positions_complete
validate :verify_supported
validate :diff_refs_match_commit, if: :for_commit?
@@ -41,6 +43,14 @@ class DiffNote < Note
create_note_diff_file(creation_params)
end
+ # Returns the diff file from `position`
+ def latest_diff_file
+ strong_memoize(:latest_diff_file) do
+ position.diff_file(repository)
+ end
+ end
+
+ # Returns the diff file from `original_position`
def diff_file
strong_memoize(:diff_file) do
enqueue_diff_file_creation_job if should_create_diff_file?
@@ -67,10 +77,10 @@ class DiffNote < Note
end
def supports_suggestion?
- return false unless noteable.supports_suggestion? && on_text?
+ return false unless noteable&.supports_suggestion? && on_text?
# We don't want to trigger side-effects of `diff_file` call.
- return false unless file = fetch_diff_file
- return false unless line = file.line_for_position(self.original_position)
+ return false unless file = latest_diff_file
+ return false unless line = file.line_for_position(self.position)
line&.suggestible?
end
@@ -80,7 +90,7 @@ class DiffNote < Note
end
def banzai_render_context(field)
- super.merge(suggestions_filter_enabled: supports_suggestion?)
+ super.merge(suggestions_filter_enabled: true)
end
private
@@ -103,7 +113,7 @@ class DiffNote < Note
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
- repository: project.repository,
+ repository: repository,
diff_refs: original_position.diff_refs)
elsif created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
@@ -114,7 +124,7 @@ class DiffNote < Note
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(original_position.diff_options).diff_files.first
else
- original_position.diff_file(self.project.repository)
+ original_position.diff_file(repository)
end
# Since persisted diff files already have its content "unfolded"
@@ -129,7 +139,7 @@ class DiffNote < Note
end
def set_line_code
- self.line_code = self.position.line_code(self.project.repository)
+ self.line_code = self.position.line_code(repository)
end
def verify_supported
@@ -163,6 +173,10 @@ class DiffNote < Note
shas << self.position.head_sha
end
- project.repository.keep_around(*shas)
+ repository.keep_around(*shas)
+ end
+
+ def repository
+ noteable.respond_to?(:repository) ? noteable.repository : project.repository
end
end
diff --git a/app/models/email.rb b/app/models/email.rb
index 3ce6e792fa8..0ddaa049c3b 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-class Email < ActiveRecord::Base
+class Email < ApplicationRecord
include Sortable
include Gitlab::SQL::Pattern
belongs_to :user
validates :user_id, presence: true
- validates :email, presence: true, uniqueness: true, email: true
+ validates :email, presence: true, uniqueness: true, devise_email: true
validate :unique_email, if: ->(email) { email.email_changed? }
scope :confirmed, -> { where.not(confirmed_at: nil) }
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 1fc088b12ae..aff20dae09b 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-class Environment < ActiveRecord::Base
+class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
# Used to generate random suffixes for the slug
- LETTERS = 'a'..'z'
- NUMBERS = '0'..'9'
+ LETTERS = ('a'..'z').freeze
+ NUMBERS = ('0'..'9').freeze
SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
belongs_to :project, required: true
@@ -35,7 +35,7 @@ class Environment < ActiveRecord::Base
validates :external_url,
length: { maximum: 255 },
allow_nil: true,
- url: true
+ addressable_url: true
delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true
@@ -119,7 +119,7 @@ class Environment < ActiveRecord::Base
def first_deployment_for(commit_sha)
ref = project.repository.ref_name_for_sha(ref_path, commit_sha)
- return nil unless ref
+ return unless ref
deployment_iid = ref.split('/').last
deployments.find_by(iid: deployment_iid)
@@ -130,7 +130,7 @@ class Environment < ActiveRecord::Base
end
def formatted_external_url
- return nil unless external_url
+ return unless external_url
external_url.gsub(%r{\A.*?://}, '')
end
@@ -155,11 +155,11 @@ class Environment < ActiveRecord::Base
end
def has_terminals?
- project.deployment_platform.present? && available? && last_deployment.present?
+ deployment_platform.present? && available? && last_deployment.present?
end
def terminals
- project.deployment_platform.terminals(self) if has_terminals?
+ deployment_platform.terminals(self) if has_terminals?
end
def has_metrics?
@@ -170,8 +170,10 @@ class Environment < ActiveRecord::Base
prometheus_adapter.query(:environment, self) if has_metrics?
end
- def additional_metrics
- prometheus_adapter.query(:additional_metrics_environment, self) if has_metrics?
+ def additional_metrics(*args)
+ return unless has_metrics?
+
+ prometheus_adapter.query(:additional_metrics_environment, self, *args.map(&:to_f))
end
# rubocop: disable CodeReuse/ServiceClass
@@ -243,6 +245,10 @@ class Environment < ActiveRecord::Base
self.environment_type || self.name
end
+ def name_without_type
+ @name_without_type ||= name.delete_prefix("#{environment_type}/")
+ end
+
def deployment_platform
strong_memoize(:deployment_platform) do
project.deployment_platform(environment: self.name)
diff --git a/app/models/epic.rb b/app/models/epic.rb
index ccd10593434..3693db1de33 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -2,7 +2,7 @@
# Placeholder class for model that is implemented in EE
# It reserves '&' as a reference prefix, but the table does not exists in CE
-class Epic < ActiveRecord::Base
+class Epic < ApplicationRecord
def self.link_reference_pattern
nil
end
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index 57283a78ea9..0b4fef5eac1 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -1,20 +1,34 @@
# frozen_string_literal: true
module ErrorTracking
- class ProjectErrorTrackingSetting < ActiveRecord::Base
+ class ProjectErrorTrackingSetting < ApplicationRecord
+ include Gitlab::Utils::StrongMemoize
include ReactiveCaching
+ SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response'
+ SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE = 'non_20x_response_from_sentry'
+
+ API_URL_PATH_REGEXP = %r{
+ \A
+ (?<prefix>/api/0/projects/+)
+ (?:
+ (?<organization>[^/]+)/+
+ (?<project>[^/]+)/*
+ )?
+ \z
+ }x.freeze
+
self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
belongs_to :project
- validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
+ validates :api_url, length: { maximum: 255 }, public_url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
- validates :api_url, presence: true, if: :enabled
+ validates :api_url, presence: { message: 'is a required field' }, if: :enabled
validate :validate_api_url_path, if: :enabled
- validates :token, presence: true, if: :enabled
+ validates :token, presence: { message: 'is a required field' }, if: :enabled
attr_encrypted :token,
mode: :per_attribute_iv,
@@ -23,6 +37,11 @@ module ErrorTracking
after_save :clear_reactive_cache!
+ def api_url=(value)
+ super
+ clear_memoization(:api_url_slugs)
+ end
+
def project_name
super || project_name_from_slug
end
@@ -40,6 +59,8 @@ module ErrorTracking
end
def self.build_api_url_from(api_host:, project_slug:, organization_slug:)
+ return if api_host.blank?
+
uri = Addressable::URI.parse("#{api_host}/api/0/projects/#{organization_slug}/#{project_slug}/")
uri.path = uri.path.squeeze('/')
@@ -72,7 +93,9 @@ module ErrorTracking
{ issues: sentry_client.list_issues(**opts.symbolize_keys) }
end
rescue Sentry::Client::Error => e
- { error: e.message }
+ { error: e.message, error_type: SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE }
+ rescue Sentry::Client::MissingKeysError => e
+ { error: e.message, error_type: SENTRY_API_ERROR_TYPE_MISSING_KEYS }
end
# http://HOST/api/0/projects/ORG/PROJECT
@@ -100,34 +123,39 @@ module ErrorTracking
end
def project_slug_from_api_url
- extract_slug(:project)
+ api_url_slug(:project)
end
def organization_slug_from_api_url
- extract_slug(:organization)
+ api_url_slug(:organization)
end
- def extract_slug(capture)
+ def api_url_slug(capture)
+ slugs = strong_memoize(:api_url_slugs) { extract_api_url_slugs || {} }
+ slugs[capture]
+ end
+
+ def extract_api_url_slugs
return if api_url.blank?
begin
url = Addressable::URI.parse(api_url)
rescue Addressable::URI::InvalidURIError
- return nil
+ return
end
- @slug_match ||= url.path.match(%r{^/api/0/projects/+(?<organization>[^/]+)/+(?<project>[^/|$]+)}) || {}
- @slug_match[capture]
+ url.path.match(API_URL_PATH_REGEXP)
end
def validate_api_url_path
return if api_url.blank?
- begin
- unless Addressable::URI.parse(api_url).path.starts_with?('/api/0/projects')
- errors.add(:api_url, 'path needs to start with /api/0/projects')
- end
- rescue Addressable::URI::InvalidURIError
+ unless api_url_slug(:prefix)
+ return errors.add(:api_url, 'is invalid')
+ end
+
+ unless api_url_slug(:organization)
+ errors.add(:project, 'is a required field')
end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 6a35bca72c5..738080eb584 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Event < ActiveRecord::Base
+class Event < ApplicationRecord
include Sortable
include IgnorableColumn
include FromUnion
@@ -68,7 +68,7 @@ class Event < ActiveRecord::Base
# Callbacks
after_create :reset_project_activity
- after_create :set_last_repository_updated_at, if: :push?
+ after_create :set_last_repository_updated_at, if: :push_action?
after_create :track_user_interacted_projects
# Scopes
@@ -138,11 +138,11 @@ class Event < ActiveRecord::Base
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil)
- if push? || commit_note?
+ if push_action? || commit_note?
Ability.allowed?(user, :download_code, project)
elsif membership_changed?
Ability.allowed?(user, :read_project, project)
- elsif created_project?
+ elsif created_project_action?
Ability.allowed?(user, :read_project, project)
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
@@ -173,56 +173,56 @@ class Event < ActiveRecord::Base
target.try(:title)
end
- def created?
+ def created_action?
action == CREATED
end
- def push?
+ def push_action?
false
end
- def merged?
+ def merged_action?
action == MERGED
end
- def closed?
+ def closed_action?
action == CLOSED
end
- def reopened?
+ def reopened_action?
action == REOPENED
end
- def joined?
+ def joined_action?
action == JOINED
end
- def left?
+ def left_action?
action == LEFT
end
- def expired?
+ def expired_action?
action == EXPIRED
end
- def destroyed?
+ def destroyed_action?
action == DESTROYED
end
- def commented?
+ def commented_action?
action == COMMENTED
end
def membership_changed?
- joined? || left? || expired?
+ joined_action? || left_action? || expired_action?
end
- def created_project?
- created? && !target && target_type.nil?
+ def created_project_action?
+ created_action? && !target && target_type.nil?
end
def created_target?
- created? && target
+ created_action? && target
end
def milestone?
@@ -258,23 +258,23 @@ class Event < ActiveRecord::Base
end
def action_name
- if push?
+ if push_action?
push_action_name
- elsif closed?
+ elsif closed_action?
"closed"
- elsif merged?
+ elsif merged_action?
"accepted"
- elsif joined?
+ elsif joined_action?
'joined'
- elsif left?
+ elsif left_action?
'left'
- elsif expired?
+ elsif expired_action?
'removed due to membership expiration from'
- elsif destroyed?
+ elsif destroyed_action?
'destroyed'
- elsif commented?
+ elsif commented_action?
"commented on"
- elsif created_project?
+ elsif created_project_action?
created_project_action_name
else
"opened"
@@ -337,7 +337,7 @@ class Event < ActiveRecord::Base
end
def body?
- if push?
+ if push_action?
push_with_commits?
elsif note?
true
diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb
index 1b9bf93cbbc..0323a8d222a 100644
--- a/app/models/fork_network.rb
+++ b/app/models/fork_network.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ForkNetwork < ActiveRecord::Base
+class ForkNetwork < ApplicationRecord
belongs_to :root_project, class_name: 'Project'
has_many :fork_network_members
has_many :projects, through: :fork_network_members
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
index 36c66f21b0b..f18c306cf91 100644
--- a/app/models/fork_network_member.rb
+++ b/app/models/fork_network_member.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ForkNetworkMember < ActiveRecord::Base
+class ForkNetworkMember < ApplicationRecord
belongs_to :fork_network
belongs_to :project
belongs_to :forked_from_project, class_name: 'Project'
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 3028bf21301..8a768b3a2c0 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -3,7 +3,7 @@
class GenericCommitStatus < CommitStatus
before_validation :set_default_values
- validates :target_url, url: true,
+ validates :target_url, addressable_url: true,
length: { maximum: 255 },
allow_nil: true
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index c5b2492bbf6..7c020dd3b3d 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
class GlobalLabel
+ include Presentable
+
attr_accessor :title, :labels
alias_attribute :name, :title
- delegate :color, :text_color, :description, to: :@first_label
+ delegate :color, :text_color, :description, :scoped_label?, to: :@first_label
def for_display
@first_label
@@ -23,4 +25,8 @@ class GlobalLabel
@labels = labels
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end
+
+ def present(attributes)
+ super(attributes.merge(presenter_class: ::LabelPresenter))
+ end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index fd17745b035..59f5a7703e2 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -8,7 +8,9 @@ class GlobalMilestone
attr_reader :milestone
alias_attribute :name, :title
- delegate :title, :state, :due_date, :start_date, :participants, :project, :group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title, :milestoneish_id, to: :milestone
+ delegate :title, :state, :due_date, :start_date, :participants, :project,
+ :group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title,
+ :milestoneish_id, :parent, to: :milestone
def to_hash
{
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 077afffd358..116beac5c2a 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GpgKey < ActiveRecord::Base
+class GpgKey < ApplicationRecord
KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
diff --git a/app/models/gpg_key_subkey.rb b/app/models/gpg_key_subkey.rb
index 440b588bc78..110bf451136 100644
--- a/app/models/gpg_key_subkey.rb
+++ b/app/models/gpg_key_subkey.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GpgKeySubkey < ActiveRecord::Base
+class GpgKeySubkey < ApplicationRecord
include ShaAttribute
sha_attribute :keyid
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 7f9ff7bbda6..46cac1d41bb 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -38,6 +38,15 @@ class GpgSignature < ApplicationRecord
.safe_find_or_create_by!(commit_sha: attributes[:commit_sha])
end
+ # Find commits that are lacking a signature in the database at present
+ def self.unsigned_commit_shas(commit_shas)
+ return [] if commit_shas.empty?
+
+ signed = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
+
+ commit_shas - signed
+ end
+
def gpg_key=(model)
case model
when GpgKey
diff --git a/app/models/group.rb b/app/models/group.rb
index 52f503404af..cdb4e6e87f6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -56,12 +56,12 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
- add_authentication_token_field :runners_token, encrypted: true, migrating: true
+ add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
after_create :post_create_hook
after_destroy :post_destroy_hook
after_save :update_two_factor_requirement
- after_update :path_changed_hook, if: :path_changed?
+ after_update :path_changed_hook, if: :saved_change_to_path?
class << self
def sort_by_attribute(method)
@@ -126,10 +126,20 @@ class Group < Namespace
# Overrides notification_settings has_many association
# This allows to apply notification settings from parent groups
# to child groups and projects.
- def notification_settings
+ def notification_settings(hierarchy_order: nil)
source_type = self.class.base_class.name
+ settings = NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
- NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
+ return settings unless hierarchy_order && self_and_ancestors_ids.length > 1
+
+ settings
+ .joins("LEFT JOIN (#{self_and_ancestors(hierarchy_order: hierarchy_order).to_sql}) AS ordered_groups ON notification_settings.source_id = ordered_groups.id")
+ .select('notification_settings.*, ordered_groups.depth AS depth')
+ .order("ordered_groups.depth #{hierarchy_order}")
+ end
+
+ def notification_settings_for(user, hierarchy_order: nil)
+ notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end
def to_reference(_from = nil, full: nil)
@@ -228,22 +238,21 @@ class Group < Namespace
def has_owner?(user)
return false unless user
- members_with_parents.owners.where(user_id: user).any?
+ members_with_parents.owners.exists?(user_id: user)
end
def has_maintainer?(user)
return false unless user
- members_with_parents.maintainers.where(user_id: user).any?
+ members_with_parents.maintainers.exists?(user_id: user)
end
# @deprecated
alias_method :has_master?, :has_maintainer?
# Check if user is a last owner of the group.
- # Parent owners are ignored for nested groups.
def last_owner?(user)
- owners.include?(user) && owners.size == 1
+ has_owner?(user) && members_with_parents.owners.size == 1
end
def ldap_synced?
@@ -405,10 +414,14 @@ class Group < Namespace
Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true)
end
+ def project_creation_level
+ super || ::Gitlab::CurrentSettings.default_project_creation
+ end
+
private
def update_two_factor_requirement
- return unless require_two_factor_authentication_changed? || two_factor_grace_period_changed?
+ return unless saved_change_to_require_two_factor_authentication? || saved_change_to_two_factor_grace_period?
users.find_each(&:update_two_factor_requirement)
end
diff --git a/app/models/group_custom_attribute.rb b/app/models/group_custom_attribute.rb
index 22f14885657..5ac6e5f2550 100644
--- a/app/models/group_custom_attribute.rb
+++ b/app/models/group_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GroupCustomAttribute < ActiveRecord::Base
+class GroupCustomAttribute < ApplicationRecord
belongs_to :group
validates :group, :key, :value, presence: true
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 1a8662db9fb..daf7ff4b771 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class WebHook < ActiveRecord::Base
+class WebHook < ApplicationRecord
include Sortable
attr_encrypted :token,
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index 2d9f7594e8c..cfb1f3ec63b 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class WebHookLog < ActiveRecord::Base
+class WebHookLog < ApplicationRecord
belongs_to :web_hook
serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/identity.rb b/app/models/identity.rb
index acdde4f296b..1cbd50205ed 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Identity < ActiveRecord::Base
+class Identity < ApplicationRecord
include Sortable
include CaseSensitivity
@@ -13,6 +13,7 @@ class Identity < ActiveRecord::Base
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
+ scope :for_user, ->(user) { where(user: user) }
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
diff --git a/app/models/identity/uniqueness_scopes.rb b/app/models/identity/uniqueness_scopes.rb
index 674b735903f..ce68371ae87 100644
--- a/app/models/identity/uniqueness_scopes.rb
+++ b/app/models/identity/uniqueness_scopes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Identity < ActiveRecord::Base
+class Identity < ApplicationRecord
# This module and method are defined in a separate file to allow EE to
# redefine the `scopes` method before it is used in the `Identity` model.
module UniquenessScopes
diff --git a/app/models/import_export_upload.rb b/app/models/import_export_upload.rb
index f0cc5aafcd4..60f5491849a 100644
--- a/app/models/import_export_upload.rb
+++ b/app/models/import_export_upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ImportExportUpload < ActiveRecord::Base
+class ImportExportUpload < ApplicationRecord
include WithUploads
include ObjectStorage::BackgroundMove
diff --git a/app/models/individual_note_discussion.rb b/app/models/individual_note_discussion.rb
index b4a661ae5b4..d926e39f96e 100644
--- a/app/models/individual_note_discussion.rb
+++ b/app/models/individual_note_discussion.rb
@@ -14,7 +14,7 @@ class IndividualNoteDiscussion < Discussion
end
def can_convert_to_discussion?
- noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes)
+ noteable.supports_replying_to_individual_notes?
end
def convert_to_discussion!(save: false)
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 11289887e00..a9b1962f24c 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -39,7 +39,7 @@ class InstanceConfiguration
def gitlab_ci
Settings.gitlab_ci
.to_h
- .merge(artifacts_max_size: { value: Settings.artifacts.max_size&.megabytes,
+ .merge(artifacts_max_size: { value: Gitlab::CurrentSettings.max_artifacts_size.megabytes,
default: 100.megabytes })
end
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
index e75c6eb2331..237401899db 100644
--- a/app/models/internal_id.rb
+++ b/app/models/internal_id.rb
@@ -15,7 +15,7 @@
# In order to leverage InternalId for other usages, the idea is to
# * Add `usage` value to enum
# * (Optionally) add columns to `internal_ids` if needed for scope.
-class InternalId < ActiveRecord::Base
+class InternalId < ApplicationRecord
belongs_to :project
belongs_to :namespace
@@ -55,7 +55,8 @@ class InternalId < ActiveRecord::Base
def track_greatest(subject, scope, usage, new_value, init)
return new_value unless available?
- InternalIdGenerator.new(subject, scope, usage, init).track_greatest(new_value)
+ InternalIdGenerator.new(subject, scope, usage)
+ .track_greatest(init, new_value)
end
def generate_next(subject, scope, usage, init)
@@ -63,7 +64,15 @@ class InternalId < ActiveRecord::Base
# This can be the case in other (unrelated) migration specs
return (init.call(subject) || 0) + 1 unless available?
- InternalIdGenerator.new(subject, scope, usage, init).generate
+ InternalIdGenerator.new(subject, scope, usage)
+ .generate(init)
+ end
+
+ def reset(subject, scope, usage, value)
+ return false unless available?
+
+ InternalIdGenerator.new(subject, scope, usage)
+ .reset(value)
end
# Flushing records is generally safe in a sense that those
@@ -78,12 +87,16 @@ class InternalId < ActiveRecord::Base
end
def available?
- @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization
+ return true unless Rails.env.test?
+
+ Gitlab::SafeRequestStore.fetch(:internal_ids_available_flag) do
+ ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION
+ end
end
# Flushes cached information about schema
def reset_column_information
- @available_flag = nil
+ Gitlab::SafeRequestStore[:internal_ids_available_flag] = nil
super
end
end
@@ -103,14 +116,11 @@ class InternalId < ActiveRecord::Base
# subject: The instance we're generating an internal id for. Gets passed to init if called.
# scope: Attributes that define the scope for id generation.
# usage: Symbol to define the usage of the internal id, see InternalId.usages
- # init: Block that gets called to initialize InternalId record if not present
- # Make sure to not throw exceptions in the absence of records (if this is expected).
- attr_reader :subject, :scope, :init, :scope_attrs, :usage
+ attr_reader :subject, :scope, :scope_attrs, :usage
- def initialize(subject, scope, usage, init)
+ def initialize(subject, scope, usage)
@subject = subject
@scope = scope
- @init = init
@usage = usage
raise ArgumentError, 'Scope is not well-defined, need at least one column for scope (given: 0)' if scope.empty?
@@ -121,23 +131,40 @@ class InternalId < ActiveRecord::Base
end
# Generates next internal id and returns it
- def generate
+ # init: Block that gets called to initialize InternalId record if not present
+ # Make sure to not throw exceptions in the absence of records (if this is expected).
+ def generate(init)
subject.transaction do
# Create a record in internal_ids if one does not yet exist
# and increment its last value
#
# Note this will acquire a ROW SHARE lock on the InternalId record
- (lookup || create_record).increment_and_save!
+ (lookup || create_record(init)).increment_and_save!
end
end
+ # Reset tries to rewind to `value-1`. This will only succeed,
+ # if `value` stored in database is equal to `last_value`.
+ # value: The expected last_value to decrement
+ def reset(value)
+ return false unless value
+
+ updated =
+ InternalId
+ .where(**scope, usage: usage_value)
+ .where(last_value: value)
+ .update_all('last_value = last_value - 1')
+
+ updated > 0
+ end
+
# Create a record in internal_ids if one does not yet exist
# and set its new_value if it is higher than the current last_value
#
# Note this will acquire a ROW SHARE lock on the InternalId record
- def track_greatest(new_value)
+ def track_greatest(init, new_value)
subject.transaction do
- (lookup || create_record).track_greatest_and_save!(new_value)
+ (lookup || create_record(init)).track_greatest_and_save!(new_value)
end
end
@@ -158,7 +185,7 @@ class InternalId < ActiveRecord::Base
# was faster in doing this, we'll realize once we hit the unique key constraint
# violation. We can safely roll-back the nested transaction and perform
# a lookup instead to retrieve the record.
- def create_record
+ def create_record(init)
subject.transaction(requires_new: true) do
InternalId.create!(
**scope,
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 0b46e949052..6da6fbe55cb 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class Issue < ActiveRecord::Base
+class Issue < ApplicationRecord
include AtomicInternalId
include IidRoutes
include Issuable
@@ -49,10 +49,6 @@ class Issue < ActiveRecord::Base
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
- scope :assigned, -> { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
- scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
- scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
-
scope :with_due_date, -> { where.not(due_date: nil) }
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
@@ -62,8 +58,10 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') }
+ scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
+ scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) }
scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }
@@ -74,8 +72,6 @@ class Issue < ActiveRecord::Base
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
- participant :assignees
-
state_machine :state, initial: :opened do
event :close do
transition [:opened] => :closed
@@ -89,7 +85,7 @@ class Issue < ActiveRecord::Base
state :closed
before_transition any => :closed do |issue|
- issue.closed_at = Time.zone.now
+ issue.closed_at = issue.system_note_timestamp
end
before_transition closed: :opened do |issue|
@@ -135,9 +131,10 @@ class Issue < ActiveRecord::Base
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'closest_future_date' then order_closest_future_date
- when 'due_date' then order_due_date_asc
- when 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
+ when 'due_date' then order_due_date_asc
+ when 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ when 'relative_position' then order_relative_position_asc
else
super
end
@@ -154,22 +151,6 @@ class Issue < ActiveRecord::Base
Gitlab::HookData::IssueBuilder.new(self).build
end
- # Returns a Hash of attributes to be used for Twitter card metadata
- def card_attributes
- {
- 'Author' => author.try(:name),
- 'Assignee' => assignee_list
- }
- end
-
- def assignee_or_author?(user)
- author_id == user.id || assignees.exists?(user.id)
- end
-
- def assignee_list
- assignees.map(&:name).to_sentence
- end
-
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -229,7 +210,13 @@ class Issue < ActiveRecord::Base
def visible_to_user?(user = nil)
return false unless project && project.feature_available?(:issues, user)
- user ? readable_by?(user) : publicly_visible?
+ return publicly_visible? unless user
+
+ return false unless readable_by?(user)
+
+ user.full_private_access? ||
+ ::Gitlab::ExternalAuthorization.access_allowed?(
+ user, project.external_authorization_classification_label)
end
def check_for_spam?
@@ -263,6 +250,10 @@ class Issue < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
+ def merge_requests_count
+ merge_requests_closing_issues.count
+ end
+
private
def ensure_metrics
@@ -293,7 +284,7 @@ class Issue < ActiveRecord::Base
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
- project.public? && !confidential?
+ project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled?
end
def expire_etag_cache
diff --git a/app/models/issue/metrics.rb b/app/models/issue/metrics.rb
index 0f5ee957ec9..8010cbc3d78 100644
--- a/app/models/issue/metrics.rb
+++ b/app/models/issue/metrics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Issue::Metrics < ActiveRecord::Base
+class Issue::Metrics < ApplicationRecord
belongs_to :issue
def record!
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
index 400c0256945..fbd9be1fb43 100644
--- a/app/models/issue_assignee.rb
+++ b/app/models/issue_assignee.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class IssueAssignee < ActiveRecord::Base
+class IssueAssignee < ApplicationRecord
belongs_to :issue
belongs_to :assignee, class_name: "User", foreign_key: :user_id
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 8f93418b88b..8aa25924c28 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -2,7 +2,7 @@
require 'digest/md5'
-class Key < ActiveRecord::Base
+class Key < ApplicationRecord
include AfterCommitQueue
include Sortable
@@ -59,6 +59,11 @@ class Key < ActiveRecord::Base
"key-#{id}"
end
+ # EE overrides this
+ def can_delete?
+ true
+ end
+
# rubocop: disable CodeReuse/ServiceClass
def update_last_used_at
Keys::LastUsedService.new(self).execute
diff --git a/app/models/label.rb b/app/models/label.rb
index 1c3db3eb35d..e9085e8bd25 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Label < ActiveRecord::Base
+class Label < ApplicationRecord
include CacheMarkdownField
include Referable
include Subscribable
@@ -8,6 +8,7 @@ class Label < ActiveRecord::Base
include OptionallySearch
include Sortable
include FromUnion
+ include Presentable
cache_markdown_field :description, pipeline: :single_line
@@ -126,6 +127,17 @@ class Label < ActiveRecord::Base
fuzzy_search(query, [:title, :description])
end
+ # Override Gitlab::SQL::Pattern.min_chars_for_partial_matching as
+ # label queries are never global, and so will not use a trigram
+ # index. That means we can have just one character in the LIKE.
+ def self.min_chars_for_partial_matching
+ 1
+ end
+
+ def self.by_ids(ids)
+ where(id: ids)
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
@@ -222,6 +234,10 @@ class Label < ActiveRecord::Base
attributes
end
+ def present(attributes)
+ super(attributes.merge(presenter_class: ::LabelPresenter))
+ end
+
private
def issues_count(user, params = {})
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 1d93a55e8e9..ffc0afd8e85 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LabelLink < ActiveRecord::Base
+class LabelLink < ApplicationRecord
include Importable
belongs_to :target, polymorphic: true, inverse_of: :label_links # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/label_note.rb b/app/models/label_note.rb
index 680952cf421..d6814f4a948 100644
--- a/app/models/label_note.rb
+++ b/app/models/label_note.rb
@@ -81,7 +81,7 @@ class LabelNote < Note
deleted = label_refs.count - existing_refs.count
deleted_str = deleted == 0 ? nil : "#{deleted} deleted"
- return nil unless refs_str || deleted_str
+ return unless refs_str || deleted_str
label_list_str = [refs_str, deleted_str].compact.join(' + ')
suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb
index 8ed8bb7577f..8f8f36efbfe 100644
--- a/app/models/label_priority.rb
+++ b/app/models/label_priority.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LabelPriority < ActiveRecord::Base
+class LabelPriority < ApplicationRecord
belongs_to :project
belongs_to :label
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 00dec6bb92b..e2c75bc7ee9 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -73,7 +73,7 @@ class LegacyDiffNote < Note
private
def find_diff
- return nil unless noteable
+ return unless noteable
return @diff if defined?(@diff)
@diff = noteable.raw_diffs(Commit.max_diff_options).find do |d|
diff --git a/app/models/lfs_file_lock.rb b/app/models/lfs_file_lock.rb
index 431d37e12e9..624b1d02e1a 100644
--- a/app/models/lfs_file_lock.rb
+++ b/app/models/lfs_file_lock.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsFileLock < ActiveRecord::Base
+class LfsFileLock < ApplicationRecord
belongs_to :project
belongs_to :user
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 69c563545bb..5245dbc8d15 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsObject < ActiveRecord::Base
+class LfsObject < ApplicationRecord
include AfterCommitQueue
include ObjectStorage::BackgroundMove
@@ -13,7 +13,7 @@ class LfsObject < ActiveRecord::Base
mount_uploader :file, LfsObjectUploader
- after_save :update_file_store, if: :file_changed?
+ after_save :update_file_store, if: :saved_change_to_file?
def update_file_store
# The file.object_store is set during `uploader.store!`
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 353602800d7..f9afb18c1d7 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsObjectsProject < ActiveRecord::Base
+class LfsObjectsProject < ApplicationRecord
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/list.rb b/app/models/list.rb
index 682af761ba0..17b1a8510cf 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class List < ActiveRecord::Base
+class List < ApplicationRecord
belongs_to :board
belongs_to :label
diff --git a/app/models/member.rb b/app/models/member.rb
index 8e071a8ff21..c7583434148 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Member < ActiveRecord::Base
+class Member < ApplicationRecord
include AfterCommitQueue
include Sortable
include Importable
@@ -28,7 +28,7 @@ class Member < ActiveRecord::Base
presence: {
if: :invite?
},
- email: {
+ devise_email: {
allow_nil: true
},
uniqueness: {
@@ -80,6 +80,8 @@ class Member < ActiveRecord::Base
scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated
scope :with_user, -> (user) { where(user: user) }
+ scope :with_source_id, ->(source_id) { where(source_id: source_id) }
+
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
scope :order_recent_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'DESC')) }
@@ -446,10 +448,10 @@ class Member < ActiveRecord::Base
end
def higher_access_level_than_group
- if highest_group_member && highest_group_member.access_level >= access_level
+ if highest_group_member && highest_group_member.access_level > access_level
error_parameters = { access: highest_group_member.human_access, group_name: highest_group_member.group.name }
- errors.add(:access_level, s_("should be higher than %{access} inherited membership from group %{group_name}") % error_parameters)
+ errors.add(:access_level, s_("should be greater than or equal to %{access} inherited membership from group %{group_name}") % error_parameters)
end
end
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 2c9e1ba1d80..4cba69069bb 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -12,7 +12,9 @@ class GroupMember < Member
validates :source_type, format: { with: /\ANamespace\z/ }
default_scope { where(source_type: SOURCE_TYPE) }
- scope :in_groups, ->(groups) { where(source_id: groups.select(:id)) }
+ scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) }
+
+ scope :count_users_by_group_id, -> { joins(:user).group(:source_id).count }
after_create :update_two_factor_requirement, unless: :invite?
after_destroy :update_two_factor_requirement, unless: :invite?
@@ -53,7 +55,7 @@ class GroupMember < Member
end
def post_update_hook
- if access_level_changed?
+ if saved_change_to_access_level?
run_after_commit { notification_service.update_group_member(self) }
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 5372c6084f4..c64e2669b6a 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -111,7 +111,7 @@ class ProjectMember < Member
end
def post_update_hook
- if access_level_changed?
+ if saved_change_to_access_level?
run_after_commit { notification_service.update_project_member(self) }
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 75fca96ce0a..4fcaac75655 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequest < ActiveRecord::Base
+class MergeRequest < ApplicationRecord
include AtomicInternalId
include IidRoutes
include Issuable
@@ -16,6 +16,7 @@ class MergeRequest < ActiveRecord::Base
include LabelEventable
include ReactiveCaching
include FromUnion
+ include DeprecatedAssignee
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
self.reactive_cache_refresh_interval = 10.minutes
@@ -65,13 +66,15 @@ class MergeRequest < ActiveRecord::Base
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
- has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
+ has_many :pipelines_for_merge_request, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
+ has_many :suggestions, through: :notes
- belongs_to :assignee, class_name: "User"
+ has_many :merge_request_assignees
+ has_many :assignees, class_name: "User", through: :merge_request_assignees
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
- after_create :ensure_merge_request_diff, unless: :importing?
+ after_create :ensure_merge_request_diff
after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
after_save :ensure_metrics
@@ -162,7 +165,7 @@ class MergeRequest < ActiveRecord::Base
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
- validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
+ validates :merge_user, presence: true, if: :auto_merge_enabled?, unless: :importing?
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork?
validate :validate_target_project, on: :create
@@ -181,18 +184,44 @@ class MergeRequest < ActiveRecord::Base
end
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
- scope :assigned, -> { where("assignee_id IS NOT NULL") }
- scope :unassigned, -> { where("assignee_id IS NULL") }
- scope :assigned_to, ->(u) { where(assignee_id: u.id)}
-
- participant :assignee
+ scope :with_api_entity_associations, -> {
+ preload(:assignees, :author, :notes, :labels, :milestone, :timelogs,
+ latest_merge_request_diff: [:merge_request_diff_commits],
+ metrics: [:latest_closed_by, :merged_by],
+ target_project: [:route, { namespace: :route }],
+ source_project: [:route, { namespace: :route }])
+ }
after_save :keep_around_commit
+ alias_attribute :project, :target_project
+ alias_attribute :project_id, :target_project_id
+ alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
+
def self.reference_prefix
'!'
end
+ def self.available_states
+ @available_states ||= super.merge(merged: 3, locked: 4)
+ end
+
+ # Returns the top 100 target branches
+ #
+ # The returned value is a Array containing branch names
+ # sort by updated_at of merge request:
+ #
+ # ['master', 'develop', 'production']
+ #
+ # limit - The maximum number of target branch to return.
+ def self.recent_target_branches(limit: 100)
+ group(:target_branch)
+ .select(:target_branch)
+ .reorder('MAX(merge_requests.updated_at) DESC')
+ .limit(limit)
+ .pluck(:target_branch)
+ end
+
def rebase_in_progress?
strong_memoize(:rebase_in_progress) do
# The source project can be deleted
@@ -206,7 +235,7 @@ class MergeRequest < ActiveRecord::Base
# branch head commit, for example checking if a merge request can be merged.
# For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004
def actual_head_pipeline
- head_pipeline&.sha == diff_head_sha ? head_pipeline : nil
+ head_pipeline&.matches_sha_or_source_sha?(diff_head_sha) ? head_pipeline : nil
end
def merge_pipeline
@@ -286,12 +315,8 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
- def commit_authors
- @commit_authors ||= commits.authors
- end
-
- def authors
- User.from_union([commit_authors, User.where(id: self.author_id)])
+ def committers
+ @committers ||= commits.committers
end
# Verifies if title has changed not taking into account WIP prefix
@@ -304,31 +329,6 @@ class MergeRequest < ActiveRecord::Base
Gitlab::HookData::MergeRequestBuilder.new(self).build
end
- # Returns a Hash of attributes to be used for Twitter card metadata
- def card_attributes
- {
- 'Author' => author.try(:name),
- 'Assignee' => assignee.try(:name)
- }
- end
-
- # These method are needed for compatibility with issues to not mess view and other code
- def assignees
- Array(assignee)
- end
-
- def assignee_ids
- Array(assignee_id)
- end
-
- def assignee_ids=(ids)
- write_attribute(:assignee_id, ids.last)
- end
-
- def assignee_or_author?(user)
- author_id == user.id || assignee_id == user.id
- end
-
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -392,7 +392,7 @@ class MergeRequest < ActiveRecord::Base
def merge_participants
participants = [author]
- if merge_when_pipeline_succeeds? && !participants.include?(merge_user)
+ if auto_merge_enabled? && !participants.include?(merge_user)
participants << merge_user
end
@@ -582,11 +582,15 @@ class MergeRequest < ActiveRecord::Base
end
def validate_branches
+ return unless target_project && source_project
+
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can't use same project/branch for source and target"
return
end
+ [:source_branch, :target_branch].each { |attr| validate_branch_name(attr) }
+
if opened?
similar_mrs = target_project
.merge_requests
@@ -607,6 +611,16 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def validate_branch_name(attr)
+ return unless changes_include?(attr)
+
+ branch = read_attribute(attr)
+
+ return unless branch
+
+ errors.add(attr) unless Gitlab::GitRefValidator.validate_merge_request_branch(branch)
+ end
+
def validate_target_project
return true if target_project.merge_requests_enabled?
@@ -699,7 +713,7 @@ class MergeRequest < ActiveRecord::Base
end
def reload_diff_if_branch_changed
- if (source_branch_changed? || target_branch_changed?) &&
+ if (saved_change_to_source_branch? || saved_change_to_target_branch?) &&
(source_branch_head && target_branch_head)
reload_diff
end
@@ -711,19 +725,16 @@ class MergeRequest < ActiveRecord::Base
MergeRequests::ReloadDiffsService.new(self, current_user).execute
end
- # rubocop: enable CodeReuse/ServiceClass
- def check_if_can_be_merged
- return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write?
-
- can_be_merged =
- !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
+ def check_mergeability
+ MergeRequests::MergeabilityCheckService.new(self).execute
+ end
+ # rubocop: enable CodeReuse/ServiceClass
- if can_be_merged
- mark_as_mergeable
- else
- mark_as_unmergeable
- end
+ # Returns boolean indicating the merge_status should be rechecked in order to
+ # switch to either can_be_merged or cannot_be_merged.
+ def recheck_merge_status?
+ self.class.state_machines[:merge_status].check_state?(merge_status)
end
def merge_event
@@ -749,7 +760,7 @@ class MergeRequest < ActiveRecord::Base
def mergeable?(skip_ci_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check)
- check_if_can_be_merged
+ check_mergeability
can_be_merged? && !should_be_rebased?
end
@@ -772,7 +783,7 @@ class MergeRequest < ActiveRecord::Base
project.ff_merge_must_be_possible? && !ff_merge_possible?
end
- def can_cancel_merge_when_pipeline_succeeds?(current_user)
+ def can_cancel_auto_merge?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
@@ -791,6 +802,16 @@ class MergeRequest < ActiveRecord::Base
Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end
+ def auto_merge_strategy
+ return unless auto_merge_enabled?
+
+ merge_params['auto_merge_strategy'] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
+ end
+
+ def auto_merge_strategy=(strategy)
+ merge_params['auto_merge_strategy'] = strategy
+ end
+
def remove_source_branch?
should_remove_source_branch? || force_remove_source_branch?
end
@@ -807,15 +828,6 @@ class MergeRequest < ActiveRecord::Base
end
def related_notes
- # Fetch comments only from last 100 commits
- commits_for_notes_limit = 100
- commit_ids = commit_shas.take(commits_for_notes_limit)
-
- commit_notes = Note
- .except(:order)
- .where(project_id: [source_project_id, target_project_id])
- .for_commit_id(commit_ids)
-
# We're using a UNION ALL here since this results in better performance
# compared to using OR statements. We're using UNION ALL since the queries
# used won't produce any duplicates (e.g. a note for a commit can't also be
@@ -827,6 +839,16 @@ class MergeRequest < ActiveRecord::Base
alias_method :discussion_notes, :related_notes
+ def commit_notes
+ # Fetch comments only from last 100 commits
+ commit_ids = commit_shas.take(100)
+
+ Note
+ .user
+ .where(project_id: [source_project_id, target_project_id])
+ .for_commit_id(commit_ids)
+ end
+
def mergeable_discussions_state?
return true unless project.only_allow_merge_if_all_discussions_are_resolved?
@@ -837,10 +859,6 @@ class MergeRequest < ActiveRecord::Base
target_project != source_project
end
- def project
- target_project
- end
-
# If the merge request closes any issues, save this information in the
# `MergeRequestsClosingIssues` model. This is a performance optimization.
# Calculating this information for a number of merge requests requires
@@ -966,20 +984,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- def reset_merge_when_pipeline_succeeds
- return unless merge_when_pipeline_succeeds?
-
- self.merge_when_pipeline_succeeds = false
- self.merge_user = nil
- if merge_params
- merge_params.delete('should_remove_source_branch')
- merge_params.delete('commit_message')
- merge_params.delete('squash_commit_message')
- end
-
- self.save
- end
-
# Return array of possible target branches
# depends on target project of MR
def target_branches
@@ -1049,6 +1053,16 @@ class MergeRequest < ActiveRecord::Base
@environments[current_user]
end
+ ##
+ # This method is for looking for active environments which created via pipelines for merge requests.
+ # Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`),
+ # we cannot look up environments with source branch name.
+ def environments
+ return Environment.none unless actual_head_pipeline&.triggered_by_merge_request?
+
+ actual_head_pipeline.environments
+ end
+
def state_human_name
if merged?
"Merged"
@@ -1073,10 +1087,24 @@ class MergeRequest < ActiveRecord::Base
target_project.repository.fetch_source_branch!(source_project.repository, source_branch, ref_path)
end
+ # Returns the current merge-ref HEAD commit.
+ #
+ def merge_ref_head
+ project.repository.commit(merge_ref_path)
+ end
+
def ref_path
"refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head"
end
+ def merge_ref_path
+ "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge"
+ end
+
+ def self.merge_request_ref?(ref)
+ ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/")
+ end
+
def in_locked_state
begin
lock_mr
@@ -1113,12 +1141,18 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
- def all_pipelines(shas: all_commit_shas)
+ def all_pipelines
return Ci::Pipeline.none unless source_project
- @all_pipelines ||=
- source_project.ci_pipelines
- .for_merge_request(self, source_branch, all_commit_shas)
+ shas = all_commit_shas
+
+ strong_memoize(:all_pipelines) do
+ Ci::Pipeline.from_union(
+ [source_project.ci_pipelines.merge_request_pipelines(self, shas),
+ source_project.ci_pipelines.detached_merge_request_pipelines(self, shas),
+ source_project.ci_pipelines.triggered_for_branch(source_branch).for_sha(shas)],
+ remove_duplicates: false).sort_by_merge_request_pipelines
+ end
end
def update_head_pipeline
@@ -1128,47 +1162,24 @@ class MergeRequest < ActiveRecord::Base
end
end
- def merge_request_pipeline_exists?
- merge_request_pipelines.exists?(sha: diff_head_sha)
- end
-
def has_test_reports?
- actual_head_pipeline&.has_test_reports?
+ actual_head_pipeline&.has_reports?(Ci::JobArtifact.test_reports)
end
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_MERGE_REQUEST_ID', value: id.to_s)
variables.append(key: 'CI_MERGE_REQUEST_IID', value: iid.to_s)
-
- variables.append(key: 'CI_MERGE_REQUEST_REF_PATH',
- value: ref_path.to_s)
-
- variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID',
- value: project.id.to_s)
-
- variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH',
- value: project.full_path)
-
- variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL',
- value: project.web_url)
-
- variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME',
- value: target_branch.to_s)
-
- if source_project
- variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID',
- value: source_project.id.to_s)
-
- variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH',
- value: source_project.full_path)
-
- variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL',
- value: source_project.web_url)
-
- variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME',
- value: source_branch.to_s)
- end
+ variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', value: ref_path.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', value: project.id.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', value: project.full_path)
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', value: project.web_url)
+ variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title)
+ variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee_username_list) if assignees.any?
+ variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone
+ variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present?
+ variables.concat(source_project_variables)
end
end
@@ -1300,7 +1311,7 @@ class MergeRequest < ActiveRecord::Base
end
def has_commits?
- merge_request_diff && commits_count > 0
+ merge_request_diff && commits_count.to_i > 0
end
def has_no_commits?
@@ -1369,10 +1380,20 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.squash_in_progress?(id)
end
+ def find_actual_head_pipeline
+ all_pipelines.for_sha_or_source_sha(diff_head_sha).first
+ end
+
private
- def find_actual_head_pipeline
- source_project&.ci_pipelines
- &.latest_for_merge_request(self, source_branch, diff_head_sha)
+ def source_project_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless source_project
+
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', value: source_project.id.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', value: source_project.full_path)
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', value: source_project.web_url)
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s)
+ end
end
end
diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb
index 65e94a97b0a..05f8e18a2c1 100644
--- a/app/models/merge_request/metrics.rb
+++ b/app/models/merge_request/metrics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequest::Metrics < ActiveRecord::Base
+class MergeRequest::Metrics < ApplicationRecord
belongs_to :merge_request
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id
belongs_to :latest_closed_by, class_name: 'User'
diff --git a/app/models/merge_request_assignee.rb b/app/models/merge_request_assignee.rb
new file mode 100644
index 00000000000..f0e6be51b7f
--- /dev/null
+++ b/app/models/merge_request_assignee.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class MergeRequestAssignee < ApplicationRecord
+ belongs_to :merge_request
+ belongs_to :assignee, class_name: "User", foreign_key: :user_id
+end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index e286a4e57f2..f45bd0e03de 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiff < ActiveRecord::Base
+class MergeRequestDiff < ApplicationRecord
include Sortable
include Importable
include ManualInverseAssociation
@@ -12,6 +12,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100
+ # Applies to closed or merged MRs when determining whether to migrate their
+ # diffs to external storage
+ EXTERNAL_DIFF_CUTOFF = 7.days.freeze
+
belongs_to :merge_request
manual_inverse_association :merge_request, :merge_request_diff
@@ -22,6 +26,8 @@ class MergeRequestDiff < ActiveRecord::Base
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
+ validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true
+
state_machine :state, initial: :empty do
event :clean do
transition any => :without_files
@@ -45,7 +51,86 @@ class MergeRequestDiff < ActiveRecord::Base
joins(:merge_request_diff_commits).where(merge_request_diff_commits: { sha: sha }).reorder(nil)
end
+ scope :by_project_id, -> (project_id) do
+ joins(:merge_request).where(merge_requests: { target_project_id: project_id })
+ end
+
scope :recent, -> { order(id: :desc).limit(100) }
+ scope :files_in_database, -> { where(stored_externally: [false, nil]) }
+
+ scope :not_latest_diffs, -> do
+ merge_requests = MergeRequest.arel_table
+ mr_diffs = arel_table
+
+ join_condition = merge_requests[:id].eq(mr_diffs[:merge_request_id])
+ .and(mr_diffs[:id].not_eq(merge_requests[:latest_merge_request_diff_id]))
+
+ arel_join = mr_diffs.join(merge_requests).on(join_condition)
+ joins(arel_join.join_sources)
+ end
+
+ scope :old_merged_diffs, -> (before) do
+ merge_requests = MergeRequest.arel_table
+ mr_metrics = MergeRequest::Metrics.arel_table
+ mr_diffs = arel_table
+
+ mr_join = mr_diffs
+ .join(merge_requests)
+ .on(mr_diffs[:merge_request_id].eq(merge_requests[:id]))
+
+ metrics_join_condition = mr_diffs[:merge_request_id]
+ .eq(mr_metrics[:merge_request_id])
+ .and(mr_metrics[:merged_at].not_eq(nil))
+
+ metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition)
+
+ condition = MergeRequest.arel_table[:state].eq(:merged)
+ .and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before))
+ .and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
+
+ joins(metrics_join.join_sources, mr_join.join_sources).where(condition)
+ end
+
+ scope :old_closed_diffs, -> (before) do
+ condition = MergeRequest.arel_table[:state].eq(:closed)
+ .and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
+
+ joins(merge_request: :metrics).where(condition)
+ end
+
+ def self.ids_for_external_storage_migration(limit:)
+ # No point doing any work unless the feature is enabled
+ return [] unless Gitlab.config.external_diffs.enabled
+
+ case Gitlab.config.external_diffs.when
+ when 'always'
+ files_in_database.limit(limit).pluck(:id)
+ when 'outdated'
+ # Outdated is too complex to be a single SQL query, so split into three
+ before = EXTERNAL_DIFF_CUTOFF.ago
+
+ ids = files_in_database
+ .old_merged_diffs(before)
+ .limit(limit)
+ .pluck(:id)
+
+ return ids if ids.size >= limit
+
+ ids += files_in_database
+ .old_closed_diffs(before)
+ .limit(limit - ids.size)
+ .pluck(:id)
+
+ return ids if ids.size >= limit
+
+ ids + files_in_database
+ .not_latest_diffs
+ .limit(limit - ids.size)
+ .pluck(:id)
+ else
+ []
+ end
+ end
mount_uploader :external_diff, ExternalDiffUploader
@@ -53,7 +138,7 @@ class MergeRequestDiff < ActiveRecord::Base
# It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
- after_save :update_external_diff_store, if: :external_diff_changed?
+ after_save :update_external_diff_store, if: -> { !importing? && saved_change_to_external_diff? }
def self.find_by_diff_refs(diff_refs)
find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha)
@@ -73,7 +158,14 @@ class MergeRequestDiff < ActiveRecord::Base
ensure_commit_shas
save_commits
save_diffs
+
+ # Another set of `after_save` hooks will be called here when we update the record
save
+ # We need to reset so that dirty tracking is reset when running the original set
+ # of `after_save` hooks that come after this `after_create` hook. Otherwise, the
+ # hooks that run when an attribute was changed are run twice.
+ reset
+
keep_around_commits
end
@@ -267,7 +359,7 @@ class MergeRequestDiff < ActiveRecord::Base
has_attribute?(:external_diff_store)
end
- def external_diff_changed?
+ def saved_change_to_external_diff?
super if has_attribute?(:external_diff)
end
@@ -284,32 +376,39 @@ class MergeRequestDiff < ActiveRecord::Base
return yield(@external_diff_file) if @external_diff_file
external_diff.open do |file|
- begin
- @external_diff_file = file
+ @external_diff_file = file
- yield(@external_diff_file)
- ensure
- @external_diff_file = nil
- end
+ yield(@external_diff_file)
+ ensure
+ @external_diff_file = nil
end
end
- private
+ # Transactionally migrate the current merge_request_diff_files entries to
+ # external storage. If external storage isn't an option for this diff, the
+ # method is a no-op.
+ def migrate_files_to_external_storage!
+ return if stored_externally? || !use_external_diff? || merge_request_diff_files.count == 0
- def create_merge_request_diff_files(diffs)
- rows =
- if has_attribute?(:external_diff) && Gitlab.config.external_diffs.enabled
- build_external_merge_request_diff_files(diffs)
- else
- build_merge_request_diff_files(diffs)
- end
+ rows = build_merge_request_diff_files(merge_request_diff_files)
- # Faster inserts
- Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
+ transaction do
+ MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
+ create_merge_request_diff_files(rows)
+ save!
+ end
+
+ merge_request_diff_files.reset
end
- def build_external_merge_request_diff_files(diffs)
- rows = build_merge_request_diff_files(diffs)
+ private
+
+ def encode_in_base64?(diff_text)
+ (diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?) ||
+ diff_text.include?("\0")
+ end
+
+ def build_external_merge_request_diff_files(rows)
tempfile = build_external_diff_tempfile(rows)
self.external_diff = tempfile
@@ -320,16 +419,21 @@ class MergeRequestDiff < ActiveRecord::Base
tempfile&.unlink
end
+ def create_merge_request_diff_files(rows)
+ rows = build_external_merge_request_diff_files(rows) if use_external_diff?
+
+ # Faster inserts
+ Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
+ end
+
def build_external_diff_tempfile(rows)
Tempfile.open(external_diff.filename) do |file|
- rows.inject(0) do |offset, row|
+ rows.each do |row|
data = row.delete(:diff)
- row[:external_diff_offset] = offset
- row[:external_diff_size] = data.size
+ row[:external_diff_offset] = file.pos
+ row[:external_diff_size] = data.bytesize
file.write(data)
-
- offset + data.size
end
file
@@ -348,7 +452,7 @@ class MergeRequestDiff < ActiveRecord::Base
diff_hash.tap do |hash|
diff_text = hash[:diff]
- if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
+ if encode_in_base64?(diff_text)
hash[:binary] = true
hash[:diff] = [diff_text].pack('m0')
end
@@ -356,6 +460,47 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
+ def use_external_diff?
+ return false unless has_attribute?(:external_diff)
+ return false unless Gitlab.config.external_diffs.enabled
+
+ case Gitlab.config.external_diffs.when
+ when 'always'
+ true
+ when 'outdated'
+ outdated_by_merge? || outdated_by_closure? || old_version?
+ else
+ false # Disable external diffs if misconfigured
+ end
+ end
+
+ def outdated_by_merge?
+ return false unless merge_request&.metrics&.merged_at
+
+ merge_request.merged? && merge_request.metrics.merged_at < EXTERNAL_DIFF_CUTOFF.ago
+ end
+
+ def outdated_by_closure?
+ return false unless merge_request&.metrics&.latest_closed_at
+
+ merge_request.closed? && merge_request.metrics.latest_closed_at < EXTERNAL_DIFF_CUTOFF.ago
+ end
+
+ # We can't rely on `merge_request.latest_merge_request_diff_id` because that
+ # may have been changed in `save_git_content` without being reflected in
+ # the association's instance. This query is always subject to races, but
+ # the worst case is that we *don't* make a diff external when we could. The
+ # background worker will make it external at a later date.
+ def old_version?
+ latest_id = MergeRequest
+ .where(id: merge_request_id)
+ .limit(1)
+ .pluck(:latest_merge_request_diff_id)
+ .first
+
+ self.id != latest_id
+ end
+
def load_diffs(options)
# Ensure all diff files operate on the same external diff file instance if
# present. This reduces file open/close overhead.
@@ -389,7 +534,8 @@ class MergeRequestDiff < ActiveRecord::Base
if diff_collection.any?
new_attributes[:state] = :collected
- create_merge_request_diff_files(diff_collection)
+ rows = build_merge_request_diff_files(diff_collection)
+ create_merge_request_diff_files(rows)
end
# Set our state to 'overflow' to make the #empty? and #collected?
@@ -406,10 +552,10 @@ class MergeRequestDiff < ActiveRecord::Base
def save_commits
MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
- # merge_request_diff_commits.reload is preferred way to reload associated
+ # merge_request_diff_commits.reset is preferred way to reload associated
# objects but it returns cached result for some reason in this case
# we can circumvent that by specifying that we need an uncached reload
- commits = self.class.uncached { merge_request_diff_commits.reload }
+ commits = self.class.uncached { merge_request_diff_commits.reset }
self.commits_count = commits.size
end
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index 4ad3690512d..b897bbc8cf5 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiffCommit < ActiveRecord::Base
+class MergeRequestDiffCommit < ApplicationRecord
include ShaAttribute
belongs_to :merge_request_diff
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
index e8d936e265c..01ee82ae398 100644
--- a/app/models/merge_request_diff_file.rb
+++ b/app/models/merge_request_diff_file.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiffFile < ActiveRecord::Base
+class MergeRequestDiffFile < ApplicationRecord
include Gitlab::EncodingHelper
include DiffFile
@@ -23,6 +23,6 @@ class MergeRequestDiffFile < ActiveRecord::Base
super
end
- binary? ? content.unpack('m0').first : content
+ binary? ? content.unpack1('m0') : content
end
end
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index 242b65bedc0..61af50841ee 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestsClosingIssues < ActiveRecord::Base
+class MergeRequestsClosingIssues < ApplicationRecord
belongs_to :merge_request
belongs_to :issue
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 26cfdc5ef30..37c129e843a 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Milestone < ActiveRecord::Base
+class Milestone < ApplicationRecord
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name, :id)
@@ -37,6 +37,7 @@ class Milestone < ActiveRecord::Base
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
scope :for_projects, -> { where(group: nil).includes(:project) }
+ scope :started, -> { active.where('milestones.start_date <= CURRENT_DATE') }
scope :for_projects_and_groups, -> (projects, groups) do
projects = projects.compact if projects.is_a? Array
@@ -57,6 +58,7 @@ class Milestone < ActiveRecord::Base
validate :uniqueness_of_title, if: :title_changed?
validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
+ validate :dates_within_4_digits
strip_attributes :title
@@ -149,7 +151,7 @@ class Milestone < ActiveRecord::Base
def self.upcoming_ids(projects, groups)
rel = unscoped
.for_projects_and_groups(projects, groups)
- .active.where('milestones.due_date > NOW()')
+ .active.where('milestones.due_date > CURRENT_DATE')
if Gitlab::Database.postgresql?
rel.order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
@@ -161,7 +163,7 @@ class Milestone < ActiveRecord::Base
ON milestones.project_id <=> earlier_milestones.project_id
AND milestones.group_id <=> earlier_milestones.group_id
AND milestones.due_date > earlier_milestones.due_date
- AND earlier_milestones.due_date > NOW()
+ AND earlier_milestones.due_date > CURRENT_DATE
AND earlier_milestones.state = 'active'
HEREDOC
@@ -290,22 +292,22 @@ class Milestone < ActiveRecord::Base
end
title_exists = relation.find_by_title(title)
- errors.add(:title, "already being used for another group or project milestone.") if title_exists
+ errors.add(:title, _("already being used for another group or project milestone.")) if title_exists
end
# Milestone should be either a project milestone or a group milestone
def milestone_type_check
if group_id && project_id
field = project_id_changed? ? :project_id : :group_id
- errors.add(field, "milestone should belong either to a project or a group.")
+ errors.add(field, _("milestone should belong either to a project or a group."))
end
end
def milestone_format_reference(format = :iid)
- raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
+ raise ArgumentError, _('Unknown format') unless [:iid, :name].include?(format)
if group_milestone? && format == :iid
- raise ArgumentError, 'Cannot refer to a group milestone by an internal id!'
+ raise ArgumentError, _('Cannot refer to a group milestone by an internal id!')
end
if format == :name && !name.include?('"')
@@ -321,7 +323,17 @@ class Milestone < ActiveRecord::Base
def start_date_should_be_less_than_due_date
if due_date <= start_date
- errors.add(:due_date, "must be greater than start date")
+ errors.add(:due_date, _("must be greater than start date"))
+ end
+ end
+
+ def dates_within_4_digits
+ if start_date && start_date > Date.new(9999, 12, 31)
+ errors.add(:start_date, _("date must not be after 9999-12-31"))
+ end
+
+ if due_date && due_date > Date.new(9999, 12, 31)
+ errors.add(:due_date, _("date must not be after 9999-12-31"))
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index f7592532c5b..3c270c7396a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -11,6 +11,7 @@ class Namespace < ApplicationRecord
include IgnorableColumn
include FeatureGate
include FromUnion
+ include Gitlab::Utils::StrongMemoize
ignore_column :deleted_at
@@ -49,17 +50,20 @@ class Namespace < ApplicationRecord
validate :nesting_level_allowed
+ validates_associated :runners
+
delegate :name, to: :owner, allow_nil: true, prefix: true
+ delegate :avatar_url, to: :owner, allow_nil: true
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
before_create :sync_share_with_group_lock_with_parent
before_update :sync_share_with_group_lock_with_parent, if: :parent_changed?
- after_update :force_share_with_group_lock_on_descendants, if: -> { share_with_group_lock_changed? && share_with_group_lock? }
+ after_update :force_share_with_group_lock_on_descendants, if: -> { saved_change_to_share_with_group_lock? && share_with_group_lock? }
# Legacy Storage specific hooks
- after_update :move_dir, if: :path_or_parent_changed?
+ after_update :move_dir, if: :saved_change_to_path_or_parent?
before_destroy(prepend: true) { prepare_for_destroy }
after_destroy :rm_dir
@@ -72,8 +76,10 @@ class Namespace < ApplicationRecord
'namespaces.*',
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
+ 'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size',
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
- 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size'
+ 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
+ 'COALESCE(SUM(ps.packages_size), 0) AS packages_size'
)
end
@@ -140,7 +146,7 @@ class Namespace < ApplicationRecord
def send_update_instructions
projects.each do |project|
- project.send_move_instructions("#{full_path_was}/#{project.path}")
+ project.send_move_instructions("#{full_path_before_last_save}/#{project.path}")
end
end
@@ -148,8 +154,12 @@ class Namespace < ApplicationRecord
type == 'Group' ? 'group' : 'user'
end
+ def user?
+ kind == 'user'
+ end
+
def find_fork_of(project)
- return nil unless project.fork_network
+ return unless project.fork_network
if Gitlab::SafeRequestStore.active?
forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
@@ -196,12 +206,12 @@ class Namespace < ApplicationRecord
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
- def self_and_ancestors
+ def self_and_ancestors(hierarchy_order: nil)
return self.class.where(id: id) unless parent_id
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
- .base_and_ancestors
+ .base_and_ancestors(hierarchy_order: hierarchy_order)
end
# Returns all the descendants of the current namespace.
@@ -221,10 +231,6 @@ class Namespace < ApplicationRecord
[owner_id]
end
- def parent_changed?
- parent_id_changed?
- end
-
# Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace
def all_projects
@@ -254,12 +260,12 @@ class Namespace < ApplicationRecord
false
end
- def full_path_was
- if parent_id_was.nil?
- path_was
+ def full_path_before_last_save
+ if parent_id_before_last_save.nil?
+ path_before_last_save
else
- previous_parent = Group.find_by(id: parent_id_was)
- previous_parent.full_path + '/' + path_was
+ previous_parent = Group.find_by(id: parent_id_before_last_save)
+ previous_parent.full_path + '/' + path_before_last_save
end
end
@@ -267,10 +273,34 @@ class Namespace < ApplicationRecord
owner.refresh_authorized_projects
end
+ def auto_devops_enabled?
+ first_auto_devops_config[:status]
+ end
+
+ def first_auto_devops_config
+ return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?
+
+ strong_memoize(:first_auto_devops_config) do
+ if has_parent?
+ parent.first_auto_devops_config
+ else
+ { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? }
+ end
+ end
+ end
+
private
- def path_or_parent_changed?
- path_changed? || parent_changed?
+ def parent_changed?
+ parent_id_changed?
+ end
+
+ def saved_change_to_parent?
+ saved_change_to_parent_id?
+ end
+
+ def saved_change_to_path_or_parent?
+ saved_change_to_path? || saved_change_to_parent_id?
end
def refresh_access_of_projects_invited_groups
diff --git a/app/models/note.rb b/app/models/note.rb
index 1578ae9c4cc..081d6f91230 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -3,7 +3,7 @@
# A note on the root of an issue, merge request, commit, or snippet.
#
# A note of this type is never resolvable.
-class Note < ActiveRecord::Base
+class Note < ApplicationRecord
extend ActiveModel::Naming
include Participable
include Mentionable
@@ -313,6 +313,14 @@ class Note < ActiveRecord::Base
!system?
end
+ # Since we're using `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note.
+ # This makes sure it is only marked as edited when the note body is updated.
+ def edited?
+ return false if updated_by.blank?
+
+ super
+ end
+
def cross_reference_not_visible_for?(user)
cross_reference? && !all_referenced_mentionables_allowed?(user)
end
diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb
index e369122003e..fcc9e2b3fd8 100644
--- a/app/models/note_diff_file.rb
+++ b/app/models/note_diff_file.rb
@@ -1,12 +1,16 @@
# frozen_string_literal: true
-class NoteDiffFile < ActiveRecord::Base
+class NoteDiffFile < ApplicationRecord
include DiffFile
scope :for_commit_or_unresolved, -> do
joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'")
end
+ scope :referencing_sha, -> (oids, project_id:) do
+ joins(:diff_note).where(notes: { project_id: project_id, commit_id: oids })
+ end
+
delegate :original_position, :project, to: :diff_note
belongs_to :diff_note, inverse_of: :note_diff_file
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 481c1d963c6..9b2bbb7eba5 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -47,14 +47,14 @@ class NotificationRecipient
def suitable_notification_level?
case notification_level
- when :disabled, nil
- false
- when :custom
- custom_enabled? || %i[participating mention].include?(@type)
- when :watch, :participating
- !action_excluded?
when :mention
@type == :mention
+ when :participating
+ @custom_action == :failed_pipeline || %i[participating mention].include?(@type)
+ when :custom
+ custom_enabled? || %i[participating mention].include?(@type)
+ when :watch
+ !excluded_watcher_action?
else
false
end
@@ -100,43 +100,37 @@ class NotificationRecipient
end
end
- def action_excluded?
- excluded_watcher_action? || excluded_participating_action?
- end
-
def excluded_watcher_action?
- return false unless @custom_action && notification_level == :watch
+ return false unless @custom_action
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
end
- def excluded_participating_action?
- return false unless @custom_action && notification_level == :participating
-
- NotificationSetting::EXCLUDED_PARTICIPATING_EVENTS.include?(@custom_action)
- end
-
private
def read_ability
- return nil if @skip_read_ability
+ return 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
+ if @target.is_a?(Ci::Pipeline)
:read_build # We have build trace in pipeline emails
- when ActiveRecord::Base
- :"read_#{@target.class.model_name.name.underscore}"
- else
- nil
+ elsif default_ability_for_target
+ :"read_#{default_ability_for_target}"
+ end
+ end
+
+ def default_ability_for_target
+ @default_ability_for_target ||=
+ if @target.respond_to?(:to_ability_name)
+ @target.to_ability_name
+ elsif @target.class.respond_to?(:model_name)
+ @target.class.model_name.name.underscore
end
end
def default_project
- return nil if @target.nil?
+ return if @target.nil?
return @target if @target.is_a?(Project)
return @target.project if @target.respond_to?(:project)
end
@@ -156,23 +150,11 @@ class NotificationRecipient
# Returns the notification_setting of the lowest group in hierarchy with non global level
def closest_non_global_group_notification_settting
return unless @group
- return if indexed_group_notification_settings.empty?
- notification_setting = nil
-
- @group.self_and_ancestors_ids.each do |id|
- notification_setting = indexed_group_notification_settings[id]
- break if notification_setting
- end
-
- notification_setting
- end
-
- def indexed_group_notification_settings
- strong_memoize(:indexed_group_notification_settings) do
- @group.notification_settings.where(user_id: user.id)
- .where.not(level: NotificationSetting.levels[:global])
- .index_by(&:source_id)
- end
+ @group
+ .notification_settings(hierarchy_order: :asc)
+ .where(user: user)
+ .where.not(level: NotificationSetting.levels[:global])
+ .first
end
end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index e82eaf4e069..8306b11a7b6 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class NotificationSetting < ActiveRecord::Base
+class NotificationSetting < ApplicationRecord
include IgnorableColumn
ignore_column :events
@@ -54,14 +54,11 @@ class NotificationSetting < ActiveRecord::Base
self.class.email_events(source)
end
- EXCLUDED_PARTICIPATING_EVENTS = [
- :success_pipeline
- ].freeze
-
EXCLUDED_WATCHER_EVENTS = [
:push_to_merge_request,
- :issue_due
- ].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
+ :issue_due,
+ :success_pipeline
+ ].freeze
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 7a33ade826b..524df30289e 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
-class PagesDomain < ActiveRecord::Base
+class PagesDomain < ApplicationRecord
VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze
VERIFICATION_THRESHOLD = 3.days.freeze
belongs_to :project
+ has_many :acme_orders, class_name: "PagesDomainAcmeOrder"
validates :domain, hostname: { allow_numeric_hostname: true }
validates :domain, uniqueness: { case_sensitive: false }
@@ -26,7 +27,7 @@ class PagesDomain < ActiveRecord::Base
after_initialize :set_verification_code
after_create :update_daemon
- after_update :update_daemon, if: :pages_config_changed?
+ after_update :update_daemon, if: :saved_change_to_pages_config?
after_destroy :update_daemon
scope :enabled, -> { where('enabled_until >= ?', Time.now ) }
@@ -38,6 +39,8 @@ class PagesDomain < ActiveRecord::Base
where(verified_at.eq(nil).or(enabled_until.eq(nil).or(enabled_until.lt(threshold))))
end
+ scope :for_removal, -> { where("remove_at < ?", Time.now) }
+
def verified?
!!verified_at
end
@@ -132,6 +135,14 @@ class PagesDomain < ActiveRecord::Base
"#{VERIFICATION_KEY}=#{verification_code}"
end
+ def certificate=(certificate)
+ super(certificate)
+
+ # set nil, if certificate is nil
+ self.certificate_valid_not_before = x509&.not_before
+ self.certificate_valid_not_after = x509&.not_after
+ end
+
private
def set_verification_code
@@ -146,21 +157,21 @@ class PagesDomain < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
- def pages_config_changed?
- project_id_changed? ||
- domain_changed? ||
- certificate_changed? ||
- key_changed? ||
+ def saved_change_to_pages_config?
+ saved_change_to_project_id? ||
+ saved_change_to_domain? ||
+ saved_change_to_certificate? ||
+ saved_change_to_key? ||
became_enabled? ||
became_disabled?
end
def became_enabled?
- enabled_until.present? && !enabled_until_was.present?
+ enabled_until.present? && !enabled_until_before_last_save.present?
end
def became_disabled?
- !enabled_until.present? && enabled_until_was.present?
+ !enabled_until.present? && enabled_until_before_last_save.present?
end
def validate_matching_key
@@ -184,7 +195,7 @@ class PagesDomain < ActiveRecord::Base
end
def x509
- return unless certificate
+ return unless certificate.present?
@x509 ||= OpenSSL::X509::Certificate.new(certificate)
rescue OpenSSL::X509::CertificateError
diff --git a/app/models/pages_domain_acme_order.rb b/app/models/pages_domain_acme_order.rb
new file mode 100644
index 00000000000..63d7fbc8206
--- /dev/null
+++ b/app/models/pages_domain_acme_order.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class PagesDomainAcmeOrder < ApplicationRecord
+ belongs_to :pages_domain
+
+ scope :expired, -> { where("expires_at < ?", Time.now) }
+
+ validates :pages_domain, presence: true
+ validates :expires_at, presence: true
+ validates :url, presence: true
+ validates :challenge_token, presence: true
+ validates :challenge_file_content, presence: true
+ validates :private_key, presence: true
+
+ attr_encrypted :private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ def self.find_by_domain_and_token(domain_name, challenge_token)
+ joins(:pages_domain).find_by(pages_domains: { domain: domain_name }, challenge_token: challenge_token)
+ end
+end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index ed78a46eaf3..f69f0e2dccb 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-class PersonalAccessToken < ActiveRecord::Base
+class PersonalAccessToken < ApplicationRecord
include Expirable
- include IgnorableColumn
include TokenAuthenticatable
add_authentication_token_field :token, digest: true
- ignore_column :token
REDIS_EXPIRY_TIME = 3.minutes
@@ -58,7 +56,7 @@ class PersonalAccessToken < ActiveRecord::Base
protected
def validate_scopes
- unless revoked || scopes.all? { |scope| Gitlab::Auth.available_scopes.include?(scope.to_sym) }
+ unless revoked || scopes.all? { |scope| Gitlab::Auth.all_available_scopes.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes"
end
end
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index 4635fc72dc7..50eed7344bd 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -3,11 +3,11 @@
# The PoolRepository model is the database equivalent of an ObjectPool for Gitaly
# That is; PoolRepository is the record in the database, ObjectPool is the
# repository on disk
-class PoolRepository < ActiveRecord::Base
+class PoolRepository < ApplicationRecord
include Shardable
include AfterCommitQueue
- has_one :source_project, class_name: 'Project'
+ belongs_to :source_project, class_name: 'Project'
validates :source_project, presence: true
has_many :member_projects, class_name: 'Project'
@@ -81,10 +81,7 @@ class PoolRepository < ActiveRecord::Base
object_pool.link(repository.raw)
end
- # This RPC can cause data loss, as not all objects are present the local repository
- def unlink_repository(repository)
- object_pool.unlink_repository(repository.raw)
-
+ def mark_obsolete_if_last(repository)
if member_projects.where.not(id: repository.project.id).exists?
true
else
@@ -102,7 +99,8 @@ class PoolRepository < ActiveRecord::Base
end
def inspect
- "#<#{self.class.name} id:#{id} state:#{state} disk_path:#{disk_path} source_project: #{source_project.full_path}>"
+ source = source_project ? source_project.full_path : 'nil'
+ "#<#{self.class.name} id:#{id} state:#{state} disk_path:#{disk_path} source_project: #{source}>"
end
private
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
index e264fe88e47..74ccf23cf69 100644
--- a/app/models/postgresql/replication_slot.rb
+++ b/app/models/postgresql/replication_slot.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Postgresql
- class ReplicationSlot < ActiveRecord::Base
+ class ReplicationSlot < ApplicationRecord
self.table_name = 'pg_replication_slots'
# Returns true if there are any replication slots in use.
diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb
index 5f0f313b7f9..375fbe9b5a9 100644
--- a/app/models/programming_language.rb
+++ b/app/models/programming_language.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProgrammingLanguage < ActiveRecord::Base
+class ProgrammingLanguage < ApplicationRecord
validates :name, presence: true
validates :color, allow_blank: false, color: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 83f8d004a46..e64a4b313aa 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class Project < ActiveRecord::Base
+class Project < ApplicationRecord
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
@@ -38,7 +38,6 @@ class Project < ActiveRecord::Base
BoardLimitExceeded = Class.new(StandardError)
STATISTICS_ATTRIBUTE = 'repositories_count'.freeze
- NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
# Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
@@ -85,13 +84,13 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
- add_authentication_token_field :runners_token, encrypted: true, migrating: true
+ add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
before_save :ensure_runners_token
- after_save :update_project_statistics, if: :namespace_id_changed?
+ after_save :update_project_statistics, if: :saved_change_to_namespace_id?
after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
@@ -117,7 +116,7 @@ class Project < ActiveRecord::Base
after_initialize :use_hashed_storage
after_create :check_repository_absence!
after_create :ensure_storage_path_exists
- after_save :ensure_storage_path_exists, if: :namespace_id_changed?
+ after_save :ensure_storage_path_exists, if: :saved_change_to_namespace_id?
acts_as_ordered_taggable
@@ -137,7 +136,7 @@ class Project < ActiveRecord::Base
alias_attribute :parent_id, :namespace_id
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
- has_many :boards, before_add: :validate_board_limit
+ has_many :boards
# Project services
has_one :campfire_service
@@ -147,6 +146,7 @@ class Project < ActiveRecord::Base
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
+ has_one :hipchat_service
has_one :flowdock_service
has_one :assembla_service
has_one :asana_service
@@ -160,6 +160,7 @@ class Project < ActiveRecord::Base
has_one :pushover_service
has_one :jira_service
has_one :redmine_service
+ has_one :youtrack_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
@@ -187,6 +188,7 @@ class Project < ActiveRecord::Base
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
+ has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
@@ -290,12 +292,14 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops, update_only: true
+ accepts_nested_attributes_for :ci_cd_settings, update_only: true
accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
accepts_nested_attributes_for :error_tracking_setting, update_only: true
+ accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -306,13 +310,15 @@ class Project < ActiveRecord::Base
delegate :group_clusters_enabled?, to: :group, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
+ delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
+ delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: %r{(\.{2}|\A/)},
- message: 'cannot include leading slash or directory traversal.' },
+ message: _('cannot include leading slash or directory traversal.') },
length: { maximum: 255 },
allow_blank: true
validates :name,
@@ -327,14 +333,14 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
- validates :import_url, public_url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
+ validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_personal_projects_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
- validate :visibility_level_allowed_by_group, if: -> { changes.has_key?(:visibility_level) }
- validate :visibility_level_allowed_as_fork, if: -> { changes.has_key?(:visibility_level) }
+ validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
+ validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
validate :check_wiki_path_conflict
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
validates :repository_storage,
@@ -353,7 +359,8 @@ class Project < ActiveRecord::Base
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") }
- scope :sorted_by_stars, -> { reorder(star_count: :desc) }
+ scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) }
+ scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
@@ -402,6 +409,7 @@ class Project < ActiveRecord::Base
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
+ scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
scope :with_remote_mirrors, -> { joins(:remote_mirrors).where(remote_mirrors: { enabled: true }).distinct }
@@ -419,13 +427,13 @@ class Project < ActiveRecord::Base
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
- default: 3600, error_message: 'Maximum job timeout has a value which could not be accepted'
+ default: 3600, error_message: _('Maximum job timeout has a value which could not be accepted')
validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 10.minutes,
less_than: 1.month,
only_integer: true,
- message: 'needs to be beetween 10 minutes and 1 month' }
+ message: _('needs to be beetween 10 minutes and 1 month') }
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
@@ -459,10 +467,12 @@ class Project < ActiveRecord::Base
# Returns a collection of projects that is either public or visible to the
# logged in user.
- def self.public_or_visible_to_user(user = nil)
+ def self.public_or_visible_to_user(user = nil, min_access_level = nil)
+ min_access_level = nil if user&.admin?
+
if user
where('EXISTS (?) OR projects.visibility_level IN (?)',
- user.authorizations_for_projects,
+ user.authorizations_for_projects(min_access_level: min_access_level),
Gitlab::VisibilityLevel.levels_for_user(user))
else
public_to_user
@@ -472,30 +482,32 @@ class Project < ActiveRecord::Base
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either public, enabled, or internal with permission for the user.
+ # Note: this scope doesn't enforce that the user has access to the projects, it just checks
+ # that the user has access to the feature. It's important to use this scope with others
+ # that checks project authorizations first.
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
- min_access_level = ProjectFeature.required_minimum_access_level(feature)
if user&.admin?
with_feature_enabled(feature)
elsif user
+ min_access_level = ProjectFeature.required_minimum_access_level(feature)
column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature
- .where(
- "(projects.visibility_level > :private AND (#{column} IS NULL OR #{column} >= (:public_visible) OR (#{column} = :private_visible AND EXISTS(:authorizations))))"\
- " OR (projects.visibility_level = :private AND (#{column} IS NULL OR #{column} >= :private_visible) AND EXISTS(:authorizations))",
- {
- private: Gitlab::VisibilityLevel::PRIVATE,
- public_visible: ProjectFeature::ENABLED,
- private_visible: ProjectFeature::PRIVATE,
- authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
- })
+ .where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
+ {
+ public_visible: visible,
+ private_visible: ProjectFeature::PRIVATE,
+ authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
+ })
else
+ # This has to be added to include features whose value is nil in the db
+ visible << nil
with_feature_access_level(feature, visible)
end
end
@@ -540,7 +552,9 @@ class Project < ActiveRecord::Base
when 'latest_activity_asc'
reorder(last_activity_at: :asc)
when 'stars_desc'
- sorted_by_stars
+ sorted_by_stars_desc
+ when 'stars_asc'
+ sorted_by_stars_asc
else
order_by(method)
end
@@ -586,6 +600,17 @@ class Project < ActiveRecord::Base
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
+
+ # Returns ids of projects with milestones available for given user
+ #
+ # Used on queries to find milestones which user can see
+ # For example: Milestone.where(project_id: ids_with_milestone_available_for(user))
+ def ids_with_milestone_available_for(user)
+ with_issues_enabled = with_issues_available_for_user(user).select(:id)
+ with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)
+
+ from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
+ end
end
def all_pipelines
@@ -630,12 +655,29 @@ class Project < ActiveRecord::Base
end
def has_auto_devops_implicitly_enabled?
- auto_devops&.enabled.nil? &&
- (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
+ auto_devops_config = first_auto_devops_config
+
+ auto_devops_config[:scope] != :project && auto_devops_config[:status]
end
def has_auto_devops_implicitly_disabled?
- auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
+ auto_devops_config = first_auto_devops_config
+
+ auto_devops_config[:scope] != :project && !auto_devops_config[:status]
+ end
+
+ def first_auto_devops_config
+ return namespace.first_auto_devops_config if auto_devops&.enabled.nil?
+
+ { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
+ end
+
+ def multiple_mr_assignees_enabled?
+ Feature.enabled?(:multiple_merge_request_assignees, self)
+ end
+
+ def daily_statistics_enabled?
+ Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
end
def empty_repo?
@@ -832,7 +874,7 @@ class Project < ActiveRecord::Base
def mark_stuck_remote_mirrors_as_failed!
remote_mirrors.stuck.update_all(
update_status: :failed,
- last_error: 'The remote mirror took to long to complete.',
+ last_error: _('The remote mirror took to long to complete.'),
last_update_at: Time.now
)
end
@@ -864,19 +906,23 @@ class Project < ActiveRecord::Base
self.errors.add(:limit_reached, error % { limit: limit })
end
+ def should_validate_visibility_level?
+ new_record? || changes.has_key?(:visibility_level)
+ end
+
def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
- self.errors.add(:visibility_level, "#{level_name} is not allowed in a #{group_level_name} group.")
+ self.errors.add(:visibility_level, _("%{level_name} is not allowed in a %{group_level_name} group.") % { level_name: level_name, group_level_name: group_level_name })
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
- self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
+ self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
end
def check_wiki_path_conflict
@@ -885,7 +931,7 @@ class Project < ActiveRecord::Base
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
- errors.add(:name, 'has already been taken')
+ errors.add(:name, _('has already been taken'))
end
end
@@ -905,7 +951,7 @@ class Project < ActiveRecord::Base
return unless pages_https_only?
unless pages_domains.all?(&:https?)
- errors.add(:pages_https_only, "cannot be enabled unless all domains have TLS certificates")
+ errors.add(:pages_https_only, _("cannot be enabled unless all domains have TLS certificates"))
end
end
@@ -1185,7 +1231,7 @@ class Project < ActiveRecord::Base
def valid_repo?
repository.exists?
rescue
- errors.add(:path, 'Invalid repository path')
+ errors.add(:path, _('Invalid repository path'))
false
end
@@ -1195,11 +1241,9 @@ class Project < ActiveRecord::Base
def repo_exists?
strong_memoize(:repo_exists) do
- begin
- repository.exists?
- rescue
- false
- end
+ repository.exists?
+ rescue
+ false
end
end
@@ -1225,7 +1269,7 @@ class Project < ActiveRecord::Base
end
def fork_source
- return nil unless forked?
+ return unless forked?
forked_from_project || fork_network&.root_project
end
@@ -1278,7 +1322,7 @@ class Project < ActiveRecord::Base
# Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
- errors.add(:base, 'There is already a repository with that name on disk')
+ errors.add(:base, _('There is already a repository with that name on disk'))
return false
end
@@ -1300,7 +1344,7 @@ class Project < ActiveRecord::Base
repository.after_create
true
else
- errors.add(:base, 'Failed to create repository via gitlab-shell')
+ errors.add(:base, _('Failed to create repository via gitlab-shell'))
false
end
end
@@ -1373,9 +1417,10 @@ class Project < ActiveRecord::Base
repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.copy_gitattributes(branch)
repository.after_change_head
+ ProjectCacheWorker.perform_async(self.id, [], [:commit_count])
reload_default_branch
else
- errors.add(:base, "Could not change HEAD: branch '#{branch}' does not exist")
+ errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
false
end
end
@@ -1413,7 +1458,7 @@ class Project < ActiveRecord::Base
# update visibility_level of forks
def update_forks_visibility_level
- return unless visibility_level < visibility_level_was
+ return unless visibility_level < visibility_level_before_last_save
forks.each do |forked_project|
if forked_project.visibility_level > visibility_level
@@ -1427,7 +1472,7 @@ class Project < ActiveRecord::Base
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError
- errors.add(:base, 'Failed create wiki')
+ errors.add(:base, _('Failed create wiki'))
false
end
@@ -1674,7 +1719,7 @@ class Project < ActiveRecord::Base
end
def export_path
- return nil unless namespace.present? || hashed_storage?(:repository)
+ return unless namespace.present? || hashed_storage?(:repository)
import_export_shared.archive_path
end
@@ -1838,7 +1883,7 @@ class Project < ActiveRecord::Base
# Set repository as writable again
def set_repository_writable!
with_lock do
- update_column(repository_read_only, false)
+ update_column(:repository_read_only, false)
end
end
@@ -1893,8 +1938,8 @@ class Project < ActiveRecord::Base
false
end
- def full_path_was
- File.join(namespace.full_path, previous_changes['path'].first)
+ def full_path_before_last_save
+ File.join(namespace.full_path, path_before_last_save)
end
alias_method :name_with_namespace, :full_name
@@ -1916,7 +1961,7 @@ class Project < ActiveRecord::Base
#
# @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
def hashed_storage?(feature)
- raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
+ raise ArgumentError, _("Invalid feature") unless HASHED_STORAGE_FEATURES.include?(feature)
self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end
@@ -1925,6 +1970,14 @@ class Project < ActiveRecord::Base
persisted? && path_changed?
end
+ def human_merge_method
+ if merge_method == :ff
+ 'Fast-forward'
+ else
+ merge_method.to_s.humanize
+ end
+ end
+
def merge_method
if self.merge_requests_ff_only_enabled
:ff
@@ -1957,9 +2010,19 @@ class Project < ActiveRecord::Base
return unless storage_upgradable?
if git_transfer_in_progress?
- ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
+ HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
- ProjectMigrateHashedStorageWorker.perform_async(id)
+ HashedStorage::ProjectMigrateWorker.perform_async(id)
+ end
+ end
+
+ def rollback_to_legacy_storage!
+ return if legacy_storage?
+
+ if git_transfer_in_progress?
+ HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
+ else
+ HashedStorage::ProjectRollbackWorker.perform_async(id)
end
end
@@ -1973,12 +2036,8 @@ class Project < ActiveRecord::Base
@storage = nil if storage_version_changed?
end
- def gl_repository(is_wiki:)
- Gitlab::GlRepository.gl_repository(self, is_wiki)
- end
-
- def reference_counter(wiki: false)
- Gitlab::ReferenceCounter.new(gl_repository(is_wiki: wiki))
+ def reference_counter(type: Gitlab::GlRepository::PROJECT)
+ Gitlab::ReferenceCounter.new(type.identifier_for_subject(self))
end
def badges
@@ -2009,6 +2068,11 @@ class Project < ActiveRecord::Base
fetch_branch_allows_collaboration(user, branch_name)
end
+ def external_authorization_classification_label
+ super || ::Gitlab::CurrentSettings.current_application_settings
+ .external_authorization_service_default_label
+ end
+
def licensed_features
[]
end
@@ -2075,7 +2139,7 @@ class Project < ActiveRecord::Base
end
def leave_pool_repository
- pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil)
+ pool_repository&.mark_obsolete_if_last(repository) && update_column(:pool_repository_id, nil)
end
def link_pool_repository
@@ -2095,13 +2159,11 @@ class Project < ActiveRecord::Base
end
def create_new_pool_repository
- pool = begin
- create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self)
- rescue ActiveRecord::RecordNotUnique
- pool_repository(true)
- end
+ pool = PoolRepository.safe_find_or_create_by!(shard: Shard.by_name(repository_storage), source_project: self)
+ update!(pool_repository: pool)
pool.schedule unless pool.scheduled?
+
pool
end
@@ -2122,14 +2184,14 @@ class Project < ActiveRecord::Base
end
def wiki_reference_count
- reference_counter(wiki: true).value
+ reference_counter(type: Gitlab::GlRepository::WIKI).value
end
def check_repository_absence!
return if skip_disk_validation
if repository_storage.blank? || repository_with_same_path_already_exists?
- errors.add(:base, 'There is already a repository with that name on disk')
+ errors.add(:base, _('There is already a repository with that name on disk'))
throw :abort
end
end
@@ -2162,17 +2224,6 @@ class Project < ActiveRecord::Base
"projects/#{id}/pushes_since_gc"
end
- # Similar to the normal callbacks that hook into the life cycle of an
- # Active Record object, you can also define callbacks that get triggered
- # when you add an object to an association collection. If any of these
- # callbacks throw an exception, the object will not be added to the
- # collection. Before you add a new board to the boards collection if you
- # already have 1, 2, or n it will fail, but it if you have 0 that is lower
- # than the number of permitted boards per project it won't fail.
- def validate_board_limit(board)
- raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
- end
-
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
@@ -2186,7 +2237,7 @@ class Project < ActiveRecord::Base
errors.delete(error)
end
- errors.add(:base, "The project is still being deleted. Please try again later.")
+ errors.add(:base, _("The project is still being deleted. Please try again later."))
end
def pending_delete_twin
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index 2c590008db2..f95d3ab54e2 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectAuthorization < ActiveRecord::Base
+class ProjectAuthorization < ApplicationRecord
include FromUnion
belongs_to :user
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index e353a6443c4..67c12363a3c 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-class ProjectAutoDevops < ActiveRecord::Base
+class ProjectAutoDevops < ApplicationRecord
+ include IgnorableColumn
+
+ ignore_column :domain
+
belongs_to :project
enum deploy_strategy: {
@@ -12,31 +16,10 @@ class ProjectAutoDevops < ActiveRecord::Base
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
- validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
-
after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token?
- def instance_domain
- Gitlab::CurrentSettings.auto_devops_domain
- end
-
- def has_domain?
- domain.present? || instance_domain.present?
- end
-
- # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
- # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
- # for more info.
- #
- # Suppport AUTO_DEVOPS_DOMAIN is scheduled to be removed on
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- if has_domain?
- variables.append(key: 'AUTO_DEVOPS_DOMAIN',
- value: domain.presence || instance_domain)
- end
-
variables.concat(deployment_strategy_default_variables)
end
end
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index 1dad235cc2b..492d50766ea 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,11 +1,23 @@
# frozen_string_literal: true
-class ProjectCiCdSetting < ActiveRecord::Base
+class ProjectCiCdSetting < ApplicationRecord
belongs_to :project, inverse_of: :ci_cd_settings
# The version of the schema that first introduced this model/table.
MINIMUM_SCHEMA_VERSION = 20180403035759
+ DEFAULT_GIT_DEPTH = 50
+
+ before_create :set_default_git_depth
+
+ validates :default_git_depth,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 1000
+ },
+ allow_nil: true
+
def self.available?
@available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
@@ -15,4 +27,10 @@ class ProjectCiCdSetting < ActiveRecord::Base
@available = nil
super
end
+
+ private
+
+ def set_default_git_depth
+ self.default_git_depth ||= DEFAULT_GIT_DEPTH
+ end
end
diff --git a/app/models/project_custom_attribute.rb b/app/models/project_custom_attribute.rb
index 4e767cb3b26..b0da586988a 100644
--- a/app/models/project_custom_attribute.rb
+++ b/app/models/project_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectCustomAttribute < ActiveRecord::Base
+class ProjectCustomAttribute < ApplicationRecord
belongs_to :project
validates :project, :key, :value, presence: true
diff --git a/app/models/project_daily_statistic.rb b/app/models/project_daily_statistic.rb
new file mode 100644
index 00000000000..5ee11ab186e
--- /dev/null
+++ b/app/models/project_daily_statistic.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ProjectDailyStatistic < ApplicationRecord
+ belongs_to :project
+
+ scope :of_project, -> (project) { where(project: project) }
+ scope :of_last_30_days, -> { where('date >= ?', 29.days.ago.utc.to_date) }
+ scope :sorted_by_date_desc, -> { order(project_id: :desc, date: :desc) }
+ scope :sum_fetch_count, -> { sum(:fetch_count) }
+end
diff --git a/app/models/project_deploy_token.rb b/app/models/project_deploy_token.rb
index 719c492a1ff..a55667496fb 100644
--- a/app/models/project_deploy_token.rb
+++ b/app/models/project_deploy_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectDeployToken < ActiveRecord::Base
+class ProjectDeployToken < ApplicationRecord
belongs_to :project
belongs_to :deploy_token, inverse_of: :project_deploy_tokens
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index f700090a493..6bcb051bff6 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectFeature < ActiveRecord::Base
+class ProjectFeature < ApplicationRecord
# == Project features permissions
#
# Grants access level to project tools
@@ -72,11 +72,13 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ scope :for_project_id, -> (project) { where(project: project) }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
- get_permission(user, access_level(feature))
+ get_permission(user, feature)
end
def access_level(feature)
@@ -134,12 +136,12 @@ class ProjectFeature < ActiveRecord::Base
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
end
- def get_permission(user, level)
- case level
+ def get_permission(user, feature)
+ case access_level(feature)
when DISABLED
false
when PRIVATE
- user && (project.team.member?(user) || user.full_private_access?)
+ team_access?(user, feature)
when ENABLED
true
when PUBLIC
@@ -148,4 +150,11 @@ class ProjectFeature < ActiveRecord::Base
true
end
end
+
+ def team_access?(user, feature)
+ return unless user
+ return true if user.full_private_access?
+
+ project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
+ end
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index bc3759142ae..feaf172d48d 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectGroupLink < ActiveRecord::Base
+class ProjectGroupLink < ApplicationRecord
include Expirable
GUEST = 10
@@ -14,7 +14,7 @@ class ProjectGroupLink < ActiveRecord::Base
validates :project_id, presence: true
validates :group, presence: true
- validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
+ validates :group_id, uniqueness: { scope: [:project_id], message: _("already shared with this group") }
validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group
@@ -44,7 +44,7 @@ class ProjectGroupLink < ActiveRecord::Base
group_ids = project_group.ancestors.map(&:id).push(project_group.id)
if group_ids.include?(self.group.id)
- errors.add(:base, "Project cannot be shared with the group it is in or one of its ancestors.")
+ errors.add(:base, _("Project cannot be shared with the group it is in or one of its ancestors."))
end
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index aa0c121fe99..580e8dfd833 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class ProjectImportData < ActiveRecord::Base
+class ProjectImportData < ApplicationRecord
belongs_to :project, inverse_of: :import_data
attr_encrypted :credentials,
key: Settings.attr_encrypted_db_key_base,
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 488f0cb5971..1605345efd5 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectImportState < ActiveRecord::Base
+class ProjectImportState < ApplicationRecord
include AfterCommitQueue
self.table_name = "project_mirror_data"
diff --git a/app/models/project_metrics_setting.rb b/app/models/project_metrics_setting.rb
new file mode 100644
index 00000000000..a2a7dc571a4
--- /dev/null
+++ b/app/models/project_metrics_setting.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class ProjectMetricsSetting < ApplicationRecord
+ belongs_to :project
+
+ validates :external_dashboard_url,
+ length: { maximum: 255 },
+ addressable_url: { enforce_sanitization: true, ascii_only: true }
+end
diff --git a/app/models/project_repository.rb b/app/models/project_repository.rb
index 38913f3f2f5..092efabd73f 100644
--- a/app/models/project_repository.rb
+++ b/app/models/project_repository.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectRepository < ActiveRecord::Base
+class ProjectRepository < ApplicationRecord
include Shardable
belongs_to :project, inverse_of: :project_repository
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index cc5f1207653..3e28dc23782 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -11,7 +11,7 @@ class AsanaService < Service
end
def description
- 'Asana - Teamwork without email'
+ s_('AsanaService|Asana - Teamwork without email')
end
def help
@@ -36,13 +36,13 @@ http://app.asana.com/-/account_api'
{
type: 'text',
name: 'api_key',
- placeholder: 'User Personal Access Token. User must have access to task, all comments will be attributed to this user.',
+ placeholder: s_('AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user.'),
required: true
},
{
type: 'text',
name: 'restrict_to_branch',
- placeholder: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+ placeholder: s_('AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.')
}
]
end
@@ -73,7 +73,7 @@ http://app.asana.com/-/account_api'
project_name = project.full_name
data[:commits].each do |commit|
- push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
+ push_msg = s_("AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):") % { user: user, branch: branch, project_name: project_name, commit_url: commit[:url] }
check_commit(commit[:message], push_msg)
end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 71f5607dbdb..dfeb21680a9 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -31,15 +31,15 @@ class BambooService < CiService
end
def title
- 'Atlassian Bamboo CI'
+ s_('BambooService|Atlassian Bamboo CI')
end
def description
- 'A continuous integration and build server'
+ s_('BambooService|A continuous integration and build server')
end
def help
- 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+ s_('BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo.')
end
def self.to_param
@@ -49,11 +49,11 @@ class BambooService < CiService
def fields
[
{ type: 'text', name: 'bamboo_url',
- placeholder: 'Bamboo root URL like https://bamboo.example.com', required: true },
+ placeholder: s_('BambooService|Bamboo root URL like https://bamboo.example.com'), required: true },
{ type: 'text', name: 'build_key',
- placeholder: 'Bamboo build plan key like KEY', required: true },
+ placeholder: s_('BambooService|Bamboo build plan key like KEY'), required: true },
{ type: 'text', name: 'username',
- placeholder: 'A user with API access, if applicable' },
+ placeholder: s_('BambooService|A user with API access, if applicable') },
{ type: 'password', name: 'password' }
]
end
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 1d7877a1fb5..ad26e42a21b 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -57,7 +57,7 @@ class CampfireService < Service
# https://github.com/basecamp/campfire-api/blob/master/sections/messages.md#create-message
def speak(room_name, message, auth)
room = rooms(auth).find { |r| r["name"] == room_name }
- return nil unless room
+ return unless room
path = "/room/#{room["id"]}/speak.json"
body = {
diff --git a/app/models/project_services/chat_message/deployment_message.rb b/app/models/project_services/chat_message/deployment_message.rb
new file mode 100644
index 00000000000..dae3a56116e
--- /dev/null
+++ b/app/models/project_services/chat_message/deployment_message.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module ChatMessage
+ class DeploymentMessage < BaseMessage
+ attr_reader :commit_title
+ attr_reader :commit_url
+ attr_reader :deployable_id
+ attr_reader :deployable_url
+ attr_reader :environment
+ attr_reader :short_sha
+ attr_reader :status
+ attr_reader :user_url
+
+ def initialize(data)
+ super
+
+ @commit_title = data[:commit_title]
+ @commit_url = data[:commit_url]
+ @deployable_id = data[:deployable_id]
+ @deployable_url = data[:deployable_url]
+ @environment = data[:environment]
+ @short_sha = data[:short_sha]
+ @status = data[:status]
+ @user_url = data[:user_url]
+ end
+
+ def attachments
+ [{
+ text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{commit_title}",
+ color: color
+ }]
+ end
+
+ def activity
+ {}
+ end
+
+ private
+
+ def message
+ "Deploy to #{environment} #{humanized_status}"
+ end
+
+ def color
+ case status
+ when 'success'
+ 'good'
+ when 'canceled'
+ 'warning'
+ when 'failed'
+ 'danger'
+ else
+ '#334455'
+ end
+ end
+
+ def project_link
+ link(project_name, project_url)
+ end
+
+ def deployment_link
+ link("##{deployable_id}", deployable_url)
+ end
+
+ def user_link
+ link(user_combined_name, user_url)
+ end
+
+ def commit_link
+ link(short_sha, commit_url)
+ end
+
+ def humanized_status
+ status == 'success' ? 'succeeded' : status
+ end
+ end
+end
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index c10ee07ccf4..7c9ecc6b821 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -33,7 +33,7 @@ class ChatNotificationService < Service
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
- pipeline wiki_page]
+ pipeline wiki_page deployment]
end
def fields
@@ -122,6 +122,8 @@ class ChatNotificationService < Service
ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
ChatMessage::WikiPageMessage.new(data)
+ when "deployment"
+ ChatMessage::DeploymentMessage.new(data)
end
end
diff --git a/app/models/project_services/discord_service.rb b/app/models/project_services/discord_service.rb
index 21afd14dbff..4385834ed0a 100644
--- a/app/models/project_services/discord_service.rb
+++ b/app/models/project_services/discord_service.rb
@@ -4,11 +4,11 @@ require "discordrb/webhooks"
class DiscordService < ChatNotificationService
def title
- "Discord Notifications"
+ s_("DiscordService|Discord Notifications")
end
def description
- "Receive event notifications in Discord"
+ s_("DiscordService|Receive event notifications in Discord")
end
def self.to_param
@@ -33,6 +33,11 @@ class DiscordService < ChatNotificationService
# No-op.
end
+ def self.supported_events
+ %w[push issue confidential_issue merge_request note confidential_note tag_push
+ pipeline wiki_page]
+ end
+
def default_fields
[
{ type: "text", name: "webhook", placeholder: "e.g. https://discordapp.com/api/webhooks/…" },
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index fb73d430fb1..45de64a9990 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -7,11 +7,11 @@ class EmailsOnPushService < Service
validates :recipients, presence: true, if: :valid_recipients?
def title
- 'Emails on push'
+ s_('EmailsOnPushService|Emails on push')
end
def description
- 'Email the commits and diff of each push to a list of recipients.'
+ s_('EmailsOnPushService|Email the commits and diff of each push to a list of recipients.')
end
def self.to_param
@@ -45,11 +45,11 @@ class EmailsOnPushService < Service
def fields
domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ")
[
- { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer",
- help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." },
- { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs",
- help: "Don't include possibly sensitive code diffs in notification body." },
- { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }
+ { type: 'checkbox', name: 'send_from_committer_email', title: s_("EmailsOnPushService|Send from committer"),
+ help: s_("EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains}).") % { domains: domains } },
+ { type: 'checkbox', name: 'disable_diffs', title: s_("EmailsOnPushService|Disable code diffs"),
+ help: s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") },
+ { type: 'textarea', name: 'recipients', placeholder: s_('EmailsOnPushService|Emails separated by whitespace') }
]
end
end
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index d2835c6ac82..593ce69b0fd 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -6,11 +6,11 @@ class ExternalWikiService < Service
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
def title
- 'External Wiki'
+ s_('ExternalWikiService|External Wiki')
end
def description
- 'Replaces the link to the internal wiki with a link to an external wiki.'
+ s_('ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki.')
end
def self.to_param
@@ -19,7 +19,7 @@ class ExternalWikiService < Service
def fields
[
- { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki', required: true }
+ { type: 'text', name: 'external_wiki_url', placeholder: s_('ExternalWikiService|The URL of the external Wiki'), required: true }
]
end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 76624263aab..094488cb431 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -9,7 +9,7 @@ class FlowdockService < Service
end
def description
- 'Flowdock is a collaboration web app for technical teams.'
+ s_('FlowdockService|Flowdock is a collaboration web app for technical teams.')
end
def self.to_param
@@ -18,7 +18,7 @@ class FlowdockService < Service
def fields
[
- { type: 'text', name: 'token', placeholder: 'Flowdock Git source token', required: true }
+ { type: 'text', name: 'token', placeholder: s_('FlowdockService|Flowdock Git source token'), required: true }
]
end
diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb
index 272cd0f4e47..699cf1659d1 100644
--- a/app/models/project_services/hangouts_chat_service.rb
+++ b/app/models/project_services/hangouts_chat_service.rb
@@ -35,6 +35,11 @@ class HangoutsChatService < ChatNotificationService
'https://chat.googleapis.com/v1/spaces…'
end
+ def self.supported_events
+ %w[push issue confidential_issue merge_request note confidential_note tag_push
+ pipeline wiki_page]
+ end
+
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
new file mode 100644
index 00000000000..a69b7b4c4b6
--- /dev/null
+++ b/app/models/project_services/hipchat_service.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+class HipchatService < Service
+ include ActionView::Helpers::SanitizeHelper
+
+ MAX_COMMITS = 3
+ HIPCHAT_ALLOWED_TAGS = %w[
+ a b i strong em br img pre code
+ table th tr td caption colgroup col thead tbody tfoot
+ ul ol li dl dt dd
+ ].freeze
+
+ prop_accessor :token, :room, :server, :color, :api_version
+ boolean_accessor :notify_only_broken_pipelines, :notify
+ validates :token, presence: true, if: :activated?
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_pipelines = true
+ end
+ end
+
+ def title
+ 'HipChat'
+ end
+
+ def description
+ 'Private group chat and IM'
+ end
+
+ def self.to_param
+ 'hipchat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: 'Room token', required: true },
+ { type: 'text', name: 'room', placeholder: 'Room name or ID' },
+ { type: 'checkbox', name: 'notify' },
+ { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
+ { type: 'text', name: 'api_version',
+ placeholder: 'Leave blank for default (v2)' },
+ { type: 'text', name: 'server',
+ placeholder: 'Leave blank for default. https://hipchat.example.com' },
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' }
+ ]
+ end
+
+ def self.supported_events
+ %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ message = create_message(data)
+ return unless message.present?
+
+ gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def test(data)
+ begin
+ result = execute(data)
+ rescue StandardError => error
+ return { success: false, result: error }
+ end
+
+ { success: true, result: result }
+ end
+
+ private
+
+ def gate
+ options = { api_version: api_version.present? ? api_version : 'v2' }
+ options[:server_url] = server unless server.blank?
+ @gate ||= HipChat::Client.new(token, options)
+ end
+
+ def message_options(data = nil)
+ { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
+ end
+
+ def create_message(data)
+ object_kind = data[:object_kind]
+
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless update?(data)
+ when "note"
+ create_note_message(data)
+ when "pipeline"
+ create_pipeline_message(data) if should_pipeline_be_notified?(data)
+ end
+ end
+
+ def render_line(text)
+ markdown(text.lines.first.chomp, pipeline: :single_line) if text
+ end
+
+ def create_push_message(push)
+ ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
+ ref = Gitlab::Git.ref_name(push[:ref])
+
+ before = push[:before]
+ after = push[:after]
+
+ message = []
+ message << "#{push[:user_name]} "
+
+ if Gitlab::Git.blank_ref?(before)
+ message << "pushed new #{ref_type} <a href=\""\
+ "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
+ " to #{project_link}\n"
+ elsif Gitlab::Git.blank_ref?(after)
+ message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
+ else
+ message << "pushed to #{ref_type} <a href=\""\
+ "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
+ message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
+ message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
+
+ push[:commits].take(MAX_COMMITS).each do |commit|
+ message << "<br /> - #{render_line(commit[:message])} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
+ end
+
+ if push[:commits].count > MAX_COMMITS
+ message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
+ end
+ end
+
+ message.join
+ end
+
+ def markdown(text, options = {})
+ return "" unless text
+
+ context = {
+ project: project,
+ pipeline: :email
+ }
+
+ Banzai.render(text, context)
+
+ context.merge!(options)
+
+ html = Banzai.render_and_post_process(text, context)
+ sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt])
+
+ sanitized_html.truncate(200, separator: ' ', omission: '...')
+ end
+
+ def create_issue_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ title = render_line(obj_attr[:title])
+ state = obj_attr[:state]
+ issue_iid = obj_attr[:iid]
+ issue_url = obj_attr[:url]
+ description = obj_attr[:description]
+
+ issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
+
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
+ message << "<pre>#{markdown(description)}</pre>"
+
+ message.join
+ end
+
+ def create_merge_request_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ merge_request_id = obj_attr[:iid]
+ state = obj_attr[:state]
+ description = obj_attr[:description]
+ title = render_line(obj_attr[:title])
+
+ merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
+ merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
+
+ message << "<pre>#{markdown(description)}</pre>"
+ message.join
+ end
+
+ def format_title(title)
+ "<b>#{render_line(title)}</b>"
+ end
+
+ def create_note_message(data)
+ data = HashWithIndifferentAccess.new(data)
+ user_name = data[:user][:name]
+
+ obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
+ note = obj_attr[:note]
+ note_url = obj_attr[:url]
+ noteable_type = obj_attr[:noteable_type]
+ commit_id = nil
+
+ case noteable_type
+ when "Commit"
+ commit_attr = HashWithIndifferentAccess.new(data[:commit])
+ commit_id = commit_attr[:id]
+ subject_desc = commit_id
+ subject_desc = Commit.truncate_sha(subject_desc)
+ subject_type = "commit"
+ title = format_title(commit_attr[:message])
+ when "Issue"
+ subj_attr = HashWithIndifferentAccess.new(data[:issue])
+ subject_id = subj_attr[:iid]
+ subject_desc = "##{subject_id}"
+ subject_type = "issue"
+ title = format_title(subj_attr[:title])
+ when "MergeRequest"
+ subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
+ subject_id = subj_attr[:iid]
+ subject_desc = "!#{subject_id}"
+ subject_type = "merge request"
+ title = format_title(subj_attr[:title])
+ when "Snippet"
+ subj_attr = HashWithIndifferentAccess.new(data[:snippet])
+ subject_id = subj_attr[:id]
+ subject_desc = "##{subject_id}"
+ subject_type = "snippet"
+ title = format_title(subj_attr[:title])
+ end
+
+ subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
+ message << title
+
+ message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
+ message.join
+ end
+
+ def create_pipeline_message(data)
+ pipeline_attributes = data[:object_attributes]
+ pipeline_id = pipeline_attributes[:id]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ ref = pipeline_attributes[:ref]
+ user_name = (data[:user] && data[:user][:name]) || 'API'
+ status = pipeline_attributes[:status]
+ duration = pipeline_attributes[:duration]
+
+ branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
+ pipeline_url = "<a href=\"#{project_url}/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
+
+ "#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
+ end
+
+ def message_color(data)
+ pipeline_status_color(data) || color || 'yellow'
+ end
+
+ def pipeline_status_color(data)
+ return unless data && data[:object_kind] == 'pipeline'
+
+ case data[:object_attributes][:status]
+ when 'success'
+ 'green'
+ else
+ 'red'
+ end
+ end
+
+ def project_name
+ project.full_name.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+
+ def project_link
+ "<a href=\"#{project_url}\">#{project_name}</a>"
+ end
+
+ def update?(data)
+ data[:object_attributes][:action] == 'update'
+ end
+
+ def humanized_status(status)
+ case status
+ when 'success'
+ 'passed'
+ else
+ status
+ end
+ end
+
+ def should_pipeline_be_notified?(data)
+ case data[:object_attributes][:status]
+ when 'success'
+ !notify_only_broken_pipelines?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 83fd9a34438..fb76bc89c98 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -112,7 +112,7 @@ class IrkerService < Service
end
def consider_uri(uri)
- return nil if uri.scheme.nil?
+ return if uri.scheme.nil?
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 9066a0b7f1d..7b4832b84a8 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -11,7 +11,7 @@ class JiraService < IssueTrackerService
validates :password, presence: true, if: :activated?
validates :jira_issue_transition_id,
- format: { with: Gitlab::Regex.jira_transition_id_regex, message: "transition ids can have only numbers which can be split with , or ;" },
+ format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") },
allow_blank: true
# JIRA cloud version is deprecating authentication via username and password.
@@ -86,7 +86,7 @@ class JiraService < IssueTrackerService
if self.properties && self.properties['description'].present?
self.properties['description']
else
- 'Jira issue tracker'
+ s_('JiraService|Jira issue tracker')
end
end
@@ -96,11 +96,11 @@ class JiraService < IssueTrackerService
def fields
[
- { type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com', required: true },
- { type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
- { type: 'text', name: 'username', title: 'Username or Email', placeholder: 'Use a username for server version and an email for cloud version', required: true },
- { type: 'password', name: 'password', title: 'Password or API token', placeholder: 'Use a password for server version and an API token for cloud version', required: true },
- { type: 'text', name: 'jira_issue_transition_id', title: 'Transition ID(s)', placeholder: 'Use , or ; to separate multiple transition IDs' }
+ { type: 'text', name: 'url', title: s_('JiraService|Web URL'), placeholder: 'https://jira.example.com', required: true },
+ { type: 'text', name: 'api_url', title: s_('JiraService|JIRA API URL'), placeholder: s_('JiraService|If different from Web URL') },
+ { type: 'text', name: 'username', title: s_('JiraService|Username or Email'), placeholder: s_('JiraService|Use a username for server version and an email for cloud version'), required: true },
+ { type: 'password', name: 'password', title: s_('JiraService|Password or API token'), placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), required: true },
+ { type: 'text', name: 'jira_issue_transition_id', title: s_('JiraService|Transition ID(s)'), placeholder: s_('JiraService|Use , or ; to separate multiple transition IDs') }
]
end
@@ -139,7 +139,7 @@ class JiraService < IssueTrackerService
def create_cross_reference_note(mentioned, noteable, author)
unless can_cross_reference?(noteable)
- return "Events for #{noteable.model_name.plural.humanize(capitalize: false)} are disabled."
+ return s_("JiraService|Events for %{noteable_model_name} are disabled.") % { noteable_model_name: noteable.model_name.plural.humanize(capitalize: false) }
end
jira_issue = jira_request { client.Issue.find(mentioned.id) }
@@ -205,17 +205,15 @@ class JiraService < IssueTrackerService
# if any transition fails it will log the error message and stop the transition sequence
def transition_issue(issue)
jira_issue_transition_id.scan(Gitlab::Regex.jira_transition_id_regex).each do |transition_id|
- begin
- issue.transitions.build.save!(transition: { id: transition_id })
- rescue => error
- log_error("Issue transition failed", error: error.message, client_url: client_url)
- return false
- end
+ issue.transitions.build.save!(transition: { id: transition_id })
+ rescue => error
+ log_error("Issue transition failed", error: error.message, client_url: client_url)
+ return false
end
end
def add_issue_solved_comment(issue, commit_id, commit_url)
- link_title = "GitLab: Solved by commit #{commit_id}."
+ link_title = "Solved by commit #{commit_id}."
comment = "Issue solved with [#{commit_id}|#{commit_url}]."
link_props = build_remote_link_props(url: commit_url, title: link_title, resolved: true)
send_message(issue, comment, link_props)
@@ -230,7 +228,7 @@ class JiraService < IssueTrackerService
project_name = data[:project][:name]
message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'"
- link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}"
+ link_title = "#{entity_name.capitalize} - #{entity_title}"
link_props = build_remote_link_props(url: entity_url, title: link_title)
unless comment_exists?(issue, message)
@@ -267,6 +265,7 @@ class JiraService < IssueTrackerService
def find_remote_link(issue, url)
links = jira_request { issue.remotelink.all }
+ return unless links
links.find { |link| link.object["url"] == url }
end
@@ -278,6 +277,7 @@ class JiraService < IssueTrackerService
{
GlobalID: 'GitLab',
+ relationship: 'mentioned on',
object: {
url: url,
title: title,
@@ -339,9 +339,9 @@ class JiraService < IssueTrackerService
def self.event_description(event)
case event
when "merge_request", "merge_request_events"
- "JIRA comments will be created when an issue gets referenced in a merge request."
+ s_("JiraService|JIRA comments will be created when an issue gets referenced in a merge request.")
when "commit", "commit_events"
- "JIRA comments will be created when an issue gets referenced in a commit."
+ s_("JiraService|JIRA comments will be created when an issue gets referenced in a commit.")
end
end
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index f69edd60003..aa6b4aa1d5e 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -86,7 +86,7 @@ class KubernetesService < DeploymentService
]
end
- def actual_namespace
+ def kubernetes_namespace_for(project)
if namespace.present?
namespace
else
@@ -113,8 +113,8 @@ class KubernetesService < DeploymentService
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables
.append(key: 'KUBE_URL', value: api_url)
- .append(key: 'KUBE_TOKEN', value: token, public: false)
- .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+ .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true)
+ .append(key: 'KUBE_NAMESPACE', value: kubernetes_namespace_for(project))
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
if ca_pem.present?
@@ -131,8 +131,10 @@ class KubernetesService < DeploymentService
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }
+ project = environment.project
+
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, kubernetes_namespace_for(project), pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
@@ -169,7 +171,7 @@ class KubernetesService < DeploymentService
def kubeconfig
to_kubeconfig(
url: api_url,
- namespace: actual_namespace,
+ namespace: kubernetes_namespace_for(project),
token: token,
ca_pem: ca_pem)
end
@@ -186,7 +188,7 @@ class KubernetesService < DeploymentService
end
def build_kube_client!
- raise "Incomplete settings" unless api_url && actual_namespace && token
+ raise "Incomplete settings" unless api_url && kubernetes_namespace_for(project) && token
Gitlab::Kubernetes::KubeClient.new(
api_url,
@@ -200,7 +202,7 @@ class KubernetesService < DeploymentService
def read_pods
kubeclient = build_kube_client!
- kubeclient.get_pods(namespace: actual_namespace).as_json
+ kubeclient.get_pods(namespace: kubernetes_namespace_for(project)).as_json
rescue Kubeclient::ResourceNotFoundError
[]
end
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index c34078f13c1..c22a6dc26f6 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -33,6 +33,11 @@ class MicrosoftTeamsService < ChatNotificationService
def default_channel_placeholder
end
+ def self.supported_events
+ %w[push issue confidential_issue merge_request note confidential_note tag_push
+ pipeline wiki_page]
+ end
+
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
index d8bba58dcbf..c5e5f4f6400 100644
--- a/app/models/project_services/mock_ci_service.rb
+++ b/app/models/project_services/mock_ci_service.rb
@@ -2,7 +2,7 @@
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
class MockCiService < CiService
- ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
+ ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze
prop_accessor :mock_service_url
validates :mock_service_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index d60a6a7efa3..ae5d5038099 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -2,19 +2,19 @@
class PipelinesEmailService < Service
prop_accessor :recipients
- boolean_accessor :notify_only_broken_pipelines
+ boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :recipients, presence: true, if: :valid_recipients?
def initialize_properties
- self.properties ||= { notify_only_broken_pipelines: true }
+ self.properties ||= { notify_only_broken_pipelines: true, notify_only_default_branch: false }
end
def title
- 'Pipelines emails'
+ _('Pipelines emails')
end
def description
- 'Email the pipelines status to a list of recipients.'
+ _('Email the pipelines status to a list of recipients.')
end
def self.to_param
@@ -51,10 +51,12 @@ class PipelinesEmailService < Service
[
{ type: 'textarea',
name: 'recipients',
- placeholder: 'Emails separated by comma',
+ placeholder: _('Emails separated by comma'),
required: true },
{ type: 'checkbox',
- name: 'notify_only_broken_pipelines' }
+ name: 'notify_only_broken_pipelines' },
+ { type: 'checkbox',
+ name: 'notify_only_default_branch' }
]
end
@@ -67,6 +69,16 @@ class PipelinesEmailService < Service
end
def should_pipeline_be_notified?(data)
+ notify_for_pipeline_branch?(data) && notify_for_pipeline?(data)
+ end
+
+ def notify_for_pipeline_branch?(data)
+ return true unless notify_only_default_branch?
+
+ data[:object_attributes][:ref] == data[:project][:default_branch]
+ end
+
+ def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 617e502b639..c15993bdc06 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -11,7 +11,7 @@ class PivotaltrackerService < Service
end
def description
- 'Project Management Software (Source Commits Endpoint)'
+ s_('PivotalTrackerService|Project Management Software (Source Commits Endpoint)')
end
def self.to_param
@@ -23,14 +23,14 @@ class PivotaltrackerService < Service
{
type: 'text',
name: 'token',
- placeholder: 'Pivotal Tracker API token.',
+ placeholder: s_('PivotalTrackerService|Pivotal Tracker API token.'),
required: true
},
{
type: 'text',
name: 'restrict_to_branch',
- placeholder: 'Comma-separated list of branches which will be ' \
- 'automatically inspected. Leave blank to include all branches.'
+ placeholder: s_('PivotalTrackerService|Comma-separated list of branches which will be ' \
+ 'automatically inspected. Leave blank to include all branches.')
}
]
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 60cb2d380d5..c68a9d923c8 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
end
def prometheus_client
- RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
+ RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client?
end
def prometheus_available?
@@ -83,6 +83,10 @@ class PrometheusService < MonitoringService
private
+ def should_return_client?
+ api_url && manual_configuration? && active? && valid?
+ end
+
def synchronize_service_state
self.active = prometheus_available? || manual_configuration?
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 4e48c348b45..0d35bab7f80 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -11,7 +11,7 @@ class PushoverService < Service
end
def description
- 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
+ s_('PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.')
end
def self.to_param
@@ -20,15 +20,15 @@ class PushoverService < Service
def fields
[
- { type: 'text', name: 'api_key', placeholder: 'Your application key', required: true },
- { type: 'text', name: 'user_key', placeholder: 'Your user key', required: true },
- { type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' },
+ { type: 'text', name: 'api_key', placeholder: s_('PushoverService|Your application key'), required: true },
+ { type: 'text', name: 'user_key', placeholder: s_('PushoverService|Your user key'), required: true },
+ { type: 'text', name: 'device', placeholder: s_('PushoverService|Leave blank for all active devices') },
{ type: 'select', name: 'priority', required: true, choices:
[
- ['Lowest Priority', -2],
- ['Low Priority', -1],
- ['Normal Priority', 0],
- ['High Priority', 1]
+ [s_('PushoverService|Lowest Priority'), -2],
+ [s_('PushoverService|Low Priority'), -1],
+ [s_('PushoverService|Normal Priority'), 0],
+ [s_('PushoverService|High Priority'), 1]
],
default_choice: 0 },
{ type: 'select', name: 'sound', choices:
@@ -73,15 +73,15 @@ class PushoverService < Service
message =
if Gitlab::Git.blank_ref?(before)
- "#{data[:user_name]} pushed new branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} pushed new branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
elsif Gitlab::Git.blank_ref?(after)
- "#{data[:user_name]} deleted branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} deleted branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
else
- "#{data[:user_name]} push to branch \"#{ref}\"."
+ s_("PushoverService|%{user_name} push to branch \"%{ref}\".") % { user_name: data[:user_name], ref: ref }
end
if data[:total_commits_count] > 0
- message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
+ message = [message, s_("PushoverService|Total commits count: %{total_commits_count}") % { total_commits_count: data[:total_commits_count] }].join("\n")
end
pushover_data = {
@@ -92,7 +92,7 @@ class PushoverService < Service
title: "#{project.full_name}",
message: message,
url: data[:project][:web_url],
- url_title: "See project #{project.full_name}"
+ url_title: s_("PushoverService|See project %{project_full_name}") % { project_full_name: project.full_name }
}
# Sound parameter MUST NOT be sent to API if not selected
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
new file mode 100644
index 00000000000..175c2ebf197
--- /dev/null
+++ b/app/models/project_services/youtrack_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class YoutrackService < IssueTrackerService
+ validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
+
+ prop_accessor :description, :project_url, :issues_url
+
+ # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030
+ def self.reference_pattern(only_long: false)
+ if only_long
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)/
+ else
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
+ end
+ end
+
+ def title
+ 'YouTrack'
+ end
+
+ def description
+ if self.properties && self.properties['description'].present?
+ self.properties['description']
+ else
+ 'YouTrack issue tracker'
+ end
+ end
+
+ def self.to_param
+ 'youtrack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'description', placeholder: description },
+ { type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
+ { type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true }
+ ]
+ end
+end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 781a197d56f..11e3737298c 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -1,13 +1,22 @@
# frozen_string_literal: true
-class ProjectStatistics < ActiveRecord::Base
+class ProjectStatistics < ApplicationRecord
belongs_to :project
belongs_to :namespace
+ default_value_for :wiki_size, 0
+
+ # older migrations fail due to non-existent attribute without this
+ def wiki_size
+ has_attribute?(:wiki_size) ? super : 0
+ end
+
before_save :update_storage_size
- COLUMNS_TO_REFRESH = [:repository_size, :lfs_objects_size, :commit_count].freeze
- INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size] }.freeze
+ COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count].freeze
+ INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze
+
+ scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
def total_repository_size
repository_size + lfs_objects_size
@@ -27,17 +36,25 @@ class ProjectStatistics < ActiveRecord::Base
self.commit_count = project.repository.commit_count
end
- # Repository#size needs to be converted from MB to Byte.
def update_repository_size
self.repository_size = project.repository.size * 1.megabyte
end
+ def update_wiki_size
+ self.wiki_size = project.wiki.repository.size * 1.megabyte
+ end
+
def update_lfs_objects_size
self.lfs_objects_size = project.lfs_objects.sum(:size)
end
+ # older migrations fail due to non-existent attribute without this
+ def packages_size
+ has_attribute?(:packages_size) ? super : 0
+ end
+
def update_storage_size
- self.storage_size = repository_size + lfs_objects_size + build_artifacts_size
+ self.storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size
end
# Since this incremental update method does not call update_storage_size above,
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index c43bd45a62f..c91add6439f 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -13,6 +13,11 @@ class ProjectWiki
CouldNotCreateWikiError = Class.new(StandardError)
SIDEBAR = '_sidebar'
+ TITLE_ORDER = 'title'
+ CREATED_AT_ORDER = 'created_at'
+ DIRECTION_DESC = 'desc'
+ DIRECTION_ASC = 'asc'
+
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
@@ -59,7 +64,7 @@ class ProjectWiki
# Returns the Gitlab::Git::Wiki object.
def wiki
@wiki ||= begin
- gl_repository = Gitlab::GlRepository.gl_repository(project, true)
+ gl_repository = Gitlab::GlRepository::WIKI.identifier_for_subject(project)
raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path)
create_repo!(raw_repository) unless raw_repository.exists?
@@ -77,13 +82,28 @@ class ProjectWiki
end
def empty?
- pages(limit: 1).empty?
+ list_pages(limit: 1).empty?
end
+ # Lists wiki pages of the repository.
+ #
+ # limit - max number of pages returned by the method.
+ # sort - criterion by which the pages are sorted.
+ # direction - order of the sorted pages.
+ # load_content - option, which specifies whether the content inside the page
+ # will be loaded.
+ #
# Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
- def pages(limit: 0)
- wiki.pages(limit: limit).map { |page| WikiPage.new(self, page, true) }
+ def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false)
+ wiki.list_pages(
+ limit: limit,
+ sort: sort,
+ direction_desc: direction == DIRECTION_DESC,
+ load_content: load_content
+ ).map do |page|
+ WikiPage.new(self, page, true)
+ end
end
# Finds a page within the repository based on a tile
@@ -151,7 +171,7 @@ class ProjectWiki
end
def repository
- @repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true)
+ @repository ||= Repository.new(full_path, @project, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
end
def default_branch
@@ -183,7 +203,7 @@ class ProjectWiki
end
def commit_details(action, message = nil, title = nil)
- commit_message = message || default_message(action, title)
+ commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(@user)
Gitlab::Git::Wiki::CommitDetails.new(@user.id,
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 5594594a48d..62090444f79 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PrometheusMetric < ActiveRecord::Base
+class PrometheusMetric < ApplicationRecord
belongs_to :project, validate: true, inverse_of: :prometheus_metrics
enum group: {
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index d075440b147..ee0c94c20af 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedBranch < ActiveRecord::Base
+class ProtectedBranch < ApplicationRecord
include ProtectedRef
protected_ref_access_levels :merge, :push
@@ -18,13 +18,23 @@ class ProtectedBranch < ActiveRecord::Base
def self.protected?(project, ref_name)
return true if project.empty_repo? && default_branch_protected?
- refs = project.protected_branches.select(:name)
+ self.matching(ref_name, protected_refs: protected_refs(project)).present?
+ end
- self.matching(ref_name, protected_refs: refs).present?
+ def self.any_protected?(project, ref_names)
+ protected_refs(project).any? do |protected_ref|
+ ref_names.any? do |ref_name|
+ protected_ref.matches?(ref_name)
+ end
+ end
end
def self.default_branch_protected?
Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
end
+
+ def self.protected_refs(project)
+ project.protected_branches.select(:name)
+ end
end
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index b0d5c64e931..de240e40316 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
+class ProtectedBranch::MergeAccessLevel < ApplicationRecord
include ProtectedBranchAccess
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index b2a88229853..bde1d29ad7f 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
+class ProtectedBranch::PushAccessLevel < ApplicationRecord
include ProtectedBranchAccess
end
diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb
index d28ebabfe49..6b507429e57 100644
--- a/app/models/protected_tag.rb
+++ b/app/models/protected_tag.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedTag < ActiveRecord::Base
+class ProtectedTag < ApplicationRecord
include ProtectedRef
validates :name, uniqueness: { scope: :project_id }
diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb
index b06e55fb5dd..9fcfa7646a2 100644
--- a/app/models/protected_tag/create_access_level.rb
+++ b/app/models/protected_tag/create_access_level.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
+class ProtectedTag::CreateAccessLevel < ApplicationRecord
include ProtectedTagAccess
def check_access(user)
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 9c0267c3140..4698df39730 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -69,7 +69,7 @@ class PushEvent < Event
PUSHED
end
- def push?
+ def push_action?
true
end
diff --git a/app/models/push_event_payload.rb b/app/models/push_event_payload.rb
index c7769edf055..537859ec7b7 100644
--- a/app/models/push_event_payload.rb
+++ b/app/models/push_event_payload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PushEventPayload < ActiveRecord::Base
+class PushEventPayload < ApplicationRecord
include ShaAttribute
belongs_to :event, inverse_of: :push_event_payload
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index c6bd4bb6dfa..2e4769364c6 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RedirectRoute < ActiveRecord::Base
+class RedirectRoute < ApplicationRecord
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :source, presence: true
diff --git a/app/models/release.rb b/app/models/release.rb
index 0dae5c90394..7bbeb3c9976 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Release < ActiveRecord::Base
+class Release < ApplicationRecord
include CacheMarkdownField
include Gitlab::Utils::StrongMemoize
@@ -15,6 +15,7 @@ class Release < ActiveRecord::Base
accepts_nested_attributes_for :links, allow_destroy: true
validates :description, :project, :tag, presence: true
+ validates :name, presence: true, on: :create
scope :sorted, -> { order(created_at: :desc) }
@@ -30,8 +31,11 @@ class Release < ActiveRecord::Base
actual_tag.nil?
end
- def assets_count
- links.count + sources.count
+ def assets_count(except: [])
+ links_count = links.count
+ sources_count = except.include?(:sources) ? 0 : sources.count
+
+ links_count + sources_count
end
def sources
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 6c507c47752..58c2b98e524 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module Releases
- class Link < ActiveRecord::Base
+ class Link < ApplicationRecord
self.table_name = 'release_links'
belongs_to :release
- validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release }
+ validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
scope :sorted, -> { order(created_at: :desc) }
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 5eba7ddd75c..af705b29f7a 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RemoteMirror < ActiveRecord::Base
+class RemoteMirror < ApplicationRecord
include AfterCommitQueue
include MirrorAuthentication
@@ -17,13 +17,13 @@ class RemoteMirror < ActiveRecord::Base
belongs_to :project, inverse_of: :remote_mirrors
- validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
+ validates :url, presence: true, public_url: { schemes: %w(ssh git http https), allow_blank: true, enforce_user: true }
before_save :set_new_remote_name, if: :mirror_url_changed?
after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.mirror_available }
- after_save :refresh_remote, if: :mirror_url_changed?
- after_update :reset_fields, if: :mirror_url_changed?
+ after_save :refresh_remote, if: :saved_change_to_mirror_url?
+ after_update :reset_fields, if: :saved_change_to_mirror_url?
after_commit :remove_remote, on: :destroy
@@ -133,6 +133,10 @@ class RemoteMirror < ActiveRecord::Base
end
alias_method :enabled?, :enabled
+ def disabled?
+ !enabled?
+ end
+
def updated_since?(timestamp)
last_update_started_at && last_update_started_at > timestamp && !update_failed?
end
@@ -248,7 +252,7 @@ class RemoteMirror < ActiveRecord::Base
# Before adding a new remote we have to delete the data from
# the previous remote name
- prev_remote_name = remote_name_was || fallback_remote_name
+ prev_remote_name = remote_name_before_last_save || fallback_remote_name
run_after_commit do
project.repository.async_remove_remote(prev_remote_name)
end
@@ -265,4 +269,8 @@ class RemoteMirror < ActiveRecord::Base
def mirror_url_changed?
url_changed? || credentials_changed?
end
+
+ def saved_change_to_mirror_url?
+ saved_change_to_url? || saved_change_to_credentials?
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ed55a6e572b..e05d3dd58ac 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -19,7 +19,7 @@ class Repository
include Gitlab::RepositoryCacheAdapter
- attr_accessor :full_path, :disk_path, :project, :is_wiki
+ attr_accessor :full_path, :disk_path, :project, :repo_type
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
@@ -39,7 +39,8 @@ class Repository
changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref has_visible_content?
- issue_template_names merge_request_template_names xcode_project?).freeze
+ issue_template_names merge_request_template_names
+ metrics_dashboard_paths xcode_project?).freeze
# Methods that use cache_method but only memoize the value
MEMOIZED_CACHED_METHODS = %i(license).freeze
@@ -57,15 +58,16 @@ class Repository
avatar: :avatar,
issue_template: :issue_template_names,
merge_request_template: :merge_request_template_names,
+ metrics_dashboard: :metrics_dashboard_paths,
xcode_config: :xcode_project?
}.freeze
- def initialize(full_path, project, disk_path: nil, is_wiki: false)
+ def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
@full_path = full_path
@disk_path = disk_path || full_path
@project = project
@commit_cache = {}
- @is_wiki = is_wiki
+ @repo_type = repo_type
end
def ==(other)
@@ -79,7 +81,7 @@ class Repository
end
def raw_repository
- return nil unless full_path
+ return unless full_path
@raw_repository ||= initialize_raw_repository
end
@@ -103,7 +105,7 @@ class Repository
end
def commit(ref = nil)
- return nil unless exists?
+ return unless exists?
return ref if ref.is_a?(::Commit)
find_commit(ref || root_ref)
@@ -265,16 +267,14 @@ class Repository
# to avoid unnecessary syncing.
def keep_around(*shas)
shas.each do |sha|
- begin
- next unless sha.present? && commit_by(oid: sha)
+ next unless sha.present? && commit_by(oid: sha)
- next if kept_around?(sha)
+ next if kept_around?(sha)
- # This will still fail if the file is corrupted (e.g. 0 bytes)
- raw_repository.write_ref(keep_around_ref_name(sha), sha)
- rescue Gitlab::Git::CommandError => ex
- Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
- end
+ # This will still fail if the file is corrupted (e.g. 0 bytes)
+ raw_repository.write_ref(keep_around_ref_name(sha), sha)
+ rescue Gitlab::Git::CommandError => ex
+ Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
end
@@ -283,14 +283,19 @@ class Repository
end
def diverging_commit_counts(branch)
+ return diverging_commit_counts_without_max(branch) if Feature.enabled?('gitaly_count_diverging_commits_no_max')
+
+ ## TODO: deprecate the below code after 12.0
@root_ref_hash ||= raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
+ branch_sha = branch.dereferenced_target.sha
+
number_commits_behind, number_commits_ahead =
raw_repository.diverging_commit_count(
@root_ref_hash,
- branch.dereferenced_target.sha,
+ branch_sha,
max_count: MAX_DIVERGING_COUNT)
if number_commits_behind + number_commits_ahead >= MAX_DIVERGING_COUNT
@@ -301,13 +306,30 @@ class Repository
end
end
- def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
+ def diverging_commit_counts_without_max(branch)
+ @root_ref_hash ||= raw_repository.commit(root_ref).id
+ cache.fetch(:"diverging_commit_counts_without_max_#{branch.name}") do
+ # Rugged seems to throw a `ReferenceError` when given branch_names rather
+ # than SHA-1 hashes
+ branch_sha = branch.dereferenced_target.sha
+
+ number_commits_behind, number_commits_ahead =
+ raw_repository.diverging_commit_count(
+ @root_ref_hash,
+ branch_sha)
+
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+
+ def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil)
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
format,
- append_sha: append_sha
+ append_sha: append_sha,
+ path: path
)
end
@@ -464,7 +486,7 @@ class Repository
def after_import
expire_content_cache
- DetectRepositoryLanguagesWorker.perform_async(project.id, project.owner.id)
+ DetectRepositoryLanguagesWorker.perform_async(project.id)
end
# Runs code after a new commit has been pushed.
@@ -534,10 +556,9 @@ class Repository
end
def root_ref
- # When the repo does not exist, or there is no root ref, we raise this error so no data is cached.
- raw_repository&.root_ref or raise Gitlab::Git::Repository::NoRepository # rubocop:disable Style/AndOr
+ raw_repository&.root_ref
end
- cache_method :root_ref
+ cache_method_asymmetrically :root_ref
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
def exists?
@@ -604,6 +625,11 @@ class Repository
end
cache_method :merge_request_template_names, fallback: []
+ def metrics_dashboard_paths
+ Gitlab::Metrics::Dashboard::Finder.find_all_paths_from_source(project)
+ end
+ cache_method :metrics_dashboard_paths
+
def readme
head_tree&.readme
end
@@ -854,6 +880,12 @@ class Repository
end
end
+ def merge_to_ref(user, source_sha, merge_request, target_ref, message)
+ branch = merge_request.target_branch
+
+ raw.merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+
def ff_merge(user, source, target_branch, merge_request: nil)
their_commit_id = commit(source)&.id
raise 'Invalid merge source' if their_commit_id.nil?
@@ -1026,11 +1058,41 @@ class Repository
raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref)
end
+ # DEPRECATED: https://gitlab.com/gitlab-org/gitaly/issues/1628
+ def rebase_deprecated(user, merge_request)
+ rebase_sha = raw.rebase_deprecated(
+ user,
+ merge_request.id,
+ branch: merge_request.source_branch,
+ branch_sha: merge_request.source_branch_sha,
+ remote_repository: merge_request.target_project.repository.raw,
+ remote_branch: merge_request.target_branch
+ )
+
+ # To support the full deprecated behaviour, set the
+ # `rebase_commit_sha` for the merge_request here and return the value
+ merge_request.update(rebase_commit_sha: rebase_sha, merge_error: nil)
+
+ rebase_sha
+ end
+
def rebase(user, merge_request)
- raw.rebase(user, merge_request.id, branch: merge_request.source_branch,
- branch_sha: merge_request.source_branch_sha,
- remote_repository: merge_request.target_project.repository.raw,
- remote_branch: merge_request.target_branch)
+ if Feature.disabled?(:two_step_rebase, default_enabled: true)
+ return rebase_deprecated(user, merge_request)
+ end
+
+ MergeRequest.transaction do
+ raw.rebase(
+ user,
+ merge_request.id,
+ branch: merge_request.source_branch,
+ branch_sha: merge_request.source_branch_sha,
+ remote_repository: merge_request.target_project.repository.raw,
+ remote_branch: merge_request.target_branch
+ ) do |commit_id|
+ merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
+ end
+ end
end
def squash(user, merge_request, message)
@@ -1061,6 +1123,19 @@ class Repository
blob.data
end
+ def create_if_not_exists
+ return if exists?
+
+ raw.create_repository
+ after_create
+ end
+
+ def blobs_metadata(paths, ref = 'HEAD')
+ references = Array.wrap(paths).map { |path| [ref, path] }
+
+ Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) }
+ end
+
private
# TODO Generice finder, later split this on finders by Ref or Oid
@@ -1109,7 +1184,7 @@ class Repository
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage,
disk_path + '.git',
- Gitlab::GlRepository.gl_repository(project, is_wiki),
+ repo_type.identifier_for_subject(project),
project.full_path)
end
end
diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb
index b18142a2ac4..e6867f905e2 100644
--- a/app/models/repository_language.rb
+++ b/app/models/repository_language.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RepositoryLanguage < ActiveRecord::Base
+class RepositoryLanguage < ApplicationRecord
belongs_to :project
belongs_to :programming_language
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 3fd96b9dc18..f2c7cb6a65d 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -2,7 +2,7 @@
# This model is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
-class ResourceLabelEvent < ActiveRecord::Base
+class ResourceLabelEvent < ApplicationRecord
include Importable
include Gitlab::Utils::StrongMemoize
include CacheMarkdownField
diff --git a/app/models/route.rb b/app/models/route.rb
index 4b23dfa5778..91ea2966013 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Route < ActiveRecord::Base
+class Route < ApplicationRecord
include CaseSensitivity
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
@@ -14,26 +14,26 @@ class Route < ActiveRecord::Base
before_validation :delete_conflicting_orphaned_routes
after_create :delete_conflicting_redirects
- after_update :delete_conflicting_redirects, if: :path_changed?
+ after_update :delete_conflicting_redirects, if: :saved_change_to_path?
after_update :create_redirect_for_old_path
after_update :rename_descendants
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
def rename_descendants
- return unless path_changed? || name_changed?
+ return unless saved_change_to_path? || saved_change_to_name?
- descendant_routes = self.class.inside_path(path_was)
+ descendant_routes = self.class.inside_path(path_before_last_save)
descendant_routes.each do |route|
attributes = {}
- if path_changed? && route.path.present?
- attributes[:path] = route.path.sub(path_was, path)
+ if saved_change_to_path? && route.path.present?
+ attributes[:path] = route.path.sub(path_before_last_save, path)
end
- if name_changed? && name_was.present? && route.name.present?
- attributes[:name] = route.name.sub(name_was, name)
+ if saved_change_to_name? && name_before_last_save.present? && route.name.present?
+ attributes[:name] = route.name.sub(name_before_last_save, name)
end
if attributes.present?
@@ -65,7 +65,7 @@ class Route < ActiveRecord::Base
private
def create_redirect_for_old_path
- create_redirect(path_was) if path_changed?
+ create_redirect(path_before_last_save) if saved_change_to_path?
end
def delete_conflicting_orphaned_routes
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 6caab24143b..0427d5b9ca7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SentNotification < ActiveRecord::Base
+class SentNotification < ApplicationRecord
serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :project
diff --git a/app/models/serverless/function.rb b/app/models/serverless/function.rb
new file mode 100644
index 00000000000..5d4f8e0c9e2
--- /dev/null
+++ b/app/models/serverless/function.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Serverless
+ class Function
+ attr_accessor :name, :namespace
+
+ def initialize(project, name, namespace)
+ @project = project
+ @name = name
+ @namespace = namespace
+ end
+
+ def id
+ @project.id.to_s + "/" + @name + "/" + @namespace
+ end
+
+ def self.find_by_id(id)
+ array = id.split("/")
+ project = Project.find_by_id(array[0])
+ name = array[1]
+ namespace = array[2]
+
+ self.new(project, name, namespace)
+ end
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index 3461e0bfe70..9896aa12e90 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,7 +2,7 @@
# To add new service you should build a class inherited from Service
# and implement a set of methods
-class Service < ActiveRecord::Base
+class Service < ApplicationRecord
include Sortable
include Importable
include ProjectServicesLoggable
@@ -50,6 +50,7 @@ class Service < ActiveRecord::Base
scope :job_hooks, -> { where(job_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
+ scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
scope :deployment, -> { where(category: 'deployment') }
@@ -255,6 +256,7 @@ class Service < ActiveRecord::Base
external_wiki
flowdock
hangouts_chat
+ hipchat
irker
jira
kubernetes
@@ -266,6 +268,7 @@ class Service < ActiveRecord::Base
prometheus
pushover
redmine
+ youtrack
slack_slash_commands
slack
teamcity
@@ -333,6 +336,8 @@ class Service < ActiveRecord::Base
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
+ when "deployment"
+ "Event will be triggered when a deployment finishes"
end
end
diff --git a/app/models/shard.rb b/app/models/shard.rb
index e39d4232486..335a279c6aa 100644
--- a/app/models/shard.rb
+++ b/app/models/shard.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Shard < ActiveRecord::Base
+class Shard < ApplicationRecord
# Store shard names from the configuration file in the database. This is not a
# list of active shards - we just want to assign an immutable, unique ID to
# every shard name for easy indexing / referencing.
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f23ddd64fe3..f4fdac2558c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Snippet < ActiveRecord::Base
+class Snippet < ApplicationRecord
include Gitlab::VisibilityLevel
include Redactable
include CacheMarkdownField
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index ef3f974b959..5b9ece8373f 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SpamLog < ActiveRecord::Base
+class SpamLog < ApplicationRecord
belongs_to :user
validates :user, presence: true
diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb
index fd23cc9ac87..b6fb39ee81f 100644
--- a/app/models/ssh_host_key.rb
+++ b/app/models/ssh_host_key.rb
@@ -27,7 +27,7 @@ class SshHostKey
def self.find_by(opts = {})
opts = HashWithIndifferentAccess.new(opts)
- return nil unless opts.key?(:id)
+ return unless opts.key?(:id)
project_id, url = opts[:id].split(':', 2)
project = Project.find_by(id: project_id)
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 76ac5c13c18..b483c677be9 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -30,7 +30,7 @@ module Storage
end
def rename_repo(old_full_path: nil, new_full_path: nil)
- old_full_path ||= project.full_path_was
+ old_full_path ||= project.full_path_before_last_save
new_full_path ||= project.build_full_path
if gitlab_shell.mv_repository(repository_storage, old_full_path, new_full_path)
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 0f6ee0ddf7e..24a2b8b5167 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Subscription < ActiveRecord::Base
+class Subscription < ApplicationRecord
belongs_to :user
belongs_to :project
belongs_to :subscribable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index 7eee4fbbe5f..22e2f11230d 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -1,11 +1,19 @@
# frozen_string_literal: true
class Suggestion < ApplicationRecord
+ include Suggestible
+
belongs_to :note, inverse_of: :suggestions
validates :note, presence: true
validates :commit_id, presence: true, if: :applied?
- delegate :original_position, :position, :noteable, to: :note
+ delegate :position, :noteable, to: :note
+
+ scope :active, -> { where(outdated: false) }
+
+ def diff_file
+ note.latest_diff_file
+ end
def project
noteable.source_project
@@ -19,42 +27,37 @@ class Suggestion < ApplicationRecord
position.file_path
end
- def diff_file
- repository = project.repository
- position.diff_file(repository)
- end
-
- # For now, suggestions only serve as a way to send patches that
- # will change a single line (being able to apply multiple in the same place),
- # which explains `from_line` and `to_line` being the same line.
- # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # when allowing multi-line suggestions.
- def from_line
- position.new_line
- end
- alias_method :to_line, :from_line
-
- def from_original_line
- original_position.new_line
- end
- alias_method :to_original_line, :from_original_line
-
# `from_line_index` and `to_line_index` represents diff/blob line numbers in
# index-like way (N-1).
def from_line_index
from_line - 1
end
- alias_method :to_line_index, :from_line_index
- def appliable?
- return false unless note.supports_suggestion?
+ def to_line_index
+ to_line - 1
+ end
+ def appliable?(cached: true)
!applied? &&
noteable.opened? &&
+ !outdated?(cached: cached) &&
+ note.supports_suggestion? &&
different_content? &&
note.active?
end
+ # Overwrites outdated column
+ def outdated?(cached: true)
+ return super() if cached
+ return true unless diff_file
+
+ from_content != fetch_from_content
+ end
+
+ def target_line
+ position.new_line
+ end
+
private
def different_content?
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index d555ebe5322..55da37c9545 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SystemNoteMetadata < ActiveRecord::Base
+class SystemNoteMetadata < ApplicationRecord
# These notes's action text might contain a reference that is external.
# We should always force a deep validation upon references that are found
# in this note type.
diff --git a/app/models/term_agreement.rb b/app/models/term_agreement.rb
index 9b3c8ac68bd..a4a9dc10282 100644
--- a/app/models/term_agreement.rb
+++ b/app/models/term_agreement.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class TermAgreement < ActiveRecord::Base
+class TermAgreement < ApplicationRecord
belongs_to :term, class_name: 'ApplicationSetting::Term'
belongs_to :user
diff --git a/app/models/timelog.rb b/app/models/timelog.rb
index e04c644a53a..048134fbf04 100644
--- a/app/models/timelog.rb
+++ b/app/models/timelog.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Timelog < ActiveRecord::Base
+class Timelog < ApplicationRecord
validates :time_spent, :user, presence: true
validate :issuable_id_is_present
diff --git a/app/models/todo.rb b/app/models/todo.rb
index d9b86d941b6..f1fc5e599eb 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Todo < ActiveRecord::Base
+class Todo < ApplicationRecord
include Sortable
include FromUnion
@@ -31,8 +31,16 @@ class Todo < ActiveRecord::Base
belongs_to :note
belongs_to :project
belongs_to :group
- belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
+ belongs_to :target, -> {
+ if self.klass.respond_to?(:with_api_entity_associations)
+ self.with_api_entity_associations
+ else
+ self
+ end
+ }, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
+
belongs_to :user
+ belongs_to :issue, -> { where("target_type = 'Issue'") }, foreign_key: :target_id
delegate :name, :email, to: :author, prefix: true, allow_nil: true
@@ -52,6 +60,8 @@ class Todo < ActiveRecord::Base
scope :for_type, -> (type) { where(target_type: type) }
scope :for_target, -> (id) { where(target_id: id) }
scope :for_commit, -> (id) { where(commit_id: id) }
+ scope :with_api_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
+ scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
state_machine :state, initial: :pending do
event :done do
diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb
index 7b22e8cb760..810dee672b2 100644
--- a/app/models/trending_project.rb
+++ b/app/models/trending_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class TrendingProject < ActiveRecord::Base
+class TrendingProject < ApplicationRecord
belongs_to :project
# The number of months to include in the trending calculation.
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
index 37598173fd1..81415eb383b 100644
--- a/app/models/u2f_registration.rb
+++ b/app/models/u2f_registration.rb
@@ -2,7 +2,7 @@
# Registration information for U2F (universal 2nd factor) devices, like Yubikeys
-class U2fRegistration < ActiveRecord::Base
+class U2fRegistration < ApplicationRecord
belongs_to :user
def self.register(user, app_id, params, challenges)
@@ -19,7 +19,7 @@ class U2fRegistration < ActiveRecord::Base
user: user,
name: params[:name])
rescue JSON::ParserError, NoMethodError, ArgumentError
- registration.errors.add(:base, 'Your U2F device did not send a valid JSON response.')
+ registration.errors.add(:base, _('Your U2F device did not send a valid JSON response.'))
rescue U2F::Error => e
registration.errors.add(:base, e.message)
end
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 20860f14b83..ca74f16b3b8 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Upload < ActiveRecord::Base
+class Upload < ApplicationRecord
# Upper limit for foreground checksum processing
CHECKSUM_THRESHOLD = 100.megabytes
@@ -45,7 +45,7 @@ class Upload < ActiveRecord::Base
end
def absolute_path
- raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
+ raise ObjectStorage::RemoteStoreError, _("Remote object has no absolute path.") unless local?
return path unless relative_path?
uploader_class.absolute_path(self)
@@ -71,10 +71,10 @@ class Upload < ActiveRecord::Base
# Help sysadmins find missing upload files
if persisted? && !exist
if Gitlab::Sentry.enabled?
- Raven.capture_message("Upload file does not exist", extra: self.attributes)
+ Raven.capture_message(_("Upload file does not exist"), extra: self.attributes)
end
- Gitlab::Metrics.counter(:upload_file_does_not_exist_total, 'The number of times an upload record could not find its file').increment
+ Gitlab::Metrics.counter(:upload_file_does_not_exist_total, _('The number of times an upload record could not find its file')).increment
end
exist
diff --git a/app/models/user.rb b/app/models/user.rb
index ee51c35d576..2eb5c63a4cc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -105,6 +105,7 @@ class User < ApplicationRecord
has_many :groups, through: :group_members
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
+ has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group
has_many :owned_or_maintainers_groups,
-> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
through: :group_members,
@@ -159,12 +160,12 @@ class User < ApplicationRecord
# Validations
#
# Note: devise :validatable above adds validations for :email and :password
- validates :name, presence: true
+ validates :name, presence: true, length: { maximum: 128 }
validates :email, confirmation: true
validates :notification_email, presence: true
- validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
- validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
- validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
+ validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
+ validates :public_email, presence: true, uniqueness: true, devise_email: true, allow_blank: true
+ validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit,
presence: true,
@@ -193,7 +194,7 @@ class User < ApplicationRecord
before_validation :ensure_namespace_correct
before_save :ensure_namespace_correct # in case validation is skipped
after_validation :set_username_errors
- after_update :username_changed_hook, if: :username_changed?
+ after_update :username_changed_hook, if: :saved_change_to_username?
after_destroy :post_destroy_hook
after_destroy :remove_key_cache
after_commit(on: :update) do
@@ -229,6 +230,9 @@ class User < ApplicationRecord
delegate :notes_filter_for, to: :user_preference
delegate :set_notes_filter, to: :user_preference
delegate :first_day_of_week, :first_day_of_week=, to: :user_preference
+ delegate :timezone, :timezone=, to: :user_preference
+ delegate :time_display_relative, :time_display_relative=, to: :user_preference
+ delegate :time_format_in_24h, :time_format_in_24h=, to: :user_preference
accepts_nested_attributes_for :user_preference, update_only: true
@@ -276,6 +280,7 @@ class User < ApplicationRecord
scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
scope :with_emails, -> { preload(:emails) }
+ scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
# Limits the users to those that have TODOs, optionally in the given state.
#
@@ -432,7 +437,7 @@ class User < ApplicationRecord
fuzzy_arel_match(:name, query, lower_exact_match: true)
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
.or(arel_table[:email].eq(query))
- ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
+ ).reorder(order % { query: ApplicationRecord.connection.quote(query) }, :name)
end
# Limits the result set to users _not_ in the given query/list of IDs.
@@ -470,7 +475,7 @@ class User < ApplicationRecord
end
def by_login(login)
- return nil unless login
+ return unless login
if login.include?('@'.freeze)
unscoped.iwhere(email: login).take
@@ -515,7 +520,7 @@ class User < ApplicationRecord
def ghost
email = 'ghost%s@example.com'
unique_internal(where(ghost: true), 'ghost', email) do |u|
- u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
+ u.bio = _('This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.')
u.name = 'Ghost User'
end
end
@@ -535,20 +540,16 @@ class User < ApplicationRecord
username
end
- def self.internal_attributes
- [:ghost]
- end
-
def internal?
- self.class.internal_attributes.any? { |a| self[a] }
+ ghost?
end
def self.internal
- where(Hash[internal_attributes.zip([true] * internal_attributes.size)])
+ where(ghost: true)
end
def self.non_internal
- where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND "))
+ where('ghost IS NOT TRUE')
end
#
@@ -624,32 +625,32 @@ class User < ApplicationRecord
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.')
+ errors.add(:username, _('cannot be changed if a personal project has container registry tags.'))
end
end
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
- errors.add(:email, 'has already been taken')
+ errors.add(:email, _('has already been taken'))
end
end
def owns_notification_email
return if temp_oauth_email?
- errors.add(:notification_email, "is not an email you own") unless all_emails.include?(notification_email)
+ errors.add(:notification_email, _("is not an email you own")) unless all_emails.include?(notification_email)
end
def owns_public_email
return if public_email.blank?
- errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
+ errors.add(:public_email, _("is not an email you own")) unless all_emails.include?(public_email)
end
def owns_commit_email
return if read_attribute(:commit_email).blank?
- errors.add(:commit_email, "is not an email you own") unless verified_emails.include?(commit_email)
+ errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
end
# Define commit_email-related attribute methods explicitly instead of relying
@@ -759,11 +760,15 @@ class User < ApplicationRecord
# Typically used in conjunction with projects table to get projects
# a user has been given access to.
+ # The param `related_project_column` is the column to compare to the
+ # project_authorizations. By default is projects.id
#
# Example use:
# `Project.where('EXISTS(?)', user.authorizations_for_projects)`
- def authorizations_for_projects(min_access_level: nil)
- authorizations = project_authorizations.select(1).where('project_authorizations.project_id = projects.id')
+ def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
+ authorizations = project_authorizations
+ .select(1)
+ .where("project_authorizations.project_id = #{related_project_column}")
return authorizations unless min_access_level.present?
@@ -882,7 +887,12 @@ class User < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass
def several_namespaces?
- owned_groups.any? || maintainers_groups.any?
+ union_sql = ::Gitlab::SQL::Union.new(
+ [owned_groups,
+ maintainers_groups,
+ groups_with_developer_maintainer_project_access]).to_sql
+
+ ::Group.from("(#{union_sql}) #{::Group.table_name}").any?
end
def namespace_id
@@ -917,6 +927,10 @@ class User < ApplicationRecord
DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
end
+ def highest_role
+ members.maximum(:access_level) || Gitlab::Access::NO_ACCESS
+ end
+
def accessible_deploy_keys
@accessible_deploy_keys ||= begin
key_ids = project_deploy_keys.pluck(:id)
@@ -1164,12 +1178,24 @@ class User < ApplicationRecord
@manageable_namespaces ||= [namespace] + manageable_groups
end
- def manageable_groups
- Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
+ def manageable_groups(include_groups_with_developer_maintainer_access: false)
+ owned_and_maintainer_group_hierarchy = Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
+
+ if include_groups_with_developer_maintainer_access
+ union_sql = ::Gitlab::SQL::Union.new(
+ [owned_and_maintainer_group_hierarchy,
+ groups_with_developer_maintainer_project_access]).to_sql
+
+ ::Group.from("(#{union_sql}) #{::Group.table_name}")
+ else
+ owned_and_maintainer_group_hierarchy
+ end
end
- def manageable_groups_with_routes
- manageable_groups.eager_load(:route).order('routes.path')
+ def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false)
+ manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access)
+ .eager_load(:route)
+ .order('routes.path')
end
def namespaces
@@ -1471,15 +1497,6 @@ class User < ApplicationRecord
devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
end
- # This works around a bug in Devise 4.2.0 that erroneously causes a user to
- # be considered active in MySQL specs due to a sub-second comparison
- # issue. For more details, see: https://gitlab.com/gitlab-org/gitlab-ee/issues/2362#note_29004709
- def confirmation_period_valid?
- return false if self.class.allow_unconfirmed_access_for == 0.days
-
- super
- end
-
def ensure_user_rights_and_limits
if external?
self.can_create_group = false
@@ -1568,4 +1585,16 @@ class User < ApplicationRecord
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end
+
+ def groups_with_developer_maintainer_project_access
+ project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS]
+
+ if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
+ project_creation_levels << nil
+ end
+
+ developer_groups_hierarchy = ::Gitlab::ObjectHierarchy.new(developer_groups).base_and_descendants
+ ::Group.where(id: developer_groups_hierarchy.select(:id),
+ project_creation_level: project_creation_levels)
+ end
end
diff --git a/app/models/user_agent_detail.rb b/app/models/user_agent_detail.rb
index e2b2e7f1df9..fea1fce3c8d 100644
--- a/app/models/user_agent_detail.rb
+++ b/app/models/user_agent_detail.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserAgentDetail < ActiveRecord::Base
+class UserAgentDetail < ApplicationRecord
belongs_to :subject, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 76e7bc06b4e..027ee44c6a9 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserCallout < ActiveRecord::Base
+class UserCallout < ApplicationRecord
belongs_to :user
# We use `UserCalloutEnums.feature_names` here so that EE can more easily
diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb
index e0ffe8ebbfd..727975c3f6e 100644
--- a/app/models/user_custom_attribute.rb
+++ b/app/models/user_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserCustomAttribute < ActiveRecord::Base
+class UserCustomAttribute < ApplicationRecord
belongs_to :user
validates :user_id, :key, :value, presence: true
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
index ae6778e49be..f6f72f4b77a 100644
--- a/app/models/user_interacted_project.rb
+++ b/app/models/user_interacted_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserInteractedProject < ActiveRecord::Base
+class UserInteractedProject < ApplicationRecord
belongs_to :user
belongs_to :project
@@ -26,16 +26,14 @@ class UserInteractedProject < ActiveRecord::Base
cached_exists?(attributes) do
transaction(requires_new: true) do
- begin
- where(attributes).select(1).first || create!(attributes)
- true # not caching the whole record here for now
- rescue ActiveRecord::RecordNotUnique
- # Note, above queries are not atomic and prone
- # to race conditions (similar like #find_or_create!).
- # In the case where we hit this, the record we want
- # already exists - shortcut and return.
- true
- end
+ where(attributes).select(1).first || create!(attributes)
+ true # not caching the whole record here for now
+ rescue ActiveRecord::RecordNotUnique
+ # Note, above queries are not atomic and prone
+ # to race conditions (similar like #find_or_create!).
+ # In the case where we hit this, the record we want
+ # already exists - shortcut and return.
+ true
end
end
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 32d0407800f..f1326f4c8cb 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserPreference < ActiveRecord::Base
+class UserPreference < ApplicationRecord
# We could use enums, but Rails 4 doesn't support multiple
# enum options with same name for multiple fields, also it creates
# extra methods that aren't really needed here.
@@ -10,6 +10,10 @@ class UserPreference < ActiveRecord::Base
validates :issue_notes_filter, :merge_request_notes_filter, inclusion: { in: NOTES_FILTERS.values }, presence: true
+ default_value_for :timezone, value: Time.zone.tzinfo.name, allows_nil: false
+ default_value_for :time_display_relative, value: true, allows_nil: false
+ default_value_for :time_format_in_24h, value: false, allows_nil: false
+
class << self
def notes_filters
{
diff --git a/app/models/user_status.rb b/app/models/user_status.rb
index 2bbb0c59ac1..6ced4f56823 100644
--- a/app/models/user_status.rb
+++ b/app/models/user_status.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserStatus < ActiveRecord::Base
+class UserStatus < ApplicationRecord
include CacheMarkdownField
self.primary_key = :user_id
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 7115262942d..5aacf11b1cb 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserSyncedAttributesMetadata < ActiveRecord::Base
+class UserSyncedAttributesMetadata < ApplicationRecord
belongs_to :user
validates :user, presence: true
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index bdaf58ae1c1..9be6bd2e6f3 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UsersStarProject < ActiveRecord::Base
+class UsersStarProject < ApplicationRecord
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index b1d6d461928..cd4c7895587 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -28,16 +28,17 @@ class WikiPage
def self.group_by_directory(pages)
return [] if pages.blank?
- pages.sort_by { |page| [page.directory, page.slug] }
- .group_by(&:directory)
- .map do |dir, pages|
- if dir.present?
- WikiDirectory.new(dir, pages)
- else
- pages
- end
+ pages.each_with_object([]) do |page, grouped_pages|
+ next grouped_pages << page unless page.directory.present?
+
+ directory = grouped_pages.find do |obj|
+ obj.is_a?(WikiDirectory) && obj.slug == page.directory
end
- .flatten
+
+ next directory.pages << page if directory
+
+ grouped_pages << WikiDirectory.new(page.directory, [page])
+ end
end
def self.unhyphenize(name)
@@ -132,7 +133,7 @@ class WikiPage
# The GitLab Commit instance for this page.
def version
- return nil unless persisted?
+ return unless persisted?
@version ||= @page.version
end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 72de04203a6..5dd2279ef99 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -22,6 +22,13 @@ class BasePolicy < DeclarativePolicy::Base
Gitlab::CurrentSettings.current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
- # This is prevented in some cases in `gitlab-ee`
+ condition(:external_authorization_enabled, scope: :global, score: 0) do
+ ::Gitlab::ExternalAuthorization.perform_check?
+ end
+
+ rule { external_authorization_enabled & ~full_private_access }.policy do
+ prevent :read_cross_project
+ end
+
rule { default }.enable :read_cross_project
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index 2c90b8a73cd..662c29a0973 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -14,6 +14,10 @@ module Ci
@subject.external?
end
+ condition(:triggerer_of_pipeline) do
+ @subject.triggered_by?(@user)
+ end
+
# Disallow users without permissions from accessing internal pipelines
rule { ~can?(:read_build) & ~external_pipeline }.policy do
prevent :read_pipeline
@@ -29,6 +33,14 @@ module Ci
enable :destroy_pipeline
end
+ rule { can?(:admin_pipeline) }.policy do
+ enable :read_pipeline_variable
+ end
+
+ rule { can?(:update_pipeline) & triggerer_of_pipeline }.policy do
+ enable :read_pipeline_variable
+ end
+
def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project)
diff --git a/app/policies/clusters/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb
index d6d590687e2..316bd39f7a3 100644
--- a/app/policies/clusters/cluster_policy.rb
+++ b/app/policies/clusters/cluster_policy.rb
@@ -6,5 +6,6 @@ module Clusters
delegate { cluster.group }
delegate { cluster.project }
+ delegate { cluster.instance }
end
end
diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb
new file mode 100644
index 00000000000..e1045c85e6d
--- /dev/null
+++ b/app/policies/clusters/instance_policy.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Clusters
+ class InstancePolicy < BasePolicy
+ include ClusterableActions
+
+ condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
+ condition(:can_have_multiple_clusters) { multiple_clusters_available? }
+ condition(:instance_clusters_enabled) { Instance.enabled? }
+
+ rule { admin & instance_clusters_enabled }.policy do
+ enable :read_cluster
+ enable :add_cluster
+ enable :create_cluster
+ enable :update_cluster
+ enable :admin_cluster
+ end
+
+ rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
+ end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 16c58730878..e85397422e6 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -44,7 +44,6 @@ class GlobalPolicy < BasePolicy
prevent :access_api
prevent :access_git
prevent :receive_notifications
- prevent :use_quick_actions
end
rule { required_terms_not_accepted }.policy do
@@ -68,6 +67,10 @@ class GlobalPolicy < BasePolicy
enable :read_users_list
end
+ rule { ~anonymous }.policy do
+ enable :read_instance_metadata
+ end
+
rule { admin }.policy do
enable :read_custom_attribute
enable :update_custom_attribute
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index c25766a5af8..ea86858181d 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -26,7 +26,7 @@ class GroupPolicy < BasePolicy
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:has_projects) do
- GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
+ GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
end
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
@@ -35,6 +35,14 @@ class GroupPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled }
+ condition(:create_projects_disabled) do
+ @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS
+ end
+
+ condition(:developer_maintainer_access) do
+ @subject.project_creation_level == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
+ end
+
rule { public_group }.policy do
enable :read_group
enable :read_list
@@ -53,8 +61,9 @@ class GroupPolicy < BasePolicy
rule { admin }.enable :read_group
rule { has_projects }.policy do
- enable :read_group
+ enable :read_list
enable :read_label
+ enable :read_group
end
rule { has_access }.enable :read_namespace
@@ -114,9 +123,16 @@ class GroupPolicy < BasePolicy
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
+ rule { developer & developer_maintainer_access }.enable :create_projects
+ rule { create_projects_disabled }.prevent :create_projects
+
def access_level
return GroupMember::NO_ACCESS if @user.nil?
- @access_level ||= @subject.max_member_access_for_user(@user)
+ @access_level ||= lookup_access_level!
+ end
+
+ def lookup_access_level!
+ @subject.max_member_access_for_user(@user)
end
end
diff --git a/app/policies/identity_provider_policy.rb b/app/policies/identity_provider_policy.rb
new file mode 100644
index 00000000000..d34cdd5bdd4
--- /dev/null
+++ b/app/policies/identity_provider_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class IdentityProviderPolicy < BasePolicy
+ desc "Provider is SAML or CAS3"
+ condition(:protected_provider, scope: :subject, score: 0) { %w(saml cas3).include?(@subject.to_s) }
+
+ rule { anonymous }.prevent_all
+
+ rule { default }.policy do
+ enable :unlink
+ enable :link
+ end
+
+ rule { protected_provider }.prevent(:unlink)
+end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index ecb2797d1d9..537319addc2 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -17,6 +17,7 @@ class IssuablePolicy < BasePolicy
enable :reopen_issue
enable :read_merge_request
enable :update_merge_request
+ enable :reopen_merge_request
end
rule { locked & ~is_project_member }.policy do
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index a2950951d03..a3692857ff4 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
class MergeRequestPolicy < IssuablePolicy
+ rule { locked }.policy do
+ prevent :reopen_merge_request
+ end
end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index 2b5cca76c20..40dd49b4afd 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -7,7 +7,7 @@ class PersonalSnippetPolicy < BasePolicy
rule { public_snippet }.policy do
enable :read_personal_snippet
- enable :comment_personal_snippet
+ enable :create_note
end
rule { is_author }.policy do
@@ -15,7 +15,7 @@ class PersonalSnippetPolicy < BasePolicy
enable :update_personal_snippet
enable :destroy_personal_snippet
enable :admin_personal_snippet
- enable :comment_personal_snippet
+ enable :create_note
end
rule { ~anonymous }.enable :create_personal_snippet
@@ -23,15 +23,12 @@ class PersonalSnippetPolicy < BasePolicy
rule { internal_snippet & ~external_user }.policy do
enable :read_personal_snippet
- enable :comment_personal_snippet
+ enable :create_note
end
- rule { anonymous }.prevent :comment_personal_snippet
+ rule { anonymous }.prevent :create_note
- rule { can?(:comment_personal_snippet) }.policy do
- enable :create_note
- enable :award_emoji
- end
+ rule { can?(:create_note) }.enable :award_emoji
rule { full_private_access }.enable :read_personal_snippet
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 95dd8b2795e..3218c04b219 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -89,6 +89,15 @@ class ProjectPolicy < BasePolicy
::Gitlab::CurrentSettings.current_application_settings.mirror_available
end
+ with_scope :subject
+ condition(:classification_label_authorized, score: 32) do
+ ::Gitlab::ExternalAuthorization.access_allowed?(
+ @user,
+ @subject.external_authorization_classification_label,
+ @subject.full_path
+ )
+ end
+
# We aren't checking `:read_issue` or `:read_merge_request` in this case
# because it could be possible for a user to see an issuable-iid
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
@@ -187,6 +196,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:reporter_access) }.policy do
enable :download_code
+ enable :read_statistics
enable :download_wiki_code
enable :fork_project
enable :create_project_snippet
@@ -203,6 +213,7 @@ class ProjectPolicy < BasePolicy
enable :read_deployment
enable :read_merge_request
enable :read_sentry_issue
+ enable :read_prometheus
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
@@ -231,6 +242,7 @@ class ProjectPolicy < BasePolicy
enable :admin_merge_request
enable :admin_milestone
enable :update_merge_request
+ enable :reopen_merge_request
enable :create_commit_status
enable :update_commit_status
enable :create_build
@@ -278,6 +290,8 @@ class ProjectPolicy < BasePolicy
enable :admin_cluster
enable :create_environment_terminal
enable :destroy_release
+ enable :destroy_artifacts
+ enable :daily_statistics
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
@@ -299,6 +313,8 @@ class ProjectPolicy < BasePolicy
rule { issues_disabled }.policy do
prevent(*create_read_update_admin_destroy(:issue))
+ prevent(*create_read_update_admin_destroy(:board))
+ prevent(*create_read_update_admin_destroy(:list))
end
rule { merge_requests_disabled | repository_disabled }.policy do
@@ -410,6 +426,25 @@ class ProjectPolicy < BasePolicy
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
+ rule { ~can?(:read_cross_project) & ~classification_label_authorized }.policy do
+ # Preventing access here still allows the projects to be listed. Listing
+ # projects doesn't check the `:read_project` ability. But instead counts
+ # on the `project_authorizations` table.
+ #
+ # All other actions should explicitly check read project, which would
+ # trigger the `classification_label_authorized` condition.
+ #
+ # `:read_project_for_iids` is not prevented by this condition, as it is
+ # used for cross-project reference checks.
+ prevent :guest_access
+ prevent :public_access
+ prevent :public_user_access
+ prevent :reporter_access
+ prevent :developer_access
+ prevent :maintainer_access
+ prevent :owner_access
+ end
+
private
def team_member?
@@ -453,6 +488,10 @@ class ProjectPolicy < BasePolicy
def team_access_level
return -1 if @user.nil?
+ lookup_access_level!
+ end
+
+ def lookup_access_level!
# NOTE: max_member_access has its own cache
project.team.max_member_access(@user.id)
end
@@ -462,7 +501,7 @@ class ProjectPolicy < BasePolicy
when ProjectFeature::DISABLED
false
when ProjectFeature::PRIVATE
- guest? || admin?
+ admin? || team_access_level >= ProjectFeature.required_minimum_access_level(feature)
else
true
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 6323c1b3389..c5675ef3ea3 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -13,4 +13,8 @@ class BlobPresenter < Gitlab::View::Presenter::Simple
plain: plain
)
end
+
+ def web_url
+ Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path))
+ end
end
diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb
new file mode 100644
index 00000000000..7b13db3bb74
--- /dev/null
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'gt_one_coercion'
+
+module Blobs
+ class UnfoldPresenter < BlobPresenter
+ include Virtus.model
+ include Gitlab::Utils::StrongMemoize
+
+ attribute :full, Boolean, default: false
+ attribute :since, GtOneCoercion
+ attribute :to, GtOneCoercion
+ attribute :bottom, Boolean
+ attribute :unfold, Boolean, default: true
+ attribute :offset, Integer
+ attribute :indent, Integer, default: 0
+
+ def initialize(blob, params)
+ @subject = blob
+ @all_lines = highlight.lines
+ super(params)
+
+ if full?
+ self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
+ end
+ end
+
+ # Converts a String array to Gitlab::Diff::Line array, with match line added
+ def diff_lines
+ diff_lines = lines.map do |line|
+ Gitlab::Diff::Line.new(line, nil, nil, nil, nil, rich_text: line)
+ end
+
+ add_match_line(diff_lines)
+
+ diff_lines
+ end
+
+ def lines
+ strong_memoize(:lines) do
+ lines = @all_lines
+ lines = lines[since - 1..to - 1] unless full?
+ lines.map(&:html_safe)
+ end
+ end
+
+ def match_line_text
+ return '' if bottom?
+
+ lines_length = lines.length - 1
+ line = [since, lines_length].join(',')
+ "@@ -#{line}+#{line} @@"
+ end
+
+ private
+
+ def add_match_line(diff_lines)
+ return unless unfold?
+
+ if bottom? && to < @all_lines.size
+ old_pos = to - offset
+ new_pos = to
+ elsif since != 1
+ old_pos = new_pos = since
+ end
+
+ # Match line is not needed when it reaches the top limit or bottom limit of the file.
+ return unless new_pos
+
+ match_line = Gitlab::Diff::Line.new(match_line_text, 'match', nil, old_pos, new_pos)
+
+ bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line)
+ end
+ end
+end
diff --git a/app/presenters/ci/bridge_presenter.rb b/app/presenters/ci/bridge_presenter.rb
new file mode 100644
index 00000000000..ee11cffe355
--- /dev/null
+++ b/app/presenters/ci/bridge_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Ci
+ class BridgePresenter < CommitStatusPresenter
+ def detailed_status
+ @detailed_status ||= subject.detailed_status(user)
+ end
+ end
+end
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index d60281c8a0b..471b6d3b726 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -25,17 +25,19 @@ module Ci
end
def git_depth
- strong_memoize(:git_depth) do
- git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
- git_depth.to_i
- end
+ if git_depth_variable
+ git_depth_variable[:value]
+ else
+ project.default_git_depth
+ end.to_i
end
def refspecs
specs = []
+ specs << refspec_for_merge_request_ref if merge_request_ref?
if git_depth > 0
- specs << refspec_for_branch(ref) if branch? || merge_request?
+ specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
specs << refspec_for_tag(ref) if tag?
else
specs << refspec_for_branch
@@ -83,5 +85,15 @@ module Ci
def refspec_for_tag(ref = '*')
"+#{Gitlab::Git::TAG_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_TAG_PREFIX}#{ref}"
end
+
+ def refspec_for_merge_request_ref
+ "+#{ref}:#{ref}"
+ end
+
+ def git_depth_variable
+ strong_memoize(:git_depth_variable) do
+ variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }
+ end
+ end
end
end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 57daf04efc6..358473d0a74 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -3,6 +3,7 @@
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
+ include ActionView::Helpers::UrlHelper
# We use a class method here instead of a constant, allowing EE to redefine
# the returned `Hash` more easily.
@@ -32,5 +33,49 @@ module Ci
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
+
+ def ref_text
+ if pipeline.detached_merge_request_pipeline?
+ _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch }
+ elsif pipeline.merge_request_pipeline?
+ _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch, link_to_merge_request_target_branch: link_to_merge_request_target_branch }
+ elsif pipeline.ref
+ if pipeline.ref_exists?
+ _("for %{link_to_pipeline_ref}").html_safe % { link_to_pipeline_ref: link_to_pipeline_ref }
+ else
+ _("for %{ref}").html_safe % { ref: content_tag(:span, pipeline.ref, class: 'ref-name') }
+ end
+ end
+ end
+
+ def link_to_pipeline_ref
+ link_to(pipeline.ref,
+ project_commits_path(pipeline.project, pipeline.ref),
+ class: "ref-name")
+ end
+
+ def link_to_merge_request
+ return unless merge_request_presenter
+
+ link_to(merge_request_presenter.to_reference,
+ project_merge_request_path(merge_request_presenter.project, merge_request_presenter),
+ class: 'mr-iid')
+ end
+
+ def link_to_merge_request_source_branch
+ merge_request_presenter&.source_branch_link
+ end
+
+ def link_to_merge_request_target_branch
+ merge_request_presenter&.target_branch_link
+ end
+
+ private
+
+ def merge_request_presenter
+ return unless pipeline.triggered_by_merge_request?
+
+ @merge_request_presenter ||= pipeline.merge_request.present(current_user: current_user)
+ end
end
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index d94d9118eee..34bdf156623 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -44,6 +44,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
+ def update_applications_cluster_path(cluster, application)
+ raise NotImplementedError
+ end
+
def cluster_path(cluster, params = {})
raise NotImplementedError
end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index 7a5b68f9a4b..1634d2479a0 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -22,10 +22,6 @@ module Clusters
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
- def can_toggle_cluster?
- can?(current_user, :update_cluster, cluster) && created?
- end
-
def can_read_cluster?
can?(current_user, :read_cluster, cluster)
end
@@ -35,6 +31,8 @@ module Clusters
s_("ClusterIntegration|Project cluster")
elsif cluster.group_type?
s_("ClusterIntegration|Group cluster")
+ elsif cluster.instance_type?
+ s_("ClusterIntegration|Instance cluster")
end
end
@@ -43,11 +41,17 @@ module Clusters
project_cluster_path(project, cluster)
elsif cluster.group_type?
group_cluster_path(group, cluster)
+ elsif cluster.instance_type?
+ admin_cluster_path(cluster)
else
raise NotImplementedError
end
end
+ def read_only_kubernetes_platform_fields?
+ !cluster.provided_by_user?
+ end
+
private
def clusterable
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 0cd77da6303..28a25c8b7a3 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -11,7 +11,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
- archived_failure: 'The job is archived and cannot be run'
+ archived_failure: 'The job is archived and cannot be run',
+ unmet_prerequisites: 'The job failed to complete prerequisite tasks'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb
index ef6bbc0d109..f5b0bb64487 100644
--- a/app/presenters/group_clusterable_presenter.rb
+++ b/app/presenters/group_clusterable_presenter.rb
@@ -14,6 +14,11 @@ class GroupClusterablePresenter < ClusterablePresenter
install_applications_group_cluster_path(clusterable, cluster, application)
end
+ override :update_applications_cluster_path
+ def update_applications_cluster_path(cluster, application)
+ update_applications_group_cluster_path(clusterable, cluster, application)
+ end
+
override :cluster_path
def cluster_path(cluster, params = {})
group_cluster_path(clusterable, cluster, params)
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
new file mode 100644
index 00000000000..f8bbe5216f1
--- /dev/null
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+class InstanceClusterablePresenter < ClusterablePresenter
+ extend ::Gitlab::Utils::Override
+ include ActionView::Helpers::UrlHelper
+
+ def self.fabricate(clusterable, **attributes)
+ attributes_with_presenter_class = attributes.merge(presenter_class: InstanceClusterablePresenter)
+
+ Gitlab::View::Presenter::Factory
+ .new(clusterable, attributes_with_presenter_class)
+ .fabricate!
+ end
+
+ override :index_path
+ def index_path
+ admin_clusters_path
+ end
+
+ override :new_path
+ def new_path
+ new_admin_cluster_path
+ end
+
+ override :cluster_status_cluster_path
+ def cluster_status_cluster_path(cluster, params = {})
+ cluster_status_admin_cluster_path(cluster, params)
+ end
+
+ override :install_applications_cluster_path
+ def install_applications_cluster_path(cluster, application)
+ install_applications_admin_cluster_path(cluster, application)
+ end
+
+ override :update_applications_cluster_path
+ def update_applications_cluster_path(cluster, application)
+ update_applications_admin_cluster_path(cluster, application)
+ end
+
+ override :cluster_path
+ def cluster_path(cluster, params = {})
+ admin_cluster_path(cluster, params)
+ end
+
+ override :create_user_clusters_path
+ def create_user_clusters_path
+ create_user_admin_clusters_path
+ end
+
+ override :create_gcp_clusters_path
+ def create_gcp_clusters_path
+ create_gcp_admin_clusters_path
+ end
+
+ override :empty_state_help_text
+ def empty_state_help_text
+ s_('ClusterIntegration|Adding an integration will share the cluster across all projects.')
+ end
+
+ override :sidebar_text
+ def sidebar_text
+ s_('ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster.')
+ end
+
+ override :learn_more_link
+ def learn_more_link
+ link_to(s_('ClusterIntegration|Learn more about instance Kubernetes clusters'), help_page_path('user/instance/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ end
+end
diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb
index c12a202efbc..c9dc0dbf443 100644
--- a/app/presenters/issue_presenter.rb
+++ b/app/presenters/issue_presenter.rb
@@ -4,6 +4,16 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
presents :issue
def web_url
- Gitlab::UrlBuilder.build(issue)
+ url_builder.url
+ end
+
+ def issue_path
+ url_builder.issue_path(issue)
+ end
+
+ private
+
+ def url_builder
+ @url_builder ||= Gitlab::UrlBuilder.new(issue)
end
end
diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb
new file mode 100644
index 00000000000..1077bf543d9
--- /dev/null
+++ b/app/presenters/label_presenter.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+class LabelPresenter < Gitlab::View::Presenter::Delegated
+ presents :label
+
+ def edit_path
+ case label
+ when GroupLabel then edit_group_label_path(label.group, label)
+ when ProjectLabel then edit_project_label_path(label.project, label)
+ end
+ end
+
+ def destroy_path
+ case label
+ when GroupLabel then group_label_path(label.group, label)
+ when ProjectLabel then project_label_path(label.project, label)
+ end
+ end
+
+ def filter_path(type: :issue)
+ case context_subject
+ when Group
+ send("#{type.to_s.pluralize}_group_path", # rubocop:disable GitlabSecurity/PublicSend
+ context_subject,
+ label_name: [label.name])
+ when Project
+ send("namespace_project_#{type.to_s.pluralize}_path", # rubocop:disable GitlabSecurity/PublicSend
+ context_subject.namespace,
+ context_subject,
+ label_name: [label.name])
+ end
+ end
+
+ def can_subscribe_to_label_in_different_levels?
+ issuable_subject.is_a?(Project) && label.is_a?(GroupLabel)
+ end
+
+ def project_label?
+ label.is_a?(ProjectLabel)
+ end
+
+ def subject_name
+ label.subject.name
+ end
+
+ private
+
+ def context_subject
+ issuable_subject || label.try(:subject)
+ end
+end
diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb
index 9e9b6973b8e..2561c3f0244 100644
--- a/app/presenters/member_presenter.rb
+++ b/app/presenters/member_presenter.rb
@@ -32,6 +32,11 @@ class MemberPresenter < Gitlab::View::Presenter::Delegated
request? && can_update?
end
+ # This functionality is only available in EE.
+ def can_override?
+ false
+ end
+
private
def admin_member_permission
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index c59e73f824c..9c44ed711a6 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -13,7 +13,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def ci_status
if pipeline
status = pipeline.status
- status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings?
+ status = "success-with-warnings" if pipeline.success? && pipeline.has_warnings?
status || "preparing"
else
@@ -22,9 +22,9 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
- def cancel_merge_when_pipeline_succeeds_path
- if can_cancel_merge_when_pipeline_succeeds?(current_user)
- cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
+ def cancel_auto_merge_path
+ if can_cancel_auto_merge?(current_user)
+ cancel_auto_merge_project_merge_request_path(project, merge_request)
end
end
@@ -50,7 +50,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
if user_can_fork_project? && cached_can_be_reverted?
continue_params = {
to: merge_request_path(merge_request),
- notice: "#{edit_in_new_fork_notice} Try to cherry-pick this commit again.",
+ notice: _('%{edit_in_new_fork_notice} Try to cherry-pick this commit again.') % { edit_in_new_fork_notice: edit_in_new_fork_notice },
notice_now: edit_in_new_fork_notice_now
}
@@ -64,7 +64,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
if user_can_fork_project? && can_be_cherry_picked?
continue_params = {
to: merge_request_path(merge_request),
- notice: "#{edit_in_new_fork_notice} Try to revert this commit again.",
+ notice: _('%{edit_in_new_fork_notice} Try to revert this commit again.') % { edit_in_new_fork_notice: edit_in_new_fork_notice },
notice_now: edit_in_new_fork_notice_now
}
@@ -98,6 +98,18 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
+ def target_branch_path
+ if target_branch_exists?
+ project_branch_path(project, target_branch)
+ end
+ end
+
+ def source_branch_commits_path
+ if source_branch_exists?
+ project_commits_path(source_project, source_branch)
+ end
+ end
+
def source_branch_path
if source_branch_exists?
project_branch_path(source_project, source_branch)
@@ -144,8 +156,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
).assignable_issues
path = assign_related_issues_project_merge_request_path(project, merge_request)
if issues.present?
- pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
- link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
+ if issues.count > 1
+ link_to _('Assign yourself to these issues'), path, method: :post
+ else
+ link_to _('Assign yourself to this issue'), path, method: :post
+ end
end
# rubocop: enable CodeReuse/ServiceClass
end
@@ -197,6 +212,26 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
help_page_path('user/project/merge_requests/resolve_conflicts.md')
end
+ def merge_request_pipelines_docs_path
+ help_page_path('ci/merge_request_pipelines/index.md')
+ end
+
+ def source_branch_link
+ if source_branch_exists?
+ link_to(source_branch, source_branch_commits_path, class: 'ref-name')
+ else
+ content_tag(:span, source_branch, class: 'ref-name')
+ end
+ end
+
+ def target_branch_link
+ if target_branch_exists?
+ link_to(target_branch, target_branch_commits_path, class: 'ref-name')
+ else
+ content_tag(:span, target_branch, class: 'ref-name')
+ end
+ end
+
private
def cached_can_be_reverted?
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
index 63e69b91b11..8661ee02b68 100644
--- a/app/presenters/project_clusterable_presenter.rb
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -14,6 +14,11 @@ class ProjectClusterablePresenter < ClusterablePresenter
install_applications_project_cluster_path(clusterable, cluster, application)
end
+ override :update_applications_cluster_path
+ def update_applications_cluster_path(cluster, application)
+ update_applications_project_cluster_path(clusterable, cluster, application)
+ end
+
override :cluster_path
def cluster_path(cluster, params = {})
project_cluster_path(clusterable, cluster, params)
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 4cac90c2567..9afbaf035c7 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -37,16 +37,12 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
gitlab_ci_anchor_data
- ].compact.reject(&:is_link)
+ ].compact.reject(&:is_link).sort_by.with_index { |item, idx| [item.class_modifier ? 0 : 1, idx] }
end
def empty_repo_statistics_anchors
[
- license_anchor_data,
- commits_anchor_data,
- branches_anchor_data,
- tags_anchor_data,
- files_anchor_data
+ license_anchor_data
].compact.select { |item| item.is_link }
end
@@ -55,9 +51,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
new_file_anchor_data,
readme_anchor_data,
changelog_anchor_data,
- contribution_guide_anchor_data,
- autodevops_anchor_data,
- kubernetes_cluster_anchor_data
+ contribution_guide_anchor_data
].compact.reject { |item| item.is_link }
end
@@ -265,7 +259,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout
if auto_devops_enabled?
AnchorData.new(false,
- statistic_icon('doc-text') + _('Auto DevOps enabled'),
+ statistic_icon('settings') + _('Auto DevOps enabled'),
project_settings_ci_cd_path(project, anchor: 'autodevops-settings'),
'default')
else
@@ -315,6 +309,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
project.tag_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
+ def topics_not_shown
+ project.tag_list - topics_to_show
+ end
+
def count_of_extra_topics_not_shown
if project.tag_list.count > MAX_TOPICS_TO_SHOW
project.tag_list.count - MAX_TOPICS_TO_SHOW
diff --git a/app/presenters/tree_entry_presenter.rb b/app/presenters/tree_entry_presenter.rb
new file mode 100644
index 00000000000..7bb10cd1455
--- /dev/null
+++ b/app/presenters/tree_entry_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class TreeEntryPresenter < Gitlab::View::Presenter::Delegated
+ presents :tree
+
+ def web_url
+ Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path))
+ end
+end
diff --git a/app/serializers/acts_as_taggable_on/tag_entity.rb b/app/serializers/acts_as_taggable_on/tag_entity.rb
new file mode 100644
index 00000000000..d4e4b69f8fa
--- /dev/null
+++ b/app/serializers/acts_as_taggable_on/tag_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class ActsAsTaggableOn::TagEntity < Grape::Entity
+ expose :id
+ expose :name
+end
diff --git a/app/serializers/acts_as_taggable_on/tag_serializer.rb b/app/serializers/acts_as_taggable_on/tag_serializer.rb
new file mode 100644
index 00000000000..87f53606aa1
--- /dev/null
+++ b/app/serializers/acts_as_taggable_on/tag_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ActsAsTaggableOn::TagSerializer < BaseSerializer
+ entity ActsAsTaggableOn::TagEntity
+end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index ae7c20c3bba..8bc6da5aeeb 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -9,7 +9,8 @@ class AnalyticsStageEntity < Grape::Entity
expose :description
expose :median, as: :value do |stage|
- # median returns a BatchLoader instance which we first have to unwrap by using to_i
- !stage.median.to_i.zero? ? distance_of_time_in_words(stage.median) : nil
+ # median returns a BatchLoader instance which we first have to unwrap by using to_f
+ # we use to_f to make sure results below 1 are presented to the end-user
+ stage.median.to_f.nonzero? ? distance_of_time_in_words(stage.median) : nil
end
end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 9ddce0d2c80..67e44ee9d10 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -8,16 +8,18 @@ class BuildDetailsEntity < JobEntity
expose :stuck?, as: :stuck
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
+ expose :metadata, using: BuildMetadataEntity
expose :pipeline, using: PipelineEntity
expose :deployment_status, if: -> (*) { build.starts_environment? } do
expose :deployment_status, as: :status
-
- expose :persisted_environment, as: :environment, with: EnvironmentEntity
+ expose :persisted_environment, as: :environment do |build, options|
+ options.merge(deployment_details: false).yield_self do |opts|
+ EnvironmentEntity.represent(build.persisted_environment, opts)
+ end
+ end
end
- expose :metadata, using: BuildMetadataEntity
-
expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
expose :download_path, if: -> (*) { build.artifacts? } do |build|
download_project_job_artifacts_path(project, build)
@@ -40,11 +42,18 @@ class BuildDetailsEntity < JobEntity
end
end
+ expose :report_artifacts,
+ as: :reports,
+ using: JobArtifactReportEntity,
+ if: -> (*) { can?(current_user, :read_build, build) }
+
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build|
erase_project_job_path(project, build)
end
+ expose :failure_reason, if: -> (*) { build.failed? }
+
expose :terminal_path, if: -> (*) { can_create_build_terminal? } do |build|
terminal_project_job_path(project, build)
end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 02df1480828..2a916b13f52 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -6,7 +6,9 @@ class ClusterApplicationEntity < Grape::Entity
expose :status_reason
expose :version
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
+ expose :external_hostname, if: -> (e, _) { e.respond_to?(:external_hostname) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
+ expose :can_uninstall?, as: :can_uninstall
end
diff --git a/app/serializers/concerns/user_status_tooltip.rb b/app/serializers/concerns/user_status_tooltip.rb
index aa6e67e3351..a81e377691e 100644
--- a/app/serializers/concerns/user_status_tooltip.rb
+++ b/app/serializers/concerns/user_status_tooltip.rb
@@ -3,7 +3,7 @@
module UserStatusTooltip
extend ActiveSupport::Concern
include ActionView::Helpers::TagHelper
- include ActionView::Context
+ include ::Gitlab::ActionViewOutput::Context
include EmojiHelper
include UsersHelper
@@ -11,7 +11,7 @@ module UserStatusTooltip
expose :user_status_if_loaded, as: :status_tooltip_html
def user_status_if_loaded
- return nil unless object.association(:status).loaded?
+ return unless object.association(:status).loaded?
user_status(object)
end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 34ae06278c8..943c707218d 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -20,16 +20,39 @@ class DeploymentEntity < Grape::Entity
expose :created_at
expose :tag
expose :last?
-
expose :user, using: UserEntity
- expose :commit, using: CommitEntity
- expose :deployable, using: JobEntity
- expose :manual_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
- expose :scheduled_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
+
+ expose :deployable do |deployment, opts|
+ deployment.deployable.yield_self do |deployable|
+ if include_details?
+ JobEntity.represent(deployable, opts)
+ elsif can_read_deployables?
+ { name: deployable.name,
+ build_path: project_job_path(deployable.project, deployable) }
+ end
+ end
+ end
+
+ expose :commit, using: CommitEntity, if: -> (*) { include_details? }
+ expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
+ expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
private
+ def include_details?
+ options.fetch(:deployment_details, true)
+ end
+
def can_create_deployment?
can?(request.current_user, :create_deployment, request.project)
end
+
+ def can_read_deployables?
+ ##
+ # We intentionally do not check `:read_build, deployment.deployable`
+ # because it triggers a policy evaluation that involves multiple
+ # Gitaly calls that might not be cached.
+ #
+ can?(request.current_user, :read_build, request.project)
+ end
end
diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb
index da994d78286..4f23ef0ed82 100644
--- a/app/serializers/detailed_status_entity.rb
+++ b/app/serializers/detailed_status_entity.rb
@@ -9,16 +9,14 @@ class DetailedStatusEntity < Grape::Entity
expose :details_path
expose :illustration do |status|
- begin
- illustration = {
- image: ActionController::Base.helpers.image_path(status.illustration[:image])
- }
- illustration = status.illustration.merge(illustration)
+ illustration = {
+ image: ActionController::Base.helpers.image_path(status.illustration[:image])
+ }
+ illustration = status.illustration.merge(illustration)
- illustration
- rescue NotImplementedError
- # ignored
- end
+ illustration
+ rescue NotImplementedError
+ # ignored
end
expose :favicon do |status|
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index ede9e04b722..d8630165e49 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -27,9 +27,13 @@ class DiffFileBaseEntity < Grape::Entity
next unless merge_request.source_project
- project_edit_blob_path(merge_request.source_project,
- tree_join(merge_request.source_branch, diff_file.new_path),
- options)
+ if Feature.enabled?(:web_ide_default)
+ ide_edit_path(merge_request.source_project, merge_request.source_branch, diff_file.new_path)
+ else
+ project_edit_blob_path(merge_request.source_project,
+ tree_join(merge_request.source_branch, diff_file.new_path),
+ options)
+ end
end
expose :old_path_html do |diff_file|
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 01ee7af37ed..2a5121a2266 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -7,7 +7,7 @@ class DiffFileEntity < DiffFileBaseEntity
expose :added_lines
expose :removed_lines
- expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.viewer.collapsed? && options[:merge_request] } do |diff_file|
+ expose :load_collapsed_diff_url, if: -> (diff_file, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
project = merge_request.target_project
@@ -57,6 +57,10 @@ class DiffFileEntity < DiffFileBaseEntity
diff_file.diff_lines_for_serializer
end
+ expose :is_fully_expanded do |diff_file|
+ diff_file.fully_expanded?
+ end
+
# Used for parallel diffs
expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? }
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 4a7d13915dd..8258135da4e 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -8,10 +8,11 @@ class EnvironmentEntity < Grape::Entity
expose :state
expose :external_url
expose :environment_type
+ expose :name_without_type
expose :last_deployment, using: DeploymentEntity
expose :stop_action_available?, as: :has_stop_action
- expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
+ expose :metrics_path, if: -> (*) { environment.has_metrics? } do |environment|
metrics_project_environment_path(environment.project, environment)
end
diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb
index 0edab4a3092..19c5fa26f34 100644
--- a/app/serializers/group_variable_entity.rb
+++ b/app/serializers/group_variable_entity.rb
@@ -6,4 +6,5 @@ class GroupVariableEntity < Grape::Entity
expose :value
expose :protected?, as: :protected
+ expose :masked?, as: :masked
end
diff --git a/app/serializers/issuable_sidebar_extras_entity.rb b/app/serializers/issuable_sidebar_extras_entity.rb
index d60253564e1..fb35b7522c5 100644
--- a/app/serializers/issuable_sidebar_extras_entity.rb
+++ b/app/serializers/issuable_sidebar_extras_entity.rb
@@ -11,4 +11,6 @@ class IssuableSidebarExtrasEntity < Grape::Entity
expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project)
end
+
+ expose :assignees, using: API::Entities::UserBasic
end
diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb
index f7719447b92..2e1d7fb3f87 100644
--- a/app/serializers/issue_board_entity.rb
+++ b/app/serializers/issue_board_entity.rb
@@ -2,6 +2,7 @@
class IssueBoardEntity < Grape::Entity
include RequestAwareEntity
+ include TimeTrackableEntity
expose :id
expose :iid
@@ -11,6 +12,7 @@ class IssueBoardEntity < Grape::Entity
expose :due_date
expose :project_id
expose :relative_position
+ expose :time_estimate
expose :project do |issue|
API::Entities::Project.represent issue.project, only: [:id, :path]
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index c3f7d4651fb..36e601f45c5 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -42,6 +42,14 @@ class IssueEntity < IssuableEntity
end
expose :preview_note_path do |issue|
- preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.iid)
+ preview_markdown_path(issue.project, target_type: 'Issue', target_id: issue.iid)
+ end
+
+ expose :confidential_issues_docs_path, if: -> (issue) { issue.confidential? } do |issue|
+ help_page_path('user/project/issues/confidential_issues.md')
+ end
+
+ expose :locked_discussion_docs_path, if: -> (issue) { issue.discussion_locked? } do |issue|
+ help_page_path('user/discussions/index.md', anchor: 'lock-discussions')
end
end
diff --git a/app/serializers/issue_sidebar_extras_entity.rb b/app/serializers/issue_sidebar_extras_entity.rb
index 7b6e860140b..dee891a50b7 100644
--- a/app/serializers/issue_sidebar_extras_entity.rb
+++ b/app/serializers/issue_sidebar_extras_entity.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity
- expose :assignees, using: API::Entities::UserBasic
end
diff --git a/app/serializers/job_artifact_report_entity.rb b/app/serializers/job_artifact_report_entity.rb
new file mode 100644
index 00000000000..4280351a6b0
--- /dev/null
+++ b/app/serializers/job_artifact_report_entity.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class JobArtifactReportEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :file_type
+ expose :file_format
+ expose :size
+
+ expose :download_path do |artifact|
+ download_project_job_artifacts_path(artifact.job.project, artifact.job, file_type: artifact.file_format)
+ end
+end
diff --git a/app/serializers/merge_request_assignee_entity.rb b/app/serializers/merge_request_assignee_entity.rb
new file mode 100644
index 00000000000..6849c62e759
--- /dev/null
+++ b/app/serializers/merge_request_assignee_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class MergeRequestAssigneeEntity < ::API::Entities::UserBasic
+ expose :can_merge do |assignee, options|
+ options[:merge_request]&.can_be_merged_by?(assignee)
+ end
+end
diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb
index 178e72f4f0a..973e971b4c0 100644
--- a/app/serializers/merge_request_basic_entity.rb
+++ b/app/serializers/merge_request_basic_entity.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class MergeRequestBasicEntity < Grape::Entity
- expose :assignee_id
expose :merge_status
expose :merge_error
expose :state
@@ -9,7 +8,7 @@ class MergeRequestBasicEntity < Grape::Entity
expose :rebase_in_progress?, as: :rebase_in_progress
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
- expose :assignee, using: API::Entities::UserBasic
+ expose :assignees, using: API::Entities::UserBasic
expose :task_status, :task_status_short
expose :lock_version, :lock_version
end
diff --git a/app/serializers/merge_request_for_pipeline_entity.rb b/app/serializers/merge_request_for_pipeline_entity.rb
new file mode 100644
index 00000000000..17a5c4ebbf9
--- /dev/null
+++ b/app/serializers/merge_request_for_pipeline_entity.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class MergeRequestForPipelineEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :iid
+
+ expose :path do |merge_request|
+ project_merge_request_path(merge_request.project, merge_request)
+ end
+
+ expose :title
+ expose :source_branch
+ expose :source_branch_commits_path, as: :source_branch_path
+ expose :target_branch
+ expose :target_branch_commits_path, as: :target_branch_path
+end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index 4cf84336aa4..6f589351670 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -8,9 +8,9 @@ class MergeRequestSerializer < BaseSerializer
entity =
case opts[:serializer]
when 'sidebar'
- MergeRequestSidebarBasicEntity
+ IssuableSidebarBasicEntity
when 'sidebar_extras'
- IssuableSidebarExtrasEntity
+ MergeRequestSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity
else
diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb
deleted file mode 100644
index 0ae7298a7c1..00000000000
--- a/app/serializers/merge_request_sidebar_basic_entity.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
- expose :assignee, if: lambda { |issuable| issuable.assignee } do
- expose :assignee, merge: true, using: API::Entities::UserBasic
-
- expose :can_merge do |issuable|
- issuable.can_be_merged_by?(issuable.assignee)
- end
- end
-end
diff --git a/app/serializers/merge_request_sidebar_extras_entity.rb b/app/serializers/merge_request_sidebar_extras_entity.rb
new file mode 100644
index 00000000000..7276509c363
--- /dev/null
+++ b/app/serializers/merge_request_sidebar_extras_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class MergeRequestSidebarExtrasEntity < IssuableSidebarExtrasEntity
+ expose :assignees do |merge_request|
+ MergeRequestAssigneeEntity.represent(merge_request.assignees, merge_request: merge_request)
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 2142ceb6122..a428930dbbf 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -9,7 +9,11 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_params
expose :merge_status
expose :merge_user_id
- expose :merge_when_pipeline_succeeds
+ expose :auto_merge_enabled
+ expose :auto_merge_strategy
+ expose :available_auto_merge_strategies do |merge_request|
+ AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
+ end
expose :source_branch
expose :source_branch_protected do |merge_request|
merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
@@ -20,6 +24,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose :squash
expose :target_branch
+ expose :target_branch_sha
expose :target_project_id
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
@@ -181,8 +186,8 @@ class MergeRequestWidgetEntity < IssuableEntity
presenter(merge_request).remove_wip_path
end
- expose :cancel_merge_when_pipeline_succeeds_path do |merge_request|
- presenter(merge_request).cancel_merge_when_pipeline_succeeds_path
+ expose :cancel_auto_merge_path do |merge_request|
+ presenter(merge_request).cancel_auto_merge_path
end
expose :create_issue_to_resolve_discussions_path do |merge_request|
@@ -234,7 +239,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, quick_actions_target_type: 'MergeRequest', quick_actions_target_id: merge_request.iid)
+ preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
end
expose :merge_commit_path do |merge_request|
@@ -255,6 +260,10 @@ class MergeRequestWidgetEntity < IssuableEntity
presenter(merge_request).conflicts_docs_path
end
+ expose :merge_request_pipelines_docs_path do |merge_request|
+ presenter(merge_request).merge_request_pipelines_docs_path
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb
index d78ad4af4dc..dfef4364965 100644
--- a/app/serializers/pipeline_details_entity.rb
+++ b/app/serializers/pipeline_details_entity.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
class PipelineDetailsEntity < PipelineEntity
+ expose :flags do
+ expose :latest?, as: :latest
+ end
+
expose :details do
- expose :ordered_stages, as: :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 29b1a6c244b..ec2698ecbe3 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -4,6 +4,7 @@ class PipelineEntity < Grape::Entity
include RequestAwareEntity
expose :id
+ expose :iid
expose :user, using: UserEntity
expose :active?, as: :active
@@ -20,22 +21,28 @@ class PipelineEntity < Grape::Entity
end
expose :flags do
- expose :latest?, as: :latest
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
- expose :merge_request?, as: :merge_request
+ expose :merge_request_event?, as: :merge_request
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
expose :failure_reason?, as: :failure_reason
+ expose :detached_merge_request_pipeline?, as: :detached_merge_request_pipeline
+ expose :merge_request_pipeline?, as: :merge_request_pipeline
end
expose :details do
expose :detailed_status, as: :status, with: DetailedStatusEntity
+ expose :ordered_stages, as: :stages, using: StageEntity
expose :duration
expose :finished_at
end
+ expose :merge_request, if: -> (*) { has_presentable_merge_request? }, with: MergeRequestForPipelineEntity do |pipeline|
+ pipeline.merge_request.present(current_user: request.current_user)
+ end
+
expose :ref do
expose :name do |pipeline|
pipeline.ref
@@ -49,10 +56,12 @@ class PipelineEntity < Grape::Entity
expose :tag?, as: :tag
expose :branch?, as: :branch
- expose :merge_request?, as: :merge_request
+ expose :merge_request_event?, as: :merge_request
end
expose :commit, using: CommitEntity
+ expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
+ expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline|
@@ -81,6 +90,11 @@ class PipelineEntity < Grape::Entity
pipeline.cancelable?
end
+ def has_presentable_merge_request?
+ pipeline.triggered_by_merge_request? &&
+ can?(request.current_user, :read_merge_request, pipeline.merge_request)
+ end
+
def detailed_status
pipeline.detailed_status(request.current_user)
end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index 7451433a841..95d73c6422d 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -7,22 +7,7 @@ class PipelineSerializer < BaseSerializer
# rubocop: disable CodeReuse/ActiveRecord
def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation)
- resource = resource.preload([
- :stages,
- :retryable_builds,
- :cancelable_statuses,
- :trigger_requests,
- :manual_actions,
- :scheduled_actions,
- :artifacts,
- {
- pending_builds: :project,
- project: [:route, { namespace: :route }],
- artifacts: {
- project: [:route, { namespace: :route }]
- }
- }
- ])
+ resource = resource.preload(preloaded_relations)
end
if paginated?
@@ -50,4 +35,26 @@ class PipelineSerializer < BaseSerializer
data = represent(resource, { only: [{ details: [:stages] }], preload: true })
data.dig(:details, :stages) || []
end
+
+ private
+
+ def preloaded_relations
+ [
+ :stages,
+ :retryable_builds,
+ :cancelable_statuses,
+ :trigger_requests,
+ :manual_actions,
+ :scheduled_actions,
+ :artifacts,
+ :merge_request,
+ {
+ pending_builds: :project,
+ project: [:route, { namespace: :route }],
+ artifacts: {
+ project: [:route, { namespace: :route }]
+ }
+ }
+ ]
+ end
end
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index c98dc1a1c4a..a46f8af1466 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -32,6 +32,13 @@ module Projects
service.dig('podcount')
end
+ expose :metrics_url do |service|
+ project_serverless_metrics_path(
+ request.project,
+ service.dig('environment_scope'),
+ service.dig('metadata', 'name')) + ".json"
+ end
+
expose :created_at do |service|
service.dig('metadata', 'creationTimestamp')
end
diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb
index 4d0d4da10be..2dd62e19e29 100644
--- a/app/serializers/suggestion_entity.rb
+++ b/app/serializers/suggestion_entity.rb
@@ -3,6 +3,8 @@
class SuggestionEntity < API::Entities::Suggestion
include RequestAwareEntity
+ unexpose :from_line, :to_line, :from_content, :to_content
+ expose :diff_lines, using: DiffLineEntity
expose :current_user do
expose :can_apply do |suggestion|
Ability.allowed?(current_user, :apply_suggestion, suggestion)
diff --git a/app/serializers/suggestion_serializer.rb b/app/serializers/suggestion_serializer.rb
new file mode 100644
index 00000000000..010344f9fcd
--- /dev/null
+++ b/app/serializers/suggestion_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class SuggestionSerializer < BaseSerializer
+ entity SuggestionEntity
+
+ def represent_diff(resource)
+ represent(resource, { only: [:diff_lines] })
+ end
+end
diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb
index ec60055ba5b..5c915c1302c 100644
--- a/app/serializers/test_case_entity.rb
+++ b/app/serializers/test_case_entity.rb
@@ -3,6 +3,7 @@
class TestCaseEntity < Grape::Entity
expose :status
expose :name
+ expose :classname
expose :execution_time
expose :system_output
expose :stack_trace
diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb
index 85cf367fe51..4d48e13cfca 100644
--- a/app/serializers/variable_entity.rb
+++ b/app/serializers/variable_entity.rb
@@ -6,4 +6,5 @@ class VariableEntity < Grape::Entity
expose :value
expose :protected?, as: :protected
+ expose :masked?, as: :masked
end
diff --git a/app/services/after_branch_delete_service.rb b/app/services/after_branch_delete_service.rb
deleted file mode 100644
index e7eb74d3e7d..00000000000
--- a/app/services/after_branch_delete_service.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-##
-# Branch can be deleted either by DeleteBranchService
-# or by GitPushService.
-#
-class AfterBranchDeleteService < BaseService
- attr_reader :branch_name
-
- def execute(branch_name)
- @branch_name = branch_name
-
- stop_environments
- end
-
- private
-
- def stop_environments
- Ci::StopEnvironmentsService
- .new(project, current_user)
- .execute(branch_name)
- end
-end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 2e4643ed668..7eeaf8aade1 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -2,9 +2,17 @@
module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService
+ include ValidatesClassificationLabel
+
attr_reader :params, :application_setting
def execute
+ validate_classification_label(application_setting, :external_authorization_service_default_label)
+
+ if application_setting.errors.any?
+ return false
+ end
+
update_terms(@params.delete(:terms))
if params.key?(:performance_bar_allowed_group_path)
@@ -38,7 +46,7 @@ module ApplicationSettings
def performance_bar_allowed_group_id
performance_bar_enabled = !params.key?(:performance_bar_enabled) || params.delete(:performance_bar_enabled)
group_full_path = params.delete(:performance_bar_allowed_group_path)
- return nil unless Gitlab::Utils.to_boolean(performance_bar_enabled)
+ return unless Gitlab::Utils.to_boolean(performance_bar_enabled)
Group.find_by_full_path(group_full_path)&.id if group_full_path.present?
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index e95ba09c006..707caee482c 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -116,7 +116,7 @@ module Auth
build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
- when '*'
+ when '*', 'delete'
user_can_admin?(requested_project)
else
false
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
new file mode 100644
index 00000000000..058105db3a4
--- /dev/null
+++ b/app/services/auto_merge/base_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module AutoMerge
+ class BaseService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute(merge_request)
+ merge_request.merge_params.merge!(params)
+ merge_request.auto_merge_enabled = true
+ merge_request.merge_user = current_user
+ merge_request.auto_merge_strategy = strategy
+
+ return :failed unless merge_request.save
+
+ yield if block_given?
+
+ strategy.to_sym
+ end
+
+ def cancel(merge_request)
+ if cancel_auto_merge(merge_request)
+ yield if block_given?
+
+ success
+ else
+ error("Can't cancel the automatic merge", 406)
+ end
+ end
+
+ private
+
+ def strategy
+ strong_memoize(:strategy) do
+ self.class.name.demodulize.remove('Service').underscore
+ end
+ end
+
+ def cancel_auto_merge(merge_request)
+ merge_request.auto_merge_enabled = false
+ merge_request.merge_user = nil
+
+ merge_request.merge_params&.except!(
+ 'should_remove_source_branch',
+ 'commit_message',
+ 'squash_commit_message',
+ 'auto_merge_strategy'
+ )
+
+ merge_request.save
+ end
+ end
+end
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
new file mode 100644
index 00000000000..c41073a73e9
--- /dev/null
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module AutoMerge
+ class MergeWhenPipelineSucceedsService < AutoMerge::BaseService
+ def execute(merge_request)
+ super do
+ if merge_request.saved_change_to_auto_merge_enabled?
+ SystemNoteService.merge_when_pipeline_succeeds(merge_request, project, current_user, merge_request.diff_head_commit)
+ end
+ end
+ end
+
+ def process(merge_request)
+ return unless merge_request.actual_head_pipeline&.success?
+ return unless merge_request.mergeable?
+
+ merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
+ end
+
+ def cancel(merge_request)
+ super do
+ SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
+ end
+ end
+
+ def available_for?(merge_request)
+ merge_request.actual_head_pipeline&.active?
+ end
+ end
+end
diff --git a/app/services/auto_merge_service.rb b/app/services/auto_merge_service.rb
new file mode 100644
index 00000000000..a3a780ff388
--- /dev/null
+++ b/app/services/auto_merge_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class AutoMergeService < BaseService
+ STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'.freeze
+ STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
+
+ class << self
+ def all_strategies
+ STRATEGIES
+ end
+
+ def get_service_class(strategy)
+ return unless all_strategies.include?(strategy)
+
+ "::AutoMerge::#{strategy.camelize}Service".constantize
+ end
+ end
+
+ def execute(merge_request, strategy)
+ service = get_service_instance(strategy)
+
+ return :failed unless service&.available_for?(merge_request)
+
+ service.execute(merge_request)
+ end
+
+ def process(merge_request)
+ return unless merge_request.auto_merge_enabled?
+
+ get_service_instance(merge_request.auto_merge_strategy).process(merge_request)
+ end
+
+ def cancel(merge_request)
+ return error("Can't cancel the automatic merge", 406) unless merge_request.auto_merge_enabled?
+
+ get_service_instance(merge_request.auto_merge_strategy).cancel(merge_request)
+ end
+
+ def available_strategies(merge_request)
+ self.class.all_strategies.select do |strategy|
+ get_service_instance(strategy).available_for?(merge_request)
+ end
+ end
+
+ private
+
+ def get_service_instance(strategy)
+ self.class.get_service_class(strategy)&.new(project, current_user, params)
+ end
+end
diff --git a/app/services/boards/visits/latest_service.rb b/app/services/boards/visits/latest_service.rb
index 9e4c77a6317..d13e25b4f12 100644
--- a/app/services/boards/visits/latest_service.rb
+++ b/app/services/boards/visits/latest_service.rb
@@ -4,13 +4,15 @@ module Boards
module Visits
class LatestService < Boards::BaseService
def execute
- return nil unless current_user
+ return unless current_user
- if parent.is_a?(Group)
- BoardGroupRecentVisit.latest(current_user, parent)
- else
- BoardProjectRecentVisit.latest(current_user, parent)
- end
+ recent_visit_model.latest(current_user, parent, count: params[:count])
+ end
+
+ private
+
+ def recent_visit_model
+ parent.is_a?(Group) ? BoardGroupRecentVisit : BoardProjectRecentVisit
end
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 35a0efcd0a1..c17712355af 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -25,7 +25,9 @@ module Ci
origin_ref: params[:ref],
checkout_sha: params[:checkout_sha],
after_sha: params[:after],
- before_sha: params[:before],
+ before_sha: params[:before], # The base SHA of the source branch (i.e merge_request.diff_base_sha).
+ source_sha: params[:source_sha], # The HEAD SHA of the source branch (i.e merge_request.diff_head_sha).
+ target_sha: params[:target_sha], # The HEAD SHA of the target branch.
trigger_request: trigger_request,
schedule: schedule,
merge_request: merge_request,
@@ -35,7 +37,7 @@ module Ci
variables_attributes: params[:variables_attributes],
project: project,
current_user: current_user,
- push_options: params[:push_options],
+ push_options: params[:push_options] || {},
chat_data: params[:chat_data],
**extra_options(options))
@@ -53,6 +55,10 @@ module Ci
end
end
+ # If pipeline is not persisted, try to recover IID
+ pipeline.reset_project_iid unless pipeline.persisted? ||
+ Feature.disabled?(:ci_pipeline_rewind_iid, project, default_enabled: true)
+
pipeline
end
@@ -98,17 +104,11 @@ module Ci
end
def schedule_head_pipeline_update
- related_merge_requests.each do |merge_request|
+ pipeline.all_merge_requests.opened.each do |merge_request|
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
end
- # rubocop: disable CodeReuse/ActiveRecord
- def related_merge_requests
- pipeline.project.source_of_merge_requests.opened.where(source_branch: pipeline.ref)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def extra_options(options = {})
# In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
# doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
index 5c4a34043c1..9aea20c45f7 100644
--- a/app/services/ci/destroy_pipeline_service.rb
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -5,6 +5,8 @@ module Ci
def execute(pipeline)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline)
+ Ci::ExpirePipelineCacheService.new.execute(pipeline, delete: true)
+
pipeline.destroy!
end
end
diff --git a/app/services/ci/expire_pipeline_cache_service.rb b/app/services/ci/expire_pipeline_cache_service.rb
new file mode 100644
index 00000000000..d8d38128af6
--- /dev/null
+++ b/app/services/ci/expire_pipeline_cache_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Ci
+ class ExpirePipelineCacheService
+ def execute(pipeline, delete: false)
+ store = Gitlab::EtagCaching::Store.new
+
+ update_etag_cache(pipeline, store)
+
+ if delete
+ Gitlab::Cache::Ci::ProjectPipelineStatus.new(pipeline.project).delete_from_cache
+ else
+ Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
+ end
+ end
+
+ private
+
+ def project_pipelines_path(project)
+ Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
+ end
+
+ def project_pipeline_path(project, pipeline)
+ Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
+ end
+
+ def commit_pipelines_path(project, commit)
+ Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json)
+ end
+
+ def new_merge_request_pipelines_path(project)
+ Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
+ end
+
+ def each_pipelines_merge_request_path(pipeline)
+ pipeline.all_merge_requests.each do |merge_request|
+ path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(merge_request.target_project, merge_request, format: :json)
+
+ yield(path)
+ end
+ end
+
+ # Updates ETag caches of a pipeline.
+ #
+ # This logic resides in a separate method so that EE can more easily extend
+ # it.
+ #
+ # @param [Ci::Pipeline] pipeline
+ # @param [Gitlab::EtagCaching::Store] store
+ def update_etag_cache(pipeline, store)
+ project = pipeline.project
+
+ store.touch(project_pipelines_path(project))
+ store.touch(project_pipeline_path(project, pipeline))
+ store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
+ store.touch(new_merge_request_pipelines_path(project))
+ each_pipelines_merge_request_path(pipeline) do |path|
+ store.touch(path)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_schedule_service.rb b/app/services/ci/pipeline_schedule_service.rb
new file mode 100644
index 00000000000..387d0351490
--- /dev/null
+++ b/app/services/ci/pipeline_schedule_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineScheduleService < BaseService
+ def execute(schedule)
+ # Ensure `next_run_at` is set properly before creating a pipeline.
+ # Otherwise, multiple pipelines could be created in a short interval.
+ schedule.schedule_next_run!
+
+ RunPipelineScheduleWorker.perform_async(schedule.id, schedule.owner.id)
+ end
+ end
+end
diff --git a/app/services/ci/play_manual_stage_service.rb b/app/services/ci/play_manual_stage_service.rb
new file mode 100644
index 00000000000..2497fc52e6b
--- /dev/null
+++ b/app/services/ci/play_manual_stage_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Ci
+ class PlayManualStageService < BaseService
+ def initialize(project, current_user, params)
+ super
+
+ @pipeline = params[:pipeline]
+ end
+
+ def execute(stage)
+ stage.builds.manual.each do |build|
+ next unless build.playable?
+
+ build.play(current_user)
+ rescue Gitlab::Access::AccessDeniedError
+ logger.error(message: 'Unable to play manual action', build_id: build.id)
+ end
+ end
+
+ private
+
+ attr_reader :pipeline, :current_user
+
+ def logger
+ Gitlab::AppLogger
+ end
+ end
+end
diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb
new file mode 100644
index 00000000000..3722faeb020
--- /dev/null
+++ b/app/services/ci/prepare_build_service.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Ci
+ class PrepareBuildService
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def execute
+ prerequisites.each(&:complete!)
+
+ build.enqueue!
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { build_id: build.id })
+
+ build.drop(:unmet_prerequisites)
+ end
+
+ private
+
+ def prerequisites
+ build.prerequisites
+ end
+ end
+end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 6707a1363d0..dedab98b56d 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -6,7 +6,7 @@ module Ci
class RegisterJobService
attr_reader :runner
- JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
Result = Struct.new(:build, :valid?)
@@ -36,6 +36,11 @@ module Ci
builds = builds.with_any_tags
end
+ # pick builds that older than specified age
+ if params.key?(:job_age)
+ builds = builds.queued_before(params[:job_age].seconds.ago)
+ end
+
builds.each do |build|
next unless runner.can_pick?(build)
diff --git a/app/services/ci/stop_environments_service.rb b/app/services/ci/stop_environments_service.rb
index 973ae5ce5aa..d9a800791f2 100644
--- a/app/services/ci/stop_environments_service.rb
+++ b/app/services/ci/stop_environments_service.rb
@@ -9,12 +9,11 @@ module Ci
return unless @ref.present?
- environments.each do |environment|
- next unless environment.stop_action_available?
- next unless can?(current_user, :stop_environment, environment)
+ environments.each { |environment| stop(environment) }
+ end
- environment.stop_with_action!(current_user)
- end
+ def execute_for_merge_request(merge_request)
+ merge_request.environments.each { |environment| stop(environment) }
end
private
@@ -24,5 +23,12 @@ module Ci
.new(project, current_user, ref: @ref, recently_updated: true)
.execute
end
+
+ def stop(environment)
+ return unless environment.stop_action_available?
+ return unless can?(current_user, :stop_environment, environment)
+
+ environment.stop_with_action!(current_user)
+ end
end
end
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index 8a71730d5ec..3e7f55f0c63 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -13,19 +13,37 @@ module Clusters
def log_error(error)
meta = {
- exception: error.class.name,
error_code: error.respond_to?(:error_code) ? error.error_code : nil,
service: self.class.name,
app_id: app.id,
+ app_name: app.name,
project_ids: app.cluster.project_ids,
- group_ids: app.cluster.group_ids,
- message: error.message
+ group_ids: app.cluster.group_ids
}
- logger.error(meta)
+ logger_meta = meta.merge(
+ exception: error.class.name,
+ message: error.message,
+ backtrace: Gitlab::Profiler.clean_backtrace(error.backtrace)
+ )
+
+ logger.error(logger_meta)
Gitlab::Sentry.track_acceptable_exception(error, extra: meta)
end
+ def log_event(event)
+ meta = {
+ service: self.class.name,
+ app_id: app.id,
+ app_name: app.name,
+ project_ids: app.cluster.project_ids,
+ group_ids: app.cluster.group_ids,
+ event: event
+ }
+
+ logger.info(meta)
+ end
+
def logger
@logger ||= Gitlab::Kubernetes::Logger.build
end
@@ -46,6 +64,10 @@ module Clusters
@install_command ||= app.install_command
end
+ def update_command
+ @update_command ||= app.update_command
+ end
+
def upgrade_command(new_values = "")
app.upgrade_command(new_values)
end
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
new file mode 100644
index 00000000000..a9feb60be6e
--- /dev/null
+++ b/app/services/clusters/applications/base_service.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class BaseService
+ InvalidApplicationError = Class.new(StandardError)
+
+ attr_reader :cluster, :current_user, :params
+
+ def initialize(cluster, user, params = {})
+ @cluster = cluster
+ @current_user = user
+ @params = params.dup
+ end
+
+ def execute(request)
+ instantiate_application.tap do |application|
+ if application.has_attribute?(:hostname)
+ application.hostname = params[:hostname]
+ end
+
+ if application.has_attribute?(:email)
+ application.email = params[:email]
+ end
+
+ if application.respond_to?(:oauth_application)
+ application.oauth_application = create_oauth_application(application, request)
+ end
+
+ worker = worker_class(application)
+
+ application.make_scheduled!
+
+ worker.perform_async(application.name, application.id)
+ end
+ end
+
+ protected
+
+ def worker_class(application)
+ raise NotImplementedError
+ end
+
+ def builder
+ raise NotImplementedError
+ end
+
+ def project_builders
+ raise NotImplementedError
+ end
+
+ def instantiate_application
+ raise_invalid_application_error if invalid_application?
+
+ builder || raise(InvalidApplicationError, "invalid application: #{application_name}")
+ end
+
+ def raise_invalid_application_error
+ raise(InvalidApplicationError, "invalid application: #{application_name}")
+ end
+
+ def invalid_application?
+ unknown_application? || (!cluster.project_type? && project_only_application?)
+ end
+
+ def unknown_application?
+ Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name)
+ end
+
+ # These applications will need extra configuration to enable them to work
+ # with groups of projects
+ def project_only_application?
+ Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name)
+ end
+
+ def application_name
+ params[:application]
+ end
+
+ def create_oauth_application(application, request)
+ oauth_application_params = {
+ name: params[:application],
+ redirect_uri: application.callback_url,
+ scopes: application.oauth_scopes,
+ owner: current_user
+ }
+
+ ::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/check_ingress_ip_address_service.rb b/app/services/clusters/applications/check_ingress_ip_address_service.rb
index 0ec06e776a7..e254a0358a0 100644
--- a/app/services/clusters/applications/check_ingress_ip_address_service.rb
+++ b/app/services/clusters/applications/check_ingress_ip_address_service.rb
@@ -11,9 +11,13 @@ module Clusters
def execute
return if app.external_ip
+ return if app.external_hostname
return unless try_obtain_lease
- app.update!(external_ip: ingress_ip) if ingress_ip
+ app.external_ip = ingress_ip if ingress_ip
+ app.external_hostname = ingress_hostname if ingress_hostname
+
+ app.save! if app.changed?
end
private
@@ -25,12 +29,16 @@ module Clusters
end
def ingress_ip
- service.status.loadBalancer.ingress&.first&.ip
+ ingress_service&.ip
+ end
+
+ def ingress_hostname
+ ingress_service&.hostname
end
- def service
+ def ingress_service
strong_memoize(:ingress_service) do
- app.ingress_service
+ app.ingress_service.status.loadBalancer.ingress&.first
end
end
end
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index c592d608b89..3c6803d24e6 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -37,7 +37,7 @@ module Clusters
end
def check_timeout
- if timeouted?
+ if timed_out?
begin
app.make_errored!("Operation timed out. Check pod logs for #{pod_name} for more details.")
end
@@ -51,8 +51,8 @@ module Clusters
install_command.pod_name
end
- def timeouted?
- Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
+ def timed_out?
+ Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
end
def remove_installation_pod
diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb
new file mode 100644
index 00000000000..8786d295d6a
--- /dev/null
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class CheckUninstallProgressService < BaseHelmService
+ def execute
+ return unless app.uninstalling?
+
+ case installation_phase
+ when Gitlab::Kubernetes::Pod::SUCCEEDED
+ on_success
+ when Gitlab::Kubernetes::Pod::FAILED
+ on_failed
+ else
+ check_timeout
+ end
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ end
+
+ private
+
+ def on_success
+ app.destroy!
+ rescue StandardError => e
+ app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message })
+ ensure
+ remove_installation_pod
+ end
+
+ def on_failed
+ app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
+ end
+
+ def check_timeout
+ if timed_out?
+ app.make_errored!(_('Operation timed out. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
+ else
+ WaitForUninstallAppWorker.perform_in(WaitForUninstallAppWorker::INTERVAL, app.name, app.id)
+ end
+ end
+
+ def pod_name
+ app.uninstall_command.pod_name
+ end
+
+ def timed_out?
+ Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT
+ end
+
+ def remove_installation_pod
+ helm_api.delete_pod!(pod_name)
+ end
+
+ def installation_phase
+ helm_api.status(pod_name)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
index 92c2c1b9834..f723c42c049 100644
--- a/app/services/clusters/applications/create_service.rb
+++ b/app/services/clusters/applications/create_service.rb
@@ -2,81 +2,16 @@
module Clusters
module Applications
- class CreateService
- InvalidApplicationError = Class.new(StandardError)
-
- attr_reader :cluster, :current_user, :params
-
- def initialize(cluster, user, params = {})
- @cluster = cluster
- @current_user = user
- @params = params.dup
- end
-
- def execute(request)
- create_application.tap do |application|
- if application.has_attribute?(:hostname)
- application.hostname = params[:hostname]
- end
-
- if application.has_attribute?(:email)
- application.email = params[:email]
- end
-
- if application.respond_to?(:oauth_application)
- application.oauth_application = create_oauth_application(application, request)
- end
-
- application.save!
-
- Clusters::Applications::ScheduleInstallationService.new(application).execute
- end
- end
-
+ class CreateService < Clusters::Applications::BaseService
private
- def create_application
- builder.call(@cluster)
+ def worker_class(application)
+ application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker
end
def builder
- builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
- end
-
- def builders
- {
- "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm },
- "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
- "cert_manager" => -> (cluster) { cluster.application_cert_manager || cluster.build_application_cert_manager }
- }.tap do |hash|
- hash.merge!(project_builders) if cluster.project_type?
- end
- end
-
- # These applications will need extra configuration to enable them to work
- # with groups of projects
- def project_builders
- {
- "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
- "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
- "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter },
- "knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative }
- }
- end
-
- def application_name
- params[:application]
- end
-
- def create_oauth_application(application, request)
- oauth_application_params = {
- name: params[:application],
- redirect_uri: application.callback_url,
- scopes: 'api read_user openid',
- owner: current_user
- }
-
- ::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ cluster.public_send(:"application_#{application_name}") || # rubocop:disable GitlabSecurity/PublicSend
+ cluster.public_send(:"build_application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/app/services/clusters/applications/destroy_service.rb b/app/services/clusters/applications/destroy_service.rb
new file mode 100644
index 00000000000..f3a4c4f754a
--- /dev/null
+++ b/app/services/clusters/applications/destroy_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class DestroyService < ::Clusters::Applications::BaseService
+ def execute(_request)
+ instantiate_application.tap do |application|
+ break unless application.can_uninstall?
+
+ application.make_scheduled!
+
+ Clusters::Applications::UninstallWorker.perform_async(application.name, application.id)
+ end
+ end
+
+ private
+
+ def builder
+ cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index 5a65dc4ef59..dffb4ce65ab 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -6,19 +6,26 @@ module Clusters
def execute
return unless app.scheduled?
- begin
- app.make_installing!
- helm_api.install(install_command)
+ app.make_installing!
- ClusterWaitForAppInstallationWorker.perform_in(
- ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
- rescue Kubeclient::HttpError => e
- log_error(e)
- app.make_errored!("Kubernetes error: #{e.error_code}")
- rescue StandardError => e
- log_error(e)
- app.make_errored!("Can't start installation process.")
- end
+ install
+ end
+
+ private
+
+ def install
+ log_event(:begin_install)
+ helm_api.install(install_command)
+
+ log_event(:schedule_wait_for_installation)
+ ClusterWaitForAppInstallationWorker.perform_in(
+ ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ rescue StandardError => e
+ log_error(e)
+ app.make_errored!(_('Failed to install.'))
end
end
end
diff --git a/app/services/clusters/applications/patch_service.rb b/app/services/clusters/applications/patch_service.rb
new file mode 100644
index 00000000000..fbea18bae6b
--- /dev/null
+++ b/app/services/clusters/applications/patch_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class PatchService < BaseHelmService
+ def execute
+ return unless app.scheduled?
+
+ app.make_updating!
+
+ patch
+ end
+
+ private
+
+ def patch
+ log_event(:begin_patch)
+ helm_api.update(update_command)
+
+ log_event(:schedule_wait_for_patch)
+ ClusterWaitForAppInstallationWorker.perform_in(
+ ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ rescue StandardError => e
+ log_error(e)
+ app.make_errored!(_('Failed to update.'))
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb
deleted file mode 100644
index 15c93f1e79b..00000000000
--- a/app/services/clusters/applications/schedule_installation_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Applications
- class ScheduleInstallationService
- attr_reader :application
-
- def initialize(application)
- @application = application
- end
-
- def execute
- application.updateable? ? schedule_upgrade : schedule_install
- end
-
- private
-
- def schedule_upgrade
- application.make_scheduled!
-
- ClusterUpgradeAppWorker.perform_async(application.name, application.id)
- end
-
- def schedule_install
- application.make_scheduled!
-
- ClusterInstallAppWorker.perform_async(application.name, application.id)
- end
- end
- end
-end
diff --git a/app/services/clusters/applications/uninstall_service.rb b/app/services/clusters/applications/uninstall_service.rb
new file mode 100644
index 00000000000..50c8d806c14
--- /dev/null
+++ b/app/services/clusters/applications/uninstall_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class UninstallService < BaseHelmService
+ def execute
+ return unless app.scheduled?
+
+ app.make_uninstalling!
+ uninstall
+ end
+
+ private
+
+ def uninstall
+ helm_api.uninstall(app.uninstall_command)
+
+ Clusters::Applications::WaitForUninstallAppWorker.perform_in(
+ Clusters::Applications::WaitForUninstallAppWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_errored!("Kubernetes error: #{e.error_code}")
+ rescue StandardError => e
+ log_error(e)
+ app.make_errored!('Failed to uninstall.')
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/update_service.rb b/app/services/clusters/applications/update_service.rb
new file mode 100644
index 00000000000..0fa937da865
--- /dev/null
+++ b/app/services/clusters/applications/update_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class UpdateService < Clusters::Applications::BaseService
+ private
+
+ def worker_class(application)
+ ClusterPatchAppWorker
+ end
+
+ def builder
+ cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/upgrade_service.rb b/app/services/clusters/applications/upgrade_service.rb
index a0ece1d2635..ac68e64af38 100644
--- a/app/services/clusters/applications/upgrade_service.rb
+++ b/app/services/clusters/applications/upgrade_service.rb
@@ -6,22 +6,28 @@ module Clusters
def execute
return unless app.scheduled?
- begin
- app.make_updating!
+ app.make_updating!
- # install_command works with upgrades too
- # as it basically does `helm upgrade --install`
- helm_api.update(install_command)
+ upgrade
+ end
+
+ private
+
+ def upgrade
+ # install_command works with upgrades too
+ # as it basically does `helm upgrade --install`
+ log_event(:begin_upgrade)
+ helm_api.update(install_command)
- ClusterWaitForAppInstallationWorker.perform_in(
- ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
- rescue Kubeclient::HttpError => e
- log_error(e)
- app.make_update_errored!("Kubernetes error: #{e.error_code}")
- rescue StandardError => e
- log_error(e)
- app.make_update_errored!("Can't start upgrade process.")
- end
+ log_event(:schedule_wait_for_upgrade)
+ ClusterWaitForAppInstallationWorker.perform_in(
+ ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ rescue StandardError => e
+ log_error(e)
+ app.make_errored!(_('Failed to upgrade.'))
end
end
end
diff --git a/app/services/clusters/build_service.rb b/app/services/clusters/build_service.rb
index 8de73831164..b1ac5549e30 100644
--- a/app/services/clusters/build_service.rb
+++ b/app/services/clusters/build_service.rb
@@ -12,6 +12,8 @@ module Clusters
cluster.cluster_type = :project_type
when ::Group
cluster.cluster_type = :group_type
+ when Instance
+ cluster.cluster_type = :instance_type
else
raise NotImplementedError
end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 5a9da053780..886e484caaf 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -38,6 +38,8 @@ module Clusters
{ cluster_type: :project_type, projects: [clusterable] }
when ::Group
{ cluster_type: :group_type, groups: [clusterable] }
+ when Instance
+ { cluster_type: :instance_type }
else
raise NotImplementedError
end
diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb
index 7c82b98a33f..3752a306793 100644
--- a/app/services/clusters/refresh_service.rb
+++ b/app/services/clusters/refresh_service.rb
@@ -21,7 +21,7 @@ module Clusters
private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster
def self.clusters_with_missing_kubernetes_namespaces_for_project(project)
- project.all_clusters.missing_kubernetes_namespace(project.kubernetes_namespaces)
+ project.clusters.managed.missing_kubernetes_namespace(project.kubernetes_namespaces)
end
private_class_method :clusters_with_missing_kubernetes_namespaces_for_project
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index 34593e12bd5..bb34a3d3352 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -11,6 +11,7 @@ module Commits
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
@branch_name = params[:branch_name]
+ @force = params[:force] || false
end
def execute
@@ -42,10 +43,14 @@ module Commits
@start_branch != @branch_name || @start_project != @project
end
+ def force?
+ !!@force
+ end
+
def validate!
validate_permissions!
validate_on_branch!
- validate_branch_existance!
+ validate_branch_existence!
validate_new_branch_name! if different_branch?
end
@@ -64,14 +69,14 @@ module Commits
end
end
- def validate_branch_existance!
- if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name)
+ def validate_branch_existence!
+ if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name) && !force?
raise_error("A branch called '#{@branch_name}' already exists. Switch to that branch in order to make changes")
end
end
def validate_new_branch_name!
- result = ValidateNewBranchService.new(project, current_user).execute(@branch_name)
+ result = ValidateNewBranchService.new(project, current_user).execute(@branch_name, force: force?)
if result[:status] == :error
raise_error("Something went wrong when we tried to create '#{@branch_name}' for you: #{result[:message]}")
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 3adf8a0c1a1..3f0aedfbfb2 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,7 +3,7 @@
require 'securerandom'
# Compare 2 refs for one repo or between repositories
-# and return Gitlab::Git::Compare object that responds to commits and diffs
+# and return Compare object that responds to commits and diffs
class CompareService
attr_reader :start_project, :start_ref_name
@@ -15,7 +15,7 @@ class CompareService
def execute(target_project, target_ref, base_sha: nil, straight: false)
raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)
- return unless raw_compare
+ return unless raw_compare && raw_compare.base && raw_compare.head
Compare.new(raw_compare,
target_project,
diff --git a/app/services/concerns/suggestible.rb b/app/services/concerns/suggestible.rb
new file mode 100644
index 00000000000..0cba9bf1b8a
--- /dev/null
+++ b/app/services/concerns/suggestible.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Suggestible
+ extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
+
+ # This translates into limiting suggestion changes to `suggestion:-100+100`.
+ MAX_LINES_CONTEXT = 100.freeze
+
+ def diff_lines
+ strong_memoize(:diff_lines) do
+ Gitlab::Diff::SuggestionDiff.new(self).diff_lines
+ end
+ end
+
+ def fetch_from_content
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ def from_line
+ real_above = [lines_above, MAX_LINES_CONTEXT].min
+ [target_line - real_above, 1].max
+ end
+
+ def to_line
+ real_below = [lines_below, MAX_LINES_CONTEXT].min
+ target_line + real_below
+ end
+
+ def diff_file
+ raise NotImplementedError
+ end
+
+ def target_line
+ raise NotImplementedError
+ end
+end
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 6713b6617ae..1c828234f1b 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -28,19 +28,35 @@ module Users
end
def groups
- current_user.authorized_groups.sort_by(&:path).map do |group|
- group_as_hash(group)
+ group_counts = GroupMember
+ .of_groups(current_user.authorized_groups)
+ .non_request
+ .count_users_by_group_id
+
+ current_user.authorized_groups.with_route.sort_by(&:path).map do |group|
+ group_as_hash(group, group_counts)
end
end
private
def user_as_hash(user)
- { type: user.class.name, username: user.username, name: user.name, avatar_url: user.avatar_url }
+ {
+ type: user.class.name,
+ username: user.username,
+ name: user.name,
+ avatar_url: user.avatar_url
+ }
end
- def group_as_hash(group)
- { type: group.class.name, username: group.full_path, name: group.full_name, avatar_url: group.avatar_url, count: group.users.count }
+ def group_as_hash(group, group_counts)
+ {
+ type: group.class.name,
+ username: group.full_path,
+ name: group.full_name,
+ avatar_url: group.avatar_url,
+ count: group_counts.fetch(group.id, 0)
+ }
end
end
end
diff --git a/app/services/concerns/validates_classification_label.rb b/app/services/concerns/validates_classification_label.rb
new file mode 100644
index 00000000000..ebcf5c24ff8
--- /dev/null
+++ b/app/services/concerns/validates_classification_label.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module ValidatesClassificationLabel
+ def validate_classification_label(record, attribute_name)
+ return unless ::Gitlab::ExternalAuthorization.enabled?
+ return unless classification_label_change?(record, attribute_name)
+
+ new_label = params[attribute_name].presence
+ new_label ||= ::Gitlab::CurrentSettings.current_application_settings
+ .external_authorization_service_default_label
+
+ unless ::Gitlab::ExternalAuthorization.access_allowed?(current_user, new_label)
+ reason = rejection_reason_for_label(new_label)
+ message = s_('ClassificationLabelUnavailable|is unavailable: %{reason}') % { reason: reason }
+ record.errors.add(attribute_name, message)
+ end
+ end
+
+ def rejection_reason_for_label(label)
+ reason_from_service = ::Gitlab::ExternalAuthorization.rejection_reason(current_user, label).presence
+ reason_from_service || _("Access to '%{classification_label}' not allowed") % { classification_label: label }
+ end
+
+ def classification_label_change?(record, attribute_name)
+ params.key?(attribute_name) || record.new_record?
+ end
+end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 8322a3d74f4..fd41ce54486 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -6,37 +6,25 @@ class DeleteBranchService < BaseService
branch = repository.find_branch(branch_name)
unless current_user.can?(:push_code, project)
- return error('You dont have push access to repo', 405)
+ return ServiceResponse.error(
+ message: 'You dont have push access to repo',
+ http_status: 405)
end
unless branch
- return error('No such branch', 404)
+ return ServiceResponse.error(
+ message: 'No such branch',
+ http_status: 404)
end
if repository.rm_branch(current_user, branch_name)
- success('Branch was deleted')
+ ServiceResponse.success(message: 'Branch was deleted')
else
- error('Failed to remove branch')
+ ServiceResponse.error(
+ message: 'Failed to remove branch',
+ http_status: 400)
end
rescue Gitlab::Git::PreReceiveError => ex
- error(ex.message)
- end
-
- def error(message, return_code = 400)
- super(message).merge(return_code: return_code)
- end
-
- def success(message)
- super().merge(message: message)
- end
-
- def build_push_data(branch)
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- branch.dereferenced_target.sha,
- Gitlab::Git::BLANK_SHA,
- "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}",
- [])
+ ServiceResponse.error(message: ex.message, http_status: 400)
end
end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
index a6c6bec9598..86ab21fa865 100644
--- a/app/services/error_tracking/list_issues_service.rb
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -18,7 +18,7 @@ module ErrorTracking
end
if result[:error].present?
- return error(result[:error], :bad_request)
+ return error(result[:error], http_status_from_error_type(result[:error_type]))
end
success(issues: result[:issues])
@@ -30,6 +30,15 @@ module ErrorTracking
private
+ def http_status_from_error_type(error_type)
+ case error_type
+ when ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ :internal_server_error
+ else
+ :bad_request
+ end
+ end
+
def project_error_tracking_setting
project.error_tracking_setting
end
diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb
index c6e8be0f2be..8d08f0cda94 100644
--- a/app/services/error_tracking/list_projects_service.rb
+++ b/app/services/error_tracking/list_projects_service.rb
@@ -15,8 +15,8 @@ module ErrorTracking
result = setting.list_sentry_projects
rescue Sentry::Client::Error => e
return error(e.message, :bad_request)
- rescue Sentry::Client::SentryError => e
- return error(e.message, :unprocessable_entity)
+ rescue Sentry::Client::MissingKeysError => e
+ return error(e.message, :internal_server_error)
end
success(projects: result[:projects])
@@ -28,8 +28,8 @@ module ErrorTracking
(project.error_tracking_setting || project.build_error_tracking_setting).tap do |setting|
setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
api_host: params[:api_host],
- organization_slug: nil,
- project_slug: nil
+ organization_slug: 'org',
+ project_slug: 'proj'
)
setting.token = params[:token]
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 0ec1f79d396..f47eb4fccd4 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -20,7 +20,7 @@ module Files
super
if file_has_changed?(@file_path, @last_commit_sha)
- raise FileChangedError, "You are attempting to delete a file that has been previously updated."
+ raise FileChangedError, _("You are attempting to delete a file that has been previously updated.")
end
end
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index 927634c2159..c1bc26c330a 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -46,7 +46,8 @@ module Files
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
- start_branch_name: @start_branch
+ start_branch_name: @start_branch,
+ force: force?
)
rescue ArgumentError => e
raise_error(e)
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 2b3e96e6c53..54ab07da680 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -19,7 +19,7 @@ module Files
super
if file_has_changed?(@file_path, @last_commit_sha)
- raise FileChangedError, "You are attempting to update a file that has changed since you started editing it."
+ raise FileChangedError, _('You are attempting to update a file that has changed since you started editing it.')
end
end
end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
new file mode 100644
index 00000000000..d30df34e54b
--- /dev/null
+++ b/app/services/git/base_hooks_service.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module Git
+ class BaseHooksService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ # The N most recent commits to process in a single push payload.
+ PROCESS_COMMIT_LIMIT = 100
+
+ def execute
+ project.repository.after_create if project.empty_repo?
+
+ create_events
+ create_pipelines
+ execute_project_hooks
+
+ # Not a hook, but it needs access to the list of changed commits
+ enqueue_invalidate_cache
+
+ update_remote_mirrors
+
+ push_data
+ end
+
+ private
+
+ def hook_name
+ raise NotImplementedError, "Please implement #{self.class}##{__method__}"
+ end
+
+ def commits
+ raise NotImplementedError, "Please implement #{self.class}##{__method__}"
+ end
+
+ def limited_commits
+ commits.last(PROCESS_COMMIT_LIMIT)
+ end
+
+ def commits_count
+ commits.count
+ end
+
+ def event_message
+ nil
+ end
+
+ def invalidated_file_types
+ []
+ end
+
+ def create_events
+ EventCreateService.new.push(project, current_user, push_data)
+ end
+
+ def create_pipelines
+ return unless params.fetch(:create_pipelines, true)
+
+ Ci::CreatePipelineService
+ .new(project, current_user, push_data)
+ .execute(:push, pipeline_options)
+ end
+
+ def execute_project_hooks
+ project.execute_hooks(push_data, hook_name)
+ project.execute_services(push_data, hook_name)
+ end
+
+ def enqueue_invalidate_cache
+ ProjectCacheWorker.perform_async(
+ project.id,
+ invalidated_file_types,
+ [:commit_count, :repository_size]
+ )
+ end
+
+ def push_data
+ @push_data ||= Gitlab::DataBuilder::Push.build(
+ project: project,
+ user: current_user,
+ oldrev: params[:oldrev],
+ newrev: params[:newrev],
+ ref: params[:ref],
+ commits: limited_commits,
+ message: event_message,
+ commits_count: commits_count,
+ push_options: params[:push_options] || {}
+ )
+
+ # Dependent code may modify the push data, so return a duplicate each time
+ @push_data.dup
+ end
+
+ # to be overridden in EE
+ def pipeline_options
+ {}
+ end
+
+ def update_remote_mirrors
+ return unless project.has_remote_mirror?
+
+ project.mark_stuck_remote_mirrors_as_failed!
+ project.update_remote_mirrors
+ end
+ end
+end
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
new file mode 100644
index 00000000000..d21a6bb1b9a
--- /dev/null
+++ b/app/services/git/branch_hooks_service.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+module Git
+ class BranchHooksService < ::Git::BaseHooksService
+ def execute
+ execute_branch_hooks
+
+ super.tap do
+ enqueue_update_gpg_signatures
+ end
+ end
+
+ private
+
+ def hook_name
+ :push_hooks
+ end
+
+ def commits
+ strong_memoize(:commits) do
+ if creating_default_branch?
+ # The most recent PROCESS_COMMIT_LIMIT commits in the default branch
+ offset = [count_commits_in_branch - PROCESS_COMMIT_LIMIT, 0].max
+ project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
+ elsif creating_branch?
+ # Use the pushed commits that aren't reachable by the default branch
+ # as a heuristic. This may include more commits than are actually
+ # pushed, but that shouldn't matter because we check for existing
+ # cross-references later.
+ project.repository.commits_between(project.default_branch, params[:newrev])
+ elsif updating_branch?
+ project.repository.commits_between(params[:oldrev], params[:newrev])
+ else # removing branch
+ []
+ end
+ end
+ end
+
+ def commits_count
+ return count_commits_in_branch if creating_default_branch?
+
+ super
+ end
+
+ def invalidated_file_types
+ return super unless default_branch? && !creating_branch?
+
+ paths = limited_commits.each_with_object(Set.new) do |commit, set|
+ commit.raw_deltas.each do |diff|
+ set << diff.new_path
+ end
+ end
+
+ Gitlab::FileDetector.types_in_paths(paths)
+ end
+
+ def execute_branch_hooks
+ project.repository.after_push_commit(branch_name)
+
+ branch_create_hooks if creating_branch?
+ branch_update_hooks if updating_branch?
+ branch_change_hooks if creating_branch? || updating_branch?
+ branch_remove_hooks if removing_branch?
+ end
+
+ def branch_create_hooks
+ project.repository.after_create_branch
+ project.after_create_default_branch if default_branch?
+ end
+
+ def branch_update_hooks
+ # Update the bare repositories info/attributes file using the contents of
+ # the default branch's .gitattributes file
+ project.repository.copy_gitattributes(params[:ref]) if default_branch?
+ end
+
+ def branch_change_hooks
+ enqueue_process_commit_messages
+ end
+
+ def branch_remove_hooks
+ project.repository.after_remove_branch
+ end
+
+ # Schedules processing of commit messages
+ def enqueue_process_commit_messages
+ # don't process commits for the initial push to the default branch
+ return if creating_default_branch?
+
+ limited_commits.each do |commit|
+ next unless commit.matches_cross_reference_regex?
+
+ ProcessCommitWorker.perform_async(
+ project.id,
+ current_user.id,
+ commit.to_hash,
+ default_branch?
+ )
+ end
+ end
+
+ def enqueue_update_gpg_signatures
+ unsigned = GpgSignature.unsigned_commit_shas(limited_commits.map(&:sha))
+ return if unsigned.empty?
+
+ signable = Gitlab::Git::Commit.shas_with_signatures(project.repository, unsigned)
+ return if signable.empty?
+
+ CreateGpgSignatureWorker.perform_async(signable, project.id)
+ end
+
+ def creating_branch?
+ Gitlab::Git.blank_ref?(params[:oldrev])
+ end
+
+ def updating_branch?
+ !creating_branch? && !removing_branch?
+ end
+
+ def removing_branch?
+ Gitlab::Git.blank_ref?(params[:newrev])
+ end
+
+ def creating_default_branch?
+ creating_branch? && default_branch?
+ end
+
+ def count_commits_in_branch
+ strong_memoize(:count_commits_in_branch) do
+ project.repository.commit_count_for_ref(params[:ref])
+ end
+ end
+
+ def default_branch?
+ strong_memoize(:default_branch) do
+ [nil, branch_name].include?(project.default_branch)
+ end
+ end
+
+ def branch_name
+ strong_memoize(:branch_name) { Gitlab::Git.ref_name(params[:ref]) }
+ end
+ end
+end
diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb
new file mode 100644
index 00000000000..c4910180787
--- /dev/null
+++ b/app/services/git/branch_push_service.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module Git
+ class BranchPushService < ::BaseService
+ include Gitlab::Access
+ include Gitlab::Utils::StrongMemoize
+
+ # This method will be called after each git update
+ # and only if the provided user and project are present in GitLab.
+ #
+ # All callbacks for post receive action should be placed here.
+ #
+ # Next, this method:
+ # 1. Creates the push event
+ # 2. Updates merge requests
+ # 3. Recognizes cross-references from commit messages
+ # 4. Executes the project's webhooks
+ # 5. Executes the project's services
+ # 6. Checks if the project's main language has changed
+ #
+ def execute
+ return unless Gitlab::Git.branch_ref?(params[:ref])
+
+ enqueue_update_mrs
+ enqueue_detect_repository_languages
+
+ execute_related_hooks
+ perform_housekeeping
+
+ stop_environments
+
+ true
+ end
+
+ # Update merge requests that may be affected by this push. A new branch
+ # could cause the last commit of a merge request to change.
+ def enqueue_update_mrs
+ UpdateMergeRequestsWorker.perform_async(
+ project.id,
+ current_user.id,
+ params[:oldrev],
+ params[:newrev],
+ params[:ref]
+ )
+ end
+
+ def enqueue_detect_repository_languages
+ return unless default_branch?
+
+ DetectRepositoryLanguagesWorker.perform_async(project.id)
+ end
+
+ # Only stop environments if the ref is a branch that is being deleted
+ def stop_environments
+ return unless removing_branch?
+
+ Ci::StopEnvironmentsService.new(project, current_user).execute(branch_name)
+ end
+
+ def update_remote_mirrors
+ return unless project.has_remote_mirror?
+
+ project.mark_stuck_remote_mirrors_as_failed!
+ project.update_remote_mirrors
+ end
+
+ def execute_related_hooks
+ BranchHooksService.new(project, current_user, params).execute
+ end
+
+ def perform_housekeeping
+ housekeeping = Projects::HousekeepingService.new(project)
+ housekeeping.increment!
+ housekeeping.execute if housekeeping.needed?
+ rescue Projects::HousekeepingService::LeaseTaken
+ end
+
+ def removing_branch?
+ Gitlab::Git.blank_ref?(params[:newrev])
+ end
+
+ def branch_name
+ strong_memoize(:branch_name) { Gitlab::Git.ref_name(params[:ref]) }
+ end
+
+ def default_branch?
+ strong_memoize(:default_branch) do
+ [nil, branch_name].include?(project.default_branch)
+ end
+ end
+ end
+end
diff --git a/app/services/git/tag_hooks_service.rb b/app/services/git/tag_hooks_service.rb
new file mode 100644
index 00000000000..18eb780579f
--- /dev/null
+++ b/app/services/git/tag_hooks_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Git
+ class TagHooksService < ::Git::BaseHooksService
+ private
+
+ def hook_name
+ :tag_push_hooks
+ end
+
+ def commits
+ [tag_commit].compact
+ end
+
+ def event_message
+ tag&.message
+ end
+
+ def tag
+ strong_memoize(:tag) do
+ next if Gitlab::Git.blank_ref?(params[:newrev])
+
+ tag_name = Gitlab::Git.ref_name(params[:ref])
+ tag = project.repository.find_tag(tag_name)
+
+ tag if tag && tag.target == params[:newrev]
+ end
+ end
+
+ def tag_commit
+ strong_memoize(:tag_commit) do
+ project.commit(tag.dereferenced_target) if tag
+ end
+ end
+ end
+end
diff --git a/app/services/git/tag_push_service.rb b/app/services/git/tag_push_service.rb
new file mode 100644
index 00000000000..ee4166dccd0
--- /dev/null
+++ b/app/services/git/tag_push_service.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Git
+ class TagPushService < ::BaseService
+ def execute
+ return unless Gitlab::Git.tag_ref?(params[:ref])
+
+ project.repository.before_push_tag
+ TagHooksService.new(project, current_user, params).execute
+
+ true
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
deleted file mode 100644
index f387c749a21..00000000000
--- a/app/services/git_push_service.rb
+++ /dev/null
@@ -1,240 +0,0 @@
-# frozen_string_literal: true
-
-class GitPushService < BaseService
- attr_accessor :push_data, :push_commits
- include Gitlab::Access
- include Gitlab::Utils::StrongMemoize
-
- # The N most recent commits to process in a single push payload.
- PROCESS_COMMIT_LIMIT = 100
-
- # This method will be called after each git update
- # and only if the provided user and project are present in GitLab.
- #
- # All callbacks for post receive action should be placed here.
- #
- # Next, this method:
- # 1. Creates the push event
- # 2. Updates merge requests
- # 3. Recognizes cross-references from commit messages
- # 4. Executes the project's webhooks
- # 5. Executes the project's services
- # 6. Checks if the project's main language has changed
- #
- def execute
- project.repository.after_create if project.empty_repo?
- project.repository.after_push_commit(branch_name)
-
- if push_remove_branch?
- project.repository.after_remove_branch
- @push_commits = []
- elsif push_to_new_branch?
- project.repository.after_create_branch
-
- # Re-find the pushed commits.
- if default_branch?
- # Initial push to the default branch. Take the full history of that branch as "newly pushed".
- process_default_branch
- else
- # Use the pushed commits that aren't reachable by the default branch
- # as a heuristic. This may include more commits than are actually pushed, but
- # that shouldn't matter because we check for existing cross-references later.
- @push_commits = project.repository.commits_between(project.default_branch, params[:newrev])
-
- # don't process commits for the initial push to the default branch
- process_commit_messages
- end
- elsif push_to_existing_branch?
- # Collect data for this git push
- @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev])
-
- process_commit_messages
-
- # Update the bare repositories info/attributes file using the contents of the default branches
- # .gitattributes file
- update_gitattributes if default_branch?
- end
-
- execute_related_hooks
- perform_housekeeping
-
- update_remote_mirrors
- update_caches
-
- update_signatures
- end
-
- def update_gitattributes
- project.repository.copy_gitattributes(params[:ref])
- end
-
- def update_caches
- if default_branch?
- if push_to_new_branch?
- # If this is the initial push into the default branch, the file type caches
- # will already be reset as a result of `Project#change_head`.
- types = []
- else
- paths = Set.new
-
- last_pushed_commits.each do |commit|
- commit.raw_deltas.each do |diff|
- paths << diff.new_path
- end
- end
-
- types = Gitlab::FileDetector.types_in_paths(paths.to_a)
- end
-
- DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id)
- else
- types = []
- end
-
- ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def update_signatures
- commit_shas = last_pushed_commits.map(&:sha)
-
- return if commit_shas.empty?
-
- shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
- commit_shas -= shas_with_cached_signatures
-
- return if commit_shas.empty?
-
- commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)
-
- CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # Schedules processing of commit messages.
- def process_commit_messages
- default = default_branch?
-
- last_pushed_commits.each do |commit|
- if commit.matches_cross_reference_regex?
- ProcessCommitWorker
- .perform_async(project.id, current_user.id, commit.to_hash, default)
- end
- end
- end
-
- protected
-
- def update_remote_mirrors
- return unless project.has_remote_mirror?
-
- project.mark_stuck_remote_mirrors_as_failed!
- project.update_remote_mirrors
- end
-
- def execute_related_hooks
- # Update merge requests that may be affected by this push. A new branch
- # could cause the last commit of a merge request to change.
- #
- UpdateMergeRequestsWorker
- .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
-
- EventCreateService.new.push(project, current_user, build_push_data)
- Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options)
-
- project.execute_hooks(build_push_data.dup, :push_hooks)
- project.execute_services(build_push_data.dup, :push_hooks)
-
- if push_remove_branch?
- AfterBranchDeleteService
- .new(project, current_user)
- .execute(branch_name)
- end
- end
-
- def perform_housekeeping
- housekeeping = Projects::HousekeepingService.new(project)
- housekeeping.increment!
- housekeeping.execute if housekeeping.needed?
- rescue Projects::HousekeepingService::LeaseTaken
- end
-
- def process_default_branch
- offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max
- @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
-
- project.after_create_default_branch
- end
-
- def build_push_data
- @push_data ||= Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- @push_commits,
- commits_count: commits_count,
- push_options: params[:push_options] || [])
- end
-
- def push_to_existing_branch?
- # Return if this is not a push to a branch (e.g. new commits)
- branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev])
- end
-
- def push_to_new_branch?
- strong_memoize(:push_to_new_branch) do
- branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev])
- end
- end
-
- def push_remove_branch?
- strong_memoize(:push_remove_branch) do
- branch_ref? && Gitlab::Git.blank_ref?(params[:newrev])
- end
- end
-
- def default_branch?
- branch_ref? &&
- (branch_name == project.default_branch || project.default_branch.nil?)
- end
-
- def commit_user(commit)
- commit.author || current_user
- end
-
- def branch_name
- strong_memoize(:branch_name) do
- Gitlab::Git.ref_name(params[:ref])
- end
- end
-
- def branch_ref?
- strong_memoize(:branch_ref) do
- Gitlab::Git.branch_ref?(params[:ref])
- end
- end
-
- def commits_count
- return push_commits_count_for_ref if default_branch? && push_to_new_branch?
-
- Array(@push_commits).size
- end
-
- def push_commits_count_for_ref
- strong_memoize(:push_commits_count_for_ref) do
- project.repository.commit_count_for_ref(params[:ref])
- end
- end
-
- def last_pushed_commits
- @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
- end
-
- private
-
- def pipeline_options
- {} # to be overridden in EE
- end
-end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
deleted file mode 100644
index e39b3603c6c..00000000000
--- a/app/services/git_tag_push_service.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-class GitTagPushService < BaseService
- attr_accessor :push_data
-
- def execute
- project.repository.after_create if project.empty_repo?
- project.repository.before_push_tag
-
- @push_data = build_push_data
-
- EventCreateService.new.push(project, current_user, push_data)
- Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options)
-
- SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
-
- ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
-
- true
- end
-
- private
-
- def build_push_data
- commits = []
- message = nil
-
- unless Gitlab::Git.blank_ref?(params[:newrev])
- tag_name = Gitlab::Git.ref_name(params[:ref])
- tag = project.repository.find_tag(tag_name)
-
- if tag && tag.target == params[:newrev]
- commit = project.commit(tag.dereferenced_target)
- commits = [commit].compact
- message = tag.message
- end
- end
-
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- commits,
- message,
- push_options: params[:push_options] || [])
- end
-
- def build_system_push_data
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- [],
- '')
- end
-
- def pipeline_options
- {} # to be overridden in EE
- end
-end
diff --git a/app/services/groups/auto_devops_service.rb b/app/services/groups/auto_devops_service.rb
new file mode 100644
index 00000000000..1925e0cc0ea
--- /dev/null
+++ b/app/services/groups/auto_devops_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Groups
+ class AutoDevopsService < Groups::BaseService
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_group, group)
+
+ group.update(auto_devops_enabled: auto_devops_enabled)
+ end
+
+ private
+
+ def auto_devops_enabled
+ params[:auto_devops_enabled]
+ end
+ end
+end
diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb
index 8c8acce5ca5..019cd047ae9 100644
--- a/app/services/groups/base_service.rb
+++ b/app/services/groups/base_service.rb
@@ -7,5 +7,11 @@ module Groups
def initialize(group, user, params = {})
@group, @current_user, @params = group, user, params.dup
end
+
+ private
+
+ def remove_unallowed_params
+ # overridden in EE
+ end
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 99ead467f74..e9659f5489a 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -8,6 +8,8 @@ module Groups
end
def execute
+ remove_unallowed_params
+
@group = Group.new(params)
after_build_hook(@group, params)
@@ -44,13 +46,13 @@ module Groups
if @group.subgroup?
unless can?(current_user, :create_subgroup, @group.parent)
@group.parent = nil
- @group.errors.add(:parent_id, 'You don’t have permission to create a subgroup in this group.')
+ @group.errors.add(:parent_id, s_('CreateGroup|You don’t have permission to create a subgroup in this group.'))
return false
end
else
unless can?(current_user, :create_group)
- @group.errors.add(:base, 'You don’t have permission to create groups.')
+ @group.errors.add(:base, s_('CreateGroup|You don’t have permission to create groups.'))
return false
end
@@ -60,12 +62,16 @@ module Groups
end
def can_use_visibility_level?
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
deny_visibility_level(@group)
return false
end
true
end
+
+ def visibility_level
+ params[:visibility].present? ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
+ end
end
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 641111aeadc..654fe84e3dc 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -20,7 +20,7 @@ module Groups
end
# reload the relation to prevent triggering destroy hooks on the projects again
- group.projects.reload
+ group.projects.reset
group.children.each do |group|
# This needs to be synchronous since the namespace gets destroyed below
diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index f01f5656296..01bd685712b 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -12,7 +12,7 @@ module Groups
end
def execute
- return nil unless group_path
+ return unless group_path
if namespace = namespace_or_group(group_path)
return namespace
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index f64e327416a..98e7c311572 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -3,11 +3,11 @@
module Groups
class TransferService < Groups::BaseService
ERROR_MESSAGES = {
- database_not_supported: 'Database is not supported.',
- namespace_with_same_path: 'The parent group already has a subgroup with the same path.',
- group_is_already_root: 'Group is already a root group.',
- same_parent_as_current: 'Group is already associated to the parent group.',
- invalid_policies: "You don't have enough permissions."
+ database_not_supported: s_('TransferGroup|Database is not supported.'),
+ namespace_with_same_path: s_('TransferGroup|The parent group already has a subgroup with the same path.'),
+ group_is_already_root: s_('TransferGroup|Group is already a root group.'),
+ same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
+ invalid_policies: s_("TransferGroup|You don't have enough permissions.")
}.freeze
TransferError = Class.new(StandardError)
@@ -26,7 +26,7 @@ module Groups
rescue TransferError, ActiveRecord::RecordInvalid, Gitlab::UpdatePathError => e
@group.errors.clear
- @error = "Transfer failed: " + e.message
+ @error = s_("TransferGroup|Transfer failed: %{error_message}") % { error_message: e.message }
false
end
@@ -35,7 +35,10 @@ module Groups
def proceed_to_transfer
Group.transaction do
update_group_attributes
+ ensure_ownership
end
+
+ true
end
def ensure_allowed_transfer
@@ -95,6 +98,13 @@ module Groups
end
# rubocop: enable CodeReuse/ActiveRecord
+ def ensure_ownership
+ return if @new_parent_group
+ return unless @group.owners.empty?
+
+ @group.add_owner(current_user)
+ end
+
def raise_transfer_error(message)
raise TransferError, ERROR_MESSAGES[message]
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 787445180f0..73e1e00dc33 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -6,6 +6,7 @@ module Groups
def execute
reject_parent_id!
+ remove_unallowed_params
return false unless valid_visibility_level_change?(group, params[:visibility_level])
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index a2533683da9..a322a306ba4 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -7,7 +7,7 @@ module Import
def execute(access_params, provider)
unless authorized?
- return error('This namespace has already been taken! Please choose another one.', :unprocessable_entity)
+ return error(_('This namespace has already been taken! Please choose another one.'), :unprocessable_entity)
end
project = Gitlab::LegacyGithubImport::ProjectCreator
diff --git a/app/services/issuable/clone/content_rewriter.rb b/app/services/issuable/clone/content_rewriter.rb
index e1e0b75085d..00d7078859d 100644
--- a/app/services/issuable/clone/content_rewriter.rb
+++ b/app/services/issuable/clone/content_rewriter.rb
@@ -28,6 +28,7 @@ module Issuable
new_params = {
project: new_entity.project, noteable: new_entity,
note: rewrite_content(new_note.note),
+ note_html: nil,
created_at: note.created_at,
updated_at: note.updated_at
}
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index ef991eaf234..26132f1824a 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -34,14 +34,20 @@ class IssuableBaseService < BaseService
end
def filter_assignee(issuable)
- return unless params[:assignee_id].present?
+ return if params[:assignee_ids].blank?
- assignee_id = params[:assignee_id]
+ unless issuable.allows_multiple_assignees?
+ params[:assignee_ids] = params[:assignee_ids].first(1)
+ end
+
+ assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) }
- if assignee_id.to_s == IssuableFinder::NONE
- params[:assignee_id] = ""
+ if params[:assignee_ids].map(&:to_s) == [IssuableFinder::NONE]
+ params[:assignee_ids] = []
+ elsif assignee_ids.any?
+ params[:assignee_ids] = assignee_ids
else
- params.delete(:assignee_id) unless assignee_can_read?(issuable, assignee_id)
+ params.delete(:assignee_ids)
end
end
@@ -70,26 +76,28 @@ class IssuableBaseService < BaseService
end
def filter_labels
- filter_labels_in_param(:add_label_ids)
- filter_labels_in_param(:remove_label_ids)
- filter_labels_in_param(:label_ids)
- find_or_create_label_ids
+ params[:add_label_ids] = labels_service.filter_labels_ids_in_param(:add_label_ids) if params[:add_label_ids]
+ params[:remove_label_ids] = labels_service.filter_labels_ids_in_param(:remove_label_ids) if params[:remove_label_ids]
+
+ if params[:label_ids]
+ params[:label_ids] = labels_service.filter_labels_ids_in_param(:label_ids)
+ elsif params[:labels]
+ params[:label_ids] = labels_service.find_or_create_by_titles.map(&:id)
+ end
end
- # rubocop: disable CodeReuse/ActiveRecord
def filter_labels_in_param(key)
return if params[key].to_a.empty?
- params[key] = available_labels.where(id: params[key]).pluck(:id)
+ params[key] = available_labels.id_in(params[key]).pluck_primary_key
end
- # rubocop: enable CodeReuse/ActiveRecord
def find_or_create_label_ids
labels = params.delete(:labels)
return unless labels
- params[:label_ids] = labels.split(",").map do |label_name|
+ params[:label_ids] = labels.map do |label_name|
label = Labels::FindOrCreateService.new(
current_user,
parent,
@@ -101,12 +109,17 @@ class IssuableBaseService < BaseService
end.compact
end
- def process_label_ids(attributes, existing_label_ids: nil)
+ def labels_service
+ @labels_service ||= ::Labels::AvailableLabelsService.new(current_user, parent, params)
+ end
+
+ def process_label_ids(attributes, existing_label_ids: nil, extra_label_ids: [])
label_ids = attributes.delete(:label_ids)
add_label_ids = attributes.delete(:add_label_ids)
remove_label_ids = attributes.delete(:remove_label_ids)
new_label_ids = existing_label_ids || label_ids || []
+ new_label_ids |= extra_label_ids
if add_label_ids.blank? && remove_label_ids.blank?
new_label_ids = label_ids if label_ids
@@ -115,11 +128,7 @@ class IssuableBaseService < BaseService
new_label_ids -= remove_label_ids if remove_label_ids
end
- new_label_ids
- end
-
- def available_labels
- @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
+ new_label_ids.uniq
end
def handle_quick_actions_on_create(issuable)
@@ -145,7 +154,7 @@ class IssuableBaseService < BaseService
params.delete(:state_event)
params[:author] ||= current_user
- params[:label_ids] = issuable.label_ids.to_a + process_label_ids(params)
+ params[:label_ids] = process_label_ids(params, extra_label_ids: issuable.label_ids.to_a)
issuable.assign_attributes(params)
@@ -349,7 +358,7 @@ class IssuableBaseService < BaseService
end
def has_changes?(issuable, old_labels: [], old_assignees: [])
- valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
+ valid_attrs = [:title, :description, :assignee_ids, :milestone_id, :target_branch]
attrs_changed = valid_attrs.any? do |attr|
issuable.previous_changes.include?(attr.to_s)
@@ -387,4 +396,10 @@ class IssuableBaseService < BaseService
def parent
project
end
+
+ # we need to check this because milestone from milestone_id param is displayed on "new" page
+ # where private project milestone could leak without this check
+ def ensure_milestone_available(issuable)
+ issuable.milestone_id = nil unless issuable.milestone_available?
+ end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index ef08adf4f92..48ed5afbc2a 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -20,7 +20,7 @@ module Issues
private
def create_assignee_note(issue, old_assignees)
- SystemNoteService.change_issue_assignees(
+ SystemNoteService.change_issuable_assignees(
issue, issue.project, current_user, old_assignees)
end
@@ -31,26 +31,6 @@ module Issues
issue.project.execute_services(issue_data, hooks_scope)
end
- # rubocop: disable CodeReuse/ActiveRecord
- def filter_assignee(issuable)
- return if params[:assignee_ids].blank?
-
- unless issuable.allows_multiple_assignees?
- params[:assignee_ids] = params[:assignee_ids].take(1)
- end
-
- assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) }
-
- if params[:assignee_ids].map(&:to_s) == [IssuableFinder::NONE]
- params[:assignee_ids] = []
- elsif assignee_ids.any?
- params[:assignee_ids] = assignee_ids
- else
- params.delete(:assignee_ids)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def update_project_counter_caches?(issue)
super || issue.confidential_changed?
end
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 3fb2c2b3007..61615ac2058 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -6,7 +6,9 @@ module Issues
def execute
filter_resolve_discussion_params
- @issue = project.issues.new(issue_params)
+ @issue = project.issues.new(issue_params).tap do |issue|
+ ensure_milestone_available(issue)
+ end
end
def issue_params_with_info_from_discussions
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index e5cc12e6082..805721212ba 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -7,7 +7,7 @@ module Issues
return issue unless can?(current_user, :update_issue, issue)
close_issue(issue,
- commit: commit,
+ closed_via: commit,
notifications: notifications,
system_note: system_note)
end
@@ -17,9 +17,9 @@ module Issues
#
# The code calling this method is responsible for ensuring that a user is
# allowed to close the given issue.
- def close_issue(issue, commit: nil, notifications: true, system_note: true)
+ def close_issue(issue, closed_via: nil, notifications: true, system_note: true)
if project.jira_tracker? && project.jira_service.active && issue.is_a?(ExternalIssue)
- project.jira_service.close_issue(commit, issue)
+ project.jira_service.close_issue(closed_via, issue)
todo_service.close_issue(issue, current_user)
return issue
end
@@ -27,8 +27,11 @@ module Issues
if project.issues_enabled? && issue.close
issue.update(closed_by: current_user)
event_service.close_issue(issue, current_user)
- create_note(issue, commit) if system_note
- notification_service.async.close_issue(issue, current_user) if notifications
+ create_note(issue, closed_via) if system_note
+
+ closed_via = _("commit %{commit_id}") % { commit_id: closed_via.id } if closed_via.is_a?(Commit)
+
+ notification_service.async.close_issue(issue, current_user, closed_via: closed_via) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 41b6a96b005..334fadadb6f 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -8,11 +8,11 @@ module Issues
@target_project = target_project
unless issue.can_move?(current_user, @target_project)
- raise MoveError, 'Cannot move issue due to insufficient permissions!'
+ raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions!')
end
if @project == @target_project
- raise MoveError, 'Cannot move issue to project it originates from!'
+ raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from!')
end
super
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index cec5b5734c0..cb2337d29d4 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -39,7 +39,7 @@ module Issues
if issue.assignees != old_assignees
create_assignee_note(issue, old_assignees)
notification_service.async.reassigned_issue(issue, current_user, old_assignees)
- todo_service.reassigned_issue(issue, current_user, old_assignees)
+ todo_service.reassigned_issuable(issue, current_user, old_assignees)
end
if issue.previous_changes.include?('confidential')
diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb
new file mode 100644
index 00000000000..fe477d96970
--- /dev/null
+++ b/app/services/labels/available_labels_service.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Labels
+ class AvailableLabelsService
+ attr_reader :current_user, :parent, :params
+
+ def initialize(current_user, parent, params)
+ @current_user = current_user
+ @parent = parent
+ @params = params
+ end
+
+ def find_or_create_by_titles
+ labels = params.delete(:labels)
+
+ return [] unless labels
+
+ labels = labels.split(',') if labels.is_a?(String)
+
+ labels.map do |label_name|
+ label = Labels::FindOrCreateService.new(
+ current_user,
+ parent,
+ include_ancestor_groups: true,
+ title: label_name.strip,
+ available_labels: available_labels
+ ).execute
+
+ label
+ end.compact
+ end
+
+ def filter_labels_ids_in_param(key)
+ return [] if params[key].to_a.empty?
+
+ # rubocop:disable CodeReuse/ActiveRecord
+ available_labels.by_ids(params[key]).pluck(:id)
+ # rubocop:enable CodeReuse/ActiveRecord
+ end
+
+ private
+
+ def available_labels
+ @available_labels ||= LabelsFinder.new(current_user, finder_params).execute
+ end
+
+ def finder_params
+ params = { include_ancestor_groups: true }
+
+ case parent
+ when Group
+ params[:group_id] = parent.id
+ params[:only_group_labels] = true
+ when Project
+ params[:project_id] = parent.id
+ end
+
+ params
+ end
+ end
+end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
index 6ecf583cb6a..5239fe1b6e3 100644
--- a/app/services/lfs/file_transformer.rb
+++ b/app/services/lfs/file_transformer.rb
@@ -24,7 +24,7 @@ module Lfs
def new_file(file_path, file_content, encoding: nil)
if project.lfs_enabled? && lfs_file?(file_path)
- file_content = Base64.decode64(file_content) if encoding == 'base64'
+ file_content = parse_file_content(file_content, encoding: encoding)
lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
@@ -66,5 +66,12 @@ module Lfs
def link_lfs_object!(lfs_object)
project.lfs_objects << lfs_object
end
+
+ def parse_file_content(file_content, encoding: nil)
+ return file_content.read if file_content.respond_to?(:read)
+ return Base64.decode64(file_content) if encoding == 'base64'
+
+ file_content
+ end
end
end
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index cf710fef52b..d6b17ec10be 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -5,11 +5,11 @@ module Members
DEFAULT_LIMIT = 100
def execute(source)
- return error('No users specified.') if params[:user_ids].blank?
+ return error(s_('AddMember|No users specified.')) if params[:user_ids].blank?
user_ids = params[:user_ids].split(',').uniq
- return error("Too many users specified (limit is #{user_limit})") if
+ return error(s_("AddMember|Too many users specified (limit is %{user_limit})") % { user_limit: user_limit }) if
user_limit && user_ids.size > user_limit
members = source.add_users(
@@ -23,7 +23,16 @@ module Members
members.each do |member|
if member.errors.any?
- errors << "#{member.user.username}: #{member.errors.full_messages.to_sentence}"
+ current_error =
+ # Invited users may not have an associated user
+ if member.user.present?
+ "#{member.user.username}: "
+ else
+ ""
+ end
+
+ current_error += member.errors.full_messages.to_sentence
+ errors << current_error
else
after_execute(member: member)
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index f9717a9426b..c8d5e563cd8 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -45,7 +45,7 @@ module Members
def delete_subgroup_members(member)
groups = member.group.descendants
- GroupMember.in_groups(groups).with_user(member.user).each do |group_member|
+ GroupMember.of_groups(groups).with_user(member.user).each do |group_member|
self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
end
end
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index 79c43b8e7d5..d3ef892875b 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -7,7 +7,7 @@ module MergeRequests
def execute(commit_status)
return if commit_status.allow_failure? || commit_status.retried?
- commit_status_merge_requests(commit_status) do |merge_request|
+ pipeline_merge_requests(commit_status.pipeline) do |merge_request|
todo_service.merge_request_build_failed(merge_request)
end
end
@@ -16,7 +16,7 @@ module MergeRequests
# build is retried
#
def close(commit_status)
- commit_status_merge_requests(commit_status) do |merge_request|
+ pipeline_merge_requests(commit_status.pipeline) do |merge_request|
todo_service.merge_request_build_retried(merge_request)
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ac51fee0b3f..2cfed62ce49 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -24,6 +24,11 @@ module MergeRequests
end
end
+ def cleanup_environments(merge_request)
+ Ci::StopEnvironmentsService.new(merge_request.source_project, current_user)
+ .execute_for_merge_request(merge_request)
+ end
+
private
def handle_wip_event(merge_request)
@@ -49,28 +54,18 @@ module MergeRequests
MergeRequestMetricsService.new(merge_request.metrics)
end
- def create_assignee_note(merge_request)
- SystemNoteService.change_assignee(
- merge_request, merge_request.project, current_user, merge_request.assignee)
+ def create_assignee_note(merge_request, old_assignees)
+ SystemNoteService.change_issuable_assignees(
+ merge_request, merge_request.project, current_user, old_assignees)
end
- def create_merge_request_pipeline(merge_request, user)
- return unless Feature.enabled?(:ci_merge_request_pipeline,
- merge_request.source_project,
- default_enabled: true)
-
- ##
- # UpdateMergeRequestsWorker could be retried by an exception.
- # MR pipelines should not be recreated in such case.
- return if merge_request.merge_request_pipeline_exists?
- return if merge_request.has_no_commits?
-
- Ci::CreatePipelineService
- .new(merge_request.source_project, user, ref: merge_request.source_branch)
- .execute(:merge_request,
- ignore_skip_ci: true,
- save_on_errors: false,
- merge_request: merge_request)
+ def create_pipeline_for(merge_request, user)
+ MergeRequests::CreatePipelineService.new(project, user).execute(merge_request)
+ end
+
+ def can_use_merge_request_ref?(merge_request)
+ Feature.enabled?(:ci_use_merge_request_ref, project, default_enabled: true) &&
+ !merge_request.for_fork?
end
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
@@ -85,22 +80,11 @@ module MergeRequests
# rubocop: enable CodeReuse/ActiveRecord
def pipeline_merge_requests(pipeline)
- merge_requests_for(pipeline.ref).each do |merge_request|
+ pipeline.all_merge_requests.opened.each do |merge_request|
next unless pipeline == merge_request.head_pipeline
yield merge_request
end
end
-
- def commit_status_merge_requests(commit_status)
- merge_requests_for(commit_status.ref).each do |merge_request|
- pipeline = merge_request.head_pipeline
-
- next unless pipeline
- next unless pipeline.sha == commit_status.sha
-
- yield merge_request
- end
- end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 48419da98ad..109c964e577 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -19,6 +19,7 @@ module MergeRequests
merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch
merge_request.can_be_created = projects_and_branches_valid?
+ ensure_milestone_available(merge_request)
# compare branches only if branches are valid, otherwise
# compare_branches may raise an error
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index 04527bb9713..b0f6166ea1c 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -17,6 +17,8 @@ module MergeRequests
execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
+ cleanup_environments(merge_request)
+ cancel_auto_merge(merge_request)
end
merge_request
@@ -32,5 +34,9 @@ module MergeRequests
merge_request_metrics_service(merge_request).close(close_event)
end
end
+
+ def cancel_auto_merge(merge_request)
+ AutoMergeService.new(project, current_user).cancel(merge_request)
+ end
end
end
diff --git a/app/services/merge_requests/create_pipeline_service.rb b/app/services/merge_requests/create_pipeline_service.rb
new file mode 100644
index 00000000000..03246cc1920
--- /dev/null
+++ b/app/services/merge_requests/create_pipeline_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class CreatePipelineService < MergeRequests::BaseService
+ def execute(merge_request)
+ return unless can_create_pipeline_for?(merge_request)
+
+ create_detached_merge_request_pipeline(merge_request)
+ end
+
+ def create_detached_merge_request_pipeline(merge_request)
+ if can_use_merge_request_ref?(merge_request)
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.ref_path)
+ .execute(:merge_request_event, merge_request: merge_request)
+ else
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.source_branch)
+ .execute(:merge_request_event, merge_request: merge_request)
+ end
+ end
+
+ def can_create_pipeline_for?(merge_request)
+ ##
+ # UpdateMergeRequestsWorker could be retried by an exception.
+ # pipelines for merge request should not be recreated in such case.
+ return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
+ return false if merge_request.has_no_commits?
+
+ true
+ end
+
+ def allow_duplicate
+ params[:allow_duplicate]
+ end
+ end
+end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 02c2388c05c..06e46595b95 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -25,7 +25,7 @@ module MergeRequests
def after_create(issuable)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
- create_merge_request_pipeline(issuable, current_user)
+ create_pipeline_for(issuable, current_user)
issuable.update_head_pipeline
super
diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb
index d5929446122..bdb7ec8a7c2 100644
--- a/app/services/merge_requests/delete_non_latest_diffs_service.rb
+++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb
@@ -8,15 +8,13 @@ module MergeRequests
@merge_request = merge_request
end
- # rubocop: disable CodeReuse/ActiveRecord
def execute
diffs = @merge_request.non_latest_diffs.with_files
diffs.each_batch(of: BATCH_SIZE) do |relation, index|
- ids = relation.pluck(:id).map { |id| [id] }
+ ids = relation.pluck_primary_key.map { |id| [id] }
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
new file mode 100644
index 00000000000..095bdca5472
--- /dev/null
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeBaseService < MergeRequests::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ MergeError = Class.new(StandardError)
+
+ attr_reader :merge_request
+
+ # Overridden in EE.
+ def hooks_validation_pass?(_merge_request)
+ true
+ end
+
+ # Overridden in EE.
+ def hooks_validation_error(_merge_request)
+ # No-op
+ end
+
+ def source
+ if merge_request.squash
+ squash_sha!
+ else
+ merge_request.diff_head_sha
+ end
+ end
+
+ private
+
+ # Overridden in EE.
+ def error_check!
+ # No-op
+ end
+
+ def raise_error(message)
+ raise MergeError, message
+ end
+
+ def handle_merge_error(*args)
+ # No-op
+ end
+
+ def commit_message
+ params[:commit_message] ||
+ merge_request.default_merge_commit_message
+ end
+
+ def squash_sha!
+ strong_memoize(:squash_sha) do
+ params[:merge_request] = merge_request
+ squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
+
+ case squash_result[:status]
+ when :success
+ squash_result[:squash_sha]
+ when :error
+ raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 449997bcf07..d8a78001b79 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -7,13 +7,7 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Executed when you do merge via GitLab UI
#
- class MergeService < MergeRequests::BaseService
- include Gitlab::Utils::StrongMemoize
-
- MergeError = Class.new(StandardError)
-
- attr_reader :merge_request, :source
-
+ class MergeService < MergeRequests::MergeBaseService
delegate :merge_jid, :state, to: :@merge_request
def execute(merge_request)
@@ -24,7 +18,7 @@ module MergeRequests
@merge_request = merge_request
- error_check!
+ validate!
merge_request.in_locked_state do
if commit
@@ -38,22 +32,22 @@ module MergeRequests
handle_merge_error(log_message: e.message, save_message_on_model: true)
end
- def source
- if merge_request.squash
- squash_sha!
- else
- merge_request.diff_head_sha
- end
- end
+ private
- # Overridden in EE.
- def hooks_validation_pass?(_merge_request)
- true
+ def validate!
+ authorization_check!
+ error_check!
end
- private
+ def authorization_check!
+ unless @merge_request.can_be_merged_by?(current_user)
+ raise_error('You are not allowed to merge this merge request')
+ end
+ end
def error_check!
+ super
+
error =
if @merge_request.should_be_rebased?
'Only fast-forward merge is allowed for your project. Please update your source branch'
@@ -63,7 +57,7 @@ module MergeRequests
'No source for merge'
end
- raise MergeError, error if error
+ raise_error(error) if error
end
def commit
@@ -73,36 +67,20 @@ module MergeRequests
if commit_id
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
else
- raise MergeError, 'Conflicts detected during merge'
+ raise_error('Conflicts detected during merge')
end
merge_request.update!(merge_commit_sha: commit_id)
end
- def squash_sha!
- strong_memoize(:squash_sha) do
- params[:merge_request] = merge_request
- squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
-
- case squash_result[:status]
- when :success
- squash_result[:squash_sha]
- when :error
- raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
- end
- end
- end
-
def try_merge
- message = params[:commit_message] || merge_request.default_merge_commit_message
-
- repository.merge(current_user, source, merge_request, message)
+ repository.merge(current_user, source, merge_request, commit_message)
rescue Gitlab::Git::PreReceiveError => e
- handle_merge_error(log_message: e.message)
- raise MergeError, 'Something went wrong during merge pre-receive hook'
+ raise MergeError,
+ "Something went wrong during merge pre-receive hook. #{e.message}".strip
rescue => e
handle_merge_error(log_message: e.message)
- raise MergeError, 'Something went wrong during merge'
+ raise_error('Something went wrong during merge')
ensure
merge_request.update!(in_progress_merge_commit_sha: nil)
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
new file mode 100644
index 00000000000..8670b9ccf3d
--- /dev/null
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ # Performs the merge between source SHA and the target branch. Instead
+ # of writing the result to the MR target branch, it targets the `target_ref`.
+ #
+ # Ideally this should leave the `target_ref` state with the same state the
+ # target branch would have if we used the regular `MergeService`, but without
+ # every side-effect that comes with it (MR updates, mails, source branch
+ # deletion, etc). This service should be kept idempotent (i.e. can
+ # be executed regardless of the `target_ref` current state).
+ #
+ class MergeToRefService < MergeRequests::MergeBaseService
+ def execute(merge_request)
+ @merge_request = merge_request
+
+ validate!
+
+ commit_id = commit
+
+ raise_error('Conflicts detected during merge') unless commit_id
+
+ success(commit_id: commit_id)
+ rescue MergeError, ArgumentError => error
+ error(error.message)
+ end
+
+ private
+
+ def validate!
+ error_check!
+ end
+
+ def error_check!
+ super
+
+ error =
+ if !hooks_validation_pass?(merge_request)
+ hooks_validation_error(merge_request)
+ elsif source.blank?
+ 'No source for merge'
+ end
+
+ raise_error(error) if error
+ end
+
+ def target_ref
+ merge_request.merge_ref_path
+ end
+
+ def commit
+ repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message)
+ rescue Gitlab::Git::PreReceiveError => error
+ raise MergeError, error.message
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb
deleted file mode 100644
index 973e5b64e88..00000000000
--- a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module MergeRequests
- class MergeWhenPipelineSucceedsService < MergeRequests::BaseService
- # Marks the passed `merge_request` to be merged when the pipeline succeeds or
- # updates the params for the automatic merge
- def execute(merge_request)
- merge_request.merge_params.merge!(params)
-
- # The service is also called when the merge params are updated.
- already_approved = merge_request.merge_when_pipeline_succeeds?
-
- unless already_approved
- merge_request.merge_when_pipeline_succeeds = true
- merge_request.merge_user = @current_user
-
- SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
- end
-
- merge_request.save
- end
-
- # Triggers the automatic merge of merge_request once the pipeline succeeds
- def trigger(pipeline)
- return unless pipeline.success?
-
- pipeline_merge_requests(pipeline) do |merge_request|
- next unless merge_request.merge_when_pipeline_succeeds?
- next unless merge_request.mergeable?
-
- merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
- end
- end
-
- # Cancels the automatic merge
- def cancel(merge_request)
- if merge_request.merge_when_pipeline_succeeds? && merge_request.open?
- merge_request.reset_merge_when_pipeline_succeeds
- SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
-
- success
- else
- error("Can't cancel the automatic merge", 406)
- end
- end
- end
-end
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
new file mode 100644
index 00000000000..ef833774e65
--- /dev/null
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeabilityCheckService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :project, to: :@merge_request
+ delegate :repository, to: :project
+
+ def initialize(merge_request)
+ @merge_request = merge_request
+ end
+
+ # Updates the MR merge_status. Whenever it switches to a can_be_merged state,
+ # the merge-ref is refreshed.
+ #
+ # Returns a ServiceResponse indicating merge_status is/became can_be_merged
+ # and the merge-ref is synced. Success in case of being/becoming mergeable,
+ # error otherwise.
+ def execute
+ return ServiceResponse.error(message: 'Invalid argument') unless merge_request
+ return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only?
+
+ update_merge_status
+
+ unless merge_request.can_be_merged?
+ return ServiceResponse.error(message: 'Merge request is not mergeable')
+ end
+
+ unless payload.fetch(:merge_ref_head)
+ return ServiceResponse.error(message: 'Merge ref was not found')
+ end
+
+ ServiceResponse.success(payload: payload)
+ end
+
+ private
+
+ attr_reader :merge_request
+
+ def payload
+ strong_memoize(:payload) do
+ {
+ merge_ref_head: merge_ref_head_payload
+ }
+ end
+ end
+
+ def merge_ref_head_payload
+ commit = merge_request.merge_ref_head
+
+ return unless commit
+
+ target_id, source_id = commit.parent_ids
+
+ {
+ commit_id: commit.id,
+ source_id: source_id,
+ target_id: target_id
+ }
+ end
+
+ def update_merge_status
+ return unless merge_request.recheck_merge_status?
+
+ if can_git_merge?
+ merge_to_ref && merge_request.mark_as_mergeable
+ else
+ merge_request.mark_as_unmergeable
+ end
+ end
+
+ def can_git_merge?
+ !merge_request.broken? && repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
+ end
+
+ def merge_to_ref
+ result = MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ result[:status] == :success
+ end
+ end
+end
diff --git a/app/services/merge_requests/migrate_external_diffs_service.rb b/app/services/merge_requests/migrate_external_diffs_service.rb
new file mode 100644
index 00000000000..16050244637
--- /dev/null
+++ b/app/services/merge_requests/migrate_external_diffs_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MigrateExternalDiffsService < ::BaseService
+ MAX_JOBS = 1000.freeze
+
+ attr_reader :diff
+
+ def self.enqueue!
+ ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS)
+
+ MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] })
+ end
+
+ def initialize(merge_request_diff)
+ @diff = merge_request_diff
+ end
+
+ def execute
+ diff.migrate_files_to_external_storage!
+ end
+ end
+end
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index f26e3bee06f..c13f7dd5088 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -18,6 +18,7 @@ module MergeRequests
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
delete_non_latest_diffs(merge_request)
+ cleanup_environments(merge_request)
end
private
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
new file mode 100644
index 00000000000..a24163331e8
--- /dev/null
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class PushOptionsHandlerService
+ LIMIT = 10
+
+ attr_reader :branches, :changes_by_branch, :current_user, :errors,
+ :project, :push_options, :target_project
+
+ def initialize(project, current_user, changes, push_options)
+ @project = project
+ @target_project = @project.default_merge_request_target
+ @current_user = current_user
+ @branches = get_branches(changes)
+ @push_options = push_options
+ @errors = []
+ end
+
+ def execute
+ validate_service
+ return self if errors.present?
+
+ branches.each do |branch|
+ execute_for_branch(branch)
+ rescue Gitlab::Access::AccessDeniedError
+ errors << 'User access was denied'
+ rescue StandardError => e
+ Gitlab::AppLogger.error(e)
+ errors << 'An unknown error occurred'
+ end
+
+ self
+ end
+
+ private
+
+ def get_branches(raw_changes)
+ Gitlab::ChangesList.new(raw_changes).map do |changes|
+ next unless Gitlab::Git.branch_ref?(changes[:ref])
+
+ # Deleted branch
+ next if Gitlab::Git.blank_ref?(changes[:newrev])
+
+ # Default branch
+ branch_name = Gitlab::Git.branch_name(changes[:ref])
+ next if branch_name == target_project.default_branch
+
+ branch_name
+ end.compact.uniq
+ end
+
+ def validate_service
+ errors << 'User is required' if current_user.nil?
+
+ unless target_project.merge_requests_enabled?
+ errors << "Merge requests are not enabled for project #{target_project.full_path}"
+ end
+
+ if branches.size > LIMIT
+ errors << "Too many branches pushed (#{branches.size} were pushed, limit is #{LIMIT})"
+ end
+
+ if push_options[:target] && !target_project.repository.branch_exists?(push_options[:target])
+ errors << "Branch #{push_options[:target]} does not exist"
+ end
+ end
+
+ # Returns a Hash of branch => MergeRequest
+ def merge_requests
+ @merge_requests ||= MergeRequest.from_project(target_project)
+ .opened
+ .from_source_branches(branches)
+ .index_by(&:source_branch)
+ end
+
+ def execute_for_branch(branch)
+ merge_request = merge_requests[branch]
+
+ if merge_request
+ update!(merge_request)
+ else
+ create!(branch)
+ end
+ end
+
+ def create!(branch)
+ unless push_options[:create]
+ errors << "A merge_request.create push option is required to create a merge request for branch #{branch}"
+ return
+ end
+
+ # Use BuildService to assign the standard attributes of a merge request
+ merge_request = ::MergeRequests::BuildService.new(
+ project,
+ current_user,
+ create_params(branch)
+ ).execute
+
+ unless merge_request.errors.present?
+ merge_request = ::MergeRequests::CreateService.new(
+ project,
+ current_user,
+ merge_request.attributes.merge(assignees: merge_request.assignees)
+ ).execute
+ end
+
+ collect_errors_from_merge_request(merge_request) unless merge_request.persisted?
+ end
+
+ def update!(merge_request)
+ merge_request = ::MergeRequests::UpdateService.new(
+ target_project,
+ current_user,
+ update_params
+ ).execute(merge_request)
+
+ collect_errors_from_merge_request(merge_request) unless merge_request.valid?
+ end
+
+ def create_params(branch)
+ params = {
+ assignees: [current_user],
+ source_branch: branch,
+ source_project: project,
+ target_branch: push_options[:target] || target_project.default_branch,
+ target_project: target_project
+ }
+
+ if push_options.key?(:merge_when_pipeline_succeeds)
+ params.merge!(
+ merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
+ merge_user: current_user
+ )
+ end
+
+ params
+ end
+
+ def update_params
+ params = {}
+
+ if push_options.key?(:merge_when_pipeline_succeeds)
+ params.merge!(
+ merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
+ merge_user: current_user
+ )
+ end
+
+ if push_options.key?(:target)
+ params[:target_branch] = push_options[:target]
+ end
+
+ params
+ end
+
+ def collect_errors_from_merge_request(merge_request)
+ merge_request.errors.full_messages.each do |error|
+ errors << error
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 31b3ebf311e..4b9921c28ba 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -20,17 +20,7 @@ module MergeRequests
return false
end
- log_prefix = "#{self.class.name} info (#{merge_request.to_reference(full: true)}):"
-
- Gitlab::GitLogger.info("#{log_prefix} rebase started")
-
- rebase_sha = repository.rebase(current_user, merge_request)
-
- Gitlab::GitLogger.info("#{log_prefix} rebased to #{rebase_sha}")
-
- merge_request.update(rebase_commit_sha: rebase_sha)
-
- Gitlab::GitLogger.info("#{log_prefix} rebase SHA saved: #{rebase_sha}")
+ repository.rebase(current_user, merge_request)
true
rescue => e
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index f712b8863cd..08130a531ee 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -14,13 +14,17 @@ module MergeRequests
private
def refresh_merge_requests!
+ # n + 1: https://gitlab.com/gitlab-org/gitlab-ce/issues/60289
Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
+
# Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge
close_upon_missing_source_branch_ref
post_merge_manually_merged
reload_merge_requests
- reset_merge_when_pipeline_succeeds
+ outdate_suggestions
+ refresh_pipelines_on_merge_requests
+ cancel_auto_merge
mark_pending_todos_done
cache_merge_requests_closing_issues
@@ -106,8 +110,6 @@ module MergeRequests
end
merge_request.mark_as_unchecked
- create_merge_request_pipeline(merge_request, current_user)
- UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
# Upcoming method calls need the refreshed version of
@@ -125,8 +127,25 @@ module MergeRequests
merge_request.source_branch == @push.branch_name
end
- def reset_merge_when_pipeline_succeeds
- merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
+ def outdate_suggestions
+ outdate_service = Suggestions::OutdateService.new
+
+ merge_requests_for_source_branch.each do |merge_request|
+ outdate_service.execute(merge_request)
+ end
+ end
+
+ def refresh_pipelines_on_merge_requests
+ merge_requests_for_source_branch.each do |merge_request|
+ create_pipeline_for(merge_request, current_user)
+ UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
+ end
+ end
+
+ def cancel_auto_merge
+ merge_requests_for_source_branch.each do |merge_request|
+ AutoMergeService.new(project, current_user).cancel(merge_request)
+ end
end
def mark_pending_todos_done
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index f6cbe769ef4..f87005bcb6c 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -3,7 +3,7 @@
module MergeRequests
class ReopenService < MergeRequests::BaseService
def execute(merge_request)
- return merge_request unless can?(current_user, :update_merge_request, merge_request)
+ return merge_request unless can?(current_user, :reopen_merge_request, merge_request)
if merge_request.reopen
create_event(merge_request)
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
index 9d1a5d5e6d4..88ca3b4f5a8 100644
--- a/app/services/merge_requests/squash_service.rb
+++ b/app/services/merge_requests/squash_service.rb
@@ -10,10 +10,10 @@ module MergeRequests
end
if merge_request.squash_in_progress?
- return error('Squash task canceled: another squash is already in progress.')
+ return error(s_('MergeRequests|Squash task canceled: another squash is already in progress.'))
end
- squash! || error('Failed to squash. Should be done manually.')
+ squash! || error(s_('MergeRequests|Failed to squash. Should be done manually.'))
end
private
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 8112c2a4299..6a0f3000ffb 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -16,7 +16,7 @@ module MergeRequests
params.delete(:force_remove_source_branch)
end
- if params[:force_remove_source_branch].present?
+ if params.has_key?(:force_remove_source_branch)
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
end
@@ -24,13 +24,13 @@ module MergeRequests
update_task_event(merge_request) || update(merge_request)
end
- # rubocop:disable Metrics/AbcSize
def handle_changes(merge_request, options)
old_associations = options.fetch(:old_associations, {})
old_labels = old_associations.fetch(:labels, [])
old_mentioned_users = old_associations.fetch(:mentioned_users, [])
+ old_assignees = old_associations.fetch(:assignees, [])
- if has_changes?(merge_request, old_labels: old_labels)
+ if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees)
todo_service.mark_pending_todos_as_done(merge_request, current_user)
end
@@ -45,15 +45,10 @@ module MergeRequests
merge_request.target_branch)
end
- if merge_request.previous_changes.include?('assignee_id')
- reassigned_merge_request_args = [merge_request, current_user]
-
- old_assignee_id = merge_request.previous_changes['assignee_id'].first
- reassigned_merge_request_args << User.find(old_assignee_id) if old_assignee_id
-
- create_assignee_note(merge_request)
- notification_service.async.reassigned_merge_request(*reassigned_merge_request_args)
- todo_service.reassigned_merge_request(merge_request, current_user)
+ if merge_request.assignees != old_assignees
+ create_assignee_note(merge_request, old_assignees)
+ notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignees)
+ todo_service.reassigned_issuable(merge_request, current_user, old_assignees)
end
if merge_request.previous_changes.include?('target_branch') ||
@@ -81,7 +76,6 @@ module MergeRequests
)
end
end
- # rubocop:enable Metrics/AbcSize
def handle_task_changes(merge_request)
todo_service.mark_pending_todos_as_done(merge_request, current_user)
@@ -95,7 +89,7 @@ module MergeRequests
merge_request.update(merge_error: nil)
if merge_request.head_pipeline && merge_request.head_pipeline.active?
- MergeRequests::MergeWhenPipelineSucceedsService.new(project, current_user).execute(merge_request)
+ AutoMergeService.new(project, current_user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
merge_request.merge_async(current_user.id, {})
end
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index cbe5996e8ca..0fe67067eb5 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -26,17 +26,15 @@ module Milestones
private
- # rubocop: disable CodeReuse/ActiveRecord
def milestone_ids_for_merge(group_milestone)
# Pluck need to be used here instead of select so the array of ids
# is persistent after old milestones gets deleted.
@milestone_ids_for_merge ||= begin
search_params = { title: group_milestone.title, project_ids: group_project_ids, state: 'all' }
milestones = MilestonesFinder.new(search_params).execute
- milestones.pluck(:id)
+ milestones.pluck_primary_key
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def move_children_to_group_milestone(group_milestone)
milestone_ids_for_merge(group_milestone).in_groups_of(100, false) do |milestone_ids|
@@ -45,7 +43,7 @@ module Milestones
end
def check_project_milestone!(milestone)
- raise_error('Only project milestones can be promoted.') unless milestone.project_milestone?
+ raise_error(s_('PromoteMilestone|Only project milestones can be promoted.')) unless milestone.project_milestone?
end
def clone_project_milestone(milestone)
@@ -73,7 +71,7 @@ module Milestones
# rubocop: enable CodeReuse/ActiveRecord
def group
- @group ||= parent.group || raise_error('Project does not belong to a group.')
+ @group ||= parent.group || raise_error(s_('PromoteMilestone|Project does not belong to a group.'))
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -87,7 +85,7 @@ module Milestones
end
def raise_error(message)
- raise PromoteMilestoneError, "Promotion failed - #{message}"
+ raise PromoteMilestoneError, s_("PromoteMilestone|Promotion failed - %{message}") % { message: message }
end
end
end
diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb
index 81f6f92f75c..60a68568833 100644
--- a/app/services/note_summary.rb
+++ b/app/services/note_summary.rb
@@ -5,7 +5,9 @@ class NoteSummary
attr_reader :metadata
def initialize(noteable, project, author, body, action: nil, commit_count: nil)
- @note = { noteable: noteable, project: project, author: author, note: body }
+ @note = { noteable: noteable,
+ created_at: noteable.system_note_timestamp,
+ project: project, author: author, note: body }
@metadata = { action: action, commit_count: commit_count }.compact
set_commit_params if note[:noteable].is_a?(Commit)
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 5a6e7338b42..1b46f6d8a72 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -21,7 +21,7 @@ module Notes
if quick_actions_service.supported?(note)
options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
- content, command_params = quick_actions_service.extract_commands(note, options)
+ content, update_params = quick_actions_service.execute(note, options)
only_commands = content.empty?
@@ -43,16 +43,17 @@ module Notes
Suggestions::CreateService.new(note).execute
end
- if command_params.present?
- quick_actions_service.execute(command_params, note)
+ if quick_actions_service.commands_executed_count.to_i > 0
+ if update_params.present?
+ quick_actions_service.apply_updates(update_params, note)
+ note.commands_changes = update_params
+ end
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
note.errors.add(:commands_only, 'Commands applied')
end
-
- note.commands_changes = command_params
end
note
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 985a03060bd..0852a708240 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -1,7 +1,18 @@
# frozen_string_literal: true
+# QuickActionsService class
+#
+# Executes quick actions commands extracted from note text
+#
+# Most commands returns parameters to be applied later
+# using QuickActionService#apply_updates
+#
module Notes
class QuickActionsService < BaseService
+ attr_reader :interpret_service
+
+ delegate :commands_executed_count, to: :interpret_service, allow_nil: true
+
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
'MergeRequest' => MergeRequests::UpdateService,
@@ -25,18 +36,21 @@ module Notes
self.class.supported?(note)
end
- def extract_commands(note, options = {})
+ def execute(note, options = {})
return [note.note, {}] unless supported?(note)
- QuickActions::InterpretService.new(project, current_user, options)
- .execute(note.note, note.noteable)
+ @interpret_service = QuickActions::InterpretService.new(project, current_user, options)
+
+ @interpret_service.execute(note.note, note.noteable)
end
- def execute(command_params, note)
- return if command_params.empty?
+ # Applies updates extracted to note#noteable
+ # The update parameters are extracted on self#execute
+ def apply_updates(update_params, note)
+ return if update_params.empty?
return unless supported?(note)
- self.class.noteable_update_service(note).new(note.parent, current_user, command_params).execute(note.noteable)
+ self.class.noteable_update_service(note).new(note.parent, current_user, update_params).execute(note.noteable)
end
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index d2052bed646..384d1dd2e50 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -22,7 +22,7 @@ module Notes
# We need to refresh the previous suggestions call cache
# in order to get the new records.
- note.reload
+ note.reset
end
note
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 56f11b31110..ca3f0b73096 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -135,7 +135,7 @@ module NotificationRecipientService
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)
- add_recipients(user_scope.where(id: user_ids), :watch, nil)
+ add_recipients(user_scope.where(id: user_ids), :custom, nil)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -247,15 +247,15 @@ module NotificationRecipientService
attr_reader :target
attr_reader :current_user
attr_reader :action
- attr_reader :previous_assignee
+ attr_reader :previous_assignees
attr_reader :skip_current_user
- def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true)
+ def initialize(target, current_user, action:, custom_action: nil, previous_assignees: nil, skip_current_user: true)
@target = target
@current_user = current_user
@action = action
@custom_action = custom_action
- @previous_assignee = previous_assignee
+ @previous_assignees = previous_assignees
@skip_current_user = skip_current_user
end
@@ -270,11 +270,7 @@ module NotificationRecipientService
# Re-assign is considered as a mention of the new assignee
case custom_action
- when :reassign_merge_request
- add_recipients(previous_assignee, :mention, nil)
- add_recipients(target.assignee, :mention, NotificationReason::ASSIGNED)
- when :reassign_issue
- previous_assignees = Array(previous_assignee)
+ when :reassign_merge_request, :reassign_issue
add_recipients(previous_assignees, :mention, nil)
add_recipients(target.assignees, :mention, NotificationReason::ASSIGNED)
end
@@ -287,17 +283,11 @@ module NotificationRecipientService
# receive them, too.
add_mentions(current_user, target: target)
- # Add the assigned users, if any
- assignees = case custom_action
- when :new_issue
- target.assignees
- else
- target.assignee
- end
-
# We use the `:participating` notification level in order to match existing legacy behavior as captured
# in existing specs (notification_service_spec.rb ~ line 507)
- add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
+ if target.is_a?(Issuable)
+ add_recipients(target.assignees, :participating, NotificationReason::ASSIGNED)
+ end
add_labels_subscribers
end
@@ -401,7 +391,7 @@ module NotificationRecipientService
def build!
return [] unless project
- add_recipients(project.team.maintainers, :watch, nil)
+ add_recipients(project.team.maintainers, :mention, nil)
end
def acting_user
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 1a65561dd70..5aa804666f0 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -89,14 +89,14 @@ class NotificationService
# * project team members with notification level higher then Participating
# * users with custom level checked with "close issue"
#
- def close_issue(issue, current_user)
- close_resource_email(issue, current_user, :closed_issue_email)
+ def close_issue(issue, current_user, closed_via: nil)
+ close_resource_email(issue, current_user, :closed_issue_email, closed_via: closed_via)
end
# When we reassign an issue we should send an email to:
#
- # * issue old assignee if their notification level is not Disabled
- # * issue new assignee if their notification level is not Disabled
+ # * issue old assignees if their notification level is not Disabled
+ # * issue new assignees if their notification level is not Disabled
# * users with custom level checked with "reassign issue"
#
def reassigned_issue(issue, current_user, previous_assignees = [])
@@ -104,7 +104,7 @@ class NotificationService
issue,
current_user,
action: "reassign",
- previous_assignee: previous_assignees
+ previous_assignees: previous_assignees
)
previous_assignee_ids = previous_assignees.map(&:id)
@@ -140,7 +140,7 @@ class NotificationService
# When create a merge request we should send an email to:
#
# * mr author
- # * mr assignee if their notification level is not Disabled
+ # * mr assignees if their notification level is not Disabled
# * project team members with notification level higher then Participating
# * watchers of the mr's labels
# * users with custom level checked with "new merge request"
@@ -184,23 +184,25 @@ class NotificationService
# When we reassign a merge_request we should send an email to:
#
- # * merge_request old assignee if their notification level is not Disabled
- # * merge_request assignee if their notification level is not Disabled
+ # * merge_request old assignees if their notification level is not Disabled
+ # * merge_request new assignees if their notification level is not Disabled
# * users with custom level checked with "reassign merge request"
#
- def reassigned_merge_request(merge_request, current_user, previous_assignee = nil)
+ def reassigned_merge_request(merge_request, current_user, previous_assignees = [])
recipients = NotificationRecipientService.build_recipients(
merge_request,
current_user,
action: "reassign",
- previous_assignee: previous_assignee
+ previous_assignees: previous_assignees
)
+ previous_assignee_ids = previous_assignees.map(&:id)
+
recipients.each do |recipient|
mailer.reassigned_merge_request_email(
recipient.user.id,
merge_request.id,
- previous_assignee&.id,
+ previous_assignee_ids,
current_user.id,
recipient.reason
).deliver_later
@@ -236,7 +238,7 @@ class NotificationService
merge_request,
current_user,
:merged_merge_request_email,
- skip_current_user: !merge_request.merge_when_pipeline_succeeds?
+ skip_current_user: !merge_request.auto_merge_enabled?
)
end
@@ -502,7 +504,7 @@ class NotificationService
end
end
- def close_resource_email(target, current_user, method, skip_current_user: true)
+ def close_resource_email(target, current_user, method, skip_current_user: true, closed_via: nil)
action = method == :merged_merge_request_email ? "merge" : "close"
recipients = NotificationRecipientService.build_recipients(
@@ -513,7 +515,7 @@ class NotificationService
)
recipients.each do |recipient|
- mailer.send(method, recipient.user.id, target.id, current_user.id, recipient.reason).deliver_later
+ mailer.send(method, recipient.user.id, target.id, current_user.id, reason: recipient.reason, closed_via: closed_via).deliver_later
end
end
diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb
new file mode 100644
index 00000000000..c600f497fa5
--- /dev/null
+++ b/app/services/pages_domains/create_acme_order_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class CreateAcmeOrderService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ lets_encrypt_client = Gitlab::LetsEncrypt::Client.new
+ order = lets_encrypt_client.new_order(pages_domain.domain)
+
+ challenge = order.new_challenge
+
+ private_key = OpenSSL::PKey::RSA.new(4096)
+ saved_order = pages_domain.acme_orders.create!(
+ url: order.url,
+ expires_at: order.expires,
+ private_key: private_key.to_pem,
+
+ challenge_token: challenge.token,
+ challenge_file_content: challenge.file_content
+ )
+
+ challenge.request_validation
+ saved_order
+ end
+ end
+end
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
new file mode 100644
index 00000000000..2dfe1a3d8ca
--- /dev/null
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class ObtainLetsEncryptCertificateService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ pages_domain.acme_orders.expired.delete_all
+ acme_order = pages_domain.acme_orders.first
+
+ unless acme_order
+ ::PagesDomains::CreateAcmeOrderService.new(pages_domain).execute
+ return
+ end
+
+ api_order = ::Gitlab::LetsEncrypt::Client.new.load_order(acme_order.url)
+
+ # https://tools.ietf.org/html/rfc8555#section-7.1.6 - statuses diagram
+ case api_order.status
+ when 'ready'
+ api_order.request_certificate(private_key: acme_order.private_key, domain: pages_domain.domain)
+ when 'valid'
+ save_certificate(acme_order.private_key, api_order)
+ acme_order.destroy!
+ # when 'invalid'
+ # TODO: implement error handling
+ end
+ end
+
+ private
+
+ def save_certificate(private_key, api_order)
+ certificate = api_order.certificate
+ pages_domain.update!(key: private_key, certificate: certificate)
+ end
+ end
+end
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index c1655c38095..2b4c4ae68e2 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -17,7 +17,7 @@ class PreviewMarkdownService < BaseService
private
def explain_quick_actions(text)
- return text, [] unless %w(Issue MergeRequest Commit).include?(commands_target_type)
+ return text, [] unless %w(Issue MergeRequest Commit).include?(target_type)
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
quick_actions_service.explain(text, find_commands_target)
@@ -30,22 +30,36 @@ class PreviewMarkdownService < BaseService
end
def find_suggestions(text)
- return [] unless params[:preview_suggestions]
+ return [] unless preview_sugestions?
- Banzai::SuggestionsParser.parse(text)
+ position = Gitlab::Diff::Position.new(new_path: params[:file_path],
+ new_line: params[:line].to_i,
+ base_sha: params[:base_sha],
+ head_sha: params[:head_sha],
+ start_sha: params[:start_sha])
+
+ Gitlab::Diff::SuggestionsParser.parse(text, position: position,
+ project: project,
+ supports_suggestion: params[:preview_suggestions])
+ end
+
+ def preview_sugestions?
+ params[:preview_suggestions] &&
+ target_type == 'MergeRequest' &&
+ Ability.allowed?(current_user, :download_code, project)
end
def find_commands_target
QuickActions::TargetService
.new(project, current_user)
- .execute(commands_target_type, commands_target_id)
+ .execute(target_type, target_id)
end
- def commands_target_type
- params[:quick_actions_target_type]
+ def target_type
+ params[:target_type]
end
- def commands_target_id
- params[:quick_actions_target_id]
+ def target_id
+ params[:target_id]
end
end
diff --git a/app/services/projects/cleanup_service.rb b/app/services/projects/cleanup_service.rb
index 12103ea34b5..5972bfd4071 100644
--- a/app/services/projects/cleanup_service.rb
+++ b/app/services/projects/cleanup_service.rb
@@ -18,9 +18,6 @@ module Projects
# per rewritten object, with the old and new SHAs space-separated. It can be
# used to update or remove content that references the objects that BFG has
# altered
- #
- # Currently, only the project repository is modified by this service, but we
- # may wish to modify other data sources in the future.
def execute
apply_bfg_object_map!
@@ -41,10 +38,52 @@ module Projects
raise NoUploadError unless project.bfg_object_map.exists?
project.bfg_object_map.open do |io|
- repository_cleaner.apply_bfg_object_map(io)
+ repository_cleaner.apply_bfg_object_map_stream(io) do |response|
+ cleanup_diffs(response)
+ end
+ end
+ end
+
+ def cleanup_diffs(response)
+ old_commit_shas = extract_old_commit_shas(response.entries)
+
+ ActiveRecord::Base.transaction do
+ cleanup_merge_request_diffs(old_commit_shas)
+ cleanup_note_diff_files(old_commit_shas)
end
end
+ def extract_old_commit_shas(batch)
+ batch.lazy.select { |entry| entry.type == :COMMIT }.map(&:old_oid).force
+ end
+
+ def cleanup_merge_request_diffs(old_commit_shas)
+ merge_request_diffs = MergeRequestDiff
+ .by_project_id(project.id)
+ .by_commit_sha(old_commit_shas)
+
+ # It's important to run the ActiveRecord callbacks here
+ merge_request_diffs.destroy_all # rubocop:disable Cop/DestroyAll
+
+ # TODO: ensure the highlight cache is removed immediately. It's too hard
+ # to calculate the Redis keys at present.
+ #
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/61115
+ end
+
+ def cleanup_note_diff_files(old_commit_shas)
+ # Pluck the IDs instead of running the query twice to ensure we clear the
+ # cache for exactly the note diffs we remove
+ ids = NoteDiffFile
+ .referencing_sha(old_commit_shas, project_id: project.id)
+ .pluck_primary_key
+
+ NoteDiffFile.id_in(ids).delete_all
+
+ # A highlighted version of the diff is stored in redis. Remove it now.
+ Gitlab::DiscussionsDiff::HighlightCache.clear_multiple(ids)
+ end
+
def repository_cleaner
@repository_cleaner ||= Gitlab::Git::RepositoryCleaner.new(repository.raw)
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index d03137b63b2..9f335cceb67 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -2,6 +2,8 @@
module Projects
class CreateService < BaseService
+ include ValidatesClassificationLabel
+
def initialize(user, params)
@current_user, @params = user, params.dup
@skip_wiki = @params.delete(:skip_wiki)
@@ -45,6 +47,8 @@ module Projects
relations_block&.call(@project)
yield(@project) if block_given?
+ validate_classification_label(@project, :external_authorization_classification_label)
+
# If the block added errors, don't try to save the project
return @project if @project.errors.any?
@@ -96,8 +100,6 @@ module Projects
current_user.invalidate_personal_projects_count
create_readme if @initialize_with_readme
-
- configure_group_clusters_for_project
end
# Refresh the current user's authorizations inline (so they can access the
@@ -123,10 +125,6 @@ module Projects
Files::CreateService.new(@project, current_user, commit_attrs).execute
end
- def configure_group_clusters_for_project
- ClusterProjectConfigureWorker.perform_async(@project.id)
- end
-
def skip_wiki?
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
@@ -155,8 +153,8 @@ module Projects
log_message << " Project ID: #{@project.id}" if @project&.id
Rails.logger.error(log_message)
- if @project
- @project.import_state.mark_as_failed(message) if @project.persisted? && @project.import?
+ if @project && @project.persisted? && @project.import_state
+ @project.import_state.mark_as_failed(message)
end
@project
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index b14b31302f5..d8e670e40ce 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -61,11 +61,11 @@ module Projects
flush_caches(@project)
unless rollback_repository(removal_path(repo_path), repo_path)
- raise_error('Failed to restore project repository. Please contact the administrator.')
+ raise_error(s_('DeleteProject|Failed to restore project repository. Please contact the administrator.'))
end
unless rollback_repository(removal_path(wiki_path), wiki_path)
- raise_error('Failed to restore wiki repository. Please contact the administrator.')
+ raise_error(s_('DeleteProject|Failed to restore wiki repository. Please contact the administrator.'))
end
end
@@ -81,11 +81,11 @@ module Projects
def trash_repositories!
unless remove_repository(repo_path)
- raise_error('Failed to remove project repository. Please try again or contact administrator.')
+ raise_error(s_('DeleteProject|Failed to remove project repository. Please try again or contact administrator.'))
end
unless remove_repository(wiki_path)
- raise_error('Failed to remove wiki repository. Please try again or contact administrator.')
+ raise_error(s_('DeleteProject|Failed to remove wiki repository. Please try again or contact administrator.'))
end
end
@@ -148,7 +148,7 @@ module Projects
def attempt_destroy_transaction(project)
unless remove_registry_tags
- raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
+ raise_error(s_('DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator.'))
end
project.leave_pool_repository
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 4a837a4fb6a..d3680637217 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -2,7 +2,7 @@
module Projects
class DetectRepositoryLanguagesService < BaseService
- attr_reader :detected_repository_languages, :programming_languages
+ attr_reader :programming_languages
# rubocop: disable CodeReuse/ActiveRecord
def execute
@@ -25,9 +25,11 @@ module Projects
RepositoryLanguage.table_name,
detection.insertions(matching_programming_languages)
)
+
+ set_detected_repository_languages
end
- project.repository_languages.reload
+ project.repository_languages.reset
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -56,5 +58,11 @@ module Projects
retry
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def set_detected_repository_languages
+ return if project.detected_repository_languages?
+
+ project.update_column(:detected_repository_languages, true)
+ end
end
end
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index dd297c9ba43..aba175eb79b 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -11,7 +11,7 @@ module Projects
end
def execute
- return nil unless valid_url?(@url)
+ return unless valid_url?(@url)
uploader = FileUploader.new(@project)
uploader.download!(@url)
diff --git a/app/services/projects/fetch_statistics_increment_service.rb b/app/services/projects/fetch_statistics_increment_service.rb
new file mode 100644
index 00000000000..8644e6bf313
--- /dev/null
+++ b/app/services/projects/fetch_statistics_increment_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Projects
+ class FetchStatisticsIncrementService
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ increment_fetch_count_sql = <<~SQL
+ INSERT INTO #{table_name} (project_id, date, fetch_count)
+ VALUES (#{project.id}, '#{Date.today}', 1)
+ SQL
+
+ increment_fetch_count_sql += if Gitlab::Database.postgresql?
+ "ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1"
+ else
+ "ON DUPLICATE KEY UPDATE fetch_count = #{table_name}.fetch_count + 1"
+ end
+
+ ActiveRecord::Base.connection.execute(increment_fetch_count_sql)
+ end
+
+ private
+
+ def table_name
+ ProjectDailyStatistic.table_name
+ end
+ end
+end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index fc234bafc57..0b4ab7b8e4d 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -36,18 +36,22 @@ module Projects
def fork_new_project
new_params = {
- visibility_level: allowed_visibility_level,
- description: @project.description,
- name: target_name,
- path: target_path,
- shared_runners_enabled: @project.shared_runners_enabled,
- namespace_id: target_namespace.id,
- fork_network: fork_network,
+ visibility_level: allowed_visibility_level,
+ description: @project.description,
+ name: target_name,
+ path: target_path,
+ shared_runners_enabled: @project.shared_runners_enabled,
+ namespace_id: target_namespace.id,
+ fork_network: fork_network,
+ # We need to set default_git_depth to 0 for the forked project when
+ # @project.default_git_depth is nil in order to keep the same behaviour
+ # and not get ProjectCiCdSetting::DEFAULT_GIT_DEPTH set on create
+ ci_cd_settings_attributes: { default_git_depth: @project.default_git_depth || 0 },
# We need to assign the fork network membership after the project has
# been instantiated to avoid ActiveRecord trying to create it when
# initializing the project, as that would cause a foreign key constraint
# exception.
- relations_block: -> (project) { build_fork_network_member(project) }
+ relations_block: -> (project) { build_fork_network_member(project) }
}
if @project.avatar.present? && @project.avatar.image?
diff --git a/app/services/projects/git_deduplication_service.rb b/app/services/projects/git_deduplication_service.rb
new file mode 100644
index 00000000000..74d469ecf37
--- /dev/null
+++ b/app/services/projects/git_deduplication_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Projects
+ class GitDeduplicationService < BaseService
+ include ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 86400
+
+ delegate :pool_repository, to: :project
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ try_obtain_lease do
+ unless project.has_pool_repository?
+ disconnect_git_alternates
+ break
+ end
+
+ if source_project? && pool_can_fetch_from_source?
+ fetch_from_source
+ end
+
+ project.link_pool_repository if same_storage_as_pool?(project.repository)
+ end
+ end
+
+ private
+
+ def disconnect_git_alternates
+ project.repository.disconnect_alternates
+ end
+
+ def pool_can_fetch_from_source?
+ project.git_objects_poolable? &&
+ same_storage_as_pool?(pool_repository.source_project.repository)
+ end
+
+ def same_storage_as_pool?(repository)
+ pool_repository.object_pool.repository.storage == repository.storage
+ end
+
+ def fetch_from_source
+ project.pool_repository.object_pool.fetch
+ end
+
+ def source_project?
+ return unless project.has_pool_repository?
+
+ project.pool_repository.source_project == project
+ end
+
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+
+ def lease_key
+ "git_deduplication:#{project.id}"
+ end
+ end
+end
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index 1392775f805..e3d5bea0852 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -4,13 +4,19 @@ module Projects
module GroupLinks
class CreateService < BaseService
def execute(group)
- return false unless group
+ return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group)
- project.project_group_links.create(
+ link = project.project_group_links.new(
group: group,
group_access: params[:link_group_access],
expires_at: params[:expires_at]
)
+
+ if link.save
+ success(link: link)
+ else
+ error(link.errors.full_messages.to_sentence, 409)
+ end
end
end
end
diff --git a/app/services/projects/hashed_storage/base_attachment_service.rb b/app/services/projects/hashed_storage/base_attachment_service.rb
new file mode 100644
index 00000000000..828ab616bab
--- /dev/null
+++ b/app/services/projects/hashed_storage/base_attachment_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ AttachmentMigrationError = Class.new(StandardError)
+
+ AttachmentCannotMoveError = Class.new(StandardError)
+
+ class BaseAttachmentService < BaseService
+ # Returns the disk_path value before the execution
+ attr_reader :old_disk_path
+
+ # Returns the disk_path value after the execution
+ attr_reader :new_disk_path
+
+ # Returns the logger currently in use
+ attr_reader :logger
+
+ # Return whether this operation was skipped or not
+ #
+ # @return [Boolean] true if skipped of false otherwise
+ def skipped?
+ @skipped
+ end
+
+ protected
+
+ def move_folder!(old_path, new_path)
+ unless File.directory?(old_path)
+ logger.info("Skipped attachments move from '#{old_path}' to '#{new_path}', source path doesn't exist or is not a directory (PROJECT_ID=#{project.id})")
+ @skipped = true
+
+ return true
+ end
+
+ if File.exist?(new_path)
+ logger.error("Cannot move attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
+ raise AttachmentCannotMoveError, "Target path '#{new_path}' already exists"
+ end
+
+ # Create base path folder on the new storage layout
+ FileUtils.mkdir_p(File.dirname(new_path))
+
+ FileUtils.mv(old_path, new_path)
+ logger.info("Project attachments moved from '#{old_path}' to '#{new_path}' (PROJECT_ID=#{project.id})")
+
+ true
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/base_repository_service.rb b/app/services/projects/hashed_storage/base_repository_service.rb
index 761c81d776f..f97a28b8c3b 100644
--- a/app/services/projects/hashed_storage/base_repository_service.rb
+++ b/app/services/projects/hashed_storage/base_repository_service.rb
@@ -2,11 +2,8 @@
module Projects
module HashedStorage
- # Returned when there is an error with the Hashed Storage migration
- RepositoryMigrationError = Class.new(StandardError)
-
- # Returned when there is an error with the Hashed Storage rollback
- RepositoryRollbackError = Class.new(StandardError)
+ # Returned when repository can't be made read-only because there is already a git transfer in progress
+ RepositoryInUseError = Class.new(StandardError)
class BaseRepositoryService < BaseService
include Gitlab::ShellAdapter
@@ -38,7 +35,10 @@ module Projects
# project was not originally empty.
if !from_exists && !to_exists
logger.warn "Can't find a repository on either source or target paths for #{project.full_path} (ID=#{project.id}) ..."
- return false
+
+ # We return true so we still reflect the change in the database.
+ # Next time the repository is (re)created it will be under the new storage layout
+ return true
elsif !from_exists
# Repository have been moved already.
return true
@@ -52,6 +52,16 @@ module Projects
move_repository(new_disk_path, old_disk_path)
move_repository("#{new_disk_path}.wiki", old_wiki_disk_path)
end
+
+ def try_to_set_repository_read_only!
+ # Mitigate any push operation to start during migration
+ unless project.set_repository_read_only!
+ migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
+ logger.error migration_error
+
+ raise RepositoryInUseError, migration_error
+ end
+ end
end
end
end
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index 03e0685d2cd..3d0b8f58612 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -2,62 +2,37 @@
module Projects
module HashedStorage
- AttachmentMigrationError = Class.new(StandardError)
-
- class MigrateAttachmentsService < BaseService
- attr_reader :logger, :old_disk_path, :new_disk_path
-
+ class MigrateAttachmentsService < BaseAttachmentService
def initialize(project, old_disk_path, logger: nil)
@project = project
@logger = logger || Rails.logger
@old_disk_path = old_disk_path
- @new_disk_path = project.disk_path
@skipped = false
end
def execute
origin = FileUploader.absolute_base_dir(project)
- # It's possible that old_disk_path does not match project.disk_path. For example, that happens when we rename a project
+ # It's possible that old_disk_path does not match project.disk_path.
+ # For example, that happens when we rename a project
origin.sub!(/#{Regexp.escape(project.full_path)}\z/, old_disk_path)
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:attachments]
target = FileUploader.absolute_base_dir(project)
- result = move_folder!(origin, target)
- project.save!
-
- if result && block_given?
- yield
- end
-
- result
- end
-
- def skipped?
- @skipped
- end
+ @new_disk_path = project.disk_path
- private
+ result = move_folder!(origin, target)
- def move_folder!(old_path, new_path)
- unless File.directory?(old_path)
- logger.info("Skipped attachments migration from '#{old_path}' to '#{new_path}', source path doesn't exist or is not a directory (PROJECT_ID=#{project.id})")
- @skipped = true
- return true
- end
+ if result
+ project.save!(validate: false)
- if File.exist?(new_path)
- logger.error("Cannot migrate attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
- raise AttachmentMigrationError, "Target path '#{new_path}' already exist"
+ yield if block_given?
+ else
+ # Rollback changes
+ project.rollback!
end
- # Create hashed storage base path folder
- FileUtils.mkdir_p(File.dirname(new_path))
-
- FileUtils.mv(old_path, new_path)
- logger.info("Migrated project attachments from '#{old_path}' to '#{new_path}' (PROJECT_ID=#{project.id})")
-
- true
+ result
end
end
end
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
index 9c672283c7e..e8393128d58 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -15,7 +15,7 @@ module Projects
result = move_repository(old_disk_path, new_disk_path)
if move_wiki
- result &&= move_repository("#{old_wiki_disk_path}", "#{new_disk_path}.wiki")
+ result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
end
if result
@@ -27,7 +27,7 @@ module Projects
end
project.repository_read_only = false
- project.save!
+ project.save!(validate: false)
if result && block_given?
yield
@@ -35,18 +35,6 @@ module Projects
result
end
-
- private
-
- def try_to_set_repository_read_only!
- # Mitigate any push operation to start during migration
- unless project.set_repository_read_only!
- migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
- logger.error migration_error
-
- raise RepositoryMigrationError, migration_error
- end
- end
end
end
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
new file mode 100644
index 00000000000..5c6b92f965c
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_attachments_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackAttachmentsService < BaseAttachmentService
+ def initialize(project, logger: nil)
+ @project = project
+ @logger = logger || Rails.logger
+ @old_disk_path = project.disk_path
+ end
+
+ def execute
+ origin = FileUploader.absolute_base_dir(project)
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+ target = FileUploader.absolute_base_dir(project)
+
+ @new_disk_path = FileUploader.base_dir(project)
+
+ result = move_folder!(origin, target)
+
+ if result
+ project.save!(validate: false)
+
+ yield if block_given?
+ else
+ # Rollback changes
+ project.rollback!
+ end
+
+ result
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/rollback_repository_service.rb b/app/services/projects/hashed_storage/rollback_repository_service.rb
new file mode 100644
index 00000000000..67733f4770b
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_repository_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackRepositoryService < BaseRepositoryService
+ def execute
+ try_to_set_repository_read_only!
+
+ @old_storage_version = project.storage_version
+ project.storage_version = nil
+ project.ensure_storage_path_exists
+
+ @new_disk_path = project.disk_path
+
+ result = move_repository(old_disk_path, new_disk_path)
+
+ if move_wiki
+ result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
+ end
+
+ if result
+ project.write_repository_config
+ project.track_project_repository
+ else
+ rollback_folder_move
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+ end
+
+ project.repository_read_only = false
+ project.save!(validate: false)
+
+ if result && block_given?
+ yield
+ end
+
+ result
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
new file mode 100644
index 00000000000..25767f5de5e
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackService < BaseService
+ attr_reader :logger, :old_disk_path
+
+ def initialize(project, old_disk_path, logger: nil)
+ @project = project
+ @old_disk_path = old_disk_path
+ @logger = logger || Rails.logger
+ end
+
+ def execute
+ # Rollback attachments from Hashed Storage to Legacy
+ if project.hashed_storage?(:attachments)
+ return false unless rollback_attachments
+ end
+
+ # Rollback repository from Hashed Storage to Legacy
+ if project.hashed_storage?(:repository)
+ rollback_repository
+ end
+ end
+
+ private
+
+ def rollback_attachments
+ HashedStorage::RollbackAttachmentsService.new(project, logger: logger).execute
+ end
+
+ def rollback_repository
+ HashedStorage::RollbackRepositoryService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ end
+end
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index 2f6dc4207dd..9428575591e 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -11,6 +11,7 @@ module Projects
class HousekeepingService < BaseService
# Timeout set to 24h
LEASE_TIMEOUT = 86400
+ PACK_REFS_PERIOD = 6
class LeaseTaken < StandardError
def to_s
@@ -18,8 +19,9 @@ module Projects
end
end
- def initialize(project)
+ def initialize(project, task = nil)
@project = project
+ @task = task
end
def execute
@@ -69,17 +71,21 @@ module Projects
end
def task
+ return @task if @task
+
if pushes_since_gc % gc_period == 0
:gc
elsif pushes_since_gc % full_repack_period == 0
:full_repack
- else
+ elsif pushes_since_gc % repack_period == 0
:incremental_repack
+ else
+ :pack_refs
end
end
def period_match?
- [gc_period, full_repack_period, repack_period].any? { |period| pushes_since_gc % period == 0 }
+ [gc_period, full_repack_period, repack_period, PACK_REFS_PERIOD].any? { |period| pushes_since_gc % period == 0 }
end
def housekeeping_enabled?
diff --git a/app/services/projects/import_error_filter.rb b/app/services/projects/import_error_filter.rb
index a0fc5149bb4..737b794484d 100644
--- a/app/services/projects/import_error_filter.rb
+++ b/app/services/projects/import_error_filter.rb
@@ -4,7 +4,7 @@ module Projects
# Used by project imports, it removes any potential paths
# included in an error message that could be stored in the DB
class ImportErrorFilter
- ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/
+ ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/.freeze
FILTER_MESSAGE = '[FILTERED]'
def self.filter_message(message)
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 7214e9efaf6..073c14040ce 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -27,13 +27,13 @@ module Projects
rescue Gitlab::UrlBlocker::BlockedUrlError => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
- error("Error importing repository #{project.safe_import_url} into #{project.full_path} - #{e.message}")
+ error(s_("ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}") % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: e.message })
rescue => e
message = Projects::ImportErrorFilter.filter_message(e.message)
Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
- error("Error importing repository #{project.safe_import_url} into #{project.full_path} - #{message}")
+ error(s_("ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}") % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: message })
end
private
@@ -43,7 +43,7 @@ module Projects
begin
Gitlab::UrlBlocker.validate!(project.import_url, ports: Project::VALID_IMPORT_PORTS)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
- raise e, "Blocked import URL: #{e.message}"
+ raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message }
end
end
@@ -61,7 +61,7 @@ module Projects
def create_repository
unless project.create_repository
- raise Error, 'The repository could not be created.'
+ raise Error, s_('ImportProjects|The repository could not be created.')
end
end
@@ -94,16 +94,13 @@ module Projects
return unless project.lfs_enabled?
- lfs_objects_to_download = Projects::LfsPointers::LfsImportService.new(project).execute
+ result = Projects::LfsPointers::LfsImportService.new(project).execute
- lfs_objects_to_download.each do |lfs_download_object|
- Projects::LfsPointers::LfsDownloadService.new(project, lfs_download_object)
- .execute
+ if result[:status] == :error
+ # To avoid aborting the importing process, we silently fail
+ # if any exception raises.
+ Gitlab::AppLogger.error("The Lfs import process failed. #{result[:message]}")
end
- rescue => e
- # Right now, to avoid aborting the importing process, we silently fail
- # if any exception raises.
- Rails.logger.error("The Lfs import process failed. #{e.message}")
end
def import_data
@@ -112,7 +109,7 @@ module Projects
project.repository.expire_content_cache unless project.gitlab_project_import?
unless importer.execute
- raise Error, 'The remote data could not be imported.'
+ raise Error, s_('ImportProjects|The remote data could not be imported.')
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
index 7998976b00a..9b72480d18b 100644
--- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
@@ -21,9 +21,9 @@ module Projects
# This method accepts two parameters:
# - oids: hash of oids to query. The structure is { lfs_file_oid => lfs_file_size }
#
- # Returns a hash with the structure { lfs_file_oids => download_link }
+ # Returns an array of LfsDownloadObject
def execute(oids)
- return {} unless project&.lfs_enabled? && remote_uri && oids.present?
+ return [] unless project&.lfs_enabled? && remote_uri && oids.present?
get_download_links(oids)
end
@@ -37,22 +37,30 @@ module Projects
raise DownloadLinksError, response.message unless response.success?
- parse_response_links(response['objects'])
+ # Since the LFS Batch API may return a Content-Ttpe of
+ # application/vnd.git-lfs+json
+ # (https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests),
+ # HTTParty does not know this is actually JSON.
+ data = JSON.parse(response.body)
+
+ raise DownloadLinksError, "LFS Batch API did return any objects" unless data.is_a?(Hash) && data.key?('objects')
+
+ parse_response_links(data['objects'])
+ rescue JSON::ParserError
+ raise DownloadLinksError, "LFS Batch API response is not JSON"
end
def parse_response_links(objects_response)
objects_response.each_with_object([]) do |entry, link_list|
- begin
- link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
+ link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
- raise DownloadLinkNotFound unless link
+ raise DownloadLinkNotFound unless link
- link_list << LfsDownloadObject.new(oid: entry['oid'],
- size: entry['size'],
- link: add_credentials(link))
- rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
- log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
- end
+ link_list << LfsDownloadObject.new(oid: entry['oid'],
+ size: entry['size'],
+ link: add_credentials(link))
+ rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
+ log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb
index 398f00a598d..a009f479d5d 100644
--- a/app/services/projects/lfs_pointers/lfs_download_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_service.rb
@@ -75,17 +75,15 @@ module Projects
create_tmp_storage_dir
File.open(tmp_filename, 'wb') do |file|
- begin
- yield file
- rescue StandardError => e
- # If the lfs file is successfully downloaded it will be removed
- # when it is added to the project's lfs files.
- # Nevertheless if any excetion raises the file would remain
- # in the file system. Here we ensure to remove it
- File.unlink(file) if File.exist?(file)
-
- raise e
- end
+ yield file
+ rescue StandardError => e
+ # If the lfs file is successfully downloaded it will be removed
+ # when it is added to the project's lfs files.
+ # Nevertheless if any excetion raises the file would remain
+ # in the file system. Here we ensure to remove it
+ File.unlink(file) if File.exist?(file)
+
+ raise e
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_import_service.rb b/app/services/projects/lfs_pointers/lfs_import_service.rb
index 9215fa0a7bf..2afcce7099b 100644
--- a/app/services/projects/lfs_pointers/lfs_import_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_import_service.rb
@@ -1,95 +1,23 @@
# frozen_string_literal: true
-# This service manages the whole worflow of discovering the Lfs files in a
-# repository, linking them to the project and downloading (and linking) the non
-# existent ones.
+# This service is responsible of managing the retrieval of the lfs objects,
+# and call the service LfsDownloadService, which performs the download
+# for each of the retrieved lfs objects
module Projects
module LfsPointers
class LfsImportService < BaseService
- include Gitlab::Utils::StrongMemoize
-
- HEAD_REV = 'HEAD'.freeze
- LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
- LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze
-
- LfsImportError = Class.new(StandardError)
-
def execute
- return {} unless project&.lfs_enabled?
+ return success unless project&.lfs_enabled?
- if external_lfs_endpoint?
- # If the endpoint host is different from the import_url it means
- # that the repo is using a third party service for storing the LFS files.
- # In this case, we have to disable lfs in the project
- disable_lfs!
+ lfs_objects_to_download = LfsObjectDownloadListService.new(project).execute
- return {}
+ lfs_objects_to_download.each do |lfs_download_object|
+ LfsDownloadService.new(project, lfs_download_object).execute
end
- get_download_links
- rescue LfsDownloadLinkListService::DownloadLinksError => e
- raise LfsImportError, "The LFS objects download list couldn't be imported. Error: #{e.message}"
- end
-
- private
-
- def external_lfs_endpoint?
- lfsconfig_endpoint_uri && lfsconfig_endpoint_uri.host != import_uri.host
- end
-
- def disable_lfs!
- project.update(lfs_enabled: false)
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def get_download_links
- existent_lfs = LfsListService.new(project).execute
- linked_oids = LfsLinkService.new(project).execute(existent_lfs.keys)
-
- # Retrieving those oids not linked and which we need to download
- not_linked_lfs = existent_lfs.except(*linked_oids)
-
- LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(not_linked_lfs)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def lfsconfig_endpoint_uri
- strong_memoize(:lfsconfig_endpoint_uri) do
- # Retrieveing the blob data from the .lfsconfig file
- data = project.repository.lfsconfig_for(HEAD_REV)
- # Parsing the data to retrieve the url
- parsed_data = data&.match(LFS_ENDPOINT_PATTERN)
-
- if parsed_data
- URI.parse(parsed_data[1]).tap do |endpoint|
- endpoint.user ||= import_uri.user
- endpoint.password ||= import_uri.password
- end
- end
- end
- rescue URI::InvalidURIError
- raise LfsImportError, 'Invalid URL in .lfsconfig file'
- end
-
- def import_uri
- @import_uri ||= URI.parse(project.import_url)
- rescue URI::InvalidURIError
- raise LfsImportError, 'Invalid project import URL'
- end
-
- def current_endpoint_uri
- (lfsconfig_endpoint_uri || default_endpoint_uri)
- end
-
- # The import url must end with '.git' here we ensure it is
- def default_endpoint_uri
- @default_endpoint_uri ||= begin
- import_uri.dup.tap do |uri|
- path = uri.path.gsub(%r(/$), '')
- path += '.git' unless path.ends_with?('.git')
- uri.path = path + LFS_BATCH_API_ENDPOINT
- end
- end
+ success
+ rescue => e
+ error(e.message)
end
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index 8401f3d1d89..e3c956250f0 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -6,9 +6,9 @@ module Projects
class LfsLinkService < BaseService
# Accept an array of oids to link
#
- # Returns a hash with the same structure with oids linked
+ # Returns an array with the oid of the existent lfs objects
def execute(oids)
- return {} unless project&.lfs_enabled?
+ return [] unless project&.lfs_enabled?
# Search and link existing LFS Object
link_existing_lfs_objects(oids)
diff --git a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb
new file mode 100644
index 00000000000..5ba0f50f2ff
--- /dev/null
+++ b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+# This service manages the whole worflow of discovering the Lfs files in a
+# repository, linking them to the project and downloading (and linking) the non
+# existent ones.
+module Projects
+ module LfsPointers
+ class LfsObjectDownloadListService < BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ HEAD_REV = 'HEAD'.freeze
+ LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
+ LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze
+
+ LfsObjectDownloadListError = Class.new(StandardError)
+
+ def execute
+ return [] unless project&.lfs_enabled?
+
+ if external_lfs_endpoint?
+ # If the endpoint host is different from the import_url it means
+ # that the repo is using a third party service for storing the LFS files.
+ # In this case, we have to disable lfs in the project
+ disable_lfs!
+
+ return []
+ end
+
+ # Getting all Lfs pointers already in the database and linking them to the project
+ linked_oids = LfsLinkService.new(project).execute(lfs_pointers_in_repository.keys)
+ # Retrieving those oids not present in the database which we need to download
+ missing_oids = lfs_pointers_in_repository.except(*linked_oids) # rubocop: disable CodeReuse/ActiveRecord
+ # Downloading the required information and gathering it inside a LfsDownloadObject for each oid
+ LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(missing_oids)
+ rescue LfsDownloadLinkListService::DownloadLinksError => e
+ raise LfsObjectDownloadListError, "The LFS objects download list couldn't be imported. Error: #{e.message}"
+ end
+
+ private
+
+ def external_lfs_endpoint?
+ lfsconfig_endpoint_uri && lfsconfig_endpoint_uri.host != import_uri.host
+ end
+
+ def disable_lfs!
+ unless project.update(lfs_enabled: false)
+ raise LfsDownloadLinkListService::DownloadLinksError, "Invalid project state"
+ end
+ end
+
+ # Retrieves all lfs pointers in the repository
+ def lfs_pointers_in_repository
+ @lfs_pointers_in_repository ||= LfsListService.new(project).execute
+ end
+
+ def lfsconfig_endpoint_uri
+ strong_memoize(:lfsconfig_endpoint_uri) do
+ # Retrieveing the blob data from the .lfsconfig file
+ data = project.repository.lfsconfig_for(HEAD_REV)
+ # Parsing the data to retrieve the url
+ parsed_data = data&.match(LFS_ENDPOINT_PATTERN)
+
+ if parsed_data
+ URI.parse(parsed_data[1]).tap do |endpoint|
+ endpoint.user ||= import_uri.user
+ endpoint.password ||= import_uri.password
+ end
+ end
+ end
+ rescue URI::InvalidURIError
+ raise LfsObjectDownloadListError, 'Invalid URL in .lfsconfig file'
+ end
+
+ def import_uri
+ @import_uri ||= URI.parse(project.import_url)
+ rescue URI::InvalidURIError
+ raise LfsObjectDownloadListError, 'Invalid project import URL'
+ end
+
+ def current_endpoint_uri
+ (lfsconfig_endpoint_uri || default_endpoint_uri)
+ end
+
+ # The import url must end with '.git' here we ensure it is
+ def default_endpoint_uri
+ @default_endpoint_uri ||= begin
+ import_uri.dup.tap do |uri|
+ path = uri.path.gsub(%r(/$), '')
+ path += '.git' unless path.ends_with?('.git')
+ uri.path = path + LFS_BATCH_API_ENDPOINT
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
index 36afcd0c503..cf4b291c761 100644
--- a/app/services/projects/move_project_group_links_service.rb
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -26,7 +26,7 @@ module Projects
# Remove remaining project group links from source_project
def remove_remaining_project_group_links
- source_project.reload.project_group_links.destroy_all # rubocop: disable DestroyAll
+ source_project.reset.project_group_links.destroy_all # rubocop: disable DestroyAll
end
def group_links_in_target_project
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index abd6d8de750..48eddb0e8d0 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -12,7 +12,37 @@ module Projects
private
def project_update_params
- params.slice(:error_tracking_setting_attributes)
+ error_tracking_params.merge(metrics_setting_params)
+ end
+
+ def metrics_setting_params
+ attribs = params[:metrics_setting_attributes]
+ return {} unless attribs
+
+ destroy = attribs[:external_dashboard_url].blank?
+
+ { metrics_setting_attributes: attribs.merge(_destroy: destroy) }
+ end
+
+ def error_tracking_params
+ settings = params[:error_tracking_setting_attributes]
+ return {} if settings.blank?
+
+ api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
+ api_host: settings[:api_host],
+ project_slug: settings.dig(:project, :slug),
+ organization_slug: settings.dig(:project, :organization_slug)
+ )
+
+ {
+ error_tracking_setting_attributes: {
+ api_url: api_url,
+ token: settings[:token],
+ enabled: settings[:enabled],
+ project_name: settings.dig(:project, :name),
+ organization_name: settings.dig(:project, :organization_name)
+ }
+ }
end
end
end
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index 633a263af7b..a2f36d2bd1b 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -80,7 +80,7 @@ module Projects
value = value.is_a?(Hash) ? value.to_json : value
service_hash[ActiveRecord::Base.connection.quote_column_name(key)] =
- ActiveRecord::Base.sanitize(value)
+ ActiveRecord::Base.connection.quote(value)
end
end
end
diff --git a/app/services/projects/repository_languages_service.rb b/app/services/projects/repository_languages_service.rb
new file mode 100644
index 00000000000..05f43c2264b
--- /dev/null
+++ b/app/services/projects/repository_languages_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Projects
+ class RepositoryLanguagesService < BaseService
+ def execute
+ perform_language_detection unless project.detected_repository_languages?
+ persisted_repository_languages
+ end
+
+ private
+
+ def perform_language_detection
+ if persisted_repository_languages.blank?
+ ::DetectRepositoryLanguagesWorker.perform_async(project.id)
+ else
+ project.update_column(:detected_repository_languages, true)
+ end
+ end
+
+ def persisted_repository_languages
+ project.repository_languages
+ end
+ end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 5da1e39a1fb..233dcf37e35 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -17,11 +17,11 @@ module Projects
@new_namespace = new_namespace
if @new_namespace.blank?
- raise TransferError, 'Please select a new namespace for your project.'
+ raise TransferError, s_('TransferProject|Please select a new namespace for your project.')
end
unless allowed_transfer?(current_user, project)
- raise TransferError, 'Transfer failed, please contact an admin.'
+ raise TransferError, s_('TransferProject|Transfer failed, please contact an admin.')
end
transfer(project)
@@ -30,7 +30,7 @@ module Projects
true
rescue Projects::TransferService::TransferError => ex
- project.reload
+ project.reset
project.errors.add(:new_namespace, ex.message)
false
end
@@ -45,16 +45,15 @@ module Projects
@old_namespace = project.namespace
if Project.where(namespace_id: @new_namespace.try(:id)).where('path = ? or name = ?', project.path, project.name).exists?
- raise TransferError.new("Project with same name or path in target namespace already exists")
+ raise TransferError.new(s_("TransferProject|Project with same name or path in target namespace already exists"))
end
if project.has_container_registry_tags?
# We currently don't support renaming repository if it contains tags in container registry
- raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
+ raise TransferError.new(s_('TransferProject|Project cannot be transferred, because tags are present in its container registry'))
end
attempt_transfer_transaction
- configure_group_clusters_for_project
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -122,7 +121,7 @@ module Projects
def rollback_side_effects
rollback_folder_move
- project.reload
+ project.reset
update_namespace_and_visibility(@old_namespace)
update_repository_configuration(@old_path)
end
@@ -145,7 +144,7 @@ module Projects
# Move main repository
unless move_repo_folder(@old_path, @new_path)
- raise TransferError.new("Cannot move project")
+ raise TransferError.new(s_("TransferProject|Cannot move project"))
end
# Disk path is changed; we need to ensure we reload it
@@ -164,9 +163,5 @@ module Projects
@new_namespace.full_path
)
end
-
- def configure_group_clusters_for_project
- ClusterProjectConfigureWorker.perform_async(project.id)
- end
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 6856009b395..2bc04470342 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,6 +3,7 @@
module Projects
class UpdateService < BaseService
include UpdateVisibilityLevel
+ include ValidatesClassificationLabel
ValidationError = Class.new(StandardError)
@@ -14,6 +15,8 @@ module Projects
yield if block_given?
+ validate_classification_label(project, :external_authorization_classification_label)
+
# If the block added errors, don't try to save the project
return update_failed! if project.errors.any?
@@ -39,15 +42,15 @@ module Projects
def validate!
unless valid_visibility_level_change?(project, params[:visibility_level])
- raise ValidationError.new('New visibility level not allowed!')
+ raise ValidationError.new(s_('UpdateProject|New visibility level not allowed!'))
end
if renaming_project_with_container_registry_tags?
- raise ValidationError.new('Cannot rename project because it contains container registry tags!')
+ raise ValidationError.new(s_('UpdateProject|Cannot rename project because it contains container registry tags!'))
end
if changing_default_branch?
- raise ValidationError.new("Could not set the default branch") unless project.change_head(params[:default_branch])
+ raise ValidationError.new(s_("UpdateProject|Could not set the default branch")) unless project.change_head(params[:default_branch])
end
end
@@ -61,6 +64,7 @@ module Projects
if project.previous_changes.include?(:visibility_level) && project.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
+ TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
elsif (project_changed_feature_keys & todos_features_changes).present?
TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
@@ -76,10 +80,7 @@ module Projects
end
def after_rename_service(project)
- # The path slug the project was using, before the rename took place.
- path_before = project.previous_changes['path'].first
-
- AfterRenameService.new(project, path_before: path_before, full_path_before: project.full_path_was)
+ AfterRenameService.new(project, path_before: project.path_before_last_save, full_path_before: project.full_path_before_last_save)
end
def changing_pages_related_config?
@@ -88,7 +89,7 @@ module Projects
def update_failed!
model_errors = project.errors.full_messages.to_sentence
- error_message = model_errors.presence || 'Project could not be updated!'
+ error_message = model_errors.presence || s_('UpdateProject|Project could not be updated!')
error(error_message)
end
diff --git a/app/services/projects/update_statistics_service.rb b/app/services/projects/update_statistics_service.rb
new file mode 100644
index 00000000000..28677a398f3
--- /dev/null
+++ b/app/services/projects/update_statistics_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Projects
+ class UpdateStatisticsService < BaseService
+ def execute
+ return unless project
+
+ Rails.logger.info("Updating statistics for project #{project.id}")
+
+ project.statistics.refresh!(only: statistics.map(&:to_sym))
+ end
+
+ private
+
+ def statistics
+ params[:statistics]
+ end
+ end
+end
diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb
new file mode 100644
index 00000000000..c5d2b84878b
--- /dev/null
+++ b/app/services/prometheus/proxy_service.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+module Prometheus
+ class ProxyService < BaseService
+ include ReactiveCaching
+ include Gitlab::Utils::StrongMemoize
+
+ self.reactive_cache_key = ->(service) { service.cache_key }
+ self.reactive_cache_lease_timeout = 30.seconds
+ self.reactive_cache_refresh_interval = 30.seconds
+ self.reactive_cache_lifetime = 1.minute
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+
+ attr_accessor :proxyable, :method, :path, :params
+
+ PROXY_SUPPORT = {
+ 'query' => {
+ method: ['GET'],
+ params: %w(query time timeout)
+ },
+ 'query_range' => {
+ method: ['GET'],
+ params: %w(query start end step timeout)
+ }
+ }.freeze
+
+ def self.from_cache(proxyable_class_name, proxyable_id, method, path, params)
+ proxyable_class = begin
+ proxyable_class_name.constantize
+ rescue NameError
+ nil
+ end
+ return unless proxyable_class
+
+ proxyable = proxyable_class.find(proxyable_id)
+
+ new(proxyable, method, path, params)
+ end
+
+ # proxyable can be any model which responds to .prometheus_adapter
+ # like Environment.
+ def initialize(proxyable, method, path, params)
+ @proxyable = proxyable
+ @path = path
+
+ # Convert ActionController::Parameters to hash because reactive_cache_worker
+ # does not play nice with ActionController::Parameters.
+ @params = filter_params(params, path).to_hash
+
+ @method = method
+ end
+
+ def id
+ nil
+ end
+
+ def execute
+ return cannot_proxy_response unless can_proxy?
+ return no_prometheus_response unless can_query?
+
+ with_reactive_cache(*cache_key) do |result|
+ result
+ end
+ end
+
+ def calculate_reactive_cache(proxyable_class_name, proxyable_id, method, path, params)
+ return no_prometheus_response unless can_query?
+
+ response = prometheus_client_wrapper.proxy(path, params)
+
+ success(http_status: response.code, body: response.body)
+ rescue Gitlab::PrometheusClient::Error => err
+ service_unavailable_response(err)
+ end
+
+ def cache_key
+ [@proxyable.class.name, @proxyable.id, @method, @path, @params]
+ end
+
+ private
+
+ def service_unavailable_response(exception)
+ error(exception.message, :service_unavailable)
+ end
+
+ def no_prometheus_response
+ error('No prometheus server found', :service_unavailable)
+ end
+
+ def cannot_proxy_response
+ error('Proxy support for this API is not available currently')
+ end
+
+ def prometheus_adapter
+ strong_memoize(:prometheus_adapter) do
+ @proxyable.prometheus_adapter
+ end
+ end
+
+ def prometheus_client_wrapper
+ prometheus_adapter&.prometheus_client_wrapper
+ end
+
+ def can_query?
+ prometheus_adapter&.can_query?
+ end
+
+ def filter_params(params, path)
+ params.slice(*PROXY_SUPPORT.dig(path, :params))
+ end
+
+ def can_proxy?
+ PROXY_SUPPORT.dig(@path, :method)&.include?(@method)
+ end
+ end
+end
diff --git a/app/services/push_event_payload_service.rb b/app/services/push_event_payload_service.rb
index bb1259787af..fe366ac225b 100644
--- a/app/services/push_event_payload_service.rb
+++ b/app/services/push_event_payload_service.rb
@@ -46,7 +46,7 @@ class PushEventPayloadService
def commit_title
commit = @push_data.fetch(:commits).last
- return nil unless commit && commit[:message]
+ return unless commit && commit[:message]
raw_msg = commit[:message]
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 5c58caee8cd..8ff73522e5f 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -4,16 +4,24 @@ module QuickActions
class InterpretService < BaseService
include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
+ include Gitlab::QuickActions::IssueActions
+ include Gitlab::QuickActions::IssueAndMergeRequestActions
+ include Gitlab::QuickActions::IssuableActions
+ include Gitlab::QuickActions::MergeRequestActions
+ include Gitlab::QuickActions::CommitActions
+ include Gitlab::QuickActions::CommonActions
- attr_reader :issuable
+ attr_reader :quick_action_target
- SHRUG = '¯\\_(ツ)_/¯'.freeze
- TABLEFLIP = '(╯°□°)╯︵ â”»â”â”»'.freeze
+ # Counts how many commands have been executed.
+ # Used to display relevant feedback on UI when a note
+ # with only commands has been processed.
+ attr_accessor :commands_executed_count
- # Takes an issuable and returns an array of all the available commands
+ # Takes an quick_action_target and returns an array of all the available commands
# represented with .to_h
- def available_commands(issuable)
- @issuable = issuable
+ def available_commands(quick_action_target)
+ @quick_action_target = quick_action_target
self.class.command_definitions.map do |definition|
next unless definition.available?(self)
@@ -24,10 +32,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
- def execute(content, issuable, only: nil)
+ def execute(content, quick_action_target, only: nil)
return [content, {}] unless current_user.can?(:use_quick_actions)
- @issuable = issuable
+ @quick_action_target = quick_action_target
@updates = {}
content, commands = extractor.extract_commands(content, only: only)
@@ -38,10 +46,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and array of changes explained.
- def explain(content, issuable)
+ def explain(content, quick_action_target)
return [content, []] unless current_user.can?(:use_quick_actions)
- @issuable = issuable
+ @quick_action_target = quick_action_target
content, commands = extractor.extract_commands(content)
commands = explain_commands(commands)
@@ -54,598 +62,6 @@ module QuickActions
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
end
- desc do
- "Close this #{issuable.to_ability_name.humanize(capitalize: false)}"
- end
- explanation do
- "Closes this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.open? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :close do
- @updates[:state_event] = 'close'
- end
-
- desc do
- "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}"
- end
- explanation do
- "Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.closed? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :reopen do
- @updates[:state_event] = 'reopen'
- end
-
- desc 'Merge (when the pipeline succeeds)'
- explanation 'Merges this merge request when the pipeline succeeds.'
- condition do
- last_diff_sha = params && params[:merge_request_diff_head_sha]
- issuable.is_a?(MergeRequest) &&
- issuable.persisted? &&
- issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
- end
- command :merge do
- @updates[:merge] = params[:merge_request_diff_head_sha]
- end
-
- desc 'Change title'
- explanation do |title_param|
- "Changes the title to \"#{title_param}\"."
- end
- params '<New title>'
- condition do
- issuable.persisted? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :title do |title_param|
- @updates[:title] = title_param
- end
-
- desc 'Assign'
- # rubocop: disable CodeReuse/ActiveRecord
- explanation do |users|
- users = issuable.allows_multiple_assignees? ? users : users.take(1)
- "Assigns #{users.map(&:to_reference).to_sentence}."
- end
- # rubocop: enable CodeReuse/ActiveRecord
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
- end
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |assignee_param|
- extract_users(assignee_param)
- end
- command :assign do |users|
- next if users.empty?
-
- if issuable.allows_multiple_assignees?
- @updates[:assignee_ids] ||= issuable.assignees.map(&:id)
- @updates[:assignee_ids] += users.map(&:id)
- else
- @updates[:assignee_ids] = [users.first.id]
- end
- end
-
- desc do
- if issuable.allows_multiple_assignees?
- 'Remove all or specific assignee(s)'
- else
- 'Remove assignee'
- end
- end
- explanation do |users = nil|
- assignees = issuable.assignees
- assignees &= users if users.present? && issuable.allows_multiple_assignees?
- "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
- end
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.assignees.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |unassign_param|
- # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
- extract_users(unassign_param) if issuable.allows_multiple_assignees?
- end
- command :unassign do |users = nil|
- if issuable.allows_multiple_assignees? && users&.any?
- @updates[:assignee_ids] ||= issuable.assignees.map(&:id)
- @updates[:assignee_ids] -= users.map(&:id)
- else
- @updates[:assignee_ids] = []
- end
- end
-
- desc 'Set milestone'
- explanation do |milestone|
- "Sets the milestone to #{milestone.to_reference}." if milestone
- end
- params '%"milestone"'
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
- find_milestones(project, state: 'active').any?
- end
- parse_params do |milestone_param|
- extract_references(milestone_param, :milestone).first ||
- find_milestones(project, title: milestone_param.strip).first
- end
- command :milestone do |milestone|
- @updates[:milestone_id] = milestone.id if milestone
- end
-
- desc 'Remove milestone'
- explanation do
- "Removes #{issuable.milestone.to_reference(format: :name)} milestone."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.milestone_id? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_milestone do
- @updates[:milestone_id] = nil
- end
-
- desc 'Add label(s)'
- explanation do |labels_param|
- labels = find_label_references(labels_param)
-
- "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- end
- params '~label1 ~"label 2"'
- condition do
- parent &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", parent) &&
- find_labels.any?
- end
- command :label do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:add_label_ids] ||= []
- @updates[:add_label_ids] += label_ids
-
- @updates[:add_label_ids].uniq!
- end
- end
-
- desc 'Remove all or specific label(s)'
- explanation do |labels_param = nil|
- if labels_param.present?
- labels = find_label_references(labels_param)
- "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- else
- 'Removes all labels.'
- end
- end
- params '~label1 ~"label 2"'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", parent)
- end
- command :unlabel do |labels_param = nil|
- if labels_param.present?
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:remove_label_ids] ||= []
- @updates[:remove_label_ids] += label_ids
-
- @updates[:remove_label_ids].uniq!
- end
- else
- @updates[:label_ids] = []
- end
- end
-
- desc 'Replace all label(s)'
- explanation do |labels_param|
- labels = find_label_references(labels_param)
- "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- end
- params '~label1 ~"label 2"'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :relabel do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:label_ids] ||= []
- @updates[:label_ids] += label_ids
-
- @updates[:label_ids].uniq!
- end
- end
-
- desc 'Copy labels and milestone from other issue or merge request'
- explanation do |source_issuable|
- "Copy labels and milestone from #{source_issuable.to_reference}."
- end
- params '#issue | !merge_request'
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- parse_params do |issuable_param|
- extract_references(issuable_param, :issue).first ||
- extract_references(issuable_param, :merge_request).first
- end
- command :copy_metadata do |source_issuable|
- if source_issuable.present? && source_issuable.project.id == issuable.project.id
- @updates[:add_label_ids] = source_issuable.labels.map(&:id)
- @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
- end
- end
-
- desc 'Add a todo'
- explanation 'Adds a todo.'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- !TodoService.new.todo_exist?(issuable, current_user)
- end
- command :todo do
- @updates[:todo_event] = 'add'
- end
-
- desc 'Mark todo as done'
- explanation 'Marks todo as done.'
- condition do
- issuable.persisted? &&
- TodoService.new.todo_exist?(issuable, current_user)
- end
- command :done do
- @updates[:todo_event] = 'done'
- end
-
- desc 'Subscribe'
- explanation do
- "Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- !issuable.subscribed?(current_user, project)
- end
- command :subscribe do
- @updates[:subscription_event] = 'subscribe'
- end
-
- desc 'Unsubscribe'
- explanation do
- "Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.subscribed?(current_user, project)
- end
- command :unsubscribe do
- @updates[:subscription_event] = 'unsubscribe'
- end
-
- desc 'Set due date'
- explanation do |due_date|
- "Sets the due date to #{due_date.to_s(:medium)}." if due_date
- end
- params '<in 2 days | this Friday | December 31st>'
- condition do
- issuable.respond_to?(:due_date) &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |due_date_param|
- Chronic.parse(due_date_param).try(:to_date)
- end
- command :due do |due_date|
- @updates[:due_date] = due_date if due_date
- end
-
- desc 'Remove due date'
- explanation 'Removes the due date.'
- condition do
- issuable.persisted? &&
- issuable.respond_to?(:due_date) &&
- issuable.due_date? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_due_date do
- @updates[:due_date] = nil
- end
-
- desc 'Toggle the Work In Progress status'
- explanation do
- verb = issuable.work_in_progress? ? 'Unmarks' : 'Marks'
- noun = issuable.to_ability_name.humanize(capitalize: false)
- "#{verb} this #{noun} as Work In Progress."
- end
- condition do
- issuable.respond_to?(:work_in_progress?) &&
- # Allow it to mark as WIP on MR creation page _or_ through MR notes.
- (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
- end
- command :wip do
- @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
- end
-
- desc 'Toggle emoji award'
- explanation do |name|
- "Toggles :#{name}: emoji award." if name
- end
- params ':emoji:'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted?
- end
- parse_params do |emoji_param|
- match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
- match[1] if match
- end
- command :award do |name|
- if name && issuable.user_can_award?(current_user)
- @updates[:emoji_award] = name
- end
- end
-
- desc 'Set time estimate'
- explanation do |time_estimate|
- time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
-
- "Sets time estimate to #{time_estimate}." if time_estimate
- end
- params '<1w 3d 2h 14m>'
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |raw_duration|
- Gitlab::TimeTrackingFormatter.parse(raw_duration)
- end
- command :estimate do |time_estimate|
- if time_estimate
- @updates[:time_estimate] = time_estimate
- end
- end
-
- desc 'Add or subtract spent time'
- explanation do |time_spent, time_spent_date|
- if time_spent
- if time_spent > 0
- verb = 'Adds'
- value = time_spent
- else
- verb = 'Subtracts'
- value = -time_spent
- end
-
- "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
- end
- end
- params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
- condition do
- issuable.is_a?(TimeTrackable) &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- parse_params do |raw_time_date|
- Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
- end
- command :spend do |time_spent, time_spent_date|
- if time_spent
- @updates[:spend_time] = {
- duration: time_spent,
- user_id: current_user.id,
- spent_at: time_spent_date
- }
- end
- end
-
- desc 'Remove time estimate'
- explanation 'Removes time estimate.'
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_estimate do
- @updates[:time_estimate] = 0
- end
-
- desc 'Remove spent time'
- explanation 'Removes spent time.'
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_time_spent do
- @updates[:spend_time] = { duration: :reset, user_id: current_user.id }
- end
-
- desc "Append the comment with #{SHRUG}"
- params '<Comment>'
- substitution :shrug do |comment|
- "#{comment} #{SHRUG}"
- end
-
- desc "Append the comment with #{TABLEFLIP}"
- params '<Comment>'
- substitution :tableflip do |comment|
- "#{comment} #{TABLEFLIP}"
- end
-
- desc "Lock the discussion"
- explanation "Locks the discussion"
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- issuable.persisted? &&
- !issuable.discussion_locked? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :lock do
- @updates[:discussion_locked] = true
- end
-
- desc "Unlock the discussion"
- explanation "Unlocks the discussion"
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- issuable.persisted? &&
- issuable.discussion_locked? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :unlock do
- @updates[:discussion_locked] = false
- end
-
- # This is a dummy command, so that it appears in the autocomplete commands
- desc 'CC'
- params '@user'
- command :cc
-
- desc 'Set target branch'
- explanation do |branch_name|
- "Sets target branch to #{branch_name}."
- end
- params '<Local branch name>'
- condition do
- issuable.respond_to?(:target_branch) &&
- (current_user.can?(:"update_#{issuable.to_ability_name}", issuable) ||
- issuable.new_record?)
- end
- parse_params do |target_branch_param|
- target_branch_param.strip
- end
- command :target_branch do |branch_name|
- @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
- end
-
- desc 'Move issue from one column of the board to another'
- explanation do |target_list_name|
- label = find_label_references(target_list_name).first
- "Moves issue to #{label} column in the board." if label
- end
- params '~"Target column"'
- condition do
- issuable.is_a?(Issue) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable) &&
- issuable.project.boards.count == 1
- end
- # rubocop: disable CodeReuse/ActiveRecord
- command :board_move do |target_list_name|
- label_ids = find_label_ids(target_list_name)
-
- if label_ids.size == 1
- label_id = label_ids.first
-
- # Ensure this label corresponds to a list on the board
- next unless Label.on_project_boards(issuable.project_id).where(id: label_id).exists?
-
- @updates[:remove_label_ids] =
- issuable.labels.on_project_boards(issuable.project_id).where.not(id: label_id).pluck(:id)
- @updates[:add_label_ids] = [label_id]
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Mark this issue as a duplicate of another issue'
- explanation do |duplicate_reference|
- "Marks this issue as a duplicate of #{duplicate_reference}."
- end
- params '#issue'
- condition do
- issuable.is_a?(Issue) &&
- issuable.persisted? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :duplicate do |duplicate_param|
- canonical_issue = extract_references(duplicate_param, :issue).first
-
- if canonical_issue.present?
- @updates[:canonical_issue_id] = canonical_issue.id
- end
- end
-
- desc 'Move this issue to another project.'
- explanation do |path_to_project|
- "Moves this issue to #{path_to_project}."
- end
- params 'path/to/project'
- condition do
- issuable.is_a?(Issue) &&
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :move do |target_project_path|
- target_project = Project.find_by_full_path(target_project_path)
-
- if target_project.present?
- @updates[:target_project] = target_project
- end
- end
-
- desc 'Make issue confidential.'
- explanation do
- 'Makes this issue confidential'
- end
- condition do
- issuable.is_a?(Issue) && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :confidential do
- @updates[:confidential] = true
- end
-
- desc 'Tag this commit.'
- explanation do |tag_name, message|
- with_message = %{ with "#{message}"} if message.present?
- "Tags this commit to #{tag_name}#{with_message}."
- end
- params 'v1.2.3 <message>'
- parse_params do |tag_name_and_message|
- tag_name_and_message.split(' ', 2)
- end
- condition do
- issuable.is_a?(Commit) && current_user.can?(:push_code, project)
- end
- command :tag do |tag_name, message|
- @updates[:tag_name] = tag_name
- @updates[:tag_message] = message
- end
-
- desc 'Create a merge request.'
- explanation do |branch_name = nil|
- branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch'
- "Creates #{branch_text} and a merge request to resolve this issue"
- end
- params "<branch name>"
- condition do
- issuable.is_a?(Issue) && current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project)
- end
- command :create_merge_request do |branch_name = nil|
- @updates[:create_merge_request] = {
- branch_name: branch_name,
- issue_iid: issuable.iid
- }
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def extract_users(params)
return [] if params.nil?
@@ -675,19 +91,32 @@ module QuickActions
def group
strong_memoize(:group) do
- issuable.group if issuable.respond_to?(:group)
+ quick_action_target.group if quick_action_target.respond_to?(:group)
end
end
def find_labels(labels_params = nil)
+ extract_references(labels_params, :label) | find_labels_by_name_no_tilde(labels_params)
+ end
+
+ def find_labels_by_name_no_tilde(labels_params)
+ return Label.none if label_with_tilde?(labels_params)
+
finder_params = { include_ancestor_groups: true }
finder_params[:project_id] = project.id if project
finder_params[:group_id] = group.id if group
- finder_params[:name] = labels_params.split if labels_params
+ finder_params[:name] = extract_label_names(labels_params) if labels_params
- result = LabelsFinder.new(current_user, finder_params).execute
+ LabelsFinder.new(current_user, finder_params).execute
+ end
+
+ def label_with_tilde?(labels_params)
+ labels_params&.include?('~')
+ end
- extract_references(labels_params, :label) | result
+ def extract_label_names(labels_params)
+ # '"A" "A B C" A B' => ["A", "A B C", "A", "B"]
+ labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
def find_label_references(labels_param)
diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb
index a04bb8f9e14..ff6b696ca96 100644
--- a/app/services/releases/concerns.rb
+++ b/app/services/releases/concerns.rb
@@ -15,7 +15,7 @@ module Releases
end
def name
- params[:name]
+ params[:name] || tag_name
end
def description
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index c6e143d440d..a271a7e5e49 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -15,6 +15,10 @@ module Releases
create_release(tag)
end
+ def find_or_build_release
+ release || build_release(existing_tag)
+ end
+
private
def ensure_tag
@@ -38,7 +42,17 @@ module Releases
end
def create_release(tag)
- release = project.releases.create!(
+ release = build_release(tag)
+
+ release.save!
+
+ success(tag: tag, release: release)
+ rescue => e
+ error(e.message, 400)
+ end
+
+ def build_release(tag)
+ project.releases.build(
name: name,
description: description,
author: current_user,
@@ -46,10 +60,6 @@ module Releases
sha: tag.dereferenced_target.sha,
links_attributes: params.dig(:assets, 'links') || []
)
-
- success(tag: tag, release: release)
- rescue => e
- error(e.message, 400)
end
end
end
diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb
index 8c2bc3b4e6e..f9f6101abdd 100644
--- a/app/services/releases/destroy_service.rb
+++ b/app/services/releases/destroy_service.rb
@@ -5,7 +5,6 @@ module Releases
include Releases::Concerns
def execute
- return error('Tag does not exist', 404) unless existing_tag
return error('Release does not exist', 404) unless release
return error('Access Denied', 403) unless allowed?
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index 039d6e2ebad..b45e567079b 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -12,7 +12,7 @@ module ResourceEvents
label_hash = {
resource_column(resource) => resource.id,
user_id: user.id,
- created_at: Time.now
+ created_at: resource.system_note_timestamp
}
labels = added_labels.map do |label|
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index d6af26d949d..f711839e389 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -23,7 +23,8 @@ module Search
def allowed_scopes
strong_memoize(:allowed_scopes) do
- %w[issues merge_requests milestones]
+ allowed_scopes = %w[issues merge_requests milestones]
+ allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
end
end
diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb
index 34803d005e3..6f3b5f00b86 100644
--- a/app/services/search/group_service.rb
+++ b/app/services/search/group_service.rb
@@ -11,6 +11,12 @@ module Search
@group = group
end
+ def execute
+ Gitlab::GroupSearchResults.new(
+ current_user, projects, group, params[:search], default_project_filter: default_project_filter
+ )
+ end
+
def projects
return Project.none unless group
return @projects if defined? @projects
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index f223c8be103..32d5cd7ddb2 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -16,7 +16,12 @@ module Search
end
def scope
- @scope ||= %w[notes issues merge_requests milestones wiki_blobs commits].delete(params[:scope]) { 'blobs' }
+ @scope ||= begin
+ allowed_scopes = %w[notes issues merge_requests milestones wiki_blobs commits]
+ allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
+
+ allowed_scopes.delete(params[:scope]) { 'blobs' }
+ end
end
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index e0cbfac2420..302510341ac 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -52,6 +52,10 @@ class SearchService
@search_objects ||= search_results.objects(scope, params[:page])
end
+ def display_options
+ @display_options ||= search_results.display_options(scope)
+ end
+
private
def search_service
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
new file mode 100644
index 00000000000..f3437ba16de
--- /dev/null
+++ b/app/services/service_response.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class ServiceResponse
+ def self.success(message: nil, payload: {})
+ new(status: :success, message: message, payload: payload)
+ end
+
+ def self.error(message:, payload: {}, http_status: nil)
+ new(status: :error, message: message, payload: payload, http_status: http_status)
+ end
+
+ attr_reader :status, :message, :http_status, :payload
+
+ def initialize(status:, message: nil, payload: {}, http_status: nil)
+ self.status = status
+ self.message = message
+ self.payload = payload
+ self.http_status = http_status
+ end
+
+ def success?
+ status == :success
+ end
+
+ def error?
+ status == :error
+ end
+
+ private
+
+ attr_writer :status, :message, :http_status, :payload
+end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index 1f720fc835f..8ba50e22b09 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -7,7 +7,7 @@ module Suggestions
end
def execute(suggestion)
- unless suggestion.appliable?
+ unless suggestion.appliable?(cached: false)
return error('Suggestion is not appliable')
end
@@ -15,7 +15,13 @@ module Suggestions
return error('The file has been changed')
end
- params = file_update_params(suggestion)
+ diff_file = suggestion.diff_file
+
+ unless diff_file
+ return error('The file was not found')
+ end
+
+ params = file_update_params(suggestion, diff_file)
result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute
if result[:status] == :success
@@ -38,8 +44,8 @@ module Suggestions
suggestion.position.head_sha == suggestion.noteable.source_branch_sha
end
- def file_update_params(suggestion)
- blob = suggestion.diff_file.new_blob
+ def file_update_params(suggestion, diff_file)
+ blob = diff_file.new_blob
file_path = suggestion.file_path
branch_name = suggestion.branch
file_content = new_file_content(suggestion, blob)
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index 77e958cbe0c..1d3338c1b45 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -9,48 +9,24 @@ module Suggestions
def execute
return unless @note.supports_suggestion?
- suggestions = Banzai::SuggestionsParser.parse(@note.note)
-
- # For single line suggestion we're only looking forward to
- # change the line receiving the comment. Though, in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # we'll introduce a ```suggestion:L<x>-<y>, so this will
- # slightly change.
- comment_line = @note.position.new_line
+ suggestions = Gitlab::Diff::SuggestionsParser.parse(@note.note,
+ project: @note.project,
+ position: @note.position)
rows =
suggestions.map.with_index do |suggestion, index|
- from_content = changing_lines(comment_line, comment_line)
-
- # The parsed suggestion doesn't have information about the correct
- # ending characters (we may have a line break, or not), so we take
- # this information from the last line being changed (last
- # characters).
- endline_chars = line_break_chars(from_content.lines.last)
- to_content = "#{suggestion}#{endline_chars}"
+ creation_params =
+ suggestion.to_hash.slice(:from_content,
+ :to_content,
+ :lines_above,
+ :lines_below)
- {
- note_id: @note.id,
- from_content: from_content,
- to_content: to_content,
- relative_order: index
- }
+ creation_params.merge!(note_id: @note.id, relative_order: index)
end
rows.in_groups_of(100, false) do |rows|
Gitlab::Database.bulk_insert('suggestions', rows)
end
end
-
- private
-
- def changing_lines(from_line, to_line)
- @note.diff_file.new_blob_lines_between(from_line, to_line).join
- end
-
- def line_break_chars(line)
- match = /\r\n|\r|\n/.match(line)
- match[0] if match
- end
end
end
diff --git a/app/services/suggestions/outdate_service.rb b/app/services/suggestions/outdate_service.rb
new file mode 100644
index 00000000000..a33aac9f6b5
--- /dev/null
+++ b/app/services/suggestions/outdate_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Suggestions
+ class OutdateService
+ def execute(merge_request)
+ # rubocop: disable CodeReuse/ActiveRecord
+ suggestions = merge_request.suggestions.active.includes(:note)
+
+ suggestions.find_in_batches(batch_size: 100) do |group|
+ outdatable_suggestion_ids = group.select do |suggestion|
+ suggestion.outdated?(cached: false)
+ end.map(&:id)
+
+ Suggestion.where(id: outdatable_suggestion_ids).update_all(outdated: true)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index bd3907cdf8e..858e04f43b2 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -47,7 +47,7 @@ class SystemHooksService
case event
when :rename
- data[:old_username] = model.username_was
+ data[:old_username] = model.username_before_last_save
when :failed_login
data[:state] = model.state
end
@@ -58,8 +58,8 @@ class SystemHooksService
if event == :rename
data.merge!(
- old_path: model.path_was,
- old_full_path: model.full_path_was
+ old_path: model.path_before_last_save,
+ old_full_path: model.full_path_before_last_save
)
end
when GroupMember
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index ea8ac7e4656..1390f7cdf46 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -25,7 +25,7 @@ module SystemNoteService
text_parts = ["added #{commits_text}"]
text_parts << commits_list(noteable, new_commits, existing_commits, oldrev)
- text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
+ text_parts << "[Compare with previous version](#{diff_comparison_path(noteable, project, oldrev)})"
body = text_parts.join("\n\n")
@@ -41,7 +41,7 @@ module SystemNoteService
#
# Returns the created Note object
def tag_commit(noteable, project, author, tag_name)
- link = url_helpers.project_tag_url(project, id: tag_name)
+ link = url_helpers.project_tag_path(project, id: tag_name)
body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
@@ -69,7 +69,7 @@ module SystemNoteService
# Called when the assignees of an Issue is changed or removed
#
- # issue - Issue object
+ # issuable - Issuable object (responds to assignees)
# project - Project owning noteable
# author - User performing the change
# assignees - Users being assigned, or nil
@@ -85,9 +85,9 @@ module SystemNoteService
# "assigned to @user1 and @user2"
#
# Returns the created Note object
- def change_issue_assignees(issue, project, author, old_assignees)
- unassigned_users = old_assignees - issue.assignees
- added_users = issue.assignees.to_a - old_assignees
+ def change_issuable_assignees(issuable, project, author, old_assignees)
+ unassigned_users = old_assignees - issuable.assignees
+ added_users = issuable.assignees.to_a - old_assignees
text_parts = []
text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
@@ -95,7 +95,7 @@ module SystemNoteService
body = text_parts.join(' and ')
- create_note(NoteSummary.new(issue, project, author, body, action: 'assignee'))
+ create_note(NoteSummary.new(issuable, project, author, body, action: 'assignee'))
end
# Called when the milestone of a Noteable is changed
@@ -258,7 +258,7 @@ module SystemNoteService
body = "created #{issue.to_reference} to continue this discussion"
note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
- note = Note.create(note_attributes.merge(system: true))
+ note = Note.create(note_attributes.merge(system: true, created_at: issue.system_note_timestamp))
note.system_note_metadata = SystemNoteMetadata.new(action: 'discussion')
note
@@ -272,7 +272,7 @@ module SystemNoteService
text_parts = ["changed this line in"]
if version_params = merge_request.version_params_for(diff_refs)
line_code = change_position.line_code(project.repository)
- url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code))
+ url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: line_code))
text_parts << "[version #{version_index} of the diff](#{url})"
else
@@ -405,7 +405,7 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch)
- link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch)
+ link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link}) to address this issue"
@@ -668,10 +668,10 @@ module SystemNoteService
@url_helpers ||= Gitlab::Routing.url_helpers
end
- def diff_comparison_url(merge_request, project, oldrev)
+ def diff_comparison_path(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id
- url_helpers.diffs_project_merge_request_url(
+ url_helpers.diffs_project_merge_request_path(
project,
merge_request,
diff_id: diff_id,
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index cab507946b4..4f6ae07be7d 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -41,12 +41,11 @@ module Tags
def build_push_data(tag)
Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- tag.dereferenced_target.sha,
- Gitlab::Git::BLANK_SHA,
- "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
- [])
+ project: project,
+ user: current_user,
+ oldrev: tag.dereferenced_target.sha,
+ newrev: Gitlab::Git::BLANK_SHA,
+ ref: "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}")
end
end
end
diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb
index 7e14ddcd017..a71278e8b8b 100644
--- a/app/services/test_hooks/project_service.rb
+++ b/app/services/test_hooks/project_service.rb
@@ -11,7 +11,7 @@ module TestHooks
private
def push_events_data
- throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo?
+ throw(:validation_error, s_('TestHooks|Ensure the project has at least one commit.')) if project.empty_repo?
Gitlab::DataBuilder::Push.build_sample(project, current_user)
end
@@ -20,14 +20,14 @@ module TestHooks
def note_events_data
note = project.notes.first
- throw(:validation_error, 'Ensure the project has notes.') unless note.present?
+ throw(:validation_error, s_('TestHooks|Ensure the project has notes.')) unless note.present?
Gitlab::DataBuilder::Note.build(note, current_user)
end
def issues_events_data
issue = project.issues.first
- throw(:validation_error, 'Ensure the project has issues.') unless issue.present?
+ throw(:validation_error, s_('TestHooks|Ensure the project has issues.')) unless issue.present?
issue.to_hook_data(current_user)
end
@@ -36,29 +36,29 @@ module TestHooks
def merge_requests_events_data
merge_request = project.merge_requests.first
- throw(:validation_error, 'Ensure the project has merge requests.') unless merge_request.present?
+ throw(:validation_error, s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present?
merge_request.to_hook_data(current_user)
end
def job_events_data
build = project.builds.first
- throw(:validation_error, 'Ensure the project has CI jobs.') unless build.present?
+ throw(:validation_error, s_('TestHooks|Ensure the project has CI jobs.')) unless build.present?
Gitlab::DataBuilder::Build.build(build)
end
def pipeline_events_data
pipeline = project.ci_pipelines.first
- throw(:validation_error, 'Ensure the project has CI pipelines.') unless pipeline.present?
+ throw(:validation_error, s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present?
Gitlab::DataBuilder::Pipeline.build(pipeline)
end
def wiki_page_events_data
- page = project.wiki.pages.first
+ page = project.wiki.list_pages(limit: 1).first
if !project.wiki_enabled? || page.blank?
- throw(:validation_error, 'Ensure the wiki is enabled and has pages.')
+ throw(:validation_error, s_('TestHooks|Ensure the wiki is enabled and has pages.'))
end
Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
index 082830c5538..fedf9c6799b 100644
--- a/app/services/test_hooks/system_service.rb
+++ b/app/services/test_hooks/system_service.rb
@@ -18,7 +18,7 @@ module TestHooks
def merge_requests_events_data
merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first
- throw(:validation_error, 'Ensure one of your projects has merge requests.') unless merge_request.present?
+ throw(:validation_error, s_('TestHooks|Ensure one of your projects has merge requests.')) unless merge_request.present?
merge_request.to_hook_data(current_user)
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index f357dc37fe7..0ea230a44a1 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -49,12 +49,12 @@ class TodoService
todo_users.each(&:update_todos_count_cache)
end
- # When we reassign an issue we should:
+ # When we reassign an issuable we should:
#
- # * create a pending todo for new assignee if issue is assigned
+ # * create a pending todo for new assignee if issuable is assigned
#
- def reassigned_issue(issue, current_user, old_assignees = [])
- create_assignment_todo(issue, current_user, old_assignees)
+ def reassigned_issuable(issuable, current_user, old_assignees = [])
+ create_assignment_todo(issuable, current_user, old_assignees)
end
# When create a merge request we should:
@@ -82,14 +82,6 @@ class TodoService
mark_pending_todos_as_done(merge_request, current_user)
end
- # When we reassign a merge request we should:
- #
- # * creates a pending todo for new assignee if merge request is assigned
- #
- def reassigned_merge_request(merge_request, current_user)
- create_assignment_todo(merge_request, current_user)
- end
-
# When merge a merge request we should:
#
# * mark all pending todos related to the target for the current user as done
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index f3f1dbb5698..7378f10e7c4 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -13,7 +13,7 @@ module Todos
# rubocop: disable CodeReuse/ActiveRecord
def without_authorized(items)
- items.where('user_id NOT IN (?)', authorized_users)
+ items.where('todos.user_id NOT IN (?)', authorized_users)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index 6276e332448..6cdd8c16894 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -2,36 +2,55 @@
module Todos
module Destroy
+ # Service class for deleting todos that belongs to confidential issues.
+ # It deletes todos for users that are not at least reporters, issue author or assignee.
+ #
+ # Accepts issue_id or project_id as argument.
+ # When issue_id is passed it deletes matching todos for one confidential issue.
+ # When project_id is passed it deletes matching todos for all confidential issues of the project.
class ConfidentialIssueService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override
- attr_reader :issue
+ attr_reader :issues
# rubocop: disable CodeReuse/ActiveRecord
- def initialize(issue_id)
- @issue = Issue.find_by(id: issue_id)
+ def initialize(issue_id: nil, project_id: nil)
+ @issues =
+ if issue_id
+ Issue.where(id: issue_id)
+ elsif project_id
+ project_confidential_issues(project_id)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
private
+ def project_confidential_issues(project_id)
+ project = Project.find(project_id)
+
+ project.issues.confidential_only
+ end
+
override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
- Todo.where(target: issue)
- .where('user_id != ?', issue.author_id)
- .where('user_id NOT IN (?)', issue.assignees.select(:id))
+ Todo.joins_issue_and_assignees
+ .where(target: issues)
+ .where('issues.confidential = ?', true)
+ .where('todos.user_id != issues.author_id')
+ .where('todos.user_id != issue_assignees.user_id')
end
# rubocop: enable CodeReuse/ActiveRecord
override :todos_to_remove?
def todos_to_remove?
- issue&.confidential?
+ issues&.any?(&:confidential?)
end
override :project_ids
def project_ids
- issue.project_id
+ issues&.distinct&.select(:project_id)
end
override :authorized_users
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index ebfb20132d0..4743e9b02ce 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -37,8 +37,8 @@ module Todos
private
def enqueue_private_features_worker
- project_ids.each do |project_id|
- TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user.id)
+ projects.each do |project|
+ TodosDestroyer::PrivateFeaturesWorker.perform_async(project.id, user.id)
end
end
@@ -62,9 +62,8 @@ module Todos
end
# rubocop: enable CodeReuse/ActiveRecord
- override :project_ids
# rubocop: disable CodeReuse/ActiveRecord
- def project_ids
+ def projects
condition = case entity
when Project
{ id: entity.id }
@@ -72,13 +71,13 @@ module Todos
{ namespace_id: non_member_groups }
end
- Project.where(condition).select(:id)
+ Project.where(condition)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def non_authorized_projects
- project_ids.where('id NOT IN (?)', user.authorized_projects.select(:id))
+ projects.where('id NOT IN (?)', user.authorized_projects.select(:id))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -110,7 +109,7 @@ module Todos
authorized_reporter_projects = user
.authorized_projects(Gitlab::Access::REPORTER).select(:id)
- Issue.where(project_id: project_ids, confidential: true)
+ Issue.where(project_id: projects, confidential: true)
.where('project_id NOT IN(?)', authorized_reporter_projects)
.where('author_id != ?', user.id)
.where('id NOT IN (?)', assigned_ids)
diff --git a/app/services/update_deployment_service.rb b/app/services/update_deployment_service.rb
index aa7fcca1e2a..49a7d0178f4 100644
--- a/app/services/update_deployment_service.rb
+++ b/app/services/update_deployment_service.rb
@@ -27,6 +27,8 @@ class UpdateDeploymentService
deployment.tap(&:update_merge_request_metrics!)
end
+
+ deployment
end
private
diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb
index 41ca95b3b6f..403944557a2 100644
--- a/app/services/upload_service.rb
+++ b/app/services/upload_service.rb
@@ -6,7 +6,7 @@ class UploadService
end
def execute
- return nil unless @file && @file.size <= max_attachment_size
+ return unless @file && @file.size <= max_attachment_size
uploader = @uploader_class.new(@model, nil, @uploader_context)
uploader.store!(@file)
diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb
index e50840a9158..33444c2a7dc 100644
--- a/app/services/users/activity_service.rb
+++ b/app/services/users/activity_service.rb
@@ -30,7 +30,7 @@ module Users
return if @user.last_activity_on == today
- lease = Gitlab::ExclusiveLease.new("acitvity_service:#{@user.id}",
+ lease = Gitlab::ExclusiveLease.new("activity_service:#{@user.id}",
timeout: LEASE_TIMEOUT)
return unless lease.try_obtain
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 3f503f3da28..30f7743c56e 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -26,7 +26,7 @@ module Users
end
end
- identity_attrs = params.slice(:extern_uid, :provider)
+ identity_attrs = params.slice(*identity_params)
unless identity_attrs.empty?
user.identities.build(identity_attrs)
@@ -37,6 +37,10 @@ module Users
private
+ def identity_params
+ [:extern_uid, :provider]
+ end
+
def can_create_user?
(current_user.nil? && Gitlab::CurrentSettings.allow_signup?) || current_user&.admin?
end
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 04fd6e37501..a66b6627e40 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -33,7 +33,7 @@ module Users
end
end
- user.reload
+ user.reset
end
private
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index fe5a82e23fa..4a26d2be2af 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -25,7 +25,7 @@ module Users
# We need an up to date User object that has access to all relations that
# may have been created earlier. The only way to ensure this is to reload
# the User object.
- user.reload
+ user.reset
end
def execute
@@ -84,7 +84,7 @@ module Users
# Since we batch insert authorization rows, Rails' associations may get
# out of sync. As such we force a reload of the User object.
- user.reload
+ user.reset
end
def fresh_access_levels_per_project
diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb
index c19e2ec2043..3f4a59e5cee 100644
--- a/app/services/validate_new_branch_service.rb
+++ b/app/services/validate_new_branch_service.rb
@@ -3,14 +3,14 @@
require_relative 'base_service'
class ValidateNewBranchService < BaseService
- def execute(branch_name)
+ def execute(branch_name, force: false)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
unless valid_branch
return error('Branch name is invalid')
end
- if project.repository.branch_exists?(branch_name)
+ if project.repository.branch_exists?(branch_name) && !force
return error('Branch already exists')
end
diff --git a/app/services/verify_pages_domain_service.rb b/app/services/verify_pages_domain_service.rb
index 07f7391f877..b53c3145caf 100644
--- a/app/services/verify_pages_domain_service.rb
+++ b/app/services/verify_pages_domain_service.rb
@@ -8,6 +8,7 @@ class VerifyPagesDomainService < BaseService
# How long verification lasts for
VERIFICATION_PERIOD = 7.days
+ REMOVAL_DELAY = 1.week.freeze
attr_reader :domain
@@ -36,7 +37,7 @@ class VerifyPagesDomainService < BaseService
# Prevent any pre-existing grace period from being truncated
reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max
- domain.assign_attributes(verified_at: Time.now, enabled_until: reverify)
+ domain.assign_attributes(verified_at: Time.now, enabled_until: reverify, remove_at: nil)
domain.save!(validate: false)
if was_disabled
@@ -49,18 +50,20 @@ class VerifyPagesDomainService < BaseService
end
def unverify_domain!
- if domain.verified?
- domain.assign_attributes(verified_at: nil)
- domain.save!(validate: false)
+ was_verified = domain.verified?
- notify(:verification_failed)
- end
+ domain.assign_attributes(verified_at: nil)
+ domain.remove_at ||= REMOVAL_DELAY.from_now unless domain.enabled?
+ domain.save!(validate: false)
+
+ notify(:verification_failed) if was_verified
error("Couldn't verify #{domain.domain}")
end
def disable_domain!
domain.assign_attributes(verified_at: nil, enabled_until: nil)
+ domain.remove_at ||= REMOVAL_DELAY.from_now
domain.save!(validate: false)
notify(:disabled)
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 0a166335b4e..b488bba00e9 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -9,6 +9,6 @@ class AttachmentUploader < GitlabUploader
private
def dynamic_segment
- File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s)
+ File.join(model.class.underscore, mounted_as.to_s, model.id.to_s)
end
end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index c0165759203..9af59b0aceb 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -25,6 +25,6 @@ class AvatarUploader < GitlabUploader
private
def dynamic_segment
- File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s)
+ File.join(model.class.underscore, mounted_as.to_s, model.id.to_s)
end
end
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index a7f8615e9ba..236b7ed2b3d 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -11,6 +11,8 @@ class FileMover
end
def execute
+ return unless valid?
+
move
if update_markdown
@@ -21,6 +23,12 @@ class FileMover
private
+ def valid?
+ Pathname.new(temp_file_path).realpath.to_path.start_with?(
+ (Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
+ )
+ end
+
def move
FileUtils.mkdir_p(File.dirname(file_path))
FileUtils.move(temp_file_path, file_path)
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index e90599f2505..1c7582533ad 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -14,8 +14,8 @@ class FileUploader < GitlabUploader
include ObjectStorage::Concern
prepend ObjectStorage::Extension::RecordsUploads
- MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
- DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)}
+ MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
+ DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)}.freeze
after :remove, :prune_store_dir
@@ -109,12 +109,20 @@ class FileUploader < GitlabUploader
def upload_path
if file_storage?
# Legacy path relative to project.full_path
- File.join(dynamic_segment, identifier)
+ local_storage_path(identifier)
else
- File.join(store_dir, identifier)
+ remote_storage_path(identifier)
end
end
+ def local_storage_path(file_identifier)
+ File.join(dynamic_segment, file_identifier)
+ end
+
+ def remote_storage_path(file_identifier)
+ File.join(store_dir, file_identifier)
+ end
+
def store_dirs
{
Store::LOCAL => File.join(base_dir, dynamic_segment),
diff --git a/app/uploaders/import_export_uploader.rb b/app/uploaders/import_export_uploader.rb
index 716922bc017..104d5d3b3dd 100644
--- a/app/uploaders/import_export_uploader.rb
+++ b/app/uploaders/import_export_uploader.rb
@@ -7,10 +7,6 @@ class ImportExportUploader < AttachmentUploader
EXTENSION_WHITELIST
end
- def move_to_store
- true
- end
-
def move_to_cache
false
end
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
deleted file mode 100644
index a9afc104ed1..00000000000
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-class LegacyArtifactUploader < GitlabUploader
- extend Workhorse::UploadPath
- include ObjectStorage::Concern
-
- ObjectNotReadyError = Class.new(StandardError)
-
- storage_options Gitlab.config.artifacts
-
- alias_method :upload, :model
-
- def store_dir
- dynamic_segment
- end
-
- private
-
- def dynamic_segment
- raise ObjectNotReadyError, 'Build is not ready' unless model.id
-
- File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
- end
-end
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index dad6e85fb56..0a44d60778d 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -117,7 +117,7 @@ module ObjectStorage
next unless uploader
next unless uploader.exists?
- next unless send(:"#{mounted_as}_changed?") # rubocop:disable GitlabSecurity/PublicSend
+ next unless send(:"saved_change_to_#{mounted_as}?") # rubocop:disable GitlabSecurity/PublicSend
mount
end.keys
@@ -278,8 +278,12 @@ module ObjectStorage
self.class.object_store_credentials
end
+ # Set ACL of uploaded objects to not-public (fog-aws)[1] or no ACL at all
+ # (fog-google). Value is ignored by other supported backends (fog-aliyun,
+ # fog-openstack, fog-rackspace)
+ # [1]: https://github.com/fog/fog-aws/blob/daa50bb3717a462baf4d04d0e0cbfc18baacb541/lib/fog/aws/models/storage/file.rb#L152-L159
def fog_public
- false
+ nil
end
def delete_migrated_file(migrated_file)
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 272837aa6ce..b43162f0935 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -6,21 +6,18 @@ class PersonalFileUploader < FileUploader
options.storage_path
end
- def self.base_dir(model, store = nil)
- base_dirs(model)[store || Store::LOCAL]
- end
-
- def self.base_dirs(model)
- {
- Store::LOCAL => File.join(options.base_dir, model_path_segment(model)),
- Store::REMOTE => model_path_segment(model)
- }
+ def self.base_dir(model, _store = nil)
+ # base_dir is the path seen by the user when rendering Markdown, so
+ # it should be the same for both local and object storage. It is
+ # typically prefaced with uploads/-/system, but that prefix
+ # is omitted in the path stored on disk.
+ File.join(options.base_dir, model_path_segment(model))
end
def self.model_path_segment(model)
return 'temp/' unless model
- File.join(model.class.to_s.underscore, model.id.to_s)
+ File.join(model.class.underscore, model.id.to_s)
end
def object_store
@@ -40,8 +37,61 @@ class PersonalFileUploader < FileUploader
store_dirs[object_store]
end
+ # A personal snippet path is stored using FileUploader#upload_path.
+ #
+ # The format for the path:
+ #
+ # Local storage: :random_hex/:filename.
+ # Object storage: personal_snippet/:id/:random_hex/:filename.
+ #
+ # upload_paths represent the possible paths for a given identifier,
+ # which will vary depending on whether the file is stored in local or
+ # object storage. upload_path should match an element in upload_paths.
+ #
+ # base_dir represents the path seen by the user in Markdown, and it
+ # should always be prefixed with uploads/-/system.
+ #
+ # store_dirs represent the paths that are actually used on disk. For
+ # object storage, this should omit the prefix /uploads/-/system.
+ #
+ # For example, consider the requested path /uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png.
+ #
+ # For local storage:
+ #
+ # File on disk: /opt/gitlab/embedded/service/gitlab-rails/public/uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png.
+ #
+ # base_dir: uploads/-/system/personal_snippet/172
+ # upload_path: ff4ad5c2e40b39ae57cda51577317d20/file.png
+ # upload_paths: ["ff4ad5c2e40b39ae57cda51577317d20/file.png", "personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png"].
+ # store_dirs:
+ # => {1=>"uploads/-/system/personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20", 2=>"personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20"}
+ #
+ # For object storage:
+ #
+ # upload_path: personal_snippet/172/ff4ad5c2e40b39ae57cda51577317d20/file.png
+ def upload_paths(identifier)
+ [
+ local_storage_path(identifier),
+ File.join(remote_storage_base_path, identifier)
+ ]
+ end
+
+ def store_dirs
+ {
+ Store::LOCAL => File.join(base_dir, dynamic_segment),
+ Store::REMOTE => remote_storage_base_path
+ }
+ end
+
private
+ # To avoid prefacing the remote storage path with `/uploads/-/system`,
+ # we just drop that part so that the destination path will be
+ # personal_snippet/:id/:random_hex/:filename.
+ def remote_storage_base_path
+ File.join(self.class.model_path_segment(model), dynamic_segment)
+ end
+
def secure_url
File.join('/', base_dir, secret, file.filename)
end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 9a243e07936..00b51f92b12 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -46,6 +46,10 @@ module RecordsUploads
File.join(store_dir, filename.to_s)
end
+ def filename
+ upload&.path ? File.basename(upload.path) : super
+ end
+
private
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
new file mode 100644
index 00000000000..273e15ef925
--- /dev/null
+++ b/app/validators/addressable_url_validator.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+# AddressableUrlValidator
+#
+# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks
+# for using the right protocol, but it actually parses the URL checking for any syntax errors.
+# The regex is also different from `URI` as we use `Addressable::URI` here.
+#
+# By default, only URLs for the HTTP(S) schemes will be considered valid.
+# Provide a `:schemes` option to configure accepted schemes.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :personal_url, addressable_url: true
+#
+# validates :ftp_url, addressable_url: { schemes: %w(ftp) }
+#
+# validates :git_url, addressable_url: { schemes: %w(http https ssh git) }
+# end
+#
+# This validator can also block urls pointing to localhost or the local network to
+# protect against Server-side Request Forgery (SSRF), or check for the right port.
+#
+# Configuration options:
+# * <tt>message</tt> - A custom error message (default is: "must be a valid URL").
+# * <tt>schemes</tt> - Array of URI schemes. Default: +['http', 'https']+
+# * <tt>allow_localhost</tt> - Allow urls pointing to +localhost+. Default: +true+
+# * <tt>allow_local_network</tt> - Allow urls pointing to private network addresses. Default: +true+
+# * <tt>allow_blank</tt> - Allow urls to be +blank+. Default: +false+
+# * <tt>allow_nil</tt> - Allow urls to be +nil+. Default: +false+
+# * <tt>ports</tt> - Allowed ports. Default: +all+.
+# * <tt>enforce_user</tt> - Validate user format. Default: +false+
+# * <tt>enforce_sanitization</tt> - Validate that there are no html/css/js tags. Default: +false+
+#
+# Example:
+# class User < ActiveRecord::Base
+# validates :personal_url, addressable_url: { allow_localhost: false, allow_local_network: false}
+#
+# validates :web_url, addressable_url: { ports: [80, 443] }
+# end
+class AddressableUrlValidator < ActiveModel::EachValidator
+ attr_reader :record
+
+ BLOCKER_VALIDATE_OPTIONS = {
+ schemes: %w(http https),
+ ports: [],
+ allow_localhost: true,
+ allow_local_network: true,
+ ascii_only: false,
+ enforce_user: false,
+ enforce_sanitization: false
+ }.freeze
+
+ DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({
+ message: 'must be a valid URL'
+ }).freeze
+
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ super(options)
+ end
+
+ def validate_each(record, attribute, value)
+ @record = record
+
+ unless value.present?
+ record.errors.add(attribute, options.fetch(:message))
+ return
+ end
+
+ value = strip_value!(record, attribute, value)
+
+ Gitlab::UrlBlocker.validate!(value, blocker_args)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ record.errors.add(attribute, "is blocked: #{e.message}")
+ end
+
+ private
+
+ def strip_value!(record, attribute, value)
+ new_value = value.strip
+ return value if new_value == value
+
+ record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def current_options
+ options.map do |option, value|
+ [option, value.is_a?(Proc) ? value.call(record) : value]
+ end.to_h
+ end
+
+ def blocker_args
+ current_options.slice(*BLOCKER_VALIDATE_OPTIONS.keys).tap do |args|
+ if self.class.allow_setting_local_requests?
+ args[:allow_localhost] = args[:allow_local_network] = true
+ end
+ end
+ end
+
+ def self.allow_setting_local_requests?
+ # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
+ # uses UrlValidator to validate urls. This ends up in a cycle
+ # when Gitlab::CurrentSettings creates an ApplicationSetting which then
+ # calls this validator.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
+ ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
+ end
+end
diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb
index 85fd63f08e5..79c9c67ae58 100644
--- a/app/validators/cluster_name_validator.rb
+++ b/app/validators/cluster_name_validator.rb
@@ -5,7 +5,9 @@
# Custom validator for ClusterName.
class ClusterNameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- if record.managed?
+ if record.provided_by_user?
+ record.errors.add(attribute, " has to be present") unless value.present?
+ else
if record.persisted? && record.name_changed?
record.errors.add(attribute, " can not be changed because it's synchronized with provider")
end
@@ -17,10 +19,6 @@ class ClusterNameValidator < ActiveModel::EachValidator
unless value =~ Gitlab::Regex.kubernetes_namespace_regex
record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message)
end
- else
- unless value.present?
- record.errors.add(attribute, " has to be present")
- end
end
end
end
diff --git a/app/validators/devise_email_validator.rb b/app/validators/devise_email_validator.rb
new file mode 100644
index 00000000000..6ca921ca7fa
--- /dev/null
+++ b/app/validators/devise_email_validator.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+# DeviseEmailValidator
+#
+# Custom validator for email formats. It asserts that there are no
+# @ symbols or whitespaces in either the localpart or the domain, and that
+# there is a single @ symbol separating the localpart and the domain.
+#
+# The available options are:
+# - regexp: Email regular expression used to validate email formats as instance of Regexp class.
+# If provided value has different type then a new Rexexp class instance is created using the value.
+# Default: +Devise.email_regexp+
+#
+# Example:
+# class User < ActiveRecord::Base
+# validates :personal_email, devise_email: true
+#
+# validates :public_email, devise_email: { regexp: Devise.email_regexp }
+# end
+class DeviseEmailValidator < ActiveModel::EachValidator
+ DEFAULT_OPTIONS = {
+ regexp: Devise.email_regexp
+ }.freeze
+
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ raise ArgumentError, "Expected 'regexp' argument of type class Regexp" unless options[:regexp].is_a?(Regexp)
+
+ super(options)
+ end
+
+ def validate_each(record, attribute, value)
+ record.errors.add(attribute, :invalid) unless value =~ options[:regexp]
+ end
+end
diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb
deleted file mode 100644
index 9459edb7515..00000000000
--- a/app/validators/email_validator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class EmailValidator < ActiveModel::EachValidator
- def validate_each(record, attribute, value)
- record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
- end
-end
diff --git a/app/validators/public_url_validator.rb b/app/validators/public_url_validator.rb
index 3ff880deedd..91847c5d866 100644
--- a/app/validators/public_url_validator.rb
+++ b/app/validators/public_url_validator.rb
@@ -2,7 +2,7 @@
# PublicUrlValidator
#
-# Custom validator for URLs. This validator works like UrlValidator but
+# Custom validator for URLs. This validator works like AddressableUrlValidator but
# it blocks by default urls pointing to localhost or the local network.
#
# This validator accepts the same params UrlValidator does.
@@ -12,17 +12,20 @@
# class User < ActiveRecord::Base
# validates :personal_url, public_url: true
#
-# validates :ftp_url, public_url: { protocols: %w(ftp) }
+# validates :ftp_url, public_url: { schemes: %w(ftp) }
#
# validates :git_url, public_url: { allow_localhost: true, allow_local_network: true}
# end
#
-class PublicUrlValidator < UrlValidator
- private
+class PublicUrlValidator < AddressableUrlValidator
+ DEFAULT_OPTIONS = {
+ allow_localhost: false,
+ allow_local_network: false
+ }.freeze
- def default_options
- # By default block all urls pointing to localhost or the local network
- super.merge(allow_localhost: false,
- allow_local_network: false)
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ super(options)
end
end
diff --git a/app/validators/sha_validator.rb b/app/validators/sha_validator.rb
new file mode 100644
index 00000000000..77e7cfa4f6b
--- /dev/null
+++ b/app/validators/sha_validator.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class ShaValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return if value.blank? || Commit.valid_hash?(value)
+
+ record.errors.add(attribute, 'is not a valid SHA')
+ end
+end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
deleted file mode 100644
index 3fd015c3cf5..00000000000
--- a/app/validators/url_validator.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-# frozen_string_literal: true
-
-# UrlValidator
-#
-# Custom validator for URLs.
-#
-# By default, only URLs for the HTTP(S) protocols will be considered valid.
-# Provide a `:protocols` option to configure accepted protocols.
-#
-# Example:
-#
-# class User < ActiveRecord::Base
-# validates :personal_url, url: true
-#
-# validates :ftp_url, url: { protocols: %w(ftp) }
-#
-# validates :git_url, url: { protocols: %w(http https ssh git) }
-# end
-#
-# This validator can also block urls pointing to localhost or the local network to
-# protect against Server-side Request Forgery (SSRF), or check for the right port.
-#
-# The available options are:
-# - protocols: Allowed protocols. Default: http and https
-# - allow_localhost: Allow urls pointing to localhost. Default: true
-# - allow_local_network: Allow urls pointing to private network addresses. Default: true
-# - ports: Allowed ports. Default: all.
-# - enforce_user: Validate user format. Default: false
-# - enforce_sanitization: Validate that there are no html/css/js tags. Default: false
-#
-# Example:
-# class User < ActiveRecord::Base
-# validates :personal_url, url: { allow_localhost: false, allow_local_network: false}
-#
-# validates :web_url, url: { ports: [80, 443] }
-# end
-class UrlValidator < ActiveModel::EachValidator
- DEFAULT_PROTOCOLS = %w(http https).freeze
-
- attr_reader :record
-
- def validate_each(record, attribute, value)
- @record = record
-
- unless value.present?
- record.errors.add(attribute, 'must be a valid URL')
- return
- end
-
- value = strip_value!(record, attribute, value)
-
- Gitlab::UrlBlocker.validate!(value, blocker_args)
- rescue Gitlab::UrlBlocker::BlockedUrlError => e
- record.errors.add(attribute, "is blocked: #{e.message}")
- end
-
- private
-
- def strip_value!(record, attribute, value)
- new_value = value.strip
- return value if new_value == value
-
- record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def default_options
- # By default the validator doesn't block any url based on the ip address
- {
- protocols: DEFAULT_PROTOCOLS,
- ports: [],
- allow_localhost: true,
- allow_local_network: true,
- ascii_only: false,
- enforce_user: false,
- enforce_sanitization: false
- }
- end
-
- def current_options
- options = self.options.map do |option, value|
- [option, value.is_a?(Proc) ? value.call(record) : value]
- end.to_h
-
- default_options.merge(options)
- end
-
- def blocker_args
- current_options.slice(*default_options.keys).tap do |args|
- if allow_setting_local_requests?
- args[:allow_localhost] = args[:allow_local_network] = true
- end
- end
- end
-
- def allow_setting_local_requests?
- # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
- # uses UrlValidator to validate urls. This ends up in a cycle
- # when Gitlab::CurrentSettings creates an ApplicationSetting which then
- # calls this validator.
- #
- # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
- ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
- end
-end
diff --git a/app/validators/x509_certificate_credentials_validator.rb b/app/validators/x509_certificate_credentials_validator.rb
new file mode 100644
index 00000000000..d2f18e956c3
--- /dev/null
+++ b/app/validators/x509_certificate_credentials_validator.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+# X509CertificateCredentialsValidator
+#
+# Custom validator to check if certificate-attribute was signed using the
+# private key stored in an attrebute.
+#
+# This can be used as an `ActiveModel::Validator` as follows:
+#
+# validates_with X509CertificateCredentialsValidator,
+# certificate: :client_certificate,
+# pkey: :decrypted_private_key,
+# pass: :decrypted_passphrase
+#
+#
+# Required attributes:
+# - certificate: The name of the accessor that returns the certificate to check
+# - pkey: The name of the accessor that returns the private key
+# Optional:
+# - pass: The name of the accessor that returns the passphrase to decrypt the
+# private key
+class X509CertificateCredentialsValidator < ActiveModel::Validator
+ def initialize(*args)
+ super
+
+ # We can't validate if we don't have a private key or certificate attributes
+ # in which case this validator is useless.
+ if options[:pkey].nil? || options[:certificate].nil?
+ raise 'Provide at least `certificate` and `pkey` attribute names'
+ end
+ end
+
+ def validate(record)
+ unless certificate = read_certificate(record)
+ record.errors.add(options[:certificate], _('is not a valid X509 certificate.'))
+ end
+
+ unless private_key = read_private_key(record)
+ record.errors.add(options[:pkey], _('could not read private key, is the passphrase correct?'))
+ end
+
+ return if private_key.nil? || certificate.nil?
+
+ unless certificate.public_key.fingerprint == private_key.public_key.fingerprint
+ record.errors.add(options[:pkey], _('private key does not match certificate.'))
+ end
+ end
+
+ private
+
+ def read_private_key(record)
+ OpenSSL::PKey.read(pkey(record).to_s, pass(record).to_s)
+ rescue OpenSSL::PKey::PKeyError, ArgumentError
+ # When the primary key could not be read, an ArgumentError is raised.
+ # This hapens when the passed key is not valid or the passphrase is incorrect
+ nil
+ end
+
+ def read_certificate(record)
+ OpenSSL::X509::Certificate.new(certificate(record).to_s)
+ rescue OpenSSL::X509::CertificateError
+ nil
+ end
+
+ # rubocop:disable GitlabSecurity/PublicSend
+ #
+ # Allowing `#public_send` here because we don't want the validator to really
+ # care about the names of the attributes or where they come from.
+ #
+ # The credentials are mostly stored encrypted so we need to go through the
+ # accessors to get the values, `read_attribute` bypasses those.
+ def certificate(record)
+ record.public_send(options[:certificate])
+ end
+
+ def pkey(record)
+ record.public_send(options[:pkey])
+ end
+
+ def pass(record)
+ return unless options[:pass]
+
+ record.public_send(options[:pass])
+ end
+ # rubocop:enable GitlabSecurity/PublicSend
+end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 92ae40512c5..c6781e91cfd 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -1,22 +1,24 @@
-- page_title _("Report abuse to GitLab")
+- page_title _("Report abuse to admin")
%h3.page-title
- = _("Report abuse to GitLab")
+ = _("Report abuse to admin")
%p
- = _("Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately.")
+ = _("Please use this form to report to the admin users who create spam issues, comments or behave inappropriately.")
%p
- = _("A member of GitLab's abuse team will review your report as soon as possible.")
+ = _("A member of the abuse team will review your report as soon as possible.")
%hr
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
= f.hidden_field :user_id
.form-group.row
- = f.label :user_id, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :user_id
.col-sm-10
- name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})"
= text_field_tag :user_name, name, class: "form-control", readonly: true
.form-group.row
- = f.label :message, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :message
.col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.form-text.text-muted
diff --git a/app/views/admin/appearances/_system_header_footer_form.html.haml b/app/views/admin/appearances/_system_header_footer_form.html.haml
index ca9d6adebeb..4301ebd05af 100644
--- a/app/views/admin/appearances/_system_header_footer_form.html.haml
+++ b/app/views/admin/appearances/_system_header_footer_form.html.haml
@@ -13,6 +13,15 @@
.form-group
= form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold'
= form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control js-autosize"
+ .form-group
+ .form-check
+ = form.check_box :email_header_and_footer_enabled, class: 'form-check-input'
+ = form.label :email_header_and_footer_enabled, class: 'label-bold' do
+ = _('Enable header and footer in emails')
+
+ .hint
+ = _('Add header and footer to emails. Please note that color settings will only be applied within the application interface')
+
.form-group.js-toggle-colors-container
%button.btn.btn-link.js-toggle-colors-link{ type: 'button' }
= _('Customize colors')
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 65a24854583..9ed4bc44aae 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -6,32 +6,35 @@
.form-check
= f.check_box :gravatar_enabled, class: 'form-check-input'
= f.label :gravatar_enabled, class: 'form-check-label' do
- Gravatar enabled
+ = _('Gravatar enabled')
.form-group
= f.label :default_projects_limit, class: 'label-bold'
= f.number_field :default_projects_limit, class: 'form-control'
.form-group
- = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'label-bold'
+ = f.label :max_attachment_size, _('Maximum attachment size (MB)'), class: 'label-bold'
= f.number_field :max_attachment_size, class: 'form-control'
+
+ = render_if_exists 'admin/application_settings/repository_size_limit_setting', form: f
+
.form-group
- = f.label :receive_max_input_size, 'Maximum push size (MB)', class: 'label-light'
+ = f.label :receive_max_input_size, _('Maximum push size (MB)'), class: 'label-light'
= f.number_field :receive_max_input_size, class: 'form-control qa-receive-max-input-size-field'
.form-group
- = f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-light'
+ = f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light'
= f.number_field :session_expire_delay, class: 'form-control'
- %span.form-text.text-muted#session_expire_delay_help_block GitLab restart is required to apply changes
+ %span.form-text.text-muted#session_expire_delay_help_block= _('GitLab restart is required to apply changes')
.form-group
- = f.label :user_oauth_applications, 'User OAuth applications', class: 'label-bold'
+ = f.label :user_oauth_applications, _('User OAuth applications'), class: 'label-bold'
.form-check
= f.check_box :user_oauth_applications, class: 'form-check-input'
= f.label :user_oauth_applications, class: 'form-check-label' do
- Allow users to register any application to use GitLab as an OAuth provider
+ = _('Allow users to register any application to use GitLab as an OAuth provider')
.form-group
- = f.label :user_default_external, 'New users set to external', class: 'label-bold'
+ = f.label :user_default_external, _('New users set to external'), class: 'label-bold'
.form-check
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
- Newly registered users will by default be external
+ = _('Newly registered users will by default be external')
.prepend-top-10
= _('Internal users')
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
@@ -40,10 +43,12 @@
= link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
target: '_blank'
.form-group
- = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
+ = f.label :user_show_add_ssh_key_message, _('Prompt users to upload SSH keys'), class: 'label-bold'
.form-check
= f.check_box :user_show_add_ssh_key_message, class: 'form-check-input'
= f.label :user_show_add_ssh_key_message, class: 'form-check-label' do
- Inform users without uploaded SSH keys that they can't push over SSH until one is added
+ = _("Inform users without uploaded SSH keys that they can't push over SSH until one is added")
+
+ = render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f
- = f.submit 'Save changes', class: 'btn btn-success qa-save-changes-button'
+ = f.submit _('Save changes'), class: 'btn btn-success qa-save-changes-button'
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index c99d7e9b8e9..b8c481df0d2 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -8,7 +8,7 @@
.form-check
= f.check_box :auto_devops_enabled, class: 'form-check-input'
= f.label :auto_devops_enabled, class: 'form-check-label' do
- Default to Auto DevOps pipeline for all projects
+ = s_('CICD|Default to Auto DevOps pipeline for all projects')
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
@@ -21,34 +21,31 @@
.form-check
= f.check_box :shared_runners_enabled, class: 'form-check-input'
= f.label :shared_runners_enabled, class: 'form-check-label' do
- Enable shared runners for new projects
+ = s_("AdminSettings|Enable shared runners for new projects")
+
+ = render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
+
.form-group
= f.label :shared_runners_text, class: 'label-bold'
= f.text_area :shared_runners_text, class: 'form-control', rows: 4
- .form-text.text-muted Markdown enabled
+ .form-text.text-muted= _("Markdown enabled")
.form-group
- = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'label-bold'
+ = f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control'
.form-text.text-muted
- Set the maximum file size for each job's artifacts
+ = _("Set the maximum file size for each job's artifacts")
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
.form-group
- = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'label-bold'
+ = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
= f.text_field :default_artifacts_expire_in, class: 'form-control'
.form-text.text-muted
- Set the default expiration time for each job's artifacts.
- 0 for unlimited.
- The default unit is in seconds, but you can define an alternative. For example:
- <code>4 mins 2 sec</code>, <code>2h42min</code>.
+ = _("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>.").html_safe
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group
- = f.label :archive_builds_in_human_readable, 'Archive jobs', class: 'label-bold'
+ = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
.form-text.text-muted
- Set the duration for which the jobs will be considered as old and expired.
- Once that time passes, the jobs will be archived and no longer able to be
- retried. Make it empty to never expire jobs. It has to be no less than 1 day,
- for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
+ = _("Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.").html_safe
.form-group
.form-check
= f.check_box :protected_ci_variables, class: 'form-check-input'
@@ -57,4 +54,4 @@
.form-text.text-muted
= s_('AdminSettings|When creating a new environment variable it will be protected by default.')
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index 60a6be731ea..3f30c75fbb6 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -6,20 +6,16 @@
.form-check
= f.check_box :email_author_in_body, class: 'form-check-input'
= f.label :email_author_in_body, class: 'form-check-label' do
- Include author name in notification email body
+ = _('Include author name in notification email body')
.form-text.text-muted
- Some email servers do not support overriding the email sender name.
- Enable this option to include the name of the author of the issue,
- merge request or comment in the email body instead.
+ = _('Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.')
.form-group
.form-check
= f.check_box :html_emails_enabled, class: 'form-check-input'
= f.label :html_emails_enabled, class: 'form-check-label' do
- Enable HTML emails
+ = _('Enable HTML emails')
.form-text.text-muted
- By default GitLab sends emails in HTML and plain text formats so mail
- clients can choose what format to use. Disable this option if you only
- want to send emails in plain text format.
+ = _('By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.')
.form-group
= f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold'
= f.text_field :commit_email_hostname, class: 'form-control'
@@ -27,4 +23,6 @@
- commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email', anchor: 'custom-private-commit-email-hostname'), target: '_blank'
= _("This setting will update the hostname that is used to generate private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
- = f.submit 'Save changes', class: "btn btn-success"
+ = render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
new file mode 100644
index 00000000000..7587ecbf9d3
--- /dev/null
+++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
@@ -0,0 +1,51 @@
+%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('External authentication')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('External Classification Policy Authorization')
+ .settings-content
+
+ = form_for @application_setting, url: admin_application_settings_path(anchor: 'js-external-auth-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :external_authorization_service_enabled, class: 'form-check-input'
+ = f.label :external_authorization_service_enabled, class: 'form-check-label' do
+ = _('Enable classification control using an external service')
+ %span.form-text.text-muted
+ = external_authorization_description
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/external_authorization')
+ .form-group
+ = f.label :external_authorization_service_url, _('Service URL'), class: 'label-bold'
+ = f.text_field :external_authorization_service_url, class: 'form-control'
+ %span.form-text.text-muted
+ = external_authorization_url_help_text
+ .form-group
+ = f.label :external_authorization_service_timeout, _('External authorization request timeout'), class: 'label-bold'
+ = f.number_field :external_authorization_service_timeout, class: 'form-control', min: 0.001, max: 10, step: 0.001
+ %span.form-text.text-muted
+ = external_authorization_timeout_help_text
+ = f.label :external_auth_client_cert, _('Client authentication certificate'), class: 'label-bold'
+ = f.text_area :external_auth_client_cert, class: 'form-control'
+ %span.form-text.text-muted
+ = external_authorization_client_certificate_help_text
+ .form-group
+ = f.label :external_auth_client_key, _('Client authentication key'), class: 'label-bold'
+ = f.text_area :external_auth_client_key, class: 'form-control'
+ %span.form-text.text-muted
+ = external_authorization_client_key_help_text
+ .form-group
+ = f.label :external_auth_client_key_pass, _('Client authentication key password'), class: 'label-bold'
+ = f.password_field :external_auth_client_key_pass, class: 'form-control'
+ %span.form-text.text-muted
+ = external_authorization_client_pass_help_text
+ .form-group
+ = f.label :external_authorization_service_default_label, _('Default classification label'), class: 'label-bold'
+ = f.text_field :external_authorization_service_default_label, class: 'form-control'
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
index 70c8c74cc5d..aa491c735d1 100644
--- a/app/views/admin/application_settings/_help_page.html.haml
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -2,18 +2,20 @@
= form_errors(@application_setting)
%fieldset
+ = render_if_exists 'admin/application_settings/help_text_setting', form: f
+
.form-group
= f.label :help_page_text, class: 'label-bold'
= f.text_area :help_page_text, class: 'form-control', rows: 4
- .form-text.text-muted Markdown enabled
+ .form-text.text-muted= _('Markdown enabled')
.form-group
.form-check
= f.check_box :help_page_hide_commercial_content, class: 'form-check-input'
= f.label :help_page_hide_commercial_content, class: 'form-check-label' do
- Hide marketing-related entries from help
+ = _('Hide marketing-related entries from help')
.form-group
- = f.label :help_page_support_url, 'Support page URL', class: 'label-bold'
+ = f.label :help_page_support_url, _('Support page URL'), class: 'label-bold'
= f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
- %span.form-text.text-muted#support_help_block Alternate support URL for help page
+ %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page')
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_logging.html.haml b/app/views/admin/application_settings/_logging.html.haml
index 41b787515b5..1da5f6fccd6 100644
--- a/app/views/admin/application_settings/_logging.html.haml
+++ b/app/views/admin/application_settings/_logging.html.haml
@@ -1,6 +1,12 @@
= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-logging-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
+ %p
+ %strong
+ NOTE:
+ These settings will be removed from the UI in a GitLab 12.0 release and made available within gitlab.yml.
+ In addition, you will be able to define a Sentry Environment to differentiate between multiple deployments. For example, development, staging, and production.
+
%fieldset
.form-group
.form-check
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index f4bfb5af385..dd56bb99a06 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -8,4 +8,12 @@
= f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
Allow requests to the local network from hooks and services
+ .form-group
+ .form-check
+ = f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input'
+ = f.label :dns_rebinding_protection_enabled, class: 'form-check-label' do
+ = _('Enforce DNS rebinding attack protection')
+ %span.form-text.text-muted
+ = _('Resolves IP addresses once and uses them to submit requests')
+
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml
index ad5c8d4da22..77795dbf913 100644
--- a/app/views/admin/application_settings/_pages.html.haml
+++ b/app/views/admin/application_settings/_pages.html.haml
@@ -5,16 +5,32 @@
.form-group
= f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'label-bold'
= f.number_field :max_pages_size, class: 'form-control'
- .form-text.text-muted 0 for unlimited
+ .form-text.text-muted
+ = _("0 for unlimited")
.form-group
.form-check
= f.check_box :pages_domain_verification_enabled, class: 'form-check-input'
= f.label :pages_domain_verification_enabled, class: 'form-check-label' do
- Require users to prove ownership of custom domains
+ = _("Require users to prove ownership of custom domains")
.form-text.text-muted
- Domain verification is an essential security measure for public GitLab
- sites. Users are required to demonstrate they control a domain before
- it is enabled
+ = _("Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled")
= link_to icon('question-circle'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ - if Feature.enabled?(:pages_auto_ssl)
+ %h5
+ = _("Configure Let's Encrypt")
+ %p
+ - lets_encrypt_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: "https://letsencrypt.org/" }
+ = _("%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites.").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: '</a>'.html_safe }
+ .form-group
+ = f.label :lets_encrypt_notification_email, _("Email"), class: 'label-bold'
+ = f.text_field :lets_encrypt_notification_email, class: 'form-control'
+ .form-text.text-muted
+ = _("A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates.")
+ .form-group
+ .form-check
+ = f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
+ = f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
+ - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
+ = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml
index 44ac8d94764..f992d531ea5 100644
--- a/app/views/admin/application_settings/_performance_bar.html.haml
+++ b/app/views/admin/application_settings/_performance_bar.html.haml
@@ -5,10 +5,10 @@
.form-group
.form-check
= f.check_box :performance_bar_enabled, class: 'form-check-input'
- = f.label :performance_bar_enabled, class: 'form-check-label' do
+ = f.label :performance_bar_enabled, class: 'form-check-label qa-enable-performance-bar-checkbox' do
Enable the Performance Bar
.form-group
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold'
= f.text_field :performance_bar_allowed_group_path, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit 'Save changes', class: 'btn btn-success qa-save-changes-button'
diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
index 615aa6317b0..f2f2cd1282a 100644
--- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml
+++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
@@ -3,13 +3,15 @@
%fieldset
.form-group
- = f.label :mirror_available, 'Enable mirror configuration', class: 'label-bold'
+ = f.label :mirror_available, _('Enable mirror configuration'), class: 'label-bold'
.form-check
= f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do
- Allow mirrors to be set up for projects
+ = _('Allow mirrors to be set up for projects')
%span.form-text.text-muted
- If disabled, only admins will be able to set up mirrors in projects.
+ = _('If disabled, only admins will be able to set up mirrors in projects.')
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
- = f.submit 'Save changes', class: "btn btn-success"
+ = render_if_exists 'admin/application_settings/mirror_settings', form: f
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml
index 0725ffb7f6c..03ef2924617 100644
--- a/app/views/admin/application_settings/_visibility_and_access.html.haml
+++ b/app/views/admin/application_settings/_visibility_and_access.html.haml
@@ -5,6 +5,9 @@
.form-group
= f.label :default_branch_protection, class: 'label-bold'
= f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
+ .form-group
+ = f.label s_('ProjectCreationLevel|Default project creation protection'), class: 'label-bold'
+ = f.select :default_project_creation, options_for_select(Gitlab::Access.project_creation_options, @application_setting.default_project_creation), {}, class: 'form-control'
.form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'label-bold'
= render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
@@ -22,32 +25,33 @@
.form-check
= level
%span.form-text.text-muted#restricted-visibility-help
- Selected levels cannot be used by non-admin users for groups, projects or snippets.
- If the public level is restricted, user profiles are only visible to logged in users.
+ = _('Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.')
.form-group
= f.label :import_sources, class: 'label-bold'
= hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help', class: 'form-check-input').each do |source|
.form-check= source
%span.form-text.text-muted#import-sources-help
- Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
+ = _('Enabled sources for code import during project creation. OmniAuth must be configured for GitHub')
= link_to "(?)", help_page_path("integration/github")
, Bitbucket
= link_to "(?)", help_page_path("integration/bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration/gitlab")
+ = render_if_exists 'admin/application_settings/ldap_access_setting', form: f
+
.form-group
.form-check
= f.check_box :project_export_enabled, class: 'form-check-input'
= f.label :project_export_enabled, class: 'form-check-label' do
- Project export enabled
+ = _('Project export enabled')
.form-group
- %label.label-bold Enabled Git access protocols
+ %label.label-bold= _('Enabled Git access protocols')
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.form-text.text-muted#clone-protocol-help
- Allow only the selected protocols to be used for Git access.
+ = _('Allow only the selected protocols to be used for Git access.')
- ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
- field_name = :"#{type}_key_restriction"
@@ -55,4 +59,4 @@
= f.label field_name, "#{type.upcase} SSH keys", class: 'label-bold'
= f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control'
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml
index f50aca32bdf..d5ba6abe7af 100644
--- a/app/views/admin/application_settings/metrics_and_profiling.html.haml
+++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml
@@ -24,7 +24,7 @@
.settings-content
= render 'prometheus'
-%section.settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded_by_default?) }
+%section.settings.qa-performance-bar-settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Profiling - Performance bar')
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index fc9dd29b8ca..31f18ba0d56 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -68,7 +68,7 @@
.settings-content
= render 'terms'
-= render_if_exists 'admin/application_settings/external_authorization_service_form', expanded: expanded_by_default?
+= render 'admin/application_settings/external_authorization_service_form', expanded: expanded_by_default?
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index 12690343f6e..21e84016c66 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -2,13 +2,15 @@
= form_errors(application)
= content_tag :div, class: 'form-group row' do
- = f.label :name, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :name
.col-sm-10
= f.text_field :name, class: 'form-control'
= doorkeeper_errors_for application, :name
= content_tag :div, class: 'form-group row' do
- = f.label :redirect_uri, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :redirect_uri
.col-sm-10
= f.text_area :redirect_uri, class: 'form-control'
= doorkeeper_errors_for application, :redirect_uri
@@ -21,14 +23,16 @@
for local tests
= content_tag :div, class: 'form-group row' do
- = f.label :trusted, class: 'col-sm-2 col-form-label pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :trusted
.col-sm-10
= f.check_box :trusted
%span.form-text.text-muted
Trusted applications are automatically authorized on GitLab OAuth flow.
.form-group.row
- = f.label :scopes, class: 'col-sm-2 col-form-label pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :scopes
.col-sm-10
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index df3eeba907c..180066723f1 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -11,7 +11,7 @@
%td
.clipboard-group
.input-group
- %input.label.label-monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
+ %input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
.input-group-append
= clipboard_button(target: '#application_id', title: _("Copy ID to clipboard"), class: "btn btn btn-default")
%tr
@@ -20,7 +20,7 @@
%td
.clipboard-group
.input-group
- %input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
+ %input.label.label-monospace.monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
= clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index c465d9f51d6..c8ee87c6212 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -10,28 +10,34 @@
= form_errors(@broadcast_message)
.form-group.row
- = f.label :message, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :message
.col-sm-10
= f.text_area :message, class: "form-control js-autosize",
required: true,
+ dir: 'auto',
data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.row.js-toggle-colors-container
.col-sm-10.offset-sm-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
.form-group.row.js-toggle-colors-container.toggle-colors.hide
- = f.label :color, "Background Color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, "Background Color"
.col-sm-10
= f.color_field :color, class: "form-control"
.form-group.row.js-toggle-colors-container.toggle-colors.hide
- = f.label :font, "Font Color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :font, "Font Color"
.col-sm-10
= f.color_field :font, class: "form-control"
.form-group.row
- = f.label :starts_at, _("Starts at (UTC)"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :starts_at, _("Starts at (UTC)")
.col-sm-10.datetime-controls
= f.datetime_select :starts_at, {}, class: 'form-control form-control-inline'
.form-group.row
- = f.label :ends_at, _("Ends at (UTC)"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :ends_at, _("Ends at (UTC)")
.col-sm-10.datetime-controls
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 9ef58faf8cc..eb4dfdf2858 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -32,7 +32,7 @@
%td
= message.ends_at
%td
- = link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-sm'
- = link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-sm btn-danger'
+ = link_to sprite_icon('pencil-square'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn'
+ = link_to sprite_icon('remove'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-danger'
= paginate @broadcast_messages, theme: 'gitlab'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 6756299cf43..581f6ae0714 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -22,9 +22,10 @@
%h3.text-center
Users:
= approximate_count_with_delimiters(@counts, User)
- = render_if_exists 'admin/dashboard/users_statistics'
%hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ .btn-group.d-flex{ role: 'group' }
+ = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ = render_if_exists 'admin/dashboard/users_statistics'
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
@@ -162,7 +163,7 @@
%span.float-right
#{Rails::VERSION::STRING}
%p
- = Gitlab::Database.adapter_name
+ = Gitlab::Database.human_adapter_name
%span.float-right
= Gitlab::Database.version
%p
diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml
index 7c04ef03947..99d8af65068 100644
--- a/app/views/admin/deploy_keys/edit.html.haml
+++ b/app/views/admin/deploy_keys/edit.html.haml
@@ -1,10 +1,10 @@
-- page_title 'Edit Deploy Key'
-%h3.page-title Edit public deploy key
+- page_title _('Edit Deploy Key')
+%h3.page-title= _('Edit public deploy key')
%hr
%div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Save changes', class: 'btn-success btn'
- = link_to 'Cancel', admin_deploy_keys_path, class: 'btn btn-cancel'
+ = f.submit _('Save changes'), class: 'btn-success btn'
+ = link_to _('Cancel'), admin_deploy_keys_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 01013be06d6..9fffa97f969 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,19 +1,19 @@
-- page_title "Deploy Keys"
+- page_title _('Deploy Keys')
%h3.page-title.deploy-keys-title
- Public deploy keys (#{@deploy_keys.count})
+ = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.count }
.float-right
- = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
+ = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
- if @deploy_keys.any?
.table-holder.deploy-keys-list
%table.table
%thead
%tr
- %th.col-sm-2 Title
- %th.col-sm-4 Fingerprint
- %th.col-sm-2 Projects with write access
- %th.col-sm-2 Added at
+ %th.col-sm-2= _('Title')
+ %th.col-sm-4= _('Fingerprint')
+ %th.col-sm-2= _('Projects with write access')
+ %th.col-sm-2= _('Added at')
%th.col-sm-2
%tbody
- @deploy_keys.each do |deploy_key|
@@ -27,8 +27,8 @@
= link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label'
%td
%span.cgray
- added #{time_ago_with_tooltip(deploy_key.created_at)}
+ = _('added %{created_at_timeago}').html_safe % { created_at_timeago: time_ago_with_tooltip(deploy_key.created_at) }
%td
.float-right
- = link_to 'Edit', edit_admin_deploy_key_path(deploy_key), class: 'btn btn-sm'
- = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key'
+ = link_to _('Edit'), edit_admin_deploy_key_path(deploy_key), class: 'btn btn-sm'
+ = link_to _('Remove'), admin_deploy_key_path(deploy_key), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-sm btn-remove delete-key'
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 5e05568e384..dd01ef8a29f 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -2,13 +2,14 @@
= form_errors(@group)
= render 'shared/group_form', f: f
- = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
+ = render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
= render_if_exists 'admin/namespace_plan', f: f
.form-group.row.group-description-holder
- = f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :avatar, _("Group avatar")
.col-sm-10
- = render 'shared/choose_group_avatar_button', f: f
+ = render 'shared/choose_avatar_button', f: f
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 00d255846f9..f524d35d79e 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -44,12 +44,10 @@
%li
%span.light= _('Storage:')
- - counter_storage = storage_counter(@group.storage_size)
- - counter_repositories = storage_counter(@group.repository_size)
- - counter_build_artifacts = storage_counter(@group.build_artifacts_size)
- - counter_lfs_objects = storage_counter(@group.lfs_objects_size)
- %strong
- = _("%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)") % { counter_storage: counter_storage, counter_repositories: counter_repositories, counter_build_artifacts: counter_build_artifacts, counter_lfs_objects: counter_lfs_objects }
+ %strong= storage_counter(@group.storage_size)
+ (
+ = storage_counters_details(@group)
+ )
%li
%span.light= _('Group Git LFS status:')
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index 0f5e97e288a..ac56e354a4d 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -23,7 +23,7 @@
%code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li
%code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
-
+ = render_if_exists 'admin/health_check/health_check_url'
%hr
.card
.card-header
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 3ab7990d9e2..40a7014e143 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -2,12 +2,14 @@
= form_errors(@identity)
.form-group.row
- = f.label :provider, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :provider
.col-sm-10
- values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group.row
- = f.label :extern_uid, _("Identifier"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :extern_uid, _("Identifier")
.col-sm-10
= f.text_field :extern_uid, class: 'form-control', required: true
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 5e7b4817461..299d0a12e6c 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -2,15 +2,18 @@
= form_errors(@label)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group.row
- = f.label :description, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :description
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit"
.form-group.row
- = f.label :color, _("Background color"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, _("Background color")
.col-sm-10
.input-group
.input-group-prepend
@@ -21,10 +24,7 @@
%br
= _("Or you can choose one of the suggested colors below")
- .suggest-colors
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp;
+ = render_suggested_colors
.form-actions
= f.submit _('Save'), class: 'btn btn-success js-save-button'
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index dbb7224f5f9..6d934654c5d 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,5 +1,5 @@
%li.label-list-item{ id: dom_id(label) }
- = render "shared/label_row", label: label
+ = render "shared/label_row", label: label.present(issuable_subject: nil)
.label-actions-list
= link_to edit_admin_label_path(label), class: 'btn btn-transparent label-action has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do
= sprite_icon('pencil')
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 5bc695aa7b5..2f7ad35eb3e 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -7,17 +7,17 @@
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
%button.delete-project-button.btn.btn-danger{ data: { toggle: 'modal',
target: '#delete-project-modal',
- delete_project_url: project_path(project),
+ delete_project_url: admin_project_path(project),
project_name: project.name }, type: 'button' }
= s_('AdminProjects|Delete')
.stats
%span.badge.badge-pill
- = storage_counter(project.statistics.storage_size)
+ = storage_counter(project.statistics&.storage_size)
- if project.archived
%span.badge.badge-warning archived
.title
- = link_to(admin_namespace_project_path(project.namespace, project)) do
+ = link_to(admin_project_path(project)) do
.dash-project-avatar
.avatar-container.rect-avatar.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 46bb57c78a8..b88b760536d 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -7,7 +7,7 @@
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.prepend-top-default
.search-holder
- = render 'shared/projects/search_form', autofocus: true, icon: true
+ = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true
.dropdown
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 03cce4745aa..e23accc1ea9 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -73,16 +73,11 @@
= @project.repository.relative_path
%li
- %span.light Storage used:
- %strong= storage_counter(@project.statistics.storage_size)
- (
- = storage_counter(@project.statistics.repository_size)
- repository,
- = storage_counter(@project.statistics.build_artifacts_size)
- build artifacts,
- = storage_counter(@project.statistics.lfs_objects_size)
- LFS
- )
+ %span.light= _('Storage:')
+ %strong= storage_counter(@project.statistics&.storage_size)
+ - if @project.statistics
+ = surround '(', ')' do
+ = storage_counters_details(@project.statistics)
%li
%span.light last commit:
@@ -105,6 +100,8 @@
%span.light archived:
%strong project is read-only
+ = render_if_exists "shared_runner_status", project: @project
+
%li
%span.light access:
%strong
@@ -120,7 +117,8 @@
.card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row
- = f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-3'
+ .col-sm-3.col-form-label
+ = f.label :new_namespace_id, "Namespace"
.col-sm-9
.dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 4641986cb56..423472324fe 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -49,8 +49,8 @@
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- - runner.tag_list.sort.each do |tag|
- %span.badge.badge-primary
+ - runner.tags.map(&:name).sort.each do |tag|
+ %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
= tag
.table-section.section-10
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 81380587fd2..2e23b748edb 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -92,6 +92,25 @@
= button_tag class: %w[btn btn-link] do
= runner_type.titleize
+ #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
+ %li.filter-dropdown-item{ data: { value: runner_type } }
+ = button_tag class: %w[btn btn-link] do
+ = runner_type.titleize
+
+ #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
+ %button.btn.btn-link
+ = _('No Tag')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value
+ %span.dropdown-light-content
+ {{name}}
+
= button_tag class: %w[clear-search hidden] do
= icon('times')
.filter-dropdown-container
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 12e24ddef02..77729636f9d 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,18 +1,20 @@
%fieldset
%legend Access
.form-group.row
- .col-sm-2.text-right
- = f.label :projects_limit, class: 'col-form-label'
- .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :projects_limit
+ .col-sm-10
+ = f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :can_create_group, class: 'col-form-label'
- .col-sm-10= f.check_box :can_create_group
+ .col-sm-2.col-form-label
+ = f.label :can_create_group
+ .col-sm-10
+ = f.check_box :can_create_group
.form-group.row
- .col-sm-2.text-right
- = f.label :access_level, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :access_level
.col-sm-10
- editing_current_user = (current_user == @user)
@@ -22,6 +24,8 @@
%p.light
Regular users have access to their groups and projects
+ = render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user
+
= f.radio_button :access_level, :admin, disabled: editing_current_user
= label_tag :admin, class: 'font-weight-bold' do
Admin
@@ -32,8 +36,8 @@
You cannot remove your own admin rights.
.form-group.row
- .col-sm-2.text-right
- = f.label :external, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :external
.hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 296ef073144..3281718071c 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -5,20 +5,20 @@
%fieldset
%legend Account
.form-group.row
- .col-sm-2.text-right
- = f.label :name, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :name
.col-sm-10
= f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group.row
- .col-sm-2.text-right
- = f.label :username, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :username
.col-sm-10
= f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group.row
- .col-sm-2.text-right
- = f.label :email, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :email
.col-sm-10
= f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
@@ -27,8 +27,8 @@
%fieldset
%legend Password
.form-group.row
- .col-sm-2.text-right
- = f.label :password, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :password
.col-sm-10
%strong
Reset link will be generated and sent to the user.
@@ -38,40 +38,52 @@
%fieldset
%legend Password
.form-group.row
- .col-sm-2.text-right
- = f.label :password, class: 'col-form-label'
- .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password
+ .col-sm-10
+ = f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :password_confirmation, class: 'col-form-label'
- .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password_confirmation
+ .col-sm-10
+ = f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
= render partial: 'access_levels', locals: { f: f }
+ = render_if_exists 'admin/users/namespace_plan_fieldset', f: f
+
+ = render_if_exists 'admin/users/limits', f: f
+
%fieldset
%legend Profile
.form-group.row
- .col-sm-2.text-right
- = f.label :avatar, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :avatar
.col-sm-10
= f.file_field :avatar
.form-group.row
- .col-sm-2.text-right
- = f.label :skype, class: 'col-form-label'
- .col-sm-10= f.text_field :skype, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :skype
+ .col-sm-10
+ = f.text_field :skype, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :linkedin, class: 'col-form-label'
- .col-sm-10= f.text_field :linkedin, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :linkedin
+ .col-sm-10
+ = f.text_field :linkedin, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :twitter, class: 'col-form-label'
- .col-sm-10= f.text_field :twitter, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :twitter
+ .col-sm-10
+ = f.text_field :twitter, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :website_url, 'Website', class: 'col-form-label'
- .col-sm-10= f.text_field :website_url, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :website_url
+ .col-sm-10
+ = f.text_field :website_url, class: 'form-control'
+
+ = render_if_exists 'admin/users/admin_notes', f: f
.form-actions
- if @user.new_record?
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index a733f420d11..e7dde7985fd 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,6 +6,7 @@
%span.cred (Internal)
- if @user.admin
%span.cred (Admin)
+ = render_if_exists 'admin/users/audtior_user_badge'
.float-right
- if impersonation_enabled? && @user != current_user && @user.can?(:log_in)
diff --git a/app/views/admin/users/_user_detail.html.haml b/app/views/admin/users/_user_detail.html.haml
index 3319b4bad3a..13d10dcd625 100644
--- a/app/views/admin/users/_user_detail.html.haml
+++ b/app/views/admin/users/_user_detail.html.haml
@@ -6,7 +6,7 @@
= image_tag avatar_icon_for_user(user), class: 'avatar s16 d-xs-flex d-md-none mr-1 prepend-top-2', alt: _('Avatar for %{name}') % { name: sanitize_name(user.name) }
= link_to user.name, admin_user_path(user), class: 'text-plain js-user-link', data: { user_id: user.id }
- = render_if_exists 'admin/users/user_detail_note', user: user
+ = render_if_exists 'admin/users/user_listing_note', user: user
- user_badges_in_admin_section(user).each do |badge|
- css_badge = "badge badge-#{badge[:variant]}" if badge[:variant].present?
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a74e052707f..dcd6f7c8078 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -53,6 +53,8 @@
- else
Disabled
+ = render_if_exists 'admin/namespace_plan_info', namespace: @user.namespace
+
%li
%span.light External User:
%strong
@@ -117,6 +119,13 @@
%strong
= @user.sign_in_count
+ %li
+ %span.light= _("Highest role:")
+ %strong
+ = Gitlab::Access.human_access_with_none(@user.highest_role)
+
+ = render_if_exists 'admin/users/using_license_seat', user: @user
+
- if @user.ldap_user?
%li
%span.light LDAP uid:
@@ -129,6 +138,8 @@
%strong
= link_to @user.created_by.name, [:admin, @user.created_by]
+ = render_if_exists partial: "namespaces/shared_runner_status", locals: { namespace: @user.namespace }
+
.col-md-6
- unless @user == current_user
- unless @user.confirmed?
@@ -141,6 +152,9 @@
%p This user has an unconfirmed email address#{email}. You may force a confirmation.
%br
= link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
+
+ = render_if_exists 'admin/users/user_detail_note'
+
- if @user.blocked?
.card.border-info
.card-header.bg-info.text-white
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 8d9c083d223..60ca7e4e267 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -13,7 +13,7 @@
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
data: { title: _('Add reaction') } }
- %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
- %span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile')
+ %span{ class: "award-control-icon award-control-icon-neutral" }= sprite_icon('slight-smile')
+ %span{ class: "award-control-icon award-control-icon-positive" }= sprite_icon('smiley')
+ %span{ class: "award-control-icon award-control-icon-super-positive" }= sprite_icon('smile')
= icon('spinner spin', class: "award-control-icon award-control-icon-loading")
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index c787d7420b7..369b0f7e62c 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -6,14 +6,14 @@
- tooltip = "#{subject.name} - #{status.status_tooltip}"
- if status.has_details?
- = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
+ = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item d-flex', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
%span{ class: klass }= sprite_icon(status.icon)
- %span.ci-build-text= subject.name
+ %span.ci-build-text.text-truncate.mw-70p.gl-pl-1= subject.name
- else
- .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
+ .menu-item.mini-pipeline-graph-dropdown-item.d-flex{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
%span{ class: klass }= sprite_icon(status.icon)
- %span.ci-build-text= subject.name
+ %span.ci-build-text.text-truncate.mw-70p.gl-pl-1= subject.name
- if status.has_action?
= link_to status.action_path, class: "ci-action-icon-container ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
diff --git a/app/views/ci/status/_icon.html.haml b/app/views/ci/status/_icon.html.haml
new file mode 100644
index 00000000000..f38bdb2e5ed
--- /dev/null
+++ b/app/views/ci/status/_icon.html.haml
@@ -0,0 +1,16 @@
+- status = local_assigns.fetch(:status)
+- size = local_assigns.fetch(:size, 16)
+- type = local_assigns.fetch(:type, 'pipeline')
+- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left")
+- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil)
+- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip"
+- title = s_("PipelineStatusTooltip|Pipeline: %{ci_status}") % {ci_status: status.label}
+- if type == 'commit'
+ - title = s_("PipelineStatusTooltip|Commit: %{ci_status}") % {ci_status: status.label}
+
+- if path
+ = link_to path, class: css_classes, title: title, data: { placement: tooltip_placement } do
+ = sprite_icon(status.icon, size: size)
+- else
+ %span{ class: css_classes, title: title, data: { placement: tooltip_placement } }
+ = sprite_icon(status.icon, size: size)
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index 90c59bec975..0b5c1a806b2 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -1,3 +1,3 @@
-= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want.')
+= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
= _('You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>.').html_safe
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'variables')
diff --git a/app/views/ci/variables/_header.html.haml b/app/views/ci/variables/_header.html.haml
index cb7779e2175..dbfa0a9e5a1 100644
--- a/app/views/ci/variables/_header.html.haml
+++ b/app/views/ci/variables/_header.html.haml
@@ -1,7 +1,7 @@
- expanded = local_assigns.fetch(:expanded)
%h4
- = _('Environment variables')
+ = _('Variables')
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index dc9ccb6cc39..94102b4dcd0 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -6,10 +6,11 @@
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.row
- .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } }
+ .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint, maskable_regex: ci_variable_maskable_regex } }
.hide.alert.alert-danger.js-ci-variable-error-box
%ul.ci-variable-list
+ = render 'ci/variables/variable_header'
- @variables.each.each do |variable|
= render 'ci/variables/variable_row', form_field: 'variables', variable: variable
= render 'ci/variables/variable_row', form_field: 'variables'
diff --git a/app/views/ci/variables/_variable_header.html.haml b/app/views/ci/variables/_variable_header.html.haml
new file mode 100644
index 00000000000..d3b7a5ae883
--- /dev/null
+++ b/app/views/ci/variables/_variable_header.html.haml
@@ -0,0 +1,16 @@
+- only_key_value = local_assigns.fetch(:only_key_value, false)
+
+%li.ci-variable-row.m-0.d-none.d-sm-block
+ .d-flex.w-100.align-items-center.pb-2
+ .bold.table-section.section-15.append-right-10
+ = s_('CiVariables|Type')
+ .bold.table-section.section-15.append-right-10
+ = s_('CiVariables|Key')
+ .bold.table-section.section-15.append-right-10
+ = s_('CiVariables|Value')
+ - unless only_key_value
+ .bold.table-section.section-20
+ = s_('CiVariables|State')
+ .bold.table-section.section-20
+ = s_('CiVariables|Masked')
+ = render_if_exists 'ci/variables/environment_scope_header'
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 16a7527c8ce..ed4bd5ae19e 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -3,35 +3,45 @@
- only_key_value = local_assigns.fetch(:only_key_value, false)
- id = variable&.id
+- variable_type = variable&.variable_type
- key = variable&.key
- value = variable&.value
- is_protected_default = ci_variable_protected_by_default?
- is_protected = ci_variable_protected?(variable, only_key_value)
+- is_masked_default = false
+- is_masked = ci_variable_masked?(variable, only_key_value)
- id_input_name = "#{form_field}[variables_attributes][][id]"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
+- variable_type_input_name = "#{form_field}[variables_attributes][][variable_type]"
- key_input_name = "#{form_field}[variables_attributes][][key]"
- value_input_name = "#{form_field}[variables_attributes][][secret_value]"
- protected_input_name = "#{form_field}[variables_attributes][][protected]"
+- masked_input_name = "#{form_field}[variables_attributes][][masked]"
%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
- .ci-variable-row-body
+ .ci-variable-row-body.border-bottom
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
- %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text",
+ %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
+ = options_for_select(ci_variable_type_options, variable_type)
+ %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
- .ci-variable-body-item
+ .ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
.form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) }
- = '*' * 20
+ = '*' * 17
%textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id),
rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
+ %p.masking-validation-error.gl-field-error.hide
+ = s_("CiVariables|Cannot use Masked Variable with current value")
+ = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer'
- unless only_key_value
- .ci-variable-body-item.ci-variable-protected-item
+ .ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0
.append-right-default
= s_("CiVariable|Protected")
%button{ type: 'button',
@@ -45,6 +55,20 @@
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
+ .ci-variable-body-item.ci-variable-masked-item.table-section.section-20.mr-0.border-top-0
+ .append-right-default
+ = s_("CiVariable|Masked")
+ %button{ type: 'button',
+ class: "js-project-feature-toggle project-feature-toggle qa-variable-masked #{'is-checked' if is_masked}",
+ "aria-label": s_("CiVariable|Toggle masked") }
+ %input{ type: "hidden",
+ class: 'js-ci-variable-input-masked js-project-feature-toggle-input',
+ name: masked_input_name,
+ value: is_masked,
+ data: { default: is_masked_default.to_s } }
+ %span.toggle-icon
+ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
+ = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
= render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable
- %button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
- = icon('minus-circle')
+ %button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
+ = icon('minus-circle')
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 7037c80aa6b..8005dcbf65f 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :admin_cluster, @cluster)
- - if @cluster.managed?
+ - unless @cluster.provided_by_user?
.append-bottom-20
%label.append-bottom-10
= s_('ClusterIntegration|Google Kubernetes Engine')
diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 160c5f009a7..a5de67be96b 100644
--- a/app/views/clusters/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
@@ -5,5 +5,17 @@
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
+.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml
index 9fb91a39387..455322b2089 100644
--- a/app/views/clusters/clusters/_form.html.haml
+++ b/app/views/clusters/clusters/_form.html.haml
@@ -33,11 +33,11 @@
- auto_devops_url = help_page_path('topics/autodevops/index')
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
= s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
- - if @cluster.application_ingress_external_ip.present?
+ %span{ :class => ["js-ingress-domain-help-text", ("hide" unless @cluster.application_ingress_external_ip.present?)] }
= s_('ClusterIntegration|Alternatively')
- %code #{@cluster.application_ingress_external_ip}.nip.io
+ %code{ :class => "js-ingress-domain-snippet" } #{@cluster.application_ingress_external_ip}.nip.io
= s_('ClusterIntegration| can be used instead of a custom domain.')
- - custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-cluster-ip')
+ - custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-external-endpoint')
- custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
= s_('ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}.').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe }
diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 6e4415c21a9..60ccad5b943 100644
--- a/app/views/clusters/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
@@ -4,3 +4,5 @@
= clusterable.sidebar_text
%p
= clusterable.learn_more_link
+
+= render_if_exists 'clusters/multiple_clusters_message'
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 8ed4666e79a..70e2eaeaf3b 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -7,23 +7,27 @@
- help_link_end = ' %{external_link_icon}</a>'.html_safe % { external_link_icon: external_link_icon }
%p
- - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration.').html_safe % { link_to_help_page: link_to_help_page}
+ - link_to_help_page = link_to(s_('ClusterIntegration|help page'),
+ help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration.').html_safe % { link_to_help_page: link_to_help_page }
%p= link_to('Select a different Google account', @authorize_url)
-= form_for @gcp_cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: clusterable.create_gcp_clusters_path, as: :cluster do |field|
- = form_errors(@gcp_cluster)
- .form-group
- = field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
- .form-group
- = field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
- = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?, placeholder: s_('ClusterIntegration|Environment scope')
+= bootstrap_form_for @gcp_cluster, html: { class: 'gl-show-field-errors js-gke-cluster-creation prepend-top-20',
+ data: { token: token_in_session } }, url: clusterable.create_gcp_clusters_path, as: :cluster do |field|
+ = field.text_field :name, required: true, title: s_('ClusterIntegration|Cluster name is required.'),
+ label: s_('ClusterIntegration|Kubernetes cluster name'), label_class: 'label-bold'
+ - if has_multiple_clusters?
+ = field.form_group :environment_scope, label: { text: s_('ClusterIntegration|Environment scope'),
+ class: 'label-bold' } do
+ = field.text_field :environment_scope, required: true, class: 'form-control',
+ title: 'Environment scope is required.', wrapper: false
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
= field.fields_for :provider_gcp, @gcp_cluster.provider_gcp do |provider_gcp_field|
.form-group
- = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project'), class: 'label-bold'
+ = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project'),
+ class: 'label-bold'
.js-gcp-project-id-dropdown-entry-point{ data: { docsUrl: 'https://console.cloud.google.com/home/dashboard' } }
= provider_gcp_field.hidden_field :gcp_project_id
.dropdown
@@ -45,9 +49,9 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: zones_link_url }, help_link_end: help_link_end }
- .form-group
- = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes'), class: 'label-bold'
- = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3'
+ = provider_gcp_field.number_field :num_nodes, required: true, placeholder: '3',
+ title: s_('ClusterIntegration|Number of nodes must be a numerical value.'),
+ label: s_('ClusterIntegration|Number of nodes'), label_class: 'label-bold'
.form-group
= provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type'), class: 'label-bold'
@@ -62,13 +66,21 @@
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
.form-group
- .form-check
- = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
- = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
+ = provider_gcp_field.check_box :legacy_abac, { label: s_('ClusterIntegration|RBAC-enabled cluster'),
+ label_class: 'label-bold' }, false, true
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md',
+ anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
- .form-group
- = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
+ .form-group
+ = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ label_class: 'label-bold' }
+ .form-text.text-muted
+ = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+
+ .form-group
+ = field.submit s_('ClusterIntegration|Create Kubernetes cluster'),
+ class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 1ef76ef801e..4dfbb310142 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -4,7 +4,7 @@
- page_title _('Kubernetes Cluster')
- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
@@ -15,15 +15,17 @@
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
+ update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
toggle_status: @cluster.enabled? ? 'true': 'false',
- has_rbac: @cluster.platform_kubernetes_rbac? ? 'true': 'false',
+ has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
cluster_type: @cluster.cluster_type,
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
- ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
- ingress_dns_help_path: help_page_path('topics/autodevops/quick_start_guide.md', anchor: 'point-dns-at-cluster-ip'),
- manage_prometheus_path: manage_prometheus_path } }
+ ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
+ ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
+ manage_prometheus_path: manage_prometheus_path,
+ cluster_id: @cluster.id } }
.js-cluster-application-notice
.flash-container
@@ -33,6 +35,8 @@
= render 'banner'
= render 'form'
+ = render_if_exists 'projects/clusters/prometheus_graphs'
+
.cluster-applications-table#js-cluster-applications
%section.settings#js-cluster-details{ class: ('expanded' if expanded) }
@@ -50,5 +54,5 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
- .settings-content
+ .settings-content#advanced-settings-section
= render 'advanced_settings'
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 9793c77fc2b..f2fc5ac93fb 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,39 +1,55 @@
-= form_for @user_cluster, url: clusterable.create_user_clusters_path, as: :cluster do |field|
- = form_errors(@user_cluster)
- .form-group
- = field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
+- more_info_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
+ anchor: 'adding-an-existing-kubernetes-cluster'), target: '_blank'
+- rbac_help_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
+ anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
+
+- api_url_help_text = s_('ClusterIntegration|The URL used to access the Kubernetes API.')
+- ca_cert_help_text = s_('ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster.')
+- token_help_text = s_('ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges.').html_safe % { code: '<code>'.html_safe, end_code: '</code>'.html_safe }
+- rbac_help_text = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + ' '
+- rbac_help_text << s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+
+= bootstrap_form_for @user_cluster, html: { class: 'gl-show-field-errors' },
+ url: clusterable.create_user_clusters_path, as: :cluster do |field|
+ = field.text_field :name, required: true, title: s_('ClusterIntegration|Cluster name is required.'),
+ label: s_('ClusterIntegration|Kubernetes cluster name'), label_class: 'label-bold'
- if has_multiple_clusters?
- .form-group
- = field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
- = field.text_field :environment_scope, class: 'form-control', placeholder: s_('ClusterIntegration|Environment scope')
+ = field.text_field :environment_scope, required: true, title: 'Environment scope is required.',
+ label: s_('ClusterIntegration|Environment scope'), label_class: 'label-bold',
+ help: s_("ClusterIntegration|Choose which of your environments will use this cluster.")
= field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
- .form-group
- = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL'), class: 'label-bold'
- = platform_kubernetes_field.text_field :api_url, class: 'form-control', placeholder: s_('ClusterIntegration|API URL')
+ = platform_kubernetes_field.url_field :api_url, required: true,
+ title: s_('ClusterIntegration|API URL should be a valid http/https url.'),
+ label: s_('ClusterIntegration|API URL'), label_class: 'label-bold',
+ help: '%{help_text} %{help_link}'.html_safe % { help_text: api_url_help_text, help_link: more_info_link }
- .form-group
- = platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate'), class: 'label-bold'
- = platform_kubernetes_field.text_area :ca_cert, class: 'form-control', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)')
+ = platform_kubernetes_field.text_area :ca_cert,
+ placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'),
+ label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
+ help: '%{help_text} %{help_link}'.html_safe % { help_text: ca_cert_help_text, help_link: more_info_link }
- .form-group
- = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-bold'
- = platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off'
+ = platform_kubernetes_field.text_field :token, required: true,
+ title: s_('ClusterIntegration|Service token is required.'), label: s_('ClusterIntegration|Service Token'),
+ autocomplete: 'off', label_class: 'label-bold',
+ help: '%{help_text} %{help_link}'.html_safe % { help_text: token_help_text, help_link: more_info_link }
- if @user_cluster.allow_user_defined_namespace?
- .form-group
- = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
- = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
-
- .form-group
- .form-check
- = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac'
- = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
-
- .form-group
- = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
+ = platform_kubernetes_field.text_field :namespace,
+ label: s_('ClusterIntegration|Project namespace (optional, unique)'), label_class: 'label-bold'
+
+ = platform_kubernetes_field.form_group :authorization_type,
+ { help: '%{help_text} %{help_link}'.html_safe % { help_text: rbac_help_text, help_link: rbac_help_link } } do
+ = platform_kubernetes_field.check_box :authorization_type,
+ { class: 'qa-rbac-checkbox', label: s_('ClusterIntegration|RBAC-enabled cluster'),
+ label_class: 'label-bold', inline: true }, 'rbac', 'abac'
+
+ .form-group
+ = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ label_class: 'label-bold' }
+ .form-text.text-muted
+ = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+
+ .form-group
+ = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index 4a452b83112..c1727cf9079 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -1,58 +1,58 @@
-= form_for cluster, url: update_cluster_url_path, as: :cluster do |field|
- = form_errors(cluster)
-
- .form-group
- - if cluster.managed?
- %label.append-bottom-10{ for: 'cluster-name' }
- = s_('ClusterIntegration|Kubernetes cluster name')
- .input-group
- %input.form-control.cluster-name.js-select-on-focus{ value: cluster.name, readonly: true }
- %span.input-group-append
- = clipboard_button(text: cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'input-group-text btn-default')
- - else
- = field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
- .input-group
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
+= bootstrap_form_for cluster, url: update_cluster_url_path, html: { class: 'gl-show-field-errors' },
+ as: :cluster do |field|
+ - copy_name_btn = clipboard_button(text: cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'),
+ class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
+ = field.text_field :name, class: 'js-select-on-focus cluster-name', required: true,
+ title: s_('ClusterIntegration|Cluster name is required.'),
+ readonly: cluster.read_only_kubernetes_platform_fields?,
+ label: s_('ClusterIntegration|Kubernetes cluster name'), label_class: 'label-bold',
+ input_group_class: 'gl-field-error-anchor', append: copy_name_btn
= field.fields_for :platform_kubernetes, platform do |platform_field|
- .form-group
- = platform_field.label :api_url, s_('ClusterIntegration|API URL')
- .input-group
- = platform_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: cluster.managed?
- - if cluster.managed?
- %span.input-group-append
- = clipboard_button(text: platform.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'input-group-text btn-default')
+ - copy_api_url = clipboard_button(text: platform.api_url, title: s_('ClusterIntegration|Copy API URL'),
+ class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
+ = platform_field.text_field :api_url, class: 'js-select-on-focus', required: true,
+ title: s_('ClusterIntegration|API URL should be a valid http/https url.'),
+ readonly: cluster.read_only_kubernetes_platform_fields?,
+ label: s_('ClusterIntegration|API URL'), label_class: 'label-bold',
+ input_group_class: 'gl-field-error-anchor', append: copy_api_url
- .form-group
- = platform_field.label :ca_cert, s_('ClusterIntegration|CA Certificate')
- .input-group
- = platform_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: cluster.managed?
- - if cluster.managed?
- %span.input-group-append.clipboard-addon
- = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'input-group-text btn-blank')
+ - copy_ca_cert_btn = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'),
+ class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
+ = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '5',
+ readonly: cluster.read_only_kubernetes_platform_fields?,
+ placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'),
+ label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
+ input_group_class: 'gl-field-error-anchor', append: copy_ca_cert_btn
- .form-group
- = platform_field.label :token, s_('ClusterIntegration|Token')
- .input-group
- = platform_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: cluster.managed?
- %span.input-group-append
- %button.btn.btn-default.input-group-text.js-show-cluster-token{ type: 'button' }
- = s_('ClusterIntegration|Show')
- - if cluster.managed?
- = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default')
+ - show_token_btn = (platform_field.button s_('ClusterIntegration|Show'),
+ type: 'button', class: 'js-show-cluster-token btn btn-default')
+ - copy_token_btn = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Service Token'),
+ class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
+
+ = platform_field.text_field :token, type: 'password', class: 'js-select-on-focus js-cluster-token',
+ required: true, title: s_('ClusterIntegration|Service token is required.'),
+ readonly: cluster.read_only_kubernetes_platform_fields?,
+ label: s_('ClusterIntegration|Service Token'), label_class: 'label-bold',
+ input_group_class: 'gl-field-error-anchor', append: show_token_btn + copy_token_btn
- if cluster.allow_user_defined_namespace?
- .form-group
- = platform_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
- = platform_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ = platform_field.text_field :namespace, label: s_('ClusterIntegration|Project namespace (optional, unique)'),
+ label_class: 'label-bold'
+
+ = platform_field.form_group :authorization_type do
+ = platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),
+ label_class: 'label-bold', inline: true }, 'rbac', 'abac'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
.form-group
- .form-check
- = platform_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
- = platform_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = field.check_box :managed, { disabled: true, label: s_('ClusterIntegration|GitLab-managed cluster'),
+ label_class: 'label-bold' }
+ .form-text.text-muted
+ = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index ec1a3fef435..3f39555a1d4 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -1,4 +1,4 @@
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Activity')
.top-area
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 8ab5dc37f34..b2fadb77418 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,4 +1,4 @@
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Groups')
- if current_user.can_create_group?
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index ae67192cbcd..97a446dbeec 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,29 +1,35 @@
+- project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
+- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
+
= content_for :flash_message do
= render 'shared/project_limit'
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Projects')
- if current_user.can_create_project?
.page-title-controls
- = link_to "New project", new_project_path, class: "btn btn-success"
+ = link_to _("New project"), new_project_path, class: "btn btn-success"
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
- %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs
+ %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs{ class: ('border-0' if feature_project_list_filter_bar) }
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
- Your projects
+ = _("Your projects")
%span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
- Starred projects
+ = _("Starred projects")
%span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
- Explore projects
-
- .nav-controls
- = render 'shared/projects/search_form'
- = render 'shared/projects/dropdown'
+ = _("Explore projects")
+ - unless feature_project_list_filter_bar
+ .nav-controls
+ = render 'shared/projects/search_form'
+ = render 'shared/projects/dropdown'
+- if feature_project_list_filter_bar
+ .project-filters
+ = render 'shared/projects/search_bar', project_tab_filter: project_tab_filter
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index a05d0190efb..34aca40d0d1 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,4 +1,4 @@
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Snippets')
- if current_user && current_user.snippets.any? || @snippets.any?
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 4dbda5c754b..b1c192d7bad 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -5,7 +5,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml
index db856ef7d7b..2f9dbf87d95 100644
--- a/app/views/dashboard/groups/_groups.html.haml
+++ b/app/views/dashboard/groups/_groups.html.haml
@@ -1,4 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 19b06ba5cdd..d1d8d970b59 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
= render 'dashboard/groups_head'
- if params[:filter].blank? && @groups.empty?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index afd46412fab..b3ee5034204 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,9 +4,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Issues')
- if current_user
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 3e5f13b92e3..3956f03a3c8 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,9 +2,9 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Merge Requests')
- if current_user
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 13822d36f15..37ba2143eba 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -2,7 +2,7 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Milestones')
- if current_user
diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml
index da3cf5807b0..f9b61bf1f3e 100644
--- a/app/views/dashboard/projects/_nav.html.haml
+++ b/app/views/dashboard/projects/_nav.html.haml
@@ -1,6 +1,21 @@
-.nav-block
- %ul.nav-links.mobile-separator.nav.nav-tabs
- = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
- = link_to s_('DashboardProjects|All'), dashboard_projects_path
- = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
- = link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true)
+- inactive_class = 'btn p-2'
+- active_class = 'btn p-2 active'
+- project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
+- is_explore_trending = project_tab_filter == :explore_trending
+- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
+
+.nav-block{ class: ("w-100" if feature_project_list_filter_bar) }
+ - if feature_project_list_filter_bar
+ .btn-group.button-filter-group.d-flex.m-0.p-0
+ - if project_tab_filter == :explore || is_explore_trending
+ = link_to s_('DashboardProjects|Trending'), trending_explore_projects_path, class: is_explore_trending ? active_class : inactive_class
+ = link_to s_('DashboardProjects|All'), explore_projects_path, class: is_explore_trending ? inactive_class : active_class
+ - else
+ = link_to s_('DashboardProjects|All'), dashboard_projects_path, class: params[:personal].present? ? inactive_class : active_class
+ = link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true), class: params[:personal].present? ? active_class : inactive_class
+ - else
+ %ul.nav-links.mobile-separator.nav.nav-tabs
+ = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
+ = link_to s_('DashboardProjects|All'), dashboard_projects_path
+ = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
+ = link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true)
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index 18a82feb189..8933c5d7227 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -1,4 +1,4 @@
-.blank-state-parent-container
+.blank-state-parent-container{ class: ('has-start-trial-container' if has_start_trial?) }
.section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" }
.container.section-body
.row
@@ -7,7 +7,12 @@
Welcome to GitLab
%p.blank-state-text
Code, test, and deploy together
- - if current_user.admin?
- = render "blank_state_admin_welcome"
- - else
- = render "blank_state_welcome"
+ .blank-state-row
+ %div{ class: ('column-large' if has_start_trial?) }
+ - if current_user.admin?
+ = render "blank_state_admin_welcome"
+ - else
+ = render "blank_state_welcome"
+ - if has_start_trial?
+ .column-small
+ = render_if_exists "blank_state_ee_trial"
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 446b4715b2d..0298f539b4b 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,7 +4,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
@@ -13,7 +13,7 @@
= render "projects/last_push"
- if show_projects?(@projects, params)
= render 'dashboard/projects_head'
- = render 'nav'
+ = render 'nav' unless Feature.enabled?(:project_list_filter_bar)
= 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 3a45f6df017..0fcc6894b68 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,11 +4,11 @@
- page_title _("Starred Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
%div{ class: container_class }
= render "projects/last_push"
- = render 'dashboard/projects_head'
+ = render 'dashboard/projects_head', project_tab_filter: :starred
- if params[:filter_projects] || any_projects?(@projects)
= render 'projects'
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index efe1fb99efc..db6e40a6fd0 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -34,7 +34,7 @@
= todo_due_date(todo)
.todo-body
- .todo-note
+ .todo-note.break-word
.md
= first_line_in_markdown(todo, :body, 150, project: todo.project)
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 47729321961..8212fb8bb33 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,9 +2,9 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('Todos')
- if current_user.todos.any?
@@ -34,7 +34,7 @@
= icon('spinner spin')
.todos-filters
- .row-content-block.second-block
+ .issues-details-filters.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form d-sm-flex' do
.filter-categories.flex-fill
.filter-item.inline
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 73e70dc63e5..f8aa3cf98dc 100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -3,7 +3,7 @@
.login-body
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
- = devise_error_messages!
+ = render "devise/shared/error_messages", resource: resource
.form-group
= f.label :email
= f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.'
diff --git a/app/views/devise/mailer/email_changed.html.haml b/app/views/devise/mailer/email_changed.html.haml
index 5398430fdfd..3689e9c5f61 100644
--- a/app/views/devise/mailer/email_changed.html.haml
+++ b/app/views/devise/mailer/email_changed.html.haml
@@ -2,7 +2,7 @@
- if @resource.try(:unconfirmed_email?)
%p
- We're contacting you to notify you that your email is being changed to #{@resource.reload.unconfirmed_email}.
+ We're contacting you to notify you that your email is being changed to #{@resource.reset.unconfirmed_email}.
- else
%p
We're contacting you to notify you that your email has been changed to #{@resource.email}.
diff --git a/app/views/devise/mailer/email_changed.text.erb b/app/views/devise/mailer/email_changed.text.erb
index 18137389e7b..69155db7246 100644
--- a/app/views/devise/mailer/email_changed.text.erb
+++ b/app/views/devise/mailer/email_changed.text.erb
@@ -1,7 +1,7 @@
Hello, <%= @resource.name %>!
<% if @resource.try(:unconfirmed_email?) %>
-We're contacting you to notify you that your email is being changed to <%= @resource.reload.unconfirmed_email %>.
+We're contacting you to notify you that your email is being changed to <%= @resource.reset.unconfirmed_email %>.
<% else %>
We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
<% end %>
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index dd1edb5fdc9..09ea7716a47 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -3,7 +3,7 @@
.login-body
= form_for(resource, as: resource_name, url: password_path(:user), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors
- = devise_error_messages!
+ = render "devise/shared/error_messages", resource: resource
= f.hidden_field :reset_password_token
.form-group
= f.label 'New password', for: "user_password"
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 99ce13adf74..fe999851605 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -3,7 +3,7 @@
.login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
- = devise_error_messages!
+ = render "devise/shared/error_messages", resource: resource
.form-group
= f.label :email
= f.email_field :email, class: "form-control", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.'
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index f379e71ae5b..5a1388ac7a1 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -1,7 +1,7 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
- <%= devise_error_messages! %>
+ <%= render "devise/shared/error_messages", resource: resource %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index ec968e435cd..f8f36a8bfff 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -3,17 +3,21 @@
.login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
.login-body
= render 'devise/sessions/new_crowd'
+
+ = render_if_exists 'devise/sessions/new_kerberos_tab'
+
- @ldap_servers.each_with_index do |server, i|
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain)) }
.login-body
= render 'devise/sessions/new_ldap', server: server
+
+ = render_if_exists 'devise/sessions/new_smartcard'
+
- if password_authentication_enabled_for_web?
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
.login-body
= render 'devise/sessions/new_base'
- = render_if_exists 'devise/sessions/new_smartcard'
-
- elsif password_authentication_enabled_for_web?
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 9c7ca6ebbd4..5eba819172b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,27 +1,29 @@
+- max_name_length = 128
+- max_username_length = 255
#register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
- = devise_error_messages!
+ = render "devise/shared/error_messages", resource: resource
.name.form-group
- = f.label :name, 'Full name', class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji", required: true, title: _("This field is required.")
+ = f.label :name, _('Full name'), class: 'label-bold'
+ = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
- %p.validation-error.hide Username is already taken.
- %p.validation-success.hide Username is available.
- %p.validation-pending.hide Checking username availability...
+ = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ %p.validation-error.field-validation.hide= _('Username is already taken.')
+ %p.validation-success.field-validation.hide= _('Username is available.')
+ %p.validation-pending.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: "Please provide a valid email address."
+ = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: "Please retype the email address."
+ = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.")
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
- %p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters
+ = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
+ %p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
= check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
@@ -29,8 +31,9 @@
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
= accept_terms_label.html_safe
+ = render_if_exists 'devise/shared/email_opted_in', f: f
%div
- if Gitlab::Recaptcha.enabled?
= recaptcha_tags
.submit-container
- = f.submit "Register", class: "btn-register btn qa-new-user-register-button"
+ = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button"
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index aee05b6c81c..b1a9470cf1c 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -2,6 +2,7 @@
- if crowd_enabled?
%li.nav-item
= link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab'
+ = render_if_exists "devise/shared/kerberos_tab"
- @ldap_servers.each_with_index do |server, i|
%li.nav-item
= link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))} qa-ldap-tab", 'data-toggle' => 'tab'
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index b2f48a4e0bf..1167f1718d6 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -3,7 +3,7 @@
.login-body
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
- = devise_error_messages!
+ = render "devise/shared/error_messages", resource: resource
.form-group.append-bottom-20
= f.label :email
= f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.'
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
index 6b8dd156874..5a47040874f 100644
--- a/app/views/discussions/_diff_discussion.html.haml
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -4,6 +4,6 @@
-# Text diff discussions
- expanded = local_assigns.fetch(:expanded, true)
%tr.notes_holder{ class: ('hide' unless expanded) }
- %td.notes_content{ colspan: 3 }
+ %td.notes-content{ colspan: 3 }
.content{ class: ('hide' unless expanded) }
= render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true }
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 44c898e0fac..8a3c841de0b 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -11,8 +11,8 @@
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
- if diff_file.text?
- .diff-content.code.js-syntax-highlight
- %table
+ .diff-content
+ %table.code.js-syntax-highlight
- if expanded
- discussions = { discussion.original_line_code => [discussion] }
= render partial: "projects/diffs/line",
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index 30b00ca86b3..0a5541c3e82 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -19,20 +19,24 @@
.discussion-reply-holder
- if can_create_note?
+ %a.user-avatar-link.d-none.d-sm-block{ href: user_path(current_user) }
+ = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40'
- if discussion.potentially_resolvable?
- line_type = local_assigns.fetch(:line_type, nil)
- .btn-group.discussion-with-resolve-btn{ role: "group" }
- .btn-group{ role: "group" }
- = link_to_reply_discussion(discussion, line_type)
+ .discussion-with-resolve-btn
+ .btn-group.discussion-with-resolve-btn{ role: "group" }
+ .btn-group{ role: "group" }
+ = link_to_reply_discussion(discussion, line_type)
- = render "discussions/resolve_all", discussion: discussion
+ = render "discussions/resolve_all", discussion: discussion
- .btn-group.discussion-actions
- = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
- = render "discussions/jump_to_next", discussion: discussion
+ .btn-group.discussion-actions
+ = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
+ = render "discussions/jump_to_next", discussion: discussion
- else
- = link_to_reply_discussion(discussion)
+ .discussion-with-resolve-btn
+ = link_to_reply_discussion(discussion)
- elsif !current_user
.disabled-comment.text-center
Please
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
index 2e621c4082d..03b428714b9 100644
--- a/app/views/discussions/_parallel_diff_discussion.html.haml
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -1,17 +1,17 @@
- expanded = [*discussions_left, *discussions_right].any?(&:expanded?)
%tr.notes_holder{ class: ('hide' unless expanded) }
- if discussions_left
- %td.notes_content.parallel.old{ colspan: 2 }
+ %td.notes-content.parallel.old{ colspan: 2 }
.content{ class: ('hide' unless discussions_left.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true }
- else
- %td.notes_content.parallel.old{ colspan: 2 }
+ %td.notes-content.parallel.old{ colspan: 2 }
.content
- if discussions_right
- %td.notes_content.parallel.new{ colspan: 2 }
+ %td.notes-content.parallel.new{ colspan: 2 }
.content{ class: ('hide' unless discussions_right.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true }
- else
- %td.notes_content.parallel.new{ colspan: 2 }
+ %td.notes-content.parallel.new{ colspan: 2 }
.content
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 1f5c70a6c6e..5d85d9e431f 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -52,7 +52,7 @@
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
%h5
- = _("Authorized applications (%{size})") % { size: @authorized_tokens.size }
+ = _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
- if @authorized_tokens.any?
.table-responsive
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index cac00f9c854..6750732ab67 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -14,7 +14,7 @@
%td
.clipboard-group
.input-group
- %input.label.label-monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
+ %input.label.label-monospace.monospace{ id: "application_id", type: "text", autocomplete: 'off', value: @application.uid, readonly: true }
.input-group-append
= clipboard_button(target: '#application_id', title: _("Copy ID to clipboard"), class: "btn btn btn-default")
%tr
@@ -23,7 +23,7 @@
%td
.clipboard-group
.input-group
- %input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
+ %input.label.label-monospace.monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
= clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 2fcb1d1fd2b..222175c818a 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,11 +3,11 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- - if event.created_project?
+ - if event.created_project_action?
= render "events/event/created_project", event: event
- - elsif event.push?
+ - elsif event.push_action?
= render "events/event/push", event: event
- - elsif event.commented?
+ - elsif event.commented_action?
= render "events/event/note", event: event
- else
= render "events/event/common", event: event
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 96d6553a2ac..b02fdb4b638 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -11,7 +11,8 @@
= link_to [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip event-target-link append-right-4', title: event.target_title do
= event.target.reference_link_text
- unless event.milestone?
- %span.event-target-title.append-right-4= "&quot;".html_safe + event.target.title + "&quot".html_safe
+ %span.event-target-title.append-right-4{ dir: "auto" }
+ = "&quot;".html_safe + event.target.title + "&quot".html_safe
- else
%span.event-type.d-inline-block.append-right-4{ class: event.action_name }
= event_action_name(event)
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 90ed8e41d32..7e2103287f7 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -7,7 +7,8 @@
%span.event-type.d-inline-block.append-right-4{ class: event.action_name }
= event.action_name
= event_note_title_html(event)
- %span.event-target-title.append-right-4= "&quot;".html_safe + event.target.title + "&quot".html_safe
+ %span.event-target-title.append-right-4{ dir: "auto" }
+ = "&quot;".html_safe + event.target.title + "&quot".html_safe
= render "events/event_scope", event: event
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 69914fccc48..21c418cb0e4 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -32,7 +32,8 @@
- from_label = from
= link_to project_compare_path(project, from: from, to: event.commit_to) do
- Compare #{from_label}...#{truncate_sha(event.commit_to)}
+ %span Compare
+ %span.commit-sha #{from_label}...#{truncate_sha(event.commit_to)}
- if create_mr
%span
diff --git a/app/views/explore/groups/_groups.html.haml b/app/views/explore/groups/_groups.html.haml
index ff57b39e947..a3249275d5e 100644
--- a/app/views/explore/groups/_groups.html.haml
+++ b/app/views/explore/groups/_groups.html.haml
@@ -1,4 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: explore_groups_path(format: :json), path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 869be4e8581..fd86d07fc86 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,7 +2,7 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/groups_head'
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index f518205f14c..d00a3d266d8 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,8 +1,12 @@
+- has_label = local_assigns.fetch(:has_label, false)
+- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
+
- if current_user
- .dropdown
+ .dropdown.js-project-filter-dropdown-wrap{ class: ('d-flex flex-grow-1 flex-shrink-1' if feature_project_list_filter_bar) }
%button.dropdown-menu-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' }
- = icon('globe', class: 'mt-1')
- %span.light.ml-3= _("Visibility:")
+ - unless has_label
+ = icon('globe', class: 'mt-1')
+ %span.light.ml-3= _("Visibility:")
- if params[:visibility_level].present?
= visibility_level_label(params[:visibility_level].to_i)
- else
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index d18dec7bd8e..341ad681c7c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,12 +2,12 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
- = render 'dashboard/projects_head'
+ = render 'dashboard/projects_head', project_tab_filter: :explore
- else
= render 'explore/head'
-= render 'explore/projects/nav'
+= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index d18dec7bd8e..ec92852ddde 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,12 +2,12 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
- = render 'dashboard/projects_head'
+ = render 'dashboard/projects_head', project_tab_filter: :starred
- else
= render 'explore/head'
-= render 'explore/projects/nav'
+= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index d18dec7bd8e..ed508fa2506 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,12 +2,12 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
- = render 'dashboard/projects_head'
+ = render 'dashboard/projects_head', project_tab_filter: :explore_trending
- else
= render 'explore/head'
-= render 'explore/projects/nav'
+= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects
diff --git a/app/views/groups/_archived_projects.html.haml b/app/views/groups/_archived_projects.html.haml
index ed79f5790f0..48e9f630050 100644
--- a/app/views/groups/_archived_projects.html.haml
+++ b/app/views/groups/_archived_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/_create_chat_team.html.haml b/app/views/groups/_create_chat_team.html.haml
index f950968030f..561e68a9155 100644
--- a/app/views/groups/_create_chat_team.html.haml
+++ b/app/views/groups/_create_chat_team.html.haml
@@ -1,8 +1,9 @@
.form-group
- = f.label :create_chat_team, class: 'col-form-label' do
- %span.mattermost-icon
- = custom_icon('icon_mattermost')
- Mattermost
+ .col-sm-2.col-form-label
+ = f.label :create_chat_team do
+ %span.mattermost-icon
+ = custom_icon('icon_mattermost')
+ Mattermost
.col-sm-10
.form-check.js-toggle-container
.js-toggle-button.form-check-input= f.check_box(:create_chat_team, { checked: true }, true, false)
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index ff59013ed67..b8f632d11d3 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -1,5 +1,6 @@
.form-group.row
- = f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2 pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :lfs_enabled, 'Large File Storage'
.col-sm-10
.form-check
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
@@ -9,9 +10,15 @@
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
%span.descr This setting can be overridden in each project.
+.form-group.row
+ .col-sm-2.col-form-label
+ = f.label s_('ProjectCreationLevel|Allowed to create projects')
+ .col-sm-10
+ = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control'
.form-group.row
- = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :require_two_factor_authentication, 'Two-factor authentication'
.col-sm-10
.form-check
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 39c0c113793..4daf3683eaf 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -13,7 +13,7 @@
= visibility_level_icon(@group.visibility_level, fw: false, options: {class: 'icon'})
.home-panel-metadata.d-flex.align-items-center.text-secondary
%span
- = _("Group")
+ = _("Group ID: %{group_id}") % { group_id: @group.id }
- if current_user
%span.access-request-links.prepend-left-8
= render 'shared/members/access_request_links', source: @group
@@ -47,7 +47,7 @@
%strong= new_subgroup_label
%span= s_("GroupsTree|Create a subgroup in this group.")
- else
- = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success"
+ = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success prepend-top-default"
- if @group.description.present?
.group-home-desc.mt-1
diff --git a/app/views/groups/_shared_projects.html.haml b/app/views/groups/_shared_projects.html.haml
index 4eb8367f633..2769b69add3 100644
--- a/app/views/groups/_shared_projects.html.haml
+++ b/app/views/groups/_shared_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/_subgroups_and_projects.html.haml b/app/views/groups/_subgroups_and_projects.html.haml
index d53c8026df8..784f5ac233e 100644
--- a/app/views/groups/_subgroups_and_projects.html.haml
+++ b/app/views/groups/_subgroups_and_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 39d0f620283..0c8f86c2822 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,6 +1,6 @@
- breadcrumb_title "General Settings"
- @content_class = "limit-container-width" unless fluid_layout
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') }
@@ -25,6 +25,8 @@
.settings-content
= render 'groups/settings/permissions'
+= render_if_exists 'groups/insights', expanded: expanded
+
%section.settings.no-animate#js-badge-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index c8cdc2cc3e4..8b511f6866f 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -1,7 +1,7 @@
= form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f|
.row
.col-md-4.col-lg-6
- = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true)
+ = users_select_tag(:user_ids, group_member_select_options)
.form-text.text-muted.append-bottom-10
Search for members by name, username, or email, or invite new ones using their email address.
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2af3e861587..021c0b6c429 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -14,6 +14,8 @@
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
+ = render_if_exists 'groups/group_members/ldap_sync'
+
.clearfix
%h5.member.existing-title
Existing members
@@ -22,7 +24,7 @@
%span.flex-project-title
Members with access to
%strong= @group.name
- %span.badge= @members.total_count
+ %span.badge.badge-pill= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative.append-right-8
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 5cf3193bc62..a8358704b03 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
-- page_title "Labels"
+- page_title 'Labels'
- can_admin_label = can?(current_user, :admin_label, @group)
-- issuables = ['issues', 'merge requests']
- search = params[:search]
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
@@ -14,11 +13,11 @@
.labels-container.prepend-top-5
- if @labels.any?
.text-muted
- = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence }
+ = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence }
.other-labels
%h5= _('Labels')
%ul.content-list.manage-labels-list.js-other-labels
- = render partial: 'shared/label', subject: @group, collection: @labels, as: :label, locals: { use_label_priority: false }
+ = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group }
= paginate @labels, theme: 'gitlab'
- elsif search.present?
.nothing-here-block
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 51dcc9d0cda..06e05d898d6 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -3,7 +3,7 @@
- page_title _('New Group')
- header_title _("Groups"), dashboard_groups_path
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('New group')
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -27,7 +27,7 @@
.form-group.group-description-holder.col-sm-12
= f.label :avatar, _("Group avatar"), class: 'label-bold'
%div
- = render 'shared/choose_group_avatar_button', f: f
+ = render 'shared/choose_avatar_button', f: f
.form-group.col-sm-12
%label.label-bold
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 9ed71d19d32..e12748666c8 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -17,17 +17,17 @@
= f.label :description, _('Group description (optional)'), class: 'label-bold'
= f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
- = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
+ = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
- .form-group.prepend-top-default.append-bottom-20
- .avatar-container.rect-avatar.s90
- = group_icon(@group, alt: '', class: 'avatar group-avatar s90')
- = f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
- = render 'shared/choose_group_avatar_button', f: f
- - if @group.avatar?
- %hr
- = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted'
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.rect-avatar.s90
+ = group_icon(@group, alt: '', class: 'avatar group-avatar s90')
+ = f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_avatar_button', f: f
+ - if @group.avatar?
+ %hr
+ = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
- = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
= f.submit _('Save changes'), class: 'btn btn-success mt-4 js-dirty-submit'
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 6b0a6e7ed99..0a14830c666 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -18,6 +18,7 @@
%span.descr.text-muted= share_with_group_lock_help_text(@group)
= render 'groups/settings/lfs', f: f
+ = render 'groups/settings/project_creation_level', f: f, group: @group
= render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
diff --git a/app/views/groups/settings/_project_creation_level.html.haml b/app/views/groups/settings/_project_creation_level.html.haml
new file mode 100644
index 00000000000..9f711e6aade
--- /dev/null
+++ b/app/views/groups/settings/_project_creation_level.html.haml
@@ -0,0 +1,3 @@
+.form-group
+ = f.label s_('ProjectCreationLevel|Allowed to create projects'), class: 'label-bold'
+ = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, group.project_creation_level), {}, class: 'form-control'
diff --git a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
new file mode 100644
index 00000000000..e7efc0237c8
--- /dev/null
+++ b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
@@ -0,0 +1,15 @@
+= form_for group, url: update_auto_devops_group_settings_ci_cd_path(group), method: :patch do |f|
+ = form_errors(group)
+ %fieldset
+ .form-group
+ .card.auto-devops-card
+ .card-body
+ .form-check
+ = f.check_box :auto_devops_enabled, class: 'form-check-input', checked: group.auto_devops_enabled?
+ = f.label :auto_devops_enabled, class: 'form-check-label' do
+ %strong= s_('GroupSettings|Default to Auto DevOps pipeline for all projects within this group')
+ %span.badge.badge-info#auto-devops-badge= badge_for_auto_devops_scope(group)
+ .form-text.text-muted
+ = s_('GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
+ = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-15'
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index d9332e36ef5..d21496ee0aa 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -1,7 +1,7 @@
- breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header
@@ -19,3 +19,17 @@
= _('Register and see your runners for this group.')
.settings-content
= render 'groups/runners/index'
+
+%section.settings#auto-devops-settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Auto DevOps')
+ %button.btn.btn-default.js-settings-toggle{ type: "button" }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ - auto_devops_url = help_page_path('topics/autodevops/index')
+ - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
+ = s_('GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
+
+ .settings-content
+ = render 'groups/settings/ci_cd/auto_devops_form', group: @group
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 77fe88dacb7..255a9ad038c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -9,7 +9,7 @@
= render 'groups/home_panel'
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
- .top-area.group-nav-container
+ .top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 28ffb2dd63c..efb3815b257 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -356,6 +356,18 @@
%td.shortcut
%kbd l
%td Change Label
+ %tr
+ %td.shortcut
+ %kbd ]
+ \/
+ %kbd j
+ %td Move to next file
+ %tr
+ %td.shortcut
+ %kbd [
+ \/
+ %kbd k
+ %td Move to previous file
%tbody.hidden-shortcut{ style: 'display:none' }
%tr
%th
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index dfa5d820ce9..50933c7d434 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,11 +1,11 @@
%div
- if Gitlab::CurrentSettings.help_page_text.present?
- = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
+ .prepend-top-default.md
+ = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
%h1
- GitLab
- Community Edition
+ = default_brand_title
- if user_signed_in?
%span= link_to_version
= version_status_badge
@@ -24,12 +24,13 @@
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
%br
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}.
- %p= link_to 'Check the current instance configuration ', help_instance_configuration_url
- %hr
+
+%p= link_to 'Check the current instance configuration ', help_instance_configuration_url
+%hr
.row.prepend-top-default
.col-md-8
- .documentation-index.wiki
+ .documentation-index.md
= markdown(@help_index)
.col-md-4
.card
diff --git a/app/views/help/instance_configuration.html.haml b/app/views/help/instance_configuration.html.haml
index f09e3825a4b..99576d45f76 100644
--- a/app/views/help/instance_configuration.html.haml
+++ b/app/views/help/instance_configuration.html.haml
@@ -1,5 +1,5 @@
- page_title 'Instance Configuration'
-.wiki.documentation
+.documentation.md
%h1 Instance Configuration
%p
diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml
index c07c148a12a..dce27dee9be 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,3 +1,3 @@
- page_title @path.split("/").reverse.map(&:humanize)
-.documentation.wiki.prepend-top-default
+.documentation.md.prepend-top-default
= markdown @markdown
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 506f580b246..cdc894ee5a0 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -70,7 +70,7 @@
.cover-title
John Smith
- .cover-desc
+ .cover-desc.cgray
= lorem
.cover-controls
@@ -513,7 +513,7 @@
%h2#markdown Markdown
%h4
- %code .md or .wiki and others
+ %code .md
Markdown rendering has a bit different css and presented in next UI elements:
diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml
index 9280f12e187..40609fddbde 100644
--- a/app/views/import/bitbucket_server/status.html.haml
+++ b/app/views/import/bitbucket_server/status.html.haml
@@ -29,7 +29,7 @@
%tr
%th= _('From Bitbucket Server')
%th= _('To GitLab')
- %th= _(' Status')
+ %th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml
index a88b04eccbb..c4670869c93 100644
--- a/app/views/import/gitea/new.html.haml
+++ b/app/views/import/gitea/new.html.haml
@@ -2,18 +2,18 @@
- header_title _("Projects"), root_path
%h3.page-title
- = custom_icon('go_logo')
+ = custom_icon('gitea_logo')
= _('Import Projects from Gitea')
%p
- - link_to_personal_token = link_to(_('Personal Access Token'), 'https://github.com/gogits/go-gogs-client/wiki#access-token')
+ - link_to_personal_token = link_to(_('Personal Access Token'), 'https://docs.gitea.io/en-us/api-usage/#authentication-via-the-api')
= _('To get started, please enter your Gitea Host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token }
= form_tag personal_access_token_import_gitea_path do
.form-group.row
= label_tag :gitea_host_url, _('Gitea Host URL'), class: 'col-form-label col-sm-2'
.col-sm-4
- = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control'
+ = text_field_tag :gitea_host_url, nil, placeholder: 'https://gitea.com', class: 'form-control'
.form-group.row
= label_tag :personal_access_token, _('Personal Access Token'), class: 'col-form-label col-sm-2'
.col-sm-4
diff --git a/app/views/import/gitea/status.html.haml b/app/views/import/gitea/status.html.haml
index 88244fde16b..ef0693e73c3 100644
--- a/app/views/import/gitea/status.html.haml
+++ b/app/views/import/gitea/status.html.haml
@@ -1,7 +1,7 @@
- page_title _("Gitea Import")
- header_title _("Projects"), root_path
%h3.page-title
- = custom_icon('go_logo')
+ = custom_icon('gitea_logo')
= _('Import Projects from Gitea')
= render 'import/githubish_status', provider: 'gitea'
diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml
index cf32c5c9387..72e5934574a 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -22,6 +22,8 @@
= text_field_tag :personal_access_token, '', class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
= submit_tag _('List your GitHub repositories'), class: 'btn btn-success'
+ = render_if_exists 'import/github/ci_cd_only'
+
- unless github_import_configured?
%hr
%p
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 5e4595d930b..a19c8911559 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -7,28 +7,7 @@
%hr
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
- .row
- .form-group.project-name.col-sm-12
- = label_tag :name, _('Project name'), class: 'label-bold'
- = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
- .form-group.col-12.col-sm-6
- = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
- .form-group
- .input-group
- - if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
- .input-group-text
- = 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
-
- - else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
- .input-group-text.border-0
- #{user_url(current_user.username)}/
- = hidden_field_tag :namespace_id, value: current_user.namespace_id
- .form-group.col-12.col-sm-6.project-path
- = label_tag :path, _('Project slug'), class: 'label-bold'
- = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
+ = render 'import/shared/new_project_form'
.row
.form-group.col-md-12
diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml
index 056e4922b9e..df00c4d2179 100644
--- a/app/views/import/manifest/new.html.haml
+++ b/app/views/import/manifest/new.html.haml
@@ -4,9 +4,5 @@
%h3.page-title
= _('Manifest file import')
-- if @errors.present?
- .alert.alert-danger
- - @errors.each do |error|
- = error
-
+= render 'import/shared/errors'
= render 'form'
diff --git a/app/views/import/phabricator/new.html.haml b/app/views/import/phabricator/new.html.haml
new file mode 100644
index 00000000000..811e126579e
--- /dev/null
+++ b/app/views/import/phabricator/new.html.haml
@@ -0,0 +1,25 @@
+- title = _('Phabricator Server Import')
+- page_title title
+- breadcrumb_title title
+- header_title _("Projects"), root_path
+
+%h3.page-title
+ = icon 'issues', text: _('Import tasks from Phabricator into issues')
+
+= render 'import/shared/errors'
+
+= form_tag import_phabricator_path, class: 'new_project', method: :post do
+ = render 'import/shared/new_project_form'
+
+ %h4.prepend-top-0= _('Enter in your Phabricator Server URL and personal access token below')
+
+ .form-group.row
+ = label_tag :phabricator_server_url, _('Phabricator Server URL'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :phabricator_server_url, params[:phabricator_server_url], class: 'form-control append-right-8', placeholder: 'https://your-phabricator-server', size: 40
+ .form-group.row
+ = label_tag :api_token, _('API Token'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = password_field_tag :api_token, params[:api_token], class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
+ .form-actions
+ = submit_tag _('Import tasks'), class: 'btn btn-success'
diff --git a/app/views/import/shared/_errors.html.haml b/app/views/import/shared/_errors.html.haml
new file mode 100644
index 00000000000..de60c15351f
--- /dev/null
+++ b/app/views/import/shared/_errors.html.haml
@@ -0,0 +1,4 @@
+- if @errors.present?
+ .alert.alert-danger
+ - @errors.each do |error|
+ = error
diff --git a/app/views/import/shared/_new_project_form.html.haml b/app/views/import/shared/_new_project_form.html.haml
new file mode 100644
index 00000000000..4d13d4f2869
--- /dev/null
+++ b/app/views/import/shared/_new_project_form.html.haml
@@ -0,0 +1,21 @@
+.row
+ .form-group.project-name.col-sm-12
+ = label_tag :name, _('Project name'), class: 'label-bold'
+ = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
+ .form-group.col-12.col-sm-6
+ = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
+ .form-group
+ .input-group.flex-nowrap
+ - if current_user.can_select_namespace?
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
+ .input-group-text
+ = 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
+ - else
+ .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-text.border-0
+ #{user_url(current_user.username)}/
+ = hidden_field_tag :namespace_id, value: current_user.namespace_id
+ .form-group.col-12.col-sm-6.project-path
+ = label_tag :path, _('Project slug'), class: 'label-bold'
+ = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
index 21cf6d0dd65..94c32df7c60 100644
--- a/app/views/issues/_issue.atom.builder
+++ b/app/views/issues/_issue.atom.builder
@@ -12,6 +12,7 @@ xml.entry do
xml.summary issue.title
xml.description issue.description if issue.description
+ xml.content issue.description if issue.description
xml.milestone issue.milestone.title if issue.milestone
xml.due_date issue.due_date if issue.due_date
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 11e83ddfe64..c357207054b 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -77,3 +77,4 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
+ = render_if_exists 'layouts/snowplow'
diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml
index 26fd34347ec..6e8294d6adc 100644
--- a/app/views/layouts/_mailer.html.haml
+++ b/app/views/layouts/_mailer.html.haml
@@ -52,6 +52,7 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ = html_header_message
= header_logo
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
@@ -63,6 +64,8 @@
%tbody
= yield
+ = render_if_exists 'layouts/mailer/additional_text'
+
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
@@ -72,3 +75,6 @@
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= yield :additional_footer
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ = html_footer_message
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1b2a4cd6780..006334ade07 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -5,8 +5,10 @@
= render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
+ = render_if_exists "layouts/header/ee_license_banner"
= render "layouts/broadcast"
= render "layouts/header/read_only_banner"
+ = render "layouts/nav/classification_level_banner"
= yield :flash_message
= render "shared/ping_consent"
- unless @hide_breadcrumbs
diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml
index a888e8ae187..473b14ce626 100644
--- a/app/views/layouts/_piwik.html.haml
+++ b/app/views/layouts/_piwik.html.haml
@@ -7,7 +7,7 @@
(function() {
var u="//#{extra_config.piwik_url}/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
- _paq.push(['setSiteId', #{extra_config.piwik_site_id}]);
+ _paq.push(['setSiteId', "#{extra_config.piwik_site_id}"]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index a6023a1cbb9..496ec3c78b0 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -16,7 +16,7 @@
mr_path: merge_requests_dashboard_path },
aria: { label: _('Search or jump to…') }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
- .dropdown-menu.dropdown-select
+ .dropdown-menu.dropdown-select.js-dashboard-search-options
= dropdown_content do
%ul
%li.dropdown-menu-empty-item
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 043cca6ad38..c38f96f302a 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -10,4 +10,6 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= footer_message
+ = render_if_exists "shared/onboarding_guide"
+
= yield :scripts_body
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 2f3c13aaf6e..ff3410f6268 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -10,15 +10,17 @@
.container.navless-container
.content
= render "layouts/flash"
- .row.append-bottom-15
- .col-sm-7.brand-holder
- %h1
+ .row.mt-3
+ .col-sm-12
+ %h1.mb-3.font-weight-normal
= brand_title
+ .row.mb-3
+ .col-sm-7.order-12.order-sm-1.brand-holder
= brand_image
- if current_appearance&.description?
= brand_text
- else
- %h3
+ %h3.mt-sm-0
= _('Open source software to collaborate on code')
%p
@@ -26,7 +28,10 @@
- if Gitlab::CurrentSettings.sign_in_text.present?
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text)
- .col-sm-5.new-session-forms-container
+
+ = render_if_exists 'layouts/devise_help_text'
+
+ .col-sm-5.order-1.order-sm-12.new-session-forms-container
= yield
%hr.footer-fixed
diff --git a/app/views/layouts/empty_mailer.html.haml b/app/views/layouts/empty_mailer.html.haml
new file mode 100644
index 00000000000..a25dcefd445
--- /dev/null
+++ b/app/views/layouts/empty_mailer.html.haml
@@ -0,0 +1,5 @@
+= html_header_message
+
+= yield
+
+= html_footer_message
diff --git a/app/views/layouts/empty_mailer.text.erb b/app/views/layouts/empty_mailer.text.erb
new file mode 100644
index 00000000000..6ab0dbead07
--- /dev/null
+++ b/app/views/layouts/empty_mailer.text.erb
@@ -0,0 +1,5 @@
+<%= text_header_message %>
+
+<%= yield -%>
+
+<%= text_footer_message %>
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index a9b85889846..f8b7d0c530a 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -17,6 +17,10 @@
- if logo_text.present?
%span.logo-text.d-none.d-lg-block.prepend-left-8
= logo_text
+ - if Gitlab.com?
+ = link_to 'https://next.gitlab.com', class: 'label-link js-canary-badge canary-badge bg-transparent hidden', target: :_blank do
+ %span.color-label.has-tooltip.badge.badge-pill.green-badge
+ = _('Next')
- if current_user
= render "layouts/nav/dashboard"
@@ -38,7 +42,7 @@
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('issues', size: 16)
- issues_count = assigned_issuables_count(:issues)
- %span.badge.badge-pill.issues-count{ class: ('hidden' if issues_count.zero?) }
+ %span.badge.badge-pill.issues-count.green-badge{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
- if header_link?(:merge_requests)
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index cd9128c452b..5643a508ddc 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -2,8 +2,13 @@
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
+ = render_if_exists "shared/learn_gitlab_menu_item"
%li.divider
%li
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
= render 'shared/user_dropdown_contributing_link'
+ = render_if_exists 'shared/user_dropdown_instance_review'
+ - if Gitlab.com?
+ %li.js-canary-link
+ = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 5a66b02c048..438340464bd 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -38,4 +38,4 @@
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group?
%li= link_to _('New group'), new_group_path
- %li= link_to _('New snippet'), new_snippet_path
+ %li= link_to _('New snippet'), new_snippet_path, class: 'qa-global-new-snippet-link'
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 8e11174f8d7..1a06ea68bcd 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -1,4 +1,9 @@
+<%= text_header_message %>
+
<%= yield -%>
-- <%# signature marker %>
<%= _("You're receiving this email because of your account on %{host}.") % { host: Gitlab.config.gitlab.host } %>
+<%= render_if_exists 'layouts/mailer/additional_text' %>
+
+<%= text_footer_message %>
diff --git a/app/views/layouts/nav/_classification_level_banner.html.haml b/app/views/layouts/nav/_classification_level_banner.html.haml
new file mode 100644
index 00000000000..cc4caf079b8
--- /dev/null
+++ b/app/views/layouts/nav/_classification_level_banner.html.haml
@@ -0,0 +1,5 @@
+- if ::Gitlab::ExternalAuthorization.enabled? && @project
+ = content_for :header_content do
+ %span.badge.color-label.classification-label.has-tooltip{ title: s_('ExternalAuthorizationService|Classification label') }
+ = sprite_icon('lock-open', size: 8, css_class: 'inline')
+ = @project.external_authorization_classification_label
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index f659c89dd30..54028dc8554 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -3,7 +3,7 @@
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
@@ -11,7 +11,7 @@
- if dashboard_nav_link?(:groups)
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
@@ -19,17 +19,17 @@
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: _('Activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link' do
= _('Snippets')
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
@@ -41,47 +41,47 @@
%ul
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, title: _('Activity') do
+ = link_to activity_dashboard_path do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets' do
= _('Snippets')
-
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown.d-lg-none
+ = render_if_exists 'dashboard/operations/nav_link_list'
- if can?(current_user, :read_instance_statistics)
- = nav_link(controller: [:conversational_development_index, :cohorts]) do
- = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: 'd-lg-none' }) do
+ = link_to instance_statistics_root_path do
= _('Instance Statistics')
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
- = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, class: 'd-lg-none admin-icon qa-admin-area-link' do
= _('Admin Area')
- if Gitlab::Sherlock.enabled?
%li
- = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'),
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to sherlock_transactions_path, class: 'd-lg-none admin-icon' do
= _('Sherlock Transactions')
-# Shortcut to Dashboard > Projects
- if dashboard_nav_link?(:projects)
%li.hidden
- = link_to dashboard_projects_path, title: _('Projects'), class: 'dashboard-shortcuts-projects' do
+ = link_to dashboard_projects_path, class: 'dashboard-shortcuts-projects' do
= _('Projects')
- if current_controller?('ide')
%li.line-separator.d-none.d-sm-block
= nav_link(controller: 'ide') do
- = link_to '#', class: 'dashboard-shortcuts-web-ide', title: _('Web IDE') do
+ = link_to '#', class: 'dashboard-shortcuts-web-ide' do
= _('Web IDE')
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown{ class: 'd-none d-lg-block' }
+ = render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
= nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
@@ -95,3 +95,4 @@
= link_to sherlock_transactions_path, class: 'admin-icon d-none d-lg-block d-xl-block', title: _('Sherlock Transactions'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
+ = render_if_exists 'layouts/nav/geo_primary_node_url'
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 2fdd65f639b..83fe871285a 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -48,7 +48,7 @@
%span
= _('Gitaly Servers')
- = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
+ = nav_link(controller: admin_monitoring_nav_links) do
= link_to admin_system_info_path do
.nav-icon-container
= sprite_icon('monitor')
@@ -81,6 +81,7 @@
= link_to admin_requests_profiles_path, title: _('Requests Profiles') do
%span
= _('Requests Profiles')
+ = render_if_exists 'layouts/nav/ee/admin/new_monitoring_sidebar'
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path do
@@ -132,6 +133,21 @@
= _('Abuse Reports')
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all))
+ = render_if_exists 'layouts/nav/sidebar/licenses_link'
+
+ - if instance_clusters_enabled?
+ = nav_link(controller: :clusters) do
+ = link_to admin_clusters_path do
+ .nav-icon-container
+ = sprite_icon('cloud-gear')
+ %span.nav-item-name
+ = _('Kubernetes')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(controller: :clusters, html_options: { class: "fly-out-top-item" } ) do
+ = link_to admin_clusters_path do
+ %strong.fly-out-top-item-name
+ = _('Kubernetes')
+
- if akismet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path do
@@ -145,6 +161,10 @@
%strong.fly-out-top-item-name
= _('Spam Logs')
+ = render_if_exists 'layouts/nav/sidebar/push_rules_link'
+
+ = render_if_exists 'layouts/nav/ee/admin/geo_sidebar'
+
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path do
.nav-icon-container
@@ -220,7 +240,7 @@
= _('Repository')
- if template_exists?('admin/application_settings/templates')
= nav_link(path: 'application_settings#templates') do
- = link_to templates_admin_application_settings_path, title: _('Templates') do
+ = link_to templates_admin_application_settings_path, title: _('Templates'), class: 'qa-admin-settings-template-item' do
%span
= _('Templates')
= nav_link(path: 'application_settings#ci_cd') do
@@ -232,7 +252,7 @@
%span
= _('Reporting')
= nav_link(path: 'application_settings#metrics_and_profiling') do
- = link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling') do
+ = link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling'), class: 'qa-admin-settings-metrics-and-profiling-item' do
%span
= _('Metrics and profiling')
= nav_link(path: 'application_settings#network') do
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 21ea9f3b2f3..0fc5ebbea7e 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,6 +1,5 @@
- issues_count = group_issues_count(state: 'opened')
- merge_requests_count = group_merge_requests_count(state: 'opened')
-- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index', 'boards#index', 'boards#show']
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
@@ -20,13 +19,14 @@
= _('Overview')
%ul.sidebar-sub-level-items
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
= link_to group_path(@group) do
%strong.fly-out-top-item-name
= _('Overview')
%li.divider.fly-out-top-item
- = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: _('Group details') do
+
+ = nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to details_group_path(@group), title: _('Group details') do
%span
= _('Details')
@@ -40,14 +40,17 @@
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
- = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
+ = link_to group_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right' } do
%span
- Contribution Analytics
+ = _('Contribution Analytics')
+
+ = render_if_exists 'layouts/nav/group_insights_link'
+ = render_if_exists 'groups/sidebar/dependency_proxy' # EE-specific
= render_if_exists "layouts/nav/ee/epic_link", group: @group
- if group_sidebar_link?(:issues)
- = nav_link(path: issues_sub_menu_items) do
+ = nav_link(path: group_issues_sub_menu_items) do
= link_to issues_group_path(@group) do
.nav-icon-container
= sprite_icon('issues')
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index 1e3bb8f1224..7dd33f3c641 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -4,7 +4,7 @@
= link_to profile_path, title: _('Profile Settings') do
.avatar-container.s40.settings-avatar
= image_tag avatar_icon_for_user(current_user, 40), class: "avatar s40 avatar-tile", alt: current_user.name
- .sidebar-context-title User Settings
+ .sidebar-context-title= _('User Settings')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path do
@@ -28,6 +28,8 @@
= link_to profile_account_path do
%strong.fly-out-top-item-name
= _('Account')
+
+ = render_if_exists 'layouts/nav/sidebar/profile_billing_link'
= nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path do
.nav-icon-container
@@ -151,4 +153,6 @@
%strong.fly-out-top-item-name
= _('Authentication Log')
+ = render_if_exists 'layouts/nav/sidebar/profile_pipeline_quota_link'
+
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 7b492efeb09..399305baec1 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -41,6 +41,8 @@
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
%span= _('Cycle Analytics')
+ = render_if_exists 'layouts/nav/project_insights_link'
+
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
@@ -268,6 +270,8 @@
%span= _("Got it!")
= sprite_icon('thumb-up')
+ = render_if_exists 'layouts/nav/sidebar/project_feature_flags_link'
+
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
@@ -281,7 +285,9 @@
%strong.fly-out-top-item-name
= _('Registry')
- - if project_nav_tab?(:wiki)
+ = render_if_exists 'layouts/nav/sidebar/project_packages_link'
+
+ - if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home)
= nav_link(controller: :wikis) do
= link_to wiki_url, class: 'shortcuts-wiki qa-wiki-link' do
@@ -355,12 +361,12 @@
= link_to project_settings_repository_path(@project), title: _('Repository') do
%span
= _('Repository')
- - if @project.feature_available?(:builds, current_user)
+ - if !@project.archived? && @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
= link_to project_settings_ci_cd_path(@project), title: _('CI / CD') do
%span
= _('CI / CD')
- - if settings_operations_available?
+ - if !@project.archived? && settings_operations_available?
= nav_link(controller: [:operations]) do
= link_to project_settings_operations_path(@project), title: _('Operations') do
= _('Operations')
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 1c3e05e07f4..de487a94d40 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -7,6 +7,7 @@
= yield :head
%body
.content
+ = html_header_message
= yield
.footer{ style: "margin-top: 10px;" }
%p
@@ -30,3 +31,6 @@
adjust your notification settings.
= email_action @target_url
+
+ = render_if_exists 'layouts/email_additional_text'
+ = html_footer_message
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index 9dc490efa9a..0ee30c2a6cf 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -1,3 +1,5 @@
+<%= text_header_message %>
+
<%= yield -%>
-- <%# signature marker %>
@@ -10,3 +12,6 @@
<% end -%>
<%= "You're receiving this email because #{notification_reason_text(@reason)}." %>
+<%= render_if_exists 'layouts/mailer/additional_text' %>
+
+<%= text_footer_message -%>
diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb
index 5a67214059c..fae8fa3ccf3 100644
--- a/app/views/notify/_note_email.text.erb
+++ b/app/views/notify/_note_email.text.erb
@@ -1,5 +1,6 @@
<% note = local_assigns.fetch(:note, @note) -%>
<% diff_limit = local_assigns.fetch(:diff_limit, nil) -%>
+<% target_url = local_assigns.fetch(:target_url, @target_url) -%>
<% discussion = note.discussion if note.part_of_discussion? -%>
<% if discussion && !discussion.individual_note? -%>
@@ -13,6 +14,9 @@
<%= " on #{discussion.file_path}" -%>
<% end -%>
<%= ":" -%>
+<% if discussion.diff_discussion? || !discussion.new_discussion? -%>
+<%= " #{target_url}" -%>
+<% end -%>
<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml
new file mode 100644
index 00000000000..4ab40ff2659
--- /dev/null
+++ b/app/views/notify/_reassigned_issuable_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Assignee changed
+ - if previous_assignees.any?
+ from
+ %strong= sanitize_name(previous_assignees.map(&:name).to_sentence)
+ to
+ - if issuable.assignees.any?
+ %strong= sanitize_name(issuable.assignee_list)
+ - else
+ %strong Unassigned
diff --git a/app/views/notify/_removal_notification.html.haml b/app/views/notify/_removal_notification.html.haml
new file mode 100644
index 00000000000..590e0d569aa
--- /dev/null
+++ b/app/views/notify/_removal_notification.html.haml
@@ -0,0 +1,9 @@
+- if @domain.remove_at
+ %p
+ Unless you verify your domain by
+ %strong= @domain.remove_at.strftime('%F %T,')
+ it will be removed from your GitLab project.
+- else
+ %p
+ If you no longer wish to use this domain with GitLab Pages, please remove it
+ from your GitLab project and delete any related DNS records.
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index eb148d72da1..d3733ab3a09 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,2 +1,2 @@
%p
- Issue was closed by #{sanitize_name(@updated_by.name)}
+ = _("Issue was closed by %{name} %{reason}").html_safe % { name: sanitize_name(@updated_by.name), reason: closure_reason_text(@closed_via, format: formats.first) }
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index b1f0a3f37ec..ff2548a4b42 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
-Issue was closed by #{sanitize_name(@updated_by.name)}
+= _("Issue was closed by %{name} %{reason}").html_safe % { name: sanitize_name(@updated_by.name), reason: closure_reason_text(@closed_via, format: formats.first) }
Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 1094d584a1c..6e84f9fb355 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -5,4 +5,4 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
-Assignee: #{sanitize_name(@merge_request.assignee_name)}
+= assignees_label(@merge_request)
diff --git a/app/views/notify/issue_due_email.html.haml b/app/views/notify/issue_due_email.html.haml
index e81144b8fcb..08bc98ca05c 100644
--- a/app/views/notify/issue_due_email.html.haml
+++ b/app/views/notify/issue_due_email.html.haml
@@ -3,7 +3,7 @@
- if @issue.assignees.any?
%p
- Assignee: #{@issue.assignee_list}
+ = assignees_label(@issue)
%p
This issue is due on: #{@issue.due_date.to_s(:medium)}
diff --git a/app/views/notify/issue_due_email.text.erb b/app/views/notify/issue_due_email.text.erb
index 3c7a57a8a2e..ae50b703fe3 100644
--- a/app/views/notify/issue_due_email.text.erb
+++ b/app/views/notify/issue_due_email.text.erb
@@ -2,6 +2,6 @@ The following issue is due on <%= @issue.due_date %>:
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
-Assignee: <%= @issue.assignee_list %>
+<%= assignees_label(@issue) %>
<%= @issue.description %>
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
index 472c31e9a5e..b766cb1a523 100644
--- a/app/views/notify/issue_moved_email.html.haml
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -1,6 +1,9 @@
%p
Issue was moved to another project.
-%p
- New issue:
- = link_to project_issue_url(@new_project, @new_issue) do
- = @new_issue.title
+- if @can_access_project
+ %p
+ New issue:
+ = link_to project_issue_url(@new_project, @new_issue) do
+ = @new_issue.title
+- else
+ You don't have access to the project.
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
index 66ede43635b..985e689aa9d 100644
--- a/app/views/notify/issue_moved_email.text.erb
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -1,4 +1,8 @@
Issue was moved to another project.
+<% if @can_access_project %>
New issue location:
<%= project_issue_url(@new_project, @new_issue) %>
+<% else %>
+You don't have access to the project.
+<% end %>
diff --git a/app/views/notify/links/ci/builds/_build.text.erb b/app/views/notify/links/ci/builds/_build.text.erb
index 773ae8174e9..afb02f97e5a 100644
--- a/app/views/notify/links/ci/builds/_build.text.erb
+++ b/app/views/notify/links/ci/builds/_build.text.erb
@@ -1 +1 @@
-Job #<%= build.id %> ( <%= pipeline_job_url(pipeline, build) %> )
+Job #<%= build.id %> ( <%= raw_project_job_url(pipeline.project, build) %> )
diff --git a/app/views/notify/member_access_granted_email.html.haml b/app/views/notify/member_access_granted_email.html.haml
index 18dec806539..1c50dba9c97 100644
--- a/app/views/notify/member_access_granted_email.html.haml
+++ b/app/views/notify/member_access_granted_email.html.haml
@@ -1,3 +1,10 @@
+- link_end = '</a>'.html_safe
+- source_type = member_source.model_name.singular
+- leave_link = polymorphic_url([member_source], leave: 1)
+- source_link = link_to(member_source.human_name, member_source.web_url, target: '_blank', rel: 'noopener noreferrer')
+
%p
- You have been granted #{member.human_access} access to the
- #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}.
+ = _('You have been granted %{access_level} access to the %{source_link} %{source_type}.').html_safe % { access_level: member.human_access, source_link: source_link, source_type: source_type }
+%p
+ - leave_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: leave_link }
+ = _('If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}.').html_safe % { source_type: source_type, leave_link_start: leave_link_start, link_end: link_end }
diff --git a/app/views/notify/member_access_granted_email.text.erb b/app/views/notify/member_access_granted_email.text.erb
index a9fb3a589a5..445009bb413 100644
--- a/app/views/notify/member_access_granted_email.text.erb
+++ b/app/views/notify/member_access_granted_email.text.erb
@@ -1,3 +1,8 @@
-You have been granted <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>.
+<% source_type = member_source.model_name.singular %>
+<%= _('You have been granted %{access_level} access to the %{source_name} %{source_type}.') % { access_level: member.human_access, source_name: member_source.human_name, source_type: source_type } %>
<%= member_source.web_url %>
+
+<%= _('If this was a mistake you can leave the %{source_type}.') % { source_type: source_type } %>
+
+<%= polymorphic_url([member_source], leave: 1) %>
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index b9b9e0c3ad7..e3b24bbd405 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -5,4 +5,4 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
-Assignee: #{sanitize_name(@merge_request.assignee_name)}
+= assignees_label(@merge_request)
diff --git a/app/views/notify/merge_request_unmergeable_email.text.haml b/app/views/notify/merge_request_unmergeable_email.text.haml
index 0c7bf1bb044..e9708a297d7 100644
--- a/app/views/notify/merge_request_unmergeable_email.text.haml
+++ b/app/views/notify/merge_request_unmergeable_email.text.haml
@@ -5,4 +5,4 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
-Assignee: #{sanitize_name(@merge_request.assignee_name)}
+= assignees_label(@merge_request)
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 045a43cbc84..d623e701a30 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -5,4 +5,4 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
-Assignee: #{sanitize_name(@merge_request.assignee_name)}
+= assignees_label(@merge_request)
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index e6cdaf85c0d..8aa7939dd0b 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -4,7 +4,7 @@
- if @issue.assignees.any?
%p
- Assignee: #{@issue.assignee_list}
+ = assignees_label(@issue)
- if @issue.description
%div
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 58a2bcbe5eb..ff258711b48 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -2,6 +2,6 @@ New Issue was created.
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= sanitize_name(@issue.author_name) %>
-Assignee: <%= @issue.assignee_list %>
+<%= assignees_label(@issue) %>
<%= @issue.description %>
diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb
index 173091e4a80..8e95063b40f 100644
--- a/app/views/notify/new_mention_in_issue_email.text.erb
+++ b/app/views/notify/new_mention_in_issue_email.text.erb
@@ -2,6 +2,6 @@ You have been mentioned in an issue.
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= sanitize_name(@issue.author_name) %>
-Assignee: <%= sanitize_name(@issue.assignee_list) %>
+<%= assignees_label(@issue) %>
<%= @issue.description %>
diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb
index 96a4f3f9eac..3c78e257a88 100644
--- a/app/views/notify/new_mention_in_merge_request_email.text.erb
+++ b/app/views/notify/new_mention_in_merge_request_email.text.erb
@@ -4,6 +4,6 @@ You have been mentioned in Merge Request <%= @merge_request.to_reference %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= sanitize_name(@merge_request.author_name) %>
-Assignee: <%= sanitize_name(@merge_request.assignee_name) %>
+= assignees_label(@merge_request)
<%= @merge_request.description %>
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index db23447dd39..9ab648e2a64 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -3,11 +3,11 @@
#{link_to @merge_request.author_name, user_url(@merge_request.author)} created a merge request:
%p.details
- != merge_path_description(@merge_request, '&rarr;')
+ = merge_path_description(@merge_request, '→')
-- if @merge_request.assignee_id.present?
+- if @merge_request.assignees.any?
%p
- Assignee: #{sanitize_name(@merge_request.assignee_name)}
+ = assignees_label(@merge_request)
= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 754f4bca1cd..e6c42f1cf5f 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -4,7 +4,7 @@ New Merge Request <%= @merge_request.to_reference %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
-Assignee: <%= @merge_request.assignee_name %>
+<%= assignees_label(@merge_request) %>
<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
<%= @merge_request.description %>
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index dfbb5c75bd3..ec135ae994f 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -13,4 +13,5 @@
%p
= link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token)
%p
- = raw reset_token_expire_message
+ This link is valid for #{password_reset_token_valid_time}.
+ After it expires, you can #{link_to("request a new one", new_user_password_url(user_email: @user.email))}.
diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb
index f3f20f3bfba..7e0db75472d 100644
--- a/app/views/notify/new_user_email.text.erb
+++ b/app/views/notify/new_user_email.text.erb
@@ -1,10 +1,17 @@
Hi <%= sanitize_name(@user.name) %>!
+<% if Gitlab::CurrentSettings.allow_signup? %>
+Your account has been created successfully.
+<% else %>
The Administrator created an account for you. Now you are a member of the company GitLab application.
+<% end %>
login.................. <%= @user.email %>
+
<% if @user.created_by_id %>
- <%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %>
+Click here to set your password:
+<%= edit_password_url(@user, :reset_password_token => @token) %>
- <%= reset_token_expire_message %>
+This link is valid for <%= password_reset_token_valid_time %>. After it expires, you can request a new one here:
+<%= new_user_password_url(user_email: @user.email) %>
<% end %>
diff --git a/app/views/notify/pages_domain_disabled_email.html.haml b/app/views/notify/pages_domain_disabled_email.html.haml
index 34ce4238a12..224b79bfde8 100644
--- a/app/views/notify/pages_domain_disabled_email.html.haml
+++ b/app/views/notify/pages_domain_disabled_email.html.haml
@@ -10,6 +10,4 @@
If this domain has been disabled in error, please follow
= link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
to verify and re-enable your domain.
-%p
- If you no longer wish to use this domain with GitLab Pages, please remove it
- from your GitLab project and delete any related DNS records.
+= render 'removal_notification'
diff --git a/app/views/notify/pages_domain_verification_failed_email.html.haml b/app/views/notify/pages_domain_verification_failed_email.html.haml
index 0bb0eb09fd5..03b298f8e7c 100644
--- a/app/views/notify/pages_domain_verification_failed_email.html.haml
+++ b/app/views/notify/pages_domain_verification_failed_email.html.haml
@@ -12,6 +12,4 @@
Please visit
= link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
for more information about custom domain verification.
-%p
- If you no longer wish to use this domain with GitLab Pages, please remove it
- from your GitLab project and delete any related DNS records.
+= render 'removal_notification'
diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml
index 6d25488a7e2..6b088927623 100644
--- a/app/views/notify/reassigned_issue_email.html.haml
+++ b/app/views/notify/reassigned_issue_email.html.haml
@@ -1,10 +1 @@
-%p
- Assignee changed
- - if @previous_assignees.any?
- from
- %strong= sanitize_name(@previous_assignees.map(&:name).to_sentence)
- to
- - if @issue.assignees.any?
- %strong= @issue.assignee_list
- - else
- %strong Unassigned
+= render 'reassigned_issuable_email', issuable: @issue, previous_assignees: @previous_assignees
diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml
index e4f19bc3200..0aefca6b14a 100644
--- a/app/views/notify/reassigned_merge_request_email.html.haml
+++ b/app/views/notify/reassigned_merge_request_email.html.haml
@@ -1,10 +1 @@
-%p
- Assignee changed
- - if @previous_assignee
- from
- %strong= sanitize_name(@previous_assignee.name)
- to
- - if @merge_request.assignee_id
- %strong= sanitize_name(@merge_request.assignee_name)
- - else
- %strong Unassigned
+= render 'reassigned_issuable_email', issuable: @merge_request, previous_assignees: @previous_assignees
diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb
index 96c770b5219..82ec7aa0fa4 100644
--- a/app/views/notify/reassigned_merge_request_email.text.erb
+++ b/app/views/notify/reassigned_merge_request_email.text.erb
@@ -2,5 +2,5 @@ Reassigned Merge Request <%= @merge_request.iid %>
<%= url_for([@merge_request.project.namespace.becomes(Namespace), @merge_request.project, @merge_request, { only_path: false }]) %>
-Assignee changed <%= "from #{sanitize_name(@previous_assignee.name)}" if @previous_assignee -%>
- to <%= "#{@merge_request.assignee_id ? sanitize_name(@merge_request.assignee_name) : 'Unassigned'}" %>
+Assignee changed <%= "from #{sanitize_name(@previous_assignees.map(&:name).to_sentence)}" if @previous_assignees.any? -%>
+ to <%= "#{@merge_request.assignees.any? ? @merge_request.assignee_list : 'Unassigned'}" %>
diff --git a/app/views/profiles/_email_settings.html.haml b/app/views/profiles/_email_settings.html.haml
new file mode 100644
index 00000000000..fb4da08e129
--- /dev/null
+++ b/app/views/profiles/_email_settings.html.haml
@@ -0,0 +1,16 @@
+- form = local_assigns.fetch(:form)
+- readonly = @user.read_only_attribute?(:email)
+- email_change_disabled = local_assigns.fetch(:email_change_disabled, nil)
+- read_only_help_text = readonly ? s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) } : user_email_help_text(@user)
+- help_text = email_change_disabled ? s_("Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO.") % { group_name: @user.managing_group.name } : read_only_help_text
+
+= form.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: help_text.html_safe, readonly: readonly || email_change_disabled
+= form.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
+ { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
+- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
+- commit_email_link_start = '<a href="%{url}">'.html_safe % { url: commit_email_link_url }
+- commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
+= form.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
+ { help: commit_email_docs_link },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index 9f525547dd9..977ff30d5a6 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,14 +1,12 @@
%h5.prepend-top-0
- History of authentications
+ = _('History of authentications')
%ul.content-list
- events.each do |event|
%li
%span.description
= audit_icon(event.details[:with], class: "append-right-5")
- Signed in with
- = event.details[:with]
- authentication
+ = _('Signed in with %{authentication} authentication') % { authentication: event.details[:with]}
%span.float-right= time_ago_with_tooltip(event.created_at)
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/accounts/_providers.html.haml b/app/views/profiles/accounts/_providers.html.haml
new file mode 100644
index 00000000000..068f9cc70f7
--- /dev/null
+++ b/app/views/profiles/accounts/_providers.html.haml
@@ -0,0 +1,21 @@
+%label.label-bold
+ = s_('Profiles|Connected Accounts')
+ %p= s_('Profiles|Click on icon to activate signin with one of the following services')
+ - providers.each do |provider|
+ - unlink_allowed = unlink_provider_allowed?(provider)
+ - link_allowed = link_provider_allowed?(provider)
+ - if unlink_allowed || link_allowed
+ .provider-btn-group
+ .provider-btn-image
+ = provider_image_tag(provider)
+ - if auth_active?(provider)
+ - if unlink_allowed
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
+ = s_('Profiles|Disconnect')
+ - else
+ %a.provider-btn
+ = s_('Profiles|Active')
+ - elsif link_allowed
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
+ = s_('Profiles|Connect')
+ = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index ee2c5a13b8a..e6380817c8f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -29,24 +29,7 @@
%p
= s_('Profiles|Activate signin with one of the following services')
.col-lg-8
- %label.label-bold
- = s_('Profiles|Connected Accounts')
- %p= s_('Profiles|Click on icon to activate signin with one of the following services')
- - button_based_providers.each do |provider|
- .provider-btn-group
- .provider-btn-image
- = provider_image_tag(provider)
- - if auth_active?(provider)
- - if unlink_allowed?(provider)
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- = s_('Profiles|Disconnect')
- - else
- %a.provider-btn
- = s_('Profiles|Active')
- - else
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- = s_('Profiles|Connect')
- = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
+ = render 'providers', providers: button_based_providers, group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml
index 23ef31a0c85..bb31049111c 100644
--- a/app/views/profiles/active_sessions/_active_session.html.haml
+++ b/app/views/profiles/active_sessions/_active_session.html.haml
@@ -8,24 +8,19 @@
%div
%strong= active_session.ip_address
- if is_current_session
- %div This is your current session
+ %div
+ = _('This is your current session')
- else
%div
- Last accessed on
+ = _('Last accessed on')
= l(active_session.updated_at, format: :short)
%div
%strong= active_session.browser
- on
+ = s_('ProfileSession|on')
%strong= active_session.os
%div
- %strong Signed in
- on
+ %strong= _('Signed in')
+ = s_('ProfileSession|on')
= l(active_session.created_at, format: :short)
-
- - unless is_current_session
- .float-right
- = link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
- %span.sr-only Revoke
- Revoke
diff --git a/app/views/profiles/active_sessions/index.html.haml b/app/views/profiles/active_sessions/index.html.haml
index 8688a52843d..d651319fc3f 100644
--- a/app/views/profiles/active_sessions/index.html.haml
+++ b/app/views/profiles/active_sessions/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Active Sessions'
+- page_title _('Active Sessions')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,7 +6,7 @@
%h4.prepend-top-0
= page_title
%p
- This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.
+ = _('This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.')
.col-lg-8
.append-bottom-default
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index a924369050b..275c0428d34 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,4 @@
-- page_title "Authentication log"
+- page_title _('Authentication log')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,6 +6,6 @@
%h4.prepend-top-0
= page_title
%p
- This is a security log of important events involving your account.
+ = _('This is a security log of important events involving your account.')
.col-lg-8
= render 'event_table', events: @events
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 9e82e47c1e1..ff67f92ad07 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -21,7 +21,7 @@
- if chat_name.last_used_at
= time_ago_with_tooltip(chat_name.last_used_at)
- else
- Never
+ = _('Never')
%td
- = link_to 'Remove', profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: 'Are you sure you want to revoke this nickname?' }
+ = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: _('Are you sure you want to revoke this nickname?') }
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 4b6e419af50..0c8098a97d5 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Chat'
+- page_title _('Chat')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,7 +6,7 @@
%h4.prepend-top-0
= page_title
%p
- You can see your Chat accounts.
+ = _('You can see your chat accounts.')
.col-lg-8
%h5 Active chat names (#{@chat_names.size})
@@ -16,15 +16,15 @@
%table.table.chat-names
%thead
%tr
- %th Project
- %th Service
- %th Team domain
- %th Nickname
- %th Last used
+ %th= _('Project')
+ %th= _('Service')
+ %th= _('Team domain')
+ %th= _('Nickname')
+ %th= _('Last used')
%th
%tbody
= render @chat_names
- else
.settings-message.text-center
- You don't have any active chat names.
+ = _("You don't have any active chat names.")
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 1823f191fb3..c90a0b3e329 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -26,7 +26,9 @@
%li
Your Commit Email will be used for web based operations, such as edits and merges.
%li
- Your Notification Email will be used for account notifications.
+ Your Default Notification Email will be used for account notifications if a
+ = link_to 'group-specific email address', profile_notifications_path
+ is not set.
%li
Your Public Email will be displayed on your public profile.
%li
@@ -41,7 +43,7 @@
- if @primary_email === current_user.public_email
%span.badge.badge-info Public email
- if @primary_email === current_user.notification_email
- %span.badge.badge-info Notification email
+ %span.badge.badge-info Default notification email
- @emails.each do |email|
%li
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml
index 6c4cb614a2b..225487b2638 100644
--- a/app/views/profiles/gpg_keys/_form.html.haml
+++ b/app/views/profiles/gpg_keys/_form.html.haml
@@ -3,8 +3,8 @@
= form_errors(@gpg_key)
.form-group
- = f.label :key, class: 'label-bold'
- = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+ = f.label :key, s_('Profiles|Key'), class: 'label-bold'
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: _("Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'.")
.prepend-top-default
- = f.submit 'Add key', class: "btn btn-success"
+ = f.submit s_('Profiles|Add key'), class: "btn btn-success"
diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml
index d1fd7bc8e71..f8351644df5 100644
--- a/app/views/profiles/gpg_keys/_key.html.haml
+++ b/app/views/profiles/gpg_keys/_key.html.haml
@@ -9,17 +9,19 @@
%code= key.fingerprint
- if key.subkeys.present?
.subkeys
- %span.bold Subkeys:
+ %span.bold
+ = _('Subkeys')
+ = ':'
%ul.subkeys-list
- key.subkeys.each do |subkey|
%li
%code= subkey.fingerprint
.float-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)}
- = link_to profile_gpg_key_path(key), data: { confirm: 'Are you sure? Removing this GPG key does not affect already signed commits.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
- %span.sr-only Remove
+ = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
+ = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only= _('Remove')
= icon('trash')
- = link_to revoke_profile_gpg_key_path(key), data: { confirm: 'Are you sure? All commits that were signed with this GPG key will be unverified.' }, method: :put, class: "btn btn-danger prepend-left-10" do
- %span.sr-only Revoke
- Revoke
+ = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only= _('Revoke')
+ = _('Revoke')
diff --git a/app/views/profiles/gpg_keys/_key_table.html.haml b/app/views/profiles/gpg_keys/_key_table.html.haml
index b9b60c218fd..ebbd1c8f672 100644
--- a/app/views/profiles/gpg_keys/_key_table.html.haml
+++ b/app/views/profiles/gpg_keys/_key_table.html.haml
@@ -6,6 +6,6 @@
- else
%p.settings-message.text-center
- if is_admin
- There are no GPG keys associated with this account.
+ = _('There are no GPG keys associated with this account.')
- else
- There are no GPG keys with access to your account.
+ = _('There are no GPG keys with access to your account.')
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 1d2e41cb437..f9f898a9225 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "GPG Keys"
+- page_title _('GPG Keys')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,16 +6,16 @@
%h4.prepend-top-0
= page_title
%p
- GPG keys allow you to verify signed commits.
+ = _('GPG keys allow you to verify signed commits.')
.col-lg-8
%h5.prepend-top-0
- Add a GPG key
+ = _('Add a GPG key')
%p.profile-settings-content
- Before you can add a GPG key you need to
- = link_to 'generate it.', help_page_path('user/project/repository/gpg_signed_commits/index.md')
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') }
+ = _('Before you can add a GPG key you need to %{help_link_start}Generate it.%{help_link_end}'.html_safe) % {help_link_start: help_link_start, help_link_end:'</a>'.html_safe }
= render 'form'
%hr
%h5
- Your GPG keys (#{@gpg_keys.count})
+ = _('Your GPG keys (%{count})') % { count:@gpg_keys.count}
.append-bottom-default
= render 'key_table'
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 21eef08983c..7846cdbcd52 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -3,11 +3,11 @@
= form_errors(@key)
.form-group
- = f.label :key, class: 'label-bold'
+ = f.label :key, s_('Profiles|Key'), class: 'label-bold'
%p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
= f.text_area :key, class: "form-control js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
.form-group
- = f.label :title, class: 'label-bold'
+ = f.label :title, _('Title'), class: 'label-bold'
= f.text_field :title, class: "form-control input-lg qa-key-title-field", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
%p.form-text.text-muted= _('Name your individual key via a title')
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index ce20994b0f4..b9d73d89334 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -17,7 +17,8 @@
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : 'n/a'
.float-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)}
- = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
- %span.sr-only Remove
- = icon('trash')
+ = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
+ - if key.can_delete?
+ = link_to path_to_key(key, is_admin), data: { confirm: _('Are you sure?')}, method: :delete, class: "btn btn-transparent prepend-left-10" do
+ %span.sr-only= _('Remove')
+ = icon('trash')
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 88473c7f72d..0ef01dec493 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -3,25 +3,26 @@
.col-md-4
.card
.card-header
- SSH Key
+ = _('SSH Key')
%ul.content-list
%li
- %span.light Title:
+ %span.light= _('Title:')
%strong= @key.title
%li
- %span.light Created on:
+ %span.light= _('Created on:')
%strong= @key.created_at.to_s(:medium)
%li
- %span.light Last used on:
+ %span.light= _('Last used on:')
%strong= @key.last_used_at.try(:to_s, :medium) || 'N/A'
.col-md-8
= form_errors(@key, type: 'key') unless @key.valid?
%p
- %span.light Fingerprint:
+ %span.light= _('Fingerprint:')
%code.key-fingerprint= @key.fingerprint
%pre.well-pre
= @key.key
.col-md-12
.float-right
- = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
+ - if @key.can_delete?
+ = link_to _('Remove'), path_to_key(@key, is_admin), data: {confirm: _('Are you sure?')}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index e088140fdd2..4a6d8a1870d 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -6,6 +6,6 @@
- else
%p.settings-message.text-center
- if is_admin
- There are no SSH keys associated with this account.
+ = _('There are no SSH keys associated with this account.')
- else
- There are no SSH keys with access to your account.
+ = _('There are no SSH keys with access to your account.')
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 55ca8d0ebd4..da6aa0fce3a 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "SSH Keys"
+- page_title _('SSH Keys')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,10 +6,10 @@
%h4.prepend-top-0
= page_title
%p
- SSH keys allow you to establish a secure connection between your computer and GitLab.
+ = _('SSH keys allow you to establish a secure connection between your computer and GitLab.')
.col-lg-8
%h5.prepend-top-0
- Add an SSH key
+ = _('Add an SSH key')
%p.profile-settings-content
- generate_link_url = help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
- existing_link_url = help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
@@ -19,6 +19,6 @@
= render 'form'
%hr
%h5
- Your SSH keys (#{@keys.count})
+ = _('Your SSH keys (%{count})') % { count:@keys.count }
.append-bottom-default
= render 'key_table'
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 28be6172219..360de7a0c11 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,5 +1,5 @@
- add_to_breadcrumbs "SSH Keys", profile_keys_path
- breadcrumb_title @key.title
-- page_title @key.title, "SSH Keys"
+- page_title @key.title, _('SSH Keys')
- @content_class = "limit-container-width" unless fluid_layout
= render "key_details"
diff --git a/app/views/profiles/notifications/_email_settings.html.haml b/app/views/profiles/notifications/_email_settings.html.haml
new file mode 100644
index 00000000000..34dcf8f5402
--- /dev/null
+++ b/app/views/profiles/notifications/_email_settings.html.haml
@@ -0,0 +1,6 @@
+- form = local_assigns.fetch(:form)
+.form-group
+ = form.label :notification_email, class: "label-bold"
+ = form.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
+ .help-block
+ = local_assigns.fetch(:help_text, nil)
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index a12246bcdcc..cf17ee44145 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -1,12 +1,17 @@
-%li.notification-list-item
- %span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+.gl-responsive-table-row.notification-list-item
+ .table-section.section-40
+ %span.notification.fa.fa-holder.append-right-5
+ - if setting.global?
+ = notification_icon(current_user.global_notification_setting.level)
+ - else
+ = notification_icon(setting.level)
- %span.str-truncated
- = link_to group.name, group_path(group)
+ %span.str-truncated
+ = link_to group.name, group_path(group)
- .float-right
+ .table-section.section-30.text-right
= render 'shared/notifications/button', notification_setting: setting
+
+ .table-section.section-30
+ = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
+ = f.select :notification_email, @user.all_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 712eb2a4573..1f311e9a4a4 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Notifications"
+- page_title _('Notifications')
- @content_class = "limit-container-width" unless fluid_layout
%div
@@ -14,17 +14,15 @@
%h4.prepend-top-0
= page_title
%p
- You can specify notification level per group or per project.
+ = _('You can specify notification level per group or per project.')
%p
- By default, all projects and groups will use the global notifications setting.
+ = _('By default, all projects and groups will use the global notifications setting.')
.col-lg-8
%h5.prepend-top-0
- Global notification settings
+ = _('Global notification settings')
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
- .form-group
- = f.label :notification_email, class: "label-bold"
- = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
+ = render_if_exists 'profiles/notifications/email_settings', form: f
= label_tag :global_notification_level, "Global notification level", class: "label-bold"
%br
@@ -37,19 +35,18 @@
= form_for @user, url: profile_notifications_path, method: :put do |f|
%label{ for: 'user_notified_of_own_activity' }
= f.check_box :notified_of_own_activity
- %span Receive notifications about your own activity
+ %span= _('Receive notifications about your own activity')
%hr
%h5
- Groups (#{@group_notifications.count})
+ = _('Groups (%{count})') % { count: @group_notifications.count }
%div
- %ul.bordered-list
- - @group_notifications.each do |setting|
- = render 'group_settings', setting: setting, group: setting.source
+ - @group_notifications.each do |setting|
+ = render 'group_settings', setting: setting, group: setting.source
%h5
- Projects (#{@project_notifications.count})
+ = _('Projects (%{count})') % { count: @project_notifications.count }
%p.account-well
- To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.
+ = _('To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.')
.append-bottom-default
%ul.bordered-list
- @project_notifications.each do |setting|
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 0b4b9841ea1..ac8c31189d0 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Edit Password"
-- page_title "Password"
+- breadcrumb_title _('Edit Password')
+- page_title _('Password')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -7,28 +7,29 @@
%h4.prepend-top-0
= page_title
%p
- After a successful password update, you will be redirected to the login page where you can log in with your new password.
+ = _('After a successful password update, you will be redirected to the login page where you can log in with your new password.')
.col-lg-8
%h5.prepend-top-0
- Change your password
- - unless @user.password_automatically_set?
- or recover your current one
+ - if @user.password_automatically_set
+ = _('Change your password')
+ - else
+ = _('Change your password or recover your current one')
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
= form_errors(@user)
- unless @user.password_automatically_set?
.form-group
- = f.label :current_password, class: 'label-bold'
+ = f.label :current_password, _('Current password'), class: 'label-bold'
= f.password_field :current_password, required: true, class: 'form-control'
%p.form-text.text-muted
- You must provide your current password in order to change it.
+ = _('You must provide your current password in order to change it.')
.form-group
- = f.label :password, 'New password', class: 'label-bold'
+ = f.label :password, _('New password'), class: 'label-bold'
= f.password_field :password, required: true, class: 'form-control'
.form-group
- = f.label :password_confirmation, class: 'label-bold'
+ = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
- = f.submit 'Save password', class: "btn btn-success append-right-10"
+ = f.submit _('Save password'), class: "btn btn-success append-right-10"
- unless @user.password_automatically_set?
- = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
+ = link_to _('I forgot my password'), reset_profile_password_path, method: :put, class: "account-btn-link"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 4b84835429c..ce60455ab89 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -13,13 +13,18 @@
- unless @user.password_automatically_set?
.form-group.row
- = f.label :current_password, class: 'col-form-label col-sm-2'
- .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :current_password, _('Current password')
+ .col-sm-10
+ = f.password_field :current_password, required: true, class: 'form-control'
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
- .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password, _('New password')
+ .col-sm-10
+ = f.password_field :password, required: true, class: 'form-control'
.form-group.row
- = f.label :password_confirmation, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :password_confirmation, _('Password confirmation')
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index bfe1c3ddf33..4ebfaff0860 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,11 +1,12 @@
-- page_title 'Preferences'
+- page_title _('Preferences')
- @content_class = "limit-container-width" unless fluid_layout
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4.application-theme
%h4.prepend-top-0
= s_('Preferences|Navigation theme')
- %p Customize the appearance of the application header and navigation sidebar.
+ %p
+ = s_('Preferences|Customize the appearance of the application header and navigation sidebar.')
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
@@ -18,11 +19,11 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Syntax highlighting theme
+ = s_('Preferences|Syntax highlighting theme')
%p
- This setting allows you to customize the appearance of the syntax.
+ = s_('Preferences|This setting allows you to customize the appearance of the syntax.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
.col-lg-8.syntax-theme
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
@@ -35,31 +36,31 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Behavior
+ = s_('Preferences|Behavior')
%p
- This setting allows you to customize the behavior of the system layout and default views.
+ = s_('Preferences|This setting allows you to customize the behavior of the system layout and default views.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
.col-lg-8
.form-group
= f.label :layout, class: 'label-bold' do
- Layout width
+ = s_('Preferences|Layout width')
= f.select :layout, layout_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose between fixed (max. 1280px) and fluid (100%) application layout.
+ = s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
- Default dashboard
+ = s_('Preferences|Default dashboard')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
- Project overview content
+ = s_('Preferences|Project overview content')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose what content you want to see on a project’s overview page.
+ = s_('Preferences|Choose what content you want to see on a project’s overview page.')
.col-sm-12
%hr
@@ -82,5 +83,31 @@
= f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week')
= f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
+ - if Feature.enabled?(:user_time_settings)
+ .col-sm-12
+ %hr
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0= s_('Preferences|Time preferences')
+ %p= s_('Preferences|These settings will update how dates and times are displayed for you.')
+ .col-lg-8
+ .form-group
+ %h5= s_('Preferences|Time format')
+ .checkbox-icon-inline-wrapper.form-check
+ - time_format_label = capture do
+ = s_('Preferences|Display time in 24-hour format')
+ = f.check_box :time_format_in_24h, class: 'form-check-input'
+ = f.label :time_format_in_24h do
+ = time_format_label
+ %h5= s_('Preferences|Time display')
+ .checkbox-icon-inline-wrapper.form-check
+ - time_display_label = capture do
+ = s_('Preferences|Use relative times')
+ = f.check_box :time_display_relative, class: 'form-check-input'
+ = f.label :time_display_relative do
+ = time_display_label
+ .text-muted
+ = s_('Preferences|For example: 30 mins ago.')
+ .col-lg-4.profile-settings-sidebar
+ .col-lg-8
.form-group
= f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 4d3d92d09c0..e36d5192a29 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -47,9 +47,9 @@
- if @user.status
= emoji_icon @user.status.emoji
%span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) }
- = sprite_icon('emoji_slightly_smiling_face', css_class: 'award-control-icon-neutral')
- = sprite_icon('emoji_smiley', css_class: 'award-control-icon-positive')
- = sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive')
+ = sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
+ = sprite_icon('smiley', css_class: 'award-control-icon-positive')
+ = sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = button_tag type: :button,
id: 'js-clear-user-status-button',
class: 'clear-user-status btn has-tooltip',
@@ -64,6 +64,18 @@
prepend: emoji_button,
append: reset_message_button,
placeholder: s_("Profiles|What's your status?")
+ - if Feature.enabled?(:user_time_settings)
+ %hr
+ .row.user-time-preferences
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0= s_("Profiles|Time settings")
+ %p= s_("Profiles|You can set your current timezone here")
+ .col-lg-8
+ -# TODO: might need an entry in user/profile.md to describe some of these settings
+ -# https://gitlab.com/gitlab-org/gitlab-ce/issues/60070
+ %h5= ("Time zone")
+ = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown input-lg', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
+ %input.hidden{ :type => 'hidden', :id => 'user_timezone', :name => 'user[timezone]', value: @user.timezone }
%hr
.row
@@ -80,21 +92,10 @@
= f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name' },
help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) }
- else
- = f.text_field :name, label: 'Full name', required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
- = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
+ = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
+ = f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
- - if @user.read_only_attribute?(:email)
- = f.text_field :email, required: true, class: 'input-lg', readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) }
- - else
- = f.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?),
- help: user_email_help_text(@user)
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
- { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
- control_class: 'select2 input-lg'
- - commit_email_docs_link = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
- = f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
- { help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } },
- control_class: 'select2 input-lg'
+ = render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
= f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
= f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username")
@@ -102,18 +103,18 @@
- if @user.read_only_attribute?(:location)
= f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
- = f.text_field :location, class: 'input-lg', placeholder: s_("Profiles|City, country")
- = f.text_field :organization, class: 'input-md', help: s_("Profiles|Who you represent or work for")
- = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
+ = f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country")
+ = f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for")
+ = f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr
- %h5= ("Private profile")
+ %h5= s_("Private profile")
.checkbox-icon-inline-wrapper
- private_profile_label = capture do
= s_("Profiles|Don't display activity-related personal information on your profiles")
- = f.check_box :private_profile, label: private_profile_label
+ = f.check_box :private_profile, label: private_profile_label, inline: true, wrapper_class: 'mr-0'
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
%h5= s_("Profiles|Private contributions")
- = f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
+ = f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true
.help-block
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
.prepend-top-default.append-bottom-default
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index 759d39cf5f5..be0af977011 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -1,8 +1,6 @@
%p.slead
- Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
- time each to regain access to your account. Please save them in a safe place, or you
- %b will
- lose access to your account.
+ - lose_2fa_message = _('Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account.') % { b_start:'<b>', b_end:'</b>' }
+ = lose_2fa_message.html_safe
.codes.card
%ul
@@ -11,5 +9,5 @@
%span.monospace= code
.d-flex
- = link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10'
- = link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
+ = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/profiles/two_factor_auths/codes.html.haml b/app/views/profiles/two_factor_auths/codes.html.haml
index addf356697a..53907ebffab 100644
--- a/app/views/profiles/two_factor_auths/codes.html.haml
+++ b/app/views/profiles/two_factor_auths/codes.html.haml
@@ -1,5 +1,6 @@
-- page_title 'Recovery Codes', 'Two-factor Authentication'
+- page_title _('Recovery Codes'), _('Two-factor Authentication')
-%h3.page-title Two-factor Authentication Recovery codes
+%h3.page-title
+ = _('Two-factor Authentication Recovery codes')
%hr
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml
index e330aadac13..973eb8136c4 100644
--- a/app/views/profiles/two_factor_auths/create.html.haml
+++ b/app/views/profiles/two_factor_auths/create.html.haml
@@ -1,6 +1,6 @@
-- page_title 'Two-factor Authentication', 'Account'
+- page_title _('Two-factor Authentication'), _('Account')
.alert.alert-success
- Congratulations! You have enabled Two-factor Authentication!
+ = _('Congratulations! You have enabled Two-factor Authentication!')
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index d986c566928..5501e63e027 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,72 +1,68 @@
-- page_title 'Two-Factor Authentication', 'Account'
-- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path)
+- page_title _('Two-Factor Authentication'), _('Account')
+- add_to_breadcrumbs(_('Two-Factor Authentication'), profile_account_path)
- @content_class = "limit-container-width" unless fluid_layout
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Two-Factor Authenticator
+ = _('Register Two-Factor Authenticator')
%p
- Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
+ = _('Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
.col-lg-8
- if current_user.two_factor_otp_enabled?
%p
- You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
+ = _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.")
%p
- If you lose your recovery codes you can generate new ones, invalidating all previous codes.
+ = _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.')
%div
- = link_to 'Disable two-factor authentication', profile_two_factor_auth_path,
+ = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path,
method: :delete,
- data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
+ data: { confirm: _('Are you sure? This will invalidate your registered applications and U2F devices.') },
class: 'btn btn-danger append-right-10'
= form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f|
- = submit_tag 'Regenerate recovery codes', class: 'btn'
+ = submit_tag _('Regenerate recovery codes'), class: 'btn'
- else
%p
- Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a>
- or Google Authenticator from your application repository and scan this QR code.
- More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
+ - help_link_start = '<a href="%{url}" target="_blank">' % { url: help_page_path('user/profile/account/two_factor_authentication') }
+ - register_2fa_token = _('Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}.') % { free_otp_link:'<a href="https://freeotp.github.io/">FreeOTP</a>', help_link_start:help_link_start, help_link_end:'</a>' }
+ = register_2fa_token.html_safe
.row.append-bottom-10
.col-md-4
= raw @qr_code
.col-md-8
.account-well
%p.prepend-top-0.append-bottom-0
- Can't scan the code?
+ = _("Can't scan the code?")
%p.prepend-top-0.append-bottom-0
- To add the entry manually, provide the following details to the application on your phone.
+ = _('To add the entry manually, provide the following details to the application on your phone.')
%p.prepend-top-0.append-bottom-0
- Account:
- = @account_string
+ = _('Account: %{account}') % { account: @account_string }
%p.prepend-top-0.append-bottom-0
- Key:
- = current_user.otp_secret.scan(/.{4}/).join(' ')
+ = _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') }
%p.two-factor-new-manual-content
- Time based: Yes
+ = _('Time based: Yes')
= form_tag profile_two_factor_auth_path, method: :post do |f|
- if @error
.alert.alert-danger
= @error
.form-group
- = label_tag :pin_code, nil, class: "label-bold"
+ = label_tag :pin_code, _('Pin code'), class: "label-bold"
= text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default
- = submit_tag 'Register with two-factor app', class: 'btn btn-success'
+ = submit_tag _('Register with two-factor app'), class: 'btn btn-success'
%hr
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Universal Two-Factor (U2F) Device
+ = _('Register Universal Two-Factor (U2F) Device')
%p
- Use a hardware device to add the second factor of authentication.
+ = _('Use a hardware device to add the second factor of authentication.')
%p
- As U2F devices are only supported by a few browsers, we require that you set up a
- two-factor authentication app before a U2F device. That way you'll always be able to
- log in - even when you're using an unsupported browser.
+ = _("As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser.")
.col-lg-8
- if @u2f_registration.errors.present?
= form_errors(@u2f_registration)
@@ -74,7 +70,8 @@
%hr
- %h5 U2F Devices (#{@u2f_registrations.length})
+ %h5
+ = _('U2F Devices (%{length})') % { length: @u2f_registrations.length }
- if @u2f_registrations.present?
.table-responsive
@@ -85,16 +82,16 @@
%col{ width: "20%" }
%thead
%tr
- %th Name
- %th Registered On
+ %th= _('Name')
+ %th= s_('2FADevice|Registered On')
%th
%tbody
- @u2f_registrations.each do |registration|
%tr
- %td= registration.name.presence || "<no name set>"
+ %td= registration.name.presence || _("<no name set>")
%td= registration.created_at.to_date.to_s(:medium)
- %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." }
+ %td= link_to _('Delete'), profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') }
- else
.settings-message.text-center
- You don't have any U2F devices registered yet.
+ = _("You don't have any U2F devices registered yet.")
diff --git a/app/views/projects/_classification_policy_settings.html.haml b/app/views/projects/_classification_policy_settings.html.haml
new file mode 100644
index 00000000000..5a766ab024f
--- /dev/null
+++ b/app/views/projects/_classification_policy_settings.html.haml
@@ -0,0 +1,6 @@
+- if ::Gitlab::ExternalAuthorization.enabled?
+ .form-group.col-md-9
+ = f.label :external_authorization_classification_label, _('Classification Label (optional)'), class: 'label-bold'
+ = f.text_field :external_authorization_classification_label, class: "form-control"
+ %span.form-text.text-muted
+ = external_classification_label_help_message
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 409b6dba9ca..1056977886a 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -1,42 +1,33 @@
- return unless Gitlab::CurrentSettings.project_export_enabled?
- project = local_assigns.fetch(:project)
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-export-project{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Export project
- %button.btn.js-settings-toggle{ type: 'button' }
- = 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
- .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 services
- %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
- %li LFS objects
- %p
- The following items will NOT be exported:
- %ul
- %li Job traces and artifacts
- %li Container registry images
- %li CI variables
- %li Webhooks
- %li Any encrypted tokens
- %p
- Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- - if project.export_status == :finished
- = 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"
+.sub-section
+ %h4= _('Export project')
+ %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.')
+
+ .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 services')
+ %li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
+ %li= _('LFS objects')
+ %p= _('The following items will NOT be exported:')
+ %ul
+ %li= _('Job traces and artifacts')
+ %li= _('Container registry images')
+ %li= _('CI variables')
+ %li= _('Webhooks')
+ %li= _('Any encrypted tokens')
+ %p= _('Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.')
+ - if project.export_status == :finished
+ = 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 22a721ee9ad..2b0c3985755 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -13,7 +13,12 @@
= render 'shared/commit_well', commit: commit, ref: ref, project: project
- if is_project_overview
- .project-buttons.append-bottom-default
+ .project-buttons.append-bottom-default{ class: ("js-keep-hidden-on-navigation" if vue_file_list_enabled?) }
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- = render 'projects/tree/tree_content', tree: @tree, content_url: content_url
+ - if vue_file_list_enabled?
+ #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
+ - if @tree.readme
+ = render "projects/tree/readme", readme: @tree.readme
+ - else
+ = render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index 7a5fff96676..b2dab0b5348 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -5,4 +5,6 @@
- if current_user && can?(current_user, :download_code, project)
= render 'shared/no_ssh'
= render 'shared/no_password'
- = render 'shared/auto_devops_implicitly_enabled_banner', project: project
+ - unless project.empty_repo?
+ = render 'shared/auto_devops_implicitly_enabled_banner', project: project
+ = render_if_exists 'projects/above_size_limit_warning', project: project
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index bba303c906c..9f5241344a7 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,6 +1,7 @@
- empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project)
-.project-home-panel{ class: ("empty-project" if empty_repo) }
+- max_project_topic_length = 15
+.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
.avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
@@ -11,7 +12,7 @@
= @project.name
%span.visibility-icon.text-secondary.prepend-left-4.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
- .home-panel-metadata.d-flex.align-items-center.text-secondary
+ .home-panel-metadata.d-flex.flex-wrap.text-secondary
- if can?(current_user, :read_project, @project)
%span.text-secondary
= s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
@@ -19,15 +20,21 @@
%span.access-request-links.prepend-left-8
= render 'shared/members/access_request_links', source: @project
- if @project.tag_list.present?
- %span.home-panel-topic-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.tag_list.join(', ') : nil }
+ %span.home-panel-topic-list.mt-2.w-100.d-inline-flex
= sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
- @project.topics_to_show.each do |topic|
- %a{ class: 'badge badge-pill badge-secondary append-right-5 str-truncated-30', href: explore_projects_path(tag: topic) }
- = topic.titleize
+ - project_topics_classes = "badge badge-pill badge-secondary append-right-5"
+ - explore_project_topic_path = explore_projects_path(tag: topic)
+ - if topic.length > max_project_topic_length
+ %a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path }
+ = topic.titleize
+ - else
+ %a{ class: project_topics_classes, href: explore_project_topic_path }
+ = topic.titleize
- if @project.has_extra_topics?
- .text-nowrap
+ .text-nowrap.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.topics_not_shown.join(', ') : nil }
= _("+ %{count} more") % { count: @project.count_of_extra_topics_not_shown }
@@ -50,7 +57,10 @@
- if can?(current_user, :download_code, @project)
%nav.project-stats
.nav-links.quick-links
- = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
+ - if @project.empty_repo?
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
+ - else
+ = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
.home-panel-home-desc.mt-1
- if @project.description.present?
@@ -70,6 +80,8 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) }
+ = render_if_exists "projects/home_mirror"
+
- if @project.badges.present?
.project-badges.mb-2
- @project.badges.each do |badge|
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 2b425f18389..28d4f8eb201 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -8,61 +8,67 @@
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
- = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_export" } do
+ = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do
= icon('gitlab', text: 'GitLab export')
- if github_import_enabled?
%div
- = link_to new_import_github_path, class: 'btn js-import-github', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "github" } do
+ = link_to new_import_github_path, class: 'btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do
= icon('github', text: 'GitHub')
- if bitbucket_import_enabled?
%div
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_cloud" } do
+ **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do
= icon('bitbucket', text: 'Bitbucket Cloud')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
- if bitbucket_server_import_enabled?
%div
- = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_server" } do
+ = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do
= icon('bitbucket-square', text: 'Bitbucket Server')
%div
- if gitlab_import_enabled?
%div
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_com" } do
+ **tracking_attrs(track_label, 'click_button', 'gitlab_com') do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
- if google_code_import_enabled?
%div
- = link_to new_import_google_code_path, class: 'btn import_google_code', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "google_code" } do
+ = link_to new_import_google_code_path, class: 'btn import_google_code', **tracking_attrs(track_label, 'click_button', 'google_code') do
= icon('google', text: 'Google Code')
- if fogbugz_import_enabled?
%div
- = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "fogbugz" } do
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do
= icon('bug', text: 'Fogbugz')
- if gitea_import_enabled?
%div
- = link_to new_import_gitea_path, class: 'btn import_gitea', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitea" } do
- = custom_icon('go_logo')
+ = link_to new_import_gitea_path, class: 'btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do
+ = custom_icon('gitea_logo')
Gitea
- if git_import_enabled?
%div
- %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active', data: { toggle_open_class: 'active', track_label: "#{track_label}" , track_event: "click_button", track_property: "repo_url" } } }
+ %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') }
= icon('git', text: 'Repo by URL')
- if manifest_import_enabled?
%div
- = link_to new_import_manifest_path, class: 'btn import_manifest', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "manifest_file" } do
+ = link_to new_import_manifest_path, class: 'btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do
= icon('file-text-o', text: 'Manifest file')
+ - if phabricator_import_enabled?
+ %div
+ = link_to new_import_phabricator_path, class: 'btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do
+ = custom_icon('issues')
+ = _("Phabricator Tasks")
+
+
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
= form_for @project, html: { class: 'new_project' } do |f|
%hr
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 03ba1104507..10575aa68b1 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -7,22 +7,22 @@
= _('This merge request is locked.')
= _('Only project members can comment.')
-.md-area
+.md-area.position-relative
.md-header
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
%button.js-md-write-button{ tabindex: -1 }
- Write
+ = _("Write")
%li.md-header-tab
%button.js-md-preview-button{ tabindex: -1 }
- Preview
+ = _("Preview")
%li.md-header-toolbar.active
= render 'projects/blob/markdown_buttons', show_fullscreen_button: true
.md-write-holder
= yield
- .md.md-preview-holder.js-md-preview.hide.md-preview{ data: { url: url } }
+ .md.md-preview-holder.js-md-preview.hide{ data: { url: url } }
.referenced-commands.hide
- if referenced_users
diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml
new file mode 100644
index 00000000000..c21d333f21a
--- /dev/null
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -0,0 +1,19 @@
+- form = local_assigns.fetch(:form)
+
+.form-group
+ %b= s_('ProjectSettings|Merge checks')
+ %p.text-secondary= s_('ProjectSettings|These checks must pass before merge requests can be merged')
+ .form-check.mb-2.builds-feature
+ = form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
+ = form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
+ = s_('ProjectSettings|Pipelines must succeed')
+ .descr.text-secondary
+ = s_('ProjectSettings|Pipelines need to be configured to enable this feature.')
+ = link_to icon('question-circle'),
+ help_page_path('ci/merge_request_pipelines/index.md',
+ anchor: 'pipelines-for-merge-requests'),
+ target: '_blank'
+ .form-check.mb-2
+ = form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input'
+ = form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do
+ = s_('ProjectSettings|All discussions must be resolved')
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index 935581643cd..47c311f42d0 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -1,35 +1,33 @@
- form = local_assigns.fetch(:form)
.form-group
- = label_tag :merge_method_merge, class: 'label-bold' do
- Merge method
- .form-check
+ %b= s_('ProjectSettings|Merge method')
+ %p.text-secondary= s_('ProjectSettings|This will dictate the commit history when you merge a merge request')
+ .form-check.mb-2
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_merge, class: 'form-check-label' do
- %strong Merge commit
- %br
- %span.descr
- A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
+ = s_('ProjectSettings|Merge commit')
+ .descr.text-secondary
+ = s_('ProjectSettings|Every merge creates a merge commit')
- .form-check
+ .form-check.mb-2
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_rebase_merge, class: 'form-check-label' do
- %strong Merge commit with semi-linear history
- %br
- %span.descr
- A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible.
- This way you could make sure that if this merge request would build, after merging to target branch it would also build.
- %br
- %span.descr
- When fast-forward merge is not possible, the user is given the option to rebase.
+ = s_('ProjectSettings|Merge commit with semi-linear history')
+ .descr.text-secondary
+ = s_('ProjectSettings|Every merge creates a merge commit')
+ %br
+ = s_('ProjectSettings|Fast-forward merges only')
+ %br
+ = s_('ProjectSettings|When conflicts arise the user is given the option to rebase')
- .form-check
+ .form-check.mb-2
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff form-check-input"
= label_tag :project_merge_method_ff, class: 'form-check-label' do
- %strong Fast-forward merge
- %br
- %span.descr
- No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
- %br
- %span.descr
- When fast-forward merge is not possible, the user is given the option to rebase.
+ = s_('ProjectSettings|Fast-forward merge')
+ .descr.text-secondary
+ = s_('ProjectSettings|No merge commits are created')
+ %br
+ = s_('ProjectSettings|Fast-forward merges only')
+ %br
+ = s_('ProjectSettings|When conflicts arise the user is given the option to rebase')
diff --git a/app/views/projects/_merge_request_merge_options_settings.html.haml b/app/views/projects/_merge_request_merge_options_settings.html.haml
new file mode 100644
index 00000000000..5ab475822de
--- /dev/null
+++ b/app/views/projects/_merge_request_merge_options_settings.html.haml
@@ -0,0 +1,14 @@
+- form = local_assigns.fetch(:form)
+
+.form-group
+ %b= s_('ProjectSettings|Merge options')
+ %p.text-secondary= s_('ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed')
+ = render_if_exists 'projects/merge_pipelines_settings', form: form
+ .form-check.mb-2
+ = form.check_box :resolve_outdated_diff_discussions, class: 'form-check-input'
+ = form.label :resolve_outdated_diff_discussions, class: 'form-check-label' do
+ = s_('ProjectSettings|Automatically resolve merge request diff discussions when they become outdated')
+ .form-check.mb-2
+ = form.check_box :printing_merge_request_link_enabled, class: 'form-check-input'
+ = form.label :printing_merge_request_link_enabled, class: 'form-check-label' do
+ = s_('ProjectSettings|Show link to create/view merge request when pushing from the command line')
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
deleted file mode 100644
index f178c94e008..00000000000
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- form = local_assigns.fetch(:form)
-
-.form-group
- .form-check.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) }
- = form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
- = form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
- %strong Only allow merge requests to be merged if the pipeline succeeds
- %br
- %span.descr
- Pipelines need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank'
- .form-check
- = form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input'
- = form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do
- %strong Only allow merge requests to be merged if all discussions are resolved
- .form-check
- = form.check_box :resolve_outdated_diff_discussions, class: 'form-check-input'
- = form.label :resolve_outdated_diff_discussions, class: 'form-check-label' do
- %strong Automatically resolve merge request diff discussions when they become outdated
- .form-check
- = form.check_box :printing_merge_request_link_enabled, class: 'form-check-input'
- = form.label :printing_merge_request_link_enabled, class: 'form-check-label' do
- %strong Show link to create/view merge request when pushing from the command line
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index c80e831dd33..f2ba38387a3 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -2,4 +2,6 @@
= render 'projects/merge_request_merge_method_settings', project: @project, form: form
-= render 'projects/merge_request_merge_settings', form: form
+= render 'projects/merge_request_merge_options_settings', project: @project, form: form
+
+= render 'projects/merge_request_merge_checks_settings', project: @project, form: form
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 276363df7da..e423631ec99 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,4 +1,4 @@
-- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
+- visibility_level = selected_visibility_level(@project, params.dig(:project, :visibility_level))
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
- hide_init_with_readme = local_assigns.fetch(:hide_init_with_readme, false)
- track_label = local_assigns.fetch(:track_label, 'blank_project')
@@ -12,21 +12,21 @@
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
%span= s_("Project URL")
- .input-group
+ .input-group.flex-nowrap
- if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
= root_url
- namespace_id = namespace_id_from(params)
= f.select(:namespace_id,
- namespaces_options(namespace_id || :current_user,
- display_path: true,
- extra_group: namespace_id),
+ namespaces_options_with_developer_maintainer_access(selected: namespace_id,
+ display_path: true,
+ extra_group: namespace_id),
{},
- { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
+ { class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
- else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0
#{user_url(current_user.username)}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
@@ -54,7 +54,7 @@
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
- = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
+ = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong Initialize repository with a README
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index de4653dad2c..6103d86bf5a 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -1,8 +1,7 @@
- if @wiki_home.present?
%div{ class: container_class }
- .prepend-top-default.append-bottom-default
- .wiki
- = render_wiki_content(@wiki_home)
+ .md.md-file.prepend-top-default.append-bottom-default
+ = render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] }
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index afc40ca4eab..c502b392384 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -8,6 +8,7 @@
= f.text_area attr,
class: classes,
placeholder: placeholder,
+ dir: 'auto',
data: { supports_quick_actions: supports_quick_actions,
supports_autocomplete: supports_autocomplete }
- else
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 09295940529..6a7cb1499c5 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -4,7 +4,7 @@
= render "projects/jobs/header"
- add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project))
-- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
+- add_to_breadcrumbs("##{@build.id}", project_job_path(@project, @build))
.tree-holder
.nav-block
diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml
index 4bef45932d0..7ed71a7d43c 100644
--- a/app/views/projects/blob/_header_content.html.haml
+++ b/app/views/projects/blob/_header_content.html.haml
@@ -1,12 +1,12 @@
.file-header-content
= blob_icon blob.mode, blob.name
- %strong.file-title-name
+ %strong.file-title-name.qa-file-title-name
= blob.name
= copy_file_path_button(blob.path)
- %small
+ %small.mr-1
= number_to_human_size(blob.raw_size)
- if blob.stored_externally? && blob.external_storage == :lfs
diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml
index 1d6acd86108..28d1ff97825 100644
--- a/app/views/projects/blob/_markdown_buttons.html.haml
+++ b/app/views/projects/blob/_markdown_buttons.html.haml
@@ -1,13 +1,13 @@
.md-header-toolbar.active
- = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
- = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
- = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
- = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
- = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
- = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
- = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
- = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
- = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
+ = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: _("Add bold text") })
+ = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: _("Add italic text") })
+ = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
+ = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
+ = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: _("Add a link") })
+ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: _("Add a bullet list") })
+ = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") })
+ = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") })
+ = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: _("Add a table") })
- if show_fullscreen_button
- %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
+ %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: _("Go full screen"), data: { container: "body" } }
= sprite_icon("screen-full")
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index ea7a71792a3..4f3db61f688 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -15,14 +15,14 @@
%a{ href: "#", data: { linenumber: line_number_old }, disabled: true }
%td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "#", data: { linenumber: line_number_new }, disabled: true }
- %td.line_content.noteable_line{ class: line_class }= line
+ %td.line_content{ class: line_class }= line
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
%a{ href: "##{line_number_old}", data: { linenumber: line_number_old }, disabled: true }
- %td.line_content.noteable_line.left-side{ class: line_class }= line
+ %td.line_content.left-side{ class: line_class }= line
%td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "##{line_number_new}", data: { linenumber: line_number_new }, disabled: true }
- %td.line_content.noteable_line.right-side{ class: line_class }= line
+ %td.line_content.right-side{ class: line_class }= line
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
%tr.line_holder{ id: @form.to, class: line_class }
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index 66687f087ff..3e893343165 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,21 +1,20 @@
-.diff-file.file-holder
- .diff-content
- - if markup?(@blob.name)
- .file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
- = markup(@blob.name, @content)
- - else
- .file-content.code.js-syntax-highlight
- - unless @diff_lines.empty?
- %table.text-file
- - @diff_lines.each do |line|
- %tr.line_holder{ class: "#{line.type}" }
- - if line.type == "match"
- %td.old_line.diff-line-num= "..."
- %td.new_line.diff-line-num= "..."
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num
- %td.new_line.diff-line-num
- %td.line_content{ class: "#{line.type}" }= diff_line_content(line.text)
- - else
- .nothing-here-block No changes.
+- if markup?(@blob.name)
+ .file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ = markup(@blob.name, @content)
+- else
+ .diff-file
+ .diff-content
+ - unless @diff_lines.empty?
+ %table.text-file.code.js-syntax-highlight
+ - @diff_lines.each do |line|
+ %tr.line_holder{ class: line.type }
+ - if line.type == "match"
+ %td.old_line.diff-line-num.match= "..."
+ %td.new_line.diff-line-num.match= "..."
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ class: line.type }
+ %td.new_line.diff-line-num{ class: line.type }
+ %td.line_content{ class: line.type }= diff_line_content(line.text)
+ - else
+ .nothing-here-block No changes.
diff --git a/app/views/projects/blob/viewers/_dependency_manager.html.haml b/app/views/projects/blob/viewers/_dependency_manager.html.haml
index 87aa7c1dbf8..5970d41fdab 100644
--- a/app/views/projects/blob/viewers/_dependency_manager.html.haml
+++ b/app/views/projects/blob/viewers/_dependency_manager.html.haml
@@ -3,9 +3,4 @@
This project manages its dependencies using
%strong= viewer.manager_name
- - if viewer.package_name
- and defines a #{viewer.package_type} named
- %strong<
- = link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
-
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index 1a77eb078be..abc74b66e90 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -1,4 +1,4 @@
- blob = viewer.blob
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
-.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+.file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(blob.name, blob.data, context)
diff --git a/app/views/projects/blob/viewers/_route_map.html.haml b/app/views/projects/blob/viewers/_route_map.html.haml
index 6d6bd79bc3c..07b9378ba97 100644
--- a/app/views/projects/blob/viewers/_route_map.html.haml
+++ b/app/views/projects/blob/viewers/_route_map.html.haml
@@ -6,4 +6,4 @@
This Route Map is invalid:
= viewer.validation_message
-= link_to 'Learn more', help_page_path('ci/environments', anchor: 'go-directly-from-source-files-to-public-pages-on-the-environment')
+= link_to 'Learn more', help_page_path('ci/environments', anchor: 'going-from-source-files-to-public-pages')
diff --git a/app/views/projects/blob/viewers/_route_map_loading.html.haml b/app/views/projects/blob/viewers/_route_map_loading.html.haml
index a5f73fb0197..f11c047e85a 100644
--- a/app/views/projects/blob/viewers/_route_map_loading.html.haml
+++ b/app/views/projects/blob/viewers/_route_map_loading.html.haml
@@ -1,4 +1,4 @@
= icon('spinner spin fw')
Validating Route Map…
-= link_to 'Learn more', help_page_path('ci/environments', anchor: 'go-directly-from-source-files-to-public-pages-on-the-environment')
+= link_to 'Learn more', help_page_path('ci/environments', anchor: 'going-from-source-files-to-public-pages')
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 91c51d5e091..1074cd6bf4e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -10,7 +10,7 @@
.branch-info
.branch-title
= sprite_icon('fork', size: 12)
- = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do
+ = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8 qa-branch-name' do
= branch.name
- if branch.name == @repository.root_ref
%span.badge.badge-primary.prepend-left-5 default
@@ -22,6 +22,8 @@
%span.badge.badge-success.prepend-left-5
= s_('Branches|protected')
+ = render_if_exists 'projects/branches/diverged_from_upstream'
+
.block-truncated
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 7892019bb15..e33e9509e3a 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,9 +1,9 @@
-.branch-commit
+.branch-commit.cgray
.icon-container.commit-icon
= custom_icon("icon_commit")
= link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
&middot;
%span.str-truncated
- = link_to_markdown commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ = link_to_markdown commit.title, project_commit_path(project, commit.id), class: "commit-row-message cgray"
&middot;
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 43f1cd01b67..d270e461ac8 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,5 +1,6 @@
- @no_container = true
- page_title _('Branches')
+- add_to_breadcrumbs(_('Repository'), project_tree_path(@project))
%div{ class: container_class }
.top-area.adjust
@@ -44,6 +45,8 @@
= link_to new_project_branch_path(@project), class: 'btn btn-success' do
= s_('Branches|New branch')
+ = render_if_exists 'projects/commits/mirror_status'
+
- if can?(current_user, :admin_project, @project)
- project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
.row-content-block
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
index 159d9e44e17..09f05b30433 100644
--- a/app/views/projects/buttons/_clone.html.haml
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -7,7 +7,7 @@
= sprite_icon("arrow-down", css_class: "icon")
%ul.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options
- if ssh_enabled?
- %li.pb-2
+ %li
%label.label-bold
= _('Clone with SSH')
.input-group
@@ -16,7 +16,7 @@
= clipboard_button(target: '#ssh_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
- if http_enabled?
- %li
+ %li.pt-2
%label.label-bold
= _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase }
.input-group
@@ -24,5 +24,6 @@
.input-group-append
= clipboard_button(target: '#http_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
+ = render_if_exists 'projects/buttons/kerberos_clone_field'
= render_if_exists 'shared/geo_info_modal', project: project
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 4eb53faa6ff..4762045ee96 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -7,31 +7,22 @@
= sprite_icon('download')
%span.sr-only= _('Select Archive Format')
= sprite_icon("arrow-down")
- %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
- %li.dropdown-header
- #{ _('Source code') }
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
- %span= _('Download zip')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
- %span= _('Download tar.gz')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
- %span= _('Download tar.bz2')
- %li
- = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
- %span= _('Download tar')
-
+ .dropdown-menu.dropdown-menu-right{ role: 'menu' }
+ %section
+ %h5.m-0.dropdown-bold-header= _('Download source code')
+ .dropdown-menu-content
+ = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
+ - if directory? && Feature.enabled?(:git_archive_path, default_enabled: true)
+ %section.border-top.pt-1.mt-1
+ %h5.m-0.dropdown-bold-header= _('Download this directory')
+ .dropdown-menu-content
+ = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
- if pipeline && pipeline.latest_builds_with_artifacts.any?
- %li.dropdown-header Artifacts
- - unless pipeline.latest?
- - latest_pipeline = project.pipeline_for(ref)
- %li
- .unclickable= ci_status_for_statuseable(latest_pipeline)
- %li.dropdown-header Previous Artifacts
- - pipeline.latest_builds_with_artifacts.each do |job|
- %li
- = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
- %span
- #{s_('DownloadArtifacts|Download')} '#{job.name}'
+ %section.border-top.pt-1.mt-1
+ %h5.m-0.dropdown-bold-header= _('Download artifacts')
+ - unless pipeline.latest?
+ %span.unclickable= ci_status_for_statuseable(project.pipeline_for(ref))
+ %h6.m-0.dropdown-header= _('Previous Artifacts')
+ %ul
+ - pipeline.latest_builds_with_artifacts.each do |job|
+ %li= link_to job.name, latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: ''
diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml
new file mode 100644
index 00000000000..d344167a6c5
--- /dev/null
+++ b/app/views/projects/buttons/_download_links.html.haml
@@ -0,0 +1,5 @@
+- formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']]
+
+.btn-group.ml-0.w-100
+ - formats.each do |(fmt, extra_class)|
+ = link_to fmt, project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}"
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 9d069c025ba..bdf7b933ab8 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -12,7 +12,7 @@
%td.status
= render "ci/status/badge", status: job.detailed_status(current_user), title: job.status_title
- %td.branch-commit
+ %td.branch-commit.cgray
- if can?(current_user, :read_build, job)
= link_to project_job_path(job.project, job) do
%span.build-link ##{job.id}
@@ -30,7 +30,7 @@
= custom_icon("icon_commit")
- if commit_sha
- = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha"
+ = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha mr-0"
- if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: _('Job is stuck. Check runners.'))
@@ -53,9 +53,10 @@
%span.badge.badge-info= _('manual')
- if pipeline_link
- %td
- = link_to pipeline_path(pipeline) do
+ %td.pipeline-link
+ = link_to pipeline_path(pipeline), class: 'has-tooltip', title: _('Pipeline ID (IID)') do
%span.pipeline-id ##{pipeline.id}
+ %span.pipeline-iid (##{pipeline.iid})
%span by
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index 888be4ee282..ed3c9890efd 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index a0db48bf8ff..ef2777e6601 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -81,7 +81,7 @@
= link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do
= ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') }
- = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
+ = link_to "##{last_pipeline.id} (##{last_pipeline.iid})", project_pipeline_path(@project, last_pipeline.id), class: "has-tooltip", title: _('Pipeline ID (IID)')
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 0d3c6e7027c..87b9920e8b4 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,10 +9,13 @@
- commit_status = commit.present(current_user: current_user).status_for(ref)
- link = commit_path(project, commit, merge_request: merge_request)
+
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.d-none.d-sm-block
- = author_avatar(commit, size: 36, has_tooltip: false)
+ = author_avatar(commit, size: 40, has_tooltip: false)
.commit-detail.flex-list
.commit-content.qa-commit-content
@@ -20,12 +23,9 @@
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
- %span.commit-row-message.d-block.d-sm-none
+ %span.commit-row-message.d-inline.d-sm-none
&middot;
= commit.short_id
- - if commit_status
- .d-block.d-sm-none
- = render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.js-toggle-button
= sprite_icon('ellipsis_h', size: 12)
@@ -35,12 +35,13 @@
- commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
+ = render_if_exists 'projects/commits/project_namespace', show_project_name: show_project_name, project: project
- if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8
= preserve(markdown_field(commit, :description))
- .commit-actions.flex-row.d-none.d-sm-flex
+ .commit-actions.flex-row
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
@@ -51,8 +52,8 @@
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
- .commit-sha-group
- .label.label-monospace
+ .commit-sha-group.d-none.d-sm-flex
+ .label.label-monospace.monospace
= commit.short_id
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index caaff082cc3..56bebeca581 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -3,6 +3,6 @@
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha"
&nbsp;
%span.str-truncated
- = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message")
+ = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message cgray")
.float-right
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 9d254463fb6..2db1efdd52f 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -30,6 +30,8 @@
= link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
+ = render_if_exists 'projects/commits/mirror_status'
+
%div{ id: dom_id(@project) }
%ol#commits-list.list-unstyled.content_list
= render 'commits', project: @project, ref: @ref
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index bdf021fd87f..59f0afd59e6 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -10,7 +10,7 @@
.wrapper{ "v-show" => "!isLoading && !hasError" }
.card
.card-header
- {{ __('Pipeline Health') }}
+ {{ __('Recent Project Activity') }}
.content-block
.container-fluid
.row
diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml
index ff6a9d49a61..59efcde5825 100644
--- a/app/views/projects/default_branch/_show.html.haml
+++ b/app/views/projects/default_branch/_show.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#default-branch-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 24d665761cc..fcf27351a21 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index f4c91377ecb..c84c376d57b 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -10,5 +10,5 @@
- actions.each do |action|
- next unless can?(current_user, :update_build, action)
%li
- = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow', class: 'btn' do
- %span= action.name.humanize
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
+ %span= action.name
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 282566eeadc..743aa60b3ba 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -1,17 +1,17 @@
.table-mobile-content
- .branch-commit
+ .branch-commit.cgray
- if deployment.ref
%span.icon-container
= deployment.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha"
+ = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha mr-0"
%p.commit-title.flex-truncate-parent
%span.flex-truncate-child
- if commit_title = deployment.commit_title
= author_avatar(deployment.commit, size: 20)
- = link_to_markdown commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
+ = link_to_markdown commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message cgray"
- else
= _("Can't find HEAD commit for this branch")
diff --git a/app/views/projects/deployments/_confirm_rollback_modal.html.haml b/app/views/projects/deployments/_confirm_rollback_modal.html.haml
new file mode 100644
index 00000000000..ff40e404e5f
--- /dev/null
+++ b/app/views/projects/deployments/_confirm_rollback_modal.html.haml
@@ -0,0 +1,23 @@
+- commit_sha = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha has-tooltip", title: h(deployment.commit_title)
+.modal.ws-normal.fade{ tabindex: -1, id: "confirm-rollback-modal-#{deployment.id}" }
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %h4.modal-title.d-flex.mw-100
+ - if deployment.last?
+ = s_("Environments|Re-deploy environment %{environment_name}?") % {environment_name: @environment.name}
+ - else
+ = s_("Environments|Rollback environment %{environment_name}?") % {environment_name: @environment.name}
+ .modal-body
+ - if deployment.last?
+ %p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
+ - else
+ %p
+ = s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
+ .modal-footer
+ = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
+ = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do
+ - if deployment.last?
+ = s_('Environments|Re-deploy')
+ - else
+ = s_('Environments|Rollback')
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 85bc8ec07e3..a11e23b6daa 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -18,7 +18,7 @@
- if deployment.user
%div
by
- = user_avatar(user: deployment.user, size: 20)
+ = user_avatar(user: deployment.user, size: 20, css_class: "mr-0 float-none")
.table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' }= _("Created")
diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml
index 1bd538a08ff..d6bf8d564de 100644
--- a/app/views/projects/deployments/_rollback.haml
+++ b/app/views/projects/deployments/_rollback.haml
@@ -1,7 +1,8 @@
- if can?(current_user, :create_deployment, deployment)
- tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment')
- = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do
+ = button_tag class: 'btn btn-default btn-build has-tooltip', type: 'button', data: { toggle: 'modal', target: "#confirm-rollback-modal-#{deployment.id}" }, title: tooltip do
- if deployment.last?
= sprite_icon('repeat')
- else
= sprite_icon('redo')
+ = render 'projects/deployments/confirm_rollback_modal', deployment: deployment
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index 68f74f702ea..590fcdb0234 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -1,2 +1,2 @@
.diff-content
- = render 'projects/diffs/viewer', viewer: diff_file.rich_viewer || diff_file.simple_viewer
+ = render 'projects/diffs/viewer', viewer: diff_file.viewer
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index ffdca500abe..d35443cca1e 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -30,7 +30,7 @@
= link_text
- else
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- %td.line_content.noteable_line{ class: type }<
+ %td.line_content{ class: type }<
- if email
%pre= line.rich_text
- else
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 4b1d4b3ea17..9587ea4696b 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,7 +1,7 @@
/ Side-by-side diff view
-.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
- %table
+.text-file{ data: diff_view_data }
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
@@ -23,7 +23,7 @@
- discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id }
- %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text)
+ %td.line_content.parallel.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.left-side
@@ -44,7 +44,7 @@
- discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id }
- %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text)
+ %td.line_content.parallel.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.right-side
diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml
index 6dffc7c4390..566dfe798c6 100644
--- a/app/views/projects/diffs/_replaced_image_diff.html.haml
+++ b/app/views/projects/diffs/_replaced_image_diff.html.haml
@@ -35,10 +35,10 @@
.swipe.view.hide
.swipe-frame
- .frame.deleted
+ .frame.deleted.old-diff
= image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
- .swipe-wrap
- = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
+ .swipe-wrap.left-oriented
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added old-diff js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
%span.swipe-bar
%span.top-handle
%span.bottom-handle
diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml
index 454f814795a..daac543b939 100644
--- a/app/views/projects/diffs/_single_image_diff.html.haml
+++ b/app/views/projects/diffs/_single_image_diff.html.haml
@@ -10,5 +10,5 @@
.image.js-single-image{ data: diff_view_data }
.wrap
- single_class_name = diff_file.deleted_file? ? 'deleted' : 'added'
- = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.file_path }
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} old-diff js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.file_path }
%p.image-info= number_to_human_size(blob.size)
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 56427a74d56..641a0689c26 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -1,9 +1,9 @@
- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES
- if too_big
.suppressed-container
- %a.show-suppressed-diff.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
+ %a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
-%table.text-file.diff-wrap-lines.code.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
+%table.text-file.diff-wrap-lines.code.code-commit.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines,
as: :line,
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 1a489bfa275..c15b84d0aac 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,227 +1,157 @@
-- breadcrumb_title "General Settings"
-- page_title "General"
+- breadcrumb_title _("General Settings")
+- page_title _("General")
- @content_class = "limit-container-width" unless fluid_layout
-- expanded = Rails.env.test?
-
-.project-edit-container
- %section.settings.general-settings.no-animate#js-general-project-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- General project
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Update your project name, description, avatar, and other general settings.
- .settings-content
- .project-edit-errors
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-general-project-settings' }
- %fieldset
- .row
- .form-group.col-md-9
- = f.label :name, class: 'label-bold', for: 'project_name_edit' do
- Project name
- = f.text_field :name, class: "form-control", id: "project_name_edit"
-
- .form-group.col-md-3
- = f.label :id, class: 'label-bold' do
- Project ID
- = f.text_field :id, class: 'form-control', readonly: true
-
- .form-group
- = f.label :description, class: 'label-bold' do
- Project description
- %span.light (optional)
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
-
- = render_if_exists 'projects/classification_policy_settings', f: f
-
- = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
-
- .form-group
- = f.label :tag_list, "Topics", class: 'label-bold'
- = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
- %p.form-text.text-muted Separate topics with commas.
- %fieldset.features
- %h5.prepend-top-0= _("Project avatar")
- .form-group
- - if @project.avatar?
- .avatar-container.rect-avatar.s160.append-bottom-15
- = project_icon(@project, alt: '', class: 'avatar project-avatar s160', width: 160, height: 160)
- - if @project.avatar_in_git
- %p.light
- = _("Project avatar in repository: %{link}").html_safe % { link: @project.avatar_in_git }
- .prepend-top-5.append-bottom-10
- %button.btn.js-choose-project-avatar-button{ type: 'button' }= _("Choose file...")
- %span.file_name.prepend-left-default.js-filename= _("No file chosen")
- = f.file_field :avatar, class: "js-project-avatar-input hidden"
- .form-text.text-muted= _("The maximum file size allowed is 200KB.")
- - if @project.avatar?
- %hr
- = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted"
- = f.submit 'Save changes', class: "btn btn-success js-btn-success-general-project-settings"
-
- %section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Permissions
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Enable or disable certain project features and choose access levels.
- .settings-content
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
- -# haml-lint:disable InlineJavaScript
- %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
- .js-project-permissions-form
- = f.submit 'Save changes', class: "btn btn-success"
-
- = render_if_exists 'projects/issues_settings'
-
- %section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
- .settings-header
- %h4
- Merge request
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Customize your merge request restrictions.
- .settings-content
- = render_if_exists 'shared/promotions/promote_mr_features'
-
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
- = render 'projects/merge_request_settings', form: f
- = f.submit 'Save changes', class: "btn btn-success qa-save-merge-request-changes"
-
- = render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
-
- = render_if_exists 'projects/service_desk_settings'
-
- %section.settings.no-animate{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = s_('ProjectSettings|Badges')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- = s_('ProjectSettings|Customize your project badges.')
- = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
- .settings-content
- = render 'shared/badges/badge_settings'
-
- = render 'export', project: @project
-
- %section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Advanced
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
- .settings-content
+- expanded = expanded_by_default?
+
+%section.settings.general-settings.no-animate.expanded#js-general-settings
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= _('Collapse')
+ %p= _('Update your project name, topics, description and avatar.')
+ .settings-content= render 'projects/settings/general'
+
+%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
+
+ .settings-content
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
+ %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
+ .js-project-permissions-form
+ = f.submit _('Save changes'), class: "btn btn-success"
+
+%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose your merge method, options, checks, and set up a default merge request description template.')
+
+ .settings-content
+ = render_if_exists 'shared/promotions/promote_mr_features'
+
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
+ = render 'projects/merge_request_settings', form: f
+ = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes"
+
+= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
+
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
+ = s_('ProjectSettings|Badges')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = s_('ProjectSettings|Customize your project badges.')
+ = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
+ .settings-content
+ = render 'shared/badges/badge_settings'
+
+= render_if_exists 'projects/settings/default_issue_template'
+
+= render_if_exists 'projects/service_desk_settings'
+
+%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Housekeeping, export, path, transfer, remove, archive.')
+
+ .settings-content
+ .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"
+
+ = render 'export', project: @project
+
+ - if can? current_user, :archive_project, @project
.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
+ %h4.warning-title
- if @project.archived?
- %p
- Unarchiving the project will restore people's ability to make changes to it.
- The repository can be committed to, and issues, comments and other entities can be created.
- %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?" },
- method: :post, class: "btn btn-success"
+ = _('Unarchive project')
- else
- %p
- Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
- %strong The repository cannot be committed to, and no issues, comments or other entities can be created.
- = link_to 'Archive project', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?" },
- method: :post, class: "btn btn-warning"
- .sub-section.rename-repository
- %h4.warning-title
- Rename repository
- = render 'projects/errors'
- = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
- .form-group.project_name_holder
- = f.label :name, class: 'label-bold' do
- Project name
- .form-group
- = f.text_field :name, class: "form-control"
+ = _('Archive project')
+ - if @project.archived?
+ %p= _("Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>").html_safe
+ = link_to _('Unarchive project'), unarchive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to unarchive this project?") },
+ method: :post, class: "btn btn-success"
+ - else
+ %p= _("Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>").html_safe
+ = link_to _('Archive project'), archive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to archive this project?") },
+ method: :post, class: "btn btn-warning"
+ .sub-section.rename-repository
+ %h4.warning-title= _('Change path')
+ = render 'projects/errors'
+ = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
+ .form-group
+ = f.label :path, _('Path'), class: 'label-bold'
+ .form-group
+ .input-group
+ .input-group-prepend
+ .input-group-text
+ #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
+ = f.text_field :path, class: 'form-control qa-project-path-field h-auto'
+ %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_platform.present?
+ %li= _('Your deployment services will be broken, you will need to manually fix the services after renaming.')
+ = f.submit _('Change path'), class: "btn btn-warning qa-change-path-button"
+
+ - 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
- = f.label :path, class: 'label-bold' do
- %span Path
+ = label_tag :new_namespace_id, nil, class: 'label-bold' do
+ %span= _('Select a new namespace')
.form-group
- .input-group
- .input-group-prepend
- .input-group-text
- #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
- = f.text_field :path, class: 'form-control'
+ = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
%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_platform.present?
- %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-bold' 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
+ %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
+ - if @project.fork_source
+ = link_to(fork_source_name(@project), project_path(@project.fork_source))
+ - else
+ = fork_source_name(@project)
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p
- This will remove the fork relationship to source project
- = succeed "." do
- - if @project.fork_source
- = link_to(fork_source_name(@project), project_path(@project.fork_source))
- - else
- = fork_source_name(@project)
- = 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
+ %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= _('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
- 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) }
+ %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
%h2
%i.fa.fa-spinner.fa-spin
- Saving project.
- %p Please wait a moment, this page will automatically refresh when ready.
+ = _('Saving project.')
+ %p= _('Please wait a moment, this page will automatically refresh when ready.')
= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 081990ac9b7..9fa31c147eb 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,89 +7,64 @@
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render "home_panel"
- .project-empty-note-panel
- %h4.append-bottom-20
- = _('The repository for this project is empty')
+ %h4.prepend-top-0.append-bottom-8
+ = _('The repository for this project is empty')
- - if @project.can_current_user_push_code?
- %p
- - link_to_cli = link_to _('command line instructions'), '#repo-command-line-instructions'
- = _('If you already have files you can push them using the %{link_to_cli} below.').html_safe % { link_to_cli: link_to_cli }
- %p
- %em
- - link_to_protected_branches = link_to _('Learn more about protected branches'), help_page_path('user/project/protected_branches')
- = _('Note that the master branch is automatically protected. %{link_to_protected_branches}').html_safe % { link_to_protected_branches: link_to_protected_branches }
-
- %hr
- %p
- - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'))
- - link_to_add_kubernetes_cluster = link_to(s_('AutoDevOps|add a Kubernetes cluster'), new_project_cluster_path(@project))
- = s_('AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}.').html_safe % { link_to_auto_devops_settings: link_to_auto_devops_settings, link_to_add_kubernetes_cluster: link_to_add_kubernetes_cluster }
+ - if @project.can_current_user_push_code?
+ %p.append-bottom-0
+ = _('You can create files directly in GitLab using one of the following options.')
- %hr
- %p
- = _('Otherwise it is recommended you start with one of the options below.')
- .prepend-top-20
-
- %nav.project-buttons
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.qa-quick-actions
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- .nav-links.scrolling-tabs.quick-links
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
+ .project-buttons.qa-quick-actions
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
- if can?(current_user, :push_code, @project)
- %div
- .prepend-top-20
- .empty_wrapper
- %h3#repo-command-line-instructions.page-title-empty
- = _('Command line instructions')
- .git-empty.js-git-empty
- %fieldset
- %h5= _('Git global setup')
- %pre.bg-light
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
-
- %fieldset
- %h5= _('Create a new repository')
- %pre.bg-light
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ .empty-wrapper.prepend-top-32
+ %h3#repo-command-line-instructions.page-title-empty
+ = _('Command line instructions')
+ %p
+ = _('You can also upload existing files from your computer using the instructions below.')
+ .git-empty.js-git-empty
+ %fieldset
+ %h5= _('Git global setup')
+ %pre.bg-light
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5= _('Existing folder')
- %pre.bg-light
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- git add .
- git commit -m "Initial commit"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ %fieldset
+ %h5= _('Create a new repository')
+ %pre.bg-light
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- %fieldset
- %h5= _('Existing Git repository')
- %pre.bg-light
- :preserve
- cd existing_repo
- git remote rename origin old-origin
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin --all
- git push -u origin --tags
+ %fieldset
+ %h5= _('Push an existing folder')
+ %pre.bg-light
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ git add .
+ git commit -m "Initial commit"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to _('Remove project'), [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
+ %fieldset
+ %h5= _('Push an existing Git repository')
+ %pre.bg-light
+ :preserve
+ cd existing_repo
+ git remote rename origin old-origin
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin --all
+ git push -u origin --tags
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index cbd5c54cecc..1fbe34cfff3 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -17,5 +17,5 @@
= f.url_field :external_url, class: 'form-control'
.form-actions
- = f.submit _('Save'), class: 'btn btn-save'
+ = f.submit _('Save'), class: 'btn btn-success'
= link_to _('Cancel'), project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index e8a89b8c6fc..b37dba8b35d 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -1,24 +1,20 @@
-- page_title "Fork project"
+- page_title _("Fork project")
- if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block
%h4
= sprite_icon('fork', size: 16)
- Fork Error!
+ = _("Fork Error!")
%p
- You tried to fork
- = link_to_project @project
- but it failed for the following reason:
-
+ = _("You tried to fork %{link_to_the_project} but it failed for the following reason:").html_safe % { link_to_the_project: link_to_project(@project) }
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
- error = @forked_project.errors.full_messages.first
- if error.include?("already been taken")
- Name has already been taken
+ = _("Name has already been taken")
- else
= error
%p
- = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
- Try to fork again
+ = link_to _("Try to fork again"), new_project_fork_path(@project), title: _("Fork"), class: "btn"
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index c63c34c4ebb..0397a7034c7 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -5,12 +5,12 @@
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
+ = search_field_tag :filter_projects, nil, placeholder: _('Search forks'), class: 'projects-list-filter project-filter-form-field form-control input-short',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.light sort:
+ %span.light= _("sort:")
- if @sort.present?
= sort_options_hash[@sort]
- else
@@ -30,13 +30,12 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-success' do
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn btn-success' do
= sprite_icon('fork', size: 12)
- %span Fork
+ %span= _('Fork')
- else
- = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-success' do
+ = link_to new_project_fork_path(@project), title: _("Fork project"), class: 'btn btn-success' do
= sprite_icon('fork', size: 12)
- %span Fork
-
+ %span= _('Fork')
= render 'projects', projects: @forks
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index a603b1024eb..bf03353a565 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -1,13 +1,11 @@
-- page_title "Fork project"
+- page_title _("Fork project")
.row.prepend-top-default
.col-lg-3
%h4.prepend-top-0
- Fork project
+ = _("Fork project")
%p
- A fork is a copy of a project.
- %br
- Forking a repository allows you to make changes without affecting the original project.
+ = _("A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project.").html_safe
.col-lg-9
- if @namespaces.present?
.fork-thumbnail-container.js-fork-content
@@ -17,13 +15,13 @@
= render 'fork_button', namespace: namespace
- else
%strong
- No available namespaces to fork the project.
+ = _("No available namespaces to fork the project.")
%p.prepend-top-default
- You must have permission to create a project in a namespace before forking.
+ = _("You must have permission to create a project in a namespace before forking.")
.save-project-loader.hide.js-fork-content
%h2.text-center
= icon('spinner spin')
- Forking repository
+ = _("Forking repository")
%p.text-center
- Please wait a moment, this page will automatically refresh when ready.
+ = _("Please wait a moment, this page will automatically refresh when ready.")
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 7614d40ba1f..1118b44d7a2 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -5,11 +5,11 @@
- pipeline_link = local_assigns.fetch(:pipeline_link, false)
- stage = local_assigns.fetch(:stage, false)
-%tr.generic_commit_status{ class: ('retried' if retried) }
+%tr.generic-commit-status{ class: ('retried' if retried) }
%td.status
= render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user)
- %td.generic_commit_status-link
+ %td.generic-commit-status-link
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
= link_to generic_commit_status.target_url do
%span.build-link ##{generic_commit_status.id}
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index f1b14d4c4d1..4b2417ff43b 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -22,6 +22,6 @@
= s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref }
%input#brush_change{ :type => "hidden" }
.graphs.row
- #contributors-master
+ #contributors-master.svg-w-100
#contributors.clearfix
- %ol.contributors-list.row
+ %ol.contributors-list.svg-w-100.row
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
deleted file mode 100644
index 38469ed4774..00000000000
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.issue-closed-by-widget.second-block
- - pluralized_mr_this = merge_request_count > 1 ? "these" : "this"
- - pluralized_mr_is = merge_request_count > 1 ? "are" : "is"
- When #{pluralized_mr_this} merge #{"request".pluralize(merge_request_count)} #{pluralized_mr_is} accepted, this issue will be closed automatically.
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index ce7c7091c93..9293aa1b309 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,7 +6,7 @@
.issuable-info-container
.issuable-main-info
.issue-title.title
- %span.issue-title-text
+ %span.issue-title-text{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
@@ -36,8 +36,10 @@
= issue.due_date.to_s(:medium)
- if issue.labels.any?
&nbsp;
- - labels_sorted_by_title(issue.labels).each do |label|
- = link_to_label(label, subject: issue.project, css_class: 'label-link')
+ - presented_labels_sorted_by_title(issue.labels, issue.project).each do |label|
+ = link_to_label(label, css_class: 'label-link')
+
+ = render_if_exists "projects/issues/issue_weight", issue: issue
.issuable-meta
%ul.controls
@@ -46,7 +48,7 @@
CLOSED
- if issue.assignees.any?
%li
- = render 'shared/issuable/assignees', project: @project, issue: issue
+ = render 'shared/issuable/assignees', project: @project, issuable: issue
= render 'shared/issuable_meta_data', issuable: issue
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
deleted file mode 100644
index 310e339ac8d..00000000000
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- if @merge_requests.any?
- .card-slim.mt-3
- .card-header
- %h2.card-title.mt-0.mb-0.h5.merge-requests-title
- %span.mr-1.bold
- = _('Related merge requests')
- .d-inline-flex.lh-100.align-middle
- .mr-count-badge
- .mr-count-badge-count
- = sprite_icon('merge-request', size: 16, css_class: 'mr-1 text-secondary')
- = @merge_requests.count
- %ul.content-list.related-items-list
- - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- - @merge_requests.each do |merge_request|
- - merge_request = merge_request.present(current_user: current_user)
- %li.list-item.py-0.px-0
- .item-body.issuable-info-container.py-lg-3.px-lg-3.pl-md-3
- .item-contents
- .item-title.d-flex.align-items-center.mr-title
- = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-none d-xl-block append-right-8' }
- = link_to merge_request.title, merge_request_path(merge_request), { class: 'mr-title-link'}
- .item-meta
- = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-xl-none d-lg-block append-right-5' }
- %span.d-flex.align-items-center.append-right-8.mr-item-path.item-path-id.mt-0
- %span.path-id-text.bold.text-truncate{ data: { toggle: 'tooltip'}, title: merge_request.target_project.full_path }
- = merge_request.target_project.full_path
- = merge_request.to_reference
- %span.mr-ci-status.flex-md-grow-1.justify-content-end.d-flex.ml-md-2
- - if merge_request.can_read_pipeline?
- = render_pipeline_status(merge_request.head_pipeline, tooltip_placement: 'bottom')
- - elsif has_any_head_pipeline
- = icon('blank fw')
-
- - if @closed_by_merge_requests.present?
- %p
- = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
diff --git a/app/views/projects/issues/_merge_requests_status.html.haml b/app/views/projects/issues/_merge_requests_status.html.haml
deleted file mode 100644
index 90838a75214..00000000000
--- a/app/views/projects/issues/_merge_requests_status.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- time_format = '%b %e, %Y %l:%M%P %Z%z'
-
-- if merge_request.merged?
- - mr_status_date = merge_request.merged_at
- - mr_status_title = _('Merged')
- - mr_status_icon = 'merge'
- - mr_status_class = 'merged'
-- elsif merge_request.closed?
- - mr_status_date = merge_request.closed_event&.created_at
- - mr_status_title = _('Closed')
- - mr_status_icon = 'issue-close'
- - mr_status_class = 'closed'
-- else
- - mr_status_date = merge_request.created_at
- - mr_status_title = mr_status_date ? _('Opened') : _('Open')
- - mr_status_icon = 'issue-open-m'
- - mr_status_class = 'open'
-
-- if mr_status_date
- - mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span> #{time_ago_in_words(mr_status_date)} ago</div><span class=\"text-tertiary\">#{l(mr_status_date.to_time, format: time_format)}</span>"
-- else
- - mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span></div>"
-
-%span.mr-status-wrapper.suggestion-help-hover{ class: css_class, data: { toggle: 'tooltip', placement: 'bottom', html: 'true', title: mr_status_tooltip } }
- = sprite_icon(mr_status_icon, size: 16, css_class: "merge-request-status #{mr_status_class}")
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index fbd70cd1906..457b2936278 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -8,18 +8,18 @@
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
- .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
- .btn-group.unavailable
+ .create-mr-dropdown-wrap.d-inline-block.full-width-mobile{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
+ .btn-group.btn-group-sm.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
%span.text
Checking branch availability…
- .btn-group.available.hidden
+ .btn-group.btn-group-sm.available.hidden
%button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
- %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
+ %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle.flex-grow-0{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
= icon('caret-down')
.droplab-dropdown
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index ffdd96870ef..6da4956a036 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -8,7 +8,7 @@
- pipeline = @project.pipeline_for(branch, target.sha) if target
- if can?(current_user, :read_pipeline, pipeline)
%span.related-branch-ci-status
- = render_pipeline_status(pipeline)
+ = render 'ci/status/icon', status: pipeline.detailed_status(current_user)
%span.related-branch-info
%strong
= link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name"
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index 9a081a42b6f..d1601d7fd10 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,9 +1,8 @@
-- add_to_breadcrumbs "Issues", project_issues_path(@project)
-- breadcrumb_title "New"
-- page_title "New Issue"
+- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
+- breadcrumb_title _("New")
+- page_title _("New Issue")
-%h3.page-title
- New Issue
+%h3.page-title= _("New Issue")
%hr
= render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 3a674da6e87..d55afee4523 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,7 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs "Issues", project_issues_path(@project)
+- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
-- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
+- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
- page_description @issue.description
- page_card_attributes @issue.card_attributes
@@ -15,7 +15,7 @@
.issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) }
= sprite_icon('mobile-issue-close', size: 16, css_class: 'd-block d-sm-none')
.d-none.d-sm-block
- - if @issue.moved?
+ - if @issue.moved? && can?(current_user, :read_issue, @issue.moved_to)
- moved_link_start = "<a href=\"#{issue_path(@issue.moved_to)}\" class=\"text-white text-underline\">".html_safe
- moved_link_end = '</a>'.html_safe
= s_('IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})').html_safe % {moved_link_start: moved_link_start,
@@ -72,16 +72,18 @@
%h2.title= markdown_field(@issue, :title)
- if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
- .wiki= markdown_field(@issue, :description)
+ .md= markdown_field(@issue, :description)
%textarea.hidden.js-task-list-field= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
- #merge-requests{ data: { url: referenced_merge_requests_project_issue_path(@project, @issue) } }
- // This element is filled in using JavaScript.
+ = render_if_exists 'projects/issues/related_issues'
- #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
- // This element is filled in using JavaScript.
+ #js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
+
+ - if can?(current_user, :download_code, @project)
+ #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
+ -# This element is filled in using JavaScript.
.content-block.emoji-block.emoji-block-sticky
.row
diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml
index d124d3ebfc1..b08223546f7 100644
--- a/app/views/projects/jobs/_table.html.haml
+++ b/app/views/projects/jobs/_table.html.haml
@@ -16,7 +16,7 @@
%th Runner
%th Stage
%th Name
- %th
+ %th Timing
%th Coverage
%th
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 475bae887ec..81a53f22f67 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -8,6 +8,7 @@
%div{ class: container_class }
#js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json),
+ deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'),
build_options: javascript_build_options } }
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index bb7c297ba1f..511d7a82d1b 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -11,31 +11,30 @@
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.prepend-top-10
- - if can_admin_label
- - if search.blank?
- %p.text-muted
- = _('Labels can be applied to issues and merge requests.')
- %br
- = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
- -# Only show it in the first page
- - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
- .prioritized-labels{ class: ('hide' if hide) }
- %h5.prepend-top-10= _('Prioritized Labels')
- .content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
- #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
- = render 'shared/empty_states/priority_labels'
- - if @prioritized_labels.present?
- = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label, locals: { force_priority: true }
- - elsif search.present?
- .nothing-here-block
- = _('No prioritised labels with such name or description')
+ - if can_admin_label && search.blank?
+ %p.text-muted
+ = _('Labels can be applied to issues and merge requests.')
+ %br
+ = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
+
+ -# Only show it in the first page
+ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
+ .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] }
+ %h5.prepend-top-10= _('Prioritized Labels')
+ .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
+ #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
+ = render 'shared/empty_states/priority_labels'
+ - if @prioritized_labels.present?
+ = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
+ - elsif search.present?
+ .nothing-here-block
+ = _('No prioritised labels with such name or description')
- if @labels.present?
.other-labels
- - if can_admin_label
- %h5{ class: ('hide' if hide) }= _('Other Labels')
+ %h5{ class: ('hide' if hide) }= _('Other Labels')
.content-list.manage-labels-list.js-other-labels
- = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
+ = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
= paginate @labels, theme: 'gitlab'
- elsif search.present?
.other-labels
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 90916191d97..67e5e4ca62d 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -34,8 +34,8 @@
= merge_request.target_branch
- if merge_request.labels.any?
&nbsp;
- - labels_sorted_by_title(merge_request.labels).each do |label|
- = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
+ - presented_labels_sorted_by_title(merge_request.labels, merge_request.project).each do |label|
+ = link_to_label(label, type: :merge_request, css_class: 'label-link')
.issuable-meta
%ul.controls
@@ -48,14 +48,14 @@
CLOSED
- if can?(current_user, :read_pipeline, merge_request.head_pipeline)
%li.issuable-pipeline-status.d-none.d-sm-inline-block
- = render_pipeline_status(merge_request.head_pipeline)
+ = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user)
- if merge_request.open? && merge_request.broken?
%li.issuable-pipeline-broken.d-none.d-sm-inline-block
= link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do
= icon('exclamation-triangle')
- - if merge_request.assignee
+ - if merge_request.assignees.any?
%li
- = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: _('Assigned to :name'))
+ = render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request
= render_if_exists 'projects/merge_requests/approvals_count', merge_request: merge_request
= render 'shared/issuable_meta_data', issuable: merge_request
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index bd6f1c05949..57fbd360d46 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -1,5 +1,5 @@
%ul.content-list.mr-list.issuable-list
- - if @merge_requests.exists?
+ - if @merge_requests.present?
= render @merge_requests
- else
= render 'shared/empty_states/merge_requests'
diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
index 1a9ab288683..7f2c9dcacfd 100644
--- a/app/views/projects/merge_requests/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/_mr_box.html.haml
@@ -5,7 +5,7 @@
%div
- if @merge_request.description.present?
.description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' }
- .wiki
+ .md
= markdown_field(@merge_request, :description)
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index 3cd83feb842..92e34b3ceda 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -1,8 +1,9 @@
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
+- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- if @merge_request.closed_without_fork?
.alert.alert-danger
- %p The source project of this merge request has been removed.
+ The source project of this merge request has been removed.
.detail-page-header
.detail-page-header-body
@@ -33,10 +34,11 @@
- if can_update_merge_request
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ - if can_reopen_merge_request
%li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn btn-grouped js-issuable-edit qa-edit-button"
- = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_update_merge_request
+ = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_reopen_merge_request
diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
index 8181267184a..55c89f137c5 100644
--- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
@@ -6,7 +6,7 @@
.form-group.row
.col-md-4
%h4= _('Resolve conflicts on source branch')
- .resolve-info
+ .resolve-info{ "v-pre": true }
= translation.html_safe
.col-md-8
%label.label-bold{ "for" => "commit-message" }
diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
index d828cb6cf9e..7bd5c437942 100644
--- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
+++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
@@ -1,5 +1,5 @@
%inline-conflict-lines{ "inline-template" => "true", ":file" => "file" }
- %table
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
%tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" }
%td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" }
%a {{line.new_line}}
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index 09aeb81671a..f48390aa046 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -26,9 +26,9 @@
%strong {{file.filePath}}
= render partial: 'projects/merge_requests/conflicts/file_actions'
.diff-content.diff-wrap-lines
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ .file-content{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ .file-content{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
%parallel-conflict-lines{ ":file" => "file" }
%div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" }
= render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 5111c9fab8d..a201fafb949 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -31,29 +31,26 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- %ul.merge-request-tabs.nav-tabs.nav.nav-links.scrolling-tabs
- %li.notes-tab.qa-notes-tab
- = tab_link_for @merge_request, :show, force_link: @commit.present? do
- Discussion
- %span.badge.badge-pill= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = tab_link_for @merge_request, :commits do
- Commits
- %span.badge.badge-pill= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = tab_link_for @merge_request, :pipelines do
- Pipelines
- %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
- %li.diffs-tab.qa-diffs-tab
- = tab_link_for @merge_request, :diffs do
- Changes
- %span.badge.badge-pill= @merge_request.diff_size
- .d-inline-flex.flex-wrap
+ %ul.merge-request-tabs.nav-tabs.nav.nav-links
+ %li.notes-tab.qa-notes-tab
+ = tab_link_for @merge_request, :show, force_link: @commit.present? do
+ = _("Discussion")
+ %span.badge.badge-pill= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = tab_link_for @merge_request, :commits do
+ = _("Commits")
+ %span.badge.badge-pill= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = tab_link_for @merge_request, :pipelines do
+ = _("Pipelines")
+ %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
+ %li.diffs-tab.qa-diffs-tab
+ = tab_link_for @merge_request, :diffs do
+ = _("Changes")
+ %span.badge.badge-pill= @merge_request.diff_size
+ .d-flex.flex-wrap.align-items-center.justify-content-lg-end
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
notes_filters: UserPreference.notes_filters.to_json } }
#js-vue-discussion-counter
@@ -82,7 +79,8 @@
help_page_path: suggest_changes_help_path,
current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json,
project_path: project_path(@merge_request.project),
- changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg') } }
+ changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
+ is_fluid_layout: fluid_layout.to_s } }
.mr-loading-status
= spinner
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 0542b349e44..1cee8be604a 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -54,13 +54,12 @@
%div
- if @milestone.description.present?
- .description
- .wiki
- = markdown_field(@milestone, :description)
+ .description.md
+ = markdown_field(@milestone, :description)
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
- - if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero?
+ - if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero?
.alert.alert-success.prepend-top-default
%span= _('Assign some issues to this milestone.')
- elsif @milestone.complete?(current_user) && @milestone.active?
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 293a2e3ebfe..ee82d68d398 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -1,14 +1,12 @@
- mirror = f.object
-- is_push = local_assigns.fetch(:is_push, false)
- auth_options = [[_('Password'), 'password'], [_('SSH public key'), 'ssh_public_key']]
-- regen_data = { auth_method: 'ssh_public_key', regenerate_ssh_private_key: true }
-- ssh_public_key_present = mirror.ssh_public_key.present?
.form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
= f.select :auth_method,
options_for_select(auth_options, mirror.auth_method),
{}, { class: "form-control js-mirror-auth-type qa-authentication-method" }
+ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group
.collapse.js-well-changing-auth
@@ -16,21 +14,3 @@
.well-password-auth.collapse.js-well-password-auth
= f.label :password, _("Password"), class: "label-bold"
= f.password_field :password, value: mirror.password, class: 'form-control qa-password', autocomplete: 'new-password'
- - unless is_push
- .well-ssh-auth.collapse.js-well-ssh-auth
- %p.js-ssh-public-key-present{ class: ('collapse' unless ssh_public_key_present) }
- = _('Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation.')
- %p.js-ssh-public-key-pending{ class: ('collapse' if ssh_public_key_present) }
- = _('An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation.')
-
- .clearfix.js-ssh-public-key-wrap{ class: ('collapse' unless ssh_public_key_present) }
- %code.prepend-top-10.ssh-public-key
- = mirror.ssh_public_key
- = clipboard_button(text: mirror.ssh_public_key, title: _("Copy SSH public key to clipboard"), class: 'prepend-top-10 btn-copy-ssh-public-key')
-
- = button_tag type: 'button',
- data: { endpoint: project_mirror_path(@project), project_data: { import_data_attributes: regen_data } },
- class: "btn btn-inverted btn-warning prepend-top-10 js-btn-regenerate-ssh-key#{ ' collapse' unless ssh_public_key_present }" do
- = icon('spinner spin', class: 'js-spinner d-none')
- = _('Regenerate key')
- = render 'projects/mirrors/regenerate_public_ssh_key_confirm_modal'
diff --git a/app/views/projects/mirrors/_disabled_mirror_badge.html.haml b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
new file mode 100644
index 00000000000..356cb43f07f
--- /dev/null
+++ b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
@@ -0,0 +1 @@
+.badge.badge-warning.qa-disabled-mirror-badge{ data: { toggle: 'tooltip', html: 'true' }, title: _('Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them.') }= _('Disabled')
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 35a6885318a..33e5a6e67c3 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -7,7 +7,7 @@
%li
- minutes = Gitlab.config.gitlab_shell.git_timeout / 60
= _("The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination.") % { number_of_minutes: minutes }
- %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
+ %li= mirror_lfs_sync_message
%li
= _('This user will be the author of all events in the activity feed that are the result of an update,
like new branches being created or new commits being pushed to existing branches.')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index 21b105e6f80..e68fa5d08c7 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) }
@@ -11,7 +11,7 @@
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content
- = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
+ = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
.panel.panel-default
.panel-heading
%h3.panel-title= _('Mirror a repository')
@@ -20,7 +20,7 @@
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
- = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url qa-mirror-repository-url-input', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
+ = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url qa-mirror-repository-url-input', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password'
= render 'projects/mirrors/instructions'
@@ -29,7 +29,7 @@
.form-check.append-bottom-10
= check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
= label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
- = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
+ = link_to icon('question-circle'), help_page_path('user/project/protected_branches'), target: '_blank'
.panel-footer
= f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
@@ -49,17 +49,19 @@
%tbody.js-mirrors-table-body
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- - if mirror.enabled
- %tr.qa-mirrored-repository-row
- %td.qa-mirror-repository-url= mirror.safe_url
- %td= _('Push')
- %td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
- %td
- - if mirror.last_error.present?
- .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
- %td.mirror-action-buttons
- .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- - if mirror.ssh_key_auth?
- = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'))
- = render 'shared/remote_mirror_update_button', remote_mirror: mirror
- %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
+ - next if mirror.new_record?
+ %tr.qa-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
+ %td.qa-mirror-repository-url= mirror.safe_url
+ %td= _('Push')
+ %td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td
+ - if mirror.disabled?
+ = render 'projects/mirrors/disabled_mirror_badge'
+ - if mirror.last_error.present?
+ .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
+ %td
+ .btn-group.mirror-actions-group.pull-right{ role: 'group' }
+ - if mirror.ssh_key_auth?
+ = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'))
+ = render 'shared/remote_mirror_update_button', remote_mirror: mirror
+ %button.js-delete-mirror.qa-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
diff --git a/app/views/projects/mirrors/_mirror_repos_push.html.haml b/app/views/projects/mirrors/_mirror_repos_push.html.haml
index 1d9c83653fe..b7c885b4a63 100644
--- a/app/views/projects/mirrors/_mirror_repos_push.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_push.html.haml
@@ -5,4 +5,4 @@
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
= render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f }
- = render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f, is_push: true }
+ = render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f }
diff --git a/app/views/projects/mirrors/_ssh_host_keys.html.haml b/app/views/projects/mirrors/_ssh_host_keys.html.haml
index f61aa6ecd11..7762fb4b844 100644
--- a/app/views/projects/mirrors/_ssh_host_keys.html.haml
+++ b/app/views/projects/mirrors/_ssh_host_keys.html.haml
@@ -3,7 +3,7 @@
- verified_at = mirror.ssh_known_hosts_verified_at
.form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) }
- %button.btn.btn-inverted.btn-success.inline.js-detect-host-keys.append-right-10{ type: 'button' }
+ %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button' }
= icon('spinner spin', class: 'js-spinner d-none')
= _('Detect host keys')
.fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index ff7c36c2d5b..d7e16dbd40c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -16,6 +16,7 @@
= _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
%p
= _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
+ = render_if_exists 'projects/new_ci_cd_banner_external_repo'
%p
- pages_getting_started_guide = link_to _('Pages getting started guide'), help_page_path("user/project/pages/getting_started_part_two", anchor: "fork-a-project-to-get-started-from"), target: '_blank'
= _('Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}.').html_safe % { pages_getting_started_guide: pages_getting_started_guide }
@@ -42,13 +43,19 @@
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
%span.d-none.d-sm-block Import project
%span.d-block.d-sm-none Import
+ = render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
.tab-content.gitlab-tab-content
.tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
- .tab-pane.no-padding{ id: 'create-from-template-pane', class: active_when(active_tab == 'template'), role: 'tabpanel' }
+ #create-from-template-pane.tab-pane.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' }
+ .card-slim.m-4.p-4
+ %div
+ - contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing'
+ - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url }
+ = _('Learn how to %{link_start}contribute to the built-in templates%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
= form_for @project, html: { class: 'new_project' } do |f|
.project-template
.form-group
@@ -63,6 +70,8 @@
%h4 No import options available
%p Contact an administrator to enable options for importing your project.
+ = render_if_exists 'projects/new_ci_cd_only_project_pane', active_tab: active_tab
+
.save-project-loader.d-none
.center
%h2
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index eb6838cec8d..044adb75bea 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -41,9 +41,9 @@
.note-actions-item
= button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} 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')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
+ %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.note-actions-item
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 8de84f82e9f..8a6e5fde99b 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -11,7 +11,7 @@
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
- = _('Report abuse to GitLab')
+ = _('Report abuse to admin')
- if note_editable
%li
= link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml
index ce3ef29c32e..74478ee011c 100644
--- a/app/views/projects/pages/_https_only.html.haml
+++ b/app/views/projects/pages/_https_only.html.haml
@@ -3,7 +3,7 @@
.form-check
= f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled?
= f.label :pages_https_only, class: pages_https_only_label_class do
- %strong Force domains with SSL certificates to use HTTPS
+ %strong Force HTTPS (requires valid certificates)
- unless pages_https_only_disabled?
.prepend-top-10
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index b7b46c56c37..33f2166480b 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -1,29 +1,80 @@
- if @domain.errors.any?
- #error_explanation
- .alert.alert-danger
- - @domain.errors.full_messages.each do |msg|
- %p= msg
+ .alert.alert-danger
+ - @domain.errors.full_messages.each do |msg|
+ = msg
.form-group.row
- = f.label :domain, class: 'col-form-label col-sm-2' do
- = _("Domain")
+ .col-sm-2.col-form-label
+ = f.label :domain, _("Domain")
.col-sm-10
- = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted?
+ = f.text_field :domain, required: true, autocomplete: "off", class: "form-control", disabled: @domain.persisted?
- if Gitlab.config.pages.external_https
- .form-group.row
- = f.label :certificate, class: 'col-form-label col-sm-2' do
- = _("Certificate (PEM)")
- .col-sm-10
- = f.text_area :certificate, rows: 5, class: 'form-control'
- %span.help-inline= _("Upload a certificate for your domain with all intermediates")
-
- .form-group.row
- = f.label :key, class: 'col-form-label col-sm-2' do
- = _("Key (PEM)")
- .col-sm-10
- = f.text_area :key, rows: 5, class: 'form-control'
- %span.help-inline= _("Upload a private key for your certificate")
+
+ - auto_ssl_available = Feature.enabled?(:pages_auto_ssl)
+ - auto_ssl_enabled = @domain.auto_ssl_enabled?
+ - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
+
+ - if auto_ssl_available
+ .form-group.row
+ .col-sm-2.col-form-label
+ %label{ for: "pages_domain_auto_ssl_enabled_button" }
+ - lets_encrypt_link_url = "https://letsencrypt.org/"
+ - lets_encrypt_link_start = "<a href=\"%{lets_encrypt_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url }
+ - lets_encrypt_link_end = "</a>".html_safe
+ = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end }
+
+ .col-sm-10.js-auto-ssl-toggle-container
+ %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button",
+ class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}",
+ "aria-label": _("Automatic certificate management using Let's Encrypt") }
+ = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input"
+ %span.toggle-icon
+ = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
+ = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
+ %p.text-secondary.mt-3
+ - docs_link_url = help_page_path("user/project/pages/lets_encrypt_for_gitlab_pages.md", anchor: "lets-encrypt-for-gitlab-pages")
+ - docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
+ - docs_link_end = "</a>".html_safe
+ = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
+
+ .js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :certificate, _("Certificate (PEM)")
+ .col-sm-10
+ - if auto_ssl_available_and_enabled && !@domain.certificate.empty?
+ = f.text_area :certificate,
+ rows: 5,
+ class: "form-control",
+ disabled: true
+ %span.help-inline.text-muted= _("This certificate is automatically managed by Let's Encrypt")
+ - else
+ %p.text-secondary.form-control-plaintext= _("The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete.")
+
+ .js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) }
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :certificate, _("Certificate (PEM)")
+ .col-sm-10
+ = f.text_area :certificate,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ value: (@domain.certificate unless auto_ssl_available_and_enabled),
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates")
+
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :key, _("Key (PEM)")
+ .col-sm-10
+ = f.text_area :key,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ value: (@domain.key unless auto_ssl_available_and_enabled),
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a private key for your certificate")
+
- else
.nothing-here-block
= _("Support for custom certificates is disabled. Ask your system's administrator to enable it.")
diff --git a/app/views/projects/pages_domains/_helper_text.html.haml b/app/views/projects/pages_domains/_helper_text.html.haml
new file mode 100644
index 00000000000..5a79fefabfc
--- /dev/null
+++ b/app/views/projects/pages_domains/_helper_text.html.haml
@@ -0,0 +1,9 @@
+- docs_link_url = help_page_path("user/project/pages/getting_started_part_three.md", anchor: "adding-certificates-to-your-project")
+- docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
+- docs_link_end = "</a>".html_safe
+
+-# Hiding behind a feature flag to avoid any changes to this feature's implemention
+-# when the :pages_auto_ssl feature flag is disabled. This check should be removed
+-# once the :pages_auto_ssl feature flag is removed.
+- if Feature.enabled?(:pages_auto_ssl)
+ %p= _("Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index e11387ae742..7c0777e5496 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -3,6 +3,7 @@
- page_title @domain.domain
%h3.page-title
= @domain.domain
+= render 'projects/pages_domains/helper_text'
%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index c7cefa87c76..e23ccb5d4c6 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -2,6 +2,7 @@
- page_title _('New Pages Domain')
%h3.page-title
= _("New Pages Domain")
+= render 'projects/pages_domains/helper_text'
%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 1121cf06b5c..396e5da87bc 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "js-pipeline-schedule-form" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "js-pipeline-schedule-form pipeline-schedule-form" } do |f|
= form_errors(@schedule)
.form-group.row
.col-md-9
@@ -11,12 +11,12 @@
.form-group.row
.col-md-9
= f.label :cron_timezone, _('Cron Timezone'), class: 'label-bold'
- = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
+ = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown w-100', dropdown_class: 'w-100', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
= f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
.form-group.row
.col-md-9
= f.label :ref, _('Target Branch'), class: 'label-bold'
- = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
+ = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown w-100', dropdown_class: 'git-revision-dropdown w-100', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group.row.js-ci-variable-list-section
.col-md-9
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 9c2efd6aa35..5d307d6a70d 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -10,13 +10,7 @@
.icon-container
= icon('clock-o')
= pluralize @pipeline.total_size, "job"
- - if @pipeline.ref
- from
- - if @pipeline.ref_exists?
- = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
- - else
- %span.ref-name
- = @pipeline.ref
+ = @pipeline.ref_text
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
@@ -48,9 +42,9 @@
content: "<a class='autodevops-link' href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
} }
Auto DevOps
- - if @pipeline.merge_request?
- %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run in a merge request context" }
- merge request
+ - if @pipeline.detached_merge_request_pipeline?
+ %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run on the source branch" }
+ detached
- if @pipeline.stuck?
%span.js-pipeline-url-stuck.badge.badge-warning
stuck
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 66e202103a9..c04f076a3ab 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -2,15 +2,15 @@
%ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator.nav.nav-tabs
%li.js-pipeline-tab-link
= link_to project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
- = _("Pipeline")
+ = _('Pipeline')
%li.js-builds-tab-link
= link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
- = _("Jobs")
+ = _('Jobs')
%span.badge.badge-pill.js-builds-counter= pipeline.total_size
- if @pipeline.failed_builds.present?
%li.js-failures-tab-link
= link_to failures_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
- = _("Failed Jobs")
+ = _('Failed Jobs')
%span.badge.badge-pill.js-failures-counter= @pipeline.failed_builds.count
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
@@ -24,41 +24,41 @@
%table.table.ci-table.pipeline
%thead
%tr
- %th Status
- %th Job ID
- %th Name
+ %th= _('Status')
+ %th= _('Job ID')
+ %th= _('Name')
%th
- %th Coverage
+ %th= _('Coverage')
%th
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
.bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
+ = _("%{gitlab_ci_yml} not found in this commit") % { gitlab_ci_yml: ".gitlab-ci.yml" }
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
%thead
%th.table-th-transparent
- %th.table-th-transparent= _("Name")
- %th.table-th-transparent= _("Stage")
- %th.table-th-transparent= _("Failure")
+ %th.table-th-transparent= _('Name')
+ %th.table-th-transparent= _('Stage')
+ %th.table-th-transparent= _('Failure')
%tbody
- @pipeline.failed_builds.each_with_index do |build, index|
- job = build.present(current_user: current_user)
%tr.build-state.responsive-table-border-start
- %td.responsive-table-cell.ci-status-icon-failed{ data: { column: "Status"} }
+ %td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
.d-none.d-md-block.build-icon
= custom_icon("icon_status_#{build.status}")
.d-md-none.build-badge
= render "ci/status/badge", link: false, status: job.detailed_status(current_user)
- %td.responsive-table-cell.build-name{ data: { column: _("Name")} }
+ %td.responsive-table-cell.build-name{ data: { column: _('Name')} }
= link_to build.name, pipeline_job_url(pipeline, build)
- %td.responsive-table-cell.build-stage{ data: { column: _("Stage")} }
+ %td.responsive-table-cell.build-stage{ data: { column: _('Stage')} }
= build.stage.titleize
- %td.responsive-table-cell.build-failure{ data: { column: _("Failure")} }
+ %td.responsive-table-cell.build-failure{ data: { column: _('Failure')} }
= build.present.callout_failure_message
%td.responsive-table-cell.build-actions
- if can?(current_user, :update_build, job)
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index ec17eddba79..4d1d078661d 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,11 +1,7 @@
- @no_container = true
-- page_title _("CI / CD Charts")
+- page_title _('CI / CD Charts')
%div{ class: container_class }
- .sub-header-block
- .oneline
- = _("A collection of graphs regarding Continuous Integration")
-
#charts.ci-charts
.row
.col-md-6
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index c0ee81fe28d..4e4638085fd 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
-- page_title "Pipelines"
+- page_title _('Pipelines')
+
+= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
%div{ 'class' => container_class }
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index f1cdc0a70dd..bfcaa09ae8c 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -1,16 +1,16 @@
-- breadcrumb_title "Pipelines"
-- page_title s_("Pipeline|Run Pipeline")
+- breadcrumb_title _('Pipelines')
+- page_title s_('Pipeline|Run Pipeline')
- settings_link = link_to _('CI/CD settings'), project_settings_ci_cd_path(@project)
%h3.page-title
- = s_("Pipeline|Run Pipeline")
+ = s_('Pipeline|Run Pipeline')
%hr
= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f|
= form_errors(@pipeline)
.form-group.row
.col-sm-12
- = f.label :ref, s_('Pipeline|Create for'), class: 'col-form-label'
+ = f.label :ref, s_('Pipeline|Run for'), class: 'col-form-label'
= hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide monospace',
@@ -28,8 +28,8 @@
= (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
.form-actions
- = f.submit s_('Pipeline|Create pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
- = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default float-right'
+ = f.submit s_('Pipeline|Run Pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
+ = link_to _('Cancel'), project_pipelines_path(@project), class: 'btn btn-default float-right'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 193d437dad1..8a6d7b082e3 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
-- add_to_breadcrumbs "Pipelines", project_pipelines_path(@project)
+- add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project)
- breadcrumb_title "##{@pipeline.id}"
-- page_title "Pipeline"
+- page_title _('Pipeline')
.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container
@@ -11,11 +11,13 @@
- if @pipeline.builds.empty? && @pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
+ %h4= _('Found errors in your %{gitlab_ci_yml}:') % { gitlab_ci_yml: '.gitlab-ci.yml' }
%ul
- @pipeline.yaml_errors.split(",").each do |error|
%li= error
- You can test your .gitlab-ci.yml in #{link_to "CI Lint", project_ci_lint_path(@project)}.
+ - lint_link_url = project_ci_lint_path(@project)
+ - lint_link_start = '<a href="%{url}">'.html_safe % { url: lint_link_url }
+ = s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
index b5d397e3065..00321014f91 100644
--- a/app/views/projects/project_members/_groups.html.haml
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -1,6 +1,6 @@
.card.project-members-groups
.card-header
- = _("Groups with access to <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(@project.name) }
+ = _("Groups with access to <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(@project.name, tags: []) }
%span.badge.badge-pill= group_links.size
%ul.content-list.members-list
= render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 0590578c3fe..efabb7f7b19 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -19,4 +19,5 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
= f.submit _("Add to project"), class: "btn btn-success qa-add-member-button"
- = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project")
+ - if can_import_members?
+ = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project")
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index e0dd386fc5d..f220299ec30 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,7 +4,7 @@
.card
.card-header.flex-project-members-panel
%span.flex-project-title
- = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(project.name) }
+ = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) }
%span.badge.badge-pill= members.total_count
= form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 242ff91f539..cc98ba64f08 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,29 +1,35 @@
- page_title _("Members")
+- can_admin_project_members = can?(current_user, :admin_project_member, @project)
.row.prepend-top-default
.col-lg-12
- %h4
- = _("Project members")
- - if can?(current_user, :admin_project_member, @project)
- %p
- = _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.").html_safe % { project_name: sanitize_project_name(@project.name) }
- - else
- %p
- = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+ - if project_can_be_shared?
+ %h4
+ = _("Project members")
+ - if can_admin_project_members
+ %p= share_project_description(@project)
+ - else
+ %p
+ = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+
.light
- - if can?(current_user, :admin_project_member, @project)
- %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
- %li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
- - if @project.allowed_to_share_with_group?
+ - if can_admin_project_members && project_can_be_shared?
+ - if !membership_locked? && @project.allowed_to_share_with_group?
+ %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
+ %li.nav-tab{ role: 'presentation', class: ('active' if membership_locked?) }
%a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite group")
- .tab-content.gitlab-tab-content
- .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
- .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ .tab-content.gitlab-tab-content
+ .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
+ = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ .tab-pane{ id: 'invite-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) }
+ = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ - elsif !membership_locked?
+ .invite-member= render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ - elsif @project.allowed_to_share_with_group?
+ .invite-group= render 'projects/project_members/new_project_group', tab_title: _('Invite group')
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index b12ae995ece..366d7a7a2eb 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,2 +1,2 @@
= render layout: 'projects/protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch } do
- = render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch }
+ = render_if_exists 'projects/protected_branches/update_protected_branch', protected_branch: protected_branch
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index d617d85afc2..3644a623d2c 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -6,8 +6,8 @@
.card-body
= form_errors(@protected_branch)
.form-group.row
- = f.label :name, class: 'col-md-2 text-right' do
- Branch:
+ .col-md-2.text-right
+ = f.label :name, 'Branch:'
.col-md-10
= render partial: "projects/protected_branches/shared/dropdown", locals: { f: f }
.form-text.text-muted
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 539b184e5c2..63748d8d85f 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.qa-protected-branches-settings.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
index bb7998f739d..81dcab1d1ab 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -1,6 +1,6 @@
- can_admin_project = can?(current_user, :admin_project, @project)
-%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
+%tr.qa-protected-branch.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td
%span.ref-name= protected_branch.name
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index cbf1938664c..020e6e187a6 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -6,8 +6,8 @@
.card-body
= form_errors(@protected_tag)
.form-group.row
- = f.label :name, class: 'col-md-2 text-right' do
- Tag:
+ .col-md-2.text-right
+ = f.label :name, 'Tag:'
.col-md-10.protected-tags-dropdown
= render partial: "projects/protected_tags/shared/dropdown", locals: { f: f }
.form-text.text-muted
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
index 9a50a51e4be..b0c87ac8c17 100644
--- a/app/views/projects/protected_tags/shared/_index.html.haml
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index a4cde53e8c6..9594c9184a2 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -1,7 +1,7 @@
%tr.tag
%td
= escape_once(tag.name)
- = clipboard_button(text: "docker pull #{tag.location}")
+ = clipboard_button(text: "#{tag.location}")
%td
- if tag.revision
%span.has-tooltip{ title: "#{tag.revision}" }
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
index 635580eac5c..9c69aedfbfc 100644
--- a/app/views/projects/serverless/functions/index.html.haml
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -5,7 +5,10 @@
- status_path = project_serverless_functions_path(@project, format: :json)
- clusters_path = project_clusters_path(@project)
-.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path, installed: @installed, clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } }
+.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path,
+ installed: @installed,
+ clusters_path: clusters_path,
+ help_path: help_page_path('user/project/clusters/serverless/index') } }
%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
.js-serverless-functions-notice
diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml
index 29737b7014a..d1fe208ce60 100644
--- a/app/views/projects/serverless/functions/show.html.haml
+++ b/app/views/projects/serverless/functions/show.html.haml
@@ -1,14 +1,19 @@
- @no_container = true
- @content_class = "limit-container-width" unless fluid_layout
+- clusters_path = project_clusters_path(@project)
+- help_path = help_page_path('user/project/clusters/serverless/index')
- add_to_breadcrumbs('Serverless', project_serverless_functions_path(@project))
- page_title @service[:name]
-.serverless-function-details-page.js-serverless-function-details-page{ data: { service: @service.as_json } }
+.serverless-function-details-page.js-serverless-function-details-page{ data: { service: @service.as_json,
+ prometheus: @prometheus,
+ clusters_path: clusters_path,
+ help_path: help_path } }
+
%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
- .top-area.adjust
- .serverless-function-details#js-serverless-function-details
+ .serverless-function-details#js-serverless-function-details
.js-serverless-function-notice
.flash-container
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 9409418bbcc..82c1d57c97e 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -18,22 +18,22 @@
.help-form
.form-group
- = label_tag :display_name, 'Display name', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :display_name, 'Display name', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#display_name', class: 'input-group-text')
.form-group
- = label_tag :description, 'Description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :description, 'Description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#description', class: 'input-group-text')
.form-group
- = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
+ = label_tag nil, 'Command trigger word', class: 'col-12 col-form-label label-bold'
+ .col-12
%p Fill in the word that works best for your team.
%p
Suggestions:
@@ -42,44 +42,44 @@
%code= @project.full_path
.form-group
- = label_tag :request_url, 'Request URL', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :request_url, 'Request URL', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :request_url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#request_url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Request method', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block POST
+ = label_tag nil, 'Request method', class: 'col-12 col-form-label label-bold'
+ .col-12 POST
.form-group
- = label_tag :response_username, 'Response username', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :response_username, 'Response username', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :response_username, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_username', class: 'input-group-text')
.form-group
- = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :response_icon, 'Response icon', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_icon', class: 'input-group-text')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block Yes
+ = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ .col-12 Yes
.form-group
- = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-12 col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_hint', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 9a7004f89c0..9b7732abc62 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -27,8 +27,8 @@
.help-form
.form-group
- = label_tag nil, 'Command', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
+ = label_tag nil, 'Command', class: 'col-12 col-form-label label-bold'
+ .col-12
%p Fill in the word that works best for your team.
%p
Suggestions:
@@ -37,50 +37,50 @@
%code= @project.full_path
.form-group
- = label_tag :url, 'URL', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :url, 'URL', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Method', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block POST
+ = label_tag nil, 'Method', class: 'col-12 col-form-label label-bold'
+ .col-12 POST
.form-group
- = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :customize_name, 'Customize name', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :customize_name, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#customize_name', class: 'input-group-text')
.form-group
- = label_tag nil, 'Customize icon', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
- = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36)
+ = label_tag nil, 'Customize icon', class: 'col-12 col-form-label label-bold'
+ .col-12
+ = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36, class: 'mr-3')
= link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block Show this command in the autocomplete list
+ = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ .col-12 Show this command in the autocomplete list
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_usage_hint', class: 'input-group-text')
.form-group
- = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :descriptive_label, 'Descriptive label', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#descriptive_label', class: 'input-group-text')
diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml
new file mode 100644
index 00000000000..520f342f567
--- /dev/null
+++ b/app/views/projects/settings/_general.html.haml
@@ -0,0 +1,42 @@
+= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
+ = form_errors(@project)
+
+ %fieldset
+ .row
+ .form-group.col-md-5
+ = f.label :name, class: 'label-bold', for: 'project_name_edit' do
+ = _('Project name')
+ = f.text_field :name, class: 'form-control qa-project-name-field', id: "project_name_edit"
+
+ .form-group.col-md-7
+ = f.label :id, class: 'label-bold' do
+ = _('Project ID')
+ = f.text_field :id, class: 'form-control w-auto', readonly: true
+
+ .row
+ .form-group.col-md-9
+ = f.label :tag_list, _('Topics'), class: 'label-bold'
+ = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
+ %p.form-text.text-muted= _('Separate topics with commas.')
+
+ .row
+ .form-group.col-md-9
+ = f.label :description, _('Project description (optional)'), class: 'label-bold'
+ = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+
+ .row= render_if_exists 'projects/classification_policy_settings', f: f
+
+ = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
+
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.s90
+ = project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
+ = f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_avatar_button', f: f
+ - if @project.avatar?
+ %hr
+ = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
+
+
+ = f.submit _('Save changes'), class: "btn btn-success mt-4 qa-save-naming-topics-avatar-button"
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 8c4d1c32ebe..fe74dc122c3 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -8,19 +8,20 @@
.card.auto-devops-card
.card-body
.form-check
- = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: @project.auto_devops_enabled?
+ = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled
= form.label :enabled, class: 'form-check-label' do
%strong= s_('CICD|Default to Auto DevOps pipeline')
- - if @project.has_auto_devops_implicitly_enabled?
- %span.badge.badge-info.js-instance-default-badge= s_('CICD|instance enabled')
+ - if auto_devops_enabled
+ %span.badge.badge-info.js-instance-default-badge= badge_for_auto_devops_scope(@project)
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
- .card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
- %p.settings-message.text-center
- - kubernetes_cluster_link = help_page_path('user/project/clusters/index')
- - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
- = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
+ .card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' }
+ - if @project.all_clusters.empty?
+ %p.settings-message.text-center
+ - kubernetes_cluster_link = help_page_path('user/project/clusters/index')
+ - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
+ = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
%label.prepend-top-10
%strong= s_('CICD|Deployment strategy')
.form-check
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index bfb275b9ef5..2d108a1cba5 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -26,6 +26,14 @@
%hr
.form-group
+ = f.fields_for :ci_cd_settings_attributes, @project.ci_cd_settings do |form|
+ = form.label :default_git_depth, _('Git shallow clone'), class: 'label-bold'
+ = form.number_field :default_git_depth, { class: 'form-control', min: 0, max: 1000 }
+ %p.form-text.text-muted
+ = _('The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time.')
+
+ %hr
+ .form-group
= f.label :build_timeout_human_readable, _('Timeout'), class: 'label-bold'
= f.text_field :build_timeout_human_readable, class: 'form-control'
%p.form-text.text-muted
@@ -102,17 +110,20 @@
tap --coverage-report=text-summary (NodeJS) -
%code ^Statements\s*:\s*([^%]+)
%li
+ nyc npm test (NodeJS) -
+ %code All files[^|]*\|[^|]*\s+([\d\.]+)
+ %li
excoveralls (Elixir) -
%code \[TOTAL\]\s+(\d+\.\d+)%
%li
+ mix test --cover (Elixir) -
+ %code \d+.\d+\%\s+\|\s+Total
+ %li
JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})%
%li
go test -cover (Go)
%code coverage: \d+.\d+% of statements
- %li
- nyc npm test (NodeJS) -
- %code All files[^|]*\|[^|]*\s+([\d\.]+)
= f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 6966bf96724..5e3e1076c2c 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -2,7 +2,7 @@
- page_title _("CI / CD Settings")
- page_title _("CI / CD")
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- general_expanded = @project.errors.empty? ? expanded : true
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
@@ -26,7 +26,7 @@
= s_('CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.')
= link_to s_('CICD|Learn more about Auto DevOps'), help_page_path('topics/autodevops/index.md')
.settings-content
- = render 'autodevops_form'
+ = render 'autodevops_form', auto_devops_enabled: @project.auto_devops_enabled?
= render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 4911e8d3770..583fc08f375 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -2,29 +2,19 @@
- setting = error_tracking_setting
-%section.settings.expanded.border-0.no-animate
+%section.settings.no-animate.js-error-tracking-settings
.settings-header
%h4
= _('Error Tracking')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = _('Expand')
%p
= _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
+ = link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
- = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
- = form_errors(@project)
- .form-group
- = f.fields_for :error_tracking_setting_attributes, setting do |form|
- .form-check.form-group
- = form.check_box :enabled, class: 'form-check-input'
- = form.label :enabled, _('Active'), class: 'form-check-label'
- .form-group
- = form.label :api_url, _('Sentry API URL'), class: 'label-bold'
- = form.url_field :api_url, class: 'form-control', placeholder: _('http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/')
- %p.form-text.text-muted
- = _('Enter your Sentry API URL')
- .form-group
- = form.label :token, _('Auth Token'), class: 'label-bold'
- = form.text_field :token, class: 'form-control'
- %p.form-text.text-muted
- = _('Find and manage Auth Tokens in your Sentry account settings page.')
-
- = f.submit _('Save changes'), class: 'btn btn-success'
+ .js-error-tracking-form{ data: { list_projects_endpoint: list_projects_project_error_tracking_index_path(@project, format: :json),
+ operations_settings_endpoint: project_settings_operations_path(@project),
+ project: error_tracking_setting_project_json,
+ api_host: setting.api_host,
+ enabled: setting.enabled.to_json,
+ token: setting.token } }
diff --git a/app/views/projects/settings/operations/_external_dashboard.html.haml b/app/views/projects/settings/operations/_external_dashboard.html.haml
new file mode 100644
index 00000000000..a124283921d
--- /dev/null
+++ b/app/views/projects/settings/operations/_external_dashboard.html.haml
@@ -0,0 +1,3 @@
+.js-operation-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
+ external_dashboard: { url: metrics_external_dashboard_url,
+ help_page_path: help_page_path('user/project/operations/link_to_external_dashboard') } } }
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index b36fa9a5f51..0a7a155bc12 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -1,5 +1,8 @@
- @content_class = 'limit-container-width' unless fluid_layout
-- page_title _('Operations')
+- page_title _('Operations Settings')
+- breadcrumb_title _('Operations Settings')
-= render 'projects/settings/operations/error_tracking', expanded: true
+= render_if_exists 'projects/settings/operations/incidents'
+= render 'projects/settings/operations/error_tracking'
+= render 'projects/settings/operations/external_dashboard'
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/projects/settings/repository/_protected_branches.html.haml b/app/views/projects/settings/repository/_protected_branches.html.haml
new file mode 100644
index 00000000000..31630828571
--- /dev/null
+++ b/app/views/projects/settings/repository/_protected_branches.html.haml
@@ -0,0 +1,2 @@
+= render "projects/protected_branches/index"
+= render "projects/protected_tags/index"
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index cb3a035c49e..ff30cc4f6db 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -3,14 +3,17 @@
- @content_class = "limit-container-width" unless fluid_layout
= render "projects/default_branch/show"
+= render_if_exists "projects/push_rules/index"
= render "projects/mirrors/mirror_repos"
-# Protected branches & tags use a lot of nested partials.
-# The shared parts of the views can be found in the `shared` directory.
-# Those are used throughout the actual views. These `shared` views are then
-# reused in EE.
-= render "projects/protected_branches/index"
-= render "projects/protected_tags/index"
+= render "projects/settings/repository/protected_branches"
+
= render @deploy_keys
= render "projects/deploy_tokens/index"
= render "projects/cleanup/show"
+
+= render_if_exists 'shared/promotions/promote_repository_features'
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index cc203cfad86..8bfface3f5a 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -20,9 +20,8 @@
%p
= s_("TagsPage|Can't find HEAD commit for this tag")
- if release && release.description.present?
- .description.prepend-top-default
- .wiki
- = markdown_field(release, :description)
+ .description.md.prepend-top-default
+ = markdown_field(release, :description)
.row-fixed-content.controls.flex-row
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 458096f9dd6..2e78b0bff3e 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -9,7 +9,7 @@
.nav-text.row-main-content
= s_('TagsPage|Tags give the ability to mark specific points in history as being important')
- .nav-controls.row-fixed-content
+ .nav-controls
= form_tag(filter_tags_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index feeaf799f51..59232372150 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -18,12 +18,12 @@
- else
= s_("TagsPage|Can't find HEAD commit for this tag")
- .nav-controls.controls-flex
+ .nav-controls
- if can?(current_user, :push_code, @project)
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= icon("pencil")
= link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
- = icon('files-o')
+ = sprite_icon('folder-open')
= link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
= icon('history')
.btn-container.controls-item
@@ -39,8 +39,7 @@
.append-bottom-default.prepend-top-default
- if @release.description.present?
- .description
- .wiki
- = markdown_field(@release, :description)
+ .description.md
+ = markdown_field(@release, :description)
- else
= s_('TagsPage|This tag has no release notes.')
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 4daacbe157c..4f6c7e1f9a6 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
- if readme.rich_viewer
- %article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
+ %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if vue_file_list_enabled?)] }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index e37fd7624be..065fef606d5 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,3 @@
+- full_title = markdown_field(commit, :full_title)
%span.str-truncated
- = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), title: commit.redacted_full_title_html, class: 'tree-commit-link'
+ = link_to_html full_title, project_commit_path(@project, commit.id), title: full_title, class: 'tree-commit-link'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ec8e5234bd4..ea6349f2f57 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -6,71 +6,74 @@
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- if on_top_of_branch?
- - addtotree_toggle_attributes = { href: '#', 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' }
+ - addtotree_toggle_attributes = { 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' }
- else
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
- %ul.breadcrumb.repo-breadcrumb
- %li.breadcrumb-item
- = link_to project_tree_path(@project, @ref) do
- = @project.path
- - path_breadcrumbs do |title, path|
+ - if vue_file_list_enabled?
+ #js-repo-breadcrumb
+ - else
+ %ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item
- = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - path_breadcrumbs do |title, path|
+ %li.breadcrumb-item
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- - if can_collaborate || can_create_mr_from_fork
- %li.breadcrumb-item
- %a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes }
- = sprite_icon('plus', size: 16, css_class: 'float-left')
- = sprite_icon('arrow-down', size: 16, css_class: 'float-left')
- - if on_top_of_branch?
- .add-to-tree-dropdown
- %ul.dropdown-menu
- - if can_edit_tree?
- %li.dropdown-header
- #{ _('This directory') }
- %li
- = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
- #{ _('New file') }
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
- #{ _('Upload file') }
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
- #{ _('New directory') }
- - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @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
- #{ _('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
- #{ _('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
- #{ _('New directory') }
+ - if can_collaborate || can_create_mr_from_fork
+ %li.breadcrumb-item
+ %button.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes, type: 'button' }
+ = sprite_icon('plus', size: 16, css_class: 'float-left')
+ = sprite_icon('arrow-down', size: 16, css_class: 'float-left')
+ - if on_top_of_branch?
+ .add-to-tree-dropdown
+ %ul.dropdown-menu
+ - if can_edit_tree?
+ %li.dropdown-header
+ #{ _('This directory') }
+ %li
+ = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
+ #{ _('New file') }
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
+ #{ _('Upload file') }
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
+ #{ _('New directory') }
+ - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @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
+ #{ _('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
+ #{ _('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
+ #{ _('New directory') }
- - if can?(current_user, :push_code, @project)
- %li.divider
- %li.dropdown-header
- #{ _('This repository') }
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ - if can?(current_user, :push_code, @project)
+ %li.divider
+ %li.dropdown-header
+ #{ _('This repository') }
+ %li
+ = link_to new_project_branch_path(@project) do
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ #{ _('New tag') }
.tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 94267b6e0cf..77fdf7f001c 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
+- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class }
.wiki-page-header
@@ -15,6 +16,18 @@
= icon('cloud-download')
= _("Clone repository")
+ .dropdown.inline.wiki-sort-dropdown
+ .btn-group{ role: 'group' }
+ .btn-group{ role: 'group' }
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
+ = sort_title
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
+ %li
+ = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title)
+ = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title)
+ = wiki_sort_controls(@project, params[:sort], params[:direction])
+
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 8e1c054b50c..40d674f3fec 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -26,7 +26,7 @@
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
- .wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render_wiki_content(@page)
= render 'sidebar'
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
index d5327a2b4cc..dfcd1c6b19f 100644
--- a/app/views/repository_check_mailer/notify.html.haml
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -6,3 +6,5 @@
%p
= _("You are receiving this message because you are a GitLab administrator for %{url}.") % { url: Gitlab.config.gitlab.url }
+
+= render_if_exists 'repository_check_mailer/email_additional_text'
diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml
index 6b64b337b0e..a2e04fa710f 100644
--- a/app/views/repository_check_mailer/notify.text.haml
+++ b/app/views/repository_check_mailer/notify.text.haml
@@ -3,3 +3,5 @@
= _("View details: %{details_url}") % { details_url: admin_projects_url(last_repository_check_failed: 1) }
= _("You are receiving this message because you are a GitLab administrator for %{url}.") % { url: Gitlab.config.gitlab.url }
+
+= render_if_exists 'repository_check_mailer/email_additional_text'
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index aaf9b973cda..df408e5fb60 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,3 +1,11 @@
+- users = capture_haml do
+ - if search_tabs?(:members)
+ %li{ class: active_when(@scope == 'users') }
+ = link_to search_filter_path(scope: 'users') do
+ Users
+ %span.badge.badge-pill
+ = limited_count(@search_results.limited_users_count)
+
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
@@ -45,6 +53,7 @@
= _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
+ = users
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
@@ -78,3 +87,4 @@
= _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
+ = users
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 4af0c6bf84a..db0dcc8adfb 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -13,3 +13,4 @@
- unless params[:snippets].eql? 'true'
= render 'filter'
= button_tag _("Search"), class: "btn btn-success btn-search"
+ = render_if_exists 'search/form_elasticsearch'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index be7a2436d16..12eb8d7fa81 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,5 +1,6 @@
- if @search_objects.to_a.empty?
= render partial: "search/results/empty"
+ = render_if_exists 'shared/promotions/promote_advanced_search'
- else
.row-content-block
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
@@ -11,7 +12,7 @@
- elsif @group
- link_to_group = link_to(@group.name, @group)
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
-
+ = render_if_exists 'shared/promotions/promote_advanced_search'
.results.prepend-top-10
- if @scope == 'commits'
%ul.content-list.commit-list
@@ -20,9 +21,10 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @search_objects
+ = render 'shared/projects/list', { projects: @search_objects, pipeline_status: false }.merge(@display_options)
- else
- = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
+ - locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope)
+ = render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals
- if @scope != 'projects'
= paginate_collection(@search_objects)
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 2a602095845..bdad07f36d1 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,4 @@
-- project = find_project_for_result_blob(blob)
+- project = find_project_for_result_blob(projects, blob)
- return unless project
- blob = parse_search_result(blob)
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 796782035f2..1f055cdfa31 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,7 +1,7 @@
.search-result-row
%h4
= confidential_icon(issue)
- = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
+ = link_to namespace_project_issue_path(issue.project.namespace.becomes(Namespace), issue.project, issue) do
%span.term.str-truncated= issue.title
- if issue.closed?
%span.badge.badge-danger.prepend-left-5= _("Closed")
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index f0e0af11f27..074bb9bce8d 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
+ = link_to namespace_project_merge_request_path(merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request) do
%span.term.str-truncated= merge_request.title
- if merge_request.merged?
%span.badge.badge-primary.prepend-left-5= _("Merged")
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index 2daa96e34d1..3201f1a7815 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [milestone.project.namespace.becomes(Namespace), milestone.project, milestone] do
+ = link_to namespace_project_milestone_path(milestone.project.namespace.becomes(Namespace), milestone.project, milestone) do
%span.term.str-truncated= milestone.title
- if milestone.description.present?
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index a60a4501557..f17dae0a94c 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -18,7 +18,7 @@
%i.fa.fa-file
%strong= snippet.file_name
- if markup?(snippet.file_name)
- .file-content.wiki
+ .file-content.md.md-file
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
= markup(snippet.file_name, chunk[:data])
diff --git a/app/views/search/results/_user.html.haml b/app/views/search/results/_user.html.haml
new file mode 100644
index 00000000000..8060a1577e4
--- /dev/null
+++ b/app/views/search/results/_user.html.haml
@@ -0,0 +1,10 @@
+%ul.content-list
+ %li
+ .avatar-cell.d-none.d-sm-block
+ = user_avatar(user: user, user_name: user.name, css_class: 'd-none d-sm-inline avatar s40')
+ .user-info
+ = link_to user_path(user), class: 'd-none d-sm-inline' do
+ .item-title
+ = user.name
+ = user_status(user)
+ .cgray= user.to_reference
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 389e4cc75b9..5847751b268 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,5 +1,5 @@
-- project = find_project_for_result_blob(wiki_blob)
+- project = find_project_for_result_blob(projects, wiki_blob)
- wiki_blob = parse_search_result(wiki_blob)
-- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
+- wiki_blob_link = project_wiki_path(project, Pathname.new(wiki_blob.filename).sub_ext(''))
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index ca392e1adfc..22fcfcda297 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -1,6 +1,6 @@
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
-- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
+- noteable_text = show_unsubscribe_title?(noteable) ? %(#{noteable.title} (#{noteable.to_reference})) : %(#{noteable.to_reference})
- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
%h3.page-title
diff --git a/app/views/shared/_choose_avatar_button.html.haml b/app/views/shared/_choose_avatar_button.html.haml
new file mode 100644
index 00000000000..0d46d047134
--- /dev/null
+++ b/app/views/shared/_choose_avatar_button.html.haml
@@ -0,0 +1,4 @@
+%button.btn.js-choose-avatar-button{ type: 'button' }= _("Choose file…")
+%span.file_name.js-avatar-filename= _("No file chosen")
+= f.file_field :avatar, class: "js-avatar-input hidden"
+.form-text.text-muted= _("The maximum file size allowed is 200KB.")
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
deleted file mode 100644
index 0552fe62090..00000000000
--- a/app/views/shared/_choose_group_avatar_button.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%button.btn.js-choose-group-avatar-button{ type: 'button' }= _("Choose File ...")
-%span.file_name.js-avatar-filename= _("No file chosen")
-= f.file_field :avatar, class: "js-group-avatar-input hidden"
-.form-text.text-muted= _("The maximum file size allowed is 200KB.")
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index a2df0347fd6..1e509ea0d1f 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -16,7 +16,12 @@
= ssh_clone_button(project)
%li
= http_clone_button(project)
+ = render_if_exists 'shared/kerberos_clone_button', project: project
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' }
.input-group-append
= clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard")
+
+ = render_if_exists 'shared/geo_modal_button'
+
+= render_if_exists 'shared/geo_modal', project: project
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 1dcf4369253..3967c8148d2 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -2,8 +2,7 @@
.modal-dialog
.modal-content
.modal-header
- %h3.page-title
- Confirmation required
+ %h3.page-title= _('Confirmation required')
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
@@ -11,8 +10,7 @@
%p.text-danger.js-confirm-text
%p
- This action can lead to data loss.
- To prevent accidental actions we ask you to confirm your intention.
+ %span.js-warning-text= _('This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.')
%br
Please type
%code.js-confirm-danger-match= phrase
@@ -21,4 +19,4 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-actions
- = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
+ = submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_delete_label_modal.html.haml b/app/views/shared/_delete_label_modal.html.haml
index b96380923ac..f37dd2cdf02 100644
--- a/app/views/shared/_delete_label_modal.html.haml
+++ b/app/views/shared/_delete_label_modal.html.haml
@@ -2,20 +2,20 @@
.modal-dialog
.modal-content
.modal-header
- %h3.page-title Delete #{render_colored_label(label, tooltip: false)} ?
+ %h3.page-title Delete #{render_label(label, tooltip: false)} ?
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
%p
%strong= label.name
- %span will be permanently deleted from #{label.is_a?(ProjectLabel)? label.project.name : label.group.name}. This cannot be undone.
+ %span will be permanently deleted from #{label.subject_name}. This cannot be undone.
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
= link_to 'Delete label',
- destroy_label_path(label),
+ label.destroy_path,
title: 'Delete',
method: :delete,
class: 'btn btn-remove'
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 5073e6ad48f..d7e57fc0d01 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,4 +1,4 @@
-.file-content.code.js-syntax-highlight
+.file-content.code.js-syntax-highlight.qa-file-content
.line-numbers
- if blob.data.present?
- link_icon = icon('link')
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 7b593ca4f76..d0f9374e832 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -1,11 +1,26 @@
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
+- import_url = Gitlab::UrlSanitizer.new(f.object.import_url)
-.form-group.import-url-data
- = f.label :import_url, class: 'label-bold' do
- %span
- = _('Git repository URL')
+.import-url-data
+ .form-group
+ = f.label :import_url, class: 'label-bold' do
+ %span
+ = _('Git repository URL')
+ = f.text_field :import_url, value: import_url.sanitized_url,
+ autocomplete: 'off', class: 'form-control', placeholder: 'https://gitlab.company.com/group/project.git', required: true
- = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true
+ .row
+ .form-group.col-md-6
+ = f.label :import_url_user, class: 'label-bold' do
+ %span
+ = _('Username (optional)')
+ = f.text_field :import_url_user, value: import_url.user, class: 'form-control', required: false, autocomplete: 'new-password'
+
+ .form-group.col-md-6
+ = f.label :import_url_password, class: 'label-bold' do
+ %span
+ = _('Password (optional)')
+ = f.password_field :import_url_password, class: 'form-control', required: false, autocomplete: 'new-password'
.info-well.prepend-top-20
.well-segment
@@ -13,8 +28,11 @@
%li
= _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
%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>.').html_safe
+ = _('If your HTTP repository is not publicly accessible, add your credentials.')
%li
= import_will_timeout_message(ci_cd_only)
%li
= import_svn_message(ci_cd_only)
+ = render_if_exists 'shared/ci_cd_only_link', ci_cd_only: ci_cd_only
+
+= render_if_exists 'shared/ee/import_form', f: f, ci_cd_only: ci_cd_only
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 6cc8c485666..31a5370a5f8 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -1,4 +1,4 @@
-- note_count = @issuable_meta_data[issuable.id].notes_count
+- note_count = @issuable_meta_data[issuable.id].user_notes_count
- issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 21ea188d7b3..c4b7ef481fd 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -1,13 +1,13 @@
+- label = label.present(issuable_subject: local_assigns[:subject])
- label_css_id = dom_id(label)
- status = label_subscription_status(label, @project).inquiry if current_user
-- subject = local_assigns[:subject]
- use_label_priority = local_assigns.fetch(:use_label_priority, false)
- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority.present? : false)
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
- tooltip_title = label_status_tooltip(label, status) if status
%li.label-list-item{ id: label_css_id, data: { id: label.id } }
- = render "shared/label_row", label: label, subject: subject, force_priority: force_priority
+ = render "shared/label_row", label: label, force_priority: force_priority
%ul.label-actions-list
- if @project
%li.inline
@@ -21,7 +21,7 @@
= sprite_icon('star')
- if can?(current_user, :admin_label, label)
%li.inline
- = link_to edit_label_path(label), class: 'btn btn-transparent label-action edit has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do
+ = link_to label.edit_path, class: 'btn btn-transparent label-action edit has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do
= sprite_icon('pencil')
- if can?(current_user, :admin_label, label)
%li.inline
@@ -30,7 +30,7 @@
= sprite_icon('ellipsis_v')
.dropdown-menu.dropdown-open-left
%ul
- - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
+ - if label.project_label? && label.project.group && can?(current_user, :admin_label, label.project.group)
%li
%button.js-promote-project-label-button.btn.btn-transparent.btn-action{ disabled: true, type: 'button',
data: { url: promote_project_label_path(label.project, label),
@@ -48,7 +48,7 @@
%button.text-danger.remove-row{ type: 'button' }= _('Delete')
- if current_user
%li.inline.label-subscription
- - if can_subscribe_to_label_in_different_levels?(label)
+ - if label.can_subscribe_to_label_in_different_levels?
%button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= _('Unsubscribe')
.dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) }
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index c5ea15a7f63..af11ce94ec5 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,13 +1,10 @@
-- subject = local_assigns[:subject]
- force_priority = local_assigns.fetch(:force_priority, false)
-- show_label_issues_link = defined?(@project) && show_label_issuables_link?(label, :issues, project: @project)
-- show_label_merge_requests_link = defined?(@project) && show_label_issuables_link?(label, :merge_requests, project: @project)
+- subject_or_group_defined = defined?(@project) || defined?(@group)
+- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues, project: @project)
+- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests, project: @project)
.label-name
- - if defined?(@project)
- = link_to_label(label, subject: @project, tooltip: false)
- - else
- = render_colored_label(label, tooltip: false)
+ = render_label(label, tooltip: false)
.label-description
.append-right-default.prepend-left-default
- if label.description.present?
@@ -16,11 +13,13 @@
%ul.label-links
- if show_label_issues_link
%li.label-link-item.inline
- = link_to_label(label, subject: subject) { 'Issues' }
+ = link_to_label(label) { 'Issues' }
- if show_label_merge_requests_link
&middot;
%li.label-link-item.inline
- = link_to_label(label, subject: subject, type: :merge_request) { _('Merge requests') }
+ = link_to_label(label, type: :merge_request) { _('Merge requests') }
- if force_priority
+ &middot;
%li.label-link-item.priority-badge.js-priority-badge.inline.prepend-left-10
.label-badge.label-badge-blue= _('Prioritized label')
+ = render_if_exists 'shared/label_row_epics_link', label: label
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index 8607f87ce0b..a1f21c2a83e 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -4,7 +4,7 @@
- detailed_status = stage.detailed_status(current_user)
- icon_status = "#{detailed_status.icon}_borderless"
- .stage-container.dropdown{ class: klass }
+ .stage-container.mt-0.ml-1.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_ajax_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= sprite_icon(icon_status)
@@ -13,5 +13,5 @@
%ul
%li.js-builds-dropdown-loading.hidden
- .text-center
- %i.fa.fa-spinner.fa-spin{ 'aria-hidden': 'true', 'aria-label': 'Loading' }
+ .loading-container.text-center
+ %span.spinner{ 'aria-label': 'Loading' }
diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml
index 6e2527bd1a1..1e6b6f7c79b 100644
--- a/app/views/shared/_mobile_clone_panel.html.haml
+++ b/app/views/shared/_mobile_clone_panel.html.haml
@@ -13,3 +13,4 @@
- if http_enabled?
%li
= dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
+ = render_if_exists 'shared/mobile_kerberos_clone'
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
index fd576e4fbea..e8f3d888cce 100644
--- a/app/views/shared/_old_visibility_level.html.haml
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -1,6 +1,6 @@
.form-group.row
.col-sm-2.col-form-label
= _('Visibility level')
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ = link_to icon('question-circle'), help_page_path("public_access/public_access"), target: '_blank'
.col-sm-10
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 721a2af8069..8da2ae5111a 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -1,6 +1,6 @@
- if remote_mirror.update_in_progress?
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
= icon("refresh spin")
-- else
+- elsif remote_mirror.enabled?
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
= icon("refresh")
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
index 2530db986e0..d90a6d43761 100644
--- a/app/views/shared/_sidebar_toggle_button.html.haml
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -1,8 +1,8 @@
%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
= sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
= sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
- %span.collapse-text Collapse sidebar
+ %span.collapse-text= _("Collapse sidebar")
= button_tag class: 'close-nav-button', type: 'button' do
= sprite_icon('close', size: 16)
- %span.collapse-text Close sidebar
+ %span.collapse-text= _("Close sidebar")
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index f0d1dd162df..813fccd217b 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -13,14 +13,14 @@
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal, show_sorting_dropdown: false
%script#js-board-promotion{ type: "text/x-template" }= render_if_exists "shared/promotions/promote_issue_board"
-#board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
+#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
.d-none.d-sm-none.d-md-block
= render 'shared/issuable/search_bar', type: :boards, board: board
- .boards-list
- .boards-app-loading.text-center{ "v-if" => "loading" }
- = icon("spinner spin")
- %board{ "v-cloak" => true,
+ .boards-list.w-100.py-3.px-2.text-nowrap
+ .boards-app-loading.w-100.text-center{ "v-if" => "loading" }
+ = icon("spinner spin 2x")
+ %board{ "v-cloak" => "true",
"v-for" => "list in state.lists",
"ref" => "board",
":list" => "list",
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 307a0919a4c..f9cfcabc015 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,8 +1,8 @@
-.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
+.board.d-inline-block.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
":data-id" => "list.id" }
- .board-inner.d-flex.flex-column
- %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
- %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
+ .board-inner.d-flex.flex-column.position-relative.h-100.rounded
+ %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
+ %h3.board-title.m-0.d-flex.align-items-center.py-2.px-3.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset), "p-0 border-bottom-0 justify-content-center": !list.isExpanded }' }
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }",
"aria-hidden": "true" }
@@ -31,9 +31,9 @@
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
- %button.board-delete.has-tooltip.float-right{ type: "button", title: _("Delete list"), "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
+ %button.board-delete.p-0.border-0.has-tooltip.float-right{ type: "button", title: _("Delete list"), ":class": "{ 'd-none': !list.isExpanded }", "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
- .issue-count-badge.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", "v-tooltip": true, data: { placement: "top" } }
+ .issue-count-badge.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", ":class": "{ 'd-none': !list.isExpanded }", "v-tooltip": true, data: { placement: "top" } }
%span.issue-count-badge-count
%icon.mr-1{ name: "issues" }
{{ list.issuesSize }}
@@ -42,6 +42,7 @@
%button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => "isNewIssueShown",
+ ":class": "{ 'd-none': !list.isExpanded }",
"aria-label" => _("New issue"),
"title" => _("New issue"),
data: { placement: "top", container: "body" } }
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index c9ff63f8c45..b4f75967a67 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -2,16 +2,16 @@
%transition{ name: "boards-sidebar-slide" }
%aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
.issuable-sidebar
- .block.issuable-sidebar-header
+ .block.issuable-sidebar-header.position-relative
%span.issuable-header-text.hide-collapsed.float-left
- %strong
+ %strong.bold
{{ issue.title }}
%br/
%span
= render_if_exists "shared/boards/components/sidebar/issue_project_path"
= precede "#" do
{{ issue.iid }}
- %a.gutter-toggle.float-right{ role: "button",
+ %a.gutter-toggle.position-absolute.position-top-0.position-right-0{ role: "button",
href: "#",
"@click.prevent" => "closeSidebar",
"aria-label" => "Toggle sidebar" }
@@ -20,6 +20,7 @@
= render "shared/boards/components/sidebar/assignee"
= render_if_exists "shared/boards/components/sidebar/epic"
= render "shared/boards/components/sidebar/milestone"
+ = render "shared/boards/components/sidebar/time_tracker"
= render "shared/boards/components/sidebar/due_date"
= render "shared/boards/components/sidebar/labels"
= render_if_exists "shared/boards/components/sidebar/weight"
diff --git a/app/views/shared/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 1374da9d82c..af6a519a967 100644
--- a/app/views/shared/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -19,7 +19,7 @@
":data-name" => "assignee.name",
":data-username" => "assignee.username" }
.dropdown
- - dropdown_options = issue_assignees_dropdown_options
+ - dropdown_options = assignees_dropdown_options('issue')
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data,
":data-issuable-id" => "issue.iid" }
= dropdown_options[:title]
diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index 5630375f428..117d56b30f5 100644
--- a/app/views/shared/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -7,7 +7,7 @@
.value
.value-content
%span.no-value{ "v-if" => "!issue.dueDate" }
- = _("No due date")
+ = _("None")
%span.bold{ "v-if" => "issue.dueDate" }
{{ issue.dueDate | due-date }}
- if can_admin_issue?
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 19159684420..c50826a7cda 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -7,10 +7,17 @@
.value.issuable-show-labels.dont-hide
%span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
= _("None")
- %a{ href: "#",
- "v-for" => "label in issue.labels" }
- .badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
- {{ label.title }}
+ %span{ "v-for" => "label in issue.labels" }
+ %span.d-inline-block.position-relative.scoped-label-wrapper{ "v-if" => "showScopedLabels(label)" }
+ %a{ href: '#' }
+ %span.badge.color-label.label{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
+ {{ label.title }}
+ %a.label.scoped-label{ ":href" => "helpLink()" }
+ %i.fa.fa-question-circle{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
+ %a{ href: "#", "v-else" => true }
+ .badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
+ {{ label.title }}
+
- if can_admin_issue?
.selectbox
%input{ type: "hidden",
@@ -21,17 +28,11 @@
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
":data-selected" => "selectedLabels",
":data-labels" => "issue.assignableLabelsEndpoint",
- data: { toggle: "dropdown",
- field_name: "issue[label_names][]",
- show_no: "true",
- show_any: "true",
- project_id: @project&.try(:id),
- namespace_path: @namespace_path,
- project_path: @project.try(:path) } }
+ data: label_dropdown_data(@project, namespace_path: @namespace_path, field_name: "issue[label_names][]") }
%span.dropdown-toggle-text
{{ labelDropdownTitle }}
= icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default"
- if can?(current_user, :admin_label, current_board_parent)
- = render partial: "shared/issuable/label_page_create"
+ = render partial: "shared/issuable/label_page_create", locals: { show_add_list: true }
diff --git a/app/views/shared/boards/components/sidebar/_time_tracker.html.haml b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml
new file mode 100644
index 00000000000..b76d44c5907
--- /dev/null
+++ b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml
@@ -0,0 +1,6 @@
+.block.time-tracking
+ %time-tracker{ ":time-estimate" => "issue.timeEstimate || 0",
+ ":time-spent" => "issue.timeSpent || 0",
+ ":human-time-estimate" => "issue.humanTimeEstimate",
+ ":human-time-spent" => "issue.humanTimeSpent",
+ "root-path" => "#{root_url}" }
diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml
index 913c065e188..bc0dc7f9631 100644
--- a/app/views/shared/deploy_keys/_form.html.haml
+++ b/app/views/shared/deploy_keys/_form.html.haml
@@ -13,8 +13,9 @@
= form.label :key, class: 'col-form-label col-sm-2'
.col-sm-10
%p.light
- Paste a machine public key here. Read more about how to generate it
- = link_to 'here', help_page_path('ssh/README')
+ - link_start = "<a href='#{help_page_path('ssh/README')}' target='_blank' rel='noreferrer noopener'>".html_safe
+ - link_end = '</a>'
+ = _('Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe }
= form.text_area :key, class: 'form-control thin_area', rows: 5
- else
= form.label :fingerprint, class: 'col-form-label col-sm-2'
@@ -28,6 +29,6 @@
.col-sm-10
= deploy_keys_project_form.label :can_push do
= deploy_keys_project_form.check_box :can_push
- %strong Write access allowed
+ %strong= _('Write access allowed')
%p.light.append-bottom-0
- Allow this key to push to repository as well? (Default only allows pull access.)
+ = _('Allow this key to push to repository as well? (Default only allows pull access.)')
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index 25df2fe5cd6..b11cb8a3076 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -5,7 +5,7 @@
- supports_quick_actions = model.new_record?
- if supports_quick_actions
- - preview_url = preview_markdown_path(project, quick_actions_target_type: model.class.name)
+ - preview_url = preview_markdown_path(project, target_type: model.class.name)
- else
- preview_url = preview_markdown_path(project)
diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml
index 1ae6d1f5ee3..f4915440cb2 100644
--- a/app/views/shared/groups/_dropdown.html.haml
+++ b/app/views/shared/groups/_dropdown.html.haml
@@ -24,10 +24,10 @@
%li.divider
%li.js-filter-archived-projects
= link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
- Hide archived projects
+ = _("Hide archived projects")
%li.js-filter-archived-projects
= link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
- Show archived projects
+ = _("Show archived projects")
%li.js-filter-archived-projects
= link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
- Show archived projects only
+ = _("Show archived projects only")
diff --git a/app/views/shared/icons/_emoji_slightly_smiling_face.svg b/app/views/shared/icons/_emoji_slightly_smiling_face.svg
deleted file mode 100644
index 56dbad91554..00000000000
--- a/app/views/shared/icons/_emoji_slightly_smiling_face.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445c.195.625.556 1.131 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
diff --git a/app/views/shared/icons/_emoji_smile.svg b/app/views/shared/icons/_emoji_smile.svg
deleted file mode 100644
index ce645fee46f..00000000000
--- a/app/views/shared/icons/_emoji_smile.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
diff --git a/app/views/shared/icons/_emoji_smiley.svg b/app/views/shared/icons/_emoji_smiley.svg
deleted file mode 100644
index ddfae50e566..00000000000
--- a/app/views/shared/icons/_emoji_smiley.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="nonzero"/></svg>
diff --git a/app/views/shared/icons/_gitea_logo.svg.erb b/app/views/shared/icons/_gitea_logo.svg.erb
new file mode 100644
index 00000000000..c8ddbc5535e
--- /dev/null
+++ b/app/views/shared/icons/_gitea_logo.svg.erb
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!-- Created with Inkscape (http://www.inkscape.org/) --><svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="<%= size %>" height="<%= size %>" viewBox="0 0 135.46667 135.46667" version="1.1" id="svg8" sodipodi:docname="logo.svg" inkscape:version="0.92.1 r15371" inkscape:export-filename="" inkscape:export-xdpi="48.000004" inkscape:export-ydpi="48.000004" style="zoom: 1;"><defs id="defs2"></defs><sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:zoom="0.70710678" inkscape:cx="418.13805" inkscape:cy="177.57445" inkscape:document-units="mm" inkscape:current-layer="layer2" showgrid="false" units="px" width="256px" showguides="false" inkscape:window-width="1920" inkscape:window-height="1137" inkscape:window-x="1912" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:pagecheckerboard="false" inkscape:measure-start="283.373,243.952" inkscape:measure-end="290.267,236.527"><sodipodi:guide position="0,0" orientation="0,512" id="guide3699" inkscape:locked="false"></sodipodi:guide><sodipodi:guide position="135.46667,0" orientation="-512,0" id="guide3701" inkscape:locked="false"></sodipodi:guide><sodipodi:guide position="135.46667,135.46667" orientation="0,-512" id="guide3703" inkscape:locked="false"></sodipodi:guide><sodipodi:guide position="0,135.46667" orientation="512,0" id="guide3705" inkscape:locked="false"></sodipodi:guide></sodipodi:namedview><metadata id="metadata5"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"></dc:type><dc:title></dc:title></cc:Work></rdf:RDF></metadata><g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-161.53334)" style="display:inline"><path d="M27.709937,195.15095 c-9.546573,-0.0272 -22.3392732,6.79805 -21.6317552,23.90397 c1.105534,26.72889 25.4565952,29.20839 35.1916502,29.42301 c1.068023,5.01357 12.521798,22.30563 21.001818,23.21667 h37.15277 c22.27763,-1.66785 38.9607,-75.75671 26.59321,-76.03825 c-46.781583,2.47691 -49.995146,2.13838 -88.599758,0 c-2.495053,-0.0266 -5.972321,-0.49474 -9.707935,-0.5054 z m2.491319,9.45886 c1.351378,13.69267 3.555849,21.70359 8.018216,33.94345 c-11.382872,-1.50473 -21.069822,-5.22443 -22.851515,-19.10984 c-0.950962,-7.4112 2.390428,-15.16769 14.833299,-14.83361 z " id="path3722" sodipodi:nodetypes="sscccccsccsc" inkscape:connector-curvature="0" style="fill:#000;fill-opacity:1;stroke:#000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"></path></g><g inkscape:groupmode="layer" id="layer2" inkscape:label="Layer 2" style="display:inline"><rect style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.24757317;stroke-opacity:1" id="rect4599" width="34.762054" height="34.762054" x="87.508659" y="18.291576" transform="rotate(25.914715)" ry="5.4825778"></rect><path style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26644793px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 79.804947,57.359056 3.241146,1.609954 V 35.255731 h -3.262698 z" id="path4525" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccc"></path></g><g inkscape:groupmode="layer" id="layer3" inkscape:label="Layer 3" style="display:inline"><g style="display:inline" id="g4539"><circle style="fill:#000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" id="path4606" cy="90.077766" r="3.4745038" cx="49.064713" transform="rotate(-19.796137)"></circle><circle style="fill:#000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" id="path4606-3" cy="102.1049" r="3.4745038" cx="36.810425" transform="rotate(-19.796137)"></circle><circle style="fill:#000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" id="path4606-1" cy="111.43928" r="3.4745038" cx="46.484283" transform="rotate(-19.796137)"></circle><rect height="27.261492" style="fill:#000;fill-opacity:1;stroke:none;stroke-width:0.27444693;stroke-opacity:1" x="97.333458" y="18.061695" id="rect4629-8" width="2.6726954" transform="rotate(26.024158)"></rect><path d="M76.558096,68.116343 c12.97589,6.395378 13.012989,4.101862 4.890858,20.907244 " id="path4514" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" style="fill:none;stroke:#000;stroke-width:2.68000007;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"></path></g></g></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_assignees.html.haml b/app/views/shared/issuable/_assignees.html.haml
index ef3d44a9241..24734ed66cf 100644
--- a/app/views/shared/issuable/_assignees.html.haml
+++ b/app/views/shared/issuable/_assignees.html.haml
@@ -1,9 +1,9 @@
- max_render = 4
-- assignees_rendering_overflow = issue.assignees.size > max_render
+- assignees_rendering_overflow = issuable.assignees.size > max_render
- render_count = assignees_rendering_overflow ? max_render - 1 : max_render
-- more_assignees_count = issue.assignees.size - render_count
+- more_assignees_count = issuable.assignees.size - render_count
-- issue.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord
+- issuable.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord
= link_to_member(@project, assignee, name: false, title: "Assigned to :name")
- if more_assignees_count.positive?
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index fd413bd68c8..416b4a34651 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -4,5 +4,5 @@
.dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- if can?(current_user, :admin_label, board.parent)
- = render partial: "shared/issuable/label_page_create"
+ = render partial: "shared/issuable/label_page_create", locals: { show_add_list: true, add_list: true, add_list_class: 'd-none' }
= dropdown_loading
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 909eb738f95..a05a13814ac 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -21,10 +21,7 @@
.title
Assignee
.filter-item
- - if type == :issues
- - field_name = "update[assignee_ids][]"
- - else
- - field_name = "update[assignee_id]"
+ - field_name = "update[assignee_ids][]"
= dropdown_tag("Select assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
.block
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index d5fb85ba0f3..483652852b6 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -8,7 +8,7 @@
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
-- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path_with_defaults, default_label: "Labels"}
+- dropdown_data = label_dropdown_data(@project, labels: labels_filter_path_with_defaults, default_label: "Labels")
- dropdown_data.merge!(data_options)
- label_name = local_assigns.fetch(:label_name, "Labels")
- no_default_styles = local_assigns.fetch(:no_default_styles, false)
@@ -25,7 +25,7 @@
%span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
= multi_label_name(selected, label_name)
= icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
= render partial: "shared/issuable/label_page_create"
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index 55edaa7eda4..a0d3bc64f1f 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -1,4 +1,7 @@
- show_close = local_assigns.fetch(:show_close, true)
+- show_add_list = local_assigns.fetch(:show_add_list, false)
+- add_list = local_assigns.fetch(:add_list, false)
+- add_list_class = local_assigns.fetch(:add_list_class, '')
- subject = @project || @group
.dropdown-page-two.dropdown-new-label
= dropdown_title(create_label_title(subject), options: { back: true, close: show_close })
@@ -6,12 +9,15 @@
.dropdown-labels-error.js-label-error
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
.suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
+ = render_suggested_colors
.dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.default-dropdown-input{ type: "text", placeholder: _('Assign custom color like #FF0000') }
+ - if show_add_list
+ .dropdown-label-input{ class: add_list_class }
+ %label
+ %input.js-add-list{ type: "checkbox", name: "add_list", checked: add_list }
+ %span= _('Add list')
.clearfix
%button.btn.btn-primary.float-left.js-new-label-btn{ type: "button" }
= _('Create')
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index aa4a5f0e0d3..a0fb5229fc3 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -11,7 +11,7 @@
= dropdown_title(title)
- if show_boards_content
.issue-board-dropdown-content
- %p
+ %p.m-0
= content_title
= dropdown_filter(filter_placeholder)
= dropdown_content
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index bdba47ed14d..3d6c5d29d44 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -3,11 +3,11 @@
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
-.issues-filters
- .issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
+.issues-filters{ class: ("w-100" if type == :boards_modal) }
+ .issues-details-filters.filtered-search-block.d-flex{ class: block_css_class, "v-pre" => type == :boards_modal }
- if type == :boards
= render_if_exists "shared/boards/switcher", board: board
- = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form' do
+ = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
- if @can_bulk_update
@@ -71,6 +71,7 @@
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
+ = render_if_exists 'shared/issuable/approver_dropdown'
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } }
@@ -136,6 +137,11 @@
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
+ #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value.monospace
+ {{title}}
= render_if_exists 'shared/issuable/filter_weight', type: type
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 9596c1df20e..3a5adb34ad1 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -73,7 +73,7 @@
%span.bold= issuable_sidebar[:due_date].to_s(:medium)
- else
%span.no-value
- = _('No due date')
+ = _('None')
- if can_edit_issuable
%span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) }
\-
@@ -105,10 +105,8 @@
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- - selected_labels.each do |label|
- = link_to sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label[:title]) do
- %span.badge.color-label.has-tooltip{ style: "background-color: #{label[:color]}; color: #{label[:text_color]}", title: label[:description], data: { container: "body" } }
- = label[:title]
+ - selected_labels.each do |label_hash|
+ = render_label(label_from_hash(label_hash).present(issuable_subject: nil), link: sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label_hash[:title]))
- else
%span.no-value
= _('None')
@@ -116,11 +114,11 @@
- selected_labels.each do |label|
= hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable_type}[label_names][]", ability_name: issuable_type, show_no: "true", show_any: "true", namespace_path: issuable_sidebar[:namespace_path], project_path: issuable_sidebar[:project_path], issue_update: issuable_sidebar[:issuable_json_path], labels: issuable_sidebar[:project_labels_path], display: 'static' } }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: sidebar_label_dropdown_data(issuable_type, issuable_sidebar) }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default"
- if issuable_sidebar.dig(:current_user, :can_admin_label)
= render partial: "shared/issuable/label_page_create"
@@ -160,13 +158,13 @@
%button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
data: { toggle: 'dropdown', display: 'static' } }
= _('Move issue')
- .dropdown-menu.dropdown-menu-selectable
+ .dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
= dropdown_title(_('Move issue'))
= dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search')
= dropdown_content
= dropdown_loading
= dropdown_footer add_content_class: true do
- %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true }
+ %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ type: 'button', disabled: true }
= _('Move')
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index 1a59055f652..ab01094ed6e 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,42 +1,10 @@
- issuable_type = issuable_sidebar[:type]
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
-- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
-- if issuable_type == "issue"
- #js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } }
- .title.hide-collapsed
- = _('Assignee')
- = icon('spinner spin')
-- else
- - assignee = assignees.first
- .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: (issuable_sidebar.dig(:assignee, :name) || _('Assignee')) }
- - if issuable_sidebar[:assignee]
- = link_to_member(@project, assignee, size: 24)
- - else
- = icon('user', 'aria-hidden': 'true')
+#js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } }
.title.hide-collapsed
= _('Assignee')
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
- - if !signed_in
- %a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') }
- = sidebar_gutter_toggle_icon
- .value.hide-collapsed
- - if issuable_sidebar[:assignee]
- = link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
- - unless issuable_sidebar[:assignee][:can_merge]
- %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') }
- = icon('exclamation-triangle', 'aria-hidden': 'true')
- %span.username
- @#{issuable_sidebar[:assignee][:username]}
- - else
- %span.assign-yourself.no-value
- = _('No assignee')
- - if can_edit_issuable
- \-
- %a.js-assign-yourself{ href: '#' }
- = _('assign yourself')
+ = icon('spinner spin')
.selectbox.hide-collapsed
- if assignees.none?
@@ -59,17 +27,15 @@
ability_name: issuable_type,
null_user: true,
display: 'static' } }
- - title = _('Select assignee')
- - if issuable_type == "issue"
- - dropdown_options = issue_assignees_dropdown_options
- - title = dropdown_options[:title]
- - options[:toggle_class] += ' js-multiselect js-save-user-data'
- - data = { field_name: "#{issuable_type}[assignee_ids][]" }
- - data[:multi_select] = true
- - data['dropdown-title'] = title
- - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- - data['max-select'] = dropdown_options[:data][:'max-select'] if dropdown_options[:data][:'max-select']
- - options[:data].merge!(data)
+ - dropdown_options = assignees_dropdown_options(issuable_type)
+ - title = dropdown_options[:title]
+ - options[:toggle_class] += ' js-multiselect js-save-user-data'
+ - data = { field_name: "#{issuable_type}[assignee_ids][]" }
+ - data[:multi_select] = true
+ - data['dropdown-title'] = title
+ - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
+ - data['max-select'] = dropdown_options[:data][:'max-select'] if dropdown_options[:data][:'max-select']
+ - options[:data].merge!(data)
= dropdown_tag(title, options: options)
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index b6ea9185b10..1dd97bc4ed1 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -5,17 +5,18 @@
.dropdown.inline.prepend-left-10.issue-sort-dropdown
.btn-group{ role: 'group' }
.btn-group{ role: 'group' }
- %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
+ %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
= sort_title
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
- = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
- = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
- = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
- = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
- = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
- = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
- = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
+ = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
+ = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
+ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
+ = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
+ = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting)
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value)
diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml
index bc9a1edc39c..a78231b37ce 100644
--- a/app/views/shared/issuable/form/_contribution.html.haml
+++ b/app/views/shared/issuable/form/_contribution.html.haml
@@ -15,6 +15,6 @@
= form.check_box :allow_collaboration, disabled: !issuable.can_allow_collaboration?(current_user), class: 'form-check-input'
= form.label :allow_collaboration, class: 'form-check-label' do
= _('Allow commits from members who can merge to the target branch.')
- = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration')
+ = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration'), target: '_blank', rel: 'noopener noreferrer nofollow'
.form-text.text-muted
= allow_collaboration_unavailable_reason(issuable)
diff --git a/app/views/shared/issuable/form/_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_merge_request_assignee.html.haml
deleted file mode 100644
index 05c03dedd91..00000000000
--- a/app/views/shared/issuable/form/_merge_request_assignee.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-- merge_request = issuable
-.block.assignee
- .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) }
- - if merge_request.assignee
- = link_to_member(@project, merge_request.assignee, size: 24)
- - else
- = icon('user', 'aria-hidden': 'true')
- .title.hide-collapsed
- Assignee
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
- .value.hide-collapsed
- - if merge_request.assignee
- = link_to_member(@project, merge_request.assignee, size: 32, extra_class: 'bold') do
- - unless merge_request.can_be_merged_by?(merge_request.assignee)
- %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
- = icon('exclamation-triangle', 'aria-hidden': 'true')
- %span.username
- = merge_request.assignee.to_reference
- - else
- %span.assign-yourself.no-value
- No assignee
- - if can_edit_issuable
- \-
- %a.js-assign-yourself{ href: '#' }
- assign yourself
-
- .selectbox.hide-collapsed
- = f.hidden_field 'assignee_id', value: merge_request.assignee_id, id: 'issue_assignee_id'
- = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: @project&.id, author_id: merge_request.author_id, field_name: 'merge_request[assignee_id]', issue_update: issuable_json_path(merge_request), ability_name: 'merge_request', null_user: true } })
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index e370dff9526..1e03440a5dc 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -8,11 +8,8 @@
%hr
.row
%div{ class: (has_due_date ? "col-lg-6" : "col-12") }
- .form-group.row.issue-assignee
- - if issuable.is_a?(Issue)
- = render "shared/issuable/form/metadata_issue_assignee", issuable: issuable, form: form, has_due_date: has_due_date
- - else
- = render "shared/issuable/form/metadata_merge_request_assignee", issuable: issuable, form: form, has_due_date: has_due_date
+ .form-group.row.merge-request-assignee
+ = render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date
.form-group.row.issue-milestone
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) }
diff --git a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
new file mode 100644
index 00000000000..5336159e762
--- /dev/null
+++ b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
@@ -0,0 +1,11 @@
+= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-lg-4" : "col-sm-2"}"
+.col-sm-10{ class: ("col-md-8" if has_due_date) }
+ .issuable-form-select-holder.selectbox
+ - issuable.assignees.each do |assignee|
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name, avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
+
+ - if issuable.assignees.length === 0
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
+
+ = dropdown_tag(users_dropdown_label(issuable.assignees), options: assignees_dropdown_options(issuable.to_ability_name))
+ = link_to 'Assign to me', '#', class: "assign-to-me-link qa-assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
diff --git a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
deleted file mode 100644
index 6d4f9ccd66f..00000000000
--- a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-= form.label :assignee_ids, "Assignee", class: "col-form-label #{"col-md-2 col-lg-4" if has_due_date}"
-.col-sm-10{ class: ("col-md-8" if has_due_date) }
- .issuable-form-select-holder.selectbox
- - issuable.assignees.each do |assignee|
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name, avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
-
- - if issuable.assignees.length === 0
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
-
- = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
- = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index 56c4b021eab..75e9ab547ce 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -6,7 +6,7 @@
%div{ class: div_class }
= form.text_field :title, required: true, maxlength: 255, autofocus: true,
- autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', placeholder: _('Title')
+ autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', placeholder: _('Title'), dir: 'auto'
- if issuable.respond_to?(:work_in_progress?)
.form-text.text-muted
diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 7619d0a2e9c..78ff225daad 100644
--- a/app/views/shared/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -2,15 +2,20 @@
= form_errors(@label)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
- = f.text_field :title, class: "form-control qa-label-title", required: true, autofocus: true
+ = f.text_field :title, class: "form-control js-label-title qa-label-title", required: true, autofocus: true
+ = render_if_exists 'shared/labels/create_label_help_text'
+
.form-group.row
- = f.label :description, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :description
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit qa-label-description"
.form-group.row
- = f.label :color, "Background color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, "Background color"
.col-sm-10
.input-group
.input-group-prepend
@@ -20,12 +25,7 @@
Choose any color.
%br
Or you can choose one of the suggested colors below
-
- .suggest-colors
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp;
-
+ = render_suggested_colors
.form-actions
- if @label.persisted?
= f.submit 'Save changes', class: 'btn btn-success js-save-button'
diff --git a/app/views/shared/members/_access_request_links.html.haml b/app/views/shared/members/_access_request_links.html.haml
index f7227b9101e..eac743b5206 100644
--- a/app/views/shared/members/_access_request_links.html.haml
+++ b/app/views/shared/members/_access_request_links.html.haml
@@ -5,7 +5,7 @@
= link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(source) },
- class: 'access-request-link'
+ class: 'access-request-link js-leave-link'
- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
method: :delete,
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 9ec76d82d18..e83ca5eaab8 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -2,9 +2,12 @@
- group = group_link.group
- can_admin_member = can?(current_user, :admin_project_member, @project)
- dom_id = "group_member_#{group_link.id}"
-%li.member.group_member{ id: dom_id }
- %span.list-item-name
- = group_icon(group, class: "avatar s40", alt: '')
+
+-# Note this is just for groups. For individual members please see shared/members/_member
+
+%li.member.group_member.py-2.px-3.d-flex.flex-column.flex-md-row{ id: dom_id }
+ %span.list-item-name.mb-2.m-md-0
+ = group_icon(group, class: "avatar s40 flex-shrink-0 flex-grow-0", alt: '')
.user-info
= link_to group.full_name, group_path(group), class: 'member'
.cgray
@@ -13,10 +16,10 @@
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(group_link.expires_at) }
- .controls.member-controls
- = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group row append-right-5' do
+ .controls.member-controls.align-items-center
+ = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group d-sm-flex' do
= hidden_field_tag "group_link[group_access]", group_link.group_access
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown.mr-sm-2.d-sm-inline-block
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "group_link[group_access]" } }
@@ -32,14 +35,14 @@
= link_to role, "javascript:void(0)",
class: ("is-active" if group_link.group_access == role_id),
data: { id: role_id, el_id: dom_id }
- .prepend-left-5.clearable-input.member-form-control
+ .clearable-input.member-form-control.d-sm-inline-block
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: _('Expiration date'), id: "member_expires_at_#{group.id}", disabled: !can_admin_member
%i.clear-icon.js-clear-input
- if can_admin_member
= link_to project_group_link_path(@project, group_link),
method: :delete,
data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } },
- class: 'btn btn-remove prepend-left-10' do
+ class: 'btn btn-remove m-0 ml-sm-2 align-self-center' do
%span.d-block.d-sm-none
= _("Delete")
= icon('trash', class: 'd-none d-sm-block')
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 2db1f67a793..331283f7eec 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -4,11 +4,14 @@
- member = local_assigns.fetch(:member)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
+- override = member.try(:override)
-%li.member{ class: dom_class(member), id: dom_id(member) }
- %span.list-item-name
+-# Note this is just for individual members. For groups please see shared/members/_group
+
+%li.member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("is-overridden" if override), ("flex-md-row" unless force_mobile_view)], id: dom_id(member) }
+ %span.list-item-name.mb-2.m-md-0
- if user
- = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_user(user, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
= link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
= user_status(user)
@@ -42,7 +45,7 @@
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(member.expires_at) }
- else
- = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
.member= member.invite_email
.cgray
@@ -53,20 +56,22 @@
= time_ago_with_tooltip(member.created_at)
- if show_roles
- current_resource = @project || @group
- .controls.member-controls
+ .controls.member-controls.align-items-center
+ = render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
- if show_controls && member.source == current_resource
- if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
- class: 'btn btn-default prepend-left-10 d-none d-sm-block',
+ class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite')
- if user != current_user && member.can_update?
- = form_for member, remote: true, html: { class: 'js-edit-member-form form-group row append-right-5' } do |f|
+ = form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
+ disabled: member.can_override? && !override,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text
= member.human_access
@@ -80,20 +85,25 @@
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) }
- .prepend-left-5.clearable-input.member-form-control
+ = render_if_exists 'shared/members/ee/revert_ldap_group_sync_option',
+ group: @group,
+ member: member,
+ can_override: member.can_override?
+ .clearable-input.member-form-control{ class: [("d-sm-inline-block" unless force_mobile_view)] }
= f.text_field :expires_at,
+ disabled: member.can_override? && !override,
class: 'form-control js-access-expiration-date js-member-update-control',
placeholder: _('Expiration date'),
id: "member_expires_at_#{member.id}",
data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
- if member.can_approve?
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
- class: 'btn btn-success prepend-left-10',
+ class: "btn btn-success align-self-center m-0 mb-2 #{'mb-sm-0 ml-sm-2' unless force_mobile_view}",
title: _('Grant access') do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _('Grant access')
@@ -105,16 +115,19 @@
= link_to icon('sign-out', text: _('Leave')), polymorphic_path([:leave, member.source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(member.source) },
- class: 'btn btn-remove prepend-left-10'
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- else
= link_to member,
method: :delete,
data: { confirm: remove_member_message(member) },
- class: 'btn btn-remove prepend-left-10',
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}",
title: remove_member_title(member) do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _("Delete")
- unless force_mobile_view
= icon('trash', class: 'd-none d-sm-block')
+ = render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override?
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
+
+= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :confirm, can_override: member.can_override?
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index eba64daaadc..ae3ab2adfd0 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -21,8 +21,7 @@
%span.issuable-number= issuable.to_reference
- labels.each do |label|
- = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- - render_colored_label(label)
+ = render_label(label.present(issuable_subject: project), link: polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }))
%span.assignee-icon
- assignees.each do |assignee|
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index 6797520650d..ecab037e378 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -2,11 +2,10 @@
- labels.each do |label|
- options = { milestone_title: @milestone.title, label_name: label.title }
- %li.is-not-draggable
+ %li.no-border
%span.label-row
%span.label-name
- = link_to milestones_label_path(options) do
- - render_colored_label(label, tooltip: false)
+ = render_label(label, tooltip: false, link: milestones_label_path(options))
%span.prepend-description-left
= markdown_field(label, :description)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 40b8374848e..e99aa3f1ee4 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -32,7 +32,7 @@
= milestone_progress_bar(milestone)
= link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
&middot;
- = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
+ = link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, 'Merge Request'), merge_requests_path
.float-lg-right.light #{milestone.percent_complete(current_user)}% complete
.col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
@@ -52,7 +52,7 @@
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped"
- unless milestone.active?
- = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- if @group
- if can?(current_user, :admin_milestone, @group)
- if milestone.closed?
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 55460acab8f..b877f66c71e 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -12,7 +12,7 @@
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
Merge Requests
- %span.badge.badge-pill= milestone.merge_requests.size
+ %span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
- else
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
@@ -21,11 +21,11 @@
%li.nav-item
= link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
Participants
- %span.badge.badge-pill= milestone.participants.count
+ %span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
%li.nav-item
= link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
Labels
- %span.badge.badge-pill= milestone.labels.count
+ %span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
- issues = milestone.sorted_issues(current_user)
- show_project_name = local_assigns.fetch(:show_project_name, false)
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 55b1c14022f..43503e1d08a 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -4,10 +4,7 @@
- group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
-.detail-page-header
- %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
-
+.detail-page-header.milestone-page-header
.status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
- if milestone.closed?
Closed
@@ -15,14 +12,17 @@
Expired
- else
Open
- %span.identifier
- Milestone #{milestone.title}
- - if milestone.due_date || milestone.start_date
- %span.creator
- &nbsp;&middot;
- = milestone_date_range(milestone)
- - if group
- .float-right
+
+ .header-text-content
+ %span.identifier
+ Milestone #{milestone.title}
+ - if milestone.due_date || milestone.start_date
+ %span.creator
+ &nbsp;&middot;
+ = milestone_date_range(milestone)
+
+ .milestone-buttons
+ - if group
- if can?(current_user, :admin_milestone, group)
- if milestone.group_milestone?
= link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do
@@ -35,6 +35,9 @@
- unless is_dynamic_milestone
= render 'shared/milestones/delete_button'
+ %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
.detail-page-description.milestone-detail
@@ -42,9 +45,8 @@
= markdown_field(milestone, :title)
- if milestone.group_milestone? && milestone.description.present?
%div
- .description
- .wiki
- = markdown_field(milestone, :description)
+ .description.md
+ = markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 6a1eea85fde..d91bc6e57c9 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -1,7 +1,7 @@
- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true)
- supports_quick_actions = note_supports_quick_actions?(@note)
- if supports_quick_actions
- - preview_url = preview_markdown_path(@project, quick_actions_target_type: @note.noteable_type, quick_actions_target_id: @note.noteable_id)
+ - preview_url = preview_markdown_path(@project, target_type: @note.noteable_type, target_id: @note.noteable_id)
- else
- preview_url = preview_markdown_path(@project)
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 46f3f8428f1..fae7d6526e8 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -28,8 +28,9 @@
or
%button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file")
- %button.markdown-selector.button-attach-file{ type: 'button', tabindex: '-1' }
+ %button.markdown-selector.button-attach-file.btn-link{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- = _("Attach a file")
+ %span.text-attach-file<>
+ = _("Attach a file")
%button.btn.btn-default.btn-sm.hide.button-cancel-uploading-files{ type: 'button' }= _("Cancel")
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 41d6ae79c81..5c9dd72418e 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -31,19 +31,18 @@
.note-header
.note-header-info
%a{ href: user_path(note.author) }
- %span.note-header-author-name
+ %span.note-header-author-name.bold
= sanitize(note.author.name)
= user_status(note.author)
%span.note-headline-light
= note.author.to_reference
- %span.note-headline-light
- %span.note-headline-meta
- - if note.system
- %span.system-note-message
- = markdown_field(note, :note)
- %span.system-note-separator
- &middot;
- %a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
+ %span.note-headline-light.note-headline-meta
+ - if note.system
+ %span.system-note-message
+ = markdown_field(note, :note)
+ %span.system-note-separator
+ &middot;
+ %a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- unless note.system?
.note-actions
- if note.for_personal_snippet?
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 2ece7b7f701..749aa258af6 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,24 +1,26 @@
- btn_class = local_assigns.fetch(:btn_class, nil)
- if notification_setting
- .js-notification-dropdown.notification-dropdown.home-panel-action-button.dropdown.inline
+ .js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
= f.hidden_field :level, class: "notification_setting_level"
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
- = icon("bell", class: "js-notification-loading")
- = notification_title(notification_setting.level)
- = icon("caret-down")
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ .float-left
+ = icon("bell", class: "js-notification-loading")
+ = notification_title(notification_setting.level)
+ .float-right
+ = icon("caret-down")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 6d26dbebbc8..052e6da5bae 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -10,14 +10,14 @@
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
- = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon")
+ = notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting - #{notification_title(notification_setting.level)}", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
- = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon")
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ = notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
= sprite_icon("arrow-down", css_class: "icon")
diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml
index 85ad74f9a39..a6ef2d51171 100644
--- a/app/views/shared/notifications/_notification_dropdown.html.haml
+++ b/app/views/shared/notifications/_notification_dropdown.html.haml
@@ -8,5 +8,5 @@
%li.divider
%li
%a.update-notification{ href: "#", role: "button", class: ("is-active" if notification_setting.custom?), data: { toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), notification_level: "custom", notification_title: "Custom" } }
- %strong.dropdown-menu-inner-title Custom
+ %strong.dropdown-menu-inner-title= s_('NotificationSetting|Custom')
%span.dropdown-menu-inner-content= notification_description("custom")
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index 98b258d9275..88ac03bf9e3 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,10 +1,9 @@
- @sort ||= sort_value_latest_activity
.dropdown.js-project-filter-dropdown-wrap
- - toggle_text = projects_sort_options_hash[@sort]
- = dropdown_toggle(toggle_text, { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' })
+ = dropdown_toggle(projects_sort_options_hash[@sort], { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
- Sort by
+ = _("Sort by")
- projects_sort_options_hash.each do |value, title|
%li
= link_to filter_projects_path(sort: value), class: ("is-active" if @sort == value) do
@@ -13,29 +12,29 @@
%li.divider
%li
= link_to filter_projects_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
- Hide archived projects
+ = _("Hide archived projects")
%li
= link_to filter_projects_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
- Show archived projects
+ = _("Show archived projects")
%li
= link_to filter_projects_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
- Show archived projects only
+ = _("Show archived projects only")
- if current_user
%li.divider
%li
= link_to filter_projects_path(personal: nil), class: ("is-active" unless params[:personal].present?) do
- Owned by anyone
+ = _("Owned by anyone")
%li
= link_to filter_projects_path(personal: true), class: ("is-active" if params[:personal].present?) do
- Owned by me
+ = _("Owned by me")
- if @group && @group.shared_projects.present?
%li.divider
%li
= link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do
- All projects
+ = _("All projects")
%li
= link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
- Hide shared projects
+ = _("Hide shared projects")
%li
= link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
- Hide group projects
+ = _("Hide group projects")
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index f1a87faa7ac..90fb067e75d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -10,7 +10,7 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- cache_key = project_list_cache_key(project)
+- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
@@ -85,7 +85,8 @@
= sprite_icon('issues', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_issues_count)
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
+ - pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status
- = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
+ = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path
.updated-note
%span Updated #{updated_tooltip}
diff --git a/app/views/shared/projects/_search_bar.html.haml b/app/views/shared/projects/_search_bar.html.haml
new file mode 100644
index 00000000000..c1f2eaba284
--- /dev/null
+++ b/app/views/shared/projects/_search_bar.html.haml
@@ -0,0 +1,28 @@
+- @sort ||= sort_value_latest_activity
+- project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
+- flex_grow_and_shrink_xs = 'd-flex flex-xs-grow-1 flex-xs-shrink-1 flex-grow-0 flex-shrink-0'
+
+.filtered-search-block.row-content-block.bt-0
+ .filtered-search-wrapper.d-flex.flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap
+ - unless project_tab_filter == :starred
+ .filtered-search-nav.mb-2.mb-lg-0{ class: flex_grow_and_shrink_xs }
+ = render 'dashboard/projects/nav', project_tab_filter: project_tab_filter
+ .filtered-search.d-flex.flex-grow-1.flex-shrink-1.w-100.mb-2.mb-lg-0.ml-0{ class: project_tab_filter == :starred ? "extended-filtered-search-box mb-2 mb-lg-0" : "ml-sm-3" }
+ .btn-group.w-100{ role: "group" }
+ .btn-group.w-100{ role: "group" }
+ .filtered-search-box.m-0
+ .filtered-search-box-input-container.pl-2
+ = render 'shared/projects/search_form', admin_view: false, search_form_placeholder: _("Search projects...")
+ %button.btn.btn-secondary{ type: 'submit', form: 'project-filter-form' }
+ = sprite_icon('search', size: 16, css_class: 'search-icon ')
+ .filtered-search-dropdown.flex-row.align-items-center.mb-2.m-sm-0#filtered-search-visibility-dropdown{ class: flex_grow_and_shrink_xs }
+ .filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold
+ %span
+ = _("Visibility")
+ = render 'explore/projects/filter', has_label: true
+ .filtered-search-dropdown.flex-row.align-items-center.m-sm-0#filtered-search-sorting-dropdown{ class: flex_grow_and_shrink_xs }
+ .filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold
+ %span
+ = _("Sort by")
+ = render 'shared/projects/sort_dropdown'
+
diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml
index 3b5c13ed93a..7c7c0a363ac 100644
--- a/app/views/shared/projects/_search_form.html.haml
+++ b/app/views/shared/projects/_search_form.html.haml
@@ -1,7 +1,10 @@
+- form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : ''
+- placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...'
+
= form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name],
- placeholder: 'Filter by name...',
- class: 'project-filter-form-field form-control input-short js-projects-list-filter',
+ placeholder: placeholder,
+ class: "project-filter-form-field form-control #{form_field_classes}",
spellcheck: false,
id: 'project-filter-form-field',
tabindex: "2",
diff --git a/app/views/shared/projects/_sort_dropdown.html.haml b/app/views/shared/projects/_sort_dropdown.html.haml
new file mode 100644
index 00000000000..f5f940db189
--- /dev/null
+++ b/app/views/shared/projects/_sort_dropdown.html.haml
@@ -0,0 +1,39 @@
+- @sort ||= sort_value_latest_activity
+- toggle_text = projects_sort_option_titles[@sort]
+
+.btn-group.w-100{ role: "group" }
+ .btn-group.w-100.dropdown.js-project-filter-dropdown-wrap{ role: "group" }
+ %button#sort-projects-dropdown.btn.btn-default.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
+ = toggle_text
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
+ %li.dropdown-header
+ = _("Sort by")
+ - projects_sort_options_hash.each do |value, title|
+ %li
+ = link_to title, filter_projects_path(sort: value), class: ("is-active" if toggle_text == title)
+
+ %li.divider
+ %li
+ = link_to filter_projects_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
+ = _("Hide archived projects")
+ %li
+ = link_to filter_projects_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
+ = _("Show archived projects")
+ %li
+ = link_to filter_projects_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
+ = _("Show archived projects only")
+
+ - if current_user && @group && @group.shared_projects.present?
+ %li.divider
+ %li
+ = link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do
+ = _("All projects")
+ %li
+ = link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
+ = _("Hide shared projects")
+ %li
+ = link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
+ = _("Hide group projects")
+
+ = project_sort_direction_button(@sort)
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 3007da0c189..2d2382e469a 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -7,9 +7,10 @@
= form_errors(@snippet)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
- = f.text_field :title, class: 'form-control', required: true, autofocus: true
+ = f.text_field :title, class: 'form-control qa-snippet-title', required: true, autofocus: true
= render 'shared/form_elements/description', model: @snippet, project: @project, form: f
@@ -17,11 +18,12 @@
.file-editor
.form-group.row
- = f.label :file_name, "File", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :file_name, "File"
.col-sm-10
.file-holder.snippet
.js-file-title.file-title
- = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
+ = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name qa-snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
@@ -31,7 +33,7 @@
.form-actions
- if @snippet.new_record?
- = f.submit 'Create snippet', class: "btn-success btn"
+ = f.submit 'Create snippet', class: "btn-success btn qa-create-snippet-button"
- else
= f.submit 'Save changes', class: "btn-success btn"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index a43296aa806..ebb634fe75f 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,6 +1,6 @@
.detail-page-header
.detail-page-header-body
- .snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
+ .snippet-box.qa-snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
%span.sr-only
= visibility_level_label(@snippet.visibility_level)
= visibility_level_icon(@snippet.visibility_level, fw: false)
@@ -17,12 +17,12 @@
= render "snippets/actions"
.snippet-header.limited-header-width
- %h2.snippet-title.prepend-top-0.append-bottom-0
+ %h2.snippet-title.prepend-top-0.append-bottom-0.qa-snippet-title
= markdown_field(@snippet, :title)
- if @snippet.description.present?
- .description
- .wiki
+ .description.qa-snippet-description
+ .md
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
= @snippet.description
@@ -34,7 +34,7 @@
.embed-snippet
.input-group
.input-group-prepend
- %button.btn.btn-svg.embed-toggle.input-group-text{ 'data-toggle': 'dropdown', type: 'button' }
+ %button.btn.btn-svg.embed-toggle.input-group-text.qa-embed-type{ 'data-toggle': 'dropdown', type: 'button' }
%span.js-embed-action= _("Embed")
= sprite_icon('angle-down', size: 12, css_class: 'caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index ef8664e6f47..9952f373156 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -7,7 +7,7 @@
- if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _("Delete")
- = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: _("New snippet") do
+ = link_to new_snippet_path, class: "btn btn-grouped btn-success btn-inverted", title: _("New snippet") do
= _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user)
= link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 114c777bdc2..9d462865471 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -2,7 +2,7 @@
- @hide_breadcrumbs = true
- page_title _("New Snippet")
-.page-title-holder
+.page-title-holder.d-flex.align-items-center
%h1.page-title= _('New Snippet')
.prepend-top-default
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 01b95145937..6e20890a47f 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -3,9 +3,9 @@
.note-actions-item
= link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji 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')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
+ %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.note-actions-item
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index 01acbf8eadd..3191eaa1e2c 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -9,7 +9,7 @@
%i.fa.fa-clock-o
= event.created_at.to_time.in_time_zone.strftime('%-I:%M%P')
- if event.visible_to_user?(current_user)
- - if event.push?
+ - if event.push_action?
#{event.action_name} #{event.ref_type}
%strong
- commits_path = project_commits_path(event.project, event.ref_name)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 211e3eafac6..a71bfd624e4 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,7 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block.user-cover-block.top-area
+ .cover-block.user-cover-block
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
@@ -45,7 +45,7 @@
= emoji_icon(@user.status.emoji)
= markdown_field(@user.status, :message)
- .cover-desc.member-date
+ .cover-desc.member-date.cgray
%p
%span.middle-dot-divider
@#{@user.username}
@@ -53,7 +53,7 @@
%span.middle-dot-divider
= s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) }
- .cover-desc
+ .cover-desc.cgray
- unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
@@ -82,7 +82,7 @@
= @user.organization
- if @user.bio.present?
- .cover-desc
+ .cover-desc.cgray
%p.profile-user-bio
= @user.bio
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index d0fc130b04f..fd0cc5fb24e 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1,11 +1,14 @@
---
- auto_devops:auto_devops_disable
+- auto_merge:auto_merge_process
+
- cronjob:admin_email
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
- cronjob:import_export_project_cleanup
- cronjob:pages_domain_verification_cron
+- cronjob:pages_domain_removal_cron
- cronjob:pipeline_schedule
- cronjob:prune_old_events
- cronjob:remove_expired_group_links
@@ -21,8 +24,10 @@
- cronjob:trending_projects
- cronjob:issue_due_scheduler
- cronjob:prune_web_hook_logs
+- cronjob:schedule_migrate_external_diffs
- gcp_cluster:cluster_install_app
+- gcp_cluster:cluster_patch_app
- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_provision
- gcp_cluster:cluster_wait_for_app_installation
@@ -30,6 +35,8 @@
- gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_configure
- gcp_cluster:cluster_project_configure
+- gcp_cluster:clusters_applications_wait_for_uninstall_app
+- gcp_cluster:clusters_applications_uninstall
- github_import_advance_stage
- github_importer:github_import_import_diff_note
@@ -47,6 +54,9 @@
- github_importer:github_import_stage_import_repository
- hashed_storage:hashed_storage_migrator
+- hashed_storage:hashed_storage_rollbacker
+- hashed_storage:hashed_storage_project_migrate
+- hashed_storage:hashed_storage_project_rollback
- mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service
@@ -67,6 +77,7 @@
- pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished
+- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue
- pipeline_processing:build_success
- pipeline_processing:pipeline_process
@@ -77,6 +88,7 @@
- pipeline_processing:ci_build_schedule
- deployment:deployments_success
+- deployment:deployments_finished
- repository_check:repository_check_clear
- repository_check:repository_check_batch
@@ -114,6 +126,7 @@
- invalid_gpg_signature_update
- irker
- merge
+- migrate_external_diffs
- namespaceless_project_destroy
- new_issue
- new_merge_request
@@ -126,7 +139,6 @@
- project_cache
- project_destroy
- project_export
-- project_migrate_hashed_storage
- project_service
- propagate_service_template
- reactive_caching
@@ -137,6 +149,7 @@
- repository_remove_remote
- system_hook_push
- update_merge_requests
+- update_project_statistics
- upload_checksum
- web_hook
- repository_update_remote_mirror
@@ -146,3 +159,4 @@
- repository_cleanup
- delete_stored_files
- import_issues_csv
+- project_daily_statistics
diff --git a/app/workers/auto_merge_process_worker.rb b/app/workers/auto_merge_process_worker.rb
new file mode 100644
index 00000000000..cd81cdbc60c
--- /dev/null
+++ b/app/workers/auto_merge_process_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AutoMergeProcessWorker
+ include ApplicationWorker
+
+ queue_namespace :auto_merge
+
+ def perform(merge_request_id)
+ MergeRequest.find_by_id(merge_request_id).try do |merge_request|
+ AutoMergeService.new(merge_request.project, merge_request.merge_user)
+ .process(merge_request)
+ end
+ end
+end
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index adc38226405..8e2a18a8fd8 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -30,6 +30,7 @@ class BuildFinishedWorker
# We execute these async as these are independent operations.
BuildHooksWorker.perform_async(build.id)
ArchiveTraceWorker.perform_async(build.id)
+ ExpirePipelineCacheWorker.perform_async(build.pipeline_id)
ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat?
end
end
diff --git a/app/workers/ci/build_prepare_worker.rb b/app/workers/ci/build_prepare_worker.rb
new file mode 100644
index 00000000000..1a35a74ae53
--- /dev/null
+++ b/app/workers/ci/build_prepare_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildPrepareWorker
+ include ApplicationWorker
+ include PipelineQueue
+
+ queue_namespace :pipeline_processing
+
+ def perform(build_id)
+ Ci::Build.find_by_id(build_id).try do |build|
+ Ci::PrepareBuildService.new(build).execute
+ end
+ end
+ end
+end
diff --git a/app/workers/cluster_configure_worker.rb b/app/workers/cluster_configure_worker.rb
index 63e6cc147be..6f64b7ea0ab 100644
--- a/app/workers/cluster_configure_worker.rb
+++ b/app/workers/cluster_configure_worker.rb
@@ -5,8 +5,10 @@ class ClusterConfigureWorker
include ClusterQueue
def perform(cluster_id)
- Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
- Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster)
+ Clusters::Cluster.managed.find_by_id(cluster_id).try do |cluster|
+ if cluster.project_type?
+ Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster)
+ end
end
end
end
diff --git a/app/workers/cluster_patch_app_worker.rb b/app/workers/cluster_patch_app_worker.rb
new file mode 100644
index 00000000000..0549e81ed05
--- /dev/null
+++ b/app/workers/cluster_patch_app_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ClusterPatchAppWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ def perform(app_name, app_id)
+ find_application(app_name, app_id) do |app|
+ Clusters::Applications::PatchService.new(app).execute
+ end
+ end
+end
diff --git a/app/workers/clusters/applications/uninstall_worker.rb b/app/workers/clusters/applications/uninstall_worker.rb
new file mode 100644
index 00000000000..85e8ecc4ad5
--- /dev/null
+++ b/app/workers/clusters/applications/uninstall_worker.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class UninstallWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ def perform(app_name, app_id)
+ find_application(app_name, app_id) do |app|
+ Clusters::Applications::UninstallService.new(app).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb b/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
new file mode 100644
index 00000000000..163c99d3c3c
--- /dev/null
+++ b/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class WaitForUninstallAppWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ INTERVAL = 10.seconds
+ TIMEOUT = 20.minutes
+
+ def perform(app_name, app_id)
+ find_application(app_name, app_id) do |app|
+ Clusters::Applications::CheckUninstallProgressService.new(app).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index d64c2f82a09..25c3a945077 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -53,7 +53,7 @@ module ApplicationWorker
schedule = now + delay.to_i
if schedule <= now
- raise ArgumentError, 'The schedule time must be in the future!'
+ raise ArgumentError, _('The schedule time must be in the future!')
end
Sidekiq::Client.push_bulk('class' => self, 'args' => args_list, 'at' => schedule)
diff --git a/app/workers/concerns/waitable_worker.rb b/app/workers/concerns/waitable_worker.rb
index 27b94a82444..17946bbc5ca 100644
--- a/app/workers/concerns/waitable_worker.rb
+++ b/app/workers/concerns/waitable_worker.rb
@@ -25,11 +25,9 @@ module WaitableWorker
failed = []
args_list.each do |args|
- begin
- new.perform(*args)
- rescue
- failed << args
- end
+ new.perform(*args)
+ rescue
+ failed << args
end
bulk_perform_async(failed) if failed.present?
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index 49c7a403838..7fac7822cf7 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -5,8 +5,8 @@ class CreateGpgSignatureWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id)
- # Older versions of GitPushService may push a single commit ID on the stack.
- # We need this to be backwards compatible.
+ # Older versions of Git::BranchPushService may push a single commit ID on
+ # the stack. We need this to be backwards compatible.
commit_shas = Array(commit_shas)
return if commit_shas.empty?
@@ -20,11 +20,9 @@ class CreateGpgSignatureWorker
# This calculates and caches the signature in the database
commits.each do |commit|
- begin
- Gitlab::Gpg::Commit.new(commit).signature
- rescue => e
- Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
- end
+ Gitlab::Gpg::Commit.new(commit).signature
+ rescue => e
+ Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/deployments/finished_worker.rb b/app/workers/deployments/finished_worker.rb
new file mode 100644
index 00000000000..c9d448d5d18
--- /dev/null
+++ b/app/workers/deployments/finished_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Deployments
+ class FinishedWorker
+ include ApplicationWorker
+
+ queue_namespace :deployment
+
+ def perform(deployment_id)
+ Deployment.find_by_id(deployment_id).try(:execute_hooks)
+ end
+ end
+end
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index 64bc9776d48..838c3be78f0 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -12,13 +12,12 @@ class DetectRepositoryLanguagesWorker
attr_reader :project
# rubocop: disable CodeReuse/ActiveRecord
- def perform(project_id, user_id)
+ def perform(project_id, user_id = nil)
@project = Project.find_by(id: project_id)
- user = User.find_by(id: user_id)
- return unless project && user
+ return unless project
try_obtain_lease do
- ::Projects::DetectRepositoryLanguagesService.new(project, user).execute
+ ::Projects::DetectRepositoryLanguagesService.new(project).execute
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index bf637f82df2..c4bcda2da16 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -24,22 +24,22 @@ class EmailReceiverWorker
reason =
case error
when Gitlab::Email::UnknownIncomingEmail
- "We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+ s_("EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface.")
when Gitlab::Email::SentNotificationNotFoundError
- "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+ s_("EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface.")
when Gitlab::Email::ProjectNotFound
- "We couldn't find the project. Please check if there's any typo."
+ s_("EmailError|We couldn't find the project. Please check if there's any typo.")
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."
+ s_("EmailError|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::UserNotFoundError
- "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+ s_("EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface.")
when Gitlab::Email::UserBlockedError
- "Your account has been blocked. If you believe this is in error, contact a staff member."
+ s_("EmailError|Your account has been blocked. If you believe this is in error, contact a staff member.")
when Gitlab::Email::UserNotAuthorizedError
- "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+ s_("EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member.")
when Gitlab::Email::NoteableNotFoundError
- "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+ s_("EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member.")
when Gitlab::Email::InvalidAttachment
error.message
when Gitlab::Email::InvalidRecordError
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 17ad1d5ab88..ed3e354e4c2 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -52,24 +52,22 @@ class EmailsOnPushWorker
end
valid_recipients(recipients).each do |recipient|
- begin
- send_email(
- recipient,
- project_id,
- author_id: author_id,
- ref: ref,
- action: action,
- compare: compare,
- reverse_compare: reverse_compare,
- diff_refs: diff_refs,
- send_from_committer_email: send_from_committer_email,
- disable_diffs: disable_diffs
- )
-
- # These are input errors and won't be corrected even if Sidekiq retries
- rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
- logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
- end
+ send_email(
+ recipient,
+ project_id,
+ author_id: author_id,
+ ref: ref,
+ action: action,
+ compare: compare,
+ reverse_compare: reverse_compare,
+ diff_refs: diff_refs,
+ send_from_committer_email: send_from_committer_email,
+ disable_diffs: disable_diffs
+ )
+
+ # These are input errors and won't be corrected even if Sidekiq retries
+ rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
+ logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
end
ensure
@email = nil
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 148384600b6..78e68d7bf46 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -11,56 +11,7 @@ class ExpirePipelineCacheWorker
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
return unless pipeline
- store = Gitlab::EtagCaching::Store.new
-
- update_etag_cache(pipeline, store)
-
- Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
+ Ci::ExpirePipelineCacheService.new.execute(pipeline)
end
# rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def project_pipelines_path(project)
- Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
- end
-
- def project_pipeline_path(project, pipeline)
- Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
- end
-
- def commit_pipelines_path(project, commit)
- Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json)
- end
-
- def new_merge_request_pipelines_path(project)
- Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
- end
-
- def each_pipelines_merge_request_path(project, pipeline)
- pipeline.all_merge_requests.each do |merge_request|
- path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json)
-
- yield(path)
- end
- end
-
- # Updates ETag caches of a pipeline.
- #
- # This logic resides in a separate method so that EE can more easily extend
- # it.
- #
- # @param [Ci::Pipeline] pipeline
- # @param [Gitlab::EtagCaching::Store] store
- def update_etag_cache(pipeline, store)
- project = pipeline.project
-
- store.touch(project_pipelines_path(project))
- store.touch(project_pipeline_path(project, pipeline))
- store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
- store.touch(new_merge_request_pipelines_path(project))
- each_pipelines_merge_request_path(project, pipeline) do |path|
- store.touch(path)
- end
- end
end
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index b33e9b1f718..489d6215774 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -23,13 +23,15 @@ class GitGarbageCollectWorker
end
task = task.to_sym
- project.link_pool_repository
+
+ ::Projects::GitDeduplicationService.new(project).execute
+
gitaly_call(task, project.repository.raw_repository)
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc
- project.repository.expire_statistics_caches
+ project.repository.expire_statistics_caches if task != :pack_refs
# In case pack files are deleted, release libgit2 cache and open file
# descriptors ASAP instead of waiting for Ruby garbage collection
@@ -58,7 +60,12 @@ class GitGarbageCollectWorker
## `repository` has to be a Gitlab::Git::Repository
def gitaly_call(task, repository)
- client = Gitlab::GitalyClient::RepositoryService.new(repository)
+ client = if task == :pack_refs
+ Gitlab::GitalyClient::RefService.new(repository)
+ else
+ Gitlab::GitalyClient::RepositoryService.new(repository)
+ end
+
case task
when :gc
client.garbage_collect(bitmaps_enabled?)
@@ -66,6 +73,8 @@ class GitGarbageCollectWorker
client.repack_full(bitmaps_enabled?)
when :incremental_repack
client.repack_incremental
+ when :pack_refs
+ client.pack_refs
end
rescue GRPC::NotFound => e
Gitlab::GitLogger.error("#{__method__} failed:\nRepository not found")
diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb
new file mode 100644
index 00000000000..816e0504db6
--- /dev/null
+++ b/app/workers/hashed_storage/base_worker.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class BaseWorker
+ include ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 30.seconds.to_i
+ LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze
+
+ protected
+
+ def lease_key
+ # we share the same lease key for both migration and rollback so they don't run simultaneously
+ "#{LEASE_KEY_SEGMENT}:#{project_id}"
+ end
+
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+ end
+end
diff --git a/app/workers/hashed_storage/project_migrate_worker.rb b/app/workers/hashed_storage/project_migrate_worker.rb
new file mode 100644
index 00000000000..f00a459a097
--- /dev/null
+++ b/app/workers/hashed_storage/project_migrate_worker.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class ProjectMigrateWorker < BaseWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ attr_reader :project_id
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(project_id, old_disk_path = nil)
+ @project_id = project_id # we need to set this in order to create the lease_key
+
+ try_obtain_lease do
+ project = Project.without_deleted.find_by(id: project_id)
+ break unless project
+
+ old_disk_path ||= project.disk_path
+
+ ::Projects::HashedStorage::MigrationService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/workers/hashed_storage/project_rollback_worker.rb b/app/workers/hashed_storage/project_rollback_worker.rb
new file mode 100644
index 00000000000..55e1d7ab23e
--- /dev/null
+++ b/app/workers/hashed_storage/project_rollback_worker.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class ProjectRollbackWorker < BaseWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ attr_reader :project_id
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(project_id, old_disk_path = nil)
+ @project_id = project_id # we need to set this in order to create the lease_key
+
+ try_obtain_lease do
+ project = Project.without_deleted.find_by(id: project_id)
+ break unless project
+
+ old_disk_path ||= project.disk_path
+
+ ::Projects::HashedStorage::RollbackService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/workers/hashed_storage/rollbacker_worker.rb b/app/workers/hashed_storage/rollbacker_worker.rb
new file mode 100644
index 00000000000..a4da8443787
--- /dev/null
+++ b/app/workers/hashed_storage/rollbacker_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class RollbackerWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ # @param [Integer] start initial ID of the batch
+ # @param [Integer] finish last ID of the batch
+ def perform(start, finish)
+ migrator = Gitlab::HashedStorage::Migrator.new
+ migrator.bulk_rollback(start: start, finish: finish)
+ end
+ end
+end
diff --git a/app/workers/migrate_external_diffs_worker.rb b/app/workers/migrate_external_diffs_worker.rb
new file mode 100644
index 00000000000..debac97af2c
--- /dev/null
+++ b/app/workers/migrate_external_diffs_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: false
+
+class MigrateExternalDiffsWorker
+ include ApplicationWorker
+
+ def perform(merge_request_diff_id)
+ diff = MergeRequestDiff.find_by_id(merge_request_diff_id)
+ return unless diff
+
+ MergeRequests::MigrateExternalDiffsService.new(diff).execute
+ end
+end
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index fe5d27b087d..12400d4e025 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -20,7 +20,7 @@ module ObjectStorage
end
def to_s
- success? ? "Migration successful." : "Error while migrating #{upload.id}: #{error.message}"
+ success? ? _("Migration successful.") : _("Error while migrating %{upload_id}: %{error_message}") % { upload_id: upload.id, error_message: error.message }
end
end
@@ -47,7 +47,7 @@ module ObjectStorage
end
def header(success, failures)
- "Migrated #{success.count}/#{success.count + failures.count} files."
+ _("Migrated %{success_count}/%{total_count} files.") % { success_count: success.count, total_count: success.count + failures.count }
end
def failures(failures)
@@ -75,9 +75,9 @@ module ObjectStorage
model_types = uploads.map(&:model_type).uniq
model_has_mount = mounted_as.nil? || model_class.uploaders[mounted_as] == uploader_class
- raise(SanityCheckError, "Multiple uploaders found: #{uploader_types}") unless uploader_types.count == 1
- raise(SanityCheckError, "Multiple model types found: #{model_types}") unless model_types.count == 1
- raise(SanityCheckError, "Mount point #{mounted_as} not found in #{model_class}.") unless model_has_mount
+ raise(SanityCheckError, _("Multiple uploaders found: %{uploader_types}") % { uploader_types: uploader_types }) unless uploader_types.count == 1
+ raise(SanityCheckError, _("Multiple model types found: %{model_types}") % { model_types: model_types }) unless model_types.count == 1
+ raise(SanityCheckError, _("Mount point %{mounted_as} not found in %{model_class}.") % { mounted_as: mounted_as, model_class: model_class }) unless model_has_mount
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -110,9 +110,9 @@ module ObjectStorage
return if args.count == 4
case args.count
- when 3 then raise SanityCheckError, "Job is missing the `model_type` argument."
+ when 3 then raise SanityCheckError, _("Job is missing the `model_type` argument.")
else
- raise SanityCheckError, "Job has wrong arguments format."
+ raise SanityCheckError, _("Job has wrong arguments format.")
end
end
@@ -126,11 +126,9 @@ module ObjectStorage
def process_uploader(uploader)
MigrationResult.new(uploader.upload).tap do |result|
- begin
- uploader.migrate!(@to_store)
- rescue => e
- result.error = e
- end
+ uploader.migrate!(@to_store)
+ rescue => e
+ result.error = e
end
end
end
diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb
new file mode 100644
index 00000000000..79f38e1b89f
--- /dev/null
+++ b/app/workers/pages_domain_removal_cron_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class PagesDomainRemovalCronWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ def perform
+ PagesDomain.for_removal.find_each do |domain|
+ domain.destroy!
+ rescue => e
+ Raven.capture_exception(e)
+ end
+ end
+end
diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb
index 92d62a15aee..60703c83e9e 100644
--- a/app/workers/pages_domain_verification_cron_worker.rb
+++ b/app/workers/pages_domain_verification_cron_worker.rb
@@ -5,6 +5,8 @@ class PagesDomainVerificationCronWorker
include CronjobQueue
def perform
+ return if Gitlab::Database.read_only?
+
PagesDomain.needs_verification.find_each do |domain|
PagesDomainVerificationWorker.perform_async(domain.id)
end
diff --git a/app/workers/pages_domain_verification_worker.rb b/app/workers/pages_domain_verification_worker.rb
index b3319ff5a13..7817b2ee5fc 100644
--- a/app/workers/pages_domain_verification_worker.rb
+++ b/app/workers/pages_domain_verification_worker.rb
@@ -5,6 +5,8 @@ class PagesDomainVerificationWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
+ return if Gitlab::Database.read_only?
+
domain = PagesDomain.find_by(id: domain_id)
return unless domain
diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb
index c2fbfd2b3a5..0ddad43b8d5 100644
--- a/app/workers/pipeline_metrics_worker.rb
+++ b/app/workers/pipeline_metrics_worker.rb
@@ -30,6 +30,6 @@ class PipelineMetricsWorker
# rubocop: enable CodeReuse/ActiveRecord
def merge_requests(pipeline)
- pipeline.merge_requests.map(&:id)
+ pipeline.merge_requests_as_head_pipeline.map(&:id)
end
end
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index ac4e9710f33..9410fd1a786 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -4,41 +4,11 @@ class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
- # rubocop: disable CodeReuse/ActiveRecord
def perform
- Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
- .preload(:owner, :project).find_each do |schedule|
- begin
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
- .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
- rescue => e
- error(schedule, e)
- ensure
- schedule.schedule_next_run!
+ Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules|
+ schedules.each do |schedule|
+ Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule)
end
end
end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def error(schedule, error)
- failed_creation_counter.increment
-
- Rails.logger.error "Failed to create a scheduled pipeline. " \
- "schedule_id: #{schedule.id} message: #{error.message}"
-
- Gitlab::Sentry
- .track_exception(error,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: schedule.id })
- end
-
- def failed_creation_counter
- @failed_creation_counter ||=
- Gitlab::Metrics.counter(:pipeline_schedule_creation_failed_total,
- "Counter of failed attempts of pipeline schedule creation")
- end
end
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 4f349ed922c..666331e6cd4 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -6,13 +6,7 @@ class PipelineSuccessWorker
queue_namespace :pipeline_processing
- # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
- Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
- MergeRequests::MergeWhenPipelineSucceedsService
- .new(pipeline.project, nil)
- .trigger(pipeline)
- end
+ # no-op
end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index bbd4ab159e4..3f1639ec2ed 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,8 +3,10 @@
class PostReceive
include ApplicationWorker
- def perform(gl_repository, identifier, changes, push_options = [])
- project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
+ PIPELINE_PROCESS_LIMIT = 4
+
+ def perform(gl_repository, identifier, changes, push_options = {})
+ project, repo_type = Gitlab::GlRepository.parse(gl_repository)
if project.nil?
log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"")
@@ -17,10 +19,12 @@ class PostReceive
Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS']
post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options)
- if is_wiki
+ if repo_type.wiki?
process_wiki_changes(post_received)
- else
+ elsif repo_type.project?
process_project_changes(post_received)
+ else
+ # Other repos don't have hooks for now
end
end
@@ -36,23 +40,24 @@ class PostReceive
return false
end
- post_received.changes_refs do |oldrev, newrev, ref|
- if Gitlab::Git.tag_ref?(ref)
- GitTagPushService.new(
- post_received.project,
- @user,
- oldrev: oldrev,
- newrev: newrev,
- ref: ref,
- push_options: post_received.push_options).execute
- elsif Gitlab::Git.branch_ref?(ref)
- GitPushService.new(
+ post_received.enum_for(:changes_refs).with_index do |(oldrev, newrev, ref), index|
+ service_klass =
+ if Gitlab::Git.tag_ref?(ref)
+ Git::TagPushService
+ elsif Gitlab::Git.branch_ref?(ref)
+ Git::BranchPushService
+ end
+
+ if service_klass
+ service_klass.new(
post_received.project,
@user,
oldrev: oldrev,
newrev: newrev,
ref: ref,
- push_options: post_received.push_options).execute
+ push_options: post_received.push_options,
+ create_pipelines: index < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, post_received.project)
+ ).execute
end
changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)
@@ -69,6 +74,8 @@ class PostReceive
def process_wiki_changes(post_received)
post_received.project.touch(:last_activity_at, :last_repository_updated_at)
+ post_received.project.wiki.repository.expire_statistics_caches
+ ProjectCacheWorker.perform_async(post_received.project.id, [], [:wiki_size])
end
def log(message)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 29a7f8e691a..3efb5343a96 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -48,7 +48,7 @@ class ProcessCommitWorker
# Issues::CloseService#execute.
IssueCollection.new(issues).updatable_by_user(user).each do |issue|
Issues::CloseService.new(project, author)
- .close_issue(issue, commit: commit)
+ .close_issue(issue, closed_via: commit)
end
end
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index d27b5e62574..4e8ea903139 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -16,9 +16,11 @@ class ProjectCacheWorker
def perform(project_id, files = [], statistics = [])
project = Project.find_by(id: project_id)
- return unless project && project.repository.exists?
+ return unless project
- update_statistics(project, statistics.map(&:to_sym))
+ update_statistics(project, statistics)
+
+ return unless project.repository.exists?
project.repository.refresh_method_caches(files.map(&:to_sym))
@@ -26,19 +28,28 @@ class ProjectCacheWorker
end
# rubocop: enable CodeReuse/ActiveRecord
+ # NOTE: triggering both an immediate update and one in 15 minutes if we
+ # successfully obtain the lease. That way, we only need to wait for the
+ # statistics to become accurate if they were already updated once in the
+ # last 15 minutes.
def update_statistics(project, statistics = [])
- return unless try_obtain_lease_for(project.id, :update_statistics)
+ return if Gitlab::Database.read_only?
+ return unless try_obtain_lease_for(project.id, statistics)
- Rails.logger.info("Updating statistics for project #{project.id}")
+ Projects::UpdateStatisticsService.new(project, nil, statistics: statistics).execute
- project.statistics.refresh!(only: statistics)
+ UpdateProjectStatisticsWorker.perform_in(LEASE_TIMEOUT, project.id, statistics)
end
private
- def try_obtain_lease_for(project_id, section)
+ def try_obtain_lease_for(project_id, statistics)
Gitlab::ExclusiveLease
- .new("project_cache_worker:#{project_id}:#{section}", timeout: LEASE_TIMEOUT)
+ .new(project_cache_worker_key(project_id, statistics), timeout: LEASE_TIMEOUT)
.try_obtain
end
+
+ def project_cache_worker_key(project_id, statistics)
+ ["project_cache_worker", project_id, *statistics.sort].join(":")
+ end
end
diff --git a/app/workers/project_daily_statistics_worker.rb b/app/workers/project_daily_statistics_worker.rb
new file mode 100644
index 00000000000..101f5c28459
--- /dev/null
+++ b/app/workers/project_daily_statistics_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ProjectDailyStatisticsWorker
+ include ApplicationWorker
+
+ def perform(project_id)
+ project = Project.find_by_id(project_id)
+
+ return unless project&.repository&.exists?
+
+ Projects::FetchStatisticsIncrementService.new(project).execute
+ end
+end
diff --git a/app/workers/project_migrate_hashed_storage_worker.rb b/app/workers/project_migrate_hashed_storage_worker.rb
deleted file mode 100644
index 1c8f313e6e9..00000000000
--- a/app/workers/project_migrate_hashed_storage_worker.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-class ProjectMigrateHashedStorageWorker
- include ApplicationWorker
-
- LEASE_TIMEOUT = 30.seconds.to_i
- LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze
-
- # rubocop: disable CodeReuse/ActiveRecord
- def perform(project_id, old_disk_path = nil)
- uuid = lease_for(project_id).try_obtain
-
- if uuid
- project = Project.find_by(id: project_id)
- return if project.nil? || project.pending_delete?
-
- old_disk_path ||= project.disk_path
-
- ::Projects::HashedStorage::MigrationService.new(project, old_disk_path, logger: logger).execute
- else
- return false
- end
-
- ensure
- cancel_lease_for(project_id, uuid) if uuid
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def lease_for(project_id)
- Gitlab::ExclusiveLease.new(lease_key(project_id), timeout: LEASE_TIMEOUT)
- end
-
- private
-
- def lease_key(project_id)
- # we share the same lease key for both migration and rollback so they don't run simultaneously
- "#{LEASE_KEY_SEGMENT}:#{project_id}"
- end
-
- def cancel_lease_for(project_id, uuid)
- Gitlab::ExclusiveLease.cancel(lease_key(project_id), uuid)
- end
-end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 9ec8bcca4f3..b30864db802 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -3,7 +3,6 @@
class ReactiveCachingWorker
include ApplicationWorker
- # rubocop: disable CodeReuse/ActiveRecord
def perform(class_name, id, *args)
klass = begin
class_name.constantize
@@ -12,7 +11,9 @@ class ReactiveCachingWorker
end
return unless klass
- klass.find_by(klass.primary_key => id).try(:exclusively_update_reactive_cache!, *args)
+ klass
+ .reactive_cache_worker_finder
+ .call(id, *args)
+ .try(:exclusively_update_reactive_cache!, *args)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb
index 41913900571..3497a1f9280 100644
--- a/app/workers/remove_expired_members_worker.rb
+++ b/app/workers/remove_expired_members_worker.rb
@@ -6,11 +6,9 @@ class RemoveExpiredMembersWorker
def perform
Member.expired.find_each do |member|
- begin
- Members::DestroyService.new.execute(member, skip_authorization: true)
- rescue => ex
- logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}")
- end
+ Members::DestroyService.new.execute(member, skip_authorization: true)
+ rescue => ex
+ logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}")
end
end
end
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index f72331c003a..43e0b9db22f 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -21,6 +21,30 @@ class RunPipelineScheduleWorker
Ci::CreatePipelineService.new(schedule.project,
user,
ref: schedule.ref)
- .execute(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
+ .execute!(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
+ rescue Ci::CreatePipelineService::CreateError
+ # no-op. This is a user operation error such as corrupted .gitlab-ci.yml.
+ rescue => e
+ error(schedule, e)
+ end
+
+ private
+
+ def error(schedule, error)
+ failed_creation_counter.increment
+
+ Rails.logger.error "Failed to create a scheduled pipeline. " \
+ "schedule_id: #{schedule.id} message: #{error.message}"
+
+ Gitlab::Sentry
+ .track_exception(error,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
+ extra: { schedule_id: schedule.id })
+ end
+
+ def failed_creation_counter
+ @failed_creation_counter ||=
+ Gitlab::Metrics.counter(:pipeline_schedule_creation_failed_total,
+ "Counter of failed attempts of pipeline schedule creation")
end
end
diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb
new file mode 100644
index 00000000000..70910f7ca04
--- /dev/null
+++ b/app/workers/schedule_migrate_external_diffs_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+
+class ScheduleMigrateExternalDiffsWorker
+ include ApplicationWorker
+ include CronjobQueue
+ include Gitlab::ExclusiveLeaseHelpers
+
+ def perform
+ in_lock(self.class.name.underscore, ttl: 2.hours, retries: 0) do
+ MergeRequests::MigrateExternalDiffsService.enqueue!
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ end
+end
diff --git a/app/workers/todos_destroyer/confidential_issue_worker.rb b/app/workers/todos_destroyer/confidential_issue_worker.rb
index 481fde8c83d..240a5f98ad5 100644
--- a/app/workers/todos_destroyer/confidential_issue_worker.rb
+++ b/app/workers/todos_destroyer/confidential_issue_worker.rb
@@ -5,8 +5,8 @@ module TodosDestroyer
include ApplicationWorker
include TodosDestroyerQueue
- def perform(issue_id)
- ::Todos::Destroy::ConfidentialIssueService.new(issue_id).execute
+ def perform(issue_id = nil, project_id = nil)
+ ::Todos::Destroy::ConfidentialIssueService.new(issue_id: issue_id, project_id: project_id).execute
end
end
end
diff --git a/app/workers/update_project_statistics_worker.rb b/app/workers/update_project_statistics_worker.rb
new file mode 100644
index 00000000000..9a29cc12707
--- /dev/null
+++ b/app/workers/update_project_statistics_worker.rb
@@ -0,0 +1,18 @@
+
+# frozen_string_literal: true
+
+# Worker for updating project statistics.
+class UpdateProjectStatisticsWorker
+ include ApplicationWorker
+
+ # project_id - The ID of the project for which to flush the cache.
+ # statistics - An Array containing columns from ProjectStatistics to
+ # refresh, if empty all columns will be refreshed
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(project_id, statistics = [])
+ project = Project.find_by(id: project_id)
+
+ Projects::UpdateStatisticsService.new(project, nil, statistics: statistics).execute
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+end
diff --git a/babel.config.js b/babel.config.js
index e3de8ef2d83..05554e8763e 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -38,10 +38,15 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') {
plugins.push('babel-plugin-rewire');
}
-// Jest is running in node environment
-if (BABEL_ENV === 'jest') {
- plugins.push('transform-es2015-modules-commonjs');
- plugins.push('dynamic-import-node');
+// Jest is running in node environment, so we need additional plugins
+const isJest = Boolean(process.env.JEST_WORKER_ID);
+if (isJest) {
+ plugins.push('@babel/plugin-transform-modules-commonjs');
+ /*
+ without the following, babel-plugin-istanbul throws an error:
+ https://gitlab.com/gitlab-org/gitlab-ce/issues/58390
+ */
+ plugins.push('babel-plugin-dynamic-import-node');
}
module.exports = { presets, plugins };
diff --git a/bin/background_jobs b/bin/background_jobs
index f28e2f722dc..9d12422b81a 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -38,7 +38,14 @@ start_no_deamonize()
start_sidekiq()
{
- exec bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV -P $sidekiq_pidfile "$@"
+ cmd="exec"
+ chpst=$(which chpst)
+
+ if [ -n "$chpst" ]; then
+ cmd="${cmd} ${chpst} -P"
+ fi
+
+ ${cmd} bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
diff --git a/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
new file mode 100644
index 00000000000..1297e9712fa
--- /dev/null
+++ b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Added code differnces from EE in file 'app/assets/javascripts/pages/projects/project.js' to CE"
+merge_request: 29271
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml b/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml
new file mode 100644
index 00000000000..4c85d4f9acb
--- /dev/null
+++ b/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml
@@ -0,0 +1,5 @@
+---
+title: Added reference, web_path, and relative_position fields to GraphQL Issue
+merge_request: 28998
+author:
+type: changed
diff --git a/changelogs/unreleased/10798-remove-dind-req-for-dast.yml b/changelogs/unreleased/10798-remove-dind-req-for-dast.yml
new file mode 100644
index 00000000000..33545fc7cfd
--- /dev/null
+++ b/changelogs/unreleased/10798-remove-dind-req-for-dast.yml
@@ -0,0 +1,5 @@
+---
+title: Remove dind from DAST template
+merge_request: 28083
+author:
+type: changed
diff --git a/changelogs/unreleased/11105-fix-cs-with-proxy.yml b/changelogs/unreleased/11105-fix-cs-with-proxy.yml
new file mode 100644
index 00000000000..ee32427d20e
--- /dev/null
+++ b/changelogs/unreleased/11105-fix-cs-with-proxy.yml
@@ -0,0 +1,5 @@
+---
+title: Fix proxy support in Container Scanning
+merge_request: 27246
+author:
+type: fixed
diff --git a/changelogs/unreleased/11204-turn-off-mask-by-default.yml b/changelogs/unreleased/11204-turn-off-mask-by-default.yml
new file mode 100644
index 00000000000..5c554e04d45
--- /dev/null
+++ b/changelogs/unreleased/11204-turn-off-mask-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Default masked to false for new variables
+merge_request: 28186
+author:
+type: changed
diff --git a/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml b/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml
new file mode 100644
index 00000000000..6570cb3e2a3
--- /dev/null
+++ b/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Remove support for using Geo with an installation from source
+merge_request: 28737
+author:
+type: other
diff --git a/changelogs/unreleased/1340-request-job-with-age.yml b/changelogs/unreleased/1340-request-job-with-age.yml
new file mode 100644
index 00000000000..766ac008c2e
--- /dev/null
+++ b/changelogs/unreleased/1340-request-job-with-age.yml
@@ -0,0 +1,5 @@
+---
+title: "Added option to filter jobs by age in the /job/request API endpoint."
+merge_request: 1340
+author: Dmitry Chepurovskiy
+type: added
diff --git a/changelogs/unreleased/13453_minimal_atom_fix.yml b/changelogs/unreleased/13453_minimal_atom_fix.yml
new file mode 100644
index 00000000000..e0c18de3b1f
--- /dev/null
+++ b/changelogs/unreleased/13453_minimal_atom_fix.yml
@@ -0,0 +1,5 @@
+---
+title: Added a content field to atom feed
+merge_request: 27652
+author:
+type: fixed
diff --git a/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml b/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml
new file mode 100644
index 00000000000..bb2fc9af2a1
--- /dev/null
+++ b/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Include information if issue was clossed via merge request or commit
+merge_request: 15610
+author: Michał Zając
+type: changed
diff --git a/changelogs/unreleased/237-style-toast-component.yml b/changelogs/unreleased/237-style-toast-component.yml
new file mode 100644
index 00000000000..2420df0ee55
--- /dev/null
+++ b/changelogs/unreleased/237-style-toast-component.yml
@@ -0,0 +1,5 @@
+---
+title: Style the toast component according to design specs.
+merge_request: 27734
+author:
+type: added
diff --git a/changelogs/unreleased/24642-activity_service_optimization.yml b/changelogs/unreleased/24642-activity_service_optimization.yml
deleted file mode 100644
index bdfa769959e..00000000000
--- a/changelogs/unreleased/24642-activity_service_optimization.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Optimize Redis usage in User::ActivityService
-merge_request: 25005
-author:
-type: performance
diff --git a/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml b/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml
new file mode 100644
index 00000000000..8d3501e0171
--- /dev/null
+++ b/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Use FindOrCreateService to create labels and check for existing ones
+merge_request: 27987
+author: Matt Duren
+type: fixed
diff --git a/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml b/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml
new file mode 100644
index 00000000000..e954b97f05b
--- /dev/null
+++ b/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `docker pull` prefix when copying a tag from the registry
+merge_request: 28757
+author: Benedikt Franke
+type: changed
diff --git a/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml b/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml
new file mode 100644
index 00000000000..9c6897babb4
--- /dev/null
+++ b/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml
@@ -0,0 +1,5 @@
+---
+title: Add auto SSL toggle option to Pages domain settings page
+merge_request: 26438
+author:
+type: added
diff --git a/changelogs/unreleased/30138-display-cycle-analytics-issue.yml b/changelogs/unreleased/30138-display-cycle-analytics-issue.yml
new file mode 100644
index 00000000000..c7faa0480bd
--- /dev/null
+++ b/changelogs/unreleased/30138-display-cycle-analytics-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Show data on Cycle Analytics page when value is less than a second
+merge_request: 28507
+author:
+type: fixed
diff --git a/changelogs/unreleased/34555-empty-state-for-starred-projects.yml b/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
deleted file mode 100644
index 926d3a2eecf..00000000000
--- a/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve empty state for starred projects
-merge_request: 25138
-author:
-type: changed
diff --git a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
deleted file mode 100644
index d8658218676..00000000000
--- a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move language setting to preferences
-merge_request: 25427
-author: Fabian Schneider @fabsrc
-type: changed
diff --git a/changelogs/unreleased/37495.yml b/changelogs/unreleased/37495.yml
new file mode 100644
index 00000000000..f6d421fc45b
--- /dev/null
+++ b/changelogs/unreleased/37495.yml
@@ -0,0 +1,5 @@
+---
+title: Add documentation links for confidental and locked discussions
+merge_request: 29073
+author:
+type: changed
diff --git a/changelogs/unreleased/38561-border-radii.yml b/changelogs/unreleased/38561-border-radii.yml
new file mode 100644
index 00000000000..510af18d651
--- /dev/null
+++ b/changelogs/unreleased/38561-border-radii.yml
@@ -0,0 +1,5 @@
+---
+title: Fix border radii on diff files and repo files
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
deleted file mode 100644
index 758b97deb3b..00000000000
--- a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add left margin to 1st time contributor badge
-merge_request: 25216
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/39304-broadcast-message-buttons.yml b/changelogs/unreleased/39304-broadcast-message-buttons.yml
new file mode 100644
index 00000000000..7eb289fca1f
--- /dev/null
+++ b/changelogs/unreleased/39304-broadcast-message-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Update broadcast message action icons
+merge_request: 28496
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
deleted file mode 100644
index 1af49fb6a2c..00000000000
--- a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Require only one parameter when updating a wiki'
-merge_request: 25191
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
deleted file mode 100644
index 742184bbe1e..00000000000
--- a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add ability to set path and name for project on fork using API
-merge_request: 25363
-author:
-type: added
diff --git a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
deleted file mode 100644
index 1c739130fcc..00000000000
--- a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose if the current user can merge a MR'
-merge_request: 25207
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/45687-web-ide-empty-state.yml b/changelogs/unreleased/45687-web-ide-empty-state.yml
new file mode 100644
index 00000000000..9ef148275ab
--- /dev/null
+++ b/changelogs/unreleased/45687-web-ide-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Empty project state for Web IDE
+merge_request: 26556
+author:
+type: added
diff --git a/changelogs/unreleased/47150-update-sshkey.yml b/changelogs/unreleased/47150-update-sshkey.yml
deleted file mode 100644
index 342bdb1e2bc..00000000000
--- a/changelogs/unreleased/47150-update-sshkey.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix validation of certain ed25519 keys
-merge_request: 25115
-author: Merlijn B. W. Wajer
-type: fixed
diff --git a/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml b/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml
new file mode 100644
index 00000000000..21dc170f1ca
--- /dev/null
+++ b/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Position is off when visiting files with anchors
+merge_request: 28913
+author:
+type: fixed
diff --git a/changelogs/unreleased/49041-issue-board-input-height.yml b/changelogs/unreleased/49041-issue-board-input-height.yml
new file mode 100644
index 00000000000..de3fbb2ee11
--- /dev/null
+++ b/changelogs/unreleased/49041-issue-board-input-height.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce height of issue board input to align with buttons
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
deleted file mode 100644
index 8393cb9d282..00000000000
--- a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add API endpoint to get a commit's GPG signature
-merge_request: 25032
-author:
-type: added
diff --git a/changelogs/unreleased/49517-fix-notes-import-export.yml b/changelogs/unreleased/49517-fix-notes-import-export.yml
new file mode 100644
index 00000000000..a9f4d736e0b
--- /dev/null
+++ b/changelogs/unreleased/49517-fix-notes-import-export.yml
@@ -0,0 +1,5 @@
+---
+title: Fix diff notes and discussion notes being exported as regular notes
+merge_request: 28401
+author:
+type: fixed
diff --git a/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml b/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml
new file mode 100644
index 00000000000..307c2bfb49d
--- /dev/null
+++ b/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml
@@ -0,0 +1,5 @@
+---
+title: Fix an error in projects admin when statistics are missing
+merge_request: 28355
+author:
+type: fixed
diff --git a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
deleted file mode 100644
index 3c8b58f3001..00000000000
--- a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose text_color for project and group labels'
-merge_request: 25172
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/50070-legacy-attachments.yml b/changelogs/unreleased/50070-legacy-attachments.yml
new file mode 100644
index 00000000000..95917f2b5b5
--- /dev/null
+++ b/changelogs/unreleased/50070-legacy-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate legacy uploads out of deprecated paths
+merge_request: 24679
+author:
+type: other
diff --git a/changelogs/unreleased/50106-hide-whitespace-changes.yml b/changelogs/unreleased/50106-hide-whitespace-changes.yml
new file mode 100644
index 00000000000..e95953c8665
--- /dev/null
+++ b/changelogs/unreleased/50106-hide-whitespace-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Fix whitespace changes visibility when the related file was initially collapsed
+merge_request: 28950
+author: Ondřej Budai
+type: fixed
diff --git a/changelogs/unreleased/50433-make-emoji-picker-bigger.yml b/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
deleted file mode 100644
index 8fcf41df09d..00000000000
--- a/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make emoji picker bigger
-merge_request: 25187
-author: Jacopo Beschi @jacopo-beschi
-type: changed
diff --git a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
new file mode 100644
index 00000000000..45770e1012c
--- /dev/null
+++ b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
@@ -0,0 +1,5 @@
+---
+title: Add API support for committing changes to different projects in same fork network
+merge_request: 27915
+author:
+type: added
diff --git a/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml b/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml
new file mode 100644
index 00000000000..07bf8b04bbe
--- /dev/null
+++ b/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: "Added the `.extended-height` class to the labels-dropdown"
+merge_request: 28659
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/51636-task-list-api-pderichs.yml b/changelogs/unreleased/51636-task-list-api-pderichs.yml
new file mode 100644
index 00000000000..f18a0936ab2
--- /dev/null
+++ b/changelogs/unreleased/51636-task-list-api-pderichs.yml
@@ -0,0 +1,5 @@
+---
+title: Add task count and completed count to responses of Issue and MR
+merge_request: 28859
+author:
+type: added
diff --git a/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml b/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml
new file mode 100644
index 00000000000..4e16b95ec11
--- /dev/null
+++ b/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml
@@ -0,0 +1,5 @@
+---
+title: Removes duplicated members from api/projects/:id/members/all
+merge_request: 24005
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
deleted file mode 100644
index 84062c6db91..00000000000
--- a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Timer and action name aligned vertically for delayed jobs in pipeline actions'
-merge_request: 25117
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/52424-goodbye-hipchat.yml b/changelogs/unreleased/52424-goodbye-hipchat.yml
deleted file mode 100644
index 26dc904af5f..00000000000
--- a/changelogs/unreleased/52424-goodbye-hipchat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove HipChat integration from GitLab
-merge_request: 22223
-author:
-type: removed
diff --git a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
deleted file mode 100644
index 9329e81eb83..00000000000
--- a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add rectangular project and group avatars
-merge_request: 25098
-author:
-type: other
diff --git a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
deleted file mode 100644
index 7fa01e2835a..00000000000
--- a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide pipeline status when pipelines are disabled on project.
-merge_request: 25204
-author:
-type: fixed
diff --git a/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml b/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml
new file mode 100644
index 00000000000..e09de8ac8fc
--- /dev/null
+++ b/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for multiple job parents in GitLab CI YAML.
+merge_request: 26801
+author: Wolphin (Nikita)
+type: added
diff --git a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
deleted file mode 100644
index e0ed38fc2fa..00000000000
--- a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use limited counter for runner build count in admin page.
-merge_request: 25220
-author:
-type: fixed
diff --git a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
deleted file mode 100644
index 32cca07f58d..00000000000
--- a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove undigested token column from personal_access_tokens table from the database
-merge_request: 22743
-author:
-type: changed
diff --git a/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml
new file mode 100644
index 00000000000..efda07380a4
--- /dev/null
+++ b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml
@@ -0,0 +1,5 @@
+---
+title: Use Redis for CacheMarkDownField on non AR models
+merge_request: 29054
+author:
+type: performance
diff --git a/changelogs/unreleased/54169-flash-warning-rebrush.yml b/changelogs/unreleased/54169-flash-warning-rebrush.yml
new file mode 100644
index 00000000000..420cc26a8cc
--- /dev/null
+++ b/changelogs/unreleased/54169-flash-warning-rebrush.yml
@@ -0,0 +1,5 @@
+---
+title: "Rebrush of flash-warning according to the new design (brighter background and darker font)"
+merge_request: 28916
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/54725-fix-emoji-button-active-state.yml b/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
deleted file mode 100644
index 4f0a436cc87..00000000000
--- a/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix hover and active state colors of award emoji button
-merge_request: 25295
-author:
-type: fixed
diff --git a/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml b/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
deleted file mode 100644
index 92b27f63f82..00000000000
--- a/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Sort tie breaker with id DESC'
-merge_request: 25311
-author: Nermin Vehabovic
-type: changed
diff --git a/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml b/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
deleted file mode 100644
index 41761213d7b..00000000000
--- a/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Require maintainer access to show pages domain settings
-merge_request: 24926
-author:
-type: fixed
diff --git a/changelogs/unreleased/54924-refactor-notes-actions-params.yml b/changelogs/unreleased/54924-refactor-notes-actions-params.yml
deleted file mode 100644
index b6083820401..00000000000
--- a/changelogs/unreleased/54924-refactor-notes-actions-params.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix commenting on commits having SHA1 starting with a large number
-merge_request: 25278
-author:
-type: fixed
diff --git a/changelogs/unreleased/55033-discussion-system-note-alignment.yml b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
new file mode 100644
index 00000000000..27072ec7e12
--- /dev/null
+++ b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Align system note within discussion with other notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/55057-system-message-to-core.yml b/changelogs/unreleased/55057-system-message-to-core.yml
deleted file mode 100644
index 3381879eb4a..00000000000
--- a/changelogs/unreleased/55057-system-message-to-core.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Port System Header and Footer feature to Core
-merge_request: 25241
-author:
-type: added
diff --git a/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml b/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
deleted file mode 100644
index c58cdc19555..00000000000
--- a/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Support Jira transition ID as string'
-merge_request: 24400
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/55125-mr-tab-scrolling.yml b/changelogs/unreleased/55125-mr-tab-scrolling.yml
new file mode 100644
index 00000000000..e03ff6c5060
--- /dev/null
+++ b/changelogs/unreleased/55125-mr-tab-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge request tabs so they no longer scroll
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml b/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
deleted file mode 100644
index 44ea4141632..00000000000
--- a/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Close More Actions tooltip when menu opens
-merge_request: 24285
-author:
-type: fixed
diff --git a/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml b/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml
new file mode 100644
index 00000000000..f7dd8c59a7c
--- /dev/null
+++ b/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml
@@ -0,0 +1,5 @@
+---
+title: Enhance line-height of Activity feed UI
+merge_request: 28856
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/55312-svg.yml b/changelogs/unreleased/55312-svg.yml
deleted file mode 100644
index a6260aeaf2a..00000000000
--- a/changelogs/unreleased/55312-svg.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use export-import svgs from gitlab-svgs
-merge_request: 24954
-author:
-type: other
diff --git a/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml b/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
deleted file mode 100644
index d2f24d6f499..00000000000
--- a/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Ensure that related merge requests are referenced cross-project'
-merge_request: 25222
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/55447-validate-k8s-ca-cert.yml b/changelogs/unreleased/55447-validate-k8s-ca-cert.yml
deleted file mode 100644
index e0448d403da..00000000000
--- a/changelogs/unreleased/55447-validate-k8s-ca-cert.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate kubernetes cluster CA certificate
-merge_request: 24990
-author:
-type: changed
diff --git a/changelogs/unreleased/55447-validate-k8s-credentials.yml b/changelogs/unreleased/55447-validate-k8s-credentials.yml
new file mode 100644
index 00000000000..81f0efdb325
--- /dev/null
+++ b/changelogs/unreleased/55447-validate-k8s-credentials.yml
@@ -0,0 +1,5 @@
+---
+title: Validate Kubernetes credentials at cluster creation
+merge_request: 27403
+author:
+type: added
diff --git a/changelogs/unreleased/55703-md-image-borders.yml b/changelogs/unreleased/55703-md-image-borders.yml
deleted file mode 100644
index 94297a42f6d..00000000000
--- a/changelogs/unreleased/55703-md-image-borders.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only show borders for markdown images in notes
-merge_request: 25448
-author:
-type: fixed
diff --git a/changelogs/unreleased/55893-artifacts-download.yml b/changelogs/unreleased/55893-artifacts-download.yml
deleted file mode 100644
index 30c118b7094..00000000000
--- a/changelogs/unreleased/55893-artifacts-download.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes not working dropdowns in pipelines page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
deleted file mode 100644
index ef3d9844acb..00000000000
--- a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Review App Link to Changed Page if Only One Change Present
-merge_request: 25048
-author:
-type: changed
diff --git a/changelogs/unreleased/56237-api-truncated-commit-title.yml b/changelogs/unreleased/56237-api-truncated-commit-title.yml
deleted file mode 100644
index 1a48d0fda1b..00000000000
--- a/changelogs/unreleased/56237-api-truncated-commit-title.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose full commit title'
-merge_request: 25189
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
deleted file mode 100644
index 7febe175faf..00000000000
--- a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate units from metrics graph
-merge_request: 25485
-author:
-type: fixed
diff --git a/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml b/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
deleted file mode 100644
index 5362ac65038..00000000000
--- a/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add field mergeRequests for project in GraphQL
-merge_request: 24805
-author:
-type: added
diff --git a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
deleted file mode 100644
index 9b7aed82d49..00000000000
--- a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL"
-merge_request: 24910
-author:
-type: changed
diff --git a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
deleted file mode 100644
index ae2d9e18e0b..00000000000
--- a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Indicate if label is a project label'
-merge_request: 25219
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml b/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
deleted file mode 100644
index cc3a60479d3..00000000000
--- a/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add realtime validation for user fullname and username on validation
-merge_request: 25017
-author: Ehsan Abdulqader @EhsanZ
-type: added
diff --git a/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml b/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
deleted file mode 100644
index 5b9253793be..00000000000
--- a/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow empty values such as [] to be stored in reactive cache
-merge_request: 25283
-author:
-type: fixed
diff --git a/changelogs/unreleased/56871-list-issues-error.yml b/changelogs/unreleased/56871-list-issues-error.yml
deleted file mode 100644
index af5585c6b5d..00000000000
--- a/changelogs/unreleased/56871-list-issues-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display error message when API call to list Sentry issues fails
-merge_request: 24936
-author:
-type: fixed
diff --git a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
deleted file mode 100644
index a7af8994852..00000000000
--- a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only load syntax highlight CSS of selected theme
-merge_request: 25232
-author:
-type: performance
diff --git a/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml b/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
deleted file mode 100644
index 11d93b34700..00000000000
--- a/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes functions finder for upgraded Knative app
-merge_request: 25067
-author:
-type: fixed
diff --git a/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml b/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml
new file mode 100644
index 00000000000..c529749670d
--- /dev/null
+++ b/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml
@@ -0,0 +1,5 @@
+---
+title: Removes project_auto_devops#domain column
+merge_request: 28574
+author:
+type: other
diff --git a/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml b/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml
new file mode 100644
index 00000000000..c1c1708f935
--- /dev/null
+++ b/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Removes support for AUTO_DEVOPS_DOMAIN
+merge_request: 28460
+author:
+type: removed
diff --git a/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml b/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml
new file mode 100644
index 00000000000..a2de6cd6d45
--- /dev/null
+++ b/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Merge Request merge checkbox alignment on mobile view
+merge_request: 28845
+author:
+type: fixed
diff --git a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
deleted file mode 100644
index 2e0ae9c3732..00000000000
--- a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Fix docs and parameters for hangouts-chat service'
-merge_request: 25180
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml b/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml
new file mode 100644
index 00000000000..86b6bb20253
--- /dev/null
+++ b/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml
@@ -0,0 +1,5 @@
+---
+title: Fix col-sm-* in forms to keep layout
+merge_request: 24885
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/57223-wiki-finder.yml b/changelogs/unreleased/57223-wiki-finder.yml
deleted file mode 100644
index 5ddf197568d..00000000000
--- a/changelogs/unreleased/57223-wiki-finder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove BATCH_SIZE from WikiFileFinder
-merge_request: 24933
-author:
-type: other
diff --git a/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml b/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
deleted file mode 100644
index 46f82afda62..00000000000
--- a/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Provide expires_in in LFS authentication payload
-merge_request: 25082
-author:
-type: fixed
diff --git a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
deleted file mode 100644
index 6be6a2115b9..00000000000
--- a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for FTP assets for releases
-merge_request: 25071
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/57414-show-pipeline-iid.yml b/changelogs/unreleased/57414-show-pipeline-iid.yml
new file mode 100644
index 00000000000..596ae00e5a3
--- /dev/null
+++ b/changelogs/unreleased/57414-show-pipeline-iid.yml
@@ -0,0 +1,5 @@
+---
+title: Show Pipeline IID everywhere Pipeline ID is shown
+merge_request: 57414
+author: Mike Scott
+type: added
diff --git a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml b/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml
deleted file mode 100644
index 9d9158ca4af..00000000000
--- a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not show file templates when creating a new directory in WebIDE
-merge_request: !25119
-author:
-type: fixed
diff --git a/changelogs/unreleased/57564-contributing-button-border.yml b/changelogs/unreleased/57564-contributing-button-border.yml
deleted file mode 100644
index e5875ef1c0f..00000000000
--- a/changelogs/unreleased/57564-contributing-button-border.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the border style of CONTRIBUTING button when it exists
-merge_request: 25124
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
deleted file mode 100644
index f7d6a6c4863..00000000000
--- a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix import_jid error on project import
-merge_request: 25239
-author:
-type: fixed
diff --git a/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml b/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
deleted file mode 100644
index d89819eee60..00000000000
--- a/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure the base pipeline of a Merge Request belongs to its target branch
-merge_request: 25226
-author:
-type: fixed
diff --git a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
deleted file mode 100644
index 6fb198e1552..00000000000
--- a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project import error importing releases
-merge_request: 25495
-author:
-type: fixed
diff --git a/changelogs/unreleased/57768-remove-vertical-line.yml b/changelogs/unreleased/57768-remove-vertical-line.yml
deleted file mode 100644
index b73b0fa229e..00000000000
--- a/changelogs/unreleased/57768-remove-vertical-line.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove vertical connecting line placeholder from diff discussion notes
-merge_request: 25292
-author:
-type: fixed
diff --git a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
deleted file mode 100644
index 2775d9f4e36..00000000000
--- a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Link from Closed (moved) Issues to Moved Issue
-merge_request: 25300
-author:
-type: added
diff --git a/changelogs/unreleased/57785-create-project-template-for-netlify.yml b/changelogs/unreleased/57785-create-project-template-for-netlify.yml
deleted file mode 100644
index 78e9e3dece5..00000000000
--- a/changelogs/unreleased/57785-create-project-template-for-netlify.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Create Project Template for Netlify
-merge_request: 25453
-author:
-type: changed
diff --git a/changelogs/unreleased/57794-project-template-for-net.yml b/changelogs/unreleased/57794-project-template-for-net.yml
deleted file mode 100644
index bc05ac10aff..00000000000
--- a/changelogs/unreleased/57794-project-template-for-net.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Project template for .NET Core
-merge_request: 25486
-author:
-type: changed
diff --git a/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml b/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml
new file mode 100644
index 00000000000..faa1784ea21
--- /dev/null
+++ b/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve moving an issue results in broken image links in comments
+merge_request: 28654
+author:
+type: fixed
diff --git a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
deleted file mode 100644
index 7cfeb4a0cd7..00000000000
--- a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect the should_remove_source_branch parameter to the merge API
-merge_request: 25525
-author:
-type: fixed
diff --git a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
deleted file mode 100644
index a7a87f60c28..00000000000
--- a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow configuring POSTGRES_VERSION in Auto DevOps
-merge_request: 25500
-author:
-type: added
diff --git a/changelogs/unreleased/58269-separate-update-patch.yml b/changelogs/unreleased/58269-separate-update-patch.yml
new file mode 100644
index 00000000000..e9b44257b07
--- /dev/null
+++ b/changelogs/unreleased/58269-separate-update-patch.yml
@@ -0,0 +1,5 @@
+---
+title: Do not display Update app button when saving Knative domain name
+merge_request: 28904
+author:
+type: changed
diff --git a/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
new file mode 100644
index 00000000000..25cc973159f
--- /dev/null
+++ b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce Gitaly calls to improve performance when rendering suggestions
+merge_request: 29027
+author:
+type: performance
diff --git a/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml b/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml
new file mode 100644
index 00000000000..7e95158a0e0
--- /dev/null
+++ b/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml
@@ -0,0 +1,5 @@
+---
+title: 58404 - setup max depth for GraphQL
+merge_request: 25737
+author: Ken Ding
+type: added
diff --git a/changelogs/unreleased/58538-discussion-top-radius.yml b/changelogs/unreleased/58538-discussion-top-radius.yml
new file mode 100644
index 00000000000..0fb16055623
--- /dev/null
+++ b/changelogs/unreleased/58538-discussion-top-radius.yml
@@ -0,0 +1,5 @@
+---
+title: Fix border radius of discussions
+merge_request: 28490
+author:
+type: fixed
diff --git a/changelogs/unreleased/58632-fix-mr-widget-padding.yml b/changelogs/unreleased/58632-fix-mr-widget-padding.yml
new file mode 100644
index 00000000000..fb4e1791359
--- /dev/null
+++ b/changelogs/unreleased/58632-fix-mr-widget-padding.yml
@@ -0,0 +1,5 @@
+---
+title: Fix padding in MR widget
+merge_request: 28472
+author:
+type: fixed
diff --git a/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml b/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml
new file mode 100644
index 00000000000..53be008816d
--- /dev/null
+++ b/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml
@@ -0,0 +1,5 @@
+---
+title: Enable function features for external Knative installations
+merge_request: 27173
+author:
+type: changed
diff --git a/changelogs/unreleased/59026-replace-favicon.yml b/changelogs/unreleased/59026-replace-favicon.yml
new file mode 100644
index 00000000000..34228bb8438
--- /dev/null
+++ b/changelogs/unreleased/59026-replace-favicon.yml
@@ -0,0 +1,5 @@
+---
+title: Update favicon from next
+merge_request: 28601
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml b/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml
new file mode 100644
index 00000000000..95f08af3cb1
--- /dev/null
+++ b/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml
@@ -0,0 +1,5 @@
+---
+title: Fix padding of unclickable pipeline dropdown items to match links
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
new file mode 100644
index 00000000000..0904f788b6f
--- /dev/null
+++ b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
@@ -0,0 +1,5 @@
+---
+title: Change "Report abuse to GitLab" to more generic wording
+merge_request: 28884
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/59587-add-graphql-logging.yml b/changelogs/unreleased/59587-add-graphql-logging.yml
new file mode 100644
index 00000000000..74c2a734f37
--- /dev/null
+++ b/changelogs/unreleased/59587-add-graphql-logging.yml
@@ -0,0 +1,5 @@
+---
+title: Add dedicated logging for GraphQL queries
+merge_request: 27885
+author:
+type: other
diff --git a/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml b/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml
new file mode 100644
index 00000000000..7863f1f7bbe
--- /dev/null
+++ b/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Prioritize login form on mobile breakpoint
+merge_request: 28360
+author:
+type: changed
diff --git a/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
new file mode 100644
index 00000000000..fdf80c660f7
--- /dev/null
+++ b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
@@ -0,0 +1,5 @@
+---
+title: Default MR checkbox to true in most cases
+merge_request: !28665
+author:
+type: changed
diff --git a/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml b/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml
new file mode 100644
index 00000000000..6891a9ca83c
--- /dev/null
+++ b/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolved JIRA service: NoMethodError: undefined method ''find'' for nil:NilClass'
+merge_request: 28206
+author:
+type: fixed
diff --git a/changelogs/unreleased/60250-remove-mr_push_options-flag.yml b/changelogs/unreleased/60250-remove-mr_push_options-flag.yml
new file mode 100644
index 00000000000..b429d83dcc7
--- /dev/null
+++ b/changelogs/unreleased/60250-remove-mr_push_options-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the mr_push_options feature flag
+merge_request: 28278
+author:
+type: changed
diff --git a/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
new file mode 100644
index 00000000000..90b72ec05c7
--- /dev/null
+++ b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Replaces sidekiq mtail metrics with ruby instrumentation metrics
+merge_request: 29215
+author:
+type: changed
diff --git a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
new file mode 100644
index 00000000000..83b7bd3433e
--- /dev/null
+++ b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
@@ -0,0 +1,5 @@
+---
+title: Update registration form to indicate invalid name or username length on input
+merge_request: 28095
+author: Jiaan Louw
+type: changed
diff --git a/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml b/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml
new file mode 100644
index 00000000000..a9b7aeb3024
--- /dev/null
+++ b/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove ability for group clusters to be automatically configured on creation
+merge_request: 27245
+author:
+type: removed
diff --git a/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml b/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml
new file mode 100644
index 00000000000..4274dc5918c
--- /dev/null
+++ b/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml
@@ -0,0 +1,5 @@
+---
+title: "Fix 500 error when accessing charts with an anonymous user"
+merge_request: 28091
+author: Diego Silva
+type: fixed
diff --git a/changelogs/unreleased/60750-milestone-header.yml b/changelogs/unreleased/60750-milestone-header.yml
new file mode 100644
index 00000000000..62cfdaf6ea7
--- /dev/null
+++ b/changelogs/unreleased/60750-milestone-header.yml
@@ -0,0 +1,5 @@
+---
+title: Fix layout of group milestone header
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/60818_yamllint_project_root.yml b/changelogs/unreleased/60818_yamllint_project_root.yml
new file mode 100644
index 00000000000..b34a50e6a9c
--- /dev/null
+++ b/changelogs/unreleased/60818_yamllint_project_root.yml
@@ -0,0 +1,5 @@
+---
+title: Fix yaml linting for project root *.yml files
+merge_request: 27579
+author: Will Hall
+type: fixed
diff --git a/changelogs/unreleased/60819_yamllint_gitlabci.yml b/changelogs/unreleased/60819_yamllint_gitlabci.yml
new file mode 100644
index 00000000000..aba3b206f7e
--- /dev/null
+++ b/changelogs/unreleased/60819_yamllint_gitlabci.yml
@@ -0,0 +1,5 @@
+---
+title: Fix yaml linting for GitLab CI inside project (.gitlab/ci) *.yml files and CI template files
+merge_request: 27576
+author: Will Hall
+type: fixed
diff --git a/changelogs/unreleased/609120-ref-link.yml b/changelogs/unreleased/609120-ref-link.yml
new file mode 100644
index 00000000000..97c93b7ff53
--- /dev/null
+++ b/changelogs/unreleased/609120-ref-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes Ref link being displayed as raw HTML in the Pipelines page
+merge_request: 28823
+author:
+type: fixed
diff --git a/changelogs/unreleased/60987-emoji-picker-popup.yml b/changelogs/unreleased/60987-emoji-picker-popup.yml
new file mode 100644
index 00000000000..3bccec8e164
--- /dev/null
+++ b/changelogs/unreleased/60987-emoji-picker-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Fix emoji picker visibility issue
+merge_request: 28984
+author:
+type: fixed
diff --git a/changelogs/unreleased/61024-update-resolved-icon.yml b/changelogs/unreleased/61024-update-resolved-icon.yml
new file mode 100644
index 00000000000..4a4de9eb13a
--- /dev/null
+++ b/changelogs/unreleased/61024-update-resolved-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Add check circle filled icon for resolved comments
+merge_request: 28663
+author:
+type: changed
diff --git a/changelogs/unreleased/6104-ee-ce-difference.yml b/changelogs/unreleased/6104-ee-ce-difference.yml
new file mode 100644
index 00000000000..59d31daf0eb
--- /dev/null
+++ b/changelogs/unreleased/6104-ee-ce-difference.yml
@@ -0,0 +1,5 @@
+---
+title: Unified EE/CS differences in repository/show.html
+merge_request: 13562
+author:
+type: other
diff --git a/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml b/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml
new file mode 100644
index 00000000000..53cc0a15417
--- /dev/null
+++ b/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate color inconsistencies in metric graphs
+merge_request: 29127
+author:
+type: fixed
diff --git a/changelogs/unreleased/61049-links-activity-stream.yml b/changelogs/unreleased/61049-links-activity-stream.yml
new file mode 100644
index 00000000000..3aac84adc31
--- /dev/null
+++ b/changelogs/unreleased/61049-links-activity-stream.yml
@@ -0,0 +1,5 @@
+---
+title: Use blue for activity stream links; use monospace font for commit sha
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml b/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml
new file mode 100644
index 00000000000..ed4cf0507c7
--- /dev/null
+++ b/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml
@@ -0,0 +1,5 @@
+---
+title: Bring secondary button styles up to design standard
+merge_request: 27920
+author:
+type: fixed
diff --git a/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml b/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml
new file mode 100644
index 00000000000..7fa15e1c0fd
--- /dev/null
+++ b/changelogs/unreleased/61246-fix-label-click-scroll-to-top.yml
@@ -0,0 +1,5 @@
+---
+title: Fix label click scrolling to top
+merge_request: 29202
+author:
+type: fixed
diff --git a/changelogs/unreleased/61313-fix-dropdown-searchbar.yml b/changelogs/unreleased/61313-fix-dropdown-searchbar.yml
new file mode 100644
index 00000000000..ba191a89da2
--- /dev/null
+++ b/changelogs/unreleased/61313-fix-dropdown-searchbar.yml
@@ -0,0 +1,5 @@
+---
+title: Fix loading.. dropdown at search field
+merge_request: 28275
+author: Pavel Chausov
+type: fixed
diff --git a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
new file mode 100644
index 00000000000..94666ac12ec
--- /dev/null
+++ b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Snippet icon button is misaligned
+merge_request: 28522
+author:
+type: other
diff --git a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
new file mode 100644
index 00000000000..a7f5706058d
--- /dev/null
+++ b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
@@ -0,0 +1,5 @@
+---
+title: Give New Snippet button green outline
+merge_request: 28559
+author:
+type: other
diff --git a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
new file mode 100644
index 00000000000..e446459ffc8
--- /dev/null
+++ b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
@@ -0,0 +1,5 @@
+---
+title: Add hover and focus to Attach a file
+merge_request: 28682
+author:
+type: fixed
diff --git a/changelogs/unreleased/61393-emoji-button.yml b/changelogs/unreleased/61393-emoji-button.yml
new file mode 100644
index 00000000000..2bcfde308db
--- /dev/null
+++ b/changelogs/unreleased/61393-emoji-button.yml
@@ -0,0 +1,5 @@
+---
+title: Change default color of award emoji button
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/61441.yml b/changelogs/unreleased/61441.yml
new file mode 100644
index 00000000000..2ad0c6f62d3
--- /dev/null
+++ b/changelogs/unreleased/61441.yml
@@ -0,0 +1,5 @@
+---
+title: Allow user to set primary email first when 2FA is required
+merge_request: 28097
+author: Kartikey Tanna
+type: fixed
diff --git a/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml b/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml
new file mode 100644
index 00000000000..1a3a019e1f4
--- /dev/null
+++ b/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Add expand/collapse to error tracking settings
+merge_request: 28619
+author:
+type: added
diff --git a/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
new file mode 100644
index 00000000000..718604c9ceb
--- /dev/null
+++ b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Merge request discussion text jumps when resolved
+merge_request: 28995
+author:
+type: fixed
diff --git a/changelogs/unreleased/61606-support-string-piwik-website-ids.yml b/changelogs/unreleased/61606-support-string-piwik-website-ids.yml
new file mode 100644
index 00000000000..5c525294132
--- /dev/null
+++ b/changelogs/unreleased/61606-support-string-piwik-website-ids.yml
@@ -0,0 +1,5 @@
+---
+title: "Supports Matomo/Piwik string website ID (\"Protect Track ID\" plugin)"
+merge_request: 28214
+author: DUVERGIER Claude
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml b/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml
new file mode 100644
index 00000000000..bbe43760953
--- /dev/null
+++ b/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml
@@ -0,0 +1,5 @@
+---
+title: Update indirect dependency fsevents from 1.2.4 to 1.2.9
+merge_request: 28220
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml b/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml
new file mode 100644
index 00000000000..9b4f13353f5
--- /dev/null
+++ b/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dropdown position when loading remote data
+merge_request: 28526
+author:
+type: fixed
diff --git a/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml b/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml
new file mode 100644
index 00000000000..8ffa8d0a51a
--- /dev/null
+++ b/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent common name collisions when requesting multiple Let's Encrypt certificates concurrently
+merge_request: 28373
+author:
+type: fixed
diff --git a/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml b/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml
new file mode 100644
index 00000000000..25c83d24007
--- /dev/null
+++ b/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Adds a text label to color pickers to improve accessibility.
+merge_request: 28343
+author: Chris Toynbee
+type: changed
diff --git a/changelogs/unreleased/61795-fix-error-when-moving-issues.yml b/changelogs/unreleased/61795-fix-error-when-moving-issues.yml
new file mode 100644
index 00000000000..6812baa07c3
--- /dev/null
+++ b/changelogs/unreleased/61795-fix-error-when-moving-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unintended error message shown when moving issues
+merge_request: 28317
+author:
+type: fixed
diff --git a/changelogs/unreleased/61821-tooltip-consistency.yml b/changelogs/unreleased/61821-tooltip-consistency.yml
new file mode 100644
index 00000000000..9b131907ebf
--- /dev/null
+++ b/changelogs/unreleased/61821-tooltip-consistency.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Tooltip Consistency
+merge_request: 28839
+author:
+type: fixed
diff --git a/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml b/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml
new file mode 100644
index 00000000000..4d6464eed52
--- /dev/null
+++ b/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent icons from shrinking in User popover when contents exceed container
+merge_request: 28696
+author:
+type: fixed
diff --git a/changelogs/unreleased/61880-download-btn-group.yml b/changelogs/unreleased/61880-download-btn-group.yml
new file mode 100644
index 00000000000..c1f6b2767c6
--- /dev/null
+++ b/changelogs/unreleased/61880-download-btn-group.yml
@@ -0,0 +1,5 @@
+---
+title: Group download buttons into a .btn-group
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/61914-fix-emojis-urls.yml b/changelogs/unreleased/61914-fix-emojis-urls.yml
new file mode 100644
index 00000000000..578edf4a063
--- /dev/null
+++ b/changelogs/unreleased/61914-fix-emojis-urls.yml
@@ -0,0 +1,5 @@
+---
+title: Fix emojis URLs
+merge_request: 28371
+author:
+type: fixed
diff --git a/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml b/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml
new file mode 100644
index 00000000000..f8ef5dbb53b
--- /dev/null
+++ b/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml
@@ -0,0 +1,6 @@
+---
+title: Fix issue that causes "Save changes" button in project settings pages to be
+ enabled/disabled incorrectly when changes are made to the form
+merge_request: 28377
+author:
+type: fixed
diff --git a/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml b/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml
new file mode 100644
index 00000000000..50b3efba0a5
--- /dev/null
+++ b/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml
@@ -0,0 +1,5 @@
+---
+title: I18n for issue closure reason in emails
+merge_request: 28489
+author: Michał Zając
+type: changed
diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
new file mode 100644
index 00000000000..4d2f73ce2ff
--- /dev/null
+++ b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
@@ -0,0 +1,5 @@
+---
+title: Change collapse icon size to size of profile picture
+merge_request: 28512
+author:
+type: other
diff --git a/changelogs/unreleased/61990-spinner.yml b/changelogs/unreleased/61990-spinner.yml
new file mode 100644
index 00000000000..27d35e56cc9
--- /dev/null
+++ b/changelogs/unreleased/61990-spinner.yml
@@ -0,0 +1,5 @@
+---
+title: Updates loading icon in commits page
+merge_request: 28475
+author:
+type: fixed
diff --git a/changelogs/unreleased/62061-note-icon-color.yml b/changelogs/unreleased/62061-note-icon-color.yml
new file mode 100644
index 00000000000..5bfea1a9ed3
--- /dev/null
+++ b/changelogs/unreleased/62061-note-icon-color.yml
@@ -0,0 +1,5 @@
+---
+title: Update icon color to match design system, pass accessibility
+merge_request: 28498
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/62091-remove-time-windows-flag.yml b/changelogs/unreleased/62091-remove-time-windows-flag.yml
new file mode 100644
index 00000000000..c6c11328312
--- /dev/null
+++ b/changelogs/unreleased/62091-remove-time-windows-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users to specify a time range on metrics dashboard
+merge_request: 28670
+author:
+type: added
diff --git a/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml b/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml
new file mode 100644
index 00000000000..3317d505924
--- /dev/null
+++ b/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Added padding to time window dropdown in monitor dashboard
+merge_request: 28897
+author:
+type: fixed
diff --git a/changelogs/unreleased/62107-fix-detail-page-header-height.yml b/changelogs/unreleased/62107-fix-detail-page-header-height.yml
new file mode 100644
index 00000000000..3723f323bcc
--- /dev/null
+++ b/changelogs/unreleased/62107-fix-detail-page-header-height.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the height of the page headers on issues/merge request/snippets pages
+merge_request: 28650
+author: Erik van der Gaag
+type: fixed
diff --git a/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml b/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml
new file mode 100644
index 00000000000..9596f487116
--- /dev/null
+++ b/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml
@@ -0,0 +1,6 @@
+---
+title: Fix performance issue with large Markdown content in issue or merge request
+ description
+merge_request: 28597
+author:
+type: performance
diff --git a/changelogs/unreleased/62124-new-threaded-discussion-design.yml b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
new file mode 100644
index 00000000000..6614e05be74
--- /dev/null
+++ b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
@@ -0,0 +1,5 @@
+---
+title: Implement borderless discussion design with new reply field
+merge_request: 28580
+author:
+type: added
diff --git a/changelogs/unreleased/62227-webkit-icon-overlap.yml b/changelogs/unreleased/62227-webkit-icon-overlap.yml
new file mode 100644
index 00000000000..47d7583f4c2
--- /dev/null
+++ b/changelogs/unreleased/62227-webkit-icon-overlap.yml
@@ -0,0 +1,5 @@
+---
+title: Add style to disable webkit icons for search inputs
+merge_request: 28833
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml b/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml
new file mode 100644
index 00000000000..c01106a15ec
--- /dev/null
+++ b/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Add Kubernetes logs to Admin Logs UI
+merge_request: 28685
+author:
+type: added
diff --git a/changelogs/unreleased/62408-dropdown-truncate.yml b/changelogs/unreleased/62408-dropdown-truncate.yml
new file mode 100644
index 00000000000..7204016efdf
--- /dev/null
+++ b/changelogs/unreleased/62408-dropdown-truncate.yml
@@ -0,0 +1,5 @@
+---
+title: Fix job name in graph dropdown overflowing
+merge_request: 28824
+author:
+type: fixed
diff --git a/changelogs/unreleased/62418-project-default-git-depth.yml b/changelogs/unreleased/62418-project-default-git-depth.yml
new file mode 100644
index 00000000000..b5647cd0859
--- /dev/null
+++ b/changelogs/unreleased/62418-project-default-git-depth.yml
@@ -0,0 +1,5 @@
+---
+title: Add project level git depth CI/CD setting
+merge_request: 28919
+author:
+type: added
diff --git a/changelogs/unreleased/62432-fix-participants-wrapping.yml b/changelogs/unreleased/62432-fix-participants-wrapping.yml
new file mode 100644
index 00000000000..a7e4bd372de
--- /dev/null
+++ b/changelogs/unreleased/62432-fix-participants-wrapping.yml
@@ -0,0 +1,5 @@
+---
+title: Fix participants list wrapping
+merge_request: 28873
+author:
+type: fixed
diff --git a/changelogs/unreleased/62485-label-weights.yml b/changelogs/unreleased/62485-label-weights.yml
new file mode 100644
index 00000000000..354b18be11e
--- /dev/null
+++ b/changelogs/unreleased/62485-label-weights.yml
@@ -0,0 +1,5 @@
+---
+title: Give labels consistent weight
+merge_request: 28895
+author:
+type: fixed
diff --git a/changelogs/unreleased/62487-external-policy-desc.yml b/changelogs/unreleased/62487-external-policy-desc.yml
new file mode 100644
index 00000000000..2e787b89db1
--- /dev/null
+++ b/changelogs/unreleased/62487-external-policy-desc.yml
@@ -0,0 +1,5 @@
+---
+title: Move text under p tag
+merge_request: 28901
+author:
+type: fixed
diff --git a/changelogs/unreleased/62656-adjusted-dropdown-styles.yml b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
new file mode 100644
index 00000000000..36f14ae2741
--- /dev/null
+++ b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
@@ -0,0 +1,5 @@
+---
+title: "changed the styles on `Add List` dropdown to look more like the EE vesion"
+merge_request: 29338
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/62713-fix-uninstalling-cluster-apps.yml b/changelogs/unreleased/62713-fix-uninstalling-cluster-apps.yml
new file mode 100644
index 00000000000..45fa668ae85
--- /dev/null
+++ b/changelogs/unreleased/62713-fix-uninstalling-cluster-apps.yml
@@ -0,0 +1,5 @@
+---
+title: Fix connection to Tiller error while uninstalling
+merge_request: 29131
+author:
+type: fixed
diff --git a/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
new file mode 100644
index 00000000000..fca92a0d4dc
--- /dev/null
+++ b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
@@ -0,0 +1,5 @@
+---
+title: Include the port in the URLs of the API Link headers
+merge_request: 29267
+author:
+type: fixed
diff --git a/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml b/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml
new file mode 100644
index 00000000000..173c7d9383e
--- /dev/null
+++ b/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml
@@ -0,0 +1,5 @@
+---
+title: 'Geo: Remove Gitlab::LfsToken::LegacyRedisDeviseToken implementation and usage'
+merge_request: 28546
+author:
+type: changed
diff --git a/changelogs/unreleased/9121-sort-relative-position.yml b/changelogs/unreleased/9121-sort-relative-position.yml
new file mode 100644
index 00000000000..adc9e87e5bb
--- /dev/null
+++ b/changelogs/unreleased/9121-sort-relative-position.yml
@@ -0,0 +1,5 @@
+---
+title: Allow issue list to be sorted by relative order
+merge_request: 28566
+author:
+type: added
diff --git a/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml b/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml
new file mode 100644
index 00000000000..0694e1462cf
--- /dev/null
+++ b/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust milestone completion rate to be based on issues count.
+merge_request: 28777
+author:
+type: changed
diff --git a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
deleted file mode 100644
index c014edf9c09..00000000000
--- a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow users to compare branches on a read-only instance
-merge_request: 25414
-author:
-type: fixed
diff --git a/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
new file mode 100644
index 00000000000..821e5d70dca
--- /dev/null
+++ b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Moved EE/CE code differences for `app/assets/javascripts/gl_dropdown.js` into CE"
+merge_request: 28711
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/abstract-auto-merge.yml b/changelogs/unreleased/abstract-auto-merge.yml
new file mode 100644
index 00000000000..d3069a3e500
--- /dev/null
+++ b/changelogs/unreleased/abstract-auto-merge.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor and abstract Auto Merge Processes
+merge_request: 28595
+author:
+type: other
diff --git a/changelogs/unreleased/ac-graphql-stats.yml b/changelogs/unreleased/ac-graphql-stats.yml
new file mode 100644
index 00000000000..8837dce4d89
--- /dev/null
+++ b/changelogs/unreleased/ac-graphql-stats.yml
@@ -0,0 +1,5 @@
+---
+title: Add Namespace and ProjectStatistics to GraphQL API
+merge_request: 28277
+author:
+type: added
diff --git a/changelogs/unreleased/ac-graphql-wikisize.yml b/changelogs/unreleased/ac-graphql-wikisize.yml
new file mode 100644
index 00000000000..be9c347ec21
--- /dev/null
+++ b/changelogs/unreleased/ac-graphql-wikisize.yml
@@ -0,0 +1,5 @@
+---
+title: Expose wiki_size on GraphQL API
+merge_request: 29123
+author:
+type: added
diff --git a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
new file mode 100644
index 00000000000..bd005206d4e
--- /dev/null
+++ b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
@@ -0,0 +1,5 @@
+---
+title: Forbid NULL in project_statistics.packages_size
+merge_request: 28400
+author:
+type: other
diff --git a/changelogs/unreleased/add-allow_failure-to-job-api.yml b/changelogs/unreleased/add-allow_failure-to-job-api.yml
new file mode 100644
index 00000000000..5dd2b9708c5
--- /dev/null
+++ b/changelogs/unreleased/add-allow_failure-to-job-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add allow_failure attribute to Job API
+merge_request: 28406
+author:
+type: added
diff --git a/changelogs/unreleased/add-branch-to-project-search-api.yml b/changelogs/unreleased/add-branch-to-project-search-api.yml
new file mode 100644
index 00000000000..74cff94ab76
--- /dev/null
+++ b/changelogs/unreleased/add-branch-to-project-search-api.yml
@@ -0,0 +1,5 @@
+---
+title: Added ref querystring parameter to project search API to allow searching on branches/tags other than the default
+merge_request: 28069
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/add-constraint-for-milestone-dates.yml b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
new file mode 100644
index 00000000000..485149cf62e
--- /dev/null
+++ b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
@@ -0,0 +1,5 @@
+---
+title: Limit milestone dates to before year 9999
+merge_request: 28742
+author: Luke Picciau
+type: fixed
diff --git a/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml b/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
deleted file mode 100644
index 7438053a84f..00000000000
--- a/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add related merge request count to api response
-merge_request: 24974
-author:
-type: added
diff --git a/changelogs/unreleased/add-title-attribute-to-file-row.yml b/changelogs/unreleased/add-title-attribute-to-file-row.yml
deleted file mode 100644
index c68d3d544e7..00000000000
--- a/changelogs/unreleased/add-title-attribute-to-file-row.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add title attribute to display file name
-merge_request: 25154
-author: Satoshi Nakamatsu @satoshicano
-type: added
diff --git a/changelogs/unreleased/add-warning-to-backup-rake-task.yml b/changelogs/unreleased/add-warning-to-backup-rake-task.yml
new file mode 100644
index 00000000000..7ddeae3f9fd
--- /dev/null
+++ b/changelogs/unreleased/add-warning-to-backup-rake-task.yml
@@ -0,0 +1,5 @@
+---
+title: Add warning that gitlab-secrets isn't included in backup
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/add-wiki-size-to-statistics.yml b/changelogs/unreleased/add-wiki-size-to-statistics.yml
new file mode 100644
index 00000000000..85b6d7a1774
--- /dev/null
+++ b/changelogs/unreleased/add-wiki-size-to-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add wiki size to project statistics
+merge_request: 25321
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
deleted file mode 100644
index 6e344dbe0e9..00000000000
--- a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow maintainers to remove pages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
new file mode 100644
index 00000000000..d60dd65be8a
--- /dev/null
+++ b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
@@ -0,0 +1,5 @@
+---
+title: Always show "Pipelines must succeed" checkbox
+merge_request: 28651
+author:
+type: fixed
diff --git a/changelogs/unreleased/an-peek-jaeger.yml b/changelogs/unreleased/an-peek-jaeger.yml
deleted file mode 100644
index 8659ee4f9e0..00000000000
--- a/changelogs/unreleased/an-peek-jaeger.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Provide a performance bar link to the Jaeger UI
-merge_request: 24902
-author:
-type: other
diff --git a/changelogs/unreleased/antonyliu-i18n-user-profile.yml b/changelogs/unreleased/antonyliu-i18n-user-profile.yml
new file mode 100644
index 00000000000..f9065ee5697
--- /dev/null
+++ b/changelogs/unreleased/antonyliu-i18n-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: 'i18n: externalize strings from user profile settings'
+merge_request: 28088
+author: Antony Liu
+type: other
diff --git a/changelogs/unreleased/api-issuable-bulk-update.yml b/changelogs/unreleased/api-issuable-bulk-update.yml
deleted file mode 100644
index 3d4e96f9d1b..00000000000
--- a/changelogs/unreleased/api-issuable-bulk-update.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Bulk update for issues and MRs'
-merge_request: 25201
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/api_make_protected_boolean_type.yml b/changelogs/unreleased/api_make_protected_boolean_type.yml
new file mode 100644
index 00000000000..765edea6427
--- /dev/null
+++ b/changelogs/unreleased/api_make_protected_boolean_type.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: change protected attribute type to Boolean'
+merge_request: 28766
+author:
+type: other
diff --git a/changelogs/unreleased/api_masked_variables.yml b/changelogs/unreleased/api_masked_variables.yml
new file mode 100644
index 00000000000..3605339cb91
--- /dev/null
+++ b/changelogs/unreleased/api_masked_variables.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Allow to get and set "masked" attribute for variables'
+merge_request: 28381
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml b/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml
new file mode 100644
index 00000000000..9ba55719bdf
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml
@@ -0,0 +1,5 @@
+---
+title: Bumps Kubernetes in Auto DevOps to 1.11.10
+merge_request: 28525
+author:
+type: other
diff --git a/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
new file mode 100644
index 00000000000..2b5a3592775
--- /dev/null
+++ b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of jobs controller
+merge_request: 28093
+author:
+type: performance
diff --git a/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml b/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml
new file mode 100644
index 00000000000..ecfbc97a8c5
--- /dev/null
+++ b/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml
@@ -0,0 +1,5 @@
+---
+title: Bump Helm version in Auto-DevOps.gitlab-ci.yml to 2.14.0
+merge_request: 28527
+author:
+type: other
diff --git a/changelogs/unreleased/bvl-graphql-multiplex.yml b/changelogs/unreleased/bvl-graphql-multiplex.yml
new file mode 100644
index 00000000000..56d39e447a5
--- /dev/null
+++ b/changelogs/unreleased/bvl-graphql-multiplex.yml
@@ -0,0 +1,5 @@
+---
+title: Support multiplex GraphQL queries
+merge_request: 28273
+author:
+type: added
diff --git a/changelogs/unreleased/bvl-use-global-ids-graphql.yml b/changelogs/unreleased/bvl-use-global-ids-graphql.yml
new file mode 100644
index 00000000000..34cb65e6001
--- /dev/null
+++ b/changelogs/unreleased/bvl-use-global-ids-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Use global IDs when exposing GraphQL resources
+merge_request: 29080
+author:
+type: added
diff --git a/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml b/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml
new file mode 100644
index 00000000000..d38046ebcbf
--- /dev/null
+++ b/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml
@@ -0,0 +1,5 @@
+---
+title: Cancel auto merge when merge request is closed
+merge_request: 28782
+author:
+type: fixed
diff --git a/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml b/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml
new file mode 100644
index 00000000000..a626193dc27
--- /dev/null
+++ b/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml
@@ -0,0 +1,5 @@
+---
+title: Add issues_statistics api endpoints and extend issues search api
+merge_request: 27366
+author:
+type: added
diff --git a/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
new file mode 100644
index 00000000000..a795e33b00d
--- /dev/null
+++ b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid setting Gitlab::Session on sessionless requests and Git HTTP
+merge_request: 29146
+author:
+type: fixed
diff --git a/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml b/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml
new file mode 100644
index 00000000000..332105bb269
--- /dev/null
+++ b/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml
@@ -0,0 +1,5 @@
+---
+title: Fix flyout nav on small viewports
+merge_request: 25998
+author:
+type: fixed
diff --git a/changelogs/unreleased/change-badges-example-to-pipeline.yml b/changelogs/unreleased/change-badges-example-to-pipeline.yml
deleted file mode 100644
index 8ed4d77fd6c..00000000000
--- a/changelogs/unreleased/change-badges-example-to-pipeline.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change badges.svg example to pipeline.svg
-merge_request: 25157
-author: Aviad Levy
-type: fixed
diff --git a/changelogs/unreleased/changelogs-readme.yml b/changelogs/unreleased/changelogs-readme.yml
deleted file mode 100644
index 9f391699575..00000000000
--- a/changelogs/unreleased/changelogs-readme.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add readme to changelogs directory
-merge_request: 25209
-author: "@glensc"
-type: added
diff --git a/changelogs/unreleased/chore-remove-circuit-breaker-api.yml b/changelogs/unreleased/chore-remove-circuit-breaker-api.yml
new file mode 100644
index 00000000000..f9532be04c8
--- /dev/null
+++ b/changelogs/unreleased/chore-remove-circuit-breaker-api.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the circuit breaker API
+merge_request: 28669
+author:
+type: removed
diff --git a/changelogs/unreleased/ci-variable-conjunction.yml b/changelogs/unreleased/ci-variable-conjunction.yml
new file mode 100644
index 00000000000..839c4285f3a
--- /dev/null
+++ b/changelogs/unreleased/ci-variable-conjunction.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for && and || to CI Pipeline Expressions. Change CI variable expression matching for Lexeme::Pattern to eagerly return tokens.
+merge_request: 27925
+author: Martin Manelli
+type: added
diff --git a/changelogs/unreleased/copy-button-in-modals.yml b/changelogs/unreleased/copy-button-in-modals.yml
new file mode 100644
index 00000000000..bc18eb9ab26
--- /dev/null
+++ b/changelogs/unreleased/copy-button-in-modals.yml
@@ -0,0 +1,5 @@
+---
+title: Add a New Copy Button That Works in Modals
+merge_request: 28676
+author:
+type: added
diff --git a/changelogs/unreleased/deprecated-migration-inheritance.yml b/changelogs/unreleased/deprecated-migration-inheritance.yml
deleted file mode 100644
index 814c511195b..00000000000
--- a/changelogs/unreleased/deprecated-migration-inheritance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Directly inheriting from ActiveRecord::Migration is deprecated
-merge_request: 25066
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/diff-tree-resizable.yml b/changelogs/unreleased/diff-tree-resizable.yml
deleted file mode 100644
index 7411640aea5..00000000000
--- a/changelogs/unreleased/diff-tree-resizable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make file tree in merge requests resizable
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/diff-whitespace-setting-changes.yml b/changelogs/unreleased/diff-whitespace-setting-changes.yml
new file mode 100644
index 00000000000..640e9e589df
--- /dev/null
+++ b/changelogs/unreleased/diff-whitespace-setting-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed show whitespace button not refetching diff content
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/display-junit-classname-in-modal.yml b/changelogs/unreleased/display-junit-classname-in-modal.yml
new file mode 100644
index 00000000000..c5140456e4e
--- /dev/null
+++ b/changelogs/unreleased/display-junit-classname-in-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Display classname JUnit attribute in report modal
+merge_request: 28376
+author:
+type: added
diff --git a/changelogs/unreleased/dm-http-hostname-override.yml b/changelogs/unreleased/dm-http-hostname-override.yml
new file mode 100644
index 00000000000..f84f36a0010
--- /dev/null
+++ b/changelogs/unreleased/dm-http-hostname-override.yml
@@ -0,0 +1,5 @@
+---
+title: Protect Gitlab::HTTP against DNS rebinding attack
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/docs-add-chatops-request-doc.yml b/changelogs/unreleased/docs-add-chatops-request-doc.yml
new file mode 100644
index 00000000000..85ba86a73af
--- /dev/null
+++ b/changelogs/unreleased/docs-add-chatops-request-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Add section to dev docs on accessing chatops
+merge_request: 28623
+author:
+type: other
diff --git a/changelogs/unreleased/dz-patch-58.yml b/changelogs/unreleased/dz-patch-58.yml
new file mode 100644
index 00000000000..97ceadd303d
--- /dev/null
+++ b/changelogs/unreleased/dz-patch-58.yml
@@ -0,0 +1,5 @@
+---
+title: Replace Oxygen-Sans font with Noto Sans
+merge_request: 28322
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-scope-project-routes.yml b/changelogs/unreleased/dz-scope-project-routes.yml
new file mode 100644
index 00000000000..66eb5d928f0
--- /dev/null
+++ b/changelogs/unreleased/dz-scope-project-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Move some project routes under /-/ scope
+merge_request: 28435
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-sort-labels-alphabetically.yml b/changelogs/unreleased/dz-sort-labels-alphabetically.yml
deleted file mode 100644
index acfde3de999..00000000000
--- a/changelogs/unreleased/dz-sort-labels-alphabetically.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sort labels alphabetically on issues and merge requests list
-merge_request: 25470
-author:
-type: changed
diff --git a/changelogs/unreleased/ee-11040-added-conditional-rendering.yml b/changelogs/unreleased/ee-11040-added-conditional-rendering.yml
new file mode 100644
index 00000000000..7b06e43830f
--- /dev/null
+++ b/changelogs/unreleased/ee-11040-added-conditional-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: "Added conditional rendering to `app/views/search/_form.html.haml` for CE/EE code base consistency"
+merge_request: 28883
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/expose-merge-ref-to-runner.yml b/changelogs/unreleased/expose-merge-ref-to-runner.yml
deleted file mode 100644
index 945f4f6e05a..00000000000
--- a/changelogs/unreleased/expose-merge-ref-to-runner.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose refspecs and depth to runner
-merge_request: 25233
-author:
-type: added
diff --git a/changelogs/unreleased/fast-destroy-uploads.yml b/changelogs/unreleased/fast-destroy-uploads.yml
deleted file mode 100644
index ee3363a6ae9..00000000000
--- a/changelogs/unreleased/fast-destroy-uploads.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: File uploads are deleted asynchronously when deleting a project or group.
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml b/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
deleted file mode 100644
index ad92135d401..00000000000
--- a/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable persisted pipeline stages by default
-merge_request: 25347
-author:
-type: performance
diff --git a/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
new file mode 100644
index 00000000000..443fff92f55
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
@@ -0,0 +1,5 @@
+---
+title: Use to 'gitlabktl' build serverless applications
+merge_request: 29258
+author:
+type: added
diff --git a/changelogs/unreleased/filter-confidential-issues.yml b/changelogs/unreleased/filter-confidential-issues.yml
deleted file mode 100644
index 83f19a57aab..00000000000
--- a/changelogs/unreleased/filter-confidential-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ability to filter confidential issues
-merge_request: 24960
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/filter-note-parameters.yml b/changelogs/unreleased/filter-note-parameters.yml
deleted file mode 100644
index fca2a394820..00000000000
--- a/changelogs/unreleased/filter-note-parameters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include note in the Rails filter_parameters configuration
-merge_request: 25238
-author:
-type: other
diff --git a/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
new file mode 100644
index 00000000000..46fa90ccda0
--- /dev/null
+++ b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
@@ -0,0 +1,5 @@
+---
+title: Allow lowercase prefix for Youtrack issue ids
+merge_request: 29057
+author: Matthias Baur
+type: fixed
diff --git a/changelogs/unreleased/fix-autodevops-postgres-versioning.yml b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
new file mode 100644
index 00000000000..8ddc70f6314
--- /dev/null
+++ b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed ignored postgres version that occurs after the first autodevops deploy when specifying custom $POSTGRES_VERSION
+merge_request: 28735
+author: Brandon Dimcheff
+type: fixed
diff --git a/changelogs/unreleased/fix-badges-logs.yml b/changelogs/unreleased/fix-badges-logs.yml
deleted file mode 100644
index 6236e7b046d..00000000000
--- a/changelogs/unreleased/fix-badges-logs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Doc - fix the url of pipeline status badge
-merge_request: 25404
-author: Aviad Levy
-type: fixed
diff --git a/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml b/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml
new file mode 100644
index 00000000000..63f134808e3
--- /dev/null
+++ b/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml
@@ -0,0 +1,5 @@
+---
+title: Fix. `db:migrate` is failed on MySQL 8
+merge_request: 28351
+author: sue445
+type: fixed
diff --git a/changelogs/unreleased/fix-format-date-safari-ff.yml b/changelogs/unreleased/fix-format-date-safari-ff.yml
new file mode 100644
index 00000000000..e71ea2867f3
--- /dev/null
+++ b/changelogs/unreleased/fix-format-date-safari-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Throw an error when formatDate's input is invalid
+merge_request: 28713
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
new file mode 100644
index 00000000000..88656b7ef4c
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
@@ -0,0 +1,5 @@
+---
+title: Fix serverless apps deployments by bumping 'tm' version
+merge_request: 29254
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
new file mode 100644
index 00000000000..f51ec273a57
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
@@ -0,0 +1,5 @@
+---
+title: Remove build policies from serverless app template
+merge_request: 29253
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-import-member-access.yml b/changelogs/unreleased/fix-import-member-access.yml
new file mode 100644
index 00000000000..5dc4ae738f8
--- /dev/null
+++ b/changelogs/unreleased/fix-import-member-access.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue importing members with owner access
+merge_request: 28636
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-import-param-ordering.yml b/changelogs/unreleased/fix-import-param-ordering.yml
new file mode 100644
index 00000000000..47e32cda977
--- /dev/null
+++ b/changelogs/unreleased/fix-import-param-ordering.yml
@@ -0,0 +1,5 @@
+---
+title: Fix order dependency with user params during imports
+merge_request: 28719
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml b/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml
new file mode 100644
index 00000000000..1041943f9c4
--- /dev/null
+++ b/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml
@@ -0,0 +1,5 @@
+---
+title: Fix milestone references containing &, <, or >
+merge_request: 28667
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-search-dropdown-blur-close.yml b/changelogs/unreleased/fix-search-dropdown-blur-close.yml
new file mode 100644
index 00000000000..1ac9dc674fe
--- /dev/null
+++ b/changelogs/unreleased/fix-search-dropdown-blur-close.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search dropdown not closing on blur if empty
+merge_request: 28730
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-time-window-default.yml b/changelogs/unreleased/fix-time-window-default.yml
new file mode 100644
index 00000000000..147f82eb6c9
--- /dev/null
+++ b/changelogs/unreleased/fix-time-window-default.yml
@@ -0,0 +1,5 @@
+---
+title: Use the selected time window for metrics dashboard
+merge_request: 29152
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-too-many-loops-cron-error.yml b/changelogs/unreleased/fix-too-many-loops-cron-error.yml
new file mode 100644
index 00000000000..a9b5b761439
--- /dev/null
+++ b/changelogs/unreleased/fix-too-many-loops-cron-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix "too many loops" error by handling gracefully cron schedules for non existent days
+merge_request: 28002
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix_-56347.yml b/changelogs/unreleased/fix_-56347.yml
deleted file mode 100644
index 1d03ed8864c..00000000000
--- a/changelogs/unreleased/fix_-56347.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix overlapping empty-header logo
-merge_request: 24868
-author: Jonas L.
-type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.43.0.yml b/changelogs/unreleased/gitaly-version-v1.43.0.yml
new file mode 100644
index 00000000000..67acd2725e1
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.43.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.43.0
+merge_request: 28867
+author:
+type: changed
diff --git a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
deleted file mode 100644
index b8668d338de..00000000000
--- a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively
-merge_request: 25268
-author:
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml b/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
deleted file mode 100644
index 29dbf2367b7..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/commit`
-merge_request: 24668
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-profiles-preferences.yml b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
new file mode 100644
index 00000000000..1a72e92a241
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize profiles preferences
+merge_request: 28470
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
new file mode 100644
index 00000000000..35515c9d639
--- /dev/null
+++ b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Open visibility help link in a new tab
+merge_request: 28603
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/gt-update-new-password-breadcrumb.yml b/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
deleted file mode 100644
index 43ea2f0d44b..00000000000
--- a/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update new password breadcrumb
-merge_request: 25037
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/helm-2-12-3.yml b/changelogs/unreleased/helm-2-12-3.yml
deleted file mode 100644
index 0d0d904a9cb..00000000000
--- a/changelogs/unreleased/helm-2-12-3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively
-merge_request: 25072
-author:
-type: other
diff --git a/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml b/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml
new file mode 100644
index 00000000000..fe6eb3a2bf7
--- /dev/null
+++ b/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of active sessions page in user profile
+merge_request: 28590
+author: antony liu
+type: other
diff --git a/changelogs/unreleased/i18n-chat-of-user-profile.yml b/changelogs/unreleased/i18n-chat-of-user-profile.yml
new file mode 100644
index 00000000000..663b4ffc1a1
--- /dev/null
+++ b/changelogs/unreleased/i18n-chat-of-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of chat page in user profile
+merge_request: 28632
+author:
+type: other
diff --git a/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml b/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml
new file mode 100644
index 00000000000..4dc45b35976
--- /dev/null
+++ b/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of PGP Keys and SSH Keys page in user profile
+merge_request: 28653
+author: Antony Liu
+type: other
diff --git a/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
new file mode 100644
index 00000000000..eae2d5f9b2a
--- /dev/null
+++ b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
@@ -0,0 +1,5 @@
+---
+title: Allow removal of empty lines via suggestions
+merge_request: 28703
+author:
+type: fixed
diff --git a/changelogs/unreleased/import-go-to-project-cta.yml b/changelogs/unreleased/import-go-to-project-cta.yml
deleted file mode 100644
index ae719f08790..00000000000
--- a/changelogs/unreleased/import-go-to-project-cta.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve GitHub and Gitea project import table UI
-merge_request: 24606
-author:
-type: other
diff --git a/changelogs/unreleased/improve-email-text-part.yml b/changelogs/unreleased/improve-email-text-part.yml
new file mode 100644
index 00000000000..ce506cb1507
--- /dev/null
+++ b/changelogs/unreleased/improve-email-text-part.yml
@@ -0,0 +1,5 @@
+---
+title: Improve new user email markup unconsistency between text and html parts
+merge_request: 29111
+author: Haunui Saint-sevin
+type: fixed
diff --git a/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml b/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
deleted file mode 100644
index 76ff15cba5b..00000000000
--- a/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance for diverging commit counts
-merge_request: 24287
-author:
-type: performance
diff --git a/changelogs/unreleased/improve-snippets-empty-state.yml b/changelogs/unreleased/improve-snippets-empty-state.yml
deleted file mode 100644
index 9859243a81f..00000000000
--- a/changelogs/unreleased/improve-snippets-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve snippets empty state
-merge_request: 18348
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/increase-move-issue-dropdown-height.yml b/changelogs/unreleased/increase-move-issue-dropdown-height.yml
new file mode 100644
index 00000000000..bb67e9341b2
--- /dev/null
+++ b/changelogs/unreleased/increase-move-issue-dropdown-height.yml
@@ -0,0 +1,5 @@
+---
+title: Increase height of move issue dropdown
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/issue_49897.yml b/changelogs/unreleased/issue_49897.yml
new file mode 100644
index 00000000000..b630b5143c6
--- /dev/null
+++ b/changelogs/unreleased/issue_49897.yml
@@ -0,0 +1,5 @@
+---
+title: Delete unauthorized Todos when project is made private
+merge_request: 28560
+author:
+type: fixed
diff --git a/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml b/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml
new file mode 100644
index 00000000000..23235060a98
--- /dev/null
+++ b/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml
@@ -0,0 +1,5 @@
+---
+title: Omit max-count for diverging_commit_counts behind feature flag
+merge_request: 28157
+author:
+type: other
diff --git a/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml b/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
deleted file mode 100644
index ba882112f70..00000000000
--- a/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow setting feature flags per GitLab group through the API
-merge_request: 25022
-author:
-type: added
diff --git a/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml b/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml
new file mode 100644
index 00000000000..dc4edbc058f
--- /dev/null
+++ b/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate the monitoring dashboard store to vuex
+merge_request: 28555
+author:
+type: other
diff --git a/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml b/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml
new file mode 100644
index 00000000000..c5918df8193
--- /dev/null
+++ b/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml
@@ -0,0 +1,5 @@
+---
+title: Pass user's identity and token from JupyterHub to user's Jupyter environment
+merge_request: 27314
+author: Amit Rathi
+type: added
diff --git a/changelogs/unreleased/kinolaev-master-patch-13154.yml b/changelogs/unreleased/kinolaev-master-patch-13154.yml
new file mode 100644
index 00000000000..3292ff797e2
--- /dev/null
+++ b/changelogs/unreleased/kinolaev-master-patch-13154.yml
@@ -0,0 +1,5 @@
+---
+title: 'Auto-DevOps: allow to disable rollout status check'
+merge_request: 28130
+author: Sergej Nikolaev <kinolaev@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/kinolaev-master-patch-87865.yml b/changelogs/unreleased/kinolaev-master-patch-87865.yml
deleted file mode 100644
index b4dbc2c0e1f..00000000000
--- a/changelogs/unreleased/kinolaev-master-patch-87865.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix rollout status for statefulsets and daemonsets
-merge_request: 24972
-author: Sergej Nikolaev <kinolaev@gmail.com>
-type: fixed
diff --git a/changelogs/unreleased/leipert-node-12-compatibility.yml b/changelogs/unreleased/leipert-node-12-compatibility.yml
new file mode 100644
index 00000000000..18025d33a6d
--- /dev/null
+++ b/changelogs/unreleased/leipert-node-12-compatibility.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade dependencies for node 12 compatibility
+merge_request: 28323
+author:
+type: fixed
diff --git a/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml b/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml
new file mode 100644
index 00000000000..daeefd3ffd7
--- /dev/null
+++ b/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of users autocomplete when there are lots of results
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/mark-comment-resolved.yml b/changelogs/unreleased/mark-comment-resolved.yml
new file mode 100644
index 00000000000..3343d18d16f
--- /dev/null
+++ b/changelogs/unreleased/mark-comment-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Change resolve button text to mark comment as resolved
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/mm12935.yml b/changelogs/unreleased/mm12935.yml
new file mode 100644
index 00000000000..782586c514e
--- /dev/null
+++ b/changelogs/unreleased/mm12935.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Content-Type override for Mattermost OAuth login
+merge_request:
+author: Harrison Healey
+type: removed
diff --git a/changelogs/unreleased/move_chatops_to_core.yml b/changelogs/unreleased/move_chatops_to_core.yml
deleted file mode 100644
index 7a75efedfa8..00000000000
--- a/changelogs/unreleased/move_chatops_to_core.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move ChatOps to Core
-merge_request: 24780
-author:
-type: changed
diff --git a/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml b/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml
new file mode 100644
index 00000000000..d0a09385d4c
--- /dev/null
+++ b/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid 500 when rendering users ATOM data
+merge_request: 25408
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml b/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml
new file mode 100644
index 00000000000..6b5f97f24b3
--- /dev/null
+++ b/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml
@@ -0,0 +1,5 @@
+---
+title: Reset merge status from mergeable MRs
+merge_request: 28843
+author:
+type: other
diff --git a/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
new file mode 100644
index 00000000000..1f40089adb8
--- /dev/null
+++ b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
@@ -0,0 +1,5 @@
+---
+title: Sync merge ref upon mergeability check
+merge_request: 28513
+author:
+type: added
diff --git a/changelogs/unreleased/patch-55.yml b/changelogs/unreleased/patch-55.yml
new file mode 100644
index 00000000000..7a6d6bfed2f
--- /dev/null
+++ b/changelogs/unreleased/patch-55.yml
@@ -0,0 +1,5 @@
+---
+title: Better isolated `Docker.gitlab-ci.yml` to avoid interference with other job configurations.
+merge_request: 28213
+author: lrkwz
+type: changed
diff --git a/changelogs/unreleased/pb-update-gitaly-1-45-0.yml b/changelogs/unreleased/pb-update-gitaly-1-45-0.yml
new file mode 100644
index 00000000000..eaad7a8378b
--- /dev/null
+++ b/changelogs/unreleased/pb-update-gitaly-1-45-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GITALY_SERVER_VERSION to 1.45.0
+merge_request: 29109
+author:
+type: changed
diff --git a/changelogs/unreleased/pipelines-email-default-branch-filter.yml b/changelogs/unreleased/pipelines-email-default-branch-filter.yml
new file mode 100644
index 00000000000..4c2a54af0bf
--- /dev/null
+++ b/changelogs/unreleased/pipelines-email-default-branch-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Add notify_only_default_branch option to PipelinesEmailService
+merge_request: 28271
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
deleted file mode 100644
index fbcbdc53cfe..00000000000
--- a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correct non-standard unicode spaces to regular unicode
-merge_request: 24795
-author: Marcel Amirault
-type: other
diff --git a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
deleted file mode 100644
index 082075506c0..00000000000
--- a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extracted ResolveWithIssueButton to its own component
-merge_request: 25093
-author: Martin Hobert
-type: other
diff --git a/changelogs/unreleased/referenced-labels.yml b/changelogs/unreleased/referenced-labels.yml
new file mode 100644
index 00000000000..c39ef4c2478
--- /dev/null
+++ b/changelogs/unreleased/referenced-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Add referenced-commands in no overflow list
+merge_request: 28858
+author:
+type: fixed
diff --git a/changelogs/unreleased/relative-urls-for-system-notes.yml b/changelogs/unreleased/relative-urls-for-system-notes.yml
new file mode 100644
index 00000000000..ec834e9f277
--- /dev/null
+++ b/changelogs/unreleased/relative-urls-for-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Change links in system notes to use relative paths
+merge_request: 28588
+author: Luke Picciau
+type: fixed
diff --git a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
new file mode 100644
index 00000000000..acde65af2d4
--- /dev/null
+++ b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove legacy artifact related code
+merge_request: 26475
+author:
+type: other
diff --git a/changelogs/unreleased/remove-mr-diff-header-height.yml b/changelogs/unreleased/remove-mr-diff-header-height.yml
new file mode 100644
index 00000000000..c06c7281c58
--- /dev/null
+++ b/changelogs/unreleased/remove-mr-diff-header-height.yml
@@ -0,0 +1,5 @@
+---
+title: Remove fixed height from MR diff headers
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml b/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
deleted file mode 100644
index 045fbbb48b7..00000000000
--- a/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove second primary button on wiki edit
-merge_request: 19959
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/revert-git-depth-for-merge-request.yml b/changelogs/unreleased/revert-git-depth-for-merge-request.yml
new file mode 100644
index 00000000000..3a258dff358
--- /dev/null
+++ b/changelogs/unreleased/revert-git-depth-for-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Remove a default git depth in Pipelines for merge requests
+merge_request: 28926
+author:
+type: fixed
diff --git a/changelogs/unreleased/rs-admin-user-case-insensitive.yml b/changelogs/unreleased/rs-admin-user-case-insensitive.yml
deleted file mode 100644
index 40398c46a1e..00000000000
--- a/changelogs/unreleased/rs-admin-user-case-insensitive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Admin section finds users case-insensitively
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml b/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml
new file mode 100644
index 00000000000..d9ad5af256a
--- /dev/null
+++ b/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent XSS injection in note imports
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-60039.yml b/changelogs/unreleased/security-60039.yml
new file mode 100644
index 00000000000..5edbf32ec97
--- /dev/null
+++ b/changelogs/unreleased/security-60039.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent invalid branch for merge request
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml b/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml
new file mode 100644
index 00000000000..5b79258af54
--- /dev/null
+++ b/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml
@@ -0,0 +1,5 @@
+---
+title: Filter relative links in wiki for XSS
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml b/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml
new file mode 100644
index 00000000000..adfd8e1298f
--- /dev/null
+++ b/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix confidential issue label disclosure on milestone view
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml b/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml
new file mode 100644
index 00000000000..084439c71d9
--- /dev/null
+++ b/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix url redaction for issue links
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix_milestones_search_api_leak.yml b/changelogs/unreleased/security-fix_milestones_search_api_leak.yml
new file mode 100644
index 00000000000..5691550b602
--- /dev/null
+++ b/changelogs/unreleased/security-fix_milestones_search_api_leak.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolve: Milestones leaked via search API'
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml b/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml
new file mode 100644
index 00000000000..df636ec37fb
--- /dev/null
+++ b/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml
@@ -0,0 +1,5 @@
+---
+title: Add extra fields for handling basic auth on import by url page
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml b/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml
new file mode 100644
index 00000000000..02773fa1d7c
--- /dev/null
+++ b/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent bypass of restriction disabling web password sign in
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-unsubscribing-from-issue.yml b/changelogs/unreleased/security-unsubscribing-from-issue.yml
new file mode 100644
index 00000000000..3a33a457c69
--- /dev/null
+++ b/changelogs/unreleased/security-unsubscribing-from-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Hide confidential issue title on unsubscribe for anonymous users
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml b/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml
new file mode 100644
index 00000000000..04eb035b157
--- /dev/null
+++ b/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml
@@ -0,0 +1,5 @@
+---
+title: Make pipeline schedule worker resilient
+merge_request: 28407
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml b/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml
new file mode 100644
index 00000000000..b089e6e4f37
--- /dev/null
+++ b/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add a column header to admin/jobs page
+merge_request: 28837
+author:
+type: other
diff --git a/changelogs/unreleased/sh-default-visibility-fix.yml b/changelogs/unreleased/sh-default-visibility-fix.yml
new file mode 100644
index 00000000000..8308f310150
--- /dev/null
+++ b/changelogs/unreleased/sh-default-visibility-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Set project default visibility to max allowed
+merge_request: 28754
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-cpp-templates-404.yml b/changelogs/unreleased/sh-fix-cpp-templates-404.yml
deleted file mode 100644
index ac958d84099..00000000000
--- a/changelogs/unreleased/sh-fix-cpp-templates-404.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 404s when C++ .gitignore template selected
-merge_request: 25416
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
deleted file mode 100644
index e6c762f1d47..00000000000
--- a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate XHR request when requesting new pipeline page
-merge_request: 25506
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-55869.yml b/changelogs/unreleased/sh-fix-issue-55869.yml
new file mode 100644
index 00000000000..7935cffc13b
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-55869.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of resend button in members page
+merge_request: 28202
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-issue-58103.yml b/changelogs/unreleased/sh-fix-issue-58103.yml
deleted file mode 100644
index 1599af23fed..00000000000
--- a/changelogs/unreleased/sh-fix-issue-58103.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Properly handle multiple X-Forwarded-For addresses in runner IP
-merge_request: 25511
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-openid-connect-defaults.yml b/changelogs/unreleased/sh-fix-openid-connect-defaults.yml
new file mode 100644
index 00000000000..1ed977c9be6
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-openid-connect-defaults.yml
@@ -0,0 +1,5 @@
+---
+title: Make OpenID Connect work without requiring a name
+merge_request: 29312
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-rebase-error-clearing.yml b/changelogs/unreleased/sh-fix-rebase-error-clearing.yml
new file mode 100644
index 00000000000..4f5f2779e7f
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-rebase-error-clearing.yml
@@ -0,0 +1,5 @@
+---
+title: Properly clear the merge error upon rebase failure
+merge_request: 28319
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml b/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml
new file mode 100644
index 00000000000..7f33ab28e3d
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml
@@ -0,0 +1,5 @@
+---
+title: Fix remote mirrors not updating after tag push
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-include-project-path-for-internal-api.yml b/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
deleted file mode 100644
index 1973049e9e3..00000000000
--- a/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include gl_project_path in API /internal/allowed response
-merge_request: 25314
-author:
-type: other
diff --git a/changelogs/unreleased/sh-log-rails-queue-duration.yml b/changelogs/unreleased/sh-log-rails-queue-duration.yml
deleted file mode 100644
index 89390aef108..00000000000
--- a/changelogs/unreleased/sh-log-rails-queue-duration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Log queue duration in production_json.log
-merge_request: 25075
-author:
-type: other
diff --git a/changelogs/unreleased/sh-project-import-visibility-error.yml b/changelogs/unreleased/sh-project-import-visibility-error.yml
new file mode 100644
index 00000000000..eb7e001c6aa
--- /dev/null
+++ b/changelogs/unreleased/sh-project-import-visibility-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix invalid visibility string comparison in project import
+merge_request: 28612
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-update-process-mem.yml b/changelogs/unreleased/sh-update-process-mem.yml
new file mode 100644
index 00000000000..51b22fb0f00
--- /dev/null
+++ b/changelogs/unreleased/sh-update-process-mem.yml
@@ -0,0 +1,5 @@
+---
+title: Update get_process_mem to 0.2.3
+merge_request: 28248
+author:
+type: other
diff --git a/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml b/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml
new file mode 100644
index 00000000000..9ad5c9ebb64
--- /dev/null
+++ b/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Ruby version to 2.6.3
+merge_request: 28117
+author:
+type: performance
diff --git a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
deleted file mode 100644
index fbab898b799..00000000000
--- a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Support `only: changes:` on MR pipelines'
-merge_request: 24490
-author: Hiroyuki Sato
-type: added
diff --git a/changelogs/unreleased/t1.yml b/changelogs/unreleased/t1.yml
new file mode 100644
index 00000000000..6740375485d
--- /dev/null
+++ b/changelogs/unreleased/t1.yml
@@ -0,0 +1,5 @@
+---
+title: Set up git client in Jupyter installtion
+merge_request: 28783
+author: Amit Rathi
+type: added
diff --git a/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
new file mode 100644
index 00000000000..3391fcc9537
--- /dev/null
+++ b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
@@ -0,0 +1,5 @@
+---
+title: Allow masking if 8 or more characters in base64.
+merge_request: 29143
+author: thomas-nilsson-irfu
+type: changed
diff --git a/changelogs/unreleased/update-babel-to-7-4-4.yml b/changelogs/unreleased/update-babel-to-7-4-4.yml
new file mode 100644
index 00000000000..0d4b4700bb2
--- /dev/null
+++ b/changelogs/unreleased/update-babel-to-7-4-4.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade babel to 7.4.4
+merge_request: 28437
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/update-gitaly-to-v1-42-1.yml b/changelogs/unreleased/update-gitaly-to-v1-42-1.yml
new file mode 100644
index 00000000000..ff42bdd9c0b
--- /dev/null
+++ b/changelogs/unreleased/update-gitaly-to-v1-42-1.yml
@@ -0,0 +1,5 @@
+---
+title: "Update Gitaly to v1.42.1"
+merge_request: 28425
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
deleted file mode 100644
index 3bf55630c4d..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.2.0
-merge_request: 25493
-author:
-type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml
new file mode 100644
index 00000000000..494331158fb
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.5.0
+merge_request: 28497
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml
new file mode 100644
index 00000000000..dd71aa7d461
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.5.1
+merge_request: 28720
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml
new file mode 100644
index 00000000000..9ca6d18c2a8
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.5.2
+merge_request: 29050
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-shell-9-3-0.yml b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
new file mode 100644
index 00000000000..781ff31c7d8
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update to GitLab Shell v9.3.0
+merge_request: 29283
+author:
+type: other
diff --git a/changelogs/unreleased/update-pages.yml b/changelogs/unreleased/update-pages.yml
new file mode 100644
index 00000000000..97a20b6b8fa
--- /dev/null
+++ b/changelogs/unreleased/update-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Pages to v1.6.0
+merge_request: 29048
+author:
+type: other
diff --git a/changelogs/unreleased/update-psd-doc.yml b/changelogs/unreleased/update-psd-doc.yml
new file mode 100644
index 00000000000..38c8d1c0d68
--- /dev/null
+++ b/changelogs/unreleased/update-psd-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Update project security dashboard documentation
+merge_request: 28681
+author:
+type: changed
diff --git a/changelogs/unreleased/update-smileys-new.yml b/changelogs/unreleased/update-smileys-new.yml
new file mode 100644
index 00000000000..0e3f606c81b
--- /dev/null
+++ b/changelogs/unreleased/update-smileys-new.yml
@@ -0,0 +1,5 @@
+---
+title: Update new smiley icons, find n replace old names with new ones
+merge_request: 28338
+author: Jarek Ostrowski
+type: changed
diff --git a/changelogs/unreleased/variables-boolean-type.yml b/changelogs/unreleased/variables-boolean-type.yml
new file mode 100644
index 00000000000..7776657f60a
--- /dev/null
+++ b/changelogs/unreleased/variables-boolean-type.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: change masked attribute type to Boolean'
+merge_request: 28758
+author:
+type: other
diff --git a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
deleted file mode 100644
index 7a6bda1580d..00000000000
--- a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed alignment of changed icon in Web IDE
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/weimeng-email-routing.yml b/changelogs/unreleased/weimeng-email-routing.yml
new file mode 100644
index 00000000000..6536433bd03
--- /dev/null
+++ b/changelogs/unreleased/weimeng-email-routing.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to define notification email addresses for groups you belong to.
+merge_request: 25299
+author:
+type: added
diff --git a/changelogs/unreleased/winh-notes-error-handling.yml b/changelogs/unreleased/winh-notes-error-handling.yml
new file mode 100644
index 00000000000..6f23dd459d4
--- /dev/null
+++ b/changelogs/unreleased/winh-notes-error-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Handle errors in successful notes reply
+merge_request: 28082
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-load-languages-from-database.yml b/changelogs/unreleased/zj-load-languages-from-database.yml
deleted file mode 100644
index 1688829b42c..00000000000
--- a/changelogs/unreleased/zj-load-languages-from-database.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Load repository language from the database if detected before
-merge_request: 25518
-author:
-type: performance
diff --git a/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml b/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml
new file mode 100644
index 00000000000..e752e01b701
--- /dev/null
+++ b/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml
@@ -0,0 +1,3 @@
+merge_request: 28871
+title: Improve clone performance by using delta islands
+type: performance
diff --git a/changelogs/unreleased/zj-usage-ping-pool-repository.yml b/changelogs/unreleased/zj-usage-ping-pool-repository.yml
new file mode 100644
index 00000000000..62044a933d0
--- /dev/null
+++ b/changelogs/unreleased/zj-usage-ping-pool-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Add Pool repository to the usage ping
+merge_request: 28267
+author:
+type: other
diff --git a/config.ru b/config.ru
index 5cd79870d54..6f6fb85d8fa 100644
--- a/config.ru
+++ b/config.ru
@@ -13,10 +13,6 @@ if defined?(Unicorn)
# Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, min, max
end
-
- # Monkey patch for fixing Rack 2.0.6 bug:
- # https://gitlab.com/gitlab-org/gitlab-ee/issues/8539
- Unicorn::StreamInput.send(:public, :eof?) # rubocop:disable GitlabSecurity/PublicSend
end
require ::File.expand_path('../config/environment', __FILE__)
diff --git a/config/application.rb b/config/application.rb
index 1c11e347281..cddd91f267a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -94,6 +94,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content)
+ # - Jira shared secret (:sharedSecret)
#
# NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
# introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
@@ -108,6 +109,7 @@ module Gitlab
trace
variables
content
+ sharedSecret
)
# Enable escaping HTML in JSON.
@@ -162,11 +164,6 @@ module Gitlab
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
- config.action_view.sanitized_allowed_protocols = %w(smb)
-
- # Can be removed once upgraded to Rails 5.1 or higher
- config.action_controller.raise_on_unfiltered_parameters = true
-
# Nokogiri is significantly faster and uses less memory than REXML
ActiveSupport::XmlMini.backend = 'Nokogiri'
diff --git a/config/database.yml.example b/config/database.yml.example
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/config/database.yml.example
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 494ddd72556..ac9b02b08d5 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -47,4 +47,7 @@ Rails.application.configure do
config.assets.quiet = true
config.allow_concurrency = defined?(::Puma)
+
+ # BetterErrors live shell (REPL) on every stack frame
+ BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 3461099253a..e7166882eea 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -11,7 +11,7 @@ Rails.application.configure do
# and recreated between test runs. Don't rely on the data there!
# Enabling caching of classes slows start-up time because all controllers
- # are loaded at initalization, but it reduces memory and load because files
+ # are loaded at initialization, but it reduces memory and load because files
# are not reloaded with every request. For example, caching is not necessary
# for loading database migrations but useful for handling Knapsack specs.
config.cache_classes = ENV['CACHE_CLASSES'] == 'true'
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index be23166cb7b..c83f569d885 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -7,7 +7,8 @@
# * are being moved to ApplicationSetting model! #
# If a setting requires an application restart say so in that screen. #
# If you change this file in a Merge Request, please also create #
-# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests #
+# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests. #
+# For more details see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/0928cfb09f43993fd9454b0b14dbd1924b1407bc/doc/settings/gitlab.yml.md #
########################################################################
#
#
@@ -227,7 +228,7 @@ production: &base
# endpoint: 'http://127.0.0.1:9000' # default: nil
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
- ## Packages (maven repository so far)
+ ## Packages (maven repository, npm registry, etc...)
packages:
enabled: false
@@ -244,7 +245,7 @@ production: &base
host: example.com
port: 80 # Set to 443 if you serve the pages with HTTPS
https: false # Set to true if you serve the pages with HTTPS
- artifacts_server: true
+ artifacts_server: true # Set to false if you want to disable online view of HTML artifacts
# external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
admin:
@@ -301,6 +302,10 @@ production: &base
pages_domain_verification_cron_worker:
cron: "*/15 * * * *"
+ # Periodically migrate diffs from the database to external storage
+ schedule_migrate_external_diffs_worker:
+ cron: "15 * * * *"
+
registry:
# enabled: true
# host: registry.example.com
@@ -310,6 +315,14 @@ production: &base
# path: shared/registry
# issuer: gitlab-issuer
+
+ ## Error Reporting and Logging with Sentry
+ sentry:
+ # enabled: false
+ # dsn: https://<key>@sentry.io/<project>
+ # clientside_dsn: https://<key>@sentry.io/<project>
+ # environment: 'production' # e.g. development, staging, production
+
#
# 2. GitLab CI settings
# ==========================
@@ -379,19 +392,54 @@ production: &base
# "start_tls" or "simple_tls". Defaults to true.
verify_certificates: true
- # Specifies the path to a file containing a PEM-format CA certificate,
- # e.g. if you need to use an internal CA.
- #
- # Example: '/etc/ca.pem'
- #
- ca_file: ''
-
- # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
- # is not appropriate.
- #
- # Example: 'TLSv1_1'
- #
- ssl_version: ''
+ # OpenSSL::SSL::SSLContext options.
+ tls_options:
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_file: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
+ # Specific SSL ciphers to use in communication with LDAP servers.
+ #
+ # Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2'
+ ciphers: ''
+
+ # Client certificate
+ #
+ # Example:
+ # cert: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
+ # bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
+ # CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4
+ # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
+ # ...
+ # 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80
+ # Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg==
+ # -----END CERTIFICATE -----
+ cert: ''
+
+ # Client private key
+ # key: |
+ # -----BEGIN PRIVATE KEY-----
+ # MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6
+ # bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN
+ # 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C
+ # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
+ # ...
+ # +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9
+ # l6RG+a/mW+0rCWn8JAd464Ps9hE=
+ # -----END PRIVATE KEY-----
+ key: ''
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
@@ -653,8 +701,8 @@ production: &base
# # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# # encryption: 'AES256'
# # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
- # # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
- # # 'encryption' must also be set in order for this to have any effect.
+ # # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
+ # # 'encryption' must also be set in order for this to have any effect.
# # encryption_key: '<base64 key>'
# # Specifies Amazon S3 storage class to use for backups, this is optional
# # storage_class: 'STANDARD'
@@ -662,7 +710,7 @@ production: &base
## GitLab Shell settings
gitlab_shell:
path: /home/git/gitlab-shell/
- hooks_path: /home/git/gitlab-shell/hooks/
+ authorized_keys_file: /home/git/.ssh/authorized_keys
# File that contains the secret key for verifying access for gitlab-shell.
# Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app).
@@ -704,6 +752,8 @@ production: &base
monitoring:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
+ # Time between sampling of Puma metrics, in seconds
+ # puma_sampler_interval: 5
# IP whitelist to access monitoring endpoints
ip_whitelist:
- 127.0.0.0/8
@@ -752,6 +802,10 @@ test:
enabled: true
external_diffs:
enabled: false
+ # Diffs may be `always` external (the default), or they can be made external
+ # after they have become `outdated` (i.e., the MR is closed or a new version
+ # has been pushed).
+ # when: always
# The location where external diffs are stored (default: shared/external-diffs).
# storage_path: shared/external-diffs
object_store:
@@ -820,7 +874,7 @@ test:
path: tmp/tests/backups
gitlab_shell:
path: tmp/tests/gitlab-shell/
- hooks_path: tmp/tests/gitlab-shell/hooks/
+ authorized_keys_file: tmp/tests/authorized_keys
issues_tracker:
redmine:
title: "Redmine"
@@ -888,6 +942,10 @@ test:
app_id: 'YOUR_CLIENT_ID',
app_secret: 'YOUR_CLIENT_SECRET',
args: { scope: 'aq:name email~rs address aq:push' } }
+ - { name: 'salesforce',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET'
+ }
ldap:
enabled: false
servers:
diff --git a/config/helpers/is_ee_env.js b/config/helpers/is_ee_env.js
new file mode 100644
index 00000000000..3fe9bb891eb
--- /dev/null
+++ b/config/helpers/is_ee_env.js
@@ -0,0 +1,9 @@
+const fs = require('fs');
+const path = require('path');
+
+const ROOT_PATH = path.resolve(__dirname, '../..');
+
+module.exports =
+ process.env.IS_GITLAB_EE !== undefined
+ ? JSON.parse(process.env.IS_GITLAB_EE)
+ : fs.existsSync(path.join(ROOT_PATH, 'ee'));
diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb
index 02bded43083..9225a99a584 100644
--- a/config/initializers/01_secret_token.rb
+++ b/config/initializers/01_secret_token.rb
@@ -1,3 +1,14 @@
+# WARNING: If you add a new secret to this file, make sure you also
+# update Omnibus GitLab or updates will fail. Omnibus is responsible for
+# writing the `secrets.yml` file. If Omnibus doesn't know about a
+# secret, Rails will attempt to write to the file, but this will fail
+# because Rails doesn't have write access.
+#
+# As an example:
+# * https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27581
+# * https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/3267
+#
+#
# This file needs to be loaded BEFORE any initializers that attempt to
# prepend modules that require access to secrets (e.g. EE's 0_as_concern.rb).
#
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index dfcf1e648b4..0c8d94ccaed 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -40,6 +40,24 @@ if Settings.ldap['enabled'] || Rails.env.test?
# Since GitLab 10.0, verify_certificates defaults to true for security.
server['verify_certificates'] = true if server['verify_certificates'].nil?
+ # Expose ability to set `tls_options` directly. Deprecate `ca_file` and
+ # `ssl_version` in favor of `tls_options` hash option.
+ server['tls_options'] ||= {}
+
+ if server['ssl_version'] || server['ca_file']
+ Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`'
+ end
+
+ if server['ssl_version']
+ server['tls_options']['ssl_version'] ||= server['ssl_version']
+ server.delete('ssl_version')
+ end
+
+ if server['ca_file']
+ server['tls_options']['ca_file'] ||= server['ca_file']
+ server.delete('ca_file')
+ end
+
Settings.ldap['servers'][key] = server
end
end
@@ -108,6 +126,7 @@ Settings['issues_tracker'] ||= {}
# GitLab
#
Settings['gitlab'] ||= Settingslogic.new({})
+Settings.gitlab['default_project_creation'] ||= ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
@@ -117,6 +136,8 @@ Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= ENV['GITLAB_PORT'] || (Settings.gitlab.https ? 443 : 80)
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
+# / is not a valid relative URL root
+Settings.gitlab['relative_url_root'] = '' if Settings.gitlab['relative_url_root'] == '/'
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}"
@@ -197,6 +218,15 @@ Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.regi
Settings.registry['path'] = Settings.absolute(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'))
#
+# Error Reporting and Logging with Sentry
+#
+Settings['sentry'] ||= Settingslogic.new({})
+Settings.sentry['enabled'] ||= false
+Settings.sentry['dsn'] ||= nil
+Settings.sentry['environment'] ||= nil
+Settings.sentry['clientside_dsn'] ||= nil
+
+#
# Pages
#
Settings['pages'] ||= Settingslogic.new({})
@@ -220,6 +250,7 @@ Settings.pages.admin['certificate'] ||= ''
#
Settings['external_diffs'] ||= Settingslogic.new({})
Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil?
+Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil?
Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs'))
Settings.external_diffs['object_store'] = ObjectStoreSettings.parse(Settings.external_diffs['object_store'])
@@ -318,6 +349,10 @@ Settings.cron_jobs['pages_domain_verification_cron_worker'] ||= Settingslogic.ne
Settings.cron_jobs['pages_domain_verification_cron_worker']['cron'] ||= '*/15 * * * *'
Settings.cron_jobs['pages_domain_verification_cron_worker']['job_class'] = 'PagesDomainVerificationCronWorker'
+Settings.cron_jobs['pages_domain_removal_cron_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['pages_domain_removal_cron_worker']['cron'] ||= '47 0 * * *'
+Settings.cron_jobs['pages_domain_removal_cron_worker']['job_class'] = 'PagesDomainRemovalCronWorker'
+
Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *'
Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker'
@@ -326,6 +361,10 @@ Settings.cron_jobs['prune_web_hook_logs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_web_hook_logs_worker']['cron'] ||= '0 */1 * * *'
Settings.cron_jobs['prune_web_hook_logs_worker']['job_class'] = 'PruneWebHookLogsWorker'
+Settings.cron_jobs['schedule_migrate_external_diffs_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['schedule_migrate_external_diffs_worker']['cron'] ||= '15 * * * *'
+Settings.cron_jobs['schedule_migrate_external_diffs_worker']['job_class'] = 'ScheduleMigrateExternalDiffsWorker'
+
#
# Sidekiq
#
@@ -337,7 +376,8 @@ Settings['sidekiq']['log_format'] ||= 'default'
#
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/')
-Settings.gitlab_shell['hooks_path'] = Settings.absolute(Settings.gitlab_shell['hooks_path'] || Settings.gitlab['user_home'] + '/gitlab-shell/hooks/')
+Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead
+Settings.gitlab_shell['authorized_keys_file'] ||= nil
Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
@@ -451,6 +491,7 @@ 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['puma_sampler_interval'] ||= 5
Settings.monitoring['ruby_sampler_interval'] ||= 60
Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({})
Settings.monitoring.sidekiq_exporter['enabled'] ||= false
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 8052880cc3d..4da683014d4 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -19,22 +19,22 @@ Gitlab::Application.configure do |config|
config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
end
-Sidekiq.configure_server do |config|
- config.on(:startup) do
- Gitlab::Metrics::SidekiqMetricsExporter.instance.start
- end
-end
-
if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
Gitlab::Cluster::LifecycleEvents.on_worker_start do
defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change
- unless Sidekiq.server?
+ if defined?(::Unicorn)
Gitlab::Metrics::Samplers::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_sampler_interval).start
end
Gitlab::Metrics::Samplers::RubySampler.initialize_instance(Settings.monitoring.ruby_sampler_interval).start
end
+
+ if defined?(::Puma)
+ Gitlab::Cluster::LifecycleEvents.on_master_start do
+ Gitlab::Metrics::Samplers::PumaSampler.initialize_instance(Settings.monitoring.puma_sampler_interval).start
+ end
+ end
end
Gitlab::Cluster::LifecycleEvents.on_master_restart do
diff --git a/config/initializers/8_devise.rb b/config/initializers/8_devise.rb
index 4683b02f300..3dd12c7e64d 100644
--- a/config/initializers/8_devise.rb
+++ b/config/initializers/8_devise.rb
@@ -100,6 +100,11 @@ Devise.setup do |config|
# secure: true in order to force SSL only cookies.
# config.cookie_options = {}
+ # When set to false, does not sign a user in automatically after their password is
+ # changed. Defaults to true, so a user is signed in automatically after a password
+ # is changed.
+ config.sign_in_after_change_password = false
+
# Send a notification email when the user's password is changed
config.send_password_change_notification = true
diff --git a/config/initializers/action_dispatch_http_mime_negotiation.rb b/config/initializers/action_dispatch_http_mime_negotiation.rb
index bdf5b0babfb..6c31de2de55 100644
--- a/config/initializers/action_dispatch_http_mime_negotiation.rb
+++ b/config/initializers/action_dispatch_http_mime_negotiation.rb
@@ -2,7 +2,7 @@
# the extension of the full URL path if no explicit `format` param or `Accept`
# header is provided, like when simply browsing to a page in your browser.
#
-# This is undesireable in GitLab, because many of our paths will end in a ref or
+# This is undesirable in GitLab, because many of our paths will end in a ref or
# blob name that can end with any extension, while these pages should still be
# presented as HTML unless otherwise specified.
diff --git a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
deleted file mode 100644
index 228ced32188..00000000000
--- a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-# This is a monkey patch which must be removed when migrating to Rails 5.1 from 5.0.
-#
-# In Rails 5.0 there was introduced a bug which casts types in the uniqueness validator.
-# https://github.com/rails/rails/pull/23523/commits/811a4fa8eb6ceea841e61e8ac05747ffb69595ae
-#
-# That causes to bugs like this:
-#
-# 1) API::Users POST /user/:id/gpg_keys/:key_id/revoke when authenticated revokes existing key
-# Failure/Error: let(:gpg_key) { create(:gpg_key, user: user) }
-#
-# TypeError:
-# can't cast Hash
-# # ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
-# # ./spec/requests/api/users_spec.rb:908:in `block (4 levels) in <top (required)>'
-# # ------------------
-# # --- Caused by: ---
-# # TypeError:
-# # TypeError
-# # ./spec/requests/api/users_spec.rb:7:in `block (2 levels) in <top (required)>'
-#
-# This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803
-
-if Rails.gem_version >= Gem::Version.new("5.1")
- raise "Remove this monkey patch: #{__FILE__}"
-end
-
-# Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb
-# including local fixes to make Rubocop happy again.
-module ActiveRecord
- module Validations
- class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
- def validate_each(record, attribute, value)
- finder_class = find_finder_class_for(record)
- table = finder_class.arel_table
- value = map_enum_attribute(finder_class, attribute, value)
-
- relation = build_relation(finder_class, table, attribute, value)
-
- if record.persisted?
- if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
- else
- raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
- end
- end
-
- relation = scope_relation(record, table, relation)
- relation = relation.merge(options[:conditions]) if options[:conditions]
-
- if relation.exists?
- error_options = options.except(:case_sensitive, :scope, :conditions)
- error_options[:value] = value
-
- record.errors.add(attribute, :taken, error_options)
- end
- rescue RangeError
- end
-
- protected
-
- def build_relation(klass, table, attribute, value) #:nodoc:
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
-
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
- end
-
- attribute_name = attribute.to_s
-
- column = klass.columns_hash[attribute_name]
- cast_type = klass.type_for_attribute(attribute_name)
-
- comparison =
- if !options[:case_sensitive] && !value.nil?
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
-
- if value.nil?
- klass.unscoped.where(comparison)
- else
- bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
- klass.unscoped.where(comparison, bind)
- end
- end
- end
- end
-end
diff --git a/config/initializers/active_record_build_select.rb b/config/initializers/active_record_build_select.rb
new file mode 100644
index 00000000000..ab5a872cac6
--- /dev/null
+++ b/config/initializers/active_record_build_select.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# rubocop:disable Gitlab/ModuleWithInstanceVariables
+
+# build_select only selects the required fields if the model has ignored_columns.
+# This is incompatible with some migrations or background migration specs because
+# rails keeps a statement cache in memory. So if a model with ignored_columns in a
+# migration is used, the query with select table.col1, table.col2 is stored in the
+# statement cache. If a different migration is then run and one of these columns is
+# removed in the meantime, the query is invalid.
+
+module ActiveRecord
+ module QueryMethods
+ private
+
+ def build_select(arel)
+ if select_values.any?
+ arel.project(*arel_columns(select_values.uniq))
+ else
+ arel.project(@klass.arel_table[Arel.star])
+ end
+ end
+ end
+end
diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb
deleted file mode 100644
index 1bd1a12e4b7..00000000000
--- a/config/initializers/active_record_locking.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# rubocop:disable Lint/RescueException
-
-# Remove this monkey patch when we move to Rails 5.1, because the bug has been fixed in https://github.com/rails/rails/pull/26050.
-if Rails.gem_version >= Gem::Version.new("5.1")
- raise "Remove this monkey patch: #{__FILE__}"
-end
-
-module ActiveRecord
- module Locking
- module Optimistic
- # We overwrite this method because we don't want to have default value
- # for newly created records
- def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
- super
- end
-
- def _update_record(attribute_names = self.attribute_names) #:nodoc:
- return super unless locking_enabled?
- return 0 if attribute_names.empty?
-
- lock_col = self.class.locking_column
- previous_lock_value = send(lock_col).to_i
- increment_lock
-
- attribute_names += [lock_col]
- attribute_names.uniq!
-
- begin
- relation = self.class.unscoped
-
- affected_rows = relation.where(
- self.class.primary_key => id,
- # Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
- lock_col => previous_lock_value == 0 ? [nil, 0] : previous_lock_value
- ).update_all(
- attributes_for_update(attribute_names).map do |name|
- [name, _read_attribute(name)]
- end.to_h
- )
-
- unless affected_rows == 1
- raise ActiveRecord::StaleObjectError.new(self, "update")
- end
-
- affected_rows
-
- # If something went wrong, revert the version.
- rescue Exception
- send(lock_col + '=', previous_lock_value)
- raise
- end
- end
-
- # This is patched because we need it to query `lock_version IS NULL`
- # rather than `lock_version = 0` whenever lock_version is NULL.
- def relation_for_destroy
- return super unless locking_enabled?
-
- column_name = self.class.locking_column
- super.where(self.class.arel_table[column_name].eq(self[column_name]))
- end
- end
-
- # This is patched because we want `lock_version` default to `NULL`
- # rather than `0`
- class LockingType
- def deserialize(value)
- super
- end
-
- def serialize(value)
- super
- end
- end
- end
-end
diff --git a/config/initializers/ar_native_database_types.rb b/config/initializers/ar_native_database_types.rb
index 3522b1db536..6d397661f75 100644
--- a/config/initializers/ar_native_database_types.rb
+++ b/config/initializers/ar_native_database_types.rb
@@ -4,7 +4,8 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter
NATIVE_DATABASE_TYPES.merge!(
- bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' }
+ bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' },
+ serial: { name: 'int auto_increment PRIMARY KEY' }
)
end
end
diff --git a/config/initializers/config_initializers_active_record_locking.rb b/config/initializers/config_initializers_active_record_locking.rb
new file mode 100644
index 00000000000..608d63223a3
--- /dev/null
+++ b/config/initializers/config_initializers_active_record_locking.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+# ensure ActiveRecord's version has been required already
+require 'active_record/locking/optimistic'
+
+# rubocop:disable Lint/RescueException
+module ActiveRecord
+ module Locking
+ module Optimistic
+ private
+
+ def _update_row(attribute_names, attempted_action = "update")
+ return super unless locking_enabled?
+
+ begin
+ locking_column = self.class.locking_column
+ previous_lock_value = read_attribute_before_type_cast(locking_column)
+ attribute_names << locking_column
+
+ self[locking_column] += 1
+
+ # Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
+ possible_previous_lock_value = previous_lock_value.to_i == 0 ? [nil, 0] : previous_lock_value
+
+ affected_rows = self.class.unscoped._update_record(
+ arel_attributes_with_values(attribute_names),
+ self.class.primary_key => id_in_database,
+ locking_column => possible_previous_lock_value
+ )
+
+ if affected_rows != 1
+ raise ActiveRecord::StaleObjectError.new(self, attempted_action)
+ end
+
+ affected_rows
+
+ # If something went wrong, revert the locking_column value.
+ rescue Exception
+ self[locking_column] = previous_lock_value.to_i
+ raise
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb
index f7c26732e6d..05eb395028d 100644
--- a/config/initializers/console_message.rb
+++ b/config/initializers/console_message.rb
@@ -5,6 +5,6 @@ if defined?(Rails::Console)
puts "-------------------------------------------------------------------------------------"
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)}"
- puts " #{Gitlab::Database.adapter_name}:".ljust(justify) + Gitlab::Database.version
+ puts " #{Gitlab::Database.human_adapter_name}:".ljust(justify) + Gitlab::Database.version
puts "-------------------------------------------------------------------------------------"
end
diff --git a/config/initializers/fog_core_patch.rb b/config/initializers/fog_core_patch.rb
new file mode 100644
index 00000000000..d3d02216d45
--- /dev/null
+++ b/config/initializers/fog_core_patch.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+#
+# fog-core v2 changed the namespace format:
+#
+# Old: Fog::<service>::<provider> (e.g. Fog::Storage::AWS).
+# New: Fog::<provider>::<service> (e.g. Fog::AWS::Storage)
+#
+# To preserve backwards compatibility, fog-core v2.1.0 tries to load the
+# old schema first, but falls back to the older version if that
+# fails. This creates misleading warnings with fog-aws. See
+# https://github.com/fog/fog-aws/issues/504#issuecomment-468067991 for
+# more details.
+#
+# fog-core v2.1.2 reverses the load order
+# (https://github.com/fog/fog-core/pull/229), which works for fog-aws
+# but causes a stream of deprecation warnings for fog-google.
+# fog-google locked the dependency on fog-core v2.1.0 as a result
+# (https://github.com/fog/fog-google/issues/421) until the new namespace
+# is supported.
+#
+# Since we currently have some Fog gems that have not been updated, this
+# monkey patch makes a smarter decision about which namespace to try
+# first. This squelches a significant number of warning messages.
+#
+# Since this patch is mostly cosmetic, it can be removed safely at any
+# time, but it's probably best to wait until the following issues are
+# closed:
+#
+# fog-google: https://github.com/fog/fog-google/issues/421
+# fog-rackspace: https://github.com/fog/fog-rackspace/issues/29
+# fog-aliyun: https://github.com/fog/fog-aliyun/issues/23
+module Fog
+ module ServicesMixin
+ # Gems that have not yet updated with the new fog-core namespace
+ LEGACY_FOG_PROVIDERS = %w(google rackspace aliyun).freeze
+
+ def service_provider_constant(service_name, provider_name)
+ args = service_provider_search_args(service_name, provider_name)
+ Fog.const_get(args.first).const_get(*const_get_args(args.second))
+ rescue NameError # Try to find the constant from in an alternate location
+ Fog.const_get(args.second).const_get(*const_get_args(args.first))
+ end
+
+ def service_provider_search_args(service_name, provider_name)
+ if LEGACY_FOG_PROVIDERS.include?(provider_name.downcase)
+ [service_name, provider_name]
+ else
+ [provider_name, service_name]
+ end
+ end
+ end
+end
diff --git a/config/initializers/gettext_rails_i18n_patch.rb b/config/initializers/gettext_rails_i18n_patch.rb
index c1342f48ebd..714dd505824 100644
--- a/config/initializers/gettext_rails_i18n_patch.rb
+++ b/config/initializers/gettext_rails_i18n_patch.rb
@@ -2,7 +2,7 @@ require 'gettext_i18n_rails/haml_parser'
require 'gettext_i18n_rails_js/parser/javascript'
require 'json'
-VUE_TRANSLATE_REGEX = /((%[\w.-]+)(?:\s))?{{ (N|n|s)?__\((.*)\) }}/
+VUE_TRANSLATE_REGEX = /((%[\w.-]+)(?:\s))?{{ (N|n|s)?__\((.*)\) }}/.freeze
module GettextI18nRails
class HamlParser
diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb
new file mode 100644
index 00000000000..f1bc289f1f0
--- /dev/null
+++ b/config/initializers/graphql.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+GraphQL::ObjectType.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
+GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
+
+GraphQL::Schema::Object.accepts_definition(:authorize)
+GraphQL::Schema::Field.accepts_definition(:authorize)
diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb
new file mode 100644
index 00000000000..51bd48af320
--- /dev/null
+++ b/config/initializers/hipchat_client_patch.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
+module HipChat
+ class Client
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
+ end
+
+ class Room
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
+ end
+
+ class User
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
+ end
+end
diff --git a/config/initializers/http_hostname_override.rb b/config/initializers/http_hostname_override.rb
new file mode 100644
index 00000000000..58dd380326f
--- /dev/null
+++ b/config/initializers/http_hostname_override.rb
@@ -0,0 +1,49 @@
+# This override allows passing `@hostname_override` to the SNI protocol,
+# which is used to lookup the correct SSL certificate in the
+# request handshake process.
+#
+# Given we've forced the HTTP request to be sent to the resolved
+# IP address in a few scenarios (e.g.: `Gitlab::HTTP` through
+# `Gitlab::UrlBlocker.validate!`), we need to provide the _original_
+# hostname via SNI in order to have a clean connection setup.
+#
+# This is ultimately needed in order to avoid DNS rebinding attacks
+# through HTTP requests.
+#
+class OpenSSL::SSL::SSLContext
+ attr_accessor :hostname_override
+end
+
+class OpenSSL::SSL::SSLSocket
+ module HostnameOverride
+ # rubocop: disable Gitlab/ModuleWithInstanceVariables
+ def hostname=(hostname)
+ super(@context.hostname_override || hostname)
+ end
+
+ def post_connection_check(hostname)
+ super(@context.hostname_override || hostname)
+ end
+ # rubocop: enable Gitlab/ModuleWithInstanceVariables
+ end
+
+ prepend HostnameOverride
+end
+
+class Net::HTTP
+ attr_accessor :hostname_override
+ SSL_IVNAMES << :@hostname_override
+ SSL_ATTRIBUTES << :hostname_override
+
+ module HostnameOverride
+ def addr_port
+ return super unless hostname_override
+
+ addr = hostname_override
+ default_port = use_ssl? ? Net::HTTP.https_default_port : Net::HTTP.http_default_port
+ default_port == port ? addr : "#{addr}:#{port}"
+ end
+ end
+
+ prepend HostnameOverride
+end
diff --git a/config/initializers/jira.rb b/config/initializers/jira.rb
new file mode 100644
index 00000000000..05f784a6a2a
--- /dev/null
+++ b/config/initializers/jira.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# Changes JIRA DVCS user agent requests in order to be successfully handled
+# by our API.
+#
+# Gitlab::Jira::Middleware is only defined on EE
+#
+# Use safe_constantize because the class may exist but has not been loaded yet
+if "Gitlab::Jira::Middleware".safe_constantize
+ Rails.application.config.middleware.use(Gitlab::Jira::Middleware)
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 164954d1293..1ad93e14f7e 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -28,9 +28,14 @@ unless Sidekiq.server?
}
gitaly_calls = Gitlab::GitalyClient.get_request_count
- payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
+
+ if gitaly_calls > 0
+ payload[:gitaly_calls] = gitaly_calls
+ payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms
+ end
+
payload[:response] = event.payload[:response] if event.payload[:response]
- payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
+ payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
payload
end
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index e051e5c68c4..cb108416b10 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -10,6 +10,18 @@ elsif Gitlab::Database.postgresql?
require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
+
+ # Remove once we have https://github.com/peek/peek-pg/pull/10
+ module ::Peek::PGInstrumented
+ def exec_params(*args)
+ start = Time.now
+ super(*args)
+ ensure
+ duration = (Time.now - start)
+ PEEK_DB_CLIENT.query_time.update { |value| value + duration }
+ PEEK_DB_CLIENT.query_count.update { |value| value + 1 }
+ end
+ end
else
raise "Unsupported database adapter for peek!"
end
@@ -19,7 +31,7 @@ Peek.into Peek::Views::Gitaly
Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::Redis
Peek.into Peek::Views::GC
-Peek.into Peek::Views::Tracing if Gitlab::Tracing.tracing_url_enabled?
+Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
# rubocop:disable Naming/ClassAndModuleCamelCase
class PEEK_DB_CLIENT
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
index b066f3788ec..7e912180820 100644
--- a/config/initializers/postgresql_opclasses_support.rb
+++ b/config/initializers/postgresql_opclasses_support.rb
@@ -78,7 +78,7 @@ module ActiveRecord
if index_name.length > max_index_length
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
diff --git a/config/initializers/premailer.rb b/config/initializers/premailer.rb
index cb00d3cfe95..87f8e67ef1c 100644
--- a/config/initializers/premailer.rb
+++ b/config/initializers/premailer.rb
@@ -4,5 +4,6 @@ Premailer::Rails.config.merge!(
preserve_styles: true,
remove_comments: true,
remove_ids: false,
- remove_scripts: false
+ remove_scripts: false,
+ output_encoding: 'US-ASCII'
)
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index 8bb9ea29c33..2a3fdc8de5f 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -1,7 +1,15 @@
+# frozen_string_literal: true
+#
# Adds logging for all Rack Attack blocks and throttling events.
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
if [:throttle, :blacklist].include? req.env['rack.attack.match_type']
- Rails.logger.info("Rack_Attack: #{req.env['rack.attack.match_type']} #{req.ip} #{req.request_method} #{req.fullpath}")
+ Gitlab::AuthLogger.error(
+ message: 'Rack_Attack',
+ env: req.env['rack.attack.match_type'],
+ ip: req.ip,
+ request_method: req.request_method,
+ fullpath: req.fullpath
+ )
end
end
diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb
new file mode 100644
index 00000000000..5c4f2dd708c
--- /dev/null
+++ b/config/initializers/rack_timeout.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# Unicorn terminates any request which runs longer than 60 seconds.
+# Puma doesn't have any timeout mechanism for terminating long-running
+# requests, to make sure that server is not paralyzed by long-running
+# or stuck queries, we add a request timeout which terminates the
+# request after 60 seconds. This may be dangerous in some situations
+# (https://github.com/heroku/rack-timeout/blob/master/doc/exceptions.md)
+# and it's used only as the last resort. In such case this termination is
+# logged and we should fix the potential timeout issue in the code itself.
+
+if defined?(::Puma) && !Rails.env.test?
+ require 'rack/timeout/base'
+
+ Gitlab::Application.configure do |config|
+ config.middleware.insert_before(Rack::Runtime, Rack::Timeout,
+ service_timeout: 60,
+ wait_timeout: 90)
+ end
+
+ observer = Gitlab::RackTimeoutObserver.new
+ Rack::Timeout.register_state_change_observer(:gitlab_rack_timeout, &observer.callback)
+end
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index 2de310753a9..715e17057e0 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -1,7 +1,28 @@
+# frozen_string_literal: true
+
+return unless Rails.env.test?
+
module RspecProfilingExt
- module PSQL
- def establish_connection
- ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+ module Collectors
+ class CSVWithTimestamps < ::RspecProfiling::Collectors::CSV
+ TIMESTAMP_FIELDS = %w(created_at updated_at).freeze
+ HEADERS = (::RspecProfiling::Collectors::CSV::HEADERS + TIMESTAMP_FIELDS).freeze
+
+ def insert(attributes)
+ output << HEADERS.map do |field|
+ if TIMESTAMP_FIELDS.include?(field)
+ Time.now
+ else
+ attributes.fetch(field.to_sym)
+ end
+ end
+ end
+
+ private
+
+ def output
+ @output ||= ::CSV.open(path, "w").tap { |csv| csv << HEADERS }
+ end
end
end
@@ -10,9 +31,13 @@ module RspecProfilingExt
if ENV['CI_COMMIT_REF_NAME']
"#{defined?(Gitlab::License) ? 'ee' : 'ce'}:#{ENV['CI_COMMIT_REF_NAME']}"
else
- super
+ super&.chomp
end
end
+
+ def sha
+ super&.chomp
+ end
end
module Run
@@ -30,16 +55,11 @@ module RspecProfilingExt
end
end
-if Rails.env.test?
- RspecProfiling.configure do |config|
- if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
- RspecProfiling::Collectors::PSQL.prepend(RspecProfilingExt::PSQL)
- config.collector = RspecProfiling::Collectors::PSQL
- end
-
- if ENV.key?('CI')
- RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
- RspecProfiling::Run.prepend(RspecProfilingExt::Run)
- end
+RspecProfiling.configure do |config|
+ if ENV.key?('CI') || ENV.key?('RSPEC_PROFILING')
+ RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
+ RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+ config.collector = RspecProfilingExt::Collectors::CSVWithTimestamps
+ config.csv_path = -> { "rspec_profiling/#{Time.now.to_i}-#{SecureRandom.hex(8)}-rspec-data.csv" }
end
end
diff --git a/config/initializers/seed_fu.rb b/config/initializers/seed_fu.rb
new file mode 100644
index 00000000000..2e48e41a311
--- /dev/null
+++ b/config/initializers/seed_fu.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+if Gitlab.ee?
+ SeedFu.fixture_paths += %W[ee/db/fixtures ee/db/fixtures/#{Rails.env}]
+end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index abc91c3ae51..e5589ce0ad1 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -14,12 +14,28 @@ def configure_sentry
Raven.configure do |config|
config.dsn = Gitlab::CurrentSettings.current_application_settings.sentry_dsn
config.release = Gitlab.revision
+ config.current_environment = Gitlab.config.sentry.environment.presence
# Sanitize fields based on those sanitized from Rails.
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
config.tags = { program: Gitlab.process_name }
+ # Debugging for https://gitlab.com/gitlab-org/gitlab-ce/issues/57727
+ config.before_send = lambda do |event, hint|
+ if ActiveModel::MissingAttributeError === hint[:exception]
+ columns_hash = ActiveRecord::Base
+ .connection
+ .schema_cache
+ .instance_variable_get(:@columns_hash)
+ .map { |k, v| [k, v.map(&:first)] }
+ .to_h
+
+ event.extra.merge!(columns_hash)
+ end
+
+ event
+ end
end
end
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index be4183f39be..7b69cf11288 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,5 +1,17 @@
require 'sidekiq/web'
+def enable_reliable_fetch?
+ return true unless Feature::FlipperFeature.table_exists?
+
+ Feature.enabled?(:gitlab_sidekiq_reliable_fetcher, default_enabled: true)
+end
+
+def enable_semi_reliable_fetch_mode?
+ return true unless Feature::FlipperFeature.table_exists?
+
+ Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true)
+end
+
# Disable the Sidekiq Rack session since GitLab already has its own session store.
# CSRF protection still works (https://github.com/mperham/sidekiq/commit/315504e766c4fd88a29b7772169060afc4c40329).
Sidekiq::Web.set :sessions, false
@@ -11,6 +23,10 @@ queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
# Default is to retry 25 times with exponential backoff. That's too much.
Sidekiq.default_worker_options = { retry: 3 }
+if Rails.env.development?
+ Sidekiq.default_worker_options[:backtrace] = true
+end
+
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
Sidekiq.configure_server do |config|
@@ -18,7 +34,7 @@ Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] && !enable_json_logs
- chain.add Gitlab::SidekiqMiddleware::Shutdown
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
@@ -41,9 +57,8 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections!
end
- if Feature::FlipperFeature.table_exists? && Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
- # By default we're going to use Semi Reliable Fetch
- config.options[:semi_reliable_fetch] = Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true)
+ if enable_reliable_fetch?
+ config.options[:semi_reliable_fetch] = enable_semi_reliable_fetch_mode?
Sidekiq::ReliableFetch.setup_reliable_fetch!(config)
end
@@ -73,6 +88,9 @@ Sidekiq.configure_server do |config|
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
# https://github.com/mikel/mail/issues/912#issuecomment-214850355
Mail.eager_autoload!
+
+ # Ensure the whole process group is terminated if possible
+ Gitlab::SidekiqSignals.install!(Sidekiq::CLI::SIGNAL_HANDLERS)
end
Sidekiq.configure_client do |config|
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
index ddd91150c90..3c8779f238f 100644
--- a/config/initializers/tracing.rb
+++ b/config/initializers/tracing.rb
@@ -1,16 +1,14 @@
# frozen_string_literal: true
-if Gitlab::Tracing.enabled?
- require 'opentracing'
-
+if Labkit::Tracing.enabled?
Rails.application.configure do |config|
- config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Gitlab::Tracing::RackMiddleware
+ config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Labkit::Tracing::RackMiddleware
end
# Instrument the Sidekiq client
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
- chain.add Gitlab::Tracing::Sidekiq::ClientMiddleware
+ chain.add Labkit::Tracing::Sidekiq::ClientMiddleware
end
end
@@ -18,20 +16,20 @@ if Gitlab::Tracing.enabled?
if Sidekiq.server?
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
- chain.add Gitlab::Tracing::Sidekiq::ServerMiddleware
+ chain.add Labkit::Tracing::Sidekiq::ServerMiddleware
end
end
end
# Instrument Rails
- Gitlab::Tracing::Rails::ActiveRecordSubscriber.instrument
- Gitlab::Tracing::Rails::ActionViewSubscriber.instrument
+ Labkit::Tracing::Rails::ActiveRecordSubscriber.instrument
+ Labkit::Tracing::Rails::ActionViewSubscriber.instrument
# In multi-processed clustered architectures (puma, unicorn) don't
# start tracing until the worker processes are spawned. This works
# around issues when the opentracing implementation spawns threads
Gitlab::Cluster::LifecycleEvents.on_worker_start do
- tracer = Gitlab::Tracing::Factory.create_tracer(Gitlab.process_name, Gitlab::Tracing.connection_string)
+ tracer = Labkit::Tracing::Factory.create_tracer(Gitlab.process_name, Labkit::Tracing.connection_string)
OpenTracing.global_tracer = tracer if tracer
end
end
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index 7af465d8443..13896408806 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -14,10 +14,8 @@ module Rack
end
gitlab_trusted_proxies = Array(Gitlab.config.gitlab.trusted_proxies).map do |proxy|
- begin
- IPAddr.new(proxy)
- rescue IPAddr::InvalidAddressError
- end
+ IPAddr.new(proxy)
+rescue IPAddr::InvalidAddressError
end.compact
Rails.application.config.action_dispatch.trusted_proxies = (
diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index 151cad3ef9a..5aa6f73c5c5 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -30,7 +30,6 @@ def instrument_classes(instrumentation)
# are included.
%w(app services [^concerns]**) => %w(app services),
%w(lib gitlab conflicts) => ['lib'],
- %w(lib gitlab diff) => ['lib'],
%w(lib gitlab email message) => ['lib'],
%w(lib gitlab checks) => ['lib']
}
diff --git a/config/karma.config.js b/config/karma.config.js
index e1d7c30b1c2..2a5bf3581e0 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -4,14 +4,27 @@ const chalk = require('chalk');
const webpack = require('webpack');
const argumentsParser = require('commander');
const webpackConfig = require('./webpack.config.js');
+const IS_EE = require('./helpers/is_ee_env');
const ROOT_PATH = path.resolve(__dirname, '..');
+const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/;
-function fatalError(message) {
+function exitError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
process.exit(1);
}
+function exitWarn(message) {
+ console.error(chalk.yellow(`\nWarn: ${message}\n`));
+ process.exit(0);
+}
+
+function exit(message, isError = true) {
+ const fn = isError ? exitError : exitWarn;
+
+ fn(message);
+}
+
// disable problematic options
webpackConfig.entry = undefined;
webpackConfig.mode = 'development';
@@ -26,10 +39,11 @@ webpackConfig.devtool = 'cheap-inline-source-map';
webpackConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV || process.env.NODE_ENV || null),
- })
+ }),
);
-const specFilters = argumentsParser
+const options = argumentsParser
+ .option('--no-fail-on-empty-test-suite')
.option(
'-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.',
@@ -37,13 +51,25 @@ const specFilters = argumentsParser
memo.push(filter, filter.replace(/\/?$/, '/**/*.js'));
return memo;
},
- []
+ [],
)
- .parse(process.argv).filterSpec;
+ .parse(process.argv);
-if (specFilters.length) {
- const specsPath = /^(?:\.[\\\/])?spec[\\\/]javascripts[\\\/]/;
+const specFilters = options.filterSpec;
+const createContext = (specFiles, regex, suffix) => {
+ const newContext = specFiles.reduce((context, file) => {
+ const relativePath = file.replace(SPECS_PATH, '');
+ context[file] = `./${relativePath}`;
+ return context;
+ }, {});
+
+ webpackConfig.plugins.push(
+ new webpack.ContextReplacementPlugin(regex, path.join(ROOT_PATH, suffix), newContext),
+ );
+};
+
+if (specFilters.length) {
// resolve filters
let filteredSpecFiles = specFilters.map(filter =>
glob
@@ -51,7 +77,7 @@ if (specFilters.length) {
root: ROOT_PATH,
matchBase: true,
})
- .filter(path => path.endsWith('spec.js'))
+ .filter(path => path.endsWith('spec.js')),
);
// flatten
@@ -61,32 +87,28 @@ if (specFilters.length) {
filteredSpecFiles = [...new Set(filteredSpecFiles)];
if (filteredSpecFiles.length < 1) {
- fatalError('Your filter did not match any test files.');
+ const isError = options.failOnEmptyTestSuite;
+
+ exit('Your filter did not match any test files.', isError);
}
- if (!filteredSpecFiles.every(file => specsPath.test(file))) {
- fatalError('Test files must be located within /spec/javascripts.');
+ if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) {
+ exitError('Test files must be located within /spec/javascripts.');
}
- const newContext = filteredSpecFiles.reduce((context, file) => {
- const relativePath = file.replace(specsPath, '');
- context[file] = `./${relativePath}`;
- return context;
- }, {});
+ const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee'));
+ createContext(CE_FILES, /[^e]{2}[\\\/]spec[\\\/]javascripts$/, 'spec/javascripts');
- webpackConfig.plugins.push(
- new webpack.ContextReplacementPlugin(
- /spec[\\\/]javascripts$/,
- path.join(ROOT_PATH, 'spec/javascripts'),
- newContext
- )
- );
+ const EE_FILES = filteredSpecFiles.filter(file => file.startsWith('ee'));
+ createContext(EE_FILES, /ee[\\\/]spec[\\\/]javascripts$/, 'ee/spec/javascripts');
}
// Karma configuration
module.exports = function(config) {
process.env.TZ = 'Etc/UTC';
+ const fixturesPath = `${IS_EE ? 'ee/' : ''}spec/javascripts/fixtures`;
+
const karmaConfig = {
basePath: ROOT_PATH,
browsers: ['ChromeHeadlessCustom'],
@@ -101,28 +123,53 @@ module.exports = function(config) {
// chrome cannot run in sandboxed mode inside a docker container unless it is run with
// escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN)
'--no-sandbox',
+ // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2870
+ '--enable-features=NetworkService,NetworkServiceInProcess',
],
},
},
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
- { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false },
+ { pattern: `${fixturesPath}/**/*@(.json|.html|.png|.bmpr|.pdf)`, included: false },
],
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
+ 'ee/spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
},
- reporters: ['progress'],
+ reporters: ['mocha'],
webpack: webpackConfig,
webpackMiddleware: { stats: 'errors-only' },
+ plugins: [
+ 'karma-chrome-launcher',
+ 'karma-coverage-istanbul-reporter',
+ 'karma-jasmine',
+ 'karma-junit-reporter',
+ 'karma-mocha-reporter',
+ 'karma-sourcemap-loader',
+ 'karma-webpack',
+ ],
};
if (process.env.CI) {
- karmaConfig.reporters = ['mocha', 'junit'];
+ karmaConfig.reporters.push('junit');
karmaConfig.junitReporter = {
outputFile: 'junit_karma.xml',
useBrowserName: false,
};
+ } else {
+ // ignore 404s in local environment because we are not fixing them and they bloat the log
+ function ignore404() {
+ return (request, response /* next */) => {
+ response.writeHead(404);
+ return response.end('NOT FOUND');
+ };
+ }
+
+ karmaConfig.middleware = ['ignore-404'];
+ karmaConfig.plugins.push({
+ 'middleware:ignore-404': ['factory', ignore404],
+ });
}
if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') {
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index a2dff92908e..23bbc9f4035 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -60,7 +60,8 @@ en:
scopes:
api: Access the authenticated user's API
read_user: Read the authenticated user's personal information
- read_repository: Allows read-access to the repository
+ read_repository: Allows read-only access to the repository
+ write_repository: Allows read-write access to the repository
read_registry: Grants permission to read container registry images
openid: Authenticate using OpenID Connect
sudo: Perform API actions as any user in the system
@@ -73,6 +74,8 @@ en:
Grants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.
read_repository:
Grants read-only access to repositories on private projects using Git-over-HTTP (not using the API).
+ write_repository:
+ Grants read-write access to repositories on private projects using Git-over-HTTP (not using the API).
read_registry:
Grants read-only access to container registry images on private projects.
openid:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e8dbc033a7c..a3dceb2fb62 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -10,6 +10,12 @@ en:
target: Target issue
group:
path: Group URL
+ project/error_tracking_setting:
+ token: "Auth Token"
+ project: "Project"
+ api_url: "Sentry API URL"
+ project/metrics_setting:
+ external_dashboard_url: "External dashboard URL"
errors:
messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index 9bdaf1575e9..3c67ca36791 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -1,42 +1,33 @@
- # NGINX Ingress metrics for pre-0.16.0 versions
+dashboard: 'Environment metrics'
+priority: 1
+panel_groups:
+# NGINX Ingress metrics for pre-0.16.0 versions
- group: Response metrics (NGINX Ingress VTS)
priority: 10
- metrics:
+ panels:
- title: "Throughput"
+ type: "area-chart"
y_label: "Requests / Sec"
- required_metrics:
- - nginx_upstream_responses_total
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_throughput_status_code
query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
unit: req / sec
label: Status Code
- series:
- - label: status_code
- when:
- - value: 2xx
- color: green
- - value: 4xx
- color: orange
- - value: 5xx
- color: red
- title: "Latency"
+ type: "area-chart"
y_label: "Latency (ms)"
- required_metrics:
- - nginx_upstream_response_msecs_avg
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_latency_pod_average
query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
label: Pod average
unit: ms
- title: "HTTP Error Rate"
+ type: "area-chart"
y_label: "HTTP Errors"
- required_metrics:
- - nginx_upstream_responses_total
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_http_error_rate
query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
label: 5xx Errors
@@ -44,218 +35,180 @@
# NGINX Ingress metrics for post-0.16.0 versions
- group: Response metrics (NGINX Ingress)
priority: 10
- metrics:
+ panels:
- title: "Throughput"
+ type: "area-chart"
y_label: "Requests / Sec"
- required_metrics:
- - nginx_ingress_controller_requests
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_16_throughput_status_code
query_range: 'sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code)'
unit: req / sec
label: Status Code
- series:
- - label: status_code
- when:
- - value: 2xx
- color: green
- - value: 3xx
- color: blue
- - value: 4xx
- color: orange
- - value: 5xx
- color: red
- title: "Latency"
+ type: "area-chart"
y_label: "Latency (ms)"
- required_metrics:
- - nginx_ingress_controller_ingress_upstream_latency_seconds_sum
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_16_latency_pod_average
query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000'
label: Pod average
unit: ms
- title: "HTTP Error Rate"
+ type: "area-chart"
y_label: "HTTP Errors"
- required_metrics:
- - nginx_ingress_controller_requests
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_ingress_16_http_error_rate
query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100'
label: 5xx Errors
unit: "%"
- group: Response metrics (HA Proxy)
priority: 10
- metrics:
+ panels:
- title: "Throughput"
+ type: "area-chart"
y_label: "Requests / Sec"
- required_metrics:
- - haproxy_frontend_http_requests_total
weight: 1
- queries:
+ metrics:
- id: response_metrics_ha_proxy_throughput_status_code
query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code)'
unit: req / sec
label: Status Code
- series:
- - label: status_code
- when:
- - value: 2xx
- color: green
- - value: 4xx
- color: yellow
- - value: 5xx
- color: red
- title: "HTTP Error Rate"
+ type: "area-chart"
y_label: "Error Rate (%)"
- required_metrics:
- - haproxy_frontend_http_responses_total
weight: 1
- queries:
+ metrics:
- id: response_metrics_ha_proxy_http_error_rate
query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
label: HTTP Errors
unit: "%"
- group: Response metrics (AWS ELB)
priority: 10
- metrics:
+ panels:
- title: "Throughput"
+ type: "area-chart"
y_label: "Requests / Sec"
- required_metrics:
- - aws_elb_request_count_sum
weight: 1
- queries:
+ metrics:
- id: response_metrics_aws_elb_throughput_requests
query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
label: Total
unit: req / sec
- title: "Latency"
+ type: "area-chart"
y_label: "Latency (ms)"
- required_metrics:
- - aws_elb_latency_average
weight: 1
- queries:
+ metrics:
- id: response_metrics_aws_elb_latency_average
query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
label: Average
unit: ms
- title: "HTTP Error Rate"
+ type: "area-chart"
y_label: "Error Rate (%)"
- required_metrics:
- - aws_elb_request_count_sum
- - aws_elb_httpcode_backend_5_xx_sum
weight: 1
- queries:
+ metrics:
- id: response_metrics_aws_elb_http_error_rate
query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
label: HTTP Errors
unit: "%"
- group: Response metrics (NGINX)
priority: 10
- metrics:
+ panels:
- title: "Throughput"
+ type: "area-chart"
y_label: "Requests / Sec"
- required_metrics:
- - nginx_server_requests
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_throughput_status_code
query_range: 'sum(rate(nginx_server_requests{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (code)'
unit: req / sec
label: Status Code
- series:
- - label: status_code
- when:
- - value: 2xx
- color: green
- - value: 4xx
- color: orange
- - value: 5xx
- color: red
- title: "Latency"
+ type: "area-chart"
y_label: "Latency (ms)"
- required_metrics:
- - nginx_server_requestMsec
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_latency
query_range: 'avg(nginx_server_requestMsec{%{environment_filter}})'
label: Upstream
unit: ms
- title: "HTTP Error Rate"
+ type: "area-chart"
y_label: "HTTP 500 Errors / Sec"
- required_metrics:
- - nginx_server_requests
weight: 1
- queries:
+ metrics:
- id: response_metrics_nginx_http_error_rate
query_range: 'sum(rate(nginx_server_requests{code="5xx", %{environment_filter}}[2m]))'
label: HTTP Errors
unit: "errors / sec"
- group: System metrics (Kubernetes)
priority: 5
- metrics:
+ panels:
- title: "Memory Usage (Total)"
+ type: "area-chart"
y_label: "Total Memory Used"
- required_metrics:
- - container_memory_usage_bytes
weight: 4
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_memory_total
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
label: Total
unit: GB
- title: "Core Usage (Total)"
+ type: "area-chart"
y_label: "Total Cores"
- required_metrics:
- - container_cpu_usage_seconds_total
weight: 3
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_cores_total
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
label: Total
unit: "cores"
- title: "Memory Usage (Pod average)"
+ type: "area-chart"
y_label: "Memory Used per Pod"
- required_metrics:
- - container_memory_usage_bytes
weight: 2
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_memory_average
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
label: Pod average
unit: MB
- title: "Canary: Memory Usage (Pod Average)"
+ type: "area-chart"
y_label: "Memory Used per Pod"
- required_metrics:
- - container_memory_usage_bytes
weight: 2
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_memory_average_canary
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
label: Pod average
unit: MB
track: canary
- title: "Core Usage (Pod Average)"
+ type: "area-chart"
y_label: "Cores per Pod"
- required_metrics:
- - container_cpu_usage_seconds_total
weight: 1
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_core_usage
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
label: Pod average
unit: "cores"
- title: "Canary: Core Usage (Pod Average)"
+ type: "area-chart"
y_label: "Cores per Pod"
- required_metrics:
- - container_cpu_usage_seconds_total
weight: 1
- queries:
+ metrics:
- id: system_metrics_kubernetes_container_core_usage_canary
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
label: Pod average
unit: "cores"
track: canary
+ - title: "Knative function invocations"
+ type: "area-chart"
+ y_label: "Invocations"
+ weight: 1
+ metrics:
+ - id: system_metrics_knative_function_invocation_count
+ query_range: 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])/3))'
+ label: invocations / minute
+ unit: requests
diff --git a/config/puma.rb.example b/config/puma.rb.example
new file mode 100644
index 00000000000..6558dbc6cfe
--- /dev/null
+++ b/config/puma.rb.example
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+# Load "path" as a rackup file.
+#
+# The default is "config.ru".
+#
+rackup 'config.ru'
+pidfile '/home/git/gitlab/tmp/pids/puma.pid'
+state_path '/home/git/gitlab/tmp/pids/puma.state'
+
+stdout_redirect '/home/git/gitlab/log/puma.stdout.log',
+ '/home/git/gitlab/log/puma.stderr.log',
+ true
+
+# Configure "min" to be the minimum number of threads to use to answer
+# requests and "max" the maximum.
+#
+# The default is "0, 16".
+#
+threads 1, 16
+
+# By default, workers accept all requests and queue them to pass to handlers.
+# When false, workers accept the number of simultaneous requests configured.
+#
+# Queueing requests generally improves performance, but can cause deadlocks if
+# the app is waiting on a request to itself. See https://github.com/puma/puma/issues/612
+#
+# When set to false this may require a reverse proxy to handle slow clients and
+# queue requests before they reach puma. This is due to disabling HTTP keepalive
+queue_requests false
+
+# Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only
+# accepted protocols.
+bind 'unix:///home/git/gitlab/tmp/sockets/gitlab.socket'
+
+workers 3
+
+require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
+require_relative "/home/git/gitlab/lib/gitlab/cluster/puma_worker_killer_initializer"
+
+on_restart do
+ # Signal application hooks that we're about to restart
+ Gitlab::Cluster::LifecycleEvents.do_master_restart
+end
+
+before_fork do
+ # Signal to the puma killer
+ Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options unless ENV['DISABLE_PUMA_WORKER_KILLER']
+
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+end
+
+Gitlab::Cluster::LifecycleEvents.set_puma_options @config.options
+on_worker_boot do
+ # Signal application hooks of worker start
+ Gitlab::Cluster::LifecycleEvents.do_worker_start
+end
+
+# Preload the application before starting the workers; this conflicts with
+# phased restart feature. (off by default)
+preload_app!
+
+tag 'gitlab-puma-worker'
+
+# Verifies that all workers have checked in to the master process within
+# the given timeout. If not the worker process will be restarted. Default
+# value is 60 seconds.
+#
+worker_timeout 60
diff --git a/config/routes.rb b/config/routes.rb
index 484e05114be..cb90a0134c4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -43,6 +43,7 @@ Rails.application.routes.draw do
get '/autocomplete/users/:id' => 'autocomplete#user'
get '/autocomplete/projects' => 'autocomplete#projects'
get '/autocomplete/award_emojis' => 'autocomplete#award_emojis'
+ get '/autocomplete/merge_request_target_branches' => 'autocomplete#merge_request_target_branches'
# Search
get 'search' => 'search#show'
@@ -74,6 +75,8 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update]
end
+ get 'acme-challenge/' => 'acme_challenges#show'
+
# UserCallouts
resources :user_callouts, only: [:create]
@@ -101,6 +104,8 @@ Rails.application.routes.draw do
member do
scope :applications do
post '/:application', to: 'clusters/applications#create', as: :install_applications
+ patch '/:application', to: 'clusters/applications#update', as: :update_applications
+ delete '/:application', to: 'clusters/applications#destroy', as: :uninstall_applications
end
get :cluster_status, format: :json
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index af333bdc748..ae79beb1dba 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -83,7 +83,7 @@ namespace :admin do
resources(:projects,
path: '/',
constraints: { id: Gitlab::PathRegex.project_route_regex },
- only: [:show]) do
+ only: [:show, :destroy]) do
member do
put :transfer
@@ -111,6 +111,7 @@ namespace :admin do
put :reset_health_check_token
put :clear_repository_check_states
get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences
+ get :lets_encrypt_terms_of_service
end
resources :labels
@@ -120,6 +121,10 @@ namespace :admin do
get :resume
get :pause
end
+
+ collection do
+ get :tag_list, format: :json
+ end
end
resources :jobs, only: :index do
@@ -128,5 +133,7 @@ namespace :admin do
end
end
+ concerns :clusterable
+
root to: 'dashboard#index'
end
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index ec5c68f81df..a959d40881b 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id',
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
- ::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
+ ::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
(request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
end
diff --git a/config/routes/group.rb b/config/routes/group.rb
index a0aeebe4b91..b300fcb757f 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -14,6 +14,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
+ get :details, as: :details_group
get :activity, as: :activity_group
put :transfer, as: :transfer_group
# TODO: Remove as part of refactor in https://gitlab.com/gitlab-org/gitlab-ce/issues/49693
@@ -31,6 +32,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd' do
put :reset_registration_token
+ patch :update_auto_devops
end
end
@@ -67,7 +69,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end
end
- resources :boards, only: [:index, :show]
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
resources :runners, only: [:index, :edit, :update, :destroy, :show] do
member do
diff --git a/config/routes/import.rb b/config/routes/import.rb
index 24013eb2c88..9fe2688de1e 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -67,4 +67,6 @@ namespace :import do
get :jobs
post :upload
end
+
+ resource :phabricator, only: [:create, :new], controller: :phabricator
end
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index c1cac3905f1..0e213b0b989 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -17,7 +17,11 @@ resource :profile, only: [:show, :update] do
delete :unlink
end
end
- resource :notifications, only: [:show, :update]
+
+ resource :notifications, only: [:show, :update] do
+ resources :groups, only: :update
+ end
+
resource :password, only: [:new, :create, :edit, :update] do
member do
put :reset
diff --git a/config/routes/project.rb b/config/routes/project.rb
index b4ebc7df4fe..d44ff62bc2a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -26,24 +26,150 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
module: :projects,
as: :project) do
- resources :autocomplete_sources, only: [] do
- collection do
- get 'members'
- get 'issues'
- get 'merge_requests'
- get 'labels'
- get 'milestones'
- get 'commands'
- get 'snippets'
+ # Begin of the /-/ scope.
+ # Use this scope for all new project routes.
+ scope '-' do
+ get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
+
+ resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
+ collection do
+ resources :artifacts, only: [] do
+ collection do
+ get :latest_succeeded,
+ path: '*ref_name_and_path',
+ format: false
+ end
+ end
+ end
+
+ member do
+ get :status
+ post :cancel
+ post :unschedule
+ post :retry
+ post :play
+ post :erase
+ get :trace, defaults: { format: 'json' }
+ get :raw
+ get :terminal
+ get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
+ end
+
+ resource :artifacts, only: [] do
+ get :download
+ get :browse, path: 'browse(/*path)', format: false
+ get :file, path: 'file/*path', format: false
+ get :raw, path: 'raw/*path', format: false
+ post :keep
+ end
+ end
+
+ namespace :ci do
+ resource :lint, only: [:show, :create]
+ end
+
+ namespace :settings do
+ get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
+
+ resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
+ post :reset_cache
+ put :reset_registration_token
+ end
+
+ resource :operations, only: [:show, :update]
+ resource :integrations, only: [:show]
+
+ resource :repository, only: [:show], controller: :repository do
+ post :create_deploy_token, path: 'deploy_token/create'
+ post :cleanup
+ end
end
+
+ resources :autocomplete_sources, only: [] do
+ collection do
+ get 'members'
+ get 'issues'
+ get 'merge_requests'
+ get 'labels'
+ get 'milestones'
+ get 'commands'
+ get 'snippets'
+ end
+ end
+
+ resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
+ collection do
+ delete :leave
+
+ # Used for import team
+ # from another project
+ get :import
+ post :apply_import
+ end
+
+ member do
+ post :resend_invite
+ end
+ end
+
+ resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
+ member do
+ put :enable
+ put :disable
+ end
+ end
+
+ resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
+ member do
+ put :revoke
+ end
+ end
+
+ resources :milestones, constraints: { id: /\d+/ } do
+ member do
+ post :promote
+ put :sort_issues
+ put :sort_merge_requests
+ get :merge_requests
+ get :participants
+ get :labels
+ end
+ end
+
+ resources :labels, except: [:show], constraints: { id: /\d+/ } do
+ collection do
+ post :generate
+ post :set_priorities
+ end
+
+ member do
+ post :promote
+ post :toggle_subscription
+ delete :remove_priority
+ end
+ end
+
+ resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
+ member do
+ put :test
+ end
+ end
+
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
+ resources :releases, only: [:index]
+ resources :forks, only: [:index, :new, :create]
+ resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
+
+ resource :import, only: [:new, :create, :show]
+ resource :avatar, only: [:show, :destroy]
end
+ # End of the /-/ scope.
#
# Templates
#
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} }
- resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
@@ -70,12 +196,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
- member do
- put :test
- end
- end
-
resource :mattermost, only: [:new, :create]
namespace :prometheus do
@@ -84,28 +204,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
- member do
- put :enable
- put :disable
- end
- end
-
- resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
- member do
- put :revoke
- end
- end
-
- resources :releases, only: [:index]
- resources :forks, only: [:index, :new, :create]
- resource :import, only: [:new, :create, :show]
-
resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do
member do
get :commit_change_content
post :merge
- post :cancel_merge_when_pipeline_succeeds
+ post :cancel_auto_merge
get :pipeline_status
get :ci_environments_status
post :toggle_subscription
@@ -201,6 +304,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :failures
get :status
end
+
+ member do
+ resources :stages, only: [], param: :name do
+ post :play_manual
+ end
+ end
end
resources :pipeline_schedules, except: [:show] do
@@ -218,7 +327,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :terminal
get :metrics
get :additional_metrics
+ get :metrics_dashboard
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
+
+ get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy'
end
collection do
@@ -250,49 +362,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
namespace :serverless do
- get '/functions/:environment_id/:id', to: 'functions#show'
- resources :functions, only: [:index]
- end
-
- scope '-' do
- get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
-
- resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
- collection do
- resources :artifacts, only: [] do
- collection do
- get :latest_succeeded,
- path: '*ref_name_and_path',
- format: false
- end
- end
- end
-
- member do
- get :status
- post :cancel
- post :unschedule
- post :retry
- post :play
- post :erase
- get :trace, defaults: { format: 'json' }
- get :raw
- get :terminal
- get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
- end
-
- resource :artifacts, only: [] do
- get :download
- get :browse, path: 'browse(/*path)', format: false
- get :file, path: 'file/*path', format: false
- get :raw, path: 'raw/*path', format: false
- post :keep
- end
+ scope :functions do
+ get '/:environment_id/:id', to: 'functions#show'
+ get '/:environment_id/:id/metrics', to: 'functions#metrics', as: :metrics
end
- namespace :ci do
- resource :lint, only: [:show, :create]
- end
+ resources :functions, only: [:index]
end
draw :legacy_builds
@@ -324,66 +399,26 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :milestones, constraints: { id: /\d+/ } do
- member do
- post :promote
- put :sort_issues
- put :sort_merge_requests
- get :merge_requests
- get :participants
- get :labels
- end
- end
-
- resources :labels, except: [:show], constraints: { id: /\d+/ } do
- collection do
- post :generate
- post :set_priorities
- end
-
- member do
- post :promote
- post :toggle_subscription
- delete :remove_priority
- end
- end
-
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
+
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
post :mark_as_spam
post :move
- get :referenced_merge_requests
get :related_branches
get :can_create_branch
get :realtime_changes
post :create_merge_request
get :discussions, format: :json
end
+
collection do
post :bulk_update
post :import_csv
end
end
- resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
- collection do
- delete :leave
-
- # Used for import team
- # from another project
- get :import
- post :apply_import
- end
-
- member do
- post :resend_invite
- end
- end
-
- resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
-
resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
delete :delete_attachment
@@ -394,9 +429,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- # On CE only index and show are needed
- resources :boards, only: [:index, :show]
-
resources :todos, only: [:create]
resources :uploads, only: [:create] do
@@ -431,18 +463,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
end
- namespace :settings do
- get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
- resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
- post :reset_cache
- put :reset_registration_token
- end
- resource :integrations, only: [:show]
- resource :repository, only: [:show], controller: :repository do
- post :create_deploy_token, path: 'deploy_token/create'
- post :cleanup
- end
- end
resources :error_tracking, only: [:index], controller: :error_tracking do
collection do
@@ -454,10 +474,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# its preferable to keep it below all other project routes
draw :wiki
draw :repository
-
- namespace :settings do
- resource :operations, only: [:show, :update]
- end
end
resources(:projects,
@@ -482,4 +498,22 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
end
+
+ # Legacy routes.
+ # Introduced in 12.0.
+ # Should be removed after 12.1
+ scope(path: '*namespace_id',
+ as: :namespace,
+ namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
+ scope(path: ':project_id',
+ constraints: { project_id: Gitlab::PathRegex.project_route_regex },
+ module: :projects,
+ as: :project) do
+ Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
+ :network, :graphs, :autocomplete_sources,
+ :project_members, :deploy_keys, :deploy_tokens,
+ :labels, :milestones, :services, :boards, :releases,
+ :forks, :group_links, :import, :avatar)
+ end
+ end
end
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index f5201b9ddbb..b96315bfe8b 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -39,7 +39,7 @@ scope format: false do
end
end
- scope constraints: { id: Gitlab::PathRegex.git_reference_regex } do
+ scope path: '-', constraints: { id: Gitlab::PathRegex.git_reference_regex } do
resources :network, only: [:show]
resources :graphs, only: [:show] do
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 90cd787d5ac..fd9ce4d3374 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -30,6 +30,7 @@
- [pipeline_default, 3]
- [pipeline_cache, 3]
- [deployment, 3]
+ - [auto_merge, 3]
- [pipeline_hooks, 2]
- [gitlab_shell, 2]
- [email_receiver, 2]
@@ -68,6 +69,7 @@
- [background_migration, 1]
- [gcp_cluster, 1]
- [project_migrate_hashed_storage, 1]
+ - [project_rollback_hashed_storage, 1]
- [hashed_storage, 1]
- [pages_domain_verification, 1]
- [object_storage_upload, 1]
@@ -85,5 +87,9 @@
- [repository_cleanup, 1]
- [delete_stored_files, 1]
- [remote_mirror_notification, 2]
+ - [project_daily_statistics, 1]
- [import_issues_csv, 2]
- [chat_notification, 2]
+ - [migrate_external_diffs, 1]
+ - [update_project_statistics, 1]
+ - [phabricator_import_import_tasks, 1]
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
index f7541bb9d55..ae3dc2e37e1 100644
--- a/config/unicorn.rb.example.development
+++ b/config/unicorn.rb.example.development
@@ -58,4 +58,3 @@ after_fork do |server, worker|
# addr = "127.0.0.1:#{9293 + worker.nr}"
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
end
-
diff --git a/config/webpack.config.js b/config/webpack.config.js
index cf9e77d2424..19b48845305 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -11,6 +11,7 @@ const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
+const IS_EE = require('./helpers/is_ee_env');
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
@@ -44,6 +45,14 @@ function generateEntries() {
pageEntries.forEach(path => generateAutoEntries(path));
+ if (IS_EE) {
+ const eePageEntries = glob.sync('pages/**/index.js', {
+ cwd: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ });
+ eePageEntries.forEach(path => generateAutoEntries(path, 'ee'));
+ watchAutoEntries.push(path.join(ROOT_PATH, 'ee/app/assets/javascripts/pages/'));
+ }
+
const autoEntryKeys = Object.keys(autoEntriesMap);
autoEntriesCount = autoEntryKeys.length;
@@ -68,6 +77,31 @@ function generateEntries() {
return Object.assign(manualEntries, autoEntries);
}
+const alias = {
+ '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
+ emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
+ empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
+ icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
+ images: path.join(ROOT_PATH, 'app/assets/images'),
+ vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
+ vue$: 'vue/dist/vue.esm.js',
+ spec: path.join(ROOT_PATH, 'spec/javascripts'),
+
+ // the following resolves files which are different between CE and EE
+ ee_else_ce: path.join(ROOT_PATH, 'app/assets/javascripts'),
+};
+
+if (IS_EE) {
+ Object.assign(alias, {
+ ee: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ ee_empty_states: path.join(ROOT_PATH, 'ee/app/views/shared/empty_states'),
+ ee_icons: path.join(ROOT_PATH, 'ee/app/views/shared/icons'),
+ ee_images: path.join(ROOT_PATH, 'ee/app/assets/images'),
+ ee_spec: path.join(ROOT_PATH, 'ee/spec/javascripts'),
+ ee_else_ce: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ });
+}
+
module.exports = {
mode: IS_PRODUCTION ? 'production' : 'development',
@@ -85,19 +119,7 @@ module.exports = {
resolve: {
extensions: ['.js', '.gql', '.graphql'],
- alias: {
- '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
- emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
- empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
- icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
- images: path.join(ROOT_PATH, 'app/assets/images'),
- vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- vue$: 'vue/dist/vue.esm.js',
- spec: path.join(ROOT_PATH, 'spec/javascripts'),
-
- // the following resolves files which are different between CE and EE
- ee_else_ce: path.join(ROOT_PATH, 'app/assets/javascripts'),
- },
+ alias,
},
module: {
@@ -245,6 +267,17 @@ module.exports = {
jQuery: 'jquery',
}),
+ new webpack.NormalModuleReplacementPlugin(/^ee_component\/(.*)\.vue/, function(resource) {
+ if (Object.keys(module.exports.resolve.alias).indexOf('ee') >= 0) {
+ resource.request = resource.request.replace(/^ee_component/, 'ee');
+ } else {
+ resource.request = path.join(
+ ROOT_PATH,
+ 'app/assets/javascripts/vue_shared/components/empty_component.js',
+ );
+ }
+ }),
+
// compression can require a lot of compute time and is disabled in CI
IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(),
@@ -256,7 +289,7 @@ module.exports = {
const missingDeps = Array.from(compilation.missingDependencies);
const nodeModulesPath = path.join(ROOT_PATH, 'node_modules');
const hasMissingNodeModules = missingDeps.some(
- file => file.indexOf(nodeModulesPath) !== -1
+ file => file.indexOf(nodeModulesPath) !== -1,
);
// watch for changes to missing node_modules
@@ -267,7 +300,7 @@ module.exports = {
// report our auto-generated bundle count
console.log(
- `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`
+ `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`,
);
callback();
@@ -287,6 +320,10 @@ module.exports = {
reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'),
statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'),
}),
+
+ new webpack.DefinePlugin({
+ 'process.env.IS_GITLAB_EE': JSON.stringify(IS_EE),
+ }),
].filter(Boolean),
devServer: {
diff --git a/config/webpack.config.review_toolbar.js b/config/webpack.config.review_toolbar.js
new file mode 100644
index 00000000000..baaba7ed387
--- /dev/null
+++ b/config/webpack.config.review_toolbar.js
@@ -0,0 +1,58 @@
+const path = require('path');
+const CompressionPlugin = require('compression-webpack-plugin');
+
+const ROOT_PATH = path.resolve(__dirname, '..');
+const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
+const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS;
+const IS_PRODUCTION = process.env.NODE_ENV === 'production';
+
+const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
+
+const alias = {
+ vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
+ spec: path.join(ROOT_PATH, 'spec/javascripts'),
+};
+
+module.exports = {
+ mode: IS_PRODUCTION ? 'production' : 'development',
+
+ context: path.join(ROOT_PATH, 'app/assets/javascripts'),
+
+ name: 'visual_review_toolbar',
+
+ entry: './visual_review_toolbar',
+
+ output: {
+ path: path.join(ROOT_PATH, 'public/assets/webpack'),
+ filename: 'visual_review_toolbar.js',
+ library: 'VisualReviewToolbar',
+ libraryTarget: 'var',
+ },
+
+ resolve: {
+ alias,
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
+ },
+ },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
+
+ plugins: [
+ // compression can require a lot of compute time and is disabled in CI
+ new CompressionPlugin(),
+ ].filter(Boolean),
+
+ devtool: NO_SOURCEMAPS ? false : devtool,
+};
diff --git a/danger/ce_ee_vue_templates/Dangerfile b/danger/ce_ee_vue_templates/Dangerfile
new file mode 100644
index 00000000000..f7715eb2a89
--- /dev/null
+++ b/danger/ce_ee_vue_templates/Dangerfile
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'cgi'
+
+def get_vue_files_with_ce_and_ee_versions(files)
+ files.select do |file|
+ if file.end_with?('.vue')
+ counterpart_path = if file.start_with?('ee/')
+ file.delete_prefix('ee/')
+ else
+ "ee/#{file}"
+ end
+
+ escaped_path = CGI.escape(counterpart_path)
+ api_endpoint = "https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab-ee/repository/files/#{escaped_path}?ref=master"
+ response = HTTParty.get(api_endpoint) # rubocop:disable Gitlab/HTTParty
+ response.code != 404
+ else
+ false
+ end
+ end
+end
+
+vue_candidates = get_vue_files_with_ce_and_ee_versions(helper.all_changed_files)
+
+return if vue_candidates.empty?
+
+message 'This merge request includes changes to Vue files that have both CE and EE versions.'
+
+markdown(<<~MARKDOWN)
+ ## Vue `<template>` in CE and EE
+
+ Some Vue files in CE have a counterpart in EE.
+ (For example, `path/to/file.vue` and `ee/path/to/file.vue`.)
+
+ When run in the context of CE, the `<template>` of the CE Vue file is used.
+ When run in the context of EE, the `<template>` of the EE Vue file is used.
+
+ It's easy to accidentally make a change to a CE `<template>` that _should_
+ appear in both CE and EE without making the change in both places.
+ When this happens, the change only takes effect in CE.
+
+ The following Vue files were changed as part of this merge request that
+ include both a CE and EE version of the file:
+
+ * #{vue_candidates.map { |path| "`#{path}`" }.join("\n* ")}
+
+ If you made a change to the `<template>` of any of these Vue files that
+ should be visible in both CE and EE, please ensure you have made your
+ change to both versions of the file.
+
+ ### A better alternative
+
+ An even _better_ alternative is to refactor this component to only use
+ a single template for both CE and EE. More info on this approach here:
+ https://docs.gitlab.com/ee/development/ee_features.html#template-tag
+MARKDOWN
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 9be1ce2ff86..bdb4343b1d6 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -21,7 +21,7 @@ class EmojiChecker
# alone is not enough, as we'd match `:foo:bar:baz`. Instead, we use this
# regex to save us from having to check for all possible emoji names when we
# know one definitely is not included.
- LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/
+ LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/.freeze
def initialize
names = JSON.parse(File.read(DIGESTS)).keys +
@@ -163,10 +163,10 @@ def lint_commit(commit)
end
if emoji_checker.includes_emoji?(commit.message)
- fail_commit(
+ warn_commit(
commit,
'Avoid the use of Markdown Emoji such as `:+1:`. ' \
- 'These add no value to the commit message, ' \
+ 'These add limited value to the commit message, ' \
'and are displayed as plain text outside of GitLab'
)
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 0cf478b4f89..96c0d9b7478 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -4,33 +4,19 @@ docs_paths_to_review = helper.changes_by_category[:docs]
unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a review ' \
- 'from the Technical Writing team whether before or after merging.'
+ 'from the Technical Writing team.'
markdown(<<~MARKDOWN)
## Documentation review
-The following files will require a review from the [Technical Writing](https://about.gitlab.com/handbook/product/technical-writing/) team:
+The following files require a review from a technical writer:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
-Per the [documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html), the review may occur prior to merging this MR **or** after a maintainer has agreed to review and merge this MR without a tech writer review. (Meanwhile, you are welcome to involve a technical writer at any time if you have questions about writing or updating the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.)
+The review does not need to block merging this merge request. See the:
-The technical writer who, by default, will review this content or collaborate as needed is dependent on the [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) to which the content applies:
-
-| Tech writer | Stage(s) |
-| ------------ | ------------------------------------------------------------ |
-| `@marcia` | ~Create ~Release + ~"development guidelines" |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
-| `@eread` | ~Manage ~Configure ~Geo ~Verify |
-| `@mikelewis` | ~Plan |
-
-**To request a review prior to merging**
-
-- Assign the MR to the applicable technical writer, above. If you are not sure which category the change falls within, or the change is not part of one of these categories, mention one of the usernames above.
-
-**To request a review to commence after merging**
-
-- Create an issue for a doc review [using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and assign it to the applicable technicial writer from the table.
+- [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) for the appropriate technical writer for this review.
+- [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
MARKDOWN
unless gitlab.mr_labels.include?('Documentation')
diff --git a/danger/gitlab_ui_wg/Dangerfile b/danger/gitlab_ui_wg/Dangerfile
new file mode 100644
index 00000000000..02d94fa5ab7
--- /dev/null
+++ b/danger/gitlab_ui_wg/Dangerfile
@@ -0,0 +1,55 @@
+def mention_single_codebase_approvers
+ frontend_maintainers = %w(@filipa @iamphill @psimyn @sarahghp @mishunov)
+ ux_maintainers = %w(@tauriedavis @rverissimo)
+
+ rows = []
+ users = []
+
+ if gitlab.mr_labels.include?('frontend')
+ frontend_maintainer = frontend_maintainers.sample
+
+ rows << "| ~frontend | `#{frontend_maintainer}`"
+ users << frontend_maintainer
+ end
+
+ if gitlab.mr_labels.include?('UX')
+ ux_maintainers = ux_maintainers.sample
+
+ rows << "| ~UX | `#{ux_maintainers}`"
+ users << ux_maintainers
+ end
+
+ if rows.empty?
+ backup_maintainer = frontend_maintainers.sample
+
+ rows << "| ~frontend / ~UX | `#{backup_maintainer}`"
+ users << backup_maintainer
+ end
+
+ markdown(<<~MARKDOWN.strip)
+ ## GitLab UI Working Group changes
+
+ This merge request contains changes related to the work of [cleaning up CSS and creating
+ reusable components](https://gitlab.com/groups/gitlab-org/-/epics/950).
+ These changes will need to be reviewed and approved by the following engineers:
+
+ | Category | Reviewer
+ |----------|---------
+ #{rows.join("\n")}
+
+ To make sure this happens, please follow these steps:
+
+ 1. Add all of the mentioned users to the list of merge request approvals.
+ 2. Assign the merge request to the first person in the above list.
+
+ If you are a reviewer, please follow these steps:
+
+ 1. Review the merge request. If it is good to go, approve it.
+ 2. Once approved, assign to the next person in the above list. If you are
+ the last person in the list, merge the merge request.
+ MARKDOWN
+end
+
+if gitlab.mr_labels.include?('CSS cleanup')
+ mention_single_codebase_approvers
+end
diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb
index 581c0720083..2d7a933e801 100644
--- a/danger/plugins/helper.rb
+++ b/danger/plugins/helper.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-require 'net/http'
-require 'yaml'
-
require_relative '../../lib/gitlab/danger/helper'
module Danger
diff --git a/danger/plugins/roulette.rb b/danger/plugins/roulette.rb
new file mode 100644
index 00000000000..7c62cff0c92
--- /dev/null
+++ b/danger/plugins/roulette.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require_relative '../../lib/gitlab/danger/roulette'
+
+module Danger
+ class Roulette < Plugin
+ # Put the helper code somewhere it can be tested
+ include Gitlab::Danger::Roulette
+ end
+end
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 6cf54d0f854..a0f1447e76a 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'digest/md5'
+
MESSAGE = <<MARKDOWN
## Reviewer roulette
@@ -29,15 +31,23 @@ Please consider creating a merge request to
for them.
MARKDOWN
-def spin(team, project, category)
- reviewers = team.select { |member| member.reviewer?(project, category) }
- maintainers = team.select { |member| member.maintainer?(project, category) }
+def spin_for_category(team, project, category, branch_name)
+ random = roulette.new_random(branch_name)
+ labels = gitlab.mr_labels
+
+ reviewers, traintainers, maintainers =
+ %i[reviewer? traintainer? maintainer?].map do |kind|
+ team.select do |member|
+ member.public_send(kind, project, category, labels) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
- # TODO: filter out people who are currently not in the office
# TODO: take CODEOWNERS into account?
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/57653
- reviewer = reviewers[rand(reviewers.size)]
- maintainer = maintainers[rand(maintainers.size)]
+ # Make traintainers have triple the chance to be picked as a reviewer
+ reviewer = roulette.spin_for_person(reviewers + traintainers + traintainers, random: random)
+ maintainer = roulette.spin_for_person(maintainers, random: random)
"| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
end
@@ -54,14 +64,22 @@ end
changes = helper.changes_by_category
-# Ignore any files that are known but uncategoried. Prompt for any unknown files
+# Ignore any files that are known but uncategorized. Prompt for any unknown files
changes.delete(:none)
categories = changes.keys - [:unknown]
-unless changes.empty?
+# Single codebase MRs are reviewed using a slightly different process, so we
+# disable the review roulette for such MRs.
+# CSS Clean up MRs are reviewed using a slightly different process, so we
+# disable the review roulette for such MRs.
+if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_labels.include?('CSS cleanup')
+ # Strip leading and trailing CE/EE markers
+ canonical_branch_name =
+ roulette.canonical_branch_name(gitlab.mr_json['source_branch'])
+
team =
begin
- helper.project_team
+ roulette.project_team(helper.project_name)
rescue => err
warn("Reviewer roulette failed to load team data: #{err.message}")
[]
@@ -73,7 +91,7 @@ unless changes.empty?
project = helper.project_name
unknown = changes.fetch(:unknown, [])
- rows = categories.map { |category| spin(team, project, category) }
+ rows = categories.map { |category| spin_for_category(team, project, category, canonical_branch_name) }
markdown(MESSAGE)
markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
diff --git a/danger/single_codebase/Dangerfile b/danger/single_codebase/Dangerfile
new file mode 100644
index 00000000000..d1f538bec7f
--- /dev/null
+++ b/danger/single_codebase/Dangerfile
@@ -0,0 +1,63 @@
+def new_teammates(usernames)
+ usernames.map { |u| ::Gitlab::Danger::Teammate.new('username' => u) }
+end
+
+def mention_single_codebase_approvers
+ canonical_branch_name =
+ roulette.canonical_branch_name(gitlab.mr_json['source_branch'])
+
+ random = roulette.new_random(canonical_branch_name)
+
+ frontend_maintainers = new_teammates(%w[filipa iamphill])
+ backend_maintainers = new_teammates(%w[rspeicher rymai yorickpeterse godfat])
+
+ rows = []
+
+ if gitlab.mr_labels.include?('frontend')
+ frontend_maintainer =
+ roulette.spin_for_person(frontend_maintainers, random: random)
+
+ rows << "| ~frontend | #{frontend_maintainer.markdown_name}"
+ end
+
+ if gitlab.mr_labels.include?('backend')
+ backend_maintainer =
+ roulette.spin_for_person(backend_maintainers, random: random)
+
+ rows << "| ~backend | #{backend_maintainer.markdown_name}"
+ end
+
+ if rows.empty?
+ backup_maintainer = backend_maintainers.sample
+
+ rows << "| ~frontend / ~backend | #{backup_maintainer.markdown_name}"
+ end
+
+ markdown(<<~MARKDOWN.strip)
+ ## Single codebase changes
+
+ This merge request contains changes related to the work of moving towards a
+ [single codebase](https://gitlab.com/groups/gitlab-org/-/epics/802) for
+ Community Edition and Enterprise Edition. These changes will need to be
+ reviewed and approved by the following engineers:
+
+ | Category | Reviewer
+ |----------|---------
+ #{rows.join("\n")}
+
+ To make sure this happens, please follow these steps:
+
+ 1. Add all of the mentioned users to the list of merge request approvals.
+ 2. Assign the merge request to the first person in the above list.
+
+ If you are a reviewer, please follow these steps:
+
+ 1. Review the merge request. If it is good to go, approve it.
+ 2. Once approved, assign to the next person in the above list. If you are
+ the last person in the list, merge the merge request.
+ MARKDOWN
+end
+
+if gitlab.mr_labels.include?('single codebase')
+ mention_single_codebase_approvers
+end
diff --git a/db/fixtures/development/02_application_settings.rb b/db/fixtures/development/02_application_settings.rb
new file mode 100644
index 00000000000..7cdc8e40b69
--- /dev/null
+++ b/db/fixtures/development/02_application_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+puts "Creating the default ApplicationSetting record.".color(:green)
+ApplicationSetting.create_from_defaults
+
+# Details https://gitlab.com/gitlab-org/gitlab-ce/issues/46241
+puts "Enable hashed storage for every new projects.".color(:green)
+ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true)
+
+print '.'
diff --git a/db/fixtures/development/03_project.rb b/db/fixtures/development/03_project.rb
new file mode 100644
index 00000000000..46018cf68aa
--- /dev/null
+++ b/db/fixtures/development/03_project.rb
@@ -0,0 +1,137 @@
+require './spec/support/sidekiq'
+
+# rubocop:disable Rails/Output
+
+Sidekiq::Testing.inline! do
+ Gitlab::Seeder.quiet do
+ Gitlab::Seeder.without_gitaly_timeout do
+ project_urls = %w[
+ https://gitlab.com/gitlab-org/gitlab-test.git
+ https://gitlab.com/gitlab-org/gitlab-shell.git
+ https://gitlab.com/gnuwget/wget2.git
+ https://gitlab.com/Commit451/LabCoat.git
+ https://github.com/jashkenas/underscore.git
+ https://github.com/flightjs/flight.git
+ https://github.com/twitter/typeahead.js.git
+ https://github.com/h5bp/html5-boilerplate.git
+ https://github.com/google/material-design-lite.git
+ https://github.com/jlevy/the-art-of-command-line.git
+ https://github.com/FreeCodeCamp/freecodecamp.git
+ https://github.com/google/deepdream.git
+ https://github.com/jtleek/datasharing.git
+ https://github.com/WebAssembly/design.git
+ https://github.com/airbnb/javascript.git
+ https://github.com/tessalt/echo-chamber-js.git
+ https://github.com/atom/atom.git
+ https://github.com/mattermost/mattermost-server.git
+ https://github.com/purifycss/purifycss.git
+ https://github.com/facebook/nuclide.git
+ https://github.com/wbkd/awesome-d3.git
+ https://github.com/kilimchoi/engineering-blogs.git
+ https://github.com/gilbarbara/logos.git
+ https://github.com/reduxjs/redux.git
+ https://github.com/awslabs/s2n.git
+ https://github.com/arkency/reactjs_koans.git
+ https://github.com/twbs/bootstrap.git
+ https://github.com/chjj/ttystudio.git
+ https://github.com/MostlyAdequate/mostly-adequate-guide.git
+ https://github.com/octocat/Spoon-Knife.git
+ https://github.com/opencontainers/runc.git
+ https://github.com/googlesamples/android-topeka.git
+ ]
+
+ large_project_urls = %w[
+ https://github.com/torvalds/linux.git
+ https://gitlab.gnome.org/GNOME/gimp.git
+ https://gitlab.gnome.org/GNOME/gnome-mud.git
+ https://gitlab.com/fdroid/fdroidclient.git
+ https://gitlab.com/inkscape/inkscape.git
+ https://github.com/gnachman/iTerm2.git
+ ]
+
+ def create_project(url, force_latest_storage: false)
+ group_path, project_path = url.split('/')[-2..-1]
+
+ group = Group.find_by(path: group_path)
+
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.description = FFaker::Lorem.sentence
+ group.save!
+
+ group.add_owner(User.first)
+ end
+
+ project_path.gsub!(".git", "")
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ skip_disk_validation: true
+ }
+
+ if force_latest_storage
+ params[:storage_version] = Project::LATEST_STORAGE_VERSION
+ end
+
+ project = nil
+
+ Sidekiq::Worker.skipping_transaction_check do
+ project = Projects::CreateService.new(User.first, params).execute
+
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
+ project.send(:_run_after_commit_queue)
+ project.import_state.send(:_run_after_commit_queue)
+ end
+
+ if project.valid? && project.valid_repo?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
+ end
+
+ # You can specify how many projects you need during seed execution
+ size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
+
+ project_urls.first(size).each_with_index do |url, i|
+ create_project(url, force_latest_storage: i.even?)
+ end
+
+ if ENV['LARGE_PROJECTS'].present?
+ large_project_urls.each(&method(:create_project))
+
+ if ENV['FORK'].present?
+ puts "\nGenerating forks"
+
+ project_name = ENV['FORK'] == 'true' ? 'torvalds/linux' : ENV['FORK']
+
+ project = Project.find_by_full_path(project_name)
+
+ User.offset(1).first(5).each do |user|
+ new_project = Projects::ForkService.new(project, user).execute
+
+ if new_project.valid? && (new_project.valid_repo? || new_project.import_state.scheduled?)
+ print '.'
+ else
+ new_project.errors.full_messages.each do |error|
+ puts "#{new_project.full_path}: #{error}"
+ end
+ print 'F'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/db/fixtures/development/03_settings.rb b/db/fixtures/development/03_settings.rb
deleted file mode 100644
index 3a4a5d436bf..00000000000
--- a/db/fixtures/development/03_settings.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-# Enable hashed storage, in development mode, for all projects by default.
-Gitlab::Seeder.quiet do
- ApplicationSetting.create_from_defaults unless ApplicationSetting.current_without_cache
- ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true)
- print '.'
-end
diff --git a/db/fixtures/development/04_labels.rb b/db/fixtures/development/04_labels.rb
new file mode 100644
index 00000000000..b9ae4098d76
--- /dev/null
+++ b/db/fixtures/development/04_labels.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'digest/md5'
+
+class Gitlab::Seeder::GroupLabels
+ def initialize(group, label_per_group: 10)
+ @group = group
+ @label_per_group = label_per_group
+ end
+
+ def seed!
+ @label_per_group.times do
+ label_title = FFaker::Product.brand
+ Labels::CreateService
+ .new(title: label_title, color: "##{Digest::MD5.hexdigest(label_title)[0..5]}")
+ .execute(group: @group)
+ print '.'
+ end
+ end
+end
+
+class Gitlab::Seeder::ProjectLabels
+ def initialize(project, label_per_project: 5)
+ @project = project
+ @label_per_project = label_per_project
+ end
+
+ def seed!
+ @label_per_project.times do
+ label_title = FFaker::Vehicle.model
+ Labels::CreateService
+ .new(title: label_title, color: "##{Digest::MD5.hexdigest(label_title)[0..5]}")
+ .execute(project: @project)
+ print '.'
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ puts "\nGenerating group labels"
+ Group.all.find_each do |group|
+ Gitlab::Seeder::GroupLabels.new(group).seed!
+ end
+
+ puts "\nGenerating project labels"
+ Project.all.find_each do |project|
+ Gitlab::Seeder::ProjectLabels.new(project).seed!
+ end
+end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
deleted file mode 100644
index 9a5f7cf8175..00000000000
--- a/db/fixtures/development/04_project.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-require './spec/support/sidekiq'
-
-# rubocop:disable Rails/Output
-
-Sidekiq::Testing.inline! do
- Gitlab::Seeder.quiet do
- Gitlab::Seeder.without_gitaly_timeout do
- project_urls = %w[
- https://gitlab.com/gitlab-org/gitlab-test.git
- https://gitlab.com/gitlab-org/gitlab-shell.git
- https://gitlab.com/gnuwget/wget2.git
- https://gitlab.com/Commit451/LabCoat.git
- https://github.com/jashkenas/underscore.git
- https://github.com/flightjs/flight.git
- https://github.com/twitter/typeahead.js.git
- https://github.com/h5bp/html5-boilerplate.git
- https://github.com/google/material-design-lite.git
- https://github.com/jlevy/the-art-of-command-line.git
- https://github.com/FreeCodeCamp/freecodecamp.git
- https://github.com/google/deepdream.git
- https://github.com/jtleek/datasharing.git
- https://github.com/WebAssembly/design.git
- https://github.com/airbnb/javascript.git
- https://github.com/tessalt/echo-chamber-js.git
- https://github.com/atom/atom.git
- https://github.com/mattermost/mattermost-server.git
- https://github.com/purifycss/purifycss.git
- https://github.com/facebook/nuclide.git
- https://github.com/wbkd/awesome-d3.git
- https://github.com/kilimchoi/engineering-blogs.git
- https://github.com/gilbarbara/logos.git
- https://github.com/reduxjs/redux.git
- https://github.com/awslabs/s2n.git
- https://github.com/arkency/reactjs_koans.git
- https://github.com/twbs/bootstrap.git
- https://github.com/chjj/ttystudio.git
- https://github.com/MostlyAdequate/mostly-adequate-guide.git
- https://github.com/octocat/Spoon-Knife.git
- https://github.com/opencontainers/runc.git
- https://github.com/googlesamples/android-topeka.git
- ]
-
- large_project_urls = %w[
- https://github.com/torvalds/linux.git
- https://gitlab.gnome.org/GNOME/gimp.git
- https://gitlab.gnome.org/GNOME/gnome-mud.git
- https://gitlab.com/fdroid/fdroidclient.git
- https://gitlab.com/inkscape/inkscape.git
- https://github.com/gnachman/iTerm2.git
- ]
-
- def create_project(url, force_latest_storage: false)
- group_path, project_path = url.split('/')[-2..-1]
-
- group = Group.find_by(path: group_path)
-
- unless group
- group = Group.new(
- name: group_path.titleize,
- path: group_path
- )
- group.description = FFaker::Lorem.sentence
- group.save
-
- group.add_owner(User.first)
- end
-
- project_path.gsub!(".git", "")
-
- params = {
- import_url: url,
- namespace_id: group.id,
- name: project_path.titleize,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample,
- skip_disk_validation: true
- }
-
- if force_latest_storage
- params[:storage_version] = Project::LATEST_STORAGE_VERSION
- end
-
- project = nil
-
- Sidekiq::Worker.skipping_transaction_check do
- project = Projects::CreateService.new(User.first, params).execute
-
- # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
- # hook won't run until after the fixture is loaded. That is too late
- # since the Sidekiq::Testing block has already exited. Force clearing
- # the `after_commit` queue to ensure the job is run now.
- project.send(:_run_after_commit_queue)
- project.import_state.send(:_run_after_commit_queue)
- end
-
- if project.valid? && project.valid_repo?
- print '.'
- else
- puts project.errors.full_messages
- print 'F'
- end
- end
-
- # You can specify how many projects you need during seed execution
- size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
-
- project_urls.first(size).each_with_index do |url, i|
- create_project(url, force_latest_storage: i.even?)
- end
-
- if ENV['LARGE_PROJECTS'].present?
- large_project_urls.each(&method(:create_project))
-
- if ENV['FORK'].present?
- puts "\nGenerating forks"
-
- project_name = ENV['FORK'] == 'true' ? 'torvalds/linux' : ENV['FORK']
-
- project = Project.find_by_full_path(project_name)
-
- User.offset(1).first(5).each do |user|
- new_project = Projects::ForkService.new(project, user).execute
-
- if new_project.valid? && (new_project.valid_repo? || new_project.import_state.scheduled?)
- print '.'
- else
- new_project.errors.full_messages.each do |error|
- puts "#{new_project.full_path}: #{error}"
- end
- print 'F'
- end
- end
- end
- end
- end
- end
-end
diff --git a/db/fixtures/development/08_settings.rb b/db/fixtures/development/08_settings.rb
deleted file mode 100644
index 141465c06cf..00000000000
--- a/db/fixtures/development/08_settings.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# We want to enable hashed storage for every new project in development
-# Details https://gitlab.com/gitlab-org/gitlab-ce/issues/46241
-Gitlab::Seeder.quiet do
- ApplicationSetting.create_from_defaults unless ApplicationSetting.current_without_cache
- ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true)
- print '.'
-end
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 16243b72f9a..582a5203d1d 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,19 +1,5 @@
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
- Project.all.each do |project|
- 10.times do
- issue_params = {
- title: FFaker::Lorem.sentence(6),
- description: FFaker::Lorem.sentence,
- state: ['opened', 'closed'].sample,
- milestone: project.milestones.sample,
- assignees: [project.team.users.sample],
- created_at: rand(12).months.ago
- }
-
- Issues::CreateService.new(project, project.team.users.sample, issue_params).execute
- print '.'
- end
- end
+ Rake::Task["gitlab:seed:issues"].invoke
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 2051bcff8f0..43b69470d2c 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -12,13 +12,17 @@ Gitlab::Seeder.quiet do
source_branch = branches.pop
target_branch = branches.pop
+ label_ids = project.labels.pluck(:id).sample(3)
+ label_ids += project.group.labels.sample(3) if project.group
+
params = {
source_branch: source_branch,
target_branch: target_branch,
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentences(3).join(" "),
milestone: project.milestones.sample,
- assignee: project.team.users.sample
+ assignees: [project.team.users.sample],
+ label_ids: label_ids
}
# Only create MRs with users that are allowed to create MRs
diff --git a/db/fixtures/development/22_labeled_issues_seed.rb b/db/fixtures/development/22_labeled_issues_seed.rb
deleted file mode 100644
index 3730e9c7958..00000000000
--- a/db/fixtures/development/22_labeled_issues_seed.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-# Creates a project with labeled issues for an user.
-# Run this single seed file using: rake db:seed_fu FILTER=labeled USER_ID=74.
-# If an USER_ID is not provided it will use the last created user.
-require './spec/support/sidekiq'
-
-class Gitlab::Seeder::LabeledIssues
- include ::Gitlab::Utils
-
- def initialize(user)
- @user = user
- end
-
- def seed!
- Sidekiq::Testing.inline! do
- group = create_group
-
- create_projects(group)
- create_labels(group)
- create_issues(group)
- end
-
- print '.'
- end
-
- private
-
- def create_group
- group_name = "group_of_#{@user.username}_#{SecureRandom.hex(4)}"
-
- group_params = {
- name: group_name,
- path: group_name,
- description: FFaker::Lorem.sentence
- }
-
- Groups::CreateService.new(@user, group_params).execute
- end
-
- def create_projects(group)
- 5.times do
- project_name = "project_#{SecureRandom.hex(6)}"
-
- params = {
- namespace_id: group.id,
- name: project_name,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample
- }
-
- Projects::CreateService.new(@user, params).execute
- end
- end
-
- def create_labels(group)
- group.projects.each do |project|
- 5.times do
- label_title = FFaker::Vehicle.model
- Labels::CreateService.new(title: label_title, color: "#69D100").execute(project: project)
- end
- end
-
- 10.times do
- label_title = FFaker::Product.brand
- Labels::CreateService.new(title: label_title).execute(group: group)
- end
- end
-
- def create_issues(group)
- # Get only group labels
- group_labels =
- LabelsFinder.new(@user, group_id: group.id).execute.where.not(group_id: nil)
-
- group.projects.each do |project|
- label_ids = project.labels.pluck(:id).sample(5)
- label_ids.push(*group.labels.sample(4))
-
- 20.times do
- issue_params = {
- title: FFaker::Lorem.sentence(6),
- description: FFaker::Lorem.sentence,
- state: 'opened',
- label_ids: label_ids
-
- }
-
- Issues::CreateService.new(project, @user, issue_params).execute if project.project_feature.present?
- end
- end
- end
-end
-
-Gitlab::Seeder.quiet do
- user_id = ENV['USER_ID']
-
- user =
- if user_id.present?
- User.find(user_id)
- else
- User.last
- end
-
- Gitlab::Seeder::LabeledIssues.new(user).seed!
-end
diff --git a/db/fixtures/development/25_api_personal_access_token.rb b/db/fixtures/development/25_api_personal_access_token.rb
new file mode 100644
index 00000000000..a2e6c674c1f
--- /dev/null
+++ b/db/fixtures/development/25_api_personal_access_token.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require './spec/support/sidekiq'
+
+# Create an api access token for root user with the value: ypCa3Dzb23o5nvsixwPA
+Gitlab::Seeder.quiet do
+ PersonalAccessToken.create!(
+ user_id: User.find_by(username: 'root').id,
+ name: "seeded-api-token",
+ scopes: ["api"],
+ token_digest: "/O0jfLERYT/L5gG8nfByQxqTj43TeLlRzOtJGTzRsbQ="
+ )
+
+ print '.'
+end
diff --git a/db/fixtures/production/001_application_settings.rb b/db/fixtures/production/001_application_settings.rb
index ab15717e9a9..cf647650142 100644
--- a/db/fixtures/production/001_application_settings.rb
+++ b/db/fixtures/production/001_application_settings.rb
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
puts "Creating the default ApplicationSetting record.".color(:green)
Gitlab::CurrentSettings.current_application_settings
diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb
index deadd653ae9..195bde8f34a 100644
--- a/db/importers/common_metrics_importer.rb
+++ b/db/importers/common_metrics_importer.rb
@@ -53,7 +53,7 @@ module Importers
private
def process_content(&blk)
- content.map do |group|
+ content['panel_groups'].map do |group|
process_group(group, &blk)
end
end
@@ -63,28 +63,28 @@ module Importers
group: find_group_title_key(group['group'])
}
- group['metrics'].map do |metric|
- process_metric(metric, attributes, &blk)
+ group['panels'].map do |panel|
+ process_panel(panel, attributes, &blk)
end
end
- def process_metric(metric, attributes, &blk)
+ def process_panel(panel, attributes, &blk)
attributes = attributes.merge(
- title: metric['title'],
- y_label: metric['y_label'])
+ title: panel['title'],
+ y_label: panel['y_label'])
- metric['queries'].map do |query|
- process_metric_query(query, attributes, &blk)
+ panel['metrics'].map do |metric_details|
+ process_metric_details(metric_details, attributes, &blk)
end
end
- def process_metric_query(query, attributes, &blk)
+ def process_metric_details(metric_details, attributes, &blk)
attributes = attributes.merge(
- legend: query['label'],
- query: query['query_range'],
- unit: query['unit'])
+ legend: metric_details['label'],
+ query: metric_details['query_range'],
+ unit: metric_details['unit'])
- yield(query['id'], attributes)
+ yield(metric_details['id'], attributes)
end
def find_or_build_metric!(id)
diff --git a/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
index 148b46f8830..1f9ae3f0080 100644
--- a/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
+++ b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
@@ -1,4 +1,3 @@
-# rubocop:disable all
class ChangeStateToAllowEmptyMergeRequestDiffs < ActiveRecord::Migration[4.2]
def up
change_column :merge_request_diffs, :state, :string, null: true,
diff --git a/db/migrate/20150509180749_convert_legacy_reference_notes.rb b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
index a44a908c2f5..84d4eb9e51f 100644
--- a/db/migrate/20150509180749_convert_legacy_reference_notes.rb
+++ b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
@@ -7,7 +7,8 @@
# mentioned in 54f7727c850972f0401c1312a7c4a6a380de5666
class ConvertLegacyReferenceNotes < ActiveRecord::Migration[4.2]
def up
- execute %q{UPDATE notes SET note = trim(both '_' from note) WHERE system = true AND note LIKE '\_%\_'}
+ quoted_column_name = ActiveRecord::Base.connection.quote_column_name('system')
+ execute %Q{UPDATE notes SET note = trim(both '_' from note) WHERE #{quoted_column_name} = true AND note LIKE '\_%\_'}
end
def down
diff --git a/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb b/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb
index 23a775d6282..9005b42b41f 100644
--- a/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb
+++ b/db/migrate/20161031181638_add_unique_index_to_subscriptions.rb
@@ -9,11 +9,11 @@ class AddUniqueIndexToSubscriptions < ActiveRecord::Migration[4.2]
def up
add_concurrent_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id, :project_id], { unique: true, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' }
- remove_index :subscriptions, name: 'subscriptions_user_id_and_ref_fields' if index_name_exists?(:subscriptions, 'subscriptions_user_id_and_ref_fields', false)
+ remove_index :subscriptions, name: 'subscriptions_user_id_and_ref_fields' if index_name_exists?(:subscriptions, 'subscriptions_user_id_and_ref_fields')
end
def down
add_concurrent_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id], { unique: true, name: 'subscriptions_user_id_and_ref_fields' }
- remove_index :subscriptions, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' if index_name_exists?(:subscriptions, 'index_subscriptions_on_subscribable_and_user_id_and_project_id', false)
+ remove_index :subscriptions, name: 'index_subscriptions_on_subscribable_and_user_id_and_project_id' if index_name_exists?(:subscriptions, 'index_subscriptions_on_subscribable_and_user_id_and_project_id')
end
end
diff --git a/db/migrate/20170330141723_disable_invalid_service_templates2.rb b/db/migrate/20170330141723_disable_invalid_service_templates2.rb
index 91ec19dfa87..f09f3b3e355 100644
--- a/db/migrate/20170330141723_disable_invalid_service_templates2.rb
+++ b/db/migrate/20170330141723_disable_invalid_service_templates2.rb
@@ -1,5 +1,5 @@
# This is the same as DisableInvalidServiceTemplates. Later migrations may have
-# inadventently enabled some invalid templates again.
+# inadvertently enabled some invalid templates again.
#
class DisableInvalidServiceTemplates2 < ActiveRecord::Migration[4.2]
DOWNTIME = false
diff --git a/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb b/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
index cfa63b65df4..65b2c6a57be 100644
--- a/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
+++ b/db/migrate/20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb
@@ -46,13 +46,6 @@ class TurnNestedGroupsIntoRegularGroupsForMysql < ActiveRecord::Migration[4.2]
bulk_insert_members(rows)
- # This method relies on the parent to determine the proper path.
- # Because we reset "parent_id" this method will not return the right path
- # when moving namespaces.
- full_path_was = namespace.send(:full_path_was)
-
- namespace.define_singleton_method(:full_path_was) { full_path_was }
-
namespace.update!(parent_id: nil, path: new_path_for(namespace))
end
end
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
index d40c61f24b1..b4658bc4017 100644
--- a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -126,11 +126,10 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration[4.2]
queues.each do |queue|
# Stealing is racy so it's possible a pop might be called on an
# already-empty queue.
- begin
- remove_orphans(*queue.pop(true))
- stolen = true
- rescue ThreadError
- end
+
+ remove_orphans(*queue.pop(true))
+ stolen = true
+ rescue ThreadError
end
break unless stolen
diff --git a/db/migrate/20171211131502_add_external_classification_authorization_settings_to_appliction_settings.rb b/db/migrate/20171211131502_add_external_classification_authorization_settings_to_appliction_settings.rb
new file mode 100644
index 00000000000..a7dec8732fb
--- /dev/null
+++ b/db/migrate/20171211131502_add_external_classification_authorization_settings_to_appliction_settings.rb
@@ -0,0 +1,29 @@
+class AddExternalClassificationAuthorizationSettingsToApplictionSettings < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings,
+ :external_authorization_service_enabled,
+ :boolean,
+ default: false
+ add_column :application_settings,
+ :external_authorization_service_url,
+ :string
+ add_column :application_settings,
+ :external_authorization_service_default_label,
+ :string
+ end
+
+ def down
+ remove_column :application_settings,
+ :external_authorization_service_default_label
+ remove_column :application_settings,
+ :external_authorization_service_url
+ remove_column :application_settings,
+ :external_authorization_service_enabled
+ end
+end
diff --git a/db/migrate/20171218140451_add_external_authorization_service_classification_label_to_projects.rb b/db/migrate/20171218140451_add_external_authorization_service_classification_label_to_projects.rb
new file mode 100644
index 00000000000..7b83580f025
--- /dev/null
+++ b/db/migrate/20171218140451_add_external_authorization_service_classification_label_to_projects.rb
@@ -0,0 +1,11 @@
+class AddExternalAuthorizationServiceClassificationLabelToProjects < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :projects,
+ :external_authorization_classification_label,
+ :string
+ end
+end
diff --git a/db/migrate/20180115094742_add_default_project_creation_setting.rb b/db/migrate/20180115094742_add_default_project_creation_setting.rb
new file mode 100644
index 00000000000..465a89c39e8
--- /dev/null
+++ b/db/migrate/20180115094742_add_default_project_creation_setting.rb
@@ -0,0 +1,19 @@
+class AddDefaultProjectCreationSetting < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless column_exists?(:application_settings, :default_project_creation)
+ add_column_with_default(:application_settings, :default_project_creation, :integer, default: 2)
+ end
+ end
+
+ def down
+ if column_exists?(:application_settings, :default_project_creation)
+ remove_column(:application_settings, :default_project_creation)
+ end
+ end
+end
diff --git a/db/migrate/20180115113902_add_project_creation_level_to_groups.rb b/db/migrate/20180115113902_add_project_creation_level_to_groups.rb
new file mode 100644
index 00000000000..a10ce54087c
--- /dev/null
+++ b/db/migrate/20180115113902_add_project_creation_level_to_groups.rb
@@ -0,0 +1,17 @@
+class AddProjectCreationLevelToGroups < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ unless column_exists?(:namespaces, :project_creation_level)
+ add_column(:namespaces, :project_creation_level, :integer)
+ end
+ end
+
+ def down
+ if column_exists?(:namespaces, :project_creation_level)
+ remove_column(:namespaces, :project_creation_level, :integer)
+ end
+ end
+end
diff --git a/db/migrate/20180314100728_add_external_authorization_service_timeout_to_application_settings.rb b/db/migrate/20180314100728_add_external_authorization_service_timeout_to_application_settings.rb
new file mode 100644
index 00000000000..c3c6aa0ddf8
--- /dev/null
+++ b/db/migrate/20180314100728_add_external_authorization_service_timeout_to_application_settings.rb
@@ -0,0 +1,18 @@
+class AddExternalAuthorizationServiceTimeoutToApplicationSettings < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ # We can use the regular `add_column` with a default since `application_settings`
+ # is a small table.
+ add_column :application_settings,
+ :external_authorization_service_timeout,
+ :float,
+ default: 0.5
+ end
+
+ def down
+ remove_column :application_settings, :external_authorization_service_timeout
+ end
+end
diff --git a/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
new file mode 100644
index 00000000000..ee3d1078f5e
--- /dev/null
+++ b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
@@ -0,0 +1,16 @@
+class AddExternalAuthMutualTlsFieldsToProjectSettings < ActiveRecord::Migration[4.2]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings,
+ :external_auth_client_cert, :text
+ add_column :application_settings,
+ :encrypted_external_auth_client_key, :text
+ add_column :application_settings,
+ :encrypted_external_auth_client_key_iv, :string
+ add_column :application_settings,
+ :encrypted_external_auth_client_key_pass, :string
+ add_column :application_settings,
+ :encrypted_external_auth_client_key_pass_iv, :string
+ end
+end
diff --git a/db/migrate/20180702134423_generate_missing_routes.rb b/db/migrate/20180702134423_generate_missing_routes.rb
index a440bc3179c..dd1106c9e6a 100644
--- a/db/migrate/20180702134423_generate_missing_routes.rb
+++ b/db/migrate/20180702134423_generate_missing_routes.rb
@@ -98,6 +98,7 @@ class GenerateMissingRoutes < ActiveRecord::Migration[4.2]
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
include EachBatch
include GenerateMissingRoutes::Routable
diff --git a/db/migrate/20181205171941_create_project_daily_statistics.rb b/db/migrate/20181205171941_create_project_daily_statistics.rb
new file mode 100644
index 00000000000..c9e2a1e1aa7
--- /dev/null
+++ b/db/migrate/20181205171941_create_project_daily_statistics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateProjectDailyStatistics < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_daily_statistics, id: :bigserial do |t|
+ t.integer :project_id, null: false
+ t.integer :fetch_count, null: false
+ t.date :date
+
+ t.index [:project_id, :date], unique: true, order: { date: :desc }
+ t.foreign_key :projects, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20190107151029_remove_hipchat_services.rb b/db/migrate/20190107151029_remove_hipchat_services.rb
deleted file mode 100644
index 4741ec88907..00000000000
--- a/db/migrate/20190107151029_remove_hipchat_services.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# See http://doc.gitlab.com/ce/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-
-class RemoveHipchatServices < ActiveRecord::Migration[5.0]
- DOWNTIME = false
-
- def up
- execute "DELETE FROM services WHERE type = 'HipchatService'"
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/migrate/20190114184258_migrate_legacy_attachments.rb b/db/migrate/20190114184258_migrate_legacy_attachments.rb
new file mode 100644
index 00000000000..e9fb7952dc9
--- /dev/null
+++ b/db/migrate/20190114184258_migrate_legacy_attachments.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class MigrateLegacyAttachments < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ MIGRATION = 'MigrateLegacyUploads'.freeze
+ BATCH_SIZE = 5000
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ class Upload < ActiveRecord::Base
+ self.table_name = 'uploads'
+
+ include ::EachBatch
+ end
+
+ def up
+ Upload.where(uploader: 'AttachmentUploader').each_batch(of: BATCH_SIZE) do |relation, index|
+ start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+ delay = index * DELAY_INTERVAL
+
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, [start_id, end_id])
+ end
+ end
+
+ # not needed
+ def down
+ end
+end
diff --git a/db/migrate/20190206193120_add_index_to_tags.rb b/db/migrate/20190206193120_add_index_to_tags.rb
new file mode 100644
index 00000000000..5257ebba003
--- /dev/null
+++ b/db/migrate/20190206193120_add_index_to_tags.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexToTags < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_tags_on_name_trigram'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :tags, :name, name: INDEX_NAME, using: :gin, opclasses: { name: :gin_trgm_ops }
+ end
+
+ def down
+ remove_concurrent_index_by_name(:tags, INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20190211131150_add_state_id_to_issuables.rb b/db/migrate/20190211131150_add_state_id_to_issuables.rb
new file mode 100644
index 00000000000..c1173eb4249
--- /dev/null
+++ b/db/migrate/20190211131150_add_state_id_to_issuables.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddStateIdToIssuables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :issues, :state_id, :integer, limit: 2
+ add_column :merge_requests, :state_id, :integer, limit: 2
+ end
+
+ def down
+ remove_column :issues, :state_id
+ remove_column :merge_requests, :state_id
+ end
+end
diff --git a/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb b/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..2a2a216da7d
--- /dev/null
+++ b/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddMergePipelinesEnabledToCiCdSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :merge_pipelines_enabled, :boolean
+ end
+end
diff --git a/db/migrate/20190218134158_add_masked_to_ci_variables.rb b/db/migrate/20190218134158_add_masked_to_ci_variables.rb
new file mode 100644
index 00000000000..b4999d5b4a9
--- /dev/null
+++ b/db/migrate/20190218134158_add_masked_to_ci_variables.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddMaskedToCiVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :ci_variables, :masked, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :ci_variables, :masked
+ end
+end
diff --git a/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb b/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb
new file mode 100644
index 00000000000..8633875b341
--- /dev/null
+++ b/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddMaskedToCiGroupVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :ci_group_variables, :masked, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :ci_group_variables, :masked
+ end
+end
diff --git a/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb b/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb
new file mode 100644
index 00000000000..85b9e0580f4
--- /dev/null
+++ b/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddEmailHeaderAndFooterEnabledFlagToAppearancesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:appearances, :email_header_and_footer_enabled, :boolean, default: false)
+ end
+
+ def down
+ remove_column(:appearances, :email_header_and_footer_enabled)
+ end
+end
diff --git a/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb
new file mode 100644
index 00000000000..45c7c0949c6
--- /dev/null
+++ b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddExtraShasToCiPipelines < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_pipelines, :source_sha, :binary
+ add_column :ci_pipelines, :target_sha, :binary
+ end
+end
diff --git a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
new file mode 100644
index 00000000000..0048268ca6f
--- /dev/null
+++ b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_SPECS = [
+ [
+ :merge_request_metrics,
+ :latest_closed_at,
+ { where: 'latest_closed_at IS NOT NULL' }
+ ],
+ [
+ :merge_request_metrics,
+ [:merge_request_id, :merged_at],
+ { where: 'merged_at IS NOT NULL' }
+ ],
+ [
+ :merge_request_diffs,
+ [:merge_request_id, :id],
+ {
+ name: 'index_merge_request_diffs_on_merge_request_id_and_id_partial',
+ where: 'NOT stored_externally OR stored_externally IS NULL'
+ }
+ ]
+ ].freeze
+
+ disable_ddl_transaction!
+
+ def up
+ INDEX_SPECS.each do |spec|
+ add_concurrent_index(*spec)
+ end
+ end
+
+ def down
+ INDEX_SPECS.reverse.each do |spec|
+ remove_concurrent_index(*spec)
+ end
+ end
+end
diff --git a/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb
new file mode 100644
index 00000000000..93e7a84fb02
--- /dev/null
+++ b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddAutoDevOpsEnabledToNamespaces < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :namespaces, :auto_devops_enabled, :boolean
+ end
+end
diff --git a/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb b/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb
new file mode 100644
index 00000000000..18c0d2a2e1b
--- /dev/null
+++ b/db/migrate/20190225160300_steal_encrypt_runners_tokens.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class StealEncryptRunnersTokens < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # This cleans after `EncryptRunnersTokens`
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::BackgroundMigration.steal('EncryptRunnersTokens')
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190225160301_add_runner_tokens_indexes.rb b/db/migrate/20190225160301_add_runner_tokens_indexes.rb
new file mode 100644
index 00000000000..3230c2809de
--- /dev/null
+++ b/db/migrate/20190225160301_add_runner_tokens_indexes.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class AddRunnerTokensIndexes < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ # It seems that `ci_runners.token_encrypted` and `projects.runners_token_encrypted`
+ # are non-unique
+
+ def up
+ add_concurrent_index :ci_runners, :token_encrypted
+ add_concurrent_index :projects, :runners_token_encrypted
+ add_concurrent_index :namespaces, :runners_token_encrypted, unique: true
+ end
+
+ def down
+ remove_concurrent_index :ci_runners, :token_encrypted
+ remove_concurrent_index :projects, :runners_token_encrypted
+ remove_concurrent_index :namespaces, :runners_token_encrypted, unique: true
+ end
+end
diff --git a/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
new file mode 100644
index 00000000000..856dfc89fa3
--- /dev/null
+++ b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMultiLineAttributesToSuggestion < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :suggestions, :lines_above, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :lines_below, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :outdated, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_columns :suggestions, :outdated, :lines_above, :lines_below
+ end
+end
diff --git a/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
new file mode 100644
index 00000000000..2c3a54b12a9
--- /dev/null
+++ b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddExternalHostnameToIngressAndKnative < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :clusters_applications_ingress, :external_hostname, :string
+ add_column :clusters_applications_knative, :external_hostname, :string
+ end
+end
diff --git a/db/migrate/20190312071108_add_detected_repository_languages_to_projects.rb b/db/migrate/20190312071108_add_detected_repository_languages_to_projects.rb
new file mode 100644
index 00000000000..5ce0ca19888
--- /dev/null
+++ b/db/migrate/20190312071108_add_detected_repository_languages_to_projects.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddDetectedRepositoryLanguagesToProjects < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :detected_repository_languages, :boolean
+ end
+end
diff --git a/db/migrate/20190312113229_add_remove_at_to_pages_domains.rb b/db/migrate/20190312113229_add_remove_at_to_pages_domains.rb
new file mode 100644
index 00000000000..aa7d2841cd2
--- /dev/null
+++ b/db/migrate/20190312113229_add_remove_at_to_pages_domains.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddRemoveAtToPagesDomains < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ def change
+ add_column :pages_domains, :remove_at, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20190312113634_add_remove_at_index_to_pages_domains.rb b/db/migrate/20190312113634_add_remove_at_index_to_pages_domains.rb
new file mode 100644
index 00000000000..b5ccebd9cfa
--- /dev/null
+++ b/db/migrate/20190312113634_add_remove_at_index_to_pages_domains.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddRemoveAtIndexToPagesDomains < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :pages_domains, :remove_at
+ end
+
+ def down
+ remove_concurrent_index :pages_domains, :remove_at
+ end
+end
diff --git a/db/migrate/20190315191339_create_merge_request_assignees_table.rb b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..6fc4463f281
--- /dev/null
+++ b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_merge_request_assignees_on_merge_request_id_and_user_id'
+
+ def up
+ create_table :merge_request_assignees do |t|
+ t.references :user, foreign_key: { on_delete: :cascade }, index: true, null: false
+ t.references :merge_request, foreign_key: { on_delete: :cascade }, null: false
+ end
+
+ add_index :merge_request_assignees, [:merge_request_id, :user_id], unique: true, name: INDEX_NAME
+ end
+
+ def down
+ drop_table :merge_request_assignees
+ end
+end
diff --git a/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
new file mode 100644
index 00000000000..e9cf2af84a5
--- /dev/null
+++ b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsEncryptNotificationEmailToApplicationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :lets_encrypt_notification_email, :string
+ end
+end
diff --git a/db/migrate/20190322164830_add_auto_ssl_enabled_to_pages_domain.rb b/db/migrate/20190322164830_add_auto_ssl_enabled_to_pages_domain.rb
new file mode 100644
index 00000000000..e74a9535ddf
--- /dev/null
+++ b/db/migrate/20190322164830_add_auto_ssl_enabled_to_pages_domain.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddAutoSslEnabledToPagesDomain < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :pages_domains, :auto_ssl_enabled, :boolean, default: false
+ end
+
+ def down
+ remove_column :pages_domains, :auto_ssl_enabled
+ end
+end
diff --git a/db/migrate/20190325080727_truncate_user_fullname.rb b/db/migrate/20190325080727_truncate_user_fullname.rb
new file mode 100644
index 00000000000..e5f88671eef
--- /dev/null
+++ b/db/migrate/20190325080727_truncate_user_fullname.rb
@@ -0,0 +1,21 @@
+# rubocop:disable Migration/UpdateLargeTable
+class TruncateUserFullname < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ truncated_name = Arel.sql('SUBSTRING(name from 1 for 128)')
+ where_clause = Arel.sql("LENGTH(name) > 128")
+
+ update_column_in_batches(:users, :name, truncated_name) do |table, query|
+ query.where(where_clause)
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/migrate/20190325105715_add_fields_to_user_preferences.rb b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
new file mode 100644
index 00000000000..9ea3b4f9cd8
--- /dev/null
+++ b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddFieldsToUserPreferences < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column(:user_preferences, :timezone, :string)
+ add_column(:user_preferences, :time_display_relative, :boolean)
+ add_column(:user_preferences, :time_format_in_24h, :boolean)
+ end
+
+ def down
+ remove_column(:user_preferences, :timezone)
+ remove_column(:user_preferences, :time_display_relative)
+ remove_column(:user_preferences, :time_format_in_24h)
+ end
+end
diff --git a/db/migrate/20190325165127_add_managed_to_cluster.rb b/db/migrate/20190325165127_add_managed_to_cluster.rb
new file mode 100644
index 00000000000..e960df9d502
--- /dev/null
+++ b/db/migrate/20190325165127_add_managed_to_cluster.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddManagedToCluster < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:clusters, :managed, :boolean, default: true)
+ end
+
+ def down
+ remove_column(:clusters, :managed)
+ end
+end
diff --git a/db/migrate/20190326164045_import_common_metrics_knative.rb b/db/migrate/20190326164045_import_common_metrics_knative.rb
new file mode 100644
index 00000000000..340ec1e1f75
--- /dev/null
+++ b/db/migrate/20190326164045_import_common_metrics_knative.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ImportCommonMetricsKnative < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+ DOWNTIME = false
+
+ def up
+ Importers::CommonMetricsImporter.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
new file mode 100644
index 00000000000..2f3069032a1
--- /dev/null
+++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :notification_settings, :notification_email, :string
+ end
+end
diff --git a/db/migrate/20190329085614_add_lets_encrypt_terms_of_service_accepted_to_application_settings.rb b/db/migrate/20190329085614_add_lets_encrypt_terms_of_service_accepted_to_application_settings.rb
new file mode 100644
index 00000000000..16de63f207f
--- /dev/null
+++ b/db/migrate/20190329085614_add_lets_encrypt_terms_of_service_accepted_to_application_settings.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsEncryptTermsOfServiceAcceptedToApplicationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:application_settings, :lets_encrypt_terms_of_service_accepted, :boolean, default: false)
+ end
+
+ def down
+ remove_column :application_settings, :lets_encrypt_terms_of_service_accepted
+ end
+end
diff --git a/db/migrate/20190408163745_prometheus_knative05_fix.rb b/db/migrate/20190408163745_prometheus_knative05_fix.rb
new file mode 100644
index 00000000000..c11f6f0e29b
--- /dev/null
+++ b/db/migrate/20190408163745_prometheus_knative05_fix.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class PrometheusKnative05Fix < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+ DOWNTIME = false
+
+ def up
+ Importers::CommonMetricsImporter.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190412155659_add_merge_request_blocks.rb b/db/migrate/20190412155659_add_merge_request_blocks.rb
new file mode 100644
index 00000000000..9e7f370d1cf
--- /dev/null
+++ b/db/migrate/20190412155659_add_merge_request_blocks.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddMergeRequestBlocks < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :merge_request_blocks, id: :bigserial do |t|
+ t.references :blocking_merge_request,
+ index: false, null: false,
+ foreign_key: { to_table: :merge_requests, on_delete: :cascade }
+
+ t.references :blocked_merge_request,
+ index: true, null: false,
+ foreign_key: { to_table: :merge_requests, on_delete: :cascade }
+
+ t.index [:blocking_merge_request_id, :blocked_merge_request_id],
+ unique: true,
+ name: 'index_mr_blocks_on_blocking_and_blocked_mr_ids'
+
+ t.timestamps_with_timezone
+ end
+ end
+end
diff --git a/db/migrate/20190415030217_add_variable_type_to_ci_variables.rb b/db/migrate/20190415030217_add_variable_type_to_ci_variables.rb
new file mode 100644
index 00000000000..433f510299a
--- /dev/null
+++ b/db/migrate/20190415030217_add_variable_type_to_ci_variables.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddVariableTypeToCiVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ ENV_VAR_VARIABLE_TYPE = 1
+
+ def up
+ add_column_with_default(:ci_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE)
+ end
+
+ def down
+ remove_column(:ci_variables, :variable_type)
+ end
+end
diff --git a/db/migrate/20190415095825_add_packages_size_to_project_statistics.rb b/db/migrate/20190415095825_add_packages_size_to_project_statistics.rb
new file mode 100644
index 00000000000..99625981563
--- /dev/null
+++ b/db/migrate/20190415095825_add_packages_size_to_project_statistics.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddPackagesSizeToProjectStatistics < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_statistics, :packages_size, :bigint
+ end
+end
diff --git a/db/migrate/20190416185130_add_merge_train_enabled_to_ci_cd_settings.rb b/db/migrate/20190416185130_add_merge_train_enabled_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..eb0e7c41f98
--- /dev/null
+++ b/db/migrate/20190416185130_add_merge_train_enabled_to_ci_cd_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeTrainEnabledToCiCdSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :project_ci_cd_settings, :merge_trains_enabled, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :project_ci_cd_settings, :merge_trains_enabled
+ end
+end
diff --git a/db/migrate/20190416213556_add_variable_type_to_ci_group_variables.rb b/db/migrate/20190416213556_add_variable_type_to_ci_group_variables.rb
new file mode 100644
index 00000000000..dce73caeb5e
--- /dev/null
+++ b/db/migrate/20190416213556_add_variable_type_to_ci_group_variables.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddVariableTypeToCiGroupVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ ENV_VAR_VARIABLE_TYPE = 1
+
+ def up
+ add_column_with_default(:ci_group_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE)
+ end
+
+ def down
+ remove_column(:ci_group_variables, :variable_type)
+ end
+end
diff --git a/db/migrate/20190416213615_add_variable_type_to_ci_pipeline_variables.rb b/db/migrate/20190416213615_add_variable_type_to_ci_pipeline_variables.rb
new file mode 100644
index 00000000000..1010d9bd29e
--- /dev/null
+++ b/db/migrate/20190416213615_add_variable_type_to_ci_pipeline_variables.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddVariableTypeToCiPipelineVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ ENV_VAR_VARIABLE_TYPE = 1
+
+ def up
+ add_column_with_default(:ci_pipeline_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE)
+ end
+
+ def down
+ remove_column(:ci_pipeline_variables, :variable_type)
+ end
+end
diff --git a/db/migrate/20190416213631_add_variable_type_to_ci_pipeline_schedule_variables.rb b/db/migrate/20190416213631_add_variable_type_to_ci_pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..3079b2afd9c
--- /dev/null
+++ b/db/migrate/20190416213631_add_variable_type_to_ci_pipeline_schedule_variables.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddVariableTypeToCiPipelineScheduleVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+ ENV_VAR_VARIABLE_TYPE = 1
+
+ def up
+ add_column_with_default(:ci_pipeline_schedule_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE)
+ end
+
+ def down
+ remove_column(:ci_pipeline_schedule_variables, :variable_type)
+ end
+end
diff --git a/db/migrate/20190418182545_create_merge_request_trains_table.rb b/db/migrate/20190418182545_create_merge_request_trains_table.rb
new file mode 100644
index 00000000000..ac927c9c6b9
--- /dev/null
+++ b/db/migrate/20190418182545_create_merge_request_trains_table.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestTrainsTable < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :merge_trains, id: :bigserial do |t|
+ t.references :merge_request, foreign_key: { on_delete: :cascade }, type: :integer, index: false, null: false
+ t.references :user, foreign_key: { on_delete: :cascade }, type: :integer, null: false
+ t.references :pipeline, foreign_key: { to_table: :ci_pipelines, on_delete: :nullify }, type: :integer
+ t.timestamps_with_timezone null: false
+
+ t.index [:merge_request_id], unique: true
+ end
+ end
+end
diff --git a/db/migrate/20190422082247_create_project_metrics_settings.rb b/db/migrate/20190422082247_create_project_metrics_settings.rb
new file mode 100644
index 00000000000..3e21dd0a934
--- /dev/null
+++ b/db/migrate/20190422082247_create_project_metrics_settings.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateProjectMetricsSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_metrics_settings, id: :int, primary_key: :project_id, default: nil do |t|
+ t.string :external_dashboard_url, null: false
+ t.foreign_key :projects, column: :project_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20190426180107_add_deployment_events_to_services.rb b/db/migrate/20190426180107_add_deployment_events_to_services.rb
new file mode 100644
index 00000000000..1fb137fb5f9
--- /dev/null
+++ b/db/migrate/20190426180107_add_deployment_events_to_services.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddDeploymentEventsToServices < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:services, :deployment_events, :boolean, default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:services, :deployment_events)
+ end
+end
diff --git a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..af811e83518
--- /dev/null
+++ b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :pages_domain_acme_orders do |t|
+ t.references :pages_domain, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
+
+ t.datetime_with_timezone :expires_at, null: false
+ t.timestamps_with_timezone null: false
+
+ t.string :url, null: false
+
+ t.string :challenge_token, null: false, index: true
+ t.text :challenge_file_content, null: false
+
+ t.text :encrypted_private_key, null: false
+ t.text :encrypted_private_key_iv, null: false
+ end
+ end
+end
diff --git a/db/migrate/20190506135337_add_temporary_indexes_to_state_id.rb b/db/migrate/20190506135337_add_temporary_indexes_to_state_id.rb
new file mode 100644
index 00000000000..8e9838e1afb
--- /dev/null
+++ b/db/migrate/20190506135337_add_temporary_indexes_to_state_id.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# This migration adds temporary indexes to state_id column of issues
+# and merge_requests tables. It will be used only to peform the scheduling
+# for populating state_id in a post migrate and will be removed after it.
+# Check: ScheduleSyncIssuablesStateIdWhereNil.
+
+class AddTemporaryIndexesToStateId < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ %w(issues merge_requests).each do |table|
+ add_concurrent_index(
+ table,
+ 'id',
+ name: index_name_for(table),
+ where: "state_id IS NULL"
+ )
+ end
+ end
+
+ def down
+ remove_concurrent_index_by_name(:issues, index_name_for("issues"))
+ remove_concurrent_index_by_name(:merge_requests, index_name_for("merge_requests"))
+ end
+
+ def index_name_for(table)
+ "idx_on_#{table}_where_state_id_is_null"
+ end
+end
diff --git a/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb b/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb
new file mode 100644
index 00000000000..9cebc0f8db4
--- /dev/null
+++ b/db/migrate/20190515125613_add_application_settings_elasticsearch_shards.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddApplicationSettingsElasticsearchShards < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :elasticsearch_shards, :integer, null: false, default: 5
+ add_column :application_settings, :elasticsearch_replicas, :integer, null: false, default: 1
+ end
+end
diff --git a/db/migrate/20190516011213_add_build_queued_at_index.rb b/db/migrate/20190516011213_add_build_queued_at_index.rb
new file mode 100644
index 00000000000..77ffa7cd4e9
--- /dev/null
+++ b/db/migrate/20190516011213_add_build_queued_at_index.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# This migration make queued_at field indexed to speed up builds filtering by job_age
+
+class AddBuildQueuedAtIndex < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_builds, :queued_at
+ end
+
+ def down
+ remove_concurrent_index :ci_builds, :queued_at
+ end
+end
diff --git a/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
new file mode 100644
index 00000000000..e1d3cca48d6
--- /dev/null
+++ b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsEncryptPrivateKeyToApplicationSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :encrypted_lets_encrypt_private_key, :text
+ add_column :application_settings, :encrypted_lets_encrypt_private_key_iv, :text
+ end
+end
diff --git a/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
new file mode 100644
index 00000000000..eba154df496
--- /dev/null
+++ b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ChangePackagesSizeDefaultsInProjectStatistics < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ change_column_default :project_statistics, :packages_size, 0
+
+ update_column_in_batches(:project_statistics, :packages_size, 0) do |table, query|
+ query.where(table[:packages_size].eq(nil))
+ end
+
+ change_column_null :project_statistics, :packages_size, false
+ end
+
+ def down
+ change_column_null :project_statistics, :packages_size, true
+ change_column_default :project_statistics, :packages_size, nil
+ end
+end
diff --git a/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
new file mode 100644
index 00000000000..86fe09d7573
--- /dev/null
+++ b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class LimitMilestoneDateYearsTo4Digits < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index", "remove_concurrent_index" or
+ # "add_column_with_default" you must disable the use of transactions
+ # as these methods can not run in an existing transaction.
+ # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+ # that either of them is the _only_ method called in the migration,
+ # any other changes should go in a separate migration.
+ # This ensures that upon failure _only_ the index creation or removing fails
+ # and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ Milestone.where("start_date > '9999-12-31'").update_all(
+ "start_date = '9999-12-31'"
+ )
+ Milestone.where("due_date > '9999-12-31'").update_all(
+ "due_date = '9999-12-31'"
+ )
+ end
+end
diff --git a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
new file mode 100644
index 00000000000..ae93a76575a
--- /dev/null
+++ b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class GenerateLetsEncryptPrivateKey < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # we now generate this key on the fly, but since this migration was merged to master, we don't remove it
+ def up
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
new file mode 100644
index 00000000000..18544dcb6d3
--- /dev/null
+++ b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddSslValidPeriodToPagesDomain < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :pages_domains, :certificate_valid_not_before, :datetime_with_timezone
+ add_column :pages_domains, :certificate_valid_not_after, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20190527194830_add_wiki_size_to_statistics.rb b/db/migrate/20190527194830_add_wiki_size_to_statistics.rb
new file mode 100644
index 00000000000..d4f16cdec18
--- /dev/null
+++ b/db/migrate/20190527194830_add_wiki_size_to_statistics.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddWikiSizeToStatistics < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :project_statistics, :wiki_size, :bigint
+ end
+end
diff --git a/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb b/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..8835dc8b7ba
--- /dev/null
+++ b/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddDnsRebindingProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:application_settings, :dns_rebinding_protection_enabled,
+ :boolean,
+ default: true,
+ allow_null: false)
+ end
+
+ def down
+ remove_column(:application_settings, :dns_rebinding_protection_enabled)
+ end
+end
diff --git a/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..8abea05def4
--- /dev/null
+++ b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddDefaultGitDepthToCiCdSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :default_git_depth, :integer
+ end
+end
diff --git a/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb b/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb
new file mode 100644
index 00000000000..e669f81ca35
--- /dev/null
+++ b/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexToMergeRequestsStateAndMergeStatus < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, [:state, :merge_status],
+ where: "state = 'opened' AND merge_status = 'can_be_merged'"
+ end
+
+ def down
+ remove_concurrent_index :merge_requests, [:state, :merge_status]
+ end
+end
diff --git a/db/post_migrate/20190214112022_schedule_sync_issuables_state_id.rb b/db/post_migrate/20190214112022_schedule_sync_issuables_state_id.rb
new file mode 100644
index 00000000000..6edb0bf2d5e
--- /dev/null
+++ b/db/post_migrate/20190214112022_schedule_sync_issuables_state_id.rb
@@ -0,0 +1,56 @@
+class ScheduleSyncIssuablesStateId < ActiveRecord::Migration[5.0]
+ # This migration schedules the sync of state_id for issues and merge requests
+ # which are converting the state column from string to integer.
+ # For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/51789
+
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # 2019-02-12 gitlab.com issuable numbers
+ # issues count: 13587305
+ # merge requests count: 18925274
+ #
+ # Using 5000 as batch size and 115 seconds interval will give:
+ # 2718 jobs for issues - taking ~86 hours
+ # 3786 jobs for merge requests - taking ~120 hours
+ #
+ BATCH_SIZE = 5000
+ DELAY_INTERVAL = 120.seconds.to_i
+ ISSUES_MIGRATION = 'SyncIssuesStateId'.freeze
+ MERGE_REQUESTS_MIGRATION = 'SyncMergeRequestsStateId'.freeze
+
+ disable_ddl_transaction!
+
+ class Issue < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'issues'
+ end
+
+ class MergeRequest < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'merge_requests'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ Issue.all,
+ ISSUES_MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ MergeRequest.all,
+ MERGE_REQUESTS_MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ # No op
+ end
+end
diff --git a/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb b/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb
new file mode 100644
index 00000000000..46108d142b5
--- /dev/null
+++ b/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class MigrateProjectMigrateSidekiqQueue < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ sidekiq_queue_migrate 'project_migrate_hashed_storage', to: 'hashed_storage:hashed_storage_project_migrate'
+ end
+
+ def down
+ sidekiq_queue_migrate 'hashed_storage:hashed_storage_project_migrate', to: 'project_migrate_hashed_storage'
+ end
+end
diff --git a/db/post_migrate/20190313092516_clean_up_noteable_id_for_notes_on_commits.rb b/db/post_migrate/20190313092516_clean_up_noteable_id_for_notes_on_commits.rb
new file mode 100644
index 00000000000..fcd63f42b0e
--- /dev/null
+++ b/db/post_migrate/20190313092516_clean_up_noteable_id_for_notes_on_commits.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CleanUpNoteableIdForNotesOnCommits < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ TEMP_INDEX_NAME = 'index_notes_on_commit_with_null_noteable_id'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:notes, TEMP_INDEX_NAME)
+
+ add_concurrent_index(:notes, :id, where: "noteable_type = 'Commit' AND noteable_id IS NOT NULL", name: TEMP_INDEX_NAME)
+
+ # rubocop:disable Migration/UpdateLargeTable
+ update_column_in_batches(:notes, :noteable_id, nil, batch_size: 300) do |table, query|
+ query.where(
+ table[:noteable_type].eq('Commit').and(table[:noteable_id].not_eq(nil))
+ )
+ end
+
+ remove_concurrent_index_by_name(:notes, TEMP_INDEX_NAME)
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..1ecb38e1a86
--- /dev/null
+++ b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class SchedulePopulateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'PopulateMergeRequestAssigneesTable'
+ DELAY_INTERVAL = 8.minutes.to_i
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Scheduling `PopulateMergeRequestAssigneesTable` jobs'
+ # We currently have ~4_500_000 merge request records on GitLab.com.
+ # This means it'll schedule ~450 jobs (10k MRs each) with a 8 minutes gap,
+ # so this should take ~60 hours for all background migrations to complete.
+ queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb b/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb
new file mode 100644
index 00000000000..8571bb82fa0
--- /dev/null
+++ b/db/post_migrate/20190325111602_rename_v2_root_namespaces.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameV2RootNamespaces < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::Database::RenameReservedPathsMigration::V1
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ # We're taking over the /v2 namespace as it necessary for Docker client to
+ # work with GitLab as Dependency proxy for containers.
+ def up
+ disable_statement_timeout do
+ rename_root_paths 'v2'
+ end
+ end
+
+ def down
+ disable_statement_timeout do
+ revert_renames
+ end
+ end
+end
diff --git a/db/post_migrate/20190424134256_drop_projects_ci_id.rb b/db/post_migrate/20190424134256_drop_projects_ci_id.rb
new file mode 100644
index 00000000000..44e8f316393
--- /dev/null
+++ b/db/post_migrate/20190424134256_drop_projects_ci_id.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class DropProjectsCiId < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if index_exists?(:projects, :ci_id)
+ remove_concurrent_index :projects, :ci_id
+ end
+
+ if column_exists?(:projects, :ci_id)
+ remove_column :projects, :ci_id
+ end
+ end
+
+ def down
+ unless column_exists?(:projects, :ci_id)
+ add_column :projects, :ci_id, :integer
+ end
+
+ unless index_exists?(:projects, :ci_id)
+ add_concurrent_index :projects, :ci_id
+ end
+ end
+end
diff --git a/db/post_migrate/20190506135400_schedule_sync_issuables_state_id_where_nil.rb b/db/post_migrate/20190506135400_schedule_sync_issuables_state_id_where_nil.rb
new file mode 100644
index 00000000000..4c31b5968ff
--- /dev/null
+++ b/db/post_migrate/20190506135400_schedule_sync_issuables_state_id_where_nil.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+class ScheduleSyncIssuablesStateIdWhereNil < ActiveRecord::Migration[5.1]
+ # Issues and MergeRequests imported by GitHub are being created with
+ # state_id = null, this fixes them.
+ #
+ # Part of a bigger plan: https://gitlab.com/gitlab-org/gitlab-ce/issues/51789
+
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # 2019-05-02 gitlab.com issuable numbers
+ # issues with state_id nil: ~40000
+ # merge requests with state_id nil: ~200000
+ #
+ # Using 5000 as batch size and 120 seconds interval will create:
+ # ~8 jobs for issues - taking ~16 minutes
+ # ~40 jobs for merge requests - taking ~1.34 hours
+ #
+ BATCH_SIZE = 5000
+ DELAY_INTERVAL = 120.seconds.to_i
+ ISSUES_MIGRATION = 'SyncIssuesStateId'.freeze
+ MERGE_REQUESTS_MIGRATION = 'SyncMergeRequestsStateId'.freeze
+
+ disable_ddl_transaction!
+
+ class Issue < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'issues'
+ end
+
+ class MergeRequest < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'merge_requests'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ Issue.where(state_id: nil),
+ ISSUES_MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ MergeRequest.where(state_id: nil),
+ MERGE_REQUESTS_MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+
+ # Remove temporary indexes added on "AddTemporaryIndexesToStateId"
+ remove_concurrent_index_by_name(:issues, "idx_on_issues_where_state_id_is_null")
+ remove_concurrent_index_by_name(:merge_requests, "idx_on_merge_requests_where_state_id_is_null")
+ end
+
+ def down
+ # No op
+ end
+end
diff --git a/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb b/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb
new file mode 100644
index 00000000000..36278d83927
--- /dev/null
+++ b/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DropProjectAutoDevopsDomain < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_column :project_auto_devops, :domain, :string
+ end
+end
diff --git a/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
new file mode 100644
index 00000000000..1d8510e4514
--- /dev/null
+++ b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ScheduleFillValidTimeForPagesDomainCertificates < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ MIGRATION = 'FillValidTimeForPagesDomainCertificate'
+ BATCH_SIZE = 500
+ BATCH_TIME = 5.minutes
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class PagesDomain < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'pages_domains'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ PagesDomain.where.not(certificate: [nil, '']),
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb b/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
new file mode 100644
index 00000000000..04cf5906b61
--- /dev/null
+++ b/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class ScheduleCalculateWikiSizes < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'CalculateWikiSizes'
+ BATCH_SIZE = 100000
+ BATCH_TIME = 5.minutes
+
+ class ProjectStatistics < ActiveRecord::Base
+ self.table_name = 'project_statistics'
+
+ scope :without_wiki_size, -> { where(wiki_size: nil) }
+
+ include ::EachBatch
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ ::ScheduleCalculateWikiSizes::ProjectStatistics.without_wiki_size,
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb b/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb
new file mode 100644
index 00000000000..1b668d85bac
--- /dev/null
+++ b/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class EnqueueResetMergeStatus < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'ResetMergeStatus'
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Scheduling `ResetMergeStatus` jobs'
+
+ # We currently have around 135_000 opened, mergeable MRs in GitLab.com. This iteration
+ # will schedule around 13 batches of 10_000 MRs, which should take around 1 hour to
+ # complete.
+ relation = MergeRequest.where(state: 'opened', merge_status: 'can_be_merged')
+
+ relation.each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, range)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1bdc9682cc9..22d0cddb7a0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,13 +10,13 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190204115450) do
+ActiveRecord::Schema.define(version: 20190530154715) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "pg_trgm"
- create_table "abuse_reports", force: :cascade do |t|
+ create_table "abuse_reports", id: :serial, force: :cascade do |t|
t.integer "reporter_id"
t.integer "user_id"
t.text "message"
@@ -26,7 +26,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "cached_markdown_version"
end
- create_table "appearances", force: :cascade do |t|
+ create_table "appearances", id: :serial, force: :cascade do |t|
t.string "title", null: false
t.text "description", null: false
t.string "header_logo"
@@ -44,15 +44,16 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.text "message_background_color"
t.text "message_font_color"
t.string "favicon"
+ t.boolean "email_header_and_footer_enabled", default: false, null: false
end
- create_table "application_setting_terms", force: :cascade do |t|
+ create_table "application_setting_terms", id: :serial, force: :cascade do |t|
t.integer "cached_markdown_version"
t.text "terms", null: false
t.text "terms_html"
end
- create_table "application_settings", force: :cascade do |t|
+ create_table "application_settings", id: :serial, force: :cascade do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
t.boolean "gravatar_enabled"
@@ -176,10 +177,27 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "runners_registration_token_encrypted"
t.integer "local_markdown_version", default: 0, null: false
t.integer "first_day_of_week", default: 0, null: false
+ t.integer "default_project_creation", default: 2, null: false
+ t.boolean "external_authorization_service_enabled", default: false, null: false
+ t.string "external_authorization_service_url"
+ t.string "external_authorization_service_default_label"
+ t.float "external_authorization_service_timeout", default: 0.5
+ t.text "external_auth_client_cert"
+ t.text "encrypted_external_auth_client_key"
+ t.string "encrypted_external_auth_client_key_iv"
+ t.string "encrypted_external_auth_client_key_pass"
+ t.string "encrypted_external_auth_client_key_pass_iv"
+ t.string "lets_encrypt_notification_email"
+ t.boolean "lets_encrypt_terms_of_service_accepted", default: false, null: false
+ t.integer "elasticsearch_shards", default: 5, null: false
+ t.integer "elasticsearch_replicas", default: 1, null: false
+ t.text "encrypted_lets_encrypt_private_key"
+ t.text "encrypted_lets_encrypt_private_key_iv"
+ t.boolean "dns_rebinding_protection_enabled", default: true, null: false
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
- create_table "audit_events", force: :cascade do |t|
+ create_table "audit_events", id: :serial, force: :cascade do |t|
t.integer "author_id", null: false
t.string "type", null: false
t.integer "entity_id", null: false
@@ -190,7 +208,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
end
- create_table "award_emoji", force: :cascade do |t|
+ create_table "award_emoji", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.integer "awardable_id"
@@ -201,7 +219,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
end
- create_table "badges", force: :cascade do |t|
+ create_table "badges", id: :serial, force: :cascade do |t|
t.string "link_url", null: false
t.string "image_url", null: false
t.integer "project_id"
@@ -213,7 +231,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_badges_on_project_id", using: :btree
end
- create_table "board_group_recent_visits", id: :bigserial, force: :cascade do |t|
+ create_table "board_group_recent_visits", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "user_id"
@@ -225,7 +243,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_board_group_recent_visits_on_user_id", using: :btree
end
- create_table "board_project_recent_visits", id: :bigserial, force: :cascade do |t|
+ create_table "board_project_recent_visits", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "user_id"
@@ -237,7 +255,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_board_project_recent_visits_on_user_id", using: :btree
end
- create_table "boards", force: :cascade do |t|
+ create_table "boards", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -246,7 +264,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_boards_on_project_id", using: :btree
end
- create_table "broadcast_messages", force: :cascade do |t|
+ create_table "broadcast_messages", id: :serial, force: :cascade do |t|
t.text "message", null: false
t.datetime "starts_at", null: false
t.datetime "ends_at", null: false
@@ -259,7 +277,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["starts_at", "ends_at", "id"], name: "index_broadcast_messages_on_starts_at_and_ends_at_and_id", using: :btree
end
- create_table "chat_names", force: :cascade do |t|
+ create_table "chat_names", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "service_id", null: false
t.string "team_id", null: false
@@ -273,7 +291,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id", "service_id"], name: "index_chat_names_on_user_id_and_service_id", unique: true, using: :btree
end
- create_table "chat_teams", force: :cascade do |t|
+ create_table "chat_teams", id: :serial, force: :cascade do |t|
t.integer "namespace_id", null: false
t.string "team_id"
t.string "name"
@@ -282,7 +300,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["namespace_id"], name: "index_chat_teams_on_namespace_id", unique: true, using: :btree
end
- create_table "ci_build_trace_chunks", id: :bigserial, force: :cascade do |t|
+ create_table "ci_build_trace_chunks", force: :cascade do |t|
t.integer "build_id", null: false
t.integer "chunk_index", null: false
t.integer "data_store", null: false
@@ -290,13 +308,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["build_id", "chunk_index"], name: "index_ci_build_trace_chunks_on_build_id_and_chunk_index", unique: true, using: :btree
end
- create_table "ci_build_trace_section_names", force: :cascade do |t|
+ create_table "ci_build_trace_section_names", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.index ["project_id", "name"], name: "index_ci_build_trace_section_names_on_project_id_and_name", unique: true, using: :btree
end
- create_table "ci_build_trace_sections", force: :cascade do |t|
+ create_table "ci_build_trace_sections", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.datetime_with_timezone "date_start", null: false
t.datetime_with_timezone "date_end", null: false
@@ -309,7 +327,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["section_name_id"], name: "index_ci_build_trace_sections_on_section_name_id", using: :btree
end
- create_table "ci_builds", force: :cascade do |t|
+ create_table "ci_builds", id: :serial, force: :cascade do |t|
t.string "status"
t.datetime "finished_at"
t.text "trace"
@@ -365,6 +383,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))", using: :btree
t.index ["protected"], name: "index_ci_builds_on_protected", using: :btree
+ t.index ["queued_at"], name: "index_ci_builds_on_queued_at", using: :btree
t.index ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
t.index ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))", using: :btree
t.index ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree
@@ -376,7 +395,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
end
- create_table "ci_builds_metadata", force: :cascade do |t|
+ create_table "ci_builds_metadata", id: :serial, force: :cascade do |t|
t.integer "build_id", null: false
t.integer "project_id", null: false
t.integer "timeout"
@@ -387,7 +406,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
end
- create_table "ci_builds_runner_session", id: :bigserial, force: :cascade do |t|
+ create_table "ci_builds_runner_session", force: :cascade do |t|
t.integer "build_id", null: false
t.string "url", null: false
t.string "certificate"
@@ -395,7 +414,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["build_id"], name: "index_ci_builds_runner_session_on_build_id", unique: true, using: :btree
end
- create_table "ci_group_variables", force: :cascade do |t|
+ create_table "ci_group_variables", id: :serial, force: :cascade do |t|
t.string "key", null: false
t.text "value"
t.text "encrypted_value"
@@ -405,10 +424,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "protected", default: false, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
+ t.boolean "masked", default: false, null: false
+ t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
end
- create_table "ci_job_artifacts", force: :cascade do |t|
+ create_table "ci_job_artifacts", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "job_id", null: false
t.integer "file_type", null: false
@@ -427,7 +448,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree
end
- create_table "ci_pipeline_chat_data", id: :bigserial, force: :cascade do |t|
+ create_table "ci_pipeline_chat_data", force: :cascade do |t|
t.integer "pipeline_id", null: false
t.integer "chat_name_id", null: false
t.text "response_url", null: false
@@ -444,10 +465,11 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "pipeline_schedule_id", null: false
t.datetime_with_timezone "created_at"
t.datetime_with_timezone "updated_at"
+ t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
end
- create_table "ci_pipeline_schedules", force: :cascade do |t|
+ create_table "ci_pipeline_schedules", id: :serial, force: :cascade do |t|
t.string "description"
t.string "ref"
t.string "cron"
@@ -463,17 +485,18 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_pipeline_schedules_on_project_id", using: :btree
end
- create_table "ci_pipeline_variables", force: :cascade do |t|
+ create_table "ci_pipeline_variables", id: :serial, force: :cascade do |t|
t.string "key", null: false
t.text "value"
t.text "encrypted_value"
t.string "encrypted_value_salt"
t.string "encrypted_value_iv"
t.integer "pipeline_id", null: false
+ t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["pipeline_id", "key"], name: "index_ci_pipeline_variables_on_pipeline_id_and_key", unique: true, using: :btree
end
- create_table "ci_pipelines", force: :cascade do |t|
+ create_table "ci_pipelines", id: :serial, force: :cascade do |t|
t.string "ref"
t.string "sha"
t.string "before_sha"
@@ -497,6 +520,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "failure_reason"
t.integer "iid"
t.integer "merge_request_id"
+ t.binary "source_sha"
+ t.binary "target_sha"
t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)", using: :btree
t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
@@ -511,14 +536,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_ci_pipelines_on_user_id", using: :btree
end
- create_table "ci_runner_namespaces", force: :cascade do |t|
+ create_table "ci_runner_namespaces", id: :serial, force: :cascade do |t|
t.integer "runner_id"
t.integer "namespace_id"
t.index ["namespace_id"], name: "index_ci_runner_namespaces_on_namespace_id", using: :btree
t.index ["runner_id", "namespace_id"], name: "index_ci_runner_namespaces_on_runner_id_and_namespace_id", unique: true, using: :btree
end
- create_table "ci_runner_projects", force: :cascade do |t|
+ create_table "ci_runner_projects", id: :serial, force: :cascade do |t|
t.integer "runner_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
@@ -527,7 +552,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
end
- create_table "ci_runners", force: :cascade do |t|
+ create_table "ci_runners", id: :serial, force: :cascade do |t|
t.string "token"
t.datetime "created_at"
t.datetime "updated_at"
@@ -552,9 +577,10 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["locked"], name: "index_ci_runners_on_locked", using: :btree
t.index ["runner_type"], name: "index_ci_runners_on_runner_type", using: :btree
t.index ["token"], name: "index_ci_runners_on_token", using: :btree
+ t.index ["token_encrypted"], name: "index_ci_runners_on_token_encrypted", using: :btree
end
- create_table "ci_stages", force: :cascade do |t|
+ create_table "ci_stages", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.integer "pipeline_id"
t.datetime "created_at"
@@ -569,7 +595,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_stages_on_project_id", using: :btree
end
- create_table "ci_trigger_requests", force: :cascade do |t|
+ create_table "ci_trigger_requests", id: :serial, force: :cascade do |t|
t.integer "trigger_id", null: false
t.text "variables"
t.datetime "created_at"
@@ -579,7 +605,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["trigger_id"], name: "index_ci_trigger_requests_on_trigger_id", using: :btree
end
- create_table "ci_triggers", force: :cascade do |t|
+ create_table "ci_triggers", id: :serial, force: :cascade do |t|
t.string "token"
t.datetime "created_at"
t.datetime "updated_at"
@@ -591,7 +617,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_triggers_on_project_id", using: :btree
end
- create_table "ci_variables", force: :cascade do |t|
+ create_table "ci_variables", id: :serial, force: :cascade do |t|
t.string "key", null: false
t.text "value"
t.text "encrypted_value"
@@ -600,17 +626,19 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "project_id", null: false
t.boolean "protected", default: false, null: false
t.string "environment_scope", default: "*", null: false
+ t.boolean "masked", default: false, null: false
+ t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
end
- create_table "cluster_groups", force: :cascade do |t|
+ create_table "cluster_groups", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "group_id", null: false
t.index ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
t.index ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
end
- create_table "cluster_platforms_kubernetes", force: :cascade do |t|
+ create_table "cluster_platforms_kubernetes", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -626,7 +654,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["cluster_id"], name: "index_cluster_platforms_kubernetes_on_cluster_id", unique: true, using: :btree
end
- create_table "cluster_projects", force: :cascade do |t|
+ create_table "cluster_projects", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
@@ -635,7 +663,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_cluster_projects_on_project_id", using: :btree
end
- create_table "cluster_providers_gcp", force: :cascade do |t|
+ create_table "cluster_providers_gcp", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "status"
t.integer "num_nodes", null: false
@@ -653,7 +681,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters", force: :cascade do |t|
+ create_table "clusters", id: :serial, force: :cascade do |t|
t.integer "user_id"
t.integer "provider_type"
t.integer "platform_type"
@@ -664,11 +692,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "environment_scope", default: "*", null: false
t.integer "cluster_type", limit: 2, default: 3, null: false
t.string "domain"
+ t.boolean "managed", default: true, null: false
t.index ["enabled"], name: "index_clusters_on_enabled", using: :btree
t.index ["user_id"], name: "index_clusters_on_user_id", using: :btree
end
- create_table "clusters_applications_cert_managers", force: :cascade do |t|
+ create_table "clusters_applications_cert_managers", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "status", null: false
t.string "version", null: false
@@ -679,7 +708,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters_applications_helm", force: :cascade do |t|
+ create_table "clusters_applications_helm", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -692,7 +721,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["cluster_id"], name: "index_clusters_applications_helm_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters_applications_ingress", force: :cascade do |t|
+ create_table "clusters_applications_ingress", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -702,10 +731,11 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "cluster_ip"
t.text "status_reason"
t.string "external_ip"
+ t.string "external_hostname"
t.index ["cluster_id"], name: "index_clusters_applications_ingress_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters_applications_jupyter", force: :cascade do |t|
+ create_table "clusters_applications_jupyter", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "oauth_application_id"
t.integer "status", null: false
@@ -718,7 +748,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["oauth_application_id"], name: "index_clusters_applications_jupyter_on_oauth_application_id", using: :btree
end
- create_table "clusters_applications_knative", force: :cascade do |t|
+ create_table "clusters_applications_knative", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -727,10 +757,11 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "hostname"
t.text "status_reason"
t.string "external_ip"
+ t.string "external_hostname"
t.index ["cluster_id"], name: "index_clusters_applications_knative_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters_applications_prometheus", force: :cascade do |t|
+ create_table "clusters_applications_prometheus", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "status", null: false
t.string "version", null: false
@@ -740,7 +771,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["cluster_id"], name: "index_clusters_applications_prometheus_on_cluster_id", unique: true, using: :btree
end
- create_table "clusters_applications_runners", force: :cascade do |t|
+ create_table "clusters_applications_runners", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "runner_id"
t.integer "status", null: false
@@ -753,7 +784,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
end
- create_table "clusters_kubernetes_namespaces", id: :bigserial, force: :cascade do |t|
+ create_table "clusters_kubernetes_namespaces", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "project_id"
t.integer "cluster_project_id"
@@ -769,7 +800,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree
end
- create_table "container_repositories", force: :cascade do |t|
+ create_table "container_repositories", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.datetime "created_at", null: false
@@ -778,7 +809,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_container_repositories_on_project_id", using: :btree
end
- create_table "conversational_development_index_metrics", force: :cascade do |t|
+ create_table "conversational_development_index_metrics", id: :serial, force: :cascade do |t|
t.float "leader_issues", null: false
t.float "instance_issues", null: false
t.float "leader_notes", null: false
@@ -813,7 +844,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.float "percentage_service_desk_issues", default: 0.0, null: false
end
- create_table "deploy_keys_projects", force: :cascade do |t|
+ create_table "deploy_keys_projects", id: :serial, force: :cascade do |t|
t.integer "deploy_key_id", null: false
t.integer "project_id", null: false
t.datetime "created_at"
@@ -822,7 +853,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
end
- create_table "deploy_tokens", force: :cascade do |t|
+ create_table "deploy_tokens", id: :serial, force: :cascade do |t|
t.boolean "revoked", default: false
t.boolean "read_repository", default: false, null: false
t.boolean "read_registry", default: false, null: false
@@ -834,7 +865,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
end
- create_table "deployments", force: :cascade do |t|
+ create_table "deployments", id: :serial, force: :cascade do |t|
t.integer "iid", null: false
t.integer "project_id", null: false
t.integer "environment_id", null: false
@@ -860,7 +891,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status", using: :btree
end
- create_table "emails", force: :cascade do |t|
+ create_table "emails", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.string "email", null: false
t.datetime "created_at"
@@ -873,7 +904,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_emails_on_user_id", using: :btree
end
- create_table "environments", force: :cascade do |t|
+ create_table "environments", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.datetime "created_at"
@@ -886,7 +917,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true, using: :btree
end
- create_table "events", force: :cascade do |t|
+ create_table "events", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.integer "author_id", null: false
t.integer "target_id"
@@ -901,7 +932,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree
end
- create_table "feature_gates", force: :cascade do |t|
+ create_table "feature_gates", id: :serial, force: :cascade do |t|
t.string "feature_key", null: false
t.string "key", null: false
t.string "value"
@@ -910,14 +941,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["feature_key", "key", "value"], name: "index_feature_gates_on_feature_key_and_key_and_value", unique: true, using: :btree
end
- create_table "features", force: :cascade do |t|
+ create_table "features", id: :serial, force: :cascade do |t|
t.string "key", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["key"], name: "index_features_on_key", unique: true, using: :btree
end
- create_table "fork_network_members", force: :cascade do |t|
+ create_table "fork_network_members", id: :serial, force: :cascade do |t|
t.integer "fork_network_id", null: false
t.integer "project_id", null: false
t.integer "forked_from_project_id"
@@ -926,13 +957,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_fork_network_members_on_project_id", unique: true, using: :btree
end
- create_table "fork_networks", force: :cascade do |t|
+ create_table "fork_networks", id: :serial, force: :cascade do |t|
t.integer "root_project_id"
t.string "deleted_root_project_name"
t.index ["root_project_id"], name: "index_fork_networks_on_root_project_id", unique: true, using: :btree
end
- create_table "forked_project_links", force: :cascade do |t|
+ create_table "forked_project_links", id: :serial, force: :cascade do |t|
t.integer "forked_to_project_id", null: false
t.integer "forked_from_project_id", null: false
t.datetime "created_at"
@@ -940,7 +971,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
end
- create_table "gpg_key_subkeys", force: :cascade do |t|
+ create_table "gpg_key_subkeys", id: :serial, force: :cascade do |t|
t.integer "gpg_key_id", null: false
t.binary "keyid"
t.binary "fingerprint"
@@ -949,7 +980,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["keyid"], name: "index_gpg_key_subkeys_on_keyid", unique: true, using: :btree
end
- create_table "gpg_keys", force: :cascade do |t|
+ create_table "gpg_keys", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "user_id"
@@ -961,7 +992,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_gpg_keys_on_user_id", using: :btree
end
- create_table "gpg_signatures", force: :cascade do |t|
+ create_table "gpg_signatures", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id"
@@ -979,7 +1010,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
end
- create_table "group_custom_attributes", force: :cascade do |t|
+ create_table "group_custom_attributes", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "group_id", null: false
@@ -989,7 +1020,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value", using: :btree
end
- create_table "identities", force: :cascade do |t|
+ create_table "identities", id: :serial, force: :cascade do |t|
t.string "extern_uid"
t.string "provider"
t.integer "user_id"
@@ -998,7 +1029,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_identities_on_user_id", using: :btree
end
- create_table "import_export_uploads", force: :cascade do |t|
+ create_table "import_export_uploads", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id"
t.text "import_file"
@@ -1007,7 +1038,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["updated_at"], name: "index_import_export_uploads_on_updated_at", using: :btree
end
- create_table "internal_ids", id: :bigserial, force: :cascade do |t|
+ create_table "internal_ids", force: :cascade do |t|
t.integer "project_id"
t.integer "usage", null: false
t.integer "last_value", null: false
@@ -1025,7 +1056,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree
end
- create_table "issue_metrics", force: :cascade do |t|
+ create_table "issue_metrics", id: :serial, force: :cascade do |t|
t.integer "issue_id", null: false
t.datetime "first_mentioned_in_commit_at"
t.datetime "first_associated_with_milestone_at"
@@ -1035,7 +1066,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["issue_id"], name: "index_issue_metrics", using: :btree
end
- create_table "issues", force: :cascade do |t|
+ create_table "issues", id: :serial, force: :cascade do |t|
t.string "title"
t.integer "author_id"
t.integer "project_id"
@@ -1060,6 +1091,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "discussion_locked"
t.datetime_with_timezone "closed_at"
t.integer "closed_by_id"
+ t.integer "state_id", limit: 2
t.index ["author_id"], name: "index_issues_on_author_id", using: :btree
t.index ["closed_by_id"], name: "index_issues_on_closed_by_id", using: :btree
t.index ["confidential"], name: "index_issues_on_confidential", using: :btree
@@ -1077,7 +1109,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
end
- create_table "keys", force: :cascade do |t|
+ create_table "keys", id: :serial, force: :cascade do |t|
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -1091,7 +1123,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_keys_on_user_id", using: :btree
end
- create_table "label_links", force: :cascade do |t|
+ create_table "label_links", id: :serial, force: :cascade do |t|
t.integer "label_id"
t.integer "target_id"
t.string "target_type"
@@ -1101,7 +1133,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
end
- create_table "label_priorities", force: :cascade do |t|
+ create_table "label_priorities", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "label_id", null: false
t.integer "priority", null: false
@@ -1112,7 +1144,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "label_id"], name: "index_label_priorities_on_project_id_and_label_id", unique: true, using: :btree
end
- create_table "labels", force: :cascade do |t|
+ create_table "labels", id: :serial, force: :cascade do |t|
t.string "title"
t.string "color"
t.integer "project_id"
@@ -1131,7 +1163,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
end
- create_table "lfs_file_locks", force: :cascade do |t|
+ create_table "lfs_file_locks", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
t.datetime "created_at", null: false
@@ -1140,7 +1172,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_lfs_file_locks_on_user_id", using: :btree
end
- create_table "lfs_objects", force: :cascade do |t|
+ create_table "lfs_objects", id: :serial, force: :cascade do |t|
t.string "oid", null: false
t.bigint "size", null: false
t.datetime "created_at"
@@ -1151,7 +1183,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
end
- create_table "lfs_objects_projects", force: :cascade do |t|
+ create_table "lfs_objects_projects", id: :serial, force: :cascade do |t|
t.integer "lfs_object_id", null: false
t.integer "project_id", null: false
t.datetime "created_at"
@@ -1159,7 +1191,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
end
- create_table "lists", force: :cascade do |t|
+ create_table "lists", id: :serial, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "label_id"
t.integer "list_type", default: 1, null: false
@@ -1171,7 +1203,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["list_type"], name: "index_lists_on_list_type", using: :btree
end
- create_table "members", force: :cascade do |t|
+ create_table "members", id: :serial, force: :cascade do |t|
t.integer "access_level", null: false
t.integer "source_id", null: false
t.string "source_type", null: false
@@ -1193,6 +1225,23 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_members_on_user_id", using: :btree
end
+ create_table "merge_request_assignees", force: :cascade do |t|
+ t.integer "user_id", null: false
+ t.integer "merge_request_id", null: false
+ t.index ["merge_request_id", "user_id"], name: "index_merge_request_assignees_on_merge_request_id_and_user_id", unique: true, using: :btree
+ t.index ["merge_request_id"], name: "index_merge_request_assignees_on_merge_request_id", using: :btree
+ t.index ["user_id"], name: "index_merge_request_assignees_on_user_id", using: :btree
+ end
+
+ create_table "merge_request_blocks", force: :cascade do |t|
+ t.integer "blocking_merge_request_id", null: false
+ t.integer "blocked_merge_request_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["blocked_merge_request_id"], name: "index_merge_request_blocks_on_blocked_merge_request_id", using: :btree
+ t.index ["blocking_merge_request_id", "blocked_merge_request_id"], name: "index_mr_blocks_on_blocking_and_blocked_mr_ids", unique: true, using: :btree
+ end
+
create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
t.datetime_with_timezone "authored_date"
t.datetime_with_timezone "committed_date"
@@ -1226,7 +1275,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true, using: :btree
end
- create_table "merge_request_diffs", force: :cascade do |t|
+ create_table "merge_request_diffs", id: :serial, force: :cascade do |t|
t.string "state"
t.integer "merge_request_id", null: false
t.datetime "created_at"
@@ -1240,9 +1289,10 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "external_diff_store"
t.boolean "stored_externally"
t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
+ t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id_partial", where: "((NOT stored_externally) OR (stored_externally IS NULL))", using: :btree
end
- create_table "merge_request_metrics", force: :cascade do |t|
+ create_table "merge_request_metrics", id: :serial, force: :cascade do |t|
t.integer "merge_request_id", null: false
t.datetime "latest_build_started_at"
t.datetime "latest_build_finished_at"
@@ -1255,13 +1305,15 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "latest_closed_by_id"
t.datetime_with_timezone "latest_closed_at"
t.index ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree
+ t.index ["latest_closed_at"], name: "index_merge_request_metrics_on_latest_closed_at", where: "(latest_closed_at IS NOT NULL)", using: :btree
t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id", using: :btree
+ t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)", using: :btree
t.index ["merge_request_id"], name: "index_merge_request_metrics", using: :btree
t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id", using: :btree
t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id", using: :btree
end
- create_table "merge_requests", force: :cascade do |t|
+ create_table "merge_requests", id: :serial, force: :cascade do |t|
t.string "target_branch", null: false
t.string "source_branch", null: false
t.integer "source_project_id"
@@ -1297,6 +1349,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "rebase_commit_sha"
t.boolean "squash", default: false, null: false
t.boolean "allow_maintainer_to_push"
+ t.integer "state_id", limit: 2
t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
t.index ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
t.index ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
@@ -1309,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)", using: :btree
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
+ t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))", using: :btree
t.index ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)", using: :btree
@@ -1318,7 +1372,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
end
- create_table "merge_requests_closing_issues", force: :cascade do |t|
+ create_table "merge_requests_closing_issues", id: :serial, force: :cascade do |t|
t.integer "merge_request_id", null: false
t.integer "issue_id", null: false
t.datetime "created_at", null: false
@@ -1327,7 +1381,18 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree
end
- create_table "milestones", force: :cascade do |t|
+ create_table "merge_trains", force: :cascade do |t|
+ t.integer "merge_request_id", null: false
+ t.integer "user_id", null: false
+ t.integer "pipeline_id"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["merge_request_id"], name: "index_merge_trains_on_merge_request_id", unique: true, using: :btree
+ t.index ["pipeline_id"], name: "index_merge_trains_on_pipeline_id", using: :btree
+ t.index ["user_id"], name: "index_merge_trains_on_user_id", using: :btree
+ end
+
+ create_table "milestones", id: :serial, force: :cascade do |t|
t.string "title", null: false
t.integer "project_id"
t.text "description"
@@ -1349,7 +1414,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
end
- create_table "namespaces", force: :cascade do |t|
+ create_table "namespaces", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.string "path", null: false
t.integer "owner_id"
@@ -1369,6 +1434,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "cached_markdown_version"
t.string "runners_token"
t.string "runners_token_encrypted"
+ t.integer "project_creation_level"
+ t.boolean "auto_devops_enabled"
t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree
t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree
t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
@@ -1378,10 +1445,11 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
t.index ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication", using: :btree
t.index ["runners_token"], name: "index_namespaces_on_runners_token", unique: true, using: :btree
+ t.index ["runners_token_encrypted"], name: "index_namespaces_on_runners_token_encrypted", unique: true, using: :btree
t.index ["type"], name: "index_namespaces_on_type", using: :btree
end
- create_table "note_diff_files", force: :cascade do |t|
+ create_table "note_diff_files", id: :serial, force: :cascade do |t|
t.integer "diff_note_id", null: false
t.text "diff", null: false
t.boolean "new_file", null: false
@@ -1394,7 +1462,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["diff_note_id"], name: "index_note_diff_files_on_diff_note_id", unique: true, using: :btree
end
- create_table "notes", force: :cascade do |t|
+ create_table "notes", id: :serial, force: :cascade do |t|
t.text "note"
t.string "noteable_type"
t.integer "author_id"
@@ -1429,7 +1497,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
end
- create_table "notification_settings", force: :cascade do |t|
+ create_table "notification_settings", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "source_id"
t.string "source_type"
@@ -1450,12 +1518,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "success_pipeline"
t.boolean "push_to_merge_request"
t.boolean "issue_due"
+ t.string "notification_email"
t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree
t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree
end
- create_table "oauth_access_grants", force: :cascade do |t|
+ create_table "oauth_access_grants", id: :serial, force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
@@ -1467,7 +1536,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
end
- create_table "oauth_access_tokens", force: :cascade do |t|
+ create_table "oauth_access_tokens", id: :serial, force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
@@ -1481,7 +1550,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
end
- create_table "oauth_applications", force: :cascade do |t|
+ create_table "oauth_applications", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -1496,13 +1565,27 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
end
- create_table "oauth_openid_requests", force: :cascade do |t|
+ create_table "oauth_openid_requests", id: :serial, force: :cascade do |t|
t.integer "access_grant_id", null: false
t.string "nonce", null: false
t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id", using: :btree
end
- create_table "pages_domains", force: :cascade do |t|
+ create_table "pages_domain_acme_orders", force: :cascade do |t|
+ t.integer "pages_domain_id", null: false
+ t.datetime_with_timezone "expires_at", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "url", null: false
+ t.string "challenge_token", null: false
+ t.text "challenge_file_content", null: false
+ t.text "encrypted_private_key", null: false
+ t.text "encrypted_private_key_iv", null: false
+ t.index ["challenge_token"], name: "index_pages_domain_acme_orders_on_challenge_token", using: :btree
+ t.index ["pages_domain_id"], name: "index_pages_domain_acme_orders_on_pages_domain_id", using: :btree
+ end
+
+ create_table "pages_domains", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
t.text "encrypted_key"
@@ -1512,14 +1595,19 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.datetime_with_timezone "verified_at"
t.string "verification_code", null: false
t.datetime_with_timezone "enabled_until"
+ t.datetime_with_timezone "remove_at"
+ t.boolean "auto_ssl_enabled", default: false, null: false
+ t.datetime_with_timezone "certificate_valid_not_before"
+ t.datetime_with_timezone "certificate_valid_not_after"
t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree
t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
+ t.index ["remove_at"], name: "index_pages_domains_on_remove_at", using: :btree
t.index ["verified_at", "enabled_until"], name: "index_pages_domains_on_verified_at_and_enabled_until", using: :btree
t.index ["verified_at"], name: "index_pages_domains_on_verified_at", using: :btree
end
- create_table "personal_access_tokens", force: :cascade do |t|
+ create_table "personal_access_tokens", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.string "name", null: false
t.boolean "revoked", default: false
@@ -1533,7 +1621,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
end
- create_table "pool_repositories", id: :bigserial, force: :cascade do |t|
+ create_table "pool_repositories", force: :cascade do |t|
t.integer "shard_id", null: false
t.string "disk_path"
t.string "state"
@@ -1543,7 +1631,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["source_project_id"], name: "index_pool_repositories_on_source_project_id", unique: true, using: :btree
end
- create_table "programming_languages", force: :cascade do |t|
+ create_table "programming_languages", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.string "color", null: false
t.datetime_with_timezone "created_at", null: false
@@ -1558,23 +1646,25 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
end
- create_table "project_auto_devops", force: :cascade do |t|
+ create_table "project_auto_devops", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.boolean "enabled"
- t.string "domain"
t.integer "deploy_strategy", default: 0, null: false
t.index ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree
end
- create_table "project_ci_cd_settings", force: :cascade do |t|
+ create_table "project_ci_cd_settings", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.boolean "group_runners_enabled", default: true, null: false
+ t.boolean "merge_pipelines_enabled"
+ t.boolean "merge_trains_enabled", default: false, null: false
+ t.integer "default_git_depth"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree
end
- create_table "project_custom_attributes", force: :cascade do |t|
+ create_table "project_custom_attributes", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id", null: false
@@ -1584,7 +1674,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
end
- create_table "project_deploy_tokens", force: :cascade do |t|
+ create_table "project_daily_statistics", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.integer "fetch_count", null: false
+ t.date "date"
+ t.index ["project_id", "date"], name: "index_project_daily_statistics_on_project_id_and_date", unique: true, order: { date: :desc }, using: :btree
+ end
+
+ create_table "project_deploy_tokens", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "deploy_token_id", null: false
t.datetime_with_timezone "created_at", null: false
@@ -1592,7 +1689,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
end
- create_table "project_error_tracking_settings", primary_key: "project_id", id: :integer, force: :cascade do |t|
+ create_table "project_error_tracking_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
t.boolean "enabled", default: false, null: false
t.string "api_url"
t.string "encrypted_token"
@@ -1601,7 +1698,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "organization_name"
end
- create_table "project_features", force: :cascade do |t|
+ create_table "project_features", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "merge_requests_access_level"
t.integer "issues_access_level"
@@ -1615,7 +1712,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
end
- create_table "project_group_links", force: :cascade do |t|
+ create_table "project_group_links", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "group_id", null: false
t.datetime "created_at"
@@ -1626,7 +1723,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_project_group_links_on_project_id", using: :btree
end
- create_table "project_import_data", force: :cascade do |t|
+ create_table "project_import_data", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.text "data"
t.text "encrypted_credentials"
@@ -1635,7 +1732,11 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
end
- create_table "project_mirror_data", force: :cascade do |t|
+ create_table "project_metrics_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
+ t.string "external_dashboard_url", null: false
+ end
+
+ create_table "project_mirror_data", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "status"
t.string "jid"
@@ -1645,7 +1746,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["status"], name: "index_project_mirror_data_on_status", using: :btree
end
- create_table "project_repositories", id: :bigserial, force: :cascade do |t|
+ create_table "project_repositories", force: :cascade do |t|
t.integer "shard_id", null: false
t.string "disk_path", null: false
t.integer "project_id", null: false
@@ -1654,7 +1755,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["shard_id"], name: "index_project_repositories_on_shard_id", using: :btree
end
- create_table "project_statistics", force: :cascade do |t|
+ create_table "project_statistics", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "namespace_id", null: false
t.bigint "commit_count", default: 0, null: false
@@ -1662,11 +1763,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.bigint "repository_size", default: 0, null: false
t.bigint "lfs_objects_size", default: 0, null: false
t.bigint "build_artifacts_size", default: 0, null: false
+ t.bigint "packages_size", default: 0, null: false
+ t.bigint "wiki_size"
t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree
t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree
end
- create_table "projects", force: :cascade do |t|
+ create_table "projects", id: :serial, force: :cascade do |t|
t.string "name"
t.string "path"
t.text "description"
@@ -1684,7 +1787,6 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "import_type"
t.string "import_source"
t.text "import_error"
- t.integer "ci_id"
t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token"
t.string "build_coverage_regex"
@@ -1721,7 +1823,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.bigint "pool_repository_id"
t.string "runners_token_encrypted"
t.string "bfg_object_map"
- t.index ["ci_id"], name: "index_projects_on_ci_id", using: :btree
+ t.boolean "detected_repository_languages"
+ t.string "external_authorization_classification_label"
t.index ["created_at"], name: "index_projects_on_created_at", using: :btree
t.index ["creator_id"], name: "index_projects_on_creator_id", using: :btree
t.index ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
@@ -1739,11 +1842,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["repository_storage", "created_at"], name: "idx_project_repository_check_partial", where: "(last_repository_check_at IS NULL)", using: :btree
t.index ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree
t.index ["runners_token"], name: "index_projects_on_runners_token", using: :btree
+ t.index ["runners_token_encrypted"], name: "index_projects_on_runners_token_encrypted", using: :btree
t.index ["star_count"], name: "index_projects_on_star_count", using: :btree
t.index ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
end
- create_table "prometheus_metrics", force: :cascade do |t|
+ create_table "prometheus_metrics", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.string "title", null: false
t.string "query", null: false
@@ -1761,7 +1865,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_prometheus_metrics_on_project_id", using: :btree
end
- create_table "protected_branch_merge_access_levels", force: :cascade do |t|
+ create_table "protected_branch_merge_access_levels", id: :serial, force: :cascade do |t|
t.integer "protected_branch_id", null: false
t.integer "access_level", default: 40, null: false
t.datetime "created_at", null: false
@@ -1769,7 +1873,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree
end
- create_table "protected_branch_push_access_levels", force: :cascade do |t|
+ create_table "protected_branch_push_access_levels", id: :serial, force: :cascade do |t|
t.integer "protected_branch_id", null: false
t.integer "access_level", default: 40, null: false
t.datetime "created_at", null: false
@@ -1777,7 +1881,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree
end
- create_table "protected_branches", force: :cascade do |t|
+ create_table "protected_branches", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.datetime "created_at"
@@ -1785,7 +1889,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
end
- create_table "protected_tag_create_access_levels", force: :cascade do |t|
+ create_table "protected_tag_create_access_levels", id: :serial, force: :cascade do |t|
t.integer "protected_tag_id", null: false
t.integer "access_level", default: 40
t.integer "user_id"
@@ -1797,7 +1901,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_protected_tag_create_access_levels_on_user_id", using: :btree
end
- create_table "protected_tags", force: :cascade do |t|
+ create_table "protected_tags", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.datetime "created_at", null: false
@@ -1818,7 +1922,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true, using: :btree
end
- create_table "redirect_routes", force: :cascade do |t|
+ create_table "redirect_routes", id: :serial, force: :cascade do |t|
t.integer "source_id", null: false
t.string "source_type", null: false
t.string "path", null: false
@@ -1828,7 +1932,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
end
- create_table "release_links", id: :bigserial, force: :cascade do |t|
+ create_table "release_links", force: :cascade do |t|
t.integer "release_id", null: false
t.string "url", null: false
t.string "name", null: false
@@ -1838,7 +1942,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["release_id", "url"], name: "index_release_links_on_release_id_and_url", unique: true, using: :btree
end
- create_table "releases", force: :cascade do |t|
+ create_table "releases", id: :serial, force: :cascade do |t|
t.string "tag"
t.text "description"
t.integer "project_id"
@@ -1854,7 +1958,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_releases_on_project_id", using: :btree
end
- create_table "remote_mirrors", force: :cascade do |t|
+ create_table "remote_mirrors", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.string "url"
t.boolean "enabled", default: false
@@ -1882,7 +1986,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "programming_language_id"], name: "index_repository_languages_on_project_and_languages_id", unique: true, using: :btree
end
- create_table "resource_label_events", id: :bigserial, force: :cascade do |t|
+ create_table "resource_label_events", force: :cascade do |t|
t.integer "action", null: false
t.integer "issue_id"
t.integer "merge_request_id"
@@ -1898,7 +2002,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_resource_label_events_on_user_id", using: :btree
end
- create_table "routes", force: :cascade do |t|
+ create_table "routes", id: :serial, force: :cascade do |t|
t.integer "source_id", null: false
t.string "source_type", null: false
t.string "path", null: false
@@ -1910,7 +2014,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree
end
- create_table "sent_notifications", force: :cascade do |t|
+ create_table "sent_notifications", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.integer "noteable_id"
t.string "noteable_type"
@@ -1924,7 +2028,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
end
- create_table "services", force: :cascade do |t|
+ create_table "services", id: :serial, force: :cascade do |t|
t.string "type"
t.string "title"
t.integer "project_id"
@@ -1946,17 +2050,18 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "commit_events", default: true, null: false
t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events", default: true
+ t.boolean "deployment_events", default: false, null: false
t.index ["project_id"], name: "index_services_on_project_id", using: :btree
t.index ["template"], name: "index_services_on_template", using: :btree
t.index ["type"], name: "index_services_on_type", using: :btree
end
- create_table "shards", force: :cascade do |t|
+ create_table "shards", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.index ["name"], name: "index_shards_on_name", unique: true, using: :btree
end
- create_table "snippets", force: :cascade do |t|
+ create_table "snippets", id: :serial, force: :cascade do |t|
t.string "title"
t.text "content"
t.integer "author_id", null: false
@@ -1979,7 +2084,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
end
- create_table "spam_logs", force: :cascade do |t|
+ create_table "spam_logs", id: :serial, force: :cascade do |t|
t.integer "user_id"
t.string "source_ip"
t.string "user_agent"
@@ -1993,7 +2098,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "recaptcha_verified", default: false, null: false
end
- create_table "subscriptions", force: :cascade do |t|
+ create_table "subscriptions", id: :serial, force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
t.string "subscribable_type"
@@ -2005,17 +2110,20 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true, using: :btree
end
- create_table "suggestions", id: :bigserial, force: :cascade do |t|
+ create_table "suggestions", force: :cascade do |t|
t.integer "note_id", null: false
t.integer "relative_order", limit: 2, null: false
t.boolean "applied", default: false, null: false
t.string "commit_id"
t.text "from_content", null: false
t.text "to_content", null: false
+ t.integer "lines_above", default: 0, null: false
+ t.integer "lines_below", default: 0, null: false
+ t.boolean "outdated", default: false, null: false
t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true, using: :btree
end
- create_table "system_note_metadata", force: :cascade do |t|
+ create_table "system_note_metadata", id: :serial, force: :cascade do |t|
t.integer "note_id", null: false
t.integer "commit_count"
t.string "action"
@@ -2024,7 +2132,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true, using: :btree
end
- create_table "taggings", force: :cascade do |t|
+ create_table "taggings", id: :serial, force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
t.string "taggable_type"
@@ -2038,13 +2146,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["taggable_id", "taggable_type"], name: "index_taggings_on_taggable_id_and_taggable_type", using: :btree
end
- create_table "tags", force: :cascade do |t|
+ create_table "tags", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "taggings_count", default: 0
t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree
+ t.index ["name"], name: "index_tags_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
end
- create_table "term_agreements", force: :cascade do |t|
+ create_table "term_agreements", id: :serial, force: :cascade do |t|
t.integer "term_id", null: false
t.integer "user_id", null: false
t.boolean "accepted", default: false, null: false
@@ -2055,7 +2164,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_term_agreements_on_user_id", using: :btree
end
- create_table "timelogs", force: :cascade do |t|
+ create_table "timelogs", id: :serial, force: :cascade do |t|
t.integer "time_spent", null: false
t.integer "user_id"
t.datetime "created_at", null: false
@@ -2068,7 +2177,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_timelogs_on_user_id", using: :btree
end
- create_table "todos", force: :cascade do |t|
+ create_table "todos", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "project_id"
t.integer "target_id"
@@ -2092,12 +2201,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_todos_on_user_id", using: :btree
end
- create_table "trending_projects", force: :cascade do |t|
+ create_table "trending_projects", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.index ["project_id"], name: "index_trending_projects_on_project_id", unique: true, using: :btree
end
- create_table "u2f_registrations", force: :cascade do |t|
+ create_table "u2f_registrations", id: :serial, force: :cascade do |t|
t.text "certificate"
t.string "key_handle"
t.string "public_key"
@@ -2110,7 +2219,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
end
- create_table "uploads", force: :cascade do |t|
+ create_table "uploads", id: :serial, force: :cascade do |t|
t.bigint "size", null: false
t.string "path", limit: 511, null: false
t.string "checksum", limit: 64
@@ -2127,7 +2236,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["uploader", "path"], name: "index_uploads_on_uploader_and_path", using: :btree
end
- create_table "user_agent_details", force: :cascade do |t|
+ create_table "user_agent_details", id: :serial, force: :cascade do |t|
t.string "user_agent", null: false
t.string "ip_address", null: false
t.integer "subject_id", null: false
@@ -2138,14 +2247,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
end
- create_table "user_callouts", force: :cascade do |t|
+ create_table "user_callouts", id: :serial, force: :cascade do |t|
t.integer "feature_name", null: false
t.integer "user_id", null: false
t.index ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
t.index ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
end
- create_table "user_custom_attributes", force: :cascade do |t|
+ create_table "user_custom_attributes", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "user_id", null: false
@@ -2162,7 +2271,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree
end
- create_table "user_preferences", force: :cascade do |t|
+ create_table "user_preferences", id: :serial, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "issue_notes_filter", limit: 2, default: 0, null: false
t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
@@ -2171,10 +2280,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "first_day_of_week"
t.string "issues_sort"
t.string "merge_requests_sort"
+ t.string "timezone"
+ t.boolean "time_display_relative"
+ t.boolean "time_format_in_24h"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
end
- create_table "user_statuses", primary_key: "user_id", force: :cascade do |t|
+ create_table "user_statuses", primary_key: "user_id", id: :serial, force: :cascade do |t|
t.integer "cached_markdown_version"
t.string "emoji", default: "speech_balloon", null: false
t.string "message", limit: 100
@@ -2182,7 +2294,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_user_statuses_on_user_id", using: :btree
end
- create_table "user_synced_attributes_metadata", force: :cascade do |t|
+ create_table "user_synced_attributes_metadata", id: :serial, force: :cascade do |t|
t.boolean "name_synced", default: false
t.boolean "email_synced", default: false
t.boolean "location_synced", default: false
@@ -2191,7 +2303,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id"], name: "index_user_synced_attributes_metadata_on_user_id", unique: true, using: :btree
end
- create_table "users", force: :cascade do |t|
+ create_table "users", id: :serial, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
@@ -2277,7 +2389,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
end
- create_table "users_star_projects", force: :cascade do |t|
+ create_table "users_star_projects", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
t.datetime "created_at"
@@ -2286,7 +2398,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
end
- create_table "web_hook_logs", force: :cascade do |t|
+ create_table "web_hook_logs", id: :serial, force: :cascade do |t|
t.integer "web_hook_id", null: false
t.string "trigger"
t.string "url"
@@ -2303,7 +2415,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["web_hook_id"], name: "index_web_hook_logs_on_web_hook_id", using: :btree
end
- create_table "web_hooks", force: :cascade do |t|
+ create_table "web_hooks", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -2434,6 +2546,10 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade
+ add_foreign_key "merge_request_assignees", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_request_assignees", "users", on_delete: :cascade
+ add_foreign_key "merge_request_blocks", "merge_requests", column: "blocked_merge_request_id", on_delete: :cascade
+ add_foreign_key "merge_request_blocks", "merge_requests", column: "blocking_merge_request_id", on_delete: :cascade
add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
@@ -2452,12 +2568,16 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "merge_requests", "users", column: "updated_by_id", name: "fk_641731faff", on_delete: :nullify
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_trains", "ci_pipelines", column: "pipeline_id", on_delete: :nullify
+ add_foreign_key "merge_trains", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_trains", "users", on_delete: :cascade
add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade
add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade
add_foreign_key "note_diff_files", "notes", column: "diff_note_id", on_delete: :cascade
add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade
add_foreign_key "notification_settings", "users", name: "fk_0c95e91db7", on_delete: :cascade
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ add_foreign_key "pages_domain_acme_orders", "pages_domains", on_delete: :cascade
add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify
@@ -2467,12 +2587,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_ci_cd_settings", "projects", name: "fk_24c15d2f2e", on_delete: :cascade
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
+ add_foreign_key "project_daily_statistics", "projects", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
add_foreign_key "project_error_tracking_settings", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
+ add_foreign_key "project_metrics_settings", "projects", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "shards", on_delete: :restrict
diff --git a/doc/README.md b/doc/README.md
index 14e8d2edb90..3863e17c268 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -4,7 +4,7 @@ description: 'Learn how to use and administer GitLab, the most scalable Git-base
---
<div class="display-none">
- <em>Visit <a href="https://docs.gitlab.com/ce/">docs.gitlab.com</a> for optimized
+ <em>Visit <a href="https://docs.gitlab.com/ee/">docs.gitlab.com</a> for optimized
navigation, discoverability, and readability.</em>
</div>
<!-- the div above will not display on the docs site but will display on /help -->
@@ -38,6 +38,7 @@ Have a look at some of our most popular documentation resources:
| [GitLab CI/CD examples](ci/examples/README.md) | Get up to speed quickly with common CI/CD scenarios. |
| [GitLab Container Registry](user/project/container_registry.md) | Host containers within GitLab. |
| [GitLab Pages](user/project/pages/index.md) | Host static websites for your projects with GitLab. |
+| [GitLab.com settings](user/gitlab_com/index.md) | Settings for [GitLab.com](#gitlabcom). |
| [Kubernetes integration](user/project/clusters/index.md) | Use GitLab with Kubernetes. |
| [SSH authentication](ssh/README.md) | Secure your network communications. |
| [Using Docker images](ci/docker/using_docker_images.md) | Build and test your applications with Docker. |
@@ -106,13 +107,18 @@ The following documentation relates to the DevOps **Plan** stage:
| Plan Topics | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Burndown Charts](user/project/milestones/burndown_charts.md) **[STARTER]** | Watch your project's progress throughout a specific milestone. |
| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
-| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
+| [Epics](user/group/epics/index.md) **[ULTIMATE]** | Tracking groups of issues that share a theme. |
| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/moving_issues.md) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. |
| [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. |
| [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. |
| [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. |
+| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
+| [Related Issues](user/project/issues/related_issues.md) **[STARTER]** | Create a relationship between issues. |
+| [Roadmap](user/group/roadmap/index.md) **[ULTIMATE]** | Visualize epic timelines. |
+| [Service Desk](user/project/service_desk.md) **[PREMIUM]** | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
| [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. |
| [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
@@ -135,16 +141,21 @@ The following documentation relates to the DevOps **Create** stage:
#### Projects and Groups
-| Create Topics - Projects and Groups | Description |
-|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|
-| [Create](gitlab-basics/create-project.md) and [fork](gitlab-basics/fork-project.md) projects, and<br/>[import and export<br/>projects between instances](user/project/settings/import_export.md) | Create, duplicate, and move projects. |
-| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. |
-| [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. |
-| [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
-| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
-| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
-| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
-| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
+| Create Topics - Projects and Groups | Description |
+|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
+| [Advanced global search](user/search/advanced_global_search.md) **[STARTER]** | Leverage Elasticsearch for faster, more advanced code search across your entire GitLab instance. |
+| [Advanced syntax search](user/search/advanced_search_syntax.md) **[STARTER]** | Use advanced queries for more targeted search results. |
+| [Contribution analytics](user/group/contribution_analytics/index.md) **[STARTER]** | See detailed statistics of group contributors. |
+| [Create](gitlab-basics/create-project.md) and [fork](gitlab-basics/fork-project.md) projects, and<br/>[import and export projects<br/>between instances](user/project/settings/import_export.md) | Create, duplicate, and move projects. |
+| [File locking](user/project/file_lock.md) **[PREMIUM]** | Lock files to avoid merge conflicts. |
+| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. |
+| [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. |
+| [Issues Analytics](user/group/issues_analytics/index.md) **[PREMIUM]** | Check how many issues were created per month. |
+| [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
+| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
+| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
+| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
+| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -164,7 +175,9 @@ The following documentation relates to the DevOps **Create** stage:
| [Files](user/project/repository/index.md#files) | Files management. |
| [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) | GitLab's support for `.ipynb` files. |
| [Protected branches](user/project/protected_branches.md) | Use protected branches. |
+| [Push rules](push_rules/push_rules.md) **[STARTER]** | Additional control over pushes to your projects. |
| [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. |
+| [Repository mirroring](workflow/repository_mirroring.md) **[STARTER]** | Push to or pull from repositories outside of GitLab |
| [Start a merge request](user/project/repository/web_editor.md#tips) | Start merge request when committing via GitLab's user interface. |
<div align="right">
@@ -191,13 +204,14 @@ The following documentation relates to the DevOps **Create** stage:
#### Integration and Automation
-| Create Topics - Integration and Automation | Description |
-|:------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------|
-| [GitLab API](api/README.md) | Integrate GitLab via a simple and powerful API. |
-| [GitLab Integration](integration/README.md) | Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication. |
-| [GitLab Webhooks](user/project/integrations/webhooks.md) | Let GitLab notify you when new code has been pushed to your project. |
-| [Project Services](user/project/integrations/project_services.md) | Integrate a project with external services, such as CI and chat. |
-| [Trello Power-Up](integration/trello_power_up.md) | Integrate with GitLab's Trello Power-Up. |
+| Create Topics - Integration and Automation | Description |
+|:------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------|
+| [GitLab API](api/README.md) | Integrate GitLab via a simple and powerful API. |
+| [GitLab Integration](integration/README.md) | Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication. |
+| [GitLab Webhooks](user/project/integrations/webhooks.md) | Let GitLab notify you when new code has been pushed to your project. |
+| [JIRA Development Panel](integration/jira_development_panel.md) **[PREMIUM]** | See GitLab information in the JIRA Development Panel. |
+| [Project Services](user/project/integrations/project_services.md) | Integrate a project with external services, such as CI and chat. |
+| [Trello Power-Up](integration/trello_power_up.md) | Integrate with GitLab's Trello Power-Up. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -217,12 +231,14 @@ scales to run your tests faster.
The following documentation relates to the DevOps **Verify** stage:
-| Verify Topics | Description |
-|:---------------------------------------------------|:-----------------------------------------------------------------------------|
-| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
-| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
-| [Pipeline Graphs](ci/pipelines.md#pipeline-graphs) | Visualize builds. |
-| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. |
+| Verify Topics | Description |
+|:----------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|
+| [Code Quality reports](user/project/merge_requests/code_quality.md) **[STARTER]** | Analyze source code quality. |
+| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
+| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
+| [Multi-project pipelines](ci/multi_project_pipelines.md) **[PREMIUM]** | Visualize entire pipelines that span multiple projects, including all cross-project inter-dependencies. |
+| [Pipeline Graphs](ci/pipelines.md#visualizing-pipelines) | Visualize builds. |
+| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -241,6 +257,7 @@ The following documentation relates to the DevOps **Package** stage:
| Package Topics | Description |
|:----------------------------------------------------------------|:-------------------------------------------------------|
| [GitLab Container Registry](user/project/container_registry.md) | Learn how to use GitLab's built-in Container Registry. |
+| [GitLab Packages](administration/packages.md) **[PREMIUM]** | Use GitLab as an NPM registry or Maven repository. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -256,14 +273,17 @@ confidently and securely with GitLab’s built-in Continuous Delivery and Deploy
The following documentation relates to the DevOps **Release** stage:
-| Release Topics | Description |
-|:------------------------------------------------------------|:---------------------------------------------------------------------------------------------|
-| [Auto Deploy](topics/autodevops/index.md#auto-deploy) | Configure GitLab for the deployment of your application. |
-| [Environments and deployments](ci/environments.md) | With environments, you can control the continuous deployment of your software within GitLab. |
-| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Deployment and Delivery with GitLab. |
-| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy a static site directly from GitLab. |
-| [Protected Runners](ci/runners/README.md#protected-runners) | Select Runners to only pick jobs for protected branches and tags. |
-| [Scheduled Pipelines](user/project/pipelines/schedules.md) | Execute pipelines on a schedule. |
+| Release Topics | Description |
+|:------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|
+| [Auto Deploy](topics/autodevops/index.md#auto-deploy) | Configure GitLab for the deployment of your application. |
+| [Canary Deployments](user/project/canary_deployments.md) **[PREMIUM]** | Employ a popular CI strategy where a small portion of the fleet is updated to the new version first. |
+| [Deploy Boards](user/project/deploy_boards.md) **[PREMIUM]** | View the current health and status of each CI environment running on Kubernetes, displaying the status of the pods in the deployment. |
+| [Environments and deployments](ci/environments.md) | With environments, you can control the continuous deployment of your software within GitLab. |
+| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) **[PREMIUM]** | Limit scope of variables to specific environments. |
+| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Deployment and Delivery with GitLab. |
+| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy a static site directly from GitLab. |
+| [Protected Runners](ci/runners/README.md#protected-runners) | Select Runners to only pick jobs for protected branches and tags. |
+| [Scheduled Pipelines](user/project/pipelines/schedules.md) | Execute pipelines on a schedule. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -287,7 +307,9 @@ The following documentation relates to the DevOps **Configure** stage:
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
-| [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. |
+| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters-premium) **[PREMIUM]** | Associate more than one Kubernetes clusters to your project. |
+| [Protected variables](ci/variables/README.md#protected-environment-variables) | Restrict variables to protected branches and tags. |
+| [Serverless](user/project/clusters/serverless/index.md) | Run serverless workloads on Kubernetes. |
| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
<div align="right">
@@ -321,16 +343,23 @@ The following documentation relates to the DevOps **Monitor** stage:
### Secure
-GitLab can help you secure your applications from within your development lifecycle.
+Check your application for security vulnerabilities that may lead to unauthorized access,
+data leaks, and denial of services. GitLab will perform static and dynamic tests on the
+code of your application, looking for known flaws and report them in the merge request
+so you can fix them before merging. Security teams can use dashboards to get a
+high-level view on projects and groups, and start remediation processes when needed.
The following documentation relates to the DevOps **Secure** stage:
-| Monitor Topics | Description |
-|:----------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------|
-| [Container Scanning example](ci/examples/container_scanning.md) | `.gitlab-ci.yml` example of using Clair and clair-scanner to scan docker images for known vulnerabilities. |
-
-NOTE: **Note:**
-Viewing [Container Scanning reports](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html) within merge requests requires [GitLab Ultimate](https://about.gitlab.com/pricing/).
+| Secure Topics | Description |
+|:------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------|
+| [Container Scanning](user/application_security/container_scanning/index.md) **[ULTIMATE]** | Use Clair to scan docker images for known vulnerabilities. |
+| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
+| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **[ULTIMATE]** | Analyze running web applications for known vulnerabilities. |
+| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all the projects in a group and its subgroups. |
+| [License Management](user/application_security/license_management/index.md) **[ULTIMATE]** | Search your project's dependencies for their licenses. |
+| [Project Security Dashboard](user/application_security/security_dashboard/index.md) **[ULTIMATE]** | View the latest security reports for your project. |
+| [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. |
## Subscribe to GitLab
@@ -340,6 +369,8 @@ There are two ways to use GitLab:
- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install anything to use GitLab.com,
you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
+For more information on managing your subscription and [Customers Portal](https://customers.gitlab.com) account, please see [Getting Started with Subscriptions](getting-started/subscription.md).
+
The following sections outline tiers and features within GitLab self-managed and GitLab.com.
<div align="right">
@@ -391,6 +422,12 @@ GitLab.com subscriptions grant access
to the same features available in GitLab self-managed, **except
[administration](administration/index.md) tools and settings**.
+GitLab.com allows you to apply your subscription to a group or your personal user.
+
+When applied to a **group**, the group, all subgroups, and all projects under the selected group on GitLab.com will have the features of the associated plan. It is recommended to go with a group plan when managing projects and users of an organization.
+
+When associated with a **personal userspace** instead, all projects will have features with the subscription applied, but as it is not a group, group features will not be available.
+
TIP: **Tip:**
To support the open source community and encourage the development of open source projects, GitLab grants access to **Gold** features for all GitLab.com **public** projects, regardless of the subscription.
@@ -416,7 +453,7 @@ We have the following documentation to rapidly uplift your GitLab knowledge:
| Topic | Description |
|:-----------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------|
-| [GitLab Basics](gitlab-basics/README.md) | Start working on the command line and with GitLab. |
+| [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. |
| [GitLab Workflow](workflow/README.md) and [overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. |
| [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. |
| [Auto DevOps](topics/autodevops/index.md) | Learn more about GitLab's Auto DevOps. |
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
new file mode 100644
index 00000000000..d7a2e13b53e
--- /dev/null
+++ b/doc/administration/audit_events.md
@@ -0,0 +1,116 @@
+---
+last_updated: 2019-02-04
+---
+
+# Audit Events **[STARTER]**
+
+GitLab offers a way to view the changes made within the GitLab server for owners and administrators on a [paid plan][ee].
+
+GitLab system administrators can also take advantage of the logs located on the
+filesystem, see [the logs system documentation](logs.md) for more details.
+
+## Overview
+
+**Audit Events** is a tool for GitLab owners and administrators to be
+able to track important events such as who performed certain actions and the
+time they happened. These actions could be, for example, change a user
+permission level, who added a new user, or who removed a user.
+
+## Use-cases
+
+- Check who was the person who changed the permission level of a particular
+ user for a project in GitLab.
+- Use it to track which users have access to a certain group of projects
+ in GitLab, and who gave them that permission level.
+
+## List of events
+
+There are two kinds of events logged:
+
+- Events scoped to the group or project, used by group / project managers
+ to look up who made what change.
+- Instance events scoped to the whole GitLab instance, used by your Compliance team to
+ perform formal audits.
+
+### Group events **[STARTER]**
+
+NOTE: **Note:**
+You need Owner [permissions] to view the group Audit Events page.
+
+To view a group's audit events, navigate to **Group > Settings > Audit Events**.
+From there, you can see the following actions:
+
+- Group name/path changed
+- Group repository size limit changed
+- Group created/deleted
+- Group changed visibility
+- User was added to group and with which [permissions]
+- Permissions changes of a user assigned to a group
+- Removed user from group
+- Project added to group and with which visibility level
+- Project removed from group
+- [Project shared with group](../user/project/members/share_project_with_groups.md)
+ and with which [permissions]
+- Removal of a previously shared group with a project
+- LFS enabled/disabled
+- Shared runners minutes limit changed
+- Membership lock enabled/disabled
+- Request access enabled/disabled
+- 2FA enforcement/grace period changed
+- Roles allowed to create project changed
+
+### Project events **[STARTER]**
+
+NOTE: **Note:**
+You need Maintainer [permissions] or higher to view the project Audit Events page.
+
+To view a project's audit events, navigate to **Project > Settings > Audit Events**.
+From there, you can see the following actions:
+
+- Added/removed deploy keys
+- Project created/deleted/renamed/moved(transferred)/changed path
+- Project changed visibility level
+- User was added to project and with which [permissions]
+- Permission changes of a user assigned to a project
+- User was removed from project
+
+### Instance events **[PREMIUM ONLY]**
+
+> [Introduced][ee-2336] in [GitLab Premium][ee] 9.3.
+
+Server-wide audit logging introduces the ability to observe user actions across
+the entire instance of your GitLab server, making it easy to understand who
+changed what and when for audit purposes.
+
+To view the server-wide admin log, visit **Admin Area > Monitoring > Audit Log**.
+
+In addition to the group and project events, the following user actions are also
+recorded:
+
+- Failed Logins
+- Sign-in events and the authentication type (standard, LDAP, OmniAuth, etc.)
+- Added SSH key
+- Added/removed email
+- Changed password
+- Ask for password reset
+- Grant OAuth access
+
+It is possible to filter particular actions by choosing an audit data type from
+the filter drop-down. You can further filter by specific group, project or user
+(for authentication events).
+
+![audit log](audit_log.png)
+
+### Missing events
+
+Some events are not being tracked in Audit Events. Please see the following
+epics for more detail on which events are not being tracked and our progress
+on adding these events into GitLab:
+
+- [Project settings and activity](https://gitlab.com/groups/gitlab-org/-/epics/474)
+- [Group settings and activity](https://gitlab.com/groups/gitlab-org/-/epics/475)
+- [Instance-level settings and activity](https://gitlab.com/groups/gitlab-org/-/epics/476)
+
+[ee-2336]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2336
+[ee]: https://about.gitlab.com/pricing/
+[permissions]: ../user/permissions.md
diff --git a/doc/administration/audit_log.png b/doc/administration/audit_log.png
new file mode 100644
index 00000000000..d4f4c2abf38
--- /dev/null
+++ b/doc/administration/audit_log.png
Binary files differ
diff --git a/doc/administration/auditor_access_form.png b/doc/administration/auditor_access_form.png
new file mode 100644
index 00000000000..c179a7d3b0a
--- /dev/null
+++ b/doc/administration/auditor_access_form.png
Binary files differ
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
new file mode 100644
index 00000000000..ef8c8197d6d
--- /dev/null
+++ b/doc/administration/auditor_users.md
@@ -0,0 +1,88 @@
+# Auditor users **[PREMIUM ONLY]**
+
+>[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
+
+Auditor users are given read-only access to all projects, groups, and other
+resources on the GitLab instance.
+
+## Overview
+
+Auditor users can have full access to their own resources (projects, groups,
+snippets, etc.), and read-only access to **all** other resources, except the
+Admin area. To put another way, they are just regular users (who can be added
+to projects, create personal snippets, create milestones on their groups, etc.)
+who also happen to have read-only access to all projects on the system that
+they haven't been explicitly [given access][permissions] to.
+
+The Auditor role is _not_ a read-only version of the Admin role. Auditor users
+will not be able to access the project/group settings pages, or the Admin Area.
+
+To sum up, assuming you have logged-in as an Auditor user:
+
+- For a project the Auditor is not member of, the Auditor should have
+ read-only access. If the project is public or internal, they would have the
+ same access as the users that are not members of that project/group.
+- For a project the Auditor owns, the Auditor should have full access to
+ everything.
+- For a project the Auditor has been added to as a member, the Auditor should
+ have the same access as the [permissions] they were given to. For example, if
+ they were added as a Developer, they could then push commits or comment on
+ issues.
+- The Auditor cannot view the Admin area, or perform any admin actions.
+
+For more information about what an Auditor can or can't do, see the
+[Permissions and restrictions of an Auditor user](#permissions-and-restrictions-of-an-auditor-user)
+section.
+
+## Use cases
+
+1. Your compliance department wants to run tests against the entire GitLab base
+ to ensure users are complying with password, credit card, and other sensitive
+ data policies. With Auditor users, this can be achieved very easily without
+ resulting to tactics like giving a user admin rights or having to use the API
+ to add them to all projects.
+1. If particular users need visibility or access to most of all projects in
+ your GitLab instance, instead of manually adding the user to all projects,
+ you can simply create an Auditor user and share the credentials with those
+ that you want to grant access to.
+
+## Adding an Auditor user
+
+1. Create a new user or edit an existing one by navigating to
+ **Admin Area > Users**. You will find the option of the access level under
+ the 'Access' section.
+
+ ![Admin Area Form](auditor_access_form.png)
+
+1. Click **Save changes** or **Create user** for the changes to take effect.
+
+To revoke the Auditor permissions from a user, simply make them a Regular user
+following the same steps as above.
+
+## Permissions and restrictions of an Auditor user
+
+An Auditor user should be able to access all projects and groups of a GitLab
+instance, with the following permissions/restrictions:
+
+- Has read-only access to the API
+- Can access projects that are:
+ - Private
+ - Public
+ - Internal
+- Can read all files in a repository
+- Can read issues / MRs
+- Can read project snippets
+- Cannot be Admin and Auditor at the same time
+- Cannot access the Admin area
+- In a group / project they're not a member of:
+ - Cannot access project settings
+ - Cannot access group settings
+ - Cannot commit to repository
+ - Cannot create / comment on issues / MRs
+ - Cannot create/modify files from the Web UI
+ - Cannot merge a merge request
+ - Cannot create project snippets
+
+[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998
+[eep]: https://about.gitlab.com/pricing/
+[permissions]: ../user/permissions.md
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index 54be7b616cc..e215a0df6ec 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -9,9 +9,11 @@ providers.
- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
and 389 Server
+ - [LDAP for GitLab EE](ldap-ee.md): LDAP additions to GitLab Enterprise Editions **[STARTER ONLY]**
- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
Bitbucket, Facebook, Shibboleth, Crowd, Azure, Authentiq ID, and JWT
- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Okta](okta.md) Configure GitLab to sign in using Okta
- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication
+- [Smartcard](smartcard.md) Smartcard authentication **[PREMIUM ONLY]**
diff --git a/doc/administration/auth/google_secure_ldap.md b/doc/administration/auth/google_secure_ldap.md
new file mode 100644
index 00000000000..760af0cfd1a
--- /dev/null
+++ b/doc/administration/auth/google_secure_ldap.md
@@ -0,0 +1,207 @@
+# Google Secure LDAP **[CORE ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46391) in GitLab 11.9.
+
+[Google Cloud Identity](https://cloud.google.com/identity/) provides a Secure
+LDAP service that can be configured with GitLab for authentication and group sync.
+
+Secure LDAP requires a slightly different configuration than standard LDAP servers.
+The steps below cover:
+
+- Configuring the Secure LDAP Client in the Google Admin console.
+- Required GitLab configuration.
+
+## Configuring Google LDAP client
+
+1. Navigate to https://admin.google.com and sign in as a GSuite domain administrator.
+
+1. Go to **Apps > LDAP > Add Client**.
+
+1. Provide an `LDAP client name` and an optional `Description`. Any descriptive
+ values are acceptable. For example, the name could be 'GitLab' and the
+ description could be 'GitLab LDAP Client'. Click the **Continue** button.
+
+ ![Add LDAP Client Step 1](img/google_secure_ldap_add_step_1.png)
+
+1. Set **Access Permission** according to your needs. You must choose either
+ 'Entire domain (GitLab)' or 'Selected organizational units' for both 'Verify user
+ credentials' and 'Read user information'. Select 'Add LDAP Client'
+
+ TIP: **Tip:** If you plan to use GitLab [LDAP Group Sync](ldap-ee.md#group-sync)
+ , turn on 'Read group information'.
+
+ ![Add LDAP Client Step 2](img/google_secure_ldap_add_step_2.png)
+
+1. Download the generated certificate. This is required for GitLab to
+ communicate with the Google Secure LDAP service. Save the downloaded certificates
+ for later use. After downloading, click the **Continue to Client Details** button.
+
+1. Expand the **Service Status** section and turn the LDAP client 'ON for everyone'.
+ After selecting 'Save', click on the 'Service Status' bar again to collapse
+ and return to the rest of the settings.
+
+1. Expand the **Authentication** section and choose 'Generate New Credentials'.
+ Copy/note these credentials for later use. After selecting 'Close', click
+ on the 'Authentication' bar again to collapse and return to the rest of the settings.
+
+Now the Google Secure LDAP Client configuration is finished. The screenshot below
+shows an example of the final settings. Continue on to configure GitLab.
+
+![LDAP Client Settings](img/google_secure_ldap_client_settings.png)
+
+## Configuring GitLab
+
+Edit GitLab configuration, inserting the access credentials and certificate
+obtained earlier.
+
+The following are the configuration keys that need to be modified using the
+values obtained during the LDAP client configuration earlier:
+
+- `bind_dn`: The access credentials username
+- `password`: The access credentials password
+- `cert`: The `.crt` file text from the downloaded certificate bundle
+- `key`: The `.key` file text from the downloaded certificate bundle
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_enabled'] = true
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ label: 'Google Secure LDAP'
+
+ host: 'ldap.google.com'
+ port: 636
+ uid: 'uid'
+ bind_dn: 'DizzyHorse'
+ password: 'd6V5H8nhMUW9AuDP25abXeLd'
+ encryption: 'simple_tls'
+ verify_certificates: true
+
+ tls_options:
+ cert: |
+ -----BEGIN CERTIFICATE-----
+ MIIDbDCCAlSgAwIBAgIGAWlzxiIfMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
+ bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
+ CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAzMTIyMTE5
+ MThaFw0yMjAzMTEyMTE5MThaMHcxFDASBgNVBAoTC0dvb2dsZSBJbmMuMRYwFAYDVQQHEw1Nb3Vu
+ dGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UECxMGR1N1aXRlMQswCQYDVQQG
+ EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ ALOTy4aC38dyjESk6N8fRsKk8DN23ZX/GaNFL5OUmmA1KWzrvVC881OzNdtGm3vNOIxr9clteEG/
+ tQwsmsJvQT5U+GkBt+tGKF/zm7zueHUYqTP7Pg5pxAnAei90qkIRFi17ulObyRHPYv1BbCt8pxNB
+ 4fG/gAXkFbCNxwh1eiQXXRTfruasCZ4/mHfX7MVm8JmWU9uAVIOLW+DSWOFhrDQduJdGBXJOyC2r
+ Gqoeg9+tkBmNH/jjxpnEkFW8q7io9DdOUqqNgoidA1h9vpKTs3084sy2DOgUvKN9uXWx14uxIyYU
+ Y1DnDy0wczcsuRt7l+EgtCEgpsLiLJQbKW+JS1UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAf60J
+ yazhbHkDKIH2gFxfm7QLhhnqsmafvl4WP7JqZt0u0KdnvbDPfokdkM87yfbKJU1MTI86M36wEC+1
+ P6bzklKz7kXbzAD4GggksAzxsEE64OWHC+Y64Tkxq2NiZTw/76POkcg9StiIXjG0ZcebHub9+Ux/
+ rTncip92nDuvgEM7lbPFKRIS/YMhLCk09B/U0F6XLsf1yYjyf5miUTDikPkov23b/YGfpc8kh6hq
+ 1kqdi6a1cYPP34eAhtRhMqcZU9qezpJF6s9EeN/3YFfKzLODFSsVToBRAdZgGHzj//SAtLyQTD4n
+ KCSvK1UmaMxNaZyTHg8JnMf0ZuRpv26iSg==
+ -----END CERTIFICATE-----
+
+ key: |
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzk8uGgt/HcoxEpOjfH0bCpPAz
+ dt2V/xmjRS+TlJpgNSls671QvPNTszXbRpt7zTiMa/XJbXhBv7UMLJrCb0E+VPhpAbfrRihf85u8
+ 7nh1GKkz+z4OacQJwHovdKpCERYte7pTm8kRz2L9QWwrfKcTQeHxv4AF5BWwjccIdXokF10U367m
+ rAmeP5h31+zFZvCZllPbgFSDi1vg0ljhYaw0HbiXRgVyTsgtqxqqHoPfrZAZjR/448aZxJBVvKu4
+ qPQ3TlKqjYKInQNYfb6Sk7N9POLMtgzoFLyjfbl1sdeLsSMmFGNQ5w8tMHM3LLkbe5fhILQhIKbC
+ 4iyUGylviUtVAgMBAAECggEAIPb0CQy0RJoX+q/lGbRVmnyJpYDf+115WNnl+mrwjdGkeZyqw4v0
+ BPzkWYzUFP1esJRO6buBNFybQRFdFW0z5lvVv/zzRKq71aVUBPInxaMRyHuJ8D5lIL8nDtgVOwyE
+ 7DOGyDtURUMzMjdUwoTe7K+O6QBU4X/1pVPZYgmissYSMmt68LiP8k0p601F4+r5xOi/QEy44aVp
+ aOJZBUOisKB8BmUXZqmQ4Cy05vU9Xi1rLyzkn9s7fxnZ+JO6Sd1r0Thm1mE0yuPgxkDBh/b4f3/2
+ GsQNKKKCiij/6TfkjnBi8ZvWR44LnKpu760g/K7psVNrKwqJG6C/8RAcgISWQQKBgQDop7BaKGhK
+ 1QMJJ/vnlyYFTucfGLn6bM//pzTys5Gop0tpcfX/Hf6a6Dd+zBhmC3tBmhr80XOX/PiyAIbc0lOI
+ 31rafZuD/oVx5mlIySWX35EqS14LXmdVs/5vOhsInNgNiE+EPFf1L9YZgG/zA7OUBmqtTeYIPDVC
+ 7ViJcydItQKBgQDFmK0H0IA6W4opGQo+zQKhefooqZ+RDk9IIZMPOAtnvOM7y3rSVrfsSjzYVuMS
+ w/RP/vs7rwhaZejnCZ8/7uIqwg4sdUBRzZYR3PRNFeheW+BPZvb+2keRCGzOs7xkbF1mu54qtYTa
+ HZGZj1OsD83AoMwVLcdLDgO1kw32dkS8IQKBgFRdgoifAHqqVah7VFB9se7Y1tyi5cXWsXI+Wufr
+ j9U9nQ4GojK52LqpnH4hWnOelDqMvF6TQTyLIk/B+yWWK26Ft/dk9wDdSdystd8L+dLh4k0Y+Whb
+ +lLMq2YABw+PeJUnqdYE38xsZVHoDjBsVjFGRmbDybeQxauYT7PACy3FAoGBAK2+k9bdNQMbXp7I
+ j8OszHVkJdz/WXlY1cmdDAxDwXOUGVKIlxTAf7TbiijILZ5gg0Cb+hj+zR9/oI0WXtr+mAv02jWp
+ W8cSOLS4TnBBpTLjIpdu+BwbnvYeLF6MmEjNKEufCXKQbaLEgTQ/XNlchBSuzwSIXkbWqdhM1+gx
+ EjtBAoGARAdMIiDMPWIIZg3nNnFebbmtBP0qiBsYohQZ+6i/8s/vautEHBEN6Q0brIU/goo+nTHc
+ t9VaOkzjCmAJSLPUanuBC8pdYgLu5J20NXUZLD9AE/2bBT3OpezKcdYeI2jqoc1qlWHlNtVtdqQ2
+ AcZSFJQjdg5BTyvdEDhaYUKGdRw=
+ -----END PRIVATE KEY-----
+ EOS
+ ```
+
+1. Save the file and [reconfigure] GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ ldap:
+ enabled: true
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ label: 'Google Secure LDAP'
+
+ host: 'ldap.google.com'
+ port: 636
+ uid: 'uid'
+ bind_dn: 'DizzyHorse'
+ password: 'd6V5H8nhMUW9AuDP25abXeLd'
+ encryption: 'simple_tls'
+ verify_certificates: true
+
+ tls_options:
+ cert: |
+ -----BEGIN CERTIFICATE-----
+ MIIDbDCCAlSgAwIBAgIGAWlzxiIfMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
+ bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
+ CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAzMTIyMTE5
+ MThaFw0yMjAzMTEyMTE5MThaMHcxFDASBgNVBAoTC0dvb2dsZSBJbmMuMRYwFAYDVQQHEw1Nb3Vu
+ dGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UECxMGR1N1aXRlMQswCQYDVQQG
+ EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ ALOTy4aC38dyjESk6N8fRsKk8DN23ZX/GaNFL5OUmmA1KWzrvVC881OzNdtGm3vNOIxr9clteEG/
+ tQwsmsJvQT5U+GkBt+tGKF/zm7zueHUYqTP7Pg5pxAnAei90qkIRFi17ulObyRHPYv1BbCt8pxNB
+ 4fG/gAXkFbCNxwh1eiQXXRTfruasCZ4/mHfX7MVm8JmWU9uAVIOLW+DSWOFhrDQduJdGBXJOyC2r
+ Gqoeg9+tkBmNH/jjxpnEkFW8q7io9DdOUqqNgoidA1h9vpKTs3084sy2DOgUvKN9uXWx14uxIyYU
+ Y1DnDy0wczcsuRt7l+EgtCEgpsLiLJQbKW+JS1UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAf60J
+ yazhbHkDKIH2gFxfm7QLhhnqsmafvl4WP7JqZt0u0KdnvbDPfokdkM87yfbKJU1MTI86M36wEC+1
+ P6bzklKz7kXbzAD4GggksAzxsEE64OWHC+Y64Tkxq2NiZTw/76POkcg9StiIXjG0ZcebHub9+Ux/
+ rTncip92nDuvgEM7lbPFKRIS/YMhLCk09B/U0F6XLsf1yYjyf5miUTDikPkov23b/YGfpc8kh6hq
+ 1kqdi6a1cYPP34eAhtRhMqcZU9qezpJF6s9EeN/3YFfKzLODFSsVToBRAdZgGHzj//SAtLyQTD4n
+ KCSvK1UmaMxNaZyTHg8JnMf0ZuRpv26iSg==
+ -----END CERTIFICATE-----
+
+ key: |
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzk8uGgt/HcoxEpOjfH0bCpPAz
+ dt2V/xmjRS+TlJpgNSls671QvPNTszXbRpt7zTiMa/XJbXhBv7UMLJrCb0E+VPhpAbfrRihf85u8
+ 7nh1GKkz+z4OacQJwHovdKpCERYte7pTm8kRz2L9QWwrfKcTQeHxv4AF5BWwjccIdXokF10U367m
+ rAmeP5h31+zFZvCZllPbgFSDi1vg0ljhYaw0HbiXRgVyTsgtqxqqHoPfrZAZjR/448aZxJBVvKu4
+ qPQ3TlKqjYKInQNYfb6Sk7N9POLMtgzoFLyjfbl1sdeLsSMmFGNQ5w8tMHM3LLkbe5fhILQhIKbC
+ 4iyUGylviUtVAgMBAAECggEAIPb0CQy0RJoX+q/lGbRVmnyJpYDf+115WNnl+mrwjdGkeZyqw4v0
+ BPzkWYzUFP1esJRO6buBNFybQRFdFW0z5lvVv/zzRKq71aVUBPInxaMRyHuJ8D5lIL8nDtgVOwyE
+ 7DOGyDtURUMzMjdUwoTe7K+O6QBU4X/1pVPZYgmissYSMmt68LiP8k0p601F4+r5xOi/QEy44aVp
+ aOJZBUOisKB8BmUXZqmQ4Cy05vU9Xi1rLyzkn9s7fxnZ+JO6Sd1r0Thm1mE0yuPgxkDBh/b4f3/2
+ GsQNKKKCiij/6TfkjnBi8ZvWR44LnKpu760g/K7psVNrKwqJG6C/8RAcgISWQQKBgQDop7BaKGhK
+ 1QMJJ/vnlyYFTucfGLn6bM//pzTys5Gop0tpcfX/Hf6a6Dd+zBhmC3tBmhr80XOX/PiyAIbc0lOI
+ 31rafZuD/oVx5mlIySWX35EqS14LXmdVs/5vOhsInNgNiE+EPFf1L9YZgG/zA7OUBmqtTeYIPDVC
+ 7ViJcydItQKBgQDFmK0H0IA6W4opGQo+zQKhefooqZ+RDk9IIZMPOAtnvOM7y3rSVrfsSjzYVuMS
+ w/RP/vs7rwhaZejnCZ8/7uIqwg4sdUBRzZYR3PRNFeheW+BPZvb+2keRCGzOs7xkbF1mu54qtYTa
+ HZGZj1OsD83AoMwVLcdLDgO1kw32dkS8IQKBgFRdgoifAHqqVah7VFB9se7Y1tyi5cXWsXI+Wufr
+ j9U9nQ4GojK52LqpnH4hWnOelDqMvF6TQTyLIk/B+yWWK26Ft/dk9wDdSdystd8L+dLh4k0Y+Whb
+ +lLMq2YABw+PeJUnqdYE38xsZVHoDjBsVjFGRmbDybeQxauYT7PACy3FAoGBAK2+k9bdNQMbXp7I
+ j8OszHVkJdz/WXlY1cmdDAxDwXOUGVKIlxTAf7TbiijILZ5gg0Cb+hj+zR9/oI0WXtr+mAv02jWp
+ W8cSOLS4TnBBpTLjIpdu+BwbnvYeLF6MmEjNKEufCXKQbaLEgTQ/XNlchBSuzwSIXkbWqdhM1+gx
+ EjtBAoGARAdMIiDMPWIIZg3nNnFebbmtBP0qiBsYohQZ+6i/8s/vautEHBEN6Q0brIU/goo+nTHc
+ t9VaOkzjCmAJSLPUanuBC8pdYgLu5J20NXUZLD9AE/2bBT3OpezKcdYeI2jqoc1qlWHlNtVtdqQ2
+ AcZSFJQjdg5BTyvdEDhaYUKGdRw=
+ -----END PRIVATE KEY-----
+ ```
+
+1. Save the file and [restart] GitLab for the changes to take effect.
+
+
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 0d03b481881..1f67e8f5744 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -14,7 +14,7 @@ Managing a large number of users in GitLab can become a burden for system admini
In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems.
-GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](#group-syncing-ee) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab.
+GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](../how_to_configure_ldap_gitlab_ee/index.html#group-sync) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab.
## Getting started
@@ -111,7 +111,7 @@ The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb`
The two Active Directory specific values are `active_directory: true` and `uid: 'sAMAccountName'`. `sAMAccountName` is an attribute returned by Active Directory used for GitLab usernames. See the example output from `ldapsearch` for a full list of attributes a "person" object (user) has in **AD** - [`ldapsearch` example](#using-ldapsearch-unix)
-> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](#gitlab-enterprise-edition---ldap-features)
+> Both group_base and admin_group configuration options are only available in GitLab Enterprise Edition. See [GitLab EE - LDAP Features](../how_to_configure_ldap_gitlab_ee/index.html#gitlab-enterprise-edition---ldap-features) **[STARTER ONLY]**
### Example `gitlab.rb` LDAP
@@ -145,7 +145,7 @@ Securing LDAP (enabling LDAPS) on Windows Server 2012 involves installing a vali
> By default a LDAP service listens for connections on TCP and UDP port 389. LDAPS (LDAP over SSL) listens on port 636
-### Testing you AD server
+### Testing your AD server
#### Using **AdFind** (Windows)
@@ -267,4 +267,4 @@ have extended functionalities with LDAP, such as:
- Updating user permissions
- Multiple LDAP servers
-Read through the article on [LDAP for GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/) for an overview.
+Read through the article on [LDAP for GitLab EE](../how_to_configure_ldap_gitlab_ee/index.md) **[STARTER ONLY]** for an overview.
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/admin_group.png b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/admin_group.png
new file mode 100644
index 00000000000..9896379d669
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/admin_group.png
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_link_final.png b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_link_final.png
new file mode 100644
index 00000000000..21fb5a7d0ce
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_link_final.png
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif
new file mode 100644
index 00000000000..d35cf55804f
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif
new file mode 100644
index 00000000000..29b28df1cbd
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/multi_login.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/multi_login.gif
new file mode 100644
index 00000000000..d317add9837
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/multi_login.gif
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
new file mode 100644
index 00000000000..4d82a7370bb
--- /dev/null
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
@@ -0,0 +1,119 @@
+---
+author: Chris Wilson
+author_gitlab: MrChrisW
+level: intermediary
+article_type: admin guide
+date: 2017-05-03
+---
+
+# How to configure LDAP with GitLab EE **[STARTER ONLY]**
+
+## Introduction
+
+The present article follows [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward.
+
+## GitLab Enterprise Edition - LDAP features
+
+[GitLab Enterprise Edition (EE)](https://about.gitlab.com/pricing/) has a number of advantages when it comes to integrating with Active Directory (LDAP):
+
+- [Administrator Sync](../ldap-ee.md#administrator-sync): As an extension of group sync, you can automatically manage your global GitLab administrators. Specify a group CN for `admin_group` and all members of the LDAP group will be given administrator privileges.
+- [Group Sync](#group-sync): This allows GitLab group membership to be automatically updated based on LDAP group members.
+- [Multiple LDAP servers](#multiple-ldap-servers): The ability to configure multiple LDAP servers. This is useful if an organization has different LDAP servers within departments. This is not designed for failover. We're working on [supporting LDAP failover](https://gitlab.com/gitlab-org/gitlab-ee/issues/139) in GitLab.
+
+- Daily user synchronization: Once a day, GitLab will run a synchronization to check and update GitLab users against LDAP. This process updates all user details automatically.
+
+On the following section, you'll find a description for each of these features. Read through [LDAP GitLab EE docs](../ldap-ee.md) for complementary information.
+
+![GitLab OU Structure](img/admin_group.png)
+
+All members of the group `Global Admins` will be given **administrator** access to GitLab, allowing them to view the `/admin` dashboard.
+
+### Group Sync
+
+Group syncing allows AD (LDAP) groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that will be associated with [GitLab groups](../../../user/group/index.md).
+
+#### Creating group links - example
+
+As an example, let's suppose we have a "UKGov" GitLab group, which deals with confidential government information. Therefore, it is important that users of this group are given the correct permissions to projects contained within the group. Granular group permissions can be applied based on the AD group.
+
+**UK Developers** of our "UKGov" group are given **"developer"** permissions.
+
+_The developer permission allows the development staff to effectively manage all project code, issues, and merge requests._
+
+**UK Support** staff of our "UKGov" group are given **"reporter"** permissions.
+
+_The reporter permission allows support staff to manage issues, labels, and review project code._
+
+**US People Ops** of our "UKGov" group are given **"guest"** permissions.
+
+![Creating group links](img/group_linking.gif)
+
+> Guest permissions allows people ops staff to review and lodge new issues while allowing no read or write access to project code or [confidential issues](../../../user/project/issues/confidential_issues.md#permissions-and-access-to-confidential-issues) created by other users.
+
+See the [permission list](../../../user/permissions.md) for complementary info.
+
+#### Group permissions - example
+
+Considering the previous example, our staff will have
+access to our GitLab instance with the following structure:
+
+![GitLab OU Structure](img/group_link_final.png)
+
+Using this permission structure in our example allows only UK staff access to sensitive information stored in the projects code, while still allowing other teams to work effectively. As all permissions are controlled via AD groups new users can be quickly added to existing groups. New group members will then automatically inherit the required permissions.
+
+> [More information](../ldap-ee.md#group-sync) on group syncing.
+
+### Updating user permissions - new feature
+
+Since GitLab [v8.15](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/822) LDAP user permissions can now be manually overridden by an admin user. To override a user's permissions visit the groups **Members** page and select **Edit permissions**.
+
+![Setting manual permissions](img/manual_permissions.gif)
+
+### Multiple LDAP servers
+
+GitLab EE can support multiple LDAP servers. Simply configure another server in the `gitlab.rb` file within the `ldap_servers` block. In the example below we configure a new secondary server with the label **GitLab Secondary AD**. This is shown on the GitLab login screen. Large enterprises often utilize multiple LDAP servers for segregating organizational departments.
+
+![Multiple LDAP Servers Login](img/multi_login.gif)
+
+Considering the example illustrated on the image above,
+our `gitlab.rb` configuration would look like:
+
+```ruby
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = {
+'main' => {
+ 'label' => 'GitLab AD',
+ 'host' => 'ad.example.org',
+ 'port' => 636,
+ 'uid' => 'sAMAccountName',
+ 'method' => 'ssl',
+ 'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=org',
+ 'password' => 'Password1',
+ 'active_directory' => true,
+ 'base' => 'OU=GitLab INT,DC=GitLab,DC=org',
+ 'group_base' => 'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org',
+ 'admin_group' => 'Global Admins'
+ },
+
+'secondary' => {
+ 'label' => 'GitLab Secondary AD',
+ 'host' => 'ad-secondary.example.net',
+ 'port' => 636,
+ 'uid' => 'sAMAccountName',
+ 'method' => 'ssl',
+ 'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=com',
+ 'password' => 'Password1',
+ 'active_directory' => true,
+ 'base' => 'OU=GitLab Secondary,DC=GitLab,DC=com',
+ 'group_base' => 'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=com',
+ 'admin_group' => 'Global Admins'
+ }
+}
+```
+
+## Conclusion
+
+Integration of GitLab with Active Directory (LDAP) reduces the complexity of user management.
+It has the advantage of improving user permission controls, whilst easing the deployment of GitLab into an existing [IT environment](https://www.techopedia.com/definition/29199/it-infrastructure). GitLab EE offers advanced group management and multiple LDAP servers.
+
+With the assistance of the [GitLab Support](https://about.gitlab.com/support) team, setting up GitLab with an existing AD/LDAP solution will be a smooth and painless process.
diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_1.png b/doc/administration/auth/img/google_secure_ldap_add_step_1.png
new file mode 100644
index 00000000000..fd254443d75
--- /dev/null
+++ b/doc/administration/auth/img/google_secure_ldap_add_step_1.png
Binary files differ
diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_2.png b/doc/administration/auth/img/google_secure_ldap_add_step_2.png
new file mode 100644
index 00000000000..611a21ae03c
--- /dev/null
+++ b/doc/administration/auth/img/google_secure_ldap_add_step_2.png
Binary files differ
diff --git a/doc/administration/auth/img/google_secure_ldap_client_settings.png b/doc/administration/auth/img/google_secure_ldap_client_settings.png
new file mode 100644
index 00000000000..3c0b3f3d4bd
--- /dev/null
+++ b/doc/administration/auth/img/google_secure_ldap_client_settings.png
Binary files differ
diff --git a/doc/administration/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md
new file mode 100644
index 00000000000..30095d35705
--- /dev/null
+++ b/doc/administration/auth/ldap-ee.md
@@ -0,0 +1,557 @@
+# LDAP Additions in GitLab EE **[STARTER ONLY]**
+
+This is a continuation of the main [LDAP documentation](ldap.md), detailing LDAP
+features specific to GitLab Enterprise Edition Starter, Premium and Ultimate.
+
+## Overview
+
+[LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
+stands for **Lightweight Directory Access Protocol**, which
+is a standard application protocol for
+accessing and maintaining distributed directory information services
+over an Internet Protocol (IP) network.
+
+GitLab integrates with LDAP to support **user authentication**. This integration
+works with most LDAP-compliant directory servers, including Microsoft Active
+Directory, Apple Open Directory, Open LDAP, and 389 Server.
+**GitLab Enterprise Edition** includes enhanced integration, including group
+membership syncing.
+
+## Use cases
+
+- User sync: Once a day, GitLab will update users against LDAP
+- Group sync: Once an hour, GitLab will update group membership
+ based on LDAP group members
+
+## Multiple LDAP servers
+
+With GitLab Enterprise Edition Starter, you can configure multiple LDAP servers
+that your GitLab instance will connect to.
+
+To add another LDAP server, you can start by duplicating the settings under
+[the main configuration](ldap.md#configuration) and edit them to match the
+additional LDAP server.
+
+Be sure to choose a different provider ID made of letters a-z and numbers 0-9.
+This ID will be stored in the database so that GitLab can remember which LDAP
+server a user belongs to.
+
+## User sync
+
+Once per day, GitLab will run a worker to check and update GitLab
+users against LDAP.
+
+The process will execute the following access checks:
+
+1. Ensure the user is still present in LDAP
+1. If the LDAP server is Active Directory, ensure the user is active (not
+ blocked/disabled state). This will only be checked if
+ `active_directory: true` is set in the LDAP configuration [^1]
+
+The user will be set to `ldap_blocked` state in GitLab if the above conditions
+fail. This means the user will not be able to login or push/pull code.
+
+The process will also update the following user information:
+
+1. Email address
+1. If `sync_ssh_keys` is set, SSH public keys
+1. If Kerberos is enabled, Kerberos identity
+
+NOTE: **Note:**
+The LDAP sync process updates existing users while new users will
+be created on first sign in.
+
+## Group Sync
+
+If your LDAP supports the `memberof` property, when the user signs in for the
+first time GitLab will trigger a sync for groups the user should be a member of.
+That way they don't need to wait for the hourly sync to be granted
+access to their groups and projects.
+
+In GitLab Premium, we can also add a GitLab group to sync with one or multiple LDAP groups or we can
+also add a filter. The filter must comply with the syntax defined in [RFC 2254](https://tools.ietf.org/search/rfc2254).
+
+A group sync process will run every hour on the hour, and `group_base` must be set
+in LDAP configuration for LDAP synchronizations based on group CN to work. This allows
+GitLab group membership to be automatically updated based on LDAP group members.
+
+The `group_base` configuration should be a base LDAP 'container', such as an
+'organization' or 'organizational unit', that contains LDAP groups that should
+be available to GitLab. For example, `group_base` could be
+`ou=groups,dc=example,dc=com`. In the config file it will look like the
+following.
+
+**Omnibus configuration**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## Base where we can search for groups
+ ##
+ ## Ex. ou=groups,dc=gitlab,dc=example
+ ##
+ ##
+ group_base: ou=groups,dc=example,dc=com
+ EOS
+ ```
+
+1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
+
+**Source configuration**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
+
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ group_base: ou=groups,dc=example,dc=com
+ ```
+
+1. [Restart GitLab][restart] for the changes to take effect.
+
+---
+
+To take advantage of group sync, group owners or maintainers will need to create an
+LDAP group link in their group **Settings > LDAP Groups** page. Multiple LDAP
+groups and/or filters can be linked with a single GitLab group. When the link is
+created, an access level/role is specified (Guest, Reporter, Developer, Maintainer,
+or Owner).
+
+## Administrator sync
+
+As an extension of group sync, you can automatically manage your global GitLab
+administrators. Specify a group CN for `admin_group` and all members of the
+LDAP group will be given administrator privileges. The configuration will look
+like the following.
+
+NOTE: **Note:**
+Administrators will not be synced unless `group_base` is also
+specified alongside `admin_group`. Also, only specify the CN of the admin
+group, as opposed to the full DN.
+
+**Omnibus configuration**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## Base where we can search for groups
+ ##
+ ## Ex. ou=groups,dc=gitlab,dc=example
+ ##
+ ##
+ group_base: ou=groups,dc=example,dc=com
+
+ ##
+ ## The CN of a group containing GitLab administrators
+ ##
+ ## Ex. administrators
+ ##
+ ## Note: Not `cn=administrators` or the full DN
+ ##
+ ##
+ admin_group: my_admin_group
+
+ EOS
+ ```
+
+1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
+
+**Source configuration**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
+
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ group_base: ou=groups,dc=example,dc=com
+ admin_group: my_admin_group
+ ```
+
+1. [Restart GitLab][restart] for the changes to take effect.
+
+## Adjusting LDAP user sync schedule
+
+> Introduced in GitLab Enterprise Edition Starter.
+
+NOTE: **Note:**
+These are cron formatted values. You can use a crontab generator to create
+these values, for example http://www.crontabgenerator.com/.
+
+By default, GitLab will run a worker once per day at 01:30 a.m. server time to
+check and update GitLab users against LDAP.
+
+You can manually configure LDAP user sync times by setting the
+following configuration values. The example below shows how to set LDAP user
+sync to run once every 12 hours at the top of the hour.
+
+**Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_sync_worker_cron'] = "0 */12 * * *"
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+**Source installations**
+
+1. Edit `config/gitlab.yaml`:
+
+ ```yaml
+ cron_jobs:
+ ldap_sync_worker_cron:
+ "0 */12 * * *"
+ ```
+
+1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+## Adjusting LDAP group sync schedule
+
+NOTE: **Note:**
+These are cron formatted values. You can use a crontab generator to create
+these values, for example http://www.crontabgenerator.com/.
+
+By default, GitLab will run a group sync process every hour, on the hour.
+
+CAUTION: **Important:**
+It's recommended that you do not run too short intervals as this
+could lead to multiple syncs running concurrently. This is primarily a concern
+for installations with a large number of LDAP users. Please review the
+[LDAP group sync benchmark metrics](#benchmarks) to see how
+your installation compares before proceeding.
+
+You can manually configure LDAP group sync times by setting the
+following configuration values. The example below shows how to set group
+sync to run once every 2 hours at the top of the hour.
+
+**Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_group_sync_worker_cron'] = "0 */2 * * * *"
+ ```
+
+1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+**Source installations**
+
+1. Edit `config/gitlab.yaml`:
+
+ ```yaml
+ cron_jobs:
+ ldap_group_sync_worker_cron:
+ "*/30 * * * *"
+ ```
+
+1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+## External groups
+
+> Introduced in GitLab Enterprise Edition Starter 8.9.
+
+Using the `external_groups` setting will allow you to mark all users belonging
+to these groups as [external users](../../user/permissions.md#external-users-permissions).
+Group membership is checked periodically through the `LdapGroupSync` background
+task.
+
+**Omnibus configuration**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## An array of CNs of groups containing users that should be considered external
+ ##
+ ## Ex. ['interns', 'contractors']
+ ##
+ ## Note: Not `cn=interns` or the full DN
+ ##
+ external_groups: ['interns', 'contractors']
+ EOS
+ ```
+
+1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
+
+**Source configuration**
+
+1. Edit `config/gitlab.yaml`:
+
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ external_groups: ['interns', 'contractors']
+ ```
+
+1. [Restart GitLab][restart] for the changes to take effect.
+
+## Group sync technical details
+
+There is a lot going on with group sync 'under the hood'. This section
+outlines what LDAP queries are executed and what behavior you can expect
+from group sync.
+
+Group member access will be downgraded from a higher level if their LDAP group
+membership changes. For example, if a user has 'Owner' rights in a group and the
+next group sync reveals they should only have 'Developer' privileges, their
+access will be adjusted accordingly. The only exception is if the user is the
+*last* owner in a group. Groups need at least one owner to fulfill
+administrative duties.
+
+### Supported LDAP group types/attributes
+
+GitLab supports LDAP groups that use member attributes `member`, `submember`,
+`uniquemember`, `memberof` and `memberuid`. This means group sync supports, at
+least, LDAP groups with object class `groupOfNames`, `posixGroup`, and
+`groupOfUniqueName`. Other object classes should work fine as long as members
+are defined as one of the mentioned attributes. This also means GitLab supports
+Microsoft Active Directory, Apple Open Directory, Open LDAP, and 389 Server.
+Other LDAP servers should work, too.
+
+Active Directory also supports nested groups. Group sync will recursively
+resolve membership if `active_directory: true` is set in the configuration file.
+
+> **Note:** Nested group membership will only be resolved if the nested group
+ also falls within the configured `group_base`. For example, if GitLab sees a
+ nested group with DN `cn=nested_group,ou=special_groups,dc=example,dc=com` but
+ the configured `group_base` is `ou=groups,dc=example,dc=com`, `cn=nested_group`
+ will be ignored.
+
+### Queries
+
+- Each LDAP group is queried a maximum of one time with base `group_base` and
+ filter `(cn=<cn_from_group_link>)`.
+- If the LDAP group has the `memberuid` attribute, GitLab will execute another
+ LDAP query per member to obtain each user's full DN. These queries are
+ executed with base `base`, scope 'base object', and a filter depending on
+ whether `user_filter` is set. Filter may be `(uid=<uid_from_group>)` or a
+ joining of `user_filter`.
+
+### Benchmarks
+
+Group sync was written to be as performant as possible. Data is cached, database
+queries are optimized, and LDAP queries are minimized. The last benchmark run
+revealed the following metrics:
+
+For 20,000 LDAP users, 11,000 LDAP groups and 1,000 GitLab groups with 10
+LDAP group links each:
+
+- Initial sync (no existing members assigned in GitLab) took 1.8 hours
+- Subsequent syncs (checking membership, no writes) took 15 minutes
+
+These metrics are meant to provide a baseline and performance may vary based on
+any number of factors. This was a pretty extreme benchmark and most instances will
+not have near this many users or groups. Disk speed, database performance,
+network and LDAP server response time will affect these metrics.
+
+## Troubleshooting
+
+### Referral error
+
+If you see `LDAP search error: Referral` in the logs, or when troubleshooting
+LDAP Group Sync, this error may indicate a configuration problem. The LDAP
+configuration `/etc/gitlab/gitlab.rb` (Omnibus) or `config/gitlab.yml` (source)
+is in YAML format and is sensitive to indentation. Check that `group_base` and
+`admin_group` configuration keys are indented 2 spaces past the server
+identifier. The default identifier is `main` and an example snippet looks like
+the following:
+
+```yaml
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ label: 'LDAP'
+ host: 'ldap.example.com'
+ ...
+ group_base: 'cn=my_group,ou=groups,dc=example,dc=com'
+ admin_group: 'my_admin_group'
+```
+
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
+
+[^1]: In Active Directory, a user is marked as disabled/blocked if the user
+ account control attribute (`userAccountControl:1.2.840.113556.1.4.803`)
+ has bit 2 set. See https://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
+ for more information.
+
+### User DN has changed
+
+When an LDAP user is created in GitLab, their LDAP DN is stored for later reference.
+
+If GitLab cannot find a user by their DN, it will attempt to fallback
+to finding the user by their email. If the lookup is successful, GitLab will
+update the stored DN to the new value.
+
+### User is not being added to a group
+
+Sometimes you may think a particular user should be added to a GitLab group via
+LDAP group sync, but for some reason it's not happening. There are several
+things to check to debug the situation.
+
+- Ensure LDAP configuration has a `group_base` specified. This configuration is
+ required for group sync to work properly.
+- Ensure the correct LDAP group link is added to the GitLab group. Check group
+ links by visiting the GitLab group, then **Settings dropdown -> LDAP groups**.
+- Check that the user has an LDAP identity
+ 1. Sign in to GitLab as an administrator user.
+ 1. Navigate to **Admin area -> Users**.
+ 1. Search for the user
+ 1. Open the user, by clicking on their name. Do not click 'Edit'.
+ 1. Navigate to the **Identities** tab. There should be an LDAP identity with
+ an LDAP DN as the 'Identifier'.
+
+If all of the above looks good, jump in to a little more advanced debugging.
+Often, the best way to learn more about why group sync is behaving a certain
+way is to enable debug logging. There is verbose output that details every
+step of the sync.
+
+1. Start a Rails console
+
+ ```bash
+ # For Omnibus installations
+ sudo gitlab-rails console
+
+ # For installations from source
+ sudo -u git -H bundle exec rails console production
+ ```
+1. Set the log level to debug (only for this session):
+
+ ```ruby
+ Rails.logger.level = Logger::DEBUG
+ ```
+1. Choose a GitLab group to test with. This group should have an LDAP group link
+ already configured. If the output is `nil`, the group could not be found.
+ If a bunch of group attributes are output, your group was found successfully.
+
+ ```ruby
+ group = Group.find_by(name: 'my_group')
+
+ # Output
+ => #<Group:0x007fe825196558 id: 1234, name: "my_group"...>
+ ```
+1. Run a group sync for this particular group.
+
+ ```ruby
+ EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
+ ```
+1. Look through the output of the sync. See [example log output](#example-log-output)
+ below for more information about the output.
+1. If you still aren't able to see why the user isn't being added, query the
+ LDAP group directly to see what members are listed. Still in the Rails console,
+ run the following query:
+
+ ```ruby
+ adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
+ ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
+
+ # Output
+ => #<EE::Gitlab::Auth::LDAP::Group:0x007fcbdd0bb6d8
+ ```
+1. Query the LDAP group's member DNs and see if the user's DN is in the list.
+ One of the DNs here should match the 'Identifier' from the LDAP identity
+ checked earlier. If it doesn't, the user does not appear to be in the LDAP
+ group.
+
+ ```ruby
+ ldap_group.member_dns
+
+ # Output
+ => ["uid=john,ou=people,dc=example,dc=com", "uid=mary,ou=people,dc=example,dc=com"]
+ ```
+1. Some LDAP servers don't store members by DN. Rather, they use UIDs instead.
+ If you didn't see results from the last query, try querying by UIDs instead.
+
+ ```ruby
+ ldap_group.member_uids
+
+ # Output
+ => ['john','mary']
+ ```
+
+#### Example log output
+
+The output of the last command will be very verbose, but contains lots of
+helpful information. For the most part you can ignore log entries that are SQL
+statements.
+
+Indicates the point where syncing actually begins:
+
+```bash
+Started syncing all providers for 'my_group' group
+```
+
+The follow entry shows an array of all user DNs GitLab sees in the LDAP server.
+Note that these are the users for a single LDAP group, not a GitLab group. If
+you have multiple LDAP groups linked to this GitLab group, you will see multiple
+log entries like this - one for each LDAP group. If you don't see an LDAP user
+DN in this log entry, LDAP is not returning the user when we do the lookup.
+Verify the user is actually in the LDAP group.
+
+```bash
+Members in 'ldap_group_1' LDAP group: ["uid=john0,ou=people,dc=example,dc=com",
+"uid=mary0,ou=people,dc=example,dc=com", "uid=john1,ou=people,dc=example,dc=com",
+"uid=mary1,ou=people,dc=example,dc=com", "uid=john2,ou=people,dc=example,dc=com",
+"uid=mary2,ou=people,dc=example,dc=com", "uid=john3,ou=people,dc=example,dc=com",
+"uid=mary3,ou=people,dc=example,dc=com", "uid=john4,ou=people,dc=example,dc=com",
+"uid=mary4,ou=people,dc=example,dc=com"]
+```
+
+Shortly after each of the above entries, you will see a hash of resolved member
+access levels. This hash represents all user DNs GitLab thinks should have
+access to this group, and at which access level (role). This hash is additive,
+and more DNs may be added, or existing entries modified, based on additional
+LDAP group lookups. The very last occurrence of this entry should indicate
+exactly which users GitLab believes should be added to the group.
+
+> **Note:** 10 is 'Guest', 20 is 'Reporter', 30 is 'Developer', 40 is 'Maintainer'
+ and 50 is 'Owner'
+
+```bash
+Resolved 'my_group' group member access: {"uid=john0,ou=people,dc=example,dc=com"=>30,
+"uid=mary0,ou=people,dc=example,dc=com"=>30, "uid=john1,ou=people,dc=example,dc=com"=>30,
+"uid=mary1,ou=people,dc=example,dc=com"=>30, "uid=john2,ou=people,dc=example,dc=com"=>30,
+"uid=mary2,ou=people,dc=example,dc=com"=>30, "uid=john3,ou=people,dc=example,dc=com"=>30,
+"uid=mary3,ou=people,dc=example,dc=com"=>30, "uid=john4,ou=people,dc=example,dc=com"=>30,
+"uid=mary4,ou=people,dc=example,dc=com"=>30}
+```
+
+It's not uncommon to see warnings like the following. These indicate that GitLab
+would have added the user to a group, but the user could not be found in GitLab.
+Usually this is not a cause for concern.
+
+If you think a particular user should already exist in GitLab, but you're seeing
+this entry, it could be due to a mismatched DN stored in GitLab. See
+[User DN has changed](#User-DN-has-changed) to update the user's LDAP identity.
+
+```bash
+User with DN `uid=john0,ou=people,dc=example,dc=com` should have access
+to 'my_group' group but there is no user in GitLab with that
+identity. Membership will be updated once the user signs in for
+the first time.
+```
+
+Finally, the following entry says syncing has finished for this group:
+
+```bash
+Finished syncing all providers for 'my_group' group
+```
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index d5d0d99ac24..54279897e04 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -14,7 +14,7 @@ including group membership syncing as well as multiple LDAP servers support.
The information on this page is relevant for both GitLab CE and EE. For more
details about EE-specific LDAP features, see the
-[LDAP Enterprise Edition documentation](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html).
+[LDAP Enterprise Edition documentation](ldap-ee.md). **[STARTER ONLY]**
## Security
@@ -30,16 +30,16 @@ the LDAP server.
### User deletion
-If a user is deleted from the LDAP server, they will be blocked in GitLab, as
+If a user is deleted from the LDAP server, they will be blocked in GitLab as
well. Users will be immediately blocked from logging in. However, there is an
-LDAP check cache time (sync time) of one hour (see note). This means users that
+LDAP check cache time of one hour (see note) which means users that
are already logged in or are using Git over SSH will still be able to access
GitLab for up to one hour. Manually block the user in the GitLab Admin area to
immediately block all access.
NOTE: **Note**:
GitLab Enterprise Edition Starter supports a
-[configurable sync time](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#adjusting-ldap-user-and-group-sync-schedules),
+[configurable sync time](ldap-ee.md#adjusting-ldap-user-sync-schedule),
with a default of one hour.
## Git password authentication
@@ -48,6 +48,14 @@ LDAP-enabled users can always authenticate with Git using their GitLab username
or email and LDAP password, even if password authentication for Git is disabled
in the application settings.
+## Google Secure LDAP **[CORE ONLY]**
+
+> Introduced in GitLab 11.9.
+
+[Google Cloud Identity](https://cloud.google.com/identity/) provides a Secure
+LDAP service that can be configured with GitLab for authentication and group sync.
+See [Google Secure LDAP](google_secure_ldap.md) for detailed configuration instructions.
+
## Configuration
NOTE: **Note**:
@@ -56,6 +64,7 @@ to connect to one GitLab server.
For a complete guide on configuring LDAP with GitLab Community Edition, please check
the admin guide [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md).
+For GitLab Enterprise Editions, see also [How to configure LDAP with GitLab EE](how_to_configure_ldap_gitlab_ee/index.md). **[STARTER ONLY]**
To enable LDAP integration you need to add your LDAP server settings in
`/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml` for Omnibus
@@ -81,6 +90,9 @@ library. `tls` corresponds to StartTLS, not to be confused with regular TLS.
Normally, if you specify `ssl` it will be on port 636, while `tls` (StartTLS)
would be on port 389. `plain` also operates on port 389.
+NOTE: **Note:**
+LDAP users must have an email address set, regardless of whether it is used to log in.
+
**Omnibus configuration**
```ruby
@@ -136,14 +148,54 @@ main:
##
verify_certificates: true
- ##
- ## Specifies the SSL version for OpenSSL to use, if the OpenSSL default
- ## is not appropriate.
- ##
- ## Example: 'TLSv1_1'
- ##
- ##
- ssl_version: ''
+ # OpenSSL::SSL::SSLContext options.
+ tls_options:
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_file: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
+ # Specific SSL ciphers to use in communication with LDAP servers.
+ #
+ # Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2'
+ ciphers: ''
+
+ # Client certificate
+ #
+ # Example:
+ # cert: |
+ # -----BEGIN CERTIFICATE-----
+ # MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ
+ # bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE
+ # CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4
+ # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
+ # ...
+ # 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80
+ # Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg==
+ # -----END CERTIFICATE -----
+ cert: ''
+
+ # Client private key
+ # key: |
+ # -----BEGIN PRIVATE KEY-----
+ # MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6
+ # bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN
+ # 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C
+ # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl
+ # ...
+ # +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9
+ # l6RG+a/mW+0rCWn8JAd464Ps9hE=
+ # -----END PRIVATE KEY-----
+ key: ''
##
## Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
@@ -337,7 +389,7 @@ group, you can use the following syntax:
Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
<https://docs.microsoft.com/en-us/windows/desktop/ADSI/search-filter-syntax>. Support for
nested members in the user filter should not be confused with
-[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes).
+[group sync nested groups support](ldap-ee.md#supported-ldap-group-typesattributes). **[STARTER ONLY]**
Please note that GitLab does not support the custom filter syntax used by
omniauth-ldap.
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
new file mode 100644
index 00000000000..df4f22aa3e7
--- /dev/null
+++ b/doc/administration/auth/oidc.md
@@ -0,0 +1,140 @@
+# OpenID Connect OmniAuth provider
+
+GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider.
+
+To enable the OpenID Connect OmniAuth provider, you must register your application with an OpenID Connect provider.
+The OpenID Connect will provide you with a client details and secret for you to use.
+
+1. On your GitLab server, open the configuration file.
+
+ For Omnibus GitLab:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+ See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration.
+
+ For Omnibus GitLab:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ { 'name' => 'openid_connect',
+ 'label' => '<your_oidc_label>',
+ 'args' => {
+ "name' => 'openid_connect',
+ 'scope' => ['openid','profile'],
+ 'response_type' => 'code',
+ 'issuer' => '<your_oidc_url>',
+ 'discovery' => true,
+ 'client_auth_method' => 'query',
+ 'uid_field' => '<uid_field>',
+ 'client_options' => {
+ 'identifier' => '<your_oidc_client_id>',
+ 'secret' => '<your_oidc_client_secret>',
+ 'redirect_uri' => '<your_gitlab_url>/users/auth/openid_connect/callback'
+ }
+ }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```yaml
+ - { name: 'openid_connect',
+ label: '<your_oidc_label>',
+ args: {
+ name: 'openid_connect',
+ scope: ['openid','profile'],
+ response_type: 'code',
+ issuer: '<your_oidc_url>',
+ discovery: true,
+ client_auth_method: 'query',
+ uid_field: '<uid_field>',
+ client_options: {
+ identifier: '<your_oidc_client_id>',
+ secret: '<your_oidc_client_secret>',
+ redirect_uri: '<your_gitlab_url>/users/auth/openid_connect/callback'
+ }
+ }
+ }
+ ```
+
+ > **Note:**
+ >
+ > - For more information on each configuration option refer to
+ the [OmniAuth OpenID Connect usage documentation](https://github.com/m0n9oose/omniauth_openid_connect#usage) and
+ the [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html).
+
+1. For the configuration above, change the values for the provider to match your OpenID Connect client setup. Use the following as a guide:
+ - `<your_oidc_label>` is the label that will be displayed on the login page.
+ - `<your_oidc_url>` (optional) is the URL that points to the OpenID Connect provider. For example, `https://example.com/auth/realms/your-realm`.
+ If this value is not provided, the URL is constructed from the `client_options` in the following format: `<client_options.scheme>://<client_options.host>:<client_options.port>`.
+ - If `discovery` is set to `true`, the OpenID Connect provider will try to auto discover the client options using `<your_oidc_url>/.well-known/openid-configuration`. Defaults to `false`.
+ - `<uid_field>` (optional) is the field name from the `user_info` details that will be used as `uid` value. For example, `preferred_username`.
+ If this value is not provided or the field with the configured value is missing from the `user_info` details, the `uid` will use the `sub` field.
+ - `client_options` are the OpenID Connect client-specific options. Specifically:
+
+ - `identifier` is the client identifier as configured in the OpenID Connect service provider.
+ - `secret` is the client secret as configured in the OpenID Connect service provider.
+ - `redirect_uri` is the GitLab URL to redirect the user to after successful login. For example, `http://example.com/users/auth/openid_connect/callback`.
+ - `end_session_endpoint` (optional) is the URL to the endpoint that end the session (logout). Can be provided if auto-discovery disabled or unsuccessful.
+
+ The following `client_options` are optional unless auto-discovery is disabled or unsuccessful:
+
+ - `authorization_endpoint` is the URL to the endpoint that authorizes the end user.
+ - `token_endpoint` is the URL to the endpoint that provides Access Token.
+ - `userinfo_endpoint` is the URL to the endpoint that provides the user information.
+ - `jwks_uri` is the URL to the endpoint where the Token signer publishes its keys.
+
+1. Save the configuration file.
+1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
+ for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+
+On the sign in page, there should now be an OpenID Connect icon below the regular sign in form.
+Click the icon to begin the authentication process. The OpenID Connect provider will ask the user to
+sign in and authorize the GitLab application (if confirmation required by the client). If everything goes well, the user
+will be redirected to GitLab and will be signed in.
+
+## Example configurations
+
+The following configurations illustrate how to set up OpenID with
+different providers with Omnibus GitLab.
+
+### Google
+
+See the [Google
+documentation](https://developers.google.com/identity/protocols/OpenIDConnect)
+for more details:
+
+```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ 'name' => 'openid_connect',
+ 'label' => 'Google OpenID',
+ 'args' => {
+ 'name' => 'openid_connect',
+ 'scope' => ['openid', 'profile', 'email'],
+ 'response_type' => 'code',
+ 'issuer' => 'https://accounts.google.com',
+ 'client_auth_method' => 'query',
+ 'discovery' => true,
+ 'uid_field' => 'preferred_username',
+ 'client_options' => {
+ 'identifier' => '<YOUR PROJECT CLIENT ID>',
+ 'secret' => '<YOUR PROJECT CLIENT SECRET>',
+ 'redirect_uri' => 'https://example.com/users/auth/openid_connect/callback',
+ }
+ }
+ }
+```
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index 3136923fa96..aa4e1b0d2e0 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -1,6 +1,6 @@
# Okta SSO provider
-Okta is a [Single Sign-on provider][okta-sso] that can be used to authenticate
+Okta is a [Single Sign-on provider](https://www.okta.com/products/single-sign-on/) that can be used to authenticate
with GitLab.
The following documentation enables Okta as a SAML provider.
@@ -92,18 +92,23 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
1. Add the provider configuration.
>**Notes:**
+ >
>- Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
installation to generate the correct value).
+ >
>- To get the `idp_cert_fingerprint` fingerprint, first download the
certificate from the Okta app you registered and then run:
`openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
with the location of your certificate.
+ >
>- Change the value of `idp_sso_target_url`, with the value of the
**Identity Provider Single Sign-On URL** from the step when you
configured the Okta app.
- >- Change the value of `issuer` to a unique name, which will identify the application
+ >
+ >- Change the value of `issuer` to the value of the **Audience Restriction** from your Okta app configuration. This will identify GitLab
to the IdP.
+ >
>- Leave `name_identifier_format` as-is.
**For Omnibus GitLab installations**
@@ -140,7 +145,7 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
}
```
-1. [Reconfigure][reconf] or [restart] GitLab for Omnibus and installations
+1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart](../restart_gitlab.md#installations-from-source) GitLab for Omnibus and installations
from source respectively for the changes to take effect.
You might want to try this out on an incognito browser window.
@@ -150,10 +155,5 @@ You might want to try this out on an incognito browser window.
>**Note:**
Make sure the groups exist and are assigned to the Okta app.
-You can take a look of the [SAML documentation][saml] on external groups since
+You can take a look of the [SAML documentation](../../integration/saml.md#marking-users-as-external-based-on-saml-groups) on external groups since
it works the same.
-
-[okta-sso]: https://www.okta.com/products/single-sign-on/
-[saml]: ../../integration/saml.md#external-groups
-[reconf]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart]: ../restart_gitlab.md#installations-from-source
diff --git a/doc/administration/auth/smartcard.md b/doc/administration/auth/smartcard.md
new file mode 100644
index 00000000000..b33c5359b44
--- /dev/null
+++ b/doc/administration/auth/smartcard.md
@@ -0,0 +1,186 @@
+# Smartcard authentication **[PREMIUM ONLY]**
+
+GitLab supports authentication using smartcards.
+
+## Authentication methods
+
+GitLab supports two authentication methods:
+
+- X.509 certificates with local databases.
+- LDAP servers.
+
+### Authentication against a local database with X.509 certificates
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/726) in
+[GitLab Premium](https://about.gitlab.com/pricing/) 11.6 as an experimental
+feature. Smartcard authentication against local databases may change or be
+removed completely in future releases.
+
+Smartcards with X.509 certificates can be used to authenticate with GitLab.
+
+To use a smartcard with an X.509 certificate to authenticate against a local
+database with GitLab, `CN` and `emailAddress` must be defined in the
+certificate. For example:
+
+```
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 12856475246677808609 (0xb26b601ecdd555e1)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: O=Random Corp Ltd, CN=Random Corp
+ Validity
+ Not Before: Oct 30 12:00:00 2018 GMT
+ Not After : Oct 30 12:00:00 2019 GMT
+ Subject: CN=Gitlab User, emailAddress=gitlab-user@example.com
+```
+
+### Authentication against an LDAP server
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7693) in
+[GitLab Premium](https://about.gitlab.com/pricing/) 11.8 as an experimental
+feature. Smartcard authentication against an LDAP server may change or be
+removed completely in future releases.
+
+GitLab implements a standard way of certificate matching following
+[RFC4523](https://tools.ietf.org/html/rfc4523). It uses the
+`certificateExactMatch` certificate matching rule against the `userCertificate`
+attribute. As a prerequisite, you must use an LDAP server that:
+
+- Supports the `certificateExactMatch` matching rule.
+- Has the certificate stored in the `userCertificate` attribute.
+
+## Configure GitLab for smartcard authentication
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['smartcard_enabled'] = true
+ gitlab_rails['smartcard_ca_file'] = "/etc/ssl/certs/CA.pem"
+ gitlab_rails['smartcard_client_certificate_required_port'] = 3444
+ ```
+
+1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure)
+ GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Configure NGINX to request a client side certificate
+
+ In NGINX configuration, an **additional** server context must be defined with
+ the same configuration except:
+
+ - The additional NGINX server context must be configured to run on a different
+ port:
+
+ ```
+ listen *:3444 ssl;
+ ```
+
+ - The additional NGINX server context must be configured to require the client
+ side certificate:
+
+ ```
+ ssl_verify_depth 2;
+ ssl_client_certificate /etc/ssl/certs/CA.pem;
+ ssl_verify_client on;
+ ```
+
+ - The additional NGINX server context must be configured to forward the client
+ side certificate:
+
+ ```
+ proxy_set_header X-SSL-Client-Certificate $ssl_client_escaped_cert;
+ ```
+
+ For example, the following is an example server context in an NGINX
+ configuration file (eg. in `/etc/nginx/sites-available/gitlab-ssl`):
+
+ ```
+ server {
+ listen *:3444 ssl;
+
+ # certificate for configuring SSL
+ ssl_certificate /path/to/example.com.crt;
+ ssl_certificate_key /path/to/example.com.key;
+
+ ssl_verify_depth 2;
+ # CA certificate for client side certificate verification
+ ssl_client_certificate /etc/ssl/certs/CA.pem;
+ ssl_verify_client on;
+
+ location / {
+ proxy_set_header Host $http_host;
+ 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_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+
+ proxy_set_header X-SSL-Client-Certificate $ssl_client_escaped_cert;
+
+ proxy_read_timeout 300;
+
+ proxy_pass http://gitlab-workhorse;
+ }
+ }
+ ```
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ ## Smartcard authentication settings
+ smartcard:
+ # Allow smartcard authentication
+ enabled: true
+
+ # Path to a file containing a CA certificate
+ ca_file: '/etc/ssl/certs/CA.pem'
+
+ # Port where the client side certificate is requested by NGINX
+ client_certificate_required_port: 3444
+ ```
+
+1. Save the file and [restart](../restart_gitlab.md#installations-from-source)
+ GitLab for the changes to take effect.
+
+### Additional steps when authenticating against an LDAP server
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ # snip...
+ # Enable smartcard authentication against the LDAP server. Valid values
+ # are "false", "optional", and "required".
+ smartcard_auth: optional
+ EOS
+ ```
+
+1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure)
+ GitLab for the changes to take effect.
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ # Enable smartcard authentication against the LDAP server. Valid values
+ # are "false", "optional", and "required".
+ smartcard_auth: optional
+ ```
+
+1. Save the file and [restart](../restart_gitlab.md#installations-from-source)
+ GitLab for the changes to take effect.
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
index 623a5321f32..85ae2946bc8 100644
--- a/doc/administration/build_artifacts.md
+++ b/doc/administration/build_artifacts.md
@@ -1 +1,5 @@
+---
+redirect_to: 'job_artifacts.md'
+---
+
This document was moved to [job_artifacts](job_artifacts.md).
diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md
index 0414b3ec12e..246addb6dc9 100644
--- a/doc/administration/compliance.md
+++ b/doc/administration/compliance.md
@@ -6,13 +6,13 @@ GitLab’s [security features](../security/README.md) may also help you meet rel
|Feature |GitLab tier |GitLab.com |
| ---------| :--------: | :-------: |
-|**[Restrict SSH Keys](../README.html#administrator-documentation)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+||
-|**[Granular user roles and flexible permissions](../user/permissions.html)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓|
-|**[Enforce TOS acceptance](../user/admin_area/settings/terms.html)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+||
-|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.html)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
+|**[Restrict SSH Keys](../security/ssh_keys_restrictions.md)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+||
+|**[Granular user roles and flexible permissions](../user/permissions.md)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓|
+|**[Enforce TOS acceptance](../user/admin_area/settings/terms.md)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+||
+|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||
-|**[Lock project membership to group](../workflow/groups.html#lock-project-membership-to-members-of-this-group)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
-|**[LDAP group sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
-|**[LDAP group sync filters](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
-|**[Audit logs](https://docs.gitlab.com/ee/administration/audit_events.html)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
-|**[Auditor users](https://docs.gitlab.com/ee/administration/auditor_users.html)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+|| \ No newline at end of file
+|**[Lock project membership to group](../user/group/index.md#member-lock-starter)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
+|**[LDAP group sync](auth/ldap-ee.md#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
+|**[LDAP group sync filters](auth/ldap-ee.md#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
+|**[Audit logs](audit_events.md)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
+|**[Auditor users](auditor_users.md)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+||
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index a1ac4a2a57c..4d55f2357c1 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -1,6 +1,7 @@
# GitLab Container Registry administration
> **Notes:**
+>
> - [Introduced][ce-4040] in GitLab 8.8.
> - Container Registry manifest `v1` support was added in GitLab 8.9 to support
> Docker versions earlier than 1.10.
@@ -361,6 +362,10 @@ configuring a different storage driver. By default the GitLab Container Registry
is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path)
configuration.
+NOTE: **Note:** Enabling a storage driver other than `filesystem` would mean
+that your Docker client needs to be able to access the storage backend directly.
+In that case, you must use an address that resolves and is accessible outside GitLab server.
+
The different supported drivers are:
| Driver | Description |
@@ -368,26 +373,23 @@ The different supported drivers are:
| filesystem | Uses a path on the local filesystem |
| azure | Microsoft Azure Blob Storage |
| gcs | Google Cloud Storage |
-| s3 | Amazon Simple Storage Service |
+| s3 | Amazon Simple Storage Service. Be sure to configure your storage bucket with the correct [S3 Permission Scopes](https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes). |
| swift | OpenStack Swift Object Storage |
| oss | Aliyun OSS |
Read more about the individual driver's config options in the
[Docker Registry docs][storage-config].
-> **Warning** GitLab will not backup Docker images that are not stored on the
+CAUTION: **Warning:** GitLab will not backup Docker images that are not stored on the
filesystem. Remember to enable backups with your object storage provider if
desired.
->
-> **Important** Enabling storage driver other than `filesystem` would mean
-that your Docker client needs to be able to access the storage backend directly.
-So you must use an address that resolves and is accessible outside GitLab server.
----
+NOTE: **Note:**
+`regionendpoint` is only required when configuring an S3 compatible service such as Minio. It takes a URL such as `http://127.0.0.1:9000`.
**Omnibus GitLab installations**
-To configure the storage driver in Omnibus:
+To configure the `s3` storage driver in Omnibus:
1. Edit `/etc/gitlab/gitlab.rb`:
@@ -397,29 +399,29 @@ To configure the storage driver in Omnibus:
'accesskey' => 's3-access-key',
'secretkey' => 's3-secret-key-for-access-key',
'bucket' => 'your-s3-bucket',
- 'region' => 'your-s3-region'
+ 'region' => 'your-s3-region',
+ 'regionendpoint' => 'your-s3-regionendpoint'
}
}
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
Configuring the storage driver is done in your registry config YML file created
when you [deployed your docker registry][registry-deploy].
-Example:
+`s3` storage driver example:
-```
+```yml
storage:
s3:
accesskey: 'AKIAKIAKI'
secretkey: 'secret123'
bucket: 'gitlab-registry-bucket-AKIAKIAKI'
region: 'your-s3-region'
+ regionendpoint: 'your-s3-regionendpoint'
cache:
blobdescriptor: inmemory
delete:
@@ -657,6 +659,37 @@ Start with a value between `25000000` (25MB) and `50000000` (50MB).
1. Save the file and [restart GitLab][] for the changes to take effect.
+### Supporting older Docker clients
+
+As of GitLab 11.9, we began shipping version 2.7.1 of the Docker container registry, which disables the schema1 manifest by default. If you are still using older Docker clients (1.9 or older), you may experience an error pushing images. See [omnibus-4145](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4145) for more details.
+
+You can add a configuration option for backwards compatibility.
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['compatibility_schema1_enabled'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit the YML configuration file you created when you [deployed the registry][registry-deploy]. Add the following snippet:
+
+ ```yaml
+ compatibility:
+ schema1:
+ enabled: true
+ ```
+
+1. Restart the registry for the changes to take affect.
+
+
[ce-18239]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18239
[docker-insecure-self-signed]: https://docs.docker.com/registry/insecure/#use-self-signed-certificates
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 60ad4bf4e8f..113514e1ee8 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -1,12 +1,16 @@
-# Custom Git Hooks
+# Custom server-side Git hooks
-> **Note:** Custom Git hooks must be configured on the filesystem of the GitLab
+NOTE: **Note:**
+Custom Git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks] and [CI] as an option if you do not
have filesystem access. For a user configurable Git hook interface, see
-[Push Rules](https://docs.gitlab.com/ee/push_rules/push_rules.html),
+[Push Rules](../push_rules/push_rules.md),
available in GitLab Enterprise Edition.
+NOTE: **Note:**
+Custom Git hooks won't be replicated to secondary nodes if you use [GitLab Geo](geo/replication/index.md)
+
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
See [Git SCM Server-Side Hooks][hooks] for more information about each hook type.
@@ -14,15 +18,14 @@ See [Git SCM Server-Side Hooks][hooks] for more information about each hook type
As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
administrators can add custom git hooks to any GitLab project.
-## Setup
-
-Normally, Git hooks are placed in the repository or project's `hooks` directory.
-GitLab creates a symlink from each project's `hooks` directory to the
-gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
-upgrades. As such, custom hooks are implemented a little differently. Behavior
-is exactly the same once the hook is created, though.
+## Create a custom Git hook for a repository
-Follow the steps below to set up a custom hook:
+Server-side Git hooks are typically placed in the repository's `hooks`
+subdirectory. In GitLab, hook directories are are symlinked to the gitlab-shell
+`hooks` directory for ease of maintenance between gitlab-shell upgrades.
+Custom hooks are implemented differently, but the behavior is exactly the same
+once the hook is created. Follow the steps below to set up a custom hook for a
+repository:
1. Pick a project that needs a custom Git hook.
1. On the GitLab server, navigate to the project's repository directory.
@@ -42,33 +45,56 @@ Follow the steps below to set up a custom hook:
That's it! Assuming the hook code is properly implemented the hook will fire
as appropriate.
+## Set a global Git hook for all repositories
+
+To create a Git hook that applies to all of your repositories in
+your instance, set a global Git hook. Since all the repositories' `hooks`
+directories are symlinked to gitlab-shell's `hooks` directory, adding any hook
+to the gitlab-shell `hooks` directory will also apply it to all repositories. Follow
+the steps below to properly set up a custom hook for all repositories:
+
+1. On the GitLab server, navigate to the configured custom hook directory. The
+ default is in the gitlab-shell directory. The gitlab-shell `hook` directory
+ for an installation from source the path is usually
+ `/home/git/gitlab-shell/hooks`. For Omnibus installs the path is usually
+ `/opt/gitlab/embedded/service/gitlab-shell/hooks`.
+ To look in a different directory for the global custom hooks,
+ set `custom_hooks_dir` in the gitlab-shell config. For
+ Omnibus installations, this can be set in `gitlab.rb`; and in source
+ installations, this can be set in `gitlab-shell/config.yml`.
+1. Create a new directory in this location. Depending on your hook, it will be
+ either a `pre-receive.d`, `post-receive.d`, or `update.d` directory.
+1. Inside this new directory, add your hook. Hooks can be
+ in any language. Ensure the 'shebang' at the top properly reflects the language
+ type. For example, if the script is in Ruby the shebang will probably be
+ `#!/usr/bin/env ruby`.
+1. Make the hook file executable and make sure it's owned by Git.
+
+Now test the hook to see that it's functioning properly.
+
## Chained hooks support
> [Introduced][93] in GitLab Shell 4.1.0 and GitLab 8.15.
-Hooks can be also placed in `hooks/<hook_name>.d` (global) or
-`custom_hooks/<hook_name>.d` (per project) directories supporting chained
+Hooks can be also global or be set per project directories and support a chained
execution of the hooks.
-NOTE: **Note:** `<hook_name>.d` would need to be either `pre-receive.d`,
-`post-receive.d`, or `update.d` to work properly. Any other names will be ignored.
+NOTE: **Note:**
+`<hook_name>.d` would need to be either `pre-receive.d`,
+`post-receive.d`, or `update.d` to work properly. Any other names will be ignored.
-To look in a different directory for the global custom hooks (those in
-`hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For
-Omnibus installations, this can be set in `gitlab.rb`; and in source
-installations, this can be set in `gitlab-shell/config.yml`.
+NOTE: **Note:**
+Files in `.d` directories need to be executable and not match the backup file
+pattern (`*~`).
The hooks are searched and executed in this order:
1. `gitlab-shell/hooks` directory as known to Gitaly
-1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is `gitlab-shell/hooks/<hook_name>`
+1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is symlinked to `gitlab-shell/hooks/<hook_name>`
1. `<project>.git/custom_hooks/<hook_name>` - per project hook (this is already existing behavior)
1. `<project>.git/custom_hooks/<hook_name>.d/*` - per project hooks
1. `<project>.git/hooks/<hook_name>.d/*` OR `<custom_hooks_dir>/<hook_name.d>/*` - global hooks: all executable files (minus editor backup files)
-Files in `.d` directories need to be executable and not match the backup file
-pattern (`*~`).
-
The hooks of the same type are executed in order and execution stops on the
first script exiting with a non-zero value.
@@ -76,9 +102,21 @@ first script exiting with a non-zero value.
> [Introduced][5073] in GitLab 8.10.
-If the commit is declined or an error occurs during the Git hook check,
-the STDERR or STDOUT message of the hook will be present in GitLab's UI.
-STDERR takes precedence over STDOUT.
+To have custom error messages appear in GitLab's UI when the commit is
+declined or an error occurs during the Git hook, your script should:
+
+- Send the custom error messages to either the script's `stdout` or `stderr`.
+- Prefix each message with `GL-HOOK-ERR:` with no characters appearing before the prefix.
+
+### Example custom error message
+
+This hook script written in bash will generate the following message in GitLab's UI:
+
+```bash
+#!/bin/sh
+echo "GL-HOOK-ERR: My custom error message.";
+exit 1
+```
![Custom message from custom Git hook](img/custom_hooks_error_msg.png)
diff --git a/doc/administration/database_load_balancing.md b/doc/administration/database_load_balancing.md
new file mode 100644
index 00000000000..7f3be402b84
--- /dev/null
+++ b/doc/administration/database_load_balancing.md
@@ -0,0 +1,277 @@
+# Database Load Balancing **[PREMIUM ONLY]**
+
+> [Introduced][ee-1283] in [GitLab Premium][eep] 9.0.
+
+Distribute read-only queries among multiple database servers.
+
+## Overview
+
+Database load balancing improves the distribution of database workloads across
+multiple computing resources. Load balancing aims to optimize resource use,
+maximize throughput, minimize response time, and avoid overload of any single
+resource. Using multiple components with load balancing instead of a single
+component may increase reliability and availability through redundancy.
+[_Wikipedia article_][wikipedia]
+
+When database load balancing is enabled in GitLab, the load is balanced using
+a simple round-robin algorithm, without any external dependencies such as Redis.
+Load balancing is not enabled for Sidekiq as this would lead to consistency
+problems, and Sidekiq mostly performs writes anyway.
+
+In the following image, you can see the load is balanced rather evenly among
+all the secondaries (`db4`, `db5`, `db6`). Because `SELECT` queries are not
+sent to the primary (unless necessary), the primary (`db3`) hardly has any load.
+
+![DB load balancing graph](img/db_load_balancing_postgres_stats.png)
+
+## Requirements
+
+For load balancing to work you will need at least PostgreSQL 9.2 or newer,
+[**MySQL is not supported**][db-req]. You also need to make sure that you have
+at least 1 secondary in [hot standby][hot-standby] mode.
+
+Load balancing also requires that the configured hosts **always** point to the
+primary, even after a database failover. Furthermore, the additional hosts to
+balance load among must **always** point to secondary databases. This means that
+you should put a load balance in front of every database, and have GitLab connect
+to those load balancers.
+
+For example, say you have a primary (`db1.gitlab.com`) and two secondaries,
+`db2.gitlab.com` and `db3.gitlab.com`. For this setup you will need to have 3
+load balancers, one for every host. For example:
+
+* `primary.gitlab.com` forwards to `db1.gitlab.com`
+* `secondary1.gitlab.com` forwards to `db2.gitlab.com`
+* `secondary2.gitlab.com` forwards to `db3.gitlab.com`
+
+Now let's say that a failover happens and db2 becomes the new primary. This
+means forwarding should now happen as follows:
+
+* `primary.gitlab.com` forwards to `db2.gitlab.com`
+* `secondary1.gitlab.com` forwards to `db1.gitlab.com`
+* `secondary2.gitlab.com` forwards to `db3.gitlab.com`
+
+GitLab does not take care of this for you, so you will need to do so yourself.
+
+Finally, load balancing requires that GitLab can connect to all hosts using the
+same credentials and port as configured in the
+[Enabling load balancing](#enabling-load-balancing) section. Using
+different ports or credentials for different hosts is not supported.
+
+## Use cases
+
+- For GitLab instances with thousands of users and high traffic, you can use
+ database load balancing to reduce the load on the primary database and
+ increase responsiveness, thus resulting in faster page load inside GitLab.
+
+## Enabling load balancing
+
+For the environment in which you want to use load balancing, you'll need to add
+the following. This will balance the load between `host1.example.com` and
+`host2.example.com`.
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['db_load_balancing'] = { 'hosts' => ['host1.example.com', 'host2.example.com'] }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/database.yml` and add or amend the following lines:
+
+ ```yaml
+ production:
+ username: gitlab
+ database: gitlab
+ encoding: unicode
+ load_balancing:
+ hosts:
+ - host1.example.com
+ - host2.example.com
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Service Discovery
+
+> [Introduced][ee-5883] in [GitLab Premium][eep] 11.0.
+
+Service discovery allows GitLab to automatically retrieve a list of secondary
+databases to use, instead of having to manually specify these in the
+`database.yml` configuration file. Service discovery works by periodically
+checking a DNS A record, using the IPs returned by this record as the addresses
+for the secondaries. For service discovery to work, all you need is a DNS server
+and an A record containing the IP addresses of your secondaries.
+
+To use service discovery you need to change your `database.yml` configuration
+file so it looks like the following:
+
+```yaml
+production:
+ username: gitlab
+ database: gitlab
+ encoding: unicode
+ load_balancing:
+ discover:
+ nameserver: localhost
+ record: secondary.postgresql.service.consul
+ port: 8600
+ interval: 60
+ disconnect_timeout: 120
+```
+
+Here the `discover:` section specifies the configuration details to use for
+service discovery.
+
+### Configuration
+
+The following options can be set:
+
+| Option | Description | Default |
+|----------------------|---------------------------------------------------------------------------------------------------|-----------|
+| `nameserver` | The nameserver to use for looking up the DNS record. | localhost |
+| `record` | The A record to look up. This option is required for service discovery to work. | |
+| `port` | The port of the nameserver. | 8600 |
+| `interval` | The minimum time in seconds between checking the DNS record. | 60 |
+| `disconnect_timeout` | The time in seconds after which an old connection is closed, after the list of hosts was updated. | 120 |
+| `use_tcp` | Lookup DNS resources using TCP instead of UDP | false |
+
+The `interval` value specifies the _minimum_ time between checks. If the A
+record has a TTL greater than this value, then service discovery will honor said
+TTL. For example, if the TTL of the A record is 90 seconds, then service
+discovery will wait at least 90 seconds before checking the A record again.
+
+When the list of hosts is updated, it might take a while for the old connections
+to be terminated. The `disconnect_timeout` setting can be used to enforce an
+upper limit on the time it will take to terminate all old database connections.
+
+Some nameservers (like [Consul][consul-udp]) can return a truncated list of hosts when
+queried over UDP. To overcome this issue, you can use TCP for querying by setting
+`use_tcp` to `true`.
+
+### Forking
+
+If you use an application server that forks, such as Unicorn, you _have to_
+update your Unicorn configuration to start service discovery _after_ a fork.
+Failure to do so will lead to service discovery only running in the parent
+process. If you are using Unicorn, then you can add the following to your
+Unicorn configuration file:
+
+```ruby
+after_fork do |server, worker|
+ defined?(Gitlab::Database::LoadBalancing) &&
+ Gitlab::Database::LoadBalancing.start_service_discovery
+end
+```
+
+This will ensure that service discovery is started in both the parent and all
+child processes.
+
+## Balancing queries
+
+Read-only `SELECT` queries will be balanced among all the secondary hosts.
+Everything else (including transactions) will be executed on the primary.
+Queries such as `SELECT ... FOR UPDATE` are also executed on the primary.
+
+## Prepared statements
+
+Prepared statements don't work well with load balancing and are disabled
+automatically when load balancing is enabled. This should have no impact on
+response timings.
+
+## Primary sticking
+
+After a write has been performed, GitLab will stick to using the primary for a
+certain period of time, scoped to the user that performed the write. GitLab will
+revert back to using secondaries when they have either caught up, or after 30
+seconds.
+
+## Failover handling
+
+In the event of a failover or an unresponsive database, the load balancer will
+try to use the next available host. If no secondaries are available the
+operation is performed on the primary instead.
+
+In the event of a connection error being produced when writing data, the
+operation will be retried up to 3 times using an exponential back-off.
+
+When using load balancing, you should be able to safely restart a database server
+without it immediately leading to errors being presented to the users.
+
+## Logging
+
+The load balancer logs various messages, such as:
+
+* When a host is marked as offline
+* When a host comes back online
+* When all secondaries are offline
+
+Each log message contains the tag `[DB-LB]` to make searching/filtering of such
+log entries easier. For example:
+
+```
+[DB-LB] Host 10.123.2.5 came back online
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Host 10.123.2.6 came back online
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Marking host 10.123.2.7 as offline
+[DB-LB] Host 10.123.2.7 came back online
+[DB-LB] Host 10.123.2.7 came back online
+```
+
+## Handling Stale Reads
+
+> [Introduced][ee-3526] in [GitLab Premium][eep] 10.3.
+
+To prevent reading from an outdated secondary the load balancer will check if it
+is in sync with the primary. If the data is determined to be recent enough the
+secondary can be used, otherwise it will be ignored. To reduce the overhead of
+these checks we only perform these checks at certain intervals.
+
+There are three configuration options that influence this behaviour:
+
+| Option | Description | Default |
+|------------------------------|----------------------------------------------------------------------------------------------------------------|------------|
+| `max_replication_difference` | The amount of data (in bytes) a secondary is allowed to lag behind when it hasn't replicated data for a while. | 8 MB |
+| `max_replication_lag_time` | The maximum number of seconds a secondary is allowed to lag behind before we stop using it. | 60 seconds |
+| `replica_check_interval` | The minimum number of seconds we have to wait before checking the status of a secondary. | 60 seconds |
+
+The defaults should be sufficient for most users. Should you want to change them
+you can specify them in `config/database.yml` like so:
+
+```yaml
+production:
+ username: gitlab
+ database: gitlab
+ encoding: unicode
+ load_balancing:
+ hosts:
+ - host1.example.com
+ - host2.example.com
+ max_replication_difference: 16777216 # 16 MB
+ max_replication_lag_time: 30
+ replica_check_interval: 30
+```
+
+[hot-standby]: https://www.postgresql.org/docs/9.6/static/hot-standby.html
+[ee-1283]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1283
+[eep]: https://about.gitlab.com/pricing/
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
+[wikipedia]: https://en.wikipedia.org/wiki/Load_balancing_(computing)
+[db-req]: ../install/requirements.md#database
+[ee-3526]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3526
+[ee-5883]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5883
+[consul-udp]: https://www.consul.io/docs/agent/dns.html#udp-based-dns-queries
diff --git a/doc/administration/dependency_proxy.md b/doc/administration/dependency_proxy.md
new file mode 100644
index 00000000000..4dc1f4dcba4
--- /dev/null
+++ b/doc/administration/dependency_proxy.md
@@ -0,0 +1,150 @@
+# GitLab Dependency Proxy administration **[PREMIUM ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing) 11.11.
+
+GitLab can be utilized as a dependency proxy for a variety of common package managers.
+
+This is the administration documentation. If you want to learn how to use the
+dependency proxies, see the [user guide](../user/group/dependency_proxy/index.md).
+
+## Enabling the Dependency Proxy feature
+
+NOTE: **Note:**
+Dependency proxy requires the Puma web server to be enabled.
+Puma support is EXPERIMENTAL at this time.
+
+To enable the Dependency proxy feature:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['dependency_proxy_enabled'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Enable the [Puma web server](https://docs.gitlab.com/omnibus/settings/puma.html).
+
+**Installations from source**
+
+1. After the installation is complete, you will have to configure the `dependency_proxy`
+ section in `config/gitlab.yml`. Set to `true` to enable it:
+
+ ```yaml
+ dependency_proxy:
+ enabled: true
+ ```
+
+1. [Restart GitLab] for the changes to take effect.
+1. Enable the [Puma web server](../install/installation.md#using-puma).
+
+## Changing the storage path
+
+By default, the dependency proxy files are stored locally, but you can change the default
+local location or even use object storage.
+
+### Changing the local storage path
+
+The dependency proxy files for Omnibus GitLab installations are stored under
+`/var/opt/gitlab/gitlab-rails/shared/dependency_proxy/` and for source
+installations under `shared/dependency_proxy/` (relative to the git home directory).
+To change the local storage path:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['dependency_proxy_storage_path'] = "/mnt/dependency_proxy"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. Edit the `dependency_proxy` section in `config/gitlab.yml`:
+
+ ```yaml
+ dependency_proxy:
+ enabled: true
+ storage_path: shared/dependency_proxy
+ ```
+1. [Restart GitLab] for the changes to take effect.
+
+### Using object storage
+
+Instead of relying on the local storage, you can use an object storage to
+upload the blobs of the dependency proxy:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
+ necessary):
+
+ ```ruby
+ gitlab_rails['dependency_proxy_enabled'] = true
+ gitlab_rails['dependency_proxy_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/dependency_proxy"
+ gitlab_rails['dependency_proxy_object_store_enabled'] = true
+ gitlab_rails['dependency_proxy_object_store_remote_directory'] = "dependency_proxy" # The bucket name.
+ gitlab_rails['dependency_proxy_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ gitlab_rails['dependency_proxy_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
+ gitlab_rails['dependency_proxy_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ gitlab_rails['dependency_proxy_object_store_connection'] = {
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #'provider' => 'AWS',
+ #'region' => 'eu-west-1',
+ #'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ #'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #'host' => 's3.amazonaws.com',
+ #'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. Edit the `dependency_proxy` section in `config/gitlab.yml` (uncomment where necessary):
+
+ ```yaml
+ dependency_proxy:
+ enabled: true
+ ##
+ ## The location where build dependency_proxy are stored (default: shared/dependency_proxy).
+ ##
+ #storage_path: shared/dependency_proxy
+ object_store:
+ enabled: false
+ remote_directory: dependency_proxy # The bucket name.
+ #direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ #background_upload: true # Temporary option to limit automatic upload (Default: true).
+ #proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ connection:
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #provider: AWS
+ #region: us-east-1
+ #aws_access_key_id: AWS_ACCESS_KEY_ID
+ #aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #host: 's3.amazonaws.com' # default: s3.amazonaws.com.
+ #aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
diff --git a/doc/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
new file mode 100644
index 00000000000..d4c8c2d3624
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -0,0 +1,182 @@
+# Automatic background verification **[PREMIUM ONLY]**
+
+NOTE: **Note:**
+Automatic background verification of repositories and wikis was added in
+GitLab EE 10.6 but is enabled by default only on GitLab EE 11.1. You can
+disable or enable this feature manually by following
+[these instructions](#disabling-or-enabling-the-automatic-background-verification).
+
+Automatic background verification ensures that the transferred data matches a
+calculated checksum. If the checksum of the data on the **primary** node matches checksum of the
+data on the **secondary** node, the data transferred successfully. Following a planned failover,
+any corrupted data may be **lost**, depending on the extent of the corruption.
+
+If verification fails on the **primary** node, this indicates that Geo is
+successfully replicating a corrupted object; restore it from backup or remove it
+it from the **primary** node to resolve the issue.
+
+If verification succeeds on the **primary** node but fails on the **secondary** node,
+this indicates that the object was corrupted during the replication process.
+Geo actively try to correct verification failures marking the repository to
+be resynced with a backoff period. If you want to reset the verification for
+these failures, so you should follow [these instructions][reset-verification].
+
+If verification is lagging significantly behind replication, consider giving
+the node more time before scheduling a planned failover.
+
+## Disabling or enabling the automatic background verification
+
+Run the following commands in a Rails console on the **primary** node:
+
+```sh
+gitlab-rails console
+```
+
+To check if automatic background verification is enabled:
+
+```ruby
+Gitlab::Geo.repository_verification_enabled?
+```
+
+To disable automatic background verification:
+
+```ruby
+Feature.disable('geo_repository_verification')
+```
+
+To enable automatic background verification:
+
+```ruby
+Feature.enable('geo_repository_verification')
+```
+
+## Repository verification
+
+Navigate to the **Admin Area > Geo** dashboard on the **primary** node and expand
+the **Verification information** tab for that node to view automatic checksumming
+status for repositories and wikis. Successes are shown in green, pending work
+in grey, and failures in red.
+
+![Verification status](img/verification-status-primary.png)
+
+Navigate to the **Admin Area > Geo** dashboard on the **secondary** node and expand
+the **Verification information** tab for that node to view automatic verification
+status for repositories and wikis. As with checksumming, successes are shown in
+green, pending work in grey, and failures in red.
+
+![Verification status](img/verification-status-secondary.png)
+
+## Using checksums to compare Geo nodes
+
+To check the health of Geo **secondary** nodes, we use a checksum over the list of
+Git references and their values. The checksum includes `HEAD`, `heads`, `tags`,
+`notes`, and GitLab-specific references to ensure true consistency. If two nodes
+have the same checksum, then they definitely hold the same references. We compute
+the checksum for every node after every update to make sure that they are all
+in sync.
+
+## Repository re-verification
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8550) in GitLab Enterprise Edition 11.6. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
+
+Due to bugs or transient infrastructure failures, it is possible for Git
+repositories to change unexpectedly without being marked for verification.
+Geo constantly reverifies the repositories to ensure the integrity of the
+data. The default and recommended re-verification interval is 7 days, though
+an interval as short as 1 day can be set. Shorter intervals reduce risk but
+increase load and vice versa.
+
+Navigate to the **Admin Area > Geo** dashboard on the **primary** node, and
+click the **Edit** button for the **primary** node to customize the minimum
+re-verification interval:
+
+![Re-verification interval](img/reverification-interval.png)
+
+The automatic background re-verification is enabled by default, but you can
+disable if you need. Run the following commands in a Rails console on the
+**primary** node:
+
+```sh
+gitlab-rails console
+```
+
+To disable automatic background re-verification:
+
+```ruby
+Feature.disable('geo_repository_reverification')
+```
+
+To enable automatic background re-verification:
+
+```ruby
+Feature.enable('geo_repository_reverification')
+```
+
+## Reset verification for projects where verification has failed
+
+Geo actively try to correct verification failures marking the repository to
+be resynced with a backoff period. If you want to reset them manually, this
+rake task marks projects where verification has failed or the checksum mismatch
+to be resynced without the backoff period:
+
+For repositories:
+
+```sh
+sudo gitlab-rake geo:verification:repository:reset
+```
+
+For wikis:
+
+```sh
+sudo gitlab-rake geo:verification:wiki:reset
+```
+
+## Reconcile differences with checksum mismatches
+
+If the **primary** and **secondary** nodes have a checksum verification mismatch, the cause may not be apparent. To find the cause of a checksum mismatch:
+
+1. Navigate to the **Admin Area > Projects** dashboard on the **primary** node, find the
+ project that you want to check the checksum differences and click on the
+ **Edit** button:
+ ![Projects dashboard](img/checksum-differences-admin-projects.png)
+
+1. On the project admin page get the **Gitaly storage name**, and **Gitaly relative path**:
+ ![Project admin page](img/checksum-differences-admin-project-page.png)
+
+1. Navigate to the project's repository directory on both **primary** and **secondary** nodes (the path is usually `/var/opt/gitlab/git-data/repositories`). Note that if `git_data_dirs` is customized, check the directory layout on your server to be sure.
+
+ ```sh
+ cd /var/opt/gitlab/git-data/repositories
+ ```
+
+1. Run the following command on the **primary** node, redirecting the output to a file:
+
+ ```sh
+ git show-ref --head | grep -E "HEAD|(refs/(heads|tags|keep-around|merge-requests|environments|notes)/)" > primary-node-refs
+ ```
+
+1. Run the following command on the **secondary** node, redirecting the output to a file:
+
+ ```sh
+ git show-ref --head | grep -E "HEAD|(refs/(heads|tags|keep-around|merge-requests|environments|notes)/)" > secondary-node-refs
+ ```
+
+1. Copy the files from the previous steps on the same system, and do a diff between the contents:
+
+ ```sh
+ diff primary-node-refs secondary-node-refs
+ ```
+
+## Current limitations
+
+Until [issue #5064][ee-5064] is completed, background verification doesn't cover
+CI job artifacts and traces, LFS objects, or user uploads in file storage.
+Verify their integrity manually by following [these instructions][foreground-verification]
+on both nodes, and comparing the output between them.
+
+Data in object storage is **not verified**, as the object store is responsible
+for ensuring the integrity of the data.
+
+[reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed
+[foreground-verification]: ../../raketasks/check.md
+[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064
diff --git a/doc/administration/geo/disaster_recovery/bring_primary_back.md b/doc/administration/geo/disaster_recovery/bring_primary_back.md
new file mode 100644
index 00000000000..f4d31a98080
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/bring_primary_back.md
@@ -0,0 +1,61 @@
+# Bring a demoted primary node back online **[PREMIUM ONLY]**
+
+After a failover, it is possible to fail back to the demoted **primary** node to
+restore your original configuration. This process consists of two steps:
+
+1. Making the old **primary** node a **secondary** node.
+1. Promoting a **secondary** node to a **primary** node.
+
+CAUTION: **Caution:**
+If you have any doubts about the consistency of the data on this node, we recommend setting it up from scratch.
+
+## Configure the former **primary** node to be a **secondary** node
+
+Since the former **primary** node will be out of sync with the current **primary** node, the first step is to bring the former **primary** node up to date. Note, deletion of data stored on disk like
+repositories and uploads will not be replayed when bringing the former **primary** node back
+into sync, which may result in increased disk usage.
+Alternatively, you can [set up a new **secondary** GitLab instance][setup-geo] to avoid this.
+
+To bring the former **primary** node up to date:
+
+1. SSH into the former **primary** node that has fallen behind.
+1. Make sure all the services are up:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+ > **Note 1:** If you [disabled the **primary** node permanently][disaster-recovery-disable-primary],
+ > you need to undo those steps now. For Debian/Ubuntu you just need to run
+ > `sudo systemctl enable gitlab-runsvdir`. For CentOS 6, you need to install
+ > the GitLab instance from scratch and set it up as a **secondary** node by
+ > following [Setup instructions][setup-geo]. In this case, you don't need to follow the next step.
+ >
+ > **Note 2:** If you [changed the DNS records](index.md#step-4-optional-updating-the-primary-domain-dns-record)
+ > for this node during disaster recovery procedure you may need to [block
+ > all the writes to this node](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/gitlab-geo/planned-failover.md#block-primary-traffic)
+ > during this procedure.
+
+1. [Setup database replication][database-replication]. Note that in this
+ case, **primary** node refers to the current **primary** node, and **secondary** node refers to the
+ former **primary** node.
+
+If you have lost your original **primary** node, follow the
+[setup instructions][setup-geo] to set up a new **secondary** node.
+
+## Promote the **secondary** node to **primary** node
+
+When the initial replication is complete and the **primary** node and **secondary** node are
+closely in sync, you can do a [planned failover].
+
+## Restore the **secondary** node
+
+If your objective is to have two nodes again, you need to bring your **secondary**
+node back online as well by repeating the first step
+([configure the former **primary** node to be a **secondary** node](#configure-the-former-primary-node-to-be-a-secondary-node))
+for the **secondary** node.
+
+[setup-geo]: ../replication/index.md#setup-instructions
+[database-replication]: ../replication/database.md
+[disaster-recovery-disable-primary]: index.md#step-2-permanently-disable-the-primary-node
+[planned failover]: planned_failover.md
diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png
new file mode 100644
index 00000000000..fd51523104b
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png
new file mode 100644
index 00000000000..b2a6da69d3d
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/replication-status.png b/doc/administration/geo/disaster_recovery/img/replication-status.png
new file mode 100644
index 00000000000..d7085927c75
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/replication-status.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/reverification-interval.png b/doc/administration/geo/disaster_recovery/img/reverification-interval.png
new file mode 100644
index 00000000000..ad4597a4f49
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/reverification-interval.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/verification-status-primary.png b/doc/administration/geo/disaster_recovery/img/verification-status-primary.png
new file mode 100644
index 00000000000..2503408ec5d
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/verification-status-primary.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/verification-status-secondary.png b/doc/administration/geo/disaster_recovery/img/verification-status-secondary.png
new file mode 100644
index 00000000000..462274d8b14
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/img/verification-status-secondary.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
new file mode 100644
index 00000000000..71dc797f281
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -0,0 +1,322 @@
+# Disaster Recovery **[PREMIUM ONLY]**
+
+Geo replicates your database, your Git repositories, and few other assets.
+We will support and replicate more data in the future, that will enable you to
+failover with minimal effort, in a disaster situation.
+
+See [Geo current limitations][geo-limitations] for more information.
+
+CAUTION: **Warning:**
+Disaster recovery for multi-secondary configurations is in **Alpha**.
+For the latest updates, check the multi-secondary [Disaster Recovery epic][gitlab-org&65].
+
+## Promoting a **secondary** Geo node in single-secondary configurations
+
+We don't currently provide an automated way to promote a Geo replica and do a
+failover, but you can do it manually if you have `root` access to the machine.
+
+This process promotes a **secondary** Geo node to a **primary** node. To regain
+geographic redundancy as quickly as possible, you should add a new **secondary** node
+immediately after following these instructions.
+
+### Step 1. Allow replication to finish if possible
+
+If the **secondary** node is still replicating data from the **primary** node, follow
+[the planned failover docs][planned-failover] as closely as possible in
+order to avoid unnecessary data loss.
+
+### Step 2. Permanently disable the **primary** node
+
+CAUTION: **Warning:**
+If the **primary** node goes offline, there may be data saved on the **primary** node
+that has not been replicated to the **secondary** node. This data should be treated
+as lost if you proceed.
+
+If an outage on the **primary** node happens, you should do everything possible to
+avoid a split-brain situation where writes can occur in two different GitLab
+instances, complicating recovery efforts. So to prepare for the failover, we
+must disable the **primary** node.
+
+1. SSH into the **primary** node to stop and disable GitLab, if possible:
+
+ ```sh
+ sudo gitlab-ctl stop
+ ```
+
+ Prevent GitLab from starting up again if the server unexpectedly reboots:
+
+ ```sh
+ sudo systemctl disable gitlab-runsvdir
+ ```
+
+ > **CentOS only**: In CentOS 6 or older, there is no easy way to prevent GitLab from being
+ > started if the machine reboots isn't available (see [gitlab-org/omnibus-gitlab#3058]).
+ > It may be safest to uninstall the GitLab package completely:
+
+ ```sh
+ yum remove gitlab-ee
+ ```
+
+ > **Ubuntu 14.04 LTS**: If you are using an older version of Ubuntu
+ > or any other distro based on the Upstart init system, you can prevent GitLab
+ > from starting if the machine reboots by doing the following:
+
+ ```sh
+ initctl stop gitlab-runsvvdir
+ echo 'manual' > /etc/init/gitlab-runsvdir.override
+ initctl reload-configuration
+ ```
+
+1. If you do not have SSH access to the **primary** node, take the machine offline and
+ prevent it from rebooting by any means at your disposal.
+ Since there are many ways you may prefer to accomplish this, we will avoid a
+ single recommendation. You may need to:
+ - Reconfigure the load balancers.
+ - Change DNS records (e.g., point the primary DNS record to the **secondary**
+ node in order to stop usage of the **primary** node).
+ - Stop the virtual servers.
+ - Block traffic through a firewall.
+ - Revoke object storage permissions from the **primary** node.
+ - Physically disconnect a machine.
+
+1. If you plan to
+ [update the primary domain DNS record](#step-4-optional-updating-the-primary-domain-dns-record),
+ you may wish to lower the TTL now to speed up propagation.
+
+### Step 3. Promoting a **secondary** node
+
+NOTE: **Note:**
+A new **secondary** should not be added at this time. If you want to add a new
+**secondary**, do this after you have completed the entire process of promoting
+the **secondary** to the **primary**.
+
+#### Promoting a **secondary** node running on a single machine
+
+1. SSH in to your **secondary** node and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` to reflect its new status as **primary** by
+ removing any lines that enabled the `geo_secondary_role`:
+
+ ```ruby
+ ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
+ geo_secondary_role['enable'] = true
+
+ ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
+ roles ['geo_secondary_role']
+ ```
+
+1. Promote the **secondary** node to the **primary** node. Execute:
+
+ ```sh
+ gitlab-ctl promote-to-primary-node
+ ```
+
+1. Verify you can connect to the newly promoted **primary** node using the URL used
+ previously for the **secondary** node.
+1. If successful, the **secondary** node has now been promoted to the **primary** node.
+
+#### Promoting a **secondary** node with HA
+
+The `gitlab-ctl promote-to-primary-node` command cannot be used yet in
+conjunction with High Availability or with multiple machines, as it can only
+perform changes on a **secondary** with only a single machine. Instead, you must
+do this manually.
+
+1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
+ promote to read-write:
+
+ ```bash
+ sudo gitlab-pg-ctl promote
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` on every machine in the **secondary** to
+ reflect its new status as **primary** by removing any lines that enabled the
+ `geo_secondary_role`:
+
+ ```ruby
+ ## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
+ geo_secondary_role['enable'] = true
+
+ ## In 11.5+ documentation, the role was enabled as follows. Remove this line.
+ roles ['geo_secondary_role']
+ ```
+
+ After making these changes [Reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) each
+ machine so the changes take effect.
+
+1. Promote the **secondary** to **primary**. SSH into a single application
+ server and execute:
+
+ ```bash
+ sudo gitlab-rake geo:set_secondary_as_primary
+ ```
+
+1. Verify you can connect to the newly promoted **primary** using the URL used
+ previously for the **secondary**.
+1. Success! The **secondary** has now been promoted to **primary**.
+
+### Step 4. (Optional) Updating the primary domain DNS record
+
+Updating the DNS records for the primary domain to point to the **secondary** node
+will prevent the need to update all references to the primary domain to the
+secondary domain, like changing Git remotes and API URLs.
+
+1. SSH into the **secondary** node and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Update the primary domain's DNS record. After updating the primary domain's
+ DNS records to point to the **secondary** node, edit `/etc/gitlab/gitlab.rb` on the
+ **secondary** node to reflect the new URL:
+
+ ```ruby
+ # Change the existing external_url configuration
+ external_url 'https://<new_external_url>'
+ ```
+
+ NOTE: **Note**
+ Changing `external_url` won't prevent access via the old secondary URL, as
+ long as the secondary DNS records are still intact.
+
+1. Reconfigure the **secondary** node for the change to take effect:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+1. Execute the command below to update the newly promoted **primary** node URL:
+
+ ```sh
+ gitlab-rake geo:update_primary_node_url
+ ```
+
+ This command will use the changed `external_url` configuration defined
+ in `/etc/gitlab/gitlab.rb`.
+
+1. Verify you can connect to the newly promoted **primary** using its URL.
+ If you updated the DNS records for the primary domain, these changes may
+ not have yet propagated depending on the previous DNS records TTL.
+
+### Step 5. (Optional) Add **secondary** Geo node to a promoted **primary** node
+
+Promoting a **secondary** node to **primary** node using the process above does not enable
+Geo on the new **primary** node.
+
+To bring a new **secondary** node online, follow the [Geo setup instructions][setup-geo].
+
+### Step 6. (Optional) Removing the secondary's tracking database
+
+Every **secondary** has a special tracking database that is used to save the status of the synchronization of all the items from the **primary**.
+Because the **secondary** is already promoted, that data in the tracking database is no longer required.
+
+The data can be removed with the following command:
+
+```sh
+sudo rm -rf /var/opt/gitlab/geo-postgresql
+```
+
+## Promoting secondary Geo replica in multi-secondary configurations
+
+If you have more than one **secondary** node and you need to promote one of them, we suggest you follow
+[Promoting a **secondary** Geo node in single-secondary configurations](#promoting-a-secondary-geo-node-in-single-secondary-configurations)
+and after that you also need two extra steps.
+
+### Step 1. Prepare the new **primary** node to serve one or more **secondary** nodes
+
+1. SSH into the new **primary** node and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ ## Enable a Geo Primary role (if you haven't yet)
+ roles ['geo_primary_role']
+
+ ##
+ # Allow PostgreSQL client authentication from the primary and secondary IPs. These IPs may be
+ # public or VPC addresses in CIDR format, for example ['198.51.100.1/32', '198.51.100.2/32']
+ ##
+ postgresql['md5_auth_cidr_addresses'] = ['<primary_node_ip>/32', '<secondary_node_ip>/32']
+
+ # Every secondary server needs to have its own slot so specify the number of secondary nodes you're going to have
+ postgresql['max_replication_slots'] = 1
+
+ ##
+ ## Disable automatic database migrations temporarily
+ ## (until PostgreSQL is restarted and listening on the private address).
+ ##
+ gitlab_rails['auto_migrate'] = false
+
+ ```
+
+ (For more details about these settings you can read [Configure the primary server][configure-the-primary-server])
+
+1. Save the file and reconfigure GitLab for the database listen changes and
+ the replication slot changes to be applied.
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+ Restart PostgreSQL for its changes to take effect:
+
+ ```sh
+ gitlab-ctl restart postgresql
+ ```
+
+1. Re-enable migrations now that PostgreSQL is restarted and listening on the
+ private address.
+
+ Edit `/etc/gitlab/gitlab.rb` and **change** the configuration to `true`:
+
+ ```ruby
+ gitlab_rails['auto_migrate'] = true
+ ```
+
+ Save the file and reconfigure GitLab:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+### Step 2. Initiate the replication process
+
+Now we need to make each **secondary** node listen to changes on the new **primary** node. To do that you need
+to [initiate the replication process][initiate-the-replication-process] again but this time
+for another **primary** node. All the old replication settings will be overwritten.
+
+## Troubleshooting
+
+### I followed the disaster recovery instructions and now two-factor auth is broken!
+
+The setup instructions for Geo prior to 10.5 failed to replicate the
+`otp_key_base` secret, which is used to encrypt the two-factor authentication
+secrets stored in the database. If it differs between **primary** and **secondary**
+nodes, users with two-factor authentication enabled won't be able to log in
+after a failover.
+
+If you still have access to the old **primary** node, you can follow the
+instructions in the
+[Upgrading to GitLab 10.5][updating-geo]
+section to resolve the error. Otherwise, the secret is lost and you'll need to
+[reset two-factor authentication for all users][sec-tfa].
+
+[gitlab-org&65]: https://gitlab.com/groups/gitlab-org/-/epics/65
+[geo-limitations]: ../replication/index.md#current-limitations
+[planned-failover]: planned_failover.md
+[setup-geo]: ../replication/index.md#setup-instructions
+[updating-geo]: ../replication/updating_the_geo_nodes.md#upgrading-to-gitlab-105
+[sec-tfa]: ../../../security/two_factor_authentication.md#disabling-2fa-for-everyone
+[gitlab-org/omnibus-gitlab#3058]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3058
+[gitlab-org/gitlab-ee#4284]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4284
+[initiate-the-replication-process]: ../replication/database.html#step-3-initiate-the-replication-process
+[configure-the-primary-server]: ../replication/database.html#step-1-configure-the-primary-server
diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md
new file mode 100644
index 00000000000..b8071b5993f
--- /dev/null
+++ b/doc/administration/geo/disaster_recovery/planned_failover.md
@@ -0,0 +1,227 @@
+# Disaster recovery for planned failover **[PREMIUM ONLY]**
+
+The primary use-case of Disaster Recovery is to ensure business continuity in
+the event of unplanned outage, but it can be used in conjunction with a planned
+failover to migrate your GitLab instance between regions without extended
+downtime.
+
+As replication between Geo nodes is asynchronous, a planned failover requires
+a maintenance window in which updates to the **primary** node are blocked. The
+length of this window is determined by your replication capacity - once the
+**secondary** node is completely synchronized with the **primary** node, the failover can occur without
+data loss.
+
+This document assumes you already have a fully configured, working Geo setup.
+Please read it and the [Disaster Recovery][disaster-recovery] failover
+documentation in full before proceeding. Planned failover is a major operation,
+and if performed incorrectly, there is a high risk of data loss. Consider
+rehearsing the procedure until you are comfortable with the necessary steps and
+have a high degree of confidence in being able to perform them accurately.
+
+## Not all data is automatically replicated
+
+If you are using any GitLab features that Geo [doesn't support][limitations],
+you must make separate provisions to ensure that the **secondary** node has an
+up-to-date copy of any data associated with that feature. This may extend the
+required scheduled maintenance period significantly.
+
+A common strategy for keeping this period as short as possible for data stored
+in files is to use `rsync` to transfer the data. An initial `rsync` can be
+performed ahead of the maintenance window; subsequent `rsync`s (including a
+final transfer inside the maintenance window) will then transfer only the
+*changes* between the **primary** node and the **secondary** nodes.
+
+Repository-centric strategies for using `rsync` effectively can be found in the
+[moving repositories][moving-repositories] documentation; these strategies can
+be adapted for use with any other file-based data, such as GitLab Pages (to
+be found in `/var/opt/gitlab/gitlab-rails/shared/pages` if using Omnibus).
+
+## Pre-flight checks
+
+Follow these steps before scheduling a planned failover to ensure the process
+will go smoothly.
+
+### Object storage
+
+Some classes of non-repository data can use object storage in preference to
+file storage. Geo [does not replicate data in object storage](../replication/object_storage.md),
+leaving that task up to the object store itself. For a planned failover, this
+means you can decouple the replication of this data from the failover of the
+GitLab service.
+
+If you're already using object storage, simply verify that your **secondary**
+node has access to the same data as the **primary** node - they must either they share the
+same object storage configuration, or the **secondary** node should be configured to
+access a [geographically-replicated][os-repl] copy provided by the object store
+itself.
+
+If you have a large GitLab installation or cannot tolerate downtime, consider
+[migrating to Object Storage][os-conf] **before** scheduling a planned failover.
+Doing so reduces both the length of the maintenance window, and the risk of data
+loss as a result of a poorly executed planned failover.
+
+### Review the configuration of each **secondary** node
+
+Database settings are automatically replicated to the **secondary** node, but the
+`/etc/gitlab/gitlab.rb` file must be set up manually, and differs between
+nodes. If features such as Mattermost, OAuth or LDAP integration are enabled
+on the **primary** node but not the **secondary** node, they will be lost during failover.
+
+Review the `/etc/gitlab/gitlab.rb` file for both nodes and ensure the **secondary** node
+supports everything the **primary** node does **before** scheduling a planned failover.
+
+### Run system checks
+
+Run the following on both **primary** and **secondary** nodes:
+
+```sh
+gitlab-rake gitlab:check
+gitlab-rake gitlab:geo:check
+```
+
+If any failures are reported on either node, they should be resolved **before**
+scheduling a planned failover.
+
+### Check that secrets match between nodes
+
+The SSH host keys and `/etc/gitlab/gitlab-secrets.json` files should be
+identical on all nodes. Check this by running the following on all nodes and
+comparing the output:
+
+```sh
+sudo sha256sum /etc/ssh/ssh_host* /etc/gitlab/gitlab-secrets.json
+```
+
+If any files differ, replace the content on the **secondary** node with the
+content from the **primary** node.
+
+### Ensure Geo replication is up-to-date
+
+The maintenance window won't end until Geo replication and verification is
+completely finished. To keep the window as short as possible, you should
+ensure these processes are close to 100% as possible during active use.
+
+Navigate to the **Admin Area > Geo** dashboard on the **secondary** node to
+review status. Replicated objects (shown in green) should be close to 100%,
+and there should be no failures (shown in red). If a large proportion of
+objects aren't yet replicated (shown in grey), consider giving the node more
+time to complete
+
+![Replication status](img/replication-status.png)
+
+If any objects are failing to replicate, this should be investigated before
+scheduling the maintenance window. Following a planned failover, anything that
+failed to replicate will be **lost**.
+
+You can use the [Geo status API](../../../api/geo_nodes.md#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node) to review failed objects and
+the reasons for failure.
+
+A common cause of replication failures is the data being missing on the
+**primary** node - you can resolve these failures by restoring the data from backup,
+or removing references to the missing data.
+
+### Verify the integrity of replicated data
+
+This [content was moved to another location][background-verification].
+
+### Notify users of scheduled maintenance
+
+On the **primary** node, navigate to **Admin Area > Messages**, add a broadcast
+message. You can check under **Admin Area > Geo** to estimate how long it
+will take to finish syncing. An example message would be:
+
+> A scheduled maintenance will take place at XX:XX UTC. We expect it to take
+> less than 1 hour.
+
+## Prevent updates to the **primary** node
+
+Until a [read-only mode][ce-19739] is implemented, updates must be prevented
+from happening manually. Note that your **secondary** node still needs read-only
+access to the **primary** node during the maintenance window.
+
+1. At the scheduled time, using your cloud provider or your node's firewall, block
+ all HTTP, HTTPS and SSH traffic to/from the **primary** node, **except** for your IP and
+ the **secondary** node's IP.
+
+ For instance, you might run the following commands on the server(s) making up your **primary** node:
+
+ ```sh
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 22 -j ACCEPT
+ sudo iptables -A INPUT --destination-port 22 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 80 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 80 -j REJECT
+
+ sudo iptables -A INPUT -p tcp -s <secondary_node_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT -p tcp -s <your_ip> --destination-port 443 -j ACCEPT
+ sudo iptables -A INPUT --tcp-dport 443 -j REJECT
+ ```
+
+ From this point, users will be unable to view their data or make changes on the
+ **primary** node. They will also be unable to log in to the **secondary** node.
+ However, existing sessions will work for the remainder of the maintenance period, and
+ public data will be accessible throughout.
+
+1. Verify the **primary** node is blocked to HTTP traffic by visiting it in browser via
+ another IP. The server should refuse connection.
+
+1. Verify the **primary** node is blocked to Git over SSH traffic by attempting to pull an
+ existing Git repository with an SSH remote URL. The server should refuse
+ connection.
+
+1. Disable non-Geo periodic background jobs on the primary node by navigating
+ to **Admin Area > Monitoring > Background Jobs > Cron** , pressing `Disable All`,
+ and then pressing `Enable` for the `geo_sidekiq_cron_config_worker` cron job.
+ This job will re-enable several other cron jobs that are essential for planned
+ failover to complete successfully.
+
+## Finish replicating and verifying all data
+
+1. If you are manually replicating any data not managed by Geo, trigger the
+ final replication process now.
+1. On the **primary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all queues except those with `geo` in the name to drop to 0.
+ These queues contain work that has been submitted by your users; failing over
+ before it is completed will cause the work to be lost.
+1. On the **primary** node, navigate to **Admin Area > Geo** and wait for the
+ following conditions to be true of the **secondary** node you are failing over to:
+ - All replication meters to each 100% replicated, 0% failures.
+ - All verification meters reach 100% verified, 0% failures.
+ - Database replication lag is 0ms.
+ - The Geo log cursor is up to date (0 events behind).
+
+1. On the **secondary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
+ and wait for all the `geo` queues to drop to 0 queued and 0 running jobs.
+1. On the **secondary** node, use [these instructions][foreground-verification]
+ to verify the integrity of CI artifacts, LFS objects and uploads in file
+ storage.
+
+At this point, your **secondary** node will contain an up-to-date copy of everything the
+**primary** node has, meaning nothing will be lost when you fail over.
+
+## Promote the **secondary** node
+
+Finally, follow the [Disaster Recovery docs][disaster-recovery] to promote the
+**secondary** node to a **primary** node. This process will cause a brief outage on the **secondary** node, and users may need to log in again.
+
+Once it is completed, the maintenance window is over! Your new **primary** node will now
+begin to diverge from the old one. If problems do arise at this point, failing
+back to the old **primary** node [is possible][bring-primary-back], but likely to result
+in the loss of any data uploaded to the new primary in the meantime.
+
+Don't forget to remove the broadcast message after failover is complete.
+
+[bring-primary-back]: bring_primary_back.md
+[ce-19739]: https://gitlab.com/gitlab-org/gitlab-ce/issues/19739
+[container-registry]: ../replication/container_registry.md
+[disaster-recovery]: index.md
+[ee-4930]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4930
+[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064
+[foreground-verification]: ../../raketasks/check.md
+[background-verification]: background_verification.md
+[limitations]: ../replication/index.md#current-limitations
+[moving-repositories]: ../../operations/moving_repositories.md
+[os-conf]: ../replication/object_storage.md#configuration
+[os-repl]: ../replication/object_storage.md#replication
diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md
new file mode 100644
index 00000000000..3d4f69d3abe
--- /dev/null
+++ b/doc/administration/geo/replication/configuration.md
@@ -0,0 +1,308 @@
+# Geo configuration **[PREMIUM ONLY]**
+
+## Configuring a new **secondary** node
+
+NOTE: **Note:**
+This is the final step in setting up a **secondary** Geo node. Stages of the
+setup process must be completed in the documented order.
+Before attempting the steps in this stage, [complete all prior stages][setup-geo-omnibus].
+
+The basic steps of configuring a **secondary** node are to:
+
+- Replicate required configurations between the **primary** node and the **secondary** nodes.
+- Configure a tracking database on each **secondary** node.
+- Start GitLab on each **secondary** node.
+
+You are encouraged to first read through all the steps before executing them
+in your testing/production environment.
+
+> **Notes:**
+> - **Do not** setup any custom authentication for the **secondary** nodes. This will be
+ handled by the **primary** node.
+> - Any change that requires access to the **Admin Area** needs to be done in the
+ **primary** node because the **secondary** node is a read-only replica.
+
+### Step 1. Manually replicate secret GitLab values
+
+GitLab stores a number of secret values in the `/etc/gitlab/gitlab-secrets.json`
+file which *must* be the same on all nodes. Until there is
+a means of automatically replicating these between nodes (see issue [gitlab-org/gitlab-ee#3789]),
+they must be manually replicated to the **secondary** node.
+
+1. SSH into the **primary** node, and execute the command below:
+
+ ```sh
+ sudo cat /etc/gitlab/gitlab-secrets.json
+ ```
+
+ This will display the secrets that need to be replicated, in JSON format.
+
+1. SSH into the **secondary** node and login as the `root` user:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Make a backup of any existing secrets:
+
+ ```sh
+ mv /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.`date +%F`
+ ```
+
+1. Copy `/etc/gitlab/gitlab-secrets.json` from the **primary** node to the **secondary** node, or
+ copy-and-paste the file contents between nodes:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab-secrets.json
+
+ # paste the output of the `cat` command you ran on the primary
+ # save and exit
+ ```
+
+1. Ensure the file permissions are correct:
+
+ ```sh
+ chown root:root /etc/gitlab/gitlab-secrets.json
+ chmod 0600 /etc/gitlab/gitlab-secrets.json
+ ```
+
+1. Reconfigure the **secondary** node for the change to take effect:
+
+ ```sh
+ gitlab-ctl reconfigure
+ gitlab-ctl restart
+ ```
+
+### Step 2. Manually replicate the **primary** node's SSH host keys
+
+GitLab integrates with the system-installed SSH daemon, designating a user
+(typically named git) through which all access requests are handled.
+
+In a [Disaster Recovery] situation, GitLab system
+administrators will promote a **secondary** node to the **primary** node. DNS records for the
+**primary** domain should also be updated to point to the new **primary** node
+(previously a **secondary** node). Doing so will avoid the need to update Git remotes and API URLs.
+
+This will cause all SSH requests to the newly promoted **primary** node to
+fail due to SSH host key mismatch. To prevent this, the primary SSH host
+keys must be manually replicated to the **secondary** node.
+
+1. SSH into the **secondary** node and login as the `root` user:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Make a backup of any existing SSH host keys:
+
+ ```sh
+ find /etc/ssh -iname ssh_host_* -exec cp {} {}.backup.`date +%F` \;
+ ```
+
+1. Copy OpenSSH host keys from the **primary** node:
+
+ If you can access your **primary** node using the **root** user:
+
+ ```sh
+ # Run this from the secondary node, change `<primary_node_fqdn>` for the IP or FQDN of the server
+ scp root@<primary_node_fqdn>:/etc/ssh/ssh_host_*_key* /etc/ssh
+ ```
+
+ If you only have access through a user with **sudo** privileges:
+
+ ```sh
+ # Run this from your primary node:
+ sudo tar --transform 's/.*\///g' -zcvf ~/geo-host-key.tar.gz /etc/ssh/ssh_host_*_key*
+
+ # Run this from your secondary node:
+ scp <user_with_sudo>@<primary_node_fqdn>:geo-host-key.tar.gz .
+ tar zxvf ~/geo-host-key.tar.gz -C /etc/ssh
+ ```
+
+1. On your **secondary** node, ensure the file permissions are correct:
+
+ ```sh
+ chown root:root /etc/ssh/ssh_host_*_key*
+ chmod 0600 /etc/ssh/ssh_host_*_key*
+ ```
+
+1. To verify key fingerprint matches, execute the following command on both nodes:
+
+ ```sh
+ for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done
+ ```
+
+ You should get an output similar to this one and they should be identical on both nodes:
+
+ ```sh
+ 1024 SHA256:FEZX2jQa2bcsd/fn/uxBzxhKdx4Imc4raXrHwsbtP0M root@serverhostname (DSA)
+ 256 SHA256:uw98R35Uf+fYEQ/UnJD9Br4NXUFPv7JAUln5uHlgSeY root@serverhostname (ECDSA)
+ 256 SHA256:sqOUWcraZQKd89y/QQv/iynPTOGQxcOTIXU/LsoPmnM root@serverhostname (ED25519)
+ 2048 SHA256:qwa+rgir2Oy86QI+PZi/QVR+MSmrdrpsuH7YyKknC+s root@serverhostname (RSA)
+ ```
+
+1. Verify that you have the correct public keys for the existing private keys:
+
+ ```sh
+ # This will print the fingerprint for private keys:
+ for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done
+
+ # This will print the fingerprint for public keys:
+ for file in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf $file; done
+ ```
+
+ NOTE: **Note**:
+ The output for private keys and public keys command should generate the same fingerprint.
+
+1. Restart sshd on your **secondary** node:
+
+ ```sh
+ # Debian or Ubuntu installations
+ sudo service ssh reload
+
+ # CentOS installations
+ sudo service sshd reload
+ ```
+
+### Step 3. Add the **secondary** node
+
+1. Visit the **primary** node's **Admin Area > Geo**
+ (`/admin/geo/nodes`) in your browser.
+1. Add the **secondary** node by providing its full URL. **Do NOT** check the
+ **This is a primary node** checkbox.
+1. Optionally, choose which groups or storage shards should be replicated by the
+ **secondary** node. Leave blank to replicate all. Read more in
+ [selective synchronization](#selective-synchronization).
+1. Click the **Add node** button.
+1. SSH into your GitLab **secondary** server and restart the services:
+
+ ```sh
+ gitlab-ctl restart
+ ```
+
+ Check if there are any common issue with your Geo setup by running:
+
+ ```sh
+ gitlab-rake gitlab:geo:check
+ ```
+
+1. SSH into your **primary** server and login as root to verify the
+ **secondary** node is reachable or there are any common issue with your Geo setup:
+
+ ```sh
+ gitlab-rake gitlab:geo:check
+ ```
+
+Once added to the admin panel and restarted, the **secondary** node will automatically start
+replicating missing data from the **primary** node in a process known as **backfill**.
+Meanwhile, the **primary** node will start to notify each **secondary** node of any changes, so
+that the **secondary** node can act on those notifications immediately.
+
+Make sure the **secondary** node is running and accessible.
+You can login to the **secondary** node with the same credentials as used for the **primary** node.
+
+### Step 4. Enabling Hashed Storage
+
+Using Hashed Storage significantly improves Geo replication. Project and group
+renames no longer require synchronization between nodes.
+
+1. Visit the **primary** node's **Admin Area > Settings > Repository**
+ (`/admin/application_settings/repository`) in your browser.
+1. In the **Repository storage** section, check **Use hashed storage paths for newly created and renamed projects**.
+
+### Step 5. (Optional) Configuring the **secondary** node to trust the **primary** node
+
+You can safely skip this step if your **primary** node uses a CA-issued HTTPS certificate.
+
+If your **primary** node is using a self-signed certificate for *HTTPS* support, you will
+need to add that certificate to the **secondary** node's trust store. Retrieve the
+certificate from the **primary** node and follow
+[these instructions][omnibus-ssl]
+on the **secondary** node.
+
+### Step 6. Enable Git access over HTTP/HTTPS
+
+Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
+method to be enabled. Navigate to **Admin Area > Settings**
+(`/admin/application_settings`) on the **primary** node, and set
+`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
+
+### Step 7. Verify proper functioning of the **secondary** node
+
+Your **secondary** node is now configured!
+
+You can login to the **secondary** node with the same credentials you used for the
+**primary** node. Visit the **secondary** node's **Admin Area > Geo**
+(`/admin/geo/nodes`) in your browser to check if it's correctly identified as a
+**secondary** Geo node and if Geo is enabled.
+
+The initial replication, or 'backfill', will probably still be in progress. You
+can monitor the synchronization process on each geo node from the **primary**
+node's Geo Nodes dashboard in your browser.
+
+![Geo dashboard](img/geo_node_dashboard.png)
+
+If your installation isn't working properly, check the
+[troubleshooting document].
+
+The two most obvious issues that can become apparent in the dashboard are:
+
+1. Database replication not working well.
+1. Instance to instance notification not working. In that case, it can be
+ something of the following:
+ - You are using a custom certificate or custom CA (see the
+ [troubleshooting document]).
+ - The instance is firewalled (check your firewall rules).
+
+Please note that disabling a **secondary** node will stop the synchronization process.
+
+Please note that if `git_data_dirs` is customized on the **primary** node for multiple
+repository shards you must duplicate the same configuration on each **secondary** node.
+
+Point your users to the ["Using a Geo Server" guide][using-geo].
+
+Currently, this is what is synced:
+
+- Git repositories.
+- Wikis.
+- LFS objects.
+- Issues, merge requests, snippets, and comment attachments.
+- Users, groups, and project avatars.
+
+## Selective synchronization
+
+Geo supports selective synchronization, which allows admins to choose
+which projects should be synchronized by **secondary** nodes.
+A subset of projects can be chosen, either by group or by storage shard. The
+former is ideal for replicating data belonging to a subset of users, while the
+latter is more suited to progressively rolling out Geo to a large GitLab
+instance.
+
+It is important to note that selective synchronization:
+
+1. Does not restrict permissions from **secondary** nodes.
+1. Does not hide project metadata from **secondary** nodes.
+ - Since Geo currently relies on PostgreSQL replication, all project metadata
+ gets replicated to **secondary** nodes, but repositories that have not been
+ selected will be empty.
+1. Does not reduce the number of events generated for the Geo event log.
+ - The **primary** node generates events as long as any **secondary** nodes are present.
+ Selective synchronization restrictions are implemented on the **secondary** nodes,
+ not the **primary** node.
+
+## Upgrading Geo
+
+See the [updating the Geo nodes document](updating_the_geo_nodes.md).
+
+## Troubleshooting
+
+See the [troubleshooting document](troubleshooting.md).
+
+[setup-geo-omnibus]: index.md#using-omnibus-gitlab
+[Hashed Storage]: ../../repository_storage_types.md
+[Disaster Recovery]: ../disaster_recovery/index.md
+[gitlab-org/gitlab-ee#3789]: https://gitlab.com/gitlab-org/gitlab-ee/issues/3789
+[gitlab-com/infrastructure#2821]: https://gitlab.com/gitlab-com/infrastructure/issues/2821
+[omnibus-ssl]: https://docs.gitlab.com/omnibus/settings/ssl.html
+[troubleshooting document]: troubleshooting.md
+[using-geo]: using_a_geo_server.md
diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md
new file mode 100644
index 00000000000..1e5a56c3f4e
--- /dev/null
+++ b/doc/administration/geo/replication/database.md
@@ -0,0 +1,508 @@
+# Geo database replication **[PREMIUM ONLY]**
+
+NOTE: **Note:**
+The following steps are for Omnibus installs only. Using Geo with source-based installs was **deprecated** in GitLab 11.5.
+
+NOTE: **Note:**
+If your GitLab installation uses external (not managed by Omnibus) PostgreSQL
+instances, the Omnibus roles will not be able to perform all necessary
+configuration steps. In this case,
+[follow the Geo with external PostgreSQL instances document instead](external_database.md).
+
+NOTE: **Note:**
+The stages of the setup process must be completed in the documented order.
+Before attempting the steps in this stage, [complete all prior stages][toc].
+
+This document describes the minimal steps you have to take in order to
+replicate your **primary** GitLab database to a **secondary** node's database. You may
+have to change some values according to your database setup, how big it is, etc.
+
+You are encouraged to first read through all the steps before executing them
+in your testing/production environment.
+
+## PostgreSQL replication
+
+The GitLab **primary** node where the write operations happen will connect to
+the **primary** database server, and **secondary** nodes will
+connect to their own database servers (which are also read-only).
+
+NOTE: **Note:**
+In database documentation, you may see "**primary**" being referenced as "master"
+and "**secondary**" as either "slave" or "standby" server (read-only).
+
+We recommend using [PostgreSQL replication slots][replication-slots-article]
+to ensure that the **primary** node retains all the data necessary for the **secondary** nodes to
+recover. See below for more details.
+
+The following guide assumes that:
+
+- You are using Omnibus and therefore you are using PostgreSQL 9.6 or later
+ which includes the [`pg_basebackup` tool][pgback] and improved
+ [Foreign Data Wrapper][FDW] support.
+- You have a **primary** node already set up (the GitLab server you are
+ replicating from), running Omnibus' PostgreSQL (or equivalent version), and
+ you have a new **secondary** server set up with the same versions of the OS,
+ PostgreSQL, and GitLab on all nodes.
+
+CAUTION: **Warning:**
+Geo works with streaming replication. Logical replication is not supported at this time.
+There is an [issue where support is being discussed](https://gitlab.com/gitlab-org/gitlab-ee/issues/7420).
+
+### Step 1. Configure the **primary** server
+
+1. SSH into your GitLab **primary** server and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Execute the command below to define the node as **primary** node:
+
+ ```sh
+ gitlab-ctl set-geo-primary-node
+ ```
+
+ This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`.
+
+1. GitLab 10.4 and up only: Do the following to make sure the `gitlab` database user has a password defined:
+
+ Generate a MD5 hash of the desired password:
+
+ ```sh
+ gitlab-ctl pg-password-md5 gitlab
+ # Enter password: <your_password_here>
+ # Confirm password: <your_password_here>
+ # fca0b89a972d69f00eb3ec98a5838484
+ ```
+
+ Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+
+ # Every node that runs Unicorn or Sidekiq needs to have the database
+ # password specified as below. If you have a high-availability setup, this
+ # must be present in all application nodes.
+ gitlab_rails['db_password'] = '<your_password_here>'
+ ```
+
+1. Omnibus GitLab already has a [replication user]
+ called `gitlab_replicator`. You must set the password for this user manually.
+ You will be prompted to enter a password:
+
+ ```sh
+ gitlab-ctl set-replication-password
+ ```
+
+ This command will also read the `postgresql['sql_replication_user']` Omnibus
+ setting in case you have changed `gitlab_replicator` username to something
+ else.
+
+ If you are using an external database not managed by Omnibus GitLab, you need
+ to create the replicator user and define a password to it manually:
+
+ ```sql
+ --- Create a new user 'replicator'
+ CREATE USER gitlab_replicator;
+
+ --- Set/change a password and grants replication privilege
+ ALTER USER gitlab_replicator WITH REPLICATION ENCRYPTED PASSWORD '<replication_password>';
+ ```
+
+1. Configure PostgreSQL to listen on network interfaces:
+
+ For security reasons, PostgreSQL does not listen on any network interfaces
+ by default. However, Geo requires the **secondary** node to be able to
+ connect to the **primary** node's database. For this reason, we need the address of
+ each node. Note: For external PostgreSQL instances, see [additional instructions](external_database.md).
+
+ If you are using a cloud provider, you can lookup the addresses for each
+ Geo node through your cloud provider's management console.
+
+ To lookup the address of a Geo node, SSH in to the Geo node and execute:
+
+ ```sh
+ ##
+ ## Private address
+ ##
+ ip route get 255.255.255.255 | awk '{print "Private address:", $NF; exit}'
+
+ ##
+ ## Public address
+ ##
+ echo "External address: $(curl --silent ipinfo.io/ip)"
+ ```
+
+ In most cases, the following addresses will be used to configure GitLab
+ Geo:
+
+ | Configuration | Address |
+ |:----------------------------------------|:------------------------------------------------------|
+ | `postgresql['listen_address']` | **Primary** node's public or VPC private address. |
+ | `postgresql['md5_auth_cidr_addresses']` | **Secondary** node's public or VPC private addresses. |
+
+ If you are using Google Cloud Platform, SoftLayer, or any other vendor that
+ provides a virtual private cloud (VPC) you can use the **secondary** node's private
+ address (corresponds to "internal address" for Google Cloud Platform) for
+ `postgresql['md5_auth_cidr_addresses']` and `postgresql['listen_address']`.
+
+ The `listen_address` option opens PostgreSQL up to network connections
+ with the interface corresponding to the given address. See [the PostgreSQL
+ documentation][pg-docs-runtime-conn] for more details.
+
+ Depending on your network configuration, the suggested addresses may not
+ be correct. If your **primary** node and **secondary** nodes connect over a local
+ area network, or a virtual network connecting availability zones like
+ [Amazon's VPC](https://aws.amazon.com/vpc/) or [Google's VPC](https://cloud.google.com/vpc/)
+ you should use the **secondary** node's private address for `postgresql['md5_auth_cidr_addresses']`.
+
+ Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP
+ addresses with addresses appropriate to your network configuration:
+
+ ```ruby
+ ##
+ ## Geo Primary role
+ ## - configure dependent flags automatically to enable Geo
+ ##
+ roles ['geo_primary_role']
+
+ ##
+ ## Primary address
+ ## - replace '<primary_node_ip>' with the public or VPC address of your Geo primary node
+ ##
+ postgresql['listen_address'] = '<primary_node_ip>'
+
+ ##
+ # Allow PostgreSQL client authentication from the primary and secondary IPs. These IPs may be
+ # public or VPC addresses in CIDR format, for example ['198.51.100.1/32', '198.51.100.2/32']
+ ##
+ postgresql['md5_auth_cidr_addresses'] = ['<primary_node_ip>/32', '<secondary_node_ip>/32']
+
+ ##
+ ## Replication settings
+ ## - set this to be the number of Geo secondary nodes you have
+ ##
+ postgresql['max_replication_slots'] = 1
+ # postgresql['max_wal_senders'] = 10
+ # postgresql['wal_keep_segments'] = 10
+
+ ##
+ ## Disable automatic database migrations temporarily
+ ## (until PostgreSQL is restarted and listening on the private address).
+ ##
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. Optional: If you want to add another **secondary** node, the relevant setting would look like:
+
+ ```ruby
+ postgresql['md5_auth_cidr_addresses'] = ['<primary_node_ip>/32', '<secondary_node_ip>/32', '<another_secondary_node_ip>/32']
+ ```
+
+ You may also want to edit the `wal_keep_segments` and `max_wal_senders` to
+ match your database replication requirements. Consult the [PostgreSQL -
+ Replication documentation][pg-docs-runtime-replication]
+ for more information.
+
+1. Save the file and reconfigure GitLab for the database listen changes and
+ the replication slot changes to be applied:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+ Restart PostgreSQL for its changes to take effect:
+
+ ```sh
+ gitlab-ctl restart postgresql
+ ```
+
+1. Re-enable migrations now that PostgreSQL is restarted and listening on the
+ private address.
+
+ Edit `/etc/gitlab/gitlab.rb` and **change** the configuration to `true`:
+
+ ```ruby
+ gitlab_rails['auto_migrate'] = true
+ ```
+
+ Save the file and reconfigure GitLab:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+1. Now that the PostgreSQL server is set up to accept remote connections, run
+ `netstat -plnt | grep 5432` to make sure that PostgreSQL is listening on port
+ `5432` to the **primary** server's private address.
+
+1. A certificate was automatically generated when GitLab was reconfigured. This
+ will be used automatically to protect your PostgreSQL traffic from
+ eavesdroppers, but to protect against active ("man-in-the-middle") attackers,
+ the **secondary** node needs a copy of the certificate. Make a copy of the PostgreSQL
+ `server.crt` file on the **primary** node by running this command:
+
+ ```sh
+ cat ~gitlab-psql/data/server.crt
+ ```
+
+ Copy the output into a clipboard or into a local file. You
+ will need it when setting up the **secondary** node! The certificate is not sensitive
+ data.
+
+### Step 2. Configure the **secondary** server
+
+1. SSH into your GitLab **secondary** server and login as root:
+
+ ```
+ sudo -i
+ ```
+
+1. Stop application server and Sidekiq
+
+ ```
+ gitlab-ctl stop unicorn
+ gitlab-ctl stop sidekiq
+ ```
+
+ NOTE: **Note**:
+ This step is important so we don't try to execute anything before the node is fully configured.
+
+1. [Check TCP connectivity][rake-maintenance] to the **primary** node's PostgreSQL server:
+
+ ```sh
+ gitlab-rake gitlab:tcp_check[<primary_node_ip>,5432]
+ ```
+
+ NOTE: **Note**:
+ If this step fails, you may be using the wrong IP address, or a firewall may
+ be preventing access to the server. Check the IP address, paying close
+ attention to the difference between public and private addresses and ensure
+ that, if a firewall is present, the **secondary** node is permitted to connect to the
+ **primary** node on port 5432.
+
+1. Create a file `server.crt` in the **secondary** server, with the content you got on the last step of the **primary** node's setup:
+
+ ```
+ editor server.crt
+ ```
+
+1. Set up PostgreSQL TLS verification on the **secondary** node:
+
+ Install the `server.crt` file:
+
+ ```sh
+ install \
+ -D \
+ -o gitlab-psql \
+ -g gitlab-psql \
+ -m 0400 \
+ -T server.crt ~gitlab-psql/.postgresql/root.crt
+ ```
+
+ PostgreSQL will now only recognize that exact certificate when verifying TLS
+ connections. The certificate can only be replicated by someone with access
+ to the private key, which is **only** present on the **primary** node.
+
+1. Test that the `gitlab-psql` user can connect to the **primary** node's database
+ (the default Omnibus database name is gitlabhq_production):
+
+ ```sh
+ sudo \
+ -u gitlab-psql /opt/gitlab/embedded/bin/psql \
+ --list \
+ -U gitlab_replicator \
+ -d "dbname=gitlabhq_production sslmode=verify-ca" \
+ -W \
+ -h <primary_node_ip>
+ ```
+
+ When prompted enter the password you set in the first step for the
+ `gitlab_replicator` user. If all worked correctly, you should see
+ the list of **primary** node's databases.
+
+ A failure to connect here indicates that the TLS configuration is incorrect.
+ Ensure that the contents of `~gitlab-psql/data/server.crt` on the **primary** node
+ match the contents of `~gitlab-psql/.postgresql/root.crt` on the **secondary** node.
+
+1. Configure PostgreSQL to enable FDW support:
+
+ This step is similar to how we configured the **primary** instance.
+ We need to enable this, to enable FDW support, even if using a single node.
+
+ Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP
+ addresses with addresses appropriate to your network configuration:
+
+ ```ruby
+ ##
+ ## Geo Secondary role
+ ## - configure dependent flags automatically to enable Geo
+ ##
+ roles ['geo_secondary_role']
+
+ ##
+ ## Secondary address
+ ## - replace '<secondary_node_ip>' with the public or VPC address of your Geo secondary node
+ ##
+ postgresql['listen_address'] = '<secondary_node_ip>'
+ postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32']
+
+ ##
+ ## Database credentials password (defined previously in primary node)
+ ## - replicate same values here as defined in primary node
+ ##
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+ gitlab_rails['db_password'] = '<your_password_here>'
+
+ ##
+ ## Enable FDW support for the Geo Tracking Database (improves performance)
+ ##
+ geo_secondary['db_fdw'] = true
+ ```
+
+ For external PostgreSQL instances, see [additional instructions](external_database.md).
+ If you bring a former **primary** node back online to serve as a **secondary** node, then you also need to remove `roles ['geo_primary_role']` or `geo_primary_role['enable'] = true`.
+
+1. Reconfigure GitLab for the changes to take effect:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+1. Restart PostgreSQL for the IP change to take effect and reconfigure again:
+
+ ```sh
+ gitlab-ctl restart postgresql
+ gitlab-ctl reconfigure
+ ```
+
+ This last reconfigure will provision the FDW configuration and enable it.
+
+### Step 3. Initiate the replication process
+
+Below we provide a script that connects the database on the **secondary** node to
+the database on the **primary** node, replicates the database, and creates the
+needed files for streaming replication.
+
+The directories used are the defaults that are set up in Omnibus. If you have
+changed any defaults, configure it as you see fit replacing the directories and paths.
+
+CAUTION: **Warning:**
+Make sure to run this on the **secondary** server as it removes all PostgreSQL's
+data before running `pg_basebackup`.
+
+1. SSH into your GitLab **secondary** server and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Choose a database-friendly name to use for your **secondary** node to
+ use as the replication slot name. For example, if your domain is
+ `secondary.geo.example.com`, you may use `secondary_example` as the slot
+ name as shown in the commands below.
+
+1. Execute the command below to start a backup/restore and begin the replication
+ CAUTION: **Warning:** Each Geo **secondary** node must have its own unique replication slot name.
+ Using the same slot name between two secondaries will break PostgreSQL replication.
+
+ ```sh
+ gitlab-ctl replicate-geo-database \
+ --slot-name=<secondary_node_name> \
+ --host=<primary_node_ip>
+ ```
+
+ When prompted, enter the _plaintext_ password you set up for the `gitlab_replicator`
+ user in the first step.
+
+ This command also takes a number of additional options. You can use `--help`
+ to list them all, but here are a couple of tips:
+ - If PostgreSQL is listening on a non-standard port, add `--port=` as well.
+ - If your database is too large to be transferred in 30 minutes, you will need
+ to increase the timeout, e.g., `--backup-timeout=3600` if you expect the
+ initial replication to take under an hour.
+ - Pass `--sslmode=disable` to skip PostgreSQL TLS authentication altogether
+ (e.g., you know the network path is secure, or you are using a site-to-site
+ VPN). This is **not** safe over the public Internet!
+ - You can read more details about each `sslmode` in the
+ [PostgreSQL documentation][pg-docs-ssl];
+ the instructions above are carefully written to ensure protection against
+ both passive eavesdroppers and active "man-in-the-middle" attackers.
+ - Change the `--slot-name` to the name of the replication slot
+ to be used on the **primary** database. The script will attempt to create the
+ replication slot automatically if it does not exist.
+ - If you're repurposing an old server into a Geo **secondary** node, you'll need to
+ add `--force` to the command line.
+ - When not in a production machine you can disable backup step if you
+ really sure this is what you want by adding `--skip-backup`
+
+The replication process is now complete.
+
+## PGBouncer support (optional)
+
+[PGBouncer](http://pgbouncer.github.io/) may be used with GitLab Geo to pool
+PostgreSQL connections. We recommend using PGBouncer if you use GitLab in a
+high-availability configuration with a cluster of nodes supporting a Geo
+**primary** node and another cluster of nodes supporting a Geo **secondary** node. For more
+information, see [High Availability with GitLab Omnibus](../../high_availability/database.md#high-availability-with-gitlab-omnibus-premium-only).
+
+For a Geo **secondary** node to work properly with PGBouncer in front of the database,
+it will need a separate read-only user to make [PostgreSQL FDW queries][FDW]
+work:
+
+1. On the **primary** Geo database, enter the PostgreSQL on the console as an
+ admin user. If you are using an Omnibus-managed database, log onto the **primary**
+ node that is running the PostgreSQL database (the default Omnibus database name is gitlabhq_production):
+
+ ```sh
+ sudo \
+ -u gitlab-psql /opt/gitlab/embedded/bin/psql \
+ -h /var/opt/gitlab/postgresql gitlabhq_production
+ ```
+
+1. Then create the read-only user:
+
+ ```sql
+ -- NOTE: Use the password defined earlier
+ CREATE USER gitlab_geo_fdw WITH password 'mypassword';
+ GRANT CONNECT ON DATABASE gitlabhq_production to gitlab_geo_fdw;
+ GRANT USAGE ON SCHEMA public TO gitlab_geo_fdw;
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO gitlab_geo_fdw;
+ GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO gitlab_geo_fdw;
+
+ -- Tables created by "gitlab" should be made read-only for "gitlab_geo_fdw"
+ -- automatically.
+ ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON TABLES TO gitlab_geo_fdw;
+ ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON SEQUENCES TO gitlab_geo_fdw;
+ ```
+
+1. On the **secondary** nodes, change `/etc/gitlab/gitlab.rb`:
+
+ ```
+ geo_postgresql['fdw_external_user'] = 'gitlab_geo_fdw'
+ ```
+
+1. Save the file and reconfigure GitLab for the changes to be applied:
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+## MySQL replication
+
+MySQL replication is not supported for Geo.
+
+## Troubleshooting
+
+Read the [troubleshooting document](troubleshooting.md).
+
+[replication-slots-article]: https://medium.com/@tk512/replication-slots-in-postgresql-b4b03d277c75
+[pgback]: http://www.postgresql.org/docs/9.2/static/app-pgbasebackup.html
+[replication user]:https://wiki.postgresql.org/wiki/Streaming_Replication
+[FDW]: https://www.postgresql.org/docs/9.6/static/postgres-fdw.html
+[toc]: index.md#using-omnibus-gitlab
+[rake-maintenance]: ../../raketasks/maintenance.md
+[pg-docs-ssl]: https://www.postgresql.org/docs/9.6/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION
+[pg-docs-runtime-conn]: https://www.postgresql.org/docs/9.6/static/runtime-config-connection.html
+[pg-docs-runtime-replication]: https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html
diff --git a/doc/administration/geo/replication/docker_registry.md b/doc/administration/geo/replication/docker_registry.md
new file mode 100644
index 00000000000..5b02b861c61
--- /dev/null
+++ b/doc/administration/geo/replication/docker_registry.md
@@ -0,0 +1,23 @@
+# Docker Registry for a secondary node **[PREMIUM ONLY]**
+
+You can set up a [Docker Registry] on your
+**secondary** Geo node that mirrors the one on the **primary** Geo node.
+
+## Storage support
+
+CAUTION: **Warning:**
+If you use [local storage][registry-storage]
+for the Container Registry you **cannot** replicate it to a **secondary** node.
+
+Docker Registry currently supports a few types of storages. If you choose a
+distributed storage (`azure`, `gcs`, `s3`, `swift`, or `oss`) for your Docker
+Registry on the **primary** node, you can use the same storage for a **secondary**
+Docker Registry as well. For more information, read the
+[Load balancing considerations][registry-load-balancing]
+when deploying the Registry, and how to set up the storage driver for GitLab's
+integrated [Container Registry][registry-storage].
+
+[ee]: https://about.gitlab.com/pricing/
+[Docker Registry]: https://docs.docker.com/registry/
+[registry-storage]: ../../container_registry.md#container-registry-storage-driver
+[registry-load-balancing]: https://docs.docker.com/registry/deploying/#load-balancing-considerations
diff --git a/doc/administration/geo/replication/external_database.md b/doc/administration/geo/replication/external_database.md
new file mode 100644
index 00000000000..177ca68613e
--- /dev/null
+++ b/doc/administration/geo/replication/external_database.md
@@ -0,0 +1,219 @@
+# Geo with external PostgreSQL instances **[PREMIUM ONLY]**
+
+This document is relevant if you are using a PostgreSQL instance that is *not
+managed by Omnibus*. This includes cloud-managed instances like AWS RDS, or
+manually installed and configured PostgreSQL instances.
+
+NOTE: **Note**:
+We strongly recommend running Omnibus-managed instances as they are actively
+developed and tested. We aim to be compatible with most external
+(not managed by Omnibus) databases but we do not guarantee compatibility.
+
+## **Primary** node
+
+1. SSH into a GitLab **primary** application server and login as root:
+
+ ```sh
+ sudo -i
+ ```
+
+1. Execute the command below to define the node as **primary** node:
+
+ ```sh
+ gitlab-ctl set-geo-primary-node
+ ```
+
+ This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`.
+
+
+### Configure the external database to be replicated
+
+To set up an external database, you can either:
+
+- Set up streaming replication yourself (for example, in AWS RDS).
+- Perform the Omnibus configuration manually as follows.
+
+#### Leverage your cloud provider's tools to replicate the primary database
+
+Given you have a primary node set up on AWS EC2 that uses RDS.
+You can now just create a read-only replica in a different region and the
+replication process will be managed by AWS. Make sure you've set Network ACL, Subnet, and
+Security Group according to your needs, so the secondary application node can access the database.
+Skip to the [Configure secondary application node](#configure-secondary-application-nodes-to-use-the-external-read-replica) section below.
+
+#### Manually configure the primary database for replication
+
+The [geo_primary_role](https://docs.gitlab.com/omnibus/roles/#gitlab-geo-roles)
+configures the **primary** node's database to be replicated by making changes to
+`pg_hba.conf` and `postgresql.conf`. Make the following configuration changes
+manually to your external database configuration:
+
+```
+##
+## Geo Primary Role
+## - pg_hba.conf
+##
+host replication gitlab_replicator <trusted secondary IP>/32 md5
+```
+
+```
+##
+## Geo Primary Role
+## - postgresql.conf
+##
+sql_replication_user = gitlab_replicator
+wal_level = hot_standby
+max_wal_senders = 10
+wal_keep_segments = 50
+max_replication_slots = 1 # number of secondary instances
+hot_standby = on
+```
+
+## **Secondary** nodes
+
+### Manually configure the replica database
+
+Make the following configuration changes manually to your `postgresql.conf`
+of external replica database:
+
+```
+##
+## Geo Secondary Role
+## - postgresql.conf
+##
+wal_level = hot_standby
+max_wal_senders = 10
+wal_keep_segments = 10
+hot_standby = on
+```
+
+### Configure **secondary** application nodes to use the external read-replica
+
+With Omnibus, the
+[geo_secondary_role](https://docs.gitlab.com/omnibus/roles/#gitlab-geo-roles)
+has three main functions:
+
+1. Configure the replica database.
+1. Configure the tracking database.
+1. Enable the [Geo Log Cursor](index.md#geo-log-cursor) (not covered in this section).
+
+To configure the connection to the external read-replica database and enable Log Cursor:
+
+1. SSH into a GitLab **secondary** application server and login as root:
+
+ ```bash
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following
+
+ ```ruby
+ ##
+ ## Geo Secondary role
+ ## - configure dependent flags automatically to enable Geo
+ ##
+ roles ['geo_secondary_role']
+
+ # note this is shared between both databases,
+ # make sure you define the same password in both
+ gitlab_rails['db_password'] = '<your_password_here>'
+
+ gitlab_rails['db_username'] = 'gitlab'
+ gitlab_rails['db_host'] = '<database_read_replica_host>'
+ ```
+1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure)
+
+### Configure the tracking database
+
+**Secondary** nodes use a separate PostgreSQL installation as a tracking
+database to keep track of replication status and automatically recover from
+potential replication issues. Omnibus automatically configures a tracking database
+when `roles ['geo_secondary_role']` is set. For high availability,
+refer to [Geo High Availability](../../high_availability/README.md).
+If you want to run this database external to Omnibus, please follow the instructions below.
+
+The tracking database requires an [FDW](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
+connection with the **secondary** replica database for improved performance.
+
+If you have an external database ready to be used as the tracking database,
+follow the instructions below to use it:
+
+NOTE: **Note:**
+If you want to use AWS RDS as a tracking database, make sure it has access to
+the secondary database. Unfortunately, just assigning the same security group is not enough as
+outbound rules do not apply to RDS PostgreSQL databases. Therefore, you need to explicitly add an inbound
+rule to the read-replica's security group allowing any TCP traffic from
+the tracking database on port 5432.
+
+1. SSH into a GitLab **secondary** server and login as root:
+
+ ```bash
+ sudo -i
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` with the connection params and credentials for
+ the machine with the PostgreSQL instance:
+
+ ```ruby
+ geo_secondary['db_username'] = 'gitlab_geo'
+ geo_secondary['db_password'] = '<your_password_here>'
+
+ geo_secondary['db_host'] = '<tracking_database_host>'
+ geo_secondary['db_port'] = <tracking_database_port> # change to the correct port
+ geo_secondary['db_fdw'] = true # enable FDW
+ geo_postgresql['enable'] = false # don't use internal managed instance
+ ```
+
+1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure)
+
+1. Run the tracking database migrations:
+
+ ```bash
+ gitlab-rake geo:db:create
+ gitlab-rake geo:db:migrate
+ ```
+
+1. Configure the
+ [PostgreSQL FDW](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
+ connection and credentials:
+
+ Save the script below in a file, ex. `/tmp/geo_fdw.sh` and modify the connection
+ params to match your environment. Execute it to set up the FDW connection.
+
+ ```bash
+ #!/bin/bash
+
+ # Secondary Database connection params:
+ DB_HOST="<public_ip_or_vpc_private_ip>"
+ DB_NAME="gitlabhq_production"
+ DB_USER="gitlab"
+ DB_PASS="<your_password_here>"
+ DB_PORT="5432"
+
+ # Tracking Database connection params:
+ GEO_DB_HOST="<public_ip_or_vpc_private_ip>"
+ GEO_DB_NAME="gitlabhq_geo_production"
+ GEO_DB_USER="gitlab_geo"
+ GEO_DB_PORT="5432"
+
+ query_exec () {
+ gitlab-psql -h $GEO_DB_HOST -d $GEO_DB_NAME -p $GEO_DB_PORT -c "${1}"
+ }
+
+ query_exec "CREATE EXTENSION postgres_fdw;"
+ query_exec "CREATE SERVER gitlab_secondary FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '${DB_HOST}', dbname '${DB_NAME}', port '${DB_PORT}');"
+ query_exec "CREATE USER MAPPING FOR ${GEO_DB_USER} SERVER gitlab_secondary OPTIONS (user '${DB_USER}', password '${DB_PASS}');"
+ query_exec "CREATE SCHEMA gitlab_secondary;"
+ query_exec "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO ${GEO_DB_USER};"
+ ```
+
+ NOTE: **Note:** The script template above uses `gitlab-psql` as it's intended to be executed from the Geo machine,
+ but you can change it to `psql` and run it from any machine that has access to the database. We also recommend using
+ `psql` for AWS RDS.
+
+1. Save the file and [restart GitLab](../../restart_gitlab.md#omnibus-gitlab-restart)
+1. Populate the FDW tables:
+
+ ```bash
+ gitlab-rake geo:db:refresh_foreign_tables
+ ```
diff --git a/doc/administration/geo/replication/faq.md b/doc/administration/geo/replication/faq.md
new file mode 100644
index 00000000000..dd1af0dbf9c
--- /dev/null
+++ b/doc/administration/geo/replication/faq.md
@@ -0,0 +1,62 @@
+# Geo Frequently Asked Questions **[PREMIUM ONLY]**
+
+## What are the minimum requirements to run Geo?
+
+The requirements are listed [on the index page](index.md#requirements-for-running-geo)
+
+## How does Geo know which projects to sync?
+
+On each **secondary** node, there is a read-only replicated copy of the GitLab database.
+A **secondary** node also has a tracking database where it stores which projects have been synced.
+Geo compares the two databases to find projects that are not yet tracked.
+
+At the start, this tracking database is empty, so Geo will start trying to update from every project that it can see in the GitLab database.
+
+For each project to sync:
+
+1. Geo will issue a `git fetch geo --mirror` to get the latest information from the **primary** node.
+If there are no changes, the sync will be fast and end quickly. Otherwise, it will pull the latest commits.
+1. The **secondary** node will update the tracking database to store the fact that it has synced projects A, B, C, etc.
+1. Repeat until all projects are synced.
+
+When someone pushes a commit to the **primary** node, it generates an event in the GitLab database that the repository has changed.
+The **secondary** node sees this event, marks the project in question as dirty, and schedules the project to be resynced.
+
+To ensure that problems with pipelines (for example, syncs failing too many times or jobs being lost) don't permanently stop projects syncing, Geo also periodically checks the tracking database for projects that are marked as dirty. This check happens when
+the number of concurrent syncs falls below `repos_max_capacity` and there are no new projects waiting to be synced.
+
+Geo also has a checksum feature which runs a SHA256 sum across all the Git references to the SHA values.
+If the refs don't match between the **primary** node and the **secondary** node, then the **secondary** node will mark that project as dirty and try to resync it.
+So even if we have an outdated tracking database, the validation should activate and find discrepancies in the repository state and resync.
+
+## Can I use Geo in a disaster recovery situation?
+
+Yes, but there are limitations to what we replicate (see
+[What data is replicated to a **secondary** node?](#what-data-is-replicated-to-a-secondary-node)).
+
+Read the documentation for [Disaster Recovery](../disaster_recovery/index.md).
+
+## What data is replicated to a **secondary** node?
+
+We currently replicate project repositories, LFS objects, generated
+attachments / avatars and the whole database. This means user accounts,
+issues, merge requests, groups, project data, etc., will be available for
+query.
+
+## Can I git push to a **secondary** node?
+
+Yes! Pushing directly to a **secondary** node (for both HTTP and SSH, including git-lfs) was [introduced](https://about.gitlab.com/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
+
+## How long does it take to have a commit replicated to a **secondary** node?
+
+All replication operations are asynchronous and are queued to be dispatched. Therefore, it depends on a lot of
+factors including the amount of traffic, how big your commit is, the
+connectivity between your nodes, your hardware, etc.
+
+## What if the SSH server runs at a different port?
+
+That's totally fine. We use HTTP(s) to fetch repository changes from the **primary** node to all **secondary** nodes.
+
+## Is this possible to set up a Docker Registry for a **secondary** node that mirrors the one on the **primary** node?
+
+Yes. See [Docker Registry for a **secondary** node](docker_registry.md).
diff --git a/doc/administration/geo/replication/high_availability.md b/doc/administration/geo/replication/high_availability.md
new file mode 100644
index 00000000000..921a3ef1c7a
--- /dev/null
+++ b/doc/administration/geo/replication/high_availability.md
@@ -0,0 +1,295 @@
+# Geo High Availability **[PREMIUM ONLY]**
+
+This document describes a minimal reference architecture for running Geo
+in a high availability configuration. If your HA setup differs from the one
+described, it is possible to adapt these instructions to your needs.
+
+## Architecture overview
+
+![Geo HA Diagram](../../high_availability/img/geo-ha-diagram.png)
+
+_[diagram source - gitlab employees only][diagram-source]_
+
+The topology above assumes that the **primary** and **secondary** Geo clusters
+are located in two separate locations, on their own virtual network
+with private IP addresses. The network is configured such that all machines within
+one geographic location can communicate with each other using their private IP addresses.
+The IP addresses given are examples and may be different depending on the
+network topology of your deployment.
+
+The only external way to access the two Geo deployments is by HTTPS at
+`gitlab.us.example.com` and `gitlab.eu.example.com` in the example above.
+
+NOTE: **Note:**
+The **primary** and **secondary** Geo deployments must be able to communicate to each other over HTTPS.
+
+## Redis and PostgreSQL High Availability
+
+The **primary** and **secondary** Redis and PostgreSQL should be configured
+for high availability. Because of the additional complexity involved
+in setting up this configuration for PostgreSQL and Redis,
+it is not covered by this Geo HA documentation.
+
+For more information about setting up a highly available PostgreSQL cluster and Redis cluster using the omnibus package see the high availability documentation for
+[PostgreSQL](../../high_availability/database.md) and
+[Redis](../../high_availability/redis.md), respectively.
+
+NOTE: **Note:**
+It is possible to use cloud hosted services for PostgreSQL and Redis, but this is beyond the scope of this document.
+
+## Prerequisites: A working GitLab HA cluster
+
+This cluster will serve as the **primary** node. Use the
+[GitLab HA documentation](../../high_availability/README.md) to set this up.
+
+## Configure the GitLab cluster to be the **primary** node
+
+The following steps enable a GitLab cluster to serve as the **primary** node.
+
+### Step 1: Configure the **primary** frontend servers
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following:
+
+ ```ruby
+ ##
+ ## Enable the Geo primary role
+ ##
+ roles ['geo_primary_role']
+
+ ##
+ ## Disable automatic migrations
+ ##
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+After making these changes, [reconfigure GitLab][gitlab-reconfigure] so the changes take effect.
+
+NOTE: **Note:** PostgreSQL and Redis should have already been disabled on the
+application servers, and connections from the application servers to those
+services on the backend servers configured, during normal GitLab HA set up. See
+high availability configuration documentation for
+[PostgreSQL](../../high_availability/database.md#configuring-the-application-nodes)
+and [Redis](../../high_availability/redis.md#example-configuration-for-the-gitlab-application).
+
+The **primary** database will require modification later, as part of
+[step 2](#step-2-configure-the-main-read-only-replica-postgresql-database-on-the-secondary-node).
+
+## Configure a **secondary** node
+
+A **secondary** cluster is similar to any other GitLab HA cluster, with two
+major differences:
+
+* The main PostgreSQL database is a read-only replica of the **primary** node's
+ PostgreSQL database.
+* There is also a single PostgreSQL database for the **secondary** cluster,
+ called the "tracking database", which tracks the synchronization state of
+ various resources.
+
+Therefore, we will set up the HA components one-by-one, and include deviations
+from the normal HA setup.
+
+### Step 1: Configure the Redis and NFS services on the **secondary** node
+
+Configure the following services, again using the non-Geo high availability
+documentation:
+
+* [Configuring Redis for GitLab HA](../../high_availability/redis.md) for high
+ availability.
+* [NFS](../../high_availability/nfs.md) which will store data that is
+ synchronized from the **primary** node.
+
+### Step 2: Configure the main read-only replica PostgreSQL database on the **secondary** node
+
+NOTE: **Note:** The following documentation assumes the database will be run on
+a single node only, rather than as a PostgreSQL cluster.
+
+Configure the [**secondary** database](database.md) as a read-only replica of
+the **primary** database. Use the following as a guide.
+
+1. Edit `/etc/gitlab/gitlab.rb` in the replica database machine, and add the
+ following:
+
+ ```ruby
+ ##
+ ## Configure the PostgreSQL role
+ ##
+ roles ['postgres_role']
+
+ ##
+ ## Secondary address
+ ## - replace '<secondary_node_ip>' with the public or VPC address of your Geo secondary node
+ ## - replace '<tracking_database_ip>' with the public or VPC address of your Geo tracking database node
+ ##
+ postgresql['listen_address'] = '<secondary_node_ip>'
+ postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32', '<tracking_database_ip>/32']
+
+ ##
+ ## Database credentials password (defined previously in primary node)
+ ## - replicate same values here as defined in primary node
+ ##
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+ gitlab_rails['db_password'] = '<your_password_here>'
+
+ ##
+ ## When running the Geo tracking database on a separate machine, disable it
+ ## here and allow connections from the tracking database host. And ensure
+ ## the tracking database IP is in postgresql['md5_auth_cidr_addresses'] above.
+ ##
+ geo_postgresql['enable'] = false
+ ```
+
+After making these changes, [reconfigure GitLab][gitlab-reconfigure] so the changes take effect.
+
+If using an external PostgreSQL instance, refer also to
+[Geo with external PostgreSQL instances](external_database.md).
+
+### Step 3: Configure the tracking database on the **secondary** node
+
+NOTE: **Note:** This documentation assumes the tracking database will be run on
+only a single machine, rather than as a PostgreSQL cluster.
+
+Configure the tracking database.
+
+1. Edit `/etc/gitlab/gitlab.rb` in the tracking database machine, and add the
+ following:
+
+ ```ruby
+ ##
+ ## Enable the Geo secondary tracking database
+ ##
+ geo_postgresql['enable'] = true
+ geo_postgresql['listen_address'] = '<ip_address_of_this_host>'
+ geo_postgresql['sql_user_password'] = '<tracking_database_password_md5_hash>'
+
+ ##
+ ## Configure FDW connection to the replica database
+ ##
+ geo_secondary['db_fdw'] = true
+ geo_postgresql['fdw_external_password'] = '<replica_database_password_plaintext>'
+ geo_postgresql['md5_auth_cidr_addresses'] = ['<replica_database_ip>/32']
+ gitlab_rails['db_host'] = '<replica_database_ip>'
+
+ # Prevent reconfigure from attempting to run migrations on the replica DB
+ gitlab_rails['auto_migrate'] = false
+
+ ##
+ ## Disable all other services that aren't needed, since we don't have a role
+ ## that does this.
+ ##
+ alertmanager['enable'] = false
+ consul['enable'] = false
+ gitaly['enable'] = false
+ gitlab_monitor['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = false
+ node_exporter['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ postgresql['enable'] = false
+ prometheus['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ repmgr['enable'] = false
+ sidekiq['enable'] = false
+ unicorn['enable'] = false
+ ```
+
+After making these changes, [reconfigure GitLab][gitlab-reconfigure] so the changes take effect.
+
+If using an external PostgreSQL instance, refer also to
+[Geo with external PostgreSQL instances](external_database.md).
+
+### Step 4: Configure the frontend application servers on the **secondary** node
+
+In the architecture overview, there are two machines running the GitLab
+application services. These services are enabled selectively in the
+configuration.
+
+Configure the application servers following
+[Configuring GitLab for HA](../../high_availability/gitlab.md), then make the
+following modifications:
+
+1. Edit `/etc/gitlab/gitlab.rb` on each application server in the **secondary**
+ cluster, and add the following:
+
+ ```ruby
+ ##
+ ## Enable the Geo secondary role
+ ##
+ roles ['geo_secondary_role', 'application_role']
+
+ ##
+ ## Disable automatic migrations
+ ##
+ gitlab_rails['auto_migrate'] = false
+
+ ##
+ ## Configure the connection to the tracking DB. And disable application
+ ## servers from running tracking databases.
+ ##
+ geo_secondary['db_host'] = '<geo_tracking_db_host>'
+ geo_secondary['db_password'] = '<geo_tracking_db_password>'
+ geo_postgresql['enable'] = false
+
+ ##
+ ## Configure connection to the streaming replica database, if you haven't
+ ## already
+ ##
+ gitlab_rails['db_host'] = '<replica_database_host>'
+ gitlab_rails['db_password'] = '<replica_database_password>'
+
+ ##
+ ## Configure connection to Redis, if you haven't already
+ ##
+ gitlab_rails['redis_host'] = '<redis_host>'
+ gitlab_rails['redis_password'] = '<redis_password>'
+
+ ##
+ ## If you are using custom users not managed by Omnibus, you need to specify
+ ## UIDs and GIDs like below, and ensure they match between servers in a
+ ## cluster to avoid permissions issues
+ ##
+ user['uid'] = 9000
+ user['gid'] = 9000
+ web_server['uid'] = 9001
+ web_server['gid'] = 9001
+ registry['uid'] = 9002
+ registry['gid'] = 9002
+ ```
+
+NOTE: **Note:**
+If you had set up PostgreSQL cluster using the omnibus package and you had set
+up `postgresql['sql_user_password'] = 'md5 digest of secret'` setting, keep in
+mind that `gitlab_rails['db_password']` and `geo_secondary['db_password']`
+mentioned above contains the plaintext passwords. This is used to let the Rails
+servers connect to the databases.
+
+NOTE: **Note:**
+Make sure that current node IP is listed in `postgresql['md5_auth_cidr_addresses']` setting of your remote database.
+
+After making these changes [Reconfigure GitLab][gitlab-reconfigure] so the changes take effect.
+
+On the secondary the following GitLab frontend services will be enabled:
+
+* geo-logcursor
+* gitlab-pages
+* gitlab-workhorse
+* logrotate
+* nginx
+* registry
+* remote-syslog
+* sidekiq
+* unicorn
+
+Verify these services by running `sudo gitlab-ctl status` on the frontend
+application servers.
+
+### Step 5: Set up the LoadBalancer for the **secondary** node
+
+In this topology, a load balancer is required at each geographic location to
+route traffic to the application servers.
+
+See [Load Balancer for GitLab HA](../../high_availability/load_balancer.md) for
+more information.
+
+[diagram-source]: https://docs.google.com/drawings/d/1z0VlizKiLNXVVVaERFwgsIOuEgjcUqDTWPdQYsE7Z4c/edit
+[gitlab-reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/geo/replication/img/geo_architecture.png b/doc/administration/geo/replication/img/geo_architecture.png
new file mode 100644
index 00000000000..d318cd5d0f4
--- /dev/null
+++ b/doc/administration/geo/replication/img/geo_architecture.png
Binary files differ
diff --git a/doc/administration/geo/replication/img/geo_node_dashboard.png b/doc/administration/geo/replication/img/geo_node_dashboard.png
new file mode 100644
index 00000000000..99792d0770d
--- /dev/null
+++ b/doc/administration/geo/replication/img/geo_node_dashboard.png
Binary files differ
diff --git a/doc/administration/geo/replication/img/geo_node_healthcheck.png b/doc/administration/geo/replication/img/geo_node_healthcheck.png
new file mode 100644
index 00000000000..33a31f7ab49
--- /dev/null
+++ b/doc/administration/geo/replication/img/geo_node_healthcheck.png
Binary files differ
diff --git a/doc/administration/geo/replication/img/geo_overview.png b/doc/administration/geo/replication/img/geo_overview.png
new file mode 100644
index 00000000000..01c1615212c
--- /dev/null
+++ b/doc/administration/geo/replication/img/geo_overview.png
Binary files differ
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
new file mode 100644
index 00000000000..54377f7ae4e
--- /dev/null
+++ b/doc/administration/geo/replication/index.md
@@ -0,0 +1,292 @@
+# Geo Replication **[PREMIUM ONLY]**
+
+Geo is the solution for widely distributed development teams.
+
+## Overview
+
+Fetching large repositories can take a long time for teams located far from a single GitLab instance.
+
+Geo provides local, read-only instances of your GitLab instances, reducing the time it takes to clone and fetch large repositories and speeding up development.
+
+> - Geo is part of [GitLab Premium](https://about.gitlab.com/pricing/#self-managed).
+> - Introduced in GitLab Enterprise Edition 8.9.
+> - We recommend you use:
+> - At least GitLab Enterprise Edition 10.0 for basic Geo features.
+> - The latest version for a better experience.
+> - Make sure that all nodes run the same GitLab version.
+> - Geo requires PostgreSQL 9.6 and Git 2.9, in addition to GitLab's usual [minimum requirements](../../../install/requirements.md).
+> - Using Geo in combination with [High Availability](../../high_availability/README.md) is considered **Generally Available** (GA) in GitLab [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
+
+For a video introduction to Geo, see [Introduction to GitLab Geo - GitLab Features](https://www.youtube.com/watch?v=-HDLxSjEh6w).
+
+CAUTION: **Caution:**
+Geo undergoes significant changes from release to release. Upgrades **are** supported and [documented](#updating-geo), but you should ensure that you're using the right version of the documentation for your installation.
+
+To make sure you're using the right version of the documentation, navigate to [the source version of this page on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/administration/geo/replication/index.md) and choose the appropriate release from the **Switch branch/tag** dropdown. For example, [`v11.2.3-ee`](https://gitlab.com/gitlab-org/gitlab-ee/blob/v11.2.3-ee/doc/administration/geo/replication/index.md).
+
+## Use cases
+
+Implementing Geo provides the following benefits:
+
+- Reduce from minutes to seconds the time taken for your distributed developers to clone and fetch large repositories and projects.
+- Enable all of your developers to contribute ideas and work in parallel, no matter where they are.
+- Balance the load between your **primary** and **secondary** nodes, or offload your automated tests to a **secondary** node.
+
+In addition, it:
+
+- Can be used for cloning and fetching projects, in addition to reading any data available in the GitLab web interface (see [current limitations](#current-limitations)).
+- Overcomes slow connections between distant offices, saving time by improving speed for distributed teams.
+- Helps reducing the loading time for automated tasks, custom integrations, and internal workflows.
+- Can quickly fail over to a **secondary** node in a [disaster recovery](../disaster_recovery/index.md) scenario.
+- Allows [planned failover](../disaster_recovery/planned_failover.md) to a **secondary** node.
+
+Geo provides:
+
+- Read-only **secondary** nodes: Maintain one **primary** GitLab node while still enabling read-only **secondary** nodes for each of your distributed teams.
+- Authentication system hooks: **Secondary** nodes receives all authentication data (like user accounts and logins) from the **primary** instance.
+- An intuitive UI: **Secondary** nodes utilize the same web interface your team has grown accustomed to. In addition, there are visual notifications that block write operations and make it clear that a user is on a **secondary** node.
+
+## How it works
+
+Your Geo instance can be used for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
+
+![Geo overview](img/geo_overview.png)
+
+When Geo is enabled, the:
+
+- Original instance is known as the **primary** node.
+- Replicated read-only nodes are known as **secondary** nodes.
+
+Keep in mind that:
+
+- **Secondary** nodes talk to the **primary** node to:
+ - Get user data for logins (API).
+ - Replicate repositories, LFS Objects, and Attachments (HTTPS + JWT).
+- Since GitLab Premium 10.0, the **primary** node no longer talks to **secondary** nodes to notify for changes (API).
+- Pushing directly to a **secondary** node (for both HTTP and SSH, including git-lfs) was [introduced](https://about.gitlab.com/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
+- There are [limitations](#current-limitations) in the current implementation.
+
+### Architecture
+
+The following diagram illustrates the underlying architecture of Geo.
+
+![Geo architecture](img/geo_architecture.png)
+
+In this diagram:
+
+- There is the **primary** node and the details of one **secondary** node.
+- Writes to the database can only be performed on the **primary** node. A **secondary** node receives database
+ updates via PostgreSQL streaming replication.
+- If present, the [LDAP server](#ldap) should be configured to replicate for [Disaster Recovery](../disaster_recovery/index.md) scenarios.
+- A **secondary** node performs different type of synchronizations against the **primary** node, using a special
+ authorization protected by JWT:
+ - Repositories are cloned/updated via Git over HTTPS.
+ - Attachments, LFS objects, and other files are downloaded via HTTPS using a private API endpoint.
+
+From the perspective of a user performing Git operations:
+
+- The **primary** node behaves as a full read-write GitLab instance.
+- **Secondary** nodes are read-only but proxy Git push operations to the **primary** node. This makes **secondary** nodes appear to support push operations themselves.
+
+To simplify the diagram, some necessary components are omitted. Note that:
+
+- Git over SSH requires [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell) and OpenSSH.
+- Git over HTTPS required [`gitlab-workhorse`](https://gitlab.com/gitlab-org/gitlab-workhorse).
+
+Note that a **secondary** node needs two different PostgreSQL databases:
+
+- A read-only database instance that streams data from the main GitLab database.
+- [Another database instance](#geo-tracking-database) used internally by the **secondary** node to record what data has been replicated.
+
+In **secondary** nodes, there is an additional daemon: [Geo Log Cursor](#geo-log-cursor).
+
+## Requirements for running Geo
+
+The following are required to run Geo:
+
+- An operating system that supports OpenSSH 6.9+ (needed for
+ [fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md))
+ The following operating systems are known to ship with a current version of OpenSSH:
+ - [CentOS](https://www.centos.org) 7.4+
+ - [Ubuntu](https://www.ubuntu.com) 16.04+
+- PostgreSQL 9.6+ with [FDW](https://www.postgresql.org/docs/9.6/postgres-fdw.html) support and [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
+- Git 2.9+
+
+### Firewall rules
+
+The following table lists basic ports that must be open between the **primary** and **secondary** nodes for Geo.
+
+| **Primary** node | **Secondary** node | Protocol |
+|:-----------------|:-------------------|:-------------|
+| 80 | 80 | HTTP |
+| 443 | 443 | TCP or HTTPS |
+| 22 | 22 | TCP |
+| 5432 | | PostgreSQL |
+
+See the full list of ports used by GitLab in [Package defaults](https://docs.gitlab.com/omnibus/package-information/defaults.html)
+
+NOTE: **Note:**
+[Web terminal](../../../ci/environments.md#web-terminals) support requires your load balancer to correctly handle WebSocket connections.
+When using HTTP or HTTPS proxying, your load balancer must be configured to pass through the `Connection` and `Upgrade` hop-by-hop headers. See the [web terminal](../../integration/terminal.md) integration guide for more details.
+
+NOTE: **Note:**
+When using HTTPS protocol for port 443, you will need to add an SSL certificate to the load balancers.
+If you wish to terminate SSL at the GitLab application server instead, use TCP protocol.
+
+### LDAP
+
+We recommend that if you use LDAP on your **primary** node, you also set up secondary LDAP servers on each **secondary** node. Otherwise, users will not be able to perform Git operations over HTTP(s) on the **secondary** node using HTTP Basic Authentication. However, Git via SSH and personal access tokens will still work.
+
+NOTE: **Note:**
+It is possible for all **secondary** nodes to share an LDAP server, but additional latency can be an issue. Also, consider what LDAP server will be available in a [disaster recovery](../disaster_recovery/index.md) scenario if a **secondary** node is promoted to be a **primary** node.
+
+Check for instructions on how to set up replication in your LDAP service. Instructions will be different depending on the software or service used. For example, OpenLDAP provides [these instructions](https://www.openldap.org/doc/admin24/replication.html).
+
+### Geo Tracking Database
+
+The tracking database instance is used as metadata to control what needs to be updated on the disk of the local instance. For example:
+
+- Download new assets.
+- Fetch new LFS Objects.
+- Fetch changes from a repository that has recently been updated.
+
+Because the replicated database instance is read-only, we need this additional database instance for each **secondary** node.
+The tracking database requires the `postgres_fdw` extension.
+
+### Geo Log Cursor
+
+This daemon:
+
+- Reads a log of events replicated by the **primary** node to the **secondary** database instance.
+- Updates the Geo Tracking Database instance with changes that need to be executed.
+
+When something is marked to be updated in the tracking database instance, asynchronous jobs running on the **secondary** node will execute the required operations and update the state.
+
+This new architecture allows GitLab to be resilient to connectivity issues between the nodes. It doesn't matter how long the **secondary** node is disconnected from the **primary** node as it will be able to replay all the events in the correct order and become synchronized with the **primary** node again.
+
+## Setup instructions
+
+These instructions assume you have a working instance of GitLab. They guide you through:
+
+1. Making your existing instance the **primary** node.
+1. Adding **secondary** nodes.
+
+CAUTION: **Caution:**
+The steps below should be followed in the order they appear. **Make sure the GitLab version is the same on all nodes.**
+
+### Using Omnibus GitLab
+
+If you installed GitLab using the Omnibus packages (highly recommended):
+
+1. [Install GitLab Enterprise Edition](https://about.gitlab.com/installation/) on the server that will serve as the **secondary** node. Do not create an account or log in to the new **secondary** node.
+1. [Upload the GitLab License](../../../user/admin_area/license.md) on the **primary** node to unlock Geo. The license must be for [GitLab Premium](https://about.gitlab.com/pricing/) or higher.
+1. [Set up the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology).
+1. [Configure fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md). This step is required and needs to be done on **both** the **primary** and **secondary** nodes.
+1. [Configure GitLab](configuration.md) to set the **primary** and **secondary** nodes.
+1. Optional: [Configure a secondary LDAP server](../../auth/ldap.md) for the **secondary** node. See [notes on LDAP](#ldap).
+1. [Follow the "Using a Geo Server" guide](using_a_geo_server.md).
+
+## Post-installation documentation
+
+After installing GitLab on the **secondary** nodes and performing the initial configuration, see the following documentation for post-installation information.
+
+### Configuring Geo
+
+For information on configuring Geo, see [Geo configuration](configuration.md).
+
+### Updating Geo
+
+For information on how to update your Geo nodes to the latest GitLab version, see [Updating the Geo nodes](updating_the_geo_nodes.md).
+
+### Configuring Geo high availability
+
+For information on configuring Geo for high availability, see [Geo High Availability](high_availability.md).
+
+### Configuring Geo with Object Storage
+
+For information on configuring Geo with object storage, see [Geo with Object storage](object_storage.md).
+
+### Disaster Recovery
+
+For information on using Geo in disaster recovery situations to mitigate data-loss and restore services, see [Disaster Recovery](../disaster_recovery/index.md).
+
+### Replicating the Container Registry
+
+For more information on how to replicate the Container Registry, see [Docker Registry for a **secondary** node](docker_registry.md).
+
+### Security Review
+
+For more information on Geo security, see [Geo security review](security_review.md).
+
+### Tuning Geo
+
+For more information on tuning Geo, see [Tuning Geo](tuning.md).
+
+## Remove Geo node
+
+For more information on removing a Geo node, see [Removing **secondary** Geo nodes](remove_geo_node.md).
+
+## Current limitations
+
+CAUTION: **Caution:**
+This list of limitations only reflects the latest version of GitLab. If you are using an older version, extra limitations may be in place.
+
+- Pushing directly to a **secondary** node redirects (for HTTP) or proxies (for SSH) the request to the **primary** node instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab-ee/issues/1381), except when using Git over HTTP with credentials embedded within the URI. For example, `https://user:password@secondary.tld`.
+- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected.
+- The installation takes multiple manual steps that together can take about an hour depending on circumstances. We are working on improving this experience. See [gitlab-org/omnibus-gitlab#2978](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2978) for details.
+- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** node.
+- [Selective synchronization](configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
+- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
+- [External merge request diffs](../../merge_request_diffs.md) will not be replicated if they are on-disk, and viewing merge requests will fail. However, external MR diffs in object storage **are** supported. The default configuration (in-database) does work.
+
+### Limitations on replication
+
+Only the following items are replicated to the **secondary** node:
+
+- All database content. For example, snippets, epics, issues, merge requests, groups, and project metadata.
+- Project repositories.
+- Project wiki repositories.
+- User uploads. For example, attachments to issues, merge requests, epics, and avatars.
+- CI job artifacts and traces.
+
+DANGER: **DANGER**
+Data not on this list is unavailable on the **secondary** node. Failing over without manually replicating data not on this list will cause the data to be **lost**.
+
+### Examples of data not replicated
+
+Take special note that these examples of GitLab features are both:
+
+- Commonly used.
+- **Not** replicated by Geo at present.
+
+Examples include:
+
+- [Elasticsearch integration](../../../integration/elasticsearch.md).
+- [Container Registry](../../container_registry.md). [Object Storage](object_storage.md) can mitigate this.
+- [GitLab Pages](../../pages/index.md).
+- [Mattermost integration](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
+
+CAUTION: **Caution:**
+If you wish to use them on a **secondary** node, or to execute a failover successfully, you will need to replicate their data using some other means.
+
+## Frequently Asked Questions
+
+For answers to common questions, see the [Geo FAQ](faq.md).
+
+## Log files
+
+Since GitLab 9.5, Geo stores structured log messages in a `geo.log` file. For Omnibus installations, this file is at `/var/log/gitlab/gitlab-rails/geo.log`.
+
+This file contains information about when Geo attempts to sync repositories and files. Each line in the file contains a separate JSON entry that can be ingested into Elasticsearch, Splunk, etc.
+
+For example:
+
+```json
+{"severity":"INFO","time":"2017-08-06T05:40:16.104Z","message":"Repository update","project_id":1,"source":"repository","resync_repository":true,"resync_wiki":true,"class":"Gitlab::Geo::LogCursor::Daemon","cursor_delay_s":0.038}
+```
+
+This message shows that Geo detected that a repository update was needed for project `1`.
+
+## Troubleshooting
+
+For troubleshooting steps, see [Geo Troubleshooting](troubleshooting.md).
diff --git a/doc/administration/geo/replication/object_storage.md b/doc/administration/geo/replication/object_storage.md
new file mode 100644
index 00000000000..c3c11dbaf1e
--- /dev/null
+++ b/doc/administration/geo/replication/object_storage.md
@@ -0,0 +1,43 @@
+# Geo with Object storage **[PREMIUM ONLY]**
+
+Geo can be used in combination with Object Storage (AWS S3, or
+other compatible object storage).
+
+## Configuration
+
+At this time it is required that if object storage is enabled on the
+**primary** node, it must also be enabled on each **secondary** node.
+
+**Secondary** nodes can use the same storage bucket as the **primary** node, or
+they can use a replicated storage bucket. At this time GitLab does not
+take care of content replication in object storage.
+
+For LFS, follow the documentation to
+[set up LFS object storage](../../../workflow/lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage).
+
+For CI job artifacts, there is similar documentation to configure
+[jobs artifact object storage](../../job_artifacts.md#using-object-storage)
+
+For user uploads, there is similar documentation to configure [upload object storage](../../uploads.md#using-object-storage-core-only)
+
+You should enable and configure object storage on both **primary** and **secondary**
+nodes. Migrating existing data to object storage should be performed on the
+**primary** node only. **Secondary** nodes will automatically notice that the migrated
+files are now in object storage.
+
+## Replication
+
+When using Amazon S3, you can use
+[CRR](https://docs.aws.amazon.com/AmazonS3/latest/dev/crr.html) to
+have automatic replication between the bucket used by the **primary** node and
+the bucket used by **secondary** nodes.
+
+If you are using Google Cloud Storage, consider using
+[Multi-Regional Storage](https://cloud.google.com/storage/docs/storage-classes#multi-regional).
+Or you can use the [Storage Transfer Service](https://cloud.google.com/storage/transfer/),
+although this only supports daily synchronization.
+
+For manual synchronization, or scheduled by `cron`, please have a look at:
+
+- [`s3cmd sync`](http://s3tools.org/s3cmd-sync)
+- [`gsutil rsync`](https://cloud.google.com/storage/docs/gsutil/commands/rsync)
diff --git a/doc/administration/geo/replication/remove_geo_node.md b/doc/administration/geo/replication/remove_geo_node.md
new file mode 100644
index 00000000000..b190fe7d42d
--- /dev/null
+++ b/doc/administration/geo/replication/remove_geo_node.md
@@ -0,0 +1,50 @@
+# Removing secondary Geo nodes **[PREMIUM ONLY]**
+
+**Secondary** nodes can be removed from the Geo cluster using the Geo admin page of the **primary** node. To remove a **secondary** node:
+
+1. Navigate to **Admin Area > Geo** (`/admin/geo/nodes`).
+1. Click the **Remove** button for the **secondary** node you want to remove.
+1. Confirm by clicking **Remove** when the prompt appears.
+
+Once removed from the Geo admin page, you must stop and uninstall the **secondary** node:
+
+1. On the **secondary** node, stop GitLab:
+
+ ```bash
+ sudo gitlab-ctl stop
+ ```
+1. On the **secondary** node, uninstall GitLab:
+
+ ```bash
+ # Stop gitlab and remove its supervision process
+ sudo gitlab-ctl uninstall
+
+ # Debian/Ubuntu
+ sudo dpkg --remove gitlab-ee
+
+ # Redhat/Centos
+ sudo rpm --erase gitlab-ee
+ ```
+
+Once GitLab has been uninstalled from the **secondary** node, the replication slot must be dropped from the **primary** node's database as follows:
+
+1. On the **primary** node, start a PostgreSQL console session:
+
+ ```bash
+ sudo gitlab-psql
+ ```
+
+ NOTE: **Note:**
+ Using `gitlab-rails dbconsole` will not work, because managing replication slots requires superuser permissions.
+
+1. Find the name of the relevant replication slot. This is the slot that is specified with `--slot-name` when running the replicate command: `gitlab-ctl replicate-geo-database`.
+
+ ```sql
+ SELECT * FROM pg_replication_slots;
+ ```
+
+1. Remove the replication slot for the **secondary** node:
+
+ ```sql
+ SELECT pg_drop_replication_slot('<name_of_slot>');
+ ```
diff --git a/doc/administration/geo/replication/security_review.md b/doc/administration/geo/replication/security_review.md
new file mode 100644
index 00000000000..cd54e2dc8c4
--- /dev/null
+++ b/doc/administration/geo/replication/security_review.md
@@ -0,0 +1,284 @@
+# Geo security review (Q&A) **[PREMIUM ONLY]**
+
+The following security review of the Geo feature set focuses on security
+aspects of the feature as they apply to customers running their own GitLab
+instances. The review questions are based in part on the [application security architecture](https://www.owasp.org/index.php/Application_Security_Architecture_Cheat_Sheet)
+questions from [owasp.org](https://www.owasp.org).
+
+## Business Model
+
+### What geographic areas does the application service?
+
+- This varies by customer. Geo allows customers to deploy to multiple areas,
+ and they get to choose where they are.
+- Region and node selection is entirely manual.
+
+## Data Essentials
+
+### What data does the application receive, produce, and process?
+
+- Geo streams almost all data held by a GitLab instance between sites. This
+ includes full database replication, most files (user-uploaded attachments,
+ etc) and repository + wiki data. In a typical configuration, this will
+ happen across the public Internet, and be TLS-encrypted.
+- PostgreSQL replication is TLS-encrypted.
+- See also: [only TLSv1.2 should be supported](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2948)
+
+### How can the data be classified into categories according to its sensitivity?
+
+- GitLab’s model of sensitivity is centered around public vs. internal vs.
+ private projects. Geo replicates them all indiscriminately. “Selective syncâ€
+ exists for files and repositories (but not database content), which would permit
+ only less-sensitive projects to be replicated to a **secondary** node if desired.
+- See also: [developing a data classification policy](https://gitlab.com/gitlab-com/security/issues/4).
+
+### What data backup and retention requirements have been defined for the application?
+
+- Geo is designed to provide replication of a certain subset of the application
+ data. It is part of the solution, rather than part of the problem.
+
+## End-Users
+
+### Who are the application's endâ€users?
+
+- **Secondary** nodes are created in regions that are distant (in terms of
+ Internet latency) from the main GitLab installation (the **primary** node). They are
+ intended to be used by anyone who would ordinarily use the **primary** node, who finds
+ that the **secondary** node is closer to them (in terms of Internet latency).
+
+### How do the endâ€users interact with the application?
+
+- **Secondary** nodes provide all the interfaces a **primary** node does
+ (notably a HTTP/HTTPS web application, and HTTP/HTTPS or SSH git repository
+ access), but is constrained to read-only activities. The principal use case is
+ envisioned to be cloning git repositories from the **secondary** node in favor of the
+ **primary** node, but end-users may use the GitLab web interface to view projects,
+ issues, merge requests, snippets, etc.
+
+### What security expectations do the endâ€users have?
+
+- The replication process must be secure. It would typically be unacceptable to
+ transmit the entire database contents or all files and repositories across the
+ public Internet in plaintext, for instance.
+- **Secondary** nodes must have the same access controls over its content as the
+ **primary** node - unauthenticated users must not be able to gain access to privileged
+ information on the **primary** node by querying the **secondary** node.
+- Attackers must not be able to impersonate the **secondary** node to the **primary** node, and
+ thus gain access to privileged information.
+
+## Administrators
+
+### Who has administrative capabilities in the application?
+
+- Nothing Geo-specific. Any user where `admin: true` is set in the database is
+ considered an admin with super-user privileges.
+- See also: [more granular access control](https://gitlab.com/gitlab-org/gitlab-ce/issues/32730)
+ (not geo-specific)
+- Much of Geo’s integration (database replication, for instance) must be
+ configured with the application, typically by system administrators.
+
+### What administrative capabilities does the application offer?
+
+- **Secondary** nodes may be added, modified, or removed by users with
+ administrative access.
+- The replication process may be controlled (start/stop) via the Sidekiq
+ administrative controls.
+
+## Network
+
+### What details regarding routing, switching, firewalling, and loadâ€balancing have been defined?
+
+- Geo requires the **primary** node and **secondary** node to be able to communicate with each
+ other across a TCP/IP network. In particular, the **secondary** nodes must be able to
+ access HTTP/HTTPS and PostgreSQL services on the **primary** node.
+
+### What core network devices support the application?
+
+- Varies from customer to customer.
+
+### What network performance requirements exist?
+
+- Maximum replication speeds between **primary** node and **secondary** node is limited by the
+ available bandwidth between sites. No hard requirements exist - time to complete
+ replication (and ability to keep up with changes on the **primary** node) is a function
+ of the size of the data set, tolerance for latency, and available network
+ capacity.
+
+### What private and public network links support the application?
+
+- Customers choose their own networks. As sites are intended to be
+ geographically separated, it is envisioned that replication traffic will pass
+ over the public Internet in a typical deployment, but this is not a requirement.
+
+## Systems
+
+### What operating systems support the application?
+
+- Geo imposes no additional restrictions on operating system (see the
+ [GitLab installation](https://about.gitlab.com/installation/) page for more
+ details), however we recommend using the operating systems listed in the [Geo documentation](index.md#requirements-for-running-geo).
+
+### What details regarding required OS components and lockâ€down needs have been defined?
+
+- The supported installation method (Omnibus) packages most components itself.
+- There are significant dependencies on the system-installed OpenSSH daemon (Geo
+ requires users to set up custom authentication methods) and the omnibus or
+ system-provided PostgreSQL daemon (it must be configured to listen on TCP,
+ additional users and replication slots must be added, etc).
+- The process for dealing with security updates (for example, if there is a
+ significant vulnerability in OpenSSH or other services, and the customer
+ wants to patch those services on the OS) is identical to the non-Geo
+ situation: security updates to OpenSSH would be provided to the user via the
+ usual distribution channels. Geo introduces no delay there.
+
+## Infrastructure Monitoring
+
+### What network and system performance monitoring requirements have been defined?
+
+- None specific to Geo.
+
+### What mechanisms exist to detect malicious code or compromised application components?
+
+- None specific to Geo.
+
+### What network and system security monitoring requirements have been defined?
+
+- None specific to Geo.
+
+## Virtualization and Externalization
+
+### What aspects of the application lend themselves to virtualization?
+
+- All.
+
+## What virtualization requirements have been defined for the application?
+
+- Nothing Geo-specific, but everything in GitLab needs to have full
+ functionality in such an environment.
+
+### What aspects of the product may or may not be hosted via the cloud computing model?
+
+- GitLab is “cloud native†and this applies to Geo as much as to the rest of the
+ product. Deployment in clouds is a common and supported scenario.
+
+## If applicable, what approach(es) to cloud computing will be taken (Managed Hosting versus "Pure" Cloud, a "full machine" approach such as AWS-EC2 versus a "hosted database" approach such as AWS-RDS and Azure, etc)?
+
+- To be decided by our customers, according to their operational needs.
+
+## Environment
+
+### What frameworks and programming languages have been used to create the application?
+
+- Ruby on Rails, Ruby.
+
+### What process, code, or infrastructure dependencies have been defined for the application?
+
+- Nothing specific to Geo.
+
+### What databases and application servers support the application?
+
+- PostgreSQL >= 9.6, Redis, Sidekiq, Unicorn.
+
+### How will database connection strings, encryption keys, and other sensitive components be stored, accessed, and protected from unauthorized detection?
+
+- There are some Geo-specific values. Some are shared secrets which must be
+ securely transmitted from the **primary** node to the **secondary** node at setup time. Our
+ documentation recommends transmitting them from the **primary** node to the system
+ administrator via SSH, and then back out to the **secondary** node in the same manner.
+ In particular, this includes the PostgreSQL replication credentials and a secret
+ key (`db_key_base`) which is used to decrypt certain columns in the database.
+ The `db_key_base` secret is stored unencrypted on the filesystem, in
+ `/etc/gitlab/gitlab-secrets.json`, along with a number of other secrets. There is
+ no at-rest protection for them.
+
+## Data Processing
+
+### What data entry paths does the application support?
+
+- Data is entered via the web application exposed by GitLab itself. Some data is
+ also entered using system administration commands on the GitLab servers (e.g.,
+ `gitlab-ctl set-primary-node`).
+- **Secondary** nodes also receive inputs via PostgreSQL streaming replication from the **primary** node.
+
+### What data output paths does the application support?
+
+- **Primary** nodes output via PostgreSQL streaming replication to the **secondary** node.
+ Otherwise, principally via the web application exposed by GitLab itself, and via
+ SSH `git clone` operations initiated by the end-user.
+
+### How does data flow across the application's internal components?
+
+- **Secondary** nodes and **primary** nodes interact via HTTP/HTTPS (secured with JSON web
+ tokens) and via PostgreSQL streaming replication.
+- Within a **primary** node or **secondary** node, the SSOT is the filesystem and the database
+ (including Geo tracking database on **secondary** node). The various internal components
+ are orchestrated to make alterations to these stores.
+
+### What data input validation requirements have been defined?
+
+- **Secondary** nodes must have a faithful replication of the **primary** node’s data.
+
+### What data does the application store and how?
+
+- Git repositories and files, tracking information related to the them, and the GitLab database contents.
+
+### What data is or may need to be encrypted and what key management requirements have been defined?
+
+- Neither **primary** nodes or **secondary** nodes encrypt Git repository or filesystem data at
+ rest. A subset of database columns are encrypted at rest using the `db_otp_key`.
+- A static secret shared across all hosts in a GitLab deployment.
+- In transit, data should be encrypted, although the application does permit
+ communication to proceed unencrypted. The two main transits are the **secondary** node’s
+ replication process for PostgreSQL, and for git repositories/files. Both should
+ be protected using TLS, with the keys for that managed via Omnibus per existing
+ configuration for end-user access to GitLab.
+
+### What capabilities exist to detect the leakage of sensitive data?
+
+- Comprehensive system logs exist, tracking every connection to GitLab and PostgreSQL.
+
+### What encryption requirements have been defined for data in transit - including transmission over WAN, LAN, SecureFTP, or publicly accessible protocols such as http: and https:?
+
+- Data must have the option to be encrypted in transit, and be secure against
+ both passive and active attack (e.g., MITM attacks should not be possible).
+
+## Access
+
+### What user privilege levels does the application support?
+
+- Geo adds one type of privilege: **secondary** nodes can access a special Geo API to
+ download files over HTTP/HTTPS, and to clone repositories using HTTP/HTTPS.
+
+### What user identification and authentication requirements have been defined?
+
+- **Secondary** nodes identify to Geo **primary** nodes via OAuth or JWT authentication
+ based on the shared database (HTTP access) or a PostgreSQL replication user (for
+ database replication). The database replication also requires IP-based access
+ controls to be defined.
+
+### What user authorization requirements have been defined?
+
+- **Secondary** nodes must only be able to *read* data. They are not currently able to mutate data on the **primary** node.
+
+### What session management requirements have been defined?
+
+- Geo JWTs are defined to last for only two minutes before needing to be regenerated.
+- Geo JWTs are generated for one of the following specific scopes:
+ - Geo API access.
+ - Git access.
+ - LFS and File ID.
+ - Upload and File ID.
+ - Job Artifact and File ID.
+
+### What access requirements have been defined for URI and Service calls?
+
+- **Secondary** nodes make many calls to the **primary** node's API. This is how file
+ replication proceeds, for instance. This endpoint is only accessible with a JWT token.
+- The **primary** node also makes calls to the **secondary** node to get status information.
+
+## Application Monitoring
+
+### What application auditing requirements have been defined? How are audit and debug logs accessed, stored, and secured?
+
+- Structured JSON log is written to the filesystem, and can also be ingested
+ into a Kibana installation for further analysis.
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
new file mode 100644
index 00000000000..c5bdd36ba70
--- /dev/null
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -0,0 +1,514 @@
+# Geo Troubleshooting **[PREMIUM ONLY]**
+
+NOTE: **Note:**
+This list is an attempt to document all the moving parts that can go wrong.
+We are working into getting all this steps verified automatically in a
+rake task in the future.
+
+Setting up Geo requires careful attention to details and sometimes it's easy to
+miss a step. Here is a list of questions you should ask to try to detect
+what you need to fix (all commands and path locations are for Omnibus installs):
+
+## First check the health of the **secondary** node
+
+Visit the **primary** node's **Admin Area > Geo** (`/admin/geo/nodes`) in
+your browser. We perform the following health checks on each **secondary** node
+to help identify if something is wrong:
+
+- Is the node running?
+- Is the node's secondary database configured for streaming replication?
+- Is the node's secondary tracking database configured?
+- Is the node's secondary tracking database connected?
+- Is the node's secondary tracking database up-to-date?
+
+![Geo health check](img/geo_node_healthcheck.png)
+
+For information on how to resolve common errors reported from the UI, see [common errors](#common-errors).
+
+If the UI is not working, or you are unable to log in, you can run the Geo
+health check manually to get this information as well as a few more details.
+This rake task can be run on an app node in the **primary** or **secondary**
+Geo nodes:
+
+```sh
+sudo gitlab-rake gitlab:geo:check
+```
+
+Example output:
+
+```
+Checking Geo ...
+
+GitLab Geo is available ... yes
+GitLab Geo is enabled ... yes
+GitLab Geo secondary database is correctly configured ... yes
+Database replication enabled? ... yes
+Database replication working? ... yes
+GitLab Geo tracking database is configured to use Foreign Data Wrapper? ... yes
+GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... yes
+GitLab Geo HTTP(S) connectivity ...
+* Can connect to the primary node ... yes
+HTTP/HTTPS repository cloning is enabled ... yes
+Machine clock is synchronized ... yes
+Git user has default SSH configuration? ... yes
+OpenSSH configured to use AuthorizedKeysCommand ... yes
+GitLab configured to disable writing to authorized_keys file ... yes
+GitLab configured to store new projects in hashed storage? ... yes
+All projects are in hashed storage? ... yes
+
+Checking Geo ... Finished
+```
+
+Current sync information can be found manually by running this rake task on any
+**secondary** app node:
+
+```sh
+sudo gitlab-rake geo:status
+```
+
+Example output:
+
+```
+http://secondary.example.com/
+-----------------------------------------------------
+ GitLab Version: 11.10.4-ee
+ Geo Role: Secondary
+ Health Status: Healthy
+ Repositories: 289/289 (100%)
+ Verified Repositories: 289/289 (100%)
+ Wikis: 289/289 (100%)
+ Verified Wikis: 289/289 (100%)
+ LFS Objects: 8/8 (100%)
+ Attachments: 5/5 (100%)
+ CI job artifacts: 0/0 (0%)
+ Repositories Checked: 0/289 (0%)
+ Sync Settings: Full
+ Database replication lag: 0 seconds
+ Last event ID seen from primary: 10215 (about 2 minutes ago)
+ Last event ID processed by cursor: 10215 (about 2 minutes ago)
+ Last status report was: 2 minutes ago
+```
+
+## Is Postgres replication working?
+
+### Are my nodes pointing to the correct database instance?
+
+You should make sure your **primary** Geo node points to the instance with
+writing permissions.
+
+Any **secondary** nodes should point only to read-only instances.
+
+### Can Geo detect my current node correctly?
+
+Geo uses the defined node from the **Admin Area > Geo** screen, and tries to match
+it with the value defined in the `/etc/gitlab/gitlab.rb` configuration file.
+The relevant line looks like: `external_url "http://gitlab.example.com"`.
+
+To check if the node on the current machine is correctly detected type:
+
+```sh
+sudo gitlab-rails runner "puts Gitlab::Geo.current_node.inspect"
+```
+
+and expect something like:
+
+```
+#<GeoNode id: 2, schema: "https", host: "gitlab.example.com", port: 443, relative_url_root: "", primary: false, ...>
+```
+
+By running the command above, `primary` should be `true` when executed in
+the **primary** node, and `false` on any **secondary** node.
+
+## How do I fix the message, "ERROR: replication slots can only be used if max_replication_slots > 0"?
+
+This means that the `max_replication_slots` PostgreSQL variable needs to
+be set on the **primary** database. In GitLab 9.4, we have made this setting
+default to 1. You may need to increase this value if you have more
+**secondary** nodes. Be sure to restart PostgreSQL for this to take
+effect. See the [PostgreSQL replication
+setup][database-pg-replication] guide for more details.
+
+## How do I fix the message, "FATAL: could not start WAL streaming: ERROR: replication slot "geo_secondary_my_domain_com" does not exist"?
+
+This occurs when PostgreSQL does not have a replication slot for the
+**secondary** node by that name. You may want to rerun the [replication
+process](database.md) on the **secondary** node .
+
+## How do I fix the message, "Command exceeded allowed execution time" when setting up replication?
+
+This may happen while [initiating the replication process][database-start-replication] on the **secondary** node,
+and indicates that your initial dataset is too large to be replicated in the default timeout (30 minutes).
+
+Re-run `gitlab-ctl replicate-geo-database`, but include a larger value for
+`--backup-timeout`:
+
+```sh
+sudo gitlab-ctl \
+ replicate-geo-database \
+ --host=<primary_node_hostname> \
+ --slot-name=<secondary_slot_name> \
+ --backup-timeout=21600
+```
+
+This will give the initial replication up to six hours to complete, rather than
+the default thirty minutes. Adjust as required for your installation.
+
+## How do I fix the message, "PANIC: could not write to file 'pg_xlog/xlogtemp.123': No space left on device"
+
+Determine if you have any unused replication slots in the **primary** database. This can cause large amounts of
+log data to build up in `pg_xlog`. Removing the unused slots can reduce the amount of space used in the `pg_xlog`.
+
+1. Start a PostgreSQL console session:
+
+ ```sh
+ sudo gitlab-psql gitlabhq_production
+ ```
+
+ > Note that using `gitlab-rails dbconsole` will not work, because managing replication slots requires superuser permissions.
+
+1. View your replication slots with:
+
+ ```sql
+ SELECT * FROM pg_replication_slots;
+ ```
+
+Slots where `active` is `f` are not active.
+
+- When this slot should be active, because you have a **secondary** node configured using that slot,
+ log in to that **secondary** node and check the PostgreSQL logs why the replication is not running.
+
+- If you are no longer using the slot (e.g. you no longer have Geo enabled), you can remove it with in the
+ PostgreSQL console session:
+
+ ```sql
+ SELECT pg_drop_replication_slot('<name_of_extra_slot>');
+ ```
+
+## Very large repositories never successfully synchronize on the **secondary** node
+
+GitLab places a timeout on all repository clones, including project imports
+and Geo synchronization operations. If a fresh `git clone` of a repository
+on the primary takes more than a few minutes, you may be affected by this.
+To increase the timeout, add the following line to `/etc/gitlab/gitlab.rb`
+on the **secondary** node:
+
+```ruby
+gitlab_rails['gitlab_shell_git_timeout'] = 10800
+```
+
+Then reconfigure GitLab:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+This will increase the timeout to three hours (10800 seconds). Choose a time
+long enough to accommodate a full clone of your largest repositories.
+
+## How to reset Geo **secondary** node replication
+
+If you get a **secondary** node in a broken state and want to reset the replication state,
+to start again from scratch, there are a few steps that can help you:
+
+1. Stop Sidekiq and the Geo LogCursor
+
+ It's possible to make Sidekiq stop gracefully, but making it stop getting new jobs and
+ wait until the current jobs to finish processing.
+
+ You need to send a **SIGTSTP** kill signal for the first phase and them a **SIGTERM**
+ when all jobs have finished. Otherwise just use the `gitlab-ctl stop` commands.
+
+ ```sh
+ gitlab-ctl status sidekiq
+ # run: sidekiq: (pid 10180) <- this is the PID you will use
+ kill -TSTP 10180 # change to the correct PID
+
+ gitlab-ctl stop sidekiq
+ gitlab-ctl stop geo-logcursor
+ ```
+
+ You can watch sidekiq logs to know when sidekiq jobs processing have finished:
+
+ ```sh
+ gitlab-ctl tail sidekiq
+ ```
+
+1. Rename repository storage folders and create new ones
+
+ ```sh
+ mv /var/opt/gitlab/git-data/repositories /var/opt/gitlab/git-data/repositories.old
+ mkdir -p /var/opt/gitlab/git-data/repositories
+ chown git:git /var/opt/gitlab/git-data/repositories
+ ```
+
+ TIP: **Tip**
+ You may want to remove the `/var/opt/gitlab/git-data/repositories.old` in the future
+ as soon as you confirmed that you don't need it anymore, to save disk space.
+
+1. _(Optional)_ Rename other data folders and create new ones
+
+ CAUTION: **Caution**:
+ You may still have files on the **secondary** node that have been removed from **primary** node but
+ removal have not been reflected. If you skip this step, they will never be removed
+ from this Geo node.
+
+ Any uploaded content like file attachments, avatars or LFS objects are stored in a
+ subfolder in one of the two paths below:
+
+ 1. /var/opt/gitlab/gitlab-rails/shared
+ 1. /var/opt/gitlab/gitlab-rails/uploads
+
+ To rename all of them:
+
+ ```sh
+ gitlab-ctl stop
+
+ mv /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared.old
+ mkdir -p /var/opt/gitlab/gitlab-rails/shared
+
+ mv /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads.old
+ mkdir -p /var/opt/gitlab/gitlab-rails/uploads
+ ```
+
+ Reconfigure in order to recreate the folders and make sure permissions and ownership
+ are correctly
+
+ ```sh
+ gitlab-ctl reconfigure
+ ```
+
+1. Reset the Tracking Database
+
+ ```sh
+ gitlab-rake geo:db:reset
+ ```
+
+1. Restart previously stopped services
+
+ ```sh
+ gitlab-ctl start
+ ```
+
+## How do I fix a "Foreign Data Wrapper (FDW) is not configured" error?
+
+When setting up Geo, you might see this warning in the `gitlab-rake
+gitlab:geo:check` output:
+
+```
+GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... foreign data wrapper is not configured
+```
+
+There are a few key points to remember:
+
+1. The FDW settings are configured on the Geo **tracking** database.
+1. The configured foreign server enables a login to the Geo
+**secondary**, read-only database.
+
+By default, the Geo secondary and tracking database are running on the
+same host on different ports. That is, 5432 and 5431 respectively.
+
+### Checking configuration
+
+NOTE: **Note:**
+The following steps are for Omnibus installs only. Using Geo with source-based installs was **deprecated** in GitLab 11.5.
+
+To check the configuration:
+
+1. Enter the database console:
+
+ ```sh
+ gitlab-geo-psql
+ ```
+
+1. Check whether any tables are present. If everything is working, you
+should see something like this:
+
+ ```sql
+ gitlabhq_geo_production=# SELECT * from information_schema.foreign_tables;
+ foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_n
+ ame
+ -------------------------+----------------------+-------------------------------------------------+-------------------------+-----------------
+ ----
+ gitlabhq_geo_production | gitlab_secondary | abuse_reports | gitlabhq_geo_production | gitlab_secondary
+ gitlabhq_geo_production | gitlab_secondary | appearances | gitlabhq_geo_production | gitlab_secondary
+ gitlabhq_geo_production | gitlab_secondary | application_setting_terms | gitlabhq_geo_production | gitlab_secondary
+ gitlabhq_geo_production | gitlab_secondary | application_settings | gitlabhq_geo_production | gitlab_secondary
+ <snip>
+ ```
+
+ However, if the query returns with `0 rows`, then continue onto the next steps.
+
+1. Check that the foreign server mapping is correct via `\des+`. The
+ results should look something like this:
+
+ ```sql
+ gitlabhq_geo_production=# \des+
+ List of foreign servers
+ -[ RECORD 1 ]--------+------------------------------------------------------------
+ Name | gitlab_secondary
+ Owner | gitlab-psql
+ Foreign-data wrapper | postgres_fdw
+ Access privileges | "gitlab-psql"=U/"gitlab-psql" +
+ | gitlab_geo=U/"gitlab-psql"
+ Type |
+ Version |
+ FDW Options | (host '0.0.0.0', port '5432', dbname 'gitlabhq_production')
+ Description |
+ ```
+
+ NOTE: **Note:** Pay particular attention to the host and port under
+ FDW options. That configuration should point to the Geo secondary
+ database.
+
+ If you need to experiment with changing the host or password, the
+ following queries demonstrate how:
+
+ ```sql
+ ALTER SERVER gitlab_secondary OPTIONS (SET host '<my_new_host>');
+ ALTER SERVER gitlab_secondary OPTIONS (SET port 5432);
+ ```
+
+ If you change the host and/or port, you will also have to adjust the
+ following settings in `/etc/gitlab/gitlab.rb` and run `gitlab-ctl
+ reconfigure`:
+
+ - `gitlab_rails['db_host']`
+ - `gitlab_rails['db_port']`
+
+1. Check that the user mapping is configured properly via `\deu+`:
+
+ ```sql
+ gitlabhq_geo_production=# \deu+
+ List of user mappings
+ Server | User name | FDW Options
+ ------------------+------------+--------------------------------------------------------------------------------
+ gitlab_secondary | gitlab_geo | ("user" 'gitlab', password 'YOUR-PASSWORD-HERE')
+ (1 row)
+ ```
+
+ Make sure the password is correct. You can test that logins work by running `psql`:
+
+ ```sh
+ # Connect to the tracking database as the `gitlab_geo` user
+ sudo \
+ -u git /opt/gitlab/embedded/bin/psql \
+ -h /var/opt/gitlab/geo-postgresql \
+ -p 5431 \
+ -U gitlab_geo \
+ -W \
+ -d gitlabhq_geo_production
+ ```
+
+ If you need to correct the password, the following query shows how:
+
+ ```sql
+ ALTER USER MAPPING FOR gitlab_geo SERVER gitlab_secondary OPTIONS (SET password '<my_new_password>');
+ ```
+
+ If you change the user or password, you will also have to adjust the
+ following settings in `/etc/gitlab/gitlab.rb` and run `gitlab-ctl
+ reconfigure`:
+
+ - `gitlab_rails['db_username']`
+ - `gitlab_rails['db_password']`
+
+ If you are using [PgBouncer in front of the secondary
+ database](database.md#pgbouncer-support-optional), be sure to update
+ the following settings:
+
+ - `geo_postgresql['fdw_external_user']`
+ - `geo_postgresql['fdw_external_password']`
+
+### Manual reload of FDW schema
+
+If you're still unable to get FDW working, you may want to try a manual
+reload of the FDW schema. To manually reload the FDW schema:
+
+1. On the node running the Geo tracking database, enter the PostgreSQL console via
+ the `gitlab_geo` user:
+
+ ```sh
+ sudo \
+ -u git /opt/gitlab/embedded/bin/psql \
+ -h /var/opt/gitlab/geo-postgresql \
+ -p 5431 \
+ -U gitlab_geo \
+ -W \
+ -d gitlabhq_geo_production
+ ```
+
+ Be sure to adjust the port and hostname for your configuration. You
+ may be asked to enter a password.
+
+1. Reload the schema via:
+
+ ```sql
+ DROP SCHEMA IF EXISTS gitlab_secondary CASCADE;
+ CREATE SCHEMA gitlab_secondary;
+ GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO gitlab_geo;
+ IMPORT FOREIGN SCHEMA public FROM SERVER gitlab_secondary INTO gitlab_secondary;
+ ```
+
+1. Test that queries work:
+
+ ```sql
+ SELECT * from information_schema.foreign_tables;
+ SELECT * FROM gitlab_secondary.projects limit 1;
+ ```
+
+[database-start-replication]: database.md#step-3-initiate-the-replication-process
+[database-pg-replication]: database.md#postgresql-replication
+
+## Common errors
+
+This section documents common errors reported in the admin UI and how to fix them.
+
+### Geo database configuration file is missing
+
+GitLab cannot find or doesn't have permission to access the `database_geo.yml` configuration file.
+
+In an Omnibus GitLab installation, the file should be in `/var/opt/gitlab/gitlab-rails/etc`.
+If it doesn't exist or inadvertent changes have been made to it, run `sudo gitlab-ctl reconfigure` to restore it to its correct state.
+
+
+If this path is mounted on a remote volume, please check your volume configuration and that it has correct permissions.
+
+### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
+
+This error refers to a problem with the database replica on a **secondary** node,
+which Geo expects to have access to. It usually means, either:
+
+- An unsupported replication method was used (for example, logical replication).
+- The instructions to setup a [Geo database replication](database.md) were not followed correctly.
+
+A common source of confusion with **secondary** nodes is that it requires two separate
+PostgreSQL instances:
+
+- A read-only replica of the **primary** node.
+- A regular, writable instance that holds replication metadata. That is, the Geo tracking database.
+
+### Geo node does not appear to be replicating the database from the primary node.
+
+The most common problems that prevent the database from replicating correctly are:
+
+- **Secondary** nodes cannot reach the **primary** node. Check credentials, firewall rules, etc.
+- SSL certificate problems. Make sure you copied `/etc/gitlab/gitlab-secrets.json` from the **primary** node.
+- Database storage disk is full.
+- Database replication slot is misconfigured.
+- Database is not using a replication slot or another alternative and cannot catch-up because WAL files were purged.
+
+Make sure you follow the [Geo database replication](database.md) instructions for supported configuration.
+
+### Geo database version (...) does not match latest migration (...)
+
+If you are using GitLab Omnibus installation, something might have failed during upgrade. You can:
+
+- Run `sudo gitlab-ctl reconfigure`.
+- Manually trigger the database migration by running: `sudo gitlab-rake geo:db:migrate` as root on the **secondary** node.
+
+### Geo database is not configured to use Foreign Data Wrapper
+
+This error means the Geo Tracking Database doesn't have the FDW server and credentials
+configured.
+
+See [How do I fix a "Foreign Data Wrapper (FDW) is not configured" error?](#how-do-i-fix-a-foreign-data-wrapper-fdw-is-not-configured-error).
diff --git a/doc/administration/geo/replication/tuning.md b/doc/administration/geo/replication/tuning.md
new file mode 100644
index 00000000000..1943f2230df
--- /dev/null
+++ b/doc/administration/geo/replication/tuning.md
@@ -0,0 +1,17 @@
+# Tuning Geo **[PREMIUM ONLY]**
+
+## Changing the sync capacity values
+
+In the Geo admin page (`/admin/geo/nodes`), there are several variables that
+can be tuned to improve performance of Geo:
+
+- Repository sync capacity.
+- File sync capacity.
+
+Increasing these values will increase the number of jobs that are scheduled.
+However, this may not lead to more downloads in parallel unless the number of
+available Sidekiq threads is also increased. For example, if repository sync
+capacity is increased from 25 to 50, you may also want to increase the number
+of Sidekiq threads from 25 to 50. See the
+[Sidekiq concurrency documentation](../../operations/extra_sidekiq_processes.md#number-of-threads)
+for more details.
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
new file mode 100644
index 00000000000..933a75c47d8
--- /dev/null
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -0,0 +1,448 @@
+# Updating the Geo nodes **[PREMIUM ONLY]**
+
+Depending on which version of Geo you are updating to/from, there may be
+different steps.
+
+## General update steps
+
+In order to update the Geo nodes when a new GitLab version is released,
+all you need to do is update GitLab itself:
+
+1. Log into each node (**primary** and **secondary** nodes).
+1. [Update GitLab][update].
+1. [Update tracking database on **secondary** node](#update-tracking-database-on-secondary-node) when
+ the tracking database is enabled.
+1. [Test](#check-status-after-updating) **primary** and **secondary** nodes, and check version in each.
+
+## Upgrading to GitLab 10.8
+
+Before 10.8, broadcast messages would not propagate without flushing the cache on the **secondary** nodes. This has been fixed in 10.8, but requires one last cache flush on each **secondary** node:
+
+```sh
+sudo gitlab-rake cache:clear
+```
+
+## Upgrading to GitLab 10.6
+
+In 10.4, we started to recommend that you define a password for database user (`gitlab`).
+
+We now require this change as we use this password to enable the Foreign Data Wrapper, as a way to optimize
+the Geo Tracking Database. We are also improving security by disabling the use of **trust**
+authentication method.
+
+1. **[primary]** Login to your **primary** node and run:
+
+ ```sh
+ gitlab-ctl pg-password-md5 gitlab
+ # Enter password: <your_password_here>
+ # Confirm password: <your_password_here>
+ # fca0b89a972d69f00eb3ec98a5838484
+ ```
+
+ Copy the generated hash and edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+
+ # Every node that runs Unicorn or Sidekiq needs to have the database
+ # password specified as below. If you have a high-availability setup, this
+ # must be present in all application nodes.
+ gitlab_rails['db_password'] = '<your_password_here>'
+ ```
+
+ Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
+
+ ```ruby
+ postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32'] # <- Remove this
+ ```
+
+1. **[primary]** Reconfigure and restart:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
+
+1. **[secondary]** Login to all **secondary** nodes and edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+
+ # Every node that runs Unicorn or Sidekiq needs to have the database
+ # password specified as below. If you have a high-availability setup, this
+ # must be present in all application nodes.
+ gitlab_rails['db_password'] = '<your_password_here>'
+
+ # Enable Foreign Data Wrapper
+ geo_secondary['db_fdw'] = true
+
+ # Secondary address in CIDR format, for example '5.6.7.8/32'
+ postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32']
+ ```
+
+ Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
+
+ ```ruby
+ postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] # <- Remove this
+ ```
+
+1. **[secondary]** Reconfigure and restart:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
+
+## Upgrading to GitLab 10.5
+
+For Geo Disaster Recovery to work with minimum downtime, your **secondary** node
+should use the same set of secrets as the **primary** node. However, setup instructions
+prior to the 10.5 release only synchronized the `db_key_base` secret.
+
+To rectify this error on existing installations, you should **overwrite** the
+contents of `/etc/gitlab/gitlab-secrets.json` on each **secondary** node with the
+contents of `/etc/gitlab/gitlab-secrets.json` on the **primary** node, then run the
+following command on each **secondary** node:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+If you do not perform this step, you may find that two-factor authentication
+[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken).
+
+To prevent SSH requests to the newly promoted **primary** node from failing
+due to SSH host key mismatch when updating the **primary** node domain's DNS record
+you should perform the step to [Manually replicate **primary** SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys) in each
+**secondary** node.
+
+## Upgrading to GitLab 10.4
+
+There are no Geo-specific steps to take!
+
+## Upgrading to GitLab 10.3
+
+### Support for SSH repository synchronization removed
+
+In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3,
+support is removed entirely. All installations will switch to the HTTP/HTTPS
+cloning method instead. Before upgrading, ensure that all your Geo nodes are
+configured to use this method and that it works for your installation. In
+particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps).
+
+Synchronizing repositories over the public Internet using HTTP is insecure, so
+you should ensure that you have HTTPS configured before upgrading. Note that
+file synchronization is **also** insecure in these cases!
+
+## Upgrading to GitLab 10.2
+
+### Secure PostgreSQL replication
+
+Support for TLS-secured PostgreSQL replication has been added. If you are
+currently using PostgreSQL replication across the open internet without an
+external means of securing the connection (e.g., a site-to-site VPN), then you
+should immediately reconfigure your **primary** and **secondary** PostgreSQL instances
+according to the [updated instructions][database].
+
+If you *are* securing the connections externally and wish to continue doing so,
+ensure you include the new option `--sslmode=prefer` in future invocations of
+`gitlab-ctl replicate-geo-database`.
+
+### HTTPS repository sync
+
+Support for replicating repositories and wikis over HTTP/HTTPS has been added.
+Replicating over SSH has been deprecated, and support for this option will be
+removed in a future release.
+
+To switch to HTTP/HTTPS replication, log into the **primary** node as an admin and visit
+**Admin Area > Geo** (`/admin/geo/nodes`). For each **secondary** node listed,
+press the "Edit" button, change the "Repository cloning" setting from
+"SSH (deprecated)" to "HTTP/HTTPS", and press "Save changes". This should take
+effect immediately.
+
+Any new secondaries should be created using HTTP/HTTPS replication - this is the
+default setting.
+
+After you've verified that HTTP/HTTPS replication is working, you should remove
+the now-unused SSH keys from your secondaries, as they may cause problems if the
+**secondary** node if ever promoted to a **primary** node:
+
+1. **[secondary]** Login to **all** your **secondary** nodes and run:
+
+ ```ruby
+ sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub
+ ```
+
+### Hashed Storage
+
+CAUTION: **Warning:**
+Hashed storage is in **Alpha**. It is considered experimental and not
+production-ready. See [Hashed Storage] for more detail.
+
+If you previously enabled Hashed Storage and migrated all your existing
+projects to Hashed Storage, disabling hashed storage will not migrate projects
+to their previous project based storage path. As such, once enabled and
+migrated we recommend leaving Hashed Storage enabled.
+
+## Upgrading to GitLab 10.1
+
+CAUTION: **Warning:**
+Hashed storage is in **Alpha**. It is considered experimental and not
+production-ready. See [Hashed Storage] for more detail.
+
+[Hashed storage] was introduced in GitLab 10.0, and a [migration path][hashed-migration]
+for existing repositories was added in GitLab 10.1.
+
+## Upgrading to GitLab 10.0
+
+Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via
+the database][ssh-fast-lookup] to avoid having to maintain consistency of the
+`authorized_keys` file for SSH access. Failing to do this will prevent users
+from being able to clone via SSH.
+
+Note that in older versions of Geo, attachments downloaded on the **secondary**
+nodes would be saved to the wrong directory. We recommend that you do the
+following to clean this up.
+
+On the **secondary** Geo nodes, run as root:
+
+```sh
+mv /var/opt/gitlab/gitlab-rails/working /var/opt/gitlab/gitlab-rails/working.old
+mkdir /var/opt/gitlab/gitlab-rails/working
+chmod 700 /var/opt/gitlab/gitlab-rails/working
+chown git:git /var/opt/gitlab/gitlab-rails/working
+```
+
+You may delete `/var/opt/gitlab/gitlab-rails/working.old` any time.
+
+Once this is done, we advise restarting GitLab on the **secondary** nodes for the
+new working directory to be used:
+
+```sh
+sudo gitlab-ctl restart
+```
+
+## Upgrading from GitLab 9.3 or older
+
+If you started running Geo on GitLab 9.3 or older, we recommend that you
+resync your **secondary** PostgreSQL databases to use replication slots. If you
+started using Geo with GitLab 9.4 or 10.x, no further action should be
+required because replication slots are used by default. However, if you
+started with GitLab 9.3 and upgraded later, you should still follow the
+instructions below.
+
+When in doubt, it does not hurt to do a resync. The easiest way to do this in
+Omnibus is the following:
+
+ 1. Make sure you have Omnibus GitLab on the **primary** server.
+ 1. Run `gitlab-ctl reconfigure` and `gitlab-ctl restart postgresql`. This will enable replication slots on the **primary** database.
+ 1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`.
+ 1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations.
+ 1. Install GitLab on the **secondary** server.
+ 1. Re-run the [database replication process][database-replication].
+
+## Special update notes for 9.0.x
+
+> **IMPORTANT**:
+With GitLab 9.0, the PostgreSQL version is upgraded to 9.6 and manual steps are
+required in order to update the **secondary** nodes and keep the Streaming
+Replication working. Downtime is required, so plan ahead.
+
+The following steps apply only if you upgrade from a 8.17 GitLab version to
+9.0+. For previous versions, update to GitLab 8.17 first before attempting to
+upgrade to 9.0+.
+
+---
+
+Make sure to follow the steps in the exact order as they appear below and pay
+extra attention in what node (either **primary** or **secondary**) you execute them! Each step
+is prepended with the relevant node for better clarity:
+
+1. **[secondary]** Login to **all** your **secondary** nodes and stop all services:
+
+ ```ruby
+ sudo gitlab-ctl stop
+ ```
+
+1. **[secondary]** Make a backup of the `recovery.conf` file on **all**
+ **secondary** nodes to preserve PostgreSQL's credentials:
+
+ ```sh
+ sudo cp /var/opt/gitlab/postgresql/data/recovery.conf /var/opt/gitlab/
+ ```
+
+1. **[primary]** Update the **primary** node to GitLab 9.0 following the
+ [regular update docs][update]. At the end of the update, the **primary** node
+ will be running with PostgreSQL 9.6.
+
+1. **[primary]** To prevent a de-synchronization of the repository replication,
+ stop all services except `postgresql` as we will use it to re-initialize the
+ **secondary** node's database:
+
+ ```sh
+ sudo gitlab-ctl stop
+ sudo gitlab-ctl start postgresql
+ ```
+
+1. **[secondary]** Run the following steps on each of the **secondary** nodes:
+
+ 1. **[secondary]** Stop all services:
+
+ ```sh
+ sudo gitlab-ctl stop
+ ```
+
+ 1. **[secondary]** Prevent running database migrations:
+
+ ```sh
+ sudo touch /etc/gitlab/skip-auto-migrations
+ ```
+
+ 1. **[secondary]** Move the old database to another directory:
+
+ ```sh
+ sudo mv /var/opt/gitlab/postgresql{,.bak}
+ ```
+
+ 1. **[secondary]** Update to GitLab 9.0 following the [regular update docs][update].
+ At the end of the update, the node will be running with PostgreSQL 9.6.
+
+ 1. **[secondary]** Make sure all services are up:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+ 1. **[secondary]** Reconfigure GitLab:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
+
+ 1. **[secondary]** Run the PostgreSQL upgrade command:
+
+ ```sh
+ sudo gitlab-ctl pg-upgrade
+ ```
+
+ 1. **[secondary]** See the stored credentials for the database that you will
+ need to re-initialize the replication:
+
+ ```sh
+ sudo grep -s primary_conninfo /var/opt/gitlab/recovery.conf
+ ```
+
+ 1. **[secondary]** Create the `replica.sh` script as described in the
+ [database configuration document][database-source-replication].
+
+ 1. 1. **[secondary]** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the
+ embedded paths if necessary:
+
+ ```
+ #!/bin/bash
+
+ PORT="5432"
+ USER="gitlab_replicator"
+ echo ---------------------------------------------------------------
+ echo WARNING: Make sure this script is run from the secondary server
+ echo ---------------------------------------------------------------
+ echo
+ echo Enter the IP or FQDN of the primary PostgreSQL server
+ read HOST
+ echo Enter the password for $USER@$HOST
+ read -s PASSWORD
+ echo Enter the required sslmode
+ read SSLMODE
+
+ echo Stopping PostgreSQL and all GitLab services
+ sudo service gitlab stop
+ sudo service postgresql stop
+
+ echo Backing up postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/
+
+ echo Cleaning up old cluster directory
+ sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data
+
+ echo Starting base backup as the replicator user
+ echo Enter the password for $USER@$HOST
+ sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P
+
+ echo Writing recovery.conf file
+ sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_
+ standby_mode = 'on'
+ primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE'
+ _EOF1_
+ "
+
+ echo Restoring postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/
+
+ echo Starting PostgreSQL
+ sudo service postgresql start
+ ```
+
+ 1. **[secondary]** Run the recovery script using the credentials from the
+ previous step:
+
+ ```sh
+ sudo bash /tmp/replica.sh
+ ```
+
+ 1. **[secondary]** Reconfigure GitLab:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
+
+ 1. **[secondary]** Start all services:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+ 1. **[secondary]** Repeat the steps for the remaining **secondary** nodes.
+
+1. **[primary]** After all **secondary** nodes are updated, start all services in
+ **primary** node:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+## Check status after updating
+
+Now that the update process is complete, you may want to check whether
+everything is working correctly:
+
+1. Run the Geo raketask on all nodes, everything should be green:
+
+ ```sh
+ sudo gitlab-rake gitlab:geo:check
+ ```
+
+1. Check the **primary** node's Geo dashboard for any errors.
+1. Test the data replication by pushing code to the **primary** node and see if it
+ is received by **secondary** nodes.
+
+## Update tracking database on **secondary** node
+
+After updating a **secondary** node, you might need to run migrations on
+the tracking database. The tracking database was added in GitLab 9.1,
+and it is required since 10.0.
+
+1. Run database migrations on tracking database:
+
+ ```sh
+ sudo gitlab-rake geo:db:migrate
+ ```
+
+1. Repeat this step for each **secondary** node.
+
+[update]: ../../../update/README.md
+[database]: database.md
+[Hashed Storage]: ../../repository_storage_types.md
+[hashed-migration]: ../../raketasks/storage.md
+[ssh-fast-lookup]: ../../operations/fast_ssh_key_lookup.md
diff --git a/doc/administration/geo/replication/using_a_geo_server.md b/doc/administration/geo/replication/using_a_geo_server.md
new file mode 100644
index 00000000000..f1f1fe48748
--- /dev/null
+++ b/doc/administration/geo/replication/using_a_geo_server.md
@@ -0,0 +1,18 @@
+[//]: # (Please update EE::GitLab::GeoGitAccess::GEO_SERVER_DOCS_URL if this file is moved)
+
+# Using a Geo Server **[PREMIUM ONLY]**
+
+After you set up the [database replication and configure the Geo nodes][req], use your closest GitLab node as you would a normal standalone GitLab instance.
+
+Pushing directly to a **secondary** node (for both HTTP, SSH including git-lfs) was [introduced](https://about.gitlab.com/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
+
+Example of the output you will see when pushing to a **secondary** node:
+
+```bash
+$ git push
+> GitLab: You're pushing to a Geo secondary.
+> GitLab: We'll help you by proxying this request to the primary: ssh://git@primary.geo/user/repo.git
+Everything up-to-date
+```
+
+[req]: index.md#setup-instructions
diff --git a/doc/administration/git_protocol.md b/doc/administration/git_protocol.md
index 11b2adeeeb8..1780e1babe9 100644
--- a/doc/administration/git_protocol.md
+++ b/doc/administration/git_protocol.md
@@ -5,13 +5,16 @@ description: "Set and configure Git protocol v2"
# Configuring Git Protocol v2
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46555) in GitLab 11.4.
-> [Temporarily disabled](https://gitlab.com/gitlab-org/gitlab-ce/issues/55769) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+
+> Temporarily disabled (see [confidential issue](../user/project/issues/confidential_issues.md)
+> `https://gitlab.com/gitlab-org/gitlab-ce/issues/55769`) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+.
NOTE: **Note:**
-Git protocol v2 support has been [temporarily disabled](https://gitlab.com/gitlab-org/gitlab-ce/issues/55769),
-as a feature used to hide certain internal references does not function when it
+Git protocol v2 support has been temporarily disabled
+because a feature used to hide certain internal references does not function when it
is enabled, and this has a security impact. Once this problem has been resolved,
-protocol v2 support will be re-enabled.
+protocol v2 support will be re-enabled. For more information, see the
+[confidential issue](../user/project/issues/confidential_issues.md)
+`https://gitlab.com/gitlab-org/gitlab-ce/issues/55769`.
Git protocol v2 improves the v1 wire protocol in several ways and is
enabled by default in GitLab for HTTP requests. In order to enable SSH,
@@ -28,7 +31,7 @@ From the client side, `git` `v2.18.0` or newer must be installed.
From the server side, if we want to configure SSH we need to set the `sshd`
server to accept the `GIT_PROTOCOL` environment.
-In installations using [GitLab Helm Charts](../install/kubernetes/gitlab_chart.md)
+In installations using [GitLab Helm Charts](https://docs.gitlab.com/charts/)
and [All-in-one docker image](https://docs.gitlab.com/omnibus/docker/), the SSH
service is already configured to accept the `GIT_PROTOCOL` environment and users
need not do anything more.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 0795d3dad40..53a85dfad6c 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -45,9 +45,17 @@ installations that are larger than a single machine. Most
installations will be better served with the default configuration
used by Omnibus and the GitLab source installation guide.
-Starting with GitLab 11.4, Gitaly is a replacement for NFS except
-when the [Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer)
-is used.
+Starting with GitLab 11.4, Gitaly is able to serve all Git requests without
+needed a shared NFS mount for Git repository data.
+Between 11.4 and 11.8 the exception was the
+[Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
+But since 11.8 the indexer uses Gitaly for data access as well. NFS can still
+be leveraged for redudancy on block level of the Git data. But only has to
+be mounted on the Gitaly server.
+
+NOTE: **Note:** While Gitaly can be used as a replacement for NFS, we do not recommend
+using EFS as it may impact GitLab's performance. Please review the [relevant documentation](../high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
+for more details.
### Network architecture
@@ -62,13 +70,16 @@ is used.
- Gitaly addresses must be specified in such a way that they resolve
correctly for ALL Gitaly clients
- Gitaly clients are: unicorn, sidekiq, gitlab-workhorse,
- gitlab-shell, and Gitaly itself
+ gitlab-shell, Elasticsearch Indexer, and Gitaly itself
- special case: a Gitaly server must be able to make RPC calls **to
itself** via its own (Gitaly address, Gitaly token) pair as
specified in `gitlab-rails/config/gitlab.yml`
- Gitaly servers must not be exposed to the public internet
-Gitaly network traffic is unencrypted so you should use a firewall to
+Gitaly network traffic is unencrypted by default, but supports
+[TLS](#tls-support). Authentication is done through a static token.
+
+NOTE: **Note:** Gitaly network traffic is unencrypted so we recommend a firewall to
restrict access to your Gitaly server.
Below we describe how to configure a Gitaly server at address
@@ -76,9 +87,19 @@ Below we describe how to configure a Gitaly server at address
your GitLab installation has two repository storages, `default` and
`storage1`.
+### Installation
+
+First install Gitaly using either Omnibus or from source.
+
+Omnibus: [Download/install](https://about.gitlab.com/installation) the Omnibus GitLab
+package you want using **steps 1 and 2** from the GitLab downloads page but
+**_do not_** provide the `EXTERNAL_URL=` value.
+
+Source: [Install Gitaly](../../install/installation.md#install-gitaly)
+
### Client side token configuration
-Start by configuring a token on the client side.
+Configure a token on the client side.
Omnibus installations:
@@ -104,7 +125,7 @@ changes to be picked up.
Next, on the Gitaly server, we need to configure storage paths, enable
the network listener and configure the token.
-Note: if you want to reduce the risk of downtime when you enable
+NOTE: **Note:** if you want to reduce the risk of downtime when you enable
authentication you can temporarily disable enforcement, see [the
documentation on configuring Gitaly
authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication)
@@ -116,12 +137,17 @@ the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gi
from an existing GitLab server to the Gitaly server. Without this shared secret,
Git operations in GitLab will result in an API error.
-> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is
+NOTE: **Note:** In most or all cases the storage paths below end in `/repositories` which is
different than `path` in `git_data_dirs` of Omnibus installations. Check the
directory layout on your Gitaly server to be sure.
Omnibus installations:
+<!--
+updates to following example must also be made at
+https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+-->
+
```ruby
# /etc/gitlab/gitlab.rb
@@ -141,6 +167,7 @@ gitlab_rails['auto_migrate'] = false
# Configure the gitlab-shell API callback URL. Without this, `git push` will
# fail. This can be your 'front door' GitLab URL or an internal load
# balancer.
+# Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
# Make Gitaly accept connections on all network interfaces. You must use
@@ -193,18 +220,20 @@ network, firewall, or name resolution problem preventing your GitLab
server from reaching the Gitaly server then all Gitaly requests will
fail.
+Additionally, you need to
+[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
+
We assume that your Gitaly server can be reached at
-`gitaly.internal:8075` from your GitLab server, and that your GitLab
-NFS shares are mounted at `/mnt/gitlab/default` and
-`/mnt/gitlab/storage1` respectively.
+`gitaly.internal:8075` from your GitLab server, and that Gitaly can read and
+write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1` respectively.
Omnibus installations:
```ruby
# /etc/gitlab/gitlab.rb
git_data_dirs({
- 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tcp://gitaly.internal:8075' },
- 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+ 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
})
gitlab_rails['gitaly_token'] = 'abc123secret'
@@ -218,10 +247,8 @@ gitlab:
repositories:
storages:
default:
- path: /mnt/gitlab/default/repositories
gitaly_address: tcp://gitaly.internal:8075
storage1:
- path: /mnt/gitlab/storage1/repositories
gitaly_address: tcp://gitaly.internal:8075
gitaly:
@@ -236,14 +263,26 @@ repository from your GitLab server over HTTP.
## TLS support
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.8.
-Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
+Gitaly supports TLS encryption. To be able to communicate
with a Gitaly instance that listens for secure connections you will need to use `tls://` url
scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration.
The admin needs to bring their own certificate as we do not provide that automatically.
-The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
+The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+
+Note that it is possible to configure Gitaly servers with both an
+unencrypted listening address `listen_addr` and an encrypted listening
+address `tls_listen_addr` at the same time. This allows you to do a
+gradual transition from unencrypted to encrypted traffic, if necessary.
+
+To observe what type of connections are actually being used in a
+production environment you can use the following Prometheus query:
+
+```
+sum(rate(gitaly_connections_total[5m])) by (type)
+```
### Example TLS configuration
@@ -300,6 +339,66 @@ certificate_path = '/path/to/cert.pem'
key_path = '/path/to/key.pem'
```
+## Gitaly-ruby
+
+Gitaly was developed to replace Ruby application code in gitlab-ce/ee.
+In order to save time and/or avoid the risk of rewriting existing
+application logic, in some cases we chose to copy some application code
+from gitlab-ce into Gitaly almost as-is. To be able to run that code, we
+made gitaly-ruby, which is a sidecar process for the main Gitaly Go
+process. Some examples of things that are implemented in gitaly-ruby are
+RPC's that deal with wiki's, and RPC's that create commits on behalf of
+a user, such as merge commits.
+
+### Number of gitaly-ruby workers
+
+Gitaly-ruby has much less capacity than Gitaly itself. If your Gitaly
+server has to handle a lot of request, the default setting of having
+just 1 active gitaly-ruby sidecar might not be enough. If you see
+ResourceExhausted errors from Gitaly it's very likely that you have not
+enough gitaly-ruby capacity.
+
+You can increase the number of gitaly-ruby processes on your Gitaly
+server with the following settings.
+
+Omnibus:
+
+```ruby
+# /etc/gitlab/gitlab.rb
+# Default is 2 workers. The minimum is 2; 1 worker is always reserved as
+# a passive stand-by.
+gitaly['ruby_num_workers'] = 4
+```
+
+Source:
+
+```toml
+# /home/git/gitaly/config.toml
+[gitaly-ruby]
+num_workers = 4
+```
+
+### Observing gitaly-ruby traffic
+
+Gitaly-ruby is a somewhat hidden, internal implementation detail of
+Gitaly. There is not that much visibility into what goes on inside
+gitaly-ruby processes.
+
+If you have Prometheus set up to scrape your Gitaly process, you can see
+request rates and error codes for individual RPC's in gitaly-ruby by
+querying `grpc_client_handled_total`. Strictly speaking this metric does
+not differentiate between gitaly-ruby and other RPC's, but in practice
+(as of GitLab 11.9), all gRPC calls made by Gitaly itself are internal
+calls from the main Gitaly process to one of its gitaly-ruby sidecars.
+
+Assuming your `grpc_client_handled_total` counter only observes Gitaly,
+the following query shows you RPC's are (most likely) internally
+implemented as calls to gitaly-ruby.
+
+```
+sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0
+```
+
## Disabling or enabling the Gitaly service in a cluster environment
If you are running Gitaly [as a remote
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 49fe80fb2a6..002deeaf945 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -1,4 +1,4 @@
-# High Availability
+# Scaling and High Availability
GitLab supports several different types of clustering and high-availability.
The solution you choose will be based on the level of scalability and
@@ -13,51 +13,197 @@ of Git, developers can still commit code locally even when GitLab is not
available. However, some GitLab features such as the issue tracker and
Continuous Integration are not available when GitLab is down.
-**Keep in mind that all Highly Available solutions come with a trade-off between
+**Keep in mind that all highly-available solutions come with a trade-off between
cost/complexity and uptime**. The more uptime you want, the more complex the
solution. And the more complex the solution, the more work is involved in
setting up and maintaining it. High availability is not free and every HA
solution should balance the costs against the benefits.
-## Architecture
-
-There are two kinds of setups:
-
-- active/active
-- active/passive
-
-### Active/Active
-
-This architecture scales easily because all application servers handle
-user requests simultaneously. The database, Redis, and GitLab application are
-all deployed on separate servers. The configuration is **only** highly-available
-if the database, Redis and storage are also configured as such.
-
-Follow the steps below to configure an active/active setup:
+There are many options when choosing a highly-available GitLab architecture. We
+recommend engaging with GitLab Support to choose the best architecture for your
+use-case. This page contains some various options and guidelines based on
+experience with GitLab.com and Enterprise Edition on-premises customers.
+
+For a detailed insight into how GitLab scales and configures GitLab.com, you can
+watch [this 1 hour Q&A](https://www.youtube.com/watch?v=uCU8jdYzpac)
+with [John Northrup](https://gitlab.com/northrup), one of our infrastructure
+engineers, and live questions coming in from some of our customers.
+
+## GitLab Components
+
+The following components need to be considered for a scaled or highly-available
+environment. In many cases components can be combined on the same nodes to reduce
+complexity.
+
+- Unicorn/Workhorse - Web-requests (UI, API, Git over HTTP)
+- Sidekiq - Asynchronous/Background jobs
+- PostgreSQL - Database
+ - Consul - Database service discovery and health checks/failover
+ - PGBouncer - Database pool manager
+- Redis - Key/Value store (User sessions, cache, queue for Sidekiq)
+ - Sentinel - Redis health check/failover manager
+- Gitaly - Provides high-level RPC access to Git repositories
+
+## Scalable Architecture Examples
+
+When an organization reaches a certain threshold it will be necessary to scale
+the GitLab instance. Still, true high availability may not be necessary. There
+are options for scaling GitLab instances relatively easily without incurring the
+infrastructure and maintenance costs of full high availability.
+
+### Basic Scaling
+
+This is the simplest form of scaling and will work for the majority of
+cases. Backend components such as PostgreSQL, Redis and storage are offloaded
+to their own nodes while the remaining GitLab components all run on 2 or more
+application nodes.
+
+This form of scaling also works well in a cloud environment when it is more
+cost-effective to deploy several small nodes rather than a single
+larger one.
+
+- 1 PostgreSQL node
+- 1 Redis node
+- 1 NFS/Gitaly storage server
+- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq)
+
+#### Installation Instructions
+
+Complete the following installation steps in order. A link at the end of each
+section will bring you back to the Scalable Architecture Examples section so
+you can continue with the next step.
+
+1. [PostgreSQL](database.md#postgresql-in-a-scaled-environment)
+1. [Redis](redis.md#redis-in-a-scaled-environment)
+1. [Gitaly](gitaly.md) (recommended) or [NFS](nfs.md)
+1. [GitLab application nodes](gitlab.md)
+
+### Full Scaling
+
+For very large installations it may be necessary to further split components
+for maximum scalability. In a fully-scaled architecture the application node
+is split into separate Sidekiq and Unicorn/Workhorse nodes. One indication that
+this architecture is required is if Sidekiq queues begin to periodically increase
+in size, indicating that there is contention or not enough resources.
+
+- 1 PostgreSQL node
+- 1 Redis node
+- 2 or more NFS/Gitaly storage servers
+- 2 or more Sidekiq nodes
+- 2 or more GitLab application nodes (Unicorn, Workhorse)
+
+## High Availability Architecture Examples
+
+When organizations require scaling *and* high availability the following
+architectures can be utilized. As the introduction section at the top of this
+page mentions, there is a tradeoff between cost/complexity and uptime. Be sure
+this complexity is absolutely required before taking the step into full
+high availability.
+
+For all examples below, we recommend running Consul and Redis Sentinel on
+dedicated nodes. If Consul is running on PostgreSQL nodes or Sentinel on
+Redis nodes there is a potential that high resource usage by PostgreSQL or
+Redis could prevent communication between the other Consul and Sentinel nodes.
+This may lead to the other nodes believing a failure has occurred and automated
+failover is necessary. Isolating them from the services they monitor reduces
+the chances of split-brain.
+
+The examples below do not really address high availability of NFS. Some enterprises
+have access to NFS appliances that manage availability. This is the best case
+scenario. In the future, GitLab may offer a more user-friendly solution to
+[GitLab HA Storage](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2472).
+
+There are many options in between each of these examples. Work with GitLab Support
+to understand the best starting point for your workload and adapt from there.
+
+### Horizontal
+
+This is the simplest form of high availability and scaling. It requires the
+fewest number of individual servers (virtual or physical) but does have some
+trade-offs and limits.
+
+This architecture will work well for many GitLab customers. Larger customers
+may begin to notice certain events cause contention/high load - for example,
+cloning many large repositories with binary files, high API usage, a large
+number of enqueued Sidekiq jobs, etc. If this happens you should consider
+moving to a hybrid or fully distributed architecture depending on what is causing
+the contention.
+
+- 3 PostgreSQL nodes
+- 2 Redis nodes
+- 3 Consul/Sentinel nodes
+- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq, PGBouncer)
+- 1 NFS/Gitaly server
+
+![Horizontal architecture diagram](img/horizontal.png)
+
+### Hybrid
+
+In this architecture, certain components are split on dedicated nodes so high
+resource usage of one component does not interfere with others. In larger
+environments this is a good architecture to consider if you foresee or do have
+contention due to certain workloads.
+
+- 3 PostgreSQL nodes
+- 1 PgBouncer node
+- 2 Redis nodes
+- 3 Consul/Sentinel nodes
+- 2 or more Sidekiq nodes
+- 2 or more GitLab application nodes (Unicorn, Workhorse)
+- 1 or more NFS/Gitaly servers
+- 1 Monitoring node (Prometheus, Grafana)
+
+![Hybrid architecture diagram](img/hybrid.png)
+
+#### Reference Architecture
+
+- **Status:** Work-in-progress
+- **Supported Users (approximate):** 10,000
+- **Related Issues:** [gitlab-com/support/support-team-meta#1513](https://gitlab.com/gitlab-com/support/support-team-meta/issues/1513),
+ [gitlab-org/quality/team-tasks#110](https://gitlab.com/gitlab-org/quality/team-tasks/issues/110)
+
+The Support and Quality teams are in the process of building and performance testing
+an environment that will support about 10,000 users. The specifications below
+are a work-in-progress representation of the work so far. Quality will be
+certifying this environment in FY20-Q2. The specifications may be adjusted
+prior to certification based on performance testing.
+
+- 3 PostgreSQL - 4 CPU, 8GB RAM
+- 1 PgBouncer - 2 CPU, 4GB RAM
+- 2 Redis - 2 CPU, 8GB RAM
+- 3 Consul/Sentinel - 2 CPU, 2GB RAM
+- 4 Sidekiq - 4 CPU, 8GB RAM
+- 5 GitLab application nodes - 20 CPU, 64GB RAM
+- 1 Gitaly - 20 CPU, 64GB RAM
+- 1 Monitoring node - 4 CPU, 8GB RAM
+
+### Fully Distributed
+
+This architecture scales to hundreds of thousands of users and projects and is
+the basis of the GitLab.com architecture. While this scales well it also comes
+with the added complexity of many more nodes to configure, manage and monitor.
+
+- 3 PostgreSQL nodes
+- 4 or more Redis nodes (2 separate clusters for persistent and cache data)
+- 3 Consul nodes
+- 3 Sentinel nodes
+- Multiple dedicated Sidekiq nodes (Split into real-time, best effort, ASAP,
+ CI Pipeline and Pull Mirror sets)
+- 2 or more Git nodes (Git over SSH/Git over HTTP)
+- 2 or more API nodes (All requests to `/api`)
+- 2 or more Web nodes (All other web requests)
+- 2 or more NFS/Gitaly servers
+
+![Fully Distributed architecture diagram](img/fully-distributed.png)
+
+The following pages outline the steps necessary to configure each component
+separately:
1. [Configure the database](database.md)
1. [Configure Redis](redis.md)
1. [Configure Redis for GitLab source installations](redis_source.md)
1. [Configure NFS](nfs.md)
+ 1. [NFS Client and Host setup](nfs_host_client_setup.md)
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
-![Active/Active HA Diagram](../img/high_availability/active-active-diagram.png)
-
-### Active/Passive
-
-For pure high-availability/failover with no scaling you can use an
-active/passive configuration. This utilizes DRBD (Distributed Replicated
-Block Device) to keep all data in sync. DRBD requires a low latency link to
-remain in sync. It is not advisable to attempt to run DRBD between data centers
-or in different cloud availability zones.
-
-> **Note:** GitLab recommends against choosing this HA method because of the
- complexity of managing DRBD and crafting automatic failover. This is
- *compatible* with GitLab, but not officially *supported*. If you are
- an EE customer, support will help you with GitLab related problems, but if the
- root cause is identified as DRBD, we will not troubleshoot further.
-
-Components/Servers Required: 2 servers/virtual machines (one active/one passive)
-
-![Active/Passive HA Diagram](../img/high_availability/active-passive-diagram.png)
diff --git a/doc/administration/high_availability/alpha_database.md b/doc/administration/high_availability/alpha_database.md
new file mode 100644
index 00000000000..7bf20be60e6
--- /dev/null
+++ b/doc/administration/high_availability/alpha_database.md
@@ -0,0 +1,6 @@
+---
+redirect_to: 'database.md'
+---
+
+This documentation has been moved to the main
+[database documentation](database.md#configure_using_omnibus_for_high_availability).
diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md
new file mode 100644
index 00000000000..056b7fc15d9
--- /dev/null
+++ b/doc/administration/high_availability/consul.md
@@ -0,0 +1,105 @@
+# Working with the bundled Consul service **[PREMIUM ONLY]**
+
+## Overview
+
+As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](http://consul.io) that can be managed through `/etc/gitlab/gitlab.rb`.
+
+A Consul cluster consists of multiple server agents, as well as client agents that run on other nodes which need to talk to the consul cluster.
+
+## Operations
+
+### Checking cluster membership
+
+To see which nodes are part of the cluster, run the following on any member in the cluster
+```
+# /opt/gitlab/embedded/bin/consul members
+Node Address Status Type Build Protocol DC
+consul-b XX.XX.X.Y:8301 alive server 0.9.0 2 gitlab_consul
+consul-c XX.XX.X.Y:8301 alive server 0.9.0 2 gitlab_consul
+consul-c XX.XX.X.Y:8301 alive server 0.9.0 2 gitlab_consul
+db-a XX.XX.X.Y:8301 alive client 0.9.0 2 gitlab_consul
+db-b XX.XX.X.Y:8301 alive client 0.9.0 2 gitlab_consul
+```
+
+Ideally all nodes will have a `Status` of `alive`.
+
+### Restarting the server cluster
+
+**Note**: This section only applies to server agents. It is safe to restart client agents whenever needed.
+
+If it is necessary to restart the server cluster, it is important to do this in a controlled fashion in order to maintain quorum. If quorum is lost, you will need to follow the consul [outage recovery](#outage-recovery) process to recover the cluster.
+
+To be safe, we recommend you only restart one server agent at a time to ensure the cluster remains intact.
+
+For larger clusters, it is possible to restart multiple agents at a time. See the [Consul consensus document](https://www.consul.io/docs/internals/consensus.html#deployment-table) for how many failures it can tolerate. This will be the number of simulateneous restarts it can sustain.
+
+## Troubleshooting
+
+### Consul server agents unable to communicate
+
+By default, the server agents will attempt to [bind](https://www.consul.io/docs/agent/options.html#_bind) to '0.0.0.0', but they will advertise the first private IP address on the node for other agents to communicate with them. If the other nodes cannot communicate with a node on this address, then the cluster will have a failed status.
+
+You will see messages like the following in `gitlab-ctl tail consul` output if you are running into this issue:
+
+```
+2017-09-25_19:53:39.90821 2017/09/25 19:53:39 [WARN] raft: no known peers, aborting election
+2017-09-25_19:53:41.74356 2017/09/25 19:53:41 [ERR] agent: failed to sync remote state: No cluster leader
+```
+
+
+To fix this:
+
+1. Pick an address on each node that all of the other nodes can reach this node through.
+1. Update your `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ consul['configuration'] = {
+ ...
+ bind_addr: 'IP ADDRESS'
+ }
+ ```
+1. Run `gitlab-ctl reconfigure`
+
+If you still see the errors, you may have to [erase the consul database and reinitialize](#recreate-from-scratch) on the affected node.
+
+### Consul agents do not start - Multiple private IPs
+
+In the case that a node has multiple private IPs the agent be confused as to which of the private addresses to advertise, and then immediately exit on start.
+
+You will see messages like the following in `gitlab-ctl tail consul` output if you are running into this issue:
+
+```
+2017-11-09_17:41:45.52876 ==> Starting Consul agent...
+2017-11-09_17:41:45.53057 ==> Error creating agent: Failed to get advertise address: Multiple private IPs found. Please configure one.
+```
+
+To fix this:
+
+1. Pick an address on the node that all of the other nodes can reach this node through.
+1. Update your `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ consul['configuration'] = {
+ ...
+ bind_addr: 'IP ADDRESS'
+ }
+ ```
+1. Run `gitlab-ctl reconfigure`
+
+### Outage recovery
+
+If you lost enough server agents in the cluster to break quorum, then the cluster is considered failed, and it will not function without manual intervenetion.
+
+#### Recreate from scratch
+By default, GitLab does not store anything in the consul cluster that cannot be recreated. To erase the consul database and reinitialize
+
+```
+# gitlab-ctl stop consul
+# rm -rf /var/opt/gitlab/consul/data
+# gitlab-ctl start consul
+```
+
+After this, the cluster should start back up, and the server agents rejoin. Shortly after that, the client agents should rejoin as well.
+
+#### Recover a failed cluster
+If you have taken advantage of consul to store other data, and want to restore the failed cluster, please follow the [Consul guide](https://www.consul.io/docs/guides/outage.html) to recover a failed cluster.
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index c1eeb40b98f..3b874e5d312 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -1,11 +1,6 @@
-# Configuring a Database for GitLab HA
+# Configuring PostgreSQL for Scaling and High Availability
-You can choose to install and manage a database server (PostgreSQL/MySQL)
-yourself, or you can use GitLab Omnibus packages to help. GitLab recommends
-PostgreSQL. This is the database that will be installed if you use the
-Omnibus package to manage your database.
-
-## Configure your own database server
+## Provide your own PostgreSQL instance **[CORE ONLY]**
If you're hosting GitLab on a cloud provider, you can optionally use a
managed service for PostgreSQL. For example, AWS offers a managed Relational
@@ -20,93 +15,1144 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
1. Configure the GitLab application servers with the appropriate details.
This step is covered in [Configuring GitLab for HA](gitlab.md).
-## Configure using Omnibus
+## PostgreSQL in a Scaled Environment
-1. Download/install GitLab Omnibus using **steps 1 and 2** from
- [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
- steps on the download page.
-1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
- Be sure to change the `external_url` to match your eventual GitLab front-end
- URL. If there is a directive listed below that you do not see in the configuration, be sure to add it.
+This section is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments including [Basic Scaling](README.md#basic-scaling) and
+[Full Scaling](README.md#full-scaling).
- ```ruby
- external_url 'https://gitlab.example.com'
+### Provide your own PostgreSQL instance **[CORE ONLY]**
+
+If you want to use your own deployed PostgreSQL instance(s),
+see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled PostgreSQL.
+
+### Standalone PostgreSQL using GitLab Omnibus **[CORE ONLY]**
+
+1. SSH into the PostgreSQL server.
+1. [Download/install](https://about.gitlab.com/installation) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Do not complete any other steps on the download page.
+1. Generate a password hash for PostgreSQL. This assumes you will use the default
+ username of `gitlab` (recommended). The command will request a password
+ and confirmation. Use the value that is output by this command in the next
+ step as the value of `POSTGRESQL_PASSWORD_HASH`.
+ ```sh
+ sudo gitlab-ctl pg-password-md5 gitlab
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents below, updating placeholder
+ values appropriately.
+
+ - `POSTGRESQL_PASSWORD_HASH` - The value output from the previous step
+ - `APPLICATION_SERVER_IP_BLOCKS` - A space delimited list of IP subnets or IP
+ addresses of the GitLab application servers that will connect to the
+ database. Example: `%w(123.123.123.123/32 123.123.123.234/32)`
+
+ ```ruby
# Disable all components except PostgreSQL
roles ['postgres_role']
+ repmgr['enable'] = false
+ consul['enable'] = false
+ prometheus['enable'] = false
+ alertmanager['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ redis_exporter['enable'] = false
+ gitlab_monitor['enable'] = false
+
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['port'] = 5432
+
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ # ????
+ postgresql['trust_auth_cidr_addresses'] = %w(APPLICATION_SERVER_IP_BLOCKS)
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+ NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
+
+1. [Reconfigure GitLab] for the changes to take effect.
+1. Note the PostgreSQL node's IP address or hostname, port, and
+ plain text password. These will be necessary when configuring the GitLab
+ application servers later.
+
+Advanced configuration options are supported and can be added if
+needed.
+
+Continue configuration of other components by going
+[back to Scaled Architectures](README.md#scalable-architecture-examples)
+
+## PostgreSQL with High Availability
+
+This section is relevant for [High Availability Architecture](README.md#high-availability-architecture-examples)
+environments including [Horizontal](README.md#horizontal),
+[Hybrid](README.md#hybrid), and
+[Fully Distributed](README.md#fully-distributed).
+
+### Provide your own PostgreSQL instance **[CORE ONLY]**
+
+If you want to use your own deployed PostgreSQL instance(s),
+see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled PostgreSQL.
+
+### High Availability with GitLab Omnibus **[PREMIUM ONLY]**
+
+> Important notes:
+>
+> - This document will focus only on configuration supported with [GitLab Premium](https://about.gitlab.com/pricing/), using the Omnibus GitLab package.
+> - If you are a Community Edition or Starter user, consider using a cloud hosted solution.
+> - This document will not cover installations from source.
+>
+> - If HA setup is not what you were looking for, see the [database configuration document](http://docs.gitlab.com/omnibus/settings/database.html)
+> for the Omnibus GitLab packages.
+>
+> Please read this document fully before attempting to configure PostgreSQL HA
+> for GitLab.
+>
+> This configuration is GA in EE 10.2.
+
+The recommended configuration for a PostgreSQL HA requires:
+
+- A minimum of three database nodes
+ - Each node will run the following services:
+ - `PostgreSQL` - The database itself
+ - `repmgrd` - A service to monitor, and handle failover in case of a failure
+ - `Consul` agent - Used for service discovery, to alert other nodes when failover occurs
+- A minimum of three `Consul` server nodes
+- A minimum of one `pgbouncer` service node
+
+You also need to take into consideration the underlying network topology,
+making sure you have redundant connectivity between all Database and GitLab instances,
+otherwise the networks will become a single point of failure.
+
+#### Architecture
+
+![PG HA Architecture](img/pg_ha_architecture.png)
+
+Database nodes run two services with PostgreSQL:
+
+- Repmgrd. Monitors the cluster and handles failover when issues with the master occur. The failover consists of:
+ - Selecting a new master for the cluster.
+ - Promoting the new node to master.
+ - Instructing remaining servers to follow the new master node.
+
+ On failure, the old master node is automatically evicted from the cluster, and should be rejoined manually once recovered.
+- Consul. Monitors the status of each node in the database cluster and tracks its health in a service definition on the consul cluster.
+
+Alongside pgbouncer, there is a consul agent that watches the status of the PostgreSQL service. If that status changes, consul runs a script which updates the configuration and reloads pgbouncer
+
+##### Connection flow
+
+Each service in the package comes with a set of [default ports](https://docs.gitlab.com/omnibus/package-information/defaults.html#ports). You may need to make specific firewall rules for the connections listed below:
+
+- Application servers connect to [PgBouncer default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#pgbouncer)
+- PgBouncer connects to the primary database servers [PostgreSQL default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#postgresql)
+- Repmgr connects to the database servers [PostgreSQL default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#postgresql)
+- Postgres secondaries connect to the primary database servers [PostgreSQL default port](https://docs.gitlab.com/omnibus/package-information/defaults.html#postgresql)
+- Consul servers and agents connect to each others [Consul default ports](https://docs.gitlab.com/omnibus/package-information/defaults.html#consul)
+
+#### Required information
+
+Before proceeding with configuration, you will need to collect all the necessary
+information.
+
+##### Network information
+
+PostgreSQL does not listen on any network interface by default. It needs to know
+which IP address to listen on in order to be accessible to other services.
+Similarly, PostgreSQL access is controlled based on the network source.
+
+This is why you will need:
+
+- IP address of each nodes network interface. This can be set to `0.0.0.0` to
+ listen on all interfaces. It cannot be set to the loopack address `127.0.0.1`.
+- Network Address. This can be in subnet (i.e. `192.168.0.0/255.255.255.0`)
+ or CIDR (i.e. `192.168.0.0/24`) form.
+
+##### User information
+
+Various services require different configuration to secure
+the communication as well as information required for running the service.
+Bellow you will find details on each service and the minimum required
+information you need to provide.
+
+##### Consul information
+
+When using default setup, minimum configuration requires:
+
+- `CONSUL_USERNAME`. Defaults to `gitlab-consul`
+- `CONSUL_DATABASE_PASSWORD`. Password for the database user.
+- `CONSUL_PASSWORD_HASH`. This is a hash generated out of consul username/password pair.
+ Can be generated with:
+
+ ```sh
+ sudo gitlab-ctl pg-password-md5 CONSUL_USERNAME
+ ```
+
+- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
+
+Few notes on the service itself:
+
+- The service runs under a system account, by default `gitlab-consul`.
+ - If you are using a different username, you will have to specify it. We
+will refer to it with `CONSUL_USERNAME`,
+- There will be a database user created with read only access to the repmgr
+database
+- Passwords will be stored in the following locations:
+ - `/etc/gitlab/gitlab.rb`: hashed
+ - `/var/opt/gitlab/pgbouncer/pg_auth`: hashed
+ - `/var/opt/gitlab/gitlab-consul/.pgpass`: plaintext
+
+##### PostgreSQL information
+
+When configuring PostgreSQL, we will set `max_wal_senders` to one more than
+the number of database nodes in the cluster.
+This is used to prevent replication from using up all of the
+available database connections.
+
+In this document we are assuming 3 database nodes, which makes this configuration:
+
+```
+postgresql['max_wal_senders'] = 4
+```
+
+As previously mentioned, you'll have to prepare the network subnets that will
+be allowed to authenticate with the database.
+You'll also need to supply the IP addresses or DNS records of Consul
+server nodes.
+
+We will need the following password information for the application's database user:
+
+- `POSTGRESQL_USERNAME`. Defaults to `gitlab`
+- `POSTGRESQL_USER_PASSWORD`. The password for the database user
+- `POSTGRESQL_PASSWORD_HASH`. This is a hash generated out of the username/password pair.
+ Can be generated with:
+
+ ```sh
+ sudo gitlab-ctl pg-password-md5 POSTGRESQL_USERNAME
+ ```
+
+##### Pgbouncer information
+
+When using default setup, minimum configuration requires:
+
+- `PGBOUNCER_USERNAME`. Defaults to `pgbouncer`
+- `PGBOUNCER_PASSWORD`. This is a password for pgbouncer service.
+- `PGBOUNCER_PASSWORD_HASH`. This is a hash generated out of pgbouncer username/password pair.
+ Can be generated with:
+
+ ```sh
+ sudo gitlab-ctl pg-password-md5 PGBOUNCER_USERNAME
+ ```
+
+- `PGBOUNCER_NODE`, is the IP address or a FQDN of the node running Pgbouncer.
+
+Few notes on the service itself:
+
+- The service runs as the same system account as the database
+ - In the package, this is by default `gitlab-psql`
+- If you use a non-default user account for Pgbouncer service (by default `pgbouncer`), you will have to specify this username. We will refer to this requirement with `PGBOUNCER_USERNAME`.
+- The service will have a regular database user account generated for it
+ - This defaults to `repmgr`
+- Passwords will be stored in the following locations:
+ - `/etc/gitlab/gitlab.rb`: hashed, and in plain text
+ - `/var/opt/gitlab/pgbouncer/pg_auth`: hashed
+
+##### Repmgr information
+
+When using default setup, you will only have to prepare the network subnets that will
+be allowed to authenticate with the service.
+
+Few notes on the service itself:
+
+- The service runs under the same system account as the database
+ - In the package, this is by default `gitlab-psql`
+- The service will have a superuser database user account generated for it
+ - This defaults to `gitlab_repmgr`
+
+#### Installing Omnibus GitLab
+
+First, make sure to [download/install](https://about.gitlab.com/installation)
+GitLab Omnibus **on each node**.
+
+Make sure you install the necessary dependencies from step 1,
+add GitLab package repository from step 2.
+When installing the GitLab package, do not supply `EXTERNAL_URL` value.
+
+#### Configuring the Consul nodes
+
+On each Consul node perform the following:
+
+1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul-information) before executing the next step.
+
+1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except Consul
+ roles ['consul_role']
+
+ # START user configuration
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ server: true,
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ #
+ # END user configuration
+ ```
+
+ > `consul_role` was introduced with GitLab 10.3
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+##### Consul Checkpoint
+
+Before moving on, make sure Consul is configured correctly. Run the following
+command to verify all server nodes are communicating:
+
+```sh
+/opt/gitlab/embedded/bin/consul members
+```
+
+The output should be similar to:
+
+```
+Node Address Status Type Build Protocol DC
+CONSUL_NODE_ONE XXX.XXX.XXX.YYY:8301 alive server 0.9.2 2 gitlab_consul
+CONSUL_NODE_TWO XXX.XXX.XXX.YYY:8301 alive server 0.9.2 2 gitlab_consul
+CONSUL_NODE_THREE XXX.XXX.XXX.YYY:8301 alive server 0.9.2 2 gitlab_consul
+```
+
+If any of the nodes isn't `alive` or if any of the three nodes are missing,
+check the [Troubleshooting section](#troubleshooting) before proceeding.
+
+#### Configuring the Database nodes
+
+1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul-information), [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer-information), [`POSTGRESQL_PASSWORD_HASH`](#postgresql-information), the [number of db nodes](#postgresql-information), and the [network address](#network-information) before executing the next step.
+
+1. On the master database node, edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
# PostgreSQL configuration
- gitlab_rails['db_password'] = 'DB password'
- postgresql['md5_auth_cidr_addresses'] = ['0.0.0.0/0']
postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
# Disable automatic database migrations
gitlab_rails['auto_migrate'] = false
+
+ # Configure the consul agent
+ consul['services'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
+ # Replace X with value of number of db nodes + 1
+ postgresql['max_wal_senders'] = X
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 XXX.XXX.XXX.XXX/YY)
+
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+ #
+ # END user configuration
```
-1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
+ > `postgres_role` was introduced with GitLab 10.3
+
+1. On secondary nodes, add all the configuration specified above for primary node
+ to `/etc/gitlab/gitlab.rb`. In addition, append the following configuration
+ to inform gitlab-ctl that they are standby nodes initially and it need not
+ attempt to register them as primary node
+ ```
+ # HA setting to specify if a node should attempt to be master on initialization
+ repmgr['master_on_initialization'] = false
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+> Please note:
+>
+> - If you want your database to listen on a specific interface, change the config:
+> `postgresql['listen_address'] = '0.0.0.0'`.
+> - If your Pgbouncer service runs under a different user account,
+> you also need to specify: `postgresql['pgbouncer_user'] = PGBOUNCER_USERNAME` in
+> your configuration.
+
+##### Database nodes post-configuration
- > **Note**: This `reconfigure` step will result in some errors.
- That's OK - don't be alarmed.
+###### Primary node
+
+Select one node as a primary node.
1. Open a database prompt:
+ ```sh
+ gitlab-psql -d gitlabhq_production
```
- su - gitlab-psql
- /bin/bash
- psql -h /var/opt/gitlab/postgresql -d template1
- # Output:
+1. Enable the `pg_trgm` extension:
+
+ ```sh
+ CREATE EXTENSION pg_trgm;
+ ```
- psql (9.2.15)
- Type "help" for help.
+1. Exit the database prompt by typing `\q` and Enter.
- template1=#
+1. Verify the cluster is initialized with one node:
+
+ ```sh
+ gitlab-ctl repmgr cluster show
+ ```
+
+ The output should be similar to the following:
+
+ ```
+ Role | Name | Upstream | Connection String
+ ----------+----------|----------|----------------------------------------
+ * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
```
-1. Run the following command at the database prompt and you will be asked to
- enter the new password for the PostgreSQL superuser.
+1. Note down the hostname/ip in the connection string: `host=HOSTNAME`. We will
+ refer to the hostname in the next section as `MASTER_NODE_NAME`. If the value
+ is not an IP address, it will need to be a resolvable name (via DNS or
+ `/etc/hosts`)
+###### Secondary nodes
+
+1. Set up the repmgr standby:
+
+ ```sh
+ gitlab-ctl repmgr standby setup MASTER_NODE_NAME
```
- \password
- # Output:
+ Do note that this will remove the existing data on the node. The command
+ has a wait time.
+
+ The output should be similar to the following:
- Enter new password:
- Enter it again:
+ ```console
+ # gitlab-ctl repmgr standby setup MASTER_NODE_NAME
+ Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data
+ If this is not what you want, hit Ctrl-C now to exit
+ To skip waiting, rerun with the -w option
+ Sleeping for 30 seconds
+ Stopping the database
+ Removing the data
+ Cloning the data
+ Starting the database
+ Registering the node with the cluster
+ ok: run: repmgrd: (pid 19068) 0s
```
-1. Similarly, set the password for the `gitlab` database user. Use the same
- password that you specified in the `/etc/gitlab/gitlab.rb` file for
- `gitlab_rails['db_password']`.
+1. Verify the node now appears in the cluster:
+ ```sh
+ gitlab-ctl repmgr cluster show
```
- \password gitlab
- # Output:
+ The output should be similar to the following:
- Enter new password:
- Enter it again:
```
-1. Exit from editing `template1` prompt by typing `\q` and Enter.
-1. Enable the `pg_trgm` extension within the `gitlabhq_production` database:
-
+ Role | Name | Upstream | Connection String
+ ----------+---------|-----------|------------------------------------------------
+ * master | MASTER | | host=MASTER_NODE_NAME user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=STANDBY_HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
```
+
+Repeat the above steps on all secondary nodes.
+
+##### Database checkpoint
+
+Before moving on, make sure the databases are configured correctly. Run the
+following command on the **primary** node to verify that replication is working
+properly:
+
+```sh
+gitlab-ctl repmgr cluster show
+```
+
+The output should be similar to:
+
+```
+Role | Name | Upstream | Connection String
+----------+--------------|--------------|--------------------------------------------------------------------
+* master | MASTER | | host=MASTER port=5432 user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=STANDBY port=5432 user=gitlab_repmgr dbname=gitlab_repmgr
+```
+
+If the 'Role' column for any node says "FAILED", check the
+[Troubleshooting section](#troubleshooting) before proceeding.
+
+Also, check that the check master command works successfully on each node:
+
+```sh
+su - gitlab-consul
+gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node'
+```
+
+This command relies on exit codes to tell Consul whether a particular node is a master
+or secondary. The most important thing here is that this command does not produce errors.
+If there are errors it's most likely due to incorrect `gitlab-consul` database user permissions.
+Check the [Troubleshooting section](#troubleshooting) before proceeding.
+
+#### Configuring the Pgbouncer node
+
+1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul-information), [`CONSUL_PASSWORD_HASH`](#consul-information), and [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer-information) before executing the next step.
+
+1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure Pgbouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
+ # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: 'CONSUL_PASSWORD_HASH'
+ },
+ 'pgbouncer': {
+ password: 'PGBOUNCER_PASSWORD_HASH'
+ }
+ }
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+ #
+ # END user configuration
+ ```
+
+ > `pgbouncer_role` was introduced with GitLab 10.3
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+1. Create a `.pgpass` file so Consule is able to
+ reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
+
+ ```sh
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+##### PGBouncer Checkpoint
+
+1. Ensure the node is talking to the current master:
+
+ ```sh
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
+
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
+
+1. Once the console prompt is available, run the following queries:
+
+ ```sh
+ show databases ; show clients ;
+ ```
+
+ The output should be similar to the following:
+
+ ```
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
+
+#### Configuring the Application nodes
+
+These will be the nodes running the `gitlab-rails` service. You may have other
+attributes set, but the following need to be set.
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Disable PostgreSQL on the application node
+ postgresql['enable'] = false
+
+ gitlab_rails['db_host'] = 'PGBOUNCER_NODE'
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = 'POSTGRESQL_USER_PASSWORD'
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+##### Application node post-configuration
+
+Ensure that all migrations ran:
+
+```sh
+gitlab-rake gitlab:db:configure
+```
+
+> **Note**: If you encounter a `rake aborted!` error stating that PGBouncer is failing to connect to
+PostgreSQL it may be that your PGBouncer node's IP address is missing from
+PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See
+[PGBouncer error `ERROR: pgbouncer cannot connect to server`](#pgbouncer-error-error-pgbouncer-cannot-connect-to-server)
+in the Troubleshooting section before proceeding.
+
+##### Ensure GitLab is running
+
+At this point, your GitLab instance should be up and running. Verify you are
+able to login, and create issues and merge requests. If you have troubles check
+the [Troubleshooting section](#troubleshooting).
+
+#### Example configuration
+
+Here we'll show you some fully expanded example configurations.
+
+##### Example recommended setup
+
+This example uses 3 consul servers, 3 postgresql servers, and 1 application node.
+
+We start with all servers on the same 10.6.0.0/16 private network range, they
+can connect to each freely other on those addresses.
+
+Here is a list and description of each machine and the assigned IP:
+
+- `10.6.0.11`: Consul 1
+- `10.6.0.12`: Consul 2
+- `10.6.0.13`: Consul 3
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
+
+All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
+
+The external_url for GitLab is `http://gitlab.example.com`
+
+Please note that after the initial configuration, if a failover occurs, the PostgresSQL master will change to one of the available secondaries until it is failed back.
+
+##### Example recommended setup for Consul servers
+
+On each server edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+# Disable all components except Consul
+roles ['consul_role']
+
+consul['configuration'] = {
+ server: true,
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+}
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+##### Example recommended setup for PostgreSQL servers
+
+###### Primary node
+
+On primary node edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+# Disable all components except PostgreSQL and Repmgr and Consul
+roles ['postgres_role']
+
+# PostgreSQL configuration
+postgresql['listen_address'] = '0.0.0.0'
+postgresql['hot_standby'] = 'on'
+postgresql['wal_level'] = 'replica'
+postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+# Disable automatic database migrations
+gitlab_rails['auto_migrate'] = false
+
+# Configure the consul agent
+consul['services'] = %w(postgresql)
+
+postgresql['pgbouncer_user_password'] = '771a8625958a529132abe6f1a4acb19c'
+postgresql['sql_user_password'] = '450409b85a0223a214b5fb1484f34d0f'
+postgresql['max_wal_senders'] = 4
+
+postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16)
+repmgr['trust_auth_cidr_addresses'] = %w(10.6.0.0/16)
+
+consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+}
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+###### Secondary nodes
+
+On secondary nodes, edit `/etc/gitlab/gitlab.rb` and add all the configuration
+added to primary node, noted above. In addition, append the following
+configuration:
+
+```
+# HA setting to specify if a node should attempt to be master on initialization
+repmgr['master_on_initialization'] = false
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+##### Example recommended setup for application server
+
+On the server edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+external_url 'http://gitlab.example.com'
+
+gitlab_rails['db_host'] = '127.0.0.1'
+gitlab_rails['db_port'] = 6432
+gitlab_rails['db_password'] = 'toomanysecrets'
+gitlab_rails['auto_migrate'] = false
+
+postgresql['enable'] = false
+pgbouncer['enable'] = true
+consul['enable'] = true
+
+# Configure Pgbouncer
+pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+# Configure Consul agent
+consul['watchers'] = %w(postgresql)
+
+pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: '5e0e3263571e3704ad655076301d6ebe'
+ },
+ 'pgbouncer': {
+ password: '771a8625958a529132abe6f1a4acb19c'
+ }
+}
+
+consul['configuration'] = {
+ retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13)
+}
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+##### Example recommended setup manual steps
+
+After deploying the configuration follow these steps:
+
+1. On `10.6.0.21`, our primary database
+
+ Enable the `pg_trgm` extension
+
+ ```sh
gitlab-psql -d gitlabhq_production
-
+ ```
+
+ ```
CREATE EXTENSION pg_trgm;
+ ```
+
+1. On `10.6.0.22`, our first standby database
- # Output:
+ Make this node a standby of the primary
- CREATE EXTENSION
+ ```sh
+ gitlab-ctl repmgr standby setup 10.6.0.21
```
-1. Exit the database prompt by typing `\q` and Enter.
-1. Exit the `gitlab-psql` user by running `exit` twice.
-1. Run `sudo gitlab-ctl reconfigure` a final time.
-1. Configure the GitLab application servers with the appropriate details.
- This step is covered in [Configuring GitLab for HA](gitlab.md).
----
+1. On `10.6.0.23`, our second standby database
+
+ Make this node a standby of the primary
+
+ ```sh
+ gitlab-ctl repmgr standby setup 10.6.0.21
+ ```
+
+1. On `10.6.0.31`, our application server
+
+ Set gitlab-consul's pgbouncer password to `toomanysecrets`
+
+ ```sh
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+ Run database migrations
+
+ ```sh
+ gitlab-rake gitlab:db:configure
+ ```
+
+#### Example minimal setup
+
+This example uses 3 postgresql servers, and 1 application node.
+
+It differs from the [recommended setup](#example-recommended-setup) by moving the consul servers into the same servers we use for PostgreSQL.
+The trade-off is between reducing server counts, against the increased operational complexity of needing to deal with postgres [failover](#failover-procedure) and [restore](#restore-procedure) procedures in addition to [consul outage recovery](consul.md#outage-recovery) on the same set of machines.
+
+In this example we start with all servers on the same 10.6.0.0/16 private network range, they can connect to each freely other on those addresses.
+
+Here is a list and description of each machine and the assigned IP:
+
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
+
+All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
+
+The external_url for GitLab is `http://gitlab.example.com`
+
+Please note that after the initial configuration, if a failover occurs, the PostgresSQL master will change to one of the available secondaries until it is failed back.
+
+##### Example minimal configuration for database servers
+
+##### Primary node
+
+On primary database node edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+# Disable all components except PostgreSQL, Repmgr, and Consul
+roles ['postgres_role']
+
+# PostgreSQL configuration
+postgresql['listen_address'] = '0.0.0.0'
+postgresql['hot_standby'] = 'on'
+postgresql['wal_level'] = 'replica'
+postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+# Disable automatic database migrations
+gitlab_rails['auto_migrate'] = false
+
+# Configure the consul agent
+consul['services'] = %w(postgresql)
+
+postgresql['pgbouncer_user_password'] = '771a8625958a529132abe6f1a4acb19c'
+postgresql['sql_user_password'] = '450409b85a0223a214b5fb1484f34d0f'
+postgresql['max_wal_senders'] = 4
+
+postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/16)
+repmgr['trust_auth_cidr_addresses'] = %w(10.6.0.0/16)
+
+consul['configuration'] = {
+ server: true,
+ retry_join: %w(10.6.0.21 10.6.0.22 10.6.0.23)
+}
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+###### Secondary nodes
+
+On secondary nodes, edit `/etc/gitlab/gitlab.rb` and add all the information added
+to primary node, noted above. In addition, append the following configuration
+
+```
+# HA setting to specify if a node should attempt to be master on initialization
+repmgr['master_on_initialization'] = false
+```
+
+##### Example minimal configuration for application server
+
+On the server edit `/etc/gitlab/gitlab.rb`:
+
+```ruby
+external_url 'http://gitlab.example.com'
+
+gitlab_rails['db_host'] = '127.0.0.1'
+gitlab_rails['db_port'] = 6432
+gitlab_rails['db_password'] = 'toomanysecrets'
+gitlab_rails['auto_migrate'] = false
+
+postgresql['enable'] = false
+pgbouncer['enable'] = true
+consul['enable'] = true
+
+# Configure Pgbouncer
+pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+# Configure Consul agent
+consul['watchers'] = %w(postgresql)
+
+pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: '5e0e3263571e3704ad655076301d6ebe'
+ },
+ 'pgbouncer': {
+ password: '771a8625958a529132abe6f1a4acb19c'
+ }
+}
+
+consul['configuration'] = {
+ retry_join: %w(10.6.0.21 10.6.0.22 10.6.0.23)
+}
+```
+
+[Reconfigure Omnibus GitLab][reconfigure GitLab] for the changes to take effect.
+
+##### Example minimal setup manual steps
+
+The manual steps for this configuration are the same as for the [example recommended setup](#example-recommended-setup-manual-steps).
+
+#### Failover procedure
+
+By default, if the master database fails, `repmgrd` should promote one of the
+standby nodes to master automatically, and consul will update pgbouncer with
+the new master.
+
+If you need to failover manually, you have two options:
+
+**Shutdown the current master database**
+
+Run:
+
+```sh
+gitlab-ctl stop postgresql
+```
+
+The automated failover process will see this and failover to one of the
+standby nodes.
+
+**Or perform a manual failover**
+
+1. Ensure the old master node is not still active.
+1. Login to the server that should become the new master and run:
+
+ ```sh
+ gitlab-ctl repmgr standby promote
+ ```
+
+1. If there are any other standby servers in the cluster, have them follow
+ the new master server:
+
+ ```sh
+ gitlab-ctl repmgr standby follow NEW_MASTER
+ ```
+
+#### Restore procedure
+
+If a node fails, it can be removed from the cluster, or added back as a standby
+after it has been restored to service.
+
+- If you want to remove the node from the cluster, on any other node in the
+ cluster, run:
+
+ ```sh
+ gitlab-ctl repmgr standby unregister --node=X
+ ```
+
+ where X is the value of node in `repmgr.conf` on the old server.
+
+ To find this, you can use:
+
+ ```sh
+ awk -F = '$1 == "node" { print $2 }' /var/opt/gitlab/postgresql/repmgr.conf
+ ```
+
+ It will output something like:
+
+ ```
+ 959789412
+ ```
+
+ Then you will use this id to unregister the node:
+
+ ```sh
+ gitlab-ctl repmgr standby unregister --node=959789412
+ ```
+
+- To add the node as a standby server:
+
+ ```sh
+ gitlab-ctl repmgr standby follow NEW_MASTER
+ gitlab-ctl restart repmgrd
+ ```
+
+ CAUTION: **Warning:** When the server is brought back online, and before
+ you switch it to a standby node, repmgr will report that there are two masters.
+ If there are any clients that are still attempting to write to the old master,
+ this will cause a split, and the old master will need to be resynced from
+ scratch by performing a `gitlab-ctl repmgr standby setup NEW_MASTER`.
+
+#### Alternate configurations
+
+##### Database authorization
+
+By default, we give any host on the database network the permission to perform
+repmgr operations using PostgreSQL's `trust` method. If you do not want this
+level of trust, there are alternatives.
+
+You can trust only the specific nodes that will be database clusters, or you
+can require md5 authentication.
+
+##### Trust specific addresses
+
+If you know the IP address, or FQDN of all database and pgbouncer nodes in the
+cluster, you can trust only those nodes.
+
+In `/etc/gitlab/gitlab.rb` on all of the database nodes, set
+`repmgr['trust_auth_cidr_addresses']` to an array of strings containing all of
+the addresses.
+
+If setting to a node's FQDN, they must have a corresponding PTR record in DNS.
+If setting to a node's IP address, specify it as `XXX.XXX.XXX.XXX/32`.
+
+For example:
+
+```ruby
+repmgr['trust_auth_cidr_addresses'] = %w(192.168.1.44/32 db2.example.com)
+```
+
+##### MD5 Authentication
+
+If you are running on an untrusted network, repmgr can use md5 authentication
+with a [.pgpass file](https://www.postgresql.org/docs/9.6/static/libpq-pgpass.html)
+to authenticate.
+
+You can specify by IP address, FQDN, or by subnet, using the same format as in
+the previous section:
+
+1. On the current master node, create a password for the `gitlab` and
+ `gitlab_repmgr` user:
+
+ ```sh
+ gitlab-psql -d template1
+ template1=# \password gitlab_repmgr
+ Enter password: ****
+ Confirm password: ****
+ template1=# \password gitlab
+ ```
+
+1. On each database node:
+
+ 1. Edit `/etc/gitlab/gitlab.rb`:
+ 1. Ensure `repmgr['trust_auth_cidr_addresses']` is **not** set
+ 1. Set `postgresql['md5_auth_cidr_addresses']` to the desired value
+ 1. Set `postgresql['sql_replication_user'] = 'gitlab_repmgr'`
+ 1. Reconfigure with `gitlab-ctl reconfigure`
+ 1. Restart postgresql with `gitlab-ctl restart postgresql`
+
+ 1. Create a `.pgpass` file. Enter the `gitlab_repmgr` password twice to
+ when asked:
+
+ ```sh
+ gitlab-ctl write-pgpass --user gitlab_repmgr --hostuser gitlab-psql --database '*'
+ ```
+
+1. On each pgbouncer node, edit `/etc/gitlab/gitlab.rb`:
+ 1. Ensure `gitlab_rails['db_password']` is set to the plaintext password for
+ the `gitlab` database user
+ 1. [Reconfigure GitLab] for the changes to take effect
+
+## Troubleshooting
+
+#### Consul and PostgreSQL changes not taking effect.
+
+Due to the potential impacts, `gitlab-ctl reconfigure` only reloads Consul and PostgreSQL, it will not restart the services. However, not all changes can be activated by reloading.
+
+To restart either service, run `gitlab-ctl restart SERVICE`
+
+For PostgreSQL, it is usually safe to restart the master node by default. Automatic failover defaults to a 1 minute timeout. Provided the database returns before then, nothing else needs to be done. To be safe, you can stop `repmgrd` on the standby nodes first with `gitlab-ctl stop repmgrd`, then start afterwards with `gitlab-ctl start repmgrd`.
+
+On the consul server nodes, it is important to restart the consul service in a controlled fashion. Read our [consul documentation](consul.md#restarting-the-server-cluster) for instructions on how to restart the service.
+
+#### `gitlab-ctl repmgr-check-master` command produces errors
+
+If this command displays errors about database permissions it is likely that something failed during
+install, resulting in the `gitlab-consul` database user getting incorrect permissions. Follow these
+steps to fix the problem:
+
+1. On the master database node, connect to the database prompt - `gitlab-psql -d template1`
+1. Delete the `gitlab-consul` user - `DROP USER "gitlab-consul";`
+1. Exit the database prompt - `\q`
+1. [Reconfigure GitLab] and the user will be re-added with the proper permissions.
+1. Change to the `gitlab-consul` user - `su - gitlab-consul`
+1. Try the check command again - `gitlab-ctl repmgr-check-master`.
+
+Now there should not be errors. If errors still occur then there is another problem.
+
+#### PGBouncer error `ERROR: pgbouncer cannot connect to server`
+
+You may get this error when running `gitlab-rake gitlab:db:configure` or you
+may see the error in the PGBouncer log file.
+
+```
+PG::ConnectionBad: ERROR: pgbouncer cannot connect to server
+```
+
+The problem may be that your PGBouncer node's IP address is not included in the
+`trust_auth_cidr_addresses` setting in `/etc/gitlab/gitlab.rb` on the database nodes.
+
+You can confirm that this is the issue by checking the PostgreSQL log on the master
+database node. If you see the following error then `trust_auth_cidr_addresses`
+is the problem.
+
+```
+2018-03-29_13:59:12.11776 FATAL: no pg_hba.conf entry for host "123.123.123.123", user "pgbouncer", database "gitlabhq_production", SSL off
+```
+
+To fix the problem, add the IP address to `/etc/gitlab/gitlab.rb`.
+
+```
+postgresql['trust_auth_cidr_addresses'] = %w(123.123.123.123/32 <other_cidrs>)
+```
+
+[Reconfigure GitLab] for the changes to take effect.
+
+#### Issues with other components
+
+If you're running into an issue with a component not outlined here, be sure to check the troubleshooting section of their specific documentation page.
+
+- [Consul](consul.md#troubleshooting)
+- [PostgreSQL](http://docs.gitlab.com/omnibus/settings/database.html#troubleshooting)
+- [GitLab application](gitlab.md#troubleshooting)
+
+## Configure using Omnibus
+
+**Note**: We recommend that you follow the instructions here for a full [PostgreSQL cluster](#high-availability-with-gitlab-omnibus-premium-only).
+If you are reading this section due to an old bookmark, you can find that old documentation [in the repository](https://gitlab.com/gitlab-org/gitlab-ce/blob/v10.1.4/doc/administration/high_availability/database.md#configure-using-omnibus).
Read more on high-availability configuration:
@@ -114,3 +1160,6 @@ Read more on high-availability configuration:
1. [Configure NFS](nfs.md)
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
+1. [Manage the bundled Consul cluster](consul.md)
+
+[reconfigure GitLab]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md
new file mode 100644
index 00000000000..1d8e6c999cb
--- /dev/null
+++ b/doc/administration/high_availability/gitaly.md
@@ -0,0 +1,21 @@
+# Configuring Gitaly for Scaled and High Availability
+
+Gitaly does not yet support full high availability. However, Gitaly is quite
+stable and is in use on GitLab.com. Scaled and highly available GitLab environments
+should consider using Gitaly on a separate node.
+
+See the [Gitaly HA Epic](https://gitlab.com/groups/gitlab-org/-/epics/289) to
+track plans and progress toward high availability support.
+
+This document is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments and [High Availability Architecture](README.md#high-availability-architecture-examples).
+
+## Running Gitaly on its own server
+
+See [Running Gitaly on its own server](../gitaly/index.md#running-gitaly-on-its-own-server)
+in Gitaly documentation.
+
+Continue configuration of other components by going back to:
+
+- [Scaled Architectures](README.md#scalable-architecture-examples)
+- [High Availability Architectures](README.md#high-availability-architecture-examples)
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index e554c06532e..888426ece5c 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -1,8 +1,4 @@
-# Configuring GitLab for HA
-
-Assuming you have already configured a [database](database.md), [Redis](redis.md), and [NFS](nfs.md), you can
-configure the GitLab application server(s) now. Complete the steps below
-for each GitLab application server in your environment.
+# Configuring GitLab Scaling and High Availability
> **Note:** There is some additional configuration near the bottom for
additional GitLab application servers. It's important to read and understand
@@ -146,6 +142,14 @@ the share is exported and exists on the NFS server and try to remount.
---
+## Upgrading GitLab HA
+
+GitLab HA installations can be upgraded with no downtime, but the
+upgrade process must be carefully coordinated to avoid failures. See the
+[Omnibus GitLab multi-node upgrade
+document](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment)
+for more details.
+
Read more on high-availability configuration:
1. [Configure the database](database.md)
diff --git a/doc/administration/high_availability/img/fully-distributed.png b/doc/administration/high_availability/img/fully-distributed.png
new file mode 100644
index 00000000000..ad23207134e
--- /dev/null
+++ b/doc/administration/high_availability/img/fully-distributed.png
Binary files differ
diff --git a/doc/administration/high_availability/img/geo-ha-diagram.png b/doc/administration/high_availability/img/geo-ha-diagram.png
new file mode 100644
index 00000000000..da5d612827c
--- /dev/null
+++ b/doc/administration/high_availability/img/geo-ha-diagram.png
Binary files differ
diff --git a/doc/administration/high_availability/img/horizontal.png b/doc/administration/high_availability/img/horizontal.png
new file mode 100644
index 00000000000..c3bd489d96f
--- /dev/null
+++ b/doc/administration/high_availability/img/horizontal.png
Binary files differ
diff --git a/doc/administration/high_availability/img/hybrid.png b/doc/administration/high_availability/img/hybrid.png
new file mode 100644
index 00000000000..7d4a56bf0ea
--- /dev/null
+++ b/doc/administration/high_availability/img/hybrid.png
Binary files differ
diff --git a/doc/administration/high_availability/img/pg_ha_architecture.png b/doc/administration/high_availability/img/pg_ha_architecture.png
new file mode 100644
index 00000000000..ef870f652ae
--- /dev/null
+++ b/doc/administration/high_availability/img/pg_ha_architecture.png
Binary files differ
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 74b0e2c8184..490c2699458 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -37,6 +37,32 @@ options:
circumstances it could lead to data loss if a failure occurs before data has
synced.
+### Improving NFS performance with GitLab
+
+NOTE: **Note:** This is only available starting in certain versions of GitLab: 11.5.11,
+11.6.11, 11.7.12, 11.8.8, 11.9.0 and up (e.g. 11.10, 11.11, etc.)
+
+If you are using NFS to share Git data, we recommend that you enable a
+number of feature flags that will allow GitLab application processes to
+access Git data directly instead of going through the [Gitaly
+service](../gitaly/index.md). Depending on your workload and disk
+performance, these flags may help improve performance. See [the
+issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/57317) for more
+details.
+
+To do this, run the Rake task:
+
+```sh
+sudo gitlab-rake gitlab:features:enable_rugged
+```
+
+If you need to undo this setting for some reason such as switching to [Gitaly without NFS](gitaly.md)
+(recommended), run:
+
+```sh
+sudo gitlab-rake gitlab:features:disable_rugged
+```
+
### Known issues
On some customer systems, we have seen NFS clients slow precipitously due to
@@ -47,9 +73,8 @@ bug](https://bugzilla.redhat.com/show_bug.cgi?id=1552203) that may be fixed in
[more recent kernels with this
commit](https://github.com/torvalds/linux/commit/95da1b3a5aded124dd1bda1e3cdb876184813140).
-Users encountering a similar issue may be advised to disable the NFS server
-delegation feature, which is an optimization to reduce the number of network
-round-trips needed to read or write files. To disable NFS server delegations
+GitLab recommends all NFS users disable the NFS server
+delegation feature. To disable NFS server delegations
on an Linux NFS server, do the following:
1. On the NFS server, run:
@@ -78,6 +103,11 @@ stored on a local volume.
For more details on another person's experience with EFS, see
[Amazon's Elastic File System: Burst Credits](https://rawkode.com/2017/04/16/amazons-elastic-file-system-burst-credits/)
+## Avoid using CephFS and GlusterFS
+
+GitLab strongly recommends against using CephFS and GlusterFS.
+These distributed file systems are not well-suited for GitLab's input/output access patterns because git uses many small files and access times and file locking times to propagate will make git activity very slow.
+
## Avoid using PostgreSQL with NFS
GitLab strongly recommends against running your PostgreSQL database
@@ -93,7 +123,7 @@ Additionally, this configuration is specifically warned against in the
>to the NFS server can cause data corruption problems.
For supported database architecture, please see our documentation on
-[Configuring a Database for GitLab HA](https://docs.gitlab.com/ee/administration/high_availability/database.html).
+[Configuring a Database for GitLab HA](database.md).
## NFS Client mount options
@@ -122,7 +152,6 @@ mountpoint
└── gitlab-data
├── builds
├── git-data
- ├── home-git
├── shared
└── uploads
```
@@ -135,16 +164,11 @@ configuration to move each data location to a subdirectory:
```ruby
git_data_dirs({"default" => { "path" => "/gitlab-nfs/gitlab-data/git-data"} })
-user['home'] = '/gitlab-nfs/gitlab-data/home'
gitlab_rails['uploads_directory'] = '/gitlab-nfs/gitlab-data/uploads'
gitlab_rails['shared_path'] = '/gitlab-nfs/gitlab-data/shared'
gitlab_ci['builds_directory'] = '/gitlab-nfs/gitlab-data/builds'
```
-To move the `git` home directory, all GitLab services must be stopped. Run
-`gitlab-ctl stop && initctl stop gitlab-runsvdir`. Then continue with the
-reconfigure.
-
Run `sudo gitlab-ctl reconfigure` to start using the central location. Please
be aware that if you had existing data you will need to manually copy/rsync it
to these new locations and then restart GitLab.
@@ -174,14 +198,13 @@ are empty before attempting a restore. Read more about the
## Multiple NFS mounts
-When using default Omnibus configuration you will need to share 5 data locations
+When using default Omnibus configuration you will need to share 4 data locations
between all GitLab cluster nodes. No other locations should be shared. The
-following are the 5 locations need to be shared:
+following are the 4 locations need to be shared:
| Location | Description | Default configuration |
| -------- | ----------- | --------------------- |
| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data | `git_data_dirs({"default" => { "path" => "/var/opt/gitlab/git-data"} })`
-| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services | `user['home'] = '/var/opt/gitlab/'`
| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments | `gitlab_rails['uploads_directory'] = '/var/opt/gitlab/gitlab-rails/uploads'`
| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data | `gitlab_rails['shared_path'] = '/var/opt/gitlab/gitlab-rails/shared'`
| `/var/opt/gitlab/gitlab-ci/builds` | GitLab CI build traces | `gitlab_ci['builds_directory'] = '/var/opt/gitlab/gitlab-ci/builds'`
diff --git a/doc/administration/high_availability/nfs_host_client_setup.md b/doc/administration/high_availability/nfs_host_client_setup.md
new file mode 100644
index 00000000000..a8d69b9ab0a
--- /dev/null
+++ b/doc/administration/high_availability/nfs_host_client_setup.md
@@ -0,0 +1,135 @@
+# Configuring NFS for GitLab HA
+
+Setting up NFS for a GitLab HA setup allows all applications nodes in a cluster
+to share the same files and maintain data consistency. Application nodes in an HA
+setup act as clients while the NFS server plays host.
+
+> Note: The instructions provided in this documentation allow for setting a quick
+proof of concept but will leave NFS as potential single point of failure and
+therefore not recommended for use in production. Explore options such as [Pacemaker
+and Corosync](http://clusterlabs.org/) for highly available NFS in production.
+
+Below are instructions for setting up an application node(client) in an HA cluster
+to read from and write to a central NFS server(host).
+
+NOTE: **Note:**
+Using EFS may negatively impact performance. Please review the [relevant documentation](nfs.md#avoid-using-awss-elastic-file-system-efs) for additional details.
+
+## NFS Server Setup
+
+> Follow the instructions below to set up and configure your NFS server.
+
+### Step 1 - Install NFS Server on Host
+
+Installing the nfs-kernel-server package allows you to share directories with the clients running the GitLab application.
+
+```sh
+apt-get update
+apt-get install nfs-kernel-server
+```
+
+### Step 2 - Export Host's Home Directory to Client
+
+In this setup we will share the home directory on the host with the client. Edit the exports file as below to share the host's home directory with the client. If you have multiple clients running GitLab you must enter the client IP addresses in line in the `/etc/exports` file.
+
+```text
+#/etc/exports for one client
+/home <client-ip-address>(rw,sync,no_root_squash,no_subtree_check)
+
+#/etc/exports for three clients
+/home <client-ip-address>(rw,sync,no_root_squash,no_subtree_check) <client-2-ip-address>(rw,sync,no_root_squash,no_subtree_check) <client-3-ip-address>(rw,sync,no_root_squash,no_subtree_check)
+```
+
+Restart the NFS server after making changes to the `exports` file for the changes
+to take effect.
+
+```sh
+systemctl restart nfs-kernel-server
+```
+
+NOTE: **Note:**
+You may need to update your server's firewall. See the [firewall section](#nfs-in-a-firewalled-environment) at the end of this guide.
+
+## Client/ GitLab application node Setup
+
+> Follow the instructions below to connect any GitLab rails application node running
+inside your HA environment to the NFS server configured above.
+
+### Step 1 - Install NFS Common on Client
+
+The nfs-common provides NFS functionality without installing server components which
+we don't need running on the application nodes.
+
+```sh
+apt-get update
+apt-get install nfs-common
+```
+
+### Step 2 - Create Mount Points on Client
+
+Create a directory on the client that we can mount the shared directory from the host.
+Please note that if your mount point directory contains any files they will be hidden
+once the remote shares are mounted. An empty/new directory on the client is recommended
+for this purpose.
+
+```sh
+mkdir -p /nfs/home
+```
+
+Confirm that the mount point works by mounting it on the client and checking that
+it is mounted with the command below:
+
+```sh
+mount <host_ip_address>:/home
+df -h
+```
+
+### Step 3 - Set up Automatic Mounts on Boot
+
+Edit `/etc/fstab` on client as below to mount the remote shares automatically at boot.
+Note that GitLab requires advisory file locking, which is only supported natively in
+NFS version 4. NFSv3 also supports locking as long as Linux Kernel 2.6.5+ is used.
+We recommend using version 4 and do not specifically test NFSv3.
+
+```text
+#/etc/fstab
+165.227.159.85:/home /nfs/home nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+```
+
+Reboot the client and confirm that the mount point is mounted automatically.
+
+### Step 4 - Set up GitLab to Use NFS mounts
+
+When using the default Omnibus configuration you will need to share 5 data locations
+between all GitLab cluster nodes. No other locations should be shared. Changing the
+default file locations in `gitlab.rb` on the client allows you to have one main mount
+point and have all the required locations as subdirectories to use the NFS mount for
+git-data.
+
+```text
+git_data_dirs({"default" => {"path" => "/nfs/home/var/opt/gitlab-data/git-data"}})
+gitlab_rails['uploads_directory'] = '/nfs/home/var/opt/gitlab-data/uploads'
+gitlab_rails['shared_path'] = '/nfs/home/var/opt/gitlab-data/shared'
+gitlab_ci['builds_directory'] = '/nfs/home/var/opt/gitlab-data/builds'
+```
+
+Save the changes in `gitlab.rb` and run `gitlab-ctl reconfigure`.
+
+## NFS in a Firewalled Environment
+
+If the traffic between your NFS server and NFS client(s) is subject to port filtering
+by a firewall, then you will need to reconfigure that firewall to allow NFS communication.
+
+[This guide from TDLP](http://tldp.org/HOWTO/NFS-HOWTO/security.html#FIREWALLS)
+covers the basics of using NFS in a firewalled environment. Additionally, we encourage you to
+search for and review the specific documentation for your OS/distro and your firewall software.
+
+Example for Ubuntu:
+
+Check that NFS traffic from the client is allowed by the firewall on the host by running
+the command: `sudo ufw status`. If it's being blocked, then you can allow traffic from a specific
+client with the command below.
+
+```sh
+sudo ufw allow from <client-ip-address> to any port nfs
+```
diff --git a/doc/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md
new file mode 100644
index 00000000000..762179cf756
--- /dev/null
+++ b/doc/administration/high_availability/pgbouncer.md
@@ -0,0 +1,132 @@
+# Working with the bundle Pgbouncer service
+
+## Overview
+
+As part of its High Availability stack, GitLab Premium includes a bundled version of [Pgbouncer](https://pgbouncer.github.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
+
+In a High Availability setup, Pgbouncer is used to seamlessly migrate database connections between servers in a failover scenario.
+
+Additionally, it can be used in a non-HA setup to pool connections, speeding up response time while reducing resource usage.
+
+It is recommended to run pgbouncer alongside the `gitlab-rails` service, or on its own dedicated node in a cluster.
+
+## Operations
+
+### Running Pgbouncer as part of an HA GitLab installation
+
+See our [HA documentation for PostgreSQL](database.md) for information on running pgbouncer as part of a HA setup
+
+### Running Pgbouncer as part of a non-HA GitLab installation
+
+1. Generate PGBOUNCER_USER_PASSWORD_HASH with the command `gitlab-ctl pg-password-md5 pgbouncer`
+
+1. Generate SQL_USER_PASSWORD_HASH with the command `gitlab-ctl pg-password-md5 gitlab`. We'll also need to enter the plaintext SQL_USER_PASSWORD later
+
+1. On your database node, ensure the following is set in your `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ postgresql['pgbouncer_user_password'] = 'PGBOUNCER_USER_PASSWORD_HASH'
+ postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
+ postgresql['listen_address'] = 'XX.XX.XX.Y' # Where XX.XX.XX.Y is the ip address on the node postgresql should listen on
+ postgresql['md5_auth_cidr_addresses'] = %w(AA.AA.AA.B/32) # Where AA.AA.AA.B is the IP address of the pgbouncer node
+ ```
+
+1. Run `gitlab-ctl reconfigure`
+
+ **Note:** If the database was already running, it will need to be restarted after reconfigure by running `gitlab-ctl restart postgresql`.
+
+1. On the node you are running pgbouncer on, make sure the following is set in `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ pgbouncer['enable'] = true
+ pgbouncer['databases'] = {
+ gitlabhq_production: {
+ host: 'DATABASE_HOST',
+ user: 'pgbouncer',
+ password: 'PGBOUNCER_USER_PASSWORD_HASH'
+ }
+ }
+ ```
+
+1. Run `gitlab-ctl reconfigure`
+
+1. On the node running unicorn, make sure the following is set in `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ gitlab_rails['db_host'] = 'PGBOUNCER_HOST'
+ gitlab_rails['db_port'] = '6432'
+ gitlab_rails['db_password'] = 'SQL_USER_PASSWORD'
+ ```
+
+1. Run `gitlab-ctl reconfigure`
+
+1. At this point, your instance should connect to the database through pgbouncer. If you are having issues, see the [Troubleshooting](#troubleshooting) section
+
+### Interacting with pgbouncer
+
+#### Administrative console
+
+As part of omnibus-gitlab, we provide a command `gitlab-ctl pgb-console` to automatically connect to the pgbouncer administrative console. Please see the [pgbouncer documentation](https://pgbouncer.github.io/usage.html#admin-console) for detailed instructions on how to interact with the console.
+
+To start a session, run
+
+```shell
+# gitlab-ctl pgb-console
+Password for user pgbouncer:
+psql (9.6.8, server 1.7.2/bouncer)
+Type "help" for help.
+
+pgbouncer=#
+```
+
+The password you will be prompted for is the PGBOUNCER_USER_PASSWORD
+
+To get some basic information about the instance, run
+```shell
+pgbouncer=# show databases; show clients; show servers;
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+---------------------+-----------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | 127.0.0.1 | 5432 | gitlabhq_production | | 100 | 5 | | 0 | 1
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+(2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link
+| remote_pid | tls
+------+-----------+---------------------+--------+-----------+-------+------------+------------+---------------------+---------------------+-----------+------
++------------+-----
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44590 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x12444c0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44592 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x12447c0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44594 | 127.0.0.1 | 6432 | 2018-04-24 22:13:10 | 2018-04-24 22:17:10 | 0x1244940 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44706 | 127.0.0.1 | 6432 | 2018-04-24 22:14:22 | 2018-04-24 22:16:31 | 0x1244ac0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44708 | 127.0.0.1 | 6432 | 2018-04-24 22:14:22 | 2018-04-24 22:15:15 | 0x1244c40 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44794 | 127.0.0.1 | 6432 | 2018-04-24 22:15:15 | 2018-04-24 22:15:15 | 0x1244dc0 |
+| 0 |
+ C | gitlab | gitlabhq_production | active | 127.0.0.1 | 44798 | 127.0.0.1 | 6432 | 2018-04-24 22:15:15 | 2018-04-24 22:16:31 | 0x1244f40 |
+| 0 |
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 44660 | 127.0.0.1 | 6432 | 2018-04-24 22:13:51 | 2018-04-24 22:17:12 | 0x1244640 |
+| 0 |
+(8 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | rem
+ote_pid | tls
+------+--------+---------------------+-------+-----------+------+------------+------------+---------------------+---------------------+-----------+------+----
+--------+-----
+ S | gitlab | gitlabhq_production | idle | 127.0.0.1 | 5432 | 127.0.0.1 | 35646 | 2018-04-24 22:15:15 | 2018-04-24 22:17:10 | 0x124dca0 | |
+ 19980 |
+(1 row)
+```
+
+## Troubleshooting
+
+In case you are experiencing any issues connecting through pgbouncer, the first place to check is always the logs:
+
+```shell
+# gitlab-ctl tail pgbouncer
+```
+
+Additionally, you can check the output from `show databases` in the [Administrative console](#administrative-console). In the output, you would expect to see values in the `host` field for the `gitlabhq_production` database. Additionally, `current_connections` should be greater than 1.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 55d85ad86e6..1aaa709fc8f 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -1,6 +1,103 @@
-# Configuring Redis for GitLab HA
+# Configuring Redis for Scaling and High Availability
-> Experimental Redis Sentinel support was [Introduced][ce-1877] in GitLab 8.11.
+## Provide your own Redis instance **[CORE ONLY]**
+
+The following are the requirements for providing your own Redis instance:
+
+- Redis version 2.8 or higher. Version 3.2 or higher is recommend as this is
+ what ships with the GitLab Omnibus package.
+- Standalone Redis or Redis high availability with Sentinel are supported. Redis
+ Cluster is not supported.
+- Managed Redis from cloud providers such as AWS Elasticache will work. If these
+ services support high availability, be sure it is not the Redis Cluster type.
+
+Note the Redis node's IP address or hostname, port, and password (if required).
+These will be necessary when configuring the GitLab application servers later.
+
+## Redis in a Scaled Environment
+
+This section is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments including [Basic Scaling](README.md#basic-scaling) and
+[Full Scaling](README.md#full-scaling).
+
+### Provide your own Redis instance **[CORE ONLY]**
+
+If you want to use your own deployed Redis instance(s),
+see [Provide your own Redis instance](#provide-your-own-redis-instance-core-only)
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled Redis.
+
+### Standalone Redis using GitLab Omnibus **[CORE ONLY]**
+
+The GitLab Omnibus package can be used to configure a standalone Redis server.
+In this configuration Redis is not highly available, and represents a single
+point of failure. However, in a scaled environment the objective is to allow
+the environment to handle more users or to increase throughput. Redis itself
+is generally stable and can handle many requests so it is an acceptable
+trade off to have only a single instance. See [Scaling and High Availability](README.md)
+for an overview of GitLab scaling and high availability options.
+
+The steps below are the minimum necessary to configure a Redis server with
+Omnibus:
+
+1. SSH into the Redis server.
+1. [Download/install](https://about.gitlab.com/installation) the Omnibus GitLab
+ package you want using **steps 1 and 2** from the GitLab downloads page.
+ - Do not complete any other steps on the download page.
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
+
+ ```ruby
+ ## Enable Redis
+ redis['enable'] = true
+
+ ## Disable all other services
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ unicorn['enable'] = false
+ postgresql['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ alertmanager['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ gitlab_monitor['enable'] = false
+ gitaly['enable'] = false
+
+ redis['bind'] = '0.0.0.0'
+ redis['port'] = '6379'
+ redis['password'] = 'SECRET_PASSWORD_HERE'
+
+ gitlab_rails['auto_migrate'] = false
+ ```
+
+1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
+1. Note the Redis node's IP address or hostname, port, and
+ Redis password. These will be necessary when configuring the GitLab
+ application servers later.
+
+Advanced configuration options are supported and can be added if
+needed.
+
+Continue configuration of other components by going
+[back to Scaled Architectures](README.md#scalable-architecture-examples)
+
+## Redis with High Availability
+
+This section is relevant for [High Availability Architecture](README.md#high-availability-architecture-examples)
+environments including [Horizontal](README.md#horizontal),
+[Hybrid](README.md#hybrid), and
+[Fully Distributed](README.md#fully-distributed).
+
+### Provide your own Redis instance **[CORE ONLY]**
+
+If you want to use your own deployed Redis instance(s),
+see [Provide your own Redis instance](#provide-your-own-redis-instance-core-only)
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled Redis.
+
+### High Availability with GitLab Omnibus **[PREMIUM ONLY]**
+
+> Experimental Redis Sentinel support was [introduced in GitLab 8.11][ce-1877].
Starting with 8.14, Redis Sentinel is no longer experimental.
If you've used it with versions `< 8.14` before, please check the updated
documentation here.
@@ -14,6 +111,7 @@ a hosted cloud solution or you can use the one that comes bundled with
Omnibus GitLab packages.
> **Notes:**
+>
> - Redis requires authentication for High Availability. See
> [Redis Security](https://redis.io/topics/security) documentation for more
> information. We recommend using a combination of a Redis password and tight
@@ -52,9 +150,8 @@ failure.
Make sure that you read this document once as a whole before configuring the
components below.
-### High Availability with Sentinel
-
> **Notes:**
+>
> - Starting with GitLab `8.11`, you can configure a list of Redis Sentinel
> servers that will monitor a group of Redis servers to provide failover support.
> - Starting with GitLab `8.14`, the Omnibus GitLab Enterprise Edition package
@@ -231,6 +328,7 @@ Pick the one that suits your needs.
This is the section where we install and set up the new Redis instances.
> **Notes:**
+>
> - We assume that you have installed GitLab and all HA components from scratch. If you
> already have it installed and running, read how to
> [switch from a single-machine installation to Redis HA](#switching-from-an-existing-single-machine-installation-to-redis-ha).
@@ -267,10 +365,9 @@ The prerequisites for a HA Redis setup are the following:
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
```ruby
- # Enable the master role and disable all other services in the machine
- # (you can still enable Sentinel).
- redis_master_role['enable'] = true
-
+ # Specify server role as 'redis_master_role'
+ roles ['redis_master_role']
+
# IP address pointing to a local IP that the other machines can reach to.
# You can also set bind to '0.0.0.0' which listen in all interfaces.
# If you really need to bind to an external accessible IP, make
@@ -284,6 +381,7 @@ The prerequisites for a HA Redis setup are the following:
# Set up password authentication for Redis (use the same password in all nodes).
redis['password'] = 'redis-password-goes-here'
```
+
1. Only the primary GitLab application server should handle migrations. To
prevent database migrations from running on upgrade, add the following
@@ -295,6 +393,10 @@ The prerequisites for a HA Redis setup are the following:
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
+> Note: You can specify multiple roles like sentinel and redis as:
+> roles ['redis_sentinel_role', 'redis_master_role']. Read more about high
+> availability roles at https://docs.gitlab.com/omnibus/roles/
+
### Step 2. Configuring the slave Redis instances
1. SSH into the **slave** Redis server.
@@ -307,11 +409,9 @@ The prerequisites for a HA Redis setup are the following:
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
```ruby
- # Enable the slave role and disable all other services in the machine
- # (you can still enable Sentinel). This will also set automatically
- # `redis['master'] = false`.
- redis_slave_role['enable'] = true
-
+ # Specify server role as 'redis_slave_role'
+ roles ['redis_slave_role']
+
# IP address pointing to a local IP that the other machines can reach to.
# You can also set bind to '0.0.0.0' which listen in all interfaces.
# If you really need to bind to an external accessible IP, make
@@ -333,17 +433,19 @@ The prerequisites for a HA Redis setup are the following:
#redis['master_port'] = 6379
```
-1. To prevent database migrations from running on upgrade, run:
+1. To prevent reconfigure from running automatically on upgrade, run:
```
sudo touch /etc/gitlab/skip-auto-reconfigure
```
- Only the primary GitLab application server should handle migrations.
-
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
1. Go through the steps again for all the other slave nodes.
+> Note: You can specify multiple roles like sentinel and redis as:
+> roles ['redis_sentinel_role', 'redis_slave_role']. Read more about high
+> availability roles at https://docs.gitlab.com/omnibus/roles/
+
---
These values don't have to be changed again in `/etc/gitlab/gitlab.rb` after
@@ -369,7 +471,7 @@ Now that the Redis servers are all set up, let's configure the Sentinel
servers.
If you are not sure if your Redis servers are working and replicating
-correctly, please read the [Troubleshooting Replication](#troubleshooting-replication)
+correctly, please read the [Troubleshooting Replication](#troubleshooting-redis-replication)
and fix it before proceeding with Sentinel setup.
You must have at least `3` Redis Sentinel servers, and they need to
@@ -397,13 +499,13 @@ multiple machines with the Sentinel daemon.
be duplicate below):
```ruby
- redis_sentinel_role['enable'] = true
+ roles ['redis_sentinel_role']
# Must be the same in every sentinel node
redis['master_name'] = 'gitlab-redis'
# The same password for Redis authentication you set up for the master node.
- redis['password'] = 'redis-password-goes-here'
+ redis['master_password'] = 'redis-password-goes-here'
# The IP of the master Redis node.
redis['master_ip'] = '10.0.0.1'
@@ -570,8 +672,7 @@ or a failover promotes a different **Master** node.
In `/etc/gitlab/gitlab.rb`:
```ruby
-redis_master_role['enable'] = true
-redis_sentinel_role['enable'] = true
+roles ['redis_sentinel_role', 'redis_master_role']
redis['bind'] = '10.0.0.1'
redis['port'] = 6379
redis['password'] = 'redis-password-goes-here'
@@ -593,8 +694,7 @@ sentinel['quorum'] = 2
In `/etc/gitlab/gitlab.rb`:
```ruby
-redis_slave_role['enable'] = true
-redis_sentinel_role['enable'] = true
+roles ['redis_sentinel_role', 'redis_slave_role']
redis['bind'] = '10.0.0.2'
redis['port'] = 6379
redis['password'] = 'redis-password-goes-here'
@@ -616,8 +716,7 @@ sentinel['quorum'] = 2
In `/etc/gitlab/gitlab.rb`:
```ruby
-redis_slave_role['enable'] = true
-redis_sentinel_role['enable'] = true
+roles ['redis_sentinel_role', 'redis_slave_role']
redis['bind'] = '10.0.0.3'
redis['port'] = 6379
redis['password'] = 'redis-password-goes-here'
@@ -640,7 +739,7 @@ In `/etc/gitlab/gitlab.rb`:
```ruby
redis['master_name'] = 'gitlab-redis'
-redis['password'] = 'redis-password-goes-here'
+redis['master_password'] = 'redis-password-goes-here'
gitlab_rails['redis_sentinels'] = [
{'host' => '10.0.0.1', 'port' => 26379},
{'host' => '10.0.0.2', 'port' => 26379},
@@ -761,15 +860,11 @@ Before proceeding with the troubleshooting below, check your firewall rules:
### Troubleshooting Redis replication
You can check if everything is correct by connecting to each server using
-`redis-cli` application, and sending the `INFO` command.
+`redis-cli` application, and sending the `info replication` command as below.
-If authentication was correctly defined, it should fail with:
-`NOAUTH Authentication required` error. Try to authenticate with the
-previous defined password with `AUTH redis-password-goes-here` and
-try the `INFO` command again.
-
-Look for the `# Replication` section where you should see some important
-information like the `role` of the server.
+```
+/opt/gitlab/embedded/bin/redis-cli -h <redis-host-or-ip> -a '<redis-password>' info replication
+```
When connected to a `master` redis, you will see the number of connected
`slaves`, and a list of each with connection details:
@@ -839,7 +934,7 @@ To make sure your configuration is correct:
1. Run in the console:
```ruby
- redis = Redis.new(Gitlab::Redis.params)
+ redis = Redis.new(Gitlab::Redis::SharedState.params)
redis.info
```
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 14e2784c419..be6b547372a 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -46,7 +46,7 @@ valuable information for the general setup.
Assuming that the Redis master instance IP is `10.0.0.1`:
-1. [Install Redis](../../install/installation.md#6-redis)
+1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
```conf
@@ -72,7 +72,7 @@ Assuming that the Redis master instance IP is `10.0.0.1`:
Assuming that the Redis slave instance IP is `10.0.0.2`:
-1. [Install Redis](../../install/installation.md#6-redis)
+1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
```conf
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index 058346df56d..1b01419e062 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -30,7 +30,7 @@ the `pushes_since_gc` value is 200 a `git gc` will be run.
`git add`.
- `git repack` ([man page][man-repack]) re-organize existing packs into a single, more efficient pack.
-You can find this option under your **[Project] > Edit Project**.
+You can find this option under your project's **Settings > General > Advanced**.
---
diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png
index 845f0de19ce..4f25c471908 100644
--- a/doc/administration/img/custom_hooks_error_msg.png
+++ b/doc/administration/img/custom_hooks_error_msg.png
Binary files differ
diff --git a/doc/administration/img/db_load_balancing_postgres_stats.png b/doc/administration/img/db_load_balancing_postgres_stats.png
new file mode 100644
index 00000000000..8b311616e7b
--- /dev/null
+++ b/doc/administration/img/db_load_balancing_postgres_stats.png
Binary files differ
diff --git a/doc/administration/img/high_availability/active-active-diagram.png b/doc/administration/img/high_availability/active-active-diagram.png
deleted file mode 100644
index 4f5984b88fe..00000000000
--- a/doc/administration/img/high_availability/active-active-diagram.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/img/high_availability/active-passive-diagram.png b/doc/administration/img/high_availability/active-passive-diagram.png
deleted file mode 100644
index 3b42ce5911c..00000000000
--- a/doc/administration/img/high_availability/active-passive-diagram.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png
index acc4506993a..356de51f0cc 100644
--- a/doc/administration/img/housekeeping_settings.png
+++ b/doc/administration/img/housekeeping_settings.png
Binary files differ
diff --git a/doc/administration/img/instance_review_button.png b/doc/administration/img/instance_review_button.png
new file mode 100644
index 00000000000..b7604d7c7e5
--- /dev/null
+++ b/doc/administration/img/instance_review_button.png
Binary files differ
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 658b2f55d30..8271c579f5b 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -10,6 +10,8 @@ GitLab has several features based on receiving incoming emails:
- [New merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email):
allow GitLab users to create a new merge request by sending an email to a
user-specific email address.
+- [Service Desk](../user/project/service_desk.md): provide e-mail support to
+ your customers through GitLab. **[PREMIUM]**
## Requirements
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 6e08d4633cd..95a0e84deb6 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -20,7 +20,7 @@ GitLab Community Edition installations only have access to Core features.
GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have
access to its admin configurations. If you're a GitLab.com user, please check the
-[user documentation](../user/index.html).
+[user documentation](../user/index.md).
NOTE: **Note:**
Non-administrator users don’t have access to GitLab administration tools and settings.
@@ -32,8 +32,15 @@ Learn how to install, configure, update, and maintain your GitLab instance.
### Installing GitLab
- [Install](../install/README.md): Requirements, directory structures, and installation methods.
+ - [Database load balancing](database_load_balancing.md): Distribute database queries among multiple database servers. **[STARTER ONLY]**
+ - [Omnibus support for external MySQL DB](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only): Omnibus package supports configuring an external MySQL database. **[STARTER ONLY]**
+ - [Omnibus support for log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only) **[STARTER ONLY]**
- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
- [High Availability on AWS](../university/high-availability/aws/README.md): Set up GitLab HA on Amazon AWS.
+- [Geo](geo/replication/index.md): Replicate your GitLab instance to other geographic locations as a read-only fully operational version. **[PREMIUM ONLY]**
+- [Disaster Recovery](geo/disaster_recovery/index.md): Quickly fail-over to a different site with minimal effort in a disaster situation. **[PREMIUM ONLY]**
+- [Pivotal Tile](../install/pivotal/index.md): Deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for Pivotal Cloud Foundry. **[PREMIUM ONLY]**
+- [Add License](../user/admin_area/license.md): Upload a license at install time to unlock features that are in paid tiers of GitLab. **[STARTER ONLY]**
### Configuring GitLab
@@ -41,10 +48,11 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
- [Security](../security/README.md): Learn what you can do to further secure your GitLab instance.
- [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc.
+- [Global user settings](user_settings.md): Configure instance-wide user permissions.
- [Polling](polling.md): Configure how often the GitLab UI polls for updates.
- [GitLab Pages configuration](pages/index.md): Enable and configure GitLab Pages.
-- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
- [source installations](../install/installation.md#installation-from-source).
+- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on [source installations](../install/installation.md#installation-from-source).
+- [Uploads configuration](uploads.md): Configure GitLab uploads storage.
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
@@ -53,6 +61,9 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage.
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
+- [Elasticsearch](../integration/elasticsearch.md): Enable Elasticsearch to empower GitLab's Advanced Global Search. Useful when you deal with a huge amount of data. **[STARTER ONLY]**
+- [External Classification Policy Authorization](../user/admin_area/settings/external_authorization.md) **[PREMIUM ONLY]**
+- [Upload a license](../user/admin_area/license.md): Upload a license to unlock features that are in paid tiers of GitLab. **[STARTER ONLY]**
- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance.
#### Customizing GitLab's appearance
@@ -62,6 +73,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
+- [Additional custom email text](../user/admin_area/settings/email.md#custom-additional-text-premium-only): Add additional custom text to emails sent from GitLab. **[PREMIUM ONLY]**
### Maintaining GitLab
@@ -95,33 +107,40 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Libravatar](../customization/libravatar.md): Use Libravatar instead of Gravatar for user avatars.
- [Sign-up restrictions](../user/admin_area/settings/sign_up_restrictions.md): block email addresses of specific domains, or whitelist only specific domains.
- [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS).
-- [Authentication and Authorization](auth/README.md): Configure external authentication with LDAP, SAML, CAS and additional providers. See also other [authentication](../topics/authentication/index.md#gitlab-administrators) topics (for example, enforcing 2FA).
+- [Authentication and Authorization](auth/README.md): Configure external authentication with LDAP, SAML, CAS and additional providers.
+ - [Sync LDAP](auth/ldap-ee.md) **[STARTER ONLY]**
+ - [Kerberos authentication](../integration/kerberos.md) **[STARTER ONLY]**
+ - See also other [authentication](../topics/authentication/index.md#gitlab-administrators) topics (for example, enforcing 2FA).
+- [Email users](../tools/email.md): Email GitLab users from within GitLab. **[STARTER ONLY]**
+- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
+- [Audit logs and events](audit_events.md): View the changes made within the GitLab server for:
+ - Groups and projects. **[STARTER]**
+ - Instances. **[PREMIUM ONLY]**
+- [Auditor users](auditor_users.md): Users with read-only access to all projects, groups, and other resources on the GitLab instance. **[PREMIUM ONLY]**
- [Incoming email](incoming_email.md): Configure incoming emails to allow
- users to [reply by email], create [issues by email] and
- [merge requests by email], and to enable [Service Desk].
+ users to [reply by email](reply_by_email.md), create [issues by email](../user/project/issues/create_new_issue.md#new-issue-via-email) and
+ [merge requests by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email), and to enable [Service Desk](../user/project/service_desk.md).
- [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
basic Postfix mail server with IMAP authentication on Ubuntu for incoming
emails.
- [Abuse reports](../user/admin_area/abuse_reports.md): View and resolve abuse reports from your users.
-[reply by email]: reply_by_email.md
-[issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email
-[merge requests by email]: ../user/project/merge_requests/index.md#create-new-merge-requests-by-email
-
## Project settings
- [Container Registry](container_registry.md): Configure Container Registry with GitLab.
- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
-- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
+- [Default labels](../user/admin_area/labels.md): Create labels that will be automatically added to every new project.
- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
-- [Custom project templates](https://docs.gitlab.com/ee/user/admin_area/custom_project_templates.html): Configure a set of projects to be used as custom templates when creating a new project. **[PREMIUM ONLY]**
+- [Custom project templates](../user/admin_area/custom_project_templates.md): Configure a set of projects to be used as custom templates when creating a new project. **[PREMIUM ONLY]**
+- [Packages](packages.md): Enable GitLab to act as a Maven repository or NPM registry. **[PREMIUM ONLY]**
### Repository settings
- [Repository checks](repository_checks.md): Periodic Git repository checks.
- [Repository storage paths](repository_storage_paths.md): Manage the paths used to store repositories.
- [Repository storage rake tasks](raketasks/storage.md): A collection of rake tasks to list and migrate existing projects and attachments associated with it from Legacy storage to Hashed storage.
+- [Limit repository size](../user/admin_area/settings/account_and_limit_settings.md): Set a hard limit for your repositories' size. **[STARTER ONLY]**
## Continuous Integration settings
@@ -130,7 +149,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Job artifacts](job_artifacts.md): Enable, disable, and configure job artifacts (a set of files and directories which are outputted by a job when it completes successfully).
- [Job traces](job_traces.md): Information about the job traces (logs).
- [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
-- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners.
+- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only): Limit the usage of pipeline minutes for Shared Runners. **[STARTER ONLY]**
- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enablingdisabling-auto-devops): Enable or disable Auto DevOps for your instance.
## Git configuration options
@@ -158,6 +177,10 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Request Profiling](monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
- [Performance Bar](monitoring/performance/performance_bar.md): Get performance information for the current page.
+## Analytics
+
+- [Pseudonymizer](pseudonymizer.md): Export data from GitLab's database to CSV files in a secure way. **[ULTIMATE]**
+
## Troubleshooting
- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong
diff --git a/doc/administration/instance_review.md b/doc/administration/instance_review.md
new file mode 100644
index 00000000000..b1244f44e95
--- /dev/null
+++ b/doc/administration/instance_review.md
@@ -0,0 +1,17 @@
+# Instance Review **[CORE ONLY]**
+
+> [Introduced][6995] in [GitLab Core][ee] 11.3.
+
+If you are running a medium size instance of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu.
+
+![Instance Review button](img/instance_review_button.png)
+
+When you click the button you will be redirected to a form with prefilled data obtained from your instance.
+
+Once you submit the data to GitLab Inc. you can see the initial report.
+
+Additionally you will be contacted by our team for further review which should help you to improve your usage of GitLab.
+
+[6995]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6995
+[ee]: https://about.gitlab.com/pricing/
+
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index d383d1efe70..82e0c14ffc2 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -27,7 +27,7 @@ own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
```
-sudo apt-get install graphviz openjdk-7-jdk git-core maven
+sudo apt-get install graphviz openjdk-8-jdk git-core maven
git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
mvn package
@@ -57,8 +57,7 @@ you can change these defaults by editing the `/etc/tomcat7/server.xml` file.
You need to enable PlantUML integration from Settings under Admin Area. To do
that, login with an Admin account and do following:
- - in GitLab go to **Admin Area** and then **Settings**
- - scroll to bottom of the page until PlantUML section
+ - in GitLab go to **Admin Area**->**Settings**->**Integrations**->**PlantUML**
- check **Enable PlantUML** checkbox
- set the PlantUML instance as **PlantUML URL**
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index 25d85d1687b..c34858cd0db 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -11,7 +11,7 @@ One of the things it uses these credentials for is providing access to
## How it works
A detailed overview of the architecture of web terminals and how they work
-can be found in [this document](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/doc/terminal.md).
+can be found in [this document](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/doc/channel.md).
In brief:
- GitLab relies on the user to provide their own Kubernetes credentials, and to
@@ -43,6 +43,11 @@ detail below.
## Enabling and disabling terminal support
+NOTE: **Note:** AWS Elastic Load Balancers (ELBs) do not support web sockets.
+AWS Application Load Balancers (ALBs) must be used if you want web terminals
+to work. See [AWS Elastic Load Balancing Product Comparison](https://aws.amazon.com/elasticloadbalancing/features/#compare)
+for more information.
+
As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
Workhorse needs to be configured to pass the `Connection` and `Upgrade` headers
through to the next one in the chain. If you installed GitLab using Omnibus, or
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 25ae535d1ec..05e15fc303b 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -1,14 +1,15 @@
# Jobs artifacts administration
> **Notes:**
+>
> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`.
> - Starting with GitLab 8.17, builds are renamed to jobs.
> - This is the administration documentation. For the user guide see [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
-Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
-GitLab installations. Keep reading if you want to know how to disable it.
+Artifacts is a list of files and directories which are attached to a job after it
+finishes. This feature is enabled by default in all GitLab installations. Keep reading
+if you want to know how to disable it.
## Disabling job artifacts
@@ -41,8 +42,9 @@ To disable artifacts site-wide, follow the steps below.
## Storing job artifacts
-After a successful job, GitLab Runner uploads an archive containing the job
-artifacts to GitLab.
+GitLab Runner can upload an archive containing the job artifacts to GitLab. By default,
+this is done when the job succeeds, but can also be done on failure, or always, via the
+[`artifacts:when`](../ci/yaml/README.md#artifactswhen) parameter.
### Using local storage
@@ -86,6 +88,7 @@ _The artifacts are stored by default in
### Using object storage
> **Notes:**
+>
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1762) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
> - Since version 9.5, artifacts are [browsable](../user/project/pipelines/job_artifacts.md#browsing-artifacts),
@@ -98,6 +101,9 @@ artifacts, you can use an object storage like AWS S3 instead.
This configuration relies on valid AWS credentials to be configured already.
Use an object storage option like AWS S3 to store job artifacts.
+DANGER: **Danger:**
+If you're enabling S3 in [GitLab HA](high_availability/README.md), you will need to have an [NFS mount set up for CI traces and artifacts](high_availability/nfs.md#a-single-nfs-mount) or enable [live tracing](job_traces.md#new-live-trace-architecture). If these settings are not set, you will risk job traces disappearing or not being saved.
+
### Object Storage Settings
For source installations the following settings are nested under `artifacts:` and then `object_store:`. On omnibus installs they are prefixed by `artifacts_object_store_`.
@@ -274,7 +280,7 @@ you can flip the feature flag from a Rails console.
## Set the maximum file size of the artifacts
Provided the artifacts are enabled, you can change the maximum file size of the
-artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size).
+artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size-core-only).
## Storage statistics
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index 63945506f3c..aa9d87562a3 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -10,13 +10,13 @@ In the following table you can see the phases a trace goes through.
| Phase | State | Condition | Data flow | Stored path |
| ----- | ----- | --------- | --------- | ----------- |
-| 1: patching | Live trace | When a job is running | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
-| 2: overwriting | Live trace | When a job is finished | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
-| 3: archiving | Archived trace | After a job is finished | Sidekiq moves live trace to artifacts folder |`#{ROOT_PATH}/shared/artifacts/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/job.log`|
+| 1: patching | Live trace | When a job is running | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/gitlab-ci/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
+| 2: overwriting | Live trace | When a job is finished | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/gitlab-ci/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
+| 3: archiving | Archived trace | After a job is finished | Sidekiq moves live trace to artifacts folder |`#{ROOT_PATH}/gitlab-rails/shared/artifacts/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/job.log`|
| 4: uploading | Archived trace | After a trace is archived | Sidekiq moves archived trace to [object storage](#uploading-traces-to-object-storage) (if configured) |`#{bucket_name}/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/job.log`|
The `ROOT_PATH` varies per your environment. For Omnibus GitLab it
-would be `/var/opt/gitlab/gitlab-ci`, whereas for installations from source
+would be `/var/opt/gitlab`, whereas for installations from source
it would be `/home/git/gitlab`.
## Changing the job traces local location
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index a836bda7778..c5cfb8d5016 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -23,16 +23,19 @@ requests from the API are logged to a separate file in `api_json.log`.
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":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47}
+{"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":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"gitaly_duration":7.41,"queue_duration": 112.47}
```
-In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data:
+In this example, you can see this was a GET request for a specific
+issue. Notice each line also contains performance data. All times are in
+milliseconds:
-1. `duration`: total time in milliseconds taken to retrieve the request
-1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse
+1. `duration`: total time taken to retrieve the request
+1. `queue_duration`: total time that the request was queued inside GitLab Workhorse
1. `view`: total time taken inside the Rails views
1. `db`: total time to retrieve data from the database
1. `gitaly_calls`: total number of calls made to Gitaly
+1. `gitaly_duration`: total time taken by Gitaly calls
User clone/fetch activity using http transport appears in this log as `action: git_upload_pack`.
@@ -85,7 +88,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36}
```
This entry above shows an access to an internal endpoint to check whether an
@@ -277,6 +280,28 @@ installations from source.
Currently it logs the progress of project imports from the Bitbucket Server
importer. Future importers may use this file.
+## `auth.log`
+
+Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for
+installations from source.
+
+It logs information whenever [Rack Attack] registers an abusive request.
+
+## `graphql_json.log`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59587) in GitLab 12.0.
+
+This file lives in `/var/log/gitlab/gitlab-rails/graphql_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/graphql_json.log` for
+installations from source.
+
+GraphQL queries are recorded in that file. For example:
+
+```json
+{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7}
+```
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
@@ -295,3 +320,4 @@ Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_exporter.log` for
installations from source.
[repocheck]: repository_checks.md
+[Rack Attack]: ../security/rack_attack.md
diff --git a/doc/administration/maven_packages.md b/doc/administration/maven_packages.md
new file mode 100644
index 00000000000..d8551f64ece
--- /dev/null
+++ b/doc/administration/maven_packages.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'packages.md'
+---
+
+This document was moved to [another location](packages.md).
diff --git a/doc/administration/maven_repository.md b/doc/administration/maven_repository.md
new file mode 100644
index 00000000000..d8551f64ece
--- /dev/null
+++ b/doc/administration/maven_repository.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'packages.md'
+---
+
+This document was moved to [another location](packages.md).
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index c34a9519ace..5e9ba4f640f 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -145,3 +145,47 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+### Alternative in-database storage
+
+Enabling external diffs may reduce the performance of merge requests, as they
+must be retrieved in a separate operation to other data. A compromise may be
+reached by only storing outdated diffs externally, while keeping current diffs
+in the database.
+
+To enable this feature, perform the following steps:
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['external_diffs_when'] = 'outdated'
+ ```
+
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+ lines:
+
+ ```yaml
+ external_diffs:
+ enabled: true
+ when: outdated
+ ```
+
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+With this feature enabled, diffs will initially stored in the database, rather
+than externally. They will be moved to external storage once any of these
+conditions become true:
+
+- A newer version of the merge request diff exists
+- The merge request was merged more than seven days ago
+- The merge request was closed more than seven day ago
+
+These rules strike a balance between space and performance by only storing
+frequently-accessed diffs in the database. Diffs that are less likely to be
+accessed are moved to external storage instead.
diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md
index d18dddf09c0..fa0459b24ff 100644
--- a/doc/administration/monitoring/index.md
+++ b/doc/administration/monitoring/index.md
@@ -7,4 +7,4 @@ Explore our features to monitor your GitLab instance:
- [GitHub imports](github_imports.md): Monitor the health and progress of GitLab's GitHub importer with various Prometheus metrics.
- [Monitoring uptime](../../user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint.
- [IP whitelists](ip_whitelist.md): Configure GitLab for monitoring endpoints that provide health check information when probed.
-- [nginx_status](https://docs.gitlab.com/omnibus/settings/nginx.html#enabling-disabling-nginx_status): Monitor your Nginx server status
+- [nginx_status](https://docs.gitlab.com/omnibus/settings/nginx.html#enablingdisabling-nginx_status): Monitor your Nginx server status
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index ab43ec2cc4f..187fb2f73a1 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -3,7 +3,7 @@
[Grafana](http://grafana.org/) is a tool that allows you to visualize time
series metrics through graphs and dashboards. It supports several backend
data stores, including InfluxDB. GitLab writes performance data to InfluxDB
-and Grafana will allow you to query InfluxDB to display useful graphs.
+and Grafana will allow you to query to display useful graphs.
For the easiest installation and configuration, install Grafana on the same
server as InfluxDB. For larger installations, you may want to split out these
@@ -11,11 +11,13 @@ services.
## Installation
-Grafana supplies package repositories (Yum/Apt) for easy installation.
+[GitLab Omnibus can help you install Grafana (recommended)](https://docs.gitlab.com/omnibus/settings/grafana.html)
+or Grafana supplies package repositories (Yum/Apt) for easy installation.
See [Grafana installation documentation](http://docs.grafana.org/installation/)
for detailed steps.
-> **Note**: Before starting Grafana for the first time, set the admin user
+NOTE: **Note:**
+Before starting Grafana for the first time, set the admin user
and password in `/etc/grafana/grafana.ini`. Otherwise, the default password
will be `admin`.
diff --git a/doc/administration/monitoring/performance/img/request_profiling_token.png b/doc/administration/monitoring/performance/img/request_profiling_token.png
index 9f3dd7f08ca..ee819fcb437 100644
--- a/doc/administration/monitoring/performance/img/request_profiling_token.png
+++ b/doc/administration/monitoring/performance/img/request_profiling_token.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md
index 37a5388d2fc..7ace0ec5a93 100644
--- a/doc/administration/monitoring/performance/introduction.md
+++ b/doc/administration/monitoring/performance/introduction.md
@@ -1 +1,5 @@
+---
+redirect_to: 'index.md'
+---
+
This document was moved to [another location](index.md).
diff --git a/doc/administration/monitoring/performance/prometheus.md b/doc/administration/monitoring/performance/prometheus.md
index d73ef5d1789..2c5bab46dd9 100644
--- a/doc/administration/monitoring/performance/prometheus.md
+++ b/doc/administration/monitoring/performance/prometheus.md
@@ -1 +1,5 @@
+---
+redirect_to: '../prometheus/index.md'
+---
+
This document was moved to [monitoring/prometheus](../prometheus/index.md).
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 3bfcc9a289e..3dcd1593099 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -43,10 +43,52 @@ The following metrics are available:
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
-| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
-| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
-| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
-| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
+| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
+| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
+| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
+| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
+| unicorn_workers | Gauge | 12.0 | The number of Unicorn workers |
+
+## Sidekiq Metrics available for Geo **[PREMIUM]**
+
+Sidekiq jobs may also gather metrics, and these metrics can be accessed if the Sidekiq exporter is enabled (e.g. via
+the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
+
+| Metric | Type | Since | Description | Labels |
+|:-------------------------------------------- |:------- |:----- |:----------- |:------ |
+| geo_db_replication_lag_seconds | Gauge | 10.2 | Database replication lag (seconds) | url
+| geo_repositories | Gauge | 10.2 | Total number of repositories available on primary | url
+| geo_repositories_synced | Gauge | 10.2 | Number of repositories synced on secondary | url
+| geo_repositories_failed | Gauge | 10.2 | Number of repositories failed to sync on secondary | url
+| geo_lfs_objects | Gauge | 10.2 | Total number of LFS objects available on primary | url
+| geo_lfs_objects_synced | Gauge | 10.2 | Number of LFS objects synced on secondary | url
+| geo_lfs_objects_failed | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | url
+| geo_attachments | Gauge | 10.2 | Total number of file attachments available on primary | url
+| geo_attachments_synced | Gauge | 10.2 | Number of attachments synced on secondary | url
+| geo_attachments_failed | Gauge | 10.2 | Number of attachments failed to sync on secondary | url
+| geo_last_event_id | Gauge | 10.2 | Database ID of the latest event log entry on the primary | url
+| geo_last_event_timestamp | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | url
+| geo_cursor_last_event_id | Gauge | 10.2 | Last database ID of the event log processed by the secondary | url
+| geo_cursor_last_event_timestamp | Gauge | 10.2 | Last UNIX timestamp of the event log processed by the secondary | url
+| geo_status_failed_total | Counter | 10.2 | Number of times retrieving the status from the Geo Node failed | url
+| geo_last_successful_status_check_timestamp | Gauge | 10.2 | Last timestamp when the status was successfully updated | url
+| geo_lfs_objects_synced_missing_on_primary | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | url
+| geo_job_artifacts_synced_missing_on_primary | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | url
+| geo_attachments_synced_missing_on_primary | Gauge | 10.7 | Number of attachments marked as synced due to the file missing on the primary | url
+| geo_repositories_checksummed_count | Gauge | 10.7 | Number of repositories checksummed on primary | url
+| geo_repositories_checksum_failed_count | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | url
+| geo_wikis_checksummed_count | Gauge | 10.7 | Number of wikis checksummed on primary | url
+| geo_wikis_checksum_failed_count | Gauge | 10.7 | Number of wikis failed to calculate the checksum on primary | url
+| geo_repositories_verified_count | Gauge | 10.7 | Number of repositories verified on secondary | url
+| geo_repositories_verification_failed_count | Gauge | 10.7 | Number of repositories failed to verify on secondary | url
+| geo_repositories_checksum_mismatch_count | Gauge | 10.7 | Number of repositories that checksum mismatch on secondary | url
+| geo_wikis_verified_count | Gauge | 10.7 | Number of wikis verified on secondary | url
+| geo_wikis_verification_failed_count | Gauge | 10.7 | Number of wikis failed to verify on secondary | url
+| geo_wikis_checksum_mismatch_count | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | url
+| geo_repositories_checked_count | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | url
+| geo_repositories_checked_failed_count | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url
+| geo_repositories_retrying_verification_count | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url
+| geo_wikis_retrying_verification_count | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url
### Ruby metrics
@@ -59,9 +101,31 @@ Some basic Ruby runtime metrics are available:
| ruby_file_descriptors | Gauge | 11.1 | File descriptors per process |
| ruby_memory_bytes | Gauge | 11.1 | Memory usage by process |
| ruby_sampler_duration_seconds_total | Counter | 11.1 | Time spent collecting stats |
+| ruby_process_cpu_seconds_total | Gauge | 12.0 | Total amount of CPU time per process |
+| ruby_process_max_fds | Gauge | 12.0 | Maximum number of open file descriptors per process |
+| ruby_process_resident_memory_bytes | Gauge | 12.0 | Memory usage by process, measured in bytes |
+| ruby_process_start_time_seconds | Gauge | 12.0 | The elapsed time between system boot and the process started, measured in seconds |
[GC.stat]: https://ruby-doc.org/core-2.3.0/GC.html#method-c-stat
+## Puma Metrics **[EXPERIMENTAL]**
+
+When Puma is used instead of Unicorn, following metrics are available:
+
+| Metric | Type | Since | Description |
+|:-------------------------------------------- |:------- |:----- |:----------- |
+| puma_workers | Gauge | 12.0 | Total number of workers |
+| puma_running_workers | Gauge | 12.0 | Number of booted workers |
+| puma_stale_workers | Gauge | 12.0 | Number of old workers |
+| puma_phase | Gauge | 12.0 | Phase number (increased during phased restarts) |
+| puma_running | Gauge | 12.0 | Number of running threads |
+| puma_queued_connections | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
+| puma_active_connections | Gauge | 12.0 | Number of threads processing a request |
+| puma_pool_capacity | Gauge | 12.0 | Number of requests the worker is capable of taking right now |
+| puma_max_threads | Gauge | 12.0 | Maximum number of worker threads |
+| puma_idle_threads | Gauge | 12.0 | Number of spawned threads which are not processing a request |
+| rack_state_total | Gauge | 12.0 | Number of requests in a given rack state |
+
## Metrics shared directory
GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 8f65cd6418c..ce65d77274b 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -1,6 +1,7 @@
# Monitoring GitLab with Prometheus
> **Notes:**
+>
> - Prometheus and the various exporters listed in this page are bundled in the
> Omnibus GitLab package. Check each exporter's documentation for the timeline
> they got added. For installations from source you will have to install them
@@ -17,7 +18,7 @@ access to high quality time-series monitoring of GitLab services.
## Overview
Prometheus works by periodically connecting to data sources and collecting their
-performance metrics via the [various exporters](#prometheus-exporters). To view
+performance metrics via the [various exporters](#bundled-software-metrics). To view
and work with the monitoring data, you can either
[connect directly to Prometheus](#viewing-performance-metrics) or utilize a
dashboard tool like [Grafana].
@@ -95,13 +96,15 @@ To use an external Prometheus server:
1. Set each bundled service's [exporter](#bundled-software-metrics) to listen on a network address, for example:
- ```ruby
+ ```ruby
gitlab_monitor['listen_address'] = '0.0.0.0'
+ sidekiq['listen_address'] = '0.0.0.0'
gitlab_monitor['listen_port'] = '9168'
- gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
node_exporter['listen_address'] = '0.0.0.0:9100'
redis_exporter['listen_address'] = '0.0.0.0:9121'
postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
+ gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
```
1. Install and set up a dedicated Prometheus instance, if necessary, using the [official installation instructions](https://prometheus.io/docs/prometheus/latest/installation/).
@@ -111,6 +114,18 @@ To use an external Prometheus server:
gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
```
+1. To scrape nginx metrics, you'll also need to configure nginx to allow the Prometheus server
+ IP. For example:
+
+ ```ruby
+ nginx['status']['options'] = {
+ "server_tokens" => "off",
+ "access_log" => "off",
+ "allow" => "192.168.0.1",
+ "deny" => "all",
+ }
+ ```
+
1. [Reconfigure GitLab][reconfigure] to apply the changes
1. Edit the Prometheus server's configuration file.
1. Add each node's exporters to the Prometheus server's
@@ -202,6 +217,12 @@ The Postgres exporter allows you to measure various PostgreSQL metrics.
[âž” Read more about the Postgres exporter.](postgres_exporter.md)
+### PgBouncer exporter
+
+The PgBouncer exporter allows you to measure various PgBouncer metrics.
+
+[âž” Read more about the PgBouncer exporter.](pgbouncer_exporter.md)
+
### GitLab monitor exporter
The GitLab monitor exporter allows you to measure various GitLab metrics, pulled from Redis and the database.
diff --git a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
new file mode 100644
index 00000000000..d76834fdbea
--- /dev/null
+++ b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
@@ -0,0 +1,34 @@
+# PgBouncer exporter
+
+>**Note:**
+Available since [Omnibus GitLab 11.0][2493]. For installations from source
+you'll have to install and configure it yourself.
+
+The [PgBouncer exporter] allows you to measure various PgBouncer metrics.
+
+To enable the PgBouncer exporter:
+
+1. [Enable Prometheus](index.md#configuring-prometheus)
+1. Edit `/etc/gitlab/gitlab.rb`
+1. Add or find and uncomment the following line, making sure it's set to `true`:
+
+ ```ruby
+ pgbouncer_exporter['enable'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
+ take effect.
+
+Prometheus will now automatically begin collecting performance data from
+the PgBouncer exporter exposed under `localhost:9188`.
+
+The PgBouncer exporter will also be enabled by default if the [pgbouncer_role][postgres roles]
+is enabled.
+
+[↠Back to the main Prometheus page](index.md)
+
+[2493]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2493
+[PgBouncer exporter]: https://github.com/stanhu/pgbouncer_exporter
+[postgres roles]: https://docs.gitlab.com/omnibus/roles/#postgres-roles
+[prometheus]: https://prometheus.io
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/npm_registry.md b/doc/administration/npm_registry.md
new file mode 100644
index 00000000000..d8551f64ece
--- /dev/null
+++ b/doc/administration/npm_registry.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'packages.md'
+---
+
+This document was moved to [another location](packages.md).
diff --git a/doc/administration/operations.md b/doc/administration/operations.md
index 4797d2a3206..9cd78105bbb 100644
--- a/doc/administration/operations.md
+++ b/doc/administration/operations.md
@@ -1 +1,5 @@
+---
+redirect_to: 'operations/index.md'
+---
+
This document was moved to [another location](operations/index.md).
diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md
index b45ca99fd80..20c19445404 100644
--- a/doc/administration/operations/cleaning_up_redis_sessions.md
+++ b/doc/administration/operations/cleaning_up_redis_sessions.md
@@ -27,7 +27,7 @@ rcli() {
# This example works for Omnibus installations of GitLab 7.3 or newer. For an
# installation from source you will have to change the socket path and the
# path to redis-cli.
- sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
+ sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.shared_state.socket "$@"
}
# test the new shell function; the response should be PONG
diff --git a/doc/administration/operations/extra_sidekiq_processes.md b/doc/administration/operations/extra_sidekiq_processes.md
new file mode 100644
index 00000000000..286b99aceb5
--- /dev/null
+++ b/doc/administration/operations/extra_sidekiq_processes.md
@@ -0,0 +1,218 @@
+# Extra Sidekiq processes **[STARTER ONLY]**
+
+GitLab Enterprise Edition allows one to start an extra set of Sidekiq processes
+besides the default one. These processes can be used to consume a dedicated set
+of queues. This can be used to ensure certain queues always have dedicated
+workers, no matter the number of jobs that need to be processed.
+
+## Starting extra processes via Omnibus GitLab
+
+To enable `sidekiq-cluster`, you must apply the `sidekiq_cluster['enable'] = true`
+setting `/etc/gitlab/gitlab.rb`:
+
+```ruby
+sidekiq_cluster['enable'] = true
+```
+
+You will then specify how many additional processes to create via `sidekiq-cluster`
+as well as which queues for them to handle. This is done via the
+`sidekiq_cluster['queue_groups']` setting. This is an array whose items contain
+which queues to process. Each item in the array will equate to one additional
+sidekiq process.
+
+As an example, to make additional sidekiq processes that process the
+`elastic_indexer` and `mailers` queues, you would apply the following:
+
+```ruby
+sidekiq_cluster['queue_groups'] = [
+ "elastic_indexer",
+ "mailers"
+]
+```
+
+To have an additional sidekiq process handle multiple queues, you simply put a
+comma after the first queue name and then put the next queue name:
+
+```ruby
+sidekiq_cluster['queue_groups'] = [
+ "elastic_indexer,elastic_commit_indexer",
+ "mailers"
+]
+```
+
+Keep in mind, all changes must be followed by reconfiguring your GitLab
+application via `sudo gitlab-ctl reconfigure`.
+
+### Monitoring
+
+Once the Sidekiq processes are added, you can visit the "Background Jobs"
+section under the admin area in GitLab (`/admin/background_jobs`).
+
+![Extra sidekiq processes](img/sidekiq-cluster.png)
+
+### All queues with exceptions
+
+To have the additional sidekiq processes work on every queue EXCEPT the ones
+you list:
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ ```ruby
+ sidekiq_cluster['negate'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+
+### Limiting concurrency
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ ```ruby
+ sidekiq_cluster['concurrency'] = 25
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+Keep in mind, this normally would not exceed the number of CPU cores available.
+
+### Modifying the check interval
+
+To modify the check interval for the additional Sidekiq processes:
+
+1. Edit `/etc/gitlab/gitlab.rb` and add:
+
+ ```ruby
+ sidekiq_cluster['interval'] = 5
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+This tells the additional processes how often to check for enqueued jobs.
+
+## Starting extra processes via command line
+
+Starting extra Sidekiq processes can be done using the command
+`/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster`. This command
+takes arguments using the following syntax:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster [QUEUE,QUEUE,...] [QUEUE, ...]
+```
+
+Each separate argument denotes a group of queues that have to be processed by a
+Sidekiq process. Multiple queues can be processed by the same process by
+separating them with a comma instead of a space.
+
+Instead of a queue, a queue namespace can also be provided, to have the process
+automatically listen on all queues in that namespace without needing to
+explicitly list all the queue names. For more information about queue namespaces,
+see the relevant section in the
+[Sidekiq style guide](../../development/sidekiq_style_guide.md#queue-namespaces).
+
+For example, say you want to start 2 extra processes: one to process the
+"process_commit" queue, and one to process the "post_receive" queue. This can be
+done as follows:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit post_receive
+```
+
+If you instead want to start one process processing both queues you'd use the
+following syntax:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive
+```
+
+If you want to have one Sidekiq process process the "process_commit" and
+"post_receive" queues, and one process to process the "gitlab_shell" queue,
+you'd use the following:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive gitlab_shell
+```
+
+### Monitoring
+
+The `sidekiq-cluster` command will not terminate once it has started the desired
+amount of Sidekiq processes. Instead, the process will continue running and
+forward any signals to the child processes. This makes it easy to stop all
+Sidekiq processes as you simply send a signal to the `sidekiq-cluster` process,
+instead of having to send it to the individual processes.
+
+If the `sidekiq-cluster` process crashes or receives a `SIGKILL`, the child
+processes will terminate themselves after a few seconds. This ensures you don't
+end up with zombie Sidekiq processes.
+
+All of this makes monitoring the processes fairly easy. Simply hook up
+`sidekiq-cluster` to your supervisor of choice (e.g. runit) and you're good to
+go.
+
+If a child process died the `sidekiq-cluster` command will signal all remaining
+process to terminate, then terminate itself. This removes the need for
+`sidekiq-cluster` to re-implement complex process monitoring/restarting code.
+Instead you should make sure your supervisor restarts the `sidekiq-cluster`
+process whenever necessary.
+
+### PID files
+
+The `sidekiq-cluster` command can store its PID in a file. By default no PID
+file is written, but this can be changed by passing the `--pidfile` option to
+`sidekiq-cluster`. For example:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster --pidfile /var/run/gitlab/sidekiq_cluster.pid process_commit
+```
+
+Keep in mind that the PID file will contain the PID of the `sidekiq-cluster`
+command and not the PID(s) of the started Sidekiq processes.
+
+### Environment
+
+The Rails environment can be set by passing the `--environment` flag to the
+`sidekiq-cluster` command, or by setting `RAILS_ENV` to a non-empty value. The
+default value is "development".
+
+### All queues with exceptions
+
+You're able to run all queues in `sidekiq_queues.yml` file on a single or
+multiple processes with exceptions using the `--negate` flag.
+
+For example, say you want to run a single process for all queues,
+except "process_commit" and "post_receive". You can do so by executing:
+
+```bash
+sidekiq-cluster process_commit,post_receive --negate
+```
+
+For multiple processes of all queues (except "process_commit" and "post_receive"):
+
+```bash
+sidekiq-cluster process_commit,post_receive process_commit,post_receive --negate
+```
+
+### Limiting concurrency
+
+By default, `sidekiq-cluster` will spin up extra Sidekiq processes that use
+one thread per queue up to a maximum of 50. If you wish to change the cap, use
+the `-m N` option. For example, this would cap the maximum number of threads to 1:
+
+```bash
+/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive -m 1
+```
+
+For each queue group, the concurrency factor will be set to min(number of
+queues, N). Setting the value to 0 will disable the limit.
+
+Note that each thread requires a Redis connection, so adding threads may
+increase Redis latency and potentially cause client timeouts. See the [Sidekiq
+documentation about Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis)
+for more details.
+
+## Number of threads
+
+Each process started using `sidekiq-cluster` (whether it be via command line or
+via the gitlab.rb file) starts with a number of threads that equals the number
+of queues, plus one spare thread. For example, a process that handles the
+"process_commit" and "post_receive" queues will use 3 threads in total.
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index c293df3fc57..3631ea0822f 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -30,6 +30,19 @@ instructions will break installations using older versions of OpenSSH, such as
those included with CentOS 6 as of September 2017. If you want to use this
feature for CentOS 6, follow [the instructions on how to build and install a custom OpenSSH package](#compiling-a-custom-version-of-openssh-for-centos-6) before continuing.
+## Fast lookup is required for Geo **[PREMIUM]**
+
+By default, GitLab manages an `authorized_keys` file, which contains all the
+public SSH keys for users allowed to access GitLab. However, to maintain a
+single source of truth, [Geo](../geo/replication/index.md) needs to be configured to perform SSH fingerprint
+lookups via database lookup.
+
+As part of [setting up Geo](../geo/replication/index.md#setup-instructions),
+you will be required to follow the steps outlined below for both the primary and
+secondary nodes, but note that the `Write to "authorized keys" file` checkbox
+only needs to be unchecked on the primary node since it will be reflected
+automatically on the secondary if database replication is working.
+
## Setting up fast lookup via GitLab Shell
GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup
@@ -58,6 +71,9 @@ sudo service sshd reload
Confirm that SSH is working by removing your user's SSH key in the UI, adding a
new one, and attempting to pull a repo.
+> **Note:** For Omnibus Docker, `AuthorizedKeysCommand` is setup by default in
+GitLab 11.11 and later.
+
> **Warning:** Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date.
@@ -66,7 +82,7 @@ file will still be scanned. So git SSH performance will still be slow for many
users as long as a large file exists.
You can disable any more writes to the `authorized_keys` file by unchecking
-`Write to "authorized_keys" file` in the Application Settings of your GitLab
+`Write to "authorized_keys" file` in the **Admin Area > Settings > Network > Performance optimization** of your GitLab
installation.
![Write to authorized keys setting](img/write_to_authorized_keys_setting.png)
diff --git a/doc/administration/operations/img/sidekiq-cluster.png b/doc/administration/operations/img/sidekiq-cluster.png
new file mode 100644
index 00000000000..4eb1849010e
--- /dev/null
+++ b/doc/administration/operations/img/sidekiq-cluster.png
Binary files differ
diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md
index 32f36d68c50..df795a48169 100644
--- a/doc/administration/operations/index.md
+++ b/doc/administration/operations/index.md
@@ -11,6 +11,7 @@ Keep your GitLab instance up and running smoothly.
by GitLab to another file system or another server.
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller
to restart Sidekiq.
+- [Extra Sidekiq operations](extra_sidekiq_processes.md): Configure an extra set of Sidekiq processes to ensure certain queues always have dedicated workers, no matter the amount of jobs that need to be processed. **[STARTER ONLY]**
- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
- Speed up SSH operations by [Authorizing SSH users via a fast,
indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or
diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md
index cbffd883774..8eac42f2fe2 100644
--- a/doc/administration/operations/sidekiq_memory_killer.md
+++ b/doc/administration/operations/sidekiq_memory_killer.md
@@ -17,6 +17,11 @@ With the default settings, the MemoryKiller will cause a Sidekiq restart no
more often than once every 15 minutes, with the restart causing about one
minute of delay for incoming background jobs.
+Some background jobs rely on long-running external processes. To ensure these
+are cleanly terminated when Sidekiq is restarted, each Sidekiq process should be
+run as a process group leader (e.g., using `chpst -P`). If using Omnibus or the
+`bin/background_jobs` script with `runit` installed, this is handled for you.
+
## Configuring the MemoryKiller
The MemoryKiller is controlled using environment variables.
diff --git a/doc/administration/operations/speed_up_ssh.md b/doc/administration/operations/speed_up_ssh.md
index 89265b3018b..6dc83c42f53 100644
--- a/doc/administration/operations/speed_up_ssh.md
+++ b/doc/administration/operations/speed_up_ssh.md
@@ -1 +1,5 @@
+---
+redirect_to: 'fast_ssh_key_lookup.md'
+---
+
This document was moved to [another location](fast_ssh_key_lookup.md).
diff --git a/doc/administration/packages.md b/doc/administration/packages.md
new file mode 100644
index 00000000000..0d5f784b71e
--- /dev/null
+++ b/doc/administration/packages.md
@@ -0,0 +1,174 @@
+# GitLab Packages administration **[PREMIUM ONLY]**
+
+GitLab Packages allows organizations to utilize GitLab as a private repository
+for a variety of common package managers. Users are able to build and publish
+packages, which can be easily consumed as a dependency in downstream projects.
+
+The Packages feature allows GitLab to act as a repository for the following:
+
+| Software repository | Description | Available in GitLab version |
+| ------------------- | ----------- | --------------------------- |
+| [Maven Repository](../user/project/packages/maven_repository.md) | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
+| [NPM Registry](../user/project/packages/npm_registry.md) | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
+
+Don't you see your package management system supported yet?
+Please consider contributing
+to GitLab. This [development documentation](../development/packages.md) will guide you through the process.
+
+## Enabling the Packages feature
+
+NOTE: **Note:**
+After the Packages feature is enabled, the repositories are available
+for all new projects by default. To enable it for existing projects, users will
+have to explicitly do so in the project's settings.
+
+To enable the Packages feature:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['packages_enabled'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. After the installation is complete, you will have to configure the `packages`
+ section in `config/gitlab.yml`. Set to `true` to enable it:
+
+ ```yaml
+ packages:
+ enabled: true
+ ```
+1. [Restart GitLab] for the changes to take effect.
+
+## Changing the storage path
+
+By default, the packages are stored locally, but you can change the default
+local location or even use object storage.
+
+### Changing the local storage path
+
+The packages for Omnibus GitLab installations are stored under
+`/var/opt/gitlab/gitlab-rails/shared/packages/` and for source
+installations under `shared/packages/` (relative to the git homedir).
+To change the local storage path:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['packages_storage_path'] = "/mnt/packages"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. Edit the `packages` section in `config/gitlab.yml`:
+
+ ```yaml
+ packages:
+ enabled: true
+ storage_path: shared/packages
+ ```
+1. [Restart GitLab] for the changes to take effect.
+
+### Using object storage
+
+Instead of relying on the local storage, you can use an object storage to
+upload packages:
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
+ necessary):
+
+ ```ruby
+ gitlab_rails['packages_enabled'] = true
+ gitlab_rails['packages_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/packages"
+ gitlab_rails['packages_object_store_enabled'] = true
+ gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
+ gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
+ gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ gitlab_rails['packages_object_store_connection'] = {
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #'provider' => 'AWS',
+ #'region' => 'eu-west-1',
+ #'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ #'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #'host' => 's3.amazonaws.com',
+ #'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. Edit the `packages` section in `config/gitlab.yml` (uncomment where necessary):
+
+ ```yaml
+ packages:
+ enabled: true
+ ##
+ ## The location where build packages are stored (default: shared/packages).
+ ##
+ #storage_path: shared/packages
+ object_store:
+ enabled: false
+ remote_directory: packages # The bucket name.
+ #direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ #background_upload: true # Temporary option to limit automatic upload (Default: true).
+ #proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ connection:
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #provider: AWS
+ #region: us-east-1
+ #aws_access_key_id: AWS_ACCESS_KEY_ID
+ #aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #host: 's3.amazonaws.com' # default: s3.amazonaws.com.
+ #aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+### Migrating local packages to object storage
+
+After [configuring the object storage](#using-object-storage), you may use the
+following task to migrate existing packages from the local storage to the remote one.
+The processing will be done in a background worker and requires **no downtime**.
+
+For Omnibus GitLab:
+
+```sh
+sudo gitlab-rake "gitlab:packages:migrate"
+```
+
+For installations from source:
+
+```bash
+RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:packages:migrate
+```
+
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
+[restart gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 279ad018aed..3a7ca517d56 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -5,6 +5,7 @@ description: 'Learn how to administer GitLab Pages.'
# GitLab Pages administration
> **Notes:**
+>
> - [Introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
@@ -124,7 +125,7 @@ The Pages daemon doesn't listen to the outside world.
pages_external_url 'http://example.io'
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
Watch the [video tutorial][video-admin] for this configuration.
@@ -156,7 +157,26 @@ outside world.
where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
respectively.
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
+
+### Additional configuration for Docker container
+
+The GitLab Pages daemon will not have permissions to bind mounts when it runs
+in a Docker container. To overcome this issue you'll need to change the chroot
+behavior:
+
+1. Edit `/etc/gitlab/gitlab.rb`.
+1. Set the `inplace_chroot` to `true` for GitLab Pages:
+
+ ```shell
+ gitlab_pages['inplace_chroot'] = true
+ ```
+
+1. [Reconfigure GitLab][reconfigure].
+
+NOTE: **Note:**
+`inplace_chroot` option might not work with the other features, such as [Pages Access Control](#access-control).
+The [GitLab Pages README](https://gitlab.com/gitlab-org/gitlab-pages#caveats) has more information about caveats and workarounds.
## Advanced configuration
@@ -194,7 +214,7 @@ world. Custom domains are supported, but no TLS.
`192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address.
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
### Custom domains with TLS support
@@ -228,7 +248,7 @@ world. Custom domains and TLS are supported.
`192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address.
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
### Custom domain verification
@@ -271,6 +291,20 @@ Pages access control is disabled by default. To enable it:
1. [Reconfigure GitLab][reconfigure].
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
+### Running behind a proxy
+
+Like the rest of GitLab, Pages can be used in those environments where external
+internet connectivity is gated by a proxy. In order to use a proxy for GitLab
+pages:
+
+1. Configure in `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_pages['http_proxy'] = 'http://example:8080'
+ ```
+
+1. [Reconfigure Gitlab][reconfigure] for the changes to take effect.
+
## Activate verbose logging for daemon
Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in
@@ -286,7 +320,7 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon.
gitlab_pages['log_verbose'] = true
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Change storage path
@@ -301,7 +335,7 @@ are stored.
gitlab_rails['pages_path'] = "/mnt/storage/pages"
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Configure listener for reverse proxy requests
@@ -324,7 +358,7 @@ Omnibus GitLab 11.1.
gitlab_pages['listen_proxy'] = "localhost:10080"
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Set maximum pages size
@@ -356,6 +390,7 @@ Follow the steps below to configure GitLab Pages in a separate server.
gitaly['enable'] = false
alertmanager['enable'] = false
node_exporter['enable'] = false
+ gitlab_rails['auto_migrate'] = false
```
1. Run `sudo gitlab-ctl reconfigure`.
1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`:
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 60800d445b8..2100f7cd707 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -137,7 +137,7 @@ The Pages daemon doesn't listen to the outside world.
```
gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
```
1. Copy the `gitlab-pages` Nginx configuration file:
diff --git a/doc/administration/pseudonymizer.md b/doc/administration/pseudonymizer.md
new file mode 100644
index 00000000000..036e1d3fe61
--- /dev/null
+++ b/doc/administration/pseudonymizer.md
@@ -0,0 +1,103 @@
+# Pseudonymizer **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5532) in [GitLab Ultimate][ee] 11.1.
+
+As GitLab's database hosts sensitive information, using it unfiltered for analytics
+implies high security requirements. To help alleviate this constraint, the Pseudonymizer
+service is used to export GitLab's data in a pseudonymized way.
+
+CAUTION: **Warning:**
+This process is not impervious. If the source data is available, it's possible for
+a user to correlate data to the pseudonymized version.
+
+The Pseudonymizer currently uses `HMAC(SHA256)` to mutate fields that shouldn't
+be textually exported. This ensures that:
+
+- the end-user of the data source cannot infer/revert the pseudonymized fields
+- the referential integrity is maintained
+
+## Configuration
+
+To configure the pseudonymizer, you need to:
+
+- Provide a manifest file that describes which fields should be included or
+ pseudonymized ([example `manifest.yml` file](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/config/pseudonymizer.yml)).
+ A default manifest is provided with the GitLab installation. Using a relative file path will be resolved from the Rails root.
+ Alternatively, you can use an absolute file path.
+- Use an object storage and specify the connection parameters in the `pseudonymizer.upload.connection` configuration option.
+
+**For Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
+ the values you want:
+
+ ```ruby
+ gitlab_rails['pseudonymizer_manifest'] = 'config/pseudonymizer.yml'
+ gitlab_rails['pseudonymizer_upload_remote_directory'] = 'gitlab-elt' # bucket name
+ gitlab_rails['pseudonymizer_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+ }
+ ```
+
+ NOTE: **Note:**
+ If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
+
+ ```ruby
+ gitlab_rails['pseudonymizer_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'use_iam_profile' => true
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure)
+ for the changes to take effect.
+
+---
+
+**For installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+ lines:
+
+ ```yaml
+ pseudonymizer:
+ manifest: config/pseudonymizer.yml
+ upload:
+ remote_directory: 'gitlab-elt' # bucket name
+ connection:
+ provider: AWS
+ aws_access_key_id: AWS_ACCESS_KEY_ID
+ aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ region: eu-central-1
+ ```
+
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source)
+ for the changes to take effect.
+
+## Usage
+
+You can optionally run the pseudonymizer using the following environment variables:
+
+- `PSEUDONYMIZER_OUTPUT_DIR` - where to store the output CSV files (defaults to `/tmp`)
+- `PSEUDONYMIZER_BATCH` - the batch size when querying the DB (defaults to `100000`)
+
+```bash
+## Omnibus
+sudo gitlab-rake gitlab:db:pseudonymizer
+
+## Source
+sudo -u git -H bundle exec rake gitlab:db:pseudonymizer RAILS_ENV=production
+```
+
+This will produce some CSV files that might be very large, so make sure the
+`PSEUDONYMIZER_OUTPUT_DIR` has sufficient space. As a rule of thumb, at least
+10% of the database size is recommended.
+
+After the pseudonymizer has run, the output CSV files should be uploaded to the
+configured object storage and deleted from the local disk.
+
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/administration/raketasks/geo.md b/doc/administration/raketasks/geo.md
new file mode 100644
index 00000000000..9f3b31442f3
--- /dev/null
+++ b/doc/administration/raketasks/geo.md
@@ -0,0 +1,57 @@
+# Geo Rake Tasks **[PREMIUM ONLY]**
+
+## Git housekeeping
+
+There are few tasks you can run to schedule a git housekeeping to start at the
+next repository sync in a **Secondary node**:
+
+### Incremental Repack
+
+This is equivalent of running `git repack -d` on a _bare_ repository.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake geo:git:housekeeping:incremental_repack
+```
+
+**Source Installation**
+
+```bash
+sudo -u git -H bundle exec rake geo:git:housekeeping:incremental_repack RAILS_ENV=production
+```
+
+### Full Repack
+
+This is equivalent of running `git repack -d -A --pack-kept-objects` on a
+_bare_ repository which will optionally, write a reachability bitmap index
+when this is enabled in GitLab.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake geo:git:housekeeping:full_repack
+```
+
+**Source Installation**
+
+```bash
+sudo -u git -H bundle exec rake geo:git:housekeeping:full_repack RAILS_ENV=production
+```
+
+### GC
+
+This is equivalent of running `git gc` on a _bare_ repository, optionally writing
+a reachability bitmap index when this is enabled in GitLab.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake geo:git:housekeeping:gc
+```
+
+**Source Installation**
+
+```bash
+sudo -u git -H bundle exec rake geo:git:housekeeping:gc RAILS_ENV=production
+```
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index b295b7d5dc4..0b4c1ae15d6 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -205,25 +205,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
-## Create or repair repository hooks symlink
-
-If the GitLab shell hooks directory location changes or another circumstance
-leads to the hooks symlink becoming missing or invalid, run this Rake task
-to create or repair the symlinks.
-
-**Omnibus Installation**
-
-```
-sudo gitlab-rake gitlab:shell:create_hooks
-```
-
-**Source Installation**
-
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
-```
-
## Check TCP connectivity to a remote site
Sometimes you need to know if your GitLab installation can connect to a TCP
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index f43bba0a7a7..6ca23aabdec 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -32,5 +32,4 @@ bundle exec rake gitlab:import_export:data RAILS_ENV=production
```
[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
-[feature-flags]: https://docs.gitlab.com/ee/api/features.html
[tmp]: ../../development/shared_files.md
diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md
index 7ad38abe4f5..42a1a1c2e60 100644
--- a/doc/administration/raketasks/storage.md
+++ b/doc/administration/raketasks/storage.md
@@ -34,17 +34,62 @@ export ID_FROM=20
export ID_TO=50
```
-You can monitor the progress in the _Admin > Monitoring > Background jobs_ screen.
-There is a specific Queue you can watch to see how long it will take to finish: **project_migrate_hashed_storage**
+You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
+There is a specific Queue you can watch to see how long it will take to finish:
+`hashed_storage:hashed_storage_project_migrate`
After it reaches zero, you can confirm every project has been migrated by running the commands bellow.
If you find it necessary, you can run this migration script again to schedule missing projects.
-Any error or warning will be logged in the sidekiq's log file.
+Any error or warning will be logged in Sidekiq's log file.
+
+NOTE: **Note:**
+If Geo is enabled, each project that is successfully migrated generates an event to replicate the changes on any **secondary** nodes.
You only need the `gitlab:storage:migrate_to_hashed` rake task to migrate your repositories, but we have additional
commands below that helps you inspect projects and attachments in both legacy and hashed storage.
+## Rollback from Hashed storage to Legacy storage
+
+If you need to rollback the storage migration for any reason, you can follow the steps described here.
+
+NOTE: **Note:** Hashed Storage will be required in future version of GitLab.
+
+To prevent new projects from being created in the Hashed storage,
+you need to undo the [enable hashed storage][storage-migration] changes.
+
+This task will schedule all your existing projects and associated attachments to be rolled back to the
+Legacy storage type.
+
+For Omnibus installations, run the following:
+
+```bash
+sudo gitlab-rake gitlab:storage:rollback_to_legacy
+```
+
+For source installations, run the following:
+
+```bash
+sudo -u git -H bundle exec rake gitlab:storage:rollback_to_legacy RAILS_ENV=production
+```
+
+Both commands accept a range as environment variable:
+
+```bash
+# to rollback any migrated project from ID 20 to 50.
+export ID_FROM=20
+export ID_TO=50
+```
+
+You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
+On the **Queues** tab, you can watch the `hashed_storage:hashed_storage_project_rollback` queue to see how long the process will take to finish.
+
+
+After it reaches zero, you can confirm every project has been rolled back by running the commands bellow.
+If some projects weren't rolled back, you can run this rollback script again to schedule further rollbacks.
+
+Any error or warning will be logged in Sidekiq's log file.
+
## List projects on Legacy storage
To have a simple summary of projects using **Legacy** storage:
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index b5c40478ea5..fd8ea8d3162 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -2,7 +2,7 @@
## Migrate to Object Storage
-After [configuring the object storage](../../uploads.md#using-object-storage) for GitLab's uploads, you may use this task to migrate existing uploads from the local storage to the remote storage.
+After [configuring the object storage](../../uploads.md#using-object-storage-core-only) for GitLab's uploads, you may use this task to migrate existing uploads from the local storage to the remote storage.
>**Note:**
All of the processing will be done in a background worker and requires **no downtime**.
diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md
new file mode 100644
index 00000000000..54a423b9571
--- /dev/null
+++ b/doc/administration/raketasks/uploads/sanitize.md
@@ -0,0 +1,62 @@
+# Uploads Sanitize tasks
+
+## Requirements
+
+You need `exiftool` installed on your system. If you installed GitLab:
+
+- Using the Omnibus package, you're all set.
+- From source, make sure `exiftool` is installed:
+
+ ```sh
+ # Debian/Ubuntu
+ sudo apt-get install libimage-exiftool-perl
+
+ # RHEL/CentOS
+ sudo yum install perl-Image-ExifTool
+ ```
+
+## Remove EXIF data from existing uploads
+
+Since 11.9 EXIF data are automatically stripped from JPG or TIFF image uploads.
+Because EXIF data may contain sensitive information (e.g. GPS location), you
+can remove EXIF data also from existing images which were uploaded before
+with the following command:
+
+```bash
+sudo RAILS_ENV=production -u git -H bundle exec rake gitlab:uploads:sanitize:remove_exif
+```
+
+This command by default runs in dry mode and it doesn't remove EXIF data. It can be used for
+checking if (and how many) images should be sanitized.
+
+The rake task accepts following parameters.
+
+Parameter | Type | Description
+--------- | ---- | -----------
+`start_id` | integer | Only uploads with equal or greater ID will be processed
+`stop_id` | integer | Only uploads with equal or smaller ID will be processed
+`dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true
+`sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds
+
+If you have too many uploads, you can speed up sanitization by setting
+`sleep_time` to a lower value or by running multiple rake tasks in parallel,
+each with a separate range of upload IDs (by setting `start_id` and `stop_id`).
+
+To run the command without dry mode and remove EXIF data from all uploads, you can use:
+
+```bash
+sudo RAILS_ENV=production -u git -H bundle exec rake gitlab:uploads:sanitize:remove_exif[,,false,] 2>&1 | tee exif.log
+```
+
+To run the command without dry mode on uploads with ID between 100 and 5000 and pause for 0.1 second, you can use:
+
+```bash
+sudo RAILS_ENV=production -u git -H bundle exec rake gitlab:uploads:sanitize:remove_exif[100,5000,false,0.1] 2>&1 | tee exif.log
+```
+
+Because the output of commands will be probably long, the output is written also into exif.log file.
+
+If sanitization fails for an upload, an error message should be in the output of the rake task (typical reasons may
+be that the file is missing in the storage or it's not a valid image). Please
+[report](https://gitlab.com/gitlab-org/gitlab-ce/issues/new) any issues at `gitlab.com` and use
+prefix 'EXIF' in issue title with the error output and (if possible) the image.
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 8b725e50f58..7cf8f20a9dc 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -31,7 +31,7 @@ panel.
If the repository check fails for some repository you should look up the error
in `repocheck.log`:
-- in the [admin panel](logs.md#repocheck-log)
+- in the [admin panel](logs.md#repochecklog)
- or on disk, see:
- `/var/log/gitlab/gitlab-rails` for Omnibus installations
- `/home/git/gitlab/log` for installations from source
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 7f25423171f..4aafc06cfdc 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -62,6 +62,8 @@ files and add the full paths of the alternative repository storage paths. In
the example below, we add two more mountpoints that are named `nfs` and `cephfs`
respectively.
+NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+
**For installations from source**
1. Edit `gitlab.yml` and add the storage paths:
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 4934aaf39f7..4db3cbb9958 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -2,6 +2,24 @@
> [Introduced][ce-28283] in GitLab 10.0.
+Two different storage layouts can be used
+to store the repositories on disk and their characteristics.
+
+GitLab can be configured to use one or multiple repository shard locations
+that can be:
+
+- Mounted to the local disk
+- Exposed as an NFS shared volume
+- Acessed via [gitaly] on its own machine.
+
+In GitLab, this is configured in `/etc/gitlab/gitlab.rb` by the `git_data_dirs({})`
+configuration hash. The storage layouts discussed here will apply to any shard
+defined in it.
+
+The `default` repository shard that is available in any installations
+that haven't customized it, points to the local folder: `/var/opt/gitlab/git-data`.
+Anything discussed below is expected to be part of that folder.
+
## Legacy Storage
Legacy Storage is the storage behavior prior to version 10.0. For historical
@@ -29,15 +47,15 @@ Any change in the URL will need to be reflected on disk (when groups / users or
projects are renamed). This can add a lot of load in big installations,
especially if using any type of network based filesystem.
-For GitLab Geo in particular: Geo does work with legacy storage, but in some
+CAUTION: **Caution:**
+For Geo in particular: Geo does work with legacy storage, but in some
edge cases due to race conditions it can lead to errors when a project is
renamed multiple times in short succession, or a project is deleted and
recreated under the same name very quickly. We expect these race events to be
rare, and we have not observed a race condition side-effect happening yet.
-
This pattern also exists in other objects stored in GitLab, like issue
Attachments, GitLab Pages artifacts, Docker Containers for the integrated
-Registry, etc.
+Registry, etc. Hashed storage is a requirement for Geo.
## Hashed Storage
@@ -66,34 +84,12 @@ by another folder with the next 2 characters. They are both stored in a special
"@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}.wiki.git"
```
-### How to migrate to Hashed Storage
-
-In GitLab, go to **Admin > Settings**, find the **Repository Storage** section
-and select "_Use hashed storage paths for newly created and renamed projects_".
-
-To migrate your existing projects to the new storage type, check the specific
-[rake tasks].
-
-[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
-[rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
-[storage-paths]: repository_storage_types.md
-
-#### Rollback
-
-There is no automated rollback implemented. Below are the steps required to rollback
-from each storage migration.
+### Hashed object pools
-The rollback has to be performed in the reverse order. To get into "Legacy" state,
-you need to rollback Attachments first, then Project.
-
-Also note that if Geo is enabled, after the migration was triggered, an event is generated
-to replicate the operation on any Secondary node. That means the on disk changes will also
-need to be performed on these nodes as well. Database changes will propagate without issues.
-
-You must make sure the migration event was already processed or otherwise it may migrate
-the files back to Hashed state again.
-
-#### Hashed object pools
+CAUTION: **Beta:**
+Hashed objects pools are considered beta, and are not ready for production use.
+Follow [gitaly#1548](https://gitlab.com/gitlab-org/gitaly/issues/1548) for
+updates.
For deduplication of public forks and their parent repository, objects are pooled
in an object pool. These object pools are a third repository where shared objects
@@ -110,36 +106,60 @@ enabled for individual projects by executing
be on hashed storage, should not be a fork itself, and hashed storage should be
enabled for all new projects.
-##### Attachments
+### How to migrate to Hashed Storage
-To rollback single Attachment migration, rename `aa/bb/abcdef1234567890...` folder back to `namespace/project`.
+To start a migration, enable Hashed Storage for new projects:
+
+1. Go to **Admin > Settings > Repository** and expand the **Repository Storage** section.
+2. Select the **Use hashed storage paths for newly created and renamed projects** checkbox.
-Both folder names can be generated by the `FileUploader.absolute_base_dir(project)`, you
-just need to switch the version from the `project` back to the previous one.
+Check if the change breaks any existing integration you may have that
+either runs on the same machine as your repositories are located, or may login to that machine
+to access data (for example, a remote backup solution).
-```ruby
-project.storage_version
-# => 2
+To schedule a complete rollout, see the
+[rake task documentation for storage migration][rake/migrate-to-hashed] for instructions.
-FileUploader.absolute_base_dir(project)
-# => "/opt/gitlab/embedded/service/gitlab-rails/public/uploads/@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"
+If you do have any existing integration, you may want to do a small rollout first,
+to validate. You can do so by specifying a range with the operation.
-project.storage_version = 1
+This is an example of how to limit the rollout to Project IDs 50 to 100, running in
+an Omnibus Gitlab installation:
-FileUploader.absolute_base_dir(project)
-# => "/opt/gitlab/embedded/service/gitlab-rails/public/uploads/gitlab/gitlab-shell-renamed"
+```bash
+sudo gitlab-rake gitlab:storage:migrate_to_hashed ID_FROM=50 ID_TO=100
```
-##### Project
+Check the [documentation][rake/migrate-to-hashed] for additional information and instructions for
+source-based installation.
+
+#### Rollback
+
+Similar to the migration, to disable Hashed Storage for new
+projects:
-To rollback single Project migration, move `@hashed/aa/bb/aabbcdef1234567890abcdef.git` and `@hashed/aa/bb/aabbcdef1234567890abcdef.wiki.git`
-back to `namespace/project.git` and `namespace/project.wiki.git` respectively and switch the version from the `project` back to `null`.
+1. Go to **Admin > Settings > Repository** and expand the **Repository Storage** section.
+2. Uncheck the **Use hashed storage paths for newly created and renamed projects** checkbox.
+
+To schedule a complete rollback, see the
+[rake task documentation for storage rollback](raketasks/storage.md#rollback-from-hashed-storage-to-legacy-storage) for instructions.
+
+The rollback task also supports specifying a range of Project IDs. Here is an example
+of limiting the rollout to Project IDs 50 to 100, in an Omnibus Gitlab installation:
+
+```bash
+sudo gitlab-rake gitlab:storage:rollback_to_legacy ID_FROM=50 ID_TO=100
+```
+
+If you have a Geo setup, please note that the rollback will not be reflected automatically
+on the **secondary** node. You may need to wait for a backfill operation to kick-in and remove
+the remaining repositories from the special `@hashed/` folder manually.
### Hashed Storage coverage
We are incrementally moving every storable object in GitLab to the Hashed
Storage pattern. You can check the current coverage status below (and also see
-the [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821)).
+the [issue][ce-2821]).
Note that things stored in an S3 compatible endpoint will not have the downsides
mentioned earlier, if they are not prefixed with `#{namespace}/#{project_name}`,
@@ -156,6 +176,7 @@ which is true for CI Cache and LFS Objects.
| CI Artifacts | No | No | Yes | 9.4 / 10.6 |
| CI Cache | No | No | Yes | - |
| LFS Objects | Yes | Similar | Yes | 10.0 / 10.7 |
+| Repository pools| No | Yes | - | 11.6 |
#### Implementation Details
@@ -180,3 +201,9 @@ LFS Objects implements a similar storage pattern using 2 chars, 2 level folders,
```
They are also S3 compatible since **10.0** (GitLab Premium), and available in GitLab Core since **10.7**.
+
+[ce-2821]: https://gitlab.com/gitlab-com/infrastructure/issues/2821
+[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
+[rake/migrate-to-hashed]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
+[storage-paths]: repository_storage_types.md
+[gitaly]: gitaly/index.md
diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md
index cf6de15743f..af7a385e5a0 100644
--- a/doc/administration/repository_storages.md
+++ b/doc/administration/repository_storages.md
@@ -1 +1,5 @@
+---
+redirect_to: 'repository_storage_paths.md'
+---
+
This document was moved to [another location](repository_storage_paths.md).
diff --git a/doc/administration/troubleshooting/migration.md b/doc/administration/troubleshooting/migration.md
new file mode 100644
index 00000000000..4d2d268b9df
--- /dev/null
+++ b/doc/administration/troubleshooting/migration.md
@@ -0,0 +1,82 @@
+# Migrations problems
+
+## Legacy upload migration
+
+> Introduced in GitLab 12.0.
+
+ The migration takes all attachments uploaded by legacy `AttachmentUploader` and
+ migrate them to the path that current uploaders expect.
+
+Although it should not usually happen there could possibly be some attachments belonging to
+LegacyDiffNotes. These attachments can't be seen before running the migration by users and
+they should not be present in your instance.
+
+However, if you have some of them, you will need to handle them manually.
+You can find the ids of failed notes in logs as "MigrateLegacyUploads: LegacyDiffNote"
+
+1. Run a Rails console:
+
+ ```sh
+ sudo gitlab-rails console production
+ ```
+
+ or for source installs:
+
+ ```sh
+ bundle exec rails console production
+ ```
+
+ 1. Check the failed upload and find the note (you can see their ids in the logs)
+
+ ```ruby
+ upload = Upload.find(upload_id)
+ note = Note.find(note_id)
+ ```
+
+
+ 1. Check the path - it should contain `system/note/attachment`
+
+ ```ruby
+ upload.absolut_path
+ ```
+
+ 1. Check the path in the uploader - it should differ from the upload path and should contain `system/legacy_diff_note`
+
+ ```ruby
+ uploader = upload.build_uploader
+ uploader.file
+ ```
+
+ 1. First, you need to move the file to the path that is expected from the uploader
+
+ ```ruby
+ old_path = upload.absolute_path
+ new_path = upload.absolute_path.sub('-/system/note/attachment', '-/system/legacy_diff_note')
+ new_dir = File.dirname(new_path)
+ FileUtils.mkdir_p(new_dir)
+
+ FileUtils.mv(old_path, new_path)
+ ```
+
+ 1. You then need to move the file to the `FileUploader` and create a new `Upload` object
+
+ ```ruby
+ file_uploader = UploadService.new(note.project, File.read(new_path)).execute
+ ```
+
+ 1. And update the legacy note to contain the file.
+
+ ```ruby
+ new_text = "#{note.note} \n #{file_uploader.markdown_link}"
+ note.update!(
+ note: new_text
+ )
+ ```
+
+ 1. And finally, you can remove the old upload
+
+ ```ruby
+ upload.destroy
+ ```
+
+If you have any problems feel free to contact [GitLab Support](https://about.gitlab.com/support/).
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 9dfe085425f..708b59a273b 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -18,7 +18,7 @@ below.
>**Notes:**
For historical reasons, uploads are stored into a base directory, which by default is `uploads/-/system`. It is strongly discouraged to change this configuration option on an existing GitLab installation.
-_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads/-/system`._
+_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
1. To change the storage path for example to `/mnt/storage/uploads`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
@@ -53,7 +53,7 @@ _The uploads are stored by default in
> **Notes:**
>
> - [Introduced][ee-3867] in [GitLab Premium][eep] 10.5.
-> - [Introduced][ce17358] in [GitLab Core][ce] 10.7.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17358) in [GitLab Core][ce] 10.7.
> - Since version 11.1, we support direct_upload to S3.
If you don't want to use the local disk where GitLab is installed to store the
@@ -152,4 +152,3 @@ _The uploads are stored by default in
[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Premium"
[ce]: https://about.gitlab.com/gitlab-ce/ "GitLab Community Edition"
[ee-3867]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3867
-[ce-17358]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17358
diff --git a/doc/administration/user_settings.md b/doc/administration/user_settings.md
new file mode 100644
index 00000000000..f9654655949
--- /dev/null
+++ b/doc/administration/user_settings.md
@@ -0,0 +1,35 @@
+# Modifying global user settings
+
+GitLab administrators can modify user settings for the entire GitLab instance.
+
+## Disallow users creating top-level groups
+
+By default, new users can create top-level groups. To disable this, modify the appropriate configuration file.
+
+For Omnibus installations, add the following to `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['gitlab_default_can_create_group'] = false
+```
+
+For source installations, uncomment the following line in `config/gitlab.yml`:
+
+```yaml
+# default_can_create_group: false # default: true
+```
+
+## Disallow users changing usernames
+
+By default, new users can change their usernames. To disable this, modify the appropriate configuration file.
+
+For Omnibus installations, add the following to `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['gitlab_username_changing_enabled'] = false
+```
+
+For source installations, uncomment the following line in `config/gitlab.yml`:
+
+```yaml
+# username_changing_enabled: false # default: true - User can change her username/namespace
+```
diff --git a/doc/analytics/README.md b/doc/analytics/README.md
new file mode 100644
index 00000000000..bfb15f6c4f3
--- /dev/null
+++ b/doc/analytics/README.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/group/index.md#user-contribution-analysis-starter'
+---
+
+This document was moved to [another location](../user/group/index.md#user-contribution-analysis-starter)
diff --git a/doc/analytics/contribution_analytics.md b/doc/analytics/contribution_analytics.md
new file mode 100644
index 00000000000..e36f55071a4
--- /dev/null
+++ b/doc/analytics/contribution_analytics.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/group/contribution_analytics/index.md'
+---
+
+This document was moved to [another location](../user/group/contribution_analytics/index.md).
diff --git a/doc/api/README.md b/doc/api/README.md
index 48d9ad8f30d..3a1064b787e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,72 +21,83 @@ See also:
The following API resources are available in the project context:
-| Resource | Available endpoints |
-|:------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
-| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
-| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
-| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
-| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
-| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
-| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
-| [Deployments](deployments.md) | `/projects/:id/deployments` |
-| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` |
-| [Environments](environments.md) | `/projects/:id/environments` |
-| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
-| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
-| [Issue boards](boards.md) | `/projects/:id/boards` |
-| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
-| [Labels](labels.md) | `/projects/:id/labels` |
-| [Members](members.md) | `/projects/:id/members` (also available for groups) |
-| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
-| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` |
-| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
-| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
-| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
-| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
-| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
-| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
-| [Project badges](project_badges.md) | `/projects/:id/badges` |
-| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
-| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
-| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
-| [Project milestones](milestones.md) | `/projects/:id/milestones` |
-| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
-| [Project templates](project_templates.md) | `/projects/:id/templates` |
-| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
-| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
-| [Releases](releases/index.md) | `/projects/:id/releases` |
-| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
-| [Repositories](repositories.md) | `/projects/:id/repository` |
-| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
-| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
-| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` |
-| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
-| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
-| [Services](services.md) | `/projects/:id/services` |
-| [Tags](tags.md) | `/projects/:id/repository/tags` |
-| [Wikis](wikis.md) | `/projects/:id/wikis` |
+| Resource | Available endpoints |
+|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
+| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
+| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
+| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
+| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
+| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
+| [Deployments](deployments.md) | `/projects/:id/deployments` |
+| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
+| [Environments](environments.md) | `/projects/:id/environments` |
+| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
+| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
+| [Issue boards](boards.md) | `/projects/:id/boards` |
+| [Issue links](issue_links.md) **[STARTER]** | `/projects/:id/issues/.../links` |
+| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
+| [Labels](labels.md) | `/projects/:id/labels` |
+| [Managed licenses](managed_licenses.md) **[ULTIMATE]** | `/projects/:id/managed_licenses` |
+| [Members](members.md) | `/projects/:id/members` (also available for groups) |
+| [Merge request approvals](merge_request_approvals.md) **[STARTER]** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` |
+| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
+| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) |
+| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
+| [Packages](packages.md) **[PREMIUM]** | `/projects/:id/packages` |
+| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
+| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
+| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
+| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
+| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
+| [Project badges](project_badges.md) | `/projects/:id/badges` |
+| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
+| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
+| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
+| [Project milestones](milestones.md) | `/projects/:id/milestones` |
+| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
+| [Project templates](project_templates.md) | `/projects/:id/templates` |
+| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
+| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
+| [Releases](releases/index.md) | `/projects/:id/releases` |
+| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
+| [Repositories](repositories.md) | `/projects/:id/repository` |
+| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
+| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
+| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
+| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
+| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
+| [Services](services.md) | `/projects/:id/services` |
+| [Tags](tags.md) | `/projects/:id/repository/tags` |
+| [Vulnerabilities](vulnerabilities.md) **[ULTIMATE]** | `/projects/:id/vulnerabilities` (also available for groups) |
+| [Wikis](wikis.md) | `/projects/:id/wikis` |
### Group resources
The following API resources are available in the group context:
-| Resource | Available endpoints |
-|:--------------------------------------------------|:---------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
-| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
-| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
-| [Group badges](group_badges.md) | `/groups/:id/badges` |
-| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
-| [Group labels](group_labels.md) | `/groups/:id/labels` |
-| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
-| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
-| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
-| [Members](members.md) | `/groups/:id/members` (also available for projects) |
-| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
-| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
-| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
+| Resource | Available endpoints |
+|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
+| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
+| [Discussions](discussions.md) (threaded comments) **[ULTIMATE]** | `/groups/:id/epics/.../discussions` (also available for projects) |
+| [Epic issues](epic_issues.md) **[ULTIMATE]** | `/groups/:id/epics/.../issues` |
+| [Epic links](epic_links.md) **[ULTIMATE]** | `/groups/:id/epics/.../epics` |
+| [Epics](epics.md) **[ULTIMATE]** | `/groups/:id/epics` |
+| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
+| [Group badges](group_badges.md) | `/groups/:id/badges` |
+| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
+| [Group labels](group_labels.md) | `/groups/:id/labels` |
+| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
+| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
+| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
+| [Members](members.md) | `/groups/:id/members` (also available for projects) |
+| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
+| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
+| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
+| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) |
+| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
### Standalone resources
@@ -102,9 +113,11 @@ The following API resources are available outside of project and group contexts
| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
| [Feature flags](features.md) | `/features` |
+| [Geo Nodes](geo_nodes.md) **[PREMIUM ONLY]** | `/geo_nodes` |
| [Import repository from GitHub](import.md) | `/import/github` |
| [Issues](issues.md) | `/issues` (also available for groups and projects) |
| [Keys](keys.md) | `/keys` |
+| [License](license.md) **[CORE ONLY]** | `/license` |
| [Markdown](markdown.md) | `/markdown` |
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Namespaces](namespaces.md) | `/namespaces` |
@@ -131,6 +144,11 @@ Endpoints are available for:
- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
- [Open source license templates](templates/licenses.md).
+## SCIM **[SILVER ONLY]**
+
+[GitLab.com Silver and above](https://about.gitlab.com/pricing/) provides an [SCIM API](scim.md) that implements [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644) and provides
+the `/Users` endpoint. The base URL is: `/api/scim/v2/groups/:group_path/Users/`.
+
## Road to GraphQL
Going forward, we will start on moving to
@@ -274,7 +292,7 @@ personal access tokens, and to using the [Sudo](#sudo) feature, since the user's
password/token may not be known or may change over time.
For more information, refer to the
-[users API](users.md#retrieve-user-impersonation-tokens) docs.
+[users API](users.md#create-an-impersonation-token) docs.
Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the
`private_token` parameter or the `Private-Token` header.
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 28c73db6b98..a96206f5df3 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -141,6 +141,173 @@ Example response:
}
```
+## Create a board **[STARTER]**
+
+Creates a board.
+
+```
+POST /projects/:id/boards
+```
+
+| 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 |
+| `name` | string | yes | The name of the new board |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards?name=newboard
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "project": {
+ "id": 5,
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site"
+ },
+ "name": "newboard",
+ "milestone": {
+ "id": 12
+ "title": "10.0"
+ },
+ "lists" : [
+ {
+ "id" : 1,
+ "label" : {
+ "name" : "Testing",
+ "color" : "#F0AD4E",
+ "description" : null
+ },
+ "position" : 1
+ },
+ {
+ "id" : 2,
+ "label" : {
+ "name" : "Ready",
+ "color" : "#FF0000",
+ "description" : null
+ },
+ "position" : 2
+ },
+ {
+ "id" : 3,
+ "label" : {
+ "name" : "Production",
+ "color" : "#FF5F00",
+ "description" : null
+ },
+ "position" : 3
+ }
+ ]
+ }
+```
+
+## Update a board **[STARTER]**
+
+> [Introduced][ee-5954] in [GitLab Starter](https://about.gitlab.com/pricing/) 11.1.
+
+Updates a board.
+
+```
+PUT /projects/:id/boards/:board_id
+```
+
+| 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 |
+| `board_id` | integer | yes | The ID of a board |
+| `name` | string | no | The new name of the board |
+| `assignee_id` | integer | no | The assignee the board should be scoped to |
+| `milestone_id` | integer | no | The milestone the board should be scoped to |
+| `labels` | string | no | Comma-separated list of label names which the board should be scoped to |
+| `weight` | integer | no | The weight range from 0 to 9, to which the board should be scoped to |
+
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1?name=new_name&milestone_id=43&assignee_id=1&labels=Doing&weight=4
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "project": {
+ "id": 5,
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "created_at": "2018-07-03T05:48:49.982Z",
+ "default_branch": null,
+ "tag_list": [],
+ "ssh_url_to_repo": "ssh://user@example.com/diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "readme_url": null,
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "last_activity_at": "2018-07-03T05:48:49.982Z"
+ },
+ "lists": [],
+ "name": "new_name",
+ "group": null,
+ "milestone": {
+ "id": 43,
+ "iid": 1,
+ "project_id": 15,
+ "title": "Milestone 1",
+ "description": "Milestone 1 desc",
+ "state": "active",
+ "created_at": "2018-07-03T06:36:42.618Z",
+ "updated_at": "2018-07-03T06:36:42.618Z",
+ "due_date": null,
+ "start_date": null,
+ "web_url": "http://example.com/root/board1/milestones/1"
+ },
+ "assignee": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://example.com/root"
+ },
+ "labels": [{
+ "id": 10,
+ "name": "Doing",
+ "color": "#5CB85C",
+ "description": null
+ }],
+ "weight": 4
+ }
+```
+
+## Delete a board **[STARTER]**
+
+Deletes a board.
+
+```
+DELETE /projects/:id/boards/:board_id
+```
+
+| 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 |
+| `board_id` | integer | yes | The ID of a board |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1
+```
+
## List board lists
Get a list of the board's lists.
@@ -237,7 +404,15 @@ POST /projects/:id/boards/:board_id/lists
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
-| `label_id` | integer | yes | The ID of a label |
+| `label_id` | integer | no | The ID of a label |
+| `assignee_id` **[PREMIUM]** | integer | no | The ID of a user |
+| `milestone_id` **[PREMIUM]** | integer | no | The ID of a milestone |
+
+NOTE: **Note**:
+Label, assignee and milestone arguments are mutually exclusive,
+that is, only one of them are accepted in a request.
+Check the [Issue Board docs](../user/project/issue_board.md#summary-of-features-per-tier)
+for more information regarding the required license for each list type.
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5
@@ -307,3 +482,5 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
```
+
+[ee-5954]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5954
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 20d924ab35e..bad7a655d08 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -1 +1,5 @@
-This document was moved to [Pipeline Triggers](pipeline_triggers.md).
+---
+redirect_to: 'pipeline_triggers.md'
+---
+
+This document was moved to [another location](pipeline_triggers.md).
diff --git a/doc/api/builds.md b/doc/api/builds.md
index a6edda68bc4..0154d35cab6 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -1 +1,5 @@
+---
+redirect_to: 'jobs.md'
+---
+
This document was moved to [another location](jobs.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 8d36ae7d559..25015fad9e3 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -75,10 +75,12 @@ POST /projects/:id/repository/commits
| `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 |
+| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. |
| `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 |
| `stats` | boolean | no | Include commit stats. Default is true |
+| `force` | boolean | no | When `true` overwrites the target branch with a new commit based on the `start_branch` |
| `actions[]` Attribute | Type | Required | Description |
| --------------------- | ---- | -------- | ----------- |
@@ -129,6 +131,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Cont
```
Example response:
+
```json
{
"id": "ed899a2f4b50b4370feeea94676502b42383c746",
@@ -154,6 +157,32 @@ Example response:
}
```
+GitLab supports [form encoding](README.md#encoding-api-parameters-of-array-and-hash-types). The following is an example using Commit API with form encoding:
+
+```bash
+curl --request POST \
+ --form "branch=master" \
+ --form "commit_message=some commit message" \
+ --form "start_branch=master" \
+ --form "actions[][action]=create" \
+ --form "actions[][file_path]=foo/bar" \
+ --form "actions[][content]=</path/to/local.file" \
+ --form "actions[][action]=delete" \
+ --form "actions[][file_path]=foo/bar2" \
+ --form "actions[][action]=move" \
+ --form "actions[][file_path]=foo/bar3" \
+ --form "actions[][previous_path]=foo/bar4" \
+ --form "actions[][content]=</path/to/local1.file" \
+ --form "actions[][action]=update" \
+ --form "actions[][file_path]=foo/bar5" \
+ --form "actions[][content]=</path/to/local2.file" \
+ --form "actions[][action]=chmod" \
+ --form "actions[][file_path]=foo/bar5" \
+ --form "actions[][execute_filemode]=true" \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/projects/1/repository/commits"
+```
+
## Get a single commit
Get a specific commit identified by the commit hash or name of a branch or tag.
@@ -195,9 +224,9 @@ Example response:
"last_pipeline" : {
"id": 8,
"ref": "master",
- "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0"
+ "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0",
"status": "created"
- }
+ },
"stats": {
"additions": 15,
"deletions": 10,
@@ -474,7 +503,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `sha` | string | yes | The commit SHA
| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
-| `name` | string | no | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit`
+| `name` | string | no | Filter by [job name](../ci/yaml/README.md#introduction), e.g., `bundler:audit`
| `all` | boolean | no | Return all statuses, not only the latest ones
```bash
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 7d68d0ae744..9defef4fd53 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -1,6 +1,21 @@
# Discussions API
-Discussions are set of related notes on snippets, issues, merge requests or commits.
+Discussions are a set of related notes on:
+
+- Snippets
+- Issues
+- Epics **[ULTIMATE]**
+- Merge requests
+- Commits
+
+This includes system notes, which are notes about changes to the object (for example, when a milestone changes, there will be a corresponding system note). Label notes are not part of this API, but recorded as separate events in [resource label events](resource_label_events.md).
+
+## Discussions pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
## Issues
@@ -144,7 +159,8 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab
### Add note to existing issue discussion
-Adds a new note to the discussion.
+Adds a new note to the discussion. This can also
+[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
```
POST /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes
@@ -414,6 +430,214 @@ Parameters:
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636
```
+## Epics **[ULTIMATE]**
+
+### List group epic discussions
+
+Gets a list of all discussions for a single epic.
+
+```
+GET /groups/:id/epics/:epic_id/discussions
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+
+```json
+[
+ {
+ "id": "6a9c1750b37d513a43987b574953fceb50b03ce7",
+ "individual_note": false,
+ "notes": [
+ {
+ "id": 1126,
+ "type": "DiscussionNote",
+ "body": "discussion text",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-03T21:54:39.668Z",
+ "updated_at": "2018-03-03T21:54:39.668Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ },
+ {
+ "id": 1129,
+ "type": "DiscussionNote",
+ "body": "reply to the discussion",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-04T13:38:02.127Z",
+ "updated_at": "2018-03-04T13:38:02.127Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ }
+ ]
+ },
+ {
+ "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6",
+ "individual_note": true,
+ "notes": [
+ {
+ "id": 1128,
+ "type": null,
+ "body": "a single comment",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-04T09:17:22.520Z",
+ "updated_at": "2018-03-04T09:17:22.520Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ }
+ ]
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions
+```
+
+### Get single epic discussion
+
+Returns a single discussion for a specific group epic
+
+```
+GET /groups/:id/epics/:epic_id/discussions/:discussion_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+```
+
+### Create new epic discussion
+
+Creates a new discussion to a single group epic. This is similar to creating
+a note but but another comments (replies) can be added to it later.
+
+```
+POST /groups/:id/epics/:epic_id/discussions
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `body` | string | yes | The content of a discussion |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions?body=comment
+```
+
+### Add note to existing epic discussion
+
+Adds a new note to the discussion. This can also
+[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
+
+```
+POST /groups/:id/epics/:epic_id/discussions/:discussion_id/notes
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+| `body` | string | yes | The content of a discussion |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+```
+
+### Modify existing epic discussion note
+
+Modify existing discussion note of an epic.
+
+```
+PUT /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+| `body` | string | yes | The content of a discussion |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+```
+
+### Delete an epic discussion note
+
+Deletes an existing discussion note of an epic.
+
+```
+DELETE /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/636
+```
+
## Merge requests
### List project merge request discussions
@@ -643,7 +867,8 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.
### Add note to existing merge request discussion
-Adds a new note to the discussion.
+Adds a new note to the discussion. This can also
+[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
```
POST /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 4a38dd73747..ebcdc546d08 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -29,6 +29,111 @@ Example response:
]
```
+## Get a specific environment
+
+```
+GET /projects/:id/environments/:environment_id
+```
+
+| 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 |
+| `environment_id` | integer | yes | The ID of the environment |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1"
+```
+
+Example of response
+
+```json
+{
+ "id": 1,
+ "name": "review/fix-foo",
+ "slug": "review-fix-foo-dfjre3",
+ "external_url": "https://review-fix-foo-dfjre3.example.gitlab.com"
+ "last_deployment": {
+ "id": 100,
+ "iid": 34,
+ "ref": "fdroid",
+ "sha": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "created_at": "2019-03-25T18:55:13.252Z",
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "state": "active",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ }
+ "deployable": {
+ "id": 710,
+ "status": "success",
+ "stage": "deploy",
+ "name": "staging",
+ "ref": "fdroid",
+ "tag": false,
+ "coverage": null,
+ "created_at": "2019-03-25T18:55:13.215Z",
+ "started_at": "2019-03-25T12:54:50.082Z",
+ "finished_at": "2019-03-25T18:55:13.216Z",
+ "duration": 21623.13423,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.dev/root",
+ "created_at": "2015-12-21T13:14:24.077Z",
+ "bio": null,
+ "location": null,
+ "public_email": "",
+ "skype": "",
+ "linkedin": "",
+ "twitter": "",
+ "website_url": "",
+ "organization": null
+ }
+ "commit": {
+ "id": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "short_id": "416d8ea1",
+ "created_at": "2016-01-02T15:39:18.000Z",
+ "parent_ids": [
+ "e9a4449c95c64358840902508fc827f1a2eab7df"
+ ],
+ "title": "Removed fabric to fix #40",
+ "message": "Removed fabric to fix #40\n",
+ "author_name": "Administrator",
+ "author_email": "admin@example.com",
+ "authored_date": "2016-01-02T15:39:18.000Z",
+ "committer_name": "Administrator",
+ "committer_email": "admin@example.com",
+ "committed_date": "2016-01-02T15:39:18.000Z"
+ },
+ "pipeline": {
+ "id": 34,
+ "sha": "416d8ea11849050d3d1f5104cf8cf51053e790ab",
+ "ref": "fdroid",
+ "status": "success",
+ "web_url": "http://localhost:3000/Commit451/lab-coat/pipelines/34"
+ },
+ "web_url": "http://localhost:3000/Commit451/lab-coat/-/jobs/710",
+ "artifacts": [
+ {
+ "file_type": "trace",
+ "size": 1305,
+ "filename": "job.log",
+ "file_format": null
+ }
+ ],
+ "runner": null,
+ "artifacts_expire_at": null
+ }
+ }
+}
+```
+
## Create a new environment
Creates a new environment with the given name and external_url.
diff --git a/doc/api/epic_issues.md b/doc/api/epic_issues.md
new file mode 100644
index 00000000000..ec59ea7068e
--- /dev/null
+++ b/doc/api/epic_issues.md
@@ -0,0 +1,413 @@
+# Epic Issues API **[ULTIMATE]**
+
+Every API call to epic_issues must be authenticated.
+
+If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
+
+Epics are available only in Ultimate. If epics feature is not available a `403` status code will be returned.
+
+## List issues for an epic
+
+Gets all issues that are assigned to an epic and the authenticated user has access to.
+
+```
+GET /groups/:id/epics/:epic_iid/issues
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 76,
+ "iid": 6,
+ "project_id": 8,
+ "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+ "description" : "Ratione dolores corrupti mollitia soluta quia.",
+ "state": "opened",
+ "created_at": "2017-11-15T13:39:24.670Z",
+ "updated_at": "2018-01-04T10:49:19.506Z",
+ "closed_at": null,
+ "labels": [],
+ "milestone": {
+ "id": 38,
+ "iid": 3,
+ "project_id": 8,
+ "title": "v2.0",
+ "description": "In tempore culpa inventore quo accusantium.",
+ "state": "closed",
+ "created_at": "2017-11-15T13:39:13.825Z",
+ "updated_at": "2017-11-15T13:39:13.825Z",
+ "due_date": null,
+ "start_date": null
+ },
+ "assignees": [{
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ }],
+ "assignee": {
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "author": {
+ "id": 13,
+ "name": "Michell Johns",
+ "username": "chris_hahn",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/30e3b2122ccd6b8e45e8e14a3ffb58fc?s=80&d=identicon",
+ "web_url": "http://localhost:3001/chris_hahn"
+ },
+ "user_notes_count": 8,
+ "upvotes": 0,
+ "downvotes": 0,
+ "due_date": null,
+ "confidential": false,
+ "weight": null,
+ "discussion_locked": null,
+ "web_url": "http://localhost:3001/h5bp/html5-boilerplate/issues/6",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ },
+ "_links":{
+ "self": "http://localhost:3001/api/v4/projects/8/issues/6",
+ "notes": "http://localhost:3001/api/v4/projects/8/issues/6/notes",
+ "award_emoji": "http://localhost:3001/api/v4/projects/8/issues/6/award_emoji",
+ "project": "http://localhost:3001/api/v4/projects/8"
+ },
+ "subscribed": true,
+ "epic_issue_id": 2
+ }
+]
+```
+
+**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
+
+## Assign an issue to the epic
+
+Creates an epic - issue association. If the issue in question belongs to another epic it is unassigned from that epic.
+
+```
+POST /groups/:id/epics/:epic_iid/issues/:issue_id
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+| `issue_id` | integer/string | yes | The ID of the issue. |
+
+```bash
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/55
+```
+
+Example response:
+
+```json
+{
+ "id": 11,
+ "epic": {
+ "id": 30,
+ "iid": 5,
+ "title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "start_date": null,
+ "end_date": null
+ },
+ "issue": {
+ "id": 55,
+ "iid": 13,
+ "project_id": 8,
+ "title": "Beatae laborum voluptatem voluptate eligendi ex accusamus.",
+ "description": "Quam veritatis debitis omnis aliquam sit.",
+ "state": "opened",
+ "created_at": "2017-11-05T13:59:12.782Z",
+ "updated_at": "2018-01-05T10:33:03.900Z",
+ "closed_at": null,
+ "labels": [],
+ "milestone": {
+ "id": 48,
+ "iid": 6,
+ "project_id": 8,
+ "title": "Sprint - Sed sed maxime temporibus ipsa ullam qui sit.",
+ "description": "Quos veritatis qui expedita sunt deleniti accusamus.",
+ "state": "active",
+ "created_at": "2017-11-05T13:59:12.445Z",
+ "updated_at": "2017-11-05T13:59:12.445Z",
+ "due_date": "2017-11-13",
+ "start_date": "2017-11-05"
+ },
+ "assignees": [{
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ }],
+ "assignee": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "author": {
+ "id": 25,
+ "name": "User 3",
+ "username": "user3",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/97d6d9441ff85fdc730e02a6068d267b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/user3"
+ },
+ "user_notes_count": 0,
+ "upvotes": 0,
+ "downvotes": 0,
+ "due_date": null,
+ "confidential": false,
+ "weight": null,
+ "discussion_locked": null,
+ "web_url": "http://localhost:3001/h5bp/html5-boilerplate/issues/13",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ }
+ }
+}
+```
+
+**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
+
+## Remove an issue from the epic
+
+Removes an epic - issue association.
+
+```
+DELETE /groups/:id/epics/:epic_iid/issues/:epic_issue_id
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+| `epic_issue_id` | integer/string | yes | The ID of the issue - epic association. |
+
+```bash
+curl --header DELETE "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/11
+```
+
+Example response:
+
+```json
+{
+ "id": 11,
+ "epic": {
+ "id": 30,
+ "iid": 5,
+ "title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "start_date": null,
+ "end_date": null
+ },
+ "issue": {
+ "id": 223,
+ "iid": 13,
+ "project_id": 8,
+ "title": "Beatae laborum voluptatem voluptate eligendi ex accusamus.",
+ "description": "Quam veritatis debitis omnis aliquam sit.",
+ "state": "opened",
+ "created_at": "2017-11-05T13:59:12.782Z",
+ "updated_at": "2018-01-05T10:33:03.900Z",
+ "closed_at": null,
+ "labels": [],
+ "milestone": {
+ "id": 48,
+ "iid": 6,
+ "project_id": 8,
+ "title": "Sprint - Sed sed maxime temporibus ipsa ullam qui sit.",
+ "description": "Quos veritatis qui expedita sunt deleniti accusamus.",
+ "state": "active",
+ "created_at": "2017-11-05T13:59:12.445Z",
+ "updated_at": "2017-11-05T13:59:12.445Z",
+ "due_date": "2017-11-13",
+ "start_date": "2017-11-05"
+ },
+ "assignees": [{
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ }],
+ "assignee": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "author": {
+ "id": 25,
+ "name": "User 3",
+ "username": "user3",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/97d6d9441ff85fdc730e02a6068d267b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/user3"
+ },
+ "user_notes_count": 0,
+ "upvotes": 0,
+ "downvotes": 0,
+ "due_date": null,
+ "confidential": false,
+ "weight": null,
+ "discussion_locked": null,
+ "web_url": "http://localhost:3001/h5bp/html5-boilerplate/issues/13",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ }
+ }
+}
+```
+
+**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
+
+## Update epic - issue association
+
+Updates an epic - issue association.
+
+```
+PUT /groups/:id/epics/:epic_iid/issues/:epic_issue_id
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+| `epic_issue_id` | integer/string | yes | The ID of the issue - epic association. |
+| `move_before_id` | integer/string | no | The ID of the issue - epic association that should be placed before the link in the question. |
+| `move_after_id` | integer/string | no | The ID of the issue - epic association that should be placed after the link in the question. |
+
+```bash
+curl --header PUT "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/issues/11?move_before_id=20
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 30,
+ "iid": 6,
+ "project_id": 8,
+ "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+ "description" : "Ratione dolores corrupti mollitia soluta quia.",
+ "state": "opened",
+ "created_at": "2017-11-15T13:39:24.670Z",
+ "updated_at": "2018-01-04T10:49:19.506Z",
+ "closed_at": null,
+ "labels": [],
+ "milestone": {
+ "id": 38,
+ "iid": 3,
+ "project_id": 8,
+ "title": "v2.0",
+ "description": "In tempore culpa inventore quo accusantium.",
+ "state": "closed",
+ "created_at": "2017-11-15T13:39:13.825Z",
+ "updated_at": "2017-11-15T13:39:13.825Z",
+ "due_date": null,
+ "start_date": null
+ },
+ "assignees": [{
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ }],
+ "assignee": {
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "author": {
+ "id": 13,
+ "name": "Michell Johns",
+ "username": "chris_hahn",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/30e3b2122ccd6b8e45e8e14a3ffb58fc?s=80&d=identicon",
+ "web_url": "http://localhost:3001/chris_hahn"
+ },
+ "user_notes_count": 8,
+ "upvotes": 0,
+ "downvotes": 0,
+ "due_date": null,
+ "confidential": false,
+ "weight": null,
+ "discussion_locked": null,
+ "web_url": "http://localhost:3001/h5bp/html5-boilerplate/issues/6",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ },
+ "_links":{
+ "self": "http://localhost:3001/api/v4/projects/8/issues/6",
+ "notes": "http://localhost:3001/api/v4/projects/8/issues/6/notes",
+ "award_emoji": "http://localhost:3001/api/v4/projects/8/issues/6/award_emoji",
+ "project": "http://localhost:3001/api/v4/projects/8"
+ },
+ "subscribed": true,
+ "epic_issue_id": 11,
+ "relative_position": 55
+ }
+]
+```
diff --git a/doc/api/epic_links.md b/doc/api/epic_links.md
new file mode 100644
index 00000000000..9ad90a6d0f1
--- /dev/null
+++ b/doc/api/epic_links.md
@@ -0,0 +1,254 @@
+# Epic Links API **[ULTIMATE]**
+
+>**Note:**
+> This endpoint was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9188) in GitLab 11.8.
+
+Manages parent-child [epic relationships](../user/group/epics/index.md#multi-level-child-epics).
+
+Every API call to `epic_links` must be authenticated.
+
+If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
+
+Epics are available only in the [Ultimate/Gold tier](https://about.gitlab.com/pricing/). If the epics feature is not available, a `403` status code will be returned.
+
+## List epics related to a given epic
+
+Gets all child epics of an epic.
+
+```
+GET /groups/:id/epics/:epic_iid/epics
+```
+
+| 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 |
+| `epic_iid` | integer | yes | The internal ID of the epic. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics/
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 29,
+ "iid": 6,
+ "group_id": 1,
+ "parent_id": 5,
+ "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": []
+ }
+]
+```
+
+## Assign a child epic
+
+Creates an association between two epics, designating one as the parent epic and the other as the child epic. A parent epic can have multiple child epics. If the new child epic already belonged to another epic, it is unassigned from that previous parent.
+
+```
+POST /groups/:id/epics/:epic_iid/epics
+```
+
+| 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 |
+| `epic_iid` | integer | yes | The internal ID of the epic. |
+| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
+
+```bash
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics/6
+```
+
+Example response:
+
+```json
+{
+ "id": 6,
+ "iid": 38,
+ "group_id": 1,
+ "parent_id": 5
+ "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": []
+}
+```
+
+## Create and assign a child epic
+
+Creates a a new epic and associates it with provided parent epic. The response is LinkedEpic object.
+
+```
+POST /groups/:id/epics/:epic_iid/epics
+```
+
+| 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 |
+| `epic_iid` | integer | yes | The internal ID of the (future parent) epic. |
+| `title` | string | yes | The title of a newly created epic. |
+
+```bash
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics?title=Newpic
+```
+
+Example response:
+
+```json
+{
+ "id": 24,
+ "iid": 2,
+ "title": "child epic",
+ "group_id": 49,
+ "parent_id": 23,
+ "has_children": false,
+ "has_issues": false,
+ "reference": "&2",
+ "url": "http://localhost/groups/group16/-/epics/2",
+ "relation_url": "http://localhost/groups/group16/-/epics/1/links/24"
+}
+```
+
+## Re-order a child epic
+
+```
+PUT /groups/:id/epics/:epic_iid/epics/:child_epic_id
+```
+
+| 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. |
+| `epic_iid` | integer | yes | The internal ID of the epic. |
+| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
+| `move_before_id` | integer | no | The global ID of a sibling epic that should be placed before the child epic. |
+| `move_after_id` | integer | no | The global ID of a sibling epic that should be placed after the child epic. |
+
+```bash
+curl --header PUT "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/4/epics/5
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 29,
+ "iid": 6,
+ "group_id": 1,
+ "parent_id": 5,
+ "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": []
+ }
+]
+```
+
+## Unassign a child epic
+
+Unassigns a child epic from a parent epic.
+
+```
+DELETE /groups/:id/epics/:epic_iid/epics/:child_epic_id
+```
+
+| 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. |
+| `epic_iid` | integer | yes | The internal ID of the epic. |
+| `child_epic_id` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
+
+```bash
+curl --header DELETE "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/4/epics/5
+```
+
+Example response:
+
+```json
+{
+ "id": 5,
+ "iid": 38,
+ "group_id": 1,
+ "parent_id": null,
+ "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": []
+}
+```
diff --git a/doc/api/epics.md b/doc/api/epics.md
new file mode 100644
index 00000000000..0541cfaa715
--- /dev/null
+++ b/doc/api/epics.md
@@ -0,0 +1,360 @@
+# Epics API **[ULTIMATE]**
+
+Every API call to epic must be authenticated.
+
+If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
+
+If epics feature is not available a `403` status code will be returned.
+
+## Epic issues API
+
+The [epic issues API](epic_issues.md) allows you to interact with issues associated with an epic.
+
+# Milestone dates integration
+
+> [Introduced][ee-6448] in GitLab 11.3.
+
+Since start date and due date can be dynamically sourced from related issue milestones, when user has edit permission, additional fields will be shown. These include two boolean fields `start_date_is_fixed` and `due_date_is_fixed`, and four date fields `start_date_fixed`, `start_date_from_milestones`, `due_date_fixed` and `due_date_from_milestones`.
+
+`end_date` has been deprecated in favor of `due_date`.
+
+## Epics pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
+## List epics for a group
+
+Gets all epics of the requested group and its subgroups.
+
+```
+GET /groups/:id/epics
+GET /groups/:id/epics?author_id=5
+GET /groups/:id/epics?labels=bug,reproduced
+GET /groups/:id/epics?state=opened
+```
+
+| 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 |
+| `author_id` | integer | no | Return epics created by the given user `id` |
+| `labels` | string | no | Return epics matching a comma separated list of labels names. Label names from the epic group or a parent group can be used |
+| `order_by` | string | no | Return epics ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return epics sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Search epics against their `title` and `description` |
+| `state` | string | no | Search epics against their `state`, possible filters: `opened`, `closed` and `all`, default: `all` |
+| `created_after` | datetime | no | Return epics created on or after the given time |
+| `created_before` | datetime | no | Return epics created on or before the given time |
+| `updated_after` | datetime | no | Return epics updated on or after the given time |
+| `updated_before` | datetime | no | Return epics updated on or before the given time |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 29,
+ "iid": 4,
+ "group_id": 7,
+ "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "state": "opened",
+ "author": {
+ "id": 10,
+ "name": "Lu Mayer",
+ "username": "kam",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon",
+ "web_url": "http://localhost:3001/kam"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": [],
+ "upvotes": 4,
+ "downvotes": 0
+ }
+]
+```
+
+## Single epic
+
+Gets a single epic
+
+```
+GET /groups/:id/epics/:epic_iid
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5
+```
+
+Example response:
+
+```json
+{
+ "id": 30,
+ "iid": 5,
+ "group_id": 7,
+ "title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "state": "opened",
+ "author":{
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": [],
+ "upvotes": 4,
+ "downvotes": 0
+}
+```
+
+## New epic
+
+Creates a new epic.
+
+NOTE: **Note:**
+Starting with GitLab [11.3][ee-6448], `start_date` and `end_date` should no longer be assigned
+directly, as they now represent composite values. You can configure it via the `*_is_fixed` and
+`*_fixed` fields instead.
+
+```
+POST /groups/:id/epics
+```
+
+| 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 |
+| `title` | string | yes | The title of the epic |
+| `labels` | string | no | The comma separated list of labels |
+| `description` | string | no | The description of the epic |
+| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
+| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
+| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
+| `due_date_fixed` | string | no | The fixed due date of an epic (since 11.3) |
+| `parent_id` | integer/string | no | The id of a parent epic (since 11.11) |
+
+```bash
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics?title=Epic&description=Epic%20description
+```
+
+Example response:
+
+```json
+{
+ "id": 33,
+ "iid": 6,
+ "group_id": 7,
+ "title": "Epic",
+ "description": "Epic description",
+ "state": "opened",
+ "author": {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": [],
+ "upvotes": 4,
+ "downvotes": 0
+}
+```
+
+## Update epic
+
+Updates an epic.
+
+NOTE: **Note:**
+Starting with GitLab [11.3][ee-6448], `start_date` and `end_date` should no longer be assigned
+directly, as they now represent composite values. You can configure it via the `*_is_fixed` and
+`*_fixed` fields instead.
+
+```
+PUT /groups/:id/epics/:epic_iid
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic |
+| `title` | string | no | The title of an epic |
+| `description` | string | no | The description of an epic |
+| `labels` | string | no | The comma separated list of labels |
+| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
+| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
+| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
+| `due_date_fixed` | string | no | The fixed due date of an epic (since 11.3) |
+| `state_event` | string | no | State event for an epic. Set `close` to close the epic and `reopen` to reopen it (since 11.4) |
+
+```bash
+curl --header PUT "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5?title=New%20Title
+```
+
+Example response:
+
+```json
+{
+ "id": 33,
+ "iid": 6,
+ "group_id": 7,
+ "title": "New Title",
+ "description": "Epic description",
+ "state": "opened",
+ "author": {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "start_date": null,
+ "start_date_is_fixed": false,
+ "start_date_fixed": null,
+ "start_date_from_milestones": null,
+ "end_date": "2018-07-31",
+ "due_date": "2018-07-31",
+ "due_date_is_fixed": false,
+ "due_date_fixed": null,
+ "due_date_from_milestones": "2018-07-31",
+ "created_at": "2018-07-17T13:36:22.770Z",
+ "updated_at": "2018-07-18T12:22:05.239Z",
+ "labels": [],
+ "upvotes": 4,
+ "downvotes": 0
+}
+```
+
+## Delete epic
+
+Deletes an epic
+
+```
+DELETE /groups/:id/epics/:epic_iid
+```
+
+| 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 |
+| `epic_iid` | integer/string | yes | The internal ID of the epic. |
+
+```bash
+curl --header DELETE "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5
+```
+
+## Create a todo
+
+Manually creates a todo for the current user on an epic. If
+there already exists a todo for the user on that epic, status code `304` is
+returned.
+
+```
+POST /groups/:id/epics/:epic_iid/todo
+```
+
+| 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 |
+| `epic_iid ` | integer | yes | The internal ID of a group's epic |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/todo
+```
+
+Example response:
+
+```json
+{
+ "id": 112,
+ "group": {
+ "id": 1,
+ "name": "Gitlab",
+ "path": "gitlab",
+ "kind": "group",
+ "full_path": "base/gitlab",
+ "parent_id": null
+ },
+ "author": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/root"
+ },
+ "action_name": "marked",
+ "target_type": "epic",
+ "target": {
+ "id": 30,
+ "iid": 5,
+ "group_id": 1,
+ "title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
+ "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
+ "author":{
+ "id": 7,
+ "name": "Pamella Huel",
+ "username": "arnita",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon",
+ "web_url": "http://localhost:3001/arnita"
+ },
+ "start_date": null,
+ "end_date": null,
+ "created_at": "2018-01-21T06:21:13.165Z",
+ "updated_at": "2018-01-22T12:41:41.166Z"
+ },
+ "target_url": "https://gitlab.example.com/groups/epics/5",
+ "body": "Vel voluptas atque dicta mollitia adipisci qui at.",
+ "state": "pending",
+ "created_at": "2016-07-01T11:09:13.992Z"
+}
+```
+
+[ee-6448]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6448
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
new file mode 100644
index 00000000000..ea31abdd87e
--- /dev/null
+++ b/doc/api/geo_nodes.md
@@ -0,0 +1,404 @@
+# Geo Nodes API **[PREMIUM ONLY]**
+
+In order to interact with Geo node endpoints, you need to authenticate yourself
+as an admin.
+
+## Retrieve configuration about all Geo nodes
+
+```
+GET /geo_nodes
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "us-node",
+ "url": "https://primary.example.com/",
+ "internal_url": "https://internal.example.com/",
+ "primary": true,
+ "enabled": true,
+ "current": true,
+ "files_max_capacity": 10,
+ "repos_max_capacity": 25,
+ "verification_max_capacity": 100,
+ "clone_protocol": "http"
+ },
+ {
+ "id": 2,
+ "name": "cn-node",
+ "url": "https://secondary.example.com/",
+ "internal_url": "https://secondary.example.com/",
+ "primary": false,
+ "enabled": true,
+ "current": false,
+ "files_max_capacity": 10,
+ "repos_max_capacity": 25,
+ "verification_max_capacity": 100,
+ "clone_protocol": "http"
+ }
+]
+```
+
+## Retrieve configuration about a specific Geo node
+
+```
+GET /geo_nodes/:id
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/1
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "us-node",
+ "url": "https://primary.example.com/",
+ "internal_url": "https://primary.example.com/",
+ "primary": true,
+ "enabled": true,
+ "current": true,
+ "files_max_capacity": 10,
+ "repos_max_capacity": 25,
+ "verification_max_capacity": 100,
+ "clone_protocol": "http"
+}
+```
+
+## Edit a Geo node
+
+Updates settings of an existing Geo node.
+
+_This can only be run against a primary Geo node._
+
+```
+PUT /geo_nodes/:id
+```
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|-----------|---------------------------------------------------------------------------|
+| `id` | integer | yes | The ID of the Geo node. |
+| `enabled` | boolean | no | Flag indicating if the Geo node is enabled. |
+| `name` | string | yes | The unique identifier for the Geo node. Must match `geo_node_name` if it is set in gitlab.rb, otherwise it must match `external_url`. |
+| `url` | string | yes | The user-facing URL of the Geo node. |
+| `internal_url` | string | no | The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set.|
+| `files_max_capacity` | integer | no | Control the maximum concurrency of LFS/attachment backfill for this secondary node. |
+| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this secondary node. |
+| `verification_max_capacity` | integer | no | Control the maximum concurrency of verification for this node. |
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "cn-node",
+ "url": "https://secondary.example.com/",
+ "internal_url": "https://secondary.example.com/",
+ "primary": false,
+ "enabled": true,
+ "current": true,
+ "files_max_capacity": 10,
+ "repos_max_capacity": 25,
+ "verification_max_capacity": 100,
+ "clone_protocol": "http"
+}
+```
+
+## Delete a Geo node
+
+Removes the Geo node.
+
+NOTE: **Note:**
+Only a Geo primary node will accept this request.
+
+```
+DELETE /geo_nodes/:id
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|-------------------------|
+| `id` | integer | yes | The ID of the Geo node. |
+
+## Repair a Geo node
+
+To repair the OAuth authentication of a Geo node.
+
+_This can only be run against a primary Geo node._
+
+```
+POST /geo_nodes/:id/repair
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "us-node",
+ "url": "https://primary.example.com/",
+ "internal_url": "https://primary.example.com/",
+ "primary": true,
+ "enabled": true,
+ "current": true,
+ "files_max_capacity": 10,
+ "repos_max_capacity": 25,
+ "verification_max_capacity": 100,
+ "clone_protocol": "http"
+}
+```
+
+## Retrieve status about all Geo nodes
+
+```
+GET /geo_nodes/status
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/status
+```
+
+Example response:
+
+```json
+[
+ {
+ "geo_node_id": 1,
+ "healthy": true,
+ "health": "Healthy",
+ "health_status": "Healthy",
+ "missing_oauth_application": false,
+ "attachments_count": 1,
+ "attachments_synced_count": nil,
+ "attachments_failed_count": nil,
+ "attachments_synced_missing_on_primary_count": 0,
+ "attachments_synced_in_percentage": "0.00%",
+ "db_replication_lag_seconds": nil,
+ "lfs_objects_count": 0,
+ "lfs_objects_synced_count": nil,
+ "lfs_objects_failed_count": nil,
+ "lfs_objects_synced_missing_on_primary_count": 0,
+ "lfs_objects_synced_in_percentage": "0.00%",
+ "job_artifacts_count": 2,
+ "job_artifacts_synced_count": nil,
+ "job_artifacts_failed_count": nil,
+ "job_artifacts_synced_missing_on_primary_count": 0,
+ "job_artifacts_synced_in_percentage": "0.00%",
+ "projects_count": 41,
+ "repositories_failed_count": nil,
+ "repositories_synced_count": nil,
+ "repositories_synced_in_percentage": "0.00%",
+ "wikis_failed_count": nil,
+ "wikis_synced_count": nil,
+ "wikis_synced_in_percentage": "0.00%",
+ "replication_slots_count": 1,
+ "replication_slots_used_count": 1,
+ "replication_slots_used_in_percentage": "100.00%",
+ "replication_slots_max_retained_wal_bytes": 0,
+ "repositories_checked_count": 20,
+ "repositories_checked_failed_count": 20,
+ "repositories_checked_in_percentage": "100.00%",
+ "repositories_checksummed_count": 20,
+ "repositories_checksum_failed_count": 5,
+ "repositories_checksummed_in_percentage": "48.78%",
+ "wikis_checksummed_count": 10,
+ "wikis_checksum_failed_count": 3,
+ "wikis_checksummed_in_percentage": "24.39%",
+ "repositories_verified_count": 20,
+ "repositories_verification_failed_count": 5,
+ "repositories_verified_in_percentage": "48.78%",
+ "repositories_checksum_mismatch_count": 3,
+ "wikis_verified_count": 10,
+ "wikis_verification_failed_count": 3,
+ "wikis_verified_in_percentage": "24.39%",
+ "wikis_checksum_mismatch_count": 1,
+ "repositories_retrying_verification_count": 1,
+ "wikis_retrying_verification_count": 3,
+ "repositories_checked_count": 7,
+ "repositories_checked_failed_count": 2,
+ "repositories_checked_in_percentage": "17.07%",
+ "last_event_id": 23,
+ "last_event_timestamp": 1509681166,
+ "cursor_last_event_id": nil,
+ "cursor_last_event_timestamp": 0,
+ "last_successful_status_check_timestamp": 1510125024,
+ "version": "10.3.0",
+ "revision": "33d33a096a",
+ },
+ {
+ "geo_node_id": 2,
+ "healthy": true,
+ "health": "Healthy",
+ "health_status": "Healthy",
+ "missing_oauth_application": false,
+ "attachments_count": 1,
+ "attachments_synced_count": 1,
+ "attachments_failed_count": 0,
+ "attachments_synced_missing_on_primary_count": 0,
+ "attachments_synced_in_percentage": "100.00%",
+ "db_replication_lag_seconds": 0,
+ "lfs_objects_count": 0,
+ "lfs_objects_synced_count": 0,
+ "lfs_objects_failed_count": 0,
+ "lfs_objects_synced_missing_on_primary_count": 0,
+ "lfs_objects_synced_in_percentage": "0.00%",
+ "job_artifacts_count": 2,
+ "job_artifacts_synced_count": 1,
+ "job_artifacts_failed_count": 1,
+ "job_artifacts_synced_missing_on_primary_count": 0,
+ "job_artifacts_synced_in_percentage": "50.00%",
+ "projects_count": 41,
+ "repositories_failed_count": 1,
+ "repositories_synced_count": 40,
+ "repositories_synced_in_percentage": "97.56%",
+ "wikis_failed_count": 0,
+ "wikis_synced_count": 41,
+ "wikis_synced_in_percentage": "100.00%",
+ "replication_slots_count": nil,
+ "replication_slots_used_count": nil,
+ "replication_slots_used_in_percentage": "0.00%",
+ "replication_slots_max_retained_wal_bytes": nil,
+ "repositories_checksummed_count": 20,
+ "repositories_checksum_failed_count": 5,
+ "repositories_checksummed_in_percentage": "48.78%",
+ "wikis_checksummed_count": 10,
+ "wikis_checksum_failed_count": 3,
+ "wikis_checksummed_in_percentage": "24.39%",
+ "repositories_verified_count": 20,
+ "repositories_verification_failed_count": 5,
+ "repositories_verified_in_percentage": "48.78%",
+ "repositories_checksum_mismatch_count": 3,
+ "wikis_verified_count": 10,
+ "wikis_verification_failed_count": 3,
+ "wikis_verified_in_percentage": "24.39%",
+ "wikis_checksum_mismatch_count": 1,
+ "repositories_retrying_verification_count": 4,
+ "wikis_retrying_verification_count": 2,
+ "repositories_checked_count": 5,
+ "repositories_checked_failed_count": 1,
+ "repositories_checked_in_percentage": "12.20%",
+ "last_event_id": 23,
+ "last_event_timestamp": 1509681166,
+ "cursor_last_event_id": 23,
+ "cursor_last_event_timestamp": 1509681166,
+ "last_successful_status_check_timestamp": 1510125024,
+ "version": "10.3.0",
+ "revision": "33d33a096a"
+ }
+]
+```
+
+NOTE: **Note:**
+In GitLab 12.0, deprecated fields `wikis_count` and `repositories_count` were removed. Use `projects_count` instead.
+
+## Retrieve status about a specific Geo node
+
+```
+GET /geo_nodes/:id/status
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/2/status
+```
+
+Example response:
+
+```json
+{
+ "geo_node_id": 2,
+ "healthy": true,
+ "health": "Healthy",
+ "health_status": "Healthy",
+ "missing_oauth_application": false,
+ "attachments_count": 1,
+ "attachments_synced_count": 1,
+ "attachments_failed_count": 0,
+ "attachments_synced_missing_on_primary_count": 0,
+ "attachments_synced_in_percentage": "100.00%",
+ "db_replication_lag_seconds": 0,
+ "lfs_objects_count": 0,
+ "lfs_objects_synced_count": 0,
+ "lfs_objects_failed_count": 0,
+ "lfs_objects_synced_missing_on_primary_count": 0,
+ "lfs_objects_synced_in_percentage": "0.00%",
+ "job_artifacts_count": 2,
+ "job_artifacts_synced_count": 1,
+ "job_artifacts_failed_count": 1,
+ "job_artifacts_synced_missing_on_primary_count": 0,
+ "job_artifacts_synced_in_percentage": "50.00%",
+ "projects_count": 41,
+ "repositories_failed_count": 1,
+ "repositories_synced_count": 40,
+ "repositories_synced_in_percentage": "97.56%",
+ "wikis_failed_count": 0,
+ "wikis_synced_count": 41,
+ "wikis_synced_in_percentage": "100.00%",
+ "replication_slots_count": nil,
+ "replication_slots_used_count": nil,
+ "replication_slots_used_in_percentage": "0.00%",
+ "replication_slots_max_retained_wal_bytes": nil,
+ "last_event_id": 23,
+ "last_event_timestamp": 1509681166,
+ "cursor_last_event_id": 23,
+ "cursor_last_event_timestamp": 1509681166,
+ "last_successful_status_check_timestamp": 1510125268,
+ "version": "10.3.0",
+ "revision": "33d33a096a"
+}
+```
+
+Note: The `health_status` parameter can only be in an "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
+
+NOTE: **Note:**
+In GitLab 12.0, deprecated fields `wikis_count` and `repositories_count` were removed. Use `projects_count` instead.
+
+## Retrieve project sync or verification failures that occurred on the current node
+
+This only works on a secondary node.
+
+```
+GET /geo_nodes/current/failures
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `type` | string | no | Type of failed objects (`repository`/`wiki`) |
+| `failure_type` | string | no | Type of failures (`sync`/`checksum_mismatch`/`verification`) |
+
+This endpoint uses [Pagination](README.md#pagination).
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/current/failures
+```
+
+Example response:
+
+```json
+[
+ {
+ "project_id": 3,
+ "last_repository_synced_at": "2017-10-31 14:25:55 UTC",
+ "last_repository_successful_sync_at": "2017-10-31 14:26:04 UTC",
+ "last_wiki_synced_at": "2017-10-31 14:26:04 UTC",
+ "last_wiki_successful_sync_at": "2017-10-31 14:26:11 UTC",
+ "repository_retry_count": null,
+ "wiki_retry_count": 1,
+ "last_repository_sync_failure": null,
+ "last_wiki_sync_failure": "Error syncing Wiki repository",
+ "last_repository_verification_failure": "",
+ "last_wiki_verification_failure": "",
+ "repository_verification_checksum_sha": "da39a3ee5e6b4b0d32e5bfef9a601890afd80709",
+ "wiki_verification_checksum_sha": "da39a3ee5e6b4b0d3255bfef9ef0189aafd80709",
+ "repository_checksum_mismatch": false,
+ "wiki_checksum_mismatch": false
+ }
+]
+```
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index ec48bf4940b..88e657a5d2f 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -16,6 +16,20 @@ added to the API without creating breaking changes. This allows us to
have a versionless API as described in [the GraphQL
documentation](https://graphql.org/learn/best-practices/#versioning).
+## Vision
+
+We want the GraphQL API to be the **primary** means of interacting
+programmatically with GitLab. To achieve this, it needs full coverage - anything
+possible in the REST API should also be possible in the GraphQL API.
+
+To help us meet this vision, the frontend should use GraphQL in preference to
+the REST API for new features, although the alpha status of GraphQL may prevent
+this from being a possibility at times.
+
+There are no plans to deprecate the REST API. To reduce the technical burden of
+supporting two APIs in parallel, they should share implementations as much as
+possible.
+
## Enabling the GraphQL feature
The GraphQL API itself is currently in Alpha, and therefore hidden behind a
@@ -29,7 +43,19 @@ curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gi
## Available queries
-A first iteration of a GraphQL API includes a query for: `project`. Within a project it is also possible to fetch a `mergeRequest` by IID.
+A first iteration of a GraphQL API includes the following queries
+
+1. `project` : Within a project it is also possible to fetch a `mergeRequest` by IID.
+1. `group` : Only basic group information is currently supported.
+1. `namespace` : Within a namespace it is also possible to fetch `projects`.
+
+### Multiplex queries
+
+GitLab supports batching queries into a single request using
+[apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http). More
+info about multiplexed queries is also available for
+[graphql-ruby](https://graphql-ruby.org/queries/multiplex.html) the
+library GitLab uses on the backend.
## GraphiQL
diff --git a/doc/api/group_level_variables.md b/doc/api/group_level_variables.md
index 3551bfa3f8b..7b00df6d775 100644
--- a/doc/api/group_level_variables.md
+++ b/doc/api/group_level_variables.md
@@ -22,10 +22,12 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
[
{
"key": "TEST_VARIABLE_1",
+ "variable_type": "env_var",
"value": "TEST_1"
},
{
"key": "TEST_VARIABLE_2",
+ "variable_type": "env_var",
"value": "TEST_2"
}
]
@@ -51,6 +53,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
```json
{
"key": "TEST_VARIABLE_1",
+ "variable_type": "env_var",
"value": "TEST_1"
}
```
@@ -63,12 +66,13 @@ Create a new variable.
POST /groups/:id/variables
```
-| Attribute | Type | required | Description |
-|-------------|---------|----------|-----------------------|
-| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
-| `value` | string | yes | The `value` of a variable |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|-----------------|---------|----------|-----------------------|
+| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
@@ -78,6 +82,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
{
"key": "NEW_VARIABLE",
"value": "new value",
+ "variable_type": "env_var",
"protected": false
}
```
@@ -90,12 +95,13 @@ Update a group's variable.
PUT /groups/:id/variables/:key
```
-| Attribute | Type | required | Description |
-|-------------|---------|----------|-------------------------|
-| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable |
-| `value` | string | yes | The `value` of a variable |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|-----------------|---------|----------|-------------------------|
+| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables/NEW_VARIABLE" --form "value=updated value"
@@ -105,6 +111,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
{
"key": "NEW_VARIABLE",
"value": "updated value",
+ "variable_type": "env_var",
"protected": true
}
```
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index eb974267084..260eb09cc38 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -1,6 +1,5 @@
# Group milestones API
-> **Notes:**
> [Introduced][ce-12819] in GitLab 9.5.
## List group milestones
@@ -13,6 +12,7 @@ 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
+GET /groups/:id/milestones?title=1.0
GET /groups/:id/milestones?search=version
```
@@ -23,6 +23,7 @@ Parameters:
| `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 `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones |
+| `title` | string | optional | Return only the milestones having the given `title` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 907b443d355..20789a1d4a4 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -68,6 +68,7 @@ GET /groups?statistics=true
"statistics": {
"storage_size" : 212,
"repository_size" : 33,
+ "wiki_size" : 100,
"lfs_objects_size" : 123,
"job_artifacts_size" : 57
diff --git a/doc/api/issue_links.md b/doc/api/issue_links.md
new file mode 100644
index 00000000000..1c7db6a8e4c
--- /dev/null
+++ b/doc/api/issue_links.md
@@ -0,0 +1,215 @@
+# Issue links API **[STARTER]**
+
+## List issue relations
+
+Get a list of related issues of a given issue, sorted by the relationship creation datetime (ascending).
+Issues will be filtered according to the user authorizations.
+
+```
+GET /projects/:id/issues/:issue_iid/links
+```
+
+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 |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+
+```json
+[
+ {
+ "id" : 84,
+ "iid" : 14,
+ "issue_link_id": 1
+ "project_id" : 4,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignees" : [],
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null,
+ "subscribed" : true,
+ "user_notes_count": 0,
+ "due_date": null,
+ "web_url": "http://example.com/example/example/issues/14",
+ "confidential": false,
+ "weight": null,
+ }
+]
+```
+
+## Create an issue link
+
+Creates a two-way relation between two issues. User must be allowed to update both issues in order to succeed.
+
+```
+POST /projects/:id/issues/:issue_iid/links
+```
+
+| 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 |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `target_project_id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) of a target project |
+| `target_issue_iid` | integer/string | yes | The internal ID of a target project's issue |
+
+
+```json
+{
+ "source_issue" : {
+ "id" : 83,
+ "iid" : 11,
+ "project_id" : 4,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignees" : [],
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null,
+ "subscribed" : true,
+ "user_notes_count": 0,
+ "due_date": null,
+ "web_url": "http://example.com/example/example/issues/11",
+ "confidential": false,
+ "weight": null,
+ },
+ "target_issue" : {
+ "id" : 84,
+ "iid" : 14,
+ "project_id" : 4,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignees" : [],
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null,
+ "subscribed" : true,
+ "user_notes_count": 0,
+ "due_date": null,
+ "web_url": "http://example.com/example/example/issues/14",
+ "confidential": false,
+ "weight": null,
+ }
+}
+```
+
+## Delete an issue link
+
+Deletes an issue link, thus removes the two-way relationship.
+
+```
+DELETE /projects/:id/issues/:issue_iid/links/:issue_link_id
+```
+
+
+| 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 |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `issue_link_id` | integer/string | yes | The ID of an issue relationship |
+
+
+```json
+{
+ "source_issue" : {
+ "id" : 83,
+ "iid" : 11,
+ "project_id" : 4,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignees" : [],
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null,
+ "subscribed" : true,
+ "user_notes_count": 0,
+ "due_date": null,
+ "web_url": "http://example.com/example/example/issues/11",
+ "confidential": false,
+ "weight": null,
+ },
+ "target_issue" : {
+ "id" : 84,
+ "iid" : 14,
+ "project_id" : 4,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignees" : [],
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null,
+ "subscribed" : true,
+ "user_notes_count": 0,
+ "due_date": null,
+ "web_url": "http://example.com/example/example/issues/14",
+ "confidential": false,
+ "weight": null,
+ }
+}
+```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index d8b2ff07e30..0d96cfa1b21 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -37,23 +37,26 @@ GET /issues?confidential=true
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
+| `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. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `with_labels_details`| Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
-| `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`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `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` |
-| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
+| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
-| `confidential ` | Boolean | no | Filter confidential or public issues. |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues
@@ -109,7 +112,7 @@ Example response:
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
- "labels" : [],
+ "labels" : ["foo", "bar"],
"upvotes": 4,
"downvotes": 0,
"merge_requests_count": 0,
@@ -122,8 +125,21 @@ Example response:
"human_time_estimate": null,
"human_total_time_spent": null
},
+ "has_tasks": true,
+ "task_status": "10 of 15 tasks completed",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "_links":{
+ "self":"http://example.com/api/v4/projects/1/issues/76",
+ "notes":"`http://example.com/`api/v4/projects/1/issues/76/notes",
+ "award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
+ "project":"http://example.com/api/v4/projects/1"
+ },
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -158,11 +174,14 @@ GET /groups/:id/issues?confidential=true
| `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. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `with_labels_details`| Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
-| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `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` |
@@ -221,7 +240,7 @@ Example response:
"id" : 9,
"name" : "Dr. Luella Kovacek"
},
- "labels" : [],
+ "labels" : ["foo", "bar"],
"upvotes": 4,
"downvotes": 0,
"merge_requests_count": 0,
@@ -240,8 +259,21 @@ Example response:
"human_time_estimate": null,
"human_total_time_spent": null
},
+ "has_tasks": true,
+ "task_status": "10 of 15 tasks completed",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "_links":{
+ "self":"http://example.com/api/v4/projects/4/issues/41",
+ "notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
+ "award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
+ "project":"http://example.com/api/v4/projects/4"
+ },
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -277,10 +309,13 @@ GET /projects/:id/issues?confidential=true
| `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. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `with_labels_details`| Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
-| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `author_username` | string | no | Return issues created by the given `username`. Simillar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. _([Introduced][ce-13004] in GitLab 9.5)_ |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Simillar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `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` |
@@ -340,7 +375,7 @@ Example response:
"id" : 9,
"name" : "Dr. Luella Kovacek"
},
- "labels" : [],
+ "labels" : ["foo", "bar"],
"upvotes": 4,
"downvotes": 0,
"merge_requests_count": 0,
@@ -366,8 +401,21 @@ Example response:
"human_time_estimate": null,
"human_total_time_spent": null
},
+ "has_tasks": true,
+ "task_status": "10 of 15 tasks completed",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "_links":{
+ "self":"http://example.com/api/v4/projects/4/issues/41",
+ "notes":"`http://example.com/`api/v4/projects/4/issues/41/notes",
+ "award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
+ "project":"http://example.com/api/v4/projects/4"
+ },
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -464,6 +512,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -547,6 +599,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -638,6 +694,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -646,37 +706,6 @@ Example response:
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
-## Bulk update issues
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21368) in GitLab 11.9.
-
-Update multiple issues using a single API call. Returns the number of successfully updated issues.
-
-```
-PUT /projects/:id/issues/bulk_update
-```
-
-| 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. |
-| `issuable_ids` | Array[integer] | yes | The IDs of issues to be updated. |
-| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
-| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
-| `add_label_ids` | Array[integer] | no | Comma-separated label IDs to be added. |
-| `remove_label_ids` | Array[integer] | no | Comma-separated label IDs to be added. |
-| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it. |
-| `subscription_event` | string | no | The subscription_event event of an issue. Set `subscribe` to subscribe to the issue and `unsubscribe` to unsubscribe from it. |
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/bulk_update?issuable_ids[]=1&issuable_ids[]=2&state_event=close
-```
-
-Example response:
-
-```json
-{ "message": "2 issues updated" }
-```
-
## Delete an issue
Only for admins and project owners. Soft deletes the issue in question.
@@ -775,6 +804,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -860,6 +893,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -926,7 +963,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/12",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1024,7 +1065,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/110",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.",
diff --git a/doc/api/issues_statistics.md b/doc/api/issues_statistics.md
new file mode 100644
index 00000000000..82bc9c142cc
--- /dev/null
+++ b/doc/api/issues_statistics.md
@@ -0,0 +1,177 @@
+# Issues Statistics API
+
+Every API call to issues_statistics must be authenticated.
+
+If a user is not a member of a project and the project is private, a `GET`
+request on that project will result to a `404` status code.
+
+## Get issues statistics
+
+Gets issues count statistics on all issues the authenticated user has access to. By default it
+returns only issues created by the current user. To get all issues,
+use parameter `scope=all`.
+
+```
+GET /issues_statistics
+GET /issues_statistics?labels=foo
+GET /issues_statistics?labels=foo,bar
+GET /issues_statistics?labels=foo,bar&state=opened
+GET /issues_statistics?milestone=1.0.0
+GET /issues_statistics?milestone=1.0.0&state=opened
+GET /issues_statistics?iids[]=42&iids[]=43
+GET /issues_statistics?author_id=5
+GET /issues_statistics?assignee_id=5
+GET /issues_statistics?my_reaction_emoji=star
+GET /issues_statistics?search=foo&in=title
+GET /issues_statistics?confidential=true
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. |
+| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
+| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me` |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
+| `search` | string | no | Search issues against their `title` and `description` |
+| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues_statistics
+```
+
+Example response:
+
+```json
+{
+ "statistics": {
+ "counts": {
+ "all": 20,
+ "closed": 5,
+ "opened": 15
+ }
+ }
+}
+```
+
+## Get group issues statistics
+
+Gets issues count statistics for given group.
+
+```
+GET /groups/:id/issues_statistics
+GET /groups/:id/issues_statistics?labels=foo
+GET /groups/:id/issues_statistics?labels=foo,bar
+GET /groups/:id/issues_statistics?labels=foo,bar&state=opened
+GET /groups/:id/issues_statistics?milestone=1.0.0
+GET /groups/:id/issues_statistics?milestone=1.0.0&state=opened
+GET /groups/:id/issues_statistics?iids[]=42&iids[]=43
+GET /groups/:id/issues_statistics?search=issue+title+or+description
+GET /groups/:id/issues_statistics?author_id=5
+GET /groups/:id/issues_statistics?assignee_id=5
+GET /groups/:id/issues_statistics?my_reaction_emoji=star
+GET /groups/:id/issues_statistics?confidential=true
+```
+
+| 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 |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. |
+| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
+| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
+| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `search` | string | no | Search group issues against their `title` and `description` |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues_statistics
+```
+
+Example response:
+
+```json
+{
+ "statistics": {
+ "counts": {
+ "all": 20,
+ "closed": 5,
+ "opened": 15
+ }
+ }
+}
+```
+
+## Get project issues statistics
+
+Gets issues count statistics for given project.
+
+```
+GET /projects/:id/issues_statistics
+GET /projects/:id/issues_statistics?labels=foo
+GET /projects/:id/issues_statistics?labels=foo,bar
+GET /projects/:id/issues_statistics?labels=foo,bar&state=opened
+GET /projects/:id/issues_statistics?milestone=1.0.0
+GET /projects/:id/issues_statistics?milestone=1.0.0&state=opened
+GET /projects/:id/issues_statistics?iids[]=42&iids[]=43
+GET /projects/:id/issues_statistics?search=issue+title+or+description
+GET /projects/:id/issues_statistics?author_id=5
+GET /projects/:id/issues_statistics?assignee_id=5
+GET /projects/:id/issues_statistics?my_reaction_emoji=star
+GET /projects/:id/issues_statistics?confidential=true
+```
+
+| 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` |
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. |
+| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
+| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. |
+| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | Array[String] | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
+| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `search` | string | no | Search project issues against their `title` and `description` |
+| `created_after` | datetime | no | Return issues created on or after the given time |
+| `created_before` | datetime | no | Return issues created on or before the given time |
+| `updated_after` | datetime | no | Return issues updated on or after the given time |
+| `updated_before` | datetime | no | Return issues updated on or before the given time |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
+
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues_statistics
+```
+
+Example response:
+
+```json
+{
+ "statistics": {
+ "counts": {
+ "all": 20,
+ "closed": 5,
+ "opened": 15
+ }
+ }
+}
+```
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 085e321b35f..72973b69117 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -10,7 +10,7 @@ GET /projects/:id/jobs
| 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. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```sh
@@ -32,6 +32,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2015-12-24T15:51:21.727Z",
"started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
@@ -81,6 +82,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2015-12-24T15:51:21.802Z",
"started_at": "2015-12-24T17:54:27.722Z",
"finished_at": "2015-12-24T17:54:27.895Z",
@@ -142,8 +144,8 @@ GET /projects/:id/pipelines/:pipeline_id/jobs
| 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. |
-| `pipeline_id` | integer | yes | The ID of a pipeline. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `pipeline_id` | integer | yes | ID of a pipeline. |
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```sh
@@ -165,6 +167,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2015-12-24T15:51:21.727Z",
"started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
@@ -214,6 +217,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2015-12-24T15:51:21.802Z",
"started_at": "2015-12-24T17:54:27.722Z",
"finished_at": "2015-12-24T17:54:27.895Z",
@@ -275,8 +279,8 @@ GET /projects/:id/jobs/:job_id
| 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
@@ -296,6 +300,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2015-12-24T15:51:21.880Z",
"started_at": "2015-12-24T17:54:30.733Z",
"finished_at": "2015-12-24T17:54:31.198Z",
@@ -341,30 +346,58 @@ Example of response
> **Notes**:
>
> - [Introduced][ce-2893] in GitLab 8.5.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Get job artifacts of a project.
+Get the job's artifacts zipped archive of a project.
```
GET /projects/:id/jobs/:job_id/artifacts
```
-| 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
-curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
+curl --output artifacts.zip --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID
+ `42`. Note that the command is wrapped into single quotes since it contains a
+ colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID `42`:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
@@ -373,9 +406,13 @@ Possible response status codes:
> **Notes**:
>
> - [Introduced][ce-5347] in GitLab 8.10.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Download the artifacts archive from the given reference name and job provided the
-job finished successfully.
+Download the artifacts zipped archive from the given reference name and job,
+provided the job finished successfully. This is the same as
+[getting the job's artifacts](#get-job-artifacts), but by defining the job's
+name instead of its ID.
```
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
@@ -383,24 +420,51 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
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. |
-| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
-| `job` | string | yes | The name of the job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
+| `job` | string | yes | The name of the job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch. Note that the command is wrapped into single quotes
+ since it contains a colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
@@ -409,7 +473,7 @@ Possible response status codes:
> Introduced in GitLab 10.0
Download a single artifact file from a job with a specified ID from within
-the job's artifacts archive. The file is extracted from the archive and
+the job's artifacts zipped archive. The file is extracted from the archive and
streamed to the client.
```
@@ -420,7 +484,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. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id ` | integer | yes | The unique job identifier. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
@@ -454,7 +518,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. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
| `job` | string | yes | The name of the job. |
@@ -483,8 +547,8 @@ GET /projects/:id/jobs/:job_id/trace
| 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. |
-| job_id | integer | yes | The ID of a job. |
+| id | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| job_id | integer | yes | ID of a job. |
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
@@ -507,8 +571,8 @@ POST /projects/:id/jobs/:job_id/cancel
| 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
@@ -528,6 +592,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:14:09.526Z",
"finished_at": null,
@@ -555,8 +620,8 @@ POST /projects/:id/jobs/:job_id/retry
| 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
@@ -576,6 +641,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": null,
"finished_at": null,
@@ -605,8 +671,8 @@ 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
Example of request
@@ -628,6 +694,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"download_url": null,
"id": 42,
"name": "rubocop",
@@ -658,8 +725,8 @@ 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
Example request:
@@ -681,6 +748,7 @@ Example response:
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"download_url": null,
"id": 42,
"name": "rubocop",
@@ -699,6 +767,33 @@ Example response:
}
```
+## Delete artifacts
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25522) in GitLab 11.9.
+
+Delete artifacts of a job.
+
+```
+DELETE /projects/:id/jobs/:job_id/artifacts
+```
+
+| Attribute | Type | Required | Description |
+|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `job_id` | integer | yes | ID of a job. |
+
+
+Example request:
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts"
+```
+
+NOTE: **Note:**
+At least Maintainer role is required to delete artifacts.
+
+If the artifacts were deleted successfully, a response with status `204 No Content` is returned.
+
## Play a job
Triggers a manual action to start a job.
@@ -709,8 +804,8 @@ POST /projects/:id/jobs/:job_id/play
| 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. |
-| `job_id` | integer | yes | The ID of a job. |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
@@ -730,6 +825,7 @@ Example of response
"title": "Test the CI integration."
},
"coverage": null,
+ "allow_failure": false,
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": null,
"finished_at": null,
@@ -746,3 +842,7 @@ Example of response
"user": null
}
```
+
+[ee]: https://about.gitlab.com/pricing/
+[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
+[triggers]: ../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium
diff --git a/doc/api/license.md b/doc/api/license.md
new file mode 100644
index 00000000000..2a8de64bdbf
--- /dev/null
+++ b/doc/api/license.md
@@ -0,0 +1,176 @@
+# License **[CORE ONLY]**
+
+In order to interact with license endpoints, you need to authenticate yourself
+as an admin.
+
+## Retrieve information about the current license
+
+```
+GET /license
+```
+
+```json
+{
+ "id": 2,
+ "plan": "gold",
+ "created_at": "2018-02-27T23:21:58.674Z",
+ "starts_at": "2018-01-27",
+ "expires_at": "2022-01-27",
+ "historical_max": 300,
+ "expired": false,
+ "overage": 200,
+ "user_limit": 100,
+ "active_users": 300,
+ "licensee": {
+ "Name": "John Doe1"
+ },
+ "add_ons": {
+ "GitLab_FileLocks": 1,
+ "GitLab_Auditor_User": 1
+ }
+}
+```
+
+## Retrieve information about all licenses
+
+```
+GET /licenses
+```
+
+```json
+[
+ {
+ "id": 1,
+ "plan": "silver",
+ "created_at": "2018-02-27T23:21:58.674Z",
+ "starts_at": "2018-01-27",
+ "expires_at": "2022-01-27",
+ "historical_max": 300,
+ "expired": false,
+ "overage": 200,
+ "user_limit": 100,
+ "licensee": {
+ "Name": "John Doe1"
+ },
+ "add_ons": {
+ "GitLab_FileLocks": 1,
+ "GitLab_Auditor_User": 1
+ }
+ },
+ {
+ "id": 2,
+ "plan": "gold",
+ "created_at": "2018-02-27T23:21:58.674Z",
+ "starts_at": "2018-01-27",
+ "expires_at": "2022-01-27",
+ "historical_max": 300,
+ "expired": false,
+ "overage": 200,
+ "user_limit": 100,
+ "licensee": {
+ "Name": "Doe John"
+ },
+ "add_ons": {
+ "GitLab_FileLocks": 1,
+ }
+ }
+]
+```
+
+Overage is the difference between the number of active users and the licensed number of users.
+This is calculated differently depending on whether the license has expired or not.
+
+- If the license has expired, it uses the historical maximum active user count (`historical_max`).
+- If the license has not expired, it uses the current active users count.
+
+Returns:
+
+- `200 OK` with response containing the licenses in JSON format. This will be an empty JSON array if there are no licenses.
+- `403 Forbidden` if the current user in not permitted to read the licenses.
+
+## Add a new license
+
+```
+POST /license
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `license` | string | yes | The license string |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/license?license=eyJkYXRhIjoiMHM5Q...S01Udz09XG4ifQ=="
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "plan": "gold",
+ "created_at": "2018-02-27T23:21:58.674Z",
+ "starts_at": "2018-01-27",
+ "expires_at": "2022-01-27",
+ "historical_max": 300,
+ "expired": false,
+ "overage": 200,
+ "user_limit": 100,
+ "active_users": 300,
+ "licensee": {
+ "Name": "John Doe1"
+ },
+ "add_ons": {
+ "GitLab_FileLocks": 1,
+ "GitLab_Auditor_User": 1
+ }
+}
+```
+
+Returns:
+
+- `201 Created` if the license is successfully added.
+- `400 Bad Request` if the license couldn't be added, with an error message explaining the reason.
+
+
+## Delete a license
+
+```
+DELETE /license/:id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | ID of the GitLab license. |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/license/:id"
+```
+
+Example response:
+
+```json
+{
+ "id": 2,
+ "plan": "gold",
+ "created_at": "2018-02-27T23:21:58.674Z",
+ "starts_at": "2018-01-27",
+ "expires_at": "2022-01-27",
+ "historical_max": 300,
+ "expired": false,
+ "overage": 200,
+ "user_limit": 100,
+ "licensee": {
+ "Name": "John Doe"
+ },
+ "add_ons": {
+ "GitLab_FileLocks": 1,
+ "GitLab_Auditor_User": 1
+ }
+}
+```
+
+Returns:
+
+- `204 No Content` if the license is successfully deleted.
+- `403 Forbidden` if the current user in not permitted to delete the license.
+- `404 Not Found` if the license to delete could not be found.
diff --git a/doc/api/license_templates.md b/doc/api/license_templates.md
new file mode 100644
index 00000000000..1b68af9ce31
--- /dev/null
+++ b/doc/api/license_templates.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'templates/licenses.md'
+---
+
+This document was moved to [another location](templates/licenses.md).
diff --git a/doc/api/managed_licenses.md b/doc/api/managed_licenses.md
new file mode 100644
index 00000000000..47b193111b6
--- /dev/null
+++ b/doc/api/managed_licenses.md
@@ -0,0 +1,136 @@
+# Managed Licenses API **[ULTIMATE]**
+
+## List managed licenses
+
+Get all managed licenses for a given project.
+
+```
+GET /projects/:id/managed_licenses
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/managed_licenses
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "MIT",
+ "approval_status": "approved"
+ },
+ {
+ "id": 3,
+ "name": "ISC",
+ "approval_status": "blacklisted"
+ }
+]
+```
+
+## Show an existing managed license
+
+Shows an existing managed license.
+
+```
+GET /projects/:id/managed_licenses/:managed_license_id
+```
+
+| 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 |
+| `managed_license_id` | integer/string | yes | The ID or URL-encoded name of the license belonging to the project |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/managed_licenses/6"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "MIT",
+ "approval_status": "blacklisted"
+}
+```
+
+## Create a new managed license
+
+Creates a new managed license for the given project with the given name and approval status.
+
+```
+POST /projects/:id/managed_licenses
+```
+
+| 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 |
+| `name` | string | yes | The name of the managed license |
+| `approval_status` | string | yes | The approval status. "approved" or "blacklisted" |
+
+```bash
+curl --data "name=MIT&approval_status=blacklisted" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/managed_licenses"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "MIT",
+ "approval_status": "approved"
+}
+```
+
+## Delete a managed license
+
+Deletes a managed license with a given id.
+
+```
+DELETE /projects/:id/managed_licenses/:managed_license_id
+```
+
+| 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 |
+| `managed_license_id` | integer/string | yes | The ID or URL-encoded name of the license belonging to the project |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/managed_licenses/4"
+```
+
+When successful, it replies with an HTTP 204 response.
+
+## Edit an existing managed license
+
+Updates an existing managed license with a new approval status.
+
+```
+PATCH /projects/:id/managed_licenses/:managed_license_id
+```
+
+| 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 |
+| `managed_license_id` | integer/string | yes | The ID or URL-encoded name of the license belonging to the project |
+| `approval_status` | string | yes | The approval status. "approved" or "blacklisted" |
+
+```bash
+curl --request PATCH --data "approval_status=blacklisted" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/managed_licenses/6"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "MIT",
+ "approval_status": "blacklisted"
+}
+```
diff --git a/doc/api/members.md b/doc/api/members.md
index 0593d2c20ea..8784d577f99 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -62,7 +62,9 @@ Example response:
## List all members of a group or project including inherited members
Gets a list of group or project members viewable by the authenticated user, including inherited members through ancestor groups.
-Returns multiple times the same user (with different member attributes) when the user is a member of the project/group and of one or more ancestor group.
+When a user is a member of the project/group and of one or more ancestor groups the user is returned only once with the project access_level (if exists)
+or the access_level for the user in the first group which he belongs to in the project groups ancestors chain.
+**Note:** We plan to [change](https://gitlab.com/gitlab-org/gitlab-ce/issues/62284) this behavior to return highest access_level instead.
```
GET /groups/:id/members/all
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
new file mode 100644
index 00000000000..ddac81328b9
--- /dev/null
+++ b/doc/api/merge_request_approvals.md
@@ -0,0 +1,425 @@
+# Merge request approvals API **[STARTER]**
+
+Configuration for approvals on all Merge Requests (MR) in the project. Must be authenticated for all endpoints.
+
+## Project-level MR approvals
+
+### Get Configuration
+
+>**Note:** This API endpoint is only available on 10.6 Starter and above.
+
+You can request information about a project's approval configuration using the
+following endpoint:
+
+```
+GET /projects/:id/approvals
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | ------------------- |
+| `id` | integer | yes | The ID of a project |
+
+```json
+{
+ "approvers": [
+ {
+ "user": {
+ "id": 5,
+ "name": "John Doe6",
+ "username": "user5",
+ "state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
+ }
+ }
+ ],
+ "approver_groups": [
+ {
+ "group": {
+ "id": 1,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ }
+ ],
+ "approvals_before_merge": 2,
+ "reset_approvals_on_push": true,
+ "disable_overriding_approvers_per_merge_request": false
+}
+```
+
+### Change configuration
+
+>**Note:** This API endpoint is only available on 10.6 Starter and above.
+
+If you are allowed to, you can change approval configuration using the following
+endpoint:
+
+```
+POST /projects/:id/approvals
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+| ------------------------------------------------ | ------- | -------- | ---------------------------------------------------------- |
+| `id` | integer | yes | The ID of a project |
+| `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged |
+| `reset_approvals_on_push` | boolean | no | Reset approvals on a new push |
+| `disable_overriding_approvers_per_merge_request` | boolean | no | Allow/Disallow overriding approvers per MR |
+| `merge_requests_author_approval` | boolean | no | Allow/Disallow authors be able to self approve merge requests |
+
+```json
+{
+ "approvers": [
+ {
+ "user": {
+ "id": 5,
+ "name": "John Doe6",
+ "username": "user5",
+ "state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
+ }
+ }
+ ],
+ "approver_groups": [
+ {
+ "group": {
+ "id": 1,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ }
+ ],
+ "approvals_before_merge": 2,
+ "reset_approvals_on_push": true,
+ "disable_overriding_approvers_per_merge_request": false,
+ "merge_requests_author_approval": false
+}
+```
+
+### Change allowed approvers
+
+>**Note:** This API endpoint is only available on 10.6 Starter and above.
+
+If you are allowed to, you can change approvers and approver groups using
+the following endpoint:
+
+```
+PUT /projects/:id/approvers
+```
+
+**Important:** Approvers and groups not in the request will be **removed**
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+| -------------------- | ------- | -------- | --------------------------------------------------- |
+| `id` | integer | yes | The ID of a project |
+| `approver_ids` | Array | yes | An array of User IDs that can approve MRs |
+| `approver_group_ids` | Array | yes | An array of Group IDs whose members can approve MRs |
+
+```json
+{
+ "approvers": [
+ {
+ "user": {
+ "id": 5,
+ "name": "John Doe6",
+ "username": "user5",
+ "state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
+ }
+ }
+ ],
+ "approver_groups": [
+ {
+ "group": {
+ "id": 1,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ }
+ ],
+ "approvals_before_merge": 2,
+ "reset_approvals_on_push": true,
+ "disable_overriding_approvers_per_merge_request": false
+}
+```
+
+
+## Merge Request-level MR approvals
+
+Configuration for approvals on a specific Merge Request. Must be authenticated for all endpoints.
+
+### Get Configuration
+
+>**Note:** This API endpoint is only available on 8.9 Starter and above.
+
+You can request information about a merge request's approval status using the
+following endpoint:
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/approvals
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|---------------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+
+```json
+{
+ "id": 5,
+ "iid": 5,
+ "project_id": 1,
+ "title": "Approvals API",
+ "description": "Test",
+ "state": "opened",
+ "created_at": "2016-06-08T00:19:52.638Z",
+ "updated_at": "2016-06-08T21:20:42.470Z",
+ "merge_status": "cannot_be_merged",
+ "approvals_required": 2,
+ "approvals_left": 1,
+ "approved_by": [
+ {
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url": "http://localhost:3000/u/root"
+ }
+ }
+ ],
+ "approvers": [],
+ "approver_groups": []
+}
+```
+
+### Change approval configuration
+
+>**Note:** This API endpoint is only available on 10.6 Starter and above.
+
+If you are allowed to, you can change `approvals_required` using the following
+endpoint:
+
+```
+POST /projects/:id/merge_requests/:merge_request_iid/approvals
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|--------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+| `approvals_required` | integer | yes | Approvals required before MR can be merged |
+
+
+```json
+{
+ "id": 5,
+ "iid": 5,
+ "project_id": 1,
+ "title": "Approvals API",
+ "description": "Test",
+ "state": "opened",
+ "created_at": "2016-06-08T00:19:52.638Z",
+ "updated_at": "2016-06-08T21:20:42.470Z",
+ "merge_status": "cannot_be_merged",
+ "approvals_required": 2,
+ "approvals_left": 2,
+ "approved_by": [],
+ "approvers": [],
+ "approver_groups": []
+}
+```
+
+### Change allowed approvers for Merge Request
+
+>**Note:** This API endpoint is only available on 10.6 Starter and above.
+
+If you are allowed to, you can change approvers and approver groups using
+the following endpoint:
+
+```
+PUT /projects/:id/merge_requests/:merge_request_iid/approvers
+```
+
+**Important:** Approvers and groups not in the request will be **removed**
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|-----------------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+| `approver_ids` | Array | yes | An array of User IDs that can approve the MR |
+| `approver_group_ids` | Array | yes | An array of Group IDs whose members can approve the MR |
+
+```json
+{
+ "id": 5,
+ "iid": 5,
+ "project_id": 1,
+ "title": "Approvals API",
+ "description": "Test",
+ "state": "opened",
+ "created_at": "2016-06-08T00:19:52.638Z",
+ "updated_at": "2016-06-08T21:20:42.470Z",
+ "merge_status": "cannot_be_merged",
+ "approvals_required": 2,
+ "approvals_left": 2,
+ "approved_by": [],
+ "approvers": [
+ {
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url": "http://localhost:3000/u/root"
+ }
+ }
+ ],
+ "approver_groups": [
+ {
+ "group": {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ }
+ ]
+}
+```
+
+## Approve Merge Request
+
+>**Note:** This API endpoint is only available on 8.9 Starter and above.
+
+If you are allowed to, you can approve a merge request using the following
+endpoint:
+
+```
+POST /projects/:id/merge_requests/:merge_request_iid/approve
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|---------------------|---------|----------|-------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+| `sha` | string | no | The HEAD of the MR |
+| `approval_password` **[STARTER]** | string | no | Current user's password. Required if [**Require user password to approve**](../user/project/merge_requests/merge_request_approvals.md#require-authentication-when-approving-a-merge-request-starter) is enabled in the project settings. |
+
+The `sha` parameter works in the same way as
+when [accepting a merge request](merge_requests.md#accept-mr): if it is passed, then it must
+match the current HEAD of the merge request for the approval to be added. If it
+does not match, the response code will be `409`.
+
+```json
+{
+ "id": 5,
+ "iid": 5,
+ "project_id": 1,
+ "title": "Approvals API",
+ "description": "Test",
+ "state": "opened",
+ "created_at": "2016-06-08T00:19:52.638Z",
+ "updated_at": "2016-06-09T21:32:14.105Z",
+ "merge_status": "can_be_merged",
+ "approvals_required": 2,
+ "approvals_left": 0,
+ "approved_by": [
+ {
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url": "http://localhost:3000/u/root"
+ }
+ },
+ {
+ "user": {
+ "name": "Nico Cartwright",
+ "username": "ryley",
+ "id": 2,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/cf7ad14b34162a76d593e3affca2adca?s=80\u0026d=identicon",
+ "web_url": "http://localhost:3000/u/ryley"
+ }
+ }
+ ],
+ "approvers": [],
+ "approver_groups": []
+}
+```
+
+## Unapprove Merge Request
+
+>**Note:** This API endpoint is only available on 9.0 Starter and above.
+
+If you did approve a merge request, you can unapprove it using the following
+endpoint:
+
+```
+POST /projects/:id/merge_requests/:merge_request_iid/unapprove
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|---------------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index ba47e507b79..96a956ad03a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -93,6 +93,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -130,7 +138,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -227,6 +239,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -264,7 +284,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -351,6 +375,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -386,7 +418,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -445,6 +481,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -513,7 +557,11 @@ Parameters:
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
"diverged_commits_count": 2,
- "rebase_in_progress": false
+ "rebase_in_progress": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -547,7 +595,7 @@ Parameters:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon",
"web_url": "http://localhost/user2"
- },
+ }
]
```
@@ -629,6 +677,14 @@ Parameters:
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon",
"web_url" : "https://gitlab.example.com/root"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
@@ -662,7 +718,11 @@ Parameters:
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ },
"changes": [
{
"old_path": "VERSION",
@@ -718,6 +778,7 @@ POST /projects/:id/merge_requests
| `target_branch` | string | yes | The target branch |
| `title` | string | yes | Title of MR |
| `assignee_id` | integer | no | Assignee user ID |
+| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
| `description` | string | no | Description of MR |
| `target_project_id` | integer | no | The target project (numeric id) |
| `labels` | string | no | Labels for MR as a comma-separated list |
@@ -824,7 +885,11 @@ POST /projects/:id/merge_requests
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -843,6 +908,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `target_branch` | string | no | The target branch |
| `title` | string | no | Title of MR |
| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
+| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
| `description` | string | no | Description of MR |
@@ -885,6 +951,14 @@ Must include at least one non-required attribute from above.
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -952,41 +1026,14 @@ Must include at least one non-required attribute from above.
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
-## Bulk update merge requests
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21368) in GitLab 11.9.
-
-Update multiple merge requests using a single API call. Returns the number of successfully updated merge requests.
-
-```
-PUT /projects/:id/merge_requests/bulk_update
-```
-
-| 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. |
-| `issuable_ids` | Array[integer] | yes | The IDs of merge requests to be updated. |
-| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
-| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
-| `add_label_ids` | Array[integer] | no | Comma-separated label IDs to be added. |
-| `remove_label_ids` | Array[integer] | no | Comma-separated label IDs to be added. |
-| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it. |
-| `subscription_event` | string | no | The subscription_event event of an issue. Set `subscribe` to subscribe to the issue and `unsubscribe` to unsubscribe from it. |
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/merge_requests/bulk_update?issuable_ids[]=1&issuable_ids[]=2&state_event=close
-```
-
-Example response:
-
-```json
-{ "message": "2 merge_requests updated" }
-```
-
## Delete a merge request
Only for admins and project owners. Soft deletes the merge request in question.
@@ -1061,6 +1108,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1128,7 +1183,41 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
+}
+```
+
+## Returns the up to date merge-ref HEAD commit
+
+Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
+ref, of the target project repository, if possible. This ref will have the state the target branch would have if
+a regular merge action was taken.
+
+This is not a regular merge action given it doesn't change the merge request target branch state in any manner.
+
+This ref (`refs/merge-requests/:iid/merge`) isn't necessarily overwritten when submitting
+requests to this API, though it'll make sure the ref has the latest possible state.
+
+If the merge request has conflicts, is empty or already merged, you'll get a `400` and a descriptive error message.
+
+It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in case of `200`.
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/merge_ref
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `merge_request_iid` (required) - Internal ID of MR
+
+```json
+{
+ "commit_id": "854a3a7a17acbcc0bbbea170986df1eb60435f34"
}
```
@@ -1177,6 +1266,14 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1244,7 +1341,11 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1280,7 +1381,7 @@ If the rebase operation is ongoing, the response will include the following:
```json
{
- "rebase_in_progress": true
+ "rebase_in_progress": true,
"merge_error": null
}
```
@@ -1291,7 +1392,7 @@ the following:
```json
{
"rebase_in_progress": false,
- "merge_error": null,
+ "merge_error": null
}
```
@@ -1300,7 +1401,7 @@ If the rebase operation fails, the response will include the following:
```json
{
"rebase_in_progress": false,
- "merge_error": "Rebase failed. Please rebase locally",
+ "merge_error": "Rebase failed. Please rebase locally"
}
```
@@ -1433,6 +1534,14 @@ Example response:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1499,7 +1608,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1554,6 +1667,14 @@ Example response:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 2,
"target_project_id": 3,
"labels": [
@@ -1620,7 +1741,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1695,6 +1820,14 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon",
"web_url": "https://gitlab.example.com/barrett.krajcik"
},
+ "assignees": [{
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/axel.block"
+ }],
"source_project_id": 3,
"target_project_id": 3,
"labels": [],
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index fa8f8a0bcf0..90d8c033cd6 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -10,6 +10,7 @@ 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
+GET /projects/:id/milestones?title=1.0
GET /projects/:id/milestones?search=version
```
@@ -20,6 +21,7 @@ Parameters:
| `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 `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones |
+| `title` | string | optional | Return only the milestones having the given `title` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
@@ -90,7 +92,7 @@ Parameters:
- `description` (optional) - The description of a milestone
- `due_date` (optional) - The due date of the milestone
- `start_date` (optional) - The start date of the milestone
-- `state_event` (optional) - The state event of the milestone (close|activate)
+- `state_event` (optional) - The state event of the milestone (close or activate)
## Delete project milestone
@@ -130,3 +132,18 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a project milestone
+
+## Promote project milestone to a group milestone
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53861) in GitLab 11.9
+
+Only for users with developer access to the group.
+
+```
+POST /projects/:id/milestones/:milestone_id/promote
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a project milestone
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index b8bc4c40124..b66a3198ffb 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -64,9 +64,9 @@ Get all namespaces that match a string in their name or path.
GET /namespaces?search=foobar
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
+| Attribute | Type | Required | Description |
+| --------- | ------ | -------- | ----------- |
+| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
Example request:
@@ -98,9 +98,9 @@ Get a namespace by ID.
GET /namespaces/:id
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | ID or path of the namespace |
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the namespace](README.md#namespaced-path-encoding) |
Example request:
diff --git a/doc/api/notes.md b/doc/api/notes.md
index dd4e18b14e6..c09129c22d4 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -1,6 +1,20 @@
# Notes API
-Notes are comments on snippets, issues or merge requests.
+Notes are comments on:
+
+- Snippets
+- Issues
+- Merge requests
+- Epics **[ULTIMATE]**
+
+This includes system notes, which are notes about changes to the object (for example, when a milestone changes, there will be a corresponding system note). Label notes are not part of this API, but recorded as separate events in [resource label events](resource_label_events.md).
+
+## Notes pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
## Issues
@@ -381,3 +395,126 @@ Parameters:
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/7/notes/1602
```
+
+## Epics **[ULTIMATE]**
+
+### List all epic notes
+
+Gets a list of all notes for a single epic. Epic notes are comments users can post to an epic.
+
+```
+GET /groups/:id/epics/:epic_id/notes
+GET /groups/:id/epics/:epic_id/notes?sort=asc&order_by=updated_at
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of a group epic |
+| `sort` | string | no | Return epic notes sorted in `asc` or `desc` order. Default is `desc` |
+| `order_by` | string | no | Return epic notes ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/notes
+```
+
+### Get single epic note
+
+Returns a single note for a given epic.
+
+```
+GET /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+
+```json
+{
+ "id": 52,
+ "title": "Epic",
+ "file_name": "epic.rb",
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
+ },
+ "expires_at": null,
+ "updated_at": "2013-10-02T07:34:20Z",
+ "created_at": "2013-10-02T07:34:20Z"
+}
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/notes/1
+```
+
+### Create new epic note
+
+Creates a new note for a single epic. Epic notes are comments users can post to an epic.
+If you create a note where the body only contains an Award Emoji, you'll receive this object back.
+
+```
+POST /groups/:id/epics/:epic_id/notes
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `body` | string | yes | The content of a note |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+```
+
+### Modify existing epic note
+
+Modify existing note of an epic.
+
+```
+PUT /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+| `body` | string | yes | The content of a note |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+```
+
+### Delete an epic note
+
+Deletes an existing note of an epic.
+
+```
+DELETE /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/52/notes/1659
+```
diff --git a/doc/api/packages.md b/doc/api/packages.md
new file mode 100644
index 00000000000..618e5c3056a
--- /dev/null
+++ b/doc/api/packages.md
@@ -0,0 +1,152 @@
+# Packages API **[PREMIUM]**
+
+This is the API docs of [GitLab Packages](../administration/packages.md).
+
+## List project packages
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9259) in GitLab 11.8.
+
+Get a list of project packages. Both Maven and NPM packages are included in results.
+When accessed without authentication, only packages of public projects are returned.
+
+```
+GET /projects/:id/packages
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/packages
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "com/mycompany/my-app",
+ "version": "1.0-SNAPSHOT",
+ "package_type": "maven"
+ },
+ {
+ "id": 2,
+ "name": "@foo/bar",
+ "version": "1.0.3",
+ "package_type": "npm"
+ }
+]
+```
+
+By default, the `GET` request will return 20 results, since the API is [paginated](README.md#pagination).
+
+## Get a project package
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9667) in GitLab 11.9.
+
+Get a single project package.
+
+```
+GET /projects/:id/packages/:package_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `package_id` | integer | yes | ID of a package. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/packages/:package_id
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "com/mycompany/my-app",
+ "version": "1.0-SNAPSHOT",
+ "package_type": "maven"
+}
+```
+
+## List package files
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9305) in GitLab 11.8.
+
+Get a list of package files of a single package.
+
+```
+GET /projects/:id/packages/:package_id/package_files
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `package_id` | integer | yes | ID of a package. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/packages/4/package_files
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 25,
+ "package_id": 4,
+ "created_at": "2018-11-07T15:25:52.199Z",
+ "file_name": "my-app-1.5-20181107.152550-1.jar",
+ "size": 2421,
+ "file_md5": "58e6a45a629910c6ff99145a688971ac",
+ "file_sha1": "ebd193463d3915d7e22219f52740056dfd26cbfe"
+ },
+ {
+ "id": 26,
+ "package_id": 4,
+ "created_at": "2018-11-07T15:25:56.776Z",
+ "file_name": "my-app-1.5-20181107.152550-1.pom",
+ "size": 1122,
+ "file_md5": "d90f11d851e17c5513586b4a7e98f1b2",
+ "file_sha1": "9608d068fe88aff85781811a42f32d97feb440b5"
+ },
+ {
+ "id": 27,
+ "package_id": 4,
+ "created_at": "2018-11-07T15:26:00.556Z",
+ "file_name": "maven-metadata.xml",
+ "size": 767,
+ "file_md5": "6dfd0cce1203145a927fef5e3a1c650c",
+ "file_sha1": "d25932de56052d320a8ac156f745ece73f6a8cd2"
+ }
+]
+```
+
+By default, the `GET` request will return 20 results, since the API is [paginated](README.md#pagination).
+
+## Delete a project package
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9623) in GitLab 11.9.
+
+Deletes a project package.
+
+```
+DELETE /projects/:id/packages/:package_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `package_id` | integer | yes | ID of a package. |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/packages/:package_id
+```
+
+Can return the following status codes:
+
+- `204 No Content`, if the package was deleted successfully.
+- `404 Not Found`, if the package was not found.
diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md
index 137f1fdddec..470e55425f8 100644
--- a/doc/api/pipeline_schedules.md
+++ b/doc/api/pipeline_schedules.md
@@ -1,4 +1,4 @@
-# Pipeline schedules
+# Pipeline schedules API
You can read more about [pipeline schedules](../user/project/pipelines/schedules.md).
@@ -88,6 +88,7 @@ curl --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gitlab.example.com/
"variables": [
{
"key": "TEST_VARIABLE_1",
+ "variable_type": "env_var",
"value": "TEST_1"
}
]
@@ -278,9 +279,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gi
}
```
-## Pipeline schedule variable
+## Pipeline schedule variables
-> [Introduced][ce-34518] in GitLab 10.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34518) in GitLab 10.0.
## Create a new pipeline schedule variable
@@ -296,6 +297,7 @@ POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
```sh
curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "key=NEW_VARIABLE" --form "value=new value" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/variables"
@@ -304,6 +306,7 @@ curl --request POST --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "key=N
```json
{
"key": "NEW_VARIABLE",
+ "variable_type": "env_var",
"value": "new value"
}
```
@@ -322,6 +325,7 @@ PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables/:key
| `pipeline_schedule_id` | integer | yes | The pipeline schedule id |
| `key` | string | yes | The `key` of a variable |
| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
```sh
curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "value=updated value" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13/variables/NEW_VARIABLE"
@@ -331,6 +335,7 @@ curl --request PUT --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" --form "value=
{
"key": "NEW_VARIABLE",
"value": "updated value"
+ "variable_type": "env_var",
}
```
@@ -358,5 +363,3 @@ curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gi
"value": "updated value"
}
```
-
-[ce-34518]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34518 \ No newline at end of file
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 43bbf463c8d..753faec3cc8 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -93,6 +93,37 @@ Example of response
}
```
+### Get variables of a pipeline
+
+```
+GET /projects/:id/pipelines/:pipeline_id/variables
+```
+
+| 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 |
+| `pipeline_id` | integer | yes | The ID of a pipeline |
+
+```
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/variables"
+```
+
+Example of response
+
+```json
+[
+ {
+ "key": "RUN_NIGHTLY_BUILD",
+ "variable_type": "env_var",
+ "value": "true"
+ },
+ {
+ "key": "foo",
+ "value": "bar"
+ }
+]
+```
+
## Create a new pipeline
> [Introduced][ce-7209] in GitLab 8.14
@@ -105,7 +136,7 @@ POST /projects/:id/pipeline
|------------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref` | string | yes | Reference to commit |
-| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] |
+| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'variable_type' => 'file', 'value' => 'true' }] |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index 8efb98fe1fc..327781f6c93 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -33,6 +33,7 @@ Example response:
{
"id":18,
"name":"cluster-1",
+ "domain":"example.com",
"created_at":"2019-01-02T20:18:12.563Z",
"provider_type":"user",
"platform_type":"kubernetes",
@@ -90,6 +91,7 @@ Example response:
{
"id":18,
"name":"cluster-1",
+ "domain":"example.com",
"created_at":"2019-01-02T20:18:12.563Z",
"provider_type":"user",
"platform_type":"kubernetes",
@@ -157,12 +159,15 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project owned by the authenticated user |
| `name` | String | yes | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
| `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true |
+| `managed` | Boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true |
| `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API |
| `platform_kubernetes_attributes[token]` | String | yes | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
| `platform_kubernetes_attributes[authorization_type]` | String | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
+| `environment_scope` | String | no | The associated environment to the cluster. Defaults to `*` **[PREMIUM]** |
Example request:
@@ -247,10 +252,12 @@ Parameters:
| `id` | integer | yes | The ID of the project owned by the authenticated user |
| `cluster_id` | integer | yes | The ID of the cluster |
| `name` | String | no | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
| `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API |
| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
+| `environment_scope` | String | no | The associated environment to the cluster **[PREMIUM]** |
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
@@ -262,7 +269,7 @@ Example request:
```bash
curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/24 \
-H "Content-Type:application/json" \
--X PUT --data '{"name":"new-cluster-name","api_url":"https://new-api-url.com"}'
+-X PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}'
```
Example response:
@@ -271,6 +278,7 @@ Example response:
{
"id":24,
"name":"new-cluster-name",
+ "domain":"new-domain.com",
"created_at":"2019-01-03T21:53:40.610Z",
"provider_type":"user",
"platform_type":"kubernetes",
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index 438bebe62f5..66a749e4811 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -1,4 +1,4 @@
-# Project-level Variables API
+# Project-level Variables API
## List project variables
@@ -20,10 +20,12 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
[
{
"key": "TEST_VARIABLE_1",
+ "variable_type": "env_var",
"value": "TEST_1"
},
{
"key": "TEST_VARIABLE_2",
+ "variable_type": "env_var",
"value": "TEST_2"
}
]
@@ -49,7 +51,10 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
```json
{
"key": "TEST_VARIABLE_1",
- "value": "TEST_1"
+ "variable_type": "env_var",
+ "value": "TEST_1",
+ "protected": false,
+ "masked": true
}
```
@@ -61,12 +66,15 @@ Create a new variable.
POST /projects/:id/variables
```
-| Attribute | Type | required | Description |
-|-------------|---------|----------|-----------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
-| `value` | string | yes | The `value` of a variable |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-----------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
@@ -76,7 +84,11 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
{
"key": "NEW_VARIABLE",
"value": "new value",
- "protected": false
+ "protected": false,
+ "variable_type": "env_var",
+ "protected": false,
+ "masked": false,
+ "environment_scope": "*"
}
```
@@ -88,12 +100,15 @@ Update a project's variable.
PUT /projects/:id/variables/:key
```
-| Attribute | Type | required | Description |
-|-------------|---------|----------|-------------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable |
-| `value` | string | yes | The `value` of a variable |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-------------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
@@ -103,7 +118,10 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
{
"key": "NEW_VARIABLE",
"value": "updated value",
- "protected": true
+ "variable_type": "env_var",
+ "protected": true,
+ "masked": false,
+ "environment_scope": "*"
}
```
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index f02674adfe2..0ccb0517e08 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -121,7 +121,6 @@ Parameters:
## Get user agent details
-> **Notes:**
> [Introduced][ce-29508] in GitLab 9.4.
Available only for admins.
diff --git a/doc/api/project_statistics.md b/doc/api/project_statistics.md
new file mode 100644
index 00000000000..34d73abfcbf
--- /dev/null
+++ b/doc/api/project_statistics.md
@@ -0,0 +1,49 @@
+# Project statistics API
+
+Every API call to [project](../user/project/index.md) statistics must be authenticated.
+
+## Get the statistics of the last 30 days
+
+Retrieving the statistics requires write access to the repository.
+Currently only HTTP fetches statistics are returned.
+Fetches statistics includes both clones and pulls count and are HTTP only, SSH fetches are not included.
+
+```
+GET /projects/:id/statistics
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ------ | -------- | ----------- |
+| `id ` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+
+Example response:
+
+```json
+{
+ "fetches": {
+ "total": 50,
+ "days": [
+ {
+ "count": 10,
+ "date": "2018-01-10"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-09"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-08"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-07"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-06"
+ }
+ ]
+ }
+}
+```
diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md
index 3b5b12c8da3..0a94a8d47ae 100644
--- a/doc/api/project_templates.md
+++ b/doc/api/project_templates.md
@@ -15,7 +15,7 @@ templates are also available from this API endpoint.
Support will be added for [Issue and Merge Request templates](../user/project/description_templates.md)
in a future release.
-Support for [Group-level file templates](../user/group/index.md#group-level-file-templates-premium)
+Support for [Group-level file templates](../user/group/index.md#group-file-templates-premium)
**[PREMIUM]** was [added](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987)
in GitLab 11.5
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0a950352ecf..75669d85803 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -153,6 +153,7 @@ When the user is authenticated and `simple` is not set this returns something li
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -234,6 +235,7 @@ When the user is authenticated and `simple` is not set this returns something li
"commit_count": 12,
"storage_size": 2066080,
"repository_size": 2066080,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -342,6 +344,7 @@ GET /users/:user_id/projects
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -423,6 +426,7 @@ GET /users/:user_id/projects
"commit_count": 12,
"storage_size": 2066080,
"repository_size": 2066080,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -494,7 +498,9 @@ GET /projects/:id
"name": "Diaspora",
"path": "diaspora",
"kind": "group",
- "full_path": "diaspora"
+ "full_path": "diaspora",
+ "avatar_url": "http://localhost:3000/uploads/group/avatar/3/foo.jpg",
+ "web_url": "http://localhost:3000/groups/diaspora"
},
"import_status": "none",
"import_error": null,
@@ -546,6 +552,7 @@ GET /projects/:id
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -561,6 +568,8 @@ GET /projects/:id
}
```
+**Note**: The `web_url` and `avatar_url` attributes on `namespace` were [introduced][ce-27427] in GitLab 11.11.
+
If the project is a fork, and you provide a valid token to authenticate, the
`forked_from_project` field will appear in the response.
@@ -1587,3 +1596,4 @@ GET /projects/:id/snapshot
[eep]: https://about.gitlab.com/pricing/ "Available only in GitLab Premium"
[ee-6137]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6137
+[ce-27427]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27427
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index fd7b9d6e6e2..9c91264ed65 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -15,7 +15,7 @@ GET /projects/:id/releases/:tag_name/assets/links
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
Example request:
@@ -53,7 +53,7 @@ GET /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
@@ -84,7 +84,7 @@ POST /projects/:id/releases/:tag_name/assets/links
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `name` | string | yes | The name of the link. |
| `url` | string | yes | The URL of the link. |
@@ -120,7 +120,7 @@ PUT /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
| `name` | string | no | The name of the link. |
@@ -156,7 +156,7 @@ DELETE /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 6fcc06ea8cd..87c7f371de1 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -181,7 +181,7 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify
## Delete existing file in repository
-This allows you to delete a single file. For deleting multiple files with a singleh request see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions).
+This allows you to delete a single file. For deleting multiple files with a single request, see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions).
```
DELETE /projects/:id/repository/files/:file_path
diff --git a/doc/api/repository_storage_health.md b/doc/api/repository_storage_health.md
deleted file mode 100644
index edf4b04acea..00000000000
--- a/doc/api/repository_storage_health.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Circuitbreaker API
-
-NOTE: **Deprecated:**
-Support of the circuit breaker is removed, as Gitaly can be configured to
-to work without NFS and [communicate solely over HTTP](../administration/gitaly/index.md).
diff --git a/doc/api/resource_label_events.md b/doc/api/resource_label_events.md
index e1f9ffa9472..f0a7ac4e41d 100644
--- a/doc/api/resource_label_events.md
+++ b/doc/api/resource_label_events.md
@@ -88,6 +88,92 @@ Parameters:
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events/1
```
+## Epics **[ULTIMATE]**
+
+### List group epic label events
+
+Gets a list of all label events for a single epic.
+
+```
+GET /groups/:id/epics/:epic_id/resource_label_events
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+
+```json
+[
+ {
+ "id": 106,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-19T11:43:01.746Z",
+ "resource_type": "Epic",
+ "resource_id": 33,
+ "label": {
+ "id": 73,
+ "name": "a1",
+ "color": "#34495E",
+ "description": ""
+ },
+ "action": "add"
+ },
+ {
+ "id": 107,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-19T11:43:01.746Z",
+ "resource_type": "Epic",
+ "resource_id": 33,
+ "label": {
+ "id": 37,
+ "name": "glabel2",
+ "color": "#A8D695",
+ "description": ""
+ },
+ "action": "add"
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/11/resource_label_events
+```
+
+### Get single epic label event
+
+Returns a single label event for a specific group epic
+
+```
+GET /groups/:id/epics/:epic_id/resource_label_events/:resource_label_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `resource_label_event_id` | integer | yes | The ID of a label event |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/11/resource_label_events/107
+```
+
## Merge requests
### List project merge request label events
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 35c18649fec..2d91428d1c1 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -4,6 +4,29 @@
[ce-2640]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2640
+## Registration and authentication tokens
+
+There are two tokens to take into account when connecting a Runner with GitLab.
+
+| Token | Description |
+| ----- | ----------- |
+| Registration token | Token used to [register the Runner](https://docs.gitlab.com/runner/register/). It can be [obtained through GitLab](../ci/runners/README.md). |
+| Authentication token | Token used to authenticate the Runner with the GitLab instance. It is obtained either automatically when [registering a Runner](https://docs.gitlab.com/runner/register/), or manually when [registering the Runner via the Runners API](#register-a-new-runner). |
+
+Here's an example of how the two tokens are used in Runner registration:
+
+1. You register the Runner via the GitLab API using a registration token, and an
+ authentication token is returned.
+1. You use that authentication token and add it to the
+ [Runner's configuration file](https://docs.gitlab.com/runner/commands/#configuration-file):
+
+ ```toml
+ [[runners]]
+ token = "<authentication_token>"
+ ```
+
+GitLab and Runner are then connected.
+
## List owned runners
Get a list of specific runners available to the user.
@@ -13,13 +36,15 @@ GET /runners
GET /runners?scope=active
GET /runners?type=project_type
GET /runners?status=active
+GET /runners?tag_list=tag1,tag2
```
-| Attribute | Type | Required | Description |
-|-----------|---------|----------|---------------------|
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|---------------------|
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
@@ -62,13 +87,15 @@ GET /runners/all
GET /runners/all?scope=online
GET /runners/all?type=project_type
GET /runners/all?status=active
+GET /runners/all?tag_list=tag1,tag2
```
-| Attribute | Type | Required | Description |
-|-----------|---------|----------|---------------------|
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|---------------------|
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
@@ -252,6 +279,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
## List runner's jobs
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15432) in GitLab 10.3.
+
List jobs that are being processed or were processed by specified Runner.
```
@@ -347,14 +376,16 @@ GET /projects/:id/runners
GET /projects/:id/runners?scope=active
GET /projects/:id/runners?type=project_type
GET /projects/:id/runners?status=active
+GET /projects/:id/runners?tag_list=tag1,tag2
```
-| 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 |
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| 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 |
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
@@ -448,17 +479,18 @@ POST /runners
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `token` | string | yes | Registration token ([Read how to obtain a token](../ci/runners/README.md)) |
+| `token` | string | yes | [Registration token](#registration-and-authentication-tokens). |
| `description`| string | no | Runner's description|
| `info` | hash | no | Runner's metadata |
| `active` | boolean| no | Whether the Runner is active |
| `locked` | boolean| no | Whether the Runner should be locked for current project |
| `run_untagged` | boolean | no | Whether the Runner should handle untagged jobs |
| `tag_list` | Array[String] | no | List of Runner's tags |
+| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
```
-curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=ipzXrMhuyyJPifUt6ANz" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
+curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=<registration_token>" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
Response:
@@ -486,10 +518,10 @@ DELETE /runners
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `token` | string | yes | Runner's authentication token |
+| `token` | string | yes | Runner's [authentication token](#registration-and-authentication-tokens). |
```
-curl --request DELETE "https://gitlab.example.com/api/v4/runners" --form "token=ebb6fc00521627750c8bb750f2490e"
+curl --request DELETE "https://gitlab.example.com/api/v4/runners" --form "token=<authentication_token>"
```
Response:
@@ -508,10 +540,10 @@ POST /runners/verify
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
-| `token` | string | yes | Runner's authentication token |
+| `token` | string | yes | Runner's [authentication token](#registration-and-authentication-tokens). |
```
-curl --request POST "https://gitlab.example.com/api/v4/runners/verify" --form "token=ebb6fc00521627750c8bb750f2490e"
+curl --request POST "https://gitlab.example.com/api/v4/runners/verify" --form "token=<authentication_token>"
```
Response:
diff --git a/doc/api/scim.md b/doc/api/scim.md
new file mode 100644
index 00000000000..3870ea788e7
--- /dev/null
+++ b/doc/api/scim.md
@@ -0,0 +1,235 @@
+# SCIM API **[SILVER ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab Silver](https://about.gitlab.com/pricing/) 11.10.
+
+The SCIM API implements the [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
+
+NOTE: **Note:**
+[Group SSO](../user/group/saml_sso/index.md) and the feature
+flag `:group_scim` must be enabled for the group. For more information, see [SCIM setup documentation](../user/group/saml_sso/scim_setup.md#requirements).
+
+## Get a list of SAML users
+
+NOTE: **Note:**
+This endpoint is used as part of the SCIM syncing mechanism and it only returns
+a single user based on a unique ID which should match the `extern_uid` of the user.
+
+```text
+GET /api/scim/v2/groups/:group_path/Users
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `filter` | string | yes | A [filter](#available-filters) expression. |
+| `group_path` | string | yes | Full path to the group. |
+
+Example request:
+
+```sh
+curl 'https://example.gitlab.com/api/scim/v2/groups/test_group/Users?filter=id%20eq%20"0b1d561c-21ff-4092-beab-8154b17f82f2"' --header "Authorization: Bearer <your_scim_token>" --header "Content-Type: application/scim+json"
+```
+
+Example response:
+
+```json
+{
+ "schemas": [
+ "urn:ietf:params:scim:api:messages:2.0:ListResponse"
+ ],
+ "totalResults": 1,
+ "itemsPerPage": 20,
+ "startIndex": 1,
+ "Resources": [
+ {
+ "schemas": [
+ "urn:ietf:params:scim:schemas:core:2.0:User"
+ ],
+ "id": "0b1d561c-21ff-4092-beab-8154b17f82f2",
+ "active": true,
+ "name.formatted": "Test User",
+ "userName": "username",
+ "meta": { "resourceType":"User" },
+ "emails": [
+ {
+ "type": "work",
+ "value": "name@example.com",
+ "primary": true
+ }
+ ]
+ }
+ ]
+}
+```
+
+## Get a single SAML user
+
+```text
+GET /api/scim/v2/groups/:group_path/Users/:id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | string | yes | External UID of the user. |
+| `group_path` | string | yes | Full path to the group. |
+
+Example request:
+
+```sh
+curl 'https://example.gitlab.com/api/scim/v2/groups/test_group/Users/f0b1d561c-21ff-4092-beab-8154b17f82f2' --header "Authorization: Bearer <your_scim_token>" --header "Content-Type: application/scim+json"
+```
+
+Example response:
+
+```json
+{
+ "schemas": [
+ "urn:ietf:params:scim:schemas:core:2.0:User"
+ ],
+ "id": "0b1d561c-21ff-4092-beab-8154b17f82f2",
+ "active": true,
+ "name.formatted": "Test User",
+ "userName": "username",
+ "meta": { "resourceType":"User" },
+ "emails": [
+ {
+ "type": "work",
+ "value": "name@example.com",
+ "primary": true
+ }
+ ]
+}
+```
+
+## Create a SAML user
+
+```text
+POST /api/scim/v2/groups/:group_path/Users/
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:---------------|:----------|:----|:--------------------------|
+| `externalId` | string | yes | External UID of the user. |
+| `userName` | string | yes | Username of the user. |
+| `emails` | JSON string | yes | Work email. |
+| `name` | JSON string | yes | Name of the user. |
+| `meta` | string | no | Resource type (`User'). |
+
+Example request:
+
+```sh
+curl --verbose --request POST 'https://example.gitlab.com/api/scim/v2/groups/test_group/Users' --data '{"externalId":"test_uid","active":null,"userName":"username","emails":[{"primary":true,"type":"work","value":"name@example.com"}],"name":{"formatted":"Test User","familyName":"User","givenName":"Test"},"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"meta":{"resourceType":"User"}}' --header "Authorization: Bearer <your_scim_token>" --header "Content-Type: application/scim+json"
+```
+
+Example response:
+
+```json
+{
+ "schemas": [
+ "urn:ietf:params:scim:schemas:core:2.0:User"
+ ],
+ "id": "0b1d561c-21ff-4092-beab-8154b17f82f2",
+ "active": true,
+ "name.formatted": "Test User",
+ "userName": "username",
+ "meta": { "resourceType":"User" },
+ "emails": [
+ {
+ "type": "work",
+ "value": "name@example.com",
+ "primary": true
+ }
+ ]
+}
+```
+
+Returns a `201` status code if successful.
+
+## Update a single SAML user
+
+Fields that can be updated are:
+
+| SCIM/IdP field | GitLab field |
+|:----------|:--------|
+| id/externalId | extern_uid |
+| name.formatted | name |
+| emails\[type eq "work"\].value | email |
+| active | Identity removal if `active = false` |
+| userName | username |
+
+```text
+PATCH /api/scim/v2/groups/:group_path/Users/:id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | string | yes | External UID of the user. |
+| `group_path` | string | yes | Full path to the group. |
+| `Operations` | JSON string | yes | An [operations](#available-operations) expression. |
+
+Example request:
+
+```sh
+curl --verbose --request PATCH 'https://example.gitlab.com/api/scim/v2/groups/test_group/Users/f0b1d561c-21ff-4092-beab-8154b17f82f2' --data '{ "Operations": [{"op":"Add","path":"name.formatted","value":"New Name"}] }' --header "Authorization: Bearer <your_scim_token>" --header "Content-Type: application/scim+json"
+```
+
+Returns an empty response with a `204` status code if successful.
+
+## Remove a single SAML user
+
+Removes the user's SSO identity and group membership.
+
+```text
+DELETE /api/scim/v2/groups/:group_path/Users/:id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | string | yes | External UID of the user. |
+| `group_path` | string | yes | Full path to the group. |
+
+Example request:
+
+```sh
+curl --verbose --request DELETE 'https://example.gitlab.com/api/scim/v2/groups/test_group/Users/f0b1d561c-21ff-4092-beab-8154b17f82f2' --header "Authorization: Bearer <your_scim_token>" --header "Content-Type: application/scim+json"
+```
+
+Returns an empty response with a `204` status code if successful.
+
+## Available filters
+
+They match an expression as specified in [the RFC7644 filtering section](https://tools.ietf.org/html/rfc7644#section-3.4.2.2).
+
+| Filter | Description |
+| ----- | ----------- |
+| `eq` | The attribute matches exactly the specified value. |
+
+Example:
+
+```
+id eq a-b-c-d
+```
+
+## Available operations
+
+They perform an operation as specified in [the RFC7644 update section](https://tools.ietf.org/html/rfc7644#section-3.5.2).
+
+| Operator | Description |
+| ----- | ----------- |
+| `Replace` | The attribute's value is updated. |
+| `Add` | The attribute has a new value. |
+
+Example:
+
+```json
+{ "op": "Add", "path": "name.formatted", "value": "New Name" }
+```
diff --git a/doc/api/search.md b/doc/api/search.md
index aa601648b2c..da81c8321c9 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -17,7 +17,9 @@ GET /search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs.
+Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs, users.
+
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
The response depends on the requested scope.
@@ -253,7 +255,7 @@ Example response:
### Scope: snippet_blobs
```bash
-curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blobs&search=test
```
Example response:
@@ -281,6 +283,119 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
## Group Search API
Search within the specified group.
@@ -297,7 +412,9 @@ GET /groups/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones.
+Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
+
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
The response depends on the requested scope.
@@ -499,6 +616,119 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
## Project Search API
Search within the specified project.
@@ -515,7 +745,7 @@ GET /projects/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs.
+Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
The response depends on the requested scope.
@@ -828,4 +1058,25 @@ Example response:
]
```
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
[ce-41763]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41763
diff --git a/doc/api/services.md b/doc/api/services.md
index 9275a1ccda8..898cfad7254 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -449,6 +449,45 @@ Get Hangouts Chat service settings for a project.
GET /projects/:id/services/hangouts-chat
```
+## HipChat
+
+Private group chat and IM
+
+### Create/Edit HipChat service
+
+Set HipChat service for a project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | true | Room token |
+| `color` | string | false | The room color |
+| `notify` | boolean | false | Enable notifications |
+| `room` | string | false |Room name or ID |
+| `api_version` | string | false | Leave blank for default (v2) |
+| `server` | string | false | Leave blank for default. For example, `https://hipchat.example.com`. |
+
+### Delete HipChat service
+
+Delete HipChat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
+
+### Get HipChat service settings
+
+Get HipChat service settings for a project.
+
+```
+GET /projects/:id/services/hipchat
+```
+
## Irker (IRC gateway)
Send IRC messages, on update, to a list of recipients through an Irker gateway.
@@ -505,10 +544,9 @@ GET /projects/:id/services/jira
Set JIRA service for a project.
-> **Notes:**
-> - Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
-> `project_url` are replaced by `project_key`, `url`. If you are using an
-> older version, [follow this documentation][old-jira-api].
+> Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
+> `project_url` are replaced by `url`. If you are using an
+> older version, [follow this documentation][old-jira-api].
```
PUT /projects/:id/services/jira
@@ -519,11 +557,11 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project. For example, `https://jira.example.com`. |
-| `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
+| `api_url` | string | no | The base URL to the JIRA instance API. Web URL value will be used if not set. For example, `https://jira-api.example.com`. |
| `username` | string | yes | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | yes | The password of the user created to be used with GitLab/JIRA. |
| `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). |
-| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
+| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
### Delete JIRA service
@@ -716,6 +754,7 @@ Parameters:
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
| `notify_only_broken_pipelines` | boolean | no | Notify only broken pipelines |
+| `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28271)) |
### Delete Pipeline-Emails service
@@ -1062,6 +1101,75 @@ Get JetBrains TeamCity CI service settings for a project.
GET /projects/:id/services/teamcity
```
+## Jenkins CI **[STARTER]**
+
+A continuous integration and build server
+
+### Create/Edit Jenkins CI service
+
+Set Jenkins CI service for a project.
+
+```
+PUT /projects/:id/services/jenkins
+```
+
+Parameters:
+
+- `jenkins_url` (**required**) - Jenkins URL like http://jenkins.example.com
+- `project_name` (**required**) - The URL-friendly project name. Example: my_project_name
+- `username` (optional) - A user with access to the Jenkins server, if applicable
+- `password` (optional) - The password of the user
+
+### Delete Jenkins CI service
+
+Delete Jenkins CI service for a project.
+
+```
+DELETE /projects/:id/services/jenkins
+```
+
+### Get Jenkins CI service settings
+
+Get Jenkins CI service settings for a project.
+
+```
+GET /projects/:id/services/jenkins
+```
+
+## Jenkins CI (Deprecated) Service
+
+A continuous integration and build server
+
+### Create/Edit Jenkins CI (Deprecated) service
+
+Set Jenkins CI (Deprecated) service for a project.
+
+```
+PUT /projects/:id/services/jenkins-deprecated
+```
+
+Parameters:
+
+- `project_url` (**required**) - Jenkins project URL like http://jenkins.example.com/job/my-project/
+- `multiproject_enabled` (optional) - Multi-project mode is configured in Jenkins GitLab Hook plugin
+- `pass_unstable` (optional) - Unstable builds will be treated as passing
+
+### Delete Jenkins CI (Deprecated) service
+
+Delete Jenkins CI (Deprecated) service for a project.
+
+```
+DELETE /projects/:id/services/jenkins-deprecated
+```
+
+### Get Jenkins CI (Deprecated) service settings
+
+Get Jenkins CI (Deprecated) service settings for a project.
+
+```
+GET /projects/:id/services/jenkins-deprecated
+```
+
[jira-doc]: ../user/project/integrations/jira.md
[old-jira-api]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/api/services.md#jira
@@ -1102,3 +1210,39 @@ GET /projects/:id/services/mock-ci
```
[11435]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11435
+
+## YouTrack
+
+YouTrack issue tracker
+
+### Create/Edit YouTrack service
+
+Set YouTrack service for a project.
+
+```
+PUT /projects/:id/services/youtrack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `issues_url` | string | true | Issue url |
+| `project_url` | string | true | Project url |
+| `description` | string | false | Description |
+
+### Delete YouTrack Service
+
+Delete YouTrack service for a project.
+
+```
+DELETE /projects/:id/services/youtrack
+```
+
+### Get YouTrack Service Settings
+
+Get YouTrack service settings for a project.
+
+```
+GET /projects/:id/services/youtrack
+```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 2e0a2a09133..c2a1f7feefd 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -161,7 +161,7 @@ are listed in the descriptions of the relevant settings.
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `enforce_terms` | boolean | no | (**If enabled, requires:** `terms`) Enforce application ToS to all users. |
-| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday and `1` for Monday. |
+| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday, `1` for Monday, and `6` for Saturday. |
| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index f90447e124e..1ce0b1e7a62 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -165,15 +165,15 @@ Parameters:
|:--------------|:-------|:---------|:---------------------------------------------------|
| `title` | string | yes | Title of a snippet. |
| `file_name` | string | yes | Name of a snippet file. |
-| `content` | string | yes | Content of a snippet. |
+| `code` | string | yes | Content of a snippet. |
| `description` | string | no | Description of a snippet. |
-| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
+| `visibility` | string | yes | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request POST \
- --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
+ --data '{"title": "This is a snippet", "code": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets
@@ -222,14 +222,14 @@ Parameters:
| `title` | string | no | Title of a snippet. |
| `file_name` | string | no | Name of a snippet file. |
| `description` | string | no | Description of a snippet. |
-| `content` | string | no | Content of a snippet. |
+| `code` | string | no | Content of a snippet. |
| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request PUT \
- --data '{"title": "foo", "content": "bar"}' \
+ --data '{"title": "foo", "code": "bar"}' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets/1
diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md
index e88d536282a..188989bc94e 100644
--- a/doc/api/suggestions.md
+++ b/doc/api/suggestions.md
@@ -24,8 +24,6 @@ Example response:
```json
{
"id": 36,
- "from_original_line": 10,
- "to_original_line": 10,
"from_line": 10,
"to_line": 10,
"appliable": false,
diff --git a/doc/api/users.md b/doc/api/users.md
index b0977810120..47028c679b8 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -33,7 +33,7 @@ GET /users
]
```
-You can also search for users by email or username with: `/users?search=John`
+You can also search for users by name or primary email using `?search=`. For example. `/users?search=John`.
In addition, you can lookup users by username:
@@ -256,7 +256,8 @@ Parameters:
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
- "private_profile": false
+ "private_profile": false,
+ "highest_role":10
}
```
diff --git a/doc/api/vulnerabilities.md b/doc/api/vulnerabilities.md
new file mode 100644
index 00000000000..390d0966244
--- /dev/null
+++ b/doc/api/vulnerabilities.md
@@ -0,0 +1,113 @@
+# Vulnerabilities API **[ULTIMATE]**
+
+Every API call to vulnerabilities must be authenticated.
+
+If a user is not a member of a project and the project is private, a `GET`
+request on that project will result in a `404` status code.
+
+CAUTION: **Caution:**
+This API is in an alpha stage and considered unstable.
+The response payload may be subject to change or breakage
+across GitLab releases.
+
+## Vulnerabilities pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
+## List project vulnerabilities
+
+List all of a project's vulnerabilities.
+
+```
+GET /projects/:id/vulnerabilities
+GET /projects/:id/vulnerabilities?report_type=sast
+GET /projects/:id/vulnerabilities?report_type=container_scanning
+GET /projects/:id/vulnerabilities?report_type=sast,dast
+GET /projects/:id/vulnerabilities?scope=all
+GET /projects/:id/vulnerabilities?scope=dismissed
+GET /projects/:id/vulnerabilities?severity=high
+GET /projects/:id/vulnerabilities?confidence=unknown,experimental
+```
+
+| 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. |
+| `report_type` | Array[string] | no | Returns vulnerabilities belonging to specified report type. Valid values: `sast`, `dast`, `dependency_scanning`, or `container_scanning`. |
+| `scope` | string | no | Returns vulnerabilities for the given scope: `all` or `dismissed`. Defaults to `dismissed` |
+| `severity` | Array[string] | no | Returns vulnerabilities belonging to specified severity level: `undefined`, `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all' |
+| `confidence` | Array[string] | no | Returns vulnerabilities belonging to specified confidence level: `undefined`, `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/vulnerabilities
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": null,
+ "report_type": "dependency_scanning",
+ "name": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
+ "severity": "unknown",
+ "confidence": "undefined",
+ "scanner": {
+ "external_id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "identifiers": [
+ {
+ "external_type": "gemnasium",
+ "external_id": "9952e574-7b5b-46fa-a270-aeb694198a98",
+ "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
+ "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
+ },
+ {
+ "external_type": "cve",
+ "external_id": "CVE-2017-11429",
+ "name": "CVE-2017-11429",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
+ }
+ ],
+ "project_fingerprint": "fa6f5b6c5d240b834ac5e901dc69f9484cef89ec",
+ "create_vulnerability_feedback_issue_path": "/tests/yarn-remediation-test/vulnerability_feedback",
+ "create_vulnerability_feedback_merge_request_path": "/tests/yarn-remediation-test/vulnerability_feedback",
+ "create_vulnerability_feedback_dismissal_path": "/tests/yarn-remediation-test/vulnerability_feedback",
+ "project": {
+ "id": 31,
+ "name": "yarn-remediation-test",
+ "full_path": "/tests/yarn-remediation-test",
+ "full_name": "tests / yarn-remediation-test"
+ },
+ "dismissal_feedback": null,
+ "issue_feedback": null,
+ "merge_request_feedback": null,
+ "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
+ "links": [
+ {
+ "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
+ },
+ {
+ "url": "https://www.kb.cert.org/vuls/id/475445"
+ },
+ {
+ "url": "https://github.com/Clever/saml2/issues/127"
+ }
+ ],
+ "location": {
+ "file": "yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ }
+ },
+ "solution": "Upgrade to fixed version.\r\n",
+ "blob_path": "/tests/yarn-remediation-test/blob/cc6c4a0778460455ae5d16ca7025ca9ca1ca75ac/yarn.lock"
+ }
+]
+```
diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md
index 6a590b53727..ed9fd135e7c 100644
--- a/doc/articles/artifactory_and_gitlab/index.md
+++ b/doc/articles/artifactory_and_gitlab/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../ci/examples/artifactory_and_gitlab/index.md'
+---
+
This document was moved to [another location](../../ci/examples/artifactory_and_gitlab/index.md)
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index a8320c12e6b..8e2e54711e7 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md'
+---
+
This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md).
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
new file mode 100644
index 00000000000..3e6f3130437
--- /dev/null
+++ b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md'
+---
+
+This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md).
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
index 3e6003a33b7..62598101895 100644
--- a/doc/articles/how_to_install_git/index.md
+++ b/doc/articles/how_to_install_git/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/git/how_to_install_git/index.md'
+---
+
This document was moved to [another location](../../topics/git/how_to_install_git/index.md).
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 87ee17bb6de..162db11d6ac 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -4,8 +4,8 @@ comments: false
# Technical articles list (deprecated)
-[Technical articles](../development/documentation/index.md#technical-articles) are
-topic-related documentation, written with an user-friendly approach and language, aiming
+Technical articles are
+topic-related documentation, written with a user-friendly approach and language, aiming
to provide the community with guidance on specific processes to achieve certain objectives.
The list of technical articles was [deprecated](https://gitlab.com/gitlab-org/gitlab-ce/issues/41138) in favor of having them linked from their topic-related documentation:
diff --git a/doc/articles/laravel_with_gitlab_and_envoy/index.md b/doc/articles/laravel_with_gitlab_and_envoy/index.md
index b092cdb0f7a..fa4f6243410 100644
--- a/doc/articles/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/articles/laravel_with_gitlab_and_envoy/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../ci/examples/laravel_with_gitlab_and_envoy/index.md'
+---
+
This document was moved to [another location](../../ci/examples/laravel_with_gitlab_and_envoy/index.md).
diff --git a/doc/articles/numerous_undo_possibilities_in_git/index.md b/doc/articles/numerous_undo_possibilities_in_git/index.md
index 3f46ee9a5e6..83aac82db4e 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/index.md
+++ b/doc/articles/numerous_undo_possibilities_in_git/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/git/numerous_undo_possibilities_in_git/index.md'
+---
+
This document was moved to [another location](../../topics/git/numerous_undo_possibilities_in_git/index.md).
diff --git a/doc/articles/runner_autoscale_aws/index.md b/doc/articles/runner_autoscale_aws/index.md
index e2667aebc5f..fb769731256 100644
--- a/doc/articles/runner_autoscale_aws/index.md
+++ b/doc/articles/runner_autoscale_aws/index.md
@@ -1 +1,5 @@
+---
+redirect_to: 'https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html'
+---
+
This document was moved to [another location](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html).
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 4c3c9920b93..635cce13b4e 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -1,114 +1,140 @@
---
comments: false
description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Integration, Continuous Deployment, and Continuous Delivery toolset to build, test, and deploy your application."
+type: index
---
-# GitLab Continuous Integration (GitLab CI/CD)
+# GitLab CI/CD
-GitLab provides tools for continuously integrating and delivering code.
+GitLab CI/CD is a tool built into GitLab for software development
+through the [continuous methodologies](introduction/index.md#introduction-to-cicd-methodologies):
-Within the [entire DevOps lifecycle](../README.md#the-entire-devops-lifecycle), GitLab CI/CD spans
-the [Verify (CI)](../README.md#verify) and [Release (CD)](../README.md#release) stages.
+- Continuous Integration (CI)
+- Continuous Delivery (CD)
+- Continuous Deployment (CD)
## Overview
-CI/CD is a vast area, so GitLab provides documentation for all levels of expertise. Consult the following table to find the right documentation for you:
+Continuous Integration works by pushing small code chunks to your
+application's code base hosted in a Git repository, and, to every
+push, run a pipeline of scripts to build, test, and validate the
+code changes before merging them into the main branch.
-| Level of expertise | Resource |
-|:------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------|
-| New to the concepts of CI and CD | For a high-level overview, see the [GitLab Continuous Integration & Delivery](https://about.gitlab.com/product/continuous-integration/) product page. |
-| Familiar with the purpose of CI/CD | Delve into GitLab CI/CD by continuing down the page, starting with our [introduction](#introduction). |
-| Familiar with GitLab CI/CD concepts | After getting familiar with GitLab CI/CD, let us walk you through a simple example in our [quick start guide](quick_start/README.md). |
-| A GitLab CI/CD expert | Jump straight to our [`.gitlab.yml`](yaml/README.md) reference. |
+Continuous Delivery and Deployment consist of a step further CI,
+deploying your application to production at every
+push to the default branch of the repository.
-## Introduction
+These methodologies allow you to catch bugs and errors early in
+the development cycle, ensuring that all the code deployed to
+production complies with the code standards you established for
+your app.
-The following introduces the process of continuous integration (CI) and continuous delivery (CD):
+For a complete overview of these methodologies and GitLab CI/CD,
+read the [Introduction to CI/CD with GitLab](introduction/index.md).
-![Pipeline graph](img/cicd_pipeline_infograph.png)
+## Getting started
-In this illustration:
+GitLab CI/CD is configured by a file called `.gitlab-ci.yml` placed
+at the repository's root. The scripts set in this file are executed
+by the [GitLab Runner](https://docs.gitlab.com/runner/).
-- New code is combined with existing code through a commit to a project's [repository](../user/project/repository/index.md).
-- The newly combined code is sent to a CI [pipeline](pipelines.md) where:
- - The code is [built](../user/project/pipelines/job_artifacts.md).
- - Unit and integration tests are run over the built code.
-- Assuming the build and tests are successful, a CD pipeline is triggered to allow for:
- - Review using [Review Apps](review_apps/index.md).
- - Deploying to configured [environments](environments.md).
+To get started with GitLab CI/CD, we recommend you read through
+the following documents:
-The benefits of CI/CD are vast, allowing automation to be an integral part of your workflow for testing, building, deploying, and monitoring your code.
+- [How GitLab CI/CD works](introduction/index.md#how-gitlab-cicd-works).
+- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
+- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
-Because CI and CD with GitLab is broad topic with many possibilities, the rest of this section provides
-links to topics and resources needed to make use of GitLab CI/CD.
+You can also get started by using one of the
+[`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates)
+available through the UI. You can use them by creating a new file,
+choosing a template that suits your application, and adjusting it
+to your needs:
-## Essentials
+![Use a .gitlab-ci.yml template](img/add_file_template_11_10.png)
-The following documentation provides the minimum required knowledge for making use of GitLab CI/CD:
+For a broader overview, see the [CI/CD getting started](quick_start/README.md) guide.
-| Topic | Description |
-|:------------------------------------------------------------------------|:---------------------------------------------------------|
-| [Getting started with GitLab CI/CD](quick_start/README.md) | Outlines the first steps for configuring GitLab CI/CD. |
-| [Introduction to pipelines and jobs](pipelines.md) | Provides an overview of GitLab CI/CD and jobs. |
-| [Configuration of your pipelines with `.gitlab-ci.yml`](yaml/README.md) | A comprehensive reference for the `.gitlab-ci.yml` file. |
+Once you're familiar with how GitLab CI/CD works, see the
+[`. gitlab-ci.yml` full reference](yaml/README.md)
+for all the attributes you can set and use.
NOTE: **Note:**
-Familiarity with [GitLab Runner](https://docs.gitlab.com/runner/) is useful because it is
-responsible for running the jobs in your CI/CD pipeline. On GitLab.com, shared Runners are enabled
-by default so you don't need to set up anything to get started.
-
-### Auto DevOps
-
-An alternative to manually configuring CI/CD, GitLab supports [Auto DevOps](../topics/autodevops/index.md),
-which:
-
-- Provides simplified setup and execution of CI/CD.
-- Allows GitLab to automatically detect, build, test, deploy, and monitor your applications.
-
-## Basic usage
-
-With basic knowledge of how GitLab CI/CD works, the following documentation extends your knowledge
-into more features:
-
-| Topic | Description |
-|:-------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------|
-| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
-| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
-| [User](../user/permissions.md#gitlab-ci) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
-| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
-
-## Advanced usage
-
-Once you get familiar with the basics of GitLab CI/CD, consult the following documentation to make
-use of advanced features:
-
-| Topic | Description |
-|:---------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|
-| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. |
-| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
-| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
-| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
-| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
-| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
-| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
-| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
-| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
-| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
-| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
-
-### GitLab Pages
-
-GitLab CI/CD can be used to build and host static websites. For more information, see the
-documentation on [GitLab Pages](../user/project/pages/index.md).
+GitLab CI/CD and [shared runners](runners/README.md#shared-specific-and-group-runners) are enabled in GitLab.com and available for all users, limited only to the [user's pipelines quota](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#extra-shared-runners-pipeline-minutes-quota).
+
+## Configuration
+
+GitLab CI/CD supports numerous configuration options:
+
+| Configuration | Description |
+|:--------------|:-------------|
+| [Pipelines](pipelines.md) | Structure your CI/CD process through pipelines. |
+| [Environment variables](variables/README.md) | Reuse values based on a variable/value key pair. |
+| [Environments](environments.md) | Deploy your application to different environments (e.g., staging, production). |
+| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Output, use, and reuse job artifacts. |
+| [Cache dependencies](caching/index.md) | Cache your dependencies for a faster execution. |
+| [Schedule pipelines](../user/project/pipelines/schedules.md) | Schedule pipelines to run as often as you need. |
+| [Custom path for `.gitlab-ci.yml`](../user/project/pipelines/settings.md#custom-ci-config-path) | Define a custom path for the CI/CD configuration file. |
+| [Git submodules for CI/CD](git_submodules.md) | Configure jobs for using Git submodules.|
+| [SSH keys for CI/CD](ssh_keys/README.md) | Using SSH keys in your CI pipelines. |
+| [Pipelines triggers](triggers/README.md) | Trigger pipelines through the API. |
+| [Integrate with Kubernetes clusters](../user/project/clusters/index.md) | Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes cluster. |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | Configure your own GitLab Runners to execute your scripts. |
+| [Optimize GitLab and Runner for large repositories](large_repositories/index.md) | Recommended strategies for handling large repos. |
+| [`.gitlab-ci.yml` full reference](yaml/README.md) | All the attributes you can use with GitLab CI/CD. |
+
+Note that certain operations can only be performed according to the
+[user](../user/permissions.md#gitlab-cicd-permissions) and [job](../user/permissions.md#job-permissions) permissions.
+
+## Feature set
+
+Use the vast GitLab CI/CD to easily configure it for specific purposes.
+Its feature set is listed on the table below according to DevOps stages.
+
+| Feature | Description |
+|:--------|:------------|
+| **Configure** ||
+| [Auto DevOps](../topics/autodevops/index.md) | Set up your app's entire lifecycle. |
+| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
+|---+---|
+| **Verify** ||
+| [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html) | Quickly determine the performance impact of pending code changes. |
+| [CI services](services/README.md) | Link Docker containers with your base image.|
+| [Code Quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) **[STARTER]** | Analyze your source code quality. |
+| [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. |
+| [Interactive Web Terminals](interactive_web_terminal/index.md) **[CORE ONLY]** | Open an interactive web terminal to debug the running jobs. |
+| [JUnit tests](junit_test_reports.md) | Identify script failures directly on merge requests. |
+| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. |
+|---+---|
+| **Release** ||
+| [Auto Deploy](../topics/autodevops/index.md#auto-deploy) | Deploy your application to a production environment in a Kubernetes cluster. |
+| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. |
+| [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) **[PREMIUM]** | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. |
+| [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. |
+| [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html) **[PREMIUM]** | Deploy your features behind Feature Flags. |
+| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. |
+| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. |
+| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. |
+|---+---|
+| **Secure** ||
+| [Container Scanning](https://docs.gitlab.com/ee/ci/examples/container_scanning.html) **[ULTIMATE]** | Check your Docker containers for known vulnerabilities.|
+| [Dependency Scanning](https://docs.gitlab.com/ee/ci/examples/dependency_scanning.html) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
+| [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/) **[ULTIMATE]** | Search your project dependencies for their licenses. |
+| [Security Test reports](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate) **[ULTIMATE]** | Check for app vulnerabilities. |
## Examples
-Check out the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on
-setting up your CI/CD pipeline for various programming languages, frameworks, and operating systems.
+GitLab provides examples of configuring GitLab CI/CD in the form of:
+
+- A collection of [examples and other resources](examples/README.md).
+- Example projects that are available at the [`gitlab-examples`](https://gitlab.com/gitlab-examples) group. For example, see:
+ - [`multi-project-pipelines`](https://gitlab.com/gitlab-examples/multi-project-pipelines) for examples of implementing multi-project pipelines.
+ - [`review-apps-nginx`](https://gitlab.com/gitlab-examples/review-apps-nginx/) provides an example of using Review Apps.
-## Administration
+## Administration **[CORE ONLY]**
-As a GitLab administrator, you can change the default behavior of GitLab CI/CD for:
+As a GitLab administrator, you can change the default behavior
+of GitLab CI/CD for:
- An [entire GitLab instance](../user/admin_area/settings/continuous_integration.md).
- Specific projects, using [pipelines settings](../user/project/pipelines/settings.md).
@@ -118,80 +144,51 @@ See also:
- [How to enable or disable GitLab CI/CD](enable_or_disable_ci.md).
- Other [CI administration settings](../administration/index.md#continuous-integration-settings).
-## Using Docker
-
-Docker is commonly used with GitLab CI/CD. Learn more about how to to accomplish this with the following
-documentation:
+## References
-| Topic | Description |
-|:-------------------------------------------------------------------------|:-------------------------------------------------------------------------|
-| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. |
-| [Building Docker images with GitLab CI/CD](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. |
-
-Related topics include:
-
-- [Docker integration](docker/README.md).
-- [CI services (linked Docker containers)](services/README.md).
-- [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) (article).
-
-## Further resources
-
-This section provides further resources to help you get familiar with GitLab CI/CD.
-
-### Articles
-
-The following table provides a list of articles about CI/CD, sorted in reverse chronological order of publish date:
-
-| Publish Date | Article |
-|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 2017-07-13 | [Making CI easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/). |
-| 2017-05-22 | [Fast and natural continuous integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/). |
-| 2016-11-22 | [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/). |
-| 2016-08-26 | [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). |
-| 2016-08-05 | [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/). |
-| 2016-07-29 | [GitLab CI: Run jobs sequentially, in parallel or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/). |
-| 2016-06-09 | [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) |
-| 2016-05-23 | [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). |
-| 2016-05-05 | [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/) |
-| 2016-04-19 | [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/) |
-| 2015-03-01 | [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/). |
-| 2015-12-14 | [Getting started with GitLab and GitLab CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/). |
-
-### Videos
-
-The following table provides a list of videos about CI/CD, sorted in reverse chronological order of publish date:
+### Why GitLab CI/CD?
-| Publish Date | Video |
-|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 2017-07-17 | [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195). |
-| 2017-03-13 | [Demo: CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/). |
-| 2016-04-20 | [Webcast Recording and Slides: Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/). |
+The following articles explain reasons to use GitLab CI/CD
+for your CI/CD infrastructure:
-In addition, the following third-party videos are available:
+- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
+- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
-- [Intégration continue avec GitLab (September 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s).
-- [GitLab CI for Minecraft Plugins (July 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8).
+See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation.
-### Example Projects
+### Breaking changes
-[`review-apps-nginx`](https://gitlab.com/gitlab-examples/review-apps-nginx/) provides an example of using Review Apps.
+As GitLab CI/CD has evolved, certain breaking changes have
+been necessary. These are:
-Other example projects are available at the [`gitlab-examples`](https://gitlab.com/gitlab-examples) group.
+#### 12.0
-### Why GitLab CI/CD?
+- [Use refspec to clone/fetch git
+ repository](https://gitlab.com/gitlab-org/gitlab-runner/issues/4069).
+- [Old cache
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4070).
+- [Old metrics server
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4072).
+- [Remove
+ `FF_K8S_USE_ENTRYPOINT_OVER_COMMAND`](https://gitlab.com/gitlab-org/gitlab-runner/issues/4073).
+- [Remove Linux distributions that reach
+ EOL](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1130).
+- [Update command line API for helper
+ images](https://gitlab.com/gitlab-org/gitlab-runner/issues/4013).
+- [Remove old `git clean`
+ flow](https://gitlab.com/gitlab-org/gitlab-runner/issues/4175).
-The following articles explain reasons why you might use GitLab CI/CD for your CI/CD infrastructure:
+#### 11.0
-- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/).
-- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/).
+- No breaking changes.
-See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation.
+#### 10.0
-## Breaking changes
+- No breaking changes.
-As GitLab CI/CD has evolved, certain breaking changes have been necessary. These are:
+#### 9.0
-- [CI variables renaming for GitLab 9.0](variables/README.md#gitlab-90-renaming). Read about the
+- [CI variables renaming for GitLab 9.0](variables/deprecated_variables.md#gitlab-90-renamed-variables). Read about the
deprecated CI variables and what you should use for GitLab 9.0+.
- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md).
See what changed in GitLab 8.12 and how that affects your jobs.
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 985ec4b972c..5221cbf8609 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/autodevops/index.md#auto-deploy'
+---
+
This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy).
diff --git a/doc/ci/autodeploy/quick_start_guide.md b/doc/ci/autodeploy/quick_start_guide.md
index 985ec4b972c..5221cbf8609 100644
--- a/doc/ci/autodeploy/quick_start_guide.md
+++ b/doc/ci/autodeploy/quick_start_guide.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/autodevops/index.md#auto-deploy'
+---
+
This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy).
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 22b3872025f..b63659c1878 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/pipelines/job_artifacts.md'
+---
+
This document was moved to [pipelines/job_artifacts.md](../../user/project/pipelines/job_artifacts.md).
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index e079483e2b5..9a5a3624c73 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -1,3 +1,7 @@
+---
+type: index, concepts, howto
+---
+
# Cache dependencies in GitLab CI/CD
GitLab CI/CD provides a caching mechanism that can be used to save time
@@ -60,7 +64,7 @@ In summary:
- Caches are disabled if not defined globally or per job (using `cache:`).
- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally.
-- Caches can be used by subsequent pipelines of that very same job (a script in
+- Caches can be used by subsequent pipelines of that same job (a script in
a stage) in which the cache was created (if not defined globally).
- Caches are stored where the Runner is installed **and** uploaded to S3 if
[distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching).
@@ -87,7 +91,7 @@ cache, when declaring `cache` in your jobs, use one or a mix of the following:
that share their cache.
- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
that will be only available to a particular project.
-- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (e.g.,
+- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (for example,
different caches on each branch). For that, you can take advantage of the
[CI/CD predefined variables](../variables/README.md#predefined-environment-variables).
@@ -100,7 +104,7 @@ From the perspective of the Runner, in order for cache to work effectively, one
of the following must be true:
- Use a single Runner for all your jobs.
-- Use multiple Runners (in autoscale mode or not) that use.
+- Use multiple Runners (in autoscale mode or not) that use
[distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching),
where the cache is stored in S3 buckets (like shared Runners on GitLab.com).
- Use multiple Runners (not in autoscale mode) of the same architecture that
@@ -289,7 +293,7 @@ jobs inherit it. Gems are installed in `vendor/ruby/` and are cached per-branch:
#
# https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
#
-image: ruby:2.5
+image: ruby:2.6
# Cache gems in between builds
cache:
@@ -420,7 +424,7 @@ mismatch and a few ideas how to fix it.
Let's explore some examples.
----
+#### Examples
Let's assume you have only one Runner assigned to your project, so the cache
will be stored in the Runner's machine by default. If two jobs, A and B,
@@ -462,8 +466,6 @@ job B:
To fix that, use different `keys` for each job.
----
-
In another case, let's assume you have more than one Runners assigned to your
project, but the distributed cache is not enabled. We want the second time the
pipeline is run, `job A` and `job B` to re-use their cache (which in this case
@@ -526,3 +528,15 @@ Behind the scenes, this works by increasing a counter in the database, and the
value of that counter is used to create the key for the cache by appending an
integer to it: `-1`, `-2`, etc. After a push, a new key is generated and the
old cache is not valid anymore.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index 6ad1df7bb2a..241134783da 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -1,18 +1,25 @@
+---
+type: index, concepts, howto
+---
+
# GitLab ChatOps
> **Notes:**
>
-> * [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
>
-> * ChatOps is currently in alpha, with some important features missing like access control.
+> - ChatOps is currently in alpha, with some important features missing like access control.
GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
## How it works
-GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md).
+GitLab ChatOps is built upon two existing features:
+
+- [GitLab CI/CD](../README.md).
+- [Slack Slash Commands](../../user/project/integrations/slack_slash_commands.md).
-A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.html#predefined-variables-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
+A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.md#predefined-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
After the job has finished, its output is sent back to Slack provided it has completed within 30 minutes. If a job takes more than 30 minutes to run it must use the Slack API to manually send data back to a channel.
@@ -22,9 +29,9 @@ After the job has finished, its output is sent back to Slack provided it has com
Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs:
-* It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
-* If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
-* It is important to keep in mind that there is very limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](https://docs.gitlab.com/ce/ci/variables/README.html#priority-of-variables).
+- It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
+- If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
+- It is important to keep in mind that there is limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.html#priority-of-environment-variables).
### Controlling the ChatOps reply
@@ -59,3 +66,15 @@ You can find and download the official GitLab ChatOps icon here.
![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png)
[Download bigger image](img/gitlab-chatops-icon.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
new file mode 100644
index 00000000000..9126eb65f07
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
@@ -0,0 +1,166 @@
+---
+type: howto
+---
+
+# Using GitLab CI/CD with a Bitbucket Cloud repository **[PREMIUM]**
+
+GitLab CI/CD can be used with Bitbucket Cloud by:
+
+1. Creating a [CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html).
+1. Connecting your Git repository via URL.
+
+To use GitLab CI/CD with a Bitbucket Cloud repository:
+
+1. In GitLab create a **CI/CD for external repo**, select **Repo by URL** and
+ create the project.
+
+ ![Create project](img/external_repository.png)
+
+ GitLab will import the repository and enable [Pull Mirroring][pull-mirroring].
+
+1. In GitLab create a
+ [Personal Access Token](../../user/profile/personal_access_tokens.md)
+ with `api` scope. This will be used to authenticate requests from the web
+ hook that will be created in Bitbucket to notify GitLab of new commits.
+
+1. In Bitbucket, from **Settings > Webhooks**, create a new web hook to notify
+ GitLab of new commits.
+
+ The web hook URL should be set to the GitLab API to trigger pull mirroring,
+ using the Personal Access Token we just generated for authentication.
+
+ ```text
+ https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
+ ```
+
+ The web hook Trigger should be set to 'Repository Push'.
+
+ ![Bitbucket Cloud webhook](img/bitbucket_webhook.png)
+
+ After saving, test the web hook by pushing a change to your Bitbucket
+ repository.
+
+1. In Bitbucket, create an **App Password** from **Bitbucket Settings > App
+ Passwords** to authenticate the build status script setting commit build
+ statuses in Bitbucket. Repository write permissions are required.
+
+ ![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
+
+1. In GitLab, from **Settings > CI/CD > Environment variables**, add variables to allow
+ communication with Bitbucket via the Bitbucket API:
+
+ `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above.
+
+ `BITBUCKET_USERNAME`: the username of the Bitbucket account.
+
+ `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ.
+
+ `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ.
+
+1. In Bitbucket, add a script to push the pipeline status to Bitbucket.
+
+ > Note: changes made in GitLab will be overwritten by any changes made
+ > upstream in Bitbucket.
+
+ Create a file `build_status` and insert the script below and run
+ `chmod +x build_status` in your terminal to make the script executable.
+
+ ```bash
+ #!/usr/bin/env bash
+
+ # Push GitLab CI/CD build status to Bitbucket Cloud
+
+ if [ -z "$BITBUCKET_ACCESS_TOKEN" ]; then
+ echo "ERROR: BITBUCKET_ACCESS_TOKEN is not set"
+ exit 1
+ fi
+ if [ -z "$BITBUCKET_USERNAME" ]; then
+ echo "ERROR: BITBUCKET_USERNAME is not set"
+ exit 1
+ fi
+ if [ -z "$BITBUCKET_NAMESPACE" ]; then
+ echo "Setting BITBUCKET_NAMESPACE to $CI_PROJECT_NAMESPACE"
+ BITBUCKET_NAMESPACE=$CI_PROJECT_NAMESPACE
+ fi
+ if [ -z "$BITBUCKET_REPOSITORY" ]; then
+ echo "Setting BITBUCKET_REPOSITORY to $CI_PROJECT_NAME"
+ BITBUCKET_REPOSITORY=$CI_PROJECT_NAME
+ fi
+
+ BITBUCKET_API_ROOT="https://api.bitbucket.org/2.0"
+ BITBUCKET_STATUS_API="$BITBUCKET_API_ROOT/repositories/$BITBUCKET_NAMESPACE/$BITBUCKET_REPOSITORY/commit/$CI_COMMIT_SHA/statuses/build"
+ BITBUCKET_KEY="ci/gitlab-ci/$CI_JOB_NAME"
+
+ case "$BUILD_STATUS" in
+ running)
+ BITBUCKET_STATE="INPROGRESS"
+ BITBUCKET_DESCRIPTION="The build is running!"
+ ;;
+ passed)
+ BITBUCKET_STATE="SUCCESSFUL"
+ BITBUCKET_DESCRIPTION="The build passed!"
+ ;;
+ failed)
+ BITBUCKET_STATE="FAILED"
+ BITBUCKET_DESCRIPTION="The build failed."
+ ;;
+ esac
+
+ echo "Pushing status to $BITBUCKET_STATUS_API..."
+ curl --request POST $BITBUCKET_STATUS_API \
+ --user $BITBUCKET_USERNAME:$BITBUCKET_ACCESS_TOKEN \
+ --header "Content-Type:application/json" \
+ --silent \
+ --data "{ \"state\": \"$BITBUCKET_STATE\", \"key\": \"$BITBUCKET_KEY\", \"description\":
+ \"$BITBUCKET_DESCRIPTION\",\"url\": \"$CI_PROJECT_URL/-/jobs/$CI_JOB_ID\" }"
+ ```
+
+1. Still in Bitbucket, create a `.gitlab-ci.yml` file to use the script to push
+ pipeline success and failures to Bitbucket.
+
+ ```yaml
+ stages:
+ - test
+ - ci_status
+
+ unit-tests:
+ script:
+ - echo "Success. Add your tests!"
+
+ success:
+ stage: ci_status
+ before_script:
+ - ""
+ after_script:
+ - ""
+ script:
+ - BUILD_STATUS=passed BUILD_KEY=push ./build_status
+ when: on_success
+
+ failure:
+ stage: ci_status
+ before_script:
+ - ""
+ after_script:
+ - ""
+ script:
+ - BUILD_STATUS=failed BUILD_KEY=push ./build_status
+ when: on_failure
+ ```
+
+GitLab is now configured to mirror changes from Bitbucket, run CI/CD pipelines
+configured in `.gitlab-ci.yml` and push the status to Bitbucket.
+
+[pull-mirroring]: ../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
new file mode 100644
index 00000000000..612dcc93bc1
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -0,0 +1,134 @@
+---
+type: howto
+---
+
+# Using GitLab CI/CD with a GitHub repository **[PREMIUM]**
+
+GitLab CI/CD can be used with **GitHub.com** and **GitHub Enterprise** by
+creating a [CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html) to connect your GitHub repository to
+GitLab.
+
+NOTE: **Note:**
+To use **GitHub Enterprise** with **GitLab.com** you should use the
+manual method.
+
+## Connect with GitHub integration
+
+If the [GitHub integration](../../integration/github.md) has been enabled by your GitLab
+administrator:
+
+NOTE: **Note:**
+Due to a 10-token limitation on the [GitHub OAuth Implementation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#creating-multiple-tokens-for-oauth-apps),
+if you import more than 10 times, your oldest imported project's token will be
+revoked. See issue [#9147](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147)
+for more information.
+
+1. In GitLab create a **CI/CD for external repo** project and select
+ **GitHub**.
+
+ ![Create project](img/github_omniauth.png)
+
+1. Once authenticated, you will be redirected to a list of your repositories to
+ connect. Click **Connect** to select the repository.
+
+ ![Create project](img/github_repo_list.png)
+
+1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
+
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
+
+## Connect with Personal Access Token
+
+NOTE: **Note:**
+Personal access tokens can only be used to connect GitHub.com
+repositories to GitLab.
+
+If you are not using the [GitHub integration](../../integration/github.md), you can
+still perform a one-off authorization with GitHub to grant GitLab access your
+repositories:
+
+1. Open <https://github.com/settings/tokens/new> to create a **Personal Access
+ Token**. This token with be used to access your repository and push commit
+ statuses to GitHub.
+
+ The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
+ your project, update commit statuses, and create a web hook to notify
+ GitLab of new commits.
+
+1. In GitLab create a **CI/CD for external repo** project and select
+ **GitHub**.
+
+ ![Create project](img/github_omniauth.png)
+
+1. Paste the token into the **Personal access token** field and click **List
+ Repositories**. Click **Connect** to select the repository.
+
+1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
+
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
+
+## Connect manually
+
+If the [GitHub integration](../../integration/github.md) is not enabled, or is enabled
+for a different GitHub instance, you GitLab CI/CD can be manually enabled for
+your repository:
+
+1. In GitHub open <https://github.com/settings/tokens/new> create a **Personal
+ Access Token.** GitLab will use this token to access your repository and
+ push commit statuses.
+
+ Enter a **Token description** and update the scope to allow:
+
+ `repo` so that GitLab can access your project and update commit statuses
+
+1. In GitLab create a **CI/CD project** using the Git URL option and the HTTPS
+ URL for your GitHub repository. If your project is private, use the personal
+ access token you just created for authentication.
+
+ GitLab will automatically configure polling-based pull mirroring.
+
+1. Still in GitLab, enable the [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html)
+ from **Settings > Integrations.**
+
+ Check the **Active** checkbox to enable the integration, paste your
+ personal access token and HTTPS repository URL into the form, and **Save.**
+
+1. Still in GitLab create a **Personal Access Token** with `API` scope to
+ authenticate the GitHub web hook notifying GitLab of new commits.
+
+1. In GitHub from **Settings > Webhooks** create a web hook to notify GitLab of
+ new commits.
+
+ The web hook URL should be set to the GitLab API to
+ [trigger pull mirroring](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter),
+ using the GitLab personal access token we just created.
+
+ ```
+ https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
+ ```
+
+ ![Create web hook](img/github_push_webhook.png)
+
+1. In GitHub add a `.gitlab-ci.yml` to configure GitLab CI/CD.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/img/bitbucket_app_password.png b/doc/ci/ci_cd_for_external_repos/img/bitbucket_app_password.png
new file mode 100644
index 00000000000..ac5be3c3058
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/bitbucket_app_password.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/bitbucket_webhook.png b/doc/ci/ci_cd_for_external_repos/img/bitbucket_webhook.png
new file mode 100644
index 00000000000..0a3476d9035
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/bitbucket_webhook.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/ci_cd_for_external_repo.png b/doc/ci/ci_cd_for_external_repos/img/ci_cd_for_external_repo.png
new file mode 100644
index 00000000000..f068688146b
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/ci_cd_for_external_repo.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/external_repository.png b/doc/ci/ci_cd_for_external_repos/img/external_repository.png
new file mode 100644
index 00000000000..b850d91f56b
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/external_repository.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/github_omniauth.png b/doc/ci/ci_cd_for_external_repos/img/github_omniauth.png
new file mode 100644
index 00000000000..71a3a057a41
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/github_omniauth.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/github_push_webhook.png b/doc/ci/ci_cd_for_external_repos/img/github_push_webhook.png
new file mode 100644
index 00000000000..e8c17d664e1
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/github_push_webhook.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png b/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png
new file mode 100644
index 00000000000..73579dd3cf1
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png
Binary files differ
diff --git a/doc/ci/ci_cd_for_external_repos/index.md b/doc/ci/ci_cd_for_external_repos/index.md
new file mode 100644
index 00000000000..5de88412121
--- /dev/null
+++ b/doc/ci/ci_cd_for_external_repos/index.md
@@ -0,0 +1,39 @@
+---
+type: index, howto
+---
+
+# GitLab CI/CD for external repositories **[PREMIUM]**
+
+NOTE: **Note:**
+This feature [is available for free](https://about.gitlab.com/2019/03/21/six-more-months-ci-cd-github/) to
+GitLab.com users until September 22nd, 2019.
+
+>[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
+
+GitLab CI/CD can be used with:
+
+- [GitHub](github_integration.md).
+- [Bitbucket Cloud](bitbucket_integration.md).
+- Any other Git server.
+
+Instead of moving your entire project to GitLab, you can connect your
+external repository to get the benefits of GitLab CI/CD.
+
+Connecting an external repository will set up [repository mirroring][mirroring]
+and create a lightweight project where issues, merge requests, wiki, and
+snippets disabled. These features
+[can be re-enabled later][settings].
+
+To connect to an external repository:
+
+1. From your GitLab dashboard, click **New project**.
+1. Switch to the **CI/CD for external repo** tab.
+1. Choose **GitHub** or **Repo by URL**.
+1. The next steps are similar to the [import flow](../../user/project/import/index.md).
+
+![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
+
+[ee-4642]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4642
+[eep]: https://about.gitlab.com/pricing/
+[mirroring]: ../../workflow/repository_mirroring.md
+[settings]: ../../user/project/settings/index.md#sharing-and-permissions
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index 446f5b54f0c..f76471b50f2 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -1,9 +1,13 @@
---
comments: false
+type: index
---
# Docker integration
+GitLab CI/CD can be combined with [Docker](https://www.docker.com) to enable
+integration between the two.
+
The following documentation is available for using GitLab CI/CD with Docker:
- [Using Docker images](using_docker_images.md).
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index a462c75f2f5..d8068bbb7f0 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -1,23 +1,25 @@
+---
+type: concepts, howto
+---
+
# Building Docker images with GitLab CI/CD
GitLab CI/CD allows you to use Docker Engine to build and test docker-based projects.
-TIP: **Tip:**
-This also allows to you to use `docker-compose` and other docker-enabled tools.
One of the new trends in Continuous Integration/Deployment is to:
-1. Create an application image
-1. Run tests against the created image
-1. Push image to a remote registry
-1. Deploy to a server from the pushed image
+1. Create an application image.
+1. Run tests against the created image.
+1. Push image to a remote registry.
+1. Deploy to a server from the pushed image.
It's also useful when your application already has the `Dockerfile` that can be
used to create and test an image:
```bash
docker build -t my-image dockerfiles/
-docker run my-docker-image /script/to/run/tests
+docker run my-image /script/to/run/tests
docker tag my-image my-registry:5000/my-image
docker push my-registry:5000/my-image
```
@@ -75,7 +77,7 @@ GitLab Runner then executes job scripts as the `gitlab-runner` user.
- docker run my-docker-image /script/to/run/tests
```
-1. You can now use `docker` command and install `docker-compose` if needed.
+1. You can now use `docker` command (and **install** `docker-compose` if needed).
NOTE: **Note:**
By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
@@ -85,8 +87,10 @@ For more information please read [On Docker security: `docker` group considered
The second approach is to use the special docker-in-docker (dind)
[Docker image](https://hub.docker.com/_/docker/) with all tools installed
-(`docker` and `docker-compose`) and run the job script in context of that
-image in privileged mode.
+(`docker`) and run the job script in context of that
+image in privileged mode.
+
+NOTE: **Note:** `docker-compose` is not part of docker-in-docker (dind). In case you'd like to use `docker-compose` in your CI builds, please follow the (installation instructions for docker-compose)[https://docs.docker.com/compose/install/] provided by docker.
In order to do that, follow the steps:
@@ -113,7 +117,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
@@ -142,9 +146,12 @@ In order to do that, follow the steps:
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
#
- # Note that if you're using Kubernetes executor, the variable should be set to
- # tcp://localhost:2375 because of how Kubernetes executor connects services
+ # Note that if you're using the Kubernetes executor, the variable should be set to
+ # tcp://localhost:2375/ because of how the Kubernetes executor connects services
# to the job container
+ # DOCKER_HOST: tcp://localhost:2375/
+ #
+ # For non-Kubernetes executors, we use tcp://docker:2375/
DOCKER_HOST: tcp://docker:2375/
# When using dind, it's wise to use the overlayfs driver for
# improved performance.
@@ -224,7 +231,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = REGISTRATION_TOKEN
@@ -267,9 +274,9 @@ aware of the following implications:
create containers with specific names, they may conflict with each other.
- Sharing files and directories from the source repo into containers may not
work as expected since volume mounting is done in the context of the host
- machine, not the build container, e.g.:
+ machine, not the build container. For example:
- ```
+ ```sh
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
```
@@ -303,20 +310,19 @@ services:
- docker:dind
variables:
- CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
before_script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
script:
- - docker pull $CONTAINER_IMAGE:latest || true
- - docker build --cache-from $CONTAINER_IMAGE:latest --tag $CONTAINER_IMAGE:$CI_COMMIT_SHA --tag $CONTAINER_IMAGE:latest .
- - docker push $CONTAINER_IMAGE:$CI_COMMIT_SHA
- - docker push $CONTAINER_IMAGE:latest
+ - docker pull $CI_REGISTRY_IMAGE:latest || true
+ - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
+ - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
+ - docker push $CI_REGISTRY_IMAGE:latest
```
The steps in the `script` section for the `build` stage can be summed up to:
@@ -324,7 +330,7 @@ The steps in the `script` section for the `build` stage can be summed up to:
1. The first command tries to pull the image from the registry so that it can be
used as a cache for the `docker build` command.
1. The second command builds a Docker image using the pulled image as a
- cache (notice the `--cache-from $CONTAINER_IMAGE:latest` argument) if
+ cache (notice the `--cache-from $CI_REGISTRY_IMAGE:latest` argument) if
available, and tags it.
1. The last two commands push the tagged Docker images to the container registry
so that they may also be used as cache for subsequent builds.
@@ -335,7 +341,7 @@ NOTE: **Note:**
The shared Runners on GitLab.com use the `overlay2` driver by default.
By default, when using `docker:dind`, Docker uses the `vfs` storage driver which
-copies the filesystem on every run. This is a very disk-intensive operation
+copies the filesystem on every run. This is a disk-intensive operation
which can be avoided if a different driver is used, for example `overlay2`.
### Requirements
@@ -343,13 +349,13 @@ which can be avoided if a different driver is used, for example `overlay2`.
1. Make sure a recent kernel is used, preferably `>= 4.2`.
1. Check whether the `overlay` module is loaded:
- ```
+ ```sh
sudo lsmod | grep overlay
```
If you see no result, then it isn't loaded. To load it use:
- ```
+ ```sh
sudo modprobe overlay
```
@@ -357,7 +363,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
On Ubuntu systems, this is done by editing `/etc/modules`. Just add the
following line into it:
- ```
+ ```text
overlay
```
@@ -365,7 +371,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
You can enable the driver for each project individually by editing the project's `.gitlab-ci.yml`:
-```
+```yaml
variables:
DOCKER_DRIVER: overlay2
```
@@ -389,6 +395,7 @@ If you're running multiple Runners you will have to modify all configuration fil
## Using the GitLab Container Registry
> **Notes:**
+>
> - This feature requires GitLab 8.8 and GitLab Runner 1.2.
> - Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
@@ -420,14 +427,14 @@ and depend on the visibility of your project.
For all projects, mostly suitable for public ones:
-- **Using the special `gitlab-ci-token` user**: This user is created for you in order to
+- **Using the special `$CI_REGISTRY_USER` variable**: The user specified by this variable is created for you in order to
push to the Registry connected to your project. Its password is automatically
- set with the `$CI_JOB_TOKEN` variable. This allows you to automate building and deploying
+ set with the `$CI_REGISTRY_PASSWORD` variable. This allows you to automate building and deploying
your Docker images and has read/write access to the Registry. This is ephemeral,
so it's only valid for one job. You can use the following example as-is:
```sh
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
```
For private and internal projects:
@@ -435,8 +442,10 @@ For private and internal projects:
- **Using a personal access token**: You can create and use a
[personal access token](../../user/profile/personal_access_tokens.md)
in case your project is private:
- - For read (pull) access, the scope should be `read_registry`.
- - For read/write (pull/push) access, use `api`.
+
+ - For read (pull) access, the scope should be `read_registry`.
+ - For read/write (pull/push) access, use `api`.
+
Replace the `<username>` and `<access_token>` in the following example:
```sh
@@ -468,9 +477,9 @@ could look like:
DOCKER_DRIVER: overlay2
stage: build
script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
- - docker build -t registry.example.com/group/project/image:latest .
- - docker push registry.example.com/group/project/image:latest
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+ - docker build -t $CI_REGISTRY/group/project/image:latest .
+ - docker push $CI_REGISTRY/group/project/image:latest
```
You can also make use of [other variables](../variables/README.md) to avoid hardcoding:
@@ -485,7 +494,7 @@ variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
before_script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
@@ -525,7 +534,7 @@ variables:
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
@@ -566,3 +575,15 @@ deploy:
[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
[2fa]: ../../user/profile/account/two_factor_authentication.md
[pat]: ../../user/profile/personal_access_tokens.md
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 3314c76d234..d09efce7cc1 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
@@ -45,10 +49,10 @@ The `image` keyword is the name of the Docker image the Docker executor
will run to perform the CI tasks.
By default, the executor will only pull images from [Docker Hub][hub],
-but this can be configured in the `gitlab-runner/config.toml` by setting
+however this can be configured in the `gitlab-runner/config.toml` by setting
the [Docker pull policy][] to allow using local images.
-For more information about images and Docker Hub please read
+For more information about images and Docker Hub, please read
the [Docker Fundamentals][] documentation.
## What is a service
@@ -95,8 +99,8 @@ required for the CI/CD job to proceed and is accessed by network.
To make sure this works, the Runner:
-1. checks which ports are exposed from the container by default
-1. starts a special container that waits for these ports to be accessible
+1. Checks which ports are exposed from the container by default.
+1. Starts a special container that waits for these ports to be accessible.
When the second stage of the check fails, either because there is no opened port in the
service, or the service was not started properly before the timeout and the port is not
@@ -106,7 +110,7 @@ In most cases it will affect the job, but there may be situations when the job
will still succeed even if that warning was printed. For example:
- The service was started a little after the warning was raised, and the job is
- not using the linked service from the very beginning. In that case, when the
+ not using the linked service from the beginning. In that case, when the
job needed to access the service, it may have been already there waiting for
connections.
- The service container is not providing any networking service, but it's doing
@@ -143,9 +147,9 @@ job:
If you need to have `php`, `node` and `go` available for your script, you should
either:
-- choose an existing Docker image that contains all required tools, or
-- create your own Docker image, which will have all the required tools included
- and use that in your job
+- Choose an existing Docker image that contains all required tools.
+- Create your own Docker image, which will have all the required tools included
+ and use that in your job.
### Accessing the services
@@ -167,18 +171,18 @@ access to it from your build container under two hostnames to choose from:
- `tutum-wordpress`
- `tutum__wordpress`
->**Note:**
+NOTE: **Note:**
Hostnames with underscores are not RFC valid and may cause problems in 3rd party
applications.
The default aliases for the service's hostname are created from its image name
following these rules:
-- Everything after the colon (`:`) is stripped
+- Everything after the colon (`:`) is stripped.
- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
- is created
+ is created.
- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
- created (requires GitLab Runner v1.1.0 or higher)
+ created (requires GitLab Runner v1.1.0 or higher).
To override the default behavior, you can
[specify a service alias](#available-settings-for-services).
@@ -333,7 +337,7 @@ services:
```
The Runner will still start two containers using the `mysql:latest` image,
-but now each of them will also be accessible with the alias configured
+however now each of them will also be accessible with the alias configured
in `.gitlab-ci.yml` file.
### Setting a command for the service
@@ -408,8 +412,6 @@ you should check which one your Runner is using. Specifically:
The syntax of `image:entrypoint` is similar to [Dockerfile's `ENTRYPOINT`][entrypoint].
-----
-
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
@@ -443,7 +445,7 @@ image:
Look for the `[runners.docker]` section:
-```
+```toml
[runners.docker]
image = "ruby:2.1"
services = ["mysql:latest", "postgres:latest"]
@@ -469,11 +471,11 @@ image which is private and requires you to login into a private container regist
Let's also assume that these are the login credentials:
-| Key | Value |
-|----------|---------------------------|
-| registry | registry.example.com:5000 |
-| username | my_username |
-| password | my_password |
+| Key | Value |
+|:---------|:----------------------------|
+| registry | `registry.example.com:5000` |
+| username | `my_username` |
+| password | `my_password` |
To configure access for `registry.example.com:5000`, follow these steps:
@@ -500,7 +502,7 @@ To configure access for `registry.example.com:5000`, follow these steps:
bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
```
-1. Create a [variable] `DOCKER_AUTH_CONFIG` with the content of the
+1. Create a [variable](../variables/README.md#gitlab-cicd-environment-variables) `DOCKER_AUTH_CONFIG` with the content of the
Docker configuration file as the value:
```json
@@ -534,7 +536,8 @@ To configure access for `registry.example.com:5000`, follow these steps:
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
-NOTE: **Note:** The full `hostname:port` combination is required everywhere
+NOTE: **Note:**
+The full `hostname:port` combination is required everywhere
for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if
`registry.example.com:5000/namespace/image:tag` is specified in `.gitlab-ci.yml`,
then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`.
@@ -551,8 +554,9 @@ service containers.
For all possible configuration variables check the documentation of each image
provided in their corresponding Docker hub page.
-*Note: All variables will be passed to all services containers. It's not
-designed to distinguish which variable should go where.*
+NOTE: **Note:**
+All variables will be passed to all services containers. It's not
+designed to distinguish which variable should go where.
### PostgreSQL service example
@@ -582,8 +586,9 @@ time.
## How to debug a job locally
-*Note: The following commands are run without root privileges. You should be
-able to run Docker with your regular user account.*
+NOTE: **Note:**
+The following commands are run without root privileges. You should be
+able to run Docker with your regular user account.
First start with creating a file named `build_script`:
@@ -602,7 +607,7 @@ is specific to your project.
Then create some service containers:
-```
+```sh
docker run -d --name service-mysql mysql:latest
docker run -d --name service-postgres postgres:latest
```
@@ -614,7 +619,7 @@ respectively. They will both run in the background (`-d`).
Finally, create a build container by executing the `build_script` file we
created earlier:
-```
+```sh
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
```
@@ -626,7 +631,7 @@ piped using STDIN to the bash interpreter which in turn executes the
When you finish testing and no longer need the containers, you can remove them
with:
-```
+```sh
docker rm -f -v build service-mysql service-postgres
```
@@ -642,7 +647,6 @@ creation.
[postgres-hub]: https://hub.docker.com/r/_/postgres/
[mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
-[variable]: ../variables/README.md#variables
[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
[register]: https://docs.gitlab.com/runner/register/
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index f354cdb398e..50f1ac3d54a 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Building images with kaniko and GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45512) in GitLab 11.2.
@@ -9,17 +13,18 @@ container images from a Dockerfile, inside a container or Kubernetes cluster.
kaniko solves two problems with using the
[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
-1. Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
- in order to function, which is a significant security concern.
-1. Docker-in-docker generally incurs a performance penalty and can be quite slow.
+- Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
+ in order to function, which is a significant security concern.
+- Docker-in-docker generally incurs a performance penalty and can be quite slow.
## Requirements
In order to utilize kaniko with GitLab, a [GitLab Runner](https://docs.gitlab.com/runner/)
-using either the [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html),
-[Docker](https://docs.gitlab.com/runner/executors/docker.html), or
-[Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html)
-executors is required.
+using one of the following executors is required:
+
+- [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html).
+- [Docker](https://docs.gitlab.com/runner/executors/docker.html).
+- [Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html).
## Building a Docker image with kaniko
@@ -34,14 +39,17 @@ few important details:
- A Docker `config.json` file needs to be created with the authentication
information for the desired container registry.
----
+In the following example, kaniko is used to:
+
+1. Build a Docker image.
+1. Then push it to [GitLab Container Registry](../../user/project/container_registry.md).
-In the following example, kaniko is used to build a Docker image and then push
-it to [GitLab Container Registry](../../user/project/container_registry.md).
The job will run only when a tag is pushed. A `config.json` file is created under
`/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the
[environment variables](../variables/README.md#predefined-environment-variables)
-GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
+GitLab CI/CD provides.
+
+In the last step, kaniko uses the `Dockerfile` under the
root directory of the project, builds the Docker image and pushes it to the
project's Container Registry while tagging it with the Git tag:
@@ -80,3 +88,15 @@ store:
...
-----END CERTIFICATE-----" >> /kaniko/ssl/certs/ca-certificates.crt
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
index 7aa7de97c43..56200142055 100644
--- a/doc/ci/enable_or_disable_ci.md
+++ b/doc/ci/enable_or_disable_ci.md
@@ -1,25 +1,32 @@
+---
+type: howto
+---
+
# How to enable or disable GitLab CI/CD
-To effectively use GitLab CI/CD, you need a valid [`.gitlab-ci.yml`](yaml/README.md)
-file present at the root directory of your project and a
-[runner](runners/README.md) properly set up. You can read our
-[quick start guide](quick_start/README.md) to get you started.
+To effectively use GitLab CI/CD, you need:
+
+- A valid [`.gitlab-ci.yml`](yaml/README.md) file present at the root directory
+ of your project.
+- A [runner](runners/README.md) properly set up.
+
+You can read our [quick start guide](quick_start/README.md) to get you started.
If you are using an external CI/CD server like Jenkins or Drone CI, it is advised
to disable GitLab CI/CD in order to not have any conflicts with the commits status
API.
----
-
GitLab CI/CD is exposed via the `/pipelines` and `/jobs` pages of a project.
Disabling GitLab CI/CD in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/jobs` pages can still be accessed, although
it's hidden from the left sidebar menu.
-GitLab CI/CD is enabled by default on new installations and can be disabled either
-individually under each project's settings, or site-wide by modifying the
-settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
-respectively.
+GitLab CI/CD is enabled by default on new installations and can be disabled
+either:
+
+- Individually under each project's settings.
+- Site-wide by modifying the settings in `gitlab.yml` and `gitlab.rb` for source
+ and Omnibus installations respectively.
## Per-project user setting
@@ -36,10 +43,10 @@ and `gitlab.rb` for source and Omnibus installations respectively.
Two things to note:
-1. Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
- had it enabled prior to this modification, will work as before.
-1. Even if you disable GitLab CI/CD, users will still be able to enable it in the
- project's settings.
+- Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
+ had it enabled prior to this modification, will work as before.
+- Even if you disable GitLab CI/CD, users will still be able to enable it in the
+ project's settings.
For installations from source, open `gitlab.yml` with your editor and set
`builds` to `false`:
@@ -54,12 +61,32 @@ default_projects_features:
builds: false
```
-Save the file and restart GitLab: `sudo service gitlab restart`.
+Save the file and restart GitLab:
+
+```sh
+sudo service gitlab restart
+```
For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line:
-```
+```ruby
gitlab_rails['gitlab_default_projects_features_builds'] = false
```
-Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`.
+Save the file and reconfigure GitLab:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index fe66f7e3c28..f2661c4bafd 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -1,45 +1,69 @@
-# Introduction to environments and deployments
+---
+type: reference
+---
+
+# Environments and deployments
> Introduced in GitLab 8.9.
-During the development of software, there can be many stages until it's ready
-for public consumption. You sure want to first test your code and then deploy it
-in a testing or staging environment before you release it to the public. That
-way you can prevent bugs not only in your software, but in the deployment
-process as well.
+Environments allow control of the continuous deployment of your software,
+all within GitLab.
+
+## Introduction
+
+There are many stages required in the software development process before the software is ready
+for public consumption.
-GitLab CI is capable of not only testing or building your projects, but also
+For example:
+
+1. Develop your code.
+1. Test your code.
+1. Deploy your code into a testing or staging environment before you release it to the public.
+
+This helps find bugs in your software, and also in the deployment process as well.
+
+GitLab CI/CD is capable of not only testing or building your projects, but also
deploying them in your infrastructure, with the added benefit of giving you a
-way to track your deployments. In other words, you can always know what is
+way to track your deployments. In other words, you will always know what is
currently being deployed or has been deployed on your servers.
-## Overview
+It's important to know that:
+
+- Environments are like tags for your CI jobs, describing where code gets deployed.
+- Deployments are created when [jobs](yaml/README.md#introduction) deploy versions of code to environments,
+ so every environment can have one or more deployments.
+
+GitLab:
-With environments, you can control the Continuous Deployment of your software
-all within GitLab. All you need to do is define them in your project's
-[`.gitlab-ci.yml`][yaml] as we will explore below. GitLab provides a full
-history of your deployments per every environment.
+- Provides a full history of your deployments for each environment.
+- Keeps track of your deployments, so you always know what is currently being deployed on your
+ servers.
-Environments are like tags for your CI jobs, describing where code gets deployed.
-Deployments are created when [jobs] deploy versions of code to environments,
-so every environment can have one or more deployments. GitLab keeps track of
-your deployments, so you always know what is currently being deployed on your
-servers. If you have a deployment service such as [Kubernetes][kube]
-enabled for your project, you can use it to assist with your deployments, and
+If you have a deployment service such as [Kubernetes](../user/project/clusters/index.md)
+associated with your project, you can use it to assist with your deployments, and
can even access a [web terminal](#web-terminals) for your environment from within GitLab!
-To better understand how environments and deployments work, let's consider an
-example. We assume that you have already created a project in GitLab and set up
-a Runner. The example will cover the following:
+## Configuring environments
-- We are developing an application
-- We want to run tests and build our app on all branches
-- Our default branch is `master`
-- We deploy the app only when a pipeline on `master` branch is run
+Configuring environments involves:
-Let's see how it all ties together.
+1. Understanding how [pipelines](pipelines.md) work.
+1. Defining environments in your project's [`.gitlab-ci.yml`](yaml/README.md) file.
-## Defining environments
+The rest of this section illustrates how to configure environments and deployments using
+an example scenario. It assumes you have already:
+
+- Created a [project](../gitlab-basics/create-project.md) in GitLab.
+- Set up [a Runner](runners/README.md).
+
+In the scenario:
+
+- We are developing an application.
+- We want to run tests and build our app on all branches.
+- Our default branch is `master`.
+- We deploy the app only when a pipeline on `master` branch is run.
+
+### Defining environments
Let's consider the following `.gitlab-ci.yml` example:
@@ -68,125 +92,64 @@ deploy_staging:
- master
```
-We have defined 3 [stages](yaml/README.md#stages):
+We have defined three [stages](yaml/README.md#stages):
+
+- `test`
+- `build`
+- `deploy`
-- test
-- build
-- deploy
+The jobs assigned to these stages will run in this order. If any job fails, then
+the pipeline fails and jobs that are assigned to the next stage won't run.
-The jobs assigned to these stages will run in this order. If a job fails, then
-the jobs that are assigned to the next stage won't run, rendering the pipeline
-as failed. In our case, the `test` job will run first, then the `build` and
-lastly the `deploy_staging`. With this, we ensure that first the tests pass,
-then our app is able to be built successfully, and lastly we deploy to the
-staging server.
+In our case:
+- The `test` job will run first.
+- Then the `build` job.
+- Lastly the `deploy_staging` job.
+
+With this configuration, we:
+
+- Check that the tests pass.
+- Ensure that our app is able to be built successfully.
+- Lastly we deploy to the staging server.
+
+NOTE: **Note:**
The `environment` keyword is just a hint for GitLab that this job actually
-deploys to this environment's `name`. It can also have a `url` which, as we
-will later see, is exposed in various places within GitLab. Each time a job that
-has an environment specified and succeeds, a deployment is recorded, remembering
+deploys to the `name` environment. It can also have a `url` that is
+exposed in various places within GitLab. Each time a job that
+has an environment specified succeeds, a deployment is recorded, storing
the Git SHA and environment name.
-> **Note:**
+In summary, with the above `.gitlab-ci.yml` we have achieved the following:
+
+- All branches will run the `test` and `build` jobs.
+- The `deploy_staging` job will run [only](yaml/README.md#onlyexcept-basic) on the `master`
+ branch, which means all merge requests that are created from branches don't
+ get deployed to the staging server.
+- When a merge request is merged, all jobs will run and the `deploy_staging`
+ job will deploy our code to a staging server while the deployment
+ will be recorded in an environment named `staging`.
+
> Starting with GitLab 8.15, the environment name is exposed to the Runner in
> two forms: `$CI_ENVIRONMENT_NAME`, and `$CI_ENVIRONMENT_SLUG`. The first is
> the name given in `.gitlab-ci.yml` (with any variables expanded), while the
> second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
> etc.
>
-> **Note:**
> Starting with GitLab 9.3, the environment URL is exposed to the Runner via
-> `$CI_ENVIRONMENT_URL`. The URL would be expanded from `.gitlab-ci.yml`, or if
-> the URL was not defined there, the external URL from the environment would be
-> used.
-
-To sum up, with the above `.gitlab-ci.yml` we have achieved that:
-
-- All branches will run the `test` and `build` jobs.
-- The `deploy_staging` job will run [only](yaml/README.md#only-and-except-simplified) on the `master`
- branch which means all merge requests that are created from branches don't
- get to deploy to the staging server
-- When a merge request is merged, all jobs will run and the `deploy_staging`
- in particular will deploy our code to a staging server while the deployment
- will be recorded in an environment named `staging`.
+> `$CI_ENVIRONMENT_URL`. The URL is expanded from `.gitlab-ci.yml`, or if
+> the URL was not defined there, the external URL from the environment is used.
-Let's now see how that information is exposed within GitLab.
+### Configuring manual deployments
-## Viewing the current status of an environment
+Adding `when: manual` to an automatically executed job's configuration converts it to
+a job requiring manual action.
-The environment list under your project's **Operations > Environments**, is
-where you can find information of the last deployment status of an environment.
+To expand on the [previous example](#defining-environments), the following includes
+another job that deploys our app to a production server and is
+tracked by a `production` environment.
-Here's how the Environments page looks so far.
-
-![Environment view](img/environments_available.png)
-
-There's a bunch of information there, specifically you can see:
-
-- The environment's name with a link to its deployments
-- The last deployment ID number and who performed it
-- The job ID of the last deployment with its respective job name
-- The commit information of the last deployment such as who committed, to what
- branch and the Git SHA of the commit
-- The exact time the last deployment was performed
-- A button that takes you to the URL that you have defined under the
- `environment` keyword in `.gitlab-ci.yml`
-- A button that re-deploys the latest deployment, meaning it runs the job
- defined by the environment name for that specific commit
-
-> **Notes:**
->
-> - While you can create environments manually in the web interface, we recommend
-> that you define your environments in `.gitlab-ci.yml` first. They will
-> be automatically created for you after the first deploy.
-> - The environments page can only be viewed by Reporters and above. For more
-> information on the permissions, see the [permissions documentation][permissions].
-> - Only deploys that happen after your `.gitlab-ci.yml` is properly configured
-> will show up in the "Environment" and "Last deployment" lists.
-
-The information shown in the Environments page is limited to the latest
-deployments, but as you may have guessed an environment can have multiple
-deployments.
-
-## Viewing the deployment history of an environment
-
-GitLab keeps track of your deployments, so you always know what is currently
-being deployed on your servers. That way you can have the full history of your
-deployments per every environment right in your browser. Clicking on an
-environment will show the history of its deployments. Assuming you have deployed
-multiple times already, here's how a specific environment's page looks like.
-
-![Deployments](img/deployments_view.png)
-
-We can see the same information as when in the Environments page, but this time
-all deployments are shown. As you may have noticed, apart from the **Re-deploy**
-button there are now **Rollback** buttons for each deployment. Let's see how
-that works.
-
-## Rolling back changes
-
-You can't control everything, so sometimes things go wrong. When that unfortunate
-time comes GitLab has you covered. Simply by clicking the **Rollback** button
-that can be found in the deployments page
-(**Operations > Environments > `environment name`**) you can relaunch the
-job with the commit associated with it.
-
->**Note:**
-Bear in mind that your mileage will vary and it's entirely up to how you define
-the deployment process in the job's `script` whether the rollback succeeds or not.
-GitLab CI is just following orders.
-
-Thankfully that was the staging server that we had to rollback, and since we
-learn from our mistakes, we decided to not make the same again when we deploy
-to the production server. Enter manual actions for deployments.
-
-## Manually deploying to environments
-
-Turning a job from running automatically to a manual action is as simple as
-adding `when: manual` to it. To expand on our previous example, let's add
-another job that this time deploys our app to a production server and is
-tracked by a `production` environment. The `.gitlab-ci.yml` looks like this
-so far:
+The `.gitlab-ci.yml` file for this is as follows:
```yaml
stages:
@@ -224,41 +187,61 @@ deploy_prod:
- master
```
-The `when: manual` action exposes a play button in GitLab's UI and the
-`deploy_prod` job will only be triggered if and when we click that play button.
-You can find it in the pipeline, job, environment, and deployment views.
+The `when: manual` action:
-| Pipelines | Single pipeline | Environments | Deployments | jobs |
-| --------- | ----------------| ------------ | ----------- | -------|
-| ![Pipelines manual action](img/environments_manual_action_pipelines.png) | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) | ![Environments manual action](img/environments_manual_action_environments.png) | ![Deployments manual action](img/environments_manual_action_deployments.png) | ![Builds manual action](img/environments_manual_action_jobs.png) |
+- Exposes a "play" button in GitLab's UI for that job.
+- Means the `deploy_prod` job will only be triggered when the "play" button is clicked.
-Clicking on the play button in either of these places will trigger the
-`deploy_prod` job, and the deployment will be recorded under a new
-environment named `production`.
+You can find the "play" button in the pipelines, environments, deployments, and jobs views.
->**Note:**
-Remember that if your environment's name is `production` (all lowercase), then
+| View | Screenshot |
+|:----------------|:-------------------------------------------------------------------------------|
+| Pipelines | ![Pipelines manual action](img/environments_manual_action_pipelines.png) |
+| Single pipeline | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) |
+| Environments | ![Environments manual action](img/environments_manual_action_environments.png) |
+| Deployments | ![Deployments manual action](img/environments_manual_action_deployments.png) |
+| Jobs | ![Builds manual action](img/environments_manual_action_jobs.png) |
+
+Clicking on the play button in any view will trigger the `deploy_prod` job, and the
+deployment will be recorded as a new environment named `production`.
+
+NOTE: **Note:**
+If your environment's name is `production` (all lowercase),
it will get recorded in [Cycle Analytics](../user/project/cycle_analytics.md).
-Double the benefit!
-## Dynamic environments
+### Configuring dynamic environments
-As the name suggests, it is possible to create environments on the fly by just
-declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
-the basis of [Review apps](review_apps/index.md).
+Regular environments are good when deploying to "stable" environments like staging or production.
-NOTE: **Note:**
-The `name` and `url` parameters can use most of the CI/CD variables,
-including [predefined](variables/README.md#predefined-environment-variables),
-[project/group ones](variables/README.md#variables) and
-[`.gitlab-ci.yml` variables](yaml/README.md#variables). You however cannot use variables
-defined under `script` or on the Runner's side. There are also other variables that
-are unsupported in the context of `environment:name`. You can read more about
-[where variables can be used](variables/where_variables_can_be_used.md).
-
-GitLab Runner exposes various [environment variables][variables] when a job runs,
-and as such, you can use them as environment names. Let's add another job in
-our example which will deploy to all branches except `master`:
+However, for environments for branches other than `master`, dynamic environments
+can be used. Dynamic environments make it possible to create environments on the fly by
+declaring their names dynamically in `.gitlab-ci.yml`.
+
+Dynamic environments are a fundamental part of [Review apps](review_apps/index.md).
+
+#### Allowed variables
+
+The `name` and `url` parameters for dynamic environments can use most available CI/CD variables,
+including:
+
+- [Predefined environment variables](variables/README.md#predefined-environment-variables)
+- [Project and group variables](variables/README.md#gitlab-cicd-environment-variables)
+- [`.gitlab-ci.yml` variables](yaml/README.md#variables)
+
+However, you cannot use variables defined:
+
+- Under `script`.
+- On the Runner's side.
+
+There are also other variables that are unsupported in the context of `environment:name`.
+For more information, see [Where variables can be used](variables/where_variables_can_be_used.md).
+
+#### Example configuration
+
+GitLab Runner exposes various [environment variables](variables/README.md) when a job runs, so
+you can use them as environment names.
+
+In the following example, the job will deploy to all branches except `master`:
```yaml
deploy_review:
@@ -274,39 +257,54 @@ deploy_review:
- master
```
-Let's break it down in pieces. The job's name is `deploy_review` and it runs
-on the `deploy` stage. The `script` at this point is fictional, you'd have to
-use your own based on your deployment. Then, we set the `environment` with the
-`environment:name` being `review/$CI_COMMIT_REF_NAME`. Now that's an interesting
-one. Since the [environment name][env-name] can contain slashes (`/`), we can
-use this pattern to distinguish between dynamic environments and the regular
-ones.
-
-So, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`
-which takes the value of the branch name. Since `$CI_COMMIT_REF_NAME` itself may
-also contain `/`, or other characters that would be invalid in a domain name or
-URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the
-environment can get a specific and distinct URL for each branch. In this case,
-given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something
-like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
-the web server to serve these requests is based on your setup.
-
-You could also use `$CI_COMMIT_REF_SLUG` in `environment:url`, e.g.:
-`https://$CI_COMMIT_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
-here because it is guaranteed to be unique, but if you're using a workflow like
-[GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer
-environment names to be more closely based on the branch name - the example
-above would give you an URL like `https://100-do-the-thing.example.com`
-
-Last but not least, we tell the job to run [`only`][only] on branches
-[`except`][only] master.
-
->**Note:**
-You are not bound to use the same prefix or only slashes in the dynamic
-environments' names (`/`), but as we will see later, this will enable the
-[grouping similar environments](#grouping-similar-environments) feature.
-
-The whole `.gitlab-ci.yml` looks like this so far:
+In this example:
+
+- The job's name is `deploy_review` and it runs on the `deploy` stage.
+- We set the `environment` with the `environment:name` as `review/$CI_COMMIT_REF_NAME`.
+ Since the [environment name](yaml/README.md#environmentname) can contain slashes (`/`), we can
+ use this pattern to distinguish between dynamic and regular environments.
+- We tell the job to run [`only`](yaml/README.md#onlyexcept-basic) on branches,
+ [`except`](yaml/README.md#onlyexcept-basic) `master`.
+
+For the value of:
+
+- `environment:name`, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`,
+ which receives the value of the branch name.
+- `environment:url`, we want a specific and distinct URL for each branch. `$CI_COMMIT_REF_NAME`
+ may contain a `/` or other characters that would be invalid in a domain name or URL,
+ so we use `$CI_ENVIRONMENT_SLUG` to guarantee that we get a valid URL.
+
+ For example, given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something
+ like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
+ the web server to serve these requests is based on your setup.
+
+ We have used `$CI_ENVIRONMENT_SLUG` here because it is guaranteed to be unique. If
+ you're using a workflow like [GitLab Flow](../workflow/gitlab_flow.md), collisions
+ are unlikely and you may prefer environment names to be more closely based on the
+ branch name. In that case, you could use `$CI_COMMIT_REF_SLUG` in `environment:url` in
+ the example above: `https://$CI_COMMIT_REF_SLUG.example.com`, which would give a URL
+ of `https://100-do-the-thing.example.com`.
+
+NOTE: **Note:**
+You are not required to use the same prefix or only slashes (`/`) in the dynamic environments'
+names. However, using this format will enable the [grouping similar environments](#grouping-similar-environments)
+feature.
+
+### Complete example
+
+The configuration in this section provides a full development workflow where your app is:
+
+- Tested.
+- Built.
+- Deployed as a Review App.
+- Deployed to a staging server once the merge request is merged.
+- Finally, able to be manually deployed to the production server.
+
+The following combines the previous configuration examples, including:
+
+- Defining [simple environments](#defining-environments) for testing, building, and deployment to staging.
+- Adding [manual actions](#configuring-manual-deployments) for deployment to production.
+- Creating [dynamic environments](#configuring-dynamic-environments) for deployments for reviewing.
```yaml
stages:
@@ -356,9 +354,10 @@ deploy_prod:
- master
```
-A more realistic example would include copying files to a location where a
-webserver (NGINX) could then read and serve. The example below will copy the
-`public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`:
+A more realistic example would also include copying files to a location where a
+webserver (for example, NGINX) could then access and serve them.
+
+The example below will copy the `public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`:
```yaml
review_app:
@@ -370,78 +369,158 @@ review_app:
url: https://$CI_COMMIT_REF_SLUG.example.com
```
-It is assumed that the user has already set up NGINX and GitLab Runner in the
-server this job will run on.
+This example requires that NGINX and GitLab Runner are set up on the server this job will run on.
->**Note:**
-Be sure to check out the [limitations](#limitations) section for some edge
-cases regarding naming of your branches and Review Apps.
+NOTE: **Note:**
+See the [limitations](#limitations) section for some edge cases regarding the naming of
+your branches and Review Apps.
----
+The complete example provides the following workflow to developers:
+
+- Create a branch locally.
+- Make changes and commit them.
+- Push the branch to GitLab.
+- Create a merge request.
+
+Behind the scenes, GitLab Runner will:
+
+- Pick up the changes and start running the jobs.
+- Run the jobs sequentially as defined in `stages`:
+ - First, run the tests.
+ - If the tests succeed, build the app.
+ - If the build succeeds, the app is deployed to an environment with a name specific to the
+ branch.
+
+So now, every branch:
+
+- Gets its own environment.
+- Is deployed to its own unique location, with the added benefit of:
+ - Having a [history of deployments](#viewing-deployment-history).
+ - Being able to [rollback changes](#retrying-and-rolling-back) if needed.
+
+For more information, see [Using the environment URL](#using-the-environment-url).
+
+### Protected environments
+
+Environments can be "protected", restricting access to them.
+
+For more information, see [Protected environments](environments/protected_environments.md).
+
+## Working with environments
+
+Once environments are configured, GitLab provides many features for working with them,
+as documented below.
+
+### Viewing environments and deployments
+
+A list of environments and deployment statuses is available on each project's **Operations > Environments** page.
+
+For example:
+
+![Environment view](img/environments_available.png)
+
+This example shows:
+
+- The environment's name with a link to its deployments.
+- The last deployment ID number and who performed it.
+- The job ID of the last deployment with its respective job name.
+- The commit information of the last deployment, such as who committed it, to what
+ branch, and the Git SHA of the commit.
+- The exact time the last deployment was performed.
+- A button that takes you to the URL that you defined under the `environment` keyword
+ in `.gitlab-ci.yml`.
+- A button that re-deploys the latest deployment, meaning it runs the job
+ defined by the environment name for that specific commit.
+
+The information shown in the **Environments** page is limited to the latest
+deployments, but an environment can have multiple deployments.
+
+> **Notes:**
+>
+> - While you can create environments manually in the web interface, we recommend
+> that you define your environments in `.gitlab-ci.yml` first. They will
+> be automatically created for you after the first deploy.
+> - The environments page can only be viewed by users with [Reporter permission](../user/permissions.md#project-members-permissions)
+> and above. For more information on permissions, see the [permissions documentation](../user/permissions.md).
+> - Only deploys that happen after your `.gitlab-ci.yml` is properly configured
+> will show up in the **Environment** and **Last deployment** lists.
+
+### Viewing deployment history
+
+GitLab keeps track of your deployments, so you:
+
+- Always know what is currently being deployed on your servers.
+- Can have the full history of your deployments for every environment.
-The development workflow would now be:
+Clicking on an environment shows the history of its deployments. Here's an example **Environments** page
+with multiple deployments:
-- Developer creates a branch locally
-- Developer makes changes, commits and pushes the branch to GitLab
-- Developer creates a merge request
+![Deployments](img/deployments_view.png)
+
+This view is similar to the **Environments** page, but all deployments are shown. Also in this view
+is a **Rollback** button. For more information, see [Retrying and rolling back](#retrying-and-rolling-back).
+
+### Retrying and rolling back
-Behind the scenes:
+If there is a problem with a deployment, you can retry it or roll it back.
-- GitLab Runner picks up the changes and starts running the jobs
-- The jobs run sequentially as defined in `stages`
- - First, the tests pass
- - Then, the job begins and successfully also passes
- - Lastly, the app is deployed to an environment with a name specific to the
- branch
+To retry or rollback a deployment:
-So now, every branch gets its own environment and is deployed to its own place
-with the added benefit of having a [history of deployments](#viewing-the-deployment-history-of-an-environment)
-and also being able to [rollback changes](#rolling-back-changes) if needed.
-Let's briefly see where URL that's defined in the environments is exposed.
+1. Navigate to **Operations > Environments**.
+1. Click on the environment.
+1. In the deployment history list for the environment, click the:
+ - **Retry** button next to the last deployment, to retry that deployment.
+ - **Rollback** button next to a previously successful deployment, to roll back to that deployment.
+
+NOTE: **Note:**
+The defined deployment process in the job's `script` determines whether the rollback succeeds or not.
-## Making use of the environment URL
+### Using the environment URL
The [environment URL](yaml/README.md#environmenturl) is exposed in a few
-places within GitLab.
+places within GitLab:
-| In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button |
-| -------------------- | ------------ | ----------- |
-| ![Environment URL in merge request](img/environments_mr_review_app.png) | ![Environment URL in environments](img/environments_available.png) | ![Environment URL in deployments](img/deployments_view.png) |
+- In a merge request widget as a link:
+ ![Environment URL in merge request](img/environments_mr_review_app.png)
+- In the Environments view as a button:
+ ![Environment URL in environments](img/environments_available.png)
+- In the Deployments view as a button:
+ ![Environment URL in deployments](img/deployments_view.png)
-If a merge request is eventually merged to the default branch (in our case
-`master`) and that branch also deploys to an environment (in our case `staging`
-and/or `production`) you can see this information in the merge request itself.
+You can see this information in a merge request itself if:
+
+- The merge request is eventually merged to the default branch (usually `master`).
+- That branch also deploys to an environment (for example, `staging` or `production`).
+
+For example:
![Environment URLs in merge request](img/environments_link_url_mr.png)
-### Go directly from source files to public pages on the environment
+#### Going from source files to public pages
With GitLab's [Route Maps](review_apps/index.md#route-maps) you can go directly
-from source files to public pages on the environment set for Review Apps.
+from source files to public pages in the environment set for Review Apps.
+
+### Stopping an environment
-From then on, you have a full development cycle, where your app is tested, built, deployed
-as a Review App, deployed to a staging server once the merge request is merged,
-and finally manually deployed to the production server. This is a simple workflow,
-but when you have multiple developers working on a project
-at the same time, each of them pushing to their own branches, dynamic environments are
-created all the time. In which case, you probably want to do some clean up. Read
-next how environments can be stopped.
+Stopping an environment:
-## Stopping an environment
+- Moves it from the list of **Available** environments to the list of **Stopped**
+ environments on the [**Environments** page](#viewing-environments-and-deployments).
+- Executes an [`on_stop` action](yaml/README.md#environmenton_stop), if defined.
-By stopping an environment, you are effectively terminating its recording of the
-deployments that happen in it.
+This is often used when multiple developers are working on a project at the same time,
+each of them pushing to their own branches, causing many dynamic environments to be created.
-A branch is associated with an environment when the CI pipeline that is created
-for this branch, was recently deployed to this environment. You can think of
-the CI pipeline as the glue between the branch and the environment:
-`branch âž” CI pipeline âž” environment`.
+NOTE: **Note:**
+Starting with GitLab 8.14, dynamic environments are stopped automatically
+when their associated branch is deleted.
-There is a special case where environments can be manually stopped. That can
-happen if you provide another job for that matter. The syntax is a little
-tricky since a job calls another job to do the job.
+#### Automatically stopping an environment
-Consider the following example where the `deploy_review` calls the `stop_review`
+Environments can be stopped automatically using special configuration.
+
+Consider the following example where the `deploy_review` job calls `stop_review`
to clean up and stop the environment:
```yaml
@@ -470,35 +549,31 @@ stop_review:
action: stop
```
-Setting the [`GIT_STRATEGY`][git-strategy] to `none` is necessary on the
-`stop_review` job so that the [GitLab Runner] won't try to checkout the code
-after the branch is deleted.
-
->**Note:**
-Starting with GitLab 8.14, dynamic environments will be stopped automatically
-when their associated branch is deleted.
+Setting the [`GIT_STRATEGY`](yaml/README.md#git-strategy) to `none` is necessary in the
+`stop_review` job so that the [GitLab Runner](https://docs.gitlab.com/runner/) won't
+try to check out the code after the branch is deleted.
When you have an environment that has a stop action defined (typically when
-the environment describes a review app), GitLab will automatically trigger a
+the environment describes a Review App), GitLab will automatically trigger a
stop action when the associated branch is deleted. The `stop_review` job must
-be in the same `stage` as the `deploy_review` one in order for the environment
+be in the same `stage` as the `deploy_review` job in order for the environment
to automatically stop.
-You can read more in the [`.gitlab-ci.yml` reference][onstop].
+You can read more in the [`.gitlab-ci.yml` reference](yaml/README.md#environmenton_stop).
-## Grouping similar environments
+### Grouping similar environments
-> [Introduced][ce-7015] in GitLab 8.14.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015) in GitLab 8.14.
-As we've seen in the [dynamic environments](#dynamic-environments), you can
-prepend their name with a word, then followed by a `/` and finally the branch
-name which is automatically defined by the `CI_COMMIT_REF_NAME` variable.
+As documented in [Configuring dynamic environments](#configuring-dynamic-environments), you can
+prepend environment name with a word, followed by a `/`, and finally the branch
+name, which is automatically defined by the `CI_COMMIT_REF_NAME` variable.
-In short, environments that are named like `type/foo` are presented under a
-group named `type`.
+In short, environments that are named like `type/foo` are all presented under the same
+group, named `type`.
-In our minimal example, we name the environments `review/$CI_COMMIT_REF_NAME`
-where `$CI_COMMIT_REF_NAME` is the branch name:
+In our [minimal example](#example-configuration), we named the environments `review/$CI_COMMIT_REF_NAME`
+where `$CI_COMMIT_REF_NAME` is the branch name. Here is a snippet of the example:
```yaml
deploy_review:
@@ -509,50 +584,49 @@ deploy_review:
name: review/$CI_COMMIT_REF_NAME
```
-In that case, if you visit the Environments page, and provided the branches
+In this case, if you visit the **Environments** page and the branches
exist, you should see something like:
![Environment groups](img/environments_dynamic_groups.png)
-## Monitoring environments
+### Monitoring environments
> **Notes:**
>
> - For the monitoring dashboard to appear, you need to:
-> - Have enabled the [Prometheus integration][prom]
-> - Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/index.md)
-> - With GitLab 9.2, all deployments to an environment are shown directly on the
-> monitoring dashboard
+> - Enable the [Prometheus integration](../user/project/integrations/prometheus.md).
+> - Configure Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/index.md).
+> - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard.
-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.
+If you have enabled [Prometheus for monitoring system and response metrics](../user/project/integrations/prometheus.md),
+you can monitor the behavior of your app running in each environment.
-Once configured, GitLab will attempt to retrieve [supported performance metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus_library/index.html) for any
-environment which has had a successful deployment. If monitoring data was
-successfully retrieved, a Monitoring button will appear for each environment.
+Once configured, GitLab will attempt to retrieve [supported performance metrics](../user/project/integrations/prometheus_library/index.md)
+for any environment that has had a successful deployment. If monitoring data was
+successfully retrieved, a **Monitoring** button will appear for each environment.
![Environment Detail with Metrics](img/deployments_view.png)
-Clicking on the Monitoring button will display a new page, showing up to the last
+Clicking on the **Monitoring** button will display a new page showing up to the last
8 hours of performance data. It may take a minute or two for data to appear
after initial deployment.
-All deployments to an environment are shown directly on the monitoring dashboard
-which allows easy correlation between any changes in performance and a new
-version of the app, all without leaving GitLab.
+All deployments to an environment are shown directly on the monitoring dashboard,
+which allows easy correlation between any changes in performance and new
+versions of the app, all without leaving GitLab.
![Monitoring dashboard](img/environments_monitoring.png)
-## Web terminals
+### Web terminals
->**Note:**
-Web terminals were added in GitLab 8.15 and are only available to project
-maintainers and owners.
+> Web terminals were added in GitLab 8.15 and are only available to project Maintainers and Owners.
-If you deploy to your environments with the help of a deployment service (e.g.,
-the [Kubernetes integration][kube]), GitLab can open
-a terminal session to your environment! This is a very powerful feature that
-allows you to debug issues without leaving the comfort of your web browser. To
-enable it, just follow the instructions given in the service integration
+If you deploy to your environments with the help of a deployment service (for example,
+the [Kubernetes integration](../user/project/clusters/index.md)), GitLab can open
+a terminal session to your environment.
+
+This is a powerful feature that allows you to debug issues without leaving the comfort
+of your web browser. To enable it, just follow the instructions given in the service integration
documentation.
Once enabled, your environments will gain a "terminal" button:
@@ -568,41 +642,86 @@ establish the terminal session:
![Terminal page](img/environments_terminal_page.png)
-This works just like any other terminal - you'll be in the container created
-by your deployment, so you can run shell commands and get responses in real
-time, check the logs, try out configuration or code tweaks, etc. You can open
-multiple terminals to the same environment - they each get their own shell
-session - and even a multiplexer like `screen` or `tmux`!
+This works just like any other terminal. You'll be in the container created
+by your deployment so you can:
->**Note:**
-Container-based deployments often lack basic tools (like an editor), and may
-be stopped or restarted at any time. If this happens, you will lose all your
-changes! Treat this as a debugging tool, not a comprehensive online IDE.
+- Run shell commands and get responses in real time.
+- Check the logs.
+- Try out configuration or code tweaks etc.
----
+You can open multiple terminals to the same environment, they each get their own shell
+session and even a multiplexer like `screen` or `tmux`.
-While this is fine for deploying to some stable environments like staging or
-production, what happens for branches? So far we haven't defined anything
-regarding deployments for branches other than `master`. Dynamic environments
-will help us achieve that.
+NOTE: **Note:**
+Container-based deployments often lack basic tools (like an editor), and may
+be stopped or restarted at any time. If this happens, you will lose all your
+changes. Treat this as a debugging tool, not a comprehensive online IDE.
-## Checkout deployments locally
+### Check out deployments locally
-Since 8.13, a reference in the git repository is saved for each deployment, so
+Since GitLab 8.13, a reference in the Git repository is saved for each deployment, so
knowing the state of your current environments is only a `git fetch` away.
-In your git config, append the `[remote "<your-remote>"]` block with an extra
+In your Git configuration, append the `[remote "<your-remote>"]` block with an extra
fetch line:
-```
+```text
fetch = +refs/environments/*:refs/remotes/origin/environments/*
```
+### Scoping environments with specs **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
+
+You can limit the environment scope of a variable by
+defining which environments it can be available for.
+
+Wildcards can be used, and the default environment scope is `*`, which means
+any jobs will have this variable, not matter if an environment is defined or
+not.
+
+For example, if the environment scope is `production`, then only the jobs
+having the environment `production` defined would have this specific variable.
+Wildcards (`*`) can be used along with the environment name, therefore if the
+environment scope is `review/*` then any jobs with environment names starting
+with `review/` would have that particular variable.
+
+Some GitLab features can behave differently for each environment.
+For example, you can
+[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables-premium). **[PREMIUM]**
+
+In most cases, these features use the _environment specs_ mechanism, which offers
+an efficient way to implement scoping within each environment group.
+
+Let's say there are four environments:
+
+- `production`
+- `staging`
+- `review/feature-1`
+- `review/feature-2`
+
+Each environment can be matched with the following environment spec:
+
+| Environment Spec | `production` | `staging` | `review/feature-1` | `review/feature-2` |
+|:-----------------|:-------------|:----------|:-------------------|:-------------------|
+| * | Matched | Matched | Matched | Matched |
+| production | Matched | | | |
+| staging | | Matched | | |
+| review/* | | | Matched | Matched |
+| review/feature-1 | | | Matched | |
+
+As you can see, you can use specific matching for selecting a particular environment,
+and also use wildcard matching (`*`) for selecting a particular environment group,
+such as [Review Apps](review_apps/index.md) (`review/*`).
+
+NOTE: **Note:**
+The most _specific_ spec takes precedence over the other wildcard matching.
+In this case, `review/feature-1` spec takes precedence over `review/*` and `*` specs.
+
## Limitations
-1. You are limited to use only the [CI predefined variables][variables] in the
- `environment: name`. If you try to re-use variables defined inside `script`
- as part of the environment name, it will not work.
+In the `environment: name`, you are limited to only the [predefined environment variables](variables/predefined_variables.md).
+Re-using variables defined inside `script` as part of the environment name will not work.
## Further reading
@@ -611,20 +730,16 @@ Below are some links you may find interesting:
- [The `.gitlab-ci.yml` definition of environments](yaml/README.md#environment)
- [A blog post on Deployments & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- [Review Apps - Use dynamic environments to deploy your code for every branch](review_apps/index.md)
+- [Deploy Boards for your applications running on Kubernetes](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]**
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-[Pipelines]: pipelines.md
-[jobs]: yaml/README.md#jobs
-[yaml]: yaml/README.md
-[environments]: #environments
-[deployments]: #deployments
-[permissions]: ../user/permissions.md
-[variables]: variables/README.md
-[env-name]: yaml/README.md#environmentname
-[only]: yaml/README.md#only-and-except-simplified
-[onstop]: yaml/README.md#environmenton_stop
-[ce-7015]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015
-[gitlab-flow]: ../workflow/gitlab_flow.md
-[gitlab runner]: https://docs.gitlab.com/runner/
-[git-strategy]: yaml/README.md#git-strategy
-[kube]: ../user/project/clusters/index.md
-[prom]: ../user/project/integrations/prometheus.md
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
new file mode 100644
index 00000000000..b72ebe838b8
--- /dev/null
+++ b/doc/ci/environments/protected_environments.md
@@ -0,0 +1,65 @@
+---
+type: concepts, howto
+---
+
+# Protected Environments **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6303) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
+
+## Overview
+
+[Environments](../environments.md) can be used for different reasons:
+
+- Some of them are just for testing.
+- Others are for production.
+
+Since deploy jobs can be raised by different users with different roles, it is important that
+specific environments are "protected" to prevent unauthorized people from affecting them.
+
+By default, a protected environment does one thing: it ensures that only people
+with the right privileges can deploy to it, thus keeping it safe.
+
+NOTE: **Note**:
+A GitLab admin is always allowed to use environments, even if they are protected.
+
+To protect, update, or unprotect an environment, you need to have at least
+[Maintainer permissions](../../user/permissions.md).
+
+## Protecting environments
+
+To protect an environment:
+
+1. Navigate to your project's **Settings > CI/CD**.
+1. Expand the **Protected Environments** section.
+1. From the **Environment** dropdown menu, select the environment you want to protect.
+1. In the **Allowed to Deploy** dropdown menu, select the role, users, or groups you
+ want to give deploy access to. Keep in mind that:
+ - There are two roles to choose from:
+ - **Maintainers**: will allow access to all maintainers in the project.
+ - **Developers**: will allow access to all maintainers and all developers in the project.
+ - You can only select groups that are already associated with the project.
+ - Only users that have at least Developer permission level will appear in
+ the **Allowed to Deploy** dropdown menu.
+1. Click the **Protect** button.
+
+The protected environment will now appear in the list of protected environments.
+
+## Modifying and unprotecting environments
+
+Maintainers can:
+
+- Update existing protected environments at any time by changing the access in the
+ **Allowed to Deploy** dropdown menu.
+- Unprotect a protected environment by clicking the **Unprotect** button for that environment.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 87e86bef44b..340a41c196b 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -4,88 +4,118 @@ comments: false
# GitLab CI/CD Examples
-A collection of [`.gitlab-ci.yml` template files][gitlab-ci-templates] is maintained in GitLab. When you create a new file via the UI,
-GitLab will give you the option to choose one of these templates.
-If your favorite programming language or framework are missing we would love your
-help by sending a merge request with a new `.gitlab-ci.yml` to this project.
-
-There's also a collection of repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork and adjust them to your own needs.
-
-## Languages, frameworks, OSs
-
-- **PHP**:
- - [Testing a PHP application](php.md)
- - [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
- - [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
-- **Ruby**: [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
-- **Python**: [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
-- **Java**:
- - [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md)
- - [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
-- **Scala**: [Test a Scala application](test-scala-application.md)
-- **Clojure**: [Test a Clojure application](test-clojure-application.md)
-- **Elixir**:
- - [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md)
- - [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
-- **iOS and macOS**:
- - [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- - [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
-- **Android**: [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
-- **Debian**: [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
-- **Maven**: [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
-
-### Game development
-
-- [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md)
-
-### Miscellaneous
+Examples are a useful way of understanding how to implement GitLab CI/CD for your specific use case.
+
+Examples are available in several forms. As a collection of:
+
+- `.gitlab-ci.yml` [template files](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates) maintained in GitLab. When you create a new file via the UI,
+ GitLab will give you the option to choose one of these templates. This will allow you to quickly bootstrap your project for CI/CD.
+ If your favorite programming language or framework are missing, we would love your help by sending a merge request with a new `.gitlab-ci.yml` to this project.
+- Repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork and adjust them to your own needs.
+- Examples and [other resources](#other-resources) listed below.
+
+## CI/CD examples
+
+The following table lists examples for different use cases:
+
+| Use case | Resource |
+|:-----------------------------------------------|:------------------------------------------------------------------------------------------------------------------------|
+| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](browser_performance.md). |
+| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
+| Code quality analysis | [Analyze your project's Code Quality](code_quality.md). **[STARTER]** |
+| Container scanning | [Container Scanning with GitLab CI/CD](../../user/application_security/container_scanning/index.md). **[ULTIMATE]** |
+| Dependency scanning | [Dependency Scanning with GitLab CI/CD](../../user/application_security/dependency_scanning/index.md). **[ULTIMATE]** |
+| Deployment with `dpl` | [Using `dpl` as deployment tool](deployment/README.md). |
+| Dynamic application<br>security testing (DAST) | [Dynamic Application Security Testing with GitLab CI/CD](../../user/application_security/dast/index.md). **[ULTIMATE]** |
+| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
+| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
+| GitLab Pages | See the [GitLab Pages](../../user/project/pages/index.md) documentation for a complete example. |
+| Java | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
+| JUnit | [JUnit test reports](../junit_test_reports.md). |
+| License management | [Dependencies license management with GitLab CI/CD](../../user/application_security/license_management/index.md). **[ULTIMATE]** |
+| Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
+| PHP | [Testing PHP projects](php.md). |
+| PHP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
+| PHP | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
+| Python | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
+| Ruby | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
+| Scala | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
+| Static application<br>security testing (SAST) | [Static Application Security Testing with GitLab CI/CD](../../user/application_security/sast/index.md). **[ULTIMATE]** |
+| Testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
+
+### Contributing examples
+
+Contributions are welcome! You can help your favorite programming
+language users and GitLab by sending a merge request with a guide for that language.
+You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
+to get paid for writing complete articles for GitLab.
-- [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md)
-- [Using `dpl` as deployment tool](deployment/README.md)
-- [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+### Adding templates to your GitLab installation **[PREMIUM ONLY]**
-## Test Reports
+If you want to have customized examples and templates for your own self-managed GitLab instance available to your team, your GitLab administrator can [designate an instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html) that contains examples and templates specific to your enterprise.
-[Collect test reports in Verify stage](../junit_test_reports.md)
+## Other resources
-## Code Quality analysis
+This section provides further resources to help you get familiar with different aspects of GitLab CI/CD.
-**(Starter)** [Analyze your project's Code Quality](code_quality.md)
+NOTE: **Note:**
+These resources may no longer reflect the current state of GitLab CI/CD.
-## Static Application Security Testing (SAST)
+### CI/CD in the cloud
-**(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
+For examples of setting up GitLab CI/CD for cloud-based environments, see:
-## Dependency Scanning
+- [How to set up multi-account AWS SAM deployments with GitLab CI](https://about.gitlab.com/2019/02/04/multi-account-aws-sam-deployments-with-gitlab-ci/)
+- [How to autoscale continuous deployment with GitLab Runner on DigitalOcean](https://about.gitlab.com/2018/06/19/autoscale-continuous-deployment-gitlab-runner-digital-ocean/)
+- [How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm](https://about.gitlab.com/2017/09/21/how-to-create-ci-cd-pipeline-with-autodeploy-to-kubernetes-using-gitlab-and-helm/)
-**(Ultimate)** [Scan your dependencies for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/dependency_scanning.html)
+### Customer stories
-## Container Scanning
+For some customer experiences with GitLab CI/CD, see:
-[Scan your Docker images for vulnerabilities](container_scanning.md)
+- [How Verizon Connect reduced datacenter deploys from 30 days to under 8 hours with GitLab](https://about.gitlab.com/2019/02/14/verizon-customer-story/)
+- [How Wag! cut their release process from 40 minutes to just 6](https://about.gitlab.com/2019/01/16/wag-labs-blog-post/)
+- [How Jaguar Land Rover embraced CI to speed up their software lifecycle](https://about.gitlab.com/2018/07/23/chris-hill-devops-enterprise-summit-talk/)
-## Dynamic Application Security Testing (DAST)
+### Getting started
-Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md)
+For some examples to help get you started, see:
-## Browser Performance Testing with Sitespeed.io
+- [GitLab CI/CD's 2018 highlights](https://about.gitlab.com/2019/01/21/gitlab-ci-cd-features-improvements/)
+- [A beginner's guide to continuous integration](https://about.gitlab.com/2018/01/22/a-beginners-guide-to-continuous-integration/)
+- [Making CI easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
-Analyze your [browser performance with Sitespeed.io](browser_performance.md)
+### Implementing GitLab CI/CD
-## GitLab CI/CD for Review Apps
+For examples of others who have implemented GitLab CI/CD, see:
-- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html)
+- [How to streamline interactions between multiple repositories with multi-project pipelines](https://about.gitlab.com/2018/10/31/use-multiproject-pipelines-with-gitlab-cicd/)
+- [How we used GitLab CI to build GitLab faster](https://about.gitlab.com/2018/05/02/using-gitlab-ci-to-build-gitlab-faster/)
+- [Test all the things in GitLab CI with Docker by example](https://about.gitlab.com/2018/02/05/test-all-the-things-gitlab-ci-docker-examples/)
+- [A Craftsman looks at continuous integration](https://about.gitlab.com/2018/01/17/craftsman-looks-at-continuous-integration/)
+- [Go tools and GitLab: How to do continuous integration like a boss](https://about.gitlab.com/2017/11/27/go-tools-and-gitlab-how-to-do-continuous-integration-like-a-boss/)
+- [GitBot – automating boring Git operations with CI](https://about.gitlab.com/2017/11/02/automating-boring-git-operations-gitlab-ci/)
+- [How to use GitLab CI for Vue.js](https://about.gitlab.com/2017/09/12/vuejs-app-gitlab/)
+- Video: [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195)
- [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
+- [Fast and natural continuous integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/)
+- [Demo: CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
-## GitLab CI/CD for GitLab Pages
+### Integrating GitLab CI/CD with other systems
-See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
+To see how you can integrate GitLab CI/CD with third-party systems, see:
-## Contributing
+- [Streamline and shorten error remediation with Sentry’s new GitLab integration](https://about.gitlab.com/2019/01/25/sentry-integration-blog-post/)
+- [How to simplify your smart home configuration with GitLab CI/CD](https://about.gitlab.com/2018/08/02/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management/)
+- [Demo: GitLab + Jira + Jenkins](https://about.gitlab.com/2018/07/30/gitlab-workflow-with-jira-jenkins/)
+- [Introducing Auto Breakfast from GitLab (sort of)](https://about.gitlab.com/2018/06/29/introducing-auto-breakfast-from-gitlab/)
-Contributions are very welcome! You can help your favorite programming
-language users and GitLab by sending a merge request with a guide for that language.
-You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
-to get paid for writing complete articles for GitLab.
+### Mobile development
+
+For help with using GitLab CI/CD for mobile application development, see:
-[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates
+- [How to publish Android apps to the Google Play Store with GitLab and fastlane](https://about.gitlab.com/2019/01/28/android-publishing-with-gitlab-and-fastlane/)
+- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/)
+- [Working with YAML in GitLab CI from the Android perspective](https://about.gitlab.com/2017/11/20/working-with-yaml-gitlab-ci-android/)
+- [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
+- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
index 9e657275d50..589912e7a2a 100644
--- a/doc/ci/examples/artifactory_and_gitlab/index.md
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -2,7 +2,7 @@
redirect_from: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
author: Fabio Busatto
author_gitlab: bikebilly
-level: intermediary
+level: intermediate
article_type: tutorial
date: 2017-08-15
---
@@ -11,7 +11,7 @@ date: 2017-08-15
## Introduction
-In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/)
+In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/)
to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://www.jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
You'll create two different projects:
@@ -19,7 +19,7 @@ You'll create two different projects:
- `simple-maven-dep`: the app built and deployed to Artifactory (available at <https://gitlab.com/gitlab-examples/maven/simple-maven-dep>)
- `simple-maven-app`: the app using the previous one as a dependency (available at <https://gitlab.com/gitlab-examples/maven/simple-maven-app>)
-We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/).
+We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/).
We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
## Create the simple Maven dependency
@@ -102,7 +102,7 @@ parameter in `.gitlab-ci.yml` to use the custom location instead of the default
### Configure GitLab CI/CD for `simple-maven-dep`
-Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and deploy the dependency!
+Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/) to automatically build, test and deploy the dependency!
GitLab CI/CD uses a file in the root of the repo, named `.gitlab-ci.yml`, to read the definitions for jobs
that will be executed by the configured GitLab Runners. You can read more about this file in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/).
@@ -230,7 +230,7 @@ Now you are ready to use the Artifactory repository to resolve dependencies and
You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
-You want to leverage [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and run your awesome application,
+You want to leverage [GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/) to automatically build, test and run your awesome application,
and see if you can get the greeting as expected!
All you need to do is to add the following `.gitlab-ci.yml` to the repo:
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index b47038011de..442d0788d37 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -56,7 +56,7 @@ provide a list of URLs to test, please consult
TIP: **Tip:**
For [GitLab Premium](https://about.gitlab.com/pricing/) users, key metrics are automatically
extracted and shown right in the merge request widget.
-[Learn more on Browser Performance Testing in merge requests](https://docs.gitlab.com/ee//user/project/merge_requests/browser_performance_testing.html).
+[Learn more on Browser Performance Testing in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
## Performance testing on Review Apps
diff --git a/doc/ci/examples/code_quality.md b/doc/ci/examples/code_quality.md
index 3e7d6e7e3f7..186d4527bb6 100644
--- a/doc/ci/examples/code_quality.md
+++ b/doc/ci/examples/code_quality.md
@@ -1,7 +1,7 @@
# Analyze your project's Code Quality
CAUTION: **Caution:**
-The job definition shown below is supported on GitLab 11.5 and later versions.
+The job definition shown below is supported on GitLab 11.11 and later versions.
It also requires the GitLab Runner 11.5 or later.
For earlier versions, use the [previous job definitions](#previous-job-definitions).
@@ -11,27 +11,11 @@ and Docker.
First, you need GitLab Runner with
[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
-Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
-generates the expected report:
+Once you set up the Runner, include the CodeQuality template in your CI config:
```yaml
-code_quality:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env SOURCE_CODE="$PWD"
- --volume "$PWD":/code
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
- artifacts:
- reports:
- codequality: gl-code-quality-report.json
+include:
+ - template: Code-Quality.gitlab-ci.yml
```
The above example will create a `code_quality` job in your CI/CD pipeline which
@@ -54,6 +38,28 @@ While these old job definitions are still maintained they have been deprecated
and may be removed in next major release, GitLab 12.0.
You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
+For GitLab 11.5 and earlier, the job should look like:
+
+```yaml
+code_quality:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - docker run
+ --env SOURCE_CODE="$PWD"
+ --volume "$PWD":/code
+ --volume /var/run/docker.sock:/var/run/docker.sock
+ "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
+ artifacts:
+ reports:
+ codequality: gl-code-quality-report.json
+```
+
For GitLab 11.4 and earlier, the job should look like:
```yaml
diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md
index e8e9c73d1b2..4d41e424f4a 100644
--- a/doc/ci/examples/container_scanning.md
+++ b/doc/ci/examples/container_scanning.md
@@ -1,119 +1,5 @@
-# Container Scanning with GitLab CI/CD
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+---
-CAUTION: **Caution:**
-The job definition shown below is supported on GitLab 11.5 and later versions.
-It also requires the GitLab Runner 11.5 or later.
-For earlier versions, use the [previous job definitions](#previous-job-definitions).
-
-You can check your Docker images (or more precisely the containers) for known
-vulnerabilities by using [Clair](https://github.com/coreos/clair) and
-[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
-for Vulnerability Static Analysis for containers.
-
-First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
-
-Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
-generates the expected report:
-
-```yaml
-container_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- ## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
- CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- CI_APPLICATION_TAG: $CI_COMMIT_SHA
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
- - apk add -U wget ca-certificates
- - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- - mv clair-scanner_linux_amd64 clair-scanner
- - chmod +x clair-scanner
- - touch clair-whitelist.yml
- - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- - retries=0
- - echo "Waiting for clair daemon to start"
- - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- artifacts:
- reports:
- container_scanning: gl-container-scanning-report.json
-```
-
-The above example will create a `container_scanning` job in your CI/CD pipeline, pull
-the image from the [Container Registry](../../user/project/container_registry.md)
-(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
-for possible vulnerabilities. The report will be saved as a
-[Container Scanning report artifact](../yaml/README.md#artifactsreportscontainer_scanning-ultimate)
-that you can later download and analyze.
-Due to implementation limitations we always take the latest Container Scanning artifact available.
-
-If you want to whitelist some specific vulnerabilities, you can do so by defining
-them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
-in our case its named `clair-whitelist.yml`.
-
-TIP: **Tip:**
-For [GitLab Ultimate][ee] users, this information will
-be automatically extracted and shown right in the merge request widget.
-[Learn more on Container Scanning in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
-
-CAUTION: **Caution:**
-Starting with GitLab 11.5, Container Scanning feature is licensed under the name `container_scanning`.
-While the old name `sast_container` is still maintained, it has been deprecated with GitLab 11.5 and
-may be removed in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
-configuration to reflect that change if you are using the `$GITLAB_FEATURES` environment variable.
-
-## Previous job definitions
-
-CAUTION: **Caution:**
-Before GitLab 11.5, Container Scanning job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained they have been deprecated
-and may be removed in next major release, GitLab 12.0.
-You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-container_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- ## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
- CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- CI_APPLICATION_TAG: $CI_COMMIT_SHA
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
- - apk add -U wget ca-certificates
- - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- - mv clair-scanner_linux_amd64 clair-scanner
- - chmod +x clair-scanner
- - touch clair-whitelist.yml
- - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- - retries=0
- - echo "Waiting for clair daemon to start"
- - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- artifacts:
- paths: [gl-container-scanning-report.json]
-```
-
-Alternatively the job name could be `sast:container`
-and the artifact name could be `gl-sast-container-report.json`.
-These names have been deprecated with GitLab 11.0
-and may be removed in next major release, GitLab 12.0.
-
-[ee]: https://about.gitlab.com/pricing/
+This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
diff --git a/doc/ci/examples/dast.md b/doc/ci/examples/dast.md
index ab0ca13d2cf..b676c661267 100644
--- a/doc/ci/examples/dast.md
+++ b/doc/ci/examples/dast.md
@@ -1,102 +1,5 @@
-# Dynamic Application Security Testing with GitLab CI/CD
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dast/index.html'
+---
-CAUTION: **Caution:**
-The job definition shown below is supported on GitLab 11.5 and later versions.
-It also requires the GitLab Runner 11.5 or later.
-For earlier versions, use the [previous job definitions](#previous-job-definitions).
-
-[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_program_analysis)
-is using the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
-to perform an analysis on your running web application.
-Since it is based on [ZAP Baseline](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan)
-DAST will perform passive scanning only;
-it will not actively attack your application.
-
-It can be very useful combined with [Review Apps](../review_apps/index.md).
-
-## Example
-
-First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
-
-Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
-generates the expected report:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- reports:
- dast: gl-dast-report.json
-```
-
-The above example will create a `dast` job in your CI/CD pipeline which will run
-the tests on the URL defined in the `website` variable (change it to use your
-own) and scan it for possible vulnerabilities. The report will be saved as a
-[DAST report artifact](../yaml/README.md#artifactsreportsdast-ultimate)
-that you can later download and analyze.
-Due to implementation limitations we always take the latest DAST artifact available.
-
-It's also possible to authenticate the user before performing DAST checks:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- login_url: "https://example.com/sign-in"
- username: "john.doe@example.com"
- password: "john-doe-password"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website
- --auth-url $login_url
- --auth-username $username
- --auth-password $password || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- reports:
- dast: gl-dast-report.json
-```
-See [zaproxy documentation](https://gitlab.com/gitlab-org/security-products/zaproxy)
-to learn more about authentication settings.
-
-TIP: **Tip:**
-For [GitLab Ultimate][ee] users, this information will
-be automatically extracted and shown right in the merge request widget.
-[Learn more on DAST in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html).
-
-## Previous job definitions
-
-CAUTION: **Caution:**
-Before GitLab 11.5, DAST job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained they have been deprecated
-and may be removed in next major release, GitLab 12.0.
-You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- paths: [gl-dast-report.json]
-```
-
-[ee]: https://about.gitlab.com/pricing/
+This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dast/index.html).
diff --git a/doc/ci/examples/dependency_scanning.md b/doc/ci/examples/dependency_scanning.md
new file mode 100644
index 00000000000..3a8b53b425c
--- /dev/null
+++ b/doc/ci/examples/dependency_scanning.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html'
+---
+
+This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 474a481836a..c622dd86828 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -1,9 +1,10 @@
---
author: Dylan Griffith
author_gitlab: DylanGriffith
-level: intermediary
+level: intermediate
article_type: tutorial
date: 2018-06-07
+last_updated: 2019-04-08
description: "Continuous Deployment of a Spring Boot application to Cloud Foundry with GitLab CI/CD"
---
@@ -77,7 +78,10 @@ image: java:8
stages:
- build
- deploy
-
+
+before_script:
+ - chmod +x mvnw
+
build:
stage: build
script: ./mvnw package
@@ -99,7 +103,7 @@ We've used the `java:8` [docker
image](../../docker/using_docker_images.md) to build
our application as it provides the up-to-date Java 8 JDK on [Docker
Hub](https://hub.docker.com/). We've also added the [`only`
-clause](../../yaml/README.md#only-and-except-simplified)
+clause](../../yaml/README.md#onlyexcept-basic)
to ensure our deployments only happen when we push to the master branch.
Now, since the steps defined in `.gitlab-ci.yml` require credentials to login
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 1e2be2e8475..d6ad00a77da 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -1,9 +1,10 @@
---
author: Ryan Hall
author_gitlab: blitzgren
-level: intermediary
+level: intermediate
article_type: tutorial
date: 2018-03-07
+last_updated: 2019-03-11
---
# DevOps and Game Dev with GitLab CI/CD
@@ -20,7 +21,7 @@ and the basics of game development.
Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
-Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/about),
+Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/),
was essential for the fast pace the team worked at. This tutorial will build upon my
[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
@@ -37,8 +38,8 @@ This will also provide
boilerplate code for starting a browser-based game with the following components:
- Written in [Typescript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
-- Building, running, and testing with [Gulp](http://gulpjs.com/)
-- Unit tests with [Chai](http://chaijs.com/) and [Mocha](https://mochajs.org/)
+- Building, running, and testing with [Gulp](https://gulpjs.com)
+- Unit tests with [Chai](https://www.chaijs.com) and [Mocha](https://mochajs.org/)
- CI/CD with GitLab
- Hosting the codebase on GitLab.com
- Hosting the game on AWS
@@ -317,7 +318,7 @@ allowing us to run our tests by every push.
Our entire `.gitlab-ci.yml` file should now look like this:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -386,7 +387,7 @@ Uploading artifacts to coordinator... ok id=17095874 responseStatus=2
We have our codebase built and tested on every push. To complete the full pipeline with Continuous Deployment,
let's set up [free web hosting with AWS S3](https://aws.amazon.com/s/dm/optimization/server-side-test/free-tier/free_np/) and a job through which our build artifacts get
-deployed. GitLab also has a free static site hosting service we could use, [GitLab Pages](https://about.gitlab.com/features/pages/),
+deployed. GitLab also has a free static site hosting service we could use, [GitLab Pages](https://about.gitlab.com/product/pages/),
however Dark Nova specifically uses other AWS tools that necessitates using `AWS S3`.
Read through this article that describes [deploying to both S3 and GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
and further delves into the principles of GitLab CI/CD than discussed in this article.
@@ -412,7 +413,7 @@ use root security credentials. Proper IAM credential management is beyond the sc
article, but AWS will remind you that using root credentials is unadvised and against their
best practices, as they should. Feel free to follow best practices and use a custom IAM user's
credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
-fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
+fully understand [IAM Best Practices in AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
@@ -454,7 +455,7 @@ Be sure to update the region and S3 URL in that last script command to fit your
Our final configuration file `.gitlab-ci.yml` looks like:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -502,7 +503,7 @@ deploy:
## Conclusion
Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
-[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](http://gulpjs.com/) and [Phaser](https://phaser.io) all playing
+[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](https://gulpjs.com/) and [Phaser](https://phaser.io) all playing
together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](http://darknova.io/).
Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
and unit tests, all running and deployed at every push to master - with shockingly little code.
@@ -510,7 +511,7 @@ Errors can be easily debugged through GitLab's build logs, and within minutes of
you can see the changes live on your game.
Setting up Continuous Integration and Continuous Deployment from the start with Dark Nova enables
-rapid but stable development. We can easily test changes in a separate [environment](../../../ci/environments.md#introduction-to-environments-and-deployments),
+rapid but stable development. We can easily test changes in a separate [environment](../../environments.md),
or multiple environments if needed. Balancing and updating a multiplayer game can be ongoing
and tedious, but having faith in a stable deployment with GitLab CI/CD allows
a lot of breathing room in quickly getting changes to players.
@@ -520,7 +521,7 @@ a lot of breathing room in quickly getting changes to players.
Here are some ideas to further investigate that can speed up or improve your pipeline:
- [Yarn](https://yarnpkg.com) instead of npm
-- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ci-yml) image that can preload dependencies and tools (like AWS CLI)
-- Forward a [custom domain](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
+- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) image that can preload dependencies and tools (like AWS CLI)
+- Forward a [custom domain](https:/docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
- Combine jobs if you find it unnecessary for a small project
- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
index d7afe11f41f..bd221b7145e 100644
--- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
@@ -139,7 +139,7 @@ new browser window interacting with your app as you specified.
Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we
need to do for this:
-1. Set up [CI/CD jobs](../../yaml/README.md#jobs) that actually have a browser available.
+1. Set up [CI/CD jobs](../../yaml/README.md#introduction) that actually have a browser available.
2. Update our WebdriverIO configuration to use those browsers to visit the review apps.
For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
@@ -184,7 +184,7 @@ option as an argument to `npm run confidence-check` on the command line.
However, we still need to tell WebdriverIO which browser is available for it to use.
[GitLab CI/CD makes
-a number of variables available](../../variables/README.html#predefined-variables-environment-variables)
+a number of variables available](../../variables/README.html#predefined-environment-variables)
with information about the current CI job. We can use this information to dynamically set
up our WebdriverIO configuration according to the job that is running. More specifically, we can
tell WebdriverIO what browser to execute the test on depending on the name of the currently running
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/laravel_with_gitlab_and_envoy.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/laravel_with_gitlab_and_envoy.png
deleted file mode 100644
index bc188f83fb1..00000000000
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/laravel_with_gitlab_and_envoy.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
index 4675e20ef79..edeaa011ada 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
Binary files differ
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index d3b6650b0f4..f56d5429fb7 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -2,9 +2,10 @@
redirect_from: 'https://docs.gitlab.com/ee/articles/laravel_with_gitlab_and_envoy/index.html'
author: Mehran Rasulian
author_gitlab: mehranrasulian
-level: intermediary
+level: intermediate
article_type: tutorial
date: 2017-08-31
+last_updated: 2019-03-06
---
# Test and deploy Laravel applications with GitLab CI/CD and Envoy
@@ -115,7 +116,7 @@ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/id_rsa
```
-Now, let's add it to your GitLab project as a [variable](../../variables/README.md#variables).
+Now, let's add it to your GitLab project as a [variable](../../variables/README.md#gitlab-cicd-environment-variables).
Variables are user-defined variables and are stored out of `.gitlab-ci.yml`, for security purposes.
They can be added per project by navigating to the project's **Settings** > **CI/CD**.
@@ -268,7 +269,7 @@ The `releases` directory will hold all our deployments:
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
- cd {{ $releases_dir }}
+ cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
@@ -346,7 +347,7 @@ At the end, our `Envoy.blade.php` file will look like this:
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
- cd {{ $releases_dir }}
+ cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
@@ -374,7 +375,7 @@ You might want to create another Envoy task to do that for you.
We also create the `.env` file in the same path to set up our production environment variables for Laravel.
These are persistent data and will be shared to every new release.
-Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-ci-cd) in this tutorial.
+Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-cicd) in this tutorial.
Now it's time to commit [Envoy.blade.php](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Envoy.blade.php) and push it to the `master` branch.
To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial.
@@ -557,7 +558,7 @@ So we should adjust the configuration of MySQL instance by defining `MYSQL_DATAB
Find out more about MySQL variables at the [official MySQL Docker Image](https://hub.docker.com/r/_/mysql/).
Also set the variables `DB_HOST` to `mysql` and `DB_USERNAME` to `root`, which are Laravel specific variables.
-We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-build).
+We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-job).
```yaml
...
diff --git a/doc/ci/examples/license_management.md b/doc/ci/examples/license_management.md
new file mode 100644
index 00000000000..08704425a75
--- /dev/null
+++ b/doc/ci/examples/license_management.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/license_management/index.html'
+---
+
+This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/license_management/index.html).
diff --git a/doc/ci/examples/sast.md b/doc/ci/examples/sast.md
new file mode 100644
index 00000000000..688cc79d0f6
--- /dev/null
+++ b/doc/ci/examples/sast.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/sast/index.html'
+---
+
+This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
index 3a657b3a3d5..570898b2d23 100644
--- a/doc/ci/examples/sast_docker.md
+++ b/doc/ci/examples/sast_docker.md
@@ -1 +1,5 @@
-This document was moved to [another location](./container_scanning.md).
+---
+redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+---
+
+This document was moved to [another location](../../user/application_security/container_scanning/index.html).
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 61bf68fa0e8..47d20a4e1c1 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -1,4 +1,4 @@
-# Test and Deploy a python application with GitLab CI/CD
+# Test and deploy a Python application with GitLab CI/CD
This example will guide you how to run tests in your Python application and deploy it automatically as Heroku application.
@@ -9,7 +9,12 @@ You can checkout the [example source](https://gitlab.com/ayufan/python-getting-s
This is what the `.gitlab-ci.yml` file looks like for this project:
```yaml
+stages:
+ - test
+ - deploy
+
test:
+ stage: test
script:
# this configures Django application to use attached postgres database that is run on `postgres` host
- export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
@@ -19,7 +24,7 @@ test:
- python manage.py test
staging:
- type: deploy
+ stage: deploy
script:
- apt-get update -qy
- apt-get install -y ruby-dev
@@ -29,7 +34,7 @@ staging:
- master
production:
- type: deploy
+ stage: deploy
script:
- apt-get update -qy
- apt-get install -y ruby-dev
@@ -65,7 +70,7 @@ First install [Docker Engine](https://docs.docker.com/installation/).
To build this project you also need to have [GitLab Runner](https://docs.gitlab.com/runner).
You can use public runners available on `gitlab.com`, but you can register your own:
-```
+```sh
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index 46e6efccaf8..3a0ddf001b8 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -1,4 +1,4 @@
-# Test and Deploy a ruby application with GitLab CI/CD
+# Test and deploy a Ruby application with GitLab CI/CD
This example will guide you how to run tests in your Ruby on Rails application and deploy it automatically as Heroku application.
@@ -58,10 +58,10 @@ You can do this through the [Dashboard](https://dashboard.heroku.com/).
## Create Runner
First install [Docker Engine](https://docs.docker.com/installation/).
-To build this project you also need to have [GitLab Runner](https://about.gitlab.com/gitlab-ci/#gitlab-runner).
+To build this project you also need to have [GitLab Runner](https://docs.gitlab.com/runner/).
You can use public runners available on `gitlab.com`, but you can register your own:
-```
+```sh
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index 66bfa41cad9..e1164b8d03a 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -1,4 +1,4 @@
-# Test and deploy to Heroku a Scala application
+# Test and deploy a Scala application to Heroku
This example demonstrates the integration of GitLab CI with Scala
applications using SBT. Checkout the example
@@ -55,8 +55,8 @@ You can use other versions of Scala and SBT by defining them in
Add the `Coverage was \[\d+.\d+\%\]` regular expression in the
**Settings âž” Pipelines âž” Coverage report** project setting to
-retrieve the [test coverage] rate from the build trace and have it
-displayed with your jobs.
+retrieve the [test coverage](../../user/project/pipelines/settings.md#test-coverage-report-badge)
+rate from the build trace and have it displayed with your jobs.
**Pipelines** must be enabled for this option to appear.
@@ -69,8 +69,5 @@ in the `.gitlab-ci.yml` file with your application's name.
## Heroku API key
You can look up your Heroku API key in your
-[account](https://dashboard.heroku.com/account). Add a secure [variable] with
+[account](https://dashboard.heroku.com/account). Add a [protected variable](../variables/README.md#protected-environment-variables) with
this value in **Project âž” Variables** with key `HEROKU_API_KEY`.
-
-[variable]: ../variables/README.md#user-defined-variables-secure-variables
-[test coverage]: ../../user/project/pipelines/settings.md#test-coverage-report-badge
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/img/job-succeeded.png b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/img/job-succeeded.png
deleted file mode 100644
index 77b05f55f88..00000000000
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/img/job-succeeded.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index f24e79355aa..4a5fda661df 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -4,13 +4,14 @@ author_gitlab: Hostert
level: beginner
article_type: tutorial
date: 2018-02-20
+last_updated: 2019-03-06
---
# Testing a Phoenix application with GitLab CI/CD
[Phoenix][phoenix-site] is a web development framework written in [Elixir][elixir-site], which is a
functional language designed for productivity and maintainability that runs on the
-[Erlang VM][erlang-site]. Erlang VM is really really fast and can handle very large numbers of
+[Erlang VM](https://www.erlang.org). Erlang VM is really really fast and can handle very large numbers of
simultaneous users.
That's why we're hearing so much about Phoenix today.
@@ -26,7 +27,7 @@ and GitLab UI._
### What is Phoenix?
[Phoenix][phoenix-site] is a web development framework written in [Elixir][elixir-site] very useful
- to build fast, reliable, and high-performance applications, as it uses [Erlang VM][erlang-site].
+ to build fast, reliable, and high-performance applications, as it uses [Erlang VM](https://www.erlang.org).
Many components and concepts are similar to Ruby on Rails or Python's Django. High developer
productivity and high application performance are only a few advantages on learning how to use it.
@@ -177,7 +178,7 @@ environment it can run. Since we will work with a single environment, we'll edit
configuration file (`test.exs`).
But, why do we need to adjust our configuration? Well, GitLab CI/CD builds and tests our code in one
-isolated virtual machine, called [Runner][runner-site], using Docker technology. In this Runner,
+isolated virtual machine, called [Runner](../../runners/README.md), using Docker technology. In this Runner,
GitLab CI/CD has access to everything our Phoenix application need to run, exactly as we have in our
`localhost`, but we have to tell GitLab CI/CD where to create and find this database using system
variables. This way, GitLab CI/CD will create our test database inside the Runner, just like we do
@@ -405,7 +406,6 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams
[elixir-site]: http://elixir-lang.org/ "Elixir"
[elixir-mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html "Introduction to mix"
[elixir-docs]: http://elixir-lang.org/getting-started/introduction.html "Elixir Documentation"
-[erlang-site]: http://erlang.org "Erlang"
[elixir-install]: https://elixir-lang.org/install.html "Elixir Installation"
[ecto]: http://hexdocs.pm/ecto "Ecto"
[ecto-repo]: https://hexdocs.pm/ecto/Ecto.html#module-repositories "Ecto Repositories"
@@ -417,7 +417,6 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams
[ci-docs]: ../../README.md "GitLab CI/CD Documentation"
[skipping-jobs]: ../../yaml/README.md#skipping-jobs "Skipping Jobs"
[gitlab-runners]: ../../runners/README.md "GitLab Runners Documentation"
-[runner-site]: ../../runners/README.md#runners "Runners"
[docker-image]: https://hub.docker.com/r/trenpixster/elixir/ "Elixir Docker Image"
[using-docker]: ../../docker/using_docker_images.md "Using Docker Images"
[hello-gitlab]: https://gitlab.com/Hostert/hello_gitlab_ci "Hello GitLab CI/CD"
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 37078230b34..551044dd76f 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using Git submodules with GitLab CI
> **Notes:**
diff --git a/doc/ci/img/add_file_template_11_10.png b/doc/ci/img/add_file_template_11_10.png
new file mode 100644
index 00000000000..ca04d72615b
--- /dev/null
+++ b/doc/ci/img/add_file_template_11_10.png
Binary files differ
diff --git a/doc/ci/img/cicd_pipeline_infograph.png b/doc/ci/img/cicd_pipeline_infograph.png
deleted file mode 100644
index 9ddd4aa828b..00000000000
--- a/doc/ci/img/cicd_pipeline_infograph.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/img/deployments_view.png b/doc/ci/img/deployments_view.png
index 45d882b536c..12090434bef 100644
--- a/doc/ci/img/deployments_view.png
+++ b/doc/ci/img/deployments_view.png
Binary files differ
diff --git a/doc/ci/img/environments_available.png b/doc/ci/img/environments_available.png
index 7ab92838ece..48fc6effc2d 100644
--- a/doc/ci/img/environments_available.png
+++ b/doc/ci/img/environments_available.png
Binary files differ
diff --git a/doc/ci/img/environments_mr_review_app.png b/doc/ci/img/environments_mr_review_app.png
index 61b7e9fe77c..6a7b7ce5679 100644
--- a/doc/ci/img/environments_mr_review_app.png
+++ b/doc/ci/img/environments_mr_review_app.png
Binary files differ
diff --git a/doc/ci/img/metrics_reports.png b/doc/ci/img/metrics_reports.png
new file mode 100644
index 00000000000..ffd9f6830a2
--- /dev/null
+++ b/doc/ci/img/metrics_reports.png
Binary files differ
diff --git a/doc/ci/img/multi_pipeline_mini_graph.gif b/doc/ci/img/multi_pipeline_mini_graph.gif
new file mode 100644
index 00000000000..de49ba5aa12
--- /dev/null
+++ b/doc/ci/img/multi_pipeline_mini_graph.gif
Binary files differ
diff --git a/doc/ci/img/multi_project_pipeline_graph.png b/doc/ci/img/multi_project_pipeline_graph.png
new file mode 100644
index 00000000000..723a455cb4a
--- /dev/null
+++ b/doc/ci/img/multi_project_pipeline_graph.png
Binary files differ
diff --git a/doc/ci/img/pipelines-goal.png b/doc/ci/img/pipelines-goal.png
deleted file mode 100644
index f15716d0b8f..00000000000
--- a/doc/ci/img/pipelines-goal.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/img/types-of-pipelines.png b/doc/ci/img/types-of-pipelines.png
deleted file mode 100644
index 829a53d5d52..00000000000
--- a/doc/ci/img/types-of-pipelines.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 2a4160f62b0..1387d4df500 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Interactive Web Terminals
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
@@ -54,3 +58,8 @@ terminal will block the job from finishing for the duration configured in
close the terminal window.
![finished job with terminal open](img/finished_job_with_terminal_open.png)
+
+## Interactive Web Terminals for the Web IDE **[ULTIMATE ONLY]**
+
+Read the Web IDE docs to learn how to run [Interactive Terminals through the Web IDE](../../user/project/web_ide/index.md).
+
diff --git a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
new file mode 100644
index 00000000000..204e9c462e5
--- /dev/null
+++ b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
Binary files differ
diff --git a/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png
new file mode 100644
index 00000000000..5089a1088c5
--- /dev/null
+++ b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png
Binary files differ
diff --git a/doc/ci/introduction/img/job_running.png b/doc/ci/introduction/img/job_running.png
new file mode 100644
index 00000000000..d5f922ceb8c
--- /dev/null
+++ b/doc/ci/introduction/img/job_running.png
Binary files differ
diff --git a/doc/ci/introduction/img/pipeline_status.png b/doc/ci/introduction/img/pipeline_status.png
new file mode 100644
index 00000000000..96881f072e1
--- /dev/null
+++ b/doc/ci/introduction/img/pipeline_status.png
Binary files differ
diff --git a/doc/ci/introduction/img/rollback.png b/doc/ci/introduction/img/rollback.png
new file mode 100644
index 00000000000..38e0552f4f1
--- /dev/null
+++ b/doc/ci/introduction/img/rollback.png
Binary files differ
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
new file mode 100644
index 00000000000..bd2b9b099f2
--- /dev/null
+++ b/doc/ci/introduction/index.md
@@ -0,0 +1,223 @@
+---
+description: "An overview of Continuous Integration, Continuous Delivery, and Continuous Deployment, as well as an introduction to GitLab CI/CD."
+type: concepts
+---
+
+# Introduction to CI/CD with GitLab
+
+In this document we'll present an overview of the concepts of Continuous Integration,
+Continuous Delivery, and Continuous Deployment, as well as an introduction to
+GitLab CI/CD.
+
+## Introduction to CI/CD methodologies
+
+The continuous methodologies of software development are based on
+automating the execution of scripts to minimize the chance of
+introducing errors while developing applications. They require
+less human intervention or even no intervention at all, from the
+development of new code until its deployment.
+
+It involves continuously building, testing, and deploying code
+changes at every small iteration, reducing the chance of developing
+new code based on bugged or failed previous versions.
+
+There are three main approaches to this methodology, each of them
+to be applied according to what best suits your strategy.
+
+### Continuous Integration
+
+Consider an application which has its code stored in a Git
+repository in GitLab. Developers push code changes every day,
+multiple times a day. For every push to the repository, you
+can create a set of scripts to build and test your application
+automatically, decreasing the chance of introducing errors to your app.
+
+This practice is known as [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration);
+for every change submitted to an application - even to development branches -
+it's built and tested automatically and continuously, ensuring the
+introduced changes pass all tests, guidelines, and code compliance
+standards you established for your app.
+
+[GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce) is an
+example of using Continuous Integration as a software
+development method. For every push to the project, there's a set
+of scripts the code is checked against.
+
+### Continuous Delivery
+
+[Continuous Delivery](https://continuousdelivery.com/) is a step
+beyond Continuous Integration. Your application is not only
+built and tested at every code change pushed to the codebase,
+but, as an additional step, it's also deployed continuously, though
+the deployments are triggered manually.
+
+This method ensures the code is checked automatically but requires
+human intervention to manually and strategically trigger the deployment
+of the changes.
+
+### Continuous Deployment
+
+[Continuous Deployment](https://www.airpair.com/continuous-deployment/posts/continuous-deployment-for-practical-people)
+is also a further step beyond Continuous Integration, similar to
+Continuous Delivery. The difference is that instead of deploying your
+application manually, you set it to be deployed automatically. It does
+not require human intervention at all to have your application
+deployed.
+
+## Introduction to GitLab CI/CD
+
+GitLab CI/CD is a powerful tool built into GitLab that allows you
+to apply all the continuous methods (Continuous Integration,
+Delivery, and Deployment) to your software with no third-party
+application or integration needed.
+
+### How GitLab CI/CD works
+
+To use GitLab CI/CD, all you need is an application codebase hosted in a
+Git repository, and for your build, test, and deployment
+scripts to be specified in a file called [`.gitlab-ci.yml`](../yaml/README.md),
+located in the root path of your repository.
+
+In this file, you can define the scripts you want to run, define include and
+cache dependencies, choose commands you want to run in sequence
+and those you want to run in parallel, define where you want to
+deploy your app, and specify whether you will want to run the scripts automatically
+or trigger any of them manually. Once you're familiar with
+GitLab CI/CD you can add more advanced steps into the configuration file.
+
+To add scripts to that file, you'll need to organize them in a
+sequence that suits your application and are in accordance with
+the tests you wish to perform. To visualize the process, imagine
+that all the scripts you add to the configuration file are the
+same as the commands you run on a terminal in your computer.
+
+Once you've added your `.gitlab-ci.yml` configuration file to your
+repository, GitLab will detect it and run your scripts with the
+tool called [GitLab Runner](https://docs.gitlab.com/runner/), which
+works similarly to your terminal.
+
+The scripts are grouped into **jobs**, and together they compose
+a **pipeline**. A minimalist example of `.gitlab-ci.yml` file
+could contain:
+
+```yml
+before_script:
+ - apt-get install rubygems ruby-dev -y
+
+run-test:
+ script:
+ - ruby --version
+```
+
+The `before_script` attribute would install the dependencies
+for your app before running anything, and a **job** called
+`run-test` would print the Ruby version of the current system.
+Both of them compose a **pipeline** triggered at every push
+to any branch of the repository.
+
+GitLab CI/CD not only executes the jobs you've
+set, but also shows you what's happening during execution, as you
+would see in your terminal:
+
+![job running](img/job_running.png)
+
+You create the strategy for your app and GitLab runs the pipeline
+for you according to what you've defined. Your pipeline status is also
+displayed by GitLab:
+
+![pipeline status](img/pipeline_status.png)
+
+At the end, if anything goes wrong, you can easily
+[roll back](../environments.md#retrying-and-rolling-back) all the changes:
+
+![rollback button](img/rollback.png)
+
+### Basic CI/CD workflow
+
+Consider the following example for how GitLab CI/CD fits in a
+common development workflow.
+
+Assume that you have discussed a code implementation in an issue
+and worked locally on your proposed changes. Once you push your
+commits to a feature branch in a remote repository in GitLab,
+the CI/CD pipeline set for your project is triggered. By doing
+so, GitLab CI/CD:
+
+- Runs automated scripts (sequential or parallel) to:
+ - Build and test your app.
+ - Preview the changes per merge request with Review Apps, as you
+ would see in your `localhost`.
+
+Once you're happy with your implementation:
+
+- Get your code reviewed and approved.
+- Merge the feature branch into the default branch.
+ - GitLab CI/CD deploys your changes automatically to a production environment.
+- And finally, you and your team can easily roll it back if something goes wrong.
+
+![GitLab workflow example](img/gitlab_workflow_example_11_9.png)
+
+GitLab CI/CD is capable of doing a lot more, but this workflow
+exemplifies GitLab's ability to track the entire process,
+without the need of any external tool to deliver your software.
+And, most usefully, you can visualize all the steps through
+the GitLab UI.
+
+#### A deeper look into the CI/CD basic workflow
+
+If we take a deeper look into the basic workflow, we can see
+the features available in GitLab at each stage of the DevOps
+lifecycle, as shown on the illustration below.
+
+![Deeper look into the basic CI/CD workflow](img/gitlab_workflow_example_extended_11_11.png)
+
+If you look at the image from the left to the right,
+you'll see some of the features available in GitLab
+according to each stage (Verify, Package, Release).
+
+1. **Verify**:
+ - Automatically build and test your application with Continuous Integration.
+ - Analyze your source code quality with [GitLab Code Quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html). **[STARTER]**
+ - Determine the performance impact of code changes with [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). **[PREMIUM]**
+ - Perform a series of tests, such as [Container Scanning](https://docs.gitlab.com/ee/ci/examples/container_scanning.html) **[ULTIMATE]**, [Dependency Scanning](https://docs.gitlab.com/ee/ci/examples/dependency_scanning.html) **[ULTIMATE]**, and [JUnit tests](../junit_test_reports.md).
+ - Deploy your changes with [Review Apps](../review_apps/index.md) to preview the app changes on every branch.
+1. **Package**:
+ - Store Docker images with [Container Registry](../../user/project/container_registry.md).
+ - Store NPM packages with [NPM Registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html). **[PREMIUM]**
+ - Store Maven artifacts with [Maven Repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html). **[PREMIUM]**
+1. **Release**:
+ - Continuous Deployment, automatically deploying your app to production.
+ - Continuous Delivery, manually click to deploy your app to production.
+ - Deploy static websites with [GitLab Pages](../../user/project/pages/index.md).
+ - Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html). **[PREMIUM]**
+ - Deploy your features behind [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html). **[PREMIUM]**
+ - Add release notes to any Git tag with [GitLab Releases](../../user/project/releases/index.md).
+ - View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html). **[PREMIUM]**
+ - Deploy your application to a production environment in a Kubernetes cluster with [Auto Deploy](../../topics/autodevops/index.md#auto-deploy).
+
+With GitLab CI/CD you can also:
+
+- Easily set up your app's entire lifecycle with [Auto DevOps](../../topics/autodevops/index.md).
+- Deploy your app to different [environments](../environments.md).
+- Install your own [GitLab Runner](https://docs.gitlab.com/runner/).
+- [Schedule pipelines](../../user/project/pipelines/schedules.md).
+- Check for app vulnerabilities with [Security Test reports](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate). **[ULTIMATE]**
+
+To see all CI/CD features, navigate back to the [CI/CD index](../README.md).
+
+### Setting up GitLab CI/CD for the first time
+
+To get started with GitLab CI/CD, you need to familiarize yourself
+with the [`.gitlab-ci.yml`](../yaml/README.md) configuration file
+syntax and with its attributes.
+
+This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started_part_four.md), for deploying static websites.
+Although it's meant for users who want to write their own Pages
+script from scratch, it also serves as an introduction to the setup process for GitLab CI/CD.
+It covers the very first general steps of writing a CI/CD configuration
+file, so we recommend you read through it to understand GitLab's CI/CD
+logic, and learn how to write your own script (or tweak an
+existing one) for any application.
+
+For a deep view of GitLab's CI/CD configuration options, check the
+[`.gitlab-ci.yml` full reference](../yaml/README.md).
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index cf18c6d9660..fa78f53f563 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# JUnit test reports
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45318) in GitLab 11.2.
@@ -71,11 +75,11 @@ merge request widget.
NOTE: **Note:**
If you also want the ability to browse JUnit output files, include the
-[`artifacts:paths`](yaml/README.md#artifactspaths) keyword.
+[`artifacts:paths`](yaml/README.md#artifactspaths) keyword. An example of this is shown in the Ruby example below.
### Ruby example
-Use the following job in `.gitlab-ci.yml`:
+Use the following job in `.gitlab-ci.yml`. This includes the `artifacts:paths` keyword to provide a link to the JUnit output file.
```yaml
## Use https://github.com/sj26/rspec_junit_formatter to generate a JUnit report with rspec
@@ -85,6 +89,8 @@ ruby:
- bundle install
- rspec spec/lib/ --format RspecJunitFormatter --out rspec.xml
artifacts:
+ paths:
+ - rspec.xml
reports:
junit: rspec.xml
```
@@ -113,8 +119,8 @@ There are a few tools that can produce JUnit reports in Java.
In the following example, `gradle` is used to generate the test reports.
If there are multiple test tasks defined, `gradle` will generate multiple
-directories under `build/test-results/`. In that case, you can leverage regex
-matching by defining the following path: `build/test-results/test/TEST-*.xml`:
+directories under `build/test-results/`. In that case, you can leverage glob
+matching by defining the following path: `build/test-results/test/**/TEST-*.xml`:
```yaml
java:
@@ -123,7 +129,7 @@ java:
- gradle test
artifacts:
reports:
- junit: build/test-results/test/TEST-*.xml
+ junit: build/test-results/test/**/TEST-*.xml
```
#### Maven
diff --git a/doc/ci/large_repositories/index.md b/doc/ci/large_repositories/index.md
new file mode 100644
index 00000000000..29d649ad717
--- /dev/null
+++ b/doc/ci/large_repositories/index.md
@@ -0,0 +1,242 @@
+---
+type: reference
+---
+
+# Optimizing GitLab for large repositories
+
+Large repositories consisting of more than 50k files in a worktree
+often require special consideration because of
+the time required to clone and check out.
+
+GitLab and GitLab Runner handle this scenario well
+but require optimized configuration to efficiently perform its
+set of operations.
+
+The general guidelines for handling big repositories are simple.
+Each guideline is described in more detail in the sections below:
+
+- Always fetch incrementally. Do not clone in a way that results in recreating all of the worktree.
+- Always use shallow clone to reduce data transfer. Be aware that this puts more burden
+ on GitLab instance due to higher CPU impact.
+- Control the clone directory if you heavily use a fork-based workflow.
+- Optimize `git clean` flags to ensure that you remove or keep data that might affect or speed-up your build.
+
+## Shallow cloning
+
+> Introduced in GitLab Runner 8.9.
+
+GitLab and GitLab Runner always perform a full clone by default.
+While it means that all changes from GitLab are received,
+it often results in receiving extra commit logs.
+
+Ideally, you should always use `GIT_DEPTH` with a small number
+like 10. This will instruct GitLab Runner to perform shallow clones.
+Shallow clones makes Git request only the latest set of changes for a given branch,
+up to desired number of commits as defined by the `GIT_DEPTH` variable.
+
+This significantly speeds up fetching of changes from Git repositories,
+especially if the repository has a very long backlog consisting of number
+of big files as we effectively reduce amount of data transfer.
+
+The following example makes GitLab Runner shallow clone to fetch only a given branch,
+it does not fetch any other branches nor tags.
+
+```yaml
+variables:
+ GIT_DEPTH: 10
+
+test:
+ script:
+ - ls -al
+```
+
+## Git strategy
+
+> Introduced in GitLab Runner 8.9.
+
+By default, GitLab is configured to always prefer the `GIT_STRATEGY: fetch` strategy.
+The `GIT_STRATEGY: fetch` strategy will re-use existing worktrees if found
+on disk. This is different to the `GIT_STRATEGY: clone` strategy
+as in case of clones, if a worktree is found, it is removed before clone.
+
+Usage of `fetch` is preferred because it reduces the amount of data to transfer and
+does not really impact the operations that you might do on a repository from CI.
+
+However, `fetch` does require access to the previous worktree. This works
+well when using the `shell` or `docker` executor because these
+try to preserve worktrees and try to re-use them by default.
+
+This does not work today for `kubernetes` executor and has limitations when using
+`docker+machine`. `kubernetes` executor today always clones into ephemeral directory.
+
+GitLab also offers the `GIT_STRATEGY: none` strategy. This disables any `fetch` and `checkout` commands
+done by GitLab, requiring you to do them.
+
+## Git clone path
+
+> Introduced in GitLab Runner 11.10.
+
+[`GIT_CLONE_PATH`](../yaml/README.md#custom-build-directories) allows you to
+control where you clone your sources. This can have implications if you
+heavily use big repositories with fork workflow.
+
+Fork workflow from GitLab Runner's perspective is stored as a separate repository
+with separate worktree. That means that GitLab Runner cannot optimize the usage
+of worktrees and you might have to instruct GitLab Runner to use that.
+
+In such cases, ideally you want to make the GitLab Runner executor be used only used only
+for the given project and not shared across different projects to make this
+process more efficient.
+
+The [`GIT_CLONE_PATH`](../yaml/README.md#custom-build-directories) has to be
+within the `$CI_BUILDS_DIR`. Currently, it is impossible to pick any path
+from disk.
+
+## Git clean flags
+
+> Introduced in GitLab Runner 11.10.
+
+[`GIT_CLEAN_FLAGS`](../yaml/README.md#git-clean-flags) allows you to control
+whether or not you require the `git clean` command to be executed for each CI
+job. By default, GitLab ensures that you have your worktree on the given SHA,
+and that your repository is clean.
+
+[`GIT_CLEAN_FLAGS`](../yaml/README.md#git-clean-flags) is disabled when set
+to `none`. On very big repositories, this might be desired because `git
+clean` is disk I/O intensive. Controlling that with `GIT_CLEAN_FLAGS: -ffdx
+-e .build/`, for example, allows you to control and disable removal of some
+directories within the worktree between subsequent runs, which can speed-up
+the incremental builds. This has the biggest effect if you re-use existing
+machines, and have an existing worktree that you can re-use for builds.
+
+For exact parameters accepted by
+[`GIT_CLEAN_FLAGS`](../yaml/README.md#git-clean-flags), see the documentation
+for [git clean](https://git-scm.com/docs/git-clean). The available parameters
+are dependent on Git version.
+
+## Fork-based workflow
+
+> Introduced in GitLab Runner 11.10.
+
+Following the guidelines above, lets imagine that we want to:
+
+- Optimize for a big project (more than 50k files in directory).
+- Use forks-based workflow for contributing.
+- Reuse existing worktrees. Have preconfigured runners that are pre-cloned with repositories.
+- Runner assigned only to project and all forks.
+
+Lets consider the following two examples, one using `shell` executor and
+other using `docker` executor.
+
+### `shell` executor example
+
+Lets assume that you have the following [config.toml](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
+
+```toml
+concurrent = 4
+
+[[runners]]
+ url = "GITLAB_URL"
+ token = "TOKEN"
+ executor = "shell"
+ builds_dir = "/builds"
+ cache_dir = "/cache"
+
+ [runners.custom_build_dir]
+ enabled = true
+```
+
+This `config.toml`:
+
+- Uses the `shell` executor,
+- Specifies a custom `/builds` directory where all clones will be stored.
+- Enables the ability to specify `GIT_CLONE_PATH`,
+- Runs at most 4 jobs at once.
+
+### `docker` executor example
+
+Lets assume that you have the following [config.toml](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
+
+```toml
+concurrent = 4
+
+[[runners]]
+ url = "GITLAB_URL"
+ token = "TOKEN"
+ executor = "docker"
+ builds_dir = "/builds"
+ cache_dir = "/cache"
+
+ [runners.docker]
+ volumes = ["/builds:/builds", "/cache:/cache"]
+```
+
+This `config.toml`:
+
+- Uses the `docker` executor,
+- Specifies a custom `/builds` directory on disk where all clones will be stored.
+ We host mount the `/builds` directory to make it reusable between subsequent runs
+ and be allowed to override the cloning strategy.
+- Doesn't enable the ability to specify `GIT_CLONE_PATH` as it is enabled by default.
+- Runs at most 4 jobs at once.
+
+### Our `.gitlab-ci.yml`
+
+Once we have the executor configured, we need to fine tune our `.gitlab-ci.yml`.
+
+Our pipeline will be most performant if we use the following `.gitlab-ci.yml`:
+
+```yaml
+variables:
+ GIT_DEPTH: 10
+ GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_NAME
+
+build:
+ script: ls -al
+```
+
+The above configures a:
+
+- Shallow clone of 10, to speed up subsequent `git fetch` commands.
+- Custom clone path to make it possible to re-use worktrees between parent project and all forks
+ because we use the same clone path for all forks.
+
+Why use `$CI_CONCURRENT_ID`? The main reason is to ensure that worktrees used are not conflicting
+between projects. The `$CI_CONCURRENT_ID` represents a unique identifier within the given executor,
+so as long as we use it to construct the path, it is guaranteed that this directory will not conflict
+with other concurrent jobs running.
+
+### Store custom clone options in `config.toml`
+
+Ideally, all job-related configuration should be stored in `.gitlab-ci.yml`.
+However, sometimes it is desirable to make these schemes part of Runner configuration.
+
+In the above example of Forks, making this configuration discoverable for users may be preferred,
+but this brings administrative overhead as the `.gitlab-ci.yml` needs to be updated for each branch.
+In such cases, it might be desirable to keep the `.gitlab-ci.yml` clone path agnostic, but make it
+a configuration of Runner.
+
+We can extend our [config.toml](https://docs.gitlab.com/runner/configuration/advanced-configuration.html)
+with the following specification that will be used by Runner if `.gitlab-ci.yml` will not override it:
+
+```toml
+concurrent = 4
+
+[[runners]]
+ url = "GITLAB_URL"
+ token = "TOKEN"
+ executor = "docker"
+ builds_dir = "/builds"
+ cache_dir = "/cache"
+
+ environment = [
+ "GIT_DEPTH=10",
+ "GIT_CLONE_PATH=$CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_NAME"
+ ]
+
+ [runners.docker]
+ volumes = ["/builds:/builds", "/cache:/cache"]
+```
+
+This makes the cloning configuration to be part of given Runner,
+and does not require us to update each `.gitlab-ci.yml`.
diff --git a/doc/ci/merge_request_pipelines/img/merge_request.png b/doc/ci/merge_request_pipelines/img/merge_request.png
index cf9c628e9a0..d03fdc6a885 100644
--- a/doc/ci/merge_request_pipelines/img/merge_request.png
+++ b/doc/ci/merge_request_pipelines/img/merge_request.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/img/merge_request_pipeline.png b/doc/ci/merge_request_pipelines/img/merge_request_pipeline.png
new file mode 100644
index 00000000000..58d5581f628
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/img/merge_request_pipeline.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/img/merge_request_pipeline_config.png b/doc/ci/merge_request_pipelines/img/merge_request_pipeline_config.png
new file mode 100644
index 00000000000..0a84e61d284
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/img/merge_request_pipeline_config.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/img/pipeline_detail.png b/doc/ci/merge_request_pipelines/img/pipeline_detail.png
deleted file mode 100644
index 6094a0975fb..00000000000
--- a/doc/ci/merge_request_pipelines/img/pipeline_detail.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index 2af0a03cbe4..fe2fc790505 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -1,21 +1,38 @@
+---
+type: reference
+---
+
# Pipelines for merge requests
+NOTE: **Note**:
+As of GitLab 11.10, pipelines for merge requests require GitLab Runner 11.9
+or higher due to the [recent refspecs
+changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25504).
+Anything lower will cause the pipeline to fail.
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310) in GitLab 11.6.
-Usually, when you create a new merge request, a pipeline runs on the
+Usually, when you create a new merge request, a pipeline runs with the
new change and checks if it's qualified to be merged into a target branch. This
-pipeline should contain only necessary jobs for checking the new changes.
+pipeline should contain only necessary jobs for validating the new changes.
For example, unit tests, lint checks, and [Review Apps](../review_apps/index.md)
are often used in this cycle.
With pipelines for merge requests, you can design a specific pipeline structure
-for merge requests. All you need to do is just adding `only: [merge_requests]` to
-the jobs that you want it to run for only merge requests.
-Every time, when developers create or update merge requests, a pipeline runs on
-their new commits at every push to GitLab.
+for when you are running a pipeline in a merge request. This
+could be either adding or removing steps in the pipeline, to make sure that
+your pipelines are as efficient as possible.
+
+## Configuring pipelines for merge requests
+
+To configure pipelines for merge requests, add the `only: merge_requests` parameter to
+the jobs that you want to run only for merge requests.
+
+Then, when developers create or update merge requests, a pipeline runs
+every time a commit is pushed to GitLab.
NOTE: **Note**:
-If you use both this feature and [Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
+If you use this feature with [merge when pipeline succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
pipelines for merge requests take precedence over the other regular pipelines.
For example, consider the following [`.gitlab-ci.yml`](../yaml/README.md):
@@ -25,9 +42,7 @@ build:
stage: build
script: ./build
only:
- - branches
- - tags
- - merge_requests
+ - master
test:
stage: test
@@ -38,33 +53,89 @@ test:
deploy:
stage: deploy
script: ./deploy
+ only:
+ - master
```
-After the merge request is updated with new commits, GitLab detects that changes
-have occurred and creates a new pipeline for the merge request.
-The pipeline fetches the latest code from the source branch and run tests against it.
-In the above example, the pipeline contains only `build` and `test` jobs.
-Since the `deploy` job doesn't have the `only: [merge_requests]` rule,
-deployment jobs will not happen in the merge request.
+After the merge request is updated with new commits:
-Pipelines tagged as **merge request** indicate that they were triggered
-when a merge request was created or updated.
+- GitLab detects that changes have occurred and creates a new pipeline for the merge request.
+- The pipeline fetches the latest code from the source branch and run tests against it.
+
+In the above example, the pipeline contains only a `test` job.
+Since the `build` and `deploy` jobs don't have the `only: merge_requests` parameter,
+they will not run in the merge request.
+
+Pipelines tagged with the **detached** badge indicate that they were triggered
+when a merge request was created or updated. For example:
![Merge request page](img/merge_request.png)
-The same tag is shown on the pipeline's details:
+## Pipelines for Merged Results **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
+> This feature is disabled by default until we resolve issues with [contention handling](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186), but [can be enabled manually](#enabling-pipelines-for-merged-results).
+
+It's possible for your source and target branches to diverge, which can result
+in the scenario that source branch's pipeline was green, the target's pipeline was green,
+but the combined output fails.
+
+By having your merge request pipeline automatically
+create a new ref that contains the merge result of the source and target branch
+(then running a pipeline on that ref), we can better test that the combined result
+is also valid.
+
+GitLab can run pipelines for merge requests
+on this merged result. That is, where the source and target branches are combined into a
+new ref and a pipeline for this ref validates the result prior to merging.
+
+![Merge request pipeline as the head pipeline](img/merge_request_pipeline.png)
-![Pipeline's details](img/pipeline_detail.png)
+There are some cases where creating a combined ref is not possible or not wanted.
+For example, a source branch that has conflicts with the target branch
+or a merge request that is still in WIP status. In this case, the merge request pipeline falls back to a "detached" state
+and runs on the source branch ref as if it was a regular pipeline.
+
+The detached state serves to warn you that you are working in a situation
+subjected to merge problems, and helps to highlight that you should
+get out of WIP status or resolve merge conflicts as soon as possible.
+
+### Enabling Pipelines for Merged Results
+
+To enable pipelines on merged results at the project level:
+
+1. Visit your project's **Settings > General** and expand **Merge requests**.
+1. Check **Merge pipelines will try to validate the post-merge result prior to merging**.
+1. Click **Save changes** button.
+
+![Merge request pipeline config](img/merge_request_pipeline_config.png)
+
+CAUTION: **Warning:**
+Make sure your `gitlab-ci.yml` file is [configured properly for pipelines for merge requests](#configuring-pipelines-for-merge-requests),
+otherwise pipelines for merged results won't run and your merge requests will be stuck in an unresolved state.
+
+### Pipelines for Merged Result's limitations
+
+- This feature requires [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner) 11.9 or newer.
+- This feature requires [Gitaly](https://gitlab.com/gitlab-org/gitaly) 1.21.0 or newer.
+- After the merge request pipeline succeeds, if the target branch has moved forward, the result of the pipeline is stale and must be retried. In busy repos, this can become a problem as it is highly probable that the target branch will have moved ahead. Improvements are [planned](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186) for future versions of GitLab.
+- Forking/cross-repo workflows are not currently supported. To follow progress, see [#9713](https://gitlab.com/gitlab-org/gitlab-ee/issues/9713).
+- This feature is not available for [fast forward merges](../../user/project/merge_requests/fast_forward_merge.md) yet. To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-ce/issues/58226).
## Excluding certain jobs
-The behavior of the `only: merge_requests` rule is such that _only_ jobs with
-that rule are run in the context of a merge request; no other jobs will be run.
+The behavior of the `only: merge_requests` parameter is such that _only_ jobs with
+that parameter are run in the context of a merge request; no other jobs will be run.
-However, you may want to reverse this behaviour, having all of your jobs to run _except_
-for one or two. Consider the following pipeline, with jobs `A`, `B`, and `C`. If you want
-all pipelines to always run `A` and `B`, but only want `C` to run for a merge request,
-you can configure your `.gitlab-ci.yml` file as follows:
+However, you may want to reverse this behavior, having all of your jobs to run _except_
+for one or two.
+
+Consider the following pipeline, with jobs `A`, `B`, and `C`. Imagine you want:
+
+- All pipelines to always run `A` and `B`.
+- `C` to run only for merge requests.
+
+To achieve this, you can configure your `.gitlab-ci.yml` file as follows:
``` yaml
.only-default: &only-default
@@ -90,9 +161,11 @@ C:
- merge_requests
```
-Since `A` and `B` are getting the `only:` rule to execute in all cases, they will
-always run. `C` specifies that it should only run for merge requests, so for any
-pipeline except a merge request pipeline, it will not run.
+Therefore:
+
+- Since `A` and `B` are getting the `only:` rule to execute in all cases, they will always run.
+- Since `C` specifies that it should only run for merge requests, it will not run for any pipeline
+ except a merge request pipeline.
As you can see, this will help you avoid a lot of boilerplate where you'd need
to add that `only:` rule to all of your jobs in order to make them always run. You
@@ -124,3 +197,12 @@ External users could steal secret variables from the parent project by modifying
We're discussing a secure solution of running pipelines for merge requests
that submitted from forked projects,
see [the issue about the permission extension](https://gitlab.com/gitlab-org/gitlab-ce/issues/23902).
+
+## Additional predefined variables
+
+By using pipelines for merge requests, GitLab exposes additional predefined variables to the pipeline jobs.
+Those variables contain information of the associated merge request, so that it's useful
+to integrate your job with [GitLab Merge Request API](../../api/merge_requests.md).
+
+You can find the list of available variables in [the reference sheet](../variables/predefined_variables.md).
+The variable names begin with the `CI_MERGE_REQUEST_` prefix.
diff --git a/doc/ci/metrics_reports.md b/doc/ci/metrics_reports.md
new file mode 100644
index 00000000000..b7824402d45
--- /dev/null
+++ b/doc/ci/metrics_reports.md
@@ -0,0 +1,44 @@
+---
+type: reference
+---
+
+# Metrics Reports **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9788) in [GitLab Premium](https://about.gitlab.com/pricing) 11.10.
+Requires GitLab Runner 11.10 and above.
+
+## Overview
+
+GitLab provides a lot of great reporting tools for [merge requests](../user/project/merge_requests/index.md) - [JUnit reports](./junit_test_reports.md), [codequality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html), performance tests, etc. While JUnit is a great open framework for tests that "pass" or "fail", it is also important to see other types of metrics from a given change.
+
+You can configure your job to use custom Metrics Reports, and GitLab will display a report on the merge request so that it's easier and faster to identify changes without having to check the entire log.
+
+![Metrics Reports](./img/metrics_reports.png)
+
+## Use cases
+
+Consider the following examples of data that can utilize Metrics Reports:
+
+1. Memory usage
+1. Load testing results
+1. Code complexity
+1. Code coverage stats
+
+## How it works
+
+Metrics are read from the metrics report (default: `metrics.txt`). They are parsed and displayed in the MR widget.
+
+## How to set it up
+
+Add a job that creates a [metrics report](yaml/README.md#artifactsreportsmetrics-premium) (default filename: `metrics.txt`). The file should conform to the [OpenMetrics](https://openmetrics.io/) format.
+
+For example:
+
+```yaml
+metrics:
+ script:
+ - echo 'metric_name metric_value' > metrics.txt
+ artifacts:
+ reports:
+ metrics: metrics.txt
+```
diff --git a/doc/ci/multi_project_pipeline_graphs.md b/doc/ci/multi_project_pipeline_graphs.md
new file mode 100644
index 00000000000..af10e5b6126
--- /dev/null
+++ b/doc/ci/multi_project_pipeline_graphs.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'multi_project_pipelines.md'
+---
+
+This document was moved to [another location](multi_project_pipelines.md).
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
new file mode 100644
index 00000000000..50c8d82602b
--- /dev/null
+++ b/doc/ci/multi_project_pipelines.md
@@ -0,0 +1,189 @@
+---
+type: reference
+---
+
+# Multi-project pipelines **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/2121) in
+[GitLab Premium 9.3](https://about.gitlab.com/2017/06/22/gitlab-9-3-released/#multi-project-pipeline-graphs).
+
+When you set up [GitLab CI/CD](README.md) across multiple projects, you can visualize
+the entire pipeline, including all cross-project inter-dependencies.
+
+## Overview
+
+GitLab CI/CD is a powerful continuous integration tool that works not only per project, but also across projects. When you
+configure GitLab CI for your project, you can visualize the stages
+of your [jobs](pipelines.md#configuring-pipelines) on a [pipeline graph](pipelines.md#visualizing-pipelines).
+
+![Multi-project pipeline graph](img/multi_project_pipeline_graph.png)
+
+In the Merge Request Widget, multi-project pipeline mini-graphs are displayed,
+and when hovering or tapping (on touchscreen devices) they will expand and be shown adjacent to each other.
+
+![Multi-project mini graph](img/multi_pipeline_mini_graph.gif)
+
+Multi-project pipelines are useful for larger products that require cross-project inter-dependencies, such as those
+adopting a [microservices architecture](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/).
+
+For a demonstration of how cross-functional development teams can use cross-pipeline
+triggering to trigger multiple pipelines for different microservices projects, see
+[Cross-project Pipeline Triggering and Visualization](https://about.gitlab.com/handbook/marketing/product-marketing/demo/#cross-project-pipeline-triggering-and-visualization-may-2019---1110).
+
+## Use cases
+
+Let's assume you deploy your web app from different projects in GitLab:
+
+- One for the free version, which has its own pipeline that builds and tests your app
+- One for the paid version add-ons, which also pass through builds and tests
+- One for the documentation, which also builds, tests, and deploys with an SSG
+
+With Multi-Project Pipelines, you can visualize the entire pipeline, including all stages of builds and tests for the three projects.
+
+## Triggering multi-project pipelines through API
+
+When you use the [`CI_JOB_TOKEN` to trigger pipelines](triggers/README.md#ci-job-token), GitLab
+recognizes the source of the job token, and thus internally ties these pipelines
+together, allowing you to visualize their relationships on pipeline graphs.
+
+These relationships are displayed in the pipeline graph by showing inbound and
+outbound connections for upstream and downstream pipeline dependencies.
+
+## Creating multi-project pipelines from `.gitlab-ci.yml`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
+
+### Triggering a downstream pipeline using a bridge job
+
+Before GitLab 11.8, it was necessary to implement a pipeline job that was
+responsible for making the API request [to trigger a pipeline](#triggering-multi-project-pipelines-through-api)
+in a different project.
+
+In GitLab 11.8, GitLab provides a new CI/CD configuration syntax to make this
+task easier, and avoid needing GitLab Runner for triggering cross-project
+pipelines. The following illustrates configuring a bridge job:
+
+```yaml
+rspec:
+ stage: test
+ script: bundle exec rspec
+
+staging:
+ variables:
+ ENVIRONMENT: staging
+ stage: deploy
+ trigger: my/deployment
+```
+
+In the example above, as soon as `rspec` job succeeds in the `test` stage,
+the `staging` bridge job is going to be started. The initial status of this
+job will be `pending`. GitLab will create a downstream pipeline in the
+`my/deployment` project and, as soon as the pipeline gets created, the
+`staging` job will succeed. `my/deployment` is a full path to that project.
+
+The user that created the upstream pipeline needs to have access rights to the
+downstream project (`my/deployment` in this case). If a downstream project can
+not be found, or a user does not have access rights to create pipeline there,
+the `staging` job is going to be marked as _failed_.
+
+CAUTION: **Caution:**
+`staging` will succeed as soon as a downstream pipeline gets created.
+GitLab does not support status attribution yet, however adding first-class
+`trigger` configuration syntax is ground work for implementing
+[status attribution](https://gitlab.com/gitlab-org/gitlab-ce/issues/39640).
+
+NOTE: **Note:**
+Bridge jobs do not support every configuration entry that a user can use
+in the case of regular jobs. Bridge jobs will not to be picked by a Runner,
+thus there is no point in adding support for `script`, for example. If a user
+tries to use unsupported configuration syntax, YAML validation will fail upon
+pipeline creation.
+
+### Specifying a downstream pipeline branch
+
+It is possible to specify a branch name that a downstream pipeline will use:
+
+```yaml
+rspec:
+ stage: test
+ script: bundle exec rspec
+
+staging:
+ stage: deploy
+ trigger:
+ project: my/deployment
+ branch: stable-11-2
+```
+
+Use a `project` keyword to specify full path to a downstream project. Use
+a `branch` keyword to specify a branch name.
+
+GitLab will use a commit that is currently on the HEAD of the branch when
+creating a downstream pipeline.
+
+### Passing variables to a downstream pipeline
+
+Sometimes you might want to pass variables to a downstream pipeline.
+You can do that using the `variables` keyword, just like you would when
+defining a regular job.
+
+```yaml
+rspec:
+ stage: test
+ script: bundle exec rspec
+
+staging:
+ variables:
+ ENVIRONMENT: staging
+ stage: deploy
+ trigger: my/deployment
+```
+
+The `ENVIRONMENT` variable will be passed to every job defined in a downstream
+pipeline. It will be available as an environment variable when GitLab Runner picks a job.
+
+In the following configuration, the `MY_VARIABLE` variable will be passed to the downstream pipeline
+that is created when the `trigger-downstream` job is queued. This is because `trigger-downstream`
+job inherits variables declared in global variables blocks, and then we pass these variables to a downstream pipeline.
+
+```yaml
+variables:
+ MY_VARIABLE: my-value
+
+trigger-downstream:
+ variables:
+ ENVIRONMENT: something
+ trigger: my/project
+```
+
+You might want to pass some information about the upstream pipeline using, for
+example, predefined variables. In order to do that, you can use interpolation
+to pass any variable. For example:
+
+```yaml
+downstream-job:
+ variables:
+ UPSTREAM_BRANCH: $CI_COMMIT_REF_NAME
+ trigger: my/project
+```
+
+In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the
+upstream pipeline will be passed to the `downstream-job` job, and will be available
+within the context of all downstream builds.
+
+### Limitations
+
+Because bridge jobs are a little different to regular jobs, it is not
+possible to use exactly the same configuration syntax here, as one would
+normally do when defining a regular job that will be picked by a runner.
+
+Some features are not implemented yet. For example, support for environments.
+
+[Configuration keywords](yaml/README.md) available for bridge jobs are:
+
+- `trigger` (to define a downstream pipeline trigger)
+- `stage`
+- `allow_failure`
+- `only` and `except`
+- `when`
+- `extends`
diff --git a/doc/ci/permissions/README.md b/doc/ci/permissions/README.md
index 80d8e46f29c..bc1e6ce3e0b 100644
--- a/doc/ci/permissions/README.md
+++ b/doc/ci/permissions/README.md
@@ -1 +1,5 @@
-This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-ci).
+---
+redirect_to: '../../user/permissions.md#gitlab-cicd-permissions'
+---
+
+This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-cicd-permissions).
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 4f3106c6dc6..1ad3d516df9 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -1,271 +1,348 @@
-# Introduction to pipelines and jobs
+---
+type: reference
+---
+
+# Creating and using CI/CD pipelines
> Introduced in GitLab 8.8.
+## Introduction
+
+Pipelines are the top-level component of continuous integration, delivery, and deployment.
+
+Pipelines comprise:
+
+- Jobs that define what to run. For example, code compilation or test runs.
+- Stages that define when and how to run. For example, that tests run only after code compilation.
+
+Multiple jobs in the same stage are executed by [Runners](runners/README.md) in parallel, if there are enough concurrent [Runners](runners/README.md).
+
+If all the jobs in a stage:
+
+- Succeed, the pipeline moves on to the next stage.
+- Fail, the next stage is not (usually) executed and the pipeline ends early.
+
NOTE: **Note:**
-If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository-starter),
+If you have a [mirrored repository that GitLab pulls from](../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter),
you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
-## Pipelines
+### Simple pipeline example
-A pipeline is a group of [jobs] that get executed in [stages].
-All of the jobs in a stage are executed in parallel (if there are enough
-concurrent [Runners]), and if they all succeed, the pipeline moves on to the
-next stage. If one of the jobs fails, the next stage is not (usually)
-executed. You can access the pipelines page in your project's **Pipelines** tab.
+As an example, imagine a pipeline consisting of four stages, executed in the following order:
-In the following image you can see that the pipeline consists of four stages
-(`build`, `test`, `staging`, `production`) each one having one or more jobs.
+- `build`, with a job called `compile`.
+- `test`, with two jobs called `test` and `test2`.
+- `staging`, with a job called `deploy-to-stage`.
+- `production`, with a job called `deploy-to-prod`.
->**Note:**
-GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs).
+## Visualizing pipelines
-![Pipelines example](img/pipelines.png)
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5742) in GitLab 8.11.
-## Types of pipelines
+Pipelines can be complex structures with many sequential and parallel jobs.
-There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
+To make it easier to understand the flow of a pipeline, GitLab has pipeline graphs for viewing pipelines
+and their statuses.
-![Types of Pipelines](img/types-of-pipelines.png)
+Pipeline graphs can be displayed in two different ways, depending on the page you
+access the graph from.
-1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`.
-1. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production.
-1. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
+NOTE: **Note:**
+GitLab capitalizes the stages' names when shown in the pipeline graphs (below).
-## Development workflows
+### Regular pipeline graphs
-Pipelines accommodate several development workflows:
+Regular pipeline graphs show the names of the jobs of each stage. Regular pipeline graphs can
+be found when you are on a [single pipeline page](#accessing-pipelines). For example:
-1. **Branch Flow** (e.g. different branch for dev, qa, staging, production).
-1. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases).
-1. **Fork-based Flow** (e.g. merge requests come from forks).
+![Pipelines example](img/pipelines.png)
-Example continuous delivery flow:
+### Pipeline mini graphs
-![CD Flow](img/pipelines-goal.png)
+Pipeline mini graphs take less space and can tell you at a
+quick glance if all jobs passed or something failed. The pipeline mini graph can
+be found when you navigate to:
-## Jobs
+- The pipelines index page.
+- A single commit page.
+- A merge request page.
-Jobs can be defined in the [`.gitlab-ci.yml`][jobs-yaml] file. Not to be
-confused with a `build` job or `build` stage.
+Pipeline mini graphs allow you to see all related jobs for a single commit and the net result
+of each stage of your pipeline. This allows you to quickly see what failed and
+fix it.
-## Defining pipelines
+Stages in pipeline mini graphs are collapsible. Hover your mouse over them and click to expand their jobs.
-Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
-[stages].
+| Mini graph | Mini graph expanded |
+|:-------------------------------------------------------------|:---------------------------------------------------------------|
+| ![Pipelines mini graph](img/pipelines_mini_graph_simple.png) | ![Pipelines mini graph extended](img/pipelines_mini_graph.png) |
-See the reference [documentation for jobs](yaml/README.md#jobs).
+### Job ordering in pipeline graphs
-## Manually executing pipelines
+Job ordering depends on the type of pipeline graph. For [regular pipeline graphs](#regular-pipeline-graphs), jobs are sorted by name.
-Pipelines can be manually executed, with predefined or manually-specified [variables](variables/README.md).
+For [pipeline mini graphs](#pipeline-mini-graphs) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760)
+in GitLab 9.0), jobs are sorted by severity and then by name.
-To execute a pipeline manually:
+The order of severity is:
-1. Navigate to your project's **CI/CD > Pipelines**.
-1. Click on the **Run Pipeline** button.
-1. Select the branch to run the pipeline for and enter any environment variables required for the pipeline run.
+- failed
+- warning
+- pending
+- running
+- manual
+- scheduled
+- canceled
+- success
+- skipped
+- created
-## Seeing pipeline status
+For example:
-You can find the current and historical pipeline runs under your project's
-**Pipelines** tab. Clicking on a pipeline will show the jobs that were run for
-that pipeline.
-
-![Pipelines index page](img/pipelines_index.png)
+![Pipeline mini graph sorting](img/pipelines_mini_graph_sorting.png)
-## Seeing job status
+### How pipeline duration is calculated
-When you visit a single pipeline you can see the related jobs for that pipeline.
-Clicking on an individual job will show you its job trace, and allow you to
-cancel the job, retry it, or erase the job trace.
+Total running time for a given pipeline excludes retries and pending
+(queued) time.
-![Pipelines example](img/pipelines.png)
+Each job is represented as a `Period`, which consists of:
-## Seeing the failure reason for jobs
+- `Period#first` (when the job started).
+- `Period#last` (when the job finished).
-> [Introduced][ce-17782] in GitLab 10.7.
+A simple example is:
-When a pipeline fails or is allowed to fail, there are several places where you
-can quickly check the reason it failed:
+- A (1, 3)
+- B (2, 4)
+- C (6, 7)
-- **In the pipeline graph** present on the pipeline detail view.
-- **In the pipeline widgets** present in the merge requests and commit pages.
-- **In the job views** present in the global and detailed views of a job.
+In the example:
-In any case, if you hover over the failed job you can see the reason it failed.
+- A begins at 1 and ends at 3.
+- B begins at 2 and ends at 4.
+- C begins at 6 and ends at 7.
-![Pipeline detail](img/job_failure_reason.png)
+Visually, it can be viewed as:
-From [GitLab 10.8][ce-17814] you can also see the reason it failed on the Job detail page.
+```text
+0 1 2 3 4 5 6 7
+ AAAAAAA
+ BBBBBBB
+ CCCC
+```
-## Pipeline graphs
+The union of A, B, and C is (1, 4) and (6, 7). Therefore, the total running time is:
-> [Introduced][ce-5742] in GitLab 8.11.
+```text
+(4 - 1) + (7 - 6) => 4
+```
-Pipelines can be complex structures with many sequential and parallel jobs.
-To make it a little easier to see what is going on, you can view a graph
-of a single pipeline and its status.
+## Configuring pipelines
-A pipeline graph can be shown in two different ways depending on what page you
-are on.
+Pipelines, and their component jobs and stages, are defined in the [`.gitlab-ci.yml`](yaml/README.md) file for each project.
----
+In particular:
-The regular pipeline graph that shows the names of the jobs of each stage can
-be found when you are on a [single pipeline page](#seeing-pipeline-status).
+- Jobs are the [basic configuration](yaml/README.html#introduction) component.
+- Stages are defined using the [`stages`](yaml/README.html#stages) keyword.
-![Pipelines example](img/pipelines.png)
+For all available configuration options, see the [GitLab CI/CD Pipeline Configuration Reference](yaml/README.md).
-Then, there is the pipeline mini graph which takes less space and can give you a
-quick glance if all jobs pass or something failed. The pipeline mini graph can
-be found when you visit:
+### Settings and schedules
-- The pipelines index page.
-- A single commit page.
-- A merge request page.
+In addition to configuring jobs through `.gitlab-ci.yml`, additional configuration options are available
+through the GitLab UI:
-That way, you can see all related jobs for a single commit and the net result
-of each stage of your pipeline. This allows you to quickly see what failed and
-fix it. Stages in pipeline mini graphs are collapsible. Hover your mouse over
-them and click to expand their jobs.
+- Pipeline settings for each project. For more information, see [Pipeline settings](../user/project/pipelines/settings.md).
+- Schedules for pipelines. For more information, see [Pipeline schedules](../user/project/pipelines/schedules.md).
-| **Mini graph** | **Mini graph expanded** |
-| :------------: | :---------------------: |
-| ![Pipelines mini graph](img/pipelines_mini_graph_simple.png) | ![Pipelines mini graph extended](img/pipelines_mini_graph.png) |
+### Grouping jobs
-### Grouping similar jobs in the pipeline graph
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242) in GitLab 8.12.
-> [Introduced][ce-6242] in GitLab 8.12.
+If you have many similar jobs, your [pipeline graph](#visualizing-pipelines) becomes long and hard
+to read.
-If you have many similar jobs, your pipeline graph becomes very long and hard
-to read. For that reason, similar jobs can automatically be grouped together.
+For that reason, similar jobs can automatically be grouped together.
If the job names are formatted in certain ways, they will be collapsed into
a single group in regular pipeline graphs (not the mini graphs).
+
You'll know when a pipeline has grouped jobs if you don't see the retry or
cancel button inside them. Hovering over them will show the number of grouped
jobs. Click to expand them.
![Grouped pipelines](img/pipelines_grouped.png)
-The basic requirements is that there are two numbers separated with one of
+#### Configuring grouping
+
+In the pipeline [configuration file](yaml/README.md), job names must include two numbers separated with one of
the following (you can even use them interchangeably):
-- A space (` `)
-- A slash (`/`)
-- A colon (`:`)
+- A space.
+- A slash (`/`).
+- A colon (`:`).
+
+NOTE: **Note:**
+More specifically, it uses [this](https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99) regular expression: `\d+[\s:\/\\]+\d+\s*`.
->**Note:**
-More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`.
+#### How grouping works
The jobs will be ordered by comparing those two numbers from left to right. You
usually want the first to be the index and the second the total.
For example, the following jobs will be grouped under a job named `test`:
-- `test 0 3` => `test`
-- `test 1 3` => `test`
-- `test 2 3` => `test`
+- `test 0 3`
+- `test 1 3`
+- `test 2 3`
The following jobs will be grouped under a job named `test ruby`:
-- `test 1:2 ruby` => `test ruby`
-- `test 2:2 ruby` => `test ruby`
+- `test 1:2 ruby`
+- `test 2:2 ruby`
The following jobs will be grouped under a job named `test ruby` as well:
-- `1/3 test ruby` => `test ruby`
-- `2/3 test ruby` => `test ruby`
-- `3/3 test ruby` => `test ruby`
+- `1/3 test ruby`
+- `2/3 test ruby`
+- `3/3 test ruby`
-### Manual actions from the pipeline graph
+### Pipelines for merge requests
-> [Introduced][ce-7931] in GitLab 8.15.
+GitLab supports configuring pipelines that run only for merge requests. For more information, see
+[Pipelines for merge requests](merge_request_pipelines/index.md).
-[Manual actions][manual] allow you to require manual interaction before moving
-forward with a particular job in CI. Your entire pipeline can run automatically,
-but the actual [deploy to production][env-manual] will require a click.
+### Badges
-You can do this straight from the pipeline graph. Just click on the play button
-to execute that particular job. For example, in the image below, the `production`
-stage has a job with a manual action.
+Pipeline status and test coverage report badges are available and configurable for each project.
-![Pipelines example](img/pipelines.png)
+For information on adding pipeline badges to projects, see [Pipeline badges](../user/project/pipelines/settings.md#pipeline-badges).
-### Delay a particular job in the pipeline graph
+## Multi-project pipelines **[PREMIUM]**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+Pipelines for different projects can be combined and visualized together.
-When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#whendelayed).
-This is especially useful for timed incremental rollout that new code is rolled out gradually.
-For example, if you start rolling out new code and users do not experience trouble, GitLab automatically completes the deployment from 0% to 100%.
-Alternatively, if you start rolling out and you noticed that a few users experience trouble with the version,
-you can stop the timed incremental rollout by canceling the pipeline, and [rolling](environments.md#rolling-back-changes) it back to the stable version.
+For more information, see [Multi-project pipelines](multi_project_pipelines.md).
-![Pipelines example](img/pipeline_incremental_rollout.png)
+## Working with pipelines
-### Ordering of jobs in pipeline graphs
+In general, pipelines are executed automatically and require no intervention once created.
-**Regular pipeline graph**
+However, there are instances where you'll need to interact with pipelines. These are documented below.
-In the single pipeline page, jobs are sorted by name.
+### Manually executing pipelines
-**Mini pipeline graph**
+Pipelines can be manually executed, with predefined or manually-specified [variables](variables/README.md).
-> [Introduced][ce-9760] in GitLab 9.0.
+You might do this if the results of a pipeline (for example, a code build) is required outside the normal
+operation of the pipeline.
-In the pipeline mini graphs, the jobs are sorted first by severity and then
-by name. The order of severity is:
+To execute a pipeline manually:
-- failed
-- warning
-- pending
-- running
-- manual
-- scheduled
-- canceled
-- success
-- skipped
-- created
+1. Navigate to your project's **CI/CD > Pipelines**.
+1. Click on the **Run Pipeline** button.
+1. On the **Run Pipeline** page:
+ 1. Select the branch to run the pipeline for in the **Create for** field.
+ 1. Enter any [environment variables](variables/README.md) required for the pipeline run.
+ 1. Click the **Create pipeline** button.
-![Pipeline mini graph sorting](img/pipelines_mini_graph_sorting.png)
+The pipeline will execute the jobs as configured.
-## How the pipeline duration is calculated
+### Accessing pipelines
-Total running time for a given pipeline would exclude retries and pending
-(queue) time. We could reduce this problem down to finding the union of
-periods.
+You can find the current and historical pipeline runs under your project's
+**CI/CD > Pipelines** page. Clicking on a pipeline will show the jobs that were run for
+that pipeline.
-So each job would be represented as a `Period`, which consists of
-`Period#first` as when the job started and `Period#last` as when the
-job was finished. A simple example here would be:
+![Pipelines index page](img/pipelines_index.png)
-- A (1, 3)
-- B (2, 4)
-- C (6, 7)
+You can also access pipelines for a merge request by navigating to its **Pipelines** tab.
-Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
-C begins from 6, and ends to 7. Visually it could be viewed as:
+### Accessing individual jobs
-```
-0 1 2 3 4 5 6 7
- AAAAAAA
- BBBBBBB
- CCCC
-```
+When you access a pipeline, you can see the related jobs for that pipeline.
-The union of A, B, and C would be (1, 4) and (6, 7), therefore the
-total running time should be:
+Clicking on an individual job will show you its job trace, and allow you to:
-```
-(4 - 1) + (7 - 6) => 4
-```
+- Cancel the job.
+- Retry the job.
+- Erase the job trace.
-## Badges
+### Seeing the failure reason for jobs
-Pipeline status and test coverage report badges are available. You can find their
-respective link in the [Pipelines settings] page.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782) in GitLab 10.7.
+
+When a pipeline fails or is allowed to fail, there are several places where you
+can quickly check the reason it failed:
+
+- In the pipeline graph, on the pipeline detail view.
+- In the pipeline widgets, in the merge requests and commit pages.
+- In the job views, in the global and detailed views of a job.
+
+In each place, if you hover over the failed job you can see the reason it failed.
+
+![Pipeline detail](img/job_failure_reason.png)
+
+From [GitLab 10.8](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814),
+you can also see the reason it failed on the Job detail page.
+
+### Manual actions from pipeline graphs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931) in GitLab 8.15.
+
+Manual actions, configured using the [`when:manual`](yaml/README.md#whenmanual) parameter,
+allow you to require manual interaction before moving forward in the pipeline.
+
+You can do this straight from the pipeline graph. Just click on the play button
+to execute that particular job.
+
+For example, your pipeline start automatically, but require manual action to
+[deploy to production](environments.md#configuring-manual-deployments). In the example below, the `production`
+stage has a job with a manual action.
+
+![Pipelines example](img/pipelines.png)
+
+### Delay a job in a pipeline graph
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+
+When you do not want to run a job immediately, you can use the [`when:delayed`](yaml/README.md#whendelayed) parameter to
+delay a job's execution for a certain period.
+
+This is especially useful for timed incremental rollout where new code is rolled out gradually.
+
+For example, if you start rolling out new code and:
+
+- Users do not experience trouble, GitLab can automatically complete the deployment from 0% to 100%.
+- Users experience trouble with the new code, you can stop the timed incremental rollout by canceling the pipeline
+ and [rolling](environments.md#retrying-and-rolling-back) back to the last stable version.
+
+![Pipelines example](img/pipeline_incremental_rollout.png)
+
+### Using the API
+
+GitLab provides API endpoints to:
+
+- Perform basic functions. For more information, see [Pipelines API](../api/pipelines.md).
+- Maintain pipeline schedules. For more information, see [Pipeline schedules API](../api/pipeline_schedules.md).
+- Trigger pipeline runs. For more information, see:
+ - [Triggering pipelines through the API](triggers/README.md).
+ - [Pipeline triggers API](../api/pipeline_triggers.md).
+
+### Start multiple manual actions in a stage
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27188) in GitLab 11.11.
+
+Multiple manual actions in a single stage can be started at the same time using the "Play all manual" button.
+Once the user clicks this button, each individual manual action will be triggered and refreshed
+to an updated status.
+
+This functionality is only available:
+
+- For users with at least Developer access.
+- If the the stage contains [manual actions](#manual-actions-from-pipeline-graphs).
## Security on protected branches
@@ -276,14 +353,14 @@ The following actions are allowed on protected branches only if the user is
[allowed to merge or push](../user/project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
on that specific branch:
-- Run **manual pipelines** (using [Web UI](#manually-executing-pipelines) or Pipelines API).
-- Run **scheduled pipelines**.
-- Run pipelines using **triggers**.
-- Trigger **manual actions** on existing pipelines.
-- **Retry/cancel** existing jobs (using Web UI or Pipelines API).
+- Run manual pipelines (using the [Web UI](#manually-executing-pipelines) or pipelines API).
+- Run scheduled pipelines.
+- Run pipelines using triggers.
+- Trigger manual actions on existing pipelines.
+- Retry or cancel existing jobs (using the Web UI or pipelines API).
**Variables** marked as **protected** are accessible only to jobs that
-run on protected branches, avoiding untrusted users to get unintended access to
+run on protected branches, preventing untrusted users getting unintended access to
sensitive information like deployment credentials and tokens.
**Runners** marked as **protected** can run jobs only on protected
@@ -291,19 +368,3 @@ branches, avoiding untrusted code to be executed on the protected runner and
preserving deployment keys and other credentials from being unintentionally
accessed. In order to ensure that jobs intended to be executed on protected
runners will not use regular runners, they must be tagged accordingly.
-
-[jobs]: #jobs
-[jobs-yaml]: yaml/README.md#jobs
-[manual]: yaml/README.md#whenmanual
-[env-manual]: environments.md#manually-deploying-to-environments
-[stages]: yaml/README.md#stages
-[runners]: runners/README.html
-[pipelines settings]: ../user/project/pipelines/settings.md
-[triggers]: triggers/README.md
-[ce-5742]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5742
-[ce-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
-[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
-[ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
-[ce-17782]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782
-[ce-17814]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814
-[regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 9684cb6ed98..11bcfd5dc2c 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,9 +1,18 @@
+---
+type: reference
+---
+
# Getting started with GitLab CI/CD
->**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
+NOTE: **Note:**
+Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
is fully integrated into GitLab itself and is [enabled] by default on all
projects.
+NOTE: **Note:**
+Please keep in mind that only project Maintainers and Admin users have
+the permissions to access a project's settings.
+
GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each commit or
@@ -35,11 +44,12 @@ project's **Pipelines** page.
---
-This guide assumes that you:
+This guide assumes that you have:
-- have a working GitLab instance of version 8.0+r or are using
- [GitLab.com](https://gitlab.com)
-- have a project in GitLab that you would like to use CI for
+- A working GitLab instance of version 8.0+r or are using
+ [GitLab.com](https://gitlab.com).
+- A project in GitLab that you would like to use CI for.
+- Maintainer or owner access to the project
Let's break it down to pieces and work on solving the GitLab CI puzzle.
@@ -73,6 +83,8 @@ You need to create a file named `.gitlab-ci.yml` in the root directory of your
repository. Below is an example for a Ruby on Rails project.
```yaml
+image: "ruby:2.5"
+
before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
@@ -105,7 +117,7 @@ Jobs are used to create jobs, which are then picked by
What is important is that each job is run independently from each other.
If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a
-Lint tool under the page `/ci/lint` of your project namespace. You can also find
+Lint tool under the page `/-/ci/lint` of your project namespace. You can also find
a "CI Lint" button to go to this page under **CI/CD âž” Pipelines** and
**Pipelines âž” Jobs** in your project.
@@ -127,7 +139,7 @@ Now if you go to the **Pipelines** page you will see that the pipeline is
pending.
NOTE: **Note:**
-If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository-starter),
+If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter),
you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index f4d7b9ad194..7b039fe6654 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -1,120 +1,79 @@
+---
+type: reference
+---
+
# Review Apps
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/21971) in GitLab 8.12. Further additions were made in GitLab 8.13 and 8.14.
> - Inspired by [Heroku's Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps), which itself was inspired by [Fourchette](https://github.com/rainforestapp/fourchette).
-For a video introduction to Review Apps, see [8.14 Webcast: Review Apps & Time Tracking Beta (EE) - GitLab Release](https://www.youtube.com/watch?v=CteZol_7pxo).
-
-## Overview
+Review Apps is a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
-Review Apps are a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
+## Introduction
Review Apps:
- Provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests.
-- Allow designers and product manages to see your changes without needing to check out your branch and run your changes in a sandbox environment.
-- Are fully integrated with the [GitLab DevOps LifeCycle](../../README.md#complete-devops-with-gitlab).
+- Allow designers and product managers to see your changes without needing to check out your branch and run your changes in a sandbox environment.
+- Are fully integrated with the [GitLab DevOps LifeCycle](../../README.md#the-entire-devops-lifecycle).
- Allow you to deploy your changes wherever you want.
![Review Apps Workflow](img/continuous-delivery-review-apps.svg)
-Reviewing anything, from performance to interface changes, becomes much easier with a live environment and so Review Apps can make a large impact on your development flow.
+In the above example:
+
+- A Review App is built every time a commit is pushed to `topic branch`.
+- The reviewer fails two reviews before passing the third review.
+- Once the review as passed, `topic branch` is merged into `master` where it's deploy to staging.
+- After been approved in staging, the changes that were merged into `master` are deployed in to production.
-## What are Review Apps?
+### How Review Apps work
-A Review App is a mapping of a branch with an [environment](../environments.md). The following is an example of a merge request with an environment set dynamically.
+A Review App is a mapping of a branch with an [environment](../environments.md).
+Access to the Review App is made available as a link on the [merge request](../../user/project/merge_requests.md) relevant to the branch.
+
+The following is an example of a merge request with an environment set dynamically.
![Review App in merge request](img/review_apps_preview_in_mr.png)
-In this example, you can see a branch was:
+In this example, a branch was:
- Successfully built.
- Deployed under a dynamic environment that can be reached by clicking on the **View app** button.
-## How do Review Apps work?
-
-The basis of Review Apps in GitLab is [dynamic environments](../environments.md#dynamic-environments), which allow you to dynamically create a new environment for each branch.
-
-Access to the Review App is made available as a link on the [merge request](../../user/project/merge_requests.md) relevant to the branch. Review Apps enable you to review all changes proposed by the merge request in live environment.
-
-## Use cases
-
-Some supported use cases include the:
-
-- Simple case of deploying a simple static HTML website.
-- More complicated case of an application that uses a database. Deploying a branch on a temporary instance and booting up this instance with all required software and services automatically on the fly is not a trivial task. However, it is possible, especially if you use Docker or a configuration management tool like Chef, Puppet, Ansible, or Salt.
-
-Review Apps usually make sense with web applications, but you can use them any way you'd like.
-
-## Implementing Review Apps
-
-Implementing Review Apps depends on your:
-
-- Technology stack.
-- Deployment process.
-
-### Prerequisite Knowledge
-
-To get a better understanding of Review Apps, review documentation on how environments and deployments work. Before you implement your own Review Apps:
-
-1. Learn about [environments](../environments.md) and their role in the development workflow.
-1. Learn about [CI variables](../variables/README.md) and how they can be used in your CI jobs.
-1. Explore the [`environment` syntax](../yaml/README.md#environment) as defined in `.gitlab-ci.yml`. This will become a primary reference.
-1. Additionally, find out about [manual actions](../environments.md#manually-deploying-to-environments) and how you can use them to deploy to critical environments like production with the push of a button.
-1. Follow the [example tutorials](#examples). These will guide you through setting up infrastructure and using Review Apps.
-
-### Configuring dynamic environments
-
-Configuring Review Apps dynamic environments depends on your technology stack and infrastructure.
-
-For more information, see [dynamic environments](../environments.md#dynamic-environments) documentation to understand how to define and create them.
-
-### Creating and destroying Review Apps
-
-Creating and destroying Review Apps is defined in `.gitlab-ci.yml` at a job level under the `environment` keyword.
+## Configuring Review Apps
-For more information, see [Introduction to environments and deployments](../environments.md).
+Review Apps are built on [dynamic environments](../environments.md#configuring-dynamic-environments), which allow you to dynamically create a new environment for each branch.
-### Adding Review Apps to your workflow
-
-The process of adding Review Apps in your workflow is as follows:
+The process of configuring Review Apps is as follows:
1. Set up the infrastructure to host and deploy the Review Apps.
1. [Install](https://docs.gitlab.com/runner/install/) and [configure](https://docs.gitlab.com/runner/commands/) a Runner to do deployment.
-1. Set up a job in `.gitlab-ci.yml` that uses the predefined [predefined CI environment variable](../variables/README.md) `${CI_COMMIT_REF_NAME}` to create dynamic environments and restrict it to run only on branches.
+1. Set up a job in `.gitlab-ci.yml` that uses the [predefined CI environment variable](../variables/README.md) `${CI_COMMIT_REF_NAME}` to create dynamic environments and restrict it to run only on branches.
1. Optionally, set a job that [manually stops](../environments.md#stopping-an-environment) the Review Apps.
-After adding Review Apps to your workflow, you follow the branched Git flow. That is:
+### Examples
-1. Push a branch and let the Runner deploy the Review App based on the `script` definition of the dynamic environment job.
-1. Wait for the Runner to build and deploy your web application.
-1. Click on the link that provided in the merge request related to the branch to see the changes live.
-
-## Limitations
-
-Check the [environments limitations](../environments.md#limitations).
-
-## Examples
-
-The following are example projects that use Review Apps with:
+The following are example projects that demonstrate Review App configuration:
- [NGINX](https://gitlab.com/gitlab-examples/review-apps-nginx).
- [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift).
See also the video [Demo: Cloud Native Development with GitLab](https://www.youtube.com/watch?v=jfIyQEwrocw), which includes a Review Apps example.
-## Route Maps
+### Route Maps
-> Introduced in GitLab 8.17. In GitLab 11.5 the file links
-are surfaced to the merge request widget.
+> Introduced in GitLab 8.17. In GitLab 11.5, the file links are available in the merge request widget.
Route Maps allows you to go directly from source files
to public pages on the [environment](../environments.md) defined for
-Review Apps. Once set up, the review app link in the merge request
+Review Apps.
+
+Once set up, the review app link in the merge request
widget can take you directly to the pages changed, making it easier
and faster to preview proposed modifications.
-All you need to do is to tell GitLab how the paths of files
+Configuring Route Maps involves telling GitLab how the paths of files
in your repository map to paths of pages on your website using a Route Map.
Once set, GitLab will display **View on ...** buttons, which will take you
to the pages changed directly from merge requests.
@@ -123,9 +82,9 @@ To set up a route map, add a a file inside the repository at `.gitlab/route-map.
which contains a YAML array that maps `source` paths (in the repository) to `public`
paths (on the website).
-### Route Maps example
+#### Route Maps example
-Below there's an example of a route map for [Middleman](https://middlemanapp.com),
+The following is an example of a route map for [Middleman](https://middlemanapp.com),
a static site generator (SSG) used to build [GitLab's website](https://about.gitlab.com),
deployed from its [project on GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com):
@@ -147,18 +106,17 @@ deployed from its [project on GitLab.com](https://gitlab.com/gitlab-com/www-gitl
public: '\1' # images/blogimages/around-the-world-in-6-releases-cover.png
```
-Mappings are defined as entries in the root YAML array, and are identified by a `-` prefix. Within an entry, we have a hash map with two keys:
+Mappings are defined as entries in the root YAML array, and are identified by a `-` prefix. Within an entry, there is a hash map with two keys:
- `source`
- - a string, starting and ending with `'`, for an exact match
- - a regular expression, starting and ending with `/`, for a pattern match
- - The regular expression needs to match the entire source path - `^` and `$` anchors are implied.
- - Can include capture groups denoted by `()` that can be referred to in the `public` path.
- - Slashes (`/`) can, but don't have to, be escaped as `\/`.
- - Literal periods (`.`) should be escaped as `\.`.
-- `public`
- - a string, starting and ending with `'`.
- - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`.
+ - A string, starting and ending with `'`, for an exact match.
+ - A regular expression, starting and ending with `/`, for a pattern match:
+ - The regular expression needs to match the entire source path - `^` and `$` anchors are implied.
+ - Can include capture groups denoted by `()` that can be referred to in the `public` path.
+ - Slashes (`/`) can, but don't have to, be escaped as `\/`.
+ - Literal periods (`.`) should be escaped as `\.`.
+- `public`, a string starting and ending with `'`.
+ - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`.
The public path for a source path is determined by finding the first
`source` expression that matches it, and returning the corresponding
@@ -171,12 +129,12 @@ will match `/source\/(.+?\.html).*/` instead of `/source\/(.*)/`,
and will result in a public path of `index.html`, instead of
`index.html.haml`.
-Once you have the route mapping set up, it will be exposed in a few places:
+Once you have the route mapping set up, it will take effect in the following locations:
-- In the merge request widget. The **View app** button will take you to the
- environment URL you have set up in `.gitlab-ci.yml`. The dropdown will render
- the first 5 matched items from the route map, but you can filter them if more
- than 5 are available.
+- In the merge request widget. The:
+ - **View app** button will take you to the environment URL set in `.gitlab-ci.yml`.
+ - Dropdown will list the first 5 matched items from the route map, but you can filter them if more
+ than 5 are available.
![View app file list in merge request widget](img/view_on_mr_widget.png)
@@ -187,3 +145,15 @@ Once you have the route mapping set up, it will be exposed in a few places:
- In the blob file view.
!["View on env" button in file view](img/view_on_env_blob.png)
+
+## Working with Review Apps
+
+After adding Review Apps to your workflow, you follow the branched Git flow. That is:
+
+1. Push a branch and let the Runner deploy the Review App based on the `script` definition of the dynamic environment job.
+1. Wait for the Runner to build and deploy your web application.
+1. Click on the link that provided in the merge request related to the branch to see the changes live.
+
+## Limitations
+
+Review App limitations are the same as [environments limitations](../environments.md#limitations).
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index ce55b231666..b089229ab58 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring GitLab Runners
In GitLab CI, Runners run the code defined in [`.gitlab-ci.yml`](../yaml/README.md).
diff --git a/doc/ci/runners/shared_to_specific_admin.png b/doc/ci/runners/shared_to_specific_admin.png
deleted file mode 100644
index 8f4010a5849..00000000000
--- a/doc/ci/runners/shared_to_specific_admin.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
index d94b472b768..7fe12eb53e7 100644
--- a/doc/ci/services/README.md
+++ b/doc/ci/services/README.md
@@ -1,13 +1,18 @@
---
comments: false
+type: index
---
-# GitLab CI Services
+# GitLab CI services examples
-GitLab CI uses the `services` keyword to define what docker containers should
-be linked with your base image. Below is a list of examples you may use.
+The [`services`](../docker/using_docker_images.md#what-is-a-service)
+keyword defines a Docker image that runs during a `job` linked to the
+Docker image that the image keyword defines. This allows you to access
+the service image during build time.
+
+The service image can run any application, but the most common use
+case is to run a database container, for example:
- [Using MySQL](mysql.md)
- [Using PostgreSQL](postgres.md)
- [Using Redis](redis.md)
-- [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index 2902c30c7c0..697452cee83 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using MySQL
As many applications depend on MySQL as their database, you will eventually
@@ -17,8 +21,8 @@ services:
variables:
# Configure mysql environment variables (https://hub.docker.com/_/mysql/)
- MYSQL_DATABASE: el_duderino
- MYSQL_ROOT_PASSWORD: mysql_strong_password
+ MYSQL_DATABASE: "<your_mysql_database>"
+ MYSQL_ROOT_PASSWORD: "<your_mysql_password>"
```
And then configure your application to use the database, for example:
@@ -26,18 +30,18 @@ And then configure your application to use the database, for example:
```yaml
Host: mysql
User: root
-Password: mysql_strong_password
-Database: el_duderino
+Password: <your_mysql_password>
+Database: <your_mysql_database>
```
If you are wondering why we used `mysql` for the `Host`, read more at
[How services are linked to the job](../docker/using_docker_images.md#how-services-are-linked-to-the-job).
-You can also use any other docker image available on [Docker Hub][hub-mysql].
+You can also use any other docker image available on [Docker Hub](https://hub.docker.com/_/mysql/).
For example, to use MySQL 5.5 the service becomes `mysql:5.5`.
The `mysql` image can accept some environment variables. For more details
-check the documentation on [Docker Hub][hub-mysql].
+check the documentation on [Docker Hub](https://hub.docker.com/_/mysql/).
## Use MySQL with the Shell executor
@@ -74,13 +78,13 @@ mysql> CREATE USER 'runner'@'localhost' IDENTIFIED BY '$password';
Create the database:
```bash
-mysql> CREATE DATABASE IF NOT EXISTS `el_duderino` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
+mysql> CREATE DATABASE IF NOT EXISTS `<your_mysql_database>` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
```
Grant the necessary permissions on the database:
```bash
-mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `el_duderino`.* TO 'runner'@'localhost';
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `<your_mysql_database>`.* TO 'runner'@'localhost';
```
If all went well you can now quit the database session:
@@ -93,7 +97,7 @@ Now, try to connect to the newly created database to check that everything is
in place:
```bash
-mysql -u runner -p -D el_duderino
+mysql -u runner -p -D <your_mysql_database>
```
As a final step, configure your application to use the database, for example:
@@ -102,17 +106,14 @@ As a final step, configure your application to use the database, for example:
Host: localhost
User: runner
Password: $password
-Database: el_duderino
+Database: <your_mysql_database>
```
## Example project
-We have set up an [Example MySQL Project][mysql-example-repo] for your
+We have set up an [Example MySQL Project](https://gitlab.com/gitlab-examples/mysql) for your
convenience that runs on [GitLab.com](https://gitlab.com) using our publicly
available [shared runners](../runners/README.md).
-Want to hack on it? Simply fork it, commit and push your changes. Within a few
+Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the job will begin.
-
-[hub-mysql]: https://hub.docker.com/_/mysql/
-[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index 3899b555f32..211eea26eb0 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using PostgreSQL
As many applications depend on PostgreSQL as their database, you will
@@ -31,7 +35,7 @@ Database: nice_marmot
```
If you are wondering why we used `postgres` for the `Host`, read more at
-[How is service linked to the job](../docker/using_docker_images.md#how-is-service-linked-to-the-job).
+[How services are linked to the job](../docker/using_docker_images.md#how-services-are-linked-to-the-job).
You can also use any other docker image available on [Docker Hub][hub-pg].
For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`.
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index 36f71427ae7..8b227154b06 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using Redis
As many applications depend on Redis as their key-value store, you will
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index ff63d0bd69f..69591ed605c 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -1,5 +1,6 @@
---
last_updated: 2017-12-13
+type: tutorial
---
# Using SSH keys with GitLab CI/CD
@@ -49,7 +50,7 @@ to access it. This is where an SSH key pair comes in handy.
**Do not** add a passphrase to the SSH key, or the `before_script` will
prompt for it.
-1. Create a new [variable](../variables/README.md#variables).
+1. Create a new [variable](../variables/README.md#gitlab-cicd-environment-variables).
As **Key** enter the name `SSH_PRIVATE_KEY` and in the **Value** field paste
the content of your _private_ key that you created earlier.
@@ -157,7 +158,7 @@ ssh-keyscan example.com
ssh-keyscan 1.2.3.4
```
-Create a new [variable](../variables/README.md#variables) with
+Create a new [variable](../variables/README.md#gitlab-cicd-environment-variables) with
`SSH_KNOWN_HOSTS` as "Key", and as a "Value" add the output of `ssh-keyscan`.
NOTE: **Note:**
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 61037360326..04c541fefe7 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -1,10 +1,14 @@
+---
+type: tutorial
+---
+
# Triggering pipelines through the API
> **Notes**:
>
> - [Introduced](https://about.gitlab.com/2015/08/22/gitlab-7-14-released/) in GitLab 7.14.
> - GitLab 8.12 has a completely redesigned job permissions system. Read all
-> about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
+> about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#pipeline-triggers).
Triggers can be used to force a pipeline rerun of a specific `ref` (branch or
tag) with an API call.
@@ -17,6 +21,69 @@ The following methods of authentication are supported.
A unique trigger token can be obtained when [adding a new trigger](#adding-a-new-trigger).
+DANGER: **Danger:**
+Passing plain text tokens in public projects is a security issue. Potential
+attackers can impersonate the user that exposed their trigger token publicly in
+their `.gitlab-ci.yml` file. Use [variables](../variables/README.md#gitlab-cicd-environment-variables)
+to protect trigger tokens.
+
+### CI job token
+
+You can use the `CI_JOB_TOKEN` [variable][predef] (used to authenticate
+with the [GitLab Container Registry][registry]) in the following cases.
+
+#### When used with multi-project pipelines **[PREMIUM]**
+
+> **Note**:
+The use of `CI_JOB_TOKEN` for multi-project pipelines was [introduced][ee-2017]
+in [GitLab Premium][ee] 9.3.
+
+This way of triggering can only be used when invoked inside `.gitlab-ci.yml`,
+and it creates a dependent pipeline relation visible on the
+[pipeline graph](../multi_project_pipelines.md#overview). For example:
+
+```yaml
+build_docs:
+ stage: deploy
+ script:
+ - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
+ only:
+ - tags
+```
+
+Pipelines triggered that way also expose a special variable:
+`CI_PIPELINE_SOURCE=pipeline`.
+
+Read more about the [pipelines trigger API][trigapi].
+
+#### When a pipeline depends on the artifacts of another pipeline **[PREMIUM]**
+
+> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+ in [GitLab Premium][ee] 9.5.
+
+With the introduction of dependencies between different projects, one of
+them may need to access artifacts created by a previous one. This process
+must be granted for authorized accesses, and it can be done using the
+`CI_JOB_TOKEN` variable that identifies a specific job. For example:
+
+```yaml
+build_submodule:
+ image: debian
+ stage: test
+ script:
+ - apt update && apt install -y unzip
+ - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"
+ - unzip artifacts.zip
+ only:
+ - tags
+```
+
+This allows you to use that for multi-project pipelines and download artifacts
+from any project to which you have access as this follows the same principles
+with the [permission model][permissions].
+
+Read more about the [jobs API](../../api/jobs.md#download-the-artifacts-archive).
+
## Adding a new trigger
You can add a new trigger by going to your project's
@@ -53,9 +120,6 @@ The action is irreversible.
>
> - Valid refs are only the branches and tags. If you pass a commit SHA as a ref,
> it will not trigger a job.
-> - If your project is public, passing the token in plain text is probably not the
-> wisest idea, so you might want to use a
-> [variable](../variables/README.md#variables) for that purpose.
To trigger a job you need to send a `POST` request to GitLab's API endpoint:
@@ -219,10 +283,13 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
removed with one of the future versions of GitLab. You are advised to
-[take ownership](#taking-ownership) of any legacy triggers.
+[take ownership](#taking-ownership-of-a-trigger) of any legacy triggers.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
+[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
[ee]: https://about.gitlab.com/pricing/
[variables]: ../variables/README.md
[predef]: ../variables/README.md#predefined-environment-variables
[registry]: ../../user/project/container_registry.md
+[permissions]: ../../user/permissions.md#job-permissions
+[trigapi]: ../../api/pipeline_triggers.md
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
index d273b1fe3a2..910622d74cc 100644
--- a/doc/ci/triggers/img/trigger_variables.png
+++ b/doc/ci/triggers/img/trigger_variables.png
Binary files differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index ca668ac526f..df455857dee 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -1,169 +1,291 @@
---
table_display_block: true
+type: reference
---
-# GitLab CI/CD Variables
+# GitLab CI/CD environment variables
-When receiving a job from GitLab CI, the [Runner](https://docs.gitlab.com/runner/) prepares the build environment.
-It starts by setting a list of:
+After a brief overview over the use of environment
+variables, this document teaches you how to use GitLab CI/CD's
+variables, presents the full reference for predefined variables,
+and dives into more advanced applications.
-- [Predefined environment variables](#predefined-environment-variables).
-- Other variables.
+## Overview
-## Priority of variables
+An environment variable is a dynamic-named value that can
+affect the way running processes will behave on an operating
+system.
-Variables of different types can take precedence over other variables, depending on where they are defined.
+They are part of the environment in which a process runs.
+For example, a running process can query the value of the
+`TEMP` environment variable to discover a suitable location
+to store temporary files, or to define a `URL` for a database
+that can be reused in different scripts.
-The order of precedence for variables is (from highest to lowest):
+Variables are useful for customizing your jobs in GitLab
+CI/CD's pipelines. Using variables means no hardcoded values.
-1. [Trigger variables](../triggers/README.md#pass-job-variables-to-a-trigger) or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables).
-1. Project-level [variables](#variables) or [protected variables](#protected-variables).
-1. Group-level [variables](#variables) or [protected variables](#protected-variables).
-1. YAML-defined [job-level variables](../yaml/README.md#variables).
-1. YAML-defined [global variables](../yaml/README.md#variables).
-1. [Deployment variables](#deployment-variables).
-1. [Predefined environment variables](#predefined-environment-variables).
+### Predefined environment variables
-For example, you define:
+GitLab CI/CD has a default set of
+[predefined variables](predefined_variables.md)
+which can be used without any specification needed.
+You can call issues numbers, user names, branch names,
+pipeline and commit IDs, and much more.
-- `API_TOKEN=secure` as a project variable.
-- `API_TOKEN=yaml` in your `.gitlab-ci.yml`.
+Predefined environment variables are the ones that GitLab
+provides out of the box for the local environment of the Runner.
-`API_TOKEN` will take the value `secure` as the project variables take precedence over those defined
-in `.gitlab-ci.yml`.
+GitLab reads the .gitlab-ci.yml file, sends the information
+to the Runner (which runs the script commands), under which
+the variables are exposed.
-## Unsupported variables
+For example, two jobs under the same pipeline can share the same
+`CI_PIPELINE_ID` variable, but each one has its own `CI_JOB_ID`
+variable.
-There are cases where some variables cannot be used in the context of a
-`.gitlab-ci.yml` definition (for example under `script`). Read more
-about which variables are [not supported](where_variables_can_be_used.md).
+### Custom environment variables
-## Predefined environment variables
+When your use case requires a specific variable, you can
+[set them up easily from the UI](#creating-a-custom-environment-variable)
+or directly in the `.gitlab-ci.yml` file and reuse them as you wish.
-Some of the predefined environment variables are available only if a minimum
-version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the table below to find the
-version of Runner required.
+That can be very powerful as it can be used for scripting without
+the need to specify the value itself.
-NOTE: **Note:**
-Starting with GitLab 9.0, we have deprecated some variables. Read the
-[9.0 Renaming](#9-0-renaming) section to find out their replacements. **You are
-strongly advised to use the new variables as we will remove the old ones in
-future GitLab releases.**
-
-| Variable | GitLab | Runner | Description |
-|-------------------------------------------|--------|--------|-------------|
-| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
-| **CHAT_INPUT** | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
-| **CHAT_CHANNEL** | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
-| **CI** | all | 0.4 | Mark that job is executed in CI environment |
-| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
-| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
-| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. |
-| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
-| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
-| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
-| **CI_COMMIT_SHORT_SHA** | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
-| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message |
-| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
-| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
-| **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
-| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
-| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
-| **CI_ENVIRONMENT_URL** | 9.3 | all | The URL of the environment for this job. Only present if [`environment:url`](../yaml/README.md#environmenturl) is set. |
-| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
-| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
-| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
-| **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
-| **CI_JOB_URL** | 11.1 | 0.5 | Job details URL |
-| **CI_MERGE_REQUEST_ID** | 11.6 | all | The ID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_IID** | 11.6 | all | The IID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_PROJECT_ID** | 11.6 | all | The ID of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_PROJECT_PATH** | 11.6 | all | The path of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`) |
-| **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) |
-| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) |
-| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
-| **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
-| **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
-| **CI_API_V4_URL** | 11.7 | all | The GitLab API v4 root URL |
-| **CI_PAGES_DOMAIN** | 11.8 | all | The configured domain that hosts GitLab Pages. |
-| **CI_PAGES_URL** | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
-| **CI_PIPELINE_ID** | 8.10 | all | The unique id of the current pipeline that GitLab CI uses internally |
-| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project |
-| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
-| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
-| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL |
-| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
-| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
-| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) |
-| **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
-| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
-| **CI_PROJECT_PATH_SLUG** | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP(S) address to access project |
-| **CI_PROJECT_VISIBILITY** | 10.3 | all | The project visibility (internal, private, public) |
-| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
-| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
-| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
-| **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
-| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository |
-| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
-| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
-| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
-| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job |
-| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
-| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job |
-| **CI_SERVER** | all | all | Mark that job is executed in CI environment |
-| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
-| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
-| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
-| **CI_SERVER_VERSION_MAJOR** | 11.4 | all | GitLab version major component |
-| **CI_SERVER_VERSION_MINOR** | 11.4 | all | GitLab version minor component |
-| **CI_SERVER_VERSION_PATCH** | 11.4 | all | GitLab version patch component |
-| **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
-| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
-| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
-| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
-| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
-| **GITLAB_USER_LOGIN** | 10.0 | all | The login username of the user who started the job |
-| **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job |
-| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
-
-## GitLab 9.0 renaming
-
-To follow conventions of naming across GitLab, and to further move away from the
-`build` term and toward `job` CI variables have been renamed for the 9.0
-release.
+#### Variable types
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46806) in GitLab 11.11.
+
+There are two types of variables supported by GitLab:
+
+- "Variable": the Runner will create an environment variable named same as the variable key and set its value to the variable value.
+- "File": the Runner will write the variable value to a temporary file and set the path to this file as the value of an environment variable named same as the variable key.
+
+Many tools (like [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) and [kubectl](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)) provide the ability to customise configuration using files by either providing the file path as a command line argument or an environment variable. Prior to the introduction of variable types, the common pattern was to use the value of a CI variable, save it in a file, and then use the newly created file in your script:
+
+```bash
+# Save the content of variable in a file
+echo "$KUBE_CA_PEM" > "$(pwd)/kube.ca.pem"
+ # Use the newly created file
+kubectl config set-cluster e2e --server="$KUBE_URL" --certificate-authority="$(pwd)/kube.ca.pem"
+```
+
+This can be simplified by creating a variable of type "File" and using it directly. For example, let's say we have the following variables.
+
+![CI/CD settings - variable types usage example](img/variable_types_usage_example.png)
+
+We can then call them from `.gitlab-ci.yml` like this:
+
+```bash
+kubectl config set-cluster e2e --server="$KUBE_URL" --certificate-authority="$KUBE_CA_PEM"
+```
+
+Variable types can be set via the [UI](#via-the-ui) or the [API](../../api/project_level_variables.md#create-variable), but not in `.gitlab-ci.yml`.
+
+#### Masked variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/13784) in GitLab 11.10
+
+Variables can be created as masked variables.
+This means that the value of the variable will be hidden in job logs,
+though it must match certain requirements to do so:
+
+- The value must be in a single line.
+- The value must only consist of characters from the Base64 alphabet, defined in [RFC4648](https://tools.ietf.org/html/rfc4648).
+- The value must be at least 8 characters long.
+- The value must not use variables.
+
+If the value does not meet the requirements above, then the CI variable will fail to save.
+In order to save, either alter the value to meet the masking requirements
+or disable **Masked** for the variable.
+
+## Getting started
+
+To get started with environment variables in the scope of GitLab
+CI/CD, let's go over a few examples.
+
+### Using predefined environment variables
+
+To get started, choose one of the existing
+[predefined variables](predefined_variables.md)
+to be output by the Runner. For example, let's say that you want
+a given job you're running through your script to output the
+stage that job is running for. In your `.gitlab-ci.yml` file,
+call the variable from your script according to the [syntaxes](#syntax-of-environment-variables-in-job-scripts) available. To
+output the job stage, use the predefined variable `CI_JOB_STAGE`:
+
+```yaml
+test_variable:
+ stage: test
+ script:
+ - echo $CI_JOB_STAGE
+```
+
+For this case, the Runner will output the `stage` for the
+job `test_variable`, which is `test`:
+
+![Output `$CI_JOB_STAGE`](img/ci_job_stage_output_example.png)
+
+As another example, let's say you're using your own GitLab
+instance you want to know what domain your GitLab Pages are
+served under. You can easily call it with the predefined
+variable `$CI_PAGES_DOMAIN` in your script:
+
+```yaml
+pages:
+ script:
+ - ...
+ - echo $CI_PAGES_DOMAIN
+```
+
+For GitLab.com users, the output will be `gitlab.io`. For your
+private instance, the output will be whatever your sysadmin has
+defined.
+
+### Creating a custom environment variable
+
+Assume you have something you want to repeat through your scripts
+in GitLab CI/CD's configuration file. To keep this example simple,
+let's say you want to output `HELLO WORLD` for a `TEST` variable.
+
+You can either set the variable directly in the `.gitlab-ci.yml`
+file or through the UI.
+
+#### Via `.gitlab-ci.yml`
+
+To create a new custom `env_var` variable via [`.gitlab-ci.yml`](../yaml/README.md#variables), define their variable/value pair under
+`variables`:
+
+```yaml
+variables:
+ TEST: "HELLO WORLD"
+```
+
+For a deeper look into them, see [`.gitlab-ci.yml` defined variables](#gitlab-ciyml-defined-variables).
+
+#### Via the UI
+
+From the UI, navigate to your project's **Settings > CI/CD** and
+expand **Variables**. Create a new variable by choosing its **type**, naming
+it in the field **Input variable key**, and defining its value in the
+**Input variable value** field:
+
+![CI/CD settings - new variable](img/new_custom_variables_example.png)
+
+You'll also see the option to mask and/or protect your variables.
+
+Once you've set the variables, call them from the `.gitlab-ci.yml` file:
+
+```yaml
+test_variable:
+ stage: test
+ script:
+ - echo $CI_JOB_STAGE # calls a predefined variable
+ - echo $TEST # calls a custom variable of type `env_var`
+ - echo $GREETING # calls a custom variable of type `file` that contains the path to the temp file
+ - cat $GREETING # the temp file itself contains the variable value
+```
+
+The output will be:
+
+![Output custom variable](img/custom_variables_output.png)
+
+### Syntax of environment variables in job scripts
+
+All variables are set as environment variables in the build environment, and
+they are accessible with normal methods that are used to access such variables.
+In most cases `bash` or `sh` is used to execute the job script.
+
+To access environment variables, use the syntax for your Runner's [shell][shellexecutors].
+
+| Shell | Usage |
+|----------------------|-----------------|
+| bash/sh | `$variable` |
+| windows batch | `%variable%` |
+| PowerShell | `$env:variable` |
+
+To access environment variables in bash, prefix the variable name with (`$`):
+
+```yaml
+job_name:
+ script:
+ - echo $CI_JOB_ID
+```
+
+To access environment variables in **Windows Batch**, surround the variable
+with (`%`):
+
+```yaml
+job_name:
+ script:
+ - echo %CI_JOB_ID%
+```
+
+To access environment variables in a **Windows PowerShell** environment, prefix
+the variable name with (`$env:`):
+
+```yaml
+job_name:
+ script:
+ - echo $env:CI_JOB_ID
+```
+
+You can also list all environment variables with the `export` command,
+but be aware that this will also expose the values of all the variables
+you set, in the job log:
+
+```yaml
+job_name:
+ script:
+ - export
+```
+
+Example values:
+
+```bash
+export CI_JOB_ID="50"
+export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
+export CI_COMMIT_SHORT_SHA="1ecfd275"
+export CI_COMMIT_REF_NAME="master"
+export CI_REPOSITORY_URL="https://gitlab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
+export CI_COMMIT_TAG="1.0.0"
+export CI_JOB_NAME="spec:other"
+export CI_JOB_STAGE="test"
+export CI_JOB_MANUAL="true"
+export CI_JOB_TRIGGERED="true"
+export CI_JOB_TOKEN="abcde-1234ABCD5678ef"
+export CI_PIPELINE_ID="1000"
+export CI_PIPELINE_IID="10"
+export CI_PAGES_DOMAIN="gitlab.io"
+export CI_PAGES_URL="https://gitlab-org.gitlab.io/gitlab-ce"
+export CI_PROJECT_ID="34"
+export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
+export CI_PROJECT_NAME="gitlab-ce"
+export CI_PROJECT_NAMESPACE="gitlab-org"
+export CI_PROJECT_PATH="gitlab-org/gitlab-ce"
+export CI_PROJECT_URL="https://example.com/gitlab-org/gitlab-ce"
+export CI_REGISTRY="registry.example.com"
+export CI_REGISTRY_IMAGE="registry.example.com/gitlab-org/gitlab-ce"
+export CI_RUNNER_ID="10"
+export CI_RUNNER_DESCRIPTION="my runner"
+export CI_RUNNER_TAGS="docker, linux"
+export CI_SERVER="yes"
+export CI_SERVER_NAME="GitLab"
+export CI_SERVER_REVISION="70606bf"
+export CI_SERVER_VERSION="8.9.0"
+export CI_SERVER_VERSION_MAJOR="8"
+export CI_SERVER_VERSION_MINOR="9"
+export CI_SERVER_VERSION_PATCH="0"
+export GITLAB_USER_ID="42"
+export GITLAB_USER_EMAIL="user@example.com"
+export CI_REGISTRY_USER="gitlab-ci-token"
+export CI_REGISTRY_PASSWORD="longalfanumstring"
+```
+
+### `.gitlab-ci.yml` defined variables
NOTE: **Note:**
-Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
-strongly advised to use the new variables as we will remove the old ones in
-future GitLab releases.**
-
-| 8.x name | 9.0+ name |
-| --------------------- |------------------------ |
-| `CI_BUILD_ID` | `CI_JOB_ID` |
-| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
-| `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
-| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
-| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
-| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
-| `CI_BUILD_NAME` | `CI_JOB_NAME` |
-| `CI_BUILD_STAGE` | `CI_JOB_STAGE` |
-| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` |
-| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
-| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` |
-| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
-
-## `.gitlab-ci.yml` defined variables
-
-NOTE **Note:**
This feature requires GitLab Runner 0.5.0 or higher and GitLab 7.14 or higher.
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in the
@@ -201,42 +323,60 @@ script:
- 'eval $LS_CMD' # will execute 'ls -al $TMP_DIR'
```
-## Variables
+### Group-level environment variables
-> Group-level variables were introduced in GitLab 9.4.
+> Introduced in GitLab 9.4.
-CAUTION: **Important:**
-Be aware that variables are not masked, and their values can be shown
-in the job logs if explicitly asked to do so. If your project is public or
-internal, you can set the pipelines private from your [project's Pipelines
-settings](../../user/project/pipelines/settings.md#visibility-of-pipelines).
-Follow the discussion in issue [#13784][ce-13784] for masking the variables.
-
-GitLab CI allows you to define per-project or per-group variables
-that are set in the pipeline environment. The variables are stored out of
+GitLab CI/CD allows you to define per-project or per-group variables
+that are set in the pipeline environment. Group-level variables are stored out of
the repository (not in `.gitlab-ci.yml`) and are securely passed to GitLab Runner
-making them available during a pipeline run. It's the recommended method to
+making them available during a pipeline run. It's the **recommended method** to
use for storing things like passwords, SSH keys, and credentials.
-Project-level variables can be added by:
-
-1. Navigating to your project's **Settings > CI/CD** page.
-1. Inputing variable keys and values in the **Environment variables** section.
-
Group-level variables can be added by:
1. Navigating to your group's **Settings > CI/CD** page.
-1. Inputing variable keys and values in the **Environment variables** section. Any variables of
- [subgroups](../../user/group/subgroups/index.md) will be inherited recursively.
+1. Inputing variable types, keys, and values in the **Variables** section.
+Any variables of [subgroups](../../user/group/subgroups/index.md) will be inherited recursively.
+
+Once you set them, they will be available for all subsequent pipelines.
-Once you set them, they will be available for all subsequent pipelines. You can also
-[protect your variables](#protected-variables).
+## Priority of environment variables
-### Protected variables
+Variables of different types can take precedence over other
+variables, depending on where they are defined.
+
+The order of precedence for variables is (from highest to lowest):
+
+1. [Trigger variables](../triggers/README.md#making-use-of-trigger-variables) or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#using-variables).
+1. Project-level [variables](#creating-a-custom-environment-variable) or [protected variables](#protected-environment-variables).
+1. Group-level [variables](#group-level-environment-variables) or [protected variables](#protected-environment-variables).
+1. YAML-defined [job-level variables](../yaml/README.md#variables).
+1. YAML-defined [global variables](../yaml/README.md#variables).
+1. [Deployment variables](#deployment-environment-variables).
+1. [Predefined environment variables](predefined_variables.md).
+
+For example, if you define:
+
+- `API_TOKEN=secure` as a project variable.
+- `API_TOKEN=yaml` in your `.gitlab-ci.yml`.
+
+`API_TOKEN` will take the value `secure` as the project
+variables take precedence over those defined in `.gitlab-ci.yml`.
+
+## Unsupported variables
+
+There are cases where some variables cannot be used in the context of a
+`.gitlab-ci.yml` definition (for example under `script`). Read more
+about which variables are [not supported](where_variables_can_be_used.md).
+
+## Advanced use
+
+### Protected environment variables
> Introduced in GitLab 9.3.
-Variables could be protected. Whenever a variable is
+Variables can be protected. Whenever a variable is
protected, it would only be securely passed to pipelines running on the
[protected branches] or [protected tags]. The other pipelines would not get any
protected variables.
@@ -247,13 +387,14 @@ Protected variables can be added by going to your project's
Once you set them, they will be available for all subsequent pipelines.
-### Manually-specified variables
+### Limiting environment scopes of environment variables **[PREMIUM]**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/44059) in GitLab 10.8.
+You can limit the environment scope of a variable by
+[defining which environments][envs] it can be available for.
-Variables can be specified for a single pipeline run when a [manual pipeline](../pipelines.md#manually-executing-pipelines) is created.
+To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs-premium).
-## Deployment variables
+### Deployment environment variables
> Introduced in GitLab 8.15.
@@ -266,7 +407,7 @@ the project services that you are using to learn which variables they define.
An example project service that defines deployment variables is the
[Kubernetes integration](../../user/project/clusters/index.md#deployment-variables).
-## Auto DevOps application variables
+### Auto DevOps environment variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
@@ -283,6 +424,136 @@ CAUTION: **Caution:**
Variables with multiline values are not currently supported due to
limitations with the current Auto DevOps scripting environment.
+### Environment variables triggered manually
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/44059) in GitLab 10.8.
+
+[Manually triggered pipelines](../pipelines.md#manually-executing-pipelines) allow you to override the value of a current variable.
+
+For instance, suppose you added a
+[custom variable `$TEST`](#creating-a-custom-environment-variable)
+as exemplified above and you want to override it in a manual pipeline.
+Navigate to your project's **CI/CD > Pipelines** and click **Run pipeline**.
+Choose the branch you want to run the pipeline for, then add a new variable through the UI:
+
+![Override variable value](img/override_variable_manual_pipeline.png)
+
+The Runner will override the value previously set and use the custom
+value you set for this specific pipeline:
+
+![Manually overridden variable output](img/override_value_via_manual_pipeline_output.png)
+
+## Environment variables expressions
+
+> Introduced in GitLab 10.7.
+
+It is possible to use variables expressions with only / except policies in
+`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to
+be created within a pipeline after pushing a code to GitLab.
+
+This is particularly useful in combination with variables and triggered
+pipeline variables.
+
+```yaml
+deploy:
+ script: cap staging deploy
+ environment: staging
+ only:
+ variables:
+ - $RELEASE == "staging"
+ - $STAGING
+```
+
+Each expression provided is going to be evaluated before creating a pipeline.
+
+If any of the conditions in `variables` evaluates to truth when using `only`,
+a new job is going to be created. If any of the expressions evaluates to truth
+when `except` is being used, a job is not going to be created.
+
+This follows usual rules for [`only` / `except` policies](../yaml/README.md#onlyexcept-advanced).
+
+### Supported syntax
+
+Below you can find supported syntax reference:
+
+1. Equality matching using a string
+
+ > Example: `$VARIABLE == "some value"`
+
+ > Example: `$VARIABLE != "some value"` _(added in 11.11)_
+
+ You can use equality operator `==` or `!=` to compare a variable content to a
+ string. We support both, double quotes and single quotes to define a string
+ value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
+ are supported. `"some value" == $VARIABLE` is correct too.
+
+1. Checking for an undefined value
+
+ > Example: `$VARIABLE == null`
+
+ > Example: `$VARIABLE != null` _(added in 11.11)_
+
+ It sometimes happens that you want to check whether a variable is defined
+ or not. To do that, you can compare a variable to `null` keyword, like
+ `$VARIABLE == null`. This expression is going to evaluate to truth if
+ variable is not defined when `==` is used, or to falsey if `!=` is used.
+
+1. Checking for an empty variable
+
+ > Example: `$VARIABLE == ""`
+
+ > Example: `$VARIABLE != ""` _(added in 11.11)_
+
+ If you want to check whether a variable is defined, but is empty, you can
+ simply compare it against an empty string, like `$VAR == ''` or non-empty
+ string `$VARIABLE != ""`.
+
+1. Comparing two variables
+
+ > Example: `$VARIABLE_1 == $VARIABLE_2`
+
+ > Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
+
+ It is possible to compare two variables. This is going to compare values
+ of these variables.
+
+1. Variable presence check
+
+ > Example: `$STAGING`
+
+ If you only want to create a job when there is some variable present,
+ which means that it is defined and non-empty, you can simply use
+ variable name as an expression, like `$STAGING`. If `$STAGING` variable
+ is defined, and is non empty, expression will evaluate to truth.
+ `$STAGING` value needs to a string, with length higher than zero.
+ Variable that contains only whitespace characters is not an empty variable.
+
+1. Pattern matching _(added in 11.0)_
+
+ > Example: `$VARIABLE =~ /^content.*/`
+
+ > Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
+
+ It is possible perform pattern matching against a variable and regular
+ expression. Expression like this evaluates to truth if matches are found
+ when using `=~`. It evaluates to truth if matches are not found when `!~` is used.
+
+ Pattern matching is case-sensitive by default. Use `i` flag modifier, like
+ `/pattern/i` to make a pattern case-insensitive.
+
+1. Conjunction / Disjunction
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ It is possible to join multiple conditions using `&&` or `||`. Any of the otherwise
+ supported syntax may be used in a conjunctive or disjunctive statement.
+ Precedence of operators follows standard Ruby 2.5 operation
+ [precedence](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html).
+
## Debug tracing
> Introduced in GitLab Runner 1.7.
@@ -305,7 +576,7 @@ variables that were set, etc.
Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should
-also [erase](../pipelines.md#seeing-build-status) all generated job traces
+also [erase](../pipelines.md#accessing-individual-jobs) all generated job traces
before making them visible again.
To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`:
@@ -347,6 +618,8 @@ $'\''git'\'' "checkout" "-f" "-q" "dd648b2e48ce6518303b0bb580b2ee32fadaf045"
Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-machine-1480971377-317a7d0f-digital-ocean-4gb...
++ export CI=true
++ CI=true
+++ export CI_API_V4_URL=https://example.com:3000/api/v4
+++ CI_API_V4_URL=https://example.com:3000/api/v4
++ export CI_DEBUG_TRACE=false
++ CI_DEBUG_TRACE=false
++ export CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
@@ -385,6 +658,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ GITLAB_CI=true
++ export CI=true
++ CI=true
+++ export CI_API_V4_URL=https://example.com:3000/api/v4
+++ CI_API_V4_URL=https://example.com:3000/api/v4
++ export GITLAB_CI=true
++ GITLAB_CI=true
++ export CI_JOB_ID=7046507
@@ -448,192 +723,14 @@ MIIFQzCCBCugAwIBAgIRAL/ElDjuf15xwja1ZnCocWAwDQYJKoZIhvcNAQELBQAw'
...
```
-## Using the CI variables in your job scripts
-
-All variables are set as environment variables in the build environment, and
-they are accessible with normal methods that are used to access such variables.
-In most cases `bash` or `sh` is used to execute the job script.
-
-To access environment variables, use the syntax for your Runner's [shell][shellexecutors].
-
-| Shell | Usage |
-|----------------------|-----------------|
-| bash/sh | `$variable` |
-| windows batch | `%variable%` |
-| PowerShell | `$env:variable` |
-
-To access environment variables in bash, prefix the variable name with (`$`):
-
-```yaml
-job_name:
- script:
- - echo $CI_JOB_ID
-```
-
-To access environment variables in **Windows Batch**, surround the variable
-with (`%`):
-
-```yaml
-job_name:
- script:
- - echo %CI_JOB_ID%
-```
-
-To access environment variables in a **Windows PowerShell** environment, prefix
-the variable name with (`$env:`):
-
-```yaml
-job_name:
- script:
- - echo $env:CI_JOB_ID
-```
-
-You can also list all environment variables with the `export` command,
-but be aware that this will also expose the values of all the variables
-you set, in the job log:
-
-```yaml
-job_name:
- script:
- - export
-```
-
-Example values:
-
-```bash
-export CI_JOB_ID="50"
-export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
-export CI_COMMIT_SHORT_SHA="1ecfd275"
-export CI_COMMIT_REF_NAME="master"
-export CI_REPOSITORY_URL="https://gitlab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
-export CI_COMMIT_TAG="1.0.0"
-export CI_JOB_NAME="spec:other"
-export CI_JOB_STAGE="test"
-export CI_JOB_MANUAL="true"
-export CI_JOB_TRIGGERED="true"
-export CI_JOB_TOKEN="abcde-1234ABCD5678ef"
-export CI_PIPELINE_ID="1000"
-export CI_PIPELINE_IID="10"
-export CI_PAGES_DOMAIN="gitlab.io"
-export CI_PAGES_URL="https://gitlab-org.gitlab.io/gitlab-ce"
-export CI_PROJECT_ID="34"
-export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
-export CI_PROJECT_NAME="gitlab-ce"
-export CI_PROJECT_NAMESPACE="gitlab-org"
-export CI_PROJECT_PATH="gitlab-org/gitlab-ce"
-export CI_PROJECT_URL="https://example.com/gitlab-org/gitlab-ce"
-export CI_REGISTRY="registry.example.com"
-export CI_REGISTRY_IMAGE="registry.example.com/gitlab-org/gitlab-ce"
-export CI_RUNNER_ID="10"
-export CI_RUNNER_DESCRIPTION="my runner"
-export CI_RUNNER_TAGS="docker, linux"
-export CI_SERVER="yes"
-export CI_SERVER_NAME="GitLab"
-export CI_SERVER_REVISION="70606bf"
-export CI_SERVER_VERSION="8.9.0"
-export CI_SERVER_VERSION_MAJOR="8"
-export CI_SERVER_VERSION_MINOR="9"
-export CI_SERVER_VERSION_PATCH="0"
-export GITLAB_USER_ID="42"
-export GITLAB_USER_EMAIL="user@example.com"
-export CI_REGISTRY_USER="gitlab-ci-token"
-export CI_REGISTRY_PASSWORD="longalfanumstring"
-```
-
-## Variables expressions
-
-> Introduced in GitLab 10.7.
-
-It is possible to use variables expressions with only / except policies in
-`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to
-be created within a pipeline after pushing a code to GitLab.
-
-This is particularly useful in combination with variables and triggered
-pipeline variables.
-
-```yaml
-deploy:
- script: cap staging deploy
- environment: staging
- only:
- variables:
- - $RELEASE == "staging"
- - $STAGING
-```
-
-Each expression provided is going to be evaluated before creating a pipeline.
-
-If any of the conditions in `variables` evaluates to truth when using `only`,
-a new job is going to be created. If any of the expressions evaluates to truth
-when `except` is being used, a job is not going to be created.
-
-This follows usual rules for [`only` / `except` policies][builds-policies].
-
-### Supported syntax
-
-Below you can find supported syntax reference:
-
-1. Equality matching using a string
-
- > Example: `$VARIABLE == "some value"`
-
- You can use equality operator `==` to compare a variable content to a
- string. We support both, double quotes and single quotes to define a string
- value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
- are supported. `"some value" == $VARIABLE` is correct too.
-
-1. Checking for an undefined value
-
- > Example: `$VARIABLE == null`
-
- It sometimes happens that you want to check whether a variable is defined
- or not. To do that, you can compare a variable to `null` keyword, like
- `$VARIABLE == null`. This expression is going to evaluate to truth if
- variable is not defined.
-
-1. Checking for an empty variable
-
- > Example: `$VARIABLE == ""`
-
- If you want to check whether a variable is defined, but is empty, you can
- simply compare it against an empty string, like `$VAR == ''`.
-
-1. Comparing two variables
-
- > Example: `$VARIABLE_1 == $VARIABLE_2`
-
- It is possible to compare two variables. This is going to compare values
- of these variables.
-
-1. Variable presence check
-
- > Example: `$STAGING`
-
- If you only want to create a job when there is some variable present,
- which means that it is defined and non-empty, you can simply use
- variable name as an expression, like `$STAGING`. If `$STAGING` variable
- is defined, and is non empty, expression will evaluate to truth.
- `$STAGING` value needs to a string, with length higher than zero.
- Variable that contains only whitespace characters is not an empty variable.
-
-1. Pattern matching _(added in 11.0)_
-
- > Example: `$VARIABLE =~ /^content.*/`
-
- It is possible perform pattern matching against a variable and regular
- expression. Expression like this evaluates to truth if matches are found.
-
- Pattern matching is case-sensitive by default. Use `i` flag modifier, like
- `/pattern/i` to make a pattern case-insensitive.
-
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI variables"
-[eep]: https://about.gitlab.com/pricing/ "Available only in GitLab Premium"
[envs]: ../environments.md
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
[shellexecutors]: https://docs.gitlab.com/runner/executors/
[triggered]: ../triggers/README.md
-[builds-policies]: ../yaml/README.md#only-and-except-complex
+[trigger-job-token]: ../triggers/README.md#ci-job-token
[gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token
[registry]: ../../user/project/container_registry.md
[dependent-repositories]: ../../user/project/new_ci_build_permissions_model.md#dependent-repositories
+[get-job-artifacts]: ../../api/jobs.html#get-job-artifacts
diff --git a/doc/ci/variables/deprecated_variables.md b/doc/ci/variables/deprecated_variables.md
new file mode 100644
index 00000000000..cdca5bf27fc
--- /dev/null
+++ b/doc/ci/variables/deprecated_variables.md
@@ -0,0 +1,34 @@
+---
+type: reference
+---
+
+# Deprecated GitLab CI/CD variables
+
+Read through this document to learn what predefined variables
+were deprecated and their new references.
+
+## GitLab 9.0 renamed variables
+
+To follow conventions of naming across GitLab, and to further move away from the
+`build` term and toward `job`, some [CI/CD environment variables](README.md#predefined-environment-variables) were renamed for GitLab 9.0
+release.
+
+NOTE: **Note:**
+Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
+strongly advised to use the new variables as we will remove the old ones in
+future GitLab releases.**
+
+| 8.x name | 9.0+ name |
+| --------------------- |------------------------ |
+| `CI_BUILD_ID` | `CI_JOB_ID` |
+| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
+| `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
+| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
+| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
+| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
+| `CI_BUILD_NAME` | `CI_JOB_NAME` |
+| `CI_BUILD_STAGE` | `CI_JOB_STAGE` |
+| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` |
+| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
+| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` |
+| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
diff --git a/doc/ci/variables/img/ci_job_stage_output_example.png b/doc/ci/variables/img/ci_job_stage_output_example.png
new file mode 100644
index 00000000000..056238d5693
--- /dev/null
+++ b/doc/ci/variables/img/ci_job_stage_output_example.png
Binary files differ
diff --git a/doc/ci/variables/img/custom_variables_output.png b/doc/ci/variables/img/custom_variables_output.png
new file mode 100644
index 00000000000..29f5c63b3d9
--- /dev/null
+++ b/doc/ci/variables/img/custom_variables_output.png
Binary files differ
diff --git a/doc/ci/variables/img/new_custom_variables_example.png b/doc/ci/variables/img/new_custom_variables_example.png
new file mode 100644
index 00000000000..efe104efe4c
--- /dev/null
+++ b/doc/ci/variables/img/new_custom_variables_example.png
Binary files differ
diff --git a/doc/ci/variables/img/override_value_via_manual_pipeline_output.png b/doc/ci/variables/img/override_value_via_manual_pipeline_output.png
new file mode 100644
index 00000000000..02369d57fb8
--- /dev/null
+++ b/doc/ci/variables/img/override_value_via_manual_pipeline_output.png
Binary files differ
diff --git a/doc/ci/variables/img/override_variable_manual_pipeline.png b/doc/ci/variables/img/override_variable_manual_pipeline.png
new file mode 100644
index 00000000000..3c8c59720cf
--- /dev/null
+++ b/doc/ci/variables/img/override_variable_manual_pipeline.png
Binary files differ
diff --git a/doc/ci/variables/img/variable_types_usage_example.png b/doc/ci/variables/img/variable_types_usage_example.png
new file mode 100644
index 00000000000..0e8bde891fe
--- /dev/null
+++ b/doc/ci/variables/img/variable_types_usage_example.png
Binary files differ
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
new file mode 100644
index 00000000000..4655eec51de
--- /dev/null
+++ b/doc/ci/variables/predefined_variables.md
@@ -0,0 +1,122 @@
+---
+type: reference
+---
+
+# Predefined environment variables reference
+
+For an introduction on this subject, read through the
+[getting started with environment variables](README.md) document.
+
+## Overview
+
+Some of the predefined environment variables are available only if a minimum
+version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the table below to find the
+version of Runner required.
+
+NOTE: **Note:**
+Starting with GitLab 9.0, we have deprecated some variables. Read the
+[9.0 Renaming](deprecated_variables.md#gitlab-90-renamed-variables) section to find out their replacements. **You are
+strongly advised to use the new variables as we will remove the old ones in
+future GitLab releases.**
+
+## Variables reference
+
+| Variable | GitLab | Runner | Description |
+|-------------------------------------------|--------|--------|-------------|
+| `ARTIFACT_DOWNLOAD_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
+| `CHAT_INPUT` | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
+| `CHAT_CHANNEL` | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
+| `CI` | all | 0.4 | Mark that job is executed in CI environment |
+| `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. |
+| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
+| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
+| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a push request. |
+| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
+| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
+| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
+| `CI_COMMIT_REF_SLUG` | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
+| `CI_COMMIT_SHA` | 9.0 | all | The commit revision for which project is built |
+| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
+| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit - the full first line of the message |
+| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
+| `CI_DEBUG_TRACE` | all | 1.7 | Whether [debug tracing](README.md#debug-tracing) is enabled |
+| `CI_DEPLOY_PASSWORD` | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
+| `CI_DEPLOY_USER` | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
+| `CI_DISPOSABLE_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
+| `CI_ENVIRONMENT_NAME` | 8.15 | all | The name of the environment for this job. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
+| `CI_ENVIRONMENT_SLUG` | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
+| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Only present if [`environment:url`](../yaml/README.md#environmenturl) is set. |
+| `CI_JOB_ID` | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
+| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
+| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
+| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
+| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
+| `CI_MERGE_REQUEST_ID` | 11.6 | all | The ID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_IID` | 11.6 | all | The IID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | 11.6 | all | The source branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` | 11.9 | all | The HEAD sha of the source branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_ID` | 11.6 | all | The ID of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` | 11.6 | all | The path of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_URL` | 11.6 | all | The URL of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | 11.6 | all | The target branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_TARGET_BRANCH_SHA` | 11.9 | all | The HEAD sha of the target branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_TITLE` | 11.9 | all | The title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of username(s) of assignee(s) for the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_NODE_INDEX` | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
+| `CI_NODE_TOTAL` | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
+| `CI_API_V4_URL` | 11.7 | all | The GitLab API v4 root URL |
+| `CI_PAGES_DOMAIN` | 11.8 | all | The configured domain that hosts GitLab Pages. |
+| `CI_PAGES_URL` | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
+| `CI_PIPELINE_ID` | 8.10 | all | The unique id of the current pipeline that GitLab CI uses internally |
+| `CI_PIPELINE_IID` | 11.0 | all | The unique id of the current pipeline scoped to project |
+| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
+| `CI_PIPELINE_TRIGGERED` | all | all | The flag to indicate that job was [triggered] |
+| `CI_PIPELINE_URL` | 11.1 | 0.5 | Pipeline details URL |
+| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
+| `CI_PROJECT_ID` | all | all | The unique id of the current project that GitLab CI uses internally |
+| `CI_PROJECT_NAME` | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) |
+| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
+| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
+| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address to access project |
+| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
+| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | If the job is running on a protected branch |
+| `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
+| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
+| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
+| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository |
+| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner as saved in GitLab |
+| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
+| `CI_RUNNER_ID` | 8.10 | 0.5 | The unique id of runner being used |
+| `CI_RUNNER_REVISION` | all | 10.6 | GitLab Runner revision that is executing the current job |
+| `CI_RUNNER_TAGS` | 8.10 | 0.5 | The defined runner tags |
+| `CI_RUNNER_VERSION` | all | 10.6 | GitLab Runner version that is executing the current job |
+| `CI_SERVER` | all | all | Mark that job is executed in CI environment |
+| `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs |
+| `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs |
+| `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs |
+| `CI_SERVER_VERSION_MAJOR` | 11.4 | all | GitLab version major component |
+| `CI_SERVER_VERSION_MINOR` | 11.4 | all | GitLab version minor component |
+| `CI_SERVER_VERSION_PATCH` | 11.4 | all | GitLab version patch component |
+| `CI_SHARED_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
+| `GET_SOURCES_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
+| `GITLAB_CI` | all | all | Mark that job is executed in GitLab CI environment |
+| `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the job |
+| `GITLAB_USER_ID` | 8.12 | all | The id of the user who started the job |
+| `GITLAB_USER_LOGIN` | 10.0 | all | The login username of the user who started the job |
+| `GITLAB_USER_NAME` | 10.0 | all | The real name of the user who started the job |
+| `RESTORE_CACHE_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
+| `GITLAB_FEATURES` | 10.6 | all | The comma separated list of licensed features available for your instance and plan |
+
+[gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token
+[registry]: ../../user/project/container_registry.md
+[dependent-repositories]: ../../user/project/new_ci_build_permissions_model.md#dependent-repositories
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index 1d98e8426fe..8009b1d5e8a 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Where variables can be used
As it's described in the [CI/CD variables](README.md) docs, you can
@@ -15,26 +19,26 @@ There are two places defined variables can be used. On the:
### `.gitlab-ci.yml` file
-| Definition | Can be expanded? | Expansion place | Description |
-|--------------------------------------|-------------------|-----------------|--------------|
-| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>Supported: all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>Not supported: variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> |
-| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
-| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `services:[]:name` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
-| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
-| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
-| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>Not supported:<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
+| Definition | Can be expanded? | Expansion place | Description |
+|:-------------------------------------------|:-----------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<br/><br/>Supported are all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules).<br/><br/>Not supported are variables defined in Runner's `config.toml` and variables created in job's `script`. |
+| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
+| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `services:[]:name` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
+| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
+| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
+| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
### `config.toml` file
NOTE: **Note:**
You can read more about `config.toml` in the [Runner's docs](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
-| Definition | Can be expanded? | Description |
-|--------------------------------------|------------------|-------------|
+| Definition | Can be expanded? | Description |
+|:-------------------------------------|:-----------------|:---------------------------------------------------------------------------------------------------------------------------------------------|
| `runners.environment` | yes | The variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `runners.kubernetes.pod_labels` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `runners.kubernetes.pod_annotations` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
@@ -106,9 +110,28 @@ The following variables are known as "persisted":
They are:
-- Supported for definitions where the ["Expansion place"](#gitlab-ci-yml-file) is:
+- Supported for definitions where the ["Expansion place"](#gitlab-ciyml-file) is:
- Runner.
- Script execution shell.
- Not supported:
- - For definitions where the ["Expansion place"](#gitlab-ci-yml-file) is GitLab.
- - In the `only` and `except` [variables expressions](README.md#variables-expressions).
+ - For definitions where the ["Expansion place"](#gitlab-ciyml-file) is GitLab.
+ - In the `only` and `except` [variables expressions](README.md#environment-variables-expressions).
+
+## Variables with an environment scope
+
+Variables defined with an environment scope are supported. Given that
+there is a variable `$STAGING_SECRET` defined in a scope of
+`review/staging/*`, the following job that is using dynamic environments
+is going to be created, based on the matching variable expression:
+
+```yaml
+my-job:
+ stage: staging
+ environment:
+ name: review/$CI_JOB_STAGE/deploy
+ script:
+ - 'deploy staging'
+ only:
+ variables:
+ - $STAGING_SECRET == 'something'
+```
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e5668c140fa..fa4b0378f61 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,26 +1,43 @@
-# Configuration of your pipelines with .gitlab-ci.yml
+---
+type: reference
+---
+
+# GitLab CI/CD Pipeline Configuration Reference
+
+GitLab CI/CD [pipelines](../pipelines.md) are configured using a YAML file called `.gitlab-ci.yml` within each project.
+
+The `.gitlab-ci.yml` file defines the structure and order of the pipelines and determines:
-This document describes the usage of `.gitlab-ci.yml`, the file that is used by
-GitLab Runner to manage your project's pipelines.
+- What to execute using [GitLab Runner](https://docs.gitlab.com/runner/).
+- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
-From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
-file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
-of your repository and contains definitions of how your project should be built.
+This topic covers CI/CD pipeline configuration. For other CI/CD configuration information, see:
-If you want a quick introduction to GitLab CI, follow our
-[quick start guide](../quick_start/README.md).
+- [GitLab CI/CD Variables](../variables/README.md), for configuring the environment the pipelines run in.
+- [GitLab Runner advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html), for configuring GitLab Runner.
+
+We have complete examples of configuring pipelines:
+
+- For a quick introduction to GitLab CI, follow our [quick start guide](../quick_start/README.md).
+- For a collection of examples, see [GitLab CI/CD Examples](../examples/README.md).
+- To see a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab-ce`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml).
NOTE: **Note:**
-If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository-starter),
+If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter),
you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
-## Jobs
+## Introduction
+
+Pipeline configuration begins with jobs. Jobs are the most fundamental element of a `.gitlab-ci.yml` file.
-The YAML file defines a set of jobs with constraints stating when they should
-be run. You can specify an unlimited number of jobs which are defined as
-top-level elements with an arbitrary name and always have to contain at least
-the `script` clause.
+Jobs are:
+
+- Defined with constraints stating under what conditions they should be executed.
+- Top-level elements with an arbitrary name and must contain at least the [`script`](#script) clause.
+- Not limited in how many can be defined.
+
+For example:
```yaml
job1:
@@ -39,6 +56,14 @@ Jobs are picked up by [Runners](../runners/README.md) and executed within the
environment of the Runner. What is important, is that each job is run
independently from each other.
+### Validate the .gitlab-ci.yml
+
+Each instance of GitLab CI has an embedded debug tool called Lint, which validates the
+content of your `.gitlab-ci.yml` files. You can find the Lint under the page `ci/lint` of your
+project namespace. For example, `https://gitlab.example.com/gitlab-org/project-123/-/ci/lint`.
+
+### Unavailable names for jobs
+
Each job must have a unique name, but there are a few **reserved `keywords` that
cannot be used as job names**:
@@ -51,42 +76,137 @@ cannot be used as job names**:
- `variables`
- `cache`
-A job is defined by a list of parameters that define the job behavior.
-
-| Keyword | Required | Description |
-|---------------|----------|-------------|
-| [script](#script) | yes | Defines a shell script which is executed by Runner |
-| [extends](#extends) | no | Defines a configuration entry that this job is going to inherit from |
-| [include](#include) | no | Defines a configuration entry that allows this job to include external YAML files |
-| [image](#image-and-services) | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| [services](#image-and-services) | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| [stage](#stage) | no | Defines a job stage (default: `test`) |
-| type | no | Alias for `stage` |
-| [variables](#variables) | no | Define job variables on a job level |
-| [only](#only-and-except-simplified) | no | Defines a list of git refs for which job is created |
-| [except](#only-and-except-simplified) | no | Defines a list of git refs for which job is not created |
-| [tags](#tags) | no | Defines a list of tags which are used to select Runner |
-| [allow_failure](#allow_failure) | no | Allow job to fail. Failed job doesn't contribute to commit status |
-| [when](#when) | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
-| [dependencies](#dependencies) | no | Define other jobs that a job depends on so that you can pass artifacts between them|
-| [artifacts](#artifacts) | no | Define list of [job artifacts](#artifacts) |
-| [cache](#cache) | no | Define list of files that should be cached between subsequent runs |
-| [before_script](#before_script-and-after_script) | no | Override a set of commands that are executed before job |
-| [after_script](#before_script-and-after_script) | no | Override a set of commands that are executed after job |
-| [environment](#environment) | no | Defines a name of environment to which deployment is done by this job |
-| [coverage](#coverage) | no | Define code coverage settings for a given job |
-| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure |
-| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel |
-
-## `image` and `services`
-
-This allows to specify a custom Docker image and a list of services that can be
-used for time of the job. The configuration of this feature is covered in
-[a separate document](../docker/README.md).
-
-## `before_script` and `after_script`
-
-> Introduced in GitLab 8.7 and requires GitLab Runner v1.2
+### Using reserved keywords
+
+If you get validation error when using specific values (for example, `true` or `false`), try to:
+
+- Quote them.
+- Change them to a different form. For example, `/bin/true`.
+
+## Configuration parameters
+
+A job is defined as a list of parameters that define the job's behavior.
+
+The following table lists available parameters for jobs:
+
+| Keyword | Description |
+|:---------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [`script`](#script) | Shell script which is executed by Runner. |
+| [`image`](#image) | Use docker images. Also available: `image:name` and `image:entrypoint`. |
+| [`services`](#services) | Use docker services images. Also available: `services:name`, `services:alias`, `services:entrypoint`, and `services:command`. |
+| [`before_script`](#before_script-and-after_script) | Override a set of commands that are executed before job. |
+| [`after_script`](#before_script-and-after_script) | Override a set of commands that are executed after job. |
+| [`stages`](#stages) | Define stages in a pipeline. |
+| [`stage`](#stage) | Defines a job stage (default: `test`). |
+| [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). |
+| [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). |
+| [`tags`](#tags) | List of tags which are used to select Runner. |
+| [`allow_failure`](#allow_failure) | Allow job to fail. Failed job doesn't contribute to commit status. |
+| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
+| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, and `environment:action`. |
+| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
+| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, and `artifacts:reports:junit`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_management`, `artifacts:reports:performance` and `artifacts:reports:metrics`. |
+| [`dependencies`](#dependencies) | Other jobs that a job depends on so that you can pass artifacts between them. |
+| [`coverage`](#coverage) | Code coverage settings for a given job. |
+| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
+| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
+| [`trigger`](#trigger-premium) | Defines a downstream pipeline trigger. |
+| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
+| [`extends`](#extends) | Configuration entries that this job is going to inherit from. |
+| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
+| [`variables`](#variables) | Define job variables on a job level. |
+
+NOTE: **Note:**
+Parameters `types` and `type` are [deprecated](#deprecated-parameters).
+
+## Parameter details
+
+The following are detailed explanations for parameters used to configure CI/CD pipelines.
+
+### `script`
+
+`script` is the only required keyword that a job needs. It's a shell script
+which is executed by the Runner. For example:
+
+```yaml
+job:
+ script: "bundle exec rspec"
+```
+
+This parameter can also contain several commands using an array:
+
+```yaml
+job:
+ script:
+ - uname -a
+ - bundle exec rspec
+```
+
+NOTE: **Note:**
+Sometimes, `script` commands will need to be wrapped in single or double quotes.
+For example, commands that contain a colon (`:`) need to be wrapped in quotes so
+that the YAML parser knows to interpret the whole thing as a string rather than
+a "key: value" pair. Be careful when using special characters:
+`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
+
+### `image`
+
+Used to specify [a Docker image](../docker/using_docker_images.md#what-is-an-image) to use for the job.
+
+For:
+
+- Simple definition examples, see [Define `image` and `services` from .gitlab-ci.yml](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml).
+- Detailed usage information, refer to [Docker integration](../docker/README.md) documentation.
+
+#### `image:name`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see [Available settings for `image`](../docker/using_docker_images.md#available-settings-for-image).
+
+#### `image:entrypoint`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see [Available settings for `image`](../docker/using_docker_images.md#available-settings-for-image).
+
+### `services`
+
+Used to specify a [service Docker image](../docker/using_docker_images.md#what-is-a-service), linked to a base image specified in [`image`](#image).
+
+For:
+
+- Simple definition examples, see [Define `image` and `services` from .gitlab-ci.yml](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml).
+- Detailed usage information, refer to [Docker integration](../docker/README.md) documentation.
+- For example services, see [GitLab CI Services](../services/README.md).
+
+#### `services:name`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see see [Available settings for `services`](../docker/using_docker_images.md#available-settings-for-services).
+
+#### `services:alias`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see see [Available settings for `services`](../docker/using_docker_images.md#available-settings-for-services).
+
+#### `services:entrypoint`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see see [Available settings for `services`](../docker/using_docker_images.md#available-settings-for-services).
+
+#### `services:command`
+
+An [extended docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options).
+
+For more information, see see [Available settings for `services`](../docker/using_docker_images.md#available-settings-for-services).
+
+### `before_script` and `after_script`
+
+> Introduced in GitLab 8.7 and requires GitLab Runner v1.2.
`before_script` is used to define the command that should be run before all
jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts).
@@ -116,7 +236,7 @@ job:
- execute this after my script
```
-## `stages`
+### `stages`
`stages` is used to define stages that can be used by jobs and is defined
globally.
@@ -150,11 +270,11 @@ There are also two edge cases worth mentioning:
`test` and `deploy` are allowed to be used as job's stage by default.
1. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
-## `stage`
+### `stage`
`stage` is defined per-job and relies on [`stages`](#stages) which is defined
globally. It allows to group jobs into different stages, and jobs of the same
-`stage` are executed in `parallel`. For example:
+`stage` are executed in parallel (subject to [certain conditions](#using-your-own-runners)). For example:
```yaml
stages:
@@ -179,38 +299,18 @@ job 4:
script: make deploy
```
-## `types`
+#### Using your own Runners
-CAUTION: **Deprecated:**
-`types` is deprecated, and could be removed in one of the future releases.
-Use [stages](#stages) instead.
-
-## `script`
-
-`script` is the only required keyword that a job needs. It's a shell script
-which is executed by the Runner. For example:
-
-```yaml
-job:
- script: "bundle exec rspec"
-```
-
-This parameter can also contain several commands using an array:
+When using your own Runners, GitLab Runner runs only one job at a time by default (see the
+`concurrent` flag in [Runner global settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section)
+for more information).
-```yaml
-job:
- script:
- - uname -a
- - bundle exec rspec
-```
+Jobs will run on your own Runners in parallel only if:
-Sometimes, `script` commands will need to be wrapped in single or double quotes.
-For example, commands that contain a colon (`:`) need to be wrapped in quotes so
-that the YAML parser knows to interpret the whole thing as a string rather than
-a "key: value" pair. Be careful when using special characters:
-`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
+- Run on different Runners.
+- The Runner's `concurrent` setting has been changed.
-## `only` and `except` (simplified)
+### `only`/`except` (basic)
`only` and `except` are two parameters that set a job policy to limit when
jobs are created:
@@ -223,7 +323,7 @@ There are a few rules that apply to the usage of job policy:
- `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`.
-- `only` and `except` allow the use of regular expressions (using [Ruby regexp syntax](https://ruby-doc.org/core/Regexp.html)).
+- `only` and `except` allow the use of regular expressions ([supported regexp syntax](#supported-onlyexcept-regexp-syntax)).
- `only` and `except` allow to specify a repository path to filter jobs for
forks.
@@ -241,6 +341,7 @@ In addition, `only` and `except` allow the use of special keywords:
| `triggers` | For pipelines created using a trigger token. |
| `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). |
| `merge_requests` | When a merge request is created or updated (See [pipelines for merge requests](../merge_request_pipelines/index.md)). |
+| `chats` | For jobs created using a [GitLab ChatOps](../chatops/README.md) command. |
In the example below, `job` will run only for refs that start with `issue-`,
whereas all branches will be skipped:
@@ -255,6 +356,19 @@ job:
- branches
```
+Pattern matching is case-sensitive by default. Use `i` flag modifier, like
+`/pattern/i` to make a pattern case-insensitive:
+
+```yaml
+job:
+ # use regexp
+ only:
+ - /^issue-.*$/i
+ # use special keyword
+ except:
+ - branches
+```
+
In this example, `job` will run only for refs that are tagged, or if a build is
explicitly requested via an API trigger or a [Pipeline Schedule][schedules]:
@@ -276,10 +390,11 @@ job:
- branches@gitlab-org/gitlab-ce
except:
- master@gitlab-org/gitlab-ce
+ - /^release/.*$/@gitlab-org/gitlab-ce
```
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
-except master.
+except `master` and those with names prefixed with `release/`.
If a job does not have an `only` rule, `only: ['branches', 'tags']` is set by
default. If it doesn't have an `except` rule, it is empty.
@@ -299,11 +414,50 @@ job:
only: ['branches', 'tags']
```
-## `only` and `except` (complex)
+#### Regular expressions
+
+Because `@` is used to denote the beginning of a ref's repository path,
+matching a ref name containing the `@` character in a regular expression
+requires the use of the hex character code match `\x40`.
+
+Only the tag or branch name can be matched by a regular expression.
+The repository path, if given, is always matched literally.
+
+If a regular expression shall be used to match the tag or branch name,
+the entire ref name part of the pattern has to be a regular expression,
+and must be surrounded by `/`.
+(With regular expression flags appended after the closing `/`.)
+So `issue-/.*/` won't work to match all tag names or branch names
+that begin with `issue-`.
-> - `refs` and `kubernetes` policies introduced in GitLab 10.0.
-> - `variables` policy introduced in GitLab 10.7.
-> - `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in GitLab 11.4.
+TIP: **Tip**
+Use anchors `^` and `$` to avoid the regular expression
+matching only a substring of the tag name or branch name.
+For example, `/^issue-.*$/` is equivalent to `/^issue-/`,
+while just `/issue/` would also match a branch called `severe-issues`.
+
+### Supported `only`/`except` regexp syntax
+
+CAUTION: **Warning:**
+This is a breaking change that was introduced with GitLab 11.9.4.
+
+In GitLab 11.9.4, GitLab begun internally converting regexp used
+in `only` and `except` parameters to [RE2](https://github.com/google/re2/wiki/Syntax).
+
+This means that only subset of features provided by [Ruby Regexp](https://ruby-doc.org/core/Regexp.html)
+is supported. [RE2](https://github.com/google/re2/wiki/Syntax) limits the set of features
+provided due to computational complexity, which means some features became unavailable in GitLab 11.9.4.
+For example, negative lookaheads.
+
+For GitLab versions from 11.9.7 and up to GitLab 12.0, GitLab provides a feature flag that can be
+enabled by administrators that allows users to use unsafe regexp syntax. This brings compatibility
+with previously allowed syntax version and allows users to gracefully migrate to the new syntax.
+
+```ruby
+Feature.enable(:allow_unsafe_ruby_regexp)
+```
+
+### `only`/`except` (advanced)
CAUTION: **Warning:**
This an _alpha_ feature, and it is subject to change at any time without
@@ -323,10 +477,12 @@ If you use multiple keys under `only` or `except`, they act as an AND. The logic
> (any of refs) AND (any of variables) AND (any of changes) AND (if kubernetes is active)
-### `only:refs` and `except:refs`
+#### `only:refs`/`except:refs`
+
+> `refs` policy introduced in GitLab 10.0.
The `refs` strategy can take the same values as the
-[simplified only/except configuration](#only-and-except-simplified).
+[simplified only/except configuration](#onlyexcept-basic).
In the example below, the `deploy` job is going to be created only when the
pipeline has been [scheduled][schedules] or runs for the `master` branch:
@@ -339,7 +495,9 @@ deploy:
- schedules
```
-### `only:kubernetes` and `except:kubernetes`
+#### `only:kubernetes`/`except:kubernetes`
+
+> `kubernetes` policy introduced in GitLab 10.0.
The `kubernetes` strategy accepts only the `active` keyword.
@@ -352,7 +510,9 @@ deploy:
kubernetes: active
```
-### `only:variables` and `except:variables`
+#### `only:variables`/`except:variables`
+
+> `variables` policy introduced in GitLab 10.7.
The `variables` keyword is used to define variables expressions. In other words,
you can use predefined variables / project / group or
@@ -382,11 +542,13 @@ end-to-end:
- $CI_COMMIT_MESSAGE =~ /skip-end-to-end-tests/
```
-Learn more about [variables expressions](../variables/README.md#variables-expressions).
+Learn more about [variables expressions](../variables/README.md#environment-variables-expressions).
-### `only:changes` and `except:changes`
+#### `only:changes`/`except:changes`
-Using the `changes` keyword with `only` or `except`, makes it possible to define if
+> `changes` policy [introduced][ce-19232] in GitLab 11.4.
+
+Using the `changes` keyword with `only` or `except` makes it possible to define if
a job should be created based on files modified by a git push event.
For example:
@@ -402,36 +564,59 @@ docker build:
- more_scripts/*.{rb,py,sh}
```
-In the scenario above, if you are pushing multiple commits to GitLab to an
-existing branch, GitLab creates and triggers the `docker build` job, provided that
-one of the commits contains changes to either:
+In the scenario above, when pushing multiple commits to GitLab to an existing
+branch, GitLab creates and triggers the `docker build` job, provided that one of the
+commits contains changes to any of the following:
- The `Dockerfile` file.
- Any of the files inside `docker/scripts/` directory.
- Any of the files and subdirectories inside the `dockerfiles` directory.
- Any of the files with `rb`, `py`, `sh` extensions inside the `more_scripts` directory.
+You can also use glob patterns to match multiple files in either the root directory of the repo, or in _any_ directory within the repo. For example:
+
+```yaml
+test:
+ script: npm run test
+ only:
+ changes:
+ - "*.json"
+ - "**/*.sql"
+```
+
+NOTE: **Note:**
+In the example above, the expressions are wrapped double quotes because they are glob patterns. GitLab will fail to parse `.gitlab-ci.yml` files with unwrapped glob patterns.
+
+The following example will skip the CI job if a change is detected in any file in the root directory of the repo with a `.md` extension:
+
+```yaml
+build:
+ script: npm run build
+ except:
+ changes:
+ - "*.md"
+```
+
CAUTION: **Warning:**
There are some caveats when using this feature with new branches and tags. See
the section below.
-#### Using `changes` with new branches and tags
+##### Using `changes` with new branches and tags
-If you are pushing a **new** branch or a **new** tag to GitLab, the policy
-always evaluates to true and GitLab will create a job. This feature is not
-connected with merge requests yet, and because GitLab is creating pipelines
-before an user can create a merge request we don't know a target branch at
-this point.
+When pushing a **new** branch or a **new** tag to GitLab, the policy always
+evaluates to true and GitLab will create a job. This feature is not connected
+with merge requests yet and, because GitLab is creating pipelines before a user
+can create a merge request, it is unknown what the target branch is at this point.
-#### Using `changes` with `merge_requests`
+##### Using `changes` with `merge_requests`
With [pipelines for merge requests](../merge_request_pipelines/index.md),
-make it possible to define if a job should be created base on files modified
+it is possible to define a job to be created based on files modified
in a merge request.
For example:
-```
+```yaml
docker build service one:
script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG .
only:
@@ -442,11 +627,11 @@ docker build service one:
- service-one/**/*
```
-In the scenario above, if you create or update a merge request that changes
-either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers
-the `docker build service one` job.
+In the scenario above, if a merge request is created or updated that changes
+either files in `service-one` directory or the `Dockerfile`, GitLab creates
+and triggers the `docker build service one` job.
-## `tags`
+### `tags`
`tags` is used to select specific Runners from the list of all Runners that are
allowed to run this project.
@@ -489,7 +674,7 @@ osx job:
- echo "Hello, $USER!"
```
-## `allow_failure`
+### `allow_failure`
`allow_failure` allows a job to fail without impacting the rest of the CI
suite.
@@ -525,7 +710,7 @@ job3:
- deploy_to_staging
```
-## `when`
+### `when`
`when` is used to implement jobs that are run in case of failure or despite the
failure.
@@ -587,10 +772,8 @@ The above script will:
success or failure.
1. Allow you to manually execute `deploy_job` from GitLab's UI.
-### `when:manual`
+#### `when:manual`
-> **Notes:**
->
> - Introduced in GitLab 8.10.
> - Blocking manual actions were introduced in GitLab 9.0.
> - Protected actions were introduced in GitLab 9.2.
@@ -599,7 +782,7 @@ Manual actions are a special type of job that are not executed automatically,
they need to be explicitly started by a user. An example usage of manual actions
would be a deployment to a production environment. Manual actions can be started
from the pipeline, job, environment, and deployment views. Read more at the
-[environments documentation](../environments.md#manually-deploying-to-environments).
+[environments documentation](../environments.md#configuring-manual-deployments).
Manual actions can be either optional or blocking. Blocking manual actions will
block the execution of the pipeline at the stage this action is defined in. It's
@@ -618,11 +801,11 @@ action fails, the pipeline will eventually succeed.
Manual actions are considered to be write actions, so permissions for
[protected branches](../../user/project/protected_branches.md) are used when
-user wants to trigger an action. In other words, in order to trigger a manual
-action assigned to a branch that the pipeline is running for, user needs to
-have ability to merge to this branch.
+a user wants to trigger an action. In other words, in order to trigger a manual
+action assigned to a branch that the pipeline is running for, the user needs to
+have the ability to merge to this branch.
-### `when:delayed`
+#### `when:delayed`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
@@ -658,10 +841,8 @@ This job will never be executed in the future unless you execute the job manuall
You can start a delayed job immediately by clicking the **Play** button.
GitLab runner will pick your job soon and start the job.
-## `environment`
+### `environment`
-> **Notes:**
->
> - Introduced in GitLab 8.9.
> - You can read more about environments and find more examples in the
> [documentation about environments][environment].
@@ -683,10 +864,8 @@ deploy to production:
In the above example, the `deploy to production` job will be marked as doing a
deployment to the `production` environment.
-### `environment:name`
+#### `environment:name`
-> **Notes:**
->
> - Introduced in GitLab 8.11.
> - Before GitLab 8.11, the name of an environment could be defined as a string like
> `environment: production`. The recommended way now is to define it under the
@@ -722,10 +901,8 @@ deploy to production:
name: production
```
-### `environment:url`
+#### `environment:url`
-> **Notes:**
->
> - Introduced in GitLab 8.11.
> - Before GitLab 8.11, the URL could be added only in GitLab's UI. The
> recommended way now is to define it in `.gitlab-ci.yml`.
@@ -749,10 +926,8 @@ deploy to production:
url: https://prod.example.com
```
-### `environment:on_stop`
+#### `environment:on_stop`
-> **Notes:**
->
> - [Introduced][ce-6669] in GitLab 8.13.
> - Starting with GitLab 8.14, when you have an environment that has a stop action
> defined, GitLab will automatically trigger a stop action when the associated
@@ -764,7 +939,7 @@ the environment.
Read the `environment:action` section for an example.
-### `environment:action`
+#### `environment:action`
> [Introduced][ce-6669] in GitLab 8.13.
@@ -805,10 +980,8 @@ The `stop_review_app` job is **required** to have the following keywords defined
- `stage` should be the same as the `review_app` in order for the environment
to stop automatically when the branch is deleted
-### Dynamic environments
+#### Dynamic environments
-> **Notes:**
->
> - [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6.
> - The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15.
> - The `name` and `url` parameters can use any of the defined CI variables,
@@ -841,10 +1014,8 @@ The common use case is to create dynamic environments for branches and use them
as Review Apps. You can see a simple example using Review Apps at
<https://gitlab.com/gitlab-examples/review-apps-nginx/>.
-## `cache`
+### `cache`
-> **Notes:**
->
> - Introduced in GitLab Runner v0.7.0.
> - `cache` can be set globally and per-job.
> - From GitLab 9.0, caching is enabled and shared between pipelines and jobs
@@ -862,7 +1033,7 @@ workspace.
If `cache` is defined outside the scope of jobs, it means it is set
globally and all jobs will use that definition.
-### `cache:paths`
+#### `cache:paths`
Use the `paths` directive to choose which files or directories will be cached.
Wildcards can be used as well.
@@ -898,7 +1069,7 @@ Note that since cache is shared between jobs, if you're using different
paths for different jobs, you should also set a different **cache:key**
otherwise cache content can be overwritten.
-### `cache:key`
+#### `cache:key`
> Introduced in GitLab Runner v1.0.0.
@@ -939,7 +1110,7 @@ cache:
- binaries/
```
-### `cache:untracked`
+#### `cache:untracked`
Set `untracked: true` to cache all files that are untracked in your Git
repository:
@@ -962,7 +1133,7 @@ rspec:
- binaries/
```
-### `cache:policy`
+#### `cache:policy`
> Introduced in GitLab 9.4.
@@ -1009,10 +1180,8 @@ Additionally, if you have a job that unconditionally recreates the cache without
reference to its previous contents, you can use `policy: push` in that job to
skip the download step.
-## `artifacts`
+### `artifacts`
-> **Notes:**
->
> - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
> - Windows support was added in GitLab Runner v.1.0.0.
> - From GitLab 9.2, caches are restored before artifacts.
@@ -1020,14 +1189,14 @@ skip the download step.
> - Job artifacts are only collected for successful jobs by default.
`artifacts` is used to specify a list of files and directories which should be
-attached to the job after success.
+attached to the job when it [succeeds, fails, or always](#artifactswhen).
-The artifacts will be sent to GitLab after the job finishes successfully and will
+The artifacts will be sent to GitLab after the job finishes and will
be available for download in the GitLab UI.
-[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md)
+[Read more about artifacts](../../user/project/pipelines/job_artifacts.md).
-### `artifacts:paths`
+#### `artifacts:paths`
You can only use paths that are within the project workspace. To pass artifacts
between different jobs, see [dependencies](#dependencies).
@@ -1072,7 +1241,7 @@ release-job:
- tags
```
-### `artifacts:name`
+#### `artifacts:name`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
@@ -1153,14 +1322,13 @@ job:
- binaries/
```
-### `artifacts:untracked`
+#### `artifacts:untracked`
`artifacts:untracked` is used to add all Git untracked files as artifacts (along
to the paths defined in `artifacts:paths`).
NOTE: **Note:**
-To exclude the folders/files which should not be a part of `untracked` just
-add them to `.gitignore`.
+`artifacts:untracked` ignores configuration in the repository's `.gitignore` file.
Send all Git untracked files:
@@ -1178,7 +1346,7 @@ artifacts:
- binaries/
```
-### `artifacts:when`
+#### `artifacts:when`
> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
@@ -1199,7 +1367,7 @@ job:
when: on_failure
```
-### `artifacts:expire_in`
+#### `artifacts:expire_in`
> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
@@ -1234,7 +1402,7 @@ job:
expire_in: 1 week
```
-### `artifacts:reports`
+#### `artifacts:reports`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in
GitLab 11.2. Requires GitLab Runner 11.2 and above.
@@ -1252,7 +1420,7 @@ NOTE: **Note:**
If you also want the ability to browse the report output files, include the
[`artifacts:paths`](#artifactspaths) keyword.
-#### `artifacts:reports:junit`
+##### `artifacts:reports:junit`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in
GitLab 11.2. Requires GitLab Runner 11.2 and above.
@@ -1286,7 +1454,7 @@ concatenated into a single file. Use a filename pattern (`junit: rspec-*.xml`),
an array of filenames (`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`), or a
combination thereof (`junit: [rspec.xml, test-results/TEST-*.xml]`).
-#### `artifacts:reports:codequality` **[STARTER]**
+##### `artifacts:reports:codequality` **[STARTER]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
@@ -1296,51 +1464,51 @@ as artifacts.
The collected Code Quality report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests.
-#### `artifacts:reports:sast` **[ULTIMATE]**
+##### `artifacts:reports:sast` **[ULTIMATE]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `sast` report collects [SAST vulnerabilities](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html)
+The `sast` report collects [SAST vulnerabilities](https://docs.gitlab.com/ee/user/application_security/sast/index.html)
as artifacts.
The collected SAST report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
-#### `artifacts:reports:dependency_scanning` **[ULTIMATE]**
+##### `artifacts:reports:dependency_scanning` **[ULTIMATE]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `dependency_scanning` report collects [Dependency Scanning vulnerabilities](https://docs.gitlab.com/ee/user/project/merge_requests/dependency_scanning.html)
+The `dependency_scanning` report collects [Dependency Scanning vulnerabilities](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html)
as artifacts.
The collected Dependency Scanning report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
-#### `artifacts:reports:container_scanning` **[ULTIMATE]**
+##### `artifacts:reports:container_scanning` **[ULTIMATE]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `container_scanning` report collects [Container Scanning vulnerabilities](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html)
+The `container_scanning` report collects [Container Scanning vulnerabilities](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html)
as artifacts.
The collected Container Scanning report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
-#### `artifacts:reports:dast` **[ULTIMATE]**
+##### `artifacts:reports:dast` **[ULTIMATE]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `dast` report collects [DAST vulnerabilities](https://docs.gitlab.com/ee/user/project/merge_requests/dast.html)
+The `dast` report collects [DAST vulnerabilities](https://docs.gitlab.com/ee/user/application_security/dast/index.html)
as artifacts.
The collected DAST report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
-#### `artifacts:reports:license_management` **[ULTIMATE]**
+##### `artifacts:reports:license_management` **[ULTIMATE]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
@@ -1351,7 +1519,7 @@ The collected License Management report will be uploaded to GitLab as an artifac
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
-#### `artifacts:reports:performance` **[PREMIUM]**
+##### `artifacts:reports:performance` **[PREMIUM]**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
@@ -1361,7 +1529,17 @@ as artifacts.
The collected Performance report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests.
-## `dependencies`
+##### `artifacts:reports:metrics` **[PREMIUM]**
+
+> Introduced in GitLab 11.10.
+
+The `metrics` report collects [Metrics](../../ci/metrics_reports.md)
+as artifacts.
+
+The collected Metrics report will be uploaded to GitLab as an artifact and will
+be automatically shown in merge requests.
+
+### `dependencies`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -1420,7 +1598,7 @@ deploy:
script: make deploy
```
-### When a dependent job will fail
+#### When a dependent job will fail
> Introduced in GitLab 10.3.
@@ -1434,7 +1612,7 @@ You can ask your administrator to
[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
and bring back the old behavior.
-## `coverage`
+### `coverage`
> [Introduced][ce-7447] in GitLab 8.17.
@@ -1454,7 +1632,7 @@ job1:
coverage: '/Code coverage: \d+\.\d+/'
```
-## `retry`
+### `retry`
> [Introduced][ce-12909] in GitLab 9.5.
> [Behaviour expanded](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21758)
@@ -1528,7 +1706,7 @@ Possible values for `when` are:
- `missing_dependency_failure`: Retry if a dependency was missing.
- `runner_unsupported`: Retry if the runner was unsupported.
-## `parallel`
+### `parallel`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22631) in GitLab 11.5.
@@ -1548,7 +1726,49 @@ test:
parallel: 5
```
-## `include`
+### `trigger` **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
+
+`trigger` allows you to define downstream pipeline trigger. When a job created
+from `trigger` definition is started by GitLab, a downstream pipeline gets
+created.
+
+Learn more about [multi-project pipelines](../multi_project_pipelines.md#creating-multi-project-pipelines-from-gitlab-ciyml).
+
+#### Simple `trigger` syntax
+
+The most simple way to configure a downstream trigger to use `trigger` keyword
+with a full path to a downstream project:
+
+```yaml
+rspec:
+ stage: test
+ script: bundle exec rspec
+
+staging:
+ stage: deploy
+ trigger: my/deployment
+```
+
+#### Complex `trigger` syntax
+
+It is possible to configure a branch name that GitLab will use to create
+a downstream pipeline with:
+
+```yaml
+rspec:
+ stage: test
+ script: bundle exec rspec
+
+staging:
+ stage: deploy
+ trigger:
+ project: my/deployment
+ branch: stable
+```
+
+### `include`
> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
> - Available for Starter, Premium and Ultimate since 10.6.
@@ -1568,9 +1788,6 @@ TIP: **Tip:**
Use merging to customize and override included CI/CD configurations with local
definitions.
-Recursive includes are not supported. Your external files should not use the
-`include` keyword as it will be ignored.
-
NOTE: **Note:**
Using YAML aliases across different YAML files sourced by `include` is not
supported. You must only refer to aliases in the same file. Instead
@@ -1585,7 +1802,12 @@ of using YAML anchors, you can use the [`extends` keyword](#extends).
See [usage examples](#include-examples).
-### `include:local`
+NOTE: **Note:**
+`.gitlab-ci.yml` configuration included by all methods is evaluated at pipeline creation.
+The configuration is a snapshot in time and persisted in the database. Any changes to
+referenced `.gitlab-ci.yml` configuration will not be reflected in GitLab until the next pipeline is created.
+
+#### `include:local`
`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
It's referenced using full paths relative to the root directory (`/`).
@@ -1594,6 +1816,9 @@ You can only use files that are currently tracked by Git on the same branch
your configuration file is on. In other words, when using a `include:local`, make
sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+All [nested includes](#nested-includes) will be executed in the scope of the same project,
+so it is possible to use local, project, remote or template includes.
+
NOTE: **Note:**
Including local files through Git submodules paths is not supported.
@@ -1604,7 +1829,7 @@ include:
- local: '/templates/.gitlab-ci-template.yml'
```
-### `include:file`
+#### `include:file`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) in GitLab 11.7.
@@ -1635,7 +1860,11 @@ include:
file: '/templates/.gitlab-ci-template.yml'
```
-### `include:template`
+All [nested includes](#nested-includes) will be executed in the scope of the target project,
+so it is possible to use local (relative to target project), project, remote
+or template includes.
+
+#### `include:template`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) in GitLab 11.7.
@@ -1650,7 +1879,10 @@ include:
- template: Auto-DevOps.gitlab-ci.yml
```
-### `include:remote`
+All [nested includes](#nested-includes) will be executed only with the permission of the user,
+so it is possible to use project, remote or template includes.
+
+#### `include:remote`
`include:remote` can be used to include a file from a different location,
using HTTP/HTTPS, referenced by using the full URL. The remote file must be
@@ -1662,16 +1894,22 @@ include:
- remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
-NOTE: **Note for GitLab admins:**
-In order to include files from another repository inside your local network,
-you may need to enable the **Allow requests to the local network from hooks and services** checkbox
-located in the **Admin area > Settings > Network > Outbound requests** section.
+All nested includes will be executed without context as public user, so only another remote,
+or public project, or template is allowed.
+
+#### Nested includes
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/56836) in GitLab 11.9.
-### `include` examples
+Nested includes allow you to compose a set of includes.
+A total of 50 includes is allowed.
+Duplicate includes are considered a configuration error.
+
+#### `include` examples
Here are a few more `include` examples.
-#### Single string or array of multiple values
+##### Single string or array of multiple values
You can include your extra YAML file(s) either as a single string or
an array of multiple values. The following examples are all valid.
@@ -1725,7 +1963,7 @@ include:
file: '/templates/.gitlab-ci-template.yml'
```
-#### Re-using a `before_script` template
+##### Re-using a `before_script` template
In the following example, the content of `.before-script-template.yml` will be
automatically fetched and evaluated along with the content of `.gitlab-ci.yml`.
@@ -1749,7 +1987,7 @@ rspec:
- bundle exec rspec
```
-#### Overriding external template values
+##### Overriding external template values
The following example shows specific YAML-defined variables and details of the
`production` job from an include file being customized in `.gitlab-ci.yml`.
@@ -1769,7 +2007,7 @@ production:
- deploy
environment:
name: production
- url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
only:
- master
```
@@ -1834,11 +2072,56 @@ In this case, if `install_dependencies` and `deploy` were not repeated in
`.gitlab-ci.yml`, they would not be part of the script for the `production`
job in the combined CI configuration.
-## `extends`
+##### Using nested includes
+
+The examples below show how includes can be nested from different sources
+using a combination of different methods.
+
+In this example, `.gitlab-ci.yml` includes local the file `/.gitlab-ci/another-config.yml`:
+
+```yaml
+include:
+ - local: /.gitlab-ci/another-config.yml
+```
+
+The `/.gitlab-ci/another-config.yml` includes a template and the `/templates/docker-workflow.yml` file
+from another project:
+
+```yaml
+include:
+ - template: Bash.gitlab-ci.yml
+ - project: group/my-project
+ file: /templates/docker-workflow.yml
+```
+
+The `/templates/docker-workflow.yml` present in `group/my-project` includes two local files
+of the `group/my-project`:
+
+```yaml
+include:
+ - local: /templates/docker-build.yml
+ - local: /templates/docker-testing.yml
+```
+
+Our `/templates/docker-build.yml` present in `group/my-project` adds a `docker-build` job:
+
+```yaml
+docker-build:
+ script: docker build -t my-image .
+```
+
+Our second `/templates/docker-test.yml` present in `group/my-project` adds a `docker-test` job:
+
+```yaml
+docker-test:
+ script: docker run my-image /run/tests.sh
+```
+
+### `extends`
> Introduced in GitLab 11.3.
-`extends` defines an entry name that a job that uses `extends` is going to
+`extends` defines entry names that a job that uses `extends` is going to
inherit from.
It is an alternative to using [YAML anchors](#anchors) and is a little
@@ -1915,7 +2198,47 @@ spinach:
script: rake spinach
```
-## Using `extends` and `include` together
+It's also possible to use multiple parents for `extends`.
+The algorithm used for merge is "closest scope wins", so keys
+from the last member will always shadow anything defined on other levels.
+For example:
+
+```yaml
+.only-important:
+ only:
+ - master
+ - stable
+ tags:
+ - production
+
+.in-docker:
+ tags:
+ - docker
+ image: alpine
+
+rspec:
+ extends:
+ - .only-important
+ - .in-docker
+ script:
+ - rake rspec
+```
+
+This results in the following `rspec` job:
+
+```yaml
+rspec:
+ only:
+ - master
+ - stable
+ tags:
+ - docker
+ image: alpine
+ script:
+ - rake rspec
+```
+
+### Using `extends` and `include` together
`extends` works across configuration files combined with `include`.
@@ -1940,7 +2263,7 @@ useTemplate:
This will run a job called `useTemplate` that runs `echo Hello!` as defined in
the `.template` job, and uses the `alpine` Docker image as defined in the local job.
-## `pages`
+### `pages`
`pages` is a special job that is used to upload static content to GitLab that
can be used to serve your website. It has a special syntax, so the two
@@ -1969,7 +2292,7 @@ pages:
Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
-## `variables`
+### `variables`
> Introduced in GitLab Runner v0.5.0.
@@ -1999,12 +2322,12 @@ Runner itself](../variables/README.md#predefined-environment-variables).
One example would be `CI_COMMIT_REF_NAME` which has the value of
the branch or tag name for which project is built. Apart from the variables
you can set in `.gitlab-ci.yml`, there are also the so called
-[Variables](../variables/README.md#variables)
+[Variables](../variables/README.md#gitlab-cicd-environment-variables)
which can be set in GitLab's UI.
-[Learn more about variables and their priority.][variables]
+Learn more about [variables and their priority][variables].
-### Git strategy
+#### Git strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
> completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
@@ -2049,7 +2372,7 @@ NOTE: **Note:** `GIT_STRATEGY` is not supported for
but may be in the future. See the [support Git strategy with Kubernetes executor feature proposal](https://gitlab.com/gitlab-org/gitlab-runner/issues/3847)
for updates.
-### Git submodule strategy
+#### Git submodule strategy
> Requires GitLab Runner v1.10+.
@@ -2087,9 +2410,9 @@ Note that for this feature to work correctly, the submodules must be configured
- a relative path to another repository on the same GitLab server. See the
[Git submodules](../git_submodules.md) documentation.
-### Git checkout
+#### Git checkout
-> Introduced in GitLab Runner 9.3
+> Introduced in GitLab Runner 9.3.
The `GIT_CHECKOUT` variable can be used when the `GIT_STRATEGY` is set to either
`clone` or `fetch` to specify whether a `git checkout` should be run. If not
@@ -2112,11 +2435,38 @@ variables:
GIT_STRATEGY: clone
GIT_CHECKOUT: "false"
script:
- - git checkout master
- - git merge $CI_BUILD_REF_NAME
+ - git checkout -B master origin/master
+ - git merge $CI_COMMIT_SHA
+```
+
+#### Git clean flags
+
+> Introduced in GitLab Runner 11.10
+
+The `GIT_CLEAN_FLAGS` variable is used to control the default behavior of
+`git clean` after checking out the sources. You can set it globally or per-job in the
+[`variables`](#variables) section.
+
+`GIT_CLEAN_FLAGS` accepts all possible options of the [git clean](https://git-scm.com/docs/git-clean)
+command.
+
+`git clean` is disabled if `GIT_CHECKOUT: "false"` is specified.
+
+If `GIT_CLEAN_FLAGS` is:
+
+- Not specified, `git clean` flags default to `-ffdx`.
+- Given the value `none`, `git clean` is not executed.
+
+For example:
+
+```yaml
+variables:
+ GIT_CLEAN_FLAGS: -ffdx -e cache/
+script:
+ - ls -al cache/
```
-### Job stages attempts
+#### Job stages attempts
> Introduced in GitLab, it requires GitLab Runner v1.9+.
@@ -2140,7 +2490,7 @@ variables:
You can set them globally or per-job in the [`variables`](#variables) section.
-### Shallow cloning
+#### Shallow cloning
> Introduced in GitLab 8.9 as an experimental feature. May change in future
releases or be removed completely.
@@ -2150,7 +2500,7 @@ shallow cloning of the repository which can significantly speed up cloning for
repositories with a large number of commits or old, large binaries. The value is
passed to `git fetch` and `git clone`.
->**Note:**
+NOTE: **Note:**
If you use a depth of 1 and have a queue of jobs or retry
jobs, jobs may fail.
@@ -2173,6 +2523,88 @@ variables:
You can set it globally or per-job in the [`variables`](#variables) section.
+## Deprecated parameters
+
+The following parameters are deprecated.
+
+### `types`
+
+CAUTION: **Deprecated:**
+`types` is deprecated, and could be removed in a future release.
+Use [`stages`](#stages) instead.
+
+### `type`
+
+CAUTION: **Deprecated:**
+`type` is deprecated, and could be removed in one of the future releases.
+Use [`stage`](#stage) instead.
+
+## Custom build directories
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1267) in Gitlab Runner 11.10
+
+NOTE: **Note:**
+This can only be used when `custom_build_dir` is enabled in the [Runner's
+configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscustom_build_dir-section).
+This is the default configuration for `docker` and `kubernetes` executor.
+
+By default, GitLab Runner clones the repository in a unique subpath of the
+`$CI_BUILDS_DIR` directory. However, your project might require the code in a
+specific directory (Go projects, for example). In that case, you can specify
+the `GIT_CLONE_PATH` variable to tell the Runner in which directory to clone the
+repository:
+
+```yml
+variables:
+ GIT_CLONE_PATH: $CI_BUILDS_DIR/project-name
+
+test:
+ script:
+ - pwd
+```
+
+The `GIT_CLONE_PATH` has to always be within `$CI_BUILDS_DIR`. The directory set in `$CI_BUILDS_DIR`
+is dependent on executor and configuration of [runners.builds_dir](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
+setting.
+
+### Handling concurrency
+
+An executor using a concurrency greater than `1` might lead
+to failures because multiple jobs might be working on the same directory if the `builds_dir`
+is shared between jobs.
+GitLab Runner does not try to prevent this situation. It is up to the administrator
+and developers to comply with the requirements of Runner configuration.
+
+To avoid this scenario, you can use a unique path within `$CI_BUILDS_DIR`, because Runner
+exposes two additional variables that provide a unique `ID` of concurrency:
+
+- `$CI_CONCURRENT_ID`: Unique ID for all jobs running within the given executor.
+- `$CI_CONCURRENT_PROJECT_ID`: Unique ID for all jobs running within the given executor and project.
+
+The most stable configuration that should work well in any scenario and on any executor
+is to use `$CI_CONCURRENT_ID` in the `GIT_CLONE_PATH`. For example:
+
+```yml
+variables:
+ GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/project-name
+
+test:
+ script:
+ - pwd
+```
+
+The `$CI_CONCURRENT_PROJECT_ID` should be used in conjunction with `$CI_PROJECT_PATH`
+as the `$CI_PROJECT_PATH` provides a path of a repository. That is, `group/subgroup/project`. For example:
+
+```yml
+variables:
+ GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH
+
+test:
+ script:
+ - pwd
+```
+
## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`)
@@ -2332,10 +2764,21 @@ You can see that the hidden keys are conveniently used as templates.
## Triggers
Triggers can be used to force a rebuild of a specific branch, tag or commit,
-with an API call.
+with an API call when a pipeline gets created using a trigger token.
+
+Not to be confused with [`trigger`](#trigger-premium).
[Read more in the triggers documentation.](../triggers/README.md)
+## Processing Git pushes
+
+GitLab will create at most 4 branch and tags pipelines when
+doing pushing multiple changes in single `git push` invocation.
+
+This limitation does not affect any of the updated Merge Request pipelines,
+all updated Merge Requests will have a pipeline created when using
+[pipelines for merge requests](../merge_request_pipelines/index.md).
+
## Skipping jobs
If your commit message contains `[ci skip]` or `[skip ci]`, using any
@@ -2348,28 +2791,25 @@ using Git 2.10 or newer:
git push -o ci.skip
```
-## Validate the .gitlab-ci.yml
-
-Each instance of GitLab CI has an embedded debug tool called Lint, which validates the
-content of your `.gitlab-ci.yml` files. You can find the Lint under the page `ci/lint` of your
-project namespace (e.g, `http://gitlab-example.com/gitlab-org/project-123/-/ci/lint`)
-
-## Using reserved keywords
-
-If you get validation error when using specific values (e.g., `true` or `false`),
-try to quote them, or change them to a different form (e.g., `/bin/true`).
+<!-- ## Troubleshooting
-## Examples
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-See a [list of examples](../examples/README.md "CI/CD examples") for using
-GitLab CI/CD with various languages.
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
[ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
+[ce-19232]: https://gitlab.com/gitlab-org/gitlab-ce/issues/19232
[environment]: ../environments.md "CI/CD environments"
[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
[variables]: ../variables/README.md "CI/CD variables"
-[push-option]: https://git-scm.com/docs/git-push#git-push--oltoptiongt
+[push-option]: https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 5d2f5edcb18..b31870df36d 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/container_registry.md'
+---
+
This document was moved to [another location](../user/project/container_registry.md).
diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md
index 2f8cd37b488..c99d7011ac2 100644
--- a/doc/container_registry/troubleshooting.md
+++ b/doc/container_registry/troubleshooting.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/container_registry.md'
+---
+
This document was moved to [user/project/container_registry](../user/project/container_registry.md).
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
new file mode 100644
index 00000000000..adaa120a37e
--- /dev/null
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter'
+---
+
+This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter).
diff --git a/doc/customization/system_header_and_footer_messages.md b/doc/customization/system_header_and_footer_messages.md
index cf7d8b3f3e8..9d6931c730d 100644
--- a/doc/customization/system_header_and_footer_messages.md
+++ b/doc/customization/system_header_and_footer_messages.md
@@ -7,6 +7,10 @@ Navigate to the **Admin** area and go to the **Appearance** page.
Under **System header and footer** insert your header message and/or footer message.
Both background and font color of the header and footer are customizable.
+You can also apply the header and footer messages to gitlab emails,
+by checking the **Enable header and footer in emails** checkbox.
+Note that color settings will only be applied within the app interface and not to emails
+
![appearance](system_header_and_footer_messages/appearance.png)
After saving, all GitLab pages will contain the custom system header and/or footer messages:
diff --git a/doc/customization/system_header_and_footer_messages/appearance.png b/doc/customization/system_header_and_footer_messages/appearance.png
index 14667f751c4..fd315bb6c07 100644
--- a/doc/customization/system_header_and_footer_messages/appearance.png
+++ b/doc/customization/system_header_and_footer_messages/appearance.png
Binary files differ
diff --git a/doc/customization/system_header_and_footer_messages/custom_header_footer.png b/doc/customization/system_header_and_footer_messages/custom_header_footer.png
index 691ca8a3484..15d19c87dd7 100644
--- a/doc/customization/system_header_and_footer_messages/custom_header_footer.png
+++ b/doc/customization/system_header_and_footer_messages/custom_header_footer.png
Binary files differ
diff --git a/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png
index a9c59bc9f62..51349ddf6f4 100644
--- a/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png
+++ b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png
Binary files differ
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 9194f847cdf..45f1fed355e 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -1 +1,5 @@
+---
+redirect_to: 'branded_login_page.md'
+---
+
This document was moved to [another location](branded_login_page.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index 13646cbfe48..d2f09fc01de 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -20,6 +20,7 @@ description: 'Learn how to contribute to GitLab.'
- [Automatic CE->EE merge](automatic_ce_ee_merge.md)
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
+- [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers)
## UX and frontend guides
@@ -38,22 +39,28 @@ description: 'Learn how to contribute to GitLab.'
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
- [Working with Gitaly](gitaly.md)
- [Manage feature flags](feature_flags.md)
+- [Licensed feature availability](licensed_feature_availability.md)
- [View sent emails or preview mailers](emails.md)
- [Shell commands](shell_commands.md) in the GitLab codebase
- [`Gemfile` guidelines](gemfile.md)
- [Pry debugging](pry_debugging.md)
- [Sidekiq debugging](sidekiq_debugging.md)
+- [Accessing session data](session.md)
- [Gotchas](gotchas.md) to avoid
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md)
- [Import/Export development documentation](import_export.md)
+- [Elasticsearch integration docs](elasticsearch.md)
- [Working with Merge Request diffs](diffs.md)
- [Kubernetes integration guidelines](kubernetes.md)
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
- [DeclarativePolicy framework](policies.md)
+- [How Git object deduplication works in GitLab](git_object_deduplication.md)
+- [Geo development](geo.md)
+- [Routing](routing.md)
## Performance guides
@@ -100,6 +107,10 @@ description: 'Learn how to contribute to GitLab.'
- [Query Count Limits](query_count_limits.md)
- [Database helper modules](database_helpers.md)
+## Integration guides
+
+- [Jira Connect app](integrations/jira_connect.md)
+
## Testing guides
- [Testing standards and style guidelines](testing_guide/index.md)
@@ -128,3 +139,8 @@ description: 'Learn how to contribute to GitLab.'
## Go guides
- [Go Guidelines](go_guide/index.md)
+
+## Other GitLab Development Kit (GDK) guides
+
+- [Run full Auto DevOps cycle in a GDK instance](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/auto_devops.md)
+- [Using GitLab Runner with GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/runner.md)
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 95722c027ba..38270af682e 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -9,28 +9,6 @@ can be shared.
It is also possible to add a `private_token` to the querystring, or
add a `HTTP_PRIVATE_TOKEN` header.
-### Authorization
-
-Fields can be authorized using the same abilities used in the Rails
-app. This can be done using the `authorize` helper:
-
-```ruby
-module Types
- class QueryType < BaseObject
- graphql_name 'Query'
-
- field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
- authorize :read_project
- end
- end
-```
-
-The object found by the resolve call is used for authorization.
-
-This works for authorizing a single record, for authorizing
-collections, we should only load what the currently authenticated user
-is allowed to view. Preferably we use our existing finders for that.
-
## Types
When exposing a model through the GraphQL API, we do so by creating a
@@ -54,6 +32,21 @@ a new presenter specifically for GraphQL.
The presenter is initialized using the object resolved by a field, and
the context.
+### Exposing Global ids
+
+When exposing an `id` field on a type, we will by default try to
+expose a global id by calling `to_global_id` on the resource being
+rendered.
+
+To override this behaviour, you can implement an `id` method on the
+type for which you are exposing an id. Please make sure that when
+exposing a `GraphQL::ID_TYPE` using a custom method that it is
+globally unique.
+
+The records that are exposing a `full_path` as an `ID_TYPE` are one of
+these exceptions. Since the full path is a unique identifier for a
+`Project` or `Namespace`.
+
### Connection Types
GraphQL uses [cursor based
@@ -101,14 +94,14 @@ look like this:
{
"cursor": "Nzc=",
"node": {
- "id": "77",
+ "id": "gid://gitlab/Pipeline/77",
"status": "FAILED"
}
},
{
"cursor": "Njc=",
"node": {
- "id": "67",
+ "id": "gid://gitlab/Pipeline/67",
"status": "FAILED"
}
}
@@ -187,6 +180,114 @@ end
policies at once. The fields for these will all have be non-nullable
booleans with a default description.
+## Authorization
+
+Authorizations can be applied to both types and fields using the same
+abilities as in the Rails app.
+
+If the:
+
+- Currently authenticated user fails the authorization, the authorized
+resource will be returned as `null`.
+- Resource is part of a collection, the collection will be filtered to
+exclude the objects that the user's authorization checks failed against.
+
+TIP: **Tip:**
+Try to load only what the currently authenticated user is allowed to
+view with our existing finders first, without relying on authorization
+to filter the records. This minimizes database queries and unnecessary
+authorization checks of the loaded records.
+
+### Type authorization
+
+Authorize a type by passing an ability to the `authorize` method. All
+fields with the same type will be authorized by checking that the
+currently authenticated user has the required ability.
+
+For example, the following authorization ensures that the currently
+authenticated user can only see projects that they have the
+`read_project` ability for (so long as the project is returned in a
+field that uses `Types::ProjectType`):
+
+```ruby
+module Types
+ class ProjectType < BaseObject
+ authorize :read_project
+ end
+end
+```
+
+You can also authorize against multiple abilities, in which case all of
+the ability checks must pass.
+
+For example, the following authorization ensures that the currently
+authenticated user must have `read_project` and `another_ability`
+abilities to see a project:
+
+```ruby
+module Types
+ class ProjectType < BaseObject
+ authorize [:read_project, :another_ability]
+ end
+end
+```
+
+### Field authorization
+
+Fields can be authorized with the `authorize` option.
+
+For example, the following authorization ensures that the currently
+authenticated user must have the `owner_access` ability to see the
+project:
+
+```ruby
+module Types
+ class MyType < BaseObject
+ field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :owner_access
+ end
+end
+```
+
+Fields can also be authorized against multiple abilities, in which case
+all of ability checks must pass. **Note:** This requires explicitly
+passing a block to `field`:
+
+```ruby
+module Types
+ class MyType < BaseObject
+ field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
+ authorize [:owner_access, :another_ability]
+ end
+ end
+end
+```
+
+NOTE: **Note:** If the field's type already [has a particular
+authorization](#type-authorization) then there is no need to add that
+same authorization to the field.
+
+### Type and Field authorizations together
+
+Authorizations are cumulative, so where authorizations are defined on
+a field, and also on the field's type, then the currently authenticated
+user would need to pass all ability checks.
+
+In the following simplified example the currently authenticated user
+would need both `first_permission` and `second_permission` abilities in
+order to see the author of the issue.
+
+```ruby
+class UserType
+ authorize :first_permission
+end
+```
+
+```ruby
+class IssueType
+ field :author, UserType, authorize: :second_permission
+end
+```
+
## Resolvers
To find objects to display in a field, we can add resolvers to
@@ -244,7 +345,7 @@ argument :project_path, GraphQL::ID_TYPE,
required: true,
description: "The project the merge request to mutate is in"
-argument :iid, GraphQL::ID_TYPE,
+argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the merge request to mutate"
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index e22552fd3a3..650325121b2 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -10,34 +10,147 @@ For information, see the [GitLab Release Process](https://gitlab.com/gitlab-org/
Both EE and CE require some add-on components called gitlab-shell and Gitaly. These components are available from the [gitlab-shell](https://gitlab.com/gitlab-org/gitlab-shell/tree/master) and [gitaly](https://gitlab.com/gitlab-org/gitaly/tree/master) repositories respectively. New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
-## GitLab Omnibus Component by Component
+## Components
-This document is designed to be consumed by systems adminstrators and GitLab Support Engineers who want to understand more about the internals of GitLab and how they work together.
+A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
-When deployed, GitLab should be considered the amalgamation of the below processes. When troubleshooting or debugging, be as specific as possible as to which component you are referencing. That should increase clarity and reduce confusion.
+We also support deploying GitLab on Kubernetes using our [gitlab Helm chart](https://docs.gitlab.com/charts/).
-### GitLab Process Descriptions
+The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
-As of this writing, a fresh GitLab 11.3.0 install will show the following processes with `gitlab-ctl status`:
+When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
+The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories through Gitaly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
+
+Gitaly executes git operations from gitlab-shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
+
+You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/).
+
+### Component diagram
+
+```mermaid
+graph TB
+
+ HTTP[HTTP/HTTPS] -- TCP 80, 443 --> NGINX[NGINX]
+ SSH -- TCP 22 --> GitLabShell[GitLab Shell]
+ SMTP[SMTP Gateway]
+ Geo[GitLab Geo Node] -- TCP 22, 80, 443 --> NGINX
+
+ GitLabShell --TCP 8080 -->Unicorn["Unicorn (GitLab Rails)"]
+ GitLabShell --> Gitaly
+ GitLabShell --> Redis
+ Unicorn --> PgBouncer[PgBouncer]
+ Unicorn --> Redis
+ Unicorn --> Gitaly
+ Redis --> Sidekiq
+ Sidekiq["Sidekiq (GitLab Rails, ES Indexer)"] --> PgBouncer
+ GitLabWorkhorse[GitLab Workhorse] --> Unicorn
+ GitLabWorkhorse --> Redis
+ GitLabWorkhorse --> Gitaly
+ Gitaly --> Redis
+ NGINX --> GitLabWorkhorse
+ NGINX -- TCP 8090 --> GitLabPages[GitLab Pages]
+ NGINX --> Grafana[Grafana]
+ Grafana -- TCP 9090 --> Prometheus[Prometheus]
+ Prometheus -- TCP 80, 443 --> Unicorn
+ RedisExporter[Redis Exporter] --> Redis
+ Prometheus -- TCP 9121 --> RedisExporter
+ PostgreSQLExporter[PostgreSQL Exporter] --> PostgreSQL
+ PgBouncerExporter[PgBouncer Exporter] --> PgBouncer
+ Prometheus -- TCP 9187 --> PostgreSQLExporter
+ Prometheus -- TCP 9100 --> NodeExporter[Node Exporter]
+ Prometheus -- TCP 9168 --> GitLabMonitor[GitLab Monitor]
+ Prometheus -- TCP 9127 --> PgBouncerExporter
+ GitLabMonitor --> PostgreSQL
+ GitLabMonitor --> GitLabShell
+ GitLabMonitor --> Sidekiq
+ PgBouncer --> Consul
+ PostgreSQL --> Consul
+ PgBouncer --> PostgreSQL
+ NGINX --> Registry
+ Unicorn --> Registry
+ NGINX --> Mattermost
+ Mattermost --- Unicorn
+ Prometheus --> Alertmanager
+ Migrations --> PostgreSQL
+ Runner -- TCP 443 --> NGINX
+ Unicorn -- TCP 9200 --> ElasticSearch
+ Sidekiq -- TCP 9200 --> ElasticSearch
+ Sidekiq -- TCP 80, 443 --> Sentry
+ Unicorn -- TCP 80, 443 --> Sentry
+ Sidekiq -- UDP 6831 --> Jaeger
+ Unicorn -- UDP 6831 --> Jaeger
+ Gitaly -- UDP 6831 --> Jaeger
+ GitLabShell -- UDP 6831 --> Jaeger
+ GitLabWorkhorse -- UDP 6831 --> Jaeger
+ Alertmanager -- TCP 25 --> SMTP
+ Sidekiq -- TCP 25 --> SMTP
+ Unicorn -- TCP 25 --> SMTP
+ Unicorn -- TCP 369 --> LDAP
+ Sidekiq -- TCP 369 --> LDAP
+ Unicorn -- TCP 443 --> ObjectStorage["Object Storage"]
+ Sidekiq -- TCP 443 --> ObjectStorage
+ GitLabWorkhorse -- TCP 443 --> ObjectStorage
+ Registry -- TCP 443 --> ObjectStorage
+ Geo -- TCP 5432 --> PostgreSQL
```
-run: alertmanager: (pid 30829) 14207s; run: log: (pid 13906) 2432044s
-run: gitaly: (pid 30771) 14210s; run: log: (pid 13843) 2432046s
-run: gitlab-monitor: (pid 30788) 14209s; run: log: (pid 13868) 2432045s
-run: gitlab-workhorse: (pid 30758) 14210s; run: log: (pid 13855) 2432046s
-run: logrotate: (pid 30246) 3407s; run: log: (pid 13825) 2432047s
-run: nginx: (pid 30849) 14207s; run: log: (pid 13856) 2432046s
-run: node-exporter: (pid 30929) 14206s; run: log: (pid 13877) 2432045s
-run: postgres-exporter: (pid 30935) 14206s; run: log: (pid 13931) 2432044s
-run: postgresql: (pid 13133) 2432214s; run: log: (pid 13848) 2432046s
-run: prometheus: (pid 30807) 14209s; run: log: (pid 13884) 2432045s
-run: redis: (pid 30560) 14274s; run: log: (pid 13807) 2432047s
-run: redis-exporter: (pid 30946) 14205s; run: log: (pid 13869) 2432045s
-run: sidekiq: (pid 30953) 14205s; run: log: (pid 13810) 2432047s
-run: unicorn: (pid 30960) 14204s; run: log: (pid 13809) 2432047s
-```
-### Layers
+### Component legend
+
+* ✅ - Installed by default
+* âš™ - Requires additional configuration, or GitLab Managed Apps
+* ⤓ - Manual installation required
+* ⌠- Not supported or no instructions available
+
+Component statuses are linked to configuration documentation for each component.
+
+### Component list
+
+| Component | Description | [Omnibus GitLab](https://docs.gitlab.com/omnibus/) | [GitLab chart](https://docs.gitlab.com/charts/) | [Minikube Minimal](https://docs.gitlab.com/charts/development/minikube/#deploying-gitlab-with-minimal-settings) | [GitLab.com](https://gitlab.com) | [Source](../install/installation.md) | [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit) | CE/EE |
+| --------- | ----------- |:--------------------:|:------------------:|:-----:|:--------:|:--------:|:-------:|:-------:|
+| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | [✅][nginx-omnibus] | [✅][nginx-charts] | [⚙][nginx-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⤓][nginx-source] | ⌠| CE & EE |
+| [Unicorn (GitLab Rails)](#unicorn) | Handles requests for the web interface and API | [✅][unicorn-omnibus] | [✅][unicorn-charts] | [✅][unicorn-charts] | [✅](../user/gitlab_com/index.md#unicorn) | [⚙][unicorn-source] | [✅][gitlab-yml] | CE & EE |
+| [Sidekiq](#sidekiq) | Background jobs processor | [✅][sidekiq-omnibus] | [✅][sidekiq-charts] | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](../user/gitlab_com/index.md#sidekiq) | [✅][gitlab-yml] | [✅][gitlab-yml] | CE & EE |
+| [Gitaly](#gitaly) | Git RPC service for handling all git calls made by GitLab | [✅][gitaly-omnibus] | [✅][gitaly-charts] | [✅][gitaly-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][gitaly-source] | ✅ | CE & EE |
+| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | [✅][workhorse-omnibus] | [✅][workhorse-charts] | [✅][workhorse-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][workhorse-source] | ✅ | CE & EE |
+| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | [✅][shell-omnibus] | [✅][shell-charts] | [✅][shell-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][shell-source] | [✅][gitlab-yml] | CE & EE |
+| [GitLab Pages](#gitlab-pages) | Hosts static websites | [âš™][pages-omnibus] | [âŒ][pages-charts] | [âŒ][pages-charts] | [✅](../user/gitlab_com/index.md#gitlab-pages) | [âš™][pages-source] | [âš™][pages-gdk] | CE & EE |
+| [Registry](#registry) | Container registry, allows pushing and pulling of images | [⚙][registry-omnibus] | [✅][registry-charts] | [✅][registry-charts] | [✅](../user/project/container_registry.md#build-and-push-images) | [⤓][registry-source] | [⚙][registry-gdk] | CE & EE |
+| [Redis](#redis) | Caching service | [✅][redis-omnibus] | [✅][redis-omnibus] | [✅][redis-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⤓][redis-source] | ✅ | CE & EE |
+| [PostgreSQL](#postgresql) | Database | [✅][postgres-omnibus] | [✅][postgres-charts] | [✅][postgres-charts] | [✅](../user/gitlab_com/index.md#postgresql) | [⤓][postgres-source] | ✅ | CE & EE |
+| [PgBouncer](#pgbouncer) | Database connection pooling, failover | [âš™][pgbouncer-omnibus] | [âŒ][pgbouncer-charts] | [âŒ][pgbouncer-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#database-architecture) | ⌠| ⌠| EE Only |
+| [Consul](#consul) | Database node discovery, failover | [âš™][consul-omnibus] | [âŒ][consul-charts] | [âŒ][consul-charts] | [✅](../user/gitlab_com/index.md#consul) | ⌠| ⌠| EE Only |
+| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | [✅][prometheus-omnibus] | [✅][prometheus-charts] | [⚙][prometheus-charts] | [✅](../user/gitlab_com/index.md#prometheus) | ⌠| ⌠| CE & EE |
+| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | [✅][alertmanager-omnibus] | [✅][alertmanager-charts] | [⚙][alertmanager-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | [⚙][grafana-omnibus] | [⤓][grafana-charts] | [⤓][grafana-charts] | [✅](https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?refresh=30s) | ⌠| ⌠| CE & EE |
+| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | [⤓][sentry-omnibus] | [âŒ][sentry-charts] | [âŒ][sentry-charts] | [✅](https://about.gitlab.com/handbook/support/workflows/services/gitlab_com/500_errors.html#searching-sentry) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
+| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | [âŒ][jaeger-omnibus] | [âŒ][jaeger-charts] | [âŒ][jaeger-charts] | [âŒ](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104) | [⤓][jaeger-source] | [âš™][jaeger-gdk] | CE & EE |
+| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | [✅][redis-exporter-omnibus] | [✅][redis-exporter-charts] | [✅][redis-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [Postgres Exporter](#postgres-exporter) | Prometheus endpoint with PostgreSQL metrics | [✅][postgres-exporter-omnibus] | [✅][postgres-exporter-charts] | [✅][postgres-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | [âš™][pgbouncer-exporter-omnibus] | [âŒ][pgbouncer-exporter-charts] | [âŒ][pgbouncer-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [GitLab Monitor](#gitlab-monitor) | Generates a variety of GitLab metrics | [✅][gitlab-monitor-omnibus] | [âŒ][gitab-monitor-charts] | [âŒ][gitab-monitor-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | [✅][node-exporter-omnibus] | [âŒ][node-exporter-charts] | [âŒ][node-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ⌠| ⌠| CE & EE |
+| [Mattermost](#mattermost) | Open-source Slack alternative | [⚙][mattermost-omnibus] | [⤓][mattermost-charts] | [⤓][mattermost-charts] | [⤓](../user/project/integrations/mattermost_slash_commands.md#manual-configuration), [⤓](../user/project/integrations/mattermost.html) | ⌠| ⌠| CE & EE |
+| [MinIO](#minio) | Object storage service | [⤓][minio-omnibus] | [✅][minio-charts] | [✅][minio-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | ⌠| [⚙][minio-gdk] | CE & EE |
+| [Runner](#gitlab-runner) | Executes GitLab CI jobs | [⤓][runner-omnibus] | [✅][runner-charts] | [⚙][runner-charts] | [✅](../user/gitlab_com/index.md#shared-runners) | [⚙][runner-source] | [⚙][runner-gdk] | CE & EE |
+| [Database Migrations](#database-migrations) | Database migrations | [✅][database-migrations-omnibus] | [✅][database-migrations-charts] | [✅][database-migrations-charts] | ✅ | [⚙][database-migrations-source] | ✅ | CE & EE |
+| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | [✅][certificate-management-omnibus] | [✅][certificate-management-charts] | [⚙][certificate-management-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#secrets-management) | [⚙][certificate-management-source] | [⚙][certificate-management-gdk] | CE & EE |
+| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | [âš™][geo-omnibus] | [âŒ][geo-charts] | [âŒ][geo-charts] | ✅ | ⌠| [âš™][geo-gdk] | EE Only |
+| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | [⤓][ldap-omnibus] | [⤓][ldap-charts] | [⤓][ldap-charts] | [âŒ](https://about.gitlab.com/pricing/#gitlab-com) | [⤓][gitlab-yml] | [⤓][ldap-gdk] | CE & EE |
+| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | [⤓][outbound-email-omnibus] | [⤓][outbound-email-charts] | [⤓][outbound-email-charts] | [✅](../user/gitlab_com/index.md#mail-configuration) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
+| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | [⤓][inbound-email-omnibus] | [⤓][inbound-email-charts] | [⤓][inbound-email-charts] | [✅](../user/gitlab_com/index.md#mail-configuration) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
+| [ElasticSearch](#elasticsearch) | Improved search within GitLab | [⤓][elasticsearch-omnibus] | [⤓][elasticsearch-charts] | [⤓][elasticsearch-charts] | [âŒ](https://gitlab.com/groups/gitlab-org/-/epics/153) | [⤓][elasticsearch-source] | [⤓][elasticsearch-gdk] | EE Only |
+| [Sentry integration](#sentry) | Error tracking for deployed apps | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | CE & EE |
+| [Jaeger integration](#jaeger) | Distributed tracing for deployed apps | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | EE Only |
+| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy [Helm](https://docs.helm.sh/), [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/), [Cert-Manager](https://docs.cert-manager.io/en/latest/), [Prometheus](https://prometheus.io/docs/introduction/overview/), a [Runner](https://docs.gitlab.com/runner/), [JupyterHub](http://jupyter.org/), [Knative](https://cloud.google.com/knative) to a cluster | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | CE & EE |
+
+### Component details
+
+This document is designed to be consumed by systems administrators and GitLab Support Engineers who want to understand more about the internals of GitLab and how they work together.
+
+When deployed, GitLab should be considered the amalgamation of the below processes. When troubleshooting or debugging, be as specific as possible as to which component you are referencing. That should increase clarity and reduce confusion.
+
+**Layers**
GitLab can be considered to have two layers from a process perspective:
@@ -46,117 +159,288 @@ GitLab can be considered to have two layers from a process perspective:
- **Processors**: These processes are responsible for actually performing operations and presenting the service.
- **Data**: These services store/expose structured data for the GitLab service.
-### alertmanager
+#### Alertmanager
-- Omnibus configuration options
+- [Project page](https://github.com/prometheus/alertmanager/blob/master/README.md)
+- Configuration: [Omnibus][alertmanager-omnibus], [Charts][alertmanager-charts]
- Layer: Monitoring
+- Process: `alertmanager`
+
+[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
+
+#### Certificate management
+
+- Project page: [Omnibus](https://github.com/certbot/certbot/blob/master/README.rst), [Charts](https://github.com/jetstack/cert-manager/blob/master/README.md)
+- Configuration: [Omnibus][certificate-management-omnibus], [Charts][certificate-management-charts], [Source][certificate-management-source], [GDK][certificate-management-gdk]
+- Layer: Core Service (Processor)
+
+#### Consul
+
+- [Project page](https://github.com/hashicorp/consul/blob/master/README.md)
+- Configuration: [Omnibus][consul-omnibus], [Charts][consul-charts]
+- Layer: Core Service (Data)
+
+Consul is a tool for service discovery and configuration. Consul is distributed, highly available, and extremely scalable.
+
+#### Database migrations
+
+- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts], [Source][database-migrations-source]
+- Layer: Core Service (Data)
+
+#### Elasticsearch
-[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
+- [Project page](https://github.com/elastic/elasticsearch/blob/master/README.textile)
+- Configuration: [Omnibus][elasticsearch-omnibus], [Charts][elasticsearch-charts], [Source][elasticsearch-source], [GDK][elasticsearch-gdk]
+- Layer: Core Service (Data)
+
+Elasticsearch is a distributed RESTful search engine built for the cloud.
-### gitaly
+#### Gitaly
-- [Omnibus configuration options](https://gitlab.com/gitlab-org/gitaly/tree/master/doc/configuration)
+- [Project page](https://gitlab.com/gitlab-org/gitaly/blob/master/README.md)
+- Configuration: [Omnibus][gitaly-omnibus], [Charts][gitaly-charts], [Source][gitaly-source]
- Layer: Core Service (Data)
+- Process: `gitaly`
-Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (Think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
+Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
-### gitlab-monitor
+#### Gitlab Geo
+
+- Configuration: [Omnibus][geo-omnibus], [Charts][geo-charts], [GDK][geo-gdk]
+- Layer: Core Service (Processor)
-- Omnibus configuration options
+#### Gitlab Monitor
+
+- [Project page](https://gitlab.com/gitlab-org/gitlab-monitor)
+- Configuration: [Omnibus][gitlab-monitor-omnibus], [Charts][gitlab-monitor-charts]
- Layer: Monitoring
+- Process: `gitlab-monitor`
+
+GitLab Monitor is a process designed in house that allows us to export metrics about GitLab application internals to Prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor).
+
+#### Gitlab Pages
+
+- Configuration: [Omnibus][pages-omnibus], [Charts][pages-charts], [Source][pages-source], [GDK][pages-gdk]
+- Layer: Core Service (Processor)
+
+GitLab Pages is a feature that allows you to publish static websites directly from a repository in GitLab.
+
+You can use it either for personal or business websites, such as portfolios, documentation, manifestos, and business presentations. You can also attribute any license to your content.
+
+#### Gitlab Runner
+
+- [Project page](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/README.md)
+- Configuration: [Omnibus][runner-omnibus], [Charts][runner-charts], [Source][runner-source], [GDK][runner-gdk]
+- Layer: Core Service (Processor)
+
+GitLab Runner runs tests and sends the results to GitLab.
-GitLab Monitor is a process disigned in house that allows us to export metrics about GitLab application internals to prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor)
+GitLab CI is the open-source continuous integration service included with GitLab that coordinates the testing. The old name of this project was GitLab CI Multi Runner but please use "GitLab Runner" (without CI) from now on.
-### gitlab-workhorse
+#### Gitlab Shell
-- Omnibus configuration options
+- [Project page](https://gitlab.com/gitlab-org/gitlab-shell/blob/master/README.md)
+- Configuration: [Omnibus][shell-omnibus], [Charts][shell-charts], [Source][shell-source], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
-[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) is a program designed at GitLab to help alieviate pressure from unicorn. You can read more about the [historical reasons for developing](https://about.gitlab.com/2016/04/12/a-brief-history-of-gitlab-workhorse/). It's designed to act as a smart reverse proxy to help speed up GitLab as a whole.
+[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) is a program designed at GitLab to handle ssh-based `git` sessions, and modifies the list of authorized keys. GitLab Shell is not a Unix shell nor a replacement for Bash or Zsh.
+
+#### Gitlab Workhorse
+
+- [Project page](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/README.md)
+- Configuration: [Omnibus][gitlab-workhorse-omnibus], [Charts][gitlab-workhorse-charts], [Source][workhorse-source]
+- Layer: Core Service (Processor)
+- Process: `gitlab-workhorse`
+
+[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) is a program designed at GitLab to help alleviate pressure from Unicorn. You can read more about the [historical reasons for developing](https://about.gitlab.com/2016/04/12/a-brief-history-of-gitlab-workhorse/). It's designed to act as a smart reverse proxy to help speed up GitLab as a whole.
+
+#### Grafana
+
+- [Project page](https://github.com/grafana/grafana/blob/master/README.md)
+- Configuration: [Omnibus][grafana-omnibus], [Charts][grafana-charts]
+- Layer: Monitoring
+
+Grafana is an open source, feature rich metrics dashboard and graph editor for Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
+
+#### Jaeger
+
+- [Project page](https://github.com/jaegertracing/jaeger/blob/master/README.md)
+- Configuration: [Omnibus][jaeger-omnibus], [Charts][jaeger-charts], [Source][jaeger-source], [GDK][jaeger-gdk]
+- Layer: Monitoring
+
+Jaeger, inspired by Dapper and OpenZipkin, is a distributed tracing system. It can be used for monitoring microservices-based distributed systems.
-### logrotate
+#### Logrotate
-- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate)
+- [Project page](https://github.com/logrotate/logrotate/blob/master/README.md)
+- Configuration: [Omnibus](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate)
- Layer: Core Service
+- Process: `logrotate`
-GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common opensource offering.
+GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common open source offering.
-### nginx
+#### Mattermost
-- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/nginx.html)
+- [Project page](https://github.com/mattermost/mattermost-server/blob/master/README.md)
+- Configuration: [Omnibus][mattermost-omnibus], [Charts][mattermost-charts]
- Layer: Core Service (Processor)
+Mattermost is an open source, private cloud, Slack-alternative from https://mattermost.com.
+
+#### MinIO
+
+- [Project page](https://github.com/minio/minio/blob/master/README.md)
+- Configuration: [Omnibus][minio-omnibus], [Charts][minio-charts], [GDK][minio-gdk]
+- Layer: Core Service (Data)
+
+MinIO is an object storage server released under Apache License v2.0. It is compatible with Amazon S3 cloud storage service. It is best suited for storing unstructured data such as photos, videos, log files, backups and container / VM images. Size of an object can range from a few KBs to a maximum of 5TB.
+
+#### NGINX
+
+- Project page: [Omnibus](https://github.com/nginx/nginx), [Charts](https://github.com/kubernetes/ingress-nginx/blob/master/README.md)
+- Configuration: [Omnibus][nginx-omnibus], [Charts][nginx-charts], [Source][nginx-source]
+- Layer: Core Service (Processor)
+- Process: `nginx`
+
Nginx as an ingress port for all HTTP requests and routes them to the approriate sub-systems within GitLab. We are bundling an unmodified version of the popular open source webserver.
-### node-exporter
+#### Node Exporter
-- [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/node_exporter.html)
+- [Project page](https://github.com/prometheus/node_exporter/blob/master/README.md)
+- Configuration: [Omnibus][node-exporter-omnibus], [Charts][node-exporter-charts]
- Layer: Monitoring
+- Process: `node-exporter`
+
+[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine (think CPU/Disk/Load). It's just a packaged version of the common open source offering from the Prometheus project.
+
+#### PgBouncer
+
+- [Project page](https://github.com/pgbouncer/pgbouncer/blob/master/README.md)
+- Configuration: [Omnibus][pgbouncer-omnibus], [Charts][pgbouncer-charts]
+- Layer: Core Service (Data)
-[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine. (Think CPU/Disk/Load) It's just a packaged version of the common open source offering from the Prometheus project.
+Lightweight connection pooler for PostgreSQL.
-### postgres-exporter
+#### PgBouncer Exporter
-- [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/postgres_exporter.html)
+- [Project page](https://github.com/stanhu/pgbouncer_exporter/blob/master/README.md)
+- Configuration: [Omnibus][pgbouncer-exporter-omnibus], [Charts][pgbouncer-exporter-charts]
- Layer: Monitoring
-[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to prometheus for use in Grafana Dashboards.
+Prometheus exporter for PgBouncer. Exports metrics at 9127/metrics.
-### postgresql
+#### Postgresql
-- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/database.html)
+- [Project page](https://github.com/postgres/postgres/blob/master/README)
+- Configuration: [Omnibus][postgres-omnibus], [Charts][postgres-charts], [Source][postgres-source]
- Layer: Core Service (Data)
+- Process: `postgresql`
GitLab packages the popular Database to provide storage for Application meta data and user information.
-### prometheus
+#### Postgres Exporter
-- [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/)
+- [Project page](https://github.com/wrouesnel/postgres_exporter/blob/master/README.md)
+- Configuration: [Omnibus][postgres-exporter-omnibus], [Charts][postgres-exporter-charts]
- Layer: Monitoring
+- Process: `postgres-exporter`
+
+[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to Prometheus for use in Grafana Dashboards.
+
+#### Prometheus
+
+- [Project page](https://github.com/prometheus/prometheus/blob/master/README.md)
+- Configuration: [Omnibus][prometheus-omnibus], [Charts][prometheus-charts]
+- Layer: Monitoring
+- Process: `prometheus`
Prometheus is a time-series tool that helps GitLab administrators expose metrics about the individual processes used to provide GitLab the service.
-### redis
+#### Redis
-- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/redis.html)
+- [Project page](https://github.com/antirez/redis/blob/unstable/README.md)
+- Configuration: [Omnibus][redis-omnibus], [Charts][redis-charts], [Source][redis-source]
- Layer: Core Service (Data)
+- Process: `redis`
Redis is packaged to provide a place to store:
- session data
- temporary cache information
-- background job queues.
+- background job queues
+
+#### Redis Exporter
+
+- [Project page](https://github.com/oliver006/redis_exporter/blob/master/README.md)
+- Configuration: [Omnibus][redis-exporter-omnibus], [Charts][redis-exporter-charts]
+- Layer: Monitoring
+- Process: `redis-exporter`
+
+[Redis Exporter](https://github.com/oliver006/redis_exporter) is designed to give specific metrics about the Redis process to Prometheus so that we can graph these metrics in Grafana.
+
+#### Registry
+
+- [Project page](https://github.com/docker/distribution/blob/master/README.md)
+- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts], [Source][registry-source], [GDK][registry-gdk]
+- Layer: Core Service (Processor)
+
+The registry is what users use to store their own Docker images. The bundled
+registry uses nginx as a load balancer and GitLab as an authentication manager.
+Whenever a client requests to pull or push an image from the registry, it will
+return a `401` response along with a header detailing where to get an
+authentication token, in this case the GitLab instance. The client will then
+request a pull or push auth token from GitLab and retry the original request
+to the registry. Learn more about [token authentication](https://docs.docker.com/registry/spec/auth/token/).
+
+An external registry can also be configured to use GitLab as an auth endpoint.
-### redis-exporter
+#### Sentry
-- [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/redis_exporter.html)
+- [Project page](https://github.com/getsentry/sentry/blob/master/README.rst)
+- Configuration: [Omnibus][sentry-omnibus], [Charts][sentry-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Monitoring
-[Redis Exporter](https://github.com/oliver006/redis_exporter) is designed to give specific metrics about the Redis process to Prometheus so that we can graph these metrics in Graphana.
+Sentry fundamentally is a service that helps you monitor and fix crashes in realtime. The server is in Python, but it contains a full API for sending events from any language, in any application.
-### sidekiq
+#### Sidekiq
-- Omnibus configuration options
+- [Project page](https://github.com/mperham/sidekiq/blob/master/README.md)
+- Configuration: [Omnibus][sidekiq-omnibus], [Charts][sidekiq-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
+- Process: `sidekiq`
Sidekiq is a Ruby background job processor that pulls jobs from the redis queue and processes them. Background jobs allow GitLab to provide a faster request/response cycle by moving work into the background.
-### unicorn
+#### Unicorn
-- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/unicorn.html)
+- [Project page](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/README.md)
+- Configuration: [Omnibus][unicorn-omnibus], [Charts][unicorn-charts], [Source][unicorn-source], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
+- Process: `unicorn`
[Unicorn](https://bogomips.org/unicorn/) is a Ruby application server that is used to run the core Rails Application that provides the user facing features in GitLab. Often process output you will see this as `bundle` or `config.ru` depending on the GitLab version.
-### Additional Processes
+#### LDAP Authentication
-### GitLab Pages
+- Configuration: [Omnibus][ldap-omnibus], [Charts][ldap-charts], [Source][gitlab-yml], [GDK][ldap-gdk]
+- Layer: Core Service (Processor)
-TODO
+#### Outbound Email
-### Mattermost
+- Configuration: [Omnibus][outbound-email-omnibus], [Charts][outbound-email-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
+- Layer: Core Service (Processor)
-TODO
+#### Inbound Email
+
+- Configuration: [Omnibus][inbound-email-omnibus], [Charts][inbound-email-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
+- Layer: Core Service (Processor)
+
+#### GitLab Managed Apps
+
+- Configuration: [Omnibus][managed-k8s-apps], [Charts][managed-k8s-apps], [Source][managed-k8s-apps], [GDK][managed-k8s-apps]
+- Layer: Core Service (Processor)
+
+GitLab provides [GitLab Managed Apps](../user/project/clusters/index.md#installing-applications), a one-click install for various applications which can be added directly to your configured cluster. These applications are needed for Review Apps and deployments when using Auto DevOps. You can install them after you create a cluster.
## GitLab by Request Type
@@ -169,10 +453,10 @@ It's important to understand the distinction as some processes are used in both
### GitLab Web HTTP Request Cycle
-When making a request to an HTTP Endpoint (Think `/users/sign_in`) the request will take the following path through the GitLab Service:
+When making a request to an HTTP Endpoint (think `/users/sign_in`) the request will take the following path through the GitLab Service:
-- nginx - Acts as our first line reverse proxy
-- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on unicorn.
+- nginx - Acts as our first line reverse proxy.
+- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on Unicorn.
- unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn.
- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data.
@@ -188,7 +472,7 @@ TODO
## System Layout
-When referring to `~git` in the pictures it means the home directory of the git user which is typically /home/git.
+When referring to `~git` in the pictures it means the home directory of the git user which is typically `/home/git`.
GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
@@ -196,24 +480,6 @@ The bare repositories are located in `/home/git/repositories`. GitLab is a ruby
To serve repositories over SSH there's an add-on application called gitlab-shell which is installed in `/home/git/gitlab-shell`.
-### Components
-
-<img src="https://docs.google.com/drawings/d/1fBzAyklyveF-i-2q-OHUIqDkYfjjxC4mq5shwKSZHLs/pub?w=987&amp;h=797">
-
-_[edit diagram (for GitLab team members only)](https://docs.google.com/drawings/d/1fBzAyklyveF-i-2q-OHUIqDkYfjjxC4mq5shwKSZHLs/edit)_
-
-A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
-
-The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
-
-When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
-
-The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories through Gitaly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
-
-Gitaly executes git operations from gitlab-shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
-
-You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/).
-
### Installation Folder Summary
To summarize here's the [directory structure of the `git` user home directory](../install/structure.md).
@@ -340,3 +606,95 @@ Note: It is recommended to log into the `git` user using `sudo -i -u git` or `su
## GitLab.com
We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users.
+
+[alertmanager-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
+[alertmanager-charts]: https://github.com/helm/charts/tree/master/stable/prometheus
+[nginx-omnibus]: https://docs.gitlab.com/omnibus/settings/
+[nginx-charts]: https://docs.gitlab.com/charts/charts/nginx/
+[nginx-source]: ../install/installation.md#9-nginx
+[unicorn-omnibus]: https://docs.gitlab.com/omnibus/settings/unicorn.html
+[unicorn-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/
+[unicorn-source]: ../install/installation.md#configure-it
+[gitlab-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
+[sidekiq-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
+[sidekiq-charts]: https://docs.gitlab.com/charts/charts/gitlab/sidekiq/
+[gitaly-omnibus]: ../administration/gitaly/index.md
+[gitaly-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitaly/
+[gitaly-source]: ../install/installation.md#install-gitaly
+[workhorse-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
+[workhorse-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/
+[workhorse-source]: ../install/installation.md#install-gitlab-workhorse
+[shell-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
+[shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/
+[shell-source]: ../install/installation.md#install-gitlab-shell
+[pages-omnibus]: ../administration/pages/index.md
+[pages-charts]: https://gitlab.com/charts/gitlab/issues/37
+[pages-source]: ../install/installation.md#install-gitlab-pages
+[pages-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/pages.md
+[registry-omnibus]: ../administration/container_registry.md#container-registry-domain-configuration
+[registry-charts]: https://docs.gitlab.com/charts/charts/registry/
+[registry-source]: ../administration/container_registry.md#enable-the-container-registry
+[registry-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/registry.md
+[redis-omnibus]: https://docs.gitlab.com/omnibus/settings/redis.html
+[redis-charts]: https://docs.gitlab.com/charts/charts/redis/
+[redis-source]: ../install/installation.md#7-redis
+[postgres-omnibus]: https://docs.gitlab.com/omnibus/settings/database.html
+[postgres-charts]: https://github.com/helm/charts/tree/master/stable/postgresql
+[postgres-source]: ../install/installation.md#6-database
+[pgbouncer-omnibus]: ../administration/high_availability/pgbouncer.md
+[pgbouncer-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
+[consul-omnibus]: ../administration/high_availability/consul.md
+[consul-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
+[prometheus-omnibus]: ../administration/monitoring/prometheus/index.md
+[prometheus-charts]: https://github.com/helm/charts/tree/master/stable/prometheus
+[grafana-omnibus]: ../administration/monitoring/performance/grafana_configuration.md
+[grafana-charts]: https://github.com/helm/charts/tree/master/stable/grafana
+[sentry-omnibus]: https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry
+[sentry-charts]: https://gitlab.com/charts/gitlab/issues/1319
+[jaeger-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104
+[jaeger-charts]: https://gitlab.com/charts/gitlab/issues/1320
+[jaeger-source]: ../development/distributed_tracing.md#enabling-distributed-tracing
+[jaeger-gdk]: ../development/distributed_tracing.html#using-jaeger-in-the-gitlab-development-kit
+[redis-exporter-omnibus]: ../administration/monitoring/prometheus/redis_exporter.md
+[redis-exporter-charts]: https://docs.gitlab.com/charts/charts/redis/
+[postgres-exporter-omnibus]: ../administration/monitoring/prometheus/postgres_exporter.md
+[postgres-exporter-charts]: https://github.com/helm/charts/tree/master/stable/postgresql
+[pgbouncer-exporter-omnibus]: ../administration/monitoring/prometheus/pgbouncer_exporter.md
+[pgbouncer-exporter-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
+[gitlab-monitor-omnibus]: ../administration/monitoring/prometheus/gitlab_monitor_exporter.md
+[gitab-monitor-charts]: https://gitlab.com/charts/gitlab/issues/319
+[node-exporter-omnibus]: ../administration/monitoring/prometheus/node_exporter.md
+[node-exporter-charts]: https://gitlab.com/charts/gitlab/issues/1332
+[mattermost-omnibus]: https://docs.gitlab.com/omnibus/gitlab-mattermost/
+[mattermost-charts]: https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html
+[minio-omnibus]: https://min.io/download
+[minio-charts]: https://docs.gitlab.com/charts/charts/minio/
+[minio-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/object_storage.md
+[runner-omnibus]: https://docs.gitlab.com/runner/
+[runner-charts]: https://docs.gitlab.com/runner/install/kubernetes.html
+[runner-source]: https://docs.gitlab.com/runner/
+[runner-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/runner.md
+[database-migrations-omnibus]: https://docs.gitlab.com/omnibus/settings/database.html#disabling-automatic-database-migration
+[database-migrations-charts]: https://docs.gitlab.com/charts/charts/gitlab/migrations/
+[database-migrations-source]: ../update/upgrading_from_source.md#14-install-libs-migrations-etc
+[certificate-management-omnibus]: https://docs.gitlab.com/omnibus/settings/ssl.html
+[certificate-management-charts]: https://docs.gitlab.com/charts/installation/tls.html
+[certificate-management-source]: ../install/installation.md#using-https
+[certificate-management-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/https.md
+[geo-omnibus]: ../administration/geo/replication/index.md#setup-instructions
+[geo-charts]: https://gitlab.com/charts/gitlab/issues/8
+[geo-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md
+[ldap-omnibus]: ../administration/auth/ldap.md
+[ldap-charts]: https://docs.gitlab.com/charts/charts/globals.html#ldap
+[ldap-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/ldap.md
+[outbound-email-omnibus]: https://docs.gitlab.com/omnibus/settings/smtp.html
+[outbound-email-charts]: https://docs.gitlab.com/charts/installation/command-line-options.html#outgoing-email-configuration
+[inbound-email-omnibus]: ../administration/incoming_email.md
+[inbound-email-charts]: https://docs.gitlab.com/charts/installation/command-line-options.html#incoming-email-configuration
+[elasticsearch-omnibus]: ../integration/elasticsearch.md
+[elasticsearch-charts]: ../integration/elasticsearch.md
+[elasticsearch-source]: ../integration/elasticsearch.md
+[elasticsearch-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/elasticsearch.md
+[sentry-integration]: ../user/project/operations/error_tracking.md
+[jaeger-integration]: ../user/project/operations/tracing.md
+[managed-k8s-apps]: ../user/project/clusters/index.md#installing-applications
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index cd0a1f46d27..45b3d5a23a1 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -35,6 +35,7 @@ the `author` field. GitLab team members **should not**.
- Any user-facing change **should** have a changelog entry. Example: "GitLab now
uses system fonts for all text."
+- Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](https://docs.gitlab.com/ee/development/feature_flags.html#developing-with-feature-flags).
- A fix for a regression introduced and then fixed in the same release (i.e.,
fixing a bug introduced during a monthly release candidate) **should not**
have a changelog entry.
@@ -43,7 +44,7 @@ the `author` field. GitLab team members **should not**.
database records created during Cycle Analytics model spec."
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
- Example: "Fixed a typo on the search results page. (Jane Smith)"
+ Example: "Fixed a typo on the search results page."
- Performance improvements **should** have a changelog entry.
- Any change that introduces a database migration **must** have a
changelog entry.
@@ -135,21 +136,13 @@ If you're working on the GitLab EE repository, the entry will be added to
| 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`, `performance`, `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`](#--amend) | | Amend the previous commit |
+| [`--force`](#--force-or--f) | `-f` | Overwrite an existing entry |
+| [`--merge-request`](#--merge-request-or--m) | `-m` | Set merge request ID |
+| [`--dry-run`](#--dry-run-or--n) | `-n` | Don't actually write anything, just print |
+| [`--git-username`](#--git-username-or--u) | `-u` | Use Git user.name configuration as the author |
+| [`--type`](#--type-or--t) | `-t` | The category of the change, valid options are: `added`, `fixed`, `changed`, `deprecated`, `removed`, `security`, `performance`, `other` |
+| `--help` | `-h` | Print help message |
##### `--amend`
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
new file mode 100644
index 00000000000..c63ec53414c
--- /dev/null
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -0,0 +1,21 @@
+# Chatops on GitLab.com
+
+Chatops on GitLab.com allows GitLabbers to run various automation tasks on GitLab.com using Slack.
+
+## Requesting access
+
+GitLabbers may need access to Chatops on GitLab.com for administration tasks such as:
+
+- Configuring feature flags on staging.
+- Running `EXPLAIN` queries against the GitLab.com production replica.
+
+To request access to Chatops on GitLab.com:
+
+1. Log into <https://ops.gitlab.net/users/sign_in> using the same username as for GitLab.com.
+1. Ask [anyone in the `chatops` project](https://gitlab.com/gitlab-com/chatops/project_members) to add you by running `/chatops run member add <username> gitlab-com/chatops --ops`.
+
+## See also
+
+ - [Chatops Usage](https://docs.gitlab.com/ee/ci/chatops/README.html)
+ - [Understanding EXPLAIN plans](understanding_explain_plans.md)
+ - [Feature Groups](feature_flags.md#feature-groups)
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index f115045dbb7..29e2aa1a581 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -20,23 +20,38 @@ importance of involving reviewer(s) in the section on the responsibility of the
If you need some guidance (e.g. it's your first merge request), feel free to ask
one of the [Merge request coaches][team].
-If you need assistance with security scans or comments, feel free to include the
+If you need assistance with security scans or comments, feel free to include the
Security Team (`@gitlab-com/gl-security`) in the review.
-The `danger-review` CI job will randomly pick a reviewer and a maintainer for
-each area of the codebase that your merge request seems to touch. It only makes
-recommendations - feel free to override it if you think someone else is a better
-fit!
-
Depending on the areas your merge request touches, it must be **approved** by one
or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer):
For approvals, we use the approval functionality found in the merge request
-widget. Reviewers can add their approval by [approving additionally](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#adding-or-removing-an-approval).
+widget. Reviewers can add their approval by [approving additionally](../user/project/merge_requests/merge_request_approvals.md#adding-or-removing-an-approval).
Getting your merge request **merged** also requires a maintainer. If it requires
more than one approval, the last maintainer to review and approve it will also merge it.
+### Reviewer roulette
+
+The `danger-review` CI job will randomly pick a reviewer and a maintainer for
+each area of the codebase that your merge request seems to touch. It only makes
+recommendations - feel free to override it if you think someone else is a better
+fit!
+
+It picks reviewers and maintainers from the list at the
+[engineering projects](https://about.gitlab.com/handbook/engineering/projects/)
+page, with these behaviours:
+
+1. It will not pick people whose [GitLab status](../user/profile/index.md#current-status)
+ contains the string 'OOO'.
+2. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
+ are three times as likely to be picked as other reviewers.
+3. It always picks the same reviewers and maintainers for the same
+ branch name (unless their OOO status changes, as in point 1). It
+ removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so
+ that it can be stable for backport branches.
+
### Approval guidelines
As described in the section on the responsibility of the maintainer below, you
@@ -58,12 +73,7 @@ from teams other than your own.
#### Security requirements
- 1. If your merge request is processing, storing, or transferring any kind of [RED or ORANGE data](https://docs.google.com/document/d/15eNKGA3zyZazsJMldqTBFbYMnVUSQSpU14lo22JMZQY/edit) (this is a confidential document), it must be
- **approved by a [Security Engineer][team]**.
- 1. If your merge request involves implementing, utilizing, or is otherwise related to any type of authentication, authorization, or session handling mechanism, it must be
- **approved by a [Security Engineer][team]**.
- 1. If your merge request has a goal which requires a cryptographic function such as: confidentiality, integrity, authentication, or non-repudiation, it must be
- **approved by a [Security Engineer][team]**.
+View the updated documentation regarding [internal application security reviews](https://about.gitlab.com/handbook/engineering/security/index.html#internal-application-security-reviews) for **when** and **how** to request a security review.
### The responsibility of the merge request author
@@ -137,21 +147,13 @@ If a developer who happens to also be a maintainer was involved in a merge reque
as a domain expert and/or reviewer, it is recommended that they are not also picked
as the maintainer to ultimately approve and merge it.
-Try to review in a timely manner; doing so allows everyone involved in the merge
-request to iterate faster as the context is fresh in memory. Further, this
-improves contributors' experiences significantly. Reviewers should aim to review
-within two working days from the date they were assigned the merge request. If
-you don't think you'll be able to review a merge request within that time, let
-the author know as soon as possible. When the author of the merge request has not
-heard anything after two days, a new reviewer should be assigned.
-
Maintainers should check before merging if the merge request is approved by the
required approvers.
Maintainers must check before merging if the merge request is introducing new
vulnerabilities, by inspecting the list in the Merge Request [Security
-Widget](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate).
-When in doubt, a [Security Engineer][team] can be involved. The list of detected
+Widget](../user/project/merge_requests/index.md#security-reports-ultimate).
+When in doubt, a [Security Engineer][team] can be involved. The list of detected
vulnerabilities must be either empty or containing:
- dismissed vulnerabilities in case of false positives
@@ -225,6 +227,37 @@ It is responsibility of the author of a merge request that the merge request is
Developers who have capacity can regularly check the list of [merge requests to review](https://gitlab.com/groups/gitlab-org/-/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&label_name%5B%5D=ready%20for%20review) and assign any merge request they want to review.
+### Review turnaround time
+
+Since [unblocking others is always a top priority](https://about.gitlab.com/handbook/values/#global-optimization),
+reviewers are expected to review assigned merge requests in a timely manner,
+even when this may negatively impact their other tasks and priorities.
+Doing so allows everyone involved in the merge request to iterate faster as the
+context is fresh in memory, improves contributors' experiences significantly,
+and gives authors more time to address feedback and iterate on their work before
+the [feature freeze](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md#feature-freeze-on-the-7th-for-the-release-on-the-22nd).
+
+A turnaround time of two working days is usually acceptable, since engineers
+will typically have other things to work on while they're waiting for review,
+but don't hesitate to ask the author if it's unclear what time frame would be
+acceptable, how urgent the review is, or how significant the blockage. Authors
+are also encouraged to provide this information up-front to reviewers, but are
+expected to be mindful of the [guidelines on when to ask for review on MRs that
+are intended to go in before the feature freeze](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md#between-the-1st-and-the-7th),
+and realistic in their expectations if these were not followed.
+
+If you don't think you'll be able to review a merge request within a reasonable
+time frame, let the author know as soon as possible and try to help them find
+another reviewer or maintainer who will be able to, so that they can be unblocked
+and get on with their work quickly. Of course, if you are out of office and have
+[communicated](https://about.gitlab.com/handbook/paid-time-off/#communicating-your-time-off)
+this through your GitLab.com Status, authors are expected to realize this and
+find a different reviewer themselves.
+
+When a merge request author feels like they have been blocked for longer than
+is reasonable, they are free to remind the reviewer through Slack or assign
+another reviewer.
+
### Reviewing code
Understand why the change is necessary (fixes a bug, improves the user
@@ -253,7 +286,7 @@ experience, refactors the existing code). Then:
author has already set this option or if the merge request clearly contains a
messy commit history that is intended to be squashed.
-[squash-and-merge]: https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html#squash-and-merge
+[squash-and-merge]: ../user/project/merge_requests/squash_and_merge.md#squash-and-merge
### The right balance
@@ -286,7 +319,7 @@ reviewee.
GitLab is used in a lot of places. Many users use
our [Omnibus packages](https://about.gitlab.com/installation/), but some use
the [Docker images](https://docs.gitlab.com/omnibus/docker/), some are
-[installed from source](https://docs.gitlab.com/ce/install/installation.html),
+[installed from source](../install/installation.md),
and there are other installation methods available. GitLab.com itself is a large
Enterprise Edition instance. This has some implications:
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index f7a0fdbeb40..59cf5014da4 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -3,9 +3,6 @@
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is easy for everyone.
-We want to create a welcoming environment for everyone who is interested in contributing.
-Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our commitment to an open and welcoming environment.
-
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
@@ -33,8 +30,8 @@ vulnerabilities.
## Code of conduct
-Our code of conduct can be found on the
-["Contributing to GitLab"](https://about.gitlab.com/contributing/) page.
+We want to create a welcoming environment for everyone who is interested in contributing.
+Please visit our [Code of Conduct page](https://about.gitlab.com/community/contribute/code-of-conduct/) to learn more about our commitment to an open and welcoming environment.
## Closing policy for issues and merge requests
@@ -52,9 +49,12 @@ for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches][team] or close the merge request. We make this decision
-based on how important the change is for our product vision. If a Merge request
+based on how important the change is for our product vision. If a merge request
coach is going to finish the merge request we assign the
-~"coach will finish" label.
+~"coach will finish" label. When a team member picks up a community contribution,
+we credit the original author by adding a changelog entry crediting the author
+and optionally include the original author on at least one of the commits
+within the MR.
## Helping others
@@ -87,7 +87,7 @@ Sometimes style guides will be followed but the code will lack structural integr
GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
-GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
+GitLab receives a lot of community contributions, so if your code has not been reviewed within two days (excluding weekend and public holidays) of its initial submission feel free to re-mention the appropriate merge request coach.
When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
@@ -119,6 +119,7 @@ This [documentation](merge_request_workflow.md) outlines the current merge reque
- [Merge request guidelines](merge_request_workflow.md#merge-request-guidelines)
- [Contribution acceptance criteria](merge_request_workflow.md#contribution-acceptance-criteria)
- [Definition of done](merge_request_workflow.md#definition-of-done)
+- [Dependencies](merge_request_workflow.md#dependencies)
## Style guides
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 4c53643ed9c..e3a1dc711fd 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -7,7 +7,7 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~feature, ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Subject: ~wiki, ~"Container Registry", ~ldap, ~api, ~frontend, etc.
- Team: ~Plan, ~Manage, ~Quality, etc.
- Stage: ~"devops:plan", ~"devops:create", etc.
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
@@ -44,7 +44,7 @@ Subject labels are labels that define what area or feature of GitLab this issue
hits. They are not always necessary, but very convenient.
Examples of subject labels are ~wiki, ~ldap, ~api,
-~issues, ~"merge requests", ~labels, and ~"container registry".
+~issues, ~"merge requests", ~labels, and ~"Container Registry".
If you are an expert in a particular area, it makes it easier to find issues to
work on. You can also subscribe to those labels to receive an email each time an
@@ -62,10 +62,12 @@ The current team labels are:
- ~Configure
- ~Create
+- ~Defend
- ~Distribution
- ~Documentation
- ~Geo
- ~Gitaly
+- ~Growth
- ~Manage
- ~Monitor
- ~Plan
@@ -90,18 +92,21 @@ Stage labels specify which [DevOps stage][devops-stages] the issue belongs to.
The current stage labels are:
-- ~"devops:manage"
-- ~"devops:plan"
-- ~"devops:create"
-- ~"devops:verify"
-- ~"devops:package"
-- ~"devops:release"
-- ~"devops:configure"
-- ~"devops:monitor"
-- ~"devops:secure"
-
-These labels should be mutually exclusive. If an issue belongs to multiple
-stages, the most relevant should be used.
+- ~"devops::manage"
+- ~"devops::plan"
+- ~"devops::create"
+- ~"devops::verify"
+- ~"devops::package"
+- ~"devops::release"
+- ~"devops::configure"
+- ~"devops::monitor"
+- ~"devops::secure"
+- ~"devops::defend"
+- ~"devops::growth"
+- ~"devops::enablement"
+
+These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
+and thus are mutually exclusive.
They differ from the [Team labels](#team-labels) because teams may work on
issues outside their stage.
@@ -117,6 +122,25 @@ The Stage labels are used to generate the [direction pages][direction-pages] aut
[devops-stages]: https://about.gitlab.com/direction/#devops-stages
[direction-pages]: https://about.gitlab.com/direction/
+## Group labels
+
+Group labels specify which [groups][structure-groups] the issue belongs to.
+
+Examples include:
+
+- ~"group::control"
+- ~"group::editor"
+
+These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
+and thus are mutually exclusive.
+
+Groups are nested beneath a particular stage, so only one stage label and one group label
+can be applied to a single issue. You can find the groups listed in the
+[Product Categories pages][product-categories].
+
+[structure-groups]: https://about.gitlab.com/company/team/structure/#groups
+[product-categories]: https://about.gitlab.com/handbook/product/categories/
+
## Release Scoping labels
Release Scoping labels help us clearly communicate expectations of the work for the
@@ -336,13 +360,14 @@ addressed.
In order to track things that can be improved in GitLab's codebase,
we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
-For user experience improvements, we use the ~"UX debt" label.
+For missed user experience requirements, we use the ~"UX debt" label.
These labels should be added to issues that describe things that can be improved,
shortcuts that have been taken, features that need additional attention, and all
other things that have been left behind due to high velocity of development.
For example, code that needs refactoring should use the ~"technical debt" label,
-user experience refinements should use the ~"UX debt" label.
+something that didn't ship according to our Design System guidelines should
+use the ~"UX debt" label.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 4e766f37871..8a4aa5dfa7f 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -1,91 +1,95 @@
# Merge requests
-We welcome merge requests with fixes and improvements to GitLab code, tests,
-and/or documentation. The issues that are specifically suitable for
-community contributions are listed with
-[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors),
-but you are free to contribute to any other issue you want.
-
-Please note that if an issue is marked for the current milestone either before
-or while you are working on it, a team member may take over the merge request
+We welcome merge requests from everyone, with fixes and improvements
+to GitLab code, tests, and documentation. The issues that are specifically suitable
+for community contributions are listed with the [`Accepting merge requests`](issue_workflow.md#label-for-community-contributors)
+label, but you are free to contribute to any issue you want.
+
+Please note that if an issue is marked for the current milestone at any time, even
+when you are working on it, a GitLab Inc. team member may take over the merge request
in order to ensure the work is finished before the release date.
-If you want to add a new feature that is not labeled it is best to first create
-a feedback issue (if there isn't one already) and leave a comment asking for it
+If you want to add a new feature that is not labeled, it is best to first create
+an issue (if there isn't one already) and leave a comment asking for it
to be marked as `Accepting Merge Requests`. Please include screenshots or
-wireframes if the feature will also change the UI.
+wireframes of the proposed feature if it will also change the UI.
-Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
+Merge requests should be submitted to the appropriate project at GitLab.com, for example
+[GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests),
+[GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests),
+[GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests),
+[GitLab Omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests), etc.
If you are new to GitLab development (or web development in general), see the
-[I want to contribute!](index.md#i-want-to-contribute) section to get you started with
+[I want to contribute!](index.md#i-want-to-contribute) section to get started with
some potentially easy issues.
-To start with GitLab development download the [GitLab Development Kit][gdk] and
-see the [Development section](../../README.md) for some guidelines.
-
-[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
-[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
+To start developing GitLab, download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit)
+and see the [Development section](../../README.md) for the required guidelines.
## Merge request guidelines
-If you can, please submit a merge request with the fix or improvements
-including tests. If you don't know how to fix the issue but can write a test
-that exposes the issue we will accept that as well. In general bug fixes that
-include a regression test are merged quickly while new features without proper
-tests are least likely to receive timely feedback. The workflow to make a merge
+If you find an issue, please submit a merge request with a fix or improvement, if
+you can, and include tests. If you don't know how to fix the issue but can write a test
+that exposes the issue, we will accept that as well. In general, bug fixes that
+include a regression test are merged quickly, while new features without proper
+tests might be slower to receive feedback. The workflow to make a merge
request is as follows:
-1. Fork the project into your personal space on GitLab.com
-1. Create a feature branch, branch away from `master`
-1. Write [tests](https://docs.gitlab.com/ee/development/rake_tasks.html#run-tests) and code
-1. [Generate a changelog entry with `bin/changelog`][changelog]
+1. [Fork](../../workflow/forking_workflow.md#creating-a-fork) the project into
+ your personal namespace (or group) on GitLab.com.
+1. Create a feature branch in your fork (don't work off `master`).
+1. Write [tests](../rake_tasks.md#run-tests) and code.
+1. [Generate a changelog entry with `bin/changelog`](../changelog.md)
1. If you are writing documentation, make sure to follow the
- [documentation guidelines][doc-guidelines]
-1. If you have multiple commits please combine them into a few logically
- organized commits by [squashing them][git-squash]
-1. Push the commit(s) to your fork
-1. Submit a merge request (MR) to the `master` branch
- 1. Your merge request needs at least 1 approval but feel free to require more.
- For instance if you're touching backend and frontend code, it's a good idea
+ [documentation guidelines](../documentation/index.md).
+1. Follow the [commit messages guidelines](#commit-messages-guidelines).
+1. If you have multiple commits, combine them into a few logically organized
+ commits by [squashing them](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#_squashing),
+ but do not change the commit history if you're working on shared branches though.
+1. Push the commit(s) to your working branch in your fork.
+1. Submit a merge request (MR) to the `master` branch in the main GitLab project.
+ 1. Your merge request needs at least 1 approval, but feel free to require more.
+ For instance if you're touching both backend and frontend code, it's a good idea
to require 2 approvals: 1 from a backend maintainer and 1 from a frontend
- maintainer
- 1. You don't have to select any approvers, but you can if you really want
- specific people to approve your merge request
-1. The MR title should describe the change you want to make
-1. The MR description should give a motive for your change and the method you
- used to achieve it.
- 1. If you are contributing code, fill in the template already provided in the
- "Description" field.
+ maintainer.
+ 1. If you're submitting changes to documentation, you'll need approval from a technical
+ writer, based on the appropriate [product category](https://about.gitlab.com/handbook/product/categories/).
+ Only assign the MR to them when it's ready for docs review.
+ 1. You don't have to select any specific approvers, but you can if you really want
+ specific people to approve your merge request.
+1. The MR title should describe the change you want to make.
+1. The MR description should give a reason for your change.
+ 1. If you are contributing code, fill in the description according to the default
+ template already provided in the "Description" field.
1. If you are contributing documentation, choose `Documentation` from the
- "Choose a template" menu and fill in the template.
+ "Choose a template" menu and fill in the description according to the template.
1. Mention the issue(s) your merge request solves, using the `Solves #XXX` or
- `Closes #XXX` syntax to auto-close the issue(s) once the merge request will
- be merged.
-1. If you're allowed to, set a relevant milestone and labels
-1. If the MR changes the UI it should include *Before* and *After* screenshots
-1. If the MR changes CSS classes please include the list of affected pages,
- `grep css-class ./app -R`
-1. Be prepared to answer questions and incorporate feedback even if requests
- for this arrive weeks or months after your MR submission
- 1. If a discussion has been addressed, select the "Resolve discussion" button
- beneath it to mark it resolved.
-1. If your MR touches code that executes shell commands, reads or opens files or
+ `Closes #XXX` syntax to [auto-close](../../user/project/issues/automatic_issue_closing.md)
+ the issue(s) once the merge request is merged.
+1. If you're allowed to (Core team members, for example), set a relevant milestone
+ and [labels](issue_workflow.md).
+1. If the MR changes the UI, it should include *Before* and *After* screenshots.
+1. If the MR changes CSS classes, please include the list of affected pages, which
+ can be found by running `grep css-class ./app -R`.
+1. Be prepared to answer questions and incorporate feedback into your MR with new
+ commits. Once you have fully addressed a suggestion from a reviewer, click the
+ "Resolve discussion" button beneath it to mark it resolved.
+ 1. The merge request author resolves only the discussions they have fully addressed.
+ If there's an open reply or discussion, a suggestion, a question, or anything else,
+ the discussion should be left to be resolved by the reviewer.
+1. If your MR touches code that executes shell commands, reads or opens files, or
handles paths to files on disk, make sure it adheres to the
[shell command guidelines](../shell_commands.md)
1. If your code creates new files on disk please read the
[shared files guidelines](../shared_files.md).
-1. When writing commit messages please follow
- [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- [guidelines](http://chris.beams.io/posts/git-commit/).
1. If your merge request adds one or more migrations, make sure to execute all
migrations on a fresh database before the MR is reviewed. If the review leads
- to large changes in the MR, do this again once the review is complete.
-1. For more complex migrations, write tests.
-1. Merge requests **must** adhere to the [merge request performance
- guidelines](../merge_request_performance_guidelines.md).
-1. For tests that use Capybara or PhantomJS, see this [article on how
- to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
+ to large changes in the MR, execute the migrations again once the review is complete.
+1. Write tests for more complex migrations.
+1. Merge requests **must** adhere to the [merge request performance guidelines](../merge_request_performance_guidelines.md).
+1. For tests that use Capybara, read
+ [how to write reliable, asynchronous integration tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
1. If your merge request introduces changes that require additional steps when
installing GitLab from source, add them to `doc/install/installation.md` in
the same merge request.
@@ -95,109 +99,117 @@ request is as follows:
instructions are specific to a version, add them to the "Version specific
upgrading instructions" section.
-Please keep the change in a single MR **as small as possible**. If you want to
-contribute a large feature think very hard what the minimum viable change is.
-Can you split the functionality? Can you only submit the backend/API code? Can
-you start with a very simple UI? Can you do part of the refactor? The increased
-reviewability of small MRs that leads to higher code quality is more important
-to us than having a minimal commit log. The smaller an MR is the more likely it
-is it will be merged (quickly). After that you can send more MRs to enhance it.
-The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
-
-For examples of feedback on merge requests please look at already
-[closed merge requests][closed-merge-requests]. If you would like quick feedback
-on your merge request feel free to mention someone from the [core team] or one
-of the [Merge request coaches][team].
-Please ensure that your merge request meets the contribution acceptance criteria.
-
-When having your code reviewed and when reviewing merge requests please take the
-[code review guidelines](../code_review.md) into account.
-
-[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
-[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
-[team]: https://about.gitlab.com/team/
+If you would like quick feedback on your merge request feel free to mention someone
+from the [core team](https://about.gitlab.com/community/core-team/) or one of the
+[merge request coaches](https://about.gitlab.com/team/). When having your code reviewed
+and when reviewing merge requests, please keep the [code review guidelines](../code_review.md)
+in mind.
+
+### Keep it simple
+
+*Live by smaller iterations.* Please keep the amount of changes in a single MR **as small as possible**.
+If you want to contribute a large feature, think very carefully about what the
+[minimum viable change](https://about.gitlab.com/handbook/product/#the-minimally-viable-change)
+is. Can you split the functionality into two smaller MRs? Can you submit only the
+backend/API code? Can you start with a very simple UI? Can you do just a part of the
+refactor?
+
+Small MRs which are more easily reviewed, lead to higher code quality which is
+more important to GitLab than having a minimal commit log. The smaller an MR is,
+the more likely it will be merged quickly. After that you can send more MRs to
+enhance and expand the feature. The [How to get faster PR reviews](https://github.com/kubernetes/kubernetes/blob/release-1.5/docs/devel/faster_reviews.md)
+document from the Kubernetes team also has some great points regarding this.
+
+### Commit messages guidelines
+
+When writing commit messages, please follow the guidelines below:
+
+- The commit subject must contain at least 3 words.
+- The commit subject should ideally contain up to 50 characters,
+and must not be longer than 72 characters.
+- The commit subject must start with a capital letter.
+- The commit subject must not end with a period.
+- The commit subject and body must be separated by a blank line.
+- The commit body must not contain more than 72 characters per line.
+- Commits that change 30 or more lines across at least 3 files must
+describe these changes in the commit body.
+- The commit subject or body must not contain Emojis.
+- Use issues and merge requests' full URLs instead of short references,
+as they are displayed as plain text outside of GitLab.
+- The merge request must not contain more than 10 commit messages.
+
+If the guidelines are not met, the MR will not pass the
+[Danger checks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/danger/commit_messages/Dangerfile).
+For more information see [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
## Contribution acceptance criteria
-1. The change is as small as possible
+To make sure that your merge request can be approved, please ensure that it meets
+the contribution acceptance criteria below:
+
+1. The change is as small as possible.
1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code). Every new class should have corresponding
unit tests, even if the class is exercised at a higher level, such as a feature test.
-1. If you suspect a failing CI build is unrelated to your contribution, you may
- try and restart the failing CI job or ask a developer to fix the
- aforementioned failing test
-1. Your MR initially contains a single commit (please use `git rebase -i` to
- squash commits)
-1. Your changes can merge without problems (if not please rebase if you're the
- only one working on your feature branch, otherwise, merge `master`)
-1. Does not break any existing functionality
-1. Fixes one specific issue or implements one specific feature (do not combine
- things, send separate merge requests if needed)
-1. Migrations should do only one thing (e.g., either create a table, move data
- to a new table or remove an old table) to aid retrying on failure
-1. Keeps the GitLab code base clean and well structured
-1. Contains functionality we think other users will benefit from too
-1. Doesn't add configuration options or settings options since they complicate
- making and testing future changes
-1. Changes do not adversely degrade performance.
- - Avoid repeated polling of endpoints that require a significant amount of overhead
- - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- - Avoid repeated access of filesystem
-1. If you need polling to support real-time features, please use
- [polling with ETag caching][polling-etag].
-1. Changes after submitting the merge request should be in separate commits
- (no squashing).
-1. It conforms to the [style guides](style_guides.md) and the following:
- - If your change touches a line that does not follow the style, modify the
- entire line to follow it. This prevents linting tools from generating warnings.
- - Don't touch neighbouring lines. As an exception, automatic mass
- refactoring modifications may leave style non-compliant.
-1. If the merge request adds any new libraries (gems, JavaScript libraries,
- etc.), they should conform to our [Licensing guidelines][license-finder-doc].
- See the instructions in that document for help if your MR fails the
- "license-finder" test with a "Dependencies that need approval" error.
-1. The merge request meets the [definition of done](#definition-of-done).
-
-[license-finder-doc]: ../licensing.md
-[polling-etag]: ../polling.md
+ - If a failing CI build seems to be unrelated to your contribution, you can try
+ restarting the failing CI job, rebasing from master to bring in updates that
+ may resolve the failure, or if it has not been fixed yet, ask a developer to
+ help you fix the test.
+1. The MR initially contains a few logically organized commits.
+1. The changes can merge without problems. If not, you should rebase if you're the
+ only one working on your feature branch, otherwise merge `master`.
+1. Only one specific issue is fixed or one specific feature is implemented. Do not
+ combine things; send separate merge requests for each issue or feature.
+1. Migrations should do only one thing (e.g., create a table, move data to a new
+ table, or remove an old table) to aid retrying on failure.
+1. Contains functionality that other users will benefit from.
+1. Doesn't add configuration options or settings options since they complicate making
+ and testing future changes.
+1. Changes do not degrade performance:
+ - Avoid repeated polling of endpoints that require a significant amount of overhead.
+ - Check for N+1 queries via the SQL log or [`QueryRecorder`](../merge_request_performance_guidelines.md).
+ - Avoid repeated access of the filesystem.
+ - Use [polling with ETag caching](../polling.md) if needed to support real-time features.
+1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.),
+ they should conform to our [Licensing guidelines](../licensing.md). See those
+ instructions for help if the "license-finder" test fails with a
+ `Dependencies that need approval` error. Also, make the reviewer aware of the new
+ library and explain why you need it.
+1. The merge request meets GitLab's [definition of done](#definition-of-done), below.
## Definition of done
If you contribute to GitLab please know that changes involve more than just
-code. We have the following [definition of done][definition-of-done]. Please ensure you support
-the feature you contribute through all of these steps.
-
-1. Description explaining the relevancy (see following item)
-1. Working and clean code that is commented where needed
-1. [Unit, integration, and system tests][testing] that pass on the CI server
-1. Performance/scalability implications have been considered, addressed, and tested
-1. [Documented][doc-guidelines] in the `/doc` directory
-1. [Changelog entry added][changelog], if necessary
-1. Reviewed by UX/FE/BE and any concerns are addressed
-1. Merged by a project maintainer
-1. Added to the release blog article, if relevant
-1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant
-1. Community questions answered
-1. Answers to questions radiated (in docs/wiki/support etc.)
-1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-or-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions
+code. We use the following [definition of done](https://www.agilealliance.org/glossary/definition-of-done).
+Your contribution is not *done* until you have made sure it meets all of these
+requirements.
+
+1. Clear description explaining the relevancy of the contribution.
+1. Working and clean code that is commented where needed.
+1. [Unit, integration, and system tests](../testing_guide/index.md) that all pass
+ on the CI server.
+1. Performance/scalability implications have been considered, addressed, and tested.
+1. [Documented](../documentation/index.md) in the `/doc` directory.
+1. [Changelog entry added](../changelog.md), if necessary.
+1. Reviewed by relevant (UX/FE/BE/tech writing) reviewers and all concerns are addressed.
+1. Merged by a project maintainer.
+1. Added to the [release post](https://about.gitlab.com/handbook/marketing/blog/release-posts/),
+ if relevant.
+1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml), if relevant.
+1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-at-the-system-level-aka-end-to-end-tests)
+ added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams)
+ with any questions.
+
+## Dependencies
If you add a dependency in GitLab (such as an operating system package) please
-consider updating the following and note the applicability of each in your
-merge request:
-
-1. Note the addition in the release blog post (create one if it doesn't exist yet) <https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/>
-1. Upgrade guide, for example <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md>
-1. Installation guide <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies>
-1. GitLab Development Kit <https://gitlab.com/gitlab-org/gitlab-development-kit>
-1. Test suite <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh>
-1. Omnibus package creator <https://gitlab.com/gitlab-org/omnibus-gitlab>
-
-[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[testing]: ../testing_guide/index.md
-
----
-
-[Return to Contributing documentation](index.md)
-
-[changelog]: ../changelog.md "Generate a changelog entry"
-[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
+consider updating the following, and note the applicability of each in your merge
+request:
+
+1. Note the addition in the [release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/)
+ (create one if it doesn't exist yet).
+1. [The upgrade guide](../../update/upgrading_from_source.md).
+1. The [GitLab Installation Guide](../../install/installation.md#1-packages-and-dependencies).
+1. The [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
+1. The [CI environment preparation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh).
+1. The [Omnibus package creator](https://gitlab.com/gitlab-org/omnibus-gitlab).
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 0eedef5e14f..f319d00d7fe 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -10,7 +10,7 @@
1. [Testing][testing]
1. [JavaScript styleguide][js-styleguide]
1. [SCSS styleguide][scss-styleguide]
-1. [Shell commands](../shell_commands.md) created by GitLab
+1. [Shell commands (Ruby)](../shell_commands.md) created by GitLab
contributors to enhance security
1. [Database Migrations](../migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
@@ -22,6 +22,7 @@
text should be _sorry, we could not create your account because:_
1. Code should be written in [US English][us-english]
1. [Go](../go_guide/index.md)
+1. [Python](../python_guide/index.md)
This is also the style used by linting tools such as
[RuboCop](https://github.com/bbatsov/rubocop) and [Hound CI](https://houndci.com).
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index f00c5ccb9e9..68d33a9d8e0 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -56,7 +56,7 @@ bundle exec rails db RAILS_ENV=development
### `ActiveRecord::PendingMigrationError` with Spring
-When running specs with the [Spring preloader](./rake_tasks.md#speed-up-tests-rake-tasks-and-migrations),
+When running specs with the [Spring preloader](rake_tasks.md#speed-up-tests-rake-tasks-and-migrations),
the test database can get into a corrupted state. Trying to run the migration or
dropping/resetting the test database has no effect.
diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md
new file mode 100644
index 00000000000..bfce7488a8d
--- /dev/null
+++ b/doc/development/distributed_tracing.md
@@ -0,0 +1,182 @@
+# Distributed Tracing - development guidelines
+
+GitLab is instrumented for distributed tracing.
+
+According to [Open Tracing](https://opentracing.io/docs/overview/what-is-tracing/):
+
+> Distributed tracing, also called distributed request tracing, is a method used to profile and
+> monitor applications, especially those built using a microservices architecture. Distributed
+> tracing helps to pinpoint where failures occur and what causes poor performance.
+
+Distributed tracing is especially helpful in understanding the lifecycle of a request as it passes
+through the different components of the GitLab application. At present, Workhorse, Rails, Sidekiq,
+and Gitaly support tracing instrumentation.
+
+Distributed tracing adds minimal overhead when disabled, but imposes only small overhead when
+enabled and is therefore capable in any environment, including production. For this reason, it can
+be useful in diagnosing production issues, particularly performance problems.
+
+## Enabling distributed tracing
+
+GitLab uses the `GITLAB_TRACING` environment variable to configure distributed tracing. The same
+configuration is used for all components (e.g., Workhorse, Rails, etc).
+
+When `GITLAB_TRACING` is not set, the application will not be instrumented, meaning that there is
+no overhead at all.
+
+To enable `GITLAB_TRACING`, a valid _"configuration-string"_ value should be set, with a URL-like
+form:
+
+```console
+GITLAB_TRACING=opentracing://<driver>?<param_name>=<param_value>&<param_name_2>=<param_value_2>
+```
+
+In this example, we have the following hypothetical values:
+
+- `driver`: the driver. [GitLab supports
+ `jaeger`](../user/project/operations/tracing.md). In future, other
+ tracing implementations may also be supported.
+- `param_name`, `param_value`: these are driver specific configuration values. Configuration
+ parameters for Jaeger are documented [further on in this
+ document](#2-configure-the-gitlab_tracing-environment-variable) they should be URL encoded.
+ Multiple values should be separated by `&` characters like a URL.
+
+## Using Jaeger in the GitLab Development Kit
+
+The first tracing implementation that GitLab supports is Jaeger, and the [GitLab Development
+Kit](https://gitlab.com/gitlab-org/gitlab-development-kit/) supports distributed tracing with
+Jaeger out-of-the-box.
+
+The easiest way to access tracing from a GDK environment is through the
+[performance-bar](../administration/monitoring/performance/performance_bar.md). This can be shown
+by typing `p` `b` in the browser window.
+
+![Jaeger Search UI](img/distributed_tracing_performance_bar.png)
+
+Once the performance bar is enabled, click on the **Trace** link in the performance bar to go to
+the Jaeger UI.
+
+The Jaeger search UI will return a query for the `Correlation-ID` of the current request. Normally,
+this search should return a single trace result. Clicking this result will show the detail of the
+trace in a hierarchical time-line.
+
+![Jaeger Search UI](img/distributed_tracing_jaeger_ui.png)
+
+## Using Jaeger without the GitLab Developer Kit
+
+Distributed Tracing can be enabled in non-GDK development environments as well as production or
+staging environments, for troubleshooting. Please note that at this time, this functionality is
+experimental, and not supported in production environments at present. In this first release, it is intended to be
+used for debugging in development environments only.
+
+Jaeger tracing can be enabled through a three-step process:
+
+1. [Start Jaeger](#1-start-jaeger).
+1. [Configure the `GITLAB_TRACING` environment variable](#2-configure-the-gitlab_tracing-environment-variable).
+1. [Start the GitLab application](#3-start-the-gitlab-application).
+1. [Go to the Jaeger Search UI in your browser](#4-open-the-jaeger-search-ui).
+
+### 1. Start Jaeger
+
+Jaeger has many configuration options, but is very easy to start in an "all-in-one" mode which uses
+memory for trace storage (and is therefore non-persistent). The main advantage of "all-in-one" mode
+being ease of use.
+
+For more detailed configuration options, refer to the [Jaeger
+documentation](https://www.jaegertracing.io/docs/1.9/getting-started/).
+
+#### Using Docker
+
+If you have Docker available, the easier approach to running the Jaeger all-in-one is through
+Docker, using the following command:
+
+```console
+$ docker run \
+ --rm \
+ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
+ -p 5775:5775/udp \
+ -p 6831:6831/udp \
+ -p 6832:6832/udp \
+ -p 5778:5778 \
+ -p 16686:16686 \
+ -p 14268:14268 \
+ -p 9411:9411 \
+ jaegertracing/all-in-one:latest
+```
+
+#### Using the Jaeger process
+
+Without Docker, the all-in-one process is still easy to setup.
+
+1. Download the [latest Jaeger release](https://github.com/jaegertracing/jaeger/releases) for your
+ platform.
+1. Extract the archive and run the `bin/all-in-one` process.
+
+This should start the process with the default listening ports.
+
+### 2. Configure the `GITLAB_TRACING` environment variable
+
+Once you have Jaeger running, you'll need to configure the `GITLAB_TRACING` variable with the
+appropriate configuration string.
+
+**TL;DR:** If you are running everything on the same host, use the following value:
+
+```console
+$ export GITLAB_TRACING="opentracing://jaeger?http_endpoint=http%3A%2F%2Flocalhost%3A14268%2Fapi%2Ftraces&sampler=const&sampler_param=1"
+```
+
+This configuration string uses the Jaeger driver `opentracing://jaeger` with the following options:
+
+| Name | Value | Description |
+|------|-------|-------------|
+| `http_endpoint` | `http://localhost:14268/api/traces` | Configures Jaeger to send trace information to the HTTP endpoint running on `http://localhost:14268/`. Alternatively, the `upd_endpoint` can be used. |
+| `sampler` | `const` | Configures Jaeger to use the constant sampler (either on or off). |
+| `sampler_param` | `1` | Configures the `const` sampler to sample _all_ traces. Using `0` would sample _no_ traces. |
+
+**Other parameter values are also possible:**
+
+| Name | Example | Description |
+|------|-------|-------------|
+| `udp_endpoint` | `localhost:6831` | This is the default. Configures Jaeger to send trace information to the UDP listener on port `6831` using compact thrift protocol. Note that we've experienced some issues with the [Jaeger Client for Ruby](https://github.com/salemove/jaeger-client-ruby) when using this protocol. |
+| `sampler` | `probabalistic` | Configures Jaeger to use a probabilistic random sampler. The rate of samples is configured by the `sampler_param` value. |
+| `sampler_param` | `0.01` | Use a ratio of `0.01` to configure the `probabalistic` sampler to randomly sample _1%_ of traces. |
+
+NOTE: **Note:**
+The same `GITLAB_TRACING` value should to be configured in the environment
+variables for all GitLab processes, including Workhorse, Gitaly, Rails, and Sidekiq.
+
+### 3. Start the GitLab application
+
+Once the `GITLAB_TRACING` environment variable is exported to all GitLab services, start the
+application.
+
+When `GITLAB_TRACING` is configured properly, the application will log this on startup:
+
+```console
+13:41:53 gitlab-workhorse.1 | 2019/02/12 13:41:53 Tracing enabled
+...
+13:41:54 gitaly.1 | 2019/02/12 13:41:54 Tracing enabled
+...
+```
+
+If `GITLAB_TRACING` is not configured correctly, this will also be logged:
+
+```console
+13:43:45 gitaly.1 | 2019/02/12 13:43:45 skipping tracing configuration step: tracer: unable to load driver mytracer
+```
+
+By default, GitLab ships with the Jaeger tracer, but other tracers can be included at compile time.
+Details of how this can be done are included in the [LabKit tracing
+documentation](https://godoc.org/gitlab.com/gitlab-org/labkit/tracing).
+
+If no log messages about tracing are emitted, the `GITLAB_TRACING` environment variable is likely
+not set.
+
+### 4. Open the Jaeger Search UI
+
+By default, the Jaeger search UI is available at <http://localhost:16686/search>.
+
+TIP: **Tip:**
+Don't forget that you will need to generate traces by using the application before
+they appear in the Jaeger UI.
+
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index 3f31fe5ca19..ca29353ecbe 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -78,7 +78,7 @@ For issues requiring any new or updated documentation, the Product Manager (PM)
must:
- Add the `Documentation` label.
-- Confirm or add the [documentation requirements](#documentation-requirements).
+- Confirm or add the [documentation requirements](#documentation-requirements-in-feature-issues).
- Ensure the issue contains any new or updated feature name, overview/description,
and use cases, as required per the [documentation structure and template](structure.md), when applicable.
@@ -86,7 +86,7 @@ Everyone is encouraged to draft the requirements in the issue, but a product man
do the following:
- When the issue is assigned a release milestone, review and update the Documentation details.
-- By the kickoff, finalizie the Documentation details.
+- By the kickoff, finalize the Documentation details.
### Developer and maintainer roles
@@ -117,7 +117,7 @@ Follow the process below unless otherwise agreed with the product manager and te
#### Reviews and merging
-All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
+All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](index.md) and [Style Guide](styleguide.md).
- **Prior to merging**, documentation changes committed by the developer must be reviewed by:
@@ -136,7 +136,7 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability.
- Upon merging, if a technical writer review has not been performed and there is not yet a linked issue for a follow-up review, the maintainer should [create an issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review), link it from the MR, and
- mention the original MR author in the new issue. Alternatively, the mainitainer can ask the MR author to create and link this issue before the MR is merged.
+ mention the original MR author in the new issue. Alternatively, the maintainer can ask the MR author to create and link this issue before the MR is merged.
- After merging, documentation changes are reviewed by:
@@ -157,14 +157,14 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
#### Collaboration
By default, the developer will work on documentation changes independently, but
-the developer, PM, or technicial writer can propose a broader collaboration for
+the developer, PM, or technical writer can propose a broader collaboration for
any given issue.
Additionally, technical writers are available for questions at any time.
#### Review
-- Techncial writers provide non-blocking reviews of all documentation changes,
+- Technical writers provide non-blocking reviews of all documentation changes,
before or after the change is merged. However, if the docs are ready in the MR while
there's time before the freeze, the technical writer's review can commence early, on request.
- The technical writer will confirm that the doc is clear, grammatically correct,
@@ -173,7 +173,7 @@ Additionally, technical writers are available for questions at any time.
the developer and code reviewer should have already made a good-faith effort to ensure:
- Clarity.
- Adherence to the plans and goals in the issue.
- - Location (make sure the docs are in the correct directorkes and has the correct name).
+ - Location (make sure the docs are in the correct directories and has the correct name).
- Syntax, typos, and broken links.
- Improvements to the content.
- Accordance with the [Documentation Style Guide](styleguide.md), and [Structure and Template](structure.md) doc.
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 652fe3ea711..c7fa40af930 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -16,13 +16,13 @@ In addition to this page, the following resources to help craft and contribute d
- [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) - A reference for the markdown implementation used by GitLab's documentation site and about.gitlab.com.
- [Site architecture](site_architecture/index.md) - How docs.gitlab.com is built.
-## Source and rendered locations
+## Source files and rendered web locations
Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance.
At `/help`, only content for your current edition and version is included, whereas multiple versions' content is available at docs.gitlab.com.
-The source of the documentation is maintained in the following repository locations:
+The source of the documentation exists within the codebase of each GitLab application in the following repository locations:
| Project | Path |
| --- | --- |
@@ -48,95 +48,67 @@ as its markdown rendering engine. See the [GitLab Markdown Guide](https://about.
Adhere to the [Documentation Style Guide](styleguide.md). If a style standard is missing, you are welcome to suggest one via a merge request.
-## Documentation directory structure
-
-The documentation is structured based on the GitLab UI structure itself,
-separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
-[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development).
-
-In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
-all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
-
-The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
-been **deprecated** and the majority their docs have been moved to their correct location
-in small iterations. Please do not create new docs in these folders. Organize docs by product area and subject, not type.
-
-### Documentation files
-
-- When you create a new directory, always start with an `index.md` file.
- Do not use another file name and **do not** create `README.md` files.
-- **Do not** use special chars and spaces, or capital letters in file names,
- directory names, branch names, and anything that generates a path.
-- Max screenshot size: 100KB.
-- We do not support videos (yet).
-
-### Location and naming documents
-
-Our goal is to have a clear hierarchical structure with meaningful URLs
-like `docs.gitlab.com/user/project/merge_requests/`. With this pattern,
-you can immediately tell that you are navigating to user-related documentation
-about project features; specifically about merge requests. Our site's paths match
-those of our repository, so the clear structure also makes documentation easier to update.
-
-While the documentation is home to a variety of content types, we do not organize by content type.
-For example, do not create groupings of similar media types (e.g. indexes of all articles, videos, etc.).
-Similarly, we do not use glossaries or FAQs. Such grouping of content by type makes
-it difficult to browse for the information you need and difficult to maintain up-to-date content.
-Instead, organize content by its subject (e.g. everything related to CI goes together)
-and cross-link between any related content.
-
-Do not simply link out to GitLab technical blog posts. There should be an up-to-date
-single source of truth on the topic within the documentation, and the top of the
-blog post should be updated to link to that doc.
-
-The table below shows what kind of documentation goes where.
-
-| Directory | What belongs here |
-|:----------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
-| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
-| `doc/api/` | API related documentation. |
-| `doc/development/` | Documentation related to the development of GitLab. Related process and style guides should go here. |
-| `doc/legal/` | Legal documents about contributing to GitLab. |
-| `doc/install/` | Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
-| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
-| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
-
-**General rules & best practices:**
-
-1. When creating a new document and it has more than one word in its name,
- make sure to use underscores instead of spaces or dashes (`-`). For example,
- a proper naming would be `import_projects_from_github.md`. The same rule
- applies to images.
-1. Start a new directory with an `index.md` file.
-1. There are four main directories, `user`, `administration`, `api` and `development`.
-1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
- `profile/`, `dashboard/` and `admin_area/`.
- 1. `doc/user/project/` should contain all project related documentation.
- 1. `doc/user/group/` should contain all group related documentation.
- 1. `doc/user/profile/` should contain all profile related documentation.
- Every page you would navigate under `/profile` should have its own document,
- i.e. `account.md`, `applications.md`, `emails.md`, etc.
- 1. `doc/user/dashboard/` should contain all dashboard related documentation.
- 1. `doc/user/admin_area/` should contain all admin related documentation
- describing what can be achieved by accessing GitLab's admin interface
- (_not to be confused with `doc/administration` where server access is
- required_).
- 1. Every category under `/admin/application_settings` should have its
- own document located at `doc/user/admin_area/settings/`. For example,
- the **Visibility and Access Controls** category should have a document
- located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
-1. The `doc/topics/` directory holds topic-related technical content. Create
- `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
- General user- and admin- related documentation, should be placed accordingly.
-
-If you are unsure where a document or a content addition should live, this should
-not stop you from authoring and contributing. You can use your best judgment and
-then ask the reviewer of your MR to confirm your decision, and/or ask a technical writer
-at any stage in the process. The techncial writing team will review all documentation
-changes, regardless, and can move content if there is a better place for it.
-
-### Changing document location
+## Folder structure and files
+
+See the [Structure](styleguide.md#structure) section of the [Documentation Style Guide](styleguide.md).
+
+## Single codebase
+
+We currently maintain two sets of docs: one in the
+[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc) repo and
+one in [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc).
+They are similar, and most pages are identical, but they are different repositories.
+With the single codebase effort, we want to make those two sets identical, so when the
+time comes to have only one codebase, we'll be ready.
+
+Here are some links to get you up to speed with the current effort:
+
+- [CE/EE codebases blueprint](https://about.gitlab.com/handbook/engineering/infrastructure/blueprint/ce-ee-codebases/)
+- [CE/EE codebases merge design](https://about.gitlab.com/handbook/engineering/infrastructure/design/merge-ce-ee-codebases/)
+- [Single docs codebase epic](https://gitlab.com/groups/gitlab-org/-/epics/199)
+- [Issue board of related issues](https://gitlab.com/groups/gitlab-org/-/boards/981090?&label_name[]=Documentation&label_name[]=single%20codebase)
+- [Related merge requests](https://gitlab.com/groups/gitlab-org/-/merge_requests?scope=all&utf8=%E2%9C%93&state=all&label_name[]=Documentation&label_name[]=single%20codebase)
+- [Visualize the existing diffs](https://leipert-projects.gitlab.io/is-gitlab-pretty-yet/diff/?search=%5Edoc)
+
+### CE first
+
+After a given documentation path is aligned across CE and EE, all merge requests
+affecting that path must be submitted to CE, regardless of the content it has.
+This means that:
+
+* For **EE-only docs changes**, you only have to submit a CE MR.
+* For **EE-only features** that touch both the code and the docs, you have to submit
+an EE MR containing all changes, and a CE MR containing only the docs changes
+and without a changelog entry.
+
+This might seem like a duplicate effort, but it's only for the short term.
+A list of the already aligned docs can be found in
+[the epic description](https://gitlab.com/groups/gitlab-org/-/epics/199#ee-specific-lines-check).
+
+Since the docs will be combined, it's crucial to add the relevant
+[product badges](styleguide.md#product-badges) for all EE documentation, so that
+we can discern which features belong to which tier.
+
+### EE specific lines check
+
+There's a special test in place
+([`ee_specific_check.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/ee_specific_check/ee_specific_check.rb)),
+which, among others, checks and prevents creating/editing new files and directories
+in EE under `doc/`.
+
+We have a long list of documentation paths that are either whitelisted or not.
+Paths in the whitelist (not commented out) will not be subject to the test,
+which means you are allowed to create/change docs content in EE for the time
+being. The goal is to not have any doc whitelisted.
+
+At the time of this writing, the only items left to be aligned are the API docs:
+
+- `doc/api/*` ([issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60045) / [merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27491))
+
+Eventually, once all docs are aligned, we'll remove any doc reference from that
+script, so it catches everything.
+
+## Changing document location
Changing a document's location requires specific steps to be followed to ensure that
users can seamlessly access the new doc page, whether they are accesing content
@@ -194,7 +166,7 @@ Things to note:
built-in help page, that's why we omit it in `git grep`.
- Use the checklist on the "Change documentation location" MR description template.
-#### Alternative redirection method
+### Alternative redirection method
Alternatively to the method described above, you can simply replace the content
of the old file with a frontmatter containing a redirect link:
@@ -212,7 +184,7 @@ This redirection method will not provide a redirect fallback on GitLab `/help`.
it, make sure to add a link to the new page on the doc, otherwise it's a dead end for users that
land on the doc via `/help`.
-#### Redirections for pages with Disqus comments
+### Redirections for pages with Disqus comments
If the documentation page being relocated already has any Disqus comments,
we need to preserve the Disqus thread.
@@ -397,7 +369,7 @@ to merge changes that will break `master` from a merge request with a successful
## Docs site architecture
See the [Docs site architecture](site_architecture/index.md) page to learn
-how we build and deploy the site at [docs.gitlab.com](https://docs.gitlab.com), and
+how we build and deploy the site at [docs.gitlab.com](https://docs.gitlab.com) and
to review all the assets and libraries in use.
### Global navigation
@@ -499,7 +471,7 @@ If you want to know the in-depth details, here's what's really happening:
The following GitLab features are used among others:
- [Manual actions](../../ci/yaml/README.md#whenmanual)
-- [Multi project pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
+- [Multi project pipelines](../../ci/multi_project_pipeline_graphs.md)
- [Review Apps](../../ci/review_apps/index.md)
- [Artifacts](../../ci/yaml/README.md#artifacts)
- [Specific Runner](../../ci/runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
@@ -511,10 +483,10 @@ Currently, the following tests are in place:
1. `docs lint`: Check that all internal (relative) links work correctly and
that all cURL examples in API docs use the full switches. It's recommended
- to [check locally](#previewing-locally) before pushing to GitLab by executing the command
+ to [check locally](#previewing-the-changes-live) before pushing to GitLab by executing the command
`bundle exec nanoc check internal_links` on your local
[`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs) directory.
-1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-gt-ee-merge-conflicts-beforehand) (runs on CE only):
+1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-ee-merge-conflicts-beforehand) (runs on CE only):
When you submit a merge request to GitLab Community Edition (CE),
there is this additional job that runs against Enterprise Edition (EE)
and checks if your changes can apply cleanly to the EE codebase.
@@ -522,6 +494,10 @@ Currently, the following tests are in place:
As CE is merged into EE once a day, it's important to avoid merge conflicts.
Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
essential to avoid them.
+1. [`ee-files-location-check`/`ee-specific-lines-check`](#ee-specific-lines-check) (runs on EE only):
+ This test ensures that no new files/directories are created/changed in EE.
+ All docs should be submitted in CE instead, regardless the tier they are on.
+ This is for the [single codebase](#single-codebase) effort.
1. In a full pipeline, tests for [`/help`](#gitlab-help-tests).
### Linting
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 0aa3c41a225..f2f4f5f0e1c 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -62,7 +62,7 @@ the consent of one of the technical writers.
The global nav is built from two files:
- [Data](#data-file)
-- [Layout](#layout-file)
+- [Layout](#layout-file-logic)
The data file feeds the layout with the links to the docs. The layout organizes
the data among the nav in containers properly [styled](#css-classes).
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 95b5fcd99a1..fe676efa94d 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -77,8 +77,8 @@ for use (e.g. variations on the main use case), but if that's not applicable, th
Examples of use cases on feature pages:
- CE and EE: [Issues](../../user/project/issues/index.md#use-cases)
- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md)
-- EE-only: [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html)
-- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.html)
+- EE-only: [Geo](../../administration/geo/replication/index.md)
+- EE-only: [Jenkins integration](../../integration/jenkins.md)
## Requirements
@@ -121,8 +121,8 @@ but commented out to help encourage others to add to it in the future. -->
Notes:
-- (1): Apply the [tier badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) accordingly
-- (2): Apply the correct format for the [GitLab version introducing the feature](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
+- (1): Apply the [tier badges](styleguide.md#product-badges) accordingly
+- (2): Apply the correct format for the [GitLab version introducing the feature](styleguide.md#gitlab-versions-and-tiers)
```
## Help and feedback section
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 7a3a8f25c2d..5caca846cc9 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -4,65 +4,192 @@ description: 'Writing styles, markup, formatting, and other standards for GitLab
# Documentation Style Guide
-The documentation style guide defines the markup structure used in
-GitLab documentation. Check the
-[documentation guidelines](index.md) for general development instructions.
+This document defines the standards for GitLab's documentation content and files.
-See the GitLab handbook for the [writing style guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines).
+For broader information about the documentation, see the [Documentation guidelines](index.md).
For programmatic help adhering to the guidelines, see [linting](index.md#linting).
-## Files
+See the GitLab handbook for further [writing style guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines)
+that apply to all GitLab content, not just documentation.
-- [Directory structure](index.md#location-and-naming-documents): place the docs
- in the correct location.
-- [Documentation files](index.md#documentation-files): name the files accordingly.
+## Documentation is the single source of truth (SSOT)
-DANGER: **Attention:**
-**Do not** use capital letters, spaces, or special chars in file names,
-branch names, directory names, headings, or in anything that generates a path.
+### Why a single source of truth
-NOTE: **Note:**
-**Do not** create new `README.md` files, name them `index.md` instead. There's
-a test that will fail if it spots a new `README.md` file.
+The documentation is the SSOT for all information related to the implementation, usage, and troubleshooting of GitLab products and features. It evolves continually, in keeping with new products and features, and with improvements for clarity, accuracy, and completeness.
+
+This policy prevents information silos, ensuring that it remains easy to find information about GitLab products.
+
+It also informs decisions about the kinds of content we include in our documentation.
+
+The documentation is a continually evolving SSOT for all information related to the implementation, usage, and troubleshooting of GitLab products and features.
+
+### All information
+
+Include problem-solving actions that may address rare cases or be considered 'risky', so long as proper context is provided in the form of fully detailed warnings and caveats. This kind of content should be included as it could be helpful to others and, when properly explained, its benefits outweigh the risks. If you think you have found an exception to this rule, contact the Technical Writing team.
+
+We will add all troubleshooting information to the documentation, no matter how unlikely a user is to encounter a situation.
+For the Troubleshooting sections, people in GitLab Support can merge additions themselves.
+
+### All media types
+
+Include any media types/sources if the content is relevant to readers. You can freely include or link presentations, diagrams, videos, etc.; no matter who it was originally composed for, if it is helpful to any of our audiences, we can include it.
+
+ - If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
+ - Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
+
+### No special types
+
+In the software industry, it is a best practice to organize documentatioin in different types. For example, [Divio recommends](https://www.divio.com/blog/documentation/):
+
+1. Tutorials
+2. How-to guides
+3. Explanation
+4. Reference (for example, a glossary)
+
+At GitLab, we have so many product changes in our monthly releases that we can't afford to continually update multiple types of information.
+If we have multiple types, the information will become outdated. Therefore, we have a [single template](structure.md) for documentation.
+
+We currently do not distinguish specific document types, although we are open to reconsidering this policy
+once the documentation has reached a future stage of maturity and quality. If you are reading this, then despite our
+continual improvement efforts, that point hasn't been reached.
+
+### Link instead of summarize
+
+There is a temptation to summarize the information on another page.
+This will cause the information to live in two places.
+Instead, link to the SSOT and explain why it is important to consume the information.
+
+### Organize by topic, not by type
+
+Beyond top-level audience-type folders (e.g. `administration`), we organize content by topic, not by type, so that it can be located as easily as possible within the single-source-of-truth (SSOT) section for the subject matter.
+
+For example, do not create groupings of similar media types (e.g. glossaries, FAQs, or sets of all articles or videos).
+
+Such grouping of content by type makes
+it difficult to browse for the information you need and difficult to maintain up-to-date content.
+Instead, organize content by its subject (e.g. everything related to CI goes together)
+and cross-link between any related content.
+
+### Docs-first methodology
+
+We employ a **docs-first methodology** to help ensure that the docs remain a complete and trusted resource, and to make communicating about the use of GitLab more efficient.
+
+* If the answer to a question exists in documentation, share the link to the docs instead of rephrasing the information.
+* When you encounter new information not available in GitLab’s documentation (for example, when working on a support case or testing a feature), your first step should be to create a merge request to add this information to the docs. You can then share the MR in order to communicate this information.
+
+New information that would be useful toward the future usage or troubleshooting of GitLab should not be written directly in a forum or other messaging system, but added to a docs MR and then referenced, as described above. Note that among any other doc changes, you can always add a Troubleshooting section to a doc if none exists, or un-comment and use the placeholder Troubleshooting section included as part of our [doc template](structure.md#template-for-new-docs), if present.
+
+The more we reflexively add useful information to the docs, the more (and more successfully) the docs will be used to efficiently accomplish tasks and solve problems.
-### Markdown
+If you have questions when considering, authoring, or editing docs, ask the Technical Writing team on Slack in `#docs` or in GitLab by mentioning the writer for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). Otherwise, forge ahead with your best effort. It does not need to be perfect; the team is happy to review and improve upon your content. Please review the [Documentation guidelines](index.md) before you begin your first documentation MR.
-The [documentation website](https://docs.gitlab.com) had its markdown engine migrated from [Redcarpet to GitLab Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108)
-in October 2018.
+Having a knowledge base is any form that is separate from the documentation would be against the docs-first methodology because the content would overlap with the documentation.
+
+## Markdown
+
+All GitLab documentation is written using [Markdown](https://en.wikipedia.org/wiki/Markdown).
+
+The [documentation website](https://docs.gitlab.com) uses GitLab Kramdown as its Markdown rendering engine. For a complete Kramdown reference, see the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
The [`gitlab-kramdown`](https://gitlab.com/gitlab-org/gitlab_kramdown)
-gem will support all [GFM markup](../../user/markdown.md) in the future. For now,
-use regular markdown markup, following the rules on this style guide. For a complete
-Kramdown reference, check the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
-Use Kramdown markup wisely: do not overuse its specific markup (e.g., `{:.class}`) as it will not render properly in
-[`/help`](#gitlab-help).
-
-## Content
-
-These guidelines help toward the goal of having every user's search of documentation
-yield a useful result, and ensuring content is helpful and easy to consume.
-
-- What to include:
- - Any and all helpful information, processes, and tips for implementing,
- using, and troubleshooting GitLab features. [The documentation is the single source of truth](https://about.gitlab.com/handbook/documentation/#documentation-as-single-source-of-truth-ssot)
- for this information.
- - 'Risky' or niche problem-solving steps. There is no reason to withhold these or
- store them elsewhere; simply include them along with the rest of the docs including all necessary
- detail, such as specific warnings and caveats about potential ramifications.
- - Any content types/sources, if relevant to users or admins. You can freely
- include presentations, videos, etc.; no matter who it was originally written for,
- if it is helpful to any of our audiences, we can include it. If an outside source
- that's under copyright, rephrase, or summarize and link out; do not copy and paste.
- - All applicable subsections as described on the [structure and template](structure.md) page,
- with files organized in the [correct directory](index.md#documentation-directory-structure).
-- To ensure discoverability, link to each doc from its higher-level index page and other related pages.
-- When referencing other GitLab products and features, link to their
- respective docs; when referencing third-party products or technologies,
- link out to their external sites, documentation, and resources.
-- Do not duplicate information.
+Ruby gem will support all [GFM markup](../../user/markdown.md) in the future. That is,
+all markup that is supported for display in the GitLab application itself. For now,
+use regular Markdown markup, following the rules in the linked style guide.
+
+Note that Kramdown-specific markup (e.g., `{:.class}`) will not render properly on GitLab instances under [`/help`](index.md#gitlab-help).
+
+## Structure
+
+### Organize by topic, not by type
+
+Because we want documentation to be a SSOT, we should [organize by topic, not by type](#organize-by-topic-not-by-type).
+
+### Folder structure overview
+
+The documentation is separated by top-level audience folders [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
+[`administration`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`development`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development) (contributing) folders.
+
+Beyond that, we primarily follow the structure of the GitLab user interface or API.
+
+Our goal is to have a clear hierarchical structure with meaningful URLs
+like `docs.gitlab.com/user/project/merge_requests/`. With this pattern,
+you can immediately tell that you are navigating to user-related documentation
+about Project features; specifically about Merge Requests. Our site's paths match
+those of our repository, so the clear structure also makes documentation easier to update.
+
+The table below shows what kind of documentation goes where.
+
+| Directory | What belongs here |
+|:----------------------|:---------------------------------------------------------------------------------------------------------------------------------|
+| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here, including usage of the `/admin` interface. |
+| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface exist under `doc/user/admin_area/`. |
+| `doc/api/` | API related documentation. |
+| `doc/development/` | Documentation related to the development of GitLab, whether contributing code or docs. Related process and style guides should go here. |
+| `doc/legal/` | Legal documents about contributing to GitLab. |
+| `doc/install/` | Contains instructions for installing GitLab. |
+| `doc/update/` | Contains instructions for updating GitLab. |
+| `doc/topics/` | Indexes per topic (`doc/topics/topic-name/index.md`): all resources for that topic. |
+
+### Working with directories and files
+
+1. When you create a new directory, always start with an `index.md` file.
+ Do not use another file name and **do not** create `README.md` files.
+1. **Do not** use special characters and spaces, or capital letters in file names,
+ directory names, branch names, and anything that generates a path.
+1. When creating a new document and it has more than one word in its name,
+ make sure to use underscores instead of spaces or dashes (`-`). For example,
+ a proper naming would be `import_projects_from_github.md`. The same rule
+ applies to images.
+1. For image files, do not exceed 100KB.
+1. We do not yet support embedded videos. Please link out.
+1. There are four main directories, `user`, `administration`, `api` and `development`.
+1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
+ `profile/`, `dashboard/` and `admin_area/`.
+ 1. `doc/user/project/` should contain all project related documentation.
+ 1. `doc/user/group/` should contain all group related documentation.
+ 1. `doc/user/profile/` should contain all profile related documentation.
+ Every page you would navigate under `/profile` should have its own document,
+ i.e. `account.md`, `applications.md`, `emails.md`, etc.
+ 1. `doc/user/dashboard/` should contain all dashboard related documentation.
+ 1. `doc/user/admin_area/` should contain all admin related documentation
+ describing what can be achieved by accessing GitLab's admin interface
+ (_not to be confused with `doc/administration` where server access is
+ required_).
+ 1. Every category under `/admin/application_settings` should have its
+ own document located at `doc/user/admin_area/settings/`. For example,
+ the **Visibility and Access Controls** category should have a document
+ located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
+1. The `doc/topics/` directory holds topic-related technical content. Create
+ `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
+ General user- and admin- related documentation, should be placed accordingly.
+1. The directories `/workflow/`, `/university/`, and `/articles/` have
+been **deprecated** and the majority their docs have been moved to their correct location
+in small iterations.
+
+If you are unsure where a document or a content addition should live, this should
+not stop you from authoring and contributing. You can use your best judgment and
+then ask the reviewer of your MR to confirm your decision, and/or ask a technical writer
+at any stage in the process. The techncial writing team will review all documentation
+changes, regardless, and can move content if there is a better place for it.
+
+### Avoid duplication
+
+Do not include the same information in multiple places. [Link to a SSOT instead.](#link-instead-of-summarize)
+
+### References across documents
+
+- Give each folder an index.md page that introduces the topic, introduces the pages within, and links to the pages within (including to the index pages of any next-level subpaths).
+- To ensure discoverability, ensure each new or renamed doc is linked from its higher-level index page and other related pages.
+- When making reference to other GitLab products and features, link to their respective docs, at least on first mention.
+- When making reference to third-party products or technologies, link out to their external sites, documentation, and resources.
+
+### Structure within documents
+
+- Include any and all applicable subsections as described on the [structure and template](structure.md) page.
- Structure content in alphabetical order in tables, lists, etc., unless there is
- a logical reason not to (for example, when mirroring the UI or an ordered sequence).
+ a logical reason not to (for example, when mirroring the UI or an otherwise ordered sequence).
## Language
@@ -80,11 +207,10 @@ yield a useful result, and ensuring content is helpful and easy to consume.
## Text
-- Split up long lines (wrap text), this makes it much easier to review and edit. Only
- double line breaks are shown as a full line break by creating new paragraphs.
- 80-100 characters is the recommended line length.
+- Splitting long lines (preferably up to 100 characters) can make it easier to provide feedback on small chunks of text.
+- Insert an empty line for new paragraphs.
- Use sentence case for titles, headings, labels, menu items, and buttons.
-- Jump a line between different markups (e.g., after every paragraph, header, list, etc). Example:
+- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
```md
## Header
@@ -95,7 +221,7 @@ yield a useful result, and ensuring content is helpful and easy to consume.
- List item 2
```
-### Tables overlapping the ToC
+### Tables overlapping the TOC
By default, all tables have a width of 100% on docs.gitlab.com.
In a few cases, the table will overlap the table of contents (ToC).
@@ -236,6 +362,31 @@ For other punctuation rules, please refer to the
E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
write `Read more about [GitLab Issue Boards](LINK)`.
+### Links requiring permissions
+
+Don't link directly to:
+
+- [Confidential issues](../../user/project/issues/confidential_issues.md).
+- Project features that require [special permissions](../../user/permissions.md) to view.
+
+These will fail for:
+
+- Those without sufficient permissions.
+- Automated link checkers.
+
+Instead:
+
+- To reduce confusion, mention in the text that the information is either:
+ - Contained in a confidential issue.
+ - Requires special permission to a project to view.
+- Provide a link in back ticks (`` ` ``) so that those with access to the issue can easily navigate to it.
+
+Example:
+
+```md
+For more information, see the [confidential issue](https://docs.gitlab.com/ee/user/project/issues/confidential_issues.html) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
+```
+
### Unlinking emails
By default, all email addresses will render in an email tag on docs.gitlab.com.
@@ -453,41 +604,49 @@ The following are recommended verbs for specific uses.
## GitLab versions and tiers
-- Every piece of documentation that comes with a new feature should declare the
- GitLab version that feature got introduced. Right below the heading add a
- blockquote:
+Tagged and released versions of GitLab documentation are available:
+
+- In the [documentation archives](https://docs.gitlab.com/archives/).
+- At the `/help` URL for any GitLab installation.
+
+The version introducing a new feature is added to the top of the topic in the documentation to provide
+a helpful link back to how the feature was developed.
+
+### Text for documentation requiring version text
+
+- For features that need to declare the GitLab version that the feature was introduced. Text similar
+ to the following should be added immediately below the heading as a blockquote:
```md
- > Introduced in GitLab 8.3.
+ > Introduced in GitLab 11.3.
```
-- Whenever possible, every feature should have a link to the issue, MR or epic
- (in that order) that introduced it. The above quote would be then transformed to:
+- Whenever possible, version text should have a link to the issue, merge request, or epic that introduced the feature.
+ An issue is preferred over a merge request, and a merge request is preferred over an epic. For example:
```md
- > [Introduced](<link-to-issue>) in GitLab 8.3.
+ > [Introduced](<link-to-issue>) in GitLab 11.3.
```
-- If the feature is only available in GitLab Enterprise Edition, don't forget to mention
+- If the feature is only available in GitLab Enterprise Edition, mention
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the feature is available in:
```md
- > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
+ > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
```
-### Early versions of EE
-
-If the feature was created before GitLab 9.2 (before [different EE tiers were introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1851)):
+### Removing version text
-- Declare it as "Introduced in GitLab Enterprise Edition X.Y".
-- Note which tier the feature is available in.
+Over time, version text will reference a progressively older version of GitLab. In cases where version text
+refers to versions of GitLab four or more major versions back, consider removing the text.
-For example:
+For example, if the current major version is 11.x, version text referencing versions of GitLab 7.x
+and older are candidates for removal.
-```md
-> [Introduced](<link-to-issue>) in GitLab Enterprise Edition 9.0. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
-```
+NOTE: **Note:**
+This guidance applies to any text that mentions a GitLab version, not just "Introduced in... " text.
+Other text includes deprecation notices and version-specific how-to information.
## Product badges
@@ -507,6 +666,12 @@ keyword "only":
- For GitLab Premium: `**[PREMIUM ONLY]**`.
- For GitLab Ultimate: `**[ULTIMATE ONLY]**`.
+For GitLab.com only tiers (when the feature is not available for self-hosted instances):
+
+- For GitLab Bronze and higher tiers: `**[BRONZE ONLY]**`.
+- For GitLab Silver and higher tiers: `**[SILVER ONLY]**`.
+- For GitLab Gold: `**[GOLD ONLY]**`.
+
The tier should be ideally added to headers, so that the full badge will be displayed.
However, it can be also mentioned from paragraphs, list items, and table cells. For these cases,
the tier mention will be represented by an orange question mark that will show the tiers on hover.
@@ -515,6 +680,7 @@ For example:
- `**[STARTER]**` renders as **[STARTER]**
- `**[STARTER ONLY]**` renders as **[STARTER ONLY]**
+- `**[SILVER ONLY]**` renders as **[SILVER ONLY]**
The absence of tiers' mentions mean that the feature is available in GitLab Core,
GitLab.com Free, and all higher tiers.
@@ -612,7 +778,7 @@ In this case:
- The code blocks are indented one or more spaces under the list item to render
correctly.
- Different highlighting languages are used for each config in the code block.
-- The [references](#references) guide is used for reconfigure/restart.
+- The [GitLab Restart](#gitlab-restart) section is used to explain a required restart/reconfigure of GitLab.
## API
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 3e85c0e1995..cca52706ddc 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -19,6 +19,11 @@ CE specs should remain untouched as much as possible and extra specs
should be added for EE. Licensed features can be stubbed using the
spec helper `stub_licensed_features` in `EE::LicenseHelpers`.
+You can force Webpack to act as CE by either deleting the `ee/` directory or by
+setting the [`IS_GITLAB_EE` environment variable](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/config/helpers/is_ee_env.js)
+to something that evaluates as `false`. The same works for running tests
+(for example `IS_GITLAB_EE=0 yarn jest`).
+
[ee-as-ce]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2500
## Separation of EE code
@@ -161,7 +166,7 @@ still having access the class's implementation with `super`.
There are a few gotchas with it:
-- you should always [`extend ::Gitlab::Utils::Override`] and use `override` to
+- you should always [`extend ::Gitlab::Utils::Override`](utilities.md#overridehttpsgitlabcomgitlab-orggitlab-ceblobmasterlibgitlabutilsoverriderb) and use `override` to
guard the "overrider" method to ensure that if the method gets renamed in
CE, the EE override won't be silently forgotten.
- when the "overrider" would add a line in the middle of the CE
@@ -273,8 +278,6 @@ module EE
end
```
-[`extend ::Gitlab::Utils::Override`]: utilities.md#override
-
##### Overriding CE class methods
The same applies to class methods, except we want to use
@@ -443,6 +446,19 @@ The disadvantage of this:
port `render_if_exists` to CE.
- If we have typos in the partial name, it would be silently ignored.
+
+##### Caveats
+
+The `render_if_exists` view path argument must be relative to `app/views/` and `ee/app/views`.
+Resolving an EE template path that is relative to the CE view path will not work.
+
+```haml
+- # app/views/projects/index.html.haml
+
+= render_if_exists 'button' # Will not render `ee/app/views/projects/_button` and will quietly fail
+= render_if_exists 'projects/button' # Will render `ee/app/views/projects/_button`
+```
+
#### Using `render_ce`
For `render` and `render_if_exists`, they search for the EE partial first,
@@ -532,40 +548,56 @@ due to `prepend`, but Grape is complex internally and we couldn't easily do
that, so we'll follow regular object-oriented practices that we define the
interface first here.
-For example, suppose we have a few more optional params for EE, given this CE
-API code:
+For example, suppose we have a few more optional params for EE. We can move the
+params out of the `Grape::API` class to a helper module, so we can `prepend` it
+before it would be used in the class.
```ruby
module API
- class MergeRequests < Grape::API
- # EE::API::MergeRequests would override the following helpers
- helpers do
- params :optional_params_ee do
+ class Projects < Grape::API
+ helpers Helpers::ProjectsHelpers
+ end
+end
+```
+
+Given this CE API `params`:
+
+```ruby
+module API
+ module Helpers
+ module ProjectsHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_project_params_ce do
+ # CE specific params go here...
end
- end
- params :optional_params do
- # CE specific params go here...
+ params :optional_project_params_ee do
+ end
- use :optional_params_ee
+ params :optional_project_params do
+ use :optional_project_params_ce
+ use :optional_project_params_ee
+ end
end
end
end
-API::MergeRequests.prepend(EE::API::MergeRequests)
+API::Helpers::ProjectsHelpers.prepend(EE::API::Helpers::ProjectsHelpers)
```
-And then we could override it in EE module:
+We could override it in EE module:
```ruby
module EE
module API
- module MergeRequests
- extend ActiveSupport::Concern
+ module Helpers
+ module ProjectsHelpers
+ extend ActiveSupport::Concern
- prepended do
- helpers do
- params :optional_params_ee do
+ prepended do
+ params :optional_project_params_ee do
# EE specific params go here...
end
end
@@ -575,9 +607,6 @@ module EE
end
```
-This way, the only difference between CE and EE for that API file would be
-`prepend EE::API::MergeRequests`.
-
#### EE helpers
To make it easy for an EE module to override the CE helpers, we need to define
@@ -877,15 +906,88 @@ import bundle from 'ee/protected_branches/protected_branches_bundle.js';
import bundle from 'ee_else_ce/protected_branches/protected_branches_bundle.js';
```
-See the frontend guide [performance section](./fe_guide/performance.md) for
+See the frontend guide [performance section](fe_guide/performance.md) for
information on managing page-specific javascript within EE.
+
+## Vue code in `assets/javascript`
+### script tag
+
+#### Child Component only used in EE
+To separate Vue template differences we should [async import the components](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components).
+
+Doing this allows for us to load the correct component in EE whilst in CE
+we can load a empty component that renders nothing. This code **should**
+exist in the CE repository as well as the EE repository.
+
+```html
+<script>
+export default {
+ components: {
+ EEComponent: () => import('ee_component/components/test.vue'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <ee-component />
+ </div>
+</template>
+```
+
+#### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
+ - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack.
+ - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
+
+##### Example:
+```javascript
+import mixin from 'ee_else_ce/path/mixin';
+
+{
+ mixins: [mixin]
+}
+```
+
+- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
+
+- For store modules, we will need a CE counterpart too.
+- You can see an MR with an example [here](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9762)
+
+#### `template` tag
+* **EE Child components**
+ - Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee).
+
+* **EE extra HTML**
+ - For the templates that have extra HTML in EE we should move it into a new component and use the `ee_else_ce` dynamic import
+
+### Non Vue Files
+For regular JS files, the approach is similar.
+
+1. We will keep using the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) helper, this means that EE only code should be inside the `ee/` folder.
+ 1. An EE file should be created with the EE only code, and it should extend the CE counterpart.
+ 1. For code inside functions that can't be extended, the code should be moved into a new file and we should use `ee_else_ce` helper:
+
+##### Example:
+
+```javascript
+ import eeCode from 'ee_else_ce/ee_code';
+
+ function test() {
+ const test = 'a';
+
+ eeCode();
+
+ return test;
+ }
+```
+
## SCSS code in `assets/stylesheets`
To separate EE-specific styles in SCSS files, if a component you're adding styles for
is limited to only EE, it is better to have a separate SCSS file in appropriate directory
within `app/assets/stylesheets`.
-See [backporting changes](#backporting-changes) for instructions on how to merge changes safely.
+See [backporting changes](#backporting-changes-from-ee-to-ce) for instructions on how to merge changes safely.
In some cases, this is not entirely possible or creating dedicated SCSS file is an overkill,
e.g. a text style of some component is different for EE. In such cases,
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
new file mode 100644
index 00000000000..8b0f4f02d19
--- /dev/null
+++ b/doc/development/elasticsearch.md
@@ -0,0 +1,166 @@
+# Elasticsearch knowledge **[STARTER ONLY]**
+
+This area is to maintain a compendium of useful information when working with elasticsearch.
+
+Information on how to enable ElasticSearch and perform the initial indexing is kept in ../integration/elasticsearch.md#enabling-elasticsearch
+
+## Initial installation on OS X
+
+It is recommended to use the Docker image. After installing docker you can immediately spin up an instance with
+
+```
+docker run --name elastic56 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:5.6.12
+```
+
+and use `docker stop elastic56` and `docker start elastic56` to stop/start it.
+
+### Installing on the host
+
+We currently only support Elasticsearch [5.6 to 6.x](../integration/elasticsearch.md#version-requirements)
+
+Version 5.6 is available on homebrew and is the recommended version to use in order to test compatibility.
+
+```
+brew install elasticsearch@5.6
+```
+
+There is no need to install any plugins
+
+## New repo indexer (beta)
+
+If you're interested on working with the new beta repo indexer, all you need to do is:
+
+- git clone git@gitlab.com:gitlab-org/gitlab-elasticsearch-indexer.git
+- make
+- make install
+
+this adds `gitlab-elasticsearch-indexer` to `$GOPATH/bin`, please make sure that is in your `$PATH`. After that GitLab will find it and you'll be able to enable it in the admin settings area.
+
+**note:** `make` will not recompile the executable unless you do `make clean` beforehand
+
+## Helpful rake tasks
+
+- `gitlab:elastic:test:index_size`: Tells you how much space the current index is using, as well as how many documents are in the index.
+- `gitlab:elastic:test:index_size_change`: Outputs index size, reindexes, and outputs index size again. Useful when testing improvements to indexing size.
+
+Additionally, if you need large repos or multiple forks for testing, please consider [following these instructions](rake_tasks.md#extra-project-seed-options)
+
+## How does it work?
+
+The ElasticSearch integration depends on an external indexer. We ship a [ruby indexer](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/bin/elastic_repo_indexer) by default but are also working on an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task, but after this is done GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_search.rb](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/concerns/elastic/application_search.rb).
+
+All indexing after the initial one is done via `ElasticIndexerWorker` (sidekiq jobs).
+
+Search queries are generated by the concerns found in [ee/app/models/concerns/elastic](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/models/concerns/elastic). These concerns are also in charge of access control, and have been a historic source of security bugs so please pay close attention to them!
+
+## Existing Analyzers/Tokenizers/Filters
+These are all defined in https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/elasticsearch/git/model.rb
+
+### Analyzers
+#### `path_analyzer`
+Used when indexing blobs' paths. Uses the `path_tokenizer` and the `lowercase` and `asciifolding` filters.
+
+Please see the `path_tokenizer` explanation below for an example.
+
+#### `sha_analyzer`
+Used in blobs and commits. Uses the `sha_tokenizer` and the `lowercase` and `asciifolding` filters.
+
+Please see the `sha_tokenizer` explanation later below for an example.
+
+#### `code_analyzer`
+Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the filters: `code`, `edgeNGram_filter`, `lowercase`, and `asciifolding`
+
+The `whitespace` tokenizer was selected in order to have more control over how tokens are split. For example the string `Foo::bar(4)` needs to generate tokens like `Foo` and `bar(4)` in order to be properly searched.
+
+Please see the `code` filter for an explanation on how tokens are split.
+
+#### `code_search_analyzer`
+Not directly used for indexing, but rather used to transform a search input. Uses the `whitespace` tokenizer and the `lowercase` and `asciifolding` filters.
+
+### Tokenizers
+#### `sha_tokenizer`
+This is a custom tokenizer that uses the [`edgeNGram` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenizer.html) to allow SHAs to be searcheable by any sub-set of it (minimum of 5 chars).
+
+example:
+
+`240c29dc7e` becomes:
+- `240c2`
+- `240c29`
+- `240c29d`
+- `240c29dc`
+- `240c29dc7`
+- `240c29dc7e`
+
+#### `path_tokenizer`
+This is a custom tokenizer that uses the [`path_hierarchy` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pathhierarchy-tokenizer.html) with `reverse: true` in order to allow searches to find paths no matter how much or how little of the path is given as input.
+
+example:
+
+`'/some/path/application.js'` becomes:
+- `'/some/path/application.js'`
+- `'some/path/application.js'`
+- `'path/application.js'`
+- `'application.js'`
+
+### Filters
+#### `code`
+Uses a [Pattern Capture token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pattern-capture-tokenfilter.html) to split tokens into more easily searched versions of themselves.
+
+Patterns:
+- `"(\\p{Ll}+|\\p{Lu}\\p{Ll}+|\\p{Lu}+)"`: captures CamelCased and lowedCameCased strings as separate tokens
+- `"(\\d+)"`: extracts digits
+- `"(?=([\\p{Lu}]+[\\p{L}]+))"`: captures CamelCased strings recursively. Ex: `ThisIsATest` => `[ThisIsATest, IsATest, ATest, Test]`
+- `'"((?:\\"|[^"]|\\")*)"'`: captures terms inside quotes, removing the quotes
+- `"'((?:\\'|[^']|\\')*)'"`: same as above, for single-quotes
+- `'\.([^.]+)(?=\.|\s|\Z)'`: separate terms with periods in-between
+- `'\/?([^\/]+)(?=\/|\b)'`: separate path terms `like/this/one`
+
+#### `edgeNGram_filter`
+Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenfilter.html) to allow inputs with only parts of a token to find the token. For example it would turn `glasses` into permutations starting with `gl` and ending with `glasses`, which would allow a search for "`glass`" to find the original token `glasses`
+
+## Gotchas
+
+- Searches can have their own analyzers. Remember to check when editing analyzers
+- `Character` filters (as opposed to token filters) always replace the original character, so they're not a good choice as they can hinder exact searches
+
+## Troubleshooting
+
+### Getting "flood stage disk watermark [95%] exceeded"
+
+You might get an error such as
+
+```
+[2018-10-31T15:54:19,762][WARN ][o.e.c.r.a.DiskThresholdMonitor] [pval5Ct]
+ flood stage disk watermark [95%] exceeded on
+ [pval5Ct7SieH90t5MykM5w][pval5Ct][/usr/local/var/lib/elasticsearch/nodes/0] free: 56.2gb[3%],
+ all indices on this node will be marked read-only
+```
+
+This is because you've exceeded the disk space threshold - it thinks you don't have enough disk space left, based on the default 95% threshold.
+
+In addition, the `read_only_allow_delete` setting will be set to `true`. It will block indexing, `forcemerge`, etc
+
+```
+curl "http://localhost:9200/gitlab-development/_settings?pretty"
+```
+
+Add this to your `elasticsearch.yml` file:
+
+```
+# turn off the disk allocator
+cluster.routing.allocation.disk.threshold_enabled: false
+```
+
+_or_
+
+```
+# set your own limits
+cluster.routing.allocation.disk.threshold_enabled: true
+cluster.routing.allocation.disk.watermark.flood_stage: 5gb # ES 6.x only
+cluster.routing.allocation.disk.watermark.low: 15gb
+cluster.routing.allocation.disk.watermark.high: 10gb
+```
+
+Restart ElasticSearch, and the `read_only_allow_delete` will clear on it's own.
+
+_from "Disk-based Shard Allocation | Elasticsearch Reference" [5.6](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/disk-allocator.html#disk-allocator) and [6.x](https://www.elastic.co/guide/en/elasticsearch/reference/6.x/disk-allocator.html)_
diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md
index aebb22caa15..c67389b169e 100644
--- a/doc/development/fe_guide/architecture.md
+++ b/doc/development/fe_guide/architecture.md
@@ -11,12 +11,9 @@ Architectural decisions should be accessible to everyone, so please document
them in the relevant Merge Request discussion or by updating our documentation
when appropriate.
-You can find the Frontend Architecture experts on the [team page][team-page].
+You can find the Frontend Architecture experts on the [team page](https://about.gitlab.com/team).
## Examples
You can find documentation about the desired architecture for a new feature
-built with Vue.js [here][vue-section].
-
-[team-page]: https://about.gitlab.com/team
-[vue-section]: vue.md#frontend.html#how-to-build-a-new-feature-with-vue-js
+built with Vue.js [here](vue.md).
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
new file mode 100644
index 00000000000..6ab3fa4acf3
--- /dev/null
+++ b/doc/development/fe_guide/event_tracking.md
@@ -0,0 +1,91 @@
+# Event Tracking
+
+We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events (available in GitLab [Enterprise Edition](https://about.gitlab.com/pricing/) only).
+
+## Generic tracking function
+
+In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events.
+
+The generic tracking function can be imported in EE-specific JS files as follows:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+```
+
+This gives the user access to the `trackEvent` method, which takes the following parameters:
+
+| parameter | type | description | required |
+| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true |
+| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true |
+| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false |
+
+Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation).
+
+### Tracking in `.js` and `.vue` files
+
+The most simple use case is to add tracking programmatically to an event of interest in Javascript.
+
+The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+
+trackEvent('dashboard:projects:index', 'click_button', {
+ label: 'create_from_template',
+ property: 'template_preview',
+ value: 'rails',
+});
+```
+
+### Tracking in HAML templates
+
+Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task.
+
+There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping.
+
+Below is an example of `data-track-*` attributes assigned to a button in HAML:
+
+```ruby
+%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
+```
+
+By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
+
+```javascript
+import Stats from 'ee/stats';
+
+document.addEventListener('DOMContentLoaded', () => {
+ Stats.bindTrackableContainer('.my-container', 'category');
+});
+```
+
+The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead.
+
+Below is a list of supported `data-track-*` attributes:
+
+| attribute | description | required |
+| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `data-track-label` | The `label` in `trackEvent` | true |
+| `data-track-event` | The `eventName` in `trackEvent` | true |
+| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false |
+| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false |
+
+Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases.
+
+## Testing
+
+Snowplow can be enabled by navigating to:
+
+- **Admin area > Settings > Integrations** in the UI.
+- `admin/application_settings/integrations` in your browser.
+
+The following configuration is required:
+
+| Name | Value |
+| ------------- | ------------------------- |
+| Collector | `snowplow.trx.gitlab.net` |
+| Site ID | `gitlab` |
+| Cookie domain | `.gitlab.com` |
+
+Now the implemented tracking events can be inspected locally by looking at the network panel of the browser's development tools.
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
new file mode 100644
index 00000000000..e4225f2bc39
--- /dev/null
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -0,0 +1,41 @@
+# Frontend FAQ
+
+## Rules of Frontend FAQ
+
+1. **You talk about Frontend FAQ.**
+ Please share links to it whenever applicable, so more eyes catch when content
+ gets outdated.
+2. **Keep it short and simple.**
+ Whenever an answer needs more than two sentences it does not belong here.
+3. **Provide background when possible.**
+ Linking to relevant source code, issue / epic, or other documentation helps
+ to understand the answer.
+4. **If you see something, do something.**
+ Please remove or update any content that is outdated as soon as you see it.
+
+## FAQ
+
+### How do I find the Rails route for a page?
+
+The easiest way is to type the following in the browser while on the page in
+question:
+
+```javascript
+document.body.dataset.page
+```
+
+Find here the [source code setting the attribute](https://gitlab.com/gitlab-org/gitlab-ce/blob/cc5095edfce2b4d4083a4fb1cdc7c0a1898b9921/app/views/layouts/application.html.haml#L4).
+
+### `modal_copy_button` vs `clipboard_button`
+
+The `clipboard_button` uses the `copy_to_clipboard.js` behaviour, which is
+initialized on page load, so if there are vue-based clipboard buttons that
+don't exist at page load (such as ones in a `GlModal`), they do not have the
+click handlers associated with the clipboard package.
+
+`modal_copy_button` was added that manages an instance of the
+[`clipboard` plugin](https://www.npmjs.com/package/clipboard) specific to
+the instance of that component, which means that clipboard events are
+bound on mounting and destroyed when the button is, mitigating the above
+issue. It also has bindings to a particular container or modal ID
+available, to work with the focus trap created by our GlModal.
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 3290f29530a..8e06aa5d173 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -43,10 +43,12 @@ new Vue({
Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
-### Local state with `apollo-link-state`
+### Local state with Apollo
+
+It is possible to manage an application state with Apollo by passing
+in a resolvers object when creating the default client. The default state can be set by writing
+to the cache after setting up the default client.
-It is possible to use our Apollo setup with [apollo-link-state][apollo-link-state] by passing
-in the client state object when creating the default client.
```javascript
import Vue from 'vue';
@@ -54,18 +56,28 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
+const defaultClient = createDefaultClient({
+ Query: {
+ ...
+ },
+ Mutations: {
+ ...
+ },
+});
+
+defaultClient.cache.writeData({
+ data: {
+ isLoading: true,
+ },
+});
+
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient({
- defaults: {
- testing: true,
- },
- resolvers: {
- ...
- },
- }),
+ defaultClient,
});
```
+Read more about local state management with Apollo in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/local-state.html#local-state).
+
### Testing
With [Vue test utils][vue-test-utils] it is easy to quickly test components that
@@ -82,6 +94,8 @@ it('tests apollo component', () => {
});
```
+Another possible way is testing queries with mocked GraphQL schema. Read more about this way in [Vue Apollo testing documentation](https://vue-apollo.netlify.com/guide/testing.html#tests-with-mocked-graqhql-schema)
+
## Usage outside of Vue
It is also possible to use GraphQL outside of Vue by directly importing
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 86b8972a69e..36d5e4ab96b 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -1,8 +1,5 @@
# Frontend Development Guidelines
-> **Notice:**
-> We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
-
This document describes various guidelines to ensure consistency and quality
across GitLab's frontend team.
@@ -12,15 +9,6 @@ GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] and also a J
Be wary of [the limitations that come with using Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with
modern ECMAScript standards supported through [Babel][babel] and ES module support through [webpack][webpack].
-### Javascript development
-
-[Vue.js][vue] is used for particularly advanced, dynamic elements and based on previous iterations [jQuery][jquery] is used in lot of places through the application's JavaScript.
-
-We also use [Axios][axios] to handle all of our network requests.
-
-We also utilize [webpack][webpack] to handle the bundling, minification, and
-compression of our assets.
-
Working with our frontend assets requires Node (v8.10.0 or greater) and Yarn
(v1.10.0 or greater). You can find information on how to install these on our
[installation guide][install].
@@ -31,6 +19,14 @@ For our currently-supported browsers, see our [requirements][requirements].
---
+## Initiatives
+
+Current high-level frontend goals are listed on [Frontend Epics](https://gitlab.com/groups/gitlab-org/-/epics?label_name%5B%5D=frontend).
+
+## [Principles](principles.md)
+
+High-level guidelines for contributing to GitLab.
+
## [Development Process](development_process.md)
How we plan and execute the work on the frontend.
@@ -73,6 +69,14 @@ How we use SVG for our Icons and Illustrations.
How we use UI components.
+## [Event Tracking](event_tracking.md)
+
+How we use Snowplow to track custom events.
+
+## Frontend FAQ
+
+Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information.
+
---
## Style Guides
@@ -124,14 +128,3 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
[scss-lint]: https://github.com/brigade/scss-lint
[install]: ../../install/installation.md#4-node
[requirements]: ../../install/requirements.md#supported-web-browsers
-
----
-
-## [DropLab](droplab/droplab.md)
-
-Our internal `DropLab` dropdown library.
-
-- [DropLab](droplab/droplab.md)
-- [Ajax plugin](droplab/plugins/ajax.md)
-- [Filter plugin](droplab/plugins/filter.md)
-- [InputSetter plugin](droplab/plugins/input_setter.md)
diff --git a/doc/development/fe_guide/principles.md b/doc/development/fe_guide/principles.md
new file mode 100644
index 00000000000..6fb3456222f
--- /dev/null
+++ b/doc/development/fe_guide/principles.md
@@ -0,0 +1,15 @@
+# Principles
+
+These principles will ensure that your frontend contribution starts off in the right direction.
+
+## Discuss architecture before implementation
+
+Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
+
+## Be consistent
+
+There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
+
+## Improve code [iteratively](https://about.gitlab.com/handbook/values/#iteration)
+
+Whenever you see with existing code that does not follow our current style guide, update it proactively. You don't need to fix everything, but each merge request should iteratively improve our codebase, and reduce technical debt where possible.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 060cd8baf7f..b50159c2b75 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -95,6 +95,7 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
#### Modules, Imports, and Exports
1. Use ES module syntax to import modules
+
```javascript
// bad
const SomeClass = require('some_class');
@@ -168,6 +169,7 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
Do not use them anymore and feel free to remove them when refactoring legacy code.
1. Avoid adding to the global namespace.
+
```javascript
// bad
window.MyClass = class { /* ... */ };
@@ -176,7 +178,8 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
export default class MyClass { /* ... */ }
```
-1. Side effects are forbidden in any script which contains exports
+1. Side effects are forbidden in any script which contains export
+
```javascript
// bad
export default class MyClass { /* ... */ }
@@ -449,6 +452,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
#### Props
1. Props should be declared as an object
+
```javascript
// bad
props: ['foo']
@@ -544,14 +548,24 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
<component @click="eventHandler"/>
```
-1. Shorthand `:` is preferable over `v-bind`
+2. Shorthand `:` is preferable over `v-bind`
```javascript
// bad
<component v-bind:class="btn"/>
// good
- <component :class="btsn"/>
+ <component :class="btn"/>
+ ```
+
+3. Shorthand `#` is preferable over `v-slot`
+
+ ```javascript
+ // bad
+ <template v-slot:header></template>
+
+ // good
+ <template #header></template>
```
#### Closing tags
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index b09243598d5..b25dce65ffe 100644
--- a/doc/development/fe_guide/style_guide_scss.md
+++ b/doc/development/fe_guide/style_guide_scss.md
@@ -5,6 +5,49 @@ easy to maintain, and performant for the end-user.
## Rules
+### Utility Classes
+As part of the effort for [cleaning up our CSS and moving our components into GitLab-UI](https://gitlab.com/groups/gitlab-org/-/epics/950)
+led by the [GitLab UI WG](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/20623) we prefer the use of utility classes over adding new CSS. However, complex CSS can be addressed by adding component classes.
+
+#### Where are utility classes defined?
+
+- [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/)
+- [`common.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/framework/common.scss) (old)
+- [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/utilities.scss) (new)
+
+#### Where should I put new utility classes?
+
+New utility classes should be added to [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/utilities.scss). Existing classes include:
+
+| Name | Pattern | Example |
+|------|---------|---------|
+| Background color | `.bg-{variant}-{shade}` | `.bg-warning-400` |
+| Text color | `.text-{variant}-{shade}` | `.text-success-500` |
+| Font size | `.text-{size}` | `.text-2` |
+
+- `{variant}` is one of 'primary', 'secondary', 'success', 'warning', 'error'
+- `{shade}` is on of the shades listed on [colors](https://design.gitlab.com/foundations/colors/)
+- `{size}` is a number from 1-6 from our [Type scale](https://design.gitlab.com/foundations/typography)
+
+#### When should I create component classes?
+
+We recommend a "utility-first" approach.
+
+1. Start with utility classes.
+2. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it.
+
+This encourages an organic growth of component classes and prevents the creation of one-off unreusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (e.g. `.button`, `.alert`, `.card`) rather than domain-centered (e.g. `.security-report-widget`, `.commit-header-icon`).
+
+Examples of component classes that were created using "utility-first" include:
+
+- [`.circle-icon-container`](https://gitlab.com/gitlab-org/gitlab-ce/blob/579fa8b8ec7eb38d40c96521f517c9dab8c3b97a/app/assets/stylesheets/framework/icons.scss#L85)
+- [`.d-flex-center`](https://gitlab.com/gitlab-org/gitlab-ce/blob/900083d89cd6af391d26ab7922b3f64fa2839bef/app/assets/stylesheets/framework/common.scss#L425)
+
+Inspiration:
+
+- https://tailwindcss.com/docs/utility-first
+- https://tailwindcss.com/docs/extracting-components
+
### Naming
Filenames should use `snake_case`.
diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md
index 98e499b8c0f..b23e37d1eef 100644
--- a/doc/development/fe_guide/testing.md
+++ b/doc/development/fe_guide/testing.md
@@ -1 +1,5 @@
-This document was moved to [../testing_guide/frontend_testing.md](../testing_guide/frontend_testing.md).
+---
+redirect_to: '../testing_guide/frontend_testing.md'
+---
+
+This document was moved to [another location](../testing_guide/frontend_testing.md).
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 3cd70bd63fa..8c6a73c6824 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -38,7 +38,7 @@ _For consistency purposes, we recommend you to follow the same structure._
Let's look into each of them:
-### A `index.js` file
+### An `index.js` file
This is the index file of your new feature. This is where the root Vue instance
of the new feature should be.
@@ -46,7 +46,7 @@ of the new feature should be.
The Store and the Service should be imported and initialized in this file and
provided as a prop to the main component.
-Don't forget to follow [these steps][page_specific_javascript].
+Be sure to read about [page-specific JavaScript][page_specific_javascript].
### Bootstrapping Gotchas
#### Providing data from HAML to JavaScript
@@ -120,9 +120,16 @@ You can read more about components in Vue.js site, [Component System][component-
#### Vuex
Check this [page](vuex.md) for more details.
+### Mixing Vue and jQuery
+
+- Mixing Vue and jQuery is not recommended.
+- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][https://vuejs.org/v2/examples/select2.html].
+- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
+- It is not recommended to add new jQuery events for Vue to interact with jQuery.
+
## Style guide
-Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js)
+Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs)
for best practices while writing your Vue components and templates.
## Testing Vue Components
@@ -132,7 +139,7 @@ Each Vue component has a unique output. This output is always present in the ren
Although we can test each method of a Vue component individually, our goal must be to test the output
of the render/template function, which represents the state at all times.
-Make use of the [axios mock adapter](axios.md#mock-axios-response-on-tests) to mock data returned.
+Make use of the [axios mock adapter](axios.md#mock-axios-response-in-tests) to mock data returned.
Here's how we would test the Todo App above:
@@ -227,13 +234,13 @@ One should apply to be a Vue.js expert by opening an MR when the Merge Request's
- Deep understanding of Vue and Vuex reactivy
- Vue and Vuex code are structured according to both official and our guidelines
- Full understanding of testing a Vue and Vuex application
-- Vuex code follows the [documented pattern](./vuex.md#actions-pattern-request-and-receive-namespaces)
+- Vuex code follows the [documented pattern](vuex.md#actions-pattern-request-and-receive-namespaces)
- Knowledge about the existing Vue and Vuex applications and existing reusable components
[vue-docs]: http://vuejs.org/guide/index.html
[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
-[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
+[page_specific_javascript]: ./performance.md#page-specific-javascript
[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 4b60ec80cb8..bf248b7f8af 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -52,7 +52,7 @@ The first thing you should do before writing any code is to design the state.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
```javascript
- export default {
+ export default () => ({
endpoint: null,
isLoading: false,
@@ -62,7 +62,7 @@ Often we need to provide data from haml to our Vue application. Let's store it i
errorAddingUser: false,
users: [],
- };
+ });
```
#### Access `state` properties
@@ -83,7 +83,7 @@ In this file, we will write the actions that will call the respective mutations:
export const requestUsers = ({ commit }) => commit(types.REQUEST_USERS);
export const receiveUsersSuccess = ({ commit }, data) => commit(types.RECEIVE_USERS_SUCCESS, data);
- export const receiveUsersError = ({ commit }, error) => commit(types.REQUEST_USERS_ERROR, error);
+ export const receiveUsersError = ({ commit }, error) => commit(types.RECEIVE_USERS_ERROR, error);
export const fetchUsers = ({ state, dispatch }) => {
dispatch('requestUsers');
@@ -175,7 +175,7 @@ Remember that actions only describe that something happened, they don't describe
state.users = data;
state.isLoading = false;
},
- [types.REQUEST_USERS_ERROR](state, error) {
+ [types.RECEIVE_USERS_ERROR](state, error) {
state.isLoading = false;
},
[types.REQUEST_ADD_USER](state, user) {
@@ -186,7 +186,7 @@ Remember that actions only describe that something happened, they don't describe
state.users.push(user);
},
[types.REQUEST_ADD_USER_ERROR](state, error) {
- state.isAddingUser = true;
+ state.isAddingUser = false;
state.errorAddingUser = error;
},
};
@@ -231,7 +231,7 @@ The store should be included in the main component of your application:
```javascript
// app.vue
- import store from 'store'; // it will include the index.js file
+ import store from './store'; // it will include the index.js file
export default {
name: 'application',
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 67356a12eba..13f0c5cc33e 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -20,14 +20,15 @@ dynamic (querying the DB etc.).
Once defined in `lib/feature.rb`, you will be able to activate a
feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
-For GitLab.com, team members have access to feature flags through chatops. Only
+For GitLab.com, [team members have access to feature flags through Chatops](chatops_on_gitlabcom.md). Only
percentage gates are supported at this time. Setting a feature to be used 50% of
the time, you should execute `/chatops run feature set my_feature_flag 50`.
## Feature flags for user applications
-GitLab does not yet support the use of feature flags in deployed user applications.
-You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
+This document only covers feature flags used in the development of GitLab
+itself. Feature flags in deployed user applications can be found at
+[Feature Flags](../user/project/operations/feature_flags.md)
## Developing with feature flags
@@ -107,11 +108,11 @@ so we make sure behavior under feature flag doesn't go untested in some non-spec
contexts.
Whenever a feature flag is present, make sure to test _both_ states of the
-feature flag. You can stub a feature flag as follows:
+feature flag.
-```ruby
-stub_feature_flags(my_feature_flag: false)
-```
+See the
+[testing guide](testing_guide/best_practices.md#feature-flags-in-tests)
+for information and examples on how to stub feature flags in tests.
## Enabling a feature flag (in development)
diff --git a/doc/development/geo.md b/doc/development/geo.md
new file mode 100644
index 00000000000..6e59fab34c7
--- /dev/null
+++ b/doc/development/geo.md
@@ -0,0 +1,529 @@
+# Geo (development) **[PREMIUM ONLY]**
+
+Geo connects GitLab instances together. One GitLab instance is
+designated as a **primary** node and can be run with multiple
+**secondary** nodes. Geo orchestrates quite a few components that can be seen on
+the diagram below and are described in more detail within this document.
+
+![Geo Architecture Diagram](../administration/geo/replication/img/geo_architecture.png)
+
+## Replication layer
+
+Geo handles replication for different components:
+
+- [Database](#database-replication): includes the entire application, except cache and jobs.
+- [Git repositories](#repository-replication): includes both projects and wikis.
+- [Uploaded blobs](#uploads-replication): includes anything from images attached on issues
+to raw logs and assets from CI.
+
+With the exception of the Database replication, on a *secondary* node, everything is coordinated
+by the [Geo Log Cursor](#geo-log-cursor).
+
+### Geo Log Cursor daemon
+
+The [Geo Log Cursor daemon](#geo-log-cursor-daemon) is a separate process running on
+each **secondary** node. It monitors the [Geo Event Log](#geo-event-log)
+for new events and creates background jobs for each specific event type.
+
+For example when a repository is updated, the Geo **primary** node creates
+a Geo event with an associated repository updated event. The Geo Log Cursor daemon
+picks the event up and schedules a `Geo::ProjectSyncWorker` job which will
+use the `Geo::RepositorySyncService` and `Geo::WikiSyncService` classes
+to update the repository and the wiki respectively.
+
+The Geo Log Cursor daemon can operate in High Availability mode automatically.
+The daemon will try to acquire a lock from time to time and once acquired, it
+will behave as the *active* daemon.
+
+Any additional running daemons on the same node, will be in standby
+mode, ready to resume work if the *active* daemon releases its lock.
+
+We use the [`ExclusiveLease`](https://www.rubydoc.info/github/gitlabhq/gitlabhq/Gitlab/ExclusiveLease) lock type with a small TTL, that is renewed at every
+pooling cycle. That allows us to implement this global lock with a timeout.
+
+At the end of the pooling cycle, if the daemon can't renew and/or reacquire
+the lock, it switches to standby mode.
+
+### Database replication
+
+Geo uses [streaming replication](#streaming-replication) to replicate
+the database from the **primary** to the **secondary** nodes. This
+replication gives the **secondary** nodes access to all the data saved
+in the database. So users can log in on the **secondary** and read all
+the issues, merge requests, etc. on the **secondary** node.
+
+### Repository replication
+
+Geo also replicates repositories. Each **secondary** node keeps track of
+the state of every repository in the [tracking database](#tracking-database).
+
+There are a few ways a repository gets replicated by the:
+
+- [Repository Sync worker](#repository-sync-worker).
+- [Geo Log Cursor](#geo-log-cursor).
+
+#### Project Registry
+
+The `Geo::ProjectRegistry` class defines the model used to track the
+state of repository replication. For each project in the main
+database, one record in the tracking database is kept.
+
+It records the following about repositories:
+
+- The last time they were synced.
+- The last time they were successfully synced.
+- If they need to be resynced.
+- When a retry should be attempted.
+- The number of retries.
+- If and when they were verified.
+
+It also stores these attributes for project wikis in dedicated columns.
+
+#### Repository Sync worker
+
+The `Geo::RepositorySyncWorker` class runs periodically in the
+background and it searches the `Geo::ProjectRegistry` model for
+projects that need updating. Those projects can be:
+
+- Unsynced: Projects that have never been synced on the **secondary**
+ node and so do not exist yet.
+- Updated recently: Projects that have a `last_repository_updated_at`
+ timestamp that is more recent than the `last_repository_successful_sync_at`
+ timestamp in the `Geo::ProjectRegistry` model.
+- Manual: The admin can manually flag a repository to resync in the
+ [Geo admin panel](../user/admin_area/geo_nodes.md).
+
+When we fail to fetch a repository on the secondary `RETRIES_BEFORE_REDOWNLOAD`
+times, Geo does a so-called _redownload_. It will do a clean clone
+into the `@geo-temporary` directory in the root of the storage. When
+it's successful, we replace the main repo with the newly cloned one.
+
+### Uploads replication
+
+File uploads are also being replicated to the **secondary** node. To
+track the state of syncing, the `Geo::FileRegistry` model is used.
+
+#### File Registry
+
+Similar to the [Project Registry](#project-registry), there is a
+`Geo::FileRegistry` model that tracks the synced uploads.
+
+CI Job Artifacts are synced in a similar way as uploads or LFS
+objects, but they are tracked by `Geo::JobArtifactRegistry` model.
+
+#### File Download Dispatch worker
+
+Also similar to the [Repository Sync worker](#repository-sync-worker),
+there is a `Geo::FileDownloadDispatchWorker` class that is run
+periodically to sync all uploads that aren't synced to the Geo
+**secondary** node yet.
+
+Files are copied via HTTP(s) and initiated via the
+`/api/v4/geo/transfers/:type/:id` endpoint,
+e.g. `/api/v4/geo/transfers/lfs/123`.
+
+## Authentication
+
+To authenticate file transfers, each `GeoNode` record has two fields:
+
+- A public access key (`access_key` field).
+- A secret access key (`secret_access_key` field).
+
+The **secondary** node authenticates itself via a [JWT request](https://jwt.io/).
+When the **secondary** node wishes to download a file, it sends an
+HTTP request with the `Authorization` header:
+
+```
+Authorization: GL-Geo <access_key>:<JWT payload>
+```
+
+The **primary** node uses the `access_key` field to look up the
+corresponding **secondary** node and decrypts the JWT payload,
+which contains additional information to identify the file
+request. This ensures that the **secondary** node downloads the right
+file for the right database ID. For example, for an LFS object, the
+request must also include the SHA256 sum of the file. An example JWT
+payload looks like:
+
+```
+{ "data": { sha256: "31806bb23580caab78040f8c45d329f5016b0115" }, iat: "1234567890" }
+```
+
+If the requested file matches the requested SHA256 sum, then the Geo
+**primary** node sends data via the [X-Sendfile](https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/)
+feature, which allows NGINX to handle the file transfer without tying
+up Rails or Workhorse.
+
+NOTE: **Note:**
+JWT requires synchronized clocks between the machines
+involved, otherwise it may fail with an encryption error.
+
+## Git Push to Geo secondary
+
+The Git Push Proxy exists as a functionality built inside the `gitlab-shell` component.
+It is active on a **secondary** node only. It allows the user that has cloned a repository
+from the secondary node to push to the same URL.
+
+Git `push` requests directed to a **secondary** node will be sent over to the **primary** node,
+while `pull` requests will continue to be served by the **secondary** node for maximum efficiency.
+
+HTTPS and SSH requests are handled differently:
+
+- With HTTPS, we will give the user a `HTTP 302 Redirect` pointing to the project on the **primary** node.
+The git client is wise enough to understand that status code and process the redirection.
+- With SSH, because there is no equivalent way to perform a redirect, we have to proxy the request.
+This is done inside [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell), by first translating the request
+to the HTTP protocol, and then proxying it to the **primary** node.
+
+The [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell) daemon knows when to proxy based on the response
+from `/api/v4/allowed`. A special `HTTP 300` status code is returned and we execute a "custom action",
+specified in the response body. The response contains additional data that allows the proxied `push` operation
+to happen on the **primary** node.
+
+## Using the Tracking Database
+
+Along with the main database that is replicated, a Geo **secondary**
+node has its own separate [Tracking database](#tracking-database).
+
+The tracking database contains the state of the **secondary** node.
+
+Any database migration that needs to be run as part of an upgrade
+needs to be applied to the tracking database on each **secondary** node.
+
+### Configuration
+
+The database configuration is set in [`config/database_geo.yml`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/config/database_geo.yml.postgresql).
+The directory [`ee/db/geo`](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/db/geo)
+contains the schema and migrations for this database.
+
+To write a migration for the database, use the `GeoMigrationGenerator`:
+
+```
+rails g geo_migration [args] [options]
+```
+
+To migrate the tracking database, run:
+
+```
+bundle exec rake geo:db:migrate
+```
+
+### Foreign Data Wrapper
+
+> Introduced in GitLab 10.1.
+
+Foreign Data Wrapper ([FDW](#fdw)) is used by the [Geo Log Cursor](#geo-log-cursor) and improves
+the performance of many synchronization operations.
+
+FDW is a PostgreSQL extension ([`postgres_fdw`](https://www.postgresql.org/docs/current/postgres-fdw.html)) that is enabled within
+the Geo Tracking Database (on a **secondary** node), which allows it
+to connect to the readonly database replica and perform queries and filter
+data from both instances.
+
+While FDW is available in older versions of PostgreSQL, we needed to
+raise the minimum required version to 9.6 as this includes many
+performance improvements to the FDW implementation.
+
+This persistent connection is configured as an FDW server
+named `gitlab_secondary`. This configuration exists within the database's user
+context only. To access the `gitlab_secondary`, GitLab needs to use the
+same database user that had previously been configured.
+
+The Geo Tracking Database accesses the readonly database replica via FDW as a regular user,
+limited by its own restrictions. The credentials are configured as a
+`USER MAPPING` associated with the `SERVER` mapped previously
+(`gitlab_secondary`).
+
+FDW configuration and credentials definition are managed automatically by the
+Omnibus GitLab `gitlab-ctl reconfigure` command.
+
+#### Refeshing the Foreign Tables
+
+Whenever a new Geo node is configured or the database schema changes on the
+**primary** node, you must refresh the foreign tables on the **secondary** node
+by running the following:
+
+```sh
+bundle exec rake geo:db:refresh_foreign_tables
+```
+
+Failure to do this will prevent the **secondary** node from
+functioning properly. The **secondary** node will generate error
+messages, as the following PostgreSQL error:
+
+```
+ERROR: relation "gitlab_secondary.ci_job_artifacts" does not exist at character 323
+STATEMENT: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ WHERE a.attrelid = '"gitlab_secondary"."ci_job_artifacts"'::regclass
+ AND a.attnum > 0 AND NOT a.attisdropped
+ ORDER BY a.attnum
+```
+
+#### Accessing data from a Foreign Table
+
+At the SQL level, all you have to do is `SELECT` data from `gitlab_secondary.*`.
+
+Here's an example of how to access all projects from the Geo Tracking Database's FDW:
+
+```sql
+SELECT * FROM gitlab_secondary.projects;
+```
+
+As a more real-world example, this is how you filter for unarchived projects
+on the Tracking Database:
+
+```sql
+SELECT project_registry.*
+ FROM project_registry
+ JOIN gitlab_secondary.projects
+ ON (project_registry.project_id = gitlab_secondary.projects.id
+ AND gitlab_secondary.projects.archived IS FALSE)
+```
+
+At the ActiveRecord level, we have additional Models that represent the
+foreign tables. They must be mapped in a slightly different way, and they are read-only.
+
+Check the existing FDW models in `ee/app/models/geo/fdw` for reference.
+
+From a developer's perspective, it's no different than creating a model that
+represents a Database View.
+
+With the examples above, you can access the projects with:
+
+```ruby
+Geo::Fdw::Project.all
+```
+
+and to access the `ProjectRegistry` filtering by unarchived projects:
+
+```ruby
+# We have to use Arel here:
+project_registry_table = Geo::ProjectRegistry.arel_table
+fdw_project_table = Geo::Fdw::Project.arel_table
+
+project_registry_table.join(fdw_project_table)
+ .on(project_registry_table[:project_id].eq(fdw_project_table[:id]))
+ .where((fdw_project_table[:archived]).eq(true)) # if you append `.to_sql` you can check generated query
+```
+
+## Finders
+
+Geo uses [Finders](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/app/finders),
+which are classes take care of the heavy lifting of looking up
+projects/attachments/etc. in the tracking database and main database.
+
+### Finders Performance
+
+The Finders need to compare data from the main database with data in
+the tracking database. For example, counting the number of synced
+projects normally involves retrieving the project IDs from one
+database and checking their state in the other database. This is slow
+and requires a lot of memory.
+
+To overcome this, the Finders use [FDW](#fdw), or Foreign Data
+Wrappers. This allows a regular `JOIN` between the main database and
+the tracking database.
+
+## Redis
+
+Redis on the **secondary** node works the same as on the **primary**
+node. It is used for caching, storing sessions, and other persistent
+data.
+
+Redis data replication between **primary** and **secondary** node is
+not used, so sessions etc. aren't shared between nodes.
+
+## Object Storage
+
+GitLab can optionally use Object Storage to store data it would
+otherwise store on disk. These things can be:
+
+ - LFS Objects
+ - CI Job Artifacts
+ - Uploads
+
+Objects that are stored in object storage, are not handled by Geo. Geo
+ignores items in object storage. Either:
+
+- The object storage layer should take care of its own geographical
+ replication.
+- All secondary nodes should use the same storage node.
+
+## Verification
+
+### Repository verification
+
+Repositories are verified with a checksum.
+
+The **primary** node calculates a checksum on the repository. It
+basically hashes all Git refs together and stores that hash in the
+`project_repository_states` table of the database.
+
+The **secondary** node does the same to calculate the hash of its
+clone, and compares the hash with the value the **primary** node
+calculated. If there is a mismatch, Geo will mark this as a mismatch
+and the administrator can see this in the [Geo admin panel](../user/admin_area/geo_nodes.md).
+
+## Glossary
+
+### Primary node
+
+A **primary** node is the single node in a Geo setup that read-write
+capabilities. It's the single source of truth and the Geo
+**secondary** nodes replicate their data from there.
+
+In a Geo setup, there can only be one **primary** node. All
+**secondary** nodes connect to that **primary**.
+
+### Secondary node
+
+A **secondary** node is a read-only replica of the **primary** node
+running in a different geographical location.
+
+### Streaming replication
+
+Geo depends on the streaming replication feature of PostgreSQL. It
+completely replicates the database data and the database schema. The
+database replica is a read-only copy.
+
+Streaming replication depends on the Write Ahead Logs, or WAL. Those
+logs are copied over to the replica and replayed there.
+
+Since streaming replication also replicates the schema, the database
+migration do not need to run on the secondary nodes.
+
+### Tracking database
+
+A database on each Geo **secondary** node that keeps state for the node
+on which it resides. Read more in [Using the Tracking database](#using-the-tracking-database).
+
+### FDW
+
+Foreign Data Wrapper, or FDW, is a feature built-in in PostgreSQL. It
+allows data to be queried from different data sources. In Geo, it's
+used to query data from different PostgreSQL instances.
+
+## Geo Event Log
+
+The Geo **primary** stores events in the `geo_event_log` table. Each
+entry in the log contains a specific type of event. These type of
+events include:
+
+ - Repository Deleted event
+ - Repository Renamed event
+ - Repositories Changed event
+ - Repository Created event
+ - Hashed Storage Migrated event
+ - Lfs Object Deleted event
+ - Hashed Storage Attachments event
+ - Job Artifact Deleted event
+ - Upload Deleted event
+
+### Geo Log Cursor
+
+The process running on the **secondary** node that looks for new
+`Geo::EventLog` rows.
+
+## Code features
+
+### `Gitlab::Geo` utilities
+
+Small utility methods related to Geo go into the
+[`ee/lib/gitlab/geo.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/gitlab/geo.rb)
+file.
+
+Many of these methods are cached using the `RequestStore` class, to
+reduce the performance impact of using the methods throughout the
+codebase.
+
+#### Current node
+
+The class method `.current_node` returns the `GeoNode` record for the
+current node.
+
+We use the `host`, `port`, and `relative_url_root` values from
+`gitlab.yml` and search in the database to identify which node we are
+in (see `GeoNode.current_node`).
+
+#### Primary or secondary
+
+To determine whether the current node is a **primary** node or a
+**secondary** node use the `.primary?` and `.secondary?` class
+methods.
+
+It is possible for these methods to both return `false` on a node when
+the node is not enabled. See [Enablement](#enablement).
+
+#### Geo Database configured?
+
+There is also an additional gotcha when dealing with things that
+happen during initialization time. In a few places, we use the
+`Gitlab::Geo.geo_database_configured?` method to check if the node has
+the tracking database, which only exists on the **secondary**
+node. This overcomes race conditions that could happen during
+bootstrapping of a new node.
+
+#### Enablement
+
+We consider Geo feature enabled when the user has a valid license with the
+feature included, and they have at least one node defined at the Geo Nodes
+screen.
+
+See `Gitlab::Geo.enabled?` and `Gitlab::Geo.license_allows?` methods.
+
+#### Read-only
+
+All Geo **secondary** nodes are read-only.
+
+The general principle of a [read-only database](verifying_database_capabilities.md#read-only-database)
+applies to all Geo **secondary** nodes. So the
+`Gitlab::Database.read_only?` method will always return `true` on a
+**secondary** node.
+
+When some write actions are not allowed because the node is a
+**secondary**, consider adding the `Gitlab::Database.read_only?` or
+`Gitlab::Database.read_write?` guard, instead of `Gitlab::Geo.secondary?`.
+
+The database itself will already be read-only in a replicated setup,
+so we don't need to take any extra step for that.
+
+## History of communication channel
+
+The communication channel has changed since first iteration, you can
+check here historic decisions and why we moved to new implementations.
+
+### Custom code (GitLab 8.6 and earlier)
+
+In GitLab versions before 8.6, custom code is used to handle
+notification from **primary** node to **secondary** nodes by HTTP
+requests.
+
+### System hooks (GitLab 8.7 to 9.5)
+
+Later, it was decided to move away from custom code and begin using
+system hooks. More people were using them, so
+many would benefit from improvements made to this communication layer.
+
+There is a specific **internal** endpoint in our API code (Grape),
+that receives all requests from this System Hooks:
+`/api/v4/geo/receive_events`.
+
+We switch and filter from each event by the `event_name` field.
+
+### Geo Log Cursor (GitLab 10.0 and up)
+
+Since GitLab 10.0, [System Webhooks](#system-hooks-gitlab-87-to-95) are no longer
+used and Geo Log Cursor is used instead. The Log Cursor traverses the
+`Geo::EventLog` rows to see if there are changes since the last time
+the log was checked and will handle repository updates, deletes,
+changes, and renames.
+
+The table is within the replicated database. This has two advantages over the
+old method:
+
+- Replication is synchronous and we preserve the order of events.
+- Replication of the events happen at the same time as the changes in the
+ database.
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
new file mode 100644
index 00000000000..81c5f69c7b8
--- /dev/null
+++ b/doc/development/git_object_deduplication.md
@@ -0,0 +1,261 @@
+# How Git object deduplication works in GitLab
+
+When a GitLab user [forks a project](../workflow/forking_workflow.md),
+GitLab creates a new Project with an associated Git repository that is a
+copy of the original project at the time of the fork. If a large project
+gets forked often, this can lead to a quick increase in Git repository
+storage disk use. To counteract this problem, we are adding Git object
+deduplication for forks to GitLab. In this document, we will describe how
+GitLab implements Git object deduplication.
+
+## Enabling Git object deduplication via feature flags
+
+As of GitLab 11.9, Git object deduplication in GitLab is in beta. In this
+document, you can read about the caveats of enabling the feature. Also,
+note that Git object deduplication is limited to forks of public
+projects on hashed repository storage.
+
+You can enable deduplication globally by setting the `object_pools`
+feature flag to `true`:
+
+``` {.ruby}
+Feature.enable(:object_pools)
+```
+
+Or just for forks of a specific project:
+
+``` {.ruby}
+fork_parent = Project.find(MY_PROJECT_ID)
+Feature.enable(:object_pools, fork_parent)
+```
+
+To check if a project uses Git object deduplication, look in a Rails
+console if `project.pool_repository` is present.
+
+## Pool repositories
+
+### Understanding Git alternates
+
+At the Git level, we achieve deduplication by using [Git
+alternates](https://git-scm.com/docs/gitrepository-layout#gitrepository-layout-objects).
+Git alternates is a mechanism that lets a repository borrow objects from
+another repository on the same machine.
+
+If we want repository A to borrow from repository B, we first write a
+path that resolves to `B.git/objects` in the special file
+`A.git/objects/info/alternates`. This establishes the alternates link.
+Next, we must perform a Git repack in A. After the repack, any objects
+that are duplicated between A and B will get deleted from A. Repository
+A is now no longer self-contained, but it still has its own refs and
+configuration. Objects in A that are not in B will remain in A. For this
+to work, it is of course critical that **no objects ever get deleted from
+B** because A might need them.
+
+### Git alternates in GitLab: pool repositories
+
+GitLab organizes this object borrowing by creating special **pool
+repositories** which are hidden from the user. We then use Git
+alternates to let a collection of project repositories borrow from a
+single pool repository. We call such a collection of project
+repositories a pool. Pools form star-shaped networks of repositories
+that borrow from a single pool, which will resemble (but not be
+identical to) the fork networks that get formed when users fork
+projects.
+
+At the Git level, pool repositories are created and managed using Gitaly
+RPC calls. Just like with normal repositories, the authority on which
+pool repositories exist, and which repositories borrow from them, lies
+at the Rails application level in SQL.
+
+In conclusion, we need three things for effective object deduplication
+across a collection of GitLab project repositories at the Git level:
+
+1. A pool repository must exist.
+2. The participating project repositories must be linked to the pool
+ repository via their respective `objects/info/alternates` files.
+3. The pool repository must contain Git object data common to the
+ participating project repositories.
+
+### Deduplication factor
+
+The effectiveness of Git object deduplication in GitLab depends on the
+amount of overlap between the pool repository and each of its
+participants. As of GitLab 11.9, we have a somewhat optimistic system.
+The only data that will be deduplicated is the data in the source
+project repository at the time the pool repository is created. That is,
+the data in the source project at the time of the first fork *after* the
+deduplication feature has been enabled.
+
+When we enable the object deduplication feature for
+gitlab.com/gitlab-org/gitlab-ce, which is about 1GB at the time of
+writing, all new forks of that project would be 1GB smaller than they
+would have been without Git object deduplication. So even in its current
+optimistic form, we expect Git object deduplication in GitLab to make a
+difference.
+
+However, if a lot of Git objects get added to the project repositories
+in a pool after the pool repository was created these new Git objects
+will currently (GitLab 11.9) not get deduplicated. Over time, the
+deduplication factor of the pool will get worse and worse.
+
+As an extreme example, if we create an empty repository A, and fork that
+to repository B, behind the scenes we get an object pool P with no
+objects in it at all. If we then push 1GB of Git data to A, and push the
+same Git data to B, it will not get deduplicated, because that data was
+not in A at the time P was created.
+
+This also matters in less extreme examples. Consider a pool P with
+source project A and 500 active forks B1, B2,...,B500. Suppose,
+optimistically, that the forks are fully deduplicated at the start of
+our scenario. Now some time passes and 200MB of new Git data gets added
+to project A. Because of the forking workflow, this data makes also its way
+into the forks B1, ..., B500. That means we would now have 100GB of Git
+data sitting around (500 \* 200MB) across the forks, that could have
+been deduplicated. But because of the way we do deduplication this new
+data will not be deduplicated.
+
+> TODO Add periodic maintenance of object pools to prevent gradual loss
+> of deduplication over time.
+> https://gitlab.com/groups/gitlab-org/-/epics/524
+
+## SQL model
+
+As of GitLab 11.8, project repositories in GitLab do not have their own
+SQL table. They are indirectly identified by columns on the `projects`
+table. In other words, the only way to look up a project repository is to
+first look up its project, and then call `project.repository`.
+
+With pool repositories we made a fresh start. These live in their own
+`pool_repositories` SQL table. The relations between these two tables
+are as follows:
+
+- a `Project` belongs to at most one `PoolRepository`
+ (`project.pool_repository`)
+- as an automatic consequence of the above, a `PoolRepository` has
+ many `Project`s
+- a `PoolRepository` has exactly one "source `Project`"
+ (`pool.source_project`)
+
+### Assumptions
+
+- All repositories in a pool must use [hashed
+ storage](../administration/repository_storage_types.md). This is so
+ that we don't have to ever worry about updating paths in
+ `object/info/alternates` files.
+- All repositories in a pool must be on the same Gitaly storage shard.
+ The Git alternates mechanism relies on direct disk access across
+ multiple repositories, and we can only assume direct disk access to
+ be possible within a Gitaly storage shard.
+- All project repositories in a pool must have "Public" visibility in
+ GitLab at the time they join. There are gotchas around visibility of
+ Git objects across alternates links. This restriction is a defense
+ against accidentally leaking private Git data.
+- The only two ways to remove a member project from a pool are (1) to
+ delete the project or (2) to move the project to another Gitaly
+ storage shard.
+
+### Creating pools and pool memberships
+
+- When a pool gets created, it must have a source project. The initial
+ contents of the pool repository are a Git clone of the source
+ project repository.
+- The occasion for creating a pool is when an existing eligible
+ (public, hashed storage, non-forked) GitLab project gets forked and
+ this project does not belong to a pool repository yet. The fork
+ parent project becomes the source project of the new pool, and both
+ the fork parent and the fork child project become members of the new
+ pool.
+- Once project A has become the source project of a pool, all future
+ eligible forks of A will become pool members.
+- If the fork source is itself a fork, the resulting repository will
+ neither join the repository nor will a new pool repository be
+ seeded.
+
+ eg:
+
+ Suppose fork A is part of a pool repository, any forks created off
+ of fork A *will not* be a part of the pool repository that fork A is
+ a part of.
+
+ Suppose B is a fork of A, and A does not belong to an object pool.
+ Now C gets created as a fork of B. C will not be part of a pool
+ repository.
+
+> TODO should forks of forks be deduplicated?
+> https://gitlab.com/gitlab-org/gitaly/issues/1532
+
+### Consequences
+
+- If a normal Project participating in a pool gets moved to another
+ Gitaly storage shard, its "belongs to PoolRepository" relation must
+ be broken. Because of the way moving repositories between shard is
+ implemented, we will automatically get a fresh self-contained copy
+ of the project's repository on the new storage shard.
+- If the source project of a pool gets moved to another Gitaly storage
+ shard or is deleted, we may have to break the "PoolRepository has
+ one source Project" relation?
+
+> TODO What happens, or should happen, if a source project changes
+> visibility, is deleted, or moves to another storage shard?
+> https://gitlab.com/gitlab-org/gitaly/issues/1488
+
+## Consistency between the SQL pool relation and Gitaly
+
+As far as Gitaly is concerned, the SQL pool relations make two types of
+claims about the state of affairs on the Gitaly server: pool repository
+existence, and the existence of an alternates connection between a
+repository and a pool.
+
+### Pool existence
+
+If GitLab thinks a pool repository exists (i.e. it exists according to
+SQL), but it does not on the Gitaly server, then certain RPC calls that
+take the object pool as an argument will fail.
+
+> TODO What happens if SQL says the pool repo exists but Gitaly says it
+> does not? https://gitlab.com/gitlab-org/gitaly/issues/1533
+
+If GitLab thinks a pool does not exist, while it does exist on disk,
+that has no direct consequences on its own. However, if other
+repositories on disk borrow objects from this unknown pool repository
+then we risk data loss, see below.
+
+### Pool relation existence
+
+There are three different things that can go wrong here.
+
+#### 1. SQL says repo A belongs to pool P but Gitaly says A has no alternate objects
+
+In this case, we miss out on disk space savings but all RPC's on A itself
+will function fine. As long as Git can find all its objects, it does not
+matter exactly where those objects are.
+
+#### 2. SQL says repo A belongs to pool P1 but Gitaly says A has alternate objects in pool P2
+
+If we are not careful, this situation can lead to data loss. During some
+operations (repository maintenance), GitLab will try to re-link A to its
+pool P1. If this clobbers the existing link to P2, then A will loose Git
+objects and become invalid.
+
+Also, keep in mind that if GitLab's database got messed up, it may not
+even know that P2 exists.
+
+> TODO Ensure that Gitaly will not clobber existing, unexpected
+> alternates links. https://gitlab.com/gitlab-org/gitaly/issues/1534
+
+#### 3. SQL says repo A does not belong to any pool but Gitaly says A belongs to P
+
+This has the same data loss possibility as scenario 2 above.
+
+## Git object deduplication and GitLab Geo
+
+When a pool repository record is created in SQL on a Geo primary, this
+will eventually trigger an event on the Geo secondary. The Geo secondary
+will then create the pool repository in Gitaly. This leads to an
+"eventually consistent" situation because as each pool participant gets
+synchronized, Geo will eventuall trigger garbage collection in Gitaly on
+the secondary, at which stage Git objects will get deduplicated.
+
+> TODO How do we handle the edge case where at the time the Geo
+> secondary tries to create the pool repository, the source project does
+> not exist? https://gitlab.com/gitlab-org/gitaly/issues/1533
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index d5fc403bf8b..4ef20d5fd74 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -3,10 +3,17 @@
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
Workhorse and GitLab-Shell.
+## Beginner's guide
+
+Start by reading the gitaly repository's
+[Beginner's guide to Gitaly contributions](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/beginners_guide.md).
+It describes how to setup gitaly, the various components of gitaly and what they do, and how to run its test suites.
+
## Developing new Git features
-Starting with GitLab 10.8, all new Git features should be developed in
-Gitaly.
+To read or write Git data, a request has to be made to Gitaly. This means that
+if you're developing a new feature where you need data that's not yet available
+in `lib/gitlab/git` changes have to be made to Gitaly.
> This is a new process that is not clearly defined yet. If you want
to contribute a Git feature and you're getting stuck, reach out to the
@@ -56,6 +63,48 @@ If your test-suite is failing with Gitaly issues, as a first step, try running:
rm -rf tmp/tests/gitaly
```
+During rspec tests, the Gitaly instance will write logs to `gitlab/log/gitaly-test.log`.
+
+## Legacy Rugged code
+
+While Gitaly can handle all Git access, many of GitLab customers still
+run Gitaly atop NFS. The legacy Rugged implementation for Git calls may
+be faster than the Gitaly RPC due to N+1 Gitaly calls and other
+reasons. See [the
+issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/57317) for more
+details.
+
+Until GitLab has eliminated most of these inefficiencies or the use of
+NFS is discontinued for Git data, Rugged implementations of some of the
+most commonly-used RPCs can be enabled via feature flags:
+
+* `rugged_find_commit`
+* `rugged_get_tree_entries`
+* `rugged_tree_entry`
+* `rugged_commit_is_ancestor`
+* `rugged_commit_tree_entry`
+* `rugged_list_commits_by_oid`
+
+A convenience Rake task can be used to enable or disable these flags
+all together. To enable:
+
+```sh
+bundle exec rake gitlab:features:enable_rugged
+```
+
+To disable:
+
+```sh
+bundle exec rake gitlab:features:disable_rugged
+```
+
+Most of this code exists in the `lib/gitlab/git/rugged_impl` directory.
+
+NOTE: **Note:** You should NOT need to add or modify code related to
+Rugged unless explicitly discussed with the [Gitaly
+Team](https://gitlab.com/groups/gl-gitaly/group_members). This code will
+NOT work on GitLab.com or other GitLab instances that do not use NFS.
+
## `TooManyInvocationsError` errors
During development and testing, you may experience `Gitlab::GitalyClient::TooManyInvocationsError` failures.
@@ -147,8 +196,87 @@ GITALY_REPO_URL=https://gitlab+deploy-token-1000:token-here@gitlab.com/nick.thom
To use a custom Gitaly repository in CI, for instance if you want your
GitLab fork to always use your own Gitaly fork, set `GITALY_REPO_URL`
-as a [CI environment variable](../ci/variables/README.md#variables).
+as a [CI environment variable](../ci/variables/README.md#gitlab-cicd-environment-variables).
---
[Return to Development documentation](README.md)
+
+## Wrapping RPCs in Feature Flags
+
+Here are the steps to gate a new feature in Gitaly behind a feature flag.
+
+### Gitaly
+
+1. Create a package scoped flag name:
+
+ ```go
+ var findAllTagsFeatureFlag = "go-find-all-tags"
+ ```
+
+1. Create a switch in the code using the `featureflag` package:
+
+ ```go
+ if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
+ // go implementation
+ } else {
+ // ruby implementation
+ }
+ ```
+
+1. Create prometheus metrics:
+
+ ```go
+ var findAllTagsRequests = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "gitaly_find_all_tags_requests_total",
+ Help: "Counter of go vs ruby implementation of FindAllTags",
+ },
+ []string{"implementation"},
+ )
+ )
+
+ func init() {
+ prometheus.Register(findAllTagsRequests)
+ }
+
+ if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
+ findAllTagsRequests.WithLabelValues("go").Inc()
+ // go implementation
+ } else {
+ findAllTagsRequests.WithLabelValues("ruby").Inc()
+ // ruby impelmentation
+ }
+ ```
+
+1. Set headers in tests:
+
+ ```go
+ import (
+ "google.golang.org/grpc/metadata"
+
+ "gitlab.com/gitlab-org/gitaly/internal/featureflag"
+ )
+
+ //...
+
+ md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"})
+ ctx = metadata.NewOutgoingContext(context.Background(), md)
+
+ c, err = client.FindAllTags(ctx, rpcRequest)
+ require.NoError(t, err)
+ ```
+
+### Gitlab-Rails
+
+1. Add feature flag to `lib/gitlab/gitaly_client.rb` (in gitlab-rails):
+
+ ```ruby
+ SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze
+ ```
+
+1. Test in rails console by setting feature flag:
+
+ ```ruby
+ Feature.enable('gitaly_go-find-all-tags')
+ ```
diff --git a/doc/development/gitlab_architecture_diagram.png b/doc/development/gitlab_architecture_diagram.png
deleted file mode 100644
index 90e27d5462a..00000000000
--- a/doc/development/gitlab_architecture_diagram.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index cdc806a2d31..4dad8815fcb 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -26,7 +26,7 @@ Reviewers and maintainers should pay attention to:
- `defer` functions: ensure the presence when needed, and after `err` check.
- Inject dependencies as parameters.
-- Void structs when marshalling to JSON (generates `null` instead of `[]`).
+- Void structs when marshaling to JSON (generates `null` instead of `[]`).
### Security
@@ -40,7 +40,7 @@ of possible security breaches in our code:
- SQL injections
Remember to run
-[SAST](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html)
+[SAST](../../user/application_security/sast/index.md)
**[ULTIMATE]** on your project (or at least the [gosec
analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/gosec)),
and to follow our [Security
@@ -93,10 +93,10 @@ become available, you will be able to share job templates like this
Dependencies should be kept to the minimum. The introduction of a new
dependency should be argued in the merge request, as per our [Approval
-Guidelines](../code_review.html#approval-guidelines). Both [License
-Management](https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html)
+Guidelines](../code_review.md#approval-guidelines). Both [License
+Management](../../user/project/merge_requests/license_management.md)
**[ULTIMATE]** and [Dependency
-Scanning](https://docs.gitlab.com/ee/user/project/merge_requests/dependency_scanning.html)
+Scanning](../../user/application_security/dependency_scanning/index.md)
**[ULTIMATE]** should be activated on all projects to ensure new dependencies
security status and license compatibility.
@@ -161,12 +161,39 @@ in the code.
### Logging
-The usage of a logging library is strongly recommended for daemons. Even though
-there is a `log` package in the standard library, we generally use
-[logrus](https://github.com/sirupsen/logrus). Its plugin ("hooks") system
+The usage of a logging library is strongly recommended for daemons. Even
+though there is a `log` package in the standard library, we generally use
+[Logrus](https://github.com/sirupsen/logrus). Its plugin ("hooks") system
makes it a powerful logging library, with the ability to add notifiers and
formatters at the logger level directly.
+#### Structured (JSON) logging
+
+Every binary ideally must have structured (JSON) logging in place as it helps
+with searching and filtering the logs. At GitLab we use structured logging in
+JSON format, as all our infrastructure assumes that. When using
+[Logrus](https://github.com/sirupsen/logrus) you can turn on structured
+logging simply by using the build in [JSON
+formatter](https://github.com/sirupsen/logrus#formatters). This follows the
+same logging type we use in our [Ruby
+applications](../logging.md#use-structured-json-logging).
+
+#### How to use Logrus
+
+There are a few guidelines one should follow when using the
+[Logrus](https://github.com/sirupsen/logrus) package:
+
+- When printing an error use
+ [WithError](https://godoc.org/github.com/sirupsen/logrus#WithError). For
+ example, `logrus.WithError(err).Error("Failed to do something")`.
+- Since we use [structured logging](#structured-json-logging) we can log
+ fields in the context of that code path, such as the URI of the request using
+ [`WithField`](https://godoc.org/github.com/sirupsen/logrus#WithField) or
+ [`WithFields`](https://godoc.org/github.com/sirupsen/logrus#WithFields). For
+ example, `logrus.WithField("file", "/app/go).Info("Opening dir")`. If you
+ have to log multiple keys, always use `WithFields` instead of calling
+ `WithField` more than once.
+
### Tracing and Correlation
[LabKit](https://gitlab.com/gitlab-org/labkit) is a place to keep common
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 223585ebb55..9fb8ea542d9 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -174,7 +174,7 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
# => When size == 2: 'There are 2 mice.'
```
- Avoid using `%d` or count variables in sigular strings. This allows more natural translation in some languages.
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
@@ -195,6 +195,7 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
Sometimes you need to add some context to the text that you want to translate
(if the word occurs in a sentence and/or the word is ambiguous).
+Namespaces should be PascalCase.
- In Ruby/HAML:
@@ -211,7 +212,7 @@ Sometimes you need to add some context to the text that you want to translate
```
Note: The namespace should be removed from the translation. See the [translation
-guidelines for more details](./translation.md#namespaced-strings).
+guidelines for more details](translation.md#namespaced-strings).
### Dates / times
@@ -300,7 +301,7 @@ file in. Once the changes are on master, they will be picked up by
[Crowdin](http://translate.gitlab.com) and be presented for translation.
If there are merge conflicts in the `gitlab.pot` file, you can delete the file
-and regenerate it using the same command. Confirm that you are not deleting any strings accidentally by looking over the diff.
+and regenerate it using the same command.
### Validating PO files
@@ -331,7 +332,7 @@ Errors in `locale/zh_HK/gitlab.po`:
Syntax error in msgstr
Syntax error in message_line
There should be only whitespace until the end of line after the double quote character of a message text.
- Parseing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
+ Parsing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
SimplePoParser filtered backtrace: SimplePoParser::ParserError
Errors in `locale/zh_TW/gitlab.po`:
1 pipeline
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
index 2fa7558d30b..eadf1cd74c5 100644
--- a/doc/development/i18n/merging_translations.md
+++ b/doc/development/i18n/merging_translations.md
@@ -32,8 +32,7 @@ clicking `Pause sync` on the [Crowdin integration settings
page](https://translate.gitlab.com/project/gitlab-ee/settings#integration).
When all failures are resolved, the translations need to be double
-checked once more [as discussed in this
-issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/37850).
+checked once more as discussed in [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-ce/issues/37850`.
## Merging translations
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 6054873cb46..35c5b155594 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -13,18 +13,21 @@ are very appreciative of the work done by translators and proofreaders!
- Lyubomir Vasilev - [Crowdin](https://crowdin.com/profile/lyubomirv)
- Catalan
- David Planella - [GitLab](https://gitlab.com/dplanella), [Crowdin](https://crowdin.com/profile/dplanella)
-- Chinese Simplified
- - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
-- Chinese Traditional
+- Chinese Simplified 简体中文
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
+ - Victor Wu - [GitLab](https://gitlab.com/victorwuky), [Crowdin](https://crowdin.com/profile/victorwu)
+ - Xiaogang Wen - [GitLab](https://gitlab.com/xiaogang_gitlab), [Crowdin](https://crowdin.com/profile/xiaogang_gitlab)
+- Chinese Traditional ç¹é«”中文
- Weizhe Ding - [GitLab](https://gitlab.com/d.weizhe), [Crowdin](https://crowdin.com/profile/d.weizhe)
- Yi-Jyun Pan - [GitLab](https://gitlab.com/pan93412), [Crowdin](https://crowdin.com/profile/pan93412)
-- Chinese Traditional, Hong Kong
- - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
+ - Victor Wu - [GitLab](https://gitlab.com/victorwuky), [Crowdin](https://crowdin.com/profile/victorwu)
+- Chinese Traditional, Hong Kong ç¹é«”中文 (香港)
+ - Victor Wu - [GitLab](https://gitlab.com/victorwuky), [Crowdin](https://crowdin.com/profile/victorwu)
+ - Ivan Ip - [GitLab](https://gitlab.com/lifehome), [Crowdin](https://crowdin.com/profile/lifehome)
- Czech
- Proofreaders needed.
- Danish
- - Proofreaders needed.
+ - Saederup92 - [GitLab](https://gitlab.com/Saederup92), [Crowdin](https://crowdin.com/profile/Saederup92)
- Dutch
- Emily Hendle - [GitLab](https://gitlab.com/pundachan), [Crowdin](https://crowdin.com/profile/pandachan)
- Esperanto
@@ -40,6 +43,7 @@ are very appreciative of the work done by translators and proofreaders!
- Pedro Garcia - [GitLab](https://gitlab.com/pedgarrod), [Crowdin](https://crowdin.com/profile/breaking_pitt)
- German
- Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
+ - Katrin Leinweber - [GitLab](https://gitlab.com/katrinleinweber/), [Crowdin](https://crowdin.com/profile/katrinleinweber)
- Greek
- Proofreaders needed.
- Hebrew
@@ -47,15 +51,15 @@ are very appreciative of the work done by translators and proofreaders!
- Hungarian
- Proofreaders needed.
- Indonesian
+ - Adi Ferdian - [GitLab](https://gitlab.com/adiferd), [Crowdin](https://crowdin.com/profile/adiferd)
- Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
- Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
- Japanese
- - Yamana Tokiuji - [GitLab](https://gitlab.com/tokiuji), [Crowdin](https://crowdin.com/profile/yamana)
- Hiroyuki Sato - [GitLab](https://gitlab.com/hiroponz), [Crowdin](https://crowdin.com/profile/hiroponz)
+ - Tomo Dote - [Gitlab](https://gitlab.com/fu7mu4), [Crowdin](https://crowdin.com/profile/fu7mu4)
- Korean
- Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Ji Hun Oh - [GitLab](https://gitlab.com/Baw-Appie), [Crowdin](https://crowdin.com/profile/BawAppie)
- Jeongwhan Choi - [GitLab](https://gitlab.com/jeongwhanchoi), [Crowdin](https://crowdin.com/profile/jeongwhanchoi)
- Mongolian
@@ -67,6 +71,7 @@ are very appreciative of the work done by translators and proofreaders!
- Maksymilian Roman - [GitLab](https://gitlab.com/villaincandle), [Crowdin](https://crowdin.com/profile/villaincandle)
- Portuguese
- Proofreaders needed.
+ - Diogo Trindade - [GitLab](https://gitlab.com/luisdiogo2071317), [Crowdin](https://crowdin.com/profile/ldiogotrindade)
- Portuguese, Brazilian
- Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [Crowdin](https://crowdin.com/profile/paulogomes.rep)
- André Gama - [GitLab](https://gitlab.com/andregamma), [Crowdin](https://crowdin.com/profile/ToeOficial)
@@ -125,5 +130,8 @@ are very appreciative of the work done by translators and proofreaders!
your previous translations by [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
the language or current proofreaders.
+ - When a request is made for the first proofreader for a language and there are no [GitLab team members](https://about.gitlab.com/team/)
+ or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects.
+
[proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md
diff --git a/doc/development/i18n/translation.md b/doc/development/i18n/translation.md
index 99c0fe6db1d..62be3786549 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -89,9 +89,7 @@ To propose additions to the glossary please
### Inclusive language in French
-In French, we should follow the guidelines from [ecriture-inclusive.fr]. For
-instance:
+In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
+So, to include both genders, write “Utilisateurs et utilisatrices†instead of “Utilisateur·rice·sâ€.
+When space is missing, the male gender should be used alone.
-- Utilisateur•rice•s
-
-[ecriture-inclusive.fr]: http://www.ecriture-inclusive.fr/
diff --git a/doc/development/i18n_guide.md b/doc/development/i18n_guide.md
index f6e949b5fd8..e588b47e203 100644
--- a/doc/development/i18n_guide.md
+++ b/doc/development/i18n_guide.md
@@ -1 +1,5 @@
-This document was moved to [a new location](i18n/index.md).
+---
+redirect_to: 'i18n/index.md'
+---
+
+This document was moved to [another location](i18n/index.md).
diff --git a/doc/development/img/distributed_tracing_jaeger_ui.png b/doc/development/img/distributed_tracing_jaeger_ui.png
new file mode 100644
index 00000000000..57517dacced
--- /dev/null
+++ b/doc/development/img/distributed_tracing_jaeger_ui.png
Binary files differ
diff --git a/doc/development/img/distributed_tracing_performance_bar.png b/doc/development/img/distributed_tracing_performance_bar.png
new file mode 100644
index 00000000000..c9998cedd2d
--- /dev/null
+++ b/doc/development/img/distributed_tracing_performance_bar.png
Binary files differ
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index f7f48b03651..fd067b80c16 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -27,7 +27,7 @@ Read through the current performance problems using the Import/Export below.
### OOM errors
-Out of memory (OOM) errors are normally caused by the [Sidekiq Memory Killer](https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html):
+Out of memory (OOM) errors are normally caused by the [Sidekiq Memory Killer](../administration/operations/sidekiq_memory_killer.md):
```bash
SIDEKIQ_MEMORY_KILLER_MAX_RSS = 2GB in GitLab.com
diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
new file mode 100644
index 00000000000..5bf43d320c6
--- /dev/null
+++ b/doc/development/integrations/jira_connect.md
@@ -0,0 +1,41 @@
+# Setting up a development environment
+
+The following are required to install and test the app:
+
+1. A Jira Cloud instance
+
+ Atlassian provides free instances for development and testing. [Click here to sign up](http://go.atlassian.com/cloud-dev).
+
+1. A GitLab instance available over the internet
+
+ For the app to work, Jira Cloud should be able to connect to the GitLab instance through the internet.
+
+ To easily expose your local development environment, you can use tools like [serveo](https://serveo.net) or [ngrok](https://ngrok.com).
+ These also take care of SSL for you because Jira requires all connections to the app host to be over SSL.
+
+> This feature is currently behind the `:jira_connect_app` feature flag
+
+# Installing the app in Jira
+
+1. Enable Jira development mode to install apps that are not from the Atlassian Marketplace
+
+ 1. Navigate to **Jira settings** (cog icon) > **Apps** > **Manage apps**.
+ 1. Scroll to the bottom of the **Manage apps** page and click **Settings**.
+ 1. Select **Enable development mode** and click **Apply**.
+
+1. Install the app
+
+ 1. Navigate to Jira, then choose **Jira settings** (cog icon) > **Apps** > **Manage apps**.
+ 1. Click **Upload app**.
+ 1. In the **From this URL** field, provide a link to the app descriptor. The host and port must point to your GitLab instance.
+
+ For example:
+ ```
+ https://xxxx.serveo.net/-/jira_connect/app_descriptor.json
+ ```
+ 1. Click **Upload**.
+
+ If the install was successful, you should see the **GitLab for Jira** app under **Manage apps**.
+ You can also click **Getting Started** to open the configuration page rendered from your GitLab instance.
+
+ _Note that any changes to the app descriptor requires you to uninstall then reinstall the app._
diff --git a/doc/development/licensed_feature_availability.md b/doc/development/licensed_feature_availability.md
new file mode 100644
index 00000000000..6f3dd59b2c3
--- /dev/null
+++ b/doc/development/licensed_feature_availability.md
@@ -0,0 +1,37 @@
+# Licensed feature availability **[STARTER]**
+
+As of GitLab 9.4, we've been supporting a simplified version of licensed
+feature availability checks via `ee/app/models/license.rb`, both for
+on-premise or GitLab.com plans and features.
+
+## Restricting features scoped by namespaces or projects
+
+GitLab.com plans are persisted on user groups and namespaces, therefore, if you're adding a
+feature such as [Related issues](../user/project/issues/related_issues.md) or
+[Service desk](../user/project/service_desk.md),
+it should be restricted on namespace scope.
+
+1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
+ `ee/app/models/license.rb`. Note on `ee/app/models/ee/namespace.rb` that _Bronze_ GitLab.com
+ features maps to on-premise _EES_, _Silver_ to _EEP_ and _Gold_ to _EEU_.
+2. Check using:
+
+```ruby
+project.feature_available?(:feature_symbol)
+```
+
+## Restricting global features (instance)
+
+However, for features such as [Geo](../administration/geo/replication/index.md) and
+[Load balancing](../administration/database_load_balancing.md), which cannot be restricted
+to only a subset of projects or namespaces, the check will be made directly in
+the instance license.
+
+1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
+ `ee/app/models/license.rb`.
+2. Add the same feature symbol to `GLOBAL_FEATURES`
+3. Check using:
+
+```ruby
+License.feature_available?(:feature_symbol)
+```
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 0e71cd47481..0db90d2872f 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -88,9 +88,11 @@ Definitions
GitLab means GitLab Inc. and its affiliates and subsidiaries.
-## Requesting Approval for Licenses
+## Requesting Approval for Licenses or any other Intellectual Property
-Libraries that are not listed in the [Acceptable Licenses][Acceptable-Licenses] or [Unacceptable Licenses][Unacceptable-Licenses] list can be submitted to the legal team for review. Please email `legal@gitlab.com` with the details. After a decision has been made, the original requestor is responsible for updating this document.
+Libraries that are not already approved and listed on the [Acceptable Licenses][Acceptable-Licenses] list or that may be listed on the [Unacceptable Licenses][Unacceptable-Licenses] list may be submitted to the legal team for review and use on a case-by-case basis. Please email `legal@gitlab.com` with the details of how the software will be used, whether or not it will be modified, and how it will be distributed (if at all). After a decision has been made, the original requestor is responsible for updating this document, if applicable. Not all approvals will be approved for universal use and may continue to remain on the Unacceptable License list.
+
+All inquiries relating to patents should be directed to the Legal team.
## Notes
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index bb40c0d32b4..9b26f691b55 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -186,7 +186,11 @@ end
When adding a foreign-key constraint to either an existing or new
column remember to also add a index on the column.
-This is _required_ for all foreign-keys.
+This is **required** for all foreign-keys, e.g., to support efficient cascading
+deleting: when a lot of rows in a table get deleted, the referenced records need
+to be deleted too. The database has to look for corresponding records in the
+referenced table. Without an index, this will result in a sequential scan on the
+table which can take a long time.
Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
@@ -423,3 +427,9 @@ _namespaces_ that have a `project_id`.
The `path` column for these rows will be renamed to their previous value followed
by an integer. For example: `users` would turn into `users0`
+
+### Moving migrations from EE to CE
+
+When migrations need to be moved from GitLab Enterprise Edition to GitLab Community Edition,
+a migration file should be moved from `ee/db/{post_,}migrate` directory in the `gitlab-ee` project to `db/{post_,}migrate` directory in the `gitlab-ce` project. This way
+the schema number remains intact, there is no need to modify old migrations, and proper columns, tables or data are added in the Community Edition.
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
index 8ae58d30c35..963ce53423b 100644
--- a/doc/development/new_fe_guide/development/components.md
+++ b/doc/development/new_fe_guide/development/components.md
@@ -17,5 +17,5 @@ D3 is very popular across many projects outside of GitLab:
Within GitLab, D3 has been used for the following notable features
-- [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
+- [Prometheus graphs](../../../user/project/integrations/prometheus.md)
- Contribution calendars
diff --git a/doc/development/new_fe_guide/development/design_patterns.md b/doc/development/new_fe_guide/development/design_patterns.md
deleted file mode 100644
index ee06566ed30..00000000000
--- a/doc/development/new_fe_guide/development/design_patterns.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Design patterns
-
-> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/new_fe_guide/development/index.md
index cee8e43ebad..5dced3dc466 100644
--- a/doc/development/new_fe_guide/development/index.md
+++ b/doc/development/new_fe_guide/development/index.md
@@ -1,9 +1,5 @@
# Development
-## [Design patterns](design_patterns.md)
-
-Examples of proven design patterns used in our codebase.
-
## [Components](components.md)
Documentation on existing components and how to best create a new component.
@@ -12,14 +8,6 @@ Documentation on existing components and how to best create a new component.
Learn how to implement an accessible frontend.
-## [Network requests](network_requests.md)
-
-Learn how to handle network requests in our codebase.
-
-## [Security](security.md)
-
-Learn how to ensure that our frontend is secure.
-
## [Performance](performance.md)
Learn how to keep our frontend performant.
diff --git a/doc/development/new_fe_guide/development/network_requests.md b/doc/development/new_fe_guide/development/network_requests.md
deleted file mode 100644
index 047c00313bc..00000000000
--- a/doc/development/new_fe_guide/development/network_requests.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Network requests
-
-> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/security.md b/doc/development/new_fe_guide/development/security.md
deleted file mode 100644
index 5bb38f17988..00000000000
--- a/doc/development/new_fe_guide/development/security.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Security
-
-## Avoid inline scripts and styles
-
-Inline scripts and styles should be avoided in almost all cases. In an effort to protect users from [XSS vulnerabilities](https://en.wikipedia.org/wiki/Cross-site_scripting), we will be disabling inline scripts using Content Security Policy.
-
-## Including external resources
-
-External fonts, CSS, and JavaScript should never be used with the exception of Google Analytics and Piwik - and only when the instance has enabled it. Assets should always be hosted and served locally from the GitLab instance. Embedded resources via `iframes` should never be used except in certain circumstances such as with ReCaptcha, which cannot be used without an `iframe`.
-
-## Resources for security testing
-
-- [Mozilla's HTTP Observatory CLI](https://github.com/mozilla/http-observatory-cli)
-- [Qualys SSL Labs Server Test](https://www.ssllabs.com/ssltest/analyze.html)
diff --git a/doc/development/new_fe_guide/event_tracking.md b/doc/development/new_fe_guide/event_tracking.md
deleted file mode 100644
index 1958f1ce528..00000000000
--- a/doc/development/new_fe_guide/event_tracking.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# Event Tracking
-
-We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events.
-
-## Generic tracking function
-
-In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events.
-
-The generic tracking function can be imported in EE-specific JS files as follows:
-
-```javascript
-import { trackEvent } from `ee/stats`;
-```
-
-This gives the user access to the `trackEvent` method, which takes the following parameters:
-
-| parameter | type | description | required |
-| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
-| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true |
-| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true |
-| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false |
-
-Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation).
-
-### Tracking in `.js` and `.vue` files
-
-The most simple use case is to add tracking programmatically to an event of interest in Javascript.
-
-The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly:
-
-```javascript
-import { trackEvent } from `ee/stats`;
-
-trackEvent('dashboard:projects:index', 'click_button', {
- label: 'create_from_template',
- property: 'template_preview',
- value: 'rails',
-});
-```
-
-### Tracking in HAML templates
-
-Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task.
-
-There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping.
-
-Below is an example of `data-track-*` attributes assigned to a button in HAML:
-
-```ruby
-%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
-```
-
-By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
-
-```javascript
-import Stats from 'ee/stats';
-
-document.addEventListener('DOMContentLoaded', () => {
- Stats.bindTrackableContainer('.my-container', 'category');
-});
-```
-
-The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead.
-
-Below is a list of supported `data-track-*` attributes:
-
-| attribute | description | required |
-| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
-| `data-track-label` | The `label` in `trackEvent` | true |
-| `data-track-event` | The `eventName` in `trackEvent` | true |
-| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false |
-| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false |
-
-Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases.
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 5fd5af252ef..0e8f5486861 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -3,14 +3,6 @@
This guide contains all the information to successfully contribute to GitLab's frontend.
This is a living document, and we welcome contributions, feedback and suggestions.
-## [Principles](principles.md)
-
-Ensure that your frontend contribution starts off in the right direction.
-
-## [Initiatives](initiatives.md)
-
-High level overview of where we are going from a frontend perspective.
-
## [Development](development/index.md)
Guidance on topics related to development.
@@ -27,9 +19,6 @@ Learn about all the internal JavaScript modules that make up our frontend.
Style guides to keep our code consistent.
-## [Event Tracking with Snowplow](event_tracking.md)
-
-How we use Snowplow to track custom events.
## [Tips](tips.md)
diff --git a/doc/development/new_fe_guide/initiatives.md b/doc/development/new_fe_guide/initiatives.md
deleted file mode 100644
index c81ed3579f0..00000000000
--- a/doc/development/new_fe_guide/initiatives.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Initiatives
-
-> TODO: Add Initiatives
diff --git a/doc/development/new_fe_guide/principles.md b/doc/development/new_fe_guide/principles.md
deleted file mode 100644
index 0af5f506a91..00000000000
--- a/doc/development/new_fe_guide/principles.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Principles
-
-These principles will ensure that your frontend contribution starts off in the right direction.
-
-## Discuss architecture before implementation
-
-Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
-
-## Be consistent
-
-There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
-
-## Enhance progressively
-
-Whenever you see with existing code that does not follow our current style guide, update it proactively. Refrain from changing everything but each merge request should progressively enhance our codebase and reduce technical debt.
-
-## When to use Vue
-
-- Use Vue for feature that make use of heavy DOM manipulation
-- Use Vue for reusable components
-
-## When to use jQuery
-
-- Use jQuery to interact with Bootstrap JavaScript components
-- Avoid jQuery when a better alternative exists. We are slowly moving away from it [#43559][jquery-future]
-
-## Mixing Vue and jQuery
-
-- Mixing Vue and jQuery is not recommended.
-- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][select2].
-- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
-- It is not recommended to add new jQuery events for Vue to interact with jQuery.
-
-[jquery-future]: https://gitlab.com/gitlab-org/gitlab-ce/issues/43559
-[select2]: https://vuejs.org/v2/examples/select2.html
diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md
index 035fcbb28df..e8c9c2ccebf 100644
--- a/doc/development/new_fe_guide/style/html.md
+++ b/doc/development/new_fe_guide/style/html.md
@@ -2,11 +2,11 @@
## Buttons
-<a name="button-type"></a><a name="1.1"></a>
+### Button type
-- [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec].
+Button tags requires a `type` attribute according to the [W3C HTML specification](https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type).
-```
+```html
// bad
<button></button>
@@ -14,11 +14,11 @@
<button type="button"></button>
```
-<a name="button-role"></a><a name="1.2"></a>
+### Button role
-- [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible].
+If an HTML element has an `onClick` handler but is not a button, it should have `role="button"`. This is [more accessible](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role).
-```
+```html
// bad
<div onClick="doSomething"></div>
@@ -28,11 +28,11 @@
## Links
-<a name="blank-links"></a><a name="2.1"></a>
+### Blank target
-- [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit
+Use `rel="noopener noreferrer"` whenever your links open in a new window, i.e. `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/).
-```
+```html
// bad
<a href="url" target="_blank"></a>
@@ -40,18 +40,14 @@
<a href="url" target="_blank" rel="noopener noreferrer"></a>
```
-<a name="fake-links"></a><a name="2.2"></a>
+### Fake links
-- [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic.
+**Do not use fake links.** Use a button tag if a link only invokes JavaScript click event handlers, which is more semantic.
-```
+```html
// bad
<a class="js-do-something" href="#"></a>
// good
<button class="js-do-something" type="button"></button>
```
-
-[button-type-spec]: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type
-[button-role-accessible]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
-[jitbit-target-blank]: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 7985b893c9e..3019eaa089c 100644
--- a/doc/development/new_fe_guide/style/javascript.md
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -1,208 +1,196 @@
# JavaScript style guide
-We use [Airbnb's JavaScript Style Guide][airbnb-style-guide] and it's accompanying linter to manage most of our JavaScript style guidelines.
+We use [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript) and it's accompanying
+linter to manage most of our JavaScript style guidelines.
-In addition to the style guidelines set by Airbnb, we also have a few specific rules listed below.
+In addition to the style guidelines set by Airbnb, we also have a few specific rules
+listed below.
> **Tip:**
You can run eslint locally by running `yarn eslint`
-## Arrays
+## Avoid forEach
+
+Avoid forEach when mutating data. Use `map`, `reduce` or `filter` instead of `forEach`
+when mutating data. This will minimize mutations in functions,
+which aligns with [Airbnb's style guide](https://github.com/airbnb/javascript#testing--for-real).
+
+```javascript
+// bad
+users.forEach((user, index) => {
+ user.id = index;
+});
+
+// good
+const usersWithId = users.map((user, index) => {
+ return Object.assign({}, user, { id: index });
+});
+```
+
+## Limit number of parameters
+
+If your function or method has more than 3 parameters, use an object as a parameter
+instead.
+
+```javascript
+// bad
+function a(p1, p2, p3) {
+ // ...
+};
+
+// good
+function a(p) {
+ // ...
+};
+```
+
+## Avoid side effects in constructors
+
+Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`.
+Move them into separate functions instead. This will make tests easier to write and
+code easier to maintain.
-<a name="avoid-foreach"></a><a name="1.1"></a>
+```javascript
+// bad
+class myClass {
+ constructor(config) {
+ this.config = config;
+ axios.get(this.config.endpoint)
+ }
+}
-- [1.1](#avoid-foreach) **Avoid ForEach when mutating data** Use `map`, `reduce` or `filter` instead of `forEach` when mutating data. This will minimize mutations in functions ([which is aligned with Airbnb's style guide][airbnb-minimize-mutations])
+// good
+class myClass {
+ constructor(config) {
+ this.config = config;
+ }
- ```
- // bad
- users.forEach((user, index) => {
- user.id = index;
- });
+ makeRequest() {
+ axios.get(this.config.endpoint)
+ }
+}
+const instance = new myClass();
+instance.makeRequest();
- // good
- const usersWithId = users.map((user, index) => {
- return Object.assign({}, user, { id: index });
- });
- ```
+```
-## Functions
+## Avoid classes to handle DOM events
-<a name="limit-params"></a><a name="2.1"></a>
+If the only purpose of the class is to bind a DOM event and handle the callback, prefer
+using a function.
-- [2.1](#limit-params) **Limit number of parameters** If your function or method has more than 3 parameters, use an object as a parameter instead.
+```javascript
+// bad
+class myClass {
+ constructor(config) {
+ this.config = config;
+ }
- ```
- // bad
- function a(p1, p2, p3) {
- // ...
- };
+ init() {
+ document.addEventListener('click', () => {});
+ }
+}
- // good
- function a(p) {
- // ...
- };
- ```
+// good
-## Classes & constructors
+const myFunction = () => {
+ document.addEventListener('click', () => {
+ // handle callback here
+ });
+}
+```
-<a name="avoid-constructor-side-effects"></a><a name="3.1"></a>
+## Pass element container to constructor
-- [3.1](#avoid-constructor-side-effects) **Avoid side effects in constructors** Avoid making some operations in the `constructor`, such as asynchronous calls, API requests and DOM manipulations. Prefer moving them into separate functions. This will make tests easier to write and code easier to maintain.
+When your class manipulates the DOM, receive the element container as a parameter.
+This is more maintainable and performant.
- ```javascript
- // bad
- class myClass {
- constructor(config) {
- this.config = config;
- axios.get(this.config.endpoint)
- }
- }
+```javascript
+// bad
+class a {
+ constructor() {
+ document.querySelector('.b');
+ }
+}
- // good
- class myClass {
- constructor(config) {
- this.config = config;
- }
+// good
+class a {
+ constructor(options) {
+ options.container.querySelector('.b');
+ }
+}
+```
- makeRequest() {
- axios.get(this.config.endpoint)
- }
- }
- const instance = new myClass();
- instance.makeRequest();
+## Use ParseInt
- ```
+Use `ParseInt` when converting a numeric string into a number.
-<a name="avoid-classes-to-handle-dom-events"></a><a name="3.2"></a>
+```javascript
+// bad
+Number('10')
-- [3.2](#avoid-classes-to-handle-dom-events) **Avoid classes to handle DOM events** If the only purpose of the class is to bind a DOM event and handle the callback, prefer using a function.
+// good
+parseInt('10', 10);
+```
- ```
- // bad
- class myClass {
- constructor(config) {
- this.config = config;
- }
+## CSS Selectors - Use `js-` prefix
- init() {
- document.addEventListener('click', () => {});
- }
- }
+If a CSS class is only being used in JavaScript as a reference to the element, prefix
+the class name with `js-`.
- // good
+```html
+// bad
+<button class="add-user"></button>
- const myFunction = () => {
- document.addEventListener('click', () => {
- // handle callback here
- });
- }
- ```
+// good
+<button class="js-add-user"></button>
+```
-<a name="element-container"></a><a name="3.3"></a>
+## Absolute vs relative paths for modules
-- [3.3](#element-container) **Pass element container to constructor** When your class manipulates the DOM, receive the element container as a parameter.
- This is more maintainable and performant.
+Use relative paths if the module you are importing is less than two levels up.
- ```
- // bad
- class a {
- constructor() {
- document.querySelector('.b');
- }
- }
+```javascript
+// bad
+import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
- // good
- class a {
- constructor(options) {
- options.container.querySelector('.b');
- }
- }
- ```
+// good
+import GitLabStyleGuide from '../GitLabStyleGuide';
+```
-## Type Casting & Coercion
+If the module you are importing is two or more levels up, use an absolute path instead:
-<a name="use-parseint"></a><a name="4.1"></a>
+```javascript
+// bad
+import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
-- [4.1](#use-parseint) **Use ParseInt** Use `ParseInt` when converting a numeric string into a number.
+// good
+import GitLabStyleGuide from '~/GitLabStyleGuide';
+```
- ```
- // bad
- Number('10')
+Additionally, **do not add to global namespace**.
- // good
- parseInt('10', 10);
- ```
+## Do not use `DOMContentLoaded` in non-page modules
-## CSS Selectors
+Imported modules should act the same each time they are loaded. `DOMContentLoaded`
+events are only allowed on modules loaded in the `/pages/*` directory because those
+are loaded dynamically with webpack.
-<a name="use-js-prefix"></a><a name="5.1"></a>
+## Avoid XSS
-- [5.1](#use-js-prefix) **Use js prefix** If a CSS class is only being used in JavaScript as a reference to the element, prefix the class name with `js-`
+Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many
+vulnerabilities.
- ```
- // bad
- <button class="add-user"></button>
+## Disabling ESLint in new files
- // good
- <button class="js-add-user"></button>
- ```
+Do not disable ESLint when creating new files. Existing files may have existing rules
+disabled due to legacy compatibility reasons but they are in the process of being refactored.
-## Modules
+Do not disable specific ESLint rules. Due to technical debt, you may disable the following
+rules only if you are invoking/instantiating existing code modules.
-<a name="use-absolute-paths"></a><a name="6.1"></a>
+ - [no-new](http://eslint.org/docs/rules/no-new)
+ - [class-method-use-this](http://eslint.org/docs/rules/class-methods-use-this)
-- [6.1](#use-absolute-paths) **Use absolute paths for nearby modules** Use absolute paths if the module you are importing is less than two levels up.
-
- ```
- // bad
- import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
-
- // good
- import GitLabStyleGuide from '../GitLabStyleGuide';
- ```
-
-<a name="use-relative-paths"></a><a name="6.2"></a>
-
-- [6.2](#use-relative-paths) **Use relative paths for distant modules** If the module you are importing is two or more levels up, use a relative path instead of an absolute path.
-
- ```
- // bad
- import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
-
- // good
- import GitLabStyleGuide from '~/GitLabStyleGuide';
- ```
-
-<a name="global-namespace"></a><a name="6.3"></a>
-
-- [6.3](#global-namespace) **Do not add to global namespace**
-
-<a name="domcontentloaded"></a><a name="6.4"></a>
-
-- [6.4](#domcontentloaded) **Do not use DOMContentLoaded in non-page modules** Imported modules should act the same each time they are loaded. `DOMContentLoaded` events are only allowed on modules loaded in the `/pages/*` directory because those are loaded dynamically with webpack.
-
-## Security
-
-<a name="avoid-xss"></a><a name="7.1"></a>
-
-- [7.1](#avoid-xss) **Avoid XSS** Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many vulnerabilities.
-
-## ESLint
-
-<a name="disable-eslint-file"></a><a name="8.1"></a>
-
-- [8.1](#disable-eslint-file) **Disabling ESLint in new files** Do not disable ESLint when creating new files. Existing files may have existing rules disabled due to legacy compatibility reasons but they are in the process of being refactored.
-
-<a name="disable-eslint-rule"></a><a name="8.2"></a>
-
-- [8.2](#disable-eslint-rule) **Disabling ESLint rule** Do not disable specific ESLint rules. Due to technical debt, you may disable the following rules only if you are invoking/instantiating existing code modules
-
- - [no-new][no-new]
- - [class-method-use-this][class-method-use-this]
-
-> Note: Disable these rules on a per line basis. This makes it easier to refactor in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`
-
-[airbnb-style-guide]: https://github.com/airbnb/javascript
-[airbnb-minimize-mutations]: https://github.com/airbnb/javascript#testing--for-real
-[no-new]: http://eslint.org/docs/rules/no-new
-[class-method-use-this]: http://eslint.org/docs/rules/class-methods-use-this
+> Note: Disable these rules on a per line basis. This makes it easier to refactor
+ in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`.
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index 881ad1662ae..4564f678ec0 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -7,3 +7,19 @@ To clear production compiled assets created with `yarn webpack-prod` you can run
```
yarn clean
```
+
+## Creating feature flags in development
+
+The process for creating a feature flag is the same as [enabling a feature flag in development](../feature_flags.md#enabling-a-feature-flag-in-development).
+
+Your feature flag can now be:
+
+- [made available to the frontend](../feature_flags.md#frontend) via the `gon`
+- queried in [tests](../feature_flags.md#specs)
+- queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
+
+### More on feature flags
+
+- [Deleting a feature flag](../../api/features.md#delete-a-feature)
+- [Manage feature flags](../feature_flags.md)
+- [Feature flags API](../../api/features.md)
diff --git a/doc/development/packages.md b/doc/development/packages.md
new file mode 100644
index 00000000000..ab0c5f9904d
--- /dev/null
+++ b/doc/development/packages.md
@@ -0,0 +1,68 @@
+# Packages **[PREMIUM]**
+
+This document will guide you through adding another [package management system](../administration/packages.md) support to GitLab.
+
+See already supported package types in [Packages documentation](../administration/packages.md)
+
+Since GitLab packages' UI is pretty generic, it is possible to add new
+package system support by solely backend changes. This guide is superficial and does
+not cover the way the code should be written. However, you can find a good example
+by looking at existing merge requests with Maven and NPM support:
+
+- [NPM registry support](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8673).
+- [Maven repository](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6607).
+- [Instance level endpoint for Maven repository](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8757)
+
+## General information
+
+The existing database model requires the following:
+
+- Every package belongs to a project.
+- Every package file belongs to a package.
+- A package can have one or more package files.
+- The package model is based on storing information about the package and its version.
+
+## API endpoints
+
+Package systems work with GitLab via API. For example `ee/lib/api/npm_packages.rb`
+implements API endpoints to work with NPM clients. So, the first thing to do is to
+add a new `ee/lib/api/your_name_packages.rb` file with API endpoints that are
+necessary to make the package system client to work. Usually that means having
+endpoints like:
+
+- GET package information.
+- GET package file content.
+- PUT upload package.
+
+Since the packages belong to a project, it's expected to have project-level endpoint
+for uploading and downloading them. For example:
+
+```
+GET https://gitlab.com/api/v4/projects/<your_project_id>/packages/npm/
+PUT https://gitlab.com/api/v4/projects/<your_project_id>/packages/npm/
+```
+
+Group-level and instance-level endpoints are good to have but are optional.
+
+NOTE: **Note:**
+To avoid name conflict for instance-level endpoints we use
+[the package naming convention](../user/project/packages/npm_registry.md#package-naming-convention)
+
+## Configuration
+
+GitLab has a `packages` section in its configuration file (`gitlab.rb`).
+It applies to all package systems supported by GitLab. Usually you don't need
+to add anything there.
+
+Packages can be configured to use object storage, therefore your code must support it.
+
+## Database
+
+The current database model allows you to store a name and a version for each package.
+Every time you upload a new package, you can either create a new record of `Package`
+or add files to existing record. `PackageFile` should be able to store all file-related
+information like the file `name`, `side`, `sha1`, etc.
+
+If there is specific data necessary to be stored for only one package system support,
+consider creating a separate metadata model. See `packages_maven_metadata` table
+and `Packages::MavenMetadatum` model as example for package specific data.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 2a287611bdf..0e21d45f57c 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -38,6 +38,7 @@ GitLab provides built-in tools to help improve performance and availability:
- [Profiling](profiling.md).
- [Sherlock](profiling.md#sherlock).
+- [Distributed Tracing](distributed_tracing.md)
- [GitLab Performance Monitoring](../administration/monitoring/performance/index.md).
- [Request Profiling](../administration/monitoring/performance/request_profiling.md).
- [QueryRecoder](query_recorder.md) for preventing `N+1` regressions.
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index f41d635de43..b2f3a105b23 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -10,6 +10,10 @@ There is a `Gitlab::Profiler.profile` method, and corresponding
`bin/profile-url` script, that enable profiling a GET or POST request to a
specific URL, either as an anonymous user (the default) or as a specific user.
+NOTE: **Note:** The first argument to the profiler is either a full URL
+(including the instance hostname) or an absolute path, including the
+leading slash.
+
When using the script, command-line documentation is available by passing no
arguments.
@@ -86,10 +90,8 @@ that builds on this to add some additional niceties, such as allowing
configuration with a single Yaml file for multiple URLs, and uploading of the
profile and log output to S3.
-For GitLab.com, currently the latest profiling data has been [moved from
-Redash to Looker](https://gitlab.com/gitlab-com/Product/issues/5#note_121194467).
-We are [currently investigating how to make this data
-public](https://gitlab.com/meltano/looker/issues/294).
+For GitLab.com, you can find the latest results here:
+<http://redash.gitlab.com/dashboard/gitlab-profiler-statistics>
## Sherlock
diff --git a/doc/development/python_guide/index.md b/doc/development/python_guide/index.md
new file mode 100644
index 00000000000..6025dc9ebf2
--- /dev/null
+++ b/doc/development/python_guide/index.md
@@ -0,0 +1,79 @@
+# Python Development Guidelines
+
+GitLab requires Python as a dependency for [reStructuredText](http://docutils.sourceforge.net/rst.html)
+markup rendering.
+
+As of GitLab 11.10, we require Python 3.
+
+## Installation
+
+There are several ways of installing python on your system. To be able to use the same version we use in production,
+we suggest you use [pyenv](https://github.com/pyenv/pyenv). It works and behave similar to its counterpart in the
+ruby world: [rbenv](https://github.com/rbenv/rbenv).
+
+### macOS
+
+To install `pyenv` on macOS, you can use [Homebrew](https://brew.sh/) with:
+
+```bash
+brew install pyenv
+```
+
+### Linux
+
+To install `pyenv` on Linux, you can run the command below:
+
+```bash
+curl https://pyenv.run | bash
+```
+
+Alternatively, you may find `pypenv` available as a system package via your distro package manager.
+
+You can read more about it in: <https://github.com/pyenv/pyenv-installer#prerequisites>.
+
+### Shell integration
+
+Pyenv installation will add required changes to Bash. If you use a different shell,
+check for any additional steps required for it.
+
+For Fish, you can install a plugin for [Fisherman](https://github.com/fisherman/fisherman):
+
+```bash
+fisher add fisherman/pyenv
+```
+
+Or for [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish):
+
+```bash
+omf install pyenv
+```
+
+## Dependency management
+
+While GitLab doesn't directly contain any Python scripts, because we depend on Python to render
+[reStructuredText](http://docutils.sourceforge.net/rst.html) markup, we need to keep track on dependencies
+on the main project level, so we can run that on our development machines.
+
+Recently, an equivalent to the `Gemfile` and the [Bundler](https://bundler.io/) project has been introduced to Python:
+`Pipfile` and [Pipenv](https://pipenv.readthedocs.io/en/latest/).
+
+You will now find a `Pipfile` with the dependencies in the root folder. To install them, run:
+
+```bash
+pipenv install
+```
+
+Running this command will install both the required Python version as well as required pip dependencies.
+
+## Use instructions
+
+To run any python code under the Pipenv environment, you need to first start a `virtualenv` based on the dependencies
+of the application. With Pipenv, this is a simple as running:
+
+```bash
+pipenv shell
+```
+
+After running that command, you can run GitLab on the same shell and it will be using the Python and dependencies
+installed from the `pipenv install` command.
+
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index dcb32c89f65..28a12572961 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -12,6 +12,40 @@ The `setup` task is an alias for `gitlab:setup`.
This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
+### Seeding issues for all or a given project
+
+You can seed issues for all or a given project with the `gitlab:seed:issues`
+task:
+
+```shell
+# All projects
+bin/rake gitlab:seed:issues
+
+# A specific project
+bin/rake "gitlab:seed:issues[group-path/project-path]"
+```
+
+By default, this seeds an average of 2 issues per week for the last 5 weeks per
+project.
+
+#### Seeding issues for Insights charts **[ULTIMATE]**
+
+You can seed issues specifically for working with the
+[Insights charts](../user/group/insights/index.md) with the
+`gitlab:seed:insights:issues` task:
+
+```shell
+# All projects
+bin/rake gitlab:seed:insights:issues
+
+# A specific project
+bin/rake "gitlab:seed:insights:issues[group-path/project-path]"
+```
+
+By default, this seeds an average of 10 issues per week for the last 52 weeks
+per project. All issues will also be randomly labeled with team, type, severity,
+and priority.
+
### Automation
If you're very sure that you want to **wipe the current database** and refill
@@ -74,11 +108,13 @@ To make sure that indices still fit. You could find great details in:
In order to run the test you can use the following commands:
-- `rake spec` to run the rspec suite
-- `rake karma` to run the karma test suite
-- `rake gitlab:test` to run all the tests
+- `bin/rake spec` to run the rspec suite
+- `bin/rake spec:unit` to run the only the unit tests
+- `bin/rake spec:integration` to run the only the integration tests
+- `bin/rake spec:system` to run the only the system tests
+- `bin/rake karma` to run the karma test suite
-Note: `rake spec` takes significant time to pass.
+Note: `bin/rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running
a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means
@@ -87,6 +123,9 @@ full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp`
+Note: You can pass RSpec command line options to the `spec:unit`,
+`spec:integration`, and `spec:system` tasks, e.g. `bin/rake "spec:unit[--tag ~geo --dry-run]"`.
+
To run a single test file you can use:
- `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index ef1aba95712..84028b1b342 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -65,15 +65,14 @@ the worst case scenario, which we should optimise for, our total cost is now 20.
If we had used a feature flag, things would have been very different. We don't
need to revert a release, and because feature flags are disabled by default we
don't need to revert and pick any Git commits. In fact, all we have to do is
-disable the feature, and _maybe_ perform some cleanup. Let's say that the cost
-of this is 1. In this case, our best case cost is 11: 10 to build the feature,
-and 1 to add the feature flag. The worst case cost is now 12: 10 to build the
-feature, 1 to add the feature flag, and 1 to disable it.
+disable the feature, and in the worst case, perform cleanup. Let's say that
+the cost of this is 2. In this case, our best case cost is 11: 10 to build the
+feature, and 1 to add the feature flag. The worst case cost is now 13: 10 to
+build the feature, 1 to add the feature flag, and 2 to disable and clean up.
Here we can see that in the best case scenario the work necessary is only a tiny
bit more compared to not using a feature flag. Meanwhile, the process of
-reverting our changes has been made significantly cheaper, to the point of being
-trivial.
+reverting our changes has been made significantly and reliably cheaper.
In other words, feature flags do not slow down the development process. Instead,
they speed up the process as managing incidents now becomes _much_ easier. Once
@@ -103,7 +102,21 @@ GitLab's feature library (using
[Flipper](https://github.com/jnunemaker/flipper), and covered in the [Feature
Flags](feature_flags.md) guide) supports rolling out changes to a percentage of
users. This in turn can be controlled using [GitLab
-chatops](https://docs.gitlab.com/ee/ci/chatops/).
+chatops](../ci/chatops/README.md).
+
+For an up to date list of feature flag commands please see [the source
+code](https://gitlab.com/gitlab-com/chatops/blob/master/lib/chatops/commands/feature.rb).
+Note that all the examples in that file must be preceded by
+`/chatops run`.
+
+If you get an error "Whoops! This action is not allowed. This incident
+will be reported." that means your Slack account is not allowed to
+change feature flags. To test if you are allowed to do anything at all,
+run:
+
+```
+/chatops run feature --help
+```
For example, to enable a feature for 25% of all users, run the following in
Slack:
@@ -135,6 +148,20 @@ want to wait several hours or even days. This is entirely up to you, just make
sure it is clearly communicated to your team, and the Production team if you
anticipate any potential problems.
+Feature gates can also be actor based, for example a feature could first be
+enabled for only the `gitlab-ce` project. The project is passed by supplying a
+`--project` flag:
+
+```
+/chatops run feature set --project=gitlab-org/gitlab-ce some_feature true
+```
+
+For groups the `--group` flag is available:
+
+```
+/chatops run feature set --group=gitlab-org some_feature true
+```
+
Once a change is deemed stable, submit a new merge request to remove the
feature flag. This ensures the change is available to all users and self-hosted
instances. Make sure to add the ~"feature flag" label to this merge request so
@@ -173,10 +200,9 @@ isn't gated by a License or Plan.
### Undefined feature flags default to "on"
-An important side-effect of the [implicit feature
-flags][#implicit-feature-flags] mentioned above is that unless the feature is
-explicitly disabled or limited to a percentage of users, the feature flag check
-will default to `true`.
+An important side-effect of the [implicit feature flags](#implicit-feature-flags)
+mentioned above is that unless the feature is explicitly disabled or limited to a
+percentage of users, the feature flag check will default to `true`.
As an example, if you were to ship the backend half of a feature behind a flag,
you'd want to explicitly disable that flag until the frontend half is also ready
@@ -188,3 +214,12 @@ to be shipped. You can do this via ChatOps:
Note that you can do this at any time, even before the merge request using the
flag has been merged!
+
+### Cleaning up
+
+When a feature gate has been removed from the code base, the value still exists
+in the database. This can be removed through ChatOps:
+
+```
+/chatops run feature delete some_feature
+```
diff --git a/doc/development/routing.md b/doc/development/routing.md
new file mode 100644
index 00000000000..e9c0ad8d4e8
--- /dev/null
+++ b/doc/development/routing.md
@@ -0,0 +1,63 @@
+# Routing
+
+The GitLab backend is written primarily with Rails so it uses [Rails
+routing](https://guides.rubyonrails.org/routing.html). Beside Rails best
+practices, there are few rules unique to the GitLab application. To
+support subgroups, GitLab project and group routes use the wildcard
+character to match project and group routes. For example, we might have
+a path such as:
+
+ /gitlab-com/customer-success/north-america/west/customerA
+
+However, paths can be ambiguous. Consider the following example:
+
+ /gitlab-com/edit
+
+It's ambiguous whether there is a subgroup named `edit` or whether
+this is a special endpoint to edit the `gitlab-com` group.
+
+To eliminate the ambiguity and to make the backend easier to maintain,
+we introduced the `/-/` scope. The purpose of it is to separate group or
+project paths from the rest of the routes. Also it helps to reduce the
+number of [reserved names](../user/reserved_names.md).
+
+## Global routes
+
+We have a number of global routes. For example:
+
+ /-/health
+ /-/metrics
+
+## Group routes
+
+Every group route must be under the `/-/` scope.
+
+Examples:
+
+ gitlab-org/-/edit
+ gitlab-org/-/activity
+ gitlab-org/-/security/dashboard
+ gitlab-org/serverless/-/activity
+
+To achieve that, use the `scope '-'` method.
+
+## Project routes
+
+Every project route must be under the `/-/` scope, except cases where a Git
+client or other software requires something different.
+
+Examples:
+
+ gitlab-org/gitlab-ce/-/activity
+ gitlab-org/gitlab-ce/-/jobs/123
+ gitlab-org/gitlab-ce/-/settings/repository
+ gitlab-org/serverless/runtimes/-/settings/repository
+
+Currently, only some project routes are placed under the `/-/` scope. However,
+you can help us migrate more of them! To migrate project routes:
+
+1. Modify existing routes by adding `-` scope.
+1. Add redirects for legacy routes by using `Gitlab::Routing.redirect_legacy_paths`.
+1. Create a technical debt issue to remove deprecated routes in later releases.
+
+To get started, see an [example merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28435).
diff --git a/doc/development/session.md b/doc/development/session.md
new file mode 100644
index 00000000000..9edce3dbda0
--- /dev/null
+++ b/doc/development/session.md
@@ -0,0 +1,65 @@
+# Accessing session data
+
+Session data in GitLab is stored in Redis and can be accessed in a variety of ways.
+
+During a web request, for example:
+
+- Rails provides access to the session from within controllers through [`ActionDispatch::Session`](https://guides.rubyonrails.org/action_controller_overview.html#session).
+- Outside of controllers, it is possible to access the session through `Gitlab::Session`.
+
+Outside of a web request it is still possible to access sessions stored in Redis. For example:
+
+- Session IDs and contents can be [looked up directly in Redis](#redis).
+- Data about the UserAgent associated with the session can be accessed through `ActiveSession`.
+
+When storing values in a session it is best to:
+
+- Use simple primitives and avoid storing objects to avoid marshaling complications.
+- Clean up after unneeded variables to keep memory usage in Redis down.
+
+## Gitlab::Session
+
+Sometimes you might want to persist data in the session instead of another store like the database. `Gitlab::Session` lets you access this without passing the session around extensively. For example, you could access it from within a policy without having to pass the session through to each place permissions are checked from.
+
+The session has a hash-like interface, just like when using it from a controller. There is also `NamespacedSessionStore` for storing key-value data in a hash.
+
+```ruby
+# Lookup a value stored in the current session
+Gitlab::Session.current[:my_feature]
+
+# Modify the current session stored in redis
+Gitlab::Session.current[:my_feature] = value
+
+# Store key-value data namespaced under a key
+Gitlab::NamespacedSessionStore.new(:my_feature)[some_key] = value
+
+# Set the session for a block of code, such as for tests
+Gitlab::Session.with_session(my_feature: value) do
+ # Code that uses Session.current[:my_feature]
+end
+```
+
+## Redis
+
+Session data can be accessed directly through Redis. This can let you check up on a browser session when debugging.
+
+```ruby
+# Get a list of sessions
+session_ids = Gitlab::Redis::SharedState.with do |redis|
+ redis.smembers("#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}")
+end
+
+# Retrieve a specific session
+session_data = Gitlab::Redis::SharedState.with { |redis| redis.get("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}") }
+Marshal.load(session_data)
+```
+
+## Getting device information with ActiveSession
+
+The [**Active Sessions** page on a user's profile](../user/profile/active_sessions.md) displays information about the device used to access each session. The methods used there to list sessions can also be useful for development.
+
+```ruby
+# Get list of sessions for a given user
+# Includes session_id and data from the UserAgent
+ActiveSession.list(user)
+```
diff --git a/doc/development/sql.md b/doc/development/sql.md
index 47519d39e74..edeca7fb298 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -155,6 +155,21 @@ The _only_ time you should use `pluck` is when you actually need to operate on
the values in Ruby itself (e.g. write them to a file). In almost all other cases
you should ask yourself "Can I not just use a sub-query?".
+In line with our `CodeReuse/ActiveRecord` cop, you should only use forms like
+`pluck(:id)` or `pluck(:user_id)` within model code. In the former case, you can
+use the `ApplicationRecord`-provided `.pluck_primary_key` helper method instead.
+In the latter, you should add a small helper method to the relevant model.
+
+## Inherit from ApplicationRecord
+
+Most models in the GitLab codebase should inherit from `ApplicationRecord`,
+rather than from `ActiveRecord::Base`. This allows helper methods to be easily
+added.
+
+An exception to this rule exists for models created in database migrations. As
+these should be isolated from application code, they should continue to subclass
+from `ActiveRecord::Base`.
+
## Use UNIONs
UNIONs aren't very commonly used in most Rails applications but they're very
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 45b1519ece8..79ef8e75432 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -1 +1,5 @@
-This document was moved to [testing_guide/index.md](testing_guide/index.md).
+---
+redirect_to: 'testing_guide/index.md'
+---
+
+This document was moved to [another location](testing_guide/index.md).
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 2c8d488877b..82439c94c5a 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -1,5 +1,30 @@
# Testing best practices
+## Test Design
+
+Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
+as we do the design of our features.
+
+When implementing a feature, we think about developing the right capabilities the right way, which helps us
+narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
+the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
+a level that is difficult to manage.
+
+Test heuristics can help solve this problem. They concisely address many of the common ways bugs
+manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
+our test design. We can find some helpful heuristics documented in the Handbook in the
+[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
+
+## Run tests against MySQL
+
+By default, tests are only run againts PostgreSQL, but you can run them on
+demand against MySQL by following one of the following conventions:
+
+| Convention | Valid example |
+|:----------------------|:-----------------------------|
+| Include `mysql` in your branch name | `enhance-mysql-support` |
+| Include `[run mysql]` in your commit message | `Fix MySQL support<br><br>[run mysql]` |
+
## Test speed
GitLab has a massive test suite that, without [parallelization], can take hours
@@ -40,7 +65,7 @@ bundle exec rspec spec/[path]/[to]/[spec].rb
to separate phases.
- 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)).
+ [Gotchas](../gotchas.md#do-not-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`
- When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element,
@@ -168,12 +193,13 @@ instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables
-GitLab's RSpec suite has made extensive use of `let` variables to reduce
-duplication. However, this sometimes [comes at the cost of clarity][lets-not],
+GitLab's RSpec suite has made extensive use of `let`(along with it strict, non-lazy
+version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not],
so we need to set some guidelines for their use going forward:
-- `let` variables are preferable to instance variables. Local variables are
- preferable to `let` variables.
+- `let!` variables are preferable to instance variables. `let` variables
+ are preferable to `let!` variables. Local variables are preferable to
+ `let` variables.
- Use `let` to reduce duplication throughout an entire spec file.
- Don't use `let` to define variables used by a single test; define them as
local variables inside the test's `it` block.
@@ -183,6 +209,9 @@ so we need to set some guidelines for their use going forward:
- Try to avoid overriding the definition of one `let` variable with another.
- Don't define a `let` variable that's only used by the definition of another.
Use a helper method instead.
+- `let!` variables should be used only in case if strict evaluation with defined
+ order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
+ be evaluated until it is referenced.
[lets-not]: https://robots.thoughtbot.com/lets-not
@@ -197,8 +226,10 @@ project, one project will do for the entire file. This can be achieved by using
reloads or recreates the model, _only_ if needed. That is, when you changed
properties or destroyed the object.
-There is one gotcha; you can't reference a model defined in a `let` block in a
-`set` block.
+Note that you can't reference a model defined in a `let` block in a `set` block.
+
+Also, `set` is not supported in `:js` specs since those don't use transactions
+to clean up database state after each example.
### Time-sensitive tests
@@ -219,6 +250,36 @@ it 'is overdue' do
end
```
+### Feature flags in tests
+
+All feature flags are stubbed to be enabled by default in our Ruby-based
+tests.
+
+To disable a feature flag in a test, use the `stub_feature_flags`
+helper. For example, to globally disable the `ci_live_trace` feature
+flag in a test:
+
+```ruby
+stub_feature_flags(ci_live_trace: false)
+
+Feature.enabled?(:ci_live_trace) # => false
+```
+
+If you wish to set up a test where a feature flag is disabled for some
+actors and not others, you can specify this in options passed to the
+helper. For example, to disable the `ci_live_trace` feature flag for a
+specifc project:
+
+```ruby
+project1, project2 = build_list(:project, 2)
+
+# Feature will only be disabled for project1
+stub_feature_flags(ci_live_trace: { enabled: false, thing: project1 })
+
+Feature.enabled?(:ci_live_trace, project1) # => false
+Feature.enabled?(:ci_live_trace, project2) # => true
+```
+
### Pristine test environments
The code exercised by a single GitLab test may access and modify many items of
diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md
index 5aa668290b4..7a7fca46534 100644
--- a/doc/development/testing_guide/ci.md
+++ b/doc/development/testing_guide/ci.md
@@ -31,7 +31,11 @@ After that, the next pipeline will use the up-to-date
The GitLab test suite is [monitored] for the `master` branch, and any branch
that includes `rspec-profile` in their name.
+A [public dashboard] is available for everyone to see. Feel free to look at the
+slowest test files and try to improve them.
+
[monitored]: ../performance.md#rspec-profiling
+[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default
## CI setup
diff --git a/doc/development/testing_guide/end_to_end/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md
new file mode 100644
index 00000000000..89500ef9a90
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/best_practices.md
@@ -0,0 +1,38 @@
+# Best practices when writing end-to-end tests
+
+The majority of the end-to-end tests require some state to be built in the application for the tests to happen.
+
+A good example is a user being logged in as a pre-condition for testing the feature.
+
+But if the login feature is already covered with end-to-end tests through the GUI, there is no reason to perform such an expensive task to test the functionality of creating a project, or importing a repo, even if these features depend on a user being logged in. Let's see an example to make things clear.
+
+Let's say that, on average, the process to perform a successful login through the GUI takes 2 seconds.
+
+Now, realize that almost all tests need the user to be logged in, and that we need every test to run in isolation, meaning that tests cannot interfere with each other. This would mean that for every test the user needs to log in, and "waste 2 seconds".
+
+Now, multiply the number of tests per 2 seconds, and as your test suite grows, the time to run it grows with it, and this is not sustainable.
+
+An alternative to perform a login in a cheaper way would be having an endpoint (available only for testing) where we could pass the user's credentials as encrypted values as query strings, and then we would be redirected to the logged in home page if the credentials are valid. Let's say that, on average, this process takes only 200 miliseconds.
+
+You see the point right?
+
+Performing a login through the GUI for every test would cost a lot in terms of tests' execution.
+
+And there is another reason.
+
+Let's say that you don't follow the above suggestion, and depend on the GUI for the creation of every application state in order to test a specific feature. In this case we could be talking about the **Issues** feature, that depends on a project to exist, and the user to be logged in.
+
+What would happen if there was a bug in the project creation page, where the 'Create' button is disabled, not allowing for the creation of a project through the GUI, but the API logic is still working?
+
+In this case, instead of having only the project creation test failing, we would have many tests that depend on a project to be failing too.
+
+But, if we were following the best practices, only one test would be failing, and tests for other features that depend on a project to exist would continue to pass, since they could be creating the project behind the scenes interacting directly with the public APIs, ensuring a more reliable metric of test failure rate.
+
+Finally, interacting with the application only by its GUI generates a higher rate of test flakiness, and we want to avoid that at max.
+
+**The takeaways here are:**
+
+- Building state through the GUI is time consuming and it's not sustainable as the test suite grows.
+- When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness.
+
+Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md).
diff --git a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
new file mode 100644
index 00000000000..f7b3ca8bc89
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
@@ -0,0 +1,113 @@
+# Dynamic Element Validation
+
+We devised a solution to solve common test automation problems such as the dreaded `NoSuchElementException`.
+
+Other problems that dynamic element validations solve are...
+
+- When we perform an action with the mouse, we expect something to occur.
+- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before
+test continuation.
+
+## How it works
+
+We interpret user actions on the page to have some sort of effect. These actions are
+
+- [Navigation](#navigation)
+- [Clicks](#clicks)
+
+### Navigation
+
+When a page is navigated to, there are elements that will always appear on the page unconditionally.
+
+Dynamic element validation is instituted when using
+
+```ruby
+Runtime::Browser.visit(:gitlab, Some::Page)
+```
+
+### Clicks
+
+When we perform a click within our tests, we expect something to occur. That something could be a component to now
+appear on the webpage, or the test to navigate away from the page entirely.
+
+Dynamic element validation is instituted when using
+
+```ruby
+click_element :my_element, Some::Page
+```
+
+### Required Elements
+
+#### Definition
+
+First it is important to define what a "required element" is.
+
+Simply put, a required element is a visible HTML element that appears on a UI component without any user input.
+
+"Visible" can be defined as
+
+- Not having any CSS preventing its display. E.g.: `display: none` or `width: 0px; height: 0px;`
+- Being able to be interacted with by the user
+
+"UI component" can be defined as
+
+- Anything the user sees
+- A button, a text field
+- A layer that sits atop the page
+
+#### Application
+
+Requiring elements is very easy. By adding `required: true` as a parameter to an `element`, you've now made it
+a requirement that the element appear on the page upon navigation.
+
+## Examples
+
+Given ...
+
+```ruby
+class MyPage < Page::Base
+ view 'app/views/view.html.haml' do
+ element :my_element, required: true
+ element :another_element, required: true
+ element :conditional_element
+ end
+
+ def open_layer
+ click_element :my_element, Layer::MyLayer
+ end
+end
+
+class Layer < Page::Component
+ view 'app/views/mylayer/layer.html.haml' do
+ element :message_content, required: true
+ end
+end
+```
+
+### Navigating
+
+Given the [source](#examples) ...
+
+```ruby
+Runtime::Browser.visit(:gitlab, Page::MyPage)
+
+execute_stuff
+```
+
+will invoke GitLab QA to scan `MyPage` for `my_element` and `another_element` to be on the page before continuing to
+`execute_stuff`
+
+### Clicking
+
+Given the [source](#examples) ...
+
+```ruby
+def open_layer
+ click_element :my_element, Layer::MyLayer
+end
+```
+
+will invoke GitLab QA to ensure that `message_content` appears on
+the Layer upon clicking `my_element`.
+
+This will imply that the Layer is indeed rendered before we continue our test.
diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
new file mode 100644
index 00000000000..afd81ff00b2
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -0,0 +1,167 @@
+# End-to-end Testing
+
+## What is end-to-end testing?
+
+End-to-end testing is a strategy used to check whether your application works
+as expected across the entire software stack and architecture, including
+integration of all micro-services and components that are supposed to work
+together.
+
+## Branch naming
+
+If your contribution contains **only** changes under the
+[`qa/` folder](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa), you can
+speed up the CI process by following some branch naming conventions. You have
+three choices:
+
+| Branch name | Valid example |
+|:----------------------|:-----------------------------|
+| Starting with `qa/` | `qa/new-oauth-login-test` |
+| Starting with `qa-` | `qa-new-oauth-login-test` |
+| Ending in `-qa` | `123-new-oauth-login-test-qa` |
+
+If your branch name matches any of the above, it will run only the QA-related
+jobs.
+If it does not, the whole application test suite will run (including QA-related
+jobs).
+
+## How do we test GitLab?
+
+We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we
+test these packages using the [GitLab QA orchestrator][gitlab-qa] tool, which is
+a black-box testing framework for the API and the UI.
+
+### Testing nightly builds
+
+We run scheduled pipeline each night to test nightly builds created by Omnibus.
+You can find these nightly pipelines at [gitlab-org/quality/nightly/pipelines][quality-nightly-pipelines].
+Results are reported in the `#qa-nightly` Slack channel.
+
+### Testing staging
+
+We run scheduled pipeline each night to test staging.
+You can find these nightly pipelines at [gitlab-org/quality/staging/pipelines][quality-staging-pipelines].
+Results are reported in the `#qa-staging` Slack channel.
+
+### Testing code in merge requests
+
+#### Using the `package-and-qa` job
+
+It is possible to run end-to-end tests for a merge request, eventually being run in
+a pipeline in the [`gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/) project,
+by triggering the `package-and-qa` manual action in the `test` stage (not
+available for forks).
+
+**This runs end-to-end tests against a custom Omnibus package built from your
+merge request's changes.**
+
+Manual action that starts end-to-end tests is also available in merge requests
+in [Omnibus GitLab][omnibus-gitlab].
+
+Below you can read more about how to use it and how does it work.
+
+#### How does it work?
+
+Currently, we are using _multi-project pipeline_-like approach to run QA
+pipelines.
+
+![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png)
+
+<details>
+<summary>Show mermaid source</summary>
+<pre>
+graph LR
+ A1 -.->|1. Triggers an omnibus-gitlab pipeline and wait for it to be done| A2
+ B2[<b>`Trigger-qa` stage</b><br />`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
+
+subgraph gitlab-ce/ee pipeline
+ A1[<b>`test` stage</b><br />`package-and-qa` job]
+ end
+
+subgraph omnibus-gitlab pipeline
+ A2[<b>`Trigger-docker` stage</b></b><br />`Trigger:gitlab-docker` job] -->|once done| B2
+ end
+
+subgraph gitlab-qa pipeline
+ A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br />and post the result on the original commit tested| A1
+ end
+</pre>
+</details>
+
+1. Developer triggers a manual action, that can be found in CE / EE merge
+ requests. This starts a chain of pipelines in multiple projects.
+
+1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab]
+ and waits for the resulting status. We call this a _status attribution_.
+
+1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab]
+ pipeline. Packages are then pushed to its Container Registry.
+
+1. When packages are ready, and available in the registry, a final step in the
+ [Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
+ GitLab QA pipeline (those with access can view them at `https://gitlab.com/gitlab-org/gitlab-qa/pipelines`). It also waits for a resulting status.
+
+1. GitLab QA pulls images from the registry, spins-up containers and runs tests
+ against a test environment that has been just orchestrated by the `gitlab-qa`
+ tool.
+
+1. The result of the GitLab QA pipeline is being
+ propagated upstream, through Omnibus, back to the CE / EE merge request.
+
+#### Using the `review-qa-all` jobs
+
+On every pipeline during the `test` stage, the `review-qa-smoke` job is
+automatically started: it runs the QA smoke suite against the
+[Review App][review-apps].
+
+You can also manually start the `review-qa-all`: it runs the full QA suite
+against the [Review App][review-apps].
+
+**This runs end-to-end tests against a Review App based on [the official GitLab
+Helm chart][helm-chart], itself deployed with custom
+[Cloud Native components][cng] built from your merge request's changes.**
+
+See [Review Apps][review-apps] for more details about Review Apps.
+
+[helm-chart]: https://gitlab.com/charts/gitlab/
+[cng]: https://gitlab.com/gitlab-org/build/CNG
+
+## How do I write tests?
+
+In order to write new tests, you first need to learn more about GitLab QA
+architecture. See the [documentation about it][gitlab-qa-architecture].
+
+Once you decided where to put [test environment orchestration scenarios] and
+[instance-level scenarios], take a look at the [GitLab QA README][instance-qa-readme],
+the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
+instance-level scenarios][instance-level scenarios].
+
+Continued reading:
+
+- [Quick Start Guide](quick_start_guide.md)
+- [Style Guide](style_guide.md)
+- [Best Practices](best_practices.md)
+
+## Where can I ask for help?
+
+You can ask question in the `#quality` channel on Slack (GitLab internal) or
+you can find an issue you would like to work on in
+[the `gitlab-ce` issue tracker][gitlab-ce-issues],
+[the `gitlab-ee` issue tracker][gitlab-ce-issues], or
+[the `gitlab-qa` issue tracker][gitlab-qa-issues].
+
+[omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
+[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
+[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
+[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
+[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
+[review-apps]: ../review_apps.md
+[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
+[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
+[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
+[gitlab-ee-issues]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name[]=QA&label_name[]=test
+[test environment orchestration scenarios]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/lib/gitlab/qa/scenario
+[instance-level scenarios]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features
+[Page objects documentation]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page/README.md
+[instance-qa-readme]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/README.md
+[instance-qa-examples]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa
diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
new file mode 100644
index 00000000000..73e1fd862c1
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -0,0 +1,167 @@
+# Page objects in GitLab QA
+
+In GitLab QA we are using a known pattern, called _Page Objects_.
+
+This means that we have built an abstraction for all GitLab pages that we use
+to drive GitLab QA scenarios. Whenever we do something on a page, like filling
+in a form, or clicking a button, we do that only through a page object
+associated with this area of GitLab.
+
+For example, when GitLab QA test harness signs in into GitLab, it needs to fill
+in a user login and user password. In order to do that, we have a class, called
+`Page::Main::Login` and `sign_in_using_credentials` methods, that is the only
+piece of the code, that has knowledge about `user_login` and `user_password`
+fields.
+
+## Why do we need that?
+
+We need page objects, because we need to reduce duplication and avoid problems
+whenever someone changes some selectors in GitLab's source code.
+
+Imagine that we have a hundred specs in GitLab QA, and we need to sign into
+GitLab each time, before we make assertions. Without a page object one would
+need to rely on volatile helpers or invoke Capybara methods directly. Imagine
+invoking `fill_in :user_login` in every `*_spec.rb` file / test example.
+
+When someone later changes `t.text_field :login` in the view associated with
+this page to `t.text_field :username` it will generate a different field
+identifier, what would effectively break all tests.
+
+Because we are using `Page::Main::Login.act { sign_in_using_credentials }`
+everywhere, when we want to sign into GitLab, the page object is the single
+source of truth, and we will need to update `fill_in :user_login`
+to `fill_in :user_username` only in a one place.
+
+## What problems did we have in the past?
+
+We do not run QA tests for every commit, because of performance reasons, and
+the time it would take to build packages and test everything.
+
+That is why when someone changes `t.text_field :login` to
+`t.text_field :username` in the _new session_ view we won't know about this
+change until our GitLab QA nightly pipeline fails, or until someone triggers
+`package-and-qa` action in their merge request.
+
+Obviously such a change would break all tests. We call this problem a _fragile
+tests problem_.
+
+In order to make GitLab QA more reliable and robust, we had to solve this
+problem by introducing coupling between GitLab CE / EE views and GitLab QA.
+
+## How did we solve fragile tests problem?
+
+Currently, when you add a new `Page::Base` derived class, you will also need to
+define all selectors that your page objects depends on.
+
+Whenever you push your code to CE / EE repository, `qa:selectors` sanity test
+job is going to be run as a part of a CI pipeline.
+
+This test is going to validate all page objects that we have implemented in
+`qa/page` directory. When it fails, you will be notified about missing
+or invalid views / selectors definition.
+
+## How to properly implement a page object?
+
+We have built a DSL to define coupling between a page object and GitLab views
+it is actually implemented by. See an example below.
+
+```ruby
+module Page
+ module Main
+ class Login < Page::Base
+ view 'app/views/devise/passwords/edit.html.haml' do
+ element :password_field
+ element :password_confirmation
+ element :change_password_button
+ end
+
+ view 'app/views/devise/sessions/_new_base.html.haml' do
+ element :login_field
+ element :password_field
+ element :sign_in_button
+ end
+
+ # ...
+ end
+ end
+end
+```
+
+### Defining Elements
+
+The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements.
+
+The `element` DSL method in turn declares an element for which a corresponding
+`qa-element-name-dasherized` CSS class will need to be added to the view file.
+
+You can also define a value (String or Regexp) to match to the actual view
+code but **this is deprecated** in favor of the above method for two reasons:
+
+- Consistency: there is only one way to define an element
+- Separation of concerns: QA uses dedicated CSS classes instead of reusing code
+ or classes used by other components (e.g. `js-*` classes etc.)
+
+```ruby
+view 'app/views/my/view.html.haml' do
+ # Implicitly require `.qa-logout-button` CSS class to be present in the view
+ element :logout_button
+
+ ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
+ # Require `f.submit "Sign in"` to be present in `my/view.html.haml
+ element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
+
+ ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
+ # Match every line in `my/view.html.haml` against
+ # `/link_to .* "My Profile"/` regexp.
+ element :profile_link, /link_to .* "My Profile"/ # rubocop:disable QA/ElementWithPattern
+end
+```
+
+### Adding Elements to a View
+
+Given the following elements...
+
+```ruby
+view 'app/views/my/view.html.haml' do
+ element :login_field
+ element :password_field
+ element :sign_in_button
+end
+```
+
+To add these elements to the view, you must change the rails View, partial, or vue component by adding a `qa-element-descriptor` class
+for each element defined.
+
+In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button`
+
+**app/views/my/view.html.haml**
+
+```haml
+= f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+= f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required."
+= f.submit "Sign in", class: "btn btn-success qa-sign-in-button"
+```
+
+Things to note:
+
+- The CSS class must be `kebab-cased` (separated with hyphens "`-`")
+- If the element appears on the page unconditionally, add `required: true` to the element. See
+[Dynamic element validation](dynamic_element_validation.md)
+
+## Running the test locally
+
+During development, you can run the `qa:selectors` test by running
+
+```shell
+bin/qa Test::Sanity::Selectors
+```
+
+from within the `qa` directory.
+
+## Where to ask for help?
+
+If you need more information, ask for help on `#quality` channel on Slack
+(internal, GitLab Team only).
+
+If you are not a Team Member, and you still need help to contribute, please
+open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
new file mode 100644
index 00000000000..afe76acf9c9
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -0,0 +1,585 @@
+# Writing end-to-end tests step-by-step
+
+In this tutorial, you will find different examples, and the steps involved, in the creation of end-to-end (_e2e_) tests for GitLab CE and GitLab EE, using GitLab QA.
+
+> When referring to end-to-end tests in this document, this means testing a specific feature end-to-end, such as a user logging in, the creation of a project, the management of labels, breaking down epics into sub-epics and issues, etc.
+
+## Important information before we start writing tests
+
+It's important to understand that end-to-end tests of isolated features, such as the ones described in the above note, doesn't mean that everything needs to happen through the GUI.
+
+If you don't exactly understand what we mean by **not everything needs to happen through the GUI,** please make sure you've read the [best practices](best_practices.md) before moving on.
+
+## This document covers the following items:
+
+- [0.](#0-are-end-to-end-tests-needed) Identifying if end-to-end tests are really needed
+- [1.](#1-identifying-the-devops-stage) Identifying the [DevOps stage](https://about.gitlab.com/stages-devops-lifecycle/) of the feature that you are going to cover with end-to-end tests
+- [2.](#2-test-skeleton) Creating the skeleton of the test file (`*_spec.rb`)
+- [3.](#3-test-cases-mvc) The [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of the test cases' logic
+- [4.](#4-extracting-duplicated-code) Extracting duplicated code into methods
+- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects]
+- [6.](#6-optimization) Optimizing the test suite
+- [7.](#7-resources) Using and implementing resources
+- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects]
+
+### 0. Are end-to-end tests needed?
+
+At GitLab we respect the [test pyramid](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/testing_guide/testing_levels.md), and so, we recommend you check the code coverage of a specific feature before writing end-to-end tests, for both [CE](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby/#_AllFiles) and [EE](https://gitlab-org.gitlab.io/gitlab-ee/coverage-ruby/#_AllFiles) projects.
+
+Sometimes you may notice that there is already good coverage in other test levels, and we can stay confident that if we break a feature, we will still have quick feedback about it, even without having end-to-end tests.
+
+If after this analysis you still think that end-to-end tests are needed, keep reading.
+
+### 1. Identifying the DevOps stage
+
+The GitLab QA end-to-end tests are organized by the different [stages in the DevOps lifecycle](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features/browser_ui), and so, if you are creating tests for issue creation, for instance, you would locate the spec files under the `qa/qa/specs/features/browser_ui/2_plan/` directory since issue creation is part of the Plan stage.
+
+ In another case of a test for listing merged merge requests (MRs), the test should go under the `qa/qa/specs/features/browser_ui/3_create/` directory since merge requests are a feature from the Create stage.
+
+> There may be sub-directories inside the stages directories, for different features. For example: `.../browser_ui/2_plan/ee_epics/` and `.../browser_ui/2_plan/issues/`.
+
+Now, let's say we want to create tests for the [scoped labels](https://about.gitlab.com/2019/04/22/gitlab-11-10-released/#scoped-labels) feature, available on GitLab EE Premium (this feature is part of the Plan stage.)
+
+> Because these tests are for a feature available only on GitLab EE, we need to create them in the [EE repository](https://gitlab.com/gitlab-org/gitlab-ee).
+
+Since [there is no specific directory for this feature](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/qa/qa/specs/features/browser_ui/2_plan), we should create a sub-directory for it.
+
+Under `.../browser_ui/2_plan/`, let's create a sub-directory called `ee_scoped_labels/`.
+
+> Notice that since this feature is only available for GitLab EE we prefix the sub-directory with `ee_`.
+
+### 2. Test skeleton
+
+Inside the newly created sub-directory, let's create a file describing the test suite (e.g. `editing_scoped_labels_spec.rb`.)
+
+#### The `context` and `describe` blocks
+
+Specs have an outer `context` that indicates the DevOps stage. The next level is the `describe` block, that briefly states the subject of the test suite. See the following example:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ end
+ end
+end
+```
+
+#### The `it` blocks
+
+Every test suite is composed of at least one `it` block, and a good way to start writing end-to-end tests is by writing test cases descriptions as `it` blocks. These might help you to think of different test scenarios. Take a look at the following example:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ it 'replaces an existing label if it has the same key' do
+ end
+
+ it 'keeps both scoped labels when adding a label with a different key' do
+ end
+ end
+ end
+end
+```
+
+### 3. Test cases MVC
+
+For the [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of our test cases, let's say that we already have the application in the state needed for the tests, and then let's focus on the logic of the test cases only.
+
+To evolve the test cases drafted on step 2, let's imagine that the user is already logged into a GitLab EE instance, they already have at least a Premium license in use, there is already a project created, there is already an issue opened in the project, the issue already has a scoped label (e.g. `animal::fox`), there are other scoped labels (for the same scope and for a different scope (e.g. `animal::dolphin` and `plant::orchid`), and finally, the user is already on the issue's page. Let's also suppose that for every test case the application is in a clean state, meaning that one test case won't affect another.
+
+> Note: there are different approaches to creating an application state for end-to-end tests. Some of them are very time consuming and subject to failures, such as when using the GUI for all the pre-conditions of the tests. On the other hand, other approaches are more efficient, such as using the public APIs. The latter is more efficient since it doesn't depend on the GUI. We won't focus on this part yet, but it's good to keep it in mind.
+
+Let's now focus on the first test case.
+
+```ruby
+it 'replaces an existing label if it has the same key' do
+ # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects (which we cover on section 8).
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['animal::dolphin', :enter]
+ page.find('#content-body').click
+ page.refresh
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content('animal::dolphin')
+ expect(labels_block).not_to have_content('animal::fox')
+ expect(page).to have_content('added animal::dolphin label and removed animal::fox')
+end
+```
+
+> Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later.
+
+> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects] before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language.
+
+Below are the steps that the test covers:
+
+1. The test finds the 'Edit' link for the labels and clicks on it.
+2. Then it fills in the 'Assign labels' input field with the value 'animal::dolphin' and press enters.
+3. Then it clicks in the content body to apply the label and refreshes the page.
+4. Finally, the expectations check that the previous scoped label was removed and that the new one was added.
+
+Let's now see how the second test case would look.
+
+```ruby
+it 'keeps both scoped labels when adding a label with a different key' do
+ # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects (which we cover on section 8).
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['plant::orchid', :enter]
+ page.find('#content-body').click
+ page.refresh
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content('animal::fox')
+ expect(labels_block).to have_content('plant::orchid')
+ expect(page).to have_content('added animal::fox')
+ expect(page).to have_content('added plant::orchid')
+end
+```
+
+> Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called adding testability to the application and we will talk more about it later.) For example, the `labels_block` element uses the selector `.qa-labels-block`, which was added specifically for testing purposes.
+
+Below are the steps that the test covers:
+
+1. The test finds the 'Edit' link for the labels and clicks on it.
+2. Then it fills in the 'Assign labels' input field with the value 'plant::orchid' and press enters.
+3. Then it clicks in the content body to apply the label and refreshes the page.
+4. Finally, the expectations check that both scoped labels are present.
+
+> Similar to the previous test, this one is also very straightforward, but there is some code duplication. Let's address it.
+
+### 4. Extracting duplicated code
+
+If we refactor the tests created on step 3 we could come up with something like this:
+
+```ruby
+before do
+ ...
+
+ @initial_label = 'animal::fox'
+ @new_label_same_scope = 'animal::dolphin'
+ @new_label_different_scope = 'plant::orchid'
+
+ ...
+end
+
+it 'replaces an existing label if it has the same key' do
+ select_label_and_refresh @new_label_same_scope
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content(@new_label_same_scope)
+ expect(labels_block).not_to have_content(@initial_label)
+ expect(page).to have_content("added #{@new_label_same_scope}")
+ expect(page).to have_content("and removed #{@initial_label}")
+end
+
+it 'keeps both scoped label when adding a label with a different key' do
+ select_label_and_refresh @new_label_different_scope
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_blocks).to have_content(@new_label_different_scope)
+ expect(labels_blocks).to have_content(@initial_label)
+ expect(page).to have_content("added #{@new_label_different_scope}")
+ expect(page).to have_content("added #{@initial_label}")
+end
+
+def select_label_and_refresh(label)
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
+ page.find('#content-body').click
+ page.refresh
+end
+```
+
+First, we remove the duplication of strings by defining the global variables `@initial_label`, `@new_label_same_scope` and `@new_label_different_scope` in the `before` block, and by using them in the expectations.
+
+Then, by creating a reusable `select_label_and_refresh` method, we remove the code duplication of this action, and later we can move this method to a Page Object class that will be created for easier maintenance purposes.
+
+> Notice that the reusable method is created at the bottom of the file. The reason for that is that reading the code should be similar to reading a newspaper, where high-level information is at the top, like the title and summary of the news, while low level, or more specific information, is at the bottom (this helps readability).
+
+### 5. Tests' pre-conditions using resources and Page Objects
+
+In this section, we will address the previously mentioned subject of creating the application state for the tests, using the `before :context` and `before` blocks, together with resources and Page Objects.
+
+#### `before :context`
+
+A pre-condition for the entire test suite is defined in the `before :context` block.
+
+> For our test suite, due to the need of the tests being completely independent of each other, we won't use the `before :context` block. The `before :context` block would make the tests dependent on each other because the first test changes the label of the issue, and the second one depends on the `'animal::fox'` label being set.
+
+> **Tip:** In case of a test suite with only one `it` block it's ok to use only the `before` block (see below) with all the test's pre-conditions.
+
+#### `before`
+
+As the pre-conditions for our test suite, the things that needs to happen before each test starts are:
+
+- The user logging in;
+- A premium license already being set;
+- A project being created with an issue and labels already set;
+- The issue page being opened with only one scoped label applied to the it.
+
+> When running end-to-end tests as part of the GitLab's continuous integration process [a license is already set as an environment variable](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a60d926740db10e3b5724713285780a4f470531/qa/qa/ee/strategy.rb#L20). For running tests locally you can set up such license by following the document [what tests can be run?](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-remote-grid-environment-variables), based on the [supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
+
+#### Implementation
+
+In the following code we will focus only on the test suite's pre-conditions:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @initial_label = 'animal::fox'
+ @new_label_same_scope = 'animal::dolphin'
+ @new_label_different_scope = 'plant::orchid'
+
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = 'Issue to test the scoped labels'
+ issue.labels = @initial_label
+ end
+
+ [@new_label_same_scope, @new_label_different_scope].each do |label|
+ Resource::Label.fabricate_via_api! do |l|
+ l.project = issue.project.id
+ l.title = label
+ end
+ end
+
+ issue.visit!
+ end
+
+ it 'replaces an existing label if it has the same key' do
+ ...
+ end
+
+ it 'keeps both scoped labels when adding a label with a different key' do
+ ...
+ end
+
+ def select_label_and_refresh(label)
+ ...
+ end
+ end
+ end
+end
+```
+
+In the `before` block we create all the application state needed for the tests to run. We do that by using the `Runtime::Browser.visit` method to go to the login page, by performing a `sign_in_using_credentials` from the `Login` Page Object, by fabricating resources via APIs (`issue`, and `Resource::Label`), and by using the `issue.visit!` to visit the issue page.
+
+> A project is created in the background by creating the `issue` resource.
+
+> When creating the [Resources], notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource.
+
+> What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage.
+
+> Soon we will cover the use of the already existing resources' methods and the creation of your own `fabricate_via_api` methods for resources where this is still not available, but first, let's optimize our implementation.
+
+### 6. Optimization
+
+As already mentioned in the [best practices](best_practices.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible.
+
+> Note that end-to-end tests are slow to run and so they can have several actions and assertions in a single test, which helps us get feedback from the tests sooner. In comparison, unit tests are much faster to run and can exercise every little piece of the application in isolation, and so they usually have only one assertion per test.
+
+Some improvements that we could make in our test suite to optimize its time to run are:
+
+1. Having a single test case (an `it` block) that exercises both scenarios to avoid "wasting" time in the tests' pre-conditions, instead of having two different test cases.
+2. Making the selection of labels more performant by allowing for the selection of more than one label in the same reusable method.
+
+Let's look at a suggestion that addresses the above points, one by one:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ ...
+ end
+
+ it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
+ select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
+
+ labels_block = page.all('.qa-labels-block')
+
+ expect(labels_block).to have_content(@new_label_same_scope)
+ expect(labels_block).to have_content(@new_label_different_scope)
+ expect(labels_block).not_to have_content(@initial_label)
+ expect(page).to have_content("added #{@initial_label}")
+ expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}")
+ end
+
+ def select_labels_and_refresh(labels)
+ find('.block.labels .edit-link').click
+ labels.each do |label|
+ find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
+ end
+ find('#content-body').click
+ refresh
+ end
+ end
+ end
+ end
+```
+
+To address point 1, we changed the test implementation from two `it` blocks into a single one that exercises both scenarios. Now the new test description is: `'correctly applies the scoped labels depending if they are from the same or a different scope'`. It's a long description, but it describes well what the test does.
+
+> Notice that the implementation of the new and unique `it` block had to change a little bit. Below we describe in details what it does.
+
+1. It selects two scoped labels simultaneously, one from the same scope of the one already applied in the issue during the setup phase (in the `before` block), and another one from a different scope.
+2. It asserts that the correct labels are visible in the `labels_block`, and that the labels were correctly added and removed;
+3. Finally, the `select_label_and_refresh` method is changed to `select_labels_and_refresh`, which accepts an array of labels instead of a single label, and it iterates on them for faster label selection (this is what is used in step 1 explained above.)
+
+### 7. Resources
+
+**Note:** When writing this document, some code that is now merged to master was not implemented yet, but we left them here for the readers to understand the whole process of end-to-end test creation.
+
+You can think of [Resources] as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI.
+
+With that in mind, resources can be a project, an epic, an issue, a label, a commit, etc.
+
+As you saw in the tests' pre-conditions and the optimization sections, we're already creating some of these resources, and we are doing that by calling the `fabricate_via_api!` method.
+
+> We could be using the `fabricate!` method instead, which would use the `fabricate_via_api!` method if it exists, and fallback to GUI fabrication otherwise, but we recommend being explicit to make it clear what the test does. Also, we always recommend fabricating resources via API since this makes tests faster and more reliable.
+
+For our test suite example, the resources that we need to create don't have the necessary code for the `fabricate_via_api!` method to correctly work (e.g., the issue and label resources), so we will have to create them.
+
+#### Implementation
+
+In the following we describe the changes needed in each of the resource files mentioned above.
+
+**Issue resource**
+
+Now, let's make it possible to create an issue resource through the API.
+
+First, in the [issue resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb), let's expose its labels attribute.
+
+Add the following `attribute :labels` right below the [`attribute :title`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L15).
+
+> This line is needed to allow for labels to be automatically added to an issue when fabricating it via API.
+
+Next, add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L27) method.
+
+```ruby
+def api_get_path
+ "/projects/#{project.id}/issues/#{id}"
+end
+
+def api_post_path
+ "/projects/#{project.id}/issues"
+end
+
+def api_post_body
+ {
+ title: title,
+ labels: [labels]
+ }
+end
+```
+
+By defining the `api_get_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single issue.
+
+> This `GET` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#single-issue).
+
+By defining the `api_post_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new issue in a specific project.
+
+> This `POST` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#new-issue).
+
+By defining the `api_post_body` method, we allow the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
+
+> Notice that we pass both `title` and `labels` attributes in the `api_post_body`, where `labels` receives an array of labels, and [`title` is required](https://docs.gitlab.com/ee/api/issues.html#new-issue).
+
+**Label resource**
+
+Finally, let's make it possible to create label resources through the API.
+
+Add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/label.rb#L36) method.
+
+```ruby
+def resource_web_url(resource)
+ super
+rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+end
+
+def api_get_path
+ raise NotImplementedError, "The Labels API doesn't expose a single-resource endpoint so this method cannot be properly implemented."
+end
+
+def api_post_path
+ "/projects/#{project}/labels"
+end
+
+def api_post_body
+ {
+ name: @title,
+ color: @color
+ }
+end
+```
+
+By defining the `resource_web_url(resource)` method, we override the one from the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb#L44) module. We do that to avoid failing the test due to this particular resource not exposing a `web_url` property.
+
+By defining the `api_get_path` method, we **would** allow for the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single label, but since there's no path available for that in the publich API, we raise a `NotImplementedError` instead.
+
+By defining the `api_post_path` method, we allow for the [`ApiFabricator `](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new label in a specific project.
+
+By defining the `api_post_body` method, we we allow for the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
+
+> Notice that we pass both `name` and `color` attributes in the `api_post_body` since [those are required](https://docs.gitlab.com/ee/api/labels.html#create-a-new-label).
+
+### 8. Page Objects
+
+Page Objects are used in end-to-end tests for maintenance reasons, where a page's elements and methods are defined to be reused in any test.
+
+> Page Objects are auto-loaded in the `qa/qa.rb` file and available in all the test files (`*_spec.rb`).
+
+Take a look at the [Page Objects] documentation.
+
+Now, let's go back to our example.
+
+As you may have noticed, we are defining elements with CSS selectors and the `select_labels_and_refresh` method directly in the test file, and this is an anti-pattern since we need to better separate the responsibilities.
+
+To address this issue, we will move the implementation to Page Objects, and the test suite will only focus on the business rules that we are testing.
+
+#### Updates in the test file
+
+As in a test-driven development approach, let's start changing the test file even before the Page Object implementation is in place.
+
+Replace the code of the `it` block in the test file by the following:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ ...
+ end
+
+ it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
+ Page::Project::Issue::Show.perform do |issue_page|
+ issue_page.select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
+
+ expect(page).to have_content("added #{@initial_label}")
+ expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}")
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_same_scope)
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_different_scope)
+ expect(issue_page.text_of_labels_block).not_to have_content(@initial_label)
+ end
+ end
+ end
+ end
+end
+```
+
+Notice that `select_labels_and_refresh` is now a method from the issue Page Object (which is not yet implemented), and that we verify the labels' text by using `text_of_labels_block`, instead of via the `labels_block` element. The `text_of_labels_block` method will also be implemented in the issue Page Object.
+
+Let's now update the Issue Page Object.
+
+#### Updates in the Issue Page Object
+
+> Page Objects are located in the `qa/qa/page/` directory, and its sub-directories.
+
+The file we will have to change is the [Issue Page Object](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/page/project/issue/show.rb).
+
+First, add the following code right below the definition of an already implemented view:
+
+```ruby
+view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :labels_block
+ element :edit_link_labels
+ element :dropdown_menu_labels
+end
+
+view 'app/helpers/dropdowns_helper.rb' do
+ element :dropdown_input_field
+end
+```
+
+Similarly to what we did before, let's first change the Page Object even without the elements being defined in the view (`_sidebar.html.haml`) and the `dropdowns_helper.rb` files, and later we will update them by adding the appropriate CSS selectors.
+
+Now, let's implement the methods `select_labels_and_refresh` and `text_of_labels_block`.
+
+Somewhere between the definition of the views and the private methods, add the following snippet of code:
+
+```ruby
+def select_labels_and_refresh(labels)
+ click_element(:edit_link_labels)
+ labels.each do |label|
+ within_element(:dropdown_menu_labels, text: label) do
+ send_keys_to_element(:dropdown_input_field, [label, :enter])
+ end
+ end
+ click_body
+ labels.each do |label|
+ has_element?(:labels_block, text: label)
+ end
+ refresh
+end
+
+def text_of_labels_block
+ find_element(:labels_block)
+end
+```
+
+##### Details of `select_labels_and_refresh`
+
+Notice that we have not only moved the `select_labels_and_refresh` method, but we have also changed its implementation to:
+1. Click the `:edit_link_labels` element previously defined, instead of using `find('.block.labels .edit-link').click`
+2. Use `within_element(:dropdown_menu_labels, text: label)`, and inside of it, we call `send_keys_to_element(:dropdown_input_field, [label, :enter])`, which is a method that we will implement in the `QA::Page::Base` class to replace `find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]`
+3. Use `click_body` after iterating on each label, instead of using `find('#content-body').click`
+4. Iterate on every label again, and then we use `has_element?(:labels_block, text: label)` after clicking the page body (which applies the labels), and before refreshing the page, to avoid test flakiness due to refreshing too fast.
+
+##### Details of `text_of_labels_block`
+
+The `text_of_labels_block` method is a simple method that returns the `:labels_block` element (`find_element(:labels_block)`).
+
+#### Updates in the view (*.html.haml) and `dropdowns_helper.rb` files
+
+Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the Page Object.
+
+In the [app/views/shared/issuable/_sidebar.html.haml](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/views/shared/issuable/_sidebar.html.haml) file, on [line 105 ](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L105), add an extra class `qa-edit-link-labels`.
+
+The code should look like this: `= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right'`.
+
+In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L121), add an extra class `.qa-dropdown-menu-labels`.
+
+The code should look like this: `.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.qa-dropdown-menu-labels.dropdown-menu-selectable`.
+
+In the [`dropdowns_helper.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/helpers/dropdowns_helper.rb) file, on [line 94](https://gitlab.com/gitlab-org/gitlab-ee/blob/99e51a374f2c20bee0989cac802e4b5621f72714/app/helpers/dropdowns_helper.rb#L94), add an extra class `qa-dropdown-input-field`.
+
+The code should look like this: `filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off'`.
+
+> Classes starting with `qa-` are used for testing purposes only, and by defining such classes in the elements we add **testability** in the application.
+
+> When defining a class like `qa-labels-block`, it is transformed into `:labels_block` for usage in the Page Objects. So, `qa-edit-link-labels` is tranformed into `:edit_link_labels`, `qa-dropdown-menu-labels` is transformed into `:dropdown_menu_labels`, and `qa-dropdown-input-field` is transformed into `:dropdown_input_field`. Also, we use a [sanity test](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page#how-did-we-solve-fragile-tests-problem) to check that defined elements have their respective `qa-` selectors in the specified views.
+
+> We did not define the `qa-labels-block` class in the `app/views/shared/issuable/_sidebar.html.haml` file because it was already there to be used.
+
+#### Updates in the `QA::Page::Base` class
+
+The last thing that we have to do is to update `QA::Page::Base` class to add the `send_keys_to_element` method on it.
+
+Add the following snippet of code somewhere where class methods are defined:
+
+```ruby
+def send_keys_to_element(name, keys)
+ find_element(name).send_keys(keys)
+end
+```
+
+This method receives an element (`name`) and the `keys` that it will send to that element, and the keys are an array that can receive strings, or "special" keys, like `:enter`.
+
+As you might remember, in the Issue Page Object we call this method like this: `send_keys_to_element(:dropdown_input_field, [label, :enter])`.
+
+___
+
+With that, you should be able to start writing end-to-end tests yourself. *Congratulations!*
+
+[Page Objects]: page_objects.md
+[Resources]: resources.md
diff --git a/doc/development/testing_guide/end_to_end/resources.md b/doc/development/testing_guide/end_to_end/resources.md
new file mode 100644
index 00000000000..1e32db4f633
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/resources.md
@@ -0,0 +1,392 @@
+# Resource class in GitLab QA
+
+Resources are primarily created using Browser UI steps, but can also
+be created via the API or the CLI.
+
+## How to properly implement a resource class?
+
+All resource classes should inherit from `Resource::Base`.
+
+There is only one mandatory method to implement to define a resource class.
+This is the `#fabricate!` method, which is used to build the resource via the
+browser UI. Note that you should only use [Page objects](page_objects.md) to
+interact with a Web page in this method.
+
+Here is an imaginary example:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ attr_accessor :name
+
+ def fabricate!
+ Page::Dashboard::Index.perform do |dashboard_index|
+ dashboard_index.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+ end
+ end
+ end
+end
+```
+
+### Define API implementation
+
+A resource class may also implement the three following methods to be able to
+create the resource via the public GitLab API:
+
+- `#api_get_path`: The `GET` path to fetch an existing resource.
+- `#api_post_path`: The `POST` path to create a new resource.
+- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource.
+
+Let's take the `Shirt` resource class, and add these three API methods:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ attr_accessor :name
+
+ def fabricate!
+ # ... same as before
+ end
+
+ def api_get_path
+ "/shirt/#{name}"
+ end
+
+ def api_post_path
+ "/shirts"
+ end
+
+ def api_post_body
+ {
+ name: name
+ }
+ end
+ end
+ end
+end
+```
+
+The `Project` resource is a good real example of Browser
+UI and API implementations.
+
+#### Resource attributes
+
+A resource may need another resource to exist first. For instance, a project
+needs a group to be created in.
+
+To define a resource attribute, you can use the `attribute` method with a
+block using the other resource class to fabricate the resource.
+
+That will allow access to the other resource from your resource object's
+methods. You would usually use it in `#fabricate!`, `#api_get_path`,
+`#api_post_path`, `#api_post_body`.
+
+Let's take the `Shirt` resource class, and add a `project` attribute to it:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ attr_accessor :name
+
+ attribute :project do
+ Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Show.perform do |project_show|
+ project_show.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+ end
+
+ def api_get_path
+ "/project/#{project.path}/shirt/#{name}"
+ end
+
+ def api_post_path
+ "/project/#{project.path}/shirts"
+ end
+
+ def api_post_body
+ {
+ name: name
+ }
+ end
+ end
+ end
+end
+```
+
+**Note that all the attributes are lazily constructed. This means if you want
+a specific attribute to be fabricated first, you'll need to call the
+attribute method first even if you're not using it.**
+
+#### Product data attributes
+
+Once created, you may want to populate a resource with attributes that can be
+found in the Web page, or in the API response.
+For instance, once you create a project, you may want to store its repository
+SSH URL as an attribute.
+
+Again we could use the `attribute` method with a block, using a page object
+to retrieve the data on the page.
+
+Let's take the `Shirt` resource class, and define a `:brand` attribute:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ attr_accessor :name
+
+ attribute :project do
+ Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ # Attribute populated from the Browser UI (using the block)
+ attribute :brand do
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+ end
+
+ # ... same as before
+ end
+ end
+end
+```
+
+**Note again that all the attributes are lazily constructed. This means if
+you call `shirt.brand` after moving to the other page, it'll not properly
+retrieve the data because we're no longer on the expected page.**
+
+Consider this:
+
+```ruby
+shirt =
+ QA::Resource::Shirt.fabricate! do |resource|
+ resource.name = "GitLab QA"
+ end
+
+shirt.project.visit!
+
+shirt.brand # => FAIL!
+```
+
+The above example will fail because now we're on the project page, trying to
+construct the brand data from the shirt page, however we moved to the project
+page already. There are two ways to solve this, one is that we could try to
+retrieve the brand before visiting the project again:
+
+```ruby
+shirt =
+ QA::Resource::Shirt.fabricate! do |resource|
+ resource.name = "GitLab QA"
+ end
+
+shirt.brand # => OK!
+
+shirt.project.visit!
+
+shirt.brand # => OK!
+```
+
+The attribute will be stored in the instance therefore all the following calls
+will be fine, using the data previously constructed. If we think that this
+might be too brittle, we could eagerly construct the data right before
+ending fabrication:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ # ... same as before
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Show.perform do |project_show|
+ project_show.go_to_new_shirt
+ end
+
+ Page::Shirt::New.perform do |shirt_new|
+ shirt_new.set_name(name)
+ shirt_new.create_shirt!
+ end
+
+ populate(:brand) # Eagerly construct the data
+ end
+ end
+ end
+end
+```
+
+The `populate` method will iterate through its arguments and call each
+attribute respectively. Here `populate(:brand)` has the same effect as
+just `brand`. Using the populate method makes the intention clearer.
+
+With this, it will make sure we construct the data right after we create the
+shirt. The drawback is that this will always construct the data when the
+resource is fabricated even if we don't need to use the data.
+
+Alternatively, we could just make sure we're on the right page before
+constructing the brand data:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ attr_accessor :name
+
+ attribute :project do
+ Project.fabricate! do |resource|
+ resource.name = 'project-to-create-a-shirt'
+ end
+ end
+
+ # Attribute populated from the Browser UI (using the block)
+ attribute :brand do
+ back_url = current_url
+ visit!
+
+ Page::Shirt::Show.perform do |shirt_show|
+ shirt_show.fetch_brand_from_page
+ end
+
+ visit(back_url)
+ end
+
+ # ... same as before
+ end
+ end
+end
+```
+
+This will make sure it's on the shirt page before constructing brand, and
+move back to the previous page to avoid breaking the state.
+
+#### Define an attribute based on an API response
+
+Sometimes, you want to define a resource attribute based on the API response
+from its `GET` or `POST` request. For instance, if the creation of a shirt via
+the API returns
+
+```ruby
+{
+ brand: 'a-brand-new-brand',
+ style: 't-shirt',
+ materials: [[:cotton, 80], [:polyamide, 20]]
+}
+```
+
+you may want to store `style` as-is in the resource, and fetch the first value
+of the first `materials` item in a `main_fabric` attribute.
+
+Let's take the `Shirt` resource class, and define a `:style` and a
+`:main_fabric` attributes:
+
+```ruby
+module QA
+ module Resource
+ class Shirt < Base
+ # ... same as before
+
+ # @style from the instance if present,
+ # or fetched from the API response if present,
+ # or a QA::Resource::Base::NoValueError is raised otherwise
+ attribute :style
+
+ # If @main_fabric is not present,
+ # and if the API does not contain this field, this block will be
+ # used to construct the value based on the API response, and
+ # store the result in @main_fabric
+ attribute :main_fabric do
+ api_response.&dig(:materials, 0, 0)
+ end
+
+ # ... same as before
+ end
+ end
+end
+```
+
+**Notes on attributes precedence:**
+
+- resource instance variables have the highest precedence
+- attributes from the API response take precedence over attributes from the
+ block (usually from Browser UI)
+- attributes without a value will raise a `QA::Resource::Base::NoValueError` error
+
+## Creating resources in your tests
+
+To create a resource in your tests, you can call the `.fabricate!` method on
+the resource class.
+Note that if the resource class supports API fabrication, this will use this
+fabrication by default.
+
+Here is an example that will use the API fabrication method under the hood
+since it's supported by the `Shirt` resource class:
+
+```ruby
+my_shirt = Resource::Shirt.fabricate! do |shirt|
+ shirt.name = 'my-shirt'
+end
+
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
+expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
+expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response
+expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
+```
+
+If you explicitly want to use the Browser UI fabrication method, you can call
+the `.fabricate_via_browser_ui!` method instead:
+
+```ruby
+my_shirt = Resource::Shirt.fabricate_via_browser_ui! do |shirt|
+ shirt.name = 'my-shirt'
+end
+
+expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
+expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block
+expect(page).to have_text(my_shirt.style) # => QA::Resource::Base::NoValueError will be raised because no API response nor a block is provided
+expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
+```
+
+You can also explicitly use the API fabrication method, by calling the
+`.fabricate_via_api!` method:
+
+```ruby
+my_shirt = Resource::Shirt.fabricate_via_api! do |shirt|
+ shirt.name = 'my-shirt'
+end
+```
+
+In this case, the result will be similar to calling
+`Resource::Shirt.fabricate!`.
+
+## Where to ask for help?
+
+If you need more information, ask for help on `#quality` channel on Slack
+(internal, GitLab Team only).
+
+If you are not a Team Member, and you still need help to contribute, please
+open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
new file mode 100644
index 00000000000..0272e1810f2
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -0,0 +1,99 @@
+# Style guide for writing end-to-end tests
+
+This document describes the conventions used at GitLab for writing End-to-end (E2E) tests using the GitLab QA project.
+
+## `click_` versus `go_to_`
+
+### When to use `click_`?
+
+When clicking in a single link to navigate, use `click_`.
+
+E.g.:
+
+```ruby
+def click_ci_cd_pipelines
+ within_sidebar do
+ click_element :link_pipelines
+ end
+end
+```
+
+From a testing perspective, if we want to check that clicking a link, or a button (a single interaction) is working as intended, we would want the test to read as:
+
+- Click a certain element
+- Verify the action took place
+
+### When to use `go_to_`?
+
+When interacting with multiple elements to go to a page, use `go_to_`.
+
+E.g.:
+
+```ruby
+def go_to_operations_environments
+ hover_operations do
+ within_submenu do
+ click_element(:operations_environments_link)
+ end
+ end
+end
+```
+
+`go_to_` fits the definition of interacting with multiple elements very well given it's more of a meta-navigation action that includes multiple interactions.
+
+Notice that in the above example, before clicking the `:operations_environments_link`, another element is hovered over.
+
+> We can create these methods as helpers to abstract multi-step navigation.
+
+### Element naming convention
+
+When adding new elements to a page, it's important that we have a uniform element naming convention.
+
+We follow a simple formula roughly based on hungarian notation.
+
+*Formula*: `element :<descriptor>_<type>`
+
+- `descriptor`: The natural-language description of what the element is. On the login page, this could be `username`, or `password`.
+- `type`: A physical control on the page that can be seen by a user.
+ - `_button`
+ - `_link`
+ - `_tab`
+ - `_dropdown`
+ - `_field`
+ - `_checkbox`
+ - `_radio`
+ - `_content`
+
+*Note: This list is a work in progress. This list will eventually be the end-all enumeration of all available types.
+ I.e., any element that does not end with something in this list is bad form.*
+
+#### Examples
+
+**Good**
+
+```ruby
+view '...' do
+ element :edit_button
+ element :notes_tab
+ element :squash_checkbox
+ element :username_field
+ element :issue_title_content
+end
+```
+
+**Bad**
+
+```ruby
+view '...' do
+ # `_confirmation` should be `_field`. what sort of confirmation? a checkbox confirmation? no real way to disambiguate.
+ # an appropriate replacement would be `element :password_confirmation_field`
+ element :password_confirmation
+
+ # `clone_options` is too vague. If it's a dropdown menu, it should be `clone_dropdown`.
+ # If it's a checkbox, it should be `clone_checkbox`
+ element :clone_options
+
+ # how is this url being displayed? is it a textbox? a simple span?
+ element :ssh_clone_url
+end
+```
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
deleted file mode 100644
index a239dc84a1c..00000000000
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# End-to-end Testing
-
-## What is end-to-end testing?
-
-End-to-end testing is a strategy used to check whether your application works
-as expected across the entire software stack and architecture, including
-integration of all micro-services and components that are supposed to work
-together.
-
-## How do we test GitLab?
-
-We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we
-test these packages using the [GitLab QA orchestrator][gitlab-qa] tool, which is
-a black-box testing framework for the API and the UI.
-
-### Testing nightly builds
-
-We run scheduled pipeline each night to test nightly builds created by Omnibus.
-You can find these nightly pipelines at [gitlab-org/quality/nightly/pipelines][quality-nightly-pipelines].
-
-### Testing staging
-
-We run scheduled pipeline each night to test staging.
-You can find these nightly pipelines at [gitlab-org/quality/staging/pipelines][quality-staging-pipelines].
-
-### Testing code in merge requests
-
-It is possible to run end-to-end tests (eventually being run within a
-[GitLab QA pipeline][gitlab-qa-pipelines]) for a merge request by triggering
-the `package-and-qa` manual action in the `test` stage, that should be present
-in a merge request widget (unless the merge request is from a fork).
-
-Manual action that starts end-to-end tests is also available in merge requests
-in [Omnibus GitLab][omnibus-gitlab].
-
-Below you can read more about how to use it and how does it work.
-
-#### How does it work?
-
-Currently, we are using _multi-project pipeline_-like approach to run QA
-pipelines.
-
-1. Developer triggers a manual action, that can be found in CE / EE merge
- requests. This starts a chain of pipelines in multiple projects.
-
-1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab]
- and waits for the resulting status. We call this a _status attribution_.
-
-1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab]
- pipeline. Packages are then pushed to its Container Registry.
-
-1. When packages are ready, and available in the registry, a final step in the
- [Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
- [GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status.
-
-1. GitLab QA pulls images from the registry, spins-up containers and runs tests
- against a test environment that has been just orchestrated by the `gitlab-qa`
- tool.
-
-1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
- propagated upstream, through Omnibus, back to the CE / EE merge request.
-
-#### How do I write tests?
-
-In order to write new tests, you first need to learn more about GitLab QA
-architecture. See the [documentation about it][gitlab-qa-architecture].
-
-Once you decided where to put [test environment orchestration scenarios] and
-[instance-level scenarios], take a look at the [GitLab QA README][instance-qa-readme],
-the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
-instance-level scenarios][instance-level scenarios].
-
-## Where can I ask for help?
-
-You can ask question in the `#quality` channel on Slack (GitLab internal) or
-you can find an issue you would like to work on in
-[the `gitlab-ce` issue tracker][gitlab-ce-issues],
-[the `gitlab-ee` issue tracker][gitlab-ce-issues], or
-[the `gitlab-qa` issue tracker][gitlab-qa-issues].
-
-[omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
-[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
-[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
-[gitlab-qa-pipelines]: https://gitlab.com/gitlab-org/gitlab-qa/pipelines
-[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
-[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
-[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
-[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
-[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
-[gitlab-ee-issues]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name[]=QA&label_name[]=test
-[test environment orchestration scenarios]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/lib/gitlab/qa/scenario
-[instance-level scenarios]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features
-[Page objects documentation]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page/README.md
-[instance-qa-readme]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/README.md
-[instance-qa-examples]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index 3d568c37fba..931cbc51cae 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -8,7 +8,7 @@ eventually.
## Quarantined tests
When a test frequently fails in `master`,
-[a ~"broken master" issue](https://about.gitlab.com/handbook/engineering/workflow/#broken-master)
+[a ~"master:broken" issue](https://about.gitlab.com/handbook/engineering/workflow/#broken-master)
should be created.
If the test cannot be fixed in a timely fashion, there is an impact on the
productivity of all the developers, so it should be placed in quarantine by
@@ -21,7 +21,7 @@ bin/rspec --tag quarantine
```
**Before putting a test in quarantine, you should make sure that a
-~"broken master" issue exists for it so it won't stay in quarantine forever.**
+~"master:broken" issue exists for it so it won't stay in quarantine forever.**
Once a test is in quarantine, there are 3 choices:
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 0470a071d39..4c9d1684c00 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -13,16 +13,60 @@ in the future.
See the [Testing Standards and Style Guidelines](index.md) page for more
information on general testing practices at GitLab.
+## Jest
+
+We have started to migrate frontend tests to the [Jest](https://jestjs.io) testing framework (see also the corresponding
+[epic](https://gitlab.com/groups/gitlab-org/-/epics/895)).
+
+Jest tests can be found in `/spec/frontend` and `/ee/spec/frontend` in EE.
+
+It is not yet a requirement to use Jest. You can view the
+[epic](https://gitlab.com/groups/gitlab-org/-/epics/873) of issues
+we need to solve before being able to use Jest for all our needs.
+
+### Differences to Karma
+
+- Jest runs in a Node.js environment, not in a browser. Support for running Jest tests in a browser [is planned](https://gitlab.com/gitlab-org/gitlab-ce/issues/58205).
+- Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default.
+- All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks).
+- `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/en/manual-mocks).
+- The following will cause tests to fail in Jest:
+ - Unmocked requests.
+ - Unhandled Promise rejections.
+ - Calls to `console.warn`, including warnings from libraries like Vue.
+
+### Debugging Jest tests
+
+Running `yarn jest-debug` will run Jest in debug mode, allowing you to debug/inspect as described in the [Jest docs](https://jestjs.io/docs/en/troubleshooting#tests-are-failing-and-you-don-t-know-why).
+
+### Timeout error
+
+The default timeout for Jest is set in
+[`/spec/frontend/test_setup.js`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/frontend/test_setup.js).
+
+If your test exceeds that time, it will fail.
+
+If you cannot improve the performance of the tests, you can increase the timeout
+for a specific test using
+[`setTestTimeout`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/frontend/helpers/timeout.js).
+
+```javascript
+import { setTestTimeout } from 'helpers/timeout';
+
+describe('Component', () => {
+ it('does something amazing', () => {
+ setTestTimeout(500);
+ // ...
+ });
+});
+```
+
+Remember that the performance of each test depends on the environment.
+
## Karma test suite
GitLab uses the [Karma][karma] test runner with [Jasmine] as its test
-framework for our JavaScript unit and integration tests. For integration tests,
-we generate HTML files using RSpec (see `spec/javascripts/fixtures/*.rb` for examples).
-Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`).
-Adding these static fixtures should be avoided as they are harder to keep up to date with real views.
-The existing static fixtures will be migrated over time.
-Please see [gitlab-org/gitlab-ce#24753](https://gitlab.com/gitlab-org/gitlab-ce/issues/24753) to track our progress.
-Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
+framework for our JavaScript unit and integration tests.
JavaScript tests live in `spec/javascripts/`, matching the folder structure
of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js`
@@ -134,7 +178,7 @@ placeholders, and recalling when they are called and the arguments that are
passed to them. These tools should be used liberally, to test for expected
behavior, to mock responses, and to block unwanted side effects (such as a
method that would generate a network request or alter `window.location`). The
-documentation for these methods can be found in the [jasmine introduction page](https://jasmine.github.io/2.0/introduction.html#section-Spies).
+documentation for these methods can be found in the [Jasmine introduction page](https://jasmine.github.io/2.0/introduction.html#section-Spies).
Sometimes you may need to spy on a method that is directly imported by another
module. GitLab has a custom `spyOnDependency` method which utilizes
@@ -149,6 +193,7 @@ export default function doSomething() {
visitUrl('/foo/bar');
}
```
+
```js
// my_module_spec.js
import doSomething from '~/my_module';
@@ -168,14 +213,202 @@ export of a module who's import you want to stub, rather than an object which
contains a method you wish to stub (if the module does not have a default
export, one is be generated by the babel plugin). The second parameter is the
name of the import you wish to change. The result of the function is a Spy
-object which can be treated like any other jasmine spy object.
+object which can be treated like any other Jasmine spy object.
Further documentation on the babel rewire pluign API can be found on
[its repository Readme doc](https://github.com/speedskater/babel-plugin-rewire#babel-plugin-rewire).
#### Waiting in tests
-If you cannot avoid using [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) in tests, please use the [Jasmine mock clock](https://jasmine.github.io/api/2.9/Clock.html).
+Sometimes a test needs to wait for something to happen in the application before it continues.
+Avoid using [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)
+because it makes the reason for waiting unclear and if passed a time larger than zero it will slow down our test suite.
+Instead use one of the following approaches.
+
+##### Promises and Ajax calls
+
+Register handler functions to wait for the `Promise` to be resolved.
+
+```javascript
+const askTheServer = () => {
+ return axios
+ .get('/endpoint')
+ .then(response => {
+ // do something
+ })
+ .catch(error => {
+ // do something else
+ });
+};
+```
+
+**in Jest:**
+
+```javascript
+it('waits for an Ajax call', () => {
+ return askTheServer().then(() => {
+ expect(something).toBe('done');
+ });
+});
+```
+
+**in Karma:**
+
+```javascript
+it('waits for an Ajax call', done => {
+ askTheServer()
+ .then(() => {
+ expect(something).toBe('done');
+ })
+ .then(done)
+ .catch(done.fail);
+});
+```
+
+If you are not able to register handlers to the `Promise`—for example because it is executed in a synchronous Vue life
+cycle hook—you can flush all pending `Promise`s:
+
+**in Jest:**
+
+```javascript
+it('waits for an Ajax call', () => {
+ synchronousFunction();
+ jest.runAllTicks();
+
+ expect(something).toBe('done');
+});
+```
+
+**in Karma:**
+
+You are out of luck. The following only works sometimes and may lead to flaky failures:
+
+```javascript
+it('waits for an Ajax call', done => {
+ synchronousFunction();
+
+ // create a new Promise and hope that it resolves after the rest
+ Promise.resolve()
+ .then(() => {
+ expect(something).toBe('done');
+ })
+ .then(done)
+ .catch(done.fail);
+});
+```
+
+##### Vue rendering
+
+To wait until a Vue component is re-rendered, use either of the equivalent
+[`Vue.nextTick()`](https://vuejs.org/v2/api/#Vue-nextTick) or `vm.$nextTick()`.
+
+**in Jest:**
+
+```javascript
+it('renders something', () => {
+ wrapper.setProps({ value: 'new value' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.text()).toBe('new value');
+ });
+});
+```
+
+**in Karma:**
+
+```javascript
+it('renders something', done => {
+ wrapper.setProps({ value: 'new value' });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.text()).toBe('new value');
+ })
+ .then(done)
+ .catch(done.fail);
+});
+```
+
+##### `setTimeout()` / `setInterval()` in application
+
+If the application itself is waiting for some time, mock await the waiting. In Jest this is already
+[done by default](https://gitlab.com/gitlab-org/gitlab-ce/blob/a2128edfee799e49a8732bfa235e2c5e14949c68/jest.config.js#L47)
+(see also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks)). In Karma you can use the
+[Jasmine mock clock](https://jasmine.github.io/api/2.9/Clock.html).
+
+```javascript
+const doSomethingLater = () => {
+ setTimeout(() => {
+ // do something
+ }, 4000);
+};
+```
+
+**in Jest:**
+
+```javascript
+it('does something', () => {
+ doSomethingLater();
+ jest.runAllTimers();
+
+ expect(something).toBe('done');
+});
+```
+
+**in Karma:**
+
+```javascript
+it('does something', () => {
+ jasmine.clock().install();
+
+ doSomethingLater();
+ jasmine.clock().tick(4000);
+
+ expect(something).toBe('done');
+ jasmine.clock().uninstall();
+});
+```
+
+##### Events
+
+If the application triggers an event that you need to wait for in your test, register an event handler which contains
+the assertions:
+
+```javascript
+it('waits for an event', done => {
+ eventHub.$once('someEvent', eventHandler);
+
+ someFunction();
+
+ function eventHandler() {
+ expect(something).toBe('done');
+ done();
+ }
+});
+```
+
+In Jest you can also use a `Promise` for this:
+
+```javascript
+it('waits for an event', () => {
+ const eventTriggered = new Promise(resolve => eventHub.$once('someEvent', resolve));
+
+ someFunction();
+
+ return eventTriggered.then(() => {
+ expect(something).toBe('done');
+ });
+});
+```
+
+#### Migrating flaky Karma tests to Jest
+
+Some of our Karma tests are flaky because they access the properties of a shared scope.
+This also means that they are not easily parallelized.
+
+Migrating flaky Karma tests to Jest will help significantly as each test is executed
+in an isolated scope, improving performance and predictability.
### Vue.js unit tests
@@ -183,32 +416,30 @@ See this [section][vue-test].
### Running frontend tests
-`rake karma` runs the frontend-only (JavaScript) tests.
-It consists of two subtasks:
+For running the frontend tests, you need the following commands:
-- `rake karma:fixtures` (re-)generates fixtures
-- `rake karma:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates [fixtures](#frontend-test-fixtures).
+- `yarn test` executes the tests.
-As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`)
-is sufficient (and saves you some time).
+As long as the fixtures don't change, `yarn test` is sufficient (and saves you some time).
### Live testing and focused testing
-While developing locally, it may be helpful to keep karma running so that you
+While developing locally, it may be helpful to keep Karma running so that you
can get instant feedback on as you write tests and modify code. To do this
-you can start karma with `yarn run karma-start`. It will compile the javascript
+you can start Karma with `yarn run karma-start`. It will compile the javascript
assets and run a server at `http://localhost:9876/` where it will automatically
run the tests on any browser which connects to it. You can enter that url on
multiple browsers at once to have it run the tests on each in parallel.
-While karma is running, any changes you make will instantly trigger a recompile
+While Karma is running, any changes you make will instantly trigger a recompile
and retest of the entire test suite, so you can see instantly if you've broken
-a test with your changes. You can use [jasmine focused][jasmine-focus] or
-excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the
+a test with your changes. You can use [Jasmine focused][jasmine-focus] or
+excluded tests (with `fdescribe` or `xdescribe`) to get Karma to run only the
tests you want while you're working on a specific feature, but make sure to
remove these directives when you commit your code.
-It is also possible to only run karma on specific folders or files by filtering
+It is also possible to only run Karma on specific folders or files by filtering
the run tests via the argument `--filter-spec` or short `-f`:
```bash
@@ -235,26 +466,49 @@ yarn karma -f 'spec/javascripts/ide/**/file_spec.js'
Information on setting up and running RSpec integration tests with
[Capybara] can be found in the [Testing Best Practices](best_practices.md).
-## Gotchas
+## Frontend test fixtures
-### Errors due to use of unsupported JavaScript features
+Code that is added to HAML templates (in `app/views/`) or makes Ajax requests to the backend has tests that require HTML or JSON from the backend.
+Fixtures for these tests are located at:
-Similar errors will be thrown if you're using JavaScript features not yet
-supported by the PhantomJS test runner which is used for both Karma and RSpec
-tests. We polyfill some JavaScript objects for older browsers, but some
-features are still unavailable:
+- `spec/javascripts/fixtures/`, for running tests in CE.
+- `ee/spec/javascripts/fixtures/`, for running tests in EE.
-- Array.from
-- Array.first
-- Async functions
-- Generators
-- Array destructuring
-- For..Of
-- Symbol/Symbol.iterator
-- Spread
+Fixture files in:
-Until these are polyfilled appropriately, they should not be used. Please
-update this list with additional unsupported features.
+- The Karma test suite are served by [jasmine-jquery](https://github.com/velesin/jasmine-jquery).
+- Jest use `spec/frontend/helpers/fixtures.js`.
+
+The following are examples of tests that work for both Karma and Jest:
+
+```javascript
+it('makes a request', () => {
+ const responseBody = getJSONFixture('some/fixture.json'); // loads spec/javascripts/fixtures/some/fixture.json
+ axiosMock.onGet(endpoint).reply(200, responseBody);
+
+ myButton.click();
+
+ // ...
+});
+
+it('uses some HTML element', () => {
+ loadFixtures('some/page.html'); // loads spec/javascripts/fixtures/some/page.html and adds it to the DOM
+
+ const element = document.getElementById('#my-id');
+
+ // ...
+});
+```
+
+HTML and JSON fixtures are generated from backend views and controllers using RSpec (see `spec/javascripts/fixtures/*.rb`).
+
+For each fixture, the content of the `response` variable is stored in the output file.
+This variable gets automagically set if the test is marked as `type: :request` or `type: :controller`.
+Fixtures are regenerated using the `bin/rake karma:fixtures` command but you can also generate them individually,
+for example `bin/rspec spec/javascripts/fixtures/merge_requests.rb`.
+When creating a new fixture, it often makes sense to take a look at the corresponding tests for the endpoint in `(ee/)spec/controllers/` or `(ee/)spec/requests/`.
+
+## Gotchas
### RSpec errors due to JavaScript
@@ -286,7 +540,6 @@ end
```
[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html
-[jasmine-jquery]: https://github.com/velesin/jasmine-jquery
[karma]: http://karma-runner.github.io/
[vue-test]: https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components
[rspec]: https://github.com/rspec/rspec-rails#feature-specs
diff --git a/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
new file mode 100644
index 00000000000..5b93a05db96
--- /dev/null
+++ b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
Binary files differ
diff --git a/doc/development/testing_guide/img/review_apps_cicd_architecture.png b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
index 87e472076f3..1ee28d3db91 100644
--- a/doc/development/testing_guide/img/review_apps_cicd_architecture.png
+++ b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
Binary files differ
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 67e4cfeda0e..93ee2a6371a 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -33,7 +33,7 @@ changes should be tested.
## [Testing best practices](best_practices.md)
-Everything you should know about how to write good tests: RSpec, FactoryBot,
+Everything you should know about how to write good tests: Test Design, RSpec, FactoryBot,
system tests, parameterized tests etc.
---
@@ -71,7 +71,7 @@ Everything you should know about how to test Rake tasks.
---
-## [End-to-end tests](end_to_end_tests.md)
+## [End-to-end tests](end_to_end/index.md)
Everything you should know about how to run end-to-end tests using
[GitLab QA][gitlab-qa] testing framework.
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 703e342fc13..b5fde92a23d 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -6,7 +6,7 @@ Review Apps are automatically deployed by each pipeline, both in
## How does it work?
-### CD/CD architecture diagram
+### CI/CD architecture diagram
![Review Apps CI/CD architecture](img/review_apps_cicd_architecture.png)
@@ -14,23 +14,29 @@ Review Apps are automatically deployed by each pipeline, both in
<summary>Show mermaid source</summary>
<pre>
graph TD
- B1 -.->|2. once gitlab:assets:compile is done,<br />triggers a CNG-mirror pipeline and wait for it to be done| A2
- C1 -.->|2. once review-build-cng is done,<br />Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline| A3
-
-subgraph gitlab-ce/ee `test` stage
- A1[gitlab:assets:compile]
- B1[review-build-cng] -->|1. wait for| A1
- C1[review-deploy] -->|1. wait for| B1
- D1[review-qa-smoke] -->|1. wait for| C1
- D1[review-qa-smoke] -.->|2. once review-deploy is done| E1>gitlab-qa runs the smoke<br/>suite against the Review App]
+ build-qa-image -.->|once the `prepare` stage is done| gitlab:assets:compile
+ review-build-cng -->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror
+ review-build-cng -.->|once the `test` stage is done| review-deploy
+ review-deploy -.->|once the `review` stage is done| review-qa-smoke
+
+subgraph 1. gitlab-ce/ee `prepare` stage
+ build-qa-image
end
-subgraph CNG-mirror pipeline
- A2>Cloud Native images are built];
+subgraph 2. gitlab-ce/ee `test` stage
+ gitlab:assets:compile -->|plays dependent job once done| review-build-cng
+ end
+
+subgraph 3. gitlab-ce/ee `review` stage
+ review-deploy["review-deploy<br /><br />Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br /><br />Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br />Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
end
-subgraph GCP `gitlab-review-apps` project
- A3>"Cloud Native images are deployed to the<br />`review-apps-ce` or `review-apps-ee` Kubernetes (GKE) cluster"];
+subgraph 4. gitlab-ce/ee `qa` stage
+ review-qa-smoke[review-qa-smoke<br /><br />gitlab-qa runs the smoke suite against the Review App.]
+ end
+
+subgraph CNG-mirror pipeline
+ CNG-mirror>Cloud Native images are built];
end
</pre>
</details>
@@ -38,107 +44,120 @@ subgraph GCP `gitlab-review-apps` project
### Detailed explanation
1. On every [pipeline][gitlab-pipeline] during the `test` stage, the
- [`review-build-cng`][review-build-cng] and
- [`review-deploy`][review-deploy] jobs are automatically started.
- - The [`review-deploy`][review-deploy] job waits for the
- [`review-build-cng`][review-build-cng] job to finish.
- - The [`review-build-cng`][review-build-cng] job waits for the
- [`gitlab:assets:compile`][gitlab:assets:compile] job to finish since the
- [`CNG-mirror`][cng-mirror] pipeline triggered in the following step depends on it.
-1. Once the [`gitlab:assets:compile`][gitlab:assets:compile] job is done,
- [`review-build-cng`][review-build-cng] [triggers a pipeline][cng-pipeline]
- in the [`CNG-mirror`][cng-mirror] project.
- - The [`CNG-mirror`][cng-pipeline] pipeline creates the Docker images of
- each component (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.)
- based on the commit from the [GitLab pipeline][gitlab-pipeline] and store
- them in its [registry][cng-mirror-registry].
- - We use the [`CNG-mirror`][cng-mirror] project so that the `CNG`, (**C**loud
- **N**ative **G**itLab), project's registry is not overloaded with a
- lot of transient Docker images.
-1. Once the [`review-build-cng`][review-build-cng] job is done, the
- [`review-deploy`][review-deploy] job deploys the Review App using
- [the official GitLab Helm chart][helm-chart] to the
- [`review-apps-ce`][review-apps-ce] / [`review-apps-ee`][review-apps-ee]
- Kubernetes cluster on GCP.
- - The actual scripts used to deploy the Review App can be found at
- [`scripts/review_apps/review-apps.sh`][review-apps.sh].
- - These scripts are basically
- [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
- default CNG images are overridden with the images built and stored in the
- [`CNG-mirror` project's registry][cng-mirror-registry].
- - Since we're using [the official GitLab Helm chart][helm-chart], this means
- you get a dedicated environment for your branch that's very close to what
- it would look in production.
+ [`gitlab:assets:compile`][gitlab:assets:compile] job is automatically started.
+ - Once it's done, it starts the [`review-build-cng`][review-build-cng]
+ manual job since the [`CNG-mirror`][cng-mirror] pipeline triggered in the
+ following step depends on it.
+1. The [`review-build-cng`][review-build-cng] job [triggers a pipeline][cng-mirror-pipeline]
+ in the [`CNG-mirror`][cng-mirror] project.
+ - The [`CNG-mirror`][cng-mirror-pipeline] pipeline creates the Docker images of
+ each component (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.)
+ based on the commit from the [GitLab pipeline][gitlab-pipeline] and stores
+ them in its [registry][cng-mirror-registry].
+ - We use the [`CNG-mirror`][cng-mirror] project so that the `CNG`, (**C**loud
+ **N**ative **G**itLab), project's registry is not overloaded with a
+ lot of transient Docker images.
+ - Note that the official CNG images are built by the `cloud-native-image`
+ job, which runs only for tags, and triggers itself a [`CNG`][cng] pipeline.
+1. Once the `test` stage is done, the [`review-deploy`][review-deploy] job
+ deploys the Review App using [the official GitLab Helm chart][helm-chart] to
+ the [`review-apps-ce`][review-apps-ce] / [`review-apps-ee`][review-apps-ee]
+ Kubernetes cluster on GCP.
+ - The actual scripts used to deploy the Review App can be found at
+ [`scripts/review_apps/review-apps.sh`][review-apps.sh].
+ - These scripts are basically
+ [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
+ default CNG images are overridden with the images built and stored in the
+ [`CNG-mirror` project's registry][cng-mirror-registry].
+ - Since we're using [the official GitLab Helm chart][helm-chart], this means
+ you get a dedicated environment for your branch that's very close to what
+ it would look in production.
1. Once the [`review-deploy`][review-deploy] job succeeds, you should be able to
- use your Review App thanks to the direct link to it from the MR widget. To log
- into the Review App, see "Log into my Review App?" below.
+ use your Review App thanks to the direct link to it from the MR widget. To log
+ into the Review App, see "Log into my Review App?" below.
**Additional notes:**
+- If the `review-deploy` job keep failing (note that we already retry it twice),
+ please post a message in the `#quality` channel and/or create a ~Quality ~bug
+ issue with a link to your merge request. Note that the deployment failure can
+ reveal an actual problem introduced in your merge request (i.e. this isn't
+ necessarily a transient failure)!
+- If the `review-qa-smoke` job keep failing (note that we already retry it twice),
+ please check the job's logs: you could discover an actual problem introduced in
+ your merge request. You can also download the artifacts to see screenshots of
+ the page at the time the failures occurred. If you don't find the cause of the
+ failure or if it seems unrelated to your change, please post a message in the
+ `#quality` channel and/or create a ~Quality ~bug issue with a link to your
+ merge request.
+- The manual [`review-stop`][gitlab-ci-yml] in the `test` stage can be used to
+ stop a Review App manually, and is also started by GitLab once a merge
+ request's branch is deleted after being merged.
+- Review Apps are cleaned up regularly via a pipeline schedule that runs
+ the [`schedule:review-cleanup`][gitlab-ci-yml] job.
- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using
[GitLab's Kubernetes integration][gitlab-k8s-integration]. This basically
allows to have a link to the Review App directly from the merge request
widget.
-- If the Review App deployment fails, you can simply retry it (there's no need
- to run the [`review-stop`][gitlab-ci-yml] job first).
-- The manual [`review-stop`][gitlab-ci-yml] in the `test` stage can be used to
- stop a Review App manually, and is also started by GitLab once a branch is
- deleted.
-- Review Apps are cleaned up regularly using a pipeline schedule that runs
- the [`schedule:review-cleanup`][gitlab-ci-yml] job.
## QA runs
-On every [pipeline][gitlab-pipeline] during the `test` stage, the
-`review-qa-smoke` job is automatically started: it runs the smoke QA suite.
+On every [pipeline][gitlab-pipeline] in the `qa` stage (which comes after the
+`review` stage), the `review-qa-smoke` job is automatically started and it runs
+the QA smoke suite.
+
You can also manually start the `review-qa-all`: it runs the full QA suite.
-Note that both jobs first wait for the `review-deploy` job to be finished.
+## Performance Metrics
-## How to?
+On every [pipeline][gitlab-pipeline] in the `qa` stage, the
+`review-performance` job is automatically started: this job does basic
+browser performance testing using a
+[Sitespeed.io Container](../../user/project/merge_requests/browser_performance_testing.md).
-### Log into my Review App?
+## How to:
+
+### Log into my Review App
The default username is `root` and its password can be found in the 1Password
secure note named **gitlab-{ce,ee} Review App's root password**.
-### Enable a feature flag for my Review App?
+### Enable a feature flag for my Review App
1. Open your Review App and log in as documented above.
1. Create a personal access token.
1. Enable the feature flag using the [Feature flag API](../../api/features.md).
-### Find my Review App slug?
+### Find my Review App slug
1. Open the `review-deploy` job.
1. Look for `Checking for previous deployment of review-*`.
1. For instance for `Checking for previous deployment of review-qa-raise-e-12chm0`,
- your Review App slug would be `review-qa-raise-e-12chm0` in this case.
+ your Review App slug would be `review-qa-raise-e-12chm0` in this case.
-### Run a Rails console?
+### Run a Rails console
1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
- , e.g. `review-29951-issu-id2qax`.
-1. Find and open the `task-runner` Deployment, e.g. `review-29951-issu-id2qax-task-runner`.
-1. Click on the Pod in the "Managed pods" section, e.g. `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`.
+ , e.g. `review-qa-raise-e-12chm0`.
+1. Find and open the `task-runner` Deployment, e.g. `review-qa-raise-e-12chm0-task-runner`.
+1. Click on the Pod in the "Managed pods" section, e.g. `review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz`.
1. Click on the `KUBECTL` dropdown, then `Exec` -> `task-runner`.
-1. Replace `-c task-runner -- ls` with `-- /srv/gitlab/bin/rails c` from the
- default command or
- - Run `kubectl exec --namespace review-apps-ce -it review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz -- /srv/gitlab/bin/rails c`
- and
- - Replace `review-apps-ce` with `review-apps-ee` if the Review App
- is running EE, and
- - Replace `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`
- with your Pod's name.
-
-### Dig into a Pod's logs?
-
-1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
- , e.g. `review-1979-1-mul-dnvlhv`.
+1. Replace `-c task-runner -- ls` with `-it -- gitlab-rails console` from the
+ default command or
+ - Run `kubectl exec --namespace review-apps-ce review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz -it -- gitlab-rails console` and
+ - Replace `review-apps-ce` with `review-apps-ee` if the Review App
+ is running EE, and
+ - Replace `review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz`
+ with your Pod's name.
+
+### Dig into a Pod's logs
+
+1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps),
+ e.g. `review-qa-raise-e-12chm0`.
1. Find and open the `migrations` Deployment, e.g.
- `review-1979-1-mul-dnvlhv-migrations.1`.
+ `review-qa-raise-e-12chm0-migrations.1`.
1. Click on the Pod in the "Managed pods" section, e.g.
- `review-1979-1-mul-dnvlhv-migrations.1-nqwtx`.
+ `review-qa-raise-e-12chm0-migrations.1-nqwtx`.
1. Click on the `Container logs` link.
## Frequently Asked Questions
@@ -174,7 +193,8 @@ find a way to limit it to only us.**
[review-build-cng]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511623
[review-deploy]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511624
[cng-mirror]: https://gitlab.com/gitlab-org/build/CNG-mirror
-[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657
+[cng]: https://gitlab.com/gitlab-org/build/CNG
+[cng-mirror-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657
[cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry
[helm-chart]: https://gitlab.com/charts/gitlab/
[review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps
@@ -183,7 +203,7 @@ find a way to limit it to only us.**
[automated_cleanup.rb]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/automated_cleanup.rb
[Auto-DevOps.gitlab-ci.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
[gitlab-ci-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml
-[gitlab-k8s-integration]: https://docs.gitlab.com/ee/user/project/clusters/index.html
+[gitlab-k8s-integration]: ../../user/project/clusters/index.md
[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621
---
diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md
index 3360031c220..c9d3238fbe9 100644
--- a/doc/development/testing_guide/smoke.md
+++ b/doc/development/testing_guide/smoke.md
@@ -7,13 +7,19 @@ functionality is working.
Currently, our suite consists of this basic functionality coverage:
-- User Login (Standard Auth)
-- Project Creation
-- Issue Creation
-- Merge Request Creation
+- User standard authentication
+- SSH Key creation and addition to a user
+- Project simple creation
+- Project creation with Auto-DevOps enabled
+- Issue creation
+- Merge Request creation
+- Snippet creation
Smoke tests have the `:smoke` RSpec metadata.
+See [End-to-end Testing](end_to_end/index.md) for more details about
+end-to-end tests.
+
---
[Return to Testing documentation](index.md)
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index a7a3459719b..e1ce4d3b7d1 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -4,6 +4,15 @@
_This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._
+As of 2019-05-01, we have the following distribution of tests per level:
+
+| Test level | Community Edition | Enterprise Edition | Community + Enterprise Edition |
+| --------- | ---------- | -------------- | ----- |
+| Black-box tests at the system level (aka end-to-end or QA tests) | 68 (0.14%) | 31 (0.2%) | 99 (0.17%) |
+| White-box tests at the system level (aka system or feature tests) | 5,471 (11.9%) | 969 (7.4%) | 6440 (10.9%) |
+| Integration tests | 8,333 (18.2%) | 2,244 (17.2%) | 10,577 (17.9%) |
+| Unit tests | 32,031 (69.7%) | 9,778 (75.1%) | 41,809 (71%) |
+
## Unit tests
Formal definition: <https://en.wikipedia.org/wiki/Unit_testing>
@@ -16,19 +25,31 @@ records should use stubs/doubles as much as possible.
| Code path | Tests path | Testing engine | Notes |
| --------- | ---------- | -------------- | ----- |
+| `app/assets/javascripts/` | `spec/javascripts/`, `spec/frontend/` | Karma & Jest | More details in the [Frontend Testing guide](frontend_testing.md) section. |
| `app/finders/` | `spec/finders/` | RSpec | |
+| `app/graphql/` | `spec/graphql/` | RSpec | |
| `app/helpers/` | `spec/helpers/` | RSpec | |
-| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | More details at [`spec/migrations/README.md`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md). |
+| `app/models/` | `spec/models/` | RSpec | |
| `app/policies/` | `spec/policies/` | RSpec | |
| `app/presenters/` | `spec/presenters/` | RSpec | |
-| `app/routing/` | `spec/routing/` | RSpec | |
| `app/serializers/` | `spec/serializers/` | RSpec | |
| `app/services/` | `spec/services/` | RSpec | |
-| `app/tasks/` | `spec/tasks/` | RSpec | |
| `app/uploaders/` | `spec/uploaders/` | RSpec | |
+| `app/validators/` | `spec/validators/` | RSpec | |
| `app/views/` | `spec/views/` | RSpec | |
| `app/workers/` | `spec/workers/` | RSpec | |
-| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [Frontend Testing guide](frontend_testing.md) section. |
+| `bin/` | `spec/bin/` | RSpec | |
+| `config/` | `spec/config/` | RSpec | |
+| `config/initializers/` | `spec/initializers/` | RSpec | |
+| `config/routes.rb`, `config/routes/` | `spec/routing/` | RSpec | |
+| `config/puma.example.development.rb`, `config/unicorn.rb.example` | `spec/rack_servers/` | RSpec | |
+| `db/` | `spec/db/` | RSpec | |
+| `db/{post_,}migrate/` | `spec/migrations/` | RSpec | More details at [`spec/migrations/README.md`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md). |
+| `Gemfile` | `spec/dependencies/`, `spec/sidekiq/` | RSpec | |
+| `lib/` | `spec/lib/` | RSpec | |
+| `lib/tasks/` | `spec/tasks/` | RSpec | |
+| `rubocop/` | `spec/rubocop/` | RSpec | |
+| `spec/factories` | `spec/factories_spec.rb` | RSpec | |
## Integration tests
@@ -46,7 +67,7 @@ They're useful to test permissions, redirections, what view is rendered etc.
| `app/mailers/` | `spec/mailers/` | RSpec | |
| `lib/api/` | `spec/requests/api/` | RSpec | |
| `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | |
-| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. |
+| `app/assets/javascripts/` | `spec/javascripts/`, `spec/frontend/` | Karma & Jest | More details in the [Frontend Testing guide](frontend_testing.md) section. |
### About controller tests
@@ -157,10 +178,10 @@ Every new feature should come with a [test plan].
| ---------- | -------------- | ----- |
| `qa/qa/specs/features/` | [Capybara] + [RSpec] + Custom QA framework | Tests should be placed under their corresponding [Product category] |
-> See [end-to-end tests](end_to_end_tests.md) for more information.
+> See [end-to-end tests](end_to_end/index.md) for more information.
Note that `qa/spec` contains unit tests of the QA framework itself, not to be
-confused with the application's [unit tests](#unit-tests) or
+confused with the application's [unit tests](#unit-tests) or
[end-to-end tests](#black-box-tests-at-the-system-level-aka-end-to-end-tests).
[multiple pieces]: ../architecture.md#components
@@ -210,7 +231,7 @@ trade-off:
- 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)
+ a JavaScript driver. Make sure to follow the guidelines in the [Speed](best_practices.md#test-speed)
section.
Another way to see it is to think about the "cost of tests", this is well
@@ -234,6 +255,8 @@ you should write an integration test using Jasmine.
[big]: https://twitter.com/timbray/status/822470746773409794
[picture]: https://twitter.com/withzombies/status/829716565834752000
[tests-cost]: https://medium.com/table-xi/high-cost-tests-and-high-value-tests-a86e27a54df#.2ulyh3a4e
+[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
+[Capybara]: https://github.com/teamcapybara/capybara
---
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 01a0044f096..bfbb7be70e3 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -80,8 +80,9 @@ Planning time: 2.861 ms
Execution time: 3428.596 ms
```
-For more information, refer to the official [EXPLAIN
-documentation](https://www.postgresql.org/docs/current/static/sql-explain.html).
+For more information, refer to the official
+[`EXPLAIN` documentation](https://www.postgresql.org/docs/current/sql-explain.html)
+and [using `EXPLAIN` guide](https://www.postgresql.org/docs/current/using-explain.html).
## Nodes
@@ -653,9 +654,39 @@ and related tools such as:
- <https://explain.depesz.com/>
- <http://tatiyants.com/postgres-query-plan-visualization/>
-GitLab employees can also use our chatops solution, available in Slack using the
-`/chatops` slash command. You can use chatops to get a query plan by running the
-following:
+
+## Producing query plans
+
+There are a few ways to get the output of a query plan. Of course you
+can directly run the `EXPLAIN` query in the `psql` console, or you can
+follow one of the other options below.
+
+### Rails console
+
+Using the [`activerecord-explain-analyze`](https://github.com/6/activerecord-explain-analyze)
+you can directly generate the query plan from the Rails console:
+
+```ruby
+pry(main)> require 'activerecord-explain-analyze'
+=> true
+pry(main)> Project.where('build_timeout > ?', 3600).explain(analyze: true)
+ Project Load (1.9ms) SELECT "projects".* FROM "projects" WHERE (build_timeout > 3600)
+ ↳ (pry):12
+=> EXPLAIN for: SELECT "projects".* FROM "projects" WHERE (build_timeout > 3600)
+Seq Scan on public.projects (cost=0.00..2.17 rows=1 width=742) (actual time=0.040..0.041 rows=0 loops=1)
+ Output: id, name, path, description, created_at, updated_at, creator_id, namespace_id, ...
+ Filter: (projects.build_timeout > 3600)
+ Rows Removed by Filter: 14
+ Buffers: shared hit=2
+Planning time: 0.411 ms
+Execution time: 0.113 ms
+```
+
+### Chatops
+
+[GitLab employees can also use our chatops solution, available in Slack using the
+`/chatops` slash command](chatops_on_gitlabcom.md).
+You can use chatops to get a query plan by running the following:
```
/chatops run explain SELECT COUNT(*) FROM projects WHERE visibility_level IN (0, 20)
@@ -674,3 +705,9 @@ For more information about the available options, run:
```
/chatops run explain --help
```
+
+## Further reading
+
+A more extensive guide on understanding query plans can be found in
+the [presentation](https://www.dalibo.org/_media/understanding_explain.pdf)
+from [Dalibo.org](https://www.dalibo.org/en/).
diff --git a/doc/development/ux_guide/img/animation-autoscroll.gif b/doc/development/ux_guide/img/animation-autoscroll.gif
deleted file mode 100644
index 155b0234c64..00000000000
--- a/doc/development/ux_guide/img/animation-autoscroll.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/animation-dropdown.gif b/doc/development/ux_guide/img/animation-dropdown.gif
deleted file mode 100644
index c9b31d26165..00000000000
--- a/doc/development/ux_guide/img/animation-dropdown.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/animation-hover.gif b/doc/development/ux_guide/img/animation-hover.gif
deleted file mode 100644
index 37ad9c76828..00000000000
--- a/doc/development/ux_guide/img/animation-hover.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/animation-quickupdate.gif b/doc/development/ux_guide/img/animation-quickupdate.gif
deleted file mode 100644
index 8db70bc3d24..00000000000
--- a/doc/development/ux_guide/img/animation-quickupdate.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/animation-reorder.gif b/doc/development/ux_guide/img/animation-reorder.gif
deleted file mode 100644
index ccdeb3d396f..00000000000
--- a/doc/development/ux_guide/img/animation-reorder.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-close--active.png b/doc/development/ux_guide/img/button-close--active.png
deleted file mode 100644
index 97a5301fb91..00000000000
--- a/doc/development/ux_guide/img/button-close--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-close--hover.png b/doc/development/ux_guide/img/button-close--hover.png
deleted file mode 100644
index 6b8fdf5695b..00000000000
--- a/doc/development/ux_guide/img/button-close--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-close--resting.png b/doc/development/ux_guide/img/button-close--resting.png
deleted file mode 100644
index 5679b51687c..00000000000
--- a/doc/development/ux_guide/img/button-close--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-danger--active.png b/doc/development/ux_guide/img/button-danger--active.png
deleted file mode 100644
index 6a9aab0fcc2..00000000000
--- a/doc/development/ux_guide/img/button-danger--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-danger--hover.png b/doc/development/ux_guide/img/button-danger--hover.png
deleted file mode 100644
index 13e21c28779..00000000000
--- a/doc/development/ux_guide/img/button-danger--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-danger--resting.png b/doc/development/ux_guide/img/button-danger--resting.png
deleted file mode 100644
index 0ff192bc463..00000000000
--- a/doc/development/ux_guide/img/button-danger--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-info--active.png b/doc/development/ux_guide/img/button-info--active.png
deleted file mode 100644
index 12ecdc72a31..00000000000
--- a/doc/development/ux_guide/img/button-info--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-info--hover.png b/doc/development/ux_guide/img/button-info--hover.png
deleted file mode 100644
index 3bf93bf2b32..00000000000
--- a/doc/development/ux_guide/img/button-info--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-info--resting.png b/doc/development/ux_guide/img/button-info--resting.png
deleted file mode 100644
index a37a37033bf..00000000000
--- a/doc/development/ux_guide/img/button-info--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-primary.png b/doc/development/ux_guide/img/button-primary.png
deleted file mode 100644
index eda5ed84aec..00000000000
--- a/doc/development/ux_guide/img/button-primary.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-secondary.png b/doc/development/ux_guide/img/button-secondary.png
deleted file mode 100644
index 26d4e8cf43d..00000000000
--- a/doc/development/ux_guide/img/button-secondary.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-spam--active.png b/doc/development/ux_guide/img/button-spam--active.png
deleted file mode 100644
index a9e115f49c1..00000000000
--- a/doc/development/ux_guide/img/button-spam--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-spam--hover.png b/doc/development/ux_guide/img/button-spam--hover.png
deleted file mode 100644
index 3b2c16430a6..00000000000
--- a/doc/development/ux_guide/img/button-spam--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-spam--resting.png b/doc/development/ux_guide/img/button-spam--resting.png
deleted file mode 100644
index 4f9f18ca68a..00000000000
--- a/doc/development/ux_guide/img/button-spam--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success--active.png b/doc/development/ux_guide/img/button-success--active.png
deleted file mode 100644
index b99f6f5e70e..00000000000
--- a/doc/development/ux_guide/img/button-success--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success--hover.png b/doc/development/ux_guide/img/button-success--hover.png
deleted file mode 100644
index 0d0a61c679a..00000000000
--- a/doc/development/ux_guide/img/button-success--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success--resting.png b/doc/development/ux_guide/img/button-success--resting.png
deleted file mode 100644
index 53b955c650a..00000000000
--- a/doc/development/ux_guide/img/button-success--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success-secondary--active.png b/doc/development/ux_guide/img/button-success-secondary--active.png
deleted file mode 100644
index 333a91f2217..00000000000
--- a/doc/development/ux_guide/img/button-success-secondary--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success-secondary--hover.png b/doc/development/ux_guide/img/button-success-secondary--hover.png
deleted file mode 100644
index 0cce59212e3..00000000000
--- a/doc/development/ux_guide/img/button-success-secondary--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-success-secondary--resting.png b/doc/development/ux_guide/img/button-success-secondary--resting.png
deleted file mode 100644
index 2779a4949f8..00000000000
--- a/doc/development/ux_guide/img/button-success-secondary--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-warning--active.png b/doc/development/ux_guide/img/button-warning--active.png
deleted file mode 100644
index f5760cd7c12..00000000000
--- a/doc/development/ux_guide/img/button-warning--active.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-warning--hover.png b/doc/development/ux_guide/img/button-warning--hover.png
deleted file mode 100644
index a1f4c5cbcc6..00000000000
--- a/doc/development/ux_guide/img/button-warning--hover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/button-warning--resting.png b/doc/development/ux_guide/img/button-warning--resting.png
deleted file mode 100644
index 3d62fed5930..00000000000
--- a/doc/development/ux_guide/img/button-warning--resting.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-blue.png b/doc/development/ux_guide/img/color-blue.png
deleted file mode 100644
index 77c1a2cab31..00000000000
--- a/doc/development/ux_guide/img/color-blue.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-green.png b/doc/development/ux_guide/img/color-green.png
deleted file mode 100644
index 51600584c96..00000000000
--- a/doc/development/ux_guide/img/color-green.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-grey.png b/doc/development/ux_guide/img/color-grey.png
deleted file mode 100644
index f0f0b9d80bb..00000000000
--- a/doc/development/ux_guide/img/color-grey.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-orange.png b/doc/development/ux_guide/img/color-orange.png
deleted file mode 100644
index f16435c0a64..00000000000
--- a/doc/development/ux_guide/img/color-orange.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-red.png b/doc/development/ux_guide/img/color-red.png
deleted file mode 100644
index 5008e75da78..00000000000
--- a/doc/development/ux_guide/img/color-red.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-textprimary.png b/doc/development/ux_guide/img/color-textprimary.png
deleted file mode 100644
index 90f2821f0cf..00000000000
--- a/doc/development/ux_guide/img/color-textprimary.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/color-textsecondary.png b/doc/development/ux_guide/img/color-textsecondary.png
deleted file mode 100644
index 61cb8a13c45..00000000000
--- a/doc/development/ux_guide/img/color-textsecondary.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-alerts.png b/doc/development/ux_guide/img/components-alerts.png
deleted file mode 100644
index 66a43ac69e1..00000000000
--- a/doc/development/ux_guide/img/components-alerts.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-anchorlinks.png b/doc/development/ux_guide/img/components-anchorlinks.png
deleted file mode 100644
index bd8d30f5905..00000000000
--- a/doc/development/ux_guide/img/components-anchorlinks.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-contentblock.png b/doc/development/ux_guide/img/components-contentblock.png
deleted file mode 100644
index 58d87729701..00000000000
--- a/doc/development/ux_guide/img/components-contentblock.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-counts.png b/doc/development/ux_guide/img/components-counts.png
deleted file mode 100644
index 19280e988a0..00000000000
--- a/doc/development/ux_guide/img/components-counts.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-coverblock.png b/doc/development/ux_guide/img/components-coverblock.png
deleted file mode 100644
index 61160de5613..00000000000
--- a/doc/development/ux_guide/img/components-coverblock.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-dateexact.png b/doc/development/ux_guide/img/components-dateexact.png
deleted file mode 100644
index cc1fb8216bf..00000000000
--- a/doc/development/ux_guide/img/components-dateexact.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-daterelative.png b/doc/development/ux_guide/img/components-daterelative.png
deleted file mode 100644
index 4954dfb51b3..00000000000
--- a/doc/development/ux_guide/img/components-daterelative.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-dropdown.png b/doc/development/ux_guide/img/components-dropdown.png
deleted file mode 100644
index 7f9a701c089..00000000000
--- a/doc/development/ux_guide/img/components-dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-fileholder.png b/doc/development/ux_guide/img/components-fileholder.png
deleted file mode 100644
index 5bf8565346a..00000000000
--- a/doc/development/ux_guide/img/components-fileholder.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-horizontalform.png b/doc/development/ux_guide/img/components-horizontalform.png
deleted file mode 100644
index e6cbc69d20a..00000000000
--- a/doc/development/ux_guide/img/components-horizontalform.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-listinsidepanel.png b/doc/development/ux_guide/img/components-listinsidepanel.png
deleted file mode 100644
index 6b773a19954..00000000000
--- a/doc/development/ux_guide/img/components-listinsidepanel.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-listwithavatar.png b/doc/development/ux_guide/img/components-listwithavatar.png
deleted file mode 100644
index f6db575433c..00000000000
--- a/doc/development/ux_guide/img/components-listwithavatar.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-listwithhover.png b/doc/development/ux_guide/img/components-listwithhover.png
deleted file mode 100644
index 0826848ff34..00000000000
--- a/doc/development/ux_guide/img/components-listwithhover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-panels.png b/doc/development/ux_guide/img/components-panels.png
deleted file mode 100644
index c1391ca07e5..00000000000
--- a/doc/development/ux_guide/img/components-panels.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referencehover.png b/doc/development/ux_guide/img/components-referencehover.png
deleted file mode 100644
index af5405d3e0b..00000000000
--- a/doc/development/ux_guide/img/components-referencehover.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referenceissues.png b/doc/development/ux_guide/img/components-referenceissues.png
deleted file mode 100644
index 4e175dc169d..00000000000
--- a/doc/development/ux_guide/img/components-referenceissues.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referencelabels.png b/doc/development/ux_guide/img/components-referencelabels.png
deleted file mode 100644
index 29a985bbaa0..00000000000
--- a/doc/development/ux_guide/img/components-referencelabels.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referencemilestone.png b/doc/development/ux_guide/img/components-referencemilestone.png
deleted file mode 100644
index 47c76a9d60f..00000000000
--- a/doc/development/ux_guide/img/components-referencemilestone.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referencemrs.png b/doc/development/ux_guide/img/components-referencemrs.png
deleted file mode 100644
index 9a5032a1516..00000000000
--- a/doc/development/ux_guide/img/components-referencemrs.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-referencepeople.png b/doc/development/ux_guide/img/components-referencepeople.png
deleted file mode 100644
index f9ef11be853..00000000000
--- a/doc/development/ux_guide/img/components-referencepeople.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-rowcontentblock.png b/doc/development/ux_guide/img/components-rowcontentblock.png
deleted file mode 100644
index c66a50f9564..00000000000
--- a/doc/development/ux_guide/img/components-rowcontentblock.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-searchbox.png b/doc/development/ux_guide/img/components-searchbox.png
deleted file mode 100644
index 5c19024bfb0..00000000000
--- a/doc/development/ux_guide/img/components-searchbox.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-searchboxscoped.png b/doc/development/ux_guide/img/components-searchboxscoped.png
deleted file mode 100644
index d4a35977658..00000000000
--- a/doc/development/ux_guide/img/components-searchboxscoped.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-simplelist.png b/doc/development/ux_guide/img/components-simplelist.png
deleted file mode 100644
index 8d11c674e84..00000000000
--- a/doc/development/ux_guide/img/components-simplelist.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-table.png b/doc/development/ux_guide/img/components-table.png
deleted file mode 100644
index cedc55758a9..00000000000
--- a/doc/development/ux_guide/img/components-table.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/components-verticalform.png b/doc/development/ux_guide/img/components-verticalform.png
deleted file mode 100644
index 489ae6f862f..00000000000
--- a/doc/development/ux_guide/img/components-verticalform.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-default.png b/doc/development/ux_guide/img/cursors-default.png
deleted file mode 100644
index c188ec4e351..00000000000
--- a/doc/development/ux_guide/img/cursors-default.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-ibeam.png b/doc/development/ux_guide/img/cursors-ibeam.png
deleted file mode 100644
index 86f28639982..00000000000
--- a/doc/development/ux_guide/img/cursors-ibeam.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-move.png b/doc/development/ux_guide/img/cursors-move.png
deleted file mode 100644
index a9c610eaa88..00000000000
--- a/doc/development/ux_guide/img/cursors-move.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-panclosed.png b/doc/development/ux_guide/img/cursors-panclosed.png
deleted file mode 100644
index 6d247a765ac..00000000000
--- a/doc/development/ux_guide/img/cursors-panclosed.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-panopened.png b/doc/development/ux_guide/img/cursors-panopened.png
deleted file mode 100644
index 76f2eeda831..00000000000
--- a/doc/development/ux_guide/img/cursors-panopened.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/cursors-pointer.png b/doc/development/ux_guide/img/cursors-pointer.png
deleted file mode 100644
index d86dd955fa7..00000000000
--- a/doc/development/ux_guide/img/cursors-pointer.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/features-contextualnav.png b/doc/development/ux_guide/img/features-contextualnav.png
deleted file mode 100644
index aa816776fad..00000000000
--- a/doc/development/ux_guide/img/features-contextualnav.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/features-emptystates.png b/doc/development/ux_guide/img/features-emptystates.png
deleted file mode 100644
index 50f31f5e523..00000000000
--- a/doc/development/ux_guide/img/features-emptystates.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/features-filters.png b/doc/development/ux_guide/img/features-filters.png
deleted file mode 100644
index 41db76db938..00000000000
--- a/doc/development/ux_guide/img/features-filters.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/features-globalnav.png b/doc/development/ux_guide/img/features-globalnav.png
deleted file mode 100644
index 73294d1b524..00000000000
--- a/doc/development/ux_guide/img/features-globalnav.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/harry-robison.png b/doc/development/ux_guide/img/harry-robison.png
deleted file mode 100644
index 702a8b02262..00000000000
--- a/doc/development/ux_guide/img/harry-robison.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-add.png b/doc/development/ux_guide/img/icon-add.png
deleted file mode 100644
index f66525cc1b4..00000000000
--- a/doc/development/ux_guide/img/icon-add.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-close.png b/doc/development/ux_guide/img/icon-close.png
deleted file mode 100644
index af6c30ebe6a..00000000000
--- a/doc/development/ux_guide/img/icon-close.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-edit.png b/doc/development/ux_guide/img/icon-edit.png
deleted file mode 100644
index b9649f4aeec..00000000000
--- a/doc/development/ux_guide/img/icon-edit.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-notification.png b/doc/development/ux_guide/img/icon-notification.png
deleted file mode 100644
index 5cf8f8ab59a..00000000000
--- a/doc/development/ux_guide/img/icon-notification.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-rss.png b/doc/development/ux_guide/img/icon-rss.png
deleted file mode 100644
index 7e2987a2656..00000000000
--- a/doc/development/ux_guide/img/icon-rss.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-spec.png b/doc/development/ux_guide/img/icon-spec.png
deleted file mode 100644
index 5bb85c5be98..00000000000
--- a/doc/development/ux_guide/img/icon-spec.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-subscribe.png b/doc/development/ux_guide/img/icon-subscribe.png
deleted file mode 100644
index 7e2f5e6a1c6..00000000000
--- a/doc/development/ux_guide/img/icon-subscribe.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/icon-trash.png b/doc/development/ux_guide/img/icon-trash.png
deleted file mode 100644
index bc46638fb2e..00000000000
--- a/doc/development/ux_guide/img/icon-trash.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustration-size-large-horizontal.png b/doc/development/ux_guide/img/illustration-size-large-horizontal.png
deleted file mode 100644
index 8aa835adccc..00000000000
--- a/doc/development/ux_guide/img/illustration-size-large-horizontal.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustration-size-large-vertical.png b/doc/development/ux_guide/img/illustration-size-large-vertical.png
deleted file mode 100644
index 813b6a065e5..00000000000
--- a/doc/development/ux_guide/img/illustration-size-large-vertical.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustration-size-medium.png b/doc/development/ux_guide/img/illustration-size-medium.png
deleted file mode 100644
index 55cfe1dcb91..00000000000
--- a/doc/development/ux_guide/img/illustration-size-medium.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustration-size-small.png b/doc/development/ux_guide/img/illustration-size-small.png
deleted file mode 100644
index 0124f58f48e..00000000000
--- a/doc/development/ux_guide/img/illustration-size-small.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-border-radius.png b/doc/development/ux_guide/img/illustrations-border-radius.png
deleted file mode 100644
index 4e2fef5c7f5..00000000000
--- a/doc/development/ux_guide/img/illustrations-border-radius.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-caps-do.png b/doc/development/ux_guide/img/illustrations-caps-do.png
deleted file mode 100644
index f1030769b94..00000000000
--- a/doc/development/ux_guide/img/illustrations-caps-do.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-caps-don't.png b/doc/development/ux_guide/img/illustrations-caps-don't.png
deleted file mode 100644
index ab7abcaaf6f..00000000000
--- a/doc/development/ux_guide/img/illustrations-caps-don't.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-color-grey.png b/doc/development/ux_guide/img/illustrations-color-grey.png
deleted file mode 100644
index 63855026c2b..00000000000
--- a/doc/development/ux_guide/img/illustrations-color-grey.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-color-orange.png b/doc/development/ux_guide/img/illustrations-color-orange.png
deleted file mode 100644
index 96765c8c28c..00000000000
--- a/doc/development/ux_guide/img/illustrations-color-orange.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-color-purple.png b/doc/development/ux_guide/img/illustrations-color-purple.png
deleted file mode 100644
index 745d2c853ba..00000000000
--- a/doc/development/ux_guide/img/illustrations-color-purple.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-geometric.png b/doc/development/ux_guide/img/illustrations-geometric.png
deleted file mode 100644
index 33f05547bac..00000000000
--- a/doc/development/ux_guide/img/illustrations-geometric.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-palette-oragne.png b/doc/development/ux_guide/img/illustrations-palette-oragne.png
deleted file mode 100644
index 15f35912646..00000000000
--- a/doc/development/ux_guide/img/illustrations-palette-oragne.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/illustrations-palette-purple.png b/doc/development/ux_guide/img/illustrations-palette-purple.png
deleted file mode 100644
index e0f5839705e..00000000000
--- a/doc/development/ux_guide/img/illustrations-palette-purple.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/james-mackey.png b/doc/development/ux_guide/img/james-mackey.png
deleted file mode 100644
index f51a45c437b..00000000000
--- a/doc/development/ux_guide/img/james-mackey.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/karolina-plaskaty.png b/doc/development/ux_guide/img/karolina-plaskaty.png
deleted file mode 100644
index d1c9528dd5a..00000000000
--- a/doc/development/ux_guide/img/karolina-plaskaty.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/matthieu-poirier.png b/doc/development/ux_guide/img/matthieu-poirier.png
deleted file mode 100644
index 0ecc2d670d6..00000000000
--- a/doc/development/ux_guide/img/matthieu-poirier.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/modals-general-confimation-dialog.png b/doc/development/ux_guide/img/modals-general-confimation-dialog.png
deleted file mode 100644
index 4ea0ea10ca7..00000000000
--- a/doc/development/ux_guide/img/modals-general-confimation-dialog.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/modals-layout-for-modals.png b/doc/development/ux_guide/img/modals-layout-for-modals.png
deleted file mode 100644
index c481edd8250..00000000000
--- a/doc/development/ux_guide/img/modals-layout-for-modals.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/modals-special-confimation-dialog.png b/doc/development/ux_guide/img/modals-special-confimation-dialog.png
deleted file mode 100644
index d966010158b..00000000000
--- a/doc/development/ux_guide/img/modals-special-confimation-dialog.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/modals-three-buttons.png b/doc/development/ux_guide/img/modals-three-buttons.png
deleted file mode 100644
index 157d1b650bf..00000000000
--- a/doc/development/ux_guide/img/modals-three-buttons.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/monospacefont-sample.png b/doc/development/ux_guide/img/monospacefont-sample.png
deleted file mode 100644
index 1cd290b713c..00000000000
--- a/doc/development/ux_guide/img/monospacefont-sample.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/nazim-ramesh.png b/doc/development/ux_guide/img/nazim-ramesh.png
deleted file mode 100644
index dad2b37010b..00000000000
--- a/doc/development/ux_guide/img/nazim-ramesh.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/popover-placement-above.png b/doc/development/ux_guide/img/popover-placement-above.png
deleted file mode 100644
index 84c9c878ec2..00000000000
--- a/doc/development/ux_guide/img/popover-placement-above.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/popover-placement-below.png b/doc/development/ux_guide/img/popover-placement-below.png
deleted file mode 100644
index f6f18199ab6..00000000000
--- a/doc/development/ux_guide/img/popover-placement-below.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/skeleton-loading.gif b/doc/development/ux_guide/img/skeleton-loading.gif
deleted file mode 100644
index 5877139171d..00000000000
--- a/doc/development/ux_guide/img/skeleton-loading.gif
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/sourcesanspro-sample.png b/doc/development/ux_guide/img/sourcesanspro-sample.png
deleted file mode 100644
index f7ecf0c7c66..00000000000
--- a/doc/development/ux_guide/img/sourcesanspro-sample.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/steven-lyons.png b/doc/development/ux_guide/img/steven-lyons.png
deleted file mode 100644
index 2efe1d0b168..00000000000
--- a/doc/development/ux_guide/img/steven-lyons.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/surfaces-contentitemtitle.png b/doc/development/ux_guide/img/surfaces-contentitemtitle.png
deleted file mode 100644
index f6cd212ecfd..00000000000
--- a/doc/development/ux_guide/img/surfaces-contentitemtitle.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/surfaces-header.png b/doc/development/ux_guide/img/surfaces-header.png
deleted file mode 100644
index ba616388003..00000000000
--- a/doc/development/ux_guide/img/surfaces-header.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/surfaces-systeminformationblock.png b/doc/development/ux_guide/img/surfaces-systeminformationblock.png
deleted file mode 100644
index f3313add2b8..00000000000
--- a/doc/development/ux_guide/img/surfaces-systeminformationblock.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/surfaces-ux.png b/doc/development/ux_guide/img/surfaces-ux.png
deleted file mode 100644
index eaa7f70c0c7..00000000000
--- a/doc/development/ux_guide/img/surfaces-ux.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/tooltip-placement.png b/doc/development/ux_guide/img/tooltip-placement.png
deleted file mode 100644
index da49c192878..00000000000
--- a/doc/development/ux_guide/img/tooltip-placement.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/img/tooltip-usage.png b/doc/development/ux_guide/img/tooltip-usage.png
deleted file mode 100644
index 4f5884c4b48..00000000000
--- a/doc/development/ux_guide/img/tooltip-usage.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index 11bac11257c..3aecbbffd73 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/getting-started/personas/'
+redirect_to: 'https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
+This document was moved to [another location](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/).
diff --git a/doc/getting-started/subscription.md b/doc/getting-started/subscription.md
new file mode 100644
index 00000000000..65999183d4a
--- /dev/null
+++ b/doc/getting-started/subscription.md
@@ -0,0 +1,3 @@
+---
+redirect_to: '../subscriptions/index.md'
+--- \ No newline at end of file
diff --git a/doc/git_hooks/git_hooks.md b/doc/git_hooks/git_hooks.md
new file mode 100644
index 00000000000..b251e58410a
--- /dev/null
+++ b/doc/git_hooks/git_hooks.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../push_rules/push_rules.md'
+---
+
+This document was moved to [another location](../push_rules/push_rules.md)
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index 4e15f7cfd49..00ac6d6b650 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -2,18 +2,36 @@
comments: false
---
-# GitLab basics
-
-Step-by-step guides on the basics of working with Git and GitLab.
-
-- [Command line basics](command-line-commands.md)
-- [Start using Git on the command line](start-using-git.md)
-- [Create and add your SSH Keys](create-your-ssh-keys.md)
-- [Create a project](create-project.md)
-- [Create a group](../user/group/index.md#create-a-new-group)
-- [Create a branch](create-branch.md)
-- [Fork a project](fork-project.md)
-- [Add a file](add-file.md)
-- [Add an image](add-image.md)
-- [Create an issue](../user/project/issues/create_new_issue.md)
-- [Create a merge request](add-merge-request.md)
+# GitLab basics guides
+
+This section provides resources to help you start with GitLab by focusing on basic functionality.
+
+This documentation is split into the following groups:
+
+- [GitLab-specific functionality](#gitlab-basics), for basic GitLab features.
+- [General Git functionality](#git-basics), for working with Git in conjunction with GitLab.
+
+## GitLab basics
+
+The following are guides to basic GitLab functionality:
+
+- [Create and add your SSH public key](create-your-ssh-keys.md), for enabling Git over SSH.
+- [Create a project](create-project.md), to start using GitLab.
+- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer projects together.
+- [Create a branch](create-branch.md), to make changes to files stored in a project's repository.
+- [Fork a project](fork-project.md), to duplicate projects so they can be worked on in parallel.
+- [Add a file](add-file.md), to add new files to a project's repository.
+- [Add an image](add-image.md), to add new images to a project's repository.
+- [Create an issue](../user/project/issues/create_new_issue.md), to start collaborating within a project.
+- [Create a merge request](add-merge-request.md), to request changes made in a branch be merged into a project's repository.
+
+## Git basics
+
+If you're familiar with Git on the command line, you can interact with your GitLab projects just as you would with any other Git repository.
+
+These resources will help get further acclimated to working on the command line.
+
+- [Start using Git on the command line](start-using-git.md), for some simple Git commands.
+- [Command line basics](command-line-commands.md), to create and edit files using the command line.
+
+More Git resources are available at GitLab's [Git documentation](../topics/git/index.md).
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 5cc014419ad..7bb2e014738 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -8,7 +8,7 @@ request. For more information, check the
---
1. Before you start, you should have already [created a branch](create-branch.md)
- and [pushed your changes](basic-git-commands.md) to GitLab.
+ and [pushed your changes](start-using-git.md#send-changes-to-gitlabcom) to GitLab.
1. Go to the project where you'd like to merge your changes and click on the
**Merge requests** tab.
1. Click on **New merge request** on the right side of the screen.
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index a0111be0767..1cf883679d7 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -13,10 +13,12 @@ button (you'll have to paste it on your shell in the next step).
![Copy the HTTPS or SSH](img/project_clone_url.png)
-## On the command line
+## Working with project files on the command line
This section has examples of some basic shell commands that you might find useful. For more information, search the web for _bash commands_.
+Alternatively, you can edit files using your choice of editor (IDE) or the GitLab user interface.
+
### Clone your project
Go to your computer's shell and type the following command with your SSH or HTTPS URL:
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index 985a52d88f5..8c0d3561882 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../user/group/index.md#create-a-new-group'
+---
This document was moved to [another location](../user/group/index.md#create-a-new-group).
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index abb163dbf18..6e2a09fc030 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../user/project/issues/index.md#issue-actions'
+---
-This document was moved to [another location](../user/project/issues/index.md#new-issue).
+This document was moved to [another location](../user/project/issues/index.md#issue-actions).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 978ffde84ad..a9ae4fb23f9 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -16,6 +16,7 @@ To create a project in GitLab:
- [Import a project](../user/project/import/index.md) from a different repository,
if enabled on your GitLab instance. Contact your GitLab admin if this
is unavailable.
+ - Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
## Blank projects
@@ -42,7 +43,9 @@ Project templates can pre-populate your project with necessary files to get you
There are two types of project templates:
-- [Built-in templates](#builtin-templates), sourced from the [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+- [Built-in templates](#built-in-templates), sourced from the following groups:
+ - [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
+ - [`pages`](https://gitlab.com/pages)
- [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users.
### Built-in templates
@@ -50,12 +53,12 @@ There are two types of project templates:
Built-in templates are project templates that are:
- Developed and maintained in the
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+ [`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
- Released with GitLab.
To use a built-in template on the **New project** page:
-1. On the **Create from template** tab.
+1. On the **Create from template** tab, select the **Built-in** tab.
1. From the list of available built-in templates, click the:
- **Preview** button to look at the template source itself.
- **Use template** button to start creating the project.
@@ -64,7 +67,7 @@ To use a built-in template on the **New project** page:
TIP: **Tip:**
You can improve the existing built-in templates or contribute new ones on the
-[`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
### Custom project templates **[PREMIUM ONLY]**
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index 8c2f48fb1e2..aac73d4c9c5 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,36 +1,22 @@
-# How to create your SSH Keys
+# Create and add your SSH public key
-1. The first thing you need to do is go to your [command line](start-using-git.md)
- and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
+This topic describes how to:
-1. Once you do that, login to GitLab with your credentials.
-1. On the upper right corner, click on your avatar and go to your **Profile settings**.
+- Create an SSH key pair to use with GitLab.
+- Add the SSH public key to your GitLab account.
- ![Profile settings dropdown](img/profile_settings.png)
+You do this to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
-1. Navigate to the **SSH keys** tab.
+## Creating your SSH key pair
- ![SSH Keys](img/profile_settings_ssh_keys.png)
+1. Go to your [command line](start-using-git.md).
+1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate your SSH key pair.
-1. Paste your **public** key that you generated in the first step in the 'Key'
- box.
+## Adding your SSH public key to GitLab
- ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png)
+To add the SSH public key to GitLab,
+see [Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
-1. Optionally, give it a descriptive title so that you can recognize it in the
- event you add multiple keys.
-
- ![SSH key title](img/profile_settings_ssh_keys_title.png)
-
-1. Finally, click on **Add key** to add it to GitLab. You will be able to see
- its fingerprint, its title and creation date.
-
- ![SSH key single page](img/profile_settings_ssh_keys_single_key.png)
-
->**Note:**
-Once you add a key, you cannot edit it, only remove it. In case the paste
-didn't work, you will have to remove the offending key and re-add it.
-
----
-
-Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP!
+NOTE: **Note:**
+Once you add a key, you cannot edit it. If the paste
+[didn't work](../ssh/README.md#testing-that-everything-is-set-up-correctly), you need to remove the key and re-add it.
diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md
index 6c232fe6086..a128a7c7dd3 100644
--- a/doc/gitlab-basics/fork-project.md
+++ b/doc/gitlab-basics/fork-project.md
@@ -1,8 +1,8 @@
# How to fork a project
-A fork is a copy of an original repository that you can put in another namespace
-where you can experiment and apply changes that you can later decide if
-publishing or not, without affecting your original project.
+A fork is a copy of an original repository that you put in another namespace
+where you can experiment and apply changes that you can later decide whether or
+not to share, without affecting the original project.
It takes just a few steps to fork a project in GitLab.
diff --git a/doc/gitlab-basics/img/new_issue_button.png b/doc/gitlab-basics/img/new_issue_button.png
deleted file mode 100644
index 3b113471f0c..00000000000
--- a/doc/gitlab-basics/img/new_issue_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/new_issue_page.png b/doc/gitlab-basics/img/new_issue_page.png
deleted file mode 100644
index ce3e60df276..00000000000
--- a/doc/gitlab-basics/img/new_issue_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png
deleted file mode 100644
index b91b698fb18..00000000000
--- a/doc/gitlab-basics/img/profile_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png
deleted file mode 100644
index 8ac603a2af9..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png
deleted file mode 100644
index 0b1c64a72f3..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
deleted file mode 100644
index 8014f1d5301..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png
deleted file mode 100644
index 02ca0bf7478..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/public_file_link.png b/doc/gitlab-basics/img/public_file_link.png
deleted file mode 100644
index f60df6807f4..00000000000
--- a/doc/gitlab-basics/img/public_file_link.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index e30afdf8a40..b3c5d32f2f5 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -71,6 +71,18 @@ git config --global --list
Start using Git via the command line with the most basic
commands as described below.
+## Initialize a local directory for Git version control
+
+If you have an existing local directory that you want to *initialize* for version control, use the `init` command to instruct Git to begin tracking the directory:
+
+```bash
+git init
+```
+
+This creates a `.git` directory that contains the Git configuration files.
+
+Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository) and [send changes to GitLab.com](#send-changes-to-gitlabcom). View the instructions on [Create a project](../gitlab-basics/create-project.html#push-to-create-a-new-project) to create a new project on GitLab with your changes.
+
### Clone a repository
To start working locally on an existing remote repository,
@@ -140,6 +152,16 @@ To view your remote repositories, type:
git remote -v
```
+### Add a remote repository
+
+To add a link to a remote repository:
+
+```bash
+git remote add SOURCE-NAME REPOSITORY-PATH
+```
+
+You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom), so use something easy to remember and type.
+
### Create a branch
To create a branch, type the following (spaces won't be recognized in the branch name, so you will need to use a hyphen or underscore):
@@ -193,7 +215,7 @@ git commit -m "COMMENT TO DESCRIBE THE INTENTION OF THE COMMIT"
NOTE: **Note:**
The `.` character typically means _all_ in Git.
-### Send changes to gitlab.com
+### Send changes to GitLab.com
To push all local commits to the remote repository:
diff --git a/doc/gitlab-geo/README.md b/doc/gitlab-geo/README.md
new file mode 100644
index 00000000000..30d21db7de5
--- /dev/null
+++ b/doc/gitlab-geo/README.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/index.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/index.md).
diff --git a/doc/gitlab-geo/after_setup.md b/doc/gitlab-geo/after_setup.md
new file mode 100644
index 00000000000..c8a7b9d1096
--- /dev/null
+++ b/doc/gitlab-geo/after_setup.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/using_a_geo_server.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/using_a_geo_server.md).
diff --git a/doc/gitlab-geo/bring-primary-back.md b/doc/gitlab-geo/bring-primary-back.md
new file mode 100644
index 00000000000..8c43f4d805f
--- /dev/null
+++ b/doc/gitlab-geo/bring-primary-back.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/disaster_recovery/bring_primary_back.md'
+---
+
+This document was moved to [another location](../administration/geo/disaster_recovery/bring_primary_back.md).
diff --git a/doc/gitlab-geo/configuration.md b/doc/gitlab-geo/configuration.md
new file mode 100644
index 00000000000..b46a2caea4a
--- /dev/null
+++ b/doc/gitlab-geo/configuration.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/configuration.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/configuration.md).
diff --git a/doc/gitlab-geo/configuration_source.md b/doc/gitlab-geo/configuration_source.md
new file mode 100644
index 00000000000..b46a2caea4a
--- /dev/null
+++ b/doc/gitlab-geo/configuration_source.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/configuration.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/configuration.md).
diff --git a/doc/gitlab-geo/database.md b/doc/gitlab-geo/database.md
new file mode 100644
index 00000000000..b4156dc4ec6
--- /dev/null
+++ b/doc/gitlab-geo/database.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/database.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/database.md).
diff --git a/doc/gitlab-geo/database_source.md b/doc/gitlab-geo/database_source.md
new file mode 100644
index 00000000000..b4156dc4ec6
--- /dev/null
+++ b/doc/gitlab-geo/database_source.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/database.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/database.md).
diff --git a/doc/gitlab-geo/disaster-recovery.md b/doc/gitlab-geo/disaster-recovery.md
new file mode 100644
index 00000000000..d42e815a879
--- /dev/null
+++ b/doc/gitlab-geo/disaster-recovery.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/disaster_recovery/index.md'
+---
+
+This document was moved to [another location](../administration/geo/disaster_recovery/index.md).
diff --git a/doc/gitlab-geo/docker_registry.md b/doc/gitlab-geo/docker_registry.md
new file mode 100644
index 00000000000..26a708f6845
--- /dev/null
+++ b/doc/gitlab-geo/docker_registry.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/docker_registry.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/docker_registry.md).
diff --git a/doc/gitlab-geo/faq.md b/doc/gitlab-geo/faq.md
new file mode 100644
index 00000000000..f1952bc7e4c
--- /dev/null
+++ b/doc/gitlab-geo/faq.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/faq.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/faq.md).
diff --git a/doc/gitlab-geo/ha.md b/doc/gitlab-geo/ha.md
new file mode 100644
index 00000000000..23ed11eaf09
--- /dev/null
+++ b/doc/gitlab-geo/ha.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/high_availability.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/high_availability.md).
diff --git a/doc/gitlab-geo/object_storage.md b/doc/gitlab-geo/object_storage.md
new file mode 100644
index 00000000000..1f29b7b7e8c
--- /dev/null
+++ b/doc/gitlab-geo/object_storage.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/object_storage.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/object_storage.md).
diff --git a/doc/gitlab-geo/planned-failover.md b/doc/gitlab-geo/planned-failover.md
new file mode 100644
index 00000000000..720b6bc9424
--- /dev/null
+++ b/doc/gitlab-geo/planned-failover.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/disaster_recovery/planned_failover.md'
+---
+
+This document was moved to [another location](../administration/geo/disaster_recovery/planned_failover.md).
diff --git a/doc/gitlab-geo/security-review.md b/doc/gitlab-geo/security-review.md
new file mode 100644
index 00000000000..a0a5b0e536c
--- /dev/null
+++ b/doc/gitlab-geo/security-review.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/security_review.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/security_review.md).
diff --git a/doc/gitlab-geo/ssh.md b/doc/gitlab-geo/ssh.md
new file mode 100644
index 00000000000..4f8db687850
--- /dev/null
+++ b/doc/gitlab-geo/ssh.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/operations/fast_ssh_key_lookup.md'
+---
+
+This document was moved to [another location](../administration/operations/fast_ssh_key_lookup.md).
diff --git a/doc/gitlab-geo/troubleshooting.md b/doc/gitlab-geo/troubleshooting.md
new file mode 100644
index 00000000000..25fe1372c69
--- /dev/null
+++ b/doc/gitlab-geo/troubleshooting.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/troubleshooting.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/troubleshooting.md).
diff --git a/doc/gitlab-geo/tuning.md b/doc/gitlab-geo/tuning.md
new file mode 100644
index 00000000000..84ac40f99db
--- /dev/null
+++ b/doc/gitlab-geo/tuning.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/tuning.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/tuning.md).
diff --git a/doc/gitlab-geo/updating_the_geo_nodes.md b/doc/gitlab-geo/updating_the_geo_nodes.md
new file mode 100644
index 00000000000..28234ec02ed
--- /dev/null
+++ b/doc/gitlab-geo/updating_the_geo_nodes.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/updating_the_geo_nodes.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/updating_the_geo_nodes.md).
diff --git a/doc/gitlab-geo/using_a_geo_server.md b/doc/gitlab-geo/using_a_geo_server.md
new file mode 100644
index 00000000000..c8a7b9d1096
--- /dev/null
+++ b/doc/gitlab-geo/using_a_geo_server.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/geo/replication/using_a_geo_server.md'
+---
+
+This document was moved to [another location](../administration/geo/replication/using_a_geo_server.md).
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index 1d5e5dd6e15..fb939ff8aac 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../administration/custom_hooks.md'
+---
+
# Custom Git Hooks
This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md).
diff --git a/doc/img/devops-stages.png b/doc/img/devops-stages.png
index 424bce95607..2fa5357062c 100644
--- a/doc/img/devops-stages.png
+++ b/doc/img/devops-stages.png
Binary files differ
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index db0f03f2c98..9544983974f 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/reply_by_email.md'
+---
+
This document was moved to [administration/reply_by_email](../administration/reply_by_email.md).
diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md
index 90833238ac5..a7192325229 100644
--- a/doc/incoming_email/postfix.md
+++ b/doc/incoming_email/postfix.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/reply_by_email_postfix_setup.md'
+---
+
This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md).
diff --git a/doc/install/README.md b/doc/install/README.md
index 52011526768..9cc21412898 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -1,6 +1,7 @@
---
comments: false
description: Read through the GitLab installation methods.
+type: index
---
# Installation **[CORE ONLY]**
@@ -55,9 +56,9 @@ need to be aware of:
- It can be more expensive for smaller installations. The default installation
requires more resources than a single node Omnibus deployment, as most services
are deployed in a redundant fashion.
-- There are some feature [limitations to be aware of](kubernetes/gitlab_chart.md#limitations).
+- There are some feature [limitations to be aware of](https://docs.gitlab.com/charts/#limitations).
-[**> Install GitLab on Kubernetes using the GitLab Helm charts.**](kubernetes/index.md)
+[**> Install GitLab on Kubernetes using the GitLab Helm charts.**](https://docs.gitlab.com/charts/)
## Installing GitLab with Docker
@@ -81,7 +82,7 @@ the above methods, provided the cloud provider supports it.
- [Install on AWS](aws/index.md): Install Omnibus GitLab on AWS using the community AMIs that GitLab provides.
- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md): Install Omnibus GitLab on a VM in GCP.
- [Install GitLab on Azure](azure/index.md): Install Omnibus GitLab from Azure Marketplace.
-- [Install GitLab on OpenShift](openshift_and_gitlab/index.md): Install GitLab using the Docker image on OpenShift.
+- [Install GitLab on OpenShift](https://docs.gitlab.com/charts/installation/cloud/openshift.html): Install GitLab on OpenShift by using GitLab's Helm charts.
- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/): Install GitLab on Mesosphere DC/OS via the [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/).
- [Install GitLab on DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): Install Omnibus GitLab on DigitalOcean.
- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md):
diff --git a/doc/install/aws/img/add_tags.png b/doc/install/aws/img/add_tags.png
deleted file mode 100644
index 3572cd5daa1..00000000000
--- a/doc/install/aws/img/add_tags.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/aws/img/create_route_table.png b/doc/install/aws/img/create_route_table.png
deleted file mode 100644
index ea72c57257e..00000000000
--- a/doc/install/aws/img/create_route_table.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 51d2a232dc0..73eaf758923 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -1,11 +1,15 @@
-# Installing GitLab on Amazon Web Services (AWS)
+---
+type: howto
+---
-To install GitLab on AWS, you can use the Amazon Machine Images (AMIs) that GitLab
-provides with [each release](https://about.gitlab.com/releases/).
+# Installing GitLab HA on Amazon Web Services (AWS)
This page offers a walkthrough of a common HA (Highly Available) configuration
for GitLab on AWS. You should customize it to accommodate your needs.
+NOTE: **Note**
+For organizations with 300 users or less, the recommended AWS installation method is to launch an EC2 single box [Omnibus Installation](https://about.gitlab.com/install/) and implement a snapshot strategy for backing up the data.
+
## Introduction
GitLab on AWS can leverage many of the services that are already
@@ -55,6 +59,8 @@ Here's a list of the AWS services we will use, with links to pricing information
- **ElastiCache**: An in-memory cache environment will be used to provide a
High Availability Redis configuration. See the
[Amazon ElastiCache pricing](https://aws.amazon.com/elasticache/pricing/).
+
+NOTE: **Note:** Please note that while we will be using EBS for storage, we do not recommend using EFS as it may negatively impact GitLab's performance. You can review the [relevant documentation](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
## Creating an IAM EC2 instance role and profile
To minimize the permissions of the user, we'll create a new [IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
@@ -379,6 +385,10 @@ size depends on your needs and you can always migrate to a bigger volume later.
You will be able to [set up that volume](#setting-up-the-ebs-volume)
after the instance is created.
+CAUTION: **Caution:**
+We **do not** recommend using the AWS Elastic File System (EFS), as it can result
+in [significantly degraded performance](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
+
### Configure security group
As a last step, configure the security group:
@@ -606,7 +616,7 @@ To back up GitLab:
To restore GitLab, first review the [restore documentation](../../raketasks/backup_restore.md#restore),
and primarily the restore prerequisites. Then, follow the steps under the
-[Omnibus installations section](../../raketasks/backup_restore.md#restore-for-omnibus-installations).
+[Omnibus installations section](../../raketasks/backup_restore.md#restore-for-omnibus-gitlab-installations).
## Updating GitLab
@@ -643,12 +653,24 @@ Have a read through these other resources and feel free to
[open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new)
to request additional material:
-- [GitLab High Availability](https://docs.gitlab.com/ee/administration/high_availability/):
+- [GitLab High Availability](../../administration/high_availability/README.md):
GitLab supports several different types of clustering and high-availability.
-- [Geo replication](https://docs.gitlab.com/ee/administration/geo/replication/):
+- [Geo replication](../../administration/geo/replication/index.md):
Geo is the solution for widely distributed development teams.
- [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know
about administering your GitLab instance.
-- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html):
+- [Upload a license](../../user/admin_area/license.md):
Activate all GitLab Enterprise Edition functionality with a license.
- [Pricing](https://about.gitlab.com/pricing): Pricing for the different tiers.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/azure/img/azure-vm-management-settings-network-interfaces.png b/doc/install/azure/img/azure-vm-management-settings-network-interfaces.png
deleted file mode 100644
index 4ff10718059..00000000000
--- a/doc/install/azure/img/azure-vm-management-settings-network-interfaces.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/azure/img/azure-vm-management.png b/doc/install/azure/img/azure-vm-management.png
deleted file mode 100644
index a0e0067258c..00000000000
--- a/doc/install/azure/img/azure-vm-management.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index fa5be1d30f9..b1f79893baf 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -1,14 +1,10 @@
---
-description: 'Learn how to spin up a
-pre-configured GitLab VM on Microsoft Azure and have your very own private GitLab instance up and running in around 30 minutes.'
+description: 'Learn how to spin up a pre-configured GitLab VM on Microsoft Azure.'
+type: howto
---
# Install GitLab on Microsoft Azure
-> _This article was originally written by Dave Wentzel and [published on the GitLab Blog][Original-Blog-Post]._
->
-> _Ported to the GitLab documentation and updated on 2017-08-24 by [Ian Scorer](https://gitlab.com/iscorer)._
-
Azure is Microsoft's business cloud and GitLab is a pre-configured offering on the Azure Marketplace.
Hopefully, you aren't surprised to hear that Microsoft and Azure have embraced open source software
like Ubuntu, Red Hat Enterprise Linux, and of course - GitLab! This means that you can spin up a
@@ -444,3 +440,15 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
[SSH]: https://en.wikipedia.org/wiki/Secure_Shell
[PuTTY]: http://www.putty.org/
[Using-SSH-In-Putty]: https://mediatemple.net/community/products/dv/204404604/using-ssh-in-putty-
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index e89846107b6..cbb3b766b4e 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Database MySQL
NOTE: **Note:**
@@ -301,3 +305,15 @@ Details can be found in the [PostgreSQL][postgres-text-type] and
[postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html
[ce-38152]: https://gitlab.com/gitlab-org/gitlab-ce/issues/38152
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
index d67695d75b4..63bb941ad47 100644
--- a/doc/install/digitaloceandocker.md
+++ b/doc/install/digitaloceandocker.md
@@ -1,8 +1,11 @@
+---
+type: howto
+---
+
# Digital Ocean and Docker Machine test environment
-CAUTION: **Caution:**
-This guide is for quickly testing different versions of GitLab and not recommended for ease of
-future upgrades or keeping the data you create.
+This guide is for quickly testing different versions of GitLab and not
+recommended for ease of future upgrades or keeping the data you create.
## Initial setup
@@ -28,7 +31,7 @@ locally on either macOS or Linux.
NOTE: **Note:**
The rest of the steps are identical for macOS and Linux.
-### Create new docker host
+## Create new docker host
1. Login to Digital Ocean.
1. Generate a new API token at <https://cloud.digitalocean.com/settings/api/tokens>.
@@ -60,9 +63,9 @@ The rest of the steps are identical for macOS and Linux.
Resource: <https://docs.docker.com/machine/drivers/digital-ocean/>.
-### Creating GitLab test instance
+## Creating GitLab test instance
-#### Connect your shell to the new machine
+### Connect your shell to the new machine
In this example we'll create a GitLab EE 8.10.8 instance.
@@ -74,7 +77,7 @@ eval "$(docker-machine env gitlab-test-env-do)"
You can add this to your `~/.bash_profile` file to ensure the `docker` client uses the `gitlab-test-env-do` docker host
-#### Create new GitLab container
+### Create new GitLab container
- HTTP port: `8888`
- SSH port: `2222`
@@ -83,7 +86,7 @@ You can add this to your `~/.bash_profile` file to ensure the `docker` client us
- Container name: `gitlab-test-8.10`
- GitLab version: **EE** `8.10.8-ee.0`
-##### Set up container settings
+#### Set up container settings
```sh
export SSH_PORT=2222
@@ -92,7 +95,7 @@ export VERSION=8.10.8-ee.0
export NAME=gitlab-test-8.10
```
-##### Create container
+#### Create container
```sh
docker run --detach \
@@ -103,9 +106,9 @@ docker run --detach \
gitlab/gitlab-ee:$VERSION
```
-#### Connect to the GitLab container
+### Connect to the GitLab container
-##### Retrieve the docker host IP
+#### Retrieve the docker host IP
```sh
docker-machine ip gitlab-test-env-do
@@ -114,7 +117,7 @@ docker-machine ip gitlab-test-env-do
Browse to: <http://192.168.151.134:8888/>.
-##### Execute interactive shell/edit configuration
+#### Execute interactive shell/edit configuration
```sh
docker exec -it $NAME /bin/bash
@@ -126,8 +129,20 @@ root@192:/# vi /etc/gitlab/gitlab.rb
root@192:/# gitlab-ctl reconfigure
```
-#### Resources
+### Resources
- <https://docs.gitlab.com/omnibus/docker/>.
- <https://docs.docker.com/machine/get-started/>.
- <https://docs.docker.com/machine/reference/ip/>.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/docker.md b/doc/install/docker.md
index d0129f0f5c4..06da65189ba 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -1,8 +1,12 @@
+---
+type: index
+---
+
# Install GitLab with Docker
[Docker](https://www.docker.com) and container technology have been revolutionizing the software world for the past few years. They combine the performance and efficiency of native execution with the abstraction, security, and immutability of virtualization.
-GitLab provides official Docker images to allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance.
+GitLab provides official Docker images allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance.
## Omnibus GitLab based images
@@ -16,4 +20,4 @@ A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these image
## Cloud native images
-GitLab is also working towards a [cloud native set of containers](https://gitlab.com/charts/helm.gitlab.io#docker-container-images), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
+GitLab is also working towards a [cloud native set of containers](https://docs.gitlab.com/charts/), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
diff --git a/doc/install/google-protobuf.md b/doc/install/google-protobuf.md
index a531b4519b3..434817c48cb 100644
--- a/doc/install/google-protobuf.md
+++ b/doc/install/google-protobuf.md
@@ -1,26 +1,5 @@
-# Installing a locally compiled google-protobuf gem
+---
+redirect_to: 'installation.md#google-protobuf-loaderror-libx86_64-linux-gnulibcso6-version-glibc_214-not-found'
+---
-First we must find the exact version of google-protobuf that your
-GitLab installation requires.
-
- cd /home/git/gitlab
-
- # Only one of the following two commands will print something. It
- # will look like: * google-protobuf (3.2.0)
- bundle list | grep google-protobuf
- bundle check | grep google-protobuf
-
-Below we use `3.2.0` as an example. Replace it with the version number
-you found above.
-
- cd /home/git/gitlab
- sudo -u git -H gem install google-protobuf --version 3.2.0 --platform ruby
-
-Finally, you can test whether google-protobuf loads correctly. The
-following should print 'OK'.
-
- sudo -u git -H bundle exec ruby -rgoogle/protobuf -e 'puts :OK'
-
-If the `gem install` command fails you may need to install developer
-tools. On Debian: `apt-get install build-essential libgmp-dev`, on
-Centos/RedHat `yum groupinstall 'Development Tools'`.
+This document was moved to [another location](installation.md#google-protobuf-loaderror-libx86_64-linux-gnulibcso6-version-glibc_214-not-found).
diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png
deleted file mode 100644
index 92a9873728c..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_landing.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index aa4b3dccf7d..77c61acbfd4 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -1,12 +1,17 @@
---
description: 'Learn how to install a GitLab instance on Google Cloud Platform.'
+type: howto
---
# Installing GitLab on Google Cloud Platform
-![GCP landing page](img/gcp_landing.png)
+This guide will help you install GitLab on a [Google Cloud Platform (GCP)][gcp] instance.
-Getting started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy.
+NOTE: **Alternative installation method:**
+Google provides a whitepaper for [deploying production-ready GitLab on
+Google Kubernetes Engine](https://cloud.google.com/solutions/deploying-production-ready-gitlab-on-gke),
+including all steps and external resource configuration. These are an alternative to using a GCP VM, and use
+the [Cloud native GitLab Helm chart](https://docs.gitlab.com/charts/).
## Prerequisites
@@ -20,10 +25,9 @@ Once you have performed those two steps, you can [create a VM](#creating-the-vm)
## Creating the VM
-To deploy GitLab on GCP you need to follow five simple steps:
-
-1. Go to <https://console.cloud.google.com/compute/instances> and login with your Google credentials.
+To deploy GitLab on GCP you first need to create a virtual machine:
+1. Go to <https://console.cloud.google.com/compute/instances> and log in with your Google credentials.
1. Click on **Create**
![Search for GitLab](img/launch_vm.png)
@@ -136,3 +140,15 @@ Kerberos, etc. Here are some documents you might be interested in reading:
[ssh]: https://cloud.google.com/compute/docs/instances/connecting-to-instance "Connecting to Linux Instances"
[omni-smtp]: https://docs.gitlab.com/omnibus/settings/smtp.html#smtp-settings "Omnibus GitLab SMTP settings"
[omni-ssl]: https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https "Omnibus GitLab enable HTTPS"
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/installation.md b/doc/install/installation.md
index fb24d4fa0ef..10436a15a9e 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,5 +1,28 @@
+---
+type: howto
+---
+
# Installation from source
+This is the official installation guide to set up a production GitLab server
+using the source files. To set up a **development installation** or for many
+other installation options, see the [main installation page](index.md).
+It was created for and tested on **Debian/Ubuntu** operating systems.
+Read [requirements.md](requirements.md) for hardware and operating system requirements.
+If you want to install on RHEL/CentOS, we recommend using the
+[Omnibus packages](https://about.gitlab.com/downloads/).
+
+This guide is long because it covers many cases and includes all commands you
+need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
+The following steps have been known to work. **Use caution when you deviate**
+from this guide. Make sure you don't violate any assumptions GitLab makes about
+its environment. For example, many people run into permission problems because
+they changed the location of directories or run services as the wrong user.
+
+If you find a bug/error in this guide, **submit a merge request**
+following the
+[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
+
## Consider the Omnibus package installation
Since an installation from source is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
@@ -9,28 +32,45 @@ On heavily used GitLab instances the memory usage of the Sidekiq background work
Omnibus packages solve this by [letting the Sidekiq terminate gracefully](../administration/operations/sidekiq_memory_killer.md) if it uses too much memory.
After this termination Runit will detect Sidekiq is not running and will start it.
-Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time.
+Since installations from source don't use Runit for process supervision, Sidekiq
+can't be terminated and its memory usage will grow over time.
-## Select Version to Install
+## Select version to install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-7-stable`).
You can select the branch in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear, check the [GitLab blog](https://about.gitlab.com/blog/) for installation guide links by version.
-## Important Notes
+## GitLab directory structure
-This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
+This is the main directory structure you will end up with following the instructions
+of this page:
-This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Read [requirements.md](requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS, we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/).
+```
+|-- home
+| |-- git
+| |-- .ssh
+| |-- gitlab
+| |-- gitlab-shell
+| |-- repositories
+```
-This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options, see [the installation section of the README](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
+- `/home/git/.ssh` - Contains OpenSSH settings. Specifically the `authorized_keys`
+ file managed by gitlab-shell.
+- `/home/git/gitlab` - GitLab core software.
+- `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH
+ cloning and other functionality.
+- `/home/git/repositories` - Bare repositories for all projects organized by
+ namespace. This is where the git repositories which are pushed/pulled are
+ maintained for all projects. **This area contains critical data for projects.
+ [Keep a backup](../raketasks/backup_restore.md).**
-The following steps have been known to work. **Use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example, many people run into permission problems because they changed the location of directories or run services as the wrong user.
+NOTE: **Note:**
+The default locations for repositories can be configured in `config/gitlab.yml`
+of GitLab and `config.yml` of gitlab-shell.
-If you find a bug/error in this guide, **submit a merge request**
-following the
-[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
+For a more in-depth overview, see the [GitLab architecture doc](../development/architecture.md).
## Overview
@@ -72,7 +112,8 @@ Install the required packages (needed to compile Ruby and native extensions to R
```sh
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 rsync python-docutils pkg-config cmake
+ libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake \
+ runit
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
@@ -93,11 +134,24 @@ Make sure you have the right version of Git installed:
# Install Git
sudo apt-get install -y git-core
-# Make sure Git is version 2.18.0 or higher
+# Make sure Git is version 2.21.0 or higher
git --version
```
-Is the system packaged Git too old? Remove it and compile from source.
+Starting with GitLab 12.0, Git is required to be compiled with `libpcre2`.
+Find out if that's the case:
+
+```sh
+ldd /usr/local/bin/git | grep pcre2
+```
+
+The output should be similar to:
+
+```
+libpcre2-8.so.0 => /usr/lib/libpcre2-8.so.0 (0x00007f08461c3000)
+```
+
+Is the system packaged Git too old, or not compiled with pcre2? Remove it and compile from source:
```sh
# Remove packaged Git
@@ -106,12 +160,21 @@ sudo apt-get remove git-core
# Install dependencies
sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+# Download and compile pcre2 from source
+curl --silent --show-error --location https://ftp.pcre.org/pub/pcre/pcre2-10.33.tar.gz --output pcre2.tar.gz
+tar -xzf pcre2.tar.gz
+cd pcre2-10.33
+chmod +x configure
+./configure --prefix=/usr --enable-jit
+make
+make install
+
# Download and compile from source
cd /tmp
-curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
-echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
-cd git-2.18.0/
-./configure
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
+echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz
+cd git-2.21.0/
+./configure --with-libpcre
make prefix=/usr/local all
# Install into /usr/local/bin
@@ -161,9 +224,9 @@ Download Ruby and compile it:
```sh
mkdir /tmp/ruby && cd /tmp/ruby
-curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
-echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
-cd ruby-2.5.3
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.gz
+echo '2347ed6ca5490a104ebd5684d2b9b5eefa6cd33c ruby-2.6.3.tar.gz' | shasum -c - && tar xzf ruby-2.6.3.tar.gz
+cd ruby-2.6.3
./configure --disable-install-rdoc
make
@@ -300,16 +363,14 @@ Redis 2.8 with:
sudo apt-get install redis-server
```
-If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
-on [an alternate Redis installation](redis.md). Once done, follow the rest of
-the guide here.
+Once done, you can configure Redis:
```sh
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
# Disable Redis listening on TCP by setting 'port' to 0
-sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+sudo sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
@@ -318,9 +379,9 @@ echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
# Create the directory which contains the socket
-mkdir /var/run/redis
-chown redis:redis /var/run/redis
-chmod 755 /var/run/redis
+sudo mkdir -p /var/run/redis
+sudo chown redis:redis /var/run/redis
+sudo chmod 755 /var/run/redis
# Persist the directory which contains the socket, if applicable
if [ -d /etc/tmpfiles.d ]; then
@@ -382,7 +443,7 @@ sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/
# Create the public/uploads/ directory
-sudo -u git -H mkdir public/uploads/
+sudo -u git -H mkdir -p public/uploads/
# Make sure only the GitLab user has access to the public/uploads/ directory
# now that files in public/uploads are served by gitlab-workhorse
@@ -432,7 +493,8 @@ sudo -u git -H editor config/resque.yml
```
CAUTION: **Caution:**
-Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
+Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
+If you want to use Puma web server, see [Using Puma](#using-puma) for the additional steps.
NOTE: **Note:**
If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
@@ -446,6 +508,18 @@ sudo -u git cp config/database.yml.postgresql config/database.yml
# MySQL only:
sudo -u git cp config/database.yml.mysql config/database.yml
+# PostgreSQL only:
+# Remove host, username, and password lines from config/database.yml.
+# Once modified, the `production` settings will be as follows:
+#
+# production:
+# adapter: postgresql
+# encoding: unicode
+# database: gitlabhq_production
+# pool: 10
+#
+sudo -u git -H editor config/database.yml
+
# MySQL and remote PostgreSQL only:
# Update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
@@ -538,6 +612,7 @@ sudo -u git -H make
```sh
# Fetch Gitaly source with Git and compile with Go
+cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
```
@@ -562,9 +637,22 @@ sudo -u git -H editor config.toml
For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly).
+### Start Gitaly
+
+Gitaly must be running for the next section.
+
+```sh
+gitlab_path=/home/git/gitlab
+gitaly_path=/home/git/gitaly
+
+sudo -u git -H $gitlab_path/bin/daemon_with_pidfile $gitlab_path/tmp/pids/gitaly.pid \
+ $gitaly_path/gitaly $gitaly_path/config.toml >> $gitlab_path/log/gitaly.log 2>&1 &
+```
+
### Initialize Database and Activate Advanced Features
```sh
+cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables.
@@ -575,10 +663,10 @@ sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
```
NOTE: **Note:**
-You can set the Administrator/root password and e-mail by supplying them in environmental variables, `GITLAB_ROOT_PASSWORD` and `GITLAB_ROOT_EMAIL` respectively, as seen below. If you don't set the password (and it is set to the default one), wait to expose GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login, you'll be forced to change the default password.
+You can set the Administrator/root password and e-mail by supplying them in environmental variables, `GITLAB_ROOT_PASSWORD` and `GITLAB_ROOT_EMAIL` respectively, as seen below. If you don't set the password (and it is set to the default one), wait to expose GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login, you'll be forced to change the default password. An Enterprise Edition license may also be installed at this time by supplying a full path in the `GITLAB_LICENSE_FILE` environment variable.
```sh
-sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail GITLAB_LICENSE_FILE="/path/to/license"
```
### Secure secrets.yml
@@ -636,6 +724,12 @@ 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
```
+If `rake` fails with `JavaScript heap out of memory` error, try to run it with `NODE_OPTIONS` set as follows.
+
+```sh
+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
+```
+
### Start Your GitLab Instance
```sh
@@ -841,6 +935,25 @@ You also need to change the corresponding options (e.g. `ssh_user`, `ssh_host`,
Apart from the always supported markdown style, there are other rich text files that GitLab can display. But you might have to install a dependency to do so. See the [github-markup gem README](https://github.com/gitlabhq/markup#markups) for more information.
+### Using Puma
+
+Puma is a multi-threaded HTTP 1.1 server for Ruby applications.
+
+To use GitLab with Puma:
+
+1. Finish GitLab setup so you have it up and running.
+1. Copy the supplied example Puma config file into place:
+
+ ```sh
+ cd /home/git/gitlab
+
+ # Copy config file for the web server
+ sudo -u git -H config/puma.rb.example config/puma.rb
+ ```
+
+1. Edit the system `init.d` script to use `EXPERIMENTAL_PUMA=1` flag. If you have `/etc/default/gitlab`, then you should edit it instead.
+1. Restart GitLab.
+
## Troubleshooting
### "You appear to have cloned an empty repository."
@@ -854,8 +967,50 @@ and correctly [configured Nginx](#site-configuration).
### google-protobuf "LoadError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found"
This can happen on some platforms for some versions of the
-google-protobuf gem. The workaround is to [install a source-only
-version of this gem](google-protobuf.md).
+`google-protobuf` gem. The workaround is to install a source-only
+version of this gem.
+
+First, you must find the exact version of `google-protobuf` that your
+GitLab installation requires:
+
+```sh
+cd /home/git/gitlab
+
+# Only one of the following two commands will print something. It
+# will look like: * google-protobuf (3.2.0)
+bundle list | grep google-protobuf
+bundle check | grep google-protobuf
+```
+
+Below, `3.2.0` is used as an example. Replace it with the version number
+you found above:
+
+```sh
+cd /home/git/gitlab
+sudo -u git -H gem install google-protobuf --version 3.2.0 --platform ruby
+```
+
+Finally, you can test whether `google-protobuf` loads correctly. The
+following should print 'OK'.
+
+```sh
+sudo -u git -H bundle exec ruby -rgoogle/protobuf -e 'puts :OK'
+```
+
+If the `gem install` command fails, you may need to install the developer
+tools of your OS.
+
+On Debian/Ubuntu:
+
+```sh
+sudo apt-get install build-essential libgmp-dev
+```
+
+On RedHat/CentOS:
+
+```sh
+sudo yum groupinstall 'Development Tools'
+```
[RVM]: https://rvm.io/ "RVM Homepage"
[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub"
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 9db246b3eb3..43655767002 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,156 +1,5 @@
-# GitLab Helm Chart
+---
+redirect_to: https://docs.gitlab.com/charts/
+---
-This is the official way to install GitLab on a cloud native environment.
-
-NOTE: **Kubernetes experience required:**
-Our Helm charts are recommended for those who are familiar with Kubernetes.
-If you're not sure if Kubernetes is for you, our
-[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
-are mature, scalable, support [high availability](../../administration/high_availability/README.md)
-and are used today on GitLab.com.
-It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
-
-## Introduction
-
-The `gitlab` chart is the best way to operate GitLab on Kubernetes. This chart
-contains all the required components to get started, and can scale to large deployments.
-
-The default deployment includes:
-
-- Core GitLab components: Unicorn, Shell, Workhorse, Registry, Sidekiq, and Gitaly
-- Optional dependencies: Postgres, Redis, Minio
-- An auto-scaling, unprivileged [GitLab Runner](https://docs.gitlab.com/runner/) using the Kubernetes executor
-- Automatically provisioned SSL via [Let's Encrypt](https://letsencrypt.org/).
-
-## Limitations
-
-Some features of GitLab are not currently available:
-
-- [GitLab Pages](https://gitlab.com/charts/gitlab/issues/37)
-- [GitLab Geo](https://gitlab.com/charts/gitlab/issues/8)
-- [No in-cluster HA database](https://gitlab.com/charts/gitlab/issues/48)
-- MySQL will not be supported, as support is [deprecated within GitLab](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only)
-
-## Installing GitLab using the Helm Chart
-
-The `gitlab` chart includes all required dependencies, and takes a few minutes
-to deploy.
-
-TIP: **Tip:**
-For production deployments, we strongly recommend using the
-[detailed installation instructions](https://gitlab.com/charts/gitlab/blob/master/doc/installation/index.md)
-utilizing [external Postgres, Redis, and object storage](https://gitlab.com/charts/gitlab/tree/master/doc/advanced) services.
-
-### Requirements
-
-In order to deploy GitLab on Kubernetes, the following are required:
-
-1. `helm` and `kubectl` [installed on your computer](preparation/tools_installation.md).
-1. A Kubernetes cluster, version 1.8 or higher. 6vCPU and 16GB of RAM is recommended.
- - [Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html)
- - [Google GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-container-cluster)
- - [IBM IKS](https://console.bluemix.net/docs/tutorials/scalable-webapp-kubernetes.html#create_kube_cluster)
- - [Microsoft AKS](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal)
-1. A [wildcard DNS entry and external IP address](preparation/networking.md)
-1. [Authenticate and connect](preparation/connect.md) to the cluster
-1. Configure and initialize [Helm Tiller](preparation/tiller.md).
-
-### Deployment of GitLab to Kubernetes
-
-To deploy GitLab, the following three parameters are required:
-
-- `global.hosts.domain`: the [base domain](preparation/networking.md) of the
- wildcard host entry. For example, `example.com` if the wild card entry is
- `*.example.com`.
-- `global.hosts.externalIP`: the [external IP](preparation/networking.md) which
- the wildcard DNS resolves to.
-- `certmanager-issuer.email`: the email address to use when requesting new SSL
- certificates from Let's Encrypt.
-
-NOTE: **Note:**
-For deployments to Amazon EKS, there are
-[additional configuration requirements](preparation/eks.md). A full list of
-configuration options is [also available](https://gitlab.com/charts/gitlab/blob/master/doc/installation/command-line-options.md).
-
-Once you have all of your configuration options collected, you can get any
-dependencies and run helm. In this example, the helm release is named "gitlab":
-
-```sh
-helm repo add gitlab https://charts.gitlab.io/
-helm repo update
-helm upgrade --install gitlab gitlab/gitlab \
- --timeout 600 \
- --set global.hosts.domain=example.com \
- --set global.hosts.externalIP=10.10.10.10 \
- --set certmanager-issuer.email=email@example.com
-```
-
-### Monitoring the Deployment
-
-This will output the list of resources installed once the deployment finishes,
-which may take 5-10 minutes.
-
-The status of the deployment can be checked by running `helm status gitlab`
-which can also be done while the deployment is taking place if you run the
-command in another terminal.
-
-### Initial login
-
-You can access the GitLab instance by visiting the domain name beginning with
-`gitlab.` followed by the domain specified during installation. From the example
-above, the URL would be `https://gitlab.example.com`.
-
-If you manually created the secret for initial root password, you
-can use that to sign in as `root` user. If not, GitLab automatically
-created a random password for `root` user. This can be extracted by the
-following command (replace `<name>` by name of the release - which is `gitlab`
-if you used the command above):
-
-```sh
-kubectl get secret <name>-gitlab-initial-root-password -ojsonpath={.data.password} | base64 --decode ; echo
-```
-
-### Outgoing email
-
-By default outgoing email is disabled. To enable it, provide details for your SMTP server
-using the `global.smtp` and `global.email` settings. You can find details for these settings in the
-[command line options](https://gitlab.com/charts/gitlab/blob/master/doc/installation/command-line-options.md#email-configuration).
-
-If your SMTP server requires authentication make sure to read the section on providing
-your password in the [secrets documentation](https://gitlab.com/charts/gitlab/blob/master/doc/installation/secrets.md#smtp-password).
-You can disable authentication settings with `--set global.smtp.authentication=""`.
-
-If your Kubernetes cluster is on GKE, be aware that SMTP port [25 is blocked](https://cloud.google.com/compute/docs/tutorials/sending-mail/#using_standard_email_ports).
-
-### Deploying the Community Edition
-
-To deploy the Community Edition, include these options in your `helm install` command:
-
-```sh
---set gitlab.migrations.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-rails-ce
---set gitlab.sidekiq.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-sidekiq-ce
---set gitlab.unicorn.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-unicorn-ce
---set gitlab.unicorn.workhorse.image=registry.gitlab.com/gitlab-org/build/cng/gitlab-workhorse-ce
---set gitlab.task-runner.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-task-runner-ce
-```
-
-## Updating GitLab using the Helm Chart
-
-Once your GitLab Chart is installed, configuration changes and chart updates
-should be done using `helm upgrade`:
-
-```sh
-helm repo update
-helm upgrade --reuse-values gitlab gitlab/gitlab
-```
-
-## Uninstalling GitLab using the Helm Chart
-
-To uninstall the GitLab Chart, run the following:
-
-```sh
-helm delete 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
+This document was moved to [another location](https://docs.gitlab.com/charts/).
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index c0cb7694e91..43655767002 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -1,246 +1,5 @@
-# GitLab-Omnibus Helm Chart
+---
+redirect_to: https://docs.gitlab.com/charts/
+---
-CAUTION: **Caution:**
-This chart is **deprecated**. We recommend using the [`gitlab` chart](gitlab_chart.md)
-instead. A comparison of the two charts is available in [this video](https://youtu.be/Z6jWR8Z8dv8).
-
-For more information on available GitLab Helm Charts, see [Installing GitLab on Kubernetes](index.md).
-
-- This GitLab-Omnibus chart has been tested on Google Kubernetes Engine 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 via [Let's Encrypt](https://letsencrypt.org/).
-
-This Helm chart is suited for small to medium deployments and is **deprecated**
-and replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
-Due to the significant architectural changes, migrating will require backing up
-data out of this instance and importing it into the new deployment.
-
-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
-
-## Limitations
-
-[High Availability](../../administration/high_availability/README.md) and
-[Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) are not supported.
-
-## Requirements
-
-- _At least_ 4 GB of RAM available on your cluster. 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
-- A [wildcard DNS entry](#networking-requirements), which resolves to the external IP address
-- The `kubectl` CLI installed locally and authenticated for the cluster
-- The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
-
-### Networking requirements
-
-This chart configures a GitLab server and Kubernetes cluster which can support
-dynamic [Review Apps](../../ci/review_apps/index.md), as well as services like
-the integrated [Container Registry](../../user/project/container_registry.md)
-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 [load balancer](#load-balancer-ip) or
-[external IP](#external-ip-recommended). Configuration of the DNS entry will depend upon
-the DNS service being used.
-
-#### External IP (recommended)
-
-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. 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.
-
-Finally, set the `baseIP` setting to this IP address when
-[deploying GitLab](#configuring-and-installing-gitlab).
-
-#### Load Balancer IP
-
-If you do not specify a `baseIP`, an IP will be assigned to the Load Balancer or
-Ingress. You can retrieve this IP by running the following command *after* deploying GitLab:
-
-```sh
-kubectl get svc -w --namespace nginx-ingress nginx
-```
-
-The IP address will be displayed in the `EXTERNAL-IP` field, and should be used
-to configure the Wildcard DNS entry. For more information on creating a wildcard
-DNS entry, consult the documentation for the DNS server you are using.
-
-For production deployments of GitLab, we strongly recommend using a
-[external IP](#external-ip-recommended).
-
-## Configuring and Installing GitLab
-
-For most installations, two parameters are required:
-
-- `baseDomain`: the [base domain](#networking-requirements) of the wildcard host entry. For example, `mycompany.io` if the wild card entry is `*.mycompany.io`.
-- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt.
-
-Other common configuration options:
-
-- `baseIP`: the desired [external IP address](#external-ip-recommended)
-- `gitlab`: Choose the [desired edition](https://about.gitlab.com/pricing), 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 [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/), with `acs` also supported for the [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/).
-
-For additional configuration options, consult the
-[`values.yaml`](https://gitlab.com/charts/gitlab-omnibus/blob/master/values.yaml).
-
-### Choosing a different GitLab release version
-
-The version of GitLab installed is based on the `gitlab` setting (see [section](#configuring-and-installing-gitLab) above), and
-the value of the corresponding helm setting: `gitlabCEImage` or `gitabEEImage`.
-
-```yaml
-gitlab: CE
-gitlabCEImage: gitlab/gitlab-ce:9.5.2-ce.0
-gitlabEEImage: gitlab/gitlab-ee:9.5.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.
-
-### Persistent storage
-
-NOTE: **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.
-
-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: **Note:**
-Let's Encrypt limits a single TLD to five certificate requests within a single
-week. This means that common DNS wildcard services like [nip.io](http://nip.io)
-and [xip.io](http://xip.io) are unlikely to work.
-
-## Installing GitLab using the Helm Chart
-
-NOTE: **Note:**
-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 start.
-This may take a couple minutes depending on your cloud provider. If the error persists,
-please review the [requirements sections](#requirements) to ensure you have enough RAM, CPU, and storage.
-
-Add the GitLab Helm repository and initialize Helm:
-
-```bash
-helm init
-helm repo add gitlab https://charts.gitlab.io
-```
-
-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:
-
-```bash
-helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
-```
-
-Or you can pass them on the command line:
-
-```bash
-helm install --name gitlab --set baseDomain=gitlab.io,baseIP=192.0.2.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
-```
-
-## Updating GitLab using the Helm Chart
-
-If you are upgrading from a previous version to 0.1.35 or above, you will need to
-change the access mode values for GitLab's storage. To do this, set the following
-in `values.yaml` or on the CLI:
-
-```sh
-gitlabDataAccessMode=ReadWriteMany
-gitlabRegistryAccessMode=ReadWriteMany
-gitlabConfigAccessMode=ReadWriteMany
-```
-
-Once your GitLab Chart is installed, configuration changes and chart updates
-should be done using `helm upgrade`:
-
-```sh
-helm upgrade -f values.yaml gitlab gitlab/gitlab-omnibus
-```
-
-## Upgrading from CE to EE using the Helm Chart
-
-If you have installed the Community Edition using this chart, upgrading to
-Enterprise Edition is easy.
-
-If you are using a `values.yaml` file to specify the configuration options, edit
-the file and set `gitlab=ee`. If you would like to run a specific version of
-GitLab EE, set `gitlabEEImage` to be the desired GitLab
-[docker image](https://hub.docker.com/r/gitlab/gitlab-ee/tags/). Then you can
-use `helm upgrade` to update your GitLab instance to EE:
-
-```bash
-helm upgrade -f values.yaml gitlab gitlab/gitlab-omnibus
-```
-
-You can also upgrade and specify these options via the command line:
-
-```bash
-helm upgrade gitlab --set gitlab=ee,gitlabEEImage=gitlab/gitlab-ee:9.5.5-ee.0 gitlab/gitlab-omnibus
-```
-
-## Uninstalling GitLab using the Helm Chart
-
-To uninstall the GitLab Chart, run the following:
-
-```bash
-helm delete --purge gitlab
-```
-
-## Troubleshooting
-
-### Storage errors when updating `gitlab-omnibus` versions prior to 0.1.35
-
-Users upgrading `gitlab-omnibus` from a version prior to 0.1.35, may see an error
-like: `Error: UPGRADE FAILED: PersistentVolumeClaim "gitlab-gitlab-config-storage" is invalid: spec: Forbidden: field is immutable after creation`.
-
-This is due to a change in the access mode for GitLab storage in version 0.1.35.
-To successfully upgrade, the access mode flags must be set to `ReadWriteMany`
-as detailed in the [update section](#updating-gitlab-using-the-helm-chart).
-
-[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
-[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
+This document was moved to [another location](https://docs.gitlab.com/charts/).
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 68b2a146115..08ccf2cf9ad 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -1,269 +1,5 @@
-# GitLab Runner Helm Chart
-> **Note:**
-These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/gitlab-org/gitlab-runner/issues).
+---
+redirect_to: https://docs.gitlab.com/runner/install/kubernetes.html
+---
-The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
-Kubernetes cluster.
-
-This chart configures the Runner to:
-
-- Run using the GitLab Runner [Kubernetes executor](https://docs.gitlab.com/runner/install/kubernetes.html)
-- For each new job it receives from [GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/), it will provision a
- new pod within the specified namespace to run it.
-
-For more information on available GitLab Helm Charts, please see our [overview](index.md).
-
-## Prerequisites
-
-- Your GitLab Server's API is reachable from the cluster
-- Kubernetes 1.4+ with Beta APIs enabled
-- The `kubectl` CLI installed locally and authenticated for the cluster
-- The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
-
-## Configuring GitLab Runner using the Helm Chart
-
-Create a `values.yaml` file for your GitLab Runner configuration. See [Helm docs](https://github.com/kubernetes/helm/blob/master/docs/chart_template_guide/values_files.md)
-for information on how your values file will override the defaults.
-
-The default configuration can always be found in the [values.yaml](https://gitlab.com/charts/gitlab-runner/blob/master/values.yaml) in the chart repository.
-
-### Required configuration
-
-In order for GitLab Runner to function, your config file **must** specify the following:
-
- - `gitlabUrl` - the GitLab Server URL (with protocol) to register the runner against
- - `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be
- retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md) for more information.
-
-Unless you need to specify additional configuration, you are [ready to install](#installing-gitlab-runner-using-the-helm-chart).
-
-### Other configuration
-
-The rest of the configuration is [documented in the `values.yaml`](https://gitlab.com/charts/gitlab-runner/blob/master/values.yaml) in the chart repository.
-
-Here is a snippet of the important settings:
-
-```yaml
-## The GitLab Server URL (with protocol) that want to register the runner against
-## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register
-##
-gitlabUrl: http://gitlab.your-domain.com/
-
-## The Registration Token for adding new Runners to the GitLab Server. This must
-## be retrieved from your GitLab Instance.
-## ref: https://docs.gitlab.com/ee/ci/runners/README.html
-##
-runnerRegistrationToken: ""
-
-## Set the certsSecretName in order to pass custom certificates for GitLab Runner to use
-## Provide resource name for a Kubernetes Secret Object in the same namespace,
-## this is used to populate the /etc/gitlab-runner/certs directory
-## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
-##
-#certsSecretName:
-
-## Configure the maximum number of concurrent jobs
-## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
-##
-concurrent: 10
-
-## Defines in seconds how often to check GitLab for a new builds
-## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
-##
-checkInterval: 30
-
-## For RBAC support:
-rbac:
- create: false
-
- ## Run the gitlab-bastion container with the ability to deploy/manage containers of jobs
- ## cluster-wide or only within namespace
- clusterWideAccess: false
-
- ## Use the following Kubernetes Service Account name if RBAC is disabled in this Helm chart (see rbac.create)
- ##
- # serviceAccountName: default
-
-## Configuration for the Pods that the runner launches for each new job
-##
-runners:
- ## Default container image to use for builds when none is specified
- ##
- image: ubuntu:16.04
-
- ## Run all containers with the privileged flag enabled
- ## This will allow the docker:stable-dind image to run if you need to run Docker
- ## commands. Please read the docs before turning this on:
- ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
- ##
- privileged: false
-
- ## Namespace to run Kubernetes jobs in (defaults to 'default')
- ##
- # namespace:
-
- ## Build Container specific configuration
- ##
- builds:
- # cpuLimit: 200m
- # memoryLimit: 256Mi
- cpuRequests: 100m
- memoryRequests: 128Mi
-
- ## Service Container specific configuration
- ##
- services:
- # cpuLimit: 200m
- # memoryLimit: 256Mi
- cpuRequests: 100m
- memoryRequests: 128Mi
-
- ## Helper Container specific configuration
- ##
- helpers:
- # cpuLimit: 200m
- # memoryLimit: 256Mi
- cpuRequests: 100m
- memoryRequests: 128Mi
-
-```
-
-### Enabling RBAC support
-
-If your cluster has RBAC enabled, you can choose to either have the chart create its own service account or provide one.
-
-To have the chart create the service account for you, set `rbac.create` to true.
-
-### Controlling maximum Runner concurrency
-
-A single GitLab Runner deployed on Kubernetes is able to execute multiple jobs in parallel by automatically starting additional Runner pods. The [`concurrent` setting](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section) controls the maximum number of pods allowed at a single time, and defaults to `10`.
-
-```yaml
-## Configure the maximum number of concurrent jobs
-## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
-##
-concurrent: 10
-```
-
-### Running Docker-in-Docker containers with GitLab Runners
-
-See [Running Privileged Containers for the Runners](#running-privileged-containers-for-the-runners) for how to enable it,
-and the [GitLab CI Runner documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-in-your-builds) on running dind.
-
-### Running privileged containers for the Runners
-
-You can tell the GitLab Runner to run using privileged containers. You may need
-this enabled if you need to use the Docker executable within your GitLab CI jobs.
-
-This comes with several risks that you can read about in the
-[GitLab CI Runner documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-in-your-builds).
-
-If you are okay with the risks, and your GitLab CI Runner instance is registered
-against a specific project in GitLab that you trust the CI jobs of, you can
-enable privileged mode in `values.yaml`:
-
-```yaml
-runners:
- ## Run all containers with the privileged flag enabled
- ## This will allow the docker:stable-dind image to run if you need to run Docker
- ## commands. Please read the docs before turning this on:
- ## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
- ##
- privileged: true
-```
-
-### Providing a custom certificate for accessing GitLab
-
-You can provide a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
-to the GitLab Runner Helm Chart, which will be used to populate the container's
-`/etc/gitlab-runner/certs` directory.
-
-Each key name in the Secret will be used as a filename in the directory, with the
-file content being the value associated with the key.
-
-More information on how GitLab Runner uses these certificates can be found in the
-[Runner Documentation](https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates).
-
- - The key/file name used should be in the format `<gitlab-hostname>.crt`. For example: `gitlab.your-domain.com.crt`.
- - Any intermediate certificates need to be concatenated to your server certificate in the same file.
- - The hostname used should be the one the certificate is registered for.
-
-The GitLab Runner Helm Chart does not create a secret for you. In order to create
-the secret, you can prepare your certificate on you local machine, and then run
-the `kubectl create secret` command from the directory with the certificate
-
-```bash
-kubectl
- --namespace <NAMESPACE>
- create secret generic <SECRET_NAME>
- --from-file=<CERTFICATE_FILENAME>
-```
-
-- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner.
-- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
-- `<CERTFICATE_FILENAME>` is the filename for the certificate in your current directory that will be imported into the secret
-
-You then need to provide the secret's name to the GitLab Runner chart.
-
-Add the following to your `values.yaml`
-
-```yaml
-## Set the certsSecretName in order to pass custom certificates for GitLab Runner to use
-## Provide resource name for a Kubernetes Secret Object in the same namespace,
-## this is used to populate the /etc/gitlab-runner/certs directory
-## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
-##
-certsSecretName: <SECRET NAME>
-```
-
-- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
-
-## Installing GitLab Runner using the Helm Chart
-
-Add the GitLab Helm repository and initialize Helm:
-
-```bash
-helm repo add gitlab https://charts.gitlab.io
-helm init
-```
-
-Once you [have configured](#configuring-gitlab-runner-using-the-helm-chart) GitLab Runner in your `values.yml` file,
-run the following:
-
-```bash
-helm install --namespace <NAMESPACE> --name gitlab-runner -f <CONFIG_VALUES_FILE> gitlab/gitlab-runner
-```
-
-- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner.
-- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom configuration. See the
- [Configuring GitLab Runner using the Helm Chart](#configuring-gitlab-runner-using-the-helm-chart) section to create it.
-
-## Updating GitLab Runner using the Helm Chart
-
-Once your GitLab Runner Chart is installed, configuration changes and chart updates should we done using `helm upgrade`
-
-```bash
-helm upgrade --namespace <NAMESPACE> -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab-runner
-```
-
-Where:
-
-- `<NAMESPACE>` is the Kubernetes namespace where GitLab Runner is installed
-- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom configuration. See the
- [Configuring GitLab Runner using the Helm Chart](#configuring-gitlab-runner-using-the-helm-chart) section to create it.
-- `<RELEASE-NAME>` is the name you gave the chart when installing it.
- In the [Installing GitLab Runner using the Helm Chart](#installing-gitlab-runner-using-the-helm-chart) section, we called it `gitlab-runner`.
-
-## Uninstalling GitLab Runner using the Helm Chart
-
-To uninstall the GitLab Runner Chart, run the following:
-
-```bash
-helm delete --namespace <NAMESPACE> <RELEASE-NAME>
-```
-
-where:
-
-- `<NAMESPACE>` is the Kubernetes namespace where GitLab Runner is installed
-- `<RELEASE-NAME>` is the name you gave the chart when installing it.
- In the [Installing GitLab Runner using the Helm Chart](#installing-gitlab-runner-using-the-helm-chart) section, we called it `gitlab-runner`.
+This document was moved to [another location](https://docs.gitlab.com/runner/install/kubernetes.html).
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index ecc956d04e9..43655767002 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,42 +1,5 @@
---
-description: 'Read through the different methods to deploy GitLab on Kubernetes.'
+redirect_to: https://docs.gitlab.com/charts/
---
-# Installing GitLab on Kubernetes
-
-NOTE: **Kubernetes experience required:**
-Our Helm charts are recommended for those who are familiar with Kubernetes.
-If you're not sure if Kubernetes is for you, our
-[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
-are mature, scalable, support [high availability](../../administration/high_availability/README.md)
-and are used today on GitLab.com.
-It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
-
-The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
-to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md)
-is a package management tool for Kubernetes, allowing apps to be easily managed via their
-Charts. A [Chart](https://github.com/kubernetes/charts) is a detailed description
-of the application including how it should be deployed, upgraded, and configured.
-
-## GitLab Chart
-
-This chart contains all the required components to get started, and can scale to
-large deployments. It offers a number of benefits:
-
-- Horizontal scaling of individual components
-- No requirement for shared storage to scale
-- Containers do not need `root` permissions
-- Automatic SSL with Let's Encrypt
-- An unprivileged GitLab Runner
-- and plenty more.
-
-Learn more about the [GitLab chart](gitlab_chart.md).
-
-## GitLab Runner Chart
-
-If you already have a GitLab instance running, inside or outside of Kubernetes,
-and you'd like to leverage the Runner's
-[Kubernetes capabilities](https://docs.gitlab.com/runner/executors/kubernetes.html),
-it can be deployed with the GitLab Runner chart.
-
-Learn more about [gitlab-runner chart](gitlab_runner_chart.md).
+This document was moved to [another location](https://docs.gitlab.com/charts/).
diff --git a/doc/install/kubernetes/preparation/connect.md b/doc/install/kubernetes/preparation/connect.md
index a3a0cba4bf2..db55e03d3d4 100644
--- a/doc/install/kubernetes/preparation/connect.md
+++ b/doc/install/kubernetes/preparation/connect.md
@@ -1,27 +1,5 @@
-# Connecting your computer to a cluster
+---
+redirect_to: https://docs.gitlab.com/charts/installation/cloud/
+---
-In order to deploy software and settings to a cluster, you must connect and authenticate to it.
-
-## Connect to GKE cluster
-
-The command for connection to the cluster can be obtained from the
-[Google Cloud Platform Console](https://console.cloud.google.com/kubernetes/list)
-by the individual cluster.
-
-Look for the **Connect** button in the clusters list page or use the command below,
-filling in your cluster's information:
-
-```
-gcloud container clusters get-credentials <cluster-name> --zone <zone> --project <project-id>
-```
-
-## Connect to EKS cluster
-
-For the most up to date instructions, follow the Amazon EKS documentation on
-[connecting to a cluster](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html#eks-configure-kubectl).
-
-## Connect to local minikube cluster
-
-If you are doing local development, you can use `minikube` as your
-local cluster. If `kubectl cluster-info` is not showing `minikube` as the current
-cluster, use `kubectl config set-cluster minikube` to set the active cluster.
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/cloud/).
diff --git a/doc/install/kubernetes/preparation/eks.md b/doc/install/kubernetes/preparation/eks.md
index ea3b075dd82..975d35c11c6 100644
--- a/doc/install/kubernetes/preparation/eks.md
+++ b/doc/install/kubernetes/preparation/eks.md
@@ -1,45 +1,5 @@
-# Running GitLab on EKS
+---
+redirect_to: https://docs.gitlab.com/charts/installation/cloud/eks.html
+---
-There are a few nuances to Amazon EKS which are important to be aware of, when deploying GitLab.
-
-## Persistent volume management
-
-There are two methods to manage volume claims on Kubernetes:
-
-1. Manually creating each persistent volume (recommended on EKS)
-1. Utilizing dynamic provisioning to automatically create the persistent volumes
-
-### Manual provisioning of volumes (Recommended)
-
-Manually creating the volumes allows you to control the zone of each volume, as well as all other details supported by the underlying storage.
-
-Follow our documentation on [manually creating persistent volumes](https://gitlab.com/charts/gitlab/blob/master/doc/installation/storage.md#manually-creating-static-volumes).
-
-### Dynamic provisioning of volumes
-
-Dynamic provisioning utilizes a Kubernetes provisioner, like `aws-ebs`, to automatically create persistent volumes to fulfill each claim.
-
-With EKS, there are a few important details to keep in mind:
-
-1. Clusters are required to span multiple AZ's
-1. Kubernetes volume provisioners create volumes across zones without regard to which pod they belong to. This leads to scenarios where a pod with multiple volumes being unable to start due to the volumes being in different zones.
-1. There is no default Storage Class.
-
-The easiest way to solve this and still utilize dynamic provisioning is to utilize, or create, a Storage Class that is locked to a specific zone.
-
-> **Note**: Restricting volumes to specific zone will cause GitLab and any other application using this Storage Class to only reside in that zone. For multiple zone support, utilize [manually provisioned volumes](#manual-provisioning-of-volumes-recommended).
-
-To create the storage class, download and edit Amazon EKS's [sample Storage Class](https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html) and add the following parameter:
-
-```yaml
-parameters:
- zone: <desired-zone>
-```
-
-Then [specify the Storage Class](https://gitlab.com/charts/gitlab/blob/master/doc/installation/storage.md#using-a-custom-storage-class) name when deploying GitLab.
-
-## External access to GitLab
-
-By default, GitLab will an deploy an ingress which will create an associated Elastic Load Balancer. Since the DNS names of ELB's cannot be known ahead of time, it is difficult to utilize Let's Encrypt to automatically provision HTTPS certificates.
-
-We recommend [using your own certificates](https://gitlab.com/charts/gitlab/blob/master/doc/installation/tls.md#option-2-use-your-own-wildcard-certificate), and then mapping your desired DNS name to the created ELB using a CNAME record.
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/cloud/eks.html).
diff --git a/doc/install/kubernetes/preparation/networking.md b/doc/install/kubernetes/preparation/networking.md
index b9fb9a7399f..2af16a752dc 100644
--- a/doc/install/kubernetes/preparation/networking.md
+++ b/doc/install/kubernetes/preparation/networking.md
@@ -1,38 +1,5 @@
-# Networking Prerequisites
+---
+redirect_to: https://docs.gitlab.com/charts/installation/deployment.html#networking-and-dns
+---
-NOTE: **Note:**
-Amazon EKS utilizes Elastic Load Balancers, which are addressed by DNS name and
-cannot be known ahead of time. If you're using EKS, you can skip this section.
-
-The `gitlab` 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).
-
-To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the external IP.
-
-## External 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, to the Load Balancer.
-
-Set `global.hosts.externalIP` to this IP address when [deploying GitLab](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
-
-Then, create a [wildcard DNS record](#wildcard-dns-entry) which resolves to this IP address.
-
-### Creating an external IP on GCP
-
-When creating the external IP, it is critical to create it in the same region as your cluster. Otherwise, the IP address will fail to bind to the Load Balancer.
-
-1. Open the [web console](https://console.cloud.google.com)
-1. In the sidebar, browse to `VPC Network > External IP addresses`
-1. Click `Reserve static address`
-1. Choose `Regional` and select the region of your cluster
-1. Leave `Attached to` blank, as it will be automatically assigned during deployment
-
-## Wildcard DNS entry
-
-Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Typically this would be an `A record` for `*`, resolving to the external IP above.
-
-Please consult the documentation for your DNS service for more information on creating DNS records:
-
-- [Google Domains](https://support.google.com/domains/answer/3290350?hl=en)
-- [GoDaddy](https://www.godaddy.com/help/add-an-a-record-19238)
-
-Set `global.hosts.domain` to this DNS name when [deploying GitLab](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/deployment.html#networking-and-dns).
diff --git a/doc/install/kubernetes/preparation/rbac.md b/doc/install/kubernetes/preparation/rbac.md
index c5f8d7a7e9e..f94e7c24cdc 100644
--- a/doc/install/kubernetes/preparation/rbac.md
+++ b/doc/install/kubernetes/preparation/rbac.md
@@ -1,20 +1,5 @@
-# Role Based Access Control
+---
+redirect_to: https://docs.gitlab.com/charts/installation/deployment.html#rbac
+---
-Until Kubernetes 1.7, there were no permissions within a cluster. With the launch
-of 1.7, there is now a [role based access control system (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/)
-which determines what services can perform actions within a cluster.
-
-RBAC affects a few different aspects of GitLab:
-
-- [Installation of GitLab using Helm](tiller.md#preparing-for-helm-with-rbac)
-- Prometheus monitoring
-- GitLab Runner
-
-## Checking that RBAC is enabled
-
-Try listing the current cluster roles, if it fails then `RBAC` is disabled.
-The following command will output `false` if `RBAC` is disabled and `true` otherwise:
-
-```sh
-kubectl get clusterroles > /dev/null 2>&1 && echo true || echo false
-```
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/deployment.html#rbac).
diff --git a/doc/install/kubernetes/preparation/tiller.md b/doc/install/kubernetes/preparation/tiller.md
index 00b0737402b..66d6c8faece 100644
--- a/doc/install/kubernetes/preparation/tiller.md
+++ b/doc/install/kubernetes/preparation/tiller.md
@@ -1,109 +1,5 @@
-# Configuring and initializing Helm Tiller
-
-To make use of Helm, you must have a [Kubernetes][k8s-io] cluster. Ensure you can
-access your cluster using `kubectl`.
-
-Helm consists of two parts, the `helm` client and a `tiller` server inside Kubernetes.
-
-NOTE: **Note:**
-If you are not able to run Tiller in your cluster, for example on OpenShift, it
-is possible to use [Tiller locally](https://gitlab.com/charts/gitlab/tree/master/doc/helm#local-tiller)
-and avoid deploying it into the cluster. This should only be used when Tiller
-cannot be normally deployed.
-
-## Initialize Helm and Tiller
-
-Tiller is deployed into the cluster and interacts with the Kubernetes API to deploy your applications. If role based access control (RBAC) is enabled, Tiller will need to be [granted permissions](#preparing-for-helm-with-rbac) to allow it to talk to the Kubernetes API.
-
-If RBAC is not enabled, skip to [initializing Helm](#initialize-helm).
-
-If you are not sure whether RBAC is enabled in your cluster, or to learn more, read through our [RBAC documentation](rbac.md).
-
-## Preparing for Helm with RBAC
-
-Helm's Tiller will need to be granted permissions to perform operations. These instructions grant cluster wide permissions, however for more advanced deployments [permissions can be restricted to a single namespace](https://docs.helm.sh/using_helm/#example-deploy-tiller-in-a-namespace-restricted-to-deploying-resources-only-in-that-namespace). To grant access to the cluster, we will create a new `tiller` service account and bind it to the `cluster-admin` role.
-
-Create a file `rbac-config.yaml` with the following contents:
-
-```yaml
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: tiller
- namespace: kube-system
---
-apiVersion: rbac.authorization.k8s.io/v1beta1
-kind: ClusterRoleBinding
-metadata:
- name: tiller
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
-subjects:
- - kind: ServiceAccount
- name: tiller
- namespace: kube-system
-```
-
-Next we need to connect to the cluster and upload the RBAC config.
-
-### Upload the RBAC config
-
-Some clusters require authentication to use `kubectl` to create the Tiller roles.
-
-#### Upload the RBAC config as an admin user (GKE)
-
-For GKE, you need to obtain the admin credentials. This command will output the admin password:
-
-```
-gcloud container clusters describe <cluster-name> --zone <zone> --project <project-id> --format='value(masterAuth.password)'
-```
-
-Use the admin password to set the admin credentials. Replace the password value below with the output value from the above step:
-
-```
-kubectl config set-credentials admin --username=admin --password=xxxxxxxxxxxxxx
-```
-
-Once credentials have been set, create the role:
-
-```
-kubectl --user=admin create -f rbac-config.yaml
-```
-
-#### Upload the RBAC config (Non-GKE clusters)
-
-For other clusters like Amazon EKS, you can directly upload the RBAC configuration.
-
-```
-kubectl create -f rbac-config.yaml
-```
-
-## Initialize Helm
-
-Deploy Helm Tiller with a service account:
-
-```
-helm init --service-account tiller
-```
-
-If your cluster previously had Helm/Tiller installed,
-run the following to ensure that the deployed version of Tiller matches the local Helm version:
-
-```
-helm init --upgrade --service-account tiller
-```
-
-### Patching Helm Tiller for Amazon EKS
-
-Helm Tiller requires a flag to be enabled to work properly on Amazon EKS:
-
-```
-kubectl -n kube-system patch deployment tiller-deploy -p '{"spec": {"template": {"spec": {"automountServiceAccountToken": true}}}}'
-```
+redirect_to: https://docs.gitlab.com/charts/installation/tools.html
+---
-[helm]: https://helm.sh
-[helm-using]: https://docs.helm.sh/using_helm
-[k8s-io]: https://kubernetes.io/
-[gcp-k8s]: https://console.cloud.google.com/kubernetes/list
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/tools.html).
diff --git a/doc/install/kubernetes/preparation/tools_installation.md b/doc/install/kubernetes/preparation/tools_installation.md
index d2f7a69a0af..66d6c8faece 100644
--- a/doc/install/kubernetes/preparation/tools_installation.md
+++ b/doc/install/kubernetes/preparation/tools_installation.md
@@ -1,19 +1,5 @@
-# Installing kubectl and Helm on your computer
+---
+redirect_to: https://docs.gitlab.com/charts/installation/tools.html
+---
-In order to work with the GitLab Helm charts, `kubectl` and `helm` must be installed and configured on your computer.
-
-## Installing `kubectl`
-
-`kubectl` is the Kubernetes command line tool, which can be used to deploy settings to the cluster.
-
-Follow the [official documentation](https://kubernetes.io/docs/tasks/tools/install-kubectl/) for the most up to date instructions.
-
-## Installing `helm`
-
-Helm is a package management tool for Kubernetes, and is used to deploy charts.
-
-You can get Helm from the project's [releases page](https://github.com/kubernetes/helm/releases), or follow other options under the official documentation of [Installing Helm](https://docs.helm.sh/using_helm/#installing-helm).
-
-# Next steps
-
-Once installed, proceed to the next [installation step](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
+This document was moved to [another location](https://docs.gitlab.com/charts/installation/tools.html).
diff --git a/doc/install/ldap.md b/doc/install/ldap.md
new file mode 100644
index 00000000000..d8d54864586
--- /dev/null
+++ b/doc/install/ldap.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../administration/auth/ldap.md'
+---
+
+This document was moved to [another location](../administration/auth/ldap.md).
diff --git a/doc/install/openshift_and_gitlab/img/pods-overview.png b/doc/install/openshift_and_gitlab/img/pods-overview.png
deleted file mode 100644
index 65927f65f4f..00000000000
--- a/doc/install/openshift_and_gitlab/img/pods-overview.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/openshift_and_gitlab/img/storage-volumes.png b/doc/install/openshift_and_gitlab/img/storage-volumes.png
deleted file mode 100644
index 3fd092919bb..00000000000
--- a/doc/install/openshift_and_gitlab/img/storage-volumes.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 509020d1975..18981c43464 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -1,8 +1,5 @@
---
-author: Achilleas Pipinellis
-author_gitlab: axil
-level: intermediary
-article_type: tutorial
+type: howto
date: 2016-06-28
---
@@ -11,7 +8,7 @@ date: 2016-06-28
CAUTION: **Deprecated:**
This article is deprecated. Use the official Kubernetes Helm charts for
installing GitLab to OpenShift. Check out the
-[official installation docs](https://gitlab.com/charts/gitlab/blob/master/doc/cloud/openshift.md)
+[official installation docs](https://docs.gitlab.com/charts/installation/cloud/openshift.html)
for details.
## Introduction
diff --git a/doc/install/pivotal/index.md b/doc/install/pivotal/index.md
new file mode 100644
index 00000000000..f068572f1e9
--- /dev/null
+++ b/doc/install/pivotal/index.md
@@ -0,0 +1,12 @@
+# GitLab Pivotal Tile **[PREMIUM ONLY]**
+
+CAUTION: **Discontinued:**
+As of September 13, 2017, the GitLab Enterprise Plus for Pivotal Cloud Foundry
+tile on Pivotal Network has reached its End of Availability (“EoAâ€) and is no
+longer available for download or sale through Pivotal. Current customers with
+active subscriptions will continue to receive support from GitLab through their
+subscription term. Pivotal and GitLab are collaborating on creating a new
+Kubernetes-based tile for the Pivotal Container Service. Please contact GitLab
+support with any questions regarding GitLab Enterprise Plus for Pivotal Cloud Foundry.
+
+Original article: <https://docs.pivotal.io/partners/gitlab/index.html>.
diff --git a/doc/install/redis.md b/doc/install/redis.md
index 4075e6283d0..cff5c2f2611 100644
--- a/doc/install/redis.md
+++ b/doc/install/redis.md
@@ -1,60 +1,5 @@
-# Install Redis on old distributions
+---
+redirect_to: installation.md#7-redis
+---
-GitLab requires at least Redis 2.8. The following guide is for Debian 7 and
-Ubuntu 12.04. If you are using Debian 8 or Ubuntu 14.04 and up, follow the
-[installation guide](installation.md).
-
-## Install Redis 2.8 in Debian 7
-
-Redis 2.8 is included in the Debian Wheezy [backports] repository.
-
-1. Edit `/etc/apt/sources.list` and add the following line:
-
- ```
- deb http://http.debian.net/debian wheezy-backports main
- ```
-
-1. Update the repositories:
-
- ```
- sudo apt-get update
- ```
-
-1. Install `redis-server`:
-
- ```
- sudo apt-get -t wheezy-backports install redis-server
- ```
-
-1. Follow the rest of the [installation guide](installation.md).
-
-## Install Redis 2.8 in Ubuntu 12.04
-
-We will [use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
-to install a recent version of Redis.
-
-1. Install the PPA repository:
-
- ```
- sudo add-apt-repository ppa:chris-lea/redis-server
- ```
-
- Your system will now fetch the PPA's key. This enables your Ubuntu system to
- verify that the packages in the PPA have not been interfered with since they
- were built.
-
-1. Update the repositories:
-
- ```
- sudo apt-get update
- ```
-
-1. Install `redis-server`:
-
- ```
- sudo apt-get install redis-server
- ```
-
-1. Follow the rest of the [installation guide](installation.md).
-
-[backports]: http://backports.debian.org/Instructions/ "Debian backports website"
+This document was moved to [another location](installation.md#7-redis).
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 5f129fd3bd1..96b7d0f3648 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -1,18 +1,19 @@
+---
+type: reference
+---
+
# Install GitLab under a relative URL
-NOTE: **Note:**
+While it is recommended to install GitLab on its own (sub)domain, sometimes
+this is not possible due to a variety of reasons. In that case, GitLab can also
+be installed under a relative URL, for example `https://example.com/gitlab`.
+
This document describes how to run GitLab under a relative URL for installations
from source. If you are using an Omnibus package,
[the steps are different][omnibus-rel]. Use this guide along with the
[installation guide](installation.md) if you are installing GitLab for the
first time.
----
-
-While it is recommended to install GitLab on its own (sub)domain, sometimes
-this is not possible due to a variety of reasons. In that case, GitLab can also
-be installed under a relative URL, for example `https://example.com/gitlab`.
-
There is no limit to how deeply nested the relative URL can be. For example you
could serve GitLab under `/foo/bar/gitlab/git` without any issues.
@@ -20,8 +21,6 @@ Note that by changing the URL on an existing GitLab installation, all remote
URLs will change, so you'll have to manually edit them in any local repository
that points to your GitLab instance.
----
-
The TL;DR list of configuration files that you need to change in order to
serve GitLab under a relative URL is:
@@ -126,3 +125,15 @@ To disable the relative URL:
[omnibus-rel]: http://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to set up relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index c1f2297f3be..107d48fb90c 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -1,5 +1,12 @@
+---
+type: reference
+---
+
# Requirements
+This page includes useful information on the supported Operating Systems as well
+as the hardware requirements that are needed to install and use GitLab.
+
## Operating Systems
### Supported Unix distributions
@@ -12,7 +19,7 @@
- Scientific Linux (please use the CentOS packages and instructions)
- Oracle Linux (please use the CentOS packages and instructions)
-For the installations options please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
+For the installations options, see [the main installation page](README.md).
### Unsupported Unix distributions
@@ -51,6 +58,8 @@ Apart from a local hard drive you can also mount a volume that supports the netw
If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+NOTE: **Note:** Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+
### CPU
- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
@@ -85,7 +94,7 @@ if your available memory changes. We also recommend [configuring the kernel's sw
to a low value like `10` to make the most of your RAM while still having the swap
available when needed.
-Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those.
+NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those.
## Database
@@ -103,8 +112,10 @@ features of GitLab work with MySQL/MariaDB:
1. MySQL support for subgroups was [dropped with GitLab 9.3][post].
See [issue #30472][30472] for more information.
-1. Geo does [not support MySQL](https://docs.gitlab.com/ee/administration/geo/replication/database.html#mysql-replication). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
+1. Geo does [not support MySQL](../administration/geo/replication/database.md). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
1. [Zero downtime migrations](../update/README.md#upgrading-without-downtime) do not work with MySQL.
+1. [Database load balancing](../administration/database_load_balancing.md) is
+ supported only for PostgreSQL. **[PREMIUM ONLY]**
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
@@ -139,7 +150,17 @@ On some systems you may need to install an additional package (e.g.
#### Additional requirements for GitLab Geo
-If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html), the [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database) also requires the `postgres_fdw` extension.
+If you are using [GitLab Geo](../development/geo.md):
+
+- We strongly recommend running Omnibus-managed instances as they are actively
+ developed and tested. We aim to be compatible with most external (not managed
+ by Omnibus) databases (for example, AWS RDS) but we do not guarantee
+ compatibility.
+- The
+ [tracking database](../development/geo.md#using-the-tracking-database)
+ requires the
+ [postgres_fdw](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
+ extension.
```
CREATE EXTENSION postgres_fdw;
@@ -155,7 +176,7 @@ So for a machine with 2 cores, 3 unicorn workers is ideal.
For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
-To change the Unicorn workers when you have the Omnibus package (which defaults to the recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
+To change the Unicorn workers when you have the Omnibus package (which defaults to the recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/unicorn.html).
## Redis and Sidekiq
@@ -187,13 +208,23 @@ you decide to run GitLab Runner and the GitLab Rails application on the same
machine.
It is also not safe to install everything on a single machine, because of the
-[security reasons] - especially when you plan to use shell executor with GitLab
+[security reasons](https://docs.gitlab.com/runner/security/), especially when you plan to use shell executor with GitLab
Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features.
+The GitLab Runner server requirements depend on:
+
+- The type of [executor](https://docs.gitlab.com/runner/executors/) you configured on GitLab Runner.
+- Resources required to run build jobs.
+- Job concurrency settings.
+
+Since the nature of the jobs varies for each use case, you will need to experiment by adjusting the job concurrency to get the optimum setting.
-[security reasons]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/security/index.md
+For reference, GitLab.com's [auto-scaling shared runner](../user/gitlab_com/index.md#shared-runners) is configured so that a **single job** will run in a **single instance** with:
+
+- 1vCPU.
+- 3.75GB of RAM.
## Supported web browsers
@@ -205,7 +236,22 @@ We support the current and the previous major release of:
- Microsoft Edge
- Internet Explorer 11
+The browser vendors release regular minor version updates with important bug fixes and security updates.
+Support is only provided for the current minor version of the major version you are running.
+
Each time a new browser version is released, we begin supporting that version and stop supporting the third most recent version.
-Note: We do not support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
+NOTE: **Note:** We do not support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
in the future because we have features such as Issue Boards which require JavaScript extensively.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/structure.md b/doc/install/structure.md
index 8fc6ab4ab2f..87ef11c60fe 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -1,19 +1,5 @@
-# GitLab directory structure
+---
+redirect_to: installation.md#gitlab-directory-structure
+---
-This is the directory structure you will end up with following the instructions in the Installation Guide.
-
- |-- home
- | |-- git
- | |-- .ssh
- | |-- gitlab
- | |-- gitlab-shell
- | |-- repositories
-
-- `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
-- `/home/git/gitlab` - GitLab core software.
-- `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
-- `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md).**
-
-*Note: the default locations for repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
-
-To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
+This page was moved to [another location](#installation.md#gitlab-directory-structure).
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f5bc0693b84..1fea6a32c28 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -15,7 +15,9 @@ See the documentation below for details on how to configure these services.
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
+- [Jenkins](jenkins.md) Integrate with the Jenkins CI
- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Kerberos](kerberos.md) Integrate with Kerberos
- [LDAP](ldap.md) Set up sign in via LDAP
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
@@ -29,8 +31,8 @@ See the documentation below for details on how to configure these services.
## Project services
-Integration with services such as Campfire, Flowdock, Pivotal Tracker, and Slack
-are available in the form of a [Project Service][].
+Integration with services such as Campfire, Flowdock, HipChat,
+Pivotal Tracker, and Slack are available in the form of a [Project Service][].
[Project Service]: ../user/project/integrations/project_services.md
diff --git a/doc/integration/chat_commands.md b/doc/integration/chat_commands.md
index 2856992ee25..1a4fb46046d 100644
--- a/doc/integration/chat_commands.md
+++ b/doc/integration/chat_commands.md
@@ -1 +1,5 @@
+---
+redirect_to: 'slash_commands.md'
+---
+
This document was moved to [integration/slash_commands.md](slash_commands.md).
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index 2bc526dc3db..30e3e888b29 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/auth/crowd.md'
+---
+
This document was moved to [`administration/auth/crowd`](../administration/auth/crowd.md).
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
new file mode 100644
index 00000000000..57a5a42fbed
--- /dev/null
+++ b/doc/integration/elasticsearch.md
@@ -0,0 +1,520 @@
+# Elasticsearch integration **[STARTER ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/109 "Elasticsearch Merge Request") in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4. Support
+> for [Amazon Elasticsearch](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html) was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1305) in GitLab
+> [Starter](https://about.gitlab.com/pricing/) 9.0.
+
+This document describes how to set up Elasticsearch with GitLab. Once enabled,
+you'll have the benefit of fast search response times and the advantage of two
+special searches:
+
+- [Advanced Global Search](../user/search/advanced_global_search.md)
+- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
+
+## Version Requirements
+<!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
+
+| GitLab version | Elasticsearch version |
+| -------------- | --------------------- |
+| GitLab Enterprise Edition 8.4 - 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
+| GitLab Enterprise Edition 9.0 - 11.4 | Elasticsearch 5.1 - 5.5 |
+| GitLab Enterprise Edition 11.5+ | Elasticsearch 5.6 - 6.x |
+
+## Installing Elasticsearch
+
+Elasticsearch is _not_ included in the Omnibus packages. You will have to
+install it yourself whether you are using the Omnibus package or installed
+GitLab from source. Providing detailed information on installing Elasticsearch
+is out of the scope of this document.
+
+Once the data is added to the database or repository and [Elasticsearch is
+enabled in the admin area](#enabling-elasticsearch) the search index will be
+updated automatically. Elasticsearch can be installed on the same machine as
+GitLab or on a separate server, or you can use the [Amazon Elasticsearch](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html)
+service.
+
+You can follow the steps as described in the [official web site](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html "Elasticsearch installation documentation") or
+use the packages that are available for your OS.
+
+## Elasticsearch repository indexer (beta)
+
+In order to improve elasticsearch indexing performance, GitLab has made available a [new indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
+This will replace the included Ruby indexer in the future but should be considered beta software for now, so there may be some bugs.
+
+If you would like to use it, please follow the instructions below.
+
+### Installation
+
+First, we need to install some dependencies, then we'll build and install
+the indexer itself.
+
+#### Dependencies
+
+This project relies on [ICU](http://site.icu-project.org/) for text encoding,
+therefore we need to ensure the development packages for your platform are
+installed before running `make`.
+
+##### Debian / Ubuntu
+
+To install on Debian or Ubuntu, run:
+
+```sh
+sudo apt install libicu-dev
+```
+
+##### CentOS / RHEL
+
+To install on CentOS or RHEL, run:
+
+```sh
+sudo yum install libicu-devel
+```
+
+##### Mac OSX
+
+To install on macOS, run:
+
+```sh
+brew install icu4c
+export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:$PKG_CONFIG_PATH"
+```
+
+#### Building and installing
+
+To build and install the indexer, run:
+
+```sh
+git clone https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer.git
+cd gitlab-elasticsearch-indexer
+make
+sudo make install
+```
+
+The `gitlab-elasticsearch-indexer` will be installed to `/usr/local/bin`.
+
+You can change the installation path with the `PREFIX` env variable.
+Please remember to pass the `-E` flag to `sudo` if you do so.
+
+Example:
+
+```sh
+PREFIX=/usr sudo -E make install
+```
+
+Once installed, enable it under your instance's elasticsearch settings explained [below](#enabling-elasticsearch).
+
+## System Requirements
+
+Elasticsearch requires additional resources in excess of those documented in the
+[GitLab system requirements](../install/requirements.md). These will vary by
+installation size, but you should ensure **at least** an additional **8 GiB of RAM**
+for each Elasticsearch node, per the [official guidelines](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html).
+
+Keep in mind, this is the **minimum requirements** as per Elasticsearch. For
+production instances, they recommend considerably more resources.
+
+Storage requirements also vary based on the installation side, but as a rule of
+thumb, you should allocate the total size of your production database, **plus**
+two-thirds of the total size of your git repositories. Efforts to reduce this
+total are being tracked in this epic: [gitlab-org&153](https://gitlab.com/groups/gitlab-org/-/epics/153).
+
+## Enabling Elasticsearch
+
+In order to enable Elasticsearch, you need to have admin access. Go to
+**Admin > Settings > Integrations** and find the "Elasticsearch" section.
+
+The following Elasticsearch settings are available:
+
+| Parameter | Description |
+| --------- | ----------- |
+| `Elasticsearch indexing` | Enables/disables Elasticsearch indexing. You may want to enable indexing but disable search in order to give the index time to be fully completed, for example. Also, keep in mind that this option doesn't have any impact on existing data, this only enables/disables background indexer which tracks data changes. So by enabling this you will not get your existing data indexed, use special rake task for that as explained in [Adding GitLab's data to the Elasticsearch index](#adding-gitlabs-data-to-the-elasticsearch-index). |
+| `Use the new repository indexer (beta)` | Perform repository indexing using [GitLab Elasticsearch Indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). |
+| `Search with Elasticsearch enabled` | Enables/disables using Elasticsearch in search. |
+| `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://host1, https://host2:9200"). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). |
+| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, larger indexes need to have more shards. Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html#create-index-settings) |
+| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value will greatly increase total disk space required by the index. |
+| `Limit namespaces and projects that can be indexed` | Enabling this will allow you to select namespaces and projects to index. All other namespaces and projects will use database search instead. Please note that if you enable this option but do not select any namespaces or projects, none will be indexed. [Read more below](#limiting-namespaces-and-projects).
+| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) or [AWS EC2 Instance Profile Credentials](http://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli). The policies must be configured to allow `es:*` actions. |
+| `AWS Region` | The AWS region your Elasticsearch service is located in. |
+| `AWS Access Key` | The AWS access key. |
+| `AWS Secret Access Key` | The AWS secret access key. |
+
+### Limiting namespaces and projects
+
+If you select `Limit namespaces and projects that can be indexed`, more options will become available
+![limit namespaces and projects options](img/limit_namespaces_projects_options.png)
+
+You can select namespaces and projects to index exclusively. Please note that if the namespace is a group it will include
+any sub-groups and projects belonging to those sub-groups to be indexed as well.
+
+You can filter the selection dropdown by writing part of the namespace or project name you're interested in.
+![limit namespace filter](img/limit_namespace_filter.png)
+
+NOTE: **Note**:
+If no namespaces or projects are selected, no Elasticsearch indexing will take place.
+
+CAUTION: **Warning**:
+If you have already indexed your instance, you will have to regenerate the index in order to delete all existing data
+for filtering to work correctly. To do this run the rake tasks `gitlab:elastic:create_empty_index` and
+`gitlab:elastic:clear_index_status` Afterwards, removing a namespace or a projeect from the list will delete the data
+from the Elasticsearch index as expected.
+
+## Disabling Elasticsearch
+
+To disable the Elasticsearch integration:
+
+1. Navigate to the **Admin > Settings > Integrations**
+1. Find the 'Elasticsearch' section and uncheck 'Search with Elasticsearch enabled'
+ and 'Elasticsearch indexing'
+1. Click **Save** for the changes to take effect
+1. [Optional] Delete the existing index by running the command `sudo gitlab-rake gitlab:elastic:delete_index`
+
+## Adding GitLab's data to the Elasticsearch index
+
+### Indexing small instances (database size less than 500 MiB, size of repos less than 5 GiB)
+
+Configure Elasticsearch's host and port in **Admin > Settings**. Then index the data using one of the following commands:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index
+
+# Installations from source
+bundle exec rake gitlab:elastic:index RAILS_ENV=production
+```
+
+After it completes the indexing process, [enable Elasticsearch searching](elasticsearch.md#enabling-elasticsearch).
+
+### Indexing large instances
+
+WARNING: **Warning**:
+Performing asynchronous indexing, as this will describe, will generate a lot of sidekiq jobs.
+Make sure to prepare for this task by either [Horizontally Scaling](../administration/high_availability/README.md#basic-scaling)
+or creating [extra sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
+
+Configure Elasticsearch's host and port in **Admin > Settings > Integrations**. Then create empty indexes using one of the following commands:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:create_empty_index
+
+# Installations from source
+bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
+```
+
+Indexing large Git repositories can take a while. To speed up the process, you
+can temporarily disable auto-refreshing and replicating. In our experience, you can expect a 20%
+decrease in indexing time. We'll enable them when indexing is done. This step is optional!
+
+```bash
+curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "refresh_interval" : "-1",
+ "number_of_replicas" : 0
+ } }'
+```
+
+Then enable Elasticsearch indexing and run project indexing tasks:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index_projects
+
+# Installations from source
+bundle exec rake gitlab:elastic:index_projects RAILS_ENV=production
+```
+
+This enqueues a Sidekiq job for each project that needs to be indexed.
+You can view the jobs in the admin panel (they are placed in the `elastic_indexer`
+queue), or you can query indexing status using a rake task:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index_projects_status
+
+# Installations from source
+bundle exec rake gitlab:elastic:index_projects_status RAILS_ENV=production
+
+Indexing is 65.55% complete (6555/10000 projects)
+```
+
+If you want to limit the index to a range of projects you can provide the
+`ID_FROM` and `ID_TO` parameters:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000
+
+# Installations from source
+bundle exec rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
+```
+
+Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
+The above examples will index all projects starting with ID `1001` up to (and including) ID `2000`.
+
+TIP: **Troubleshooting:**
+Sometimes the project indexing jobs queued by `gitlab:elastic:index_projects`
+can get interrupted. This may happen for many reasons, but it's always safe
+to run the indexing task again - it will skip those repositories that have
+already been indexed.
+
+As the indexer stores the last commit SHA of every indexed repository in the
+database, you can run the indexer with the special parameter `UPDATE_INDEX` and
+it will check every project repository again to make sure that every commit in
+that repository is indexed, it can be useful in case if your index is outdated:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000
+
+# Installations from source
+bundle exec rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
+```
+
+You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
+indexer to "forget" all progress, so retrying the indexing process from the
+start.
+
+To index all wikis:
+
+```sh
+# Omnibus installations
+sudo gitlab-rake gitlab:elastic:index_wikis
+
+# Installations from source
+bundle exec rake gitlab:elastic:index_wikis RAILS_ENV=production
+```
+
+The wiki indexer also supports the `ID_FROM` and `ID_TO` parameters if you want
+to limit a project set.
+
+Enable replication and refreshing again after indexing (only if you previously disabled it):
+
+```bash
+curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "number_of_replicas" : 1,
+ "refresh_interval" : "1s"
+ } }'
+```
+
+A force merge should be called after enabling the refreshing above.
+
+For Elasticsearch 6.x, before proceeding with the force merge, the index should be in read-only mode:
+
+```bash
+curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "settings": {
+ "index.blocks.write": true
+ } }'
+```
+
+Then, initiate the force merge:
+
+```bash
+curl --request POST 'http://localhost:9200/gitlab-production/_forcemerge?max_num_segments=5'
+```
+
+After this, if your index is in read-only, switch back to read-write:
+
+```bash
+curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "settings": {
+ "index.blocks.write": false
+ } }'
+```
+
+Enable Elasticsearch search in **Admin > Settings > Integrations**. That's it. Enjoy it!
+
+## GitLab Elasticsearch Rake Tasks
+
+There are several rake tasks available to you via the command line:
+
+- [sudo gitlab-rake gitlab:elastic:index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This is a wrapper task. It does the following:
+ - `sudo gitlab-rake gitlab:elastic:create_empty_index`
+ - `sudo gitlab-rake gitlab:elastic:clear_index_status`
+ - `sudo gitlab-rake gitlab:elastic:index_projects`
+ - `sudo gitlab-rake gitlab:elastic:index_wikis`
+ - `sudo gitlab-rake gitlab:elastic:index_snippets`
+- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This iterates over all projects and queues sidekiq jobs to index them in the background.
+- [sudo gitlab-rake gitlab:elastic:index_projects_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100.
+- [sudo gitlab-rake gitlab:elastic:index_wikis](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - Iterates over every project, determines if said project contains wiki data, and then indexes the blobs (content) of said wiki data.
+- [sudo gitlab-rake gitlab:elastic:create_empty_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This generates an empty index on the Elasticsearch side.
+- [sudo gitlab-rake gitlab:elastic:clear_index_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This deletes all instances of IndexStatus for all projects.
+- [sudo gitlab-rake gitlab:elastic:delete_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This removes the GitLab index on the Elasticsearch instance.
+- [sudo gitlab-rake gitlab:elastic:recreate_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - Does the same thing as `sudo gitlab-rake gitlab:elastic:create_empty_index`
+- [sudo gitlab-rake gitlab:elastic:add_feature_visibility_levels_to_project](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - Adds visibility information to the indices for projects.
+- [sudo gitlab-rake gitlab:elastic:index_snippets](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - Performs an Elasticsearch import that indexes the snippets data.
+
+### Environment Variables
+
+In addition to the rake tasks, there are some environment variables that can be used to modify the process:
+
+| Environment Variable | Data Type | What it does |
+| -------------------- |:---------:| ---------------------------------------------------------------------------- |
+| `UPDATE_INDEX` | Boolean | Tells the indexer to overwrite any existing index data (true/false). |
+| `ID_TO` | Integer | Tells the indexer to only index projects less than or equal to the value. |
+| `ID_FROM` | Integer | Tells the indexer to only index projects greater than or equal to the value. |
+
+### Indexing a specific project
+
+Because the `ID_TO` and `ID_FROM` environment variables use the `or equal to` comparison, you can index only one project by using both these variables with the same project ID number:
+
+```sh
+root@git:~# sudo gitlab-rake gitlab:elastic:index_projects ID_TO=5 ID_FROM=5
+Indexing project repositories...I, [2019-03-04T21:27:03.083410 #3384] INFO -- : Indexing GitLab User / test (ID=33)...
+I, [2019-03-04T21:27:05.215266 #3384] INFO -- : Indexing GitLab User / test (ID=33) is done!
+```
+
+## Elasticsearch Index Scopes
+
+When performing a search, the GitLab index will use the following scopes:
+
+| Scope Name | What it searches |
+| ---------------- | ---------------------- |
+| `commits` | Commit data |
+| `projects` | Project data (default) |
+| `blobs` | Code |
+| `issues` | Issue data |
+| `merge_requests` | Merge Request data |
+| `milestones` | Milestone data |
+| `notes` | Note data |
+| `snippets` | Snippet data |
+| `wiki_blobs` | Wiki contents |
+
+## Tuning
+
+### Deleted documents
+
+Whenever a change or deletion is made to an indexed GitLab object (a merge request description is changed, a file is deleted from the master branch in a repository, a project is deleted, etc), a document in the index is deleted. However, since these are "soft" deletes, the overall number of "deleted documents", and therefore wasted space, increases. Elasticsearch does intelligent merging of segments in order to remove these deleted documents. However, depending on the amount and type of activity in your GitLab installation, it's possible to see as much as 50% wasted space in the index.
+
+In general, we recommend simply letting Elasticseach merge and reclaim space automatically, with the default settings. From [Lucene's Handling of Deleted Documents](https://www.elastic.co/blog/lucenes-handling-of-deleted-documents "Lucene's Handling of Deleted Documents"), _"Overall, besides perhaps decreasing the maximum segment size, it is best to leave Lucene's defaults as-is and not fret too much about when deletes are reclaimed."_
+
+However, some larger installations may wish to tune the merge policy settings:
+
+- Consider reducing the `index.merge.policy.max_merged_segment` size from the default 5 GB to maybe 2 GB or 3 GB. Merging only happens when a segment has at least 50% deletions. Smaller segment sizes will allow merging to happen more frequently.
+
+ ```bash
+ curl --request PUT http://localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "merge.policy.max_merged_segment": "2gb"
+ }
+ }'
+ ```
+
+- You can also adjust `index.merge.policy.reclaim_deletes_weight`, which controls how aggressively deletions are targeted. But this can lead to costly merge decisions, so we recommend not changing this unless you understand the tradeoffs.
+
+ ```bash
+ curl --request PUT http://localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "merge.policy.reclaim_deletes_weight": "3.0"
+ }
+ }'
+ ```
+
+- Do not do a [force merge](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html "Force Merge") to remove deleted documents. A warning in the [documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html "Force Merge") states that this can lead to very large segments that may never get reclaimed, and can also cause significant performance or availability issues.
+
+## Troubleshooting
+
+Here are some common pitfalls and how to overcome them:
+
+- **How can I verify my GitLab instance is using Elasticsearch?**
+
+ The easiest method is via the rails console (`sudo gitlab-rails console`) by running the following:
+
+ ```ruby
+ u = User.find_by_username('your-username')
+ s = SearchService.new(u, {:search => 'search_term'})
+ pp s.search_objects.class.name
+ ```
+
+ If you see `Elasticsearch::Model::Response::Records`, you are using Elasticsearch.
+
+- **I updated GitLab and now I can't find anything**
+
+ We continuously make updates to our indexing strategies and aim to support
+ newer versions of Elasticsearch. When indexing changes are made, it may
+ be necessary for you to [reindex](#adding-gitlabs-data-to-the-elasticsearch-index) after updating GitLab.
+
+- **I indexed all the repositories but I can't find anything**
+
+ Make sure you indexed all the database data [as stated above](#adding-gitlabs-data-to-the-elasticsearch-index).
+
+ Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side.
+
+ If it shows up via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html), check that it shows up via the rails console (`sudo gitlab-rails console`):
+
+ ```ruby
+ u = User.find_by_username('your-username')
+ s = SearchService.new(u, {:search => 'search_term', :scope => ‘blobs’})
+ pp s.search_objects.to_a
+ ```
+
+ See [Elasticsearch Index Scopes](elasticsearch.md#elasticsearch-index-scopes) for more information on searching for specific types of data.
+
+- **I indexed all the repositories but then switched elastic search servers and now I can't find anything**
+
+ You will need to re-run all the rake tasks to re-index the database, repositories, and wikis.
+
+- **The indexing process is taking a very long time**
+
+ The more data present in your GitLab instance, the longer the indexing process takes.
+
+- **No new data is added to the Elasticsearch index when I push code**
+
+ When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could
+ happen that an error during the process causes one or multiple projects to remain locked. In order to unlock them,
+ run the `gitlab:elastic:clear_locked_projects` rake task.
+
+- **"Can't specify parent if no parent field has been configured"**
+
+ If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes you will get
+ exception in lots of different cases:
+
+ ```text
+ Elasticsearch::Transport::Transport::Errors::BadRequest([400] {
+ "error": {
+ "root_cause": [{
+ "type": "illegal_argument_exception",
+ "reason": "Can't specify parent if no parent field has been configured"
+ }],
+ "type": "illegal_argument_exception",
+ "reason": "Can't specify parent if no parent field has been configured"
+ },
+ "status": 400
+ }):
+ ```
+
+ This is because we changed the index mapping in GitLab 8.12 and the old indexes should be removed and built from scratch again,
+ see details in the [8-11-to-8-12 update guide](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/8.11-to-8.12.md#11-elasticsearch-index-update-if-you-currently-use-elasticsearch).
+
+- Exception `Elasticsearch::Transport::Transport::Errors::BadRequest`
+
+ If you have this exception (just like in the case above but the actual message is different) please check if you have the correct Elasticsearch version and you met the other [requirements](#system-requirements).
+ There is also an easy way to check it automatically with `sudo gitlab-rake gitlab:check` command.
+
+- Exception `Elasticsearch::Transport::Transport::Errors::RequestEntityTooLarge`
+
+ ```text
+ [413] {"Message":"Request size exceeded 10485760 bytes"}
+ ```
+
+ This exception is seen when your Elasticsearch cluster is configured to reject
+ requests above a certain size (10MiB in this case). This corresponds to the
+ `http.max_content_length` setting in `elasticsearch.yml`. Increase it to a
+ larger size and restart your Elasticsearch cluster.
+
+ AWS has [fixed limits](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html)
+ for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of
+ the underlying instance.
+
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 075feaeead9..c3328e01081 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,14 +1,17 @@
# External issue tracker
-GitLab has a great issue tracker but you can also use an external one such as
-Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow
-you to do the following:
+GitLab has a great [issue tracker](../user/project/issues/index.md) but you can also use an external one
+such as Jira, Redmine, YouTrack, or Bugzilla. External issue trackers are configurable per GitLab project.
-- you can reference these external issues inside GitLab interface
- (merge requests, commits, comments) and they will be automatically converted
- into links
+Once configured, you can reference external issues using the format `CODE-123`, where:
-You can have enabled both external and internal GitLab issue trackers in parallel. The **Issues** link always opens the internal issue tracker and in case the internal issue tracker is disabled the link is not visible in the menu.
+- `CODE` is a unique code for the tracker.
+- `123` is the issue number in the tracker.
+
+These references in GitLab merge requests, commits, or comments are automatically converted to links to the issues.
+
+You can keep GitLab's issue tracker enabled in parallel or disable it. When enabled, the **Issues** link in the
+GitLab menu always opens the internal issue tracker. When disabled, the link is not visible in the menu.
## Configuration
@@ -20,6 +23,7 @@ To enable an external issue tracker you must configure the appropriate **Service
Visit the links below for details:
- [Redmine](../user/project/integrations/redmine.md)
+- [YouTrack](../user/project/integrations/youtrack.md)
- [Jira](../user/project/integrations/jira.md)
- [Bugzilla](../user/project/integrations/bugzilla.md)
- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
diff --git a/doc/integration/github.md b/doc/integration/github.md
index bee68688ace..5b01dd9feb7 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -19,12 +19,12 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
1. Provide the required details.
- Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- - Homepage URL: the URL to your GitLab installation. e.g., `https://gitlab.company.com`
+ - Homepage URL: The URL of your GitLab installation. For example, `https://gitlab.example.com`.
- Application description: Fill this in if you wish.
- - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth/github/callback`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
![Register OAuth App](img/github_register_app.png)
- NOTE: Be sure to append `/users/auth/github/callback` to the end of the callback URL
+ NOTE: Be sure to append `/users/auth` to the end of the callback URL
to prevent a [OAuth2 convert
redirect](http://tetraph.com/covert_redirect/) vulnerability.
diff --git a/doc/integration/img/github_app_entry.png b/doc/integration/img/github_app_entry.png
index 0a1fe0ca65a..c736bed3dd6 100644
--- a/doc/integration/img/github_app_entry.png
+++ b/doc/integration/img/github_app_entry.png
Binary files differ
diff --git a/doc/integration/img/github_register_app.png b/doc/integration/img/github_register_app.png
index 5786b822f53..d03ca5cc2aa 100644
--- a/doc/integration/img/github_register_app.png
+++ b/doc/integration/img/github_register_app.png
Binary files differ
diff --git a/doc/integration/img/google_app.png b/doc/integration/img/google_app.png
deleted file mode 100644
index 08f230452b4..00000000000
--- a/doc/integration/img/google_app.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/img/jenkins_gitlab_plugin_config.png b/doc/integration/img/jenkins_gitlab_plugin_config.png
new file mode 100644
index 00000000000..3e1f83e96dc
--- /dev/null
+++ b/doc/integration/img/jenkins_gitlab_plugin_config.png
Binary files differ
diff --git a/doc/integration/img/jenkins_gitlab_service.png b/doc/integration/img/jenkins_gitlab_service.png
new file mode 100644
index 00000000000..682a5ae8ee2
--- /dev/null
+++ b/doc/integration/img/jenkins_gitlab_service.png
Binary files differ
diff --git a/doc/integration/img/jenkins_gitlab_service_settings.png b/doc/integration/img/jenkins_gitlab_service_settings.png
new file mode 100644
index 00000000000..5a12e9cb39a
--- /dev/null
+++ b/doc/integration/img/jenkins_gitlab_service_settings.png
Binary files differ
diff --git a/doc/integration/img/jenkins_project.png b/doc/integration/img/jenkins_project.png
new file mode 100644
index 00000000000..126b05c8879
--- /dev/null
+++ b/doc/integration/img/jenkins_project.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_gl_setup_1.png b/doc/integration/img/jira_dev_panel_gl_setup_1.png
new file mode 100644
index 00000000000..75279877c93
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_gl_setup_1.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_jira_setup_1.png b/doc/integration/img/jira_dev_panel_jira_setup_1.png
new file mode 100644
index 00000000000..5c0f594cc1d
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_jira_setup_1.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_jira_setup_2.png b/doc/integration/img/jira_dev_panel_jira_setup_2.png
new file mode 100644
index 00000000000..a4778a00dd5
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_jira_setup_2.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_jira_setup_3.png b/doc/integration/img/jira_dev_panel_jira_setup_3.png
new file mode 100644
index 00000000000..4049a65f56b
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_jira_setup_3.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_jira_setup_4.png b/doc/integration/img/jira_dev_panel_jira_setup_4.png
new file mode 100644
index 00000000000..81d84cb173d
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_jira_setup_4.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_jira_setup_5.png b/doc/integration/img/jira_dev_panel_jira_setup_5.png
new file mode 100644
index 00000000000..73dc867d301
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_jira_setup_5.png
Binary files differ
diff --git a/doc/integration/img/jira_dev_panel_manual_refresh.png b/doc/integration/img/jira_dev_panel_manual_refresh.png
new file mode 100644
index 00000000000..dc92d533bde
--- /dev/null
+++ b/doc/integration/img/jira_dev_panel_manual_refresh.png
Binary files differ
diff --git a/doc/integration/img/limit_namespace_filter.png b/doc/integration/img/limit_namespace_filter.png
new file mode 100644
index 00000000000..88f5caa41db
--- /dev/null
+++ b/doc/integration/img/limit_namespace_filter.png
Binary files differ
diff --git a/doc/integration/img/limit_namespaces_projects_options.png b/doc/integration/img/limit_namespaces_projects_options.png
new file mode 100644
index 00000000000..488341f7e92
--- /dev/null
+++ b/doc/integration/img/limit_namespaces_projects_options.png
Binary files differ
diff --git a/doc/integration/img/salesforce_app_details.png b/doc/integration/img/salesforce_app_details.png
new file mode 100644
index 00000000000..00e66f07282
--- /dev/null
+++ b/doc/integration/img/salesforce_app_details.png
Binary files differ
diff --git a/doc/integration/img/salesforce_app_secret_details.png b/doc/integration/img/salesforce_app_secret_details.png
new file mode 100644
index 00000000000..fad2a4a1f97
--- /dev/null
+++ b/doc/integration/img/salesforce_app_secret_details.png
Binary files differ
diff --git a/doc/integration/img/salesforce_oauth_app_details.png b/doc/integration/img/salesforce_oauth_app_details.png
new file mode 100644
index 00000000000..a5fb680cca6
--- /dev/null
+++ b/doc/integration/img/salesforce_oauth_app_details.png
Binary files differ
diff --git a/doc/integration/img/ultra_auth_credentials.png b/doc/integration/img/ultra_auth_credentials.png
new file mode 100644
index 00000000000..cff98a4b056
--- /dev/null
+++ b/doc/integration/img/ultra_auth_credentials.png
Binary files differ
diff --git a/doc/integration/img/ultra_auth_edit_callback_url.png b/doc/integration/img/ultra_auth_edit_callback_url.png
new file mode 100644
index 00000000000..b7548122c5e
--- /dev/null
+++ b/doc/integration/img/ultra_auth_edit_callback_url.png
Binary files differ
diff --git a/doc/integration/img/ultra_auth_edit_callback_url_highlighted.png b/doc/integration/img/ultra_auth_edit_callback_url_highlighted.png
new file mode 100644
index 00000000000..4abf224756c
--- /dev/null
+++ b/doc/integration/img/ultra_auth_edit_callback_url_highlighted.png
Binary files differ
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
new file mode 100644
index 00000000000..f60333aa07c
--- /dev/null
+++ b/doc/integration/jenkins.md
@@ -0,0 +1,129 @@
+# Jenkins CI service **[STARTER]**
+
+>**Note:**
+In GitLab 8.3, Jenkins integration using the
+[GitLab Hook Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
+was deprecated in favor of the
+[GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
+The deprecated integration has been renamed to [Jenkins CI (Deprecated)](jenkins_deprecated.md) in the
+project service settings. We may remove this in a future release and recommend
+using the new 'Jenkins CI' project service instead which is described in this
+document.
+
+## Overview
+
+[Jenkins](https://jenkins.io/) is a great Continuous Integration tool, similar to our built-in
+[GitLab CI](../ci/README.md).
+
+GitLab's Jenkins integration allows you to trigger a Jenkins build when you
+push code to a repository, or when a merge request is created. Additionally,
+it shows the pipeline status on merge requests widgets and on the project's home page.
+
+## Use cases
+
+- Suppose you are new to GitLab, and want to keep using Jenkins until you prepare
+your projects to build with [GitLab CI/CD](../ci/README.md). You set up the
+integration between GitLab and Jenkins, then you migrate to GitLab CI later. While
+you organize yourself and your team to onboard GitLab, you keep your pipelines
+running with Jenkins, but view the results in your project's repository in GitLab.
+- Your team uses [Jenkins Plugins](https://plugins.jenkins.io/) for other proceedings,
+therefore, you opt for keep using Jenkins to build your apps. Show the results of your
+pipelines directly in GitLab.
+
+## Requirements
+
+* [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
+* [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin)
+* Git clone access for Jenkins from the GitLab repository
+* GitLab API access to report build status
+
+## Configure GitLab users
+
+Create a user or choose an existing user that Jenkins will use to interact
+through the GitLab API. This user will need to be a global Admin or added
+as a member to each Group/Project. Developer permission is required for reporting
+build status. This is because a successful build status can trigger a merge
+when 'Merge when pipeline succeeds' feature is used. Some features of the GitLab
+Plugin may require additional privileges. For example, there is an option to
+accept a merge request if the build is successful. Using this feature would
+require developer, maintainer or owner-level permission.
+
+Copy the private API token from **Profile Settings -> Account**. You will need this
+when configuring the Jenkins server later.
+
+## Configure the Jenkins server
+
+Install [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
+and [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin).
+
+Go to Manage Jenkins -> Configure System and scroll down to the 'GitLab' section.
+Enter the GitLab server URL in the 'GitLab host URL' field and paste the API token
+copied earlier in the 'API Token' field.
+
+For more information, see GitLab Plugin documentation about
+[Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication)
+
+![Jenkins GitLab plugin configuration](img/jenkins_gitlab_plugin_config.png)
+
+## Configure a Jenkins project
+
+Follow the GitLab Plugin documentation about [Jenkins Job Configuration](https://github.com/jenkinsci/gitlab-plugin#jenkins-job-configuration).
+
+NOTE: **Note:**
+Be sure to include the steps about [Build status configuration](https://github.com/jenkinsci/gitlab-plugin#build-status-configuration).
+The 'Publish build status to GitLab' post-build step is required to view
+Jenkins build status in GitLab Merge Requests.
+
+## Configure a GitLab project
+
+Create a new GitLab project or choose an existing one. Then, go to **Integrations ->
+Jenkins CI**.
+
+Check the 'Active' box. Select whether you want GitLab to trigger a build
+on push, Merge Request creation, tag push, or any combination of these. We
+recommend unchecking 'Merge Request events' unless you have a specific use-case
+that requires re-building a commit when a merge request is created. With 'Push
+events' selected, GitLab will build the latest commit on each push and the build
+status will be displayed in the merge request.
+
+Enter the Jenkins URL and Project name. The project name should be URL-friendly
+where spaces are replaced with underscores. To be safe, copy the project name
+from the URL bar of your browser while viewing the Jenkins project.
+
+Optionally, enter a username and password if your Jenkins server requires
+authentication.
+
+![GitLab service settings](img/jenkins_gitlab_service_settings.png)
+
+## Plugin functional overview
+
+GitLab does not contain a database table listing commits. Commits are always
+read from the repository directly. Therefore, it is not possible to retain the
+build status of a commit in GitLab. This is overcome by requesting build
+information from the integrated CI tool. The CI tool is responsible for creating
+and storing build status for Commits and Merge Requests.
+
+### Steps required to implement a similar integration
+
+>**Note:**
+All steps are implemented using AJAX requests on the merge request page.
+
+1. In order to display the build status in a merge request you must create a project service in GitLab.
+2. Your project service will do a (JSON) query to a URL of the CI tool with the SHA1 of the commit.
+3. The project service builds this URL and payload based on project service settings and knowledge of the CI tool.
+4. The response is parsed to give a response in GitLab (success/failed/pending).
+
+## Troubleshooting
+
+### Error in merge requests - "Could not connect to the CI server"
+
+This integration relies on Jenkins reporting the build status back to GitLab via
+the [Commit Status API](../api/commits.md#commit-status).
+
+The error 'Could not connect to the CI server' usually means that GitLab did not
+receive a build status update via the API. Either Jenkins was not properly
+configured or there was an error reporting the status via the API.
+
+1. [Configure the Jenkins server](#configure-the-jenkins-server) for GitLab API access
+2. [Configure a Jenkins project](#configure-a-jenkins-project), including the
+ 'Publish build status to GitLab' post-build action.
diff --git a/doc/integration/jenkins_deprecated.md b/doc/integration/jenkins_deprecated.md
new file mode 100644
index 00000000000..8001c5dbd83
--- /dev/null
+++ b/doc/integration/jenkins_deprecated.md
@@ -0,0 +1,53 @@
+# Jenkins CI (deprecated) service
+
+>**Note:** In GitLab 8.3, Jenkins integration using the
+[GitLab Hook Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
+was deprecated in favor of the
+[GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
+Please use documentation for the new [Jenkins CI service](jenkins.md).
+
+Integration includes:
+
+* Trigger Jenkins build after push to repo
+* Show build status on Merge Request page
+
+Requirements:
+
+* [Jenkins GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
+* git clone access for Jenkins from GitLab repo (via ssh key)
+
+## Jenkins
+
+1. Install [GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
+2. Set up jenkins project
+
+![screen](img/jenkins_project.png)
+
+## GitLab
+
+In GitLab, perform the following steps.
+
+### Read access to repository
+
+Jenkins needs read access to the GitLab repository. We already specified a
+private key to use in Jenkins, now we need to add a public one to the GitLab
+project. For that case we will need a Deploy key. Read the documentation on
+[how to set up a Deploy key](../ssh/README.md#deploy-keys).
+
+### Jenkins service
+
+Now navigate to GitLab services page and activate Jenkins
+
+![screen](img/jenkins_gitlab_service.png)
+
+Done! Now when you push to GitLab - it will create a build for Jenkins.
+And also you will be able to see merge request build status with a link to the Jenkins build.
+
+### Multi-project Configuration
+
+The GitLab Hook plugin in Jenkins supports the automatic creation of a project
+for each feature branch. After configuration GitLab will trigger feature branch
+builds and a corresponding project will be created in Jenkins.
+
+Configure the GitLab Hook plugin in Jenkins. Go to 'Manage Jenkins' and then
+'Configure System'. Find the 'GitLab Web Hook' section and configure as shown below.
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index b6923f74e28..c09fde08326 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/jira.md'
+---
+
This document was moved to [integrations/jira](../user/project/integrations/jira.md).
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
new file mode 100644
index 00000000000..703736eeb3c
--- /dev/null
+++ b/doc/integration/jira_development_panel.md
@@ -0,0 +1,141 @@
+# GitLab Jira development panel integration **[PREMIUM]**
+
+> [Introduced][ee-2381] in [GitLab Premium][eep] 10.0.
+
+Complementary to our [existing Jira][existing-jira] project integration, you're now able to integrate
+GitLab projects with [Jira Development Panel][jira-development-panel]. Both can be used
+simultaneously. This works with self-hosted GitLab or GitLab.com integrated with self-hosted Jira
+or cloud Jira.
+
+By doing this you can easily access related GitLab merge requests, branches, and commits directly from a Jira issue.
+
+This integration connects all GitLab projects within a top-level group or a personal namespace to projects in the Jira instance.
+A top-level GitLab group is one that does not have any parent group itself. All the projects of that top-level group,
+as well as projects of the top-level group's subgroups nesting down, are connected. Alternatively, you can specify
+a GitLab personal namespace in the Jira configuration, which will then connect the projects in that personal namespace to Jira.
+
+NOTE: **Note**:
+Note this is different from the [existing Jira][existing-jira] project integration, where the mapping
+is one GitLab project to the entire Jira instance.
+
+We recommend that a GitLab group admin
+or instance admin (in the case of self-hosted GitLab) set up the integration,
+in order to simplify administration.
+
+TIP: **Tip:**
+Create and use a single-purpose "jira" user in GitLab, so that removing
+regular users won't impact your integration.
+
+## Requirements
+
+### Self-hosted GitLab
+
+If you are using self-hosted GitLab, make sure your GitLab instance is accessible by Jira.
+
+- If you are connecting to Jira Cloud, make sure your instance is accessible via the internet.
+- If you are using Jira Server, make sure your instance is accessible however your network is set up.
+
+### GitLab.com
+
+There are no special requirements if you are using GitLab.com.
+
+## GitLab Configuration
+
+1. In GitLab, create a new application in order to allow Jira to connect with your GitLab account
+
+ While logged-in, go to `Settings -> Applications`. (Click your profile avatar at
+ the top right, choose `Settings`, and then navigate to `Applications` from the left
+ navigation menu.) Use the form to create a new application.
+
+ Enter a useful name for the `Name` field.
+
+ For the `Redirect URI` field, enter `https://<your-gitlab-instance-domain>/login/oauth/callback`,
+ replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
+ this would be `https://gitlab.com/login/oauth/callback`.
+
+ NOTE: **Note**:
+ If using a GitLab version earlier than 11.3 the `Redirect URI` value should be `https://<your-gitlab-instance-domain>/-/jira/login/oauth/callback`.
+
+ ![GitLab Application setup](img/jira_dev_panel_gl_setup_1.png)
+ - Check `api` in the Scopes section.
+
+2. Click `Save application`. You will see the generated 'Application Id' and 'Secret' values.
+ Copy these values that you will use on the Jira configuration side.
+
+## Jira Configuration
+
+1. In Jira, from the gear menu at the top right, go to `Applications`. Navigate to `DVCS accounts`
+ from the left navigation menu. Click `Link GitHub account` to start creating a new integration.
+ (We are pretending to be GitHub in this integration until there is further platform support from Jira.)
+
+ ![Jira DVCS from Dashboard](img/jira_dev_panel_jira_setup_1.png)
+
+2. Complete the form
+
+ Select GitHub Enterprise for the `Host` field.
+
+ For the `Team or User Account` field, enter the relative path of a top-level GitLab group that you have access to,
+ or the relative path of your personal namespace.
+
+ ![Creation of Jira DVCS integration](img/jira_dev_panel_jira_setup_2.png)
+
+ For the `Host URL` field, enter `https://<your-gitlab-instance-domain>/`,
+ replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
+ this would be `https://gitlab.com/`.
+
+ NOTE: **Note**:
+ If using a GitLab version earlier than 11.3 the `Host URL` value should be `https://<your-gitlab-instance-domain>/-/jira`
+
+ For the `Client ID` field, use the `Application ID` value from the previous section.
+
+ For the `Client Secret` field, use the `Secret` value from the previous section.
+
+ Ensure that the rest of the checkboxes are checked.
+
+3. Click `Add` to complete and create the integration.
+
+ Jira takes up to a few minutes to know about (import behind the scenes) all the commits and branches
+ for all the projects in the GitLab group you specified in the previous step. These are refreshed
+ every 60 minutes.
+
+ > **Note:**
+ > In the future, we plan on implementating real-time integration. If you need
+ > to refresh the data manually, you can do this from the `Applications -> DVCS
+ > accounts` screen where you initially set up the integration:
+ >
+ > ![Refresh GitLab information in Jira](img/jira_dev_panel_manual_refresh.png)
+
+To connect additional GitLab projects from other GitLab top-level groups (or personal namespaces), repeat the above
+steps with additional Jira DVCS accounts.
+
+You may now refer any Jira issue by its ID in branch names, commit messages and merge request names on GitLab's side,
+and you will be able to see the linked `branches`, `commits`, and `merge requests` when entering a Jira issue
+(inside the Jira issue, merge requests will be called "pull requests").
+
+![Branch, Commit and Pull Requests links on Jira issue](img/jira_dev_panel_jira_setup_3.png)
+
+Click the links to see your GitLab repository data.
+
+![GitLab commits details on a Jira issue](img/jira_dev_panel_jira_setup_4.png)
+
+![GitLab merge requests details on a Jira issue](img/jira_dev_panel_jira_setup_5.png)
+
+## Limitations
+
+- This integration is currently not supported on GitLab instances under a [relative url][relative-url] (e.g. `http://example.com/gitlab`).
+
+## Changelog
+
+### 11.10
+
+- [Instance admins can now setup integration for all namespaces](https://gitlab.com/gitlab-org/gitlab-ee/issues/8902)
+
+### 11.1
+
+- [Support GitLab subgroups in Jira development panel](https://gitlab.com/gitlab-org/gitlab-ee/issues/3561)
+
+[existing-jira]: ../user/project/integrations/jira.md
+[jira-development-panel]: https://confluence.atlassian.com/adminjiraserver070/integrating-with-development-tools-776637096.html#Integratingwithdevelopmenttools-Developmentpanelonissues
+[eep]: https://about.gitlab.com/pricing/
+[ee-2381]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2381
+[relative-url]: https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
new file mode 100644
index 00000000000..44117755b83
--- /dev/null
+++ b/doc/integration/kerberos.md
@@ -0,0 +1,315 @@
+# Kerberos integration **[STARTER ONLY]**
+
+GitLab can integrate with [Kerberos][kerb] as an authentication mechanism.
+
+## Overview
+
+[Kerberos][kerb] is a secure method for authenticating a request for a service in a
+computer network. Kerberos was developed in the Athena Project at the
+[Massachusetts Institute of Technology (MIT)][mit]. The name is taken from Greek
+mythology; Kerberos was a three-headed dog who guarded the gates of Hades.
+
+## Use-cases
+
+- GitLab can be configured to allow your users to sign with their Kerberos credentials.
+- You can use Kerberos to [prevent][why-kerb] anyone from intercepting or eavesdropping on the transmitted password.
+
+## Configuration
+
+For GitLab to offer Kerberos token-based authentication, perform the
+following prerequisites. You still need to configure your system for
+Kerberos usage, such as specifying realms. GitLab will make use of the
+system's Kerberos settings.
+
+### GitLab keytab
+
+1. Create a Kerberos Service Principal for the HTTP service on your GitLab server.
+ If your GitLab server is `gitlab.example.com` and your Kerberos realm
+ `EXAMPLE.COM`, create a Service Principal `HTTP/gitlab.example.com@EXAMPLE.COM`
+ in your Kerberos database.
+1. Create a keytab on the GitLab server for the above Service Principal, e.g.
+ `/etc/http.keytab`.
+
+The keytab is a sensitive file and must be readable by the GitLab user. Set
+ownership and protect the file appropriately:
+
+```
+sudo chown git /etc/http.keytab
+sudo chmod 0600 /etc/http.keytab
+```
+
+### Configure GitLab
+
+**Installations from source**
+
+>**Note:**
+For source installations, make sure the `kerberos` gem group
+[has been installed](../install/installation.md#install-gems).
+
+1. Edit the kerberos section of [gitlab.yml] to enable Kerberos ticket-based
+ authentication. In most cases, you only need to enable Kerberos and specify
+ the location of the keytab:
+
+ ```yaml
+ omniauth:
+ enabled: true
+ allow_single_sign_on: ['kerberos']
+
+ kerberos:
+ # Allow the HTTP Negotiate authentication method for Git clients
+ enabled: true
+
+ # Kerberos 5 keytab file. The keytab file must be readable by the GitLab user,
+ # and should be different from other keytabs in the system.
+ # (default: use default keytab from Krb5 config)
+ keytab: /etc/http.keytab
+ ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+---
+
+**Omnibus package installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['kerberos']
+
+ gitlab_rails['kerberos_enabled'] = true
+ gitlab_rails['kerberos_keytab'] = "/etc/http.keytab"
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+---
+
+GitLab will now offer the `negotiate` authentication method for signing in and
+HTTP Git access, enabling Git clients that support this authentication protocol
+to authenticate with Kerberos tokens.
+
+## Creating and linking Kerberos accounts
+
+The Administrative user can navigate to **Admin > Users > Example User > Identities**
+and attach a Kerberos account. Existing GitLab users can go to **Profile > Account**
+and attach a Kerberos account. If you want to allow users without a GitLab
+account to login, you should enable the option `allow_single_sign_on` as
+described in the [Configure GitLab](#configure-gitlab) section. Then, the first
+time a user signs in with Kerberos credentials, GitLab will create a new GitLab
+user associated with the email, which is built from the Kerberos username and
+realm. User accounts will be created automatically when authentication was
+successful.
+
+## Linking Kerberos and LDAP accounts together
+
+If your users log in with Kerberos, but you also have [LDAP integration](../administration/auth/ldap.md)
+enabled, then your users will be automatically linked to their LDAP accounts on
+first login. For this to work, some prerequisites must be met:
+
+The Kerberos username must match the LDAP user's UID. You can choose which LDAP
+attribute is used as the UID in GitLab's [LDAP configuration](../administration/auth/ldap.md#configuration)
+but for Active Directory, this should be `sAMAccountName`.
+
+The Kerberos realm must match the domain part of the LDAP user's Distinguished
+Name. For instance, if the Kerberos realm is `AD.EXAMPLE.COM`, then the LDAP
+user's Distinguished Name should end in `dc=ad,dc=example,dc=com`.
+
+Taken together, these rules mean that linking will only work if your users'
+Kerberos usernames are of the form `foo@AD.EXAMPLE.COM` and their
+LDAP Distinguished Names look like `sAMAccountName=foo,dc=ad,dc=example,dc=com`.
+
+## HTTP Git access
+
+A linked Kerberos account enables you to `git pull` and `git push` using your
+Kerberos account, as well as your standard GitLab credentials.
+
+GitLab users with a linked Kerberos account can also `git pull` and `git push`
+using Kerberos tokens, i.e., without having to send their password with each
+operation.
+
+### HTTP Git access with Kerberos token (passwordless authentication)
+
+#### Support for Git before 2.4
+
+Until Git version 2.4, the `git` command uses only the `negotiate` authentication
+method if the HTTP server offers it, even if this method fails (such as when
+the client does not have a Kerberos token). It is thus not possible to fall back
+to username/password (also known as `basic`) authentication if Kerberos
+authentication fails.
+
+For GitLab users to be able to use either `basic` or `negotiate` authentication
+with older Git versions, it is possible to offer Kerberos ticket-based
+authentication on a different port (e.g. 8443) while the standard port will
+keep offering only `basic` authentication.
+
+**For source installations with HTTPS**
+
+1. Edit the NGINX configuration file for GitLab
+ (e.g., `/etc/nginx/sites-available/gitlab-ssl`) and configure NGINX to
+ listen to port `8443` in addition to the standard HTTPS port:
+
+ ```conf
+ server {
+ listen 0.0.0.0:443 ssl;
+ listen [::]:443 ipv6only=on ssl default_server;
+ listen 0.0.0.0:8443 ssl;
+ listen [::]:8443 ipv6only=on ssl;
+ ```
+
+1. Update the Kerberos section of [gitlab.yml]:
+
+ ```yaml
+ kerberos:
+ # Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
+ # To support both Basic and Negotiate methods with older versions of Git, configure
+ # nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
+ # to dedicate this port to Kerberos authentication. (default: false)
+ use_dedicated_port: true
+ port: 8443
+ https: true
+ ```
+
+1. [Restart GitLab] and NGINX for the changes to take effect.
+
+---
+
+**For Omnibus package installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['kerberos_use_dedicated_port'] = true
+ gitlab_rails['kerberos_port'] = 8443
+ gitlab_rails['kerberos_https'] = true
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+---
+
+After this change, all Git remote URLs will have to be updated to
+`https://gitlab.example.com:8443/mygroup/myproject.git` in order to use
+Kerberos ticket-based authentication.
+
+## Upgrading from password-based to ticket-based Kerberos sign-ins
+
+Prior to GitLab 8.10 Enterprise Edition, users had to submit their
+Kerberos username and password to GitLab when signing in. We will
+remove support for password-based Kerberos sign-ins in a future
+release, so we recommend that you upgrade to ticket-based sign-ins.
+
+Depending on your existing GitLab configuration, the 'Sign in with:
+Kerberos Spnego' button may already be visible on your GitLab sign-in
+page. If not, then add the settings [described above](#configuration).
+
+Once you have verified that the 'Kerberos Spnego' button works
+without entering any passwords, you can proceed to disable
+password-based Kerberos sign-ins. To do this you need only need to
+remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
+`gitlab.rb` file.
+
+**For installations from source**
+
+1. Edit [gitlab.yml] and remove the `- { name: 'kerberos' }` line under omniauth
+ providers:
+
+ ```yaml
+ omniauth:
+ # ...
+ providers:
+ - { name: 'kerberos' } # <-- remove this line
+ ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+---
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and remove the `{ "name" => "kerberos" }` line
+ under `gitlab_rails['omniauth_providers']`:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ { "name" => "kerberos" } # <-- remove this entry
+ ]
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect.
+
+## Support for Active Directory Kerberos environments
+
+When using Kerberos ticket-based authentication in an Active Directory domain,
+it may be necessary to increase the maximum header size allowed by NGINX,
+as extensions to the Kerberos protocol may result in HTTP authentication headers
+larger than the default size of 8kB. Configure `large_client_header_buffers`
+to a larger value in [the NGINX configuration][nginx].
+
+## Troubleshooting
+
+### Unsupported GSSAPI mechanism
+
+With Kerberos SPNEGO authentication, the browser is expected to send a list of
+mechanisms it supports to GitLab. If it doesn't support any of the mechanisms
+GitLab supports, authentication will fail with a message like this in the log:
+
+```
+OmniauthKerberosSpnegoController: failed to process Negotiate/Kerberos authentication: gss_accept_sec_context did not return GSS_S_COMPLETE: An unsupported mechanism was requested Unknown error
+```
+
+This is usually seen when the browser is unable to contact the Kerberos server
+directly. It will fall back to an unsupported mechanism known as
+[`IAKERB`](https://k5wiki.kerberos.org/wiki/Projects/IAKERB), which tries to use
+the GitLab server as an intermediary to the Kerberos server.
+
+If you're experiencing this error, ensure there is connectivity between the
+client machine and the Kerberos server - this is a prerequisite! Traffic may be
+blocked by a firewall, or the DNS records may be incorrect.
+
+Another failure mode occurs when the forward and reverse DNS records for the
+GitLab server do not match. Often, Windows clients will work in this case, while
+Linux clients will fail. They use reverse DNS while detecting the Kerberos
+realm. If they get the wrong realm, then ordinary Kerberos mechanisms will fail,
+so the client will fall back to attempting to negotiate `IAKERB`, leading to the
+above error message.
+
+To fix this, ensure that the forward and reverse DNS for your GitLab server
+match. So for instance, if you acces GitLab as `gitlab.example.com`, resolving
+to IP address `1.2.3.4`, then `4.3.2.1.in-addr.arpa` must be a PTR record for
+`gitlab.example.com`.
+
+Finally, it's possible that the browser or client machine lack Kerberos support
+completely. Ensure that the Kerberos libraries are installed and that you can
+authenticate to other Kerberos services.
+
+### HTTP Basic: Access denied when cloning
+
+```sh
+remote: HTTP Basic: Access denied
+fatal: Authentication failed for '<KRB5 path>'
+```
+
+If you are using Git v2.11 or newer and see the above error when cloning, you can
+set the `http.emptyAuth` Git option to `true` to fix this:
+
+```
+git config --global http.emptyAuth true
+```
+
+See also: [Git v2.11 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.11.0.txt#L482-L486)
+
+## Helpful links
+
+- <https://help.ubuntu.com/community/Kerberos>
+- <http://blog.manula.org/2012/04/setting-up-kerberos-server-with-debian.html>
+- <http://www.roguelynn.com/words/explain-like-im-5-kerberos/>
+
+[gitlab.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
+[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source
+[reconfigure gitlab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[nginx]: http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers
+[kerb]: https://web.mit.edu/kerberos/
+[mit]: http://web.mit.edu/
+[why-kerb]: http://web.mit.edu/sipb/doc/working/guide/guide/node20.html
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 242890af981..76c124d2ce9 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/auth/ldap.md'
+---
+
This document was moved to [`administration/auth/ldap`](../administration/auth/ldap.md).
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 4db986197f3..a13e9f73f48 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -33,6 +33,9 @@ contains some settings that are common for all providers.
- [Authentiq](../administration/auth/authentiq.md)
- [OAuth2Generic](oauth2_generic.md)
- [JWT](../administration/auth/jwt.md)
+- [OpenID Connect](../administration/auth/oidc.md)
+- [UltraAuth](ultra_auth.md)
+- [SalesForce](salesforce.md)
## Initial OmniAuth Configuration
@@ -199,7 +202,7 @@ from the Omniauth provider's documentation.
sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
- > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
+ > These are the same commands you used during initial installation in the [Install Gems section](../install/installation.md#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
- Start GitLab:
@@ -256,7 +259,7 @@ gitlab_rails['omniauth_enabled'] = false
You can enable profile syncing from selected OmniAuth providers and for all or for specific user information.
-When authenticating using LDAP, the user's email is always synced.
+When authenticating using LDAP, the user's name and email are always synced.
```ruby
gitlab_rails['sync_profile_from_provider'] = ['twitter', 'google_oauth2']
diff --git a/doc/integration/salesforce.md b/doc/integration/salesforce.md
new file mode 100644
index 00000000000..1ef43cfcece
--- /dev/null
+++ b/doc/integration/salesforce.md
@@ -0,0 +1,79 @@
+# Salesforce OmniAuth Provider
+
+You can integrate your GitLab instance with [Salesforce](https://www.salesforce.com/) to enable users to log in to your GitLab instance with their Salesforce account.
+
+## Create a Salesforce Connected App
+
+To enable Salesforce OmniAuth provider, you must use Salesforce's credentials for your GitLab instance.
+To get the credentials (a pair of Client ID and Client Secret), you must [create a Connected App](https://help.salesforce.com/articleView?id=connected_app_create.htm&type=5) on Salesforce.
+
+1. Sign in to [Salesforce](https://login.salesforce.com/).
+
+1. In Setup, enter `App Manager` in the Quick Find box, click **App Manager**, then click **New Connected App**.
+
+1. Fill in the application details into the following fields:
+ - **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
+ - **Contact Email**: Enter the contact email for Salesforce to use when contacting you or your support team.
+ - **Description**: Description for the application.
+
+ ![Salesforce App Details](img/salesforce_app_details.png)
+1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**.
+1. Fill in the application details into the following fields:
+ - **Callback URL**: The callback URL of your GitLab installation. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
+ - **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column.
+
+ ![Salesforce Oauth App Details](img/salesforce_oauth_app_details.png)
+1. Click **Save**.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "salesforce",
+ "app_id" => "SALESFORCE_CLIENT_ID",
+ "app_secret" => "SALESFORCE_CLIENT_SECRET"
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'salesforce',
+ app_id: 'SALESFORCE_CLIENT_ID',
+ app_secret: 'SALESFORCE_CLIENT_SECRET'
+ }
+ ```
+1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the Salesforce connected application page.
+1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the Salesforce connected application page.
+ ![Salesforce App Secret Details](img/salesforce_app_secret_details.png)
+
+1. Save the configuration file.
+1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+
+On the sign in page, there should now be a Salesforce icon below the regular sign in form.
+Click the icon to begin the authentication process. Salesforce will ask the user to sign in and authorize the GitLab application.
+If everything goes well, the user will be returned to GitLab and will be signed in.
+
+NOTE: **Note:**
+GitLab requires the email address of each new user. Once the user is logged in using Salesforce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email. \ No newline at end of file
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 8ee07a7fcdc..22e07594d6f 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -1,5 +1,7 @@
# SAML OmniAuth Provider
+> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](../user/group/saml_sso/index.md).
+
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
@@ -10,115 +12,115 @@ Microsoft ADFS to authenticate users.
First configure SAML 2.0 support in GitLab, then register the GitLab application
in your SAML IdP:
-1. Make sure GitLab is configured with HTTPS.
- See [Using HTTPS](../install/installation.md#using-https) for instructions.
-
-1. On your GitLab server, open the configuration file.
+1. Make sure GitLab is configured with HTTPS.
+ See [Using HTTPS](../install/installation.md#using-https) for instructions.
+
+1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
-1. To allow your users to use SAML to sign up without having to manually create
- an account first, don't forget to add the following values to your configuration:
+1. To allow your users to use SAML to sign up without having to manually create
+ an account first, don't forget to add the following values to your configuration:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_enabled'] = true
- gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
- gitlab_rails['omniauth_block_auto_created_users'] = false
- ```
+ ```ruby
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
+ gitlab_rails['omniauth_block_auto_created_users'] = false
+ ```
- For installations from source:
+ For installations from source:
- ```yaml
- omniauth:
- enabled: true
- allow_single_sign_on: ["saml"]
- block_auto_created_users: false
- ```
+ ```yaml
+ omniauth:
+ enabled: true
+ allow_single_sign_on: ["saml"]
+ block_auto_created_users: false
+ ```
-1. You can also automatically link SAML users with existing GitLab users if their
- email addresses match by adding the following setting:
+1. You can also automatically link SAML users with existing GitLab users if their
+ email addresses match by adding the following setting:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_auto_link_saml_user'] = true
- ```
+ ```ruby
+ gitlab_rails['omniauth_auto_link_saml_user'] = true
+ ```
- For installations from source:
+ For installations from source:
- ```yaml
- auto_link_saml_user: true
- ```
+ ```yaml
+ auto_link_saml_user: true
+ ```
-1. Add the provider configuration:
+1. Add the provider configuration:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://login.example.com/idp',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
- },
- label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ]
+ ```
- For installations from source:
-
- ```yaml
- omniauth:
- providers:
- - {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://login.example.com/idp',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
- },
- label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
- }
- ```
+ For installations from source:
-1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
- of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
- installation to generate the correct value).
+ ```yaml
+ omniauth:
+ providers:
+ - {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ```
-1. Change the values of `idp_cert_fingerprint`, `idp_sso_target_url`,
- `name_identifier_format` to match your IdP. If a fingerprint is used it must
- be a SHA1 fingerprint; check
- [the omniauth-saml documentation](https://github.com/omniauth/omniauth-saml)
- for more details on these options.
+1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
+ of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
+ installation to generate the correct value).
-1. Change the value of `issuer` to a unique name, which will identify the application
- to the IdP.
+1. Change the values of `idp_cert_fingerprint`, `idp_sso_target_url`,
+ `name_identifier_format` to match your IdP. If a fingerprint is used it must
+ be a SHA1 fingerprint; check
+ [the omniauth-saml documentation](https://github.com/omniauth/omniauth-saml)
+ for more details on these options.
-1. For the changes to take effect, you must [reconfigure][] GitLab if you installed via Omnibus or [restart GitLab][] if you installed from source.
+1. Change the value of `issuer` to a unique name, which will identify the application
+ to the IdP.
-1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
- in `issuer`.
+1. For the changes to take effect, you must [reconfigure][] GitLab if you installed via Omnibus or [restart GitLab][] if you installed from source.
+
+1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
+ in `issuer`.
To ease configuration, most IdP accept a metadata URL for the application to provide
configuration information to the IdP. To build the metadata URL for GitLab, append
@@ -185,6 +187,78 @@ tell GitLab which groups are external via the `external_groups:` element:
} }
```
+## Required groups
+
+>**Note:**
+This setting is only available on GitLab 10.2 EE and above.
+
+This setting works like `External Groups` setting. Just like there, your IdP has to
+pass Group Information to GitLab, you have to tell GitLab where to look or the
+groups SAML response, and which group membership should be requisite for logging in.
+When `required_groups` is not set or it is empty, anyone with proper authentication
+will be able to use the service.
+
+Example:
+
+```yaml
+{ name: 'saml',
+ label: 'Our SAML Provider',
+ groups_attribute: 'Groups',
+ required_groups: ['Developers', 'Managers', 'Admins'],
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ } }
+```
+
+## Admin Groups
+
+>**Note:**
+This setting is only available on GitLab 8.8 EE and above.
+
+This setting works very similarly to the `External Groups` setting. The requirements
+are the same, your IdP needs to pass Group information to GitLab, you need to tell
+GitLab where to look for the groups in the SAML response, and which group should be
+considered `admin groups`.
+
+```yaml
+{ name: 'saml',
+ label: 'Our SAML Provider',
+ groups_attribute: 'Groups',
+ admin_groups: ['Managers', 'Admins'],
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ } }
+```
+
+## Auditor Groups
+
+>**Note:**
+This setting is only available on GitLab 11.4 EE and above.
+
+This setting also follows the requirements documented for the `External Groups` setting. GitLab uses the Group information provided by your IdP to determine if a user should be assigned the `auditor` role.
+
+```yaml
+{ name: 'saml',
+ label: 'Our SAML Provider',
+ groups_attribute: 'Groups',
+ auditor_groups: ['Auditors', 'Security'],
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ } }
+```
+
## Bypass two factor authentication
If you want some SAML authentication methods to count as 2FA on a per session basis, you can register them in the
@@ -194,28 +268,28 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://login.example.com/idp',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
- upstream_two_factor_authn_contexts:
- %w(
- urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
- urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
- urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
- )
-
- },
- label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ upstream_two_factor_authn_contexts:
+ %w(
+ urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
+ )
+
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ]
+ ```
1. Save the file and [reconfigure][] GitLab for the changes to take effect.
@@ -225,27 +299,27 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
1. Edit `config/gitlab.yml`:
- ```yaml
- omniauth:
- providers:
- - {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://login.example.com/idp',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
- upstream_two_factor_authn_contexts:
- [
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
- ]
- },
- label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
- }
- ```
+ ```yaml
+ omniauth:
+ providers:
+ - {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ upstream_two_factor_authn_contexts:
+ [
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
+ ]
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ```
1. Save the file and [restart GitLab][] for the changes ot take effect
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 8cd151fbf95..f84ab769218 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -1 +1,5 @@
+---
+redirect_to: '../project_services/slack.md'
+---
+
This document was moved to [project_services/slack.md](../project_services/slack.md).
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index cd755089be8..71ea2e25533 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -20,8 +20,8 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` |
-Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
-your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
+Note that if you are using the [GitLab Slack application](../user/project/integrations/gitlab_slack_application.md) for
+your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](../user/project/integrations/gitlab_slack_application.md#usage).
## Issue commands
diff --git a/doc/integration/ultra_auth.md b/doc/integration/ultra_auth.md
new file mode 100644
index 00000000000..139cca456aa
--- /dev/null
+++ b/doc/integration/ultra_auth.md
@@ -0,0 +1,78 @@
+# UltraAuth OmniAuth Provider
+
+You can integrate your GitLab instance with [UltraAuth](https://ultraauth.com) to enable users to perform secure biometric authentication to your GitLab instance with your UltraAuth account. Users have to perform the biometric authentication using their mobile device with fingerprint sensor.
+
+## Create UltraAuth Application
+
+To enable UltraAuth OmniAuth provider, you must use UltraAuth's credentials for your GitLab instance.
+To get the credentials (a pair of Client ID and Client Secret), you must register an application on UltraAuth.
+
+1. Sign in to [UltraAuth](https://ultraauth.com).
+1. Navigate to [Create an App](https://ultraauth.com/select-strategy) and click on "Ruby on Rails".
+1. Scroll down the page that is displayed to locate the **Client ID** and **Client Secret**.
+ Keep this page open as you continue configuration.
+ ![UltraAuth Credentials: OPENID_CLIENT_ID and OPENID_CLIENT_SECRET](img/ultra_auth_credentials.png)
+1. Click on "Edit Callback URL" link.
+ ![Edit UltraAuth Callback URL](img/ultra_auth_edit_callback_url_highlighted.png)
+1. The callback URL will be `http(s)://<your_domain>/users/auth/ultraauth/callback`
+ ![UltraAuth Callback URL](img/ultra_auth_edit_callback_url.png)
+1. Select **Register application**.
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "ultraauth",
+ "app_id" => "OPENID_CLIENT_ID",
+ "app_secret" => "OPENID_CLIENT_SECRET",
+ "args" => {
+ "client_options" => {
+ "redirect_uri" => "https://example.com/users/auth/ultraauth/callback"
+ }
+ }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'ultraauth',
+ app_id: 'OPENID_CLIENT_ID',
+ app_secret: 'OPENID_CLIENT_SECRET',
+ args: {
+ client_options: {
+ redirect_uri: 'https://example.com/users/auth/ultraauth/callback'
+ }
+ }
+ }
+ ```
+ __Replace `https://example.com/users/auth/ultraauth/callback` with your application's Callback URL.__
+1. Change `OPENID_CLIENT_ID` to the Client ID from the UltraAuth application page.
+1. Change `OPENID_CLIENT_SECRET` to the Client Secret from the UltraAuth application page.
+1. Save the configuration file.
+1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you
+ installed GitLab via Omnibus or from source respectively.
+
+On the sign in page, there should now be a UltraAuth icon below the regular sign in form.
+Click the icon to begin the authentication process. UltraAuth will ask the user to sign in and authorize the GitLab application.
+If everything goes well, the user will be returned to GitLab and will be signed in.
+
+**Note:** GitLab requires the email address of each new user. Once the user is logged in using UltraAuth, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email.
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index e5fc7a3c85f..7f08188bd65 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -1,3 +1,29 @@
----
-redirect_to: 'README.md'
----
+# Corporate contributor license agreement
+
+You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions.
+
+1. Definitions.
+
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+
+4. You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of Your corporation here]." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com.
+
+5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
+
+6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+
+7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
+
+8. It is Your responsibility to notify GitLab.com when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf per Section 4. Such notification should be sent via email to legal@gitlab.com.
+
+This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md
index e5fc7a3c85f..59803aea080 100644
--- a/doc/legal/individual_contributor_license_agreement.md
+++ b/doc/legal/individual_contributor_license_agreement.md
@@ -1,3 +1,25 @@
----
-redirect_to: 'README.md'
----
+# Individual contributor license agreement
+
+You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions.
+
+1. Definitions.
+
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+
+2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+
+3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+
+4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V..
+
+5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
+
+6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+
+7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]".
+
+8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
+
+This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/license/README.md b/doc/license/README.md
new file mode 100644
index 00000000000..fd110a39b61
--- /dev/null
+++ b/doc/license/README.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/admin_area/license.md'
+---
+
+This document was moved to [another location](../user/admin_area/license.md).
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
index a2eca62d691..0cb092c85fd 100644
--- a/doc/logs/logs.md
+++ b/doc/logs/logs.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/logs.md'
+---
+
This document was moved to [administration/logs.md](../administration/logs.md).
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 4ac81ab3ee7..29cb6ac9164 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -1 +1,5 @@
-This document was moved to [user/markdown.md](../user/markdown.md).
+---
+redirect_to: '../user/markdown.md'
+---
+
+This document was moved to [another location](../user/markdown.md).
diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md
index 6cf93c33ec2..b611fa388b4 100644
--- a/doc/monitoring/health_check.md
+++ b/doc/monitoring/health_check.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/admin_area/monitoring/health_check.md'
+---
+
This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md).
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
index 19d46135930..233a12ebd6f 100644
--- a/doc/monitoring/performance/gitlab_configuration.md
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/gitlab_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/gitlab_configuration](../../administration/monitoring/performance/gitlab_configuration.md).
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index 0d4be02ff5f..f4e3561a19f 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/grafana_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/grafana_configuration](../../administration/monitoring/performance/grafana_configuration.md).
diff --git a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
deleted file mode 100644
index 51eef90068d..00000000000
--- a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_dashboard_import.png b/doc/monitoring/performance/img/grafana_dashboard_import.png
deleted file mode 100644
index fd639ee0eb8..00000000000
--- a/doc/monitoring/performance/img/grafana_dashboard_import.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_configuration.png b/doc/monitoring/performance/img/grafana_data_source_configuration.png
deleted file mode 100644
index a98e0ed1e7d..00000000000
--- a/doc/monitoring/performance/img/grafana_data_source_configuration.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_empty.png b/doc/monitoring/performance/img/grafana_data_source_empty.png
deleted file mode 100644
index 549ada8343e..00000000000
--- a/doc/monitoring/performance/img/grafana_data_source_empty.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_save_icon.png b/doc/monitoring/performance/img/grafana_save_icon.png
deleted file mode 100644
index 68a071f5ae2..00000000000
--- a/doc/monitoring/performance/img/grafana_save_icon.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
deleted file mode 100644
index b9563a00e97..00000000000
--- a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
index 15fd275e916..ae5f4c7e9df 100644
--- a/doc/monitoring/performance/influxdb_configuration.md
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/influxdb_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/influxdb_configuration](../../administration/monitoring/performance/influxdb_configuration.md).
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
index e53f9701dc3..57fb74cb6cd 100644
--- a/doc/monitoring/performance/influxdb_schema.md
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/influxdb_schema.md'
+---
+
This document was moved to [administration/monitoring/performance/influxdb_schema](../../administration/monitoring/performance/influxdb_schema.md).
diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md
index 4d6f02b6547..e23eabd5f40 100644
--- a/doc/monitoring/performance/introduction.md
+++ b/doc/monitoring/performance/introduction.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/index.md'
+---
+
This document was moved to [administration/monitoring/performance/introduction](../../administration/monitoring/performance/index.md).
diff --git a/doc/operations/README.md b/doc/operations/README.md
index d7a83948b87..319e5f48d38 100644
--- a/doc/operations/README.md
+++ b/doc/operations/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/operations/index.md'
+---
+
This document was moved to [another location](../administration/operations/index.md).
diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md
index 2a1d0a8c8eb..bde7fffd090 100644
--- a/doc/operations/cleaning_up_redis_sessions.md
+++ b/doc/operations/cleaning_up_redis_sessions.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md).
+---
+redirect_to: '../administration/operations/cleaning_up_redis_sessions.md'
+---
+
+This document was moved to [another location](../administration/operations/cleaning_up_redis_sessions.md).
diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md
index c54bca324a5..57d47e3e9ea 100644
--- a/doc/operations/moving_repositories.md
+++ b/doc/operations/moving_repositories.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md).
+---
+redirect_to: '../administration/operations/moving_repositories.md'
+---
+
+This document was moved to [another location](../administration/operations/moving_repositories.md).
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
index cf7c3b2e2ed..2df4a6e5648 100644
--- a/doc/operations/sidekiq_memory_killer.md
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md).
+---
+redirect_to: '../administration/operations/sidekiq_memory_killer.md'
+---
+
+This document was moved to [another location](../administration/operations/sidekiq_memory_killer.md).
diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md
index fbc9697b755..949f4a66c9d 100644
--- a/doc/operations/unicorn.md
+++ b/doc/operations/unicorn.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md).
+---
+redirect_to: '../administration/operations/unicorn.md'
+---
+
+This document was moved to [another location](../administration/operations/unicorn.md).
diff --git a/doc/pages/README.md b/doc/pages/README.md
index 7878bce3f10..c67847f1a83 100644
--- a/doc/pages/README.md
+++ b/doc/pages/README.md
@@ -1 +1,5 @@
-This document was moved to [pages/index.md](../user/project/pages/index.md).
+---
+redirect_to: '../user/project/pages/index.md'
+---
+
+This document was moved to [another location](../user/project/pages/index.md).
diff --git a/doc/pages/administration.md b/doc/pages/administration.md
index 4eb3bb32c77..015dd54ec7f 100644
--- a/doc/pages/administration.md
+++ b/doc/pages/administration.md
@@ -1 +1,5 @@
-This document was moved to [administration/pages](../administration/pages/index.md).
+---
+redirect_to: '../administration/pages/index.md'
+---
+
+This document was moved to [another location](../administration/pages/index.md).
diff --git a/doc/pages/getting_started_part_one.md b/doc/pages/getting_started_part_one.md
index 1d63ccb4d2f..a0feed0b477 100644
--- a/doc/pages/getting_started_part_one.md
+++ b/doc/pages/getting_started_part_one.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_one.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_one.md).
diff --git a/doc/pages/getting_started_part_three.md b/doc/pages/getting_started_part_three.md
index 1697b5cd6b4..b65247ff7b7 100644
--- a/doc/pages/getting_started_part_three.md
+++ b/doc/pages/getting_started_part_three.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_three.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_three.md).
diff --git a/doc/pages/getting_started_part_two.md b/doc/pages/getting_started_part_two.md
index a58affec73d..05353c171fc 100644
--- a/doc/pages/getting_started_part_two.md
+++ b/doc/pages/getting_started_part_two.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_two.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_two.md).
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 78d67aeec78..85923a40b2c 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../user/permissions.md'
+---
+
# Permissions
This document was moved to [user/permissions.md](../user/permissions.md).
diff --git a/doc/profile/README.md b/doc/profile/README.md
index fda6d85a84c..4932cf33b87 100644
--- a/doc/profile/README.md
+++ b/doc/profile/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/index.md'
+---
+
This document was moved to [user/profile/account](../user/profile/index.md).
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index cc16f3afe41..cf99bd61f5d 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/preferences.md'
+---
+
This document was moved to [another location](../user/profile/preferences.md).
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index 60918a0339c..453ac833f59 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/account/two_factor_authentication.md'
+---
+
This document was moved to [user/profile/account](../user/profile/account/two_factor_authentication.md).
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
index 5b171080c72..a1ff8909f87 100644
--- a/doc/project_services/bamboo.md
+++ b/doc/project_services/bamboo.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/bamboo.md'
+---
+
This document was moved to [user/project/integrations/bamboo.md](../user/project/integrations/bamboo.md).
diff --git a/doc/project_services/bugzilla.md b/doc/project_services/bugzilla.md
index e67055d5616..1abf1b28939 100644
--- a/doc/project_services/bugzilla.md
+++ b/doc/project_services/bugzilla.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/bugzilla.md'
+---
+
This document was moved to [user/project/integrations/bugzilla.md](../user/project/integrations/bugzilla.md).
diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md
index a2e831ada34..c5ab8aa5c70 100644
--- a/doc/project_services/emails_on_push.md
+++ b/doc/project_services/emails_on_push.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/emails_on_push.md'
+---
+
This document was moved to [user/project/integrations/emails_on_push.md](../user/project/integrations/emails_on_push.md).
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
new file mode 100644
index 00000000000..4ae9f6c6b2e
--- /dev/null
+++ b/doc/project_services/hipchat.md
@@ -0,0 +1 @@
+This document was moved to [user/project/integrations/hipchat.md](../user/project/integrations/hipchat.md).
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
index 7f0850dcc24..af47abab117 100644
--- a/doc/project_services/irker.md
+++ b/doc/project_services/irker.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/irker.md'
+---
+
This document was moved to [user/project/integrations/irker.md](../user/project/integrations/irker.md).
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
index 63614feba82..6a0108f400f 100644
--- a/doc/project_services/jira.md
+++ b/doc/project_services/jira.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/jira.md'
+---
+
This document was moved to [user/project/integrations/jira.md](../user/project/integrations/jira.md).
diff --git a/doc/project_services/kubernetes.md b/doc/project_services/kubernetes.md
index 0497a13c2b7..cfe36fcd1b2 100644
--- a/doc/project_services/kubernetes.md
+++ b/doc/project_services/kubernetes.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/kubernetes.md'
+---
+
This document was moved to [user/project/integrations/kubernetes.md](../user/project/integrations/kubernetes.md).
diff --git a/doc/project_services/mattermost.md b/doc/project_services/mattermost.md
index 554a028853e..de9f4d14cf7 100644
--- a/doc/project_services/mattermost.md
+++ b/doc/project_services/mattermost.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/mattermost.md'
+---
+
This document was moved to [user/project/integrations/mattermost.md](../user/project/integrations/mattermost.md).
diff --git a/doc/project_services/mattermost_slash_commands.md b/doc/project_services/mattermost_slash_commands.md
index 7c238b5dc37..82ec34739c1 100644
--- a/doc/project_services/mattermost_slash_commands.md
+++ b/doc/project_services/mattermost_slash_commands.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/mattermost_slash_commands.md'
+---
+
This document was moved to [user/project/integrations/mattermost_slash_commands.md](../user/project/integrations/mattermost_slash_commands.md).
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index 2c555c4edae..a355851a273 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/project_services.md'
+---
+
This document was moved to [user/project/integrations/project_services.md](../user/project/integrations/project_services.md).
diff --git a/doc/project_services/redmine.md b/doc/project_services/redmine.md
index 6010aa4dc75..05f28f00adc 100644
--- a/doc/project_services/redmine.md
+++ b/doc/project_services/redmine.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/redmine.md'
+---
+
This document was moved to [user/project/integrations/redmine.md](../user/project/integrations/redmine.md).
diff --git a/doc/project_services/services_templates.md b/doc/project_services/services_templates.md
index 8905d667c5a..ac6b85cc801 100644
--- a/doc/project_services/services_templates.md
+++ b/doc/project_services/services_templates.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/services_templates.md'
+---
+
This document was moved to [user/project/integrations/services_templates.md](../user/project/integrations/services_templates.md).
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
index 1d3f98705e3..4c89ce92002 100644
--- a/doc/project_services/slack.md
+++ b/doc/project_services/slack.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/slack.md'
+---
+
This document was moved to [user/project/integrations/slack.md](../user/project/integrations/slack.md).
diff --git a/doc/project_services/slack_slash_commands.md b/doc/project_services/slack_slash_commands.md
index 9554c8decc8..ca0034256f1 100644
--- a/doc/project_services/slack_slash_commands.md
+++ b/doc/project_services/slack_slash_commands.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/slack_slash_commands.md'
+---
+
This document was moved to [user/project/integrations/slack_slash_commands.md](../user/project/integrations/slack_slash_commands.md).
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
new file mode 100644
index 00000000000..e44eab2556e
--- /dev/null
+++ b/doc/push_rules/push_rules.md
@@ -0,0 +1,156 @@
+# Push Rules **[STARTER]**
+
+Gain additional control over pushes to your repository.
+
+## Overview
+
+GitLab already offers [protected branches][protected-branches], but there are
+cases when you need some specific rules like preventing git tag removal or
+enforcing a special format for commit messages.
+
+Push rules are essentially [pre-receive Git hooks][hooks] that are easy to
+enable in a user-friendly interface. They are defined globally if you are an
+admin or per project so you can have different rules applied to different
+projects depending on your needs.
+
+## Use cases
+
+Every push rule could have its own use case, but let's consider some examples.
+
+### Commit messages with a specific reference
+
+Let's assume you have the following requirements for your workflow:
+
+- every commit should reference a JIRA issue, for example: `Refactored css. Fixes JIRA-123.`
+- users should not be able to remove git tags with `git push`
+
+All you need to do is write a simple regular expression that requires the mention
+of a JIRA issue in the commit message, like `JIRA\-\d+`.
+
+Now when a user tries to push a commit with a message `Bugfix`, their push will
+be declined. Only pushing commits with messages like `Bugfix according to JIRA-123`
+will be accepted.
+
+### Restrict branch names
+
+Let's assume there's a strictly policy for branch names in your company, and
+you want the branches to start with a certain name because you have different
+GitLab CI jobs (`feature`, `hotfix`, `docker`, `android`, etc.) that rely on the
+branch name.
+
+Your developers however, don't always remember that policy, so they push
+various branches and CI pipelines do not work as expected. By restricting the
+branch names globally in Push Rules, you can now sleep without the anxiety
+of your developers' mistakes. Every branch that doesn't match your push rule
+will get rejected.
+
+## Enabling push rules
+
+>**Note:**
+GitLab administrators can set push rules globally under
+**Admin area > Push Rules** that all new projects will inherit. You can later
+override them in a project's settings.
+
+1. Navigate to your project's **Settings > Repository** and expand **Push Rules**
+1. Set the rule you want
+1. Click **Save Push Rules** for the changes to take effect
+
+The following options are available.
+
+| Push rule | GitLab version | Description |
+| --------- | :------------: | ----------- |
+| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. |
+| Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. |
+| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
+| Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. |
+| Prevent committing secrets to Git | **Starter** 8.12 | GitLab will reject any files that are likely to contain secrets. Read [what files are forbidden](#prevent-pushing-secrets-to-the-repository). |
+| Restrict by commit message | **Starter** 7.10 | Only commit messages that match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. |
+| Restrict by commit message (negative match)| **Starter** 11.1 | Only commit messages that do not match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. |
+| Restrict by branch name | **Starter** 9.3 | Only branch names that match this regular expression are allowed to be pushed. Leave empty to allow any branch name. |
+| Restrict by commit author's email | **Starter** 7.10 | Only commit author's email that match this regular expression are allowed to be pushed. Leave empty to allow any email. |
+| Prohibited file names | **Starter** 7.10 | Any committed filenames that match this regular expression are not allowed to be pushed. Leave empty to allow any filenames. |
+| Maximum file size | **Starter** 7.12 | Pushes that contain added or updated files that exceed this file size (in MB) are rejected. Set to 0 to allow files of any size. |
+
+>**Tip:**
+GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions in push rules. You can check your regular expressions at <https://regex-golang.appspot.com>.
+
+## Prevent pushing secrets to the repository
+
+> [Introduced][ee-385] in [GitLab Starter][ee] 8.12.
+
+You can turn on a predefined blacklist of files which won't be allowed to be
+pushed to a repository.
+
+By selecting the checkbox *Prevent committing secrets to Git*, GitLab prevents
+pushes to the repository when a file matches a regular expression as read from
+[`files_blacklist.yml`][list] (make sure you are at the right branch
+as your GitLab version when viewing this file).
+
+NOTE: **Note**:
+Files already committed won't get restricted by this push rule.
+
+Below is an example list of what will be rejected by these regular expressions:
+
+```shell
+#####################
+# AWS CLI credential blobs
+#####################
+.aws/credentials
+aws/credentials
+homefolder/aws/credentials
+
+#####################
+# Private RSA SSH keys
+#####################
+/ssh/id_rsa
+/.ssh/personal_rsa
+/config/server_rsa
+id_rsa
+.id_rsa
+
+#####################
+# Private DSA SSH keys
+#####################
+/ssh/id_dsa
+/.ssh/personal_dsa
+/config/server_dsa
+id_dsa
+.id_dsa
+
+#####################
+# Private ed25519 SSH keys
+#####################
+/ssh/id_ed25519
+/.ssh/personal_ed25519
+/config/server_ed25519
+id_ed25519
+.id_ed25519
+
+#####################
+# Private ECDSA SSH keys
+#####################
+/ssh/id_ecdsa
+/.ssh/personal_ecdsa
+/config/server_ecdsa
+id_ecdsa
+.id_ecdsa
+
+#####################
+# Any file with .pem or .key extensions
+#####################
+*.pem
+*.key
+
+#####################
+# Any file ending with _history or .history extension
+#####################
+pry.history
+bash_history
+```
+
+[protected-branches]: ../user/project/protected_branches.md
+[signing-commits]: ../user/project/repository/gpg_signed_commits/index.md
+[ee-385]: https://gitlab.com/gitlab-org/gitlab-ee/issues/385
+[list]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/gitlab/checks/files_blacklist.yml
+[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 90187617c41..0729875daf8 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -15,3 +15,4 @@ comments: false
- [Import](import.md) of git repositories in bulk
- [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
- [Migrate Uploads](../administration/raketasks/uploads/migrate.md)
+- [Sanitize Uploads](../administration/raketasks/uploads/sanitize.md)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 069c87f921a..764916ca82d 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -100,6 +100,13 @@ the gitlab task runner pod via `kubectl`. Refer to [backing up a GitLab installa
kubectl exec -it <gitlab task-runner pod> backup-utility
```
+Similarly to the Kubernetes case, if you have scaled out your GitLab
+cluster to use multiple application servers, you should pick a
+designated node (that won't be auto-scaled away) for running the
+backup rake task. Because the backup rake task is tightly coupled to
+the main Rails application, this is typically a node on which you're
+also running Unicorn/Puma and/or Sidekiq.
+
Example output:
```
@@ -195,6 +202,26 @@ To use the `copy` strategy instead of the default streaming strategy, specify
sudo gitlab-rake gitlab:backup:create STRATEGY=copy
```
+### Backup filename
+
+By default a backup file is created according to the specification in [the Backup timestamp](#backup-timestamp) section above. You can however override the `[TIMESTAMP]` part of the filename by setting the `BACKUP` environment variable. For example:
+
+```sh
+sudo gitlab-rake gitlab:backup:create BACKUP=dump
+```
+
+The resulting file will then be `dump_gitlab_backup.tar`. This is useful for systems that make use of rsync and incremental backups, and will result in considerably faster transfer speeds.
+
+### Rsyncable
+
+To make sure the generated archive is intelligently transferable by rsync, the `GZIP_RSYNCABLE=yes` option can be set. This will set the `--rsyncable` option to `gzip`. This is only useful in combination with setting [the Backup filename option](#backup-filename).
+
+Note that the `--rsyncable` option in `gzip` is not guaranteed to be available on all distributions. To verify that it is available in your distribution you can run `gzip --help` or consult the man pages.
+
+```sh
+sudo gitlab-rake gitlab:backup:create BACKUP=dump GZIP_RSYNCABLE=yes
+```
+
### Excluding specific directories from the backup
You can choose what should be exempt from the backup up by adding the environment variable `SKIP`.
@@ -382,6 +409,8 @@ an access key from the Google console first:
1. Select "Interoperability" and create an access key
1. Make note of the "Access Key" and "Secret" and replace them in the
configurations below
+1. In the buckets advanced settings ensure the Access Control option "Set object-level
+ and bucket-level permissions" is selected
1. Make sure you already have a bucket created
For Omnibus GitLab packages:
@@ -603,7 +632,7 @@ directory (repositories, uploads).
To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json`
(for Omnibus packages) or `/home/git/gitlab/.secret` (for installations
from source). This file contains the database encryption key,
-[CI/CD variables](../ci/variables/README.md#variables), and
+[CI/CD variables](../ci/variables/README.md#gitlab-cicd-environment-variables), and
variables used for [two-factor authentication](../user/profile/account/two_factor_authentication.md).
If you fail to restore this encryption key file along with the application data
backup, users with two-factor authentication enabled and GitLab Runners will
@@ -819,15 +848,24 @@ including (but not restricted to):
* [Project mirroring](../workflow/repository_mirroring.md)
* [Web hooks](../user/project/integrations/webhooks.md)
-In the case of CI/CD, variables, you might experience some weird behavior, like
-stuck jobs or 500 errors. In that case, you can try removing contents of the
-`ci_group_variables` and `ci_project_variables` tables from the database.
+In cases like CI/CD variables and Runner authentication, you might
+experience some unexpected behavior such as:
+
+- Stuck jobs.
+- 500 errors.
+
+In this case, you are required to reset all the tokens for CI/CD variables
+and Runner Authentication, which is described in more detail below. After
+resetting the tokens, you should be able to visit your project and the jobs
+will have started running again.
CAUTION: **Warning:**
Use the following commands at your own risk, and make sure you've taken a
backup beforehand.
-1. Enter the Rails console:
+#### Reset CI/CD variables
+
+1. Enter the DB console:
For Omnibus GitLab packages:
@@ -860,8 +898,39 @@ backup beforehand.
1. You may need to reconfigure or restart GitLab for the changes to take
effect.
-You should now be able to visit your project, and the jobs will start
-running again.
+
+#### Reset Runner registration tokens
+
+1. Enter the DB console:
+
+ For Omnibus GitLab packages:
+
+ ```sh
+ sudo gitlab-rails dbconsole
+ ```
+
+ For installations from source:
+
+ ```sh
+ sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
+ ```
+
+1. Clear all the tokens for projects, groups, and the whole instance:
+
+ CAUTION: **Caution:**
+ The last UPDATE operation will stop the runners being able to pick up
+ new jobs. You must register new runners.
+
+ ```sql
+ -- Clear project tokens
+ UPDATE projects SET runners_token = null, runners_token_encrypted = null;
+ -- Clear group tokens
+ UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
+ -- Clear instance tokens
+ UPDATE application_settings SET runners_registration_token_encrypted = null;
+ -- Clear runner tokens
+ UPDATE ci_runners SET token = null, token_encrypted = null;
+ ```
A similar strategy can be employed for the remaining features - by removing the
data that cannot be decrypted, GitLab can be brought back into working order,
diff --git a/doc/raketasks/check.md b/doc/raketasks/check.md
index f7f6a40cd04..ceb089e80c0 100644
--- a/doc/raketasks/check.md
+++ b/doc/raketasks/check.md
@@ -1,3 +1,5 @@
-# Check Rake Tasks
+---
+redirect_to: '../administration/raketasks/check.md'
+---
-This document was moved to [administration/raketasks/check](../administration/raketasks/check.md).
+This document was moved to [another location](../administration/raketasks/check.md).
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index e70a009323e..f5c788af578 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -23,6 +23,16 @@ sudo gitlab-rake gitlab:cleanup:repos
bundle exec rake gitlab:cleanup:repos RAILS_ENV=production
```
+Remove old repository copies from repositories moved to another storage.
+
+```
+# omnibus-gitlab
+sudo gitlab-rake gitlab:cleanup:moved
+
+# installation from source
+bundle exec rake gitlab:cleanup:moved RAILS_ENV=production
+```
+
Clean up local project upload files if they don't exist in GitLab database. The
task attempts to fix the file if it can find its project, otherwise it moves the
file to a lost and found directory.
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index bb316df5b9a..b59c06a24ea 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -16,7 +16,7 @@
The new folder needs to have git user ownership and read/write/execute access for git user and its group:
```
-sudo -u git mkdir /var/opt/gitlab/git-data/repository-import-<date>/new_group
+sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
```
### Copy your bare repositories inside this newly created folder:
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 266aeb7d60e..f554a09d94d 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -1,3 +1,5 @@
-# Maintenance Rake Tasks
+---
+redirect_to: '../administration/raketasks/maintenance.md'
+---
-This document was moved to [administration/raketasks/maintenance](../administration/raketasks/maintenance.md).
+This document was moved to [another location](../administration/raketasks/maintenance.md).
diff --git a/doc/security/img/two_factor_authentication_group_settings.png b/doc/security/img/two_factor_authentication_group_settings.png
deleted file mode 100644
index 05d95554fd9..00000000000
--- a/doc/security/img/two_factor_authentication_group_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
deleted file mode 100644
index 2a2208f98bd..00000000000
--- a/doc/security/img/two_factor_authentication_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index ad83dc05a93..66081d7e376 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -94,7 +94,7 @@ In case you want to remove a blocked IP, follow these steps:
1. Find the IPs that have been blocked in the production log:
```sh
- grep "Rack_Attack" /var/log/gitlab/gitlab-rails/production.log
+ grep "Rack_Attack" /var/log/gitlab/gitlab-rails/auth.log
```
1. Since the blacklist is stored in Redis, you need to open up `redis-cli`:
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 4b65b901487..2ece4ed3fc9 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -16,39 +16,35 @@ enforce everyone to set up 2FA, you can choose from two different ways:
- Enforce on next login.
- Suggest on next login, but allow a grace period before enforcing.
-In the Admin area under **Settings** (`/admin/application_settings`), look for
-the "Sign-in Restrictions" area, where you can configure both.
+After the configured grace period has elapsed, users will be able to log in but
+won't be able to leave the 2FA configuration area at `/profile/two_factor_auth`.
+
+To enable 2FA for all users:
+
+1. Navigate to **Admin area > Settings > General** (`/admin/application_settings`).
+1. Expand the **Sign-in restrictions** section, where you can configure both.
If you want 2FA enforcement to take effect on next login, change the grace
period to `0`.
----
-
-![Two factor authentication admin settings](img/two_factor_authentication_settings.png)
+## Enforcing 2FA for all users in a group
----
+If you want to enforce 2FA only for certain groups, you can:
-## Enforcing 2FA for all users in a group
+1. Enable it in the group's **Settings > General** page.
+1. Optionally specify a grace period as above.
-If you want to enforce 2FA only for certain groups, you can enable it in the
-group settings and specify a grace period as above. To change this setting you
-need to be administrator or owner of the group.
+To change this setting, you need to be administrator or owner of the group.
If there are multiple 2FA requirements (i.e. group + all users, or multiple
groups) the shortest grace period will be used.
----
-
-![Two factor authentication group settings](img/two_factor_authentication_group_settings.png)
-
----
-
## Disabling 2FA for everyone
There may be some special situations where you want to disable 2FA for everyone
even when forced 2FA is disabled. There is a rake task for that:
-```
+```sh
# Omnibus installations
sudo gitlab-rake gitlab:two_factor:disable_for_all_users
@@ -56,5 +52,6 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users
sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
```
-**IMPORTANT: this is a permanent and irreversible action. Users will have to
- reactivate 2FA from scratch if they want to use it again.**
+CAUTION: **Caution:**
+This is a permanent and irreversible action. Users will have to
+reactivate 2FA from scratch if they want to use it again.
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9c4a391e8da..3bfebfc5d9b 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -55,7 +55,7 @@ As an admin, you can restrict
By default, all keys are permitted, which is also the case for
[GitLab.com](../user/gitlab_com/index.md#ssh-host-keys-fingerprints).
-## ED25519 SSH keys
+### ED25519 SSH keys
Following [best practices](https://linux-audit.com/using-ed25519-openssh-keys-instead-of-dsa-rsa-ecdsa/),
you should always favor [ED25519](https://ed25519.cr.yp.to/) SSH keys, since they
@@ -65,7 +65,7 @@ They were introduced in OpenSSH 6.5, so any modern OS should include the
option to create them. If for any reason your OS or the GitLab instance you
interact with doesn't support this, you can fallback to RSA.
-## RSA SSH keys
+### RSA SSH keys
RSA keys are the most common ones and therefore the most compatible with
servers that may have an old OpenSSH version. Use them if the GitLab server
@@ -166,12 +166,13 @@ Now, it's time to add the newly created public key to your GitLab account.
NOTE: **Note:**
If you opted to create an RSA key, the name might differ.
-1. Add your public SSH key to your GitLab account by clicking your avatar
- in the upper right corner and selecting **Settings**. From there on,
- navigate to **SSH Keys** and paste your public key in the "Key" section.
- If you created the key with a comment, this will appear under "Title".
- If not, give your key an identifiable title like _Work Laptop_ or
- _Home Workstation_, and click **Add key**.
+1. Add your **public** SSH key to your GitLab account by:
+ 1. Clicking your avatar in the upper right corner and selecting **Settings**.
+ 1. Navigating to **SSH Keys** and pasting your **public** key in the **Key** field. If you:
+
+ - Created the key with a comment, this will appear in the **Title** field.
+ - Created the key without a comment, give your key an identifiable title like _Work Laptop_ or _Home Workstation_.
+ 1. Click the **Add key** button.
NOTE: **Note:**
If you manually copied your public SSH key make sure you copied the entire
@@ -305,7 +306,7 @@ who needs to know and configure the private key.
GitLab administrators set up Global Deploy keys in the Admin area under the
section **Deploy Keys**. Ensure keys have a meaningful title as that will be
the primary way for project maintainers and owners to identify the correct Global
-Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
+Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
use the name of that service in the key name if that is all it is used for.
When creating Global Shared Deploy keys, give some thought to the granularity
of keys - they could be of very narrow usage such as just a specific service or
diff --git a/doc/subscriptions/billing_table.png b/doc/subscriptions/billing_table.png
new file mode 100644
index 00000000000..acd1b6193ec
--- /dev/null
+++ b/doc/subscriptions/billing_table.png
Binary files differ
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
new file mode 100644
index 00000000000..dfd80f8882e
--- /dev/null
+++ b/doc/subscriptions/index.md
@@ -0,0 +1,103 @@
+# Subscription setup and management
+
+This page will help get you started with your new subscription or manage an existing one, whether you have subscribed to GitLab.com or self-managed GitLab.
+
+To subscribe, upgrade, or read more about the types of subscriptions, please see [Subscribe to GitLab](../README.md#subscribe-to-gitlab) on the GitLab Documentation landing page.
+
+## Set up GitLab
+
+Learn how GitLab helps you in the stages of the DevOps lifecycle by learning more [about the GitLab product](https://about.gitlab.com/product/), [GitLab features](https://about.gitlab.com/features/), and [GitLab Documentation](../README.md).
+
+### Self-managed: Install GitLab
+
+Take a look at [installing GitLab](https://about.gitlab.com/install/) and our [administrator documentation](../administration/index.md). Then, follow the instructions below under [Your subscription](#your-subscription) to apply your license file.
+
+### GitLab.com: Create a user and group
+
+Start with creating a user account for yourself using our [sign up page](https://gitlab.com/users/sign_in#register-pane).
+
+[GitLab groups](../user/group/index.md) help assemble related projects together allowing you to grant members access to several projects at once. A group is not required if you plan on having [projects](../user/project/) inside a personal namespace.
+
+## Your subscription
+
+You can view and manage subscriptions through our [Customers portal](https://customers.gitlab.com/). Information on applying your subscription is below.
+
+Please also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/)
+
+### View subscription and seats
+
+To view and manage the subscriptions you have purchased and the number of seats associated with the subscription, please visit and log into the [Customers’ Portal](https://customers.gitlab.com/subscriptions). For more information, please see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/) and [pricing page](https://about.gitlab.com/pricing/), which includes information on our [true-up pricing policy](https://about.gitlab.com/handbook/product/pricing/#true-up-pricing) when adding more users than at the time of purchase.
+
+Please note that this account may have the same email, but is a _separate_ login from your GitLab.com account. If the two accounts are linked together, then you can use the "sign in with GitLab.com account" link underneath the `Sign In` button.
+
+### Change billing information
+
+In the customers portal, go to the `My Account` page, then revise the `Account Details` information and click on the `Update Account` button.
+
+Future purchases will use the information in this section. The email listed in this section is used for the Customers Portal login and for license related email communication.
+
+### Self-managed: Apply your license file
+
+After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license).
+
+### Link your GitLab.com account with your Customers Portal account
+
+NOTE: **Note:** This is *required* for GitLab.com subscriptions.
+
+Once signed into the customers portal, if your account is not already linked, you should be prompted to link your account with a "Link my GitLab Account" button.
+
+You can also go to the [My Account](https://customers.gitlab.com/customers/edit) page to add or change the GitLab.com account link.
+
+### Change the linked GitLab.com account for your Customers Portal account
+
+To change which GitLab.com account is associated with a Customers Portal account, please follow these steps:
+
+1. Log into the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
+1. In a separate browser tab, visit [GitLab.com](https://gitlab.com) to ensure you are not logged in, or if you are, log out.
+1. Back on the Customers Portal page, click [My Account](https://customers.gitlab.com/customers/edit) in the top menu.
+1. Under `Your GitLab.com account`, click the `Change linked account` button.
+1. Have the user you want associated log in to their [GitLab.com](https://gitlab.com) account.
+
+### GitLab.com: Associate your namespace with your subscription
+
+Once your GitLab.com account is linked, you can go to your [Subscriptions](https://customers.gitlab.com/subscriptions) page to choose or change the namespace your subscription applies to.
+
+Please note that you need to be a group owner to associate a group to your subscription.
+
+### Confirm or upgrade your GitLab.com subscription details within GitLab
+
+To see the status of your GitLab.com subscription, you can click on the Billings
+section of the relevant namespace:
+
+* For individuals, this is located at https://gitlab.com/profile/billings under
+in your Settings,
+* For groups, this is located under the group's Settings dropdown, under Billing.
+
+For groups, you can see details of your subscription - including your current
+plan - in the included table:
+
+![Billing table](billing_table.png)
+
+| Field | Description |
+| ------ | ------ |
+| Seats in subscription | If this is a paid plan, this represents the number of seats you've paid to support in your group. |
+| Seats currently in use | The number of active seats currently in use. |
+| Max seats used | The highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. |
+| Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. |
+| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. |
+| Subscription end date | The date your current subscription will end. This does not apply to Free plans. |
+
+## Need help?
+
+[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of topics covering the use and administration of GitLab.
+
+We also encourage all users to search our project trackers for known issues and existing feature requests in:
+
+- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/) for features included in all tiers, and
+- [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/issues/) for paid-tier features.
+
+These issues are the best avenue for getting updates on specific product plans and for communicating directly with the relevant GitLab team members.
+
+### Contacting Support
+
+Learn more about the tiers of [GitLab Support](https://about.gitlab.com/support/) or [submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
diff --git a/doc/tools/email.md b/doc/tools/email.md
new file mode 100644
index 00000000000..ab39206ffa4
--- /dev/null
+++ b/doc/tools/email.md
@@ -0,0 +1,38 @@
+# Email from GitLab **[STARTER ONLY]**
+
+As a GitLab administrator you can email GitLab users from within GitLab.
+
+## Overview
+
+GitLab provides a simple tool to email all users or users of a chosen group or
+project right from the admin area. Users will receive the email to their primary
+email address.
+
+## Use-cases
+
+- Notify your users about a new project, a new feature, or a new product launch.
+- Notify your users about a new deployment, or that will be downtime expected
+ for a particular reason.
+
+## Sending emails to users from within GitLab
+
+1. Go to the admin area using the wrench icon in the top right corner and
+ navigate to **Overview > Users > Send email to users**.
+
+ ![admin users](email1.png)
+
+1. Compose an email and choose where it will be sent (all users or users of a
+ chosen group or project):
+
+ ![compose an email](email2.png)
+
+## Unsubscribing from emails
+
+User can choose to unsubscribe from receiving emails from GitLab by following
+the unsubscribe link from the email. Unsubscribing is unauthenticated in order
+to keep the simplicity of this feature.
+
+On unsubscribe, user will receive an email notifying that unsubscribe happened.
+The endpoint that provides the unsubscribe option is rate-limited.
+
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/tools/email1.png b/doc/tools/email1.png
new file mode 100644
index 00000000000..e79ccc3e9a9
--- /dev/null
+++ b/doc/tools/email1.png
Binary files differ
diff --git a/doc/tools/email2.png b/doc/tools/email2.png
new file mode 100644
index 00000000000..d073c0e42da
--- /dev/null
+++ b/doc/tools/email2.png
Binary files differ
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index df6897002c9..228da2d1f57 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -17,11 +17,11 @@ This page gathers all the resources for the topic **Authentication** within GitL
## GitLab administrators
- [LDAP (Community Edition)](../../administration/auth/ldap.md)
-- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html)
+- [LDAP (Enterprise Edition)](../../administration/auth/ldap-ee.md) **[STARTER]**
- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
- **Articles:**
- [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.md)
+ - [How to Configure LDAP with GitLab EE](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md) **[STARTER]**
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html)
- **Integrations:**
@@ -30,12 +30,14 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Atlassian Crowd OmniAuth Provider](../../administration/auth/crowd.md)
- [CAS OmniAuth Provider](../../integration/cas.md)
- [SAML OmniAuth Provider](../../integration/saml.md)
+ - [SAML for GitLab.com Groups](../../user/group/saml_sso/index.md) **[SILVER ONLY]**
+ - [SCIM user provisioning for GitLab.com Groups](../../user/group/saml_sso/scim_setup.md) **[SILVER ONLY]**
- [Okta SSO provider](../../administration/auth/okta.md)
- - [Kerberos integration (GitLab EE)](https://docs.gitlab.com/ee/integration/kerberos.html)
+ - [Kerberos integration (GitLab EE)](../../integration/kerberos.md) **[STARTER]**
## API
-- [OAuth 2 Tokens](../../api/README.md#oauth-2-tokens)
+- [OAuth 2 Tokens](../../api/README.md#oauth2-tokens)
- [Personal access tokens](../../api/README.md#personal-access-tokens)
- [Impersonation tokens](../../api/README.md#impersonation-tokens)
- [GitLab as an OAuth2 provider](../../api/oauth2.md#gitlab-as-an-oauth2-provider)
diff --git a/doc/topics/autodevops/img/autodevops_domain_variables.png b/doc/topics/autodevops/img/autodevops_domain_variables.png
deleted file mode 100644
index b6f8864796f..00000000000
--- a/doc/topics/autodevops/img/autodevops_domain_variables.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/img/guide_connect_cluster.png b/doc/topics/autodevops/img/guide_connect_cluster.png
deleted file mode 100644
index 703d536f37a..00000000000
--- a/doc/topics/autodevops/img/guide_connect_cluster.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/img/guide_create_cluster.png b/doc/topics/autodevops/img/guide_create_cluster.png
deleted file mode 100644
index cd1d0fdd8da..00000000000
--- a/doc/topics/autodevops/img/guide_create_cluster.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/img/guide_gke_apis_after.png b/doc/topics/autodevops/img/guide_gke_apis_after.png
deleted file mode 100644
index 380de958867..00000000000
--- a/doc/topics/autodevops/img/guide_gke_apis_after.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/img/guide_gke_apis_before.png b/doc/topics/autodevops/img/guide_gke_apis_before.png
deleted file mode 100644
index d06fc707887..00000000000
--- a/doc/topics/autodevops/img/guide_gke_apis_before.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/img/guide_merge_request_ide.png b/doc/topics/autodevops/img/guide_merge_request_ide.png
deleted file mode 100644
index c825b0849e1..00000000000
--- a/doc/topics/autodevops/img/guide_merge_request_ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 7a18354bf66..b00a8afa386 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -13,7 +13,7 @@ Starting with GitLab 11.3, the Auto DevOps pipeline is enabled by default for al
projects. If it has not been explicitly enabled for the project, Auto DevOps will be automatically
disabled on the first pipeline failure. Your project will continue to use an alternative
[CI/CD configuration file](../../ci/yaml/README.md) if one is found. A GitLab
-administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops-core-only)
+administrator can [change this setting](../../user/admin_area/settings/continuous_integration.md#auto-devops-core-only)
in the admin area.
With Auto DevOps, the software development process becomes easier to set up
@@ -126,10 +126,6 @@ Auto Deploy, and Auto Monitoring will be silently skipped.
## Auto DevOps base domain
-NOTE: **Note**
-`AUTO_DEVOPS_DOMAIN` environment variable is deprecated and
-[is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
-
The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It can be defined
in any of the following places:
@@ -141,7 +137,7 @@ in any of the following places:
NOTE: **Note**
The Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence
-as other environment [variables](../../ci/variables/README.md#priority-of-variables).
+as other environment [variables](../../ci/variables/README.md#priority-of-environment-variables).
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
@@ -162,6 +158,12 @@ Auto DevOps base domain to `1.2.3.4.nip.io`.
Once set up, all requests will hit the load balancer, which in turn will route
them to the Kubernetes pods that run your application(s).
+NOTE: **Note:**
+From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
+Support for `AUTO_DEVOPS_DOMAIN` was [removed in GitLab
+12.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
+
+
## Using multiple Kubernetes clusters **[PREMIUM]**
When using Auto DevOps, you may want to deploy different environments to
@@ -179,7 +181,7 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so
except for the environment scope, they would also need to have a different
domain they would be deployed to. This is why you need to define a separate
`KUBE_INGRESS_BASE_DOMAIN` variable for all the above
-[based on the environment](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-variables-premium).
+[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
The following table is an example of how the three different clusters would
be configured.
@@ -209,10 +211,6 @@ and verifying that your app is deployed as a review app in the Kubernetes
cluster with the `review/*` environment scope. Similarly, you can check the
other environments.
-NOTE: **Note:**
-From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
-`AUTO_DEVOPS_DOMAIN` [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
-
## Enabling/Disabling Auto DevOps
When first using Auto Devops, review the [requirements](#requirements) to ensure all necessary components to make
@@ -230,8 +228,25 @@ can enable/disable Auto DevOps at either the project-level or instance-level.
1. Click **Save changes** for the changes to take effect.
NOTE: **Note:**
-Even when disabled at the instance level, project maintainers are still able to enable
-Auto DevOps at the project level.
+Even when disabled at the instance level, group owners and project maintainers are still able to enable
+Auto DevOps at group-level and project-level, respectively.
+
+### Enabling/disabling Auto DevOps at the group-level
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10.
+
+To enable or disable Auto DevOps at the group-level:
+
+1. Go to group's **Settings > CI/CD > Auto DevOps** page.
+1. Toggle the **Default to Auto DevOps pipeline** checkbox (checked to enable, unchecked to disable).
+1. Click **Save changes** button for the changes to take effect.
+
+When enabling or disabling Auto DevOps at group-level, group configuration will be implicitly used for
+the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on
+the subgroup or project.
+
+NOTE: **Note**
+Only administrators and group owners are allowed to enable or disable Auto DevOps at group-level.
### Enabling/disabling Auto DevOps at the project-level
@@ -291,7 +306,7 @@ Auto Build creates a build of the application using an existing `Dockerfile` or
Heroku buildpacks.
Either way, the resulting Docker image is automatically pushed to the
-[Container Registry][container-registry] and tagged with the commit SHA.
+[Container Registry][container-registry] and tagged with the commit SHA or tag.
#### Auto Build using a Dockerfile
@@ -348,7 +363,7 @@ created, and is uploaded as an artifact which you can later download and check
out.
Any differences between the source and target branches are also
-[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html).
+[shown in the merge request widget](../../user/project/merge_requests/code_quality.md).
### Auto SAST **[ULTIMATE]**
@@ -360,8 +375,8 @@ analysis on the current code and checks for potential security issues. Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
-Any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/sast.html).
+Any security warnings are also shown in the merge request widget. Read more how
+[SAST works](../../user/application_security/sast/index.md).
NOTE: **Note:**
The Auto SAST stage will be skipped on licenses other than Ultimate.
@@ -379,8 +394,8 @@ to run analysis on the project dependencies and checks for potential security is
report is created, it's uploaded as an artifact which you can later download and
check out.
-Any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/dependency_scanning.html).
+Any security warnings are also shown in the merge request widget. Read more about
+[Dependency Scanning](../../user/application_security/dependency_scanning/index.md).
NOTE: **Note:**
The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate.
@@ -398,8 +413,8 @@ to search the project dependencies for their license. Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
-Any licenses are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/license_management.html).
+Any licenses are also shown in the merge request widget. Read more how
+[License Management works](../../user/application_security/license_management/index.md).
NOTE: **Note:**
The Auto License Management stage will be skipped on licenses other than Ultimate.
@@ -414,8 +429,8 @@ Docker image and checks for potential security issues. Once the report is
created, it's uploaded as an artifact which you can later download and
check out.
-Any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/container_scanning.html).
+Any security warnings are also shown in the merge request widget. Read more how
+[Container Scanning works](../../user/application_security/container_scanning/index.md).
NOTE: **Note:**
The Auto Container Scanning stage will be skipped on licenses other than Ultimate.
@@ -427,13 +442,6 @@ This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
-CAUTION: **Caution:**
-Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
-This can cause confusion with Helm not detecting the change, and subsequent
-deploys with Auto DevOps can undo your changes. Also, if you change something
-and want to undo it by deploying again, Helm may not detect that anything changed
-in the first place, and thus not realize that it needs to re-apply the old config.
-
[Review Apps][review-app] are temporary application environments based on the
branch's code so developers, designers, QA, product managers, and other
reviewers can actually see and interact with code changes as part of the review
@@ -442,13 +450,31 @@ process. Auto Review Apps create a Review App for each branch.
Auto Review Apps will deploy your app to your Kubernetes cluster only. When no cluster
is available, no deployment will occur.
-The Review App will have a unique URL based on the project name, the branch
+The Review App will have a unique URL based on the project ID, the branch or tag
name, and a unique number, combined with the Auto DevOps base domain. For
-example, `user-project-branch-1234.example.com`. A link to the Review App shows
-up in the merge request widget for easy discovery. When the branch is deleted,
+example, `13083-review-project-branch-123456.example.com`. A link to the Review App shows
+up in the merge request widget for easy discovery. When the branch or tag is deleted,
for example after the merge request is merged, the Review App will automatically
be deleted.
+Review apps are deployed using the
+[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
+Helm. The app will be deployed into the [Kubernetes
+namespace](../../user/project/clusters/index.md#deployment-variables)
+for the environment.
+
+Since GitLab 11.4, a [local
+Tiller](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22036) is
+used. Previous versions of GitLab had a Tiller installed in the project
+namespace.
+
+CAUTION: **Caution:**
+Your apps should *not* be manipulated outside of Helm (using Kubernetes directly).
+This can cause confusion with Helm not detecting the change and subsequent
+deploys with Auto DevOps can undo your changes. Also, if you change something
+and want to undo it by deploying again, Helm may not detect that anything changed
+in the first place, and thus not realize that it needs to re-apply the old config.
+
### Auto DAST **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 10.4.
@@ -459,8 +485,8 @@ to perform an analysis on the current code and checks for potential security
issues. Once the report is created, it's uploaded as an artifact which you can
later download and check out.
-Any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/dast.html).
+Any security warnings are also shown in the merge request widget. Read how
+[DAST works](../../user/application_security/dast/index.md).
NOTE: **Note:**
The Auto DAST stage will be skipped on licenses other than Ultimate.
@@ -478,7 +504,7 @@ Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://h
```
Any performance differences between the source and target branches are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/browser_performance_testing.html).
+[shown in the merge request widget](../../user/project/merge_requests/browser_performance_testing.md).
### Auto Deploy
@@ -487,13 +513,6 @@ This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
-CAUTION: **Caution:**
-Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
-This can cause confusion with Helm not detecting the change, and subsequent
-deploys with Auto DevOps can undo your changes. Also, if you change something
-and want to undo it by deploying again, Helm may not detect that anything changed
-in the first place, and thus not realize that it needs to re-apply the old config.
-
After a branch or merge request is merged into the project's default branch (usually
`master`), Auto Deploy deploys the application to a `production` environment in
the Kubernetes cluster, with a namespace based on the project name and unique
@@ -506,17 +525,40 @@ enable them.
You can make use of [environment variables](#environment-variables) to automatically
scale your pod replicas.
-It's important to note that when a project is deployed to a Kubernetes cluster,
-it relies on a Docker image that has been pushed to the
-[GitLab Container Registry](../../user/project/container_registry.md). Kubernetes
-fetches this image and uses it to run the application. If the project is public,
-the image can be accessed by Kubernetes without any authentication, allowing us
-to have deployments more usable. If the project is private/internal, the
-Registry requires credentials to pull the image. Currently, this is addressed
-by providing `CI_JOB_TOKEN` as the password that can be used, but this token will
-no longer be valid as soon as the deployment job finishes. This means that
-Kubernetes can run the application, but in case it should be restarted or
-executed somewhere else, it cannot be accessed again.
+Apps are deployed using the
+[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
+Helm. The app will be deployed into the [Kubernetes
+namespace](../../user/project/clusters/index.md#deployment-variables)
+for the environment.
+
+Since GitLab 11.4, a [local
+Tiller](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22036) is
+used. Previous versions of GitLab had a Tiller installed in the project
+namespace.
+
+CAUTION: **Caution:**
+Your apps should *not* be manipulated outside of Helm (using Kubernetes directly).
+This can cause confusion with Helm not detecting the change and subsequent
+deploys with Auto DevOps can undo your changes. Also, if you change something
+and want to undo it by deploying again, Helm may not detect that anything changed
+in the first place, and thus not realize that it needs to re-apply the old config.
+
+> [Introduced][ce-19507] in GitLab 11.0.
+
+For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
+will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
+can be used for permanent access to the registry.
+
+If the GitLab Deploy Token cannot be found, `CI_REGISTRY_PASSWORD` is
+used. Note that `CI_REGISTRY_PASSWORD` is only valid during deployment.
+This means that Kubernetes will be able to successfully pull the
+container image during deployment but in cases where the image needs to
+be pulled again, e.g. after pod eviction, Kubernetes will fail to do so
+as it will be attempting to fetch the image using
+`CI_REGISTRY_PASSWORD`.
+
+NOTE: **Note:**
+When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
#### Migrations
@@ -540,25 +582,17 @@ Note that a post-install hook means that if any deploy succeeds,
If present, `DB_MIGRATE` will be run as a shell command within an application pod as
a helm pre-upgrade hook.
-For example, in a Rails application:
+For example, in a Rails application in an image built with
+[Herokuish](https://github.com/gliderlabs/herokuish):
-- `DB_INITIALIZE` can be set to `cd /app && RAILS_ENV=production
- bin/setup`
-- `DB_MIGRATE` can be set to `cd /app && RAILS_ENV=production bin/update`
+- `DB_INITIALIZE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:setup`
+- `DB_MIGRATE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:migrate`
NOTE: **Note:**
-The `/app` path is the directory of your project inside the docker image
-as [configured by
-Herokuish](https://github.com/gliderlabs/herokuish#paths)
-
-> [Introduced][ce-19507] in GitLab 11.0.
-
-For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
-will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
-can be used for permanent access to the registry.
-
-Note: **Note**
-When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
+Unless you have a `Dockerfile` in your repo, your image is built with
+Herokuish. You must prefix commands run in these images with `/bin/herokuish
+procfile exec` in order to replicate the the environment your application is
+run in.
### Auto Monitoring
@@ -632,15 +666,15 @@ repo or by specifying a project variable:
- **Bundled chart** - If your project has a `./chart` directory with a `Chart.yaml`
file in it, Auto DevOps will detect the chart and use it instead of the [default
- one](https://gitlab.com/charts/auto-deploy-app).
+ one](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
This can be a great way to control exactly how your application is deployed.
-- **Project variable** - Create a [project variable](../../ci/variables/README.md#variables)
+- **Project variable** - Create a [project variable](../../ci/variables/README.md#gitlab-cicd-environment-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use or create two project variables `AUTO_DEVOPS_CHART_REPOSITORY` with the URL of a custom chart repository and `AUTO_DEVOPS_CHART` with the path to the chart.
### Custom Helm chart per environment **[PREMIUM]**
-You can specify the use of a custom Helm chart per environment by scoping the environment variable
-to the desired environment. See [Limiting environment scopes of variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-variables-premium).
+You can specify the use of a custom Helm chart per environment by scoping the environment variable
+to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
### Customizing `.gitlab-ci.yml`
@@ -664,6 +698,21 @@ renaming `.staging` to `staging`. Then make sure to uncomment the `when` key of
the `production` job to turn it into a manual action instead of deploying
automatically.
+### Using components of Auto-DevOps
+
+If you only require a subset of the features offered by Auto-DevOps, you can include
+individual Auto-DevOps jobs into your own `.gitlab-ci.yml`.
+
+For example, to make use of [Auto Build](#auto-build), you can add the following to
+your `.gitlab-ci.yml`:
+
+```yaml
+include:
+ - template: Jobs/Build.gitlab-ci.yml
+```
+
+Consult the [Auto DevOps template] for information on available jobs.
+
### PostgreSQL database support
In order to support applications that require a database,
@@ -684,20 +733,22 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| **Variable** | **Description** |
| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-base-domain). By default, set automatically by the [Auto DevOps setting](#enablingdisabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
-| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
+| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
+| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" |
+| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, this variable can be used to set a username to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD) |
+| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
-| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
-| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
+| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1 |
+| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
-| `POSTGRES_VERSION` | The PostgreSQL version; defaults to `9.6.2` |
+| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
@@ -708,6 +759,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
+| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. |
@@ -716,10 +768,13 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
+| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
+| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
+| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. |
TIP: **Tip:**
Set up the replica variables using a
-[project variable](../../ci/variables/README.md#variables)
+[project variable](../../ci/variables/README.md#gitlab-cicd-environment-variables)
and scale your application by just redeploying it!
CAUTION: **Caution:**
@@ -863,7 +918,7 @@ you when you're ready to manually deploy to production.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/171)
in GitLab 11.0.
-A [canary environment](https://docs.gitlab.com/ee/user/project/canary_deployments.html) can be used
+A [canary environment](../../user/project/canary_deployments.md) can be used
before any changes are deployed to production.
If `CANARY_ENABLED` is defined in your project (e.g., set `CANARY_ENABLED` to
@@ -887,7 +942,7 @@ increasing the rollout up to 100%.
If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead
of the standard `production` job, 4 different
-[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph)
+[manual jobs](../../ci/pipelines.md#manual-actions-from-pipeline-graphs)
will be created:
1. `rollout 10%`
@@ -904,7 +959,7 @@ required to go from `10%` to `100%`, you can jump to whatever job you want.
You can also scale down by running a lower percentage job, just before hitting
`100%`. Once you get to `100%`, you cannot scale down, and you'd have to roll
back by redeploying the old version using the
-[rollback button](../../ci/environments.md#rolling-back-changes) in the
+[rollback button](../../ci/environments.md#retrying-and-rolling-back) in the
environment page.
Below, you can see how the pipeline will look if the rollout or staging
@@ -1008,6 +1063,9 @@ planned for a subsequent release.
buildpack](#custom-buildpacks).
- Auto Test may fail because of a mismatch between testing frameworks. In this
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
+- Auto Deploy will fail if GitLab can not create a Kubernetes namespace and
+ service account for your project. For help debugging this issue, see
+ [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs).
### Disable the banner instance wide
@@ -1040,3 +1098,7 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
[ee]: https://about.gitlab.com/pricing/
[ce-21955]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21955
[ce-19507]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19507
+
+## Development guides
+
+Configuring [GDK for Auto DevOps](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/auto_devops.md).
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 367e192b85a..cc83d20d65a 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -208,7 +208,7 @@ applications. In the rightmost column for the production environment, you can ma
application is running.
Right below, there is the
-[Deploy Board](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
+[Deploy Board](../../user/project/deploy_boards.md).
The squares represent pods in your Kubernetes cluster that are associated with
the given environment. Hovering above each square you can see the state of a
deployment and clicking a square will take you to the pod's logs page.
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index d7e1979217e..c7bede2d269 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -5,14 +5,20 @@ level: beginner
article_type: user guide
date: 2017-05-15
description: 'This article describes how to install Git on macOS, Ubuntu Linux and Windows.'
+type: howto
+last_updated: 2019-05-31
---
# Installing Git
-To begin contributing to GitLab projects
+To begin contributing to GitLab projects,
you will need to install the Git client on your computer.
+
This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
+Information on [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+is also available at the official Git website.
+
## Install Git on macOS using the Homebrew package manager
Although it is easy to use the version of Git shipped with macOS
@@ -21,7 +27,7 @@ we recommend installing it via Homebrew to get access to
an extensive selection of dependency managed libraries and applications.
If you are sure you don't need access to any additional development libraries
-or don't have approximately 15gb of available disk space for Xcode and Homebrew
+or don't have approximately 15gb of available disk space for Xcode and Homebrew,
use one of the aforementioned methods.
### Installing Xcode
@@ -40,11 +46,12 @@ for the official Homebrew installation instructions.
With Homebrew installed you are now ready to install Git.
Open a Terminal and enter in the following command:
-```bash
+```sh
brew install git
```
Congratulations you should now have Git installed via Homebrew.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Install Git on Ubuntu Linux
@@ -55,16 +62,30 @@ it is recommended to use the built in package manager to install Git.
Open a Terminal and enter in the following commands
to install the latest Git from the official Git maintained package archives:
-```bash
+```sh
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
```
Congratulations you should now have Git installed via the Ubuntu package manager.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Installing Git on Windows from the Git website
Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index 7707d56764e..841746cc5de 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -1,8 +1,12 @@
-# Git documentation
+---
+type: index
+---
+
+# Git
Git is a [free and open source](https://git-scm.com/about/free-and-open-source)
distributed version control system designed to handle everything from small to
-very large projects with speed and efficiency.
+large projects with speed and efficiency.
[GitLab](https://about.gitlab.com) is a Git-based fully integrated platform for
software development. Besides Git's functionalities, GitLab has a lot of
@@ -11,64 +15,71 @@ powerful [features](https://about.gitlab.com/features/) to enhance your
We've gathered some resources to help you to get the best from Git with GitLab.
+More information is also available on the [Git website](https://git-scm.com).
+
## Getting started
-- [Git concepts](../../university/training/user_training.md#git-concepts)
+The following resources will help you get started with Git:
+
+- [Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
+- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
- [How to install Git](how_to_install_git/index.md)
- [Start using Git on the command line](../../gitlab-basics/start-using-git.md)
- [Command Line basic commands](../../gitlab-basics/command-line-commands.md)
- [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf)
-- Commits
- - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
+- Commits:
+ - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
-**Third-party references:**
+### Concepts
-- [Getting Started - Git website](https://git-scm.com)
-- [Getting Started - Version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
-- [Getting Started - Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
-- [Getting Started - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
-- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
+The following are resources about version control concepts:
-### Concepts
+- [Git concepts](../../university/training/user_training.md#git-concepts)
+- [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
+- [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
+- [Git website topic about version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
+- [GitLab University presentation about Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
-- Article (2017-05-17): [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
-- Article (2016-05-11): [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
-- GLU Course (Presentation): [About Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
+## Git tips
-## Exploring Git
+The following resources may help you become more efficient at using Git:
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
## Troubleshooting Git
+If you have problems with Git, the following may help:
+
- [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md)
-- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
+- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques
## Branching strategies
-- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
-
-**Third-party references:**
-
- [Git Branching - Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)
- [Git Branching - Branching Workflows](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows)
+- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
## Advanced use
+The following are advanced topics for those who want to get the most out of Git:
+
- [Custom Git Hooks](../../administration/custom_hooks.md)
- [Git Attributes](../../user/project/git_attributes.md)
- Git Submodules: [Using Git submodules with GitLab CI](../../ci/git_submodules.md#using-git-submodules-with-gitlab-ci)
## API
-- [Gitignore templates](../../api/templates/gitignores.md)
+[Gitignore templates](../../api/templates/gitignores.md) API allow for
+Git-related queries from GitLab.
## Git LFS
+The following relate to Git Large File Storage:
+
- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [GitLab Git LFS documentation](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
-- [Git-Annex to Git-LFS migration guide](https://docs.gitlab.com/ee/workflow/lfs/migrate_from_git_annex_to_git_lfs.html)
-- Article (2015-08-13): [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
+- [Git-Annex to Git-LFS migration guide](../../workflow/lfs/migrate_from_git_annex_to_git_lfs.md)
+- [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index 8a8021dc36d..84201e11831 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -4,25 +4,30 @@ author_gitlab: Letme
level: intermediary
article_type: tutorial
date: 2017-05-15
+type: howto
+last_updated: 2019-05-31
---
# Numerous undo possibilities in Git
-## Introduction
-
In this tutorial, we will show you different ways of undoing your work in Git, for which
we will assume you have a basic working knowledge of. Check GitLab's
-[Git documentation](../index.md#git-documentation) for reference.
+[Git documentation](../index.md) for reference.
+
Also, we will only provide some general info of the commands, which is enough
-to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
+to get you started for the easy cases/examples, but for anything more advanced
+please refer to the [Git book](https://git-scm.com/book/en/v2).
We will explain a few different techniques to undo your changes based on the stage
of the change in your current development. Also, keep in mind that [nothing in
-Git is really deleted.][git-autoclean-ref]
+Git is really deleted][git-autoclean-ref].
+
This means that until Git automatically cleans detached commits (which cannot be
accessed by branch or tag) it will be possible to view them with `git reflog` command
and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
+## Introduction
+
This guide is organized depending on the [stage of development][git-basics]
where you want to undo your changes from and if they were shared with other developers
or not. Because Git is tracking changes a created or edited file is in the unstaged state
@@ -31,35 +36,41 @@ a file into the **staged** state, which is then committed (`git commit`) to your
local repository. After that, file can be shared with other developers (`git push`).
Here's what we'll cover in this tutorial:
- - [Undo local changes](#undo-local-changes) which were not pushed to remote repository
+- [Undo local changes](#undo-local-changes) which were not pushed to remote repository:
- - Before you commit, in both unstaged and staged state
- - After you committed
+ - Before you commit, in both unstaged and staged state.
+ - After you committed.
- - Undo changes after they are pushed to remote repository
+- Undo changes after they are pushed to remote repository:
- - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
- - [With history modification](#undo-remote-changes-with-modifying-history) (requires
- coordination with team and force pushes).
- - [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
- - [How to modify history](#how-modifying-history-is-done)
- - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
+ - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way).
+ - [With history modification](#undo-remote-changes-with-modifying-history) (requires
+ coordination with team and force pushes).
+ - [Use cases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable).
+ - [How to modify history](#how-modifying-history-is-done).
+ - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits).
### Branching strategy
[Git][git-official] is a de-centralized version control system, which means that beside regular
versioning of the whole repository, it has possibilities to exchange changes
-with other repositories. To avoid chaos with
+with other repositories.
+
+To avoid chaos with
[multiple sources of truth][git-distributed], various
development workflows have to be followed, and it depends on your internal
workflow how certain changes or commits can be undone or changed.
+
[GitLab Flow][gitlab-flow] provides a good
balance between developers clashing with each other while
developing the same feature and cooperating seamlessly, but it does not enable
joined development of the same feature by multiple developers by default.
+
When multiple developers develop the same feature on the same branch, clashing
with every synchronization is unavoidable, but a proper or chosen Git Workflow will
-prevent that anything is lost or out of sync when feature is complete. You can also
+prevent that anything is lost or out of sync when feature is complete.
+
+You can also
read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
to learn how to easily **do** things in Git.
@@ -97,19 +108,19 @@ no changes added to commit (use "git add" and/or "git commit -a")
At this point there are 3 options to undo the local changes you have:
-- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discarding local changes (permanently) to a file
+- Discarding local changes (permanently) to a file:
```shell
git checkout -- <file>
```
-- Discard all local changes to all files permanently
+- Discard all local changes to all files permanently:
```shell
git reset --hard
@@ -150,7 +161,7 @@ of the staging tree. You also have an option to discard all changes with
Lets start the example by editing a file, with your favorite editor, to change the
content and add it to staging
-```
+```sh
vim <file>
git add <file>
```
@@ -164,30 +175,30 @@ Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
- new file: <file>
+ new file: <file>
```
Now you have 4 options to undo your changes:
-- Unstage the file to current commit (HEAD)
+- Unstage the file to current commit (HEAD):
```shell
git reset HEAD <file>
```
-- Unstage everything - retain changes
+- Unstage everything - retain changes:
```shell
git reset
```
-- Discard all local changes, but save them for [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discard everything permanently
+- Discard everything permanently:
```shell
git reset --hard
@@ -206,7 +217,9 @@ your code, you'll have less options to troubleshoot your work.
Through the development process some of the previously committed changes do not
fit anymore in the end solution, or are source of the bugs. Once you find the
commit which triggered bug, or once you have a faulty commit, you can simply
-revert it with `git revert commit-id`. This command inverts (swaps) the additions and
+revert it with `git revert commit-id`.
+
+This command inverts (swaps) the additions and
deletions in that commit, so that it does not modify history. Retaining history
can be helpful in future to notice that some changes have been tried
unsuccessfully in the past.
@@ -225,19 +238,19 @@ through simple bisection process. You can read more about it [in official Git To
In our example we will end up with commit `B`, that introduced bug/error. We have
4 options on how to remove it (or part of it) from our repository.
-- Undo (swap additions and deletions) changes introduced by commit `B`.
+- Undo (swap additions and deletions) changes introduced by commit `B`:
```shell
git revert commit-B-id
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the staged state:
```shell
git checkout commit-B-id <file>
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state:
```shell
git reset commit-B-id <file>
@@ -246,7 +259,9 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
- There is one command we also must not forget: **creating a new branch**
from the point where changes are not applicable or where the development has hit a
dead end. For example you have done commits `A-B-C-D` on your feature-branch
- and then you figure `C` and `D` are wrong. At this point you either reset to `B`
+ and then you figure `C` and `D` are wrong.
+
+ At this point you either reset to `B`
and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
[change history](#with-history-modification)), or you simply checkout commit `B` create
@@ -269,13 +284,13 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
There is one command for history modification and that is `git rebase`. Command
provides interactive mode (`-i` flag) which enables you to:
- - **reword** commit messages (there is also `git commit --amend` for editing
- last commit message)
- - **edit** the commit content (changes introduced by commit) and message
- - **squash** multiple commits into a single one, and have a custom or aggregated
- commit message
- - **drop** commits - simply delete them
- - and few more options
+- **reword** commit messages (there is also `git commit --amend` for editing
+ last commit message).
+- **edit** the commit content (changes introduced by commit) and message.
+- **squash** multiple commits into a single one, and have a custom or aggregated
+ commit message.
+- **drop** commits - simply delete them.
+- and few more options.
Let us check few examples. Again there are commits `A-B-C-D` where you want to
delete commit `B`.
@@ -301,7 +316,7 @@ In case you want to modify something introduced in commit `B`.
- Command opens your favorite text editor where you write `edit` in front of commit
`B`, but leave default `pick` with all other commits. Save and exit the editor to
- perform a rebase
+ perform a rebase.
- Now do your edits and commit changes:
@@ -348,7 +363,9 @@ and then on end description of that action.
This topic is roughly same as modifying committed local changes without modifying
history. **It should be the preferred way of undoing changes on any remote repository
or public branch.** Keep in mind that branching is the best solution when you want
-to retain the history of faulty development, yet start anew from certain point. Branching
+to retain the history of faulty development, yet start anew from certain point.
+
+Branching
enables you to include the existing changes in new development (by merging) and
it also provides a clear timeline and development structure.
@@ -386,12 +403,14 @@ the cleanup of detached commits (happens automatically).
Modified history breaks the development chain of other developers, as changed
history does not have matching commits'ids. For that reason it should not
be used on any public branch or on branch that *might* be used by other
-developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
+developers. When contributing to big open source repositories (for example, [GitLab CE][gitlab-ce]),
it is acceptable to *squash* commits into a single one, to present
a nicer history of your contribution.
+
Keep in mind that this also removes the comments attached to certain commits
in merge requests, so if you need to retain traceability in GitLab, then
modifying history is not acceptable.
+
A feature-branch of a merge request is a public branch and might be used by
other developers, but project process and rules might allow or require
you to use `git rebase` (command that changes history) to reduce number of
@@ -400,8 +419,8 @@ GitLab). There is a `git merge --squash` command which does exactly that
(squashes commits on feature-branch to a single commit on target branch
at merge).
->**Note:**
-Never modify the commit history of `master` or shared branch
+NOTE: **Note:**
+Never modify the commit history of `master` or shared branch.
### How modifying history is done
@@ -436,7 +455,7 @@ pick <commit3-id> <commit3-commit-message>
# Note that empty commits are commented out
```
->**Note:**
+NOTE: **Note:**
It is important to notice that comment from the output clearly states that, if
you decide to abort, then do not just close your editor (as that will in-fact
modify history), but remove all uncommented lines and save.
@@ -470,7 +489,7 @@ tools that can use some of Git specifics to enable faster execution of common
tasks (which is exactly what removing sensitive information file is about).
An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
tools are faster because they do not provide a same fully feature set as `git filter-branch`
-does, but focus on specific usecases.
+does, but focus on specific use cases.
## Conclusion
@@ -480,6 +499,18 @@ depending on the stage of your process. Git also enables rewriting history, but
should be avoided as it might cause problems when multiple developers are
contributing to the same codebase.
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
<!-- Identifiers, in alphabetical order -->
[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md
index d1729d70158..417d91bf834 100644
--- a/doc/topics/git/troubleshooting_git.md
+++ b/doc/topics/git/troubleshooting_git.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Troubleshooting Git
Sometimes things don't work the way they should or as you might expect when
@@ -9,7 +13,7 @@ with Git.
'Broken pipe' errors can occur when attempting to push to a remote repository.
When pushing you will usually see:
-```
+```text
Write failed: Broken pipe
fatal: The remote end hung up unexpectedly
```
@@ -39,14 +43,15 @@ There's another option where you can prevent session timeouts by configuring
SSH 'keep alive' either on the client or on the server (if you are a GitLab
admin and have access to the server).
-NOTE: **Note:** configuring *both* the client and the server is unnecessary.
+NOTE: **Note:**
+Configuring *both* the client and the server is unnecessary.
**To configure SSH on the client side**:
-- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
- add or edit:
+- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
+ add or edit:
- ```
+ ```text
Host your-gitlab-instance-url.com
ServerAliveInterval 60
ServerAliveCountMax 5
@@ -58,7 +63,7 @@ NOTE: **Note:** configuring *both* the client and the server is unnecessary.
**To configure SSH on the server side**, edit `/etc/ssh/sshd_config` and add:
-```
+```text
ClientAliveInterval 60
ClientAliveCountMax 5
```
@@ -78,13 +83,40 @@ git push
In case you're running an older version of Git (< 2.9), consider upgrading
to >= 2.9 (see [Broken pipe when pushing to Git repository][Broken-Pipe]).
+## `ssh_exchange_identification` error
+
+Users may experience the following error when attempting to push or pull
+using Git over SSH:
+
+```text
+Please make sure you have the correct access rights
+and the repository exists.
+...
+ssh_exchange_identification: read: Connection reset by peer
+fatal: Could not read from remote repository.
+```
+
+This error usually indicates that SSH daemon's `MaxStartups` value is throttling
+SSH connections. This setting specifies the maximum number of unauthenticated
+connections to the SSH daemon. This affects users with proper authentication
+credentials (SSH keys) because every connection is 'unauthenticated' in the
+beginning. The default value is `10`.
+
+Increase `MaxStartups` by adding or modifying the value in `/etc/ssh/sshd_config`:
+
+```text
+MaxStartups 100
+```
+
+Restart SSHD for the change to take effect.
+
## Timeout during git push/pull
If pulling/pushing from/to your repository ends up taking more than 50 seconds,
-a timeout will be issued with a log of the number of operations performed
+a timeout will be issued with a log of the number of operations performed
and their respective timings, like the example below:
-```
+```text
remote: Running checks for branch: master
remote: Scanning for LFS objects... (153ms)
remote: Calculating new repository size... (cancelled after 729ms)
diff --git a/doc/university/README.md b/doc/university/README.md
index 3e7d02770e4..61ed72d25fb 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -4,12 +4,9 @@ comments: false
# GitLab University
-GitLab University is the best place to learn about **Version Control with Git and GitLab**.
+GitLab University is a great place to start when learning about version control with Git and GitLab, as well as other GitLab features.
-It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
-and [Blog Articles](https://about.gitlab.com/blog/).
-
-Would you like to contribute to GitLab University? Then please take a look at our contribution [process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) for more information.
+If you're looking for a GitLab subscription for _your university_, see our [Education](https://about.gitlab.com/solutions/education/) page.
## GitLab University Curriculum
@@ -54,10 +51,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control
-1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/user/project/import/bitbucket.html)
-1. [Migrating from GitHub](https://docs.gitlab.com/ee/user/project/import/github.html)
-1. [Migrating from SVN](https://docs.gitlab.com/ee/user/project/import/svn.html)
-1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/user/project/import/fogbugz.html)
+1. [Migrating from BitBucket/Stash](../user/project/import/bitbucket.md)
+1. [Migrating from GitHub](../user/project/import/github.md)
+1. [Migrating from SVN](../user/project/import/svn.md)
+1. [Migrating from Fogbugz](../user/project/import/fogbugz.md)
#### 1.6. GitLab Inc.
@@ -95,7 +92,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
-1. [GitLab Pages Documentation](https://docs.gitlab.com/ce/user/project/pages/)
+1. [GitLab Pages Documentation](../user/project/pages/index.md)
#### 2.2. GitLab Issues
@@ -134,7 +131,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA)
1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/)
1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)
-1. [GitLab Flow Documentation](https://docs.gitlab.com/ee/workflow/gitlab_flow.html)
+1. [GitLab Flow Documentation](../workflow/gitlab_flow.md)
#### 2.5. GitLab Comparisons
@@ -194,10 +191,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.9. Integrations
1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
-1. [How to Integrate Jira with GitLab](https://docs.gitlab.com/ce/user/project/integrations/jira.html)
-1. [How to Integrate Jenkins with GitLab](https://docs.gitlab.com/ee/integration/jenkins.html)
-1. [How to Integrate Bamboo with GitLab](https://docs.gitlab.com/ce/user/project/integrations/bamboo.html)
-1. [How to Integrate Slack with GitLab](https://docs.gitlab.com/ce/user/project/integrations/slack.html)
+1. [How to Integrate Jira with GitLab](../user/project/integrations/jira.md)
+1. [How to Integrate Jenkins with GitLab](../integration/jenkins.md)
+1. [How to Integrate Bamboo with GitLab](../user/project/integrations/bamboo.md)
+1. [How to Integrate Slack with GitLab](../user/project/integrations/slack.md)
1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
1. [Getting Started with GitLab and Shippable CI](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/)
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 254e234a22c..f15b0107de5 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -41,7 +41,7 @@ Objects (usually binary and large) created by a build process. These can include
### Atlassian
-A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Confluence, Bamboo.
+A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
### Audit Log
@@ -62,7 +62,7 @@ Entry level [subscription](https://about.gitlab.com/pricing/) for GitLab EE curr
### Bitbucket
-Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_bitbucket.html) from BitBucket to a GitLab instance.
+Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](../../user/project/import/bitbucket.md) from BitBucket to a GitLab instance.
### Branch
@@ -70,10 +70,10 @@ A branch is a parallel version of a repository. This allows you to work on the r
### Branded Login
-Having your own logo on [your GitLab instance login page](https://docs.gitlab.com/ee/customization/branded_login_page.html) instead of the GitLab logo.
+Having your own logo on [your GitLab instance login page](../../customization/branded_login_page.md) instead of the GitLab logo.
### Job triggers (Build Triggers)
-These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](https://docs.gitlab.com/ce/ci/triggers/README.html) job triggers.
+These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](../../ci/triggers/README.md) job triggers.
### CEPH
@@ -149,7 +149,7 @@ As in "specify [dependencies](https://gitlab.com/gitlab-org/gitlab-ce/issues/147
### Deploy Keys
-A [SSH key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html)stored on your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code.
+A [SSH key](../../gitlab-basics/create-your-ssh-keys.md)stored on your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code.
### Developer
@@ -169,7 +169,7 @@ A folder used for storing multiple files.
### Docker Container Registry
-A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of [GitLab projects](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.
+A [feature](../../user/project/container_registry.md) of [GitLab projects](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.
### Dynamic Environment (review apps)
@@ -191,7 +191,7 @@ First Byte (sometimes referred to as time to first byte or [TTFB](https://en.wik
### Fork
-Your [own copy](https://docs.gitlab.com/ce/workflow/forking_workflow.html) of a repository that allows you to make changes to the repository without affecting the original.
+Your [own copy](../../workflow/forking_workflow.md) of a repository that allows you to make changes to the repository without affecting the original.
### Funnel, or: TOFU, MOFU, BOFU
@@ -221,7 +221,7 @@ A single-tenant solution that provides GitLab CE or EE as a managed service. Git
### GitHub
-A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. It offers free public repos, private repos and enterprise services are paid. Read about [importing a project](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_github.html) from GitHub to GitLab.
+A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. It offers free public repos, private repos and enterprise services are paid. Read about [importing a project](../../workflow/importing/import_projects_from_github.md) from GitHub to GitLab.
### GitLab CE
@@ -241,7 +241,7 @@ Our free SaaS for public and private repositories.
### GitLab Geo
-Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](https://docs.gitlab.com/ee/administration/geo/replication/index.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
+Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](../../administration/geo/replication/index.md) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
### GitLab High Availability
@@ -299,15 +299,15 @@ An [application layer protocol](http://www.irchelp.org/) that facilitates commun
### Issue Tracker
-A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) used to manage, organize, and maintain a list of issues, making it easier for an organization to manage.
+A [tool](../../integration/external-issue-tracker.md) used to manage, organize, and maintain a list of issues, making it easier for an organization to manage.
### Jenkins
-An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
+An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](../../integration/jenkins.md).
### Jira
-Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](https://docs.gitlab.com/ee/project_services/jira.html) to interact with JIRA Core either using an on-premise instance or the SaaS solution that Atlassian offers.
+Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](../../project_services/jira.md) to interact with JIRA Core either using an on-premise instance or the SaaS solution that Atlassian offers.
### JUnit
@@ -323,7 +323,7 @@ An open source container cluster manager originally designed by Google. It's bas
### Labels
-An [identifier](https://docs.gitlab.com/ce/user/project/labels.html) to describe a group of one or more specific file revisions.
+An [identifier](../../user/project/labels.md) to describe a group of one or more specific file revisions.
### Lightweight Directory Access Protocol (LDAP)
@@ -331,7 +331,7 @@ An [identifier](https://docs.gitlab.com/ce/user/project/labels.html) to describe
### LDAP User Authentication
-GitLab [integrates](https://docs.gitlab.com/ce/administration/auth/ldap.html) with LDAP to support user authentication. This enables GitLab to sign in people from an LDAP server (i.e., allowing people whose names are on the electronic user directory server to be able to use their LDAP accounts to login.)
+GitLab [integrates](../../administration/auth/ldap.md) with LDAP to support user authentication. This enables GitLab to sign in people from an LDAP server (i.e., allowing people whose names are on the electronic user directory server to be able to use their LDAP accounts to login.)
### LDAP Group Sync
@@ -381,9 +381,9 @@ Takes changes from one branch, and [applies them](https://git-scm.com/docs/git-m
[Arises](https://about.gitlab.com/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) when a merge can't be performed cleanly between two versions of the same file.
-#### Merge Request
+#### Merge Request (MR)
-[Takes changes](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html) from one branch, and applies them into another branch.
+[Takes changes](../../gitlab-basics/add-merge-request.md) from one branch, and applies them into another branch.
### Meteor
@@ -395,11 +395,11 @@ Allow you to [organize issues](../../user/project/milestones/index.md) and merge
### Mirror Repositories
-A project that is set up to automatically have its branches, tags, and commits [updated from an upstream repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html). This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and activity using the familiar GitLab interface.
+A project that is set up to automatically have its branches, tags, and commits [updated from an upstream repository](../../workflow/repository_mirroring.md). This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and activity using the familiar GitLab interface.
### MIT License
-A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this [license](https://docs.gitlab.com/ce/development/licensing.html). This means you can download the code, modify it as you want, and even build a new commercial product using the underlying code and it's not illegal. The only condition is that there is no form of warranty provided by GitLab so whatever happens when you use the code is your own problem.
+A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this [license](../../development/licensing.md). This means you can download the code, modify it as you want, and even build a new commercial product using the underlying code and it's not illegal. The only condition is that there is no form of warranty provided by GitLab so whatever happens when you use the code is your own problem.
### Mondo Rescue
@@ -427,7 +427,7 @@ A web [server](https://www.nginx.com/resources/wiki/) (pronounced "engine x"). [
### OAuth
-An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](https://docs.gitlab.com/ce/integration/oauth_provider.html) an OAuth2 authentication service provider.
+An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](../../integration/oauth_provider.md) an OAuth2 authentication service provider.
### Omnibus Packages
@@ -479,11 +479,11 @@ An [object-relational](https://en.wikipedia.org/wiki/PostgreSQL) database. Toute
### Protected Branches
-A [feature](https://docs.gitlab.com/ce/user/project/protected_branches.html) that protects branches from unauthorized pushes, force pushing or deletion.
+A [feature](../../user/project/protected_branches.md) that protects branches from unauthorized pushes, force pushing or deletion.
### Protected Tags
-A [feature](https://docs.gitlab.com/ce/user/project/protected_tags.html) that protects tags from unauthorized creation, update or deletion
+A [feature](../../user/project/protected_tags.md) that protects tags from unauthorized creation, update or deletion
### Pull
@@ -547,7 +547,7 @@ Actual build machines/containers that [run and execute tests](https://gitlab.com
### Sidekiq
-The background job processor GitLab [uses](https://docs.gitlab.com/ce/administration/troubleshooting/sidekiq.html) to asynchronously run tasks.
+The background job processor GitLab [uses](../../administration/troubleshooting/sidekiq.md) to asynchronously run tasks.
### Software as a service (SaaS)
@@ -567,7 +567,7 @@ The board used to track the status and progress of each of the sprint backlog it
### Shell
-Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell.
+Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git](../../gitlab-basics/start-using-git.md) and make changes to GitLab projects in your shell. You [use git](../../gitlab-basics/start-using-git.md) and make changes to GitLab projects in your shell.
### Shell command runner
@@ -577,7 +577,7 @@ The tenant purchases their own copy of the software and the software can be cust
### Slack
-Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](https://docs.gitlab.com/ce/project_services/slack.html) to trigger push, issue, and merge request events among others.
+Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](../../project_services/slack.md) to trigger push, issue, and merge request events among others.
### Slash commands
@@ -595,7 +595,7 @@ Program code as typed by a computer programmer (i.e. it has not yet been compile
### SSH Key
-A unique identifier of a computer. It is used to identify computers without the need for a password (e.g., On GitLab I have [added the ssh key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html) of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added.)
+A unique identifier of a computer. It is used to identify computers without the need for a password (e.g., On GitLab I have [added the ssh key](../../gitlab-basics/create-your-ssh-keys.md) of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added.)
### Single Sign On (SSO)
@@ -627,11 +627,11 @@ A program that allows you to perform superuser/administrator actions on Unix Ope
### Subversion (SVN)
-An open source version control system. Read about [migrating from SVN](https://docs.gitlab.com/ce/workflow/importing/migrating_from_svn.html) to GitLab using SubGit.
+An open source version control system. Read about [migrating from SVN](../../workflow/importing/migrating_from_svn.md) to GitLab using SubGit.
### Tag
-[Represents](https://docs.gitlab.com/ce/api/tags.html) a version of a particular branch at a moment in time.
+[Represents](../../api/tags.md) a version of a particular branch at a moment in time.
### Tenancy
@@ -673,7 +673,7 @@ Version control is a system that records changes to a file or set of files over
### Virtual Private Cloud (VPC)
-A [VPC](https://docs.gitlab.com/ce/university/glossary/README.html#virtual-private-cloud-vpc) is an on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [set up High Availability](https://docs.gitlab.com/ce/university/high-availability/aws/).
+A [VPC](#virtual-private-cloud-vpc) is an on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [set up High Availability](../../install/aws/index.md).
### Virtual private server (VPS)
@@ -689,7 +689,7 @@ A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building softwa
### Webhooks
-A way for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
+A way for an app to [provide](../../user/project/integrations/webhooks.md) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](../../administration/custom_hooks.md) for when webhooks are insufficient.
### Wiki
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index 4095f71e501..fa04e988042 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -3,7 +3,7 @@ comments: false
---
> **Note**: We **do not** recommend using the AWS Elastic File System (EFS), as it can result
-in [significantly degraded performance](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/administration/high_availability/nfs.md#aws-elastic-file-system).
+in [significantly degraded performance](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
# High Availability on AWS
@@ -83,7 +83,7 @@ our newly created VPC.
### Internet Gateway
Now still on the same dashboard head over to Internet Gateways and
-create a new one. After its created pres on the `Attach to VPC` button and
+create a new one. After its created press on the `Attach to VPC` button and
select our VPC.
![Internet Gateway](img/ig.png)
@@ -182,7 +182,7 @@ Another option is to build a simple NFS server using a vanilla Linux server back
by AWS Elastic Block Storage (EBS).
> **Note:** GitLab does not recommend using AWS Elastic File System (EFS). See
- details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#aws-elastic-file-system)
+ details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
***
@@ -390,5 +390,5 @@ some redundancy options but it might also imply Geographic replication.
There is a lot of ground yet to cover so have a read through these other
resources and feel free to open an issue to request additional material.
-- [GitLab High Availability](http://docs.gitlab.com/ce/administration/high_availability/README.html#sts=High%20Availability)
-- [GitLab Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html)
+- [GitLab High Availability](../../../administration/high_availability/README.md)
+- [GitLab Geo](../../../administration/geo/replication/index.md)
diff --git a/doc/university/high-availability/aws/img/elastic-file-system.png b/doc/university/high-availability/aws/img/elastic-file-system.png
deleted file mode 100644
index 5bcfb8d0588..00000000000
--- a/doc/university/high-availability/aws/img/elastic-file-system.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/reference-arch2.png b/doc/university/high-availability/aws/img/reference-arch2.png
index a9cb6663103..0f8790d0f74 100644
--- a/doc/university/high-availability/aws/img/reference-arch2.png
+++ b/doc/university/high-availability/aws/img/reference-arch2.png
Binary files differ
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index feb90ae9bad..c8ade54a77c 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -61,28 +61,27 @@ Sometimes we need to upgrade customers from old versions of GitLab to latest, so
- Users
- Groups
- Projects
- - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - [Backup using our Backup rake task](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system)
- [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md)
- [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md)
- [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md)
- [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md)
- - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- - [Perform the MySQL to PostgreSQL migration to convert your backup](https://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
+ - [Perform the MySQL to PostgreSQL migration to convert your backup](../../update/mysql_to_postgresql.md)
- [Upgrade to Omnibus 7.14](https://docs.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
- - [Restore backup using our Restore rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
+ - [Restore backup using our Restore rake task](../../raketasks/backup_restore.md#restore)
- [Upgrade to latest EE](https://about.gitlab.com/downloads-ee)
- (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
-- Perform a downgrade from [EE to CE](https://docs.gitlab.com/ee/downgrade_ee_to_ce/README.html)
+- Perform a downgrade from [EE to CE](../../downgrade_ee_to_ce/README.md)
#### Start to learn about some of the integrations that we support
Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved
- Learn about our Integrations (specially, not only):
- - [LDAP](https://docs.gitlab.com/ee/integration/ldap.html)
- - [JIRA](https://docs.gitlab.com/ee/project_services/jira.html)
- - [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html)
- - [SAML](https://docs.gitlab.com/ce/integration/saml.html)
+ - [LDAP](../../integration/ldap.md)
+ - [JIRA](../../project_services/jira.md)
+ - [Jenkins](../../integration/jenkins.md)
+ - [SAML](../../integration/saml.md)
#### Goals
@@ -94,8 +93,8 @@ Our integrations add great value to GitLab. User questions often relate to integ
#### Understand the gathering of diagnostics for GitLab instances
- Learn about the GitLab checks that are available
- - [Environment Information and maintenance checks](https://docs.gitlab.com/ce/raketasks/maintenance.html)
- - [GitLab check](https://docs.gitlab.com/ce/raketasks/check.html)
+ - [Environment Information and maintenance checks](../../raketasks/maintenance.md)
+ - [GitLab check](../../raketasks/check.md)
- Omnibus commands
- [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
- [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
@@ -170,11 +169,11 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
-- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
-- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
-- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
-- Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html)
-- Create your first [GitLab Page](https://docs.gitlab.com/ce/administration/pages/)
+- Set up and try [Git LFS](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- Get to know the [GitLab API](../../api/README.md), its capabilities and shortcomings
+- Learn how to [migrate from SVN to Git](../../user/project/import/svn.md)
+- Set up [GitLab CI](../../ci/quick_start/README.md)
+- Create your first [GitLab Page](../../administration/pages/index.md)
- Get to know the GitLab Codebase by reading through the source code:
- Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index da36a3218e5..c926f0b4888 100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
@@ -8,13 +8,13 @@ comments: false
## Unstage
-- To remove files from stage use reset HEAD. Where HEAD is the last commit of the current branch.
+- To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This will unstage the file but maintain the modifications.
```bash
git reset HEAD <file>
```
-- This will unstage the file but maintain the modifications. To revert the file back to the state it was in before the changes we can use:
+- To revert the file back to the state it was in before the changes we can use:
```bash
git checkout -- <file>
diff --git a/doc/update/10.0-ce-to-ee.md b/doc/update/10.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.1-ce-to-ee.md b/doc/update/10.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.2-ce-to-ee.md b/doc/update/10.2-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.2-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.3-ce-to-ee.md b/doc/update/10.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.4-ce-to-ee.md b/doc/update/10.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.5-ce-to-ee.md b/doc/update/10.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.6-ce-to-ee.md b/doc/update/10.6-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.6-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.7-ce-to-ee.md b/doc/update/10.7-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.7-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/10.8-ce-to-ee.md b/doc/update/10.8-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/10.8-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.0-ce-to-ee.md b/doc/update/11.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.1-ce-to-ee.md b/doc/update/11.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.2-ce-to-ee.md b/doc/update/11.2-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.2-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.3-ce-to-ee.md b/doc/update/11.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.4-ce-to-ee.md b/doc/update/11.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.5-ce-to-ee.md b/doc/update/11.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.6-ce-to-ee.md b/doc/update/11.6-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.6-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.7-ce-to-ee.md b/doc/update/11.7-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.7-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/11.8-ce-to-ee.md b/doc/update/11.8-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/11.8-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.0-ce-to-ee.md b/doc/update/6.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.1-ce-to-ee.md b/doc/update/6.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.2-ce-to-ee.md b/doc/update/6.2-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.2-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.3-ce-to-ee.md b/doc/update/6.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.4-ce-to-ee.md b/doc/update/6.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.5-ce-to-ee.md b/doc/update/6.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.6-ce-to-ee.md b/doc/update/6.6-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.6-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.7-ce-to-ee.md b/doc/update/6.7-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.7-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.8-ce-to-ee.md b/doc/update/6.8-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.8-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/6.9-ce-to-ee.md b/doc/update/6.9-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/6.9-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.0-ce-to-ee.md b/doc/update/7.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.1-ce-to-ee.md b/doc/update/7.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.10-ce-to-ee.md b/doc/update/7.10-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.10-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.11-ce-to-ee.md b/doc/update/7.11-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.11-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.12-ce-to-ee.md b/doc/update/7.12-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.12-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.13-ce-to-ee.md b/doc/update/7.13-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.13-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.14-ce-to-ee.md b/doc/update/7.14-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.14-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.3-ce-to-ee.md b/doc/update/7.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.4-ce-to-ee.md b/doc/update/7.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.5-ce-to-ee.md b/doc/update/7.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.6-ce-to-ee.md b/doc/update/7.6-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.6-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.7-ce-to-ee.md b/doc/update/7.7-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.7-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.8-ce-to-ee.md b/doc/update/7.8-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.8-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/7.9-ce-to-ee.md b/doc/update/7.9-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/7.9-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.0-ce-to-ee.md b/doc/update/8.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.1-ce-to-ee.md b/doc/update/8.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.10-ce-to-ee.md b/doc/update/8.10-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.10-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.11-ce-to-ee.md b/doc/update/8.11-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.11-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.12-ce-to-ee.md b/doc/update/8.12-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.12-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.13-ce-to-ee.md b/doc/update/8.13-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.13-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.14-ce-to-ee.md b/doc/update/8.14-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.14-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.15-ce-to-ee.md b/doc/update/8.15-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.15-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.16-ce-to-ee.md b/doc/update/8.16-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.16-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.17-ce-to-ee.md b/doc/update/8.17-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.17-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.2-ce-to-ee.md b/doc/update/8.2-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.2-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.3-ce-to-ee.md b/doc/update/8.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.4-ce-to-ee.md b/doc/update/8.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.5-ce-to-ee.md b/doc/update/8.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.6-ce-to-ee.md b/doc/update/8.6-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.6-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.7-ce-to-ee.md b/doc/update/8.7-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.7-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.8-ce-to-ee.md b/doc/update/8.8-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.8-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/8.9-ce-to-ee.md b/doc/update/8.9-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/8.9-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.0-ce-to-ee.md b/doc/update/9.0-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.0-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.1-ce-to-ee.md b/doc/update/9.1-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.1-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.2-ce-to-ee.md b/doc/update/9.2-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.2-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.3-ce-to-ee.md b/doc/update/9.3-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.3-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.4-ce-to-ee.md b/doc/update/9.4-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.4-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/9.5-ce-to-ee.md b/doc/update/9.5-ce-to-ee.md
new file mode 100644
index 00000000000..10c9e21fa81
--- /dev/null
+++ b/doc/update/9.5-ce-to-ee.md
@@ -0,0 +1,5 @@
+---
+redirect_to: upgrading_from_ce_to_ee.md
+---
+
+This document was moved to [another location](upgrading_from_ce_to_ee.md).
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 350072186ee..b83abcd36f7 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -1,31 +1,74 @@
---
-last_updated: 2018-02-07
+last_updated: 2019-03-27
---
# Migrating from MySQL to PostgreSQL
-> **Note:** This guide assumes you have a working GitLab instance with
-> MySQL and want to migrate to bundled PostgreSQL database.
+This guide documents how to take a working GitLab instance that uses MySQL and
+migrate it to a PostgreSQL database.
-## Omnibus installation
+## Requirements
-### Prerequisites
+[pgloader](http://pgloader.io) 3.4.1+ is required.
-First, we'll need to enable the bundled PostgreSQL database with up-to-date
-schema. Next, we'll use [pgloader](http://pgloader.io) to migrate the data
-from the old MySQL database to the new PostgreSQL one.
+You can install it directly from your distribution, for example in
+Debian/Ubuntu:
-Here's what you'll need to have installed:
+1. Search for the version:
-- pgloader 3.4.1+
-- Omnibus GitLab
-- MySQL
+ ```bash
+ apt-cache madison pgloader
+ ```
-### Enable bundled PostgreSQL database
+1. If the version is 3.4.1+, install it with:
+
+ ```bash
+ sudo apt-get install pgloader
+ ```
+
+ If your distribution's version is too old, use PostgreSQL's repository:
+
+ ```bash
+ # Add repository
+ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+
+ # Add key
+ sudo apt-get install wget ca-certificates
+ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+
+ # Install package
+ sudo apt-get update
+ sudo apt-get install pgloader
+ ```
+
+For other distributions, follow the instructions in PostgreSQL's
+[download page](https://www.postgresql.org/download/) to add their repository
+and then install `pgloader`.
+
+If you are migrating to a Docker based installation, you will need to install
+pgloader within the container as it is not included in the container image.
+
+1. Start a shell session in the context of the running container:
+
+ ``` bash
+ docker exec -it gitlab bash
+ ```
+
+1. Install pgloader:
+
+ ``` bash
+ apt-get update
+ apt-get -y install pgloader
+ ```
+
+## Omnibus GitLab installations
+
+For [Omnibus GitLab packages](https://about.gitlab.com/install/), you'll first
+need to enable the bundled PostgreSQL:
1. Stop GitLab:
- ``` bash
+ ```bash
sudo gitlab-ctl stop
```
@@ -40,39 +83,34 @@ Here's what you'll need to have installed:
and alike. You could just comment all of them out so that we'll just use
the defaults.
-1. [Reconfigure GitLab] for the changes to take effect:
-
- ``` bash
- sudo gitlab-ctl reconfigure
- ```
-
+1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
+ for the changes to take effect.
1. Start Unicorn and PostgreSQL so that we can prepare the schema:
- ``` bash
+ ```bash
sudo gitlab-ctl start unicorn
sudo gitlab-ctl start postgresql
```
1. Run the following commands to prepare the schema:
- ``` bash
+ ```bash
sudo gitlab-rake db:create db:migrate
```
1. Stop Unicorn to prevent other database access from interfering with the loading of data:
- ``` bash
+ ```bash
sudo gitlab-ctl stop unicorn
```
After these steps, you'll have a fresh PostgreSQL database with up-to-date schema.
-### Migrate data from MySQL to PostgreSQL
-
-Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
+Next, we'll use `pgloader` to migrate the data from the old MySQL database to the
+new PostgreSQL one:
1. Save the following snippet in a `commands.load` file, and edit with your
- database `username`, `password` and `host`:
+ MySQL database `username`, `password` and `host`:
```
LOAD DATABASE
@@ -90,7 +128,7 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
1. Start the migration:
- ``` bash
+ ```bash
sudo -u gitlab-psql pgloader commands.load
```
@@ -117,170 +155,140 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
Total import time 1894 1894 0 12.497s
```
- If there is no output for more than 30 minutes, it's possible pgloader encountered an error. See
- the [troubleshooting guide](#Troubleshooting) for more details.
+ If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See
+ the [troubleshooting guide](#troubleshooting) for more details.
1. Start GitLab:
- ``` bash
+ ```bash
sudo gitlab-ctl start
```
-Now, you can verify that everything worked by visiting GitLab.
-
-### Troubleshooting
-
-#### Permissions
-
-Note that the PostgreSQL user that you use for the above MUST have **superuser** privileges. Otherwise, you may see
-a similar message to the following:
-
-```
-debugger invoked on a CL-POSTGRES-ERROR:INSUFFICIENT-PRIVILEGE in thread
- #<THREAD "lparallel" RUNNING {10078A3513}>:
- Database error 42501: permission denied: "RI_ConstraintTrigger_a_20937" is a system trigger
- QUERY: ALTER TABLE ci_builds DISABLE TRIGGER ALL;
- 2017-08-23T00:36:56.782000Z ERROR Database error 42501: permission denied: "RI_ConstraintTrigger_c_20864" is a system trigger
- QUERY: ALTER TABLE approver_groups DISABLE TRIGGER ALL;
-```
-
-#### Experiencing 500 errors after the migration
-
-If you experience 500 errors after the migration, try to clear the cache:
-
-``` bash
-sudo gitlab-rake cache:clear
-```
-
-[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
-
-## Source installation
+You can now verify that everything works as expected by visiting GitLab.
-### Prerequisites
+## Source installations
-#### Install PostgreSQL and create database
+For installations from source that use MySQL, you'll first need to
+[install PostgreSQL and create a database](../install/installation.md#6-database).
-See [installation guide](../install/installation.md#6-database).
-
-#### Install [pgloader](http://pgloader.io) 3.4.1+
-
-Install directly from your distro:
-``` bash
-sudo apt-get install pgloader
-```
-
-If this version is too old, use PostgreSQL's repository:
-``` bash
-# add repository
-sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
-
-# add key
-sudo apt-get install wget ca-certificates
-wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
-
-# install package
-sudo apt-get update
-sudo apt-get install pgloader
-```
-
-### Enable bundled PostgreSQL database
+After the database is created, go on with the following steps:
1. Stop GitLab:
- ``` bash
- sudo service gitlab stop
- ```
+ ```bash
+ sudo service gitlab stop
+ ```
1. Switch database from MySQL to PostgreSQL
- ``` bash
- cd /home/git/gitlab
- sudo -u git mv config/database.yml config/database.yml.bak
- sudo -u git cp config/database.yml.postgresql config/database.yml
- sudo -u git -H chmod o-rwx config/database.yml
- ```
+ ```bash
+ cd /home/git/gitlab
+ sudo -u git mv config/database.yml config/database.yml.bak
+ sudo -u git cp config/database.yml.postgresql config/database.yml
+ sudo -u git -H chmod o-rwx config/database.yml
+ ```
+
1. Install Gems related to Postgresql
- ``` bash
- sudo -u git -H rm .bundle/config
- sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
- ```
+ ```bash
+ sudo -u git -H rm .bundle/config
+ sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+ ```
1. Run the following commands to prepare the schema:
- ``` bash
- sudo -u git -H bundle exec rake db:create db:migrate RAILS_ENV=production
- ```
+ ```bash
+ sudo -u git -H bundle exec rake db:create db:migrate RAILS_ENV=production
+ ```
After these steps, you'll have a fresh PostgreSQL database with up-to-date schema.
-### Migrate data from MySQL to PostgreSQL
-
-Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
+Next, we'll use `pgloader` to migrate the data from the old MySQL database to the
+new PostgreSQL one:
1. Save the following snippet in a `commands.load` file, and edit with your
MySQL `username`, `password` and `host`:
- ```
- LOAD DATABASE
- FROM mysql://username:password@host/gitlabhq_production
- INTO postgresql://postgres@unix://var/run/postgresql:/gitlabhq_production
+ ```
+ LOAD DATABASE
+ FROM mysql://username:password@host/gitlabhq_production
+ INTO postgresql://postgres@unix://var/run/postgresql:/gitlabhq_production
- WITH include no drop, truncate, disable triggers, create no tables,
- create no indexes, preserve index names, no foreign keys,
- data only
+ WITH include no drop, truncate, disable triggers, create no tables,
+ create no indexes, preserve index names, no foreign keys,
+ data only
- ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public'
+ ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public'
- ;
- ```
+ ;
+ ```
1. Start the migration:
- ``` bash
- sudo -u postgres pgloader commands.load
- ```
+ ```bash
+ sudo -u postgres pgloader commands.load
+ ```
1. Once the migration finishes, you should see a summary table that looks like
the following:
- ```
- table name read imported errors total time
- ----------------------------------------------- --------- --------- --------- --------------
- fetch meta data 119 119 0 0.388s
- Truncate 119 119 0 1.134s
- ----------------------------------------------- --------- --------- --------- --------------
- public.abuse_reports 0 0 0 0.490s
- public.appearances 0 0 0 0.488s
- .
- .
- .
- public.web_hook_logs 0 0 0 1.080s
- ----------------------------------------------- --------- --------- --------- --------------
- COPY Threads Completion 4 4 0 2.008s
- Reset Sequences 113 113 0 0.304s
- Install Comments 0 0 0 0.000s
- ----------------------------------------------- --------- --------- --------- --------------
- Total import time 1894 1894 0 12.497s
- ```
-
- If there is no output for more than 30 minutes, it's possible pgloader encountered an error. See
- the [troubleshooting guide](#Troubleshooting) for more details.
+ ```
+ table name read imported errors total time
+ ----------------------------------------------- --------- --------- --------- --------------
+ fetch meta data 119 119 0 0.388s
+ Truncate 119 119 0 1.134s
+ ----------------------------------------------- --------- --------- --------- --------------
+ public.abuse_reports 0 0 0 0.490s
+ public.appearances 0 0 0 0.488s
+ .
+ .
+ .
+ public.web_hook_logs 0 0 0 1.080s
+ ----------------------------------------------- --------- --------- --------- --------------
+ COPY Threads Completion 4 4 0 2.008s
+ Reset Sequences 113 113 0 0.304s
+ Install Comments 0 0 0 0.000s
+ ----------------------------------------------- --------- --------- --------- --------------
+ Total import time 1894 1894 0 12.497s
+ ```
+
+ If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See
+ the [troubleshooting guide](#troubleshooting) for more details.
1. Start GitLab:
- ``` bash
- sudo service gitlab start
- ```
+ ```bash
+ sudo service gitlab start
+ ```
-Now, you can verify that everything worked by visiting GitLab.
+You can now verify that everything works as expected by visiting GitLab.
-### Troubleshooting
+## Troubleshooting
-#### Experiencing 500 errors after the migration
+Sometimes, you might encounter some errors during or after the migration.
+
+### Database error permission denied
+
+The PostgreSQL user that you use for the migration MUST have **superuser** privileges.
+Otherwise, you may see a similar message to the following:
+
+```
+debugger invoked on a CL-POSTGRES-ERROR:INSUFFICIENT-PRIVILEGE in thread
+ #<THREAD "lparallel" RUNNING {10078A3513}>:
+ Database error 42501: permission denied: "RI_ConstraintTrigger_a_20937" is a system trigger
+ QUERY: ALTER TABLE ci_builds DISABLE TRIGGER ALL;
+ 2017-08-23T00:36:56.782000Z ERROR Database error 42501: permission denied: "RI_ConstraintTrigger_c_20864" is a system trigger
+ QUERY: ALTER TABLE approver_groups DISABLE TRIGGER ALL;
+```
+
+### Experiencing 500 errors after the migration
If you experience 500 errors after the migration, try to clear the cache:
-``` bash
+```bash
+# Omnibus GitLab
+sudo gitlab-rake cache:clear
+
+# Installations from source
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 2e8380aa5d8..f2df4277ca8 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -67,7 +67,7 @@ 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
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
```
### 4. Update gitlab-workhorse to the corresponding version
@@ -106,14 +106,20 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
-### 8. Start application
+### 8. Install/Update `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
+
+If you're interested in using GitLab's new [elasticsearch repository indexer](../integration/elasticsearch.md#elasticsearch-repository-indexer-beta) (currently in beta)
+please follow the instructions on the document linked above and enable the
+indexer usage in the GitLab admin settings.
+
+### 9. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 9. Check application status
+### 10. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/restore_after_failure.md b/doc/update/restore_after_failure.md
index faa3a6e65cb..964a3c76c04 100644
--- a/doc/update/restore_after_failure.md
+++ b/doc/update/restore_after_failure.md
@@ -12,7 +12,7 @@ First, roll back the code or package. For source installations this involves
checking out the older version (branch or tag). For Omnibus installations this
means installing the older .deb or .rpm package. Then, restore from a backup.
Follow the instructions in the
-[Backup and Restore](../raketasks/backup_restore.md#restore-a-previously-created-backup)
+[Backup and Restore](../raketasks/backup_restore.md#restore)
documentation.
## Potential problems on the next upgrade
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index 0d1ecab5f8e..428377adb19 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -9,6 +9,10 @@ Community Edition to Enterprise Edition. These documents can be found in the
[`doc/update` directory of Enterprise Edition's source
code][old-ee-upgrade-docs].
+If you want to upgrade the version only, for example 11.8 to 11.9, *without* changing the
+GitLab edition you are using (Community or Enterprise), see the
+[Upgrading from source](upgrading_from_source.md) documentation.
+
## General upgrading steps
This guide assumes you have a correctly configured and tested installation of
@@ -71,7 +75,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
### 4. Install `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
If you're interested in using GitLab's new [elasticsearch repository
-indexer][indexer-beta] (currently in beta) please follow the instructions on the
+indexer](../integration/elasticsearch.md) (currently in beta) please follow the instructions on the
document linked above and enable the indexer usage in the GitLab admin settings.
### 5. Start application
@@ -129,4 +133,3 @@ Additional instructions here.
[support@gitlab.com]: mailto:support@gitlab.com
[old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update
-[indexer-beta]: https://docs.gitlab.com/ee/integration/elasticsearch.html
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 20d8ebecc0a..f82d666c7be 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -16,19 +16,16 @@ 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.
+If you are changing from GitLab Community Edition to GitLab Enterprise Edition, see
+the [Upgrading from CE to EE](upgrading_from_ce_to_ee.md) documentation.
+
## Guidelines for all versions
This section contains all the steps necessary to upgrade Community Edition or
Enterprise Edition, regardless of the version you are upgrading to. Version
specific guidelines (should there be any) are covered separately.
-### 1. Stop server
-
-```bash
-sudo service gitlab stop
-```
-
-### 2. Backup
+### 1. Backup
NOTE: If you installed GitLab from source, make sure `rsync` is installed.
@@ -38,10 +35,16 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
+### 2. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
### 3. Update Ruby
-NOTE: Beginning in GitLab 11.0, we only support Ruby 2.4 or higher, and dropped
-support for Ruby 2.3. Be sure to upgrade if necessary.
+NOTE: Beginning in GitLab 11.6, we only support Ruby 2.5 or higher, and dropped
+support for Ruby 2.4. Be sure to upgrade if necessary.
You can check which version you are running with `ruby -v`.
@@ -49,9 +52,9 @@ Download Ruby and compile it:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
-curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
-echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
-cd ruby-2.5.3
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.3.tar.gz
+echo '2347ed6ca5490a104ebd5684d2b9b5eefa6cd33c ruby-2.6.3.tar.gz' | shasum -c - && tar xzf ruby-2.6.3.tar.gz
+cd ruby-2.6.3
./configure --disable-install-rdoc
make
@@ -111,7 +114,47 @@ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.10.5.linux-amd64.tar.gz
```
-### 6. Get latest code
+### 6. Update git
+
+NOTE: **Note:**
+GitLab 11.11 and higher only supports Git 2.21.x and newer, and
+[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-ce/issues/54255).
+Be sure to upgrade your installation if necessary.
+
+```bash
+# Make sure Git is version 2.21.0 or higher
+git --version
+
+# Remove packaged Git
+sudo apt-get remove git-core
+
+# Install dependencies
+sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+
+# Download and compile pcre2 from source
+curl --silent --show-error --location https://ftp.pcre.org/pub/pcre/pcre2-10.33.tar.gz --output pcre2.tar.gz
+tar -xzf pcre2.tar.gz
+cd pcre2-10.33
+chmod +x configure
+./configure --prefix=/usr --enable-jit
+make
+make install
+
+# Download and compile from source
+cd /tmp
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
+echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz
+cd git-2.21.0/
+./configure --with-libpcre
+make prefix=/usr/local all
+
+# Install into /usr/local/bin
+sudo make prefix=/usr/local install
+
+# You should edit config/gitlab.yml, change the git -> bin_path to /usr/local/bin/git
+```
+
+### 7. Get latest code
```bash
cd /home/git/gitlab
@@ -139,7 +182,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout BRANCH-ee
```
-### 7. Update gitlab-shell
+### 8. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -149,7 +192,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 8. Update gitlab-workhorse
+### 9. Update gitlab-workhorse
Install and compile gitlab-workhorse. GitLab-Workhorse uses
[GNU Make](https://www.gnu.org/software/make/).
@@ -164,7 +207,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 9. Update Gitaly
+### 10. Update Gitaly
#### Compile Gitaly
@@ -175,7 +218,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update gitlab-pages
+### 11. Update gitlab-pages
#### Only needed if you use GitLab Pages
@@ -192,7 +235,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
-### 11. Update MySQL permissions
+### 12. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
@@ -214,7 +257,7 @@ You can make this setting permanent by adding it to your `my.cnf`:
log_bin_trust_function_creators=1
```
-### 12. Update configuration files
+### 13. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -288,7 +331,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 13. Install libs, migrations, etc.
+### 14. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -311,7 +354,7 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
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
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
# Clean up cache
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
@@ -320,14 +363,14 @@ 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).
-### 14. Start application
+### 15. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 15. Check application status
+### 16. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/user/account/security.md b/doc/user/account/security.md
index f4078876fab..8a8edc23529 100644
--- a/doc/user/account/security.md
+++ b/doc/user/account/security.md
@@ -1 +1,5 @@
+---
+redirect_to: '../profile/account/index.md'
+---
+
This document was moved to [profile](../profile/account/index.md).
diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md
index ea2c8307860..42a66becc50 100644
--- a/doc/user/account/two_factor_authentication.md
+++ b/doc/user/account/two_factor_authentication.md
@@ -1 +1,5 @@
+---
+redirect_to: '../profile/account/two_factor_authentication.md'
+---
+
This document was moved to [profile/account/two_factor_authentication](../profile/account/two_factor_authentication.md).
diff --git a/doc/user/admin_area/diff_limits.md b/doc/user/admin_area/diff_limits.md
index 9205860ef1f..4063c40a751 100644
--- a/doc/user/admin_area/diff_limits.md
+++ b/doc/user/admin_area/diff_limits.md
@@ -1,21 +1,40 @@
+---
+type: reference
+---
+
# Diff limits administration
+You can set a maximum size for display of diff files (patches).
+
+## Maximum diff patch size
+
+Diff files which exceed this value will be presented as 'too large' and won't
+be expandable. Instead of an expandable view, a link to the blob view will be
+shown.
+
+Patches greater than 10% of this size will be automatically collapsed, and a
+link to expand the diff will be presented.
+
NOTE: **Note:**
Merge requests and branch comparison views will be affected.
CAUTION: **Caution:**
-These settings are currently under experimental state. They'll
-increase the resource consumption of your instance and should
-be edited mindfully.
+This setting is experimental. An increased maximum will increase resource
+consumption of your instance. Keep this in mind when adjusting the maximum.
-1. Access **Admin area > Settings > General**
-1. Expand **Diff limits**
+1. Go to **Admin area > Settings > General**.
+1. Expand **Diff limits**.
+1. Enter a value for **Maximum diff patch size**, measured in bytes.
+1. Click on **Save changes**.
-### Maximum diff patch size
+<!-- ## Troubleshooting
-This is the content size each diff file (patch) is allowed to reach before
-it's collapsed, without the possibility of being expanded. A link redirecting
-to the blob view will be presented for the patches that surpass this limit.
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-Patches surpassing 10% of this content size will be automatically collapsed,
-but expandable (a link to expand the diff will be presented).
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/geo_nodes.md b/doc/user/admin_area/geo_nodes.md
new file mode 100644
index 00000000000..d99b87cbc5c
--- /dev/null
+++ b/doc/user/admin_area/geo_nodes.md
@@ -0,0 +1,86 @@
+---
+type: howto
+---
+
+# Geo nodes admin area **[PREMIUM ONLY]**
+
+You can configure various settings for GitLab Geo nodes. For more information, see
+[Geo documentation](../../administration/geo/replication/index.md).
+
+On the primary node, go to **Admin area > Geo**. On secondary nodes, go to **Admin area > Geo > Nodes**.
+
+## Common settings
+
+All Geo nodes have the following settings:
+
+| Setting | Description |
+| --------| ----------- |
+| Primary | This marks a Geo Node as **primary** node. There can be only one **primary** node; make sure that you first add the **primary** node and then all the others. |
+| Name | The unique identifier for the Geo node. Must match the setting `gitlab_rails[geo_node_name]` in `/etc/gitlab/gitlab.rb`. The setting defaults to `external_url` with a trailing slash. |
+| URL | The instance's user-facing URL. |
+
+The node you're reading from is indicated with a green `Current node` label, and
+the **primary** node is given a blue `Primary` label. Remember that you can only make
+changes on the **primary** node!
+
+## **Secondary** node settings
+
+**Secondary** nodes have a number of additional settings available:
+
+| Setting | Description |
+|---------------------------|-------------|
+| Selective synchronization | Enable Geo [selective sync](../../administration/geo/replication/configuration.md#selective-synchronization) for this **secondary** node. |
+| Repository sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling repositories. |
+| File sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling files. |
+
+## Geo backfill
+
+**Secondary** nodes are notified of changes to repositories and files by the **primary** node,
+and will always attempt to synchronize those changes as quickly as possible.
+
+Backfill is the act of populating the **secondary** node with repositories and files that
+existed *before* the **secondary** node was added to the database. Since there may be
+extremely large numbers of repositories and files, it's infeasible to attempt to
+download them all at once, so GitLab places an upper limit on the concurrency of
+these operations.
+
+How long the backfill takes is a function of the maximum concurrency, but higher
+values place more strain on the **primary** node. From [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3107),
+the limits are configurable. If your **primary** node has lots of surplus capacity,
+you can increase the values to complete backfill in a shorter time. If it's
+under heavy load and backfill is reducing its availability for normal requests,
+you can decrease them.
+
+## Using a different URL for synchronization
+
+The **primary** node's Internal URL is used by **secondary** nodes to contact it
+(to sync repositories, for example). The name Internal URL distinguishes it from
+[External URL](https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-the-external-url-for-gitlab)
+which is used by users. Internal URL does not need to be a private address.
+
+Internal URL defaults to External URL, but you can customize it under
+**Admin area > Geo Nodes**.
+
+## Multiple secondary nodes behind a load balancer
+
+In GitLab 11.11, **secondary** nodes can use identical external URLs as long as
+a unique `name` is set for each Geo node. The `gitlab.rb` setting
+`gitlab_rails[geo_node_name]` must:
+
+- Be set for each GitLab instance that runs `unicorn`, `sidekiq`, or `geo_logcursor`.
+- Match a Geo node name.
+
+The load balancer must use sticky sessions in order to avoid authentication
+failures and cross site request errors.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/img/admin_wrench.png b/doc/user/admin_area/img/admin_wrench.png
new file mode 100644
index 00000000000..17eee143e87
--- /dev/null
+++ b/doc/user/admin_area/img/admin_wrench.png
Binary files differ
diff --git a/doc/user/admin_area/img/index_runners_search_or_filter.png b/doc/user/admin_area/img/index_runners_search_or_filter.png
new file mode 100644
index 00000000000..5176a1a39bf
--- /dev/null
+++ b/doc/user/admin_area/img/index_runners_search_or_filter.png
Binary files differ
diff --git a/doc/user/admin_area/img/license_admin_area.png b/doc/user/admin_area/img/license_admin_area.png
new file mode 100644
index 00000000000..b5662b81c5e
--- /dev/null
+++ b/doc/user/admin_area/img/license_admin_area.png
Binary files differ
diff --git a/doc/user/admin_area/img/license_details.png b/doc/user/admin_area/img/license_details.png
new file mode 100644
index 00000000000..2085bb437ad
--- /dev/null
+++ b/doc/user/admin_area/img/license_details.png
Binary files differ
diff --git a/doc/user/admin_area/img/license_upload.png b/doc/user/admin_area/img/license_upload.png
new file mode 100644
index 00000000000..29d55175a2d
--- /dev/null
+++ b/doc/user/admin_area/img/license_upload.png
Binary files differ
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 00cea22e4e1..527110d53df 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# GitLab Admin Area **[CORE ONLY]**
The Admin Area provides a web UI for administering some features of GitLab self-managed instances.
@@ -14,16 +18,191 @@ Only admin users can access the Admin Area.
The Admin Area is made up of the following sections:
-| Section | Description |
-|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Overview | View your GitLab Dashboard, and maintain projects, users, groups, jobs, runners, and Gitaly servers. |
-| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
-| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
-| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
-| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
-| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
-| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
-| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
-| Labels | Create and maintain [labels](labels.md) for your GitLab instance. |
-| Appearance | Customize [GitLab's appearance](../../customization/index.md). |
-| Settings | Modify the [settings](settings/index.md) for your GitLab instance. |
+| Section | Description |
+|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), [users](#administer-users), groups, [jobs](#administer-jobs), [Runners](#administer-runners), and [Gitaly servers](#administer-gitaly-servers). |
+| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
+| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
+| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
+| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
+| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
+| License **[STARTER ONLY]** | Upload, display, and remove [licenses](license.md). |
+| Push Rules **[STARTER]** | Configure pre-defined git [push rules](../../push_rules/push_rules.md) for projects. |
+| Geo **[PREMIUM ONLY]** | Configure and maintain [Geo nodes](geo_nodes.md). |
+| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
+| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
+| Labels | Create and maintain [labels](labels.md) for your GitLab instance. |
+| Appearance | Customize [GitLab's appearance](../../customization/index.md). |
+| Settings | Modify the [settings](settings/index.md) for your GitLab instance. |
+
+## Admin Dashboard
+
+The Dashboard provides statistics and system information about the GitLab instance.
+
+To access the Dashboard, either:
+
+- Click the Admin Area icon (the wrench icon).
+- Visit `/admin` on your self-managed instance.
+
+The Dashboard is the default view of the Admin Area, and is made up of the following sections:
+
+| Section | Description |
+|------------|---------------|
+| Projects | The total number of projects, up to 10 of the latest projects, and the option of creating a new project. |
+| Users | The total number of users, up to 10 of the latest users, and the option of creating a new user. |
+| Groups | The total number of groups, up to 10 of the latest groups, and the option of creating a new group. |
+| Statistics | Totals of all elements of the GitLab instance. |
+| Features | All features available on the GitLab instance. Enabled features are marked with a green circle icon, and disabled features are marked with a power icon. |
+| Components | The major components of GitLab and the version number of each. A link to the Gitaly Servers is also included. |
+
+## Administer Projects
+
+You can administer all projects in the GitLab instance from the Admin Area's Projects page.
+
+To access the Projects page, go to **Admin Area > Overview > Projects**.
+
+Click the **All**, **Private**, **Internal**, or **Public** tab to list only projects of that
+criteria.
+
+By default, all projects are listed, in reverse order of when they were last updated. For each
+project, the following information is listed:
+
+- Name.
+- Namespace.
+- Description.
+- Size, updated every 15 minutes at most.
+
+Projects can be edited or deleted.
+
+The list of projects can be sorted by:
+
+- Name.
+- Last created.
+- Oldest created.
+- Last updated.
+- Oldest updated.
+- Owner.
+
+A user can choose to hide or show archived projects in the list.
+
+In the **Filter by name** field, type the project name you want to find, and GitLab will filter
+them as you type.
+
+Select from the **Namespace** dropdown to filter only projects in that namespace.
+
+You can combine the filter options. For example, to list only public projects with `score` in their name:
+
+1. Click the **Public** tab.
+1. Enter `score` in the **Filter by name...** input box.
+
+## Administer Users
+
+You can administer all users in the GitLab instance from the Admin Area's Users page.
+
+To access the Users page, go to **Admin Area > Overview > Users**.
+
+Click the **Active**, **Admins**, **2FA Enabled**, or **2FA Disabled**, **External**, or
+**Without projects** tab to list only users of that criteria.
+
+For each user, their username, email address, are listed, also the date their account was
+created and the date of last activity. To edit a user, click the **Edit** button in that user's
+row. To delete the user, or delete the user and their contributions, click the cog dropdown in
+that user's row, and select the desired option.
+
+To change the sort order:
+
+1. Click the sort dropdown.
+1. Select the desired order.
+
+By default the sort dropdown shows **Name**.
+
+To search for users, enter your criteria in the search field. The user search is case
+insensitive, and applies partial matching to name and username. To search for an email address,
+you must provide the complete email address.
+
+## Administer Jobs
+
+You can administer all jobs in the GitLab instance from the Admin Area's Jobs page.
+
+To access the Jobs page, go to **Admin Area > Overview > Jobs**.
+
+All jobs are listed, in reverse order of their job ID.
+
+Click the **All** tab to list all jobs. Click the **Pending**, **Running**, or **Finished** tab to list only jobs of that status.
+
+For each job, the following details are listed:
+
+| Field | Description |
+|--------- | ----------- |
+| Status | Job status, either **passed**, **skipped**, or **failed**. |
+| Job | Includes links to the job, branch, and the commit that started the job. |
+| Pipeline | Includes a link to the specific pipeline. |
+| Project | Name of the project, and organization, to which the job belongs. |
+| Runner | Name of the CI runner assigned to execute the job. |
+| Stage | Stage that the job is declared in a `.gitlab-ci.yml` file. |
+| Name | Name of the job specified in a `.gitlab-ci.yml` file. |
+| Timing | Duration of the job, and how long ago the job completed. |
+| Coverage | Percentage of tests coverage. |
+
+## Administer Runners
+
+You can adminster all Runners in the GitLab instance from the Admin Area's **Runners** page. See
+[GitLab Runner](https://docs.gitlab.com/runner/) for more information on Runner itself.
+
+To access the **Runners** page, go to **Admin Area > Overview > Runners**.
+
+The **Runners** page features:
+
+- A description of Runners, and their possible states.
+- Instructions on installing a Runner.
+- A list of all registered Runners.
+
+Runners are listed in descending order by the date they were created, by default. You can change
+the sort order to *Last Contacted* from the dropdown beside the search field.
+
+To search Runners' descriptions:
+
+1. In the **Search or filter results...** field, type the description of the Runner you want to
+find.
+1. Press Enter.
+
+You can also filter Runners by status, type, and tag. To filter:
+
+1. Click in the **Search or filter results...** field.
+1. Select **status:**, **type:**, or **tag:**
+1. Select or enter your search criteria.
+
+![Attributes of a Runner, with the **Search or filter results...** field active](img/index_runners_search_or_filter.png)
+
+For each Runner, the following attributes are listed:
+
+| Attribute | Description |
+| ------------ | ----------- |
+| Type | One or more of the following states: shared, group, specific, locked, or paused |
+| Runner token | Token used to identify the Runner, and which the Runner uses to communicate with the GitLab instance |
+| Description | Description given to the Runner when it was created |
+| Version | GitLab Runner version |
+| IP address | IP address of the host on which the Runner is registered |
+| Projects | Projects to which the Runner is assigned |
+| Jobs | Total of jobs run by the Runner |
+| Tags | Tags associated with the Runner |
+| Last contact | Timestamp indicating when the GitLab instance last contacted the Runner |
+
+You can also edit, pause, or remove each Runner.
+
+## Administer Gitaly servers
+
+You can list all Gitaly servers in the GitLab instance from the Admin Area's **Gitaly Servers**
+page. For more details, see [Gitaly](../../administration/gitaly/index.md).
+
+To access the **Gitaly Servers** page, go to **Admin Area > Overview > Gitaly Servers**.
+
+For each Gitaly server, the following details are listed:
+
+| Field | Description |
+| -------------- | ----------- |
+| Storage | Repository storage |
+| Address | Network address on which the Gitaly server is listening |
+| Server version | Gitaly version |
+| Git version | Version of Git installed on the Gitaly server |
+| Up to date | Indicates if the Gitaly server version is the latest version available. A green dot indicates the server is up to date. |
diff --git a/doc/user/admin_area/labels.md b/doc/user/admin_area/labels.md
index e383142c33e..eba27548f86 100644
--- a/doc/user/admin_area/labels.md
+++ b/doc/user/admin_area/labels.md
@@ -1,9 +1,25 @@
+---
+type: reference
+---
+
# Labels administration **[CORE ONLY]**
-## Default Labels
+In the Admin Area, you can manage labels for the GitLab instance. For more details, see [Labels](../project/labels.md).
-### Define your own default Label Set
+## Default Labels
-Labels that are created within the Labels view on the Admin Dashboard will be automatically added to each new project.
+Labels created in the Admin Area become available to each _new_ project.
![Default label set](img/admin_labels.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
new file mode 100644
index 00000000000..1e8ce04da92
--- /dev/null
+++ b/doc/user/admin_area/license.md
@@ -0,0 +1,126 @@
+---
+type: howto
+---
+
+# Activate all GitLab Enterprise Edition functionality with a license **[STARTER ONLY]**
+
+To activate all GitLab Enterprise Edition (EE) functionality, you need to upload
+a license. Once you've received your license from GitLab Inc., you can upload it
+by **signing into your GitLab instance as an admin** or add it at
+installation time.
+
+The license has the form of a base64 encoded ASCII text with a `.gitlab-license`
+extension and can be obtained when you [purchase one][pricing] or when you sign
+up for a [free trial].
+
+NOTE: **Note:**
+As of GitLab Enterprise Edition 9.4.0, a newly-installed instance without an
+uploaded license will only have the Core features active. A trial license will
+activate all Ultimate features, but after
+[the trial expires](#what-happens-when-your-license-expires), some functionality
+will be locked.
+
+## Uploading your license
+
+The very first time you visit your GitLab EE installation signed in as an admin,
+you should see a note urging you to upload a license with a link that takes you
+straight to the License admin area.
+
+Otherwise, you can:
+
+1. Navigate manually to the **Admin Area** by clicking the wrench icon in the menu bar.
+
+ ![Admin area icon](img/admin_wrench.png)
+
+1. And then going to the **License** tab and click on **Upload New License**.
+
+ ![License admin area](img/license_admin_area.png)
+
+1. If you've received a `.gitlab-license` file, you should have already downloaded
+ it in your local machine. You can then upload it directly by choosing the
+ license file and clicking the **Upload license** button. In the image below,
+ you can see that the selected license file is named `GitLab.gitlab-license`.
+
+ ![Upload license](img/license_upload.png)
+
+ If you've received your license as plain text, you need to select the
+ "Enter license key" option, copy the license, paste it into the "License key"
+ field and click **Upload license**.
+
+## Add your license at install time
+
+The license may be automatically injected during installation using one of
+two methods.
+
+The first requires a license file named `Gitlab.gitlab-release`.
+
+Place it in the `config/` directory if installing from source or in the
+`/etc/gitlab/` directory if installing Omnibus.
+
+The second allows the administrator to configure the location and
+filename of the license.
+
+Source installations should set the `GITLAB_LICENSE_FILE` environment
+variable with the path to a valid GitLab Enterprise Edition license.
+
+```sh
+export GITLAB_LICENSE_FILE="/path/to/license/file"
+```
+
+Omnibus installations should add this entry to `gitlab.rb`:
+
+```ruby
+gitlab_rails['license_file'] = "/path/to/license/file"
+```
+
+CAUTION:: **Caution:**
+These methods will only add a license at the time of installation. Use the
+admin area in the web ui to renew or upgrade licenses.
+
+---
+
+Once the license is uploaded, all GitLab Enterprise Edition functionality
+will be active until the end of the license period. When that period ends, the
+instance will [fall back](#what-happens-when-your-license-expires) to Core-only
+functionality.
+
+You can review the license details at any time in the License section of the
+Admin Area.
+
+![License details](img/license_details.png)
+
+## Notification before the license expires
+
+One month before the license expires, a message informing when the expiration
+is due to, will start appearing to GitLab admins. Make sure that you update your
+license, otherwise you will miss all the paid features if it expires.
+
+## What happens when your license expires
+
+In case your license expires, GitLab will lock down some features like Git pushes,
+issue creation, etc., and a message to inform of the expired license will be
+presented to all admins.
+
+In order to get back all the previous functionality, a new license must be uploaded.
+To fall back to having only the Core features active, you'll need to delete the
+expired license(s).
+
+## License history
+
+It's possible to upload and view more than one license,
+but only the latest license will be used as the active license.
+
+[free trial]: https://about.gitlab.com/free-trial/
+[pricing]: https://about.gitlab.com/pricing/
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index c22982ac190..ff056490653 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -1,12 +1,14 @@
+---
+type: concepts, howto
+---
+
# Health Check
-> **Notes:**
-> - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
-> - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
-> be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
-> section.
-> - [Access token](#access-token) has been deprecated in GitLab 9.4
-> in favor of [IP whitelist](#ip-whitelist)
+> - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
+> - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and was
+> be deprecated in GitLab 9.1.
+> - [Access token](#access-token-deprecated) has been deprecated in GitLab 9.4
+> in favor of [IP whitelist](#ip-whitelist).
GitLab provides liveness and readiness probes to indicate service health and
reachability to required services. These probes report on the status of the
@@ -16,31 +18,28 @@ traffic until the system is ready or restart the container as needed.
## IP whitelist
-To access monitoring resources, the client IP needs to be included in a whitelist.
-
-[Read how to add IPs to a whitelist for the monitoring endpoints][admin].
+To access monitoring resources, the requesting client IP needs to be included in a whitelist.
+For details, see [how to add IPs to a whitelist for the monitoring endpoints](../../../administration/monitoring/ip_whitelist.md).
## Using the endpoints
-With default whitelist settings, the probes can be accessed from localhost:
+With default whitelist settings, the probes can be accessed from localhost using the following URLs:
- `http://localhost/-/health`
- `http://localhost/-/readiness`
- `http://localhost/-/liveness`
-The first endpoint, `/-/health/`, only checks whether the application server is running. It does
--not verify the database or other services are running. A successful response will return
-a 200 status code with the following message:
+The first endpoint, `health`, only checks whether the application server is running. It does not verify the database or other services are running. A successful response will return a 200 status code with the following message:
-```
+```text
GitLab OK
```
The readiness and liveness probes will provide a report of system health in JSON format.
-Readiness example output:
+`readiness` probe example output:
-```
+```json
{
"queues_check" : {
"status" : "ok"
@@ -60,9 +59,9 @@ Readiness example output:
}
```
-Liveness example output:
+`liveness` probe example output:
-```
+```json
{
"cache_check" : {
"status" : "ok"
@@ -89,9 +88,8 @@ will return a valid successful HTTP status code, and a `success` message.
## Access token (Deprecated)
->**Note:**
-Access token has been deprecated in GitLab 9.4
-in favor of [IP whitelist](#ip-whitelist)
+> NOTE: **Note:**
+> Access token has been deprecated in GitLab 9.4 in favor of [IP whitelist](#ip-whitelist).
An access token needs to be provided while accessing the probe endpoints. The current
accepted token can be found under the **Admin area âž” Monitoring âž” Health check**
@@ -101,14 +99,25 @@ accepted token can be found under the **Admin area âž” Monitoring âž” Health che
The access token can be passed as a URL parameter:
-```
+```text
https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
```
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416
[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[pingdom]: https://www.pingdom.com
[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
[kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
-[admin]: ../../../administration/monitoring/ip_whitelist.md
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
new file mode 100644
index 00000000000..001e4b6bf48
--- /dev/null
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -0,0 +1,62 @@
+---
+type: reference
+---
+
+# Account and limit settings
+
+## Repository size limit **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740) in [GitLab Enterprise Edition 8.12](https://about.gitlab.com/2016/09/22/gitlab-8-12-released/#limit-project-size-ee).
+> Available in [GitLab Starter](https://about.gitlab.com/pricing/).
+
+Repositories within your GitLab instance can grow quickly, especially if you are
+using LFS. Their size can grow exponentially, rapidly consuming available storage.
+
+To avoid this from happening, you can set a hard limit for your repositories' size.
+This limit can be set globally, per group, or per project, with per project limits
+taking the highest priority.
+
+There are numerous use cases where you might set up a limit for repository size.
+For instance, consider the following workflow:
+
+1. Your team develops apps which require large files to be stored in
+ the application repository.
+1. Although you have enabled [Git LFS](../../../workflow/lfs/manage_large_binaries_with_git_lfs.md#git-lfs)
+ to your project, your storage has grown significantly.
+1. Before you exceed available storage, you set up a limit of 10 GB
+ per repository.
+
+### How it works
+
+Only a GitLab administrator can set those limits. Setting the limit to `0` means
+there are no restrictions.
+
+These settings can be found within:
+
+- Each project's settings.
+- A group's settings.
+- The **Size limit per repository (MB)** field in the **Account and limit** section of a GitLab instance's
+ settings by navigating to either:
+ - **Admin Area > Settings > General**.
+ - The path `/admin/application_settings`.
+
+The first push of a new project, including LFS objects, will be checked for size
+and **will** be rejected if the sum of their sizes exceeds the maximum allowed
+repository size.
+
+For details on manually purging files, see [reducing the repository size using Git](../../project/repository/reducing_the_repo_size_using_git.md).
+
+NOTE: **Note:**
+For GitLab.com, the repository size limit is 10 GB.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 01979f12a01..6c4abce83c2 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Continuous Integration and Deployment Admin settings **[CORE ONLY]**
In this area, you will find settings for Auto DevOps, Runners and job artifacts.
@@ -10,8 +14,8 @@ You can find it in the admin area, under **Settings > Continuous Integration and
To enable (or disable) [Auto DevOps](../../../topics/autodevops/index.md)
for all projects:
-1. Go to **Admin area > Settings > Continuous Integration and Deployment**.
-1. Check (or uncheck to disable) the box that says "Default to Auto DevOps pipeline for all projects".
+1. Go to **Admin area > Settings > Continuous Integration and Deployment**
+1. Check (or uncheck to disable) the box that says "Default to Auto DevOps pipeline for all projects"
1. Optionally, set up the [Auto DevOps base domain](../../../topics/autodevops/index.md#auto-devops-base-domain)
which is going to be used for Auto Deploy and Auto Review Apps.
1. Hit **Save changes** for the changes to take effect.
@@ -20,13 +24,13 @@ From now on, every existing project and newly created ones that don't have a
`.gitlab-ci.yml`, will use the Auto DevOps pipelines.
If you want to disable it for a specific project, you can do so in
-[its settings](../../../topics/autodevops/index.md##enablingdisabling-auto-devops).
+[its settings](../../../topics/autodevops/index.md#enablingdisabling-auto-devops).
## Maximum artifacts size **[CORE ONLY]**
-The maximum size of the [job artifacts][art-yml] can be set in the Admin area
-of your GitLab instance. The value is in *MB* and the default is 100MB per job;
-on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-ci-cd).
+The maximum size of the [job artifacts](../../../administration/job_artifacts.md)
+can be set in the Admin area of your GitLab instance. The value is in *MB* and
+the default is 100MB per job; on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-cicd).
To change it:
@@ -40,7 +44,7 @@ The default expiration time of the [job artifacts](../../../administration/job_a
can be set in the Admin area of your GitLab instance. The syntax of duration is
described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in)
and the default value is `30 days`. On GitLab.com they
-[never expire](../../gitlab_com/index.md#gitlab-ci-cd).
+[never expire](../../gitlab_com/index.md#gitlab-cicd).
1. Go to **Admin area > Settings > Continuous Integration and Deployment**.
1. Change the value of default expiration time.
@@ -50,6 +54,86 @@ This setting is set per job and can be overridden in
[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifactsexpire_in).
To disable the expiration, set it to `0`. The default unit is in seconds.
+## Shared Runners pipeline minutes quota **[STARTER ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1078)
+in GitLab Starter 8.16.
+
+If you have enabled shared Runners for your GitLab instance, you can limit their
+usage by setting a maximum number of pipeline minutes that a group can use on
+shared Runners per month. Setting this to `0` (default value) will grant
+unlimited pipeline minutes. While build limits are stored as minutes, the
+counting is done in seconds. Usage resets on the first day of each month.
+On GitLab.com, the quota is calculated based on your
+[subscription plan](https://about.gitlab.com/pricing/#gitlab-com).
+
+To change the pipelines minutes quota:
+
+1. Go to **Admin area > Settings > Continuous Integration and Deployment**
+1. Set the pipeline minutes quota limit.
+1. Hit **Save changes** for the changes to take effect
+
+---
+
+While the setting in the Admin area has a global effect, as an admin you can
+also change each group's pipeline minutes quota to override the global value.
+
+1. Navigate to the **Groups** admin area and hit the **Edit** button for the
+ group you wish to change the pipeline minutes quota.
+1. Set the pipeline minutes quota to the desired value
+1. Hit **Save changes** for the changes to take effect.
+
+Once saved, you can see the build quota in the group admin view.
+The quota can also be viewed in the project admin view if shared Runners
+are enabled.
+
+![Project admin info](img/admin_project_quota_view.png)
+
+When the pipeline minutes quota for a group is set to a value different than 0,
+the **Pipelines quota** page is available to the group page settings list.
+You can see there an overview of the pipeline minutes quota of all projects of
+the group.
+
+![Group pipelines quota](img/group_pipelines_quota.png)
+
+
+## Extra Shared Runners pipeline minutes quota
+
+NOTE: **Note:**
+Only available on GitLab.com.
+
+You can purchase additional CI minutes so your pipelines will not be blocked after you have
+used all your CI minutes from your main quota.
+
+In order to purchase additional minutes, you should follow these steps:
+
+1. Go to **Group > Settings > Pipelines quota**. Once you are on that page, click on **Buy additional minutes**.
+
+ ![Buy additional minutes](img/buy_btn.png)
+
+1. Locate the subscription card that is linked to your group on GitLab.com,
+click on **Buy more CI minutes**, and complete the details about the transaction.
+
+ ![Buy additional minutes](img/buy_minutes_card.png)
+
+1. Once we have processed your payment, the extra CI minutes
+will be synced to your Group and you can visualize it from the
+**Group > Settings > Pipelines quota** page:
+
+ ![Additional minutes](img/additional_minutes.png)
+
+Be aware that:
+
+1. If you have purchased extra CI minutes before the purchase of a paid plan,
+we will calculate a pro-rated charge for your paid plan. That means you may
+be charged for less than one year since your subscription was previously
+created with the extra CI minutes.
+1. Once the extra CI minutes has been assigned to a Group they cannot be transferred
+to a different Group.
+1. If you have some minutes used over your default quota, these minutes will
+be deducted from your Additional Minutes quota immediately after your purchase of additional
+minutes.
+
## Archive jobs **[CORE ONLY]**
Archiving jobs is useful for reducing the CI/CD footprint on the system by
@@ -65,3 +149,15 @@ To set the duration for which the jobs will be considered as old and expired:
Once that time passes, the jobs will be archived and no longer able to be
retried. Make it empty to never expire jobs. It has to be no less than 1 day,
for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 50c318a4969..9555a695b13 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -1,15 +1,37 @@
+---
+type: reference
+---
+
# Email
+You can customize some of the content in emails sent from your GitLab instance.
+
## Custom logo
The logo in the header of some emails can be customized, see the [logo customization section](../../../customization/branded_page_and_email_header.md).
+## Custom additional text **[PREMIUM ONLY]**
+
+> [Introduced][ee-5031] in [GitLab Premium][eep] 10.7.
+
+The additional text will appear at the bottom of any email and can be used for
+legal/auditing/compliance reasons.
+
+1. Go to **Admin area > Settings** (`/admin/application_settings`).
+1. Under the **Email** section, change the **Additional text** field.
+1. Hit **Save** for the changes to take effect.
+
+![Admin email settings](img/email_settings.png)
+
+[ee-5031]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5031
+[eep]: https://about.gitlab.com/pricing/
+
## Custom hostname for private commit emails
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22560) in GitLab 11.5.
-This configuration option sets the email hostname for [private commit emails](../../profile/index.md#private-commit-email),
-and it's, by default, set to `users.noreply.YOUR_CONFIGURED_HOSTNAME`.
+This configuration option sets the email hostname for [private commit emails](../../profile/index.md#private-commit-email).
+ By default it is set to `users.noreply.YOUR_CONFIGURED_HOSTNAME`.
In order to change this option:
@@ -18,5 +40,17 @@ In order to change this option:
1. Hit **Save** for the changes to take effect.
NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get
-recognized by GitLab. This can directly conflict with certain [Push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) such as
+recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as
`Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md
new file mode 100644
index 00000000000..11c0867da17
--- /dev/null
+++ b/doc/user/admin_area/settings/external_authorization.md
@@ -0,0 +1,128 @@
+---
+type: reference
+---
+
+# External authorization control **[CORE ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4216) in
+> [GitLab Premium](https://about.gitlab.com/pricing) 10.6.
+> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27056) to
+> [GitLab Core](https://about.gitlab.com/pricing/) in 11.10.
+
+In highly controlled environments, it may be necessary for access policy to be
+controlled by an external service that permits access based on project
+classification and user access. GitLab provides a way to check project
+authorization with your own defined service.
+
+## Overview
+
+Once the external service is configured and enabled, when a project is accessed,
+a request is made to the external service with the user information and project
+classification label assigned to the project. When the service replies with a
+known response, the result is cached for 6 hours.
+
+If the external authorization is enabled, GitLab will further block pages and
+functionality that render cross-project data. That includes:
+
+- most pages under Dashboard (Activity, Milestones, Snippets, Assigned merge
+ requests, Assigned issues, Todos)
+- under a specific group (Activity, Contribution analytics, Issues, Issue boards,
+ Labels, Milestones, Merge requests)
+- Global and Group search will be disabled
+
+This is to prevent performing to many requests at once to the external
+authorization service.
+
+Whenever access is granted or denied this is logged in a logfile called
+`external-policy-access-control.log`.
+Read more about logs GitLab keeps in the [omnibus documentation][omnibus-log-docs].
+
+## Configuration
+
+The external authorization service can be enabled by an admin on the GitLab's
+admin area under the settings page:
+
+![Enable external authorization service](img/external_authorization_service_settings.png)
+
+The available required properties are:
+
+- **Service URL**: The URL to make authorization requests to. When leaving the
+ URL blank, cross project features will remain available while still being able
+ to specify classification labels for projects.
+- **External authorization request timeout**: The timeout after which an
+ authorization request is aborted. When a request times out, access is denied
+ to the user.
+- **Client authentication certificate**: The certificate to use to authenticate
+ with the external authorization service.
+- **Client authentication key**: Private key for the certificate when
+ authentication is required for the external authorization service, this is
+ encrypted when stored.
+- **Client authentication key password**: Passphrase to use for the private key when authenticating with the external service this is encrypted when stored.
+- **Default classification label**: The classification label to use when
+ requesting authorization if no specific label is defined on the project
+
+When using TLS Authentication with a self signed certificate, the CA certificate
+needs to be trused by the openssl installation. When using GitLab installed using
+Omnibus, learn to install a custom CA in the
+[omnibus documentation][omnibus-ssl-docs]. Alternatively learn where to install
+custom certificates using `openssl version -d`.
+
+## How it works
+
+When GitLab requests access, it will send a JSON POST request to the external
+service with this body:
+
+```json
+{
+ "user_identifier": "jane@acme.org",
+ "project_classification_label": "project-label",
+ "user_ldap_dn": "CN=Jane Doe,CN=admin,DC=acme"
+}
+```
+
+The `user_ldap_dn` is optional and is only sent when the user is logged in
+through LDAP.
+
+When the external authorization service responds with a status code 200, the
+user is granted access. When the external service responds with a status code
+401 or 403, the user is denied access. In any case, the request is cached for 6 hours.
+
+When denying access, a `reason` can be optionally specified in the JSON body:
+
+```json
+{
+ "reason": "You are not allowed access to this project."
+}
+```
+
+Any other status code than 200, 401 or 403 will also deny access to the user, but the
+response will not be cached.
+
+If the service times out (after 500ms), a message "External Policy Server did
+not respond" will be displayed.
+
+## Classification labels
+
+You can use your own classification label in the project's
+**Settings > General > General project settings** page in the "Classification
+label" box. When no classification label is specified on a project, the default
+label defined in the [global settings](#configuration) will be used.
+
+The label will be shown on all project pages in the upper right corner.
+
+![classification label on project page](img/classification_label_on_project_page.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
+[omnibus-ssl-docs]: https://docs.gitlab.com/omnibus/settings/ssl.html
+[omnibus-log-docs]: https://docs.gitlab.com/omnibus/settings/logs.html
diff --git a/doc/user/admin_area/settings/img/additional_minutes.png b/doc/user/admin_area/settings/img/additional_minutes.png
new file mode 100644
index 00000000000..d148ed79b92
--- /dev/null
+++ b/doc/user/admin_area/settings/img/additional_minutes.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png b/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png
deleted file mode 100644
index 723be23e77b..00000000000
--- a/doc/user/admin_area/settings/img/admin_area_default_artifacts_expiration.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
deleted file mode 100644
index 3f827f1f7a3..00000000000
--- a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_project_quota_view.png b/doc/user/admin_area/settings/img/admin_project_quota_view.png
new file mode 100644
index 00000000000..8320be860da
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_project_quota_view.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/buy_btn.png b/doc/user/admin_area/settings/img/buy_btn.png
new file mode 100644
index 00000000000..0cc88b8a48f
--- /dev/null
+++ b/doc/user/admin_area/settings/img/buy_btn.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/buy_minutes_card.png b/doc/user/admin_area/settings/img/buy_minutes_card.png
new file mode 100644
index 00000000000..cf4ad34ead7
--- /dev/null
+++ b/doc/user/admin_area/settings/img/buy_minutes_card.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/classification_label_on_project_page.png b/doc/user/admin_area/settings/img/classification_label_on_project_page.png
new file mode 100644
index 00000000000..4aedb332cec
--- /dev/null
+++ b/doc/user/admin_area/settings/img/classification_label_on_project_page.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/email_settings.png b/doc/user/admin_area/settings/img/email_settings.png
new file mode 100644
index 00000000000..ed0a80d10ce
--- /dev/null
+++ b/doc/user/admin_area/settings/img/email_settings.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/external_authorization_service_settings.png b/doc/user/admin_area/settings/img/external_authorization_service_settings.png
new file mode 100644
index 00000000000..9b8658fd1a1
--- /dev/null
+++ b/doc/user/admin_area/settings/img/external_authorization_service_settings.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/file_template_admin_area.png b/doc/user/admin_area/settings/img/file_template_admin_area.png
new file mode 100644
index 00000000000..269d997e1d9
--- /dev/null
+++ b/doc/user/admin_area/settings/img/file_template_admin_area.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/file_template_user_dropdown.png b/doc/user/admin_area/settings/img/file_template_user_dropdown.png
new file mode 100644
index 00000000000..8c9eb49f6c9
--- /dev/null
+++ b/doc/user/admin_area/settings/img/file_template_user_dropdown.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/group_pipelines_quota.png b/doc/user/admin_area/settings/img/group_pipelines_quota.png
new file mode 100644
index 00000000000..d94b609ad6f
--- /dev/null
+++ b/doc/user/admin_area/settings/img/group_pipelines_quota.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/mirror_settings.png b/doc/user/admin_area/settings/img/mirror_settings.png
new file mode 100644
index 00000000000..090db6808a7
--- /dev/null
+++ b/doc/user/admin_area/settings/img/mirror_settings.png
Binary files differ
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 8358fe64f18..0e697b9e445 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -6,6 +6,7 @@ instance like sign-up restrictions, account limits and quota, metrics, etc.
Navigate to it by going to **Admin area > Settings**. Some of the settings
include:
+- [Account and limit settings](account_and_limit_settings.md) **[STARTER]**
- [Continuous Integration and Deployment](continuous_integration.md)
- [Email](email.md)
- [Sign up restrictions](sign_up_restrictions.md)
@@ -13,6 +14,7 @@ include:
- [Third party offers](third_party_offers.md)
- [Usage statistics](usage_statistics.md)
- [Visibility and access controls](visibility_and_access_controls.md)
+- [Custom templates repository](instance_template_repository.md) **[PREMIUM]**
NOTE: **Note:**
You can change the [first day of the week](../../profile/preferences.md) for the entire GitLab instance
diff --git a/doc/user/admin_area/settings/instance_template_repository.md b/doc/user/admin_area/settings/instance_template_repository.md
new file mode 100644
index 00000000000..91286a67c31
--- /dev/null
+++ b/doc/user/admin_area/settings/instance_template_repository.md
@@ -0,0 +1,79 @@
+---
+type: reference
+---
+
+# Instance template repository **[PREMIUM ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5986) in
+> [GitLab Premium](https://about.gitlab.com/pricing) 11.3.
+
+## Overview
+
+In hosted systems, enterprises often have a need to share their own templates
+across teams. This feature allows an administrator to pick a project to be the
+instance-wide collection of file templates. These templates are then exposed to
+all users [via the web editor](../../project/repository/web_editor.md#template-dropdowns)
+while the project remains secure.
+
+## Configuration
+
+As an administrator, navigate to **Admin area > Settings > Templates** and
+select the project to serve as the custom template repository.
+
+![File templates in the admin area](img/file_template_admin_area.png)
+
+Once a project has been selected, you can add custom templates to the repository,
+and they will appear in the appropriate places in the
+[frontend](../../project/repository/web_editor.md#template-dropdowns) and
+[API](../../../api/settings.md).
+
+Templates must be added to a specific subdirectory in the repository,
+corresponding to the kind of template. The following types of custom templates
+are supported:
+
+| Type | Directory | Extension |
+| :---------------: | :-----------: | :-----------: |
+| `Dockerfile` | `Dockerfile` | `.dockerfile` |
+| `.gitignore` | `gitignore` | `.gitignore` |
+| `.gitlab-ci.yml` | `gitlab-ci` | `.yml` |
+| `LICENSE` | `LICENSE` | `.txt` |
+
+Each template must go in its respective subdirectory, have the correct
+extension and not be empty. So, the hierarchy should look like this:
+
+```text
+|-- README.md
+|-- Dockerfile
+ |-- custom_dockerfile.dockerfile
+ |-- another_dockerfile.dockerfile
+|-- gitignore
+ |-- custom_gitignore.gitignore
+ |-- another_gitignore.gitignore
+|-- gitlab-ci
+ |-- custom_gitlab-ci.yml
+ |-- another_gitlab-ci.yml
+|-- LICENSE
+ |-- custom_license.txt
+ |-- another_license.txt
+```
+
+Once this is established, the list of custom templates will be included when
+creating a new file and the template type is selected. These will appear at the
+top of the list.
+
+![Custom template dropdown menu](img/file_template_user_dropdown.png)
+
+If this feature is disabled or no templates are present, there will be
+no "Custom" section in the selection dropdown.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index d3ecfd42903..cebf36c7ec1 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Sign-up restrictions
You can block email addresses of specific domains, or whitelist only some
@@ -37,5 +41,17 @@ semicolon, comma, or a new line.
![Domain Blacklist](img/domain_blacklist.png)
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-5259]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5259
[ce-598]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/598
diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md
index e2290bf0598..a5f8d05f662 100644
--- a/doc/user/admin_area/settings/terms.md
+++ b/doc/user/admin_area/settings/terms.md
@@ -1,29 +1,35 @@
+---
+type: reference
+---
+
# Enforce accepting Terms of Service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
+An admin can enforce acceptance of a terms of service and privacy policy. When this option is enabled, new and existing users must accept the terms.
+
## Configuration
-When it is required for all users of the GitLab instance to accept the
-Terms of Service, this can be configured by an admin on the settings
-page:
+To enforce acceptance of a Terms of Service and Privacy Policy:
-![Enable enforcing Terms of Service](img/enforce_terms.png).
+1. Log in to the GitLab instance as an admin user.
+1. Go to **Admin Area > Settings > General**.
+1. Expand the **Terms of Service and Privacy Policy** section.
+1. Check the **Require all users to accept Terms of Service and Privacy Policy when they access
+GitLab.** checkbox.
+1. Input the text of the **Terms of Service and Privacy Policy**. Markdown formatting can be used in this input box.
+1. Click **Save changes**.
+1. When you are presented with the **Terms of Service** statement, click **Accept terms**.
-The terms itself can be entered using Markdown. For each update to the
-terms, a new version is stored. When a user accepts or declines the
-terms, GitLab will keep track of which version they accepted or
-declined.
+![Enable enforcing Terms of Service](img/enforce_terms.png).
-When an admin enables this feature, they will automattically be
-directed to the page to accept the terms themselves. After they
-accept, they will be directed back to the settings page.
+For each update to the terms, a new version is stored. When a user accepts or declines the terms,
+GitLab will record which version they accepted or declined.
-## New registrations
+## New users
-When this feature is enabled, a checkbox will be available in the
-sign-up form.
+When this feature is enabled, a checkbox is added to the sign-up form.
![Sign up form](img/sign_up_terms.png)
@@ -49,3 +55,15 @@ If the user was already logged in when the feature was turned on,
they will be asked to accept the terms on their next interaction.
If a user declines the terms, they will be signed out.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index 23311801790..d3c9cf7d8ff 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -1,9 +1,26 @@
+---
+type: reference
+---
+
# Third party offers
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
-Within GitLab, we inform users of available third-party offers they might find valuable in order to enhance the development of their projects.
-An example is the Google Cloud Platform free credit for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
+Within GitLab, we inform users of available third-party offers they might find valuable in order
+to enhance the development of their projects. An example is the Google Cloud Platform free credit
+for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
+
+The display of third-party offers can be toggled in the **Admin Area > Settings** page.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-The display of third-party offers can be toggled in the Admin area on the Settings page.
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index e165a120162..652d6ad2cdd 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -1,10 +1,14 @@
+---
+type: reference
+---
+
# Usage statistics
GitLab Inc. will periodically collect information about your instance in order
to perform various actions.
All statistics are opt-out, you can enable/disable them from the admin panel
-under **Admin area > Settings > Usage statistics**.
+under **Admin area > Settings > Metrics and profiling > Usage statistics**.
## Version check **[CORE ONLY]**
@@ -48,6 +52,8 @@ You can view the exact JSON payload in the administration panel. To view the pay
1. Expand **Settings** in the left sidebar and click on **Metrics and profiling**.
1. Expand **Usage statistics** and click on the **Preview payload** button.
+You can see how [the usage ping data maps to different stages of the product](https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv).
+
### Deactivate the usage ping
The usage ping is opt-out. If you want to deactivate this feature, go to
@@ -81,6 +87,18 @@ of your instance to your users.
This can be restricted to admins by selecting "Only admins" in the Instance
Statistics visibility section under **Admin area > Settings > Usage statistics**.
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ee-557]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557
[ee-735]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735
[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index 6a1e8004f87..4d1b7d0f252 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -49,5 +49,15 @@ block access to the server itself. The ports used for the protocol, be it SSH or
HTTP, will still be accessible. What GitLab does is restrict access on the
application level.
+## Allow mirrors to be set up for projects
+
+> [Introduced][ee-3586] in GitLab 10.3.
+
+This option is enabled by default. By disabling it, both pull and push mirroring will no longer
+work in every repository and can only be re-enabled on a per-project basis by an admin.
+
+![Mirror settings](img/mirror_settings.png)
+
[ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
[ce-18021]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18021
+[ee-3586]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3586
diff --git a/doc/user/application_security/container_scanning/img/container_scanning.png b/doc/user/application_security/container_scanning/img/container_scanning.png
new file mode 100644
index 00000000000..e47f62acd9d
--- /dev/null
+++ b/doc/user/application_security/container_scanning/img/container_scanning.png
Binary files differ
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
new file mode 100644
index 00000000000..adb6868516e
--- /dev/null
+++ b/doc/user/application_security/container_scanning/index.md
@@ -0,0 +1,204 @@
+# Container Scanning **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3672)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can check your Docker
+images (or more precisely the containers) for known vulnerabilities by using
+[Clair](https://github.com/coreos/clair) and [clair-scanner](https://github.com/arminc/clair-scanner),
+two open source tools for Vulnerability Static Analysis for containers.
+
+You can take advantage of Container Scanning by either [including the CI job](#including-the-provided-template) in
+your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto Container Scanning](../../../topics/autodevops/index.md#auto-container-scanning)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the Container Scanning report, compares the found vulnerabilities
+between the source and target branches, and shows the information right on the
+merge request.
+
+![Container Scanning Widget](img/container_scanning.png)
+
+## Use cases
+
+If you distribute your application with Docker, then there's a great chance
+that your image is based on other Docker images that may in turn contain some
+known vulnerabilities that could be exploited.
+
+Having an extra job in your pipeline that checks for those vulnerabilities,
+and the fact that they are displayed inside a merge request, makes it very easy
+to perform audits for your Docker-based apps.
+
+## Requirements
+
+To enable Container Scanning in your pipeline, you need:
+
+- A GitLab Runner with the
+ [`docker`](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode) or
+ [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html#running-privileged-containers-for-the-runners)
+ executor running in privileged mode. If you're using the shared Runners on GitLab.com,
+ this is enabled by default.
+- To [build and push](../../../ci/docker/using_docker_build.md#container-registry-examples)
+ your Docker image to your project's [Container Registry](../../project/container_registry.md).
+ The name of the Docker image should match the following scheme:
+
+ ```
+ $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
+ ```
+
+ The variables above can be found in the
+ [predefined environment variables](../../../ci/variables/predefined_variables.md)
+ document.
+
+## Configuring Container Scanning
+
+To enable Container Scanning in your project, define a job in your
+`.gitlab-ci.yml` file that generates the
+[Container Scanning report artifact](../../../ci/yaml/README.md#artifactsreportscontainer_scanning-ultimate).
+
+This can be done in two ways:
+
+- For GitLab 11.9 and later, including the provided
+ `Container-Scanning.gitlab-ci.yml` template (recommended).
+- Manually specifying the job definition. Not recommended unless using GitLab
+ 11.8 and earlier.
+
+### Including the provided template
+
+NOTE: **Note:**
+The CI/CD Container Scanning template is supported on GitLab 11.9 and later versions.
+For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
+
+A CI/CD [Container Scanning template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml)
+with the default Container Scanning job definition is provided as a part of your GitLab
+installation that you can [include](../../../ci/yaml/README.md#includetemplate)
+in your `.gitlab-ci.yml` file.
+
+To enable Container Scanning using the provided template, add the following to
+your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ template: Container-Scanning.gitlab-ci.yml
+```
+
+The included template will:
+
+- Create a `container_scanning` job in your CI/CD pipeline.
+- Pull the already built Docker image from your project's
+ [Container Registry](../../project/container_registry.md) (see [requirements](#requirements))
+ and scan it for possible vulnerabilities.
+
+The report will be saved as a
+[Container Scanning report artifact](../../../ci/yaml/README.md#artifactsreportscontainer_scanning-ultimate)
+that you can later download and analyze.
+Due to implementation limitations, we always take the latest Container Scanning
+artifact available. Behind the scenes, the
+[GitLab Container Scanning analyzer](https://gitlab.com/gitlab-org/security-products/container-scanning)
+is used and runs the scans.
+
+If you want to whitelist some specific vulnerabilities, you can do so by defining
+them in a YAML file named `clair-whitelist.yml`. Read more in the
+[Clair documentation](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file).
+
+### Manual job definition for GitLab 11.5 and later
+
+CAUTION: **Caution:**
+The job definition shown below is supported on GitLab 11.5 and later versions.
+However, if you're using GitLab 11.9+, it's recommended to use
+[the provided Container Scanning template](#including-the-provided-template).
+
+For GitLab 11.5 and GitLab Runner 11.5 and later, the following `container_scanning`
+job can be added:
+
+```yaml
+container_scanning:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ ## Define two new variables based on GitLab's CI/CD predefined variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
+ CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+ CI_APPLICATION_TAG: $CI_COMMIT_SHA
+ CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - docker run -d --name db arminc/clair-db:latest
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
+ - apk add -U wget ca-certificates
+ - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - mv clair-scanner_linux_amd64 clair-scanner
+ - chmod +x clair-scanner
+ - touch clair-whitelist.yml
+ - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
+ - retries=0
+ - echo "Waiting for clair daemon to start"
+ - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
+ - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ artifacts:
+ reports:
+ container_scanning: gl-container-scanning-report.json
+```
+
+### Manual job definition for GitLab 11.4 and earlier (deprecated)
+
+CAUTION: **Deprecated:**
+Before GitLab 11.5, the Container Scanning job and artifact had to be named specifically
+to automatically extract report data and show it in the merge request widget.
+While these old job definitions are still maintained, they have been deprecated
+and may be removed in the next major release, GitLab 12.0. You are strongly
+advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the Container Scanning job should look like:
+
+```yaml
+container_scanning:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ ## Define two new variables based on GitLab's CI/CD predefined variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
+ CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+ CI_APPLICATION_TAG: $CI_COMMIT_SHA
+ CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - docker run -d --name db arminc/clair-db:latest
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
+ - apk add -U wget ca-certificates
+ - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - mv clair-scanner_linux_amd64 clair-scanner
+ - chmod +x clair-scanner
+ - touch clair-whitelist.yml
+ - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
+ - retries=0
+ - echo "Waiting for clair daemon to start"
+ - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
+ - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ artifacts:
+ paths: [gl-container-scanning-report.json]
+```
+
+Alternatively, the job name could be `sast:container`
+and the artifact name could be `gl-sast-container-report.json`.
+These names have been deprecated with GitLab 11.0
+and may be removed in the next major release, GitLab 12.0.
+
+## Security Dashboard
+
+The Security Dashboard is a good place to get an overview of all the security
+vulnerabilities in your groups and projects. Read more about the
+[Security Dashboard](../security_dashboard/index.md).
+
+## Interacting with the vulnerabilities
+
+Once a vulnerability is found, you can interact with it. Read more on how to
+[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
diff --git a/doc/user/application_security/dast/img/dast_all.png b/doc/user/application_security/dast/img/dast_all.png
new file mode 100644
index 00000000000..b6edc928dc3
--- /dev/null
+++ b/doc/user/application_security/dast/img/dast_all.png
Binary files differ
diff --git a/doc/user/application_security/dast/img/dast_single.png b/doc/user/application_security/dast/img/dast_single.png
new file mode 100644
index 00000000000..26ca4bde786
--- /dev/null
+++ b/doc/user/application_security/dast/img/dast_single.png
Binary files differ
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
new file mode 100644
index 00000000000..028ff72a160
--- /dev/null
+++ b/doc/user/application_security/dast/index.md
@@ -0,0 +1,261 @@
+# Dynamic Application Security Testing (DAST) **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4348)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
+
+Running [static checks](../sast/index.md) on your code is the first step to detect
+vulnerabilities that can put the security of your code at risk. Yet, once
+deployed, your application is exposed to a new category of possible attacks,
+such as cross-site scripting or broken authentication flaws. This is where
+Dynamic Application Security Testing (DAST) comes into place.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your running web application(s)
+for known vulnerabilities using Dynamic Application Security Testing (DAST).
+
+You can take advantage of DAST by either [including the CI job](#configuring-dast) in
+your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto DAST](../../../topics/autodevops/index.md#auto-dast-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the DAST report, compares the found vulnerabilities between the source and target
+branches, and shows the information right on the merge request.
+
+![DAST Widget](img/dast_all.png)
+
+By clicking on one of the detected linked vulnerabilities, you will be able to
+see the details and the URL(s) affected.
+
+![DAST Widget Clicked](img/dast_single.png)
+
+[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_Application_Security_Testing)
+is using the popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
+to perform an analysis on your running web application.
+
+By default, DAST executes [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan) and will perform passive scanning only. It will not actively attack your application.
+
+However, DAST can be [configured](#full-scan)
+to also perform a so-called "active scan". That is, attack your application and produce a more extensive security report.
+It can be very useful combined with [Review Apps](../../../ci/review_apps/index.md).
+
+The [`dast`](https://gitlab.com/gitlab-org/security-products/dast/container_registry) Docker image in GitLab container registry is updated on a weekly basis to have all [`owasp2docker-weekly`](https://hub.docker.com/r/owasp/zap2docker-weekly/) updates in it.
+
+## Use cases
+
+It helps you automatically find security vulnerabilities in your running web
+applications while you are developing and testing your applications.
+
+## Requirements
+
+To run a DAST job, you need GitLab Runner with the
+[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
+
+## Configuring DAST
+
+To enable DAST in your project, define a job in your `.gitlab-ci.yml` file that generates the
+[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate).
+
+This can be done in two ways:
+
+- For GitLab 11.9 and later, including the provided DAST `.gitlab-ci.yml` template (recommended).
+- Manually specifying the job definition. Not recommended unless using GitLab
+ 11.8 and earlier.
+
+### Including the provided template
+
+NOTE: **Note:**
+The CI/CD DAST template is supported on GitLab 11.9 and later versions.
+For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
+
+A CI/CD [DAST template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml)
+with the default DAST job definition is provided as a part of your GitLab
+installation which you can [include](../../../ci/yaml/README.md#includetemplate)
+in your `.gitlab-ci.yml` file.
+
+To enable DAST using the provided template, add the following to your `.gitlab-ci.yml`
+file:
+
+```yaml
+include:
+ template: DAST.gitlab-ci.yml
+
+variables:
+ DAST_WEBSITE: https://example.com
+```
+
+The included template will create a `dast` job in your CI/CD pipeline and scan
+your project's source code for possible vulnerabilities.
+
+The report will be saved as a
+[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate)
+that you can later download and analyze. Due to implementation limitations we
+always take the latest DAST artifact available. Behind the scenes, the
+[GitLab DAST Docker image](https://gitlab.com/gitlab-org/security-products/dast)
+is used to run the tests on the specified URL and scan it for possible vulnerabilities.
+
+There are two ways to define the URL to be scanned by DAST:
+
+- Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
+- Add it in an `environment_url.txt` file at the root of your project.
+
+#### Authenticated scan
+
+It's also possible to authenticate the user before performing the DAST checks:
+
+```yaml
+include:
+ template: DAST.gitlab-ci.yml
+
+variables:
+ DAST_WEBSITE: https://example.com
+ DAST_AUTH_URL: https://example.com/sign-in
+ DAST_USERNAME: john.doe@example.com
+ DAST_PASSWORD: john-doe-password
+ DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
+ DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
+ DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
+```
+
+The report will be saved as a
+[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate)
+that you can later download and analyze.
+Due to implementation limitations, we always take the latest DAST artifact available.
+
+#### Full scan
+
+DAST can be configured to perform [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan), which
+includes both passive and active scanning against the same target website:
+
+```yaml
+include:
+ template: DAST.gitlab-ci.yml
+
+variables:
+ DAST_FULL_SCAN_ENABLED: "true"
+```
+
+#### Customizing the DAST settings
+
+The DAST settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
+These variables are documented in the [DAST README](https://gitlab.com/gitlab-org/security-products/dast#settings).
+
+For example:
+
+```yaml
+include:
+ template: DAST.gitlab-ci.yml
+
+variables:
+ DAST_WEBSITE: https://example.com
+ DAST_TARGET_AVAILABILITY_TIMEOUT: 120
+```
+
+Because the template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
+configuration, the last mention of the variable will take precedence.
+
+#### Overriding the DAST template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `dast` job after the
+template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: DAST.gitlab-ci.yml
+
+dast:
+ stage: dast # IMPORTANT: don't forget to add this
+ variables:
+ DAST_WEBSITE: https://example.com
+ CI_DEBUG_TRACE: "true"
+```
+
+As the DAST job belongs to a separate `dast` stage that runs after all
+[default stages](../../../ci/yaml/README.md#stages),
+don't forget to add `stage: dast` when you override the template job definition.
+
+### Manual job definition for GitLab 11.5 and later
+
+For GitLab 11.5 and GitLab Runner 11.5 and later, the following `dast`
+job can be added:
+
+```yaml
+dast:
+ image: registry.gitlab.com/gitlab-org/security-products/zaproxy
+ variables:
+ website: "https://example.com"
+ allow_failure: true
+ script:
+ - mkdir /zap/wrk/
+ - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
+ - cp /zap/wrk/gl-dast-report.json .
+ artifacts:
+ reports:
+ dast: gl-dast-report.json
+```
+
+Where the `website` variable holds the URL to run the tests against.
+
+For an authenticated scan, use the following definition:
+
+```yaml
+dast:
+ image: registry.gitlab.com/gitlab-org/security-products/zaproxy
+ variables:
+ website: "https://example.com"
+ login_url: "https://example.com/sign-in"
+ username: "john.doe@example.com"
+ password: "john-doe-password"
+ allow_failure: true
+ script:
+ - mkdir /zap/wrk/
+ - /zap/zap-baseline.py -J gl-dast-report.json -t $website
+ --auth-url $login_url
+ --auth-username $username
+ --auth-password $password || true
+ - cp /zap/wrk/gl-dast-report.json .
+ artifacts:
+ reports:
+ dast: gl-dast-report.json
+```
+
+See the [zaproxy documentation](https://gitlab.com/gitlab-org/security-products/zaproxy)
+to learn more about the authentication settings.
+
+### Manual job definition for GitLab 11.4 and earlier (deprecated)
+
+CAUTION: **Caution:**
+Before GitLab 11.5, DAST job and artifact had to be named specifically
+to automatically extract report data and show it in the merge request widget.
+While these old job definitions are still maintained they have been deprecated
+and may be removed in next major release, GitLab 12.0. You are strongly advised
+to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the job should look like:
+
+```yaml
+dast:
+ image: registry.gitlab.com/gitlab-org/security-products/zaproxy
+ variables:
+ website: "https://example.com"
+ allow_failure: true
+ script:
+ - mkdir /zap/wrk/
+ - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
+ - cp /zap/wrk/gl-dast-report.json .
+ artifacts:
+ paths: [gl-dast-report.json]
+```
+
+## Security Dashboard
+
+The Security Dashboard is a good place to get an overview of all the security
+vulnerabilities in your groups and projects. Read more about the
+[Security Dashboard](../security_dashboard/index.md).
+
+## Interacting with the vulnerabilities
+
+Once a vulnerability is found, you can interact with it. Read more on how to
+[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
diff --git a/doc/user/application_security/dependency_scanning/img/dependency_scanning.png b/doc/user/application_security/dependency_scanning/img/dependency_scanning.png
new file mode 100644
index 00000000000..18df356f846
--- /dev/null
+++ b/doc/user/application_security/dependency_scanning/img/dependency_scanning.png
Binary files differ
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
new file mode 100644
index 00000000000..d78cf778110
--- /dev/null
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -0,0 +1,243 @@
+# Dependency Scanning **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5105)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.7.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your dependencies for known
+vulnerabilities using Dependency Scanning.
+
+You can take advantage of Dependency Scanning by either [including the CI job](#including-the-provided-template)
+in your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto Dependency Scanning](../../../topics/autodevops/index.md#auto-dependency-scanning-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the Dependency Scanning report, compares the found vulnerabilities
+between the source and target branches, and shows the information right on the
+merge request.
+
+![Dependency Scanning Widget](img/dependency_scanning.png)
+
+The results are sorted by the severity of the vulnerability:
+
+1. Critical
+1. High
+1. Medium
+1. Low
+1. Unknown
+1. Everything else
+
+## Use cases
+
+It helps to automatically find security vulnerabilities in your dependencies
+while you are developing and testing your applications. For example when your
+application is using an external (open source) library which is known to be vulnerable.
+
+## Requirements
+
+To run a Dependency Scanning job, you need GitLab Runner with the
+[`docker`](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode) or
+[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html#running-privileged-containers-for-the-runners)
+executor running in privileged mode. If you're using the shared Runners on GitLab.com,
+this is enabled by default.
+
+## Supported languages and package managers
+
+The following languages and dependency managers are supported.
+
+| Language (package managers) | Scan tool |
+|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
+| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [Retire.js](https://retirejs.github.io/retire.js) |
+| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
+| Ruby ([gem](https://rubygems.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [bundler-audit](https://github.com/rubysec/bundler-audit) |
+| Java ([Maven](https://maven.apache.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
+| PHP ([Composer](https://getcomposer.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) |
+
+Some scanners require to send a list of project dependencies to GitLab's central
+servers to check for vulnerabilities. To learn more about this or to disable it,
+refer to the [GitLab Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).
+
+## Configuring Dependency Scanning
+
+To enable Dependency Scanning in your project, define a job in your `.gitlab-ci.yml`
+file that generates the
+[Dependency Scanning report artifact](../../../ci/yaml/README.md#artifactsreportsdependency_scanning-ultimate).
+
+This can be done in two ways:
+
+- For GitLab 11.9 and later, including the provided Dependency Scanning
+ `.gitlab-ci.yml` template (recommended).
+- Manually specifying the job definition. Not recommended unless using GitLab
+ 11.8 and earlier.
+
+### Including the provided template
+
+NOTE: **Note:**
+The CI/CD Dependency Scanning template is supported on GitLab 11.9 and later versions.
+For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
+
+A CI/CD [Dependency Scanning template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml)
+with the default Dependency Scanning job definition is provided as a part of your GitLab
+installation which you can [include](../../../ci/yaml/README.md#includetemplate)
+in your `.gitlab-ci.yml` file.
+
+To enable Dependency Scanning using the provided template, add the following to
+your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ template: Dependency-Scanning.gitlab-ci.yml
+```
+
+The included template will create a `dependency_scanning` job in your CI/CD
+pipeline and scan your project's source code for possible vulnerabilities.
+
+The report will be saved as a
+[Dependency Scanning report artifact](../../../ci/yaml/README.md#artifactsreportsdependency_scanning-ultimate)
+that you can later download and analyze. Due to implementation limitations, we
+always take the latest Dependency Scanning artifact available.
+
+Some security scanners require to send a list of project dependencies to GitLab
+central servers to check for vulnerabilities. To learn more about this or to
+disable it, check the
+[GitLab Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).
+
+#### Customizing the Dependency Scanning settings
+
+The Dependency Scanning settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
+These variables are documented in the
+[Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings).
+
+For example:
+
+```yaml
+include:
+ template: Dependency-Scanning.gitlab-ci.yml
+
+variables:
+ DEP_SCAN_DISABLE_REMOTE_CHECKS: true
+```
+
+Because template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
+configuration, the last mention of the variable will take precedence.
+
+#### Overriding the Dependency Scanning template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `dependency_scanning` job
+after the template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: Dependency-Scanning.gitlab-ci.yml
+
+dependency_scanning:
+ variables:
+ CI_DEBUG_TRACE: "true"
+```
+
+### Manual job definition for GitLab 11.5 and later
+
+For GitLab 11.5 and GitLab Runner 11.5 and later, the following `dependency_scanning`
+job can be added:
+
+```yaml
+dependency_scanning:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - |
+ docker run \
+ --env DS_ANALYZER_IMAGES \
+ --env DS_ANALYZER_IMAGE_PREFIX \
+ --env DS_ANALYZER_IMAGE_TAG \
+ --env DS_DEFAULT_ANALYZERS \
+ --env DEP_SCAN_DISABLE_REMOTE_CHECKS \
+ --env DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ --env DS_PULL_ANALYZER_IMAGE_TIMEOUT \
+ --env DS_RUN_ANALYZER_TIMEOUT \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code
+ dependencies: []
+ artifacts:
+ reports:
+ dependency_scanning: gl-dependency-scanning-report.json
+```
+
+You can supply many other [settings variables](https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings)
+via `docker run --env` to customize your job execution.
+
+### Manual job definition for GitLab 11.4 and earlier (deprecated)
+
+CAUTION: **Caution:**
+Before GitLab 11.5, the Dependency Scanning job and artifact had to be named specifically
+to automatically extract the report data and show it in the merge request widget.
+While these old job definitions are still maintained, they have been deprecated
+and may be removed in the next major release, GitLab 12.0. You are strongly advised
+to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the job should look like:
+
+```yaml
+dependency_scanning:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - |
+ docker run \
+ --env DS_ANALYZER_IMAGES \
+ --env DS_ANALYZER_IMAGE_PREFIX \
+ --env DS_ANALYZER_IMAGE_TAG \
+ --env DS_DEFAULT_ANALYZERS \
+ --env DS_EXCLUDED_PATHS \
+ --env DEP_SCAN_DISABLE_REMOTE_CHECKS \
+ --env DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ --env DS_PULL_ANALYZER_IMAGE_TIMEOUT \
+ --env DS_RUN_ANALYZER_TIMEOUT \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code
+ artifacts:
+ paths: [gl-dependency-scanning-report.json]
+```
+
+## Security Dashboard
+
+The Security Dashboard is a good place to get an overview of all the security
+vulnerabilities in your groups and projects. Read more about the
+[Security Dashboard](../security_dashboard/index.md).
+
+## Interacting with the vulnerabilities
+
+Once a vulnerability is found, you can interact with it. Read more on how to
+[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+
+## Dependency List
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
+An additional benefit of Dependency Scanning is the ability to get a list of your project's dependencies with their versions.
+
+This list can be generated only for [supported languages and package managers](#supported-languages-and-package-managers).
+
+To see the generated dependency list, navigate to the Dependency List page under your project's left sidebar menu **Project > Dependency List**.
+
+## Contributing to the vulnerability database
+
+You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project
+to find a vulnerability in the Gemnasium database.
+You can also [submit new vulnerabilities](https://gitlab.com/gitlab-org/security-products/gemnasium-db/blob/master/CONTRIBUTING.md). \ No newline at end of file
diff --git a/doc/user/application_security/img/create_issue_with_list_hover.png b/doc/user/application_security/img/create_issue_with_list_hover.png
new file mode 100644
index 00000000000..7d70e8299f5
--- /dev/null
+++ b/doc/user/application_security/img/create_issue_with_list_hover.png
Binary files differ
diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png
new file mode 100644
index 00000000000..64d5cf26ed2
--- /dev/null
+++ b/doc/user/application_security/img/dismissed_info.png
Binary files differ
diff --git a/doc/user/application_security/img/interactive_reports.png b/doc/user/application_security/img/interactive_reports.png
new file mode 100644
index 00000000000..373b39104db
--- /dev/null
+++ b/doc/user/application_security/img/interactive_reports.png
Binary files differ
diff --git a/doc/user/application_security/img/issue.png b/doc/user/application_security/img/issue.png
new file mode 100644
index 00000000000..6467201df3f
--- /dev/null
+++ b/doc/user/application_security/img/issue.png
Binary files differ
diff --git a/doc/user/application_security/img/vulnerability_solution.png b/doc/user/application_security/img/vulnerability_solution.png
new file mode 100644
index 00000000000..7443b9b6eea
--- /dev/null
+++ b/doc/user/application_security/img/vulnerability_solution.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
new file mode 100644
index 00000000000..679847b76d7
--- /dev/null
+++ b/doc/user/application_security/index.md
@@ -0,0 +1,114 @@
+# GitLab Secure **[ULTIMATE]**
+
+Check your application for security vulnerabilities that may lead to unauthorized access,
+data leaks, and denial of services. GitLab will perform static and dynamic tests on the
+code of your application, looking for known flaws and report them in the merge request
+so you can fix them before merging. Security teams can use dashboards to get a
+high-level view on projects and groups, and start remediation processes when needed.
+
+## Security scanning tools
+
+GitLab can scan and report any vulnerabilities found in your project.
+
+| Secure scanning tools | Description |
+|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
+| [Container Scanning](container_scanning/index.md) **[ULTIMATE]** | Scan Docker containers for known vulnerabilities. |
+| [Dependency Scanning](dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
+| [Dynamic Application Security Testing (DAST)](dast/index.md) **[ULTIMATE]** | Analyze running web applications for known vulnerabilities. |
+| [License Management](license_management/index.md) **[ULTIMATE]** | Search your project's dependencies for their licenses. |
+| [Security Dashboard](security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all your projects and groups. |
+| [Static Application Security Testing (SAST)](sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. |
+
+## Interacting with the vulnerabilities
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.8.
+
+CAUTION: **Warning:**
+This feature is currently [Alpha](https://about.gitlab.com/handbook/product/#alpha-beta-ga) and while you can start using it, it may receive important changes in the future.
+
+Each security vulnerability in the merge request report or the
+[Security Dashboard](security_dashboard/index.md) is actionable. Clicking on an
+entry, a detailed information will pop up with different possible options:
+
+- [Dismiss vulnerability](#dismissing-a-vulnerability): Dismissing a vulnerability
+ will place a <s>strikethrough</s> styling on it.
+- [Create issue](#creating-an-issue-for-a-vulnerability): The new issue will
+ have the title and description pre-populated with the information from the
+ vulnerability report and will be created as [confidential](../project/issues/confidential_issues.md) by default.
+- [Solution](#solutions-for-vulnerabilities): For some vulnerabilities
+ ([Dependency Scanning](dependency_scanning/index.md) and [Container Scanning](container_scanning/index.md))
+ a solution is provided for how to fix the vulnerability.
+
+![Interacting with security reports](img/interactive_reports.png)
+
+### Dismissing a vulnerability
+
+You can dismiss vulnerabilities by clicking the **Dismiss vulnerability** button.
+This will dismiss the vulnerability and re-render it to reflect its dismissed state.
+If you wish to undo this dismissal, you can click the **Undo dismiss** button.
+
+#### Adding a dismissal reason
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.0.
+
+When dismissing a vulnerability, it's often helpful to provide a reason for doing so.
+If you press the comment button next to **Dismiss vulnerability** in the modal, a text box will appear, allowing you to add a comment with your dismissal.
+This comment can not currently be edited or removed, but [future versions](https://gitlab.com/gitlab-org/gitlab-ee/issues/11721) will add this functionality.
+
+![Dismissed vulnerability comment](img/dismissed_info.png)
+
+### Creating an issue for a vulnerability
+
+You can create an issue for a vulnerability by selecting the **Create issue**
+button from within the vulnerability modal or using the action buttons to the right of
+a vulnerability row when in the group security dashboard.
+
+This will create a [confidential issue](../project/issues/confidential_issues.md)
+on the project this vulnerability came from and pre-fill it with some useful
+information taken from the vulnerability report. Once the issue is created, you
+will be redirected to it so you can edit, assign, or comment on it.
+
+Upon returning to the group security dashboard, you'll see that
+the vulnerability will now have an associated issue next to the name.
+
+![Linked issue in the group security dashboard](img/issue.png)
+
+### Solutions for vulnerabilities
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.7.
+
+CAUTION: **Warning:**
+Automatic Patch creation is only available for a subset of
+[Dependency Scanning](dependency_scanning/index.md). At the moment only Node.JS
+projects managed with yarn are supported.
+
+Some vulnerabilities can be fixed by applying the solution that GitLab
+automatically generates.
+
+#### Manually applying the suggested patch
+
+Some vulnerabilities can be fixed by applying a patch that is automatically
+generated by GitLab. To apply the fix:
+
+1. Click on the vulnerability.
+1. Download and review the patch file `remediation.patch`.
+2. Ensure your local project has the same commit checked out that was used to generate the patch.
+3. Run `git apply remediation.patch`.
+4. Verify and commit the changes to your branch.
+
+![Apply patch for dependency scanning](img/vulnerability_solution.png)
+
+#### Creating a merge request from a vulnerability
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9224) in
+ [GitLab Ultimate](https://about.gitlab.com/pricing) 11.9.
+
+In certain cases, GitLab will allow you to create a merge request that will
+automatically remediate the vulnerability. Any vulnerability that has a
+[solution](#solutions-for-vulnerabilities) can have a merge request created to
+automatically solve the issue.
+
+If this action is available there will be a **Create merge request** button in the vulnerability modal.
+Clicking on this button will create a merge request to apply the solution onto the source branch.
+
+![Create merge request from vulnerability](img/create_issue_with_list_hover.png)
diff --git a/doc/user/application_security/license_management/img/license_management.png b/doc/user/application_security/license_management/img/license_management.png
new file mode 100644
index 00000000000..cdce6b5fe38
--- /dev/null
+++ b/doc/user/application_security/license_management/img/license_management.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_decision.png b/doc/user/application_security/license_management/img/license_management_decision.png
new file mode 100644
index 00000000000..0763130c375
--- /dev/null
+++ b/doc/user/application_security/license_management/img/license_management_decision.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_pipeline_tab.png b/doc/user/application_security/license_management/img/license_management_pipeline_tab.png
new file mode 100644
index 00000000000..80ffca815b9
--- /dev/null
+++ b/doc/user/application_security/license_management/img/license_management_pipeline_tab.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_settings.png b/doc/user/application_security/license_management/img/license_management_settings.png
new file mode 100644
index 00000000000..b5490e59074
--- /dev/null
+++ b/doc/user/application_security/license_management/img/license_management_settings.png
Binary files differ
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
new file mode 100644
index 00000000000..8b75995c377
--- /dev/null
+++ b/doc/user/application_security/license_management/index.md
@@ -0,0 +1,264 @@
+# License Management **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
+using License Management.
+
+You can take advantage of License Management by either [including the job](#configuring-license-management)
+in your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto License Management](../../../topics/autodevops/index.md#auto-license-management-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the License Management report, compares the licenses between the
+source and target branches, and shows the information right on the merge request.
+Blacklisted licenses will be clearly visible with an `x` red icon next to them
+as well as new licenses which need a decision from you. In addition, you can
+[manually approve or blacklist](#project-policies-for-license-management)
+licenses in your project's settings.
+
+NOTE: **Note:**
+If the license management report doesn't have anything to compare to, no information
+will be displayed in the merge request area. That is the case when you add the
+`license_management` job in your `.gitlab-ci.yml` for the first time.
+Consecutive merge requests will have something to compare to and the license
+management report will be shown properly.
+
+![License Management Widget](img/license_management.png)
+
+If you are a project or group Maintainer, you can click on a license to be given
+the choice to approve it or blacklist it.
+
+![License approval decision](img/license_management_decision.png)
+
+## Use cases
+
+It helps you find what licenses your project uses in its dependencies, and decide for each of then
+whether to allow it or forbid it. For example, your application is using an external (open source)
+library whose license is incompatible with yours.
+
+## Supported languages and package managers
+
+The following languages and package managers are supported.
+
+| Language | Package managers |
+|------------|-------------------------------------------------------------------|
+| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/) |
+| Go | [Godep](https://github.com/tools/godep), go get |
+| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |
+| .NET | [Nuget](https://www.nuget.org/) |
+| Python | [pip](https://pip.pypa.io/en/stable/) |
+| Ruby | [gem](https://rubygems.org/) |
+
+## Requirements
+
+To run a License Management scanning job, you need GitLab Runner with the
+[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
+
+## Configuring License Management
+
+To enable License Management in your project, define a job in your `.gitlab-ci.yml`
+file that generates the [License Management report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate).
+
+This can be done in two ways:
+
+- For GitLab 11.9 and later, including the provided License Management `.gitlab-ci.yml` template (recommended).
+- Manually specifying the job definition. Not recommended unless using GitLab
+ 11.8 and earlier.
+
+### Including the provided template
+
+NOTE: **Note:**
+The CI/CD License Management template is supported on GitLab 11.9 and later versions.
+For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
+
+A CI/CD [License Management template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
+with the default License Management job definition is provided as a part of your GitLab
+installation which you can [include](../../../ci/yaml/README.md#includetemplate)
+in your `.gitlab-ci.yml` file.
+
+To enable License Management using the provided template, add the following to
+your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+```
+
+The included template will create a `license_management` job in your CI/CD pipeline
+and scan your dependencies to find their licenses.
+
+The report will be saved as a
+[License Management report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
+that you can later download and analyze. Due to implementation limitations, we
+always take the latest License Management artifact available. Behind the scenes, the
+[GitLab License Management Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
+is used to detect the languages/frameworks and in turn analyzes the licenses.
+
+#### Installing custom dependencies
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+The `license_management` image already embeds many auto-detection scripts, languages,
+and packages. Nevertheless, it's almost impossible to cover all cases for all projects.
+That's why sometimes it's necessary to install extra packages, or to have extra steps
+in the project automated setup, like the download and installation of a certificate.
+For that, a `LICENSE_MANAGEMENT_SETUP_CMD` environment variable can be passed to the container,
+with the required commands to run before the license detection.
+
+If present, this variable will override the setup step necessary to install all the packages
+of your application (e.g.: for a project with a `Gemfile`, the setup step could be
+`bundle install`).
+
+For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+variables:
+ LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
+```
+
+In this example, `my-custom-install-script.sh` is a shell script at the root
+directory of your project.
+
+#### Overriding the template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `license_management` job
+after the template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ CI_DEBUG_TRACE: "true"
+```
+
+#### Configuring Maven projects
+
+The License Management tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
+the command line arguments to pass to the `mvn install` command which is executed under the hood.
+Feel free to use it for the customization of Maven execution. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ MAVEN_CLI_OPTS: --debug
+```
+
+`mvn install` runs through all of the [build life cycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
+stages prior to `install`, including `test`. Running unit tests is not directly
+necessary for the license scanning purposes and consumes time, so it's skipped
+by having the default value of `MAVEN_CLI_OPTS` as `-DskipTests`. If you want
+to supply custom `MAVEN_CLI_OPTS` and skip tests at the same time, don't forget
+to explicitly add `-DskipTests` to your options.
+If you still need to run tests during `mvn install`, add `-DskipTests=false` to
+`MAVEN_CLI_OPTS`.
+
+### Manual job definition for GitLab 11.5 and later
+
+For GitLab 11.5 and GitLab Runner 11.5 and later, the following `license_management`
+job can be added:
+
+```yaml
+license_management:
+ image:
+ name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
+ entrypoint: [""]
+ stage: test
+ allow_failure: true
+ script:
+ - /run.sh analyze .
+ artifacts:
+ reports:
+ license_management: gl-license-management-report.json
+```
+
+If you want to install custom project dependencies via the `SETUP_CMD` variable:
+
+```yaml
+license_management:
+ image:
+ name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
+ entrypoint: [""]
+ stage: test
+ variables:
+ SETUP_CMD: ./my-custom-install-script.sh
+ allow_failure: true
+ script:
+ - /run.sh analyze .
+ artifacts:
+ reports:
+ license_management: gl-license-management-report.json
+```
+
+### Manual job definition for GitLab 11.4 and earlier (deprecated)
+
+CAUTION: **Caution:**
+Before GitLab 11.5, the License Management job and artifact had to be named specifically
+to automatically extract the report data and show it in the merge request widget.
+While these old job definitions are still maintained, they have been deprecated
+and may be removed in the next major release, GitLab 12.0. You are strongly advised
+to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the job should look like:
+
+```yaml
+license_management:
+ image:
+ name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
+ entrypoint: [""]
+ stage: test
+ allow_failure: true
+ script:
+ - /run.sh analyze .
+ artifacts:
+ paths: [gl-license-management-report.json]
+```
+
+## Project policies for License Management
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+From the project's settings:
+
+- The list of licenses and their status can be managed.
+- Licenses can be manually approved or blacklisted.
+
+To approve or blacklist a license:
+
+1. Either use the **Manage licenses** button in the merge request widget, or
+ navigate to the project's **Settings > CI/CD** and expand the
+ **License Management** section.
+1. Click the **Add a license** button.
+1. In the **License name** dropdown, either:
+ - Select one of the available licenses. You can search for licenses in the field
+ at the top of the list.
+ - Enter arbitrary text in the field at the top of the list. This will cause the text to be
+ added as a license name to the list.
+1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
+ the selected license.
+
+ ![License Management Settings](img/license_management_settings.png)
+
+## License Management report under pipelines
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
+
+From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
+pipeline ID that has a `license_management` job to see the Licenses tab with the listed
+licenses (if any).
+
+![License Management Pipeline Tab](img/license_management_pipeline_tab.png)
diff --git a/doc/user/application_security/sast/img/sast.png b/doc/user/application_security/sast/img/sast.png
new file mode 100644
index 00000000000..2c75592c32a
--- /dev/null
+++ b/doc/user/application_security/sast/img/sast.png
Binary files differ
diff --git a/doc/user/application_security/sast/img/security_report.png b/doc/user/application_security/sast/img/security_report.png
new file mode 100644
index 00000000000..ba41b707238
--- /dev/null
+++ b/doc/user/application_security/sast/img/security_report.png
Binary files differ
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
new file mode 100644
index 00000000000..db328262aba
--- /dev/null
+++ b/doc/user/application_security/sast/index.md
@@ -0,0 +1,254 @@
+# Static Application Security Testing (SAST) **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3775)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.3.
+
+NOTE: **4 of the top 6 attacks were application based.**
+Download our whitepaper,
+["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+to learn how to protect your organization.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your source code for known
+vulnerabilities using Static Application Security Testing (SAST).
+
+You can take advantage of SAST by either [including the CI job](#configuring-sast) in
+your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto SAST](../../../topics/autodevops/index.md#auto-sast-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the SAST report, compares the found vulnerabilities between the
+source and target branches, and shows the information right on the merge request.
+
+![SAST Widget](img/sast.png)
+
+The results are sorted by the priority of the vulnerability:
+
+1. Critical
+1. High
+1. Medium
+1. Low
+1. Unknown
+1. Everything else
+
+## Use cases
+
+- Your code has a potentially dangerous attribute in a class, or unsafe code
+ that can lead to unintended code execution.
+- Your application is vulnerable to cross-site scripting (XSS) attacks that can
+ be leveraged to unauthorized access to session data.
+
+## Requirements
+
+To run a SAST job, you need GitLab Runner with the
+[`docker`](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode) or
+[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html#running-privileged-containers-for-the-runners)
+executor running in privileged mode. If you're using the shared Runners on GitLab.com,
+this is enabled by default.
+
+## Supported languages and frameworks
+
+The following table shows which languages, package managers and frameworks are supported and which tools are used.
+
+| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
+|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------|
+| .NET | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
+| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
+| C/C++ | [Flawfinder](https://www.dwheeler.com/flawfinder/) | 10.7 |
+| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
+| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
+| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
+| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT) |
+| Javascript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 |
+| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
+| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
+| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
+| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3 |
+| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven) |
+| Typescript | [TSLint config security](https://github.com/webschik/tslint-config-security/) | 11.9 |
+
+NOTE: **Note:**
+The Java analyzers can also be used for variants like the
+[Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html),
+[Grails](https://grails.org/) and the [Maven wrapper](https://github.com/takari/maven-wrapper).
+
+## Configuring SAST
+
+To enable SAST in your project, define a job in your `.gitlab-ci.yml` file that generates the
+[SAST report artifact](../../../ci/yaml/README.md#artifactsreportssast-ultimate).
+
+This can be done in two ways:
+
+- For GitLab 11.9 and later, including the provided SAST `.gitlab-ci.yml` template (recommended).
+- Manually specifying the job definition. Not recommended unless using GitLab
+ 11.8 and earlier.
+
+### Including the provided template
+
+NOTE: **Note:**
+The CI/CD SAST template is supported on GitLab 11.9 and later versions.
+For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
+
+A CI/CD [SAST template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml)
+with the default SAST job definition is provided as a part of your GitLab
+installation which you can [include](../../../ci/yaml/README.md#includetemplate)
+in your `.gitlab-ci.yml` file.
+
+To enable SAST using the provided template, add the following to your `.gitlab-ci.yml`
+file:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+```
+
+The included template will create a `sast` job in your CI/CD pipeline and scan
+your project's source code for possible vulnerabilities.
+
+The report will be saved as a
+[SAST report artifact](../../../ci/yaml/README.md#artifactsreportssast-ultimate)
+that you can later download and analyze. Due to implementation limitations, we
+always take the latest SAST artifact available. Behind the scenes, the
+[GitLab SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast)
+is used to detect the languages/frameworks and in turn runs the matching scan tools.
+
+#### Customizing the SAST settings
+
+The SAST settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
+These variables are documented in the
+[SAST tool documentation](https://gitlab.com/gitlab-org/security-products/sast#settings).
+
+In the following example, we include the SAST template and at the same time we
+set the `SAST_GOSEC_LEVEL` variable to `2`:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+variables:
+ SAST_GOSEC_LEVEL: 2
+```
+
+Because the template is [evaluated before](../../../ci/yaml/README.md#include)
+the pipeline configuration, the last mention of the variable will take precedence.
+
+#### Overriding the SAST template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `sast` job after the
+template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+sast:
+ variables:
+ CI_DEBUG_TRACE: "true"
+```
+
+### Manual job definition for GitLab 11.5 and later
+
+For GitLab 11.5 and GitLab Runner 11.5 and later, the following `sast`
+job can be added:
+
+```yaml
+sast:
+ stage: test
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - |
+ docker run \
+ --env SAST_ANALYZER_IMAGES \
+ --env SAST_ANALYZER_IMAGE_PREFIX \
+ --env SAST_ANALYZER_IMAGE_TAG \
+ --env SAST_DEFAULT_ANALYZERS \
+ --env SAST_EXCLUDED_PATHS \
+ --env SAST_BANDIT_EXCLUDED_PATHS \
+ --env SAST_BRAKEMAN_LEVEL \
+ --env SAST_GOSEC_LEVEL \
+ --env SAST_FLAWFINDER_LEVEL \
+ --env SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ --env SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
+ --env SAST_RUN_ANALYZER_TIMEOUT \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
+ dependencies: []
+ artifacts:
+ reports:
+ sast: gl-sast-report.json
+```
+
+You can supply many other [settings variables](https://gitlab.com/gitlab-org/security-products/sast#settings)
+via `docker run --env` to customize your job execution.
+
+## Manual job definition for GitLab 11.4 and earlier (deprecated)
+
+CAUTION: **Deprecated:**
+Before GitLab 11.5, the SAST job and artifact had to be named specifically
+to automatically extract report data and show it in the merge request widget.
+While these old job definitions are still maintained, they have been deprecated
+and may be removed in the next major release, GitLab 12.0. You are strongly
+advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the SAST job should look like:
+
+```yaml
+sast:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - docker run
+ --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
+ --volume "$PWD:/code"
+ --volume /var/run/docker.sock:/var/run/docker.sock
+ "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
+ artifacts:
+ paths: [gl-sast-report.json]
+```
+
+## Secret detection
+
+GitLab is also able to detect secrets and credentials that have been unintentionally pushed to the repository.
+For example, an API key that allows write access to third-party deployment environments.
+
+This check is performed by a specific analyzer during the `sast` job. It runs regardless of the programming
+language of your app, and you don't need to change anything to your
+CI/CD configuration file to turn it on. Results are available in the SAST report.
+
+GitLab currently includes [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) checks.
+
+## Security report under pipelines
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3776)
+in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.6.
+
+Visit any pipeline page which has a `sast` job and you will be able to see
+the security report tab with the listed vulnerabilities (if any).
+
+![Security Report](img/security_report.png)
+
+## Security Dashboard
+
+The Security Dashboard is a good place to get an overview of all the security
+vulnerabilities in your groups and projects. Read more about the
+[Security Dashboard](../security_dashboard/index.md).
+
+## Interacting with the vulnerabilities
+
+Once a vulnerability is found, you can interact with it. Read more on how to
+[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
diff --git a/doc/user/application_security/security_dashboard/img/dashboard.png b/doc/user/application_security/security_dashboard/img/dashboard.png
new file mode 100644
index 00000000000..a75168b1ce4
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
new file mode 100644
index 00000000000..f0dad6c54d0
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
new file mode 100644
index 00000000000..19eeb06a259
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -0,0 +1,104 @@
+# GitLab Security Dashboard **[ULTIMATE]**
+
+The Security Dashboard is a good place to get an overview of all the security
+vulnerabilities in your groups and projects.
+
+You can also drill down into a vulnerability and get extra information, see which
+project it comes from, the file it's in, and various metadata to help you analyze
+the risk. You can also action these vulnerabilities by creating an issue for them,
+or by dismissing them.
+
+To benefit from the Security Dashboard you must first configure one of the
+[security reports](../index.md).
+
+## Supported reports
+
+The Security Dashboard supports the following reports:
+
+- [Container Scanning](../container_scanning/index.md)
+- [DAST](../dast/index.md)
+- [Dependency Scanning](../dependency_scanning/index.md)
+- [SAST](../sast/index.md)
+
+## Requirements
+
+To use the project or group security dashboard:
+
+1. At least one project inside a group must be configured with at least one of
+ the [supported reports](#supported-reports).
+2. The configured jobs must use the [new `reports` syntax](../../../ci/yaml/README.md#artifactsreports).
+3. [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or newer must be used.
+ If you're using the shared Runners on GitLab.com, this is already the case.
+
+## Project Security Dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6165) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.1.
+
+At the project level, the Security Dashboard displays the latest security reports
+for your project. Use it to find and fix vulnerabilities affecting the
+[default branch](../../project/repository/branches/index.md#default-branch).
+
+![Project Security Dashboard](img/project_security_dashboard.png)
+
+## Group Security Dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in
+ [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
+
+The group Security Dashboard gives an overview of the vulnerabilities of all the
+projects in a group and its subgroups.
+
+First, navigate to the Security Dashboard found under your group's
+**Overview > Security Dashboard**.
+
+Once you're on the dashboard, at the top you should see a series of filters for:
+
+- Severity
+- Confidence
+- Report type
+- Project
+
+![dashboard with action buttons and metrics](img/dashboard.png)
+
+Selecting one or more filters will filter the results in this page.
+The first section is an overview of all the vulnerabilities, grouped by severity.
+Underneath this overview is a timeline chart that shows how many open
+vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
+90 days, with the default being 90. Hover over the chart to get more details about
+the open vulnerabilities at a specific time.
+
+Finally, there is a list of all the vulnerabilities in the group, sorted by severity.
+In that list, you can see the severity of the vulnerability, its name, its
+confidence (likelihood of the vulnerability to be a positive one), and the project
+it's from.
+
+If you hover over a row, there will appear some actions you can take:
+
+- "More info"
+- "Create issue"
+- "Dismiss vulnerability"
+
+Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+
+## Keeping the dashboards up to date
+
+The Security Dashboard displays information from the results of the most recent
+security scan on the [default branch](../../project/repository/branches/index.md#default-branch),
+which means that security scans are performed every time the branch is updated.
+
+If the default branch is updated infrequently, scans are run infrequently and the
+information on the Security Dashboard can become outdated as new vulnerabilities
+are discovered.
+
+To ensure the information on the Security Dashboard is regularly updated,
+[configure a scheduled pipeline](../../project/pipelines/schedules.md) to run a
+daily security scan. This will update the information displayed on the Security
+Dashboard regardless of how often the default branch is updated.
+
+That way, reports are created even if no code change happens.
+
+## Security scans using Auto DevOps
+
+When using [Auto DevOps](../../../topics/autodevops/index.md), use
+[special environment variables](../../../topics/autodevops/index.md#environment-variables)
+to configure daily security scans.
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
new file mode 100644
index 00000000000..b520c4fb579
--- /dev/null
+++ b/doc/user/clusters/applications.md
@@ -0,0 +1,290 @@
+# GitLab Managed Apps
+
+GitLab provides **GitLab Managed Apps**, a one-click install for various applications which can
+be added directly to your configured cluster. These applications are
+needed for [Review Apps](../../ci/review_apps/index.md) and
+[deployments](../../ci/environments.md) when using [Auto DevOps](../../topics/autodevops/index.md).
+You can install them after you
+[create a cluster](../project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+
+## Installing applications
+
+Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace.
+This namespace:
+
+- Is different from the namespace used for project deployments.
+- Is created once.
+- Has a non-configurable name.
+
+To see a list of available applications to install:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+
+Install Helm first as it's used to install other applications.
+
+NOTE: **Note:**
+As of GitLab 11.6, Helm will be upgraded to the latest version supported
+by GitLab before installing any of the applications.
+
+The following applications can be installed:
+
+- [Helm](#helm)
+- [Ingress](#ingress)
+- [Cert-Manager](#cert-manager)
+- [Prometheus](#prometheus)
+- [GitLab Runner](#gitlab-runner)
+- [JupyterHub](#jupyterhub)
+- [Knative](#knative)
+
+With the exception of Knative, the applications will be installed in a dedicated
+namespace called `gitlab-managed-apps`.
+
+NOTE: **Note:**
+Some applications are installable only for a project-level cluster.
+Support for installing these applications in a group-level cluster is
+planned for future releases.
+For updates, see [the issue tracking
+progress](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989).
+
+CAUTION: **Caution:**
+If you have an existing Kubernetes cluster with Helm already installed,
+you should be careful as GitLab cannot detect it. In this case, installing
+Helm via the applications will result in the cluster having it twice, which
+can lead to confusion during deployments.
+
+### Helm
+
+> - Available for project-level clusters since GitLab 10.2.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Helm](https://docs.helm.sh/) is a package manager for Kubernetes and is
+required to install all the other applications. It is installed in its
+own pod inside the cluster which can run the `helm` CLI in a safe
+environment.
+
+### Cert-Manager
+
+> - Available for project-level clusters since GitLab 11.6.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Cert-Manager](https://docs.cert-manager.io/en/latest/) is a native
+Kubernetes certificate management controller that helps with issuing
+certificates. Installing Cert-Manager on your cluster will issue a
+certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that
+certificates are valid and up-to-date.
+
+NOTE: **Note:**
+The
+[stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/cert_manager/values.yaml)
+file.
+
+### GitLab Runner
+
+> - Available for project-level clusters since GitLab 10.6.
+> - Available for group-level clusters since GitLab 11.10.
+
+[GitLab Runner](https://docs.gitlab.com/runner/) is the open source
+project that is used to run your jobs and send the results back to
+GitLab. It is used in conjunction with [GitLab
+CI/CD](../../ci/README.md), the open-source continuous integration
+service included with GitLab that coordinates the jobs. When installing
+the GitLab Runner via the applications, it will run in **privileged
+mode** by default. Make sure you read the [security
+implications](../project/clusters/index.md/#security-implications) before doing so.
+
+NOTE: **Note:**
+The
+[runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
+file.
+
+### Ingress
+
+> - Available for project-level clusters since GitLab 10.2.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Ingress](https://kubernetes.github.io/ingress-nginx/) can provide load
+balancing, SSL termination, and name-based virtual hosting. It acts as a
+web proxy for your applications and is useful if you want to use [Auto
+DevOps] or deploy your own web apps.
+
+NOTE: **Note:**
+The
+[stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/ingress/values.yaml)
+file.
+
+### JupyterHub
+
+> Available for project-level clusters since GitLab 11.0.
+
+[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a
+multi-user service for managing notebooks across a team. [Jupyter
+Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a
+web-based interactive programming environment used for data analysis,
+visualization, and machine learning.
+
+Authentication will be enabled only for [project
+members](../project/members/index.md) with [Developer or
+higher](../permissions.md) access to the project.
+
+We use a [custom Jupyter
+image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile)
+that installs additional useful packages on top of the base Jupyter. You
+will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix).
+
+More information on
+creating executable runbooks can be found in [our Runbooks
+documentation](../project/clusters/runbooks/index.md#executable-runbooks). Note that
+Ingress must be installed and have an IP address assigned before
+JupyterHub can be installed.
+
+NOTE: **Note:**
+The
+[jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/jupyter/values.yaml)
+file.
+
+#### Jupyter Git Integration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28783) in GitLab 12 for project-level clusters.
+
+When installing JupyterHub onto your Kubernetes cluster, [JupyterLab's Git extension](https://github.com/jupyterlab/jupyterlab-git)
+is automatically provisioned and configured using the authenticated user's:
+
+- Name
+- Email
+- Newly created access token
+
+JupyterLab's Git extension enables full version control of your notebooks as well as issuance of Git commands within Jupyter.
+Git commands can be issued via the **Git** tab on the left panel or via Jupyter's command line prompt.
+
+NOTE: **Note:**
+JupyterLab's Git extension stores the user token in the JupyterHub DB in encrypted format
+and in the single user Jupyter instance as plain text. This is because [Git requires storing
+credentials as plain text](https://git-scm.com/docs/git-credential-store). Potentially, if
+a nefarious user finds a way to read from the file system in the single user Jupyter instance
+they could retrieve the token.
+
+![Jupyter's Git Extension](img/jupyter-git-extension.gif)
+
+You can clone repositories from the files tab in Jupyter:
+
+![Jupyter clone repository](img/jupyter-gitclone.png)
+
+### Knative
+
+> Available for project-level clusters since GitLab 11.5.
+
+[Knative](https://cloud.google.com/knative) provides a platform to
+create, deploy, and manage serverless workloads from a Kubernetes
+cluster. It is used in conjunction with, and includes
+[Istio](https://istio.io) to provide an external IP address for all
+programs hosted by Knative.
+
+You will be prompted to enter a wildcard
+domain where your applications will be exposed. Configure your DNS
+server to use the external IP address for that domain. For any
+application created and installed, they will be accessible as
+`<program_name>.<kubernetes_namespace>.<domain_name>`. This will require
+your kubernetes cluster to have [RBAC
+enabled](../project/clusters/index.md#rbac-cluster-resources).
+
+NOTE: **Note:**
+The
+[knative/knative](https://storage.googleapis.com/triggermesh-charts)
+chart is used to install this application.
+
+### Prometheus
+
+> - Available for project-level clusters since GitLab 10.4.
+> - Available for group-level clusters since GitLab 11.11.
+
+[Prometheus](https://prometheus.io/docs/introduction/overview/) is an
+open-source monitoring and alerting system useful to supervise your
+deployed applications.
+
+NOTE: **Note:**
+The
+[stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/prometheus/values.yaml)
+file.
+
+## Upgrading applications
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
+in GitLab 11.8.
+
+The applications below can be upgraded.
+
+| Application | GitLab version |
+| ----------- | -------------- |
+| Runner | 11.8+ |
+
+To upgrade an application:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+1. Select your cluster.
+1. If an upgrade is available, the **Upgrade** button is displayed. Click the button to upgrade.
+
+NOTE: **Note:**
+Upgrades will reset values back to the values built into the `runner`
+chart plus the values set by
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
+
+## Uninstalling applications
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60665) in
+> GitLab 11.11.
+
+The applications below can be uninstalled.
+
+| Application | GitLab version | Notes |
+| ----------- | -------------- | ----- |
+| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
+
+To uninstall an application:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+1. Select your cluster.
+1. Click the **Uninstall** button for the application.
+
+Support for uninstalling all applications is planned for progressive rollout.
+To follow progress, see [the relevant
+epic](https://gitlab.com/groups/gitlab-org/-/epics/1201).
+
+## Troubleshooting applications
+
+Applications can fail with the following error:
+
+```text
+Error: remote error: tls: bad certificate
+```
+
+To avoid installation errors:
+
+- Before starting the installation of applications, make sure that time is synchronized
+ between your GitLab server and your Kubernetes cluster.
+- Ensure certificates are not out of sync. When installing applications, GitLab expects a new cluster with no previous installation of Helm.
+
+ You can confirm that the certificates match via `kubectl`:
+
+ ```sh
+ kubectl get configmaps/values-content-configuration-ingress -n gitlab-managed-apps -o \
+ "jsonpath={.data['cert\.pem']}" | base64 -d > a.pem
+ kubectl get secrets/tiller-secret -n gitlab-managed-apps -o "jsonpath={.data['ca\.crt']}" | base64 -d > b.pem
+ diff a.pem b.pem
+ ```
+
diff --git a/doc/user/clusters/img/jupyter-git-extension.gif b/doc/user/clusters/img/jupyter-git-extension.gif
new file mode 100644
index 00000000000..13a88d97425
--- /dev/null
+++ b/doc/user/clusters/img/jupyter-git-extension.gif
Binary files differ
diff --git a/doc/user/clusters/img/jupyter-gitclone.png b/doc/user/clusters/img/jupyter-gitclone.png
new file mode 100644
index 00000000000..41d467f806a
--- /dev/null
+++ b/doc/user/clusters/img/jupyter-gitclone.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_resolve.png b/doc/user/discussions/img/mr_review_resolve.png
new file mode 100644
index 00000000000..34176f3fa8e
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_resolve.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_resolve2.png b/doc/user/discussions/img/mr_review_resolve2.png
new file mode 100644
index 00000000000..e4adb5f2c2d
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_resolve2.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_second_comment.png b/doc/user/discussions/img/mr_review_second_comment.png
new file mode 100644
index 00000000000..920ea05ad66
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_second_comment.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_second_comment_added.png b/doc/user/discussions/img/mr_review_second_comment_added.png
new file mode 100644
index 00000000000..1fb54348547
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_second_comment_added.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_start.png b/doc/user/discussions/img/mr_review_start.png
new file mode 100644
index 00000000000..38b44bda0d2
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_start.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_unresolve.png b/doc/user/discussions/img/mr_review_unresolve.png
new file mode 100644
index 00000000000..da895ceb89f
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_unresolve.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_unresolve2.png b/doc/user/discussions/img/mr_review_unresolve2.png
new file mode 100644
index 00000000000..a824b806e4a
--- /dev/null
+++ b/doc/user/discussions/img/mr_review_unresolve2.png
Binary files differ
diff --git a/doc/user/discussions/img/multi-line-suggestion-preview.png b/doc/user/discussions/img/multi-line-suggestion-preview.png
new file mode 100644
index 00000000000..4288d0ba034
--- /dev/null
+++ b/doc/user/discussions/img/multi-line-suggestion-preview.png
Binary files differ
diff --git a/doc/user/discussions/img/multi-line-suggestion-syntax.png b/doc/user/discussions/img/multi-line-suggestion-syntax.png
new file mode 100644
index 00000000000..df0c99b84ef
--- /dev/null
+++ b/doc/user/discussions/img/multi-line-suggestion-syntax.png
Binary files differ
diff --git a/doc/user/discussions/img/pending_review_comment.png b/doc/user/discussions/img/pending_review_comment.png
new file mode 100644
index 00000000000..916ef5b7452
--- /dev/null
+++ b/doc/user/discussions/img/pending_review_comment.png
Binary files differ
diff --git a/doc/user/discussions/img/reply_to_comment.gif b/doc/user/discussions/img/reply_to_comment.gif
new file mode 100644
index 00000000000..c62f7fdb5fe
--- /dev/null
+++ b/doc/user/discussions/img/reply_to_comment.gif
Binary files differ
diff --git a/doc/user/discussions/img/reply_to_comment_button.png b/doc/user/discussions/img/reply_to_comment_button.png
new file mode 100644
index 00000000000..d362b19785c
--- /dev/null
+++ b/doc/user/discussions/img/reply_to_comment_button.png
Binary files differ
diff --git a/doc/user/discussions/img/review_comment_quickactions.png b/doc/user/discussions/img/review_comment_quickactions.png
new file mode 100644
index 00000000000..bd9880c329a
--- /dev/null
+++ b/doc/user/discussions/img/review_comment_quickactions.png
Binary files differ
diff --git a/doc/user/discussions/img/review_preview.png b/doc/user/discussions/img/review_preview.png
new file mode 100644
index 00000000000..4bf53a81b9c
--- /dev/null
+++ b/doc/user/discussions/img/review_preview.png
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 1e2fad1efe9..5d69efc3600 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -5,36 +5,42 @@ The ability to contribute conversationally is offered throughout GitLab.
You can leave a comment in the following places:
- issues
+- epics **[ULTIMATE]**
- merge requests
- snippets
- commits
- commit diffs
-The comment area supports [Markdown] and [quick actions]. One can edit their
-own comment at any time, and anyone with [Maintainer access level][permissions] or
-higher can also edit a comment made by someone else.
+There are standard comments, and you also have the option to create a comment
+in the form of a threaded discussion. A comment can also be [turned into a discussion](#start-a-discussion-by-replying-to-a-standard-comment)
+when it receives a reply.
-You could also reply to the notification email in order to reply to a comment,
-provided that [Reply by email] is configured by your GitLab admin. This also
-supports [Markdown] and [quick actions] as if replied from the web.
+The comment area supports [Markdown] and [quick actions]. You can edit your own
+comment at any time, and anyone with [Maintainer access level][permissions] or
+higher can also edit a comment made by someone else.
-Apart from the standard comments, you also have the option to create a comment
-in the form of a resolvable or threaded discussion.
+You can also reply to a comment notification email to reply to the comment if
+[Reply by email] is configured for your GitLab instance. Replying to a standard comment
+creates another standard comment. Replying to a discussion comment creates a reply in the
+discussion thread. Email replies support [Markdown] and [quick actions], just as if you replied from the web.
-## Resolvable discussions
+## Resolvable comments and discussions
> **Notes:**
+>
> - The main feature was [introduced][ce-5022] in GitLab 8.11.
> - Resolvable discussions can be added only to merge request diffs.
Discussion resolution helps keep track of progress during planning or code review.
-Resolving comments prevents you from forgetting to address feedback and lets you
-hide discussions that are no longer relevant.
-!["A discussion between two people on a piece of code"][discussion-view]
+Every standard comment or discussion thread in merge requests, commits, commit diffs, and
+snippets is initially displayed as unresolved. They can then be individually resolved by anyone
+with at least Developer access to the project or by the author of the change being reviewed.
-Comments and discussions can be resolved by anyone with at least Developer
-access to the project or the author of the merge request.
+The need to resolve all standard comments or discussions prevents you from forgetting
+to address feedback and lets you hide discussions that are no longer relevant.
+
+!["A discussion between two people on a piece of code"][discussion-view]
### Commit discussions in the context of a merge request
@@ -272,7 +278,76 @@ edit existing comments. Non-team members are restricted from adding or editing c
| :-----------: | :----------: |
| ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) |
-Additionally locked issues can not be reopened.
+Additionally, locked issues and merge requests can not be reopened.
+
+## Merge Request Reviews **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4213) in GitLab 11.4.
+
+When looking at a Merge Request diff, you are able to start a review.
+This allows you to create comments inside a Merge Request that are **only visible to you** until published,
+in order to allow you to submit them all as a single action.
+
+### Starting a review
+
+In order to start a review, simply write a comment on a diff as normal under the **Changes** tab
+in an MR and click on the **Start a review** button.
+
+![Starting a review](img/mr_review_start.png)
+
+Once a review is started, you will see any comments that are part of this review marked `Pending`.
+All comments that are part of a review show two buttons:
+
+- **Submit review**: Submits all comments that are part of the review, making them visible to other users.
+- **Add comment now**: Submits the specific comment as a regular comment instead of as part of the review.
+
+![A comment that is part of a review](img/pending_review_comment.png)
+
+You can use [quick actions] inside review comments. The comment will show the actions that will be performed once published.
+
+![A review comment with quick actions](img/review_comment_quickactions.png)
+
+To add more comments to a review, start writing a comment as normal and click the **Add to review** button.
+
+![Adding a second comment to a review](img/mr_review_second_comment.png)
+
+This will add the comment to the review.
+
+![Second review comment](img/mr_review_second_comment_added.png)
+
+### Resolving/Unresolving discussions
+
+Review comments can also resolve/unresolve [resolvable discussions](#resolvable-comments-and-discussions).
+When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
+the discussion once published.
+
+![Resolve checkbox](img/mr_review_resolve.png)
+![Unresolve checkbox](img/mr_review_unresolve.png)
+
+If a particular pending comment will resolve or unresolve the discussion, this will be shown on the pending
+comment itself.
+
+![Resolve status](img/mr_review_resolve2.png)
+![Unresolve status](img/mr_review_unresolve2.png)
+
+### Submitting a review
+
+If you have any comments that have not been submitted, you will see a bar at the
+bottom of the screen with two buttons:
+
+- **Discard**: Discards all comments that have not been submitted.
+- **Finish review**: Opens a list of comments ready to be submitted for review.
+ Clicking **Submit review** will publish all comments. Any quick actions
+ submitted are performed at this time.
+
+Alternatively, every pending comment has a button to finish the entire review.
+
+![Review submission](img/review_preview.png)
+
+Submitting the review will send a single email to every notifiable user of the
+merge request with all the comments associated to it.
+
+Replying to this email will, consequentially, create a new comment on the associated merge request.
## Filtering notes
@@ -310,11 +385,6 @@ the Merge Request authored by the user that applied them.
![Add a new comment](img/insert_suggestion.png)
- > **Note:**
- The suggestion will only affect the commented line. Multi-line
- suggestions are currently not supported. Will be introduced by
- [#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310).
-
1. In the comment, add your suggestion to the pre-populated code block:
![Add a suggestion into a code block tagged properly](img/make_suggestion.png)
@@ -326,19 +396,58 @@ the Merge Request authored by the user that applied them.
![Apply suggestions](img/suggestion.png)
- > **Note:**
- Discussions are _not_ automatically resolved. Will be introduced by
- [#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405).
-
Once the author applies a suggestion, it will be marked with the **Applied** label,
-and GitLab will create a new commit with the message `Apply suggestion to <file-name>`
-and push the suggested change directly into the codebase in the merge request's branch.
+the discussion will be automatically resolved, and GitLab will create a new commit
+with the message `Apply suggestion to <file-name>` and push the suggested change
+directly into the codebase in the merge request's branch.
[Developer permission](../permissions.md) is required to do so.
> **Note:**
Custom commit messages will be introduced by
[#54404](https://gitlab.com/gitlab-org/gitlab-ce/issues/54404).
+### Multi-line suggestions
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310) in GitLab 11.10.
+
+Reviewers can also suggest changes to multiple lines with a single suggestion
+within Merge Request diff discussions by adjusting the range offsets. The
+offsets are relative to the position of the diff discussion, and specify the
+range to be replaced by the suggestion when it is applied.
+
+![Multi-line suggestion syntax](img/multi-line-suggestion-syntax.png)
+
+In the example above, the suggestion covers three lines above and four lines
+below the commented line. When applied, it would replace from 3 lines _above_
+to 4 lines _below_ the commented line, with the suggested change.
+
+![Multi-line suggestion preview](img/multi-line-suggestion-preview.png)
+
+NOTE: **Note:**
+Suggestions covering multiple lines are limited to 100 lines _above_ and 100
+lines _below_ the commented diff line, allowing up to 200 changed lines per
+suggestion.
+
+## Start a discussion by replying to a standard comment
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30299) in GitLab 11.9
+
+To reply to a standard (non-discussion) comment, you can use the **Reply to comment** button.
+
+![Reply to comment button](img/reply_to_comment_button.png)
+
+The **Reply to comment** button is only displayed if you have permissions to reply to an existing discussion, or start a discussion from a standard comment.
+
+Clicking on the **Reply to comment** button will bring the reply area into focus and you can type your reply.
+
+![Reply to comment feature](img/reply_to_comment.gif)
+
+Relying to a non-discussion comment will convert the non-discussion comment to a
+threaded discussion once the reply is submitted. This conversion is considered an edit
+to the original comment, so a note about when it was last edited will appear underneath it.
+
+This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff discussions are not supported yet.
+
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 762cf911fcf..2fb6cec55fa 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -51,7 +51,7 @@ Below are the settings for [GitLab Pages].
| TLS certificates support| yes | no |
The maximum size of your Pages site is regulated by the artifacts maximum size
-which is part of [GitLab CI/CD](#gitlab-ci-cd).
+which is part of [GitLab CI/CD](#gitlab-cicd).
## GitLab CI/CD
@@ -71,6 +71,14 @@ or over the size limit, you can [reduce your repository size with Git](../projec
| ----------- | ----------------- | ------------- |
| Repository size including LFS | 10G | Unlimited |
+## IP range
+
+GitLab.com, CI/CD, and related services are deployed into Google Cloud Platform (GCP). Any
+IP based firewall can be configured by looking up all
+[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#where_can_i_find_product_name_short_ip_ranges).
+
+[Static endpoints](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5071) are being considered.
+
## Shared Runners
Shared Runners on GitLab.com run in [autoscale mode] and powered by
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 8d223d1c5f0..3c5e820c1ca 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Group-level Kubernetes clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6.
@@ -5,28 +9,17 @@
## Overview
-Similar to [project Kubernetes
-clusters](../../project/clusters/index.md), Group-level Kubernetes
-clusters allow you to connect a Kubernetes cluster to your group,
-enabling you to use the same cluster across multiple projects.
+Similar to [project-level](../../project/clusters/index.md) and
+[instance-level](../../instance/clusters/index.md) Kubernetes clusters,
+group-level Kubernetes clusters allow you to connect a Kubernetes cluster to
+your group, enabling you to use the same cluster across multiple projects.
## Installing applications
-GitLab provides a one-click install for various applications that can be
-added directly to your cluster.
-
-NOTE: **Note:**
-Applications will be installed in a dedicated namespace called
-`gitlab-managed-apps`. If you have added an existing Kubernetes cluster
-with Tiller already installed, you should be careful as GitLab cannot
-detect it. In this event, installing Tiller via the applications will
-result in the cluster having it twice. This can lead to confusion during
-deployments.
-
-| Application | GitLab version | Description | Helm Chart |
-| ----------- | -------------- | ----------- | ---------- |
-| [Helm Tiller](https://docs.helm.sh) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
+GitLab can install and manage some applications in your group-level
+cluster. For more information on installing, upgrading, uninstalling,
+and troubleshooting applications for your group cluster, see
+[Gitlab Managed Apps](../../clusters/applications.md).
## RBAC compatibility
@@ -56,9 +49,32 @@ group. That way you can have different clusters for different environments,
like dev, staging, production, etc.
Add another cluster similar to the first one and make sure to
-[set an environment scope](#environment-scopes) that will
+[set an environment scope](#environment-scopes-premium) that will
differentiate the new cluster from the rest.
+## GitLab-managed clusters
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
+> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
+
+NOTE: **Note:**
+Only available when creating clusters. Existing clusters not managed by GitLab
+cannot become GitLab-managed later.
+
+You can choose to allow GitLab to manage your cluster for you. If your cluster is
+managed by GitLab, resources for your projects will be automatically created. See the
+[Access controls](../../project/clusters/index.md#access-controls) section for details on which resources will
+be created.
+
+If you choose to manage your own cluster, project-specific resources will not be created
+automatically. If you are using [Auto DevOps](../../../topics/autodevops/index.md), you will
+need to explicitly provide the `KUBE_NAMESPACE` [deployment variable](../../project/clusters/index.md#deployment-variables)
+that will be used by your deployment jobs.
+
+NOTE: **Note:**
+If you [install applications](#installing-applications) on your cluster, GitLab will create
+the resources required to run these even if you have chosen to manage your own cluster.
+
## Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
@@ -72,11 +88,10 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
## Environment scopes **[PREMIUM]**
-When adding more than one Kubernetes cluster to your project, you need
-to differentiate them with an environment scope. The environment scope
-associates clusters with [environments](../../../ci/environments.md)
-similar to how the [environment-specific
-variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with
+[environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium)
work.
While evaluating which environment matches the environment scope of a
@@ -133,4 +148,16 @@ The following features are not currently available for group-level clusters:
1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)).
1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
-1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
+1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55489)).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/contribution_analytics/img/group_stats_cal.png b/doc/user/group/contribution_analytics/img/group_stats_cal.png
new file mode 100644
index 00000000000..0238c7bf088
--- /dev/null
+++ b/doc/user/group/contribution_analytics/img/group_stats_cal.png
Binary files differ
diff --git a/doc/user/group/contribution_analytics/img/group_stats_graph.png b/doc/user/group/contribution_analytics/img/group_stats_graph.png
new file mode 100644
index 00000000000..ccfd3782c6f
--- /dev/null
+++ b/doc/user/group/contribution_analytics/img/group_stats_graph.png
Binary files differ
diff --git a/doc/user/group/contribution_analytics/img/group_stats_table.png b/doc/user/group/contribution_analytics/img/group_stats_table.png
new file mode 100644
index 00000000000..f1d1031fa18
--- /dev/null
+++ b/doc/user/group/contribution_analytics/img/group_stats_table.png
Binary files differ
diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md
new file mode 100644
index 00000000000..7e6cb24a51e
--- /dev/null
+++ b/doc/user/group/contribution_analytics/index.md
@@ -0,0 +1,77 @@
+---
+type: reference
+---
+
+# Contribution Analytics **[STARTER]**
+
+> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+
+## Overview
+
+With Contribution Analytics you can get an overview of the following activity in your
+group:
+
+- Issues
+- Merge requests
+- Push events
+
+To view the Contribution Analytics, go to your group's **Overview > Contribution Analytics**
+page.
+
+## Use cases
+
+- Analyze your team's contributions over a period of time, and offer a bonus for the top
+contributors.
+- Identify opportunities for improvement with group members who may benefit from additional
+support.
+
+## Using Contribution Analytics
+
+There are three main bar graphs that illustrate the number of contributions per group
+member for the following:
+
+- Push events
+- Merge requests
+- Closed issues
+
+Hover over each bar to display the number of events for a specific group member.
+
+![Contribution analytics bar graphs](img/group_stats_graph.png)
+
+## Changing the period time
+
+You can choose from the following three periods:
+
+- Last week (default)
+- Last month
+- Last three months
+
+Select the desired period from the calendar dropdown.
+
+![Contribution analytics choose period](img/group_stats_cal.png)
+
+## Sorting by different factors
+
+Contributions per group member are also presented in tabular format. Click a column header to sort the table by that column:
+
+* Member name
+* Number of pushed events
+* Number of opened issues
+* Number of closed issues
+* Number of opened MRs
+* Number of accepted MRs
+* Number of total contributions
+
+![Contribution analytics contributions table](img/group_stats_table.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index 8e101407ac0..aa088d2fcdb 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -1,4 +1,8 @@
-# Custom group-level project templates **[PREMIUM ONLY]**
+---
+type: reference
+---
+
+# Custom group-level project templates **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6.
@@ -24,3 +28,15 @@ Projects of nested subgroups of a selected template source cannot be used.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/dependency_proxy/img/group_dependency_proxy.png b/doc/user/group/dependency_proxy/img/group_dependency_proxy.png
new file mode 100644
index 00000000000..035aff0b6c4
--- /dev/null
+++ b/doc/user/group/dependency_proxy/img/group_dependency_proxy.png
Binary files differ
diff --git a/doc/user/group/dependency_proxy/index.md b/doc/user/group/dependency_proxy/index.md
new file mode 100644
index 00000000000..4fc2d8e9509
--- /dev/null
+++ b/doc/user/group/dependency_proxy/index.md
@@ -0,0 +1,74 @@
+# Dependency Proxy **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
+
+NOTE: **Note:**
+This is the user guide. In order to use the dependency proxy, an administrator
+must first [configure it](../../../administration/dependency_proxy.md).
+
+For many organizations, it is desirable to have a local proxy for frequently used
+upstream images/packages. In the case of CI/CD, the proxy is responsible for
+receiving a request and returning the upstream image from a registry, acting
+as a pull-through cache.
+
+The dependency proxy is available in the group level. To access it, navigate to
+a group's **Overview > Dependency Proxy**.
+
+![Dependency Proxy group page](img/group_dependency_proxy.png)
+
+## Supported dependency proxies
+
+NOTE: **Note:**
+For a list of the upcoming additions to the proxies, visit the
+[direction page](https://about.gitlab.com/direction/package/dependency_proxy/#top-vision-items).
+
+The following dependency proxies are supported.
+
+| Dependency proxy | GitLab version |
+| ---------------- | -------------- |
+| Docker | 11.11+ |
+
+## Using the Docker dependency proxy
+
+With the Docker dependency proxy, you can use GitLab as a source for a Docker image.
+To get a Docker image into the dependency proxy:
+
+1. Find the proxy URL on your group's page under **Overview > Dependency Proxy**,
+ for example `gitlab.com/groupname/dependency_proxy/containers`.
+1. Trigger GitLab to pull the Docker image you want (e.g., `alpine:latest` or
+ `linuxserver/nextcloud:latest`) and store it in the proxy storage by using
+ one of the following ways:
+
+ - Manually pulling the Docker image:
+
+ ```bash
+ docker pull gitlab.com/groupname/dependency_proxy/containers/alpine:latest
+ ```
+
+ - From a `Dockerfile`:
+
+ ```bash
+ FROM gitlab.com/groupname/dependency_proxy/containers/alpine:latest
+ ```
+
+ - In [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image):
+
+ ```bash
+ image: gitlab.com/groupname/dependency_proxy/containers/alpine:latest
+ ```
+
+GitLab will then pull the Docker image from Docker Hub and will cache the blobs
+on the GitLab server. The next time you pull the same image, it will get the latest
+information about the image from Docker Hub but will serve the existing blobs
+from GitLab.
+
+The blobs are kept forever, and there is no hard limit on how much data can be
+stored.
+
+## Limitations
+
+The following limitations apply:
+
+- Only public groups are supported (authentication is not supported yet).
+- Only Docker Hub is supported.
+- This feature requires Docker Hub being available.
diff --git a/doc/user/group/epics/img/button_close_epic.png b/doc/user/group/epics/img/button_close_epic.png
new file mode 100644
index 00000000000..aa1a889ea23
--- /dev/null
+++ b/doc/user/group/epics/img/button_close_epic.png
Binary files differ
diff --git a/doc/user/group/epics/img/button_reopen_epic.png b/doc/user/group/epics/img/button_reopen_epic.png
new file mode 100644
index 00000000000..7a953189270
--- /dev/null
+++ b/doc/user/group/epics/img/button_reopen_epic.png
Binary files differ
diff --git a/doc/user/group/epics/img/child_epics_roadmap.png b/doc/user/group/epics/img/child_epics_roadmap.png
new file mode 100644
index 00000000000..819fed58989
--- /dev/null
+++ b/doc/user/group/epics/img/child_epics_roadmap.png
Binary files differ
diff --git a/doc/user/group/epics/img/containing_epic.png b/doc/user/group/epics/img/containing_epic.png
new file mode 100644
index 00000000000..5ed693e6d6a
--- /dev/null
+++ b/doc/user/group/epics/img/containing_epic.png
Binary files differ
diff --git a/doc/user/group/epics/img/epic_view.png b/doc/user/group/epics/img/epic_view.png
new file mode 100644
index 00000000000..dbda98e4351
--- /dev/null
+++ b/doc/user/group/epics/img/epic_view.png
Binary files differ
diff --git a/doc/user/group/epics/img/epics_list_view.png b/doc/user/group/epics/img/epics_list_view.png
new file mode 100644
index 00000000000..b30608d9d31
--- /dev/null
+++ b/doc/user/group/epics/img/epics_list_view.png
Binary files differ
diff --git a/doc/user/group/epics/img/epics_search.png b/doc/user/group/epics/img/epics_search.png
new file mode 100644
index 00000000000..96335527468
--- /dev/null
+++ b/doc/user/group/epics/img/epics_search.png
Binary files differ
diff --git a/doc/user/group/epics/img/epics_sort.png b/doc/user/group/epics/img/epics_sort.png
new file mode 100644
index 00000000000..b23c65fd0ef
--- /dev/null
+++ b/doc/user/group/epics/img/epics_sort.png
Binary files differ
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
new file mode 100644
index 00000000000..2e4106f55e5
--- /dev/null
+++ b/doc/user/group/epics/index.md
@@ -0,0 +1,233 @@
+---
+type: reference, howto
+---
+
+# Epics **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate][ee] 10.2.
+
+Epics let you manage your portfolio of projects more efficiently and with less
+effort by tracking groups of issues that share a theme, across projects and
+milestones.
+
+![epics list view](img/epics_list_view.png)
+
+## Use cases
+
+- Suppose your team is working on a large feature that involves multiple discussions throughout different issues created in distinct projects within a [Group](../index.md). With Epics, you can track all the related activities that together contribute to that single feature.
+- Track when the work for the group of issues is targeted to begin, and when it is targeted to end.
+- Discuss and collaborate on feature ideas and scope at a high-level.
+
+## Creating an epic
+
+A paginated list of epics is available in each group from where you can create
+a new epic. The list of epics includes also epics from all subgroups of the
+selected group. From your group page:
+
+1. Go to **Epics**
+1. Click the **New epic** button at the top right
+1. Enter a descriptive title and hit **Create epic**
+
+Once created, you will be taken to the view for that newly-created epic where
+you can change its title, description, start date, and due date.
+
+![epic view](img/epic_view.png)
+
+## Adding an issue to an epic
+
+An epic contains a list of issues and an issue can be associated with at most
+one epic. When on an epic, you can add its associated issues:
+
+1. Click the plus icon (<kbd>+</kbd>) under the epic description.
+1. Paste the link of the issue (you can hit <kbd>Spacebar</kbd> to add more than
+ one issues at a time).
+1. Click **Add**.
+
+Any issue belonging to a project in the epic's group or any of the epic's
+subgroups are eligible to be added. To remove an issue from an epic, click
+on the <kbd>x</kbd> button in the epic's issue list.
+
+NOTE: **Note:**
+When you add an issue or an epic to an epic that's already associated with another epic,
+the issue or the epic is automatically removed from the previous epic.
+
+## Multi-level child epics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8333) in GitLab Ultimate 11.7.
+
+Much like adding issues to an epic, an epic can have multiple child epics with
+the maximum depth being 5. To add a child epic:
+
+1. Click the plus icon (<kbd>+</kbd>) under the epic description.
+1. Paste the link of the epic.
+1. Click **Add**.
+
+Any epic that belongs to a group or subgroup of the parent epic's group is
+eligible to be added. To remove a child epic from a parent epic,
+click on the <kbd>x</kbd> button in the parent epic's epic list.
+
+## Start date and due date
+
+For each of the dates in the sidebar of an epic, you can choose to either:
+
+- Enter a fixed value.
+- Inherit a dynamic value called "From milestones".
+
+If you select "From milestones" for the start date, GitLab will automatically set the
+date to be earliest start date across all milestones that are currently assigned
+to the issues that are attached to the epic. Similarly, if you select "From milestones"
+for the due date, GitLab will set it to be the latest due date across all
+milestones that are currently assigned to those issues.
+
+These are dynamic dates in that if milestones are re-assigned to the issues, if the
+milestone dates change, or if issues are added or removed from the epic, then
+the re-calculation will happen immediately to set a new dynamic date.
+
+## Roadmap in epics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7327) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.10.
+
+If your epic contains one or more [child epics](#multi-level-child-epics) which
+have a [start or due date](#start-date-and-due-date), then you can see a
+[roadmap](../roadmap/index.md) view of the child epics under the parent epic itself.
+
+![Child epics roadmap](img/child_epics_roadmap.png)
+
+## Reordering issues and child epics
+
+Drag and drop to reorder issues and child epics. New issues and child epics added to an epic appear at the top of the list.
+
+## Deleting an epic
+
+NOTE: **Note:**
+To delete an epic, you need to be an [Owner][permissions] of a group/subgroup.
+
+When inside a single epic view, click the **Delete** button to delete the epic.
+A modal will pop-up to confirm your action.
+
+Deleting an epic releases all existing issues from their associated epic in the
+system.
+
+## Closing and reopening epics
+
+### Using buttons
+
+Whenever you decide that there is no longer need for that epic,
+close the epic using the close button:
+
+![close epic - button](img/button_close_epic.png)
+
+You can always reopen it using the reopen button.
+
+![reopen epic - button](img/button_reopen_epic.png)
+
+### Using quick actions
+
+You can close or reopen an epic using [Quick actions](../../project/quick_actions.md)
+
+## Navigating to an epic from an issue
+
+If an issue belongs to an epic, you can navigate to the containing epic with the
+link in the issue sidebar.
+
+![containing epic](img/containing_epic.png)
+
+## Promoting an issue to an epic
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3777) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.6.
+
+If you have [permissions](../../permissions.md) to close an issue and create an
+epic in the parent group, you can promote an issue to an epic with the `/promote`
+[quick action](../../project/quick_actions.md#quick-actions-for-epics-ultimate).
+Only issues from projects that are in groups can be promoted.
+
+When the quick action is executed:
+
+- An epic is created in the same group as the project of the issue.
+- Subscribers of the issue are notified that the epic was created.
+
+The following issue metadata will be copied to the epic:
+
+- Title, description, activity/comment thread.
+- Upvotes/downvotes.
+- Participants.
+- Group labels that the issue already has.
+
+## Searching for an epic from epics list page
+
+> Introduced in [GitLab Ultimate][ee] 10.5.
+
+You can search for an epic from the list of epics using filtered search bar (similar to
+that of Issues and Merge requests) based on following parameters:
+
+- Title or description
+- Author name / username
+- Labels
+
+![epics search](img/epics_search.png)
+
+To search, go to the list of epics and click on the field **Search or filter results...**.
+It will display a dropdown menu, from which you can add an author. You can also enter plain
+text to search by epic title or description. When done, press <kbd>Enter</kbd> on your
+keyboard to filter the list.
+
+You can also sort epics list by:
+
+- **Created date**
+- **Last updated**
+- **Start date**
+- **Due date**
+
+Each option contains a button that can toggle the order between **ascending** and **descending**. The sort option and order will be persisted to be used wherever epics are browsed including the [roadmap](../roadmap/index.md).
+
+![epics sort](img/epics_sort.png)
+
+## Permissions
+
+If you have access to view an epic and have access to view an issue already
+added to that epic, then you can view the issue in the epic issue list.
+
+If you have access to edit an epic and have access to edit an issue, then you
+can add the issue to or remove it from the epic.
+
+Note that for a given group, the visibility of all projects must be the same as
+the group, or less restrictive. That means if you have access to a group's epic,
+then you already have access to its projects' issues.
+
+You may also consult the [group permissions table][permissions].
+
+[ee]: https://about.gitlab.com/pricing/
+[permissions]: ../../permissions.md#group-members-permissions
+
+## Thread
+
+- Comments: collaborate on that epic by posting comments in its thread.
+These text fields also fully support
+[GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+
+## Comment, or start a discussion
+
+Once you wrote your comment, you can either:
+
+- Click "Comment" and your comment will be published.
+- Click "Start discussion": start a thread within that epic's thread to discuss specific points.
+
+## Award emoji
+
+- You can [award an emoji](../../award_emojis.md) to that epic or its comments.
+
+## Notifications
+
+- [Receive notifications](../../../workflow/notifications.md) for epic events.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/img/create_new_group_info.png b/doc/user/group/img/create_new_group_info.png
index c2e6ed43c5b..bd724240c37 100644
--- a/doc/user/group/img/create_new_group_info.png
+++ b/doc/user/group/img/create_new_group_info.png
Binary files differ
diff --git a/doc/user/group/img/group_file_template_dropdown.png b/doc/user/group/img/group_file_template_dropdown.png
new file mode 100644
index 00000000000..d9caae1a223
--- /dev/null
+++ b/doc/user/group/img/group_file_template_dropdown.png
Binary files differ
diff --git a/doc/user/group/img/group_file_template_settings.png b/doc/user/group/img/group_file_template_settings.png
new file mode 100644
index 00000000000..ca42f7726db
--- /dev/null
+++ b/doc/user/group/img/group_file_template_settings.png
Binary files differ
diff --git a/doc/user/group/img/member_lock.png b/doc/user/group/img/member_lock.png
new file mode 100644
index 00000000000..3f1382e76c6
--- /dev/null
+++ b/doc/user/group/img/member_lock.png
Binary files differ
diff --git a/doc/user/group/img/membership_lock.png b/doc/user/group/img/membership_lock.png
deleted file mode 100644
index c9ad82c90f2..00000000000
--- a/doc/user/group/img/membership_lock.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/img/new_group_form.png b/doc/user/group/img/new_group_form.png
deleted file mode 100644
index 1c4d3ec6ceb..00000000000
--- a/doc/user/group/img/new_group_form.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 300e0115c60..eb0c7bc998f 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -1,35 +1,49 @@
+---
+type: reference, howto
+---
+
# Groups
-With GitLab Groups you can assemble related projects together
-and grant members access to several projects at once.
+With GitLab Groups, you can:
+
+- Assemble related projects together.
+- Grant members access to several projects at once.
Groups can also be nested in [subgroups](subgroups/index.md).
-Find your groups by expanding the left menu and clicking **Groups**:
+Find your groups by clicking **Groups > Your Groups** in the top navigation.
![GitLab Groups](img/groups.png)
-The Groups page displays all groups you are a member of, how many projects it holds,
-how many members it has, the group visibility, and, if you have enough permissions,
-a link to the group settings. By clicking the last button you can leave that group.
+> The **Groups** dropdown in the top navigation was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/36234) in [GitLab 11.1](https://about.gitlab.com/2018/07/22/gitlab-11-1-released/#groups-dropdown-in-navigation).
+
+The **Groups** page displays:
+
+- All groups you are a member of, when **Your groups** is selected.
+- A list of public groups, when **Explore public groups** is selected.
+
+Each group on the **Groups** page is listed with:
+
+- How many subgroups it has.
+- How many projects it contains.
+- How many members the group has, not including members inherited from parent groups.
+- The group's visibility.
+- A link to the group's settings, if you have sufficient permissions.
+- A link to leave the group, if you are a member.
## Use cases
-You can create groups for numerous reasons. To name a few:
-
-- Organize related projects under the same [namespace](#namespaces), add members to that
- group and grant access to all their projects at once
-- Create a group, include members of your team, and make it easier to
- `@mention` all the team at once in issues and merge requests
- - Create a group for your company members, and create [subgroups](subgroups/index.md)
- for each individual team. Let's say you create a group called `company-team`, and among others,
- you created subgroups in this group for each individual team `backend-team`,
- `frontend-team`, and `production-team`:
- 1. When you start a new implementation from an issue, you add a comment:
+You can create groups for numerous reasons. To name a couple:
+
+- Grant access to multiple projects and multiple team members in fewer steps by organizing related projects under the same [namespace](#namespaces) and adding members to the top-level group.
+- Make it easier to `@mention` all of your team at once in issues and merge requests by creating a group and including the appropriate members.
+
+For example, you could create a group for your company members, and create a [subgroup](subgroups/index.md) for each individual team. Let's say you create a group called `company-team`, and you create subgroups in this group for the individual teams `backend-team`, `frontend-team`, and `production-team`.
+ - When you start a new implementation from an issue, you add a comment:
_"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_
- 1. When your backend team needs help from frontend, they add a comment:
+ - When your backend team needs help from frontend, they add a comment:
_"`@company-team/frontend-team` could you help us here please?"_
- 1. When the frontend team completes their implementation, they comment:
+ - When the frontend team completes their implementation, they comment:
_"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_
## Namespaces
@@ -44,7 +58,7 @@ For example, consider a user named Alex:
1. Alex creates an account on GitLab.com with the username `alex`;
their profile will be accessed under `https://gitlab.example.com/alex`
-1. Alex creates a group for their team with the groupname `alex-team`;
+1. Alex creates a group for their team with the group name `alex-team`;
the group and its projects will be accessed under `https://gitlab.example.com/alex-team`
1. Alex creates a subgroup of `alex-team` with the subgroup name `marketing`;
this subgroup and its projects will be accessed under `https://gitlab.example.com/alex-team/marketing`
@@ -57,23 +71,22 @@ By doing so:
## Issues and merge requests within a group
-Issues and merge requests are part of projects. For a given group, view all the
-[issues](../project/issues/index.md#issues-per-group) and [merge requests](../project/merge_requests/index.md#merge-requests-per-group) across all the projects in that group,
+Issues and merge requests are part of projects. For a given group, you can view all of the
+[issues](../project/issues/index.md#issues-list) and [merge requests](../project/merge_requests/index.md#merge-requests-per-group) across all projects in that group,
together in a single list view.
## Create a new group
-> **Notes:**
-> - For a list of words that are not allowed to be used as group names see the
-> [reserved names](../reserved_names.md).
+> For a list of words that are not allowed to be used as group names see the
+> [reserved names](../reserved_names.md).
-You can create a group in GitLab from:
+To create a new Group, either:
-1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**:
+- In the top menu, click **Groups** and then **Your Groups**, and click the green button **New group**.
![new group from groups page](img/new_group_from_groups.png)
-1. Elsewhere: expand the `plus` sign button on the top navbar and choose **New group**:
+- Or, in the top menu, expand the `plus` sign and choose **New group**.
![new group from elsewhere](img/new_group_from_other_pages.png)
@@ -81,57 +94,58 @@ Add the following information:
![new group info](img/create_new_group_info.png)
-1. Set the **Group path** which will be the **namespace** under which your projects
- will be hosted (path can contain only letters, digits, underscores, dashes
- and dots; it cannot start with dashes or end in dot).
-1. The **Group name** will populate with the path. Optionally, you can change
- it. This is the name that will display in the group views.
-1. Optionally, you can add a description so that others can briefly understand
+1. The **Group name** will automatically populate the URL. Optionally, you can change it.
+ This is the name that displays in group views.
+ The name can contain only:
+ - Alphanumeric characters
+ - Underscores
+ - Dashes and dots
+ - Spaces
+1. The **Group URL** is the namespace under which your projects will be hosted.
+ The URL can contain only:
+ - Alphanumeric characters
+ - Underscores
+ - Dashes and dots (it cannot start with dashes or end in a dot)
+1. Optionally, you can add a brief description to tell others
what this group is about.
-1. Optionally, choose an avatar for your project.
+1. Optionally, choose an avatar for your group.
1. Choose the [visibility level](../../public_access/public_access.md).
## Add users to a group
-Add members to a group by navigating to the group's dashboard, and clicking **Members**:
+A benefit of putting multiple projects in one group is that you can
+give a user to access to all projects in the group with one action.
-![add members to group](img/add_new_members.png)
+Add members to a group by navigating to the group's dashboard and clicking **Members**.
-Select the [permission level](../permissions.md#permissions) and add the new member. You can also set the expiring
-date for that user, from which they will no longer have access to your group.
+![add members to group](img/add_new_members.png)
-One of the benefits of putting multiple projects in one group is that you can
-give a user to access to all projects in the group with one action.
+Select the [permission level](../permissions.md#permissions), and add the new member. You can also set the expiring date for that user; this is the date on which they will no longer have access to your group.
-Consider we have a group with two projects:
+Consider a group with two projects:
-- On the **Group Members** page we can now add a new user to the group.
-- Now because this user is a **Developer** member of the group, he automatically
+- On the **Group Members** page, you can now add a new user to the group.
+- Now, because this user is a **Developer** member of the group, they automatically
gets **Developer** access to **all projects** within that group.
-If necessary, you can increase the access level of an individual user for a specific project,
-by adding them again as a new member to the project with the new permission levels.
+To increase the access level of an existing user for a specific project,
+add them again as a new member to the project with the desired permission level.
## Request access to a group
-As a group owner you can enable or disable non members to request access to
-your group. Go to the group settings and click on **Allow users to request access**.
+As a group owner, you can enable or disable the ability for non members to request access to
+your group. Go to the group settings, and click **Allow users to request access**.
-As a user, you can request to be a member of a group. Go to the group you'd
-like to be a member of, and click the **Request Access** button on the right
+As a user, you can request to be a member of a group, if that setting is enabled. Go to the group for which you'd like to be a member, and click the **Request Access** button on the right
side of your screen.
![Request access button](img/request_access_button.png)
----
-
Group owners and maintainers 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.
@@ -141,57 +155,102 @@ If you change your mind before your request is approved, just click the
There are two different ways to add a new project to a group:
-- Select a group and then click on the **New project** button.
+- Select a group, and then click **New project**. You can then continue [creating your project](../../gitlab-basics/create-project.md).
![New project](img/create_new_project_from_group.png)
- You can then continue on [creating a project](../../gitlab-basics/create-project.md).
-
- While you are creating a project, select a group namespace
you've already created from the dropdown menu.
![Select group](img/select_group_dropdown.png)
+### Default project-creation level
+
+> [Introduced][ee-2534] in [GitLab Premium][ee] 10.5.
+> Brought to [GitLab Starter][ee] in 10.7.
+> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25975) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.10.
+
+Group owners and administrators can allow users with the
+Developer role to create projects under groups.
+
+By default, [Developers and Maintainers](../permissions.md#group-members-permissions) can create projects under a group. You can change this setting for a specific group within the group settings, or
+you can set this option globally in the Admin area
+at **Settings > General > Visibility and access controls** (you must be a GitLab administrator).
+
+Available settings are `No one`, `Maintainers`, or `Developers + Maintainers`.
+
## Transfer projects into groups
-Learn how to [transfer a project into a group](../project/index.md#transfer-an-existing-project-into-a-group).
+Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace).
## 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.
+and give all group members access to the project at once.
Alternatively, you can [lock the sharing with group feature](#share-with-group-lock).
## Manage group memberships via LDAP
-In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
+In GitLab Enterprise Edition, it is possible to manage GitLab group memberships using LDAP groups.
See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information.
-## Transfer groups to another group
+## Epics **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate][ee] 10.2.
+
+Epics let you manage your portfolio of projects more efficiently and with less
+effort by tracking groups of issues that share a theme, across projects and
+milestones.
+
+[Learn more about Epics.](epics/index.md)
-From 10.5 there are two different ways to transfer a group:
+## Group Security Dashboard **[ULTIMATE]**
-- Either by transferring a group into another group (making it a subgroup of that group).
-- Or by converting a subgroup into a root group (a group with no parent).
+Get an overview of the vulnerabilities of all the projects in a group and its subgroups.
-Please make sure to understand that:
+[Learn more about the Group Security Dashboard.](security_dashboard/index.md)
-- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths)
-- You can only transfer the group to a group you manage.
-- You will need to update your local repositories to point to the new location.
-- If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
+## Insights **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate][ee] 11.9 behind the `insights` feature flag.
+
+Configure the Insights that matter for your groups or projects, allowing users to explore data
+such as:
+
+- Triage hygiene
+- Issues created/closed per a given period
+- Average time for merge requests to be merged
+- Much more
+
+[Learn more about Insights](insights/index.md).
+
+## Transferring groups
+
+From GitLab 10.5, you can transfer groups in the following ways:
+
+- Transfer a subgroup to a new parent group.
+- Convert a top-level group into a subgroup by transfering it to the desired group.
+- Convert a subgroup into a top-level group by transfering it out of its current group.
+
+When transferring groups, note:
+
+- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](../project/index.md#redirects-when-changing-repository-paths).
+- You can only transfer groups to groups you manage.
+- You must update your local repositories to point to the new location.
+- If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will change to match the new parent group's visibility.
+- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this leaves the group without an owner. In this case, the user transferring the group becomes the group's owner.
## Group settings
-Once you have created a group, you can manage its settings by navigating to
+After creating a group, you can manage its settings by navigating to
the group's dashboard, and clicking **Settings**.
![group settings](img/group_settings.png)
### General settings
-Besides giving you the option to edit any settings you've previously
+In addition to editing any settings you previously
set when [creating the group](#create-a-new-group), you can also
access further configurations for your group.
@@ -202,14 +261,14 @@ Changing a group's path can have unintended side effects. Read
before proceeding.
If you are vacating the path so it can be claimed by another group or user,
-you may need to rename the group name as well since both names and paths must
+you may need to rename the group, too, since both names and paths must
be unique.
To change your group path:
1. Navigate to your group's **Settings > General**.
-1. Enter a new name under "Group path".
-1. Hit **Save group**.
+1. Enter a new name under **Group path**.
+1. Click **Save group**.
CAUTION: **Caution:**
It is currently not possible to rename a namespace if it contains a
@@ -225,20 +284,17 @@ username, you can create a new group and transfer projects to it.
Add a security layer to your group by
[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group)
-to all group members.
+for all group members.
#### Share with group lock
Prevent projects in a group from [sharing
-a project with another group](../project/members/share_project_with_groups.md).
-This allows for tighter control over project access.
+a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
-For example, consider you have two distinct teams (Group A and Group B)
-working together in a project.
-To inherit the group membership, you share the project between the
+For example, let's say you have two distinct teams (Group A and Group B) working together in a project, and to inherit the group membership, you share the project between the
two groups A and B. **Share with group lock** prevents any project within
-the group from being shared with another group. By doing so, you
-guarantee only the right group members have access to that projects.
+the group from being shared with another group,
+guaranteeing that only the right group members have access to those projects.
To enable this feature, navigate to the group settings page. Select
**Share with group lock** and **Save the group**.
@@ -247,29 +303,88 @@ To enable this feature, navigate to the group settings page. Select
#### Member Lock **[STARTER]**
-With **Member Lock** it is possible to lock membership in project to the
-level of members in group.
+Member lock lets a group owner prevent any new project membership to all of the
+projects within a group, allowing tighter control over project membership.
+
+For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
+enable Member lock to guarantee that project membership cannot be modified during that audit.
+
+To enable this feature:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and select **Member lock**.
+1. Click **Save changes**.
+
+![Checkbox for membership lock](img/member_lock.png)
+
+This will disable the option for all users who previously had permissions to
+operate project memberships, so no new users can be added. Furthermore, any
+request to add a new user to a project through API will not be possible.
-Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock).
+#### Group file templates **[PREMIUM]**
-#### Group-level file templates **[PREMIUM]**
+Group file templates allow you to share a set of templates for common file
+types with every project in a group. It is analogous to the
+[instance template repository](../admin_area/settings/instance_template_repository.md)
+feature, and the selected project should follow the same naming conventions as
+are documented on that page.
-Group-level file templates allow you to share a set of templates for common file
-types with every project in a group.
+You can only choose projects in the group as the template source.
+This includes projects shared with the group, but it **excludes** projects in
+subgroups or parent groups of the group being configured.
-Learn more about [Group-level file templates](https://docs.gitlab.com/ee/user/group/index.html#group-level-file-templates-premium).
+You can configure this feature for both subgroups and parent groups. A project
+in a subgroup will have access to the templates for that subgroup, as well as
+any parent groups.
+
+![Group file template dropdown](img/group_file_template_dropdown.png)
+
+To enable this feature, navigate to the group settings page, expand the
+**Templates** section, choose a project to act as the template repository, and
+**Save group**.
+
+![Group file template settings](img/group_file_template_settings.png)
#### Group-level project templates **[PREMIUM]**
-Define project templates at a group-level by setting a group as a template source.
+Define project templates at a group level by setting a group as the template source.
[Learn more about group-level project templates](custom_project_templates.md).
### Advanced settings
-- **Projects**: view all projects within that group, add members to each project,
- access each project's settings, and remove any project from the same screen.
-- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
-- **Kubernetes cluster integration**: connect your GitLab group with [Kubernetes clusters](clusters/index.md).
-- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
+- **Projects**: View all projects within that group, add members to each project,
+ access each project's settings, and remove any project, all from the same screen.
+- **Webhooks**: Configure [webhooks](../project/integrations/webhooks.md) for your group.
+- **Kubernetes cluster integration**: Connect your GitLab group with [Kubernetes clusters](clusters/index.md).
+- **Audit Events**: View [Audit Events](../../administration/audit_events.md)
for the group. **[STARTER ONLY]**
-- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
+- **Pipelines quota**: Keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
+
+## User contribution analysis **[STARTER]**
+
+With [GitLab Contribution Analytics](contribution_analytics/index.md),
+you have an overview of the contributions (pushes, merge requests,
+and issues) performed by your group members.
+
+## Issues analytics **[PREMIUM]**
+
+With [GitLab Issues Analytics](issues_analytics/index.md), you can see a bar chart of the number of issues created each month in your groups.
+
+## Dependency Proxy **[PREMIUM]**
+
+Use GitLab as a [dependency proxy](dependency_proxy/index.md) for upstream Docker images.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
+[ee]: https://about.gitlab.com/pricing/
+[ee-2534]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2534
diff --git a/doc/user/group/insights/img/insights_example_stacked_bar_chart.png b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png
new file mode 100644
index 00000000000..791d0e4bcdf
--- /dev/null
+++ b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png
Binary files differ
diff --git a/doc/user/group/insights/img/insights_group_configuration.png b/doc/user/group/insights/img/insights_group_configuration.png
new file mode 100644
index 00000000000..0af0073e448
--- /dev/null
+++ b/doc/user/group/insights/img/insights_group_configuration.png
Binary files differ
diff --git a/doc/user/group/insights/img/insights_sidebar_link.png b/doc/user/group/insights/img/insights_sidebar_link.png
new file mode 100644
index 00000000000..64bbd1fc4d3
--- /dev/null
+++ b/doc/user/group/insights/img/insights_sidebar_link.png
Binary files differ
diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md
new file mode 100644
index 00000000000..a4ea71074ec
--- /dev/null
+++ b/doc/user/group/insights/index.md
@@ -0,0 +1,62 @@
+---
+type: reference, howto
+---
+
+# Insights **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
+
+CAUTION: **Beta:**
+Insights is considered beta, and is not ready for production use.
+Follow [gitlab-org/quality/team-tasks#137](https://gitlab.com/gitlab-org/quality/team-tasks/issues/137#general-availability)
+for updates.
+
+Configure the Insights that matter for your groups to explore data such as
+triage hygiene, issues created/closed per a given period, average time for merge
+requests to be merged and much more.
+
+![Insights example stacked bar chart](img/insights_example_stacked_bar_chart.png)
+
+## View your group's Insights
+
+You can access your group's Insights by clicking the **Overview > Insights**
+link in the left sidebar:
+
+![Insights sidebar link](img/insights_sidebar_link.png)
+
+## Configure your Insights
+
+Navigate to your group's **Settings > General**, expand **Insights**, and choose
+the project that holds your `.gitlab/insights.yml` configuration file:
+
+![group insights configuration](img/insights_group_configuration.png)
+
+If no configuration was set, a [default configuration file](
+https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/fixtures/insights/default.yml)
+will be used.
+
+See the [Project's Insights documentation](../../project/insights/index.md) for
+more details about the `.gitlab/insights.yml` configuration file.
+
+## Permissions
+
+If you have access to view a group, then you have access to view their Insights.
+
+NOTE: **Note:**
+Issues or merge requests that you don't have access to (because you don't have
+access to the project they belong to, or because they are confidential) are
+filtered out of the Insights charts.
+
+You may also consult the [group permissions table](../../permissions.md#group-members-permissions).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/issues_analytics/img/issues_created_per_month.png b/doc/user/group/issues_analytics/img/issues_created_per_month.png
new file mode 100644
index 00000000000..96f0d36c917
--- /dev/null
+++ b/doc/user/group/issues_analytics/img/issues_created_per_month.png
Binary files differ
diff --git a/doc/user/group/issues_analytics/index.md b/doc/user/group/issues_analytics/index.md
new file mode 100644
index 00000000000..46d5c1e2e09
--- /dev/null
+++ b/doc/user/group/issues_analytics/index.md
@@ -0,0 +1,43 @@
+---
+type: reference
+---
+
+# Issues Analytics **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7478) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.5.
+
+Issues Analytics is a bar graph which illustrates the number of issues created each month.
+The default timespan is 13 months, which includes the current month, and the 12 months
+prior.
+
+To access the chart, navigate to a group's sidebar and select **Issues > Analytics**.
+
+Hover over each bar to see the total number of issues.
+
+To narrow the scope of issues included in the graph, enter your criteria in the
+**Search or filter results...** field. Criteria from the following list can be typed in or selected from a menu:
+
+- Author
+- Assignee
+- Milestone
+- Label
+- My reaction
+- Weight
+
+You can change the total number of months displayed by setting a URL parameter.
+For example, `https://gitlab.com/groups/gitlab-org/-/issues_analytics?months_back=15`
+shows a total of 15 months for the chart in the GitLab.org group.
+
+![Issues created per month](img/issues_created_per_month.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/roadmap/img/epics_state_dropdown.png b/doc/user/group/roadmap/img/epics_state_dropdown.png
new file mode 100644
index 00000000000..cbcc3658a54
--- /dev/null
+++ b/doc/user/group/roadmap/img/epics_state_dropdown.png
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_timeline_months.png b/doc/user/group/roadmap/img/roadmap_timeline_months.png
new file mode 100644
index 00000000000..5a046b21507
--- /dev/null
+++ b/doc/user/group/roadmap/img/roadmap_timeline_months.png
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_timeline_quarters.png b/doc/user/group/roadmap/img/roadmap_timeline_quarters.png
new file mode 100644
index 00000000000..56f428cb471
--- /dev/null
+++ b/doc/user/group/roadmap/img/roadmap_timeline_quarters.png
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_timeline_weeks.png b/doc/user/group/roadmap/img/roadmap_timeline_weeks.png
new file mode 100644
index 00000000000..bf4c1dc0284
--- /dev/null
+++ b/doc/user/group/roadmap/img/roadmap_timeline_weeks.png
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_view.png b/doc/user/group/roadmap/img/roadmap_view.png
new file mode 100644
index 00000000000..ff41a2e0441
--- /dev/null
+++ b/doc/user/group/roadmap/img/roadmap_view.png
Binary files differ
diff --git a/doc/user/group/roadmap/index.md b/doc/user/group/roadmap/index.md
new file mode 100644
index 00000000000..683c715c8d5
--- /dev/null
+++ b/doc/user/group/roadmap/index.md
@@ -0,0 +1,87 @@
+---
+type: reference
+---
+
+# Roadmap **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.5.
+
+An Epic within a group containing **Start date** and/or **Due date**
+can be visualized in a form of a timeline (e.g. a Gantt chart). The Epics Roadmap page
+shows such a visualization for all the epics which are under a group and/or its subgroups.
+
+![roadmap view](img/roadmap_view.png)
+
+A dropdown allows you to show only open or closed epics. By default, all epics are shown.
+
+![epics state dropdown](img/epics_state_dropdown.png)
+
+Epics in the view can be sorted by:
+
+- **Created date**
+- **Last updated**
+- **Start date**
+- **Due date**
+
+Each option contains a button that toggles the sort order between **ascending** and **descending**. The sort option and order will be persisted when browsing Epics,
+including the [epics list view](../epics/index.md).
+
+Roadmaps can also be [visualized inside an epic](../epics/index.md#roadmap-in-epics).
+
+## Timeline duration
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.0.
+
+Roadmap supports the following date ranges:
+
+- Quarters
+- Months (Default)
+- Weeks
+
+### Quarters
+
+![roadmap date range in quarters](img/roadmap_timeline_quarters.png)
+
+In _Quarters_ preset, roadmap shows epics which have start or due dates _falling within_ or
+_going through_ **past quarter**, **current quarter** and **next 4 quarters**, where _today_
+is shown by the vertical red line in the timeline. The sub-headers underneath the quarter name on
+the timeline header represent the month of the quarter.
+
+### Months
+
+![roadmap date range in months](img/roadmap_timeline_months.png)
+
+In _Months_ preset, roadmap shows epics which have start or due dates _falling within_ or
+_going through_ **past month**, **current month** and **next 5 months**, where _today_
+is shown by the vertical red line in the timeline. The sub-headers underneath the month name on
+the timeline header represent the date on starting day (Sunday) of the week. This preset is
+selected by default.
+
+### Weeks
+
+![roadmap date range in weeks](img/roadmap_timeline_weeks.png)
+
+In _Weeks_ preset, roadmap shows epics which have start or due dates _falling within_ or
+_going through_ **past week**, **current week** and **next 4 weeks**, where _today_
+is shown by the vertical red line in the timeline. The sub-headers underneath the week name on
+the timeline header represent the days of the week.
+
+## Timeline bar for an epic
+
+The timeline bar indicates the approximate position of an epic based on its start
+and due date. If an epic doesn't have a due date, the timeline bar fades
+away towards the future. Similarly, if an epic doesn't have a start date, the
+timeline bar becomes more visible as it approaches the epic's due date on the
+timeline.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/saml_sso/img/group_saml_configuration_information.png b/doc/user/group/saml_sso/img/group_saml_configuration_information.png
new file mode 100644
index 00000000000..98b83d0cb0f
--- /dev/null
+++ b/doc/user/group/saml_sso/img/group_saml_configuration_information.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/group_saml_settings.png b/doc/user/group/saml_sso/img/group_saml_settings.png
new file mode 100644
index 00000000000..d95acb5075f
--- /dev/null
+++ b/doc/user/group/saml_sso/img/group_saml_settings.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_advanced.png b/doc/user/group/saml_sso/img/scim_advanced.png
new file mode 100644
index 00000000000..3b70e3fbe83
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_advanced.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_attribute_mapping.png b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
new file mode 100644
index 00000000000..c9f6b71f5b0
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_token.png b/doc/user/group/saml_sso/img/scim_token.png
new file mode 100644
index 00000000000..7eb52bf6ea2
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_token.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/unlink_group_saml.png b/doc/user/group/saml_sso/img/unlink_group_saml.png
new file mode 100644
index 00000000000..0561443b5f4
--- /dev/null
+++ b/doc/user/group/saml_sso/img/unlink_group_saml.png
Binary files differ
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
new file mode 100644
index 00000000000..fcfd638f185
--- /dev/null
+++ b/doc/user/group/saml_sso/index.md
@@ -0,0 +1,140 @@
+---
+type: reference, howto
+---
+
+# SAML SSO for GitLab.com Groups **[SILVER ONLY]**
+
+> Introduced in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.0.
+
+NOTE: **Note:**
+This topic is for SAML on GitLab.com Silver tier and above. For SAML on self-managed GitLab instances, see [SAML OmniAuth Provider](../../../integration/saml.md).
+
+SAML on GitLab.com allows users to be automatically added to a group, and then allows those users to sign into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time.
+
+User synchronization for GitLab.com is partially supported using [SCIM](scim_setup.md).
+
+NOTE: **Note:**
+SAML SSO for GitLab.com groups does not sync users between providers without using SCIM. If a group is not using SCIM, group Owners will still need to manage user accounts (for example, removing users when necessary).
+
+## Configuring your Identity Provider
+
+1. Navigate to the group and click **Settings > SAML SSO**.
+1. Configure your SAML server using the **Assertion consumer service URL** and **Identifier**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details.
+1. Configure the SAML response to include a NameID that uniquely identifies each user.
+1. Configure required assertions using the [table below](#assertions).
+1. Once the identity provider is set up, move on to [configuring GitLab](#configuring-gitlab).
+
+![Issuer and callback for configuring SAML identity provider with GitLab.com](img/group_saml_configuration_information.png)
+
+### SSO enforcement
+
+SSO enforcement was:
+
+- [Introduced in GitLab 11.8](https://gitlab.com/gitlab-org/gitlab-ee/issues/5291).
+- [Improved upon in GitLab 11.11 with ongoing enforcement in the GitLab UI](https://gitlab.com/gitlab-org/gitlab-ee/issues/9255).
+
+With this option enabled, users must use your group's GitLab single sign on URL to be added to the group or be added via SCIM. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
+
+We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab-ee/issues/9152) in the future.
+
+### NameID
+
+GitLab.com uses the SAML NameID to identify users. The NameID element:
+
+- Is a required field in the SAML response.
+- Must be unique to each user.
+- Must be a persistent value that will never change, such as a unique ID or username. Email could also be used as the NameID, but only if it can be guaranteed to never change.
+
+### Assertions
+
+| Field | Supported keys |
+|-------|----------------|
+| Email (required)| `email`, `mail` |
+| Full Name | `name` |
+| First Name | `first_name`, `firstname`, `firstName` |
+| Last Name | `last_name`, `lastname`, `lastName` |
+
+## Metadata configuration
+
+GitLab provides metadata XML that can be used to configure your Identity Provider.
+
+1. Navigate to the group and click **Settings > SAML SSO**.
+1. Copy the provided **GitLab metadata URL**
+1. Follow your Identity Provider's documentation and paste the metadata URL when it is requested.
+
+## Configuring GitLab
+
+Once you've set up your identity provider to work with GitLab, you'll need to configure GitLab to use it for authentication:
+
+1. Navigate to the group's **Settings > SAML SSO**.
+1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign on URL** field.
+1. Find and enter the fingerprint for the SAML token signing certificate in the **Certificate** field.
+1. Check the **Enable SAML authentication for this group** checkbox.
+1. Click the **Save changes** button.
+
+![Group SAML Settings for GitLab.com](img/group_saml_settings.png)
+
+## Providers
+
+| Provider | Documentation |
+|----------|---------------|
+| ADFS (Active Directory Federation Services) | [Create a Relying Party Trust](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/create-a-relying-party-trust) |
+| Azure | [Configuring single sign-on to applications](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-saas-custom-apps) |
+| Auth0 | [Auth0 as Identity Provider](https://auth0.com/docs/protocols/saml/saml-idp-generic) |
+| G Suite | [Set up your own custom SAML application](https://support.google.com/a/answer/6087519?hl=en) |
+| JumpCloud | [Single Sign On (SSO) with GitLab](https://support.jumpcloud.com/customer/en/portal/articles/2810701-single-sign-on-sso-with-gitlab) |
+| Okta | [Setting up a SAML application in Okta](https://developer.okta.com/standards/SAML/setting_up_a_saml_application_in_okta) |
+| OneLogin | [Use the OneLogin SAML Test Connector](https://onelogin.service-now.com/support?id=kb_article&sys_id=93f95543db109700d5505eea4b96198f) |
+| Ping Identity | [Add and configure a new SAML application](https://docs.pingidentity.com/bundle/p1_enterpriseConfigSsoSaml_cas/page/enableAppWithoutURL.html) |
+
+## Linking SAML to your existing GitLab.com account
+
+To link SAML to your existing GitLab.com account:
+
+1. Sign in to your GitLab.com account.
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page.
+1. Visit the SSO URL and click **Authorize**.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
+
+## Signing in to GitLab.com with SAML
+
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Settings > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider.
+1. Visit the SSO URL and click the **Sign in with Single Sign-On** button.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be signed in to GitLab.com and redirected to the group.
+
+## Unlinking accounts
+
+Users can unlink SAML for a group from their profile page. This can be helpful if:
+
+- You no longer want a group to be able to sign you in to GitLab.com.
+- Your SAML NameID has changed and so GitLab can no longer find your user.
+
+For example, to unlink the `MyOrg` account, the following **Disconnect** button will be available under **Profile > Accounts**:
+
+![Unlink Group SAML](img/unlink_group_saml.png)
+
+## Glossary
+
+| Term | Description |
+|------|-------------|
+| Identity Provider | The service which manages your user identities such as ADFS, Okta, Onelogin or Ping Identity. |
+| Service Provider | SAML considers GitLab to be a service provider. |
+| Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. |
+| SSO | Single Sign On. |
+| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
+| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
+| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
new file mode 100644
index 00000000000..c00628bf909
--- /dev/null
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -0,0 +1,102 @@
+# SCIM provisioning using SAML SSO for Groups **[SILVER ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10.
+
+GitLab's [SCIM API](../../../api/scim.md) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
+
+Currently, the following actions are available:
+
+- CREATE
+- UPDATE
+- DELETE (deprovisioning)
+
+The following identity providers are supported:
+
+- Azure
+
+## Requirements
+
+- [Group SSO](index.md) needs to be configured.
+- The `scim_group` feature flag must be enabled:
+
+ Run the following commands in a Rails console:
+
+ ```sh
+ # Omnibus GitLab
+ gitlab-rails console
+
+ # Installation from source
+ cd /home/git/gitlab
+ sudo -u git -H bin/rails console RAILS_ENV=production
+ ```
+
+ To enable SCIM for a group named `group_name`:
+
+ ```ruby
+ group = Group.find_by_full_path('group_name')
+ Feature.enable(:group_scim, group)
+ ```
+
+### GitLab configuration
+
+Once [Single sign-on](index.md) has been configured, we can:
+
+1. Navigate to the group and click **Settings > SAML SSO**.
+1. Click on the **Generate a SCIM token** button.
+1. Save the token and URL so they can be used in the next step.
+
+![SCIM token configuration](img/scim_token.png)
+
+## SCIM IdP configuration
+
+### Configuration on Azure
+
+In the [Single sign-on](index.md) configuration for the group, make sure
+that the **Name identifier value** (NameID) points to a unique identifier, such
+as the `user.objectid`. This will match the `extern_uid` used on GitLab.
+
+The GitLab app in Azure needs to be configured following
+[Azure's SCIM setup](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#getting-started).
+
+Note the following:
+
+- The `Tenant URL` and `secret token` are the ones retrieved in the
+[previous step](#gitlab-configuration).
+- Should there be any problems with the availability of GitLab or similar
+errors, the notification email set will get those.
+- For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled.
+
+You can then test the connection clicking on `Test Connection`.
+
+### Synchronize Azure Active Directory users
+
+1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure
+the attribute mapping.
+1. Select the unique identifier (in the example `objectId`) as the `id` and `externalId`,
+and enable the `Create`, `Update`, and `Delete` actions.
+1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to
+`userName`.
+
+ Example configuration:
+
+ ![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
+
+1. Click on **Show advanced options > Edit attribute list for AppName**.
+1. Leave the `id` as the primary and only required field.
+
+ NOTE: **Note:**
+ `username` should neither be primary nor required as we don't support
+ that field on GitLab SCIM yet.
+
+ ![Azure's attribute advanced configuration](img/scim_advanced.png)
+
+1. Save all the screens and, in the **Provisioning** step, set
+the `Provisioning Status` to `ON`.
+
+ NOTE: **Note:**
+ You can control what is actually synced by selecting the `Scope`. For example,
+ `Sync only assigned users and groups` will only sync the users assigned to
+ the application (`Users and groups`), otherwise it will sync the whole Active Directory.
+
+Once enabled, the synchronization details and any errors will appear on the
+bottom of the **Provisioning** screen, together with a link to the audit logs.
diff --git a/doc/user/group/security_dashboard/index.md b/doc/user/group/security_dashboard/index.md
new file mode 100644
index 00000000000..c59198df081
--- /dev/null
+++ b/doc/user/group/security_dashboard/index.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/security_dashboard/index.md'
+---
+
+This document was moved to [another location](../../application_security/security_dashboard/index.md).
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index b6f8f55978b..4e81e28a45a 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -1,8 +1,8 @@
# Subgroups
NOTE: **Note:**
-[Introduced][ce-2772] in GitLab 9.0. Not available when using MySQL as external
-database (support removed in GitLab 9.3 [due to performance reasons][issue]).
+[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2772) in GitLab 9.0. Not available when using MySQL as external
+database (support removed in GitLab 9.3 [due to performance reasons](https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600)).
With subgroups (aka nested groups or hierarchical groups) you can have
up to 20 levels of nested groups, which among other things can help you to:
@@ -13,7 +13,7 @@ up to 20 levels of nested groups, which among other things can help you to:
- **Organize large projects.** For large projects, subgroups makes it
potentially easier to separate permissions on parts of the source code.
- **Make it easier to manage people and control visibility.** Give people
- different [permissions][] depending on their group [membership](#membership).
+ different [permissions](../../permissions.md#group-members-permissions) depending on their group [membership](#membership).
## Database Requirements
@@ -80,9 +80,9 @@ structure.
NOTE: **Note:**
You need to be an Owner of a group in order to be able to create a subgroup. For
-more information check the [permissions table][permissions].
+more information check the [permissions table](../../permissions.md#group-members-permissions).
For a list of words that are not allowed to be used as group names see the
-[reserved names][reserved].
+[reserved names](../../reserved_names.md).
Users can always create subgroups if they are explicitly added as an Owner to
a parent group even if group creation is disabled by an administrator in their
settings.
@@ -167,7 +167,7 @@ Here's a list of what you can't do with subgroups:
- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
a subgroup, but not subgroup websites.
That means that only the highest-level group supports
- [group websites](../../project/pages/introduction.html#user-or-group-pages),
+ [group websites](../../project/pages/getting_started_part_one.md#gitlab-pages-domain-names),
although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
@@ -176,6 +176,6 @@ Here's a list of what you can't do with subgroups:
`group/subgroup01/subgroup03`.
[ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
-[permissions]: ../../permissions.md#group
+[permissions]: ../../permissions.md#group-members-permissions
[reserved]: ../../reserved_names.md
[issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600
diff --git a/doc/user/img/color_inline_colorchip_render_gfm.png b/doc/user/img/color_inline_colorchip_render_gfm.png
index fed8ca5c34b..6f93dbeeb10 100644
--- a/doc/user/img/color_inline_colorchip_render_gfm.png
+++ b/doc/user/img/color_inline_colorchip_render_gfm.png
Binary files differ
diff --git a/doc/user/img/mermaid_diagram_render_gfm.png b/doc/user/img/mermaid_diagram_render_gfm.png
deleted file mode 100644
index 9d192a30a85..00000000000
--- a/doc/user/img/mermaid_diagram_render_gfm.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/img/task_list_ordered_render_gfm.png b/doc/user/img/task_list_ordered_render_gfm.png
index 0905a8378be..98ec791e958 100644
--- a/doc/user/img/task_list_ordered_render_gfm.png
+++ b/doc/user/img/task_list_ordered_render_gfm.png
Binary files differ
diff --git a/doc/user/img/unordered_check_list_render_gfm.png b/doc/user/img/unordered_check_list_render_gfm.png
index ccdeab6e62c..2ce0fb95645 100644
--- a/doc/user/img/unordered_check_list_render_gfm.png
+++ b/doc/user/img/unordered_check_list_render_gfm.png
Binary files differ
diff --git a/doc/user/index.md b/doc/user/index.md
index 71378920ed4..1fc4e4c43cf 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -8,13 +8,13 @@ Welcome to GitLab! We're glad to have you here!
As a GitLab user you'll have access to all the features
your [subscription](https://about.gitlab.com/pricing/)
-includes, except [GitLab administrator](../README.md#administrator-documentation)
+includes, except [GitLab administrator](../administration/index.md)
settings, unless you have admin privileges to install, configure,
and upgrade your GitLab instance.
Admin privileges for [GitLab.com](https://gitlab.com/) are restricted to the GitLab team.
-For more information on configuring GitLab self-managed instances, see [Administrator documentation](../README.md#administrator-documentation).
+For more information on configuring GitLab self-managed instances, see [Administrator documentation](../administration/index.md).
## Overview
@@ -36,38 +36,37 @@ To get familiar with the concepts needed to develop code on GitLab, read the fol
GitLab is a Git-based platform that integrates a great number of essential tools for software development and deployment, and project management:
-- Hosting code in repositories with version control
+- Hosting code in repositories with version control.
- Tracking proposals for new implementations, bug reports, and feedback with a
- fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
-- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards)
+ fully featured [Issue Tracker](project/issues/index.md#issues-list).
+- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards).
- Reviewing code in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
- branch with [Review Apps](../ci/review_apps/index.md)
-- Building, testing and deploying with built-in [Continuous Integration](../ci/README.md)
-- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md)
-- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md)
-- Tracking the development lifecycle by usingn [GitLab Cycle Analytics](project/cycle_analytics.md)
+ branch with [Review Apps](../ci/review_apps/index.md).
+- Building, testing, and deploying with built-in [Continuous Integration](../ci/README.md).
+- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md).
+- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md).
+- Tracking the development lifecycle by using [GitLab Cycle Analytics](project/cycle_analytics.md).
With GitLab Enterprise Edition, you can also:
-- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html)
+- Provide support with [Service Desk](project/service_desk.md).
- Improve collaboration with
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
- [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
- and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
-- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html)
-- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
-- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance
-- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html)
-- [Mirror a repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html) from elsewhere on your local server.
-- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html)
-- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
-- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
-- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
-- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
-
-You can also [integrate](project/integrations/project_services.md) GitLab with
-numerous third-party applications, such as Mattermost, Microsoft Teams, Trello,
-Slack, Bamboo CI, JIRA, and a lot more.
+ [Merge Request Approvals](project/merge_requests/index.md#merge-request-approvals-starter),
+ [Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md),
+ and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter).
+- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md).
+- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software.
+- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
+- [Authenticate users with Kerberos](../integration/kerberos.md).
+- [Mirror a repository](../workflow/repository_mirroring.md) from elsewhere on your local server.
+- [Export issues as CSV](project/issues/csv_export.md).
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipeline_graphs.md).
+- [Lock files](project/file_lock.md) to prevent conflicts.
+- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](project/deploy_boards.md).
+- Leverage continuous delivery method with [Canary Deployments](project/canary_deployments.md).
+- Scan your code for vulnerabilities and [display them in merge requests](application_security/sast/index.md).
+
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
## Projects
@@ -108,8 +107,8 @@ to enjoy the best of GitLab.
- [Permissions](permissions.md): Learn the different set of permissions levels for each
user type (guest, reporter, developer, maintainer, owner).
- [Feature highlight](feature_highlight.md): Learn more about the little blue dots
- around the app that explain certain features
-- [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators
+ around the app that explain certain features.
+- [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators.
## Groups
@@ -125,7 +124,7 @@ merge requests, code snippets, and commits.
When performing inline reviews to implementations
to your codebase through merge requests you can
-gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
+gather feedback through [resolvable discussions](discussions/index.md#resolvable-comments-and-discussions).
### GitLab Flavored Markdown (GFM)
@@ -173,3 +172,8 @@ Learn what is [Git](../topics/git/index.md) and its best practices.
## Instance statistics
See [various statistics](instance_statistics/index.md) of your GitLab instance.
+
+## Operations Dashboard **[PREMIUM]**
+
+See [Operations Dashboard](operations_dashboard/index.md) for a summary of each
+project's operational health.
diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md
new file mode 100644
index 00000000000..894f83d3c75
--- /dev/null
+++ b/doc/user/instance/clusters/index.md
@@ -0,0 +1,23 @@
+# Instance-level Kubernetes clusters
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840) in GitLab 11.11.
+> Instance-level cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga).
+
+## Overview
+
+Similar to [project-level](../../project/clusters/index.md)
+and [group-level](../../group/clusters/index.md) Kubernetes clusters,
+instance-level Kubernetes clusters allow you to connect a Kubernetes cluster to
+the GitLab instance, which enables you to use the same cluster across multiple
+projects.
+
+## Cluster precedence
+
+GitLab will try match to clusters in the following order:
+
+- Project-level clusters
+- Group-level clusters
+- Instance level
+
+To be selected, the cluster must be enabled and
+match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs-premium).
diff --git a/doc/user/instance_statistics/convdev.md b/doc/user/instance_statistics/convdev.md
index 52b99b69a02..2c9e0ecbf65 100644
--- a/doc/user/instance_statistics/convdev.md
+++ b/doc/user/instance_statistics/convdev.md
@@ -1,27 +1,26 @@
# Conversational Development Index
-> [Introduced][ce-30469] in GitLab 9.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30469) in GitLab 9.3.
+
+NOTE: **Note:**
+Your GitLab instance's [usage ping](../admin_area/settings/usage_statistics.md#usage-ping-core-only) must be activated in order to use this feature.
The Conversational Development Index (ConvDev Index) gives you an overview of your entire
instance's adoption of [Concurrent DevOps](https://about.gitlab.com/concurrent-devops/)
-from planning to monitoring. It displays the usage of these GitLab features over
+from planning to monitoring.
+
+This displays the usage of these GitLab features over
the last 30 days, averaged over the number of active users in that time period. It also
provides a Lead score per feature, which is calculated based on GitLab's analysis
-of top-performing instances based on [usage ping data][ping] that GitLab has
-collected. Your score is compared to the lead score, expressed as a percentage.
-Your overall index score is an average of all your feature score percentages.
+of top-performing instances based on [usage ping data](../admin_area/settings/usage_statistics.md#usage-ping-core-only) that GitLab has
+collected. Your score is compared to the lead score of each feature and then expressed as a percentage at the bottom of said feature.
+Your overall index score is an average of all your feature score percentages - this percentage value is presented above all the of features on the page.
![ConvDev index](img/convdev_index.png)
The page also provides helpful links to articles and GitLab docs, to help you
improve your scores.
-Your GitLab instance's [usage ping][ping] must be activated in order to use this feature.
Usage ping data is aggregated on GitLab's servers for analysis. Your usage
-information is **not sent** to any other GitLab instances.
-
-If you have just started using GitLab, it may take a few weeks for data to be
+information is **not sent** to any other GitLab instances. If you have just started using GitLab, it may take a few weeks for data to be
collected before this feature is available.
-
-[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469
-[ping]: ../admin_area/settings/usage_statistics.md#usage-ping
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index a7a87773eec..6b6e5ab7634 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -20,6 +20,7 @@ You can use GFM in the following areas:
- Snippets (the snippet must be named with a `.md` extension)
- Wiki pages
- Markdown documents inside repositories
+- Epics **[ULTIMATE]**
You can also use other rich text files in GitLab. You might have to install a
dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information.
@@ -267,10 +268,7 @@ However the wrapping tags cannot be mixed as such:
### Emoji
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji
-
-```
+```md
Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
:zap: You can use emoji anywhere GFM is supported. :v:
@@ -288,15 +286,15 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he
Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
```
-Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you:
+Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you:
-<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px">
+<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px" style="display:inline;margin:0">
-You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px"> you for that.
+You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that.
-If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes.
+If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px">
+Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0">
Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
@@ -323,6 +321,7 @@ GFM will recognize the following:
| `#12345` | issue |
| `!123` | merge request |
| `$123` | snippet |
+| `&123` | epic **[ULTIMATE]** |
| `~123` | label by ID |
| `~bug` | one-word label by name |
| `~"feature request"` | multi-word label by name |
@@ -343,6 +342,7 @@ GFM also recognizes certain cross-project references:
| `namespace/project%123` | project milestone |
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
+| `group1/subgroup&123` | epic **[ULTIMATE]** |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
| `namespace/project~"Some label"` | issues with given label |
@@ -480,7 +480,7 @@ GitLab 10.3.
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#mermaid
-It is possible to generate diagrams and flowcharts from text using [Mermaid][mermaid].
+It is possible to generate diagrams and flowcharts from text using [Mermaid](https://mermaidjs.github.io/).
In order to generate a diagram or flowchart, you should write your text inside the `mermaid` block.
@@ -496,9 +496,15 @@ Example:
Becomes:
-<img src="./img/mermaid_diagram_render_gfm.png" width="200px" height="400px">
+```mermaid
+graph TD;
+ A-->B;
+ A-->C;
+ B-->D;
+ C-->D;
+```
-For details see the [Mermaid official page][mermaid].
+For details see the [Mermaid official page](https://mermaidjs.github.io/).
### Front matter
@@ -581,11 +587,11 @@ Alt-H2
------
```
-### Header IDs and links
+#### Header IDs and links
-All Markdown-rendered headers automatically get IDs, except in comments.
+All Markdown-rendered headers automatically get IDs, which can be linked to, except in comments.
-On hover, a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
+On hover, a link to those IDs becomes visible to make it easier to copy the link to the header to use it somewhere else.
The IDs are generated from the content of the header according to the following rules:
@@ -612,8 +618,8 @@ Would generate the following link IDs:
1. `this-header-has-spaces-in-it`
1. `this-header-has-a-in-it`
1. `this-header-has-unicode-in-it-한글`
-1. `this-header-has-spaces-in-it`
1. `this-header-has-spaces-in-it-1`
+1. `this-header-has-spaces-in-it-2`
1. `this-header-has-3-5-in-it-and-parentheses`
Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
@@ -718,25 +724,25 @@ Becomes:
There are two ways to create links, inline-style and reference-style.
- [I'm an inline-style link](https://www.google.com)
-
- [I'm a reference-style link][Arbitrary case-insensitive reference text]
-
- [I'm a relative reference to a repository file](LICENSE)
-
- [I am an absolute reference within the repository](/doc/user/markdown.md)
-
- [I link to the Milestones page](/../milestones)
+```markdown
+[I'm an inline-style link](https://www.google.com)
+[I'm a link to a repository file in the same directory](index.md)
+[I am an absolute reference within the repository](/doc/user/index.md)
+[I'm a relative link to the Milestones page](../README.md)
- [You can use numbers for reference-style link definitions][1]
+[I link to a section on a different markdown page, using a header ID](index.md#overview)
+[I link to a different section on the same page, using the header ID](#header-ids-and-links)
- Or leave it empty and use the [link text itself][]
+[I'm a reference-style link][Arbitrary case-insensitive reference text]
+[You can use numbers for reference-style link definitions][1]
+Or leave it empty and use the [link text itself][]
- Some text to show that the reference links can follow later.
+Some text to show that the reference links can follow later.
- [arbitrary case-insensitive reference text]: https://www.mozilla.org
- [1]: http://slashdot.org
- [link text itself]: https://www.reddit.com
+[arbitrary case-insensitive reference text]: https://www.mozilla.org
+[1]: http://slashdot.org
+[link text itself]: https://www.reddit.com
+```
>**Note:**
Relative links do not allow referencing project files in a wiki page or wiki
@@ -1021,7 +1027,7 @@ A link can be constructed relative to the current wiki page using `./<page>`,
it would link to `<your_wiki>/documentation/related`:
```markdown
- [Link to Related Page](./related)
+ [Link to Related Page](related)
```
- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
@@ -1035,7 +1041,7 @@ A link can be constructed relative to the current wiki page using `./<page>`,
it would link to `<your_wiki>/documentation/related.md`:
```markdown
- [Link to Related Page](./related.md)
+ [Link to Related Page](related.md)
```
- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
@@ -1072,7 +1078,6 @@ A link starting with a `/` is relative to the wiki root.
[^2]: This is my awesome footnote.
[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md
-[mermaid]: https://mermaidjs.github.io/ "Mermaid website"
[rouge]: http://rouge.jneen.net/ "Rouge website"
[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
[katex]: https://github.com/Khan/KaTeX "KaTeX website"
diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
new file mode 100644
index 00000000000..9d6a509ea72
--- /dev/null
+++ b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
Binary files differ
diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png
new file mode 100644
index 00000000000..ee33eee8134
--- /dev/null
+++ b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png
Binary files differ
diff --git a/doc/user/operations_dashboard/index.md b/doc/user/operations_dashboard/index.md
new file mode 100644
index 00000000000..66362f27299
--- /dev/null
+++ b/doc/user/operations_dashboard/index.md
@@ -0,0 +1,37 @@
+# Operations Dashboard **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5781)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5.
+[Moved](https://gitlab.com/gitlab-org/gitlab-ee/issues/9218) to
+[GitLab Premium](https://about.gitlab.com/pricing/) in 11.10.
+
+The Operations Dashboard provides a summary of each project's operational health,
+including pipeline and alert status.
+
+The dashboard can be accessed via the top bar, by clicking on the new
+dashboard icon:
+
+![Operations Dashboard icon in top bar](img/index_operations_dashboard_top_bar_icon.png)
+
+## Adding a project to the dashboard
+
+NOTE: **Note:**
+For GitLab.com, the Operations Dashboard is available for free for public projects.
+If your project is private, the group it belongs to must have a
+[Gold](https://about.gitlab.com/pricing/) plan.
+
+To add a project to the dashboard:
+
+1. Click the **Add projects** button in the homescreen of the dashboard.
+1. Search and add one or more projects using the **Search your projects** field.
+1. Click the **Add projects** button.
+
+Once added, the dashboard will display the project's number of active alerts,
+last commit, pipeline status, and when it was last deployed.
+
+![Operations Dashboard with projects](img/index_operations_dashboard_with_projects.png)
+
+## Making it the default dashboard when you sign in
+
+The Operations Dashboard can also be made the default GitLab dashboard shown when
+you sign in. To make it the default, visit your [profile preferences](../profile/preferences.md).
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index dff77acd71b..a6e2f187090 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -11,10 +11,10 @@ project itself, the highest permission level is used.
On public and internal projects the Guest role is not enforced. All users will
be able to create issues, leave comments, and clone or download the project code.
-When a member leaves the team all the assigned [Issues](project/issues/index.md) and [Merge Requests](project/merge_requests/index.md)
+When a member leaves a team's project, all the assigned [Issues](project/issues/index.md) and [Merge Requests](project/merge_requests/index.md)
will be unassigned automatically.
-GitLab [administrators](../README.md#administrator-documentation) receive all permissions.
+GitLab [administrators](../administration/index.md) receive all permissions.
To add or import a user, you can follow the
[project members documentation](../user/project/members/index.md).
@@ -23,6 +23,12 @@ To add or import a user, you can follow the
See our [product handbook on permissions](https://about.gitlab.com/handbook/product#permissions-in-gitlab)
+## Instance-wide user permissions
+
+By default, users can create top-level groups and change their
+usernames. A GitLab administrator can configure the GitLab instance to
+[modify this behavior](../administration/user_settings.md).
+
## Project members permissions
NOTE: **Note:**
@@ -30,86 +36,96 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
The following table depicts the various user permission levels in a project.
-| Action | Guest | Reporter | Developer |Maintainer| Owner |
-|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
-| Lock merge request discussions | | | ✓ | ✓ | ✓ |
-| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Download project | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Assign issues | | ✓ | ✓ | ✓ | ✓ |
-| Assign merge requests | | | ✓ | ✓ | ✓ |
-| Label issues | | ✓ | ✓ | ✓ | ✓ |
-| Label merge requests | | | ✓ | ✓ | ✓ |
-| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
-| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
-| Manage labels | | ✓ | ✓ | ✓ | ✓ |
-| See a commit status | | ✓ | ✓ | ✓ | ✓ |
-| See a container registry | | ✓ | ✓ | ✓ | ✓ |
-| See environments | | ✓ | ✓ | ✓ | ✓ |
-| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
-| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
-| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
-| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
-| Lock merge request discussions | | | ✓ | ✓ | ✓ |
-| Create new environments | | | ✓ | ✓ | ✓ |
-| Stop environments | | | ✓ | ✓ | ✓ |
-| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
-| Create new merge request | | | ✓ | ✓ | ✓ |
-| Create new branches | | | ✓ | ✓ | ✓ |
-| Push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Remove non-protected branches | | | ✓ | ✓ | ✓ |
-| Add tags | | | ✓ | ✓ | ✓ |
-| Write a wiki | | | ✓ | ✓ | ✓ |
-| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
-| Create or update commit status | | | ✓ | ✓ | ✓ |
-| Update a container registry | | | ✓ | ✓ | ✓ |
-| Remove a container registry image | | | ✓ | ✓ | ✓ |
-| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
-| View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Apply code change suggestions | | | ✓ | ✓ | ✓ |
-| Use environment terminals | | | | ✓ | ✓ |
-| Add new team members | | | | ✓ | ✓ |
-| Push to protected branches | | | | ✓ | ✓ |
-| Enable/disable branch protection | | | | ✓ | ✓ |
-| Turn on/off protected branch push for devs| | | | ✓ | ✓ |
-| Enable/disable tag protections | | | | ✓ | ✓ |
-| Rewrite/remove Git tags | | | | ✓ | ✓ |
-| Edit project | | | | ✓ | ✓ |
-| Add deploy keys to project | | | | ✓ | ✓ |
-| Configure project hooks | | | | ✓ | ✓ |
-| Manage Runners | | | | ✓ | ✓ |
-| Manage job triggers | | | | ✓ | ✓ |
-| Manage variables | | | | ✓ | ✓ |
-| Manage GitLab Pages | | | | ✓ | ✓ |
-| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
-| Remove GitLab Pages | | | | ✓ | ✓ |
+| Action | Guest | Reporter | Developer |Maintainer| Owner |
+|---------------------------------------------------|---------|------------|-------------|----------|--------|
+| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View approved/blacklisted licenses **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View license management reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Security reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Manage clusters | | | | ✓ | ✓ |
-| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
-| Edit comments (posted by any user) | | | | ✓ | ✓ |
-| Manage Error Tracking | | | | ✓ | ✓ |
-| Switch visibility level | | | | | ✓ |
-| Transfer project to another namespace | | | | | ✓ |
-| Remove project | | | | | ✓ |
-| Delete issues | | | | | ✓ |
-| Force push to protected branches [^4] | | | | | |
-| Remove protected branches [^4] | | | | | |
-| View project Audit Events | | | | ✓ | ✓ |
+| View wiki pages | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See a list of jobs | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Create new issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
+| Assign issues | | ✓ | ✓ | ✓ | ✓ |
+| Label issues | | ✓ | ✓ | ✓ | ✓ |
+| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
+| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
+| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
+| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
+| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
+| See environments | | ✓ | ✓ | ✓ | ✓ |
+| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
+| View project statistics | | ✓ | ✓ | ✓ | ✓ |
+| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
+| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ |
+| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | | ✓ | ✓ | ✓ ||
+| Create new branches | | | ✓ | ✓ | ✓ |
+| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Remove non-protected branches | | | ✓ | ✓ | ✓ |
+| Create new merge request | | | ✓ | ✓ | ✓ |
+| Assign merge requests | | | ✓ | ✓ | ✓ |
+| Label merge requests | | | ✓ | ✓ | ✓ |
+| Lock merge request discussions | | | ✓ | ✓ | ✓ |
+| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
+| Create new environments | | | ✓ | ✓ | ✓ |
+| Stop environments | | | ✓ | ✓ | ✓ |
+| Add tags | | | ✓ | ✓ | ✓ |
+| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
+| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
+| Remove a container registry image | | | ✓ | ✓ | ✓ |
+| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
+| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Apply code change suggestions | | | ✓ | ✓ | ✓ |
+| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
+| Use environment terminals | | | | ✓ | ✓ |
+| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
+| Add new team members | | | | ✓ | ✓ |
+| Enable/disable branch protection | | | | ✓ | ✓ |
+| Push to protected branches | | | | ✓ | ✓ |
+| Turn on/off protected branch push for devs | | | | ✓ | ✓ |
+| Enable/disable tag protections | | | | ✓ | ✓ |
+| Rewrite/remove Git tags | | | | ✓ | ✓ |
+| Edit project | | | | ✓ | ✓ |
+| Add deploy keys to project | | | | ✓ | ✓ |
+| Configure project hooks | | | | ✓ | ✓ |
+| Manage Runners | | | | ✓ | ✓ |
+| Manage job triggers | | | | ✓ | ✓ |
+| Manage variables | | | | ✓ | ✓ |
+| Manage GitLab Pages | | | | ✓ | ✓ |
+| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
+| Remove GitLab Pages | | | | ✓ | ✓ |
+| Manage clusters | | | | ✓ | ✓ |
+| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
+| Edit comments (posted by any user) | | | | ✓ | ✓ |
+| Manage Error Tracking | | | | ✓ | ✓ |
+| Delete wiki pages | | | | ✓ | ✓ |
+| View project Audit Events | | | | ✓ | ✓ |
+| Switch visibility level | | | | | ✓ |
+| Transfer project to another namespace | | | | | ✓ |
+| Remove project | | | | | ✓ |
+| Delete issues | | | | | ✓ |
+| Force push to protected branches [^4] | | | | | |
+| Remove protected branches [^4] | | | | | |
+
+- (*1*): All users are able to perform this action on public and internal projects, but not private projects.
+- (*2*): Guest users can only view the confidential issues they created themselves
+- (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**
+- (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
## Project features permissions
@@ -152,7 +168,7 @@ to learn more.
The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
-Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more.
+Read through the documentation on [permissions for File Locking](project/file_lock.md#permissions-on-file-locking) to learn more.
### Confidential Issues permissions
@@ -162,8 +178,12 @@ read through the documentation on [permissions and access to confidential issues
### Releases permissions
-[Project Releases](project/releases/index.md) can be read by all project
-members (Reporters, Developers, Maintainers, Owners) **except Guests**.
+[Project Releases](project/releases/index.md) can be read by project
+members with Reporter, Developer, Maintainer, and Owner permissions.
+Guest users can access Release pages for downloading assets but
+are not allowed to download the source code nor see repository
+information such as tags and commits.
+
Releases can be created, updated, or deleted via [Releases APIs](../api/releases/index.md)
by project Developers, Maintainers, and Owners.
@@ -176,20 +196,21 @@ Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
-| Action | Guest | Reporter | Developer | Maintainer | Owner |
-|-------------------------|-------|----------|-----------|--------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Edit group | | | | | ✓ |
-| Create subgroup | | | | | ✓ |
-| Create project in group | | | ✓ | ✓ | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| Delete group epic **[ULTIMATE]** | | | | | ✓ |
-| View group Audit Events | | | | | ✓ |
+| Action | Guest | Reporter | Developer | Maintainer | Owner |
+|---------------------------------------|-------|----------|-----------|------------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
+| Create project in group | | | ✓ | ✓ | ✓ |
+| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Edit group | | | | | ✓ |
+| Create subgroup | | | | | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+| Delete group epic **[ULTIMATE]** | | | | | ✓ |
+| View group Audit Events | | | | | ✓ |
### Subgroup permissions
@@ -234,22 +255,22 @@ The regex pattern format is Ruby, but it needs to be convertible to JavaScript,
Here are some examples:
-- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
+- Use `\.internal@domain\.com$` to mark email addresses ending with ".internal@domain.com" internal.
- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
## Auditor users **[PREMIUM ONLY]**
->[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
+>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.17.
Auditor users are given read-only access to all projects, groups, and other
resources on the GitLab instance.
An Auditor user should be able to access all projects and groups of a GitLab instance
-with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user).
+with the permissions described on the documentation on [auditor users permissions](../administration/auditor_users.md#permissions-and-restrictions-of-an-auditor-user).
-[Read more about Auditor users.](https://docs.gitlab.com/ee/administration/auditor_users.html)
+[Read more about Auditor users.](../administration/auditor_users.md)
## Project features
@@ -282,7 +303,7 @@ instance and project. In addition, all admins can use the admin interface under
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel job | | ✓ | ✓ | ✓ |
-| Erase job artifacts and trace | | ✓ [^5] | ✓ | ✓ |
+| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
@@ -291,6 +312,8 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
+- *1*: Only if the job was triggered by the user
+
### Job permissions
NOTE: **Note:**
@@ -298,25 +321,28 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
>**Note:**
GitLab 8.12 has a completely redesigned job permissions system.
-Read all about the [new model and its implications][new-mod].
+Read all about the [new model and its implications](project/new_ci_build_permissions_model.md).
This table shows granted privileges for jobs triggered by specific types of
users:
-| Action | Guest, Reporter | Developer |Maintainer| Admin |
-|---------------------------------------------|-----------------|-------------|----------|--------|
-| Run CI job | | ✓ | ✓ | ✓ |
-| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
-| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
-| Clone source and LFS from internal projects | | ✓ [^6] | ✓ [^6] | ✓ |
-| Clone source and LFS from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push source and LFS | | | | |
-| Pull container images from current project | | ✓ | ✓ | ✓ |
-| Pull container images from public projects | | ✓ | ✓ | ✓ |
-| Pull container images from internal projects| | ✓ [^6] | ✓ [^6] | ✓ |
-| Pull container images from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push container images to current project | | ✓ | ✓ | ✓ |
-| Push container images to other projects | | | | |
+| Action | Guest, Reporter | Developer |Maintainer| Admin |
+|---------------------------------------------|-----------------|-------------|----------|---------|
+| Run CI job | | ✓ | ✓ | ✓ |
+| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
+| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
+| Clone source and LFS from internal projects | | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Clone source and LFS from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Pull container images from current project | | ✓ | ✓ | ✓ |
+| Pull container images from public projects | | ✓ | ✓ | ✓ |
+| Pull container images from internal projects| | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Pull container images from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Push container images to current project | | ✓ | ✓ | ✓ |
+| Push container images to other projects | | | | |
+| Push source and LFS | | | | |
+
+- *1*: Only if the user is not an external one
+- *2*: Only if the user is a member of the project
### New CI job permissions model
@@ -334,17 +360,4 @@ for details about the pipelines security model.
## LDAP users permissions
Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
-Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/index.html#updating-user-permissions-new-feature) to learn more.
-
-[^1]: On public and internal projects, all users are able to perform this action
-[^2]: Guest users can only view the confidential issues they created themselves
-[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD**
-[^4]: Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
-[^5]: Only if the job was triggered by the user
-[^6]: Only if user is not external one
-[^7]: Only if user is a member of the project
-
-[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
-[new-mod]: project/new_ci_build_permissions_model.md
-[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998
-[eep]: https://about.gitlab.com/pricing/
+Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index b497cc414af..0d7bbb0af79 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -14,7 +14,7 @@ Deleting a user will delete all projects in that user namespace.
[GitLab 9.1][ce-10273], and from the API in [GitLab 9.3][ce-11853].
When a user account is deleted, not all associated records are deleted with it.
-Here's a list of things that will not be deleted:
+Here's a list of things that will **not** be deleted:
- Issues that the user created
- Merge requests that the user created
diff --git a/doc/user/profile/account/index.md b/doc/user/profile/account/index.md
index 06667bfc5f1..56b6498e16c 100644
--- a/doc/user/profile/account/index.md
+++ b/doc/user/profile/account/index.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../index.md#profile-settings'
+---
This document was moved to [../index.md#profile-settings](../index.md#profile-settings).
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 8e5fe4b0fb9..df413a11af0 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -18,9 +18,10 @@ the second factor of authentication. Once enabled, in addition to supplying your
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
-The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend
-that you set up both methods of two-factor authentication, so you can still access your account
-from other browsers.
+The U2F workflow is [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera, and Firefox.
+
+We recommend that you set up 2FA with both a [one-time password authenticator](#enable-2fa-via-one-time-password-authenticator) and a [U2F device](#enable-2fa-via-u2f-device), so you can still access your account
+if you lose your U2F device.
## Enabling 2FA
@@ -59,6 +60,7 @@ of recovery codes.
### Enable 2FA via U2F device
> **Notes:**
+>
> - GitLab officially only supports [Yubikey] U2F devices.
> - Support for U2F devices was added in GitLab 8.8.
@@ -159,8 +161,8 @@ a new set of recovery codes with SSH.
1. Run `ssh git@gitlab.example.com 2fa_recovery_codes`.
1. You are prompted to confirm that you want to generate new codes. Continuing this process invalidates previously saved codes.
- ```
- bash
+
+ ```sh
$ ssh git@gitlab.example.com 2fa_recovery_codes
Are you sure you want to generate new two-factor recovery codes?
Any existing recovery codes you saved will be invalidated. (yes/no)
@@ -205,17 +207,17 @@ Sign in and re-enable two-factor authentication as soon as possible.
- You need to take special care to that 2FA keeps working after
[restoring a GitLab backup](../../../raketasks/backup_restore.md).
- To ensure 2FA authorizes correctly with TOTP server, you may want to ensure
- your GitLab server's time is synchronized via a service like NTP. Otherwise,
+ your GitLab server's time is synchronized via a service like NTP. Otherwise,
you may have cases where authorization always fails because of time differences.
- The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from
multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at
the time of registration, and cannot be used for other hostnames/FQDNs.
- For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`:
+ For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`:
- - The user logs in via `first.host.xyz` and registers their U2F key.
- - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
- - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
+ - The user logs in via `first.host.xyz` and registers their U2F key.
+ - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
+ - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`.
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md
index 5119c0e30d0..28e3f4904a9 100644
--- a/doc/user/profile/active_sessions.md
+++ b/doc/user/profile/active_sessions.md
@@ -4,7 +4,7 @@
> in GitLab 10.8.
GitLab lists all devices that have logged into your account. This allows you to
-review the sessions and revoke any of it that you don't recognize.
+review the sessions.
## Listing all active sessions
@@ -12,9 +12,3 @@ review the sessions and revoke any of it that you don't recognize.
1. Navigate to the **Active Sessions** tab.
![Active sessions list](img/active_sessions_list.png)
-
-## Revoking a session
-
-1. Navigate to your [profile's](#profile-settings) **Settings > Active Sessions**.
-1. Click on **Revoke** besides a session. The current session cannot be
- revoked, as this would sign you out of GitLab.
diff --git a/doc/user/profile/img/active_sessions_list.png b/doc/user/profile/img/active_sessions_list.png
index 5d94dca69cc..1e242ac4710 100644
--- a/doc/user/profile/img/active_sessions_list.png
+++ b/doc/user/profile/img/active_sessions_list.png
Binary files differ
diff --git a/doc/user/profile/img/personal_access_tokens.png b/doc/user/profile/img/personal_access_tokens.png
deleted file mode 100644
index d29f4cb0a20..00000000000
--- a/doc/user/profile/img/personal_access_tokens.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index a2b15d058d7..61a30a775b0 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -33,13 +33,14 @@ From there, you can:
- Set a [custom status](#current-status) for your profile
- Manage your [commit email](#commit-email) for your profile
- Manage [2FA](account/two_factor_authentication.md)
+- Add details of [external accounts](#add-details-of-external-accounts).
- Change your username and [delete your account](account/delete_account.md)
- Manage applications that can
[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
- Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications
- Add and delete emails linked to your account
- Choose which email to use for notifications, web-based commits, and display on your public profile
-- Manage [SSH keys](../../ssh/README.md#ssh) to access your account via SSH
+- Manage [SSH keys](../../ssh/README.md) to access your account via SSH
- Manage your [preferences](preferences.md#syntax-highlighting-theme)
to customize your own GitLab experience
- [View your active sessions](active_sessions.md) and revoke any of them if necessary
@@ -92,6 +93,16 @@ To enable private profile:
NOTE: **Note:**
You and GitLab admins can see your the abovementioned information on your profile even if it is private.
+## Add details of external accounts
+
+GitLab allows you to add links to certain other external accounts you might have, like Skype and Twitter. They can help other users connect with you on other platforms.
+
+To add links to other accounts:
+
+1. Navigate to your **User Settings > Profile**.
+1. In the **Main settings** section, locate and fill out fields for links to external accounts like Skype and Twitter.
+1. Click the **Update profile settings** button.
+
## Private contributions
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/14078) in GitLab 11.3.
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 7d55048c994..0b224fc7e01 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -5,12 +5,9 @@
Personal access tokens are the preferred way for third party applications and scripts to
authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical.
-You can also use them to authenticate against Git over HTTP. They are the only
-accepted method of authentication when you have
-[Two-Factor Authentication (2FA)][2fa] enabled.
+You can also use personal access tokens to authenticate against Git over HTTP or SSH. They must be used when you have [Two-Factor Authentication (2FA)][2fa] enabled. Authenticate with a token in place of your password.
-Once you have your token, [pass it to the API][usage] using either the
-`private_token` parameter or the `Private-Token` header.
+To make [authenticated requests to the API][usage], use either the `private_token` parameter or the `Private-Token` header.
The expiration of personal access tokens happens on the date you define,
at midnight UTC.
@@ -20,21 +17,19 @@ at midnight UTC.
You can create as many personal access tokens as you like from your GitLab
profile.
-1. Log in to your GitLab account.
-1. Go to your **Profile settings**.
-1. Go to **Access tokens**.
-1. Choose a name and optionally an expiry date for the token.
+1. Log in to GitLab.
+1. In the upper-right corner, click your avatar and select **Settings**.
+1. On the **User Settings** menu, select **Access Tokens**.
+1. Choose a name and optional expiry date for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token).
-1. Click on **Create personal access token**.
+1. Click the **Create personal access token** button.
1. Save the personal access token somewhere safe. Once you leave or refresh
the page, you won't be able to access it again.
-![Personal access tokens page](img/personal_access_tokens.png)
+### Revoking a personal access token
-## Revoking a personal access token
-
-At any time, you can revoke any personal access token by just clicking the
-respective **Revoke** button under the 'Active personal access tokens' area.
+At any time, you can revoke any personal access token by clicking the
+respective **Revoke** button under the **Active Personal Access Token** area.
## Limiting scopes of a personal access token
@@ -42,18 +37,18 @@ Personal access tokens can be created with one or more scopes that allow various
actions that a given token can perform. The available scopes are depicted in
the following table.
-| Scope | Description |
-| ----- | ----------- |
-|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
-| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
-| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). |
-| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
-| `read_repository` | Allows read-access (pull) to the repository through git clone. |
+| Scope | Introduced in | Description |
+| ------------------ | ------------- | ----------- |
+| `read_user` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed. |
+| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete access to the API and Container Registry (read/write). |
+| `read_registry` | [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) | Allows to read (pull) [container registry] images if a project is private and authorization is required. |
+| `sudo` | [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838) | Allows performing API actions as any user in the system (if the authenticated user is an admin). |
+| `read_repository` | [GitLab 10.7](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894) | Allows read-only access (pull) to the repository through git clone. |
+| `write_repository` | [GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26021) | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
-[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#personal-access-tokens
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index db68510c46d..b61216b7b67 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -79,6 +79,7 @@ You have 8 options here that you can use for your default dashboard view:
- Your [Todos](../../workflow/todos.md)
- Assigned Issues
- Assigned Merge Requests
+- Operations Dashboard **[PREMIUM]**
### Project overview content
@@ -103,4 +104,10 @@ Select your preferred language from a list of supported languages.
The first day of the week can be customised for calendar views and date pickers.
-You can choose **Sunday** or **Monday** as the first day of the week. If you select **System Default**, the system-wide default setting will be used.
+You can choose one of the following options as the first day of the week:
+
+- Saturday
+- Sunday
+- Monday
+
+If you select **System Default**, the system-wide default setting will be used.
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
index 19eb95099ce..8849dd2d684 100644
--- a/doc/user/project/badges.md
+++ b/doc/user/project/badges.md
@@ -63,6 +63,12 @@ are available:
- `%{commit_sha}`: ID of the most recent commit to the default branch of a
project's repository
+NOTE: **Note:**
+Placeholders allow badges to expose otherwise-private information, such as the
+default branch or commit SHA when the project is configured to have a private
+repository. This is by design, as badges are intended to be used publicly. Avoid
+using these placeholders if the information is sensitive.
+
## API
You can also configure badges via the GitLab API. As in the settings, there is
diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md
index 514c729b37d..1b0f3f61394 100644
--- a/doc/user/project/builds/artifacts.md
+++ b/doc/user/project/builds/artifacts.md
@@ -1 +1,5 @@
+---
+redirect_to: '../pipelines/job_artifacts.md'
+---
+
This document was moved to [pipelines/job_artifacts](../pipelines/job_artifacts.md).
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index fead99c5e88..d0c7daf4692 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -1,6 +1,7 @@
# Bulk editing issues and merge requests
> **Notes:**
+>
> - A permission level of `Reporter` or higher is required in order to manage
> issues.
> - A permission level of `Developer` or higher is required in order to manage
diff --git a/doc/user/project/canary_deployments.md b/doc/user/project/canary_deployments.md
new file mode 100644
index 00000000000..cce862b0911
--- /dev/null
+++ b/doc/user/project/canary_deployments.md
@@ -0,0 +1,71 @@
+# Canary Deployments **[PREMIUM]**
+
+> [Introduced][ee-1659] in [GitLab Premium][eep] 9.1.
+
+A popular [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration)
+strategy, where a small portion of the fleet is updated to the new version of
+your application.
+
+## Overview
+
+When embracing [Continuous Delivery][cd-blog], a company needs to decide what
+type of deployment strategy to use. One of the most popular strategies is canary
+deployments, where a small portion of the fleet is updated to the new version
+first. This subset, the canaries, then serve as the proverbial
+[canary in the coal mine](https://en.wiktionary.org/wiki/canary_in_a_coal_mine).
+
+If there is a problem with the new version of the application, only a small
+percentage of users are affected and the change can either be fixed or quickly
+reverted.
+
+Leveraging [Kubernetes' Canary deployments][kube-canary], visualize your canary
+deployments right inside the [Deploy Board], without the need to leave GitLab.
+
+## Use cases
+
+Canary deployments can be used when you want to ship features to only a portion of
+your pods fleet and watch their behavior as a percentage of your user base
+visits the temporarily deployed feature. If all works well, you can deploy the
+feature to production knowing that it won't cause any problems.
+
+Canary deployments are also especially useful for backend refactors, performance
+improvements, or other changes where the user interface doesn't change, but you
+want to make sure the performance stays the same, or improves. Developers need
+to be careful when using canaries with user-facing changes, because by default,
+requests from the same user will be randomly distributed between canary and
+non-canary pods, which could result in confusion or even errors. If needed, you
+may want to consider [setting `service.spec.sessionAffinity` to `ClientIP` in
+your Kubernetes service definitions][kube-net], but that is beyond the scope of
+this document.
+
+## Enabling Canary Deployments
+
+Canary deployments require that you properly configure Deploy Boards:
+
+1. Follow the steps to [enable Deploy Boards](deploy_boards.md#enabling-deploy-boards).
+1. To track canary deployments you need to label your Kubernetes deployments and
+ pods with `track: canary`. To get started quickly, you can use the [Auto Deploy]
+ template for canary deployments that GitLab provides.
+
+Depending on the deploy, the label should be either `stable` or `canary`.
+Usually, `stable` and blank or missing label means the same thing, and `canary`
+or any other track means canary/temporary.
+This allows GitLab to discover whether deployment is stable or canary (temporary).
+
+Once all of the above are set up and the pipeline has run at least once,
+navigate to the environments page under **Pipelines > Environments**.
+As the pipeline executes Deploy Boards will clearly mark canary pods, enabling
+quick and easy insight into the status of each environment and deployment.
+
+Canary deployments are marked with a yellow dot in the Deploy Board so that you
+can easily notice them.
+
+![Canary deployments on Deploy Board](img/deploy_boards_canary_deployments.png)
+
+[autodeploy]: ../../ci/autodeploy/index.md "GitLab Autodeploy"
+[eep]: https://about.gitlab.com/pricing/
+[ee-1659]: https://gitlab.com/gitlab-org/gitlab-ee/issues/1659
+[kube-canary]: https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments
+[deploy board]: deploy_boards.md
+[cd-blog]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/
+[kube-net]: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
diff --git a/doc/user/project/ci_cd_for_external_repo.md b/doc/user/project/ci_cd_for_external_repo.md
new file mode 100644
index 00000000000..a92d3a2c308
--- /dev/null
+++ b/doc/user/project/ci_cd_for_external_repo.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../ci/ci_cd_for_external_repos/index.md'
+---
+
+This document was moved to [another location](../../ci/ci_cd_for_external_repos/index.md).
diff --git a/doc/user/project/clusters/img/k8s_cluster_monitoring.png b/doc/user/project/clusters/img/k8s_cluster_monitoring.png
new file mode 100644
index 00000000000..e449893a606
--- /dev/null
+++ b/doc/user/project/clusters/img/k8s_cluster_monitoring.png
Binary files differ
diff --git a/doc/user/project/clusters/img/kubernetes_pod_logs.png b/doc/user/project/clusters/img/kubernetes_pod_logs.png
new file mode 100644
index 00000000000..e664a47386a
--- /dev/null
+++ b/doc/user/project/clusters/img/kubernetes_pod_logs.png
Binary files differ
diff --git a/doc/user/project/clusters/img/pod_logs_deploy_board.png b/doc/user/project/clusters/img/pod_logs_deploy_board.png
new file mode 100644
index 00000000000..7f83382968b
--- /dev/null
+++ b/doc/user/project/clusters/img/pod_logs_deploy_board.png
Binary files differ
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index e601d7b2ccc..dc21db603d6 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -19,8 +19,10 @@ or provide the credentials to an [existing Kubernetes cluster](#adding-an-existi
NOTE: **Note:**
From [GitLab 11.6](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) you
-can also associate a Kubernetes cluster to your groups. Learn more about
-[group Kubernetes clusters](../../group/clusters/index.md).
+can also associate a Kubernetes cluster to your groups and from
+[GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840),
+to the GitLab instance. Learn more about [group-level](../../group/clusters/index.md)
+and [instance-level](../../instance/clusters/index.md) Kubernetes clusters.
## Adding and creating a new GKE cluster via GitLab
@@ -41,7 +43,7 @@ integration, make sure the following requirements are met:
- A [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
is set up and you have permissions to access it.
-- The Kubernetes Engine API is enabled. Follow the steps as outlined in the
+- The Kubernetes Engine API and related service are enabled. It should work immediately but may take up to 10 minutes after you create a project. For more information see the
["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin).
### Creating the cluster
@@ -69,11 +71,21 @@ new Kubernetes cluster to your project:
- **Number of nodes** - Enter the number of nodes you wish the cluster to have.
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
+ - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#rbac-cluster-resources) for more information.
+ - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
+NOTE: **Note:**
+GitLab requires basic authentication enabled and a client certificate issued for
+the cluster in order to setup an [initial service
+account](#access-controls). Starting from [GitLab
+11.10](https://gitlab.com/gitlab-org/gitlab-ce/issues/58208), the cluster
+creation process will explicitly request that basic authentication and
+client certificate is enabled.
+
## Adding an existing Kubernetes cluster
To add an existing Kubernetes cluster to your project:
@@ -87,19 +99,25 @@ To add an existing Kubernetes cluster to your project:
1. Click **Add an existing Kubernetes cluster** and fill in the details:
- **Kubernetes cluster name** (required) - The name you wish to give the cluster.
- **Environment scope** (required) - The
- [associated environment](#setting-the-environment-scope) to this cluster.
+ [associated environment](#setting-the-environment-scope-premium) to this cluster.
- **API URL** (required) -
It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
exposes several APIs, we want the "base" URL that is common to all of them,
e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
+
+ Get the API URL by running this command:
+
+ ```sh
+ kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
+ ```
- **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
- - List the secrets with `kubectl get secrets`, and one should named similar to
+ - List the secrets with `kubectl get secrets`, and one should named similar to
`default-token-xxxxx`. Copy that token name for use below.
- - Get the certificate by running this command:
+ - Get the certificate by running this command:
- ```sh
- kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
- ```
+ ```sh
+ kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
+ ```
- **Token** -
GitLab authenticates against Kubernetes using service tokens, which are
scoped to a particular `namespace`.
@@ -109,15 +127,28 @@ To add an existing Kubernetes cluster to your project:
1. Create a file called `gitlab-admin-service-account.yaml` with contents:
- ```yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: gitlab-admin
- namespace: kube-system
- ```
-
- 2. Apply the service account to your cluster:
+ ```yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: gitlab-admin
+ namespace: kube-system
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1beta1
+ kind: ClusterRoleBinding
+ metadata:
+ name: gitlab-admin
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-admin
+ subjects:
+ - kind: ServiceAccount
+ name: gitlab-admin
+ namespace: kube-system
+ ```
+
+ 1. Apply the service account and cluster role binding to your cluster:
```bash
kubectl apply -f gitlab-admin-service-account.yaml
@@ -125,69 +156,44 @@ To add an existing Kubernetes cluster to your project:
Output:
- ```bash
- serviceaccount "gitlab-admin" created
- ```
-
- 3. Create a file called `gitlab-admin-cluster-role-binding.yaml` with contents:
-
- ```yaml
- apiVersion: rbac.authorization.k8s.io/v1beta1
- kind: ClusterRoleBinding
- metadata:
- name: gitlab-admin
- roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
- subjects:
- - kind: ServiceAccount
- name: gitlab-admin
- namespace: kube-system
- ```
-
- 4. Apply the cluster role binding to your cluster:
-
- ```bash
- kubectl apply -f gitlab-admin-cluster-role-binding.yaml
- ```
-
- Output:
-
```bash
+ serviceaccount "gitlab-admin" created
clusterrolebinding "gitlab-admin" created
```
- 5. Retrieve the token for the `gitlab-admin` service account:
+ 1. Retrieve the token for the `gitlab-admin` service account:
```bash
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
```
- Copy the `<authentication_token>` value from the output:
+ Copy the `<authentication_token>` value from the output:
- ```yaml
- Name: gitlab-admin-token-b5zv4
- Namespace: kube-system
- Labels: <none>
- Annotations: kubernetes.io/service-account.name=gitlab-admin
- kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
+ ```yaml
+ Name: gitlab-admin-token-b5zv4
+ Namespace: kube-system
+ Labels: <none>
+ Annotations: kubernetes.io/service-account.name=gitlab-admin
+ kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
- Type: kubernetes.io/service-account-token
+ Type: kubernetes.io/service-account-token
+
+ Data
+ ====
+ ca.crt: 1025 bytes
+ namespace: 11 bytes
+ token: <authentication_token>
+ ```
- Data
- ====
- ca.crt: 1025 bytes
- namespace: 11 bytes
- token: <authentication_token>
- ```
-
NOTE: **Note:**
For GKE clusters, you will need the
`container.clusterRoleBindings.create` permission to create a cluster
role binding. You can follow the [Google Cloud
documentation](https://cloud.google.com/iam/docs/granting-changing-revoking-access)
to grant access.
+
+ - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
+
- **Project namespace** (optional) - You don't have to fill it in; by leaving
it blank, GitLab will create one for you. Also:
- Each project should have a unique namespace.
@@ -203,14 +209,6 @@ To add an existing Kubernetes cluster to your project:
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
-To determine the:
-
-- API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'`.
-- Token:
- 1. List the secrets by running: `kubectl get secrets`. Note the name of the secret you need the token for.
- 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 --decode`.
-- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode`.
-
## Security implications
CAUTION: **Important:**
@@ -222,78 +220,112 @@ functionalities needed to successfully build and deploy a containerized
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
+## Gitlab-managed clusters
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
+> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
+
+NOTE: **Note:**
+Only available when creating clusters. Existing clusters not managed by GitLab
+cannot become GitLab-managed later.
+
+You can choose to allow GitLab to manage your cluster for you. If your cluster is
+managed by GitLab, resources for your projects will be automatically created. See the
+[Access controls](#access-controls) section for details on which resources will
+be created.
+
+If you choose to manage your own cluster, project-specific resources will not be created
+automatically. If you are using [Auto DevOps](../../../topics/autodevops/index.md), you will
+need to explicitly provide the `KUBE_NAMESPACE` [deployment variable](#deployment-variables)
+that will be used by your deployment jobs, otherwise a namespace will be created for you.
+
+NOTE: **Note:**
+If you [install applications](#installing-applications) on your cluster, GitLab will create
+the resources required to run these even if you have chosen to manage your own cluster.
+
## Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
-Domains at the cluster level permit support for multiple domains
-per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
-this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
-the [Auto DevOps](../../../topics/autodevops/index.md) stages.
+NOTE: **Note:**
+You do not need to specify a base domain on cluster settings when using GitLab Serverless. The domain in that case
+will be specified as part of the Knative installation. See [Installing Applications](#installing-applications).
+
+Specifying a base domain will automatically set `KUBE_INGRESS_BASE_DOMAIN` as an environment variable.
+If you are using [Auto DevOps](../../../topics/autodevops/index.md), this domain will be used for the different
+stages. For example, Auto Review Apps and Auto Deploy.
+
+The domain should have a wildcard DNS configured to the Ingress IP address. After ingress has been installed (see [Installing Applications](#installing-applications)),
+you can either:
-The domain should have a wildcard DNS configured to the Ingress IP address.
+- Create an `A` record that points to the Ingress IP address with your domain provider.
+- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
## Access controls
-When creating a cluster in GitLab, you will be asked if you would like to create an
-[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/) cluster, or
-a [Role-based access control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) one.
+When creating a cluster in GitLab, you will be asked if you would like to create either:
-NOTE: **Note:**
-[RBAC](#role-based-access-control-rbac) is recommended and the GitLab default.
+- An [Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/) cluster.
+- A [Role-based access control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) cluster.
-Whether [ABAC](#attribute-based-access-control-abac) or [RBAC](#role-based-access-control-rbac) is enabled,
-GitLab will create the necessary service accounts and privileges in order to install and run
-[GitLab managed applications](#installing-applications):
+NOTE: **Note:**
+[RBAC](#rbac-cluster-resources) is recommended and the GitLab default.
-- If GitLab is creating the cluster, a `gitlab` service account with
- `cluster-admin` privileges will be created in the `default` namespace,
- which will be used by GitLab to manage the newly created cluster.
+GitLab creates the necessary service accounts and privileges to install and run
+[GitLab managed applications](#installing-applications). When GitLab creates the cluster:
+- A `gitlab` service account with `cluster-admin` privileges is created in the `default` namespace
+ to manage the newly created cluster.
- A project service account with [`edit`
privileges](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
- will be created in the project namespace (also created by GitLab), which will
- be used in [deployment jobs](#deployment-variables).
+ is created in the GitLab-created project namespace for [deployment jobs](#deployment-variables).
NOTE: **Note:**
Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716) in GitLab 11.5.
-- When you install Helm Tiller into your cluster, the `tiller` service account
- will be created with `cluster-admin` privileges in the `gitlab-managed-apps`
- namespace. This service account will be added to the installed Helm Tiller and will
- be used by Helm to install and run [GitLab managed applications](#installing-applications).
- Helm Tiller will also create additional service accounts and other resources for each
- installed application. Consult the documentation of the Helm charts for each application
- for details.
+When you install Helm into your cluster, the `tiller` service account
+is created with `cluster-admin` privileges in the `gitlab-managed-apps`
+namespace. This service account will be added to the installed Helm Tiller and will
+be used by Helm to install and run [GitLab managed applications](#installing-applications).
+Helm will also create additional service accounts and other resources for each
+installed application. Consult the documentation of the Helm charts for each application
+for details.
If you are [adding an existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster),
ensure the token of the account has administrator privileges for the cluster.
-The following sections summarize which resources will be created on ABAC/RBAC clusters.
+The resources created by GitLab differ depending on the type of cluster.
+
+### ABAC cluster resources
+
+GitLab creates the following resources for ABAC clusters.
-### Attribute-based access control (ABAC)
+| Name | Type | Details | Created when |
+|:------------------|:---------------------|:----------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
-| Name | Kind | Details | Created when |
-| --- | --- | --- | --- |
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Creating/Adding a new GKE Cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Creating/Adding a new GKE Cluster |
+### RBAC cluster resources
-### Role-based access control (RBAC)
+GitLab creates the following resources for RBAC clusters.
-| Name | Kind | Details | Created when |
-| --- | --- | --- | --- |
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Creating/Adding a new GKE Cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Creating/Adding a new GKE Cluster |
-| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating/Adding a new GKE Cluster |
+| Name | Type | Details | Created when |
+|:------------------|:---------------------|:-----------------------------------------------------------------------------------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
+| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Deploying to a cluster |
+
+NOTE: **Note:**
+Project-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters).
### Security of GitLab Runners
@@ -315,91 +347,38 @@ install it manually.
## Installing applications
-NOTE: **Note:**
-Before starting the installation of applications, make sure that time is synchronized
-between your GitLab server and your Kubernetes cluster. Otherwise, installation could fail
-and you may get errors like `Error: remote error: tls: bad certificate`
-in the `stdout` of pods created by GitLab in your Kubernetes cluster.
-
-GitLab provides a one-click install for various applications which can
-be added directly to your configured cluster. Those applications are
-needed for [Review Apps](../../../ci/review_apps/index.md) and
-[deployments](../../../ci/environments.md). You can install them after you
-[create a cluster](#adding-and-creating-a-new-gke-cluster-via-gitlab).
-
-To see a list of available applications to install:
-
-1. Navigate to your project's **Operations > Kubernetes**.
-1. Select your cluster.
-
-Install Helm Tiller first because it's used to install other applications.
-
-NOTE: **Note:**
-As of GitLab 11.6, Helm Tiller will be upgraded to the latest version supported
-by GitLab before installing any of the applications.
-
-| Application | GitLab version | Description | Helm Chart |
-| ----------- | :------------: | ----------- | --------------- |
-| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
-| [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
-| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
-| [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts)
-
-With the exception of Knative, the applications will be installed in a dedicated
-namespace called `gitlab-managed-apps`.
-
-CAUTION: **Caution:**
-If you have an existing Kubernetes cluster with Tiller already installed,
-you should be careful as GitLab cannot detect it. In this case, installing
-Tiller via the applications will result in the cluster having it twice, which
-can lead to confusion during deployments.
-
-### Upgrading applications
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
-in GitLab 11.8.
-
-Users can perform a one-click upgrade for the GitLab Runner application,
-when there is an upgrade available.
-
-To upgrade the GitLab Runner application:
-
-1. Navigate to your project's **Operations > Kubernetes**.
-1. Select your cluster.
-1. Click the **Upgrade** button for the Runnner application.
-
-The **Upgrade** button will not be shown if there is no upgrade
-available.
+GitLab can install and manage some applications in your project-level
+cluster. For more information on installing, upgrading, uninstalling,
+and troubleshooting applications for your project cluster, see
+[Gitlab Managed Apps](../../clusters/applications.md).
-NOTE: **Note:**
-Upgrades will reset values back to the values built into the `runner`
-chart plus the values set by
-[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
-
-## Getting the external IP address
+## Getting the external endpoint
NOTE: **Note:**
With the following procedure, a load balancer must be installed in your cluster
-to obtain the external IP address. You can use either
+to obtain the endpoint. You can use either
[Ingress](#installing-applications), or Knative's own load balancer
([Istio](https://istio.io)) if using [Knative](#installing-applications).
-In order to publish your web application, you first need to find the external IP
-address associated to your load balancer.
+In order to publish your web application, you first need to find the endpoint which will be either an IP
+address or a hostname associated with your load balancer.
-### Let GitLab fetch the IP address
+### Automatically determining the external endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
-If you [installed Ingress or Knative](#installing-applications),
-you should see the Ingress IP address on this same page within a few minutes.
-If you don't see this, GitLab might not be able to determine the IP address of
-your ingress application in which case you should manually determine it.
+After you install [Ingress or Knative](#installing-applications), Gitlab attempts to determine the external endpoint
+and it should be available within a few minutes. If the endpoint doesn't appear
+and your cluster runs on Google Kubernetes Engine:
+
+1. Check your [Kubernetes cluster on Google Kubernetes Engine](https://console.cloud.google.com/kubernetes) to ensure there are no errors on its nodes.
+1. Ensure you have enough [Quotas](https://console.cloud.google.com/iam-admin/quotas) on Google Kubernetes Engine. For more information, see [Resource Quotas](https://cloud.google.com/compute/quotas).
+1. Check [Google Cloud's Status](https://status.cloud.google.com/) to ensure they are not having any disruptions.
-### Manually determining the IP address
+If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can
+manually determine it by following the steps below.
+
+### Manually determining the external endpoint
If the cluster is on GKE, click the **Google Kubernetes Engine** link in the
**Advanced settings**, or go directly to the
@@ -409,7 +388,7 @@ the `gcloud` command in a local terminal or using the **Cloud Shell**.
If the cluster is not on GKE, follow the specific instructions for your
Kubernetes provider to configure `kubectl` with the right credentials.
-The output of the following examples will show the external IP address of your
+The output of the following examples will show the external endpoint of your
cluster. This information can then be used to set up DNS entries and forwarding
rules that allow external access to your deployed applications.
@@ -417,19 +396,19 @@ If you installed the Ingress [via the **Applications**](#installing-applications
run the following command:
```bash
-kubectl get svc --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
+kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
-For Istio/Knative, the command will be different:
+Some Kubernetes clusters return a hostname instead, like [Amazon EKS](https://aws.amazon.com/eks/). For these platforms, run:
```bash
-kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
+kubectl get service --namespace=gitlab-managed-apps ingress-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
```
-Some Kubernetes clusters return a hostname instead, like [Amazon EKS](https://aws.amazon.com/eks/). For these platforms, run:
+For Istio/Knative, the command will be different:
```bash
-kubectl get service ingress-nginx-ingress-controller -n gitlab-managed-apps -o jsonpath="{.status.loadBalancer.ingress[0].hostname}".
+kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
```
Otherwise, you can list the IP addresses of all load balancers:
@@ -448,13 +427,12 @@ reserved IP.
Read how to [promote an ephemeral external IP address in GKE](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#promote_ephemeral_ip).
-### Pointing your DNS at the cluster IP
+### Pointing your DNS at the external endpoint
-Once you've set up the static IP, you should associate it to a [wildcard DNS
-record](https://en.wikipedia.org/wiki/Wildcard_DNS_record), in order to be able
-to reach your apps. This heavily depends on your domain provider, but in case
-you aren't sure, just create an A record with a wildcard host like
-`*.example.com.`.
+Once you've set up the external endpoint, you should associate it with a [wildcard DNS
+record](https://en.wikipedia.org/wiki/Wildcard_DNS_record) such as `*.example.com.`
+in order to be able to reach your apps. If your external endpoint is an IP address,
+use an A record. If your external endpoint is a hostname, use a CNAME record.
## Multiple Kubernetes clusters **[PREMIUM]**
@@ -465,17 +443,14 @@ project. That way you can have different clusters for different environments,
like dev, staging, production, etc.
Simply add another cluster, like you did the first time, and make sure to
-[set an environment scope](#setting-the-environment-scope) that will
+[set an environment scope](#setting-the-environment-scope-premium) that will
differentiate the new cluster with the rest.
## Setting the environment scope **[PREMIUM]**
-When adding more than one Kubernetes clusters to your project, you need
-to differentiate them with an environment scope. The environment scope
-associates clusters with [environments](../../../ci/environments.md)
-similar to how the [environment-specific
-variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
-work.
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
The default environment scope is `*`, which means all jobs, regardless of their
environment, will use that cluster. Each scope can only be used by a single
@@ -527,7 +502,7 @@ The result will then be:
## Deployment variables
The Kubernetes cluster integration exposes the following
-[deployment variables](../../../ci/variables/README.md#deployment-variables) in the
+[deployment variables](../../../ci/variables/README.md#deployment-environment-variables) in the
GitLab CI/CD build environment.
| Variable | Description |
@@ -538,33 +513,34 @@ GitLab CI/CD build environment.
| `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. |
| `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. |
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. |
-| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. | 
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. |
NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
service account of the cluster integration.
-### Troubleshooting missing `KUBECONFIG` or `KUBE_TOKEN`
+### Troubleshooting failed deployment jobs
+
+GitLab will create a namespace and service account specifically for your
+deployment jobs. On project level clusters, this happens when the cluster
+is created. On group level clusters, resources are created immediately
+before the deployment job starts.
-GitLab will create a new service account specifically for your CI builds. The
-new service account is created when the cluster is added to the project.
-Sometimes there may be errors that cause the service account creation to fail.
+However, sometimes GitLab can not create them. In such instances, your job will fail with the message:
-In such instances, your build will not be passed the `KUBECONFIG` or
-`KUBE_TOKEN` variables and, if you are using Auto DevOps, your Auto DevOps
-pipelines will no longer trigger a `production` deploy build. You will need to
-check the [logs](../../../administration/logs.md) to debug why the service
-account creation failed.
+```text
+This job failed because the necessary resources were not successfully created.
+```
-A common reason for failure is that the token you gave GitLab did not have
-[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
-privileges as GitLab expects.
+To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#kuberneteslog).
-Another common problem for why these variables are not being passed to your
-builds is that they must have a matching
-[`environment:name`](../../../ci/environments.md#defining-environments). If
-your build has no `environment:name` set, it will not be passed the Kubernetes
-credentials.
+Common reasons for failure include:
+
+- The token you gave GitLab did not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+ privileges required by GitLab.
+- Missing `KUBECONFIG` or `KUBE_TOKEN` variables. To be passed to your job, they must have a matching
+ [`environment:name`](../../../ci/environments.md#defining-environments). If your job has no
+ `environment:name` set, it will not be passed the Kubernetes credentials.
## Monitoring your Kubernetes cluster **[ULTIMATE]**
@@ -572,7 +548,7 @@ credentials.
When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
-![Cluster Monitoring](https://docs.gitlab.com/ee/user/project/clusters/img/k8s_cluster_monitoring.png)
+![Cluster Monitoring](img/k8s_cluster_monitoring.png)
## Enabling or disabling the Kubernetes cluster integration
@@ -600,11 +576,6 @@ To remove the Kubernetes cluster integration from your project, simply click the
**Remove integration** button. You will then be able to follow the procedure
and add a Kubernetes cluster again.
-## View Kubernetes pod logs from GitLab **[ULTIMATE]**
-
-Learn how to easily
-[view the logs of running pods in connected Kubernetes clusters](https://docs.gitlab.com/ee/user/project/clusters/kubernetes_pod_logs.html).
-
## What you can get with the Kubernetes integration
Here's what you can do with GitLab if you enable the Kubernetes integration.
@@ -617,7 +588,7 @@ displaying the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
-[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+[Read more about Deploy Boards](../deploy_boards.md)
### Canary Deployments **[PREMIUM]**
@@ -625,7 +596,13 @@ Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cl
and visualize your canary deployments right inside the Deploy Board, without
the need to leave GitLab.
-[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+[Read more about Canary Deployments](../canary_deployments.md)
+
+### Pod logs **[ULTIMATE]**
+
+GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
+
+[Read more about Kubernetes pod logs](kubernetes_pod_logs.md)
### Kubernetes monitoring
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
new file mode 100644
index 00000000000..368031070c1
--- /dev/null
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -0,0 +1,21 @@
+# Kubernetes Pod Logs **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4752) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
+
+GitLab makes it easy to view the logs of running pods in [connected Kubernetes clusters](index.md).
+By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
+
+## Overview
+
+[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
+
+1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`.
+1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md).
+1. When mousing over the list of pods, a tooltip will appear with the exact pod name and status.
+![Deploy Boards pod list](img/pod_logs_deploy_board.png)
+1. Click on the desired pod to bring up the logs view, which will contain the last 500 lines for that pod. Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab-ee/issues/6502).
+![Deploy Boards pod list](img/kubernetes_pod_logs.png)
+
+## Requirements
+
+[Enabling Deploy Boards](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs.
diff --git a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
index 84cce311483..f3e868de802 100644
--- a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
+++ b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/gitlab-variables.png b/doc/user/project/clusters/runbooks/img/gitlab-variables.png
index 1d338f063a9..7c9c9099851 100644
--- a/doc/user/project/clusters/runbooks/img/gitlab-variables.png
+++ b/doc/user/project/clusters/runbooks/img/gitlab-variables.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/helm-install.png b/doc/user/project/clusters/runbooks/img/helm-install.png
index 003e482e756..e67cf317ec1 100644
--- a/doc/user/project/clusters/runbooks/img/helm-install.png
+++ b/doc/user/project/clusters/runbooks/img/helm-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/ingress-install.png b/doc/user/project/clusters/runbooks/img/ingress-install.png
index 7edc11d5b45..08256a65138 100644
--- a/doc/user/project/clusters/runbooks/img/ingress-install.png
+++ b/doc/user/project/clusters/runbooks/img/ingress-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
index 75c6028a763..784e508ff25 100644
--- a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
+++ b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/postgres-query.png b/doc/user/project/clusters/runbooks/img/postgres-query.png
index 04315d54d5e..dbb5a62ebba 100644
--- a/doc/user/project/clusters/runbooks/img/postgres-query.png
+++ b/doc/user/project/clusters/runbooks/img/postgres-query.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/sample-runbook.png b/doc/user/project/clusters/runbooks/img/sample-runbook.png
index 70011202bf0..545d34fd4ad 100644
--- a/doc/user/project/clusters/runbooks/img/sample-runbook.png
+++ b/doc/user/project/clusters/runbooks/img/sample-runbook.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index 512a4cb32f4..c67b12fb91a 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -4,6 +4,10 @@ Runbooks are a collection of documented procedures that explain how to
carry out a particular process, be it starting, stopping, debugging,
or troubleshooting a particular system.
+Using [Jupyter Notebooks](https://jupyter.org/) and the [Rubix library](https://github.com/Nurtch/rubix),
+users can get started writing their own executable runbooks.
+
+
## Overview
Historically, runbooks took the form of a decision tree or a detailed
@@ -13,13 +17,15 @@ Modern implementations have introduced the concept of an "executable
runbooks", where, along with a well-defined process, operators can execute
pre-written code blocks or database queries against a given environment.
-## Nurtch Executable Runbooks
+## Executable Runbooks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45912) in GitLab 11.4.
The JupyterHub app offered via GitLab’s Kubernetes integration now ships
with Nurtch’s Rubix library, providing a simple way to create DevOps
-runbooks. A sample runbook is provided, showcasing common operations.
+runbooks. A sample runbook is provided, showcasing common operations. While Rubix makes it
+simple to create common Kubernetes and AWS workflows, you can also create them manually without
+Rubix.
**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE)
@@ -30,13 +36,13 @@ for an overview of how this is accomplished in GitLab!**
To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
all the other applications. It is installed in its own pod inside the cluster which
can run the helm CLI in a safe environment.
1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
virtual hosting. It acts as a web proxy for your applications.
-1. **JupyterHub** - JupyterHub is a multi-user service for managing notebooks across
+1. **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user service for managing notebooks across
a team. Jupyter Notebooks provide a web-based interactive programming environment
used for data analysis, visualization, and machine learning.
@@ -55,7 +61,7 @@ the components outlined above and the preloaded demo runbook.
### 1. Add a Kubernetes cluster
-Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab)
+Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
@@ -86,7 +92,7 @@ The server will take a couple of seconds to start.
### 4. Configure access
In order for the runbook to access your GitLab project, you will need to enter a
-[GitLab Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
+[GitLab Access Token](../../../profile/personal_access_tokens.md)
as well as your Project ID in the **Setup** section of the demo runbook.
Double-click the **DevOps-Runbook-Demo** folder located on the left panel.
diff --git a/doc/user/project/clusters/serverless/img/function-details-loaded.png b/doc/user/project/clusters/serverless/img/function-details-loaded.png
new file mode 100644
index 00000000000..34465c5c087
--- /dev/null
+++ b/doc/user/project/clusters/serverless/img/function-details-loaded.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 46d50f8894a..2cda850f24e 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -11,18 +11,22 @@ Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/
Knative extends Kubernetes to provide a set of middleware components that are useful to build modern, source-centric, container-based applications. Knative brings some significant benefits out of the box through its main components:
-- [Build](https://github.com/knative/build): Source-to-container build orchestration.
-- [Eventing](https://github.com/knative/eventing): Management and delivery of events.
- [Serving](https://github.com/knative/serving): Request-driven compute that can scale to zero.
+- [Eventing](https://github.com/knative/eventing): Management and delivery of events.
For more information on Knative, visit the [Knative docs repo](https://github.com/knative/docs).
-With GitLab serverless, you can deploy both functions-as-a-service (FaaS) and serverless applications.
+With GitLab Serverless, you can deploy both functions-as-a-service (FaaS) and serverless applications.
-## Requirements
+## Prerequisites
To run Knative on Gitlab, you will need:
+1. **Existing GitLab project:** You will need a GitLab project to associate all resources. The simplest way to get started:
+
+ - If you are planning on deploying functions, clone the [functions example project](https://gitlab.com/knative-examples/functions) to get started.
+ - If you are planning on deploying a serverless application, clone the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
+
1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
@@ -32,17 +36,22 @@ To run Knative on Gitlab, you will need:
applications or functions onto your cluster. You can install the GitLab Runner
onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an
- external IP address for all the applications served by Knative. You will be prompted to enter a
+ external IP address or hostname for all the applications served by Knative. You will be prompted to enter a
wildcard domain where your applications will be served. Configure your DNS server to use the
- external IP address for that domain.
+ external IP address or hostname for that domain.
1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
- to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm) to simplify the
- deployment of knative services and functions.
+ to build the application. We also use [gitlabktl](https://gitlab.com/gitlab-org/gitlabktl)
+ and [TriggerMesh CLI](https://github.com/triggermesh/tm) CLIs to simplify the
+ deployment of services and functions to Knative.
1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file
will contain the information for all the functions being hosted in the repository as well as a reference to the
runtime being used.
-1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included
- at the root of your project's repo and expose port `8080`.
+1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a
+ `Dockerfile` in order to build your applications. It should be included at the root of your
+ project's repo and expose port `8080`. `Dockerfile` is not require if you plan to build serverless functions
+ using our [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes).
+1. **Prometheus** (optional): Installing Prometheus allows you to monitor the scale and traffic of your serverless function/application.
+ See [Installing Applications](../index.md#installing-applications) for more information.
## Installing Knative via GitLab's Kubernetes integration
@@ -55,23 +64,18 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.
![install-knative](img/install-knative.png)
-1. After the Knative installation has finished, you can wait for the IP address to be displayed in the
- **Knative IP Address** field or retrieve the Istio Ingress IP address by running the following command:
-
- ```bash
- kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
- ```
-
- Output:
+1. After the Knative installation has finished, you can wait for the IP address or hostname to be displayed in the
+ **Knative Endpoint** field or [retrieve the Istio Ingress Endpoint manually](../#manually-determining-the-external-endpoint).
- ```bash
- 35.161.143.124 my-machine-name:~ my-user$
- ```
+ NOTE: **Note:**
+ Running `kubectl` commands on your cluster requires setting up access to the cluster first.
+ For clusters created on GKE, see [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl),
+ for other platforms [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS
name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example,
- if your Knative base domain is `example.com` then you need to create an A record with domain `*.example.com`
- pointing the ip address of the ingress.
+ if your Knative base domain is `knative.info` then you need to create an A record or CNAME record with domain `*.knative.info`
+ pointing the ip address or hostname of the ingress.
![dns entry](img/dns-entry.png)
@@ -87,14 +91,15 @@ Using functions is useful for dealing with independent
events without needing to maintain a complex unified infrastructure. This allows
you to focus on a single task that can be executed/scaled automatically and independently.
-Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are offered:
+Currently the following [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes) are offered:
+- ruby
- node.js
-- kaniko
+- Dockerfile
You can find and import all the files referenced in this doc in the **[functions example project](https://gitlab.com/knative-examples/functions)**.
-Follow these steps to deploy a function using the Node.js runtime to your Knative instance:
+Follow these steps to deploy a function using the Node.js runtime to your Knative instance (you can skip these steps if you've cloned the example project):
1. Create a directory that will house the function. In this example we will create a directory called `echo` at the root of the project.
@@ -102,53 +107,60 @@ Follow these steps to deploy a function using the Node.js runtime to your Knativ
- Public, continue to the next step.
- Private, you will need to [create a GitLab deploy token](../../deploy_tokens/index.md#creating-a-deploy-token) with `gitlab-deploy-token` as the name and the `read_registry` scope.
-1. `.gitlab-ci.yml`: This template allows to define the stage, environment, and
- image to be used for your functions. It must be included at the root of your repository:
+1. `.gitlab-ci.yml`: this defines a pipeline used to deploy your functions.
+ It must be included at the root of your repository:
```yaml
- stages:
- - deploy
+ include:
+ template: Serverless.gitlab-ci.yml
- functions:
- stage: deploy
- environment: test
- image: gcr.io/triggermesh/tm:v0.0.9
- script:
- - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN" --push
- - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_DEPLOY_USER" --password "$CI_DEPLOY_PASSWORD" --pull
- - tm -n "$KUBE_NAMESPACE" deploy --wait
+ functions:build:
+ extends: .serverless:build:functions
+ environment: production
+ functions:deploy:
+ extends: .serverless:deploy:functions
+ environment: production
```
- The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters.
+ This `.gitlab-ci.yml` creates jobs that invoke some predefined commands to
+ build and deploy your functions to your cluster.
+
+ `Serverless.gitlab-ci.yml` is a template that allows customization.
+ You can either import it with `include` parameter and use `extends` to
+ customize your jobs, or you can inline the entire template by choosing it
+ from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through
+ the user interface.
-2. `serverless.yml`: This file contains the metadata for your functions,
- such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file. You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions).
+2. `serverless.yml`: this file contains the metadata for your functions,
+ such as name, runtime, and environment.
+
+ It must be included at the root of your repository.
+ The following is a sample `echo` function which shows the required structure
+ for the file.
+
+ You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions).
```yaml
- service: my-functions
- description: "Deploying functions from GitLab using Knative"
+ service: functions
+ description: "GitLab Serverless functions using Knative"
provider:
name: triggermesh
- registry-secret: gitlab-registry
environment:
- FOO: BAR
+ FOO: value
functions:
- echo:
- handler: echo
- runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml
- description: "echo function using node.js runtime"
- buildargs:
- - DIRECTORY=echo
+ echo-js:
+ handler: echo-js
+ source: ./echo-js
+ runtime: https://gitlab.com/gitlab-org/serverless/runtimes/nodejs
+ description: "node.js runtime function"
environment:
- FUNCTION: echo
+ MY_FUNCTION: echo-js
```
-The `serverless.yml` file references both an `echo` directory (under `buildargs`) and an `echo` file (under `handler`),
-which is a reference to `echo.js` in the [repository](https://gitlab.com/knative-examples/functions). Additionally, it
-contains three sections with distinct parameters:
+Explanation of the fields used above:
### `service`
@@ -162,7 +174,6 @@ contains three sections with distinct parameters:
| Parameter | Description |
|-----------|-------------|
| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh `tm` CLI. |
-| `registry-secret` | Indicates which registry will be used to store docker images. The sample function is using the GitLab Registry (`gitlab-registry`). A different registry host may be specified using `registry` key in the `provider` object. If changing the default, update the permission and the secret value on the `gitlab-ci.yml` file |
| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. |
### `functions`
@@ -171,10 +182,10 @@ In the `serverless.yml` example above, the function name is `echo` and the subse
| Parameter | Description |
|-----------|-------------|
-| `handler` | The function's file name. In the example above, both the function name and the handler are the same. |
+| `handler` | The function's name. |
+| `source` | Directory with sources of a functions. |
| `runtime` | The runtime to be used to execute the function. |
| `description` | A short description of the function. |
-| `buildargs` | Pointer to the function file in the repo. In the sample the function is located in the `echo` directory. |
| `environment` | Sets an environment variable for the specific function only. |
After the `gitlab-ci.yml` template has been added and the `serverless.yml` file has been
@@ -188,7 +199,7 @@ appear under **Operations > Serverless**.
This page contains all functions available for the project, the description for
accessing the function, and, if available, the function's runtime information.
The details are derived from the Knative installation inside each of the project's
-Kubernetes cluster.
+Kubernetes cluster. Click on each function to obtain detailed scale and invocation data.
The function details can be retrieved directly from Knative on the cluster:
@@ -198,7 +209,7 @@ kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev
The sample function can now be triggered from any HTTP client using a simple `POST` call:
- 1. Using curl
+ 1. Using curl (replace the URL on the last line with the URL of your application):
```bash
curl \
@@ -219,35 +230,25 @@ NOTE: **Note:**
You can reference and import the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
Add the following `.gitlab-ci.yml` to the root of your repository
-(you may skip this step if using the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above):
+(you may skip this step if you've previously cloned the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above):
```yaml
-stages:
- - build
- - deploy
+include:
+ template: Serverless.gitlab-ci.yml
build:
- stage: build
- image:
- name: gcr.io/kaniko-project/executor:debug
- entrypoint: [""]
- only:
- - master
- script:
- - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE
+ extends: .serverless:build:image
deploy:
- stage: deploy
- image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5
- only:
- - master
- environment: production
- script:
- - echo "$CI_REGISTRY_IMAGE"
- - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
+ extends: .serverless:deploy:image
```
+`Serverless.gitlab-ci.yml` is a template that allows customization.
+You can either import it with `include` parameter and use `extends` to
+customize your jobs, or you can inline the entire template by choosing it
+from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through
+the user interface.
+
### Deploy the application with Knative
With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to
@@ -285,3 +286,23 @@ The second to last line, labeled **Service domain** contains the URL for the dep
browser to see the app live.
![knative app](img/knative-app.png)
+
+## Function details
+
+Go to the **Operations > Serverless** page and click on one of the function
+rows to bring up the function details page.
+
+![function_details](img/function-details-loaded.png)
+
+The pod count will give you the number of pods running the serverless function instances on a given cluster.
+
+### Prometheus support
+
+For the Knative function invocations to appear,
+[Prometheus must be installed](../index.md#installing-applications).
+
+Once Prometheus is installed, a message may appear indicating that the metrics data _is
+loading or is not available at this time._ It will appear upon the first access of the
+page, but should go away after a few seconds. If the message does not disappear, then it
+is possible that GitLab is unable to connect to the Prometheus instance running on the
+cluster.
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
new file mode 100644
index 00000000000..ae04616943f
--- /dev/null
+++ b/doc/user/project/code_owners.md
@@ -0,0 +1,85 @@
+# Code Owners **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6916)
+in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
+
+You can use a `CODEOWNERS` file to specify users that are responsible
+for certain files in a repository.
+
+You can choose and add the `CODEOWNERS` file in three places:
+
+- to the root directory of the repository
+- inside the `.gitlab/` directory
+- inside the `docs/` directory
+
+The `CODEOWNERS` file is scoped to a branch, which means that with the
+introduction of new files, the person adding the new content can
+specify themselves as a code owner, all before the new changes
+get merged to the default branch.
+
+When a file matches multiple entries in the `CODEOWNERS` file,
+the users from all entries are displayed on the blob page of
+the given file.
+
+## The syntax of Code Owners files
+
+Files can be specified using the same kind of patterns you would use
+in the `.gitignore` file followed by the `@username` or email of one
+or more users that should be owners of the file.
+
+The order in which the paths are defined is significant: the last
+pattern that matches a given path will be used to find the code
+owners.
+
+Starting a line with a `#` indicates a comment. This needs to be
+escaped using `\#` to address files for which the name starts with a
+`#`.
+
+Example `CODEOWNERS` file:
+
+```
+# This is an example code owners file, lines starting with a `#` will
+# be ignored.
+
+# app/ @commented-rule
+
+# We can specify a default match using wildcards:
+* @default-codeowner
+
+# Rules defined later in the file take precedence over the rules
+# defined before.
+# This will match all files for which the file name ends in `.rb`
+*.rb @ruby-owner
+
+# Files with a `#` can still be accesssed by escaping the pound sign
+\#file_with_pound.rb @owner-file-with-pound
+
+# Multiple codeowners can be specified, separated by whitespace
+CODEOWNERS @multiple @owners @tab-separated
+
+# Both usernames or email addresses can be used to match
+# users. Everything else will be ignored. For example this will
+# specify `@legal` and a user with email `janedoe@gitlab.com` as the
+# owner for the LICENSE file
+LICENSE @legal this_does_not_match janedoe@gitlab.com
+
+# Ending a path in a `/` will specify the code owners for every file
+# nested in that directory, on any level
+/docs/ @all-docs
+
+# Ending a path in `/*` will specify code owners for every file in
+# that directory, but not nested deeper. This will match
+# `docs/index.md` but not `docs/projects/index.md`
+/docs/* @root-docs
+
+# This will make a `lib` directory nested anywhere in the repository
+# match
+lib/ @lib-owner
+
+# This will only match a `config` directory in the root of the
+# repository
+/config/ @config-owner
+
+# If the path contains spaces, these need to be escaped like this:
+path\ with\ spaces/ @space-owner
+```
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index dec6eac2508..58b7fe33906 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -1,7 +1,8 @@
# GitLab Container Registry
> **Notes:**
-> [Introduced][ce-4040] in GitLab 8.8.
+>
+> - [Introduced][ce-4040] in GitLab 8.8.
> - Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
> versions earlier than 1.10.
> - This document is about the user guide. To learn how to enable GitLab Container
@@ -10,7 +11,7 @@
> - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
> login to GitLab's Container Registry.
-> - Multiple level image names support was added in GitLab 9.1
+> - Multiple level image names support was added in GitLab 9.1.
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
@@ -41,6 +42,7 @@ to enable it.
## Build and push images
> **Notes:**
+>
> - Moving or renaming existing container registry repositories is not supported
> once you have pushed images because the images are signed, and the
> signature includes the repository name.
@@ -51,7 +53,7 @@ If you visit the **Registry** link under your project's menu, you can see the
explicit instructions to login to the Container Registry using your GitLab
credentials.
-For example if the Registry's URL is `registry.example.com`, the you should be
+For example if the Registry's URL is `registry.example.com`, then you should be
able to login with:
```
@@ -202,7 +204,7 @@ at the communication between the client and the Registry.
The REST API between the Docker client and Registry is [described
here](https://docs.docker.com/registry/spec/api/). Normally, one would just
use Wireshark or tcpdump to capture the traffic and see where things went
-wrong. However, since all communication between Docker clients and servers
+wrong. However, since all communications between Docker clients and servers
are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even
if you know the private key. What can we do instead?
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 05127b274e0..19ab911e39f 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -3,45 +3,40 @@
> [Introduced][ce-5986] in GitLab 8.12. Further features were added in GitLab
8.14.
-Cycle Analytics measures the time it takes to go from an [idea to production] for
-each project you have. This is achieved by not only indicating the total time it
-takes to reach that point, but the total time is broken down into the
-multiple stages an idea has to pass through to be shipped.
+Cycle Analytics measures the time spent to go from an [idea to production] for
+each of your projects. Cycle Analytics displays the median time for an idea to
+reach production, along with the time typically spent in each DevOps stage along the way.
+
+Cycle Analytics is useful in order to quickly determine the velocity of a given
+project. It points to bottlenecks in the development process, enabling management
+to uncover, triage, and root-cause slowdowns in the software development life cycle.
Cycle Analytics is tightly coupled with the [GitLab flow] and
calculates a separate median for each stage.
## Overview
-You can find the Cycle Analytics page under your project's **Pipelines âž” Cycle
+You can find the Cycle Analytics page under your project's **Project âž” Cycle
Analytics** tab.
![Cycle Analytics landing page](img/cycle_analytics_landing_page.png)
-You can see that there are seven stages in total:
+There are seven stages that are tracked as part of the Cycle Analytics calculations.
- **Issue** (Tracker)
- - Median time from issue creation until given a milestone or list label
- (first assignment, any milestone, milestone date or assignee is not required)
+ - Time to schedule an issue (by milestone or by adding it to an issue board)
- **Plan** (Board)
- - Median time from giving an issue a milestone or label until pushing the
- first commit to the branch
+ - Time to first commit
- **Code** (IDE)
- - Median time from the first commit to the branch until the merge request is
- created
+ - Time to create a merge request
- **Test** (CI)
- - Median total test time for all commits/merges
+ - Time it takes GitLab CI/CD to test your code
- **Review** (Merge Request/MR)
- - Median time from merge request creation until the merge request is merged
- (closed merge requests won't be taken into account)
+ - Time spent on code review
- **Staging** (Continuous Deployment)
- - Median time from when the merge request got merged until the deploy to
- production (production is last stage/environment)
+ - Time between merging and deploying to production
- **Production** (Total)
- - Sum of all the above stages' times excluding the Test (CI) time. To clarify,
- it's not so much that CI time is "excluded", but rather CI time is already
- counted in the review stage since CI is done automatically. Most of the
- other stages are purely sequential, but **Test** is not.
+ - Total lifecycle time; i.e. the velocity of the project or team
## How the data is measured
diff --git a/doc/user/project/deploy_boards.md b/doc/user/project/deploy_boards.md
new file mode 100644
index 00000000000..2f2a9c5eec3
--- /dev/null
+++ b/doc/user/project/deploy_boards.md
@@ -0,0 +1,132 @@
+# Deploy Boards **[PREMIUM]**
+
+> [Introduced][ee-1589] in [GitLab Premium][ee] 9.0.
+
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment] running on [Kubernetes], displaying the status
+of the pods in the deployment. Developers and other teammates can view the
+progress and status of a rollout, pod by pod, in the workflow they already use
+without any need to access Kubernetes.
+
+## Overview
+
+With Deploy Boards you can gain more insight into deploys with benefits such as:
+
+- Following a deploy from the start, not just when it's done
+- Watching the rollout of a build across multiple servers
+- Finer state detail (Waiting, Deploying, Finished, Unknown)
+- See [Canary Deployments](canary_deployments.md)
+
+Here's an example of a Deploy Board of the production environment.
+
+![Deploy Boards landing page](img/deploy_boards_landing_page.png)
+
+The squares represent pods in your Kubernetes cluster that are associated with
+the given environment. Hovering above each square you can see the state of a
+deploy rolling out. The percentage is the percent of the pods that are updated
+to the latest release.
+
+Since Deploy Boards are tightly coupled with Kubernetes, there is some required
+knowledge. In particular you should be familiar with:
+
+- [Kubernetes pods](https://kubernetes.io/docs/user-guide/pods)
+- [Kubernetes labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
+- [Kubernetes namespaces](https://kubernetes.io/docs/user-guide/namespaces/)
+- [Kubernetes canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+
+## Use cases
+
+Since the Deploy Board is a visual representation of the Kubernetes pods for a
+specific environment, there are lot of uses cases. To name a few:
+
+- You want to promote what's running in staging, to production. You go to the
+ environments list, verify that what's running in staging is what you think is
+ running, then click on the [manual action](../../ci/yaml/README.md#whenmanual) to deploy to production.
+- You trigger a deploy, and you've got lots of containers to upgrade so you know
+ it'll take a while (you've also throttled your deploy to only take down X
+ containers at a time). But you need to tell someone when it's deployed, so you
+ go to the environments list, look at the production environment to see what
+ the progress is in real-time as each pod is rolled.
+- You get a report that something is weird in production, so you look at the
+ production environment to see what is running, and if a deploy is ongoing or
+ stuck or failed.
+- You've got an MR that looks good, but you want to run it on staging because
+ staging is set up in some way closer to production. You go to the environment
+ list, find the [Review App][review apps] you're interested in, and click the
+ manual action to deploy it to staging.
+
+## Enabling Deploy Boards
+
+To display the Deploy Boards for a specific [environment] you should:
+
+1. Have a Kubernetes cluster up and running.
+
+ NOTE: **Running on OpenShift:**
+ If you are using OpenShift, ensure that you're using the `Deployment` resource
+ instead of `DeploymentConfiguration`, otherwise the Deploy Boards won't render
+ correctly. For more information, read the
+ [OpenShift docs](https://docs.openshift.com/container-platform/3.7/dev_guide/deployments/kubernetes_deployments.html#kubernetes-deployments-vs-deployment-configurations)
+ and [GitLab issue #4584](https://gitlab.com/gitlab-org/gitlab-ee/issues/4584).
+
+1. [Configure GitLab Runner][runners] with the [Docker][docker-exec] or
+ [Kubernetes][kube-exec] executor.
+1. Configure the [Kubernetes service][kube-service] in your project for the
+ cluster. The Kubernetes namespace is of particular note as you will need it
+ for your deployment scripts (exposed by the `KUBE_NAMESPACE` env variable).
+1. Ensure Kubernetes annotations of `app.gitlab.com/env: $CI_ENVIRONMENT_SLUG`
+ and `app.gitlab.com/app: $CI_PROJECT_PATH_SLUG` are applied to the
+ deployments, replica sets, and pods, where `$CI_ENVIRONMENT_SLUG` and
+ `$CI_PROJECT_PATH_SLUG` are the values of the CI variables. This is so we can
+ lookup the proper environment in a cluster/namespace which may have more
+ than one. These resources should be contained in the namespace defined in
+ the Kubernetes service setting. You can use an [Autodeploy] `.gitlab-ci.yml`
+ template which has predefined stages and commands to use, and automatically
+ applies the annotations. Each project will need to have a unique namespace in
+ Kubernetes as well. The image below demonstrates how this is shown inside
+ Kubernetes.
+
+ NOTE: **Note:**
+ The Kubernetes label of `app` is deprecated and may be removed in next major
+ release, GitLab 12.0.
+
+ ![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png)
+
+Once all of the above are set up and the pipeline has run at least once,
+navigate to the environments page under **Operations > Environments**.
+
+Deploy Boards are visible by default. You can explicitly click
+the triangle next to their respective environment name in order to hide them.
+GitLab will then query Kubernetes for the state of each pod (e.g., waiting,
+deploying, finished, unknown), and the Deploy Board status will finally appear.
+
+GitLab will only display a Deploy Board for top-level environments. Foldered
+environments like `review/*` (usually used for [Review Apps]) won't have a
+Deploy Board attached to them.
+
+## Canary Deployments
+
+A popular CI strategy, where a small portion of the fleet is updated to the new
+version of your application.
+
+[Read more about Canary Deployments.](canary_deployments.md)
+
+## Further reading
+
+- [GitLab Autodeploy][autodeploy]
+- [GitLab CI environment variables][variables]
+- [Environments and deployments][environment]
+- [Kubernetes deploy example][kube-deploy]
+
+[ee-1589]: https://gitlab.com/gitlab-org/gitlab-ee/issues/1589 "Deploy Boards initial issue"
+[ee]: https://about.gitlab.com/pricing/ "GitLab Enterprise Edition landing page"
+[kube-deploy]: https://gitlab.com/gitlab-examples/kubernetes-deploy "Kubernetes deploy example project"
+[kubernetes]: https://kubernetes.io "Kubernetes website"
+[environment]: ../../ci/environments.md "Environments and deployments documentation"
+[docker-exec]: https://docs.gitlab.com/runner/executors/docker.html "GitLab Runner Docker executor"
+[kube-exec]: https://docs.gitlab.com/runner/executors/kubernetes.html "GitLab Runner Kubernetes executor"
+[kube-service]: integrations/kubernetes.md "Kubernetes project service"
+[review apps]: ../../ci/review_apps/index.md "Review Apps documentation"
+[variables]: ../../ci/variables/README.md "GitLab CI variables"
+[autodeploy]: ../../ci/autodeploy/index.md "GitLab Autodeploy"
+[kube-image]: https://gitlab.com/gitlab-examples/kubernetes-deploy/container_registry "Kubernetes deploy Container Registry"
+[runners]: ../../ci/runners/README.md
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 7688508c6ac..92a29b68a22 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -5,7 +5,7 @@
Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password.
Please note, that the expiration of deploy tokens happens on the date you define,
-at midnight UTC and that they can be only managed by [maintainers](https://docs.gitlab.com/ee/user/permissions.html).
+at midnight UTC and that they can be only managed by [maintainers](../../permissions.md).
## Creating a Deploy Token
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 08647caaf07..05ad15476ab 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -2,8 +2,12 @@
>[Introduced][ce-4981] in GitLab 8.11.
+We all know that a properly submitted issue is more likely to be addressed in
+a timely manner by the developers of a project.
+
Description templates allow you to define context-specific templates for issue
-and merge request description fields for your project.
+and merge request description fields for your project, as well as help filter
+out a lot of unnecessary noise from issues.
## Overview
@@ -18,6 +22,18 @@ Description templates must be written in [Markdown](../markdown.md) and stored
in your project's repository under a directory named `.gitlab`. Only the
templates of the default branch will be taken into account.
+## Use-cases
+
+- Add a template to be used in every issue for a specific project,
+giving instructions and guidelines, requiring for information specific to that subject.
+For example, if you have a project for tracking new blog posts, you can require the
+title, outlines, author name, author social media information, etc.
+- Following the previous example, you can make a template for every MR submitted
+with a new blog post, requiring information about the post date, frontmatter data,
+images guidelines, link to the related issue, reviewer name, etc.
+- You can also create issues and merge request templates for different
+stages of your workflow, e.g., feature proposal, feature improvement, bug report, etc.
+
## Creating issue templates
Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/`
@@ -39,12 +55,38 @@ changes you made after picking the template and return it to its initial status.
![Description templates](img/description_templates.png)
+## Setting a default template for issues and merge requests **[STARTER]**
+
+> **Notes:**
+> - This feature was introduced before [description templates](#overview) and is
+> available in [GitLab Starter][products]. It can be enabled
+> in the project's settings.
+> - Templates for issues were [introduced][ee-28] in GitLab EE 8.1.
+> - Templates for merge requests were [introduced][ee-7478ece] in GitLab EE 6.9.
+
+The visibility of issues and/or merge requests should be set to either "Everyone
+with access" or "Only team members" in your project's **Settings** otherwise the
+template text areas won't show. This is the default behavior so in most cases
+you should be fine.
+
+Go to your project's **Settings** and fill in the "Default description template
+for issues" and "Default description template for merge requests" text areas
+for issues and merge requests respectively. Since GitLab issues and merge
+request support [Markdown](../markdown.md), you can use special markup like
+headings, lists, etc.
+
+![Default description templates](img/description_templates_default_settings.png)
+
+After you add the description, hit **Save changes** for the settings to take
+effect. Now, every time a new issue or merge request is created, it will be
+pre-filled with the text you entered in the template(s).
+
## Description template example
We make use of Description Templates for Issues and Merge Requests within the GitLab Community Edition project. Please refer to the [`.gitlab` folder][gitlab-ce-templates] for some examples.
> **Tip:**
-It is possible to use [quick actions](./quick_actions.md) within description templates to quickly add labels, assignees, and milestones. The quick actions will only be executed if the user submitting the Issue or Merge Request has the permissions perform the relevant actions.
+It is possible to use [quick actions](quick_actions.md) within description templates to quickly add labels, assignees, and milestones. The quick actions will only be executed if the user submitting the Issue or Merge Request has the permissions perform the relevant actions.
Here is an example for a Bug report template:
@@ -93,3 +135,6 @@ Possible fixes
[ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981
[gitlab-ce-templates]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/.gitlab
+[ee-28]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/28 "Merge Request for adding issues template"
+[ee-7478ece]: https://gitlab.com/gitlab-org/gitlab-ee/commit/7478ece8b48e80782b5465b96c79f85cc91d391b "Commit that introduced merge requests templates"
+[products]: https://about.gitlab.com/pricing/
diff --git a/doc/user/project/file_lock.md b/doc/user/project/file_lock.md
new file mode 100644
index 00000000000..3386eb9d0d4
--- /dev/null
+++ b/doc/user/project/file_lock.md
@@ -0,0 +1,101 @@
+# File Locking **[PREMIUM]**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/440) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.9.
+> - This feature needs to have a license with the "File Lock" option enabled.
+> - If you are using Premium but you don't see the "Lock" button, ask your GitLab administrator.
+
+File Locking helps you avoid merge conflicts and better manage your binary files.
+Lock any file or directory, make your changes, and then unlock it so another
+member of the team can edit it.
+
+## Overview
+
+Working with multiple people on the same file can be a risk. Conflicts
+when merging a non-text file are hard to overcome and will require a lot
+of manual work to resolve. With GitLab Premium, File
+Locking helps you avoid merge conflicts and better manage your binary
+files by preventing everyone, except you, from modifying a specific file
+or entire directory.
+
+## Use-cases
+
+The file locking feature is useful in situations when:
+
+- Multiple people are working on the same file and you want to avoid merge
+ conflicts.
+- Your repository contains binary files in which situation there is no easy
+ way to tell the diff between yours and your colleagues' changes.
+- Prevent design assets from being overwritten.
+
+Locked directories are locked recursively, which means that everything that
+lies under them is also locked.
+
+## Permissions on file locking
+
+The user that locks a file or directory **is the only one** that can edit and
+push their changes back to the repository where the locked objects are located.
+
+Locks can be created by any person who has [push access](../../user/permissions.md) to the repository; i.e.,
+Developer and higher level, and can be removed solely by their author and any
+user with Maintainer permissions and above.
+
+If a file is locked and you are not the author of its locked state, a
+pre-receive hook will reject your changes when you try to push. In the
+following example, a user who has no permissions on the locked `.gitignore`
+file will see the message below:
+
+```bash
+Counting objects: 3, done.
+Delta compression using up to 4 threads.
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (3/3), 320 bytes | 0 bytes/s, done.
+Total 3 (delta 1), reused 0 (delta 0)
+remote: GitLab: The path '.gitignore' is locked by Administrator
+To https://example.com/gitlab-org/gitlab-ce.git
+ ! [remote rejected] master -> master (pre-receive hook declined)
+ error: failed to push some refs to 'https://example.com/gitlab-org/gitlab-ce.git'
+```
+
+Similarly, when a user that is not the author of the locked state of a file
+accepts a merge request, an error message will appear stating that the file
+is locked.
+
+![Merge request error message](img/file_lock_merge_request_error_message.png)
+
+## Locking a file or a directory
+
+>**Note:**
+Locking only works for the default branch you have set in the project's settings
+(usually `master`).
+
+To lock a file, navigate to the repository tree under the **Repository > Files** tab,
+pick the file you want to lock and hit the "Lock" button.
+
+![Locking file](img/file_lock.png)
+
+---
+
+To lock an entire directory, look for the "Lock" link next to "History".
+
+![Locking directory](img/file_lock_folders.png)
+
+---
+
+After you lock a file or directory, it will appear as locked in the repository
+view.
+
+![Repository view](img/file_lock_repository_view.png)
+
+## Unlocking a file or a directory
+
+To unlock a file or a directory, follow the same procedure as when you locked
+them. For a detailed view of every existing lock, see the next section on
+"Viewing and managing existing locks".
+
+## Viewing and managing existing locks
+
+To view or manage every existing lock, navigate to the
+**Project > Repository > Locked Files** area. There, you can view all existing
+locks and [remove the ones you have permission for](#permissions-on-file-locking).
+
+![Locked Files](img/file_lock_list.png)
diff --git a/doc/user/project/gpg_signed_commits/index.md b/doc/user/project/gpg_signed_commits/index.md
index 261eedeb412..bd9a5313489 100644
--- a/doc/user/project/gpg_signed_commits/index.md
+++ b/doc/user/project/gpg_signed_commits/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../repository/gpg_signed_commits/index.md'
+---
+
This document was moved to [another location](../repository/gpg_signed_commits/index.md).
diff --git a/doc/user/project/img/cycle_analytics_landing_page.png b/doc/user/project/img/cycle_analytics_landing_page.png
index 8b17fae5e05..cf46098b9a4 100644
--- a/doc/user/project/img/cycle_analytics_landing_page.png
+++ b/doc/user/project/img/cycle_analytics_landing_page.png
Binary files differ
diff --git a/doc/user/project/img/deploy_boards_canary_deployments.png b/doc/user/project/img/deploy_boards_canary_deployments.png
new file mode 100644
index 00000000000..037b4a908ab
--- /dev/null
+++ b/doc/user/project/img/deploy_boards_canary_deployments.png
Binary files differ
diff --git a/doc/user/project/img/deploy_boards_kubernetes_label.png b/doc/user/project/img/deploy_boards_kubernetes_label.png
new file mode 100644
index 00000000000..19785c74d07
--- /dev/null
+++ b/doc/user/project/img/deploy_boards_kubernetes_label.png
Binary files differ
diff --git a/doc/user/project/img/deploy_boards_landing_page.png b/doc/user/project/img/deploy_boards_landing_page.png
new file mode 100644
index 00000000000..c9621a06860
--- /dev/null
+++ b/doc/user/project/img/deploy_boards_landing_page.png
Binary files differ
diff --git a/doc/user/project/img/description_templates_default_settings.png b/doc/user/project/img/description_templates_default_settings.png
new file mode 100644
index 00000000000..ab314e83d06
--- /dev/null
+++ b/doc/user/project/img/description_templates_default_settings.png
Binary files differ
diff --git a/doc/user/project/img/file_lock.png b/doc/user/project/img/file_lock.png
new file mode 100644
index 00000000000..33f96f20e91
--- /dev/null
+++ b/doc/user/project/img/file_lock.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_folders.png b/doc/user/project/img/file_lock_folders.png
new file mode 100644
index 00000000000..5ff3d4d9dbd
--- /dev/null
+++ b/doc/user/project/img/file_lock_folders.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_list.png b/doc/user/project/img/file_lock_list.png
new file mode 100644
index 00000000000..2c276335c83
--- /dev/null
+++ b/doc/user/project/img/file_lock_list.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_merge_request_error_message.png b/doc/user/project/img/file_lock_merge_request_error_message.png
new file mode 100644
index 00000000000..65bc3692da0
--- /dev/null
+++ b/doc/user/project/img/file_lock_merge_request_error_message.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_repository_view.png b/doc/user/project/img/file_lock_repository_view.png
new file mode 100644
index 00000000000..8f4ef52aacd
--- /dev/null
+++ b/doc/user/project/img/file_lock_repository_view.png
Binary files differ
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
deleted file mode 100644
index b753593d212..00000000000
--- a/doc/user/project/img/issue_board.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png
index 242460925f7..4b1a8356dc9 100644
--- a/doc/user/project/img/issue_boards_multiple.png
+++ b/doc/user/project/img/issue_boards_multiple.png
Binary files differ
diff --git a/doc/user/project/img/key_value_labels.png b/doc/user/project/img/key_value_labels.png
new file mode 100644
index 00000000000..bec901f127f
--- /dev/null
+++ b/doc/user/project/img/key_value_labels.png
Binary files differ
diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png
index 7a7fab611a4..221c5cf55a2 100644
--- a/doc/user/project/img/labels_default.png
+++ b/doc/user/project/img/labels_default.png
Binary files differ
diff --git a/doc/user/project/img/labels_epic_sidebar.png b/doc/user/project/img/labels_epic_sidebar.png
new file mode 100644
index 00000000000..f8162d89e9d
--- /dev/null
+++ b/doc/user/project/img/labels_epic_sidebar.png
Binary files differ
diff --git a/doc/user/project/img/labels_list.png b/doc/user/project/img/labels_list.png
index 6878349fc0c..6940dde68bf 100644
--- a/doc/user/project/img/labels_list.png
+++ b/doc/user/project/img/labels_list.png
Binary files differ
diff --git a/doc/user/project/img/labels_sidebar_inline.png b/doc/user/project/img/labels_sidebar_inline.png
deleted file mode 100644
index 2186f14ea92..00000000000
--- a/doc/user/project/img/labels_sidebar_inline.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/priority_sort_order.png b/doc/user/project/img/priority_sort_order.png
deleted file mode 100644
index cd1dd8237c0..00000000000
--- a/doc/user/project/img/priority_sort_order.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_choose_branch.png b/doc/user/project/img/protected_branches_choose_branch.png
deleted file mode 100644
index c2848db9c96..00000000000
--- a/doc/user/project/img/protected_branches_choose_branch.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_error_ui.png b/doc/user/project/img/protected_branches_error_ui.png
deleted file mode 100644
index 62839e49d89..00000000000
--- a/doc/user/project/img/protected_branches_error_ui.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_select_roles_and_users.png b/doc/user/project/img/protected_branches_select_roles_and_users.png
new file mode 100644
index 00000000000..4f62b057cc7
--- /dev/null
+++ b/doc/user/project/img/protected_branches_select_roles_and_users.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_select_roles_and_users_list.png b/doc/user/project/img/protected_branches_select_roles_and_users_list.png
new file mode 100644
index 00000000000..519f2267496
--- /dev/null
+++ b/doc/user/project/img/protected_branches_select_roles_and_users_list.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_confirmation_email.png b/doc/user/project/img/service_desk_confirmation_email.png
new file mode 100644
index 00000000000..e08dced1e38
--- /dev/null
+++ b/doc/user/project/img/service_desk_confirmation_email.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_disabled.png b/doc/user/project/img/service_desk_disabled.png
new file mode 100644
index 00000000000..3ae64dcbe96
--- /dev/null
+++ b/doc/user/project/img/service_desk_disabled.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_enabled.png b/doc/user/project/img/service_desk_enabled.png
new file mode 100644
index 00000000000..329348e4b52
--- /dev/null
+++ b/doc/user/project/img/service_desk_enabled.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_issue_tracker.png b/doc/user/project/img/service_desk_issue_tracker.png
new file mode 100644
index 00000000000..485751fab0a
--- /dev/null
+++ b/doc/user/project/img/service_desk_issue_tracker.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_nav_item.png b/doc/user/project/img/service_desk_nav_item.png
new file mode 100644
index 00000000000..3420e355f67
--- /dev/null
+++ b/doc/user/project/img/service_desk_nav_item.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_reply.png b/doc/user/project/img/service_desk_reply.png
new file mode 100644
index 00000000000..ae6ce31713b
--- /dev/null
+++ b/doc/user/project/img/service_desk_reply.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_thread.png b/doc/user/project/img/service_desk_thread.png
new file mode 100644
index 00000000000..8d8ac39cc5b
--- /dev/null
+++ b/doc/user/project/img/service_desk_thread.png
Binary files differ
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
new file mode 100644
index 00000000000..7f79ebf6353
--- /dev/null
+++ b/doc/user/project/import/gemnasium.md
@@ -0,0 +1,102 @@
+# Gemnasium **[ULTIMATE]**
+
+This guide describes how to migrate from Gemnasium.com to your own GitLab
+instance or GitLab.com.
+
+## Why is Gemnasium.com closed?
+
+Gemnasium has been [acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html)
+in January 2018. Since May 15, 2018, the services provided by Gemnasium are no longer available.
+The team behind Gemnasium has joined GitLab as the new Security Products team
+and is working on a wider range of tools than just Dependency Scanning:
+[SAST](../../application_security/sast/index.md),
+[DAST](../../application_security/dast/index.md),
+[Container Scanning](../../application_security/container_scanning/index.md) and more.
+If you want to continue monitoring your dependencies, see the
+[Migrating to GitLab](#migrating-to-gitlab) section below.
+
+## What happened to my account?
+
+Your account has been automatically closed on May 15th, 2018. If you had a paid
+subscription at that time, your card will be refunded on a pro rata temporis basis.
+You may contact `gemnasium@gitlab.com` regarding your closed account.
+
+## Will my account/data be transferred to GitLab Inc.?
+
+All accounts and data have been deleted on May 15th, 2018. GitLab Inc.
+doesn't know anything about your private data, nor your projects, and therefore
+if they were vulnerable or not. GitLab Inc. takes personal information very seriously.
+
+## What happened to my badge?
+
+To avoid broken 404 images, all badges pointing to Gemnasium.com will be a
+placeholder, inviting you to migrate to GitLab (and pointing to this page).
+
+## Migrating to GitLab
+
+Gemnasium has been ported and integrated directly into GitLab CI/CD.
+You can still benefit from our dependency monitoring features, and it requires
+some steps to migrate your projects. There is no automatic import since GitLab
+doesn't know anything about any projects which existed on Gemnasium.com.
+Security features are free for public (open-source) projects hosted on GitLab.com.
+
+### If your project is hosted on GitLab (https://gitlab.com / self-hosted)
+
+You're almost set! If you're already using
+[Auto DevOps](../../../topics/autodevops/), you are already covered.
+Otherwise, you must configure your `.gitlab-ci.yml` according to the
+[dependency scanning page](../../application_security/dependency_scanning/index.md).
+
+### If your project is hosted on GitHub (https://github.com / GitHub Enterprise)
+
+Since [GitLab 10.6 comes with GitHub integration](https://about.gitlab.com/features/github/),
+GitLab users can now create a CI/CD project in GitLab connected to an external
+GitHub.com or GitHub Enterprise repository. This will automatically prompt
+GitLab CI/CD to run whenever code is pushed to GitHub and post CI/CD results
+back to both GitLab and GitHub when completed.
+
+1. Create a new project, and select the "CI/CD for external repo" tab:
+
+ ![Create new Project](img/gemnasium/create_project.png)
+
+1. Use the "GitHub" button to connect your repositories.
+
+ ![Connect from GitHub](img/gemnasium/connect_github.png)
+
+1. Select the project(s) to be set up with GitLab CI/CD and chose "Connect".
+
+ ![Select projects](img/gemnasium/select_project.png)
+
+ Once the configuration is done, you may click on your new
+ project on GitLab.
+
+ ![click on connected project](img/gemnasium/project_connected.png)
+
+ Your project is now mirrored on GitLab, where the Runners will be able to access
+ your source code and run your tests.
+
+ Optional step: If you set this up on GitLab.com, make sure the project is
+ public (in the project settings) if your GitHub project is public, since
+ the security feature is available only for [GitLab Ultimate](https://about.gitlab.com/pricing).
+
+1. To set up the dependency scanning job, corresponding to what Gemnasium was
+ doing, you must create a `.gitlab-ci.yml` file, or update it according to
+ the [dependency scanning docs](../../application_security/dependency_scanning/index.md).
+ The mirroring is pull-only by default, so you may create or update the file on
+ GitHub:
+
+ ![Edit gitlab-ci.yml file](img/gemnasium/edit_gitlab-ci.png)
+
+1. Once your file has been committed, a new pipeline will be automatically
+ triggered if your file is valid:
+
+ ![pipeline](img/gemnasium/pipeline.png)
+
+1. The result of the job will be visible directly from the pipeline view:
+
+ ![security report](img/gemnasium/report.png)
+
+NOTE: **Note:**
+If you don't commit very often to your project, you may want to use
+[scheduled pipelines](../pipelines/schedules.md) to run the job on a regular
+basis.
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 63b90dd76fd..8fba892594b 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -119,9 +119,9 @@ Depending your GitLab tier, [project mirroring](../../../workflow/repository_mir
your imported project in sync with its GitHub copy.
Additionally, you can configure GitLab to send pipeline status updates back GitHub with the
-[GitHub Project Integration](https://docs.gitlab.com/ee/user/project/integrations/github.html). **[PREMIUM]**
+[GitHub Project Integration](../integrations/github.md). **[PREMIUM]**
-If you import your project using [CI/CD for external repo](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/), then both
+If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/index.md), then both
of the above are automatically configured. **[PREMIUM]**
## Improving the speed of imports on self-hosted instances
diff --git a/doc/user/project/import/gitlab_com.md b/doc/user/project/import/gitlab_com.md
index 3b37da67a5b..f48a158e2d3 100644
--- a/doc/user/project/import/gitlab_com.md
+++ b/doc/user/project/import/gitlab_com.md
@@ -1,8 +1,9 @@
# Project importing from GitLab.com to your private GitLab instance
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
-GitLab support is enabled on your GitLab instance.
-You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
+GitLab.com integration is enabled on your GitLab instance.
+[Read more about GitLab.com integration for self-managed GitLab instances](../../../integration/gitlab.md).
+
To get to the importer page you need to go to "New project" page.
>**Note:**
diff --git a/doc/user/project/import/img/gemnasium/connect_github.png b/doc/user/project/import/img/gemnasium/connect_github.png
new file mode 100644
index 00000000000..5933f4d5d0a
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/connect_github.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/create_project.png b/doc/user/project/import/img/gemnasium/create_project.png
new file mode 100644
index 00000000000..6e00bf63405
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/create_project.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/edit_gitlab-ci.png b/doc/user/project/import/img/gemnasium/edit_gitlab-ci.png
new file mode 100644
index 00000000000..8336c803b83
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/edit_gitlab-ci.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/pipeline.png b/doc/user/project/import/img/gemnasium/pipeline.png
new file mode 100644
index 00000000000..ae4d5295ffa
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/pipeline.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/project_connected.png b/doc/user/project/import/img/gemnasium/project_connected.png
new file mode 100644
index 00000000000..8e7216c015b
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/project_connected.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/report.png b/doc/user/project/import/img/gemnasium/report.png
new file mode 100644
index 00000000000..5c4d58662c0
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/report.png
Binary files differ
diff --git a/doc/user/project/import/img/gemnasium/select_project.png b/doc/user/project/import/img/gemnasium/select_project.png
new file mode 100644
index 00000000000..588c5ca7ce1
--- /dev/null
+++ b/doc/user/project/import/img/gemnasium/select_project.png
Binary files differ
diff --git a/doc/user/project/import/img/import_projects_from_github_select_auth_method.png b/doc/user/project/import/img/import_projects_from_github_select_auth_method.png
deleted file mode 100644
index 90e6243aec0..00000000000
--- a/doc/user/project/import/img/import_projects_from_github_select_auth_method.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index ca02e4e9e96..2b6927bd780 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -13,18 +13,39 @@
1. [From TFS](tfs.md)
1. [From repo by URL](repo_by_url.md)
1. [By uploading a manifest file (AOSP)](manifest.md)
+1. [From Gemnasium](gemnasium.md)
+1. [From Phabricator](phabricator.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
repository is too large the import can timeout.
+There is also the option of [connecting your external repository to get CI/CD benefits](../../../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
+
## Migrating from self-hosted GitLab to GitLab.com
-You can copy your repos by changing the remote and pushing to the new server,
-but issues and merge requests can't be imported.
+If you only need to migrate git repos, you can [import each project by URL](repo_by_url.md), but issues and merge requests can't be imported.
If you want to retain all metadata like issues and merge requests, you can use
-the [import/export feature](../settings/import_export.md).
+the [import/export feature](../settings/import_export.md) to export projects from self-hosted GitLab and import those projects into GitLab.com.
+
+NOTE: **Note:**
+This approach assumes all users from the self-hosted instance have already been migrated.
+If the users haven't been migrated yet, the user conducting the import
+will take the place of all references to the missing user(s).
+
+If you need to migrate all data over, you can leverage our [api](../../../api/README.md) to migrate from self-hosted to GitLab.com.
+The order of assets to migrate from a self-hosted instance to GitLab is the following:
+
+1. [Users](../../../api/users.md)
+1. [Groups](../../../api/groups.md)
+1. [Projects](../../../api/projects.md)
+1. [Project variables](../../../api/project_level_variables.md)
+
+Keep in mind the limitations of the [import/export feature](../settings/import_export.md#exported-contents).
+
+You will still need to migrate your Container Registry over a series of
+Docker pulls and pushes and re-run any CI pipelines to retrieve any build artifacts.
## Migrating between two self-hosted GitLab instances
@@ -32,3 +53,6 @@ The best method for migrating a project from one GitLab instance to another,
perhaps from an old server to a new server for example, is to
[back up the project](../../../raketasks/backup_restore.md),
then restore it on the new server.
+
+In the event of merging two GitLab instances together (for example, both instances have existing data on them and one can't be wiped),
+refer to the instructions in [Migrating from self-hosted GitLab to GitLab.com](#migrating-from-self-hosted-gitlab-to-gitlabcom).
diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md
new file mode 100644
index 00000000000..5c624e3aff6
--- /dev/null
+++ b/doc/user/project/import/phabricator.md
@@ -0,0 +1,29 @@
+# Import Phabricator tasks into a GitLab project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60562) in
+GitLab 12.0.
+
+GitLab allows you to import all tasks from a Phabricator instance into
+GitLab issues. The import creates a single project with the
+repository disabled.
+
+Currently, only the following basic fields are imported:
+
+- Title
+- Description
+- State (open or closed)
+- Created at
+- Closed at
+
+## Enabling this feature
+
+While this feature is incomplete, a feature flag is required to enable it so that
+we can gain early feedback before releasing it for everyone. To enable it:
+
+1. Run the following command in a Rails console:
+
+ ```ruby
+ Feature.enable(:phabricator_import)
+ ```
+
+1. Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin area.
diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md
index a5923986292..4825b005a85 100644
--- a/doc/user/project/import/svn.md
+++ b/doc/user/project/import/svn.md
@@ -8,7 +8,7 @@ between the two, for more information consult your favorite search engine.
There are two approaches to SVN to Git migration:
-1. [Git/SVN Mirror](#smooth-migration-with-a-git-svn-mirror-using-subgit) which:
+1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which:
- Makes the GitLab repository to mirror the SVN project.
- Git and SVN repositories are kept in sync; you can use either one.
- Smoothens the migration process and allows to manage migration risks.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index c62de41c539..a24f525253d 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -8,16 +8,16 @@ Your projects can be [available](../../public_access/public_access.md)
publicly, internally, or privately, at your choice. GitLab does not limit
the number of private projects you create.
-## Project's features
+## Project features
When you create a project in GitLab, you'll have access to a large number of
[features](https://about.gitlab.com/features/):
-**Issues and merge requests:**
+**Repositories:**
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
+ - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Repositories](repository/index.md): Host your code in a fully
integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to
@@ -28,9 +28,16 @@ When you create a project in GitLab, you'll have access to a large number of
permission to create tags, and prevent accidental update or deletion
- [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
- [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
+- [Web IDE](web_ide/index.md)
+
+**Issues and merge requests:**
+
+- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
+ - [Issue Boards](issue_board.md): Organize and prioritize your workflow
+ - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
+ - [Merge Request Approvals](merge_requests/merge_request_approvals.md): Ask for approval before
implementing a change **[STARTER]**
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI
@@ -55,11 +62,11 @@ When you create a project in GitLab, you'll have access to a large number of
- [Auto Deploy](../../ci/autodeploy/index.md): Configure GitLab CI/CD
to automatically set up your app's deployment
- [Enable and disable GitLab CI](../../ci/enable_or_disable_ci.md)
- - [Pipelines](../../ci/pipelines.md#pipelines): Configure and visualize
+ - [Pipelines](../../ci/pipelines.md): Configure and visualize
your GitLab CI/CD pipelines from the UI
- [Scheduled Pipelines](pipelines/schedules.md): Schedule a pipeline
to start at a chosen time
- - [Pipeline Graphs](../../ci/pipelines.md#pipeline-graphs): View your
+ - [Pipeline Graphs](../../ci/pipelines.md#visualizing-pipelines): View your
entire pipeline from the UI
- [Job artifacts](pipelines/job_artifacts.md): Define,
browse, and download job artifacts
@@ -67,6 +74,8 @@ When you create a project in GitLab, you'll have access to a large number of
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
+ - [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in
+ different flavors by dynamically toggling certain functionality **[PREMIUM]**
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
@@ -75,14 +84,19 @@ When you create a project in GitLab, you'll have access to a large number of
- [Wiki](wiki/index.md): document your GitLab project in an integrated Wiki.
- [Snippets](../snippets.md): store, share and collaborate on code snippets.
- [Cycle Analytics](cycle_analytics.md): review your development lifecycle.
+- [Security Dashboard](security_dashboard.md): Security Dashboard. **[ULTIMATE]**
- [Syntax highlighting](highlighting.md): an alternative to customize
your code blocks, overriding GitLab's default choice of language.
- [Badges](badges.md): badges for the project overview.
- [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of
the source, build output, and other metadata or artifacts
associated with a released version of your code.
+- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **[PREMIUM]**
+- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **[PREMIUM]**
+- [Code owners](code_owners.md): specify code owners for certain files **[STARTER]**
+- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **[ULTIMATE]**
-### Project's integrations
+### Project integrations
[Integrate your project](integrations/index.md) with Jira, Mattermost,
Kubernetes, Slack, and a lot more.
@@ -116,7 +130,14 @@ Read through the documentation on [project settings](settings/index.md).
- [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)
-## Project's members
+## CI/CD for external repositories **[PREMIUM]**
+
+Instead of importing a repository directly to GitLab, you can connect your repository
+as a CI/CD project.
+
+Read through the documentation on [CI/CD for external repositories](../../ci/ci_cd_for_external_repos/index.md).
+
+## Project members
Learn how to [add members to your projects](members/index.md).
@@ -137,7 +158,7 @@ and Git push/pull redirects.
Depending on the situation, different things apply.
When [renaming a user](../profile/index.md#changing-your-username),
-[changing a group path](../group/index.md#changing-a-group-s-path) or [renaming a repository](settings/index.md#renaming-a-repository):
+[changing a group path](../group/index.md#changing-a-groups-path) or [renaming a repository](settings/index.md#renaming-a-repository):
- Existing web URLs for the namespace and anything under it (e.g., projects) will
redirect to the new URLs.
@@ -170,3 +191,23 @@ password <personal_access_token>
To quickly access a project from the GitLab UI using the project ID,
visit the `/projects/:id` URL in your browser or other tool accessing the project.
+
+## Project APIs
+
+There are numerous [APIs](../../api/README.md) to use with your projects:
+
+- [Badges](../../api/project_badges.md)
+- [Clusters](../../api/project_clusters.md)
+- [Discussions](../../api/discussions.md)
+- [General](../../api/projects.md)
+- [Import/export](../../api/project_import_export.md)
+- [Issue Board](../../api/boards.md)
+- [Labels](../../api/labels.md)
+- [Markdown](../../api/markdown.md)
+- [Merge Requests](../../api/merge_requests.md)
+- [Milestones](../../api/milestones.md)
+- [Services](../../api/services.md)
+- [Snippets](../../api/project_snippets.md)
+- [Templates](../../api/project_templates.md)
+- [Traffic](../../api/project_statistics.md)
+- [Variables](../../api/project_level_variables.md)
diff --git a/doc/user/project/insights/img/insights_example_bar_chart.png b/doc/user/project/insights/img/insights_example_bar_chart.png
new file mode 100644
index 00000000000..eb96eb4a90f
--- /dev/null
+++ b/doc/user/project/insights/img/insights_example_bar_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_bar_time_series_chart.png b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png
new file mode 100644
index 00000000000..28aa81939d8
--- /dev/null
+++ b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_line_chart.png b/doc/user/project/insights/img/insights_example_line_chart.png
new file mode 100644
index 00000000000..131dbedd35e
--- /dev/null
+++ b/doc/user/project/insights/img/insights_example_line_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_pie_chart.png b/doc/user/project/insights/img/insights_example_pie_chart.png
new file mode 100644
index 00000000000..518ed7338f9
--- /dev/null
+++ b/doc/user/project/insights/img/insights_example_pie_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_stacked_bar_chart.png b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png
new file mode 100644
index 00000000000..aafec4b394e
--- /dev/null
+++ b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_sidebar_link.png b/doc/user/project/insights/img/insights_sidebar_link.png
new file mode 100644
index 00000000000..aadb5745992
--- /dev/null
+++ b/doc/user/project/insights/img/insights_sidebar_link.png
Binary files differ
diff --git a/doc/user/project/insights/img/project_insights.png b/doc/user/project/insights/img/project_insights.png
new file mode 100644
index 00000000000..2d0292dda54
--- /dev/null
+++ b/doc/user/project/insights/img/project_insights.png
Binary files differ
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
new file mode 100644
index 00000000000..3344e560870
--- /dev/null
+++ b/doc/user/project/insights/index.md
@@ -0,0 +1,307 @@
+# Insights **[ULTIMATE]**
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
+
+CAUTION: **Beta:**
+Insights is considered beta, and is not ready for production use.
+Follow [gitlab-org/quality/team-tasks#137](https://gitlab.com/gitlab-org/quality/team-tasks/issues/137#general-availability)
+for updates.
+
+Configure the Insights that matter for your projects to explore data such as
+triage hygiene, issues created/closed per a given period, average time for merge
+requests to be merged and much more.
+
+![Insights example bar chart](img/project_insights.png)
+
+NOTE: **Note:**
+This feature is [also available at the group level](../../group/insights/index.md).
+
+## View your project's Insights
+
+You can access your project's Insights by clicking the **Project > Insights**
+link in the left sidebar:
+
+![Insights sidebar link](img/insights_sidebar_link.png)
+
+## Configure your Insights
+
+Insights are configured using a YAML file called `.gitlab/insights.yml` within
+a project. That file will then be used in the project's Insights page.
+
+See [Writing your `.gitlab/insights.yml`](#writing-your-gitlabinsightsyml) below
+for details about the content of this file.
+
+NOTE: **Note:**
+Once the configuration file is created, you can also
+[use it for your project's group](../../group/insights/index.md#configure-your-insights).
+
+NOTE: **Note:**
+If the project doesn't have any configuration file, it'll try to use
+the group configuration if possible. If the group doesn't have any
+configuration, the default configuration will be used.
+
+## Permissions
+
+If you have access to view a project, then you have access to view their
+Insights.
+
+NOTE: **Note:**
+Issues or merge requests that you don't have access to (because you don't have
+access to the project they belong to, or because they are confidential) are
+filtered out of the Insights charts.
+
+You may also consult the [group permissions table](../../permissions.md#group-members-permissions).
+
+## Writing your `.gitlab/insights.yml`
+
+The `.gitlab/insights.yml` file defines the structure and order of the Insights
+charts that will be displayed in each Insights page of your project or group.
+
+Each page has a unique key and a collection of charts to fetch and display.
+
+For example, here's a single definition for Insights that will display one page with one chart:
+
+```yaml
+bugsCharts:
+ title: 'Charts for Bugs'
+ charts:
+ - title: Monthly Bugs Created (bar)
+ type: bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ group_by: month
+ period_limit: 24
+```
+
+Each chart definition is made up of a hash composed of key-value pairs.
+
+For example, here's single chart definition:
+
+```yaml
+- title: Monthly Bugs Created (bar)
+ type: bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ group_by: month
+ period_limit: 24
+```
+
+## Configuration parameters
+
+A chart is defined as a list of parameters that define the chart's behavior.
+
+The following table lists available parameters for charts:
+
+| Keyword | Description |
+|:---------------------------------------------------|:------------|
+| [`title`](#title) | The title of the chart. This will displayed on the Insights page. |
+| [`type`](#type) | The type of chart: `bar`, `line`, `stacked-bar`, `pie` etc. |
+| [`query`](#query) | A hash that defines the conditions for issues / merge requests to be part of the chart. |
+
+## Parameter details
+
+The following are detailed explanations for parameters used to configure
+Insights charts.
+
+### `title`
+
+`title` is the title of the chart as it will be displayed on the Insights page.
+For example:
+
+```yaml
+monthlyBugsCreated:
+ title: Monthly Bugs Created (bar)
+```
+
+### `type`
+
+`type` is the chart type.
+
+For example:
+
+```yaml
+monthlyBugsCreated:
+ title: Monthly Bugs Created (bar)
+ type: bar
+```
+
+Supported values are:
+
+| Name | Example |
+| ----- | ------- |
+| `bar` | ![Insights example bar chart](img/insights_example_bar_chart.png) |
+| `bar` (time series, i.e. when `group_by` is used) | ![Insights example bar time series chart](img/insights_example_bar_time_series_chart.png) |
+| `pie` | ![Insights example pie chart](img/insights_example_pie_chart.png) |
+| `line` | ![Insights example stacked bar chart](img/insights_example_line_chart.png) |
+| `stacked-bar` | ![Insights example stacked bar chart](img/insights_example_stacked_bar_chart.png) |
+
+### `query`
+
+`query` allows to define the conditions for issues / merge requests to be part
+of the chart.
+
+Example:
+
+```yaml
+monthlyBugsCreated:
+ title: Monthly Bugs Created (bar)
+ type: bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ collection_labels:
+ - S1
+ - S2
+ - S3
+ - S4
+ group_by: week
+ period_limit: 104
+```
+
+#### `query.issuable_type`
+
+Defines the type of "issuable" you want to create a chart for.
+
+Supported values are:
+
+- `issue`: The chart will display issues' data.
+- `merge_request`: The chart will display merge requests' data.
+
+#### `query.issuable_state`
+
+Filter by the state of the queried "issuable".
+
+If you omit it, the `opened` state filter will be applied.
+
+Supported values are:
+
+- `opened`: Open issues / merge requests.
+- `closed`: Closed Open issues / merge requests.
+- `locked`: Issues / merge requests that have their discussion locked.
+- `merged`: Merged merge requests.
+- `all`: Issues / merge requests in all states
+
+#### `query.filter_labels`
+
+Filter by labels applied to the queried "issuable".
+
+If you omit it, no labels filter will be applied. All the defined labels must be
+applied to the "issuable" in order for it to be selected.
+
+Example:
+
+```yaml
+monthlyBugsCreated:
+ title: Monthly regressions Created (bar)
+ type: bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ - regression
+```
+
+#### `query.collection_labels`
+
+Group "issuable" by the configured labels.
+
+If you omit it, no grouping will be done. When using this keyword, you need to
+set `type` to either `line` or `stacked-bar`.
+
+Example:
+
+```yaml
+weeklyBugsBySeverity:
+ title: Weekly Bugs By Severity (stacked bar)
+ type: stacked-bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ collection_labels:
+ - S1
+ - S2
+ - S3
+ - S4
+```
+
+#### `query.group_by`
+
+Define the X-axis of your chart.
+
+Supported values are:
+
+- `day`: Group data per day.
+- `week`: Group data per week.
+- `month`: Group data per month.
+
+#### `query.period_limit`
+
+Define how far "issuables" are queried in the past.
+
+The unit is related to the `query.group_by` you defined. For instance if you
+defined `query.group_by: 'day'` then `query.period_limit: 365` would mean
+"Gather and display data for the last 365 days".
+
+If you omit it, default values will be applied depending on the `query.group_by`
+you defined.
+
+| `query.group_by` | Default value |
+| ---------------- | ------------- |
+| `day` | 30 |
+| `week` | 4 |
+| `month` | 12 |
+
+## Complete example
+
+```yaml
+bugsCharts:
+ title: 'Charts for Bugs'
+ charts:
+ - title: Monthly Bugs Created (bar)
+ type: bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ group_by: month
+ period_limit: 24
+ - title: Weekly Bugs By Severity (stacked bar)
+ type: stacked-bar
+ query:
+ issuable_type: issue
+ issuable_state: opened
+ filter_labels:
+ - bug
+ collection_labels:
+ - S1
+ - S2
+ - S3
+ - S4
+ group_by: week
+ period_limit: 104
+ - title: Monthly Bugs By Team (line)
+ type: line
+ query:
+ issuable_type: merge_request
+ issuable_state: opened
+ filter_labels:
+ - bug
+ collection_labels:
+ - Manage
+ - Plan
+ - Create
+ group_by: month
+ period_limit: 24
+```
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
index 6fc083170b6..23f1ce7a15a 100644
--- a/doc/user/project/integrations/custom_issue_tracker.md
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -7,9 +7,9 @@ in the table below.
| Field | Description |
| ----- | ----------- |
-| `title` | A title for the issue tracker (to differentiate between instances, for example) |
+| `title` | A title for the issue tracker (to differentiate between instances, for example). |
| `description` | A name for the issue tracker (to differentiate between instances, for example) |
-| `project_url` | Currently unused. Will be changed in a future release. |
+| `project_url` | The URL to the project in the custom issue tracker. |
| `issues_url` | The URL to the issue in the issue tracker project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. For example, `https://customissuetracker.com/project-name/:id`. |
| `new_issue_url` | Currently unused. Will be changed in a future release. |
diff --git a/doc/user/project/integrations/github.md b/doc/user/project/integrations/github.md
new file mode 100644
index 00000000000..cdb0e34fdf6
--- /dev/null
+++ b/doc/user/project/integrations/github.md
@@ -0,0 +1,48 @@
+# GitHub project integration **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3836) in GitLab Premium 10.6.
+
+GitLab provides an integration for updating the pipeline statuses on GitHub.
+This is especially useful if using GitLab for CI/CD only.
+
+This project integration is separate from the [instance wide GitHub integration](../import/github.md#mirroring-and-pipeline-status-sharing)
+and is automatically configured on [GitHub import](../../../integration/github.md).
+
+![Pipeline status update on GitHub](img/github_status_check_pipeline_update.png)
+
+## Configuration
+
+### Complete these steps on GitHub
+
+This integration requires a [GitHub API token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)
+with `repo:status` access granted:
+
+1. Go to your "Personal access tokens" page at https://github.com/settings/tokens
+1. Click "Generate New Token"
+1. Ensure that `repo:status` is checked and click "Generate token"
+1. Copy the generated token to use on GitLab
+
+### Complete these steps on GitLab
+
+1. Navigate to the project you want to configure.
+1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
+1. Click "GitHub".
+1. Select the "Active" checkbox.
+1. Paste the token you've generated on GitHub
+1. Enter the path to your project on GitHub, such as `https://github.com/username/repository`
+1. Optionally check "Static status check names" checkbox to enable static status check names.
+1. Save or optionally click "Test Settings".
+
+#### Static / dynamic status check names
+
+Since GitLab 11.5 it is possible to opt-in to using static status check names.
+
+This makes it possible to mark these status checks as _Required_ on GitHub.
+If you check "Static status check names" checkbox on the integration page, your
+GitLab instance host name is going to be appended to a status check name,
+whereas in case of dynamic status check names, a branch name is going to be
+appended.
+
+Dynamic status check name is a default behavior.
+
+![Configure GitHub Project Integration](img/github_configuration.png)
diff --git a/doc/user/project/integrations/gitlab_slack_application.md b/doc/user/project/integrations/gitlab_slack_application.md
new file mode 100644
index 00000000000..8e062ca627b
--- /dev/null
+++ b/doc/user/project/integrations/gitlab_slack_application.md
@@ -0,0 +1,65 @@
+# GitLab Slack application **[FREE ONLY]**
+
+NOTE: **Note:**
+The GitLab Slack application is only configurable for GitLab.com. It will **not**
+work for on-premises installations where you can configure the
+[Slack slash commands](slack_slash_commands.md) service instead. We're working
+with Slack on making this configurable for all GitLab installations, but there's
+no ETA.
+It was first introduced in GitLab 9.4 and distributed to Slack App Directory in
+GitLab 10.2.
+
+Slack provides a native application which you can enable via your project's
+integrations on GitLab.com.
+
+## Slack App Directory
+
+The simplest way to enable the GitLab Slack application for your workspace is to
+install the [GitLab application](https://slack-platform.slack.com/apps/A676ADMV5-gitlab) from
+the [Slack App Directory](https://slack.com/apps).
+
+Clicking install will take you to the
+[GitLab Slack application landing page](https://gitlab.com/profile/slack/edit)
+where you can select a project to enable the GitLab Slack application for.
+
+![GitLab Slack application landing page](img/gitlab_slack_app_landing_page.png)
+
+## Configuration
+
+Alternatively, you can configure the Slack application with a project's
+integration settings.
+
+Keep in mind that you need to have the appropriate permissions for your Slack
+team in order to be able to install a new application, read more in Slack's
+docs on [Adding an app to your team][slack-docs].
+
+To enable GitLab's service for your Slack team:
+
+1. Go to your project's **Settings > Integration > Slack application** (only
+ visible on GitLab.com)
+1. Click the "Add to Slack" button
+
+That's all! You can now start using the Slack slash commands.
+
+## Usage
+
+After confirming the installation, you, and everyone else in your Slack team,
+can use all the [slash commands].
+
+When you perform your first slash command you will be asked to authorize your
+Slack user on GitLab.com.
+
+The only difference with the [manually configurable Slack slash commands][slack-manual]
+is that all the commands should be prefixed with the `/gitlab` keyword.
+We are working on making this configurable in the future.
+
+For example, to show the issue number `1001` under the `gitlab-org/gitlab-ce`
+project, you would do:
+
+```
+/gitlab gitlab-org/gitlab-ce issue show 1001
+```
+
+[slack-docs]: https://get.slack.help/hc/en-us/articles/202035138-Adding-apps-to-your-team
+[slash commands]: ../../../integration/slash_commands.md
+[slack-manual]: slack_slash_commands.md
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
new file mode 100644
index 00000000000..0fd847d415f
--- /dev/null
+++ b/doc/user/project/integrations/hipchat.md
@@ -0,0 +1,53 @@
+# Atlassian HipChat
+
+GitLab provides a way to send HipChat notifications upon a number of events,
+such as when a user pushes code, creates a branch or tag, adds a comment, and
+creates a merge request.
+
+## Setup
+
+GitLab requires the use of a HipChat v2 API token to work. v1 tokens are
+not supported at this time. Note the differences between v1 and v2 tokens:
+
+HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1
+token is allowed to send messages to *any* room.
+
+HipChat v2 API has tokens that are can be created using the Integrations tab
+in the Group or Room admin page. By design, these are lightweight tokens that
+allow GitLab to send messages only to *one* room.
+
+### Complete these steps in HipChat
+
+1. Go to: <https://admin.hipchat.com/admin>
+1. Click on "Group Admin" -> "Integrations".
+1. Find "Build Your Own!" and click "Create".
+1. Select the desired room, name the integration "GitLab", and click "Create".
+1. In the "Send messages to this room by posting this URL" column, you should
+see a URL in the format:
+
+```
+https://api.hipchat.com/v2/room/<room>/notification?auth_token=<token>
+```
+
+HipChat is now ready to accept messages from GitLab. Next, set up the HipChat
+service in GitLab.
+
+### Complete these steps in GitLab
+
+1. Navigate to the project you want to configure for notifications.
+1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
+1. Click "HipChat".
+1. Select the "Active" checkbox.
+1. Insert the `token` field from the URL into the `Token` field on the Web page.
+1. Insert the `room` field from the URL into the `Room` field on the Web page.
+1. Save or optionally click "Test Settings".
+
+## Troubleshooting
+
+If you do not see notifications, make sure you are using a HipChat v2 API
+token, not a v1 token.
+
+Note that the v2 token is tied to a specific room. If you want to be able to
+specify arbitrary rooms, you can create an API token for a specific user in
+HipChat under "Account settings" and "API access". Use the `XXX` value under
+`auth_token=XXX`.
diff --git a/doc/user/project/integrations/img/github_configuration.png b/doc/user/project/integrations/img/github_configuration.png
new file mode 100644
index 00000000000..9f2d47921c7
--- /dev/null
+++ b/doc/user/project/integrations/img/github_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/github_status_check_pipeline_update.png b/doc/user/project/integrations/img/github_status_check_pipeline_update.png
new file mode 100644
index 00000000000..9d02ca18497
--- /dev/null
+++ b/doc/user/project/integrations/img/github_status_check_pipeline_update.png
Binary files differ
diff --git a/doc/user/project/integrations/img/gitlab_slack_app_landing_page.png b/doc/user/project/integrations/img/gitlab_slack_app_landing_page.png
new file mode 100644
index 00000000000..57cd35c9f5d
--- /dev/null
+++ b/doc/user/project/integrations/img/gitlab_slack_app_landing_page.png
Binary files differ
diff --git a/doc/user/project/integrations/img/issue_configuration.png b/doc/user/project/integrations/img/issue_configuration.png
deleted file mode 100644
index 5dfd85974d8..00000000000
--- a/doc/user/project/integrations/img/issue_configuration.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_add_user_to_group.png b/doc/user/project/integrations/img/jira_add_user_to_group.png
index 27dac49260c..d8cf541a81e 100644
--- a/doc/user/project/integrations/img/jira_add_user_to_group.png
+++ b/doc/user/project/integrations/img/jira_add_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_added_user_to_group.png b/doc/user/project/integrations/img/jira_added_user_to_group.png
new file mode 100644
index 00000000000..b3e29a65d6e
--- /dev/null
+++ b/doc/user/project/integrations/img/jira_added_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_api_token_menu.png b/doc/user/project/integrations/img/jira_api_token_menu.png
index 55c8fb1bdb9..14037bd0b47 100644
--- a/doc/user/project/integrations/img/jira_api_token_menu.png
+++ b/doc/user/project/integrations/img/jira_api_token_menu.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_group.png b/doc/user/project/integrations/img/jira_create_new_group.png
index 06c4e84fc61..84be3a94a45 100644
--- a/doc/user/project/integrations/img/jira_create_new_group.png
+++ b/doc/user/project/integrations/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_user.png b/doc/user/project/integrations/img/jira_create_new_user.png
index e9c03ed770d..8460dc98ef9 100644
--- a/doc/user/project/integrations/img/jira_create_new_user.png
+++ b/doc/user/project/integrations/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_group_access.png b/doc/user/project/integrations/img/jira_group_access.png
index 448cc55504d..58cf114bd55 100644
--- a/doc/user/project/integrations/img/jira_group_access.png
+++ b/doc/user/project/integrations/img/jira_group_access.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_project_name.png b/doc/user/project/integrations/img/jira_project_name.png
deleted file mode 100644
index 981c7f7ca18..00000000000
--- a/doc/user/project/integrations/img/jira_project_name.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service.png b/doc/user/project/integrations/img/jira_service.png
deleted file mode 100644
index 0ae2fa28756..00000000000
--- a/doc/user/project/integrations/img/jira_service.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png
index 3a27b4df841..377b69d9d06 100644
--- a/doc/user/project/integrations/img/jira_service_page.png
+++ b/doc/user/project/integrations/img/jira_service_page.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_user_management_link.png b/doc/user/project/integrations/img/jira_user_management_link.png
index 5eb9d031c3e..43ef18da6c8 100644
--- a/doc/user/project/integrations/img/jira_user_management_link.png
+++ b/doc/user/project/integrations/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/user/project/integrations/img/mattermost_configuration.png b/doc/user/project/integrations/img/mattermost_configuration.png
index e0b55b23520..75ef0310f2d 100644
--- a/doc/user/project/integrations/img/mattermost_configuration.png
+++ b/doc/user/project/integrations/img/mattermost_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_add_metric.png b/doc/user/project/integrations/img/prometheus_add_metric.png
new file mode 100644
index 00000000000..e85670e1a13
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_add_metric.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_alert.png b/doc/user/project/integrations/img/prometheus_alert.png
new file mode 100644
index 00000000000..a37f0477fd9
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_alert.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_service_alerts.png b/doc/user/project/integrations/img/prometheus_service_alerts.png
new file mode 100644
index 00000000000..a81dfe7da14
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_service_alerts.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_yaml_deploy.png b/doc/user/project/integrations/img/prometheus_yaml_deploy.png
deleted file mode 100644
index 78dd178a077..00000000000
--- a/doc/user/project/integrations/img/prometheus_yaml_deploy.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/slack_configuration.png b/doc/user/project/integrations/img/slack_configuration.png
index 53b30e0e8cd..a14d2969488 100644
--- a/doc/user/project/integrations/img/slack_configuration.png
+++ b/doc/user/project/integrations/img/slack_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 754711f5919..c652149052e 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -47,15 +47,16 @@ project in Jira and then enter the correct values in GitLab.
When connecting to **JIRA Server**, which supports basic authentication, a **username and password** are required. Check the link below and proceed to the next step:
-- [Setting up an user in JIRA server](jira_server_configuration.md)
+- [Setting up a user in JIRA server](jira_server_configuration.md)
When connecting to **JIRA Cloud**, which supports authentication via API token, an **email and API token**, are required. Check the link below and proceed to the next step:
-- [Setting up an user in JIRA cloud](jira_cloud_configuration.md)
+- [Setting up a user in JIRA cloud](jira_cloud_configuration.md)
### Configuring GitLab
> **Notes:**
+>
> - The currently supported Jira versions are `v6.x` and `v7.x.`. GitLab 7.8 or
> higher is required.
> - GitLab 8.14 introduced a new way to integrate with Jira which greatly simplified
@@ -142,6 +143,7 @@ the same goal:
where `PROJECT-1` is the issue ID of the Jira project.
> **Notes:**
+>
> - Only commits and merges into the project's default branch (usually **master**) will
> close an issue in Jira. You can change your projects default branch under
> [project settings](img/jira_project_settings.png).
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 20036183187..13d65c4d8e4 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -1,18 +1,16 @@
# Creating a username and password for JIRA server
We need to create a user in Jira which will have access to all projects that
-need to integrate with GitLab. Login to your Jira instance as admin and under
-*Administration*, go to *User Management* and create a new user.
+need to integrate with GitLab.
As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
group.
NOTE: **Note**
-It is important that the user `gitlab` has 'write' access to projects in Jira.
+It is important that the Jira user created for the integration is given 'write'
+access to your Jira projects. This is covered in the process below.
-We have split this stage in steps so it is easier to follow.
-
-1. Log in to your Jira instance as an administrator and under **Administration**
+1. Log in to your Jira instance as an administrator and under **Jira Administration**
go to **User Management** to create a new user.
![Jira user management link](img/jira_user_management_link.png)
@@ -27,27 +25,34 @@ We have split this stage in steps so it is easier to follow.
![Jira create new user](img/jira_create_new_user.png)
-1. Create a `gitlab-developers` group which will have write access
- to projects in Jira. Go to the **Groups** tab and select **Create group**.
+1. Create a `gitlab-developers` group. (We will give this group write access to Jira
+ projects in a later step). Go to the **Groups** tab on the left, and select **Add group**.
![Jira create new user](img/jira_create_new_group.png)
- Give it an optional description and click **Create group**.
+ Give it a name and click **Add group**.
- ![Jira create new group](img/jira_create_new_group_name.png)
+1. Add the `gitlab` user to the `gitlab-developers` group by clicking **Edit members**.
+ The `gitlab-developers` group should be listed in the leftmost box as a selected group.
+ Under **Add members to selected group(s)**, enter `gitlab`.
-1. To give the newly-created group 'write' access, go to
- **Application access > View configuration** and add the `gitlab-developers`
- group to Jira Core.
+ ![Jira add user to group](img/jira_add_user_to_group.png)
+
+ Click **Add selected users** and `gitlab` should appear in the **Group member(s)** box.
+ This membership is saved automatically.
+
+ ![Jira added user to group](img/jira_added_user_to_group.png)
+
+1. To give the newly-created group 'write' access, you need to create a **Permission Scheme**.
+ To do this, click the gear icon and select **Issues**. Then click **Permission Schemes**.
+ Click **Add Permission Scheme** and enter a **Name** and, optionally, a **Description**.
+
+1. Once your permission scheme is created, you'll be taken back to the permissions scheme list.
+ Locate your new permissions scheme and click **Permissions**. Next to **Administer Projects**,
+ click **Edit**. In the resulting dialog box, select **Group** and select `gitlab-developers`
+ from the dropdown.
![Jira group access](img/jira_group_access.png)
-1. Add the `gitlab` user to the `gitlab-developers` group by going to
- **Users > GitLab user > Add group** and selecting the `gitlab-developers`
- group from the dropdown menu. Notice that the group says _Access_, which is
- intended as part of this process.
-
- ![Jira add user to group](img/jira_add_user_to_group.png)
-
The Jira configuration is complete. Write down the new Jira username and its
password as they will be needed when [configuring GitLab in the next section](jira.md#configuring-gitlab).
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index 9342a2cbb00..ab43eb2da7e 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -1 +1,5 @@
+---
+redirect_to: '../clusters/index.md'
+---
+
This document was moved to [another location](../clusters/index.md).
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index 8c5461de42f..d7fd75fd728 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -27,9 +27,11 @@ There, you will see a checkbox with the following events that can be triggered:
- Confidential issue
- Merge request
- Note
+- Confidential note
- Tag push
- Pipeline
- Wiki page
+- Deployment
Below each of these event checkboxes, you have an input field to enter
which Mattermost channel you want to send that event message. Enter your preferred channel handle (the hash sign `#` is optional).
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index e031dcad2c3..9c69437537a 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -152,7 +152,7 @@ trigger word followed by <kbd>help</kbd>. Example: <samp>/gitlab help</samp>
## Permissions
The permissions to run the [available commands](#available-slash-commands) derive from
-the [permissions you have on the project](../../permissions.md#project).
+the [permissions you have on the project](../../permissions.md#project-members-permissions).
## Further reading
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
index 8b1908c46fe..1c64b275d6e 100644
--- a/doc/user/project/integrations/mock_ci.md
+++ b/doc/user/project/integrations/mock_ci.md
@@ -5,7 +5,7 @@
To set up the mock CI service server, respond to the following endpoints
- `commit_status`: `#{project.namespace.path}/#{project.path}/status/#{sha}.json`
- - Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success_with_warnings'|'skipped'|'not_found'] }`
+ - Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success-with-warnings'|'skipped'|'not_found'] }`
- If the service returns a 404, it is interpreted as `pending`
- `build_page`: `#{project.namespace.path}/#{project.path}/status/#{sha}`
- Just where the build is linked to, doesn't matter if implemented
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index cec9018b67f..0bfee3bac99 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -14,8 +14,7 @@ want to configure.
![Project services list](img/project_services.png)
-Below, you will find a list of the currently supported ones accompanied with
-comprehensive documentation.
+Below, you will find a list of the currently supported ones accompanied with comprehensive documentation.
## Services
@@ -35,9 +34,12 @@ Click on the service links to see further configuration instructions and details
| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams |
+| [GitHub](github.md) **[PREMIUM]** | Sends pipeline notifications to GitHub |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
+| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
| [JIRA](jira.md) | JIRA issue tracker |
+| [Jenkins](../../../integration/jenkins.md) **[STARTER]** | An extendable open source continuous integration server |
| JetBrains TeamCity CI | A continuous integration and build server |
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
@@ -45,11 +47,13 @@ Click on the service links to see further configuration instructions and details
| Packagist | Update your project on Packagist, the main Composer repository |
| Pipelines emails | Email the pipeline status to a list of recipients |
| [Slack Notifications](slack.md) | Send GitLab events (e.g. issue created) to Slack as notifications |
-| [Slack slash commands](slack_slash_commands.md) | Use slash commands in Slack to control GitLab |
+| [Slack slash commands](slack_slash_commands.md) **[CORE ONLY]** | Use slash commands in Slack to control GitLab |
+| [GitLab Slack application](gitlab_slack_application.md) **[FREE ONLY]** | Use Slack's official application |
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker |
+| [YouTrack](youtrack.md) | YouTrack issue tracker |
## Services templates
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index ed289b0c4eb..751e8e44e60 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -13,7 +13,7 @@ There are two ways to set up Prometheus integration, depending on where your app
- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
- For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
-Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-ci-cd-environments).
+Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-cicd-environments). You are also able to [add your own metrics](#adding-additional-metrics-premium) as well.
## Enabling Prometheus Integration
@@ -93,6 +93,85 @@ GitLab will automatically scan the Prometheus server for metrics from known serv
You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments).
+### Adding additional metrics **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3799) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.6.
+
+Additional metrics can be monitored by adding them on the Prometheus integration page. Once saved, they will be displayed on the environment performance dashboard.
+
+![Add New Metric](img/prometheus_add_metric.png)
+
+A few fields are required:
+
+- **Name**: Chart title
+- **Type**: Type of metric. Metrics of the same type will be shown together.
+- **Query**: Valid [PromQL query](https://prometheus.io/docs/prometheus/latest/querying/basics/).
+- **Y-axis label**: Y axis title to display on the dashboard.
+- **Unit label**: Query units, for example `req / sec`. Shown next to the value.
+
+Multiple metrics can be displayed on the same chart if the fields **Name**, **Type**, and **Y-axis label** match between metrics. For example, a metric with **Name** `Requests Rate`, **Type** `Business`, and **Y-axis label** `rec / sec` would display on the same chart as a second metric with the same values. A **Legend label** is suggested if this feature used.
+
+#### Query Variables
+
+GitLab supports a limited set of [CI variables](../../../ci/variables/README.html) in the Prometheus query. This is particularly useful for identifying a specific environment, for example with `CI_ENVIRONMENT_SLUG`. The supported variables are:
+
+- CI_ENVIRONMENT_SLUG
+- KUBE_NAMESPACE
+
+To specify a variable in a query, enclose it in curly braces with a leading percent. For example: `%{ci_environment_slug}`.
+
+### Setting up alerts for Prometheus metrics **[ULTIMATE]**
+
+#### Managed Prometheus instances
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6590) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2 for [custom metrics](#adding-additional-metrics-premium), and 11.3 for [library metrics](prometheus_library/metrics.md).
+
+For managed Prometheus instances using auto configuration, alerts for metrics [can be configured](#adding-additional-metrics-premium) directly in the performance dashboard.
+
+To set an alert, click on the alarm icon in the top right corner of the metric you want to create the alert for. A dropdown
+will appear, with options to set the threshold and operator. Click **Add** to save and activate the alert.
+
+![Adding an alert](img/prometheus_alert.png)
+
+To remove the alert, click back on the alert icon for the desired metric, and click **Delete**.
+
+#### External Prometheus instances
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9258) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.8.
+
+For manually configured Prometheus servers, a notify endpoint is provided to use with Prometheus webhooks. If you have manual configuration enabled, an **Alerts** section is added to **Settings > Integrations > Prometheus**. This contains the *URL* and *Authorization Key*. The **Reset Key** button will invalidate the key and generate a new one.
+
+![Prometheus service configuration of Alerts](img/prometheus_service_alerts.png)
+
+To send GitLab alert notifications, copy the *URL* and *Authorization Key* into the [`webhook_configs`](https://prometheus.io/docs/alerting/configuration/#webhook_config) section of your Prometheus Alertmanager configuration:
+
+```yaml
+receivers:
+ name: gitlab
+ webhook_configs:
+ - http_config:
+ bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
+ send_resolved: true
+ url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
+ ...
+```
+
+### Taking action on incidents **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4925) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
+
+Alerts can be used to trigger actions, like open an issue automatically. To configure the actions:
+
+1. Navigate to your project's **Settings > Operations > Incidents**.
+1. Enable the option to create issues.
+1. Choose the [issue template](../description_templates.md) to create the issue from.
+1. Optionally, select whether to send an email notification to the developers of the project.
+1. Click **Save changes**.
+
+Once enabled, an issue will be opened automatically when an alert is triggered. The author of the issue will be the GitLab Alert Bot. To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template.
+
+If the metric exceeds the threshold of the alert for over 5 minutes, an email will be sent to all [Maintainers and Owners](../../permissions.md#project-members-permissions) of the project.
+
## Determining the performance impact of a merge
> [Introduced][ce-10408] in GitLab 9.2.
@@ -120,7 +199,7 @@ If the "No data found" screen continues to appear, it could be due to:
- No successful deployments have occurred to this environment.
- Prometheus does not have performance data for this environment, or the metrics
are not labeled correctly. To test this, connect to the Prometheus server and
- [run a query](#gitlab-prometheus-queries), replacing `$CI_ENVIRONMENT_SLUG`
+ [run a query](prometheus_library/kubernetes.html#metrics-supported), replacing `$CI_ENVIRONMENT_SLUG`
with the name of your environment.
[autodeploy]: ../../../ci/autodeploy/index.md
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 7a45c87ada0..8b1cf1a251a 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -1,6 +1,6 @@
# Monitoring Kubernetes
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0.
GitLab has support for automatically detecting and monitoring Kubernetes metrics.
@@ -27,7 +27,7 @@ integration services must be enabled.
Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so:
-- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
+- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.md#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/).
## Specifying the Environment
@@ -35,3 +35,25 @@ Prometheus needs to be deployed into the cluster and configured properly in orde
In order to isolate and only display relevant CPU and Memory metrics for a given environment, GitLab needs a method to detect which containers it is running. Because these metrics are tracked at the container level, traditional Kubernetes labels are not available.
Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name should begin with [CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-environment-variables). It can be followed by a `-` and additional content if desired. For example, a deployment name of `review-homepage-5620p5` would match the `review/homepage` environment.
+
+## Displaying Canary metrics **[PREMIUM]**
+
+> Introduced in [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15201).
+
+GitLab also gathers Kubernetes metrics for [canary deployments](../../canary_deployments.md), allowing easy comparison between the current deployed version and the canary.
+
+These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name to begin with `$CI_ENVIRONMENT_SLUG-canary`, to isolate the canary metrics.
+
+### Canary metrics supported
+
+- Average Memory Usage (MB)
+
+ ```
+ avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024
+ ```
+
+- Average CPU Utilization (%)
+
+ ```
+ avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))
+ ```
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index 37a5388d2fc..7ace0ec5a93 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -1 +1,5 @@
+---
+redirect_to: 'index.md'
+---
+
This document was moved to [another location](index.md).
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index b7601f26802..de7fc93f0a4 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -30,7 +30,7 @@ For other deployments, there is [some configuration](#manually-setting-up-nginx-
### About managed NGINX Ingress deployments
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's Endpoint](../../clusters/index.md#getting-the-external-endpoint).
NGINX is configured for Prometheus monitoring, by setting:
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
index 081eb8732ad..31ac53c0d14 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -30,7 +30,7 @@ For other deployments, there is [some configuration](#manually-setting-up-nginx-
### About managed NGINX Ingress deployments
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's Endpoint](../../clusters/index.md#getting-the-external-endpoint).
NGINX is configured for Prometheus monitoring, by setting:
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 8112aa21859..bac7eecfce4 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -18,9 +18,7 @@
![Redmine configuration](img/redmine_configuration.png)
-1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
-
- ![Issue configuration](img/issue_configuration.png)
+1. To disable the internal issue tracking system in a project, navigate to the General page, expand the [permissions](../settings/index.md#sharing-and-permissions) section and switch the **Issues** toggle to disabled.
## Referencing issues in Redmine
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index c267da69bb3..371e78ca3a4 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -1,10 +1,15 @@
-# Slack slash commands
+# Slack slash commands **[CORE ONLY]**
-> Introduced in GitLab 8.15
+> Introduced in GitLab 8.15.
-Slack slash commands allow you to control GitLab and view content right inside Slack, without having to leave it. This requires configurations in both Slack and GitLab.
+Slack slash commands allow you to control GitLab and view content right inside
+Slack, without having to leave it. This requires configurations in both Slack and GitLab.
-> Note: GitLab can also send events (e.g. issue created) to Slack as notifications. This is the separately configured [Slack Notifications Service](slack.md).
+GitLab can also send events (e.g., `issue created`) to Slack as notifications.
+This is the separately configured [Slack Notifications Service](slack.md).
+
+NOTE: **Note:**
+For GitLab.com, use the [Slack app](gitlab_slack_application.md) instead.
## Configuration
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index c3fc6d4b859..a0f500a939f 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -48,7 +48,7 @@ Navigate to the webhooks page by going to your project's
## Use-cases
- You can set up a webhook in GitLab to send a notification to
- [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
+ [Slack](https://api.slack.com/incoming-webhooks) every time a job fails.
- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
every time an issue is created for a specific project or group within GitLab
- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/).
@@ -321,8 +321,14 @@ X-Gitlab-Event: Issue Hook
"group_id": 41
}],
"changes": {
- "updated_by_id": [null, 1],
- "updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
+ "updated_by_id": {
+ "previous": null,
+ "current": 1
+ },
+ "updated_at": {
+ "previous": "2017-09-15 16:50:55 UTC",
+ "current": "2017-09-15 16:52:00 UTC"
+ },
"labels": {
"previous": [{
"id": 206,
@@ -851,8 +857,14 @@ X-Gitlab-Event: Merge Request Hook
"group_id": 41
}],
"changes": {
- "updated_by_id": [null, 1],
- "updated_at": ["2017-09-15 16:50:55 UTC", "2017-09-15 16:52:00 UTC"],
+ "updated_by_id": {
+ "previous": null,
+ "current": 1
+ },
+ "updated_at": {
+ "previous": "2017-09-15 16:50:55 UTC",
+ "current":"2017-09-15 16:52:00 UTC"
+ },
"labels": {
"previous": [{
"id": 206,
@@ -1041,7 +1053,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
@@ -1062,7 +1079,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
@@ -1083,7 +1105,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
@@ -1114,34 +1141,34 @@ X-Gitlab-Event: Pipeline Hook
}
```
-### Build events
+### Job events
-Triggered on status change of a Build.
+Triggered on status change of a job.
**Request Header**:
```
-X-Gitlab-Event: Build Hook
+X-Gitlab-Event: Job Hook
```
**Request Body**:
```json
{
- "object_kind": "build",
+ "object_kind": "job",
"ref": "gitlab-script-trigger",
"tag": false,
"before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
- "build_id": 1977,
- "build_name": "test",
- "build_stage": "test",
- "build_status": "created",
- "build_started_at": null,
- "build_finished_at": null,
- "build_duration": null,
- "build_allow_failure": false,
- "build_failure_reason": "script_failure",
+ "job_id": 1977,
+ "job_name": "test",
+ "job_stage": "test",
+ "job_status": "created",
+ "job_started_at": null,
+ "job_finished_at": null,
+ "job_duration": null,
+ "job_allow_failure": false,
+ "job_failure_reason": "script_failure",
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
"user": {
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
new file mode 100644
index 00000000000..81c148e41fd
--- /dev/null
+++ b/doc/user/project/integrations/youtrack.md
@@ -0,0 +1,38 @@
+# YouTrack Service
+
+JetBrains [YouTrack](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) is a web-based issue tracking and project management platform.
+
+You can configure YouTrack as an [External Issue Tracker](../../../integration/external-issue-tracker.md) in GitLab.
+
+## Enable the YouTrack integration
+
+To enable YouTrack integration in a project:
+
+1. Navigate to the project's **Settings > [Integrations](project_services.md#accessing-the-project-services)** page.
+1. Click the **YouTrack** service, ensure it's active, and enter the required details on the page as described in the table below.
+
+ | Field | Description |
+ |:----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+ | **Description** | Name for the issue tracker (to differentiate between instances, for example). |
+ | **Project url** | URL to the project in YouTrack which is being linked to this GitLab project. |
+ | **Issues url** | URL to the issue in YouTrack project that is linked to this GitLab project. Note that the **Issues url** requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+
+1. Click the **Save changes** button.
+
+Once you have configured and enabled YouTrack, you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project.
+
+## Disable the internal issue tracker
+
+To disable the internal issue tracker in a project:
+
+1. Navigate to the project's **Settings > General** page.
+1. Expand the [permissions section](../settings/index.md#sharing-and-permissions) and switch the **Issues** toggle to disabled.
+
+## Referencing YouTrack issues in GitLab
+
+Issues in YouTrack can be referenced as `<PROJECT>-<ID>`. `<PROJECT>`
+must start with a letter and is followed by letters, numbers, or underscores.
+`<ID>` is a number. An example reference is `YT-101`, `Api_32-143` or `gl-030`.
+
+References to `<PROJECT>-<ID>` in merge requests, commits, or comments are automatically linked to the YouTrack issue URL.
+For more information, see the [External Issue Tracker](../../../integration/external-issue-tracker.md) documentation.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 0bd565547c3..31020de5208 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -28,11 +28,11 @@ Issue Boards** (version introduced in GitLab 8.11 - August 2016).
### Advanced features of Issue Boards
With [GitLab Starter](https://about.gitlab.com/pricing/), you can create
-[multiple issue boards](#multiple-issue-boards) for a given project. **[STARTER]**
+[multiple issue boards](#multiple-issue-boards-starter) for a given project. **[STARTER]**
With [GitLab Premium](https://about.gitlab.com/pricing/), you can also create multiple
-issue boards for your groups, and add lists for [assignees](#assignee-lists) and
-[milestones](#milestone-lists). **[PREMIUM]**
+issue boards for your groups, and add lists for [assignees](#assignee-lists-premium) and
+[milestones](#milestone-lists-premium). **[PREMIUM]**
Check all the [advanced features of Issue Boards](#gitlab-enterprise-features-for-issue-boards)
below.
@@ -42,7 +42,7 @@ below.
## How it works
The Issue Board builds on GitLab's existing
-[issue tracking functionality](issues/index.md#issue-tracker) and
+[issue tracking functionality](issues/index.md#issues-list) and
leverages the power of [labels](labels.md) by utilizing them as lists of the scrum board.
With the Issue Board you can have a different view of your issues while
@@ -97,7 +97,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and
### Use cases for Multiple Issue Boards
-With [Multiple Issue Boards](#multiple-issue-boards), available only in
+With [Multiple Issue Boards](#multiple-issue-boards-starter), available only in
[GitLab Enterprise Edition](https://about.gitlab.com/pricing/),
each team can have their own board to organize their workflow individually.
@@ -151,7 +151,7 @@ Create lists for each of your team members and quickly drag-and-drop issues onto
## Permissions
-[Developers and up](../permissions.md) can use all the functionality of the
+[Reporters and up](../permissions.md) can use all the functionality of the
Issue Board, that is, create or delete lists and drag issues from one list to another.
## GitLab Enterprise features for Issue Boards
@@ -173,10 +173,14 @@ or in situations where a repository is used to host the code of multiple
products.
Clicking on the current board name in the upper left corner will reveal a
-menu from where you can create another Issue Board and rename or delete the
-existing one.
+menu from where you can create another Issue Board or delete the existing one.
Using the search box at the top of the menu, you can filter the listed boards.
+When you have 10 or more boards available, a "Recent" section is also shown in the menu.
+These are shortcuts to your last 4 visited boards.
+
+![Multiple Issue Boards](img/issue_boards_multiple.png)
+
When you're revisiting an issue board in a project or group with multiple boards,
GitLab will automatically load the last board you visited.
@@ -184,8 +188,6 @@ NOTE: **Note:**
The Multiple Issue Boards feature is available for
**projects in GitLab Starter Edition** and for **groups in GitLab Premium Edition**.
-![Multiple Issue Boards](img/issue_boards_multiple.png)
-
### Configurable Issue Boards **[STARTER]**
> Introduced in [GitLab Starter Edition 10.2](https://about.gitlab.com/2017/11/22/gitlab-10-2-released/#issue-boards-configuration).
@@ -218,7 +220,7 @@ Click the button at the top right to toggle focus mode on and off. In focus mode
The top of each list indicates the sum of issue weights for the issues that
belong to that list. This is useful when using boards for capacity allocation,
-especially in combination with [assignee lists](#assignee-lists).
+especially in combination with [assignee lists](#assignee-lists-premium).
![Issue Board summed weights](img/issue_board_summed_weights.png)
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 8eada25234f..2c755e0fb4d 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -1,6 +1,6 @@
# Confidential issues
-> [Introduced][ce-3282] in GitLab 8.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3282) in GitLab 8.6.
Confidential issues are issues visible only to members of a project with
[sufficient permissions](#permissions-and-access-to-confidential-issues).
@@ -67,7 +67,7 @@ There is also an indicator on the sidebar denoting confidentiality.
There are two kinds of level access for confidential issues. The general rule
is that confidential issues are visible only to members of a project with at
-least [Reporter access][permissions]. However, a guest user can also create
+least [Reporter access](../../permissions.md#project-members-permissions). However, a guest user can also create
confidential issues, but can only view the ones that they created themselves.
Confidential issues are also hidden in search results for unprivileged users.
@@ -77,6 +77,3 @@ project's search results respectively.
| Maintainer access | Guest access |
| :-----------: | :----------: |
| ![Confidential issues search master](img/confidential_issues_search_master.png) | ![Confidential issues search guest](img/confidential_issues_search_guest.png) |
-
-[permissions]: ../../permissions.md#project
-[ce-3282]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3282
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index 40040e44d64..c2916c79876 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -7,7 +7,7 @@ the information illustrated on the image below.
![New issue from the issues list](img/new_issue.png)
-Read through the [issues functionalities documentation](issues_functionalities.md#issues-functionalities)
+Read through the [issue data and actions documentation](issue_data_and_actions.md#parts-of-an-issue)
to understand these fields one by one.
## New issue from the Issue Tracker
@@ -65,6 +65,26 @@ In GitLab 11.7, we updated the format of the generated email address.
However the older format is still supported, allowing existing aliases
or contacts to continue working._
+## New issue via Service Desk **[PREMIUM]**
+
+Enable [Service Desk](../service_desk.md) to your project and offer email support.
+By doing so, when your customer sends a new email, a new issue can be created in
+the appropriate project and followed up from there.
+
+## New issue from the group-level Issue Tracker
+
+Head to the Group dashboard and click "Issues" in the sidebar to visit the Issue Tracker
+for all projects in your Group. Select the project you'd like to add an issue for
+using the dropdown button at the top-right of the page.
+
+![Select project to create issue](img/select_project_from_group_level_issue_tracker.png)
+
+We'll keep track of the project you selected most recently, and use it as the default
+for your next visit. This should save you a lot of time and clicks, if you mostly
+create issues for the same project.
+
+![Create issue from group-level issue tracker](img/create_issue_from_group_level_issue_tracker.png)
+
## New issue via URL with prefilled fields
You can link directly to the new issue page for a given project, with prefilled
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 786d1c81b1b..ff5b1f2ce50 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -48,13 +48,12 @@ issues in merge requests.
## From Merge Requests
-Mentioning issues in merge request comments work exactly the same way
+Mentioning issues in merge request comments works exactly the same way as
they do for [related issues](#from-related-issues).
-When you mention an issue in a merge request description, you can either
-[close the issue as soon as the merge request is merged](closing_issues.md#via-merge-request),
-or simply link both issue and merge request as described in the
-[closing issues documentation](closing_issues.md#from-related-issues).
+When you mention an issue in a merge request description, it will simply
+[link the issue and merge request together](#from-related-issues). Additionally,
+you can also [set an issue to close as soon as the merge request is merged](closing_issues.md#via-merge-request).
![issue mentioned in MR](img/mention_in_merge_request.png)
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
new file mode 100644
index 00000000000..56b94585672
--- /dev/null
+++ b/doc/user/project/issues/csv_export.md
@@ -0,0 +1,77 @@
+# Export Issues to CSV **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1126) in [GitLab Starter 9.0](https://about.gitlab.com/2017/03/22/gitlab-9-0-released/#export-issues-ees-eep).
+
+Issues can be exported as CSV from GitLab and are sent to your default notification email as an attachment.
+
+## Overview
+
+**Export Issues to CSV** enables you and your team to export all the data collected from issues into
+a **[comma-separated values](https://en.wikipedia.org/wiki/Comma-separated_values)** (CSV) file,
+which stores tabular data in plain text.
+
+> _CSVs are a handy way of getting data from one program to another where one program cannot read the other ones normal output._ [Ref](https://www.quora.com/What-is-a-CSV-file-and-its-uses)
+
+CSV files can be used with any plotter or spreadsheet-based program, such as Microsoft Excel,
+Open Office Calc, or Google Spreadsheets.
+
+## Use cases
+
+Among numerous use cases for exporting issues for CSV, we can name a few:
+
+- Make a snapshot of issues for offline analysis or to communicate with other teams who may not be in GitLab
+- Create diagrams, graphs, and charts from the CSV data
+- Present the data in any other format for auditing or sharing reasons
+- Import the issues elsewhere to a system outside of GitLab
+- Long-term issues' data analysis with multiple snapshots created along the time
+- Use the long-term data to gather relevant feedback given in the issues, and improve your product based on real metrics
+
+## Choosing which issues to include
+
+From the issues page you can narrow down which issues to export using the search bar, along with the All/Open/Closed tabs. All issues returned will be exported, including those not shown on the first page.
+
+![CSV export button](img/csv_export_button.png)
+
+You will be asked to confirm the number of issues and email address for the export, after which the email will begin being prepared.
+
+![CSV export modal dialog](img/csv_export_modal.png)
+
+## Sorting
+
+Exported issues are always sorted by `Issue ID`.
+
+## Format
+
+> **Time Estimate** and **Time Spent** columns were [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2627) in GitLab Starter 10.0.
+>
+> **Weight** and **Locked** columns were [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5300) in GitLab Starter 10.8.
+
+Data will be encoded with a comma as the column delimiter, with `"` used to quote fields if needed, and newlines to separate rows. The first row will be the headers, which are listed in the following table along with a description of the values:
+
+
+| Column | Description |
+|---------|-------------|
+| Issue ID | Issue `iid` |
+| URL | A link to the issue on GitLab |
+| Title | Issue `title` |
+| State | `Open` or `Closed` |
+| Description | Issue `description` |
+| Author | Full name of the issue author |
+| Author Username | Username of the author, with the `@` symbol omitted |
+| Assignee | Full name of the issue assignee |
+| Assignee Username | Username of the author, with the `@` symbol omitted |
+| Confidential | `Yes` or `No` |
+| Locked | `Yes` or `No` |
+| Due Date | Formated as `YYYY-MM-DD` |
+| Created At (UTC) | Formated as `YYYY-MM-DD HH:MM:SS` |
+| Updated At (UTC) | Formated as `YYYY-MM-DD HH:MM:SS` |
+| Milestone | Title of the issue milestone |
+| Weight | Issue weight |
+| Labels | Title of any labels joined with a `,` |
+| Time Estimate | [Time estimate](../../../workflow/time_tracking.md#estimates) in seconds |
+| Time Spent | [Time spent](../../../workflow/time_tracking.md#time-spent) in seconds |
+
+
+## Limitations
+
+As the issues will be sent as an email attachment, there is a limit on how much data can be exported. Currently this limit is 20MB to ensure successful delivery across a range of email providers. If this limit is reached we suggest narrowing the search before export, perhaps by exporting open and closed issues separately.
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
index 032e3a73ad0..b0b1cfcfdf7 100644
--- a/doc/user/project/issues/csv_import.md
+++ b/doc/user/project/issues/csv_import.md
@@ -1,17 +1,23 @@
-# Importing Issues from CSV
+# Importing issues from CSV
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7.
-Issues can be imported to a project by uploading a CSV file. Supported fields are
-`title` and `description`.
+Issues can be imported to a project by uploading a CSV file with the columns
+`title` and `description`, in that order.
The user uploading the CSV file will be set as the author of the imported issues.
> **Note:** A permission level of `Developer` or higher is required to import issues.
+## Prepare for the import
+
+- Consider importing a test file containing only a few issues. There is no way to undo a large import without using the GitLab API.
+- Ensure your CSV file meets the [file format](#csv-file-format) requirements.
+
+## Import the file
+
To import issues:
-1. Ensure your CSV file meets the [file format](#csv-file-format) requirements.
1. Navigate to a project's Issues list page.
1. If existing issues are present, click the import icon at the top right, next to the **Edit issues** button.
1. For a project without any issues, click the button labeled **Import CSV** in the middle of the page.
@@ -20,11 +26,11 @@ To import issues:
The file is processed in the background and a notification email is sent
to you once the import is completed.
-## CSV File Format
+## CSV file format
### Header row
-CSV files must contain a header row beginning with at least two columns, `title` and `description`, in that order.
+CSV files must contain a header row where the first column header is `title` and the second is `description`.
If additional columns are present, they will be ignored.
### Column separator
@@ -53,7 +59,7 @@ The limit depends on the configuration value of Max Attachment Size for the GitL
For GitLab.com, it is set to 10 MB.
-## Sample Data
+## Sample data
```csv
title,description
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index 7972c14c1c4..987c16dfab6 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -1,11 +1,11 @@
# Due dates
-> [Introduced][ce-3614] in GitLab 8.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614) in GitLab 8.7.
Please read through the [GitLab Issue Documentation](index.md) for an overview on GitLab Issues.
Due dates can be used in issues to keep track of deadlines and make sure
-features are shipped on time. Due dates require at least [Reporter permissions][permissions]
+features are shipped on time. Due dates require at least [Reporter permissions](../../permissions.md#project-members-permissions)
to be able to edit them. On the contrary, they can be seen by everybody.
## Setting a due date
@@ -47,6 +47,3 @@ on the _Subscribe to calendar_ button on the following pages:
GitLab header
- on the **Project Issues** page
- on the **Group Issues** page
-
-[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
-[permissions]: ../../permissions.md#project
diff --git a/doc/user/project/issues/img/create_issue_from_group_level_issue_tracker.png b/doc/user/project/issues/img/create_issue_from_group_level_issue_tracker.png
new file mode 100644
index 00000000000..8996490ae63
--- /dev/null
+++ b/doc/user/project/issues/img/create_issue_from_group_level_issue_tracker.png
Binary files differ
diff --git a/doc/user/project/issues/img/csv_export_button.png b/doc/user/project/issues/img/csv_export_button.png
new file mode 100644
index 00000000000..f9fcfd71c2f
--- /dev/null
+++ b/doc/user/project/issues/img/csv_export_button.png
Binary files differ
diff --git a/doc/user/project/issues/img/csv_export_modal.png b/doc/user/project/issues/img/csv_export_modal.png
new file mode 100644
index 00000000000..f988deec966
--- /dev/null
+++ b/doc/user/project/issues/img/csv_export_modal.png
Binary files differ
diff --git a/doc/user/project/issues/img/group_issues_list_view.png b/doc/user/project/issues/img/group_issues_list_view.png
deleted file mode 100644
index c951a9e2dcd..00000000000
--- a/doc/user/project/issues/img/group_issues_list_view.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/issues/img/issue_template.png b/doc/user/project/issues/img/issue_template.png
deleted file mode 100644
index 6cb2c07d27e..00000000000
--- a/doc/user/project/issues/img/issue_template.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/issues/img/multiple_assignees.gif b/doc/user/project/issues/img/multiple_assignees.gif
new file mode 100644
index 00000000000..055a0efd9ae
--- /dev/null
+++ b/doc/user/project/issues/img/multiple_assignees.gif
Binary files differ
diff --git a/doc/user/project/issues/img/multiple_assignees_for_issues.png b/doc/user/project/issues/img/multiple_assignees_for_issues.png
new file mode 100644
index 00000000000..e8ae37d427d
--- /dev/null
+++ b/doc/user/project/issues/img/multiple_assignees_for_issues.png
Binary files differ
diff --git a/doc/user/project/issues/img/related_issues_add.png b/doc/user/project/issues/img/related_issues_add.png
new file mode 100644
index 00000000000..f59d2335386
--- /dev/null
+++ b/doc/user/project/issues/img/related_issues_add.png
Binary files differ
diff --git a/doc/user/project/issues/img/related_issues_remove.png b/doc/user/project/issues/img/related_issues_remove.png
new file mode 100644
index 00000000000..be2ec59e61b
--- /dev/null
+++ b/doc/user/project/issues/img/related_issues_remove.png
Binary files differ
diff --git a/doc/user/project/issues/img/select_project_from_group_level_issue_tracker.png b/doc/user/project/issues/img/select_project_from_group_level_issue_tracker.png
new file mode 100644
index 00000000000..97d93620b2a
--- /dev/null
+++ b/doc/user/project/issues/img/select_project_from_group_level_issue_tracker.png
Binary files differ
diff --git a/doc/user/project/issues/img/similar_issues.png b/doc/user/project/issues/img/similar_issues.png
index 0dfb5b00e02..c5b7902b2ac 100644
--- a/doc/user/project/issues/img/similar_issues.png
+++ b/doc/user/project/issues/img/similar_issues.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 5a3ac9c175b..94865ad46ee 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -1,170 +1,135 @@
# Issues
-The GitLab Issue Tracker is an advanced and complete tool
-for tracking the evolution of a new idea or the process
-of solving a problem.
+Issues are the fundamental medium for collaborating on ideas and planning work in GitLab.
-It allows you, your team, and your collaborators to share
-and discuss proposals before and while implementing them.
+## Overview
-GitLab Issues and the GitLab Issue Tracker are available in all
-[GitLab Products](https://about.gitlab.com/pricing/) as
-part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
+The GitLab issue tracker is an advanced tool for collaboratively developing ideas, solving problems, and planning work.
-## Use cases
+Issues can allow you, your team, and your collaborators to share and discuss proposals before and during their implementation.
+However, they can be used for a variety of other purposes, customized to your needs and workflow.
-Issues can have endless applications. Just to exemplify, these are
-some cases for which creating issues are most used:
+Issues are always associated with a specific project, but if you have multiple projects in a group,
+you can also view all the issues collectively at the group level.
+
+**Common use cases include:**
- Discussing the implementation of a new idea
-- Submitting feature proposals
-- Asking questions
-- Reporting bugs and malfunction
-- Obtaining support
+- Tracking tasks and work status
+- Accepting feature proposals, questions, support requests, or bug reports
- Elaborating new code implementations
See also the blog post "[Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)".
-### Keep private things private
-
-For instance, let's assume you have a public project but want to start a discussion on something
-you don't want to be public. With [Confidential Issues](#confidential-issues),
-you can discuss private matters among the project members, and still keep
-your project public, open to collaboration.
-
-### Streamline collaboration
-
-With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
-available in [GitLab Starter](https://about.gitlab.com/pricing/)
-you can streamline collaboration and allow shared responsibilities to be clearly displayed.
-All assignees are shown across your workflows and receive notifications (as they
-would as single assignees), simplifying communication and ownership.
-
-### Consistent collaboration
-
-Create [issue templates](#issue-templates) to make collaboration consistent and
-containing all information you need. For example, you can create a template
-for feature proposals and another one for bug reports.
-
-## Issue Tracker
-
-The Issue Tracker is the collection of opened and closed issues created in a project.
-It is available for all projects, from the moment the project is created.
-
-Find the issue tracker by navigating to your **Project's homepage** > **Issues**.
-
-### Issues per project
-
-When you access your project's issues, GitLab will present them in a list,
-and you can use the tabs available to quickly filter by open and closed issues.
-
-![Project issues list view](img/project_issues_list_view.png)
-
-You can also [search and filter](../../search/index.md#issues-and-merge-requests-per-project) the results more deeply with GitLab's search capacities.
-
-### Issues per group
-
-View issues in all projects in the group, including all projects of all descendant subgroups of the group. Navigate to **Group > Issues** to view these issues. This view also has the open and closed issues tabs.
-
-![Group Issues list view](img/group_issues_list_view.png)
-
-## GitLab Issues Functionalities
-
-The image bellow illustrates how an issue looks like:
+## Parts of an issue
+
+Issues contain a variety of content and metadata, enabling a large range of flexibility in how they are used. Each issue can contain the following attributes, though some items may remain unset.
+
+<table class="borderless-table fixed-table">
+<tr>
+ <td>
+ <ul>
+ <li>Content</li>
+ <ul>
+ <li>Title</li>
+ <li>Description and tasks</li>
+ <li>Comments and other activity</li>
+ </ul>
+ <li>People</li>
+ <ul>
+ <li>Author</li>
+ <li>Assignee(s)</li>
+ </ul>
+ <li>State</li>
+ <ul>
+ <li>Status (open/closed)</li>
+ <li>Confidentiality</li>
+ <li>Tasks (completed vs. outstanding)</li>
+ </ul>
+ </ul>
+ </td>
+ <td>
+ <ul>
+ <li>Planning and tracking</li>
+ <ul>
+ <li>Milestone</li>
+ <li>Due date</li>
+ <li>Weight</li>
+ <li>Time tracking</li>
+ <li>Labels</li>
+ <li>Votes</li>
+ <li>Reaction emoji</li>
+ <li>Linked issues</li>
+ <li>Assigned epic</li>
+ <li>Unique issue number and URL</li>
+ </ul>
+ </ul>
+ </td>
+</tr>
+</table>
+
+## Viewing and managing issues
+
+While you can view and manage the full detail of an issue at its URL, you can also work with multiple issues at a time using the Issues List, Issue Boards, Epics **[ULTIMATE]**, and issue references.
+
+### Issue page
![Issue view](img/issues_main_view.png)
-Learn more about it on the [GitLab Issues Functionalities documentation](issues_functionalities.md).
-
-## New issue
+On an issue’s page, you can view all aspects of the issue, and you can also modify them if you you have the necessary [permissions](../../permissions.md).
-Read through the [documentation on creating issues](create_new_issue.md).
+For more information, see the [Issue Data and Actions](issue_data_and_actions.md) page.
-## Closing issues
+### Issues list
-Learn distinct ways to [close issues](closing_issues.md) in GitLab.
-
-## Moving issues
-
-Read through the [documentation on moving issues](moving_issues.md).
-
-## Deleting issues
+![Project issues list view](img/project_issues_list_view.png)
-Read through the [documentation on deleting issues](deleting_issues.md)
+On the Issues List, you can view all issues in the current project, or from multiple projects when opening the Issues List from the higher-level group context. Filter the issue list by [any search query](../../search/index.md#issues-and-merge-requests-per-project) and/or specific metadata, such as label(s), assignees(s), status, and more. From this view, you can also make certain changes [in bulk](../bulk_editing.md) to the displayed issues.
-## Create a merge request from an issue
+For more information, see the [Issue Data and Actions](issue_data_and_actions.md) page.
-Learn more about it on the [GitLab Issues Functionalities documentation](issues_functionalities.md#18-new-merge-request).
+### Issue boards
-## Search for an issue
+![Issue board](img/issue_board.png)
-Learn how to [find an issue](../../search/index.md) by searching for and filtering them.
+Issue boards are Kanban boards with columns that display issues based on their labels or their assignees**[PREMIUM]**. They offer the flexibility to manage issues using highly customizable workflows.
-## Advanced features
+You can reorder issues within a column, or drag an issue card to another column; its associated label or assignee will change to match that of the new column. The entire board can also be filtered to only include issues from a certain milestone or an overarching label.
-### Confidential Issues
+For more information, see the [Issue Boards](../issue_board.md) page.
-Whenever you want to keep the discussion presented in a
-issue within your team only, you can make that
-[issue confidential](confidential_issues.md). Even if your project
-is public, that issue will be preserved. The browser will
-respond with a 404 error whenever someone who is not a project
-member with at least [Reporter level](../../permissions.md#project) tries to
-access that issue's URL.
+### Epics **[ULTIMATE]**
-Learn more about them on the [confidential issues documentation](confidential_issues.md).
+Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones.
-### Issue templates
+For more information, see the [Epics](../../group/epics/index.md) page.
-Create templates for every new issue. They will be available from
-the dropdown menu **Choose a template** when you create a new issue:
+### Related issues **[STARTER]**
-![issue template](img/issue_template.png)
+You can mark two issues as related, so that when viewing each one, the other is always listed in its Related Issues section. This can help display important context, such as past work, dependencies, or duplicates.
-Learn more about them on the [issue templates documentation](../../project/description_templates.md#creating-issue-templates).
+For more information, see [Related Issues](related_issues.md).
### Crosslinking issues
-Learn more about [crosslinking](crosslinking_issues.md) issues and merge requests.
-
-### Issue Board
-
-The [GitLab Issue Board](https://about.gitlab.com/features/issueboard/) is a way to
-enhance your workflow by organizing and prioritizing issues in GitLab.
-
-![Issue board](img/issue_board.png)
-
-Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issues** > **Board**.
+When you reference an issue from another issue or merge request by including its URL or ID, the referenced issue displays a message in the Activity stream about the reference, with a link to the other issue or MR.
-Read through the documentation for [Issue Boards](../issue_board.md)
-to find out more about this feature.
+For more information, see [Crosslinking issues](crosslinking_issues.md).
-With [GitLab Starter](https://about.gitlab.com/pricing/), you can also
-create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+## Issue actions
-### Import Issues from CSV
+- [Create an issue](create_new_issue.md)
+- [Create an issue from a template](../../project/description_templates.md#using-the-templates)
+- [Close an issue](closing_issues.md)
+- [Move an issue](moving_issues.md)
+- [Delete an issue](deleting_issues.md)
+- [Create a merge request from an issue](issue_data_and_actions.md#18-new-merge-request)
-You can import a CSV file containing issue titles and descriptions to create
-a batch of issues simultaneously.
+## Advanced issue management
-When you navigate to the Issues list page, an import button is displayed.
-
-For further details, see [Importing issues from CSV](csv_import.md)
-
-### External Issue Tracker
-
-Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
-tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
+- [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues in order to change their status, assignee, milestone, or labels in bulk.
+- [Import issues](csv_import.md)
+- [Export issues](csv_export.md) **[STARTER]**
+- [Issues API](../../../api/issues.md)
+- Configure an [external issue tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
-
-### Issue API
-
-See the [API documentation](../../../api/issues.md).
-
-### Bulk editing issues
-
-See the [bulk editing issues](../../project/bulk_editing.md) page.
-
-### Similar issues
-
-See the [similar issues](similar_issues.md) page.
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
new file mode 100644
index 00000000000..fc11c0251e0
--- /dev/null
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -0,0 +1,172 @@
+# Issue Data and Actions
+
+Please read through the [GitLab Issue Documentation](index.md) for an overview on GitLab Issues.
+
+## Parts of an Issue
+
+The image below illustrates what an issue looks like:
+
+![Issue view](img/issues_main_view_numbered.jpg)
+
+You can find all the information on that issue on one screen.
+
+### Issue screen
+
+An issue starts with its status (open or closed), followed by its author,
+and includes many other functionalities, numbered in the image above to
+explain what they mean, one by one.
+
+Many of the elements of the issue screen refresh automatically, such as the title and description, when they are changed by another user.
+Comments and system notes also appear automatically in response to various actions and content updates.
+
+#### 1. New Issue, close issue, edit
+
+- New issue: create a new issue in the same project
+- Close issue: close this issue
+- Edit: edit the same fields available when you create an issue.
+
+#### 2. Todos
+
+- Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.md) list
+- Mark todo as done: mark that issue as done (reflects on the Todo list)
+
+#### 3. Assignee
+
+Whenever someone starts to work on an issue, it can be assigned
+to that person. The assignee can be changed as much as needed.
+The idea is that the assignee is responsible for that issue until
+it's reassigned to someone else to take it from there.
+
+> **Tip:**
+if a user is not member of that project, it can only be
+assigned to them if they created the issue themselves.
+
+##### 3.1. Multiple Assignees **[STARTER]**
+
+Often multiple people work on the same issue together,
+which can be especially difficult to track in large teams
+where there is shared ownership of an issue.
+
+In [GitLab Starter](https://about.gitlab.com/pricing/), you can
+assign multiple people to an issue.
+
+Learn more in the [Multiple Assignees documentation](multiple_assignees_for_issues.md).
+
+#### 4. Milestone
+
+- Select a [milestone](../milestones/index.md) to attribute that issue to.
+
+#### 5. Time Tracking
+
+- Estimate time: add an estimate of the time it will take to resolve the issue.
+- Spend: add the time spent on the resolution of the issue
+
+> **Note:**
+Both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
+
+Learn more in the [Time Tracking documentation](../../../workflow/time_tracking.md).
+
+#### 6. Due date
+
+When you work on a tight schedule, it's important to
+have a way to set a deadline for implementations and for solving
+problems. This can be done in the [due date](due_dates.md) element. Due dates
+can be changed as many times as needed.
+
+#### 7. Labels
+
+Categorize issues by giving them [labels](../labels.md). They help to
+organize workflows, and they enable you to work with the
+[GitLab Issue Board](index.md#issue-boards).
+
+Group Labels, which allow you to use the same labels for a
+group of projects, can be also given to issues. They work exactly the same,
+but they are immediately available to all projects in the group.
+
+> **Tip:**
+If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu from which you can select **Create new label**.
+
+#### 8. Weight **[STARTER]**
+
+- Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed.
+
+Learn more in the [Issue Weight documentation](../../../workflow/issue_weight.md).
+
+#### 9. Participants
+
+- People involved in that issue (mentioned in the description or in the [discussion](../../discussions/index.md)).
+
+#### 10. Notifications
+
+- Subscribe: if you are not a participant of the discussion on that issue, but
+ want to receive notifications on each new input, subscribe to it.
+- Unsubscribe: if you are receiving notifications on that issue but no
+ longer want to receive them, unsubscribe from it.
+
+Read more in the [notifications documentation](../../../workflow/notifications.md#issue--epics--merge-request-events).
+
+#### 11. Reference
+
+- A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
+ is the `project-name`, and `xxx` is the issue number.
+
+#### 12. Title and description
+
+- Title: a plain text title for describing the subject of the issue.
+- Description: a large text field which fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
+ to describe all the details of the issue.
+
+#### 13. Mentions
+
+- You can mention a user or a group present in your GitLab instance with
+ `@username` or `@groupname` and they will be notified via todos and email, unless
+ they have disabled all notifications in their profile settings.
+- Mentions for yourself (the current logged in user), will be highlighted
+ in a different color, allowing you to easily see which comments involve you,
+ helping you focus on them quickly.
+
+To change your [notification settings](../../../workflow/notifications.md), navigate to
+**Profile Settings** > **Notifications** > **Global notification level**
+and choose your preference from the dropdown menu.
+
+> **Tip:**
+Avoid mentioning `@all` in issues and merge requests,
+as it sends an email notification
+to all the members of that project's group, which can be
+interpreted as spam.
+
+#### 14. Related Merge Requests
+
+- Any merge requests mentioned in that issue's description
+ or in the issue discussion thread.
+
+#### 15. Award emoji
+
+- Award an emoji to that issue.
+
+> **Tip:**
+Posting "+1" as a comment in a thread spams all subscribed
+participants of that issue. Awarding an emoji is a way to let them
+know you like it without spamming them.
+
+#### 16. Thread
+
+- Comments: collaborate to that issue by posting comments in its thread.
+ These text fields also fully support
+ [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+
+#### 17. Comment, start a discussion, or comment and close
+
+Once you write a comment, you can either:
+
+- Click "Comment" and your comment will be published.
+- Click "Start discussion": start a thread within that issue's thread to discuss specific points.
+- Click "Comment and close issue": post your comment and close that issue in one click.
+
+#### 18. New Merge Request
+
+- Create a new merge request (with a new source branch named after the issue) in one action.
+ The merge request will automatically inherit the milestone and labels of the issue. The merge
+ request will automatically close that issue when it is merged.
+- Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
+ named after that issue.
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
deleted file mode 100644
index 27b9dc51760..00000000000
--- a/doc/user/project/issues/issues_functionalities.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# GitLab Issues Functionalities
-
-Please read through the [GitLab Issue Documentation](index.md) for an overview on GitLab Issues.
-
-## Issues Functionalities
-
-The image below illustrates what an issue looks like:
-
-![Issue view](img/issues_main_view_numbered.jpg)
-
-You can find all the information on that issue on one screen.
-
-### Issue screen
-
-An issue starts with its status (open or closed), followed by its author,
-and includes many other functionalities, numbered in the image above to
-explain what they mean, one by one.
-
-Many of the elements of the issue screen refresh automatically, such as the title and description, when they are changed by another user.
-Comments and system notes also appear automatically in response to various actions and content updates.
-
-#### 1. New Issue, close issue, edit
-
-- New issue: create a new issue in the same project
-- Close issue: close this issue
-- Edit: edit the same fields available when you create an issue.
-
-#### 2. Todos
-
-- Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.md) list
-- Mark todo as done: mark that issue as done (reflects on the Todo list)
-
-#### 3. Assignee
-
-Whenever someone starts to work on an issue, it can be assigned
-to that person. The assignee can be changed as much as needed.
-The idea is that the assignee is responsible for that issue until
-it's reassigned to someone else to take it from there.
-
-> **Tip:**
-if a user is not member of that project, it can only be
-assigned to them if they created the issue themselves.
-
-##### 3.1. Multiple Assignees **[STARTER]**
-
-Often multiple people work on the same issue together,
-which can be especially difficult to track in large teams
-where there is shared ownership of an issue.
-
-In [GitLab Starter](https://about.gitlab.com/pricing/), you can
-assign multiple people to an issue.
-
-Learn more in the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html).
-
-#### 4. Milestone
-
-- Select a [milestone](../milestones/index.md) to attribute that issue to.
-
-#### 5. Time Tracking
-
-- Estimate time: add an estimate of the time it will take to resolve the issue.
-- Spend: add the time spent on the resolution of the issue
-
-> **Note:**
-Both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
-
-Learn more in the [Time Tracking documentation](../../../workflow/time_tracking.md).
-
-#### 6. Due date
-
-When you work on a tight schedule, it's important to
-have a way to set a deadline for implementations and for solving
-problems. This can be done in the [due date](due_dates.md) element. Due dates
-can be changed as many times as needed.
-
-#### 7. Labels
-
-Categorize issues by giving them [labels](../labels.md). They help to
-organize workflows, and they enable you to work with the
-[GitLab Issue Board](index.md#issue-board).
-
-Group Labels, which allow you to use the same labels for a
-group of projects, can be also given to issues. They work exactly the same,
-but they are immediately available to all projects in the group.
-
-> **Tip:**
-If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu from which you can select **Create new label**.
-
-#### 8. Weight **[STARTER]**
-
-- Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed.
-
-Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html).
-
-#### 9. Participants
-
-- People involved in that issue (mentioned in the description or in the [discussion](../../discussions/index.md)).
-
-#### 10. Notifications
-
-- Subscribe: if you are not a participant of the discussion on that issue, but
- want to receive notifications on each new input, subscribe to it.
-- Unsubscribe: if you are receiving notifications on that issue but no
- longer want to receive them, unsubscribe from it.
-
-Read more in the [notifications documentation](../../../workflow/notifications.md#issue--merge-request-events).
-
-#### 11. Reference
-
-- A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
- is the `project-name`, and `xxx` is the issue number.
-
-#### 12. Title and description
-
-- Title: a plain text title for describing the subject of the issue.
-- Description: a large text field which fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
- to describe all the details of the issue.
-
-#### 13. Mentions
-
-- You can mention a user or a group present in your GitLab instance with
- `@username` or `@groupname` and they will be notified via todos and email, unless
- they have disabled all notifications in their profile settings.
-- Mentions for yourself (the current logged in user), will be highlighted
- in a different color, allowing you to easily see which comments involve you,
- helping you focus on them quickly.
-
-To change your [notification settings](../../../workflow/notifications.md), navigate to
-**Profile Settings** > **Notifications** > **Global notification level**
-and choose your preference from the dropdown menu.
-
-> **Tip:**
-Avoid mentioning `@all` in issues and merge requests,
-as it sends an email notification
-to all the members of that project's group, which can be
-interpreted as spam.
-
-#### 14. Related Merge Requests
-
-- Any merge requests mentioned in that issue's description
- or in the issue discussion thread.
-
-#### 15. Award emoji
-
-- Award an emoji to that issue.
-
-> **Tip:**
-Posting "+1" as a comment in a thread spams all subscribed
-participants of that issue. Awarding an emoji is a way to let them
-know you like it without spamming them.
-
-#### 16. Thread
-
-- Comments: collaborate to that issue by posting comments in its thread.
- These text fields also fully support
- [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
-
-#### 17. Comment, start a discussion, or comment and close
-
-Once you write a comment, you can either:
-
-- Click "Comment" and your comment will be published.
-- Click "Start discussion": start a thread within that issue's thread to discuss specific points.
-- Click "Comment and close issue": post your comment and close that issue in one click.
-
-#### 18. New Merge Request
-
-- Create a new merge request (with a new source branch named after the issue) in one action.
- The merge request will automatically inherit the milestone and labels of the issue. The merge
- request will automatically close that issue when it is merged.
-- Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
- named after that issue.
diff --git a/doc/user/project/issues/multiple_assignees_for_issues.md b/doc/user/project/issues/multiple_assignees_for_issues.md
new file mode 100644
index 00000000000..d1db0790d69
--- /dev/null
+++ b/doc/user/project/issues/multiple_assignees_for_issues.md
@@ -0,0 +1,41 @@
+# Multiple Assignees for Issues **[STARTER]**
+
+> **Note:**
+[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1904)
+in [GitLab Starter 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues).
+
+## Overview
+
+In large teams, where there is shared ownership of an issue, it can be difficult
+to track who is working on it, who already completed their contributions, who
+didn't even start yet.
+
+In [GitLab Enterprise Edition](https://about.gitlab.com/pricing/),
+you can also select multiple assignees to an issue, making it easier to
+track, and making clearer who is accountable for it.
+
+![multiple assignees for issues](img/multiple_assignees_for_issues.png)
+
+## Use cases
+
+Consider a team formed by frontend developers, backend developers,
+UX designers, QA testers, and a product manager working together to bring an idea to
+market.
+
+Multiple Assignees for Issues makes collaboration smoother,
+and allows shared responsibilities to be clearly displayed.
+All assignees are shown across your team's workflows and receive notifications (as they
+would as single assignees), simplifying communication and ownership.
+
+Once an assignee had their work completed, they would remove themselves as assignees, making
+it clear that their role is complete.
+
+## How it works
+
+From an opened issue, expand the right sidebar, locate the assignees entry,
+and click on **Edit**. From the dropdown menu, select as many users as you want
+to assign the issue to.
+
+![adding multiple assignees](img/multiple_assignees.gif)
+
+An assignee can be easily removed by deselecting them from the same dropdown menu.
diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md
new file mode 100644
index 00000000000..e679ebf86e6
--- /dev/null
+++ b/doc/user/project/issues/related_issues.md
@@ -0,0 +1,40 @@
+# Related issues **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.4.
+
+Related issues are a bi-directional relationship between any two issues
+and appear in a block below the issue description. Issues can be across groups
+and projects.
+
+The relationship only shows up in the UI if the user can see both issues.
+
+## Adding a related issue
+
+You can relate one issue to another by clicking the related issues "+" button
+in the header of the related issue block. Then, input the issue reference number
+or paste in the full URL of the issue.
+
+Issues of the same project can be specified just by the reference number.
+Issues from a different project require additional information like the
+group and the project name. For example:
+
+- same project: `#44`
+- same group: `project#44 `
+- different group: `group/project#44`
+
+Valid references will be added to a temporary list that you can review.
+When ready, click the green "Add related issues" button to submit.
+
+![Adding a related issue](img/related_issues_add.png)
+
+## Removing a related issue
+
+In the related issues block, click the "x" icon on the right-side of each issue
+token that you wish to remove. Due to the bi-directional relationship, it
+will no longer appear in either issue.
+
+![Removing a related issue](img/related_issues_remove.png)
+
+Please access our [permissions](../../permissions.md) page for more information.
+
+Additionally, you are also able to manage related issues through [our API](../../../api/issue_links.md).
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 9c045dfcce4..e5f62a3bb8d 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -8,13 +8,60 @@ Labels allow you to categorize issues or merge requests using descriptive titles
In GitLab, you can create project and group labels:
-- **Project labels** can be assigned to issues or merge requests in that project only.
+- **Project labels** can be assigned to issues or merge requests in that project only.
- **Group labels** can be assigned to any issue or merge request of any project in that group or any subgroups of the group.
+## Scoped labels **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9175) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
+
+Scoped labels allow teams to use the simple and familiar feature of labels to
+annotate their issues, merge requests, and epics to achieve custom fields and
+custom workflow states by leveraging a special label title syntax.
+
+A scoped label is a kind of label defined only by a special double-colon syntax
+in the label’s title, using the format `key::value`. For example:
+
+![A sample scoped label](img/key_value_labels.png)
+
+Two scoped labels with the same key but a different value cannot simultaneously
+apply to an issue, epic, or merge request. For example, if an issue already has `priority::3`
+and you apply `priority::2` to it, `priority::3` is automatically removed from the issue.
+
+An issue, epic, or merge request cannot have two scoped labels with the same key.
+For example, if an issue is already labeled `priority::3` and you apply the label `priority::2` to it,
+`priority::3` is automatically removed.
+
+### Labels with multiple colon pairs
+
+If labels have multiple instances of `::`, the longest path from left to right, until the last `::`, is considered the "key" or the "scope".
+
+For example, `nested::key1::value1` and `nested::key1::value2` cannot both exist on the same issue. Adding the latter label will automatically remove the former due to the shared scope of `nested::key1`.
+
+`nested::key1::value1` and `nested::key2::value1` can both exist on the same issue, as these are considered to use two different label scopes, `nested::key1` and `nested::key2`.
+
+### Workflows with scoped labels **[PREMIUM]**
+
+Suppose you wanted a custom field in issues to track the platform operating system
+that your features target, where each issue should only target one platform. You
+would then create labels `platform::iOS`, `platform::Android`, `platform::Linux`,
+etc., as necessary. Applying any one of these labels on a given issue would
+automatically remove any other existing label that starts with `platform::`.
+
+The same pattern could be applied to represent the workflow states of your teams.
+Suppose you have the labels `workflow::development`, `workflow::review`, and
+`workflow::deployed`. If an issue already has the label `workflow::development`
+applied, and a developer wanted to advance the issue to `workflow::review`, they
+would simply apply that label, and the `workflow::development` label would
+automatically be removed. This behavior already exists when you move issues
+across label lists in an [issue board](issue_board.md#creating-workflows), but
+now, team members who may not be working in an issue board directly would still
+be able to advance workflow states consistently in issues themselves.
+
## Creating labels
>**Note:**
-A permission level of `Developer` or higher is required to create labels.
+A permission level of Reporter or higher is required to create labels.
### New project label
@@ -35,6 +82,9 @@ GitLab will add the following default labels to the project:
To create a **group label**, follow similar steps from above to project labels. Navigate to **Issues > Labels** in the group and create it from there.
This page only shows group labels in this group.
+Alternatively, you can create group labels also from Epic sidebar. Please note that the created label will belong to the immediate group to which epic belongs.
+
+![Create Labels from Epic](img/labels_epic_sidebar.png)
Group labels appear in every label list page of the group's child projects.
@@ -42,14 +92,14 @@ Group labels appear in every label list page of the group's child projects.
### New project label from sidebar
-From the sidebar of an issue or a merge request, you can create a create a new **project label** inline immediately, instead of navigating to the project label list page.
+From the sidebar of an issue or a merge request, you can create a new **project label** inline immediately, instead of navigating to the project label list page.
![Labels inline](img/new_label_from_sidebar.gif)
## Editing labels
NOTE: **Note:**
-A permission level of `Developer` or higher is required to edit labels.
+A permission level of Reporter or higher is required to edit labels.
You can update a label by navigating to **Issues > Labels** in the project or group and clicking the pencil icon.
@@ -81,7 +131,7 @@ top-right:
GitLab will consider the label title and description for the search.
-## Filtering issues and merge requests by label
+## Filtering issues, merge requests and epics by label
### Filtering in list pages
@@ -89,11 +139,16 @@ From the project issue list page and the project merge request list page, you ca
From the group issue list page and the group merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels (including subgroup ancestors and subgroup descendants) and project labels.
+From the group epic list page, you can [filter](../search/index.md#issues-and-merge-requests) by both current group labels as well as descendant group labels.
+
![Labels group issues](img/labels_group_issues.png)
### Filtering in issue boards
- From [project boards](issue_board.md), you can filter by both group labels and project labels in the [search and filter bar](../search/index.md#issue-boards).
+- From [group issue boards](issue_board.md#group-issue-boards-premium), you can filter by only group labels in the [search and filter bar](../search/index.md#issue-boards). **[PREMIUM]**
+- From [project boards](issue_board.md), you can filter by both group labels and project labels in the [issue board configuration](issue_board.md#configurable-issue-boards-starter). **[STARTER]**
+- From [group issue boards](issue_board.md#group-issue-boards-premium), you can filter by only group labels in the [issue board configuration](issue_board.md#configurable-issue-boards-starter). **[STARTER]**
## Subscribing to labels
diff --git a/doc/user/project/maven_packages.md b/doc/user/project/maven_packages.md
new file mode 100644
index 00000000000..48835a2dac7
--- /dev/null
+++ b/doc/user/project/maven_packages.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'packages/maven_repository.md'
+---
+
+This document was moved to [another location](packages/maven_repository.md).
diff --git a/doc/user/project/members/img/add_new_user_to_project_settings.png b/doc/user/project/members/img/add_new_user_to_project_settings.png
deleted file mode 100644
index e49ea1a3e3d..00000000000
--- a/doc/user/project/members/img/add_new_user_to_project_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/img/add_user_members_menu.png b/doc/user/project/members/img/add_user_members_menu.png
deleted file mode 100644
index 6f08088b52f..00000000000
--- a/doc/user/project/members/img/add_user_members_menu.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/img/max_access_level.png b/doc/user/project/members/img/max_access_level.png
deleted file mode 100644
index 42a0416ffbb..00000000000
--- a/doc/user/project/members/img/max_access_level.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md
index 84a79f04094..1d7ebc856c3 100644
--- a/doc/user/project/merge_requests.md
+++ b/doc/user/project/merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: 'merge_requests/index.md'
+---
+
This document was moved to [merge_requests/index.md](merge_requests/index.md).
diff --git a/doc/user/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md
new file mode 100644
index 00000000000..65ee2e128ae
--- /dev/null
+++ b/doc/user/project/merge_requests/browser_performance_testing.md
@@ -0,0 +1,54 @@
+# Browser Performance Testing **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3507)
+in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
+
+## Overview
+
+If your application offers a web interface and you are using
+[GitLab CI/CD](../../../ci/README.md), you can quickly determine the performance
+impact of pending code changes.
+
+GitLab uses [Sitespeed.io](https://www.sitespeed.io), a free and open source
+tool for measuring the performance of web sites, and has built a simple
+[Sitespeed plugin](https://gitlab.com/gitlab-org/gl-performance)
+which outputs the results in a file called `performance.json`. This plugin
+outputs the performance score for each page that is analyzed.
+
+The [Sitespeed.io performance score](http://examples.sitespeed.io/6.0/2017-11-23-23-43-35/help.html)
+is a composite value based on best practices, and we will be expanding support
+for [additional metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/4370)
+in a future release.
+
+Going a step further, GitLab can show the Performance report right
+in the merge request widget area:
+
+## Use cases
+
+For instance, consider the following workflow:
+
+1. A member of the marketing team is attempting to track engagement by adding a new tool
+1. With browser performance metrics, they see how their changes are impacting the usability of the page for end users
+1. The metrics show that after their changes the performance score of the page has gone down
+1. When looking at the detailed report, they see that the new Javascript library was included in `<head>` which affects loading page speed
+1. They ask a front end developer to help them, who sets the library to load asynchronously
+1. The frontend developer approves the merge request and authorizes its deployment to production
+
+## How it works
+
+First of all, you need to define a job in your `.gitlab-ci.yml` file that generates the
+[Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance-premium).
+For more information on how the Performance job should look like, check the
+example on [Testing Browser Performance](../../../ci/examples/browser_performance.md).
+
+GitLab then checks this report, compares key performance metrics for each page
+between the source and target branches, and shows the information right on the merge request.
+
+>**Note:**
+If the Performance report doesn't have anything to compare to, no information
+will be displayed in the merge request area. That is the case when you add the
+Performance job in your `.gitlab-ci.yml` for the very first time.
+Consecutive merge requests will have something to compare to and the Performance
+report will be shown properly.
+
+![Performance Widget](img/browser_performance_testing.png)
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
new file mode 100644
index 00000000000..705ff333579
--- /dev/null
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -0,0 +1,82 @@
+# Code Quality **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1984)
+in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your
+source code quality using GitLab Code Quality.
+Code Quality uses [Code Climate Engines](https://codeclimate.com), which are
+free and open source. Code Quality doesn’t require a Code Climate subscription.
+
+Going a step further, GitLab can show the Code Quality report right
+in the merge request widget area:
+
+![Code Quality Widget](img/code_quality.gif)
+
+## Use cases
+
+For instance, consider the following workflow:
+
+1. Your backend team member starts a new implementation for making a certain feature in your app faster
+1. With Code Quality reports, they analyze how their implementation is impacting the code quality
+1. The metrics show that their code degrade the quality in 10 points
+1. You ask a co-worker to help them with this modification
+1. They both work on the changes until Code Quality report displays no degradations, only improvements
+1. You approve the merge request and authorize its deployment to staging
+1. Once verified, their changes are deployed to production
+
+## How it works
+
+First of all, you need to define a job in your `.gitlab-ci.yml` file that generates the
+[Code Quality report artifact](../../../ci/yaml/README.md#artifactsreportscodequality-starter).
+
+The Code Quality report artifact is a subset of the
+[Code Climate spec](https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types).
+It must be a JSON file containing an array of objects with the following properties:
+
+| Name | Description |
+| ---------------------- | -------------------------------------------------------------------------------------- |
+| `description` | A description of the code quality violation. |
+| `fingerprint` | A unique fingerprint to identify the code quality violation. For example, an MD5 hash. |
+| `location.path` | The relative path to the file containing the code quality violation. |
+| `location.lines.begin` | The line on which the code quality violation occurred. |
+
+Example:
+
+```json
+[
+ {
+ "description": "'unused' is assigned a value but never used.",
+ "fingerprint": "7815696ecbf1c96e6894b779456d330e",
+ "location": {
+ "path": "lib/index.js",
+ "lines": {
+ "begin": 42
+ }
+ }
+ }
+]
+```
+
+NOTE: **Note:**
+Although the Code Climate spec supports more properties, those are ignored by GitLab.
+
+For more information on what the Code Quality job should look like, check the
+example on [analyzing a project's code quality](../../../ci/examples/code_quality.md).
+
+GitLab then checks this report, compares the metrics between the source and target
+branches, and shows the information right on the merge request.
+
+CAUTION: **Caution:**
+If multiple jobs in a pipeline generate a code quality artifact, only the artifact from
+the last created job (the job with the largest job ID) is used. To avoid confusion,
+configure only one job to generate a code quality artifact.
+
+NOTE: **Note:**
+If the Code Quality report doesn't have anything to compare to, no information
+will be displayed in the merge request area. That is the case when you add the
+Code Quality job in your `.gitlab-ci.yml` for the very first time.
+Consecutive merge requests will have something to compare to and the Code Quality
+report will be shown properly.
diff --git a/doc/user/project/merge_requests/code_quality_diff.md b/doc/user/project/merge_requests/code_quality_diff.md
new file mode 100644
index 00000000000..ccc694672a6
--- /dev/null
+++ b/doc/user/project/merge_requests/code_quality_diff.md
@@ -0,0 +1,6 @@
+---
+redirect_from: 'code_quality_diff.md'
+redirect_to: 'code_quality.md'
+---
+
+This document was moved to [another location](code_quality.md).
diff --git a/doc/user/project/merge_requests/container_scanning.md b/doc/user/project/merge_requests/container_scanning.md
new file mode 100644
index 00000000000..a062731ea35
--- /dev/null
+++ b/doc/user/project/merge_requests/container_scanning.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/container_scanning/index.md'
+---
+
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/merge_requests/dast.md b/doc/user/project/merge_requests/dast.md
new file mode 100644
index 00000000000..98a2906e560
--- /dev/null
+++ b/doc/user/project/merge_requests/dast.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/dast/index.md'
+---
+
+This document was moved to [another location](../../application_security/dast/index.md).
diff --git a/doc/user/project/merge_requests/dependency_scanning.md b/doc/user/project/merge_requests/dependency_scanning.md
new file mode 100644
index 00000000000..bdc1c355016
--- /dev/null
+++ b/doc/user/project/merge_requests/dependency_scanning.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/dependency_scanning/index.md'
+---
+
+This document was moved to [another location](../../application_security/dependency_scanning/index.md).
diff --git a/doc/user/project/merge_requests/img/approvals_can_override.png b/doc/user/project/merge_requests/img/approvals_can_override.png
new file mode 100644
index 00000000000..8d207d018e0
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_can_override.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
new file mode 100644
index 00000000000..b6dc86f312e
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_premium_project_edit.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
new file mode 100644
index 00000000000..b6f6188b9cd
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_remove_on_push.png b/doc/user/project/merge_requests/img/approvals_remove_on_push.png
new file mode 100644
index 00000000000..73964827587
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_remove_on_push.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_edit.png b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png
new file mode 100644
index 00000000000..868b9d58740
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_empty.png b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png
new file mode 100644
index 00000000000..7375820224c
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approve.png b/doc/user/project/merge_requests/img/approve.png
new file mode 100644
index 00000000000..e68259ac5c2
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approve.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approve_additionally.png b/doc/user/project/merge_requests/img/approve_additionally.png
new file mode 100644
index 00000000000..3db5a9159e5
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approve_additionally.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/browser_performance_testing.png b/doc/user/project/merge_requests/img/browser_performance_testing.png
new file mode 100644
index 00000000000..eea77fb8b93
--- /dev/null
+++ b/doc/user/project/merge_requests/img/browser_performance_testing.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/code_quality.gif b/doc/user/project/merge_requests/img/code_quality.gif
new file mode 100644
index 00000000000..bab921cf38b
--- /dev/null
+++ b/doc/user/project/merge_requests/img/code_quality.gif
Binary files differ
diff --git a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
index c2455c2d1e5..5b9844bf02f 100644
--- a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
+++ b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_approver_merge_requests.png b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png
new file mode 100644
index 00000000000..9c386391a4f
--- /dev/null
+++ b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
index 81878709487..8df6a3c9a29 100644
--- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
+++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
index 2a2101719ba..ea3aff59aa1 100644
--- a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
+++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
new file mode 100644
index 00000000000..9ae6e350798
--- /dev/null
+++ b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/remove_approval.png b/doc/user/project/merge_requests/img/remove_approval.png
new file mode 100644
index 00000000000..6083e1745ef
--- /dev/null
+++ b/doc/user/project/merge_requests/img/remove_approval.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/revert_changes_commit.png b/doc/user/project/merge_requests/img/revert_changes_commit.png
deleted file mode 100644
index c9dd0019024..00000000000
--- a/doc/user/project/merge_requests/img/revert_changes_commit.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/revert_changes_mr.png b/doc/user/project/merge_requests/img/revert_changes_mr.png
deleted file mode 100644
index 06b841b3002..00000000000
--- a/doc/user/project/merge_requests/img/revert_changes_mr.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
index 31f23be4d3d..b6d38d85165 100644
--- a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
+++ b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_mark_as_wip.png b/doc/user/project/merge_requests/img/wip_mark_as_wip.png
deleted file mode 100644
index 2c2a263b316..00000000000
--- a/doc/user/project/merge_requests/img/wip_mark_as_wip.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
deleted file mode 100644
index 327ad9a8448..00000000000
--- a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 593eb80e044..86e06c2ea55 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -18,12 +18,12 @@ With GitLab merge requests, you can:
- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project
- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md)
- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests)
-- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs)
+- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#visualizing-pipelines)
- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request
- Assign it to any registered user, and change the assignee how many times you need
- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation
- Organize your issues and merge requests consistently throughout the project with [labels](../../project/labels.md)
-- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.html#time-tracking)
+- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.md#time-tracking)
- [Resolve merge conflicts from the UI](#resolve-conflicts)
- Enable [fast-forward merge requests](#fast-forward-merge-requests)
- Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch
@@ -33,9 +33,16 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also:
-- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) **[PREMIUM]**
-- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]**
-- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) **[STARTER]**
+- Prepare a full review and submit it once it's ready with [Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium) **[PREMIUM]**
+- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **[PREMIUM]**
+- Request [approvals](merge_request_approvals.md) from your managers **[STARTER]**
+- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **[STARTER]**
+- Manage the licenses of your dependencies with [License Management](../../application_security/license_management/index.md) **[ULTIMATE]**
+- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **[ULTIMATE]**
+- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **[ULTIMATE]**
+- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **[ULTIMATE]**
+- Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **[ULTIMATE]**
+- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **[PREMIUM]**
## Use cases
@@ -43,19 +50,21 @@ A. Consider you are a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team
+1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **[STARTER]**
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
-1. You request the approval from your manager
-1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
+1. You avoid using dependencies whose license is not compatible with your project with [License Management reports](license_management.md) **[ULTIMATE]**
+1. You request the [approval](#merge-request-approvals-starter) from your manager
+1. Your manager pushes a commit with their final review, [approves the merge request](merge_request_approvals.md), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#whenmanual) for GitLab CI/CD
1. Your implementations were successfully shipped to your customer
-B. Consider you're a web developer writing a webpage for your company's:
+B. Consider you're a web developer writing a webpage for your company's website:
1. You checkout a new branch, and submit a new page through a merge request
1. You gather feedback from your reviewers
1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
1. You request your web designers for their implementation
-1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager **[STARTER]**
+1. You request the [approval](merge_request_approvals.md) from your manager **[STARTER]**
1. Once approved, your merge request is [squashed and merged](squash_and_merge.md), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
@@ -159,6 +168,21 @@ in a Merge Request. To do so, click the **...** button in the gutter of the Merg
![Comment on any diff file line](img/comment-on-any-diff-line.png)
+## Perform a Review **[PREMIUM]**
+
+Start a review in order to create multiple comments on a diff and publish them once you're ready.
+Starting a review allows you to get all your thoughts in order and ensure you haven't missed anything
+before submitting all your comments.
+
+[Learn more about Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium)
+
+## Squash and merge
+
+GitLab allows you to squash all changes present in a merge request into a single
+commit when merging, to allow for a neater commit history.
+
+[Learn more about squash and merge.](squash_and_merge.md)
+
## Suggest changes
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
@@ -169,6 +193,28 @@ can easily apply them to the codebase directly from the UI. Read
through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes)
to learn more.
+## Multiple assignees **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/2004)
+in [GitLab Starter 11.11](https://about.gitlab.com/pricing).
+
+Multiple people often review merge requests at the same time. GitLab allows you to have multiple assignees for merge requests to indicate everyone that is reviewing or accountable for it.
+
+![multiple assignees for merge requests sidebar](img/multiple_assignees_for_merge_requests_sidebar.png)
+
+To assign multiple assignees to a merge request:
+
+1. From a merge request, expand the right sidebar and locate the **Assignees** section.
+1. Click on **Edit** and from the dropdown menu, select as many users as you want
+ to assign the merge request to.
+
+Similarly, assignees are removed by deselecting them from the same dropdown menu.
+
+It's also possible to manage multiple assignees:
+
+- When creating a merge request.
+- Using [quick actions](../quick_actions.md#quick-actions-for-issues-and-merge-requests).
+
## Resolve conflicts
When a merge request has conflicts, GitLab may provide the option to resolve
@@ -219,6 +265,64 @@ apply the patches. The target branch can be specified using the
[`/target_branch` quick action](../quick_actions.md). If the source
branch already exists, the patches will be applied on top of it.
+## Git push options
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26752) in GitLab 11.10.
+
+NOTE: **Note:**
+Git push options are only available with Git 2.10 or newer.
+
+GitLab supports using
+[Git push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt)
+to perform the following actions against merge requests at the same time
+as pushing changes:
+
+- Create a new merge request for the pushed branch.
+- Set the target of the merge request to a particular branch.
+- Set the merge request to merge when its pipeline succeeds.
+
+### Create a new merge request using git push options
+
+To create a new merge request for a branch, use the
+`merge_request.create` push option:
+
+```sh
+git push -o merge_request.create
+```
+
+### Set the target branch of a merge request using git push options
+
+To update an existing merge request's target branch, use the
+`merge_request.target=<branch_name>` push option:
+
+```sh
+git push -o merge_request.target=branch_name
+```
+
+You can also create a merge request and set its target branch at the
+same time using a `-o` flag per push option:
+
+```sh
+git push -o merge_request.create -o merge_request.target=branch_name
+```
+
+### Set merge when pipeline succeeds using git push options
+
+To set an existing merge request to
+[merge when its pipeline succeeds](merge_when_pipeline_succeeds.md), use
+the `merge_request.merge_when_pipeline_succeeds` push option:
+
+```sh
+git push -o merge_request.merge_when_pipeline_succeeds
+```
+
+You can also create a merge request and set it to merge when its
+pipeline succeeds at the same time using a `-o` flag per push option:
+
+```sh
+git push -o merge_request.create -o merge_request.merge_when_pipeline_succeeds
+```
+
## Find the merge request that introduced a change
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383) in GitLab 10.5.
@@ -254,7 +358,61 @@ To prevent merge requests from accidentally being accepted before they're
completely ready, GitLab blocks the "Accept" button for merge requests that
have been marked as a **Work In Progress**.
-[Learn more about settings a merge request as "Work In Progress".](work_in_progress_merge_requests.md)
+[Learn more about setting a merge request as "Work In Progress".](work_in_progress_merge_requests.md)
+
+## Merge request approvals **[STARTER]**
+
+> Included in [GitLab Starter][products].
+
+If you want to make sure every merge request is approved by one or more people,
+you can enforce this workflow by using merge request approvals. Merge request
+approvals allow you to set the number of necessary approvals and predefine a
+list of approvers that will need to approve every merge request in a project.
+
+[Read more about merge request approvals.](merge_request_approvals.md)
+
+## Code Quality **[STARTER]**
+
+> Introduced in [GitLab Starter][products] 9.3.
+
+If you are using [GitLab CI][ci], you can analyze your source code quality using
+the [Code Climate][cc] analyzer [Docker image][cd]. Going a step further, GitLab
+can show the Code Climate report right in the merge request widget area.
+
+[Read more about Code Quality reports.](code_quality.md)
+
+## Browser Performance Testing **[PREMIUM]**
+
+> Introduced in [GitLab Premium][products] 10.3.
+
+If your application offers a web interface and you are using [GitLab CI/CD][ci], you can quickly determine the performance impact of pending code changes. GitLab uses [Sitespeed.io][sitespeed], a free and open source tool for measuring the performance of web sites, to analyze the performance of specific pages.
+
+GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the difference in overall performance scores between the source and target branches.
+
+[Read more about Browser Performance Testing.](browser_performance_testing.md)
+
+## Security reports **[ULTIMATE]**
+
+GitLab can scan and report any vulnerabilities found in your project.
+
+[Read more about security reports.](../../application_security/index.md)
+
+## JUnit test reports
+
+Configure your CI jobs to use JUnit test reports, and let GitLab display a report
+on the merge request so that it’s easier and faster to identify the failure
+without having to check the entire job log.
+
+[Read more about JUnit test reports](../../../ci/junit_test_reports.md).
+
+## Live preview with Review Apps
+
+If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
+you can preview the changes submitted to a feature-branch through a merge request
+in a per-branch basis. No need to checkout the branch, install and preview locally;
+all your changes will be available to preview by anyone with the Review Apps link.
+
+[Read more about Review Apps.](../../../ci/review_apps/index.md)
## Merge request diff file navigation
@@ -353,7 +511,7 @@ seconds and the status will update automatically.
Merge Request pipeline statuses can't be retrieved when the following occurs:
-1. A Merge Requst is created
+1. A Merge Request is created
1. The Merge Request is closed
1. Changes are made in the project
1. The Merge Request is reopened
@@ -389,13 +547,13 @@ Add the following alias to your `~/.gitconfig`:
Now you can check out a particular merge request from any repository and any
remote. For example, to check out the merge request with ID 5 as shown in GitLab
-from the `upstream` remote, do:
+from the `origin` remote, do:
```
-git mr upstream 5
+git mr origin 5
```
-This will fetch the merge request into a local `mr-upstream-5` branch and check
+This will fetch the merge request into a local `mr-origin-5` branch and check
it out.
#### Checkout locally by modifying `.git/config` for a given repository
@@ -448,5 +606,14 @@ And to check out a particular merge request:
git checkout origin/merge-requests/1
```
+all the above can be done with [git-mr] script.
+
+[git-mr]: https://gitlab.com/glensc/git-mr
+[products]: https://about.gitlab.com/products/ "GitLab products page"
[protected branches]: ../protected_branches.md
+[ci]: ../../../ci/README.md
+[cc]: https://codeclimate.com/
+[cd]: https://hub.docker.com/r/codeclimate/codeclimate/
+[sitespeed]: https://www.sitespeed.io
+[sitespeed-container]: https://hub.docker.com/r/sitespeedio/sitespeed.io/
[ee]: https://about.gitlab.com/pricing/ "GitLab Enterprise Edition"
diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md
new file mode 100644
index 00000000000..93116ebd7c6
--- /dev/null
+++ b/doc/user/project/merge_requests/license_management.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/license_management/index.md'
+---
+
+This document was moved to [another location](../../application_security/license_management/index.md).
diff --git a/doc/user/project/merge_requests/maintainer_access.md b/doc/user/project/merge_requests/maintainer_access.md
index d59afecd375..fe7e1f558c7 100644
--- a/doc/user/project/merge_requests/maintainer_access.md
+++ b/doc/user/project/merge_requests/maintainer_access.md
@@ -1 +1,5 @@
+---
+redirect_to: 'allow_collaboration.md'
+---
+
This document was moved to [another location](allow_collaboration.md).
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
new file mode 100644
index 00000000000..2e9db949890
--- /dev/null
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -0,0 +1,330 @@
+# Merge request approvals **[STARTER]**
+
+> Introduced in [GitLab Enterprise Edition 7.12](https://about.gitlab.com/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only).
+
+NOTE: **Note:**
+Prior to 12.0, if you are running a self-managed instance, the new interface shown on
+this page will not be available unless the feature flag
+`approval_rules` is enabled, which can be done from the Rails console by
+instance administrators.
+
+Use these commands to start the Rails console:
+
+```sh
+# Omnibus GitLab
+gitlab-rails console
+
+# Installation from source
+cd /home/git/gitlab
+sudo -u git -H bin/rails console RAILS_ENV=production
+```
+
+Then run `Feature.enable(:approval_rules)` to enable the feature flag.
+
+The documentation for the older interface can be accessed
+[here](/11.7/ee/user/project/merge_requests/merge_request_approvals.html).
+
+## Overview
+
+Merge request approvals enable enforced code review by requiring specified people to approve a merge request before it can be unblocked for merging.
+
+## Use cases
+
+1. Enforcing review of all code that gets merged into a repository.
+2. Specifying code maintainers for an entire repository.
+3. Specifying reviewers for a given proposed code change.
+4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes.
+
+## Editing approvals
+
+To edit the merge request approvals:
+
+1. Navigate to your project's **Settings > General** and expand
+ **Merge request approvals**.
+
+ ![Approvals starter project empty](img/approvals_starter_project_empty.png)
+
+1. Click **Edit**.
+1. Search for users or groups that will be [eligible to approve](#eligible-approvers)
+ merge requests and click the **Add** button to add them as approvers. Note: selecting
+ approvers is optional.
+1. Set the minimum number of required approvals under the **No. approvals required**
+ box. Note: the minimum can be 0.
+1. Click **Update approvers**.
+
+ ![Approvals starter project edit](img/approvals_starter_project_edit.png)
+
+The steps above are the minimum required to get approvals working in your
+merge requests, but there are a couple more options available that might be
+suitable to your workflow:
+
+- Choose whether the default settings can be
+ [overridden per merge request](#overriding-the-merge-request-approvals-default-settings)
+- Choose whether [approvals will be reset with new pushed commits](#resetting-approvals-on-push)
+
+## Editing approvals **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
+
+CAUTION: **Caution:**
+There was a [regression affecting this feature in 11.8](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9648). We recommend upgrading _at least_ to version 11.8.2. to avoid any issues.
+
+NOTE: **Note:**
+In 11.8 this feature does not work in [private groups](https://gitlab.com/gitlab-org/gitlab-ee/issues/10356).
+
+For GitLab Premium, [multiple approver rules](#multiple-approval-rules-premium) can be configured. To configure the merge
+request approval rules:
+
+1. Navigate to your project's **Settings > General** and expand **Merge request approvals**.
+1. Click **Add approvers** to create a new approval rule.
+1. Just like in [GitLab Starter](#editing-approvals), select the approval members and approvals required.
+1. Give the approval rule a name that describes the set of approvers selected.
+1. Click **Add approvers** to submit the new rule.
+
+ ![Approvals premium project edit](img/approvals_premium_project_edit.png)
+
+## Multiple approval rules **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
+
+For GitLab Premium, a merge request's overall approval status is determined by a set of rules. Each rule contains:
+
+- A set of [eligible approvers](#eligible-approvers).
+- A minimum number of approvals required.
+
+When an [eligible approver](#eligible-approvers) approves a merge request, it will reduce the number of approvals left for
+all rules that the approver belongs to.
+
+![Approvals premium merge request widget](img/approvals_premium_mr_widget.png)
+
+If no approval rules are set, then the overall minimum number of approvals required can be configured. With no approval rules,
+any [eligible approver](#eligible-approvers) may approve.
+
+## Eligible approvers
+
+The following can approve merge requests:
+
+- Users being added as approvers at project or merge request level.
+- [Code owners](../code_owners.md) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5).
+
+An individual user can be added as an approver for a project if they are a member of:
+
+- The project.
+- The project's immediate parent group.
+- A group that has access to the project via a [share](../members/share_project_with_groups.md).
+
+A group can also be added as an approver. [In the future](https://gitlab.com/gitlab-org/gitlab-ee/issues/2048),
+group approvers will be restricted.
+
+If a user is added as an individual approver and is also part of a group approver,
+then that user is just counted once. The merge request author, as well as users who have committed
+to the merge request, do not count as eligible approvers,
+if [**Prevent author approval**](#allowing-merge-request-authors-to-approve-their-own-merge-requests) (enabled by default)
+and [**Prevent committers approval**](#prevent-approval-of-merge-requests-by-their-committers) (disabled by default)
+are enabled on the project settings.
+
+### Implicit approvers
+
+If the number of required approvals is greater than the number of approvers,
+other users will become implicit approvers to fill the gap.
+Those implicit approvers include members of the given project with Developer role or higher.
+
+## Adding or removing an approval
+
+If approvals are activated for the given project, when a user visits an open
+merge request, depending on their [eligibility](#eligible-approvers), one of
+the following is possible:
+
+- **They are not an eligible approver**: They cannot do anything with respect
+ to approving this merge request.
+
+- **They have not approved this merge request**:
+
+ - If the required number of approvals has _not_ been yet met, they can approve
+ it by clicking the displayed **Approve** button.
+ ![Approve](img/approve.png)
+ - If the required number of approvals has already been met, they can still
+ approve it by clicking the displayed **Approve additionally** button.
+ ![Add approval](img/approve_additionally.png)
+
+- **They have already approved this merge request**: They can remove their approval.
+
+ ![Remove approval](img/remove_approval.png)
+
+NOTE: **Note:**
+The merge request author is only allowed to approve their own merge request
+if [**Prevent author approval**](#allowing-merge-request-authors-to-approve-their-own-merge-requests) is disabled on the project settings.
+
+For a given merge request, if the approval restrictions have been satisfied,
+the merge request is unblocked and can be merged.
+Note, that meeting the required number of approvals is a necessary, but not
+sufficient condition for unblocking a merge request from being merged. There
+are other conditions that may block it, such as merge conflicts,
+[pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved)
+or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
+
+## Code Owners approvals **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4418) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.9.
+
+It is possible to require at least one approval for each entry in the
+[`CODEOWNERS` file](../code_owners.md) that matches a file changed in
+the merge request. To enable this feature:
+
+1. Navigate to your project's **Settings > General** and expand
+ **Merge request approvals**.
+1. Tick the **Require approval from code owners** checkbox.
+1. Click **Save changes**.
+
+When this feature is enabled, all merge requests will need approval
+from one code owner per matched rule before it can be merged.
+
+## Overriding the merge request approvals default settings
+
+> Introduced in GitLab Enterprise Edition 9.4.
+
+NOTE: **Note:**
+If you are using GitLab Premium, things are a little different with [multiple approval rules](#multiple-approval-rules-premium).
+Read the differences [in GitLab Premium when overriding merge request approvals](#overriding-merge-request-approvals-default-settings-premium).
+
+If approvals are [set at the project level](#editing-approvals), the
+default configuration (number of required approvals and approvers) can be
+overridden for each merge request in that project.
+
+One possible scenario would be to to assign a group of approvers at the project
+level and change them later when creating or editing the merge request.
+
+First, you have to enable this option in the project's settings:
+
+1. Navigate to your project's **Settings > General** and expand
+ **Merge request approvals**
+1. Tick the "Can override approvers and approvals required per merge request"
+ checkbox
+
+ ![Approvals can override](img/approvals_can_override.png)
+
+1. Click **Save changes**
+
+NOTE: **Note:**
+If approver overriding is enabled
+and the project level approvers are changed after a merge request is created,
+the merge request retains the previous approvers.
+However, the approvers can be changed by [editing the merge request](#overriding-the-merge-request-approvals-default-settings).
+
+---
+
+The default approval settings can now be overridden when creating a
+[merge request](index.md) or by editing it after it's been created:
+
+1. Click **Edit** under the **Approvers** section.
+1. Search for users or groups that will be [eligible to approve](#eligible-approvers)
+ merge requests and click the **Add** button to add them as approvers or
+ remove existing approvers that were set in the project's settings.
+1. If you want to change the number of required approvals, set a new number
+ in the **No. approvals required** box.
+1. Click **Update approvers**.
+
+There are however some restrictions:
+
+- The amount of required approvals, if changed, must be greater than the default
+ set at the project level. This ensures that you're not forced to adjust settings
+ when someone is unavailable for approval, yet the process is still enforced.
+
+NOTE: **Note:**
+If you are contributing to a forked project, things are a little different.
+Read what happens when the
+[source and target branches are not the same](#merge-requests-with-different-source-branch-and-target-branch-projects).
+
+## Overriding merge request approvals default settings **[PREMIUM]**
+
+In GitLab Premium, when the approval rules are [set at the project level](#editing-approvals-premium), and
+**Can override approvers and approvals required per merge request** is checked, there are a few more
+restrictions (compared to [GitLab Starter](#overriding-the-merge-request-approvals-default-settings)):
+
+- Approval rules can be added to an MR with no restriction.
+- For project sourced approval rules, editing and removing approvers is not allowed.
+- The approvals required of all approval rules is configurable, but if a rule is backed by a project rule, then it is restricted
+to the minimum approvals required set in the project's corresponding rule.
+
+## Resetting approvals on push
+
+If approvals are [set at the project level](#editing-approvals),
+you can choose whether all approvals on a merge request are removed when
+new commits are pushed to the source branch of the merge request:
+
+1. Navigate to your project's **Settings > General** and expand
+ **Merge request approvals**
+1. Tick the "Remove all approvals in a merge request when new commits are pushed to its source branch"
+ checkbox
+
+ ![Approvals remove on push](img/approvals_remove_on_push.png)
+
+1. Click **Save changes**
+
+NOTE: **Note:**
+Approvals do not get reset when [rebasing a merge request](fast_forward_merge.md)
+from the UI.
+However, approvals will be reset if the target branch is changed.
+
+If you want approvals to persist, independent of changes to the merge request,
+turn this setting to off by unchecking the box and saving the changes.
+
+## Allowing merge request authors to approve their own merge requests
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3349) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
+
+You can allow merge request authors to self-approve merge requests by
+enabling it [at the project level](#editing-approvals). Authors
+also need to be included in the approvers list in order to be able to
+approve their merge request.
+
+1. Navigate to your project's **Settings > General** and expand **Merge request approvals**.
+1. Uncheck the **Prevent approval of merge requests by merge request author** checkbox, which is enabled by default.
+1. Click **Save changes**.
+
+## Prevent approval of merge requests by their committers
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10441) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.10.
+
+You can prevent users that have committed to a merge request from approving it by
+enabling [**Prevent approval of merge requests by their committers**](#prevent-approval-of-merge-requests-by-their-committers).
+
+1. Navigate to your project's **Settings > General** and expand **Merge request approvals**.
+1. Tick the checkbox **Prevent approval of merge requests by their committers**.
+1. Click **Save changes**.
+
+## Require authentication when approving a merge request **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5981) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.0.
+
+You can force the approver to enter a password in order to authenticate who is approving the merge request by
+enabling **Require user password to approve**. This enables an Electronic Signature
+for approvals such as the one defined by [CFR Part 11](https://www.accessdata.fda.gov/scripts/cdrh/cfdocs/cfcfr/CFRSearch.cfm?CFRPart=11&showFR=1&subpartNode=21:1.0.1.1.8.3)):
+
+1. Navigate to your project's **Settings > General** and expand **Merge request approvals**.
+1. Tick the checkbox **Require user password to approve**.
+1. Click **Save changes**.
+
+## Merge requests with different source branch and target branch projects
+
+If the merge request source branch and target branch belong to different
+projects (which happens in merge requests in forked projects), everything is
+with respect to the target branch's project (typically the original project).
+In particular, since the merge request in this case is part of the target
+branch's project, the relevant settings are the target project's. The source
+branch's project settings are not applicable. Even if you start the merge
+request from the source branch's project UI, pay attention to the created merge
+request itself. It belongs to the target branch's project.
+
+## Approver suggestions
+
+Approvers are suggested for merge requests based on the previous authors of the files affected by the merge request.
+
+## Filtering merge requests by approvers
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9468) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.9.
+
+To filter merge requests by an individual approver, you can type (or select from
+the dropdown) `approver` and select the user.
+
+![Filter MRs by an approver](img/filter_approver_merge_requests.png)
diff --git a/doc/user/project/merge_requests/merge_request_discussion_resolution.md b/doc/user/project/merge_requests/merge_request_discussion_resolution.md
index 200965875a1..a554d727ca4 100644
--- a/doc/user/project/merge_requests/merge_request_discussion_resolution.md
+++ b/doc/user/project/merge_requests/merge_request_discussion_resolution.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../discussions/index.md'
+---
+
This document was moved to [another location](../../discussions/index.md).
diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md
index 2167fdfbf7e..cf5e3af16c9 100644
--- a/doc/user/project/merge_requests/merge_when_build_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md
@@ -1,5 +1,7 @@
-This document was moved to [merge_when_pipeline_succeeds](merge_when_pipeline_succeeds.md).
+---
+redirect_to: 'merge_when_pipeline_succeeds.md'
+---
->[Introduced][ce-7135] by the "Rename MWBS service to Merge When Pipeline Succeeds" change.
+This document was moved to [merge_when_pipeline_succeeds](merge_when_pipeline_succeeds.md).
-[ce-7135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7135
+>[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7135) by the "Rename MWBS service to Merge When Pipeline Succeeds" change.
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index bdd7d0022e6..1477e35dca8 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,4 +1,4 @@
-# Merge When Pipeline Succeeds
+# Merge when pipeline succeeds
When reviewing a merge request that looks ready to merge but still has one or
more CI jobs running, you can set it to be merged automatically when the
@@ -35,11 +35,11 @@ You need to have jobs configured to enable this feature.
You can prevent merge requests from being merged if their pipeline did not succeed
or if there are discussions to be resolved.
-Navigate to your project's settings page, select the
-**Only allow merge requests to be merged if the pipeline succeeds** check box and
-hit **Save** for the changes to take effect.
+Navigate to your project's settings page and expand the **Merge requests** section.
+In the **Merge checks** subsection, select the **Pipelines must succeed** check
+box and hit **Save** for the changes to take effect.
-![Only allow merge if pipeline succeeds settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
+![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant jobs pass.
diff --git a/doc/user/project/merge_requests/sast.md b/doc/user/project/merge_requests/sast.md
new file mode 100644
index 00000000000..165290eb114
--- /dev/null
+++ b/doc/user/project/merge_requests/sast.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/sast/index.md'
+---
+
+This document was moved to [another location](../../application_security/sast/index.md).
diff --git a/doc/user/project/merge_requests/sast_docker.md b/doc/user/project/merge_requests/sast_docker.md
new file mode 100644
index 00000000000..a062731ea35
--- /dev/null
+++ b/doc/user/project/merge_requests/sast_docker.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../../application_security/container_scanning/index.md'
+---
+
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 90500fd9c21..70bd1e60594 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,6 +1,7 @@
# Merge requests versions
> **Notes:**
+>
> - [Introduced][ce-5467] in GitLab 8.12.
> - Comments are disabled while viewing outdated merge versions or comparing to
> versions other than base.
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 66ac7740157..6f33eb9a482 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -1,25 +1,46 @@
# "Work In Progress" Merge Requests
-To prevent merge requests from accidentally being accepted before they're
-completely ready, GitLab blocks the "Accept" button for merge requests that
-have been marked a **Work In Progress**.
+If a merge request is not yet ready to be merged, perhaps due to continued development
+or open discussions, you can prevent it from being accepted before it's ready by flagging
+it as a **Work In Progress**. This will disable the "Merge" button, preventing it from
+being merged, and it will stay disabled until the "WIP" flag has been removed.
![Blocked Accept Button](img/wip_blocked_accept_button.png)
-To mark a merge request a Work In Progress, simply start its title with `[WIP]`
-or `WIP:`. As an alternative, you're also able to do it by sending a commit
-with its title starting with `wip` or `WIP` to the merge request's source branch.
-
-![Mark as WIP](img/wip_mark_as_wip.png)
-
-To allow a Work In Progress merge request to be accepted again when it's ready,
-simply remove the `WIP` prefix.
-
-![Unmark as WIP](img/wip_unmark_as_wip.png)
-
-## Filtering merge requests with WIP Status
-
-To filter merge requests with the `WIP` status, you can type `wip`
-and select the value for your filter from the merge request search input.
+## Adding the "Work In Progress" flag to a Merge Request
+
+There are several ways to flag a merge request as a Work In Progress:
+
+- Add "[WIP]" or "WIP:" to the start of the merge request's title. Clicking on
+ **Start the title with WIP:**, under the title box, when editing the merge request's
+ description will have the same effect.
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+ in a discussion comment in the merge request. This is a toggle, and can be repeated
+ to change the status back. Note that any other text in the comment will be discarded.
+- Add "wip" or "WIP" to the start of a commit message targeting the merge request's
+ source branch. This is not a toggle, and doing it again in another commit will have
+ no effect.
+
+## Removing the "Work In Progress" flag from a Merge Request
+
+Similar to above, when a Merge Request is ready to be merged, you can remove the
+"Work in Progress" flag in several ways:
+
+- Remove "[WIP]" or "WIP:" from the start of the merge request's title. Clicking on
+ **Remove the WIP: prefix from the title**, under the title box, when editing the merge
+ request's description, will have the same effect.
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+ in a discussion comment in the merge request. This is a toggle, and can be repeated
+ to change the status back. Note that any other text in the comment will be discarded.
+- Click on the **Resolve WIP status** button near the bottom of the merge request description,
+ next to the "Merge" button (see [image above](#work-in-progress-merge-requests)).
+ Must have at least Developer level permissions on the project for the button to
+ be visible.
+
+## Including/Excluding WIP Merge Requests when searching
+
+When viewing/searching the merge requests list, you can choose to include or exclude
+WIP merge requests by adding a "WIP" filter in the search box, and choosing "Yes"
+(to include) or "No" (to exclude).
![Filter WIP MRs](img/filter_wip_merge_requests.png)
diff --git a/doc/user/project/milestones/burndown_charts.md b/doc/user/project/milestones/burndown_charts.md
new file mode 100644
index 00000000000..7ffeb032d7f
--- /dev/null
+++ b/doc/user/project/milestones/burndown_charts.md
@@ -0,0 +1,70 @@
+# Burndown Charts **[STARTER]**
+
+> **Notes:**
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1540) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.1 for project milestones.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5354) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.8 for group milestones.
+> - [Added](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6495) to [GitLab Starter](https://about.gitlab.com/pricing/) 11.2 for group milestones.
+> - Closed or reopened issues prior to GitLab 9.1 won't have a `closed_at`
+> value, so the burndown chart considers them as closed on the milestone
+> `start_date`. In that case, a warning will be displayed.
+
+## Overview
+
+Burndown Charts are visual representations of the progress of completing a milestone.
+
+![burndown chart](img/burndown_chart.png)
+
+At a glance, you see the current state for the completion a given milestone.
+Without them, you would have to organize the data from the milestone and plot it
+yourself to have the same sense of progress.
+
+GitLab Starter plots it for you and presents it in a clear and beautiful chart.
+
+For an overview, check the video demonstration on [Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/).
+
+## Use cases
+
+Burndown Charts, in general, are used for tracking and analyzing the completion of
+a milestone. Therefore, their use cases are tied to the
+[use you are assigning your milestone to](index.md).
+
+To exemplify, suppose you lead a team of developers in a large company,
+and you follow this workflow:
+
+- Your company set the goal for the quarter to deliver 10 new features for your app
+ in the upcoming major release.
+- You create a milestone, and remind your team to assign that milestone to every new issue
+ and merge request that's part of the launch of your app.
+- Every week, you open the milestone, visualize the progress, identify the gaps,
+ and help your team to get their work done.
+- Every month, you check in with your supervisor, and show the progress of that milestone
+ from the Burndown Chart.
+- By the end of the quarter, your team successfully delivered 100% of that milestone, as
+ it was taken care of closely throughout the whole quarter.
+
+## How it works
+
+A Burndown Chart is available for every project or group milestone that has been attributed a **start
+date** and a **due date**.
+
+Find your project's **Burndown Chart** under **Project > Issues > Milestones**,
+and select a milestone from your current ones, while for group's, access the **Groups** dashboard,
+select a group, and go through **Issues > Milestones** on the sidebar.
+
+NOTE: **Note:**
+You're able to [promote project](index.md#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations.
+
+The chart indicates the project's progress throughout that milestone (for issues assigned to it).
+
+In particular, it shows how many issues were or are still open for a given day in the
+milestone's corresponding period.
+
+The Burndown Chart tracks when issues were created and when they were last closed—not their full history. For each day, it takes the number of issues still open and issues created that day and subtracts the number of issues closed that day.
+**Issues that were created and assigned a milestone before its start date—and remain open as of the start date—are considered as having been opened on the start date**. Therefore, when the milestone start date is changed the number of opened issues on each day may change.
+Reopened issues are
+considered as having been opened on the day after they were last closed.
+
+The Burndown Chart can also be toggled to display the cumulative open issue
+weight for a given day. When using this feature, make sure issue weights have
+been properly assigned, since an open issue with no weight adds zero to the
+cumulative value.
diff --git a/doc/user/project/milestones/img/burndown_chart.png b/doc/user/project/milestones/img/burndown_chart.png
new file mode 100644
index 00000000000..e06b24f9907
--- /dev/null
+++ b/doc/user/project/milestones/img/burndown_chart.png
Binary files differ
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index e6033ca8655..6cd866b5c0d 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -83,7 +83,10 @@ From the project issue/merge request list pages and the group issue/merge reques
### Filtering in issue boards
-From [project issue boards](../issue_board.md), you can filter by both group milestones and project milestones in the [search and filter bar](../../search/index.md#issue-boards).
+- From [project issue boards](../issue_board.md), you can filter by both group milestones and project milestones in the [search and filter bar](../../search/index.md#issue-boards).
+- From [group issue boards](../issue_board.md#group-issue-boards-premium), you can filter by only group milestones in the [search and filter bar](../../search/index.md#issue-boards). **[PREMIUM]**
+- From [project issue boards](../issue_board.md), you can filter by both group milestones and project milestones in the [issue board configuration](../issue_board.md#configurable-issue-boards-starter). **[STARTER]**
+- From [group issue boards](../issue_board.md#group-issue-boards-premium) you can filter by only group milestones in the [issue board configuration](../issue_board.md#configurable-issue-boards-starter). **[STARTER]**
### Special milestone filters
@@ -92,22 +95,23 @@ When filtering by milestone, in addition to choosing a specific project mileston
- **None**: Show issues or merge requests with no assigned milestone.
- **Any**: Show issues or merge requests that have an assigned milestone.
- **Upcoming**: Show issues or merge requests that have been assigned the open milestone that has the next upcoming due date (i.e. nearest due date in the future).
-- **Started**: Show issues or merge requests that have an assigned milestone with a start date that is before today.
+- **Started**: Show issues or merge requests that have an open assigned milestone with a start date that is before today.
## Milestone view
Not all features in the project milestone view are available in the group milestone view. This table summarizes the differences:
-| Feature | Project milestone view | Group milestone view |
-|---|:---:|:---:|
-| Title an description | ✓ | ✓ |
-| Issues assigned to milestone | ✓ | |
-| Merge requests assigned to milestone | ✓ | |
-| Participants and labels used | ✓ | |
-| Percentage complete | ✓ | ✓ |
-| Start date and due date | ✓ | ✓ |
-| Total issue time spent | ✓ | ✓ |
-| Total issue weight | ✓ | |
+| Feature | Project milestone view | Group milestone view |
+|--------------------------------------|:----------------------:|:--------------------:|
+| Title an description | ✓ | ✓ |
+| Issues assigned to milestone | ✓ | |
+| Merge requests assigned to milestone | ✓ | |
+| Participants and labels used | ✓ | |
+| Percentage complete | ✓ | ✓ |
+| Start date and due date | ✓ | ✓ |
+| Total issue time spent | ✓ | ✓ |
+| Total issue weight | ✓ | |
+| Burndown chart **[STARTER}** | ✓ | ✓ |
The milestone view shows the title and description.
@@ -118,12 +122,23 @@ These features are only available for project milestones and not group milestone
- Issues assigned to the milestone are displayed in three columns: Unstarted issues, ongoing issues, and completed issues.
- Merge requests assigned to the milestone are displayed in four columns: Work in progress merge requests, waiting for merge, rejected, and closed.
- Participants and labels that are used in issues and merge requests that have the milestone assigned are displayed.
+- [Burndown chart](#project-burndown-charts-starter).
+
+### Project Burndown Charts **[STARTER]**
+
+For project milestones in [GitLab Starter](https://about.gitlab.com/pricing), a [burndown chart](burndown_charts.md) is in the milestone view, showing the progress of completing a milestone.
+
+![burndown chart](img/burndown_chart.png)
+
+### Group Burndown Charts **[PREMIUM]**
+
+For group milestones in [GitLab Premium](https://about.gitlab.com/pricing), a [burndown chart](burndown_charts.md) is in the milestone view, showing the progress of completing a milestone.
### Milestone sidebar
The milestone sidebar on the milestone view shows the following:
-- Percentage complete, which is calculated as number of closed issues plus number of closed/merged merge requests divided by total number issues and merge requests.
+- Percentage complete, which is calculated as number of closed issues divided by total number of issues.
- The start date and due date.
- The total time spent on all issues that have the milestone assigned.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d7a1a69f29d..d36312c9b8d 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -44,14 +44,14 @@ It is important to note that we have a few types of users:
- **Administrators**: CI jobs created by Administrators will not have access
to all GitLab projects, but only to projects and container images of projects
- that the administrator is a member of.That means that if a project is either
+ that the administrator is a member of. That means that if a project is either
public or internal users have access anyway, but if a project is private, the
Administrator will have to be a member of it in order to have access to it
via another project's job.
-- **External users**: CI jobs created by [external users][ext] will have
+- **External users**: CI jobs created by [external users](../permissions.md#external-users-permissions) will have
access only to projects to which user has at least reporter access. This
- rules out accessing all internal projects by default,
+ rules out accessing all internal projects by default.
This allows us to make the CI and permission system more trustworthy.
Let's consider the following scenario:
@@ -60,7 +60,7 @@ Let's consider the following scenario:
hosted in private repositories and you have multiple CI jobs that make use
of these repositories.
-1. You invite a new [external user][ext]. CI jobs created by that user do not
+1. You invite a new [external user](../permissions.md#external-users-permissions). CI jobs created by that user do not
have access to internal repositories, because the user also doesn't have the
access from within GitLab. You as an employee have to grant explicit access
for this user. This allows us to prevent from accidental data leakage.
@@ -205,6 +205,7 @@ With the update permission model we also extended the support for accessing
Container Registries for private projects.
> **Notes:**
+>
> - GitLab Runner versions prior to 1.8 don't incorporate the introduced changes
> for permissions. This makes the `image:` directive to not work with private
> projects automatically and it needs to be configured manually on Runner's host
@@ -232,7 +233,6 @@ test:
[job permissions]: ../permissions.md#job-permissions
[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302
-[ext]: ../permissions.md#external-users
[gitsub]: ../../ci/git_submodules.md
[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
[triggers]: ../../ci/triggers/README.md
diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md
index 41aa5b91aa7..1b319c5641c 100644
--- a/doc/user/project/operations/error_tracking.md
+++ b/doc/user/project/operations/error_tracking.md
@@ -22,8 +22,12 @@ GitLab provides an easy way to connect Sentry to your project:
1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance.
1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project.
Make sure to give the token at least the following scopes: `event:read` and `project:read`.
-1. Navigate to your project’s **Settings > Operations** and provide the Sentry API URL and auth token.
-1. Ensure that the 'Active' checkbox is set.
+1. Navigate to your project’s **Settings > Operations**.
+1. Ensure that the **Active** checkbox is set.
+1. In the **Sentry API URL** field, enter your Sentry hostname. For example, `https://sentry.example.com`.
+1. In the **Auth Token** field, enter the token you previously generated.
+1. Click the **Connect** button to test the connection to Sentry and populate the **Project** dropdown.
+1. From the **Project** dropdown, choose a Sentry project to link to your GitLab project.
1. Click **Save changes** for the changes to take effect.
1. You can now visit **Operations > Error Tracking** in your project's sidebar to [view a list](#error-tracking-list) of Sentry errors.
diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md
new file mode 100644
index 00000000000..a5db3d70bb9
--- /dev/null
+++ b/doc/user/project/operations/feature_flags.md
@@ -0,0 +1,177 @@
+# Feature Flags **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 11.4.
+
+CAUTION: **Warning:**
+This an _alpha_ feature and is subject to change at any time without
+prior notice.
+
+Feature flags allow you to ship a project in different flavors by
+dynamically toggling certain functionality.
+
+## Overview
+
+Feature Flags offer a feature toggle system for your application. They enable teams
+to achieve Continuous Delivery by deploying new features to production at smaller
+batches for controlled testing, separating feature delivery from customer launch.
+This helps reducing risk and allows you to easily manage which features to enable.
+
+GitLab offers a Feature Flags interface that allows you to create, toggle and
+remove feature flags.
+
+## How it works
+
+Underneath, GitLab uses [unleash](https://github.com/Unleash/unleash), a feature
+toggle service. GitLab provides an API where your application can talk to and get the
+list of feature flags you set in GitLab.
+
+The application must be configured to talk to GitLab, so that's up to the
+developers to use a compatible [client library](#client-libraries) and
+integrate it in their app.
+
+By setting a flag active or inactive via GitLab, your application will automatically
+know which features to enable or disable respectively.
+
+## Adding a new feature flag
+
+To add a new feature flag:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. Click on the **New Feature Flag** button.
+1. Give it a name.
+
+ NOTE: **Note:**
+ A name can contain only lowercase letters, digits, underscores (`_`)
+ and dashes (`-`), must start with a letter, and cannot end with a dash (`-`)
+ or an underscore (`_`).
+
+1. Give it a description (optional, 255 characters max).
+1. Define environment [specs](#define-environment-specs). If you want the flag on by default, enable the catch-all [wildcard spec (`*`)](#define-environment-specs)
+1. Click **Create feature flag**.
+
+Once a feature flag is created, the list of existing feature flags will be presented
+with ability to edit or remove them.
+
+To make a feature flag active or inactive, click the pencil icon to edit it,
+and toggle the status for each [spec](#define-environment-specs).
+
+![Feature flags list](img/feature_flags_list.png)
+
+## Define environment specs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8621) in GitLab 11.8.
+
+In general, an application is deployed to multiple environments, such as
+production, staging and [review apps](../../../ci/review_apps/index.md).
+For example, you may not want to enable a feature flag on production until your QA team has
+first confirmed that the feature is working correctly on testing environments.
+
+To handle these situations, you can enable a feature flag on a particular environment
+with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs-premium).
+You can define multiple specs per flag so that you can control your feature flag more granularly.
+
+To define specs for each environment:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. Click on the **New Feature Flag** button or edit an existing flag.
+1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). This status will be used for _all_ environments.
+1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name.
+1. Set the status of the additional spec. This status takes precedence over the default spec's status since we always use the most specific match available.
+1. Click **Create feature flag** or **Update feature flag**.
+
+![Feature flag specs list](img/specs_list.png)
+
+NOTE: **NOTE**
+We'd highly recommend you to use the [Environment](../../../ci/environments.md)
+feature in order to quickly assess which flag is enabled per environment.
+
+## Integrating with your application
+
+In order to use Feature Flags, you need to first
+[get the access credentials](#configuring-feature-flags) from GitLab and then
+prepare your application and hook it with a [client library](#client-libraries).
+
+### Configuring Feature Flags
+
+To get the access credentials that your application will need to talk to GitLab:
+
+1. Navigate to your project's **Operations > Feature Flags**.
+1. Click on the **Configure** button to see the values:
+ - **API URL**: URL where the client (application) connects to get a list of feature flags.
+ - **Instance ID**: Unique token that authorizes the retrieval of the feature flags.
+ - **Application name**: The name of the running environment. For instance,
+ if the application runs for production server, application name would be
+ `production` or similar. This value is used for
+ [the environment spec evaluation](#define-environment-specs).
+
+NOTE: **Note:**
+The meaning of these fields might change over time. For example, we are not sure
+if **Instance ID** will be single token or multiple tokens, assigned to the
+**Environment**. Also, **Application name** could describe the version of
+application instead of the running environment.
+
+### Client libraries
+
+GitLab currently implements a single backend that is compatible with
+[Unleash](https://github.com/Unleash/unleash#client-implementations) clients.
+
+Unleash clients allow the developers to define in the app's code the default
+values for flags. Each feature flag evaluation can express the desired
+outcome in case the flag isn't present on the provided configuration file.
+
+Unleash currently offers a number of official SDKs for various frameworks and
+a number of community contributed libraries.
+
+Official clients:
+
+- [unleash/unleash-client-java](https://github.com/unleash/unleash-client-java)
+- [unleash/unleash-client-node](https://github.com/unleash/unleash-client-node)
+- [unleash/unleash-client-go](https://github.com/unleash/unleash-client-go)
+- [unleash/unleash-client-ruby](https://github.com/unleash/unleash-client-ruby)
+
+Community contributed clients:
+
+- [stiano/unleash-client-dotnet](https://github.com/stiano/unleash-client-dotnet) (.Net Core)
+- [onybo/unleash-client-core](https://github.com/onybo/unleash-client-core) (.Net Core)
+- [aes/unleash-client-python](https://github.com/aes/unleash-client-python) (Python 3)
+
+### Golang application example
+
+Here's an example of how to integrate the feature flags in a Golang application:
+
+```golang
+package main
+
+import (
+ "io"
+ "log"
+ "net/http"
+
+ "github.com/Unleash/unleash-client-go"
+)
+
+type metricsInterface struct {
+}
+
+func init() {
+ unleash.Initialize(
+ unleash.WithUrl("https://gitlab.com/api/v4/feature_flags/unleash/42"),
+ unleash.WithInstanceId("29QmjsW6KngPR5JNPMWx"),
+ unleash.WithAppName("production"),
+ unleash.WithListener(&metricsInterface{}),
+ )
+}
+
+func helloServer(w http.ResponseWriter, req *http.Request) {
+ if unleash.IsEnabled("my_feature_name") {
+ io.WriteString(w, "Feature enabled\n")
+ } else {
+ io.WriteString(w, "hello, world!\n")
+ }
+}
+
+func main() {
+ http.HandleFunc("/", helloServer)
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
+```
diff --git a/doc/user/project/operations/img/feature_flags_list.png b/doc/user/project/operations/img/feature_flags_list.png
new file mode 100644
index 00000000000..5313a163fec
--- /dev/null
+++ b/doc/user/project/operations/img/feature_flags_list.png
Binary files differ
diff --git a/doc/user/project/operations/img/specs_list.png b/doc/user/project/operations/img/specs_list.png
new file mode 100644
index 00000000000..9630c907cfc
--- /dev/null
+++ b/doc/user/project/operations/img/specs_list.png
Binary files differ
diff --git a/doc/user/project/operations/index.md b/doc/user/project/operations/index.md
index b0f9936be5c..0086c15c98a 100644
--- a/doc/user/project/operations/index.md
+++ b/doc/user/project/operations/index.md
@@ -7,5 +7,5 @@ your applications:
- Deploy to different [environments](../../../ci/environments.md).
- Connect your project to a [Kubernetes cluster](../clusters/index.md).
- Discover and view errors generated by your applications with [Error Tracking](error_tracking.md).
-- Create, toggle, and remove [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html). **[PREMIUM]**
-- [Trace](https://docs.gitlab.com/ee/user/project/operations/tracing.html) the performance and health of a deployed application. **[ULTIMATE]**
+- Create, toggle, and remove [Feature Flags](feature_flags.md). **[PREMIUM]**
+- [Trace](tracing.md) the performance and health of a deployed application. **[ULTIMATE]**
diff --git a/doc/user/project/operations/tracing.md b/doc/user/project/operations/tracing.md
new file mode 100644
index 00000000000..0416e096cf2
--- /dev/null
+++ b/doc/user/project/operations/tracing.md
@@ -0,0 +1,34 @@
+# Tracing **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7903) in GitLab Ultimate 11.5.
+
+Tracing provides insight into the performance and health of a deployed application,
+tracking each function or microservice which handles a given request.
+
+This makes it easy to
+understand the end-to-end flow of a request, regardless of whether you are using a monolithic or distributed system.
+
+## Jaeger tracing
+
+[Jaeger](https://www.jaegertracing.io/) is an open source, end-to-end distributed
+tracing system used for monitoring and troubleshooting microservices-based distributed
+systems.
+
+### Deploying Jaeger
+
+To learn more about deploying Jaeger, read the official
+[Getting Started documentation](https://www.jaegertracing.io/docs/latest/getting-started/).
+There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/latest/getting-started/#AllinoneDockerimage),
+as well as deployment options for [Kubernetes](https://github.com/jaegertracing/jaeger-kubernetes)
+and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
+
+### Enabling Jaeger
+
+GitLab provides an easy way to open the Jaeger UI from within your project:
+
+1. [Set up Jaeger](#deploying-jaeger) and configure your application using one of the
+ [client libraries](https://www.jaegertracing.io/docs/latest/client-libraries/).
+1. Navigate to your project's **Settings > Operations** and provide the Jaeger URL.
+1. Click **Save changes** for the changes to take effect.
+1. You can now visit **Operations > Tracing** in your project's sidebar and
+ GitLab will redirect you to the configured Jaeger URL. \ No newline at end of file
diff --git a/doc/user/project/packages/img/maven_package_view.png b/doc/user/project/packages/img/maven_package_view.png
new file mode 100644
index 00000000000..2eb4b6f76b4
--- /dev/null
+++ b/doc/user/project/packages/img/maven_package_view.png
Binary files differ
diff --git a/doc/user/project/packages/img/npm_package_view.png b/doc/user/project/packages/img/npm_package_view.png
new file mode 100644
index 00000000000..8baf7d0ef9f
--- /dev/null
+++ b/doc/user/project/packages/img/npm_package_view.png
Binary files differ
diff --git a/doc/user/project/packages/maven.md b/doc/user/project/packages/maven.md
new file mode 100644
index 00000000000..266225fdb8d
--- /dev/null
+++ b/doc/user/project/packages/maven.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'maven_repository.md'
+---
+
+This document was moved to [another location](maven_repository.md).
diff --git a/doc/user/project/packages/maven_packages.md b/doc/user/project/packages/maven_packages.md
new file mode 100644
index 00000000000..266225fdb8d
--- /dev/null
+++ b/doc/user/project/packages/maven_packages.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'maven_repository.md'
+---
+
+This document was moved to [another location](maven_repository.md).
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
new file mode 100644
index 00000000000..9b7af738696
--- /dev/null
+++ b/doc/user/project/packages/maven_repository.md
@@ -0,0 +1,341 @@
+# GitLab Maven Repository **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5811) in
+ [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
+
+With the GitLab [Maven](https://maven.apache.org) Repository, every
+project can have its own space to store its Maven artifacts.
+
+![GitLab Maven Repository](img/maven_package_view.png)
+
+## Enabling the Maven Repository
+
+NOTE: **Note:**
+This option is available only if your GitLab administrator has
+[enabled support for the Maven repository](../../../administration/packages.md).**[PREMIUM ONLY]**
+
+After the Packages feature is enabled, the Maven Repository will be available for
+all new projects by default. To enable it for existing projects, or if you want
+to disable it:
+
+1. Navigate to your project's **Settings > General > Permissions**.
+1. Find the Packages feature and enable or disable it.
+1. Click on **Save changes** for the changes to take effect.
+
+You should then be able to see the **Packages** section on the left sidebar.
+Next, you must configure your project to authorize with the GitLab Maven
+repository.
+
+## Authenticating to the GitLab Maven Repository
+
+If a project is private or you want to upload Maven artifacts to GitLab,
+credentials will need to be provided for authorization. Support is available for
+[personal access tokens](#authenticating-with-a-personal-access-token) and
+[CI job tokens](#authenticating-with-a-ci-job-token) only.
+[Deploy tokens](../deploy_tokens/index.md) and regular username/password
+credentials do not work.
+
+### Authenticating with a personal access token
+
+To authenticate with a [personal access token](../../profile/personal_access_tokens.md),
+add a corresponding section to your
+[`settings.xml`](https://maven.apache.org/settings.html) file:
+
+```xml
+<settings>
+ <servers>
+ <server>
+ <id>gitlab-maven</id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name>Private-Token</name>
+ <value>REPLACE_WITH_YOUR_PERSONAL_ACCESS_TOKEN</value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+ </servers>
+</settings>
+```
+
+You should now be able to upload Maven artifacts to your project.
+
+### Authenticating with a CI job token
+
+If you're using Maven with GitLab CI/CD, a CI job token can be used instead
+of a personal access token.
+
+To authenticate with a CI job token, add a corresponding section to your
+[`settings.xml`](https://maven.apache.org/settings.html) file:
+
+```xml
+<settings>
+ <servers>
+ <server>
+ <id>gitlab-maven</id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name>Job-Token</name>
+ <value>${env.CI_JOB_TOKEN}</value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+ </servers>
+</settings>
+```
+
+You can read more on
+[how to create Maven packages using GitLab CI/CD](#creating-maven-packages-with-gitlab-cicd).
+
+## Configuring your project to use the GitLab Maven repository URL
+
+To download and upload packages from GitLab, you need a `repository` and
+`distributionManagement` section in your `pom.xml` file.
+
+Depending on your workflow and the amount of Maven packages you have, there are
+3 ways you can configure your project to use the GitLab endpoint for Maven packages:
+
+- **Project level**: Useful when you have few Maven packages which are not under
+ the same GitLab group.
+- **Group level**: Useful when you have many Maven packages under the same GitLab
+ group.
+- **Instance level**: Useful when you have many Maven packages under different
+ GitLab groups or on their own namespace.
+
+NOTE: **Note:**
+In all cases, you need a project specific URL for uploading a package in
+the `distributionManagement` section.
+
+### Project level Maven endpoint
+
+The example below shows how the relevant `repository` section of your `pom.xml`
+would look like:
+
+```xml
+<repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </repository>
+</repositories>
+<distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </snapshotRepository>
+</distributionManagement>
+```
+
+The `id` must be the same with what you
+[defined in `settings.xml`](#authenticating-to-the-gitlab-maven-repository).
+
+Replace `PROJECT_ID` with your project ID which can be found on the home page
+of your project.
+
+If you have a self-hosted GitLab installation, replace `gitlab.com` with your
+domain name.
+
+NOTE: **Note:**
+For retrieving artifacts, you can use either the
+[URL encoded](../../../api/README.md#namespaced-path-encoding) path of the project
+(e.g., `group%2Fproject`) or the project's ID (e.g., `42`). However, only the
+project's ID can be used for uploading.
+
+### Group level Maven endpoint
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8798) in GitLab Premium 11.7.
+
+If you rely on many packages, it might be inefficient to include the `repository` section
+with a unique URL for each package. Instead, you can use the group level endpoint for
+all your Maven packages stored within one GitLab group. Only packages you have access to
+will be available for download.
+
+The group level endpoint works with any package names, which means the you
+have the flexibility of naming compared to [instance level endpoint](#instance-level-maven-endpoint).
+However, GitLab will not guarantee the uniqueness of the package names within
+the group. You can have two projects with the same package name and package
+version. As a result, GitLab will serve whichever one is more recent.
+
+The example below shows how the relevant `repository` section of your `pom.xml`
+would look like. You still need a project specific URL for uploading a package in
+the `distributionManagement` section:
+
+```xml
+<repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/groups/my-group/-/packages/maven</url>
+ </repository>
+</repositories>
+<distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </snapshotRepository>
+</distributionManagement>
+```
+
+The `id` must be the same with what you
+[defined in `settings.xml`](#authenticating-to-the-gitlab-maven-repository).
+
+Replace `my-group` with your group name and `PROJECT_ID` with your project ID
+which can be found on the home page of your project.
+
+If you have a self-hosted GitLab installation, replace `gitlab.com` with your
+domain name.
+
+NOTE: **Note:**
+For retrieving artifacts, you can use either the
+[URL encoded](../../../api/README.md#namespaced-path-encoding) path of the group
+(e.g., `group%2Fsubgroup`) or the group's ID (e.g., `12`).
+
+### Instance level Maven endpoint
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/8274) in GitLab Premium 11.7.
+
+If you rely on many packages, it might be inefficient to include the `repository` section
+with a unique URL for each package. Instead, you can use the instance level endpoint for
+all maven packages stored in GitLab and the packages you have access to will be available
+for download.
+
+Note that **only packages that have the same path as the project** are exposed via
+the instance level endpoint.
+
+| Project | Package | Instance level endpoint available |
+| ------- | ------- | --------------------------------- |
+| `foo/bar` | `foo/bar/1.0-SNAPSHOT` | Yes |
+| `gitlab-org/gitlab-ce` | `foo/bar/1.0-SNAPSHOT` | No |
+| `gitlab-org/gitlab-ce` | `gitlab-org/gitlab-ce/1.0-SNAPSHOT` | Yes |
+
+The example below shows how the relevant `repository` section of your `pom.xml`
+would look like. You still need a project specific URL for uploading a package in
+the `distributionManagement` section:
+
+```xml
+<repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/packages/maven</url>
+ </repository>
+</repositories>
+<distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven</url>
+ </snapshotRepository>
+</distributionManagement>
+```
+
+The `id` must be the same with what you
+[defined in `settings.xml`](#authenticating-to-the-gitlab-maven-repository).
+
+Replace `PROJECT_ID` with your project ID which can be found on the home page
+of your project.
+
+If you have a self-hosted GitLab installation, replace `gitlab.com` with your
+domain name.
+
+NOTE: **Note:**
+For retrieving artifacts, you can use either the
+[URL encoded](../../../api/README.md#namespaced-path-encoding) path of the project
+(e.g., `group%2Fproject`) or the project's ID (e.g., `42`). However, only the
+project's ID can be used for uploading.
+
+## Uploading packages
+
+Once you have set up the [authentication](#authenticating-to-the-gitlab-maven-repository)
+and [configuration](#configuring-your-project-to-use-the-gitlab-maven-repository-url),
+test to upload a Maven artifact from a project of yours:
+
+```sh
+mvn deploy
+```
+
+You can then navigate to your project's **Packages** page and see the uploaded
+artifacts or even delete them.
+
+## Creating Maven packages with GitLab CI/CD
+
+Once you have your repository configured to use the GitLab Maven Repository,
+you can configure GitLab CI/CD to build new packages automatically. The example below
+shows how to create a new package each time the `master` branch is updated:
+
+1. Create a `ci_settings.xml` file that will serve as Maven's `settings.xml` file.
+ Add the server section with the same id you defined in your `pom.xml` file.
+ For example, in our case it's `gitlab-maven`:
+
+ ```xml
+ <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
+ <servers>
+ <server>
+ <id>gitlab-maven</id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name>Job-Token</name>
+ <value>${env.CI_JOB_TOKEN}</value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+ </servers>
+ </settings>
+ ```
+
+1. Make sure your `pom.xml` file includes the following:
+
+ ```xml
+ <repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </repository>
+ </repositories>
+ <distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </snapshotRepository>
+ </distributionManagement>
+ ```
+
+ TIP: **Tip:**
+ You can either let Maven utilize the CI environment variables or hardcode your project's ID.
+
+1. Add a `deploy` job to your `.gitlab-ci.yml` file:
+
+ ```yaml
+ deploy:
+ image: maven:3.3.9-jdk-8
+ script:
+ - 'mvn deploy -s ci_settings.xml'
+ only:
+ - master
+ ```
+
+1. Push those files to your repository.
+
+The next time the `deploy` job runs, it will copy `ci_settings.xml` to the
+user's home location (in this case the user is `root` since it runs in a
+Docker container), and Maven will utilize the configured CI
+[environment variables](../../../ci/variables/README.md#predefined-environment-variables).
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
new file mode 100644
index 00000000000..2e274573434
--- /dev/null
+++ b/doc/user/project/packages/npm_registry.md
@@ -0,0 +1,120 @@
+# GitLab NPM Registry **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934)
+ in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
+
+With the GitLab NPM Registry, every
+project can have its own space to store NPM packages.
+
+![GitLab NPM Registry](img/npm_package_view.png)
+
+NOTE: **Note:**
+Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported.
+
+
+NOTE: **Note:**
+As `@group/subgroup/project` is not a valid NPM package name, publishing a package
+within a subgroup is not supported yet.
+
+## Enabling the NPM Registry
+
+NOTE: **Note:**
+This option is available only if your GitLab administrator has
+[enabled support for the NPM registry](../../../administration/packages.md).**[PREMIUM ONLY]**
+
+After the NPM registry is enabled, it will be available for all new projects
+by default. To enable it for existing projects, or if you want to disable it:
+
+1. Navigate to your project's **Settings > General > Permissions**.
+1. Find the Packages feature and enable or disable it.
+1. Click on **Save changes** for the changes to take effect.
+
+You should then be able to see the **Packages** section on the left sidebar.
+
+Before proceeding to authenticating with the GitLab NPM Registry, you should
+get familiar with the package naming convention.
+
+## Package naming convention
+
+**Only packages that have the same path as the project** are supported. For
+ example:
+
+| Project | Package | Supported |
+| ---------------------- | ----------------------- | --------- |
+| `foo/bar` | `@foo/bar` | Yes |
+| `gitlab-org/gitlab-ce` | `@gitlab-org/gitlab-ce` | Yes |
+| `gitlab-org/gitlab-ce` | `@foo/bar` | No |
+
+Now, you can configure your project to authenticate with the GitLab NPM
+Registry.
+
+## Authenticating to the GitLab NPM Registry
+
+If a project is private or you want to upload an NPM package to GitLab,
+credentials will need to be provided for authentication. Support is available
+only for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow).
+
+CAUTION: **2FA not supported:**
+Authentication for personal access tokens is not yet supported
+([#9140](https://gitlab.com/gitlab-org/gitlab-ee/issues/9140)). If you have 2FA
+enabled, you won't be able to authenticate to the GitLab NPM Registry.
+
+### Authenticating with an OAuth token
+
+To authenticate with an [OAuth token](../../../api/oauth2.md#resource-owner-password-credentials-flow),
+add a corresponding section to your `.npmrc` file:
+
+```ini
+; Set URL for your scoped packages.
+; For example package with name `@foo/bar` will use this URL for download
+@foo:registry=https://gitlab.com/api/v4/packages/npm/
+
+; Add the OAuth token for the scoped packages URL. This will allow you to download
+; `@foo/` packages from private projects.
+//gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token>
+
+; Add OAuth token for uploading to the registry. Replace <your_project_id>
+; with the project you want your package to be uploaded to.
+//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_oauth_token>
+```
+
+Replace `<your_project_id>` with your project ID which can be found on the home page
+of your project and `<your_oauth_token>` with your OAuth token.
+
+If you have a self-hosted GitLab installation, replace `gitlab.com` with your
+domain name.
+
+You should now be able to download and upload NPM packages to your project.
+
+## Uploading packages
+
+Before you will be able to upload a package, you need to specify the registry
+for NPM. To do this, add the following section to the bottom of `package.json`:
+
+```json
+ "publishConfig": {
+ "@foo:registry":"https://gitlab.com/api/v4/projects/<your_project_id>/packages/npm/"
+ }
+```
+
+Replace `<your_project_id>` with your project ID, which can be found on the home
+page of your project, and replace `@foo` with your own scope.
+
+If you have a self-hosted GitLab installation, replace `gitlab.com` with your
+domain name.
+
+Once you have enabled it and set up [authentication](#authenticating-to-the-gitlab-npm-registry),
+you can upload an NPM package to your project:
+
+```sh
+npm publish
+```
+
+You can then navigate to your project's **Packages** page and see the uploaded
+packages or even delete them.
+
+## Uploading a package with the same version twice
+
+If you upload a package with a same name and version twice, GitLab will show
+both packages in the UI, but the GitLab NPM Registry will expose the most recent
+one as it supports only one package per version for `npm install`.
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 05d5a2fd99a..8baf41dba78 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -1,22 +1,30 @@
---
-last_updated: 2018-02-16
-author: Marcia Ramos
-author_gitlab: marcia
-level: intermediate
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: reference, howto
---
# Creating and Tweaking GitLab CI/CD for GitLab Pages
-[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves
+To [get started with GitLab Pages](index.md#getting-started), you can
+use one of the project templates, a `.gitlab-ci.yml` template,
+or fork an existing example project. Therefore, you don't need to
+understand _all_ the ins and odds of GitLab CI/CD to get your site
+deployed. Still, there are cases where you want to write your own
+script or tweak an existing one. This document guides you through
+this process.
+
+This guide also provides a general overview and clear introduction
+for **getting familiar with the `.gitlab-ci.yml` file and writing
+one for the first time.**
+
+[GitLab CI/CD](../../../ci/README.md) serves
numerous purposes, to build, test, and deploy your app
from GitLab through
-[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+[Continuous Integration, Continuous Delivery, and Continuous Deployment](../../../ci/introduction/index.md#introduction-to-cicd-methodologies)
methods. You will need it to build your website with GitLab Pages,
and deploy it to the Pages server.
-To implement GitLab CI/CD, the first thing we need is a configuration
+To implement GitLab CI/CD, the first thing you need is a configuration
file called `.gitlab-ci.yml` placed at your website's root directory.
What this file actually does is telling the
@@ -26,7 +34,7 @@ terminal. GitLab CI/CD tells the Runner which commands to run.
Both are built-in in GitLab, and you don't need to set up
anything for them to work.
-Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
+Explaining [every detail of GitLab CI/CD](../../../ci/yaml/README.md)
and GitLab Runner is out of the scope of this guide, but we'll
need to understand just a few things to be able to write our own
`.gitlab-ci.yml` or tweak an existing one. It's an
@@ -42,7 +50,7 @@ Of course, before building it, you had to install Jekyll in your computer.
For that, you had to open your terminal and run `gem install jekyll`.
Right? GitLab CI + GitLab Runner do the same thing. But you need to
write in the `.gitlab-ci.yml` the script you want to run so
-GitLab Runner will do it for you. It looks more complicated then it
+GitLab Runner will do it for you. It looks more complicated than it
is. What you need to tell the Runner:
```
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 9a95fb70964..6d538ca2455 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -1,42 +1,12 @@
---
-last_updated: 2018-02-16
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
+last_updated: 2018-06-04
+type: concepts, reference
---
# Static sites and GitLab Pages domains
-This document is the beginning of a comprehensive guide, made for those who want to
-publish a website with GitLab Pages but aren't familiar with
-the entire process involved.
-
-This [first document](#what-you-need-to-know-before-getting-started) of this series will present you to the concepts of
-static sites, and go over how the default Pages domains work.
-
-The [second document](getting_started_part_two.md) covers how to get started with GitLab Pages: deploy
-a website from a forked project or create a new one from scratch.
-
-The [third document](getting_started_part_three.md) will show you how to set up a custom domain or subdomain
-to your site already deployed.
-
-The [fourth document](getting_started_part_four.md) will show you how to create and tweak GitLab CI for
-GitLab Pages.
-
-To **enable** GitLab Pages for GitLab CE (Community Edition)
-and GitLab EE (Enterprise Edition), please read the
-[admin documentation](https://docs.gitlab.com/ce/administration/pages/index.html),
-and/or watch this [video tutorial](https://youtu.be/dD8c7WNcc6s).
-
->**Note:**
-For this guide, we assume you already have GitLab Pages
-server up and running for your GitLab instance.
-
-## What you need to know before getting started
-
-Before we begin, let's understand a few concepts first.
+On this docucument, learn how to name your project for GitLab Pages
+according to your intended website's URL.
## Static sites
@@ -48,20 +18,10 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
to simplify your code and build the static site for you,
which is highly recommendable and much faster than hardcoding.
-### Further reading
-
-- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
-- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
-- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- Fork an [example project](https://gitlab.com/pages) to build your website based upon
-
-## GitLab Pages domain
+See the [further reading](#further-reading) section below for
+references on static site concepts.
-If you set up a GitLab Pages project on GitLab.com,
-it will automatically be accessible under a
-[subdomain of `namespace.gitlab.io`](introduction.md#gitlab-pages-on-gitlab-com).
-The `namespace` is defined by your username on GitLab.com,
-or the group name you created this project under.
+## GitLab Pages domain names
>**Note:**
If you use your own GitLab instance to deploy your
@@ -70,11 +30,32 @@ Pages wildcard domain. This guide is valid for any GitLab instance,
you just need to replace Pages wildcard domain on GitLab.com
(`*.gitlab.io`) with your own.
-Learn more about [namespaces](../../group/index.md#namespaces).
+If you set up a GitLab Pages project on GitLab,
+it will automatically be accessible under a
+subdomain of `namespace.example.io`.
+The [`namespace`](../../group/index.md#namespaces)
+is defined by your username on GitLab.com,
+or the group name you created this project under.
+For GitLab self-managed instances, replace `example.io`
+with your instance's Pages domain. For GitLab.com,
+Pages domains are `*.gitlab.io`.
+
+| Type of GitLab Pages | The name of the project created in GitLab | Website URL |
+| -------------------- | ------------ | ----------- |
+| User pages | `username.example.io` | `http(s)://username.example.io` |
+| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
+| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
+| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
+| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
+
+CAUTION: **Warning:**
+There are some known [limitations](introduction.md#limitations)
+regarding namespaces served under the general domain name and HTTPS.
+Make sure to read that section.
-### Practical examples
+To understand Pages domains clearly, read the examples below.
-#### Project Websites
+### Project website examples
- You created a project called `blog` under your username `john`,
therefore your project URL is `https://gitlab.com/john/blog/`.
@@ -92,7 +73,7 @@ Learn more about [namespaces](../../group/index.md#namespaces).
GitLab Pages for this project, the site will live under
`https://engineering.gitlab.io/docs/workflows`.
-#### User and Group Websites
+### User and Group website examples
- Under your username, `john`, you created a project called
`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
@@ -103,8 +84,6 @@ Learn more about [namespaces](../../group/index.md#namespaces).
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
-> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
-
**General example:**
- On GitLab.com, a project site will always be available under
@@ -115,3 +94,10 @@ Learn more about [namespaces](../../group/index.md#namespaces).
Pages server domain. Ask your sysadmin for this information.
_Read on about [Projects for GitLab Pages and URL structure](getting_started_part_two.md)._
+
+### Further reading
+
+- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
+- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
+- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+- Fork an [example project](https://gitlab.com/pages) to build your website based upon \ No newline at end of file
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index daae2f4b5a3..d585c19fc5c 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -1,10 +1,6 @@
---
-last_updated: 2018-11-19
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: concepts, reference, howto
---
# GitLab Pages custom domains and SSL/TLS Certificates
@@ -20,13 +16,13 @@ you should consider first, which we'll cover in this guide:
1. Either if you're adding a **root domain** or a **subdomain**, for which
you'll need to set up [DNS records](#dns-records)
-1. Whether you want to add an [SSL/TLS certificate](#ssl-tls-certificates) or not
+1. Whether you want to add an [SSL/TLS certificate](#ssltls-certificates) or not
To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings).
Let's start from the beginning with [DNS records](#dns-records).
If you already know how they work and want to skip the introduction to DNS,
-you may be interested in skipping it until the [TL;DR](#tl-dr) section below.
+you may be interested in skipping it until the [TL;DR](#tldr) section below.
### DNS Records
@@ -96,7 +92,7 @@ you need to log into your domain's admin control panel and add a DNS
`CNAME` record pointing your subdomain to your website URL
(`namespace.gitlab.io`) address.
-Notice that, despite it's a user or project website, the `CNAME`
+Note that, whether it's a user or a project website, the `CNAME`
should point to your Pages domain (`namespace.gitlab.io`),
without any `/project-name`.
@@ -115,6 +111,8 @@ If using a [DNS A record](#dns-a-record), you can place the TXT record directly
under the domain. If using a [DNS CNAME record](#dns-cname-record), the two record types won't
co-exist, so you need to place the TXT record in a special subdomain of its own.
+If the domain cannot be verified for 7 days, it will be removed from the GitLab project.
+
#### TL;DR
For root domains (`domain.com`), set a DNS `A` record and verify your
@@ -148,8 +146,8 @@ verify your domain's ownership with a TXT record:
Once you've set the DNS record, you'll need navigate to your project's
**Setting > Pages** and click **+ New domain** to add your custom domain to
-GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssl-tls-certificates)
-to make your website accessible under HTTPS or leave it blank. If don't add a certificate,
+GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssltls-certificates)
+to make your website accessible under HTTPS or leave it blank. If you don't add a certificate,
your site will be accessible only via HTTP:
![Add new domain](img/add_certificate_to_pages.png)
@@ -175,9 +173,6 @@ Note that [DNS propagation may take some time (up to 24h)](http://www.inmotionho
although it's usually a matter of minutes to complete. Until it does, verification
will fail and attempts to visit your domain will respond with a 404.
-Read through the [general documentation on GitLab Pages](introduction.md#add-a-custom-domain-to-your-pages-website) to learn more about adding
-custom domains to GitLab Pages sites.
-
### Redirecting `www.domain.com` to `domain.com` with Cloudflare
If you use Cloudflare, you can redirect `www` to `domain.com` without the need of adding both
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 644a1c951d3..3e50cd4887c 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -1,10 +1,6 @@
---
-last_updated: 2018-02-16
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: reference, howto
---
# Projects for GitLab Pages and URL structure
@@ -13,16 +9,16 @@ date: 2017-02-22
To get started with GitLab Pages, you need:
-1. A project
-1. A configuration file (`.gitlab-ci.yml`) to deploy your site
+1. A project, thus a repository to hold your website's codebase.
+1. A configuration file (`.gitlab-ci.yml`) to deploy your site.
1. A specific `job` called `pages` in the configuration file
- that will make GitLab aware that you are deploying a GitLab Pages website
-1. A `public` directory with the content of the website
+ that will make GitLab aware that you are deploying a GitLab Pages website.
+1. A `public` directory with the static content of the website.
Optional Features:
-1. A custom domain or subdomain
-1. A DNS pointing your (sub)domain to your Pages site
+1. A custom domain or subdomain.
+1. A DNS pointing your (sub)domain to your Pages site.
1. **Optional**: an SSL/TLS certificate so your custom
domain is accessible under HTTPS.
@@ -33,60 +29,80 @@ The optional settings, custom domain, DNS records, and SSL/TLS certificates, are
Your GitLab Pages project is a regular project created the
same way you do for the other ones. To get started with GitLab Pages, you have three ways:
-- Use one of the popular templates already in the app,
-- Fork one of the templates from Page Examples, or
-- Create a new project from scratch
-
-Let's go over each option.
+- [Use one of the popular project templates bundled with GitLab](#use-one-of-the-popular-pages-templates-bundled-with-gitlab).
+- [Fork one of the templates from Page Examples](#fork-a-project-to-get-started-from).
+- [Create a new project from scratch](#create-a-project-from-scratch).
### Use one of the popular Pages templates bundled with GitLab
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47857)
in GitLab 11.8.
-The simplest way to create a GitLab Pages site is to use one of the most
-popular templates, which come already bundled and ready to go. To use one
-of these templates:
-
-1. From the top navigation, click the **+** button and select **New project**
-1. Select **Create from Template**
-1. Choose one of the templates starting with **Pages**
+The simplest way to create a GitLab Pages site is to
+[use one of the most popular templates](index.md#getting-started),
+which come already bundled with GitLab and are ready to go.
### Fork a project to get started from
-To make things easy for you, we've created this
-[group](https://gitlab.com/pages) of default projects
-containing the most popular SSGs templates.
-
-Watch the [video tutorial](https://youtu.be/TWqh9MtT4Bg) we've
-created for the steps below.
-
-1. [Fork a sample project](../../../gitlab-basics/fork-project.md) from the [Pages group](https://gitlab.com/pages)
-1. Trigger a build (push a change to any file)
-1. As soon as the build passes, your website will have been deployed with GitLab Pages. Your website URL will be available under your project's **Settings** > **Pages**
-1. Optionally, remove the fork relationship by navigating to your project's **Settings** > expanding **Advanced settings** and scrolling down to **Remove fork relationship**:
+If you don't find an existing project template that suits you,
+we've created this [group](https://gitlab.com/pages) of default projects
+containing the most popular SSGs templates to get you started.
+
+<table class="borderless-table center fixed-table middle width-80">
+ <tr>
+ <td style="width: 30%"><img src="img/icons/fork.png" alt="Fork" class="image-noshadow half-width"></td>
+ <td style="width: 10%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 30%"><img src="img/icons/terminal.png" alt="Deploy" class="image-noshadow half-width"></td>
+ <td style="width: 10%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 30%"><img src="img/icons/click.png" alt="Visit" class="image-noshadow half-width"></td>
+ </tr>
+ <tr>
+ <td><em>Fork an example project</em></td>
+ <td></td>
+ <td><em>Deploy your website</em></td>
+ <td></td>
+ <td><em>Visit your website's URL</em></td>
+ </tr>
+</table>
+
+**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) with all the steps below.**
+
+1. [Fork](../../../gitlab-basics/fork-project.md) a sample project from the [GitLab Pages examples](https://gitlab.com/pages) group.
+1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
+and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
+site to the server.
+1. Once the pipeline has finished successfully, find the link to visit your
+website from your project's **Settings > Pages**.
+
+You can also take some **optional** further steps:
+
+- _Remove the fork relationship._ The fork relationship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
![remove fork relationship](img/remove_fork_relationship.png)
-To turn a **project website** forked from the Pages group into a **user/group** website, you'll need to:
-
-- Rename it to `namespace.gitlab.io`: navigate to project's **Settings** > expand **Advanced settings** > and scroll down to **Rename repository**
-- Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likely, it will be in the SSG's config file.
-
-> **Notes:**
->
-> Why do I need to remove the fork relationship?
->
-> Unless you want to contribute to the original project,
-you won't need it connected to the upstream. A
-[fork](https://about.gitlab.com/2016/12/01/how-to-keep-your-fork-up-to-date-with-its-origin/#fork)
-is useful for submitting merge requests to the upstream.
+- _Make it a user or group website._ To turn a **project website** forked
+from the Pages group into a **user/group** website, you'll need to:
+ - Rename it to `namespace.gitlab.io`: go to your project's
+ **Settings > General** and expand **Advanced**. Scroll down to
+ **Rename repository** and change the path to `namespace.gitlab.io`.
+ - Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to
+ `""`. This setting will be at a different place for each SSG, as each of them
+ have their own structure and file tree. Most likely, it will be in the SSG's
+ config file.
### Create a project from scratch
1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**,
- click **New project**, and name it considering the
- [practical examples](getting_started_part_one.md#practical-examples).
+ click **New project**, and name it according to the
+ [Pages domain names](getting_started_part_one.md#gitlab-pages-domain-names).
1. Clone it to your local computer, add your website
files to your project, add, commit and push to GitLab.
1. From the your **Project**'s page, click **Set up CI/CD**:
@@ -120,7 +136,7 @@ where you'll find its default URL.
repository to you local computer and moving your site files into it,
you can run `git init` in your local website directory, add the
remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
- then add, commit, and push.
+ then add, commit, and push to GitLab.
## URLs and Baseurls
@@ -153,4 +169,4 @@ baseurl: ""
## Custom Domains
GitLab Pages supports custom domains and subdomains, served under HTTP or HTTPS.
-Please check the [next part](getting_started_part_three.md) of this series for an overview.
+See [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md) for more information.
diff --git a/doc/user/project/pages/img/icons/click.png b/doc/user/project/pages/img/icons/click.png
index a534ae29e0f..62a997a7591 100644
--- a/doc/user/project/pages/img/icons/click.png
+++ b/doc/user/project/pages/img/icons/click.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/fork.png b/doc/user/project/pages/img/icons/fork.png
index 8a3aa46eb37..1ae8fa722b7 100644
--- a/doc/user/project/pages/img/icons/fork.png
+++ b/doc/user/project/pages/img/icons/fork.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/free.png b/doc/user/project/pages/img/icons/free.png
index ae455033e94..c74cd90fa1a 100644
--- a/doc/user/project/pages/img/icons/free.png
+++ b/doc/user/project/pages/img/icons/free.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/lock.png b/doc/user/project/pages/img/icons/lock.png
index f4c35c84112..f7f32fded45 100644
--- a/doc/user/project/pages/img/icons/lock.png
+++ b/doc/user/project/pages/img/icons/lock.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/monitor.png b/doc/user/project/pages/img/icons/monitor.png
index 8bad059a74c..ce27b7e177e 100644
--- a/doc/user/project/pages/img/icons/monitor.png
+++ b/doc/user/project/pages/img/icons/monitor.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/terminal.png b/doc/user/project/pages/img/icons/terminal.png
index 377eeb4edc6..5a711f05c4e 100644
--- a/doc/user/project/pages/img/icons/terminal.png
+++ b/doc/user/project/pages/img/icons/terminal.png
Binary files differ
diff --git a/doc/user/project/pages/img/pages_create_project.png b/doc/user/project/pages/img/pages_create_project.png
deleted file mode 100644
index 69e84b84984..00000000000
--- a/doc/user/project/pages/img/pages_create_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_create_user_page.png b/doc/user/project/pages/img/pages_create_user_page.png
deleted file mode 100644
index 2f1a19ae424..00000000000
--- a/doc/user/project/pages/img/pages_create_user_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_dns_details.png b/doc/user/project/pages/img/pages_dns_details.png
deleted file mode 100644
index 3e57f43f7ba..00000000000
--- a/doc/user/project/pages/img/pages_dns_details.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_multiple_domains.png b/doc/user/project/pages/img/pages_multiple_domains.png
deleted file mode 100644
index 76c39101439..00000000000
--- a/doc/user/project/pages/img/pages_multiple_domains.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_new_domain_button.png b/doc/user/project/pages/img/pages_new_domain_button.png
deleted file mode 100644
index cd59defa006..00000000000
--- a/doc/user/project/pages/img/pages_new_domain_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_project_templates_11-8.png b/doc/user/project/pages/img/pages_project_templates_11-8.png
new file mode 100644
index 00000000000..a645d28260b
--- /dev/null
+++ b/doc/user/project/pages/img/pages_project_templates_11-8.png
Binary files differ
diff --git a/doc/user/project/pages/img/pages_remove.png b/doc/user/project/pages/img/pages_remove.png
deleted file mode 100644
index 10299880247..00000000000
--- a/doc/user/project/pages/img/pages_remove.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/pages_upload_cert.png b/doc/user/project/pages/img/pages_upload_cert.png
deleted file mode 100644
index 64e5f8eced1..00000000000
--- a/doc/user/project/pages/img/pages_upload_cert.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/remove_pages.png b/doc/user/project/pages/img/remove_pages.png
new file mode 100644
index 00000000000..60f76f15f93
--- /dev/null
+++ b/doc/user/project/pages/img/remove_pages.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index e0b78753e21..04bda212128 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -1,15 +1,24 @@
---
description: 'Learn how to use GitLab Pages to deploy a static website at no additional cost.'
+last_updated: 2019-06-04
+type: index, reference
---
# GitLab Pages
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80) in GitLab Enterprise Edition 8.3.
+> - Custom CNAMEs with TLS support were [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173) in GitLab Enterprise Edition 8.5.
+> - [Ported](https://gitlab.com/gitlab-org/gitlab-ce/issues/14605) to GitLab Community Edition in GitLab 8.17.
+> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
+> - Bundled project templates were [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47857) in GitLab 11.8.
+
+
**GitLab Pages is a feature that allows you to publish static websites
directly from a repository in GitLab.**
You can use it either for personal or business websites, such as
-portfolios, documentation, manifestos, and business presentations,
-and attribute any license to your content.
+portfolios, documentation, manifestos, and business presentations.
+You can also attribute any license to your content.
<table class="borderless-table center fixed-table">
<tr>
@@ -61,14 +70,6 @@ publish any website written directly in plain HTML, CSS, and JavaScript.</p>
<div class="col-md-3"><img src="img/ssgs_pages.png" alt="Examples of SSGs supported by Pages" class="image-noshadow middle display-block"></div>
</div>
-### Availability
-
-If you're using GitLab.com, your website will be publicly available to the internet.
-If you're using self-managed instances (Core, Starter, Premium, or Ultimate),
-your websites will be published on your own server, according to the
-[Pages admin settings](../../../administration/pages/index.md) chosen by your sysadmin,
-who can opt for making them public or internal to your server.
-
### How it works
To use GitLab Pages, first you need to create a project in GitLab to upload your website's
@@ -78,99 +79,77 @@ repository. Note that when you create a new project in GitLab, a [repository](..
becomes available automatically.
To deploy your site, GitLab will use its built-in tool called [GitLab CI/CD](../../../ci/README.md),
-that will build your site and publish it to the GitLab Pages server. The sequence of
+to build your site and publish it to the GitLab Pages server. The sequence of
scripts that GitLab CI/CD runs to accomplish this task is created from a file named
`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will.
-You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain),
+You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain-names),
`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
need admin access to your domain's registrar (or control panel) to set it up with Pages.
Optionally, when adding your own domain, you can add an SSL/TLS certificate to secure your
site under the HTTPS protocol.
-## Getting started
+### Getting started
-To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch),
-use a [bundled template](getting_started_part_two.md#use-one-of-the-popular-pages-templates-bundled-with-gitlab), or copy any of our existing example projects:
+To get started with GitLab Pages, you can either:
-1. Choose an [example project](https://gitlab.com/pages) to [fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project):
- by forking a project, you create a copy of the codebase you're forking from to start from a template instead of starting from scratch.
-1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** and click
- **Run pipeline** so that GitLab CI/CD will build and deploy your site to the server.
-1. Once the pipeline has finished successfully, find the link to visit your website from your
- project's **Settings > Pages**.
-
-<table class="borderless-table center fixed-table middle width-80">
- <tr>
- <td style="width: 30%"><img src="img/icons/fork.png" alt="Fork" class="image-noshadow half-width"></td>
- <td style="width: 10%">
- <strong>
- <i class="fa fa-angle-double-right" aria-hidden="true"></i>
- </strong>
- </td>
- <td style="width: 30%"><img src="img/icons/terminal.png" alt="Deploy" class="image-noshadow half-width"></td>
- <td style="width: 10%">
- <strong>
- <i class="fa fa-angle-double-right" aria-hidden="true"></i>
- </strong>
- </td>
- <td style="width: 30%"><img src="img/icons/click.png" alt="Visit" class="image-noshadow half-width"></td>
- </tr>
- <tr>
- <td><em>Fork an example project</em></td>
- <td></td>
- <td><em>Deploy your website</em></td>
- <td></td>
- <td><em>Visit your website's URL</em></td>
- </tr>
-</table>
+- [Create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch).
+- [Copy an existing example project](getting_started_part_two.md#fork-a-project-to-get-started-from).
+- Use a bundled project template ready to go:
-Your website is then visible on your domain, and you can modify your files
-as you wish. For every modification pushed to your repository, GitLab CI/CD will run
-a new pipeline to publish your changes to the server.
+1. From the top navigation, click the **+** button and select **New project**.
+1. Select **Create from Template**.
+1. Choose one of the templates starting with **Pages**:
-You can also take some optional further steps:
+ ![Project templates for Pages](img/pages_project_templates_11-8.png)
-- Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from)
- (_You don't need the relationship unless you intent to contribute back to the example project you forked from_).
-- Make it a [user/group website](getting_started_part_one.md#user-and-group-websites)
+1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
+and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
+site to the server.
+1. Once the pipeline has finished successfully, find the link to visit your
+website from your project's **Settings > Pages**.
-**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) with all the steps above!**
+Your website is then visible on your domain, and you can modify yourfiles
+as you wish. For every modification pushed to your repository, GitLab CI/CD
+will run a new pipeline to publish your changes to the server.
_Advanced options:_
- [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages)
-- Apply [SSL/TLS certification](getting_started_part_three.md#ssl-tls-certificates) to your custom domain
-
-## Explore GitLab Pages
+- Apply [SSL/TLS certification](getting_started_part_three.md#ssltls-certificates) to your custom domain
-To learn more about GitLab Pages, read the following tutorials:
+## Availability
-- [Static websites and GitLab Pages domains](getting_started_part_one.md): Understand what is a static website, and how GitLab Pages default domains work
-- [Projects for GitLab Pages and URL structure](getting_started_part_two.md): Forking projects and creating new ones from scratch, understanding URLs structure and baseurls
-- [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md): How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates
-- [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md): Understand how to create your own `.gitlab-ci.yml` for your site
-- [Technical aspects, custom 404 pages, limitations](introduction.md)
-
-### GitLab Pages with Static Site Generators (SSGs)
-
-To understand more about SSGs, their advantages, and how to get the most from them
-with Pages, read through this series:
-
-- [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
-- [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
-- [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+If you're using GitLab.com, your website will be publicly available to the internet.
+If you're using self-managed instances (Core, Starter, Premium, or Ultimate),
+your websites will be published on your own server, according to the
+[Pages admin settings](../../../administration/pages/index.md) chosen by your sysadmin,
+who can opt for making them public or internal to your server.
-### GitLab Pages with SSL/TLS certificates
+Note that, if you're using GitLab Pages default domain (`.gitlab.io`),
+your website will be automatically secure and available under
+HTTPS. If you're using your own custom domain, you can
+optionally secure it with SSL/TLS certificates.
-If you're using GitLab Pages default domain (`.gitlab.io`), your website will be
-automatically secure and available under HTTPS. If you're using your own domain, you can
-optionally secure it with SSL/TLS certificates. You can read the following
-tutorials to learn how to use these third-party certificates with GitLab Pages:
+## Explore GitLab Pages
-- [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
-- [Let's Encrypt](lets_encrypt_for_gitlab_pages.md)
+To learn more about configuration options for GitLab Pages, read the following:
+
+| Document | Description |
+| --- | --- |
+| [Static websites and Pages domains](getting_started_part_one.md) | Understand what is a static website, and how GitLab Pages default domains work. |
+| [Projects and URL structure](getting_started_part_two.md) | Forking projects and creating new ones from scratch, understanding URLs structure and baseurls. |
+| [GitLab CI/CD for GitLab Pages](getting_started_part_four.md) | Understand how to create your own `.gitlab-ci.yml` for your site. |
+| [Exploring GitLab Pages](introduction.md) | Requirements, technical aspects, specific GitLab CI's configuration options, custom 404 pages, limitations, FAQ. |
+|---+---|
+| [Custom domains and SSL/TLS Certificates](getting_started_part_three.md) | How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates. |
+| [CloudFlare certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Secure your Pages site with CloudFlare certificates. |
+| [Let's Encrypt certificates](lets_encrypt_for_gitlab_pages.md) | Secure your Pages site with Let's Encrypt certificates. |
+|---+---|
+| [Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | A conceptual overview on static versus dynamic sites. |
+| [Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | A conceptual overview on SSGs. |
+| [Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) | An overview on using SSGs for GitLab Pages. |
## Advanced use
@@ -178,13 +157,13 @@ There are quite some great examples of GitLab Pages websites built for some
specific reasons. These examples can teach you some advanced techniques
to use and adapt to your own needs:
-- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
-- [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
-- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
-- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
-- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
+- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/).
+- [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/).
+- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
+- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/).
+- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
-## Admin GitLab Pages for CE and EE
+## Admin GitLab Pages for self-managed instances
Enable and configure GitLab Pages on your own instance (GitLab Community Edition and Enterprise Editions) with
the [admin guide](../../../administration/pages/index.md).
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 23eb88fd305..4fab7f79e0c 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,177 +1,142 @@
-# Exploring GitLab Pages
-
-> **Notes:**
-> - This feature was [introduced][ee-80] in GitLab EE 8.3.
-> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
-> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
-> - This document is about the user guide. To learn how to enable GitLab Pages
-> across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md).
-
-With GitLab Pages you can host for free your static websites on GitLab.
-Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can
-deploy static pages for your individual projects, your user or your group.
-
-Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific
-information, if you are using GitLab.com to host your website.
+---
+type: reference
+last_updated: 2018-06-04
+---
-## Getting started with GitLab Pages domains
+# Exploring GitLab Pages
-> **Note:**
-> In the rest of this document we will assume that the general domain name that
-> is used for GitLab Pages is `example.io`.
+This document is a user guide to explore the options and settings
+GitLab Pages offers.
-In general there are two types of pages one might create:
+To familiarize yourself with GitLab Pages first:
-- Pages per user (`username.example.io`) or per group (`groupname.example.io`)
-- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`)
+- Read an [introduction to GitLab Pages](index.md#overview).
+- Learn [how to get started with Pages](index.md#getting-started).
+- Learn how to enable GitLab Pages
+across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md).
-In GitLab, usernames and groupnames are unique and we often refer to them
-as [namespaces](../../group/index.md#namespaces). There can be only one namespace
-in a GitLab instance. Below you
-can see the connection between the type of GitLab Pages, what the project name
-that is created on GitLab looks like and the website URL it will be ultimately
-be served on.
+## GitLab Pages requirements
-| Type of GitLab Pages | The name of the project created in GitLab | Website URL |
-| -------------------- | ------------ | ----------- |
-| User pages | `username.example.io` | `http(s)://username.example.io` |
-| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
-| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
-| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
-| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
+In brief, this is what you need to upload your website in GitLab Pages:
-> **Warning:**
-> There are some known [limitations](#limitations) regarding namespaces served
-> under the general domain name and HTTPS. Make sure to read that section.
+1. Domain of the instance: domain name that is used for GitLab Pages
+(ask your administrator).
+1. GitLab CI/CD: a `.gitlab-ci.yml` file with a specific job named [`pages`][pages] in the root directory of your repository.
+1. A directory called `public` in your site's repo containing the content
+to be published.
+1. GitLab Runner enabled for the project.
-### GitLab Pages requirements
+## GitLab Pages on GitLab.com
-In brief, this is what you need to upload your website in GitLab Pages:
+If you are using [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) to host your website, then:
-1. Find out the general domain name that is used for GitLab Pages
- (ask your administrator). This is very important, so you should first make
- sure you get that right.
-1. Create a project
-1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
- of your repository with a specific job named [`pages`][pages]
-1. Set up a GitLab Runner to build your website
+- The domain name for GitLab Pages on GitLab.com is `gitlab.io`.
+- Custom domains and TLS support are enabled.
+- Shared runners are enabled by default, provided for free and can be used to
+ build your website. If you want you can still bring your own Runner.
-> **Note:**
-If [shared runners](../../../ci/runners/README.md) are enabled by your GitLab
-administrator, you should be able to use them instead of bringing your own.
+## Example projects
-### User or group Pages
+Visit the [GitLab Pages group](https://gitlab.com/groups/pages) for a complete list of example projects. Contributions are very welcome.
-For user and group pages, the name of the project should be specific to the
-username or groupname and the general domain name that is used for GitLab Pages.
-Head over your GitLab instance that supports GitLab Pages and create a
-repository named `username.example.io`, where `username` is your username on
-GitLab. If the first part of the project name doesn't match exactly your
-username, it won’t work, so make sure to get it right.
+## Custom error codes Pages
-To create a group page, the steps are the same like when creating a website for
-users. Just make sure that you are creating the project within the group's
-namespace.
+You can provide your own 403 and 404 error pages by creating the `403.html` and
+`404.html` files respectively in the root directory of the `public/` directory
+that will be included in the artifacts. Usually this is the root directory of
+your project, but that may differ depending on your static generator
+configuration.
-![Create a user-based pages project](img/pages_create_user_page.png)
+If the case of `404.html`, there are different scenarios. For example:
----
+- If you use project Pages (served under `/projectname/`) and try to access
+ `/projectname/non/existing_file`, GitLab Pages will try to serve first
+ `/projectname/404.html`, and then `/404.html`.
+- If you use user/group Pages (served under `/`) and try to access
+ `/non/existing_file` GitLab Pages will try to serve `/404.html`.
+- If you use a custom domain and try to access `/non/existing_file`, GitLab
+ Pages will try to serve only `/404.html`.
-After you push some static content to your repository and GitLab Runner uploads
-the artifacts to GitLab CI, you will be able to access your website under
-`http(s)://username.example.io`. Keep reading to find out how.
+## Redirects in GitLab Pages
->**Note:**
-If your username/groupname contains a dot, for example `foo.bar`, you will not
-be able to use the wildcard domain HTTPS, read more at [limitations](#limitations).
+Since you cannot use any custom server configuration files, like `.htaccess` or
+any `.conf` file, if you want to redirect a page to another
+location, you can use the [HTTP meta refresh tag][metarefresh].
-### Project Pages
+Some static site generators provide plugins for that functionality so that you
+don't have to create and edit HTML files manually. For example, Jekyll has the
+[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
-GitLab Pages for projects can be created by both user and group accounts.
-The steps to create a project page for a user or a group are identical:
+## GitLab Pages Access Control **[CORE ONLY]**
-1. Create a new project
-1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
- of your repository with a specific job named [`pages`][pages].
-1. Set up a GitLab Runner to build your website
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
-A user's project will be served under `http(s)://username.example.io/projectname`
-whereas a group's project under `http(s)://groupname.example.io/projectname`.
+NOTE: **Note:**
+GitLab Pages access control is not activated on GitLab.com. You can check its
+progress on the
+[infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5576).
-For practical examples for group and project Pages, read through the guide
-[GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#practical-examples).
+You can enable Pages access control on your project, so that only
+[members of your project](../../permissions.md#project-members-permissions)
+(at least Guest) can access your website:
-## Quick Start
+1. Navigate to your project's **Settings > General > Permissions**.
+1. Toggle the **Pages** button to enable the access control.
-Read through [GitLab Pages Quick Start Guide][pages-quick] or watch the video tutorial on
-[how to publish a website with GitLab Pages on GitLab.com from a forked project][video-pages-fork].
+ NOTE: **Note:**
+ If you don't see the toggle button, that means that it's not enabled.
+ Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
-See also [All you Need to Know About GitLab Pages][pages-index-guide] for a list with all the resources we have for GitLab Pages.
+1. The Pages access control dropdown allows you to set who can view pages hosted
+ with GitLab Pages, depending on your project's visibility:
-### Explore the contents of `.gitlab-ci.yml`
+ - If your project is private:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is internal:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is public:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
-The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that
-gives you absolute control over the build process. You can actually watch your
-website being built live by following the CI job traces.
+1. Click **Save changes**.
-For a simplified user guide on setting up GitLab CI/CD for Pages, read through
-the article [GitLab Pages from A to Z: Part 4 - Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md#creating-and-tweaking-gitlab-ci-yml-for-gitlab-pages)
+---
-> **Note:**
-> Before reading this section, make sure you familiarize yourself with GitLab CI
-> and the specific syntax of[`.gitlab-ci.yml`][yaml] by
-> following our [quick start guide].
+The next time someone tries to access your website and the access control is
+enabled, they will be presented with a page to sign into GitLab and verify they
+can access the website.
-To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the
-rules below:
+## Unpublishing your Pages
-1. A special job named [`pages`][pages] must be defined
-1. Any static content which will be served by GitLab Pages must be placed under
- a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
+If you ever feel the need to purge your Pages content, you can do so by going
+to your project's settings through the gear icon in the top right, and then
+navigating to **Pages**. Hit the **Remove pages** button and your Pages website
+will be deleted.
-In its simplest form, `.gitlab-ci.yml` looks like:
+![Remove pages](img/remove_pages.png)
-```yaml
-pages:
- script:
- - my_commands
- artifacts:
- paths:
- - public
-```
+## Limitations
-When the Runner reaches to build the `pages` job, it executes whatever is
-defined in the `script` parameter and if the job completes with a non-zero
-exit status, it then uploads the `public/` directory to GitLab Pages.
+When using Pages under the general domain of a GitLab instance (`*.example.io`),
+you _cannot_ use HTTPS with sub-subdomains. That means that if your
+username/groupname contains a dot, for example `foo.bar`, the domain
+`https://foo.bar.example.io` will _not_ work. This is a limitation of the
+[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you
+don't redirect HTTP to HTTPS.
-The `public/` directory should contain all the static content of your website.
-Depending on how you plan to publish your website, the steps defined in the
-[`script` parameter](../../../ci/yaml/README.md#script) may differ.
+[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
-Be aware that Pages are by default branch/tag agnostic and their deployment
-relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the
-`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
-whenever a new commit is pushed to whatever branch or tag, the Pages will be
-overwritten. In the example below, we limit the Pages to be deployed whenever
-a commit is pushed only on the `master` branch:
+GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest-level group website.
-```yaml
-pages:
- script:
- - my_commands
- artifacts:
- paths:
- - public
- only:
- - master
-```
+## Specific configuration options for Pages
-We then tell the Runner to treat the `public/` directory as `artifacts` and
-upload it to GitLab. And since all these parameters were all under a `pages`
-job, the contents of the `public` directory will be served by GitLab Pages.
+Learn how to set up GitLab CI/CD for specific use cases.
-#### How `.gitlab-ci.yml` looks like when the static content is in your repository
+### `.gitlab-ci.yml` for plain HTML websites
Supposed your repository contained the following files:
@@ -200,59 +165,15 @@ pages:
- master
```
-#### How `.gitlab-ci.yml` looks like when using a static generator
-
-In general, GitLab Pages support any kind of [static site generator][staticgen],
-since `.gitlab-ci.yml` can be configured to run any possible command.
-
-In the root directory of your Git repository, place the source files of your
-favorite static generator. Then provide a `.gitlab-ci.yml` file which is
-specific to your static generator.
+### `.gitlab-ci.yml` for a static site generator
-The example below, uses [Jekyll] to build the static site:
+See this document for a [step-by-step guide](getting_started_part_four.md).
-```yaml
-image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1
-
-pages: # the build job must be named pages
- script:
- - gem install jekyll # we install jekyll
- - jekyll build -d public/ # we tell jekyll to build the site for us
- artifacts:
- paths:
- - public # this is where the site will live and the Runner uploads it in GitLab
- only:
- - master # this script is only affecting the master branch
-```
-
-Here, we used the Docker executor and in the first line we specified the base
-image against which our jobs will run.
-
-You have to make sure that the generated static files are ultimately placed
-under the `public` directory, that's why in the `script` section we run the
-`jekyll` command that jobs the website and puts all content in the `public/`
-directory. Depending on the static generator of your choice, this command will
-differ. Search in the documentation of the static generator you will use if
-there is an option to explicitly set the output directory. If there is not
-such an option, you can always add one more line under `script` to rename the
-resulting directory in `public/`.
-
-We then tell the Runner to treat the `public/` directory as `artifacts` and
-upload it to GitLab.
-
----
-
-See the [jekyll example project][pages-jekyll] to better understand how this
-works.
-
-For a list of Pages projects, see the [example projects](#example-projects) to
-get you started.
-
-#### How to set up GitLab Pages in a repository where there's also actual code
+### `.gitlab-ci.yml` for a repository where there's also actual code
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
-the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
+the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#onlyexcept-basic),
whenever a new commit is pushed to a branch that will be used specifically for
your pages.
@@ -293,28 +214,6 @@ also includes `.gitlab-ci.yml`.
[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master
[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages
-## Next steps
-
-So you have successfully deployed your website, congratulations! Let's check
-what more you can do with GitLab Pages.
-
-### Example projects
-
-Below is a list of example projects for GitLab Pages with a plain HTML website
-or various static site generators. Contributions are very welcome.
-
-- [Plain HTML](https://gitlab.com/pages/plain-html)
-- [Jekyll](https://gitlab.com/pages/jekyll)
-- [Hugo](https://gitlab.com/pages/hugo)
-- [Middleman](https://gitlab.com/pages/middleman)
-- [Hexo](https://gitlab.com/pages/hexo)
-- [Brunch](https://gitlab.com/pages/brunch)
-- [Metalsmith](https://gitlab.com/pages/metalsmith)
-- [Harp](https://gitlab.com/pages/harp)
-
-Visit the GitLab Pages group for a full list of example projects:
-<https://gitlab.com/groups/pages>.
-
### Serving compressed assets
Most modern browsers support downloading files in a compressed format. This
@@ -407,158 +306,6 @@ NOTE: **Note:**
When `public/data/index.html` exists, it takes priority over the `public/data.html`
file for both the `/data` and `/data/` URL paths.
-### Add a custom domain to your Pages website
-
-For a complete guide on Pages domains, read through the article
-[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates)
-
-If this setting is enabled by your GitLab administrator, you should be able to
-see the **New Domain** button when visiting your project's settings through the
-gear icon in the top right and then navigating to **Pages**.
-
-![New domain button](img/pages_new_domain_button.png)
-
----
-
-You can add multiple domains pointing to your website hosted under GitLab.
-Once the domain is added, you can see it listed under the **Domains** section.
-
-![Pages multiple domains](img/pages_multiple_domains.png)
-
----
-
-As a last step, you need to configure your DNS and add a CNAME pointing to your
-user/group page. Click on the **Details** button of a domain for further
-instructions.
-
-![Pages DNS details](img/pages_dns_details.png)
-
----
-
->**Note:**
-Currently there is support only for custom domains on per-project basis. That
-means that if you add a custom domain (`example.com`) for your user website
-(`username.example.io`), a project that is served under `username.example.io/foo`,
-will not be accessible under `example.com/foo`.
-
-### Secure your custom domain website with TLS
-
-When you add a new custom domain, you also have the chance to add a TLS
-certificate. If this setting is enabled by your GitLab administrator, you
-should be able to see the option to upload the public certificate and the
-private key when adding a new domain.
-
-![Pages upload cert](img/pages_upload_cert.png)
-
-For a complete guide on Pages domains, read through the article
-[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates)
-
-### Custom error codes pages
-
-You can provide your own 403 and 404 error pages by creating the `403.html` and
-`404.html` files respectively in the root directory of the `public/` directory
-that will be included in the artifacts. Usually this is the root directory of
-your project, but that may differ depending on your static generator
-configuration.
-
-If the case of `404.html`, there are different scenarios. For example:
-
-- If you use project Pages (served under `/projectname/`) and try to access
- `/projectname/non/existing_file`, GitLab Pages will try to serve first
- `/projectname/404.html`, and then `/404.html`.
-- If you use user/group Pages (served under `/`) and try to access
- `/non/existing_file` GitLab Pages will try to serve `/404.html`.
-- If you use a custom domain and try to access `/non/existing_file`, GitLab
- Pages will try to serve only `/404.html`.
-
-### Remove the contents of your pages
-
-If you ever feel the need to purge your Pages content, you can do so by going
-to your project's settings through the gear icon in the top right, and then
-navigating to **Pages**. Hit the **Remove pages** button and your Pages website
-will be deleted. Simple as that.
-
-![Remove pages](img/pages_remove.png)
-
-## GitLab Pages on GitLab.com
-
-If you are using GitLab.com to host your website, then:
-
-- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`.
-- Custom domains and TLS support are enabled.
-- Shared runners are enabled by default, provided for free and can be used to
- build your website. If you want you can still bring your own Runner.
-
-The rest of the guide still applies.
-
-See also: [GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain).
-
-## GitLab Pages access control **[CORE ONLY]**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
-
-NOTE: **Note:**
-GitLab Pages access control is not activated on GitLab.com. You can check its
-progress on the
-[infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5576).
-
-You can enable Pages access control on your project, so that only
-[members of your project](../../permissions.md#project-members-permissions)
-(at least Guest) can access your website:
-
-1. Navigate to your project's **Settings > General > Permissions**.
-1. Toggle the **Pages** button to enable the access control.
-
- NOTE: **Note:**
- If you don't see the toggle button, that means that it's not enabled.
- Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
-
-1. The Pages access control dropdown allows you to set who can view pages hosted
- with GitLab Pages, depending on your project's visibility:
-
- - If your project is private:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is internal:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is public:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
-
-1. Click **Save changes**.
-
----
-
-The next time someone tries to access your website and the access control is
-enabled, they will be presented with a page to sign into GitLab and verify they
-can access the website.
-
-## Limitations
-
-When using Pages under the general domain of a GitLab instance (`*.example.io`),
-you _cannot_ use HTTPS with sub-subdomains. That means that if your
-username/groupname contains a dot, for example `foo.bar`, the domain
-`https://foo.bar.example.io` will _not_ work. This is a limitation of the
-[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you
-don't redirect HTTP to HTTPS.
-
-[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
-
-GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest-level group website.
-
-## Redirects in GitLab Pages
-
-Since you cannot use any custom server configuration files, like `.htaccess` or
-any `.conf` file, if you want to redirect a page to another
-location, you can use the [HTTP meta refresh tag][metarefresh].
-
-Some static site generators provide plugins for that functionality so that you
-don't have to create and edit HTML files manually. For example, Jekyll has the
-[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
-
## Frequently Asked Questions
### Can I download my generated pages?
@@ -580,8 +327,6 @@ No, you don't. You can create your project first and it will be accessed under
For a list of known issues, visit GitLab's [public issue tracker].
[jekyll]: http://jekyllrb.com/
-[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
-[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages
[gitlab ci]: https://about.gitlab.com/gitlab-ci
[gitlab runner]: https://docs.gitlab.com/runner/
@@ -591,7 +336,6 @@ For a list of known issues, visit GitLab's [public issue tracker].
[pages-jekyll]: https://gitlab.com/pages/jekyll
[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh
[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=pages
-[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
[quick start guide]: ../../../ci/quick_start/README.md
[pages-index-guide]: index.md
[pages-quick]: getting_started_part_one.md
diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
index f639188684b..91a660c0f7a 100644
--- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -1,5 +1,7 @@
---
description: "How to secure GitLab Pages websites with Let's Encrypt."
+type: howto
+last_updated: 2019-06-04
---
# Let's Encrypt for GitLab Pages
@@ -134,19 +136,19 @@ Now that your certificate has been issued, let's add it to your Pages site:
sudo cat /etc/letsencrypt/live/example.com/fullchain.pem | pbcopy
```
-1. Copy and paste the public key into the second field **Key (PEM)**:
+1. Copy and paste the private key into the second field **Key (PEM)**:
```bash
sudo cat /etc/letsencrypt/live/example.com/privkey.pem | pbcopy
```
1. Click **Save changes** to apply them to your website.
-1. Wait a few minutes for DNS propagation.
+1. Wait a few minutes for the configuration changes to take effect.
1. Visit your website at `https://example.com`.
To force `https` connections on your site, navigate to your
-project's **Settings > Pages** and check **Force domains with SSL
-certificates to use HTTPS**.
+project's **Settings > Pages** and check **Force HTTPS (requires
+valid certificates)**.
## Renewal
diff --git a/doc/user/project/pipelines/img/pipeline_schedule_variables.png b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
index 74692add93a..29846206491 100644
--- a/doc/user/project/pipelines/img/pipeline_schedule_variables.png
+++ b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
index 95203ec861b..e135dd51070 100644
--- a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
Binary files differ
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index bf939dbdaa3..002addfc043 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -1,6 +1,7 @@
# Introduction to job artifacts
> **Notes:**
+>
> - Since GitLab 8.2 and GitLab Runner 0.7.0, job artifacts that are created by
> GitLab Runner are uploaded to GitLab and are downloadable as a single archive
> (`tar.gz`) using the GitLab UI.
@@ -15,7 +16,7 @@
> [administration/job_artifacts](../../../administration/job_artifacts.md).
Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
+after it finishes. This feature is enabled by default in all
GitLab installations.
## Defining artifacts in `.gitlab-ci.yml`
@@ -35,12 +36,14 @@ pdf:
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
-are relative to the repository that was cloned during the build. These uploaded
-artifacts will be kept in GitLab for 1 week as defined by the `expire_in`
-definition. You have the option to keep the artifacts from expiring via the
-[web interface](#browsing-job-artifacts). If the expiry time is not defined,
-it defaults to the [instance wide
-setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
+are relative to the repository that was cloned during the build.
+
+The artifacts will be uploaded when the job succeeds by default, but can be set to upload
+when the job fails, or always, if the [`artifacts:when`](../../../ci/yaml/README.md#artifactswhen)
+parameter is used. These uploaded artifacts will be kept in GitLab for 1 week as defined
+by the `expire_in` definition. You have the option to keep the artifacts from expiring
+via the [web interface](#browsing-artifacts). If the expiry time is not defined, it defaults
+to the [instance wide setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
@@ -54,7 +57,8 @@ For more examples on artifacts, follow the [artifacts reference in
> **Note:**
> With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed
> directly in a new tab without the need to download them when
-> [GitLab Pages](../../../administration/pages/index.md) is enabled
+> [GitLab Pages](../../../administration/pages/index.md) is enabled.
+> The same holds for textual formats (currently supported extensions: `.txt`, `.json`, and `.log`).
After a job finishes, if you visit the job's specific page, there are three
buttons. You can download the artifacts archive or browse its contents, whereas
@@ -152,7 +156,7 @@ For example:
https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/master/browse?job=coverage
```
-There is also a URL to specific files, including html files that
+There is also a URL to specific files, including html files that
are shown in [GitLab Pages](../../../administration/pages/index.md):
```
@@ -183,7 +187,7 @@ information in the UI.
DANGER: **Warning:**
This is a destructive action that leads to data loss. Use with caution.
-If you have at least Developer [permissions](../../permissions.md#gitlab-ci-cd-permissions)
+If you have at least Developer [permissions](../../permissions.md#gitlab-cicd-permissions)
on the project, you can erase a single job via the UI which will also remove the
artifacts and the job's trace.
@@ -191,9 +195,9 @@ artifacts and the job's trace.
1. Click the trash icon at the top right of the job's trace.
1. Confirm the deletion.
-## Retrieve artifacts of private projects when using GitLab CI
+## Retrieve artifacts of private projects when using GitLab CI
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts.
[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in
-[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399 \ No newline at end of file
+[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 58a0fbc97cd..dd5427ab35a 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -1,29 +1,34 @@
# Pipeline schedules
> **Notes**:
-> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
-> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
+>
+> - Introduced in GitLab 9.1 as [Trigger Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533).
+> - [Renamed to Pipeline Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853) in GitLab 9.2.
> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
-Pipeline schedules can be used to run a pipeline at specific intervals, for example every
-month on the 22nd for a certain branch.
+Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository.
-## Using Pipeline schedules
+Pipeline schedules can be used to also run [pipelines](../../../ci/pipelines.md) at specific intervals. For example:
-In order to schedule a pipeline:
+- Every month on the 22nd for a certain branch.
+- Once every day.
-1. Navigate to your project's **CI / CD âž” Schedules** and click the
- **New Schedule** button.
-1. Fill in the form
-1. Hit **Save pipeline schedule** for the changes to take effect.
+In addition to using the GitLab UI, pipeline schedules can be maintained using the
+[Pipeline schedules API](../../../api/pipeline_schedules.md).
+
+## Configuring pipeline schedules
+
+To schedule a pipeline for project:
+
+1. Navigate to the project's **CI / CD > Schedules** page.
+1. Click the **New schedule** button.
+1. Fill in the **Schedule a new pipeline** form.
+1. Click the **Save pipeline schedule** button.
![New Schedule Form](img/pipeline_schedules_new_form.png)
-> **Attention:**
-The pipelines won't be executed precisely, because schedules are handled by
-Sidekiq, which runs according to its interval.
-See [advanced admin configuration](#advanced-admin-configuration) for more
-information.
+NOTE: **Note:**
+Pipelines execution [timing is dependent](#advanced-configuration) on Sidekiq's own schedule.
In the **Schedules** index page you can see a list of the pipelines that are
scheduled to run. The next run is automatically calculated by the server GitLab
@@ -31,36 +36,24 @@ is installed on.
![Schedules list](img/pipeline_schedules_list.png)
-### Running a scheduled pipeline manually
-
-> [Introduced][ce-15700] in GitLab 10.4.
-
-To trigger a pipeline schedule manually, click the "Play" button:
-
-![Play Pipeline Schedule](img/pipeline_schedule_play.png)
-
-This will schedule a background job to run the pipeline schedule. A flash
-message will provide a link to the CI/CD Pipeline index page.
-
-To help avoid abuse, users are rate limited to triggering a pipeline once per
-minute.
-
-### Making use of scheduled pipeline variables
+### Using variables
-> [Introduced][ce-12328] in GitLab 9.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328) in GitLab 9.4.
You can pass any number of arbitrary variables and they will be available in
-GitLab CI so that they can be used in your `.gitlab-ci.yml` file.
+GitLab CI so that they can be used in your [`.gitlab-ci.yml` file](../../../ci/yaml/README.md).
![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
-## Using only and except
+### Using only and except
To configure that a job can be executed only when the pipeline has been
scheduled (or the opposite), you can use
-[only and except](../../../ci/yaml/README.md#only-and-except-simplified) configuration keywords.
+[only and except](../../../ci/yaml/README.md#onlyexcept-basic) configuration keywords.
-```
+For example:
+
+```yaml
job:on-schedule:
only:
- schedules
@@ -74,11 +67,47 @@ job:
- make build
```
-## Taking ownership
+### Advanced configuration
+
+The pipelines won't be executed exactly on schedule because schedules are handled by
+Sidekiq, which runs according to its interval.
+
+For example, only two pipelines will be created per day if:
-Pipelines are executed as a user, who owns a schedule. This influences what
-projects and other resources the pipeline has access to. If a user does not own
-a pipeline, you can take ownership by clicking the **Take ownership** button.
+- You set a schedule to create a pipeline every minute (`* * * * *`).
+- The Sidekiq worker runs on 00:00 and 12:00 every day (`0 */12 * * *`).
+
+To change the Sidekiq worker's frequency:
+
+1. Edit the `gitlab_rails['pipeline_schedule_worker_cron']` value in your instance's `gitlab.rb` file.
+1. [Restart GitLab](../../../administration/restart_gitlab.md).
+
+For GitLab.com, refer to the [dedicated settings page](../../gitlab_com/index.md#cron-jobs).
+
+## Working with scheduled pipelines
+
+Once configured, GitLab supports many functions for working with scheduled pipelines.
+
+### Running manually
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700) in GitLab 10.4.
+
+To trigger a pipeline schedule manually, click the "Play" button:
+
+![Play Pipeline Schedule](img/pipeline_schedule_play.png)
+
+This will schedule a background job to run the pipeline schedule. A flash
+message will provide a link to the CI/CD Pipeline index page.
+
+NOTE: **Note:**
+To help avoid abuse, users are rate limited to triggering a pipeline once per
+minute.
+
+### Taking ownership
+
+Pipelines are executed as a user, who owns a schedule. This influences what projects and other resources the pipeline has access to.
+
+If a user does not own a pipeline, you can take ownership by clicking the **Take ownership** button.
The next time a pipeline is scheduled, your credentials will be used.
![Schedules list](img/pipeline_schedules_ownership.png)
@@ -89,20 +118,3 @@ on the target branch, the schedule will stop creating new pipelines. This can
happen if, for example, the owner is blocked or removed from the project, or
the target branch or tag is protected. In this case, someone with sufficient
privileges must take ownership of the schedule.
-
-## Advanced admin configuration
-
-The pipelines won't be executed precisely, because schedules are handled by
-Sidekiq, which runs according to its interval. For example, if you set a
-schedule to create a pipeline every minute (`* * * * *`) and the Sidekiq worker
-runs on 00:00 and 12:00 every day (`0 */12 * * *`), only 2 pipelines will be
-created per day. To change the Sidekiq worker's frequency, you have to edit the
-`pipeline_schedule_worker_cron` value in your `gitlab.rb` and restart GitLab.
-For GitLab.com, you can check the [dedicated settings page][settings]. If you
-don't have admin access to the server, ask your administrator.
-
-[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
-[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
-[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328
-[ce-15700]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700
-[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 4a989afad4d..16f48c462eb 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -20,6 +20,22 @@ There are two options. Using:
The default Git strategy can be overridden by the [GIT_STRATEGY variable](../../../ci/yaml/README.md#git-strategy)
in `.gitlab-ci.yml`.
+## Git shallow clone
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28919) in GitLab 12.0.
+
+NOTE: **Note**: As of GitLab 12.0, newly created projects will automaticallyl have a default
+`git depth` value of `50`.
+
+It is possible to limit the number of changes that GitLab CI/CD will fetch when cloning
+a repository. Setting a limit to `git depth` can speed up Pipelines execution. Maximum
+allowed value is `1000`.
+
+To disable shallow clone and make GitLab CI/CD fetch all branches and tags each time,
+keep the value empty or set to `0`.
+
+This value can also be [overridden by `GIT_DEPTH`](../../../ci/large_repositories/index.md#shallow-cloning) variable in `.gitlab-ci.yml` file.
+
## Timeout
Timeout defines the maximum amount of time in minutes that a job is able run.
@@ -182,4 +198,4 @@ https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?st
## Environment Variables
-[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
+[Environment variables](../../../ci/variables/README.html#gitlab-cicd-environment-variables) can be set in an environment to be available to a runner.
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index db706e5020e..56e8f1731ae 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -15,12 +15,10 @@ By default, a protected branch does four simple things:
- it prevents **anyone** from force pushing to the branch
- it prevents **anyone** from deleting the branch
-See the [Changelog](#changelog) section for changes over time.
+>**Note**:
+A GitLab admin is allowed to push to the protected branches.
->
->Additional functionality for GitLab Enterprise Edition:
->
->- Restrict push and merge access to [certain users][ee-restrict]
+See the [Changelog](#changelog) section for changes over time.
## Configuring protected branches
@@ -68,6 +66,21 @@ dropdown list in the "Already protected" area.
If you don't choose any of those options while creating a protected branch,
they are set to "Maintainers" by default.
+## Restricting push and merge access to certain users **[STARTER]**
+
+> This feature was [introduced][ce-5081] in [GitLab Starter][ee] 8.11.
+
+With GitLab Enterprise Edition you can restrict access to protected branches
+by choosing a role (Maintainers, Developers) as well as certain users. From the
+dropdown menu select the role and/or the users you want to have merge or push
+access.
+
+![Select roles and users](img/protected_branches_select_roles_and_users.png)
+
+Click **Protect** and the branch will appear in the "Protected branch" list.
+
+![Roles and users list](img/protected_branches_select_roles_and_users_list.png)
+
## Wildcard protected branches
> [Introduced][ce-4665] in GitLab 8.10.
@@ -94,6 +107,25 @@ all matching branches:
![Protected branch matches](img/protected_branches_matches.png)
+## Creating a protected branch
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53361) in GitLab 11.9.
+
+When a protected branch or wildcard protected branches are set to
+[**No one** is **Allowed to push**](#using-the-allowed-to-merge-and-allowed-to-push-settings),
+Developers (and users with higher [permission levels](../permissions.md)) are allowed
+to create a new protected branch, but only via the UI or through the API (to avoid
+creating protected branches accidentally from the command line or from a Git
+client application).
+
+To create a new branch through the user interface:
+
+1. Visit **Repository > Branches**.
+1. Click on **New branch**.
+1. Fill in the branch name and select an existing branch, tag, or commit that
+ the new branch will be based off. Only existing protected branches and commits
+ that are already in protected branches will be accepted.
+
## Deleting a protected branch
> [Introduced][ce-21393] in GitLab 9.3.
@@ -125,6 +157,10 @@ for details about the pipelines security model.
## Changelog
+**11.9**
+
+- [Allow protected branches to be created](https://gitlab.com/gitlab-org/gitlab-ce/issues/53361) by Developers (and users with higher permission levels) through the API and the user interface.
+
**9.2**
- Allow deletion of protected branches via the web interface [gitlab-org/gitlab-ce#21393][ce-21393]
@@ -146,3 +182,4 @@ for details about the pipelines security model.
[ce-21393]: https://gitlab.com/gitlab-org/gitlab-ce/issues/21393
[ee-restrict]: http://docs.gitlab.com/ee/user/project/protected_branches.html#restricting-push-and-merge-access-to-certain-users
[perm]: ../permissions.md
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 392e72dcc5c..1d640966013 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -26,15 +26,16 @@ discussions, and descriptions:
| `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
| `/assign me` | Assign yourself | ✓ | ✓ |
| `/assign @user` | Assign one user | ✓ | ✓ |
-| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | |
-| `/unassign` | Remove assignee(s) | ✓ | ✓ |
-| `/reassign @user1 @user2` | Change assignee | ✓ | ✓ |
+| `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | ✓ |
+| `/unassign @user1 @user2` | Remove assignee(s) **[STARTER]** | ✓ | ✓ |
+| `/reassign @user1 @user2` | Change assignee **[STARTER]** | ✓ | ✓ |
+| `/unassign` | Remove current assignee | ✓ | ✓ |
| `/milestone %milestone` | Set milestone | ✓ | ✓ |
| `/remove_milestone` | Remove milestone | ✓ | ✓ |
-| `/label ~label1 ~label2` | Add label(s) | ✓ | ✓ |
+| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace label | ✓ | ✓ |
-| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request | ✓ | ✓ |
+| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
@@ -45,13 +46,15 @@ discussions, and descriptions:
| `/remove_due_date` | Remove due date | ✓ | |
| `/weight 0,1,2, ...` | Set weight **[STARTER]** | ✓ | |
| `/clear_weight` | Clears weight **[STARTER]** | ✓ | |
-| `/epic <group&epic &#124; Epic URL>` | Add to epic **[ULTIMATE]** | ✓ | |
+| `/epic <&epic &#124; group&epic &#124; Epic URL>` | Add to epic **[ULTIMATE]** | ✓ | |
| `/remove_epic` | Removes from epic **[ULTIMATE]** | ✓ | |
+| `/promote` | Promote issue to epic **[ULTIMATE]** | ✓ | |
| `/confidential` | Make confidential | ✓ | |
| `/duplicate #issue` | Mark this issue as a duplicate of another issue | ✓ |
| `/move path/to/project` | Move this issue to another project | ✓ | |
| `/target_branch <Local branch Name>` | Set target branch | | ✓ |
| `/wip` | Toggle the Work In Progress status | | ✓ |
+| `/approve` | Approve the merge request | | ✓ |
| `/merge` | Merge (when pipeline succeeds) | | ✓ |
| `/create_merge_request <branch name>` | Create a new merge request starting from the current issue | ✓ | |
diff --git a/doc/user/project/releases.md b/doc/user/project/releases.md
index 737842962a9..baa7a295273 100644
--- a/doc/user/project/releases.md
+++ b/doc/user/project/releases.md
@@ -1 +1,5 @@
+---
+redirect_to: 'releases/index.md'
+---
+
This document was moved to [another location](releases/index.md).
diff --git a/doc/user/project/repository/branches/img/branch_filter_search_box.png b/doc/user/project/repository/branches/img/branch_filter_search_box.png
index 5dc7eccf189..5dc300cf24e 100644
--- a/doc/user/project/repository/branches/img/branch_filter_search_box.png
+++ b/doc/user/project/repository/branches/img/branch_filter_search_box.png
Binary files differ
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index f05554ffc5b..13e4f2ce163 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -11,7 +11,7 @@ Read through GiLab's branching documentation:
See also:
- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
-- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow). Use the best of GitLab for your branching strategies.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md). Use the best of GitLab for your branching strategies.
- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index 6495ede42e0..3260a355fdc 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -264,7 +264,7 @@ To remove a GPG key from your account:
## Rejecting commits that are not signed **[PREMIUM]**
You can configure your project to reject commits that aren't GPG-signed
-via [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html).
+via [push rules](../../../../push_rules/push_rules.md).
## GPG signing API
diff --git a/doc/user/project/repository/img/download_source_code.png b/doc/user/project/repository/img/download_source_code.png
new file mode 100644
index 00000000000..17f2cb4b3e8
--- /dev/null
+++ b/doc/user/project/repository/img/download_source_code.png
Binary files differ
diff --git a/doc/user/project/repository/img/repository_cleanup.png b/doc/user/project/repository/img/repository_cleanup.png
index bda40d3e193..e343f23ac27 100644
--- a/doc/user/project/repository/img/repository_cleanup.png
+++ b/doc/user/project/repository/img/repository_cleanup.png
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 22d912cd9d1..6fccfd40987 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -97,7 +97,7 @@ Some things to note about precedence:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508) in GitLab 9.1
-[Jupyter][jupyter] Notebook (previously IPython Notebook) files are used for
+[Jupyter](https://jupyter.org) Notebook (previously IPython Notebook) files are used for
interactive computing in many fields and contain a complete record of the
user's sessions and include code, narrative text, equations and rich output.
@@ -123,7 +123,7 @@ You can live preview changes submitted to a new branch with
[Review Apps](../../../ci/review_apps/index.md).
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
-[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers.
+[approval](../merge_requests/merge_request_approvals.md) from your managers.
To create, delete, and view [branches](branches/index.md) via GitLab's UI:
@@ -154,7 +154,7 @@ Via command line, you can commit multiple times before pushing.
you will trigger a pipeline per push, not per commit.
- **Skip pipelines:**
You can add to you commit message the keyword
- [`[ci skip]`](../../../ci/yaml/README.html#skipping-jobs)
+ [`[ci skip]`](../../../ci/yaml/README.md#skipping-jobs)
and GitLab CI will skip that pipeline.
- **Cross-link issues and merge requests:**
[Cross-linking](../issues/crosslinking_issues.md#from-commit-messages)
@@ -173,11 +173,15 @@ Via command line, you can commit multiple times before pushing.
## Repository size
-On GitLab.com, your [repository size limit is 10GB](../../gitlab_com/index.md#repository-size-limit)
-(including LFS). For other instances, the repository size is limited by your
-system administrators.
+A project's repository size is reported on the project's **Details** page. The reported size is
+updated every 15 minutes at most, so may not reflect recent activity.
-You can also [reduce a repository size using Git](reducing_the_repo_size_using_git.md).
+The repository size for:
+
+- GitLab.com [is set by GitLab](../../gitlab_com/index.md#repository-size-limit).
+- Self-managed instances is set by your GitLab administrators.
+
+You can [reduce a repository's size using Git](reducing_the_repo_size_using_git.md).
## Contributors
@@ -220,14 +224,10 @@ Select branches to compare using the [branch filter search box](branches/index.m
Find it under your project's **Repository > Compare**.
-## Locked files
-
-> Available in [GitLab Premium](https://about.gitlab.com/pricing/).
-
-Lock your files to prevent any conflicting changes.
+## Locked files **[PREMIUM]**
-[File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) is available only in
-[GitLab Premium](https://about.gitlab.com/pricing/).
+Use [File Locking](../file_lock.md) to
+lock your files to prevent any conflicting changes.
## Repository's API
@@ -241,4 +241,21 @@ Projects that contain a `.xcodeproj` or `.xcworkspace` directory can now be clon
in Xcode using the new **Open in Xcode** button, located next to the Git URL
used for cloning your project. The button is only shown on macOS.
-[jupyter]: https://jupyter.org
+## Download Source Code
+
+> Support for directory download was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24704) in GitLab 11.11.
+
+The source code stored in a repository can be downloaded from the UI.
+By clicking the download icon, a dropdown will open with links to download the following:
+
+![Download source code](img/download_source_code.png)
+
+- **Source code:**
+ allows users to download the source code on branch they're currently
+ viewing. Available extensions: `zip`, `tar`, `tar.gz`, and `tar.bz2`.
+- **Directory:**
+ only shows up when viewing a sub-directory. This allows users to download
+ the specific directory they're currently viewing. Also available in `zip`,
+ `tar`, `tar.gz`, and `tar.bz2`.
+- **Artifacts:**
+ allows users to download the artifacts of the latest CI build.
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 672567a8d7d..e3d771524ce 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,6 +1,6 @@
# Reducing the repository size using Git
-A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size]
+A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md)
which will prevent you from exceeding it.
When a project has reached its size limit, you will not be able to push to it,
@@ -14,7 +14,8 @@ move some blobs to LFS, or remove some old dependency updates from history.
Unfortunately, it's not so easy and that workflow won't work. Deleting files in
a commit doesn't actually reduce the size of the repo since the earlier commits
and blobs are still around. What you need to do is rewrite history with Git's
-[`filter-branch` option][gitscm], or a tool like the [BFG Repo-Cleaner][bfg].
+[`filter-branch` option](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch),
+or a tool like the [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/).
Note that even with that method, until `git gc` runs on the GitLab side, the
"removed" commits and blobs will still be around. You also need to be able to
@@ -98,6 +99,12 @@ up its own internal state, maximizing the space saved.
`git gc` against the repository. You will receive an email once it has
completed.
+This process will remove some copies of the rewritten commits from GitLab's
+cache and database, but there are still numerous gaps in coverage - at present,
+some of the copies may persist indefinitely. [Clearing the instance cache]
+(../../../administration/raketasks/maintenance.md#clear-redis-cache) may help to
+remove some of them, but it should not be depended on for security purposes!
+
## Using `git filter-branch`
1. Navigate to your repository:
@@ -131,7 +138,3 @@ up its own internal state, maximizing the space saved.
```
Your repository should now be below the size limit.
-
-[admin-repo-size]: https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html#repository-size-limit
-[bfg]: https://rtyley.github.io/bfg-repo-cleaner/
-[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch
diff --git a/doc/user/project/security_dashboard.md b/doc/user/project/security_dashboard.md
new file mode 100644
index 00000000000..a3da1ec97d3
--- /dev/null
+++ b/doc/user/project/security_dashboard.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../application_security/security_dashboard/index.md'
+---
+
+This document was moved to [another location](../application_security/security_dashboard/index.md).
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
new file mode 100644
index 00000000000..1a582164d03
--- /dev/null
+++ b/doc/user/project/service_desk.md
@@ -0,0 +1,124 @@
+# Service Desk **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/149) in [GitLab Premium 9.1](https://about.gitlab.com/2017/04/22/gitlab-9-1-released/#service-desk-eep).
+
+## Overview
+
+Service Desk is a module that allows your team to connect directly
+with any external party through email right inside of GitLab; no external tools required.
+An ongoing conversation right where your software is built ensures that user feedback ends
+up directly where it's needed, helping you build the right features to solve your users'
+real problems.
+
+With Service Desk, you can provide efficient email support to your customers, who can now
+email you bug reports, feature requests, or general feedback that will all end up in your
+GitLab project as new issues. In turn, your team can respond straight from the project.
+
+As Service Desk is built right into GitLab itself, the complexity and inefficiencies
+of multiple tools and external integrations are eliminated, significantly shortening
+the cycle time from feedback to software update.
+
+For an overview, check the video demonstration on [GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/).
+
+## Use cases
+
+For instance, let's assume you develop a game for iOS or Android.
+The codebase is hosted in your GitLab instance, built and deployed
+with GitLab CI.
+
+Here's how Service Desk will work for you:
+
+1. You'll provide a project-specific email address to your paying customers, who can email you directly from within the app
+1. Each email they send creates an issue in the appropriate project
+1. Your team members navigate to the Service Desk issue tracker, where they can see new support requests and respond inside associated issues
+1. Your team communicates back and forth with the customer to understand the request
+1. Your team starts working on implementing code to solve your customer's problem
+1. When your team finishes the implementation, whereupon the merge request is merged and the issue is closed automatically
+1. The customer will have been attended successfully via email, without having real access to your GitLab instance
+1. Your team saved time by not having to leave GitLab (or setup any integrations) to follow up with your customer
+
+## How it works
+
+GitLab Service Desk is a simple way to allow people to create issues in your
+GitLab instance without needing their own user account.
+
+It provides a unique email address for end users to create issues in a project,
+and replies can be sent either through the GitLab interface or by email. End
+users will only see the thread through email.
+
+## Configuring Service Desk
+
+> **Note:**
+Service Desk is enabled on GitLab.com. If you're a
+[Silver subscriber](https://about.gitlab.com/gitlab-com/),
+you can skip the step 1 below; you only need to enable it per project.
+
+1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance. This must
+ support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing).
+2. Navigate to your project's **Settings** and scroll down to the **Service Desk**
+ section.
+3. If you have the correct access and an Premium license,
+ you will see an option to set up Service Desk:
+
+ ![Activate Service Desk option](img/service_desk_disabled.png)
+
+4. Checking that box will enable Service Desk for the project, and show a
+ unique email address to email issues to the project. These issues will be
+ [confidential](issues/confidential_issues.md), so they will only be visible to project members.
+
+ **Warning**: this email address can be used by anyone to create an issue on
+ this project, whether or not they have access to your GitLab instance.
+ We recommend **putting this behind an alias** so that it can be changed if
+ needed, and **[enabling Akismet](../../integration/akismet.md)** on your GitLab instance to add spam
+ checking to this service. Unblocked email spam would result in many spam
+ issues being created, and may disrupt your GitLab service.
+
+ ![Service Desk enabled](img/service_desk_enabled.png)
+
+ _In GitLab 11.7, we updated the format of the generated email address.
+ However the older format is still supported, allowing existing aliases
+ or contacts to continue working._
+
+
+5. Service Desk is now enabled for this project! You should be able to access it from your project's navigation **Issue submenu**:
+
+ ![Service Desk Navigation Item](img/service_desk_nav_item.png)
+
+## Using Service Desk
+
+### As an end user (issue creator)
+
+To create a Service Desk issue, an end user doesn't need to know anything about
+the GitLab instance. They just send an email to the address they are given, and
+receive an email back confirming receipt:
+
+![Service Desk enabled](img/service_desk_confirmation_email.png)
+
+This also gives the end user an option to unsubscribe.
+
+If they don't choose to unsubscribe, then any new comments added to the issue
+will be sent as emails:
+
+![Service Desk reply email](img/service_desk_reply.png)
+
+And any responses they send will be displayed in the issue itself.
+
+### As a responder to the issue
+
+For responders to the issue, everything works as usual. They'll see a familiar looking
+issue tracker, where they can see issues created via customer support requests and
+filter and interact with them just like other GitLab issues.
+
+![Service Desk Issue tracker](img/service_desk_issue_tracker.png)
+
+Messages from the end user will show as coming from the special Support Bot user, but apart from that,
+you can read and write comments as you normally do:
+
+![Service Desk issue thread](img/service_desk_thread.png)
+
+> Note that the project's visibility (private, internal, public) does not affect Service Desk.
+
+### Support Bot user
+
+Behind the scenes, Service Desk works by the special Support Bot user creating issues. This user
+does not count toward the license limit count.
diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png
index 668254073e8..ab81c87bf5f 100644
--- a/doc/user/project/settings/img/import_export_download_export.png
+++ b/doc/user/project/settings/img/import_export_download_export.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png
index 7f21bb2335b..9e368739695 100644
--- a/doc/user/project/settings/img/import_export_export_button.png
+++ b/doc/user/project/settings/img/import_export_export_button.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png
index 48ef42855bc..985c37650d3 100644
--- a/doc/user/project/settings/img/import_export_mail_link.png
+++ b/doc/user/project/settings/img/import_export_mail_link.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png
index b335700c5be..fc1f73c5d6e 100644
--- a/doc/user/project/settings/img/import_export_new_project.png
+++ b/doc/user/project/settings/img/import_export_new_project.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png
index e1e5e031d81..e3e1a5ef980 100644
--- a/doc/user/project/settings/img/import_export_select_file.png
+++ b/doc/user/project/settings/img/import_export_select_file.png
Binary files differ
diff --git a/doc/user/project/settings/img/settings_edit_button.png b/doc/user/project/settings/img/settings_edit_button.png
deleted file mode 100644
index 32bcda03c7e..00000000000
--- a/doc/user/project/settings/img/settings_edit_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 89008fd15b9..819515d7a4c 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -2,10 +2,11 @@
>**Notes:**
>
-> - [Introduced][ce-3050] in GitLab 8.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/3050) in GitLab 8.9.
> - Importing will not be possible if the import instance version differs from
> that of the exporter.
-> - For GitLab admins, please read through [Project import/export administration](../../../administration/raketasks/project_import_export.md).
+> - For GitLab admins, please read through
+> [Project import/export administration](../../../administration/raketasks/project_import_export.md).
> - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'.
> Ask your administrator if you don't see the **GitLab export** button when
@@ -14,15 +15,15 @@
> 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.
-> - The exports are stored in a temporary [shared directory][tmp] and are deleted
-> every 24 hours by a specific worker.
+> [import_export](../../../administration/raketasks/project_import_export.md) raketask.
+> - The exports are stored in a temporary [shared directory](../../../development/shared_files.md)
+> and are deleted every 24 hours by a specific worker.
> - Group members will get exported as project members, as long as the user has
> maintainer or admin access to the group where the exported project lives. An admin
> in the import side is required to map the users, based on email or username.
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
+> - Project members with owner access will get imported as maintainers.
> - Control project Import/Export with the [API](../../../api/project_import_export.md).
> - If an imported project contains merge requests originated from forks,
> then new branches associated with such merge requests will be created
@@ -76,9 +77,9 @@ The following items will NOT be exported:
## Exporting a project and its data
-1. Go to the project settings page by clicking on **Edit Project**:
+1. Go to your project's homepage.
- ![Project settings button](img/settings_edit_button.png)
+1. Click **Settings** in the sidebar.
1. Scroll down to find the **Export project** button:
@@ -97,19 +98,14 @@ The following items will NOT be exported:
## Importing the project
-1. The new GitLab project import feature is at the far right of the import
- options when creating a New Project. Make sure you are in the right namespace
- and you have entered a project name. Click on **GitLab export**:
+1. The GitLab project import feature is the first import option when creating a
+ new project. Click on **GitLab export**:
![New project](img/import_export_new_project.png)
-1. You can see where the project will be imported to. You can now select file
- exported previously:
+1. Enter your project name and URL. Then select the file you exported previously:
![Select file](img/import_export_select_file.png)
1. Click on **Import project** to begin importing. Your newly imported project
page will appear soon.
-
-[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
-[tmp]: ../../../development/shared_files.md
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index d5f4a7fd4ac..e3502a632d9 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -36,15 +36,15 @@ Set up your project's merge request settings:
- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)).
- Merge request [description templates](../description_templates.md#description-templates).
-- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]**
+- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **[STARTER]**
- Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
![project's merge request settings](img/merge_requests_settings.png)
-### Service Desk
+### Service Desk **[PREMIUM]**
-Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support. Service Desk is available in [GitLab Premium](https://about.gitlab.com/pricing/).
+Enable [Service Desk](../service_desk.md) for your project to offer customer support.
### Export project
@@ -100,9 +100,9 @@ Only project Owners and Admin users have the [permissions] to transfer a project
You can transfer an existing project into a [group](../../group/index.md) if:
-1. you have at least **Maintainer** [permissions] to that group
-1. you are an **Owner** of the project.
-
+1. You have at least **Maintainer** [permissions] to that group.
+1. The project is in a subgroup you own.
+1. You are at least a **Maintainer** of the project under your personal namespace.
Similarly, if you are an owner of a group, you can transfer any of its projects
under your own user.
@@ -128,3 +128,7 @@ namespace if needed.
### Error Tracking
Configure Error Tracking to discover and view [Sentry errors within GitLab](../operations/error_tracking.md).
+
+### Jaeger tracing **[ULTIMATE]**
+
+Add the URL of a Jaeger server to allow your users to [easily access the Jaeger UI from within GitLab](../operations/tracing.md).
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index e9103a3f49c..1280524bc9b 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -1 +1,5 @@
+---
+redirect_to: 'quick_actions.md'
+---
+
This document was moved to [user/project/quick_actions.md](quick_actions.md).
diff --git a/doc/user/project/web_ide/img/enable_web_ide.png b/doc/user/project/web_ide/img/enable_web_ide.png
deleted file mode 100644
index 196baa82ad2..00000000000
--- a/doc/user/project/web_ide/img/enable_web_ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 46a1b2bc3aa..a634a8b2f54 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -8,7 +8,7 @@ projects by providing an advanced editor with commit staging.
## Open the Web IDE
-The Web IDE can be opened when viewing a file, from the repository file list,
+You can open the Web IDE when viewing a file, from the repository file list,
and from merge requests.
![Open Web IDE](img/open_web_ide.png)
@@ -45,7 +45,7 @@ Single file editing is based on the [Ace Editor](https://ace.c9.io).
## Stage and commit changes
-After making your changes, click the Commit button in the bottom left to
+After making your changes, click the **Commit** button in the bottom left to
review the list of changed files. Click on each file to review the changes and
click the tick icon to stage the file.
@@ -67,10 +67,11 @@ shows you a preview of the merge request diff if you commit your changes.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19279) in [GitLab Core][ce] 11.0.
-The Web IDE can be used to quickly fix failing tests by opening the branch or
-merge request in the Web IDE and opening the logs of the failed job. The status
-of all jobs for the most recent pipeline and job traces for the current commit
-can be accessed by clicking the **Pipelines** button in the top right.
+You can use the Web IDE to quickly fix failing tests by opening
+the branch or merge request in the Web IDE and opening the logs of the failed
+job. You can access the status of all jobs for the most recent pipeline and job
+traces for the current commit by clicking the **Pipelines** button in the top
+right.
The pipeline status is also shown at all times in the status bar in the bottom
left.
@@ -79,31 +80,31 @@ left.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) in [GitLab Core][ce] 11.0.
-Switching between your authored and assigned merge requests can be done without
-leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
-of merge requests. You will need to commit or discard all your changes before
-switching to a different merge request.
+To switch between your authored and assigned merge requests, click the
+dropdown in the top of the sidebar to open a list of merge requests. You will
+need to commit or discard all your changes before switching to a different merge
+request.
## Switching branches
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) in [GitLab Core][ce] 11.2.
-Switching between branches of the current project repository can be done without
-leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
-of branches. You will need to commit or discard all your changes before
-switching to a different branch.
+To switch between branches of the current project repository, click the dropdown
+in the top of the sidebar to open a list of branches.
+You will need to commit or discard all your changes before switching to a
+different branch.
## Client Side Evaluation
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) in [GitLab Core][ce] 11.2.
-The Web IDE can be used to preview JavaScript projects right in the browser.
+You can use the Web IDE to preview JavaScript projects right in the browser.
This feature uses CodeSandbox to compile and bundle the JavaScript used to
preview the web application.
![Web IDE Client Side Evaluation](img/clientside_evaluation.png)
-Additionally, for public projects an `Open in CodeSandbox` button is available
+Additionally, for public projects an **Open in CodeSandbox** button is available
to transfer the contents of the project into a public CodeSandbox project to
quickly share your project with others.
@@ -115,9 +116,9 @@ GitLab.com
![Admin Client Side Evaluation setting](img/admin_clientside_evaluation.png)
-Once it has been enabled in application settings, projects with a
-`package.json` file and a `main` entry point can be previewed inside of the Web
-IDE. An example `package.json` is below.
+Once you have done that, you can preview projects with a `package.json` file and
+a `main` entry point inside the Web IDE. An example `package.json` is shown
+below.
```json
{
@@ -128,5 +129,123 @@ IDE. An example `package.json` is below.
}
```
+## Interactive Web Terminals for the Web IDE **[ULTIMATE ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.6.
+
+CAUTION: **Warning:**
+Interactive Web Terminals for the Web IDE is currently in **Beta**.
+
+[Interactive web terminals](../../../ci/interactive_web_terminal/index.md)
+give the user access to a terminal to interact with the Runner directly from
+GitLab, including through the Web IDE.
+
+Only project [**maintainers**](../../permissions.md#project-members-permissions)
+can run Interactive Web Terminals through the Web IDE.
+
+CAUTION: **Warning:**
+GitLab.com [does not support Interactive Web Terminals yet](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611).
+Shared Runners in private instances are not supported either.
+
+### Runner configuration
+
+Some things need to be configured in the runner for the interactive web terminal
+to work:
+
+- The Runner needs to have
+ [`[session_server]` configured properly](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section).
+- If you are using a reverse proxy with your GitLab instance, web terminals need to be
+ [enabled](../../../administration/integration/terminal.md#enabling-and-disabling-terminal-support). **[ULTIMATE ONLY]**
+
+If you have the terminal open and the job has finished with its tasks, the
+terminal will block the job from finishing for the duration configured in
+[`[session_server].terminal_max_retention_time`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section)
+until you close the terminal window.
+
+NOTE: **Note:** Not all executors are
+[supported](https://docs.gitlab.com/runner/executors/#compatibility-chart)
+
+### Web IDE configuration file
+
+In order to enable the Web IDE terminals you need to create the file
+`.gitlab/.gitlab-webide.yml` inside the repository's root. This
+file is fairly similar to the [CI configuration file](../../../ci/yaml/README.md)
+syntax but with some restrictions:
+
+- No global blocks can be defined (ie: `before_script` or `after_script`)
+- Only one job named `terminal` can be added to this file.
+- Only the keywords `image`, `services`, `tags`, `before_script`, `script`, and
+`variables` are allowed to be used to configure the job.
+- To connect to the interactive terminal, the `terminal` job must be still alive
+and running, otherwise the terminal won't be able to connect to the job's session.
+By default the `script` keyword has the value `sleep 60` to prevent
+the job from ending and giving the Web IDE enough time to connect. This means
+that, if you override the default `script` value, you'll have to add a command
+which would keep the job running, like `sleep`.
+
+In the code below there is an example of this configuration file:
+
+```yaml
+terminal:
+ before_script:
+ - apt-get update
+ script: sleep 60
+ variables:
+ RAILS_ENV: "test"
+ NODE_ENV: "test"
+```
+
+Once the terminal has started, the console will be displayed and we could access
+the project repository files.
+
+**Important**. The terminal job is branch dependant. This means that the
+configuration file used to trigger and configure the terminal will be the one in
+the selected branch of the Web IDE.
+
+If there is no configuration file in a branch, an error message will be shown.
+
+### Running Interactive Terminals in the Web IDE
+
+If Interactive Terminals are available for the current user, the **Terminal** button
+will be visible in the right sidebar of the Web IDE. Click this button to open
+or close the terminal tab.
+
+Once open, the tab will show the **Start Web Terminal** button. This button may
+be disabled if the environment is not configured correctly. If so, a status
+message will describe the issue. Here are some reasons why **Start Web Terminal**
+may be disabled:
+
+- `.gitlab/.gitlab-webide.yml` does not exist or is set up incorrectly.
+- No active private runners are available for the project.
+
+If active, clicking the **Start Web Terminal** button will load the terminal view
+and start connecting to the runner's terminal. At any time, the **Terminal** tab
+can be closed and reopened and the state of the terminal will not be affected.
+
+When the terminal is started and is successfully connected to the runner, then the
+runner's shell prompt will appear in the terminal. From here, you can enter
+commands that will be executed within the runner's environment. This is similar
+to running commands in a local terminal or through SSH.
+
+While the terminal is running, it can be stopped by clicking **Stop Terminal**.
+This will disconnect the terminal and stop the runner's terminal job. From here,
+click **Restart Terminal** to start a new terminal session.
+
+### Limitations
+
+Interactive Terminals is in a beta phase and will continue to be improved upon in upcoming
+releases. In the meantime, please note that the user is limited to having only one
+active terminal at a time.
+
+### Troubleshooting
+
+- If the terminal's text is gray and unresponsive, then the terminal has stopped
+ and it can no longer be used. A stopped terminal can be restarted by clicking
+ **Restart Terminal**.
+- If the terminal displays **Connection Failure**, then the terminal could not
+ connect to the runner. Please try to stop and restart the terminal. If the
+ problem persists, double check your runner configuration.
+
+
[ce]: https://about.gitlab.com/pricing/
[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index 127a30d6669..e3c6cd6d6ff 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -12,10 +12,6 @@ You can create Wiki pages in the web interface or
[locally using Git](#adding-and-editing-wiki-pages-locally) since every Wiki is
a separate Git repository.
->**Note:**
-A [permission level][permissions] of **Guest** is needed to view a Wiki and
-**Developer** is needed to create and edit Wiki pages.
-
## First time creating the Home page
The first time you visit a Wiki, you will be directed to create the Home page.
@@ -28,6 +24,9 @@ message.
## Creating a new wiki page
+NOTE: **Note:**
+Requires Developer [permissions](../../permissions.md).
+
Create a new page by clicking the **New page** button that can be found
in all wiki pages. You will be asked to fill in the page name from which GitLab
will create the path to the page. You can specify a full path for the new file
@@ -58,12 +57,18 @@ repository, you will have to upload them again.
## Editing a wiki page
+NOTE: **Note:**
+Requires Developer [permissions](../../permissions.md).
+
To edit a page, simply click on the **Edit** button. From there on, you can
change its content. When done, click **Save changes** for the changes to take
effect.
## Deleting a wiki page
+NOTE: **Note:**
+Requires Maintainer [permissions](../../permissions.md).
+
You can find the **Delete** button only when editing a page. Click on it and
confirm you want the page to be deleted.
@@ -109,11 +114,11 @@ them like you would do with every other Git repository.
On the right sidebar, click on **Clone repository** and follow the on-screen
instructions.
-[permissions]: ../../permissions.md
-
## Customizing sidebar
-By default, the wiki would render a sidebar which lists all the pages for the
-wiki. You could as well provide a `_sidebar` page to replace this default
-sidebar. When this customized sidebar page is provided, the default sidebar
-would not be rendered, but the customized one.
+On the project's Wiki page, there is a right side navigation that renders the full Wiki pages list by default, with hierarchy.
+
+If the Wiki repository contains a `_sidebar` page, the file of this page replaces the default side navigation.
+This custom file serves to render it's custom content, fully replacing the standard sidebar.
+
+Support for displaying a generated TOC with a custom side navigation is planned.
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 9aa81e33fc0..9e8475d8294 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -83,6 +83,7 @@ Currently the following names are reserved as top level groups:
- unsubscribes
- uploads
- users
+- v2
These group names are unavailable as subgroup names:
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
new file mode 100644
index 00000000000..f80f4183802
--- /dev/null
+++ b/doc/user/search/advanced_global_search.md
@@ -0,0 +1,75 @@
+# Advanced Global Search **[STARTER ONLY]**
+
+> - [Introduced][ee-109] in GitLab [Starter][ee] 8.4.
+> - This is the user documentation. To install and configure Elasticsearch,
+> visit the [administrator documentation](../../integration/elasticsearch.md).
+
+NOTE: **Note**
+Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
+
+Leverage Elasticsearch for faster, more advanced code search across your entire
+GitLab instance.
+
+## Overview
+
+The Advanced Global Search in GitLab is a powerful search service that saves
+you time. Instead of creating duplicate code and wasting time, you can
+now search for code within other teams that can help your own project.
+
+GitLab leverages the search capabilities of [Elasticsearch] and enables it when
+searching in:
+
+- GitLab application
+- Projects
+- Repositories
+- Commits
+- Issues
+- Merge requests
+- Milestones
+- Notes (comments)
+- Snippets
+- Wiki
+
+## Use cases
+
+The Advanced Global Search can be useful in various scenarios.
+
+### Faster searches
+
+If you are dealing with huge amount of data and want to keep GitLab's search
+fast, the Advanced Global Search will help you achieve that.
+
+### Promote innersourcing
+
+Your company may consist of many different developer teams each of which has
+their own group where the various projects are hosted. Some of your applications
+may be connected to each other, so your developers need to instantly search
+throughout the GitLab instance and find the code they search for.
+
+## Searching globally
+
+Just use the search as before and GitLab will show you matching code from each
+project you have access to.
+
+![Advanced Global Search](img/advanced_global_search.png)
+
+You can also use the [Advanced Syntax Search](advanced_search_syntax.md) which
+provides some useful queries.
+
+>**Note:**
+Elasticsearch has only data for the default branch. That means that if you go
+to the repository tree and switch the branch from the default to something else,
+then the "Code" tab in the search result page will be served by the regular
+search even if Elasticsearch is enabled.
+
+[ee-1305]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1305
+[aws-elastic]: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html
+[aws-iam]: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html
+[aws-instance-profile]: http://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli
+[ee-109]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/109 "Elasticsearch Merge Request"
+[elasticsearch]: https://www.elastic.co/products/elasticsearch "Elasticsearch website"
+[install]: https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html "Elasticsearch installation documentation"
+[pkg]: https://about.gitlab.com/downloads/ "Download Omnibus GitLab"
+[elastic-settings]: https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#settings "Elasticsearch configuration settings"
+[ee]: https://about.gitlab.com/pricing/
+[es]: https://www.elastic.co/products/elasticsearch
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
new file mode 100644
index 00000000000..d302cb7a809
--- /dev/null
+++ b/doc/user/search/advanced_search_syntax.md
@@ -0,0 +1,69 @@
+# Advanced Syntax Search **[STARTER ONLY]**
+
+> **Notes:**
+> - Introduced in [GitLab Enterprise Starter][ee] 9.2
+> - This is the user documentation. To install and configure Elasticsearch,
+> visit the [administrator documentation](../../integration/elasticsearch.md).
+
+NOTE: **Note**
+Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
+
+Use advanced queries for more targeted search results.
+
+## Overview
+
+The Advanced Syntax Search is a subset of the
+[Advanced Global Search](advanced_global_search.md), which you can use if you
+want to have more specific search results.
+
+## Use cases
+
+Let's say for example that the product you develop relies on the code of another
+product that's hosted under some other group.
+
+Since under your GitLab instance there are hosted hundreds of different projects,
+you need the search results to be as efficient as possible. You have a feeling
+of what you want to find (e.g., a function name), but at the same you're also
+not so sure.
+
+In that case, using the advanced search syntax in your query will yield much
+better results.
+
+## Using the Advanced Syntax Search
+
+The Advanced Syntax Search supports fuzzy or exact search queries with prefixes,
+boolean operators, and much more.
+
+Full details can be found in the [Elasticsearch documentation][elastic], but
+here's a quick guide:
+
+- Searches look for all the words in a query, in any order - e.g.: searching
+ issues for `display bug` will return all issues matching both those words, in any order.
+- To find the exact phrase (stemming still applies), use double quotes: `"display bug"`
+- To find bugs not mentioning display, use `-`: `bug -display`
+- To find a bug in display or sound, use `|`: `bug display | sound`
+- To group terms together, use parentheses: `bug | (display +sound)`
+- To match a partial word, use `*`: `bug find_by_*`
+- To find a term containing one of these symbols, use `\`: `argument \-last`
+
+### Syntax search filters
+
+The Advanced Syntax Search also supports the use of filters. The available filters are:
+
+ - filename: Filters by filename. You can use the glob (`*`) operator for fuzzy matching.
+ - path: Filters by path. You can use the glob (`*`) operator for fuzzy matching.
+ - extension: Filters by extension in the filename. Please write the extension without a leading dot. Exact match only.
+
+To use them, simply add them to your query in the format `<filter_name>:<value>` without
+ any spaces between the colon (`:`) and the value.
+
+Examples:
+
+- Finding a file with any content named `hello_world.rb`: `* filename:hello_world.rb`
+- Finding a file named `hello_world` with the text `whatever` inside of it: `whatever filename:hello_world`
+- Finding the text 'def create' inside files with the `.rb` extension: `def create extension:rb`
+- Finding the text `sha` inside files in a folder called `encryption`: `sha path:encryption`
+- Finding any file starting with `hello` containing `world` and with the `.js` extension: `world filename:hello* extension:js`
+
+[ee]: https://about.gitlab.com/pricing/
+[elastic]: https://www.elastic.co/guide/en/elasticsearch/reference/5.3/query-dsl-simple-query-string-query.html#_simple_query_string_syntax
diff --git a/doc/user/search/img/advanced_global_search.png b/doc/user/search/img/advanced_global_search.png
new file mode 100644
index 00000000000..4903bbb07e1
--- /dev/null
+++ b/doc/user/search/img/advanced_global_search.png
Binary files differ
diff --git a/doc/user/search/img/issues_any_assignee.png b/doc/user/search/img/issues_any_assignee.png
deleted file mode 100644
index 2f902bcc66c..00000000000
--- a/doc/user/search/img/issues_any_assignee.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/search/img/issues_author.png b/doc/user/search/img/issues_author.png
deleted file mode 100644
index 792f9746db6..00000000000
--- a/doc/user/search/img/issues_author.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/search/img/multiple_assignees.png b/doc/user/search/img/multiple_assignees.png
new file mode 100644
index 00000000000..5c46f3dda46
--- /dev/null
+++ b/doc/user/search/img/multiple_assignees.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 770cef42995..bb6c48471c7 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -84,6 +84,12 @@ You can view recent searches by clicking on the little arrow-clock icon, which i
Individual filters can be removed by clicking on the filter's (x) button or backspacing. The entire search filter can be cleared by clicking on the search box's (x) button.
+## Filtering with multiple filters of the same type
+
+Some filters can be added multiple times. These include but are not limited to assignees and labels. When you filter with these multiple filters of the same type, the AND logic is applied. For example, if you were filtering `assignee:@sam assignee:@sarah`, your results will only include entries whereby the assignees are assigned to both Sam and Sarah are returned.
+
+![multiple assignees filtering](img/multiple_assignees.png)
+
### Shortcut
You'll also find a shortcut on the search field on the top-right of the project's dashboard to
@@ -96,7 +102,7 @@ quickly access issues and merge requests created or assigned to you within that
Your [todos](../../workflow/todos.md#gitlab-todos) can be searched by "to do" and "done".
You can [filter](../../workflow/todos.md#filtering-your-todos) them per project,
author, type, and action. Also, you can sort them by
-[**Label priority**](../../user/project/labels.md#prioritize-labels),
+[**Label priority**](../../user/project/labels.md#label-priority),
**Last created** and **Oldest created**.
## Projects
@@ -136,3 +142,18 @@ you'll be able to, besides filtering them by **Name**, **Author**, **Assignee**,
and **Labels**, select multiple issues to add to a list of your choice:
![search and select issues to add to board](img/search_issues_board.png)
+
+## Advanced Global Search **[STARTER]**
+
+Leverage Elasticsearch for faster, more advanced code search across your entire
+GitLab instance.
+
+[Learn how to use the Advanced Global Search.](advanced_global_search.md)
+
+## Advanced Syntax Search **[STARTER]**
+
+Use advanced queries for more targeted search results.
+
+[Learn how to use the Advanced Syntax Search.](advanced_search_syntax.md)
+
+[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 569bdc9e2d5..7b580a057f2 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -20,7 +20,7 @@ and private. See [Public access](../public_access/public_access.md) for more inf
## Project snippets
Project snippets are always related to a specific project.
-See [Project's features](project/index.md#projects-features) for more information.
+See [Project features](project/index.md#project-features) for more information.
## Discover snippets
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 0ebe5eea173..fffb6a5d86d 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/webhooks.md'
+---
+
This document was moved to [project/integrations/webhooks](../user/project/integrations/webhooks.md).
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 272f7807ac0..40e2486ace5 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -13,12 +13,15 @@ comments: false
- [Groups](../user/group/index.md)
- Issues - The GitLab Issue Tracker is an advanced and complete tool for
tracking the evolution of a new idea or the process of solving a problem.
+ - [Exporting Issues](../user/project/issues/csv_export.md) **[STARTER]** Export issues as a CSV, emailed as an attachment.
- [Confidential issues](../user/project/issues/confidential_issues.md)
- [Due date for issues](../user/project/issues/due_dates.md)
- [Issue Board](../user/project/issue_board.md)
- [Keyboard shortcuts](shortcuts.md)
- [File finder](file_finder.md)
+- [File lock](../user/project/file_lock.md) **[PREMIUM]**
- [Labels](../user/project/labels.md)
+- [Issue weight](issue_weight.md) **[STARTER]**
- [Notification emails](notifications.md)
- [Projects](../user/project/index.md)
- [Project forking workflow](forking_workflow.md)
@@ -41,6 +44,9 @@ comments: false
- [Merge requests versions](../user/project/merge_requests/versions.md)
- ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md)
- [Fast-forward merge requests](../user/project/merge_requests/fast_forward_merge.md)
+ - [Merge request approvals](../user/project/merge_requests/merge_request_approvals.md) **[STARTER]**
+- [Repository mirroring](repository_mirroring.md) **[STARTER]**
+- [Service Desk](../user/project/service_desk.md) **[PREMIUM]**
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
- [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md)
- [Todos](todos.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index 35cc080d2b7..f1ec771dd9a 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/members/index.md'
+---
+
This document was moved to [../../user/project/members/index.md](../../user/project/members/index.md)
diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md
index 7bf80a3ad0d..8e43d340613 100644
--- a/doc/workflow/authorization_for_merge_requests.md
+++ b/doc/workflow/authorization_for_merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/authorization_for_merge_requests.md'
+---
+
This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md)
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
index d74378cc564..02db97b8dd6 100644
--- a/doc/workflow/award_emoji.md
+++ b/doc/workflow/award_emoji.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/award_emojis.md'
+---
+
This document was moved to [another location](../user/award_emojis.md).
diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png
deleted file mode 100644
index 1ad634a343e..00000000000
--- a/doc/workflow/award_emoji.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
index 663ffd3f746..29c4f854416 100644
--- a/doc/workflow/cherry_pick_changes.md
+++ b/doc/workflow/cherry_pick_changes.md
@@ -1 +1,5 @@
-This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md).
+---
+redirect_to: '../user/project/merge_requests/cherry_pick_changes.md'
+---
+
+This document was moved to [another location](../user/project/merge_requests/cherry_pick_changes.md).
diff --git a/doc/workflow/ff_merge.md b/doc/workflow/ff_merge.md
new file mode 100644
index 00000000000..11e9e1bbd6b
--- /dev/null
+++ b/doc/workflow/ff_merge.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/fast_forward_merge.md'
+---
+
+This document was moved to [user/project/merge_requests/fast_forward_merge](../user/project/merge_requests/fast_forward_merge.md).
diff --git a/doc/workflow/git_annex.md b/doc/workflow/git_annex.md
new file mode 100644
index 00000000000..84d25951908
--- /dev/null
+++ b/doc/workflow/git_annex.md
@@ -0,0 +1,238 @@
+# Git annex
+
+> **Warning:** GitLab has [completely
+removed][deprecate-annex-issue] in GitLab 9.0 (2017/03/22).
+Read through the [migration guide from git-annex to git-lfs][guide].
+
+The biggest limitation of Git, compared to some older centralized version
+control systems, has been the maximum size of the repositories.
+
+The general recommendation is to not have Git repositories larger than 1GB to
+preserve performance. Although GitLab has no limit (some repositories in GitLab
+are over 50GB!), we subscribe to the advice to keep repositories as small as
+you can.
+
+Not being able to version control large binaries is a big problem for many
+larger organizations.
+Videos, photos, audio, compiled binaries and many other types of files are too
+large. As a workaround, people keep artwork-in-progress in a Dropbox folder and
+only check in the final result. This results in using outdated files, not
+having a complete history and increases the risk of losing work.
+
+This problem is solved in GitLab Enterprise Edition by integrating the
+[git-annex] application.
+
+`git-annex` allows managing large binaries with Git without checking the
+contents into Git.
+You check-in only a symlink that contains the SHA-1 of the large binary. If you
+need the large binary, you can sync it from the GitLab server over `rsync`, a
+very fast file copying tool.
+
+## GitLab git-annex Configuration
+
+`git-annex` is disabled by default in GitLab. Below you will find the
+configuration options required to enable it.
+
+### Requirements
+
+`git-annex` needs to be installed both on the server and the client side.
+
+For Debian-like systems (e.g., Debian, Ubuntu) this can be achieved by running:
+
+```
+sudo apt-get update && sudo apt-get install git-annex
+```
+
+For RedHat-like systems (e.g., CentOS, RHEL) this can be achieved by running:
+
+```
+sudo yum install epel-release && sudo yum install git-annex
+```
+
+### Configuration for Omnibus packages
+
+For omnibus-gitlab packages, only one configuration setting is needed.
+The Omnibus package will internally set the correct options in all locations.
+
+1. In `/etc/gitlab/gitlab.rb` add the following line:
+
+ ```ruby
+ gitlab_shell['git_annex_enabled'] = true
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+### Configuration for installations from source
+
+There are 2 settings to enable git-annex on your GitLab server.
+
+One is located in `config/gitlab.yml` of the GitLab repository and the other
+one is located in `config.yml` of gitlab-shell.
+
+1. In `config/gitlab.yml` add or edit the following lines:
+
+ ```yaml
+ gitlab_shell:
+ git_annex_enabled: true
+ ```
+
+1. In `config.yml` of gitlab-shell add or edit the following lines:
+
+ ```yaml
+ git_annex_enabled: true
+ ```
+
+1. Save the files and [restart GitLab][] for the changes to take effect.
+
+## Using GitLab git-annex
+
+> **Note:**
+> Your Git remotes must be using the SSH protocol, not HTTP(S).
+
+Here is an example workflow of uploading a very large file and then checking it
+into your Git repository:
+
+```bash
+git clone git@example.com:group/project.git
+
+git annex init 'My Laptop' # initialize the annex project and give an optional description
+cp ~/tmp/debian.iso ./ # copy a large file into the current directory
+git annex add debian.iso # add the large file to git annex
+git commit -am "Add Debian iso" # commit the file metadata
+git annex sync --content # sync the Git repo and large file to the GitLab server
+```
+
+The output should look like this:
+
+```
+commit
+On branch master
+Your branch is ahead of 'origin/master' by 1 commit.
+ (use "git push" to publish your local commits)
+nothing to commit, working tree clean
+ok
+pull origin
+remote: Counting objects: 5, done.
+remote: Compressing objects: 100% (4/4), done.
+remote: Total 5 (delta 2), reused 0 (delta 0)
+Unpacking objects: 100% (5/5), done.
+From example.com:group/project
+ 497842b..5162f80 git-annex -> origin/git-annex
+ok
+(merging origin/git-annex into git-annex...)
+(recording state in git...)
+copy debian.iso (checking origin...) (to origin...)
+SHA256E-s26214400--8092b3d482fb1b7a5cf28c43bc1425c8f2d380e86869c0686c49aa7b0f086ab2.iso
+ 26,214,400 100% 638.88kB/s 0:00:40 (xfr#1, to-chk=0/1)
+ok
+pull origin
+ok
+(recording state in git...)
+push origin
+Counting objects: 15, done.
+Delta compression using up to 4 threads.
+Compressing objects: 100% (13/13), done.
+Writing objects: 100% (15/15), 1.64 KiB | 0 bytes/s, done.
+Total 15 (delta 1), reused 0 (delta 0)
+To example.com:group/project.git
+ * [new branch] git-annex -> synced/git-annex
+ * [new branch] master -> synced/master
+ok
+```
+
+Your files can be found in the `master` branch, but you'll notice that there
+are more branches created by the `annex sync` command.
+
+Git Annex will also create a new directory at `.git/annex/` and will record the
+tracked files in the `.git/config` file. The files you assign to be tracked
+with `git-annex` will not affect the existing `.git/config` records. The files
+are turned into symbolic links that point to data in `.git/annex/objects/`.
+
+The `debian.iso` file in the example will contain the symbolic link:
+
+```
+.git/annex/objects/ZW/1k/SHA256E-s82701--6384039733b5035b559efd5a2e25a493ab6e09aabfd5162cc03f6f0ec238429d.png/SHA256E-s82701--6384039733b5035b559efd5a2e25a493ab6e09aabfd5162cc03f6f0ec238429d.iso
+```
+
+Use `git annex info` to retrieve the information about the local copy of your
+repository.
+
+---
+
+Downloading a single large file is also very simple:
+
+```bash
+git clone git@gitlab.example.com:group/project.git
+
+git annex sync # sync Git branches but not the large file
+git annex get debian.iso # download the large file
+```
+
+To download all files:
+
+```bash
+git clone git@gitlab.example.com:group/project.git
+
+git annex sync --content # sync Git branches and download all the large files
+```
+
+By using `git-annex` without GitLab, anyone that can access the server can also
+access the files of all projects, but GitLab Annex ensures that you can only
+access files of projects you have access to (developer, maintainer, or owner role).
+
+## How it works
+
+Internally GitLab uses [GitLab Shell] to handle SSH access and this was a great
+integration point for `git-annex`.
+There is a setting in gitlab-shell so you can disable GitLab Annex support
+if you want to.
+
+## Troubleshooting tips
+
+Differences in version of `git-annex` on the GitLab server and on local machines
+can cause `git-annex` to raise unpredicted warnings and errors.
+
+Consult the [Annex upgrade page][annex-upgrade] for more information about
+the differences between versions. You can find out which version is installed
+on your server by navigating to <https://pkgs.org/download/git-annex> and
+searching for your distribution.
+
+Although there is no general guide for `git-annex` errors, there are a few tips
+on how to go around the warnings.
+
+### git-annex-shell: Not a git-annex or gcrypt repository.
+
+This warning can appear on the initial `git annex sync --content` and is caused
+by differences in `git-annex-shell`. You can read more about it
+[in this git-annex issue][issue].
+
+One important thing to note is that despite the warning, the `sync` succeeds
+and the files are pushed to the GitLab repository.
+
+If you get hit by this, you can run the following command inside the repository
+that the warning was raised:
+
+```
+git config remote.origin.annex-ignore false
+```
+
+Consecutive runs of `git annex sync --content` **should not** produce this
+warning and the output should look like this:
+
+```
+commit ok
+pull origin
+ok
+pull origin
+ok
+push origin
+```
+
+[annex-upgrade]: https://git-annex.branchable.com/upgrades/
+[deprecate-annex-issue]: https://gitlab.com/gitlab-org/gitlab-ee/issues/1648
+[git-annex]: https://git-annex.branchable.com/ "git-annex website"
+[gitlab shell]: https://gitlab.com/gitlab-org/gitlab-shell "GitLab Shell repository"
+[guide]: lfs/migrate_from_git_annex_to_git_lfs.html
+[issue]: https://git-annex.branchable.com/forum/Error_from_git-annex-shell_on_creation_of_gcrypt_special_remote/ "git-annex issue"
+[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md
new file mode 100644
index 00000000000..da217b0a5da
--- /dev/null
+++ b/doc/workflow/git_lfs.md
@@ -0,0 +1,5 @@
+---
+redirect_to: 'lfs/manage_large_binaries_with_git_lfs.md'
+---
+
+This document was moved to [another location](lfs/manage_large_binaries_with_git_lfs.md).
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 1b9fb504b15..7d0abb93262 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -166,7 +166,7 @@ This branch is the place for any work related to this change.
NOTE: **Note:**
The name of a branch might be dictated by organizational standards.
-For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#cherry-picking-from-ce-to-ee).
+For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](../development/automatic_ce_ee_merge.md#cherry-picking-from-ce-to-ee).
When you are done or want to discuss the code, open a merge request.
A merge request is an online place to discuss the change and review the code.
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 06eec1ed928..c7f4647baa9 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../user/group/index.md'
+---
This document was moved to [another location](../user/group/index.md).
diff --git a/doc/workflow/img/copy_ssh_public_key_button.png b/doc/workflow/img/copy_ssh_public_key_button.png
new file mode 100644
index 00000000000..e20dae09a4d
--- /dev/null
+++ b/doc/workflow/img/copy_ssh_public_key_button.png
Binary files differ
diff --git a/doc/workflow/img/new_branch_from_issue.png b/doc/workflow/img/new_branch_from_issue.png
deleted file mode 100644
index 286d775bb9e..00000000000
--- a/doc/workflow/img/new_branch_from_issue.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png
index 8a5494d16a8..72f7418f1f8 100644
--- a/doc/workflow/img/notification_global_settings.png
+++ b/doc/workflow/img/notification_global_settings.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_pull_settings_upper.png b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
index c60354fdca7..8e15b5a0784 100644
--- a/doc/workflow/img/repository_mirroring_pull_settings_upper.png
+++ b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
Binary files differ
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index e0a445920b4..29da321ba46 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/index.md'
+---
+
This document was moved to [another location](../../user/project/import/index.md).
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md
index ec9a11f390e..a42ba7d4518 100644
--- a/doc/workflow/importing/import_projects_from_bitbucket.md
+++ b/doc/workflow/importing/import_projects_from_bitbucket.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/bitbucket.md'
+---
+
This document was moved to [another location](../../user/project/import/bitbucket.md).
diff --git a/doc/workflow/importing/import_projects_from_fogbugz.md b/doc/workflow/importing/import_projects_from_fogbugz.md
index 876eb0434f0..f5c791dc6de 100644
--- a/doc/workflow/importing/import_projects_from_fogbugz.md
+++ b/doc/workflow/importing/import_projects_from_fogbugz.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/fogbugz.md'
+---
+
This document was moved to [another location](../../user/project/import/fogbugz.md).
diff --git a/doc/workflow/importing/import_projects_from_gitea.md b/doc/workflow/importing/import_projects_from_gitea.md
index 8b55b6c23eb..df053835b44 100644
--- a/doc/workflow/importing/import_projects_from_gitea.md
+++ b/doc/workflow/importing/import_projects_from_gitea.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/gitea.md'
+---
+
This document was moved to [another location](../../user/project/import/gitea.md).
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 72dfe5403c3..6397fcc74b8 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/github.md'
+---
+
This document was moved to [another location](../../user/project/import/github.md).
diff --git a/doc/workflow/importing/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index 3256088c014..135b9704df9 100644
--- a/doc/workflow/importing/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/gitlab_com.md'
+---
+
This document was moved to [another location](../../user/project/import/gitlab_com.md).
diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 32a75a6c6af..99f13d6354c 100644
--- a/doc/workflow/importing/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/svn.md'
+---
+
This document was moved to [another location](../../user/project/import/svn.md).
diff --git a/doc/workflow/issue_weight.md b/doc/workflow/issue_weight.md
new file mode 100644
index 00000000000..267160dae2a
--- /dev/null
+++ b/doc/workflow/issue_weight.md
@@ -0,0 +1,22 @@
+# Issue Weight **[STARTER]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76)
+> in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+
+When you have a lot of issues, it can be hard to get an overview.
+By adding a weight to each issue, you can get a better idea of how much time,
+value or complexity a given issue has or will cost.
+
+You can set the weight of an issue during its creation, by simply changing the
+value in the dropdown menu. You can set it to a non-negative integer
+value from 0, 1, 2, and so on. (The database stores a 4-byte value, so the
+upper bound is essentially limitless).
+You can remove weight from an issue
+as well.
+
+This value will appear on the right sidebar of an individual issue, as well as
+in the issues page next to a distinctive balance scale icon.
+
+As an added bonus, you can see the total sum of all issues on the milestone page.
+
+![issue page](issue_weight/issue.png)
diff --git a/doc/workflow/issue_weight/issue.png b/doc/workflow/issue_weight/issue.png
new file mode 100644
index 00000000000..3800b5940b8
--- /dev/null
+++ b/doc/workflow/issue_weight/issue.png
Binary files differ
diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md
index 5c09891dfdd..3d07d411dd4 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../user/project/labels.md'
+---
+
# Labels
This document was moved to [user/project/labels.md](../user/project/labels.md).
diff --git a/doc/workflow/lfs/images/git-annex-branches.png b/doc/workflow/lfs/images/git-annex-branches.png
new file mode 100644
index 00000000000..3d614f68177
--- /dev/null
+++ b/doc/workflow/lfs/images/git-annex-branches.png
Binary files differ
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 3fb553280d0..6d941135bf2 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -108,6 +108,18 @@ Here is a configuration example with GCS.
_NOTE: The service account must have permission to access the bucket. [See more](https://cloud.google.com/storage/docs/authentication)_
+Here is a configuration example with Rackspace Cloud Files.
+
+| Setting | Description | example |
+|---------|-------------|---------|
+| `provider` | The provider name | `Rackspace` |
+| `rackspace_username` | The username of the Rackspace account with access to the container | `joe.smith` |
+| `rackspace_api_key` | The API key of the Rackspace account with access to the container | `ABC123DEF456ABC123DEF456ABC123DE` |
+| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` |
+| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` |
+
+_NOTES: Regardless of whether the container has public access enabled or disabled, Fog will use the TempURL method to grant access to LFS objects. If you see errors in logs referencing instantiating storage with a temp-url-key, ensure that you have set they key properly on the Rackspace API and in gitlab.rb. You can verify the value of the key Rackspace has set by sending a GET request with token header to the service access endpoint URL and comparing the output of the returned headers._
+
### Manual uploading to an object storage
There are two ways to manually do the same thing as automatic uploading (described above).
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index da0243705aa..202f2e39975 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -250,6 +250,7 @@ If you are storing LFS files outside of GitLab you can disable LFS on the projec
It is possible to host LFS objects externally by setting a custom LFS url with `git config -f .lfsconfig lfs.url https://example.com/<project>.git/info/lfs`.
-Because GitLab verifies the existence of objects referenced by LFS pointers, push will fail when LFS is enabled for the project.
+You might choose to do this if you are using an appliance like a Sonatype Nexus to store LFS data. If you choose to use an external LFS store,
+GitLab will not be able to verify LFS objects which means that pushes will fail if you have GitLab LFS support enabled.
-LFS can be disabled from the [Project settings](../../user/project/settings/index.md).
+To stop push failure, LFS support can be disabled in the [Project settings](../../user/project/settings/index.md). This means you will lose GitLab LFS value-adds (Verifying LFS objects, UI integration for LFS).
diff --git a/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
new file mode 100644
index 00000000000..71c73e3dffe
--- /dev/null
+++ b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
@@ -0,0 +1,255 @@
+# Migration guide from Git Annex to Git LFS
+
+>**Note:**
+Git Annex support [has been removed][issue-remove-annex] in GitLab Enterprise
+Edition 9.0 (2017/03/22).
+
+Both [Git Annex][] and [Git LFS][] are tools to manage large files in Git.
+
+## History
+
+Git Annex [was introduced in GitLab Enterprise Edition 7.8][post-3], at a time
+where Git LFS didn't yet exist. A few months later, GitLab brought support for
+Git LFS in [GitLab 8.2][post-2] and is available for both Community and
+Enterprise editions.
+
+## Differences between Git Annex and Git LFS
+
+Some items below are general differences between the two protocols and some are
+ones that GitLab developed.
+
+- Git Annex works only through SSH, whereas Git LFS works both with SSH and HTTPS
+ (SSH support was added in GitLab 8.12).
+- Annex files are stored in a sub-directory of the normal repositories, whereas
+ LFS files are stored outside of the repositories in a place you can define.
+- Git Annex requires a more complex setup, but has much more options than Git
+ LFS. You can compare the commands each one offers by running `man git-annex`
+ and `man git-lfs`.
+- Annex files cannot be browsed directly in GitLab's interface, whereas LFS
+ files can.
+
+## Migration steps
+
+>**Note:**
+Since Git Annex files are stored in a sub-directory of the normal repositories
+(`.git/annex/objects`) and LFS files are stored outside of the repositories,
+they are not compatible as they are using a different scheme. Therefore, the
+migration has to be done manually per repository.
+
+There are basically two steps you need to take in order to migrate from Git
+Annex to Git LFS.
+
+### TL; DR
+
+If you know what you are doing and want to skip the reading, this is what you
+need to do (we assume you have [git-annex enabled](../git_annex.md#using-gitlab-git-annex) in your
+repository and that you have made backups in case something goes wrong).
+Fire up a terminal, navigate to your Git repository and:
+
+
+1. Disable `git-annex`:
+
+ ```bash
+ git annex sync --content
+ git annex direct
+ git annex uninit
+ git annex indirect
+ ```
+
+1. Enable `git-lfs`:
+
+ ```
+ git lfs install
+ git lfs track <files>
+ git add .
+ git commit -m "commit message"
+ git push
+ ```
+
+### Disabling Git Annex in your repo
+
+Before changing anything, make sure you have a backup of your repository first.
+There are a couple of ways to do that, but you can simply clone it to another
+local path and maybe push it to GitLab if you want a remote backup as well.
+Here you'll find a guide on
+[how to back up a **git-annex** repository to an external hard drive][bkp-ext-drive].
+
+Since Annex files are stored as objects with symlinks and cannot be directly
+modified, we need to first remove those symlinks.
+
+>**Note:**
+Make sure the you read about the [`direct` mode][annex-direct] as it contains
+useful information that may fit in your use case. Note that `annex direct` is
+deprecated in Git Annex version 6, so you may need to upgrade your repository
+if the server also has Git Annex 6 installed. Read more in the
+[Git Annex troubleshooting tips](../git_annex.md#troubleshooting-tips) section.
+
+1. Backup your repository
+
+ ```bash
+ cd repository
+ git annex sync --content
+ cd ..
+ git clone repository repository-backup
+ cd repository-backup
+ git annex get
+ cd ..
+ ```
+
+1. Use `annex direct`:
+
+ ```bash
+ cd repository
+ git annex direct
+ ```
+
+ The output should be similar to this:
+
+ ```bash
+ commit
+ On branch master
+ Your branch is up-to-date with 'origin/master'.
+ nothing to commit, working tree clean
+ ok
+ direct debian.iso ok
+ direct ok
+ ```
+
+1. Disable Git Annex with [`annex uninit`][uninit]:
+
+ ```bash
+ git annex uninit
+ ```
+
+ The output should be similar to this:
+
+ ```bash
+ unannex debian.iso ok
+ Deleted branch git-annex (was 2534d2c).
+ ```
+
+ This will `unannex` every file in the repository, leaving the original files.
+
+1. Switch back to `indirect` mode:
+
+ ```bash
+ git annex indirect
+ ```
+
+ The output should be similar to this:
+
+ ```bash
+ (merging origin/git-annex into git-annex...)
+ (recording state in git...)
+ commit (recording state in git...)
+
+ ok
+ (recording state in git...)
+ [master fac3194] commit before switching to indirect mode
+ 1 file changed, 1 deletion(-)
+ delete mode 120000 alpine-virt-3.4.4-x86_64.iso
+ ok
+ indirect ok
+ ok
+ ```
+
+---
+
+At this point, you have two options. Either add, commit and push the files
+directly back to GitLab or switch to Git LFS. We will tackle the LFS switch in
+the next section.
+
+### Enabling Git LFS in your repo
+
+Git LFS is enabled by default on all GitLab products (GitLab CE, GitLab EE,
+GitLab.com), therefore, you don't need to do anything server-side.
+
+1. First, make sure you have `git-lfs` installed locally:
+
+ ```bash
+ git lfs help
+ ```
+
+ If the terminal doesn't prompt you with a full response on `git-lfs` commands,
+ [install the Git LFS client][install-lfs] first.
+
+1. Inside the repo, run the following command to initiate LFS:
+
+ ```bash
+ git lfs install
+ ```
+
+1. Enable `git-lfs` for the group of files you want to track. You
+ can track specific files, all files containing the same extension, or an
+ entire directory:
+
+ ```bash
+ git lfs track images/01.png # per file
+ git lfs track **/*.png # per extension
+ git lfs track images/ # per directory
+ ```
+
+ Once you do that, run `git status` and you'll see `.gitattributes` added
+ to your repo. It collects all file patterns that you chose to track via
+ `git-lfs`.
+
+1. Add the files, commit and push them to GitLab:
+
+ ```bash
+ git add .
+ git commit -m "commit message"
+ git push
+ ```
+
+ If your remote is set up with HTTP, you will be asked to enter your login
+ credentials. If you have [2FA enabled](../../user/profile/account/two_factor_authentication.md), make sure to use a
+ [personal access token](../../user/profile/account/two_factor_authentication.md#personal-access-tokens)
+ instead of your password.
+
+## Removing the Git Annex branches
+
+After the migration finishes successfully, you can remove all `git-annex`
+related branches from your repository.
+
+On GitLab, navigate to your project's **Repository âž” Branches** and delete all
+branches created by Git Annex: `git-annex`, and all under `synced/`.
+
+![repository branches](images/git-annex-branches.png)
+
+You can also do this on the commandline with:
+
+ ```bash
+ git branch -d synced/master
+ git branch -d synced/git-annex
+ git push origin :synced/master
+ git push origin :synced/git-annex
+ git push origin :git-annex
+ git remote prune origin
+ ```
+
+If there are still some Annex objects inside your repository (`.git/annex/`)
+or references inside `.git/config`, run `annex uninit` again:
+
+```bash
+git annex uninit
+```
+
+## Further Reading
+
+- (Blog Post) [Getting Started with Git FLS][post-1]
+- (Blog Post) [Announcing LFS Support in GitLab][post-2]
+- (Blog Post) [GitLab Annex Solves the Problem of Versioning Large Binaries with Git][post-3]
+- (GitLab Docs) [Git Annex](../git_annex.md)
+- (GitLab Docs) [Git LFS](manage_large_binaries_with_git_lfs.md)
+
+[annex-direct]: https://git-annex.branchable.com/direct_mode/
+[bkp-ext-drive]: https://www.thomas-krenn.com/en/wiki/Git-annex_Repository_on_an_External_Hard_Drive
+[Git Annex]: http://git-annex.branchable.com/
+[Git LFS]: https://git-lfs.github.com/
+[install-lfs]: https://git-lfs.github.com/
+[issue-remove-annex]: https://gitlab.com/gitlab-org/gitlab-ee/issues/1648
+[lfs-track]: https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/#tracking-files-with-lfs
+[post-1]: https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/
+[post-2]: https://about.gitlab.com/2015/11/23/announcing-git-lfs-support-in-gitlab/
+[post-3]: https://about.gitlab.com/2015/02/17/gitlab-annex-solves-the-problem-of-versioning-large-binaries-with-git/
+[uninit]: https://git-annex.branchable.com/git-annex-uninit/
diff --git a/doc/workflow/merge_request_approvals.md b/doc/workflow/merge_request_approvals.md
new file mode 100644
index 00000000000..bfcd8faf236
--- /dev/null
+++ b/doc/workflow/merge_request_approvals.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/merge_request_approvals.md'
+---
+
+This document was moved to [another location](../user/project/merge_requests/merge_request_approvals.md).
diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md
index dc6da1938f3..fd9f9b81bc9 100644
--- a/doc/workflow/merge_requests.md
+++ b/doc/workflow/merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/index.md'
+---
+
This document was moved to [user/project/merge_requests/index.md](../user/project/merge_requests/index.md).
diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md
index b4f6d6117de..41e6ff0cdd6 100644
--- a/doc/workflow/merge_when_build_succeeds.md
+++ b/doc/workflow/merge_when_build_succeeds.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/merge_when_pipeline_succeeds.md'
+---
+
This document was moved to [merge_when_pipeline_succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md).
diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md
index 69eb6b286b0..18dc15f7327 100644
--- a/doc/workflow/milestones.md
+++ b/doc/workflow/milestones.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/milestones/index.md'
+---
+
This document was moved to [another location](../user/project/milestones/index.md).
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 6ce789998a4..5d560f2e000 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -16,12 +16,16 @@ Notification settings are divided into three groups:
Each of these settings have levels of notification:
+- Global: For groups and projects, notifications as per global settings.
- Watch: Receive notifications for any activity.
-- On Mention: Receive notifications when `@mentioned` in comments.
- Participate: Receive notifications for threads you have participated in.
+- On Mention: Receive notifications when `@mentioned` in comments.
- Disabled: Turns off notifications.
- Custom: Receive notifications for custom selected events.
-- Global: For groups and projects, notifications as per global settings.
+
+> Introduced in GitLab 12.0
+
+You can also select an email address to receive notifications for each group you belong to.
### Global Settings
@@ -73,40 +77,43 @@ Below is the table of events users can be notified of:
| Group access level changed | User | Sent when user group access level is changed |
| Project moved | Project members [1] | [1] not disabled |
-### Issue / Merge request events
+### Issue / Epics / Merge request events
In most of the below cases, the notification will be sent to:
- Participants:
- the author and assignee of the issue/merge request
- authors of comments on the issue/merge request
- - anyone mentioned by `@username` in the issue/merge request title or description
- - anyone mentioned by `@username` in any of the comments on the issue/merge request
- ...with notification level "Participating" or higher
+ - anyone mentioned by `@username` in the title or description of the issue, merge request or epic **[ULTIMATE]**
+ - anyone with notification level "Participating" or higher that is mentioned by `@username`
+ in any of the comments on the issue, merge request, or epic **[ULTIMATE]**
- Watchers: users with notification level "Watch"
-- Subscribers: anyone who manually subscribed to the issue/merge request
+- Subscribers: anyone who manually subscribed to the issue, merge request, or epic **[ULTIMATE]**
- Custom: Users with notification level "custom" who turned on notifications for any of the events present in the table below
| Event | Sent to |
|------------------------|---------|
-| New issue | |
-| Close issue | |
+| New issue | |
+| Close issue | |
| Reassign issue | The above, plus the old assignee |
-| Reopen issue | |
+| Reopen issue | |
| Due issue | Participants and Custom notification level with this event selected |
| Change milestone issue | Subscribers, participants mentioned, and Custom notification level with this event selected |
| Remove milestone issue | Subscribers, participants mentioned, and Custom notification level with this event selected |
-| New merge request | |
+| New merge request | |
| Push to merge request | Participants and Custom notification level with this event selected |
| Reassign merge request | The above, plus the old assignee |
-| Close merge request | |
-| Reopen merge request | |
-| Merge merge request | |
+| Close merge request | |
+| Reopen merge request | |
+| Merge merge request | |
| Change milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
| Remove milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
| Failed pipeline | The author of the pipeline |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
+| New epic **[ULTIMATE]** | |
+| Close epic **[ULTIMATE]** | |
+| Reopen epic **[ULTIMATE]** | |
In addition, if the title or description of an Issue or Merge Request is
changed, notifications will be sent to any **new** mentions by `@username` as
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index feb88712f5a..f54afb768a1 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/index.md'
+---
+
This document was moved to [../user/project/index.md](../user/project/index.md)
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index ced7d391ace..1bcac4a2de5 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/protected_branches.md'
+---
+
This document was moved to [another location](../user/project/protected_branches.md).
diff --git a/doc/workflow/rebase_before_merge.md b/doc/workflow/rebase_before_merge.md
new file mode 100644
index 00000000000..10e768d3371
--- /dev/null
+++ b/doc/workflow/rebase_before_merge.md
@@ -0,0 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/fast_forward_merge.md'
+---
+
+This document was moved to [another location](../user/project/merge_requests/fast_forward_merge.md).
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 8a2f4e1b40e..9772bd385ba 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -84,7 +84,7 @@ To set up a mirror from GitLab to GitHub, you need to follow these steps:
1. Fill in **Password** field with your GitHub personal access token.
1. Click the **Mirror repository** button.
-The mirrored repository will be listed. For example, `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git`.
+The mirrored repository will be listed. For example, `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git`.
The repository will push soon. To force a push, click the appropriate button.
@@ -99,6 +99,7 @@ The repository will push soon. To force a push, click the appropriate button.
## Pulling from a remote repository **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
+> [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
You can set up a repository to automatically have its branches, tags, and commits updated from an
upstream repository.
@@ -138,13 +139,18 @@ upstream and GitLab will no longer automatically update this branch to prevent a
### How it works
-Once you activate the pull mirroring feature, the mirror will be inserted into a queue. A scheduler
-will start every minute and schedule a fixed number of mirrors for update, based on the configured maximum capacity.
+Once the pull mirroring feature has been enabled for a repository, the repository is added to a queue.
-If the mirror updates successfully, it will be enqueued once again with a small backoff period.
+Once per minute, a Sidekiq cron job schedules repository mirrors to update, based on:
-If the mirror fails (for example, a branch diverged from upstream), the project's backoff period is
-increased each time it fails, up to a maximum amount of time.
+- The capacity available. This is determined by Sidekiq settings. For GitLab.com, see [GitLab.com Sidekiq settings](../user/gitlab_com/index.md#sidekiq).
+- The number of repository mirrors already in the queue that are due to be updated. Being due depends on when the repository mirror was last updated and how many times it's been retried.
+
+Repository mirrors are updated as Sidekiq becomes available to process them. If the process of updating the repository mirror:
+
+- Succeeds, an update will be enqueued again with at least a 30 minute wait.
+- Fails (for example, a branch diverged from upstream), it will be attempted again later. Mirrors can fail
+ up to 14 times before they will not be enqueued for update again.
### SSH authentication
@@ -217,8 +223,10 @@ being injected into your mirror, or your password being stolen.
### SSH public key authentication
To use SSH public key authentication, you'll also need to choose that option
-from the **Authentication method** dropdown. GitLab will generate a 4096-bit RSA
-key and display the public component of that key to you.
+from the **Authentication method** dropdown. When the mirror is created,
+GitLab generates a 4096-bit RSA key that can be copied by clicking the **Copy SSH public key** button.
+
+![Repository mirroring copy SSH public key to clipboard button](img/copy_ssh_public_key_button.png)
You then need to add the public SSH key to the other repository's configuration:
@@ -275,10 +283,10 @@ project mirroring again by [Forcing an update](#forcing-an-update-core).
[GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream, often minutes
-afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project),
+afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter),
updates will be pulled immediately.
-For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project).
+For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter).
## Forcing an update **[CORE]**
@@ -408,3 +416,11 @@ settings are recommended:
Perforce Helix.
Read about [Git Fusion settings on Perforce.com](https://www.perforce.com/perforce/doc.current/manuals/git-fusion/Content/Git-Fusion/section_vss_bdw_w3.html#section_zdp_zz1_3l).
+
+## Troubleshooting
+
+Should an error occur during a push, GitLab will display an "Error" highlight for that repository. Details on the error can then be seen by hovering over the highlight text.
+
+### 13:Received RST_STREAM with error code 2 with GitHub
+
+If you receive an "13:Received RST_STREAM with error code 2" while mirroring to a GitHub repository, your GitHub settings might be set to block pushes that expose your email address used in commits. Either set your email address on GitHub to be public, or disable the [Block command line pushes that expose my email](http://github.com/settings/emails) setting.
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
index cf1292253fc..15f199af703 100644
--- a/doc/workflow/revert_changes.md
+++ b/doc/workflow/revert_changes.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/revert_changes.md'
+---
+
This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md).
diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md
index 2eb4d24958a..c39cd78f32d 100644
--- a/doc/workflow/share_projects_with_other_groups.md
+++ b/doc/workflow/share_projects_with_other_groups.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/members/share_project_with_groups.md'
+---
+
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 2eb4d24958a..c39cd78f32d 100644
--- a/doc/workflow/share_with_group.md
+++ b/doc/workflow/share_with_group.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/members/share_project_with_groups.md'
+---
+
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.png b/doc/workflow/share_with_group.png
deleted file mode 100644
index 2c47625e29a..00000000000
--- a/doc/workflow/share_with_group.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index 7359e1c6119..5068b5d4d20 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -1,6 +1,6 @@
# GitLab keyboard shortcuts
-You can see GitLab's keyboard shortcuts by using 'shift + ?'
+You can see GitLab's keyboard shortcuts by using <kbd>shift</kbd> + <kbd>?</kbd>
## Global Shortcuts
@@ -82,6 +82,16 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>r</kbd> | Reply (quoting selected text) |
| <kbd>e</kbd> | Edit issue/merge request |
| <kbd>l</kbd> | Change label |
+| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file |
+| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file |
+
+## Epics **[ULTIMATE]**
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>r</kbd> | Reply (quoting selected text) |
+| <kbd>e</kbd> | Edit description |
+| <kbd>l</kbd> | Change label |
## Wiki pages
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index e60b6819bf1..c03dffa967d 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -75,6 +75,6 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
Other interesting links:
-- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/features/time-tracking)
+- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/)
[quick actions]: ../user/project/quick_actions.md
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index 338b3a32265..da51c0f2c93 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -1,31 +1,39 @@
# Changing your time zone
The global time zone configuration parameter can be changed in `config/gitlab.yml`:
+
```
- # time_zone: 'UTC'
+# time_zone: 'UTC'
```
-Uncomment and customize if you want to change the default time zone of GitLab application.
+Uncomment and customize if you want to change the default time zone of the GitLab application.
+
+
+## Viewing available timezones
To see all available time zones, run `bundle exec rake time:zones:all`.
-With Omnibus installations, run `gitlab-rake time:zones:all`.
+For Omnibus installations, run `gitlab-rake time:zones:all`.
+
+NOTE: **Note:**
+Currently, this rake task does not list timezones in TZInfo format required by GitLab Omnibus during a reconfigure: [#58672](https://gitlab.com/gitlab-org/gitlab-ce/issues/58672).
## Changing time zone in omnibus installations
GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in `/etc/gitlab/gitlab.rb`.
-To update, add the time zone that best applies to your location. Here are two examples:
+To obtain a list of timezones, log in to your GitLab application server and run a command that generates a list of timezones in TZInfo format for the server. For example, install `timedatectl` and run `timedatectl list-timezones`.
+
+To update, add the timezone that best applies to your location. For example:
+
```
gitlab_rails['time_zone'] = 'America/New_York'
```
-or
-```
-gitlab_rails['time_zone'] = 'Europe/Brussels'
-```
-After you added this field, reconfigure and restart:
+After adding the configuration parameter, reconfigure and restart your GitLab instance:
+
```
gitlab-ctl reconfigure
gitlab-ctl restart
```
+
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 830f17aa7f2..32907db4f46 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -25,9 +25,8 @@ will still be shown in the body of the _To do_ tab.
A Todo appears in your Todos dashboard when:
-- an issue or merge request is assigned to you,
-- you are `@mentioned` in an issue or merge request, be it the description of
- the issue/merge request or in a comment,
+- an issue or merge request is assigned to you
+- you are `@mentioned` in the description or in a comment of an issue, merge request, or epic **[ULTIMATE]**
- you are `@mentioned` in a comment on a commit,
- a job in the CI pipeline running for your merge request failed, but this
job is not allowed to fail.
@@ -63,14 +62,14 @@ for filtering; otherwise, they appear as normal.
### Manually creating a Todo
-You can also add an issue or merge request to your Todos dashboard by clicking
-the "Add todo" button in the issue or merge request sidebar.
+You can also add an issue, merge request or epic to your Todos dashboard by clicking
+the "Add todo" button in the sidebar of the issue, merge request, or epic **[ULTIMATE]**.
![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png)
## Marking a Todo as done
-Any action to the corresponding issue or merge request will mark your Todo as
+Any action to the corresponding issue, merge request or epic **[ULTIMATE]** will mark your Todo as
**Done**. Actions that dismiss Todos include:
- changing the assignee
@@ -84,10 +83,10 @@ Todos are personal, and they're only marked as done if the action is coming from
you. If you close the issue or merge request, your Todo will automatically
be marked as done.
-If someone else closes, merges, or takes action on the issue or merge
+If someone else closes, merges, or takes action on the issue, epic or merge
request, your Todo will remain pending. This prevents other users from closing issues without you being notified.
-There is just one Todo per issue or merge request, so mentioning a user a
+There is just one Todo per issue, epic or merge request, so mentioning a user a
hundred times in an issue will only trigger one Todo.
---
@@ -97,7 +96,7 @@ corresponding **Done** button, and it will disappear from your Todo list.
![A Todo in the Todos dashboard](img/todo_list_item.png)
-A Todo can also be marked as done from the issue or merge request sidebar using
+A Todo can also be marked as done from the issue, merge request or epic sidebar using
the "Mark todo as done" button.
![Mark todo as done from the issuable sidebar](img/todos_mark_done_sidebar.png)
@@ -114,7 +113,7 @@ There are four kinds of filters you can use on your Todos dashboard.
| Project | Filter by project |
| Group | Filter by group |
| Author | Filter by the author that triggered the Todo |
-| Type | Filter by issue or merge request |
+| Type | Filter by issue, merge request, or epic **[ULTIMATE]** |
| Action | Filter by the action that triggered the Todo |
You can also filter by more than one of these at the same time. The possible Actions are `Any Action`, `Assigned`, `Mentioned`, `Added`, `Pipelines`, and `Directly Addressed`, [as described above](#what-triggers-a-todo).
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
index 595c7da155b..2366372d984 100644
--- a/doc/workflow/web_editor.md
+++ b/doc/workflow/web_editor.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/repository/web_editor.md'
+---
+
This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md).
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
index abb8002f442..020455dcbdc 100644
--- a/doc/workflow/wip_merge_requests.md
+++ b/doc/workflow/wip_merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/work_in_progress_merge_requests.md'
+---
+
This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md).
diff --git a/fixtures/emojis/index.json b/fixtures/emojis/index.json
index f55571d31fa..0406d404532 100644
--- a/fixtures/emojis/index.json
+++ b/fixtures/emojis/index.json
@@ -16344,7 +16344,7 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "accomodation",
+ "accommodation",
"building",
"checkin",
"whotel",
diff --git a/haml_lint/inline_javascript.rb b/haml_lint/inline_javascript.rb
new file mode 100644
index 00000000000..da6af92e82b
--- /dev/null
+++ b/haml_lint/inline_javascript.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+unless Rails.env.production?
+ require_dependency 'haml_lint/haml_visitor'
+ require_dependency 'haml_lint/linter'
+ require_dependency 'haml_lint/linter_registry'
+
+ module HamlLint
+ class Linter::InlineJavaScript < Linter
+ include ::HamlLint::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
+
+ def visit_tag(node)
+ return unless node.tag_name == 'script'
+
+ 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/haml_lint/linter/no_plain_nodes.rb b/haml_lint/linter/no_plain_nodes.rb
new file mode 100644
index 00000000000..c39f61fa80d
--- /dev/null
+++ b/haml_lint/linter/no_plain_nodes.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'active_support/core_ext/array/grouping'
+
+module HamlLint
+ class Linter
+ class NoPlainNodes < Linter
+ include ::HamlLint::LinterRegistry
+
+ def visit_tag(node)
+ if inline_plain_node?(node)
+ check_inline(node)
+ elsif !node.script.empty?
+ check_script(node)
+ else
+ check(node)
+ end
+ end
+
+ private
+
+ def check(node)
+ text_in_node(node).each { |string| record(node, string) }
+ end
+
+ def check_inline(node)
+ text = inline_text(node)
+ record(node, text) unless text.empty?
+ end
+
+ def check_script(node)
+ text = inline_text(node)
+ record(node, text) unless text.start_with?('=') || text.empty?
+ end
+
+ # Build an array of all strings in child text nodes.
+ # non text nodes are nil, where we'll split the sentences.
+ def text_in_node(node)
+ texts = node.children.map do |child|
+ child.text.strip if text_node?(child)
+ end
+
+ texts.split(nil).map { |sentence| sentence.join(' ') unless sentence.empty? }.compact
+ end
+
+ # Removes a node's attributes and tag from the source code,
+ # returning the inline text of a node.
+ def inline_text(node)
+ text = node.source_code.gsub("%#{node.tag_name}", '')
+
+ attributes = node.attributes_source.map(&:last)
+ attributes.each { |attribute| text = text.gsub(attribute, '') }
+
+ text.strip
+ end
+
+ def record(node, string)
+ record_lint(node, message(string))
+ end
+
+ def message(string)
+ "`#{string}` is a plain node. Please use an i18n method like `#{fixed(string)}`"
+ end
+
+ def fixed(string)
+ "= _('#{string}')"
+ end
+
+ def inline_plain_node?(node)
+ node.children.empty? && node.script.empty?
+ end
+
+ def plain_node?(node)
+ node.is_a?(::HamlLint::Tree::PlainNode)
+ end
+
+ def text_node?(node)
+ return false unless plain_node?(node)
+
+ !node.text.empty?
+ end
+ end
+ end
+end
diff --git a/jest.config.js b/jest.config.js
index 3fa39dd7e8d..84481642250 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,7 +1,11 @@
-/* eslint-disable filenames/match-regex */
+const IS_EE = require('./config/helpers/is_ee_env');
const reporters = ['default'];
+// To have consistent date time parsing both in local and CI environments we set
+// the timezone of the Node process. https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27738
+process.env.TZ = 'GMT';
+
if (process.env.CI) {
reporters.push([
'jest-junit',
@@ -16,10 +20,15 @@ module.exports = {
testMatch: ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'],
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
- '^~(.*)$': '<rootDir>/app/assets/javascripts$1',
- '^ee(.*)$': '<rootDir>/ee/app/assets/javascripts$1',
- '^helpers(.*)$': '<rootDir>/spec/frontend/helpers$1',
- '^vendor(.*)$': '<rootDir>/vendor/assets/javascripts$1',
+ '^~(/.*)$': '<rootDir>/app/assets/javascripts$1',
+ '^ee(/.*)$': '<rootDir>/ee/app/assets/javascripts$1',
+ '^ee_else_ce(/.*)$': IS_EE
+ ? '<rootDir>/ee/app/assets/javascripts$1'
+ : '<rootDir>/app/assets/javascripts$1',
+ '^helpers(/.*)$': '<rootDir>/spec/frontend/helpers$1',
+ '^vendor(/.*)$': '<rootDir>/vendor/assets/javascripts$1',
+ '\\.(jpg|jpeg|png|svg)$': '<rootDir>/spec/frontend/__mocks__/file_mock.js',
+ 'emojis(/.*).json': '<rootDir>/fixtures/emojis$1.json',
},
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/coverage-frontend/',
@@ -27,11 +36,17 @@ module.exports = {
cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
reporters,
- setupTestFrameworkScriptFile: '<rootDir>/spec/frontend/test_setup.js',
+ setupFilesAfterEnv: ['<rootDir>/spec/frontend/test_setup.js'],
restoreMocks: true,
transform: {
+ '^.+\\.(gql|graphql)$': 'jest-transform-graphql',
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': 'vue-jest',
},
transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'],
+ timers: 'fake',
+ testEnvironment: '<rootDir>/spec/frontend/environment.js',
+ testEnvironmentOptions: {
+ IS_EE,
+ },
};
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 3bcf5150b43..20f8c637274 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -6,7 +6,7 @@ module API
LOG_FILENAME = Rails.root.join("log", "api_json.log")
- NO_SLASH_URL_PART_REGEX = %r{[^/]+}
+ NO_SLASH_URL_PART_REGEX = %r{[^/]+}.freeze
NAMESPACE_OR_PROJECT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
COMMIT_ENDPOINT_REQUIREMENTS = NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze
USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze
@@ -98,7 +98,6 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
- mount ::API::CircuitBreakers
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistry
@@ -115,7 +114,6 @@ module API
mount ::API::GroupVariables
mount ::API::ImportGithub
mount ::API::Internal
- mount ::API::IssuableBulkUpdate
mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs
@@ -135,6 +133,7 @@ module API
mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectClusters
+ mount ::API::ProjectEvents
mount ::API::ProjectExport
mount ::API::ProjectImport
mount ::API::ProjectHooks
@@ -142,6 +141,7 @@ module API
mount ::API::Projects
mount ::API::ProjectSnapshots
mount ::API::ProjectSnippets
+ mount ::API::ProjectStatistics
mount ::API::ProjectTemplates
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 07f529b01bb..65d7f68bbf9 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -34,11 +34,11 @@ module API
repository = user_project.repository
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
- branches = ::Kaminari.paginate_array(branches)
+ branches = paginate(::Kaminari.paginate_array(branches))
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present(
- paginate(branches),
+ branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
@@ -162,8 +162,8 @@ module API
result = DeleteBranchService.new(user_project, current_user)
.execute(params[:branch])
- if result[:status] != :success
- render_api_error!(result[:message], result[:return_code])
+ if result.error?
+ render_api_error!(result.message, result.http_status)
end
end
end
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
deleted file mode 100644
index da756daadcc..00000000000
--- a/lib/api/circuit_breakers.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-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
- desc 'Get all git storages' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- get do
- present []
- end
-
- desc 'Get all failing git storages' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- get 'failing' do
- present []
- end
-
- desc 'Reset all storage failures and open circuitbreaker' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- delete do
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 8defc59224d..80913f4ca07 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -96,16 +96,27 @@ module API
end
end
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
+ optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
end
post ':id/repository/commits' do
+ if params[:start_project]
+ start_project = find_project!(params[:start_project])
+
+ unless user_project.forked_from?(start_project)
+ forbidden!("Project is not included in the fork network for #{start_project.full_name}")
+ end
+ end
+
authorize_push_to_branch!(params[:branch])
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
@@ -318,10 +329,18 @@ module API
use :pagination
end
get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
+ authorize! :read_merge_request, user_project
+
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
- present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
+ commit_merge_requests = MergeRequestsFinder.new(
+ current_user,
+ project_id: user_project.id,
+ commit_sha: commit.sha
+ ).execute
+
+ present paginate(commit_merge_requests), with: Entities::MergeRequestBasic
end
desc "Get a commit's GPG signature" do
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 91eb6a23701..693172b7d08 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- NOTEABLE_TYPES = [Issue, Snippet, MergeRequest, Commit].freeze
-
- NOTEABLE_TYPES.each do |noteable_type|
+ Helpers::DiscussionsHelpers.noteable_types.each do |noteable_type|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str
@@ -136,9 +134,13 @@ module API
post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do
noteable = find_noteable(parent_type, noteables_str, params[:noteable_id])
notes = readable_discussion_notes(noteable, params[:discussion_id])
+ first_note = notes.first
break not_found!("Discussion") if notes.empty?
- break bad_request!("Discussion is an individual note.") unless notes.first.part_of_discussion?
+
+ unless first_note.part_of_discussion? || first_note.to_discussion.can_convert_to_discussion?
+ break bad_request!("Discussion can not be replied to.")
+ end
opts = {
note: params[:body],
@@ -204,7 +206,7 @@ module API
delete_note(noteable, params[:note_id])
end
- if Noteable::RESOLVABLE_TYPES.include?(noteable_type.to_s)
+ if Noteable.resolvable_types.include?(noteable_type.to_s)
desc "Resolve/unresolve an existing #{noteable_type.to_s.downcase} discussion" do
success Entities::Discussion
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 7c035990fb0..f8b950cb55d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -86,6 +86,10 @@ module API
expose :admin?, as: :is_admin
end
+ class UserDetailsWithAdmin < UserWithAdmin
+ expose :highest_role
+ end
+
class UserStatus < Grape::Entity
expose :emoji
expose :message
@@ -156,7 +160,7 @@ module API
class BasicProjectDetails < ProjectIdentity
include ::API::ProjectsRelationBuilder
- expose :default_branch
+ expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
expose :tag_list do |project|
# project.tags.order(:name).pluck(:name) is the most suitable option
@@ -235,6 +239,7 @@ module API
end
end
+ expose :empty_repo?, as: :empty_repo
expose :archived?, as: :archived
expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
@@ -261,7 +266,7 @@ module API
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
- expose :ci_config_path
+ expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links, options)
end
@@ -270,8 +275,10 @@ module API
expose :only_allow_merge_if_all_discussions_are_resolved
expose :printing_merge_request_link_enabled
expose :merge_method
-
- expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+ expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
+ options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
+ }
+ expose :external_authorization_classification_label
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
@@ -296,10 +303,23 @@ module API
expose :commit_count
expose :storage_size
expose :repository_size
+ expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
end
+ class ProjectDailyFetches < Grape::Entity
+ expose :fetch_count, as: :count
+ expose :date
+ end
+
+ class ProjectDailyStatistics < Grape::Entity
+ expose :fetches do
+ expose :total_fetch_count, as: :total
+ expose :fetches, as: :days, using: ProjectDailyFetches
+ end
+ end
+
class Member < Grape::Entity
expose :user, merge: true, using: UserBasic
expose :access_level
@@ -336,6 +356,7 @@ module API
with_options format_with: -> (value) { value.to_i } do
expose :storage_size
expose :repository_size
+ expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
end
@@ -473,6 +494,12 @@ module API
expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
expose :state, :created_at, :updated_at
+
+ # Avoids an N+1 query when metadata is included
+ def issuable_metadata(subject, options, method)
+ cached_subject = options.dig(:issuable_metadata, subject.id)
+ (cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend
+ end
end
class Diff < Grape::Entity
@@ -518,39 +545,31 @@ module API
class IssueBasic < ProjectEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
+
expose :labels do |issue, options|
- # Avoids an N+1 query since labels are preloaded
- issue.labels.map(&:title).sort
+ if options[:with_labels_details]
+ ::API::Entities::LabelBasic.represent(issue.labels.sort_by(&:title))
+ else
+ issue.labels.map(&:title).sort
+ end
end
+
expose :milestone, using: Entities::Milestone
expose :assignees, :author, using: Entities::UserBasic
- expose :assignee, using: ::API::Entities::UserBasic do |issue, options|
+ expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first
end
- expose :user_notes_count
- expose :upvotes do |issue, options|
- if options[:issuable_metadata]
- # Avoids an N+1 query when metadata is included
- options[:issuable_metadata][issue.id].upvotes
- else
- issue.upvotes
- end
- end
- expose :downvotes do |issue, options|
- if options[:issuable_metadata]
- # Avoids an N+1 query when metadata is included
- options[:issuable_metadata][issue.id].downvotes
- else
- issue.downvotes
- end
- end
+ expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
+ expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) }
+ expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
+ expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date
expose :confidential
expose :discussion_locked
- expose :web_url do |issue, options|
+ expose :web_url do |issue|
Gitlab::UrlBuilder.build(issue)
end
@@ -558,19 +577,20 @@ module API
issue
end
- expose :merge_requests_count do |issue, options|
- if options[:issuable_metadata]
- # Avoids an N+1 query when metadata is included
- options[:issuable_metadata][issue.id].merge_requests_count
- else
- issue.merge_requests_closing_issues.count
- end
- end
+ expose :task_completion_status
end
class Issue < IssueBasic
include ::API::Helpers::RelatedResourcesHelpers
+ expose(:has_tasks) do |issue, _|
+ !issue.task_list_items.empty?
+ end
+
+ expose :task_status, if: -> (issue, _) do
+ !issue.task_list_items.empty?
+ end
+
expose :_links do
expose :self do |issue|
expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid))
@@ -659,23 +679,16 @@ module API
MarkupHelper.markdown_field(entity, :description)
end
expose :target_branch, :source_branch
- expose :upvotes do |merge_request, options|
- if options[:issuable_metadata]
- options[:issuable_metadata][merge_request.id].upvotes
- else
- merge_request.upvotes
- end
+ expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
+ expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
+ expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
+ expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
+ merge_request.assignee
end
- expose :downvotes do |merge_request, options|
- if options[:issuable_metadata]
- options[:issuable_metadata][merge_request.id].downvotes
- else
- merge_request.downvotes
- end
- end
- expose :author, :assignee, using: Entities::UserBasic
+ expose :author, :assignees, using: Entities::UserBasic
+
expose :source_project_id, :target_project_id
- expose :labels do |merge_request, options|
+ expose :labels do |merge_request|
# Avoids an N+1 query since labels are preloaded
merge_request.labels.map(&:title).sort
end
@@ -688,12 +701,11 @@ module API
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
# information.
expose :merge_status do |merge_request|
- merge_request.check_if_can_be_merged
+ merge_request.check_mergeability
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
- expose :user_notes_count
expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
@@ -701,7 +713,11 @@ module API
# Deprecated
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
- expose :web_url do |merge_request, options|
+ expose :reference do |merge_request, options|
+ merge_request.to_reference(options[:project])
+ end
+
+ expose :web_url do |merge_request|
Gitlab::UrlBuilder.build(merge_request)
end
@@ -710,6 +726,8 @@ module API
end
expose :squash
+
+ expose :task_completion_status
end
class MergeRequest < MergeRequestBasic
@@ -737,6 +755,8 @@ module API
merge_request.metrics&.pipeline
end
+ expose :head_pipeline, using: 'API::Entities::Pipeline'
+
expose :diff_refs, using: Entities::DiffRefs
# Allow the status of a rebase to be determined
@@ -878,7 +898,7 @@ module API
expose :push_event_payload,
as: :push_data,
using: PushEventPayload,
- if: -> (event, _) { event.push? }
+ if: -> (event, _) { event.push_action? }
expose :author_username do |event, options|
event.author&.username
@@ -898,7 +918,8 @@ module API
expose :target_type
expose :target do |todo, options|
- todo_target_class(todo.target_type).represent(todo.target, options)
+ todo_options = options.fetch(todo.target_type, {})
+ todo_target_class(todo.target_type).represent(todo.target, todo_options)
end
expose :target_url do |todo, options|
@@ -921,7 +942,15 @@ module API
end
class NamespaceBasic < Grape::Entity
- expose :id, :name, :path, :kind, :full_path, :parent_id
+ expose :id, :name, :path, :kind, :full_path, :parent_id, :avatar_url
+
+ expose :web_url do |namespace|
+ if namespace.user?
+ Gitlab::Routing.url_helpers.user_url(namespace.owner)
+ else
+ namespace.web_url
+ end
+ end
end
class Namespace < NamespaceBasic
@@ -1120,6 +1149,8 @@ module API
expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
+ expose(*::ApplicationSettingsHelper.external_authorization_service_attributes)
+
# support legacy names, can be removed in v5
expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
expose :password_authentication_enabled_for_web, as: :signin_enabled
@@ -1145,22 +1176,33 @@ module API
end
end
- class Release < TagRelease
+ class Release < Grape::Entity
expose :name
+ expose :tag, as: :tag_name, if: lambda { |_, _| can_download_code? }
+ expose :description
expose :description_html do |entity|
MarkupHelper.markdown_field(entity, :description)
end
expose :created_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
- expose :commit, using: Entities::Commit
+ expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
expose :assets do
- expose :assets_count, as: :count
- expose :sources, using: Entities::Releases::Source
+ expose :assets_count, as: :count do |release, _|
+ assets_to_exclude = can_download_code? ? [] : [:sources]
+ release.assets_count(except: assets_to_exclude)
+ end
+ expose :sources, using: Entities::Releases::Source, if: lambda { |_, _| can_download_code? }
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
end
+
+ private
+
+ def can_download_code?
+ Ability.allowed?(options[:current_user], :download_code, object.project)
+ end
end
class Tag < Grape::Entity
@@ -1231,7 +1273,7 @@ module API
end
class JobBasic < Grape::Entity
- expose :id, :status, :stage, :name, :ref, :tag, :coverage
+ expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure
expose :created_at, :started_at, :finished_at
expose :duration
expose :user, with: User
@@ -1266,8 +1308,9 @@ module API
end
class Variable < Grape::Entity
- expose :key, :value
+ expose :variable_type, :key, :value
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
+ expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
end
class Pipeline < PipelineBasic
@@ -1277,6 +1320,9 @@ module API
expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
expose :duration
expose :coverage
+ expose :detailed_status, using: DetailedStatusEntity do |pipeline, options|
+ pipeline.detailed_status(options[:current_user])
+ end
end
class PipelineSchedule < Grape::Entity
@@ -1295,10 +1341,6 @@ module API
expose :id, :name, :slug, :external_url
end
- class Environment < EnvironmentBasic
- expose :project, using: Entities::BasicProjectDetails
- end
-
class Deployment < Grape::Entity
expose :id, :iid, :ref, :sha, :created_at
expose :user, using: Entities::UserBasic
@@ -1306,6 +1348,11 @@ module API
expose :deployable, using: Entities::Job
end
+ class Environment < EnvironmentBasic
+ expose :project, using: Entities::BasicProjectDetails
+ expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true }
+ end
+
class LicenseBasic < Grape::Entity
expose :key, :name, :nickname
expose :url, as: :html_url
@@ -1399,8 +1446,13 @@ module API
expose :name, :script, :timeout, :when, :allow_failure
end
+ class Port < Grape::Entity
+ expose :number, :protocol, :name
+ end
+
class Image < Grape::Entity
expose :name, :entrypoint
+ expose :ports, using: JobRequest::Port
end
class Service < Image
@@ -1568,8 +1620,6 @@ module API
class Suggestion < Grape::Entity
expose :id
- expose :from_original_line
- expose :to_original_line
expose :from_line
expose :to_line
expose :appliable?, as: :appliable
@@ -1600,7 +1650,7 @@ module API
end
class Cluster < Grape::Entity
- expose :id, :name, :created_at
+ expose :id, :name, :created_at, :domain
expose :provider_type, :platform_type, :environment_scope, :cluster_type
expose :user, using: Entities::UserBasic
expose :platform_kubernetes, using: Entities::Platform::Kubernetes
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 0278c6c54a5..6cd43923559 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -22,7 +22,7 @@ module API
get ':id/environments' do
authorize! :read_environment, user_project
- present paginate(user_project.environments), with: Entities::Environment
+ present paginate(user_project.environments), with: Entities::Environment, current_user: current_user
end
desc 'Creates a new environment' do
@@ -40,7 +40,7 @@ module API
environment = user_project.environments.create(declared_params)
if environment.persisted?
- present environment, with: Entities::Environment
+ present environment, with: Entities::Environment, current_user: current_user
else
render_validation_error!(environment)
end
@@ -63,7 +63,7 @@ module API
update_params = declared_params(include_missing: false).extract!(:name, :external_url)
if environment.update(update_params)
- present environment, with: Entities::Environment
+ present environment, with: Entities::Environment, current_user: current_user
else
render_validation_error!(environment)
end
@@ -99,7 +99,22 @@ module API
environment.stop_with_action!(current_user)
status 200
- present environment, with: Entities::Environment
+ present environment, with: Entities::Environment, current_user: current_user
+ end
+
+ desc 'Get a single environment' do
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ get ':id/environments/:environment_id' do
+ authorize! :read_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+ present environment, with: Entities::Environment, current_user: current_user,
+ except: [:project, { last_deployment: [:environment] }],
+ last_deployment: true
end
end
end
diff --git a/lib/api/events.rb b/lib/api/events.rb
index b98aa9f31e1..e4c017fab42 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -4,34 +4,11 @@ module API
class Events < Grape::API
include PaginationParams
include APIGuard
+ helpers ::API::Helpers::EventsHelpers
- helpers do
- params :event_filter_params do
- optional :action, type: String, values: Event.actions, desc: 'Event action to filter on'
- optional :target_type, type: String, values: Event.target_types, desc: 'Event target type to filter on'
- optional :before, type: Date, desc: 'Include only events created before this date'
- optional :after, type: Date, desc: 'Include only events created after this date'
- end
-
- params :sort_params do
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return events sorted in ascending and descending order'
- end
-
- def present_events(events)
- events = paginate(events)
-
- present events, with: Entities::Event
- end
-
- def find_events(source)
- EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute
- end
- end
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
resource :events do
- allow_access_with_scope :read_user, if: -> (request) { request.get? }
-
desc "List currently authenticated user's events" do
detail 'This feature was introduced in GitLab 9.3.'
success Entities::Event
@@ -55,8 +32,6 @@ module API
requires :id, type: String, desc: 'The ID or Username of the user'
end
resource :users do
- allow_access_with_scope :read_user, if: -> (request) { request.get? }
-
desc 'Get the contribution events of a specified user' do
detail 'This feature was introduced in GitLab 8.13.'
success Entities::Event
@@ -76,25 +51,5 @@ module API
present_events(events)
end
end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc "List a Project's visible events" do
- success Entities::Event
- end
- params do
- use :pagination
- use :event_filter_params
- use :sort_params
- end
-
- get ":id/events" do
- events = find_events(user_project)
-
- present_events(events)
- end
- end
end
end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 3f048e0dc56..47fcbabb4d4 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -47,6 +47,7 @@ module API
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -67,6 +68,7 @@ module API
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :variable_type, type: String, values: Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 64958ff982a..ec1020c7c78 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -7,23 +7,9 @@ module API
before { authenticate_non_get! }
- helpers do
- params :optional_params_ce do
- optional :description, type: String, desc: 'The description of the group'
- optional :visibility, type: String,
- values: Gitlab::VisibilityLevel.string_values,
- default: Gitlab::VisibilityLevel.string_level(
- Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
- desc: 'The visibility of the group'
- optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
- optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
- optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
- end
-
- params :optional_params do
- use :optional_params_ce
- end
+ helpers Helpers::GroupsHelpers
+ helpers do
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
@@ -58,6 +44,22 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ def create_group
+ # This is a separate method so that EE can extend its behaviour, without
+ # having to modify this code directly.
+ ::Groups::CreateService
+ .new(current_user, declared_params(include_missing: false))
+ .execute
+ end
+
+ def update_group(group)
+ # This is a separate method so that EE can extend its behaviour, without
+ # having to modify this code directly.
+ ::Groups::UpdateService
+ .new(group, current_user, declared_params(include_missing: false))
+ .execute
+ end
+
def find_group_projects(params)
group = find_group!(params[:id])
options = {
@@ -127,7 +129,7 @@ module API
authorize! :create_group
end
- group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
+ group = create_group
if group.persisted?
present group, with: Entities::GroupDetail, current_user: current_user
@@ -148,12 +150,13 @@ module API
optional :name, type: String, desc: 'The name of the group'
optional :path, type: String, desc: 'The path of the group'
use :optional_params
+ use :optional_update_params_ee
end
put ':id' do
group = find_group!(params[:id])
authorize! :admin_group, group
- if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
+ if update_group(group)
present group, with: Entities::GroupDetail, current_user: current_user
else
render_validation_error!(group)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 54cd4cd9cdb..00bcf6b055b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -6,6 +6,7 @@ module API
include Helpers::Pagination
SUDO_HEADER = "HTTP_SUDO".freeze
+ GITLAB_SHARED_SECRET_HEADER = "Gitlab-Shared-Secret".freeze
SUDO_PARAM = :sudo
API_USER_ENV = 'gitlab.api.user'.freeze
@@ -66,10 +67,6 @@ module API
initial_current_user != current_user
end
- def user_namespace
- @user_namespace ||= find_namespace!(params[:id])
- end
-
def user_group
@group ||= find_group!(params[:id])
end
@@ -212,10 +209,12 @@ module API
end
def authenticate_by_gitlab_shell_token!
- input = params['secret_token'].try(:chomp)
- unless Devise.secure_compare(secret_token, input)
- unauthorized!
- end
+ input = params['secret_token']
+ input ||= Base64.decode64(headers[GITLAB_SHARED_SECRET_HEADER]) if headers.key?(GITLAB_SHARED_SECRET_HEADER)
+
+ input&.chomp!
+
+ unauthorized! unless Devise.secure_compare(secret_token, input)
end
def authenticated_with_full_private_access!
@@ -244,6 +243,10 @@ module API
authorize! :read_build, user_project
end
+ def authorize_destroy_artifacts!
+ authorize! :destroy_artifacts, user_project
+ end
+
def authorize_update_builds!
authorize! :update_build, user_project
end
@@ -295,6 +298,12 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
+ def filter_by_title(items, title)
+ items.where(title: title)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def filter_by_search(items, text)
items.search(text)
end
@@ -436,7 +445,7 @@ module API
end
def present_carrierwave_file!(file, supports_direct_download: true)
- return not_found! unless file.exists?
+ return not_found! unless file&.exists?
if file.file_storage?
present_disk_file!(file.path, file.filename)
diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb
index 1058f4e8a5e..c86eae6f2da 100644
--- a/lib/api/helpers/custom_validators.rb
+++ b/lib/api/helpers/custom_validators.rb
@@ -22,9 +22,22 @@ module API
message: "should be an integer, 'None' or 'Any'"
end
end
+
+ class ArrayNoneAny < Grape::Validations::Base
+ def validate_param!(attr_name, params)
+ value = params[attr_name]
+
+ return if value.is_a?(Array) ||
+ [IssuableFinder::FILTER_NONE, IssuableFinder::FILTER_ANY].include?(value.to_s.downcase)
+
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],
+ message: "should be an array, 'None' or 'Any'"
+ end
+ end
end
end
end
Grape::Validations.register_validator(:absence, ::API::Helpers::CustomValidators::Absence)
Grape::Validations.register_validator(:integer_none_any, ::API::Helpers::CustomValidators::IntegerNoneAny)
+Grape::Validations.register_validator(:array_none_any, ::API::Helpers::CustomValidators::ArrayNoneAny)
diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb
new file mode 100644
index 00000000000..94a5bf75c39
--- /dev/null
+++ b/lib/api/helpers/discussions_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module DiscussionsHelpers
+ def self.noteable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, Snippet, MergeRequest, Commit]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/events_helpers.rb b/lib/api/helpers/events_helpers.rb
new file mode 100644
index 00000000000..bf3b76bb92d
--- /dev/null
+++ b/lib/api/helpers/events_helpers.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module EventsHelpers
+ extend Grape::API::Helpers
+
+ params :event_filter_params do
+ optional :action, type: String, values: Event.actions, desc: 'Event action to filter on'
+ optional :target_type, type: String, values: Event.target_types, desc: 'Event target type to filter on'
+ optional :before, type: Date, desc: 'Include only events created before this date'
+ optional :after, type: Date, desc: 'Include only events created after this date'
+ end
+
+ params :sort_params do
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return events sorted in ascending and descending order'
+ end
+
+ def present_events(events)
+ events = paginate(events)
+
+ present events, with: Entities::Event
+ end
+
+ def find_events(source)
+ EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
new file mode 100644
index 00000000000..94010ab1bc2
--- /dev/null
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ # GraphqlHelpers is used by the REST API when it is acting like a client
+ # against the graphql API. Helper code for the graphql server implementation
+ # should be in app/graphql/ or lib/gitlab/graphql/
+ module GraphqlHelpers
+ def conditionally_graphql!(fallback:, query:, context: {}, transform: nil)
+ return fallback.call unless Feature.enabled?(:graphql)
+
+ result = GitlabSchema.execute(query, context: context)
+
+ if transform
+ transform.call(result)
+ else
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
new file mode 100644
index 00000000000..2c33d79f6c8
--- /dev/null
+++ b/lib/api/helpers/groups_helpers.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module GroupsHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ce do
+ optional :description, type: String, desc: 'The description of the group'
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ default: Gitlab::VisibilityLevel.string_level(
+ Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
+ desc: 'The visibility of the group'
+ optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
+ optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
+ end
+
+ params :optional_params_ee do
+ end
+
+ params :optional_update_params_ee do
+ end
+
+ params :optional_params do
+ use :optional_params_ce
+ use :optional_params_ee
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index fe78049af87..c318f5b9127 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -5,9 +5,11 @@ module API
module InternalHelpers
attr_reader :redirected_path
- def wiki?
- set_project unless defined?(@wiki) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- @wiki # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ delegate :wiki?, to: :repo_type
+
+ def repo_type
+ set_project unless defined?(@repo_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @repo_type # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def project
@@ -41,6 +43,30 @@ module API
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
+ def process_mr_push_options(push_options, project, user, changes)
+ output = {}
+
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/61359')
+
+ service = ::MergeRequests::PushOptionsHandlerService.new(
+ project,
+ user,
+ changes,
+ push_options
+ ).execute
+
+ if service.errors.present?
+ output[:warnings] = push_options_warning(service.errors.join("\n\n"))
+ end
+
+ output
+ end
+
+ def push_options_warning(warning)
+ options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ')
+ "Error encountered with push options #{options}: #{warning}"
+ end
+
def redis_ping
result = Gitlab::Redis::SharedState.with { |redis| redis.ping }
@@ -67,10 +93,10 @@ module API
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project
if params[:gl_repository]
- @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository])
+ @project, @repo_type = Gitlab::GlRepository.parse(params[:gl_repository])
@redirected_path = nil
else
- @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project])
+ @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(params[:project])
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -78,7 +104,7 @@ module API
# Project id to pass between components that don't share/don't have
# access to the same filesystem mounts
def gl_repository
- Gitlab::GlRepository.gl_repository(project, wiki?)
+ repo_type.identifier_for_subject(project)
end
def gl_project_path
@@ -92,7 +118,7 @@ module API
# Return the repository depending on whether we want the wiki or the
# regular repository
def repository
- if wiki?
+ if repo_type.wiki?
project.wiki.repository
else
project.repository
diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
new file mode 100644
index 00000000000..5b7199fddb0
--- /dev/null
+++ b/lib/api/helpers/issues_helpers.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module IssuesHelpers
+ extend Grape::API::Helpers
+
+ params :optional_issue_params_ee do
+ end
+
+ params :optional_issues_params_ee do
+ end
+
+ def self.update_params_at_least_one_of
+ [
+ :assignee_id,
+ :assignee_ids,
+ :confidential,
+ :created_at,
+ :description,
+ :discussion_locked,
+ :due_date,
+ :labels,
+ :milestone_id,
+ :state_event,
+ :title
+ ]
+ end
+
+ def issue_finder(args = {})
+ args = declared_params.merge(args)
+
+ args.delete(:id)
+ args[:milestone_title] ||= args.delete(:milestone)
+ args[:label_name] ||= args.delete(:labels)
+ args[:scope] = args[:scope].underscore if args[:scope]
+
+ IssuesFinder.new(current_user, args)
+ end
+
+ def find_issues(args = {})
+ finder = issue_finder(args)
+ issues = finder.execute.with_api_entity_associations
+
+ issues.reorder(order_options_with_tie_breaker) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def issues_statistics(args = {})
+ finder = issue_finder(args)
+ counter = Gitlab::IssuablesCountForState.new(finder)
+
+ {
+ statistics: {
+ counts: {
+ all: counter[:all],
+ closed: counter[:closed],
+ opened: counter[:opened]
+ }
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 73d58ee7f37..1395ffadab9 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -19,28 +19,13 @@ module API
.non_request
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_all_members_for_project(project)
- shared_group_ids = project.project_group_links.pluck(:group_id)
- project_group_ids = project.group&.self_and_ancestors&.pluck(:id)
- source_ids = [project.id, project_group_ids, shared_group_ids]
- .flatten
- .compact
- Member.includes(:user)
- .joins(user: :project_authorizations)
- .where(project_authorizations: { project_id: project.id })
- .where(source_id: source_ids)
+ MembersFinder.new(project, current_user).execute(include_invited_groups_members: true)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def find_all_members_for_group(group)
- source_ids = group.self_and_ancestors.pluck(:id)
- Member.includes(:user)
- .where(source_id: source_ids)
- .where(source_type: 'Namespace')
+ GroupMembersFinder.new(group).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 216b2c45741..a068de4361c 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -3,6 +3,12 @@
module API
module Helpers
module NotesHelpers
+ def self.noteable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, MergeRequest, Snippet]
+ end
+
def update_note(noteable, note_id)
note = noteable.notes.find(params[:note_id])
@@ -70,14 +76,7 @@ module API
def find_noteable(parent, noteables_str, noteable_id)
noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
- readable =
- if noteable.is_a?(Commit)
- # for commits there is not :read_commit policy, check if user
- # has :read_note permission on the commit's project
- can?(current_user, :read_note, user_project)
- else
- can?(current_user, noteable_read_ability_name(noteable), noteable)
- end
+ readable = can?(current_user, noteable_read_ability_name(noteable), noteable)
return not_found!(noteables_str) unless readable
@@ -89,12 +88,11 @@ module API
end
def create_note(noteable, opts)
- policy_object = noteable.is_a?(Commit) ? user_project : noteable
- authorize!(:create_note, policy_object)
+ authorize!(:create_note, noteable)
parent = noteable_parent(noteable)
- opts.delete(:created_at) unless current_user.can?(:set_note_created_at, policy_object)
+ opts.delete(:created_at) unless current_user.can?(:set_note_created_at, noteable)
opts[:updated_at] = opts[:created_at] if opts[:created_at]
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index d00e61678b5..2a9b17ad22a 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -23,7 +23,7 @@ module API
def base_request_uri
@base_request_uri ||= URI.parse(request.url).tap do |uri|
uri.host = Gitlab.config.gitlab.host
- uri.port = nil
+ uri.port = Gitlab.config.gitlab.port
end
end
@@ -149,7 +149,7 @@ module API
def conditions(pagination)
fields = pagination.fields
- return nil if fields.empty?
+ return if fields.empty?
placeholder = fields.map { '?' }
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index e6a72b949f9..813e46e9520 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -4,37 +4,74 @@ module API
module Helpers
module ProjectsHelpers
extend ActiveSupport::Concern
+ extend Grape::API::Helpers
- included do
- helpers do
- params :optional_project_params_ce do
- optional :description, type: String, desc: 'The description of the project'
- optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
- optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
- optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
- optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
- optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
- optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
- optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
- optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
- optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
- optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
- optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
- optional :public_builds, type: Boolean, desc: 'Perform public builds'
- optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
- optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
- optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
- optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
- optional :avatar, type: File, desc: 'Avatar image for project'
- optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
- optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
- optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
- end
+ params :optional_project_params_ce do
+ optional :description, type: String, desc: 'The description of the project'
+ optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+ optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
+ optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
+ optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
+ optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
+ optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+ optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
+ optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+ optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
+ optional :public_builds, type: Boolean, desc: 'Perform public builds'
+ optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+ optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
+ optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
+ optional :avatar, type: File, desc: 'Avatar image for project'
+ optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
+ optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
+ optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
+ optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
+ end
+
+ params :optional_project_params_ee do
+ end
+
+ params :optional_project_params do
+ use :optional_project_params_ce
+ use :optional_project_params_ee
+ end
+
+ params :optional_filter_params_ee do
+ end
+
+ params :optional_update_params_ee do
+ end
- params :optional_project_params do
- use :optional_project_params_ce
- end
- end
+ def self.update_params_at_least_one_of
+ [
+ :jobs_enabled,
+ :resolve_outdated_diff_discussions,
+ :ci_config_path,
+ :container_registry_enabled,
+ :default_branch,
+ :description,
+ :issues_enabled,
+ :lfs_enabled,
+ :merge_requests_enabled,
+ :merge_method,
+ :name,
+ :only_allow_merge_if_all_discussions_are_resolved,
+ :only_allow_merge_if_pipeline_succeeds,
+ :path,
+ :printing_merge_request_link_enabled,
+ :public_builds,
+ :request_access_enabled,
+ :shared_runners_enabled,
+ :snippets_enabled,
+ :tag_list,
+ :visibility,
+ :wiki_enabled,
+ :avatar,
+ :external_authorization_classification_label
+ ]
end
end
end
diff --git a/lib/api/helpers/protected_branches_helpers.rb b/lib/api/helpers/protected_branches_helpers.rb
new file mode 100644
index 00000000000..0fc6841d79a
--- /dev/null
+++ b/lib/api/helpers/protected_branches_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module ProtectedBranchesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
index 793ae11b41d..9cdde25fe4e 100644
--- a/lib/api/helpers/related_resources_helpers.rb
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -13,6 +13,10 @@ module API
available?(:merge_requests, project, options[:current_user])
end
+ def expose_path(path)
+ Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, path)
+ end
+
def expose_url(path)
url_options = Gitlab::Application.routes.default_url_options
protocol, host, port, script_name = url_options.values_at(:protocol, :host, :port, :script_name)
diff --git a/lib/api/helpers/resource_label_events_helpers.rb b/lib/api/helpers/resource_label_events_helpers.rb
new file mode 100644
index 00000000000..23574deb59b
--- /dev/null
+++ b/lib/api/helpers/resource_label_events_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module ResourceLabelEventsHelpers
+ def self.eventable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, MergeRequest]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb
new file mode 100644
index 00000000000..0e052e0e273
--- /dev/null
+++ b/lib/api/helpers/search_helpers.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module SearchHelpers
+ def self.global_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(projects issues merge_requests milestones snippet_titles snippet_blobs users)
+ end
+
+ def self.group_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(projects issues merge_requests milestones users)
+ end
+
+ def self.project_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(issues merge_requests milestones notes wiki_blobs commits blobs users)
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
new file mode 100644
index 00000000000..44c577204b8
--- /dev/null
+++ b/lib/api/helpers/services_helpers.rb
@@ -0,0 +1,767 @@
+# coding: utf-8
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ # Helpers module for API::Services
+ #
+ # The data structures inside this model are returned using class methods,
+ # allowing EE to extend them where necessary.
+ module ServicesHelpers
+ def self.chat_notification_settings
+ [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The chat webhook'
+ },
+ {
+ required: false,
+ name: :username,
+ type: String,
+ desc: 'The chat username'
+ },
+ {
+ required: false,
+ name: :channel,
+ type: String,
+ desc: 'The default chat channel'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_flags
+ [
+ {
+ required: false,
+ name: :notify_only_broken_pipelines,
+ type: Boolean,
+ desc: 'Send notifications for broken pipelines'
+ },
+ {
+ required: false,
+ name: :notify_only_default_branch,
+ type: Boolean,
+ desc: 'Send notifications only for the default branch'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_channels
+ [
+ {
+ required: false,
+ name: :push_channel,
+ type: String,
+ desc: 'The name of the channel to receive push_events notifications'
+ },
+ {
+ required: false,
+ name: :issue_channel,
+ type: String,
+ desc: 'The name of the channel to receive issues_events notifications'
+ },
+ {
+ required: false,
+ name: :confidential_issue_channel,
+ type: String,
+ desc: 'The name of the channel to receive confidential_issues_events notifications'
+ },
+ {
+ required: false,
+ name: :merge_request_channel,
+ type: String,
+ desc: 'The name of the channel to receive merge_requests_events notifications'
+ },
+ {
+ required: false,
+ name: :note_channel,
+ type: String,
+ desc: 'The name of the channel to receive note_events notifications'
+ },
+ {
+ required: false,
+ name: :tag_push_channel,
+ type: String,
+ desc: 'The name of the channel to receive tag_push_events notifications'
+ },
+ {
+ required: false,
+ name: :pipeline_channel,
+ type: String,
+ desc: 'The name of the channel to receive pipeline_events notifications'
+ },
+ {
+ required: false,
+ name: :wiki_page_channel,
+ type: String,
+ desc: 'The name of the channel to receive wiki_page_events notifications'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_events
+ [
+ {
+ required: false,
+ name: :push_events,
+ type: Boolean,
+ desc: 'Enable notifications for push_events'
+ },
+ {
+ required: false,
+ name: :issues_events,
+ type: Boolean,
+ desc: 'Enable notifications for issues_events'
+ },
+ {
+ required: false,
+ name: :confidential_issues_events,
+ type: Boolean,
+ desc: 'Enable notifications for confidential_issues_events'
+ },
+ {
+ required: false,
+ name: :merge_requests_events,
+ type: Boolean,
+ desc: 'Enable notifications for merge_requests_events'
+ },
+ {
+ required: false,
+ name: :note_events,
+ type: Boolean,
+ desc: 'Enable notifications for note_events'
+ },
+ {
+ required: false,
+ name: :tag_push_events,
+ type: Boolean,
+ desc: 'Enable notifications for tag_push_events'
+ },
+ {
+ required: false,
+ name: :pipeline_events,
+ type: Boolean,
+ desc: 'Enable notifications for pipeline_events'
+ },
+ {
+ required: false,
+ name: :wiki_page_events,
+ type: Boolean,
+ desc: 'Enable notifications for wiki_page_events'
+ }
+ ].freeze
+ end
+
+ def self.services
+ {
+ 'asana' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'User API token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
+ }
+ ],
+ 'assembla' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The authentication token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Subdomain setting'
+ }
+ ],
+ 'bamboo' => [
+ {
+ required: true,
+ name: :bamboo_url,
+ type: String,
+ desc: 'Bamboo root URL like https://bamboo.example.com'
+ },
+ {
+ required: true,
+ name: :build_key,
+ type: String,
+ desc: 'Bamboo build plan key like'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with API access, if applicable'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'Password of the user'
+ }
+ ],
+ 'bugzilla' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'buildkite' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Buildkite project GitLab token'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The buildkite project URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'campfire' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Campfire token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Campfire subdomain'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'Campfire room'
+ }
+ ],
+ 'custom-issue-tracker' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'discord' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
+ }
+ ],
+ 'drone-ci' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Drone CI token'
+ },
+ {
+ required: true,
+ name: :drone_url,
+ type: String,
+ desc: 'Drone CI URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'emails-on-push' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :disable_diffs,
+ type: Boolean,
+ desc: 'Disable code diffs'
+ },
+ {
+ required: false,
+ name: :send_from_committer_email,
+ type: Boolean,
+ desc: 'Send from committer'
+ }
+ ],
+ 'external-wiki' => [
+ {
+ required: true,
+ name: :external_wiki_url,
+ type: String,
+ desc: 'The URL of the external Wiki'
+ }
+ ],
+ 'flowdock' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Flowdock token'
+ }
+ ],
+ 'hangouts-chat' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
+ },
+ chat_notification_events
+ ].flatten,
+ 'hipchat' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The room token'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'The room name or ID'
+ },
+ {
+ required: false,
+ name: :color,
+ type: String,
+ desc: 'The room color'
+ },
+ {
+ required: false,
+ name: :notify,
+ type: Boolean,
+ desc: 'Enable notifications'
+ },
+ {
+ required: false,
+ name: :api_version,
+ type: String,
+ desc: 'Leave blank for default (v2)'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'Leave blank for default. https://hipchat.example.com'
+ }
+ ],
+ 'irker' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Recipients/channels separated by whitespaces'
+ },
+ {
+ required: false,
+ name: :default_irc_uri,
+ type: String,
+ desc: 'Default: irc://irc.network.net:6697'
+ },
+ {
+ required: false,
+ name: :server_host,
+ type: String,
+ desc: 'Server host. Default localhost'
+ },
+ {
+ required: false,
+ name: :server_port,
+ type: Integer,
+ desc: 'Server port. Default 6659'
+ },
+ {
+ required: false,
+ name: :colorize_messages,
+ type: Boolean,
+ desc: 'Colorize messages'
+ }
+ ],
+ 'jira' => [
+ {
+ required: true,
+ name: :url,
+ type: String,
+ desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
+ },
+ {
+ required: false,
+ name: :api_url,
+ type: String,
+ desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'The password of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: false,
+ name: :jira_issue_transition_id,
+ type: String,
+ desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+ }
+ ],
+ 'kubernetes' => [
+ {
+ required: true,
+ name: :namespace,
+ type: String,
+ desc: 'The Kubernetes namespace to use'
+ },
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The service token to authenticate against the Kubernetes cluster with'
+ },
+ {
+ required: false,
+ name: :ca_pem,
+ type: String,
+ desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
+ }
+ ],
+ 'mattermost-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Mattermost token'
+ }
+ ],
+ 'slack-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Slack token'
+ }
+ ],
+ 'packagist' => [
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Packagist API token'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'The server'
+ }
+ ],
+ 'pipelines-email' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :notify_only_broken_pipelines,
+ type: Boolean,
+ desc: 'Notify only broken pipelines'
+ },
+ {
+ required: false,
+ name: :notify_only_default_branch,
+ type: Boolean,
+ desc: 'Send notifications only for the default branch'
+ }
+ ],
+ 'pivotaltracker' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Pivotaltracker token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+ }
+ ],
+ 'prometheus' => [
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ],
+ 'pushover' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'The application key'
+ },
+ {
+ required: true,
+ name: :user_key,
+ type: String,
+ desc: 'The user key'
+ },
+ {
+ required: true,
+ name: :priority,
+ type: String,
+ desc: 'The priority'
+ },
+ {
+ required: true,
+ name: :device,
+ type: String,
+ desc: 'Leave blank for all active devices'
+ },
+ {
+ required: true,
+ name: :sound,
+ type: String,
+ desc: 'The sound of the notification'
+ }
+ ],
+ 'redmine' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'The new issue URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
+ 'youtrack' => [
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
+ 'slack' => [
+ chat_notification_settings,
+ chat_notification_flags,
+ chat_notification_channels,
+ chat_notification_events
+ ].flatten,
+ 'microsoft-teams' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
+ }
+ ],
+ 'mattermost' => [
+ chat_notification_settings,
+ chat_notification_flags,
+ chat_notification_channels,
+ chat_notification_events
+ ].flatten,
+ 'teamcity' => [
+ {
+ required: true,
+ name: :teamcity_url,
+ type: String,
+ desc: 'TeamCity root URL like https://teamcity.example.com'
+ },
+ {
+ required: true,
+ name: :build_type,
+ type: String,
+ desc: 'Build configuration ID'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with permissions to trigger a manual build'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'The password of the user'
+ }
+ ]
+ }
+ end
+
+ def self.service_classes
+ [
+ ::AsanaService,
+ ::AssemblaService,
+ ::BambooService,
+ ::BugzillaService,
+ ::BuildkiteService,
+ ::CampfireService,
+ ::CustomIssueTrackerService,
+ ::DiscordService,
+ ::DroneCiService,
+ ::EmailsOnPushService,
+ ::ExternalWikiService,
+ ::FlowdockService,
+ ::HangoutsChatService,
+ ::HipchatService,
+ ::IrkerService,
+ ::JiraService,
+ ::KubernetesService,
+ ::MattermostSlashCommandsService,
+ ::SlackSlashCommandsService,
+ ::PackagistService,
+ ::PipelinesEmailService,
+ ::PivotaltrackerService,
+ ::PrometheusService,
+ ::PushoverService,
+ ::RedmineService,
+ ::YoutrackService,
+ ::SlackService,
+ ::MattermostService,
+ ::MicrosoftTeamsService,
+ ::TeamcityService
+ ]
+ end
+
+ def self.development_service_classes
+ [
+ ::MockCiService,
+ ::MockDeploymentService,
+ ::MockMonitoringService
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb
new file mode 100644
index 00000000000..6441bb579ff
--- /dev/null
+++ b/lib/api/helpers/settings_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module SettingsHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+
+ def self.optional_attributes
+ [*::ApplicationSettingsHelper.visible_attributes,
+ *::ApplicationSettingsHelper.external_authorization_service_attributes,
+ :performance_bar_allowed_group_id].freeze
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb
new file mode 100644
index 00000000000..56fd3c6602d
--- /dev/null
+++ b/lib/api/helpers/users_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module UsersHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+
+ params :optional_index_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
new file mode 100644
index 00000000000..78a92d0f5a6
--- /dev/null
+++ b/lib/api/helpers/variables_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module VariablesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index bb4e536cf57..e7504051808 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -20,6 +20,10 @@ module API
def provider
:github
end
+
+ def provider_unauthorized
+ error!("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.", 401)
+ end
end
desc 'Import a GitHub project' do
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 70b32f7d758..224aaaaf006 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -15,6 +15,12 @@ module API
status code
{ status: success, message: message }.merge(extra_options).compact
end
+
+ def lfs_authentication_url(project)
+ # This is a separate method so that EE can alter its behaviour more
+ # easily.
+ project.http_url_to_repo
+ end
end
namespace 'internal' do
@@ -53,7 +59,7 @@ module API
actor
end
- access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ access_checker_klass = repo_type.access_checker_class
access_checker = access_checker_klass.new(actor, project,
protocol, authentication_abilities: ssh_authentication_abilities,
namespace_path: namespace_path, project_path: project_path,
@@ -81,12 +87,8 @@ module API
gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
git_config_options: [],
-
- # This repository_path is a bogus value but gitlab-shell still requires
- # its presence. https://gitlab.com/gitlab-org/gitlab-shell/issues/135
- repository_path: '/',
-
- gitaly: gitaly_payload(params[:action])
+ gitaly: gitaly_payload(params[:action]),
+ gl_console_messages: check_result.console_messages
}
# Custom option for git-receive-pack command
@@ -118,7 +120,9 @@ module API
raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
end
- Gitlab::LfsToken.new(actor).authentication_payload(project.http_url_to_repo)
+ Gitlab::LfsToken
+ .new(actor)
+ .authentication_payload(lfs_authentication_url(project))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -252,19 +256,25 @@ module API
post '/post_receive' do
status 200
+ output = {} # Messages to gitlab-shell
+ user = identify(params[:identifier])
+ project = Gitlab::GlRepository.parse(params[:gl_repository]).first
+ push_options = Gitlab::PushOptions.new(params[:push_options])
+
PostReceive.perform_async(params[:gl_repository], params[:identifier],
- params[:changes], params[:push_options].to_a)
+ params[:changes], push_options.as_json)
+
+ mr_options = push_options.get(:merge_request)
+ output.merge!(process_mr_push_options(mr_options, project, user, params[:changes])) if mr_options.present?
+
broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
- output = {
- merge_request_urls: merge_request_urls,
+ output.merge!(
broadcast_message: broadcast_message,
- reference_counter_decreased: reference_counter_decreased
- }
-
- project = Gitlab::GlRepository.parse(params[:gl_repository]).first
- user = identify(params[:identifier])
+ reference_counter_decreased: reference_counter_decreased,
+ merge_request_urls: merge_request_urls
+ )
# A user is not guaranteed to be returned; an orphaned write deploy
# key could be used
diff --git a/lib/api/issuable_bulk_update.rb b/lib/api/issuable_bulk_update.rb
deleted file mode 100644
index 5ac6c252d96..00000000000
--- a/lib/api/issuable_bulk_update.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class IssuableBulkUpdate < Grape::API
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- %w(issue merge_request).each do |issuable|
- desc "Updates a list of #{issuable.pluralize}" do
- detail 'This feature was introduced in 11.9'
- end
- params do
- requires :issuable_ids, type: Array[Integer], desc: "Array of #{issuable.pluralize} IDs to be updated"
- optional :state_event, type: String, values: %w(reopen close), desc: 'Reopens or closes a resource'
- optional :milestone_id, type: Integer, desc: 'The milestone ID number'
- optional :add_label_ids, type: Array[Integer], desc: 'IDs of labels to be added'
- optional :remove_label_ids, type: Array[Integer], desc: 'IDs of labels to be removed'
- optional :subscription_event, type: String, values: %w(subscribe unsubscribe),
- desc: 'Subscribes or unsubscribes from a resource'
-
- if issuable == 'issue'
- optional :assignee_ids, type: Array[Integer], desc: 'List of assignee IDs'
- at_least_one_of :state_event, :milestone_id, :add_label_ids, :remove_label_ids,
- :subscription_event, :assignee_ids
- else
- optional :assignee_id, type: Integer, desc: 'ID of the assignee'
- at_least_one_of :state_event, :milestone_id, :add_label_ids, :remove_label_ids,
- :subscription_event, :assignee_id
- end
- end
- put ":id/#{issuable.pluralize}/bulk_update" do
- authorize! :"admin_#{issuable}", user_project
-
- update_params = declared_params(include_missing: false)
-
- result = Issuable::BulkUpdateService.new(user_project, current_user, update_params)
- .execute(issuable)
-
- if result[:success]
- status 200
- quantity = result[:count]
- { message: "#{quantity} #{issuable.pluralize(quantity)} updated" }
- else
- render_api_error!('Bulk update failed', 400)
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index f43f4d961d6..56960a2eb64 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,43 +3,15 @@
module API
class Issues < Grape::API
include PaginationParams
-
- before { authenticate_non_get! }
-
+ helpers Helpers::IssuesHelpers
helpers ::Gitlab::IssuableMetadata
- # EE::API::Issues would override the following helpers
- helpers do
- params :issues_params_ee do
- end
-
- params :issue_params_ee do
- end
- end
+ before { authenticate_non_get! }
helpers do
- # rubocop: disable CodeReuse/ActiveRecord
- def find_issues(args = {})
- args = declared_params.merge(args)
-
- args.delete(:id)
- args[:milestone_title] = args.delete(:milestone)
- args[:label_name] = args.delete(:labels)
- args[:scope] = args[:scope].underscore if args[:scope]
-
- issues = IssuesFinder.new(current_user, args).execute
- .preload(:assignees, :labels, :notes, :timelogs, :project, :author, :closed_by)
- issues.reorder(order_options_with_tie_breaker)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- params :issues_params do
- optional :labels, type: String, desc: 'Comma-separated list of label names'
+ params :issues_stats_params do
+ optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
- optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
- desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these'
@@ -48,16 +20,37 @@ module API
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
+
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
+ optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username'
+ mutually_exclusive :author_id, :author_username
+
optional :assignee_id, types: [Integer, String], integer_none_any: true,
desc: 'Return issues which are assigned to the user with the given ID'
+ optional :assignee_username, type: Array[String], check_assignees_count: true,
+ coerce_with: Validations::CheckAssigneesCount.coerce,
+ desc: 'Return issues which are assigned to the user with the given username'
+ mutually_exclusive :assignee_id, :assignee_username
+
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
- use :pagination
- use :issues_params_ee
+ use :optional_issues_params_ee
+ end
+
+ params :issues_params do
+ optional :with_labels_details, type: Boolean, desc: 'Return more label data than just lable title', default: false
+ optional :state, type: String, values: %w[opened closed all], default: 'all',
+ desc: 'Return opened, closed, or all issues'
+ optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return issues sorted in `asc` or `desc` order.'
+
+ use :issues_stats_params
+ use :pagination
end
params :issue_params do
@@ -65,22 +58,32 @@ module API
optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
optional :assignee_id, type: Integer, desc: '[Deprecated] The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
- optional :labels, type: String, desc: 'Comma-separated list of label names'
+ optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
- use :issue_params_ee
+ use :optional_issue_params_ee
end
end
+ desc "Get currently authenticated user's issues statistics"
+ params do
+ use :issues_stats_params
+ optional :scope, type: String, values: %w[created_by_me assigned_to_me all], default: 'created_by_me',
+ desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
+ end
+ get '/issues_statistics' do
+ authenticate! unless params[:scope] == 'all'
+
+ present issues_statistics, with: Grape::Presenters::Presenter
+ end
+
resource :issues do
desc "Get currently authenticated user's issues" do
- success Entities::IssueBasic
+ success Entities::Issue
end
params do
- optional :state, type: String, values: %w[opened closed all], default: 'all',
- desc: 'Return opened, closed, or all issues'
use :issues_params
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me',
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
@@ -90,7 +93,8 @@ module API
issues = paginate(find_issues)
options = {
- with: Entities::IssueBasic,
+ with: Entities::Issue,
+ with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue')
}
@@ -104,11 +108,9 @@ module API
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of group issues' do
- success Entities::IssueBasic
+ success Entities::Issue
end
params do
- optional :state, type: String, values: %w[opened closed all], default: 'all',
- desc: 'Return opened, closed, or all issues'
use :issues_params
end
get ":id/issues" do
@@ -117,13 +119,24 @@ module API
issues = paginate(find_issues(group_id: group.id, include_subgroups: true))
options = {
- with: Entities::IssueBasic,
+ with: Entities::Issue,
+ with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue')
}
present issues, options
end
+
+ desc 'Get statistics for the list of group issues'
+ params do
+ use :issues_stats_params
+ end
+ get ":id/issues_statistics" do
+ group = find_group!(params[:id])
+
+ present issues_statistics(group_id: group.id, include_subgroups: true), with: Grape::Presenters::Presenter
+ end
end
params do
@@ -133,11 +146,9 @@ module API
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
- success Entities::IssueBasic
+ success Entities::Issue
end
params do
- optional :state, type: String, values: %w[opened closed all], default: 'all',
- desc: 'Return opened, closed, or all issues'
use :issues_params
end
get ":id/issues" do
@@ -146,7 +157,8 @@ module API
issues = paginate(find_issues(project_id: project.id))
options = {
- with: Entities::IssueBasic,
+ with: Entities::Issue,
+ with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
project: user_project,
issuable_metadata: issuable_meta_data(issues, 'Issue')
@@ -155,6 +167,16 @@ module API
present issues, options
end
+ desc 'Get statistics for the list of project issues'
+ params do
+ use :issues_stats_params
+ end
+ get ":id/issues_statistics" do
+ project = find_project!(params[:id])
+
+ present issues_statistics(project_id: project.id), with: Grape::Presenters::Presenter
+ end
+
desc 'Get a single project issue' do
success Entities::Issue
end
@@ -191,6 +213,7 @@ module API
params.delete(:iid) unless current_user.can?(:set_issue_iid, user_project)
issue_params = declared_params(include_missing: false)
+ issue_params[:system_note_timestamp] = params[:created_at]
issue_params = convert_parameters_from_legacy_format(issue_params)
@@ -219,8 +242,8 @@ module API
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
use :issue_params
- at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id, :discussion_locked,
- :labels, :created_at, :due_date, :confidential, :state_event
+
+ at_least_one_of(*Helpers::IssuesHelpers.update_params_at_least_one_of)
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/issues/:issue_iid' do
@@ -229,9 +252,13 @@ module API
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
- # Setting created_at time only allowed for admins and project/group owners
- unless current_user.admin? || user_project.owner == current_user || current_user.owned_groups.include?(user_project.owner)
- params.delete(:updated_at)
+ # Setting updated_at only allowed for admins and owners as well
+ if params[:updated_at].present?
+ if current_user.admin? || user_project.owner == current_user || current_user.owned_groups.include?(user_project.owner)
+ issue.system_note_timestamp = params[:updated_at]
+ else
+ params.delete(:updated_at)
+ end
end
update_params = declared_params(include_missing: false).merge(request: request, api: true)
@@ -306,10 +333,10 @@ module API
merge_requests = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
.execute(issue)
- .flatten
+ .first
present paginate(::Kaminari.paginate_array(merge_requests)),
- with: Entities::MergeRequestBasic,
+ with: Entities::MergeRequest,
current_user: current_user,
project: user_project
end
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 933bd067e26..e7fed55170e 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -109,6 +109,22 @@ module API
status 200
present build, with: Entities::Job
end
+
+ desc 'Delete the artifacts files from a job' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ params do
+ requires :job_id, type: Integer, desc: 'The ID of a job'
+ end
+ delete ':id/jobs/:job_id/artifacts' do
+ authorize_destroy_artifacts!
+ build = find_build!(params[:job_id])
+ authorize!(:destroy_artifacts, build)
+
+ build.erase_erasable_artifacts!
+
+ status :no_content
+ end
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 03f6684226f..5bbf6df78b0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -12,11 +12,15 @@ module API
helpers do
params :optional_params_ee do
end
+
+ params :optional_merge_requests_search_params do
+ end
end
def self.update_params_at_least_one_of
%i[
assignee_id
+ assignee_ids
description
labels
milestone_id
@@ -45,7 +49,7 @@ module API
return merge_requests if args[:view] == 'simple'
merge_requests
- .preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs, metrics: [:latest_closed_by, :merged_by])
+ .with_api_entity_associations
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -95,7 +99,7 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return merge requests sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
- optional :labels, type: String, desc: 'Comma-separated list of label names'
+ optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
@@ -108,10 +112,13 @@ module API
desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :source_branch, type: String, desc: 'Return merge requests with the given source branch'
+ optional :source_project_id, type: Integer, desc: 'Return merge requests with the given source project id'
optional :target_branch, type: String, desc: 'Return merge requests with the given target branch'
optional :search, type: String, desc: 'Search merge requests for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title'
+
+ use :optional_merge_requests_search_params
use :pagination
end
end
@@ -178,9 +185,10 @@ module API
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
+ optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
- optional :labels, type: String, desc: 'Comma-separated list of label names'
- optional :remove_source_branch, type: Boolean, desc: 'Delete source branch when merging'
+ optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
+ optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
@@ -225,6 +233,7 @@ module API
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
+ mr_params = convert_parameters_from_legacy_format(mr_params)
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
@@ -327,7 +336,8 @@ module API
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false)
- mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
+ mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params.has_key?(:remove_source_branch)
+ mr_params = convert_parameters_from_legacy_format(mr_params)
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request)
@@ -376,9 +386,8 @@ module API
)
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(merge_request.target_project, current_user, merge_params)
- .execute(merge_request)
+ AutoMergeService.new(merge_request.target_project, current_user, merge_params)
+ .execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
::MergeRequests::MergeService
.new(merge_request.target_project, current_user, merge_params)
@@ -388,17 +397,28 @@ module API
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
+ desc 'Returns the up to date merge-ref HEAD commit'
+ get ':id/merge_requests/:merge_request_iid/merge_ref' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
+
+ result = ::MergeRequests::MergeabilityCheckService.new(merge_request).execute
+
+ if result.success?
+ present :commit_id, result.payload.dig(:merge_ref_head, :commit_id)
+ else
+ render_api_error!(result.message, 400)
+ end
+ end
+
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
merge_request = find_project_merge_request(params[:merge_request_iid])
- unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
+ unauthorized! unless merge_request.can_cancel_auto_merge?(current_user)
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(merge_request.target_project, current_user)
- .cancel(merge_request)
+ AutoMergeService.new(merge_request.target_project, current_user).cancel(merge_request)
end
desc 'Rebase the merge request against its target branch' do
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index a0ca39b69d4..62e159ab003 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -16,6 +16,7 @@ module API
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
+ optional :title, type: String, desc: 'The title of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
end
@@ -33,6 +34,7 @@ module API
milestones = parent.milestones.order_id_desc
milestones = Milestone.filter_by_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
+ milestones = filter_by_title(milestones, params[:title]) if params[:title]
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 3cc09f6ac3f..77ecb3e7cde 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -44,6 +44,8 @@ module API
requires :id, type: String, desc: "Namespace's ID or path"
end
get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ user_namespace = find_namespace!(params[:id])
+
present user_namespace, with: Entities::Namespace, current_user: current_user
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index f7bd092ce50..416cf39d3ec 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
-
- NOTEABLE_TYPES.each do |noteable_type|
+ Helpers::NotesHelpers.noteable_types.each do |noteable_type|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index c86b50d3736..1d1ef1afc6b 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -118,6 +118,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
+ optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
authorize! :update_pipeline_schedule, pipeline_schedule
@@ -138,6 +139,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
requires :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
+ optional :variable_type, type: String, values: Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
end
put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
authorize! :update_pipeline_schedule, pipeline_schedule
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index ac8fe98e55e..667bf1ec801 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -81,6 +81,19 @@ module API
present pipeline, with: Entities::Pipeline
end
+ desc 'Gets the variables for a given pipeline' do
+ detail 'This feature was introduced in GitLab 11.11'
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/variables' do
+ authorize! :read_pipeline_variable, pipeline
+
+ present pipeline.variables, with: Entities::Variable
+ end
+
desc 'Deletes a pipeline' do
detail 'This feature was introduced in GitLab 11.6'
http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
@@ -124,7 +137,7 @@ module API
pipeline.cancel_running
status 200
- present pipeline.reload, with: Entities::Pipeline
+ present pipeline.reset, with: Entities::Pipeline
end
end
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index c96261a7b57..dcc8d94fb79 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -53,6 +53,8 @@ module API
params do
requires :name, type: String, desc: 'Cluster name'
optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
@@ -83,6 +85,7 @@ module API
params do
requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name'
+ optional :domain, type: String, desc: 'Cluster base domain'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb
new file mode 100644
index 00000000000..734311e1142
--- /dev/null
+++ b/lib/api/project_events.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectEvents < Grape::API
+ include PaginationParams
+ include APIGuard
+ helpers ::API::Helpers::EventsHelpers
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc "List a Project's visible events" do
+ success Entities::Event
+ end
+ params do
+ use :pagination
+ use :event_filter_params
+ use :sort_params
+ end
+
+ get ":id/events" do
+ events = find_events(user_project)
+
+ present_events(events)
+ end
+ end
+ end
+end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index c64ec2fcc95..71891e43dcc 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -3,7 +3,8 @@
module API
class ProjectImport < Grape::API
include PaginationParams
- include Helpers::ProjectsHelpers
+
+ helpers Helpers::ProjectsHelpers
helpers do
def import_params
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index da31bcb8dac..9ecbf37b49a 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -98,6 +98,21 @@ module API
milestone_issuables_for(user_project, :merge_request)
end
+
+ desc 'Promote a milestone to group milestone' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ post ':id/milestones/:milestone_id/promote' do
+ authorize! :admin_milestone, user_project
+ authorize! :admin_milestone, user_project.group
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+ Milestones::PromoteService.new(user_project, current_user).execute(milestone)
+
+ status(200)
+ rescue Milestones::PromoteService::PromoteMilestoneError => error
+ render_api_error!(error.message, 400)
+ end
end
end
end
diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb
new file mode 100644
index 00000000000..2f73785f72d
--- /dev/null
+++ b/lib/api/project_statistics.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectStatistics < Grape::API
+ before do
+ authenticate!
+ not_found! unless user_project.daily_statistics_enabled?
+ authorize! :daily_statistics, user_project
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get the list of project fetch statistics for the last 30 days'
+ get ":id/statistics" do
+ statistic_finder = ::Projects::DailyStatisticsFinder.new(user_project)
+
+ present statistic_finder, with: Entities::ProjectDailyStatistics
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index b23fe6cd4e7..1e14c77b5be 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -6,19 +6,12 @@ module API
class Projects < Grape::API
include PaginationParams
include Helpers::CustomAttributes
- include Helpers::ProjectsHelpers
+
+ helpers Helpers::ProjectsHelpers
before { authenticate_non_get! }
helpers do
- params :optional_filter_params_ee do
- # EE::API::Projects would override this helper
- end
-
- params :optional_update_params_ee do
- # EE::API::Projects would override this helper
- end
-
# EE::API::Projects would override this method
def apply_filters(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
@@ -35,34 +28,6 @@ module API
end
end
- def self.update_params_at_least_one_of
- [
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
- :ci_config_path,
- :container_registry_enabled,
- :default_branch,
- :description,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :merge_method,
- :name,
- :only_allow_merge_if_all_discussions_are_resolved,
- :only_allow_merge_if_pipeline_succeeds,
- :path,
- :printing_merge_request_link_enabled,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :tag_list,
- :visibility,
- :wiki_enabled,
- :avatar
- ]
- end
-
helpers do
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
@@ -184,7 +149,8 @@ module API
if project.saved?
present project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, project)
+ user_can_admin_project: can?(current_user, :admin_project, project),
+ current_user: current_user
else
if project.errors[:limit_reached].present?
error!(project.errors[:limit_reached], 403)
@@ -217,7 +183,8 @@ module API
if project.saved?
present project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, project)
+ user_can_admin_project: can?(current_user, :admin_project, project),
+ current_user: current_user
else
render_validation_error!(project)
end
@@ -281,7 +248,8 @@ module API
conflict!(forked_project.errors.messages)
else
present forked_project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, forked_project)
+ user_can_admin_project: can?(current_user, :admin_project, forked_project),
+ current_user: current_user
end
end
@@ -313,8 +281,9 @@ module API
optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params
+ use :optional_update_params_ee
- at_least_one_of(*::API::Projects.update_params_at_least_one_of)
+ at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end
put ':id' do
authorize_admin_project
@@ -330,7 +299,8 @@ module API
if result[:status] == :success
present user_project, with: Entities::Project,
- user_can_admin_project: can?(current_user, :admin_project, user_project)
+ user_can_admin_project: can?(current_user, :admin_project, user_project),
+ current_user: current_user
else
render_validation_error!(user_project)
end
@@ -344,7 +314,7 @@ module API
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
- present user_project, with: Entities::Project
+ present user_project, with: Entities::Project, current_user: current_user
end
desc 'Unarchive a project' do
@@ -355,7 +325,7 @@ module API
::Projects::UpdateService.new(@project, current_user, archived: false).execute
- present user_project, with: Entities::Project
+ present user_project, with: Entities::Project, current_user: current_user
end
desc 'Star a project' do
@@ -366,9 +336,9 @@ module API
not_modified!
else
current_user.toggle_star(user_project)
- user_project.reload
+ user_project.reset
- present user_project, with: Entities::Project
+ present user_project, with: Entities::Project, current_user: current_user
end
end
@@ -378,9 +348,9 @@ module API
post ':id/unstar' do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
- user_project.reload
+ user_project.reset
- present user_project, with: Entities::Project
+ present user_project, with: Entities::Project, current_user: current_user
else
not_modified!
end
@@ -388,11 +358,9 @@ module API
desc 'Get languages in project repository'
get ':id/languages' do
- if user_project.repository_languages.present?
- user_project.repository_languages.map { |l| [l.name, l.share] }.to_h
- else
- user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
- end
+ ::Projects::RepositoryLanguagesService
+ .new(user_project, current_user)
+ .execute.map { |lang| [lang.name, lang.share] }.to_h
end
desc 'Remove a project'
@@ -420,7 +388,7 @@ module API
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
if result
- present user_project.reload, with: Entities::Project
+ present user_project.reset, with: Entities::Project, current_user: current_user
else
render_api_error!("Project already forked", 409) if user_project.forked?
end
@@ -442,27 +410,24 @@ module API
end
params do
requires :group_id, type: Integer, desc: 'The ID of a group'
- requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
+ requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level'
optional :expires_at, type: Date, desc: 'Share expiration date'
end
post ":id/share" do
authorize! :admin_project, user_project
group = Group.find_by_id(params[:group_id])
- unless group && can?(current_user, :read_group, group)
- not_found!('Group')
- end
-
unless user_project.allowed_to_share_with_group?
break render_api_error!("The project sharing with group is disabled", 400)
end
- link = user_project.project_group_links.new(declared_params(include_missing: false))
+ result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false))
+ .execute(group)
- if link.save
- present link, with: Entities::ProjectGroupLink
+ if result[:status] == :success
+ present result[:link], with: Entities::ProjectGroupLink
else
- render_api_error!(link.errors.full_messages.first, 409)
+ render_api_error!(result[:message], result[:http_status])
end
end
@@ -526,7 +491,7 @@ module API
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
if result
- present user_project, with: Entities::Project
+ present user_project, with: Entities::Project, current_user: current_user
else
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 5af43448727..33dea25289a 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ helpers Helpers::ProtectedBranchesHelpers
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -51,6 +53,8 @@ module API
optional :merge_access_level, type: Integer,
values: ProtectedBranch::MergeAccessLevel.allowed_access_levels,
desc: 'Access levels allowed to merge (defaults: `40`, maintainer access level)'
+
+ use :optional_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/protected_branches' do
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index e3072684ef7..5d1b40e3bff 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -8,6 +8,8 @@ module API
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
+ before { authorize! :read_release, user_project }
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index cb85028f22c..6b17f4317db 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -23,7 +23,7 @@ module API
get ':id/releases' do
releases = ::ReleasesFinder.new(user_project, current_user).execute
- present paginate(releases), with: Entities::Release
+ present paginate(releases), with: Entities::Release, current_user: current_user
end
desc 'Get a single project release' do
@@ -34,9 +34,9 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
- authorize_read_release!
+ authorize_download_code!
- present release, with: Entities::Release
+ present release, with: Entities::Release, current_user: current_user
end
desc 'Create a new release' do
@@ -63,7 +63,7 @@ module API
.execute
if result[:status] == :success
- present result[:release], with: Entities::Release
+ present result[:release], with: Entities::Release, current_user: current_user
else
render_api_error!(result[:message], result[:http_status])
end
@@ -86,7 +86,7 @@ module API
.execute
if result[:status] == :success
- present result[:release], with: Entities::Release
+ present result[:release], with: Entities::Release, current_user: current_user
else
render_api_error!(result[:message], result[:http_status])
end
@@ -107,7 +107,7 @@ module API
.execute
if result[:status] == :success
- present result[:release], with: Entities::Release
+ present result[:release], with: Entities::Release, current_user: current_user
else
render_api_error!(result[:message], result[:http_status])
end
@@ -135,6 +135,10 @@ module API
authorize! :destroy_release, release
end
+ def authorize_download_code!
+ authorize! :download_code, release
+ end
+
def release
@release ||= user_project.releases.find_by_tag(params[:tag])
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 32e05d84491..4106a2cdf38 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -89,11 +89,9 @@ module API
optional :format, type: String, desc: 'The archive format'
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
- begin
- send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
- rescue
- not_found!('File')
- end
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
+ rescue
+ not_found!('File')
end
desc 'Compare two branches, tags, or commits' do
@@ -118,12 +116,10 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
end
get ':id/repository/contributors' do
- begin
- contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort]))
- present paginate(contributors), with: Entities::Contributor
- rescue
- not_found!
- end
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort]))
+ present paginate(contributors), with: Entities::Contributor
+ rescue
+ not_found!
end
desc 'Get the common ancestor between commits' do
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index 0c328f7268e..448bef12cec 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- EVENTABLE_TYPES = [Issue, MergeRequest].freeze
-
- EVENTABLE_TYPES.each do |eventable_type|
+ Helpers::ResourceLabelEventsHelpers.eventable_types.each do |eventable_type|
parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index c60d25b88cb..fdf4904e9f5 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -15,12 +15,14 @@ module API
optional :info, type: Hash, desc: %q(Runner's metadata)
optional :active, type: Boolean, desc: 'Should Runner be active'
optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
+ optional :access_level, type: String, values: Ci::Runner.access_levels.keys,
+ desc: 'The access_level of the runner'
optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
end
post '/' do
- attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :maximum_timeout])
+ attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
.merge(get_runner_details_from_request)
attributes =
@@ -96,6 +98,7 @@ module API
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
+ optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
end
post '/request' do
authenticate_runner!
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index f72b33605a7..f3fea463e7f 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -17,6 +17,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get do
@@ -24,6 +25,7 @@ module API
runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
@@ -38,6 +40,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get 'all' do
@@ -47,6 +50,7 @@ module API
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
@@ -139,6 +143,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
@@ -146,6 +151,7 @@ module API
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
diff --git a/lib/api/search.rb b/lib/api/search.rb
index f5db692afe5..1cab1a97186 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -17,7 +17,8 @@ module API
blobs: Entities::Blob,
wiki_blobs: Entities::Blob,
snippet_titles: Entities::Snippet,
- snippet_blobs: Entities::Snippet
+ snippet_blobs: Entities::Snippet,
+ users: Entities::UserBasic
}.freeze
def search(additional_params = {})
@@ -45,6 +46,18 @@ module API
def entity
SCOPE_ENTITY[params[:scope].to_sym]
end
+
+ def verify_search_scope!
+ # In EE we have additional validation requirements for searches.
+ # Defining this method here as a noop allows us to easily extend it in
+ # EE, without having to modify this file directly.
+ end
+
+ def check_users_search_allowed!
+ if params[:scope].to_sym == :users && Feature.disabled?(:users_search, default_enabled: true)
+ render_api_error!({ error: _("Scope not supported with disabled 'users_search' feature!") }, 400)
+ end
+ end
end
resource :search do
@@ -55,12 +68,14 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs',
- values: %w(projects issues merge_requests milestones snippet_titles snippet_blobs)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.global_search_scopes
use :pagination
end
get do
+ verify_search_scope!
+ check_users_search_allowed!
+
present search, with: entity
end
end
@@ -74,12 +89,14 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- projects, issues, merge_requests, milestones',
- values: %w(projects issues merge_requests milestones)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.group_search_scopes
use :pagination
end
get ':id/(-/)search' do
+ verify_search_scope!
+ check_users_search_allowed!
+
present search(group_id: user_group.id), with: entity
end
end
@@ -93,13 +110,15 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs',
- values: %w(issues merge_requests milestones notes wiki_blobs commits blobs)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.project_search_scopes
+ optional :ref, type: String, desc: 'The name of a repository branch or tag. If not given, the default branch is used'
use :pagination
end
get ':id/(-/)search' do
- present search(project_id: user_project.id), with: entity
+ check_users_search_allowed!
+
+ present search({ project_id: user_project.id, repository_ref: params[:ref] }), with: entity
end
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 145897516a0..bc77fae87fa 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,675 +1,8 @@
# frozen_string_literal: true
module API
class Services < Grape::API
- CHAT_NOTIFICATION_SETTINGS = [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The chat webhook'
- },
- {
- required: false,
- name: :username,
- type: String,
- desc: 'The chat username'
- },
- {
- required: false,
- name: :channel,
- type: String,
- desc: 'The default chat channel'
- }
- ].freeze
-
- CHAT_NOTIFICATION_FLAGS = [
- {
- required: false,
- name: :notify_only_broken_pipelines,
- type: Boolean,
- desc: 'Send notifications for broken pipelines'
- },
- {
- required: false,
- name: :notify_only_default_branch,
- type: Boolean,
- desc: 'Send notifications only for the default branch'
- }
- ].freeze
-
- CHAT_NOTIFICATION_CHANNELS = [
- {
- required: false,
- name: :push_channel,
- type: String,
- desc: 'The name of the channel to receive push_events notifications'
- },
- {
- required: false,
- name: :issue_channel,
- type: String,
- desc: 'The name of the channel to receive issues_events notifications'
- },
- {
- required: false,
- name: :confidential_issue_channel,
- type: String,
- desc: 'The name of the channel to receive confidential_issues_events notifications'
- },
- {
- required: false,
- name: :merge_request_channel,
- type: String,
- desc: 'The name of the channel to receive merge_requests_events notifications'
- },
- {
- required: false,
- name: :note_channel,
- type: String,
- desc: 'The name of the channel to receive note_events notifications'
- },
- {
- required: false,
- name: :tag_push_channel,
- type: String,
- desc: 'The name of the channel to receive tag_push_events notifications'
- },
- {
- required: false,
- name: :pipeline_channel,
- type: String,
- desc: 'The name of the channel to receive pipeline_events notifications'
- },
- {
- required: false,
- name: :wiki_page_channel,
- type: String,
- desc: 'The name of the channel to receive wiki_page_events notifications'
- }
- ].freeze
-
- CHAT_NOTIFICATION_EVENTS = [
- {
- required: false,
- name: :push_events,
- type: Boolean,
- desc: 'Enable notifications for push_events'
- },
- {
- required: false,
- name: :issues_events,
- type: Boolean,
- desc: 'Enable notifications for issues_events'
- },
- {
- required: false,
- name: :confidential_issues_events,
- type: Boolean,
- desc: 'Enable notifications for confidential_issues_events'
- },
- {
- required: false,
- name: :merge_requests_events,
- type: Boolean,
- desc: 'Enable notifications for merge_requests_events'
- },
- {
- required: false,
- name: :note_events,
- type: Boolean,
- desc: 'Enable notifications for note_events'
- },
- {
- required: false,
- name: :tag_push_events,
- type: Boolean,
- desc: 'Enable notifications for tag_push_events'
- },
- {
- required: false,
- name: :pipeline_events,
- type: Boolean,
- desc: 'Enable notifications for pipeline_events'
- },
- {
- required: false,
- name: :wiki_page_events,
- type: Boolean,
- desc: 'Enable notifications for wiki_page_events'
- }
- ].freeze
-
- services = {
- 'asana' => [
- {
- required: true,
- name: :api_key,
- type: String,
- desc: 'User API token'
- },
- {
- required: false,
- name: :restrict_to_branch,
- type: String,
- desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
- }
- ],
- 'assembla' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The authentication token'
- },
- {
- required: false,
- name: :subdomain,
- type: String,
- desc: 'Subdomain setting'
- }
- ],
- 'bamboo' => [
- {
- required: true,
- name: :bamboo_url,
- type: String,
- desc: 'Bamboo root URL like https://bamboo.example.com'
- },
- {
- required: true,
- name: :build_key,
- type: String,
- desc: 'Bamboo build plan key like'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'A user with API access, if applicable'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'Passord of the user'
- }
- ],
- 'bugzilla' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'New issue URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'Issues URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
- }
- ],
- 'buildkite' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Buildkite project GitLab token'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'The buildkite project URL'
- },
- {
- required: false,
- name: :enable_ssl_verification,
- type: Boolean,
- desc: 'Enable SSL verification for communication'
- }
- ],
- 'campfire' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Campfire token'
- },
- {
- required: false,
- name: :subdomain,
- type: String,
- desc: 'Campfire subdomain'
- },
- {
- required: false,
- name: :room,
- type: String,
- desc: 'Campfire room'
- }
- ],
- 'custom-issue-tracker' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'New issue URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'Issues URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
- }
- ],
- 'discord' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
- }
- ],
- 'drone-ci' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Drone CI token'
- },
- {
- required: true,
- name: :drone_url,
- type: String,
- desc: 'Drone CI URL'
- },
- {
- required: false,
- name: :enable_ssl_verification,
- type: Boolean,
- desc: 'Enable SSL verification for communication'
- }
- ],
- 'emails-on-push' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Comma-separated list of recipient email addresses'
- },
- {
- required: false,
- name: :disable_diffs,
- type: Boolean,
- desc: 'Disable code diffs'
- },
- {
- required: false,
- name: :send_from_committer_email,
- type: Boolean,
- desc: 'Send from committer'
- }
- ],
- 'external-wiki' => [
- {
- required: true,
- name: :external_wiki_url,
- type: String,
- desc: 'The URL of the external Wiki'
- }
- ],
- 'flowdock' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Flowdock token'
- }
- ],
- 'hangouts-chat' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
- },
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'irker' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Recipients/channels separated by whitespaces'
- },
- {
- required: false,
- name: :default_irc_uri,
- type: String,
- desc: 'Default: irc://irc.network.net:6697'
- },
- {
- required: false,
- name: :server_host,
- type: String,
- desc: 'Server host. Default localhost'
- },
- {
- required: false,
- name: :server_port,
- type: Integer,
- desc: 'Server port. Default 6659'
- },
- {
- required: false,
- name: :colorize_messages,
- type: Boolean,
- desc: 'Colorize messages'
- }
- ],
- 'jira' => [
- {
- required: true,
- name: :url,
- type: String,
- desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
- },
- {
- required: false,
- name: :api_url,
- type: String,
- desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'The username of the user created to be used with GitLab/JIRA'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'The password of the user created to be used with GitLab/JIRA'
- },
- {
- required: false,
- name: :jira_issue_transition_id,
- type: String,
- desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
- }
- ],
-
- 'kubernetes' => [
- {
- required: true,
- name: :namespace,
- type: String,
- desc: 'The Kubernetes namespace to use'
- },
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The service token to authenticate against the Kubernetes cluster with'
- },
- {
- required: false,
- name: :ca_pem,
- type: String,
- desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
- }
- ],
- 'mattermost-slash-commands' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Mattermost token'
- }
- ],
- 'slack-slash-commands' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Slack token'
- }
- ],
- 'packagist' => [
- {
- required: true,
- name: :username,
- type: String,
- desc: 'The username'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Packagist API token'
- },
- {
- required: false,
- name: :server,
- type: String,
- desc: 'The server'
- }
- ],
- 'pipelines-email' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Comma-separated list of recipient email addresses'
- },
- {
- required: false,
- name: :notify_only_broken_pipelines,
- type: Boolean,
- desc: 'Notify only broken pipelines'
- }
- ],
- 'pivotaltracker' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Pivotaltracker token'
- },
- {
- required: false,
- name: :restrict_to_branch,
- type: String,
- desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
- }
- ],
- 'prometheus' => [
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
- }
- ],
- 'pushover' => [
- {
- required: true,
- name: :api_key,
- type: String,
- desc: 'The application key'
- },
- {
- required: true,
- name: :user_key,
- type: String,
- desc: 'The user key'
- },
- {
- required: true,
- name: :priority,
- type: String,
- desc: 'The priority'
- },
- {
- required: true,
- name: :device,
- type: String,
- desc: 'Leave blank for all active devices'
- },
- {
- required: true,
- name: :sound,
- type: String,
- desc: 'The sound of the notification'
- }
- ],
- 'redmine' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'The new issue URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'The project URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'The issues URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'The description of the tracker'
- }
- ],
- 'slack' => [
- CHAT_NOTIFICATION_SETTINGS,
- CHAT_NOTIFICATION_FLAGS,
- CHAT_NOTIFICATION_CHANNELS,
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'microsoft-teams' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
- }
- ],
- 'mattermost' => [
- CHAT_NOTIFICATION_SETTINGS,
- CHAT_NOTIFICATION_FLAGS,
- CHAT_NOTIFICATION_CHANNELS,
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'teamcity' => [
- {
- required: true,
- name: :teamcity_url,
- type: String,
- desc: 'TeamCity root URL like https://teamcity.example.com'
- },
- {
- required: true,
- name: :build_type,
- type: String,
- desc: 'Build configuration ID'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'A user with permissions to trigger a manual build'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'The password of the user'
- }
- ]
- }
-
- service_classes = [
- AsanaService,
- AssemblaService,
- BambooService,
- BugzillaService,
- BuildkiteService,
- CampfireService,
- CustomIssueTrackerService,
- DiscordService,
- DroneCiService,
- EmailsOnPushService,
- ExternalWikiService,
- FlowdockService,
- HangoutsChatService,
- IrkerService,
- JiraService,
- KubernetesService,
- MattermostSlashCommandsService,
- SlackSlashCommandsService,
- PackagistService,
- PipelinesEmailService,
- PivotaltrackerService,
- PrometheusService,
- PushoverService,
- RedmineService,
- SlackService,
- MattermostService,
- MicrosoftTeamsService,
- TeamcityService
- ]
+ services = Helpers::ServicesHelpers.services
+ service_classes = Helpers::ServicesHelpers.service_classes
if Rails.env.development?
services['mock-ci'] = [
@@ -683,11 +16,7 @@ module API
services['mock-deployment'] = []
services['mock-monitoring'] = []
- service_classes += [
- MockCiService,
- MockDeploymentService,
- MockMonitoringService
- ]
+ service_classes += Helpers::ServicesHelpers.development_service_classes
end
SERVICES = services.freeze
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index b16faffe335..6767ef882cb 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -4,11 +4,18 @@ module API
class Settings < Grape::API
before { authenticated_as_admin! }
+ helpers Helpers::SettingsHelpers
+
helpers do
def current_settings
@current_setting ||=
(ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults)
end
+
+ def filter_attributes_using_license(attrs)
+ # This method will be redefined in EE.
+ attrs
+ end
end
desc 'Get the current application settings' do
@@ -35,7 +42,8 @@ module API
end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
- optional :default_branch_protection, type: Integer, values: Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
+ optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
+ optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
@@ -130,10 +138,10 @@ module API
desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys."
end
- optional_attributes = ::ApplicationSettingsHelper.visible_attributes << :performance_bar_allowed_group_id
+ use :optional_params_ee
- optional(*optional_attributes)
- at_least_one_of(*optional_attributes)
+ optional(*Helpers::SettingsHelpers.optional_attributes)
+ at_least_one_of(*Helpers::SettingsHelpers.optional_attributes)
end
put "application/settings" do
attrs = declared_params(include_missing: false)
@@ -156,6 +164,8 @@ module API
attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
+ attrs = filter_attributes_using_license(attrs)
+
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
present current_settings, with: Entities::ApplicationSetting
else
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 326d55afd0e..f8b37b33348 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -16,6 +16,10 @@ module API
def public_snippets
SnippetsFinder.new(current_user, scope: :are_public).execute
end
+
+ def snippets
+ SnippetsFinder.new(current_user).execute
+ end
end
desc 'Get a snippets list for authenticated user' do
@@ -48,7 +52,10 @@ module API
requires :id, type: Integer, desc: 'The ID of a snippet'
end
get ':id' do
- snippet = snippets_for_current_user.find(params[:id])
+ snippet = snippets.find_by_id(params[:id])
+
+ break not_found!('Snippet') unless snippet
+
present snippet, with: Entities::PersonalSnippet
end
@@ -94,9 +101,8 @@ module API
desc: 'The visibility of the snippet'
at_least_one_of :title, :file_name, :content, :visibility
end
- # rubocop: disable CodeReuse/ActiveRecord
put ':id' do
- snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet
@@ -113,7 +119,6 @@ module API
render_validation_error!(snippet)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Remove snippet' do
detail 'This feature was introduced in GitLab 8.15.'
@@ -122,16 +127,14 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
- # rubocop: disable CodeReuse/ActiveRecord
delete ':id' do
- snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :destroy_personal_snippet, snippet
destroy_conditionally!(snippet)
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Get a raw snippet' do
detail 'This feature was introduced in GitLab 8.15.'
@@ -139,9 +142,8 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
- # rubocop: disable CodeReuse/ActiveRecord
get ":id/raw" do
- snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ snippet = snippets.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
env['api.format'] = :txt
@@ -149,7 +151,6 @@ module API
header['Content-Disposition'] = 'attachment'
present snippet.content
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Get the user agent details for a snippet' do
success Entities::UserAgentDetail
@@ -157,17 +158,15 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
end
- # rubocop: disable CodeReuse/ActiveRecord
get ":id/user_agent_detail" do
authenticated_as_admin!
- snippet = Snippet.find_by!(id: params[:id])
+ snippet = Snippet.find_by_id!(params[:id])
break not_found!('UserAgentDetail') unless snippet.user_agent_detail
present snippet.user_agent_detail, with: Entities::UserAgentDetail
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 64ac8ece56c..d2196f05173 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -6,6 +6,8 @@ module API
before { authenticate! }
+ helpers ::Gitlab::IssuableMetadata
+
ISSUABLE_TYPES = {
'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
'issues' => ->(iid) { find_project_issue(iid) }
@@ -42,6 +44,30 @@ module API
def find_todos
TodosFinder.new(current_user, params).execute
end
+
+ def issuable_and_awardable?(type)
+ obj_type = Object.const_get(type)
+
+ (obj_type < Issuable) && (obj_type < Awardable)
+ rescue NameError
+ false
+ end
+
+ def batch_load_issuable_metadata(todos, options)
+ # This should be paginated and will cause Rails to SELECT for all the Todos
+ todos_by_type = todos.group_by(&:target_type)
+
+ todos_by_type.keys.each do |type|
+ next unless issuable_and_awardable?(type)
+
+ collection = todos_by_type[type]
+
+ next unless collection
+
+ targets = collection.map(&:target)
+ options[type] = { issuable_metadata: issuable_meta_data(targets, type) }
+ end
+ end
end
desc 'Get a todo list' do
@@ -51,7 +77,11 @@ module API
use :pagination
end
get do
- present paginate(find_todos), with: Entities::Todo, current_user: current_user
+ todos = paginate(find_todos.with_api_entity_associations)
+ options = { with: Entities::Todo, current_user: current_user }
+ batch_load_issuable_metadata(todos, options)
+
+ present todos, options
end
desc 'Mark a todo as done' do
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 8fc7c7361e1..0e829c5699b 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -13,7 +13,7 @@ module API
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :token, type: String, desc: 'The unique token of trigger or job token'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7d88880d412..6afeebb6890 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -15,6 +15,8 @@ module API
authenticate_non_get!
end
+ helpers Helpers::UsersHelpers
+
helpers do
# rubocop: disable CodeReuse/ActiveRecord
def find_user_by_id(params)
@@ -51,6 +53,8 @@ module API
optional :avatar, type: File, desc: 'Avatar image for user'
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider
+
+ use :optional_params_ee
end
params :sort_params do
@@ -80,6 +84,7 @@ module API
use :sort_params
use :pagination
use :with_custom_attributes
+ use :optional_index_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
get do
@@ -124,7 +129,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user && can?(current_user, :read_user, user)
- opts = { with: current_user&.admin? ? Entities::UserWithAdmin : Entities::User, current_user: current_user }
+ opts = { with: current_user&.admin? ? Entities::UserDetailsWithAdmin : Entities::User, current_user: current_user }
user, opts = with_custom_attributes(user, opts)
present user, opts
diff --git a/lib/api/validations/check_assignees_count.rb b/lib/api/validations/check_assignees_count.rb
new file mode 100644
index 00000000000..836ec936b31
--- /dev/null
+++ b/lib/api/validations/check_assignees_count.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ class CheckAssigneesCount < Grape::Validations::Base
+ def self.coerce
+ lambda do |value|
+ case value
+ when String, Array
+ Array.wrap(value)
+ else
+ []
+ end
+ end
+ end
+
+ def validate_param!(attr_name, params)
+ return if param_allowed?(attr_name, params)
+
+ raise Grape::Exceptions::Validation,
+ params: [@scope.full_name(attr_name)],
+ message: "allows one value, but found #{params[attr_name].size}: #{params[attr_name].join(", ")}"
+ end
+
+ private
+
+ def param_allowed?(attr_name, params)
+ params[attr_name].size <= 1
+ end
+ end
+ end
+end
diff --git a/lib/api/validations/types/labels_list.rb b/lib/api/validations/types/labels_list.rb
new file mode 100644
index 00000000000..47cd83c29cf
--- /dev/null
+++ b/lib/api/validations/types/labels_list.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Types
+ class LabelsList
+ def self.coerce
+ lambda do |value|
+ case value
+ when String
+ value.split(',').map(&:strip)
+ when Array
+ value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ when LabelsList
+ value
+ else
+ []
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 148deb86c4c..af1d7936556 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,6 +7,16 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ helpers Helpers::VariablesHelpers
+
+ helpers do
+ def filter_variable_parameters(params)
+ # This method exists so that EE can more easily filter out certain
+ # parameters, without having to modify the source code directly.
+ params
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -46,10 +56,15 @@ module API
params do
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
+ optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+
+ use :optional_params_ee
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
+ variable_params = filter_variable_parameters(variable_params)
variable = user_project.variables.create(variable_params)
@@ -66,7 +81,11 @@ module API
params do
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
+ optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+
+ use :optional_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
@@ -75,6 +94,7 @@ module API
break not_found!('Variable') unless variable
variable_params = declared_params(include_missing: false).except(:key)
+ variable_params = filter_variable_parameters(variable_params)
if variable.update(variable_params)
present variable, with: Entities::Variable
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 74cd857f447..eca1b529094 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -2,13 +2,29 @@
module API
class Version < Grape::API
+ helpers ::API::Helpers::GraphqlHelpers
+
before { authenticate! }
+ METADATA_QUERY = <<~EOF
+ {
+ metadata {
+ version
+ revision
+ }
+ }
+ EOF
+
desc 'Get the version information of the GitLab instance.' do
detail 'This feature was introduced in GitLab 8.13.'
end
get '/version' do
- { version: Gitlab::VERSION, revision: Gitlab.revision }
+ conditionally_graphql!(
+ query: METADATA_QUERY,
+ context: { current_user: current_user },
+ transform: ->(result) { result.dig('data', 'metadata') },
+ fallback: -> { { version: Gitlab::VERSION, revision: Gitlab.revision } }
+ )
end
end
end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 994074ddc67..5724adb2c40 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -33,7 +33,8 @@ module API
authorize! :read_wiki, user_project
entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
- present user_project.wiki.pages, with: entity
+
+ present user_project.wiki.list_pages(load_content: params[:with_content]), with: entity
end
desc 'Get a wiki page' do
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index e6bf3d1856f..cd8e29d14d3 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -4,6 +4,7 @@ require 'yaml'
module Backup
class Database
+ include Backup::Helper
attr_reader :progress
attr_reader :config, :db_file_name
@@ -17,7 +18,7 @@ module Backup
FileUtils.mkdir_p(File.dirname(db_file_name))
FileUtils.rm_f(db_file_name)
compress_rd, compress_wr = IO.pipe
- compress_pid = spawn(*%w(gzip -1 -c), in: compress_rd, out: [db_file_name, 'w', 0600])
+ compress_pid = spawn(gzip_cmd, in: compress_rd, out: [db_file_name, 'w', 0600])
compress_rd.close
dump_pid =
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 2bac84846c5..098f2da6d88 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -31,10 +31,10 @@ module Backup
raise Backup::Error, 'Backup failed'
end
- run_pipeline!([%W(#{tar} --exclude=lost+found -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ run_pipeline!([%W(#{tar} --exclude=lost+found -C #{@backup_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
FileUtils.rm_rf(@backup_files_dir)
else
- run_pipeline!([%W(#{tar} --exclude=lost+found -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ run_pipeline!([%W(#{tar} --exclude=lost+found -C #{app_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
end
end
diff --git a/lib/backup/helper.rb b/lib/backup/helper.rb
index 22f00aef569..2c2e35add0e 100644
--- a/lib/backup/helper.rb
+++ b/lib/backup/helper.rb
@@ -29,5 +29,13 @@ module Backup
EOS
raise message
end
+
+ def gzip_cmd
+ @gzip_cmd ||= if ENV['GZIP_RSYNCABLE'] == 'yes'
+ "gzip --rsyncable -c -1"
+ else
+ "gzip -c -1"
+ end
+ end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 06b0338b1ed..aeaf61cda39 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -235,7 +235,11 @@ module Backup
end
def tar_file
- @tar_file ||= "#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
+ @tar_file ||= if ENV['BACKUP']
+ ENV['BACKUP'] + "#{FILE_NAME_SUFFIX}"
+ else
+ "#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
+ end
end
def backup_information
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 9577df2634a..5a20b6ae0a6 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -9,7 +9,7 @@ module Backup
def initialize(progress)
@progress = progress
- super('uploads', Rails.root.join('public/uploads'))
+ super('uploads', File.join(Gitlab.config.uploads.storage_path, "uploads"))
end
end
end
diff --git a/lib/banzai/color_parser.rb b/lib/banzai/color_parser.rb
index 6d01d51955c..cce79e73d2d 100644
--- a/lib/banzai/color_parser.rb
+++ b/lib/banzai/color_parser.rb
@@ -2,13 +2,13 @@
module Banzai
module ColorParser
- ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/ # 0.0..1.0
- PERCENTS = /(?:\d{1,2}|100)%/ # 00%..100%
- ALPHA_CHANNEL = /(?:,\s*(?:#{ALPHA}|#{PERCENTS}))?/
- BITS = /\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5])/ # 00..255
- DEGS = /-?\d+(?:deg)?/i # [-]digits[deg]
- RADS = /-?(?:\d+(?:\.\d+)?|\.\d+)rad/i # [-](digits[.digits] OR .digits)rad
- HEX_FORMAT = /\#(?:\h{3}|\h{4}|\h{6}|\h{8})/
+ ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/.freeze # 0.0..1.0
+ PERCENTS = /(?:\d{1,2}|100)%/.freeze # 00%..100%
+ ALPHA_CHANNEL = /(?:,\s*(?:#{ALPHA}|#{PERCENTS}))?/.freeze
+ BITS = /\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5])/.freeze # 00..255
+ DEGS = /-?\d+(?:deg)?/i.freeze # [-]digits[deg]
+ RADS = /-?(?:\d+(?:\.\d+)?|\.\d+)rad/i.freeze # [-](digits[.digits] OR .digits)rad
+ HEX_FORMAT = /\#(?:\h{3}|\h{4}|\h{6}|\h{8})/.freeze
RGB_FORMAT = %r{
(?:rgba?
\(
@@ -20,7 +20,7 @@ module Banzai
#{ALPHA_CHANNEL}
\)
)
- }xi
+ }xi.freeze
HSL_FORMAT = %r{
(?:hsla?
\(
@@ -28,11 +28,11 @@ module Banzai
#{ALPHA_CHANNEL}
\)
)
- }xi
+ }xi.freeze
FORMATS = [HEX_FORMAT, RGB_FORMAT, HSL_FORMAT].freeze
- COLOR_FORMAT = /\A(#{Regexp.union(FORMATS)})\z/ix
+ COLOR_FORMAT = /\A(#{Regexp.union(FORMATS)})\z/ix.freeze
# Public: Analyzes whether the String is a color code.
#
diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb
index f346151a3c1..2acc9d13f07 100644
--- a/lib/banzai/commit_renderer.rb
+++ b/lib/banzai/commit_renderer.rb
@@ -2,7 +2,7 @@
module Banzai
module CommitRenderer
- ATTRIBUTES = [:description, :title].freeze
+ ATTRIBUTES = [:description, :title, :full_title].freeze
def self.render(commits, project, user = nil)
obj_renderer = ObjectRenderer.new(user: user, default_project: project)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 4764f8e1e19..0224dd8fcd1 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -181,9 +181,10 @@ module Banzai
title = object_link_title(object, matches)
klass = reference_class(object_sym)
- data = data_attributes_for(link_content || match, parent, object,
- link_content: !!link_content,
- link_reference: link_reference)
+ data_attributes = data_attributes_for(link_content || match, parent, object,
+ link_content: !!link_content,
+ link_reference: link_reference)
+ data = data_attribute(data_attributes)
url =
if matches.names.include?("url") && matches[:url]
@@ -194,25 +195,31 @@ module Banzai
content = link_content || object_link_text(object, matches)
- %(<a href="#{url}" #{data}
- title="#{escape_once(title)}"
- class="#{klass}">#{content}</a>)
+ link = %(<a href="#{url}" #{data}
+ title="#{escape_once(title)}"
+ class="#{klass}">#{content}</a>)
+
+ wrap_link(link, object)
else
match
end
end
end
+ def wrap_link(link, object)
+ link
+ end
+
def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
object_parent_type = parent.is_a?(Group) ? :group : :project
- data_attribute(
+ {
original: text,
link: link_content,
link_reference: link_reference,
object_parent_type => parent.id,
object_sym => object.id
- )
+ }
end
def object_link_text_extras(object, matches)
@@ -356,6 +363,14 @@ module Banzai
group_ref
end
+
+ def unescape_html_entities(text)
+ CGI.unescapeHTML(text.to_s)
+ end
+
+ def escape_html_entities(text)
+ CGI.escapeHTML(text.to_s)
+ end
end
end
end
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 086adf59d2b..56214043d87 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -33,7 +33,7 @@ module Banzai
# https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65
#
# Rubular: http://rubular.com/r/nrL3r9yUiq
- LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}
+ LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!\?|!|\.|,|:)}.freeze
# Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb
index ad367cc5efe..8f5ad9981e5 100644
--- a/lib/banzai/filter/blockquote_fence_filter.rb
+++ b/lib/banzai/filter/blockquote_fence_filter.rb
@@ -42,7 +42,9 @@ module Banzai
def call
@text.gsub(REGEX) do
if $~[:quote]
- $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">")
+ # keep the same number of source lines/positions by replacing the
+ # fence lines with newlines
+ "\n" + $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">") + "\n"
else
$~[0]
end
diff --git a/lib/banzai/filter/front_matter_filter.rb b/lib/banzai/filter/front_matter_filter.rb
index a27d18facd1..544231adea4 100644
--- a/lib/banzai/filter/front_matter_filter.rb
+++ b/lib/banzai/filter/front_matter_filter.rb
@@ -20,7 +20,7 @@ module Banzai
\s*
^\k<delim> # closing front matter marker
\s*
- }mx
+ }mx.freeze
def call
html.sub(PATTERN) do |_match|
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index f90a35952e5..4892668fc22 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -82,28 +82,26 @@ module Banzai
def object_link_text(object, matches)
label_suffix = ''
+ parent = project || group
if project || full_path_ref?(matches)
project_path = full_project_path(matches[:namespace], matches[:project])
parent_from_ref = from_ref_cached(project_path)
- reference = parent_from_ref.to_human_reference(project || group)
+ reference = parent_from_ref.to_human_reference(parent)
label_suffix = " <i>in #{reference}</i>" if reference.present?
end
- LabelsHelper.render_colored_label(object, label_suffix)
+ presenter = object.present(issuable_subject: parent)
+ LabelsHelper.render_colored_label(presenter, label_suffix: label_suffix, title: tooltip_title(presenter))
end
- def full_path_ref?(matches)
- matches[:namespace] && matches[:project]
- end
-
- def unescape_html_entities(text)
- CGI.unescapeHTML(text.to_s)
+ def tooltip_title(label)
+ nil
end
- def escape_html_entities(text)
- CGI.escapeHTML(text.to_s)
+ def full_path_ref?(matches)
+ matches[:namespace] && matches[:project]
end
def object_link_title(object, matches)
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 7098767b583..f05902078dc 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -20,7 +20,9 @@ module Banzai
end
def object_link_title(object, matches)
- object_link_commit_title(object, matches) || super
+ # The method will return `nil` if object is not a commit
+ # allowing for properly handling the extended MR Tooltip
+ object_link_commit_title(object, matches)
end
def object_link_text_extras(object, matches)
@@ -53,6 +55,14 @@ module Banzai
.includes(target_project: :namespace)
end
+ def reference_class(object_sym, options = {})
+ super(object_sym, tooltip: false)
+ end
+
+ def data_attributes_for(text, parent, object, data = {})
+ super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title)
+ end
+
private
def object_link_commit_title(object, matches)
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index fce042e8946..08969753d75 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -51,13 +51,13 @@ module Banzai
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
- text.gsub(pattern) do |match|
+ unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
yield match, milestone.id, $~[:project], $~[:namespace], $~
else
- match
+ escape_html_entities(match)
end
end
end
diff --git a/lib/banzai/filter/output_safety.rb b/lib/banzai/filter/output_safety.rb
new file mode 100644
index 00000000000..d4ebce5d9c9
--- /dev/null
+++ b/lib/banzai/filter/output_safety.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ module OutputSafety
+ def escape_once(html)
+ html.html_safe? ? html : ERB::Util.html_escape_once(html)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 42f9b3a689c..b3ce9200b49 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -12,6 +12,7 @@ module Banzai
# :only_path - Generate path-only links.
class ReferenceFilter < HTML::Pipeline::Filter
include RequestStoreReferenceCache
+ include OutputSafety
class << self
attr_accessor :reference_type
@@ -43,10 +44,6 @@ module Banzai
end.join(' ')
end
- def escape_once(html)
- html.html_safe? ? html : ERB::Util.html_escape_once(html)
- end
-
def ignore_ancestor_query
@ignore_ancestor_query ||= begin
parents = %w(pre code a style)
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 2745905c5ff..199b3533cf4 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -150,7 +150,7 @@ module Banzai
end
def uri_type(path)
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/58011
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58657
Gitlab::GitalyClient.allow_n_plus_1_calls do
@uri_types[path] ||= current_commit.uri_type(path)
end
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index 50bf823929c..ee7f10ebdf6 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -33,7 +33,7 @@ module Banzai
(?<new_link>.+?)
(?<title>\ ".+?")?
\)
- }x
+ }x.freeze
# Text matching LINK_OR_IMAGE_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
index 9950db373d8..3d40abfc540 100644
--- a/lib/banzai/filter/suggestion_filter.rb
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -20,6 +20,12 @@ module Banzai
def suggestions_filter_enabled?
context[:suggestions_filter_enabled]
end
+
+ private
+
+ def project
+ context[:project]
+ end
end
end
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 9ffde52b5f2..fe56f9a1e33 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -8,6 +8,11 @@ module Banzai
# HTML Filter to highlight fenced code blocks
#
class SyntaxHighlightFilter < HTML::Pipeline::Filter
+ include OutputSafety
+
+ PARAMS_DELIMITER = ':'.freeze
+ LANG_PARAMS_ATTR = 'data-lang-params'.freeze
+
def call
doc.search('pre > code').each do |node|
highlight_node(node)
@@ -18,7 +23,7 @@ module Banzai
def highlight_node(node)
css_classes = +'code highlight js-syntax-highlight'
- lang = node.attr('lang')
+ lang, lang_params = parse_lang_params(node.attr('lang'))
retried = false
if use_rouge?(lang)
@@ -46,7 +51,10 @@ module Banzai
retry
end
- highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
+ highlighted = %(<pre class="#{css_classes}"
+ lang="#{language}"
+ #{lang_params}
+ v-pre="true"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
@@ -54,6 +62,15 @@ module Banzai
private
+ def parse_lang_params(language)
+ return unless language
+
+ lang, params = language.split(PARAMS_DELIMITER, 2)
+ formatted_params = %(#{LANG_PARAMS_ATTR}="#{escape_once(params)}") if params
+
+ [lang, formatted_params]
+ end
+
# Separate method so it can be instrumented.
def lex(lexer, code)
lexer.lex(code)
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index f2ae17b44fa..ade4d260be1 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -17,7 +17,7 @@ module Banzai
# :toc - String containing Table of Contents data as a `ul` element with
# `li` child elements.
class TableOfContentsFilter < HTML::Pipeline::Filter
- PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u
+ PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
def call
return doc if context[:no_header_anchors]
@@ -31,6 +31,7 @@ module Banzai
if header_content = node.children.first
id = node
.text
+ .strip
.downcase
.gsub(PUNCTUATION_REGEXP, '') # remove punctuation
.tr(' ', '-') # replace spaces with dash
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index f4cc8beeb52..77b5053f38c 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -4,6 +4,8 @@ module Banzai
module Filter
class WikiLinkFilter < HTML::Pipeline::Filter
class Rewriter
+ UNSAFE_SLUG_REGEXES = [/\Ajavascript:/i].freeze
+
def initialize(link_string, wiki:, slug:)
@uri = Addressable::URI.parse(link_string)
@wiki_base_path = wiki && wiki.wiki_base_path
@@ -35,6 +37,8 @@ module Banzai
# Of the form `./link`, `../link`, or similar
def apply_hierarchical_link_rules!
+ return if slug_considered_unsafe?
+
@uri = Addressable::URI.join(@slug, @uri) if @uri.to_s[0] == '.'
end
@@ -54,6 +58,10 @@ module Banzai
def repository_upload?
@uri.relative? && @uri.path.starts_with?(Wikis::CreateAttachmentService::ATTACHMENT_PATH)
end
+
+ def slug_considered_unsafe?
+ UNSAFE_SLUG_REGEXES.any? { |r| r.match?(@slug) }
+ end
end
end
end
diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb
index db79a22549c..ceba082cd4f 100644
--- a/lib/banzai/pipeline/markup_pipeline.rb
+++ b/lib/banzai/pipeline/markup_pipeline.rb
@@ -7,7 +7,8 @@ module Banzai
@filters ||= FilterArray[
Filter::SanitizationFilter,
Filter::ExternalLinkFilter,
- Filter::PlantumlFilter
+ Filter::PlantumlFilter,
+ Filter::SyntaxHighlightFilter
]
end
end
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
index 7db5f5e1f7d..c2da7fec7cc 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/redactor.rb
@@ -70,8 +70,11 @@ module Banzai
# Build the raw <a> tag just with a link as href and content if
# it's originally a link pattern. We shouldn't return a plain text href.
original_link =
- if link_reference == 'true' && href = original_content
- %(<a href="#{href}">#{href}</a>)
+ if link_reference == 'true'
+ href = node.attr('href')
+ content = original_content
+
+ %(<a href="#{href}">#{content}</a>)
end
# The reference should be replaced by the original link's content,
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index e8147ac591a..d7bf450465e 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -10,7 +10,7 @@ module Banzai
nodes,
MergeRequest.includes(
:author,
- :assignee,
+ :assignees,
{
# These associations are primarily used for checking permissions.
# Eager loading these ensures we don't end up running dozens of
diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb
deleted file mode 100644
index 09f36635020..00000000000
--- a/lib/banzai/suggestions_parser.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Banzai
- module SuggestionsParser
- # Returns the content of each suggestion code block.
- #
- def self.parse(text)
- html = Banzai.render(text, project: nil, no_original_data: true)
- doc = Nokogiri::HTML(html)
-
- doc.search('pre.suggestion').map { |node| node.text }
- end
- end
-end
diff --git a/lib/bitbucket_server/collection.rb b/lib/bitbucket_server/collection.rb
index 7e4b2277bbe..f549acbd87f 100644
--- a/lib/bitbucket_server/collection.rb
+++ b/lib/bitbucket_server/collection.rb
@@ -25,13 +25,13 @@ module BitbucketServer
end
def prev_page
- return nil unless current_page > 1
+ return unless current_page > 1
current_page - 1
end
def next_page
- return nil unless has_next_page?
+ return unless has_next_page?
current_page + 1
end
diff --git a/lib/bitbucket_server/representation/repo.rb b/lib/bitbucket_server/representation/repo.rb
index 6c494b79166..dab7f8f22a1 100644
--- a/lib/bitbucket_server/representation/repo.rb
+++ b/lib/bitbucket_server/representation/repo.rb
@@ -20,7 +20,7 @@ module BitbucketServer
end
def browse_url
- # The JSON reponse contains an array of 1 element. Not sure if there
+ # The JSON response contains an array of 1 element. Not sure if there
# are cases where multiple links would be provided.
raw.dig('links', 'self').first.fetch('href')
end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index eadfbf7bc01..d41490d2ebd 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -2,12 +2,13 @@
module Constraints
class ProjectUrlConstrainer
- def matches?(request)
+ def matches?(request, existence_check: true)
namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id]
full_path = [namespace_path, project_path].join('/')
return false unless ProjectPathValidator.valid_path?(full_path)
+ return true unless existence_check
# 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
diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb
index 239780d8626..9b7d1548056 100644
--- a/lib/declarative_policy/preferred_scope.rb
+++ b/lib/declarative_policy/preferred_scope.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module DeclarativePolicy
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb
index f38f4f0a50f..964d35cde9e 100644
--- a/lib/declarative_policy/rule.rb
+++ b/lib/declarative_policy/rule.rb
@@ -84,7 +84,7 @@ module DeclarativePolicy
# returns nil unless it's already cached
def cached_pass?(context)
condition = context.condition(@name)
- return nil unless condition.cached?
+ return unless condition.cached?
condition.pass?
end
@@ -124,7 +124,7 @@ module DeclarativePolicy
def cached_pass?(context)
condition = delegated_context(context).condition(@name)
- return nil unless condition.cached?
+ return unless condition.cached?
condition.pass?
rescue MissingDelegate
@@ -161,7 +161,7 @@ module DeclarativePolicy
def cached_pass?(context)
runner = context.runner(@ability)
- return nil unless runner.cached?
+ return unless runner.cached?
runner.pass?
end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 24fdcd6fbb1..85bf9c14f26 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -33,7 +33,7 @@ class EventFilter
when TEAM
events.where(action: [Event::JOINED, Event::LEFT, Event::EXPIRED])
when ISSUE
- events.where(action: [Event::CREATED, Event::UPDATED, Event::CLOSED, Event::REOPENED])
+ events.where(action: [Event::CREATED, Event::UPDATED, Event::CLOSED, Event::REOPENED], target_type: 'Issue')
else
events
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index b2c8d46ede1..44a9c7ea536 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -113,6 +113,9 @@ module ExtractsPath
@id = get_id
@ref, @path = extract_ref(@id)
@repo = @project.repository
+ @ref.strip!
+
+ raise InvalidPathError if @ref.match?(/\s/)
@commit = @repo.commit(@ref)
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index e073450283b..ccaf06c5d6a 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -24,7 +24,7 @@ module Gitlab
if File.exist?(root.join("REVISION"))
File.read(root.join("REVISION")).strip.freeze
else
- result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h -n 1])
+ result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1])
if result.status.success?
result.stdout.chomp.freeze
@@ -36,10 +36,11 @@ module Gitlab
end
COM_URL = 'https://gitlab.com'.freeze
- APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
- SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
+ APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}.freeze
+ SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze
VERSION = File.read(root.join("VERSION")).strip.freeze
INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze
+ HTTP_PROXY_ENV_VARS = %w(http_proxy https_proxy HTTP_PROXY HTTPS_PROXY).freeze
def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
@@ -58,6 +59,18 @@ module Gitlab
Rails.env.development? || org? || com?
end
+ def self.ee?
+ if ENV['IS_GITLAB_EE'].present?
+ Gitlab::Utils.to_boolean(ENV['IS_GITLAB_EE'])
+ else
+ Object.const_defined?(:License)
+ end
+ end
+
+ def self.http_proxy_env?
+ HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] }
+ end
+
def self.process_name
return 'sidekiq' if Sidekiq.server?
return 'console' if defined?(Rails::Console)
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index ec090aea784..6eb08f674c2 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -24,6 +24,11 @@ module Gitlab
PROTECTION_FULL = 2
PROTECTION_DEV_CAN_MERGE = 3
+ # Default project creation level
+ NO_ONE_PROJECT_ACCESS = 0
+ MAINTAINER_PROJECT_ACCESS = 1
+ DEVELOPER_MAINTAINER_PROJECT_ACCESS = 2
+
class << self
delegate :values, to: :options
@@ -46,6 +51,12 @@ module Gitlab
)
end
+ def options_with_none
+ options_with_owner.merge(
+ "None" => NO_ACCESS
+ )
+ end
+
def sym_options
{
guest: GUEST,
@@ -75,12 +86,36 @@ module Gitlab
def human_access(access)
options_with_owner.key(access)
end
+
+ def human_access_with_none(access)
+ options_with_none.key(access)
+ end
+
+ def project_creation_options
+ {
+ s_('ProjectCreationLevel|No one') => NO_ONE_PROJECT_ACCESS,
+ s_('ProjectCreationLevel|Maintainers') => MAINTAINER_PROJECT_ACCESS,
+ s_('ProjectCreationLevel|Developers + Maintainers') => DEVELOPER_MAINTAINER_PROJECT_ACCESS
+ }
+ end
+
+ def project_creation_values
+ project_creation_options.values
+ end
+
+ def project_creation_level_name(name)
+ project_creation_options.key(name)
+ end
end
def human_access
Gitlab::Access.human_access(access_field)
end
+ def human_access_with_none
+ Gitlab::Access.human_access_with_none(access_field)
+ end
+
def owner?
access_field == OWNER
end
diff --git a/lib/gitlab/action_view_output/context.rb b/lib/gitlab/action_view_output/context.rb
new file mode 100644
index 00000000000..9fbc9811636
--- /dev/null
+++ b/lib/gitlab/action_view_output/context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# This file was simplified from https://raw.githubusercontent.com/rails/rails/195f39804a7a4a0034f25e8704220e03d95a752a/actionview/lib/action_view/context.rb.
+#
+# It is only needed by modules that need to call ActionView helper
+# methods (e.g. those in
+# https://github.com/rails/rails/tree/c4d3e202e10ae627b3b9c34498afb45450652421/actionview/lib/action_view/helpers)
+# to generate tags outside of a Rails controller (e.g. API, Sidekiq,
+# etc.).
+#
+# In Rails 5, ActionView::Context automatically includes CompiledTemplates.
+# This means that any module that includes ActionView::Context is now a descendant
+# of CompiledTemplates.
+#
+# When a partial is rendered for the first time, it runs
+# Module#module_eval, which will evaluate a string source that defines a
+# new method. For example:
+#
+# def _app_views_profiles_show_html_haml___1285955918103175884_70307801785400(local_assigns, output_buffer)
+# "hello world"
+# end
+#
+# When a new method is defined, the Ruby interpreter clears the method
+# cache for all descendants, and all methods for those modules will have
+# to be redefined. This can lead to a significant performance penalty.
+#
+# Rails 6 fixes this behavior by moving out the `include
+# CompiledTemplates` into ActionView::Base so that including `ActionView::Context`
+# doesn't quietly affect other modules in this way.
+
+if Rails::VERSION::STRING.start_with?('6')
+ raise 'This module is no longer needed in Rails 6. Use ActionView::Context instead.'
+end
+
+module Gitlab
+ module ActionViewOutput
+ module Context
+ attr_accessor :output_buffer, :view_flow
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index b2ef04d23d7..4317992d933 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -4,10 +4,17 @@ module Gitlab
module Auth
MissingPersonalAccessTokenError = Class.new(StandardError)
+ # Scopes used for GitLab API access
+ API_SCOPES = [:api, :read_user].freeze
+
+ # Scopes used for GitLab Repository access
+ REPOSITORY_SCOPES = [:read_repository, :write_repository].freeze
+
+ # Scopes used for GitLab Docker Registry access
REGISTRY_SCOPES = [:read_registry].freeze
- # Scopes used for GitLab API access
- API_SCOPES = [:api, :read_user, :sudo, :read_repository].freeze
+ # Scopes used for GitLab as admin
+ ADMIN_SCOPES = [:sudo].freeze
# Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze
@@ -159,7 +166,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password)
- if token && valid_scoped_token?(token, available_scopes)
+ if token && valid_scoped_token?(token, all_available_scopes)
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end
end
@@ -176,7 +183,8 @@ module Gitlab
abilities_by_scope = {
api: full_authentication_abilities,
read_registry: [:read_container_image],
- read_repository: [:download_code]
+ read_repository: [:download_code],
+ write_repository: [:download_code, :push_code]
}
scopes.flat_map do |scope|
@@ -196,7 +204,7 @@ module Gitlab
scopes = abilities_for_scopes(token.scopes)
- if valid_scoped_token?(token, available_scopes)
+ if valid_scoped_token?(token, all_available_scopes)
Gitlab::Auth::Result.new(token, token.project, :deploy_token, scopes)
end
end
@@ -222,7 +230,7 @@ module Gitlab
elsif token_handler.deploy_key_pushable?(project)
read_write_authentication_abilities
else
- read_authentication_abilities
+ read_only_authentication_abilities
end
if token_handler.token_valid?(encoded_token)
@@ -258,7 +266,7 @@ module Gitlab
]
end
- def read_authentication_abilities
+ def read_only_authentication_abilities
[
:read_project,
:download_code,
@@ -267,7 +275,7 @@ module Gitlab
end
def read_write_authentication_abilities
- read_authentication_abilities + [
+ read_only_authentication_abilities + [
:push_code,
:create_container_image
]
@@ -279,15 +287,19 @@ module Gitlab
]
end
- def available_scopes(current_user = nil)
- scopes = API_SCOPES + registry_scopes
- scopes.delete(:sudo) if current_user && !current_user.admin?
+ def available_scopes_for(current_user)
+ scopes = non_admin_available_scopes
+ scopes += ADMIN_SCOPES if current_user.admin?
scopes
end
+ def all_available_scopes
+ non_admin_available_scopes + ADMIN_SCOPES
+ end
+
# Other available scopes
def optional_scopes
- available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES
+ all_available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES
end
def registry_scopes
@@ -298,6 +310,10 @@ module Gitlab
private
+ def non_admin_available_scopes
+ API_SCOPES + REPOSITORY_SCOPES + registry_scopes
+ end
+
def find_build_by_token(token)
::Ci::Build.running.find_by_token(token)
end
diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb
index 7ceb96f502b..47d63eb53cf 100644
--- a/lib/gitlab/auth/ldap/config.rb
+++ b/lib/gitlab/auth/ldap/config.rb
@@ -75,7 +75,8 @@ module Gitlab
encryption: options['encryption'],
filter: omniauth_user_filter,
name_proc: name_proc,
- disable_verify_certificates: !options['verify_certificates']
+ disable_verify_certificates: !options['verify_certificates'],
+ tls_options: tls_options
)
if has_auth?
@@ -85,9 +86,6 @@ module Gitlab
)
end
- opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
- opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
-
opts
end
@@ -196,24 +194,28 @@ module Gitlab
end
def encryption_options
- method = translate_method(options['encryption'])
- return nil unless method
+ method = translate_method
+ return unless method
{
method: method,
- tls_options: tls_options(method)
+ tls_options: tls_options
}
end
- def translate_method(method_from_config)
- NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
+ def translate_method
+ NET_LDAP_ENCRYPTION_METHOD[options['encryption']&.to_sym]
end
- def tls_options(method)
- return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
+ def tls_options
+ return @tls_options if defined?(@tls_options)
+
+ method = translate_method
+ return unless method
- opts = if options['verify_certificates']
- OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
+ opts = if options['verify_certificates'] && method != 'plain'
+ # Dup so we don't accidentally overwrite the constant
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.dup
else
# It is important to explicitly set verify_mode for two reasons:
# 1. The behavior of OpenSSL is undefined when verify_mode is not set.
@@ -222,10 +224,35 @@ module Gitlab
{ verify_mode: OpenSSL::SSL::VERIFY_NONE }
end
- opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
- opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+ opts.merge!(custom_tls_options)
- opts
+ @tls_options = opts
+ end
+
+ def custom_tls_options
+ return {} unless options['tls_options']
+
+ # Dup so we don't overwrite the original value
+ custom_options = options['tls_options'].dup.delete_if { |_, value| value.nil? || value.blank? }
+ custom_options.symbolize_keys!
+
+ if custom_options[:cert]
+ begin
+ custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert])
+ rescue OpenSSL::X509::CertificateError => e
+ Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}"
+ end
+ end
+
+ if custom_options[:key]
+ begin
+ custom_options[:key] = OpenSSL::PKey.read(custom_options[:key])
+ rescue OpenSSL::PKey::PKeyError => e
+ Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}"
+ end
+ end
+
+ custom_options
end
def auth_options
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index 48d134f91b0..c1517222956 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -69,7 +69,7 @@ module Gitlab
end
def name
- attribute_value(:name).first
+ attribute_value(:name)&.first
end
def uid
@@ -112,7 +112,7 @@ module Gitlab
attributes = Array(config.attributes[attribute.to_s])
selected_attr = attributes.find { |attr| entry.respond_to?(attr) }
- return nil unless selected_attr
+ return unless selected_attr
entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index 36fc8061d92..72a187377d0 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -55,7 +55,7 @@ module Gitlab
private
def info
- auth_hash.info
+ auth_hash['info']
end
def get_info(key)
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index f38c5d57c44..09d1d79fefc 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -146,7 +146,6 @@ module Gitlab
Gitlab::Auth::LDAP::Person.find_by_uid(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_email(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
-
rescue Gitlab::Auth::LDAP::LDAPConnectionError
nil
end
@@ -200,22 +199,19 @@ module Gitlab
# Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user?
username = ldap_person.username.presence
+ name = ldap_person.name.presence
email = ldap_person.email.first.presence
end
username ||= auth_hash.username
+ name ||= auth_hash.name
email ||= auth_hash.email
valid_username = ::Namespace.clean_path(username)
-
- uniquify = Uniquify.new
- valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
-
- name = auth_hash.name
- name = valid_username if name.strip.empty?
+ valid_username = Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
{
- name: name,
+ name: name.strip.presence || valid_username,
username: valid_username,
email: email,
password: auth_hash.password,
@@ -248,8 +244,9 @@ module Gitlab
metadata.provider = auth_hash.provider
end
- if creating_linked_ldap_user? && gl_user.email == ldap_person.email.first
- metadata.set_attribute_synced(:email, true)
+ if creating_linked_ldap_user?
+ metadata.set_attribute_synced(:name, true) if gl_user.name == ldap_person.name
+ metadata.set_attribute_synced(:email, true) if gl_user.email == ldap_person.email.first
metadata.provider = ldap_person.provider
end
end
diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb
index 253445570f2..c620fc5d6bd 100644
--- a/lib/gitlab/auth/omniauth_identity_linker_base.rb
+++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def link
- save if identity.new_record?
+ save if unlinked?
end
def changed?
@@ -35,6 +35,10 @@ module Gitlab
@changed = identity.save
end
+ def unlinked?
+ identity.new_record?
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def identity
@identity ||= current_user.identities
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
index 78fa25c5516..4ebf2afb9cb 100644
--- a/lib/gitlab/auth/result.rb
+++ b/lib/gitlab/auth/result.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab
diff --git a/lib/gitlab/auth/saml/auth_hash.rb b/lib/gitlab/auth/saml/auth_hash.rb
index 1af9fa40c3a..b0df9757bbd 100644
--- a/lib/gitlab/auth/saml/auth_hash.rb
+++ b/lib/gitlab/auth/saml/auth_hash.rb
@@ -10,11 +10,11 @@ module Gitlab
def authn_context
response_object = auth_hash.extra[:response_object]
- return nil if response_object.blank?
+ return if response_object.blank?
document = response_object.decrypted_document
document ||= response_object.document
- return nil if document.blank?
+ return if document.blank?
extract_authn_context(document)
end
diff --git a/lib/gitlab/auth_logger.rb b/lib/gitlab/auth_logger.rb
new file mode 100644
index 00000000000..6d3edba02b0
--- /dev/null
+++ b/lib/gitlab/auth_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AuthLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'auth'
+ end
+ end
+end
diff --git a/lib/gitlab/authorized_keys.rb b/lib/gitlab/authorized_keys.rb
new file mode 100644
index 00000000000..3fe72f5fd43
--- /dev/null
+++ b/lib/gitlab/authorized_keys.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AuthorizedKeys
+ KeyError = Class.new(StandardError)
+
+ attr_reader :logger
+
+ # Initializes the class
+ #
+ # @param [Gitlab::Logger] logger
+ def initialize(logger = Gitlab::AppLogger)
+ @logger = logger
+ end
+
+ # Add id and its key to the authorized_keys file
+ #
+ # @param [String] id identifier of key prefixed by `key-`
+ # @param [String] key public key to be added
+ # @return [Boolean]
+ def add_key(id, key)
+ lock do
+ public_key = strip(key)
+ logger.info("Adding key (#{id}): #{public_key}")
+ open_authorized_keys_file('a') { |file| file.puts(key_line(id, public_key)) }
+ end
+
+ true
+ end
+
+ # Atomically add all the keys to the authorized_keys file
+ #
+ # @param [Array<::Key>] keys list of Key objects to be added
+ # @return [Boolean]
+ def batch_add_keys(keys)
+ lock(300) do # Allow 300 seconds (5 minutes) for batch_add_keys
+ open_authorized_keys_file('a') do |file|
+ keys.each do |key|
+ public_key = strip(key.key)
+ logger.info("Adding key (#{key.shell_id}): #{public_key}")
+ file.puts(key_line(key.shell_id, public_key))
+ end
+ end
+ end
+
+ true
+ rescue Gitlab::AuthorizedKeys::KeyError
+ false
+ end
+
+ # Remove key by ID from the authorized_keys file
+ #
+ # @param [String] id identifier of the key to be removed prefixed by `key-`
+ # @return [Boolean]
+ def rm_key(id)
+ lock do
+ logger.info("Removing key (#{id})")
+ open_authorized_keys_file('r+') do |f|
+ while line = f.gets
+ next unless line.start_with?("command=\"#{command(id)}\"")
+
+ f.seek(-line.length, IO::SEEK_CUR)
+ # Overwrite the line with #'s. Because the 'line' variable contains
+ # a terminating '\n', we write line.length - 1 '#' characters.
+ f.write('#' * (line.length - 1))
+ end
+ end
+ end
+
+ true
+ rescue Errno::ENOENT
+ false
+ end
+
+ # Clear the authorized_keys file
+ #
+ # @return [Boolean]
+ def clear
+ open_authorized_keys_file('w') { |file| file.puts '# Managed by gitlab-rails' }
+
+ true
+ end
+
+ # Read the authorized_keys file and return IDs of each key
+ #
+ # @return [Array<Integer>]
+ def list_key_ids
+ logger.info('Listing all key IDs')
+
+ [].tap do |a|
+ open_authorized_keys_file('r') do |f|
+ f.each_line do |line|
+ key_id = line.match(/key-(\d+)/)
+
+ next unless key_id
+
+ a << key_id[1].chomp.to_i
+ end
+ end
+ end
+ rescue Errno::ENOENT
+ []
+ end
+
+ private
+
+ def lock(timeout = 10)
+ File.open("#{authorized_keys_file}.lock", "w+") do |f|
+ f.flock File::LOCK_EX
+ Timeout.timeout(timeout) { yield }
+ ensure
+ f.flock File::LOCK_UN
+ end
+ end
+
+ def open_authorized_keys_file(mode)
+ File.open(authorized_keys_file, mode, 0o600) do |file|
+ file.chmod(0o600)
+ yield file
+ end
+ end
+
+ def key_line(id, key)
+ key = key.chomp
+
+ if key.include?("\n") || key.include?("\t")
+ raise KeyError, "Invalid public_key: #{key.inspect}"
+ end
+
+ %Q(command="#{command(id)}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{strip(key)})
+ end
+
+ def command(id)
+ unless /\A[a-z0-9-]+\z/ =~ id
+ raise KeyError, "Invalid ID: #{id.inspect}"
+ end
+
+ "#{File.join(Gitlab.config.gitlab_shell.path, 'bin', 'gitlab-shell')} #{id}"
+ end
+
+ def strip(key)
+ key.split(/[ ]+/)[0, 2].join(' ')
+ end
+
+ def authorized_keys_file
+ Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 5251e0fadf9..2e3a4f3b869 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -58,11 +58,31 @@ module Gitlab
migration_class_for(class_name).new.perform(*arguments)
end
- def self.exists?(migration_class)
+ def self.exists?(migration_class, additional_queues = [])
enqueued = Sidekiq::Queue.new(self.queue)
scheduled = Sidekiq::ScheduledSet.new
- [enqueued, scheduled].each do |queue|
+ enqueued_job?([enqueued, scheduled], migration_class)
+ end
+
+ def self.dead_jobs?(migration_class)
+ dead_set = Sidekiq::DeadSet.new
+
+ enqueued_job?([dead_set], migration_class)
+ end
+
+ def self.retrying_jobs?(migration_class)
+ retry_set = Sidekiq::RetrySet.new
+
+ enqueued_job?([retry_set], migration_class)
+ end
+
+ def self.migration_class_for(class_name)
+ const_get(class_name)
+ end
+
+ def self.enqueued_job?(queues, migration_class)
+ queues.each do |queue|
queue.each do |job|
return true if job.queue == self.queue && job.args.first == migration_class
end
@@ -70,9 +90,5 @@ module Gitlab
false
end
-
- def self.migration_class_for(class_name)
- const_get(class_name)
- end
end
end
diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb
index 92096e29ef1..7ee783b8489 100644
--- a/lib/gitlab/background_migration/archive_legacy_traces.rb
+++ b/lib/gitlab/background_migration/archive_legacy_traces.rb
@@ -11,11 +11,10 @@ module Gitlab
# So we chose a way to use ::Ci::Build directly and we don't change the `archive!` method until 11.1
::Ci::Build.finished.without_archived_trace
.where(id: start_id..stop_id).find_each do |build|
- begin
- build.trace.archive!
- rescue => e
- Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
- end
+
+ build.trace.archive!
+ rescue => e
+ Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
end
end
end
diff --git a/lib/gitlab/background_migration/calculate_wiki_sizes.rb b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
new file mode 100644
index 00000000000..886c41a2b9d
--- /dev/null
+++ b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class CalculateWikiSizes
+ def perform(start_id, stop_id)
+ ::ProjectStatistics.where(wiki_size: nil)
+ .where(id: start_id..stop_id)
+ .includes(project: [:route, :group, namespace: [:owner]]).find_each do |statistics|
+ statistics.refresh!(only: [:wiki_size])
+ rescue => e
+ Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/encrypt_columns.rb b/lib/gitlab/background_migration/encrypt_columns.rb
index b9ad8267e37..173543b7c25 100644
--- a/lib/gitlab/background_migration/encrypt_columns.rb
+++ b/lib/gitlab/background_migration/encrypt_columns.rb
@@ -91,7 +91,8 @@ module Gitlab
# No need to do anything if the plaintext is nil, or an encrypted
# value already exists
- return nil unless plaintext.present? && !ciphertext.present?
+ return unless plaintext.present?
+ return if ciphertext.present?
# attr_encrypted will calculate and set the expected value for us
instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
new file mode 100644
index 00000000000..0e93b2cb2fa
--- /dev/null
+++ b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # save validity time pages domain
+ class FillValidTimeForPagesDomainCertificate
+ # define PagesDomain with only needed code
+ class PagesDomain < ActiveRecord::Base
+ self.table_name = 'pages_domains'
+
+ def x509
+ return unless certificate.present?
+
+ @x509 ||= OpenSSL::X509::Certificate.new(certificate)
+ rescue OpenSSL::X509::CertificateError
+ nil
+ end
+ end
+
+ def perform(start_id, stop_id)
+ PagesDomain.where(id: start_id..stop_id).find_each do |domain|
+ if Gitlab::Database.mysql?
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before,
+ certificate_valid_not_after: domain.x509&.not_after
+ )
+ else
+ # for some reason activerecord doesn't append timezone, iso8601 forces this
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before&.iso8601,
+ certificate_valid_not_after: domain.x509&.not_after&.iso8601
+ )
+ end
+ rescue => e
+ Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fix_cross_project_label_links.rb b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
index 0a12401c35f..bf5d7f5f322 100644
--- a/lib/gitlab/background_migration/fix_cross_project_label_links.rb
+++ b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
@@ -95,7 +95,7 @@ module Gitlab
local_labels = available_labels(project_id)
# get all label links for the given resource (issue/MR)
- # which reference a label not included in avaiable_labels
+ # which reference a label not included in available_labels
# (other than its project labels and labels of ancestor groups)
cross_labels = LabelLink
.select('label_id, labels.title as title, labels.color as color, label_links.id as label_link_id')
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
index 38fecac1bfe..42fcaa87e66 100644
--- a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
+++ b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
@@ -24,7 +24,7 @@ module Gitlab
def commit_title
commit = commits.last
- return nil unless commit && commit[:message]
+ return unless commit && commit[:message]
index = commit[:message].index("\n")
message = index ? commit[:message][0..index] : commit[:message]
diff --git a/lib/gitlab/background_migration/migrate_legacy_uploads.rb b/lib/gitlab/background_migration/migrate_legacy_uploads.rb
new file mode 100644
index 00000000000..af1ad930aed
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_legacy_uploads.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This migration takes all legacy uploads (that were uploaded using AttachmentUploader)
+ # and migrate them to the new (FileUploader) location (=under projects).
+ #
+ # We have dependencies (uploaders) in this migration because extracting code would add a lot of complexity
+ # and possible errors could appear as the logic in the uploaders is not trivial.
+ #
+ # This migration will be removed in 12.4 in order to get rid of a migration that depends on
+ # the application code.
+ class MigrateLegacyUploads
+ include Database::MigrationHelpers
+ include ::Gitlab::Utils::StrongMemoize
+
+ # This class takes a legacy upload and migrates it to the correct location
+ class UploadMover
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :upload, :project, :note
+
+ def initialize(upload)
+ @upload = upload
+ @note = Note.find_by(id: upload.model_id)
+ @project = note&.project
+ end
+
+ def execute
+ return unless upload
+
+ if !project
+ # if we don't have models associated with the upload we can not move it
+ say "MigrateLegacyUploads: Deleting upload due to model not found: #{upload.inspect}"
+ destroy_legacy_upload
+ elsif note.is_a?(LegacyDiffNote)
+ handle_legacy_note_upload
+ elsif !legacy_file_exists?
+ # if we can not find the file we just remove the upload record
+ say "MigrateLegacyUploads: Deleting upload due to file not found: #{upload.inspect}"
+ destroy_legacy_upload
+ else
+ migrate_upload
+ end
+ end
+
+ private
+
+ def migrate_upload
+ return unless copy_upload_to_project
+
+ add_upload_link_to_note_text
+ destroy_legacy_file
+ destroy_legacy_upload
+ end
+
+ # we should proceed and log whenever one upload copy fails, no matter the reasons
+ # rubocop: disable Lint/RescueException
+ def copy_upload_to_project
+ @uploader = FileUploader.copy_to(legacy_file_uploader, project)
+
+ say "MigrateLegacyUploads: Copied file #{legacy_file_uploader.file.path} -> #{@uploader.file.path}"
+ true
+ rescue Exception => e
+ say "MigrateLegacyUploads: File #{legacy_file_uploader.file.path} couldn't be copied to project uploads. Error: #{e.message}"
+ false
+ end
+ # rubocop: enable Lint/RescueException
+
+ def destroy_legacy_upload
+ note.remove_attachment = true
+ note.save
+
+ if upload.destroy
+ say "MigrateLegacyUploads: Upload #{upload.inspect} was destroyed."
+ else
+ say "MigrateLegacyUploads: Upload #{upload.inspect} destroy failed."
+ end
+ end
+
+ def destroy_legacy_file
+ legacy_file_uploader.file.delete
+ end
+
+ def add_upload_link_to_note_text
+ new_text = "#{note.note} \n #{@uploader.markdown_link}"
+ note.update!(
+ note: new_text
+ )
+ end
+
+ def legacy_file_uploader
+ strong_memoize(:legacy_file_uploader) do
+ uploader = upload.build_uploader
+ uploader.retrieve_from_store!(File.basename(upload.path))
+ uploader
+ end
+ end
+
+ def legacy_file_exists?
+ legacy_file_uploader.file.exists?
+ end
+
+ def handle_legacy_note_upload
+ note.note += "\n \n Attachment ##{upload.id} with URL \"#{note.attachment.url}\" failed to migrate \
+ for model class #{note.class}. See #{help_doc_link}."
+ note.save
+
+ say "MigrateLegacyUploads: LegacyDiffNote ##{note.id} found, can't move the file: #{upload.inspect} for upload ##{upload.id}. See #{help_doc_link}."
+ end
+
+ def say(message)
+ Rails.logger.info(message)
+ end
+
+ def help_doc_link
+ 'https://docs.gitlab.com/ee/administration/troubleshooting/migrations.html#legacy-upload-migration'
+ end
+ end
+
+ def perform(start_id, end_id)
+ Upload.where(id: start_id..end_id, uploader: 'AttachmentUploader').find_each do |upload|
+ UploadMover.new(upload).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/migrate_stage_index.rb b/lib/gitlab/background_migration/migrate_stage_index.rb
index f90f35a913d..f921233460d 100644
--- a/lib/gitlab/background_migration/migrate_stage_index.rb
+++ b/lib/gitlab/background_migration/migrate_stage_index.rb
@@ -21,8 +21,8 @@ module Gitlab
AND stage_idx IS NOT NULL
GROUP BY stage_id, stage_idx
), indexes AS (
- SELECT DISTINCT stage_id, last_value(stage_idx)
- OVER (PARTITION BY stage_id ORDER BY freq ASC) AS index
+ SELECT DISTINCT stage_id, first_value(stage_idx)
+ OVER (PARTITION BY stage_id ORDER BY freq DESC) AS index
FROM freqs
)
diff --git a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
index 698f5e46c0c..48aa369705f 100644
--- a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
+++ b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
@@ -302,14 +302,12 @@ module Gitlab
ldap_identities = Identity.where("provider like 'ldap%'").where(id: start_id..end_id)
ldap_identities.each do |identity|
- begin
- identity.extern_uid = Gitlab::Auth::LDAP::DN.new(identity.extern_uid).to_normalized_s
- unless identity.save
- Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping."
- end
- rescue Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping."
+ identity.extern_uid = Gitlab::Auth::LDAP::DN.new(identity.extern_uid).to_normalized_s
+ unless identity.save
+ Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping."
end
+ rescue Gitlab::Auth::LDAP::DN::FormatError => e
+ Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping."
end
end
diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..a4c6540c61b
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This background migration creates records on merge_request_assignees according
+ # to the given merge request IDs range. A _single_ INSERT is issued for the given range.
+ # This is required for supporting multiple assignees on merge requests.
+ class PopulateMergeRequestAssigneesTable
+ def perform(from_id, to_id)
+ select_sql =
+ MergeRequest
+ .where(merge_request_assignees_not_exists_clause)
+ .where(id: from_id..to_id)
+ .where('assignee_id IS NOT NULL')
+ .select(:id, :assignee_id)
+ .to_sql
+
+ execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}")
+ end
+
+ private
+
+ def merge_request_assignees_not_exists_clause
+ <<~SQL
+ NOT EXISTS (SELECT 1 FROM merge_request_assignees
+ WHERE merge_request_assignees.merge_request_id = merge_requests.id)
+ SQL
+ end
+
+ def execute(sql)
+ @connection ||= ActiveRecord::Base.connection
+ @connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index a19dc9747fb..755b5ee725a 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -34,18 +34,16 @@ module Gitlab
def filter_error_files(files)
files.partition do |file|
- begin
- file.to_h
- true
- rescue => e
- msg = <<~MSG
+ file.to_h
+ true
+ rescue => e
+ msg = <<~MSG
Error parsing path "#{file.path}":
#{e.message}
#{e.backtrace.join("\n ")}
MSG
- Rails.logger.error(msg)
- false
- end
+ Rails.logger.error(msg)
+ false
end
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb b/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
index 4a9a62aaeb5..1924f2ffee2 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
@@ -8,8 +8,8 @@ module Gitlab
self.table_name = 'untracked_files_for_uploads'
# Ends with /:random_hex/:filename
- FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}
- FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/
+ FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}.freeze
+ FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/.freeze
# These regex patterns are tested against a relative path, relative to
# the upload directory.
@@ -127,7 +127,7 @@ module Gitlab
full_path = matchd[1]
project = Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::Project.find_by_full_path(full_path)
- return nil unless project
+ return unless project
project.id
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 81ca2b0a9b7..1ee44a3a5a9 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -16,7 +16,7 @@ module Gitlab
RELATIVE_UPLOAD_DIR
)
FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze
- START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}
+ START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}.freeze
EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
diff --git a/lib/gitlab/background_migration/reset_merge_status.rb b/lib/gitlab/background_migration/reset_merge_status.rb
new file mode 100644
index 00000000000..447fec8903c
--- /dev/null
+++ b/lib/gitlab/background_migration/reset_merge_status.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Updates the range of given MRs to merge_status "unchecked", if they're opened
+ # and mergeable.
+ class ResetMergeStatus
+ def perform(from_id, to_id)
+ relation = MergeRequest.where(id: from_id..to_id,
+ state: 'opened',
+ merge_status: 'can_be_merged')
+
+ relation.update_all(merge_status: 'unchecked')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/sync_issues_state_id.rb b/lib/gitlab/background_migration/sync_issues_state_id.rb
new file mode 100644
index 00000000000..2a0751928b8
--- /dev/null
+++ b/lib/gitlab/background_migration/sync_issues_state_id.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class SyncIssuesStateId
+ def perform(start_id, end_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ UPDATE issues
+ SET state_id =
+ CASE state
+ WHEN 'opened' THEN 1
+ WHEN 'closed' THEN 2
+ END
+ WHERE state_id IS NULL
+ AND id BETWEEN #{start_id} AND #{end_id}
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/sync_merge_requests_state_id.rb b/lib/gitlab/background_migration/sync_merge_requests_state_id.rb
new file mode 100644
index 00000000000..6707e178d8b
--- /dev/null
+++ b/lib/gitlab/background_migration/sync_merge_requests_state_id.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class SyncMergeRequestsStateId
+ def perform(start_id, end_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ UPDATE merge_requests
+ SET state_id =
+ CASE state
+ WHEN 'opened' THEN 1
+ WHEN 'closed' THEN 2
+ WHEN 'merged' THEN 3
+ WHEN 'locked' THEN 4
+ END
+ WHERE state_id IS NULL
+ AND id BETWEEN #{start_id} AND #{end_id}
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/pipeline/template.rb b/lib/gitlab/badge/pipeline/template.rb
index 64c3dfcd10b..2c5f9654496 100644
--- a/lib/gitlab/badge/pipeline/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -15,6 +15,7 @@ module Gitlab
failed: '#e05d44',
running: '#dfb317',
pending: '#dfb317',
+ preparing: '#dfb317',
canceled: '#9f9f9f',
skipped: '#9f9f9f',
unknown: '#9f9f9f'
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 3cd327f5109..144ba2ec031 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -108,7 +108,7 @@ module Gitlab
end
def find_or_create_groups
- return nil unless group_path.present?
+ return unless group_path.present?
log " * Using namespace: #{group_path}"
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 75a3f17f549..c9f0ed66a54 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -47,7 +47,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find_user_id(username)
- return nil unless username
+ return unless username
return users[username] if users.key?(username)
@@ -79,31 +79,29 @@ module Gitlab
create_labels
client.issues(repo).each do |issue|
- begin
- description = ''
- description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
- description += issue.description
-
- label_name = issue.kind
- milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
-
- gitlab_issue = project.issues.create!(
- iid: issue.iid,
- title: issue.title,
- description: description,
- state: issue.state,
- author_id: gitlab_user_id(project, issue.author),
- milestone: milestone,
- created_at: issue.created_at,
- updated_at: issue.updated_at
- )
-
- gitlab_issue.labels << @labels[label_name]
-
- import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
- rescue StandardError => e
- errors << { type: :issue, iid: issue.iid, errors: e.message }
- end
+ description = ''
+ description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
+ description += issue.description
+
+ label_name = issue.kind
+ milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
+
+ gitlab_issue = project.issues.create!(
+ iid: issue.iid,
+ title: issue.title,
+ description: description,
+ state: issue.state,
+ author_id: gitlab_user_id(project, issue.author),
+ milestone: milestone,
+ created_at: issue.created_at,
+ updated_at: issue.updated_at
+ )
+
+ gitlab_issue.labels << @labels[label_name]
+
+ import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
+ rescue StandardError => e
+ errors << { type: :issue, iid: issue.iid, errors: e.message }
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -137,7 +135,7 @@ module Gitlab
def create_labels
LABELS.each do |label_params|
- label = ::Labels::CreateService.new(label_params).execute(project: project)
+ label = ::Labels::FindOrCreateService.new(nil, project, label_params).execute(skip_authorization: true)
if label.valid?
@labels[label_params[:title]] = label
else
@@ -150,37 +148,35 @@ module Gitlab
pull_requests = client.pull_requests(repo)
pull_requests.each do |pull_request|
- begin
- description = ''
- description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
- description += pull_request.description
-
- source_branch_sha = pull_request.source_branch_sha
- target_branch_sha = pull_request.target_branch_sha
- source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
- target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
-
- merge_request = project.merge_requests.create!(
- iid: pull_request.iid,
- title: pull_request.title,
- description: description,
- source_project: project,
- source_branch: pull_request.source_branch_name,
- source_branch_sha: source_branch_sha,
- target_project: project,
- target_branch: pull_request.target_branch_name,
- target_branch_sha: target_branch_sha,
- state: pull_request.state,
- author_id: gitlab_user_id(project, pull_request.author),
- assignee_id: nil,
- created_at: pull_request.created_at,
- updated_at: pull_request.updated_at
- )
-
- import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
- rescue StandardError => e
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
- end
+ description = ''
+ description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
+ description += pull_request.description
+
+ source_branch_sha = pull_request.source_branch_sha
+ target_branch_sha = pull_request.target_branch_sha
+ source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
+ target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
+
+ merge_request = project.merge_requests.create!(
+ iid: pull_request.iid,
+ title: pull_request.title,
+ description: description,
+ source_project: project,
+ source_branch: pull_request.source_branch_name,
+ source_branch_sha: source_branch_sha,
+ target_project: project,
+ target_branch: pull_request.target_branch_name,
+ target_branch_sha: target_branch_sha,
+ state: pull_request.state,
+ author_id: gitlab_user_id(project, pull_request.author),
+ assignee_id: nil,
+ created_at: pull_request.created_at,
+ updated_at: pull_request.updated_at
+ )
+
+ import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
end
end
@@ -211,23 +207,21 @@ module Gitlab
end
inline_comments.each do |comment|
- begin
- attributes = pull_request_comment_attributes(comment)
- attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
+ attributes = pull_request_comment_attributes(comment)
+ attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
- attributes.merge!(
- position: position_map[comment.iid],
- type: 'DiffNote')
+ attributes.merge!(
+ position: position_map[comment.iid],
+ type: 'DiffNote')
- note = merge_request.notes.create!(attributes)
+ note = merge_request.notes.create!(attributes)
- # We can't store a discussion ID until a note is created, so if
- # replies are created before the parent the discussion ID won't be
- # linked properly.
- discussion_map[comment.iid] = note.discussion_id
- rescue StandardError => e
- errors << { type: :pull_request, iid: comment.iid, errors: e.message }
- end
+ # We can't store a discussion ID until a note is created, so if
+ # replies are created before the parent the discussion ID won't be
+ # linked properly.
+ discussion_map[comment.iid] = note.discussion_id
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: comment.iid, errors: e.message }
end
end
@@ -245,11 +239,9 @@ module Gitlab
def import_standalone_pr_comments(pr_comments, merge_request)
pr_comments.each do |comment|
- begin
- merge_request.notes.create!(pull_request_comment_attributes(comment))
- rescue StandardError => e
- errors << { type: :pull_request, iid: comment.iid, errors: e.message }
- end
+ merge_request.notes.create!(pull_request_comment_attributes(comment))
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: comment.iid, errors: e.message }
end
end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index dbbedd5dcbe..ff2694abd5e 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -65,7 +65,7 @@ module Gitlab
end
def find_user_id(email)
- return nil unless email
+ return unless email
return users[email] if users.key?(email)
@@ -162,27 +162,23 @@ module Gitlab
restore_branches(batch) if recover_missing_commits
batch.each do |pull_request|
- begin
- import_bitbucket_pull_request(pull_request)
- rescue StandardError => e
- backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
- log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
-
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
- end
+ import_bitbucket_pull_request(pull_request)
+ rescue StandardError => e
+ backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
+ log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
+
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
end
end
end
def delete_temp_branches
@temp_branches.each do |branch|
- begin
- client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
- project.repository.delete_branch(branch.name)
- rescue BitbucketServer::Connection::ConnectionError => e
- log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
- @errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
- end
+ client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
+ project.repository.delete_branch(branch.name)
+ rescue BitbucketServer::Connection::ConnectionError => e
+ log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
+ @errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
end
end
@@ -205,6 +201,7 @@ module Gitlab
target_branch: Gitlab::Git.ref_name(pull_request.target_branch_name),
target_branch_sha: pull_request.target_branch_sha,
state: pull_request.state,
+ state_id: MergeRequest.available_states[pull_request.state],
author_id: author_id,
assignee_id: nil,
created_at: pull_request.created_at,
@@ -323,16 +320,14 @@ module Gitlab
def import_standalone_pr_comments(pr_comments, merge_request)
pr_comments.each do |comment|
- begin
- merge_request.notes.create!(pull_request_comment_attributes(comment))
+ merge_request.notes.create!(pull_request_comment_attributes(comment))
- comment.comments.each do |replies|
- merge_request.notes.create!(pull_request_comment_attributes(replies))
- end
- rescue StandardError => e
- log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
- errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
+ comment.comments.each do |replies|
+ merge_request.notes.create!(pull_request_comment_attributes(replies))
end
+ rescue StandardError => e
+ log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
+ errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
end
end
diff --git a/lib/gitlab/checks/branch_check.rb b/lib/gitlab/checks/branch_check.rb
index d06b2df36f2..4ddc1c718c7 100644
--- a/lib/gitlab/checks/branch_check.rb
+++ b/lib/gitlab/checks/branch_check.rb
@@ -9,13 +9,17 @@ module Gitlab
non_master_delete_protected_branch: 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.',
non_web_delete_protected_branch: 'You can only delete protected branches using the web interface.',
merge_protected_branch: 'You are not allowed to merge code into protected branches on this project.',
- push_protected_branch: 'You are not allowed to push code to protected branches on this project.'
+ push_protected_branch: 'You are not allowed to push code to protected branches on this project.',
+ create_protected_branch: 'You are not allowed to create protected branches on this project.',
+ invalid_commit_create_protected_branch: 'You can only use an existing protected branch ref as the basis of a new protected branch.',
+ non_web_create_protected_branch: 'You can only create protected branches using the web interface and API.'
}.freeze
LOG_MESSAGES = {
delete_default_branch_check: "Checking if default branch is being deleted...",
protected_branch_checks: "Checking if you are force pushing to a protected branch...",
protected_branch_push_checks: "Checking if you are allowed to push to the protected branch...",
+ protected_branch_creation_checks: "Checking if you are allowed to create a protected branch...",
protected_branch_deletion_checks: "Checking if you are allowed to delete the protected branch..."
}.freeze
@@ -42,13 +46,35 @@ module Gitlab
end
end
- if deletion?
+ if project.empty_repo?
+ protected_branch_push_checks
+ elsif creation?
+ protected_branch_creation_checks
+ elsif deletion?
protected_branch_deletion_checks
else
protected_branch_push_checks
end
end
+ def protected_branch_creation_checks
+ logger.log_timed(LOG_MESSAGES[:protected_branch_creation_checks]) do
+ break if user_access.can_push_to_branch?(branch_name)
+
+ unless user_access.can_merge_to_branch?(branch_name)
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_branch]
+ end
+
+ unless safe_commit_for_new_protected_branch?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:invalid_commit_create_protected_branch]
+ end
+
+ unless updated_from_web?
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_create_protected_branch]
+ end
+ end
+ end
+
def protected_branch_deletion_checks
logger.log_timed(LOG_MESSAGES[:protected_branch_deletion_checks]) do
unless user_access.can_delete_branch?(branch_name)
@@ -105,6 +131,10 @@ module Gitlab
def forced_push?
Gitlab::Checks::ForcePush.force_push?(project, oldrev, newrev)
end
+
+ def safe_commit_for_new_protected_branch?
+ ProtectedBranch.any_protected?(project, project.repository.branch_names_contains_sha(newrev))
+ end
end
end
end
diff --git a/lib/gitlab/checks/lfs_check.rb b/lib/gitlab/checks/lfs_check.rb
index cc6a14d2d9a..67a65d61441 100644
--- a/lib/gitlab/checks/lfs_check.rb
+++ b/lib/gitlab/checks/lfs_check.rb
@@ -7,6 +7,7 @@ module Gitlab
ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'.freeze
def validate!
+ return unless Feature.enabled?(:lfs_check, default_enabled: true)
return unless project.lfs_enabled?
return if skip_lfs_integrity_check
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index 4dcb3869d4f..fba0de20ced 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -313,7 +313,7 @@ module Gitlab
def get_term_color_class(color_index, prefix)
color_name = COLOR[color_index]
- return nil if color_name.nil?
+ return if color_name.nil?
get_color_class(["term", prefix, color_name])
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index 08dac756cc1..1c3ce08be76 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -11,8 +11,8 @@ module Gitlab
ParserError = Class.new(StandardError)
InvalidStreamError = Class.new(StandardError)
- VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/
- INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}
+ VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/.freeze
+ INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}.freeze
attr_reader :stream, :path, :full_version
@@ -98,12 +98,12 @@ module Gitlab
def read_uint32(gz)
binary = gz.read(4)
- binary.unpack('L>')[0] if binary
+ binary.unpack1('L>') if binary
end
def read_string(gz)
string_size = read_uint32(gz)
- return nil unless string_size
+ return unless string_size
gz.read(string_size)
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index d0a80518ae8..80e69cdcc95 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -44,7 +44,7 @@ module Gitlab
end
def parent
- return nil unless has_parent?
+ return unless has_parent?
self.class.new(@path.to_s.chomp(basename), @entries)
end
diff --git a/lib/gitlab/ci/build/image.rb b/lib/gitlab/ci/build/image.rb
index 4dd932f61d4..1d7bfba75cd 100644
--- a/lib/gitlab/ci/build/image.rb
+++ b/lib/gitlab/ci/build/image.rb
@@ -4,7 +4,7 @@ module Gitlab
module Ci
module Build
class Image
- attr_reader :alias, :command, :entrypoint, :name
+ attr_reader :alias, :command, :entrypoint, :name, :ports
class << self
def from_image(job)
@@ -26,17 +26,25 @@ module Gitlab
def initialize(image)
if image.is_a?(String)
@name = image
+ @ports = []
elsif image.is_a?(Hash)
@alias = image[:alias]
@command = image[:command]
@entrypoint = image[:entrypoint]
@name = image[:name]
+ @ports = build_ports(image).select(&:valid?)
end
end
def valid?
@name.present?
end
+
+ private
+
+ def build_ports(image)
+ image[:ports].to_a.map { |port| ::Gitlab::Ci::Build::Port.new(port) }
+ end
end
end
end
diff --git a/lib/gitlab/ci/build/policy/refs.rb b/lib/gitlab/ci/build/policy/refs.rb
index 0e9bb5c94bb..c3005303fd8 100644
--- a/lib/gitlab/ci/build/policy/refs.rb
+++ b/lib/gitlab/ci/build/policy/refs.rb
@@ -29,19 +29,23 @@ module Gitlab
def matches_pattern?(pattern, pipeline)
return true if pipeline.tag? && pattern == 'tags'
return true if pipeline.branch? && pattern == 'branches'
- return true if pipeline.source == pattern
- return true if pipeline.source&.pluralize == pattern
+ return true if sanitized_source_name(pipeline) == pattern
+ return true if sanitized_source_name(pipeline)&.pluralize == pattern
# patterns can be matched only when branch or tag is used
# the pattern matching does not work for merge requests pipelines
if pipeline.branch? || pipeline.tag?
- if pattern.first == "/" && pattern.last == "/"
- Regexp.new(pattern[1...-1]) =~ pipeline.ref
+ if regexp = Gitlab::UntrustedRegexp::RubySyntax.fabricate(pattern, fallback: true)
+ regexp.match?(pipeline.ref)
else
pattern == pipeline.ref
end
end
end
+
+ def sanitized_source_name(pipeline)
+ @sanitized_source_name ||= pipeline&.source&.delete_suffix('_event')
+ end
end
end
end
diff --git a/lib/gitlab/ci/build/port.rb b/lib/gitlab/ci/build/port.rb
new file mode 100644
index 00000000000..6c4656ffea2
--- /dev/null
+++ b/lib/gitlab/ci/build/port.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Port
+ DEFAULT_PORT_NAME = 'default_port'.freeze
+ DEFAULT_PORT_PROTOCOL = 'http'.freeze
+
+ attr_reader :number, :protocol, :name
+
+ def initialize(port)
+ @name = DEFAULT_PORT_NAME
+ @protocol = DEFAULT_PORT_PROTOCOL
+
+ case port
+ when Integer
+ @number = port
+ when Hash
+ @number = port[:number]
+ @protocol = port.fetch(:protocol, @protocol)
+ @name = port.fetch(:name, @name)
+ end
+ end
+
+ def valid?
+ @number.present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/base.rb b/lib/gitlab/ci/build/prerequisite/base.rb
new file mode 100644
index 00000000000..156aa22d95b
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Base
+ include Utils::StrongMemoize
+
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet?
+ raise NotImplementedError
+ end
+
+ def complete!
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/factory.rb b/lib/gitlab/ci/build/prerequisite/factory.rb
new file mode 100644
index 00000000000..60cdf7af418
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/factory.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Factory
+ attr_reader :build
+
+ def self.prerequisites
+ [KubernetesNamespace]
+ end
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet
+ build_prerequisites.select(&:unmet?)
+ end
+
+ private
+
+ def build_prerequisites
+ self.class.prerequisites.map do |prerequisite|
+ prerequisite.new(build)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
new file mode 100644
index 00000000000..dbdc59505ac
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class KubernetesNamespace < Base
+ def unmet?
+ deployment_cluster.present? &&
+ deployment_cluster.managed? &&
+ !deployment_cluster.project_type? &&
+ kubernetes_namespace.new_record?
+ end
+
+ def complete!
+ return unless unmet?
+
+ create_or_update_namespace
+ end
+
+ private
+
+ def deployment_cluster
+ build.deployment&.cluster
+ end
+
+ def kubernetes_namespace
+ strong_memoize(:kubernetes_namespace) do
+ deployment_cluster.find_or_initialize_kubernetes_namespace_for_project(build.project)
+ end
+ end
+
+ def create_or_update_namespace
+ Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
+ cluster: deployment_cluster,
+ kubernetes_namespace: kubernetes_namespace
+ ).execute
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index 5875479183e..f187e456993 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -73,18 +73,15 @@ module Gitlab
def build_config(config, project:, sha:, user:)
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
- if project
- process_external_files(initial_config, project: project, sha: sha, user: user)
- else
- initial_config
- end
+ process_external_files(initial_config, project: project, sha: sha, user: user)
end
def process_external_files(config, project:, sha:, user:)
Config::External::Processor.new(config,
project: project,
- sha: sha || project.repository.root_ref_sha,
- user: user).perform
+ sha: sha || project&.repository&.root_ref_sha,
+ user: user,
+ expandset: Set.new).perform
end
end
end
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index 69a3a1aedef..5a13fd18504 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -36,10 +36,12 @@ module Gitlab
validates :config, allowed_keys: ALLOWED_KEYS
validates :url,
+ type: String,
length: { maximum: 255 },
allow_nil: true
validates :action,
+ type: String,
inclusion: { in: %w[start stop], message: 'should be start or stop' },
allow_nil: true
diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb
index 09ecb5fdb99..2b5a59c078e 100644
--- a/lib/gitlab/ci/config/entry/global.rb
+++ b/lib/gitlab/ci/config/entry/global.rb
@@ -17,6 +17,9 @@ module Gitlab
entry :image, Entry::Image,
description: 'Docker image that will be used to execute jobs.'
+ entry :include, Entry::Includes,
+ description: 'List of external YAML files to include.'
+
entry :services, Entry::Services,
description: 'Docker images that will be linked to the container.'
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index a13a0625e90..21c42857895 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -9,24 +9,24 @@ module Gitlab
#
class Image < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
+ include ::Gitlab::Config::Entry::Attributable
+ include ::Gitlab::Config::Entry::Configurable
- ALLOWED_KEYS = %i[name entrypoint].freeze
+ ALLOWED_KEYS = %i[name entrypoint ports].freeze
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
+ validates :config, disallowed_keys: %i[ports], unless: :with_image_ports?
validates :name, type: String, presence: true
validates :entrypoint, array_of_strings: true, allow_nil: true
end
- def hash?
- @config.is_a?(Hash)
- end
+ entry :ports, Entry::Ports,
+ description: 'Ports used to expose the image'
- def string?
- @config.is_a?(String)
- end
+ attributes :ports
def name
value[:name]
@@ -42,6 +42,14 @@ module Gitlab
{}
end
+
+ def with_image_ports?
+ opt(:with_image_ports)
+ end
+
+ def skip_config_hash_validation?
+ true
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/include.rb b/lib/gitlab/ci/config/entry/include.rb
new file mode 100644
index 00000000000..f2f3dd84eda
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/include.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a single include.
+ #
+ class Include < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ ALLOWED_KEYS = %i[local file remote template].freeze
+
+ validations do
+ validates :config, hash_or_string: true
+ validates :config, allowed_keys: ALLOWED_KEYS
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/includes.rb b/lib/gitlab/ci/config/entry/includes.rb
new file mode 100644
index 00000000000..43e74dfd628
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/includes.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a list of include.
+ #
+ class Includes < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ validations do
+ validates :config, array_or_string: true
+ end
+
+ def self.aspects
+ super.append -> do
+ @config = Array.wrap(@config)
+
+ @config.each_with_index do |config, i|
+ @entries[i] = ::Gitlab::Config::Entry::Factory.new(Entry::Include)
+ .value(config || {})
+ .create!
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 290c9591b98..762532f7007 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -34,7 +34,7 @@ module Gitlab
message: 'should be on_success, on_failure, ' \
'always, manual or delayed' }
validates :dependencies, array_of_strings: true
- validates :extends, type: String
+ validates :extends, array_of_strings_or_string: true
end
validates :start_in, duration: { limit: '1 day' }, if: :delayed?
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index adc3660d950..7b14218d3ea 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -17,7 +17,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
validations do
- validates :config, array_of_strings_or_regexps: true
+ validates :config, array_of_strings_or_regexps_with_fallback: true
end
def value
@@ -38,7 +38,7 @@ module Gitlab
validate :variables_expressions_syntax
with_options allow_nil: true do
- validates :refs, array_of_strings_or_regexps: true
+ validates :refs, array_of_strings_or_regexps_with_fallback: true
validates :kubernetes, allowed_values: %w[active]
validates :variables, array_of_strings: true
validates :changes, array_of_strings: true
diff --git a/lib/gitlab/ci/config/entry/port.rb b/lib/gitlab/ci/config/entry/port.rb
new file mode 100644
index 00000000000..c239b1225c5
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/port.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a configuration of an Image Port.
+ #
+ class Port < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ ALLOWED_KEYS = %i[number protocol name].freeze
+
+ validations do
+ validates :config, hash_or_integer: true
+ validates :config, allowed_keys: ALLOWED_KEYS
+
+ validates :number, type: Integer, presence: true
+ validates :protocol, type: String, inclusion: { in: %w[http https], message: 'should be http or https' }, allow_blank: true
+ validates :name, type: String, presence: false, allow_nil: true
+ end
+
+ def number
+ value[:number]
+ end
+
+ def protocol
+ value[:protocol]
+ end
+
+ def name
+ value[:name]
+ end
+
+ def value
+ return { number: @config } if integer?
+ return @config if hash?
+
+ {}
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/ports.rb b/lib/gitlab/ci/config/entry/ports.rb
new file mode 100644
index 00000000000..01ffcc7dd87
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/ports.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a configuration of the ports of a Docker service.
+ #
+ class Ports < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ validations do
+ validates :config, type: Array
+ validates :config, port_name_present_and_unique: true
+ validates :config, port_unique: true
+ end
+
+ def compose!(deps = nil)
+ super do
+ @entries = []
+ @config.each do |config|
+ @entries << ::Gitlab::Config::Entry::Factory.new(Entry::Port)
+ .value(config || {})
+ .with(key: "port", parent: self, description: "port definition.") # rubocop:disable CodeReuse/ActiveRecord
+ .create!
+ end
+
+ @entries.each do |entry|
+ entry.compose!(deps)
+ end
+ end
+ end
+
+ def value
+ @entries.map(&:value)
+ end
+
+ def descendants
+ @entries
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index a3f6cc31321..eeaac52eefd 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -11,7 +11,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
- ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast performance license_management].freeze
+ ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast performance license_management metrics].freeze
attributes ALLOWED_KEYS
@@ -28,6 +28,7 @@ module Gitlab
validates :dast, array_of_strings_or_string: true
validates :performance, array_of_strings_or_string: true
validates :license_management, array_of_strings_or_string: true
+ validates :metrics, array_of_strings_or_string: true
end
end
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index 6df67083310..8d16371e857 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -10,18 +10,23 @@ module Gitlab
class Service < Image
include ::Gitlab::Config::Entry::Validatable
- ALLOWED_KEYS = %i[name entrypoint command alias].freeze
+ ALLOWED_KEYS = %i[name entrypoint command alias ports].freeze
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
+ validates :config, disallowed_keys: %i[ports], unless: :with_image_ports?
validates :name, type: String, presence: true
validates :entrypoint, array_of_strings: true, allow_nil: true
validates :command, array_of_strings: true, allow_nil: true
validates :alias, type: String, allow_nil: true
+ validates :alias, type: String, presence: true, unless: ->(record) { record.ports.blank? }
end
+ entry :ports, Entry::Ports,
+ description: 'Ports used to expose the service'
+
def alias
value[:alias]
end
diff --git a/lib/gitlab/ci/config/entry/services.rb b/lib/gitlab/ci/config/entry/services.rb
index 71475f69218..83baa83711f 100644
--- a/lib/gitlab/ci/config/entry/services.rb
+++ b/lib/gitlab/ci/config/entry/services.rb
@@ -12,6 +12,7 @@ module Gitlab
validations do
validates :config, type: Array
+ validates :config, services_with_ports_alias_unique: true, if: ->(record) { record.opt(:with_image_ports) }
end
def compose!(deps = nil)
@@ -20,6 +21,7 @@ module Gitlab
@config.each do |config|
@entries << ::Gitlab::Config::Entry::Factory.new(Entry::Service)
.value(config || {})
+ .with(key: "service", parent: self, description: "service definition.") # rubocop:disable CodeReuse/ActiveRecord
.create!
end
diff --git a/lib/gitlab/ci/config/extendable/entry.rb b/lib/gitlab/ci/config/extendable/entry.rb
index 7793db09d33..0001a259281 100644
--- a/lib/gitlab/ci/config/extendable/entry.rb
+++ b/lib/gitlab/ci/config/extendable/entry.rb
@@ -5,6 +5,8 @@ module Gitlab
class Config
class Extendable
class Entry
+ include Gitlab::Utils::StrongMemoize
+
InvalidExtensionError = Class.new(Extendable::ExtensionError)
CircularDependencyError = Class.new(Extendable::ExtensionError)
NestingTooDeepError = Class.new(Extendable::ExtensionError)
@@ -28,34 +30,46 @@ module Gitlab
end
def value
- @value ||= @context.fetch(@key)
+ strong_memoize(:value) do
+ @context.fetch(@key)
+ end
end
- def base_hash!
- @base ||= Extendable::Entry
- .new(extends_key, @context, self)
- .extend!
+ def base_hashes!
+ strong_memoize(:base_hashes) do
+ extends_keys.map do |key|
+ Extendable::Entry
+ .new(key, @context, self)
+ .extend!
+ end
+ end
end
- def extends_key
- value.fetch(:extends).to_s.to_sym if extensible?
+ def extends_keys
+ strong_memoize(:extends_keys) do
+ next unless extensible?
+
+ Array(value.fetch(:extends)).map(&:to_s).map(&:to_sym)
+ end
end
def ancestors
- @ancestors ||= Array(@parent&.ancestors) + Array(@parent&.key)
+ strong_memoize(:ancestors) do
+ Array(@parent&.ancestors) + Array(@parent&.key)
+ end
end
def extend!
return value unless extensible?
- if unknown_extension?
+ if unknown_extensions.any?
raise Entry::InvalidExtensionError,
- "#{key}: unknown key in `extends`"
+ "#{key}: unknown keys in `extends` (#{show_keys(unknown_extensions)})"
end
- if invalid_base?
+ if invalid_bases.any?
raise Entry::InvalidExtensionError,
- "#{key}: invalid base hash in `extends`"
+ "#{key}: invalid base hashes in `extends` (#{show_keys(invalid_bases)})"
end
if nesting_too_deep?
@@ -68,11 +82,18 @@ module Gitlab
"#{key}: circular dependency detected in `extends`"
end
- @context[key] = base_hash!.deep_merge(value)
+ merged = {}
+ base_hashes!.each { |h| merged.deep_merge!(h) }
+
+ @context[key] = merged.deep_merge!(value)
end
private
+ def show_keys(keys)
+ keys.join(', ')
+ end
+
def nesting_too_deep?
ancestors.count > MAX_NESTING_LEVELS
end
@@ -81,12 +102,16 @@ module Gitlab
ancestors.include?(key)
end
- def unknown_extension?
- !@context.key?(extends_key)
+ def unknown_extensions
+ strong_memoize(:unknown_extensions) do
+ extends_keys.reject { |key| @context.key?(key) }
+ end
end
- def invalid_base?
- !@context[extends_key].is_a?(Hash)
+ def invalid_bases
+ strong_memoize(:invalid_bases) do
+ extends_keys.reject { |key| @context[key].is_a?(Hash) }
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index a747886093c..2ffbb214a92 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -12,7 +12,7 @@ module Gitlab
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
- Context = Struct.new(:project, :sha, :user)
+ Context = Struct.new(:project, :sha, :user, :expandset)
def initialize(params, context)
@params = params
@@ -43,13 +43,27 @@ module Gitlab
end
def to_hash
- @hash ||= Gitlab::Config::Loader::Yaml.new(content).load!
- rescue Gitlab::Config::Loader::FormatError
- nil
+ expanded_content_hash
end
protected
+ def expanded_content_hash
+ return unless content_hash
+
+ strong_memoize(:expanded_content_yaml) do
+ expand_includes(content_hash)
+ end
+ end
+
+ def content_hash
+ strong_memoize(:content_yaml) do
+ Gitlab::Config::Loader::Yaml.new(content).load!
+ end
+ rescue Gitlab::Config::Loader::FormatError
+ nil
+ end
+
def validate!
validate_location!
validate_content! if errors.none?
@@ -73,6 +87,14 @@ module Gitlab
errors.push("Included file `#{location}` does not have valid YAML syntax!")
end
end
+
+ def expand_includes(hash)
+ External::Processor.new(hash, **expand_context).perform
+ end
+
+ def expand_context
+ { project: nil, sha: nil, user: nil, expandset: context.expandset }
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/local.rb b/lib/gitlab/ci/config/external/file/local.rb
index 2535d178ba8..cac321ec4a6 100644
--- a/lib/gitlab/ci/config/external/file/local.rb
+++ b/lib/gitlab/ci/config/external/file/local.rb
@@ -21,7 +21,9 @@ module Gitlab
private
def validate_content!
- if content.nil?
+ if context.project&.repository.nil?
+ errors.push("Local file `#{location}` does not have project!")
+ elsif content.nil?
errors.push("Local file `#{location}` does not exist!")
elsif content.blank?
errors.push("Local file `#{location}` is empty!")
@@ -31,6 +33,13 @@ module Gitlab
def fetch_local_content
context.project.repository.blob_data_at(context.sha, location)
end
+
+ def expand_context
+ super.merge(
+ project: context.project,
+ sha: context.sha,
+ user: context.user)
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/project.rb b/lib/gitlab/ci/config/external/file/project.rb
index e75540dbe5a..b828f77835c 100644
--- a/lib/gitlab/ci/config/external/file/project.rb
+++ b/lib/gitlab/ci/config/external/file/project.rb
@@ -64,6 +64,13 @@ module Gitlab
project.commit(ref_name).try(:sha)
end
end
+
+ def expand_context
+ super.merge(
+ project: project,
+ sha: sha,
+ user: context.user)
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
index 108bfd5eb43..aff5c5b9651 100644
--- a/lib/gitlab/ci/config/external/mapper.rb
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -7,6 +7,8 @@ module Gitlab
class Mapper
include Gitlab::Utils::StrongMemoize
+ MAX_INCLUDES = 50
+
FILE_CLASSES = [
External::File::Remote,
External::File::Template,
@@ -14,25 +16,34 @@ module Gitlab
External::File::Project
].freeze
- AmbigiousSpecificationError = Class.new(StandardError)
+ Error = Class.new(StandardError)
+ AmbigiousSpecificationError = Class.new(Error)
+ DuplicateIncludesError = Class.new(Error)
+ TooManyIncludesError = Class.new(Error)
+
+ def initialize(values, project:, sha:, user:, expandset:)
+ raise Error, 'Expanded needs to be `Set`' unless expandset.is_a?(Set)
- def initialize(values, project:, sha:, user:)
@locations = Array.wrap(values.fetch(:include, []))
@project = project
@sha = sha
@user = user
+ @expandset = expandset
end
def process
+ return [] if locations.empty?
+
locations
.compact
.map(&method(:normalize_location))
+ .each(&method(:verify_duplicates!))
.map(&method(:select_first_matching))
end
private
- attr_reader :locations, :project, :sha, :user
+ attr_reader :locations, :project, :sha, :user, :expandset
# convert location if String to canonical form
def normalize_location(location)
@@ -51,6 +62,23 @@ module Gitlab
end
end
+ def verify_duplicates!(location)
+ if expandset.count >= MAX_INCLUDES
+ raise TooManyIncludesError, "Maximum of #{MAX_INCLUDES} nested includes are allowed!"
+ end
+
+ # We scope location to context, as this allows us to properly support
+ # relative incldues, and similarly looking relative in another project
+ # does not trigger duplicate error
+ scoped_location = location.merge(
+ context_project: project,
+ context_sha: sha)
+
+ unless expandset.add?(scoped_location)
+ raise DuplicateIncludesError, "Include `#{location.to_json}` was already included!"
+ end
+ end
+
def select_first_matching(location)
matching = FILE_CLASSES.map do |file_class|
file_class.new(location, context)
@@ -63,7 +91,7 @@ module Gitlab
def context
strong_memoize(:context) do
- External::File::Base::Context.new(project, sha, user)
+ External::File::Base::Context.new(project, sha, user, expandset)
end
end
end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
index 69bc164a039..4a049ecae49 100644
--- a/lib/gitlab/ci/config/external/processor.rb
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -7,11 +7,12 @@ module Gitlab
class Processor
IncludeError = Class.new(StandardError)
- def initialize(values, project:, sha:, user:)
+ def initialize(values, project:, sha:, user:, expandset:)
@values = values
- @external_files = External::Mapper.new(values, project: project, sha: sha, user: user).process
+ @external_files = External::Mapper.new(values, project: project, sha: sha, user: user, expandset: expandset).process
@content = {}
- rescue External::Mapper::AmbigiousSpecificationError => e
+ rescue External::Mapper::Error,
+ OpenSSL::SSL::SSLError => e
raise IncludeError, e.message
end
diff --git a/lib/gitlab/ci/model.rb b/lib/gitlab/ci/model.rb
index fbdb84c0522..1625cb841b6 100644
--- a/lib/gitlab/ci/model.rb
+++ b/lib/gitlab/ci/model.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def model_name
- @model_name ||= ActiveModel::Name.new(self, nil, self.name.split("::").last)
+ @model_name ||= ActiveModel::Name.new(self, nil, self.name.demodulize)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 41632211374..164a4634d84 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -12,6 +12,8 @@ module Gitlab
ref: @command.ref,
sha: @command.sha,
before_sha: @command.before_sha,
+ source_sha: @command.source_sha,
+ target_sha: @command.target_sha,
tag: @command.tag_exists?,
trigger_requests: Array(@command.trigger_request),
user: @command.current_user,
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index e4ed1424865..c911bfa7ff6 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab
@@ -7,11 +6,11 @@ module Gitlab
module Chain
Command = Struct.new(
:source, :project, :current_user,
- :origin_ref, :checkout_sha, :after_sha, :before_sha,
+ :origin_ref, :checkout_sha, :after_sha, :before_sha, :source_sha, :target_sha,
:trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
- :chat_data
+ :chat_data, :allow_mirror_update
) do
include Gitlab::Utils::StrongMemoize
@@ -33,6 +32,13 @@ module Gitlab
end
end
+ def merge_request_ref_exists?
+ strong_memoize(:merge_request_ref_exists) do
+ MergeRequest.merge_request_ref?(origin_ref) &&
+ project.repository.ref_exists?(origin_ref)
+ end
+ end
+
def ref
strong_memoize(:ref) do
Gitlab::Git.ref_name(origin_ref)
diff --git a/lib/gitlab/ci/pipeline/chain/limit/activity.rb b/lib/gitlab/ci/pipeline/chain/limit/activity.rb
index fe7c8738cc0..68482cf08a9 100644
--- a/lib/gitlab/ci/pipeline/chain/limit/activity.rb
+++ b/lib/gitlab/ci/pipeline/chain/limit/activity.rb
@@ -7,11 +7,11 @@ module Gitlab
module Limit
class Activity < Chain::Base
def perform!
- # to be overriden in EE
+ # to be overridden in EE
end
def break?
- false # to be overriden in EE
+ false # to be overridden in EE
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/limit/size.rb b/lib/gitlab/ci/pipeline/chain/limit/size.rb
index b4d51437cd6..cd330c58406 100644
--- a/lib/gitlab/ci/pipeline/chain/limit/size.rb
+++ b/lib/gitlab/ci/pipeline/chain/limit/size.rb
@@ -7,11 +7,11 @@ module Gitlab
module Limit
class Size < Chain::Base
def perform!
- # to be overriden in EE
+ # to be overridden in EE
end
def break?
- false # to be overriden in EE
+ false # to be overridden in EE
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb
index 79bbcc1ed1e..df92e229f12 100644
--- a/lib/gitlab/ci/pipeline/chain/skip.rb
+++ b/lib/gitlab/ci/pipeline/chain/skip.rb
@@ -7,8 +7,7 @@ module Gitlab
class Skip < Chain::Base
include ::Gitlab::Utils::StrongMemoize
- SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
- SKIP_PUSH_OPTION = 'ci.skip'
+ SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i.freeze
def perform!
if skipped?
@@ -35,7 +34,8 @@ module Gitlab
end
def push_option_skips_ci?
- !!(@command.push_options&.include?(SKIP_PUSH_OPTION))
+ @command.push_options.present? &&
+ @command.push_options.deep_symbolize_keys.dig(:ci, :skip).present?
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
index ebd7e6e8289..aaa3daddcc5 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -44,6 +44,8 @@ module Gitlab
access.can_update_branch?(@command.ref)
elsif @command.tag_exists?
access.can_create_tag?(@command.ref)
+ elsif @command.merge_request_ref_exists?
+ access.can_update_branch?(@command.merge_request.source_branch)
else
true # Allow it for now and we'll reject when we check ref existence
end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
index 9c6c2bc8e25..8f5445850d7 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
@@ -9,7 +9,7 @@ module Gitlab
include Chain::Helpers
def perform!
- unless @command.branch_exists? || @command.tag_exists?
+ unless @command.branch_exists? || @command.tag_exists? || @command.merge_request_ref_exists?
return error('Reference not found')
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/and.rb b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
new file mode 100644
index 00000000000..54a0e2ad9dd
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class And < Lexeme::Operator
+ PATTERN = /&&/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) && @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 11 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
index 70c774416f6..7ebd2e25398 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
@@ -15,10 +15,14 @@ module Gitlab
end
def self.scan(scanner)
- if scanner.scan(self::PATTERN)
+ if scanner.scan(pattern)
Expression::Token.new(scanner.matched, self)
end
end
+
+ def self.pattern
+ self::PATTERN
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
index 668e85f5b9e..62f4c14f597 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
@@ -8,11 +8,6 @@ module Gitlab
class Equals < Lexeme::Operator
PATTERN = /==/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
@left.evaluate(variables) == @right.evaluate(variables)
end
@@ -20,6 +15,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
index cd17bc4d78b..ecfab627226 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
@@ -8,21 +8,36 @@ module Gitlab
class Matches < Lexeme::Operator
PATTERN = /=~/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
regexp.scan(text.to_s).any?
+
+ if ci_variables_complex_expressions?
+ # return offset of first match, or nil if no matches
+ if match = regexp.scan(text.to_s).first
+ text.to_s.index(match)
+ end
+ else
+ # return true or false
+ regexp.scan(text.to_s).any?
+ end
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+
+ private
+
+ def ci_variables_complex_expressions?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
new file mode 100644
index 00000000000..8166bcd5730
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class NotEquals < Lexeme::Operator
+ PATTERN = /!=/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) != @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
new file mode 100644
index 00000000000..831c27fa0ea
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class NotMatches < Lexeme::Operator
+ PATTERN = /\!~/.freeze
+
+ def evaluate(variables = {})
+ text = @left.evaluate(variables)
+ regexp = @right.evaluate(variables)
+
+ regexp.scan(text.to_s).none?
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
index 3ebceb92eb7..3ddab7800c8 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
@@ -6,9 +6,29 @@ module Gitlab
module Expression
module Lexeme
class Operator < Lexeme::Base
+ # This operator class is design to handle single operators that take two
+ # arguments. Expression::Parser was originally designed to read infix operators,
+ # and so the two operands are called "left" and "right" here. If we wish to
+ # implement an Operator that takes a greater or lesser number of arguments, a
+ # structural change or additional Operator superclass will likely be needed.
+
+ OperatorError = Class.new(Expression::ExpressionError)
+
+ def initialize(left, right)
+ raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate
+ raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate
+
+ @left = left
+ @right = right
+ end
+
def self.type
:operator
end
+
+ def self.precedence
+ raise NotImplementedError
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/or.rb b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
new file mode 100644
index 00000000000..807876f905a
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class Or < Lexeme::Operator
+ PATTERN = /\|\|/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) || @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 12 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
index d7e6dacf068..e4cf360a1c1 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
@@ -8,25 +8,34 @@ module Gitlab
require_dependency 're2'
class Pattern < Lexeme::Value
- PATTERN = %r{^/.+/[ismU]*$}.freeze
+ PATTERN = %r{^/.+/[ismU]*$}.freeze
+ NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
- @value = regexp
+ @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp
- unless Gitlab::UntrustedRegexp.valid?(@value)
+ unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
end
end
def evaluate(variables = {})
- Gitlab::UntrustedRegexp.fabricate(@value)
+ Gitlab::UntrustedRegexp::RubySyntax.fabricate!(@value)
rescue RegexpError
raise Expression::RuntimeError, 'Invalid regular expression!'
end
+ def self.pattern
+ eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN
+ end
+
def self.build(string)
new(string)
end
+
+ def self.eager_matching_with_escape_characters?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index f26542361a2..22c210ae26b 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -15,7 +15,22 @@ module Gitlab
Expression::Lexeme::Pattern,
Expression::Lexeme::Null,
Expression::Lexeme::Equals,
- Expression::Lexeme::Matches
+ Expression::Lexeme::Matches,
+ Expression::Lexeme::NotEquals,
+ Expression::Lexeme::NotMatches
+ ].freeze
+
+ NEW_LEXEMES = [
+ Expression::Lexeme::Variable,
+ Expression::Lexeme::String,
+ Expression::Lexeme::Pattern,
+ Expression::Lexeme::Null,
+ Expression::Lexeme::Equals,
+ Expression::Lexeme::Matches,
+ Expression::Lexeme::NotEquals,
+ Expression::Lexeme::NotMatches,
+ Expression::Lexeme::And,
+ Expression::Lexeme::Or
].freeze
MAX_TOKENS = 100
@@ -43,7 +58,7 @@ module Gitlab
return tokens if @scanner.eos?
- lexeme = LEXEMES.find do |type|
+ lexeme = available_lexemes.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
@@ -56,6 +71,10 @@ module Gitlab
raise Lexer::SyntaxError, 'Too many tokens!'
end
+
+ def available_lexemes
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb
index ed184309ab4..589bf32a4d7 100644
--- a/lib/gitlab/ci/pipeline/expression/parser.rb
+++ b/lib/gitlab/ci/pipeline/expression/parser.rb
@@ -5,17 +5,30 @@ module Gitlab
module Pipeline
module Expression
class Parser
+ ParseError = Class.new(Expression::ExpressionError)
+
def initialize(tokens)
@tokens = tokens.to_enum
@nodes = []
end
- ##
- # This produces a reverse descent parse tree.
- #
- # It currently does not support precedence of operators.
- #
def tree
+ if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ rpn_parse_tree
+ else
+ reverse_descent_parse_tree
+ end
+ end
+
+ def self.seed(statement)
+ new(Expression::Lexer.new(statement).tokens)
+ end
+
+ private
+
+ # This produces a reverse descent parse tree.
+ # It does not support precedence of operators.
+ def reverse_descent_parse_tree
while token = @tokens.next
case token.type
when :operator
@@ -32,8 +45,51 @@ module Gitlab
@nodes.last || Lexeme::Null.new
end
- def self.seed(statement)
- new(Expression::Lexer.new(statement).tokens)
+ def rpn_parse_tree
+ results = []
+
+ tokens_rpn.each do |token|
+ case token.type
+ when :value
+ results.push(token.build)
+ when :operator
+ right_operand = results.pop
+ left_operand = results.pop
+
+ token.build(left_operand, right_operand).tap do |res|
+ results.push(res)
+ end
+ else
+ raise ParseError, 'Unprocessable token found in parse tree'
+ end
+ end
+
+ raise ParseError, 'Unreachable nodes in parse tree' if results.count > 1
+ raise ParseError, 'Empty parse tree' if results.count < 1
+
+ results.pop
+ end
+
+ # Parse the expression into Reverse Polish Notation
+ # (See: Shunting-yard algorithm)
+ def tokens_rpn
+ output = []
+ operators = []
+
+ @tokens.each do |token|
+ case token.type
+ when :value
+ output.push(token)
+ when :operator
+ if operators.any? && token.lexeme.precedence >= operators.last.lexeme.precedence
+ output.push(operators.pop)
+ end
+
+ operators.push(token)
+ end
+ end
+
+ output.concat(operators.reverse)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index b03611f756e..0e81e1bd34c 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -7,16 +7,6 @@ module Gitlab
class Statement
StatementError = Class.new(Expression::ExpressionError)
- GRAMMAR = [
- %w[variable],
- %w[variable equals string],
- %w[variable equals variable],
- %w[variable equals null],
- %w[string equals variable],
- %w[null equals variable],
- %w[variable matches pattern]
- ].freeze
-
def initialize(statement, variables = {})
@lexer = Expression::Lexer.new(statement)
@variables = variables.with_indifferent_access
@@ -25,10 +15,6 @@ module Gitlab
def parse_tree
raise StatementError if @lexer.lexemes.empty?
- unless GRAMMAR.find { |syntax| syntax == @lexer.lexemes }
- raise StatementError, 'Unknown pipeline expression!'
- end
-
Expression::Parser.new(@lexer.tokens).tree
end
@@ -43,6 +29,7 @@ module Gitlab
end
def valid?
+ evaluate
parse_tree.is_a?(Lexeme::Base)
rescue Expression::ExpressionError
false
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index 6e4bfe23f2b..96d05842838 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -11,11 +11,13 @@ module Gitlab
Status::Build::Manual,
Status::Build::Canceled,
Status::Build::Created,
+ Status::Build::Preparing,
Status::Build::Pending,
Status::Build::Skipped],
[Status::Build::Cancelable,
Status::Build::Retryable],
- [Status::Build::Failed],
+ [Status::Build::FailedUnmetPrerequisites,
+ Status::Build::Failed],
[Status::Build::FailedAllowed,
Status::Build::Unschedule,
Status::Build::Play,
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index d40454df737..76dfe7b7639 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -15,7 +15,8 @@ module Gitlab
runner_unsupported: 'unsupported runner',
stale_schedule: 'stale schedule',
job_execution_timeout: 'job execution timeout',
- archived_failure: 'archived failure'
+ archived_failure: 'archived failure',
+ unmet_prerequisites: 'unmet prerequisites'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb
index d7570fdd3e2..8972498a069 100644
--- a/lib/gitlab/ci/status/build/failed_allowed.rb
+++ b/lib/gitlab/ci/status/build/failed_allowed.rb
@@ -14,7 +14,7 @@ module Gitlab
end
def group
- 'failed_with_warnings'
+ 'failed-with-warnings'
end
def status_tooltip
diff --git a/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb b/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb
new file mode 100644
index 00000000000..eaad3969a4c
--- /dev/null
+++ b/lib/gitlab/ci/status/build/failed_unmet_prerequisites.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class FailedUnmetPrerequisites < Status::Extended
+ def illustration
+ {
+ image: 'illustrations/pipelines_failed.svg',
+ size: 'svg-430',
+ title: _('Failed to create resources'),
+ content: _('Retry this job in order to create the necessary resources.')
+ }
+ end
+
+ def self.matches?(build, _)
+ build.unmet_prerequisites?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/preparing.rb b/lib/gitlab/ci/status/build/preparing.rb
new file mode 100644
index 00000000000..1fddcb05f79
--- /dev/null
+++ b/lib/gitlab/ci/status/build/preparing.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Preparing < Status::Extended
+ ##
+ # TODO: image is shared with 'pending'
+ # until we get a dedicated one
+ #
+ def illustration
+ {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: _('This job is preparing to start'),
+ content: _('This job is performing tasks that must complete before it can start')
+ }
+ end
+
+ def self.matches?(build, _)
+ build.preparing?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/preparing.rb b/lib/gitlab/ci/status/preparing.rb
new file mode 100644
index 00000000000..62985d0a9f9
--- /dev/null
+++ b/lib/gitlab/ci/status/preparing.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ class Preparing < Status::Core
+ def text
+ s_('CiStatusText|preparing')
+ end
+
+ def label
+ s_('CiStatusLabel|preparing')
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def icon
+ 'status_created'
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def favicon
+ 'favicon_status_created'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb
index 58f4642510b..e50b0853725 100644
--- a/lib/gitlab/ci/status/stage/factory.rb
+++ b/lib/gitlab/ci/status/stage/factory.rb
@@ -6,7 +6,8 @@ module Gitlab
module Stage
class Factory < Status::Factory
def self.extended_statuses
- [Status::SuccessWarning]
+ [[Status::SuccessWarning],
+ [Status::Stage::PlayManual]]
end
def self.common_helpers
diff --git a/lib/gitlab/ci/status/stage/play_manual.rb b/lib/gitlab/ci/status/stage/play_manual.rb
new file mode 100644
index 00000000000..ac3fc0912fa
--- /dev/null
+++ b/lib/gitlab/ci/status/stage/play_manual.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Stage
+ class PlayManual < Status::Extended
+ include Gitlab::Routing
+
+ def action_icon
+ 'play'
+ end
+
+ def action_title
+ 'Play all manual'
+ end
+
+ def action_path
+ pipeline = subject.pipeline
+
+ project_stage_play_manual_path(pipeline.project, pipeline, subject.name)
+ end
+
+ def action_method
+ :post
+ end
+
+ def action_button_title
+ _('Play all manual')
+ end
+
+ def self.matches?(stage, user)
+ stage.manual_playable?
+ end
+
+ def has_action?
+ true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/success_warning.rb b/lib/gitlab/ci/status/success_warning.rb
index 6632cd9b143..47623ad945f 100644
--- a/lib/gitlab/ci/status/success_warning.rb
+++ b/lib/gitlab/ci/status/success_warning.rb
@@ -21,7 +21,7 @@ module Gitlab
end
def group
- 'success_with_warnings'
+ 'success-with-warnings'
end
def self.matches?(subject, user)
diff --git a/lib/gitlab/ci/templates/.yamllint b/lib/gitlab/ci/templates/.yamllint
new file mode 100644
index 00000000000..669c8646ff2
--- /dev/null
+++ b/lib/gitlab/ci/templates/.yamllint
@@ -0,0 +1,5 @@
+extends: default
+
+rules:
+ line-length: disable
+ document-start: disable
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
index 9c534b2b8e7..2ca6e73a803 100644
--- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -6,6 +6,7 @@ stages:
- environment
- build
- test
+ - deploy
- internal
- alpha
- beta
@@ -53,7 +54,7 @@ ensureContainer:
- rm -f android-signing-keystore.jks || true
artifacts:
paths:
- - app/build/outputs
+ - app/build/outputs
buildDebug:
extends: .build_job
@@ -115,7 +116,6 @@ promoteProduction:
# We only allow production promotion on `master` because
# it has its own production scoped secret variables
only:
- - master
+ - master
script:
- bundle exec fastlane promote_beta_to_production
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index c169e3f7686..b7194110002 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -6,7 +6,7 @@ image: openjdk:8-jdk
variables:
ANDROID_COMPILE_SDK: "28"
ANDROID_BUILD_TOOLS: "28.0.2"
- ANDROID_SDK_TOOLS: "4333796"
+ ANDROID_SDK_TOOLS: "4333796"
before_script:
- apt-get --quiet update --yes
@@ -24,10 +24,6 @@ before_script:
- yes | android-sdk-linux/tools/bin/sdkmanager --licenses
- set -o pipefail
-stages:
- - build
- - test
-
lintDebug:
stage: build
script:
@@ -39,7 +35,7 @@ assembleDebug:
- ./gradlew assembleDebug
artifacts:
paths:
- - app/build/outputs/
+ - app/build/outputs/
debugTests:
stage: test
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 37fc1fccfb9..65a6630365d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -50,8 +50,8 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.7
- HELM_VERSION: 2.12.3
+ KUBERNETES_VERSION: 1.11.10
+ HELM_VERSION: 2.14.0
DOCKER_DRIVER: overlay2
@@ -60,6 +60,7 @@ variables:
stages:
- build
- test
+ - deploy # dummy stage to follow the template guidelines
- review
- dast
- staging
@@ -72,900 +73,20 @@ stages:
- performance
- cleanup
-build:
- stage: build
- image: docker:stable-git
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - build
- only:
- - branches
-
-test:
- services:
- - postgres:latest
- variables:
- POSTGRES_DB: test
- stage: test
- image: gliderlabs/herokuish:latest
- script:
- - setup_test_db
- - cp -R . /tmp/app
- - /bin/herokuish buildpack test
- only:
- - branches
- except:
- variables:
- - $TEST_DISABLED
-
-code_quality:
- stage: test
- image: docker:stable
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - code_quality
- artifacts:
- paths: [gl-code-quality-report.json]
- only:
- - branches
- except:
- variables:
- - $CODE_QUALITY_DISABLED
-
-license_management:
- stage: test
- image:
- name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
- entrypoint: [""]
- allow_failure: true
- script:
- - license_management
- artifacts:
- paths: [gl-license-management-report.json]
- only:
- refs:
- - branches
- variables:
- - $GITLAB_FEATURES =~ /\blicense_management\b/
- except:
- variables:
- - $LICENSE_MANAGEMENT_DISABLED
-
-performance:
- stage: performance
- image: docker:stable
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - performance
- artifacts:
- paths:
- - performance.json
- - sitespeed-results/
- only:
- refs:
- - branches
- kubernetes: active
- except:
- variables:
- - $PERFORMANCE_DISABLED
-
-sast:
- stage: test
- image: docker:stable
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - sast
- artifacts:
- reports:
- sast: gl-sast-report.json
- only:
- refs:
- - branches
- variables:
- - $GITLAB_FEATURES =~ /\bsast\b/
- except:
- variables:
- - $SAST_DISABLED
-
-dependency_scanning:
- stage: test
- image: docker:stable
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - dependency_scanning
- artifacts:
- reports:
- dependency_scanning: gl-dependency-scanning-report.json
- only:
- refs:
- - branches
- variables:
- - $GITLAB_FEATURES =~ /\bdependency_scanning\b/
- except:
- variables:
- - $DEPENDENCY_SCANNING_DISABLED
-
-container_scanning:
- stage: test
- image: docker:stable
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - setup_docker
- - container_scanning
- artifacts:
- paths: [gl-container-scanning-report.json]
- only:
- refs:
- - branches
- variables:
- - $GITLAB_FEATURES =~ /\bcontainer_scanning\b/
- except:
- variables:
- - $CONTAINER_SCANNING_DISABLED
-
+include:
+ - template: Jobs/Build.gitlab-ci.yml
+ - template: Jobs/Test.gitlab-ci.yml
+ - template: Jobs/Code-Quality.gitlab-ci.yml
+ - template: Jobs/Deploy.gitlab-ci.yml
+ - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml
+ - template: Security/DAST.gitlab-ci.yml
+ - template: Security/Container-Scanning.gitlab-ci.yml
+ - template: Security/Dependency-Scanning.gitlab-ci.yml
+ - template: Security/License-Management.gitlab-ci.yml
+ - template: Security/SAST.gitlab-ci.yml
+
+# Override DAST job to exclude master branch
dast:
- stage: dast
- allow_failure: true
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- POSTGRES_DB: "false"
- script:
- - dast
- artifacts:
- paths: [gl-dast-report.json]
- only:
- refs:
- - branches
- kubernetes: active
- variables:
- - $GITLAB_FEATURES =~ /\bdast\b/
except:
refs:
- master
- variables:
- - $DAST_DISABLED
-
-review:
- stage: review
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - persist_environment_url
- environment:
- name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
- on_stop: stop_review
- artifacts:
- paths: [environment_url.txt]
- only:
- refs:
- - branches
- kubernetes: active
- except:
- refs:
- - master
- variables:
- - $REVIEW_DISABLED
-
-stop_review:
- stage: cleanup
- variables:
- GIT_STRATEGY: none
- script:
- - install_dependencies
- - initialize_tiller
- - delete
- environment:
- name: review/$CI_COMMIT_REF_NAME
- action: stop
- when: manual
- allow_failure: true
- only:
- refs:
- - branches
- kubernetes: active
- except:
- refs:
- - master
- variables:
- - $REVIEW_DISABLED
-
-# Staging deploys are disabled by default since
-# continuous deployment to production is enabled by default
-# If you prefer to automatically deploy to staging and
-# only manually promote to production, enable this job by setting
-# STAGING_ENABLED.
-
-staging:
- stage: staging
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- environment:
- name: staging
- url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN
- only:
- refs:
- - master
- kubernetes: active
- variables:
- - $STAGING_ENABLED
-
-# Canaries are also disabled by default, but if you want them,
-# and know what the downsides are, you can enable this by setting
-# CANARY_ENABLED.
-
-canary:
- stage: canary
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy canary
- environment:
- name: production
- url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
- when: manual
- only:
- refs:
- - master
- kubernetes: active
- variables:
- - $CANARY_ENABLED
-
-.production: &production_template
- stage: production
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - delete canary
- - delete rollout
- - persist_environment_url
- environment:
- name: production
- url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
- artifacts:
- paths: [environment_url.txt]
-
-production:
- <<: *production_template
- only:
- refs:
- - master
- kubernetes: active
- except:
- variables:
- - $STAGING_ENABLED
- - $CANARY_ENABLED
- - $INCREMENTAL_ROLLOUT_ENABLED
- - $INCREMENTAL_ROLLOUT_MODE
-
-production_manual:
- <<: *production_template
- when: manual
- allow_failure: false
- only:
- refs:
- - master
- kubernetes: active
- variables:
- - $STAGING_ENABLED
- - $CANARY_ENABLED
- except:
- variables:
- - $INCREMENTAL_ROLLOUT_ENABLED
- - $INCREMENTAL_ROLLOUT_MODE
-
-# This job implements incremental rollout on for every push to `master`.
-
-.rollout: &rollout_template
- script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy rollout $ROLLOUT_PERCENTAGE
- - scale stable $((100-ROLLOUT_PERCENTAGE))
- - delete canary
- - persist_environment_url
- environment:
- name: production
- url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
- artifacts:
- paths: [environment_url.txt]
-
-.manual_rollout_template: &manual_rollout_template
- <<: *rollout_template
- stage: production
- when: manual
- # This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4)
- only:
- refs:
- - master
- kubernetes: active
- variables:
- - $INCREMENTAL_ROLLOUT_MODE == "manual"
- - $INCREMENTAL_ROLLOUT_ENABLED
- except:
- variables:
- - $INCREMENTAL_ROLLOUT_MODE == "timed"
-
-.timed_rollout_template: &timed_rollout_template
- <<: *rollout_template
- when: delayed
- start_in: 5 minutes
- only:
- refs:
- - master
- kubernetes: active
- variables:
- - $INCREMENTAL_ROLLOUT_MODE == "timed"
-
-timed rollout 10%:
- <<: *timed_rollout_template
- stage: incremental rollout 10%
- variables:
- ROLLOUT_PERCENTAGE: 10
-
-timed rollout 25%:
- <<: *timed_rollout_template
- stage: incremental rollout 25%
- variables:
- ROLLOUT_PERCENTAGE: 25
-
-timed rollout 50%:
- <<: *timed_rollout_template
- stage: incremental rollout 50%
- variables:
- ROLLOUT_PERCENTAGE: 50
-
-timed rollout 100%:
- <<: *timed_rollout_template
- <<: *production_template
- stage: incremental rollout 100%
- variables:
- ROLLOUT_PERCENTAGE: 100
-
-rollout 10%:
- <<: *manual_rollout_template
- variables:
- ROLLOUT_PERCENTAGE: 10
-
-rollout 25%:
- <<: *manual_rollout_template
- variables:
- ROLLOUT_PERCENTAGE: 25
-
-rollout 50%:
- <<: *manual_rollout_template
- variables:
- ROLLOUT_PERCENTAGE: 50
-
-rollout 100%:
- <<: *manual_rollout_template
- <<: *production_template
- allow_failure: false
-
-# ---------------------------------------------------------------------------
-
-.auto_devops: &auto_devops |
- # Auto DevOps variables and functions
- [[ "$TRACE" ]] && set -x
- auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
- export DATABASE_URL=${DATABASE_URL-$auto_database_url}
- export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- export CI_APPLICATION_TAG=$CI_COMMIT_SHA
- export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
- export TILLER_NAMESPACE=$KUBE_NAMESPACE
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
-
- function registry_login() {
- if [[ -n "$CI_REGISTRY_USER" ]]; then
- echo "Logging to GitLab Container Registry with CI credentials..."
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- echo ""
- fi
- }
-
- function container_scanning() {
- registry_login
-
- docker run -d --name db arminc/clair-db:latest
- docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
- retries=0
- echo "Waiting for clair daemon to start"
- while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- }
-
- function code_quality() {
- docker run --env SOURCE_CODE="$PWD" \
- --volume "$PWD":/code \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
- }
-
- function license_management() {
- /run.sh analyze .
- }
-
- function sast() {
- case "$CI_SERVER_VERSION" in
- *-ee)
-
- # Deprecation notice for CONFIDENCE_LEVEL variable
- if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then
- SAST_CONFIDENCE_LEVEL="$CONFIDENCE_LEVEL"
- echo "WARNING: CONFIDENCE_LEVEL is deprecated and MUST be replaced with SAST_CONFIDENCE_LEVEL"
- fi
-
- docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
- ;;
- *)
- echo "GitLab EE is required"
- ;;
- esac
- }
-
- function dependency_scanning() {
- case "$CI_SERVER_VERSION" in
- *-ee)
- docker run --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}" \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
- ;;
- *)
- echo "GitLab EE is required"
- ;;
- esac
- }
-
- function get_replicas() {
- track="${1:-stable}"
- percentage="${2:-100}"
-
- env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
- env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
-
- if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then
- # for stable track get number of replicas from `PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- new_replicas=$REPLICAS
- fi
- else
- # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- eval new_replicas=\${env_track}_REPLICAS
- fi
- fi
-
- replicas="${new_replicas:-1}"
- replicas="$(($replicas * $percentage / 100))"
-
- # always return at least one replicas
- if [[ $replicas -gt 0 ]]; then
- echo "$replicas"
- else
- echo 1
- fi
- }
-
- # Extracts variables prefixed with K8S_SECRET_
- # and creates a Kubernetes secret.
- #
- # e.g. If we have the following environment variables:
- # K8S_SECRET_A=value1
- # K8S_SECRET_B=multi\ word\ value
- #
- # Then we will create a secret with the following key-value pairs:
- # data:
- # A: dmFsdWUxCg==
- # B: bXVsdGkgd29yZCB2YWx1ZQo=
- function create_application_secret() {
- track="${1-stable}"
- export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
-
- env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
-
- kubectl create secret \
- -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
- --from-env-file k8s_prefixed_variables -o yaml --dry-run |
- kubectl replace -n "$KUBE_NAMESPACE" --force -f -
-
- export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
-
- rm k8s_prefixed_variables
- }
-
- function deploy_name() {
- name="$CI_ENVIRONMENT_SLUG"
- track="${1-stable}"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- echo $name
- }
-
- function application_secret_name() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- echo "${name}-secret"
- }
-
- function deploy() {
- track="${1-stable}"
- percentage="${2:-100}"
- name=$(deploy_name "$track")
-
- replicas="1"
- service_enabled="true"
- postgres_enabled="$POSTGRES_ENABLED"
-
- # if track is different than stable,
- # re-use all attached resources
- if [[ "$track" != "stable" ]]; then
- service_enabled="false"
- postgres_enabled="false"
- fi
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
- secret_name='gitlab-registry'
- else
- secret_name=''
- fi
-
- create_application_secret "$track"
-
- env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
- eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
- if [ -n "$env_ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$env_ADDITIONAL_HOSTS}"
- elif [ -n "$ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$ADDITIONAL_HOSTS}"
- fi
-
- if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
- echo "Deploying first release with database initialization..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="$CI_APPLICATION_REPOSITORY" \
- --set image.tag="$CI_APPLICATION_TAG" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set postgresql.imageTag="$POSTGRES_VERSION" \
- --set application.initializeCommand="$DB_INITIALIZE" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
-
- echo "Deploying second release..."
- helm upgrade --reuse-values \
- --wait \
- --set application.initializeCommand="" \
- --set application.migrateCommand="$DB_MIGRATE" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- else
- echo "Deploying new release..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="$CI_APPLICATION_REPOSITORY" \
- --set image.tag="$CI_APPLICATION_TAG" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set application.migrateCommand="$DB_MIGRATE" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
-
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
- }
-
- function scale() {
- track="${1-stable}"
- percentage="${2-100}"
- name=$(deploy_name "$track")
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm upgrade --reuse-values \
- --wait \
- --set replicaCount="$replicas" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
- }
-
- function install_dependencies() {
- apk add -U openssl curl tar gzip bash ca-certificates git
- curl -L -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- curl -L -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
- apk add glibc-2.28-r0.apk
- rm glibc-2.28-r0.apk
-
- curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
- mv linux-amd64/helm /usr/bin/
- mv linux-amd64/tiller /usr/bin/
- helm version --client
- tiller -version
-
- curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
- chmod +x /usr/bin/kubectl
- kubectl version --client
- }
-
- function setup_docker() {
- if ! docker info &>/dev/null; then
- if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
- export DOCKER_HOST='tcp://localhost:2375'
- fi
- fi
- }
-
- function setup_test_db() {
- if [ -z ${KUBERNETES_PORT+x} ]; then
- DB_HOST=postgres
- else
- DB_HOST=localhost
- fi
- export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:5432/${POSTGRES_DB}"
- }
-
- function download_chart() {
- if [[ ! -d chart ]]; then
- auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
- auto_chart_name=$(basename $auto_chart)
- auto_chart_name=${auto_chart_name%.tgz}
- auto_chart_name=${auto_chart_name%.tar.gz}
- else
- auto_chart="chart"
- auto_chart_name="chart"
- fi
-
- helm init --client-only
- helm repo add gitlab ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io}
- if [[ ! -d "$auto_chart" ]]; then
- helm fetch ${auto_chart} --untar
- fi
- if [ "$auto_chart_name" != "chart" ]; then
- mv ${auto_chart_name} chart
- fi
-
- helm dependency update chart/
- helm dependency build chart/
- }
-
- function ensure_namespace() {
- kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
- }
-
-
- # Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN
- function ensure_kube_ingress_base_domain() {
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ] && [ -n "$AUTO_DEVOPS_DOMAIN" ] ; then
- export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN
- fi
- }
-
- function check_kube_domain() {
- ensure_kube_ingress_base_domain
-
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
- echo "In order to deploy or use Review Apps,"
- echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set"
- echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
- echo "or by defining a variable at group or project level."
- echo "You can also manually add it in .gitlab-ci.yml"
- echo "AUTO_DEVOPS_DOMAIN support will be dropped on 12.0"
- false
- else
- true
- fi
- }
-
- function build() {
- registry_login
-
- if [[ -f Dockerfile ]]; then
- echo "Building Dockerfile-based application..."
- docker build \
- --build-arg HTTP_PROXY="$HTTP_PROXY" \
- --build-arg http_proxy="$http_proxy" \
- --build-arg HTTPS_PROXY="$HTTPS_PROXY" \
- --build-arg https_proxy="$https_proxy" \
- --build-arg FTP_PROXY="$FTP_PROXY" \
- --build-arg ftp_proxy="$ftp_proxy" \
- --build-arg NO_PROXY="$NO_PROXY" \
- --build-arg no_proxy="$no_proxy" \
- -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
- else
- echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
- docker run -i \
- -e BUILDPACK_URL \
- -e HTTP_PROXY \
- -e http_proxy \
- -e HTTPS_PROXY \
- -e https_proxy \
- -e FTP_PROXY \
- -e ftp_proxy \
- -e NO_PROXY \
- -e no_proxy \
- --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
-
- echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..."
- docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
- fi
-
- echo "Pushing to GitLab Container Registry..."
- docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- echo ""
- }
-
- function initialize_tiller() {
- echo "Checking Tiller..."
-
- export HELM_HOST="localhost:44134"
- tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
- echo "Tiller is listening on ${HELM_HOST}"
-
- if ! helm version --debug; then
- echo "Failed to init Tiller."
- return 1
- fi
- echo ""
- }
-
- function create_secret() {
- echo "Create secret..."
- if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
- return
- fi
-
- kubectl create secret -n "$KUBE_NAMESPACE" \
- docker-registry gitlab-registry \
- --docker-server="$CI_REGISTRY" \
- --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
- --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
- --docker-email="$GITLAB_USER_EMAIL" \
- -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
- }
-
- function dast() {
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
-
- mkdir /zap/wrk/
- /zap/zap-baseline.py -J gl-dast-report.json -t "$CI_ENVIRONMENT_URL" || true
- cp /zap/wrk/gl-dast-report.json .
- }
-
- function performance() {
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
-
- mkdir gitlab-exporter
- wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-5/index.js
-
- mkdir sitespeed-results
-
- if [ -f .gitlab-urls.txt ]
- then
- sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt
- else
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
- fi
-
- mv sitespeed-results/data/performance.json performance.json
- }
-
- function persist_environment_url() {
- echo $CI_ENVIRONMENT_URL > environment_url.txt
- }
-
- function delete() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm delete --purge "$name"
- fi
-
- secret_name=$(application_secret_name "$track")
- kubectl delete secret --ignore-not-found -n "$KUBE_NAMESPACE" "$secret_name"
- }
-
-before_script:
- - *auto_devops
diff --git a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
index 2d218b2e164..368069844ea 100644
--- a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
@@ -7,28 +7,28 @@ before_script:
- echo "Before script section"
- echo "For example you might run an update here or install a build dependency"
- echo "Or perhaps you might print out some debugging details"
-
+
after_script:
- echo "After script section"
- echo "For example you might do some cleanup here"
-
+
build1:
stage: build
script:
- echo "Do your build here"
-
+
test1:
stage: test
- script:
+ script:
- echo "Do a test here"
- echo "For example run a test suite"
-
+
test2:
stage: test
- script:
+ script:
- echo "Do another parallel test here"
- echo "For example run a lint test"
-
+
deploy1:
stage: deploy
script:
diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
index c83c49d8c95..33a2a534508 100644
--- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
@@ -7,17 +7,17 @@ build:
stage: build
# instead of calling g++ directly you can also use some build toolkit like make
# install the necessary build tools when needed
- # before_script:
- # - apt update && apt -y install make autoconf
- script:
+ # before_script:
+ # - apt update && apt -y install make autoconf
+ script:
- g++ helloworld.cpp -o mybinary
artifacts:
paths:
- mybinary
- # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
- # cache:
- # paths:
- # - "*.o"
+ # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
+ # cache:
+ # paths:
+ # - "*.o"
# run tests using the binary built before
test:
diff --git a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
index 4d5b6484d6e..5f17c93b853 100644
--- a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
@@ -14,38 +14,40 @@ variables:
KITCHEN_LOCAL_YAML: ".kitchen.dokken.yml"
stages:
+ - build
- lint
- - unit
+ - test
- functional
+ - deploy
foodcritic:
stage: lint
script:
- - chef exec foodcritic .
+ - chef exec foodcritic .
cookstyle:
stage: lint
script:
- - chef exec cookstyle .
+ - chef exec cookstyle .
chefspec:
- stage: unit
+ stage: test
script:
- - chef exec rspec spec
+ - chef exec rspec spec
# Set up your test matrix here. Example:
-#verify-centos-6:
-# stage: functional
-# before_script:
-# - apt-get update
-# - apt-get -y install rsync
-# script:
-# - kitchen verify default-centos-6 --destroy=always
-#
-#verify-centos-7:
-# stage: functional
-# before_script:
-# - apt-get update
-# - apt-get -y install rsync
-# script:
-# - kitchen verify default-centos-7 --destroy=always
+# verify-centos-6:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-6 --destroy=always
+#
+# verify-centos-7:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-7 --destroy=always
diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
index f066285b1ad..c3568c0d2c8 100644
--- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
@@ -5,10 +5,10 @@ image: clojure:lein-2.7.0
# Make sure you configure the connection as well
before_script:
- # If you need to install any external applications, like a
+ # If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
- #
- #- apt-get update -y
+ #
+ # - apt-get update -y
#
# Retrieve project dependencies
# Do this on before_script since it'll be shared between both test and
@@ -17,6 +17,6 @@ before_script:
test:
script:
- # If you need to run any migrations or configure the database, this
- # would be the point to do it.
- - lein test
+ # If you need to run any migrations or configure the database, this
+ # would be the point to do it.
+ - lein test
diff --git a/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
new file mode 100644
index 00000000000..10b25af904f
--- /dev/null
+++ b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
@@ -0,0 +1,17 @@
+code_quality:
+ image: docker:stable
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ variables:
+ DOCKER_DRIVER: overlay2
+ script:
+ - docker run
+ --env SOURCE_CODE="$PWD"
+ --volume "$PWD":/code
+ --volume /var/run/docker.sock:/var/run/docker.sock
+ "registry.gitlab.com/gitlab-org/security-products/codequality:11-8-stable" /code
+ artifacts:
+ reports:
+ codequality: gl-code-quality-report.json
+ expire_in: 1 week
diff --git a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
index 36386a19fdc..e9301a2638d 100644
--- a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
@@ -22,15 +22,15 @@ cache:
# services such as redis or postgres
before_script:
- apt-get update -qq && apt-get install -y -qq libxml2-dev
- - crystal -v # Print out Crystal version for debugging
+ - crystal -v # Print out Crystal version for debugging
- shards
# If you are using built-in Crystal Spec.
spec:
script:
- - crystal spec
+ - crystal spec
# If you are using minitest.cr
minitest:
script:
- - crystal test/spec_test.cr # change to the file(s) you execute for tests
+ - crystal test/spec_test.cr # change to the file(s) you execute for tests
diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
index 57afcbbe8b5..d35fcb0f807 100644
--- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
@@ -16,19 +16,19 @@ variables:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - ~/.cache/pip/
+ - ~/.cache/pip/
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
# To get Django tests to work you may need to create a settings file using
# the following DATABASES:
-#
+#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
@@ -46,4 +46,4 @@ test:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - python manage.py test
+ - python manage.py test
diff --git a/lib/gitlab/ci/templates/Docker.gitlab-ci.yml b/lib/gitlab/ci/templates/Docker.gitlab-ci.yml
index eeefadaa019..f6d240b7b6d 100644
--- a/lib/gitlab/ci/templates/Docker.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Docker.gitlab-ci.yml
@@ -1,14 +1,11 @@
-# Official docker image.
-image: docker:latest
-
-services:
- - docker:dind
-
-before_script:
- - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
-
build-master:
+ # Official docker image.
+ image: docker:latest
stage: build
+ services:
+ - docker:dind
+ before_script:
+ - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE" .
- docker push "$CI_REGISTRY_IMAGE"
@@ -16,7 +13,13 @@ build-master:
- master
build:
+ # Official docker image.
+ image: docker:latest
stage: build
+ services:
+ - docker:dind
+ before_script:
+ - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
diff --git a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
index cf9c731637c..4d4c6a64cd5 100644
--- a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
@@ -15,4 +15,4 @@ before_script:
mix:
script:
- - mix test
+ - mix test
diff --git a/lib/gitlab/ci/templates/Go.gitlab-ci.yml b/lib/gitlab/ci/templates/Go.gitlab-ci.yml
index d572d7a1edc..1b686bc6cc0 100644
--- a/lib/gitlab/ci/templates/Go.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Go.gitlab-ci.yml
@@ -16,20 +16,21 @@ before_script:
- cd $GOPATH/src/$REPO_NAME
stages:
- - test
- - build
+ - test
+ - build
+ - deploy
format:
- stage: test
- script:
- - go fmt $(go list ./... | grep -v /vendor/)
- - go vet $(go list ./... | grep -v /vendor/)
- - go test -race $(go list ./... | grep -v /vendor/)
+ stage: test
+ script:
+ - go fmt $(go list ./... | grep -v /vendor/)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -race $(go list ./... | grep -v /vendor/)
compile:
- stage: build
- script:
- - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary
- artifacts:
- paths:
- - mybinary
+ stage: build
+ script:
+ - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary
+ artifacts:
+ paths:
+ - mybinary
diff --git a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
index 48d98dddfad..cbf4d58bdad 100644
--- a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
@@ -23,7 +23,6 @@ build:
- build
- .gradle
-
test:
stage: test
script: gradle check
@@ -33,4 +32,3 @@ test:
paths:
- build
- .gradle
-
diff --git a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
index 7fc698d50cf..efcd1d3ddc0 100644
--- a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
@@ -13,28 +13,28 @@ image: java:8
variables:
GRAILS_VERSION: "3.1.9"
GRADLE_VERSION: "2.13"
-
+
# We use SDKMan as tool for managing versions
before_script:
- - apt-get update -qq && apt-get install -y -qq unzip
- - curl -sSL https://get.sdkman.io | bash
- - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
- - source /root/.sdkman/bin/sdkman-init.sh
- - sdk install gradle $GRADLE_VERSION < /dev/null
- - sdk use gradle $GRADLE_VERSION
-# As it's not a good idea to version gradle.properties feel free to add your
-# environments variable here
- - echo grailsVersion=$GRAILS_VERSION > gradle.properties
- - echo gradleWrapperVersion=2.14 >> gradle.properties
-# refresh dependencies from your project
- - ./gradlew --refresh-dependencies
-# Be aware that if you are using Angular profile,
-# Bower cannot be run as root if you don't allow it before.
-# Feel free to remove next line if you are not using Bower
- - echo {\"allow_root\":true} > /root/.bowerrc
+ - apt-get update -qq && apt-get install -y -qq unzip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install gradle $GRADLE_VERSION < /dev/null
+ - sdk use gradle $GRADLE_VERSION
+ # As it's not a good idea to version gradle.properties feel free to add your
+ # environments variable here
+ - echo grailsVersion=$GRAILS_VERSION > gradle.properties
+ - echo gradleWrapperVersion=2.14 >> gradle.properties
+ # refresh dependencies from your project
+ - ./gradlew --refresh-dependencies
+ # Be aware that if you are using Angular profile,
+ # Bower cannot be run as root if you don't allow it before.
+ # Feel free to remove next line if you are not using Bower
+ - echo {\"allow_root\":true} > /root/.bowerrc
# This build job does the full grails pipeline
# (compile, test, integrationTest, war, assemble).
build:
- script:
- - ./gradlew build \ No newline at end of file
+ script:
+ - ./gradlew build
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
new file mode 100644
index 00000000000..a09217e8cf0
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
@@ -0,0 +1,38 @@
+performance:
+ stage: performance
+ image: docker:stable
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
+ - export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
+ - mkdir gitlab-exporter
+ - wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-5/index.js
+ - mkdir sitespeed-results
+ - |
+ if [ -f .gitlab-urls.txt ]
+ then
+ sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt
+ else
+ docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
+ fi
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - performance.json
+ - sitespeed-results/
+ only:
+ refs:
+ - branches
+ - tags
+ kubernetes: active
+ except:
+ variables:
+ - $PERFORMANCE_DISABLED
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
new file mode 100644
index 00000000000..18f7290e1d9
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -0,0 +1,18 @@
+build:
+ stage: build
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
+ services:
+ - docker:stable-dind
+ script:
+ - |
+ if [[ -z "$CI_COMMIT_TAG" ]]; then
+ export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
+ export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
+ else
+ export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
+ export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
+ fi
+ - /build/build.sh
+ only:
+ - branches
+ - tags
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
new file mode 100644
index 00000000000..b09a24d8e22
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -0,0 +1,27 @@
+code_quality:
+ stage: test
+ image: docker:stable
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export CQ_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
+ - |
+ docker run --env SOURCE_CODE="$PWD" \
+ --volume "$PWD":/code \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/codequality:$CQ_VERSION" /code
+ artifacts:
+ paths: [gl-code-quality-report.json]
+ only:
+ - branches
+ - tags
+ except:
+ variables:
+ - $CODE_QUALITY_DISABLED
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
new file mode 100644
index 00000000000..1d55c64ec56
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -0,0 +1,570 @@
+review:
+ stage: review
+ script:
+ - check_kube_domain
+ - install_dependencies
+ - download_chart
+ - ensure_namespace
+ - initialize_tiller
+ - create_secret
+ - deploy
+ - persist_environment_url
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: http://$CI_PROJECT_ID-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
+ on_stop: stop_review
+ artifacts:
+ paths: [environment_url.txt]
+ only:
+ refs:
+ - branches
+ - tags
+ kubernetes: active
+ except:
+ refs:
+ - master
+ variables:
+ - $REVIEW_DISABLED
+
+stop_review:
+ stage: cleanup
+ variables:
+ GIT_STRATEGY: none
+ script:
+ - install_dependencies
+ - initialize_tiller
+ - delete
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ action: stop
+ when: manual
+ allow_failure: true
+ only:
+ refs:
+ - branches
+ - tags
+ kubernetes: active
+ except:
+ refs:
+ - master
+ variables:
+ - $REVIEW_DISABLED
+
+# Staging deploys are disabled by default since
+# continuous deployment to production is enabled by default
+# If you prefer to automatically deploy to staging and
+# only manually promote to production, enable this job by setting
+# STAGING_ENABLED.
+
+staging:
+ stage: staging
+ script:
+ - check_kube_domain
+ - install_dependencies
+ - download_chart
+ - ensure_namespace
+ - initialize_tiller
+ - create_secret
+ - deploy
+ environment:
+ name: staging
+ url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN
+ only:
+ refs:
+ - master
+ kubernetes: active
+ variables:
+ - $STAGING_ENABLED
+
+# Canaries are disabled by default, but if you want them,
+# and know what the downsides are, you can enable this by setting
+# CANARY_ENABLED.
+
+canary:
+ stage: canary
+ script:
+ - check_kube_domain
+ - install_dependencies
+ - download_chart
+ - ensure_namespace
+ - initialize_tiller
+ - create_secret
+ - deploy canary
+ environment:
+ name: production
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
+ when: manual
+ only:
+ refs:
+ - master
+ kubernetes: active
+ variables:
+ - $CANARY_ENABLED
+
+.production: &production_template
+ stage: production
+ script:
+ - check_kube_domain
+ - install_dependencies
+ - download_chart
+ - ensure_namespace
+ - initialize_tiller
+ - create_secret
+ - deploy
+ - delete canary
+ - delete rollout
+ - persist_environment_url
+ environment:
+ name: production
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
+ artifacts:
+ paths: [environment_url.txt]
+
+production:
+ <<: *production_template
+ only:
+ refs:
+ - master
+ kubernetes: active
+ except:
+ variables:
+ - $STAGING_ENABLED
+ - $CANARY_ENABLED
+ - $INCREMENTAL_ROLLOUT_ENABLED
+ - $INCREMENTAL_ROLLOUT_MODE
+
+production_manual:
+ <<: *production_template
+ when: manual
+ allow_failure: false
+ only:
+ refs:
+ - master
+ kubernetes: active
+ variables:
+ - $STAGING_ENABLED
+ - $CANARY_ENABLED
+ except:
+ variables:
+ - $INCREMENTAL_ROLLOUT_ENABLED
+ - $INCREMENTAL_ROLLOUT_MODE
+
+# This job implements incremental rollout on for every push to `master`.
+
+.rollout: &rollout_template
+ script:
+ - check_kube_domain
+ - install_dependencies
+ - download_chart
+ - ensure_namespace
+ - initialize_tiller
+ - create_secret
+ - deploy rollout $ROLLOUT_PERCENTAGE
+ - scale stable $((100-ROLLOUT_PERCENTAGE))
+ - delete canary
+ - persist_environment_url
+ environment:
+ name: production
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
+ artifacts:
+ paths: [environment_url.txt]
+
+.manual_rollout_template: &manual_rollout_template
+ <<: *rollout_template
+ stage: production
+ when: manual
+ # This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4)
+ only:
+ refs:
+ - master
+ kubernetes: active
+ variables:
+ - $INCREMENTAL_ROLLOUT_MODE == "manual"
+ - $INCREMENTAL_ROLLOUT_ENABLED
+ except:
+ variables:
+ - $INCREMENTAL_ROLLOUT_MODE == "timed"
+
+.timed_rollout_template: &timed_rollout_template
+ <<: *rollout_template
+ when: delayed
+ start_in: 5 minutes
+ only:
+ refs:
+ - master
+ kubernetes: active
+ variables:
+ - $INCREMENTAL_ROLLOUT_MODE == "timed"
+
+timed rollout 10%:
+ <<: *timed_rollout_template
+ stage: incremental rollout 10%
+ variables:
+ ROLLOUT_PERCENTAGE: 10
+
+timed rollout 25%:
+ <<: *timed_rollout_template
+ stage: incremental rollout 25%
+ variables:
+ ROLLOUT_PERCENTAGE: 25
+
+timed rollout 50%:
+ <<: *timed_rollout_template
+ stage: incremental rollout 50%
+ variables:
+ ROLLOUT_PERCENTAGE: 50
+
+timed rollout 100%:
+ <<: *timed_rollout_template
+ <<: *production_template
+ stage: incremental rollout 100%
+ variables:
+ ROLLOUT_PERCENTAGE: 100
+
+rollout 10%:
+ <<: *manual_rollout_template
+ variables:
+ ROLLOUT_PERCENTAGE: 10
+
+rollout 25%:
+ <<: *manual_rollout_template
+ variables:
+ ROLLOUT_PERCENTAGE: 25
+
+rollout 50%:
+ <<: *manual_rollout_template
+ variables:
+ ROLLOUT_PERCENTAGE: 50
+
+rollout 100%:
+ <<: *manual_rollout_template
+ <<: *production_template
+ allow_failure: false
+
+.deploy_helpers: &deploy_helpers |
+ [[ "$TRACE" ]] && set -x
+ auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
+ export DATABASE_URL=${DATABASE_URL-$auto_database_url}
+ export TILLER_NAMESPACE=$KUBE_NAMESPACE
+ # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
+
+ function get_replicas() {
+ track="${1:-stable}"
+ percentage="${2:-100}"
+
+ env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
+ env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
+
+ if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then
+ # for stable track get number of replicas from `PRODUCTION_REPLICAS`
+ eval new_replicas=\$${env_slug}_REPLICAS
+ if [[ -z "$new_replicas" ]]; then
+ new_replicas=$REPLICAS
+ fi
+ else
+ # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
+ eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
+ if [[ -z "$new_replicas" ]]; then
+ eval new_replicas=\${env_track}_REPLICAS
+ fi
+ fi
+
+ replicas="${new_replicas:-1}"
+ replicas="$(($replicas * $percentage / 100))"
+
+ # always return at least one replicas
+ if [[ $replicas -gt 0 ]]; then
+ echo "$replicas"
+ else
+ echo 1
+ fi
+ }
+
+ # Extracts variables prefixed with K8S_SECRET_
+ # and creates a Kubernetes secret.
+ #
+ # e.g. If we have the following environment variables:
+ # K8S_SECRET_A=value1
+ # K8S_SECRET_B=multi\ word\ value
+ #
+ # Then we will create a secret with the following key-value pairs:
+ # data:
+ # A: dmFsdWUxCg==
+ # B: bXVsdGkgd29yZCB2YWx1ZQo=
+ function create_application_secret() {
+ track="${1-stable}"
+ export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
+
+ env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
+
+ kubectl create secret \
+ -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
+ --from-env-file k8s_prefixed_variables -o yaml --dry-run |
+ kubectl replace -n "$KUBE_NAMESPACE" --force -f -
+
+ export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
+
+ rm k8s_prefixed_variables
+ }
+
+ function deploy_name() {
+ name="$CI_ENVIRONMENT_SLUG"
+ track="${1-stable}"
+
+ if [[ "$track" != "stable" ]]; then
+ name="$name-$track"
+ fi
+
+ echo $name
+ }
+
+ function application_secret_name() {
+ track="${1-stable}"
+ name=$(deploy_name "$track")
+
+ echo "${name}-secret"
+ }
+
+ function deploy() {
+ track="${1-stable}"
+ percentage="${2:-100}"
+ name=$(deploy_name "$track")
+
+ if [[ -z "$CI_COMMIT_TAG" ]]; then
+ image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
+ image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
+ else
+ image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
+ image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
+ fi
+
+ service_enabled="true"
+ postgres_enabled="$POSTGRES_ENABLED"
+
+ # if track is different than stable,
+ # re-use all attached resources
+ if [[ "$track" != "stable" ]]; then
+ service_enabled="false"
+ postgres_enabled="false"
+ fi
+
+ replicas=$(get_replicas "$track" "$percentage")
+
+ if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
+ secret_name='gitlab-registry'
+ else
+ secret_name=''
+ fi
+
+ create_application_secret "$track"
+
+ env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
+ eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
+ if [ -n "$env_ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$env_ADDITIONAL_HOSTS}"
+ elif [ -n "$ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$ADDITIONAL_HOSTS}"
+ fi
+
+ if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
+ echo "Deploying first release with database initialization..."
+ helm upgrade --install \
+ --wait \
+ --set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
+ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set image.repository="$image_repository" \
+ --set image.tag="$image_tag" \
+ --set image.pullPolicy=IfNotPresent \
+ --set image.secrets[0].name="$secret_name" \
+ --set application.track="$track" \
+ --set application.database_url="$DATABASE_URL" \
+ --set application.secretName="$APPLICATION_SECRET_NAME" \
+ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
+ --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
+ --set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
+ --set replicaCount="$replicas" \
+ --set postgresql.enabled="$postgres_enabled" \
+ --set postgresql.nameOverride="postgres" \
+ --set postgresql.postgresUser="$POSTGRES_USER" \
+ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
+ --set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
+ --set application.initializeCommand="$DB_INITIALIZE" \
+ $HELM_UPGRADE_EXTRA_ARGS \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+
+ echo "Deploying second release..."
+ helm upgrade --reuse-values \
+ --wait \
+ --set application.initializeCommand="" \
+ --set application.migrateCommand="$DB_MIGRATE" \
+ $HELM_UPGRADE_EXTRA_ARGS \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+ else
+ echo "Deploying new release..."
+ helm upgrade --install \
+ --wait \
+ --set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
+ --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set image.repository="$image_repository" \
+ --set image.tag="$image_tag" \
+ --set image.pullPolicy=IfNotPresent \
+ --set image.secrets[0].name="$secret_name" \
+ --set application.track="$track" \
+ --set application.database_url="$DATABASE_URL" \
+ --set application.secretName="$APPLICATION_SECRET_NAME" \
+ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
+ --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
+ --set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
+ --set replicaCount="$replicas" \
+ --set postgresql.enabled="$postgres_enabled" \
+ --set postgresql.nameOverride="postgres" \
+ --set postgresql.postgresUser="$POSTGRES_USER" \
+ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
+ --set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
+ --set application.migrateCommand="$DB_MIGRATE" \
+ $HELM_UPGRADE_EXTRA_ARGS \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+ fi
+
+ if [[ -z "$ROLLOUT_STATUS_DISABLED" ]]; then
+ kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
+ fi
+ }
+
+ function scale() {
+ track="${1-stable}"
+ percentage="${2-100}"
+ name=$(deploy_name "$track")
+
+ replicas=$(get_replicas "$track" "$percentage")
+
+ if [[ -n "$(helm ls -q "^$name$")" ]]; then
+ helm upgrade --reuse-values \
+ --wait \
+ --set replicaCount="$replicas" \
+ --namespace="$KUBE_NAMESPACE" \
+ "$name" \
+ chart/
+ fi
+ }
+
+ function install_dependencies() {
+ apk add -U openssl curl tar gzip bash ca-certificates git
+ curl -sSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
+ curl -sSL -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
+ apk add glibc-2.28-r0.apk
+ rm glibc-2.28-r0.apk
+
+ curl -sS "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
+ mv linux-amd64/helm /usr/bin/
+ mv linux-amd64/tiller /usr/bin/
+ helm version --client
+ tiller -version
+
+ curl -sSL -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
+ chmod +x /usr/bin/kubectl
+ kubectl version --client
+ }
+
+ function download_chart() {
+ if [[ ! -d chart ]]; then
+ auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
+ auto_chart_name=$(basename $auto_chart)
+ auto_chart_name=${auto_chart_name%.tgz}
+ auto_chart_name=${auto_chart_name%.tar.gz}
+ else
+ auto_chart="chart"
+ auto_chart_name="chart"
+ fi
+
+ helm init --client-only
+ helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"}
+ if [[ ! -d "$auto_chart" ]]; then
+ helm fetch ${auto_chart} --untar
+ fi
+ if [ "$auto_chart_name" != "chart" ]; then
+ mv ${auto_chart_name} chart
+ fi
+
+ helm dependency update chart/
+ helm dependency build chart/
+ }
+
+ function ensure_namespace() {
+ kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
+ }
+
+ function check_kube_domain() {
+ if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then
+ echo "In order to deploy or use Review Apps,"
+ echo "KUBE_INGRESS_BASE_DOMAIN variables must be set"
+ echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
+ echo "or by defining a variable at group or project level."
+ echo "You can also manually add it in .gitlab-ci.yml"
+ false
+ else
+ true
+ fi
+ }
+
+ function initialize_tiller() {
+ echo "Checking Tiller..."
+
+ export HELM_HOST="localhost:44134"
+ tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
+ echo "Tiller is listening on ${HELM_HOST}"
+
+ if ! helm version --debug; then
+ echo "Failed to init Tiller."
+ return 1
+ fi
+ echo ""
+ }
+
+ function create_secret() {
+ echo "Create secret..."
+ if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
+ return
+ fi
+
+ kubectl create secret -n "$KUBE_NAMESPACE" \
+ docker-registry gitlab-registry \
+ --docker-server="$CI_REGISTRY" \
+ --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
+ --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
+ --docker-email="$GITLAB_USER_EMAIL" \
+ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
+ }
+
+ function persist_environment_url() {
+ echo $CI_ENVIRONMENT_URL > environment_url.txt
+ }
+
+ function delete() {
+ track="${1-stable}"
+ name=$(deploy_name "$track")
+
+ if [[ -n "$(helm ls -q "^$name$")" ]]; then
+ helm delete --purge "$name"
+ fi
+
+ secret_name=$(application_secret_name "$track")
+ kubectl delete secret --ignore-not-found -n "$KUBE_NAMESPACE" "$secret_name"
+ }
+
+before_script:
+ - *deploy_helpers
diff --git a/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
new file mode 100644
index 00000000000..b9fe838d1da
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
@@ -0,0 +1,23 @@
+test:
+ services:
+ - postgres:latest
+ variables:
+ POSTGRES_DB: test
+ stage: test
+ image: gliderlabs/herokuish:latest
+ script:
+ - |
+ if [ -z ${KUBERNETES_PORT+x} ]; then
+ DB_HOST=postgres
+ else
+ DB_HOST=localhost
+ fi
+ - export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:5432/${POSTGRES_DB}"
+ - cp -R . /tmp/app
+ - /bin/herokuish buildpack test
+ only:
+ - branches
+ - tags
+ except:
+ variables:
+ - $TEST_DISABLED
diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
index 04c21b4725d..32d4e07d398 100644
--- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
@@ -20,7 +20,7 @@
# want coverage results.
- julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage"); Pkg.test("MyPackage"; coverage = true)'
# Comment out below if you do not want coverage results.
- - julia -e 'using Pkg; Pkg.add("Coverage");
+ - julia -e 'using Pkg; Pkg.add("Coverage");
import MyPackage; cd(joinpath(dirname(pathof(MyPackage)), ".."));
using Coverage; cl, tl = get_summary(process_folder());
println("(", cl/tl*100, "%) covered")'
@@ -30,7 +30,7 @@
test:0.7:
image: julia:0.7
<<: *test_definition
-
+
test:1.0:
image: julia:1.0
<<: *test_definition
@@ -55,17 +55,16 @@ pages:
image: julia:0.7
stage: deploy
script:
- - apt-get update -qq && apt-get install -y git # needed by Documenter
- - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure
- - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter
- - julia --color=yes docs/make.jl # make documentation
- - mv docs/build public # move to the directory picked up by Gitlab pages
+ - apt-get update -qq && apt-get install -y git # needed by Documenter
+ - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure
+ - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter
+ - julia --color=yes docs/make.jl # make documentation
+ - mv docs/build public # move to the directory picked up by Gitlab pages
artifacts:
paths:
- public
only:
- - master
-
+ - master
# WARNING: This template is using the `julia` images from [Docker
# Hub][3]. One can use custom Julia images and/or the official ones found
diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
index d0cad285572..9bde04dff19 100644
--- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
@@ -16,39 +16,31 @@ variables:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - vendor/
- - node_modules/
+ - vendor/
+ - node_modules/
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Prep for Node
- apt-get install gnupg -yqq
-
# Upgrade to Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
-
# Install dependencies
- apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq
-
# Install php extensions
- docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
-
# Install & enable Xdebug for code coverage reports
- pecl install xdebug
- docker-php-ext-enable xdebug
-
# Install Composer and project dependencies.
- curl -sS https://getcomposer.org/installer | php
- - php composer.phar install
-
+ - php composer.phar install
# Install Node dependencies.
# comment this out if you don't have a node dependency
- npm install
-
# Copy over testing configuration.
# Don't forget to set the database config in .env.testing correctly
# DB_HOST=mysql
@@ -56,30 +48,25 @@ before_script:
# DB_USERNAME=root
# DB_PASSWORD=secret
- cp .env.testing .env
-
# Run npm build
# comment this out if you don't have a frontend build
# you can change this to to your frontend building script like
# npm run build
- npm run dev
-
# Generate an application key. Re-cache.
- php artisan key:generate
- php artisan config:cache
-
# Run database migrations.
- php artisan migrate
-
# Run database seed
- php artisan db:seed
test:
script:
- # run laravel tests
- - php vendor/bin/phpunit --coverage-text --colors=never
-
- # run frontend tests
- # if you have any task for testing frontend
- # set it in your package.json script
- # comment this out if you don't have a frontend test
- - npm test
+ # run laravel tests
+ - php vendor/bin/phpunit --coverage-text --colors=never
+ # run frontend tests
+ # if you have any task for testing frontend
+ # set it in your package.json script
+ # comment this out if you don't have a frontend test
+ - npm test
diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
index 492b3d03db2..13ab98d3a16 100644
--- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
@@ -1,18 +1,14 @@
----
+# This file is a template, and might need editing before it works on your project.
+
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
-#
-# This template will build and test your projects as well as create the documentation.
-#
+
+# This template will build and test your projects
# * Caches downloaded dependencies and plugins between invocation.
# * Verify but don't deploy merge requests.
# * Deploy built artifacts from master branch only.
-# * Shows how to use multiple jobs in test stage for verifying functionality
-# with multiple JDKs.
-# * Uses site:stage to collect the documentation for multi-module projects.
-# * Publishes the documentation for `master` branch.
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
@@ -23,80 +19,38 @@ variables:
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
+# This template uses jdk8 for verifying and deploying images
+image: maven:3.3.9-jdk-8
+
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
-# This will only validate and compile stuff and run e.g. maven-enforcer-plugin.
-# Because some enforcer rules might check dependency convergence and class duplications
-# we use `test-compile` here instead of `validate`, so the correct classpath is picked up.
-.validate: &validate
- stage: build
- script:
- - 'mvn $MAVEN_CLI_OPTS test-compile'
-
# For merge requests do not `deploy` but only run `verify`.
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
stage: test
script:
- - 'mvn $MAVEN_CLI_OPTS verify site site:stage'
+ - 'mvn $MAVEN_CLI_OPTS verify'
except:
- master
-# Validate merge requests using JDK7
-validate:jdk7:
- <<: *validate
- image: maven:3.3.9-jdk-7
-
-# Validate merge requests using JDK8
-validate:jdk8:
- <<: *validate
- image: maven:3.3.9-jdk-8
-
-# Verify merge requests using JDK7
-verify:jdk7:
- <<: *verify
- image: maven:3.3.9-jdk-7
-
# Verify merge requests using JDK8
verify:jdk8:
<<: *verify
- image: maven:3.3.9-jdk-8
-
+# To deploy packages from CI, create a ci_settings.xml file
+# For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details.
+# Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate.
# For `master` branch run `mvn deploy` automatically.
-# Here you need to decide whether you want to use JDK7 or 8.
-# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner.
-# Mount your `settings.xml` as `/root/.m2/settings.xml` which holds your secrets.
-# See https://maven.apache.org/settings.html
deploy:jdk8:
- # Use stage test here, so the pages job may later pickup the created site.
- stage: test
- script:
- - 'mvn $MAVEN_CLI_OPTS deploy site site:stage'
- only:
- - master
- # Archive up the built documentation site.
- artifacts:
- paths:
- - target/staging
- image: maven:3.3.9-jdk-8
-
-
-pages:
- image: busybox:latest
stage: deploy
script:
- # Because Maven appends the artifactId automatically to the staging path if you did define a parent pom,
- # you might need to use `mv target/staging/YOUR_ARTIFACT_ID public` instead.
- - mv target/staging public
- dependencies:
- - deploy:jdk8
- artifacts:
- paths:
- - public
+ - if [ ! -f ci_settings.xml ];
+ then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for instructions.";
+ fi
+ - 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml'
only:
- master
diff --git a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
index 3585f99760f..10fb6be6c39 100644
--- a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
@@ -15,6 +15,7 @@
image: mono:latest
stages:
+ - build
- test
- deploy
@@ -32,11 +33,11 @@ release:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Release" /p:Platform="Any CPU"
/p:OutputPath="./../../build/release/" "MyProject.sln"
-
+
debug:
stage: test
script:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Debug" /p:Platform="Any CPU"
/p:OutputPath="./../../build/debug/" "MyProject.sln"
- - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll \ No newline at end of file
+ - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll
diff --git a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
index 41de1458582..b87178141a1 100644
--- a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
@@ -14,14 +14,14 @@ services:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - node_modules/
+ - node_modules/
test_async:
script:
- - npm install
- - node ./specs/start.js ./specs/async.spec.js
+ - npm install
+ - node ./specs/start.js ./specs/async.spec.js
test_db:
script:
- - npm install
- - node ./specs/start.js ./specs/db-postgres.spec.js
+ - npm install
+ - node ./specs/start.js ./specs/db-postgres.spec.js
diff --git a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
index 290b9997084..65abee1f5eb 100644
--- a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
@@ -1,7 +1,9 @@
image: ayufan/openshift-cli
stages:
+ - build # dummy stage to follow the template guidelines
- test
+ - deploy # dummy stage to follow the template guidelines
- review
- staging
- production
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index 33f44ee9222..b9fee2d5731 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -4,19 +4,19 @@ image: php:7.1.1
# Select what we should cache between builds
cache:
paths:
- - vendor/
+ - vendor/
before_script:
-- apt-get update -yqq
-- apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
-# Install PHP extensions
-- docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache
-# Install & enable Xdebug for code coverage reports
-- pecl install xdebug
-- docker-php-ext-enable xdebug
-# Install and run Composer
-- curl -sS https://getcomposer.org/installer | php
-- php composer.phar install
+ - apt-get update -yqq
+ - apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
+ # Install PHP extensions
+ - docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache
+ # Install & enable Xdebug for code coverage reports
+ - pecl install xdebug
+ - docker-php-ext-enable xdebug
+ # Install and run Composer
+ - curl -sS https://getcomposer.org/installer | php
+ - php composer.phar install
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
# See http://docs.gitlab.com/ce/ci/services/README.html for examples.
@@ -33,4 +33,4 @@ variables:
# If Xdebug was installed you can generate a coverage report and see code coverage metrics.
test:
script:
- - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never \ No newline at end of file
+ - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
diff --git a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
index fa296057c72..83e179f37c3 100644
--- a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
@@ -9,6 +9,8 @@ before_script:
stages:
- validate
+ - build
+ - test
- deploy
validate:
diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
index 7fcc0b436b5..d2dd3fbfb75 100644
--- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
@@ -4,13 +4,12 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules/
-
+ - node_modules/
script:
- - npm install -g brunch
- - brunch build --production
+ - npm install -g brunch
+ - brunch build --production
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
index 791afdd23f1..ba422c08614 100644
--- a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
@@ -3,11 +3,11 @@ image: alpine
pages:
script:
- - apk update && apk add doxygen
- - doxygen doxygen/Doxyfile
- - mv doxygen/documentation/html/ public/
+ - apk update && apk add doxygen
+ - doxygen doxygen/Doxyfile
+ - mv doxygen/documentation/html/ public/
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
index 9df2a4797b2..a683561a455 100644
--- a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
@@ -4,14 +4,14 @@ image: node:latest
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - node_modules/
+ - node_modules/
pages:
script:
- - yarn install
- - ./node_modules/.bin/gatsby build --prefix-paths
+ - yarn install
+ - ./node_modules/.bin/gatsby build --prefix-paths
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
index 249a168aa33..92f25280c6e 100644
--- a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
@@ -2,11 +2,11 @@
pages:
stage: deploy
script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
index dd3ef149668..0e206423fa5 100644
--- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
@@ -4,13 +4,12 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules
-
+ - node_modules
script:
- - npm install -g harp
- - harp compile ./ public
+ - npm install -g harp
+ - harp compile ./ public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
index 02d02250bbf..d91a8d7421f 100644
--- a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
@@ -1,16 +1,17 @@
# Full project: https://gitlab.com/pages/hexo
-image: node:6.10.0
+image: node:10.15.3
pages:
script:
- - npm install
- - ./node_modules/hexo/bin/hexo generate
+ - npm install hexo-cli -g
+ - test -e package.json && npm install
+ - hexo generate
artifacts:
paths:
- - public
+ - public
cache:
paths:
- node_modules
key: project
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
index b8cfb0f56f6..9a3ecd1c34f 100644
--- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
@@ -3,15 +3,15 @@ image: dettmering/hugo-build
pages:
script:
- - hugo
+ - hugo
artifacts:
paths:
- - public
+ - public
only:
- - master
-
+ - master
+
test:
script:
- - hugo
+ - hugo
except:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
index f5b40f2b9f1..7a441a2f70f 100644
--- a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
@@ -3,7 +3,7 @@ image: python:2.7
cache:
paths:
- - vendor/
+ - vendor/
test:
stage: test
@@ -20,6 +20,6 @@ pages:
- hyde gen -d public
artifacts:
paths:
- - public
+ - public
only:
- master
diff --git a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
index 7abfaf53e8e..886b6c36249 100644
--- a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
@@ -4,29 +4,28 @@
# JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers
#
# This yml works with jBake 2.5.1
-# Feel free to change JBAKE_VERSION version
+# Feel free to change JBAKE_VERSION version
#
# HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/
image: java:8
variables:
- JBAKE_VERSION: 2.5.1
-
+ JBAKE_VERSION: 2.5.1
# We use SDKMan as tool for managing versions
before_script:
- - apt-get update -qq && apt-get install -y -qq unzip zip
- - curl -sSL https://get.sdkman.io | bash
- - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
- - source /root/.sdkman/bin/sdkman-init.sh
- - sdk install jbake $JBAKE_VERSION < /dev/null
- - sdk use jbake $JBAKE_VERSION
+ - apt-get update -qq && apt-get install -y -qq unzip zip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install jbake $JBAKE_VERSION < /dev/null
+ - sdk use jbake $JBAKE_VERSION
# This build job produced the output directory of your site
pages:
- script:
- - jbake . public
- artifacts:
- paths:
- - public \ No newline at end of file
+ script:
+ - jbake . public
+ artifacts:
+ paths:
+ - public
diff --git a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
index 37f50554036..0d742aa282d 100644
--- a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
@@ -6,25 +6,24 @@ variables:
JEKYLL_ENV: production
before_script:
-- bundle install
+ - bundle install
test:
stage: test
script:
- - bundle exec jekyll build -d test
+ - bundle exec jekyll build -d test
artifacts:
paths:
- - test
+ - test
except:
- - master
+ - master
pages:
stage: deploy
script:
- - bundle exec jekyll build -d public
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
only:
- - master
-
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
index 0e5fb410a4e..2d26b86a328 100644
--- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
@@ -7,36 +7,31 @@ image: php:7.2
# These folders are cached between builds
cache:
paths:
- - vendor/
- - node_modules/
+ - vendor/
+ - node_modules/
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Install dependencies
- apt-get install -yqq gnupg zlib1g-dev libpng-dev
-
# Install Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
- apt-get install -yqq nodejs
-
# Install php extensions
- docker-php-ext-install zip
-
- # Install Composer and project dependencies.
+ # Install Composer and project dependencies
- curl -sS https://getcomposer.org/installer | php
- - php composer.phar install
-
- # Install Node dependencies.
+ - php composer.phar install
+ # Install Node dependencies
- npm install
pages:
script:
- - npm run production
- - mv build_production public
+ - npm run production
+ - mv build_production public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
index c5c44a5d86c..93ab8e0be0d 100644
--- a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
@@ -3,10 +3,10 @@ image: python:2.7
pages:
script:
- - pip install lektor
- - lektor build --output-path public
+ - pip install lektor
+ - lektor build --output-path public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
index 50e8b7ccd46..6524405133a 100644
--- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
@@ -4,14 +4,13 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules/
-
+ - node_modules/
script:
- - npm install -g metalsmith
- - npm install
- - make build
+ - npm install -g metalsmith
+ - npm install
+ - make build
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
index 9f4cc0574d6..57ac323dfdf 100644
--- a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
@@ -3,25 +3,25 @@ image: ruby:2.3
cache:
paths:
- - vendor
+ - vendor
test:
script:
- - apt-get update -yqqq
- - apt-get install -y nodejs
- - bundle install --path vendor
- - bundle exec middleman build
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
except:
- master
pages:
script:
- - apt-get update -yqqq
- - apt-get install -y nodejs
- - bundle install --path vendor
- - bundle exec middleman build
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
index b469b316ba5..7f037b5f5cf 100644
--- a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
@@ -3,10 +3,10 @@ image: ruby:2.3
pages:
script:
- - bundle install -j4
- - nanoc
+ - bundle install -j4
+ - nanoc
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
index 4762ec9acfd..6d912a89bc1 100644
--- a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
@@ -3,13 +3,13 @@ image: ruby:2.3
pages:
script:
- - apt-get update -qq && apt-get install -qq nodejs
- - bundle install -j4
- - bundle exec rake generate
- - mv public .public
- - mv .public/octopress public
+ - apt-get update -qq && apt-get install -qq nodejs
+ - bundle install -j4
+ - bundle exec rake generate
+ - mv public .public
+ - mv .public/octopress public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
index c5f3154f587..09c6649fc13 100644
--- a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
@@ -3,8 +3,8 @@ image: python:2.7-alpine
pages:
script:
- - pip install -r requirements.txt
- - pelican -s publishconf.py
+ - pip install -r requirements.txt
+ - pelican -s publishconf.py
artifacts:
paths:
- - public/
+ - public/
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index 098abe4daf5..00b8b94b574 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -18,34 +18,34 @@ cache:
- venv/
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
test:
script:
- - python setup.py test
- - pip install tox flake8 # you can also use tox
- - tox -e py36,flake8
+ - python setup.py test
+ - pip install tox flake8 # you can also use tox
+ - tox -e py36,flake8
run:
script:
- - python setup.py bdist_wheel
- # an alternative approach is to install and run:
- - pip install dist/*
- # run the command here
+ - python setup.py bdist_wheel
+ # an alternative approach is to install and run:
+ - pip install dist/*
+ # run the command here
artifacts:
paths:
- - dist/*.whl
+ - dist/*.whl
pages:
script:
- - pip install sphinx sphinx-rtd-theme
- - cd doc ; make html
- - mv build/html/ ../public/
+ - pip install sphinx sphinx-rtd-theme
+ - cd doc ; make html
+ - mv build/html/ ../public/
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
index 0d12cbc6460..b3cad8b858a 100644
--- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - ruby -v # Print out ruby version for debugging
+ - ruby -v # Print out ruby version for debugging
# Uncomment next line if your rails app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
@@ -29,19 +29,19 @@ before_script:
# Optional - Delete if not using `rubocop`
rubocop:
script:
- - rubocop
+ - rubocop
rspec:
script:
- - rspec spec
+ - rspec spec
rails:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - rails db:migrate
- - rails db:seed
- - rails test
+ - rails db:migrate
+ - rails db:seed
+ - rails test
# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk
# are supported too: https://github.com/travis-ci/dpl
@@ -49,5 +49,5 @@ deploy:
type: deploy
environment: production
script:
- - gem install dpl
- - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
index cab087c48c7..a25dc38e4e7 100644
--- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
@@ -5,19 +5,19 @@ image: "rust:latest"
# Optional: Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
-#services:
-# - mysql:latest
-# - redis:latest
-# - postgres:latest
+# services:
+# - mysql:latest
+# - redis:latest
+# - postgres:latest
# Optional: Install a C compiler, cmake and git into the container.
# You will often need this when you (or any of your dependencies) depends on C code.
-#before_script:
-#- apt-get update -yqq
-#- apt-get install -yqq --no-install-recommends build-essential
+# before_script:
+# - apt-get update -yqq
+# - apt-get install -yqq --no-install-recommends build-essential
# Use cargo to test the project
test:cargo:
script:
- - rustc --version && cargo --version # Print version info for debugging
- - cargo test --all --verbose
+ - rustc --version && cargo --version # Print version info for debugging
+ - cargo test --all --verbose
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
new file mode 100644
index 00000000000..5372ec6cceb
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -0,0 +1,63 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html
+
+container_scanning:
+ stage: test
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ # Defining two new variables based on GitLab's CI/CD predefined variables
+ # https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
+ CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+ CI_APPLICATION_TAG: $CI_COMMIT_SHA
+ # Prior to this, you need to have the Container Registry running for your project and setup a build job
+ # with at least the following steps:
+ #
+ # docker build -t $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG .
+ # docker push $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
+ #
+ # Container Scanning deals with Docker images only so no need to import the project's Git repository:
+ GIT_STRATEGY: none
+ # Services and containers running in the same Kubernetes pod are all sharing the same localhost address
+ # https://docs.gitlab.com/runner/executors/kubernetes.html
+ DOCKER_SERVICE: docker
+ DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
+ # https://hub.docker.com/r/arminc/clair-local-scan/tags
+ CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ ## Disable the proxy for clair-local-scan, otherwise Container Scanning will
+ ## fail when a proxy is used.
+ NO_PROXY: ${DOCKER_SERVICE},localhost
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then { export DOCKER_SERVICE="localhost" ; export DOCKER_HOST="tcp://${DOCKER_SERVICE}:2375" ; } fi
+ - |
+ if [[ -n "$CI_REGISTRY_USER" ]]; then
+ echo "Logging to GitLab Container Registry with CI credentials..."
+ docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
+ echo ""
+ fi
+ - docker run -d --name db arminc/clair-db:latest
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
+ - apk add -U wget ca-certificates
+ - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - mv clair-scanner_linux_amd64 clair-scanner
+ - chmod +x clair-scanner
+ - touch clair-whitelist.yml
+ - retries=0
+ - echo "Waiting for clair daemon to start"
+ - while( ! wget -T 10 -q -O /dev/null http://${DOCKER_SERVICE}:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
+ - ./clair-scanner -c http://${DOCKER_SERVICE}:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ artifacts:
+ reports:
+ container_scanning: gl-container-scanning-report.json
+ dependencies: []
+ only:
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bcontainer_scanning\b/
+ except:
+ variables:
+ - $CONTAINER_SCANNING_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
new file mode 100644
index 00000000000..4b55ffd3771
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -0,0 +1,48 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
+
+# Configure the scanning tool through the environment variables.
+# List of the variables: https://gitlab.com/gitlab-org/security-products/dast#settings
+# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
+
+stages:
+ - build
+ - test
+ - deploy
+ - dast
+
+dast:
+ stage: dast
+ image:
+ name: "registry.gitlab.com/gitlab-org/security-products/dast:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
+ variables:
+ # URL to scan:
+ # DAST_WEBSITE: https://example.com/
+ #
+ # Time limit for target availability (scan is attempted even when timeout):
+ # DAST_TARGET_AVAILABILITY_TIMEOUT: 60
+ #
+ # Set these variables to scan with an authenticated user:
+ # DAST_AUTH_URL: https://example.com/sign-in
+ # DAST_USERNAME: john.doe@example.com
+ # DAST_PASSWORD: john-doe-password
+ # DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
+ # DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
+ # DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional: URLs to skip during the authenticated scan; comma-separated, no spaces in between
+ #
+ # Perform ZAP Full Scan, which includes both passive and active scanning:
+ # DAST_FULL_SCAN_ENABLED: "true"
+ allow_failure: true
+ script:
+ - export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
+ - /analyze -t $DAST_WEBSITE
+ artifacts:
+ reports:
+ dast: gl-dast-report.json
+ only:
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bdast\b/
+ except:
+ variables:
+ - $DAST_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
new file mode 100644
index 00000000000..8dd9775c583
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -0,0 +1,58 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/dependency_scanning.html
+#
+# Configure the scanning tool through the environment variables.
+# List of the variables: https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings
+# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
+
+dependency_scanning:
+ stage: test
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
+ - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
+ function propagate_env_vars() {
+ CURRENT_ENV=$(printenv)
+
+ for VAR_NAME; do
+ echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
+ done
+ }
+ - |
+ docker run \
+ $(propagate_env_vars \
+ DS_ANALYZER_IMAGES \
+ DS_ANALYZER_IMAGE_PREFIX \
+ DS_ANALYZER_IMAGE_TAG \
+ DS_DEFAULT_ANALYZERS \
+ DS_EXCLUDED_PATHS \
+ DEP_SCAN_DISABLE_REMOTE_CHECKS \
+ DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ DS_PULL_ANALYZER_IMAGE_TIMEOUT \
+ DS_RUN_ANALYZER_TIMEOUT \
+ ) \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code
+ artifacts:
+ reports:
+ dependency_scanning: gl-dependency-scanning-report.json
+ dependencies: []
+ only:
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bdependency_scanning\b/
+ except:
+ variables:
+ - $DEPENDENCY_SCANNING_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
new file mode 100644
index 00000000000..b6555e627a1
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html
+
+variables:
+ LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
+
+license_management:
+ stage: test
+ image:
+ name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
+ entrypoint: [""]
+ variables:
+ SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD
+ allow_failure: true
+ script:
+ - /run.sh analyze .
+ artifacts:
+ reports:
+ license_management: gl-license-management-report.json
+ dependencies: []
+ only:
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\blicense_management\b/
+ except:
+ variables:
+ - $LICENSE_MANAGEMENT_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
new file mode 100644
index 00000000000..abf16e5b2e7
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -0,0 +1,62 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/sast.html
+#
+# Configure the scanning tool through the environment variables.
+# List of the variables: https://gitlab.com/gitlab-org/security-products/sast#settings
+# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
+
+sast:
+ stage: test
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
+ - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
+ function propagate_env_vars() {
+ CURRENT_ENV=$(printenv)
+
+ for VAR_NAME; do
+ echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
+ done
+ }
+ - |
+ docker run \
+ $(propagate_env_vars \
+ SAST_ANALYZER_IMAGES \
+ SAST_ANALYZER_IMAGE_PREFIX \
+ SAST_ANALYZER_IMAGE_TAG \
+ SAST_DEFAULT_ANALYZERS \
+ SAST_EXCLUDED_PATHS \
+ SAST_BANDIT_EXCLUDED_PATHS \
+ SAST_BRAKEMAN_LEVEL \
+ SAST_GOSEC_LEVEL \
+ SAST_FLAWFINDER_LEVEL \
+ SAST_GITLEAKS_ENTROPY_LEVEL \
+ SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
+ SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
+ SAST_RUN_ANALYZER_TIMEOUT \
+ ) \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
+ artifacts:
+ reports:
+ sast: gl-sast-report.json
+ dependencies: []
+ only:
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bsast\b/
+ except:
+ variables:
+ - $SAST_DISABLED
diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
new file mode 100644
index 00000000000..3f46bb89e94
--- /dev/null
+++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
@@ -0,0 +1,33 @@
+# GitLab Serverless template
+
+image: alpine:latest
+
+stages:
+ - build
+ - test
+ - deploy
+
+.serverless:build:image:
+ stage: build
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ script: /usr/bin/gitlabktl app build
+
+.serverless:deploy:image:
+ stage: deploy
+ image: gcr.io/triggermesh/tm@sha256:3cfdd470a66b741004fb02354319d79f1598c70117ce79978d2e07e192bfb336 # v0.0.11
+ environment: development
+ script:
+ - echo "$CI_REGISTRY_IMAGE"
+ - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
+
+.serverless:build:functions:
+ stage: build
+ environment: development
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ script: /usr/bin/gitlabktl serverless build
+
+.serverless:deploy:functions:
+ stage: deploy
+ environment: development
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ script: /usr/bin/gitlabktl serverless deploy
diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
index ba8a802ba4f..ffed7a0fec2 100644
--- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
@@ -2,7 +2,9 @@
# This file assumes an own GitLab CI runner, setup on a macOS system.
stages:
- build
+ - test
- archive
+ - deploy
build_project:
stage: build
@@ -23,7 +25,7 @@ archive_project:
- master
artifacts:
paths:
- - build/ProjectName.ipa
+ - build/ProjectName.ipa
tags:
- ios_11-3
- xcode_9-3
diff --git a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
index 7160fce26a8..f374bc7e26a 100644
--- a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
@@ -24,6 +24,7 @@ before_script:
stages:
- validate
- build
+ - test
- deploy
validate:
diff --git a/lib/gitlab/ci/templates/dotNET-Core.yml b/lib/gitlab/ci/templates/dotNET-Core.yml
new file mode 100644
index 00000000000..40ca296d7bd
--- /dev/null
+++ b/lib/gitlab/ci/templates/dotNET-Core.yml
@@ -0,0 +1,117 @@
+---
+# This is a simple example illustrating how to build and test .NET Core project
+# with GitLab Continuous Integration / Continuous Delivery.
+
+# ### Specify the Docker image
+#
+# Instead of installing .NET Core SDK manually, a docker image is used
+# with already pre-installed .NET Core SDK.
+#
+# The 'latest' tag targets the latest available version of .NET Core SDK image.
+# If preferred, you can explicitly specify version of .NET Core (e.g. using '2.2-sdk' tag).
+#
+# See other available tags for .NET Core: https://hub.docker.com/r/microsoft/dotnet
+# Learn more about Docker tags: https://docs.docker.com/glossary/?term=tag
+# and the Docker itself: https://opensource.com/resources/what-docker
+image: microsoft/dotnet:latest
+
+# ### Define variables
+#
+variables:
+ # 1) Name of directory where restore and build objects are stored.
+ OBJECTS_DIRECTORY: 'obj'
+ # 2) Name of directory used for keeping restored dependencies.
+ NUGET_PACKAGES_DIRECTORY: '.nuget'
+ # 3) A relative path to the source code from project repository root.
+ # NOTE: Please edit this path so it matches the structure of your project!
+ SOURCE_CODE_PATH: '*/*/'
+
+# ### Define stage list
+#
+# In this example there are only two stages.
+# Initially, the project will be built and then tested.
+stages:
+ - build
+ - test
+
+# ### Define global cache rule
+#
+# Before building the project, all dependencies (e.g. third-party NuGet packages)
+# must be restored. Jobs on GitLab.com's Shared Runners are executed on autoscaled machines.
+#
+# Each machine is used only once (for security reasons) and after that is removed.
+# This means that, before every job, a dependency restore must be performed
+# because restored dependencies are removed along with machines. Fortunately,
+# GitLab provides cache mechanism with the aim of keeping restored dependencies
+# for other jobs.
+#
+# This example shows how to configure cache to pass over restored
+# dependencies for re-use.
+#
+# With global cache rule, cached dependencies will be downloaded before every job
+# and then unpacked to the paths as specified below.
+cache:
+ # Per-stage and per-branch caching.
+ key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
+ paths:
+ # Specify three paths that should be cached:
+ #
+ # 1) Main JSON file holding information about package dependency tree, packages versions,
+ # frameworks etc. It also holds information where to the dependencies were restored.
+ - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
+ # 2) Other NuGet and MSBuild related files. Also needed.
+ - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
+ # 3) Path to the directory where restored dependencies are kept.
+ - '$NUGET_PACKAGES_DIRECTORY'
+ #
+ # 'pull-push' policy means that latest cache will be downloaded (if it exists)
+ # before executing the job, and a newer version will be uploaded afterwards.
+ # Such a setting saves time when there are no changes in referenced third-party
+ # packages.
+ #
+ # For example, if you run a pipeline with changes in your code,
+ # but with no changes within third-party packages which your project is using,
+ # then project restore will happen quickly as all required dependencies
+ # will already be there — unzipped from cache.
+
+ # 'pull-push' policy is the default cache policy, you do not have to specify it explicitly.
+ policy: pull-push
+
+# ### Restore project dependencies
+#
+# NuGet packages by default are restored to '.nuget/packages' directory
+# in the user's home directory. That directory is out of scope of GitLab caching.
+#
+# To get around this, a custom path can be specified using the '--packages <PATH>' option
+# for 'dotnet restore' command. In this example, a temporary directory is created
+# in the root of project repository, so its content can be cached.
+#
+# Learn more about GitLab cache: https://docs.gitlab.com/ee/ci/caching/index.html
+before_script:
+ - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
+
+build:
+ stage: build
+ # ### Build all projects discovered from solution file.
+ #
+ # Note: this will fail if you have any projects in your solution that are not
+ # .NET Core-based projects (e.g. WCF service), which is based on .NET Framework,
+ # not .NET Core. In this scenario, you will need to build every .NET Core-based
+ # project by explicitly specifying a relative path to the directory
+ # where it is located (e.g. 'dotnet build ./src/ConsoleApp').
+ # Only one project path can be passed as a parameter to 'dotnet build' command.
+ script:
+ - 'dotnet build --no-restore'
+
+tests:
+ stage: test
+ # ### Run the tests
+ #
+ # You can either run tests for all test projects that are defined in your solution
+ # with 'dotnet test' or run tests only for specific project by specifying
+ # a relative path to the directory where it is located (e.g. 'dotnet test ./test/UnitTests').
+ #
+ # You may want to define separate testing jobs for different types of testing
+ # (e.g. integration tests, unit tests etc).
+ script:
+ - 'dotnet test --no-restore'
diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
index 25a32ba0f74..b29f45323f5 100644
--- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
@@ -3,12 +3,12 @@
# (located in gitlab-runner.exe directory):
# shell = "powershell"
#
-# The script is composed of 3 stages: build, test and deploy.
+# The script is composed of 3 stages: build, test and deploy.
#
# The build stage restores NuGet packages and uses msbuild to build the exe and msi
# One major issue you'll find is that you can't build msi projects from command line
# if you use vdproj. There are workarounds building msi via devenv, but they rarely work
-# The best solution is migrating your vdproj projects to WiX, as it can be build directly
+# The best solution is migrating your vdproj projects to WiX, as it can be build directly
# by msbuild.
#
# The test stage runs nunit from command line against Test project inside your solution
@@ -26,7 +26,6 @@ variables:
MSI_RELEASE_FOLDER: 'Setup\bin\Release'
TEST_FOLDER: 'Tests\bin\Release'
DEPLOY_FOLDER: 'P:\Projects\YourApp\Builds'
-
NUGET_PATH: 'C:\NuGet\nuget.exe'
MSBUILD_PATH: 'C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild.exe'
NUNIT_PATH: 'C:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe'
@@ -39,28 +38,28 @@ stages:
build_job:
stage: build
only:
- - tags # the build process will only be started by git tag commits
+ - tags # the build process will only be started by git tag commits
script:
- - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies
- - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project
+ - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies
+ - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project
artifacts:
- expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
+ expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
paths:
- - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder
- - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder
- - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests
+ - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder
+ - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder
+ - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests
test_job:
stage: test
only:
- tags
script:
- - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests
+ - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests
artifacts:
- when: always # save test results even when the task fails
- expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
+ when: always # save test results even when the task fails
+ expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
paths:
- - '.\TestResult.xml' # saving NUnit results to copy to deploy folder
+ - '.\TestResult.xml' # saving NUnit results to copy to deploy folder
dependencies:
- build_job
@@ -80,8 +79,6 @@ deploy_job:
- 'xcopy /y ".\$env:EXE_RELEASE_FOLDER\YourApp.exe" "$deployFolder"'
- 'xcopy /y ".\$env:MSI_RELEASE_FOLDER\YourApp Setup.msi" "$deployFolder"'
- 'xcopy /y ".\TestResult.xml" "$deployFolder"'
-
dependencies:
- build_job
- test_job
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
new file mode 100644
index 00000000000..87aea8527d1
--- /dev/null
+++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
@@ -0,0 +1,30 @@
+# This is a very simple template that mainly relies on FastLane to build and distribute your app.
+# Read more about how to use this template on the blog post https://about.gitlab.com/2019/03/06/ios-publishing-with-gitlab-and-fastlane/
+# You will also need fastlane and signing configuration for this to work, along with a MacOS runner.
+# These details are provided in the blog post.
+
+# Note that when you're using the shell executor for MacOS builds, the
+# build and tests run as the identity of the runner logged in user, directly on
+# the build host. This is less secure than using container executors, so please
+# take a look at our security implications documentation at
+# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional
+# detail on what to keep in mind in this scenario.
+
+stages:
+ - build
+ - test
+ - deploy
+
+variables:
+ LC_ALL: "en_US.UTF-8"
+ LANG: "en_US.UTF-8"
+ GIT_STRATEGY: clone
+
+build:
+ stage: build
+ script:
+ - bundle install
+ - bundle exec fastlane build
+ artifacts:
+ paths:
+ - ./*.ipa
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index bf5f2a31f0e..dfae260239e 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -209,10 +209,7 @@ module Gitlab
end
def paths
- [
- default_path,
- deprecated_path
- ].compact
+ [default_path]
end
def default_directory
@@ -227,15 +224,6 @@ module Gitlab
File.join(default_directory, "#{job.id}.log")
end
- def deprecated_path
- File.join(
- Settings.gitlab_ci.builds_path,
- job.created_at.utc.strftime("%Y_%m"),
- job.project.ci_id.to_s,
- "#{job.id}.log"
- ) if job.project&.ci_id
- end
-
def trace_artifact
job.job_artifacts_trace
end
diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb
index a7b4e0348c2..f7bbb58df7e 100644
--- a/lib/gitlab/ci/variables/collection.rb
+++ b/lib/gitlab/ci/variables/collection.rb
@@ -17,6 +17,8 @@ module Gitlab
end
def concat(resources)
+ return self if resources.nil?
+
tap { resources.each { |variable| self.append(variable) } }
end
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
index e3e4e62cc02..aab10aef398 100644
--- a/lib/gitlab/ci/variables/collection/item.rb
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -5,12 +5,12 @@ module Gitlab
module Variables
class Collection
class Item
- def initialize(key:, value:, public: true, file: false)
+ def initialize(key:, value:, public: true, file: false, masked: false)
raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless
value.is_a?(String) || value.nil?
@variable = {
- key: key, value: value, public: public, file: file
+ key: key, value: value, public: public, file: file, masked: masked
}
end
diff --git a/lib/gitlab/cluster/lifecycle_events.rb b/lib/gitlab/cluster/lifecycle_events.rb
index b05dca409d1..e0f9eb59924 100644
--- a/lib/gitlab/cluster/lifecycle_events.rb
+++ b/lib/gitlab/cluster/lifecycle_events.rb
@@ -44,6 +44,14 @@ module Gitlab
(@master_restart_hooks ||= []) << block
end
+ def on_master_start(&block)
+ if in_clustered_environment?
+ on_before_fork(&block)
+ else
+ on_worker_start(&block)
+ end
+ end
+
#
# Lifecycle integration methods (called from unicorn.rb, puma.rb, etc.)
#
diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb
index 37ba16dba25..6667a5d3d33 100644
--- a/lib/gitlab/config/entry/configurable.rb
+++ b/lib/gitlab/config/entry/configurable.rb
@@ -21,7 +21,7 @@ module Gitlab
include Validatable
validations do
- validates :config, type: Hash
+ validates :config, type: Hash, unless: :skip_config_hash_validation?
end
end
@@ -30,6 +30,10 @@ module Gitlab
return unless valid?
self.class.nodes.each do |key, factory|
+ # If we override the config type validation
+ # we can end with different config types like String
+ next unless config.is_a?(Hash)
+
factory
.value(config[key])
.with(key: key, parent: self)
@@ -45,6 +49,10 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ def skip_config_hash_validation?
+ false
+ end
+
class_methods do
def nodes
Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
diff --git a/lib/gitlab/config/entry/factory.rb b/lib/gitlab/config/entry/factory.rb
index 79f9ff32514..3c06b1e0d24 100644
--- a/lib/gitlab/config/entry/factory.rb
+++ b/lib/gitlab/config/entry/factory.rb
@@ -61,7 +61,7 @@ module Gitlab
end
def fabricate(entry, value = nil)
- entry.new(value, @metadata).tap do |node|
+ entry.new(value, @metadata) do |node|
node.key = @attributes[:key]
node.parent = @attributes[:parent]
node.default = @attributes[:default]
diff --git a/lib/gitlab/config/entry/legacy_validation_helpers.rb b/lib/gitlab/config/entry/legacy_validation_helpers.rb
index d3ab5625743..0a629075302 100644
--- a/lib/gitlab/config/entry/legacy_validation_helpers.rb
+++ b/lib/gitlab/config/entry/legacy_validation_helpers.rb
@@ -45,17 +45,15 @@ module Gitlab
end
def validate_regexp(value)
- !value.nil? && Regexp.new(value.to_s) && true
- rescue RegexpError, TypeError
- false
+ Gitlab::UntrustedRegexp::RubySyntax.valid?(value)
end
def validate_string_or_regexp(value)
return true if value.is_a?(Symbol)
return false unless value.is_a?(String)
- if value.first == '/' && value.last == '/'
- validate_regexp(value[1...-1])
+ if Gitlab::UntrustedRegexp::RubySyntax.matches_syntax?(value)
+ validate_regexp(value)
else
true
end
diff --git a/lib/gitlab/config/entry/node.rb b/lib/gitlab/config/entry/node.rb
index 9999ab4ff95..e014f15fbd8 100644
--- a/lib/gitlab/config/entry/node.rb
+++ b/lib/gitlab/config/entry/node.rb
@@ -17,6 +17,8 @@ module Gitlab
@metadata = metadata
@entries = {}
+ yield(self) if block_given?
+
self.class.aspects.to_a.each do |aspect|
instance_exec(&aspect)
end
@@ -44,6 +46,12 @@ module Gitlab
@parent ? @parent.ancestors + [@parent] : []
end
+ def opt(key)
+ opt = metadata[key]
+ opt = @parent.opt(key) if opt.nil? && @parent
+ opt
+ end
+
def valid?
errors.none?
end
@@ -85,6 +93,18 @@ module Gitlab
"#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
end
+ def hash?
+ @config.is_a?(Hash)
+ end
+
+ def string?
+ @config.is_a?(String)
+ end
+
+ def integer?
+ @config.is_a?(Integer)
+ end
+
def self.default(**)
end
diff --git a/lib/gitlab/config/entry/simplifiable.rb b/lib/gitlab/config/entry/simplifiable.rb
index 5fbf7565e2a..a56a89adb35 100644
--- a/lib/gitlab/config/entry/simplifiable.rb
+++ b/lib/gitlab/config/entry/simplifiable.rb
@@ -19,7 +19,10 @@ module Gitlab
entry = self.class.entry_class(strategy)
- super(@subject = entry.new(config, metadata))
+ @subject = entry.new(config, metadata)
+
+ yield(@subject) if block_given?
+ super(@subject)
end
def self.strategy(name, **opts)
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index 25bfa50f829..6796fcce75f 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -15,6 +15,17 @@ module Gitlab
end
end
+ class DisallowedKeysValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ present_keys = value.try(:keys).to_a & options[:in]
+
+ if present_keys.any?
+ record.errors.add(attribute, "contains disallowed keys: " +
+ present_keys.join(', '))
+ end
+ end
+ end
+
class AllowedValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless options[:in].include?(value.to_s)
@@ -25,10 +36,10 @@ module Gitlab
class AllowedArrayValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- unkown_values = value - options[:in]
- unless unkown_values.empty?
+ unknown_values = value - options[:in]
+ unless unknown_values.empty?
record.errors.add(attribute, "contains unknown values: " +
- unkown_values.join(', '))
+ unknown_values.join(', '))
end
end
end
@@ -43,6 +54,14 @@ module Gitlab
end
end
+ class ArrayOrStringValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value.is_a?(Array) || value.is_a?(String)
+ record.errors.add(attribute, 'should be an array or a string')
+ end
+ end
+ end
+
class BooleanValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
@@ -118,19 +137,21 @@ module Gitlab
end
end
+ protected
+
+ def fallback
+ false
+ end
+
private
- def look_like_regexp?(value)
- value.is_a?(String) && value.start_with?('/') &&
- value.end_with?('/')
+ def matches_syntax?(value)
+ Gitlab::UntrustedRegexp::RubySyntax.matches_syntax?(value)
end
def validate_regexp(value)
- look_like_regexp?(value) &&
- Regexp.new(value.to_s[1...-1]) &&
- true
- rescue RegexpError
- false
+ matches_syntax?(value) &&
+ Gitlab::UntrustedRegexp::RubySyntax.valid?(value, fallback: fallback)
end
end
@@ -149,8 +170,16 @@ module Gitlab
def validate_string_or_regexp(value)
return false unless value.is_a?(String)
- return validate_regexp(value) if look_like_regexp?(value)
+ return validate_regexp(value) if matches_syntax?(value)
+
+ true
+ end
+ end
+ class ArrayOfStringsOrRegexpsWithFallbackValidator < ArrayOfStringsOrRegexpsValidator
+ protected
+
+ def fallback
true
end
end
@@ -190,6 +219,97 @@ module Gitlab
end
end
end
+
+ class PortNamePresentAndUniqueValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return unless value.is_a?(Array)
+
+ ports_size = value.count
+ return if ports_size <= 1
+
+ named_ports = value.select { |e| e.is_a?(Hash) }.map { |e| e[:name] }.compact.map(&:downcase)
+
+ if ports_size != named_ports.size
+ record.errors.add(attribute, 'when there is more than one port, a unique name should be added')
+ end
+
+ if ports_size != named_ports.uniq.size
+ record.errors.add(attribute, 'each port name must be different')
+ end
+ end
+ end
+
+ class PortUniqueValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ value = ports(value)
+ return unless value.is_a?(Array)
+
+ ports_size = value.count
+ return if ports_size <= 1
+
+ if transform_ports(value).size != ports_size
+ record.errors.add(attribute, 'each port number can only be referenced once')
+ end
+ end
+
+ private
+
+ def ports(current_data)
+ current_data
+ end
+
+ def transform_ports(raw_ports)
+ raw_ports.map do |port|
+ case port
+ when Integer
+ port
+ when Hash
+ port[:number]
+ end
+ end.uniq
+ end
+ end
+
+ class JobPortUniqueValidator < PortUniqueValidator
+ private
+
+ def ports(current_data)
+ return unless current_data.is_a?(Hash)
+
+ (image_ports(current_data) + services_ports(current_data)).compact
+ end
+
+ def image_ports(current_data)
+ return [] unless current_data[:image].is_a?(Hash)
+
+ current_data.dig(:image, :ports).to_a
+ end
+
+ def services_ports(current_data)
+ current_data.dig(:services).to_a.flat_map { |service| service.is_a?(Hash) ? service[:ports] : nil }
+ end
+ end
+
+ class ServicesWithPortsAliasUniqueValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ current_aliases = aliases(value)
+ return if current_aliases.empty?
+
+ unless aliases_unique?(current_aliases)
+ record.errors.add(:config, 'alias must be unique in services with ports')
+ end
+ end
+
+ private
+
+ def aliases(value)
+ value.select { |s| s.is_a?(Hash) && s[:ports] }.pluck(:alias) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def aliases_unique?(aliases)
+ aliases.size == aliases.uniq.size
+ end
+ end
end
end
end
diff --git a/lib/gitlab/content_disposition.rb b/lib/gitlab/content_disposition.rb
index 32207514ce5..ff6154a5b26 100644
--- a/lib/gitlab/content_disposition.rb
+++ b/lib/gitlab/content_disposition.rb
@@ -22,13 +22,13 @@ module Gitlab
end
# rubocop:disable Style/VariableInterpolation
- TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
+ TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/.freeze
def ascii_filename
'filename="' + percent_escape(::I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
end
- RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
+ RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/.freeze
# rubocop:enable Style/VariableInterpolation
def utf8_filename
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 5ed6427072a..f7d046600e8 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -49,6 +49,7 @@ module Gitlab
Event.contributions.where(author_id: contributor.id)
.where(created_at: date.beginning_of_day..date.end_of_day)
.where(project_id: projects)
+ .with_associations
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 552aad83dd4..469a7fd9f7b 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -47,7 +47,7 @@ module Gitlab
# defaults for missing columns.
if ActiveRecord::Migrator.needs_migration?
db_attributes = current_settings&.attributes || {}
- ::ApplicationSetting.build_from_defaults(db_attributes)
+ fake_application_settings(db_attributes)
elsif current_settings.present?
current_settings
else
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index db8ac3becea..aeca9d00156 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -40,11 +40,11 @@ module Gitlab
end
def first_time_reference_commit(event)
- return nil unless event && merge_request_diff_commits
+ return unless event && merge_request_diff_commits
commits = merge_request_diff_commits[event['id'].to_i]
- return nil if commits.blank?
+ return if commits.blank?
commits.find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index d3c86fdb629..7a0fb419f8e 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
-require 'net/http'
-require 'json'
require_relative 'teammate'
module Gitlab
module Danger
module Helper
- ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze
+ RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
# Returns a list of all files that have been added, modified or renamed.
# `git.modified_files` might contain paths that already have been renamed,
@@ -40,34 +38,12 @@ module Gitlab
ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
end
- def project_name
- ee? ? 'gitlab-ee' : 'gitlab-ce'
- end
-
- # Looks up the current list of GitLab team members and parses it into a
- # useful form
- #
- # @return [Array<Teammate>]
- def team
- @team ||=
- begin
- rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
- raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
- rsp.is_a?(Net::HTTPSuccess)
-
- data = JSON.parse(rsp.body)
- data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
- rescue JSON::ParserError
- raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
- end
+ def release_automation?
+ gitlab.mr_author == RELEASE_TOOLS_BOT
end
- # Like +team+, but only returns teammates in the current project, based on
- # project_name.
- #
- # @return [Array<Teammate>]
- def project_team
- team.select { |member| member.in_project?(project_name) }
+ def project_name
+ ee? ? 'gitlab-ee' : 'gitlab-ce'
end
# @return [Hash<String,Array<String>>]
@@ -94,24 +70,42 @@ module Gitlab
end
CATEGORY_LABELS = {
- docs: "~Documentation",
+ docs: "~Documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "",
- qa: "~QA"
+ qa: "~QA",
+ test: "~test for `spec/features/*`"
}.freeze
-
- # rubocop:disable Style/RegexpLiteral
CATEGORIES = {
- %r{\Adoc/} => :docs,
- %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
+ %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
%r{\A(ee/)?app/(assets|views)/} => :frontend,
%r{\A(ee/)?public/} => :frontend,
%r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend,
- %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
+ %r{\Ascripts/frontend/} => :frontend,
+ %r{(\A|/)(
+ \.babelrc |
+ \.eslintignore |
+ \.eslintrc(\.yml)? |
+ \.nvmrc |
+ \.prettierignore |
+ \.prettierrc |
+ \.scss-lint.yml |
+ \.stylelintrc |
+ \.haml-lint.yml |
+ \.haml-lint_todo.yml |
+ babel\.config\.js |
+ jest\.config\.js |
+ karma\.config\.js |
+ webpack\.config\.js |
+ package\.json |
+ yarn\.lock
+ )\z}x => :frontend,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/features/} => :test,
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
@@ -123,13 +117,13 @@ module Gitlab
# Files that don't fit into any category are marked with :none
%r{\A(ee/)?changelogs/} => :none,
+ %r{\Alocale/gitlab\.pot\z} => :none,
# Fallbacks in case the above patterns miss anything
%r{\.rb\z} => :backend,
- %r{\.(md|txt)\z} => :docs,
+ %r{\.(md|txt)\z} => :none, # To reinstate roulette for documentation, set to `:docs`.
%r{\.js\z} => :frontend
}.freeze
- # rubocop:enable Style/RegexpLiteral
end
end
end
diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb
new file mode 100644
index 00000000000..062eda38ee4
--- /dev/null
+++ b/lib/gitlab/danger/roulette.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'net/http'
+require 'json'
+require 'cgi'
+
+require_relative 'teammate'
+
+module Gitlab
+ module Danger
+ module Roulette
+ ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json'
+ HTTPError = Class.new(RuntimeError)
+
+ # Looks up the current list of GitLab team members and parses it into a
+ # useful form
+ #
+ # @return [Array<Teammate>]
+ def team
+ @team ||=
+ begin
+ data = http_get_json(ROULETTE_DATA_URL)
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
+ rescue JSON::ParserError
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
+ end
+ end
+
+ # Like +team+, but only returns teammates in the current project, based on
+ # project_name.
+ #
+ # @return [Array<Teammate>]
+ def project_team(project_name)
+ team.select { |member| member.in_project?(project_name) }
+ end
+
+ def canonical_branch_name(branch_name)
+ branch_name.gsub(/^[ce]e-|-[ce]e$/, '')
+ end
+
+ def new_random(seed)
+ Random.new(Digest::MD5.hexdigest(seed).to_i(16))
+ end
+
+ # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
+ # selection will change on next spin
+ def spin_for_person(people, random:)
+ person = nil
+ people = people.dup
+
+ people.size.times do
+ person = people.sample(random: random)
+
+ break person unless out_of_office?(person)
+
+ people -= [person]
+ end
+
+ person
+ end
+
+ private
+
+ def out_of_office?(person)
+ username = CGI.escape(person.username)
+ api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
+ response = http_get_json(api_endpoint)
+ response["message"]&.match?(/OOO/i)
+ rescue HTTPError, JSON::ParserError
+ false # this is no worse than not checking for OOO
+ end
+
+ def http_get_json(url)
+ rsp = Net::HTTP.get_response(URI.parse(url))
+
+ unless rsp.is_a?(Net::HTTPSuccess)
+ raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
+ end
+
+ JSON.parse(rsp.body)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index 4b822aa86c5..b44f134f2c1 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -3,11 +3,12 @@
module Gitlab
module Danger
class Teammate
- attr_reader :name, :username, :projects
+ attr_reader :name, :username, :role, :projects
def initialize(options = {})
- @name = options['name']
@username = options['username']
+ @name = options['name'] || @username
+ @role = options['role']
@projects = options['projects']
end
@@ -20,22 +21,34 @@ module Gitlab
end
# Traintainers also count as reviewers
- def reviewer?(project, category)
- capabilities(project) == "reviewer #{category}" || traintainer?(project, category)
+ def reviewer?(project, category, labels)
+ has_capability?(project, category, :reviewer, labels) ||
+ traintainer?(project, category, labels)
end
- def traintainer?(project, category)
- capabilities(project) == "trainee_maintainer #{category}"
+ def traintainer?(project, category, labels)
+ has_capability?(project, category, :trainee_maintainer, labels)
end
- def maintainer?(project, category)
- capabilities(project) == "maintainer #{category}"
+ def maintainer?(project, category, labels)
+ has_capability?(project, category, :maintainer, labels)
end
private
+ def has_capability?(project, category, kind, labels)
+ case category
+ when :test
+ area = role[/Test Automation Engineer, (\w+)/, 1]
+
+ area && labels.any?(area) if kind == :reviewer
+ else
+ capabilities(project).include?("#{kind} #{category}")
+ end
+ end
+
def capabilities(project)
- projects.fetch(project, '')
+ Array(projects.fetch(project, []))
end
end
end
diff --git a/lib/gitlab/data_builder/deployment.rb b/lib/gitlab/data_builder/deployment.rb
new file mode 100644
index 00000000000..f11e032ab84
--- /dev/null
+++ b/lib/gitlab/data_builder/deployment.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DataBuilder
+ module Deployment
+ extend self
+
+ def build(deployment)
+ {
+ object_kind: 'deployment',
+ status: deployment.status,
+ deployable_id: deployment.deployable_id,
+ deployable_url: Gitlab::UrlBuilder.build(deployment.deployable),
+ environment: deployment.environment.name,
+ project: deployment.project.hook_attrs,
+ short_sha: deployment.short_sha,
+ user: deployment.user.hook_attrs,
+ user_url: Gitlab::UrlBuilder.build(deployment.user),
+ commit_url: Gitlab::UrlBuilder.build(deployment.commit),
+ commit_title: deployment.commit.title
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index 76c8b4ec5c2..e1e813849bf 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -19,7 +19,7 @@ module Gitlab
def hook_attrs(pipeline)
{
id: pipeline.id,
- ref: pipeline.ref,
+ ref: pipeline.source_ref,
tag: pipeline.tag,
sha: pipeline.sha,
before_sha: pipeline.before_sha,
@@ -47,7 +47,7 @@ module Gitlab
user: build.user.try(:hook_attrs),
runner: build.runner && runner_hook_attrs(build.runner),
artifacts_file: {
- filename: build.artifacts_file.filename,
+ filename: build.artifacts_file&.filename,
size: build.artifacts_size
}
}
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index ea08b5f7eae..40bda3410e1 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -32,10 +32,7 @@ module Gitlab
}
],
total_commits_count: 1,
- push_options: [
- "ci.skip",
- "custom option"
- ]
+ push_options: { ci: { skip: true } }
}.freeze
# Produce a hash of post-receive data
@@ -57,11 +54,14 @@ module Gitlab
# },
# commits: Array,
# total_commits_count: Fixnum,
- # push_options: Array
+ # push_options: Hash
# }
#
# rubocop:disable Metrics/ParameterLists
- def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: [])
+ def build(
+ project:, user:, ref:, oldrev: nil, newrev: nil,
+ commits: [], commits_count: nil, message: nil, push_options: {})
+
commits = Array(commits)
# Total commits count
@@ -116,7 +116,12 @@ module Gitlab
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
commits = project.repository.commits(project.default_branch.to_s, limit: 3)
- build(project, user, commits.last&.id, commits.first&.id, ref, commits)
+ build(project: project,
+ user: user,
+ oldrev: commits.last&.id,
+ newrev: commits.first&.id,
+ ref: ref,
+ commits: commits)
end
def sample_data
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index b6ca777e029..8da98cc3909 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -27,6 +27,10 @@ module Gitlab
config['adapter']
end
+ def self.human_adapter_name
+ postgresql? ? 'PostgreSQL' : 'MySQL'
+ end
+
def self.mysql?
adapter_name.casecmp('mysql2').zero?
end
@@ -76,7 +80,7 @@ module Gitlab
postgresql? && version.to_f >= 9.4
end
- def self.pg_stat_wal_receiver_supported?
+ def self.postgresql_minimum_supported_version?
postgresql? && version.to_f >= 9.6
end
@@ -98,6 +102,10 @@ module Gitlab
Gitlab::Database.postgresql_9_or_less? ? 'pg_last_xlog_replay_location' : 'pg_last_wal_replay_lsn'
end
+ def self.pg_last_xact_replay_timestamp
+ 'pg_last_xact_replay_timestamp'
+ end
+
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
diff --git a/lib/gitlab/database/count/reltuples_count_strategy.rb b/lib/gitlab/database/count/reltuples_count_strategy.rb
index c3a674aeb7e..695f6fa766e 100644
--- a/lib/gitlab/database/count/reltuples_count_strategy.rb
+++ b/lib/gitlab/database/count/reltuples_count_strategy.rb
@@ -37,6 +37,22 @@ module Gitlab
private
+ # Models using single-type inheritance (STI) don't work with
+ # reltuple count estimates. We just have to ignore them and
+ # use another strategy to compute them.
+ def non_sti_models
+ models.reject { |model| sti_model?(model) }
+ end
+
+ def non_sti_table_names
+ non_sti_models.map(&:table_name)
+ end
+
+ def sti_model?(model)
+ model.column_names.include?(model.inheritance_column) &&
+ model.base_class != model
+ end
+
def table_names
models.map(&:table_name)
end
@@ -47,7 +63,7 @@ module Gitlab
# Querying tuple stats only works on the primary. Due to load balancing, the
# easiest way to do this is to start a transaction.
ActiveRecord::Base.transaction do
- get_statistics(table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
+ get_statistics(non_sti_table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
model = table_to_model[row.table_name]
data[model] = row.estimate
end
diff --git a/lib/gitlab/database/count/tablesample_count_strategy.rb b/lib/gitlab/database/count/tablesample_count_strategy.rb
index cf1cf054dbf..7777f31f702 100644
--- a/lib/gitlab/database/count/tablesample_count_strategy.rb
+++ b/lib/gitlab/database/count/tablesample_count_strategy.rb
@@ -36,7 +36,7 @@ module Gitlab
def perform_count(model, estimate)
# If we estimate 0, we may not have statistics at all. Don't use them.
- return nil unless estimate && estimate > 0
+ return unless estimate && estimate > 0
if estimate < EXACT_COUNT_THRESHOLD
# The table is considered small, the assumption here is that
@@ -48,12 +48,21 @@ module Gitlab
end
end
+ def where_clause(model)
+ return unless sti_model?(model)
+
+ "WHERE #{model.inheritance_column} = '#{model.name}'"
+ end
+
def tablesample_count(model, estimate)
portion = (TABLESAMPLE_ROW_TARGET.to_f / estimate).round(4)
inverse = 1 / portion
query = <<~SQL
SELECT (COUNT(*)*#{inverse})::integer AS count
- FROM #{model.table_name} TABLESAMPLE SYSTEM (#{portion * 100})
+ FROM #{model.table_name}
+ TABLESAMPLE SYSTEM (#{portion * 100})
+ REPEATABLE (0)
+ #{where_clause(model)}
SQL
rows = ActiveRecord::Base.connection.select_all(query)
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 3abd0600e9d..cc61bb7fa02 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -282,6 +282,7 @@ module Gitlab
# Updates the value of a column in batches.
#
# This method updates the table in batches of 5% of the total row count.
+ # A `batch_size` option can also be passed to set this to a fixed number.
# This method will continue updating rows until no rows remain.
#
# When given a block this method will yield two values to the block:
@@ -320,7 +321,7 @@ module Gitlab
# make things _more_ complex).
#
# rubocop: disable Metrics/AbcSize
- def update_column_in_batches(table, column, value)
+ def update_column_in_batches(table, column, value, batch_size: nil)
if transaction_open?
raise 'update_column_in_batches can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \
@@ -336,14 +337,16 @@ module Gitlab
return if total == 0
- # Update in batches of 5% until we run out of any rows to update.
- batch_size = ((total / 100.0) * 5.0).ceil
- max_size = 1000
+ if batch_size.nil?
+ # Update in batches of 5% until we run out of any rows to update.
+ batch_size = ((total / 100.0) * 5.0).ceil
+ max_size = 1000
- # The upper limit is 1000 to ensure we don't lock too many rows. For
- # example, for "merge_requests" even 1% of the table is around 35 000
- # rows for GitLab.com.
- batch_size = max_size if batch_size > max_size
+ # The upper limit is 1000 to ensure we don't lock too many rows. For
+ # example, for "merge_requests" even 1% of the table is around 35 000
+ # rows for GitLab.com.
+ batch_size = max_size if batch_size > max_size
+ end
start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
start_arel = yield table, start_arel if block_given?
@@ -902,6 +905,12 @@ module Gitlab
end
end
+ def remove_foreign_key_if_exists(*args)
+ if foreign_key_exists?(*args)
+ remove_foreign_key(*args)
+ end
+ end
+
def remove_foreign_key_without_error(*args)
remove_foreign_key(*args)
rescue ArgumentError
diff --git a/lib/gitlab/database/multi_threaded_migration.rb b/lib/gitlab/database/multi_threaded_migration.rb
index 1d39a3d0b57..65a6cb8e369 100644
--- a/lib/gitlab/database/multi_threaded_migration.rb
+++ b/lib/gitlab/database/multi_threaded_migration.rb
@@ -35,12 +35,10 @@ module Gitlab
threads = Array.new(thread_count) do
Thread.new do
pool.with_connection do |connection|
- begin
- Thread.current[MULTI_THREAD_AR_CONNECTION] = connection
- yield
- ensure
- Thread.current[MULTI_THREAD_AR_CONNECTION] = nil
- end
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = connection
+ yield
+ ensure
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = nil
end
end
end
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index 8d97adaff99..109ae7893da 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -22,7 +22,7 @@ module Gitlab
# Casts binary data to a SHA1 in hexadecimal.
def deserialize(value)
value = super(value)
- value ? value.unpack(PACK_FORMAT)[0] : nil
+ value ? value.unpack1(PACK_FORMAT) : nil
end
# Casts a SHA1 in hexadecimal to the proper binary format.
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index ac2efe598b4..ffad00fa7d7 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -4,6 +4,7 @@ module Gitlab
module DependencyLinker
class BaseLinker
URL_REGEX = %r{https?://[^'" ]+}.freeze
+ GIT_INVALID_URL_REGEX = /^git\+#{URL_REGEX}/.freeze
REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
class_attribute :file_type
@@ -29,8 +30,25 @@ module Gitlab
highlighted_lines.join.html_safe
end
+ def external_url(name, external_ref)
+ return if external_ref =~ GIT_INVALID_URL_REGEX
+
+ case external_ref
+ when /\A#{URL_REGEX}\z/
+ external_ref
+ when /\A#{REPO_REGEX}\z/
+ github_url(external_ref)
+ else
+ package_url(name)
+ end
+ end
+
private
+ def package_url(_name)
+ raise NotImplementedError
+ end
+
def link_dependencies
raise NotImplementedError
end
diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb
index 22d2bead891..4b8862b31ee 100644
--- a/lib/gitlab/dependency_linker/composer_json_linker.rb
+++ b/lib/gitlab/dependency_linker/composer_json_linker.rb
@@ -8,8 +8,8 @@ module Gitlab
private
def link_packages
- link_packages_at_key("require", &method(:package_url))
- link_packages_at_key("require-dev", &method(:package_url))
+ link_packages_at_key("require")
+ link_packages_at_key("require-dev")
end
def package_url(name)
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
index 8ab219c4962..c6e02248b0a 100644
--- a/lib/gitlab/dependency_linker/gemfile_linker.rb
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -3,8 +3,14 @@
module Gitlab
module DependencyLinker
class GemfileLinker < MethodLinker
+ class_attribute :package_keyword
+
+ self.package_keyword = :gem
self.file_type = :gemfile
+ GITHUB_REGEX = /(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/.freeze
+ GIT_REGEX = /(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/.freeze
+
private
def link_dependencies
@@ -14,21 +20,35 @@ module Gitlab
def link_urls
# Link `github: "user/repo"` to https://github.com/user/repo
- link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
+ link_regex(GITHUB_REGEX, &method(:github_url))
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
- link_regex(/(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/, &:itself)
+ link_regex(GIT_REGEX, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org
link_method_call('source', URL_REGEX, &:itself)
end
def link_packages
- # Link `gem "package_name"` to https://rubygems.org/gems/package_name
- link_method_call('gem') do |name|
- "https://rubygems.org/gems/#{name}"
+ packages = parse_packages
+
+ return if packages.blank?
+
+ packages.each do |package|
+ link_method_call('gem', package.name) do
+ external_url(package.name, package.external_ref)
+ end
end
end
+
+ def package_url(name)
+ "https://rubygems.org/gems/#{name}"
+ end
+
+ def parse_packages
+ parser = Gitlab::DependencyLinker::Parser::Gemfile.new(plain_text)
+ parser.parse(keyword: self.class.package_keyword)
+ end
end
end
end
diff --git a/lib/gitlab/dependency_linker/gemspec_linker.rb b/lib/gitlab/dependency_linker/gemspec_linker.rb
index b924ea86d89..94c2b375cf9 100644
--- a/lib/gitlab/dependency_linker/gemspec_linker.rb
+++ b/lib/gitlab/dependency_linker/gemspec_linker.rb
@@ -11,7 +11,7 @@ module Gitlab
link_method_call('homepage', URL_REGEX, &:itself)
link_method_call('license', &method(:license_url))
- link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name|
+ link_method_call(%w[add_dependency add_runtime_dependency add_development_dependency]) do |name|
"https://rubygems.org/gems/#{name}"
end
end
diff --git a/lib/gitlab/dependency_linker/method_linker.rb b/lib/gitlab/dependency_linker/method_linker.rb
index d4d85bb3390..33899a931c6 100644
--- a/lib/gitlab/dependency_linker/method_linker.rb
+++ b/lib/gitlab/dependency_linker/method_linker.rb
@@ -23,18 +23,22 @@ module Gitlab
# link_method_call('name')
# # Will link `package` in `self.name = "package"`
def link_method_call(method_name, value = nil, &url_proc)
+ regex = method_call_regex(method_name, value)
+
+ link_regex(regex, &url_proc)
+ end
+
+ def method_call_regex(method_name, value = nil)
method_name = regexp_for_value(method_name)
value = regexp_for_value(value)
- regex = %r{
+ %r{
#{method_name} # Method name
\s* # Whitespace
[(=]? # Opening brace or equals sign
\s* # Whitespace
['"](?<name>#{value})['"] # Package name in quotes
}x
-
- link_regex(regex, &url_proc)
end
end
end
diff --git a/lib/gitlab/dependency_linker/package.rb b/lib/gitlab/dependency_linker/package.rb
new file mode 100644
index 00000000000..8a509bbd562
--- /dev/null
+++ b/lib/gitlab/dependency_linker/package.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DependencyLinker
+ class Package
+ attr_reader :name, :git_ref, :github_ref
+
+ def initialize(name, git_ref, github_ref)
+ @name = name
+ @git_ref = git_ref
+ @github_ref = github_ref
+ end
+
+ def external_ref
+ @git_ref || @github_ref
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb
index 578e25f806a..6857f2a4fa2 100644
--- a/lib/gitlab/dependency_linker/package_json_linker.rb
+++ b/lib/gitlab/dependency_linker/package_json_linker.rb
@@ -8,7 +8,6 @@ module Gitlab
private
def link_dependencies
- link_json('name', json["name"], &method(:package_url))
link_json('license', &method(:license_url))
link_json(%w[homepage url], URL_REGEX, &:itself)
@@ -16,25 +15,19 @@ module Gitlab
end
def link_packages
- link_packages_at_key("dependencies", &method(:package_url))
- link_packages_at_key("devDependencies", &method(:package_url))
+ link_packages_at_key("dependencies")
+ link_packages_at_key("devDependencies")
end
- def link_packages_at_key(key, &url_proc)
+ def link_packages_at_key(key)
dependencies = json[key]
return unless dependencies
dependencies.each do |name, version|
- link_json(name, version, link: :key, &url_proc)
-
- link_json(name) do |value|
- case value
- when /\A#{URL_REGEX}\z/
- value
- when /\A#{REPO_REGEX}\z/
- github_url(value)
- end
- end
+ external_url = external_url(name, version)
+
+ link_json(name, version, link: :key) { external_url }
+ link_json(name) { external_url }
end
end
diff --git a/lib/gitlab/dependency_linker/parser/gemfile.rb b/lib/gitlab/dependency_linker/parser/gemfile.rb
new file mode 100644
index 00000000000..7f755375cea
--- /dev/null
+++ b/lib/gitlab/dependency_linker/parser/gemfile.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DependencyLinker
+ module Parser
+ class Gemfile < MethodLinker
+ GIT_REGEX = Gitlab::DependencyLinker::GemfileLinker::GIT_REGEX
+ GITHUB_REGEX = Gitlab::DependencyLinker::GemfileLinker::GITHUB_REGEX
+
+ def initialize(plain_text)
+ @plain_text = plain_text
+ end
+
+ # Returns a list of Gitlab::DependencyLinker::Package
+ #
+ # keyword - The package definition keyword, e.g. `:gem` for
+ # Gemfile parsing, `:pod` for Podfile.
+ def parse(keyword:)
+ plain_lines.each_with_object([]) do |line, packages|
+ name = fetch(line, method_call_regex(keyword))
+
+ next unless name
+
+ git_ref = fetch(line, GIT_REGEX)
+ github_ref = fetch(line, GITHUB_REGEX)
+
+ packages << Gitlab::DependencyLinker::Package.new(name, git_ref, github_ref)
+ end
+ end
+
+ private
+
+ def fetch(line, regex, group: :name)
+ match = line.match(regex)
+ match[group] if match
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/podfile_linker.rb b/lib/gitlab/dependency_linker/podfile_linker.rb
index def9b04cca9..a20d285da79 100644
--- a/lib/gitlab/dependency_linker/podfile_linker.rb
+++ b/lib/gitlab/dependency_linker/podfile_linker.rb
@@ -5,12 +5,21 @@ module Gitlab
class PodfileLinker < GemfileLinker
include Cocoapods
+ self.package_keyword = :pod
self.file_type = :podfile
private
def link_packages
- link_method_call('pod', &method(:package_url))
+ packages = parse_packages
+
+ return unless packages
+
+ packages.each do |package|
+ link_method_call('pod', package.name) do
+ external_url(package.name, package.external_ref)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb
index 6b1758c5a43..14abd3999c4 100644
--- a/lib/gitlab/dependency_linker/podspec_linker.rb
+++ b/lib/gitlab/dependency_linker/podspec_linker.rb
@@ -19,7 +19,7 @@ module Gitlab
link_method_call('license', &method(:license_url))
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
- link_method_call(%w[name dependency], &method(:package_url))
+ link_method_call('dependency', &method(:package_url))
end
end
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index c9d89d56884..c46087e65de 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -75,7 +75,7 @@ module Gitlab
end
def line_for_position(pos)
- return nil unless pos.position_type == 'text'
+ return unless pos.position_type == 'text'
# This method is normally used to find which line the diff was
# commented on, and in this context, it's normally the raw diff persisted
@@ -133,11 +133,15 @@ module Gitlab
end
def new_blob
- new_blob_lazy&.itself
+ strong_memoize(:new_blob) do
+ new_blob_lazy&.itself
+ end
end
def old_blob
- old_blob_lazy&.itself
+ strong_memoize(:old_blob) do
+ old_blob_lazy&.itself
+ end
end
def new_blob_lines_between(from_line, to_line)
@@ -158,7 +162,10 @@ module Gitlab
new_blob || old_blob
end
- attr_writer :highlighted_diff_lines
+ def highlighted_diff_lines=(value)
+ clear_memoization(:diff_lines_for_serializer)
+ @highlighted_diff_lines = value
+ end
# Array of Gitlab::Diff::Line objects
def diff_lines
@@ -314,19 +321,31 @@ module Gitlab
# This adds the bottom match line to the array if needed. It contains
# the data to load more context lines.
def diff_lines_for_serializer
- lines = highlighted_diff_lines
+ strong_memoize(:diff_lines_for_serializer) do
+ lines = highlighted_diff_lines
- return if lines.empty?
- return if blob.nil?
+ next if lines.empty?
+ next if blob.nil?
- last_line = lines.last
+ last_line = lines.last
+
+ if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
+ match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
+ lines.push(match_line)
+ end
- if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
- match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
- lines.push(match_line)
+ lines
end
+ end
+
+ def fully_expanded?
+ return true if binary?
+
+ lines = diff_lines_for_serializer
+
+ return true if lines.nil?
- lines
+ lines.none? { |line| line.type.to_s == 'match' }
end
private
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
new file mode 100644
index 00000000000..4a3ac2106e2
--- /dev/null
+++ b/lib/gitlab/diff/suggestion.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class Suggestion
+ include Suggestible
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :diff_file, :lines_above, :lines_below,
+ :target_line
+
+ def initialize(text, line:, above:, below:, diff_file:)
+ @text = text
+ @target_line = line
+ @lines_above = above.to_i
+ @lines_below = below.to_i
+ @diff_file = diff_file
+ end
+
+ def to_hash
+ {
+ from_content: from_content,
+ to_content: to_content,
+ lines_above: @lines_above,
+ lines_below: @lines_below
+ }
+ end
+
+ def from_content
+ strong_memoize(:from_content) do
+ fetch_from_content
+ end
+ end
+
+ def to_content
+ return "" if @text.blank?
+
+ # The parsed suggestion doesn't have information about the correct
+ # ending characters (we may have a line break, or not), so we take
+ # this information from the last line being changed (last
+ # characters).
+ endline_chars = line_break_chars(from_content.lines.last)
+ "#{@text}#{endline_chars}"
+ end
+
+ private
+
+ def line_break_chars(line)
+ match = /\r\n|\r|\n/.match(line)
+ match[0] if match
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestion_diff.rb b/lib/gitlab/diff/suggestion_diff.rb
new file mode 100644
index 00000000000..ee153c226b7
--- /dev/null
+++ b/lib/gitlab/diff/suggestion_diff.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class SuggestionDiff
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :from_content, :to_content, :from_line, to: :@suggestible
+
+ def initialize(suggestible)
+ @suggestible = suggestible
+ end
+
+ def diff_lines
+ Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
+ end
+
+ private
+
+ def raw_diff
+ "#{diff_header}\n#{from_content_as_diff}#{to_content_as_diff}"
+ end
+
+ def diff_header
+ "@@ -#{from_line} +#{from_line}"
+ end
+
+ def from_content_as_diff
+ from_content.lines.map { |line| line.prepend('-') }.join
+ end
+
+ def to_content_as_diff
+ to_content.lines.map { |line| line.prepend('+') }.join
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
new file mode 100644
index 00000000000..6e17ffaf6ff
--- /dev/null
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class SuggestionsParser
+ # Matches for instance "-1", "+1" or "-1+2".
+ SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+
+ class << self
+ # Returns an array of Gitlab::Diff::Suggestion which represents each
+ # suggestion in the given text.
+ #
+ def parse(text, position:, project:, supports_suggestion: true)
+ return [] unless position.complete?
+
+ html = Banzai.render(text, project: nil,
+ no_original_data: true,
+ suggestions_filter_enabled: supports_suggestion)
+ doc = Nokogiri::HTML(html)
+ suggestion_nodes = doc.search('pre.suggestion')
+
+ return [] if suggestion_nodes.empty?
+
+ diff_file = position.diff_file(project.repository)
+
+ suggestion_nodes.map do |node|
+ lang_param = node['data-lang-params']
+
+ lines_above, lines_below = nil
+
+ if lang_param && suggestion_params = fetch_suggestion_params(lang_param)
+ lines_above, lines_below =
+ suggestion_params[:above],
+ suggestion_params[:below]
+ end
+
+ Gitlab::Diff::Suggestion.new(node.text,
+ line: position.new_line,
+ above: lines_above.to_i,
+ below: lines_below.to_i,
+ diff_file: diff_file)
+ end
+ end
+
+ private
+
+ def fetch_suggestion_params(lang_param)
+ lang_param.match(SUGGESTION_CONTEXT)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb
index 270cfb89488..369c6b87fb4 100644
--- a/lib/gitlab/discussions_diff/highlight_cache.rb
+++ b/lib/gitlab/discussions_diff/highlight_cache.rb
@@ -52,6 +52,19 @@ module Gitlab
end
end
+ # Clears multiple cache keys at once.
+ #
+ # raw_keys - An Array of unique cache keys, without namespaces.
+ #
+ # It returns the number of cache keys cleared. Ex.: 42
+ def clear_multiple(raw_keys)
+ return [] if raw_keys.empty?
+
+ keys = raw_keys.map { |id| cache_key_for(id) }
+
+ Redis::Cache.with { |redis| redis.del(keys) }
+ end
+
def cache_key_for(raw_key)
"#{cache_key_prefix}:#{raw_key}"
end
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index 2743f011ca6..dc44e9d7481 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -61,7 +61,7 @@ module Gitlab
# Force encoding to UTF-8 on a Mail::Message or Mail::Part
def fix_charset(object)
- return nil if object.nil?
+ return if object.nil?
if object.charset
object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index a4a154c80f7..5a61a7f5d60 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -76,8 +76,11 @@ module Gitlab
str.dup.force_encoding(Encoding::ASCII_8BIT)
end
- def binary_stringio(str)
- StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
+ def binary_io(str_or_io)
+ io = str_or_io.to_io.dup if str_or_io.respond_to?(:to_io)
+ io ||= StringIO.new(str_or_io.to_s.freeze)
+
+ io.tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
end
private
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index 0891f79198d..17fbecbd097 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -15,50 +15,51 @@ module Gitlab
new environments].freeze
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape)))
+ RESERVED_WORDS_PREFIX = %Q(^(?!.*\/(#{RESERVED_WORDS_REGEX})\/).*)
ROUTES = [
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/noteable/issue/\d+/notes\z),
+ %r(#{RESERVED_WORDS_PREFIX}/noteable/issue/\d+/notes\z),
'issue_notes'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/issues/\d+/realtime_changes\z),
+ %r(#{RESERVED_WORDS_PREFIX}/issues/\d+/realtime_changes\z),
'issue_title'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/commit/\S+/pipelines\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/commit/\S+/pipelines\.json\z),
'commit_pipelines'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/merge_requests/new\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/merge_requests/new\.json\z),
'new_merge_request_pipelines'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/merge_requests/\d+/pipelines\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/merge_requests/\d+/pipelines\.json\z),
'merge_request_pipelines'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/pipelines\.json\z),
'project_pipelines'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/pipelines/\d+\.json\z),
'project_pipeline'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/builds/\d+\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/builds/\d+\.json\z),
'project_build'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/environments\.json\z),
'environments'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/github/realtime_changes\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/import/github/realtime_changes\.json\z),
'realtime_changes_import_github'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/gitea/realtime_changes\.json\z),
+ %r(#{RESERVED_WORDS_PREFIX}/import/gitea/realtime_changes\.json\z),
'realtime_changes_import_gitea'
)
].freeze
diff --git a/lib/gitlab/external_authorization.rb b/lib/gitlab/external_authorization.rb
new file mode 100644
index 00000000000..25f8b7b3628
--- /dev/null
+++ b/lib/gitlab/external_authorization.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ extend ExternalAuthorization::Config
+
+ RequestFailed = Class.new(StandardError)
+
+ def self.access_allowed?(user, label, project_path = nil)
+ return true unless perform_check?
+ return false unless user
+
+ access_for_user_to_label(user, label, project_path).has_access?
+ end
+
+ def self.rejection_reason(user, label)
+ return unless enabled?
+ return unless user
+
+ access_for_user_to_label(user, label, nil).reason
+ end
+
+ def self.access_for_user_to_label(user, label, project_path)
+ if RequestStore.active?
+ RequestStore.fetch("external_authorisation:user-#{user.id}:label-#{label}") do
+ load_access(user, label, project_path)
+ end
+ else
+ load_access(user, label, project_path)
+ end
+ end
+
+ def self.load_access(user, label, project_path)
+ access = ::Gitlab::ExternalAuthorization::Access.new(user, label).load!
+ ::Gitlab::ExternalAuthorization::Logger.log_access(access, project_path)
+
+ access
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/access.rb b/lib/gitlab/external_authorization/access.rb
new file mode 100644
index 00000000000..e111c41fcc2
--- /dev/null
+++ b/lib/gitlab/external_authorization/access.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Access
+ attr_reader :user,
+ :reason,
+ :loaded_at,
+ :label,
+ :load_type
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def loaded?
+ loaded_at && (loaded_at > ExternalAuthorization::Cache::VALIDITY_TIME.ago)
+ end
+
+ def has_access?
+ @access
+ end
+
+ def load!
+ load_from_cache
+ load_from_service unless loaded?
+ self
+ end
+
+ private
+
+ def load_from_cache
+ @load_type = :cache
+ @access, @reason, @loaded_at = cache.load
+ end
+
+ def load_from_service
+ @load_type = :request
+ response = Client.new(@user, @label).request_access
+ @access = response.successful?
+ @reason = response.reason
+ @loaded_at = Time.now
+ cache.store(@access, @reason, @loaded_at) if response.valid?
+ rescue ::Gitlab::ExternalAuthorization::RequestFailed => e
+ @access = false
+ @reason = e.message
+ @loaded_at = Time.now
+ end
+
+ def cache
+ @cache ||= ExternalAuthorization::Cache.new(@user, @label)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/cache.rb b/lib/gitlab/external_authorization/cache.rb
new file mode 100644
index 00000000000..acdc028b4dc
--- /dev/null
+++ b/lib/gitlab/external_authorization/cache.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Cache
+ VALIDITY_TIME = 6.hours
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def load
+ @access, @reason, @refreshed_at = ::Gitlab::Redis::Cache.with do |redis|
+ redis.hmget(cache_key, :access, :reason, :refreshed_at)
+ end
+
+ [access, reason, refreshed_at]
+ end
+
+ def store(new_access, new_reason, new_refreshed_at)
+ ::Gitlab::Redis::Cache.with do |redis|
+ redis.pipelined do
+ redis.mapped_hmset(
+ cache_key,
+ {
+ access: new_access.to_s,
+ reason: new_reason.to_s,
+ refreshed_at: new_refreshed_at.to_s
+ }
+ )
+
+ redis.expire(cache_key, VALIDITY_TIME)
+ end
+ end
+ end
+
+ private
+
+ def access
+ ::Gitlab::Utils.to_boolean(@access)
+ end
+
+ def reason
+ # `nil` if the cached value was an empty string
+ return unless @reason.present?
+
+ @reason
+ end
+
+ def refreshed_at
+ # Don't try to parse a time if there was no cache
+ return unless @refreshed_at.present?
+
+ Time.parse(@refreshed_at)
+ end
+
+ def cache_key
+ "external_authorization:user-#{@user.id}:label-#{@label}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/client.rb b/lib/gitlab/external_authorization/client.rb
new file mode 100644
index 00000000000..60aab2e7044
--- /dev/null
+++ b/lib/gitlab/external_authorization/client.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+Excon.defaults[:ssl_verify_peer] = false
+
+module Gitlab
+ module ExternalAuthorization
+ class Client
+ include ExternalAuthorization::Config
+
+ REQUEST_HEADERS = {
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json'
+ }.freeze
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def request_access
+ response = Excon.post(
+ service_url,
+ post_params
+ )
+ ::Gitlab::ExternalAuthorization::Response.new(response)
+ rescue Excon::Error => e
+ raise ::Gitlab::ExternalAuthorization::RequestFailed.new(e)
+ end
+
+ private
+
+ def post_params
+ params = { headers: REQUEST_HEADERS,
+ body: body.to_json,
+ connect_timeout: timeout,
+ read_timeout: timeout,
+ write_timeout: timeout }
+
+ if has_tls?
+ params[:client_cert_data] = client_cert
+ params[:client_key_data] = client_key
+ params[:client_key_pass] = client_key_pass
+ end
+
+ params
+ end
+
+ def body
+ @body ||= begin
+ body = {
+ user_identifier: @user.email,
+ project_classification_label: @label
+ }
+
+ if @user.ldap_identity
+ body[:user_ldap_dn] = @user.ldap_identity.extern_uid
+ end
+
+ body
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/config.rb b/lib/gitlab/external_authorization/config.rb
new file mode 100644
index 00000000000..8654a8c1e2e
--- /dev/null
+++ b/lib/gitlab/external_authorization/config.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ module Config
+ extend self
+
+ def timeout
+ application_settings.external_authorization_service_timeout
+ end
+
+ def service_url
+ application_settings.external_authorization_service_url
+ end
+
+ def enabled?
+ application_settings.external_authorization_service_enabled
+ end
+
+ def perform_check?
+ enabled? && service_url.present?
+ end
+
+ def client_cert
+ application_settings.external_auth_client_cert
+ end
+
+ def client_key
+ application_settings.external_auth_client_key
+ end
+
+ def client_key_pass
+ application_settings.external_auth_client_key_pass
+ end
+
+ def has_tls?
+ client_cert.present? && client_key.present?
+ end
+
+ private
+
+ def application_settings
+ ::Gitlab::CurrentSettings.current_application_settings
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/logger.rb b/lib/gitlab/external_authorization/logger.rb
new file mode 100644
index 00000000000..61246cd870e
--- /dev/null
+++ b/lib/gitlab/external_authorization/logger.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Logger < ::Gitlab::Logger
+ def self.log_access(access, project_path)
+ status = access.has_access? ? "GRANTED" : "DENIED"
+ message = ["#{status} #{access.user.email} access to '#{access.label}'"]
+
+ message << "(#{project_path})" if project_path.present?
+ message << "- #{access.load_type} #{access.loaded_at}" if access.load_type == :cache
+
+ info(message.join(' '))
+ end
+
+ def self.file_name_noext
+ 'external-policy-access-control'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/response.rb b/lib/gitlab/external_authorization/response.rb
new file mode 100644
index 00000000000..4f3fe5882db
--- /dev/null
+++ b/lib/gitlab/external_authorization/response.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Response
+ include ::Gitlab::Utils::StrongMemoize
+
+ def initialize(excon_response)
+ @excon_response = excon_response
+ end
+
+ def valid?
+ @excon_response && [200, 401, 403].include?(@excon_response.status)
+ end
+
+ def successful?
+ valid? && @excon_response.status == 200
+ end
+
+ def reason
+ parsed_response['reason'] if parsed_response
+ end
+
+ private
+
+ def parsed_response
+ strong_memoize(:parsed_response) { parse_response! }
+ end
+
+ def parse_response!
+ JSON.parse(@excon_response.body)
+ rescue JSON::JSONError
+ # The JSON response is optional, so don't fail when it's missing
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
index bd806269bf0..77f7d9490f3 100644
--- a/lib/gitlab/fake_application_settings.rb
+++ b/lib/gitlab/fake_application_settings.rb
@@ -7,6 +7,8 @@
# column type without parsing db/schema.rb.
module Gitlab
class FakeApplicationSettings < OpenStruct
+ include ApplicationSettingImplementation
+
# Mimic ActiveRecord predicate methods for boolean values
def self.define_predicate_methods(options)
options.each do |key, value|
@@ -26,20 +28,7 @@ module Gitlab
FakeApplicationSettings.define_predicate_methods(options)
end
- def key_restriction_for(type)
- 0
- end
-
- def allowed_key_types
- ApplicationSetting::SUPPORTED_KEY_TYPES
- end
-
- def pick_repository_storage
- repository_storages.sample
- end
-
- def commit_email_hostname
- super.presence || ApplicationSetting.default_commit_email_hostname
- end
+ alias_method :read_attribute, :[]
+ alias_method :has_attribute?, :[]
end
end
diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb
index 1ae2f9dfd93..6e31064f737 100644
--- a/lib/gitlab/favicon.rb
+++ b/lib/gitlab/favicon.rb
@@ -10,7 +10,7 @@ module Gitlab
elsif Gitlab::Utils.to_boolean(ENV['CANARY'])
'favicon-yellow.png'
elsif Rails.env.development?
- 'favicon-blue.png'
+ development_favicon
else
'favicon.png'
end
@@ -18,6 +18,12 @@ module Gitlab
ActionController::Base.helpers.image_path(image_name, host: host)
end
+ def development_favicon
+ # This is a separate method so that EE can return a different favicon
+ # for development environments.
+ 'favicon-blue.png'
+ end
+
def status_overlay(status_name)
path = File.join(
'ci_favicons',
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index 2770469ca9f..9fc2217ad43 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -16,6 +16,7 @@ module Gitlab
avatar: /\Alogo\.(png|jpg|gif)\z/,
issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z},
merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z},
+ metrics_dashboard: %r{\A\.gitlab/dashboards/[^/]+\.yml\z},
xcode_config: %r{\A[^/]*\.(xcodeproj|xcworkspace)(/.+)?\z},
# Configuration files
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 431911d1eee..2c53f9b026d 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -239,7 +239,7 @@ module Gitlab
res = ::Projects::DownloadService.new(project, link).execute
- return nil if res.nil?
+ return if res.nil?
res[:markdown]
end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 259a2b7911a..10df4ed72d9 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -23,6 +23,10 @@ module Gitlab
class << self
def find(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
+ tree_entry(repository, sha, path, limit)
+ end
+
+ def tree_entry(repository, sha, path, limit)
return unless path
path = path.sub(%r{\A/*}, '')
@@ -179,3 +183,5 @@ module Gitlab
end
end
end
+
+Gitlab::Git::Blob.singleton_class.prepend Gitlab::Git::RuggedImpl::Blob::ClassMethods
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 5863815ca85..8fac3621df9 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -5,6 +5,7 @@ module Gitlab
module Git
class Commit
include Gitlab::EncodingHelper
+ prepend Gitlab::Git::RuggedImpl::Commit
extend Gitlab::Git::WrapsGitalyErrors
attr_accessor :raw_commit, :head
@@ -57,20 +58,24 @@ module Gitlab
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
# Some weird thing?
- return nil unless commit_id.is_a?(String)
+ return unless commit_id.is_a?(String)
# This saves us an RPC round trip.
- return nil if commit_id.include?(':')
+ return if commit_id.include?(':')
- commit = wrapped_gitaly_errors do
- repo.gitaly_commit_client.find_commit(commit_id)
- end
+ commit = find_commit(repo, commit_id)
decorate(repo, commit) if commit
rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository, ArgumentError
nil
end
+ def find_commit(repo, commit_id)
+ wrapped_gitaly_errors do
+ repo.gitaly_commit_client.find_commit(commit_id)
+ end
+ end
+
# Get last commit for HEAD
#
# Ex.
@@ -179,12 +184,17 @@ module Gitlab
end
end
- def initialize(repository, raw_commit, head = nil)
+ def initialize(repository, raw_commit, head = nil, lazy_load_parents: false)
raise "Nil as raw commit passed" unless raw_commit
@repository = repository
@head = head
+ @lazy_load_parents = lazy_load_parents
+
+ init_commit(raw_commit)
+ end
+ def init_commit(raw_commit)
case raw_commit
when Hash
init_from_hash(raw_commit)
@@ -216,6 +226,12 @@ module Gitlab
author_name != committer_name || author_email != committer_email
end
+ def parent_ids
+ return @parent_ids unless @lazy_load_parents
+
+ @parent_ids ||= @repository.commit(id).parent_ids
+ end
+
def parent_id
parent_ids.first
end
@@ -302,14 +318,23 @@ module Gitlab
parent_ids.size > 1
end
+ def gitaly_commit?
+ raw_commit.is_a?(Gitaly::GitCommit)
+ end
+
def tree_entry(path)
return unless path.present?
+ commit_tree_entry(path)
+ end
+
+ def commit_tree_entry(path)
# We're only interested in metadata, so limit actual data to 1 byte
# since Gitaly doesn't support "send no data" option.
entry = @repository.gitaly_commit_client.tree_entry(id, path, 1)
return unless entry
+ # To be compatible with the rugged format
entry = entry.to_h
entry.delete(:data)
entry[:name] = File.basename(path)
@@ -319,7 +344,7 @@ module Gitlab
end
def to_gitaly_commit
- return raw_commit if raw_commit.is_a?(Gitaly::GitCommit)
+ return raw_commit if gitaly_commit?
message_split = raw_commit.message.split("\n", 2)
Gitaly::GitCommit.new(
@@ -400,3 +425,5 @@ module Gitlab
end
end
end
+
+Gitlab::Git::Commit.singleton_class.prepend Gitlab::Git::RuggedImpl::Commit::ClassMethods
diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb
index e93ca3e11f8..d0577d7a4ff 100644
--- a/lib/gitlab/git/object_pool.rb
+++ b/lib/gitlab/git/object_pool.rb
@@ -8,7 +8,7 @@ module Gitlab
GL_REPOSITORY = ""
delegate :exists?, :size, to: :repository
- delegate :unlink_repository, :delete, to: :object_pool_service
+ delegate :delete, to: :object_pool_service
attr_reader :storage, :relative_path, :source_repository, :gl_project_path
@@ -40,6 +40,10 @@ module Gitlab
@repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY, gl_project_path)
end
+ def fetch
+ object_pool_service.fetch(source_repository)
+ end
+
private
def object_pool_service
diff --git a/lib/gitlab/git/pre_receive_error.rb b/lib/gitlab/git/pre_receive_error.rb
index 03caace6fce..ef9b1bf5224 100644
--- a/lib/gitlab/git/pre_receive_error.rb
+++ b/lib/gitlab/git/pre_receive_error.rb
@@ -4,19 +4,38 @@ module Gitlab
module Git
#
# PreReceiveError is special because its message gets displayed to users
- # in the web UI. To prevent XSS we sanitize the message on
- # initialization.
+ # in the web UI. Because of this, we:
+ # - Only display errors that have been marked as safe with a prefix.
+ # This is to prevent leaking of stacktraces, or other sensitive info.
+ # - Sanitize the string of any XSS
class PreReceiveError < StandardError
- def initialize(msg = '')
- super(nlbr(msg))
+ SAFE_MESSAGE_PREFIXES = [
+ 'GitLab:', # Messages from gitlab-shell
+ 'GL-HOOK-ERR:' # Messages marked as safe by user
+ ].freeze
+
+ SAFE_MESSAGE_REGEX = /^(#{SAFE_MESSAGE_PREFIXES.join('|')})\s*(?<safe_message>.+)/.freeze
+
+ def initialize(message = '')
+ super(sanitize(message))
end
private
# In gitaly-ruby we override this method to do nothing, so that
# sanitization happens in gitlab-rails only.
- def nlbr(str)
- Gitlab::Utils.nlbr(str)
+ def sanitize(message)
+ return message if message.blank?
+
+ safe_messages = message.split("\n").map do |msg|
+ if (match = msg.match(SAFE_MESSAGE_REGEX))
+ match[:safe_message].presence
+ end
+ end
+
+ safe_messages = safe_messages.compact.join("\n")
+
+ Gitlab::Utils.nlbr(safe_messages)
end
end
end
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index eec91194949..47cfb483509 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -4,6 +4,7 @@ module Gitlab
module Git
class Ref
include Gitlab::EncodingHelper
+ include Gitlab::Git::RuggedImpl::Ref
# Branch or tag name
# without "refs/tags|heads" prefix
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 593a3676519..8a2e711ec4e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -11,6 +11,7 @@ module Gitlab
include Gitlab::Git::WrapsGitalyErrors
include Gitlab::EncodingHelper
include Gitlab::Utils::StrongMemoize
+ prepend Gitlab::Git::RuggedImpl::Repository
SEARCH_CONTEXT_LINES = 3
REV_LIST_COMMIT_LIMIT = 2_000
@@ -33,34 +34,6 @@ module Gitlab
TagExistsError = Class.new(StandardError)
ChecksumError = Class.new(StandardError)
- class << self
- def create_hooks(repo_path, global_hooks_path)
- local_hooks_path = File.join(repo_path, 'hooks')
- real_local_hooks_path = :not_found
-
- begin
- real_local_hooks_path = File.realpath(local_hooks_path)
- rescue Errno::ENOENT
- # real_local_hooks_path == :not_found
- end
-
- # Do nothing if hooks already exist
- unless real_local_hooks_path == File.realpath(global_hooks_path)
- if File.exist?(local_hooks_path)
- # Move the existing hooks somewhere safe
- FileUtils.mv(
- local_hooks_path,
- "#{local_hooks_path}.old.#{Time.now.to_i}")
- end
-
- # Create the hooks symlink
- FileUtils.ln_sf(global_hooks_path, local_hooks_path)
- end
-
- true
- end
- end
-
# Directory name of repo
attr_reader :name
@@ -117,6 +90,12 @@ module Gitlab
gitaly_repository_client.exists?
end
+ def create_repository
+ wrapped_gitaly_errors do
+ gitaly_repository_client.create_repository
+ end
+ end
+
# Returns an Array of branch names
# sorted by name ASC
def branch_names
@@ -230,12 +209,12 @@ module Gitlab
end
end
- def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
+ def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
- prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha)
+ prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)
{
'ArchivePrefix' => prefix,
@@ -247,13 +226,14 @@ module Gitlab
# This is both the filename of the archive (missing the extension) and the
# name of the top-level member of the archive under which all files go
- def archive_prefix(ref, sha, project_path, append_sha:)
+ def archive_prefix(ref, sha, project_path, append_sha:, path:)
append_sha = (ref != sha) if append_sha.nil?
formatted_ref = ref.tr('/', '-')
prefix_segments = [project_path, formatted_ref]
prefix_segments << sha if append_sha
+ prefix_segments << path.tr('/', '-').gsub(%r{^/|/$}, '') if path
prefix_segments.join('-')
end
@@ -275,7 +255,7 @@ module Gitlab
# senddata response.
def archive_file_path(storage_path, sha, name, format = "tar.gz")
# Build file path
- return nil unless name
+ return unless name
extension =
case format
@@ -302,6 +282,11 @@ module Gitlab
(size.to_f / 1024).round(2)
end
+ # Return git object directory size in bytes
+ def object_directory_size
+ gitaly_repository_client.get_object_directory_size.to_f * 1024
+ end
+
# Build an array of commits.
#
# Usage.
@@ -343,12 +328,12 @@ module Gitlab
end
end
- def new_blobs(newrev)
+ def new_blobs(newrev, dynamic_timeout: nil)
return [] if newrev.blank? || newrev == ::Gitlab::Git::BLANK_SHA
strong_memoize("new_blobs_#{newrev}") do
wrapped_gitaly_errors do
- gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT)
+ gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT, dynamic_timeout: dynamic_timeout)
end
end
end
@@ -464,7 +449,7 @@ module Gitlab
@refs_hash = Hash.new { |h, k| h[k] = [] }
(tags + branches).each do |ref|
- next unless ref.target && ref.name
+ next unless ref.target && ref.name && ref.dereferenced_target&.id
@refs_hash[ref.dereferenced_target.id] << ref.name
end
@@ -492,7 +477,7 @@ module Gitlab
end
# Return total diverging commits count
- def diverging_commit_count(from, to, max_count:)
+ def diverging_commit_count(from, to, max_count: 0)
wrapped_gitaly_errors do
gitaly_commit_client.diverging_commit_count(from, to, max_count: max_count)
end
@@ -556,6 +541,12 @@ module Gitlab
tags.find { |tag| tag.name == name }
end
+ def merge_to_ref(user, source_sha, branch, target_ref, message)
+ wrapped_gitaly_errors do
+ gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+ end
+
def merge(user, source_sha, target_branch, message, &block)
wrapped_gitaly_errors do
gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
@@ -724,18 +715,29 @@ module Gitlab
end
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
+ reachable_ref =
+ if source_repository == self
+ source_branch_name
+ else
+ # If a tmp ref was created before for a separate repo comparison (forks),
+ # we're able to short-circuit the tmp ref re-creation:
+ # 1. Take the SHA from the source repo
+ # 2. Read that in the current "target" repo
+ # 3. If that SHA is still known (readable), it means GC hasn't
+ # cleaned it up yet, so we can use it instead re-writing the tmp ref.
+ source_commit_id = source_repository.commit(source_branch_name)&.sha
+ commit(source_commit_id)&.sha if source_commit_id
+ end
+
+ return compare(target_branch_name, reachable_ref, straight: straight) if reachable_ref
+
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
return unless fetch_source_branch!(source_repository, source_branch_name, tmp_ref)
- Gitlab::Git::Compare.new(
- self,
- target_branch_name,
- tmp_ref,
- straight: straight
- )
+ compare(target_branch_name, tmp_ref, straight: straight)
ensure
- delete_refs(tmp_ref)
+ delete_refs(tmp_ref) if tmp_ref
end
def write_ref(ref_path, ref, old_ref: nil)
@@ -809,7 +811,8 @@ module Gitlab
gitaly_repository_client.create_from_snapshot(url, auth)
end
- def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ # DEPRECATED: https://gitlab.com/gitlab-org/gitaly/issues/1628
+ def rebase_deprecated(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
wrapped_gitaly_errors do
gitaly_operation_client.user_rebase(user, rebase_id,
branch: branch,
@@ -819,6 +822,20 @@ module Gitlab
end
end
+ def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, &block)
+ wrapped_gitaly_errors do
+ gitaly_operation_client.rebase(
+ user,
+ rebase_id,
+ branch: branch,
+ branch_sha: branch_sha,
+ remote_repository: remote_repository,
+ remote_branch: remote_branch,
+ &block
+ )
+ end
+ end
+
def rebase_in_progress?(rebase_id)
wrapped_gitaly_errors do
gitaly_repository_client.rebase_in_progress?(rebase_id)
@@ -846,17 +863,20 @@ module Gitlab
true
end
+ # rubocop:disable Metrics/ParameterLists
def multi_action(
user, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
- start_branch_name: nil, start_repository: self)
+ start_branch_name: nil, start_repository: self,
+ force: false)
wrapped_gitaly_errors do
gitaly_operation_client.user_commit_files(user, branch_name,
message, actions, author_email, author_name,
- start_branch_name, start_repository)
+ start_branch_name, start_repository, force)
end
end
+ # rubocop:enable Metrics/ParameterLists
def write_config(full_path:)
return unless full_path.present?
@@ -879,6 +899,12 @@ module Gitlab
end
end
+ def disconnect_alternates
+ wrapped_gitaly_errors do
+ gitaly_repository_client.disconnect_alternates
+ end
+ end
+
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository, @gl_project_path)
end
@@ -988,6 +1014,13 @@ module Gitlab
private
+ def compare(base_ref, head_ref, straight:)
+ Gitlab::Git::Compare.new(self,
+ base_ref,
+ head_ref,
+ straight: straight)
+ end
+
def empty_diff_stats
Gitlab::Git::DiffStatsCollection.new([])
end
diff --git a/lib/gitlab/git/repository_cleaner.rb b/lib/gitlab/git/repository_cleaner.rb
index 2d1d8435cf3..9dd0ddfb44b 100644
--- a/lib/gitlab/git/repository_cleaner.rb
+++ b/lib/gitlab/git/repository_cleaner.rb
@@ -12,9 +12,9 @@ module Gitlab
@repository = repository
end
- def apply_bfg_object_map(io)
+ def apply_bfg_object_map_stream(io, &blk)
wrapped_gitaly_errors do
- gitaly_cleanup_client.apply_bfg_object_map(io)
+ gitaly_cleanup_client.apply_bfg_object_map_stream(io, &blk)
end
end
diff --git a/lib/gitlab/git/rugged_impl/blob.rb b/lib/gitlab/git/rugged_impl/blob.rb
new file mode 100644
index 00000000000..11ee4ebda4b
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/blob.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+# NOTE: This code is legacy. Do not add/modify code here unless you have
+# discussed with the Gitaly team. See
+# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code
+# for more details.
+
+module Gitlab
+ module Git
+ module RuggedImpl
+ module Blob
+ module ClassMethods
+ extend ::Gitlab::Utils::Override
+
+ override :tree_entry
+ def tree_entry(repository, sha, path, limit)
+ if Feature.enabled?(:rugged_tree_entry)
+ rugged_tree_entry(repository, sha, path, limit)
+ else
+ super
+ end
+ end
+
+ private
+
+ def rugged_tree_entry(repository, sha, path, limit)
+ return unless path
+
+ # Strip any leading / characters from the path
+ path = path.sub(%r{\A/*}, '')
+
+ rugged_commit = repository.lookup(sha)
+ root_tree = rugged_commit.tree
+
+ blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/'))
+
+ return 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
+ rescue Rugged::ReferenceError
+ nil
+ end
+
+ # Recursive search of blob id by path
+ #
+ # Ex.
+ # blog/ # oid: 1a
+ # app/ # oid: 2a
+ # models/ # oid: 3a
+ # file.rb # oid: 4a
+ #
+ #
+ # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a'
+ #
+ def find_entry_by_path(repository, root_id, *path_parts)
+ root_tree = repository.lookup(root_id)
+
+ entry = root_tree.find do |entry|
+ entry[:name] == path_parts[0]
+ end
+
+ return unless entry
+
+ if path_parts.size > 1
+ return unless entry[:type] == :tree
+
+ path_parts.shift
+ find_entry_by_path(repository, entry[:oid], *path_parts)
+ else
+ [:blob, :commit].include?(entry[:type]) ? entry : nil
+ end
+ end
+
+ def submodule_blob(blob_entry, path, sha)
+ new(
+ id: blob_entry[:oid],
+ name: blob_entry[:name],
+ size: 0,
+ data: '',
+ path: path,
+ commit_id: sha
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb
new file mode 100644
index 00000000000..bce4fa14fb4
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/commit.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+# NOTE: This code is legacy. Do not add/modify code here unless you have
+# discussed with the Gitaly team. See
+# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code
+# for more details.
+
+# rubocop:disable Gitlab/ModuleWithInstanceVariables
+module Gitlab
+ module Git
+ module RuggedImpl
+ module Commit
+ module ClassMethods
+ extend ::Gitlab::Utils::Override
+
+ def rugged_find(repo, commit_id)
+ obj = repo.rev_parse_target(commit_id)
+
+ obj.is_a?(::Rugged::Commit) ? obj : nil
+ rescue ::Rugged::Error
+ nil
+ end
+
+ # This needs to return an array of Gitlab::Git:Commit objects
+ # instead of Rugged::Commit objects to ensure upstream models
+ # operate on a consistent interface. Unlike
+ # Gitlab::Git::Commit.find, Gitlab::Git::Commit.batch_by_oid
+ # doesn't attempt to decorate the result.
+ def rugged_batch_by_oid(repo, oids)
+ oids.map { |oid| rugged_find(repo, oid) }
+ .compact
+ .map { |commit| decorate(repo, commit) }
+ end
+
+ override :find_commit
+ def find_commit(repo, commit_id)
+ if Feature.enabled?(:rugged_find_commit)
+ rugged_find(repo, commit_id)
+ else
+ super
+ end
+ end
+
+ override :batch_by_oid
+ def batch_by_oid(repo, oids)
+ if Feature.enabled?(:rugged_list_commits_by_oid)
+ rugged_batch_by_oid(repo, oids)
+ else
+ super
+ end
+ end
+ end
+
+ extend ::Gitlab::Utils::Override
+
+ override :init_commit
+ def init_commit(raw_commit)
+ case raw_commit
+ when ::Rugged::Commit
+ init_from_rugged(raw_commit)
+ else
+ super
+ end
+ end
+
+ override :commit_tree_entry
+ def commit_tree_entry(path)
+ if Feature.enabled?(:rugged_commit_tree_entry)
+ rugged_tree_entry(path)
+ else
+ super
+ end
+ end
+
+ # Is this the same as Blob.find_entry_by_path ?
+ def rugged_tree_entry(path)
+ rugged_commit.tree.path(path)
+ rescue Rugged::TreeError
+ nil
+ end
+
+ def rugged_commit
+ @rugged_commit ||= if raw_commit.is_a?(Rugged::Commit)
+ raw_commit
+ else
+ @repository.rev_parse_target(id)
+ end
+ end
+
+ def init_from_rugged(commit)
+ author = commit.author
+ committer = commit.committer
+
+ @raw_commit = commit
+ @id = commit.oid
+ @message = commit.message
+ @authored_date = author[:time]
+ @committed_date = committer[:time]
+ @author_name = author[:name]
+ @author_email = author[:email]
+ @committer_name = committer[:name]
+ @committer_email = committer[:email]
+ @parent_ids = commit.parents.map(&:oid)
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/gitlab/git/rugged_impl/ref.rb b/lib/gitlab/git/rugged_impl/ref.rb
new file mode 100644
index 00000000000..b553e82dc47
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/ref.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# NOTE: This code is legacy. Do not add/modify code here unless you have
+# discussed with the Gitaly team. See
+# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code
+# for more details.
+
+module Gitlab
+ module Git
+ module RuggedImpl
+ module Ref
+ def self.dereference_object(object)
+ object = object.target while object.is_a?(::Rugged::Tag::Annotation)
+
+ object
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb
new file mode 100644
index 00000000000..e91b0ddcd31
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/repository.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+# NOTE: This code is legacy. Do not add/modify code here unless you have
+# discussed with the Gitaly team. See
+# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code
+# for more details.
+
+# rubocop:disable Gitlab/ModuleWithInstanceVariables
+module Gitlab
+ module Git
+ module RuggedImpl
+ module Repository
+ extend ::Gitlab::Utils::Override
+
+ FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor rugged_commit_tree_entry rugged_list_commits_by_oid).freeze
+
+ def alternate_object_directories
+ relative_object_directories.map { |d| File.join(path, d) }
+ end
+
+ ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES = %w[
+ GIT_OBJECT_DIRECTORY_RELATIVE
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
+ ].freeze
+
+ def relative_object_directories
+ Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
+ end
+
+ def rugged
+ @rugged ||= ::Rugged::Repository.new(path, alternates: alternate_object_directories)
+ rescue ::Rugged::RepositoryError, ::Rugged::OSError
+ raise ::Gitlab::Git::Repository::NoRepository.new('no repository for such path')
+ end
+
+ def cleanup
+ @rugged&.close
+ end
+
+ # Return the object that +revspec+ points to. If +revspec+ is an
+ # annotated tag, then return the tag's target instead.
+ def rev_parse_target(revspec)
+ obj = rugged.rev_parse(revspec)
+ Ref.dereference_object(obj)
+ end
+
+ override :ancestor?
+ def ancestor?(from, to)
+ if Feature.enabled?(:rugged_commit_is_ancestor)
+ rugged_is_ancestor?(from, to)
+ else
+ super
+ end
+ end
+
+ def rugged_is_ancestor?(ancestor_id, descendant_id)
+ return false if ancestor_id.nil? || descendant_id.nil?
+
+ rugged_merge_base(ancestor_id, descendant_id) == ancestor_id
+ rescue Rugged::OdbError
+ false
+ end
+
+ def rugged_merge_base(from, to)
+ rugged.merge_base(from, to)
+ rescue Rugged::ReferenceError
+ nil
+ end
+
+ # Lookup for rugged object by oid or ref name
+ def lookup(oid_or_ref_name)
+ rugged.rev_parse(oid_or_ref_name)
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/gitlab/git/rugged_impl/tree.rb b/lib/gitlab/git/rugged_impl/tree.rb
new file mode 100644
index 00000000000..9c37bb01961
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/tree.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+# NOTE: This code is legacy. Do not add/modify code here unless you have
+# discussed with the Gitaly team. See
+# https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code
+# for more details.
+
+module Gitlab
+ module Git
+ module RuggedImpl
+ module Tree
+ module ClassMethods
+ extend ::Gitlab::Utils::Override
+
+ override :tree_entries
+ def tree_entries(repository, sha, path, recursive)
+ if Feature.enabled?(:rugged_tree_entries)
+ tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
+ else
+ super
+ end
+ end
+
+ def tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
+ tree_entries_from_rugged(repository, sha, path, recursive).tap do |entries|
+ # This was an optimization to reduce N+1 queries for Gitaly
+ # (https://gitlab.com/gitlab-org/gitaly/issues/530). It
+ # used to be done lazily in the view via
+ # TreeHelper#flatten_tree, so it's possible there's a
+ # performance impact by loading this eagerly.
+ rugged_populate_flat_path(repository, sha, path, entries)
+ end
+ end
+
+ def tree_entries_from_rugged(repository, sha, path, recursive)
+ current_path_entries = get_tree_entries_from_rugged(repository, sha, path)
+ ordered_entries = []
+
+ current_path_entries.each do |entry|
+ ordered_entries << entry
+
+ if recursive && entry.dir?
+ ordered_entries.concat(tree_entries_from_rugged(repository, sha, entry.path, true))
+ end
+ end
+
+ ordered_entries
+ end
+
+ def rugged_populate_flat_path(repository, sha, path, entries)
+ entries.each do |entry|
+ entry.flat_path = entry.path
+
+ next unless entry.dir?
+
+ entry.flat_path =
+ if path
+ File.join(path, rugged_flatten_tree(repository, sha, entry, path))
+ else
+ rugged_flatten_tree(repository, sha, entry, path)
+ end
+ end
+ end
+
+ # Returns the relative path of the first subdir that doesn't have only one directory descendant
+ def rugged_flatten_tree(repository, sha, tree, root_path)
+ subtree = tree_entries_from_rugged(repository, sha, tree.path, false)
+
+ if subtree.count == 1 && subtree.first.dir?
+ File.join(tree.name, rugged_flatten_tree(repository, sha, subtree.first, root_path))
+ else
+ tree.name
+ end
+ end
+
+ def get_tree_entries_from_rugged(repository, sha, path)
+ commit = repository.lookup(sha)
+ root_tree = commit.tree
+
+ tree = if path
+ id = find_id_by_path(repository, root_tree.oid, path)
+ if id
+ repository.lookup(id)
+ else
+ []
+ end
+ else
+ root_tree
+ end
+
+ tree.map do |entry|
+ current_path = path ? File.join(path, entry[:name]) : entry[:name]
+
+ new(
+ id: entry[:oid],
+ root_id: root_tree.oid,
+ name: entry[:name],
+ type: entry[:type],
+ mode: entry[:filemode].to_s(8),
+ path: current_path,
+ commit_id: sha
+ )
+ end
+ rescue Rugged::ReferenceError
+ []
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index 51542bcaaa2..7e072c5db50 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -18,6 +18,10 @@ module Gitlab
def where(repository, sha, path = nil, recursive = false)
path = nil if path == '' || path == '/'
+ tree_entries(repository, sha, path, recursive)
+ end
+
+ def tree_entries(repository, sha, path, recursive)
wrapped_gitaly_errors do
repository.gitaly_commit_client.tree_entries(repository, sha, path, recursive)
end
@@ -44,7 +48,7 @@ module Gitlab
entry[:name] == path_arr[0] && entry[:type] == :tree
end
- return nil unless entry
+ return unless entry
if path_arr.size > 1
path_arr.shift
@@ -95,3 +99,5 @@ module Gitlab
end
end
end
+
+Gitlab::Git::Tree.singleton_class.prepend Gitlab::Git::RuggedImpl::Tree::ClassMethods
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index c43331bed60..c1bcd8e934a 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -86,9 +86,14 @@ module Gitlab
end
end
- def pages(limit: 0)
+ def list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
wrapped_gitaly_errors do
- gitaly_get_all_pages(limit: limit)
+ gitaly_list_pages(
+ limit: limit,
+ sort: sort,
+ direction_desc: direction_desc,
+ load_content: load_content
+ )
end
end
@@ -168,8 +173,17 @@ module Gitlab
Gitlab::Git::WikiFile.new(wiki_file)
end
- def gitaly_get_all_pages(limit: 0)
- gitaly_wiki_client.get_all_pages(limit: limit).map do |wiki_page, version|
+ def gitaly_list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
+ params = { limit: limit, sort: sort, direction_desc: direction_desc }
+
+ gitaly_pages =
+ if load_content
+ gitaly_wiki_client.load_all_pages(params)
+ else
+ gitaly_wiki_client.list_all_pages(params)
+ end
+
+ gitaly_pages.map do |wiki_page, version|
Gitlab::Git::WikiPage.new(wiki_page, version)
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 010bd0e520c..4b626509008 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -85,7 +85,7 @@ module Gitlab
check_push_access!
end
- ::Gitlab::GitAccessResult::Success.new
+ success_result(cmd)
end
def guest_can_download_code?
@@ -116,6 +116,10 @@ module Gitlab
nil
end
+ def check_for_console_messages(cmd)
+ []
+ end
+
def check_valid_actor!
return unless actor.is_a?(Key)
@@ -361,6 +365,10 @@ module Gitlab
protected
+ def success_result(cmd)
+ ::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd))
+ end
+
def changes_list
@changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes)
end
diff --git a/lib/gitlab/git_access_result/success.rb b/lib/gitlab/git_access_result/success.rb
index 7bb9f24cb0e..e950d727e2e 100644
--- a/lib/gitlab/git_access_result/success.rb
+++ b/lib/gitlab/git_access_result/success.rb
@@ -3,6 +3,11 @@
module Gitlab
module GitAccessResult
class Success
+ attr_reader :console_messages
+
+ def initialize(console_messages: [])
+ @console_messages = console_messages
+ end
end
end
end
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index 426436c2164..d98b85fecc4 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -5,7 +5,7 @@ module Gitlab
include Gitlab::Identifier
attr_reader :project, :identifier, :changes, :push_options
- def initialize(project, identifier, changes, push_options)
+ def initialize(project, identifier, changes, push_options = {})
@project = project
@identifier = identifier
@changes = deserialize_changes(changes)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 3f13ebeb9d0..dfff6823689 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -5,12 +5,15 @@
module Gitlab
module GitRefValidator
extend self
+
+ EXPANDED_PREFIXES = %w[refs/heads/ refs/remotes/].freeze
+ DISALLOWED_PREFIXES = %w[-].freeze
+
# Validates a given name against the git reference specification
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
- not_allowed_prefixes = %w(refs/heads/ refs/remotes/ -)
- return false if ref_name.start_with?(*not_allowed_prefixes)
+ return false if ref_name.start_with?(*(EXPANDED_PREFIXES + DISALLOWED_PREFIXES))
return false if ref_name == 'HEAD'
begin
@@ -19,5 +22,21 @@ module Gitlab
return false
end
end
+
+ def validate_merge_request_branch(ref_name)
+ return false if ref_name.start_with?(*DISALLOWED_PREFIXES)
+
+ expanded_name = if ref_name.start_with?(*EXPANDED_PREFIXES)
+ ref_name
+ else
+ "refs/heads/#{ref_name}"
+ end
+
+ begin
+ Rugged::Reference.valid_name?(expanded_name)
+ rescue ArgumentError
+ return false
+ end
+ end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 5aeedb0f50d..e683d4e5bbe 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -26,18 +26,16 @@ module Gitlab
end
end
- PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m
+ PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m.freeze
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
MAXIMUM_GITALY_CALLS = 30
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
- MUTEX = Mutex.new
-
- class << self
- attr_accessor :query_time
- end
+ SERVER_FEATURE_CATFILE_CACHE = 'catfile-cache'.freeze
+ # Server feature flags should use '_' to separate words.
+ SERVER_FEATURE_FLAGS = [SERVER_FEATURE_CATFILE_CACHE].freeze
- self.query_time = 0
+ MUTEX = Mutex.new
define_histogram :gitaly_controller_action_duration_seconds do
docstring "Gitaly endpoint histogram by controller and action combination"
@@ -58,9 +56,9 @@ module Gitlab
end
def self.interceptors
- return [] unless Gitlab::Tracing.enabled?
+ return [] unless Labkit::Tracing.enabled?
- [Gitlab::Tracing::GRPCInterceptor.instance]
+ [Labkit::Tracing::GRPCInterceptor.instance]
end
private_class_method :interceptors
@@ -75,13 +73,11 @@ module Gitlab
@certs = stub_cert_paths.flat_map do |cert_file|
File.read(cert_file).scan(PEM_REGEX).map do |cert|
- begin
- OpenSSL::X509::Certificate.new(cert).to_pem
- rescue OpenSSL::OpenSSLError => e
- Rails.logger.error "Could not load certificate #{cert_file} #{e}"
- Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
- nil
- end
+ OpenSSL::X509::Certificate.new(cert).to_pem
+ rescue OpenSSL::OpenSSLError => e
+ Rails.logger.error "Could not load certificate #{cert_file} #{e}"
+ Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
+ nil
end.compact
end.uniq.join("\n")
end
@@ -164,8 +160,6 @@ module Gitlab
kwargs = yield(kwargs) if block_given?
stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
- rescue GRPC::Unavailable => ex
- handle_grpc_unavailable!(ex)
ensure
duration = Gitlab::Metrics::System.monotonic_time - start
@@ -175,29 +169,23 @@ module Gitlab
current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
duration)
- add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc)
+ if peek_enabled?
+ add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc,
+ backtrace: Gitlab::Profiler.clean_backtrace(caller))
+ end
end
- def self.handle_grpc_unavailable!(ex)
- status = ex.to_status
- raise ex unless status.details == 'Endpoint read failed'
-
- # There is a bug in grpc 1.8.x that causes a client process to get stuck
- # always raising '14:Endpoint read failed'. The only thing that we can
- # do to recover is to restart the process.
- #
- # See https://gitlab.com/gitlab-org/gitaly/issues/1029
+ def self.query_time
+ SafeRequestStore[:gitaly_query_time] ||= 0
+ end
- if Sidekiq.server?
- raise Gitlab::SidekiqMiddleware::Shutdown::WantShutdown.new(ex.to_s)
- else
- # SIGQUIT requests a Unicorn worker to shut down gracefully after the current request.
- Process.kill('QUIT', Process.pid)
- end
+ def self.query_time=(duration)
+ SafeRequestStore[:gitaly_query_time] = duration
+ end
- raise ex
+ def self.query_time_ms
+ (self.query_time * 1000).round(2)
end
- private_class_method :handle_grpc_unavailable!
def self.current_transaction_labels
Gitlab::Metrics::Transaction.current&.labels || {}
@@ -234,7 +222,8 @@ module Gitlab
feature = feature_stack && feature_stack[0]
metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
- metadata['x-gitlab-correlation-id'] = Gitlab::CorrelationId.current_id if Gitlab::CorrelationId.current_id
+ metadata['x-gitlab-correlation-id'] = Labkit::Correlation::CorrelationId.current_id if Labkit::Correlation::CorrelationId.current_id
+ metadata['gitaly-session-id'] = session_id if feature_enabled?(SERVER_FEATURE_CATFILE_CACHE)
metadata.merge!(server_feature_flags)
@@ -251,7 +240,9 @@ module Gitlab
result
end
- SERVER_FEATURE_FLAGS = %w[].freeze
+ def self.session_id
+ Gitlab::SafeRequestStore[:gitaly_session_id] ||= SecureRandom.uuid
+ end
def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f|
@@ -267,7 +258,9 @@ module Gitlab
end
def self.feature_enabled?(feature_name)
- Feature.enabled?("gitaly_#{feature_name}")
+ Feature::FlipperFeature.table_exists? && Feature.enabled?("gitaly_#{feature_name}")
+ rescue ActiveRecord::NoDatabaseError
+ false
end
# Ensures that Gitaly is not being abuse through n+1 misuse etc
@@ -278,8 +271,7 @@ module Gitlab
# This is this actual number of times this call was made. Used for information purposes only
actual_call_count = increment_call_count("gitaly_#{call_site}_actual")
- # Do no enforce limits in production
- return if Rails.env.production? || ENV["GITALY_DISABLE_REQUEST_LIMITS"]
+ return unless enforce_gitaly_request_limits?
# Check if this call is nested within a allow_n_plus_1_calls
# block and skip check if it is
@@ -296,6 +288,19 @@ module Gitlab
raise TooManyInvocationsError.new(call_site, actual_call_count, max_call_count, max_stacks)
end
+ def self.enforce_gitaly_request_limits?
+ # We typically don't want to enforce request limits in production
+ # However, we have some production-like test environments, i.e., ones
+ # where `Rails.env.production?` returns `true`. We do want to be able to
+ # check if the limit is being exceeded while testing in those environments
+ # In that case we can use a feature flag to indicate that we do want to
+ # enforce request limits.
+ return true if feature_enabled?('enforce_requests_limits')
+
+ !(Rails.env.production? || ENV["GITALY_DISABLE_REQUEST_LIMITS"])
+ end
+ private_class_method :enforce_gitaly_request_limits?
+
def self.allow_n_plus_1_calls
return yield unless Gitlab::SafeRequestStore.active?
@@ -307,6 +312,26 @@ module Gitlab
end
end
+ # Normally a FindCommit RPC will cache the commit with its SHA
+ # instead of a ref name, since it's possible the branch is mutated
+ # afterwards. However, for read-only requests that never mutate the
+ # branch, this method allows caching of the ref name directly.
+ def self.allow_ref_name_caching
+ return yield unless Gitlab::SafeRequestStore.active?
+ return yield if ref_name_caching_allowed?
+
+ begin
+ Gitlab::SafeRequestStore[:allow_ref_name_caching] = true
+ yield
+ ensure
+ Gitlab::SafeRequestStore[:allow_ref_name_caching] = false
+ end
+ end
+
+ def self.ref_name_caching_allowed?
+ Gitlab::SafeRequestStore[:allow_ref_name_caching]
+ end
+
def self.get_call_count(key)
Gitlab::SafeRequestStore[key] || 0
end
@@ -335,15 +360,17 @@ module Gitlab
Gitlab::SafeRequestStore["gitaly_call_permitted"] = 0
end
- def self.add_call_details(details)
- return unless Gitlab::SafeRequestStore[:peek_enabled]
+ def self.peek_enabled?
+ Gitlab::SafeRequestStore[:peek_enabled]
+ end
+ def self.add_call_details(details)
Gitlab::SafeRequestStore['gitaly_call_details'] ||= []
Gitlab::SafeRequestStore['gitaly_call_details'] << details
end
def self.list_call_details
- return [] unless Gitlab::SafeRequestStore[:peek_enabled]
+ return [] unless peek_enabled?
Gitlab::SafeRequestStore['gitaly_call_details'] || []
end
@@ -407,13 +434,13 @@ module Gitlab
# Returns the stacks that calls Gitaly the most times. Used for n+1 detection
def self.max_stacks
- return nil unless Gitlab::SafeRequestStore.active?
+ return unless Gitlab::SafeRequestStore.active?
stack_counter = Gitlab::SafeRequestStore[:stack_counter]
- return nil unless stack_counter
+ return unless stack_counter
max = max_call_count
- return nil if max.zero?
+ return if max.zero?
stack_counter.select { |_, v| v == max }.keys
end
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index 39547328210..8ccefb00d20 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -27,7 +27,7 @@ module Gitlab
data << msg.data
end
- return nil if blob.oid.blank?
+ return if blob.oid.blank?
data = data.join
@@ -55,13 +55,13 @@ module Gitlab
def get_blobs(revision_paths, limit = -1)
return [] if revision_paths.empty?
- revision_paths.map! do |rev, path|
+ request_revision_paths = revision_paths.map do |rev, path|
Gitaly::GetBlobsRequest::RevisionPath.new(revision: rev, path: encode_binary(path))
end
request = Gitaly::GetBlobsRequest.new(
repository: @gitaly_repo,
- revision_paths: revision_paths,
+ revision_paths: request_revision_paths,
limit: limit
)
diff --git a/lib/gitlab/gitaly_client/blobs_stitcher.rb b/lib/gitlab/gitaly_client/blobs_stitcher.rb
index 01bab854082..f860d8ce517 100644
--- a/lib/gitlab/gitaly_client/blobs_stitcher.rb
+++ b/lib/gitlab/gitaly_client/blobs_stitcher.rb
@@ -13,17 +13,15 @@ module Gitlab
current_blob_data = nil
@rpc_response.each do |msg|
- begin
- if msg.oid.blank? && msg.data.blank?
- next
- elsif msg.oid.present?
- yield new_blob(current_blob_data) if current_blob_data
-
- current_blob_data = msg.to_h.slice(:oid, :path, :size, :revision, :mode)
- current_blob_data[:data] = msg.data.dup
- else
- current_blob_data[:data] << msg.data
- end
+ if msg.oid.blank? && msg.data.blank?
+ next
+ elsif msg.oid.present?
+ yield new_blob(current_blob_data) if current_blob_data
+
+ current_blob_data = msg.to_h.slice(:oid, :path, :size, :revision, :mode)
+ current_blob_data[:data] = msg.data.dup
+ else
+ current_blob_data[:data] << msg.data
end
end
diff --git a/lib/gitlab/gitaly_client/cleanup_service.rb b/lib/gitlab/gitaly_client/cleanup_service.rb
index 3e8d6a773ca..a56bc35f6d7 100644
--- a/lib/gitlab/gitaly_client/cleanup_service.rb
+++ b/lib/gitlab/gitaly_client/cleanup_service.rb
@@ -12,25 +12,32 @@ module Gitlab
@storage = repository.storage
end
- def apply_bfg_object_map(io)
- first_request = Gitaly::ApplyBfgObjectMapRequest.new(repository: gitaly_repo)
+ def apply_bfg_object_map_stream(io, &blk)
+ responses = GitalyClient.call(
+ storage,
+ :cleanup_service,
+ :apply_bfg_object_map_stream,
+ build_object_map_enum(io),
+ timeout: GitalyClient.no_timeout
+ )
+
+ responses.each(&blk)
+ end
+
+ private
- enum = Enumerator.new do |y|
- y.yield first_request
+ def build_object_map_enum(io)
+ Enumerator.new do |y|
+ # First request. For simplicity, doesn't include any object map data
+ y << Gitaly::ApplyBfgObjectMapStreamRequest.new(repository: gitaly_repo)
+ # Now stream the BFG object map file to gitaly in chunks
while data = io.read(RepositoryService::MAX_MSG_SIZE)
- y.yield Gitaly::ApplyBfgObjectMapRequest.new(object_map: data)
+ y << Gitaly::ApplyBfgObjectMapStreamRequest.new(object_map: data)
+
break if io&.eof?
end
end
-
- GitalyClient.call(
- storage,
- :cleanup_service,
- :apply_bfg_object_map,
- enum,
- timeout: GitalyClient.no_timeout
- )
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index ea12424eb4a..d21b98d36ea 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -79,7 +79,7 @@ module Gitlab
def tree_entry(ref, path, limit = nil)
if Pathname.new(path).cleanpath.to_s.start_with?('../')
- # The TreeEntry RPC should return an empty reponse in this case but in
+ # The TreeEntry RPC should return an empty response in this case but in
# Gitaly 0.107.0 and earlier we get an exception instead. This early return
# saves us a Gitaly roundtrip while also avoiding the exception.
return
@@ -174,7 +174,7 @@ module Gitlab
response.each_with_object({}) do |gitaly_response, hsh|
gitaly_response.commits.each do |commit_for_tree|
- hsh[commit_for_tree.path] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit)
+ hsh[commit_for_tree.path_bytes] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit)
end
end
end
@@ -286,7 +286,7 @@ module Gitlab
commit = call_find_commit(revision)
return unless commit
- key[:commit_id] = commit.id
+ key[:commit_id] = commit.id unless GitalyClient.ref_name_caching_allowed?
Gitlab::SafeRequestStore[key] = commit
else
call_find_commit(revision)
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 6304f998563..077b63205a8 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
- reader = binary_stringio(resolution.files.to_json)
+ reader = binary_io(resolution.files.to_json)
req_enum = Enumerator.new do |y|
header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch)
diff --git a/lib/gitlab/gitaly_client/object_pool_service.rb b/lib/gitlab/gitaly_client/object_pool_service.rb
index 6e7ede5fd18..d7fac26bc13 100644
--- a/lib/gitlab/gitaly_client/object_pool_service.rb
+++ b/lib/gitlab/gitaly_client/object_pool_service.rb
@@ -34,14 +34,13 @@ module Gitlab
request, timeout: GitalyClient.fast_timeout)
end
- def unlink_repository(repository)
- request = Gitaly::UnlinkRepositoryFromObjectPoolRequest.new(
+ def fetch(repository)
+ request = Gitaly::FetchIntoObjectPoolRequest.new(
object_pool: object_pool,
- repository: repository.gitaly_repository
+ origin: repository.gitaly_repository
)
- GitalyClient.call(storage, :object_pool_service, :unlink_repository_from_object_pool,
- request, timeout: GitalyClient.fast_timeout)
+ GitalyClient.call(storage, :object_pool_service, :fetch_into_object_pool, request)
end
end
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 22d2d149e65..e4a59ee3f9b 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -62,7 +62,7 @@ module Gitlab
end
branch = response.branch
- return nil unless branch
+ return unless branch
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
Gitlab::Git::Branch.new(@repository, branch.name, target_commit.id, target_commit)
@@ -100,6 +100,25 @@ module Gitlab
end
end
+ def user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ request = Gitaly::UserMergeToRefRequest.new(
+ repository: @gitaly_repo,
+ source_sha: source_sha,
+ branch: encode_binary(branch),
+ target_ref: encode_binary(target_ref),
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ message: message
+ )
+
+ response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
+
+ if pre_receive_error = response.pre_receive_error.presence
+ raise Gitlab::Git::PreReceiveError, pre_receive_error
+ end
+
+ response.commit_id
+ end
+
def user_merge_branch(user, source_sha, target_branch, message)
request_enum = QueueEnumerator.new
response_enum = GitalyClient.call(
@@ -178,6 +197,7 @@ module Gitlab
start_repository: start_repository)
end
+ # DEPRECATED: https://gitlab.com/gitlab-org/gitaly/issues/1628
def user_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
request = Gitaly::UserRebaseRequest.new(
repository: @gitaly_repo,
@@ -206,6 +226,49 @@ module Gitlab
end
end
+ def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ request_enum = QueueEnumerator.new
+ rebase_sha = nil
+
+ response_enum = GitalyClient.call(
+ @repository.storage,
+ :operation_service,
+ :user_rebase_confirmable,
+ request_enum.each,
+ remote_storage: remote_repository.storage
+ )
+
+ # First request
+ request_enum.push(
+ Gitaly::UserRebaseConfirmableRequest.new(
+ header: Gitaly::UserRebaseConfirmableRequest::Header.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ rebase_id: rebase_id.to_s,
+ branch: encode_binary(branch),
+ branch_sha: branch_sha,
+ remote_repository: remote_repository.gitaly_repository,
+ remote_branch: encode_binary(remote_branch)
+ )
+ )
+ )
+
+ perform_next_gitaly_rebase_request(response_enum) do |response|
+ rebase_sha = response.rebase_sha
+ end
+
+ yield rebase_sha
+
+ # Second request confirms with gitaly to finalize the rebase
+ request_enum.push(Gitaly::UserRebaseConfirmableRequest.new(apply: true))
+
+ perform_next_gitaly_rebase_request(response_enum)
+
+ rebase_sha
+ ensure
+ request_enum.close
+ end
+
def user_squash(user, squash_id, branch, start_sha, end_sha, author, message)
request = Gitaly::UserSquashRequest.new(
repository: @gitaly_repo,
@@ -258,14 +321,14 @@ module Gitlab
end
end
+ # rubocop:disable Metrics/ParameterLists
def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository)
-
+ start_branch_name, start_repository, force = false)
req_enum = Enumerator.new do |y|
header = user_commit_files_request_header(user, branch_name,
commit_message, actions, author_email, author_name,
- start_branch_name, start_repository)
+ start_branch_name, start_repository, force)
y.yield Gitaly::UserCommitFilesRequest.new(header: header)
@@ -275,7 +338,7 @@ module Gitlab
action: Gitaly::UserCommitFilesAction.new(header: action_header)
)
- reader = binary_stringio(action[:content])
+ reader = binary_io(action[:content])
until reader.eof?
chunk = reader.read(MAX_MSG_SIZE)
@@ -300,6 +363,7 @@ module Gitlab
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
end
+ # rubocop:enable Metrics/ParameterLists
def user_commit_patches(user, branch_name, patches)
header = Gitaly::UserApplyPatchRequest::Header.new(
@@ -307,7 +371,7 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
target_branch: encode_binary(branch_name)
)
- reader = binary_stringio(patches)
+ reader = binary_io(patches)
chunks = Enumerator.new do |chunk|
chunk.yield Gitaly::UserApplyPatchRequest.new(header: header)
@@ -326,6 +390,20 @@ module Gitlab
private
+ def perform_next_gitaly_rebase_request(response_enum)
+ response = response_enum.next
+
+ if response.pre_receive_error.present?
+ raise Gitlab::Git::PreReceiveError, response.pre_receive_error
+ elsif response.git_error.present?
+ raise Gitlab::Git::Repository::GitError, response.git_error
+ end
+
+ yield response if block_given?
+
+ response
+ end
+
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
request_class = "Gitaly::User#{rpc.to_s.camelcase}Request".constantize
@@ -363,9 +441,10 @@ module Gitlab
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
end
+ # rubocop:disable Metrics/ParameterLists
def user_commit_files_request_header(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository)
+ start_branch_name, start_repository, force)
Gitaly::UserCommitFilesRequestHeader.new(
repository: @gitaly_repo,
@@ -375,9 +454,11 @@ module Gitlab
commit_author_name: encode_binary(author_name),
commit_author_email: encode_binary(author_email),
start_branch_name: encode_binary(start_branch_name),
- start_repository: start_repository.gitaly_repository
+ start_repository: start_repository.gitaly_repository,
+ force: force
)
end
+ # rubocop:enable Metrics/ParameterLists
def user_commit_files_action_header(action)
Gitaly::UserCommitFilesActionHeader.new(
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index d5633d167ac..b7d509dfa48 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -84,15 +84,22 @@ module Gitlab
commits
end
- def list_new_blobs(newrev, limit = 0)
+ def list_new_blobs(newrev, limit = 0, dynamic_timeout: nil)
request = Gitaly::ListNewBlobsRequest.new(
repository: @gitaly_repo,
commit_id: newrev,
limit: limit
)
+ timeout =
+ if dynamic_timeout
+ [dynamic_timeout, GitalyClient.medium_timeout].min
+ else
+ GitalyClient.medium_timeout
+ end
+
response = GitalyClient
- .call(@storage, :ref_service, :list_new_blobs, request, timeout: GitalyClient.medium_timeout)
+ .call(@storage, :ref_service, :list_new_blobs, request, timeout: timeout)
response.flat_map do |msg|
# Returns an Array of Gitaly::NewBlobObject objects
@@ -232,6 +239,12 @@ module Gitlab
messages
end
+ def pack_refs
+ request = Gitaly::PackRefsRequest.new(repository: @gitaly_repo)
+
+ GitalyClient.call(@storage, :ref_service, :pack_refs, request)
+ end
+
private
def consume_refs_response(response)
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 81fac37ee68..f3589fea39f 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -38,7 +38,7 @@ module Gitlab
def remove_remote(name)
request = Gitaly::RemoveRemoteRequest.new(repository: @gitaly_repo, name: name)
- response = GitalyClient.call(@storage, :remote_service, :remove_remote, request, timeout: GitalyClient.fast_timeout)
+ response = GitalyClient.call(@storage, :remote_service, :remove_remote, request)
response.result
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index a7e20d9429e..d8e9dccb644 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -47,6 +47,13 @@ module Gitlab
response.size
end
+ def get_object_directory_size
+ request = Gitaly::GetObjectDirectorySizeRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :repository_service, :get_object_directory_size, request, timeout: GitalyClient.medium_timeout)
+
+ response.size
+ end
+
def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout)
@@ -190,7 +197,7 @@ module Gitlab
def fsck
request = Gitaly::FsckRequest.new(repository: @gitaly_repo)
- response = GitalyClient.call(@storage, :repository_service, :fsck, request)
+ response = GitalyClient.call(@storage, :repository_service, :fsck, request, timeout: GitalyClient.no_timeout)
if response.error.empty?
return "", 0
@@ -324,13 +331,21 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
end
- def search_files_by_content(ref, query, chunked_response: true)
- request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query, chunked_response: chunked_response)
+ def search_files_by_content(ref, query)
+ request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query)
response = GitalyClient.call(@storage, :repository_service, :search_files_by_content, request)
search_results_from_response(response)
end
+ def disconnect_alternates
+ request = Gitaly::DisconnectGitAlternatesRequest.new(
+ repository: @gitaly_repo
+ )
+
+ GitalyClient.call(@storage, :object_pool_service, :disconnect_git_alternates, request)
+ end
+
private
def search_results_from_response(gitaly_response)
@@ -340,18 +355,11 @@ module Gitlab
gitaly_response.each do |message|
next if message.nil?
- # Old client will ignore :chunked_response flag
- # and return messages with `matches` key.
- # This code path will be removed post 12.0 release
- if message.matches.any?
- matches += message.matches
- else
- current_match << message.match_data
-
- if message.end_of_match
- matches << current_match
- current_match = +""
- end
+ current_match << message.match_data
+
+ if message.end_of_match
+ matches << current_match
+ current_match = +""
end
end
diff --git a/lib/gitlab/gitaly_client/storage_settings.rb b/lib/gitlab/gitaly_client/storage_settings.rb
index 754cccb6b3f..78ef6bfc0ec 100644
--- a/lib/gitlab/gitaly_client/storage_settings.rb
+++ b/lib/gitlab/gitaly_client/storage_settings.rb
@@ -32,11 +32,19 @@ module Gitlab
end
def self.disk_access_denied?
+ return false if rugged_enabled?
+
!temporarily_allowed?(ALLOW_KEY) && GitalyClient.feature_enabled?(DISK_ACCESS_DENIED_FLAG)
rescue
false # Err on the side of caution, don't break gitlab for people
end
+ def self.rugged_enabled?
+ Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.any? do |flag|
+ Feature.enabled?(flag)
+ end
+ end
+
def initialize(storage)
raise InvalidConfigurationError, "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
raise InvalidConfigurationError, INVALID_STORAGE_MESSAGE unless storage.has_key?('path')
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 2b3d622af4d..ce9faad825c 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -22,7 +22,7 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = binary_stringio(content)
+ strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
@@ -49,7 +49,7 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = binary_stringio(content)
+ strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
@@ -87,9 +87,35 @@ module Gitlab
wiki_page_from_iterator(response)
end
- def get_all_pages(limit: 0)
- request = Gitaly::WikiGetAllPagesRequest.new(repository: @gitaly_repo, limit: limit)
+ def list_all_pages(limit: 0, sort: nil, direction_desc: false)
+ sort_value = Gitaly::WikiListPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
+
+ params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
+ params[:sort] = sort_value if sort_value
+
+ request = Gitaly::WikiListPagesRequest.new(params)
+ stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_list_pages, request, timeout: GitalyClient.medium_timeout)
+ stream.each_with_object([]) do |message, pages|
+ page = message.page
+
+ next unless page
+
+ wiki_page = GitalyClient::WikiPage.new(page.to_h)
+ version = new_wiki_page_version(page.version)
+
+ pages << [wiki_page, version]
+ end
+ end
+
+ def load_all_pages(limit: 0, sort: nil, direction_desc: false)
+ sort_value = Gitaly::WikiGetAllPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
+
+ params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
+ params[:sort] = sort_value if sort_value
+
+ request = Gitaly::WikiGetAllPagesRequest.new(params)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout)
+
pages = []
loop do
diff --git a/lib/gitlab/github_import/importer/issue_importer.rb b/lib/gitlab/github_import/importer/issue_importer.rb
index 656d46b6a7d..a468f6d8821 100644
--- a/lib/gitlab/github_import/importer/issue_importer.rb
+++ b/lib/gitlab/github_import/importer/issue_importer.rb
@@ -53,6 +53,7 @@ module Gitlab
description: description,
milestone_id: milestone_finder.id_for(issue),
state: issue.state,
+ state_id: ::Issue.available_states[issue.state],
created_at: issue.created_at,
updated_at: issue.updated_at
}
diff --git a/lib/gitlab/github_import/importer/milestones_importer.rb b/lib/gitlab/github_import/importer/milestones_importer.rb
index 87cf2c8b598..71ff7465d9b 100644
--- a/lib/gitlab/github_import/importer/milestones_importer.rb
+++ b/lib/gitlab/github_import/importer/milestones_importer.rb
@@ -42,6 +42,7 @@ module Gitlab
description: milestone.description,
project_id: project.id,
state: state_for(milestone),
+ due_date: milestone.due_on&.to_date,
created_at: milestone.created_at,
updated_at: milestone.updated_at
}
diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb
index e294173f992..377e873d24d 100644
--- a/lib/gitlab/github_import/importer/pull_request_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_importer.rb
@@ -55,6 +55,7 @@ module Gitlab
source_branch: pull_request.formatted_source_branch,
target_branch: pull_request.target_branch,
state: pull_request.state,
+ state_id: ::MergeRequest.available_states[pull_request.state],
milestone_id: milestone_finder.id_for(pull_request),
author_id: author_id,
assignee_id: user_finder.assignee_id_for(pull_request),
@@ -89,7 +90,7 @@ module Gitlab
return if project.repository.branch_exists?(source_branch)
- project.repository.add_branch(merge_request.author, source_branch, pull_request.source_branch_sha)
+ project.repository.add_branch(project.creator, source_branch, pull_request.source_branch_sha)
rescue Gitlab::Git::CommandError => e
Gitlab::Sentry.track_acceptable_exception(e,
extra: {
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index e2dfb00dcc5..6aad7955415 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -5,6 +5,7 @@ module Gitlab
module Importer
class RepositoryImporter
include Gitlab::ShellAdapter
+ include Gitlab::Utils::StrongMemoize
attr_reader :project, :client, :wiki_formatter
@@ -17,7 +18,7 @@ module Gitlab
# Returns true if we should import the wiki for the project.
# rubocop: disable CodeReuse/ActiveRecord
def import_wiki?
- client.repository(project.import_source)&.has_wiki &&
+ client_repository&.has_wiki &&
!project.wiki_repository_exists? &&
Gitlab::GitalyClient::RemoteService.exists?(wiki_url)
end
@@ -52,6 +53,12 @@ module Gitlab
refmap = Gitlab::GithubImport.refmap
project.repository.fetch_as_mirror(project.import_url, refmap: refmap, forced: true, remote_name: 'github')
+ project.change_head(default_branch) if default_branch
+
+ # The initial fetch can bring in lots of loose refs and objects.
+ # Running a `git gc` will make importing pull requests faster.
+ Projects::HousekeepingService.new(project, :gc).execute
+
true
rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e
fail_import("Failed to import the repository: #{e.message}")
@@ -82,6 +89,18 @@ module Gitlab
project.import_state.mark_as_failed(message)
false
end
+
+ private
+
+ def default_branch
+ client_repository&.default_branch
+ end
+
+ def client_repository
+ strong_memoize(:client_repository) do
+ client.repository(project.import_source)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index 9d81441d96e..1d3541b80c7 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -29,29 +29,13 @@ module Gitlab
end
def execute
- jid = generate_jid
-
- # The original import JID is the JID of the RepositoryImportWorker job,
- # which will be removed once that job completes. Reusing that JID could
- # result in StuckImportJobsWorker marking the job as stuck before we get
- # to running Stage::ImportRepositoryWorker.
- #
- # We work around this by setting the JID to a custom generated one, then
- # refreshing it in the various stages whenever necessary.
- Gitlab::SidekiqStatus
- .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
-
- project.import_state.update_column(:jid, jid)
+ Gitlab::Import::SetAsyncJid.set_jid(project)
Stage::ImportRepositoryWorker
.perform_async(project.id)
true
end
-
- def generate_jid
- "github-importer/#{project.id}"
- end
end
end
end
diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb
index be1334ca98a..d336b1ba797 100644
--- a/lib/gitlab/github_import/representation/diff_note.rb
+++ b/lib/gitlab/github_import/representation/diff_note.rb
@@ -13,7 +13,7 @@ module Gitlab
:diff_hunk, :author, :note, :created_at, :updated_at,
:github_id
- NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i
+ NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i.freeze
# Builds a diff note from a GitHub API response.
#
diff --git a/lib/gitlab/github_import/representation/note.rb b/lib/gitlab/github_import/representation/note.rb
index 070e3b2db8d..5b98ce7d5ed 100644
--- a/lib/gitlab/github_import/representation/note.rb
+++ b/lib/gitlab/github_import/representation/note.rb
@@ -12,7 +12,7 @@ module Gitlab
expose_attribute :noteable_id, :noteable_type, :author, :note,
:created_at, :updated_at, :github_id
- NOTEABLE_TYPE_REGEX = %r{/(?<type>(pull|issues))/(?<iid>\d+)}i
+ NOTEABLE_TYPE_REGEX = %r{/(?<type>(pull|issues))/(?<iid>\d+)}i.freeze
# Builds a note from a GitHub API response.
#
diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb
index 435b74806e7..04dabe423e8 100644
--- a/lib/gitlab/gl_repository.rb
+++ b/lib/gitlab/gl_repository.rb
@@ -1,24 +1,51 @@
# frozen_string_literal: true
module Gitlab
- module GlRepository
- def self.gl_repository(project, is_wiki)
- "#{is_wiki ? 'wiki' : 'project'}-#{project.id}"
+ class GlRepository
+ include Singleton
+
+ PROJECT = RepoType.new(
+ name: :project,
+ access_checker_class: Gitlab::GitAccess,
+ repository_accessor: -> (project) { project.repository }
+ ).freeze
+ WIKI = RepoType.new(
+ name: :wiki,
+ access_checker_class: Gitlab::GitAccessWiki,
+ repository_accessor: -> (project) { project.wiki.repository }
+ ).freeze
+
+ TYPES = {
+ PROJECT.name.to_s => PROJECT,
+ WIKI.name.to_s => WIKI
+ }.freeze
+
+ def self.types
+ instance.types
end
- # rubocop: disable CodeReuse/ActiveRecord
def self.parse(gl_repository)
- match_data = /\A(project|wiki)-([1-9][0-9]*)\z/.match(gl_repository)
- unless match_data
+ type_name, _id = gl_repository.split('-').first
+ type = types[type_name]
+ subject_id = type&.fetch_id(gl_repository)
+
+ unless subject_id
raise ArgumentError, "Invalid GL Repository \"#{gl_repository}\""
end
- type, id = match_data.captures
- project = Project.find_by(id: id)
- wiki = type == 'wiki'
+ project = Project.find_by_id(subject_id)
- [project, wiki]
+ [project, type]
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ def self.default_type
+ PROJECT
+ end
+
+ def types
+ TYPES
+ end
+
+ private_class_method :instance
end
end
diff --git a/lib/gitlab/gl_repository/repo_type.rb b/lib/gitlab/gl_repository/repo_type.rb
new file mode 100644
index 00000000000..19915980d7f
--- /dev/null
+++ b/lib/gitlab/gl_repository/repo_type.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GlRepository
+ class RepoType
+ attr_reader :name,
+ :access_checker_class,
+ :repository_accessor
+
+ def initialize(name:, access_checker_class:, repository_accessor:)
+ @name = name
+ @access_checker_class = access_checker_class
+ @repository_accessor = repository_accessor
+ end
+
+ def identifier_for_subject(subject)
+ "#{name}-#{subject.id}"
+ end
+
+ def fetch_id(identifier)
+ match = /\A#{name}-(?<id>\d+)\z/.match(identifier)
+ match[:id] if match
+ end
+
+ def wiki?
+ self == WIKI
+ end
+
+ def project?
+ self == PROJECT
+ end
+
+ def path_suffix
+ project? ? "" : ".#{name}"
+ end
+
+ def repository_for(subject)
+ repository_accessor.call(subject)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 3235d3ccc4e..582c3065189 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -15,7 +15,12 @@ module Gitlab
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = Gitlab::Routing.url_helpers.help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
- gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn if Gitlab::CurrentSettings.clientside_sentry_enabled
+
+ if Gitlab::CurrentSettings.clientside_sentry_enabled
+ gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn
+ gon.sentry_environment = Gitlab.config.sentry.environment
+ end
+
gon.gitlab_url = Gitlab.config.gitlab.url
gon.revision = Gitlab.revision
gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png')
@@ -25,6 +30,7 @@ module Gitlab
gon.test_env = Rails.env.test?
gon.suggested_label_colors = LabelsHelper.suggested_colors
gon.first_day_of_week = current_user&.first_day_of_week || Gitlab::CurrentSettings.first_day_of_week
+ gon.ee = Gitlab.ee?
if current_user
gon.current_user_id = current_user.id
diff --git a/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb b/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb
index fa4c5d86d44..bbe4b24c7d4 100644
--- a/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb
+++ b/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb
@@ -6,7 +6,7 @@ module Gitlab
module Loggers
class CorrelationIdLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
- { Gitlab::CorrelationId::LOG_KEY => Gitlab::CorrelationId.current_id }
+ { Labkit::Correlation::CorrelationId::LOG_KEY => Labkit::Correlation::CorrelationId.current_id }
end
end
end
diff --git a/lib/gitlab/grape_logging/loggers/perf_logger.rb b/lib/gitlab/grape_logging/loggers/perf_logger.rb
index e3b9c59bd6e..18ea3a8d2f3 100644
--- a/lib/gitlab/grape_logging/loggers/perf_logger.rb
+++ b/lib/gitlab/grape_logging/loggers/perf_logger.rb
@@ -6,7 +6,10 @@ module Gitlab
module Loggers
class PerfLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
- { gitaly_calls: Gitlab::GitalyClient.get_request_count }
+ {
+ gitaly_calls: Gitlab::GitalyClient.get_request_count,
+ gitaly_duration: Gitlab::GitalyClient.query_time_ms
+ }
end
end
end
diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb
index 5e48bf9043d..f8d0208e275 100644
--- a/lib/gitlab/graphql/authorize.rb
+++ b/lib/gitlab/graphql/authorize.rb
@@ -8,22 +8,7 @@ module Gitlab
extend ActiveSupport::Concern
def self.use(schema_definition)
- schema_definition.instrument(:field, Instrumentation.new)
- end
-
- def required_permissions
- # If the `#authorize` call is used on multiple classes, we add the
- # permissions specified on a subclass, to the ones that were specified
- # on it's superclass.
- @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
- superclass.required_permissions.dup
- else
- []
- end
- end
-
- def authorize(*permissions)
- required_permissions.concat(permissions)
+ schema_definition.instrument(:field, Instrumentation.new, after_built_ins: true)
end
end
end
diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb
new file mode 100644
index 00000000000..619ce100421
--- /dev/null
+++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Authorize
+ class AuthorizeFieldService
+ def initialize(field)
+ @field = field
+ @old_resolve_proc = @field.resolve_proc
+ end
+
+ def authorizations?
+ authorizations.present?
+ end
+
+ def authorized_resolve
+ proc do |parent_typed_object, args, ctx|
+ resolved_type = @old_resolve_proc.call(parent_typed_object, args, ctx)
+ authorizing_object = authorize_against(parent_typed_object, resolved_type)
+
+ filter_allowed(ctx[:current_user], resolved_type, authorizing_object)
+ end
+ end
+
+ private
+
+ def authorizations
+ @authorizations ||= (type_authorizations + field_authorizations).uniq
+ end
+
+ # Returns any authorize metadata from the return type of @field
+ def type_authorizations
+ type = @field.type
+
+ # When the return type of @field is a collection, find the singular type
+ if @field.connection?
+ type = node_type_for_relay_connection(type)
+ elsif type.list?
+ type = node_type_for_basic_connection(type)
+ end
+
+ Array.wrap(type.metadata[:authorize])
+ end
+
+ # Returns any authorize metadata from @field
+ def field_authorizations
+ Array.wrap(@field.metadata[:authorize])
+ end
+
+ def authorize_against(parent_typed_object, resolved_type)
+ if scalar_type?
+ # The field is a built-in/scalar type, or a list of scalars
+ # authorize using the parent's object
+ parent_typed_object.object
+ elsif resolved_type.respond_to?(:object)
+ # The field is a type representing a single object, we'll authorize
+ # against the object directly
+ resolved_type.object
+ elsif @field.connection? || resolved_type.is_a?(Array)
+ # The field is a connection or a list of non-built-in types, we'll
+ # authorize each element when rendering
+ nil
+ else
+ # Resolved type is a single object that might not be loaded yet by
+ # the batchloader, we'll authorize that
+ resolved_type
+ end
+ end
+
+ def filter_allowed(current_user, resolved_type, authorizing_object)
+ if authorizing_object
+ # Authorizing fields representing scalars, or a simple field with an object
+ resolved_type if allowed_access?(current_user, authorizing_object)
+ elsif @field.connection?
+ # A connection with pagination, modify the visible nodes in on the
+ # connection type in place
+ resolved_type.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) }
+ resolved_type
+ elsif resolved_type.is_a? Array
+ # A simple list of rendered types each object being an object to authorize
+ resolved_type.select do |single_object_type|
+ allowed_access?(current_user, single_object_type.object)
+ end
+ elsif resolved_type.nil?
+ # We're not rendering anything, for example when a record was not found
+ # no need to do anything
+ else
+ raise "Can't authorize #{@field}"
+ end
+ end
+
+ def allowed_access?(current_user, object)
+ object = object.sync if object.respond_to?(:sync)
+
+ authorizations.all? do |ability|
+ Ability.allowed?(current_user, ability, object)
+ end
+ end
+
+ # Returns the singular type for relay connections.
+ # This will be the type class of edges.node
+ def node_type_for_relay_connection(type)
+ type.unwrap.get_field('edges').type.unwrap.get_field('node').type
+ end
+
+ # Returns the singular type for basic connections, for example `[Types::ProjectType]`
+ def node_type_for_basic_connection(type)
+ type.unwrap
+ end
+
+ def scalar_type?
+ node_type_for_basic_connection(@field.type).kind.scalar?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index a56c4f6368d..b367a97105c 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -6,8 +6,21 @@ module Gitlab
module AuthorizeResource
extend ActiveSupport::Concern
- included do
- extend Gitlab::Graphql::Authorize
+ class_methods do
+ def required_permissions
+ # If the `#authorize` call is used on multiple classes, we add the
+ # permissions specified on a subclass, to the ones that were specified
+ # on it's superclass.
+ @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
+ superclass.required_permissions.dup
+ else
+ []
+ end
+ end
+
+ def authorize(*permissions)
+ required_permissions.concat(permissions)
+ end
end
def find_object(*args)
diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb
index 2a3d790d67b..15ecc3b04f0 100644
--- a/lib/gitlab/graphql/authorize/instrumentation.rb
+++ b/lib/gitlab/graphql/authorize/instrumentation.rb
@@ -6,51 +6,13 @@ module Gitlab
class Instrumentation
# Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful.
- #
- # Collections are not supported. Apply permissions checks for those at the
- # database level instead, to avoid loading superfluous data from the DB
def instrument(_type, field)
- field_definition = field.metadata[:type_class]
- return field unless field_definition.respond_to?(:required_permissions)
- return field if field_definition.required_permissions.empty?
+ service = AuthorizeFieldService.new(field)
- old_resolver = field.resolve_proc
-
- new_resolver = -> (obj, args, ctx) do
- resolved_obj = old_resolver.call(obj, args, ctx)
- checker = build_checker(ctx[:current_user], field_definition.required_permissions)
-
- if resolved_obj.respond_to?(:then)
- resolved_obj.then(&checker)
- else
- checker.call(resolved_obj)
- end
- end
-
- field.redefine do
- resolve(new_resolver)
- end
- end
-
- private
-
- def build_checker(current_user, abilities)
- lambda do |value|
- # Load the elements if they weren't loaded by BatchLoader yet
- value = value.sync if value.respond_to?(:sync)
-
- check = lambda do |object|
- abilities.all? do |ability|
- Ability.allowed?(current_user, ability, object)
- end
- end
-
- case value
- when Array
- value.select(&check)
- else
- value if check.call(value)
- end
+ if service.authorizations?
+ field.redefine { resolve(service.authorized_resolve) }
+ else
+ field
end
end
end
diff --git a/lib/gitlab/graphql/connections/keyset_connection.rb b/lib/gitlab/graphql/connections/keyset_connection.rb
index 851054c0393..715963a44c1 100644
--- a/lib/gitlab/graphql/connections/keyset_connection.rb
+++ b/lib/gitlab/graphql/connections/keyset_connection.rb
@@ -22,8 +22,17 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def paged_nodes
+ # These are the nodes that will be loaded into memory for rendering
+ # So we're ok loading them into memory here as that's bound to happen
+ # anyway. Having them ready means we can modify the result while
+ # rendering the fields.
+ @paged_nodes ||= load_paged_nodes.to_a
+ end
+
+ private
+
+ def load_paged_nodes
if first && last
raise Gitlab::Graphql::Errors::ArgumentError.new("Can only provide either `first` or `last`, not both")
end
@@ -31,12 +40,9 @@ module Gitlab
if last
sliced_nodes.last(limit_value)
else
- sliced_nodes.limit(limit_value)
+ sliced_nodes.limit(limit_value) # rubocop: disable CodeReuse/ActiveRecord
end
end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
def before_slice
if sort_direction == :asc
diff --git a/lib/gitlab/graphql/generic_tracing.rb b/lib/gitlab/graphql/generic_tracing.rb
new file mode 100644
index 00000000000..936b22d5afa
--- /dev/null
+++ b/lib/gitlab/graphql/generic_tracing.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# This class is used as a hook to observe graphql runtime events. From this
+# hook both gitlab metrics and opentracking measurements are generated
+
+module Gitlab
+ module Graphql
+ class GenericTracing < GraphQL::Tracing::PlatformTracing
+ self.platform_keys = {
+ 'lex' => 'graphql.lex',
+ 'parse' => 'graphql.parse',
+ 'validate' => 'graphql.validate',
+ 'analyze_query' => 'graphql.analyze',
+ 'analyze_multiplex' => 'graphql.analyze',
+ 'execute_multiplex' => 'graphql.execute',
+ 'execute_query' => 'graphql.execute',
+ 'execute_query_lazy' => 'graphql.execute',
+ 'execute_field' => 'graphql.execute',
+ 'execute_field_lazy' => 'graphql.execute'
+ }
+
+ def platform_field_key(type, field)
+ "#{type.name}.#{field.name}"
+ end
+
+ def platform_trace(platform_key, key, data, &block)
+ tags = { platform_key: platform_key, key: key }
+ start = Gitlab::Metrics::System.monotonic_time
+
+ with_labkit_tracing(tags, &block)
+ ensure
+ duration = Gitlab::Metrics::System.monotonic_time - start
+
+ graphql_duration_seconds.observe(tags, duration)
+ end
+
+ private
+
+ def with_labkit_tracing(tags, &block)
+ return yield unless Labkit::Tracing.enabled?
+
+ name = "#{tags[:platform_key]}.#{tags[:key]}"
+ span_tags = {
+ 'component' => 'web',
+ 'span.kind' => 'server'
+ }.merge(tags.stringify_keys)
+
+ Labkit::Tracing.with_tracing(operation_name: name, tags: span_tags, &block)
+ end
+
+ def graphql_duration_seconds
+ @graphql_duration_seconds ||= Gitlab::Metrics.histogram(
+ :graphql_duration_seconds,
+ 'GraphQL execution time'
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb
index 5a0099dc6b1..50d3293fcbb 100644
--- a/lib/gitlab/graphql/loaders/batch_model_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb
@@ -12,7 +12,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find
- BatchLoader.for({ model: model_class, id: model_id }).batch do |loader_info, loader|
+ BatchLoader.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
per_model = loader_info.group_by { |info| info[:model] }
per_model.each do |model, info|
ids = info.map { |i| i[:id] }
diff --git a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
new file mode 100644
index 00000000000..5e151f4dbd7
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class BatchProjectStatisticsLoader
+ attr_reader :project_id
+
+ def initialize(project_id)
+ @project_id = project_id
+ end
+
+ def find
+ BatchLoader.for(project_id).batch do |project_ids, loader|
+ ProjectStatistics.for_project_ids(project_ids).each do |statistics|
+ loader.call(statistics.project_id, statistics)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
new file mode 100644
index 00000000000..01b55a1667f
--- /dev/null
+++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module QueryAnalyzers
+ class LoggerAnalyzer
+ COMPLEXITY_ANALYZER = GraphQL::Analysis::QueryComplexity.new { |query, complexity_value| complexity_value }
+ DEPTH_ANALYZER = GraphQL::Analysis::QueryDepth.new { |query, depth_value| depth_value }
+
+ def analyze?(query)
+ Feature.enabled?(:graphql_logging, default_enabled: true)
+ end
+
+ def initial_value(query)
+ variables = process_variables(query.provided_variables)
+ default_initial_values(query).merge({
+ query_string: query.query_string,
+ variables: variables
+ })
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ default_initial_values(query)
+ end
+
+ def call(memo, visit_type, irep_node)
+ memo
+ end
+
+ def final_value(memo)
+ return if memo.nil?
+
+ analyzers = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER]
+ complexity, depth = GraphQL::Analysis.analyze_query(memo[:query], analyzers)
+
+ memo[:depth] = depth
+ memo[:complexity] = complexity
+ memo[:duration] = duration(memo[:time_started]).round(1)
+
+ GraphqlLogger.info(memo.except!(:time_started, :query))
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ end
+
+ private
+
+ def process_variables(variables)
+ if variables.respond_to?(:to_s)
+ variables.to_s
+ else
+ variables
+ end
+ end
+
+ def duration(time_started)
+ nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started
+ nanoseconds * 1000000
+ end
+
+ def default_initial_values(query)
+ {
+ time_started: Gitlab::Metrics::System.monotonic_time,
+ query_string: nil,
+ query: query,
+ variables: nil,
+ duration: nil
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/representation/tree_entry.rb b/lib/gitlab/graphql/representation/tree_entry.rb
new file mode 100644
index 00000000000..7ea83db5876
--- /dev/null
+++ b/lib/gitlab/graphql/representation/tree_entry.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Representation
+ class TreeEntry < SimpleDelegator
+ class << self
+ def decorate(entries, repository)
+ return if entries.nil?
+
+ entries.map do |entry|
+ if entry.is_a?(TreeEntry)
+ entry
+ else
+ self.new(entry, repository)
+ end
+ end
+ end
+ end
+
+ attr_accessor :repository
+
+ def initialize(raw_entry, repository)
+ @repository = repository
+
+ super(raw_entry)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql_logger.rb b/lib/gitlab/graphql_logger.rb
new file mode 100644
index 00000000000..43d917908b6
--- /dev/null
+++ b/lib/gitlab/graphql_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GraphqlLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'graphql_json'
+ end
+ end
+end
diff --git a/lib/gitlab/group_search_results.rb b/lib/gitlab/group_search_results.rb
new file mode 100644
index 00000000000..334642f252e
--- /dev/null
+++ b/lib/gitlab/group_search_results.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GroupSearchResults < SearchResults
+ attr_reader :group
+
+ def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20)
+ super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page)
+
+ @group = group
+ end
+
+ # rubocop:disable CodeReuse/ActiveRecord
+ def users
+ # 1: get all groups the current user has access to
+ groups = GroupsFinder.new(current_user).execute.joins(:users)
+
+ # 2: Get the group's whole hierarchy
+ group_users = @group.direct_and_indirect_users
+
+ # 3: get all users the current user has access to (->
+ # `SearchResults#users`), which also applies the query.
+ users = super
+
+ # 4: filter for users that belong to the previously selected groups
+ users
+ .where(id: group_users.select('id'))
+ .where(id: groups.select('members.user_id'))
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+
+ def issuable_params
+ super.merge(group_id: group.id)
+ end
+ end
+end
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index bf463077dcc..1f0deebea39 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -13,10 +13,18 @@ module Gitlab
#
# @param [Integer] start first project id for the range
# @param [Integer] finish last project id for the range
- def bulk_schedule(start:, finish:)
+ def bulk_schedule_migration(start:, finish:)
::HashedStorage::MigratorWorker.perform_async(start, finish)
end
+ # Schedule a range of projects to be bulk rolledback with #bulk_rollback asynchronously
+ #
+ # @param [Integer] start first project id for the range
+ # @param [Integer] finish last project id for the range
+ def bulk_schedule_rollback(start:, finish:)
+ ::HashedStorage::RollbackerWorker.perform_async(start, finish)
+ end
+
# Start migration of projects from specified range
#
# Flagging a project to be migrated is a synchronous action
@@ -34,6 +42,23 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ # Start rollback of projects from specified range
+ #
+ # Flagging a project to be rolled back is a synchronous action
+ # but the rollback runs through async jobs
+ #
+ # @param [Integer] start first project id for the range
+ # @param [Integer] finish last project id for the range
+ # rubocop: disable CodeReuse/ActiveRecord
+ def bulk_rollback(start:, finish:)
+ projects = build_relation(start, finish)
+
+ projects.with_route.find_each(batch_size: BATCH_SIZE) do |project|
+ rollback(project)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
# Flag a project to be migrated to Hashed Storage
#
# @param [Project] project that will be migrated
@@ -45,12 +70,37 @@ module Gitlab
Rails.logger.error("#{err.message} migrating storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # Flag a project to be rolled-back to Legacy Storage
+ #
+ # @param [Project] project that will be rolled-back
def rollback(project)
- # TODO: implement rollback strategy
+ Rails.logger.info "Starting storage rollback of #{project.full_path} (ID=#{project.id})..."
+
+ project.rollback_to_legacy_storage!
+ rescue => err
+ Rails.logger.error("#{err.message} rolling-back storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
+ end
+
+ # Returns whether we have any pending storage migration
+ #
+ def migration_pending?
+ any_non_empty_queue?(::HashedStorage::MigratorWorker, ::HashedStorage::ProjectMigrateWorker)
+ end
+
+ # Returns whether we have any pending storage rollback
+ #
+ def rollback_pending?
+ any_non_empty_queue?(::HashedStorage::RollbackerWorker, ::HashedStorage::ProjectRollbackWorker)
end
private
+ def any_non_empty_queue?(*workers)
+ workers.any? do |worker|
+ !Sidekiq::Queue.new(worker.queue).size.zero?
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def build_relation(start, finish)
relation = Project
diff --git a/lib/gitlab/hashed_storage/rake_helper.rb b/lib/gitlab/hashed_storage/rake_helper.rb
index 38f552fab03..87a31a37e3f 100644
--- a/lib/gitlab/hashed_storage/rake_helper.rb
+++ b/lib/gitlab/hashed_storage/rake_helper.rb
@@ -24,7 +24,7 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def self.project_id_batches(&block)
+ def self.project_id_batches_migration(&block)
Project.with_unmigrated_storage.in_batches(of: batch_size, start: range_from, finish: range_to) do |relation| # rubocop: disable Cop/InBatches
ids = relation.pluck(:id)
@@ -34,6 +34,16 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
+ def self.project_id_batches_rollback(&block)
+ Project.with_storage_feature(:repository).in_batches(of: batch_size, start: range_from, finish: range_to) do |relation| # rubocop: disable Cop/InBatches
+ ids = relation.pluck(:id)
+
+ yield ids.min, ids.max
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
def self.legacy_attachments_relation
Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments])
JOIN projects
diff --git a/lib/gitlab/health_checks/metric.rb b/lib/gitlab/health_checks/metric.rb
index 62a5216d159..184083de2bc 100644
--- a/lib/gitlab/health_checks/metric.rb
+++ b/lib/gitlab/health_checks/metric.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab::HealthChecks
diff --git a/lib/gitlab/health_checks/result.rb b/lib/gitlab/health_checks/result.rb
index d32a6980eb8..4586b1d94a7 100644
--- a/lib/gitlab/health_checks/result.rb
+++ b/lib/gitlab/health_checks/result.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab::HealthChecks
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index a4e60bbd828..381f1dd4e55 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -39,7 +39,7 @@ module Gitlab
private
def custom_language
- return nil unless @language
+ return unless @language
Rouge::Lexer.find_fancy(@language)
end
diff --git a/lib/gitlab/hook_data/issuable_builder.rb b/lib/gitlab/hook_data/issuable_builder.rb
index 0803df65632..b8da6731081 100644
--- a/lib/gitlab/hook_data/issuable_builder.rb
+++ b/lib/gitlab/hook_data/issuable_builder.rb
@@ -20,11 +20,7 @@ module Gitlab
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
}
- if issuable.is_a?(Issue)
- hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
- else
- hook_data[:assignee] = issuable.assignee.hook_attrs if issuable.assignee
- end
+ hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
hook_data
end
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index c99353b9d49..d39ff8c21cc 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -48,7 +48,7 @@ module Gitlab
}
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
- .merge!(attrs)
+ .merge!(attrs)
end
end
end
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index ad38e26e40a..a8e993e087e 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -34,7 +34,6 @@ module Gitlab
end
SAFE_HOOK_RELATIONS = %i[
- assignee
labels
total_time_spent
].freeze
@@ -51,11 +50,13 @@ module Gitlab
work_in_progress: merge_request.work_in_progress?,
total_time_spent: merge_request.total_time_spent,
human_total_time_spent: merge_request.human_total_time_spent,
- human_time_estimate: merge_request.human_time_estimate
+ human_time_estimate: merge_request.human_time_estimate,
+ assignee_ids: merge_request.assignee_ids,
+ assignee_id: merge_request.assignee_ids.first # This key is deprecated
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
- .merge!(attrs)
+ .merge!(attrs)
end
end
end
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index bcd9e2be35f..db2b4dde244 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -9,9 +9,16 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
RedirectionTooDeep = Class.new(StandardError)
+ HTTP_ERRORS = [
+ SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET,
+ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout,
+ Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
+ Gitlab::HTTP::RedirectionTooDeep
+ ].freeze
+
include HTTParty # rubocop:disable Gitlab/HTTParty
- connection_adapter ProxyHTTPConnectionAdapter
+ connection_adapter HTTPConnectionAdapter
def self.perform_request(http_method, path, options, &block)
super
diff --git a/lib/gitlab/http_connection_adapter.rb b/lib/gitlab/http_connection_adapter.rb
new file mode 100644
index 00000000000..41eab3658bc
--- /dev/null
+++ b/lib/gitlab/http_connection_adapter.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+# This class is part of the Gitlab::HTTP wrapper. Depending on the value
+# of the global setting allow_local_requests_from_hooks_and_services this adapter
+# will allow/block connection to internal IPs and/or urls.
+#
+# This functionality can be overridden by providing the setting the option
+# allow_local_requests = true in the request. For example:
+# Gitlab::HTTP.get('http://www.gitlab.com', allow_local_requests: true)
+#
+# This option will take precedence over the global setting.
+module Gitlab
+ class HTTPConnectionAdapter < HTTParty::ConnectionAdapter
+ def connection
+ begin
+ @uri, hostname = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_local_requests?,
+ allow_localhost: allow_local_requests?,
+ dns_rebind_protection: dns_rebind_protection?)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
+ end
+
+ super.tap do |http|
+ http.hostname_override = hostname if hostname
+ end
+ end
+
+ private
+
+ def allow_local_requests?
+ options.fetch(:allow_local_requests, allow_settings_local_requests?)
+ end
+
+ def dns_rebind_protection?
+ return false if Gitlab.http_proxy_env?
+
+ Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
+ end
+
+ def allow_settings_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+ end
+ end
+end
diff --git a/lib/gitlab/i18n/metadata_entry.rb b/lib/gitlab/i18n/metadata_entry.rb
index 3764e379681..4facd10bfc8 100644
--- a/lib/gitlab/i18n/metadata_entry.rb
+++ b/lib/gitlab/i18n/metadata_entry.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def expected_forms
- return nil unless plural_information
+ return unless plural_information
plural_information['nplurals'].to_i
end
diff --git a/lib/gitlab/import/merge_request_helpers.rb b/lib/gitlab/import/merge_request_helpers.rb
index fa3ff6c3f12..4bc39868389 100644
--- a/lib/gitlab/import/merge_request_helpers.rb
+++ b/lib/gitlab/import/merge_request_helpers.rb
@@ -22,7 +22,7 @@ module Gitlab
# additional work that is strictly necessary.
merge_request_id = insert_and_return_id(attributes, project.merge_requests)
- merge_request = project.merge_requests.reload.find(merge_request_id)
+ merge_request = project.merge_requests.reset.find(merge_request_id)
[merge_request, false]
end
@@ -38,7 +38,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def insert_or_replace_git_data(merge_request, source_branch_sha, target_branch_sha, already_exists = false)
# These fields are set so we can create the correct merge request
# diffs.
@@ -47,24 +46,21 @@ module Gitlab
merge_request.keep_around_commit
+ # We force to recreate all diffs to replace all existing data
+ # We use `.all` as otherwise `dependent: :nullify` (the default)
+ # takes an effect
+ merge_request.merge_request_diffs.all.delete_all if already_exists
+
# MR diffs normally use an "after_save" hook to pull data from Git.
# All of this happens in the transaction started by calling
# create/save/etc. This in turn can lead to these transactions being
# held open for much longer than necessary. To work around this we
# first save the diff, then populate it.
- diff =
- if already_exists
- merge_request.merge_request_diffs.take ||
- merge_request.merge_request_diffs.build
- else
- merge_request.merge_request_diffs.build
- end
-
+ diff = merge_request.merge_request_diffs.build
diff.importing = true
diff.save
diff.save_git_content
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/gitlab/import/set_async_jid.rb b/lib/gitlab/import/set_async_jid.rb
new file mode 100644
index 00000000000..584d24045ee
--- /dev/null
+++ b/lib/gitlab/import/set_async_jid.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# The original import JID is the JID of the RepositoryImportWorker job,
+# which will be removed once that job completes. Reusing that JID could
+# result in StuckImportJobsWorker marking the job as stuck before we get
+# to running Stage::ImportRepositoryWorker.
+#
+# We work around this by setting the JID to a custom generated one, then
+# refreshing it in the various stages whenever necessary.
+module Gitlab
+ module Import
+ module SetAsyncJid
+ def self.set_jid(project)
+ jid = generate_jid(project)
+
+ Gitlab::SidekiqStatus
+ .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+
+ project.import_state.update_column(:jid, jid)
+ end
+
+ def self.generate_jid(project)
+ "async-import/#{project.id}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
index b30900f7c61..acb7f225b17 100644
--- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
@@ -8,7 +8,7 @@ module Gitlab
POST_METHOD = 'POST'.freeze
INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze
- validates :url, url: true
+ validates :url, addressable_url: true
validate do
unless [PUT_METHOD, POST_METHOD].include?(http_method.upcase)
@@ -30,10 +30,7 @@ module Gitlab
def handle_response_error(response)
unless response.success?
- error_code = response.dig('Error', 'Code') || response.code
- error_message = response.dig('Error', 'Message') || response.message
-
- raise StrategyError.new("Error uploading the project. Code #{error_code}: #{error_message}")
+ raise StrategyError.new("Error uploading the project. Code #{response.code}: #{response.message}")
end
end
diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb
index 93b37b7bc5f..c28a1674018 100644
--- a/lib/gitlab/import_export/attribute_cleaner.rb
+++ b/lib/gitlab/import_export/attribute_cleaner.rb
@@ -4,6 +4,7 @@ module Gitlab
module ImportExport
class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
+ PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_html\Z/).freeze
def self.clean(*args)
new(*args).clean
@@ -24,7 +25,11 @@ module Gitlab
private
def prohibited_key?(key)
- key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
+ key =~ PROHIBITED_REFERENCES && !permitted_key?(key)
+ end
+
+ def permitted_key?(key)
+ ALLOWED_REFERENCES.include?(key)
end
def excluded_key?(key)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index fa54fc17d95..7bbcb53f016 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -33,6 +33,7 @@ project_tree:
- :user
- merge_requests:
- :metrics
+ - :suggestions
- notes:
- :author
- events:
@@ -74,6 +75,7 @@ project_tree:
- :project_badges
- :ci_cd_settings
- :error_tracking_setting
+ - :metrics_setting
# Only include the following attributes for the models specified.
included_attributes:
@@ -117,6 +119,8 @@ excluded_attributes:
- :description_html
- :repository_languages
- :bfg_object_map
+ - :detected_repository_languages
+ - :tag_list
namespaces:
- :runners_token
- :runners_token_encrypted
@@ -172,6 +176,8 @@ excluded_attributes:
- :enabled
methods:
+ notes:
+ - :type
labels:
- :type
label:
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
index 477499e1688..b145f37c052 100644
--- a/lib/gitlab/import_export/json_hash_builder.rb
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -67,7 +67,7 @@ module Gitlab
# +value+ existing model to be included in the hash
# +parsed_hash+ the original hash
def parse_hash(value)
- return nil if already_contains_methods?(value)
+ return if already_contains_methods?(value)
@attributes_finder.parse(value) do |hash|
{ include: hash_or_merge(value, hash) }
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 6be95a16513..a154de5419e 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -59,7 +59,11 @@ module Gitlab
end
def member_hash(member)
- parsed_hash(member).merge('source_id' => @project.id, 'importing' => true)
+ parsed_hash(member).merge(
+ 'source_id' => @project.id,
+ 'importing' => true,
+ 'access_level' => [member['access_level'], ProjectMember::MAINTAINER].min
+ ).except('user_id')
end
def parsed_hash(member)
diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index 040a70d6775..deb2f59f05f 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -20,6 +20,17 @@ module Gitlab
create_target_branch unless branch_exists?(@merge_request.target_branch)
end
+ # The merge_request_diff associated with the current @merge_request might
+ # be invalid. Than means, when the @merge_request object is saved, the
+ # @merge_request.merge_request_diff won't. This can leave the merge request
+ # in an invalid state, because a merge request must have an associated
+ # merge request diff.
+ # In this change, if the associated merge request diff is invalid, we set
+ # it to nil. This change, in association with the after callback
+ # :ensure_merge_request_diff in the MergeRequest class, makes that
+ # when the merge request is going to be created and it doesn't have
+ # one, a default one will be generated.
+ @merge_request.merge_request_diff = nil unless @merge_request.merge_request_diff&.valid?
@merge_request
end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 51001750a6c..20caadb89c0 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -129,7 +129,7 @@ module Gitlab
def visibility_level
level = override_params['visibility_level'] || json_params['visibility_level'] || @project.visibility_level
- level = @project.group.visibility_level if @project.group && level > @project.group.visibility_level
+ level = @project.group.visibility_level if @project.group && level.to_i > @project.group.visibility_level
{ 'visibility_level' => level }
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 099b488f68e..e1e70a008d9 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -25,7 +25,8 @@ module Gitlab
metrics: 'MergeRequest::Metrics',
ci_cd_settings: 'ProjectCiCdSetting',
error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting',
- links: 'Releases::Link' }.freeze
+ links: 'Releases::Link',
+ metrics_setting: 'ProjectMetricsSetting' }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
@@ -75,7 +76,7 @@ module Gitlab
# the relation_hash, updating references with new object IDs, mapping users using
# the "members_mapper" object, also updating notes if required.
def create
- return nil if unknown_service?
+ return if unknown_service?
setup_models
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 67952ca0f2d..e4d625b5738 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -20,7 +20,8 @@ module Gitlab
ImportSource.new('git', 'Repo by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer),
- ImportSource.new('manifest', 'Manifest file', nil)
+ ImportSource.new('manifest', 'Manifest file', nil),
+ ImportSource.new('phabricator', 'Phabricator', Gitlab::PhabricatorImport::Importer)
].freeze
class << self
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index cc0c633b943..8b346f6d7d2 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -57,7 +57,7 @@ module Gitlab
def address_regex
wildcard_address = config.address
- return nil unless wildcard_address
+ return unless wildcard_address
regex = Regexp.escape(wildcard_address)
regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)')
diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb
index 1adf83739ad..e4bc437d787 100644
--- a/lib/gitlab/json_cache.rb
+++ b/lib/gitlab/json_cache.rb
@@ -71,7 +71,36 @@ module Gitlab
end
def parse_entry(raw, klass)
- klass.new(raw) if valid_entry?(raw, klass)
+ return unless valid_entry?(raw, klass)
+ return klass.new(raw) unless klass.ancestors.include?(ActiveRecord::Base)
+
+ # When the cached value is a persisted instance of ActiveRecord::Base in
+ # some cases a relation can return an empty collection becauses scope.none!
+ # is being applied on ActiveRecord::Associations::CollectionAssociation#scope
+ # when the new_record? method incorrectly returns false.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9903#note_145329964
+ klass
+ .allocate
+ .init_with(
+ "attributes" => attributes_for(klass, raw),
+ "new_record" => new_record?(raw, klass)
+ )
+ end
+
+ def attributes_for(klass, raw)
+ # We have models that leave out some fields from the JSON export for
+ # security reasons, e.g. models that include the CacheMarkdownField.
+ # The ActiveRecord::AttributeSet we build from raw does know about
+ # these columns so we need manually set them.
+ missing_attributes = (klass.columns.map(&:name) - raw.keys)
+ missing_attributes.each { |column| raw[column] = nil }
+
+ klass.attributes_builder.build_from_database(raw, {})
+ end
+
+ def new_record?(raw, klass)
+ raw.fetch(klass.primary_key, nil).blank?
end
def valid_entry?(raw, klass)
diff --git a/lib/gitlab/json_logger.rb b/lib/gitlab/json_logger.rb
index a5a5759cc89..ab34fb03158 100644
--- a/lib/gitlab/json_logger.rb
+++ b/lib/gitlab/json_logger.rb
@@ -10,7 +10,7 @@ module Gitlab
data = {}
data[:severity] = severity
data[:time] = timestamp.utc.iso8601(3)
- data[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
+ data[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
case message
when String
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index a9957a85d48..d46b5e3aee3 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -24,6 +24,30 @@ module Gitlab
end
end
+ # Filters an array of pods (as returned by the kubernetes API) by their annotations
+ def filter_by_annotation(items, annotations = {})
+ items.select do |item|
+ metadata = item.fetch("metadata", {})
+ item_annotations = metadata.fetch("annotations", nil)
+ next unless item_annotations
+
+ annotations.all? { |k, v| item_annotations[k.to_s] == v }
+ end
+ end
+
+ # Filters an array of pods (as returned by the kubernetes API) by their project and environment
+ def filter_by_project_environment(items, app, env)
+ pods = filter_by_annotation(items, {
+ 'app.gitlab.com/app' => app,
+ 'app.gitlab.com/env' => env
+ })
+ return pods unless pods.empty?
+
+ filter_by_label(items, {
+ 'app' => env, # deprecated: replaced by app.gitlab.com/env
+ })
+ end
+
# Converts a pod (as returned by the kubernetes API) into a terminal
def terminals_for_pod(api_url, namespace, pod)
metadata = pod.fetch("metadata", {})
diff --git a/lib/gitlab/kubernetes/errors.rb b/lib/gitlab/kubernetes/errors.rb
new file mode 100644
index 00000000000..81bf636eef7
--- /dev/null
+++ b/lib/gitlab/kubernetes/errors.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module Errors
+ CONNECTION = [
+ SocketError,
+ OpenSSL::SSL::SSLError,
+ Errno::ECONNRESET,
+ Errno::ENETUNREACH,
+ Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH,
+ Net::OpenTimeout,
+ Net::ReadTimeout,
+ IPAddr::InvalidAddressError
+ ].freeze
+
+ AUTHENTICATION = [
+ OpenSSL::X509::CertificateError
+ ].freeze
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index 7dfd9ed4f35..978cafae9ac 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -22,6 +22,14 @@ module Gitlab
alias_method :update, :install
+ def uninstall(command)
+ namespace.ensure_exists!
+ create_config_map(command)
+
+ delete_pod!(command.pod_name)
+ kubeclient.create_pod(command.pod_resource)
+ end
+
##
# Returns Pod phase
#
diff --git a/lib/gitlab/kubernetes/helm/delete_command.rb b/lib/gitlab/kubernetes/helm/delete_command.rb
new file mode 100644
index 00000000000..876994d2678
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/delete_command.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module Helm
+ class DeleteCommand
+ include BaseCommand
+ include ClientCommand
+
+ attr_accessor :name, :files
+
+ def initialize(name:, rbac:, files:)
+ @name = name
+ @files = files
+ @rbac = rbac
+ end
+
+ def generate_script
+ super + [
+ init_command,
+ wait_for_tiller_command,
+ delete_command
+ ].compact.join("\n")
+ end
+
+ def pod_name
+ "uninstall-#{name}"
+ end
+
+ def rbac?
+ @rbac
+ end
+
+ private
+
+ def delete_command
+ command = ['helm', 'delete', '--purge', name] + optional_tls_flags
+
+ command.shelljoin
+ end
+
+ def optional_tls_flags
+ return [] unless files.key?(:'ca.pem')
+
+ [
+ '--tls',
+ '--tls-ca-cert', "#{files_dir}/ca.pem",
+ '--tls-cert', "#{files_dir}/cert.pem",
+ '--tls-key', "#{files_dir}/key.pem"
+ ]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
index f931248b747..e33ba9305ce 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -7,7 +7,8 @@ module Gitlab
include BaseCommand
include ClientCommand
- attr_reader :name, :files, :chart, :version, :repository, :preinstall, :postinstall
+ attr_reader :name, :files, :chart, :repository, :preinstall, :postinstall
+ attr_accessor :version
def initialize(name:, chart:, files:, rbac:, version: nil, repository: nil, preinstall: nil, postinstall: nil)
@name = name
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 624c2c67551..de14df56555 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -82,6 +82,8 @@ module Gitlab
def initialize(api_prefix, **kubeclient_options)
@api_prefix = api_prefix
@kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
+
+ validate_url!
end
def create_or_update_cluster_role_binding(resource)
@@ -118,6 +120,12 @@ module Gitlab
private
+ def validate_url!
+ return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+
+ Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
+ end
+
def cluster_role_binding_exists?(resource)
get_cluster_role_binding(resource.metadata.name)
rescue ::Kubeclient::ResourceNotFoundError
diff --git a/lib/gitlab/kubernetes/namespace.rb b/lib/gitlab/kubernetes/namespace.rb
index 919f19c86d7..8a3bea95a04 100644
--- a/lib/gitlab/kubernetes/namespace.rb
+++ b/lib/gitlab/kubernetes/namespace.rb
@@ -19,11 +19,40 @@ module Gitlab
def create!
resource = ::Kubeclient::Resource.new(metadata: { name: name })
+ log_event(:begin_create)
@client.create_namespace(resource)
end
def ensure_exists!
exists? || create!
+ rescue ::Kubeclient::HttpError => error
+ log_create_failed(error)
+ raise
+ end
+
+ private
+
+ def log_create_failed(error)
+ logger.error({
+ exception: error.class.name,
+ status_code: error.error_code,
+ namespace: name,
+ class_name: self.class.name,
+ event: :failed_to_create_namespace,
+ message: error.message
+ })
+ end
+
+ def log_event(event)
+ logger.info(
+ namespace: name,
+ class_name: self.class.name,
+ event: event
+ )
+ end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
end
end
end
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index bc952147667..bbdd094e33b 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -68,7 +68,7 @@ module Gitlab
end
def user(login)
- return nil unless login.present?
+ return unless login.present?
return @users[login] if @users.key?(login)
@users[login] = api.user(login)
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index f3323c98af2..70b18221a66 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -89,12 +89,10 @@ module Gitlab
def import_labels
fetch_resources(:labels, repo, per_page: 100) do |labels|
labels.each do |raw|
- begin
- gh_label = LabelFormatter.new(project, raw)
- gh_label.create!
- rescue => e
- errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(gh_label.url), errors: e.message }
- end
+ gh_label = LabelFormatter.new(project, raw)
+ gh_label.create!
+ rescue => e
+ errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(gh_label.url), errors: e.message }
end
end
@@ -104,12 +102,10 @@ module Gitlab
def import_milestones
fetch_resources(:milestones, repo, state: :all, per_page: 100) do |milestones|
milestones.each do |raw|
- begin
- gh_milestone = MilestoneFormatter.new(project, raw)
- gh_milestone.create!
- rescue => e
- errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(gh_milestone.url), errors: e.message }
- end
+ gh_milestone = MilestoneFormatter.new(project, raw)
+ gh_milestone.create!
+ rescue => e
+ errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(gh_milestone.url), errors: e.message }
end
end
end
@@ -223,24 +219,22 @@ module Gitlab
def create_comments(comments)
ActiveRecord::Base.no_touching do
comments.each do |raw|
- begin
- comment = CommentFormatter.new(project, raw, client)
+ comment = CommentFormatter.new(project, raw, client)
- # GH does not return info about comment's parent, so we guess it by checking its URL!
- *_, parent, iid = URI(raw.html_url).path.split('/')
+ # GH does not return info about comment's parent, so we guess it by checking its URL!
+ *_, parent, iid = URI(raw.html_url).path.split('/')
- issuable = if parent == 'issues'
- Issue.find_by(project_id: project.id, iid: iid)
- else
- MergeRequest.find_by(target_project_id: project.id, iid: iid)
- end
+ issuable = if parent == 'issues'
+ Issue.find_by(project_id: project.id, iid: iid)
+ else
+ MergeRequest.find_by(target_project_id: project.id, iid: iid)
+ end
- next unless issuable
+ next unless issuable
- issuable.notes.create!(comment.attributes)
- rescue => e
- errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
- end
+ issuable.notes.create!(comment.attributes)
+ rescue => e
+ errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end
end
@@ -281,12 +275,10 @@ module Gitlab
def import_releases
fetch_resources(:releases, repo, per_page: 100) do |releases|
releases.each do |raw|
- begin
- gh_release = ReleaseFormatter.new(project, raw)
- gh_release.create! if gh_release.valid?
- rescue => e
- errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(gh_release.url), errors: e.message }
- end
+ gh_release = ReleaseFormatter.new(project, raw)
+ gh_release.create! if gh_release.valid?
+ rescue => e
+ errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(gh_release.url), errors: e.message }
end
end
end
diff --git a/lib/gitlab/legacy_github_import/project_creator.rb b/lib/gitlab/legacy_github_import/project_creator.rb
index ca1a1b8e9bd..b484b69c932 100644
--- a/lib/gitlab/legacy_github_import/project_creator.rb
+++ b/lib/gitlab/legacy_github_import/project_creator.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def visibility_level
- visibility_level = repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC
+ visibility_level = repo.private ? Gitlab::VisibilityLevel::PRIVATE : @namespace.visibility_level
visibility_level = Gitlab::CurrentSettings.default_project_visibility if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level)
visibility_level
diff --git a/lib/gitlab/legacy_github_import/release_formatter.rb b/lib/gitlab/legacy_github_import/release_formatter.rb
index 8c0c17780ca..746786b5a66 100644
--- a/lib/gitlab/legacy_github_import/release_formatter.rb
+++ b/lib/gitlab/legacy_github_import/release_formatter.rb
@@ -7,6 +7,7 @@ module Gitlab
{
project: project,
tag: raw_data.tag_name,
+ name: raw_data.name,
description: raw_data.body,
created_at: raw_data.created_at,
updated_at: raw_data.created_at
diff --git a/lib/gitlab/legacy_github_import/user_formatter.rb b/lib/gitlab/legacy_github_import/user_formatter.rb
index ec0e221b1ff..889e6aaa968 100644
--- a/lib/gitlab/legacy_github_import/user_formatter.rb
+++ b/lib/gitlab/legacy_github_import/user_formatter.rb
@@ -25,7 +25,7 @@ module Gitlab
end
def find_by_email
- return nil unless email
+ return unless email
User.find_by_any_email(email)
.try(:id)
@@ -33,7 +33,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find_by_external_uid
- return nil unless id
+ return unless id
identities = ::Identity.arel_table
diff --git a/lib/gitlab/lets_encrypt/challenge.rb b/lib/gitlab/lets_encrypt/challenge.rb
new file mode 100644
index 00000000000..136268c974b
--- /dev/null
+++ b/lib/gitlab/lets_encrypt/challenge.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LetsEncrypt
+ class Challenge
+ def initialize(acme_challenge)
+ @acme_challenge = acme_challenge
+ end
+
+ delegate :token, :file_content, :status, :request_validation, to: :acme_challenge
+
+ private
+
+ attr_reader :acme_challenge
+ end
+ end
+end
diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb
new file mode 100644
index 00000000000..66aea137012
--- /dev/null
+++ b/lib/gitlab/lets_encrypt/client.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LetsEncrypt
+ class Client
+ include Gitlab::Utils::StrongMemoize
+
+ PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory'
+ STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
+
+ def new_order(domain_name)
+ ensure_account
+
+ acme_order = acme_client.new_order(identifiers: [domain_name])
+
+ ::Gitlab::LetsEncrypt::Order.new(acme_order)
+ end
+
+ def load_order(url)
+ ensure_account
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ ::Gitlab::LetsEncrypt::Order.new(acme_client.order(url: url))
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
+ def load_challenge(url)
+ ensure_account
+
+ ::Gitlab::LetsEncrypt::Challenge.new(acme_client.challenge(url: url))
+ end
+
+ def terms_of_service_url
+ acme_client.terms_of_service
+ end
+
+ def enabled?
+ return false unless Feature.enabled?(:pages_auto_ssl)
+
+ return false unless private_key
+
+ Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
+ end
+
+ private
+
+ def acme_client
+ @acme_client ||= ::Acme::Client.new(private_key: private_key, directory: acme_api_directory_url)
+ end
+
+ def private_key
+ strong_memoize(:private_key) do
+ private_key_string = Gitlab::CurrentSettings.lets_encrypt_private_key
+ private_key_string ||= generate_private_key
+ OpenSSL::PKey.read(private_key_string) if private_key_string
+ end
+ end
+
+ def admin_email
+ Gitlab::CurrentSettings.lets_encrypt_notification_email
+ end
+
+ def contact
+ "mailto:#{admin_email}"
+ end
+
+ def ensure_account
+ raise 'Acme integration is disabled' unless enabled?
+
+ @acme_account ||= acme_client.new_account(contact: contact, terms_of_service_agreed: true)
+ end
+
+ def acme_api_directory_url
+ if Rails.env.production?
+ PRODUCTION_DIRECTORY_URL
+ else
+ STAGING_DIRECTORY_URL
+ end
+ end
+
+ def generate_private_key
+ return if Gitlab::Database.read_only?
+
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+ application_settings.with_lock do
+ unless application_settings.lets_encrypt_private_key
+ application_settings.update(lets_encrypt_private_key: OpenSSL::PKey::RSA.new(4096).to_pem)
+ end
+
+ application_settings.lets_encrypt_private_key
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/lets_encrypt/order.rb b/lib/gitlab/lets_encrypt/order.rb
new file mode 100644
index 00000000000..9c2365f98a8
--- /dev/null
+++ b/lib/gitlab/lets_encrypt/order.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LetsEncrypt
+ class Order
+ def initialize(acme_order)
+ @acme_order = acme_order
+ end
+
+ def new_challenge
+ authorization = @acme_order.authorizations.first
+ challenge = authorization.http
+ ::Gitlab::LetsEncrypt::Challenge.new(challenge)
+ end
+
+ def request_certificate(domain:, private_key:)
+ csr = ::Acme::Client::CertificateRequest.new(
+ private_key: OpenSSL::PKey.read(private_key),
+ subject: { common_name: domain }
+ )
+
+ acme_order.finalize(csr: csr)
+ end
+
+ delegate :url, :status, :expires, :certificate, to: :acme_order
+
+ private
+
+ attr_reader :acme_order
+ end
+ end
+end
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index 31e6fc9d8c7..124e34562c1 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -35,8 +35,7 @@ module Gitlab
end
def token_valid?(token_to_check)
- HMACToken.new(actor).token_valid?(token_to_check) ||
- LegacyRedisDeviseToken.new(actor).token_valid?(token_to_check)
+ HMACToken.new(actor).token_valid?(token_to_check)
end
def deploy_key_pushable?(project)
@@ -103,44 +102,5 @@ module Gitlab
Settings.attr_encrypted_db_key_base.first(16)
end
end
-
- # TODO: LegacyRedisDeviseToken and references need to be removed after
- # next released milestone
- #
- class LegacyRedisDeviseToken
- TOKEN_LENGTH = 50
- DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins
-
- def initialize(actor)
- @actor = actor
- end
-
- def token_valid?(token_to_check)
- Devise.secure_compare(stored_token, token_to_check)
- end
-
- def stored_token
- Gitlab::Redis::SharedState.with { |redis| redis.get(state_key) }
- end
-
- # This method exists purely to facilitate legacy testing to ensure the
- # same redis key is used.
- #
- def store_new_token(expiry_time_in_ms = DEFAULT_EXPIRY_TIME)
- Gitlab::Redis::SharedState.with do |redis|
- new_token = Devise.friendly_token(TOKEN_LENGTH)
- redis.set(state_key, new_token, px: expiry_time_in_ms)
- new_token
- end
- end
-
- private
-
- attr_reader :actor
-
- def state_key
- "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}"
- end
- end
end
end
diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb
new file mode 100644
index 00000000000..0354c710a3f
--- /dev/null
+++ b/lib/gitlab/markdown_cache.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ # Increment this number every time the renderer changes its output
+ CACHE_COMMONMARK_VERSION_START = 10
+ CACHE_COMMONMARK_VERSION = 16
+
+ BaseError = Class.new(StandardError)
+ UnsupportedClassError = Class.new(BaseError)
+ end
+end
diff --git a/lib/gitlab/markdown_cache/active_record/extension.rb b/lib/gitlab/markdown_cache/active_record/extension.rb
new file mode 100644
index 00000000000..f3abe631779
--- /dev/null
+++ b/lib/gitlab/markdown_cache/active_record/extension.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module ActiveRecord
+ module Extension
+ extend ActiveSupport::Concern
+
+ included do
+ # Using before_update here conflicts with elasticsearch-model somehow
+ before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
+ before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
+ end
+
+ # Always exclude _html fields from attributes (including serialization).
+ # They contain unredacted HTML, which would be a security issue
+ def attributes
+ attrs = super
+ html_fields = cached_markdown_fields.html_fields
+ whitelisted = cached_markdown_fields.html_fields_whitelisted
+ exclude_fields = html_fields - whitelisted
+
+ attrs.except!(*exclude_fields)
+ attrs.delete('cached_markdown_version') if whitelisted.empty?
+
+ attrs
+ end
+
+ def changed_markdown_fields
+ changed_attributes.keys.map(&:to_s) & cached_markdown_fields.markdown_fields.map(&:to_s)
+ end
+
+ def write_markdown_field(field_name, value)
+ write_attribute(field_name, value)
+ end
+
+ def markdown_field_changed?(field_name)
+ attribute_changed?(field_name)
+ end
+
+ def save_markdown(updates)
+ return unless persisted? && Gitlab::Database.read_write?
+
+ update_columns(updates)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/field_data.rb b/lib/gitlab/markdown_cache/field_data.rb
new file mode 100644
index 00000000000..14622c0f186
--- /dev/null
+++ b/lib/gitlab/markdown_cache/field_data.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ # Knows about the relationship between markdown and html field names, and
+ # stores the rendering contexts for the latter
+ class FieldData
+ def initialize
+ @data = {}
+ end
+
+ delegate :[], :[]=, to: :@data
+
+ def markdown_fields
+ @data.keys
+ end
+
+ def html_field(markdown_field)
+ "#{markdown_field}_html"
+ end
+
+ def html_fields
+ @html_fields ||= markdown_fields.map { |field| html_field(field) }
+ end
+
+ def html_fields_whitelisted
+ markdown_fields.each_with_object([]) do |field, fields|
+ if @data[field].fetch(:whitelisted, false)
+ fields << html_field(field)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/redis/extension.rb b/lib/gitlab/markdown_cache/redis/extension.rb
new file mode 100644
index 00000000000..97fc23343b4
--- /dev/null
+++ b/lib/gitlab/markdown_cache/redis/extension.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module Redis
+ module Extension
+ extend ActiveSupport::Concern
+
+ attr_reader :cached_markdown_version
+
+ class_methods do
+ def cache_markdown_field(markdown_field, context = {})
+ super
+
+ # define the `[field]_html` accessor
+ html_field = cached_markdown_fields.html_field(markdown_field)
+ define_method(html_field) do
+ load_cached_markdown unless markdown_data_loaded?
+
+ instance_variable_get("@#{html_field}")
+ end
+ end
+ end
+
+ private
+
+ def save_markdown(updates)
+ markdown_store.save(updates)
+ end
+
+ def write_markdown_field(field_name, value)
+ instance_variable_set("@#{field_name}", value)
+ end
+
+ def markdown_field_changed?(field_name)
+ false
+ end
+
+ def changed_markdown_fields
+ []
+ end
+
+ def cached_markdown
+ @cached_data ||= markdown_store.read
+ end
+
+ def load_cached_markdown
+ cached_markdown.each do |field_name, value|
+ write_markdown_field(field_name, value)
+ end
+ end
+
+ def markdown_data_loaded?
+ cached_markdown_version.present? || markdown_store.loaded?
+ end
+
+ def markdown_store
+ @store ||= Gitlab::MarkdownCache::Redis::Store.new(self)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb
new file mode 100644
index 00000000000..0f954404808
--- /dev/null
+++ b/lib/gitlab/markdown_cache/redis/store.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module Redis
+ class Store
+ EXPIRES_IN = 1.day
+
+ def initialize(subject)
+ @subject = subject
+ @loaded = false
+ end
+
+ def save(updates)
+ @loaded = false
+
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(markdown_cache_key, updates)
+ r.expire(markdown_cache_key, EXPIRES_IN)
+ end
+ end
+
+ def read
+ @loaded = true
+
+ results = Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(markdown_cache_key, *fields)
+ end
+ # The value read from redis is a string, so we're converting it back
+ # to an int.
+ results[:cached_markdown_version] = results[:cached_markdown_version].to_i
+ results
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ private
+
+ def fields
+ @fields ||= @subject.cached_markdown_fields.html_fields + [:cached_markdown_version]
+ end
+
+ def markdown_cache_key
+ unless @subject.respond_to?(:cache_key)
+ raise Gitlab::MarkdownCache::UnsupportedClassError,
+ "This class has no cache_key to use for caching"
+ end
+
+ "markdown_cache:#{@subject.cache_key}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/base_service.rb b/lib/gitlab/metrics/dashboard/base_service.rb
new file mode 100644
index 00000000000..94aabd0466c
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/base_service.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+module Gitlab
+ module Metrics
+ module Dashboard
+ class BaseService < ::BaseService
+ DASHBOARD_LAYOUT_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError
+
+ def get_dashboard
+ return error("#{dashboard_path} could not be found.", :not_found) unless path_available?
+
+ success(dashboard: process_dashboard)
+ rescue DASHBOARD_LAYOUT_ERROR => e
+ error(e.message, :unprocessable_entity)
+ end
+
+ # Summary of all known dashboards for the service.
+ # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
+ def all_dashboard_paths(_project)
+ raise NotImplementedError
+ end
+
+ private
+
+ # Returns a new dashboard Hash, supplemented with DB info
+ def process_dashboard
+ Gitlab::Metrics::Dashboard::Processor
+ .new(project, params[:environment], raw_dashboard)
+ .process(insert_project_metrics: insert_project_metrics?)
+ end
+
+ # @return [String] Relative filepath of the dashboard yml
+ def dashboard_path
+ params[:dashboard_path]
+ end
+
+ # Returns an un-processed dashboard from the cache.
+ def raw_dashboard
+ Rails.cache.fetch(cache_key) { get_raw_dashboard }
+ end
+
+ # @return [Hash] an unmodified dashboard
+ def get_raw_dashboard
+ raise NotImplementedError
+ end
+
+ # @return [String]
+ def cache_key
+ raise NotImplementedError
+ end
+
+ # Determines whether custom metrics should be included
+ # in the processed output.
+ def insert_project_metrics?
+ false
+ end
+
+ # Checks if dashboard path exists or should be rejected
+ # as a result of file-changes to the project repository.
+ # @return [Boolean]
+ def path_available?
+ available_paths = Gitlab::Metrics::Dashboard::Finder.find_all_paths(project)
+
+ available_paths.any? do |path_params|
+ path_params[:path] == dashboard_path
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
new file mode 100644
index 00000000000..4a41590f000
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/finder.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+# Returns DB-supplmented dashboard info for determining
+# the layout of UI. Intended entry-point for the Metrics::Dashboard
+# module.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class Finder
+ class << self
+ # Returns a formatted dashboard packed with DB info.
+ # @return [Hash]
+ def find(project, user, environment, dashboard_path = nil)
+ service = system_dashboard?(dashboard_path) ? system_service : project_service
+
+ service
+ .new(project, user, environment: environment, dashboard_path: dashboard_path)
+ .get_dashboard
+ end
+
+ # Summary of all known dashboards.
+ # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
+ def find_all_paths(project)
+ project.repository.metrics_dashboard_paths
+ end
+
+ # Summary of all known dashboards. Used to populate repo cache.
+ # Prefer #find_all_paths.
+ def find_all_paths_from_source(project)
+ system_service.all_dashboard_paths(project)
+ .+ project_service.all_dashboard_paths(project)
+ end
+
+ private
+
+ def system_service
+ Gitlab::Metrics::Dashboard::SystemDashboardService
+ end
+
+ def project_service
+ Gitlab::Metrics::Dashboard::ProjectDashboardService
+ end
+
+ def system_dashboard?(filepath)
+ !filepath || system_service.system_dashboard?(filepath)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/processor.rb b/lib/gitlab/metrics/dashboard/processor.rb
new file mode 100644
index 00000000000..dd986020693
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/processor.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ # Responsible for processesing a dashboard hash, inserting
+ # relevant DB records & sorting for proper rendering in
+ # the UI. These includes shared metric info, custom metrics
+ # info, and alerts (only in EE).
+ class Processor
+ SYSTEM_SEQUENCE = [
+ Stages::CommonMetricsInserter,
+ Stages::ProjectMetricsInserter,
+ Stages::Sorter
+ ].freeze
+
+ PROJECT_SEQUENCE = [
+ Stages::CommonMetricsInserter,
+ Stages::Sorter
+ ].freeze
+
+ def initialize(project, environment, dashboard)
+ @project = project
+ @environment = environment
+ @dashboard = dashboard
+ end
+
+ # Returns a new dashboard hash with the results of
+ # running transforms on the dashboard.
+ def process(insert_project_metrics:)
+ @dashboard.deep_symbolize_keys.tap do |dashboard|
+ sequence(insert_project_metrics).each do |stage|
+ stage.new(@project, @environment, dashboard).transform!
+ end
+ end
+ end
+
+ private
+
+ def sequence(insert_project_metrics)
+ insert_project_metrics ? SYSTEM_SEQUENCE : PROJECT_SEQUENCE
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/project_dashboard_service.rb b/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
new file mode 100644
index 00000000000..fdffd067c93
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class ProjectDashboardService < Gitlab::Metrics::Dashboard::BaseService
+ DASHBOARD_ROOT = ".gitlab/dashboards"
+
+ class << self
+ def all_dashboard_paths(project)
+ file_finder(project)
+ .list_files_for(DASHBOARD_ROOT)
+ .map do |filepath|
+ Rails.cache.delete(cache_key(project.id, filepath))
+
+ { path: filepath, default: false }
+ end
+ end
+
+ def file_finder(project)
+ Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml')
+ end
+
+ def cache_key(id, dashboard_path)
+ "project_#{id}_metrics_dashboard_#{dashboard_path}"
+ end
+ end
+
+ private
+
+ # Searches the project repo for a custom-defined dashboard.
+ def get_raw_dashboard
+ yml = self.class.file_finder(project).read(dashboard_path)
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ self.class.cache_key(project.id, dashboard_path)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
new file mode 100644
index 00000000000..a6d1f974556
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class BaseStage
+ DashboardLayoutError = Class.new(StandardError)
+
+ DEFAULT_PANEL_TYPE = 'area-chart'
+
+ attr_reader :project, :environment, :dashboard
+
+ def initialize(project, environment, dashboard)
+ @project = project
+ @environment = environment
+ @dashboard = dashboard
+ end
+
+ # Entry-point to the stage
+ def transform!
+ raise NotImplementedError
+ end
+
+ protected
+
+ def missing_panel_groups!
+ raise DashboardLayoutError.new('Top-level key :panel_groups must be an array')
+ end
+
+ def missing_panels!
+ raise DashboardLayoutError.new('Each "panel_group" must define an array :panels')
+ end
+
+ def missing_metrics!
+ raise DashboardLayoutError.new('Each "panel" must define an array :metrics')
+ end
+
+ def for_metrics
+ missing_panel_groups! unless dashboard[:panel_groups].is_a?(Array)
+
+ dashboard[:panel_groups].each do |panel_group|
+ missing_panels! unless panel_group[:panels].is_a?(Array)
+
+ panel_group[:panels].each do |panel|
+ missing_metrics! unless panel[:metrics].is_a?(Array)
+
+ panel[:metrics].each do |metric|
+ yield metric
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb
new file mode 100644
index 00000000000..188912bedb4
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class CommonMetricsInserter < BaseStage
+ # For each metric in the dashboard config, attempts to
+ # find a corresponding database record. If found,
+ # includes the record's id in the dashboard config.
+ def transform!
+ common_metrics = ::PrometheusMetric.common
+
+ for_metrics do |metric|
+ metric_record = common_metrics.find { |m| m.identifier == metric[:id] }
+ metric[:metric_id] = metric_record.id if metric_record
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb
new file mode 100644
index 00000000000..221610a14d1
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class ProjectMetricsInserter < BaseStage
+ # Inserts project-specific metrics into the dashboard
+ # config. If there are no project-specific metrics,
+ # this will have no effect.
+ def transform!
+ project.prometheus_metrics.each do |project_metric|
+ group = find_or_create_panel_group(dashboard[:panel_groups], project_metric)
+ panel = find_or_create_panel(group[:panels], project_metric)
+ find_or_create_metric(panel[:metrics], project_metric)
+ end
+ end
+
+ private
+
+ # Looks for a panel_group corresponding to the
+ # provided metric object. If unavailable, inserts one.
+ # @param panel_groups [Array<Hash>]
+ # @param metric [PrometheusMetric]
+ def find_or_create_panel_group(panel_groups, metric)
+ panel_group = find_panel_group(panel_groups, metric)
+ return panel_group if panel_group
+
+ panel_group = new_panel_group(metric)
+ panel_groups << panel_group
+
+ panel_group
+ end
+
+ # Looks for a panel corresponding to the provided
+ # metric object. If unavailable, inserts one.
+ # @param panels [Array<Hash>]
+ # @param metric [PrometheusMetric]
+ def find_or_create_panel(panels, metric)
+ panel = find_panel(panels, metric)
+ return panel if panel
+
+ panel = new_panel(metric)
+ panels << panel
+
+ panel
+ end
+
+ # Looks for a metric corresponding to the provided
+ # metric object. If unavailable, inserts one.
+ # @param metrics [Array<Hash>]
+ # @param metric [PrometheusMetric]
+ def find_or_create_metric(metrics, metric)
+ target_metric = find_metric(metrics, metric)
+ return target_metric if target_metric
+
+ target_metric = new_metric(metric)
+ metrics << target_metric
+
+ target_metric
+ end
+
+ def find_panel_group(panel_groups, metric)
+ return unless panel_groups
+
+ panel_groups.find { |group| group[:group] == metric.group_title }
+ end
+
+ def find_panel(panels, metric)
+ return unless panels
+
+ panel_identifiers = [DEFAULT_PANEL_TYPE, metric.title, metric.y_label]
+ panels.find { |panel| panel.values_at(:type, :title, :y_label) == panel_identifiers }
+ end
+
+ def find_metric(metrics, metric)
+ return unless metrics
+
+ metrics.find { |m| m[:id] == metric.identifier }
+ end
+
+ def new_panel_group(metric)
+ {
+ group: metric.group_title,
+ priority: metric.priority,
+ panels: []
+ }
+ end
+
+ def new_panel(metric)
+ {
+ type: DEFAULT_PANEL_TYPE,
+ title: metric.title,
+ y_label: metric.y_label,
+ metrics: []
+ }
+ end
+
+ def new_metric(metric)
+ metric.queries.first.merge(metric_id: metric.id)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/sorter.rb b/lib/gitlab/metrics/dashboard/stages/sorter.rb
new file mode 100644
index 00000000000..ba5aa78059c
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/sorter.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class Sorter < BaseStage
+ def transform!
+ missing_panel_groups! unless dashboard[:panel_groups].is_a? Array
+
+ sort_groups!
+ sort_panels!
+ end
+
+ private
+
+ # Sorts the groups in the dashboard by the :priority key
+ def sort_groups!
+ dashboard[:panel_groups] = dashboard[:panel_groups].sort_by { |group| -group[:priority].to_i }
+ end
+
+ # Sorts the panels in the dashboard by the :weight key
+ def sort_panels!
+ dashboard[:panel_groups].each do |group|
+ missing_panels! unless group[:panels].is_a? Array
+
+ group[:panels] = group[:panels].sort_by { |panel| -panel[:weight].to_i }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/system_dashboard_service.rb b/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
new file mode 100644
index 00000000000..67509ed4230
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# Fetches the system metrics dashboard and formats the output.
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class SystemDashboardService < Gitlab::Metrics::Dashboard::BaseService
+ SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
+
+ class << self
+ def all_dashboard_paths(_project)
+ [{
+ path: SYSTEM_DASHBOARD_PATH,
+ default: true
+ }]
+ end
+
+ def system_dashboard?(filepath)
+ filepath == SYSTEM_DASHBOARD_PATH
+ end
+ end
+
+ private
+
+ def dashboard_path
+ SYSTEM_DASHBOARD_PATH
+ end
+
+ # Returns the base metrics shipped with every GitLab service.
+ def get_raw_dashboard
+ yml = File.read(Rails.root.join(dashboard_path))
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ "metrics_dashboard_#{dashboard_path}"
+ end
+
+ def insert_project_metrics?
+ true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 0b04340fbb5..269d90fa971 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -52,10 +52,8 @@ module Gitlab
pool&.with do |connection|
prepared.each_slice(settings[:packet_size]) do |slice|
- begin
- connection.write_points(slice)
- rescue StandardError
- end
+ connection.write_points(slice)
+ rescue StandardError
end
end
rescue Errno::EADDRNOTAVAIL, SocketError => ex
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
index 9e4d70a71ff..30f181542be 100644
--- a/lib/gitlab/metrics/metric.rb
+++ b/lib/gitlab/metrics/metric.rb
@@ -4,7 +4,7 @@ module Gitlab
module Metrics
# Class for storing details of a single metric (label, value, etc).
class Metric
- JITTER_RANGE = 0.000001..0.001
+ JITTER_RANGE = (0.000001..0.001).freeze
attr_reader :series, :values, :tags, :type
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
new file mode 100644
index 00000000000..25e40c70230
--- /dev/null
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'puma/state_file'
+
+module Gitlab
+ module Metrics
+ module Samplers
+ class PumaSampler < BaseSampler
+ def metrics
+ @metrics ||= init_metrics
+ end
+
+ def init_metrics
+ {
+ puma_workers: ::Gitlab::Metrics.gauge(:puma_workers, 'Total number of workers'),
+ puma_running_workers: ::Gitlab::Metrics.gauge(:puma_running_workers, 'Number of active workers'),
+ puma_stale_workers: ::Gitlab::Metrics.gauge(:puma_stale_workers, 'Number of stale workers'),
+ puma_phase: ::Gitlab::Metrics.gauge(:puma_phase, 'Phase number (increased during phased restarts)'),
+ puma_running: ::Gitlab::Metrics.gauge(:puma_running, 'Number of running threads'),
+ puma_queued_connections: ::Gitlab::Metrics.gauge(:puma_queued_connections, 'Number of connections in that worker\'s "todo" set waiting for a worker thread'),
+ puma_active_connections: ::Gitlab::Metrics.gauge(:puma_active_connections, 'Number of threads processing a request'),
+ puma_pool_capacity: ::Gitlab::Metrics.gauge(:puma_pool_capacity, 'Number of requests the worker is capable of taking right now'),
+ puma_max_threads: ::Gitlab::Metrics.gauge(:puma_max_threads, 'Maximum number of worker threads'),
+ puma_idle_threads: ::Gitlab::Metrics.gauge(:puma_idle_threads, 'Number of spawned threads which are not processing a request')
+ }
+ end
+
+ def sample
+ json_stats = puma_stats
+ return unless json_stats
+
+ stats = JSON.parse(json_stats)
+
+ if cluster?(stats)
+ sample_cluster(stats)
+ else
+ sample_single_worker(stats)
+ end
+ end
+
+ private
+
+ def puma_stats
+ Puma.stats
+ rescue NoMethodError
+ Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot"
+ nil
+ end
+
+ def sample_cluster(stats)
+ set_master_metrics(stats)
+
+ stats['worker_status'].each do |worker|
+ last_status = worker['last_status']
+ labels = { worker: "worker_#{worker['index']}" }
+
+ metrics[:puma_phase].set(labels, worker['phase'])
+ set_worker_metrics(last_status, labels) if last_status.present?
+ end
+ end
+
+ def sample_single_worker(stats)
+ metrics[:puma_workers].set({}, 1)
+ metrics[:puma_running_workers].set({}, 1)
+
+ set_worker_metrics(stats)
+ end
+
+ def cluster?(stats)
+ stats['worker_status'].present?
+ end
+
+ def set_master_metrics(stats)
+ labels = { worker: "master" }
+
+ metrics[:puma_workers].set(labels, stats['workers'])
+ metrics[:puma_running_workers].set(labels, stats['booted_workers'])
+ metrics[:puma_stale_workers].set(labels, stats['old_workers'])
+ metrics[:puma_phase].set(labels, stats['phase'])
+ end
+
+ def set_worker_metrics(stats, labels = {})
+ metrics[:puma_running].set(labels, stats['running'])
+ metrics[:puma_queued_connections].set(labels, stats['backlog'])
+ metrics[:puma_active_connections].set(labels, stats['max_threads'] - stats['pool_capacity'])
+ metrics[:puma_pool_capacity].set(labels, stats['pool_capacity'])
+ metrics[:puma_max_threads].set(labels, stats['max_threads'])
+ metrics[:puma_idle_threads].set(labels, stats['running'] + stats['pool_capacity'] - stats['max_threads'])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 18a69321905..17eacbd21d8 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -23,25 +23,32 @@ module Gitlab
end
def init_metrics
- metrics = {}
- metrics[:sampler_duration] = ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels)
- metrics[:total_time] = ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ metrics = {
+ file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum),
+ memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum),
+ process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
+ process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
+ process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels, :livesum),
+ process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
+ sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
+ total_time: ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ }
+
GC.stat.keys.each do |key|
metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum)
end
- metrics[:memory_usage] = ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum)
- metrics[:file_descriptors] = ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum)
-
metrics
end
def sample
start_time = System.monotonic_time
- metrics[:memory_usage].set(labels.merge(worker_label), System.memory_usage)
metrics[:file_descriptors].set(labels.merge(worker_label), System.file_descriptor_count)
-
+ metrics[:process_cpu_seconds_total].set(labels.merge(worker_label), ::Gitlab::Metrics::System.cpu_time)
+ metrics[:process_max_fds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.max_open_file_descriptors)
+ metrics[:process_start_time_seconds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.process_start_time)
+ set_memory_usage_metrics
sample_gc
metrics[:sampler_duration].increment(labels, System.monotonic_time - start_time)
@@ -61,11 +68,19 @@ module Gitlab
metrics[:total_time].increment(labels, GC::Profiler.total_time)
end
+ def set_memory_usage_metrics
+ memory_usage = System.memory_usage
+ memory_labels = labels.merge(worker_label)
+
+ metrics[:memory_bytes].set(memory_labels, memory_usage)
+ metrics[:process_resident_memory_bytes].set(memory_labels, memory_usage)
+ end
+
def worker_label
+ return { worker: 'sidekiq' } if Sidekiq.server?
return {} unless defined?(Unicorn::Worker)
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
-
if worker_no
{ worker: worker_no }
else
diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index bec64e864b3..9af7e0afed4 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -4,16 +4,16 @@ module Gitlab
module Metrics
module Samplers
class UnicornSampler < BaseSampler
- def initialize(interval)
- super(interval)
+ def metrics
+ @metrics ||= init_metrics
end
- def unicorn_active_connections
- @unicorn_active_connections ||= ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
- end
-
- def unicorn_queued_connections
- @unicorn_queued_connections ||= ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ def init_metrics
+ {
+ unicorn_active_connections: ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max),
+ unicorn_queued_connections: ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max),
+ unicorn_workers: ::Gitlab::Metrics.gauge(:unicorn_workers, 'Unicorn workers')
+ }
end
def enabled?
@@ -23,14 +23,13 @@ module Gitlab
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
- unicorn_active_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.active)
- unicorn_queued_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.queued)
+ set_unicorn_connection_metrics('tcp', addr, stats)
end
-
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
- unicorn_active_connections.set({ socket_type: 'unix', socket_address: addr }, stats.active)
- unicorn_queued_connections.set({ socket_type: 'unix', socket_address: addr }, stats.queued)
+ set_unicorn_connection_metrics('unix', addr, stats)
end
+
+ metrics[:unicorn_workers].set({}, unicorn_workers_count)
end
private
@@ -39,6 +38,13 @@ module Gitlab
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
+ def set_unicorn_connection_metrics(type, addr, stats)
+ labels = { socket_type: type, socket_address: addr }
+
+ metrics[:unicorn_active_connections].set(labels, stats.active)
+ metrics[:unicorn_queued_connections].set(labels, stats.queued)
+ end
+
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
@@ -46,6 +52,10 @@ module Gitlab
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
+
+ def unicorn_workers_count
+ `pgrep -f '[u]nicorn_rails worker.+ #{Rails.root.to_s}'`.split.count
+ end
end
end
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 426496855e3..33c0de91c11 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -23,6 +23,22 @@ module Gitlab
def self.file_descriptor_count
Dir.glob('/proc/self/fd/*').length
end
+
+ def self.max_open_file_descriptors
+ match = File.read('/proc/self/limits').match(/Max open files\s*(\d+)/)
+
+ return unless match && match[1]
+
+ match[1].to_i
+ end
+
+ def self.process_start_time
+ fields = File.read('/proc/self/stat').split
+
+ # fields[21] is linux proc stat field "(22) starttime".
+ # The value is expressed in clock ticks, divide by clock ticks for seconds.
+ ( fields[21].to_i || 0 ) / clk_tck
+ end
else
def self.memory_usage
0.0
@@ -31,6 +47,14 @@ module Gitlab
def self.file_descriptor_count
0
end
+
+ def self.max_open_file_descriptors
+ 0
+ end
+
+ def self.process_start_time
+ 0
+ end
end
# THREAD_CPUTIME is not supported on OS X
@@ -59,6 +83,10 @@ module Gitlab
def self.monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
+
+ def self.clk_tck
+ @clk_tck ||= `getconf CLK_TCK`.to_i
+ end
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index e91803ecd62..d7986685c65 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -8,6 +8,8 @@ module Gitlab
# base labels shared among all transactions
BASE_LABELS = { controller: nil, action: nil }.freeze
+ # labels that potentially contain sensitive information and will be filtered
+ FILTERED_LABELS = [:branch, :path].freeze
THREAD_KEY = :_gitlab_metrics_transaction
@@ -64,7 +66,7 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, tags)
+ @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, filter_tags(tags))
end
# Tracks a business level event
@@ -75,8 +77,9 @@ module Gitlab
# event_name - The name of the event (e.g. "git_push").
# tags - A set of tags to attach to the event.
def add_event(event_name, tags = {})
- self.class.transaction_metric(event_name, :counter, prefix: 'event_', use_feature_flag: true, tags: tags).increment(tags.merge(labels))
- @metrics << Metric.new(EVENT_SERIES, { count: 1 }, tags.merge(event: event_name), :event)
+ filtered_tags = filter_tags(tags)
+ self.class.transaction_metric(event_name, :counter, prefix: 'event_', use_feature_flag: true, tags: filtered_tags).increment(filtered_tags.merge(labels))
+ @metrics << Metric.new(EVENT_SERIES, { count: 1 }, filtered_tags.merge(event: event_name), :event)
end
# Returns a MethodCall object for the given name.
@@ -164,6 +167,12 @@ module Gitlab
end
end
end
+
+ private
+
+ def filter_tags(tags)
+ tags.without(*FILTERED_LABELS)
+ end
end
end
end
diff --git a/lib/gitlab/middleware/basic_health_check.rb b/lib/gitlab/middleware/basic_health_check.rb
index acf8c301b8f..84e49805428 100644
--- a/lib/gitlab/middleware/basic_health_check.rb
+++ b/lib/gitlab/middleware/basic_health_check.rb
@@ -24,7 +24,13 @@ module Gitlab
def call(env)
return @app.call(env) unless env['PATH_INFO'] == HEALTH_PATH
- request = ActionDispatch::Request.new(env)
+ # We should be using ActionDispatch::Request instead of
+ # Rack::Request to be consistent with Rails, but due to a Rails
+ # bug described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58573#note_149799010
+ # hosts behind a load balancer will only see 127.0.0.1 for the
+ # load balancer's IP.
+ request = Rack::Request.new(env)
return OK_RESPONSE if client_ip_whitelisted?(request)
diff --git a/lib/gitlab/middleware/correlation_id.rb b/lib/gitlab/middleware/correlation_id.rb
index 80dddc41c12..fffd5da827f 100644
--- a/lib/gitlab/middleware/correlation_id.rb
+++ b/lib/gitlab/middleware/correlation_id.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def call(env)
- ::Gitlab::CorrelationId.use_id(correlation_id(env)) do
+ ::Labkit::Correlation::CorrelationId.use_id(correlation_id(env)) do
@app.call(env)
end
end
diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb
index 83c52a6c6e0..8e17073abab 100644
--- a/lib/gitlab/middleware/read_only.rb
+++ b/lib/gitlab/middleware/read_only.rb
@@ -3,7 +3,7 @@
module Gitlab
module Middleware
class ReadOnly
- API_VERSIONS = (3..4)
+ API_VERSIONS = (3..4).freeze
def self.internal_routes
@internal_routes ||=
diff --git a/lib/gitlab/middleware/release_env.rb b/lib/gitlab/middleware/release_env.rb
index 849cf8f759b..0719fb2e8c6 100644
--- a/lib/gitlab/middleware/release_env.rb
+++ b/lib/gitlab/middleware/release_env.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab
diff --git a/lib/gitlab/namespaced_session_store.rb b/lib/gitlab/namespaced_session_store.rb
new file mode 100644
index 00000000000..34520078bfb
--- /dev/null
+++ b/lib/gitlab/namespaced_session_store.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class NamespacedSessionStore
+ delegate :[], :[]=, to: :store
+
+ def initialize(key)
+ @key = key
+ end
+
+ def initiated?
+ !Session.current.nil?
+ end
+
+ def store
+ return unless Session.current
+
+ Session.current[@key] ||= {}
+ Session.current[@key]
+ end
+ end
+end
diff --git a/lib/gitlab/object_hierarchy.rb b/lib/gitlab/object_hierarchy.rb
index f2772c733c7..38b32770e90 100644
--- a/lib/gitlab/object_hierarchy.rb
+++ b/lib/gitlab/object_hierarchy.rb
@@ -5,6 +5,8 @@ module Gitlab
#
# This class uses recursive CTEs and as a result will only work on PostgreSQL.
class ObjectHierarchy
+ DEPTH_COLUMN = :depth
+
attr_reader :ancestors_base, :descendants_base, :model
# ancestors_base - An instance of ActiveRecord::Relation for which to
@@ -27,6 +29,17 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ # Returns the maximum depth starting from the base
+ # A base object with no children has a maximum depth of `1`
+ def max_descendants_depth
+ unless hierarchy_supported?
+ # This makes the return value consistent with the case where hierarchy is supported
+ return descendants_base.exists? ? 1 : nil
+ end
+
+ base_and_descendants(with_depth: true).maximum(DEPTH_COLUMN)
+ end
+
# Returns the set of ancestors of a given relation, but excluding the given
# relation
#
@@ -64,10 +77,15 @@ module Gitlab
# Returns a relation that includes the descendants_base set of objects
# and all their descendants (recursively).
- def base_and_descendants
- return descendants_base unless hierarchy_supported?
-
- read_only(base_and_descendants_cte.apply_to(model.all))
+ #
+ # When `with_depth` is `true`, a `depth` column is included where it starts with `1` for the base objects
+ # and incremented as we go down the descendant tree
+ def base_and_descendants(with_depth: false)
+ unless hierarchy_supported?
+ return with_depth ? descendants_base.select("1 as #{DEPTH_COLUMN}", objects_table[Arel.star]) : descendants_base
+ end
+
+ read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all))
end
# Returns a relation that includes the base objects, their ancestors,
@@ -124,10 +142,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
- depth_column = :depth
base_query = ancestors_base.except(:order)
- base_query = base_query.select("1 as #{depth_column}", objects_table[Arel.star]) if hierarchy_order
+ base_query = base_query.select("1 as #{DEPTH_COLUMN}", "ARRAY[id] AS tree_path", "false AS tree_cycle", objects_table[Arel.star]) if hierarchy_order
cte << base_query
@@ -137,7 +154,17 @@ module Gitlab
.where(objects_table[:id].eq(cte.table[:parent_id]))
.except(:order)
- parent_query = parent_query.select(cte.table[depth_column] + 1, objects_table[Arel.star]) if hierarchy_order
+ if hierarchy_order
+ quoted_objects_table_name = model.connection.quote_table_name(objects_table.name)
+
+ parent_query = parent_query.select(
+ cte.table[DEPTH_COLUMN] + 1,
+ "tree_path || #{quoted_objects_table_name}.id",
+ "#{quoted_objects_table_name}.id = ANY(tree_path)",
+ objects_table[Arel.star]
+ ).where(cte.table[:tree_cycle].eq(false))
+ end
+
parent_query = parent_query.where(cte.table[:parent_id].not_eq(stop_id)) if stop_id
cte << parent_query
@@ -146,17 +173,32 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def base_and_descendants_cte
+ def base_and_descendants_cte(with_depth: false)
cte = SQL::RecursiveCTE.new(:base_and_descendants)
- cte << descendants_base.except(:order)
+ base_query = descendants_base.except(:order)
+ base_query = base_query.select("1 AS #{DEPTH_COLUMN}", "ARRAY[id] AS tree_path", "false AS tree_cycle", objects_table[Arel.star]) if with_depth
+
+ cte << base_query
# Recursively get all the descendants of the base set.
- cte << model
+ descendants_query = model
.from([objects_table, cte.table])
.where(objects_table[:parent_id].eq(cte.table[:id]))
.except(:order)
+ if with_depth
+ quoted_objects_table_name = model.connection.quote_table_name(objects_table.name)
+
+ descendants_query = descendants_query.select(
+ cte.table[DEPTH_COLUMN] + 1,
+ "tree_path || #{quoted_objects_table_name}.id",
+ "#{quoted_objects_table_name}.id = ANY(tree_path)",
+ objects_table[Arel.star]
+ ).where(cte.table[:tree_cycle].eq(false))
+ end
+
+ cte << descendants_query
cte
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index e0ac9eec1f2..83204fa5d18 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -36,12 +36,25 @@ module Gitlab
hash_arguments = provider['args'].merge(provider_defaults(provider))
# A Hash from the configuration will be passed as is.
- provider_arguments << hash_arguments.symbolize_keys
+ provider_arguments << normalize_hash_arguments(hash_arguments)
end
provider_arguments
end
+ def normalize_hash_arguments(args)
+ args.symbolize_keys!
+
+ # Rails 5.1 deprecated the use of string names in the middleware
+ # (https://github.com/rails/rails/commit/83b767ce), so we need to
+ # pass in the actual class to Devise.
+ if args[:strategy_class].is_a?(String)
+ args[:strategy_class] = args[:strategy_class].constantize
+ end
+
+ args
+ end
+
def provider_defaults(provider)
case provider['name']
when 'cas3'
@@ -50,6 +63,12 @@ module Gitlab
{ remote_sign_out_handler: authentiq_signout_handler }
when 'shibboleth'
{ fail_with_empty_uid: true }
+ when 'openid_connect'
+ # If a name argument is omitted, OmniAuth will expect that the
+ # matching route is /auth/users/openidconnect instead of
+ # /auth/users/openid_connect because of
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/62208#note_178780341.
+ { name: 'openid_connect' }
else
{}
end
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index ce4ba9f752b..868b2ae641a 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -12,7 +12,7 @@ module Gitlab
retries -= 1
raise unless retries >= 0
- subject.reload
+ subject.reset
retry
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 3c888be0710..a07b1246bee 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -57,6 +57,7 @@ module Gitlab
unsubscribes
uploads
users
+ v2
].freeze
# This list should contain all words following `/*namespace_id/:project_id` in
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
index ac392432427..16c16aa0265 100644
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -26,11 +26,7 @@ module Gitlab
subscribe('sql.active_record') do |_, start, finish, _, data|
if Gitlab::SafeRequestStore.store[:peek_enabled]
- # data[:cached] is only available starting from Rails 5.1.0
- # https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113
- # Before that, data[:name] was set to 'CACHE'
- # https://github.com/rails/rails/blob/v4.2.9/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L80
- unless data.fetch(:cached, data[:name] == 'CACHE')
+ unless data[:cached]
track_query(data[:sql].strip, data[:binds], start, finish)
end
end
diff --git a/lib/gitlab/phabricator_import.rb b/lib/gitlab/phabricator_import.rb
new file mode 100644
index 00000000000..3885a9934d5
--- /dev/null
+++ b/lib/gitlab/phabricator_import.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ BaseError = Class.new(StandardError)
+
+ def self.available?
+ Feature.enabled?(:phabricator_import) &&
+ Gitlab::CurrentSettings.import_sources.include?('phabricator')
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb
new file mode 100644
index 00000000000..b69c65e78f8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/base_worker.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+# All workers within a Phabricator import should inherit from this worker and
+# implement the `#import` method. The jobs should then be scheduled using the
+# `.schedule` class method instead of `.perform_async`
+#
+# Doing this makes sure that only one job of that type is running at the same time
+# for a certain project. This will avoid deadlocks. When a job is already running
+# we'll wait for it for 10 times 5 seconds to restart. If the running job hasn't
+# finished, by then, we'll retry in 30 seconds.
+#
+# It also makes sure that we keep the import state of the project up to date:
+# - It keeps track of the jobs so we know how many jobs are running for the
+# project
+# - It refreshes the import jid, so it doesn't get cleaned up by the
+# `StuckImportJobsWorker`
+# - It marks the import as failed if a job failed to many times
+# - It marks the import as finished when all remaining jobs are done
+module Gitlab
+ module PhabricatorImport
+ class BaseWorker
+ include ApplicationWorker
+ include ProjectImportOptions # This marks the project as failed after too many tries
+ include Gitlab::ExclusiveLeaseHelpers
+
+ class << self
+ def schedule(project_id, *args)
+ perform_async(project_id, *args)
+ add_job(project_id)
+ end
+
+ def add_job(project_id)
+ worker_state(project_id).add_job
+ end
+
+ def remove_job(project_id)
+ worker_state(project_id).remove_job
+ end
+
+ def worker_state(project_id)
+ Gitlab::PhabricatorImport::WorkerState.new(project_id)
+ end
+ end
+
+ def perform(project_id, *args)
+ in_lock("#{self.class.name.underscore}/#{project_id}/#{args}", ttl: 2.hours, sleep_sec: 5.seconds) do
+ project = Project.find_by_id(project_id)
+ next unless project
+
+ # Bail if the import job already failed
+ next unless project.import_state&.in_progress?
+
+ project.import_state.refresh_jid_expiration
+
+ import(project, *args)
+
+ # If this is the last running job, finish the import
+ project.after_import if self.class.worker_state(project_id).running_count < 2
+
+ self.class.remove_job(project_id)
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ # Reschedule a job if there was already a running one
+ # Running them at the same time could cause a deadlock updating the same
+ # resource
+ self.class.perform_in(30.seconds, project_id, *args)
+ end
+
+ private
+
+ def import(project, *args)
+ importer_class.new(project, *args).execute
+ end
+
+ def importer_class
+ raise NotImplementedError, "Implement `#{__method__}` on #{self.class}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/cache/map.rb b/lib/gitlab/phabricator_import/cache/map.rb
new file mode 100644
index 00000000000..fa8b37b20ca
--- /dev/null
+++ b/lib/gitlab/phabricator_import/cache/map.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Cache
+ class Map
+ def initialize(project)
+ @project = project
+ end
+
+ def get_gitlab_model(phabricator_id)
+ cached_info = get(phabricator_id)
+ return unless cached_info[:classname] && cached_info[:database_id]
+
+ cached_info[:classname].constantize.find_by_id(cached_info[:database_id])
+ end
+
+ def set_gitlab_model(object, phabricator_id)
+ set(object.class, object.id, phabricator_id)
+ end
+
+ private
+
+ attr_reader :project
+
+ def set(klass_name, object_id, phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.multi do |multi|
+ multi.mapped_hmset(key,
+ { classname: klass_name, database_id: object_id })
+ multi.expire(key, timeout)
+ end
+ end
+ end
+
+ def get(phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.pipelined do |pipe|
+ # Extend the TTL when a key was
+ pipe.expire(key, timeout)
+ pipe.mapped_hmget(key, :classname, :database_id)
+ end.last
+ end
+ end
+
+ def cache_key_for_phabricator_id(phabricator_id)
+ "#{Redis::Cache::CACHE_NAMESPACE}/phabricator-import/#{project.id}/#{phabricator_id}"
+ end
+
+ def redis
+ Gitlab::Redis::Cache
+ end
+
+ def timeout
+ # Setting the timeout to the same one as we do for clearing stuck jobs
+ # this makes sure all cache is available while the import is running.
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit.rb b/lib/gitlab/phabricator_import/conduit.rb
new file mode 100644
index 00000000000..4c64d737389
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ ApiError = Class.new(Gitlab::PhabricatorImport::BaseError)
+ ResponseError = Class.new(ApiError)
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/client.rb b/lib/gitlab/phabricator_import/conduit/client.rb
new file mode 100644
index 00000000000..4469a3f5849
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/client.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Client
+ def initialize(phabricator_url, api_token)
+ @phabricator_url = phabricator_url
+ @api_token = api_token
+ end
+
+ def get(path, params: {})
+ response = Gitlab::HTTP.get(build_url(path), body: build_params(params), headers: headers)
+ Response.parse!(response)
+ rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ # Wrap all errors from the API into an API-error.
+ raise ApiError.new(e)
+ end
+
+ private
+
+ attr_reader :phabricator_url, :api_token
+
+ def headers
+ { "Accept" => 'application/json' }
+ end
+
+ def build_url(path)
+ URI.join(phabricator_url, '/api/', path).to_s
+ end
+
+ def build_params(params)
+ params = params.dup
+ params.compact!
+ params.reverse_merge!("api.token" => api_token)
+
+ CGI.unescape(params.to_query)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/maniphest.rb b/lib/gitlab/phabricator_import/conduit/maniphest.rb
new file mode 100644
index 00000000000..848b71e49e7
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/maniphest.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Maniphest
+ def initialize(phabricator_url:, api_token:)
+ @client = Client.new(phabricator_url, api_token)
+ end
+
+ def tasks(after: nil)
+ TasksResponse.new(get_tasks(after))
+ end
+
+ private
+
+ def get_tasks(after)
+ client.get('maniphest.search',
+ params: {
+ after: after,
+ attachments: { projects: 1, subscribers: 1, columns: 1 }
+ })
+ end
+
+ attr_reader :client
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/pagination.rb b/lib/gitlab/phabricator_import/conduit/pagination.rb
new file mode 100644
index 00000000000..5f54cccdbc8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/pagination.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Pagination
+ def initialize(cursor_json)
+ @cursor_json = cursor_json
+ end
+
+ def has_next_page?
+ next_page.present?
+ end
+
+ def next_page
+ cursor_json["after"]
+ end
+
+ private
+
+ attr_reader :cursor_json
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/response.rb b/lib/gitlab/phabricator_import/conduit/response.rb
new file mode 100644
index 00000000000..6053ecfbd5e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/response.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Response
+ def self.parse!(http_response)
+ unless http_response.success?
+ raise Gitlab::PhabricatorImport::Conduit::ResponseError,
+ "Phabricator responded with #{http_response.status}"
+ end
+
+ response = new(JSON.parse(http_response.body))
+
+ unless response.success?
+ raise ResponseError,
+ "Phabricator Error: #{response.error_code}: #{response.error_info}"
+ end
+
+ response
+ rescue JSON::JSONError => e
+ raise ResponseError.new(e)
+ end
+
+ def initialize(json)
+ @json = json
+ end
+
+ def success?
+ error_code.nil?
+ end
+
+ def error_code
+ json['error_code']
+ end
+
+ def error_info
+ json['error_info']
+ end
+
+ def data
+ json_result&.fetch('data')
+ end
+
+ def pagination
+ return unless cursor_info = json_result&.fetch('cursor')
+
+ @pagination ||= Pagination.new(cursor_info)
+ end
+
+ private
+
+ attr_reader :json
+
+ def json_result
+ json['result']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/tasks_response.rb b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
new file mode 100644
index 00000000000..cbcf7259fb2
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class TasksResponse
+ def initialize(conduit_response)
+ @conduit_response = conduit_response
+ end
+
+ delegate :pagination, to: :conduit_response
+
+ def tasks
+ @tasks ||= conduit_response.data.map do |task_json|
+ Gitlab::PhabricatorImport::Representation::Task.new(task_json)
+ end
+ end
+
+ private
+
+ attr_reader :conduit_response
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/import_tasks_worker.rb b/lib/gitlab/phabricator_import/import_tasks_worker.rb
new file mode 100644
index 00000000000..c36954a8d41
--- /dev/null
+++ b/lib/gitlab/phabricator_import/import_tasks_worker.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class ImportTasksWorker < BaseWorker
+ def importer_class
+ Gitlab::PhabricatorImport::Issues::Importer
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/importer.rb b/lib/gitlab/phabricator_import/importer.rb
new file mode 100644
index 00000000000..c1797f4027e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/importer.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class Importer
+ def self.async?
+ true
+ end
+
+ def self.imports_repository?
+ # This does not really import a repository, but we want to skip all
+ # repository related tasks in the `Projects::ImportService`
+ true
+ end
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ Gitlab::Import::SetAsyncJid.set_jid(project)
+ schedule_first_tasks_page
+
+ true
+ rescue => e
+ fail_import(e.message)
+
+ false
+ end
+
+ private
+
+ attr_reader :project
+
+ def schedule_first_tasks_page
+ ImportTasksWorker.schedule(project.id)
+ end
+
+ def fail_import(message)
+ project.import_state.mark_as_failed(message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/importer.rb b/lib/gitlab/phabricator_import/issues/importer.rb
new file mode 100644
index 00000000000..a58438452ff
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/importer.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class Importer
+ def initialize(project, after = nil)
+ @project, @after = project, after
+ end
+
+ def execute
+ schedule_next_batch
+
+ tasks_response.tasks.each do |task|
+ TaskImporter.new(project, task).execute
+ end
+ end
+
+ private
+
+ attr_reader :project, :after
+
+ def schedule_next_batch
+ return unless tasks_response.pagination.has_next_page?
+
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ .schedule(project.id, tasks_response.pagination.next_page)
+ end
+
+ def tasks_response
+ @tasks_response ||= client.tasks(after: after)
+ end
+
+ def client
+ @client ||=
+ Gitlab::PhabricatorImport::Conduit::Maniphest
+ .new(phabricator_url: project.import_data.data['phabricator_url'],
+ api_token: project.import_data.credentials[:api_token])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/task_importer.rb b/lib/gitlab/phabricator_import/issues/task_importer.rb
new file mode 100644
index 00000000000..40d4392cbc1
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/task_importer.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class TaskImporter
+ def initialize(project, task)
+ @project, @task = project, task
+ end
+
+ def execute
+ # TODO: get the user from the project namespace from the username loaded by Phab-id
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60565
+ issue.author = User.ghost
+
+ # TODO: Reformat the description with attachments, escaping accidental
+ # links and add attachments
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60603
+ issue.assign_attributes(task.issue_attributes)
+
+ save!
+
+ issue
+ end
+
+ private
+
+ attr_reader :project, :task
+
+ def save!
+ # Just avoiding an extra redis call, we've already updated the expiry
+ # when reading the id from the map
+ was_persisted = issue.persisted?
+
+ issue.save! if issue.changed?
+
+ object_map.set_gitlab_model(issue, task.phabricator_id) unless was_persisted
+ end
+
+ def issue
+ @issue ||= find_issue_by_phabricator_id(task.phabricator_id) ||
+ project.issues.new
+ end
+
+ def find_issue_by_phabricator_id(phabricator_id)
+ object_map.get_gitlab_model(phabricator_id)
+ end
+
+ def object_map
+ Gitlab::PhabricatorImport::Cache::Map.new(project)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/project_creator.rb b/lib/gitlab/phabricator_import/project_creator.rb
new file mode 100644
index 00000000000..b37a5b44980
--- /dev/null
+++ b/lib/gitlab/phabricator_import/project_creator.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class ProjectCreator
+ def initialize(current_user, params)
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ return unless import_url.present? && api_token.present?
+
+ project = Projects::CreateService.new(current_user, create_params).execute
+ return project unless project.persisted?
+
+ project.project_feature.update!(project_feature_attributes)
+
+ project
+ end
+
+ private
+
+ attr_reader :current_user, :params
+
+ def create_params
+ {
+ name: project_name,
+ path: project_path,
+ namespace_id: namespace_id,
+ import_type: 'phabricator',
+ import_url: Project::UNKNOWN_IMPORT_URL,
+ import_data: import_data
+ }
+ end
+
+ def project_name
+ params[:name]
+ end
+
+ def project_path
+ params[:path]
+ end
+
+ def namespace_id
+ params[:namespace_id] || current_user.namespace_id
+ end
+
+ def import_url
+ params[:phabricator_server_url]
+ end
+
+ def api_token
+ params[:api_token]
+ end
+
+ def project_feature_attributes
+ @project_features_attributes ||= begin
+ # everything disabled except for issues
+ ProjectFeature::FEATURES.map do |feature|
+ [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
+ end.to_h.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
+ end
+ end
+
+ def import_data
+ {
+ data: {
+ phabricator_url: import_url
+ },
+ credentials: {
+ api_token: params.fetch(:api_token)
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/representation/task.rb b/lib/gitlab/phabricator_import/representation/task.rb
new file mode 100644
index 00000000000..6aedc71b626
--- /dev/null
+++ b/lib/gitlab/phabricator_import/representation/task.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Representation
+ class Task
+ def initialize(json)
+ @json = json
+ end
+
+ def phabricator_id
+ json['phid']
+ end
+
+ def issue_attributes
+ @issue_attributes ||= {
+ title: issue_title,
+ description: issue_description,
+ state: issue_state,
+ created_at: issue_created_at,
+ closed_at: issue_closed_at
+ }
+ end
+
+ private
+
+ attr_reader :json
+
+ def issue_title
+ # The 255 limit is the validation we impose on the Issue title in
+ # Issuable
+ @issue_title ||= json['fields']['name'].truncate(255)
+ end
+
+ def issue_description
+ json['fields']['description']['raw']
+ end
+
+ def issue_state
+ issue_closed_at.present? ? :closed : :opened
+ end
+
+ def issue_created_at
+ return unless json['fields']['dateCreated']
+
+ @issue_created_at ||= cast_datetime(json['fields']['dateCreated'])
+ end
+
+ def issue_closed_at
+ return unless json['fields']['dateClosed']
+
+ @issue_closed_at ||= cast_datetime(json['fields']['dateClosed'])
+ end
+
+ def cast_datetime(value)
+ Time.at(value.to_i)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/worker_state.rb b/lib/gitlab/phabricator_import/worker_state.rb
new file mode 100644
index 00000000000..38829e34509
--- /dev/null
+++ b/lib/gitlab/phabricator_import/worker_state.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class WorkerState
+ def initialize(project_id)
+ @project_id = project_id
+ end
+
+ def add_job
+ redis.with do |r|
+ r.pipelined do |pipe|
+ pipe.incr(all_jobs_key)
+ pipe.expire(all_jobs_key, timeout)
+ end
+ end
+ end
+
+ def remove_job
+ redis.with do |r|
+ r.decr(all_jobs_key)
+ end
+ end
+
+ def running_count
+ redis.with { |r| r.get(all_jobs_key) }.to_i
+ end
+
+ private
+
+ attr_reader :project_id
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+
+ def all_jobs_key
+ @all_jobs_key ||= "phabricator-import/jobs/project-#{project_id}/job-count"
+ end
+
+ def timeout
+ # Make sure we get rid of all the information after a job is marked
+ # as failed/succeeded
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 93a9fcf1591..890228e5e78 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -16,7 +16,11 @@ module Gitlab
ee/lib/gitlab/middleware/
lib/gitlab/performance_bar/
lib/gitlab/request_profiler/
+ lib/gitlab/query_limiting/
+ lib/gitlab/tracing/
lib/gitlab/profiler.rb
+ lib/gitlab/correlation_id.rb
+ lib/gitlab/webpack/dev_server_middleware.rb
].freeze
# Takes a URL to profile (can be a fully-qualified URL, or an absolute path)
@@ -69,7 +73,7 @@ module Gitlab
result = with_custom_logger(logger) do
with_user(user) do
- RubyProf.profile { app.public_send(verb, url, post_data, headers) } # rubocop:disable GitlabSecurity/PublicSend
+ RubyProf.profile { app.public_send(verb, url, params: post_data, headers: headers) } # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index a68f8801c2a..0f3b97e2317 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -22,11 +22,17 @@ module Gitlab
paginated_blobs(wiki_blobs, page)
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
+ when 'users'
+ users.page(page).per(per_page)
else
super(scope, page, false)
end
end
+ def users
+ super.where(id: @project.team.members) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def blobs_count
@blobs_count ||= blobs.count
end
@@ -132,6 +138,12 @@ module Gitlab
project
end
+ def filter_milestones_by_project(milestones)
+ return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project)
+
+ milestones.where(project_id: project.id) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
def repository_project_ref
@repository_project_ref ||= repository_ref || project.default_branch
end
@@ -139,5 +151,9 @@ module Gitlab
def repository_wiki_ref
@repository_wiki_ref ||= repository_ref || project.wiki.default_branch
end
+
+ def issuable_params
+ super.merge(project_id: project.id)
+ end
end
end
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 29c511524a2..99885be8755 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -28,7 +28,10 @@ module Gitlab
ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'),
ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'),
ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
+ ProjectTemplate.new('iosswift', 'iOS (Swift)', _('A ready-to-go template for use with iOS Swift apps.'), 'https://gitlab.com/gitlab-org/project-templates/iosswift'),
ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'),
+ ProjectTemplate.new('android', 'Android', _('A ready-to-go template for use with Android apps.'), 'https://gitlab.com/gitlab-org/project-templates/android', 'illustrations/logos/android.svg'),
+ ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development.'), 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'),
ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'),
ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'),
diff --git a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
index 34b705138ba..c49877ddf9d 100644
--- a/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
+++ b/lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
@@ -7,12 +7,16 @@ module Gitlab
include QueryAdditionalMetrics
# rubocop: disable CodeReuse/ActiveRecord
- def query(environment_id)
+ def query(environment_id, timeframe_start = 8.hours.ago, timeframe_end = Time.now)
::Environment.find_by(id: environment_id).try do |environment|
query_metrics(
environment.project,
environment,
- common_query_context(environment, timeframe_start: 8.hours.ago.to_f, timeframe_end: Time.now.to_f)
+ common_query_context(
+ environment,
+ timeframe_start: timeframe_start.to_f,
+ timeframe_end: timeframe_end.to_f
+ )
)
end
end
diff --git a/lib/gitlab/prometheus/queries/knative_invocation_query.rb b/lib/gitlab/prometheus/queries/knative_invocation_query.rb
new file mode 100644
index 00000000000..2691abe46d6
--- /dev/null
+++ b/lib/gitlab/prometheus/queries/knative_invocation_query.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Prometheus
+ module Queries
+ class KnativeInvocationQuery < BaseQuery
+ include QueryAdditionalMetrics
+
+ def query(serverless_function_id)
+ PrometheusMetric
+ .find_by_identifier(:system_metrics_knative_function_invocation_count)
+ .to_query_metric.tap do |q|
+ q.queries[0][:result] = run_query(q.queries[0][:query_range], context(serverless_function_id))
+ end
+ end
+
+ protected
+
+ def context(function_id)
+ function = Serverless::Function.find_by_id(function_id)
+ {
+ function_name: function.name,
+ kube_namespace: function.namespace
+ }
+ end
+
+ def run_query(query, context)
+ query %= context
+ client_query_range(query, start: 8.hours.ago.to_f, stop: Time.now.to_f)
+ end
+
+ def self.transform_reactive_result(result)
+ result[:metrics] = result.delete :data
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/query_variables.rb b/lib/gitlab/prometheus/query_variables.rb
index 1cc85d4b4a6..9cc21129547 100644
--- a/lib/gitlab/prometheus/query_variables.rb
+++ b/lib/gitlab/prometheus/query_variables.rb
@@ -4,9 +4,12 @@ module Gitlab
module Prometheus
module QueryVariables
def self.call(environment)
+ deployment_platform = environment.deployment_platform
+ namespace = deployment_platform&.kubernetes_namespace_for(environment.project) || ''
+
{
ci_environment_slug: environment.slug,
- kube_namespace: environment.deployment_platform&.actual_namespace || '',
+ kube_namespace: namespace,
environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
}
end
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 45828c77a33..f13156f898e 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -6,6 +6,14 @@ module Gitlab
Error = Class.new(StandardError)
QueryError = Class.new(Gitlab::PrometheusClient::Error)
+ # Target number of data points for `query_range`.
+ # Please don't exceed the limit of 11000 data points
+ # See https://github.com/prometheus/prometheus/blob/91306bdf24f5395e2601773316945a478b4b263d/web/api/v1/api.go#L347
+ QUERY_RANGE_DATA_POINTS = 600
+
+ # Minimal value of the `step` parameter for `query_range` in seconds.
+ QUERY_RANGE_MIN_STEP = 60
+
attr_reader :rest_client, :headers
def initialize(rest_client)
@@ -16,6 +24,19 @@ module Gitlab
json_api_get('query', query: '1')
end
+ def proxy(type, args)
+ path = api_path(type)
+ get(path, args)
+ rescue RestClient::ExceptionWithResponse => ex
+ if ex.response
+ ex.response
+ else
+ raise PrometheusClient::Error, "Network connection error"
+ end
+ rescue RestClient::Exception
+ raise PrometheusClient::Error, "Network connection error"
+ end
+
def query(query, time: Time.now)
get_result('vector') do
json_api_get('query', query: query, time: time.to_f)
@@ -23,12 +44,18 @@ module Gitlab
end
def query_range(query, start: 8.hours.ago, stop: Time.now)
+ start = start.to_f
+ stop = stop.to_f
+ step = self.class.compute_step(start, stop)
+
get_result('matrix') do
- json_api_get('query_range',
- query: query,
- start: start.to_f,
- end: stop.to_f,
- step: 1.minute.to_i)
+ json_api_get(
+ 'query_range',
+ query: query,
+ start: start,
+ end: stop,
+ step: step
+ )
end
end
@@ -40,24 +67,24 @@ module Gitlab
json_api_get('series', 'match': matches, start: start.to_f, end: stop.to_f)
end
+ def self.compute_step(start, stop)
+ diff = stop - start
+
+ step = (diff / QUERY_RANGE_DATA_POINTS).ceil
+
+ [QUERY_RANGE_MIN_STEP, step].max
+ end
+
private
- def json_api_get(type, args = {})
- path = ['api', 'v1', type].join('/')
- get(path, args)
- rescue JSON::ParserError
- raise PrometheusClient::Error, 'Parsing response failed'
- rescue Errno::ECONNREFUSED
- raise PrometheusClient::Error, 'Connection refused'
+ def api_path(type)
+ ['api', 'v1', type].join('/')
end
- def get(path, args)
- response = rest_client[path].get(params: args)
+ def json_api_get(type, args = {})
+ path = api_path(type)
+ response = get(path, args)
handle_response(response)
- rescue SocketError
- raise PrometheusClient::Error, "Can't connect to #{rest_client.url}"
- rescue OpenSSL::SSL::SSLError
- raise PrometheusClient::Error, "#{rest_client.url} contains invalid SSL data"
rescue RestClient::ExceptionWithResponse => ex
if ex.response
handle_exception_response(ex.response)
@@ -68,8 +95,18 @@ module Gitlab
raise PrometheusClient::Error, "Network connection error"
end
+ def get(path, args)
+ rest_client[path].get(params: args)
+ rescue SocketError
+ raise PrometheusClient::Error, "Can't connect to #{rest_client.url}"
+ rescue OpenSSL::SSL::SSLError
+ raise PrometheusClient::Error, "#{rest_client.url} contains invalid SSL data"
+ rescue Errno::ECONNREFUSED
+ raise PrometheusClient::Error, 'Connection refused'
+ end
+
def handle_response(response)
- json_data = JSON.parse(response.body)
+ json_data = parse_json(response.body)
if response.code == 200 && json_data['status'] == 'success'
json_data['data'] || {}
else
@@ -81,7 +118,7 @@ module Gitlab
if response.code == 200 && response['status'] == 'success'
response['data'] || {}
elsif response.code == 400
- json_data = JSON.parse(response.body)
+ json_data = parse_json(response.body)
raise PrometheusClient::QueryError, json_data['error'] || 'Bad data received'
else
raise PrometheusClient::Error, "#{response.code} - #{response.body}"
@@ -92,5 +129,11 @@ module Gitlab
data = yield
data['result'] if data['resultType'] == expected_type
end
+
+ def parse_json(response_body)
+ JSON.parse(response_body)
+ rescue JSON::ParserError
+ raise PrometheusClient::Error, 'Parsing response failed'
+ end
end
end
diff --git a/lib/gitlab/proxy_http_connection_adapter.rb b/lib/gitlab/proxy_http_connection_adapter.rb
deleted file mode 100644
index a64cb47e77e..00000000000
--- a/lib/gitlab/proxy_http_connection_adapter.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# This class is part of the Gitlab::HTTP wrapper. Depending on the value
-# of the global setting allow_local_requests_from_hooks_and_services this adapter
-# will allow/block connection to internal IPs and/or urls.
-#
-# This functionality can be overridden by providing the setting the option
-# allow_local_requests = true in the request. For example:
-# Gitlab::HTTP.get('http://www.gitlab.com', allow_local_requests: true)
-#
-# This option will take precedence over the global setting.
-module Gitlab
- class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter
- def connection
- unless allow_local_requests?
- begin
- Gitlab::UrlBlocker.validate!(uri, allow_local_network: false)
- rescue Gitlab::UrlBlocker::BlockedUrlError => e
- raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
- end
- end
-
- super
- end
-
- private
-
- def allow_local_requests?
- options.fetch(:allow_local_requests, allow_settings_local_requests?)
- end
-
- def allow_settings_local_requests?
- Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
- end
- end
-end
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
new file mode 100644
index 00000000000..3137676ba4b
--- /dev/null
+++ b/lib/gitlab/push_options.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class PushOptions
+ VALID_OPTIONS = HashWithIndifferentAccess.new({
+ merge_request: {
+ keys: [:create, :merge_when_pipeline_succeeds, :target]
+ },
+ ci: {
+ keys: [:skip]
+ }
+ }).freeze
+
+ NAMESPACE_ALIASES = HashWithIndifferentAccess.new({
+ mr: :merge_request
+ }).freeze
+
+ OPTION_MATCHER = /(?<namespace>[^\.]+)\.(?<key>[^=]+)=?(?<value>.*)/.freeze
+
+ attr_reader :options
+
+ def initialize(options = [])
+ @options = parse_options(options)
+ end
+
+ def get(*args)
+ options.dig(*args)
+ end
+
+ # Allow #to_json serialization
+ def as_json(*_args)
+ options
+ end
+
+ private
+
+ def parse_options(raw_options)
+ options = HashWithIndifferentAccess.new
+
+ Array.wrap(raw_options).each do |option|
+ namespace, key, value = parse_option(option)
+
+ next if [namespace, key].any?(&:nil?)
+
+ options[namespace] ||= HashWithIndifferentAccess.new
+ options[namespace][key] = value
+ end
+
+ options
+ end
+
+ def parse_option(option)
+ parts = OPTION_MATCHER.match(option)
+ return unless parts
+
+ namespace, key, value = parts.values_at(:namespace, :key, :value).map(&:strip)
+ namespace = NAMESPACE_ALIASES[namespace] if NAMESPACE_ALIASES[namespace]
+ value = value.presence || true
+
+ return unless valid_option?(namespace, key)
+
+ [namespace, key, value]
+ end
+
+ def valid_option?(namespace, key)
+ keys = VALID_OPTIONS.dig(namespace, :keys)
+ keys && keys.include?(key.to_sym)
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb
index 259345b8a9a..93030fd454e 100644
--- a/lib/gitlab/quick_actions/command_definition.rb
+++ b/lib/gitlab/quick_actions/command_definition.rb
@@ -4,7 +4,7 @@ module Gitlab
module QuickActions
class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params,
- :condition_block, :parse_params_block, :action_block, :warning
+ :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {})
@name = name
@@ -17,6 +17,7 @@ module Gitlab
@condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block]
@action_block = attributes[:action_block]
+ @types = attributes[:types] || []
end
def all_names
@@ -28,6 +29,7 @@ module Gitlab
end
def available?(context)
+ return false unless valid_type?(context)
return true unless condition_block
context.instance_exec(&condition_block)
@@ -48,6 +50,8 @@ module Gitlab
def execute(context, arg)
return if noop? || !available?(context)
+ count_commands_executed_in(context)
+
execute_block(action_block, context, arg)
end
@@ -73,6 +77,13 @@ module Gitlab
private
+ def count_commands_executed_in(context)
+ return unless context.respond_to?(:commands_executed_count=)
+
+ context.commands_executed_count ||= 0
+ context.commands_executed_count += 1
+ end
+
def execute_block(block, context, arg)
if arg.present?
parsed = parse_params(arg, context)
@@ -87,6 +98,10 @@ module Gitlab
context.instance_exec(arg, &parse_params_block)
end
+
+ def valid_type?(context)
+ types.blank? || types.any? { |type| context.quick_action_target.is_a?(type) }
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb
new file mode 100644
index 00000000000..1018910e8e9
--- /dev/null
+++ b/lib/gitlab/quick_actions/commit_actions.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module CommitActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Commit only quick actions definitions
+ desc _('Tag this commit.')
+ explanation do |tag_name, message|
+ if message.present?
+ _("Tags this commit to %{tag_name} with \"%{message}\".") % { tag_name: tag_name, message: message }
+ else
+ _("Tags this commit to %{tag_name}.") % { tag_name: tag_name }
+ end
+ end
+ params 'v1.2.3 <message>'
+ parse_params do |tag_name_and_message|
+ tag_name_and_message.split(' ', 2)
+ end
+ types Commit
+ condition do
+ current_user.can?(:push_code, project)
+ end
+ command :tag do |tag_name, message|
+ @updates[:tag_name] = tag_name
+ @updates[:tag_message] = message
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/common_actions.rb b/lib/gitlab/quick_actions/common_actions.rb
new file mode 100644
index 00000000000..5d2732d4826
--- /dev/null
+++ b/lib/gitlab/quick_actions/common_actions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module CommonActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # This is a dummy command, so that it appears in the autocomplete commands
+ desc 'CC'
+ params '@user'
+ command :cc
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb
index a3aab92061b..ecb2169151e 100644
--- a/lib/gitlab/quick_actions/dsl.rb
+++ b/lib/gitlab/quick_actions/dsl.rb
@@ -24,7 +24,7 @@ module Gitlab
# Example:
#
# desc do
- # "This is a dynamic description for #{noteable.to_ability_name}"
+ # "This is a dynamic description for #{quick_action_target.to_ability_name}"
# end
# command :command_key do |arguments|
# # Awesome code block
@@ -66,6 +66,23 @@ module Gitlab
@explanation = block_given? ? block : text
end
+ # Allows to define type(s) that must be met in order for the command
+ # to be returned by `.command_names` & `.command_definitions`.
+ #
+ # It is being evaluated before the conditions block is being evaluated
+ #
+ # If no types are passed then any type is allowed as the check is simply skipped.
+ #
+ # Example:
+ #
+ # types Commit, Issue, MergeRequest
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # end
+ def types(*types_list)
+ @types = types_list
+ end
+
# Allows to define conditions that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
# It accepts a block that will be evaluated with the context
@@ -144,7 +161,8 @@ module Gitlab
params: @params,
condition_block: @condition_block,
parse_params_block: @parse_params_block,
- action_block: block
+ action_block: block,
+ types: @types
)
self.command_definitions << definition
@@ -159,6 +177,7 @@ module Gitlab
@condition_block = nil
@warning = nil
@parse_params_block = nil
+ @types = nil
end
end
end
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
new file mode 100644
index 00000000000..572c55efcc2
--- /dev/null
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssuableActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ SHRUG = '¯\\_(ツ)_/¯'.freeze
+ TABLEFLIP = '(╯°□°)╯︵ â”»â”â”»'.freeze
+
+ included do
+ # Issue, MergeRequest, Epic: quick actions definitions
+ desc do
+ "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ end
+ explanation do
+ "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.open? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :close do
+ @updates[:state_event] = 'close'
+ end
+
+ desc do
+ "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ end
+ explanation do
+ "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.closed? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :reopen do
+ @updates[:state_event] = 'reopen'
+ end
+
+ desc _('Change title')
+ explanation do |title_param|
+ _("Changes the title to \"%{title_param}\".") % { title_param: title_param }
+ end
+ params '<New title>'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :title do |title_param|
+ @updates[:title] = title_param
+ end
+
+ desc _('Add label(s)')
+ explanation do |labels_param|
+ labels = find_label_references(labels_param)
+
+ "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ parent &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) &&
+ find_labels.any?
+ end
+ command :label do |labels_param|
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:add_label_ids] ||= []
+ @updates[:add_label_ids] += label_ids
+
+ @updates[:add_label_ids].uniq!
+ end
+ end
+
+ desc _('Remove all or specific label(s)')
+ explanation do |labels_param = nil|
+ if labels_param.present?
+ labels = find_label_references(labels_param)
+ "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ else
+ _('Removes all labels.')
+ end
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.labels.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
+ end
+ command :unlabel do |labels_param = nil|
+ if labels_param.present?
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:remove_label_ids] ||= []
+ @updates[:remove_label_ids] += label_ids
+
+ @updates[:remove_label_ids].uniq!
+ end
+ else
+ @updates[:label_ids] = []
+ end
+ end
+
+ desc _('Replace all label(s)')
+ explanation do |labels_param|
+ labels = find_label_references(labels_param)
+ "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.labels.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
+ end
+ command :relabel do |labels_param|
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:label_ids] ||= []
+ @updates[:label_ids] += label_ids
+
+ @updates[:label_ids].uniq!
+ end
+ end
+
+ desc _('Add a todo')
+ explanation _('Adds a todo.')
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ !TodoService.new.todo_exist?(quick_action_target, current_user)
+ end
+ command :todo do
+ @updates[:todo_event] = 'add'
+ end
+
+ desc _('Mark todo as done')
+ explanation _('Marks todo as done.')
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ TodoService.new.todo_exist?(quick_action_target, current_user)
+ end
+ command :done do
+ @updates[:todo_event] = 'done'
+ end
+
+ desc _('Subscribe')
+ explanation do
+ "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ !quick_action_target.subscribed?(current_user, project)
+ end
+ command :subscribe do
+ @updates[:subscription_event] = 'subscribe'
+ end
+
+ desc _('Unsubscribe')
+ explanation do
+ "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.subscribed?(current_user, project)
+ end
+ command :unsubscribe do
+ @updates[:subscription_event] = 'unsubscribe'
+ end
+
+ desc _('Toggle emoji award')
+ explanation do |name|
+ _("Toggles :%{name}: emoji award.") % { name: name } if name
+ end
+ params ':emoji:'
+ types Issuable
+ condition do
+ quick_action_target.persisted?
+ end
+ parse_params do |emoji_param|
+ match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
+ match[1] if match
+ end
+ command :award do |name|
+ if name && quick_action_target.user_can_award?(current_user)
+ @updates[:emoji_award] = name
+ end
+ end
+
+ desc _("Append the comment with %{shrug}") % { shrug: SHRUG }
+ params '<Comment>'
+ types Issuable
+ substitution :shrug do |comment|
+ "#{comment} #{SHRUG}"
+ end
+
+ desc _("Append the comment with %{TABLEFLIP}") % { tableflip: TABLEFLIP }
+ params '<Comment>'
+ types Issuable
+ substitution :tableflip do |comment|
+ "#{comment} #{TABLEFLIP}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
new file mode 100644
index 00000000000..85e62f950c8
--- /dev/null
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssueActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Issue only quick actions definition
+ desc _('Set due date')
+ explanation do |due_date|
+ _("Sets the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
+ end
+ params '<in 2 days | this Friday | December 31st>'
+ types Issue
+ condition do
+ quick_action_target.respond_to?(:due_date) &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |due_date_param|
+ Chronic.parse(due_date_param).try(:to_date)
+ end
+ command :due do |due_date|
+ @updates[:due_date] = due_date if due_date
+ end
+
+ desc _('Remove due date')
+ explanation _('Removes the due date.')
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.respond_to?(:due_date) &&
+ quick_action_target.due_date? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_due_date do
+ @updates[:due_date] = nil
+ end
+
+ desc _('Move issue from one column of the board to another')
+ explanation do |target_list_name|
+ label = find_label_references(target_list_name).first
+ _("Moves issue to %{label} column in the board.") % { label: label } if label
+ end
+ params '~"Target column"'
+ types Issue
+ condition do
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) &&
+ quick_action_target.project.boards.count == 1
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ command :board_move do |target_list_name|
+ label_ids = find_label_ids(target_list_name)
+
+ if label_ids.size == 1
+ label_id = label_ids.first
+
+ # Ensure this label corresponds to a list on the board
+ next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists?
+
+ @updates[:remove_label_ids] =
+ quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
+ @updates[:add_label_ids] = [label_id]
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc _('Mark this issue as a duplicate of another issue')
+ explanation do |duplicate_reference|
+ _("Marks this issue as a duplicate of %{duplicate_reference}.") % { duplicate_reference: duplicate_reference }
+ end
+ params '#issue'
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :duplicate do |duplicate_param|
+ canonical_issue = extract_references(duplicate_param, :issue).first
+
+ if canonical_issue.present?
+ @updates[:canonical_issue_id] = canonical_issue.id
+ end
+ end
+
+ desc _('Move this issue to another project.')
+ explanation do |path_to_project|
+ _("Moves this issue to %{path_to_project}.") % { path_to_project: path_to_project }
+ end
+ params 'path/to/project'
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :move do |target_project_path|
+ target_project = Project.find_by_full_path(target_project_path)
+
+ if target_project.present?
+ @updates[:target_project] = target_project
+ end
+ end
+
+ desc _('Make issue confidential.')
+ explanation do
+ _('Makes this issue confidential')
+ end
+ types Issue
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :confidential do
+ @updates[:confidential] = true
+ end
+
+ desc _('Create a merge request.')
+ explanation do |branch_name = nil|
+ if branch_name
+ _("Creates branch '%{branch_name}' and a merge request to resolve this issue") % { branch_name: branch_name }
+ else
+ "Creates a branch and a merge request to resolve this issue"
+ end
+ end
+ params "<branch name>"
+ types Issue
+ condition do
+ current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project)
+ end
+ command :create_merge_request do |branch_name = nil|
+ @updates[:create_merge_request] = {
+ branch_name: branch_name,
+ issue_iid: quick_action_target.iid
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
new file mode 100644
index 00000000000..e1579cfddc0
--- /dev/null
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -0,0 +1,225 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssueAndMergeRequestActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Issue, MergeRequest: quick actions definitions
+ desc _('Assign')
+ # rubocop: disable CodeReuse/ActiveRecord
+ explanation do |users|
+ users = quick_action_target.allows_multiple_assignees? ? users : users.take(1)
+ "Assigns #{users.map(&:to_reference).to_sentence}."
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ params do
+ quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
+ end
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |assignee_param|
+ extract_users(assignee_param)
+ end
+ command :assign do |users|
+ next if users.empty?
+
+ if quick_action_target.allows_multiple_assignees?
+ @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
+ @updates[:assignee_ids] += users.map(&:id)
+ else
+ @updates[:assignee_ids] = [users.first.id]
+ end
+ end
+
+ desc do
+ if quick_action_target.allows_multiple_assignees?
+ _('Remove all or specific assignee(s)')
+ else
+ _('Remove assignee')
+ end
+ end
+ explanation do |users = nil|
+ assignees = quick_action_target.assignees
+ assignees &= users if users.present? && quick_action_target.allows_multiple_assignees?
+ "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
+ end
+ params do
+ quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
+ end
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.assignees.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |unassign_param|
+ # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
+ extract_users(unassign_param) if quick_action_target.allows_multiple_assignees?
+ end
+ command :unassign do |users = nil|
+ if quick_action_target.allows_multiple_assignees? && users&.any?
+ @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
+ @updates[:assignee_ids] -= users.map(&:id)
+ else
+ @updates[:assignee_ids] = []
+ end
+ end
+
+ desc _('Set milestone')
+ explanation do |milestone|
+ _("Sets the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
+ end
+ params '%"milestone"'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) &&
+ find_milestones(project, state: 'active').any?
+ end
+ parse_params do |milestone_param|
+ extract_references(milestone_param, :milestone).first ||
+ find_milestones(project, title: milestone_param.strip).first
+ end
+ command :milestone do |milestone|
+ @updates[:milestone_id] = milestone.id if milestone
+ end
+
+ desc _('Remove milestone')
+ explanation do
+ _("Removes %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
+ end
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.milestone_id? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_milestone do
+ @updates[:milestone_id] = nil
+ end
+
+ desc _('Copy labels and milestone from other issue or merge request in this project')
+ explanation do |source_issuable|
+ _("Copy labels and milestone from %{source_issuable_reference}.") % { source_issuable_reference: source_issuable.to_reference }
+ end
+ params '#issue | !merge_request'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ parse_params do |issuable_param|
+ extract_references(issuable_param, :issue).first ||
+ extract_references(issuable_param, :merge_request).first
+ end
+ command :copy_metadata do |source_issuable|
+ if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id
+ @updates[:add_label_ids] = source_issuable.labels.map(&:id)
+ @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
+ end
+ end
+
+ desc _('Set time estimate')
+ explanation do |time_estimate|
+ time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
+
+ _("Sets time estimate to %{time_estimate}.") % { time_estimate: time_estimate } if time_estimate
+ end
+ params '<1w 3d 2h 14m>'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |raw_duration|
+ Gitlab::TimeTrackingFormatter.parse(raw_duration)
+ end
+ command :estimate do |time_estimate|
+ if time_estimate
+ @updates[:time_estimate] = time_estimate
+ end
+ end
+
+ desc _('Add or subtract spent time')
+ explanation do |time_spent, time_spent_date|
+ if time_spent
+ if time_spent > 0
+ verb = _('Adds')
+ value = time_spent
+ else
+ verb = _('Subtracts')
+ value = -time_spent
+ end
+
+ _("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: Gitlab::TimeTrackingFormatter.output(value) }
+ end
+ end
+ params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ parse_params do |raw_time_date|
+ Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
+ end
+ command :spend do |time_spent, time_spent_date|
+ if time_spent
+ @updates[:spend_time] = {
+ duration: time_spent,
+ user_id: current_user.id,
+ spent_at: time_spent_date
+ }
+ end
+ end
+
+ desc _('Remove time estimate')
+ explanation _('Removes time estimate.')
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_estimate do
+ @updates[:time_estimate] = 0
+ end
+
+ desc _('Remove spent time')
+ explanation _('Removes spent time.')
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ types Issue, MergeRequest
+ command :remove_time_spent do
+ @updates[:spend_time] = { duration: :reset, user_id: current_user.id }
+ end
+
+ desc _("Lock the discussion")
+ explanation _("Locks the discussion")
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ !quick_action_target.discussion_locked? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :lock do
+ @updates[:discussion_locked] = true
+ end
+
+ desc _("Unlock the discussion")
+ explanation _("Unlocks the discussion")
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.discussion_locked? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :unlock do
+ @updates[:discussion_locked] = false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
new file mode 100644
index 00000000000..bade59182a1
--- /dev/null
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module MergeRequestActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # MergeRequest only quick actions definitions
+ desc 'Merge (when the pipeline succeeds)'
+ explanation 'Merges this merge request when the pipeline succeeds.'
+ types MergeRequest
+ condition do
+ last_diff_sha = params && params[:merge_request_diff_head_sha]
+ quick_action_target.persisted? &&
+ quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
+ end
+ command :merge do
+ @updates[:merge] = params[:merge_request_diff_head_sha]
+ end
+
+ desc 'Toggle the Work In Progress status'
+ explanation do
+ verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks'
+ noun = quick_action_target.to_ability_name.humanize(capitalize: false)
+ "#{verb} this #{noun} as Work In Progress."
+ end
+ types MergeRequest
+ condition do
+ quick_action_target.respond_to?(:work_in_progress?) &&
+ # Allow it to mark as WIP on MR creation page _or_ through MR notes.
+ (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
+ end
+ command :wip do
+ @updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
+ end
+
+ desc 'Set target branch'
+ explanation do |branch_name|
+ "Sets target branch to #{branch_name}."
+ end
+ params '<Local branch name>'
+ types MergeRequest
+ condition do
+ quick_action_target.respond_to?(:target_branch) &&
+ (current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) ||
+ quick_action_target.new_record?)
+ end
+ parse_params do |target_branch_param|
+ target_branch_param.strip
+ end
+ command :target_branch do |branch_name|
+ @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
index f5176376a60..4a62e83e8e9 100644
--- a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
+++ b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
@@ -11,7 +11,7 @@ module Gitlab
# if date doesn't present return time with current date
# in other cases return nil
class SpendTimeAndDateSeparator
- DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})}
+ DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})}.freeze
def initialize(spend_command_arg)
@spend_arg = spend_command_arg
diff --git a/lib/gitlab/rack_timeout_observer.rb b/lib/gitlab/rack_timeout_observer.rb
new file mode 100644
index 00000000000..80d3f7dea60
--- /dev/null
+++ b/lib/gitlab/rack_timeout_observer.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class RackTimeoutObserver
+ def initialize
+ @counter = Gitlab::Metrics.counter(:rack_state_total, 'Number of requests in a given rack state')
+ end
+
+ # returns the Proc to be used as the observer callback block
+ def callback
+ method(:log_timeout_exception)
+ end
+
+ private
+
+ def log_timeout_exception(env)
+ info = env[::Rack::Timeout::ENV_INFO_KEY]
+ return unless info
+
+ @counter.increment(labels(info, env))
+ end
+
+ def labels(info, env)
+ params = controller_params(env) || grape_params(env) || {}
+
+ {
+ controller: params['controller'],
+ action: params['action'],
+ route: params['route'],
+ state: info.state
+ }
+ end
+
+ def controller_params(env)
+ env['action_dispatch.request.parameters']
+ end
+
+ def grape_params(env)
+ endpoint = env[Grape::Env::API_ENDPOINT]
+ route = endpoint&.route&.pattern&.origin
+ return unless route
+
+ { 'route' => route }
+ end
+ end
+end
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index 6559c3e3c57..772d743c9b0 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -5,8 +5,8 @@ module Gitlab
def self.load_configurations!
if Gitlab::CurrentSettings.recaptcha_enabled
::Recaptcha.configure do |config|
- config.public_key = Gitlab::CurrentSettings.recaptcha_site_key
- config.private_key = Gitlab::CurrentSettings.recaptcha_private_key
+ config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
+ config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
end
true
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 202d310e237..b4f41b9cd9a 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -5,19 +5,29 @@ module Gitlab
NotFoundError = Class.new(StandardError)
def self.parse(repo_path)
- wiki = false
project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '')
- project, was_redirected = find_project(project_path)
-
- if project_path.end_with?('.wiki') && project.nil?
- project, was_redirected = find_project(project_path.chomp('.wiki'))
- wiki = true
+ # Detect the repo type based on the path, the first one tried is the project
+ # type, which does not have a suffix.
+ Gitlab::GlRepository.types.each do |_name, type|
+ # If the project path does not end with the defined suffix, try the next
+ # type.
+ # We'll always try to find a project with an empty suffix (for the
+ # `Gitlab::GlRepository::PROJECT` type.
+ next unless project_path.end_with?(type.path_suffix)
+
+ project, was_redirected = find_project(project_path.chomp(type.path_suffix))
+ redirected_path = project_path if was_redirected
+
+ # If we found a matching project, then the type was matched, no need to
+ # continue looking.
+ return [project, type, redirected_path] if project
end
- redirected_path = project_path if was_redirected
-
- [project, wiki, redirected_path]
+ # When a project did not exist, the parsed repo_type would be empty.
+ # In that case, we want to continue with a regular project repository. As we
+ # could create the project if the user pushing is allowed to do so.
+ [nil, Gitlab::GlRepository.default_type, nil]
end
def self.find_project(project_path)
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
index d9811e036d3..f6d289476c5 100644
--- a/lib/gitlab/request_context.rb
+++ b/lib/gitlab/request_context.rb
@@ -13,7 +13,13 @@ module Gitlab
end
def call(env)
- req = ActionDispatch::Request.new(env)
+ # We should be using ActionDispatch::Request instead of
+ # Rack::Request to be consistent with Rails, but due to a Rails
+ # bug described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58573#note_149799010
+ # hosts behind a load balancer will only see 127.0.0.1 for the
+ # load balancer's IP.
+ req = Rack::Request.new(env)
Gitlab::SafeRequestStore[:client_ip] = req.ip
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 3b05f181ed2..84885be9bda 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -45,7 +45,7 @@ module Gitlab
def self.redirect_legacy_paths(router, *paths)
build_redirect_path = lambda do |request, _params, path|
- # Only replace the last occurence of `path`.
+ # Only replace the last occurrence of `path`.
#
# `request.fullpath` includes the querystring
new_path = request.path.sub(%r{/#{path}(/*)(?!.*#{path})}, "/-/#{path}\\1")
diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
new file mode 100644
index 00000000000..0928ccdc324
--- /dev/null
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Sanitizers
+ class Exif
+ # these tags are not removed from the image
+ WHITELISTED_TAGS = %w(
+ ResolutionUnit
+ XResolution
+ YResolution
+ YCbCrSubSampling
+ YCbCrPositioning
+ BitsPerSample
+ ImageHeight
+ ImageWidth
+ ImageSize
+ Copyright
+ CopyrightNotice
+ Orientation
+ ).freeze
+
+ # these tags are common in exiftool output, these
+ # do not contain any sensitive information, but
+ # we don't need to preserve them when removing
+ # exif tags
+ IGNORED_TAGS = %w(
+ ColorComponents
+ EncodingProcess
+ ExifByteOrder
+ ExifToolVersion
+ JFIFVersion
+ Directory
+ FileAccessDate
+ FileInodeChangeDate
+ FileModifyDate
+ FileName
+ FilePermissions
+ FileSize
+ SourceFile
+ Megapixels
+ FileType
+ FileTypeExtension
+ MIMEType
+ ).freeze
+
+ ALLOWED_TAGS = WHITELISTED_TAGS + IGNORED_TAGS
+ EXCLUDE_PARAMS = WHITELISTED_TAGS.map { |tag| "-#{tag}" }
+
+ attr_reader :logger
+
+ def initialize(logger: Rails.logger)
+ @logger = logger
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil)
+ relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?',
+ '%.jpg', '%.jpeg', '%.tiff')
+
+ logger.info "running in dry run mode, no images will be rewritten" if dry_run
+
+ find_params = {
+ start: start_id.present? ? start_id.to_i : nil,
+ finish: stop_id.present? ? stop_id.to_i : Upload.last&.id
+ }
+
+ relation.find_each(find_params) do |upload|
+ clean(upload.build_uploader, dry_run: dry_run)
+ sleep sleep_time if sleep_time
+ rescue => err
+ logger.error "failed to sanitize #{upload_ref(upload)}: #{err.message}"
+ logger.debug err.backtrace.join("\n ")
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def clean(uploader, dry_run: true)
+ Dir.mktmpdir('gitlab-exif') do |tmpdir|
+ src_path = fetch_upload_to_file(uploader, tmpdir)
+
+ to_remove = extra_tags(src_path)
+
+ if to_remove.empty?
+ logger.info "#{upload_ref(uploader.upload)}: only whitelisted tags present, skipping"
+ break
+ end
+
+ logger.info "#{upload_ref(uploader.upload)}: found exif tags to remove: #{to_remove}"
+
+ break if dry_run
+
+ remove_and_store(tmpdir, src_path, uploader)
+ end
+ end
+
+ def extra_tags(path)
+ exif_tags(path).keys - ALLOWED_TAGS
+ end
+
+ private
+
+ def remove_and_store(tmpdir, src_path, uploader)
+ exec_remove_exif!(src_path)
+ logger.info "#{upload_ref(uploader.upload)}: exif removed, storing"
+ File.open(src_path, 'r') { |f| uploader.store!(f) }
+ end
+
+ def exec_remove_exif!(path)
+ # IPTC and XMP-iptcExt groups may keep copyright information so
+ # we always preserve them
+ cmd = ["exiftool", "-all=", "-tagsFromFile", "@", *EXCLUDE_PARAMS, "--IPTC:all", "--XMP-iptcExt:all", path]
+ output, status = Gitlab::Popen.popen(cmd)
+
+ if status != 0
+ raise "exiftool return code is #{status}: #{output}"
+ end
+
+ if File.size(path) == 0
+ raise "size of file is 0"
+ end
+
+ # exiftool creates backup of the original file in filename_original
+ old_path = "#{path}_original"
+ if File.size(path) == File.size(old_path)
+ raise "size of sanitized file is same as original size"
+ end
+ end
+
+ def fetch_upload_to_file(uploader, dir)
+ # upload is stored into the file with the original name - this filename
+ # is used by carrierwave when storing the file back to the storage
+ filename = File.join(dir, uploader.filename)
+
+ File.open(filename, 'w') do |file|
+ file.binmode
+ file.write uploader.read
+ end
+
+ filename
+ end
+
+ def upload_ref(upload)
+ "#{upload.id}:#{upload.path}"
+ end
+
+ def exif_tags(path)
+ cmd = ["exiftool", "-all", "-j", "-sort", "--IPTC:all", "--XMP-iptcExt:all", path]
+ output, status = Gitlab::Popen.popen(cmd)
+
+ raise "failed to get exif tags: #{output}" if status != 0
+
+ JSON.parse(output).first
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb
index 0d4e6be2129..98f78c5e74b 100644
--- a/lib/gitlab/sanitizers/svg.rb
+++ b/lib/gitlab/sanitizers/svg.rb
@@ -9,7 +9,7 @@ module Gitlab
class Scrubber < Loofah::Scrubber
# http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data-with-the-data-*-attributes
- DATA_ATTR_PATTERN = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u
+ DATA_ATTR_PATTERN = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u.freeze
def scrub(node)
unless Whitelist::ALLOWED_ELEMENTS.include?(node.name)
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 491148ec1a6..3daa03d01d6 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -2,6 +2,8 @@
module Gitlab
class SearchResults
+ COUNT_LIMIT = 1001
+
attr_reader :current_user, :query, :per_page
# Limit search results by passed projects
@@ -25,27 +27,26 @@ module Gitlab
def objects(scope, page = nil, without_count = true)
collection = case scope
when 'projects'
- projects.page(page).per(per_page)
+ projects
when 'issues'
- issues.page(page).per(per_page)
+ issues
when 'merge_requests'
- merge_requests.page(page).per(per_page)
+ merge_requests
when 'milestones'
- milestones.page(page).per(per_page)
+ milestones
+ when 'users'
+ users
else
- Kaminari.paginate_array([]).page(page).per(per_page)
- end
+ Kaminari.paginate_array([])
+ end.page(page).per(per_page)
without_count ? collection.without_count : collection
end
- # rubocop: disable CodeReuse/ActiveRecord
def limited_projects_count
- @limited_projects_count ||= projects.limit(count_limit).count
+ @limited_projects_count ||= limited_count(projects)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def limited_issues_count
return @limited_issues_count if @limited_issues_count
@@ -54,29 +55,38 @@ module Gitlab
# and confidential issues user has access to, is too complex.
# It's faster to try to fetch all public issues first, then only
# if necessary try to fetch all issues.
- sum = issues(public_only: true).limit(count_limit).count
- @limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum
+ sum = limited_count(issues(public_only: true))
+ @limited_issues_count = sum < count_limit ? limited_count(issues) : sum
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def limited_merge_requests_count
- @limited_merge_requests_count ||= merge_requests.limit(count_limit).count
+ @limited_merge_requests_count ||= limited_count(merge_requests)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def limited_milestones_count
- @limited_milestones_count ||= milestones.limit(count_limit).count
+ @limited_milestones_count ||= limited_count(milestones)
+ end
+
+ def limited_users_count
+ @limited_users_count ||= limited_count(users)
end
- # rubocop: enable CodeReuse/ActiveRecord
def single_commit_result?
false
end
def count_limit
- 1001
+ COUNT_LIMIT
+ end
+
+ def users
+ return User.none unless Ability.allowed?(current_user, :read_users_list)
+
+ UsersFinder.new(current_user, search: query).execute
+ end
+
+ def display_options(_scope)
+ {}
end
private
@@ -85,58 +95,82 @@ module Gitlab
limit_projects.search(query)
end
- # rubocop: disable CodeReuse/ActiveRecord
def issues(finder_params = {})
- issues = IssuesFinder.new(current_user, finder_params).execute
+ issues = IssuesFinder.new(current_user, issuable_params.merge(finder_params)).execute
+
unless default_project_filter
- issues = issues.where(project_id: project_ids_relation)
+ issues = issues.where(project_id: project_ids_relation) # rubocop: disable CodeReuse/ActiveRecord
end
- issues =
- if query =~ /#(\d+)\z/
- issues.where(iid: $1)
- else
- issues.full_search(query)
- end
-
- issues.reorder('updated_at DESC')
+ issues
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def milestones
- milestones = Milestone.where(project_id: project_ids_relation)
- milestones = milestones.search(query)
+ milestones = Milestone.search(query)
+
+ milestones = filter_milestones_by_project(milestones)
+
milestones.reorder('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def merge_requests
- merge_requests = MergeRequestsFinder.new(current_user).execute
+ merge_requests = MergeRequestsFinder.new(current_user, issuable_params).execute
+
unless default_project_filter
merge_requests = merge_requests.in_projects(project_ids_relation)
end
- merge_requests =
- if query =~ /[#!](\d+)\z/
- merge_requests.where(iid: $1)
- else
- merge_requests.full_search(query)
- end
-
- merge_requests.reorder('updated_at DESC')
+ merge_requests
end
- # rubocop: enable CodeReuse/ActiveRecord
def default_scope
'projects'
end
+ # Filter milestones by authorized projects.
+ # For performance reasons project_id is being plucked
+ # to be used on a smaller query.
+ #
+ # rubocop: disable CodeReuse/ActiveRecord
+ def filter_milestones_by_project(milestones)
+ project_ids =
+ milestones.where(project_id: project_ids_relation)
+ .select(:project_id).distinct
+ .pluck(:project_id)
+
+ return Milestone.none if project_ids.nil?
+
+ authorized_project_ids_relation =
+ Project.where(id: project_ids).ids_with_milestone_available_for(current_user)
+
+ milestones.where(project_id: authorized_project_ids_relation)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
# rubocop: disable CodeReuse/ActiveRecord
def project_ids_relation
limit_projects.select(:id).reorder(nil)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def issuable_params
+ {}.tap do |params|
+ params[:sort] = 'updated_desc'
+
+ if query =~ /#(\d+)\z/
+ params[:iids] = $1
+ else
+ params[:search] = query
+ end
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def limited_count(relation)
+ relation.reorder(nil).limit(count_limit).size
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 956c16117f5..72c44114001 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -10,7 +10,7 @@ module Gitlab
def self.context(current_user = nil)
return unless enabled?
- Raven.tags_context(locale: I18n.locale)
+ Raven.tags_context(default_tags)
if current_user
Raven.user_context(
@@ -44,16 +44,19 @@ module Gitlab
extra[:issue_url] = issue_url if issue_url
context # Make sure we've set everything we know in the context
- tags = {
- Gitlab::CorrelationId::LOG_KEY.to_sym => Gitlab::CorrelationId.current_id
- }
-
- Raven.capture_exception(exception, tags: tags, extra: extra)
+ Raven.capture_exception(exception, tags: default_tags, extra: extra)
end
end
def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
+
+ def self.default_tags
+ {
+ Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id,
+ locale: I18n.locale
+ }
+ end
end
end
diff --git a/lib/gitlab/session.rb b/lib/gitlab/session.rb
new file mode 100644
index 00000000000..7487ba04a6d
--- /dev/null
+++ b/lib/gitlab/session.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class Session
+ STORE_KEY = :session_storage
+
+ class << self
+ def with_session(session)
+ old = self.current
+ self.current = session
+ yield
+ ensure
+ self.current = old
+ end
+
+ def current
+ Thread.current[STORE_KEY]
+ end
+
+ protected
+
+ def current=(value)
+ Thread.current[STORE_KEY] = value
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 2b7e12639be..0d3e78c0a66 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -44,6 +44,13 @@ module Gitlab
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
+ if Rails.env.test?
+ # Compared to production, tests run in constrained environments. This
+ # number is meant to grow with the number of concurrent rails requests /
+ # sidekiq jobs, and concurrency will be low anyway in test.
+ config[:git] = { catfile_cache_size: 5 }
+ end
+
TomlRB.dump(config)
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index c7d8dfcd495..93182607616 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -10,18 +10,6 @@ module Gitlab
Error = Class.new(StandardError)
- KeyAdder = Struct.new(:io) do
- def add_key(id, key)
- key = Gitlab::Shell.strip_key(key)
- # Newline and tab are part of the 'protocol' used to transmit id+key to the other end
- if key.include?("\t") || key.include?("\n")
- raise Error.new("Invalid key: #{key.inspect}")
- end
-
- io.puts("#{id}\t#{key}")
- end
- end
-
class << self
def secret_token
@secret_token ||= begin
@@ -40,10 +28,6 @@ module Gitlab
.join('GITLAB_SHELL_VERSION')).strip
end
- def strip_key(key)
- key.split(/[ ]+/)[0, 2].join(' ')
- end
-
private
# Create (if necessary) and link the secret token file
@@ -173,7 +157,7 @@ module Gitlab
false
end
- # Add new key to gitlab-shell
+ # Add new key to authorized_keys
#
# Ex.
# add_key("key-42", "sha-rsa ...")
@@ -181,33 +165,53 @@ module Gitlab
def add_key(key_id, key_content)
return unless self.authorized_keys_enabled?
- gitlab_shell_fast_execute([gitlab_shell_keys_path,
- 'add-key', key_id, self.class.strip_key(key_content)])
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([
+ gitlab_shell_keys_path,
+ 'add-key',
+ key_id,
+ strip_key(key_content)
+ ])
+ else
+ gitlab_authorized_keys.add_key(key_id, key_content)
+ end
end
# Batch-add keys to authorized_keys
#
# Ex.
- # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
- def batch_add_keys(&block)
+ # batch_add_keys(Key.all)
+ def batch_add_keys(keys)
return unless self.authorized_keys_enabled?
- IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
- yield(KeyAdder.new(io))
+ if shell_out_for_gitlab_keys?
+ begin
+ IO.popen("#{gitlab_shell_keys_path} batch-add-keys", 'w') do |io|
+ add_keys_to_io(keys, io)
+ end
+
+ $?.success?
+ rescue Error
+ false
+ end
+ else
+ gitlab_authorized_keys.batch_add_keys(keys)
end
end
- # Remove ssh key from gitlab shell
+ # Remove ssh key from authorized_keys
#
# Ex.
- # remove_key("key-342", "sha-rsa ...")
+ # remove_key("key-342")
#
- def remove_key(key_id, key_content = nil)
+ def remove_key(id, _ = nil)
return unless self.authorized_keys_enabled?
- args = [gitlab_shell_keys_path, 'rm-key', key_id]
- args << key_content if key_content
- gitlab_shell_fast_execute(args)
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([gitlab_shell_keys_path, 'rm-key', id])
+ else
+ gitlab_authorized_keys.rm_key(id)
+ end
end
# Remove all ssh keys from gitlab shell
@@ -218,7 +222,11 @@ module Gitlab
def remove_all_keys
return unless self.authorized_keys_enabled?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
+ else
+ gitlab_authorized_keys.clear
+ end
end
# Remove ssh keys from gitlab shell that are not in the DB
@@ -247,33 +255,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # Iterate over all ssh key IDs from gitlab shell, in batches
- #
- # Ex.
- # batch_read_key_ids { |batch| keys = Key.where(id: batch) }
- #
- def batch_read_key_ids(batch_size: 100, &block)
- return unless self.authorized_keys_enabled?
-
- list_key_ids do |key_id_stream|
- key_id_stream.lazy.each_slice(batch_size) do |lines|
- key_ids = lines.map { |l| l.chomp.to_i }
- yield(key_ids)
- end
- end
- end
-
- # Stream all ssh key IDs from gitlab shell, separated by newlines
- #
- # Ex.
- # list_key_ids
- #
- def list_key_ids(&block)
- return unless self.authorized_keys_enabled?
-
- IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids), &block)
- end
-
# Add empty directory for storing repositories
#
# Ex.
@@ -340,16 +321,16 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ def hooks_path
+ File.join(gitlab_shell_path, 'hooks')
+ end
+
protected
def gitlab_shell_path
File.expand_path(Gitlab.config.gitlab_shell.path)
end
- def gitlab_shell_hooks_path
- File.expand_path(Gitlab.config.gitlab_shell.hooks_path)
- end
-
def gitlab_shell_user_home
File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
end
@@ -378,6 +359,10 @@ module Gitlab
private
+ def shell_out_for_gitlab_keys?
+ Gitlab.config.gitlab_shell.authorized_keys_file.blank?
+ end
+
def gitlab_shell_fast_execute(cmd)
output, status = gitlab_shell_fast_execute_helper(cmd)
@@ -415,6 +400,40 @@ module Gitlab
raise Error, e
end
+ def gitlab_authorized_keys
+ @gitlab_authorized_keys ||= Gitlab::AuthorizedKeys.new
+ end
+
+ def batch_read_key_ids(batch_size: 100, &block)
+ return unless self.authorized_keys_enabled?
+
+ if shell_out_for_gitlab_keys?
+ IO.popen("#{gitlab_shell_keys_path} list-key-ids") do |key_id_stream|
+ key_id_stream.lazy.each_slice(batch_size) do |lines|
+ yield(lines.map { |l| l.chomp.to_i })
+ end
+ end
+ else
+ gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
+ yield(key_ids)
+ end
+ end
+ end
+
+ def strip_key(key)
+ key.split(/[ ]+/)[0, 2].join(' ')
+ end
+
+ def add_keys_to_io(keys, io)
+ keys.each do |k|
+ key = strip_key(k.key)
+
+ raise Error.new("Invalid key: #{key.inspect}") if key.include?("\t") || key.include?("\n")
+
+ io.puts("#{k.shell_id}\t#{key}")
+ end
+ end
+
class GitalyGitlabProjects
attr_reader :shard_name, :repository_relative_path, :output, :gl_project_path
diff --git a/lib/gitlab/sherlock/middleware.rb b/lib/gitlab/sherlock/middleware.rb
index 747cb0f9142..f7b08d58e49 100644
--- a/lib/gitlab/sherlock/middleware.rb
+++ b/lib/gitlab/sherlock/middleware.rb
@@ -4,9 +4,9 @@ module Gitlab
module Sherlock
# Rack middleware used for tracking request metrics.
class Middleware
- CONTENT_TYPES = %r{text/html|application/json}i
+ CONTENT_TYPES = %r{text/html|application/json}i.freeze
- IGNORE_PATHS = %r{^/sherlock}
+ IGNORE_PATHS = %r{^/sherlock}.freeze
def initialize(app)
@app = app
diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb
index 11561eec32a..159ce27e702 100644
--- a/lib/gitlab/sherlock/query.rb
+++ b/lib/gitlab/sherlock/query.rb
@@ -15,7 +15,7 @@ module Gitlab
|GROUP\s+BY
|ORDER\s+BY
|LIMIT
- |OFFSET)\s+}ix # Vim indent breaks when this is on a newline :<
+ |OFFSET)\s+}ix.freeze # Vim indent breaks when this is on a newline :<
# Creates a new Query using a String and a separate Array of bindings.
#
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index 3b8de64913b..c102fa14cfc 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -7,7 +7,7 @@ module Gitlab
module SidekiqConfig
QUEUE_CONFIG_PATHS = %w[app/workers/all_queues.yml ee/app/workers/all_queues.yml].freeze
- # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
# of bundler/Rails context, so we cannot use any gem or Rails methods.
def self.worker_queues(rails_path = Rails.root.to_s)
@worker_queues ||= {}
@@ -19,7 +19,7 @@ module Gitlab
end
end
- # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
# of bundler/Rails context, so we cannot use any gem or Rails methods.
def self.expand_queues(queues, all_queues = self.worker_queues)
return [] if queues.empty?
@@ -48,7 +48,9 @@ module Gitlab
end
def self.workers
- @workers ||= find_workers(Rails.root.join('app', 'workers'))
+ @workers ||=
+ find_workers(Rails.root.join('app', 'workers')) +
+ find_workers(Rails.root.join('ee', 'app', 'workers'))
end
def self.find_workers(root)
diff --git a/lib/gitlab/sidekiq_middleware/correlation_injector.rb b/lib/gitlab/sidekiq_middleware/correlation_injector.rb
index b807b3a03ed..1539fd706ab 100644
--- a/lib/gitlab/sidekiq_middleware/correlation_injector.rb
+++ b/lib/gitlab/sidekiq_middleware/correlation_injector.rb
@@ -4,8 +4,8 @@ module Gitlab
module SidekiqMiddleware
class CorrelationInjector
def call(worker_class, job, queue, redis_pool)
- job[Gitlab::CorrelationId::LOG_KEY] ||=
- Gitlab::CorrelationId.current_or_new_id
+ job[Labkit::Correlation::CorrelationId::LOG_KEY] ||=
+ Labkit::Correlation::CorrelationId.current_or_new_id
yield
end
diff --git a/lib/gitlab/sidekiq_middleware/correlation_logger.rb b/lib/gitlab/sidekiq_middleware/correlation_logger.rb
index cb8ff4a6284..cffc4483573 100644
--- a/lib/gitlab/sidekiq_middleware/correlation_logger.rb
+++ b/lib/gitlab/sidekiq_middleware/correlation_logger.rb
@@ -4,9 +4,9 @@ module Gitlab
module SidekiqMiddleware
class CorrelationLogger
def call(worker, job, queue)
- correlation_id = job[Gitlab::CorrelationId::LOG_KEY]
+ correlation_id = job[Labkit::Correlation::CorrelationId::LOG_KEY]
- Gitlab::CorrelationId.use_id(correlation_id) do
+ Labkit::Correlation::CorrelationId.use_id(correlation_id) do
yield
end
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
new file mode 100644
index 00000000000..671d795ec33
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class MemoryKiller
+ # Default the RSS limit to 0, meaning the MemoryKiller is disabled
+ MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
+ # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
+ GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
+ # Wait 30 seconds for running jobs to finish during graceful shutdown
+ SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
+
+ # Create a mutex used to ensure there will be only one thread waiting to
+ # shut Sidekiq down
+ MUTEX = Mutex.new
+
+ def call(worker, job, queue)
+ yield
+
+ current_rss = get_rss
+
+ return unless MAX_RSS > 0 && current_rss > MAX_RSS
+
+ Thread.new do
+ # Return if another thread is already waiting to shut Sidekiq down
+ next unless MUTEX.try_lock
+
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
+ " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}"
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
+
+ # Wait `GRACE_TIME` to give the memory intensive job time to finish.
+ # Then, tell Sidekiq to stop fetching new jobs.
+ wait_and_signal(GRACE_TIME, 'SIGTSTP', 'stop fetching new jobs')
+
+ # Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
+ # Then, tell Sidekiq to gracefully shut down by giving jobs a few more
+ # moments to finish, killing and requeuing them if they didn't, and
+ # then terminating itself. Sidekiq will replicate the TERM to all its
+ # children if it can.
+ wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
+
+ # Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
+ # Kill the whole pgroup, so we can be sure no children are left behind
+ wait_and_signal_pgroup(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
+ end
+ end
+
+ private
+
+ def get_rss
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
+ return 0 unless status.zero?
+
+ output.to_i
+ end
+
+ # If this sidekiq process is pgroup leader, signal to the whole pgroup
+ def wait_and_signal_pgroup(time, signal, explanation)
+ return wait_and_signal(time, signal, explanation) unless Process.getpgrp == pid
+
+ Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ sleep(time)
+
+ Sidekiq.logger.warn "sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ Process.kill(signal, 0)
+ end
+
+ def wait_and_signal(time, signal, explanation)
+ Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ sleep(time)
+
+ Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ Process.kill(signal, pid)
+ end
+
+ def pid
+ Process.pid
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/shutdown.rb b/lib/gitlab/sidekiq_middleware/shutdown.rb
deleted file mode 100644
index 19f3be83bce..00000000000
--- a/lib/gitlab/sidekiq_middleware/shutdown.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-
-require 'mutex_m'
-
-module Gitlab
- module SidekiqMiddleware
- class Shutdown
- extend Mutex_m
-
- # Default the RSS limit to 0, meaning the MemoryKiller is disabled
- MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
- # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
- GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
- # Wait 30 seconds for running jobs to finish during graceful shutdown
- SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
-
- # This exception can be used to request that the middleware start shutting down Sidekiq
- WantShutdown = Class.new(StandardError)
-
- ShutdownWithoutRaise = Class.new(WantShutdown)
- private_constant :ShutdownWithoutRaise
-
- # For testing only, to avoid race conditions (?) in Rspec mocks.
- attr_reader :trace
-
- # We store the shutdown thread in a class variable to ensure that there
- # can be only one shutdown thread in the process.
- def self.create_shutdown_thread
- mu_synchronize do
- break unless @shutdown_thread.nil?
-
- @shutdown_thread = Thread.new { yield }
- end
- end
-
- # For testing only: so we can wait for the shutdown thread to finish.
- def self.shutdown_thread
- mu_synchronize { @shutdown_thread }
- end
-
- # For testing only: so that we can reset the global state before each test.
- def self.clear_shutdown_thread
- mu_synchronize { @shutdown_thread = nil }
- end
-
- def initialize
- @trace = Queue.new if Rails.env.test?
- end
-
- def call(worker, job, queue)
- shutdown_exception = nil
-
- begin
- yield
- check_rss!
- rescue WantShutdown => ex
- shutdown_exception = ex
- end
-
- return unless shutdown_exception
-
- self.class.create_shutdown_thread do
- do_shutdown(worker, job, shutdown_exception)
- end
-
- raise shutdown_exception unless shutdown_exception.is_a?(ShutdownWithoutRaise)
- end
-
- private
-
- def do_shutdown(worker, job, shutdown_exception)
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} shutting down because of #{shutdown_exception} after job "\
- "#{worker.class} JID-#{job['jid']}"
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
-
- # Wait `GRACE_TIME` to give the memory intensive job time to finish.
- # Then, tell Sidekiq to stop fetching new jobs.
- wait_and_signal(GRACE_TIME, 'SIGTSTP', 'stop fetching new jobs')
-
- # Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
- # Then, tell Sidekiq to gracefully shut down by giving jobs a few more
- # moments to finish, killing and requeuing them if they didn't, and
- # then terminating itself.
- wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
-
- # Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
- wait_and_signal(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
- end
-
- def check_rss!
- return unless MAX_RSS > 0
-
- current_rss = get_rss
- return unless current_rss > MAX_RSS
-
- raise ShutdownWithoutRaise.new("current RSS #{current_rss} exceeds maximum RSS #{MAX_RSS}")
- end
-
- def get_rss
- output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
- return 0 unless status.zero?
-
- output.to_i
- end
-
- def wait_and_signal(time, signal, explanation)
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
- sleep(time)
-
- Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
- kill(signal, pid)
- end
-
- def pid
- Process.pid
- end
-
- def sleep(time)
- if Rails.env.test?
- @trace << [:sleep, time]
- else
- Kernel.sleep(time)
- end
- end
-
- def kill(signal, pid)
- if Rails.env.test?
- @trace << [:kill, signal, pid]
- else
- Process.kill(signal, pid)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/sidekiq_signals.rb b/lib/gitlab/sidekiq_signals.rb
new file mode 100644
index 00000000000..82462544d07
--- /dev/null
+++ b/lib/gitlab/sidekiq_signals.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Gitlab
+ # As a process group leader, we can ensure that children of sidekiq are killed
+ # at the same time as sidekiq itself, to stop long-lived children from being
+ # reparented to init and "escaping". To do this, we override the default
+ # handlers used by sidekiq for INT and TERM signals
+ module SidekiqSignals
+ REPLACE_SIGNALS = %w[INT TERM].freeze
+
+ SIDEKIQ_CHANGED_MESSAGE =
+ "Intercepting signal handlers: #{REPLACE_SIGNALS.join(", ")} failed. " \
+ "Sidekiq should have registered them, but appears not to have done so."
+
+ def self.install!(sidekiq_handlers)
+ # This only works if we're process group leader
+ return unless Process.getpgrp == Process.pid
+
+ raise SIDEKIQ_CHANGED_MESSAGE unless
+ REPLACE_SIGNALS == sidekiq_handlers.keys & REPLACE_SIGNALS
+
+ REPLACE_SIGNALS.each do |signal|
+ old_handler = sidekiq_handlers[signal]
+ sidekiq_handlers[signal] = ->(cli) do
+ blindly_signal_pgroup!(signal)
+ old_handler.call(cli)
+ end
+ end
+ end
+
+ # The process group leader can forward INT and TERM signals to the whole
+ # group. However, the forwarded signal is *also* received by the leader,
+ # which could lead to an infinite loop. We can avoid this by temporarily
+ # ignoring the forwarded signal. This may cause us to miss some repeated
+ # signals from outside the process group, but that isn't fatal.
+ def self.blindly_signal_pgroup!(signal)
+ old_trap = trap(signal, 'IGNORE')
+ begin
+ Process.kill(signal, 0)
+ ensure
+ trap(signal, old_trap)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/result.rb b/lib/gitlab/slash_commands/result.rb
index 607c9c8dec1..a66a2e0726b 100644
--- a/lib/gitlab/slash_commands/result.rb
+++ b/lib/gitlab/slash_commands/result.rb
@@ -1,4 +1,3 @@
-# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index 92388262035..fd108b4c124 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -6,7 +6,7 @@ module Gitlab
extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3
- REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/
+ REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/.freeze
class_methods do
def fuzzy_search(query, columns)
@@ -23,8 +23,12 @@ module Gitlab
end
end
+ def min_chars_for_partial_matching
+ MIN_CHARS_FOR_PARTIAL_MATCHING
+ end
+
def partial_matching?(query)
- query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
+ query.length >= min_chars_for_partial_matching
end
# column - The column name to search in.
@@ -33,7 +37,7 @@ module Gitlab
# `LOWER(column) = query` instead of using `ILIKE`.
def fuzzy_arel_match(column, query, lower_exact_match: false)
query = query.squish
- return nil unless query.present?
+ return unless query.present?
words = select_fuzzy_words(query)
diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb
index 0b4cc571dc0..14c9a3e0389 100644
--- a/lib/gitlab/template/base_template.rb
+++ b/lib/gitlab/template/base_template.rb
@@ -16,6 +16,12 @@ module Gitlab
end
alias_method :key, :name
+ def full_name
+ Pathname.new(@path)
+ .relative_path_from(self.class.base_dir)
+ .to_s
+ end
+
def content
@finder.read(@path)
end
diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb
index fbefb5f7f0e..3e2bb11c35f 100644
--- a/lib/gitlab/template/gitlab_ci_yml_template.rb
+++ b/lib/gitlab/template/gitlab_ci_yml_template.rb
@@ -28,11 +28,6 @@ module Gitlab
def finder(project = nil)
Gitlab::Template::Finders::GlobalTemplateFinder.new(self.base_dir, self.extension, self.categories)
end
-
- def dropdown_names(context)
- categories = context == 'autodeploy' ? ['Auto deploy'] : %w(General Pages)
- super().slice(*categories)
- end
end
end
end
diff --git a/lib/gitlab/tracing/common.rb b/lib/gitlab/tracing/common.rb
deleted file mode 100644
index 3a08ede8138..00000000000
--- a/lib/gitlab/tracing/common.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-require 'opentracing'
-
-module Gitlab
- module Tracing
- module Common
- def tracer
- OpenTracing.global_tracer
- end
-
- # Convience method for running a block with a span
- def in_tracing_span(operation_name:, tags:, child_of: nil)
- scope = tracer.start_active_span(
- operation_name,
- child_of: child_of,
- tags: tags
- )
- span = scope.span
-
- # Add correlation details to the span if we have them
- correlation_id = Gitlab::CorrelationId.current_id
- if correlation_id
- span.set_tag('correlation_id', correlation_id)
- end
-
- begin
- yield span
- rescue => e
- log_exception_on_span(span, e)
- raise e
- ensure
- scope.close
- end
- end
-
- def postnotify_span(operation_name, start_time, end_time, tags: nil, child_of: nil, exception: nil)
- span = OpenTracing.start_span(operation_name, start_time: start_time, tags: tags, child_of: child_of)
-
- log_exception_on_span(span, exception) if exception
-
- span.finish(end_time: end_time)
- end
-
- def log_exception_on_span(span, exception)
- span.set_tag('error', true)
- span.log_kv(kv_tags_for_exception(exception))
- end
-
- def kv_tags_for_exception(exception)
- case exception
- when Exception
- {
- 'event': 'error',
- 'error.kind': exception.class.to_s,
- 'message': Gitlab::UrlSanitizer.sanitize(exception.message),
- 'stack': exception.backtrace&.join("\n")
- }
- else
- {
- 'event': 'error',
- 'error.kind': exception.class.to_s,
- 'error.object': Gitlab::UrlSanitizer.sanitize(exception.to_s)
- }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/factory.rb b/lib/gitlab/tracing/factory.rb
deleted file mode 100644
index fc714164353..00000000000
--- a/lib/gitlab/tracing/factory.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-require "cgi"
-
-module Gitlab
- module Tracing
- class Factory
- OPENTRACING_SCHEME = "opentracing"
-
- def self.create_tracer(service_name, connection_string)
- return unless connection_string.present?
-
- begin
- opentracing_details = parse_connection_string(connection_string)
- driver_name = opentracing_details[:driver_name]
-
- case driver_name
- when "jaeger"
- JaegerFactory.create_tracer(service_name, opentracing_details[:options])
- else
- raise "Unknown driver: #{driver_name}"
- end
- rescue => e
- # Can't create the tracer? Warn and continue sans tracer
- warn "Unable to instantiate tracer: #{e}"
- nil
- end
- end
-
- def self.parse_connection_string(connection_string)
- parsed = URI.parse(connection_string)
-
- unless valid_uri?(parsed)
- raise "Invalid tracing connection string"
- end
-
- {
- driver_name: parsed.host,
- options: parse_query(parsed.query)
- }
- end
- private_class_method :parse_connection_string
-
- def self.parse_query(query)
- return {} unless query
-
- CGI.parse(query).symbolize_keys.transform_values(&:first)
- end
- private_class_method :parse_query
-
- def self.valid_uri?(uri)
- return false unless uri
-
- uri.scheme == OPENTRACING_SCHEME &&
- uri.host.to_s =~ /^[a-z0-9_]+$/ &&
- uri.path.empty?
- end
- private_class_method :valid_uri?
- end
- end
-end
diff --git a/lib/gitlab/tracing/grpc_interceptor.rb b/lib/gitlab/tracing/grpc_interceptor.rb
deleted file mode 100644
index 6c2aab73125..00000000000
--- a/lib/gitlab/tracing/grpc_interceptor.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'opentracing'
-require 'grpc'
-
-module Gitlab
- module Tracing
- class GRPCInterceptor < GRPC::ClientInterceptor
- include Common
- include Singleton
-
- def request_response(request:, call:, method:, metadata:)
- wrap_with_tracing(method, 'unary', metadata) do
- yield
- end
- end
-
- def client_streamer(requests:, call:, method:, metadata:)
- wrap_with_tracing(method, 'client_stream', metadata) do
- yield
- end
- end
-
- def server_streamer(request:, call:, method:, metadata:)
- wrap_with_tracing(method, 'server_stream', metadata) do
- yield
- end
- end
-
- def bidi_streamer(requests:, call:, method:, metadata:)
- wrap_with_tracing(method, 'bidi_stream', metadata) do
- yield
- end
- end
-
- private
-
- def wrap_with_tracing(method, grpc_type, metadata)
- tags = {
- 'component' => 'grpc',
- 'span.kind' => 'client',
- 'grpc.method' => method,
- 'grpc.type' => grpc_type
- }
-
- in_tracing_span(operation_name: "grpc:#{method}", tags: tags) do |span|
- OpenTracing.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, metadata)
-
- yield
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
deleted file mode 100644
index 2682007302a..00000000000
--- a/lib/gitlab/tracing/jaeger_factory.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-require 'jaeger/client'
-
-module Gitlab
- module Tracing
- class JaegerFactory
- # When the probabilistic sampler is used, by default 0.1% of requests will be traced
- DEFAULT_PROBABILISTIC_RATE = 0.001
-
- # The default port for the Jaeger agent UDP listener
- DEFAULT_UDP_PORT = 6831
-
- # Reduce this from default of 10 seconds as the Ruby jaeger
- # client doesn't have overflow control, leading to very large
- # messages which fail to send over UDP (max packet = 64k)
- # Flush more often, with smaller packets
- FLUSH_INTERVAL = 5
-
- def self.create_tracer(service_name, options)
- kwargs = {
- service_name: service_name,
- sampler: get_sampler(options[:sampler], options[:sampler_param]),
- reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
- }.compact
-
- extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
- if extra_params.present?
- message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
-
- if options[:strict_parsing]
- raise message
- else
- warn message
- end
- end
-
- Jaeger::Client.build(kwargs)
- end
-
- def self.get_sampler(sampler_type, sampler_param)
- case sampler_type
- when "probabilistic"
- sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
- Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
- when "const"
- const_value = sampler_param == "1"
- Jaeger::Samplers::Const.new(const_value)
- else
- nil
- end
- end
- private_class_method :get_sampler
-
- def self.get_reporter(service_name, http_endpoint, udp_endpoint)
- encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
-
- if http_endpoint.present?
- sender = get_http_sender(encoder, http_endpoint)
- elsif udp_endpoint.present?
- sender = get_udp_sender(encoder, udp_endpoint)
- else
- return nil
- end
-
- Jaeger::Reporters::RemoteReporter.new(
- sender: sender,
- flush_interval: FLUSH_INTERVAL
- )
- end
- private_class_method :get_reporter
-
- def self.get_http_sender(encoder, address)
- Jaeger::HttpSender.new(
- url: address,
- encoder: encoder,
- logger: Logger.new(STDOUT)
- )
- end
- private_class_method :get_http_sender
-
- def self.get_udp_sender(encoder, address)
- pair = address.split(":", 2)
- host = pair[0]
- port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
-
- Jaeger::UdpSender.new(
- host: host,
- port: port,
- encoder: encoder,
- logger: Logger.new(STDOUT)
- )
- end
- private_class_method :get_udp_sender
- end
- end
-end
diff --git a/lib/gitlab/tracing/rack_middleware.rb b/lib/gitlab/tracing/rack_middleware.rb
deleted file mode 100644
index e6a31293f7b..00000000000
--- a/lib/gitlab/tracing/rack_middleware.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'opentracing'
-
-module Gitlab
- module Tracing
- class RackMiddleware
- include Common
-
- REQUEST_METHOD = 'REQUEST_METHOD'
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- method = env[REQUEST_METHOD]
-
- context = tracer.extract(OpenTracing::FORMAT_RACK, env)
- tags = {
- 'component' => 'rack',
- 'span.kind' => 'server',
- 'http.method' => method,
- 'http.url' => self.class.build_sanitized_url_from_env(env)
- }
-
- in_tracing_span(operation_name: "http:#{method}", child_of: context, tags: tags) do |span|
- @app.call(env).tap do |status_code, _headers, _body|
- span.set_tag('http.status_code', status_code)
- end
- end
- end
-
- # Generate a sanitized (safe) request URL from the rack environment
- def self.build_sanitized_url_from_env(env)
- request = ActionDispatch::Request.new(env)
-
- original_url = request.original_url
- uri = URI.parse(original_url)
- uri.query = request.filtered_parameters.to_query if uri.query.present?
-
- uri.to_s
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/rails/action_view_subscriber.rb b/lib/gitlab/tracing/rails/action_view_subscriber.rb
deleted file mode 100644
index 88816e1fb32..00000000000
--- a/lib/gitlab/tracing/rails/action_view_subscriber.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Tracing
- module Rails
- class ActionViewSubscriber
- include RailsCommon
-
- COMPONENT_TAG = 'ActionView'
- RENDER_TEMPLATE_NOTIFICATION_TOPIC = 'render_template.action_view'
- RENDER_COLLECTION_NOTIFICATION_TOPIC = 'render_collection.action_view'
- RENDER_PARTIAL_NOTIFICATION_TOPIC = 'render_partial.action_view'
-
- # Instruments Rails ActionView events for opentracing.
- # Returns a lambda, which, when called will unsubscribe from the notifications
- def self.instrument
- subscriber = new
-
- subscriptions = [
- ActiveSupport::Notifications.subscribe(RENDER_TEMPLATE_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
- subscriber.notify_render_template(start, finish, payload)
- end,
- ActiveSupport::Notifications.subscribe(RENDER_COLLECTION_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
- subscriber.notify_render_collection(start, finish, payload)
- end,
- ActiveSupport::Notifications.subscribe(RENDER_PARTIAL_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
- subscriber.notify_render_partial(start, finish, payload)
- end
- ]
-
- create_unsubscriber subscriptions
- end
-
- # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
- def notify_render_template(start, finish, payload)
- generate_span_for_notification("render_template", start, finish, payload, tags_for_render_template(payload))
- end
-
- def notify_render_collection(start, finish, payload)
- generate_span_for_notification("render_collection", start, finish, payload, tags_for_render_collection(payload))
- end
-
- def notify_render_partial(start, finish, payload)
- generate_span_for_notification("render_partial", start, finish, payload, tags_for_render_partial(payload))
- end
-
- private
-
- def tags_for_render_template(payload)
- {
- 'component' => COMPONENT_TAG,
- 'template.id' => payload[:identifier],
- 'template.layout' => payload[:layout]
- }
- end
-
- def tags_for_render_collection(payload)
- {
- 'component' => COMPONENT_TAG,
- 'template.id' => payload[:identifier],
- 'template.count' => payload[:count] || 0,
- 'template.cache.hits' => payload[:cache_hits] || 0
- }
- end
-
- def tags_for_render_partial(payload)
- {
- 'component' => COMPONENT_TAG,
- 'template.id' => payload[:identifier]
- }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/rails/active_record_subscriber.rb b/lib/gitlab/tracing/rails/active_record_subscriber.rb
deleted file mode 100644
index 32f5658e57e..00000000000
--- a/lib/gitlab/tracing/rails/active_record_subscriber.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Tracing
- module Rails
- class ActiveRecordSubscriber
- include RailsCommon
-
- ACTIVE_RECORD_NOTIFICATION_TOPIC = 'sql.active_record'
- OPERATION_NAME_PREFIX = 'active_record:'
- DEFAULT_OPERATION_NAME = 'sqlquery'
-
- # Instruments Rails ActiveRecord events for opentracing.
- # Returns a lambda, which, when called will unsubscribe from the notifications
- def self.instrument
- subscriber = new
-
- subscription = ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
- subscriber.notify(start, finish, payload)
- end
-
- create_unsubscriber [subscription]
- end
-
- # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
- def notify(start, finish, payload)
- generate_span_for_notification(notification_name(payload), start, finish, payload, tags_for_notification(payload))
- end
-
- private
-
- def notification_name(payload)
- OPERATION_NAME_PREFIX + (payload[:name].presence || DEFAULT_OPERATION_NAME)
- end
-
- def tags_for_notification(payload)
- {
- 'component' => 'ActiveRecord',
- 'span.kind' => 'client',
- 'db.type' => 'sql',
- 'db.connection_id' => payload[:connection_id],
- 'db.cached' => payload[:cached] || false,
- 'db.statement' => payload[:sql]
- }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/rails/rails_common.rb b/lib/gitlab/tracing/rails/rails_common.rb
deleted file mode 100644
index 88e914f62f8..00000000000
--- a/lib/gitlab/tracing/rails/rails_common.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Tracing
- module Rails
- module RailsCommon
- extend ActiveSupport::Concern
- include Gitlab::Tracing::Common
-
- class_methods do
- def create_unsubscriber(subscriptions)
- -> { subscriptions.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } }
- end
- end
-
- def generate_span_for_notification(operation_name, start, finish, payload, tags)
- exception = payload[:exception]
-
- postnotify_span(operation_name, start, finish, tags: tags, exception: exception)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/sidekiq/client_middleware.rb b/lib/gitlab/tracing/sidekiq/client_middleware.rb
deleted file mode 100644
index 2b71c1ea21e..00000000000
--- a/lib/gitlab/tracing/sidekiq/client_middleware.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'opentracing'
-
-module Gitlab
- module Tracing
- module Sidekiq
- class ClientMiddleware
- include SidekiqCommon
-
- SPAN_KIND = 'client'
-
- def call(worker_class, job, queue, redis_pool)
- in_tracing_span(
- operation_name: "sidekiq:#{job['class']}",
- tags: tags_from_job(job, SPAN_KIND)) do |span|
- # Inject the details directly into the job
- tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
-
- yield
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/sidekiq/server_middleware.rb b/lib/gitlab/tracing/sidekiq/server_middleware.rb
deleted file mode 100644
index 5b43c4310e6..00000000000
--- a/lib/gitlab/tracing/sidekiq/server_middleware.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'opentracing'
-
-module Gitlab
- module Tracing
- module Sidekiq
- class ServerMiddleware
- include SidekiqCommon
-
- SPAN_KIND = 'server'
-
- def call(worker, job, queue)
- context = tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
-
- in_tracing_span(
- operation_name: "sidekiq:#{job['class']}",
- child_of: context,
- tags: tags_from_job(job, SPAN_KIND)) do |span|
- yield
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tracing/sidekiq/sidekiq_common.rb b/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
deleted file mode 100644
index a911a29d773..00000000000
--- a/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Tracing
- module Sidekiq
- module SidekiqCommon
- include Gitlab::Tracing::Common
-
- def tags_from_job(job, kind)
- {
- 'component' => 'sidekiq',
- 'span.kind' => kind,
- 'sidekiq.queue' => job['queue'],
- 'sidekiq.jid' => job['jid'],
- 'sidekiq.retry' => job['retry'].to_s,
- 'sidekiq.args' => job['args']&.join(", ")
- }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index 453d78e2f7b..8518a13cd1c 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -95,7 +95,7 @@ module Gitlab
end
def cache_commit(commit)
- return nil unless commit.present?
+ return unless commit.present?
resolved_commits[commit.id] ||= commit
end
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
index ba1137313d8..c237f4a7404 100644
--- a/lib/gitlab/untrusted_regexp.rb
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -35,6 +35,10 @@ module Gitlab
matches
end
+ def match?(text)
+ text.present? && scan(text).present?
+ end
+
def replace(text, rewrite)
RE2.Replace(text, regexp, rewrite)
end
@@ -56,24 +60,6 @@ module Gitlab
Regexp.new(pattern)
end
- def self.valid?(pattern)
- !!self.fabricate(pattern)
- rescue RegexpError
- false
- end
-
- def self.fabricate(pattern)
- matches = pattern.match(%r{^/(?<regexp>.+)/(?<flags>[ismU]*)$})
-
- raise RegexpError, 'Invalid regular expression!' if matches.nil?
-
- expression = matches[:regexp]
- flags = matches[:flags]
- expression.prepend("(?#{flags})") if flags.present?
-
- self.new(expression, multiline: false)
- end
-
private
attr_reader :regexp
diff --git a/lib/gitlab/untrusted_regexp/ruby_syntax.rb b/lib/gitlab/untrusted_regexp/ruby_syntax.rb
new file mode 100644
index 00000000000..6adf119aa75
--- /dev/null
+++ b/lib/gitlab/untrusted_regexp/ruby_syntax.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class UntrustedRegexp
+ # This class implements support for Ruby syntax of regexps
+ # and converts that to RE2 representation:
+ # /<regexp>/<flags>
+ class RubySyntax
+ PATTERN = %r{^/(?<regexp>.*)/(?<flags>[ismU]*)$}.freeze
+
+ # Checks if pattern matches a regexp pattern
+ # but does not enforce it's validity
+ def self.matches_syntax?(pattern)
+ pattern.is_a?(String) && pattern.match(PATTERN).present?
+ end
+
+ # The regexp can match the pattern `/.../`, but may not be fabricatable:
+ # it can be invalid or incomplete: `/match ( string/`
+ def self.valid?(pattern, fallback: false)
+ !!self.fabricate(pattern, fallback: fallback)
+ end
+
+ def self.fabricate(pattern, fallback: false)
+ self.fabricate!(pattern, fallback: fallback)
+ rescue RegexpError
+ nil
+ end
+
+ def self.fabricate!(pattern, fallback: false)
+ raise RegexpError, 'Pattern is not string!' unless pattern.is_a?(String)
+
+ matches = pattern.match(PATTERN)
+ raise RegexpError, 'Invalid regular expression!' if matches.nil?
+
+ begin
+ create_untrusted_regexp(matches[:regexp], matches[:flags])
+ rescue RegexpError
+ raise unless fallback &&
+ Feature.enabled?(:allow_unsafe_ruby_regexp, default_enabled: false)
+
+ create_ruby_regexp(matches[:regexp], matches[:flags])
+ end
+ end
+
+ def self.create_untrusted_regexp(pattern, flags)
+ pattern.prepend("(?#{flags})") if flags.present?
+
+ UntrustedRegexp.new(pattern, multiline: false)
+ end
+ private_class_method :create_untrusted_regexp
+
+ def self.create_ruby_regexp(pattern, flags)
+ options = 0
+ options += Regexp::IGNORECASE if flags&.include?('i')
+ options += Regexp::MULTILINE if flags&.include?('m')
+
+ Regexp.new(pattern, options)
+ end
+ private_class_method :create_ruby_regexp
+ end
+ end
+end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 9b7b0db9525..9a8df719827 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,38 +8,68 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
- def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
- return true if url.nil?
+ # Validates the given url according to the constraints specified by arguments.
+ #
+ # ports - Raises error if the given URL port does is not between given ports.
+ # allow_localhost - Raises error if URL resolves to a localhost IP address and argument is true.
+ # allow_local_network - Raises error if URL resolves to a link-local address and argument is true.
+ # ascii_only - Raises error if URL has unicode characters and argument is true.
+ # enforce_user - Raises error if URL user doesn't start with alphanumeric characters and argument is true.
+ # enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true.
+ #
+ # Returns an array with [<uri>, <original-hostname>].
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/ParameterLists
+ def validate!(
+ url,
+ ports: [],
+ schemes: [],
+ allow_localhost: false,
+ allow_local_network: true,
+ ascii_only: false,
+ enforce_user: false,
+ enforce_sanitization: false,
+ dns_rebind_protection: true)
+ # rubocop:enable Metrics/CyclomaticComplexity
+ # rubocop:enable Metrics/ParameterLists
+
+ return [nil, nil] if url.nil?
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
validate_html_tags!(uri) if enforce_sanitization
- # Allow imports from the GitLab instance itself but only from the configured ports
- return true if internal?(uri)
-
+ hostname = uri.hostname
port = get_port(uri)
- validate_protocol!(uri.scheme, protocols)
- validate_port!(port, ports) if ports.any?
- validate_user!(uri.user) if enforce_user
- validate_hostname!(uri.hostname)
- validate_unicode_restriction!(uri) if ascii_only
+
+ unless internal?(uri)
+ validate_scheme!(uri.scheme, schemes)
+ validate_port!(port, ports) if ports.any?
+ validate_user!(uri.user) if enforce_user
+ validate_hostname!(hostname)
+ validate_unicode_restriction!(uri) if ascii_only
+ end
begin
- addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
+ addrs_info = Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
- return true
+ return [uri, nil]
end
+ protected_uri_with_hostname = enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
+
+ # Allow url from the GitLab instance itself but only for the configured hostname and ports
+ return protected_uri_with_hostname if internal?(uri)
+
validate_localhost!(addrs_info) unless allow_localhost
validate_loopback!(addrs_info) unless allow_localhost
validate_local_network!(addrs_info) unless allow_local_network
validate_link_local!(addrs_info) unless allow_local_network
- true
+ protected_uri_with_hostname
end
def blocked_url?(*args)
@@ -52,6 +82,25 @@ module Gitlab
private
+ # Returns the given URI with IP address as hostname and the original hostname respectively
+ # in an Array.
+ #
+ # It checks whether the resolved IP address matches with the hostname. If not, it changes
+ # the hostname to the resolved IP address.
+ #
+ # The original hostname is used to validate the SSL, given in that scenario
+ # we'll be making the request to the IP address, instead of using the hostname.
+ def enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
+ address = addrs_info.first
+ ip_address = address&.ip_address
+
+ return [uri, nil] unless dns_rebind_protection && ip_address && ip_address != hostname
+
+ uri = uri.dup
+ uri.hostname = ip_address
+ [uri, hostname]
+ end
+
def get_port(uri)
uri.port || uri.default_port
end
@@ -85,9 +134,9 @@ module Gitlab
raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024"
end
- def validate_protocol!(protocol, protocols)
- if protocol.blank? || (protocols.any? && !protocols.include?(protocol))
- raise BlockedUrlError, "Only allowed protocols are #{protocols.join(', ')}"
+ def validate_scheme!(scheme, schemes)
+ if scheme.blank? || (schemes.any? && !schemes.include?(scheme))
+ raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}"
end
end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index f86d599e4cb..42cf1ec1f0e 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -30,6 +30,10 @@ module Gitlab
snippet_url(object)
when Milestone
milestone_url(object)
+ when ::Ci::Build
+ project_job_url(object.project, object)
+ when User
+ user_url(object)
else
raise NotImplementedError.new("No URL builder defined for #{object.class}")
end
diff --git a/lib/gitlab/url_helpers.rb b/lib/gitlab/url_helpers.rb
new file mode 100644
index 00000000000..883585c196f
--- /dev/null
+++ b/lib/gitlab/url_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class UrlHelpers
+ WSS_PROTOCOL = "wss".freeze
+ def self.as_wss(url)
+ return unless url.present?
+
+ URI.parse(url).tap do |uri|
+ uri.scheme = WSS_PROTOCOL
+ end.to_s
+ rescue URI::InvalidURIError
+ nil
+ end
+ end
+end
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 880712de5fe..215454fe63c 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -47,6 +47,10 @@ module Gitlab
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
end
+ def user
+ credentials[:user]
+ end
+
def full_url
@full_url ||= generate_full_url.to_s
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 0101ccc046a..9aa2e972adf 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -26,7 +26,7 @@ module Gitlab
uuid: Gitlab::CurrentSettings.uuid,
hostname: Gitlab.config.gitlab.host,
version: Gitlab::VERSION,
- installation_type: Gitlab::INSTALLATION_TYPE,
+ installation_type: installation_type,
active_user_count: count(User.active),
recorded_at: Time.now,
edition: 'CE'
@@ -54,6 +54,8 @@ module Gitlab
auto_devops_disabled: count(::ProjectAutoDevops.disabled),
deploy_keys: count(DeployKey),
deployments: count(Deployment),
+ successful_deployments: count(Deployment.success),
+ failed_deployments: count(Deployment.failed),
environments: count(::Environment),
clusters: count(::Clusters::Cluster),
clusters_enabled: count(::Clusters::Cluster.enabled),
@@ -79,9 +81,11 @@ module Gitlab
milestone_lists: count(List.milestone),
milestones: count(Milestone),
pages_domains: count(PagesDomain),
+ pool_repositories: count(PoolRepository),
projects: count(Project),
projects_imported_from_github: count(Project.where(import_type: 'github')),
projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
+ projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
protected_branches: count(ProtectedBranch),
releases: count(Release),
remote_mirrors: count(RemoteMirror),
@@ -114,9 +118,11 @@ module Gitlab
container_registry_enabled: Gitlab.config.registry.enabled,
gitlab_shared_runners_enabled: Gitlab.config.gitlab_ci.shared_runners_enabled,
gravatar_enabled: Gitlab::CurrentSettings.gravatar_enabled?,
+ influxdb_metrics_enabled: Gitlab::Metrics.influx_metrics_enabled?,
ldap_enabled: Gitlab.config.ldap.enabled,
mattermost_enabled: Gitlab.config.mattermost.enabled,
omniauth_enabled: Gitlab::Auth.omniauth_enabled?,
+ prometheus_metrics_enabled: Gitlab::Metrics.prometheus_metrics_enabled?,
reply_by_email_enabled: Gitlab::IncomingEmail.enabled?,
signup_enabled: Gitlab::CurrentSettings.allow_signup?
}
@@ -185,6 +191,14 @@ module Gitlab
result[key] = approx_counts[model] || -1
end
end
+
+ def installation_type
+ if Rails.env.production?
+ Gitlab::INSTALLATION_TYPE
+ else
+ "gitlab-development-kit"
+ end
+ end
end
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 980a8014409..9ef23cf849f 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -118,8 +118,8 @@ module Gitlab
protected_refs: project.protected_tags)
end
- request_cache def protected?(kind, project, ref)
- kind.protected?(project, ref)
+ request_cache def protected?(kind, project, refs)
+ kind.protected?(project, refs)
end
end
end
diff --git a/lib/gitlab/user_extractor.rb b/lib/gitlab/user_extractor.rb
index 874599688bb..ede60c9ab1d 100644
--- a/lib/gitlab/user_extractor.rb
+++ b/lib/gitlab/user_extractor.rb
@@ -7,15 +7,18 @@ module Gitlab
class UserExtractor
# Not using `Devise.email_regexp` to filter out any chars that an email
# does not end with and not pinning the email to a start of end of a string.
- EMAIL_REGEXP = /(?<email>([^@\s]+@[^@\s]+(?<!\W)))/
+ EMAIL_REGEXP = /(?<email>([^@\s]+@[^@\s]+(?<!\W)))/.freeze
USERNAME_REGEXP = User.reference_pattern
def initialize(text)
- @text = text
+ # EE passes an Array to `text` in a few places, so we want to support both
+ # here.
+ @text = Array(text).join(' ')
end
def users
return User.none unless @text.present?
+ return User.none if references.empty?
@users ||= User.from_union(union_relations)
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 99fa65e0e90..16ec8a8bb28 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -104,6 +104,12 @@ module Gitlab
nil
end
+ def try_megabytes_to_bytes(size)
+ Integer(size).megabytes
+ rescue ArgumentError
+ size
+ end
+
def bytes_to_megabytes(bytes)
bytes.to_f / Numeric::MEGABYTE
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index a3c7de87765..8f9d5cf1e63 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -75,8 +75,8 @@ module Gitlab
user.admin? || allowed_level?(level.to_i)
end
+ # Level should be a numeric value, e.g. `20`
# Return true if the specified level is allowed for the current user.
- # Level should be a numeric value, e.g. `20`.
def allowed_level?(level)
valid_level?(level) && non_restricted_level?(level)
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 265f6213a99..46a7b5b982a 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -20,14 +20,14 @@ module Gitlab
SECRET_LENGTH = 32
class << self
- def git_http_ok(repository, is_wiki, user, action, show_all_refs: false)
+ def git_http_ok(repository, repo_type, user, action, show_all_refs: false)
raise "Unsupported action: #{action}" unless ALLOWED_GIT_HTTP_ACTIONS.include?(action.to_s)
project = repository.project
attrs = {
GL_ID: Gitlab::GlId.gl_id(user),
- GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki),
+ GL_REPOSITORY: repo_type.identifier_for_subject(project),
GL_USERNAME: user&.username,
ShowAllRefs: show_all_refs,
Repository: repository.gitaly_repository.to_h,
@@ -63,16 +63,34 @@ module Gitlab
]
end
- def send_git_archive(repository, ref:, format:, append_sha:)
+ def send_git_archive(repository, ref:, format:, append_sha:, path: nil)
+ path_enabled = Feature.enabled?(:git_archive_path, default_enabled: true)
+ path = nil unless path_enabled
+
format ||= 'tar.gz'
format = format.downcase
- params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
- raise "Repository or ref not found" if params.empty?
- params['GitalyServer'] = gitaly_server_hash(repository)
+ metadata = repository.archive_metadata(
+ ref,
+ Gitlab.config.gitlab.repository_downloads_path,
+ format,
+ append_sha: append_sha,
+ path: path
+ )
- # If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
+ raise "Repository or ref not found" if metadata.empty?
+
+ params =
+ if path_enabled
+ send_git_archive_params(repository, metadata, path, archive_format(format))
+ else
+ metadata
+ end
+
+ # If present, DisableCache must be a Boolean. Otherwise
+ # workhorse ignores it.
params['DisableCache'] = true if git_archive_cache_disabled?
+ params['GitalyServer'] = gitaly_server_hash(repository)
[
SEND_DATA_HEADER,
@@ -149,16 +167,16 @@ module Gitlab
]
end
- def terminal_websocket(terminal)
+ def channel_websocket(channel)
details = {
- 'Terminal' => {
- 'Subprotocols' => terminal[:subprotocols],
- 'Url' => terminal[:url],
- 'Header' => terminal[:headers],
- 'MaxSessionTime' => terminal[:max_session_time]
+ 'Channel' => {
+ 'Subprotocols' => channel[:subprotocols],
+ 'Url' => channel[:url],
+ 'Header' => channel[:headers],
+ 'MaxSessionTime' => channel[:max_session_time]
}
}
- details['Terminal']['CAPem'] = terminal[:ca_pem] if terminal.key?(:ca_pem)
+ details['Channel']['CAPem'] = channel[:ca_pem] if channel.key?(:ca_pem)
details
end
@@ -216,10 +234,19 @@ module Gitlab
protected
+ # This is the outermost encoding of a senddata: header. It is safe for
+ # inclusion in HTTP response headers
def encode(hash)
Base64.urlsafe_encode64(JSON.dump(hash))
end
+ # This is for encoding individual fields inside the senddata JSON that
+ # contain binary data. In workhorse, the corresponding struct field should
+ # be type []byte
+ def encode_binary(binary)
+ Base64.encode64(binary)
+ end
+
def gitaly_server_hash(repository)
{
address: Gitlab::GitalyClient.address(repository.project.repository_storage),
@@ -238,6 +265,34 @@ module Gitlab
def git_archive_cache_disabled?
ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
end
+
+ def archive_format(format)
+ case format
+ when "tar.bz2", "tbz", "tbz2", "tb2", "bz2"
+ Gitaly::GetArchiveRequest::Format::TAR_BZ2
+ when "tar"
+ Gitaly::GetArchiveRequest::Format::TAR
+ when "zip"
+ Gitaly::GetArchiveRequest::Format::ZIP
+ else
+ Gitaly::GetArchiveRequest::Format::TAR_GZ
+ end
+ end
+
+ def send_git_archive_params(repository, metadata, path, format)
+ {
+ 'ArchivePath' => metadata['ArchivePath'],
+ 'GetArchiveRequest' => encode_binary(
+ Gitaly::GetArchiveRequest.new(
+ repository: repository.gitaly_repository,
+ commit_id: metadata['CommitId'],
+ prefix: metadata['ArchivePrefix'],
+ format: format,
+ path: path.presence || ""
+ ).to_proto
+ )
+ }
+ end
end
end
end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index e74ff6a9129..b5f99ea012b 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -10,6 +10,7 @@ module GoogleApi
class Client < GoogleApi::Auth
SCOPE = 'https://www.googleapis.com/auth/cloud-platform'.freeze
LEAST_TOKEN_LIFE_TIME = 10.minutes
+ CLUSTER_MASTER_AUTH_USERNAME = 'admin'.freeze
class << self
def session_key_for_token
@@ -64,6 +65,12 @@ module GoogleApi
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": CLUSTER_MASTER_AUTH_USERNAME,
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": legacy_abac
}
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
deleted file mode 100644
index 2e98227a05e..00000000000
--- a/lib/haml_lint/inline_javascript.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# rubocop:disable Naming/FileName
-# frozen_string_literal: true
-
-unless Rails.env.production?
- require_dependency 'haml_lint/haml_visitor'
- require_dependency 'haml_lint/linter'
- require_dependency '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
-
- def visit_tag(node)
- return unless node.tag_name == 'script'
-
- 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 e2083848a8d..722e3e04d1c 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -122,7 +122,7 @@ module Mattermost
@oauth_uri = nil
- response = get('/oauth/gitlab/login', follow_redirects: false, format: 'text/html')
+ response = get('/oauth/gitlab/login', follow_redirects: false)
return unless (300...400) === response.code
redirect_uri = response.headers['location']
diff --git a/lib/peek/views/tracing.rb b/lib/peek/views/tracing.rb
index 0de32a8fdda..94726a498ea 100644
--- a/lib/peek/views/tracing.rb
+++ b/lib/peek/views/tracing.rb
@@ -4,9 +4,9 @@ module Peek
module Views
class Tracing < View
def results
- {
- tracing_url: Gitlab::Tracing.tracing_url
- }
+ tracing_url = Labkit::Tracing.tracing_url(Gitlab.process_name)
+
+ { tracing_url: tracing_url }
end
end
end
diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb
index 2ff9e811425..190b48ba7cb 100644
--- a/lib/quality/kubernetes_client.rb
+++ b/lib/quality/kubernetes_client.rb
@@ -18,6 +18,8 @@ module Quality
'delete',
'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa',
'--now',
+ '--ignore-not-found',
+ '--include-uninitialized',
%(-l release="#{release_name}")
]
diff --git a/lib/quality/seeders/issues.rb b/lib/quality/seeders/issues.rb
new file mode 100644
index 00000000000..4c8cb6e97cc
--- /dev/null
+++ b/lib/quality/seeders/issues.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+# rubocop:disable CodeReuse/ActiveRecord
+module Quality
+ module Seeders
+ class Issues
+ DEFAULT_BACKFILL_WEEKS = 52
+ DEFAULT_AVERAGE_ISSUES_PER_WEEK = 10
+
+ attr_reader :project, :user
+
+ def initialize(project:)
+ @project = project
+ end
+
+ def seed(backfill_weeks: DEFAULT_BACKFILL_WEEKS, average_issues_per_week: DEFAULT_AVERAGE_ISSUES_PER_WEEK)
+ created_at = backfill_weeks.to_i.weeks.ago
+ team = project.team.users
+ created_issues_count = 0
+
+ loop do
+ rand(average_issues_per_week * 2).times do
+ params = {
+ title: FFaker::Lorem.sentence(6),
+ description: FFaker::Lorem.sentence,
+ created_at: created_at + rand(6).days,
+ state: %w[opened closed].sample,
+ milestone: project.milestones.sample,
+ assignee_ids: Array(team.pluck(:id).sample(3)),
+ labels: labels.join(',')
+ }
+ issue = ::Issues::CreateService.new(project, team.sample, params).execute
+
+ if issue.persisted?
+ created_issues_count += 1
+ print '.' # rubocop:disable Rails/Output
+ end
+ end
+
+ created_at += 1.week
+
+ break if created_at > Time.now
+ end
+
+ created_issues_count
+ end
+
+ private
+
+ def labels
+ @labels_pool ||= project.labels.limit(rand(3)).pluck(:title).tap do |labels_array|
+ labels_array.concat(project.group.labels.limit(rand(3)).pluck(:title)) if project.group
+ end
+ end
+ end
+ end
+end
+# rubocop:enable CodeReuse/ActiveRecord
diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb
new file mode 100644
index 00000000000..24d8eac200c
--- /dev/null
+++ b/lib/quality/test_level.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Quality
+ class TestLevel
+ UnknownTestLevelError = Class.new(StandardError)
+
+ TEST_LEVEL_FOLDERS = {
+ unit: %w[
+ bin
+ config
+ db
+ dependencies
+ factories
+ finders
+ frontend
+ graphql
+ helpers
+ initializers
+ javascripts
+ lib
+ migrations
+ models
+ policies
+ presenters
+ rack_servers
+ routing
+ rubocop
+ serializers
+ services
+ sidekiq
+ tasks
+ uploaders
+ validators
+ views
+ workers
+ elastic_integration
+ ],
+ integration: %w[
+ controllers
+ mailers
+ requests
+ ],
+ system: ['features']
+ }.freeze
+
+ attr_reader :prefix
+
+ def initialize(prefix = nil)
+ @prefix = prefix
+ @patterns = {}
+ @regexps = {}
+ end
+
+ def pattern(level)
+ @patterns[level] ||= "#{prefix}spec/{#{TEST_LEVEL_FOLDERS.fetch(level).join(',')}}{,/**/}*_spec.rb".freeze
+ end
+
+ def regexp(level)
+ @regexps[level] ||= Regexp.new("#{prefix}spec/(#{TEST_LEVEL_FOLDERS.fetch(level).join('|')})").freeze
+ end
+
+ def level_for(file_path)
+ case file_path
+ when regexp(:unit)
+ :unit
+ when regexp(:integration)
+ :integration
+ when regexp(:system)
+ :system
+ else
+ raise UnknownTestLevelError, "Test level for #{file_path} couldn't be set. Please rename the file properly or change the test level detection regexes in #{__FILE__}."
+ end
+ end
+ end
+end
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 49ec196b103..4022e8ff946 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -3,7 +3,7 @@
module Sentry
class Client
Error = Class.new(StandardError)
- SentryError = Class.new(StandardError)
+ MissingKeysError = Class.new(StandardError)
attr_accessor :url, :token
@@ -14,18 +14,29 @@ module Sentry
def list_issues(issue_status:, limit:)
issues = get_issues(issue_status: issue_status, limit: limit)
- map_to_errors(issues)
+
+ handle_mapping_exceptions do
+ map_to_errors(issues)
+ end
end
def list_projects
projects = get_projects
- map_to_projects(projects)
- rescue KeyError => e
- raise Client::SentryError, "Sentry API response is missing keys. #{e.message}"
+
+ handle_mapping_exceptions do
+ map_to_projects(projects)
+ end
end
private
+ def handle_mapping_exceptions(&block)
+ yield
+ rescue KeyError => e
+ Gitlab::Sentry.track_acceptable_exception(e)
+ raise Client::MissingKeysError, "Sentry API response is missing keys. #{e.message}"
+ end
+
def request_params
{
headers: {
@@ -36,9 +47,11 @@ module Sentry
end
def http_get(url, params = {})
- resp = Gitlab::HTTP.get(url, **request_params.merge(params))
+ response = handle_request_exceptions do
+ Gitlab::HTTP.get(url, **request_params.merge(params))
+ end
- handle_response(resp)
+ handle_response(response)
end
def get_issues(issue_status:, limit:)
@@ -52,12 +65,34 @@ module Sentry
http_get(projects_api_url)
end
+ def handle_request_exceptions
+ yield
+ rescue HTTParty::Error => e
+ Gitlab::Sentry.track_acceptable_exception(e)
+ raise_error 'Error when connecting to Sentry'
+ rescue Net::OpenTimeout
+ raise_error 'Connection to Sentry timed out'
+ rescue SocketError
+ raise_error 'Received SocketError when trying to connect to Sentry'
+ rescue OpenSSL::SSL::SSLError
+ raise_error 'Sentry returned invalid SSL data'
+ rescue Errno::ECONNREFUSED
+ raise_error 'Connection refused'
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e)
+ raise_error "Sentry request failed due to #{e.class}"
+ end
+
def handle_response(response)
unless response.code == 200
- raise Client::Error, "Sentry response status code: #{response.code}"
+ raise_error "Sentry response status code: #{response.code}"
end
- response.as_json
+ response
+ end
+
+ def raise_error(message)
+ raise Client::Error, message
end
def projects_api_url
@@ -94,7 +129,6 @@ module Sentry
def map_to_error(issue)
id = issue.fetch('id')
- project = issue.fetch('project')
count = issue.fetch('count', nil)
@@ -117,9 +151,9 @@ module Sentry
short_id: issue.fetch('shortId', nil),
status: issue.fetch('status', nil),
frequency: frequency,
- project_id: project.fetch('id'),
- project_name: project.fetch('name', nil),
- project_slug: project.fetch('slug', nil)
+ project_id: issue.dig('project', 'id'),
+ project_name: issue.dig('project', 'name'),
+ project_slug: issue.dig('project', 'slug')
)
end
@@ -127,12 +161,12 @@ module Sentry
organization = project.fetch('organization')
Gitlab::ErrorTracking::Project.new(
- id: project.fetch('id'),
+ id: project.fetch('id', nil),
name: project.fetch('name'),
slug: project.fetch('slug'),
status: project.dig('status'),
organization_name: organization.fetch('name'),
- organization_id: organization.fetch('id'),
+ organization_id: organization.fetch('id', nil),
organization_slug: organization.fetch('slug')
)
end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 2f2de083dc0..32df74f104a 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -1,8 +1,8 @@
#! /bin/sh
# GITLAB
-# Maintainer: @randx
-# Authors: rovanion.luckey@gmail.com, @randx
+# Maintainer: @dzaporozhets
+# Authors: rovanion.luckey@gmail.com, @dzaporozhets
### BEGIN INIT INFO
# Provides: gitlab
@@ -26,6 +26,7 @@
### Environment variables
RAILS_ENV="production"
+EXPERIMENTAL_PUMA=""
# Script variable names should be lower-case not to conflict with
# internal /bin/sh variables such as PATH, EDITOR or SHELL.
@@ -75,7 +76,7 @@ check_pids(){
echo "Could not create the path $pid_path needed to store the pids."
exit 1
fi
- # If there exists a file which should hold the value of the Unicorn pid: read it.
+ # If there exists a file which should hold the value of the web server pid: read it.
if [ -f "$web_server_pid_path" ]; then
wpid=$(cat "$web_server_pid_path")
else
@@ -198,7 +199,7 @@ check_stale_pids(){
# If there is a pid it is something else than 0, the service is running if
# *_status is == 0.
if [ "$wpid" != "0" ] && [ "$web_status" != "0" ]; then
- echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran."
+ echo "Removing stale web server pid. This is most likely caused by the web server crashing the last time it ran."
if ! rm "$web_server_pid_path"; then
echo "Unable to remove stale pid, exiting."
exit 1
@@ -250,12 +251,12 @@ exit_if_not_running(){
fi
}
-## Starts Unicorn and Sidekiq if they're not running.
+## Starts web server and Sidekiq if they're not running.
start_gitlab() {
check_stale_pids
if [ "$web_status" != "0" ]; then
- echo "Starting GitLab Unicorn"
+ echo "Starting GitLab web server"
fi
if [ "$sidekiq_status" != "0" ]; then
echo "Starting GitLab Sidekiq"
@@ -275,12 +276,12 @@ start_gitlab() {
# Then check if the service is running. If it is: don't start again.
if [ "$web_status" = "0" ]; then
- echo "The Unicorn web server already running with pid $wpid, not restarting."
+ echo "The web server already running with pid $wpid, not restarting."
else
# Remove old socket if it exists
rm -f "$rails_socket" 2>/dev/null
# Start the web server
- RAILS_ENV=$RAILS_ENV bin/web start
+ RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web start
fi
# If sidekiq is already running, don't start it again.
@@ -336,13 +337,13 @@ start_gitlab() {
print_status
}
-## Asks Unicorn, Sidekiq and MailRoom if they would be so kind as to stop, if not kills them.
+## Asks web server, Sidekiq and MailRoom if they would be so kind as to stop, if not kills them.
stop_gitlab() {
exit_if_not_running
if [ "$web_status" = "0" ]; then
- echo "Shutting down GitLab Unicorn"
- RAILS_ENV=$RAILS_ENV bin/web stop
+ echo "Shutting down GitLab web server"
+ RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web stop
fi
if [ "$sidekiq_status" = "0" ]; then
echo "Shutting down GitLab Sidekiq"
@@ -398,9 +399,9 @@ print_status() {
return
fi
if [ "$web_status" = "0" ]; then
- echo "The GitLab Unicorn web server with pid $wpid is running."
+ echo "The GitLab web server with pid $wpid is running."
else
- printf "The GitLab Unicorn web server is \033[31mnot running\033[0m.\n"
+ printf "The GitLab web server is \033[31mnot running\033[0m.\n"
fi
if [ "$sidekiq_status" = "0" ]; then
echo "The GitLab Sidekiq job dispatcher with pid $spid is running."
@@ -438,15 +439,15 @@ print_status() {
fi
}
-## Tells unicorn to reload its config and Sidekiq to restart
+## Tells web server to reload its config and Sidekiq to restart
reload_gitlab(){
exit_if_not_running
if [ "$wpid" = "0" ];then
- echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
+ echo "The GitLab web server Web server is not running thus its configuration can't be reloaded."
exit 1
fi
- printf "Reloading GitLab Unicorn configuration... "
- RAILS_ENV=$RAILS_ENV bin/web reload
+ printf "Reloading GitLab web server configuration... "
+ RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web reload
echo "Done."
echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
@@ -461,7 +462,7 @@ reload_gitlab(){
print_status
}
-## Restarts Sidekiq and Unicorn.
+## Restarts Sidekiq and web server.
restart_gitlab(){
check_status
if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; } || { [ "$gitaly_enabled" = true ] && [ "$gitaly_status" = "0" ]; }; then
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 295c79fccfc..ab41dba3017 100644
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -5,6 +5,9 @@
# Normal values are "production", "test" and "development".
RAILS_ENV="production"
+# Uncomment the line below to enable Puma web server instead of Unicorn.
+# EXPERIMENTAL_PUMA=1
+
# app_user defines the user that GitLab is run as.
# The default is "git".
app_user="git"
diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb
index 6cd53779bfd..a331f88873b 100644
--- a/lib/system_check/app/git_user_default_ssh_config_check.rb
+++ b/lib/system_check/app/git_user_default_ssh_config_check.rb
@@ -53,7 +53,7 @@ module SystemCheck
end
def ssh_dir
- return nil unless home_dir
+ return unless home_dir
File.join(home_dir, '.ssh')
end
diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb
index 7c3e7759dd0..467711fb74e 100644
--- a/lib/system_check/app/git_version_check.rb
+++ b/lib/system_check/app/git_version_check.rb
@@ -7,7 +7,7 @@ module SystemCheck
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
- @required_version ||= Gitlab::VersionInfo.parse('2.18.0')
+ @required_version ||= Gitlab::VersionInfo.parse('2.21.0')
end
def self.current_version
diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb
index 60e07718338..53da62df176 100644
--- a/lib/system_check/app/ruby_version_check.rb
+++ b/lib/system_check/app/ruby_version_check.rb
@@ -7,7 +7,7 @@ module SystemCheck
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
- @required_version ||= Gitlab::VersionInfo.new(2, 3, 5)
+ @required_version ||= Gitlab::VersionInfo.new(2, 5, 3)
end
def self.current_version
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index 46aad8aa885..c36cacbaf4f 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -121,7 +121,7 @@ module SystemCheck
#
# @see #try_fixing_it
# @see #fix_and_rerun
- # @see #for_more_infromation
+ # @see #for_more_information
def show_error
raise NotImplementedError
end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index 560a52053d8..8cf7c9e89f0 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -209,7 +209,7 @@ namespace :gemojione do
image.destroy!
end
- EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i
+ EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i.freeze
def rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
# Rename file from unicode to emoji name
matches = EMOJI_IMAGE_PATH_RE.match(image_path)
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index 2235a6ba194..91a52144dd2 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -19,6 +19,7 @@ namespace :gettext do
Rake::Task['gettext:po_to_json'].invoke
end
+ desc 'Regenerate gitlab.pot file'
task :regenerate do
pot_file = 'locale/gitlab.pot'
# Remove all translated files, this speeds up finding
diff --git a/lib/tasks/gitlab/artifacts/migrate.rake b/lib/tasks/gitlab/artifacts/migrate.rake
index e7634d2ed4f..9012e55a70c 100644
--- a/lib/tasks/gitlab/artifacts/migrate.rake
+++ b/lib/tasks/gitlab/artifacts/migrate.rake
@@ -11,14 +11,13 @@ namespace :gitlab do
Ci::Build.joins(:project)
.with_artifacts_stored_locally
.find_each(batch_size: 10) do |build|
- begin
- build.artifacts_file.migrate!(ObjectStorage::Store::REMOTE)
- build.artifacts_metadata.migrate!(ObjectStorage::Store::REMOTE)
- logger.info("Transferred artifact ID #{build.id} with size #{build.artifacts_size} to object storage")
- rescue => e
- logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
- end
+ build.artifacts_file.migrate!(ObjectStorage::Store::REMOTE)
+ build.artifacts_metadata.migrate!(ObjectStorage::Store::REMOTE)
+
+ logger.info("Transferred artifact ID #{build.id} with size #{build.artifacts_size} to object storage")
+ rescue => e
+ logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index 7a42e4e92a0..a07ae3a418a 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -10,9 +10,15 @@ namespace :gitlab do
rake:assets:precompile
webpack:compile
gitlab:assets:fix_urls
+ gitlab:assets:compile_vrt
].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task))
end
+ desc 'GitLab | Assets | Compile visual review toolbar'
+ task :compile_vrt do
+ system 'yarn', 'webpack-vrt'
+ end
+
desc 'GitLab | Assets | Clean up old compiled frontend assets'
task clean: ['rake:assets:clean']
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 3a1a36e36ce..c531eb1d216 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -20,6 +20,11 @@ namespace :gitlab do
backup.pack
backup.cleanup
backup.remove_old
+
+ puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
+ "and are not included in this backup. You will need these files to restore a backup.\n" \
+ "Please back them up manually.".color(:red)
+ puts "Backup task is done."
end
# Restore backup of GitLab system
@@ -61,13 +66,16 @@ namespace :gitlab do
Rake::Task['gitlab:backup:uploads:restore'].invoke unless backup.skipped?('uploads')
Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds')
Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts')
- Rake::Task["gitlab:backup:pages:restore"].invoke unless backup.skipped?('pages')
+ Rake::Task['gitlab:backup:pages:restore'].invoke unless backup.skipped?('pages')
Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs')
Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry')
Rake::Task['gitlab:shell:setup'].invoke
Rake::Task['cache:clear'].invoke
backup.cleanup
+ puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \
+ "and are not included in this backup. You will need to restore these files manually.".color(:red)
+ puts "Restore task is done."
end
namespace :repo do
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 451ba651674..760331620ef 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -60,7 +60,7 @@ namespace :gitlab do
.new(server.storage)
.rename(path, new_path)
rescue StandardError => e
- puts "Error occured while moving the repository: #{e.message}".color(:red)
+ puts "Error occurred while moving the repository: #{e.message}".color(:red)
end
end
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index b94b21775ee..4e7a8adbef6 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -29,7 +29,10 @@ namespace :gitlab do
# If MySQL, turn off foreign key checks
connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
- tables = connection.data_sources
+ # connection.tables is deprecated in MySQLAdapter, but in PostgreSQLAdapter
+ # data_sources returns both views and tables, so use #tables instead
+ tables = Gitlab::Database.mysql? ? connection.data_sources : connection.tables
+
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
diff --git a/lib/tasks/gitlab/features.rake b/lib/tasks/gitlab/features.rake
new file mode 100644
index 00000000000..d88bcca0819
--- /dev/null
+++ b/lib/tasks/gitlab/features.rake
@@ -0,0 +1,24 @@
+namespace :gitlab do
+ namespace :features do
+ desc 'GitLab | Features | Enable direct Git access via Rugged for NFS'
+ task enable_rugged: :environment do
+ set_rugged_feature_flags(true)
+ puts 'All Rugged feature flags were enabled.'
+ end
+
+ task disable_rugged: :environment do
+ set_rugged_feature_flags(false)
+ puts 'All Rugged feature flags were disabled.'
+ end
+ end
+
+ def set_rugged_feature_flags(status)
+ Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
+ if status
+ Feature.enable(flag)
+ else
+ Feature.get(flag).remove
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index e97d77d20e0..8fadadccce9 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -14,6 +14,12 @@ namespace :gitlab do
rake_version = run_and_match(%w(rake --version), /[\d\.]+/).try(:to_s)
# check redis version
redis_version = run_and_match(%w(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
+
+ # check for system defined proxies
+ if Gitlab.ee?
+ proxies = Gitlab::Proxy.detect_proxy.map {|k, v| "#{k}: #{v}"}.join("\n\t\t")
+ end
+
# check Git version
git_version = run_and_match([Gitlab.config.git.bin_path, '--version'], /git version ([\d\.]+)/).to_a
# check Go version
@@ -22,6 +28,11 @@ namespace :gitlab do
puts ""
puts "System information".color(:yellow)
puts "System:\t\t#{os_name || "unknown".color(:red)}"
+
+ if Gitlab.ee?
+ puts "Proxy:\t\t#{proxies.present? ? proxies.color(:green) : "no"}"
+ end
+
puts "Current User:\t#{run_command(%w(whoami))}"
puts "Using RVM:\t#{rvm_version.present? ? "yes".color(:green) : "no"}"
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
@@ -34,14 +45,20 @@ namespace :gitlab do
puts "Sidekiq Version:#{Sidekiq::VERSION}"
puts "Go Version:\t#{go_version[1] || "unknown".color(:red)}"
- # check database adapter
- database_adapter = ActiveRecord::Base.connection.adapter_name.downcase
-
project = Group.new(path: "some-group").projects.build(path: "some-project")
# construct clone URLs
http_clone_url = project.http_url_to_repo
ssh_clone_url = project.ssh_url_to_repo
+ if Gitlab.ee?
+ geo_node_type =
+ if Gitlab::Geo.current_node
+ Gitlab::Geo.current_node.primary ? 'Primary' : 'Secondary'
+ else
+ 'Undefined'.color(:red)
+ end
+ end
+
omniauth_providers = Gitlab.config.omniauth.providers.map { |provider| provider['name'] }
puts ""
@@ -49,16 +66,24 @@ namespace :gitlab do
puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab.revision}"
puts "Directory:\t#{Rails.root}"
- puts "DB Adapter:\t#{database_adapter}"
+ puts "DB Adapter:\t#{Gitlab::Database.human_adapter_name}"
+ puts "DB Version:\t#{Gitlab::Database.version}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
puts "HTTP Clone URL:\t#{http_clone_url}"
puts "SSH Clone URL:\t#{ssh_clone_url}"
+
+ if Gitlab.ee?
+ puts "Elasticsearch:\t#{Gitlab::CurrentSettings.current_application_settings.elasticsearch_indexing? ? "yes".color(:green) : "no"}"
+ puts "Geo:\t\t#{Gitlab::Geo.enabled? ? "yes".color(:green) : "no"}"
+ puts "Geo node:\t#{geo_node_type}" if Gitlab::Geo.enabled?
+ end
+
puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".color(:green) : "no"}"
puts "Using Omniauth:\t#{Gitlab::Auth.omniauth_enabled? ? "yes".color(:green) : "no"}"
puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab::Auth.omniauth_enabled?
# check Gitolite version
- gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.hooks_path}/../VERSION"
+ gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.path}/VERSION"
if File.readable?(gitlab_shell_version_file)
gitlab_shell_version = File.read(gitlab_shell_version_file)
end
@@ -72,7 +97,7 @@ namespace :gitlab do
puts "- #{name}: \t#{repository_storage.legacy_disk_path}"
end
end
- puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
+ puts "GitLab Shell path:\t\t#{Gitlab.config.gitlab_shell.path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
end
end
diff --git a/lib/tasks/gitlab/lfs/migrate.rake b/lib/tasks/gitlab/lfs/migrate.rake
index a45e5ca91e0..97c15175a23 100644
--- a/lib/tasks/gitlab/lfs/migrate.rake
+++ b/lib/tasks/gitlab/lfs/migrate.rake
@@ -9,13 +9,12 @@ namespace :gitlab do
LfsObject.with_files_stored_locally
.find_each(batch_size: 10) do |lfs_object|
- begin
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
- rescue => e
- logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
- end
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+
+ logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
+ rescue => e
+ logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake
new file mode 100644
index 00000000000..155ba979b36
--- /dev/null
+++ b/lib/tasks/gitlab/seed.rake
@@ -0,0 +1,34 @@
+namespace :gitlab do
+ namespace :seed do
+ desc "GitLab | Seed | Seeds issues"
+ task :issues, [:project_full_path] => :environment do |t, args|
+ projects =
+ if args.project_full_path
+ project = Project.find_by_full_path(args.project_full_path)
+
+ unless project
+ error_message = "Project '#{args.project_full_path}' does not exist!"
+ potential_projects = Project.search(args.project_full_path)
+
+ if potential_projects.present?
+ error_message += " Did you mean '#{potential_projects.first.full_path}'?"
+ end
+
+ puts error_message.color(:red)
+ exit 1
+ end
+
+ [project]
+ else
+ Project.find_each
+ end
+
+ projects.each do |project|
+ puts "\nSeeding issues for the '#{project.full_path}' project"
+ seeder = Quality::Seeders::Issues.new(project: project)
+ issues_created = seeder.seed(backfill_weeks: 5, average_issues_per_week: 2)
+ puts "\n#{issues_created} issues created!"
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0ebc6f00793..487808a7baa 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -51,9 +51,6 @@ namespace :gitlab do
end
end
- # (Re)create hooks
- Rake::Task['gitlab:shell:create_hooks'].invoke
-
Gitlab::Shell.ensure_secret_token!
end
@@ -78,15 +75,6 @@ namespace :gitlab do
end
end
end
-
- desc 'Create or repair repository hooks symlink'
- task create_hooks: :gitlab_environment do
- warn_user_is_not_gitlab
-
- puts 'Creating/Repairing hooks symlinks for all repositories'
- system(*%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
- puts 'done'.color(:green)
- end
end
def setup
@@ -103,19 +91,12 @@ namespace :gitlab do
Gitlab::Shell.new.remove_all_keys
- Gitlab::Shell.new.batch_add_keys do |adder|
- Key.find_each(batch_size: 1000) do |key|
- adder.add_key(key.shell_id, key.key)
- print '.'
+ Key.find_in_batches(batch_size: 1000) do |keys|
+ unless Gitlab::Shell.new.batch_add_keys(keys)
+ puts "Failed to add keys...".color(:red)
+ exit 1
end
end
- puts ""
-
- unless $?.success?
- puts "Failed to add keys...".color(:red)
- exit 1
- end
-
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".color(:red)
exit 1
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index f9ce3e1d338..954f827f716 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -11,6 +11,13 @@ namespace :gitlab do
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
+ if storage_migrator.rollback_pending?
+ warn "There is already a rollback operation in progress, " \
+ "running a migration at the same time may have unexpected consequences."
+
+ next
+ end
+
if helper.range_single_item?
project = Project.with_unmigrated_storage.find_by(id: helper.range_from)
@@ -36,8 +43,60 @@ namespace :gitlab do
print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{helper.batch_size}"
- helper.project_id_batches do |start, finish|
- storage_migrator.bulk_schedule(start: start, finish: finish)
+ helper.project_id_batches_migration do |start, finish|
+ storage_migrator.bulk_schedule_migration(start: start, finish: finish)
+
+ print '.'
+ end
+
+ puts ' Done!'
+ end
+
+ desc 'GitLab | Storage | Rollback existing projects to Legacy Storage'
+ task rollback_to_legacy: :environment do
+ if Gitlab::Database.read_only?
+ warn 'This task requires database write access. Exiting.'
+
+ next
+ end
+
+ storage_migrator = Gitlab::HashedStorage::Migrator.new
+ helper = Gitlab::HashedStorage::RakeHelper
+
+ if storage_migrator.migration_pending?
+ warn "There is already a migration operation in progress, " \
+ "running a rollback at the same time may have unexpected consequences."
+
+ next
+ end
+
+ if helper.range_single_item?
+ project = Project.with_storage_feature(:repository).find_by(id: helper.range_from)
+
+ unless project
+ warn "There are no projects that can be rolledback with ID=#{helper.range_from}"
+
+ next
+ end
+
+ puts "Enqueueing storage rollback of #{project.full_path} (ID=#{project.id})..."
+ storage_migrator.rollback(project)
+
+ next
+ end
+
+ hashed_projects_count = Project.with_storage_feature(:repository).count
+
+ if hashed_projects_count == 0
+ warn 'There are no projects that can have storage rolledback. Nothing to do!'
+
+ next
+ end
+
+ print "Enqueuing rollback of #{hashed_projects_count} projects in batches of #{helper.batch_size}"
+
+ helper.project_id_batches_rollback do |start, finish|
+ storage_migrator.bulk_schedule_rollback(start: start, finish: finish)
print '.'
end
diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake
index 5a232091a7e..5e1ec481ece 100644
--- a/lib/tasks/gitlab/traces.rake
+++ b/lib/tasks/gitlab/traces.rake
@@ -26,13 +26,12 @@ namespace :gitlab do
Ci::Build.joins(:project)
.with_archived_trace_stored_locally
.find_each(batch_size: 10) do |build|
- begin
- build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE)
- logger.info("Transferred job trace of #{build.id} to object storage")
- rescue => e
- logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
- end
+ build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE)
+
+ logger.info("Transferred job trace of #{build.id} to object storage")
+ rescue => e
+ logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index abe10f5580e..e058e9fe069 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -50,7 +50,7 @@ namespace :gitlab do
puts "Waiting for the import to finish"
sleep(5)
- project.reload
+ project.reset
end
Projects::ImportExport::ExportService.new(project, admin).execute
diff --git a/lib/tasks/gitlab/uploads/sanitize.rake b/lib/tasks/gitlab/uploads/sanitize.rake
new file mode 100644
index 00000000000..12cf5302555
--- /dev/null
+++ b/lib/tasks/gitlab/uploads/sanitize.rake
@@ -0,0 +1,18 @@
+namespace :gitlab do
+ namespace :uploads do
+ namespace :sanitize do
+ desc 'GitLab | Uploads | Remove EXIF from images.'
+ task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time] => :environment do |task, args|
+ args.with_defaults(dry_run: 'true')
+ args.with_defaults(sleep_time: 0.3)
+
+ logger = Logger.new(STDOUT)
+
+ sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger)
+ sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id,
+ dry_run: args.dry_run != 'false',
+ sleep_time: args.sleep_time.to_f)
+ end
+ end
+ end
+end
diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake
index 786efd14b1a..305e15d69d5 100644
--- a/lib/tasks/haml-lint.rake
+++ b/lib/tasks/haml-lint.rake
@@ -1,6 +1,6 @@
unless Rails.env.production?
require 'haml_lint/rake_task'
- require 'haml_lint/inline_javascript'
+ require Rails.root.join('haml_lint/inline_javascript')
# Workaround for warnings from parser/current
# Keep it even if it no longer emits any warnings,
diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake
index 62a12174efa..2dc14183fa3 100644
--- a/lib/tasks/karma.rake
+++ b/lib/tasks/karma.rake
@@ -1,8 +1,11 @@
unless Rails.env.production?
namespace :karma do
desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
- RSpec::Core::RakeTask.new(:fixtures, [:pattern]) do |t, args|
- args.with_defaults(pattern: 'spec/javascripts/fixtures/*.rb')
+ task fixtures: ['karma:rspec_fixtures']
+
+ desc 'GitLab | Karma | Generate fixtures using RSpec'
+ RSpec::Core::RakeTask.new(:rspec_fixtures, [:pattern]) do |t, args|
+ args.with_defaults(pattern: '{spec,ee/spec}/javascripts/fixtures/*.rb')
ENV['NO_KNAPSACK'] = 'true'
t.pattern = args[:pattern]
t.rspec_opts = '--format documentation'
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 5d673a1a285..2353b2dc659 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -19,11 +19,9 @@ unless Rails.env.production?
desc "GitLab | lint | Lint HAML files"
task :haml do
- begin
- Rake::Task['haml_lint'].invoke
- rescue RuntimeError # The haml_lint tasks raise a RuntimeError
- exit(1)
- end
+ Rake::Task['haml_lint'].invoke
+ rescue RuntimeError # The haml_lint tasks raise a RuntimeError
+ exit(1)
end
desc "GitLab | lint | Run several lint checks"
@@ -39,32 +37,15 @@ unless Rails.env.production?
lint:static_verification
].each do |task|
pid = Process.fork do
- rd_out, wr_out = IO.pipe
- rd_err, wr_err = IO.pipe
- stdout = $stdout.dup
- stderr = $stderr.dup
- $stdout.reopen(wr_out)
- $stderr.reopen(wr_err)
-
- begin
- Rake::Task[task].invoke
- rescue SystemExit => ex
- msg = "*** Rake task #{task} exited:"
- raise ex
- rescue => ex
- msg = "*** Rake task #{task} raised #{ex.class}:"
- raise ex
- ensure
- $stdout.reopen(stdout)
- $stderr.reopen(stderr)
- wr_out.close
- wr_err.close
-
- warn "\n#{msg}\n\n" if msg
-
- IO.copy_stream(rd_out, $stdout)
- IO.copy_stream(rd_err, $stderr)
- end
+ puts "*** Running rake task: #{task} ***"
+
+ Rake::Task[task].invoke
+ rescue SystemExit => ex
+ warn "!!! Rake task #{task} exited:"
+ raise ex
+ rescue StandardError, ScriptError => ex
+ warn "!!! Rake task #{task} raised #{ex.class}:"
+ raise ex
end
Process.waitpid(pid)
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
index aa2d01730d7..cb7c496c31c 100644
--- a/lib/tasks/migrate/migrate_iids.rake
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -2,49 +2,43 @@ desc "GitLab | Build internal ids for issues and merge requests"
task migrate_iids: :environment do
puts 'Issues'.color(:yellow)
Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
- begin
- issue.set_iid
+ issue.set_iid
- if issue.update_attribute(:iid, issue.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if issue.update_attribute(:iid, issue.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
puts 'Merge Requests'.color(:yellow)
MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
- begin
- mr.set_iid
+ mr.set_iid
- if mr.update_attribute(:iid, mr.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if mr.update_attribute(:iid, mr.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
puts 'Milestones'.color(:yellow)
Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
- begin
- m.set_iid
+ m.set_iid
- if m.update_attribute(:iid, m.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if m.update_attribute(:iid, m.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 2eddcb3c777..c881ad4cf12 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -1,7 +1,32 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
namespace :spec do
- desc 'GitLab | Rspec | Run request specs'
+ desc 'GitLab | RSpec | Run unit tests'
+ RSpec::Core::RakeTask.new(:unit, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:unit)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc 'GitLab | RSpec | Run integration tests'
+ RSpec::Core::RakeTask.new(:integration, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:integration)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc 'GitLab | RSpec | Run system tests'
+ RSpec::Core::RakeTask.new(:system, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:system)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc '[Deprecated] Use the "bin/rspec --tag api" instead'
task :api do
cmds = [
%w(rake gitlab:setup),
@@ -10,7 +35,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run feature specs'
+ desc '[Deprecated] Use the "spec:system" task instead'
task :feature do
cmds = [
%w(rake gitlab:setup),
@@ -19,7 +44,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run model specs'
+ desc '[Deprecated] Use "bin/rspec spec/models" instead'
task :models do
cmds = [
%w(rake gitlab:setup),
@@ -28,7 +53,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run service specs'
+ desc '[Deprecated] Use "bin/rspec spec/services" instead'
task :services do
cmds = [
%w(rake gitlab:setup),
@@ -37,7 +62,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run lib specs'
+ desc '[Deprecated] Use "bin/rspec spec/lib" instead'
task :lib do
cmds = [
%w(rake gitlab:setup),
@@ -45,15 +70,6 @@ namespace :spec do
]
run_commands(cmds)
end
-
- desc 'GitLab | Rspec | Run other specs'
- task :other do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
- ]
- run_commands(cmds)
- end
end
desc "GitLab | Run specs"
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index eec024f9bbb..46635cd7c8f 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -24,7 +24,7 @@ namespace :tokens do
end
end
-class TmpUser < ActiveRecord::Base
+class TmpUser < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
include TokenAuthenticatable
self.table_name = 'users'
diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb
deleted file mode 100644
index 05bb3ed7f1c..00000000000
--- a/lib/unfold_form.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'gt_one_coercion'
-
-class UnfoldForm
- include Virtus.model
-
- attribute :since, GtOneCoercion
- attribute :to, GtOneCoercion
- attribute :bottom, Boolean
- attribute :unfold, Boolean, default: true
- attribute :offset, Integer
- attribute :indent, Integer, default: 0
-end
diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index 71f89268a38..9d3218cbd36 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ar\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:11\n"
+"PO-Revision-Date: 2019-04-12 12:34\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -48,8 +57,14 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -186,12 +201,30 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -216,7 +249,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -225,6 +258,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -234,6 +273,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -246,6 +288,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -303,6 +354,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -312,6 +366,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -342,6 +399,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -360,6 +426,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -423,6 +492,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -432,6 +504,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -447,6 +528,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -480,7 +564,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -492,18 +588,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -534,12 +651,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -549,6 +672,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -579,34 +708,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -630,18 +786,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -678,6 +843,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -759,12 +927,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -780,6 +957,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -789,15 +969,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -810,6 +1008,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -828,6 +1032,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -849,9 +1056,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -861,6 +1065,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -873,18 +1086,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -897,6 +1119,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -909,6 +1134,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -924,6 +1152,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -966,12 +1197,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -1029,6 +1266,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -1041,15 +1281,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -1062,6 +1320,60 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1077,15 +1389,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1098,15 +1425,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1119,6 +1455,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1173,9 +1512,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1191,6 +1527,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1206,10 +1545,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1218,6 +1557,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1236,6 +1581,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1275,16 +1623,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1410,6 +1752,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1647,6 +1995,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1668,9 +2022,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1689,6 +2052,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1722,6 +2088,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1737,6 +2106,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1752,6 +2124,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1764,19 +2139,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1803,6 +2193,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1815,6 +2208,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1842,9 +2241,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1866,25 +2262,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1911,6 +2307,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1944,6 +2343,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1959,6 +2361,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1971,6 +2376,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1980,12 +2388,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -2043,6 +2457,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -2058,9 +2475,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2076,15 +2490,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2100,16 +2514,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2148,6 +2559,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2157,19 +2574,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2217,7 +2634,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2244,13 +2661,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2259,15 +2676,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2295,7 +2715,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2307,9 +2727,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2352,9 +2769,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2367,10 +2781,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2379,15 +2793,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2418,7 +2829,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2454,16 +2865,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2475,7 +2886,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2484,10 +2895,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2526,9 +2943,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2538,6 +2952,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2547,15 +2964,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2655,6 +3084,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2724,6 +3156,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2739,12 +3177,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2808,6 +3258,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2835,18 +3288,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2859,6 +3303,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2883,9 +3330,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2901,9 +3345,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2913,10 +3381,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2928,6 +3396,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2952,9 +3423,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -3024,6 +3492,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -3063,9 +3534,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3108,6 +3576,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3132,15 +3603,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3180,6 +3660,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3192,6 +3675,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3207,6 +3705,9 @@ msgstr[5] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3345,12 +3846,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3429,6 +3963,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3468,6 +4005,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3513,6 +4056,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3534,6 +4080,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3546,18 +4095,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3573,6 +4158,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3600,6 +4188,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3609,9 +4203,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3627,16 +4218,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3654,13 +4257,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3681,6 +4278,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3732,15 +4335,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3753,6 +4374,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3804,6 +4437,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3852,6 +4488,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3867,12 +4515,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3936,12 +4620,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3951,12 +4629,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3990,6 +4674,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -4008,9 +4698,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -4032,13 +4719,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -4050,9 +4743,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -4065,12 +4773,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4104,9 +4818,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4164,7 +4875,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4191,6 +4902,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4257,9 +4980,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4275,6 +4995,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4368,13 +5091,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4404,12 +5133,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4419,6 +5157,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4443,9 +5184,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4464,6 +5202,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4509,6 +5250,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4617,9 +5361,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4635,6 +5394,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4665,6 +5427,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4704,24 +5472,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4740,9 +5529,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4764,6 +5550,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4785,6 +5574,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4800,12 +5592,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4839,15 +5637,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4866,9 +5673,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4878,6 +5694,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4911,6 +5730,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4929,6 +5751,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4938,6 +5763,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4947,18 +5838,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -5052,6 +5955,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5091,6 +5997,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5106,12 +6015,27 @@ msgstr[5] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5169,15 +6093,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5190,9 +6117,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5277,12 +6201,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5298,15 +6240,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5322,6 +6273,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5331,9 +6285,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5358,6 +6321,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5388,6 +6354,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5406,6 +6387,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5418,6 +6402,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5448,7 +6435,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5493,12 +6483,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5529,6 +6537,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5544,9 +6555,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5589,9 +6612,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5607,6 +6636,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5637,6 +6675,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5712,16 +6753,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5733,6 +6774,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5817,9 +6861,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5841,6 +6891,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5898,15 +6951,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5937,15 +7011,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5970,46 +7044,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -6033,10 +7104,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -6045,9 +7116,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -6057,12 +7134,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6132,16 +7218,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6201,13 +7290,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6249,6 +7341,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6294,6 +7392,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6312,6 +7413,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6321,6 +7425,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6354,9 +7461,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6366,6 +7479,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6378,6 +7497,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6435,13 +7560,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6450,6 +7575,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6465,6 +7593,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6486,6 +7617,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6498,19 +7632,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6540,7 +7683,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6558,15 +7701,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6615,9 +7767,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6633,10 +7782,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6750,6 +7905,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6762,6 +7920,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6771,6 +7935,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6780,6 +7947,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6816,6 +7989,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6843,15 +8019,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6870,9 +8061,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6894,6 +8082,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6918,18 +8112,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6939,6 +8136,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6948,6 +8148,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6966,6 +8169,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6981,21 +8187,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -7044,6 +8253,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -7059,6 +8274,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7101,10 +8322,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7119,6 +8337,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7146,9 +8367,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7173,15 +8406,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7191,9 +8442,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7206,6 +8469,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7215,18 +8481,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7254,6 +8535,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7404,21 +8688,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7518,6 +8802,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7533,16 +8826,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7551,6 +8853,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7560,6 +8865,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7569,6 +8877,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7659,12 +8973,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7713,6 +9036,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7815,6 +9141,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7881,6 +9210,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7893,6 +9228,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7923,6 +9264,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7938,6 +9282,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7977,6 +9327,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -8022,9 +9375,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -8034,12 +9396,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -8058,6 +9441,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8091,6 +9477,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8109,6 +9507,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8121,6 +9525,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8157,6 +9564,27 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "Resend invite"
msgstr ""
@@ -8190,9 +9618,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8217,12 +9654,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8259,12 +9702,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8286,6 +9738,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8319,25 +9777,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8349,6 +9807,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8373,6 +9834,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8382,15 +9846,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8442,6 +9915,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8469,6 +9945,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8490,9 +9975,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8508,10 +9990,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8562,6 +10044,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8571,9 +10062,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8604,10 +10092,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8625,18 +10116,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8652,9 +10164,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8682,9 +10191,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8700,6 +10218,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8709,6 +10230,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8724,6 +10257,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8781,9 +10317,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8793,6 +10335,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8856,6 +10401,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8865,6 +10416,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8886,6 +10440,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8931,6 +10506,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8958,6 +10536,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9108,6 +10689,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9156,15 +10740,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9198,6 +10794,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9351,6 +10950,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9360,15 +10980,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9378,6 +11007,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9501,12 +11133,27 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9525,6 +11172,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9537,15 +11190,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9555,6 +11256,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9582,9 +11295,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9603,6 +11325,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9618,9 +11355,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9675,9 +11421,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9693,6 +11451,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9711,6 +11472,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9723,6 +11487,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9744,6 +11523,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9759,6 +11541,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9807,6 +11592,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9828,10 +11619,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9840,7 +11637,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9864,6 +11661,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9873,10 +11673,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9891,6 +11691,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9957,9 +11760,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10197,6 +11997,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10236,6 +12039,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10260,6 +12069,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10272,6 +12084,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10281,6 +12096,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10305,6 +12132,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10314,15 +12144,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10335,6 +12177,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10395,21 +12240,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10425,6 +12288,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10449,18 +12315,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10491,18 +12369,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10581,18 +12495,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10617,9 +12552,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10629,7 +12561,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10641,12 +12573,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10680,6 +12615,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10716,12 +12654,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10770,6 +12714,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10788,9 +12738,21 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "When:"
msgstr ""
@@ -10803,6 +12765,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10914,6 +12879,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10932,6 +12900,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10944,12 +12915,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10980,6 +12963,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10995,6 +12981,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -11004,6 +12999,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -11013,6 +13014,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -11022,12 +13026,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -11046,12 +13068,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -11064,7 +13101,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -11076,12 +13113,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11109,6 +13158,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11142,6 +13200,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11160,9 +13227,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11178,15 +13251,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11196,12 +13284,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11226,6 +13317,24 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11235,6 +13344,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11244,6 +13356,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11283,6 +13404,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11292,6 +13416,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11301,12 +13434,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11349,9 +13494,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11403,9 +13557,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11427,6 +13578,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11445,9 +13599,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11463,10 +13626,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11499,6 +13683,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11538,22 +13725,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11622,6 +13818,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11646,6 +13845,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11682,6 +13884,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11754,16 +13959,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11823,6 +14031,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11943,12 +14154,24 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11988,9 +14211,36 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -12015,6 +14265,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index ed642e63cdf..f62cc8295f4 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: bg\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:12\n"
+"PO-Revision-Date: 2019-04-12 12:35\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,8 +424,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Ðабор от графики отноÑно непрекъÑнатата интеграциÑ"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивно"
@@ -451,36 +568,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr "ДобавÑне на нова папка"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
msgstr ""
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "ÐаиÑтина ли иÑкате да изтриете този план за Ñхема?"
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Преглед на папката"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "от"
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr "ОтмÑна"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr "Графики"
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr "уÑпешно, Ñ Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ"
msgid "CiStatusLabel|pending"
msgstr "на изчакване"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "пропуÑнато"
@@ -1812,6 +2183,9 @@ msgstr "уÑпешно"
msgid "CiStatusText|pending"
msgstr "на изчакване"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "пропуÑнато"
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr "Подадено от"
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Сравнение"
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr "Копиране на идентификатора на подаване
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr "Създаване на нова папка"
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Създайте Ñи личен жетон за доÑтъп в акаунта Ñи, за да можете да изтеглÑте и изпращате промени чрез %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr "СинтакÑÐ¸Ñ Ð½Ð° „Cron“"
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] "ВнедрÑваниÑ"
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr "ОпиÑание"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,20 +3837,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "СвалÑне във формат „tar“"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "СвалÑне във формат „tar.bz2“"
+msgstr ""
msgid "Download tar.gz"
-msgstr "СвалÑне във формат „tar.gz“"
+msgstr ""
msgid "Download zip"
-msgstr "СвалÑне във формат „zip“"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "СвалÑне на"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Изпращане на кръпките по е-поща"
@@ -3373,6 +3888,9 @@ msgstr "Редактиране"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr "СобÑтвеникът не може да бъде променен"
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr "Планът за Ñхема не може да бъде премахнат"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "ТърÑене по път"
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ОÑвежаването започна уÑпешно"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr "ВнаÑÑне на хранилище"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr "ПредÑтавÑме Ви анализа на циклите"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Изключено"
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr "документациÑта отноÑно планирането на
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "ÐапуÑкане на групата"
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "добавите SSH ключ"
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,21 +7368,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Ðов план за Ñхема"
msgid "New Snippet"
msgstr ""
-msgid "New Snippets"
-msgstr ""
-
msgid "New branch"
msgstr "Ðов клон"
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Ðова папка"
@@ -6301,6 +7401,9 @@ msgstr "Ðов файл"
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Ðов план"
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr "Ðов етикет"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr "Ðе е налично"
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr "ÐÑма доÑтатъчно данни"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr "Филтър"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Схема"
-msgid "Pipeline Health"
-msgstr "СъÑтоÑние"
-
msgid "Pipeline Schedule"
msgstr "План за Ñхема"
msgid "Pipeline Schedules"
msgstr "Планове за Ñхема"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "ÐеуÑпешни:"
@@ -6876,6 +8057,12 @@ msgstr "Променливи"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "ÑобÑтвен"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Схеми"
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr "вÑички"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "уÑпешни"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "Ñ ÐµÑ‚Ð°Ð¿"
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr "Проектът „%{project_name}“ беше обновен уÑпеÑ
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr "ДоÑтъпът до проекта Ñ‚Ñ€Ñбва да бъде даван поотделно на вÑеки потребител."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr "ИзнеÑените данни на проекта не могат да бъдат изтрити."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "ИзнеÑените данни на проекта бÑха изтрити."
@@ -7392,6 +8669,9 @@ msgstr "Връзката към изнеÑените данни на проекÑ
msgid "Project export started. A download link will be sent by email."
msgstr "ИзнаÑÑнето на проекта започна. Ще получите връзка към данните по е-поща."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Прочетете повече"
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr "Премахване на проекта"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr "Запазване на плана за Ñхема"
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Създаване на нов план за Ñхема"
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Планиране на Ñхемите"
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr "Изберете чаÑова зона"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Задайте парола на акаунта Ñи, за да можете да изтеглÑте и изпращате промени чрез %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Създайте %{new_merge_request} Ñ Ñ‚ÐµÐ·Ð¸ промени"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Преминаване към клон/етикет"
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr "СъвкупноÑтта от ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð´Ð¾Ð±Ð°Ð²ÐµÐ½Ð¸ към дÐ
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "Връзката на разклонение беше премахната."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "Етапът на проблемите показва колко е времето от Ñъздаването на проблем до определÑнето на целеви етап на проекта за него, или до добавÑнето му в ÑпиÑък на дъÑката за проблеми. Започнете да добавÑте проблеми, за да видите данните за този етап."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr "Ð’Ñеки впиÑан потребител има доÑтъп до п
msgid "The project can be accessed without any authentication."
msgstr "Ð’Ñеки може да има доÑтъп до проекта, без нужда от удоÑтоверÑване."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Хранилището за този проект не ÑъщеÑтвува."
@@ -9423,6 +11105,21 @@ msgstr "Етапът на преглед и одобрение показва в
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "Етапът на подготовка за издаване показва времето между прилагането на заÑвката за Ñливане и внедрÑването на кода в Ñредата на работещата крайна верÑиÑ. Данните ще бъдат добавени автоматично Ñлед като направите първото Ñи внедрÑване в крайната верÑиÑ."
@@ -9438,9 +11135,18 @@ msgstr "Времето, което отнема вÑеки Ð·Ð°Ð¿Ð¸Ñ Ð¾Ñ‚ даÐ
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr "Това означава, че нÑма да можете да изпр
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr "преди %s меÑеца"
msgid "Timeago|%s months remaining"
msgstr "оÑтават %s меÑеца"
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr "оÑтават %s Ñекунди"
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "щракнете за качване"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "ОттеглÑне на заÑвката за доÑтъп"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr "Ðа път Ñте да премахнете връзката на раÐ
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Можете да добавÑте файлове Ñамо когато Ñе намирате в клон"
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Ðе можете да Ñъздавате повече проекти"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "Ðуждаете Ñе от разрешение."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "ÐÑма да получавате никакви извеÑÑ‚Ð¸Ñ Ð¿Ð¾ е-поща"
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Вашето име"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/bn_BD/gitlab.po b/locale/bn_BD/gitlab.po
new file mode 100644
index 00000000000..dd593020e0f
--- /dev/null
+++ b/locale/bn_BD/gitlab.po
@@ -0,0 +1,13998 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: gitlab <community@gitlab.com>\n"
+"Language-Team: Bengali\n"
+"Language: bn_BD\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: bn\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"PO-Revision-Date: 2019-04-12 12:35\n"
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Status"
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <#epic id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d failed test result"
+msgid_plural "%d failed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d staged change"
+msgid_plural "%d staged changes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d unstaged change"
+msgid_plural "%d unstaged changes"
+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 "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s discussion"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
+msgstr ""
+
+msgid "%{count} %{alerts}"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{unstaged} unstaged and %{staged} staged changes"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed issue"
+msgid_plural "%d closed issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed merge request"
+msgid_plural "%d closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 day"
+msgstr ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 merged merge request"
+msgid_plural "%d merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open issue"
+msgid_plural "%d open issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open merge request"
+msgid_plural "%d open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 user"
+msgid_plural "%d users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "API Help"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Accepted MR"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add to project"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add todo"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users or groups who are allowed to approve every merge request"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Aditional minutes"
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin Section"
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will be blocked"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "Advanced settings"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access"
+msgstr ""
+
+msgid "Allow users to request access if visibility is public or internal."
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page"
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the discussion."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching stages."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred whilst committing your changes."
+msgstr ""
+
+msgid "An error occurred whilst fetching the job trace."
+msgstr ""
+
+msgid "An error occurred whilst fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred whilst loading all the files."
+msgstr ""
+
+msgid "An error occurred whilst loading the file content."
+msgstr ""
+
+msgid "An error occurred whilst loading the file."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request."
+msgstr ""
+
+msgid "An error occurred whilst loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approvals"
+msgstr ""
+
+msgid "Approvals required"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
+msgid "Are you sure"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}"
+msgstr ""
+
+msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
+msgstr ""
+
+msgid "BillingPlans|features"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Block"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+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 ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Charts"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|Learn more about Auto DevOps"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+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|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} upgraded successfully."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|Hide"
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Let's Encrypt"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Retry update"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade failed"
+msgstr ""
+
+msgid "ClusterIntegration|Upgraded"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrading"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve discussion"
+msgstr ""
+
+msgid "Comment & unresolve discussion"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure push mirrors."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images."
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "Contribution Charts"
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} – %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "ConvDev Index"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy ID to clipboard"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy file path to clipboard"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Copy secret to clipboard"
+msgstr ""
+
+msgid "Copy to clipboard"
+msgstr ""
+
+msgid "Copy token to clipboard"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "Custom CI config path"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+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 ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Cycle Analytics"
+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 ""
+
+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 "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Copy username to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow read-only access to your repository and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|This project has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke"
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Discard"
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all unstaged changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss ConvDev introduction"
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download tar"
+msgstr ""
+
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod name has been specified"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod logs from"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating a new path"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "Error:"
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day (at 4:00am)"
+msgstr ""
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr ""
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "Fast-forward merge"
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Get started with Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|Loading Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|More Information"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There are no active Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There are no inactive Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File"
+msgid_plural "Files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From milestones:"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Data is out of date from %{timeago}"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository verification"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki verification"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Local attachments"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
+msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Repositories verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-check"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|All"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-check"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|Batch operations"
+msgstr ""
+
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Internal URL"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Re-verification interval"
+msgstr ""
+
+msgid "Geo|Recheck"
+msgstr ""
+
+msgid "Geo|Recheck all projects"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all projects"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Selective synchronization"
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Sync"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|URL"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification capacity"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLab’s issue tracker"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group:"
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+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 "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Client side evaluation"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Open in file view"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "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>."
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance Statistics"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations Settings"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Introducing Your Conversational Development Index"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invoke Count"
+msgstr ""
+
+msgid "Invoke Time"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues Created"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues created per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job traces and artifacts"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "LicenseManagement|Add a license"
+msgstr ""
+
+msgid "LicenseManagement|Add licenses manually to approve or blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Approve"
+msgstr ""
+
+msgid "LicenseManagement|Approve license"
+msgstr ""
+
+msgid "LicenseManagement|Approve license?"
+msgstr ""
+
+msgid "LicenseManagement|Approved"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license?"
+msgstr ""
+
+msgid "LicenseManagement|Blacklisted"
+msgstr ""
+
+msgid "LicenseManagement|Cancel"
+msgstr ""
+
+msgid "LicenseManagement|License"
+msgstr ""
+
+msgid "LicenseManagement|License Management"
+msgstr ""
+
+msgid "LicenseManagement|License details"
+msgstr ""
+
+msgid "LicenseManagement|License name"
+msgstr ""
+
+msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
+msgstr ""
+
+msgid "LicenseManagement|Packages"
+msgstr ""
+
+msgid "LicenseManagement|Remove license"
+msgstr ""
+
+msgid "LicenseManagement|Remove license?"
+msgstr ""
+
+msgid "LicenseManagement|Submit"
+msgstr ""
+
+msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseManagement|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseManagement|URL"
+msgstr ""
+
+msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark todo as done"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown Help"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge commit"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Discussion stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved discussion"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this discussion in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Filter files"
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics for environment"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|Environment"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Learn about environments"
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|No deployed environments"
+msgstr ""
+
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Show last"
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unexpected metrics data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}. Existing project milestones with the same name will be merged. "
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Mirror a repository"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name your individual key via a title"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No container images stored for this project. Add one by following the instructions above."
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No details available"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No issues for the selected time period."
+msgstr ""
+
+msgid "No job trace"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "No merge requests for the selected time period."
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritised labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No pushes for the selected time period."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+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 epic"
+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 ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Documentation"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open source software to collaborate on code"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MR"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages Domains"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path, transfer, remove"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, or removing the group."
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions Help"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline quota"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+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|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+msgstr ""
+
+msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgstr ""
+
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Press Enter or click to search"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+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 has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project:"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|No alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Choose who is allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect Environments in order to restrict who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Rake Tasks Help"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register and see your runners for this group."
+msgstr ""
+
+msgid "Register and see your runners for this project."
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove approver"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removing group will cause all child projects and resources to be removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report abuse to GitLab"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports|%{failedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Confidence"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve all discussions in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resume replication"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used all your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH Keys Help"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scoped label"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in this group"
+msgstr ""
+
+msgid "SearchAutocomplete|in this project"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the dashboard data. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Issue Created"
+msgstr ""
+
+msgid "Security Reports|Create issue"
+msgstr ""
+
+msgid "Security Reports|Dismiss vulnerability"
+msgstr ""
+
+msgid "Security Reports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "Security Reports|More info"
+msgstr ""
+
+msgid "Security Reports|No Vulnerabilities"
+msgstr ""
+
+msgid "Security Reports|There was an error creating the issue."
+msgstr ""
+
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
+msgid "Security Reports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "Security Reports|Undo dismiss"
+msgstr ""
+
+msgid "Security Reports|We've found no vulnerabilities for your group"
+msgstr ""
+
+msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityDashboard|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select members to invite"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Send an email notification to Developers."
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL to clipboard"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set number of approvers required before open merge requests can be merged"
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up a specific Runner automatically"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippet Contents"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get operations projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage all changes"
+msgstr ""
+
+msgid "Stage changes"
+msgstr ""
+
+msgid "Staged"
+msgstr ""
+
+msgid "Staged %{type}"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start discussion"
+msgstr ""
+
+msgid "Start discussion & close %{noteable_name}"
+msgstr ""
+
+msgid "Start discussion & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop environment"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping this environment is currently not possible as a deployment is in progress"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Hooks Help"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Test SAML SSO"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+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 ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>"
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+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 ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
+
+msgid "The tabs below will be removed in a future version"
+msgstr ""
+
+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 ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "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."
+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 no approvers"
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no staged changes"
+msgstr ""
+
+msgid "There are no unstaged changes"
+msgstr ""
+
+msgid "There was an error adding a todo."
+msgstr ""
+
+msgid "There was an error deleting the todo."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This container registry has been scheduled for deletion."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is the most recent deployment to %{link}."
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline is run on the source branch"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Titles and Filenames"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD → Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Todo was successfully marked as done."
+msgstr ""
+
+msgid "Todos"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle discussion"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Token"
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstage"
+msgstr ""
+
+msgid "Unstage all changes"
+msgstr ""
+
+msgid "Unstage changes"
+msgstr ""
+
+msgid "Unstaged"
+msgstr ""
+
+msgid "Unstaged %{type}"
+msgstr ""
+
+msgid "Unstaged and staged %{type}"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "View app"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View in Sentry"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job trace"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Vulnerability Chart"
+msgstr ""
+
+msgid "Vulnerability List"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Confidence"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Report Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We can't find an epic that matches what you are looking for."
+msgstr ""
+
+msgid "We can't find an issue that matches what you are looking for."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't find any results matching"
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We heard back from your U2F device. You have been authenticated."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "When:"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+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_full_name}. 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?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved"
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need a different license to use Geo replication."
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+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 ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because %{reason}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "at"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{namespace} is affected by %{vulnerability}."
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerability"
+msgid_plural "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Class"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Confidence"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Description"
+msgstr ""
+
+msgid "ciReport|Dismiss vulnerability"
+msgstr ""
+
+msgid "ciReport|Dismissed by"
+msgstr ""
+
+msgid "ciReport|Download and apply the patch to fix this vulnerability."
+msgstr ""
+
+msgid "ciReport|Download patch"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|File"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Identifiers"
+msgstr ""
+
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports (Alpha)."
+msgstr ""
+
+msgid "ciReport|License management detected %d license for the source branch only"
+msgid_plural "ciReport|License management detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected %d new license"
+msgid_plural "ciReport|License management detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected no licenses for the source branch only"
+msgstr ""
+
+msgid "ciReport|License management detected no new licenses"
+msgstr ""
+
+msgid "ciReport|Links"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|Method"
+msgstr ""
+
+msgid "ciReport|Namespace"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Severity"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error loading DAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading SAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading container scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error loading dependency scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "ciReport|on pipeline"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy token"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "discussion resolved"
+msgid_plural "discussions resolved"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "done"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is out of the hierarchy of the Group owning the template"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issue boards"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "license management"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "missing"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merge request approved; you can approve additionally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Requires 1 more approval"
+msgid_plural "mrWidget|Requires %d more approvals"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Requires 1 more approval by"
+msgid_plural "mrWidget|Requires %d more approvals by"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "within %d minute "
+msgid_plural "within %d minutes "
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/bn_IN/gitlab.po b/locale/bn_IN/gitlab.po
new file mode 100644
index 00000000000..75fdf861c0d
--- /dev/null
+++ b/locale/bn_IN/gitlab.po
@@ -0,0 +1,13998 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: gitlab <community@gitlab.com>\n"
+"Language-Team: Bengali, India\n"
+"Language: bn_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: bn-IN\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"PO-Revision-Date: 2019-04-12 12:35\n"
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Status"
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <#epic id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d failed test result"
+msgid_plural "%d failed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d staged change"
+msgid_plural "%d staged changes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d unstaged change"
+msgid_plural "%d unstaged changes"
+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 "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s discussion"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
+msgstr ""
+
+msgid "%{count} %{alerts}"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{unstaged} unstaged and %{staged} staged changes"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed issue"
+msgid_plural "%d closed issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed merge request"
+msgid_plural "%d closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 day"
+msgstr ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 merged merge request"
+msgid_plural "%d merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open issue"
+msgid_plural "%d open issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open merge request"
+msgid_plural "%d open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 user"
+msgid_plural "%d users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "API Help"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Accepted MR"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add to project"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add todo"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users or groups who are allowed to approve every merge request"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Aditional minutes"
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin Section"
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will be blocked"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "Advanced settings"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access"
+msgstr ""
+
+msgid "Allow users to request access if visibility is public or internal."
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page"
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the discussion."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching stages."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred whilst committing your changes."
+msgstr ""
+
+msgid "An error occurred whilst fetching the job trace."
+msgstr ""
+
+msgid "An error occurred whilst fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred whilst loading all the files."
+msgstr ""
+
+msgid "An error occurred whilst loading the file content."
+msgstr ""
+
+msgid "An error occurred whilst loading the file."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request."
+msgstr ""
+
+msgid "An error occurred whilst loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approvals"
+msgstr ""
+
+msgid "Approvals required"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
+msgid "Are you sure"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}"
+msgstr ""
+
+msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
+msgstr ""
+
+msgid "BillingPlans|features"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Block"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+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 ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Charts"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|Learn more about Auto DevOps"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+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|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} upgraded successfully."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|Hide"
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Let's Encrypt"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Retry update"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade failed"
+msgstr ""
+
+msgid "ClusterIntegration|Upgraded"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrading"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve discussion"
+msgstr ""
+
+msgid "Comment & unresolve discussion"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure push mirrors."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images."
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "Contribution Charts"
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} – %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "ConvDev Index"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy ID to clipboard"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy file path to clipboard"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Copy secret to clipboard"
+msgstr ""
+
+msgid "Copy to clipboard"
+msgstr ""
+
+msgid "Copy token to clipboard"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "Custom CI config path"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+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 ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Cycle Analytics"
+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 ""
+
+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 "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Copy username to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow read-only access to your repository and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|This project has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke"
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Discard"
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all unstaged changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss ConvDev introduction"
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download tar"
+msgstr ""
+
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod name has been specified"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod logs from"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating a new path"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "Error:"
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day (at 4:00am)"
+msgstr ""
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr ""
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "Fast-forward merge"
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Get started with Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|Loading Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|More Information"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There are no active Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There are no inactive Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File"
+msgid_plural "Files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From milestones:"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Data is out of date from %{timeago}"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository verification"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki verification"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Local attachments"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
+msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Repositories verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-check"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|All"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-check"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|Batch operations"
+msgstr ""
+
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Internal URL"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Re-verification interval"
+msgstr ""
+
+msgid "Geo|Recheck"
+msgstr ""
+
+msgid "Geo|Recheck all projects"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all projects"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Selective synchronization"
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Sync"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|URL"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification capacity"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLab’s issue tracker"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group:"
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+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 "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Client side evaluation"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Open in file view"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "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>."
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance Statistics"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations Settings"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Introducing Your Conversational Development Index"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invoke Count"
+msgstr ""
+
+msgid "Invoke Time"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues Created"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues created per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job traces and artifacts"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "LicenseManagement|Add a license"
+msgstr ""
+
+msgid "LicenseManagement|Add licenses manually to approve or blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Approve"
+msgstr ""
+
+msgid "LicenseManagement|Approve license"
+msgstr ""
+
+msgid "LicenseManagement|Approve license?"
+msgstr ""
+
+msgid "LicenseManagement|Approved"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license?"
+msgstr ""
+
+msgid "LicenseManagement|Blacklisted"
+msgstr ""
+
+msgid "LicenseManagement|Cancel"
+msgstr ""
+
+msgid "LicenseManagement|License"
+msgstr ""
+
+msgid "LicenseManagement|License Management"
+msgstr ""
+
+msgid "LicenseManagement|License details"
+msgstr ""
+
+msgid "LicenseManagement|License name"
+msgstr ""
+
+msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
+msgstr ""
+
+msgid "LicenseManagement|Packages"
+msgstr ""
+
+msgid "LicenseManagement|Remove license"
+msgstr ""
+
+msgid "LicenseManagement|Remove license?"
+msgstr ""
+
+msgid "LicenseManagement|Submit"
+msgstr ""
+
+msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseManagement|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseManagement|URL"
+msgstr ""
+
+msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark todo as done"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown Help"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge commit"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Discussion stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved discussion"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this discussion in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Filter files"
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics for environment"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|Environment"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Learn about environments"
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|No deployed environments"
+msgstr ""
+
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Show last"
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unexpected metrics data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}. Existing project milestones with the same name will be merged. "
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Mirror a repository"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name your individual key via a title"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No container images stored for this project. Add one by following the instructions above."
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No details available"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No issues for the selected time period."
+msgstr ""
+
+msgid "No job trace"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "No merge requests for the selected time period."
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritised labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No pushes for the selected time period."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+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 epic"
+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 ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Documentation"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open source software to collaborate on code"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MR"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages Domains"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path, transfer, remove"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, or removing the group."
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions Help"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline quota"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+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|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+msgstr ""
+
+msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgstr ""
+
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Press Enter or click to search"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+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 has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project:"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|No alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Choose who is allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect Environments in order to restrict who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Rake Tasks Help"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register and see your runners for this group."
+msgstr ""
+
+msgid "Register and see your runners for this project."
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove approver"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removing group will cause all child projects and resources to be removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report abuse to GitLab"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports|%{failedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Confidence"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve all discussions in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resume replication"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used all your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH Keys Help"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scoped label"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in this group"
+msgstr ""
+
+msgid "SearchAutocomplete|in this project"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the dashboard data. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Issue Created"
+msgstr ""
+
+msgid "Security Reports|Create issue"
+msgstr ""
+
+msgid "Security Reports|Dismiss vulnerability"
+msgstr ""
+
+msgid "Security Reports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "Security Reports|More info"
+msgstr ""
+
+msgid "Security Reports|No Vulnerabilities"
+msgstr ""
+
+msgid "Security Reports|There was an error creating the issue."
+msgstr ""
+
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
+msgid "Security Reports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "Security Reports|Undo dismiss"
+msgstr ""
+
+msgid "Security Reports|We've found no vulnerabilities for your group"
+msgstr ""
+
+msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityDashboard|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select members to invite"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Send an email notification to Developers."
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL to clipboard"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set number of approvers required before open merge requests can be merged"
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up a specific Runner automatically"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippet Contents"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get operations projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage all changes"
+msgstr ""
+
+msgid "Stage changes"
+msgstr ""
+
+msgid "Staged"
+msgstr ""
+
+msgid "Staged %{type}"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start discussion"
+msgstr ""
+
+msgid "Start discussion & close %{noteable_name}"
+msgstr ""
+
+msgid "Start discussion & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop environment"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping this environment is currently not possible as a deployment is in progress"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Hooks Help"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Test SAML SSO"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+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 ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>"
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+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 ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
+
+msgid "The tabs below will be removed in a future version"
+msgstr ""
+
+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 ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "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."
+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 no approvers"
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no staged changes"
+msgstr ""
+
+msgid "There are no unstaged changes"
+msgstr ""
+
+msgid "There was an error adding a todo."
+msgstr ""
+
+msgid "There was an error deleting the todo."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This container registry has been scheduled for deletion."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is the most recent deployment to %{link}."
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline is run on the source branch"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Titles and Filenames"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD → Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Todo was successfully marked as done."
+msgstr ""
+
+msgid "Todos"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle discussion"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Token"
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstage"
+msgstr ""
+
+msgid "Unstage all changes"
+msgstr ""
+
+msgid "Unstage changes"
+msgstr ""
+
+msgid "Unstaged"
+msgstr ""
+
+msgid "Unstaged %{type}"
+msgstr ""
+
+msgid "Unstaged and staged %{type}"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "View app"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View in Sentry"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job trace"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Vulnerability Chart"
+msgstr ""
+
+msgid "Vulnerability List"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Confidence"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Report Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We can't find an epic that matches what you are looking for."
+msgstr ""
+
+msgid "We can't find an issue that matches what you are looking for."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't find any results matching"
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We heard back from your U2F device. You have been authenticated."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "When:"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+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_full_name}. 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?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved"
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need a different license to use Geo replication."
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+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 ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because %{reason}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "at"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{namespace} is affected by %{vulnerability}."
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerability"
+msgid_plural "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Class"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Confidence"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Description"
+msgstr ""
+
+msgid "ciReport|Dismiss vulnerability"
+msgstr ""
+
+msgid "ciReport|Dismissed by"
+msgstr ""
+
+msgid "ciReport|Download and apply the patch to fix this vulnerability."
+msgstr ""
+
+msgid "ciReport|Download patch"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|File"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Identifiers"
+msgstr ""
+
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports (Alpha)."
+msgstr ""
+
+msgid "ciReport|License management detected %d license for the source branch only"
+msgid_plural "ciReport|License management detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected %d new license"
+msgid_plural "ciReport|License management detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected no licenses for the source branch only"
+msgstr ""
+
+msgid "ciReport|License management detected no new licenses"
+msgstr ""
+
+msgid "ciReport|Links"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|Method"
+msgstr ""
+
+msgid "ciReport|Namespace"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Severity"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error loading DAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading SAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading container scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error loading dependency scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "ciReport|on pipeline"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy token"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "discussion resolved"
+msgid_plural "discussions resolved"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "done"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is out of the hierarchy of the Group owning the template"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issue boards"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "license management"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "missing"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merge request approved; you can approve additionally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Requires 1 more approval"
+msgid_plural "mrWidget|Requires %d more approvals"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Requires 1 more approval by"
+msgid_plural "mrWidget|Requires %d more approvals by"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "within %d minute "
+msgid_plural "within %d minutes "
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index a6bc33b3974..1facbc90b47 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ca\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:12\n"
+"PO-Revision-Date: 2019-04-12 12:36\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " Estat"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 grup"
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] "1 usuari"
msgstr[1] "%d usuaris"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "2FA activat"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr "404|No s'ha trobat la pàgina"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "Quant al GitLab"
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr "Compte"
msgid "Account and limit"
msgstr "Compte i límit"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Actiu"
@@ -451,36 +568,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr "Afegeix una taula"
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr "Afegeix el comentari"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "Afegeix un comentari a la imatge"
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr "Afegeix una llicència"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Afegeix una aplicació nova"
msgid "Add new directory"
msgstr "Afegeix un directori nou"
-msgid "Add projects"
-msgstr "Afegeix projectes"
-
msgid "Add reaction"
msgstr "Afegeix una reacció"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr "Afegeix usuaris al grup"
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr "Text addicional"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "Àrea d'administració"
msgid "Admin Overview"
msgstr "Informació general d'administració"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Configuració avançada"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr "Tots"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "Tots els usuaris"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr "Analítiques"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Anònim"
@@ -909,15 +1137,33 @@ msgstr "Qualsevol"
msgid "Any Label"
msgstr "Qualsevol etiqueta"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Aparença"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Aplicació"
msgid "Application ID"
msgstr "ID de l'aplicació"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Aplicació: %{name}"
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr "abr"
msgid "April"
msgstr "abril"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr "Projectes arxivats"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr "Peticions de fusió assignades"
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr "ag."
msgid "August"
msgstr "agost"
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Autor"
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr "Cancel·la aquesta tasca"
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr "Diagrames"
msgid "Chat"
msgstr "Xat"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr "Trieu el fitxer..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr "Trieu un color."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
-msgstr "Trieu el fitxer..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr "Tancades"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Incidències tancades"
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Compara"
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Connecta"
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr "S'està connectant..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "Registre de contenidors"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr "Branca actual"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "Node actual"
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Suprimeix la llista"
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr "Descendent"
msgid "Description"
msgstr "Descripció"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr "Descripció:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,17 +3837,23 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "Baixa un arxiu tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Baixa un arxiu tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Baixa un arxiu tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Baixa un arxiu zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr "Edita"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "Adreça electrònica"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr "Activa"
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Activat"
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr "Introduïu el títol de la petició de fusió"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Mostra-ho tot"
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,8 +4707,8 @@ msgstr "Nova"
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
-msgstr "Desa els canvis"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "Estat"
@@ -4051,6 +4734,18 @@ msgstr "feb."
msgid "February"
msgstr "febrer"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr "Filtra..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr "Format"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "Claus GPG"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "General"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "Fallit"
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr "Estat"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Sincronitzat"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "Estat desconegut"
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr "Usuari del GitLab"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr "Enrere"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "Enrere"
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr "Vés a"
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "Grup"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] "Amaga els valors"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "Historial"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr "Identificador"
msgid "Identities"
msgstr "Identitats"
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,21 +6916,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr ""
-
msgid "Merge Request"
msgstr "Petició de fusió"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Peticions de fusió"
msgid "Merge Requests created"
msgstr "S'han creat les peticions de fusió"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "Esdeveniments de fusió"
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Petició de fusió"
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Peticions de fusió"
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,14 +7102,17 @@ msgstr "Mètrica nova"
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
-msgstr "Sistema"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr "Cancel·la"
msgid "Modal|Close"
msgstr "Tanca"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr "Més informació"
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "Mou"
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Nom"
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr "Nom:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index c71b768ece2..f41277b2c79 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: cs\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:13\n"
+"PO-Revision-Date: 2019-04-12 12:35\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " a"
@@ -44,8 +53,12 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -156,12 +169,28 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -182,7 +211,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -191,6 +220,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -200,6 +235,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Profilový obrázek %{name}"
@@ -212,6 +250,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr "%{percent}%% dokonÄeno"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -261,6 +308,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -270,6 +320,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -296,6 +349,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -310,6 +370,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -359,6 +422,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "1. příspěvek!"
@@ -368,6 +434,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "Dvoufaktorové ověření povoleno"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -383,6 +458,9 @@ msgstr "404 | Stránka nebyla nalezena"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -416,7 +494,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -428,18 +518,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -470,12 +581,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -485,6 +602,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -515,34 +638,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -566,18 +716,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr "DodateÄný text"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -614,6 +773,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -695,12 +857,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -714,6 +885,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -723,15 +897,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -744,6 +936,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -762,6 +960,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -783,9 +984,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -795,6 +993,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -807,18 +1014,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -831,6 +1047,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -843,6 +1062,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -858,6 +1080,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -900,12 +1125,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -963,6 +1194,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -975,15 +1209,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Vzhled"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -996,6 +1248,52 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1011,15 +1309,30 @@ msgstr "Dub"
msgid "April"
msgstr "Duben"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1032,15 +1345,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1053,6 +1375,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1107,9 +1432,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr "Přiřazeno k :name"
-
msgid "Assigned to me"
msgstr ""
@@ -1125,6 +1447,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1140,10 +1465,10 @@ msgstr "Srp"
msgid "August"
msgstr "Srpen"
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1152,6 +1477,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Autor"
@@ -1170,6 +1501,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1209,16 +1543,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1344,6 +1672,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1581,6 +1915,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1602,9 +1942,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1623,6 +1972,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1656,6 +2008,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1671,6 +2026,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1686,6 +2044,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1698,19 +2059,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1737,6 +2113,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1749,6 +2128,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1776,9 +2161,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1800,25 +2182,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1845,6 +2227,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1878,6 +2263,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1893,6 +2281,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1905,6 +2296,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1914,12 +2308,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1977,6 +2377,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1992,9 +2395,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2034,16 +2434,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2082,6 +2479,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,19 +2494,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2151,7 +2554,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2178,13 +2581,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2193,15 +2596,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2229,7 +2635,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2286,9 +2689,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2301,10 +2701,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2313,15 +2713,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2352,7 +2749,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,16 +2785,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2409,7 +2806,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2418,10 +2815,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2460,9 +2863,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2472,6 +2872,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2481,15 +2884,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2587,6 +3002,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2656,6 +3074,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2671,12 +3095,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2740,6 +3176,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2767,18 +3206,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2791,6 +3221,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2815,9 +3248,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2833,9 +3263,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2845,10 +3299,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2860,6 +3314,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2884,9 +3341,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2956,6 +3410,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2995,9 +3452,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3040,6 +3494,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3064,15 +3521,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3112,6 +3578,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3124,6 +3593,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3137,6 +3621,9 @@ msgstr[3] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3275,12 +3762,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3359,6 +3879,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3398,6 +3921,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3443,6 +3972,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3464,6 +3996,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3476,18 +4011,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3503,6 +4074,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3530,6 +4104,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3539,9 +4119,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3557,16 +4134,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,13 +4173,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3611,6 +4194,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3662,15 +4251,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3683,6 +4290,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3734,6 +4353,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3782,6 +4404,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3797,12 +4431,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3866,12 +4536,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3881,12 +4545,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3920,6 +4590,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3938,9 +4614,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3962,13 +4635,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3995,12 +4689,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4034,9 +4734,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4094,7 +4791,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4121,6 +4818,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4185,9 +4894,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4203,6 +4909,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4296,13 +5005,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4332,12 +5047,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4347,6 +5071,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4371,9 +5098,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4392,6 +5116,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4437,6 +5164,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4545,9 +5275,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4563,6 +5308,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4593,6 +5341,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4632,24 +5386,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4668,9 +5443,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4692,6 +5464,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4713,6 +5488,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4767,15 +5551,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4794,9 +5587,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4806,6 +5608,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4839,6 +5644,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4857,6 +5665,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4866,6 +5677,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4875,18 +5752,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4980,6 +5869,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5019,6 +5911,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5032,12 +5927,27 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5095,15 +6005,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5116,9 +6029,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5203,12 +6113,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5224,15 +6152,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5248,6 +6185,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5257,9 +6197,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5282,6 +6231,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5312,6 +6264,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5342,6 +6312,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5372,7 +6345,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5417,12 +6393,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5453,6 +6447,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5468,9 +6465,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5513,9 +6522,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5531,6 +6546,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5561,6 +6585,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5634,16 +6661,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5655,6 +6682,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5739,9 +6769,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5761,6 +6797,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5818,15 +6857,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5857,15 +6917,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5890,46 +6950,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5953,10 +7010,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5965,9 +7022,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5977,12 +7040,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6052,16 +7124,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6121,13 +7196,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6169,6 +7247,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6214,6 +7298,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6232,6 +7319,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6241,6 +7331,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6274,9 +7367,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6286,6 +7385,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6298,6 +7403,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6353,13 +7464,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6368,6 +7479,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6383,6 +7497,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6404,6 +7521,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6416,19 +7536,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6458,7 +7587,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6476,15 +7605,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6533,9 +7671,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6551,10 +7686,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6668,6 +7809,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6680,6 +7824,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6687,6 +7837,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6696,6 +7849,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6732,6 +7891,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6759,15 +7921,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6786,9 +7963,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6810,6 +7984,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6834,18 +8014,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6855,6 +8038,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6864,6 +8050,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6882,6 +8071,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6897,21 +8089,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6960,6 +8155,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6975,6 +8176,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7017,10 +8224,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7035,6 +8239,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7062,9 +8269,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7089,15 +8308,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7122,6 +8371,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7131,18 +8383,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7170,6 +8437,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7320,21 +8590,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7434,6 +8704,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7449,16 +8728,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7467,6 +8755,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7476,6 +8767,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7485,6 +8779,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7575,12 +8875,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7629,6 +8938,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7731,6 +9043,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7797,6 +9112,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7809,6 +9130,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7839,6 +9166,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7854,6 +9184,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7891,6 +9227,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7936,9 +9275,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7948,12 +9296,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7972,6 +9341,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8005,6 +9377,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8023,6 +9407,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8035,6 +9425,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8071,6 +9464,23 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
msgstr ""
@@ -8104,9 +9514,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8131,12 +9550,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8171,12 +9596,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8198,6 +9632,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8231,25 +9671,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8261,6 +9701,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8285,6 +9728,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8294,15 +9740,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8354,6 +9809,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8381,6 +9839,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8402,9 +9869,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8420,10 +9884,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8474,6 +9938,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8483,9 +9956,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8516,10 +9986,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8537,18 +10010,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8564,9 +10058,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8594,9 +10085,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8612,6 +10112,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8621,6 +10124,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8636,6 +10151,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8693,9 +10211,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8705,6 +10229,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8766,6 +10293,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8775,6 +10308,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8796,6 +10332,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8841,6 +10398,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8868,6 +10428,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9018,6 +10581,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9066,15 +10632,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9108,6 +10686,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9261,6 +10842,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9270,15 +10872,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9288,6 +10899,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9411,12 +11025,25 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9435,6 +11062,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9447,15 +11080,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9465,6 +11146,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9492,9 +11185,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9513,6 +11215,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9528,9 +11245,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9585,9 +11311,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9603,6 +11341,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9621,6 +11362,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9633,6 +11377,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9654,6 +11413,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9669,6 +11431,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9717,6 +11482,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9738,10 +11509,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9750,7 +11527,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9774,6 +11551,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9783,10 +11563,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9801,6 +11581,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9867,9 +11650,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10103,6 +11883,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10142,6 +11925,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10166,6 +11955,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10178,6 +11970,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10187,6 +11982,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10211,6 +12018,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10220,15 +12030,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10241,6 +12063,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10301,21 +12126,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10331,6 +12174,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10397,18 +12255,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10487,18 +12381,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10523,9 +12438,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10535,7 +12447,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10547,12 +12459,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10586,6 +12501,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10622,12 +12540,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10676,6 +12600,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10694,9 +12624,19 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
msgstr ""
@@ -10709,6 +12649,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10820,6 +12763,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10838,6 +12784,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10850,12 +12799,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10886,6 +12847,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10901,6 +12865,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10910,6 +12883,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10919,6 +12898,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10928,12 +12910,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10952,12 +12952,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10970,7 +12985,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10982,12 +12997,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11015,6 +13042,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11048,6 +13084,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11066,9 +13111,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11084,15 +13135,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11102,12 +13168,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11132,6 +13201,20 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11139,6 +13222,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11146,6 +13232,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11181,6 +13276,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11190,6 +13288,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11199,12 +13306,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11247,9 +13366,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11297,9 +13425,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11321,6 +13446,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11339,9 +13467,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11355,10 +13492,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11389,6 +13547,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11424,22 +13585,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11506,6 +13676,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11528,6 +13701,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11564,6 +13740,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11636,16 +13815,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11701,6 +13883,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11817,12 +14002,22 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11858,9 +14053,36 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11885,6 +14107,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/cy_GB/gitlab.po b/locale/cy_GB/gitlab.po
index 174737050e6..5d41baffb24 100644
--- a/locale/cy_GB/gitlab.po
+++ b/locale/cy_GB/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: cy\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:16\n"
+"PO-Revision-Date: 2019-04-12 12:40\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -48,8 +57,14 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -186,12 +201,30 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -216,7 +249,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -225,6 +258,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -234,6 +273,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -246,6 +288,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -303,6 +354,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -312,6 +366,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -342,6 +399,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -360,6 +426,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -423,6 +492,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -432,6 +504,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -447,6 +528,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -480,7 +564,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -492,18 +588,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -534,12 +651,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -549,6 +672,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -579,34 +708,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -630,18 +786,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -678,6 +843,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -759,12 +927,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -780,6 +957,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -789,15 +969,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -810,6 +1008,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -828,6 +1032,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -849,9 +1056,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -861,6 +1065,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -873,18 +1086,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -897,6 +1119,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -909,6 +1134,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -924,6 +1152,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -966,12 +1197,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -1029,6 +1266,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -1041,15 +1281,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -1062,6 +1320,60 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1077,15 +1389,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1098,15 +1425,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1119,6 +1455,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1173,9 +1512,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1191,6 +1527,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1206,10 +1545,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1218,6 +1557,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1236,6 +1581,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1275,16 +1623,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1410,6 +1752,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1647,6 +1995,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1668,9 +2022,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1689,6 +2052,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1722,6 +2088,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1737,6 +2106,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1752,6 +2124,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1764,19 +2139,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1803,6 +2193,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1815,6 +2208,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1842,9 +2241,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1866,25 +2262,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1911,6 +2307,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1944,6 +2343,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1959,6 +2361,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1971,6 +2376,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1980,12 +2388,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -2043,6 +2457,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -2058,9 +2475,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2076,15 +2490,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2100,16 +2514,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2148,6 +2559,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2157,19 +2574,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2217,7 +2634,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2244,13 +2661,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2259,15 +2676,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2295,7 +2715,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2307,9 +2727,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2352,9 +2769,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2367,10 +2781,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2379,15 +2793,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2418,7 +2829,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2454,16 +2865,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2475,7 +2886,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2484,10 +2895,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2526,9 +2943,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2538,6 +2952,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2547,15 +2964,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2655,6 +3084,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2724,6 +3156,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2739,12 +3177,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2808,6 +3258,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2835,18 +3288,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2859,6 +3303,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2883,9 +3330,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2901,9 +3345,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2913,10 +3381,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2928,6 +3396,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2952,9 +3423,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -3024,6 +3492,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -3063,9 +3534,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3108,6 +3576,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3132,15 +3603,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3180,6 +3660,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3192,6 +3675,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3207,6 +3705,9 @@ msgstr[5] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3345,12 +3846,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3429,6 +3963,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3468,6 +4005,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3513,6 +4056,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3534,6 +4080,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3546,18 +4095,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3573,6 +4158,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3600,6 +4188,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3609,9 +4203,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3627,16 +4218,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3654,13 +4257,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3681,6 +4278,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3732,15 +4335,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3753,6 +4374,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3804,6 +4437,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3852,6 +4488,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3867,12 +4515,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3936,12 +4620,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3951,12 +4629,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3990,6 +4674,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -4008,9 +4698,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -4032,13 +4719,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -4050,9 +4743,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -4065,12 +4773,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4104,9 +4818,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4164,7 +4875,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4191,6 +4902,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4257,9 +4980,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4275,6 +4995,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4368,13 +5091,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4404,12 +5133,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4419,6 +5157,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4443,9 +5184,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4464,6 +5202,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4509,6 +5250,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4617,9 +5361,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4635,6 +5394,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4665,6 +5427,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4704,24 +5472,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4740,9 +5529,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4764,6 +5550,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4785,6 +5574,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4800,12 +5592,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4839,15 +5637,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4866,9 +5673,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4878,6 +5694,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4911,6 +5730,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4929,6 +5751,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4938,6 +5763,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4947,18 +5838,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -5052,6 +5955,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5091,6 +5997,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5106,12 +6015,27 @@ msgstr[5] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5169,15 +6093,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5190,9 +6117,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5277,12 +6201,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5298,15 +6240,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5322,6 +6273,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5331,9 +6285,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5358,6 +6321,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5388,6 +6354,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5406,6 +6387,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5418,6 +6402,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5448,7 +6435,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5493,12 +6483,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5529,6 +6537,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5544,9 +6555,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5589,9 +6612,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5607,6 +6636,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5637,6 +6675,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5712,16 +6753,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5733,6 +6774,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5817,9 +6861,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5841,6 +6891,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5898,15 +6951,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5937,15 +7011,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5970,46 +7044,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -6033,10 +7104,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -6045,9 +7116,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -6057,12 +7134,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6132,16 +7218,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6201,13 +7290,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6249,6 +7341,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6294,6 +7392,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6312,6 +7413,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6321,6 +7425,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6354,9 +7461,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6366,6 +7479,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6378,6 +7497,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6435,13 +7560,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6450,6 +7575,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6465,6 +7593,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6486,6 +7617,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6498,19 +7632,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6540,7 +7683,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6558,15 +7701,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6615,9 +7767,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6633,10 +7782,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6750,6 +7905,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6762,6 +7920,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6771,6 +7935,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6780,6 +7947,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6816,6 +7989,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6843,15 +8019,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6870,9 +8061,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6894,6 +8082,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6918,18 +8112,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6939,6 +8136,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6948,6 +8148,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6966,6 +8169,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6981,21 +8187,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -7044,6 +8253,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -7059,6 +8274,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7101,10 +8322,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7119,6 +8337,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7146,9 +8367,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7173,15 +8406,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7191,9 +8442,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7206,6 +8469,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7215,18 +8481,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7254,6 +8535,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7404,21 +8688,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7518,6 +8802,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7533,16 +8826,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7551,6 +8853,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7560,6 +8865,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7569,6 +8877,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7659,12 +8973,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7713,6 +9036,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7815,6 +9141,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7881,6 +9210,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7893,6 +9228,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7923,6 +9264,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7938,6 +9282,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7977,6 +9327,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -8022,9 +9375,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -8034,12 +9396,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -8058,6 +9441,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8091,6 +9477,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8109,6 +9507,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8121,6 +9525,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8157,6 +9564,27 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "Resend invite"
msgstr ""
@@ -8190,9 +9618,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8217,12 +9654,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8259,12 +9702,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8286,6 +9738,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8319,25 +9777,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8349,6 +9807,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8373,6 +9834,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8382,15 +9846,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8442,6 +9915,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8469,6 +9945,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8490,9 +9975,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8508,10 +9990,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8562,6 +10044,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8571,9 +10062,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8604,10 +10092,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8625,18 +10116,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8652,9 +10164,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8682,9 +10191,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8700,6 +10218,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8709,6 +10230,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8724,6 +10257,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8781,9 +10317,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8793,6 +10335,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8856,6 +10401,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8865,6 +10416,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8886,6 +10440,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8931,6 +10506,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8958,6 +10536,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9108,6 +10689,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9156,15 +10740,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9198,6 +10794,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9351,6 +10950,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9360,15 +10980,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9378,6 +11007,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9501,12 +11133,27 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9525,6 +11172,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9537,15 +11190,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9555,6 +11256,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9582,9 +11295,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9603,6 +11325,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9618,9 +11355,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9675,9 +11421,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9693,6 +11451,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9711,6 +11472,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9723,6 +11487,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9744,6 +11523,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9759,6 +11541,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9807,6 +11592,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9828,10 +11619,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9840,7 +11637,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9864,6 +11661,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9873,10 +11673,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9891,6 +11691,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9957,9 +11760,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10197,6 +11997,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10236,6 +12039,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10260,6 +12069,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10272,6 +12084,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10281,6 +12096,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10305,6 +12132,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10314,15 +12144,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10335,6 +12177,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10395,21 +12240,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10425,6 +12288,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10449,18 +12315,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10491,18 +12369,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10581,18 +12495,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10617,9 +12552,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10629,7 +12561,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10641,12 +12573,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10680,6 +12615,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10716,12 +12654,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10770,6 +12714,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10788,9 +12738,21 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "When:"
msgstr ""
@@ -10803,6 +12765,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10914,6 +12879,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10932,6 +12900,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10944,12 +12915,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10980,6 +12963,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10995,6 +12981,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -11004,6 +12999,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -11013,6 +13014,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -11022,12 +13026,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -11046,12 +13068,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -11064,7 +13101,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -11076,12 +13113,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11109,6 +13158,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11142,6 +13200,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11160,9 +13227,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11178,15 +13251,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11196,12 +13284,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11226,6 +13317,24 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11235,6 +13344,9 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11244,6 +13356,15 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11283,6 +13404,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11292,6 +13416,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11301,12 +13434,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11349,9 +13494,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11403,9 +13557,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11427,6 +13578,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11445,9 +13599,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11463,10 +13626,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11499,6 +13683,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11538,22 +13725,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11622,6 +13818,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11646,6 +13845,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11682,6 +13884,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11754,16 +13959,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11823,6 +14031,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11943,12 +14154,24 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11988,9 +14211,36 @@ msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -12015,6 +14265,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index ee02b31f2b0..0741f7e7e87 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: da\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:13\n"
+"PO-Revision-Date: 2019-04-12 12:37\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index f77e2599726..7bf03d54f8a 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:15\n"
+"PO-Revision-Date: 2019-04-12 12:39\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " Status"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " und"
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" in Projekten"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr "%{counter_storage} (%{counter_repositories} Repositories, %{counter_buil
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr "%{count} weitere Zugewiesene"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} Teilnehmer"
@@ -148,7 +173,7 @@ msgstr "%{filePath} gelöscht"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} mehr"
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr "%{group_docs_link_start}Gruppen%{group_docs_link_end} ermöglichen dir d
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} wird entfernt! Bist du sicher?"
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr "%{loadingIcon} Gestartet"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} ist durch Gitlab-Benutzer(in) %{lock_user_id} gesperrt"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Avatar von %{name}"
@@ -178,6 +212,15 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% abgeschlossen"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr "%{usage_ping_link_start}Erfahre mehr%{usage_ping_link_end} darüber, wel
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr "+ %{count} weitere"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} weitere"
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] "1 %{type} Änderung"
msgstr[1] "%{count} %{type} Änderungen"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 geschlossenes Ticket"
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] "1 geschlossener Merge-Request"
msgstr[1] "%d geschlossene Merge-Requests"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "Eine Gruppe"
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] "1 Benutzer(in)"
msgstr[1] "%d Benutzer(innen)"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "Erster Beitrag!"
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "Zwei-Faktor-Authentifizierung aktiviert"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr "Seite nicht gefunden"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "Bitte kontaktiere deine(n) GitLab-Administrator(in), wenn du denkst, dass dies ein Fehler ist."
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> fügt allen von johnsmith@example.com erstellten Tickets und Kommentaren \"Von <a href=\"#\">@johnsmith</a>\" hinzu und setzt <a href=\"#\">@johnsmith</a> als Zuständigen für alle Tickets die ursprünglich johnsmith@example.com zugewiesen waren."
@@ -352,8 +424,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "Ein 'Runner' ist ein Prozess, welcher ein Job ausführt. Du kannst so viele Runner erstellen wie du benötigst."
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Eine Sammlung von Graphen bezüglich kontinuierlicher Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr "Ein Default-Branch kann nicht für ein leeres Projekt ausgewählt werden."
@@ -364,18 +448,39 @@ msgstr "Ein(e) gelöschte(r) Benutzer(in)"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "Ein Mitglied des GitLab-Missbrauchsteams wird deinen Bericht so schnell wie möglich überprüfen."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "Ein neuer Branch wird in deinem Fork erzeugt und ein neuer Merge-Request wird gestartet."
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "In einem Projekt speicherst du deine Dateien (Repository), planst deine Arbeit (Tickets) und veröffentlichst deine Dokumentation (Wiki), %{among_other_things_link}."
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "Ein regulärer Ausdruck, um die Ausgabe der Testabdeckung in der Fehlerprotokollierung des Jobs zu finden. Zum Deaktivieren leer lassen"
msgid "A user with write access to the source branch selected this option"
msgstr "Ein(e) Benutzer(in) mit Schreibzugriff auf den Quellbranch hat diese Option ausgewählt"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "Ãœber GitLab"
@@ -406,12 +511,18 @@ msgstr "Akzeptierter MR"
msgid "Access Tokens"
msgstr "Zugangs-Token"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "Zugriff abgelehnt! Stelle sicher, dass du diesem Repository Bereitstellungsschlüssel hinzufügen kannst."
msgid "Access expiration date"
msgstr "Zugriff auf das Ablaufdatum"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "Zugriff auf '%{classification_label}' nicht erlaubt"
@@ -421,6 +532,12 @@ msgstr "Konto"
msgid "Account and limit"
msgstr "Konto und Limit"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Aktiv"
@@ -451,36 +568,63 @@ msgstr "Kubernetes-Cluster hinzufügen"
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "Füge zu deinem Wiki eine Startseite mit Informationen zu deinem GitLab-Project hinzu und GitLab wird sie hier anstatt dieser Meldung anzeigen."
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr "Tabelle hinzufügen"
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "Füge zusätzlichen Text hinzu, der in jeglicher E-Mail-Kommunikation angezeigt wird. Maximal %{character_limit} Zeichen"
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr "Jetzt kommentieren"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "Bildkommentar hinzufügen"
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr "Lizenz hinzufügen"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Neue Anwendung hinzufügen"
msgid "Add new directory"
msgstr "Erstelle eine neues Verzeichnis"
-msgid "Add projects"
-msgstr "Projekte hinzufügen"
-
msgid "Add reaction"
msgstr "Reaktion hinzufügen"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr "Füge Benutzer(innen) zur Gruppe hinzu"
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "Das Hinzufügen neuer Anwendungen ist in deiner GitLab-Instanz deaktiviert. Bitte kontaktiere deine(n) GitLab-Administrator(in), um die Berechtigung zu erhalten"
msgid "Additional text"
msgstr "Zusätzlicher Text"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "Adminbereich"
msgid "Admin Overview"
msgstr "Admin-Ãœbersicht"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "Du bist gerade dabei den/die Benutzer(in) %{username} dauerhaft zu löschen. Tickets, Merge-Requests und Gruppen, die mit ihm/ihr verknüpft sind, werden einem systemweiten \"Ghost-user\" zugewiesen. Um Datenverlust zu vermeiden, ziehe in Erwägung stattdessen die Funktion %{strong_start}Benutzer(in) blockieren%{strong_end} zu verwenden. Nachdem du %{strong_start}Benutzer(in) entfernen%{strong_end} ausgeführt hast, kann es nicht rückgängig gemacht oder der/die Benutzer(in) wiederhergestellt werden."
@@ -550,6 +703,9 @@ msgstr "Projekt löschen"
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Erweiterte Berechtigungen, Large File Storage und Einstellungen zur Zwei-Faktor-Authentifizierung."
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Erweiterte Einstellungen"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Alarm"
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr "Alle"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr "Alle Änderungen sind committed"
@@ -657,15 +825,33 @@ msgstr "Alle Features sind für leere Projekte, Vorlagen, oder beim Import aktiv
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "Alle Benutzer(innen)"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr "Erlaube Commits von Mitgliedern die zum Zielbranch mergen können."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr "Erlaube es Projekten in dieser Gruppe Git LFS zu verwenden"
@@ -678,6 +864,12 @@ msgstr "Rendering von PlantUML-Diagrammen in Asciidoc-Dokumenten erlauben."
msgid "Allow requests to the local network from hooks and services."
msgstr "Erlaube Anfragen an das lokale Netzwerk von Hooks und Diensten."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Erlaube es Benutzer(innen) Zugriff zu beantragen"
@@ -696,6 +888,9 @@ msgstr "Wird auch \"Herausgeber\" oder \"Relying-Party-Trust-Identifier\" genann
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "Wird auch \"Relying-Party-Service-URL\" oder \"Antwort-URL\" genannt"
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "Alternativ kannst du einen %{personal_access_token_link} verwenden. Wenn du deinen persönlichen Access-Token erzeugst, musst du den Gültigkeitsbereich <code>repo</code> auswählen, damit wir eine Liste deiner öffentlichen und privaten Repositories anzeigen können, die für die Verbindung verfügbar sind."
@@ -717,9 +912,6 @@ msgstr "Bei einem leeren GitLab-Benutzerfeld wird der vollständigen Namen des F
msgid "An error has occurred"
msgstr "Es ist ein Fehler aufgetreten"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Beim Hinzufügen eines Entwurfs zur Diskussion ist ein Fehler aufgetreten."
@@ -729,6 +921,15 @@ msgstr "Beim Hinzufügen eines neuen Entwurfs ist ein Fehler aufgetreten."
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr "Bei der Vorschau des Blobs ist ein Fehler aufgetreten"
@@ -741,18 +942,27 @@ msgstr "Beim Aktualisieren der Ticket-Gewichtung ist ein Fehler aufgetreten"
msgid "An error occurred while adding approver"
msgstr "Beim Hinzufügen eines Genehmigungsberechtigten ist ein Fehler aufgetreten"
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "Beim Löschen des Kommentars ist ein Fehler aufgetreten"
msgid "An error occurred while detecting host keys"
msgstr "Bei der Erkennung von Hostschlüsseln ist ein Fehler aufgetreten"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Beim Ausblenden des Alarms ist ein Fehler aufgetreten. Aktualisiere die Seite und versuche es erneut."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "Beim Ausblenden des Feature-Highlights ist ein Fehler aufgetreten. Aktualisiere die Seite und versuche es erneut auszublenden."
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "Beim Abrufen der Markdown-Vorschau ist ein Fehler aufgetreten"
@@ -765,6 +975,9 @@ msgstr "Während des Abrufens der Daten für die Seitenleiste ist ein Fehler auf
msgid "An error occurred while fetching stages."
msgstr "Beim Abrufen der Phasen ist ein Fehler aufgetreten."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "Beim Abrufen des Jobprotokolls ist ein Fehler aufgetreten."
@@ -777,6 +990,9 @@ msgstr "Beim Abrufen der Jobs ist ein Fehler aufgetreten."
msgid "An error occurred while fetching the pipeline."
msgstr "Beim Abrufen der Pipeline ist ein Fehler aufgetreten."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "Beim Abrufen der Projekte ist ein Fehler aufgetreten"
@@ -792,6 +1008,9 @@ msgstr "Beim Laden der Diagrammdaten ist ein Fehler aufgetreten"
msgid "An error occurred while loading commit signatures"
msgstr "Beim Laden der Commit-Signaturen ist ein Fehler aufgetreten"
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "Beim Laden des Diffs ist ein Fehler aufgetreten"
@@ -834,12 +1053,18 @@ msgstr "Beim Speichern des LDAP-Ãœberschreibungsstatus ist ein Fehler aufgetrete
msgid "An error occurred while saving assignees"
msgstr "Beim Speichern der Zuweisungen ist ein Fehler aufgetreten"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "Beim Abonnieren von Benachrichtigungen ist ein Fehler aufgetreten."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Beim Abbestellen der Benachrichtigungen ist ein Fehler aufgetreten."
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "Beim Aktualisieren des Kommentars ist ein Fehler aufgetreten"
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr "Analysen"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Anonym"
@@ -909,15 +1137,33 @@ msgstr "Irgendein"
msgid "Any Label"
msgstr "Beliebiges Label"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Aussehen"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Anwendung"
msgid "Application ID"
msgstr "Anwendungs-ID"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Anwendung: %{name}"
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr "Apr"
msgid "April"
msgstr "April"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "Archiviertes Projekt! Repository und andere Projektressourcen sind schreibgeschützt"
msgid "Archived projects"
msgstr "Archivierte Projekte"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Bist Du sicher, dass Du diesen Pipeline-Zeitplan löschen möchtest?"
@@ -966,15 +1265,24 @@ msgstr "Bist du sicher, dass du alle nicht gespeicherten Änderungen verwerfen m
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "Möchtest du den öffentlichen Schlüssel wirklich neu generieren? Du musst dann den öffentlichen Schlüssel auf dem Remote-Server aktualisieren, bevor die Spiegelung wieder funktioniert."
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Bist du sicher, dass du %{group_name} wirklich entfernen willst?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr "Bist du sicher, dass du diese Identität entfernen willst?"
msgid "Are you sure you want to reset registration token?"
msgstr "Bist du sicher, dass du den Registrierungstoken zurücksetzen willst?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Bist du sicher, dass du den Health-Check-Token zurücksetzen willst?"
@@ -1041,9 +1352,6 @@ msgstr "Zugewiesene Tickets"
msgid "Assigned Merge Requests"
msgstr "Zugewiesene Merge-Requests"
-msgid "Assigned to :name"
-msgstr "Zugewiesen an :name"
-
msgid "Assigned to me"
msgstr "Mir zugewiesen"
@@ -1059,6 +1367,9 @@ msgstr "Zuweisungslisten zeigen alle dem ausgewählten Benutzer zugewiesenen Tic
msgid "Assignee(s)"
msgstr "Zugewiesene Personen"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,18 +1385,24 @@ msgstr "Aug"
msgid "August"
msgstr "August"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Authentifizierungsprotokoll"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "Authentifizierungsprotokoll"
msgid "Authentication method"
msgstr "Authentifizierungsmethode"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Autor(in)"
@@ -1104,6 +1421,9 @@ msgstr "Autorisieren"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "Autorisierst du %{link_to_client} dein Konto zu verwenden?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "Autorisiert bei"
@@ -1143,18 +1463,12 @@ msgstr "Erfahre mehr in der %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Die Auto-DevOps-Pipeline wurde aktiviert und wir verwendet, falls keine alternative CI-Konfigurationsdatei gefunden wurde. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "Du kannst deine Anwendung automatisiert erstellen und testen, wenn du für dieses Projekt %{link_to_auto_devops_settings}. Du kannst sie auch automatisch bereitstellen, wenn du %{link_to_add_kubernetes_cluster}."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "Ein Kubernetes-Cluster hinzufügen"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "aktiviere Auto-DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "Automatisch als interne(r) Standardbenutzer(in) gekennzeichnet"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "Automatically resolved"
msgstr ""
@@ -1278,6 +1592,12 @@ msgstr "Review verwerfen?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "Du bist dabei deinen Review zu verwerfen und damit alle ausstehenden Kommentare zu löschen. Die gelöschten Kommentare können %{strong_start}nicht%{strong_end} wiederhergestellt werden."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "Mit dem ausgewählten Commit beginnen"
@@ -1515,6 +1835,12 @@ msgstr "Projekteinstellungen"
msgid "Branches|protected"
msgstr "geschützt"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Verzeichnis durchsuchen"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr "Business-Metriken (benutzerdefiniert)"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "von"
@@ -1557,6 +1892,9 @@ msgstr "CI/CD-Einstellungen"
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr "CI wird mit den oben angegebenen Anmeldeinformationen ausgeführt."
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "Standard für Auto-DevOps-Pipeline"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "Bereitstellungsstrategie"
@@ -1605,6 +1946,9 @@ msgstr "Die Auto-DevOps-Pipeline wird ausgeführt, wenn keine alternative CI-Kon
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "Instanz aktiviert"
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr "HEAD-Commit kann für diesen Branch nicht gefunden werden"
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,21 +1979,36 @@ msgstr "Job abbrechen"
msgid "Cannot be merged automatically"
msgstr "Kann nicht automatisch zusammengeführt werden"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Kann verwalteten Kubernetes-Cluster nicht ändern"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Zertifikat-Fingerabdruck"
-
msgid "Change Weight"
msgstr "Gewichtung ändern"
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1671,6 +2033,9 @@ msgstr "Wiederherstellen "
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Dies wird einen neuen Commit erzeugen, um die vorhandenen Änderungen rückgängig zu machen."
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "Änderungen werden angezeigt, als ob die <b>Quell</b>-Revision in die <b>Ziel</b>-Revision gemerged wurde."
@@ -1683,6 +2048,12 @@ msgstr "Diagramme"
msgid "Chat"
msgstr "Chat"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "Lies die %{docs_link_start}Dokumentation%{docs_link_end}."
@@ -1710,9 +2081,6 @@ msgstr "Wähle <strong>Archiv erstellen</strong> und warte, bis die Archivierung
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "Wähle <strong>Nächste</strong> unten auf der Seite."
-msgid "Choose File ..."
-msgstr "Datei auswählen ..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Wähle einen Branch/Tag (z. B. %{master}) oder gib einen Commit ein (z. B. %{sha}), um zu sehen, was geändert wurde oder um einen Merge-Request zu erstellen."
@@ -1734,27 +2102,27 @@ msgstr "Wähle eine Farbe."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "Wähle zwischen <code>clone</code> oder <code>fetch</code>, um den aktuellen Anwendungscode zu erhalten"
-msgid "Choose file..."
-msgstr "Datei auswählen..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "Wähle die übergeordnetste Gruppe für deine Repository-Importe."
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "Wähle die Gruppen aus, die du mit diesem sekundären Knoten synchronisieren möchtest."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Wähle welche Repositories du verbinden und die CI/CD-Pipelines ausführen möchtest."
-msgid "Choose which repositories you want to import."
-msgstr "Wähle aus, welche Repositories du importieren möchtest."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "Wähle die Fragmente aus, die du mit diesem sekundären Knoten synchronisieren möchtest."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "abgebrochen"
@@ -1779,6 +2147,9 @@ msgstr "mit Warnungen bestanden"
msgid "CiStatusLabel|pending"
msgstr "ausstehend"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "übersprungen"
@@ -1812,6 +2183,9 @@ msgstr "bestanden"
msgid "CiStatusText|pending"
msgstr "ausstehend"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "übersprungen"
@@ -1827,6 +2201,9 @@ msgstr "Variablenwert eingeben"
msgid "CiVariables|Remove variable row"
msgstr "Variable jetzt löschen"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (Alle Umgebungen)"
@@ -1839,6 +2216,9 @@ msgstr "Wildcard erstellen"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr "Neue Umgebung"
@@ -1848,12 +2228,18 @@ msgstr "Geschützt"
msgid "CiVariable|Search environments"
msgstr "Umgebungen durchsuchen"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Schutzstatus umschalten"
msgid "CiVariable|Validation failed"
msgstr "Validierung fehlgeschlagen"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "ist nicht verfügbar: %{reason}"
@@ -1911,6 +2297,9 @@ msgstr "Repository klonen"
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr "Geschlossen"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Geschlossene Tickets"
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} wurde erfolgreich auf deinem Kubernetes-Cluster installiert"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} Hierdurch werden einige zusätzliche Ressourcen wie ein Load-Balancer hinzugefügt, was je nach Hosting-Provider, auf dem dein Kubernetes-Cluster installiert ist, zusätzliche Kosten verursachen kann. Wenn du Google Kubernetes-Engine verwendest, kannst du %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API-URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Kubernetes-Cluster hinzufügen"
@@ -1968,18 +2354,15 @@ msgstr "Wenn du deiner Gruppe eine Integration hinzufügst, wird das Cluster von
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Erweiterte Optionen für die Integration dieses Kubernetes-Clusters"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "Nachdem Ingress installiert wurde, solltest du deine Wildcard-DNS an die generierte externe IP-Adresse anpassen, um deine App nach der Bereitstellung sichtbar zu machen. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Beim Abrufen der Projektzonen ist ein Fehler aufgetreten: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Ein Fehler ist bei dem Verbindungsversuch mit der Google Cloud API aufgetreten. Bitte versuche es später erneut."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -2016,6 +2399,12 @@ msgstr "Wähle, welche Anwendungen auf deinem Kubernetes Cluster installiert wer
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Wähle, welche deiner Umgebungen du für dieses Cluster verwenden willst."
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,20 +2414,20 @@ msgstr "API-URL kopieren"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "CA-Zertifikat kopieren"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Ingress-IP-Adresse in die Zwischenablage kopieren"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Jupyter-Hostnamen in die Zwischenablage kopieren"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Kubernetes-Clusternamen kopieren"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Token kopieren"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Kubernetes-Cluster erstellen"
@@ -2085,8 +2474,8 @@ msgstr "GitLab-Integration"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "Gitlab Runner verbindet sich mit dem Repository für dieses Projekt und führt CI/CD-Jobs aus, ruft die Ergebnisse ab und veröffentlicht die Anwendung zur Produktionsumgebung."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Google Cloud Platform-Projekt"
@@ -2112,14 +2501,14 @@ msgstr "Ausblenden"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Wenn du mehrere Cluster einrichtest und Auto-DevOps verwendest, lies zuerst %{help_link_start}dies%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Um den Zustand des Clusters anzuzeigen, müssen wir dein Cluster mit Prometheus bereitstellen, um die erforderlichen Daten zu sammeln."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress-IP-Adresse"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress bietet die Möglichkeit, Dienste-Anfragen je nach Anfragehost oder -pfad weiterzuleiten. So werden viele Dienste an einer Stelle zentral zusammengefasst."
@@ -2127,15 +2516,18 @@ msgstr "Ingress bietet die Möglichkeit, Dienste-Anfragen je nach Anfragehost od
msgid "ClusterIntegration|Install"
msgstr "Installieren"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Prometheus installieren"
-
msgid "ClusterIntegration|Installed"
msgstr "Installiert"
msgid "ClusterIntegration|Installing"
msgstr "Installiere"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Kubernetes-Cluster-Automatisierung integrieren"
@@ -2163,7 +2555,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Knative Domainname:"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr "Kubernetes-Cluster"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Kubernetes-Cluster-Details"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Zustand des Kubernetes-Cluster"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes-Cluster wird mit der Google Kubernetes-Engine erstellt..."
@@ -2220,9 +2609,6 @@ msgstr "Manage"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Verwalte dein Kubernetes-Cluster hier: %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Mehr Informationen"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Keine Maschinentypen stimmen mit deiner Suche überein"
@@ -2235,27 +2621,24 @@ msgstr "Keine Projekte entsprechen deiner Suche"
msgid "ClusterIntegration|No zones matched your search"
msgstr "Keine Zonen entsprechen deiner Suche"
-msgid "ClusterIntegration|Note:"
-msgstr "Hinweis:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Anzahl der Knoten"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Bitte gib die Zugangsinformationen deines Kubernetes-Clusters ein. Wenn du Hilfe benötigst, kannst du unsere %{link_to_help_page} zu Kubernetes lesen"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Bitte stelle sicher, dass dein Google-Konto die folgenden Voraussetzungen erfüllt:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "Verweise mit einem Wildcard-DNS auf diese generierte IP-Adresse um auf deine Anwendung zugreifen zu können, nachdem sie bereitgestellt wurde."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "Projektcluster"
-msgid "ClusterIntegration|Project namespace"
-msgstr "Projekt-Namensraum"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "Projekt-Namensraum (optional, einzigartig)"
@@ -2286,7 +2669,7 @@ msgstr "Ersetze dies mit deinem eigenen Hostnamen wenn du möchtest. Wenn du die
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Anfrage zur Installation fehlgeschlagen"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,8 +2705,11 @@ msgstr "Wähle eine Zone aus"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Wähle eine Zone aus, um den Maschinentyp auszuwählen"
-msgid "ClusterIntegration|Service token"
-msgstr "Servicetoken"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Zeige"
@@ -2331,9 +2717,6 @@ msgstr "Zeige"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Auf unserer Seite ist etwas schief gelaufen."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Beim Erstellen deines Kubernetes-Clusters auf der Google Kubernetes Engine ist etwas schief gelaufen"
@@ -2343,8 +2726,8 @@ msgstr "Beim Installieren von %{title} ist ein Fehler aufgetreten"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "Die IP-Adresse wird gerade zugewiesen. Wenn es lange dauert, überprüfe bitte dein Kubernetes-Cluster oder dein Kontingent für die Google Kubernetes-Engine."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "Dieses Konto benötigt die Berechtigungen um ein Kubernetes-Cluster in dem untenstehenden %{link_to_container_project} zu erstellen"
@@ -2352,11 +2735,17 @@ msgstr "Dieses Konto benötigt die Berechtigungen um ein Kubernetes-Cluster in d
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "Mit dieser Option kannst du Anwendungen auf RBAC-Clustern installieren."
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Kubernetes-Cluster umschalten"
-msgid "ClusterIntegration|Token"
-msgstr "Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2394,9 +2783,6 @@ msgstr "Zone"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "Zugriff zur Google Kubernetes-Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "Unsere Preise findest du hier"
-
msgid "ClusterIntegration|documentation"
msgstr "Dokumentation"
@@ -2406,6 +2792,9 @@ msgstr "Hilfeseite"
msgid "ClusterIntegration|meets the requirements"
msgstr "erfüllt die Anforderungen"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "ordnungsgemäß konfiguriert"
@@ -2415,15 +2804,27 @@ msgstr "Registrierung"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr "Code-Besitzer(innen)"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Kohorten"
msgid "Collapse"
msgstr "Reduzieren"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "Seitenleiste einklappen"
@@ -2519,6 +2920,9 @@ msgstr "Committed von"
msgid "Commit…"
msgstr "Commit…"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Vergleichen"
@@ -2588,6 +2992,12 @@ msgstr "Die %{link}-Integration konfigurieren."
msgid "Configure the way a user creates a new account."
msgstr "Konfiguriere, wie ein(e) Benutzer(in) ein neues Konto erstellt."
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Verbinden"
@@ -2603,12 +3013,24 @@ msgstr "Verbinde deine externen Repositories, und CI/CD-Pipelines werden für ne
msgid "Connecting..."
msgstr "Verbinden..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "Container-Registry"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Erstellt"
@@ -2672,6 +3094,9 @@ msgstr "Trage etwas zu GitLab bei"
msgid "Contribution"
msgstr "Beitrag"
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr "Bitte warte einen Moment, diese Seite wird automatisch aktualisiert, wen
msgid "Control the display of third party offers."
msgstr "Steuern Sie die Anzeige von Angeboten Dritter."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Kontrolliere die maximale Parallelität des LFS/Anhangs-Backfills für diesen sekundären Knoten"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "Steuere die maximale Parallelität des Repository-Backfills für diesen sekundären Knoten"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "Steuere die maximale Parallelität von Verifizierungsoperationen für diesen Geo-Knoten"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "ConvDev-Index"
@@ -2723,6 +3139,9 @@ msgstr "Kopiere %{protocol} clone-URL"
msgid "Copy ID to clipboard"
msgstr "ID in Zwischenablage kopieren"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "Kopiere SSH clone-URL"
@@ -2747,9 +3166,6 @@ msgstr "Kopiere Commit SHA in die Zwischenablage"
msgid "Copy file path to clipboard"
msgstr "Dateipfad in Zwischenablage kopieren"
-msgid "Copy incoming email address to clipboard"
-msgstr "Eingehende E-Mail-Adresse in Zwischenablage kopieren"
-
msgid "Copy link"
msgstr "Link kopieren"
@@ -2765,9 +3181,33 @@ msgstr "In Zwischenablage kopieren"
msgid "Copy token to clipboard"
msgstr "Token in Zwischenablage kopieren"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "Der Status der Pipeline konnte nicht abgerufen werden. Zur Fehlerbehebung lies die %{linkStart}Dokumentation.%{linkEnd}"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr "Erstellen"
@@ -2777,12 +3217,12 @@ msgstr "Erstelle neues Verzeichnis"
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
msgid "Create a new branch"
msgstr "Erstelle einen neuen Branch"
-msgid "Create a new branch and merge request"
-msgstr "Erstelle einen neuen Branch und Merge-Request"
-
msgid "Create a new issue"
msgstr "Neues Ticket erstellen"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Erstelle einen persönlichen Zugriffstoken in deinem Konto um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr "Branch erstellen"
@@ -2816,9 +3259,6 @@ msgstr "Gruppe erstellen"
msgid "Create group label"
msgstr "Gruppen-Label erstellen"
-msgid "Create issue"
-msgstr "Ticket erstellen"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Erstelle Listen anhand von Labels. Tickets mit diesem Label werden in dieser Liste angezeigt."
@@ -2888,6 +3328,9 @@ msgstr "Cron-Syntax"
msgid "Current Branch"
msgstr "Aktueller Branch"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "Aktueller Knoten"
@@ -2927,9 +3370,6 @@ msgstr "Passe an, wie E-Mail-Adressen und Benutzernamen von Google Code in GitLa
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Passe deine Pipeline-Konfiguration an und zeige deinen Pipeline-Status und den Abdeckungsbericht an."
@@ -2972,6 +3412,9 @@ msgstr "Alle"
msgid "DashboardProjects|Personal"
msgstr "Persönliche"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr "Ablehnen und abmelden"
msgid "Default Branch"
msgstr "Default-Branch"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "Standardklassifikationslabel"
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr "Standard: Importiere direkt die Google Code-E-Mail-Adresse oder den Nutzernamen"
@@ -3044,6 +3496,9 @@ msgstr "Codeausschnitt löschen"
msgid "Delete comment"
msgstr "Kommentar entfernen"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Liste löschen"
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr "Gelöscht"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "Verweigern"
@@ -3067,6 +3537,9 @@ msgstr[1] "Bereitstellungen"
msgid "Deploy Keys"
msgstr "Bereitstellungsschlüssel"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} andere"
@@ -3205,12 +3678,45 @@ msgstr "Absteigend"
msgid "Description"
msgstr "Beschreibung"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "Beschreibungsvorlagen ermöglichen dir, kontextspezifische Vorlagen für das Beschreibungsfeld von Tickets und Merge-Requests für dein Projekts zu definieren."
msgid "Description:"
msgstr "Beschreibung:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "Löschen"
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr "Verwerfen"
@@ -3328,20 +3837,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "TAR-Datei herunterladen"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "TAR.BZ2-Datei herunterladen"
+msgstr ""
msgid "Download tar.gz"
-msgstr "TAR.GZ-Datei herunterladen"
+msgstr ""
msgid "Download zip"
-msgstr "ZIP-Datei herunterladen"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Herunterladen"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "E-Mail-Patch"
@@ -3373,6 +3888,9 @@ msgstr "Bearbeiten"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr "Label bearbeiten"
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr "Bearbeite Dateien im Editor und committe die Änderungen hier"
@@ -3406,18 +3927,54 @@ msgstr "Identität für %{user_name} bearbeiten"
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Elasticsearch-Integration. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "E-Mail"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "E-Mail-Patch"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "E-Mails"
@@ -3433,6 +3990,9 @@ msgstr "Aktivieren"
msgid "Enable Auto DevOps"
msgstr "Auto-DevOps aktivieren"
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr "Aktiviere die Pseudonymizer-Datensammlung"
@@ -3460,6 +4020,12 @@ msgstr "Für dieses Projekt aktivieren"
msgid "Enable group Runners"
msgstr "Gruppen-Runner aktivieren"
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "Aktiviere oder deaktiviere die Pseudonymizer-Datensammlung."
@@ -3469,9 +4035,6 @@ msgstr "Aktiviere oder deaktiviere die Versionsprüfung und den Nutzungsbericht.
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "Aktiviere reCAPTCHA oder Akismet und lege die IP-Grenzwerte fest."
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr "Nutzungsbericht einschalten"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "Nutzungsbericht einschalten um einen Ãœberblick zu erhalten, welche Features von GitLab wie benutzt werden."
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Aktiviert"
-msgid "Ends at (UTC)"
-msgstr "Endet am (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr "Endet am (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr "Gib die Beschreibung des Merge-Requests ein"
msgid "Enter the merge request title"
msgstr "Gib den Titel des Merge-Requests ein"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr "Beim Abrufen der Umgebungen ist ein Fehler aufgetreten."
msgid "Environments|An error occurred while making the request."
msgstr "Während der Anfrage ist ein Fehler aufgetreten."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "Beim Anhalten der Umgebung ist ein Fehler aufgetreten. Bitte versuche es erneut"
@@ -3592,15 +4167,33 @@ msgstr "Öffne Live-Umgebung"
msgid "Environments|Pod logs from"
msgstr "Pod-Protokolle von"
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr "Erneute Bereitstellung in der Umgebung"
msgid "Environments|Read more about environments"
msgstr "Lies mehr über Umgebungen"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr "Umgebung wiederherstellen"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Alle anzeigen"
@@ -3613,6 +4206,18 @@ msgstr "Umgebung stoppen"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Aktualisiert"
@@ -3664,6 +4269,9 @@ msgstr "Fehlerberichterstattung und Protokollierung"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr "Fehler beim Erstellen des Epics"
@@ -3712,6 +4320,18 @@ msgstr "Fehler beim Laden der Vorlage."
msgid "Error occurred when toggling the notification subscription"
msgstr "Beim Umschalten des Benachrichtigungs-Abonnements trat ein Fehler auf"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr "Fehler beim Aktualisieren des Status für alle To-dos."
msgid "Error updating todo status."
msgstr "Fehler beim Aktualisieren des To-do-Status."
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr "Fehler beim Laden des Merge-Requests. Bitte versuche es erneut."
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr "Aufklappen"
msgid "Expand all"
msgstr "Alle erweitern"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Menüleiste ausklappen"
msgid "Expiration date"
msgstr "Ablaufdatum"
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr "Externe Autorisierung der Klassifikationsrichtlinie"
@@ -3868,9 +4530,6 @@ msgstr "Externe Autorisierung verbietet den Zugriff auf dieses Projekt"
msgid "External authorization request timeout"
msgstr "Zeitüberschreitung für externe Autorisierungsanfrage"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "Klassifizierungslabel"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "Klassifizierungslabel"
@@ -3892,13 +4551,19 @@ msgstr "Wechsel des/der Besitzers/-in fehlgeschlagen"
msgid "Failed to check related branches."
msgstr "Verwandte Branches konnten nicht überprüft werden."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "Bereitstellung fehlgeschlagen für"
msgid "Failed to load emoji list."
msgstr "Fehler beim Laden der Emoji-Liste."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr "Fehler beim Entfernen des Mirrors."
msgid "Failed to remove the pipeline schedule"
msgstr "Entfernung der Pipelineplanung fehlgeschlagen"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Signieren mit Smartcard-Authentifizierung fehlgeschlagen"
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr "Fehlschlag"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "Schneller als wenn es den Projektarbeitsplatz wieder benützt (fällt zurück auf den Clone wenn es nicht existiert)"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "Feature Flags"
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "Beschreibung"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "%{feature_flag_name} bearbeiten"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "Feature-Flag bearbeiten"
@@ -4024,8 +4707,8 @@ msgstr "Neu"
msgid "FeatureFlags|New Feature Flag"
msgstr "Neues Feature-Flag"
-msgid "FeatureFlags|Save changes"
-msgstr "Änderungen speichern"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "Status"
@@ -4051,6 +4734,18 @@ msgstr "Feb"
msgid "February"
msgstr "Februar"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "Felder auf dieser Seite sind jetzt nicht mehr editierbar, du kannst nun konfigurieren"
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr "Filter..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Finde über den Pfad"
@@ -4131,6 +4823,9 @@ msgstr "Finde die heruntergeladene ZIP-Datei und dekomprimiere sie."
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Suche die neu extrahierte Datei <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "Fingerabdrücke"
@@ -4224,14 +4919,20 @@ msgstr ""
msgid "Format"
msgstr "Format"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "Fehler in deiner .gitlab-ci.yml gefunden:"
msgid "Free Trial of GitLab.com Gold"
msgstr "Kostenlose Testversion von GitLab.com Gold"
-msgid "From %{provider_title}"
-msgstr "Von %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "Von Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr "Von Meilensteinen:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Installiere den Runner aus der Anwendungsliste in der Kubernetes-Cluster Detailansicht"
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG-Schlüssel"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "Allgemein"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr "Allgemeine Pipelines"
@@ -4275,6 +4985,9 @@ msgstr "Generiere einen Standardsatz von Labels"
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr "Geo"
@@ -4299,9 +5012,6 @@ msgstr "Daten sind veraltet von %{timeago}"
msgid "GeoNodes|Data replication lag"
msgstr "Datenreplikationsverzögerung"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "Durch das Deaktivieren eines Knotens wird der Synchronisierungsprozess gestoppt. Bist du sicher?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "Stimmt nicht mit der primären Speicherkonfiguration überein"
@@ -4320,6 +5030,9 @@ msgstr "Die GitLab Version stimmt nicht mit der Version des primären Knotens ü
msgid "GeoNodes|Health status"
msgstr "Systemzustand"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "Letzte Event-ID, die vom Cursor verarbeitet wurde"
@@ -4365,6 +5078,9 @@ msgstr "Keine Prüfsumme"
msgid "GeoNodes|Out of sync"
msgstr "Nicht synchron"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr "Alle Projekte sind für eine erneute Synchronisierung eingeplant"
msgid "Geo|Batch operations"
msgstr "Stapelverarbeitung"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "Tracking-Eintrag für ein vorhandenes Projekt konnte nicht entfernt werden."
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "Fehlgeschlagen"
@@ -4491,6 +5222,9 @@ msgstr "Gruppen für die Synchronisierung"
msgid "Geo|In sync"
msgstr "Wird synchronisiert"
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr "Ausstehende Synchronisation"
msgid "Geo|Pending verification"
msgstr "Ausstehende Überprüfung"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "Projekt (ID: %{project_id}) existiert nicht mehr in der Primärdatenbank. Es ist sicher, diesen Eintrag zu entfernen, da dadurch keine Daten auf der Festplatte gelöscht werden."
@@ -4560,24 +5300,45 @@ msgstr "Wiederholversuch"
msgid "Geo|Select groups to replicate."
msgstr "Wähle zu replizierende Gruppen aus."
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "Zu synchronisierende Fragmente"
msgid "Geo|Status"
msgstr "Status"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Synchronisiert"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "Synchronisierung fehlgeschlagen - %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "Tracking-Eintrag für Projekt (%{project_id}) wurde erfolgreich entfernt."
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "Tracking-Eintrag wird entfernt. Bist du sicher?"
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "Unbekannter Status"
@@ -4596,9 +5357,6 @@ msgstr "Du befindest dich auf einem sekundären, <b>read-only</b> Geo-Knoten. Um
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "Du befindest dich auf einem sekundären, <b>read-only</b> Geo-Knoten. Möglicherweise kannst du nur eine begrenzte Anzahl an Änderungen oder Aktionen auf dieser Seite durchführen."
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "Du benötigst eine andere Lizenz, um die Geo-Replikation zu verwenden"
-
msgid "Geo|misconfigured"
msgstr "falsch konfiguriert"
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr "GitHub-Import"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter wurde verschoben"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr "GitLab-Nutzer(in)"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "GitLab-Projekt exportieren"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "GitLab Single Sign On-URL"
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr "Zurück"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "Zurück"
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr "Gehe zu"
msgid "Go to %{link_to_google_takeout}."
msgstr "Gehe zu %{link_to_google_takeout}."
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr "Google Code-Import"
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr "Diagramm"
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "Gruppe"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "Gruppen-CI/CD-Einstellungen"
@@ -4734,6 +5522,9 @@ msgstr "Gruppen-Git-LFS-Status:"
msgid "Group ID"
msgstr "Gruppen-ID"
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "Gruppen Runner"
@@ -4767,6 +5558,9 @@ msgstr "Gruppenname"
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr "Entschuldigung, keine Epics stimmen mit deiner Suche überein"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "Die Roadmap zeigt den Fortschritt deiner Epics anhand einer Zeitleiste an"
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "Bis %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "Badges"
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr "Deine Gruppen-Badges anpassen."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "Erfahre mehr über Badges."
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Freigabe eines Projekts innerhalb %{group} mit anderen Gruppen verhindern"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "Diese Einstellung wird auf %{ancestor_group} angewendet und wurde für diese Untergruppe überschrieben."
@@ -4908,6 +5783,9 @@ msgstr "Nach Namen suchen"
msgid "Have your users email"
msgstr "Lasse deine Nutzer an diese Adresse mailen"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "Kopfzeilennachricht"
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "Manuelle Eingabe der Host-Tasten ausblenden"
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr "Nutzdaten ausblenden"
@@ -4958,12 +5839,27 @@ msgstr[1] "Werte ausblenden"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "Verlauf"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Bereinigung erfolgreich gestartet"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr "Identifikator"
msgid "Identities"
msgstr "Identitäten"
-msgid "Identity provider single sign on URL"
-msgstr "Single Sign On-URL des Identitätsanbieters"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "Wenn diese Option deaktiviert ist, wird ein abweichender lokaler Branch nicht automatisch mit Commits von seinem remote Gegenstück aktualisiert, um lokalen Datenverlust zu verhindern. Wenn der Standardbranch (%{default_branch}) abweicht und nicht aktualisiert werden kann, schlägt die Spiegelung fehl. Andere abweichende Branches werden ohne Meldung ignoriert."
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "Wenn deaktiviert, hängt der Zugriffslevel von den Berechtigungen des/der Benutzers/Benutzerin im Projekts ab."
@@ -5042,9 +5941,6 @@ msgstr "Wenn diese Option aktiviert ist, wird der Zugriff auf Projekte mit einem
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "Wenn du GitHub verwendest, siehst du den Pipeline-Status für deine Commits und Pull-Anfragen, auf GitHub. %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "Wenn du bereits Dateien hast, kannst du diese pushen in dem du den %{link_to_cli} unten verwendest."
-
msgid "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>."
msgstr "Wenn dein HTTP-Repository nicht öffentlich zugänglich ist, füge der URL Authentifizierungsinformationen hinzu: <code>https://benutzername:password@gitlab.company.com/group/project.git</code>."
@@ -5129,12 +6025,30 @@ msgstr "Repository importieren"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "Verbinde Repositories von"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "Verbessere Ticketboards mit der GitLab Enterprise Edition."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "Verbessere das Ticketmanagement mit Ticketgewichtung und der Gitlab Enterprise Edition."
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "Im nächsten Schritt kannst du die Projekte auswählen, die du importieren möchtest."
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "Füge Nutzungsbedingungen und eine Datenschutzrichtlinie hinzu, die alle Benutzer(innen) akzeptieren müssen."
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "Füge bei Bedarf den Benutzernamen in die URL ein: <code>https://benutzername@gitlab.company.com/group/project.git</code>."
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr "Inkompatibles Projekt"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "Gibt an, ob dieser Runner Jobs ohne Tags auswählen kann"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "Inline"
@@ -5183,9 +6109,18 @@ msgstr "Gib die Host-Schlüssel manuell ein"
msgid "Input your repository URL"
msgstr "Gib deine Repository-URL ein"
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr "Installiere einen GitLab Runner"
@@ -5206,6 +6141,9 @@ msgstr "Sichtbarkeit der Instanzstatistiken"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "Instanz unterstützt nicht mehrere Kubernetes-Cluster"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "Integrationen"
@@ -5236,6 +6174,21 @@ msgstr "Arbeitsablaufsanalysen vorgestellt"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Ticket"
@@ -5266,6 +6222,9 @@ msgstr "Ticket-Board Fokusmodus"
msgid "Issue events"
msgstr "Ticketereignisse"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "Board"
@@ -5296,8 +6255,11 @@ msgstr "Tickets können Bugs, Tasks oder zu diskutierende Ideen sein. Tickets si
msgid "Issues closed"
msgstr "Geschlossene Tickets"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "Tickets, Merge-Requests, Pushes und Kommentare."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr ""
@@ -5341,12 +6303,30 @@ msgstr "Januar"
msgid "Job"
msgstr "Job"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "Job wurde gelöscht"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr "Job wurde gelöscht von"
msgid "Job|Keep"
msgstr "Behalten"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "Zum Seitenende scrollen"
@@ -5392,9 +6375,21 @@ msgstr "Die Artefakte wurden entfernt"
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "Juli"
@@ -5437,9 +6432,15 @@ msgstr "Kubernetes konfiguriert"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "Die Kubernetes-Service-Integration wurde eingestellt. %{deprecated_message_content} deine Kubernetes-Cluster mit der neuen Seite <a href=\"%{url}\"/>Kubernetes-Cluster</a>"
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Deaktiviert"
@@ -5455,6 +6456,15 @@ msgstr "Dropdown-Menü für die Label-Aktionen"
msgid "Label lists show all issues with the selected label."
msgstr "Labellisten zeigen alle Tickets mit dem gewählten Label."
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} weitere"
@@ -5485,6 +6495,9 @@ msgstr "Label hochstufen"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "%{labelTitle} hochzustufen macht es für alle Projekte in %{groupName} verfügbar. Existierende Projektlabels mit dem selben Titel werden zusammengeführt. Diese Aktion kann nicht rückgängig gemacht werden."
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr "Large File Storage"
@@ -5556,18 +6569,18 @@ msgstr "Erfahre mehr über Kubernetes"
msgid "Learn more about Web Terminal"
msgstr ""
+msgid "Learn more about approvals."
+msgstr ""
+
msgid "Learn more about custom project templates"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about signing commits"
msgstr ""
-msgid "Learn more about protected branches"
-msgstr "Erfahre mehr über geschützte Branches"
-
msgid "Learn more in the"
msgstr "Erfahre mehr in den"
@@ -5577,6 +6590,9 @@ msgstr "Pipelineplanungsdokumentation"
msgid "Leave"
msgstr "Verlassen"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Verlasse die Gruppe"
@@ -5661,9 +6677,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "Du bist im Begriff, die Lizenz %{name} aus diesem Projekt zu entfernen."
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "Lizenzen"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Anzeige beschränkt auf maximal %d Ereignis"
@@ -5681,6 +6703,9 @@ msgstr "Liste deine Gitea-Repositories auf"
msgid "List available repositories"
msgstr "Verfügbare Repositories auflisten"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "Listenansicht"
@@ -5738,15 +6763,36 @@ msgstr "Sperrungen bieten die Möglichkeit, bestimmte Dateien oder Ordner zu spe
msgid "Login with smartcard"
msgstr "Mit Smartcard einloggen"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Protokolle"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Mache alle in deinem Team unabhängig von ihrem Standort produktiver. GitLab Geo erstellt schreibgeschützte Mirror deiner GitLab-Instanz, sodass du die Zeit zum Klonen und Abrufen großer Repos reduzieren kannst."
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "Stelle sicher, dass du mit dem Konto angemeldet bist, welches Besitzer der zu importierenden Projekte ist."
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr "Verwalte Git-Repositories mit detaillierten Zugriffskontrollen, die deinen Code schützen. Führe Codeüberprüfungen durch und verbessere die Zusammenarbeit mit Merge-Requests. Jedes Projekt kann auch ein Ticketsystem und ein Wiki haben."
@@ -5777,15 +6823,15 @@ msgstr "Projektlabel verwalten"
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "Verwalte die Mitgliedschaft deiner Gruppe, während du mit SAML eine weitere Sicherheitsstufe hinzufügst."
-
msgid "Manifest"
msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifestdateiimport"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Ordne eine FogBugz-Konto-ID einem/einer GitLab-Benutzer(in) zu"
@@ -5810,47 +6856,44 @@ msgstr "To-do als erledigt markieren"
msgid "Markdown"
msgstr ""
+msgid "Markdown Help"
+msgstr ""
+
msgid "Markdown enabled"
msgstr "Markdown aktiviert"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "Eine Punktliste hinzufügen"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "Einen Link hinzufügen"
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "Eine nummerierte Liste hinzufügen"
+msgid "Maven Metadata"
+msgstr "Maven-Metadaten"
-msgid "MarkdownToolbar|Add a table"
-msgstr "Eine Tabelle hinzufügen"
+msgid "Max access level"
+msgstr "Maximale Zugriffsstufe"
-msgid "MarkdownToolbar|Add a task list"
-msgstr "Eine Aufgabenliste hinzufügen"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr "Einen fetten Text hinzufügen"
+msgid "Maximum artifacts size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Add italic text"
-msgstr "Einen kursiven Text hinzufügen"
+msgid "Maximum attachment size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Go full screen"
-msgstr "Gehe in den Vollbildmodus"
+msgid "Maximum capacity"
+msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "Füge ein Zitat ein"
+msgid "Maximum delay (Minutes)"
+msgstr ""
-msgid "MarkdownToolbar|Insert code"
-msgstr "Füge Code ein"
+msgid "Maximum job timeout"
+msgstr "Maximale Job-Zeitüberschreitung"
-msgid "Maven Metadata"
-msgstr "Maven-Metadaten"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
-msgid "Max access level"
-msgstr "Maximale Zugriffsstufe"
+msgid "Maximum push size (MB)"
+msgstr ""
-msgid "Maximum job timeout"
-msgstr "Maximale Job-Zeitüberschreitung"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "Mai"
@@ -5873,21 +6916,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "Mitglieder werden hierher weitergeleitet, wenn diese sich bei deiner Gruppe anmelden. Diese URL bekommst du von deinem Identitätsanbieter, wo es auch als \"SSO Service Location\", \"SAML Token Issuance Endpoint\" oder \"SAML 2.0/W-Federation URL\" bezeichnet sein könnte."
-
msgid "Merge Request"
msgstr "Merge-Request"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Merge-Requests"
msgid "Merge Requests created"
msgstr "Merge_Request erstellt"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "Merge-Ereignisse"
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Merge-Request"
msgid "Merge request approvals"
msgstr "Merge-Request-Genehmigungen"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Merge-Requests"
@@ -5972,18 +7030,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart}hat die Beschreibung %{descriptionChangedTimes} Mal %{timeDifferenceMinutes} geändert%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr "Dateien filtern"
msgid "MergeRequest|No files found"
msgstr "Keine Dateien gefunden"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "Nachrichten"
@@ -6041,14 +7102,17 @@ msgstr "Neue Metrik"
msgid "Metrics|No deployed environments"
msgstr "Keine bereitgestellten Umgebungen"
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr "Prometheus Query-Dokumentation"
-msgid "Metrics|System"
-msgstr "System"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "Beim Abrufen der Umgebungsdaten trat ein Fehler auf, bitte versuche es erneut"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "z. B. Durchsatz"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Meilenstein"
@@ -6134,6 +7204,9 @@ msgstr "Die Hochstufung von %{milestone} wird ihn für alle Projekte innerhalb v
msgid "Milestones|This action cannot be reversed."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden."
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "Repository spiegeln"
@@ -6152,6 +7225,9 @@ msgstr "Gespiegelte Repositories"
msgid "Mirroring repositories"
msgstr "Spiegelnde Repositories"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "einen SSH-Schlüssel hinzufügen"
@@ -6161,6 +7237,9 @@ msgstr "Abbrechen"
msgid "Modal|Close"
msgstr "Schließen"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr "Mehr Informationen"
msgid "More information is available|here"
msgstr "hier"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Die meisten Sterne"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "Verschieben"
@@ -6206,6 +7291,12 @@ msgstr "Ticket verschieben"
msgid "Multiple issue boards"
msgstr "Mehrere Ticket-Boards"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Name"
@@ -6218,6 +7309,12 @@ msgstr "Benenne deinen individuellen Schlüssel mit einem Titel"
msgid "Name:"
msgstr "Name:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "Benennung, Sichtbarkeit"
@@ -6271,21 +7368,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Neuer Pipeline Zeitplan"
msgid "New Snippet"
msgstr "Neuer Codeausschnitt"
-msgid "New Snippets"
-msgstr "Neue Codeausschnitte"
-
msgid "New branch"
msgstr "Neuer Branch"
msgid "New branch unavailable"
msgstr "Neuer Branch ist nicht verfügbar"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Neues Verzeichnis"
@@ -6301,6 +7401,9 @@ msgstr "Neue Datei"
msgid "New group"
msgstr "Neue Gruppe"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "Neue Identität"
@@ -6322,6 +7425,9 @@ msgstr "Neue Pipelines brechen ältere, ausstehende Pipelines in dem selben Bran
msgid "New project"
msgstr "Neues Projekt"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Neuer Zeitplan"
@@ -6334,20 +7440,29 @@ msgstr "Neue Untergruppe"
msgid "New tag"
msgstr "Neuer Tag"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "Neu..."
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "Nein"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "Kein Label"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "Niemandem zugewiesen"
+msgid "No activities found"
+msgstr ""
msgid "No branches found"
msgstr "Keine Branches gefunden"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr "Kein Fälligkeitsdatum"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr "Keine Dateien gefunden."
msgid "No issues for the selected time period."
msgstr "Keine Tickets für den ausgewählten Zeitraum."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "Keine Tags mit einem solchen Namen oder einer solchen Beschreibung"
msgid "No license. All rights reserved"
msgstr "Keine Lizenz. Alle Rechte vorbehalten"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr "Keine Merge-Requests für den gewählten Zeitraum."
@@ -6451,9 +7575,6 @@ msgstr "Knoten"
msgid "None"
msgstr "Keine"
-msgid "Not allowed to merge"
-msgstr "Keine Berechtigung um zu mergen"
-
msgid "Not available"
msgstr "Nicht verfügbar"
@@ -6469,11 +7590,17 @@ msgstr "Nicht vertraulich"
msgid "Not enough data"
msgstr "Nicht genügend Daten"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "Nicht jetzt"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Beachte, dass der master-Branch automatisch geschützt ist. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6586,6 +7713,9 @@ msgstr "November"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "Okt"
@@ -6598,11 +7728,20 @@ msgstr "Filter"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "Nach dem Import können Repositories über SSH gespiegelt werden. Lies mehr %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Ein weiteres Element"
msgstr[1] "%d weitere Elemente"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Ein oder mehrere deiner Bitbucket-Projekte kann/können nicht direkt in GitLab importiert werden, da sie Subversion oder Mercurial für die Versionskontrolle anstelle von Git verwenden."
@@ -6612,6 +7751,12 @@ msgstr "Ein oder mehrere Ihrer Google Code-Projekte kann/können nicht direkt in
msgid "Only admins"
msgstr "Nur Administrator(inn)en"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "Nur geschützte Branches spiegeln"
@@ -6648,6 +7793,9 @@ msgstr "In Xcode öffnen"
msgid "Open projects"
msgstr "Projekte öffnen"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "Menüleiste öffnen"
@@ -6675,15 +7823,30 @@ msgstr "Vorgänge"
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Ein Projekt zum Dashboard hinzufügen"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr "Optional kannst du %{link_to_customize}, wie FogBugz E-Mail-Adressen und Benutzernamen in GitLab importiert werden."
@@ -6702,9 +7865,6 @@ msgstr "Andere Labels"
msgid "Other information"
msgstr "Andere Informationen"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "Andernfalls empfiehlt es sich, mit einer der folgenden Optionen zu beginnen."
-
msgid "Outbound requests"
msgstr "Ausgehende Anfragen"
@@ -6726,6 +7886,12 @@ msgstr "Paket wurde entfernt"
msgid "Packages"
msgstr "Pakete"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Seiten"
@@ -6750,18 +7916,21 @@ msgstr "« Erste"
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr "Teil der Merge-Request-Änderungen"
msgid "Password"
msgstr "Passwort"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "Füge deinen öffentlichen SSH-Schlüssel ein, der normalerweise in der Datei '~/.ssh/id_rsa.pub' enthalten ist und mit 'ssh-rsa' beginnt. Verwende nicht deinen privaten SSH-Schlüssel."
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "Pfad, übertragen, entfernen"
@@ -6780,6 +7952,9 @@ msgstr "Pfad:"
msgid "Pause"
msgstr "Pausieren"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "Angehaltene Runner akzeptieren keine neuen Jobs"
@@ -6798,6 +7973,9 @@ msgstr "Leistungsoptimierung"
msgid "Permissions"
msgstr "Zugriffsberechtigungen"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "Berechtigungen, LFS, 2FA"
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Pipeline"
-msgid "Pipeline Health"
-msgstr "Zustand der Pipeline"
-
msgid "Pipeline Schedule"
msgstr "Zeitplan der Pipeline"
msgid "Pipeline Schedules"
msgstr "Zeitpläne der Pipeline"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "Pipeline-Kontingent"
msgid "Pipeline triggers"
msgstr "Pipeline-Trigger"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Fehlgeschlagen:"
@@ -6876,6 +8057,12 @@ msgstr "Variablen"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Benutzerdefiniert"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Pipelines"
@@ -6891,6 +8078,12 @@ msgstr "Pipelines der letzten Woche"
msgid "Pipelines for last year"
msgstr "Pipelines des letzten Jahres"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "Baue mit Vertrauen"
@@ -6933,11 +8126,8 @@ msgstr "Dieses Projekt ist derzeit nicht zum Ausführen von Pipelines eingericht
msgid "Pipeline|Commit"
msgstr "Commit"
-msgid "Pipeline|Create for"
-msgstr "Erstellen für"
-
-msgid "Pipeline|Create pipeline"
-msgstr "Pipeline erstellen"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "Dauer"
@@ -6951,6 +8141,9 @@ msgstr "Pipeline"
msgid "Pipeline|Run Pipeline"
msgstr "Pipeline ausführen"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "Branches durchsuchen"
@@ -6978,9 +8171,21 @@ msgstr "Du bist dabei die Pipeline %{pipelineId} zu stoppen."
msgid "Pipeline|all"
msgstr "Alle"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "Erfolg"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "mit Phase"
@@ -7005,15 +8210,33 @@ msgstr "Bitte akzeptiere die Nutzungsbedingungen, um fortzufahren."
msgid "Please choose a group URL with no special characters."
msgstr "Bitte wähle eine Gruppen-URL ohne Sonderzeichen."
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Bitte wandle sie in %{link_to_git} um und durchlaufe erneut die %{link_to_import_flow}."
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "Bitte wandle sie in Git auf Google Code um und durchlaufe erneut die %{link_to_import_flow}."
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr "Bitte gib einen beschreibenden Namen für deine Gruppe ein."
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "Bitte beachte, dass diese Anwendung nicht von GitLab bereitgestellt wird. Du solltest daher die Authentizität überprüfen, bevor du den Zugriff erlaubst."
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "Bitte wähle zumindest einen Filter um Ergebnisse zu sehen"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "Bitte löse das reCAPTCHA"
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "Bitte verwende dieses Formular um Benutzer(innen) zu melden, die Spam-Tickets, -Kommentare erstellen oder sich unangemessen verhalten."
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "Bitte warte, während wir uns mit deinem Repository verbinden. Aktualisiere nach Belieben."
@@ -7047,18 +8285,33 @@ msgstr "Warte bitte, während wir das Repository für dich importieren. Aktualis
msgid "Preferences"
msgstr "Einstellungen"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "Navigationsthema"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr "Drücke Eingabe oder klicke um zu suchen"
msgid "Prevent adding new members to project membership within this group"
msgstr "Verhindere das Hinzufügen neuer Mitglieder zur Projektmitgliedschaft in dieser Gruppe"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Vorschau"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "Vorschau der Nutzdaten"
@@ -7086,6 +8339,9 @@ msgstr "Privat - Projektzugriff muss explizit jedem/jeder Benutzer(in) gewährt
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privat - Die Gruppe und dessen Projekte können nur von Mitgliedern eingesehen werden."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Private Projekte können in deinem persönlichen Namensraum erstellt werden:"
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "Die maximal zulässige Dateigröße beträgt 200KB."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "Das sieht nicht aus wie ein öffentlicher SSH-Schlüssel aus. Bist du sicher, dass du ihn hinzufügen willst?"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "Diese E-Mail-Adresse wird für webbasierte Vorgänge wie Bearbeitungen und Merges verwendet. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "Dieses Emoji und diese Nachricht werden in deinem Profil und auf der gesamten Benutzeroberfläche angezeigt."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr "Fortschritt"
msgid "Project"
msgstr "Projekt"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Projekt \"%{project_name}\" wird gerade gelöscht."
@@ -7365,17 +8630,26 @@ msgstr "Das Projekt '%{project_name}' wurde erfolgreich aktualisiert."
msgid "Project Badges"
msgstr "Projekt-Badges"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "Projekt-URL"
msgid "Project access must be granted explicitly to each user."
msgstr "Jedem/Jeder Benutzer(in) muss explizit der Zugriff auf das Projekt gewährt werden."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Projektavatar"
-msgid "Project avatar in repository: %{link}"
-msgstr "Projektavatar im Repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "Projektdetails"
@@ -7383,6 +8657,9 @@ msgstr "Projektdetails"
msgid "Project export could not be deleted."
msgstr "Der Export des Projekts konnte nicht gelöscht werden."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "Der Export des Projekts wurde gelöscht."
@@ -7392,6 +8669,9 @@ msgstr "Der Link für den Export des Projektes ist abgelaufen. Bitte generiere e
msgid "Project export started. A download link will be sent by email."
msgstr "Export des Projektes gestartet. Ein Link zum Herunterladen wir dir per E-Mail zugesandt."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr "Projektname"
msgid "Project slug"
msgstr "Projekt-Slug"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr "Benutzer(innen) können nur Commits in dieses Repository pushen die mit
msgid "Projects"
msgstr "Projekte"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "Geteilte Projekte mit %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "Häufig besucht"
@@ -7545,6 +8840,9 @@ msgstr "Kein Alarm eingerichtet"
msgid "PrometheusAlerts|Operator"
msgstr "Operator"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "Grenzwert"
@@ -7647,6 +8945,9 @@ msgstr "Diese Funktion ist gesperrt."
msgid "Promotions|Upgrade plan"
msgstr "Upgrade-Plan"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "Geschützt"
@@ -7713,6 +9014,12 @@ msgstr "Öffentlich - Die Gruppe und alle öffentlichen Projekte können ohne Au
msgid "Public - The project can be accessed without any authentication."
msgstr "Öffentlich - Auf das Projekt kann ohne Authentifizierung zugegriffen werden."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "Öffentliche Pipelines"
@@ -7725,6 +9032,12 @@ msgstr "Push"
msgid "Push Rules"
msgstr "Push-Regeln"
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "Push-Ereignisse"
@@ -7755,6 +9068,9 @@ msgstr "Kurzbefehle können in Ticket-Beschreibungen und Kommentarfeldern verwen
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Mehr lesen"
@@ -7770,6 +9086,12 @@ msgstr "Echtzeit-Features"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr "Zuletzt gesucht"
@@ -7805,6 +9127,9 @@ msgstr "Registriere dich und sieh deine Runner für diese Gruppe."
msgid "Register and see your runners for this project."
msgstr "Registriere die Runner für dieses Projekt und zeige sie an."
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "Registry"
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "Avatar entfernen"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "Gruppe entfernen"
@@ -7862,12 +9196,33 @@ msgstr "Priorität entfernen"
msgid "Remove project"
msgstr "Projekt entfernen"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "Entfernte Gruppe kann nicht wiederhergestellt werden!"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "Durch das Entfernen der Gruppe werden alle untergeordneten Projekte und Ressourcen entfernt."
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "Umbenennen"
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr "Authentifizierung reparieren"
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr "Ausführungszeit"
msgid "Reports|Failure"
msgstr "Fehlschlag"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Schweregrad"
@@ -7937,6 +9307,12 @@ msgstr "Ergebnisse der Testzusammenfassung werden analysiert"
msgid "Reports|Vulnerability"
msgstr "Sicherheitslücke"
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "Keine geänderten Testergebnisse"
@@ -7949,6 +9325,9 @@ msgstr "Repository-Einstellungen"
msgid "Repository URL"
msgstr "Repository-URL"
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr "Alle Benutzer(innen) dieser Gruppe müssen die Zwei-Faktor-Authentifizie
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Fordere alle Benutzer(innen) auf, die Nutzungsbedingungen und Datenschutzrichtlinien zu akzeptieren, wenn sie auf GitLab zugreifen."
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr "Diskussion beenden"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr "Antwortmesswerte (AWS ELB)"
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr "Fortsetzen"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Wiederholen"
msgid "Retry this job"
msgstr "Wiederhole diese Aufgabe"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "Wiederhole die Überprüfung"
@@ -8083,12 +9490,21 @@ msgstr "Überprüfung (Merge-Request !%{mergeRequestId})"
msgid "Revoke"
msgstr "Zurücknehmen"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "Roadmap"
msgid "Run CI/CD pipelines for external repositories"
msgstr "Führe CI/CD-Pipelines für externe Repositories aus"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr "Runner führt Jobs von zugewiesenen Projekten aus"
msgid "Runner token"
msgstr "Runner-Token"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "Runner wird keine neuen Jobs annehmen"
@@ -8143,27 +9565,27 @@ msgstr "Du hast alle deine Pipeline-Minuten für geteilte Runner verbraucht."
msgid "Running"
msgstr "Wird ausgeführt"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "SAML SSO für %{group_name}"
-msgid "SAML Single Sign On"
-msgstr "SAML Single Sign On"
-
-msgid "SAML Single Sign On Settings"
-msgstr "SAML Single Sign On-Einstellungen"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "SHA-1-Fingerabdruck des SAML-Tokensignaturzertifikats. Hole diesen von deinem Identitätsanbieter, wo es auch \"Daumenabdruck (Thumbprint)\" heißen könnte."
-
msgid "SSH Keys"
msgstr "SSH-Schlüssel"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH-Hostschlüssel"
@@ -8173,6 +9595,9 @@ msgstr "Öffentlicher SSH-Schlüssel"
msgid "SSL Verification"
msgstr "SSL-Verifizierung"
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "Speichern"
@@ -8197,6 +9622,9 @@ msgstr "Zeitplan der Pipeline speichern"
msgid "Save variables"
msgstr "Variablen speichern"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Plane eine neue Pipeline"
@@ -8206,15 +9634,24 @@ msgstr "Geplant"
msgid "Schedules"
msgstr "Zeitpläne"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Pipelines planen"
msgid "Scope"
msgstr "Gültigkeitsbereich"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "Ticketboards mit festgelegtem Umfang"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "Scrolle nach unten zu <strong>Google Code Project Hosting</strong> und aktiviere den Schalter auf der rechten Seite."
@@ -8266,6 +9703,9 @@ msgstr "Projekte suchen"
msgid "Search users"
msgstr "Suche Benutzer(innen)"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr "Deine Projekte durchsuchen"
@@ -8293,6 +9733,15 @@ msgstr "in dieser Gruppe"
msgid "SearchAutocomplete|in this project"
msgstr "in diesem Projekt"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "Secret"
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr "Ticket erstellt"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "Ticket erstellen"
@@ -8332,10 +9778,10 @@ msgstr "Mehr Informationen"
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr "Wähle eine Gruppe aus, die du einladen möchtest"
msgid "Select a namespace to fork the project"
msgstr "Wähle einen Namensraum aus, um einen Fork des Projekts zu erstellen"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr "Wähle ein Vorlagen-Repository aus"
@@ -8395,9 +9850,6 @@ msgstr "Zeitzone auswählen"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Wähle eine existierendes Kubernetes-Cluster aus oder erstelle ein neues"
-msgid "Select assignee"
-msgstr "Bearbeiter auswählen"
-
msgid "Select branch/tag"
msgstr "Branch/Tag auswählen"
@@ -8428,11 +9880,14 @@ msgstr "Wähle den Branch aus, den du als Standard für dieses Projekt festlegen
msgid "Select the custom project template source group."
msgstr "Wähle die Quellgruppe der benutzerdefinierten Projektvorlage aus."
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "Wenn du eine(n) GitLab-Benutzer(in) azswählst, wird in der Beschreibung des Tickets und den Kommentaren ein Link zum/zur Benutzer(in) hinzugefügt (z. B. \"Von <a href=\"#\">@johnsmith</a>\"). Außerdem wird der/die ausgewählte Benutzer(in) dem Ticket oder Kommentar zugeordnet und/oder es ihm/ihr zugewiesen."
-msgid "Selective synchronization"
-msgstr "Selektive Synchronisation"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "E-Mail senden"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr "Sept"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "September"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "Serverversion"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr "Servicevorlagen"
msgid "Service URL"
msgstr "Service-URL"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "Sitzungsablauf, Projektlimit und Anhangsgröße."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Lege ein Passwort für dein Konto fest, um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)."
@@ -8524,6 +10006,9 @@ msgstr "Lege ein Instanz-weites Vorlagen-Repository"
msgid "Set max session time for web terminal."
msgstr "Lege die maximale Sitzungszeit für das Web-Terminal fest."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "Benachrichtigungs-E-Mail für Missbrauchsberichte festlegen."
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "Lege Anforderungen für eine(n) Benutzer(in) fest, um sich anzumelden. Aktiviere die erforderliche Zwei-Faktor-Authentifizierung."
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "CI/CD einrichten"
@@ -8548,6 +10045,9 @@ msgstr "Richte notwendige Angaben (E-Mail, Vorname, Nachname) und NameID gemäß
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "Richte dein Projekt so ein, dass Änderungen automatisch an ein anderes Repository gesendet bzw. von diesem abgerufen werden. Branches, Tags und Commits werden automatisch synchronisiert."
@@ -8605,9 +10105,15 @@ msgstr "Verwendete Pipeline-Minuten zurücksetzen"
msgid "Sherlock Transactions"
msgstr "Sherlock-Transaktionen"
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "Befehl anzeigen"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "Zeige komplettes Rohprotokoll"
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr "Neuste Version zeigen"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Ãœbergeordnete Seiten anzeigen"
@@ -8676,6 +10185,12 @@ msgstr "Anmeldebeschränkungen"
msgid "Sign-up restrictions"
msgstr "Registrierungsbeschränkungen"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr "Größe"
msgid "Size and domain settings for static websites"
msgstr "Größe und Domain-Einstellungen für statische Webseiten"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Slack-Anwendung"
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr "Codeausschnitte"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr "Beim Abrufen der Kommentare ist etwas schiefgelaufen. Bitte versuche es
msgid "Something went wrong while fetching group member contributions"
msgstr "Beim Abrufen von Gruppenmitgliedsbeiträgen ist ein Fehler aufgetreten"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Beim Abrufen der Umgebungen für diesen Merge-Request ist etwas schiefgelaufen. Bitte versuche es erneut."
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr "Etwas lief schief, Projekt konnte nicht entfernt werden"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Etwas ist schief gelaufen. Bitte versuche es erneut."
@@ -8928,6 +10473,9 @@ msgstr "Spam-Protokolle"
msgid "Spam and Anti-bot Protection"
msgstr "Spam- und Anti-Bot-Schutz"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Spezifische Runner"
@@ -8976,15 +10524,27 @@ msgstr "Aktivität markierter Projekte"
msgid "Starred projects"
msgstr "Markierte Projekte"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Beginne einen %{new_merge_request} mit diesen Änderungen"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Einen Review starten"
@@ -9018,6 +10578,9 @@ msgstr "Begonnen"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Zu Branch/Tag wechseln"
msgid "Sync information"
msgstr "Informationen synchronisieren"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "System-Hooks"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Systeminformationen"
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "Systemmetriken (benutzerdefiniert)"
@@ -9321,12 +10917,23 @@ msgstr "SAML SSO testen"
msgid "Test coverage parsing"
msgstr "Testabdeckung analysieren"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "Vielen Dank! Zeig es mir nicht nochmal"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "Die „Erweiterte Globale Suche“ in GitLab ist ein leistungsfähiger Suchdienst, der dir Zeit spart. Anstatt doppelten Code zu erstellen und Zeit zu verschwenden, kannst du nun bei anderen Teams nach Code suchen, der für dein eigenes Projekt hilfreich sein kann."
@@ -9345,6 +10952,12 @@ msgstr "Das Ticketsystem ist der Ort, um Dinge hinzuzufügen, die in einem Proje
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Das zu verwendende X.509-Zertifikat, wenn über MTLS mit dem externen Autorisierungsdienst kommunizieren muss. Wenn dieses Feld leer gelassen wird, wird das Serverzertifikat beim Zugriff über HTTPS weiterhin überprüft."
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "Das Character-Highlighter hilft dir, die Betreffzeile auf %{titleLength} Zeichen zu beschränken und den Textkörper auf %{bodyLength} zu kürzen, damit sie in Git lesbar sind."
@@ -9357,15 +10970,63 @@ msgstr "Ereignisse, die für diese Phase ausgewertet wurden."
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Die Verbindung wird nach %{timeout} beendet. Verwende eine Clone/Push-Kombination für Repositorys, die länger brauchen."
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "Die Bereitstellung dieses Jobs auf %{environmentLink} war nicht erfolgreich."
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "Die Beziehung des Forks wurde entfernt."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Der Import wird nach %{timeout} beendet. Verwende eine Clone/Push-Kombination für Repositorys, die länger brauchen."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "Die Ticketphase stellt die Zeit vom Anlegen eines Tickets bis zum Zuweisen zu einem Meilenstein oder dem Hinzufügen zu deinem Ticket-Board dar. Erstelle einen Ticket, damit dessen Daten hier erscheinen."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "Die maximal zulässige Dateigröße beträgt 200 KB."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "Das Kennwort, das zum Entschlüsseln des privaten Schlüssels erforderlich ist. Dies ist optional und der Wert wird im Ruhezustand verschlüsselt."
@@ -9402,9 +11075,18 @@ msgstr "Auf das Projekt kann jede(r) angemeldete Nutzer(in) zugreifen."
msgid "The project can be accessed without any authentication."
msgstr "Auf das Projekt kann ohne Authentifizierung zugegriffen werden."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "Die Pseudonymizer-Datensammlung ist deaktiviert. Wenn sie aktiv ist, führt GitLab einen Hintergrundjob aus, der pseudonymisierte CSVs der GitLab-Datenbank erzeugt, die in dein konfiguriertes Objektspeicherverzeichnis hochgeladen werden."
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Das Repository für dieses Projekt existiert nicht."
@@ -9423,6 +11105,21 @@ msgstr "Die Review-Phase stellt die Zeit vom Anlegen eines Merge-Requests bis zu
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "Die Roadmap zeigt den Fortschritt deiner Epics anhand einer Zeitleiste an"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "Die Staging-Phase stellt die Zeit zwischen dem Mergen des Merge-Requests und der Bereitstellung des Codes zur Produktivumgebung dar. Sobald du das erste Mal zur Produktivumgebung ausgeliefert hast, werden deren Daten hier automatisch angezeigt."
@@ -9438,9 +11135,18 @@ msgstr "Die Zeit, die jede Dateneingabe in dieser Phase benötigt."
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Die Aktualisierungsaktion wird nach %{number_of_minutes} Minuten abgebrochen. Verwende für große Repositories eine Clone / Push-Kombination."
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "Der Nutzungsbericht ist deaktiviert und kann nicht über dieses Formular konfiguriert werden."
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "Die Benutzerzuordnung ist ein JSON-Dokument das festlegt, wie die E-Mail-Adressen und Benutzernamen der Google Code-Benutzer(innen), die an deinem Projekt teilnehmen, in GitLab importiert werden. Du kannst dies ändern, indem du den Wert auf der rechten Seite von <code>:</code> anpasst. Stelle sicher, dass du umgebenden Anführungszeichen, andere Interpunktion sowie die E-Mail-Adresse oder den Benutzernamen auf der linken Seite erhältst."
@@ -9495,9 +11201,21 @@ msgstr "Beim Hinzufügen eines To-dos ist ein Fehler aufgetreten."
msgid "There was an error deleting the todo."
msgstr "Beim Löschen des To-dos ist ein Fehler aufgetreten."
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "Beim Laden des Benutzeraktivitäts-Kalenders ist ein Fehler aufgetreten."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Beim Speichern deiner Benachrichtigungseinstellungen ist ein Fehler aufgetreten."
@@ -9513,6 +11231,9 @@ msgstr "Beim Abonnieren dieses Labels ist ein Fehler aufgetreten."
msgid "There was an error when unsubscribing from this label."
msgstr "Beim Abmelden von der Abonnierung dieses Labels ist ein Fehler aufgetreten."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Diese GitLab-Instanz stellt noch keine geteilten Runner bereit. Instanz-Administrator(inn)en können geteilte Runner im Admin-Bereich registrieren."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "Diese Anwendung wurde erstellt von %{link_to_owner}."
@@ -9543,6 +11267,21 @@ msgstr "Der Umfang dieses Boards ist reduziert"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Dieser Branch wurde verändert, seit du begonnen hast ihn zu bearbeiten. Möchtest du einen neuen Branch anlegen?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr "Diese Container-Registry wurde zur Löschung vorgesehen."
@@ -9564,6 +11303,9 @@ msgstr "Dieses Verzeichnis"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "Diese Gruppe"
@@ -9579,6 +11321,9 @@ msgstr "Dies ist ein in %{remainingTime} auszuführender verzögerter Job"
msgid "This is the author's first Merge Request to this project."
msgstr "Dies ist der erste Merge-Request des Autors für dieses Projekt."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Dieses Ticket ist vertraulich"
@@ -9627,6 +11372,12 @@ msgstr "Dieser Job erstellt eine Bereitstellung für %{environmentLink}."
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Dieser Job befindet sich im Status \"ausstehend\" und wartet darauf, durch einen Runner ausgewählt zu werden"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,19 +11399,25 @@ msgstr "Dies bedeutet, dass du keinen Code pushen kannst, bevor du kein leeres R
msgid "This merge request is locked."
msgstr "Dieser Merge-Request ist gesperrt."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "Diese Option wurde deaktiviert, da du keine Schreibrechte für diesen Branch hast"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "Diese Option ist deaktiviert, solange du noch nicht vorgemerkte Änderungen hast"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "Diese Seite ist nicht verfügbar, da du nicht Informationen über mehrere Projekte hinweg lesen darfst."
msgid "This page will be removed in a future release."
msgstr "Diese Seite wird in einer zukünftigen Version entfernt."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr "Für dieses Projekt ist keine Abrechnung aktiviert. Um ein Cluster zu er
msgid "This repository"
msgstr "Dieses Repository"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "Dieser Runner wird nur Pipelines von geschützen Branches ausführen"
@@ -9693,12 +11453,12 @@ msgstr "Diese Einstellung kann in jedem Projekt überschrieben werden."
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "Diese Einstellung aktualisiert den Hostnamen der für die Generierung von privaten Commit-E-Mails verwendet wird. %{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "Dieser Quelltext-Diff konnte nicht angezeigt werden, da er zu groß ist."
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "Diese(r) Benutzer(in) hat keine Identitäten"
@@ -9711,6 +11471,9 @@ msgstr "Diese(r) Benutzer(in) wird der/die Autor(in) aller Ereignisse im Aktivit
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "Diese E-Mails werden automatisch zu hier gelisteten Tickets (wobei die Kommentare zur E-Mail-Konversation werden)."
@@ -9777,9 +11540,6 @@ msgstr "vor %s Monaten"
msgid "Timeago|%s months remaining"
msgstr "%s Monate verbleibend"
-msgid "Timeago|%s seconds ago"
-msgstr "vor %s Sekunden"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s Sekunden verbleibend"
@@ -10009,6 +11769,9 @@ msgstr "Heute"
msgid "Todo"
msgstr "To-do"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "To-dos"
@@ -10048,6 +11811,12 @@ msgstr "Morgen"
msgid "Too many changes to show."
msgstr "Zu viele Änderungen um sie anzuzeigen."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Gesamte Beiträge"
@@ -10072,6 +11841,9 @@ msgstr "Verfolge Gruppen von Tickets, die ein Thema teilen, über Projekte und M
msgid "Track time with quick actions"
msgstr "Verfolge die Zeit mit Kurzbefehlen"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "Baumstrukturansicht"
@@ -10084,6 +11856,9 @@ msgstr "Pipelines für Mirror-Updates auslösen"
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "Triggere Pipelines, wenn Branches oder Tags aus dem Upstream-Repository aktualisiert werden. Abhängig von der Aktivität des Upstream-Repositorys kann dies die Auslastung deiner CI-Runner erheblich erhöhen. Aktiviere diese Option nur, wenn du weißt, dass sie mit der Last umgehen können."
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "Löse diese manuelle Aktion aus"
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "Trigger können erzwingen, dass einen bestimmten Branch oder Tag mit einem API-Aufruf neu erstellt wird. Diese Token nehmen die Identität ihres/ihrer verbundenen Benutzers/Benutzerin an, einschließlich ihres Zugriffs auf Projekte und ihrer Projektberechtigungen."
@@ -10117,6 +11904,9 @@ msgstr "Aktiviere den Service-Desk"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "Zwei-Faktor-Authentifizierung"
@@ -10126,15 +11916,27 @@ msgstr "Typ"
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Der Diff konnten nicht geladen werden. %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Aufgrund von \"%{reason}\" konntest du dich nicht mit SAML anmelden"
msgid "Unable to update this epic at this time."
msgstr "Dieses Epic kann gerade nicht aktualisiert werden."
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr "Unbekannt"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "Entsperren"
@@ -10207,21 +12012,39 @@ msgstr "Demnächst"
msgid "Update"
msgstr "Aktualisieren"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "Jetzt updaten"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Deine Gruppenbezeichnung, Beschreibung, Avatar und Sichtbarkeit anpassen."
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "Aktualisiere"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Aktualisiere deinen Tarif, um die erweiterte globale Suche zu aktivieren."
@@ -10237,6 +12060,9 @@ msgstr "Aktualisiere deinen Tarif, um die Ticket-Gewichtung zu aktivieren."
msgid "Upgrade your plan to improve Issue boards."
msgstr "Upgrade deinen Tarif um die Ticketboards zu verbessern."
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "Lade <code>GoogleCodeProjectHosting.json</code> hier hoch:"
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "Zum Upload klicken"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "Upvotes"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "Der Nutzungsbericht ist nicht aktiviert"
msgid "Usage statistics"
msgstr "Nutzungsstatistiken"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "Verwende <code>%{native_redirect_uri}</code> für lokale Tests"
@@ -10303,18 +12141,54 @@ msgstr "Wird von Mitgliedern verwendet, um sich in GitLab in deiner Gruppe anzum
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "Benutzer-Kohorten sind nur sichtbar, wenn der %{usage_ping_link_start}Nutzungsbericht%{usage_ping_link_end} aktiviert ist."
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "Benutzereinstellungen"
msgid "User and IP Rate Limits"
msgstr "Benutzer- und IP-Beschränkungen"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "Benutzerzuordnung"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "Aktivität"
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr "Benutzer(innen)"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "Verschiedene Einstellungen für die Container-Registry."
@@ -10429,9 +12324,6 @@ msgstr "Bestätigt"
msgid "Version"
msgstr "Version"
-msgid "View %{alerts}"
-msgstr "%{alerts} anzeigen"
-
msgid "View app"
msgstr "App anzeigen"
@@ -10441,8 +12333,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "Dokumentation anzeigen"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "Epic-Liste anzeigen"
@@ -10453,12 +12345,15 @@ msgstr "Zeige Datei @ "
msgid "View group labels"
msgstr "Gruppenlabels ansehen"
-msgid "View issue"
-msgstr "Ticket anzeigen"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "Sieh es auf GitLab an"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "Jobs ansehen"
@@ -10492,6 +12387,9 @@ msgstr "Sichtbarkeitsstufe"
msgid "Visibility level:"
msgstr "Sichtbarkeitsstufe:"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "Sichtbarkeit:"
@@ -10528,12 +12426,18 @@ msgstr "Datei"
msgid "Vulnerability|Identifiers"
msgstr "Bezeichner"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "Instanzen"
msgid "Vulnerability|Links"
msgstr "Links"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "Projekt"
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr "Web-Terminal"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Webhooks ermöglichen es dir eine URL aufzurufen, wenn z. B. neuer Code gepusht oder ein neues Ticket erstellt wird. Du kannst Webhooks so konfigurieren, dass sie auf bestimmte Ereignisse wie Pushes, Tickets oder Merge-Requests reagieren. Gruppen-Webhooks werden auf alle Projekte in einer Gruppe angewendet, so dass du die Webhook-Funktionalität in der gesamten Gruppe standardisieren kannst."
@@ -10600,9 +12510,17 @@ msgstr "Wenn ein Runner gesperrt ist, kann er keinem anderen Projekt zugewiesen
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "Wenn diese Option aktiviert ist, können Benutzer(innen) GitLab nicht verwenden, bis die Bedingungen akzeptiert wurden."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "Wenn die URL leer gelassen wird, können Klassifizierungslabels weiterhin angegeben werden ohne projektübergreifende Eigenschaften auszuschalten oder einen externen Autorisierungscheck durchzuführen."
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr "Wer wird in der Lage sein diese Gruppe zu sehen?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Wiki klonen"
@@ -10726,6 +12647,9 @@ msgstr "Seite erstellen"
msgid "Wiki|Create page"
msgstr "Seite erstellen"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Seite bearbeiten"
@@ -10744,6 +12668,9 @@ msgstr "Seitenversion"
msgid "Wiki|Pages"
msgstr "Seiten"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Wiki-Seiten"
@@ -10756,12 +12683,24 @@ msgstr "Mit der Beitragsanalyse hast du einen Überblick über die Aktivitäten
msgid "Withdraw Access Request"
msgstr "Zugriffsanfrage widerrufen"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "Ja"
@@ -10792,6 +12731,9 @@ msgstr "Du bist dabei, die Beziehung des Forks zum Ursprungsprojekt %{forked_fro
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "Du bist dabei %{project_full_name} einem/einer andere(n) Besitzer(in) zu übergeben. Bist du dir WIRKLICH sicher?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "Du bist auf einer GitLab-Instanz, die nur Lesezugriff erlaubt."
@@ -10807,6 +12749,15 @@ msgstr "Du kannst ein Projekt auch über die Kommandozeile erstellen."
msgid "You can also star a label to make it a priority label."
msgstr "Du kannst auch ein Label markieren, um es zu einem Prioritätslabel zu machen."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Du kannst einfach dazu beitragen, indem du um Beitritt zu dieser Gruppen bittest."
@@ -10816,6 +12767,12 @@ msgstr "Du kannst ganz einfach einen Runner auf einem Kubernetes-Cluster install
msgid "You can move around the graph by using the arrow keys."
msgstr "Du kannst das Diagramm mit den Pfeiltasten bewegen."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Du kannst Dateien nur hinzufügen, wenn du dich auf einem Branch befindest"
@@ -10825,6 +12782,9 @@ msgstr "Du kannst Dateien nur bearbeiten, wenn du dich auf einem Branch befindes
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr "Du kannst den Merge-Konflikt lösen, indem du entweder den interaktiven Modus verwendest, indem du die Schaltflächen %{use_ours} oder %{use_theirs} wählst, oder indem du die Dateien direkt bearbeitest. Übernimm diese Änderungen mittels Commit in %{branch_name}"
@@ -10834,12 +12794,30 @@ msgstr "Du kannst Jobs so einrichten, dass nur Runner mit bestimmten Tags verwen
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "Du kannst deine .gitlab-ci.yml mit %{linkStart}CI Lint%{linkEnd} testen."
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "Du kannst nicht auf eine schreibgeschützte sekundäre Gitlab Geo-Instanz schreiben. Bitte verwende stattdessen %{link_to_primary_node}."
msgid "You cannot write to this read-only GitLab instance."
msgstr "Du kannst nicht auf dieser schreibgeschützte GitLab-Instanz schreiben."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "Du hast noch keine Abonnements"
@@ -10858,12 +12836,27 @@ msgstr "Du hast keine autorisierten Anwendungen"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "Du hast keine Berechtigungen"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Du hast die Projektbegrenzung erreicht"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr "Du musst Betreuerzugriff besitzen, um das Entfernen einer Sperre zu erzw
msgid "You need a different license to enable FileLocks feature"
msgstr "Du benötigst eine andere Lizenz, um die FileLocks-Funktion zu aktivieren"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "Du brauchst eine Genehmigung."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "Du wirst keine Benachrichtigungen per E-Mail erhalten"
@@ -10921,6 +12926,15 @@ msgstr "Du musst einen deinem Profil einen SSH-Schlüssel hinzufügen, bevor du
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Du musst unterschiedliche Branchnamen verwenden, um einen gültigen Vergleich zu erhalten."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "Du erhältst diese E-Mail, weil %{reason}."
@@ -10954,6 +12968,15 @@ msgstr "Deine To-dos"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "Deine Anwendungen (%{size})"
@@ -10972,9 +12995,15 @@ msgstr "Deine Änderungen wurden committet. Commit %{commitId}%{commitStats}"
msgid "Your changes have been saved"
msgstr "Deine Änderungen wurden gespeichert"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Dein Kommentar wird nicht öffentlich sichtbar sein."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Dein Name"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "Deine Projekte"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "ein(e) gelöschte(r) Benutzer(in)"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "vor"
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr "unter anderem"
-msgid "assign yourself"
-msgstr "selber zuweisen"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "Branch-Name"
@@ -11038,16 +13085,38 @@ msgstr "%{namespace} ist von %{vulnerability} betroffen."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} mehr"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} hat %{fixedCount} behobene Sicherheitslücke entdeckt"
msgstr[1] "%{reportType} %{status} hat %{fixedCount} behobene Sicherheitslücke entdeckt"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} hat %{newCount} neue Sicherheitslücke entdeckt"
msgstr[1] "%{reportType} %{status} hat %{newCount} neue Sicherheitslücken entdeckt"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} hat %{newCount} neue und %{fixedCount} behobene Sicherheitslücken entdeckt"
@@ -11079,6 +13148,9 @@ msgstr "%{reportType} wird geladen"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType}: Beim Laden trat ein Fehler auf"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(Fehler beim Laden der Ergebnisse)"
@@ -11088,6 +13160,15 @@ msgstr "(wird geladen)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(Lädt, Fehler beim Laden der Ergebnisse)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "Klasse"
@@ -11097,12 +13178,24 @@ msgstr "Code-Qualität"
msgid "ciReport|Confidence"
msgstr "Vertrauen"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "Container-Scan"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Containerüberprüfung entdeckt bekannte Sicherheitslücken in deinen Docker-Images."
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11145,9 +13238,18 @@ msgstr "Behoben:"
msgid "ciReport|Identifiers"
msgstr "Bezeichner"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "Instanzen"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "Lerne mehr über die Interaktion mit Sicherheitsberichten (Alpha)."
@@ -11191,9 +13293,6 @@ msgstr "Keine Änderungen an den Leistungsmetriken"
msgid "ciReport|Performance metrics"
msgstr "Leistungsmetriken"
-msgid "ciReport|Revert dismissal"
-msgstr "Ausblenden rückgängig machen"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11215,6 +13314,9 @@ msgstr "Static Application Security Testing (SAST) entdeckt bekannte Sicherheits
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "Fehler beim Erzeugen des Tickets. Bitte versuche es erneut."
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "Fehler beim Ausblenden der Sicherheitslücke. Bitte versuche es erneut."
@@ -11233,9 +13335,18 @@ msgstr "Fehler beim Laden des Abhängigkeitsprüfungs-Berichtes"
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "Fehler beim Wiedereinblenden. Bitte versuche es erneut."
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "Upgrade %{name} von %{version} zu %{fixed}."
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "Verwendet von %{packagesString}"
@@ -11247,12 +13358,33 @@ msgstr "Gesamten Bericht anzeigen"
msgid "ciReport|on pipeline"
msgstr "in der Pipeline"
-msgid "command line instructions"
-msgstr "Befehlszeilenanweisungen"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr "Du willst die Vertraulichkeit deaktivieren. Das bedeutet, dass <strong>jeder</strong> das Ticket betrachten und kommentieren kann."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr "Bereitstellungstoken"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "dektiviert"
@@ -11310,6 +13445,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} aktualisiert die geschätzte Zeit mit dem aktuellsten Befehl."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "für dieses Projekt"
@@ -11319,15 +13469,9 @@ msgstr "von"
msgid "group"
msgstr ""
-msgid "help"
-msgstr "Hilfe"
-
msgid "here"
msgstr "hier"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://dein-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr "neuste Bereitstellung"
msgid "latest version"
msgstr "Neuste Version"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "Lizenzverwaltung"
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr "Während des Sendens deiner Genehmigung trat ein Fehler auf."
msgid "mrWidget|Approve"
msgstr "Genehmigen"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Genehmigt von"
@@ -11518,17 +13671,20 @@ msgstr "Lokal mergen"
msgid "mrWidget|Merge request approved"
msgstr "Merge-Request genehmigt"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Merge-Request genehmigt; du kannst zusätzlich genehmigen"
msgid "mrWidget|Merged by"
msgstr "Merged von"
-msgid "mrWidget|No Approval required"
-msgstr "Keine Genehmigung erforderlich"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "Keine Genehmigung erforderlich; du kannst noch genehmigen"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "In Web-IDE öffnen"
@@ -11579,6 +13735,9 @@ msgstr "Zurücksetzen"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "Diesen Merge-Request in einem neuen Merge-Request rückgängig machen"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "Festlegt von"
@@ -11691,12 +13850,20 @@ msgstr "Passwort"
msgid "personal access token"
msgstr "Persönlicher Zugangs-Token"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr "Der private Schlüssel stimmt nicht mit dem Zertifikat überein."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "Projekt"
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] "Antwort"
msgstr[1] "Antworten"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr "gestartet"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/el_GR/gitlab.po b/locale/el_GR/gitlab.po
index 68aebe5b9f2..142d421aed0 100644
--- a/locale/el_GR/gitlab.po
+++ b/locale/el_GR/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: el\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:15\n"
+"PO-Revision-Date: 2019-04-12 12:39\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index 5e14e94e6fc..151fbffeb02 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -52,9 +52,6 @@ msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr ""
-
msgid "About auto deploy"
msgstr ""
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index fde168f06af..ef131535e9d 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: eo\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:13\n"
+"PO-Revision-Date: 2019-04-12 12:38\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,8 +424,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Aro da diagramoj pri la seninterrompa integrado"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Aktiva"
@@ -451,36 +568,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr "Aldoni novan dosierujon"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
msgstr ""
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Foliumi dosierujon"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "de"
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr "Malfari"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr "Diagramoj"
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr "sukcesa, kun avertoj"
msgid "CiStatusLabel|pending"
msgstr "okazonta"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "transsaltita"
@@ -1812,6 +2183,9 @@ msgstr "sukcesa"
msgid "CiStatusText|pending"
msgstr "okazonta"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "transsaltita"
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr "Enmetita de"
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Kompari"
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr "Kopii la identigilon de la enmetado"
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr "Krei novan dosierujon"
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Kreu propran atingoĵetonon en via konto por ebligi al vi eltiri kaj alpuÅi per %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr "La sintakso de Cron"
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] "Disponigadoj"
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr "Priskribo"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,20 +3837,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "ElÅuti en formato „tar“"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "ElÅuti en formato „tar.bz2“"
+msgstr ""
msgid "Download tar.gz"
-msgstr "ElÅuti en formato „tar.gz“"
+msgstr ""
msgid "Download zip"
-msgstr "ElÅuti en formato „zip“"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "ElÅuti"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Sendi flikaĵojn per retpoÅto"
@@ -3373,6 +3888,9 @@ msgstr "Redakti"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr "Ne eblas ÅanÄi la posedanton"
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr "Ne eblas forigi la ĉenstablan planon"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Trovi per dosierindiko"
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "La refreÅigo komenciÄis sukcese"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr "Enporti deponejon"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr "Ni prezentas al vi la ciklan analizon"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "MalÅaltita"
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr "dokumentado pri ĉenstablaj planoj"
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Forlasi la grupon"
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "aldonos SSH-Ålosilon"
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,21 +7368,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Nova ĉenstabla plano"
msgid "New Snippet"
msgstr ""
-msgid "New Snippets"
-msgstr ""
-
msgid "New branch"
msgstr "Nova branĉo"
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Nova dosierujo"
@@ -6301,6 +7401,9 @@ msgstr "Nova dosiero"
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Nova plano"
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr "Nova etikedo"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr "Ne disponebla"
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr "Ne estas sufiĉe da datenoj"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr "Filtrilo"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Ĉenstablo"
-msgid "Pipeline Health"
-msgstr "Stato"
-
msgid "Pipeline Schedule"
msgstr "Ĉenstabla plano"
msgid "Pipeline Schedules"
msgstr "Ĉenstablaj planoj"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Malsukcesaj:"
@@ -6876,6 +8057,12 @@ msgstr "Variabloj"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Propra"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Ĉenstabloj"
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr "ĉiuj"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "sukcesaj"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "kun etapo"
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr "La projekto „%{project_name}“ estis sukcese Äisdatigita."
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr "Ĉiu uzanto devas akiri propran atingon al la projekto."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr "Ne eblas forigi la projektan elporton."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "La projekta elporto estis forigita."
@@ -7392,6 +8669,9 @@ msgstr "La ligilo por la projekta elporto eksvalidiÄis. Bonvolu krei novan elpo
msgid "Project export started. A download link will be sent by email."
msgstr "La elporto de la projekto komenciÄis. Vi ricevos ligilon per retpoÅto por elÅuti la datenoj."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Legu pli"
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr "Forigi la projekton"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr "Konservi ĉenstablan planon"
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Plani novan ĉenstablon"
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Planado de la ĉenstabloj"
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr "Elektu horzonon"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuÅi per %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Kreu %{new_merge_request} kun ĉi tiuj ÅanÄoj"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Iri al branĉo/etikedo"
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr "La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "La rilato de disbranĉigo estis forigita."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo Äis la atribuado de la problemo al cela etapo de la projekto, aÅ­ al listo sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi tiu etapo."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr "Ĉiu ensalutita uzanto havas atingon al la projekto"
msgid "The project can be accessed without any authentication."
msgstr "Ĉiu povas havi atingon al la projekto, sen ensaluti"
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "La deponejo por ĉi tiu projekto ne ekzistas."
@@ -9423,6 +11105,21 @@ msgstr "La etapo de la kontrolo montras la tempon de la kreado de la peto pri ku
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "La etapo de preparo por eldono montras la tempon inter la aplikado de la peto pri kunfando kaj la disponigado de la kodo en la publika versio. La datenoj aldoniÄos aÅ­tomate post kiam vi faros la unuan disponigadon en la publika versio."
@@ -9438,9 +11135,18 @@ msgstr "La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr "Ĉi tiu signifas, ke vi ne povos alpuÅi kodon, antaÅ­ ol vi kreos malpl
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr "antaÅ­ %s monatoj"
msgid "Timeago|%s months remaining"
msgstr "restas %s monatoj"
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr "restas %s sekundoj"
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "alklaku por alÅuti"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "Nuligi la peton pri atingeblo"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr "Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Oni povas aldoni dosierojn nur kiam oni estas en branĉo"
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Vi ne povas krei pliajn projektojn"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "VI bezonas permeson."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "VI ne ricevos sciigojn per retpoÅto"
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Via nomo"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index ae1db647187..0775ef83503 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 10:21\n"
+"PO-Revision-Date: 2019-04-12 12:32\n"
+
+msgid " Please sign in."
+msgstr " Por favor, inicie sesión."
msgid " Status"
msgstr " Estado"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " y"
@@ -32,16 +41,18 @@ msgstr[0] " mejorado en %d punto"
msgstr[1] " mejorado en %d puntos"
msgid " or "
-msgstr ""
+msgstr " o "
msgid " or <#epic id>"
-msgstr ""
+msgstr " o <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " o <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" en proyectos"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d comentario"
+msgstr[1] "%d comentarios"
msgid "%d commit"
msgid_plural "%d commits"
@@ -54,7 +65,7 @@ msgstr[0] "%d commit detrás"
msgstr[1] "%d commits detrás"
msgid "%d commits"
-msgstr ""
+msgstr "%d commits"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
-msgid "%{count} more"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
msgstr ""
+msgid "%{count} more"
+msgstr "%{count} más"
+
msgid "%{count} more assignees"
+msgstr "%{count} asignados más"
+
+msgid "%{count} of %{required} approvals from %{name}"
msgstr ""
+msgid "%{count} of %{total}"
+msgstr "%{count} de %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} participante"
@@ -148,8 +173,8 @@ msgstr "%{filePath} eliminado"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} más"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "No es posible encontrar %{gitlab_ci_yml} en este commit"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "¡Va a eliminar %{issuableType}! ¿Está seguro de que desea realizar esta acción?"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} no disponible"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr "%{loadingIcon} Iniciado"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} está bloqueado por el usuario de GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Avatar de %{name}"
@@ -178,9 +212,18 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% completado"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
+msgid "%{state} epics"
+msgstr "%{state} épicas"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
msgstr[0] ""
@@ -219,17 +262,23 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
-msgid "(external source)"
+msgid "(No changes)"
msgstr ""
+msgid "(external source)"
+msgstr "(fuente externa)"
+
msgid "+ %{count} more"
msgstr "+ %{count} más"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} más"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} más"
+
msgid ", or "
-msgstr ""
+msgstr ", o "
msgid "- Runner is active and can process any new jobs"
msgstr "- El ejecutor está activo y puede procesar cualquier trabajo nuevo"
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] "1 %{type} modificación"
msgstr[1] "%{count} %{type} modificaciones"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "1 Día"
+msgstr[1] "%d Días"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 incidencia cerrada"
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr "1 día"
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 grupo"
@@ -295,17 +352,29 @@ msgid_plural "%d users"
msgstr[0] "1 usuario"
msgstr[1] "%d usuarios"
+msgid "1 week"
+msgstr "1 semana"
+
msgid "1st contribution!"
msgstr "¡1ra contribución!"
msgid "2FA"
-msgstr ""
+msgstr "2FA"
msgid "2FA enabled"
msgstr "Verificación en 2 pasos habilitada"
+msgid "3 days"
+msgstr "3 días"
+
+msgid "3 hours"
+msgstr "3 horas"
+
+msgid "30 minutes"
+msgstr "30 minutos"
+
msgid "403|Please contact your GitLab administrator to get permission."
-msgstr ""
+msgstr "Por favor póngase en contacto con su administrador de GitLab para obtener los permisos necesarios."
msgid "403|You don't have the permission to access this page."
msgstr "No tiene permisos para acceder a esta página."
@@ -319,6 +388,9 @@ msgstr "Página no encontrada"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "Por favor, contacte con su administrador de GitLab si cree que se trata de un error."
+msgid "8 hours"
+msgstr "8 horas"
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,8 +424,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Una colección de gráficos sobre Integración Continua"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr "Una plantilla de aplicación de consola .NET Core, personalizable para cualquier proyecto .NET Core"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un sitio basado en GitBook que utiliza Netlify como herramienta de CI/CD en lugar de GitLab, pero con todas las otras excelentes características de GitLab."
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un sitio basado en Hexo que usa Netlify como herramienta de CI/CD en lugar de GitLab, pero con todas las otras excelentes características de GitLab."
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un sitio basado en Hugo que usa Netlify como herramienta de CI/CD en lugar de GitLab, pero con todas las otras excelentes características de GitLab."
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un sitio basado en Jekyll que usa Netlify como herramienta de CI/CD en lugar de GitLab, pero con todas las otras excelentes características de GitLab."
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
@@ -364,18 +448,39 @@ msgstr "Un usuario eliminado"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "Un miembro del equipo de GitLab revisará tus comentarios tan pronto como sea posible."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "Acerca de Gitlab"
@@ -395,7 +500,7 @@ msgid "Abuse reports"
msgstr ""
msgid "Accept invitation"
-msgstr ""
+msgstr "Aceptar invitación"
msgid "Accept terms"
msgstr "Aceptar los términos"
@@ -406,12 +511,18 @@ msgstr "Merge Request aceptado"
msgid "Access Tokens"
msgstr "Tokens de acceso"
-msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgid "Access denied for your LDAP account."
msgstr ""
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr "¡Acceso denegado! Por favor, verifique que puede agregar las claves de despliegue a este repositorio."
+
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr "Cuenta"
msgid "Account and limit"
msgstr "Cuenta y límite"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Activo"
@@ -434,7 +551,7 @@ msgid "Add"
msgstr "Añadir"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Añadir CHANGELOG"
msgid "Add CONTRIBUTING"
msgstr ""
@@ -449,7 +566,10 @@ msgid "Add Kubernetes cluster"
msgstr "Añadir clúster de Kubernetes"
msgid "Add README"
-msgstr ""
+msgstr "Añadir README"
+
+msgid "Add a bullet list"
+msgstr "Añadir una lista de viñetas"
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
@@ -457,35 +577,59 @@ msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr "Añadir un enlace"
+
+msgid "Add a numbered list"
+msgstr "Añadir una lista numerada"
+
msgid "Add a table"
msgstr "Añadir tabla"
+msgid "Add a task list"
+msgstr "Añadir una lista de tareas"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr "Añadir texto en negrita"
+
msgid "Add comment now"
msgstr "Añadir comentario ahora"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "Añadir comentario a la imagen"
+msgid "Add italic text"
+msgstr "Añadir texto en cursiva"
+
msgid "Add license"
msgstr "Añadir licencia"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Añadir una nueva aplicación"
msgid "Add new directory"
msgstr "Agregar nuevo directorio"
-msgid "Add projects"
-msgstr "Añadir proyectos"
-
msgid "Add reaction"
msgstr "Añadir reacción"
msgid "Add to project"
-msgstr ""
+msgstr "Añadir al proyecto"
msgid "Add to review"
msgstr "Añadir a revisión"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr "Agregar usuarios al grupo"
+msgid "Added at"
+msgstr "Añadido el"
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr "Texto adicional"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "Ãrea de administración"
msgid "Admin Overview"
msgstr "Vista de administrador"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -548,10 +701,13 @@ msgid "AdminProjects|Delete project"
msgstr "Eliminar proyecto"
msgid "AdminSettings|Auto DevOps domain"
+msgstr "Dominio Auto DevOps"
+
+msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Las variables de entorno están protegidas por defecto"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr ""
@@ -560,25 +716,25 @@ msgid "AdminSettings|When creating a new environment variable it will be protect
msgstr ""
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA desactivado"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA Habilitado"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Activo"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "Admin"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "Administradores"
msgid "AdminUsers|Block user"
msgstr "Bloquear usuario"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Bloqueado"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
msgstr ""
@@ -596,58 +752,70 @@ msgid "AdminUsers|Delete user and contributions"
msgstr ""
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Externos"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "¡Es usted!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Nuevo usuario"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "No se encontraron usuarios"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "Búsqueda por nombre, correo electrónico o nombre de usuario"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "Buscar usuarios"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Enviar correo electrónico a los usuarios"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Ordenar por"
msgid "AdminUsers|To confirm, type %{projectName}"
-msgstr ""
+msgstr "Para confirmar, escriba %{projectName}"
msgid "AdminUsers|To confirm, type %{username}"
-msgstr ""
+msgstr "Para confirmar, escriba %{username}"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "El usuario será bloqueado"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "Sin proyectos"
+
+msgid "Advanced"
+msgstr "Avanzado"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Permisos avanzados, almacenamiento de grandes archivos y configuraciones de autenticación de dos factores."
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Configuración avanzada"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Alerta"
msgstr[1] "Alertas"
msgid "Alerts"
-msgstr ""
+msgstr "Alertas"
msgid "All"
msgstr "Todos"
+msgid "All Members"
+msgstr "Todos los miembros"
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr "Todas las funcionalidades están habilitadas para proyectos en blanco, p
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "Todos los usuarios"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Permitir a los usuarios solicitar acceso"
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr "Se ha producido un error"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Se ha producido un error al agregar un borrador a la discusión."
@@ -727,7 +919,16 @@ msgid "An error occurred adding a new draft."
msgstr "Se ha producido un error al añadir un nuevo borrador."
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "Se ha producido un error al crear una rama nueva."
+
+msgid "An error occurred fetching the approval rules."
+msgstr "Se ha producido un error al obtener las reglas de aprobación."
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "Se ha producido un error al obtener los aprobadores para la nueva regla."
+
+msgid "An error occurred fetching the dropdown data."
+msgstr "Se ha producido un error al recuperar los datos de la lista desplegable."
msgid "An error occurred previewing the blob"
msgstr "Ha ocurrido un error visualizando el blob"
@@ -741,16 +942,25 @@ msgstr "Se ha producido un error al actualizar el tamaño de la incidencia"
msgid "An error occurred while adding approver"
msgstr "Se ha producido un error al añadir el aprobador."
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "Se ha producido un error al eliminar el comentario"
msgid "An error occurred while detecting host keys"
msgstr "Se ha producido un error al detectar las claves del host."
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Se ha producido un error al desactivar la alerta. Actualice la página y vuelva a intentarlo."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr "Se ha producido un error al desactivar la función resaltar. Actualice la página y vuelva a intentarlo."
+
+msgid "An error occurred while enabling Service Desk."
msgstr ""
msgid "An error occurred while fetching markdown preview"
@@ -763,6 +973,9 @@ msgid "An error occurred while fetching sidebar data"
msgstr "Se ha producido un error al obtener los datos de la barra lateral."
msgid "An error occurred while fetching stages."
+msgstr "Se ha producido un error al obtener las etapas del pipeline."
+
+msgid "An error occurred while fetching the Service Desk address."
msgstr ""
msgid "An error occurred while fetching the job log."
@@ -777,6 +990,9 @@ msgstr "Se ha producido un error al obtener los trabajos."
msgid "An error occurred while fetching the pipeline."
msgstr "Se ha producido un error mientras se obtenía el pipeline."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "Se ha producido un error al obtener los proyectos"
@@ -792,6 +1008,9 @@ msgstr "Se ha producido un error al cargar los datos del gráfico."
msgid "An error occurred while loading commit signatures"
msgstr "Se ha producido un error al cargar las firmas de los commits"
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "Se ha producido un error al cargar el diff"
@@ -802,7 +1021,7 @@ msgid "An error occurred while loading the file"
msgstr "Se ha producido un error al cargar el archivo"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "Se ha producido un error al cargar los detalles de la suscripción."
msgid "An error occurred while making the request."
msgstr "Se ha producido un error al crear la petición."
@@ -811,7 +1030,7 @@ msgid "An error occurred while removing approver"
msgstr "Se ha producido un error al eliminar el aprobador."
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "Se ha producido un error al eliminar las tareas épicas."
msgid "An error occurred while removing issues."
msgstr ""
@@ -834,12 +1053,18 @@ msgstr "Se ha producido un error al guardar el estado de LDAP. Por favor, intén
msgid "An error occurred while saving assignees"
msgstr "Se ha producido un error al guardar las asignaciones"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "Se ha producido un error al suscribirse a las notificaciones."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Se ha producido un error al cancelar la suscripción a las notificaciones."
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "Se ha producido un error al actualizar el comentario"
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr "Analíticas"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Anónimo"
@@ -909,15 +1137,33 @@ msgstr "Cualquiera"
msgid "Any Label"
msgstr "Cualquier etiqueta"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Apariencia"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Aplicación"
msgid "Application ID"
msgstr "ID de la aplicación"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Aplicación: %{name}"
@@ -925,14 +1171,52 @@ msgid "Applications"
msgstr "Aplicaciones"
msgid "Applied"
-msgstr ""
+msgstr "Aplicado"
msgid "Apply suggestion"
+msgstr "Aplicar sugerencia"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d miembro"
+msgstr[1] "%d miembros"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
msgstr ""
-msgid "Approvals"
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d miembro"
+msgstr[1] "%d miembros"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr "Miembros"
+
+msgid "ApprovalRule|Name"
msgstr ""
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approvals"
+msgstr "Aprobaciones"
+
msgid "Approvals required"
msgstr ""
@@ -945,13 +1229,28 @@ msgstr "Abril"
msgid "April"
msgstr "Abril"
+msgid "Archive jobs"
+msgstr "Archivar trabajos"
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "¡Proyecto archivado!. El repositorio y el resto de recursos del proyecto son de sólo lectura"
msgid "Archived projects"
msgstr "Proyectos archivados"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
+msgstr "¿Está seguro?"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
@@ -966,20 +1265,29 @@ msgstr "¿Estás seguro de que deseas descartar los cambios no guardados?"
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "¿Seguro que quiere regenerar la clave pública? Deberá actualizar la clave pública en el servidor remoto antes de que la duplicación funcione nuevamente."
msgid "Are you sure you want to remove %{group_name}?"
msgstr "¿Está seguro que desea eliminar %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "¿Está seguro que desea eliminar el archivo adjunto?"
msgid "Are you sure you want to remove this identity?"
msgstr "¿Está seguro de que desea eliminar esta identidad?"
@@ -987,6 +1295,9 @@ msgstr "¿Está seguro de que desea eliminar esta identidad?"
msgid "Are you sure you want to reset registration token?"
msgstr "¿Está seguro que desea reinicializar el token de registro?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "¿Está seguro que desea reinicializar el token de Verificación de Estado?"
@@ -997,7 +1308,7 @@ msgid "Are you sure you want to unlock %{path_lock_path}?"
msgstr "¿Está seguro que quieres desbloquear %{path_lock_path}?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "¿Está seguro de que desea darse de baja de la %{type}: %{link_to_noteable_text}?"
msgid "Are you sure?"
msgstr "¿Estás seguro?"
@@ -1041,9 +1352,6 @@ msgstr "Incidencias asignadas"
msgid "Assigned Merge Requests"
msgstr "Merge requests asignados"
-msgid "Assigned to :name"
-msgstr "Asignado a :name"
-
msgid "Assigned to me"
msgstr "Asignado a mí"
@@ -1059,9 +1367,12 @@ msgstr ""
msgid "Assignee(s)"
msgstr "Asignado(s)"
-msgid "Attach a file"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
msgstr ""
+msgid "Attach a file"
+msgstr "Adjuntar un archivo"
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Adjunte un archivo arrastrando &amp; soltando o %{upload_link}"
@@ -1074,18 +1385,24 @@ msgstr "Ago"
msgid "August"
msgstr "Agosto"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Registro de Autenticación"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "Log de autenticación"
msgid "Authentication method"
msgstr "Método de autenticación"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Autor"
@@ -1093,7 +1410,7 @@ msgid "Authorization code:"
msgstr "Código de autorización:"
msgid "Authorization key"
-msgstr ""
+msgstr "Clave de autorización"
msgid "Authorization was granted by entering your username and password in the application."
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr "Autorizar"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "¿Autorizar %{link_to_client} para utilizar su cuenta?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "Autorizado en"
@@ -1143,21 +1463,15 @@ msgstr "Más información en %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "añadir un clúster de Kubernetes"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "habilitar Auto DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "Marcado automáticamente como usuario interno por defecto"
-msgid "Automatically resolved"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
+msgid "Automatically resolved"
+msgstr "Resuelto automáticamente"
+
msgid "Available"
msgstr "Disponible"
@@ -1171,10 +1485,10 @@ msgid "Available specific runners"
msgstr ""
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "Avatar para %{assigneeName}"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "Avatar para %{name}"
msgid "Avatar will be removed. Are you sure?"
msgstr "El avatar será eliminado. ¿Está seguro?"
@@ -1278,6 +1592,12 @@ msgstr "¿Descartar revisión?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "Iniciar con el commit seleccionado"
@@ -1363,7 +1683,7 @@ msgid "Block"
msgstr ""
msgid "Blocked"
-msgstr ""
+msgstr "Bloqueado"
msgid "Blog"
msgstr "Blog"
@@ -1402,7 +1722,7 @@ msgid "Branches|Active branches"
msgstr "Branches activos"
msgid "Branches|All"
-msgstr ""
+msgstr "Todas"
msgid "Branches|Cant find HEAD commit for this branch"
msgstr "No puedo encontrar el commit HEAD para esta rama"
@@ -1435,40 +1755,40 @@ msgid "Branches|Filter by branch name"
msgstr "Filtrar por nombre de rama"
msgid "Branches|Merged into %{default_branch}"
-msgstr ""
+msgstr "Fusionadas con %{default_branch}"
msgid "Branches|New branch"
-msgstr ""
+msgstr "Nueva rama"
msgid "Branches|No branches to show"
-msgstr ""
+msgstr "No hay ramas para mostrar"
msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
-msgstr ""
+msgstr "Una vez que confirme y pulse %{delete_protected_branch}, no se podrá ni deshacer ni recuperar la operación."
msgid "Branches|Only a project maintainer or owner can delete a protected branch"
-msgstr ""
+msgstr "Sólo el responsable del proyecto o el propietario pueden borrar una rama protegida"
msgid "Branches|Overview"
msgstr "Resumen"
msgid "Branches|Protected branches can be managed in %{project_settings_link}."
-msgstr ""
+msgstr "Puede gestionar las ramas protegidas desde %{project_settings_link}."
msgid "Branches|Show active branches"
-msgstr ""
+msgstr "Mostrar ramas activas"
msgid "Branches|Show all branches"
-msgstr ""
+msgstr "Mostrar todas las ramas"
msgid "Branches|Show more active branches"
-msgstr ""
+msgstr "Mostrar más ramas activas"
msgid "Branches|Show more stale branches"
-msgstr ""
+msgstr "Mostrar más ramas obsoletas"
msgid "Branches|Show overview of the branches"
-msgstr ""
+msgstr "Mostrar un resumen de las ramas"
msgid "Branches|Show stale branches"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr "configuración del proyecto"
msgid "Branches|protected"
msgstr "protegido"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Examinar directorio"
@@ -1528,33 +1854,45 @@ msgid "Browse files"
msgstr "Examinar archivos"
msgid "Built-in"
-msgstr ""
+msgstr "Integrado"
msgid "Business"
-msgstr ""
+msgstr "Negocio"
msgid "Business metrics (Custom)"
msgstr "Métricas de negocio (personalizadas)"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
+msgstr "Por %{user_name}"
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
msgid "ByAuthor|by"
msgstr "por"
msgid "CHANGELOG"
-msgstr ""
+msgstr "CHANGELOG"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "Gráficos de CI/CD"
msgid "CI / CD Settings"
msgstr "Configuración de CI/CD"
msgid "CI Lint"
+msgstr "Cl Lint"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "Estrategia de despliegue"
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "Instancia habilitada"
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr "No se puede encontrar el commit HEAD para este 'branch'"
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,24 +1979,39 @@ msgstr "Cancelar este trabajo"
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "No se puede modificar el clúster de Kubernetes gestionado"
-msgid "Certificate"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Huella digital del certificado"
+msgid "Certificate"
+msgstr "Certificado"
+
+msgid "Certificate (PEM)"
+msgstr "Certificado (PEM)"
msgid "Change Weight"
msgstr "Cambiar peso"
-msgid "Change permissions"
+msgid "Change path"
msgstr ""
+msgid "Change permissions"
+msgstr "Modificar los permisos"
+
msgid "Change template"
msgstr "Cambiar plantilla"
@@ -1671,11 +2033,14 @@ msgstr "Revertir"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr "Cambios"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
msgid "Changes suppressed. Click to show."
-msgstr ""
+msgstr "Cambios eliminados. Haga clic para mostrar."
msgid "Charts"
msgstr "Gráficos"
@@ -1683,11 +2048,17 @@ msgstr "Gráficos"
msgid "Chat"
msgstr "Chat"
+msgid "Check again"
+msgstr "Verficar de nuevo"
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr "Compruebe su fichero .gitlab-ci.yml"
msgid "Checking %{text} availability…"
msgstr "Comprobando disponibilidad de %{text}..."
@@ -1710,14 +2081,11 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr "Elegir archivo ..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Elija un branch/tag (por ejemplo, %{master}) o introduzca un commit (por ejemplo, %{sha}) para ver lo qué ha cambiado o para crear un merge request."
msgid "Choose a file"
-msgstr ""
+msgstr "Seleccione un archivo"
msgid "Choose a role permission"
msgstr ""
@@ -1734,27 +2102,27 @@ msgstr "Elegir cualquier color."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
-msgstr "Elegir archivo..."
+msgid "Choose file…"
+msgstr "Seleccione un archivo…"
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
-msgstr "Seleccione que repositorios desea importar."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "cancelado"
@@ -1779,6 +2147,9 @@ msgstr "pasó con advertencias"
msgid "CiStatusLabel|pending"
msgstr "pendiente"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "omitido"
@@ -1812,6 +2183,9 @@ msgstr "pasó"
msgid "CiStatusText|pending"
msgstr "pendiente"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "omitido"
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (Todos los entornos)"
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,17 +2228,23 @@ msgstr "Protegido"
msgid "CiVariable|Search environments"
msgstr "Buscar entornos"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr "Error de validación"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "no está disponible: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "Limpiar"
msgid "Clear input"
msgstr ""
@@ -1903,16 +2289,19 @@ msgid "Clients"
msgstr "Clientes"
msgid "Clone"
-msgstr ""
+msgstr "Clonar"
msgid "Clone repository"
msgstr "Clonar repositorio"
msgid "Clone with %{http_label}"
-msgstr ""
+msgstr "Clonar con %{http_label}"
+
+msgid "Clone with KRB5"
+msgstr "Clonar con KRB5"
msgid "Clone with SSH"
-msgstr ""
+msgstr "Clonar con SSH"
msgid "Close"
msgstr "Cerrar"
@@ -1921,14 +2310,11 @@ msgid "Close epic"
msgstr "Cerrar épica"
msgid "Close milestone"
-msgstr ""
+msgstr "Cerrar hito"
msgid "Closed"
msgstr "Cerrado"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Incidencias cerradas"
@@ -1942,10 +2328,7 @@ msgid "ClusterIntegration| is the default environment scope for this cluster. Th
msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
-msgstr ""
-
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
+msgstr "%{appList} se instaló correctamente en su clúster de Kubernetes"
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
@@ -1953,6 +2336,9 @@ msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Añadir cluster de Kubernetes"
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
-msgstr ""
+msgstr "Alternativamente"
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -1987,16 +2370,16 @@ msgid "ClusterIntegration|An error occurred while trying to fetch zone machine t
msgstr ""
msgid "ClusterIntegration|Applications"
-msgstr ""
+msgstr "Aplicaciones"
msgid "ClusterIntegration|Apply for credit"
msgstr ""
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
-msgstr ""
+msgstr "¿Está seguro de que desea eliminar la integración con este clúster de Kubernetes? Esta acción no eliminará su clúster de Kubernetes."
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "Dominio base"
msgid "ClusterIntegration|CA Certificate"
msgstr "Certificado CA"
@@ -2008,7 +2391,7 @@ msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate manage
msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
-msgstr ""
+msgstr "paquete de certificado de autoridad (formato PEM)"
msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
msgstr ""
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr "Estado de salud del cluster"
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,20 +2414,20 @@ msgstr "Copiar la URL del API"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Copiar Certificado CA"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Copiar la dirección IP de entrada al portapapeles"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Copiar el nombre del host de Jupyter en el portapapeles"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Copiar el nombre del clúster Kubernetes"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Copiar token"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Crear cluster de Kubernetes"
@@ -2074,10 +2463,10 @@ msgid "ClusterIntegration|Fetching machine types"
msgstr ""
msgid "ClusterIntegration|Fetching projects"
-msgstr ""
+msgstr "Obtener proyectos"
msgid "ClusterIntegration|Fetching zones"
-msgstr ""
+msgstr "Obtener zonas"
msgid "ClusterIntegration|GitLab Integration"
msgstr "Integración GitLab"
@@ -2085,7 +2474,7 @@ msgstr "Integración GitLab"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,14 +2501,14 @@ msgstr "Ocultar"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Entrada"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Dirección IP de entrada"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr "Instalar"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr "Instalado"
msgid "ClusterIntegration|Installing"
msgstr "Instalando"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr "cluster de Kubernetes"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Detalles del cluster de Kubernetes"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2206,7 +2595,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "Más información sobre los grupos de clústeres de Kubernetes"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "Tipo de máquina"
@@ -2220,9 +2609,6 @@ msgstr "Administrar"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Administre su cluster de Kubernetes visitando %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Más información"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "No hay tipos de máquinas que coincidan con su búsqueda"
@@ -2235,27 +2621,24 @@ msgstr "No hay proyectos que coincidan con su búsqueda"
msgid "ClusterIntegration|No zones matched your search"
msgstr "No hay zonas que coincidan con su búsqueda"
-msgid "ClusterIntegration|Note:"
-msgstr "Nota:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Número de nodos"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Por favor, introduzca la información de acceso a su clúster de Kubernetes. Si necesita ayuda, puede leer nuestra %{link_to_help_page} en Kubernetes"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Asegúrese de que su cuenta de Google cumpla con los siguientes requisitos:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "Clúster del proyecto"
-msgid "ClusterIntegration|Project namespace"
-msgstr "Espacio de nombres del proyecto"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "Espacio de nombres del proyecto (opcional, único)"
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Falló la solicitud para iniciar la instalación"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,8 +2705,11 @@ msgstr "Seleccione la zona"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Seleccione la zona para elegir el tipo de máquina"
-msgid "ClusterIntegration|Service token"
-msgstr "Token de servicio"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Mostrar"
@@ -2331,9 +2717,6 @@ msgstr "Mostrar"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Algo salió mal de nuestro lado."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr ""
@@ -2343,32 +2726,38 @@ msgstr "Algo salió mal durante la instalación de %{title}"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "La dirección IP está en proceso de ser asignada. Por favor, verifique su clúster de Kubernetes o las cuotas en Google Kubernetes Engine si este proceso lleva mucho tiempo"
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
-msgstr ""
+msgstr "Esta cuenta debe tener permisos para crear un clúster de Kubernetes en el %{link_to_container_project} especificado a continuación"
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr "Activar/Desactivar el clúster de Kubernetes"
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
-msgid "ClusterIntegration|Token"
-msgstr "Token"
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
-msgstr ""
+msgstr "Actualización"
msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
+msgstr "Actualización fallida"
msgid "ClusterIntegration|Upgraded"
-msgstr ""
+msgstr "Actualizado"
msgid "ClusterIntegration|Upgrading"
-msgstr ""
+msgstr "Actualizando"
msgid "ClusterIntegration|Validating project billing status"
msgstr "Validación del estado de facturación del proyecto"
@@ -2394,9 +2783,6 @@ msgstr "Zona"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "acceso a Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "consulte los precios aqui"
-
msgid "ClusterIntegration|documentation"
msgstr "documentación"
@@ -2406,6 +2792,9 @@ msgstr "página de ayuda"
msgid "ClusterIntegration|meets the requirements"
msgstr "cumple con los requisitos"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "configurado correctamente"
@@ -2413,31 +2802,43 @@ msgid "ClusterIntegration|sign up"
msgstr "regístrese"
msgid "Code"
-msgstr ""
+msgstr "Código"
+
+msgid "Code Owners"
+msgstr "Propietarios del código"
+
+msgid "Code owner approval is required"
+msgstr "Se requiere la aprobación del propietario del código"
msgid "Code owners"
msgstr "Propietarios del código"
+msgid "CodeOwner|Pattern"
+msgstr "Patrón"
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr "Contraer"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "Contraer la barra lateral"
msgid "Command line instructions"
-msgstr ""
+msgstr "Instrucciones de la línea de comando"
msgid "Comment"
msgstr "Comentar"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "Comentar y cerrar %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "Comentar y volver a abrir %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "Comentar y resolver la discusión"
@@ -2457,13 +2858,13 @@ msgstr[0] "Cambio"
msgstr[1] "Cambios"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "Commit %{commit_id}"
msgid "Commit Message"
msgstr "Mensaje del commit"
msgid "Commit deleted"
-msgstr ""
+msgstr "Commit borrado"
msgid "Commit duration in minutes for last 30 commits"
msgstr "Duración de los cambios en minutos para los últimos 30"
@@ -2472,7 +2873,7 @@ msgid "Commit message"
msgstr "Mensaje del cambio"
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "Estadísticas de los commits para %{ref} %{start_time} - %{end_time}"
msgid "Commit to %{branchName} branch"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr "Enviado por"
msgid "Commit…"
msgstr "Commit..."
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Comparar"
@@ -2529,7 +2933,7 @@ msgid "Compare Revisions"
msgstr "Comparar revisiones"
msgid "Compare changes"
-msgstr ""
+msgstr "Comparar cambios"
msgid "Compare changes with the last commit"
msgstr "Comparar los cambios con el último commit"
@@ -2562,7 +2966,7 @@ msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Lea
msgstr ""
msgid "Configure Gitaly timeouts."
-msgstr ""
+msgstr "Configurar los tiempos de espera de Gitaly."
msgid "Configure Tracing"
msgstr ""
@@ -2583,11 +2987,17 @@ msgid "Configure storage path settings."
msgstr ""
msgid "Configure the %{link} integration."
-msgstr ""
+msgstr "Configurar la %{link} integración."
msgid "Configure the way a user creates a new account."
msgstr "Configure la forma en la que un usuario crea una cuenta nueva."
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Conectar"
@@ -2603,12 +3013,24 @@ msgstr "Conecte sus repositorios externos, y los pipelines de CI/CD se ejecutarÃ
msgid "Connecting..."
msgstr "Conectando..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "Registro de contenedores"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Creado"
@@ -2655,7 +3077,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr ""
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr "Contenido de .gitlab-ci.yml"
msgid "Continue"
msgstr "Continuar"
@@ -2664,14 +3086,17 @@ msgid "Continue to the next step"
msgstr "Continuar con el siguiente paso"
msgid "Continuous Integration and Deployment"
-msgstr ""
+msgstr "Integración y entrega continua"
msgid "Contribute to GitLab"
-msgstr ""
+msgstr "Contribuir a GitLab"
msgid "Contribution"
msgstr "Contribución"
+msgid "Contribution Analytics"
+msgstr "Análisis de contribución"
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr "Espere un momento; esta página se actualizará automáticamente cuando
msgid "Control the display of third party offers."
msgstr "Controlar la visualización de las ofertas de terceros."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "Ãndice ConvDev"
@@ -2723,11 +3139,14 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr "Copiar el ID a portapapeles"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
msgid "Copy SSH public key"
-msgstr ""
+msgstr "Copiar la clave pública SSH"
msgid "Copy SSH public key to clipboard"
msgstr "Copiar la clave pública SSH al portapapeles"
@@ -2747,9 +3166,6 @@ msgstr "Copiar SHA del cambio al portapapeles"
msgid "Copy file path to clipboard"
msgstr "Copie la ruta del archivo al portapapeles"
-msgid "Copy incoming email address to clipboard"
-msgstr "Copiar la dirección de corro electrónico al portapapeles"
-
msgid "Copy link"
msgstr "Copiar enlace"
@@ -2765,9 +3181,33 @@ msgstr "Copiar al portapapeles"
msgid "Copy token to clipboard"
msgstr "Copiar token al portapapeles"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr "Crear"
@@ -2775,26 +3215,29 @@ msgid "Create New Directory"
msgstr "Crear Nuevo Directorio"
msgid "Create New Domain"
+msgstr "Crear un nuevo dominio"
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
msgid "Create a new branch"
msgstr "Crear un nuevo 'branch'"
-msgid "Create a new branch and merge request"
-msgstr "Crear un nuevo 'branch' y 'merge request'"
-
msgid "Create a new issue"
msgstr "Crear una nueva incidencia"
msgid "Create a new repository"
-msgstr ""
+msgstr "Crear un nuevo repositorio"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a través de %{protocol}."
-msgid "Create branch"
+msgid "Create an issue. Issues are created for each alert triggered."
msgstr ""
+msgid "Create branch"
+msgstr "Crear rama"
+
msgid "Create commit"
msgstr "Crear commit"
@@ -2816,9 +3259,6 @@ msgstr "Crear grupo"
msgid "Create group label"
msgstr "Crear etiqueta de grupo"
-msgid "Create issue"
-msgstr "Crear incidencia"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2829,7 +3269,7 @@ msgid "Create merge request and branch"
msgstr ""
msgid "Create milestone"
-msgstr ""
+msgstr "Crear hito"
msgid "Create new branch"
msgstr "Crear una nueva rama"
@@ -2853,7 +3293,7 @@ msgid "Create project label"
msgstr "Crear etiqueta de proyecto"
msgid "Create your first page"
-msgstr ""
+msgstr "Crea tu primera página"
msgid "CreateTag|Tag"
msgstr "Etiqueta"
@@ -2888,14 +3328,17 @@ msgstr "Sintaxis de Cron"
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "Nodo actual"
msgid "CurrentUser|Profile"
-msgstr ""
+msgstr "Perfil"
msgid "CurrentUser|Settings"
-msgstr ""
+msgstr "Configuración"
msgid "Custom CI config path"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2961,7 +3401,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "Pruebas"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "Panel de control"
@@ -2972,11 +3412,14 @@ msgstr "Todos"
msgid "DashboardProjects|Personal"
msgstr "Personales"
-msgid "Data is still calculating..."
+msgid "Dashboards"
msgstr ""
+msgid "Data is still calculating..."
+msgstr "Los datos aún se están calculando..."
+
msgid "Date picker"
-msgstr ""
+msgstr "Selector de fecha"
msgid "Debug"
msgstr "Depurar"
@@ -2988,15 +3431,21 @@ msgid "December"
msgstr "Diciembre"
msgid "Decline"
-msgstr ""
+msgstr "Declinar"
msgid "Decline and sign out"
msgstr "Rechazar y cerrar sesión"
msgid "Default Branch"
+msgstr "Branch por defecto"
+
+msgid "Default artifacts expiration"
msgstr ""
msgid "Default classification label"
+msgstr "Etiqueta de clasificación por defecto"
+
+msgid "Default description template for issues"
msgstr ""
msgid "Default first day of the week"
@@ -3005,9 +3454,12 @@ msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
-msgid "Default: Directly import the Google Code email address or username"
+msgid "Default issue template"
msgstr ""
+msgid "Default: Directly import the Google Code email address or username"
+msgstr "Por defecto: Importar directamente el nombre de usuario o la dirección de correo electrónico de Google Code"
+
msgid "Default: Map a FogBugz account ID to a full name"
msgstr ""
@@ -3024,13 +3476,13 @@ msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This jo
msgstr ""
msgid "DelayedJobs|Start now"
-msgstr ""
+msgstr "Comenzar ahora"
msgid "DelayedJobs|Unschedule"
msgstr ""
msgid "DelayedJobs|delayed"
-msgstr ""
+msgstr "retrasado"
msgid "Delete"
msgstr "Eliminar"
@@ -3044,18 +3496,36 @@ msgstr "Eliminar fragmento de código"
msgid "Delete comment"
msgstr "Eliminar comentario"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Eliminar lista"
msgid "Delete source branch"
-msgstr ""
+msgstr "Eliminar rama origen"
msgid "Delete this attachment"
-msgstr ""
+msgstr "Eliminar este adjunto"
msgid "Deleted"
msgstr "Eliminado"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "Denegar"
@@ -3067,6 +3537,9 @@ msgstr[1] "Despliegues"
msgid "Deploy Keys"
msgstr "Claves de despliegue"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} otros"
@@ -3113,7 +3586,7 @@ msgid "DeployKeys|Write access allowed"
msgstr "Acceso de escritura permitido"
msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
-msgstr ""
+msgstr "Está a punto de eliminar esta clave de despliegue. ¿Está seguro?"
msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
msgstr ""
@@ -3128,10 +3601,10 @@ msgid "DeployTokens|Allows read-only access to the repository"
msgstr ""
msgid "DeployTokens|Copy deploy token to clipboard"
-msgstr ""
+msgstr "Copiar el token de despliegue al portapapeles"
msgid "DeployTokens|Copy username to clipboard"
-msgstr ""
+msgstr "Copiar el nombre de usuario al portapapeles"
msgid "DeployTokens|Create deploy token"
msgstr "Crear un token de despliegue"
@@ -3152,31 +3625,31 @@ msgid "DeployTokens|Name"
msgstr "Nombre"
msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
-msgstr ""
+msgstr "Seleccione un nombre para la aplicación, y le proporcionaremos un token de despliegue único."
msgid "DeployTokens|Revoke"
msgstr "Revocar"
msgid "DeployTokens|Revoke %{name}"
-msgstr ""
+msgstr "Revocar %{name}"
msgid "DeployTokens|Scopes"
msgstr ""
msgid "DeployTokens|This action cannot be undone."
-msgstr ""
+msgstr "Esta acción no se puede deshacer."
msgid "DeployTokens|This project has no active Deploy Tokens."
-msgstr ""
+msgstr "Este proyecto no tiene ningun token de despligue activo."
msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
-msgstr ""
+msgstr "Utilice este token como contraseña. Asegúrese de guardarlo ya que no podrá acceder a él nuevamente."
msgid "DeployTokens|Use this username as a login."
-msgstr ""
+msgstr "Utilizar este nombre de usuario para el inicio de sesión."
msgid "DeployTokens|Username"
-msgstr ""
+msgstr "Nombre de usuario"
msgid "DeployTokens|You are about to revoke"
msgstr ""
@@ -3188,37 +3661,70 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr ""
msgid "Deployed"
-msgstr ""
+msgstr "Desplegado"
msgid "Deployed to"
-msgstr ""
+msgstr "Desplegado en"
msgid "Deploying to"
-msgstr ""
+msgstr "Desplegando en"
msgid "Deprioritize label"
msgstr ""
msgid "Descending"
-msgstr ""
+msgstr "Descendente"
msgid "Description"
msgstr "Descripción"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
+msgstr "Descripción:"
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
msgstr ""
-msgid "Destroy"
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
msgstr ""
+msgid "Designs"
+msgstr "Diseños"
+
+msgid "Destroy"
+msgstr "Destruir"
+
msgid "Details"
msgstr "Detalles"
msgid "Details (default)"
-msgstr ""
+msgstr "Detalles (por defecto)"
msgid "Detect host keys"
msgstr ""
@@ -3254,34 +3760,34 @@ msgid "Disable shared Runners"
msgstr ""
msgid "Disabled"
-msgstr ""
+msgstr "Deshabilitado"
msgid "Discard"
-msgstr ""
+msgstr "Descartar"
msgid "Discard all changes"
-msgstr ""
+msgstr "Descartar todos los cambios"
msgid "Discard all unstaged changes?"
msgstr ""
msgid "Discard changes"
-msgstr ""
+msgstr "Descartar los cambios"
msgid "Discard changes to %{path}?"
-msgstr ""
+msgstr "¿Descartar los cambios en %{path}?"
msgid "Discard draft"
-msgstr ""
+msgstr "Descartar borrador"
msgid "Discard review"
-msgstr ""
+msgstr "Descartar la revisión"
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "Descubra GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
-msgstr ""
+msgstr "Descubra proyectos, grupos y fragmentos. Comparta sus proyectos con otros"
msgid "Discuss a specific suggestion or question"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr "Discusión"
+
msgid "Dismiss"
msgstr "Descartar"
@@ -3323,11 +3832,17 @@ msgid "Download"
msgstr "Descargar"
msgid "Download artifacts"
-msgstr ""
+msgstr "Descargar artefactos"
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr "Descargar tar"
@@ -3365,19 +3880,22 @@ msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use
msgstr ""
msgid "Each Runner can be in one of the following states:"
-msgstr ""
+msgstr "Cada ejecutor puede estar en uno de los siguientes estados:"
msgid "Edit"
msgstr "Editar"
msgid "Edit %{name}"
+msgstr "Editar %{name}"
+
+msgid "Edit Deploy Key"
msgstr ""
msgid "Edit Label"
msgstr "Editar etiqueta"
msgid "Edit Milestone"
-msgstr ""
+msgstr "Editar hito"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Editar Programación del Pipeline %{id}"
@@ -3389,10 +3907,13 @@ msgid "Edit application"
msgstr "Editar aplicación"
msgid "Edit comment"
-msgstr ""
+msgstr "Editar comentario"
msgid "Edit environment"
-msgstr ""
+msgstr "Editar el entorno"
+
+msgid "Edit file"
+msgstr "Editar archivo"
msgid "Edit files in the editor and commit changes here"
msgstr "Editar los archivos en el editor y confirmar los cambios aquí"
@@ -3404,7 +3925,10 @@ msgid "Edit identity for %{user_name}"
msgstr "Editar la identidad para %{user_name}"
msgid "Edit issues"
-msgstr ""
+msgstr "Editar incidencias"
+
+msgid "Edit public deploy key"
+msgstr "Editar clave pública de despliegue"
msgid "Elasticsearch"
msgstr "Elasticsearch"
@@ -3412,12 +3936,45 @@ msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Integración de Elastisearch. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "Correo electrónico"
+msgid "Email address"
+msgstr "Correo electrónico"
+
msgid "Email patch"
msgstr "Enviar parche por correo electrónico"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "Correos electrónicos"
@@ -3425,7 +3982,7 @@ msgid "Embed"
msgstr "Incrustado"
msgid "Empty file"
-msgstr ""
+msgstr "Archivo vacío"
msgid "Enable"
msgstr "Habilitar"
@@ -3433,6 +3990,9 @@ msgstr "Habilitar"
msgid "Enable Auto DevOps"
msgstr "Activar Auto DevOps"
+msgid "Enable HTML emails"
+msgstr "Habilitar correos HTML"
+
msgid "Enable Pseudonymizer data collection"
msgstr "Habilitar la recolección de datos Pseudonymizer"
@@ -3452,13 +4012,19 @@ msgid "Enable classification control using an external service"
msgstr "Habilitar control de clasificación utilizando un servicio externo"
msgid "Enable error tracking"
-msgstr ""
+msgstr "Habilitar seguimiento de errores"
msgid "Enable for this project"
msgstr "Habilitar para este proyecto"
msgid "Enable group Runners"
-msgstr ""
+msgstr "Habilitar el grupo de ejecutores"
+
+msgid "Enable header and footer in emails"
+msgstr "Habilitar el encabezado y pie de página en los correos electrónicos"
+
+msgid "Enable mirror configuration"
+msgstr "Habilitar configuración de espejo"
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Habilitado"
-msgid "Ends at (UTC)"
-msgstr "Finaliza a las (UTC)"
+msgid "Enabled Git access protocols"
+msgstr "Protocolos de acceso Git habilitados"
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr "Finaliza a las (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,20 +4089,14 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
msgid "Environment:"
-msgstr ""
+msgstr "Entorno:"
msgid "Environments"
msgstr "Entornos"
@@ -3541,20 +4110,26 @@ msgstr "Se ha producido un error al recuperar los entornos."
msgid "Environments|An error occurred while making the request."
msgstr "Se ha producido un error al realizar la solicitud."
-msgid "Environments|An error occurred while stopping the environment, please try again"
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
msgstr ""
-msgid "Environments|Are you sure you want to stop this environment?"
+msgid "Environments|An error occurred while rolling back the environment, please try again"
msgstr ""
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr "Ocurrió un error al detener el entorno, por favor inténtelo de nuevo"
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr "¿Está seguro de que desea detener este entorno?"
+
msgid "Environments|Commit"
msgstr "Commit"
msgid "Environments|Deploy to..."
-msgstr ""
+msgstr "Desplegar en..."
msgid "Environments|Deployment"
-msgstr ""
+msgstr "Despliegue"
msgid "Environments|Environment"
msgstr "Entorno"
@@ -3566,7 +4141,7 @@ msgid "Environments|Environments are places where code gets deployed, such as st
msgstr ""
msgid "Environments|Job"
-msgstr ""
+msgstr "Trabajo"
msgid "Environments|Learn more about stopping environments"
msgstr ""
@@ -3575,7 +4150,7 @@ msgid "Environments|New environment"
msgstr "Nuevo entorno"
msgid "Environments|No deployments yet"
-msgstr ""
+msgstr "Todavía no hay despliegues"
msgid "Environments|No pod name has been specified"
msgstr ""
@@ -3592,27 +4167,57 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
-msgid "Environments|Re-deploy to environment"
+msgid "Environments|Re-deploy"
msgstr ""
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr "Re-desplegar al entorno"
+
msgid "Environments|Read more about environments"
+msgstr "Obtenga más información sobre los entornos"
+
+msgid "Environments|Rollback"
msgstr ""
msgid "Environments|Rollback environment"
+msgstr "Deshacer entorno"
+
+msgid "Environments|Rollback environment %{environment_name}?"
msgstr ""
-msgid "Environments|Show all"
+msgid "Environments|Rollback environment %{name}?"
msgstr ""
+msgid "Environments|Show all"
+msgstr "Mostrar todo"
+
msgid "Environments|Stop"
-msgstr ""
+msgstr "Detener"
msgid "Environments|Stop environment"
-msgstr ""
+msgstr "Detener entorno"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Actualizado"
@@ -3620,7 +4225,7 @@ msgid "Environments|You don't have any environments right now"
msgstr ""
msgid "Environments|protected"
-msgstr ""
+msgstr "protegido"
msgid "Epic"
msgstr "Épica"
@@ -3638,10 +4243,10 @@ msgid "Epics|An error occurred while saving the %{epicDateType} date"
msgstr ""
msgid "Epics|How can I solve this?"
-msgstr ""
+msgstr "¿Cómo puedo resolver esto?"
msgid "Epics|More information"
-msgstr ""
+msgstr "Más información"
msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
msgstr ""
@@ -3653,7 +4258,7 @@ msgid "Epics|due"
msgstr ""
msgid "Epics|start"
-msgstr ""
+msgstr "Inicio"
msgid "Error"
msgstr "Error"
@@ -3664,11 +4269,14 @@ msgstr "Informe y registro de errores"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr "Se ha producido un error al crear una nueva ruta"
+
msgid "Error creating epic"
msgstr "Error al crear la tarea épica"
msgid "Error deleting %{issuableType}"
-msgstr ""
+msgstr "Se ha producido un error al eliminar %{issuableType}"
msgid "Error fetching contributors data."
msgstr "Se ha producido un error al obtener los datos de los colaboradores."
@@ -3686,56 +4294,104 @@ msgid "Error fetching usage ping data."
msgstr "Se ha producido un error al obtener los datos de uso de ping."
msgid "Error loading branch data. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar los datos de la rama. Por favor, vuelva a intentarlo."
msgid "Error loading branches."
-msgstr ""
+msgstr "Error al cargar ramas."
msgid "Error loading last commit."
-msgstr ""
+msgstr "Error al cargar el último commit."
msgid "Error loading markdown preview"
-msgstr ""
+msgstr "Se ha producido un error al cargar la vista previa de Markdown"
msgid "Error loading merge requests."
-msgstr ""
+msgstr "Se ha producido un error al cargar los merge requests."
msgid "Error loading project data. Please try again."
-msgstr ""
+msgstr "Se ha producido un error al cargar los datos del proyecto. Por favor, vuelva a intentarlo."
msgid "Error loading template types."
-msgstr ""
+msgstr "Se ha producido un error al cargar tipos de plantilla."
msgid "Error loading template."
-msgstr ""
+msgstr "Se ha producido un error al cargar la plantilla."
msgid "Error occurred when toggling the notification subscription"
msgstr "Se ha producido un error al activar/desactivar la suscripción de las notificaciones"
-msgid "Error rendering markdown preview"
+msgid "Error occurred. User was not blocked"
msgstr ""
-msgid "Error saving label update."
+msgid "Error occurred. User was not confirmed"
msgstr ""
-msgid "Error updating %{issuableType}"
+msgid "Error occurred. User was not unblocked"
msgstr ""
-msgid "Error updating status for all todos."
+msgid "Error occurred. User was not unlocked"
msgstr ""
+msgid "Error rendering markdown preview"
+msgstr "Se ha producido un error al renderizar la vista previa de markdown"
+
+msgid "Error saving label update."
+msgstr "Se ha producido un error al guardar la actualización de la etiqueta."
+
+msgid "Error updating %{issuableType}"
+msgstr "Se ha producido un error al actualizar %{issuableType}"
+
+msgid "Error updating status for all todos."
+msgstr "Se ha producido un error al actualizar el estado de las tareas pendientes."
+
msgid "Error updating todo status."
+msgstr "Se ha producido un error al actualizar el estado de las tareas pendientes."
+
+msgid "Error uploading file"
msgstr ""
msgid "Error while loading the merge request. Please try again."
+msgstr "Se ha producido un error al procesar su merge request. Por favor, inténtelo de nuevo."
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
msgstr ""
msgid "Error:"
+msgstr "Error:"
+
+msgid "ErrorTracking|Active"
+msgstr "Activo"
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
msgstr ""
-msgid "Errors"
+msgid "ErrorTracking|Auth Token"
+msgstr "Token de autenticación"
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
msgstr ""
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr "No hay proyectos disponibles"
+
+msgid "ErrorTracking|Select project"
+msgstr "Seleccionar proyecto"
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr "Errores"
+
msgid "Estimated"
msgstr "Estimado"
@@ -3746,19 +4402,19 @@ msgid "EventFilterBy|Filter by comments"
msgstr "Filtrar por comentarios"
msgid "EventFilterBy|Filter by issue events"
-msgstr ""
+msgstr "Filtrar por incidencias"
msgid "EventFilterBy|Filter by merge events"
-msgstr ""
+msgstr "Filtrar eventos por merge request"
msgid "EventFilterBy|Filter by push events"
-msgstr ""
+msgstr "Filtrar eventos por push"
msgid "EventFilterBy|Filter by team"
msgstr "Filtrar por equipo"
msgid "Events"
-msgstr ""
+msgstr "Eventos"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -3773,7 +4429,7 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "Todas las semanas (domingos a las 4:00 am)"
msgid "Everyone"
-msgstr ""
+msgstr "Todo el mundo"
msgid "Everyone can contribute"
msgstr "Todo el mundo puede colaborar"
@@ -3796,14 +4452,8 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
-msgstr ""
+msgstr "Miembros y grupos existentes"
msgid "Expand"
msgstr "Expandir"
@@ -3811,32 +4461,38 @@ msgstr "Expandir"
msgid "Expand all"
msgstr "Expandir todo"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Expandir barra lateral"
msgid "Expiration date"
msgstr "Fecha de vencimiento"
+msgid "Expired"
+msgstr "Caducó"
+
msgid "Expired %{expiredOn}"
-msgstr ""
+msgstr "Caducado el %{expiredOn}"
msgid "Expires in %{expires_at}"
-msgstr ""
+msgstr "Caduca en %{expires_at}"
msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
-msgstr ""
+msgstr "Explique el problema. Si es necesario, proporcione un enlace al problema o a un comentario relevante."
msgid "Explore"
msgstr "Explorar"
msgid "Explore GitLab"
-msgstr ""
+msgstr "Explorar GitLab"
msgid "Explore Groups"
-msgstr ""
+msgstr "Explorar Grupos"
msgid "Explore groups"
-msgstr ""
+msgstr "Explorar grupos"
msgid "Explore projects"
msgstr "Explorar proyectos"
@@ -3845,22 +4501,28 @@ msgid "Explore public groups"
msgstr "Explorar grupos públicos"
msgid "Export as CSV"
-msgstr ""
+msgstr "Exportar como CSV"
msgid "Export issues"
+msgstr "Exportar incidencias"
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
msgstr ""
msgid "External Classification Policy Authorization"
msgstr ""
msgid "External URL"
-msgstr ""
+msgstr "URL externa"
msgid "External Wiki"
-msgstr ""
+msgstr "Wiki externo"
msgid "External authentication"
-msgstr ""
+msgstr "Autenticación externa"
msgid "External authorization denied access to this project"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "Etiqueta de clasificación"
@@ -3892,13 +4551,19 @@ msgstr "Error al cambiar el propietario"
msgid "Failed to check related branches."
msgstr "Se ha producido un error al verificar las branchs relacionadas."
+msgid "Failed to create resources"
+msgstr "Se ha producido un error al crear los recursos"
+
msgid "Failed to deploy to"
msgstr "Se ha producido un error al desplegar a"
msgid "Failed to load emoji list."
msgstr "Se ha producido un error al cargar la lista de emojis."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr "Se ha producido un error al cargar los errores desde Sentry. El mensaje de error es: %{errorMessage}"
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr "Se ha producido un error al eliminar el mirror."
msgid "Failed to remove the pipeline schedule"
msgstr "Error al eliminar la programación del pipeline"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Se ha producido un error al inciar sesión utilizando la autenticación mediante una tarjeta inteligente"
@@ -3925,29 +4605,35 @@ msgstr ""
msgid "Failure"
msgstr "Error"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
msgid "FeatureFlags|* (All Environments)"
-msgstr ""
+msgstr "* (Todos los entornos)"
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "* (Todos los entornos)"
msgid "FeatureFlags|API URL"
-msgstr ""
+msgstr "API Url"
msgid "FeatureFlags|Active"
-msgstr ""
+msgstr "Activo"
msgid "FeatureFlags|Configure"
-msgstr ""
+msgstr "Configurar"
msgid "FeatureFlags|Configure feature flags"
msgstr ""
@@ -3956,16 +4642,13 @@ msgid "FeatureFlags|Create feature flag"
msgstr ""
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "¿Eliminar %{name}?"
msgid "FeatureFlags|Delete feature flag"
msgstr ""
msgid "FeatureFlags|Description"
-msgstr ""
-
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
+msgstr "Descripción"
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -3995,7 +4678,7 @@ msgid "FeatureFlags|Get started with Feature Flags"
msgstr ""
msgid "FeatureFlags|Inactive"
-msgstr ""
+msgstr "Inactivo"
msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr ""
@@ -4016,19 +4699,19 @@ msgid "FeatureFlags|More information"
msgstr ""
msgid "FeatureFlags|Name"
-msgstr ""
+msgstr "Nombre"
msgid "FeatureFlags|New"
-msgstr ""
+msgstr "Nuevo"
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
-msgstr ""
+msgstr "Estado"
msgid "FeatureFlags|Target environments"
msgstr ""
@@ -4051,34 +4734,46 @@ msgstr "Feb"
msgid "February"
msgstr "Febrero"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "Ahora puede editar los campos en esta página, puede configurar"
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Archivo"
+msgstr[1] "Archivos"
msgid "File added"
msgstr ""
msgid "File browser"
-msgstr ""
+msgstr "Explorador de archivos"
msgid "File deleted"
-msgstr ""
+msgstr "Archivo eliminado"
msgid "File mode changed from %{a_mode} to %{b_mode}"
msgstr ""
msgid "File moved"
-msgstr ""
+msgstr "Archivo movido"
msgid "File templates"
-msgstr ""
+msgstr "Plantillas de archivos"
msgid "File upload error."
-msgstr ""
+msgstr "Error al subir el archivo."
msgid "Files"
msgstr "Archivos"
@@ -4102,25 +4797,22 @@ msgid "Filter by milestone name"
msgstr ""
msgid "Filter by two-factor authentication"
-msgstr ""
+msgstr "Filtrar por autenticación de dos factores"
msgid "Filter results by group"
-msgstr ""
+msgstr "Filtrar resultados por grupo"
msgid "Filter results by project"
-msgstr ""
+msgstr "Filtrar resultados por proyecto"
msgid "Filter..."
msgstr "Filtrar..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Buscar por ruta"
msgid "Find existing members by name"
-msgstr ""
+msgstr "Buscar miembros existentes por su nombre"
msgid "Find file"
msgstr "Buscar archivo"
@@ -4131,6 +4823,9 @@ msgstr "Busque el archivo ZIP descargado y descomprímalo."
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Busque el archivo extraído <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "Huellas digitales"
@@ -4144,7 +4839,7 @@ msgid "Finished"
msgstr "Finalizado"
msgid "First day of the week"
-msgstr ""
+msgstr "Primer día de la semana"
msgid "FirstPushedBy|First"
msgstr "Primer"
@@ -4156,13 +4851,13 @@ msgid "Fixed date"
msgstr "Fecha fija"
msgid "Fixed due date"
-msgstr ""
+msgstr "Fecha de finalicación fija"
msgid "Fixed start date"
-msgstr ""
+msgstr "Fecha de inicio fija"
msgid "Fixed:"
-msgstr ""
+msgstr "Arreglado:"
msgid "FogBugz Email"
msgstr "Correo electrónico de FogBugz"
@@ -4177,10 +4872,10 @@ msgid "FogBugz URL"
msgstr "URL de FogBugz"
msgid "FogBugz import"
-msgstr ""
+msgstr "Importar desde FogBugz"
msgid "Follow the steps below to export your Google Code project data."
-msgstr ""
+msgstr "Siga los pasos a continuación para exportar sus datos de proyecto de Google Code."
msgid "Font Color"
msgstr "Color de la fuente"
@@ -4192,7 +4887,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr ""
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "Para más información, por favor lea la documentación."
msgid "For more information, go to the "
msgstr ""
@@ -4224,14 +4919,20 @@ msgstr ""
msgid "Format"
msgstr "Formato"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "Se han encontrado errores en su fichero .gitlab-ci.yml:"
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
-msgstr "De %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr "De %{providerTitle}"
msgid "From Bitbucket"
msgstr "Desde Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr "De los hitos:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Desde la vista de detalles del clúster de Kubernetes, seleccione instalar 'runner' desde la lista de aplicaciones"
+msgid "GPG Key ID:"
+msgstr "ID de la clave GPG:"
+
msgid "GPG Keys"
msgstr "Llaves GPG"
+msgid "GPG signature (loading...)"
+msgstr "Firma GPG (cargando...)"
+
msgid "General"
msgstr "General"
+msgid "General Settings"
+msgstr "Configuración general"
+
msgid "General pipelines"
msgstr ""
@@ -4273,6 +4983,9 @@ msgid "Generate a default set of labels"
msgstr "Generar un conjunto predeterminado de etiquetas"
msgid "Generate key"
+msgstr "Generar clave"
+
+msgid "Generate new export"
msgstr ""
msgid "Geo"
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "Al deshabilitar un nodo se detiene el proceso de sincronización. ¿Está seguro que desea continuar con la proceso?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4309,55 +5019,58 @@ msgid "GeoNodes|Failed"
msgstr "Fallido"
msgid "GeoNodes|Full"
-msgstr ""
+msgstr "Completo"
msgid "GeoNodes|GitLab version"
-msgstr ""
+msgstr "Versión de GitLab"
msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "La versión de GitLab no coincide con la versión del nodo principal"
msgid "GeoNodes|Health status"
+msgstr "Estado de salud"
+
+msgid "GeoNodes|Internal URL"
msgstr ""
msgid "GeoNodes|Last event ID processed by cursor"
-msgstr ""
+msgstr "ID del último evento procesado por el cursor"
msgid "GeoNodes|Last event ID seen from primary"
-msgstr ""
+msgstr "ID del último evento visto desde el principal"
msgid "GeoNodes|Learn more about Repository checksum progress"
-msgstr ""
+msgstr "Obtenga más información sobre el progreso de verificación de repositorios"
msgid "GeoNodes|Learn more about Repository verification"
-msgstr ""
+msgstr "Obtenga más información sobre la verificación de repositorios"
msgid "GeoNodes|Learn more about Wiki checksum progress"
-msgstr ""
+msgstr "Obtenga más información sobre el progreso de verificación de Wikis"
msgid "GeoNodes|Learn more about Wiki verification"
-msgstr ""
+msgstr "Obtenga más información sobre la verificación de Wikis"
msgid "GeoNodes|Loading nodes"
-msgstr ""
+msgstr "Cargando nodos"
msgid "GeoNodes|Local LFS objects"
-msgstr ""
+msgstr "Objetos LFS locales"
msgid "GeoNodes|Local attachments"
-msgstr ""
+msgstr "Adjuntos locales"
msgid "GeoNodes|Local job artifacts"
-msgstr ""
+msgstr "Artefactos de trabajo locales"
msgid "GeoNodes|New node"
-msgstr ""
+msgstr "Nuevo nodo"
msgid "GeoNodes|Node Authentication was successfully repaired."
-msgstr ""
+msgstr "La autenticación del nodo se reparó correctamente."
msgid "GeoNodes|Node was successfully removed."
-msgstr ""
+msgstr "El nodo se eliminó correctamente."
msgid "GeoNodes|Not checksummed"
msgstr "Sin suma de verificación"
@@ -4365,6 +5078,9 @@ msgstr "Sin suma de verificación"
msgid "GeoNodes|Out of sync"
msgstr "Sin sincronización"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,35 +5189,53 @@ msgstr "Se ha programado la sincronización de los proyectos"
msgid "Geo|Batch operations"
msgstr "Operaciones por lotes"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "No se puede eliminar la entrada de seguimiento para un proyecto existente."
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "Fallido"
msgid "Geo|File sync capacity"
-msgstr ""
+msgstr "Capacidad de sincronización de archivos"
msgid "Geo|Geo Status"
msgstr ""
msgid "Geo|Groups to synchronize"
-msgstr ""
+msgstr "Grupos a sincronizar"
msgid "Geo|In sync"
msgstr "Sincronizado"
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
msgid "Geo|Last successful sync"
-msgstr ""
+msgstr "Última sincronización correcta"
msgid "Geo|Last sync attempt"
-msgstr ""
+msgstr "Último intento de sincronización"
msgid "Geo|Last time verified"
-msgstr ""
+msgstr "Verficado por última vez"
msgid "Geo|Never"
msgstr "Nunca"
@@ -4521,6 +5255,12 @@ msgstr "Sincronización pendiente"
msgid "Geo|Pending verification"
msgstr "Pendiente de verificación"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4543,43 +5283,64 @@ msgid "Geo|Redownload"
msgstr ""
msgid "Geo|Remove"
-msgstr ""
+msgstr "Eliminar"
msgid "Geo|Repository sync capacity"
-msgstr ""
+msgstr "Capacidad de sincronización del repositorio"
msgid "Geo|Resync"
-msgstr ""
+msgstr "Resincronizar"
msgid "Geo|Resync all projects"
-msgstr ""
+msgstr "Resincronizar todos los proyectos"
msgid "Geo|Retry count"
-msgstr ""
+msgstr "Contador de reintentos"
msgid "Geo|Select groups to replicate."
-msgstr ""
+msgstr "Seleccionar grupos a replicar."
+
+msgid "Geo|Selective synchronization"
+msgstr "Sincronización selectiva"
msgid "Geo|Shards to synchronize"
-msgstr ""
+msgstr "Grupos para sincronizar"
msgid "Geo|Status"
msgstr "Estado"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Sincronizado"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
+msgstr "Se ha producido un error durante la sincronización - %{error}"
+
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
msgstr ""
+msgid "Geo|This is a primary node"
+msgstr "Este es un nodo primario"
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr "La entrada de seguimiento del proyecto (%{project_id}) se eliminó correctamente."
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr ""
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr "URL"
+
msgid "Geo|Unknown state"
-msgstr ""
+msgstr "Estado desconocido"
msgid "Geo|Verification capacity"
msgstr ""
@@ -4596,11 +5357,8 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
-msgstr ""
+msgstr "desconfigurado"
msgid "Geo|primary"
msgstr "primario"
@@ -4620,9 +5378,12 @@ msgstr ""
msgid "Git"
msgstr "Git"
-msgid "Git global setup"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
+msgid "Git global setup"
+msgstr "Configuración global de Git"
+
msgid "Git repository URL"
msgstr "URL del repositorio Git"
@@ -4641,6 +5402,9 @@ msgstr "Importar desde GitHub"
msgid "GitLab CI Linter has been moved"
msgstr "Se ha movido GitLab CI Linter"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr "Usuario de GitLab"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "Exportar proyecto desde GitLab"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "URL de inicio de sesión único de GitLab"
@@ -4695,14 +5465,23 @@ msgstr ""
msgid "Go Back"
msgstr "Volver"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "Volver"
+msgid "Go full screen"
+msgstr "Ir a pantalla completa"
+
msgid "Go to"
msgstr "Ir a"
msgid "Go to %{link_to_google_takeout}."
-msgstr ""
+msgstr "Ir a %{link_to_google_takeout}."
+
+msgid "Go to project"
+msgstr "Ir al proyecto"
msgid "Google Code import"
msgstr "Importar desde Google Code"
@@ -4717,23 +5496,35 @@ msgid "Got it!"
msgstr "¡Lo tengo!"
msgid "Grant access"
-msgstr ""
+msgstr "Conceder acceso"
msgid "Graph"
msgstr "Gráfico"
+msgid "Gravatar enabled"
+msgstr "Gravatar habilitado"
+
msgid "Group"
msgstr "Grupo"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "Configuración de grupo CI/CD"
msgid "Group Git LFS status:"
-msgstr ""
+msgstr "Estado del grupo GIT LFS:"
msgid "Group ID"
msgstr "Id de grupo"
+msgid "Group ID: %{group_id}"
+msgstr "ID de grupo: %{group_id}"
+
msgid "Group Runners"
msgstr "Grupo de ejecutores"
@@ -4767,9 +5558,12 @@ msgstr "Nombre del grupo"
msgid "Group overview content"
msgstr ""
-msgid "Group:"
+msgid "Group was successfully updated."
msgstr ""
+msgid "Group:"
+msgstr "Grupo:"
+
msgid "Group: %{group_name}"
msgstr "Grupo: %{group_name}"
@@ -4785,6 +5579,9 @@ msgstr "Lo sentimos, no hay tareas épicas que coincidan con su búsqueda"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4792,6 +5589,72 @@ msgid "GroupRoadmap|To widen your search, change or remove filters; from %{start
msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
+msgstr "Hasta %{dateWord}"
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
msgstr ""
msgid "GroupSettings|Badges"
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Prevenir que se comparta un proyecto de %{group} con otros grupos"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4903,11 +5778,14 @@ msgid "GroupsTree|No groups or projects matched your search"
msgstr ""
msgid "GroupsTree|Search by name"
-msgstr ""
+msgstr "Buscar por nombre"
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "Encabezado del mensaje"
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4956,14 +5837,29 @@ msgstr[0] "Ocultar valor"
msgstr[1] "Ocultar valores"
msgid "Hide values"
+msgstr "Ocultar valores"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr "Historial"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Servicio de limpieza iniciado con éxito"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4995,7 +5891,7 @@ msgid "IDE|Get started with Live Preview"
msgstr ""
msgid "IDE|Go to project"
-msgstr ""
+msgstr "Ir al proyecto"
msgid "IDE|Live Preview"
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr "Identificador"
msgid "Identities"
msgstr "Identidades"
-msgid "Identity provider single sign on URL"
-msgstr "URL de inicio de sesión único del proveedor de identidades"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr "Si está habilitado, el acceso a los proyectos se validará en un servic
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "Si utiliza GitHub, verá los estados del pipeline en GitHub para sus commit y sus pull request. %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "Si ya tiene archivos listos, puede subirlos usando el link que se muestra a continuación %{link_to_cli}."
-
msgid "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>."
msgstr ""
@@ -5064,7 +5960,7 @@ msgid "Import"
msgstr "Importar"
msgid "Import CSV"
-msgstr ""
+msgstr "Importar CSV"
msgid "Import Projects from Gitea"
msgstr "Importar proyectos desde Gitea"
@@ -5085,13 +5981,13 @@ msgid "Import in progress"
msgstr "Importación en progreso"
msgid "Import issues"
-msgstr ""
+msgstr "Importar incidencias"
msgid "Import members"
-msgstr ""
+msgstr "Importar miembros"
msgid "Import members from another project"
-msgstr ""
+msgstr "Importar miembros desde otro proyecto"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "Importar varios repositorios subiendo un archivo manifiesto."
@@ -5100,7 +5996,7 @@ msgid "Import project"
msgstr "Importar proyecto"
msgid "Import project members"
-msgstr ""
+msgstr "Importar miembros del proyecto"
msgid "Import projects from Bitbucket"
msgstr "Importar proyectos desde Bitbucket"
@@ -5129,12 +6025,30 @@ msgstr "Importar repositorio"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "Conectar repositorios desde"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr "Incidentes"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr "Incluir el nombre del autor en el cuerpo del correo electrónico de notificación"
+
msgid "Include merge request description"
-msgstr ""
+msgstr "Introduzca la descripción del merge request"
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr "Proyecto incompatible"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "En línea"
@@ -5183,7 +6109,16 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr "Insertar código"
+
msgid "Insert suggestion"
+msgstr "Insertar sugerencia"
+
+msgid "Insights"
msgstr ""
msgid "Install GitLab Runner"
@@ -5204,19 +6139,22 @@ msgid "Instance Statistics visibility"
msgstr "Visibilidad de las estadísticas de instancia"
msgid "Instance does not support multiple Kubernetes clusters"
+msgstr "La instancia no soporta múltiples clústeres de Kubernetes"
+
+msgid "Instance license"
msgstr ""
msgid "Integrations"
-msgstr ""
+msgstr "Integraciones"
msgid "Integrations Settings"
-msgstr ""
+msgstr "Configuración de las integraciones"
msgid "Interested parties can even contribute by pushing commits if they want to."
-msgstr ""
+msgstr "Las partes interesadas incluso pueden contribuir haciendo push commit si lo desean."
msgid "Internal"
-msgstr ""
+msgstr "Interno"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "Interno - cualquier usuario que haya iniciado sesión puede ver el grupo y cualquier proyecto interno."
@@ -5236,17 +6174,32 @@ msgstr "Introducción a Cycle Analytics"
msgid "Introducing Your Conversational Development Index"
msgstr ""
-msgid "Invitation"
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
msgstr ""
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr "Invitación"
+
msgid "Invite"
msgstr "Invitar"
msgid "Invite group"
-msgstr ""
+msgstr "Invitar al grupo"
msgid "Invite member"
-msgstr ""
+msgstr "Invitar al miembro"
msgid "Invoke Count"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Incidencia"
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr "Eventos de incidencia"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "Tablero"
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr "Incidencias cerradas"
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5324,7 +6286,7 @@ msgid "It must have a header row and at least two columns: the first column is t
msgstr ""
msgid "It's you"
-msgstr ""
+msgstr "¡Es usted!"
msgid "Jaeger URL"
msgstr "URL de Jaeger"
@@ -5341,12 +6303,30 @@ msgstr "Enero"
msgid "Job"
msgstr "Trabajo"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "El trabajo ha sido eliminado"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5360,7 +6340,7 @@ msgid "Job|Complete Raw"
msgstr ""
msgid "Job|Download"
-msgstr ""
+msgstr "Descargar"
msgid "Job|Erase job log"
msgstr ""
@@ -5375,6 +6355,9 @@ msgid "Job|Job has been erased by"
msgstr ""
msgid "Job|Keep"
+msgstr "Mantener"
+
+msgid "Job|Pipeline"
msgstr ""
msgid "Job|Scroll to bottom"
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "Jul"
@@ -5408,7 +6403,7 @@ msgid "June"
msgstr "Junio"
msgid "Key (PEM)"
-msgstr ""
+msgstr "Clave (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5417,7 +6412,7 @@ msgid "Kubernetes Cluster"
msgstr "Clúster de Kubernetes"
msgid "Kubernetes Clusters"
-msgstr ""
+msgstr "Clúster de Kubernetes"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "El tiempo de creación del clúster de Kubernetes excede el tiempo de espera; %{timeout}"
@@ -5437,9 +6432,15 @@ msgstr "Kubernetes configurado"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr "Configuración LDAP"
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Deshabilitado"
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,8 +6495,11 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr "Idioma"
+
msgid "Large File Storage"
-msgstr ""
+msgstr "Almacenamiento de archivos grandes"
msgid "Last %d day"
msgid_plural "Last %d days"
@@ -5497,7 +6510,7 @@ msgid "Last Pipeline"
msgstr "Último Pipeline"
msgid "Last activity"
-msgstr ""
+msgstr "Última actividad"
msgid "Last commit"
msgstr "Último cambio"
@@ -5515,7 +6528,7 @@ msgid "Last reply by"
msgstr "Última respuesta por"
msgid "Last seen"
-msgstr ""
+msgstr "Visto por ùltima vez"
msgid "Last update"
msgstr "Última actualización"
@@ -5527,7 +6540,7 @@ msgid "LastPushEvent|You pushed to"
msgstr "Ha hecho push a"
msgid "LastPushEvent|at"
-msgstr ""
+msgstr "en"
msgid "Latest changes"
msgstr "Últimos cambios"
@@ -5548,12 +6561,15 @@ msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple
msgstr ""
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "Aprenda más sobre Auto DevOps"
msgid "Learn more about Kubernetes"
msgstr "Aprenda más acerca de Kubernetes"
msgid "Learn more about Web Terminal"
+msgstr "Aprenda más sobre la Terminal Web"
+
+msgid "Learn more about approvals."
msgstr ""
msgid "Learn more about custom project templates"
@@ -5562,10 +6578,7 @@ msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
-msgstr ""
-
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr "documentación sobre la programación de pipelines"
msgid "Leave"
msgstr "Abandonar"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Abandonar grupo"
@@ -5605,7 +6621,7 @@ msgid "LicenseManagement|Approve license?"
msgstr ""
msgid "LicenseManagement|Approved"
-msgstr ""
+msgstr "Aprobado"
msgid "LicenseManagement|Blacklist"
msgstr ""
@@ -5620,54 +6636,60 @@ msgid "LicenseManagement|Blacklisted"
msgstr ""
msgid "LicenseManagement|Cancel"
-msgstr ""
+msgstr "Cancelar"
msgid "LicenseManagement|License"
-msgstr ""
+msgstr "Licencia"
msgid "LicenseManagement|License Management"
-msgstr ""
+msgstr "Gestión de licencias"
msgid "LicenseManagement|License details"
-msgstr ""
+msgstr "Detalles de la licencia"
msgid "LicenseManagement|License name"
-msgstr ""
+msgstr "Nombre de la licencia"
msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
-msgstr ""
+msgstr "Administrar licencias aprobadas y en lista negra para este proyecto."
msgid "LicenseManagement|Packages"
-msgstr ""
+msgstr "Paquetes"
msgid "LicenseManagement|Remove license"
-msgstr ""
+msgstr "Eliminar la licencia"
msgid "LicenseManagement|Remove license?"
-msgstr ""
+msgstr "¿Eliminar la licencia?"
msgid "LicenseManagement|Submit"
-msgstr ""
+msgstr "Enviar"
msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
-msgstr ""
+msgstr "Actualmente no hay licencias aprobadas o incluidas en la lista negra en este proyecto."
msgid "LicenseManagement|This license already exists in this project."
-msgstr ""
+msgstr "Esta licencia ya existe en este proyecto."
msgid "LicenseManagement|URL"
msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
+msgstr "Está a punto de eliminar la licencia, %{name}, de este proyecto."
+
+msgid "Licensed to"
msgstr ""
msgid "Licenses"
msgstr "Licencias"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Limitado a mostrar %d evento como máximo"
+msgstr[1] "Limitado a mostrar %d eventos como máximo"
msgid "LinkedIn"
msgstr "LinkedIn"
@@ -5681,6 +6703,9 @@ msgstr "Mostrar sus repositorios de Gitea"
msgid "List available repositories"
msgstr "Lista de repositorios disponibles"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "Vista de lista"
@@ -5703,10 +6728,10 @@ msgid "Loading..."
msgstr "Cargando..."
msgid "Loading…"
-msgstr ""
+msgstr "Cargando…"
msgid "Localization"
-msgstr ""
+msgstr "Localización"
msgid "Lock"
msgstr "Bloquear"
@@ -5718,7 +6743,7 @@ msgid "Lock not found"
msgstr "Bloqueo no encontrado"
msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgstr "¿Bloquear este %{issuableDisplayName}? Sólo los <strong>miembros del proyecto</strong> podrán comentar."
msgid "Lock to current projects"
msgstr "Bloquear al proyecto actual"
@@ -5738,20 +6763,41 @@ msgstr "Los bloqueos brindan la capacidad de bloquear archivos o carpetas especÃ
msgid "Login with smartcard"
msgstr "Iniciar sesión con una tarjeta inteligente"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Logs"
+msgid "MRApprovals|Approved by"
+msgstr "Aprobado por"
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Haga que todos en su equipo sean más productivos, independientemente de su ubicación. GitLab Geo crea réplicas de solo lectura de su instancia de GitLab para que pueda reducir el tiempo que lleva clonar y obtener grandes repositorios."
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "Asegúrese de haber iniciado sesión en la cuenta que posee los proyectos que desea importar."
-msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgid "Manage"
msgstr ""
+msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgstr "Administre los repositorios de Git con controles de acceso detallados que mantienen su código seguro. Realice revisiones de código y mejore la colaboración con merge request. Cada proyecto también puede tener una herramienta de gestión de incidencias y un wiki."
+
msgid "Manage Web IDE features"
-msgstr ""
+msgstr "Administrar las características del Web IDE"
msgid "Manage access"
msgstr "Administrar accesos"
@@ -5775,10 +6821,7 @@ msgid "Manage project labels"
msgstr "Administrar etiquetas de proyectos"
msgid "Manage two-factor authentication"
-msgstr ""
-
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
+msgstr "Administrar autenticación de dos factores"
msgid "Manifest"
msgstr "Manifiesto"
@@ -5786,6 +6829,9 @@ msgstr "Manifiesto"
msgid "Manifest file import"
msgstr "Importar fichero de manifiesto"
+msgid "Manual job"
+msgstr "Trabajo manual"
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Asigne una ID de cuenta de FogBugz a un usuario de GitLab"
@@ -5808,48 +6854,45 @@ msgid "Mark todo as done"
msgstr "Marcar tarea pendiente como completada"
msgid "Markdown"
+msgstr "Markdown"
+
+msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr "Markdown habilitado"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
+msgid "Maven Metadata"
+msgstr "Metadatos de Maven"
-msgid "MarkdownToolbar|Add a link"
-msgstr ""
+msgid "Max access level"
+msgstr "Nivel de acceso máximo"
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
-msgstr ""
+msgid "Maximum artifacts size (MB)"
+msgstr "Tamaño máximo de los artefactos (MB)"
-msgid "MarkdownToolbar|Add a task list"
-msgstr ""
+msgid "Maximum attachment size (MB)"
+msgstr "Tamaño máximo del archivo adjunto (MB)"
-msgid "MarkdownToolbar|Add bold text"
-msgstr ""
+msgid "Maximum capacity"
+msgstr "Capacidad máxima"
-msgid "MarkdownToolbar|Add italic text"
-msgstr ""
+msgid "Maximum delay (Minutes)"
+msgstr "Retraso máximo (minutos)"
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum job timeout"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maven Metadata"
-msgstr "Metadatos de Maven"
-
-msgid "Max access level"
-msgstr "Nivel de acceso máximo"
-
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,21 +6916,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr ""
-
msgid "Merge Request"
msgstr "Merge request"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Merge requests"
msgid "Merge Requests created"
msgstr "Solicitudes de merge creadas"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "Eventos de merge"
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Merge request"
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Merge requests"
@@ -5934,22 +6992,22 @@ msgid "MergeRequests|Jump to next unresolved discussion"
msgstr ""
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "Responder..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr ""
msgid "MergeRequests|Saving the comment failed"
-msgstr ""
+msgstr "Se ha producido un error al guardar el comentario"
msgid "MergeRequests|Toggle comments for this file"
-msgstr ""
+msgstr "Alternar los comentarios para este archivo"
msgid "MergeRequests|View file @ %{commitId}"
-msgstr ""
+msgstr "Ver el archivo @ %{commitId}"
msgid "MergeRequests|View replaced file @ %{commitId}"
-msgstr ""
+msgstr "Ver el archivo reemplazado @ %{commitId}"
msgid "MergeRequests|commented on commit %{commitLink}"
msgstr ""
@@ -5972,17 +7030,20 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
+msgid "MergeRequest|Filter files"
msgstr ""
-msgid "MergeRequest|Search files"
-msgstr ""
+msgid "MergeRequest|No files found"
+msgstr "No se encontraron archivos"
msgid "Merged"
-msgstr "Mergeado"
+msgstr "Fusionado"
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
msgid "Messages"
msgstr "Mensajes"
@@ -6000,7 +7061,7 @@ msgid "Metrics and profiling"
msgstr ""
msgid "Metrics for environment"
-msgstr ""
+msgstr "Métricas para el entorno"
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr "Consulte la documentación de CI/CD sobre la implementación en un entorno"
@@ -6009,10 +7070,10 @@ msgid "Metrics|Create metric"
msgstr "Crear métrica"
msgid "Metrics|Delete metric"
-msgstr ""
+msgstr "Eliminar métrica"
msgid "Metrics|Delete metric?"
-msgstr ""
+msgstr "¿Eliminar métrica?"
msgid "Metrics|Edit metric"
msgstr "Editar métrica"
@@ -6041,14 +7102,17 @@ msgstr "Nueva métrica"
msgid "Metrics|No deployed environments"
msgstr "No hay entornos desplegados"
-msgid "Metrics|PromQL query is valid"
+msgid "Metrics|Not enough data to display"
msgstr ""
+msgid "Metrics|PromQL query is valid"
+msgstr "La consulta PromQL es válida"
+
msgid "Metrics|Prometheus Query Documentation"
msgstr "Documentción sobre las consultas de Prometheus"
-msgid "Metrics|System"
-msgstr "Sistema"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "Se ha producido un error al recuperar los datos de los distintos entornos, por favor inténtelo de nuevo"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "por ejemplo, rendimiento"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Hito"
@@ -6111,7 +7181,7 @@ msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
msgstr ""
msgid "Milestones|Delete milestone"
-msgstr ""
+msgstr "Eliminar hito"
msgid "Milestones|Delete milestone %{milestoneTitle}?"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "agregar una clave SSH"
@@ -6161,6 +7237,9 @@ msgstr "Cancelar"
msgid "Modal|Close"
msgstr "Cerrar"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6168,7 +7247,7 @@ msgid "Modify merge commit"
msgstr ""
msgid "Monday"
-msgstr ""
+msgstr "Lunes"
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr "Más información"
msgid "More information is available|here"
msgstr "Hay más información disponible | aquí"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Más estrellas"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "Mover"
@@ -6206,6 +7291,12 @@ msgstr "Mover incidencia"
msgid "Multiple issue boards"
msgstr "Múltiples tableros de incidencias"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Nombre"
@@ -6218,6 +7309,12 @@ msgstr "Nombre su clave individual mediante un título"
msgid "Name:"
msgstr "Nombre:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6228,13 +7325,13 @@ msgid "Nav|Home"
msgstr "Inicio"
msgid "Nav|Sign In / Register"
-msgstr ""
+msgstr "Iniciar sesión/Registro"
msgid "Nav|Sign out and sign in with a different account"
-msgstr ""
+msgstr "Cerrar la sesión actual e iniciar sesión con una cuenta diferente"
msgid "Need help?"
-msgstr ""
+msgstr "¿Necesita ayuda?"
msgid "Network"
msgstr "Red"
@@ -6249,7 +7346,7 @@ msgid "New Application"
msgstr "Nueva aplicación"
msgid "New Environment"
-msgstr ""
+msgstr "Nuevo entorno"
msgid "New Group"
msgstr "Nuevo grupo"
@@ -6266,31 +7363,34 @@ msgid "New Label"
msgstr "Nueva etiqueta"
msgid "New Milestone"
-msgstr ""
+msgstr "Nuevo hito"
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr "Nueva contraseña"
+
msgid "New Pipeline Schedule"
msgstr "Nueva Programación del Pipeline"
msgid "New Snippet"
msgstr "Nuevo fragmento de código"
-msgid "New Snippets"
-msgstr "Nuevo fragmento de código"
-
msgid "New branch"
msgstr "Nueva rama"
msgid "New branch unavailable"
msgstr "Nueva rama no disponible"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Nuevo directorio"
msgid "New environment"
-msgstr ""
+msgstr "Nuevo entorno"
msgid "New epic"
msgstr "Nueva tarea épica"
@@ -6301,6 +7401,9 @@ msgstr "Nuevo archivo"
msgid "New group"
msgstr "Nuevo grupo"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "Nueva identidad"
@@ -6314,7 +7417,7 @@ msgid "New merge request"
msgstr "Nueva solicitud de fusión"
msgid "New milestone"
-msgstr ""
+msgstr "Nuevo hito"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "Los nuevos 'pipelines' cancelarán los 'pipelines' más antiguos y pendientes en el mismo 'branch'"
@@ -6322,6 +7425,9 @@ msgstr "Los nuevos 'pipelines' cancelarán los 'pipelines' más antiguos y pendi
msgid "New project"
msgstr "Nuevo proyecto"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Nueva programación"
@@ -6334,23 +7440,32 @@ msgstr "Nuevo sub-grupo"
msgid "New tag"
msgstr "Nueva etiqueta"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "Nuevo..."
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "No"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "Sin etiqueta"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "Sin asignar"
+msgid "No activities found"
+msgstr "No se han encontrado actividades"
msgid "No branches found"
-msgstr ""
+msgstr "No se han encontrado ramas"
msgid "No changes"
msgstr "Sin cambios"
@@ -6359,7 +7474,7 @@ msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start
msgstr ""
msgid "No connection could be made to a Gitaly Server, please check your logs!"
-msgstr ""
+msgstr "¡No ha sido posible realizar la conexión con un servidor de Gitaly, por favor compruebe sus logs!"
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr ""
@@ -6371,12 +7486,12 @@ msgid "No credit card required."
msgstr "No se necesita tarjeta de crédito."
msgid "No details available"
-msgstr ""
+msgstr "No hay detalles disponibles"
msgid "No due date"
msgstr "Sin fecha límite"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6386,7 +7501,7 @@ msgid "No file chosen"
msgstr "No se ha seleccionado nignun archivo"
msgid "No file selected"
-msgstr ""
+msgstr "No ha seleccionado ningún archivo"
msgid "No files found."
msgstr "No se han encontrado archivos."
@@ -6394,13 +7509,22 @@ msgstr "No se han encontrado archivos."
msgid "No issues for the selected time period."
msgstr "No hay incidencias para el período de tiempo seleccionado."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr "Sin licencia. Todos los derechos reservados"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
+msgstr "No se han encontrado resultados"
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
msgid "No merge requests for the selected time period."
@@ -6413,13 +7537,13 @@ msgid "No messages were logged"
msgstr "No se registraron mensajes"
msgid "No milestones to show"
-msgstr ""
+msgstr "No hay hitos para mostrar"
msgid "No other labels with such name or description"
msgstr ""
msgid "No preview for this file type"
-msgstr ""
+msgstr "No hay vista previa para este tipo de archivo"
msgid "No prioritised labels with such name or description"
msgstr ""
@@ -6440,7 +7564,7 @@ msgid "No schedules"
msgstr "No hay programaciones"
msgid "No start date"
-msgstr ""
+msgstr "Sin fecha de inicio"
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr "Nodos"
msgid "None"
msgstr "Ninguno"
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr "No disponible"
@@ -6469,26 +7590,32 @@ msgstr "No es confidencial"
msgid "Not enough data"
msgstr "No hay suficientes datos"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "Ahora no"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Tenga en cuenta que la rama principal está protegida automáticamente. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
-msgstr ""
+msgstr "Nota: Como administrador, puede configurar %{github_integration_link}, que le permitirá realizar el inicio de sesión a través de GitHub y le permitirá la conexión de repositorios sin la necesidad de generar un token de acceso personal."
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
-msgstr ""
+msgstr "Nota: Como administrador, puede configurar %{github_integration_link}, que le permitirá realizar el inicio de sesión a través de GitHub y le permitirá la conexión de repositorios sin la necesidad de generar un token de acceso personal."
msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
-msgstr ""
+msgstr "Nota: Considere preguntarle a su administrador de GitLab que configure %{github_integration_link}, le permitirá el inicio de sesión a través de GitHub y le permitirá la conexión de repositorios sin la necesidad de generar un token de acceso personal."
msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
-msgstr ""
+msgstr "Nota: Considere pedirle a su administrador de GitLab que configure %{github_integration_link}, que le permitirá el inicio de sesión a través de GitHub y le permitirá la conexión de repositorios sin la necesidad de generar un token de acceso personal."
msgid "Notes|Are you sure you want to cancel creating this comment?"
msgstr "¿Está seguro de que desea cancelar la creación de este comentario?"
@@ -6586,6 +7713,9 @@ msgstr "Noviembre"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "Oct"
@@ -6598,11 +7728,20 @@ msgstr "Filtrar"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "Una vez importados, los repositorios se pueden duplicar a través de SSH. Lea más en %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Un elemento más"
msgstr[1] "%d elementos más"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr "Sólo administradores"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6634,13 +7779,13 @@ msgid "Open"
msgstr "Abrir"
msgid "Open Documentation"
-msgstr ""
+msgstr "Abrir documentación"
msgid "Open comment type dropdown"
msgstr ""
msgid "Open errors"
-msgstr ""
+msgstr "Errores abiertos"
msgid "Open in Xcode"
msgstr "Abrir en Xcode"
@@ -6648,6 +7793,9 @@ msgstr "Abrir en Xcode"
msgid "Open projects"
msgstr "Proyectos abiertos"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "Abrir barra lateral"
@@ -6675,15 +7823,30 @@ msgstr "Operaciones"
msgid "Operations Dashboard"
msgstr "Panel de operaciones"
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Agregar un proyecto al panel"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr "Opcional"
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr "Otras etiquetas"
msgid "Other information"
msgstr "Otra información"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "De lo contrario, se recomienda empezar con una de las siguientes opciones."
-
msgid "Outbound requests"
msgstr "Peticiones salientes"
@@ -6726,6 +7886,12 @@ msgstr "El paquete ha sido eliminado"
msgid "Packages"
msgstr "Paquetes"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Páginas"
@@ -6742,26 +7908,29 @@ msgid "Pagination|Next"
msgstr "Siguiente"
msgid "Pagination|Prev"
-msgstr ""
+msgstr "Previo"
msgid "Pagination|« First"
-msgstr ""
+msgstr "« Primero"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "Parámetro"
msgid "Part of merge request changes"
-msgstr ""
+msgstr "Parte de los cambios de los merge requests"
msgid "Password"
msgstr "Contraseña"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6769,63 +7938,75 @@ msgid "Paste issue link"
msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
+msgstr "Pegue su clave pública SSH, que generalmente está contenida en el archivo '~ /.ssh/id_rsa.pub' y comienza con 'ssh-rsa'. No utilice su clave SSH privada."
+
+msgid "Path"
msgstr ""
msgid "Path, transfer, remove"
msgstr ""
msgid "Path:"
-msgstr ""
+msgstr "Ruta:"
msgid "Pause"
+msgstr "Pausar"
+
+msgid "Pause replication"
msgstr ""
msgid "Paused Runners don't accept new jobs"
-msgstr ""
+msgstr "Los runers pausados no aceptan nuevos trabajos"
msgid "Pending"
-msgstr ""
+msgstr "Pendiente"
msgid "People without permission will never get a notification and won't be able to comment."
-msgstr ""
+msgstr "Las personas no autorizadas nunca recibirán una notificación y no podrán comentar."
msgid "Perform advanced options such as changing path, transferring, or removing the group."
-msgstr ""
+msgstr "Realice opciones avanzadas como cambiar la ruta, transferir o eliminar el grupo."
msgid "Performance optimization"
-msgstr ""
+msgstr "Optimización del rendimiento"
msgid "Permissions"
+msgstr "Permisos"
+
+msgid "Permissions Help"
msgstr ""
msgid "Permissions, LFS, 2FA"
-msgstr ""
+msgstr "Permisos, LFS, 2FA"
msgid "Personal Access Token"
-msgstr ""
+msgstr "Token de acceso personal"
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
msgstr ""
msgid "Pick a name"
-msgstr ""
+msgstr "Escoja un nombre"
msgid "Pipeline"
msgstr "Pipeline"
-msgid "Pipeline Health"
-msgstr "Estado del Pipeline"
-
msgid "Pipeline Schedule"
msgstr "Programación del Pipeline"
msgid "Pipeline Schedules"
msgstr "Programaciones de los Pipelines"
-msgid "Pipeline quota"
+msgid "Pipeline minutes quota"
msgstr ""
+msgid "Pipeline quota"
+msgstr "Cuota de pipelines"
+
msgid "Pipeline triggers"
+msgstr "Disparadores de los triggers"
+
+msgid "Pipeline: %{status}"
msgstr ""
msgid "PipelineCharts|Failed:"
@@ -6876,6 +8057,12 @@ msgstr "Variables"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizado"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Pipelines"
@@ -6883,37 +8070,43 @@ msgid "Pipelines charts"
msgstr "Gráficos de los pipelines"
msgid "Pipelines for last month"
-msgstr ""
+msgstr "Pipelines del mes pasado"
msgid "Pipelines for last week"
-msgstr ""
+msgstr "Pipelines de la semana pasada"
msgid "Pipelines for last year"
+msgstr "Pipelines del año pasado"
+
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr ""
msgid "Pipelines|Build with confidence"
msgstr ""
msgid "Pipelines|CI Lint"
-msgstr ""
+msgstr "CI Lint"
msgid "Pipelines|Clear Runner Caches"
-msgstr ""
+msgstr "Limpiar la caché de los runners"
msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
msgstr ""
msgid "Pipelines|Get started with Pipelines"
-msgstr ""
+msgstr "Comenzar a utilizar los pipelines"
msgid "Pipelines|Loading Pipelines"
-msgstr ""
+msgstr "Cargar pipelines"
msgid "Pipelines|Project cache successfully reset."
-msgstr ""
+msgstr "Caché del proyecto restablecida correctamente."
msgid "Pipelines|Run Pipeline"
-msgstr ""
+msgstr "Ejecutar Pipeline"
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
@@ -6922,7 +8115,7 @@ msgid "Pipelines|There are currently no %{scope} pipelines."
msgstr ""
msgid "Pipelines|There are currently no pipelines."
-msgstr ""
+msgstr "Actualmente no hay pipelines."
msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
msgstr "Se ha producido un error al obtener los pipelines. Por favor, inténtelo de nuevo en unos momentos o contacte con su equipo de soporte."
@@ -6933,11 +8126,8 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr "Commit"
-msgid "Pipeline|Create for"
-msgstr "Crear para"
-
-msgid "Pipeline|Create pipeline"
-msgstr "Crear pipeline"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "Duración"
@@ -6946,19 +8136,22 @@ msgid "Pipeline|Existing branch name or tag"
msgstr ""
msgid "Pipeline|Pipeline"
-msgstr ""
+msgstr "Pipeline"
msgid "Pipeline|Run Pipeline"
msgstr "Ejecutar Pipeline"
-msgid "Pipeline|Search branches"
+msgid "Pipeline|Run for"
msgstr ""
+msgid "Pipeline|Search branches"
+msgstr "Buscar ramas"
+
msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
msgstr ""
msgid "Pipeline|Stages"
-msgstr ""
+msgstr "Etapas"
msgid "Pipeline|Status"
msgstr "Estado"
@@ -6978,9 +8171,21 @@ msgstr "Está a punto de detener la ejecución del pipeline %{pipelineId}."
msgid "Pipeline|all"
msgstr "todos"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "exitósos"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "con etapa"
@@ -7005,15 +8210,33 @@ msgstr "Por favor, acepte los términos del servicio antes de continuar."
msgid "Please choose a group URL with no special characters."
msgstr "Por favor, elija una URL de grupo sin caracteres especiales."
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr "Por favor, introduzca un número válido"
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,31 +8246,52 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
-msgid "Please select at least one filter to see results"
+msgid "Please provide a name"
+msgstr "Por favor, proporcione un nombre"
+
+msgid "Please select a group."
msgstr ""
+msgid "Please select and add a member"
+msgstr "Por favor seleccione y añada un miembro"
+
+msgid "Please select at least one filter to see results"
+msgstr "Por favor seleccione por lo menos un filtro para ver los resultados"
+
+msgid "Please set a new password before proceeding."
+msgstr "Por favor, establezca una nueva contraseña antes de continuar."
+
msgid "Please solve the reCAPTCHA"
-msgstr ""
+msgstr "Por favor resuelva el reCAPTCHA"
msgid "Please try again"
-msgstr ""
+msgstr "Por favor inténtelo de nuevo"
msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
+msgstr "Por favor, actualice PostgreSQL a la versión 9.6 o superior. No es posible determinar el estado de la replicación de manera fiable con la versión instalada actualmente."
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
-msgid "Please wait while we connect to your repository. Refresh at will."
+msgid "Please wait a moment, this page will automatically refresh when ready."
msgstr ""
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr "Por favor espere mientras nos conectamos a su repositorio."
+
msgid "Please wait while we import the repository for you. Refresh at will."
-msgstr ""
+msgstr "Por favor espere mientras importamos el repositorio por usted."
msgid "Preferences"
msgstr "Preferencias"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
+msgstr "Tema de navegación"
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr ""
msgid "Press Enter or click to search"
@@ -7056,9 +8300,18 @@ msgstr "Presione Enter o haga clic para buscar"
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Vista previa"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7078,7 +8331,7 @@ msgid "Prioritized label"
msgstr "Etiquetas priorizada"
msgid "Private"
-msgstr ""
+msgstr "Privado"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "Privado - El acceso al proyecto debe ser concedido explícitamente para cada usuario."
@@ -7086,6 +8339,9 @@ msgstr "Privado - El acceso al proyecto debe ser concedido explícitamente para
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privado - El grupo y sus proyectos sólo pueden ser vistos por sus miembros."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Los proyectos privados se pueden crear en su espacio de nombres personal con:"
@@ -7102,19 +8358,19 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr ""
msgid "Profiles|@username"
-msgstr ""
+msgstr "@nombredeusuario"
msgid "Profiles|Account scheduled for removal."
-msgstr ""
+msgstr "Cuenta programada para su eliminación."
msgid "Profiles|Activate signin with one of the following services"
msgstr ""
msgid "Profiles|Active"
-msgstr ""
+msgstr "Activo"
msgid "Profiles|Add key"
-msgstr ""
+msgstr "Añadir clave"
msgid "Profiles|Add status emoji"
msgstr ""
@@ -7126,61 +8382,61 @@ msgid "Profiles|Avatar will be removed. Are you sure?"
msgstr ""
msgid "Profiles|Change username"
-msgstr ""
+msgstr "Cambiar el nombre de usuario"
msgid "Profiles|Changing your username can have unintended side effects."
msgstr ""
msgid "Profiles|Choose file..."
-msgstr ""
+msgstr "Seleccionar un archivo..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
msgstr ""
msgid "Profiles|City, country"
-msgstr ""
+msgstr "Ciudad, país"
msgid "Profiles|Clear status"
-msgstr ""
+msgstr "Borrar estado"
msgid "Profiles|Click on icon to activate signin with one of the following services"
msgstr ""
msgid "Profiles|Connect"
-msgstr ""
+msgstr "Conectar"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "Cuentas conectadas"
msgid "Profiles|Current path: %{path}"
-msgstr ""
+msgstr "Ruta actual: %{path}"
msgid "Profiles|Current status"
-msgstr ""
+msgstr "Estado actual"
msgid "Profiles|Delete Account"
-msgstr ""
+msgstr "Eliminar la cuenta"
msgid "Profiles|Delete account"
-msgstr ""
+msgstr "Eliminar la cuenta"
msgid "Profiles|Delete your account?"
-msgstr ""
+msgstr "¿Eliminar su cuenta?"
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "Desconectar"
msgid "Profiles|Do not show on profile"
-msgstr ""
+msgstr "No mostrar en el perfil"
msgid "Profiles|Don't display activity-related personal information on your profiles"
msgstr ""
msgid "Profiles|Edit Profile"
-msgstr ""
+msgstr "Editar perfil"
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
@@ -7195,19 +8451,19 @@ msgid "Profiles|Invalid username"
msgstr "Nombre de usuario inválido"
msgid "Profiles|Learn more"
-msgstr ""
+msgstr "Más información"
msgid "Profiles|Made a private contribution"
msgstr ""
msgid "Profiles|Main settings"
-msgstr ""
+msgstr "Configuración principal"
msgid "Profiles|No file chosen"
msgstr ""
msgid "Profiles|Path"
-msgstr ""
+msgstr "Ruta"
msgid "Profiles|Position and size your new avatar"
msgstr ""
@@ -7216,7 +8472,7 @@ msgid "Profiles|Private contributions"
msgstr ""
msgid "Profiles|Public Avatar"
-msgstr ""
+msgstr "Avatar público"
msgid "Profiles|Remove avatar"
msgstr "Eliminar avatar"
@@ -7236,19 +8492,19 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "El tamaño máximo de archivo permitido es 200KB."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "No se parece a una clave SSH pública, ¿está seguro de que desea agregarla?"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "Este correo electrónico se utilizará para operaciones basadas en la web, como por ejemplo, realizar ediciones y merges. %{learn_more}"
-
-msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
msgid "Profiles|This information will appear on your profile"
@@ -7270,7 +8526,7 @@ msgid "Profiles|Update username"
msgstr "Actualizar el nombre de usuario"
msgid "Profiles|Upload new avatar"
-msgstr ""
+msgstr "Cargar un avatar nuevo"
msgid "Profiles|Use a private email - %{email}"
msgstr ""
@@ -7285,7 +8541,7 @@ msgid "Profiles|Using emojis in names seems fun, but please try to set a status
msgstr ""
msgid "Profiles|What's your status?"
-msgstr ""
+msgstr "¿Cuál es su estado?"
msgid "Profiles|Who you represent or work for"
msgstr ""
@@ -7330,10 +8586,10 @@ msgid "Profiles|e.g. My MacBook key"
msgstr "por ejemplo, mi clave MacBook"
msgid "Profiles|username"
-msgstr ""
+msgstr "Nombre de usuario"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "website.com"
msgid "Profiles|your account"
msgstr "tu cuenta"
@@ -7350,6 +8606,15 @@ msgstr "Progreso"
msgid "Project"
msgstr "Proyecto"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "El proyecto '%{project_name}' está en proceso de ser eliminado."
@@ -7365,17 +8630,26 @@ msgstr "Proyecto ‘%{project_name}’ fue actualizado satisfactoriamente."
msgid "Project Badges"
msgstr "Insignias de proyecto"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "URL del proyecto"
msgid "Project access must be granted explicitly to each user."
msgstr "El acceso al proyecto debe concederse explícitamente a cada usuario."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Avatar del proyecto"
-msgid "Project avatar in repository: %{link}"
-msgstr "Avatar de proyecto en el repositorio: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "Detalles del Proyecto"
@@ -7383,6 +8657,9 @@ msgstr "Detalles del Proyecto"
msgid "Project export could not be deleted."
msgstr "No se pudo eliminar la exportación del proyecto."
+msgid "Project export enabled"
+msgstr "Exportación de proyecto habilitada"
+
msgid "Project export has been deleted."
msgstr "La exportación del proyecto ha sido eliminada."
@@ -7392,18 +8669,27 @@ msgstr "El enlace de exportación del proyecto ha caducado. Por favor, genera un
msgid "Project export started. A download link will be sent by email."
msgstr "Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico."
-msgid "Project members"
+msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project members"
+msgstr "Miembros del proyecto"
+
msgid "Project name"
msgstr "Nombre del proyecto"
msgid "Project slug"
msgstr "Slug del proyecto"
-msgid "Project:"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
+msgid "Project:"
+msgstr "Proyecto:"
+
msgid "ProjectActivityRSS|Subscribe"
msgstr "Suscribirse"
@@ -7456,7 +8742,7 @@ msgid "ProjectPage|Project ID: %{project_id}"
msgstr "ID de proyecto: %{project_id}"
msgid "ProjectSettings|Badges"
-msgstr ""
+msgstr "Insignias"
msgid "ProjectSettings|Contact an admin to change this setting."
msgstr ""
@@ -7465,10 +8751,10 @@ msgid "ProjectSettings|Customize your project badges."
msgstr ""
msgid "ProjectSettings|Failed to protect the tag"
-msgstr ""
+msgstr "¡Se ha producido un error al proteger la etiqueta!"
msgid "ProjectSettings|Failed to update tag!"
-msgstr ""
+msgstr "¡Se ha producido un error al actualizar la etiqueta!"
msgid "ProjectSettings|Learn more about badges."
msgstr ""
@@ -7491,20 +8777,29 @@ msgstr ""
msgid "Projects"
msgstr "Proyectos"
+msgid "Projects Successfully Retrieved"
+msgstr "Proyectos recuperados con éxito"
+
msgid "Projects shared with %{group_name}"
-msgstr ""
+msgstr "Proyectos compartidos con %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
-msgid "ProjectsDropdown|Frequently visited"
+msgid "Projects to index"
msgstr ""
+msgid "Projects with write access"
+msgstr "Proyectos con acceso de escritura"
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr "Visitados frecuentemente"
+
msgid "ProjectsDropdown|Loading projects"
msgstr "Cargando proyectos"
msgid "ProjectsDropdown|Projects you visit often will appear here"
-msgstr ""
+msgstr "Sus proyectos más visitados aparecerán aquí"
msgid "ProjectsDropdown|Search your projects"
msgstr "Busca en tus proyectos"
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr "Operador"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "Umbral"
@@ -7555,7 +8853,7 @@ msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">co
msgstr ""
msgid "PrometheusService|Active"
-msgstr ""
+msgstr "Activo"
msgid "PrometheusService|Auto configuration"
msgstr "Configuración automática"
@@ -7564,7 +8862,7 @@ msgid "PrometheusService|Automatically deploy and configure Prometheus on your c
msgstr ""
msgid "PrometheusService|Common metrics"
-msgstr ""
+msgstr "Métricas comunes"
msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr "Esta funcionalidad está bloqueada."
msgid "Promotions|Upgrade plan"
msgstr "Plan de actualización"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "Protegido"
@@ -7663,10 +8964,10 @@ msgid "ProtectedEnvironment|Choose who is allowed to deploy"
msgstr ""
msgid "ProtectedEnvironment|Environment"
-msgstr ""
+msgstr "Entorno"
msgid "ProtectedEnvironment|Protect"
-msgstr ""
+msgstr "Proteger"
msgid "ProtectedEnvironment|Protect Environments in order to restrict who can execute deployments."
msgstr ""
@@ -7684,7 +8985,7 @@ msgid "ProtectedEnvironment|There are currently no protected environments, prote
msgstr ""
msgid "ProtectedEnvironment|Unprotect"
-msgstr ""
+msgstr "Desproteger"
msgid "ProtectedEnvironment|Your environment can't be unprotected"
msgstr ""
@@ -7705,7 +9006,7 @@ msgid "Pseudonymizer data collection"
msgstr ""
msgid "Public"
-msgstr ""
+msgstr "Público"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "Público - El grupo y cualquier proyecto público puede ser visto sin autenticación."
@@ -7713,9 +9014,15 @@ msgstr "Público - El grupo y cualquier proyecto público puede ser visto sin au
msgid "Public - The project can be accessed without any authentication."
msgstr "Público - El proyecto puede ser accedido sin ninguna autenticación."
-msgid "Public pipelines"
+msgid "Public Access Help"
msgstr ""
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr "Pipelines públicos"
+
msgid "Pull"
msgstr "Pull"
@@ -7725,6 +9032,12 @@ msgstr "Push"
msgid "Push Rules"
msgstr "Reglas de Push"
+msgid "Push an existing Git repository"
+msgstr "Push a un repositorio de Git existente"
+
+msgid "Push an existing folder"
+msgstr "Push a una carpeta existente"
+
msgid "Push events"
msgstr "Eventos Push"
@@ -7747,19 +9060,22 @@ msgid "Quarters"
msgstr "Trimestres"
msgid "Query"
-msgstr ""
+msgstr "Consulta"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "Se pueden utilizar acciones rápidas en la descripción de las incidencias y en los cuadros de texto de los comentarios."
msgid "README"
+msgstr "README"
+
+msgid "Rake Tasks Help"
msgstr ""
msgid "Read more"
msgstr "Leer más"
msgid "Read more about environments"
-msgstr ""
+msgstr "Lea más acerca de los entornos"
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr "Características en tiempo real"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr "Reciente"
+
+msgid "Recent Project Activity"
+msgstr "Actividad reciente del proyecto"
+
msgid "Recent searches"
msgstr "Búsquedas recientes"
@@ -7797,7 +9119,7 @@ msgid "Register / Sign In"
msgstr "Registrarse / Iniciar sesión"
msgid "Register U2F device"
-msgstr ""
+msgstr "Registrar dispositivo U2F"
msgid "Register and see your runners for this group."
msgstr "Registrar y ver sus ejecutores en este grupo."
@@ -7805,6 +9127,9 @@ msgstr "Registrar y ver sus ejecutores en este grupo."
msgid "Register and see your runners for this project."
msgstr "Registrar y ver sus ejecutores en este proyecto."
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "Registro"
@@ -7830,7 +9155,7 @@ msgid "Related merge requests"
msgstr ""
msgid "Releases"
-msgstr ""
+msgstr "Versiones"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "Eliminar avatar"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "Eliminar grupo"
@@ -7862,12 +9196,33 @@ msgstr "Eliminar prioridad"
msgid "Remove project"
msgstr "Eliminar proyecto"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "¡No se puede restaurar un grupo eliminado!"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "Eliminar un grupo hará que todos los proyectos y recursos sean eliminados."
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "Renombrar"
@@ -7881,14 +9236,17 @@ msgid "Reopen epic"
msgstr ""
msgid "Reopen milestone"
-msgstr ""
+msgstr "Reabrir hito"
msgid "Repair authentication"
msgstr "Reparar la autenticación"
-msgid "Reply to comment"
+msgid "Replace"
msgstr ""
+msgid "Reply to comment"
+msgstr "Responder a este comentario"
+
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr "Responda a este correo directamente o %{view_it_on_gitlab}."
@@ -7905,26 +9263,38 @@ msgid "Reports|%{failedString} and %{resolvedString}"
msgstr ""
msgid "Reports|Actions"
-msgstr ""
+msgstr "Acciones"
msgid "Reports|Class"
-msgstr ""
+msgstr "Clase"
msgid "Reports|Confidence"
-msgstr ""
+msgstr "Confianza"
msgid "Reports|Execution time"
-msgstr ""
+msgstr "Tiempo de ejecución"
msgid "Reports|Failure"
+msgstr "Fallo"
+
+msgid "Reports|Metrics reports are loading"
msgstr ""
-msgid "Reports|Severity"
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
msgstr ""
-msgid "Reports|System output"
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
msgstr ""
+msgid "Reports|Severity"
+msgstr "Severidad"
+
+msgid "Reports|System output"
+msgstr "Salida del sistema"
+
msgid "Reports|Test summary"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,9 +9325,12 @@ msgstr "Configuración del repositorio"
msgid "Repository URL"
msgstr "URL del repositorio"
-msgid "Repository cleanup"
+msgid "Repository check was triggered."
msgstr ""
+msgid "Repository cleanup"
+msgstr "Limpieza del repositorio"
+
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
@@ -7985,20 +9364,33 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
-msgid "Resend invite"
+msgid "Require approval from code owners"
msgstr ""
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Resend invite"
+msgstr "Reenviar invitación"
+
msgid "Reset authorization key"
-msgstr ""
+msgstr "Reiniciar la clave de autorización"
msgid "Reset authorization key?"
-msgstr ""
+msgstr "¿Reiniciar la clave de autorización?"
msgid "Reset health check access token"
msgstr ""
msgid "Reset key"
-msgstr ""
+msgstr "Reiniciar la clave"
msgid "Reset runners registration token"
msgstr "Reinicializar el token de registro del runner"
@@ -8007,25 +9399,34 @@ msgid "Resetting the authorization key will invalidate the previous key. Existin
msgstr ""
msgid "Resolve all discussions in new issue"
-msgstr ""
+msgstr "Resolver todas las discusiones en una nueva incidencia"
msgid "Resolve conflicts on source branch"
-msgstr ""
+msgstr "Resolver conflictos en la rama origen"
msgid "Resolve discussion"
-msgstr ""
+msgstr "Resolver discusión"
msgid "Resolved"
+msgstr "Resuelto"
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
msgstr ""
msgid "Response"
+msgstr "Respuesta"
+
+msgid "Response didn't include `service_desk_address`"
msgstr ""
msgid "Response metrics (AWS ELB)"
-msgstr ""
+msgstr "Métricas de respuesta (AWS ELB)"
msgid "Response metrics (Custom)"
-msgstr ""
+msgstr "Métricas de respuesta (personalizadas)"
msgid "Response metrics (HA Proxy)"
msgstr ""
@@ -8037,20 +9438,26 @@ msgid "Response metrics (NGINX Ingress)"
msgstr ""
msgid "Response metrics (NGINX)"
-msgstr ""
+msgstr "Respuesta métricas (NGINX)"
msgid "Restart Terminal"
-msgstr ""
+msgstr "Reiniciar el terminal"
msgid "Resume"
msgstr "Reanudar"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Reintentar"
msgid "Retry this job"
msgstr "Reintentar este trabajo"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "Reintentar la verificación"
@@ -8060,7 +9467,7 @@ msgstr[0] "Mostrar valor"
msgstr[1] "Mostrar valores"
msgid "Reveal values"
-msgstr ""
+msgstr "Mostrar valores"
msgid "Revert this commit"
msgstr "Revertir este cambio"
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr "Revocar"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "Hoja de ruta"
msgid "Run CI/CD pipelines for external repositories"
msgstr "Ejecutar los pipelines de CI/CD para los repositorios externos"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr "Token del ejecutor"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "El ejecutor no recibirá ningún trabajo nuevo"
@@ -8120,7 +9542,7 @@ msgid "Runners API"
msgstr "Runners API"
msgid "Runners activated for this project"
-msgstr ""
+msgstr "Ejecutores activos para este proyecto"
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr ""
@@ -8143,27 +9565,27 @@ msgstr ""
msgid "Running"
msgstr "En ejecución"
+msgid "Running…"
+msgstr "Ejecutando…"
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "SAML SSO para %{group_name}"
-msgid "SAML Single Sign On"
-msgstr "SAML Inicio de sesión único"
-
-msgid "SAML Single Sign On Settings"
-msgstr "Configuración de inicio de sesión SAML"
-
msgid "SAML for %{group_name}"
-msgstr ""
-
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr ""
+msgstr "SAML para %{group_name}"
msgid "SSH Keys"
msgstr "Llaves SSH"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "Claves de host SSH"
@@ -8173,11 +9595,14 @@ msgstr "Clave pública SSH"
msgid "SSL Verification"
msgstr "Verificación SSL"
+msgid "Saturday"
+msgstr "Sábado"
+
msgid "Save"
msgstr "Guardar"
msgid "Save Changes"
-msgstr ""
+msgstr "Guardar cambios"
msgid "Save application"
msgstr "Guardar aplicación"
@@ -8189,7 +9614,7 @@ msgid "Save changes before testing"
msgstr ""
msgid "Save comment"
-msgstr ""
+msgstr "Guardar comentario"
msgid "Save pipeline schedule"
msgstr "Guardar programación del pipeline"
@@ -8197,6 +9622,9 @@ msgstr "Guardar programación del pipeline"
msgid "Save variables"
msgstr "Guardar variables"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Programar un nuevo pipeline"
@@ -8206,15 +9634,24 @@ msgstr "Programado"
msgid "Schedules"
msgstr "Programaciones"
+msgid "Scheduling"
+msgstr "Programación"
+
msgid "Scheduling Pipelines"
msgstr "Programación de Pipelines"
msgid "Scope"
msgstr "Alcance"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "Desplácese hasta <strong>Google Code Project</strong> y active el selector de la derecha."
@@ -8228,7 +9665,7 @@ msgid "Search"
msgstr "Buscar"
msgid "Search an environment spec"
-msgstr ""
+msgstr "Buscar un entorno específico"
msgid "Search branches"
msgstr "Buscar ramas"
@@ -8243,7 +9680,7 @@ msgid "Search for projects, issues, etc."
msgstr "Buscar proyectos, incidencias, etc."
msgid "Search groups"
-msgstr ""
+msgstr "Buscar grupos"
msgid "Search merge requests"
msgstr "Buscar merge requests"
@@ -8266,6 +9703,9 @@ msgstr "Buscar proyectos"
msgid "Search users"
msgstr "Buscar usuarios"
+msgid "Search users or groups"
+msgstr "Buscar usuarios o grupos"
+
msgid "Search your projects"
msgstr "Buscar tus proyectos"
@@ -8293,6 +9733,15 @@ msgstr "en este grupo"
msgid "SearchAutocomplete|in this project"
msgstr "en este proyecto"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "Secreto"
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr "Incidencia creada"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8327,15 +9773,15 @@ msgid "Security Reports|Learn more about setting up your dashboard"
msgstr ""
msgid "Security Reports|More info"
-msgstr ""
+msgstr "Más información"
msgid "Security Reports|No Vulnerabilities"
-msgstr ""
+msgstr "Sin vulnerabilidades"
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr "Seleccione un grupo a invitar"
msgid "Select a namespace to fork the project"
msgstr "Seleccione un espacio de nombres para hacer un fork del proyecto"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr "Seleccione un repositorio"
+
msgid "Select a template repository"
msgstr "Seleccione una plantilla de repositorio"
@@ -8395,14 +9850,11 @@ msgstr "Selecciona una zona horaria"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Seleccione un clúster existente de Kubernetes o cree un cluster nuevo"
-msgid "Select assignee"
-msgstr "Seleccionar persona asignada"
-
msgid "Select branch/tag"
msgstr ""
msgid "Select members to invite"
-msgstr ""
+msgstr "Seleccione los miembros a invitar"
msgid "Select project"
msgstr "Seleccionar proyecto"
@@ -8428,39 +9880,63 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
-msgstr "Sincronización selectiva"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "Enviar correo electrónico"
msgid "Send report"
-msgstr ""
+msgstr "Enviar informe"
msgid "Send usage data"
msgstr "Enviar estadísticas de uso"
msgid "Sentry API URL"
-msgstr ""
+msgstr "URL de la API de Sentry"
msgid "Sep"
msgstr "Sep"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "Septiembre"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "Versión del servidor"
msgid "Serverless"
+msgstr "Serverless"
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
msgstr ""
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8486,13 +9959,13 @@ msgid "Serverless|If you believe none of these apply, please check back later as
msgstr ""
msgid "Serverless|Install Knative"
-msgstr ""
+msgstr "Instalar Knative"
msgid "Serverless|Learn more about Serverless"
-msgstr ""
+msgstr "Obtenga más información sobre Serverless"
msgid "Serverless|No functions available"
-msgstr ""
+msgstr "No hay funciones disponibles"
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr "Plantillas de Servicio"
msgid "Service URL"
msgstr "URL del servicio"
+msgid "Session duration (minutes)"
+msgstr "Duración de la sesión (minutos)"
+
msgid "Session expiration, projects limit and attachment size."
msgstr "Caducidad de la sesión, límite de proyectos y tamaño de los archivos adjuntos."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr "Establecer una nueva contraseña"
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "Configurar CI/CD"
@@ -8546,7 +10043,10 @@ msgid "Set up assertions/attributes/claims (email, first_name, last_name) and Na
msgstr "Configure las afirmaciones/atributos/reclamaciones (correo electrónico, first_name, last_name) y el NameID según %{docsLinkStart}la documentación %{icon}%{docsLinkEnd}"
msgid "Set up new U2F device"
-msgstr ""
+msgstr "Configurar nuevo dispositivo U2F"
+
+msgid "Set up new password"
+msgstr "Configurar nueva contraseña"
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8567,16 +10067,16 @@ msgid "SetStatusModal|Remove status"
msgstr ""
msgid "SetStatusModal|Set a status"
-msgstr ""
+msgstr "Establecer un estado"
msgid "SetStatusModal|Set status"
-msgstr ""
+msgstr "Establecer estado"
msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
msgstr ""
msgid "SetStatusModal|What's your status?"
-msgstr ""
+msgstr "¿Cuál es su estado?"
msgid "Settings"
msgstr "Configuración"
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr "Transacciones de Sherlock"
+msgid "Show all activity"
+msgstr "Mostrar toda la actividad"
+
msgid "Show command"
msgstr "Mostrar comando"
+msgid "Show comments only"
+msgstr "Mostrar solo los comentarios"
+
msgid "Show complete raw log"
msgstr "Mostrar el registro completo sin procesar"
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr "Mostrar la última versión"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Mostrar páginas padre"
@@ -8656,16 +10165,16 @@ msgid "Sign in to \"%{group_name}\""
msgstr ""
msgid "Sign in using smart card"
-msgstr ""
+msgstr "Inicie sesión mediante una tarjeta inteligente"
msgid "Sign in via 2FA code"
-msgstr ""
+msgstr "Inicie sesión mediante un código 2FA"
msgid "Sign in with Single Sign-On"
msgstr "Inicie sesión mediante inicio de sesión único"
msgid "Sign in with smart card"
-msgstr ""
+msgstr "Inicie sesión con una tarjeta inteligente"
msgid "Sign out"
msgstr "Cerrar sesión"
@@ -8676,15 +10185,24 @@ msgstr "Restricciones de inicio de sesión"
msgid "Sign-up restrictions"
msgstr "Restricciones de registro"
-msgid "Similar issues"
+msgid "Signing in using %{label} has been disabled"
msgstr ""
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr "Incidencias similares"
+
msgid "Size"
msgstr "Tamaño"
msgid "Size and domain settings for static websites"
msgstr "Configuración del tamaño y del dominio para los sitios web estáticos"
+msgid "Size limit per repository (MB)"
+msgstr "Límite de tamaño por repositorio (MB)"
+
msgid "Slack application"
msgstr "Aplicación Slack"
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr "Fragmentos de código"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr "Algo salió mal al buscar comentarios. Inténtalo de nuevo. ¡Por favor,
msgid "Something went wrong while fetching group member contributions"
msgstr "Algo salió mal al obtener las contribuciones de los miembros del grupo. ¡Por favor, inténtelo de nuevo!"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Algo salió mal al buscar los entornos para este merge request. ¡Por favor, inténtelo de nuevo!"
@@ -8778,6 +10320,9 @@ msgstr "Algo salió mal, no es posible obtener los proyectos operativos"
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Algo salió mal. ¡Por favor, inténtelo de nuevo!"
@@ -8797,10 +10342,10 @@ msgid "Sort direction"
msgstr ""
msgid "SortOptions|Access level, ascending"
-msgstr ""
+msgstr "Nivel de acceso, ascendente"
msgid "SortOptions|Access level, descending"
-msgstr ""
+msgstr "Nivel de acceso, descendente"
msgid "SortOptions|Created date"
msgstr "Fecha de creación"
@@ -8854,7 +10399,7 @@ msgid "SortOptions|More weight"
msgstr ""
msgid "SortOptions|Most popular"
-msgstr ""
+msgstr "Más populares"
msgid "SortOptions|Most stars"
msgstr ""
@@ -8884,10 +10429,10 @@ msgid "SortOptions|Oldest updated"
msgstr ""
msgid "SortOptions|Popularity"
-msgstr ""
+msgstr "Popularidad"
msgid "SortOptions|Priority"
-msgstr ""
+msgstr "Prioridad"
msgid "SortOptions|Recent last activity"
msgstr ""
@@ -8896,7 +10441,7 @@ msgid "SortOptions|Recent sign in"
msgstr ""
msgid "SortOptions|Start date"
-msgstr ""
+msgstr "Fecha de inicio"
msgid "SortOptions|Start later"
msgstr ""
@@ -8911,7 +10456,7 @@ msgid "Source"
msgstr "Fuente"
msgid "Source (branch or tag)"
-msgstr ""
+msgstr "Origen (rama o tag)"
msgid "Source code"
msgstr "Código fuente"
@@ -8928,6 +10473,9 @@ msgstr "Registros de correo no deseado"
msgid "Spam and Anti-bot Protection"
msgstr "Protección Anti-Spam y Anti-Bot"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Ejecutores específicos"
@@ -8968,15 +10516,24 @@ msgid "StarProject|Star"
msgstr "Destacar"
msgid "Starred Projects"
-msgstr ""
+msgstr "Proyectos destacados"
msgid "Starred Projects' Activity"
-msgstr ""
+msgstr "Actividad de los proyectos destacados"
msgid "Starred projects"
msgstr "Proyectos favoritos"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
+msgstr "Estrellas"
+
+msgid "Start GitLab Ultimate trial"
msgstr ""
msgid "Start Web Terminal"
@@ -8985,11 +10542,14 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Iniciar una %{new_merge_request} con estos cambios"
-msgid "Start a review"
+msgid "Start a new merge request"
msgstr ""
+msgid "Start a review"
+msgstr "Comenzar una revisión"
+
msgid "Start and due date"
-msgstr ""
+msgstr "Fechas de inicio y de fin"
msgid "Start cleanup"
msgstr ""
@@ -9016,10 +10576,13 @@ msgid "Started"
msgstr "Iniciado"
msgid "Started %{startsIn}"
+msgstr "Iniciado %{startsIn}"
+
+msgid "Started asynchronous removal of all repository check states."
msgstr ""
msgid "Starting..."
-msgstr ""
+msgstr "Iniciando..."
msgid "Starts %{startsIn}"
msgstr ""
@@ -9034,16 +10597,16 @@ msgid "Status"
msgstr "Estado"
msgid "Status:"
-msgstr ""
+msgstr "Estado:"
msgid "Stop Terminal"
-msgstr ""
+msgstr "Detener Terminal"
msgid "Stop environment"
msgstr "Detener este entorno"
msgid "Stop impersonation"
-msgstr ""
+msgstr "Detener suplantación"
msgid "Stop this environment"
msgstr "Detener este entorno"
@@ -9052,10 +10615,10 @@ msgid "Stopped"
msgstr "Detenido"
msgid "Stopping this environment is currently not possible as a deployment is in progress"
-msgstr ""
+msgstr "En este momento no es posible detener este entorno ya que hay un despliegue en progreso"
msgid "Stopping..."
-msgstr ""
+msgstr "Deteniendo..."
msgid "Storage"
msgstr "Almacenamiento"
@@ -9073,7 +10636,7 @@ msgid "Submit as spam"
msgstr "Enviar como correo no deseado"
msgid "Submit feedback"
-msgstr ""
+msgstr "Enviar comentarios"
msgid "Submit review"
msgstr "Enviar para revisión"
@@ -9091,10 +10654,10 @@ msgid "Subscribe at project level"
msgstr "Suscribir a nivel de proyecto"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "Suscríbase al RSS"
msgid "Subscribe to calendar"
-msgstr ""
+msgstr "Suscríbase al calendario"
msgid "Subscribed"
msgstr "Suscrito"
@@ -9154,7 +10717,7 @@ msgid "SubscriptionTable|This is the number of seats you will be required to pur
msgstr ""
msgid "SubscriptionTable|Trial"
-msgstr ""
+msgstr "Prueba"
msgid "SubscriptionTable|Trial end date"
msgstr ""
@@ -9171,33 +10734,66 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
-msgid "Suggested change"
+msgid "Successfully blocked"
msgstr ""
-msgid "Sunday"
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
msgstr ""
+msgid "Suggested change"
+msgstr "Cambio sugerido"
+
+msgid "Sunday"
+msgstr "Domingo"
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr "URL de la página de soporte"
+
msgid "Switch branch/tag"
msgstr "Cambiar rama/etiqueta"
msgid "Sync information"
msgstr "Sincronizar información"
+msgid "System"
+msgstr "Sistema"
+
msgid "System Hooks"
msgstr "Hooks de sistema"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Información del sistema"
msgid "System default (%{default})"
-msgstr ""
+msgstr "Sistema predeterminado (%{default})"
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "Métricas del sistema (personalizadas)"
@@ -9205,10 +10801,10 @@ msgid "System metrics (Kubernetes)"
msgstr "Métricas del sistema (Kubernetes)"
msgid "Tag"
-msgstr ""
+msgstr "Etiqueta"
msgid "Tag list:"
-msgstr ""
+msgstr "Listas de etiquetas:"
msgid "Tags"
msgstr "Etiquetas"
@@ -9241,13 +10837,13 @@ msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
msgstr ""
msgid "TagsPage|Edit release notes"
-msgstr ""
+msgstr "Editar las notas de la versión"
msgid "TagsPage|Existing branch name, tag, or commit SHA"
-msgstr ""
+msgstr "Nombre de la rama, del tag o commit SHA existente"
msgid "TagsPage|Filter by tag name"
-msgstr ""
+msgstr "Filtrar por el nombre del tag"
msgid "TagsPage|New Tag"
msgstr "Nueva etiqueta"
@@ -9304,10 +10900,10 @@ msgid "Templates"
msgstr "Plantillas"
msgid "Terminal"
-msgstr ""
+msgstr "Terminal"
msgid "Terminal for environment"
-msgstr ""
+msgstr "Terminal para el entorno"
msgid "Terms of Service Agreement and Privacy Policy"
msgstr "Términos del acuerdo de servicio y de la política de privacidad"
@@ -9321,12 +10917,23 @@ msgstr "Prueba SAML SSO"
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "¡Gracias! No mostrar esto de nuevo"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,24 +10970,84 @@ msgstr "La colección de eventos agregados a los datos recopilados para esa etap
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "La relación con la bifurcación se ha eliminado."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "La etapa de incidencia muestra el tiempo que toma desde la creación de un tema hasta asignar el tema a un hito, o añadir el tema a una lista en el panel de temas. Empieza a crear temas para ver los datos de esta etapa."
msgid "The maximum file size allowed is %{size}."
-msgstr ""
+msgstr "El tamaño máximo de archivo permitido es de %{size}."
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr "El proyecto puede ser accedido por cualquier usuario conectado."
msgid "The project can be accessed without any authentication."
msgstr "El proyecto puede accederse sin ninguna autenticación."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "El repositorio para este proyecto no existe."
@@ -9423,6 +11105,21 @@ msgstr "La etapa de revisión muestra el tiempo desde la creación de la solicit
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "La hoja de ruta muestra el progreso de sus tareas épicas a lo largo de una línea de tiempo"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez."
@@ -9438,9 +11135,18 @@ msgstr "El tiempo utilizado por cada entrada de datos obtenido por esa etapa."
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9472,7 +11178,7 @@ msgid "There are no labels yet"
msgstr "Aún no hay etiquetas"
msgid "There are no open issues"
-msgstr ""
+msgstr "No hay ninguna incidencia abierta"
msgid "There are no open merge requests"
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "Se ha producido un error al cargar el calendario de actividades de los usuarios."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Esta instancia de GitLab aún no proporciona ningún 'runner' compartido. Los administradores de instancias pueden registrar 'runner' compartidos en el área de administración."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "Esta aplicación fue creada por %{link_to_owner}."
@@ -9543,6 +11267,21 @@ msgstr "El alcance de este tablero es limitado"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Esta rama ha cambiado desde que se comenzó a editar. ¿Le gustaría crear una nueva rama?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr "Este directorio"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr "Este campo es obligatorio."
+
msgid "This group"
msgstr "Este grupo"
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Esta incidencia es confidencial"
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,19 +11399,25 @@ msgstr "Esto significa que no puede enviar código hasta que cree un repositorio
msgid "This merge request is locked."
msgstr "Este merge request está bloqueado."
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr ""
msgid "This page will be removed in a future release."
-msgstr ""
+msgstr "Esta página se eliminará en una versión futura."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr "Este repositorio"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr "hace %s meses"
msgid "Timeago|%s months remaining"
msgstr "%s meses restantes"
-msgid "Timeago|%s seconds ago"
-msgstr "hace %s segundos"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s segundos restantes"
@@ -9902,7 +11662,7 @@ msgid "Title"
msgstr "Título"
msgid "Titles and Filenames"
-msgstr ""
+msgstr "Títulos y nombres de archivos"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr "Hoy"
msgid "Todo"
msgstr "Tareas pendientes"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "Tareas pendientes"
@@ -10019,7 +11782,7 @@ msgid "Toggle comments for this file"
msgstr ""
msgid "Toggle commit description"
-msgstr ""
+msgstr "Cambiar la descripción del commit"
msgid "Toggle commit list"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr "Mañana"
msgid "Too many changes to show."
msgstr "Hay demasiados cambios para mostrar."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Contribuciones totales"
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "Vista de árbol"
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "Disparar esta acción manual"
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,27 +11904,42 @@ msgstr ""
msgid "Twitter"
msgstr "Twitter"
-msgid "Two-factor authentication"
+msgid "Two-factor Authentication has been disabled for this user"
msgstr ""
+msgid "Two-factor authentication"
+msgstr "Autenticación de doble factor"
+
msgid "Type"
msgstr "Tipo"
msgid "URL"
+msgstr "URL"
+
+msgid "Unable to connect to server: %{error}"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
-msgid "Unblock"
+msgid "Unarchive project"
msgstr ""
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
+msgid "Unblock"
+msgstr "Desbloquear"
+
msgid "Undo"
msgstr "Deshacer"
@@ -10147,11 +11949,14 @@ msgstr ""
msgid "Unknown"
msgstr "Desconocido"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "Desbloquear"
msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
-msgstr ""
+msgstr "Desbloquear este %{issuableDisplayName}? <strong>Todos</strong> podrán comentar."
msgid "Unlocked"
msgstr "Desbloqueado"
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr "Actualizar"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "Actualizar ahora"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Actualice el nombre de su grupo, su descripción, su avatar y su visibilidad."
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr "Actualizado"
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "Actualizando"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Actualice su plan para activar la búsqueda global avanzada."
@@ -10237,11 +12060,14 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
msgid "Upload CSV file"
-msgstr ""
+msgstr "Subir fichero CSV"
msgid "Upload New File"
msgstr "Subir nuevo archivo"
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "Hacer clic para subir"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10286,7 +12124,7 @@ msgid "Use one line per URI"
msgstr ""
msgid "Use template"
-msgstr ""
+msgstr "Usar plantilla"
msgid "Use the following registration token during setup:"
msgstr ""
@@ -10303,21 +12141,57 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
-msgid "User Settings"
+msgid "User OAuth applications"
msgstr ""
+msgid "User Settings"
+msgstr "Ajustes de usuario"
+
msgid "User and IP Rate Limits"
+msgstr "Usuarios y ratio de límites de IP"
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
msgstr ""
msgid "User map"
msgstr ""
-msgid "UserProfile|Activity"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
msgstr ""
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr "Actividad"
+
msgid "UserProfile|Already reported for abuse"
msgstr ""
@@ -10325,13 +12199,13 @@ msgid "UserProfile|Contributed projects"
msgstr "Proyectos contribuidos"
msgid "UserProfile|Edit profile"
-msgstr ""
+msgstr "Editar perfil"
msgid "UserProfile|Explore public groups to find projects to contribute to."
msgstr ""
msgid "UserProfile|Groups"
-msgstr ""
+msgstr "Grupos"
msgid "UserProfile|Groups are the best way to manage projects and members."
msgstr ""
@@ -10393,16 +12267,37 @@ msgstr ""
msgid "Users"
msgstr "Usuarios"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
-msgid "Validate"
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
msgstr ""
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Validate"
+msgstr "Validar"
+
msgid "Validate your GitLab CI configuration file"
+msgstr "Valide su archivo de configuración de GitLab CI"
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
msgstr ""
msgid "Value"
+msgstr "Valor"
+
+msgid "Variables"
msgstr ""
msgid "Various container registry settings."
@@ -10429,9 +12324,6 @@ msgstr "Verificado"
msgid "Version"
msgstr "Versión"
-msgid "View %{alerts}"
-msgstr "Ver %{alerts}"
-
msgid "View app"
msgstr "Ver aplicación"
@@ -10441,8 +12333,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "Ver documentación"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "Ver lista de tareas épicas"
@@ -10453,12 +12345,15 @@ msgstr "Ver archivo @ "
msgid "View group labels"
msgstr ""
-msgid "View issue"
-msgstr "Ver incidencia"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "Ver en GitLab"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "Ver trabajos"
@@ -10492,6 +12387,9 @@ msgstr "Nivel de visibilidad"
msgid "Visibility level:"
msgstr "Nivel de visibilidad:"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "Visibilidad:"
@@ -10528,12 +12426,18 @@ msgstr "Archivo"
msgid "Vulnerability|Identifiers"
msgstr "Identificadores"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "Instancias"
msgid "Vulnerability|Links"
msgstr "Enlaces"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "Proyecto"
@@ -10562,7 +12466,7 @@ msgid "We couldn't find any results matching"
msgstr ""
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
-msgstr ""
+msgstr "Hemos detectado spam potencial en %{humanized_resource_name}. Por favor resuelva el reCAPTCHA para poder continuar."
msgid "We don't have enough data to show this stage."
msgstr "No hay suficientes datos para mostrar en esta etapa."
@@ -10571,20 +12475,26 @@ msgid "We heard back from your U2F device. You have been authenticated."
msgstr ""
msgid "We want to be sure it is you, please confirm you are not a robot."
-msgstr ""
+msgstr "Queremos asegurarnos de que sea usted, por favor, ayudenos a confirmar que no es un robot."
msgid "Web IDE"
msgstr "Web IDE"
msgid "Web Terminal"
-msgstr ""
+msgstr "Terminal web"
msgid "Web terminal"
msgstr "Terminal web"
-msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
msgstr ""
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr "Los Webhooks le permiten invocar una URL si, por ejemplo, se realiza un push a o se crea una nueva incidencia. Puede configurar los webhooks para escuchar eventos específicos como pushes, incidencias, o merge request. Los webhooks de grupo se aplicarán a todos los proyectos de un grupo, permitiéndote estandarizar la funcionalidad del webhook a través de todo el grupo."
+
msgid "Weeks"
msgstr "Semanas"
@@ -10595,16 +12505,24 @@ msgid "Weight %{weight}"
msgstr "Peso %{weight}"
msgid "When a runner is locked, it cannot be assigned to other projects"
-msgstr ""
+msgstr "Cuando un ejecutor está bloqueado, no se puede asignar a otros proyectos"
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "Cuando está habilitado, los usuarios no pueden utilizar GitLab hasta que se hayan aceptado los términos de uso."
-msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr "Al dejar la URL en blanco, aún se pueden especificar las etiquetas de clasificación sin deshabilitar las funciones de proyectos cruzados ni realizar comprobaciones de autorización externas."
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
-msgstr ""
+msgstr "Cuando:"
msgid "Who can see this group?"
msgstr "¿Quién puede ver este grupo?"
@@ -10615,6 +12533,9 @@ msgstr "¿Quién podrá ver a este grupo?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Clonar su wiki"
@@ -10628,61 +12549,61 @@ msgid "WikiClone|It is recommended to install %{markdown} so that GFM features r
msgstr "Se recomienda instalar %{markdown} para que las características de GFM se procesen localmente:"
msgid "WikiClone|Start Gollum and edit locally"
-msgstr ""
+msgstr "Iniciar a Gollum y editar localmente"
msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
-msgstr ""
+msgstr "Sugerencia: Puede mover esta página agregando la ruta al comienzo del título."
msgid "WikiEdit|There is already a page with the same title in that path."
-msgstr ""
+msgstr "Ya hay una página con el mismo título en la ruta."
msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
-msgstr ""
+msgstr "Sugerir una mejora en el wiki"
msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
-msgstr ""
+msgstr "Debe ser un miembro del proyecto para poder agregar páginas al wiki. Si tiene sugerencias sobre cómo mejorar el wiki para este proyecto, considere abrir una incidencia en el enlace, %{issues_link}."
msgid "WikiEmptyIssueMessage|issue tracker"
-msgstr ""
+msgstr "Gestor de incidencias"
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
-msgstr ""
+msgstr "Un wiki es una herramienta en la que puede almacenar todos los detalles sobre su proyecto. Puede indicar por qué lo ha creado, sus principios, instrucciones sobre cómo utilizarlo, etc."
msgid "WikiEmpty|Create your first page"
-msgstr ""
+msgstr "Cree su primera página"
msgid "WikiEmpty|Suggest wiki improvement"
-msgstr ""
+msgstr "Sugerir una mejora del wiki"
msgid "WikiEmpty|The wiki lets you write documentation for your project"
-msgstr ""
+msgstr "El wiki le permite escribir documentación para su proyecto"
msgid "WikiEmpty|This project has no wiki pages"
-msgstr ""
+msgstr "Este proyecto no tiene páginas en el wiki"
msgid "WikiEmpty|You must be a project member in order to add wiki pages."
-msgstr ""
+msgstr "Debe ser un miembro del proyecto para agregar páginas al wiki."
msgid "WikiHistoricalPage|This is an old version of this page."
-msgstr ""
+msgstr "Esta es una versión antigua de esta página."
msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
-msgstr ""
+msgstr "Puedes ver %{most_recent_link} o revisar la %{history_link}."
msgid "WikiHistoricalPage|history"
-msgstr ""
+msgstr "historial"
msgid "WikiHistoricalPage|most recent version"
-msgstr ""
+msgstr "versión más reciente"
msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
-msgstr ""
+msgstr "Más ejemplos disponilbles en %{docs_link}"
msgid "WikiMarkdownDocs|documentation"
-msgstr ""
+msgstr "documentación"
msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
-msgstr ""
+msgstr "Para vincular a una página (nueva), simplemente escriba %{link_example}"
msgid "WikiNewPagePlaceholder|how-to-setup"
msgstr ""
@@ -10691,34 +12612,34 @@ msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We wi
msgstr ""
msgid "WikiNewPageTitle|New Wiki Page"
-msgstr ""
+msgstr "Nueva página de Wiki"
msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
-msgstr ""
+msgstr "¿Está seguro de que desea eliminar esta página?"
msgid "WikiPageConfirmDelete|Delete page"
-msgstr ""
+msgstr "Eliminar página"
msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
-msgstr ""
+msgstr "¿Desea eliminar la página %{pageTitle}?"
msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
-msgstr ""
+msgstr "Alguien editó esta página al mismo tiempo que usted. Por favor revise %{page_link} y asegúrese de que los cambios realizados por usted no eliminen sin querer los cambios realizados por la otra persona."
msgid "WikiPageConflictMessage|the page"
-msgstr ""
+msgstr "la página"
msgid "WikiPageCreate|Create %{page_title}"
-msgstr ""
+msgstr "Crear %{page_title}"
msgid "WikiPageEdit|Update %{page_title}"
-msgstr ""
+msgstr "Actualizar %{page_title}"
msgid "WikiPage|Page slug"
msgstr ""
msgid "WikiPage|Write your content or drag files here…"
-msgstr ""
+msgstr "Escriba su contenido o arrastre archivos aquí…"
msgid "Wiki|Create Page"
msgstr "Crear página"
@@ -10726,6 +12647,9 @@ msgstr "Crear página"
msgid "Wiki|Create page"
msgstr "Crear página"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Editar página"
@@ -10744,22 +12668,37 @@ msgstr "Versión de la página"
msgid "Wiki|Pages"
msgstr "Páginas"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Páginas del Wiki"
msgid "Will deploy to"
-msgstr ""
+msgstr "Se desplegará a"
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
-msgstr ""
+msgstr "Con contribution analytics, puede tener una visión general de la actividad de las incidencias, los merge requests y los eventos push de su organización y sus miembros."
msgid "Withdraw Access Request"
msgstr "Retirar Solicitud de Acceso"
-msgid "Write a comment or drag your files here…"
+msgid "Workflow Help"
msgstr ""
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr "Escriba un comentario o arrastre sus archivos aquí…"
+
+msgid "Write access allowed"
+msgstr "Acceso de escritura permitido"
+
msgid "Write milestone description..."
+msgstr "Escriba la descripción del hito..."
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
@@ -10775,7 +12714,7 @@ msgid "Yesterday"
msgstr "Ayer"
msgid "You"
-msgstr ""
+msgstr "Usted"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr ""
@@ -10792,39 +12731,60 @@ msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{f
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
-msgid "You are on a read-only GitLab instance."
+msgid "You are now impersonating %{username}"
msgstr ""
+msgid "You are on a read-only GitLab instance."
+msgstr "Está en una instancia de sólo lectura GitLab."
+
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
msgstr ""
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
-msgstr ""
+msgstr "Puede %{linkStart}ver el blob%{linkEnd} en su lugar."
msgid "You can also create a project from the command line."
-msgstr ""
+msgstr "También puede crear un proyecto desde la línea de comandos."
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr "También puede subir archivos existentes desde su ordenador utilizando las instrucciones que se muestran a continuación."
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr "Puede crear archivos directamente en GitLab utiliznado una de las siguientes opciones."
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
-msgstr ""
+msgstr "Puede instalar fácilmente un ejecutor en un clúster de Kubernetes. %{link_to_help_page}"
msgid "You can move around the graph by using the arrow keys."
+msgstr "Puede moverse por el gráfico usando las teclas de flecha."
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
msgstr ""
msgid "You can only add files when you are on a branch"
msgstr "Solo puedes agregar archivos cuando estás en una rama"
msgid "You can only edit files when you are on a branch"
-msgstr ""
+msgstr "Solo puede agregar archivos cuando está en una rama"
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10832,55 +12792,88 @@ msgid "You can set up jobs to only use Runners with specific tags. Separate tags
msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr "Puede probar su archivo .gitlab-ci.yml en %{linkStart}CI Lint%{linkEnd}."
+
+msgid "You cannot impersonate a blocked user"
msgstr ""
-msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
msgstr ""
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr "No puedes escribir en una instancia secundaria de sólo lectura de GitLab Geo. Utilice %{link_to_primary_node} en lugar de otro."
+
msgid "You cannot write to this read-only GitLab instance."
+msgstr "No puede escribir en esta instancia de sólo lectura de GitLab."
+
+msgid "You could not create a new trigger."
msgstr ""
-msgid "You do not have any subscriptions yet"
+msgid "You could not take ownership of trigger."
msgstr ""
+msgid "You do not have any subscriptions yet"
+msgstr "Aún no tiene ninguna suscripción"
+
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
msgstr ""
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
-msgstr ""
+msgstr "No tiene los permisos necesarios para sobreescribir la configuración de la sincronización de grupos de LDAP."
msgid "You don't have any applications"
-msgstr ""
+msgstr "No tiene ninguna aplicación"
msgid "You don't have any authorized applications"
-msgstr ""
+msgstr "No tiene ninguna aplicación autorizada"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
+msgstr "No tiene permisos"
+
+msgid "You have not added any approvers. Start by adding users or groups."
msgstr ""
msgid "You have reached your project limit"
msgstr "Has alcanzado el límite de tu proyecto"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
msgid "You must accept our Terms of Service and privacy policy in order to register an account"
-msgstr ""
+msgstr "Debe aceptar nuestros Términos de servicio y la política de privacidad para poder registrar una cuenta"
msgid "You must have maintainer access to force delete a lock"
-msgstr ""
+msgstr "Debe tener permiso como mantenedor para forzar la eliminación de un bloqueo"
msgid "You need a different license to enable FileLocks feature"
-msgstr ""
+msgstr "Necesita una licencia diferente para habilitar la función FileLocks"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
-msgstr ""
+msgstr "Necesita la versión %{min_git_lfs_version} de git-lfs para continuar. Por favor, visite la URL https://git-lfs.github.com"
msgid "You need permission."
msgstr "Necesitas permisos."
@@ -10888,12 +12881,24 @@ msgstr "Necesitas permisos."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "No recibirás ninguna notificación por correo electrónico"
@@ -10921,14 +12926,23 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
-msgid "You're receiving this email because %{reason}."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
msgstr ""
+msgid "You're receiving this email because %{reason}."
+msgstr "Está recibiendo este correo porque %{reason}."
+
msgid "You're receiving this email because of your account on %{host}."
msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
-msgstr ""
+msgstr "Está recibiendo este correo electrónico porque tiene su cuenta en %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgid "YouTube"
msgstr "YouTube"
@@ -10954,6 +12968,15 @@ msgstr "Sus tareas pendientes"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "Sus aplicaciones (%{size})"
@@ -10964,17 +12987,23 @@ msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (versi
msgstr ""
msgid "Your changes can be committed to %{branch_name} because a merge request is open."
-msgstr ""
+msgstr "Puede hacer commit de sus cambios a %{branch_name} porque hay un merge request abierto."
msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
-msgstr ""
+msgstr "Se han confirmado sus cambios. Commit %{commitId} %{commitStats}"
msgid "Your changes have been saved"
msgstr "Se han guardado sus cambios"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Tus comentarios no serán visibles al público."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Tu nombre"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "Tus proyectos"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "un usuario eliminado"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "hace"
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr "entre otras cosas"
-msgid "assign yourself"
-msgstr "asignar a uno mismo"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "nombre de la rama"
@@ -11038,16 +13085,38 @@ msgstr "%{namespace} está afectado por %{vulnerability}."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} más"
-msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
-msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
+msgstr[0] "%{reportType} %{status} detectada %{fixedCount} vulnerabilidad corregida"
+msgstr[1] "%{reportType} %{status} detectado %{fixedCount} vulnerabilidad corregida"
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11071,7 +13140,7 @@ msgstr[0] ""
msgstr[1] ""
msgid "ciReport|%{reportType} detected no vulnerabilities"
-msgstr ""
+msgstr "%{reportType} no detectó vulnerabilidades"
msgid "ciReport|%{reportType} is loading"
msgstr "%{reportType} está cargando"
@@ -11079,6 +13148,9 @@ msgstr "%{reportType} está cargando"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(se han producido errores al cargar resultados)"
@@ -11088,6 +13160,15 @@ msgstr "(está cargando)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(está cargando, se han producido errores al cargar resultados)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "Clase"
@@ -11097,12 +13178,24 @@ msgstr "Calidad del código"
msgid "ciReport|Confidence"
msgstr "Confianza"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "Análisis de contenedores"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "El análisis de contenedores detecta vulnerabilidades conocidas en sus imágenes de Docker."
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11145,9 +13238,18 @@ msgstr "Solucionado:"
msgid "ciReport|Identifiers"
msgstr "Identificadores"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "Instancias"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "Obtenga más información sobre cómo interactuar con los informes de seguridad (Alpha)."
@@ -11191,9 +13293,6 @@ msgstr "No hay cambios en las métricas de rendimiento"
msgid "ciReport|Performance metrics"
msgstr "Métricas de rendimiento"
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11215,6 +13314,9 @@ msgstr "Static Application Security Testing (SAST) detecta vulnerabilidades cono
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,12 +13358,33 @@ msgstr "Ver informe completo"
msgid "ciReport|on pipeline"
msgstr "en el pipeline"
-msgid "command line instructions"
-msgstr "instrucciones de la línea de comando"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr ""
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr "desplegar token"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "deshabilitado"
@@ -11302,14 +13437,29 @@ msgid "epic"
msgstr ""
msgid "error"
-msgstr ""
+msgstr "error"
msgid "error code:"
-msgstr ""
+msgstr "código de error:"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} actualizará la hora estimada con el último comando."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "para este proyecto"
@@ -11317,17 +13467,11 @@ msgid "from"
msgstr "de"
msgid "group"
-msgstr ""
-
-msgid "help"
-msgstr "ayuda"
+msgstr "grupo"
msgid "here"
msgstr "aquí"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://tu-servidor-bitbucket"
@@ -11335,7 +13479,7 @@ msgid "image diff"
msgstr ""
msgid "import flow"
-msgstr ""
+msgstr "flujo de importación"
msgid "importing"
msgstr "importando"
@@ -11347,7 +13491,7 @@ msgid "in project %{link_to_project}"
msgstr ""
msgid "index"
-msgstr ""
+msgstr "índice"
msgid "instance completed"
msgid_plural "instances completed"
@@ -11390,6 +13534,9 @@ msgstr "último despliegue"
msgid "latest version"
msgstr "última versión"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "gestión de licencias"
@@ -11401,8 +13548,8 @@ msgstr ""
msgid "merge request"
msgid_plural "merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "merge request"
+msgstr[1] "merge requests"
msgid "missing"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr "Aprobar"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Aprobado por"
@@ -11459,7 +13612,7 @@ msgid "mrWidget|Checking ability to merge automatically"
msgstr ""
msgid "mrWidget|Cherry-pick"
-msgstr ""
+msgstr "Cherry-pick"
msgid "mrWidget|Cherry-pick this merge request in a new merge request"
msgstr ""
@@ -11518,17 +13671,20 @@ msgstr "Merge local"
msgid "mrWidget|Merge request approved"
msgstr "Solicitud de merge request aprobada"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr "Merge realizado por"
-msgid "mrWidget|No Approval required"
-msgstr "No es necesaria la aprobación"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "No es necesaria una aprobación; todavía puede aprobarlo"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "Abrir en Web IDE"
@@ -11543,19 +13699,19 @@ msgid "mrWidget|Ready to be merged automatically. Ask someone with write access
msgstr ""
msgid "mrWidget|Refresh"
-msgstr ""
+msgstr "Actualizar"
msgid "mrWidget|Refresh now"
-msgstr ""
+msgstr "Actualizar ahora"
msgid "mrWidget|Refreshing now"
-msgstr ""
+msgstr "Actualizar ahora"
msgid "mrWidget|Remove your approval"
-msgstr ""
+msgstr "Eliminar su aprobación"
msgid "mrWidget|Request to merge"
-msgstr ""
+msgstr "Solicitud de merge"
msgid "mrWidget|Requires 1 more approval"
msgid_plural "mrWidget|Requires %d more approvals"
@@ -11574,19 +13730,22 @@ msgid "mrWidget|Resolve these conflicts or ask someone with write access to this
msgstr ""
msgid "mrWidget|Revert"
-msgstr ""
+msgstr "Revertir"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "Establecido por"
msgid "mrWidget|The changes were merged into"
-msgstr ""
+msgstr "Los cambios se fusionaron en"
msgid "mrWidget|The changes were not merged into"
-msgstr ""
+msgstr "Los cambios no se fusionaron en"
msgid "mrWidget|The changes will be merged into"
msgstr ""
@@ -11616,16 +13775,16 @@ msgid "mrWidget|There are merge conflicts"
msgstr ""
msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
-msgstr ""
+msgstr "Hay discusiones sin resolver. Por favor resuelve estas discusiones"
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
msgstr ""
msgid "mrWidget|This merge request failed to be merged automatically"
-msgstr ""
+msgstr "Esta merge request no se realizó automáticamente"
msgid "mrWidget|This merge request is in the process of being merged"
-msgstr ""
+msgstr "Este mergre request está en proceso de ser realizado"
msgid "mrWidget|This project is archived, write access has been disabled"
msgstr "Este proyecto está archivado, se ha deshabilitado el acceso de escritura"
@@ -11640,7 +13799,7 @@ msgid "mrWidget|You can merge this merge request manually using the"
msgstr ""
msgid "mrWidget|branch does not exist."
-msgstr ""
+msgstr "la rama no existe."
msgid "mrWidget|command line"
msgstr "línea de comandos"
@@ -11691,22 +13850,30 @@ msgstr "contraseña"
msgid "personal access token"
msgstr "token de acceso personal"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
-msgstr ""
+msgstr "privado"
msgid "private key does not match certificate."
msgstr "la clave privada no coincide con el certificado."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "proyecto"
msgstr[1] "proyectos"
msgid "quick actions"
-msgstr ""
+msgstr "acciones rápidas"
msgid "register"
-msgstr ""
+msgstr "registrar"
msgid "remaining"
msgstr "restante"
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] "respuesta"
msgstr[1] "respuestas"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr "iniciado"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
@@ -11771,7 +13968,7 @@ msgid "triggered"
msgstr ""
msgid "updated"
-msgstr ""
+msgstr "actualizado"
msgid "username"
msgstr "usuario"
@@ -11797,5 +13994,5 @@ msgstr[0] "dentro de un %d minuto"
msgstr[1] "dentro de %d minutos "
msgid "yaml invalid"
-msgstr ""
+msgstr "El fichero yaml no es válido"
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index ee015172f6d..36f5074b8ff 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: et\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:14\n"
+"PO-Revision-Date: 2019-04-12 12:38\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index c943b2814a3..c2139dcc495 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: fil\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:14\n"
+"PO-Revision-Date: 2019-04-12 12:38\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 83e62a5f9de..0843adc6d56 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:14\n"
+"PO-Revision-Date: 2019-04-12 12:38\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " Statut"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " et"
@@ -32,16 +41,18 @@ msgstr[0] " amélioré sur %d point"
msgstr[1] " amélioré sur %d points"
msgid " or "
-msgstr ""
+msgstr " ou "
msgid " or <#epic id>"
-msgstr ""
+msgstr " ou <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " ou <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "« %{query} » dans les projets"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d commentaire"
+msgstr[1] "%d commentaires"
msgid "%d commit"
msgid_plural "%d commits"
@@ -54,7 +65,7 @@ msgstr[0] "%d commit de retard"
msgstr[1] "%d commits de retard"
msgid "%d commits"
-msgstr ""
+msgstr "%d commits"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -78,8 +89,8 @@ msgstr[1] "%d tickets"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d ticket sélectionné"
+msgstr[1] "%d tickets sélectionnés"
msgid "%d layer"
msgid_plural "%d layers"
@@ -124,13 +135,27 @@ msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build
msgstr "%{counter_storage} (%{counter_repositories} dépôts, %{counter_build_artifacts} artefacts construits, %{counter_lfs_objects} LFS)"
msgid "%{count} %{alerts}"
-msgstr ""
+msgstr "%{count} %{alerts}"
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "%{count} approbation de %{name} requise"
+msgstr[1] "%{count} approbations de %{name} requises"
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{count} approbations de %{name}"
msgid "%{count} more"
-msgstr ""
+msgstr "encore %{count}"
msgid "%{count} more assignees"
-msgstr ""
+msgstr "%{count} autres personnes assignées"
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{count} approbations de %{name} sur %{required}"
+
+msgid "%{count} of %{total}"
+msgstr "%{count} sur %{total}"
msgid "%{count} participant"
msgid_plural "%{count} participants"
@@ -146,10 +171,10 @@ msgid "%{filePath} deleted"
msgstr "%{filePath} supprimé"
msgid "%{firstLabel} +%{labelCount} more"
-msgstr ""
+msgstr "%{firstLabel} et %{labelCount} de plus"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} non trouvé dans ce commit"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "Les %{group_docs_link_start}groupes%{group_docs_link_end} vous permettent de gérer plusieurs projets et d’y collaborer. Les membres d’un groupe ont accès à tous ses projets."
@@ -157,15 +182,24 @@ msgstr "Les %{group_docs_link_start}groupes%{group_docs_link_end} vous permetten
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} sera supprimé ! Êtesâ€vous sûr ?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} indisponible"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}En savoir plus%{link_end} sur les droits des rôles"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} Démarré"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} est verrouillé par l’utilisateur GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Avatar de %{name}"
@@ -178,26 +212,35 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent} %% effectués"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
msgstr ""
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{state} epics"
+msgstr "épopées %{state}"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} branche"
+msgstr[1] "%{strong_start}%{branch_count}%{strong_end} branches"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} commit"
+msgstr[1] "%{strong_start}%{commit_count}%{strong_end} commits"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} fichiers"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} étiquette"
+msgstr[1] "%{strong_start}%{tag_count}%{strong_end} étiquettes"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -217,10 +260,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "%{usage_ping_link_start}En savoir plus%{usage_ping_link_end} sur les informations partagées avec GitLab Inc."
msgid "%{user_name} profile page"
+msgstr "Profil de %{user_name}"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(source externe)"
msgid "+ %{count} more"
msgstr "+ %{count} de plus"
@@ -228,8 +274,11 @@ msgstr "+ %{count} de plus"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} de plus"
+msgid "+%{extraOptionCount} more"
+msgstr "%{extraOptionCount} de plus"
+
msgid ", or "
-msgstr ""
+msgstr ", ou "
msgid "- Runner is active and can process any new jobs"
msgstr "- l’exécuteur est actif et peut traiter de nouvelles tâches"
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] "une modification de %{type}"
msgstr[1] "%{count} modifications de %{type}"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "un jour"
+msgstr[1] "%d jours"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "un ticket fermé"
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] "une demande de fusion fermée"
msgstr[1] "%d demandes de fusion fermées"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "Un groupe"
@@ -295,18 +352,30 @@ msgid_plural "%d users"
msgstr[0] "Un utilisateur"
msgstr[1] "%d utilisateurs"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "1ʳᵉ contribution !"
msgid "2FA"
-msgstr ""
+msgstr "Authentification à deux facteurs"
msgid "2FA enabled"
msgstr "2FA activé"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
msgstr ""
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "Veuillez contacter votre administrateur·rice GitLab afin d’obtenir l’autorisation."
+
msgid "403|You don't have the permission to access this page."
msgstr "Vous n’avez pas l’autorisation d’accéder à cette page."
@@ -319,6 +388,9 @@ msgstr "Page introuvable"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "Veuillez contacter votre administrateur GitLab si vous pensez qu’il s’agit d’une erreur."
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> ajoutera « Par <a href=\"#\">@johnsmith</a> » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com, et tous les tickets initialement assignés à johnsmith@example.com seront assignés à <a href=\"#\">@johnsmith</a>."
@@ -347,13 +419,25 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong> poussées Git, plus de <strong>%{commits}</strong> commits effectués par <strong>%{people}</strong> contributeurs."
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>Supprime</strong> la branche source"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "Un « exécuteur » est un processus qui exécute une tâche. Vous pouvez configurer autant d’exécuteurs que nécessaire."
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Un ensemble de graphiques concernant l’intégration continue (CI)"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr "Un modèle d’application de console .NET Core, personnalisable pour tout projet .NET Core"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un site GitBook qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un site Hexo qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un site Hugo qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un site Jekyll qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
msgid "A default branch cannot be chosen for an empty project."
msgstr "Une branche par défaut ne peut pas être choisie pour un projet vide."
@@ -364,18 +448,39 @@ msgstr "Un utilisateur supprimé"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "Un membre de l’équipe de vérification des abus de GitLab examinera votre rapport dès que possible."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "Une nouvelle branche sera créée dans votre dépôt divergent (fork) et une nouvelle demande de fusion sera lancée."
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Un site HTML simple qui utilise Netlify comme intégration et livraison continues (CI/CD) au lieu de GitLab, mais en gardant toutes les autres fonctionnalités géniales de GitLab."
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "Un projet est l’endroit où vous hébergez vos fichiers (dépôt), planifiez votre travail (tickets) et publiez votre documentation (wiki), %{among_other_things_link}."
+msgid "A ready-to-go template for use with Android apps."
+msgstr "Un modèle prêt à l’emploi à utiliser pour les applications Android."
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "Un modèle prêt à l’emploi à utiliser pour les applications iOS en Swift."
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "Une expression rationnelle qui sera utilisée pour déterminer la sortie du test lors du traçage d’une tâche. Laissez vide pour désactiver"
msgid "A user with write access to the source branch selected this option"
msgstr "Une personne avec un accès en écriture à la branche source a sélectionné cette option"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "À propos de GitLab"
@@ -395,7 +500,7 @@ msgid "Abuse reports"
msgstr "Rapports d’abus"
msgid "Accept invitation"
-msgstr ""
+msgstr "Accepter l’invitation"
msgid "Accept terms"
msgstr "Accepter les conditions"
@@ -406,12 +511,18 @@ msgstr "Demandes de fusion acceptées"
msgid "Access Tokens"
msgstr "Jetons d’accès"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "Accès refusé ! Veuillez vérifier que vous pouvez ajouter des clefs de déploiement à ce dépôt."
msgid "Access expiration date"
msgstr "Date d’expiration de l’accès"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "Accès à « %{classification_label} » non autorisé"
@@ -421,6 +532,12 @@ msgstr "Compte"
msgid "Account and limit"
msgstr "Limitations du compte"
+msgid "Action to take when receiving an alert."
+msgstr "Action à réaliser lors de la réception d’une alerte."
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Actif"
@@ -434,10 +551,10 @@ msgid "Add"
msgstr "Ajouter"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Ajouter un CHANGELOG"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "Ajouter un CONTRIBUTING"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "Ajoutez des Webhooks de groupe avec GitLab Enterprise Edition."
@@ -449,43 +566,70 @@ msgid "Add Kubernetes cluster"
msgstr "Ajouter une grappe de serveurs Kubernetes"
msgid "Add README"
-msgstr ""
+msgstr "Ajouter un README"
+
+msgid "Add a bullet list"
+msgstr "Ajouter une liste à puces"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Ajoutez un commentaire général à %{noteable_name}."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "Ajoutez une page d’accueil à votre wiki contenant des informations sur votre projet. GitLab l’affichera ici à la place de ce message."
+msgid "Add a link"
+msgstr "Ajouter un lien"
+
+msgid "Add a numbered list"
+msgstr "Ajouter une liste numérotée"
+
msgid "Add a table"
msgstr "Ajouter un tableau"
+msgid "Add a task list"
+msgstr "Ajouter une liste de tâches"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "Ajouter un texte apparaissant dans toutes communications par courriel (%{character_limit} caractères maximum)"
+msgid "Add approver(s)"
+msgstr "Ajouter un ou des approbateurs"
+
+msgid "Add approvers"
+msgstr "Ajouter des approbateurs"
+
+msgid "Add bold text"
+msgstr "Ajouter du texte en gras"
+
msgid "Add comment now"
msgstr "Ajouter un commentaire"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "Ajouter un enâ€tête et un pied de page aux courriels. Veuillez noter que les paramètres de couleur seront appliqués uniquement à l’intérieur de l’interface de l’application"
+
msgid "Add image comment"
msgstr "Ajouter un commentaire à l’image"
+msgid "Add italic text"
+msgstr "Ajouter du texte en italique"
+
msgid "Add license"
msgstr "Ajouter une licence"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Ajouter une nouvelle application"
msgid "Add new directory"
msgstr "Ajouter un nouveau dossier"
-msgid "Add projects"
-msgstr "Ajouter des projets"
-
msgid "Add reaction"
msgstr "Ajouter une réaction"
msgid "Add to project"
-msgstr ""
+msgstr "Ajouter au projet"
msgid "Add to review"
msgstr "Ajouter à la revue de code"
@@ -497,23 +641,32 @@ msgid "Add user(s) to the group:"
msgstr "Ajouter un ou des utilisateurs au groupe :"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Ajouter des utilisateurs ou des groupes autorisés à approuver chaque demande de fusion"
msgid "Add users to group"
msgstr "Ajouter des utilisateurs au groupe"
+msgid "Added at"
+msgstr "Ajouté à"
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "L’ajout de nouvelles applications est désactivé dans votre instance GitLab. Veuillez contacter votre administrateur GitLab pour en obtenir la permission."
msgid "Additional text"
msgstr "Texte supplémentaire"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "Espace d’administration"
msgid "Admin Overview"
msgstr "Vue administrateur"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "Vous êtes sur le point de supprimer définitivement l’utilisateur %{username}. Les tickets, demandes de fusion et les groupes qui leur sont liés seront transférés à un « utilisateur fantôme » du système. Pour éviter toute perte de données, envisagez plutôt d’utiliser la fonctionnalité « %{strong_start}bloquer l’utilisateur%{strong_end} ». L’action « %{strong_start}supprimer l’utilisateur%{strong_end} » ne peut être annulée ni faire l’objet d’une restauration."
@@ -548,6 +701,9 @@ msgid "AdminProjects|Delete project"
msgstr "Supprimer le projet"
msgid "AdminSettings|Auto DevOps domain"
+msgstr "Domaine de DevOps automatique"
+
+msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
msgid "AdminSettings|Environment variables are protected by default"
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Autorisations avancées, stockage de fichiers volumineux et paramètres d’authentification à double facteur."
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Paramètres avancés"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "Après modification de votre mot de passe, vous serez redirigé vers l’écran de connexion."
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Alerte"
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr "Tous"
+msgid "All Members"
+msgstr "Tous les membres"
+
msgid "All changes are committed"
msgstr "Toutes les modifications sont validées"
@@ -657,15 +825,33 @@ msgstr "Toutes les fonctionnalités sont activées pour les projets vierges, à
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "Tous les utilisateurs"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr "Autoriser les commits des membres qui peuvent fusionner dans la branche cible."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr "Autoriser les projets de ce groupe à utiliser le stockage Git LFS"
@@ -678,6 +864,12 @@ msgstr "Permettre le rendu des diagrammes PlantUML dans les documents Asciidoc."
msgid "Allow requests to the local network from hooks and services."
msgstr "Autoriser les requêtes sur le réseau local à partir de hooks et de services."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Autoriser les utilisateurs à demander un accès"
@@ -696,6 +888,9 @@ msgstr "Également appelé « émetteur » ou « identifiant du tiers de conf
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "Aussi appelée « adresse URL du service du tiers de confiance » ou « URL de réponse »"
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "Vous pouvez également utiliser un %{personal_access_token_link}. Lorsque vous créerez votre jeton d’accès personnel, sélectionnez la portée <code>dépôt</code>, afin de permettre l’affichage d’une liste de vos dépôts publics et privés disponibles à la connexion."
@@ -717,9 +912,6 @@ msgstr "Un champ utilisateur Gitlab vide ajoutera le nom complet de l’utilisat
msgid "An error has occurred"
msgstr "Une erreur est survenue"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Une erreur est survenue lors de l’ajout d’un brouillon à la discussion."
@@ -729,6 +921,15 @@ msgstr "Une erreur est survenue lors de l’ajout d’un nouveau brouillon."
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr "Une erreur s’est produite lors de la prévisualisation du blob"
@@ -741,18 +942,27 @@ msgstr "Une erreur est survenue lors de la mise à jour du poids du ticket"
msgid "An error occurred while adding approver"
msgstr "Une erreur s’est produite lors de l’ajout de l’approba·teur·trice"
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "Une erreur est survenue lors de la suppression du commentaire"
msgid "An error occurred while detecting host keys"
msgstr "Une erreur est survenue lors de la détection des clefs de l’hôte"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Une erreur s’est produite lors de la révocation de l’alerte. Actualisez la page et essayez à nouveau."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "Une erreur s’est produite lors du rejet de la mise en avant de la fonctionnalité. Actualisez la page et essayez de la rejeter à nouveau."
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "Une erreur s’est produite lors de la prévisualisation markdown"
@@ -765,6 +975,9 @@ msgstr "Une erreur s’est produite lors de la récupération des données de la
msgid "An error occurred while fetching stages."
msgstr "Une erreur est survenue lors de la récupération des étapes."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "Une erreur est survenue pendant la récupération du journal de la tâche."
@@ -777,6 +990,9 @@ msgstr "Une erreur est survenue pendant la récupération des tâches."
msgid "An error occurred while fetching the pipeline."
msgstr "Une erreur est survenue pendant la récupération du pipeline."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "Une erreur s’est produite lors de la récupération des projets"
@@ -792,6 +1008,9 @@ msgstr "Une erreur est survenue lors du chargement des données du graphique"
msgid "An error occurred while loading commit signatures"
msgstr "Une erreur s’est produite lors du chargement des signatures du commit"
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "Une erreur s’est produite lors du chargement du diff"
@@ -834,12 +1053,18 @@ msgstr "Une erreur est survenue lors de l’enregistrement du statut d’outrepa
msgid "An error occurred while saving assignees"
msgstr "Une erreur s’est produite lors de l’enregistrement des destinataires"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "Une erreur est survenue lors de l’abonnement aux notifications."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Une erreur est survenue lors du désabonnement aux notifications."
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "Une erreur est survenue lors de la mise à jour du commentaire"
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr "Analyse"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Anonyme"
@@ -909,15 +1137,33 @@ msgstr "Tout"
msgid "Any Label"
msgstr "Toute étiquette"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Apparence"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Application"
msgid "Application ID"
msgstr "Identifiant de l’application"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Application : %{name}"
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr "avr."
msgid "April"
msgstr "avril"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "Projet archivé ! Le dépôt et les autres ressources du projet sont en lecture seule"
msgid "Archived projects"
msgstr "Projets archivés"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Êtesâ€vous sûr·e de vouloir supprimer ce pipeline programmé ?"
@@ -966,15 +1265,24 @@ msgstr "Êtesâ€vous vraiment prêt(e) à perdre les modifications non enregistr
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "Êtesâ€vous sûr de vouloir générer une nouvelle paire de clefs ? Vous devrez copier la nouvelle clef publique sur le serveur distant pour que la mise en miroir refonctionne."
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Voulezâ€vous vraiment supprimer %{group_name} ?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "Êtesâ€vous sûr(e) de vouloir supprimer %{name} des approbateurs"
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr "Êtesâ€vous sûr(e) de vouloir supprimer le groupe « %{name} »"
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr "Êtesâ€vous sûr(e) de vouloir supprimer cette identité ?"
msgid "Are you sure you want to reset registration token?"
msgstr "Êtesâ€vous sûr·e de vouloir réinitialiser le jeton d’inscription ?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Êtesâ€vous sûr·e de vouloir réinitialiser le jeton de bilan de santé ?"
@@ -1041,9 +1352,6 @@ msgstr "Tickets assignés"
msgid "Assigned Merge Requests"
msgstr "Demandes de fusion assignées"
-msgid "Assigned to :name"
-msgstr "Assigné·e à :name"
-
msgid "Assigned to me"
msgstr "Assigné à moi"
@@ -1059,6 +1367,9 @@ msgstr "Les listes d’assignation montrent tous les bogues assignés à l’uti
msgid "Assignee(s)"
msgstr "Assigné·e(s)"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr "Au moins une approbation d’un propriétaire de code est requise pour modifier les fichiers correspondant aux règles de propriété du code (CODEOWNER)."
+
msgid "Attach a file"
msgstr ""
@@ -1074,18 +1385,24 @@ msgstr "août"
msgid "August"
msgstr "août"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Journal d’authentification"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "Journal d’authentification"
msgid "Authentication method"
msgstr "Méthode d’authentification"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Auteur·e"
@@ -1104,6 +1421,9 @@ msgstr "Autoriser"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "Autoriser %{link_to_client} à utiliser votre compte ?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "Autorisé à"
@@ -1143,18 +1463,12 @@ msgstr "Apprenezâ€en davantage en consultant la %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Le pipeline Auto DevOps a été activé et sera utilisé si aucun autre fichier de configuration d’intégration continue n’est trouvé. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "Vous pouvez automatiquement générer et tester votre application si vous %{link_to_auto_devops_settings} pour ce projet. Vous pouvez aussi la déployer automatiquement, si vous %{link_to_add_kubernetes_cluster}."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "ajoutez une grappe de serveurs Kubernetes"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "activez Auto DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "Marqué automatiquement comme utilisateur interne par défaut"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "Automatically resolved"
msgstr ""
@@ -1278,6 +1592,12 @@ msgstr "Abandonner la revue de code ?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "Vous êtes sur le point d’abonner votre revue de code, ce qui entraînera la suppression de l’ensemble de vos commentaires en attente. Les commentaires supprimés %{strong_start}ne peuvent pas%{strong_end} être restaurés."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "Commencer avec le commit sélectionné"
@@ -1515,6 +1835,12 @@ msgstr "paramètres du projet"
msgid "Branches|protected"
msgstr "protégée"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Parcourir le dossier"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr "Métriques commerciales (personnalisées)"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "par"
@@ -1557,6 +1892,9 @@ msgstr "Paramètres CI / CD (intégration et livraison continues)"
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr "L’intégration continue fonctionnera avec les paramètres d’authentification ciâ€dessus."
@@ -1590,6 +1928,9 @@ msgstr "Déploiement continu en production par sorties incrémentielles programm
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "Pipeline Auto DevOps par défaut"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "Stratégie de déploiement"
@@ -1605,6 +1946,9 @@ msgstr "Le pipeline Auto DevOps sera exécuté si aucun autre fichier de config
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "instance activée"
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr "Impossible de trouver le dernier commit (HEAD) pour cette branche"
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,21 +1979,36 @@ msgstr "Annuler cette tâche"
msgid "Cannot be merged automatically"
msgstr "Ne peut être fusionnée automatiquement"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Impossible de modifier la grappe de serveurs gérée par Kubernetes"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Empreinte du certificat"
-
msgid "Change Weight"
msgstr "Modifier le poids"
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1671,6 +2033,9 @@ msgstr "Défaire"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Cela va créer un nouveau commit afin de défaire les modifications existantes."
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "Les modifications sont affichées comme si la révision <b>source</b> était fusionnée dans la révision<b>cible</b>."
@@ -1683,6 +2048,12 @@ msgstr "Statistiques"
msgid "Chat"
msgstr "Discussion"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "Veuillez vérifier la %{docs_link_start}documentation%{docs_link_end}."
@@ -1710,9 +2081,6 @@ msgstr "Sélectionnez <strong>Créer une archive</strong> et attendez que l’
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "Cliquez sur <strong>Suivant</strong> au bas de la page."
-msgid "Choose File ..."
-msgstr "Choisir le fichier…"
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Choisissez une branche ou une étiquette (par exemple %{master}) ou entrez un commit (par exemple %{sha}) pour voir ce qui a changé ou pour créer une demande de fusion."
@@ -1734,27 +2102,27 @@ msgstr "Choisissez n’importe quelle couleur."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "Choisissez entre <code>clone</code> ou <code>fetch</code> pour obtenir les dernières modifications du code de l’application"
-msgid "Choose file..."
-msgstr "Choisir le fichier…"
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "Choisissez le groupe de premier niveau pour vos importations dans le dépôt."
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "Choisissez les groupes que vous souhaitez synchroniser avec ce nœud secondaire."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Choisissez quels dépôts vous voulez connecter pour exécuter des pipelines d’intégration et de livraison continues (CI/CD)."
-msgid "Choose which repositories you want to import."
-msgstr "Choisissez les dépôts que vous voulez importer."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "Choisissez les fragments que vous souhaitez synchroniser avec ce nœud secondaire."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "annulé"
@@ -1779,6 +2147,9 @@ msgstr "réussi avec des avertissements"
msgid "CiStatusLabel|pending"
msgstr "en attente"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "ignoré"
@@ -1812,6 +2183,9 @@ msgstr "réussi"
msgid "CiStatusText|pending"
msgstr "en attente"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "ignoré"
@@ -1827,6 +2201,9 @@ msgstr "Valeur de la variable"
msgid "CiVariables|Remove variable row"
msgstr "Supprimer cette variable"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (tout environnement)"
@@ -1839,6 +2216,9 @@ msgstr "Créer un joker"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr "Nouvel environnement"
@@ -1848,12 +2228,18 @@ msgstr "Protégée"
msgid "CiVariable|Search environments"
msgstr "Chercher des environnements"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Changer l’état de protection"
msgid "CiVariable|Validation failed"
msgstr "La validation a échoué"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "est indisponible : %{reason}"
@@ -1911,6 +2297,9 @@ msgstr "Cloner le dépôt"
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr "Fermé(e)"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Tickets clos"
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} a été installé avec succès sur votre grappe de serveurs Kubernetes"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} Ceci ajoutera quelques ressources supplémentaires comme un équilibreur de charge, ce qui peut engager des frais supplémentaires selon l’hébergeur de votre grappe de serveurs Kubernetes. Si vous utilisez Google Kubernetes Engine, vous pouvez %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "URL de l’API"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Ajouter une grappe de serveurs Kubernetes"
@@ -1968,18 +2354,15 @@ msgstr "Ajouter une intégration à votre groupe rendra la grappe de serveurs di
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Options avancées concernant l’intégration de cette grappe de serveurs Kubernetes"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "Après avoir installé Ingress, vous devrez faire pointer votre entrée DNS générique (wildcard) vers l’adresse IP externe générée afin que votre application puisse s’afficher après son déploiement. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Une erreur est survenue lors de la tentative de récupération des zones du projet : %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Une erreur est survenue lors de la tentative de contact de l’API Google Cloud. Veuillez réessayer plus tard."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -2016,6 +2399,12 @@ msgstr "Choisissez les applications à installer sur votre grappe de serveurs Ku
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Choisissez lequel de vos environnements utilisera cette grappe de serveurs."
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,20 +2414,20 @@ msgstr "Copier l’URL de l’API"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Copier le certificat de l’AC"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Copier l’adresse IP Ingress dans le presseâ€papiers"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Copier le nom d’hôte Jupyter dans le presseâ€papiers"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Copier le nom de la grappe de serveurs Kubernetes"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Copier le jeton"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Créer une grappe de serveurs Kubernetes"
@@ -2085,8 +2474,8 @@ msgstr "Intégration GitLab"
msgid "ClusterIntegration|GitLab Runner"
msgstr "Exécuteur GitLab"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner se connecte au dépôt de ce projet et exécute les tâches d’intégration et livraison continues (CI/CD), en renvoyant les résultats et en déployant les applications en production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Projet Google Cloud Platform"
@@ -2112,14 +2501,14 @@ msgstr "Masquer"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Si vous configurez plusieurs grappes de serveurs et que vous utilisez Auto DevOps, %{help_link_start}veillez d’abord lire ceci%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Afin d’afficher son état de santé, nous devons mettre votre grappe de serveurs à disposition de Prometheus pour récupérer les données nécessaires."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Adresse IP Ingress"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress vous permet de router les requêtes vers des services en fonction de l’hôte ou du chemin de la requête, en centralisant un certain nombre de services vers un seul point d’entrée."
@@ -2127,15 +2516,18 @@ msgstr "Ingress vous permet de router les requêtes vers des services en fonctio
msgid "ClusterIntegration|Install"
msgstr "Installer"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Installer Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "Installé"
msgid "ClusterIntegration|Installing"
msgstr "En cours d’installation"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Intégrez l’automatisation de la grappe de serveurs Kubernetes"
@@ -2163,7 +2555,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Nom de domaine Knative :"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr "Grappe de serveurs Kubernetes"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Détails de la grappe de serveurs Kubernetes"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "État de santé de la grappe de serveurs Kubernetes"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "La grappe de serveurs Kubernetes est en cours de création sur Google Kubernetes Engine…"
@@ -2220,9 +2609,6 @@ msgstr "Gérer"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Gérez votre grappe de serveurs Kubernetes en visitant %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Plus d’informations"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Aucun type de machine ne correspond à votre recherche"
@@ -2235,27 +2621,24 @@ msgstr "Aucun projet ne correspond à votre recherche"
msgid "ClusterIntegration|No zones matched your search"
msgstr "Aucune zone ne correspond à votre recherche"
-msgid "ClusterIntegration|Note:"
-msgstr "Remarque :"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Nombre de nœuds"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Veuillez entrer les informations d’accès de votre grappe de serveurs Kubernetes. Si vous avez besoin d’aide, vous pouvez lire notre %{link_to_help_page} sur Kubernetes"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Veuillez vous assurer que votre compte Google répond aux exigences suivantes :"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "Faites pointer une entrée DNS générique (wildcard) sur cette adresse IP générée afin de pouvoir accéder à votre application après son déploiement."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "Grappe de serveurs du projet"
-msgid "ClusterIntegration|Project namespace"
-msgstr "Espace de noms du projet"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "Espace de noms du projet (facultatif, unique)"
@@ -2286,7 +2669,7 @@ msgstr "Vous pouvez remplacer ceci par un nom d’hôte personnalisé. Auquel ca
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "La demande de lancement de l’installation a échoué"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,8 +2705,11 @@ msgstr "Sélectionnez la zone"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Sélectionnez la zone afin de choisir le type de machine"
-msgid "ClusterIntegration|Service token"
-msgstr "Jeton de service"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Afficher"
@@ -2331,9 +2717,6 @@ msgstr "Afficher"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Un problème est survenu de notre côté."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Une erreur s’est produite lors de la création de votre grappe de serveurs Kubernetes sur Google Kubernetes Engine"
@@ -2343,8 +2726,8 @@ msgstr "Une erreur s’est produite lors de l’installation de %{title}"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "L’adresse IP est en cours d’affectation. Si cela dure trop longtemps, veuillez vérifier votre grappe de serveurs Kubernetes ou vos quotas sur Google Kubernetes Engine."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "Ce compte doit disposer des autorisations pour créer une grappe de serveurs Kubernetes dans le %{link_to_container_project} spécifié ciâ€dessous"
@@ -2352,11 +2735,17 @@ msgstr "Ce compte doit disposer des autorisations pour créer une grappe de serv
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "Cette option vous permettra d’installer des applications sur des grappes de serveurs avec contrôle d’accès basé sur le rôle (RBAC)."
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Activer/désactiver la grappe de serveurs Kubernetes"
-msgid "ClusterIntegration|Token"
-msgstr "Jeton"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2394,9 +2783,6 @@ msgstr "Zone"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "Accèder à Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "vérifiez le prix ici"
-
msgid "ClusterIntegration|documentation"
msgstr "documentation"
@@ -2406,6 +2792,9 @@ msgstr "page d’aide"
msgid "ClusterIntegration|meets the requirements"
msgstr "répond aux exigences"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "correctement configuré"
@@ -2415,15 +2804,27 @@ msgstr "s’inscrire"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr "Propriétaires du code"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Cohortes"
msgid "Collapse"
msgstr "Réduire"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "Masquer la barre latérale"
@@ -2519,6 +2920,9 @@ msgstr "Commit de"
msgid "Commit…"
msgstr "Commit…"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Comparer"
@@ -2588,6 +2992,12 @@ msgstr "Configurez l’intégration de %{link}."
msgid "Configure the way a user creates a new account."
msgstr "Configurez la manière dont une personne crée un nouveau compte."
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Connecter"
@@ -2603,12 +3013,24 @@ msgstr "Connectez vos dépôts externes afin que les pipelines d’intégration
msgid "Connecting..."
msgstr "Connexion en cours…"
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "Registre de conteneur"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Créé"
@@ -2672,6 +3094,9 @@ msgstr "Contribuer à GitLab"
msgid "Contribution"
msgstr "Contribution"
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr "Veuillez patienter, cette page va être automatiquement actualisée."
msgid "Control the display of third party offers."
msgstr "Contrôle l’affichage des offres tierces."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Contrôle la concurrence maximale du remplissage du stockage pour les gros fichiers (LFS) pour ce nœud secondaire"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "Contrôle la concurrence maximale des remplacements de dépôt pour ce nœud secondaire"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "Contrôle la concurrence maximale des opérations de vérification pour ce nœud Geo"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "Index ConvDev"
@@ -2723,6 +3139,9 @@ msgstr "Copier l’URL %{protocol} de clonage"
msgid "Copy ID to clipboard"
msgstr "Copier l’identifiant dans le presseâ€papiers"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "Copier l’URL SSH de clonage"
@@ -2747,9 +3166,6 @@ msgstr "Copier le condensat SHA du commit"
msgid "Copy file path to clipboard"
msgstr "Copier le chemin d’accès du fichier dans le presseâ€papiers"
-msgid "Copy incoming email address to clipboard"
-msgstr "Copier l’adresse de courriel de l’expéditeur dans le presse-papiers"
-
msgid "Copy link"
msgstr "Copier le lien"
@@ -2765,9 +3181,33 @@ msgstr "Copier dans le presseâ€papiers"
msgid "Copy token to clipboard"
msgstr "Copier le jeton dans le presseâ€papiers"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "Impossible de récupérer l’état du pipeline. Pour obtenir un dépannage pas à pas, veuillez vous référer à la %{linkStart}documentation%{linkEnd}."
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr "Créer"
@@ -2777,12 +3217,12 @@ msgstr "Créer un nouveau dossier"
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
msgid "Create a new branch"
msgstr "Créer une nouvelle branche"
-msgid "Create a new branch and merge request"
-msgstr "Créer une nouvelle branche et une nouvelle demande de fusion"
-
msgid "Create a new issue"
msgstr "Créer un nouveau ticket"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Créer un jeton d’accès personnel pour votre compte afin de récupérer ou pousser par %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr "Créer une branche"
@@ -2816,9 +3259,6 @@ msgstr "Créer un groupe"
msgid "Create group label"
msgstr "Créer une étiquette de groupe"
-msgid "Create issue"
-msgstr "Créer un ticket"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Créer des listes à partir d’étiquettes. Les tickets avec l’étiquette sélectionnée apparaissent dans cette liste."
@@ -2888,6 +3328,9 @@ msgstr "Syntaxe de la planification cron"
msgid "Current Branch"
msgstr "Branche actuelle"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "NÅ“ud actuel"
@@ -2927,9 +3370,6 @@ msgstr "Personnalisez la manière dont les adresses de courriel et les noms d’
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Personnalisez la configuration de votre pipeline et affichez son état et le rapport de couverture."
@@ -2972,6 +3412,9 @@ msgstr "Tous"
msgid "DashboardProjects|Personal"
msgstr "Personnels"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr "Refuser et se déconnecter"
msgid "Default Branch"
msgstr "Branche par défaut"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "Étiquette de classement par défaut"
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr "Par défaut : importer directement l’adresse de courriel ou le nom d’utilisateur provenant de Google Code"
@@ -3044,6 +3496,9 @@ msgstr "Supprimer L’extrait de code"
msgid "Delete comment"
msgstr "Supprimer le commentaire"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Supprimer la liste"
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr "Supprimé"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "Refuser"
@@ -3067,6 +3537,9 @@ msgstr[1] "Déploiements"
msgid "Deploy Keys"
msgstr "Clefs de déploiement"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} autres"
@@ -3205,12 +3678,45 @@ msgstr "Décroissant"
msgid "Description"
msgstr "Description"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "Les modèles de description permettent de définir des modèles spécifiques au contexte et propres à votre projet pour les champs de description des tickets et des demandes de fusion."
msgid "Description:"
msgstr "Description :"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "Détruire"
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr "Rejeter"
@@ -3328,20 +3837,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "Télécharger une archive TAR"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Télécharger une archive tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Télécharger une archive tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Télécharger une archive ZIP"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Télécharger"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Envoyer les correctifs par courriel"
@@ -3373,6 +3888,9 @@ msgstr "Éditer"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr "Modifier l’étiquette"
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr "Modifier les fichiers dans l’éditeur et valider les modifications ici"
@@ -3406,18 +3927,54 @@ msgstr "Modifier l’identité de %{user_name}"
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Intégration d’Elasticsearch. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "Courriel"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "Correctif par courriel"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "Courriels"
@@ -3433,6 +3990,9 @@ msgstr "Activer"
msgid "Enable Auto DevOps"
msgstr "Activer Auto DevOps"
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr "Activer la collecte de données Pseudonymizer"
@@ -3460,6 +4020,12 @@ msgstr "Activer pour ce projet"
msgid "Enable group Runners"
msgstr "Activer les exécuteurs de groupe"
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "Activer ou désactiver la collecte de données Pseudonymizer."
@@ -3469,9 +4035,6 @@ msgstr "Activer ou désactiver le contrôle de version et l’envoi des données
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "Activer reCAPTCHA ou Akismet et définir des limites d’adresse IP."
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr "Activer la collecte des données d’utilisation"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "Activez la collecte des données d’utilisation afin d’avoir une vue d’ensemble de la manière dont vous utilisez les fonctionnalités de GitLab."
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "activé"
-msgid "Ends at (UTC)"
-msgstr "Se termine à (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr "Se termine à (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr "Entrez la description de la demande de fusion"
msgid "Enter the merge request title"
msgstr "Entrez l’intitulé de la demande de fusion"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr "Une erreur s’est produite lors de la récupération des environnements
msgid "Environments|An error occurred while making the request."
msgstr "Une erreur s’est produite lors de la requête."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "Une erreur s’est produite lors de l’arrêt de l’environnement. Veuillez réessayer"
@@ -3592,15 +4167,33 @@ msgstr "Ouvrir l’environnement en cours"
msgid "Environments|Pod logs from"
msgstr "Journaux du pod depuis"
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr "Redéployer dans l’environnement"
msgid "Environments|Read more about environments"
msgstr "En savoir plus sur les environnements"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr "Restaurer l’environnement"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Tout afficher"
@@ -3613,6 +4206,18 @@ msgstr "Arrêter l’environnement"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Mis à jour"
@@ -3664,6 +4269,9 @@ msgstr "Rapport d’erreur et journalisation"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr "Erreur lors de la création de l’épopée"
@@ -3712,6 +4320,18 @@ msgstr "Erreur lors du chargement du modèle."
msgid "Error occurred when toggling the notification subscription"
msgstr "Une erreur s’est produite lors de l’activation ou de la désactivation de l’abonnement aux notifications"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr "Erreur lors de la mise à jour de l’état de la liste de tâches à fa
msgid "Error updating todo status."
msgstr "Erreur lors de la mise à jour du statut de tâche à faire."
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr "Erreur lors du chargement de la demande de fusion. Veuillez réessayer."
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr "Étendre"
msgid "Expand all"
msgstr "Tout étendre"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Étendre la barre latérale"
msgid "Expiration date"
msgstr "Date d’expiration"
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr "Autorisation de politique de classification externe"
@@ -3868,9 +4530,6 @@ msgstr "L’autorisation externe a refusé l’accès à ce projet"
msgid "External authorization request timeout"
msgstr "Expiration du délai d’attente de la demande d’autorisation externe"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "Étiquette de classification"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "Étiquette de classification"
@@ -3892,13 +4551,19 @@ msgstr "Échec du changement de propriétaire"
msgid "Failed to check related branches."
msgstr "Échec de la vérification des branches liées."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "Échec du déploiement sur"
msgid "Failed to load emoji list."
msgstr "Impossible de charger la liste des émojis."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr "Impossible de supprimer le miroir."
msgid "Failed to remove the pipeline schedule"
msgstr "Échec de la suppression du pipeline programmé"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Échec de la signature via authentification par carte à puce"
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr "Échec"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "Plus rapide parce qu’il réutilise l’espace de travail du projet (faire un clone en solution de secours s’il n’existe pas)"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "Indicateurs de fonctionnalités"
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "Description"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "Modifier %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "Modifier l’indicateur de fonctionnalité"
@@ -4024,8 +4707,8 @@ msgstr "Nouveau"
msgid "FeatureFlags|New Feature Flag"
msgstr "Nouvel indicateur de fonctionnalité"
-msgid "FeatureFlags|Save changes"
-msgstr "Enregistrer les modifications"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "État"
@@ -4051,6 +4734,18 @@ msgstr "févr."
msgid "February"
msgstr "février"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "Les champs de cette page sont désormais non modifiables, vous pouvez configurer"
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr "Filtrer…"
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Rechercher par chemin d’accès"
@@ -4131,6 +4823,9 @@ msgstr "Cherchez le fichier ZIP téléchargé et décompressezâ€le."
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Cherchez le fichier <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> fraîchement extrait."
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "Empreintes"
@@ -4224,14 +4919,20 @@ msgstr ""
msgid "Format"
msgstr "Format"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "Erreurs trouvées dans votre fichier .gitlab-ci.yml :"
msgid "Free Trial of GitLab.com Gold"
msgstr "Essai gratuit de GitLab.com Gold"
-msgid "From %{provider_title}"
-msgstr "De %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "Depuis Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr "À partir des jalons :"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "À partir de l’affichage des détails de la grappe de serveurs Kubernetes, installez un exécuteur à partir de la liste des applications"
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "Clefs GPG"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "Général"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr "Pipelines généraux"
@@ -4275,6 +4985,9 @@ msgstr "Générer un jeu d’étiquettes par défaut"
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr "Geo"
@@ -4299,9 +5012,6 @@ msgstr "Données périmées à partir de %{timeago}"
msgid "GeoNodes|Data replication lag"
msgstr "Latence de la réplication des données"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "La désactivation d’un nÅ“ud arrête le processus de synchronisation. Êtesâ€vous sûr(e) ?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "Ne correspond pas à la configuration du stockage principal"
@@ -4320,6 +5030,9 @@ msgstr "La version de GitLab ne correspond pas à la version du nœud principal"
msgid "GeoNodes|Health status"
msgstr "État de santé"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "Dernier identifiant d’événement traité par le curseur"
@@ -4365,6 +5078,9 @@ msgstr "Non vérifié par somme de contrôle"
msgid "GeoNodes|Out of sync"
msgstr "Désynchronisé"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr "Tous les projets ont été planifiés pour resynchronisation"
msgid "Geo|Batch operations"
msgstr "Opérations par lot"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "Impossible de supprimer l’entrée de suivi d’un projet existant."
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "En échec"
@@ -4491,6 +5222,9 @@ msgstr "Groupes à synchroniser"
msgid "Geo|In sync"
msgstr "Synchronisé"
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr "En attente de synchronisation"
msgid "Geo|Pending verification"
msgstr "En attente de vérification"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "Le projet (identifiant : %{project_id}) n’existe plus sur le primaire. Vous pouvez supprimer cette entrée en toute sécurité, car cela ne supprimera aucune donnée sur le disque."
@@ -4560,24 +5300,45 @@ msgstr "Nombre de tentatives"
msgid "Geo|Select groups to replicate."
msgstr "Sélectionner les groupes à répliquer."
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "Fragments à synchroniser"
msgid "Geo|Status"
msgstr "Statut"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Synchronisé"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "Synchro en échec — %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "L’entrée de suivi du projet (%{project_id}) a été supprimée avec succès."
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "L’entrée de suivi va être supprimée. Êtesâ€vous sur(e) ?"
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "État inconnu"
@@ -4596,9 +5357,6 @@ msgstr "Vous êtes sur un nœud Geo secondaire <b>en lecture seule</b>. Si vou
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "Vous êtes sur un nœud Geo secondaire <b>en lecture seule</b>. Nous ne pourrez effectuer que des modifications ou des actions limitées depuis cette page."
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "Il faut une licence différente pour utiliser la réplication géographique"
-
msgid "Geo|misconfigured"
msgstr "mal configuré"
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr "Importation de GitHub"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter a été déplacé"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr "Utilisateur GitLab"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "Exportation de projet GitLab"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "URL d’authentification unique GitLab"
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr "Retour"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "Retour"
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr "Aller vers"
msgid "Go to %{link_to_google_takeout}."
msgstr "Consultez le site de %{link_to_google_takeout}."
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr "Importation depuis Google Code"
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr "Graphique"
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "Groupe"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "Paramètres du groupe CI/CD"
@@ -4734,6 +5522,9 @@ msgstr "Statut du stockage LFS Git du groupe :"
msgid "Group ID"
msgstr "Identifiant du groupe"
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "Exécuteurs de groupe"
@@ -4767,6 +5558,9 @@ msgstr "Nom du groupe"
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr "Désolé, aucune épopée ne correspond à votre recherche"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "La feuille de route affiche la progression de vos épopées dans le temps"
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "Jusqu’au %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "Badges numériques"
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr "Personnalisez vos badges numériques de groupe."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "En savoir plus sur les badges numériques."
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Empêcher le partage d’un projet du groupe %{group} avec d’autres groupes"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "Ce paramètre s’applique au groupe %{ancestor_group} et a été forcé pour ce sousâ€groupe."
@@ -4908,6 +5783,9 @@ msgstr "Rechercher par nom"
msgid "Have your users email"
msgstr "Récupérer les adresses de courriel des utilisateurs"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "Message d’enâ€tête"
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "Masquer la saisie manuelle des clefs d’hôtes"
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr "Masquer la charge utile"
@@ -4958,12 +5839,27 @@ msgstr[1] "Masquer les valeurs"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "Historique"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Maintenance démarrée avec succès"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr "Identifiant"
msgid "Identities"
msgstr "Identités"
-msgid "Identity provider single sign on URL"
-msgstr "URL d’authentification unique du fournisseur d’identité"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "Si cette option est désactivée, une branche locale divergente ne sera pas automatiquement mise à jour avec les commits de son homologue distant, afin d’éviter toute perte de données locales. Si la branche par défaut (%{default_branch}) a divergé et ne peut pas être mise à jour, la mise en miroir échouera. Les autres branches divergentes sont ignorées silencieusement."
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "Si désactivé, le niveau d’accès dépendra des autorisations de l’utilisateur ou l’utilisatrice pour ce projet."
@@ -5042,9 +5941,6 @@ msgstr "Si activé, l’accès aux projets sera validé sur un service externe e
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "Si vous utilisez GitHub, vous verrez les statuts des pipelines sur GitHub pour vos commits et demandes d’intégration (pull requests). %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "Si vous avez déjà des fichiers, vous pouvez les pousser à l’aide de la %{link_to_cli} ciâ€dessous."
-
msgid "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>."
msgstr "Si votre dépôt HTTP n’est pas accessible publiquement, ajoutez des informations d’authentification à l’URL : <code>https://utilisateur:mot_de_passe@gitlab.company.com/group/project.git</code>."
@@ -5129,12 +6025,30 @@ msgstr "Importer un dépôt"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "Connecter des dépôts provenant de"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "Améliorez le tableau des tickets avec Gitlab Entreprise Edition."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "Améliorez la gestion des tickets grâce à la pondération disponible dans la version Entreprise Edition de GitLab."
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "À la prochaine étape, vous pourrez sélectionner les projets que vous souhaitez importer."
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "Inclure un accord sur les conditions générales d’utilisation et la politique de confidentialité que tous les utilisateurs doivent accepter."
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "Si besoin, ajouter le nom d’utilisateur dans l’URL : <code>https: //username@gitlab.company.com/group/project.git</code>."
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr "Projet incompatible"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "Indique si l’exécuteur peut choisir des tâches sans étiquettes (tags)"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "En ligne"
@@ -5183,9 +6109,18 @@ msgstr "Entrer les clefs d’hôte manuellement"
msgid "Input your repository URL"
msgstr "Entrez l’URL de votre dépôt"
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr "Installer GitLab Runner"
@@ -5206,6 +6141,9 @@ msgstr "Visibilité des statistiques de l’instance"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "L’instance ne prend pas en charge plusieurs grappes de serveurs Kubernetes"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "Intégrations"
@@ -5236,6 +6174,21 @@ msgstr "Introduction à l’analyseur de cycle"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Ticket"
@@ -5266,6 +6222,9 @@ msgstr "Mode d’affichage du tableau des tickets"
msgid "Issue events"
msgstr "Événements du ticket"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "Tableau"
@@ -5296,8 +6255,11 @@ msgstr "Les tickets peuvent être des bogues, des tâches ou des sujets de discu
msgid "Issues closed"
msgstr "Tickets clos"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "Tickets, demandes de fusion, poussées Git et commentaires."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr "Une fois que vous aurez commencé à créer des tickets d’incident en relation avec vos projets, nous pourrons commencer à en effectuer le suivi et afficher des statistiques les concernant"
@@ -5341,12 +6303,30 @@ msgstr "janvier"
msgid "Job"
msgstr "Tâche"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "La tâche a été supprimée"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr "La tâche a été supprimée par"
msgid "Job|Keep"
msgstr "Garder"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "Faire défiler vers le bas"
@@ -5392,9 +6375,21 @@ msgstr "Les artefacts ont été supprimés"
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "juill."
@@ -5437,9 +6432,15 @@ msgstr "Kubernetes configuré"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "L’intégration du service Kubernetes est obsolète. %{deprecated_message_content} vos grappes de serveurs Kubernetes en utilisant la nouvelle page <a href=\"%{url}\"/>Clusters Kubernetes</a>"
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Désactivé"
@@ -5455,6 +6456,15 @@ msgstr "Menu déroulant des actions sur les étiquettes"
msgid "Label lists show all issues with the selected label."
msgstr "Les listes étiquetées affichent tous les tickets ayant l’étiquette sélectionnée."
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} de plus"
@@ -5485,6 +6495,9 @@ msgstr "Promouvoir l’étiquette"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "La promotion de l’étiquette « %{labelTitle} » va la rendre disponible pour tous les projets du groupe %{groupName}. Les étiquettes de projet ayant le même intitulé seront fusionnées. Cette action est irréversible."
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr "Stockage de fichiers volumineux"
@@ -5556,18 +6569,18 @@ msgstr "En savoir plus sur Kubernetes"
msgid "Learn more about Web Terminal"
msgstr ""
+msgid "Learn more about approvals."
+msgstr ""
+
msgid "Learn more about custom project templates"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about signing commits"
msgstr ""
-msgid "Learn more about protected branches"
-msgstr "En savoir plus sur les branches protégées"
-
msgid "Learn more in the"
msgstr "Apprenezâ€en plus dans la"
@@ -5577,6 +6590,9 @@ msgstr "documentation sur la programmation de pipelines"
msgid "Leave"
msgstr "Quitter"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Quitter le groupe"
@@ -5661,9 +6677,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "Vous êtes sur le point de supprimer la licence, %{name}, de ce projet."
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "Licences"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Affichage limité à %d événement maximum"
@@ -5681,6 +6703,9 @@ msgstr "Lister vos dépôts Gitea"
msgid "List available repositories"
msgstr "Lister les dépôts disponibles"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "Vue en liste"
@@ -5738,15 +6763,36 @@ msgstr "Les verrous permettent de verrouiller un fichier ou un dossier spécifiq
msgid "Login with smartcard"
msgstr "Se connecter avec une carte à puce"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Journaux"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Rendez chaque membre de votre équipe plus productif, quel que soit l’endroit où il se situe. GitLab Geo crée des miroirs en lecture seule de votre instance GitLab afin que vous puissiez réduire le temps nécessaire pour cloner et récupérer de gros dépôts."
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "Assurezâ€vous d’être connecté avec le compte propriétaire des projets que vous souhaitez importer."
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr "Gérez les dépôts Git avec des contrôles d’accès très précis permettant de sécuriser votre code. Effectuez des revues de code et renforcez la collaboration avec les demandes de fusion Git. Chaque projet peut également avoir un système de tickets de suivi et un wiki."
@@ -5777,15 +6823,15 @@ msgstr "Gérer les étiquettes de projet"
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "Gérez les membres de votre groupe tout en ajoutant un niveau de sécurité supplémentaire avec SAML."
-
msgid "Manifest"
msgstr "Manifeste"
msgid "Manifest file import"
msgstr "Importation de fichier manifeste"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Associer un identifiant de compte FogBugz à un utilisateur GitLab"
@@ -5810,47 +6856,44 @@ msgstr "Marquer comme fait"
msgid "Markdown"
msgstr ""
+msgid "Markdown Help"
+msgstr ""
+
msgid "Markdown enabled"
msgstr "Markdown activé"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "Ajouter une liste à puces"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "Ajouter un lien"
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "Ajouter une liste numérotée"
+msgid "Maven Metadata"
+msgstr "Métadonnées Maven"
-msgid "MarkdownToolbar|Add a table"
-msgstr "Ajouter un tableau"
+msgid "Max access level"
+msgstr "Niveau d’accès maximum"
-msgid "MarkdownToolbar|Add a task list"
-msgstr "Ajouter une liste de tâches"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr "Ajouter du texte en gras"
+msgid "Maximum artifacts size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Add italic text"
-msgstr "Ajouter du texte en italique"
+msgid "Maximum attachment size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Go full screen"
-msgstr "Afficher en plein écran"
+msgid "Maximum capacity"
+msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "Insérer une citation"
+msgid "Maximum delay (Minutes)"
+msgstr ""
-msgid "MarkdownToolbar|Insert code"
-msgstr "Insérer du code"
+msgid "Maximum job timeout"
+msgstr "Durée maximale d’exécution de la tâche"
-msgid "Maven Metadata"
-msgstr "Métadonnées Maven"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
-msgid "Max access level"
-msgstr "Niveau d’accès maximum"
+msgid "Maximum push size (MB)"
+msgstr ""
-msgid "Maximum job timeout"
-msgstr "Durée maximale d’exécution de la tâche"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "mai"
@@ -5873,21 +6916,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "Les membres seront redirigés à cet emplacement lors de la connexion à votre groupe. Obtenezâ€le auprès de votre fournisseur d’identités, sous la dénomination « emplacement du service SSO », « point de terminaison d’émission de jetons SAML » ou « URL SAML 2.0 / W-Federation »."
-
msgid "Merge Request"
msgstr "Demande de fusion"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Demandes de fusion"
msgid "Merge Requests created"
msgstr "Demandes de fusion créées"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "Événements de fusion"
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Demande de fusion"
msgid "Merge request approvals"
msgstr "Approbations de la demande de fusion"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Demandes de fusion"
@@ -5972,18 +7030,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart}a changé la description %{descriptionChangedTimes} fois %{timeDifferenceMinutes}%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr "Filtrer les fichiers"
msgid "MergeRequest|No files found"
msgstr "Aucun fichier trouvé"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "Fusionnée"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "Messages"
@@ -6041,14 +7102,17 @@ msgstr "Nouvelle métrique"
msgid "Metrics|No deployed environments"
msgstr "Aucun environnement déployé"
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr "Documentation des requêtes Prometheus"
-msgid "Metrics|System"
-msgstr "Système"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "Une erreur s’est produite lors de la récupération des données d’environnement. Veuillez réessayer."
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "p. ex., débit"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Jalon"
@@ -6134,6 +7204,9 @@ msgstr "Promouvoir %{milestone} le rendra disponible pour tous les projets du gr
msgid "Milestones|This action cannot be reversed."
msgstr "Cette action ne peut pas être annulée."
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "Créer un miroir de dépôt"
@@ -6152,6 +7225,9 @@ msgstr "Dépôts mis en miroir"
msgid "Mirroring repositories"
msgstr "Dépôts miroir"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "ajouter une clef SSH"
@@ -6161,6 +7237,9 @@ msgstr "Annuler"
msgid "Modal|Close"
msgstr "Fermer"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr "Plus d’informations"
msgid "More information is available|here"
msgstr "ici"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Les plus étoilés"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "Déplacer"
@@ -6206,6 +7291,12 @@ msgstr "Déplacer le ticket"
msgid "Multiple issue boards"
msgstr "Tableaux de tickets multiples"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Nom"
@@ -6218,6 +7309,12 @@ msgstr "Nommez votre clef personnelle avec un titre"
msgid "Name:"
msgstr "Nom :"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "Nommage, visibilité"
@@ -6271,21 +7368,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Nouvelle planification de pipeline"
msgid "New Snippet"
msgstr "Nouvel extrait de code"
-msgid "New Snippets"
-msgstr "Nouveaux extraits de code"
-
msgid "New branch"
msgstr "Nouvelle branche"
msgid "New branch unavailable"
msgstr "Nouvelle branche indisponible"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Nouveau dossier"
@@ -6301,6 +7401,9 @@ msgstr "Nouveau fichier"
msgid "New group"
msgstr "Nouveau groupe"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "Nouvelle identité"
@@ -6322,6 +7425,9 @@ msgstr "De nouveaux pipelines annuleront les anciens pipelines en attente sur la
msgid "New project"
msgstr "Nouveau projet"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Nouvelle planification"
@@ -6334,20 +7440,29 @@ msgstr "Nouveau sousâ€groupe"
msgid "New tag"
msgstr "Nouvelle étiquette"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "Nouveau…"
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "Non"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "Aucune étiquette"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "Aucune personne assignée"
+msgid "No activities found"
+msgstr ""
msgid "No branches found"
msgstr "Aucune branche trouvée"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr "Aucune date d’échéance"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr "Aucun fichier trouvé."
msgid "No issues for the selected time period."
msgstr "Aucun ticket pour la période sélectionnée."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "Aucune étiquette avec un tel nom ou une telle description"
msgid "No license. All rights reserved"
msgstr "Aucune licence. Tous droits réservés"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr "Aucune demande de fusion pour la période sélectionnée."
@@ -6451,9 +7575,6 @@ msgstr "NÅ“uds"
msgid "None"
msgstr "Aucun·e"
-msgid "Not allowed to merge"
-msgstr "Non autorisé·e à fusionner"
-
msgid "Not available"
msgstr "Indisponible"
@@ -6469,11 +7590,17 @@ msgstr "Pas confidentiel·le"
msgid "Not enough data"
msgstr "Données insuffisantes"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "Pas maintenant"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Notez que la branche principale « master » est automatiquement protégée. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6586,6 +7713,9 @@ msgstr "novembre"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "oct."
@@ -6598,11 +7728,20 @@ msgstr "Filtre"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "Une fois importés, les dépôts peuvent être mis en miroir via SSH. Voir %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Un élément de plus"
msgstr[1] "%d éléments de plus"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Un ou plusieurs de vos projets Bitbucket ne peuvent être importés directement dans GitLab parce qu’ils utilisent Subversion ou Mercurial comme gestionnaire de versions au lieu de Git."
@@ -6612,6 +7751,12 @@ msgstr "Un ou plusieurs de vos projets Google Code ne peuvent être importés d
msgid "Only admins"
msgstr "Seulement les administrateurs"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "Ne mettre en miroir que les branches protégées"
@@ -6648,6 +7793,9 @@ msgstr "Ouvrir dans Xcode"
msgid "Open projects"
msgstr "Ouvrir des projets"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "Ouvrir la barre latérale"
@@ -6675,15 +7823,30 @@ msgstr "Opérations"
msgid "Operations Dashboard"
msgstr "Tableau de bord des opérations"
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Ajouter un projet au tableau de bord"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr "Le tableau de bord des opérations fournit un résumé de l’état de santé opérationnel de chaque projet, comprenant les états des pipelines et des alertes."
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr "Vous pouvez éventuellement %{link_to_customize} la manière dont les adresses de courriel et les noms d’utilisateur issus de FogBugz sont importés dans GitLab."
@@ -6702,9 +7865,6 @@ msgstr "Autres étiquettes"
msgid "Other information"
msgstr "Autres informations"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "Sinon, il est recommandé de commencer avec l’une des options ciâ€dessous."
-
msgid "Outbound requests"
msgstr "Requêtes sortantes"
@@ -6726,6 +7886,12 @@ msgstr "Le paquet a été supprimé"
msgid "Packages"
msgstr "Paquets"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Pages"
@@ -6750,18 +7916,21 @@ msgstr "⇤ Première"
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr "Partie des modifications de la demande de fusion"
msgid "Password"
msgstr "Mot de Passe"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "Collez votre clef SSH publique, qui est habituellement située dans le fichier « ~/.ssh/id_rsa.pub » et commence par « ssh-rsa ». N’utilisez pas votre clef SSH privée !"
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "Chemin d’accès, transfert et suppression"
@@ -6780,6 +7952,9 @@ msgstr "Chemin d’accès :"
msgid "Pause"
msgstr "Pause"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "Les exécuteurs en pause n’acceptent pas de nouvelles tâches"
@@ -6798,6 +7973,9 @@ msgstr "Optimisation des performances"
msgid "Permissions"
msgstr "Droits d’accès"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "Autorisations, LFS, 2FA"
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Pipeline"
-msgid "Pipeline Health"
-msgstr "État de santé du pipeline"
-
msgid "Pipeline Schedule"
msgstr "Planification de pipeline"
msgid "Pipeline Schedules"
msgstr "Planifications de pipelines"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "Quota du pipeline"
msgid "Pipeline triggers"
msgstr "Déclencheurs de pipeline"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Échecs :"
@@ -6876,6 +8057,12 @@ msgstr "Variables"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personnalisé"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Pipelines"
@@ -6891,6 +8078,12 @@ msgstr "Pipelines de la semaine dernière"
msgid "Pipelines for last year"
msgstr "Pipelines de l’année dernière"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "Construire en toute confiance"
@@ -6933,11 +8126,8 @@ msgstr "Ce projet n’est actuellement pas configuré pour exécuter des pipelin
msgid "Pipeline|Commit"
msgstr "Commit"
-msgid "Pipeline|Create for"
-msgstr "Créer pour"
-
-msgid "Pipeline|Create pipeline"
-msgstr "Créer un pipeline"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "Durée"
@@ -6951,6 +8141,9 @@ msgstr "Pipeline"
msgid "Pipeline|Run Pipeline"
msgstr "Exécuter le pipeline"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "Chercher des branches"
@@ -6978,9 +8171,21 @@ msgstr "Vous êtes sur le point d’arrêter le pipeline %{pipelineId}."
msgid "Pipeline|all"
msgstr "tous"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "réussi"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "avec l’étape"
@@ -7005,15 +8210,33 @@ msgstr "Veuillez accepter les conditions générales d’utilisation avant de co
msgid "Please choose a group URL with no special characters."
msgstr "Veuillez choisir une URL de groupe sans caractères spéciaux."
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Veuillez les convertir en %{link_to_git} et repasser par %{link_to_import_flow}."
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "Veuillez les convertir en dépôts Git sur Google Code et repasser par %{link_to_import_flow}."
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr "Veuillez saisir un nom descriptif pour votre groupe."
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "Veuillez noter que cette application n’est pas fournie par GitLab, vous devriez vérifier son authenticité avant d’autoriser son accès."
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "Veuillez sélectionner au moins un filtre pour voir les résultats"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "Veuillez résoudre le reCAPTCHA"
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "Veuillez utiliser ce formulaire pour signaler à GitLab les utilisateurs qui créent des tickets ou des commentaires indésirables, ou qui se comportent de manière inappropriée."
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "Veuillez patienter pendant la connexion à votre dépôt. Actualisez à votre guise."
@@ -7047,18 +8285,33 @@ msgstr "Veuillez patienter pendant l’importation de votre dépôt. Actualisez
msgid "Preferences"
msgstr "Préférences"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "Thème de navigation"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr "Appuyez sur Entrée ou cliquez pour rechercher"
msgid "Prevent adding new members to project membership within this group"
msgstr "Empêcher l’ajout de nouveaux membres au projet au sein de ce groupe"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Aperçu"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "Aperçu de la charge utile"
@@ -7086,6 +8339,9 @@ msgstr "Privé — l’accès au projet doit être autorisé explicitement pour
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privé — le groupe ainsi que ses projets ne sont accessibles qu’à ses membres."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Des projets privés peuvent être créés dans votre espace de noms personnel avec :"
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "La taille de fichier maximale autorisée est de 200 Kio."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "Ceci ne ressemble pas à une clef SSH publique, êtesâ€vous sûr(e) de vouloir l’ajouter ?"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "Cette adresse de courriel sera utilisée pour les opérations Web, telles que les modifications et les fusions. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "Cet émoji et ce message apparaîtront sur votre profil et partout dans l’interface."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr "Progression"
msgid "Project"
msgstr "Projet"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Le projet « %{project_name} » est en cours de suppression."
@@ -7365,17 +8630,26 @@ msgstr "Mise à jour du projet « %{project_name} » effectuée."
msgid "Project Badges"
msgstr "Badges numériques du projet"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "URL du projet"
msgid "Project access must be granted explicitly to each user."
msgstr "L’accès au projet doit être explicitement accordé à chaque utilisateur."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Avatar du projet"
-msgid "Project avatar in repository: %{link}"
-msgstr "Avatar du projet dans le dépôt : %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "Détails du projet"
@@ -7383,6 +8657,9 @@ msgstr "Détails du projet"
msgid "Project export could not be deleted."
msgstr "L’exportation du projet n’a pas pu être supprimée."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "L’exportation du projet a été supprimée."
@@ -7392,6 +8669,9 @@ msgstr "Le lien de l’exportation du projet a expiré. Merci de générer une n
msgid "Project export started. A download link will be sent by email."
msgstr "L’exportation du projet a débuté. Un lien de téléchargement sera envoyé par courriel."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr "Nom du projet"
msgid "Project slug"
msgstr "Identifiant « slug » du projet"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr "Les utilisateurs et utilisatrices ne peuvent uniquement pousser sur ce d
msgid "Projects"
msgstr "Projets"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "Projets partagés avec %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "Les projets appartenant à un groupe sont préfixés avec l’espace de noms du groupe. Les projets existants peuvent être déplacés dans un groupe."
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "Les plus consultés"
@@ -7545,6 +8840,9 @@ msgstr "Aucune alerte définie"
msgid "PrometheusAlerts|Operator"
msgstr "Opérateur"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "Seuil"
@@ -7647,6 +8945,9 @@ msgstr "Cette fonctionnalité est verrouillée."
msgid "Promotions|Upgrade plan"
msgstr "Mise à niveau du forfait"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "Protégé"
@@ -7713,6 +9014,12 @@ msgstr "Public — le groupe ainsi que n’importe quel projet public est access
msgid "Public - The project can be accessed without any authentication."
msgstr "Public - le projet est accessible sans aucune authentification."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "Pipelines publics"
@@ -7725,6 +9032,12 @@ msgstr "Push"
msgid "Push Rules"
msgstr "Règles de poussage Git"
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "Événements de poussée"
@@ -7755,6 +9068,9 @@ msgstr "Les actions rapides peuvent être utilisées dans la description des tic
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Lire plus"
@@ -7770,6 +9086,12 @@ msgstr "Fonctionnalités en temps réel"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr "Recherches récentes"
@@ -7805,6 +9127,9 @@ msgstr "Identifiezâ€vous et accédez à vos exécuteurs pour ce groupe."
msgid "Register and see your runners for this project."
msgstr "Enregistrer et afficher vos exécuteurs pour ce projet."
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "Registre"
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "Supprimer l’avatar"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "Supprimer le groupe"
@@ -7862,12 +9196,33 @@ msgstr "Supprimer la priorité"
msgid "Remove project"
msgstr "Supprimer le projet"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "Un groupe supprimé ne peut être restauré !"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "Supprimer le groupe entraînera la suppression de tous les projets et ressources affiliés."
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "Renommer"
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr "Réparer l’authentification"
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr "Durée d’exécution"
msgid "Reports|Failure"
msgstr "Échec"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Sévérité"
@@ -7937,6 +9307,12 @@ msgstr "Les résultats de la synthèse des tests sont en cours d’analyse"
msgid "Reports|Vulnerability"
msgstr "Vulnérabilité"
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "aucun résultat de test modifié"
@@ -7949,6 +9325,9 @@ msgstr "Paramètres du dépôt"
msgid "Repository URL"
msgstr "URL du dépôt"
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr "Exiger de tous les utilisateurs de ce groupe la configuration de l’aut
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Exiger que tous les utilisateurs acceptent les conditions générales d’utilisation et la politique de confidentialité quand ils accèdent à GitLab."
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr "Résoudre la discussion"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr "Métriques de réponse (AWS ELB)"
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr "Reprendre"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Réessayer"
msgid "Retry this job"
msgstr "Relancer cette tâche"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "Relancer la vérification"
@@ -8083,12 +9490,21 @@ msgstr "Examen (demande de fusion !%{mergeRequestId})"
msgid "Revoke"
msgstr "Révoquer"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "Feuille de route"
msgid "Run CI/CD pipelines for external repositories"
msgstr "Exécuter des pipelines CI / CD pour les dépôts externes"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr "L’exécuteur exécute des tâches de projets attribués"
msgid "Runner token"
msgstr "Jeton de l’exécuteur"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "L’exécuteur ne recevra aucune nouvelle tâche"
@@ -8143,27 +9565,27 @@ msgstr "Vous avez utilisé tout le temps de pipeline partagé de vos exécuteurs
msgid "Running"
msgstr "En cours d’exécution"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "Authentification unique SAML"
msgid "SAML SSO for %{group_name}"
msgstr "Authentification unique SAML pour %{group_name}"
-msgid "SAML Single Sign On"
-msgstr "Authentification unique SAML"
-
-msgid "SAML Single Sign On Settings"
-msgstr "Paramètres d’authentification unique SAML"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "Empreinte SHA-1 du certificat de signature de jetons SAML. Obtenezâ€la auprès de votre fournisseur d’identité, parfois sous la dénomination « Thumbprint »."
-
msgid "SSH Keys"
msgstr "Clefs SSH"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "Clefs SSH de l’hôte"
@@ -8173,6 +9595,9 @@ msgstr "Clef SSH publique"
msgid "SSL Verification"
msgstr "Vérification SSL"
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "Enregistrer"
@@ -8197,6 +9622,9 @@ msgstr "Sauvegarder la planification du pipeline"
msgid "Save variables"
msgstr "Enregistrer les variables"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Planifier un nouveau pipeline"
@@ -8206,15 +9634,24 @@ msgstr "Planifié"
msgid "Schedules"
msgstr "Planifications"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Planification des pipelines"
msgid "Scope"
msgstr "Portée"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "Tableaux de tickets à portée limitée"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "Faites défiler jusqu’à <strong>Hébergement de projet Google Code</strong> et activez le commutateur sur la droite."
@@ -8266,6 +9703,9 @@ msgstr "Rechercher des projets"
msgid "Search users"
msgstr "Rechercher des utilisateurs et utilisatrices"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr "Rechercher dans vos projets"
@@ -8293,6 +9733,15 @@ msgstr "Dans ce groupe"
msgid "SearchAutocomplete|in this project"
msgstr "Dans ce projet"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "Secret"
@@ -8314,9 +9763,6 @@ msgstr "Erreur lors de la récupération de la liste des vulnérabilités. Veuil
msgid "Security Dashboard|Issue Created"
msgstr "Ticket créé"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "Créer un ticket"
@@ -8332,12 +9778,12 @@ msgstr "Plus d’informations"
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
-msgstr "Documentation du tableau de bord de sécurité"
-
msgid "Security Reports|There was an error creating the issue."
msgstr "Une erreur est survenue lors de la création du ticket."
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "Une erreur est survenue lors du rejet de la vulnérabilité."
@@ -8386,6 +9832,15 @@ msgstr "Sélectionnez un groupe à inviter"
msgid "Select a namespace to fork the project"
msgstr "Sélectionnez un espace de noms afin de créer une divergence du projet"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr "Sélectionner un modèle de dépôt"
@@ -8395,9 +9850,6 @@ msgstr "Sélectionnez un fuseau horaire"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Sélectionnez une grappe de serveurs Kubernetes existante ou créezâ€en une nouvelle"
-msgid "Select assignee"
-msgstr "Sélectionner une personne assignée"
-
msgid "Select branch/tag"
msgstr "Sélectionner une branche ou une étiquette"
@@ -8428,11 +9880,14 @@ msgstr "Sélectionnez la branche que vous souhaitez définir comme branche par d
msgid "Select the custom project template source group."
msgstr "Sélectionnez le groupe source de modèles de projet personnalisés."
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "La sélection d’un utilisateur de GitLab va ajouter un lien vers cet utilisateur dans les descriptions des tickets et des commentaires (p. ex., « Par <a href=\"#\">@johnsmith</a> »). Les tickets et commentaires seront également associés ou assignés à cet utilisateur."
-msgid "Selective synchronization"
-msgstr "Synchronisation sélective"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "Envoyer un courriel"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr "sept."
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "septembre"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "Version du serveur"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr "Modèles de service"
msgid "Service URL"
msgstr "URL du service"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "Expiration de la session, restrictions des projets et taille des pièces jointes."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Définissez un mot de passe pour votre compte afin de pouvoir récupérer ou pousser vos modification via %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr "Définir un dépôt de modèles au niveau de l’instance"
msgid "Set max session time for web terminal."
msgstr "Définissez le temps maximal de la session pour le terminal Web."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "Définissez un courriel de notification pour les rapports d’abus."
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "Définissez les exigences pour la connexion d’un utilisateur. Activez l’authentification obligatoire à deux facteurs."
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "Configuration CI/CD"
@@ -8548,6 +10045,9 @@ msgstr "Configure les assertions, attributs et revendications (courriel, prénom
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "Configurez votre projet afin de pouvoir pousser et/ou récupérer automatiquement les modifications vers ou depuis un autre dépôt. Les branches, les étiquetets et les commits seront automatiquement synchronisés."
@@ -8605,9 +10105,15 @@ msgstr "Réinitialiser les minutes du pipeline utilisées"
msgid "Sherlock Transactions"
msgstr "Transactions Sherlock"
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "Afficher la commande"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "Afficher le journal brut complet"
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr "Afficher la dernière version"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Afficher les pages parentes"
@@ -8676,6 +10185,12 @@ msgstr "Restrictions de connexion"
msgid "Sign-up restrictions"
msgstr "Restrictions d’inscription"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr "Taille"
msgid "Size and domain settings for static websites"
msgstr "Paramètres de taille et de domaine pour les sites Web statiques"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Application Slack"
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr "Extraits de code"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr "Une erreur est survenue lors de la récupération des commentaires. Veui
msgid "Something went wrong while fetching group member contributions"
msgstr "Une erreur s’est produite lors de la récupération des contributions des membres du groupe"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Une erreur est survenue lors de la récupération des environnements pour cette demande de fusion. Veuillez réessayer."
@@ -8778,6 +10320,9 @@ msgstr "Une erreur est survenue, impossible d’obtenir des projets d’opérati
msgid "Something went wrong, unable to remove project"
msgstr "Une erreur est survenue, impossible de supprimer le projet"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Quelque chose s’est mal passé. Veuillez réessayer."
@@ -8928,6 +10473,9 @@ msgstr "Journaux des messages indésirables"
msgid "Spam and Anti-bot Protection"
msgstr "Protection antiâ€pourriel et antiâ€robot"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Exécuteurs spécifiques"
@@ -8976,15 +10524,27 @@ msgstr "Activité des projets favoris"
msgid "Starred projects"
msgstr "Projets favoris"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Créer une %{new_merge_request} avec ces changements"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Démarrer une revue de code"
@@ -9018,6 +10578,9 @@ msgstr "Démarré"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Changer de branche ou d’étiquette"
msgid "Sync information"
msgstr "Synchroniser les informations"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "« Hooks » système"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Informations système"
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "Métriques du système (personnalisées)"
@@ -9321,12 +10917,23 @@ msgstr "Tester l’authentification SAML"
msgid "Test coverage parsing"
msgstr "Analyse de la couverture des tests"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "Merci ! Ne plus afficher ce message"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "La recherche globale avancée de Gitlab est un outil puissant qui vous fait gagner du temps. Au lieu de perdre du temps à recréer du code existant, vous pouvez maintenant faire des recherches dans le code d’autres équipes afin de vous aider sur votre projet."
@@ -9345,6 +10952,12 @@ msgstr "Le système de suivi est un endroit où l’on peut ouvrir un ticket pou
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Le certificat X.509 à utiliser lorsque l’authentification TLS mutuelle est requise pour communiquer avec le service d’autorisation externe. Si ce champ est vide, le certificat du serveur est tout de même validé lors de l’accès via HTTPS."
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "Le surligneur de caractères vous aide à garder la longueur de l’objet à %{titleLength} caractères maximum et à faire des renvois à la ligne pour limiter les lignes du corps du message à %{bodyLength} caractères, afin de les rendre lisibles sous Git."
@@ -9357,15 +10970,63 @@ msgstr "L’ensemble d’événements ajoutés aux données recueillies pour cet
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "La connexion expirera après %{timeout}. Pour les dépôts qui nécessitent plus de temps, utilisez une combinaison de clone et push."
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "Le déploiement de cette tâche sur %{environmentLink} a échoué."
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "La relation de divergence a été supprimée."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "L’importation expirera après %{timeout}. Pour les dépôts qui prennent plus de temps, utilisez une combinaison de clone et push."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "Le présentoir des tickets affiche le temps nécessaire entre la création d’un ticket et son assignation à un jalon ou son ajout à une liste dans votre tableau de tickets. Commencez par créer des tickets pour voir des données sur ce présentoir."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "La taille maximale autorisée pour un fichier est de 200 Kio."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "La phrase de passe permettant de déchiffrer la clef privée. Ceci est facultatif et la valeur est chiffrée au repos."
@@ -9402,9 +11075,18 @@ msgstr "Votre projet est accessible à tous les utilisateur et utilisatrices aut
msgid "The project can be accessed without any authentication."
msgstr "Votre projet est accessible sans aucune authentification."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "La collecte de données du pseudonymiseur est désactivée. Lorsque cette option est activée, GitLab exécute un travail en tâche de fond produisant des fichiers CSV pseudonymisés à partir de la base de données GitLab qui seront téléchargés dans le répertoire de stockage d’objets que vous avez configuré."
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Le dépôt de ce projet n’existe pas."
@@ -9423,6 +11105,21 @@ msgstr "L’étape d’évaluation montre le temps entre la création de la dema
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "La feuille de route affiche la progression de vos épopées dans le temps"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "L’étape de pré-production indique le temps entre l’acceptation d’une demande fusion et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées lorsque vous aurez fait votre première mise en production."
@@ -9438,9 +11135,18 @@ msgstr "Le temps pris par chaque entrée récoltée durant cette étape."
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "L’action de mise à jour expirera au bout de %{number_of_minutes} minutes. Pour les gros dépôts, utilisez une combinaison de clone et push."
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "La collecte des données d’utilisation est désactivée et ne peut pas être configurée par le biais de ce formulaire."
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "La carte des utilisateurs (<code>user map</code>) est un document JSON qui met en correspondance les utilisateurs de Google Code qui ont participé à vos projets en précisant la manière dont leurs adresses de courriel et leurs noms d’utilisateur sont importés dans GitLab. Vous pouvez y apporter des modifications en changeant la valeur à droite du « <code>:</code> ». Assurezâ€vous de conserver les guillemets droits doubles (<code>\"</code>), les autres signes de ponctuation, ainsi que l’adresse de courriel ou le nom d’utilisateur à gauche du deuxâ€points."
@@ -9495,9 +11201,21 @@ msgstr "Une erreur est survenue lors de l’ajout d’une tâche à accomplir (t
msgid "There was an error deleting the todo."
msgstr "Une erreur est survenue lors de la suppression de la tâche à accomplir (todo)."
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "Une erreur s’est produite lors du chargement du calendrier d’activité des utilisateurs."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Une erreur s’est produite lors de l’enregistrement de vos paramètres de notification."
@@ -9513,6 +11231,9 @@ msgstr "Une erreur s’est produite lors de l’abonnement à cette étiquette."
msgid "There was an error when unsubscribing from this label."
msgstr "Une erreur s’est produite lors de la désinscription à cette étiquette."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Cette instance de GitLab ne fournit aucun exécuteur partagé pour le moment. Les administra·teur·trice·s de l’instance peuvent enregistrer des exécuteurs partagés dans la zone d’administration."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "Cette application a été créée par %{link_to_owner}."
@@ -9543,6 +11267,21 @@ msgstr "La portée de ce tableau est réduite"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Cette branche a changé depuis que vous y avez apporté des modifications. Souhaitezâ€vous créer une nouvelle branche ?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr "Ce registre de conteneur a été programmé pour suppression."
@@ -9564,6 +11303,9 @@ msgstr "Ce répertoire"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "Ce groupe"
@@ -9579,6 +11321,9 @@ msgstr "Il s’agit d’une tâche différée devant être exécutée dans %{rem
msgid "This is the author's first Merge Request to this project."
msgstr "C’est la première demande de fusion de cet auteur pour ce projet."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Ce ticket est confidentiel"
@@ -9627,6 +11372,12 @@ msgstr "Cette tâche va effectuer un déploiement sur %{environmentLink}."
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Cette tâche est en attente d’être choisie par un exécuteur"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,19 +11399,25 @@ msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n’a
msgid "This merge request is locked."
msgstr "Cette demande de fusion est verrouillée."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "Cette option est désactivée car vous n’avez pas les droits d’écriture sur la branche actuelle"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "Cette option est désactivée tant que vous avez des changements non indexés"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "Cette page n’est pas disponible car vous n’êtes pas autorisé à lire des informations à travers de multiples projets."
msgid "This page will be removed in a future release."
msgstr "Cette page sera supprimée dans une version ultérieure."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr "Ce projet n’a pas de facturation activée. Afin de créer une grappe d
msgid "This repository"
msgstr "Ce dépôt"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "Cet exécuteur ne fonctionnera que sur les pipelines déclenchés sur des branches protégées"
@@ -9693,12 +11453,12 @@ msgstr "Ce paramètre peut être outrepassé pour chacun des projets."
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "Ce paramètre modifie le nom d’hôte qui est utilisé pour générer des courriels de commit privés. %{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "Ce diff n’a pas pu être affiché car il est trop grand."
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "Cet utilisateur ou cette utilisatrice n’a aucune identité"
@@ -9711,6 +11471,9 @@ msgstr "Cet utilisateur sera l’auteur de tous les événements du flux d’act
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "Ces courriels deviennent automatiquement des tickets (dont les commentaires résultent de la conversation par courriel) répertoriés ici."
@@ -9777,9 +11540,6 @@ msgstr "il y a %s mois"
msgid "Timeago|%s months remaining"
msgstr "Il reste %s mois"
-msgid "Timeago|%s seconds ago"
-msgstr "il y a %s secondes"
-
msgid "Timeago|%s seconds remaining"
msgstr "Il reste %s secondes"
@@ -10009,6 +11769,9 @@ msgstr "Aujourd’hui"
msgid "Todo"
msgstr "Tâche"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "À faire"
@@ -10048,6 +11811,12 @@ msgstr "Demain"
msgid "Too many changes to show."
msgstr "Trop de changements à afficher."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Total des contributions"
@@ -10072,6 +11841,9 @@ msgstr "Suivre les groupes de tickets qui partagent un thème, entre différents
msgid "Track time with quick actions"
msgstr "Suivre le temps estimé/passé avec les actions rapides"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "Vue arborescente"
@@ -10084,6 +11856,9 @@ msgstr "Déclencher des pipelines pour les mises à jour de miroirs"
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "Déclenche les pipelines lorsque les branches ou les étiquettes sont mises à jour sur le dépôt en amont. Selon l’activité du dépôt en amont, cela peut considérablement augmenter la charge de vos exécuteurs d’intégration continue. N’activez ceci que si vous savez qu’ils peuvent tenir la charge."
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "Déclencher cette action manuelle"
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "Les déclencheurs peuvent forcer la reconstruction pour une branche ou une étiquette spécifique via l’appel à une API. Ces jetons emprunteront l’identité de l’utilisateur auquel ils sont associés, ainsi que ses accès et permissions sur les projets."
@@ -10117,6 +11904,9 @@ msgstr "Activer le service d’assistance"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "Authentification à double facteur"
@@ -10126,15 +11916,27 @@ msgstr "Type"
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Impossible de charger le diff. %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Impossible de vous connecter au groupe via SAML en raison de « %{reason} »"
msgid "Unable to update this epic at this time."
msgstr "Impossible de mettre à jour cette épopée pour le moment."
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr "Inconnu"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "Déverrouiller"
@@ -10207,21 +12012,39 @@ msgstr "À venir"
msgid "Update"
msgstr "Mettre à jour"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "Mettre à jour maintenant"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Modifiez le nom du groupe, sa description, son avatar et sa visibilité."
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "Mise à jour en cours"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Mettez à niveau votre forfait pour activer la recherche globale avancée."
@@ -10237,6 +12060,9 @@ msgstr "Mettez à niveau votre forfait pour activer la pondération des tickets.
msgid "Upgrade your plan to improve Issue boards."
msgstr "Mettez à niveau votre forfait pour améliorer les tableaux de tickets."
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "Téléversez le fichier <code>GoogleCodeProjectHosting.json</code> ici :"
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "Cliquez pour envoyer"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "Votes positifs"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "L’envoi des données d’utilisation est désactivé"
msgid "Usage statistics"
msgstr "Statistiques d’utilisation"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "Utiliser <code>%{native_redirect_uri}</code> pour les tests locaux"
@@ -10303,18 +12141,54 @@ msgstr "Utilisé par les membres pour se connecter à votre groupe dans GitLab"
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "Les cohortes d’utilisateurs ne sont affichées que lorsque la %{usage_ping_link_start}collecte des données d’utilisation%{usage_ping_link_end} est activée."
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "Paramètres de l’utilisateur"
msgid "User and IP Rate Limits"
msgstr "Limites de l’utilisateur et du débit IP"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "Correspondance entre utilisateurs"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "Activité"
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr "Utilisateurs et utilisatrices"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "Divers paramètres de registre de conteneur."
@@ -10429,9 +12324,6 @@ msgstr "Vérifié"
msgid "Version"
msgstr "Version"
-msgid "View %{alerts}"
-msgstr "Afficher les %{alerts}"
-
msgid "View app"
msgstr "Voir l’application"
@@ -10441,8 +12333,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "Voir la documentation"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "Afficher la liste des épopées"
@@ -10453,12 +12345,15 @@ msgstr "Voir le fichier @ "
msgid "View group labels"
msgstr "Afficher les labels de groupe"
-msgid "View issue"
-msgstr "Afficher le ticket"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "Voir sur GitLab"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "Afficher les tâches"
@@ -10492,6 +12387,9 @@ msgstr "Niveau de visibilité"
msgid "Visibility level:"
msgstr "Niveau de visibilité :"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "Visibilité :"
@@ -10528,12 +12426,18 @@ msgstr "Fichier"
msgid "Vulnerability|Identifiers"
msgstr "Identifiants"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "Instances"
msgid "Vulnerability|Links"
msgstr "Liens"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "Projet"
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr "Terminal Web"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Les webhooks vous permettent de déclencher une URL si, par exemple, du nouveau code est poussé ou un nouveau ticket est créé. Vous pouvez configurer les webhooks pour écouter les événements spécifiques comme des poussées de code, des tickets ou des demandes de fusion. Les webhooks de groupes s’appliqueront à tous les projets dans un groupe, ce qui vous permet de normaliser la fonctionnalité du webhook dans votre groupe entier."
@@ -10600,9 +12510,17 @@ msgstr "Lorsqu’un exécuteur est verrouillé, il ne peut pas être affecté à
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "Lorsque cette option est activée, les utilisateurs et utilisarices ne pourront pas utiliser GitLab tant que les conditions générales d’utilisation ne seront pas acceptées."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "Lorsque vous laissez l’URL vide, des étiquettes de classification peuvent toujours être spécifiées sans désactiver les fonctionnalités interâ€projets ni effectuer de vérifications d’autorisation externes."
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr "Qui sera en mesure de voir ce groupe ?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Clonez votre wiki"
@@ -10726,6 +12647,9 @@ msgstr "Créer une Page"
msgid "Wiki|Create page"
msgstr "Créer la page"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Modifier cette page"
@@ -10744,6 +12668,9 @@ msgstr "Version de la page"
msgid "Wiki|Pages"
msgstr "Pages"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Pages du Wiki"
@@ -10756,12 +12683,24 @@ msgstr "Grâce à l’analyse des contributions, vous pouvez avoir une vue d’e
msgid "Withdraw Access Request"
msgstr "Retirer la demande d’accès"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "Oui"
@@ -10792,6 +12731,9 @@ msgstr "Vous êtes sur le point de supprimer la relation de divergence avec le p
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "Vous allez transférer %{project_full_name} à un nouveau ou une nouvelle propriétaire. Êtesâ€vous VRAIMENT sûr(e) ?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "Vous êtes sur une instance GitLab en lecture seule."
@@ -10807,6 +12749,15 @@ msgstr "Vous pouvez également créer un projet en ligne de commande."
msgid "You can also star a label to make it a priority label."
msgstr "Vous pouvez marquer un label comme important pour en faire un label prioritaire."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Vous pouvez facilement y contribuer en demandant à rejoindre ces groupes."
@@ -10816,6 +12767,12 @@ msgstr "Vous pouvez facilement installer un Exécuteur sur un cluster Kubernetes
msgid "You can move around the graph by using the arrow keys."
msgstr "Vous pouvez vous déplacer dans le graphique en utilisant les touches fléchées."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
@@ -10825,6 +12782,9 @@ msgstr "Vous ne pouvez modifier des fichiers que dans une branche"
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr "Vous pouvez résoudre le conflit de fusion Git soit en mode interactif, en cliquant sur les boutons « %{use_ours} » ou « %{use_theirs} », soit en modifiant directement les fichiers. Valider ces modifications dans la branche « %{branch_name} »"
@@ -10834,12 +12794,30 @@ msgstr "Vous pouvez configurer des tâches pour n’utiliser des exécuteurs quâ
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "Vous pouvez tester votre fichier « .gitlab-ci.yml » avec %{linkStart}CI Lint%{linkEnd}."
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "Vous ne pouvez pas écrire sur une instance GitLab Geo secondaire en lecture seule. Veuillez utiliser le %{link_to_primary_node} à la place."
msgid "You cannot write to this read-only GitLab instance."
msgstr "Vous ne pouvez pas écrire sur cette instance GitLab en lecture seule."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "Vous n’avez souscrit à aucun abonnement pour le moment"
@@ -10858,12 +12836,27 @@ msgstr "Vous ne disposez d’aucune application autorisée"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "Vous n’avez pas les autorisations"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Vous avez atteint votre limite de projet"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr "Seul un responsable peut forcer la suppression d’un verrou"
msgid "You need a different license to enable FileLocks feature"
msgstr "Vous avez besoin d’une licence différente pour activer la fonctionnalité de verrouillage de fichiers FileLocks"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "Vous avez besoin d’une autorisation."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "Vous ne recevrez aucune notification par courriel"
@@ -10921,6 +12926,15 @@ msgstr "Vous ne pourrez pas récupérer ou pousser de code via SSH tant que vous
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Vous devrez utiliser différents noms de branches pour obtenir une comparaison valide."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "Vous recevez ce courriel parce que %{reason}."
@@ -10954,6 +12968,15 @@ msgstr "Vos tâches à faire"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "Vos applications (%{size})"
@@ -10972,9 +12995,15 @@ msgstr "Vos modifications ont été validées. Commit %{commitId} %{commitStats}
msgid "Your changes have been saved"
msgstr "Vos modifications ont été enregistrées"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Votre commentaire ne sera pas visible publiquement."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Votre nom"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "Vos projets"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "un utilisateur supprimé"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "auparavant "
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr "entre autres choses"
-msgid "assign yourself"
-msgstr "assignez vous"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "nom de la branche"
@@ -11038,16 +13085,38 @@ msgstr "%{namespace} est affecté par « %{vulnerability} »."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} restant(s)"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} a détecté %{fixedCount} vulnérabilité corrigée"
msgstr[1] "%{reportType} %{status} a détecté %{fixedCount} vulnérabilités corrigées"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} a détecté %{newCount} nouvelle vulnérabilité"
msgstr[1] "%{reportType} %{status} a détecté %{newCount} nouvelles vulnérabilités"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} a détecté de nouvelles vulnérabilités (%{newCount}) et des vulnérabilités corrigées (%{fixedCount})"
@@ -11079,6 +13148,9 @@ msgstr "%{reportType} est en cours de chargement"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType} : le chargement a généré une erreur"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(erreurs lors du chargement des résultats)"
@@ -11088,6 +13160,15 @@ msgstr "(en cours de chargement)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(en cours de chargement, erreurs lors du chargement des résultats)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "Classe"
@@ -11097,12 +13178,24 @@ msgstr "Qualité du code"
msgid "ciReport|Confidence"
msgstr "Niveau de confiance"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "Analyse du conteneur"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "L’analyse des conteneurs permet la détection de vulnérabilités connues dans vos images Docker."
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11145,9 +13238,18 @@ msgstr "Corrigé :"
msgid "ciReport|Identifiers"
msgstr "Identifiants"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "Instances"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "En savoir plus sur l’interaction avec les rapports de sécurité (alpha)."
@@ -11191,9 +13293,6 @@ msgstr "Aucun changement dans les indicateurs de performance"
msgid "ciReport|Performance metrics"
msgstr "Indicateurs de performance"
-msgid "ciReport|Revert dismissal"
-msgstr "Annuler le rejet"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11215,6 +13314,9 @@ msgstr "Le test de sécurité d’applications statique (Static Application Secu
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "Une erreur s’est produite lors de la création du ticket. Veuillez réessayer."
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "Une erreur s’est produite lors du rejet de la vulnérabilité. Veuillez réessayer."
@@ -11233,9 +13335,18 @@ msgstr "Une erreur s’est produite lors du chargement du rapport d’analyse de
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "Une erreur s’est produite lors de l’annulation du rejet. Veuillez réessayer."
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "Mise à niveau de %{name} de %{version} à %{fixed}."
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "Utilisé par %{packagesString}"
@@ -11247,12 +13358,33 @@ msgstr "Voir le rapport complet"
msgid "ciReport|on pipeline"
msgstr "dans le pipeline"
-msgid "command line instructions"
-msgstr "instructions en ligne de commande"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr "Vous êtes sur le point de désactiver la confidentialité. Cela signifie que <strong>tout le monde</strong> sera en mesure de voir et de laisser un commentaire sur ce ticket."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr "jeton de déploiement"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "désactivé"
@@ -11310,6 +13445,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} mettra à jour la durée estimée avec la dernière commande."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "pour ce projet"
@@ -11319,15 +13469,9 @@ msgstr "de"
msgid "group"
msgstr ""
-msgid "help"
-msgstr "aide"
-
msgid "here"
msgstr "ici"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://votre-serveur-bitbucket"
@@ -11390,6 +13534,9 @@ msgstr "dernier déploiement"
msgid "latest version"
msgstr "dernière version"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "gestion des licences"
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr "Une erreur est survenue lors de l’envoi de votre approbation."
msgid "mrWidget|Approve"
msgstr "Approuver"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Approuvée par"
@@ -11518,17 +13671,20 @@ msgstr "Fusionner localement"
msgid "mrWidget|Merge request approved"
msgstr "Demande de fusion approuvée"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Demande de fusion approuvée ; vous pouvez ajouter votre approbation"
msgid "mrWidget|Merged by"
msgstr "Fusionnée par"
-msgid "mrWidget|No Approval required"
-msgstr "Aucune approbation exigée"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "Aucune approbation exigée ; vous pouvez tout de même approuver"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "Ouvrir dans l’EDI Web"
@@ -11579,6 +13735,9 @@ msgstr "Défaire"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "Défaire cette demande de fusion dans une nouvelle demande de fusion"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "Marqué par"
@@ -11691,12 +13850,20 @@ msgstr "mot de passe"
msgid "personal access token"
msgstr "jeton d’accès personnel"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr "la clef privée ne correspond pas au certificat."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "projet"
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] "réponse"
msgstr[1] "réponses"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr "démarré"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bd45b01d463..2845daf51e3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16,7 +16,13 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-msgid " Status"
+msgid " Please sign in."
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
msgstr ""
msgid " or "
@@ -35,6 +41,11 @@ msgstr[1] ""
msgid "%d commits"
msgstr ""
+msgid "%d contribution"
+msgid_plural "%d contributions"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
@@ -70,6 +81,11 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d more comment"
+msgid_plural "%d more comments"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d staged change"
msgid_plural "%d staged changes"
msgstr[0] ""
@@ -91,7 +107,7 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
-msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
+msgid "%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS"
msgstr ""
msgid "%{count} more"
@@ -105,12 +121,21 @@ msgid_plural "%{count} participants"
msgstr[0] ""
msgstr[1] ""
+msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
+msgstr ""
+
+msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
+msgstr ""
+
msgid "%{filePath} deleted"
msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr ""
+
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -120,6 +145,18 @@ msgstr ""
msgid "%{label_for_message} unavailable"
msgstr ""
+msgid "%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites."
+msgstr ""
+
+msgid "%{level_name} is not allowed in a %{group_level_name} group."
+msgstr ""
+
+msgid "%{level_name} is not allowed since the fork source project has lower visibility."
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -129,6 +166,15 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name} contained %{resultsString}"
+msgstr ""
+
+msgid "%{name} found %{resultsString}"
+msgstr ""
+
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
@@ -138,6 +184,27 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
msgstr[0] ""
@@ -176,6 +243,15 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "%{verb} %{time_spent_value} spent time."
+msgstr ""
+
+msgid "'%{level}' is not a valid visibility level"
+msgstr ""
+
+msgid "'%{source}' is not a import source"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -197,6 +273,9 @@ msgstr ""
msgid "- show less"
msgstr ""
+msgid "0 for unlimited"
+msgstr ""
+
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
msgstr[0] ""
@@ -208,27 +287,30 @@ msgstr[0] ""
msgstr[1] ""
msgid "1 closed issue"
-msgid_plural "%d closed issues"
+msgid_plural "%{issues} closed issues"
msgstr[0] ""
msgstr[1] ""
msgid "1 closed merge request"
-msgid_plural "%d closed merge requests"
+msgid_plural "%{merge_requests} closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 merged merge request"
-msgid_plural "%d merged merge requests"
+msgid_plural "%{merge_requests} merged merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 open issue"
-msgid_plural "%d open issues"
+msgid_plural "%{issues} open issues"
msgstr[0] ""
msgstr[1] ""
msgid "1 open merge request"
-msgid_plural "%d open merge requests"
+msgid_plural "%{merge_requests} open merge requests"
msgstr[0] ""
msgstr[1] ""
@@ -237,15 +319,42 @@ msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
+msgid "1-9 contributions"
+msgstr ""
+
+msgid "10-19 contributions"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
+msgid "20-29 contributions"
+msgstr ""
+
msgid "2FA"
msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "2FADevice|Registered On"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "30+ contributions"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -261,6 +370,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -273,6 +385,9 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<no name set>"
+msgstr ""
+
msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
msgstr ""
@@ -300,7 +415,7 @@ msgstr ""
msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -309,24 +424,42 @@ msgstr ""
msgid "A deleted user"
msgstr ""
-msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
+msgstr ""
+
+msgid "A member of the abuse team will review your report as soon as possible."
msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
+msgid "API Token"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -351,18 +484,30 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
msgid "Account"
msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Account: %{account}"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -384,33 +529,78 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a GPG key"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add a todo"
+msgstr ""
+
+msgid "Add an SSH key"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add label(s)"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr ""
+msgid "Add or subtract spent time"
+msgstr ""
+
msgid "Add reaction"
msgstr ""
msgid "Add to project"
msgstr ""
+msgid "Add to review"
+msgstr ""
+
msgid "Add todo"
msgstr ""
@@ -420,15 +610,33 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "AddMember|No users specified."
+msgstr ""
+
+msgid "AddMember|Too many users specified (limit is %{user_limit})"
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
+msgid "Adds"
+msgstr ""
+
+msgid "Adds a todo."
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -465,6 +673,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -543,6 +754,9 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
@@ -552,6 +766,9 @@ msgstr ""
msgid "After a successful password update you will be redirected to login screen."
msgstr ""
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
msgid "All"
msgstr ""
@@ -564,12 +781,30 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All projects"
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -582,6 +817,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -594,13 +835,13 @@ msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
-msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgid "Alternate support URL for help page"
msgstr ""
-msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
-msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
@@ -612,10 +853,10 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
+msgid "An error occurred creating the new branch."
msgstr ""
-msgid "An error occurred creating the new branch."
+msgid "An error occurred fetching the dropdown data."
msgstr ""
msgid "An error occurred previewing the blob"
@@ -624,6 +865,12 @@ msgstr ""
msgid "An error occurred when toggling the notification subscription"
msgstr ""
+msgid "An error occurred when trying to resolve a comment. Please try again."
+msgstr ""
+
+msgid "An error occurred when trying to resolve a discussion. Please try again."
+msgstr ""
+
msgid "An error occurred while detecting host keys"
msgstr ""
@@ -633,13 +880,25 @@ msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
+msgid "An error occurred while fetching label colors."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
+msgid "An error occurred while fetching projects autocomplete."
+msgstr ""
+
msgid "An error occurred while fetching sidebar data"
msgstr ""
-msgid "An error occurred while fetching stages."
+msgid "An error occurred while fetching the board lists. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching the builds."
msgstr ""
msgid "An error occurred while fetching the job log."
@@ -654,6 +913,12 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while fetching this tab."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -675,6 +940,12 @@ msgstr ""
msgid "An error occurred while making the request."
msgstr ""
+msgid "An error occurred while moving the issue."
+msgstr ""
+
+msgid "An error occurred while parsing recent searches"
+msgstr ""
+
msgid "An error occurred while rendering KaTeX"
msgstr ""
@@ -702,6 +973,9 @@ msgstr ""
msgid "An error occurred whilst fetching the latest pipeline."
msgstr ""
+msgid "An error occurred whilst getting files for - %{branchId}"
+msgstr ""
+
msgid "An error occurred whilst loading all the files."
msgstr ""
@@ -735,15 +1009,51 @@ msgstr ""
msgid "Any"
msgstr ""
+msgid "Any Label"
+msgstr ""
+
+msgid "Any Milestone"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Any namespace"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Append the comment with %{TABLEFLIP}"
+msgstr ""
+
+msgid "Append the comment with %{shrug}"
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application uninstalled but failed to destroy: %{error_message}"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -756,21 +1066,60 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Applying command"
+msgstr ""
+
+msgid "Applying command to %{commandDescription}"
+msgstr ""
+
+msgid "Applying multiple commands"
+msgstr ""
+
+msgid "Applying suggestion"
+msgstr ""
+
msgid "Apr"
msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Are you sure you want to cancel editing this comment?"
+msgstr ""
+
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
+msgid "Are you sure you want to delete this list?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -801,6 +1150,9 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
msgid "Are you sure you want to stop this environment?"
msgstr ""
@@ -810,15 +1162,42 @@ msgstr ""
msgid "Are you sure?"
msgstr ""
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
msgid "Artifacts"
msgstr ""
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
+msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
+msgstr ""
+
+msgid "AsanaService|Asana - Teamwork without email"
+msgstr ""
+
+msgid "AsanaService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "AsanaService|User Personal Access Token. User must have access to task, all comments will be attributed to this user."
+msgstr ""
+
msgid "Ask your group maintainer to set up a group Runner."
msgstr ""
msgid "Assets"
msgstr ""
+msgid "Assign"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -834,13 +1213,16 @@ msgstr ""
msgid "Assign to"
msgstr ""
-msgid "Assigned Issues"
+msgid "Assign yourself to these issues"
msgstr ""
-msgid "Assigned Merge Requests"
+msgid "Assign yourself to this issue"
msgstr ""
-msgid "Assigned to :name"
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
msgstr ""
msgid "Assigned to me"
@@ -858,16 +1240,24 @@ msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
+msgid "Attaching a file"
+msgid_plural "Attaching %d files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Attaching the file failed."
+msgstr ""
+
msgid "Aug"
msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -876,6 +1266,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -891,6 +1287,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -930,13 +1329,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatic certificate management using Let's Encrypt"
msgstr ""
msgid "Automatically marked as default internal user"
@@ -945,6 +1341,9 @@ msgstr ""
msgid "Automatically resolved"
msgstr ""
+msgid "Autosave|Note"
+msgstr ""
+
msgid "Available"
msgstr ""
@@ -1056,6 +1455,33 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
+msgid "Balsamiq file could not be loaded."
+msgstr ""
+
+msgid "BambooService|A continuous integration and build server"
+msgstr ""
+
+msgid "BambooService|A user with API access, if applicable"
+msgstr ""
+
+msgid "BambooService|Atlassian Bamboo CI"
+msgstr ""
+
+msgid "BambooService|Bamboo build plan key like KEY"
+msgstr ""
+
+msgid "BambooService|Bamboo root URL like https://bamboo.example.com"
+msgstr ""
+
+msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1098,6 +1524,9 @@ msgstr ""
msgid "Branch name"
msgstr ""
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr ""
@@ -1218,6 +1647,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1236,6 +1671,12 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1254,6 +1695,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI/CD"
msgstr ""
@@ -1281,6 +1725,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1296,6 +1743,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1308,6 +1758,12 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't find variable: ZiteReader"
+msgstr ""
+
+msgid "Can't scan the code?"
+msgstr ""
+
msgid "Cancel"
msgstr ""
@@ -1317,15 +1773,36 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot refer to a group milestone by an internal id!"
+msgstr ""
+
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1335,6 +1812,15 @@ msgstr ""
msgid "Change this value to influence how frequently the GitLab UI polls for updates."
msgstr ""
+msgid "Change title"
+msgstr ""
+
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -1359,12 +1845,18 @@ msgstr ""
msgid "Changes suppressed. Click to show."
msgstr ""
+msgid "Changes the title to \"%{title_param}\"."
+msgstr ""
+
msgid "Charts"
msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1377,6 +1869,9 @@ msgstr ""
msgid "Checking branch availability..."
msgstr ""
+msgid "Checking username availability..."
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr ""
@@ -1389,9 +1884,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1401,6 +1893,9 @@ msgstr ""
msgid "Choose a role permission"
msgstr ""
+msgid "Choose a template"
+msgstr ""
+
msgid "Choose a template..."
msgstr ""
@@ -1413,12 +1908,18 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose your merge method, options, checks, and set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr ""
@@ -1443,6 +1944,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1476,21 +1980,42 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
msgid "CiStatus|running"
msgstr ""
+msgid "CiVariables|Cannot use Masked Variable with current value"
+msgstr ""
+
msgid "CiVariables|Input variable key"
msgstr ""
msgid "CiVariables|Input variable value"
msgstr ""
+msgid "CiVariables|Key"
+msgstr ""
+
+msgid "CiVariables|Masked"
+msgstr ""
+
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|State"
+msgstr ""
+
+msgid "CiVariables|Type"
+msgstr ""
+
+msgid "CiVariables|Value"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1500,15 +2025,27 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|Protected"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
msgid "Clear"
msgstr ""
@@ -1539,6 +2076,15 @@ msgstr ""
msgid "Click to expand text"
msgstr ""
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
msgid "Clients"
msgstr ""
@@ -1560,6 +2106,9 @@ msgstr ""
msgid "Close milestone"
msgstr ""
+msgid "Close sidebar"
+msgstr ""
+
msgid "Closed"
msgstr ""
@@ -1575,15 +2124,21 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
+msgid "ClusterIntegration|%{title} uninstalled successfully."
+msgstr ""
+
+msgid "ClusterIntegration|%{title} updated successfully."
msgstr ""
-msgid "ClusterIntegration|%{title} upgraded successfully."
+msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1593,30 +2148,42 @@ msgstr ""
msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
msgstr ""
+msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
msgstr ""
+msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
+msgstr ""
+
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
+msgid "ClusterIntegration|All data will be deleted and cannot be restored."
msgstr ""
-msgid "ClusterIntegration|Alternatively"
+msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster."
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|Alternatively"
msgstr ""
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
msgstr ""
+msgid "ClusterIntegration|Any running pipelines will be canceled."
+msgstr ""
+
msgid "ClusterIntegration|Applications"
msgstr ""
@@ -1647,6 +2214,9 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1656,19 +2226,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -1716,7 +2286,10 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -1743,19 +2316,16 @@ msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Install"
-msgstr ""
-
-msgid "ClusterIntegration|Installed"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
-msgid "ClusterIntegration|Installing"
+msgid "ClusterIntegration|Instance cluster"
msgstr ""
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
@@ -1785,7 +2355,10 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative domain name was updated successfully."
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -1824,6 +2397,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr ""
+msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
+msgstr ""
+
msgid "ClusterIntegration|Let's Encrypt"
msgstr ""
@@ -1833,15 +2409,9 @@ msgstr ""
msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Manage"
-msgstr ""
-
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -1854,10 +2424,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -1866,15 +2436,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -1905,7 +2472,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -1941,16 +2508,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -1959,34 +2526,52 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
+msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster."
msgstr ""
-msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgid "ClusterIntegration|The URL used to access the Kubernetes API."
msgstr ""
-msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgid "ClusterIntegration|The associated IP will be deleted and cannot be restored."
msgstr ""
-msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgid "ClusterIntegration|The associated certifcate will be deleted and cannot be restored."
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
msgstr ""
-msgid "ClusterIntegration|Upgrade"
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
-msgid "ClusterIntegration|Upgrade failed"
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
msgstr ""
-msgid "ClusterIntegration|Upgraded"
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Upgrading"
+msgid "ClusterIntegration|Uninstall %{appTitle}"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
msgid "ClusterIntegration|Validating project billing status"
@@ -1998,6 +2583,9 @@ msgstr ""
msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
+msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
+msgstr ""
+
msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
msgstr ""
@@ -2007,13 +2595,13 @@ msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
-msgid "ClusterIntegration|Zone"
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
msgstr ""
-msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgid "ClusterIntegration|Zone"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|documentation"
@@ -2025,6 +2613,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2043,9 +2634,15 @@ msgstr ""
msgid "Collapse sidebar"
msgstr ""
+msgid "ComboSearch is not defined"
+msgstr ""
+
msgid "Command line instructions"
msgstr ""
+msgid "Commands applied"
+msgstr ""
+
msgid "Comment"
msgstr ""
@@ -2064,6 +2661,9 @@ msgstr ""
msgid "Comment form position"
msgstr ""
+msgid "Comment is being updated"
+msgstr ""
+
msgid "Comments"
msgstr ""
@@ -2177,6 +2777,9 @@ msgstr ""
msgid "Configure Gitaly timeouts."
msgstr ""
+msgid "Configure Let's Encrypt"
+msgstr ""
+
msgid "Configure automatic git checks and housekeeping on repositories."
msgstr ""
@@ -2192,15 +2795,30 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
msgid "Connect"
msgstr ""
msgid "Connect repositories from GitHub"
msgstr ""
+msgid "Connection failure"
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2264,6 +2882,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2291,6 +2912,9 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
+msgid "Copied"
+msgstr ""
+
msgid "Copy %{http_label} clone URL"
msgstr ""
@@ -2306,9 +2930,6 @@ msgstr ""
msgid "Copy SSH public key"
msgstr ""
-msgid "Copy SSH public key to clipboard"
-msgstr ""
-
msgid "Copy URL to clipboard"
msgstr ""
@@ -2324,6 +2945,12 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
+msgid "Copy labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
+msgid "Copy labels and milestone from other issue or merge request in this project"
+msgstr ""
+
msgid "Copy link"
msgstr ""
@@ -2333,15 +2960,45 @@ msgstr ""
msgid "Copy secret to clipboard"
msgstr ""
+msgid "Copy source to clipboard"
+msgstr ""
+
msgid "Copy to clipboard"
msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not change HEAD: branch '%{branch}' does not exist"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2351,10 +3008,16 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a merge request."
+msgstr ""
+
msgid "Create a new branch"
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
msgstr ""
msgid "Create a new issue"
@@ -2423,6 +3086,12 @@ msgstr ""
msgid "Create your first page"
msgstr ""
+msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
+msgstr ""
+
+msgid "CreateGroup|You don’t have permission to create groups."
+msgstr ""
+
msgid "CreateTag|Tag"
msgstr ""
@@ -2444,6 +3113,9 @@ msgstr ""
msgid "Created on:"
msgstr ""
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue"
+msgstr ""
+
msgid "Cron Timezone"
msgstr ""
@@ -2453,6 +3125,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current password"
+msgstr ""
+
msgid "CurrentUser|Profile"
msgstr ""
@@ -2525,12 +3200,27 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "DashboardProjects|Trending"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
msgid "Date picker"
msgstr ""
+msgid "DayTitle|F"
+msgstr ""
+
+msgid "DayTitle|M"
+msgstr ""
+
+msgid "DayTitle|S"
+msgstr ""
+
+msgid "DayTitle|W"
+msgstr ""
+
msgid "Debug"
msgstr ""
@@ -2549,6 +3239,12 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
@@ -2600,9 +3296,30 @@ msgstr ""
msgid "Delete this attachment"
msgstr ""
+msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove some tags in project container registry. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to remove wiki repository. Please try again or contact administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore project repository. Please contact the administrator."
+msgstr ""
+
+msgid "DeleteProject|Failed to restore wiki repository. Please contact the administrator."
+msgstr ""
+
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -2614,6 +3331,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -2791,9 +3511,15 @@ msgstr ""
msgid "Disable shared Runners"
msgstr ""
+msgid "Disable two-factor authentication"
+msgstr ""
+
msgid "Disabled"
msgstr ""
+msgid "Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them."
+msgstr ""
+
msgid "Discard"
msgstr ""
@@ -2812,6 +3538,12 @@ msgstr ""
msgid "Discard draft"
msgstr ""
+msgid "DiscordService|Discord Notifications"
+msgstr ""
+
+msgid "DiscordService|Receive event notifications in Discord"
+msgstr ""
+
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
@@ -2821,6 +3553,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -2833,9 +3568,18 @@ msgstr ""
msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
msgstr ""
+msgid "Dockerfile"
+msgstr ""
+
msgid "Domain"
msgstr ""
+msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
+msgstr ""
+
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
msgid "Don't show again"
msgstr ""
@@ -2851,19 +3595,16 @@ msgstr ""
msgid "Download asset"
msgstr ""
-msgid "Download tar"
+msgid "Download codes"
msgstr ""
-msgid "Download tar.bz2"
+msgid "Download export"
msgstr ""
-msgid "Download tar.gz"
+msgid "Download source code"
msgstr ""
-msgid "Download zip"
-msgstr ""
-
-msgid "DownloadArtifacts|Download"
+msgid "Download this directory"
msgstr ""
msgid "DownloadCommit|Email Patches"
@@ -2887,12 +3628,18 @@ msgstr ""
msgid "Edit"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
msgid "Edit Milestone"
msgstr ""
+msgid "Edit Password"
+msgstr ""
+
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
@@ -2908,6 +3655,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -2920,15 +3670,75 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Email"
msgstr ""
msgid "Email patch"
msgstr ""
+msgid "Email the pipelines status to a list of recipients."
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailToken|reset it"
+msgstr ""
+
+msgid "EmailToken|resetting..."
+msgstr ""
+
msgid "Emails"
msgstr ""
+msgid "Emails separated by comma"
+msgstr ""
+
+msgid "EmailsOnPushService|Disable code diffs"
+msgstr ""
+
+msgid "EmailsOnPushService|Don't include possibly sensitive code diffs in notification body."
+msgstr ""
+
+msgid "EmailsOnPushService|Email the commits and diff of each push to a list of recipients."
+msgstr ""
+
+msgid "EmailsOnPushService|Emails on push"
+msgstr ""
+
+msgid "EmailsOnPushService|Emails separated by whitespace"
+msgstr ""
+
+msgid "EmailsOnPushService|Send from committer"
+msgstr ""
+
+msgid "EmailsOnPushService|Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. %{domains})."
+msgstr ""
+
msgid "Embed"
msgstr ""
@@ -2941,6 +3751,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Sentry for error reporting and logging."
msgstr ""
@@ -2950,6 +3763,9 @@ msgstr ""
msgid "Enable and configure Prometheus metrics."
msgstr ""
+msgid "Enable classification control using an external service"
+msgstr ""
+
msgid "Enable error tracking"
msgstr ""
@@ -2959,6 +3775,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable version check and usage ping."
msgstr ""
@@ -2983,12 +3805,27 @@ msgstr ""
msgid "Enabled"
msgstr ""
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
msgid "Ends at (UTC)"
msgstr ""
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
msgid "Enter the issue description"
msgstr ""
@@ -3001,13 +3838,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3028,6 +3859,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3073,15 +3910,33 @@ msgstr ""
msgid "Environments|Open live environment"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3094,12 +3949,27 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
msgid "Environments|You don't have any environments right now"
msgstr ""
+msgid "Environments|protected"
+msgstr ""
+
msgid "Epic"
msgstr ""
@@ -3112,6 +3982,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error deleting %{issuableType}"
msgstr ""
@@ -3124,6 +3997,9 @@ msgstr ""
msgid "Error fetching network graph."
msgstr ""
+msgid "Error fetching projects"
+msgstr ""
+
msgid "Error fetching refs"
msgstr ""
@@ -3136,6 +4012,9 @@ msgstr ""
msgid "Error loading branches."
msgstr ""
+msgid "Error loading file viewer."
+msgstr ""
+
msgid "Error loading last commit."
msgstr ""
@@ -3145,6 +4024,9 @@ msgstr ""
msgid "Error loading merge requests."
msgstr ""
+msgid "Error loading milestone tab"
+msgstr ""
+
msgid "Error loading project data. Please try again."
msgstr ""
@@ -3154,9 +4036,27 @@ msgstr ""
msgid "Error loading template."
msgstr ""
+msgid "Error loading viewer"
+msgstr ""
+
+msgid "Error occurred when fetching sidebar data"
+msgstr ""
+
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3172,12 +4072,54 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error uploading file: %{stripped}"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while loading the project data. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3241,12 +4183,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3289,12 +4225,57 @@ msgstr ""
msgid "Explore public groups"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
msgid "External URL"
msgstr ""
msgid "External Wiki"
msgstr ""
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "ExternalMetrics|Add a button to the metrics dashboard linking directly to your existing external dashboards."
+msgstr ""
+
+msgid "ExternalMetrics|Enter the URL of the dashboard you want to link to"
+msgstr ""
+
+msgid "ExternalMetrics|External Dashboard"
+msgstr ""
+
+msgid "ExternalMetrics|Full dashboard URL"
+msgstr ""
+
+msgid "ExternalWikiService|External Wiki"
+msgstr ""
+
+msgid "ExternalWikiService|Replaces the link to the internal wiki with a link to an external wiki."
+msgstr ""
+
+msgid "ExternalWikiService|The URL of the external Wiki"
+msgstr ""
+
msgid "Facebook"
msgstr ""
@@ -3304,21 +4285,51 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
+msgid "Failed create wiki"
+msgstr ""
+
msgid "Failed to change the owner"
msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to connect to the prometheus server"
+msgstr ""
+
+msgid "Failed to create Merge Request. Please try again."
+msgstr ""
+
+msgid "Failed to create a branch for this issue. Please try again."
+msgstr ""
+
+msgid "Failed to create repository via gitlab-shell"
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
+msgid "Failed to get ref."
+msgstr ""
+
+msgid "Failed to install."
+msgstr ""
+
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
+msgid "Failed to load related branches"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3328,9 +4339,42 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to save comment!"
+msgstr ""
+
+msgid "Failed to save merge conflicts resolutions. Please try again!"
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to update branch!"
+msgstr ""
+
msgid "Failed to update issues, please try again."
msgstr ""
+msgid "Failed to update tag!"
+msgstr ""
+
+msgid "Failed to update."
+msgstr ""
+
+msgid "Failed to upgrade."
+msgstr ""
+
msgid "Failed to upload object map file"
msgstr ""
@@ -3343,6 +4387,9 @@ msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feb"
msgstr ""
@@ -3381,6 +4428,12 @@ msgstr ""
msgid "Files"
msgstr ""
+msgid "Files breadcrumb"
+msgstr ""
+
+msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
+msgstr ""
+
msgid "Filter"
msgstr ""
@@ -3402,9 +4455,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -3420,6 +4470,12 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprint:"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -3438,6 +4494,12 @@ msgstr ""
msgid "FirstPushedBy|pushed by"
msgstr ""
+msgid "FlowdockService|Flowdock Git source token"
+msgstr ""
+
+msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
+msgstr ""
+
msgid "FogBugz Email"
msgstr ""
@@ -3474,6 +4536,15 @@ msgstr ""
msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
msgstr ""
+msgid "Fork"
+msgstr ""
+
+msgid "Fork Error!"
+msgstr ""
+
+msgid "Fork project"
+msgstr ""
+
msgid "ForkedFromProjectPath|Forked from"
msgstr ""
@@ -3483,15 +4554,24 @@ msgstr ""
msgid "Forking in progress"
msgstr ""
+msgid "Forking repository"
+msgstr ""
+
msgid "Forks"
msgstr ""
msgid "Format"
msgstr ""
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
+msgid "Friday"
+msgstr ""
+
msgid "From %{providerTitle}"
msgstr ""
@@ -3519,24 +4599,36 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "Full name"
+msgstr ""
+
msgid "GPG Key ID:"
msgstr ""
msgid "GPG Keys"
msgstr ""
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
msgid "GPG signature (loading...)"
msgstr ""
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -3549,6 +4641,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -3558,6 +4653,9 @@ msgstr ""
msgid "Git revision"
msgstr ""
+msgid "Git shallow clone"
+msgstr ""
+
msgid "Git strategy for pipelines"
msgstr ""
@@ -3585,6 +4683,9 @@ msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab.com import"
msgstr ""
@@ -3609,21 +4710,36 @@ msgstr ""
msgid "Given access %{time_ago}"
msgstr ""
+msgid "Global notification settings"
+msgstr ""
+
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to parent"
+msgstr ""
+
msgid "Go to project"
msgstr ""
+msgid "Go to your fork"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -3642,9 +4758,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -3654,6 +4779,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -3681,24 +4809,45 @@ msgstr ""
msgid "Group name"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
msgid "Group: %{group_name}"
msgstr ""
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -3720,6 +4869,9 @@ msgstr ""
msgid "Groups"
msgstr ""
+msgid "Groups (%{count})"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -3789,6 +4941,12 @@ msgstr ""
msgid "GroupsTree|Search by name"
msgstr ""
+msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -3819,18 +4977,27 @@ msgstr ""
msgid "Help page text and support page url."
msgstr ""
-msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
+msgid "Hide archived projects"
msgstr ""
msgid "Hide file browser"
msgstr ""
+msgid "Hide group projects"
+msgstr ""
+
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
+msgid "Hide shared projects"
+msgstr ""
+
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
@@ -3839,12 +5006,30 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "History of authentications"
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -3854,6 +5039,12 @@ msgstr ""
msgid "I accept the|Terms of Service and Privacy Policy"
msgstr ""
+msgid "I forgot my password"
+msgstr ""
+
+msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}"
+msgstr ""
+
msgid "ID"
msgstr ""
@@ -3905,16 +5096,28 @@ msgstr ""
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
msgid "If enabled"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}."
+msgstr ""
+
+msgid "If this was a mistake you can leave the %{source_type}."
+msgstr ""
+
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
msgstr ""
-msgid "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>."
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
msgstr ""
msgid "ImageDiffViewer|2-up"
@@ -3995,12 +5198,24 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
msgid "Import/Export illustration"
msgstr ""
+msgid "ImportProjects|Blocked import URL: %{message}"
+msgstr ""
+
+msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
+msgstr ""
+
msgid "ImportProjects|Importing the project failed"
msgstr ""
@@ -4010,6 +5225,12 @@ msgstr ""
msgid "ImportProjects|Select the projects you want to import"
msgstr ""
+msgid "ImportProjects|The remote data could not be imported."
+msgstr ""
+
+msgid "ImportProjects|The repository could not be created."
+msgstr ""
+
msgid "ImportProjects|Updating the imported projects failed"
msgstr ""
@@ -4025,6 +5246,9 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
@@ -4043,9 +5267,15 @@ msgstr ""
msgid "Incompatible Project"
msgstr ""
+msgid "Incompatible options set!"
+msgstr ""
+
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
msgstr ""
@@ -4058,15 +5288,33 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Install"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
msgid "Install Runner on Kubernetes"
msgstr ""
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
msgid "Instance Statistics"
msgstr ""
@@ -4106,15 +5354,45 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid date"
+msgstr ""
+
+msgid "Invalid feature"
+msgstr ""
+
+msgid "Invalid field"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid import params"
+msgstr ""
+
msgid "Invalid input, please avoid emojis"
msgstr ""
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid repository path"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
msgid "Invite"
msgstr ""
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
msgid "Invite group"
msgstr ""
@@ -4139,6 +5417,12 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue update failed"
+msgstr ""
+
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -4148,6 +5432,9 @@ msgstr ""
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
msgid "Issues, merge requests, pushes, and comments."
msgstr ""
@@ -4163,15 +5450,75 @@ msgstr ""
msgid "January"
msgstr ""
+msgid "JiraService|Events for %{noteable_model_name} are disabled."
+msgstr ""
+
+msgid "JiraService|If different from Web URL"
+msgstr ""
+
+msgid "JiraService|JIRA API URL"
+msgstr ""
+
+msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgstr ""
+
+msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgstr ""
+
+msgid "JiraService|Jira issue tracker"
+msgstr ""
+
+msgid "JiraService|Password or API token"
+msgstr ""
+
+msgid "JiraService|Transition ID(s)"
+msgstr ""
+
+msgid "JiraService|Use , or ; to separate multiple transition IDs"
+msgstr ""
+
+msgid "JiraService|Use a password for server version and an API token for cloud version"
+msgstr ""
+
+msgid "JiraService|Use a username for server version and an email for cloud version"
+msgstr ""
+
+msgid "JiraService|Username or Email"
+msgstr ""
+
+msgid "JiraService|Web URL"
+msgstr ""
+
+msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
+msgstr ""
+
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -4202,6 +5549,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -4217,15 +5567,33 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
msgid "July"
msgstr ""
+msgid "Jump to first unresolved discussion"
+msgstr ""
+
+msgid "Jump to next unresolved discussion"
+msgstr ""
+
msgid "Jun"
msgstr ""
@@ -4235,6 +5603,9 @@ msgstr ""
msgid "Key (PEM)"
msgstr ""
+msgid "Key: %{key}"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -4259,30 +5630,51 @@ msgstr ""
msgid "Kubernetes configured"
msgstr ""
+msgid "Kubernetes error: %{error_code}"
+msgstr ""
+
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
msgid "LFSStatus|Enabled"
msgstr ""
+msgid "LICENSE"
+msgstr ""
+
msgid "Label"
msgstr ""
msgid "Label actions dropdown"
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
msgstr ""
+msgid "LabelSelect|Labels"
+msgstr ""
+
msgid "Labels"
msgstr ""
@@ -4304,6 +5696,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Labels|and %{count} more"
+msgstr ""
+
msgid "Language"
msgstr ""
@@ -4318,6 +5713,9 @@ msgstr[1] ""
msgid "Last Pipeline"
msgstr ""
+msgid "Last accessed on"
+msgstr ""
+
msgid "Last activity"
msgstr ""
@@ -4345,6 +5743,12 @@ msgstr ""
msgid "Last updated"
msgstr ""
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
msgid "LastPushEvent|You pushed to"
msgstr ""
@@ -4360,6 +5764,9 @@ msgstr ""
msgid "Lead"
msgstr ""
+msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
+msgstr ""
+
msgid "Learn more"
msgstr ""
@@ -4372,7 +5779,7 @@ msgstr ""
msgid "Learn more about Kubernetes"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
msgstr ""
msgid "Learn more about signing commits"
@@ -4387,6 +5794,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -4396,6 +5806,12 @@ msgstr ""
msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
msgstr ""
+msgid "Let's Encrypt does not accept emails on example.com"
+msgstr ""
+
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -4425,6 +5841,9 @@ msgstr ""
msgid "Live preview"
msgstr ""
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
msgid "Loading the GitLab IDE..."
msgstr ""
@@ -4446,6 +5865,9 @@ msgstr ""
msgid "Lock not found"
msgstr ""
+msgid "Lock the discussion"
+msgstr ""
+
msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
msgstr ""
@@ -4458,12 +5880,36 @@ msgstr ""
msgid "Locked to current projects"
msgstr ""
+msgid "Locks the discussion"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
+msgid "Make issue confidential."
+msgstr ""
+
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Makes this issue confidential"
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -4521,49 +5967,49 @@ msgstr ""
msgid "March"
msgstr ""
-msgid "Mark todo as done"
+msgid "Mark as resolved"
msgstr ""
-msgid "Markdown"
+msgid "Mark comment as resolved"
msgstr ""
-msgid "Markdown enabled"
+msgid "Mark this issue as a duplicate of another issue"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Mark todo as done"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Marks todo as done."
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Max access level"
+msgid "Maximum job timeout has a value which could not be accepted"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum push size (MB)"
msgstr ""
msgid "May"
@@ -4614,9 +6060,36 @@ msgstr ""
msgid "Merge when pipeline succeeds"
msgstr ""
+msgid "MergeConflict|Commit to source branch"
+msgstr ""
+
+msgid "MergeConflict|Committing..."
+msgstr ""
+
+msgid "MergeConflict|HEAD//our changes"
+msgstr ""
+
+msgid "MergeConflict|Use ours"
+msgstr ""
+
+msgid "MergeConflict|Use theirs"
+msgstr ""
+
+msgid "MergeConflict|conflict"
+msgstr ""
+
+msgid "MergeConflict|conflicts"
+msgstr ""
+
+msgid "MergeConflict|origin//their changes"
+msgstr ""
+
msgid "MergeRequests|Add a reply"
msgstr ""
+msgid "MergeRequests|Failed to squash. Should be done manually."
+msgstr ""
+
msgid "MergeRequests|Jump to next unresolved discussion"
msgstr ""
@@ -4629,6 +6102,9 @@ msgstr ""
msgid "MergeRequests|Saving the comment failed"
msgstr ""
+msgid "MergeRequests|Squash task canceled: another squash is already in progress."
+msgstr ""
+
msgid "MergeRequests|Toggle comments for this file"
msgstr ""
@@ -4659,18 +6135,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
+msgid "MergeRequest|Filter files or search with %{modifier_key}+p"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "MergeRequest|No files found"
msgstr ""
msgid "Merged"
msgstr ""
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -4689,6 +6168,9 @@ msgstr ""
msgid "Metrics for environment"
msgstr ""
+msgid "Metrics|Add metric"
+msgstr ""
+
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr ""
@@ -4701,6 +6183,9 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Show last"
+msgstr ""
+
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
@@ -4710,12 +6195,21 @@ msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
msgid "Metrics|Unexpected metrics data response from prometheus endpoint"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -4746,7 +6240,16 @@ msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
-msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged. This action cannot be reversed."
+msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
msgstr ""
msgid "Mirror a repository"
@@ -4764,6 +6267,12 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "Missing commit signatures endpoint!"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -4773,6 +6282,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -4803,15 +6315,45 @@ msgstr ""
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
msgid "Move issue"
msgstr ""
+msgid "Move issue from one column of the board to another"
+msgstr ""
+
+msgid "Move this issue to another project."
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
+msgstr ""
+
+msgid "MoveIssue|Cannot move issue to project it originates from!"
+msgstr ""
+
+msgid "Moves issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moves this issue to %{path_to_project}."
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
+msgid "Name has already been taken"
+msgstr ""
+
msgid "Name new label"
msgstr ""
@@ -4821,6 +6363,9 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -4886,6 +6431,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -4898,6 +6446,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -4913,12 +6464,18 @@ msgstr ""
msgid "New milestone"
msgstr ""
+msgid "New password"
+msgstr ""
+
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -4931,19 +6488,40 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Nickname"
+msgstr ""
+
msgid "No"
msgstr ""
msgid "No %{providerTitle} repositories available to import"
msgstr ""
+msgid "No Label"
+msgstr ""
+
+msgid "No Milestone"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
msgid "No activities found"
msgstr ""
-msgid "No assignee"
+msgid "No available namespaces to fork the project."
msgstr ""
msgid "No branches found"
@@ -4961,16 +6539,16 @@ msgstr ""
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr ""
-msgid "No contributions were found"
+msgid "No contributions"
msgstr ""
-msgid "No details available"
+msgid "No contributions were found"
msgstr ""
-msgid "No due date"
+msgid "No details available"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -4982,9 +6560,15 @@ msgstr ""
msgid "No file selected"
msgstr ""
+msgid "No files"
+msgstr ""
+
msgid "No files found."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
@@ -5003,6 +6587,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No parent group"
+msgstr ""
+
msgid "No preview for this file type"
msgstr ""
@@ -5027,9 +6614,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -5045,13 +6629,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Not started"
+msgid "Not ready yet. Try again later."
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -5063,6 +6650,9 @@ msgstr ""
msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
msgstr ""
+msgid "NoteForm|Note"
+msgstr ""
+
msgid "Notes|Are you sure you want to cancel creating this comment?"
msgstr ""
@@ -5078,6 +6668,12 @@ msgstr ""
msgid "Notes|Show history only"
msgstr ""
+msgid "Notes|This comment has changed since you started editing, please review the %{open_link}updated comment%{close_link} to ensure information is not lost"
+msgstr ""
+
+msgid "Nothing to preview."
+msgstr ""
+
msgid "Notification events"
msgstr ""
@@ -5087,6 +6683,9 @@ msgstr ""
msgid "Notification setting - %{notification_title}"
msgstr ""
+msgid "Notification settings saved"
+msgstr ""
+
msgid "NotificationEvent|Close issue"
msgstr ""
@@ -5138,6 +6737,9 @@ msgstr ""
msgid "NotificationLevel|Watch"
msgstr ""
+msgid "NotificationSetting|Custom"
+msgstr ""
+
msgid "Notifications"
msgstr ""
@@ -5153,6 +6755,9 @@ msgstr ""
msgid "November"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -5162,6 +6767,12 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -5206,6 +6817,9 @@ msgstr ""
msgid "Open in Xcode"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -5221,12 +6835,21 @@ msgstr ""
msgid "Opens in a new window"
msgstr ""
+msgid "Operation failed. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
+msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
+msgstr ""
+
msgid "Operations"
msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -5242,18 +6865,27 @@ msgstr ""
msgid "Other Labels"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
msgid "Overview"
msgstr ""
+msgid "Owned by anyone"
+msgstr ""
+
+msgid "Owned by me"
+msgstr ""
+
msgid "Owner"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -5287,12 +6919,33 @@ msgstr ""
msgid "Password"
msgstr ""
+msgid "Password (optional)"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -5311,6 +6964,9 @@ msgstr ""
msgid "People without permission will never get a notification and won't be able to comment."
msgstr ""
+msgid "People without permission will never get a notification."
+msgstr ""
+
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr ""
@@ -5320,6 +6976,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -5329,13 +6988,25 @@ msgstr ""
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
msgstr ""
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
+msgid "Pin code"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
+msgid "Pipeline ID (IID)"
msgstr ""
msgid "Pipeline Schedule"
@@ -5347,6 +7018,9 @@ msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -5395,12 +7069,21 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
+msgid "Pipelines emails"
+msgstr ""
+
msgid "Pipelines for last month"
msgstr ""
@@ -5410,6 +7093,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|API"
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -5452,10 +7141,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -5470,6 +7156,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -5488,6 +7177,9 @@ msgstr ""
msgid "Pipeline|Stop pipeline #%{pipelineId}?"
msgstr ""
+msgid "Pipeline|Triggerer"
+msgstr ""
+
msgid "Pipeline|Variables"
msgstr ""
@@ -5497,15 +7189,36 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
msgid "Pipeline|with stages"
msgstr ""
+msgid "PivotalTrackerService|Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches."
+msgstr ""
+
+msgid "PivotalTrackerService|Pivotal Tracker API token."
+msgstr ""
+
+msgid "PivotalTrackerService|Project Management Software (Source Commits Endpoint)"
+msgstr ""
+
msgid "Plain diff"
msgstr ""
@@ -5515,21 +7228,33 @@ msgstr ""
msgid "Play"
msgstr ""
+msgid "Play all manual"
+msgstr ""
+
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
msgstr ""
msgid "Please accept the Terms of Service before continuing."
msgstr ""
+msgid "Please add a list to your board first"
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
msgid "Please create a username with only alphanumeric characters."
msgstr ""
@@ -5539,6 +7264,18 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
+msgid "Please select a file"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
@@ -5551,7 +7288,10 @@ msgstr ""
msgid "Please try again"
msgstr ""
-msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
+msgstr ""
+
+msgid "Please wait a moment, this page will automatically refresh when ready."
msgstr ""
msgid "Please wait while we import the repository for you. Refresh at will."
@@ -5560,21 +7300,87 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
+msgid "Preferences|Display time in 24-hour format"
+msgstr ""
+
+msgid "Preferences|For example: 30 mins ago."
+msgstr ""
+
+msgid "Preferences|Layout width"
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
+msgid "Preferences|These settings will update how dates and times are displayed for you."
+msgstr ""
+
msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr ""
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
+msgid "Preferences|Time display"
+msgstr ""
+
+msgid "Preferences|Time format"
+msgstr ""
+
+msgid "Preferences|Time preferences"
+msgstr ""
+
+msgid "Preferences|Use relative times"
+msgstr ""
+
+msgid "Press %{key}-C to copy"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Preview"
msgstr ""
+msgid "Preview Markdown"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
+msgid "Previous Artifacts"
+msgstr ""
+
msgid "Prioritize"
msgstr ""
@@ -5596,15 +7402,24 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private profile"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
+msgid "Proceed"
+msgstr ""
+
msgid "Profile"
msgstr ""
msgid "Profile Settings"
msgstr ""
+msgid "ProfileSession|on"
+msgstr ""
+
msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
msgstr ""
@@ -5635,6 +7450,9 @@ msgstr ""
msgid "Profiles|Avatar will be removed. Are you sure?"
msgstr ""
+msgid "Profiles|Bio"
+msgstr ""
+
msgid "Profiles|Change username"
msgstr ""
@@ -5695,6 +7513,18 @@ msgstr ""
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
+msgid "Profiles|Feed token was successfully reset"
+msgstr ""
+
+msgid "Profiles|Full name"
+msgstr ""
+
+msgid "Profiles|Include private contributions on my profile"
+msgstr ""
+
+msgid "Profiles|Incoming email token was successfully reset"
+msgstr ""
+
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
msgstr ""
@@ -5704,9 +7534,15 @@ msgstr ""
msgid "Profiles|Invalid username"
msgstr ""
+msgid "Profiles|Key"
+msgstr ""
+
msgid "Profiles|Learn more"
msgstr ""
+msgid "Profiles|Location"
+msgstr ""
+
msgid "Profiles|Made a private contribution"
msgstr ""
@@ -5716,6 +7552,9 @@ msgstr ""
msgid "Profiles|No file chosen"
msgstr ""
+msgid "Profiles|Organization"
+msgstr ""
+
msgid "Profiles|Path"
msgstr ""
@@ -5725,6 +7564,9 @@ msgstr ""
msgid "Profiles|Private contributions"
msgstr ""
+msgid "Profiles|Profile was successfully updated"
+msgstr ""
+
msgid "Profiles|Public Avatar"
msgstr ""
@@ -5752,7 +7594,7 @@ msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
@@ -5761,6 +7603,9 @@ msgstr ""
msgid "Profiles|This information will appear on your profile"
msgstr ""
+msgid "Profiles|Time settings"
+msgstr ""
+
msgid "Profiles|Two-Factor Authentication"
msgstr ""
@@ -5782,6 +7627,9 @@ msgstr ""
msgid "Profiles|Use a private email - %{email}"
msgstr ""
+msgid "Profiles|User ID"
+msgstr ""
+
msgid "Profiles|Username change failed - %{message}"
msgstr ""
@@ -5803,6 +7651,9 @@ msgstr ""
msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
msgstr ""
+msgid "Profiles|You can set your current timezone here"
+msgstr ""
+
msgid "Profiles|You can upload your avatar here"
msgstr ""
@@ -5857,6 +7708,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -5872,16 +7732,28 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project cannot be shared with the group it is in or one of its ancestors."
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -5890,6 +7762,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -5911,12 +7786,33 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
msgid "ProjectFileTree|Name"
msgstr ""
@@ -5950,24 +7846,144 @@ msgstr ""
msgid "ProjectPage|Project ID: %{project_id}"
msgstr ""
+msgid "ProjectSelect| or group"
+msgstr ""
+
+msgid "ProjectSelect|Search for project"
+msgstr ""
+
+msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
+msgstr ""
+
+msgid "ProjectSettings|All discussions must be resolved"
+msgstr ""
+
+msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "ProjectSettings|Badges"
msgstr ""
msgid "ProjectSettings|Customize your project badges."
msgstr ""
+msgid "ProjectSettings|Every merge creates a merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merge"
+msgstr ""
+
+msgid "ProjectSettings|Fast-forward merges only"
+msgstr ""
+
msgid "ProjectSettings|Learn more about badges."
msgstr ""
+msgid "ProjectSettings|Merge checks"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit"
+msgstr ""
+
+msgid "ProjectSettings|Merge commit with semi-linear history"
+msgstr ""
+
+msgid "ProjectSettings|Merge method"
+msgstr ""
+
+msgid "ProjectSettings|Merge options"
+msgstr ""
+
+msgid "ProjectSettings|No merge commits are created"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines must succeed"
+msgstr ""
+
+msgid "ProjectSettings|Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "ProjectSettings|These checks must pass before merge requests can be merged"
+msgstr ""
+
+msgid "ProjectSettings|This will dictate the commit history when you merge a merge request"
+msgstr ""
+
+msgid "ProjectSettings|When conflicts arise the user is given the option to rebase"
+msgstr ""
+
+msgid "ProjectTemplates|.NET Core"
+msgstr ""
+
+msgid "ProjectTemplates|Android"
+msgstr ""
+
+msgid "ProjectTemplates|Go Micro"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Netlify/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|NodeJS Express"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/GitBook"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hexo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Hugo"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Jekyll"
+msgstr ""
+
+msgid "ProjectTemplates|Pages/Plain HTML"
+msgstr ""
+
+msgid "ProjectTemplates|Ruby on Rails"
+msgstr ""
+
+msgid "ProjectTemplates|Spring"
+msgstr ""
+
+msgid "ProjectTemplates|iOS (Swift)"
+msgstr ""
+
msgid "Projects"
msgstr ""
+msgid "Projects (%{count})"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -6061,9 +8077,27 @@ msgstr ""
msgid "Promote to group label"
msgstr ""
+msgid "PromoteMilestone|Only project milestones can be promoted."
+msgstr ""
+
+msgid "PromoteMilestone|Project does not belong to a group."
+msgstr ""
+
+msgid "PromoteMilestone|Promotion failed - %{message}"
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
+msgid "Protected Branch"
+msgstr ""
+
+msgid "Protected Tag"
+msgstr ""
+
msgid "Protip:"
msgstr ""
@@ -6079,12 +8113,24 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
msgid "Push"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -6094,12 +8140,54 @@ msgstr ""
msgid "Push to create a project"
msgstr ""
+msgid "PushoverService|%{user_name} deleted branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} push to branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
+msgstr ""
+
+msgid "PushoverService|High Priority"
+msgstr ""
+
+msgid "PushoverService|Leave blank for all active devices"
+msgstr ""
+
+msgid "PushoverService|Low Priority"
+msgstr ""
+
+msgid "PushoverService|Lowest Priority"
+msgstr ""
+
+msgid "PushoverService|Normal Priority"
+msgstr ""
+
+msgid "PushoverService|Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop."
+msgstr ""
+
+msgid "PushoverService|See project %{project_full_name}"
+msgstr ""
+
+msgid "PushoverService|Total commits count: %{total_commits_count}"
+msgstr ""
+
+msgid "PushoverService|Your application key"
+msgstr ""
+
+msgid "PushoverService|Your user key"
+msgstr ""
+
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -6112,9 +8200,21 @@ msgstr ""
msgid "Real-time features"
msgstr ""
+msgid "Receive notifications about your own activity"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent Searches Service is unavailable"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
+msgid "Recovery Codes"
+msgstr ""
+
msgid "Reference:"
msgstr ""
@@ -6126,21 +8226,36 @@ msgstr[1] ""
msgid "Regenerate key"
msgstr ""
+msgid "Regenerate recovery codes"
+msgstr ""
+
msgid "Regex pattern"
msgstr ""
+msgid "Register"
+msgstr ""
+
msgid "Register / Sign In"
msgstr ""
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
msgid "Register U2F device"
msgstr ""
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
msgid "Register and see your runners for this group."
msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Register with two-factor app"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -6174,30 +8289,78 @@ msgstr ""
msgid "Remind later"
msgstr ""
+msgid "Remote object has no absolute path."
+msgstr ""
+
msgid "Remove"
msgstr ""
msgid "Remove Runner"
msgstr ""
+msgid "Remove all or specific assignee(s)"
+msgstr ""
+
+msgid "Remove all or specific label(s)"
+msgstr ""
+
+msgid "Remove assignee"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove due date"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
+msgid "Remove milestone"
+msgstr ""
+
msgid "Remove priority"
msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove spent time"
+msgstr ""
+
+msgid "Remove time estimate"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removes %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removes all labels."
+msgstr ""
+
+msgid "Removes spent time."
+msgstr ""
+
+msgid "Removes the due date."
+msgstr ""
+
+msgid "Removes time estimate."
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -6210,13 +8373,19 @@ msgstr ""
msgid "Reopen milestone"
msgstr ""
+msgid "Replace"
+msgstr ""
+
+msgid "Replace all label(s)"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr ""
-msgid "Report abuse to GitLab"
+msgid "Report abuse to admin"
msgstr ""
msgid "Reporting"
@@ -6228,6 +8397,9 @@ msgstr ""
msgid "Reports|Class"
msgstr ""
+msgid "Reports|Classname"
+msgstr ""
+
msgid "Reports|Execution time"
msgstr ""
@@ -6258,6 +8430,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -6288,6 +8463,9 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require users to prove ownership of custom domains"
+msgstr ""
+
msgid "Resend invite"
msgstr ""
@@ -6309,6 +8487,21 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Resolved by %{name}"
+msgstr ""
+
+msgid "Resolved by %{resolvedByName}"
+msgstr ""
+
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -6336,6 +8529,12 @@ msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry update"
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -6365,6 +8564,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "RightSidebar|adding a"
+msgstr ""
+
+msgid "RightSidebar|deleting the"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run untagged jobs"
msgstr ""
@@ -6383,6 +8597,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -6413,24 +8633,42 @@ msgstr ""
msgid "Running…"
msgstr ""
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SSH Key"
+msgstr ""
+
msgid "SSH Keys"
msgstr ""
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr ""
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
msgid "SSH public key"
msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
msgid "Save Changes"
msgstr ""
+msgid "Save anyway"
+msgstr ""
+
msgid "Save application"
msgstr ""
@@ -6440,12 +8678,18 @@ msgstr ""
msgid "Save comment"
msgstr ""
+msgid "Save password"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -6464,6 +8708,12 @@ msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -6485,9 +8735,18 @@ msgstr ""
msgid "Search files"
msgstr ""
+msgid "Search for a group"
+msgstr ""
+
+msgid "Search for a user"
+msgstr ""
+
msgid "Search for projects, issues, etc."
msgstr ""
+msgid "Search forks"
+msgstr ""
+
msgid "Search groups"
msgstr ""
@@ -6509,9 +8768,15 @@ msgstr ""
msgid "Search projects"
msgstr ""
+msgid "Search projects..."
+msgstr ""
+
msgid "Search users"
msgstr ""
+msgid "Search your projects"
+msgstr ""
+
msgid "SearchAutocomplete|All GitLab"
msgstr ""
@@ -6536,6 +8801,9 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -6551,19 +8819,22 @@ msgstr ""
msgid "Select Archive Format"
msgstr ""
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
msgid "Select a group to invite"
msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
-msgid "Select a timezone"
+msgid "Select a new namespace"
msgstr ""
-msgid "Select an existing Kubernetes cluster or create a new one"
+msgid "Select a timezone"
msgstr ""
-msgid "Select assignee"
+msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
msgid "Select branch/tag"
@@ -6593,6 +8864,9 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
@@ -6611,18 +8885,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -6638,9 +8933,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -6659,9 +8951,18 @@ msgstr ""
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
msgstr ""
+msgid "Service"
+msgstr ""
+
msgid "Service Templates"
msgstr ""
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
@@ -6671,9 +8972,15 @@ msgstr ""
msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
msgstr ""
+msgid "Set due date"
+msgstr ""
+
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set milestone"
+msgstr ""
+
msgid "Set new password"
msgstr ""
@@ -6683,6 +8990,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set time estimate"
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -6728,6 +9047,15 @@ msgstr ""
msgid "SetStatusModal|What's your status?"
msgstr ""
+msgid "Sets the due date to %{due_date}."
+msgstr ""
+
+msgid "Sets the milestone to %{milestone_reference}."
+msgstr ""
+
+msgid "Sets time estimate to %{time_estimate}."
+msgstr ""
+
msgid "Settings"
msgstr ""
@@ -6743,9 +9071,24 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show archived projects"
+msgstr ""
+
+msgid "Show archived projects only"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -6790,6 +9133,24 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -6823,6 +9184,9 @@ msgstr ""
msgid "SnippetsEmptyState|They can be either public or private."
msgstr ""
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -6847,6 +9211,9 @@ msgstr ""
msgid "Something went wrong when toggling the button"
msgstr ""
+msgid "Something went wrong while adding your award. Please try again."
+msgstr ""
+
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
@@ -6859,6 +9226,12 @@ msgstr ""
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
+msgid "Something went wrong while fetching latest comments."
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -6877,15 +9250,27 @@ msgstr ""
msgid "Something went wrong while resolving this discussion. Please try again."
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
+msgid "Something went wrong. Try again later."
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
msgid "Sorry, your filter produced no results"
msgstr ""
msgid "Sort by"
msgstr ""
+msgid "Sort direction"
+msgstr ""
+
msgid "SortOptions|Access level, ascending"
msgstr ""
@@ -6928,6 +9313,9 @@ msgstr ""
msgid "SortOptions|Least popular"
msgstr ""
+msgid "SortOptions|Manual"
+msgstr ""
+
msgid "SortOptions|Milestone due date"
msgstr ""
@@ -6979,6 +9367,12 @@ msgstr ""
msgid "SortOptions|Recent sign in"
msgstr ""
+msgid "SortOptions|Sort direction"
+msgstr ""
+
+msgid "SortOptions|Stars"
+msgstr ""
+
msgid "SortOptions|Start later"
msgstr ""
@@ -7006,6 +9400,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -7042,6 +9439,9 @@ msgstr ""
msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
msgstr ""
+msgid "Star toggle failed. Try again later."
+msgstr ""
+
msgid "StarProject|Star"
msgstr ""
@@ -7066,6 +9466,12 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
msgid "Start and due date"
msgstr ""
@@ -7093,6 +9499,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starts %{startsIn}"
msgstr ""
@@ -7129,12 +9538,18 @@ msgstr ""
msgid "Storage:"
msgstr ""
+msgid "StorageSize|Unknown"
+msgstr ""
+
msgid "Subgroups"
msgstr ""
msgid "Subgroups and projects"
msgstr ""
+msgid "Subkeys"
+msgstr ""
+
msgid "Submit as spam"
msgstr ""
@@ -7162,21 +9577,123 @@ msgstr ""
msgid "Subscribed"
msgstr ""
+msgid "Subscription"
+msgstr ""
+
+msgid "Subtracts"
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
msgid "Sunday"
msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
+msgid "Switch to GitLab Next"
+msgstr ""
+
+msgid "Switch to the source to copy it to the clipboard"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -7186,24 +9703,39 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
msgid "System metrics (Kubernetes)"
msgstr ""
+msgid "Table of Contents"
+msgstr ""
+
msgid "Tag"
msgstr ""
msgid "Tag list:"
msgstr ""
+msgid "Tag this commit."
+msgstr ""
+
msgid "Tags"
msgstr ""
msgid "Tags feed"
msgstr ""
+msgid "Tags this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tags this commit to %{tag_name}."
+msgstr ""
+
msgid "Tags:"
msgstr ""
@@ -7285,6 +9817,9 @@ msgstr ""
msgid "Team"
msgstr ""
+msgid "Team domain"
+msgstr ""
+
msgid "Template"
msgstr ""
@@ -7303,6 +9838,44 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Test settings and save changes"
+msgstr ""
+
+msgid "TestHooks|Ensure one of your projects has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI jobs."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has CI pipelines."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has at least one commit."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has issues."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has merge requests."
+msgstr ""
+
+msgid "TestHooks|Ensure the project has notes."
+msgstr ""
+
+msgid "TestHooks|Ensure the wiki is enabled and has pages."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr ""
@@ -7312,6 +9885,12 @@ msgstr ""
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
msgstr ""
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -7321,15 +9900,63 @@ msgstr ""
msgid "The collection of events added to the data gathered for that stage."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -7339,6 +9966,27 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
+msgid "The number of times an upload record could not find its file"
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
msgid "The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>"
msgstr ""
@@ -7351,15 +9999,42 @@ 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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 by any user who is logged in."
+msgstr ""
+
+msgid "The project can be accessed by anyone, regardless of authentication."
+msgstr ""
+
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
+msgstr ""
+
+msgid "The project is still being deleted. Please try again later."
+msgstr ""
+
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The remote mirror took to long to complete."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -7375,6 +10050,21 @@ 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 ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -7390,9 +10080,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -7402,6 +10101,18 @@ 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 no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
msgid "There are no archived projects yet"
msgstr ""
@@ -7432,9 +10143,24 @@ msgstr ""
msgid "There are no unstaged changes"
msgstr ""
+msgid "There is already a repository with that name on disk"
+msgstr ""
+
+msgid "There was a problem communicating with your device."
+msgstr ""
+
+msgid "There was an error %{message} todo."
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -7450,6 +10176,12 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error while fetching cycle analytics data."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -7459,6 +10191,9 @@ msgstr ""
msgid "Third party offers"
msgstr ""
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
@@ -7468,6 +10203,12 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This also resolves the discussion"
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -7477,6 +10218,9 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This certificate is automatically managed by Let's Encrypt"
+msgstr ""
+
msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
msgstr ""
@@ -7495,6 +10239,12 @@ msgstr ""
msgid "This container registry has been scheduled for deletion."
msgstr ""
+msgid "This device has already been registered with us."
+msgstr ""
+
+msgid "This device has not been registered with us."
+msgstr ""
+
msgid "This diff is collapsed."
msgstr ""
@@ -7516,19 +10266,28 @@ msgstr ""
msgid "This group does not provide any group Runners yet."
msgstr ""
+msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
+msgstr ""
+
msgid "This is a confidential issue."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
msgid "This is the author's first Merge Request to this project."
msgstr ""
-msgid "This issue is confidential"
+msgid "This is your current session"
msgstr ""
-msgid "This issue is confidential and locked."
+msgid "This issue is confidential"
msgstr ""
msgid "This issue is locked."
@@ -7573,6 +10332,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -7594,10 +10359,10 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -7606,7 +10371,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -7630,6 +10395,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -7639,10 +10407,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -7651,6 +10419,15 @@ msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
+msgid "Thursday"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -7666,6 +10443,9 @@ msgstr ""
msgid "Time estimate"
msgstr ""
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
msgid "Time remaining"
msgstr ""
@@ -7711,9 +10491,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -7835,6 +10612,9 @@ msgstr ""
msgid "Title"
msgstr ""
+msgid "Title:"
+msgstr ""
+
msgid "Titles and Filenames"
msgstr ""
@@ -7850,6 +10630,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
msgid "To define internal users, first enable new users set to external"
msgstr ""
@@ -7892,6 +10675,9 @@ msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
msgstr ""
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
msgid "To start serving your jobs you can add Runners to your group"
msgstr ""
@@ -7913,12 +10699,18 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
msgid "Toggle Sidebar"
msgstr ""
+msgid "Toggle backtrace"
+msgstr ""
+
msgid "Toggle comments for this file"
msgstr ""
@@ -7931,10 +10723,10 @@ msgstr ""
msgid "Toggle discussion"
msgstr ""
-msgid "Toggle navigation"
+msgid "Toggle emoji award"
msgstr ""
-msgid "Toggle sidebar"
+msgid "Toggle navigation"
msgstr ""
msgid "ToggleButton|Toggle Status: OFF"
@@ -7943,6 +10735,9 @@ msgstr ""
msgid "ToggleButton|Toggle Status: ON"
msgstr ""
+msgid "Toggles :%{name}: emoji award."
+msgstr ""
+
msgid "Token"
msgstr ""
@@ -7952,6 +10747,9 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
msgid "Total Time"
msgstr ""
@@ -7964,12 +10762,51 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
+msgid "TransferGroup|Database is not supported."
+msgstr ""
+
+msgid "TransferGroup|Group is already a root group."
+msgstr ""
+
+msgid "TransferGroup|Group is already associated to the parent group."
+msgstr ""
+
+msgid "TransferGroup|The parent group already has a subgroup with the same path."
+msgstr ""
+
+msgid "TransferGroup|Transfer failed: %{error_message}"
+msgstr ""
+
+msgid "TransferGroup|You don't have enough permissions."
+msgstr ""
+
+msgid "TransferProject|Cannot move project"
+msgstr ""
+
+msgid "TransferProject|Please select a new namespace for your project."
+msgstr ""
+
+msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
+msgstr ""
+
+msgid "TransferProject|Project with same name or path in target namespace already exists"
+msgstr ""
+
+msgid "TransferProject|Transfer failed, please contact an admin."
+msgstr ""
+
msgid "Tree view"
msgstr ""
msgid "Trending"
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -7979,6 +10816,15 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -7988,21 +10834,66 @@ msgstr ""
msgid "Try again?"
msgstr ""
+msgid "Try to fork again"
+msgstr ""
+
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
msgstr ""
+msgid "Tuesday"
+msgstr ""
+
msgid "Twitter"
msgstr ""
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
msgid "Type"
msgstr ""
+msgid "U2F Devices (%{length})"
+msgstr ""
+
+msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to resolve"
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to update label prioritization at this time"
+msgstr ""
+
+msgid "Unable to update this issue at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -8012,15 +10903,30 @@ msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
+msgid "Unknown encryption strategy: %{encrypted_strategy}!"
+msgstr ""
+
+msgid "Unknown format"
+msgstr ""
+
+msgid "Unknown response text"
+msgstr ""
+
msgid "Unlock"
msgstr ""
+msgid "Unlock the discussion"
+msgstr ""
+
msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
msgstr ""
msgid "Unlocked"
msgstr ""
+msgid "Unlocks the discussion"
+msgstr ""
+
msgid "Unresolve discussion"
msgstr ""
@@ -8078,9 +10984,30 @@ msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "UpdateProject|Cannot rename project because it contains container registry tags!"
+msgstr ""
+
+msgid "UpdateProject|Could not set the default branch"
+msgstr ""
+
+msgid "UpdateProject|New visibility level not allowed!"
+msgstr ""
+
+msgid "UpdateProject|Project could not be updated!"
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
msgid "Updating"
msgstr ""
@@ -8102,6 +11029,9 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "Upload file does not exist"
+msgstr ""
+
msgid "Upload object map"
msgstr ""
@@ -8120,6 +11050,12 @@ msgstr ""
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
msgid "Use group milestones to manage issues from multiple projects in the same milestone."
msgstr ""
@@ -8135,18 +11071,54 @@ msgstr ""
msgid "Use your global notification setting"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -8222,21 +11194,57 @@ msgstr ""
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
msgstr ""
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
msgid "Users"
msgstr ""
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
+msgid "Using required encryption strategy when encrypted field is missing!"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -8270,12 +11278,21 @@ msgstr ""
msgid "View file @ "
msgstr ""
+msgid "View full dashboard"
+msgstr ""
+
msgid "View group labels"
msgstr ""
+msgid "View in Sentry"
+msgstr ""
+
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -8300,6 +11317,9 @@ msgstr ""
msgid "Viewing commit"
msgstr ""
+msgid "Visibility"
+msgstr ""
+
msgid "Visibility and access controls"
msgstr ""
@@ -8309,6 +11329,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -8324,6 +11347,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
+msgid "Wait for the source to load to copy it to the clipboard"
+msgstr ""
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
@@ -8339,6 +11365,9 @@ msgstr ""
msgid "We heard back from your U2F device. You have been authenticated."
msgstr ""
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
@@ -8348,12 +11377,32 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
+msgid "Wednesday"
+msgstr ""
+
+msgid "Welcome to your Issue Board!"
+msgstr ""
+
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -8366,6 +11415,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -8477,6 +11529,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -8495,6 +11550,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -8504,15 +11562,30 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
+msgid "Yes or No"
+msgstr ""
+
msgid "Yes, add it"
msgstr ""
@@ -8528,7 +11601,13 @@ msgstr ""
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr ""
-msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are attempting to delete a file that has been previously updated."
+msgstr ""
+
+msgid "You are attempting to update a file that has changed since you started editing it."
+msgstr ""
+
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
@@ -8540,6 +11619,12 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -8552,9 +11637,24 @@ msgstr ""
msgid "You can also create a project from the command line."
msgstr ""
+msgid "You can also press &#8984;-Enter"
+msgstr ""
+
+msgid "You can also press Ctrl-Enter"
+msgstr ""
+
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -8564,9 +11664,21 @@ msgstr ""
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
msgstr ""
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -8576,21 +11688,57 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
+msgid "You can see your chat accounts."
+msgstr ""
+
msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
msgstr ""
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
+msgid "You do not have permission to leave this %{namespaceType}."
+msgstr ""
+
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
msgid "You don't have any applications"
msgstr ""
@@ -8600,12 +11748,30 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -8615,18 +11781,45 @@ msgstr ""
msgid "You must have maintainer access to force delete a lock"
msgstr ""
+msgid "You must have permission to create a project in a namespace before forking."
+msgstr ""
+
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
msgid "You need permission."
msgstr ""
+msgid "You need to be logged in."
+msgstr ""
+
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -8654,6 +11847,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -8663,12 +11865,18 @@ msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgstr ""
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
msgid "YouTube"
msgstr ""
msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
msgstr ""
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
msgid "Your Groups"
msgstr ""
@@ -8681,12 +11889,27 @@ msgstr ""
msgid "Your Projects' Activity"
msgstr ""
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
msgid "Your Todos"
msgstr ""
+msgid "Your U2F device did not send a valid JSON response."
+msgstr ""
+
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -8705,7 +11928,16 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
-msgid "Your comment will not be visible to the public."
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment could not be submitted! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your comment could not be updated! Please check your network connection and try again."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
msgstr ""
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
@@ -8720,28 +11952,46 @@ msgstr ""
msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
msgstr ""
+msgid "Your message here"
+msgstr ""
+
msgid "Your name"
msgstr ""
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
msgid "allowed to fail"
msgstr ""
-msgid "among other things"
+msgid "already being used for another group or project milestone."
msgstr ""
-msgid "assign yourself"
+msgid "already shared with this group"
+msgstr ""
+
+msgid "among other things"
msgstr ""
msgid "attach a new file"
@@ -8753,12 +12003,21 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "command line instructions"
+msgid "cannot be changed if a personal project has container registry tags."
+msgstr ""
+
+msgid "cannot be enabled unless all domains have TLS certificates"
+msgstr ""
+
+msgid "cannot include leading slash or directory traversal."
msgstr ""
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "commit %{commit_id}"
+msgstr ""
+
msgid "confidentiality|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."
msgstr ""
@@ -8768,9 +12027,15 @@ msgstr ""
msgid "connecting"
msgstr ""
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
msgid "customize"
msgstr ""
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
msgid "day"
msgid_plural "days"
msgstr[0] ""
@@ -8782,6 +12047,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -8793,9 +12061,15 @@ msgstr[1] ""
msgid "done"
msgstr ""
+msgid "element is not a hierarchy"
+msgstr ""
+
msgid "enabled"
msgstr ""
+msgid "encrypted: needs to be a :required, :optional or :migrating!"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -8805,6 +12079,27 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
+msgid "expired on %{milestone_due_date}"
+msgstr ""
+
+msgid "expires on %{milestone_due_date}"
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr ""
@@ -8814,10 +12109,10 @@ msgstr ""
msgid "group"
msgstr ""
-msgid "here"
+msgid "has already been taken"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -8841,6 +12136,15 @@ msgstr ""
msgid "index"
msgstr ""
+msgid "invalid milestone state `%{state}`"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is not an email you own"
+msgstr ""
+
msgid "issue boards"
msgstr ""
@@ -8853,6 +12157,9 @@ msgstr ""
msgid "it is too large"
msgstr ""
+msgid "jigsaw is not defined"
+msgstr ""
+
msgid "latest"
msgstr ""
@@ -8862,6 +12169,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "manual"
msgstr ""
@@ -8870,12 +12180,18 @@ msgid_plural "merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "milestone should belong either to a project or a group."
+msgstr ""
+
msgid "missing"
msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -8942,6 +12258,9 @@ msgstr ""
msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
msgstr ""
+msgid "mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result"
+msgstr ""
+
msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
msgstr ""
@@ -8960,6 +12279,9 @@ msgstr ""
msgid "mrWidget|Merge failed."
msgstr ""
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
msgid "mrWidget|Merge locally"
msgstr ""
@@ -9035,6 +12357,9 @@ msgstr ""
msgid "mrWidget|The source branch will not be deleted"
msgstr ""
+msgid "mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging"
+msgstr ""
+
msgid "mrWidget|There are merge conflicts"
msgstr ""
@@ -9074,12 +12399,21 @@ msgstr ""
msgid "mrWidget|to be merged automatically when the pipeline succeeds"
msgstr ""
+msgid "must be greater than start date"
+msgstr ""
+
msgid "n/a"
msgstr ""
+msgid "needs to be beetween 10 minutes and 1 month"
+msgstr ""
+
msgid "new merge request"
msgstr ""
+msgid "no contributions"
+msgstr ""
+
msgid "none"
msgstr ""
@@ -9117,6 +12451,12 @@ msgstr ""
msgid "private"
msgstr ""
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
msgid "project"
msgstr ""
@@ -9143,7 +12483,7 @@ msgstr[1] ""
msgid "score"
msgstr ""
-msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr ""
msgid "show less"
@@ -9152,21 +12492,36 @@ msgstr ""
msgid "sign in"
msgstr ""
+msgid "sort:"
+msgstr ""
+
msgid "source"
msgstr ""
msgid "source diff"
msgstr ""
+msgid "specified top is not part of the tree"
+msgstr ""
+
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr ""
msgid "started"
msgstr ""
+msgid "started on %{milestone_start_date}"
+msgstr ""
+
+msgid "starts on %{milestone_start_date}"
+msgstr ""
+
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
@@ -9191,6 +12546,12 @@ msgstr ""
msgid "verify ownership"
msgstr ""
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
msgid "view it on GitLab"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index a931df046da..a773d0af51f 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -13,9 +13,18 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: gl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:15\n"
+"PO-Revision-Date: 2019-04-12 12:39\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
+msgstr "Estado"
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
msgstr ""
msgid " and"
@@ -23,25 +32,27 @@ msgstr " e"
msgid " degraded on %d point"
msgid_plural " degraded on %d points"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] " rebaixado en %d puntos"
+msgstr[1] " rebaixado en %d puntos"
msgid " improved on %d point"
msgid_plural " improved on %d points"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] " mellorado en %d puntos"
+msgstr[1] " mellorado en %d punto"
msgid " or "
-msgstr ""
+msgstr " ou "
msgid " or <#epic id>"
-msgstr ""
+msgstr " ou <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " ou <identificación #issue>"
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index 0c3654b1266..6210346cb46 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: he\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:15\n"
+"PO-Revision-Date: 2019-04-12 12:37\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -44,8 +53,12 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -156,12 +169,28 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -182,7 +211,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -191,6 +220,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -200,6 +235,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -212,6 +250,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -261,6 +308,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -270,6 +320,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -296,6 +349,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -310,6 +370,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -359,6 +422,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -368,6 +434,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -383,6 +458,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -416,7 +494,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -428,18 +518,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -470,12 +581,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -485,6 +602,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -515,34 +638,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -566,18 +716,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -614,6 +773,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -695,12 +857,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -714,6 +885,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -723,15 +897,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -744,6 +936,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -762,6 +960,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -783,9 +984,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -795,6 +993,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -807,18 +1014,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -831,6 +1047,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -843,6 +1062,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -858,6 +1080,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -900,12 +1125,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -963,6 +1194,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -975,15 +1209,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -996,6 +1248,52 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1011,15 +1309,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1032,15 +1345,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1053,6 +1375,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1107,9 +1432,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1125,6 +1447,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1140,10 +1465,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1152,6 +1477,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1170,6 +1501,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1209,16 +1543,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1344,6 +1672,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1581,6 +1915,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1602,9 +1942,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1623,6 +1972,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1656,6 +2008,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1671,6 +2026,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1686,6 +2044,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1698,19 +2059,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1737,6 +2113,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1749,6 +2128,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1776,9 +2161,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1800,25 +2182,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1845,6 +2227,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1878,6 +2263,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1893,6 +2281,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1905,6 +2296,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1914,12 +2308,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1977,6 +2377,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1992,9 +2395,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2034,16 +2434,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2082,6 +2479,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,19 +2494,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2151,7 +2554,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2178,13 +2581,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2193,15 +2596,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2229,7 +2635,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2286,9 +2689,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2301,10 +2701,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2313,15 +2713,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2352,7 +2749,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,16 +2785,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2409,7 +2806,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2418,10 +2815,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2460,9 +2863,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2472,6 +2872,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2481,15 +2884,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2587,6 +3002,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2656,6 +3074,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2671,12 +3095,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2740,6 +3176,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2767,18 +3206,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2791,6 +3221,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2815,9 +3248,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2833,9 +3263,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2845,10 +3299,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2860,6 +3314,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2884,9 +3341,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2956,6 +3410,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2995,9 +3452,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3040,6 +3494,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3064,15 +3521,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3112,6 +3578,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3124,6 +3593,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3137,6 +3621,9 @@ msgstr[3] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3275,12 +3762,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3359,6 +3879,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3398,6 +3921,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3443,6 +3972,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3464,6 +3996,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3476,18 +4011,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3503,6 +4074,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3530,6 +4104,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3539,9 +4119,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3557,16 +4134,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,13 +4173,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3611,6 +4194,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3662,15 +4251,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3683,6 +4290,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3734,6 +4353,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3782,6 +4404,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3797,12 +4431,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3866,12 +4536,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3881,12 +4545,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3920,6 +4590,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3938,9 +4614,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3962,13 +4635,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3995,12 +4689,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4034,9 +4734,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4094,7 +4791,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4121,6 +4818,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4185,9 +4894,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4203,6 +4909,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4296,13 +5005,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4332,12 +5047,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4347,6 +5071,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4371,9 +5098,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4392,6 +5116,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4437,6 +5164,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4545,9 +5275,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4563,6 +5308,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4593,6 +5341,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4632,24 +5386,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4668,9 +5443,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4692,6 +5464,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4713,6 +5488,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4767,15 +5551,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4794,9 +5587,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4806,6 +5608,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4839,6 +5644,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4857,6 +5665,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4866,6 +5677,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4875,18 +5752,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4980,6 +5869,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5019,6 +5911,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5032,12 +5927,27 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5095,15 +6005,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5116,9 +6029,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5203,12 +6113,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5224,15 +6152,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5248,6 +6185,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5257,9 +6197,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5282,6 +6231,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5312,6 +6264,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5342,6 +6312,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5372,7 +6345,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5417,12 +6393,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5453,6 +6447,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5468,9 +6465,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5513,9 +6522,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5531,6 +6546,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5561,6 +6585,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5634,16 +6661,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5655,6 +6682,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5739,9 +6769,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5761,6 +6797,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5818,15 +6857,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5857,15 +6917,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5890,46 +6950,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5953,10 +7010,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5965,9 +7022,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5977,12 +7040,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6052,16 +7124,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6121,13 +7196,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6169,6 +7247,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6214,6 +7298,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6232,6 +7319,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6241,6 +7331,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6274,9 +7367,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6286,6 +7385,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6298,6 +7403,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6353,13 +7464,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6368,6 +7479,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6383,6 +7497,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6404,6 +7521,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6416,19 +7536,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6458,7 +7587,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6476,15 +7605,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6533,9 +7671,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6551,10 +7686,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6668,6 +7809,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6680,6 +7824,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6687,6 +7837,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6696,6 +7849,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6732,6 +7891,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6759,15 +7921,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6786,9 +7963,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6810,6 +7984,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6834,18 +8014,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6855,6 +8038,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6864,6 +8050,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6882,6 +8071,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6897,21 +8089,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6960,6 +8155,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6975,6 +8176,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7017,10 +8224,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7035,6 +8239,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7062,9 +8269,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7089,15 +8308,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7122,6 +8371,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7131,18 +8383,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7170,6 +8437,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7320,21 +8590,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7434,6 +8704,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7449,16 +8728,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7467,6 +8755,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7476,6 +8767,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7485,6 +8779,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7575,12 +8875,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7629,6 +8938,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7731,6 +9043,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7797,6 +9112,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7809,6 +9130,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7839,6 +9166,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7854,6 +9184,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7891,6 +9227,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7936,9 +9275,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7948,12 +9296,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7972,6 +9341,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8005,6 +9377,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8023,6 +9407,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8035,6 +9425,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8071,6 +9464,23 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
msgstr ""
@@ -8104,9 +9514,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8131,12 +9550,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8171,12 +9596,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8198,6 +9632,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8231,25 +9671,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8261,6 +9701,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8285,6 +9728,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8294,15 +9740,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8354,6 +9809,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8381,6 +9839,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8402,9 +9869,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8420,10 +9884,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8474,6 +9938,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8483,9 +9956,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8516,10 +9986,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8537,18 +10010,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8564,9 +10058,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8594,9 +10085,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8612,6 +10112,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8621,6 +10124,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8636,6 +10151,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8693,9 +10211,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8705,6 +10229,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8766,6 +10293,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8775,6 +10308,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8796,6 +10332,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8841,6 +10398,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8868,6 +10428,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9018,6 +10581,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9066,15 +10632,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9108,6 +10686,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9261,6 +10842,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9270,15 +10872,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9288,6 +10899,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9411,12 +11025,25 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9435,6 +11062,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9447,15 +11080,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9465,6 +11146,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9492,9 +11185,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9513,6 +11215,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9528,9 +11245,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9585,9 +11311,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9603,6 +11341,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9621,6 +11362,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9633,6 +11377,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9654,6 +11413,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9669,6 +11431,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9717,6 +11482,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9738,10 +11509,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9750,7 +11527,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9774,6 +11551,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9783,10 +11563,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9801,6 +11581,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9867,9 +11650,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10103,6 +11883,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10142,6 +11925,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10166,6 +11955,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10178,6 +11970,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10187,6 +11982,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10211,6 +12018,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10220,15 +12030,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10241,6 +12063,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10301,21 +12126,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10331,6 +12174,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10397,18 +12255,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10487,18 +12381,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10523,9 +12438,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10535,7 +12447,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10547,12 +12459,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10586,6 +12501,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10622,12 +12540,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10676,6 +12600,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10694,9 +12624,19 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
msgstr ""
@@ -10709,6 +12649,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10820,6 +12763,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10838,6 +12784,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10850,12 +12799,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10886,6 +12847,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10901,6 +12865,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10910,6 +12883,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10919,6 +12898,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10928,12 +12910,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10952,12 +12952,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10970,7 +12985,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10982,12 +12997,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11015,6 +13042,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11048,6 +13084,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11066,9 +13111,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11084,15 +13135,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11102,12 +13168,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11132,6 +13201,20 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11139,6 +13222,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11146,6 +13232,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11181,6 +13276,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11190,6 +13288,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11199,12 +13306,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11247,9 +13366,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11297,9 +13425,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11321,6 +13446,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11339,9 +13467,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11355,10 +13492,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11389,6 +13547,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11424,22 +13585,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11506,6 +13676,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11528,6 +13701,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11564,6 +13740,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11636,16 +13815,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11701,6 +13883,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11817,12 +14002,22 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11858,9 +14053,36 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11885,6 +14107,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/hi_IN/gitlab.po b/locale/hi_IN/gitlab.po
index 8a3603ef135..b7ea475b90a 100644
--- a/locale/hi_IN/gitlab.po
+++ b/locale/hi_IN/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: hi\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:15\n"
+"PO-Revision-Date: 2019-04-12 12:34\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/hr_HR/gitlab.po b/locale/hr_HR/gitlab.po
index 5267aa42b8d..5fe10132d71 100644
--- a/locale/hr_HR/gitlab.po
+++ b/locale/hr_HR/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: hr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:13\n"
+"PO-Revision-Date: 2019-04-12 12:37\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -42,8 +51,11 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -141,12 +153,27 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -165,7 +192,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -174,6 +201,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -183,6 +216,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -195,6 +231,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -240,6 +285,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -249,6 +297,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -273,6 +324,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -285,6 +342,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -327,6 +387,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -336,6 +399,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -351,6 +423,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -384,7 +459,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -396,18 +483,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -438,12 +546,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -453,6 +567,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -483,34 +603,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -534,18 +681,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -582,6 +738,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -663,12 +822,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -681,6 +849,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -690,15 +861,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -711,6 +900,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -729,6 +924,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -750,9 +948,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -762,6 +957,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -774,18 +978,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -798,6 +1011,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -810,6 +1026,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -825,6 +1044,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -867,12 +1089,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -930,6 +1158,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -942,15 +1173,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -963,6 +1212,48 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -978,15 +1269,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -999,15 +1305,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1020,6 +1335,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1074,9 +1392,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1092,6 +1407,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1107,10 +1425,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1119,6 +1437,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1137,6 +1461,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1176,16 +1503,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1311,6 +1632,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1548,6 +1875,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1569,9 +1902,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1590,6 +1932,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1623,6 +1968,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1638,6 +1986,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1653,6 +2004,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1665,19 +2019,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1704,6 +2073,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1716,6 +2088,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1743,9 +2121,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1767,25 +2142,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1812,6 +2187,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1845,6 +2223,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1860,6 +2241,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1872,6 +2256,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1881,12 +2268,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1944,6 +2337,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1959,9 +2355,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1977,15 +2370,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2001,16 +2394,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2049,6 +2439,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2058,19 +2454,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2118,7 +2514,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2145,13 +2541,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2160,15 +2556,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2196,7 +2595,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2208,9 +2607,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2253,9 +2649,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2268,10 +2661,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2280,15 +2673,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2319,7 +2709,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2355,16 +2745,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2376,7 +2766,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2385,10 +2775,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2427,9 +2823,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2439,6 +2832,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2448,15 +2844,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2553,6 +2961,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2622,6 +3033,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2637,12 +3054,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2706,6 +3135,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2733,18 +3165,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2757,6 +3180,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2781,9 +3207,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2799,9 +3222,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2811,10 +3258,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2826,6 +3273,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2850,9 +3300,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2922,6 +3369,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2961,9 +3411,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3006,6 +3453,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3030,15 +3480,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3078,6 +3537,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3090,6 +3552,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3102,6 +3579,9 @@ msgstr[2] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3240,12 +3720,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3324,6 +3837,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3363,6 +3879,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3408,6 +3930,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3429,6 +3954,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3441,18 +3969,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3468,6 +4032,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3495,6 +4062,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3504,9 +4077,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3522,16 +4092,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3549,13 +4131,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3576,6 +4152,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3627,15 +4209,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3648,6 +4248,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3699,6 +4311,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3747,6 +4362,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3762,12 +4389,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3831,12 +4494,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3846,12 +4503,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3885,6 +4548,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3903,9 +4572,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3927,13 +4593,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3945,9 +4617,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3960,12 +4647,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3999,9 +4692,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4059,7 +4749,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4086,6 +4776,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4149,9 +4851,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4167,6 +4866,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4260,13 +4962,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4296,12 +5004,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4311,6 +5028,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4335,9 +5055,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4356,6 +5073,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4401,6 +5121,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4509,9 +5232,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4527,6 +5265,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4557,6 +5298,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4596,24 +5343,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4632,9 +5400,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4656,6 +5421,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4677,6 +5445,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4692,12 +5463,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4731,15 +5508,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4758,9 +5544,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4770,6 +5565,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4803,6 +5601,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4821,6 +5622,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4830,6 +5634,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4839,18 +5709,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4944,6 +5826,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4983,6 +5868,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4995,12 +5883,27 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5058,15 +5961,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5079,9 +5985,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5166,12 +6069,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5187,15 +6108,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5211,6 +6141,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5220,9 +6153,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5244,6 +6186,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5274,6 +6219,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5292,6 +6252,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5304,6 +6267,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5334,7 +6300,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5379,12 +6348,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5415,6 +6402,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5430,9 +6420,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5475,9 +6477,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5493,6 +6501,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5523,6 +6540,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5595,16 +6615,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5616,6 +6636,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5700,9 +6723,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5721,6 +6750,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5778,15 +6810,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5817,15 +6870,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5850,46 +6903,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5913,10 +6963,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5925,9 +6975,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5937,12 +6993,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6012,16 +7077,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6081,13 +7149,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6129,6 +7200,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6174,6 +7251,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6192,6 +7272,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6201,6 +7284,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6234,9 +7320,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6246,6 +7338,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6258,6 +7356,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6312,13 +7416,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6327,6 +7431,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6342,6 +7449,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6363,6 +7473,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6375,19 +7488,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6417,7 +7539,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6435,15 +7557,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6492,9 +7623,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6510,10 +7638,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6627,6 +7761,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6639,12 +7776,21 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6654,6 +7800,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6690,6 +7842,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6717,15 +7872,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6744,9 +7914,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6768,6 +7935,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6792,18 +7965,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6813,6 +7989,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6822,6 +8001,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6840,6 +8022,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6855,21 +8040,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6918,6 +8106,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6933,6 +8127,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6975,10 +8175,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6993,6 +8190,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7020,9 +8220,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7047,15 +8259,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7065,9 +8295,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7080,6 +8322,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7089,18 +8334,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7128,6 +8388,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7278,21 +8541,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7392,6 +8655,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7407,16 +8679,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7425,6 +8706,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7434,6 +8718,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7443,6 +8730,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7533,12 +8826,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7587,6 +8889,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7689,6 +8994,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7755,6 +9063,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7767,6 +9081,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7797,6 +9117,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7812,6 +9135,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7848,6 +9177,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7893,9 +9225,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7905,12 +9246,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7929,6 +9291,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7962,6 +9327,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7980,6 +9357,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7992,6 +9375,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8028,6 +9414,21 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "Resend invite"
msgstr ""
@@ -8061,9 +9462,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8088,12 +9498,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8127,12 +9543,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8154,6 +9579,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8187,25 +9618,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8217,6 +9648,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8241,6 +9675,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8250,15 +9687,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8310,6 +9756,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8337,6 +9786,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8358,9 +9816,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8376,10 +9831,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8430,6 +9885,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8439,9 +9903,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8472,10 +9933,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8493,18 +9957,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8520,9 +10005,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8550,9 +10032,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8568,6 +10059,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8577,6 +10071,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8592,6 +10098,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8649,9 +10158,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8661,6 +10176,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8721,6 +10239,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8730,6 +10254,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8751,6 +10278,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8796,6 +10344,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8823,6 +10374,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8973,6 +10527,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9021,15 +10578,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9063,6 +10632,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9216,6 +10788,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9225,15 +10818,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9243,6 +10845,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9366,12 +10971,24 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9390,6 +11007,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9402,15 +11025,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9420,6 +11091,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9447,9 +11130,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9468,6 +11160,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9483,9 +11190,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9540,9 +11256,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9558,6 +11286,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9576,6 +11307,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9588,6 +11322,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9609,6 +11358,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9624,6 +11376,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9672,6 +11427,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9693,10 +11454,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9705,7 +11472,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9729,6 +11496,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9738,10 +11508,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9756,6 +11526,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9822,9 +11595,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10056,6 +11826,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10095,6 +11868,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10119,6 +11898,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10131,6 +11913,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10140,6 +11925,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10164,6 +11961,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10173,15 +11973,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10194,6 +12006,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10254,21 +12069,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10284,6 +12117,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10308,18 +12144,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10350,18 +12198,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10440,18 +12324,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10476,9 +12381,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10488,7 +12390,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10500,12 +12402,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10539,6 +12444,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10575,12 +12483,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10629,6 +12543,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10647,9 +12567,18 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "When:"
msgstr ""
@@ -10662,6 +12591,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10773,6 +12705,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10791,6 +12726,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10803,12 +12741,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10839,6 +12789,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10854,6 +12807,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10863,6 +12825,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10872,6 +12840,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10881,12 +12852,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10905,12 +12894,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10923,7 +12927,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10935,12 +12939,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10968,6 +12984,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11001,6 +13026,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11019,9 +13053,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11037,15 +13077,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11055,12 +13110,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11085,18 +13143,42 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11130,6 +13212,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11139,6 +13224,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11148,12 +13242,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11196,9 +13302,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11244,9 +13359,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11268,6 +13380,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11286,9 +13401,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11301,10 +13425,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11334,6 +13479,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11367,22 +13515,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11448,6 +13605,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11469,6 +13629,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11505,6 +13668,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11577,16 +13743,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11640,6 +13809,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11754,12 +13926,21 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11793,9 +13974,36 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11820,6 +14028,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/hu_HU/gitlab.po b/locale/hu_HU/gitlab.po
index bc8e95021c1..b702cb701c6 100644
--- a/locale/hu_HU/gitlab.po
+++ b/locale/hu_HU/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: hu\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:14\n"
+"PO-Revision-Date: 2019-04-12 12:31\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index 79a8b5aa073..e3f45e5a4f1 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: id\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:11\n"
+"PO-Revision-Date: 2019-04-12 12:34\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -38,8 +47,9 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -111,12 +121,25 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -131,7 +154,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -140,6 +163,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -149,6 +178,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -161,6 +193,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -198,6 +239,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -207,6 +251,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -263,6 +317,9 @@ msgid "1 user"
msgid_plural "%d users"
msgstr[0] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -272,6 +329,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -287,6 +353,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -320,7 +389,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -332,18 +413,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -374,12 +476,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -389,6 +497,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -419,34 +533,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -470,18 +611,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -518,6 +668,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -599,12 +752,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -615,6 +777,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -624,15 +789,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -645,6 +828,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -663,6 +852,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -684,9 +876,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -696,6 +885,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -708,18 +906,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -732,6 +939,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -744,6 +954,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -759,6 +972,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -801,12 +1017,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -864,6 +1086,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -876,15 +1101,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -897,6 +1140,40 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -912,15 +1189,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -933,15 +1225,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -954,6 +1255,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1008,9 +1312,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1026,6 +1327,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1041,10 +1345,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1053,6 +1357,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1071,6 +1381,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1110,16 +1423,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1245,6 +1552,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1482,6 +1795,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1503,9 +1822,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1524,6 +1852,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1557,6 +1888,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1572,6 +1906,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1587,6 +1924,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1599,19 +1939,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1638,6 +1993,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1650,6 +2008,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1677,9 +2041,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1701,25 +2062,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1746,6 +2107,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1779,6 +2143,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1794,6 +2161,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1806,6 +2176,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1815,12 +2188,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1878,6 +2257,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1893,9 +2275,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1911,15 +2290,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1935,16 +2314,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -1983,6 +2359,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1992,19 +2374,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2052,7 +2434,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2079,13 +2461,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2094,15 +2476,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2130,7 +2515,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2142,9 +2527,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2187,9 +2569,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2202,10 +2581,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2214,15 +2593,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2253,7 +2629,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2289,16 +2665,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2310,7 +2686,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2319,10 +2695,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2361,9 +2743,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2373,6 +2752,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2382,15 +2764,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2485,6 +2879,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2554,6 +2951,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2569,12 +2972,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2638,6 +3053,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2665,18 +3083,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2689,6 +3098,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2713,9 +3125,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2731,9 +3140,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2743,10 +3176,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2758,6 +3191,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2782,9 +3218,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2854,6 +3287,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2893,9 +3329,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2938,6 +3371,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2962,15 +3398,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3010,6 +3455,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3022,6 +3470,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3032,6 +3495,9 @@ msgstr[0] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3170,12 +3636,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3254,6 +3753,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3293,6 +3795,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3338,6 +3846,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3359,6 +3870,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3371,18 +3885,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3398,6 +3948,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3425,6 +3978,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3434,9 +3993,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3452,16 +4008,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3479,13 +4047,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3506,6 +4068,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3557,15 +4125,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3578,6 +4164,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3629,6 +4227,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3677,6 +4278,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3692,12 +4305,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3761,12 +4410,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3776,12 +4419,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3815,6 +4464,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3833,9 +4488,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3857,13 +4509,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3875,9 +4533,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3890,12 +4563,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3929,9 +4608,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -3989,7 +4665,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4016,6 +4692,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4077,9 +4765,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4095,6 +4780,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4188,13 +4876,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4224,12 +4918,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4239,6 +4942,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4263,9 +4969,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4284,6 +4987,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4329,6 +5035,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4437,9 +5146,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4455,6 +5179,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4485,6 +5212,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4524,24 +5257,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4560,9 +5314,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4584,6 +5335,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4605,6 +5359,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4620,12 +5377,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4659,15 +5422,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4686,9 +5458,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4698,6 +5479,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4731,6 +5515,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4749,6 +5536,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4758,6 +5548,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4767,18 +5623,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4872,6 +5740,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4911,6 +5782,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4921,12 +5795,27 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4984,15 +5873,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5005,9 +5897,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5092,12 +5981,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5113,15 +6020,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5137,6 +6053,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5146,9 +6065,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5168,6 +6096,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5198,6 +6129,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5216,6 +6162,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5228,6 +6177,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5258,7 +6210,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5303,12 +6258,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5339,6 +6312,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5354,9 +6330,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5399,9 +6387,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5417,6 +6411,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5447,6 +6450,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5517,16 +6523,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5538,6 +6544,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5622,9 +6631,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5641,6 +6656,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5698,15 +6716,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5737,15 +6776,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5770,46 +6809,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5833,10 +6869,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5845,9 +6881,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5857,12 +6899,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5932,16 +6983,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6001,13 +7055,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6049,6 +7106,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6094,6 +7157,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6112,6 +7178,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6121,6 +7190,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6154,9 +7226,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6166,6 +7244,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6178,6 +7262,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6230,13 +7320,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6245,6 +7335,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6260,6 +7353,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6281,6 +7377,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6293,19 +7392,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6335,7 +7443,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6353,15 +7461,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6410,9 +7527,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6428,10 +7542,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6545,6 +7665,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6557,10 +7680,19 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6570,6 +7702,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6606,6 +7744,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6633,15 +7774,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6660,9 +7816,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6684,6 +7837,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6708,18 +7867,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6729,6 +7891,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6738,6 +7903,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6756,6 +7924,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6771,21 +7942,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6834,6 +8008,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6849,6 +8029,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6891,10 +8077,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6909,6 +8092,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6936,9 +8122,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -6963,15 +8161,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -6981,9 +8197,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -6996,6 +8224,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7005,18 +8236,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7044,6 +8290,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7194,21 +8443,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7308,6 +8557,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7323,16 +8581,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7341,6 +8608,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7350,6 +8620,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7359,6 +8632,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7449,12 +8728,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7503,6 +8791,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7605,6 +8896,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7671,6 +8965,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7683,6 +8983,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7713,6 +9019,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7728,6 +9037,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7762,6 +9077,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7807,9 +9125,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7819,12 +9146,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7843,6 +9191,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7876,6 +9227,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7894,6 +9257,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7906,6 +9275,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7942,6 +9314,17 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
msgid "Resend invite"
msgstr ""
@@ -7975,9 +9358,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8002,12 +9394,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8039,12 +9437,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8066,6 +9473,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8099,25 +9512,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8129,6 +9542,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8153,6 +9569,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8162,15 +9581,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8222,6 +9650,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8249,6 +9680,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8270,9 +9710,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8288,10 +9725,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8342,6 +9779,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8351,9 +9797,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8384,10 +9827,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8405,18 +9851,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8432,9 +9899,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8462,9 +9926,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8480,6 +9953,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8489,6 +9965,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8504,6 +9992,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8561,9 +10052,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8573,6 +10070,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8631,6 +10131,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8640,6 +10146,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8661,6 +10170,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8706,6 +10236,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8733,6 +10266,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8883,6 +10419,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8931,15 +10470,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -8973,6 +10524,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9126,6 +10680,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9135,15 +10710,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9153,6 +10737,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9276,12 +10863,22 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9300,6 +10897,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9312,15 +10915,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9330,6 +10981,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9357,9 +11020,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9378,6 +11050,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9393,9 +11080,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9450,9 +11146,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9468,6 +11176,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9486,6 +11197,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9498,6 +11212,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9519,6 +11248,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9534,6 +11266,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9582,6 +11317,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9603,10 +11344,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9615,7 +11362,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9639,6 +11386,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9648,10 +11398,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9666,6 +11416,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9732,9 +11485,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -9962,6 +11712,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10001,6 +11754,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10025,6 +11784,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10037,6 +11799,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10046,6 +11811,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10070,6 +11847,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10079,15 +11859,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10100,6 +11892,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10160,21 +11955,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10190,6 +12003,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10214,18 +12030,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10256,18 +12084,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10346,18 +12210,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10382,9 +12267,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10394,7 +12276,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10406,12 +12288,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10445,6 +12330,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10481,12 +12369,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10535,6 +12429,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10553,9 +12453,16 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
msgstr ""
@@ -10568,6 +12475,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10679,6 +12589,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10697,6 +12610,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10709,12 +12625,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10745,6 +12673,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10760,6 +12691,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10769,6 +12709,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10778,6 +12724,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10787,12 +12736,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10811,12 +12778,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10829,7 +12811,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10841,12 +12823,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10874,6 +12868,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10907,6 +12910,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10925,9 +12937,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10943,15 +12961,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -10961,12 +12994,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -10991,14 +13027,34 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11028,6 +13084,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11037,6 +13096,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11046,12 +13114,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11094,9 +13174,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11138,9 +13227,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11162,6 +13248,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11180,9 +13269,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11193,10 +13291,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11224,6 +13343,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11253,22 +13375,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11332,6 +13463,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11351,6 +13485,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11387,6 +13524,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11459,16 +13599,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11518,6 +13661,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11628,12 +13774,19 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11663,9 +13816,36 @@ msgid "reply"
msgid_plural "replies"
msgstr[0] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11690,6 +13870,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index a7dd46e61dd..3a8b10706bc 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:09\n"
+"PO-Revision-Date: 2019-04-12 12:28\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " Stato"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " e"
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" nei progetti"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr "%{count}%{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr "%{count} altri assegnatari"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} partecipante"
@@ -148,7 +173,7 @@ msgstr "%{filePath} eliminato"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} + %{labelCount} più"
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr "I %{group_docs_link_start}Gruppi%{group_docs_link_end} consentono di ges
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} sarà rimosso! Sei sicuro?"
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr "%{loadingIcon} Partito"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} è bloccato dall'utente GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Avatar di %{name}"
@@ -178,6 +212,15 @@ msgstr "%{openOrClose}%{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% completato"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} più"
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "Primo contributo!"
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "2FA abilitata"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,8 +424,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Un insieme di grafici riguardo la Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr "Token di accesso"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr "Account"
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Attivo"
@@ -451,36 +568,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr "Aggiungi una directory (cartella)"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
msgstr ""
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Impostazioni Avanzate"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr "Tutto"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr "Errore durante il recupero dei dati della barra laterale"
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Aspetto"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr "Apr"
msgid "April"
msgstr "Aprile"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Sei sicuro di voler cancellare questa pipeline programmata?"
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr "Sei sicuro di voler ripristinare il token di registrazione?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Confermi di voler resettare il token di controllo di stato?"
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,18 +1385,24 @@ msgstr "Ago"
msgid "August"
msgstr "Agosto"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Log di autenticazione"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Autore"
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr "Approfondisci: %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr "Impostazioni"
msgid "Branches|protected"
msgstr "Protetta"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Naviga direttori"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "per"
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr "Ripristina"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr "Grafici"
msgid "Chat"
msgstr "Chat"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr "superata con avvisi"
msgid "CiStatusLabel|pending"
msgstr "in coda"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "saltata"
@@ -1812,6 +2183,9 @@ msgstr "superata"
msgid "CiStatusText|pending"
msgstr "in coda"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "saltata"
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr "Clona repository"
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,20 +2414,20 @@ msgstr "Copia URL API"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Copia Certificato CA"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
-msgstr "Copia Token"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr ""
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr "Gitlab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingresso"
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr "Installa"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr "Installato"
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,27 +2621,24 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
-msgstr "Nota:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Numero di nodi"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr "Committato da "
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Confronta"
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr "Attendere prego, questa pagina si ricaricherà automaticamente appena pr
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr "Copia l'SHA del commit negli appunti"
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr "Crea una nuova cartella"
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Creare un token di accesso sul tuo account per eseguire pull o push tramite %{protocol}"
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr "Sintassi Cron"
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr "Tutti"
msgid "DashboardProjects|Personal"
msgstr "Personale"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] "Rilasci"
msgid "Deploy Keys"
msgstr "Chiavi di Deploy (rilascio)"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr "Descrizione"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,20 +3837,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "Scarica tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Scarica tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Scarica tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Scarica zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Scarica"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Email Patches"
@@ -3373,6 +3888,9 @@ msgstr "Modifica"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "E-mail"
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr "Errore durante il fetch degli ambienti."
msgid "Environments|An error occurred while making the request."
msgstr "Errore durante l'esecuzione della richiesta."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr "Leggi di più sugli ambienti"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Mostra tutti"
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Aggiornato"
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr "Errore durante l'attivazione/disattivazione della sottoscrizione per l'iscrizione"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr "Impossibile cambiare owner"
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr "Impossibile rimuovere la pipeline pianificata"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr "Feb"
msgid "February"
msgstr "Febbraio"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Trova in percorso"
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr "Formato"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "Chiavi GPG"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Blocca la condivisione di un progetto di %{group} con altri gruppi"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "Questa modifica è stata applicata su %{ancestor_group} ed è stata ignorata nel sottogruppo."
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "Cronologia"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Housekeeping iniziato con successo"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr "Importa repository"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr "Introduzione delle Analisi Cicliche"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr "Gennaio"
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "Lug"
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Disabilitato"
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr "documentazione sulla pianificazione delle pipelines"
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Abbandona il gruppo"
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr "Richieste di merge"
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Richiesta di merge"
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "aggiungi una chiave SSH"
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr "Ulteriori informazioni sono disponibili | qui"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,21 +7368,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Nuova pianificazione Pipeline"
msgid "New Snippet"
msgstr ""
-msgid "New Snippets"
-msgstr ""
-
msgid "New branch"
msgstr "Nuova Branch"
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Nuova directory"
@@ -6301,6 +7401,9 @@ msgstr "Nuovo file"
msgid "New group"
msgstr "Nuovo gruppo"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr "Nuovo progetto"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Nuova pianficazione"
@@ -6334,19 +7440,28 @@ msgstr "Nuovo sottogruppo"
msgid "New tag"
msgstr "Nuovo tag"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr "Nessuno"
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr "Non disponibile"
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr "Dati insufficienti "
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr "Novembre"
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "Ott"
@@ -6598,11 +7728,20 @@ msgstr "Filtra"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr "« Prima"
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr "Password"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Pipeline"
-msgid "Pipeline Health"
-msgstr "Stato della Pipeline"
-
msgid "Pipeline Schedule"
msgstr "Pianificazione Pipeline"
msgid "Pipeline Schedules"
msgstr "Pianificazione multipla Pipeline"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Fallita:"
@@ -6876,6 +8057,12 @@ msgstr "Variabili"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizzato"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Pipeline"
@@ -6891,6 +8078,12 @@ msgstr "Pipeline per la settimana scorsa"
msgid "Pipelines for last year"
msgstr "Pipeline per l'ultimo anno"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr "tutto"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "successo"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "con stadio"
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr "Preferenze"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr "Privato - L'accesso al progetto deve essere fornito esplicitamente ad og
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privato - Il gruppo e i suoi progetti possono essere visualizzati solo dai membri."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Il progetto '%{project_name}' è in fase di eliminazione."
@@ -7365,16 +8630,25 @@ msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo."
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente"
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr "Dettagli del progetto"
msgid "Project export could not be deleted."
msgstr "L'esportazione del progetto non può essere eliminata."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "L'esportazione del progetto è stata eliminata."
@@ -7392,6 +8669,9 @@ msgstr "Il link d'esportazione del progetto è scaduto. Genera una nuova esporta
msgid "Project export started. A download link will be sent by email."
msgstr "Esportazione del progetto iniziata. Un link di download sarà inviato via email."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr "Progetti"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "Visitati di frequente"
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr "Pubblico - il gruppo e tutti i progetti pubblici possono essere visualiz
msgid "Public - The project can be accessed without any authentication."
msgstr "Public - Chiunque può accedere a questo progetto senza alcuna autenticazione."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Vedi altro"
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr "Rimuovi progetto"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,27 +9565,27 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr ""
-
msgid "SSH Keys"
msgstr "Chiavi SSH"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr ""
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr "Salva pianificazione pipeline"
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Pianifica una nuova Pipeline"
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Pianificazione pipelines"
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr "Seleziona una timezone"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr "Set"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "Settembre"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr "Snippet"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "inizia una %{new_merge_request} con queste modifiche"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Cambia branch/tag"
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio."
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "La relazione del fork è stata rimossa"
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la creazione di problemi per visualizzare i dati in questo stadio."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr "Qualunque utente autenticato può accedere a questo progetto."
msgid "The project can be accessed without any authentication."
msgstr "Chiunque può accedere a questo progetto (senza alcuna autenticazione)."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "La repository di questo progetto non esiste."
@@ -9423,6 +11105,21 @@ msgstr "Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta di Merge) completata al suo rilascio in ambiente di produzione. Questa informazione sarà disponibile dal tuo primo rilascio in produzione"
@@ -9438,9 +11135,18 @@ msgstr "Il tempo aggregato relativo eventi/data entry raccolto in quello stadio.
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr "Questo significa che non è possibile effettuare push di codice fino a c
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr "%s mesi fa"
msgid "Timeago|%s months remaining"
msgstr "%s mesi rimanenti"
-msgid "Timeago|%s seconds ago"
-msgstr "%s secondi fa"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s secondi rimanenti"
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "clicca per caricare"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "Ritira richiesta d'accesso"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr "Stai per rimuovere la relazione con il progetto sorgente %{forked_from_p
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Puoi aggiungere files solo quando sei in una branch"
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Hai raggiunto il tuo limite di progetto"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "Necessiti del permesso."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "Non riceverai alcuna notifica via email"
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Il tuo nome"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "fa"
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 8998c660e18..ed9037c9503 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 12:12\n"
+"PO-Revision-Date: 2019-04-12 12:29\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " ステータス"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " ã¨"
@@ -30,16 +39,17 @@ msgid_plural " improved on %d points"
msgstr[0] " %dãƒã‚¤ãƒ³ãƒˆã§æ”¹å–„"
msgid " or "
-msgstr ""
+msgstr " ã¾ãŸã¯ "
msgid " or <#epic id>"
-msgstr ""
+msgstr " ã¾ãŸã¯ <#エピックID>"
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr "プロジェクト内㮠\"%{query}\""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d 件ã®ã‚³ãƒ¡ãƒ³ãƒˆ"
msgid "%d commit"
msgid_plural "%d commits"
@@ -50,7 +60,7 @@ msgid_plural "%d commits behind"
msgstr[0] "%d個ã®ã‚³ãƒŸãƒƒãƒˆå¾…ã¡"
msgid "%d commits"
-msgstr ""
+msgstr "%d個ã®ã‚³ãƒŸãƒƒãƒˆ"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -58,11 +68,11 @@ msgstr[0] "%d exporter"
msgid "%d failed test result"
msgid_plural "%d failed test results"
-msgstr[0] ""
+msgstr[0] "%d件ã®ãƒ†ã‚¹ãƒˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
msgid "%d fixed test result"
msgid_plural "%d fixed test results"
-msgstr[0] ""
+msgstr[0] "%d 件ã®ãƒ†ã‚¹ãƒˆã§ä¿®æ­£ã•ã‚Œã¾ã—ãŸ"
msgid "%d issue"
msgid_plural "%d issues"
@@ -70,7 +80,7 @@ msgstr[0] "%d個ã®èª²é¡Œ"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
+msgstr[0] "%d個ã®èª²é¡Œã‚’é¸æŠžæ¸ˆã¿"
msgid "%d layer"
msgid_plural "%d layers"
@@ -111,10 +121,23 @@ msgstr "%{counter_storage} (%{counter_repositories} リãƒã‚¸ãƒˆãƒªã€%{counter_
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "%{name} ㌠%{count} 件ã®æ‰¿èªã‚’è¦æ±‚ã—ã¦ã„ã¾ã™"
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{name} ㌠%{count} 件ã®æ‰¿èªã‚’了承ã—ã¾ã—ãŸ"
+
msgid "%{count} more"
-msgstr ""
+msgstr "%{count} 以上"
msgid "%{count} more assignees"
+msgstr "%{count} 人以上ã®æ‹…当者"
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{name} ã‹ã‚‰ã®æ‰¿èª %{required} 中 %{count}"
+
+msgid "%{count} of %{total}"
msgstr ""
msgid "%{count} participant"
@@ -131,8 +154,8 @@ msgstr "%{filePath} ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} ã®è©³ç´°"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} ã¯ã“ã®ã‚³ãƒŸãƒƒãƒˆã«è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}グループ%{group_docs_link_end}を使用ã™ã‚‹ã¨ã€è¤‡æ•°ã®ãƒ—ロジェクトを管ç†ã—ã¦å…±åŒä½œæ¥­ã‚’è¡Œã†ã“ã¨ãŒã§ãã¾ã™ã€‚グループã®ãƒ¡ãƒ³ãƒãƒ¼ã¯ã€æ‰€å±žã™ã‚‹ãƒ—ロジェクトã®ã™ã¹ã¦ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚"
@@ -140,15 +163,24 @@ msgstr "%{group_docs_link_start}グループ%{group_docs_link_end}を使用ã™ã‚
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType}を削除ã—ã¾ã™ï¼ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} ã¯åˆ©ç”¨ä¸å¯"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "ロールã®æ¨©é™ã«ã¤ã„ã¦%{link_start}ã‚‚ã£ã¨èª­ã‚€%{link_end}"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} 開始"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} ã¯GitLab ユーザー %{lock_user_id} ã«ã‚ˆã£ã¦ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "%{name}ã®ã‚¢ãƒã‚¿ãƒ¼"
@@ -161,23 +193,32 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% 完了"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
msgstr ""
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{state} epics"
+msgstr "%{state}エピック"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} ブランãƒ"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} コミット"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} ファイル"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} ã‚¿ã‚°"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -196,19 +237,25 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "GitLab Inc. ã¨ã©ã®ã‚ˆã†ãªæƒ…報を共有ã™ã‚‹ã‹ã«ã¤ã„ã¦ã¯ %{usage_ping_link_start} ã“ã¡ã‚‰%{usage_ping_link_end} ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
msgid "%{user_name} profile page"
+msgstr "%{user_name} プロフィールページ"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(外部ソース)"
msgid "+ %{count} more"
-msgstr ""
+msgstr "%{count} 以上"
msgid "+ %{moreCount} more"
msgstr "+ 他 %{moreCount} 件"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} 以上"
+
msgid ", or "
-msgstr ""
+msgstr "ã€ã¾ãŸã¯"
msgid "- Runner is active and can process any new jobs"
msgstr "- RunnerãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–ã§æ–°ã—ã„ジョブを処ç†ã§ãã¾ã™"
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] "%{count} 件 %{type} ã®ä¿®æ­£"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "%d æ—¥"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "%d件ã®ã‚¯ãƒ­ãƒ¼ã‚ºã•ã‚ŒãŸèª²é¡Œ"
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] "%d件ã®ã‚¯ãƒ­ãƒ¼ã‚ºã•ã‚ŒãŸãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "%dグループ"
@@ -257,24 +311,36 @@ msgstr[0] "%d 個ã®ãƒ‘イプライン"
msgid "1 role"
msgid_plural "%d roles"
-msgstr[0] ""
+msgstr[0] "役割 %d 件"
msgid "1 user"
msgid_plural "%d users"
msgstr[0] "%dユーザー"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "最åˆã®è²¢çŒ®!"
msgid "2FA"
-msgstr ""
+msgstr "2FA"
msgid "2FA enabled"
msgstr "2段階èªè¨¼ãŒæœ‰åŠ¹"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
msgstr ""
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã®é–²è¦§ã«ã¯æ¨©é™ãŒå¿…è¦ã§ã™ã€‚GitLab 管ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。"
+
msgid "403|You don't have the permission to access this page."
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。"
@@ -287,6 +353,9 @@ msgstr "ページãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ãŒæ­£ã—ããªã„å ´åˆã€GitLab 管ç†è€…ã«é€£çµ¡ã—ã¦ãã ã•ã„。"
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> 㯠johnsmith@example.com ã«ã‚ˆã‚‹å…¨ã¦ã®èª²é¡Œã¨ã‚³ãƒ¡ãƒ³ãƒˆã« \"By <a href=\"#\">@johnsmith</a>\" を追加ã—ã¾ã™ã€‚ã¾ãŸã€ <a href=\"#\">@johnsmith</a> を元々 johnsmith@example.com ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ãŸå…¨ã¦ã®èª²é¡Œã®æ‹…当者ã¨ã—ã¦è¨­å®šã—ã¾ã™ã€‚"
@@ -300,7 +369,7 @@ msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will ad
msgstr "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> ã¯ã€johnsmith@example.com ãŒä½œæˆã—ãŸå…¨ã¦ã®èª²é¡Œã¨ã‚³ãƒ¡ãƒ³ãƒˆã« \"By <a href=\"#\">johnsmith@example.com</a>\" を追加ã—ã¾ã™ã€‚デフォルトã§ã€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚„ユーザーåã‚’éš ã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ライãƒã‚·ãƒ¼ã‚’ä¿è­·ã•ã‚Œã¾ã™ã€‚メールアドレスを全ã¦è¡¨ç¤ºã—ãŸã„å ´åˆã€ã“ã®æ–¹æ³•ã‚’指定ã—ã¦ãã ã•ã„。"
msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
-msgstr ""
+msgstr "<strong>ステージã•ã‚Œã¦ã„ãªã„変更 %{changedFilesLength} </strong> ã¨<strong>ステージã•ã‚ŒãŸå¤‰æ›´ %{stagedFilesLength} </strong>"
msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
msgstr "<strong>%{created_count}</strong> を作æˆã€<strong>%{accepted_count}</strong> 個をå—ã‘入れã¾ã—ãŸã€‚"
@@ -315,16 +384,28 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong>回ã®ãƒ—ッシュã€<strong>%{commits}</strong>回以上ã®ã‚³ãƒŸãƒƒãƒˆãŒè²¢çŒ®è€…<strong>%{people}</strong>ã«ã‚ˆã£ã¦è¡Œã‚ã‚Œã¾ã—ãŸã€‚"
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "ソースブランãƒã‚’<strong>削除</strong>"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "「Runnerã€ã¯ã‚¸ãƒ§ãƒ–を実行ã™ã‚‹ãƒ—ロセスã§ã™ã€‚å¿…è¦ãªæ•°ã® Runner ã‚’ä»»æ„ã«ã‚»ãƒƒãƒˆã‚¢ãƒƒãƒ—ã§ãã¾ã™ã€‚"
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "CIã«ã¤ã„ã¦ã®ã‚°ãƒ©ãƒ•"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„ã‚‹ GitBook サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„ã‚‹ Hexo サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„ã‚‹ Hugo サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„ã‚‹ Jekyll サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
msgid "A default branch cannot be chosen for an empty project."
-msgstr ""
+msgstr "プロジェクトãŒç©ºã®å ´åˆã¯ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ãƒ–ランãƒã‚’é¸æŠžã§ãã¾ã›ã‚“。"
msgid "A deleted user"
msgstr "削除ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
@@ -332,18 +413,39 @@ msgstr "削除ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "ãŸã ã¡ã«é–‹ç™ºãƒ¡ãƒ³ãƒãƒ¼ã§ã„ãŸã ã„ãŸãƒ¬ãƒãƒ¼ãƒˆã‚’æ‹èª­ã—å‚考ã«ã•ã›ã¦ã„ãŸã ãã¾ã™ã€‚"
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "フォークã«æ–°ã—ã„ブランãƒãŒä½œæˆã•ã‚Œã€æ–°ã—ã„マージリクエストãŒé–‹å§‹ã—ã¾ã™ã€‚"
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab ã®ä»£ã‚ã‚Šã« Netlify for CI/CD を使用ã—ã¦ã„るプレーン HTML サイトã§ã™ãŒã€GitLab ã«ã¯ãªã„優れãŸæ©Ÿèƒ½ã‚‚å‚™ãˆã¦ã„ã¾ã™ã€‚"
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "プロジェクトã¨ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã‚’æ ¼ç´(リãƒã‚¸ãƒˆãƒª) ã—ã€è¨ˆç”»ã‚’ç«‹ã¦(課題)ã€ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’公開(wiki) ã™ã‚‹å ´æ‰€ã§ã™ã€‚ %{among_other_things_link}"
+msgid "A ready-to-go template for use with Android apps."
+msgstr "Android アプリã§ã™ãã«ä½¿ãˆã‚‹ãƒ†ãƒ³ãƒ—レート。"
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "iOS Swift アプリã§ã™ãã«ä½¿ãˆã‚‹ãƒ†ãƒ³ãƒ—レート。"
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "ジョブトレースã§ãƒ†ã‚¹ãƒˆã‚«ãƒãƒ¬ãƒƒã‚¸ã®çµæžœã‚’見ã¤ã‘ã‚‹ãŸã‚ã«ä½¿ç”¨ã•ã‚Œã‚‹æ­£è¦è¡¨ç¾ã§ã™ã€‚無効ã«ã™ã‚‹å ´åˆã¯ç©ºç™½ã®ã¾ã¾ã«ã—ã¾ã™ã€‚"
msgid "A user with write access to the source branch selected this option"
msgstr "ã“ã®ã‚ªãƒ—ションをé¸æŠžã—ãŸã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¸ã®æ›¸ãè¾¼ã¿ã‚’許å¯ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "GitLab ã«ã¤ã„ã¦"
@@ -363,7 +465,7 @@ msgid "Abuse reports"
msgstr "迷惑行為レãƒãƒ¼ãƒˆ"
msgid "Accept invitation"
-msgstr ""
+msgstr "招待をå—ã‘入れる"
msgid "Accept terms"
msgstr "利用è¦ç´„ã«åŒæ„ã™ã‚‹"
@@ -374,12 +476,18 @@ msgstr "承èªã•ã‚ŒãŸãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
msgid "Access Tokens"
msgstr "アクセス トークン"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "アクセスãŒæ‹’å¦ã•ã‚Œã¾ã—ãŸï¼ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«ãƒ‡ãƒ—ロイキーを追加ã§ãã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。"
msgid "Access expiration date"
msgstr "アクセス有効期é™"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "'%{classification_label}'ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã¯è¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚“"
@@ -389,6 +497,12 @@ msgstr "アカウント"
msgid "Account and limit"
msgstr "アカウントã¨åˆ¶é™"
+msgid "Action to take when receiving an alert."
+msgstr "アラートをå—ä¿¡ã—ãŸéš›ã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³"
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "有効"
@@ -405,7 +519,7 @@ msgid "Add CHANGELOG"
msgstr ""
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "CONTRIBUTINGを追加"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "グループ Webhooks 㨠GitLab ã®ã‚¨ãƒ³ã‚¿ãƒ¼ãƒ—ライズ版を追加ã—ã¾ã™ã€‚"
@@ -417,43 +531,70 @@ msgid "Add Kubernetes cluster"
msgstr "Kubernetes クラスターを追加"
msgid "Add README"
-msgstr ""
+msgstr "README を追加"
+
+msgid "Add a bullet list"
+msgstr "箇æ¡æ›¸ãリストを追加"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "ã“ã® %{noteable_name} ã«ã¤ã„ã¦å¤§ã¾ã‹ãªã‚³ãƒ¡ãƒ³ãƒˆã‚’追加"
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "ã‚ãªãŸã® Wiki ã«ãƒ—ロジェクトã«é–¢ã™ã‚‹æƒ…報をå«ã‚€ãƒ›ãƒ¼ãƒ ãƒšãƒ¼ã‚¸ã‚’追加ã™ã‚‹ã¨ã€GitLab ã¯ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®ä»£ã‚ã‚Šã«ãれをã“ã“ã«è¡¨ç¤ºã—ã¾ã™ã€‚"
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr "番å·ä»˜ãリストを追加"
+
msgid "Add a table"
msgstr "テーブルを追加ã™ã‚‹"
+msgid "Add a task list"
+msgstr "タスクリストを追加"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "ã™ã¹ã¦ã®ãƒ¡ãƒ¼ãƒ«ã«è¡¨ç¤ºã™ã‚‹ãƒ†ã‚­ã‚¹ãƒˆã‚’追加ã—ã¾ã™ã€‚ ãŸã ã—ã€%{character_limit} 文字ã®åˆ¶é™ãŒã‚ã‚Šã¾ã™ã€‚"
+msgid "Add approver(s)"
+msgstr "承èªè€…を追加"
+
+msgid "Add approvers"
+msgstr "承èªè€…を追加"
+
+msgid "Add bold text"
+msgstr "太字ã®ãƒ†ã‚­ã‚¹ãƒˆã‚’追加"
+
msgid "Add comment now"
-msgstr ""
+msgstr "コメントã™ã‚‹"
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "メールã«ãƒ˜ãƒƒãƒ€ãƒ¼ã¨ãƒ•ãƒƒã‚¿ãƒ¼ã‚’追加ã—ã¾ã™ã€‚色設定ã¯ã‚¢ãƒ—リケーションインターフェース内ã§ã®ã¿é©ç”¨ã•ã‚Œã¾ã™ã€‚"
msgid "Add image comment"
msgstr "ç”»åƒã‚³ãƒ¡ãƒ³ãƒˆã‚’追加"
+msgid "Add italic text"
+msgstr "斜体ã®ãƒ†ã‚­ã‚¹ãƒˆã‚’追加"
+
msgid "Add license"
msgstr "ライセンスを追加"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "æ–°ã—ã„アプリケーションを追加"
msgid "Add new directory"
msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’追加"
-msgid "Add projects"
-msgstr "プロジェクト追加"
-
msgid "Add reaction"
msgstr "リアクションã®è¿½åŠ "
msgid "Add to project"
-msgstr ""
+msgstr "プロジェクトã«è¿½åŠ "
msgid "Add to review"
msgstr "レビュー追加"
@@ -465,23 +606,32 @@ msgid "Add user(s) to the group:"
msgstr "グループã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’追加:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®æ‰¿èªã‚’許å¯ã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¾ãŸã¯ã‚°ãƒ«ãƒ¼ãƒ—ã®è¿½åŠ "
msgid "Add users to group"
msgstr "ユーザーをグループã¸è¿½åŠ "
-msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgid "Added at"
msgstr ""
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr "ã‚ãªãŸã® GitLab インスタンスã§æ–°ã—ã„アプリケーションを追加ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。権é™ã‚’å–å¾—ã™ã‚‹ã«ã¯ã€GitLab 管ç†è€…ã«é€£çµ¡ã—ã¦ãã ã•ã„。"
+
msgid "Additional text"
msgstr "追加テキスト"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "管ç†è€…エリア"
msgid "Admin Overview"
msgstr "管ç†è€…用概è¦"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -504,7 +654,7 @@ msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs
msgstr "å…¨ã¦ã®ã‚¸ãƒ§ãƒ–ã‚’åœæ­¢ã—ã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šç¾åœ¨å®Ÿè¡Œä¸­ã®ã‚¸ãƒ§ãƒ–ã¯åœæ­¢ã•ã‚Œã¾ã™ã€‚"
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
-msgstr ""
+msgstr "プロジェクト %{projectName} ã¨ãã®ãƒªãƒã‚¸ãƒˆãƒªã€èª²é¡Œã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãªã©ã®ã™ã¹ã¦ã®é–¢é€£ãƒªã‚½ãƒ¼ã‚¹ã‚’完全ã«å‰Šé™¤ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã„ã¾ä¸€åº¦ç¢ºèªã—〠%{strong_start}プロジェクトã®å‰Šé™¤%{strong_end}を押ã—ã¦ãã ã•ã„。削除ã—ãŸã‚ã¨ã€å…ƒã«æˆ»ã™ã“ã¨ã¯ã§ãã¾ã›ã‚“。"
msgid "AdminProjects|Delete"
msgstr "削除"
@@ -516,40 +666,43 @@ msgid "AdminProjects|Delete project"
msgstr "プロジェクトã®å‰Šé™¤"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Auto DevOps ドメイン"
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr "æ–°ã—ã„プロジェクトã§å…±æœ‰ Runner を有効ã«ã™ã‚‹"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "環境変数ã¯ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ä¿è­·ã•ã‚Œã¦ã„ã¾ã™"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクトã®Auto Review AppsãŠã‚ˆã³Auto Deployステージã§ä½¿ç”¨ã™ã‚‹ã€ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’指定ã—ã¾ã™ã€‚"
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "環境変数ã®æ–°è¦ä½œæˆæ™‚ã«ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ä¿è­·ã•ã‚Œã¾ã™"
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA 無効"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA 有効"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "アクティブ"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "管ç†è€…"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "管ç†è€…"
msgid "AdminUsers|Block user"
msgstr "ブロックユーザー"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "ブロック済ã¿"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "LDAP ã§ãƒ–ロックã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ブロック解除ã§ãã¾ã›ã‚“"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "ユーザー %{username} ã¨è²¢çŒ®åº¦ã‚’削除ã—ã¾ã™ã‹?"
@@ -564,28 +717,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "ユーザーã¨è²¢çŒ®åº¦ã®å‰Šé™¤"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "外部"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "ã‚ãªãŸã§ã™ï¼"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "æ–°ã—ã„ユーザー"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "ユーザーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "åå‰ã€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã§æ¤œç´¢"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "ユーザーを検索"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "ユーザーã«Eメールをé€ä¿¡ã™ã‚‹"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "並ã³æ›¿ãˆ"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "確èªã®ãŸã‚ã€%{projectName} を入力ã—ã¦ãã ã•ã„"
@@ -594,27 +747,39 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "確èªã®ãŸã‚ã€%{username} を入力ã—ã¦ãã ã•ã„"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "ユーザーã¯ãƒ–ロックã•ã‚Œã¾ã™"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "プロジェクトãªã—"
+
+msgid "Advanced"
+msgstr "高度ãªè¨­å®š"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "高度ãªè¨­å®š"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "パスワードã®æ›´æ–°ã«æˆåŠŸã™ã‚‹ã¨ã€ãƒ­ã‚°ã‚¤ãƒ³ç”»é¢ã«ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã•ã‚Œã¾ã™ã€‚"
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "アラート"
msgid "Alerts"
-msgstr ""
+msgstr "アラート"
msgid "All"
msgstr "ã™ã¹ã¦"
+msgid "All Members"
+msgstr "ã™ã¹ã¦ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
msgid "All changes are committed"
msgstr "ã™ã¹ã¦ã®å¤‰æ›´ãŒã‚³ãƒŸãƒƒãƒˆã•ã‚Œã¦ã„ã¾ã™"
@@ -622,17 +787,35 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "空ã®ãƒ—ロジェクトã€ãƒ†ãƒ³ãƒ—レートã‹ã‚‰ã€ã¾ãŸã¯ã‚¤ãƒ³ãƒãƒ¼ãƒˆæ™‚ã«ã™ã¹ã¦ã®æ©Ÿèƒ½ãŒæœ‰åŠ¹ã«ãªã£ã¦ã„ã¾ã™ãŒã€å¾Œã§ãƒ—ロジェクト設定ã§ç„¡åŠ¹ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr "ã“ã®ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«é–¢ã™ã‚‹èª²é¡Œã¯ã™ã¹ã¦è§£æ±ºã•ã‚Œã¾ã—ãŸã€‚ã“ã®ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã‚’クローズã§ãã¾ã™ã€‚"
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
msgstr ""
msgid "All users"
msgstr "ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr "ターゲットブランãƒã«ãƒžãƒ¼ã‚¸ã§ãるメンãƒãƒ¼ã‹ã‚‰ã®ã‚³ãƒŸãƒƒãƒˆã‚’許å¯ã—ã¾ã™ã€‚"
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr "グループオーナー㌠LDAP 関連ã®è¨­å®šã‚’管ç†ã§ãるよã†ã«ã™ã‚‹"
+
+msgid "Allow mirrors to be set up for projects"
+msgstr "プロジェクトã®ãƒŸãƒ©ãƒ¼ã‚’設定ã§ãるよã†ã«ã™ã‚‹"
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr "é¸æŠžã—ãŸãƒ—ロトコルã®ã¿ã‚’ Git アクセスã«ä½¿ç”¨ã§ãるよã†ã«ã—ã¾ã™ã€‚"
+
msgid "Allow projects within this group to use Git LFS"
msgstr "ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—内ã®ãƒ—ロジェクト㫠Git LFS を使用ã§ãるよã†ã«ã™ã‚‹"
@@ -645,11 +828,17 @@ msgstr "Asciidocドキュメントã§ã®PlantUML図ã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã‚’許å
msgid "Allow requests to the local network from hooks and services."
msgstr "フックãŠã‚ˆã³ã‚µãƒ¼ãƒ“スã‹ã‚‰ã®ãƒ­ãƒ¼ã‚«ãƒ«ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã¸ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’許å¯ã™ã‚‹ã€‚"
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr "ã“ã®ã‚­ãƒ¼ã‚‚リãƒã‚¸ãƒˆãƒªã«ãƒ—ッシュã§ãるよã†ã«ã—ã¾ã™ã‹ï¼Ÿ (デフォルトã¯ãƒ—ルアクセスã®ã¿ã‚’許å¯ã—ã¾ã™)"
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "ユーザーãŒã‚¢ã‚¯ã‚»ã‚¹ã‚’è¦æ±‚ã§ãるよã†ã«ã™ã‚‹"
msgid "Allow users to request access if visibility is public or internal."
-msgstr ""
+msgstr "å¯è¦–性ãŒå…¬é–‹ã¾ãŸã¯å†…部ã®å ´åˆã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã‚¢ã‚¯ã‚»ã‚¹ã‚’è¦æ±‚ã§ãるよã†ã«ã—ã¾ã™ã€‚"
msgid "Allowed to fail"
msgstr ""
@@ -663,6 +852,9 @@ msgstr "\"Issuer\" ã¾ãŸã¯ \"Relying party trust identifier\" ã¨ã‚‚呼ã°ã‚Œã
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "\"Relying party service URL\" ã¾ãŸã¯ \"Reply URL\" ã¨ã‚‚呼ã°ã‚Œã¾ã™"
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "ã‚ã‚‹ã„ã¯ã€ %{personal_access_token_link} を使用ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚パーソナルアクセストークンを作æˆã™ã‚‹éš›ã«ã€<code>repo</code> スコープをé¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šã€æŽ¥ç¶šå¯èƒ½ãªå…¬é–‹ãƒªãƒã‚¸ãƒˆãƒªã¨ãƒ—ライベートリãƒã‚¸ãƒˆãƒªã®ä¸€è¦§ã‚’表示ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
@@ -670,10 +862,10 @@ msgid "Alternatively, you can use a %{personal_access_token_link}. When you crea
msgstr "ã‚ã‚‹ã„ã¯ã€ %{personal_access_token_link} を使用ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚パーソナルアクセストークンを作æˆã™ã‚‹éš›ã«ã€<code>repo</code>スコープをé¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šã€ã‚¤ãƒ³ãƒãƒ¼ãƒˆã™ã‚‹ã“ã¨ãŒã§ãるパブリックリãƒã‚¸ãƒˆãƒªã¨ãƒ—ライベートリãƒã‚¸ãƒˆãƒªã®ä¸€è¦§ã‚’表示ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
-msgstr ""
+msgstr "ユーザーãŒå¼·åˆ¶çš„ãª2è¦ç´ èªè¨¼ã‚’無視ã§ãる時間(時間å˜ä½)"
msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
-msgstr ""
+msgstr "ã“ã®ãƒ•ã‚©ãƒ¼ãƒ ã‚’é€ä¿¡ã™ã‚‹ã¨è‡ªå‹•çš„ã« SSH éµã‚’生æˆã—ã¾ã™ã€‚詳細ã«ã¤ã„ã¦ã¯æ–‡æ›¸ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr "アプリケーション㮠%{link_to_client} ãŒã‚ãªãŸã® GitLab アカウントã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’è¦æ±‚ã—ã¦ã„ã¾ã™ã€‚"
@@ -684,9 +876,6 @@ msgstr "GitLab ユーザフィールドãŒç©ºã®å ´åˆã€ã™ã¹ã¦ã®å•é¡Œã¨ã‚
msgid "An error has occurred"
msgstr "エラーãŒç™ºç”Ÿã—ã¾ã—ãŸ"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "ディスカッションã«ä¸‹æ›¸ãを追加ã™ã‚‹éš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -694,7 +883,16 @@ msgid "An error occurred adding a new draft."
msgstr "æ–°ã—ã„ドラフトã®è¿½åŠ ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "æ–°ã—ã„ブランãƒã®ä½œæˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
+msgid "An error occurred fetching the approval rules."
+msgstr "承èªãƒ«ãƒ¼ãƒ«ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "æ–°ã—ã„ルールã®æ‰¿èªè€…ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
+msgid "An error occurred fetching the dropdown data."
+msgstr "ドロップダウンデータã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred previewing the blob"
msgstr "Blobã®ãƒ—レビュー中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -708,18 +906,27 @@ msgstr "課題ã®ã‚¦ã‚¨ã‚¤ãƒˆæ›´æ–°æ™‚ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "An error occurred while adding approver"
msgstr "承èªè€…ã®è¿½åŠ ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while deleting the approvers group"
+msgstr "承èªè€…グループã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
msgid "An error occurred while deleting the comment"
msgstr "コメントã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while detecting host keys"
msgstr "ホストキーã®æ¤œå‡ºä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "アラートã®æ¶ˆåŽ»ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ページを更新ã—ã¦ã‚„ã‚Šç›´ã—ã¦ä¸‹ã•ã„。"
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "ãƒã‚¤ãƒ©ã‚¤ãƒˆã‚’消去ã—ã¦ã„ã‚‹ã¨ãã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ページを更新ã—ã¦ã‚‚ã†ä¸€åº¦æ¶ˆåŽ»ã—ã¦ãã ã•ã„。"
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "Markdown プレビューã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -732,6 +939,9 @@ msgstr "サイドãƒãƒ¼ã®ãƒ‡ãƒ¼ã‚¿å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -739,11 +949,14 @@ msgid "An error occurred while fetching the job."
msgstr ""
msgid "An error occurred while fetching the jobs."
-msgstr ""
+msgstr "ジョブã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while fetching the pipeline."
msgstr "パイプラインã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr "リリースã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+
msgid "An error occurred while getting projects"
msgstr "プロジェクトã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -759,6 +972,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr "コミットシグãƒãƒãƒ£ã®èª­è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while loading designs. Please try again."
+msgstr "デザインã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+
msgid "An error occurred while loading diff"
msgstr "差分ã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -769,7 +985,7 @@ msgid "An error occurred while loading the file"
msgstr "ファイルã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "サブスクリプションã®è©³ç´°ã‚’ロード中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while making the request."
msgstr "リクエスト作æˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -778,10 +994,10 @@ msgid "An error occurred while removing approver"
msgstr "承èªè€…ã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "エピックã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "課題ã®å‰Šé™¤ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while rendering KaTeX"
msgstr "KaTeXã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -801,12 +1017,18 @@ msgstr "LDAP ã®ã‚ªãƒ¼ãƒãƒ¼ãƒ©ã‚¤ãƒ‰çŠ¶æ…‹ã‚’ä¿å­˜ã™ã‚‹ã¨ãã«ã‚¨ãƒ©ãƒ¼ãŒç
msgid "An error occurred while saving assignees"
msgstr "担当者ã®ç™»éŒ²ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "An error occurred while saving the approval settings"
+msgstr "承èªè¨­å®šã®ä¿å­˜ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+
msgid "An error occurred while subscribing to notifications."
msgstr "通知ã®è³¼èª­ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred while unsubscribing to notifications."
msgstr "通知ã®è³¼èª­ã‚’解除中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "An error occurred while updating approvers"
+msgstr "承èªè€…ã®æ›´æ–°ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
msgid "An error occurred while updating the comment"
msgstr "コメントを更新中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -814,28 +1036,28 @@ msgid "An error occurred while validating username"
msgstr "ユーザåã®æ¤œè¨¼ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "変更ã®ã‚³ãƒŸãƒƒãƒˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "ジョブトレースã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "最新ã®ãƒ‘イプラインã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®èª­è¾¼ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "ファイルã®å†…容を読込中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst loading the file."
msgstr ""
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "マージリクエストã®å¤‰æ›´ã‚’読込中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "マージリクエストã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³æƒ…報を読込中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An error occurred whilst loading the merge request."
msgstr ""
@@ -847,23 +1069,26 @@ msgid "An error occurred. Please try again."
msgstr "エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚å†åº¦ãŠè©¦ã—ãã ã•ã„。"
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "プロジェクト環境ã®ç¢ºèªä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "プロジェクトランナーã®ç¢ºèªä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "Web ターミナルã¨ã®é€šä¿¡ä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "Web ターミナルã®èµ·å‹•ä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "Web ターミナルã®åœæ­¢ä¸­ã«äºˆæœŸã—ãªã„エラーãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "Analytics"
msgstr "アクセス解æž"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "匿å"
@@ -876,15 +1101,33 @@ msgstr "ä»»æ„ã®"
msgid "Any Label"
msgstr "ä»»æ„ã®ãƒ©ãƒ™ãƒ«"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "外観"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "アプリケーション"
msgid "Application ID"
msgstr "アプリケーション ID"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "アプリケーション:%{name}"
@@ -892,19 +1135,53 @@ msgid "Applications"
msgstr "アプリケーション"
msgid "Applied"
-msgstr ""
+msgstr "é©ç”¨æ¸ˆã¿"
msgid "Apply suggestion"
+msgstr "æ案をé©ç”¨"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d 人ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr "%{nMembers} 人ã®ãƒ¡ãƒ³ãƒãƒ¼ãŒã„ã‚‹ %{name} 承èªè€…グループを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚"
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d 人ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
msgstr ""
-msgid "Approvals"
+msgid "ApprovalRule|Members"
+msgstr "メンãƒãƒ¼"
+
+msgid "ApprovalRule|Name"
+msgstr "åå‰"
+
+msgid "ApprovalRule|No. approvals required"
msgstr ""
-msgid "Approvals required"
+msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
+msgid "Approvals"
+msgstr "承èª"
+
+msgid "Approvals required"
+msgstr "承èªãŒå¿…è¦"
+
msgid "Approvers"
-msgstr ""
+msgstr "承èªè€…"
msgid "Apr"
msgstr "4月"
@@ -912,13 +1189,28 @@ msgstr "4月"
msgid "April"
msgstr "4月"
+msgid "Archive jobs"
+msgstr "アーカイブジョブ"
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "ã“ã®ãƒ—ロジェクトã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•ã‚Œã¦ã„ã¾ã™ã€‚リãƒã‚¸ãƒˆãƒªãŠã‚ˆã³ãã®ä»–ã®ãƒ—ロジェクトリソースã¯èª­ã¿å–り専用ã§ã™"
msgid "Archived projects"
msgstr "アーカイブã•ã‚ŒãŸãƒ—ロジェクト"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
+msgstr "よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
@@ -931,22 +1223,31 @@ msgid "Are you sure you want to lose unsaved changes?"
msgstr "変更ãŒä¿å­˜ã•ã‚Œã¦ã„ã¾ã›ã‚“ãŒç ´æ£„ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Are you sure you want to lose your issue information?"
+msgstr "本当ã«èª²é¡Œæƒ…報を削除ã—ã¾ã™ã‹?"
+
+msgid "Are you sure you want to permanently delete this license?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
-msgstr ""
+msgstr "公開éµã‚’å†ç”Ÿæˆã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹? ミラーリングを行ã†å‰ã«ãƒªãƒ¢ãƒ¼ãƒˆã‚µãƒ¼ãƒãƒ¼ã®å…¬é–‹éµã‚’æ›´æ–°ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
msgid "Are you sure you want to remove %{group_name}?"
msgstr "ã“ã® %{group_name} を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "承èªè€… %{name} を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
msgid "Are you sure you want to remove approver %{name}?"
-msgstr ""
+msgstr "承èªè€… %{name} を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr "グループ %{name} を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Are you sure you want to remove group %{name}?"
-msgstr ""
+msgstr "グループ %{name} を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "添付ファイルを削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Are you sure you want to remove this identity?"
msgstr "ã“ã® ID を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
@@ -954,6 +1255,9 @@ msgstr "ã“ã® ID を削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Are you sure you want to reset registration token?"
msgstr "本当ã«ç™»éŒ²ãƒˆãƒ¼ã‚¯ãƒ³ã‚’リセットã—ã¾ã™ã‹ï¼Ÿ"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "本当ã«ãƒ˜ãƒ«ã‚¹ãƒã‚§ãƒƒã‚¯ãƒˆãƒ¼ã‚¯ãƒ³ã‚’リセットã—ã¾ã™ã‹ï¼Ÿ"
@@ -985,7 +1289,7 @@ msgid "Assertion consumer service URL"
msgstr "アサーション コンシューマー サービス URL"
msgid "Assets"
-msgstr ""
+msgstr "アセット"
msgid "Assign custom color like #FF0000"
msgstr "#FF0000ã®ã‚ˆã†ãªã‚«ã‚¹ã‚¿ãƒ ã‚«ãƒ©ãƒ¼ã‚’割り当ã¦ã‚‹"
@@ -997,7 +1301,7 @@ msgid "Assign milestone"
msgstr "マイルストーンを割り当ã¦ã‚‹"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "ã“ã®ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«ã„ãã¤ã‹ã®èª²é¡Œã‚’割り当ã¦ã¾ã™ã€‚"
msgid "Assign to"
msgstr "割り当ã¦å…ˆ"
@@ -1008,9 +1312,6 @@ msgstr "割り当ã¦ã‚‰ã‚ŒãŸèª²é¡Œ"
msgid "Assigned Merge Requests"
msgstr "割り当ã¦ã‚‰ã‚ŒãŸãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
-msgid "Assigned to :name"
-msgstr ":name ãŒæ‹…当"
-
msgid "Assigned to me"
msgstr "自分ã«å‰²ã‚Šå½“ã¦ã‚‹"
@@ -1026,9 +1327,12 @@ msgstr "担当者一覧ã«ã¯ã€é¸æŠžã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦
msgid "Assignee(s)"
msgstr "担当者"
-msgid "Attach a file"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
msgstr ""
+msgid "Attach a file"
+msgstr "ファイルを添付"
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "ドラッグ&ドロップã¾ãŸã¯ %{upload_link} ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’添付"
@@ -1041,18 +1345,24 @@ msgstr "8月"
msgid "August"
msgstr "8月"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "èªè¨¼ãƒ­ã‚°"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "èªè¨¼ãƒ­ã‚°"
msgid "Authentication method"
msgstr "èªè¨¼æ–¹æ³•"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "作æˆè€…"
@@ -1060,7 +1370,7 @@ msgid "Authorization code:"
msgstr "èªè¨¼ã‚³ãƒ¼ãƒ‰:"
msgid "Authorization key"
-msgstr ""
+msgstr "èªè¨¼ã‚­ãƒ¼"
msgid "Authorization was granted by entering your username and password in the application."
msgstr "ã“ã®ã‚¢ãƒ—リケーションã«ã‚ãªãŸã®ãƒ¦ãƒ¼ã‚¶ãƒ¼åã¨ãƒ‘スワードãŒå…¥åŠ›ã•ã‚ŒãŸã®ã§ã€æ‰¿èªãŒè¨±å¯ã•ã‚Œã¾ã—ãŸã€‚"
@@ -1071,6 +1381,9 @@ msgstr "承èªã™ã‚‹"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã« %{link_to_client} を承èªã—ã¾ã™ã‹ï¼Ÿ"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1110,38 +1423,32 @@ msgstr "詳ã—ãã¯ã€ %{link_to_documentation} を見ã¦ãã ã•ã„。"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "ã“ã®ãƒ—ロジェクト㮠%{link_to_auto_devops_settings} ã‚’ã™ã‚‹å ´åˆã€è‡ªå‹•ãƒ“ルドãŠã‚ˆã³ãƒ†ã‚¹ãƒˆã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚%{link_to_add_kubernetes_cluster} ã—ãŸå ´åˆã€è‡ªå‹•ãƒ‡ãƒ—ロイã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "Kubernetes クラスターを追加"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "Auto DevOps を有効ã«ã™ã‚‹"
-
msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically resolved"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
+msgid "Automatically resolved"
+msgstr "自動解決"
+
msgid "Available"
msgstr "利用å¯èƒ½"
msgid "Available group Runners: %{runners}"
-msgstr ""
+msgstr "利用å¯èƒ½ãªã‚°ãƒ«ãƒ¼ãƒ— Runner:%{runners}"
msgid "Available shared Runners:"
-msgstr ""
+msgstr "利用å¯èƒ½ãªå…±æœ‰ Runner"
msgid "Available specific runners"
-msgstr ""
+msgstr "利用å¯èƒ½ãª Specific Runner"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "%{assigneeName} ã®ã‚¢ãƒã‚¿ãƒ¼"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "%{name} ã®ã‚¢ãƒã‚¿ãƒ¼"
msgid "Avatar will be removed. Are you sure?"
msgstr "ã‚¢ãƒã‚¿ãƒ¼ã‚’削除ã—ã¾ã™ã€‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
@@ -1245,6 +1552,12 @@ msgstr "レビューを破棄ã—ã¾ã™ã‹ï¼Ÿ"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "é¸æŠžã—ãŸã‚³ãƒŸãƒƒãƒˆã§ã¯ã˜ã‚ã‚‹"
@@ -1300,7 +1613,7 @@ msgid "BillingPlans|You are currently on the %{plan_link} plan."
msgstr "ç¾åœ¨ã®ãƒ—ランã¯%{plan_link} ã§ã™ã€‚"
msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}"
-msgstr ""
+msgstr "ã‚ãªãŸã® GitLab.com 試用版㯠%{expiration_date} ã«æœŸé™åˆ‡ã‚Œã«ãªã‚Šã¾ã—ãŸã€‚%{learn_more_text}"
msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
msgstr ""
@@ -1327,10 +1640,10 @@ msgid "Bitbucket import"
msgstr "Bitbucket インãƒãƒ¼ãƒˆ"
msgid "Block"
-msgstr ""
+msgstr "ブロック"
msgid "Blocked"
-msgstr ""
+msgstr "ブロック中"
msgid "Blog"
msgstr "ブログ"
@@ -1482,6 +1795,12 @@ msgstr "プロジェクト設定"
msgid "Branches|protected"
msgstr "ä¿è­·"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "ディレクトリを表示"
@@ -1495,33 +1814,45 @@ msgid "Browse files"
msgstr "ファイルを表示"
msgid "Built-in"
-msgstr ""
+msgstr "ビルトイン"
msgid "Business"
-msgstr ""
+msgstr "ビジãƒã‚¹"
msgid "Business metrics (Custom)"
msgstr "ビジãƒã‚¹ãƒ¡ãƒˆãƒªã‚¯ã‚¹ï¼ˆã‚«ã‚¹ã‚¿ãƒ ï¼‰"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "作者"
msgid "CHANGELOG"
-msgstr ""
+msgstr "変更履歴"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "CI / CD ãƒãƒ£ãƒ¼ãƒˆ"
msgid "CI / CD Settings"
msgstr "CI / CD 設定"
msgid "CI Lint"
+msgstr "CI Lint"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
@@ -1552,11 +1883,14 @@ msgid "CICD|Continuous deployment to production"
msgstr "本番環境ã¸ã®ç¶™ç¶šçš„デプロイ"
msgid "CICD|Continuous deployment to production using timed incremental rollout"
-msgstr ""
+msgstr "タイム・インクリメンタル・ロールアウトを用ã„ãŸæœ¬ç•ªç’°å¢ƒã¸ã®ç¶™ç¶šçš„デプロイ"
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "デフォルト㮠Auto DevOps パイプライン"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "デプロイ戦略"
@@ -1572,6 +1906,9 @@ msgstr "ãã®ä»–ã« CI 設定ファイルãŒè¦‹ã¤ã‹ã‚‰ãªã„å ´åˆã€Auto Dev
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "インスタンスãŒæœ‰åŠ¹"
@@ -1582,11 +1919,14 @@ msgid "Callback URL"
msgstr "コールãƒãƒƒã‚¯ URL"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "マージリクエストã”ã¨ã«å¿…è¦ãªæ‰¿èªè€…ã¨æ‰¿èªã‚’上書ãã§ãã¾ã™"
msgid "Can't find HEAD commit for this branch"
msgstr "ã“ã®ãƒ–ランãƒã«ã¯ HEAD コミットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+msgid "Can't remove group members without group managed account"
+msgstr "グループ管ç†ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒãªã„ã¨ã‚°ãƒ«ãƒ¼ãƒ—メンãƒãƒ¼ã¯å‰Šé™¤ã§ãã¾ã›ã‚“"
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1599,24 +1939,39 @@ msgstr "ã“ã®ã‚¸ãƒ§ãƒ–をキャンセルã™ã‚‹"
msgid "Cannot be merged automatically"
msgstr "自動的ã«ãƒžãƒ¼ã‚¸ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "管ç†ä¸‹ã® Kubernetes クラスターを変更ã§ãã¾ã›ã‚“"
-msgid "Certificate"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "証明書ã®ãƒ•ã‚£ãƒ³ã‚¬ãƒ¼ プリント"
+msgid "Capacity threshold"
+msgstr "キャパシティ判定ã®ã—ãã„値"
+
+msgid "Certificate"
+msgstr "証明書"
+
+msgid "Certificate (PEM)"
+msgstr "証明書 (PEM)"
msgid "Change Weight"
msgstr "ウェイトを変更ã™ã‚‹"
-msgid "Change permissions"
+msgid "Change path"
msgstr ""
+msgid "Change permissions"
+msgstr "アクセス権é™ã‚’変更"
+
msgid "Change template"
msgstr "テンプレートを変更"
@@ -1638,11 +1993,14 @@ msgstr "リãƒãƒ¼ãƒˆ"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "コミット済ã®å¤‰æ›´ã‚’ revert ã™ã‚‹ãŸã‚ã«æ–°ã—ã„コミットを作æˆã—ã¾ã™"
+msgid "Changes"
+msgstr "変更"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "<b>source</b>リビジョンãŒ<b>target</b>リビジョン内ã«å–ã‚Šè¾¼ã¾ã‚Œã¦ã„るよã†ãªå¤‰æ›´ã¨ã—ã¦è¡¨ç¤ºã•ã‚Œã¾ã™"
msgid "Changes suppressed. Click to show."
-msgstr ""
+msgstr "変更ã¯æŠ‘制ã•ã‚Œã¾ã—ãŸã€‚クリックã—ã¦è¡¨ç¤º"
msgid "Charts"
msgstr "ãƒãƒ£ãƒ¼ãƒˆ"
@@ -1650,17 +2008,23 @@ msgstr "ãƒãƒ£ãƒ¼ãƒˆ"
msgid "Chat"
msgstr "ãƒãƒ£ãƒƒãƒˆ"
-msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgid "Check again"
+msgstr "å†ãƒã‚§ãƒƒã‚¯"
+
+msgid "Check feature availability on namespace plan"
msgstr ""
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr "%{docs_link_start}ドキュメント%{docs_link_end}を確èª"
+
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr ".gitlab-ci.yml を確èªã—ã¦ãã ã•ã„"
msgid "Checking %{text} availability…"
msgstr "%{text} ãŒåˆ©ç”¨å¯èƒ½ã‹ç¢ºèªã—ã¦ã„ã¾ã™â€¦"
msgid "Checking approval status"
-msgstr ""
+msgstr "承èªã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’確èªã™ã‚‹"
msgid "Checking branch availability..."
msgstr "ブランãƒãŒåˆ©ç”¨å¯èƒ½ã‹ç¢ºèªã—ã¦ã„ã¾ã™â€¦"
@@ -1677,23 +2041,20 @@ msgstr "<strong>アーカイブã®ä½œæˆ</strong>ã‚’é¸æŠžã—ã€ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ä
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã®ä¸‹éƒ¨ã«ã‚ã‚‹<strong>次ã¸</strong>ã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-msgid "Choose File ..."
-msgstr "ファイルをé¸æŠž..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "変更内容を確èªã—ãŸã‚Šãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’作æˆã™ã‚‹ãŸã‚ã«ã€Branch/tag (例: %{master}) ã‚’é¸æŠžã™ã‚‹ã‹ã€ã‚³ãƒŸãƒƒãƒˆID(例: %{sha})を入力ã—ã¦ãã ã•ã„。"
msgid "Choose a file"
-msgstr ""
+msgstr "ファイルをé¸æŠžã—ã¦ãã ã•ã„"
msgid "Choose a role permission"
msgstr ""
msgid "Choose a template..."
-msgstr ""
+msgstr "テンプレートをé¸æŠžã—ã¦ãã ã•ã„..."
msgid "Choose a type..."
-msgstr ""
+msgstr "タイプをé¸æŠžã—ã¦ãã ã•ã„..."
msgid "Choose any color."
msgstr "カラーをé¸æŠžã—ã¦ãã ã•ã„。"
@@ -1701,27 +2062,27 @@ msgstr "カラーをé¸æŠžã—ã¦ãã ã•ã„。"
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "<code>clone</code> ã¾ãŸã¯ <code>fetch</code> ã‚’é¸æŠžã—ã¦æœ€æ–°ã®ã‚¢ãƒ—リケーションコードをå–å¾—ã—ã¦ãã ã•ã„"
-msgid "Choose file..."
-msgstr "ファイルをé¸æŠž..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
-msgstr ""
+msgstr "インãƒãƒ¼ãƒˆãƒªãƒã‚¸ãƒˆãƒªã®ãƒˆãƒƒãƒ—レベルã®ã‚°ãƒ«ãƒ¼ãƒ—ã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "åŒæœŸã•ã›ãŸã„セカンダリノードã®ã‚°ãƒ«ãƒ¼ãƒ—ã‚’é¸æŠžã—ã¦ãã ã•ã„。"
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "グループã®æ¦‚è¦ãƒšãƒ¼ã‚¸ã«è¡¨ç¤ºã—ãŸã„コンテンツをé¸æŠžã—ã¦ãã ã•ã„"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "CI/CD パイプラインを実行ã—ãŸã„リãƒã‚¸ãƒˆãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-msgid "Choose which repositories you want to import."
-msgstr "インãƒãƒ¼ãƒˆã—ãŸã„リãƒã‚¸ãƒˆãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。"
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "セカンダリノードã¨åŒæœŸã•ã›ãŸã„シャードをé¸æŠžã—ã¦ãã ã•ã„。"
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "キャンセル"
@@ -1746,6 +2107,9 @@ msgstr "æˆåŠŸï¼ˆè­¦å‘Šã‚り)"
msgid "CiStatusLabel|pending"
msgstr "開始待ã¡"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "スキップ済ã¿"
@@ -1779,6 +2143,9 @@ msgstr "æˆåŠŸ"
msgid "CiStatusText|pending"
msgstr "実行待ã¡"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "スキップ済ã¿"
@@ -1794,6 +2161,9 @@ msgstr "値を入力"
msgid "CiVariables|Remove variable row"
msgstr "環境変数を削除"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (ã™ã¹ã¦ã®ç’°å¢ƒ)"
@@ -1804,6 +2174,9 @@ msgid "CiVariable|Create wildcard"
msgstr "ワイルドカードã®ä½œæˆ"
msgid "CiVariable|Error occurred while saving variables"
+msgstr "変数ä¿å­˜ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+
+msgid "CiVariable|Masked"
msgstr ""
msgid "CiVariable|New environment"
@@ -1815,20 +2188,26 @@ msgstr "ä¿è­·"
msgid "CiVariable|Search environments"
msgstr "環境を検索"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "ä¿è­·ã®åˆ‡ã‚Šæ›¿ãˆ"
msgid "CiVariable|Validation failed"
msgstr "検証ã«å¤±æ•—ã—ã¾ã—ãŸ"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "使用ã§ãã¾ã›ã‚“:%{reason}"
msgid "Clear"
-msgstr ""
+msgstr "クリア"
msgid "Clear input"
-msgstr ""
+msgstr "入力をクリア"
msgid "Clear search"
msgstr "検索をクリア"
@@ -1846,7 +2225,7 @@ msgid "Click the <strong>Promote</strong> button in the top right corner to prom
msgstr "å³ä¸Šã®<strong>昇格</strong>ボタンをクリックã—ã¦ã‚°ãƒ«ãƒ¼ãƒ—マイルストーンã¸æ˜‡æ ¼"
msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
-msgstr ""
+msgstr "å³ã®<strong>Select none</strong>ボタンをクリックã—ã¦ãã ã•ã„。ã“ã‚Œã¯ã€GitLabã«å¿…è¦ãªã®ã¯\"Google Code Project Hosting\"ã ã‘ã ã‹ã‚‰ã§ã™ã€‚"
msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
msgstr "下ã®ãƒœã‚¿ãƒ³ã‚’クリックã™ã‚‹ã¨ã€Kubernetesã®ãƒšãƒ¼ã‚¸ã«é·ç§»ã—ã€ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ãƒ—ロセスを開始ã—ã¾ã™"
@@ -1870,32 +2249,32 @@ msgid "Clients"
msgstr "クライアント"
msgid "Clone"
-msgstr ""
+msgstr "クローン"
msgid "Clone repository"
msgstr "リãƒã‚¸ãƒˆãƒªã‚’クローン"
msgid "Clone with %{http_label}"
-msgstr ""
+msgstr "%{http_label} ã§ã‚¯ãƒ­ãƒ¼ãƒ³"
+
+msgid "Clone with KRB5"
+msgstr "KRB5 ã§ã‚¯ãƒ­ãƒ¼ãƒ³"
msgid "Clone with SSH"
-msgstr ""
+msgstr "SSH ã§ã‚¯ãƒ­ãƒ¼ãƒ³"
msgid "Close"
msgstr "クローズã™ã‚‹"
msgid "Close epic"
-msgstr ""
+msgstr "エピックを閉ã˜ã‚‹"
msgid "Close milestone"
-msgstr ""
+msgstr "マイルストーンを閉ã˜ã‚‹"
msgid "Closed"
msgstr "クローズ"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "クローズã—ãŸèª²é¡Œ"
@@ -1903,7 +2282,7 @@ msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain
msgstr ""
msgid "ClusterIntegration| can be used instead of a custom domain."
-msgstr ""
+msgstr "カスタムドメインã®ä»£ã‚ã‚Šã«ä½¿ç”¨ã§ãã¾ã™ã€‚"
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
msgstr ""
@@ -1911,15 +2290,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} ã¯æ­£å¸¸ã« Kubernetes クラスターã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¾ã—ãŸ"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} ã“ã‚Œã«ã‚ˆã‚Šã€ãƒ­ãƒ¼ãƒ‰ãƒãƒ©ãƒ³ã‚µã®ã‚ˆã†ãªã€è¿½åŠ ã®ãƒªã‚½ãƒ¼ã‚¹ã‚’追加ã—ã¾ã™ã€‚例ãˆã°ã€ã‚ãªãŸãŒ Kubernetes クラスターãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„るホスティングプロãƒã‚¤ãƒ€ を使用ã—ã¦ã„るよã†ãªå ´åˆã€è¿½åŠ ã®è²»ç”¨ãŒç™ºç”Ÿã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚Google Kubernetes Engine を使用ã—ã¦ã„ã‚‹å ´åˆã¯ã€ %{pricingLink} ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
-
msgid "ClusterIntegration|%{title} upgraded successfully."
-msgstr ""
+msgstr "%{title} ã¯æ­£å¸¸ã«ã‚¢ãƒƒãƒ—グレードã•ã‚Œã¾ã—ãŸã€‚"
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Kubernetes クラスターを追加"
@@ -1927,7 +2306,7 @@ msgid "ClusterIntegration|Add a Kubernetes cluster integration"
msgstr "Kubernetes クラスターã®çµ±åˆã‚’追加"
msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
-msgstr ""
+msgstr "グループ㫠Kubernetes クラスターを追加ã™ã‚‹ã¨ã€ã™ã¹ã¦ã®ãƒ—ロジェクトã§ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ãŒè‡ªå‹•çš„ã«å…±æœ‰ã•ã‚Œã¾ã™ã€‚Review Apps を使用ã—ã€ã‚¢ãƒ—リケーションを導入ã—ã€åŒã˜ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ã‚’使用ã™ã‚‹ã™ã¹ã¦ã®ãƒ—ロジェクトã®ãƒ‘イプラインを簡å˜ã«å®Ÿè¡Œã—ã¾ã™ã€‚"
msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
msgstr "グループã«çµ±åˆã‚’追加ã™ã‚‹ã¨ã€ã™ã¹ã¦ã®ãƒ—ロジェクトã§ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ãŒå…±æœ‰ã•ã‚Œã¾ã™ã€‚"
@@ -1935,35 +2314,32 @@ msgstr "グループã«çµ±åˆã‚’追加ã™ã‚‹ã¨ã€ã™ã¹ã¦ã®ãƒ—ロジェクト
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "ã“ã® Kubernetes クラスター統åˆã«é–¢ã™ã‚‹è©³ç´°ã‚ªãƒ—ション"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "プロジェクトゾーンã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Google Cloud API ã¸æŽ¥ç¶šã‚’試ã¿ãŸéš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚後ã§ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。"
-msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr "プロジェクトã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ:%{error}"
+
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
-msgstr ""
+msgstr "ゾーンã®ãƒžã‚·ãƒ³ã‚¿ã‚¤ãƒ—ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸ:%{error}"
msgid "ClusterIntegration|Applications"
msgstr "アプリケーション"
msgid "ClusterIntegration|Apply for credit"
-msgstr ""
+msgstr "クレジットをé©ç”¨"
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr "ã“ã® Kubernetes クラスター統åˆã‚’削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿã“ã®æ“作ã§ã¯å®Ÿéš›ã® Kubernetes クラスターã¯å‰Šé™¤ã•ã‚Œã¾ã›ã‚“。"
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "ベースドメイン"
msgid "ClusterIntegration|CA Certificate"
msgstr "CA 証明書"
@@ -1983,6 +2359,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1992,20 +2374,20 @@ msgstr "API URLをコピー"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "CA 証明書をコピー"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Ingressã®IPアドレスをクリップボードã«ã‚³ãƒ”ーã™ã‚‹"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr "Ingress ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’クリップボードã«ã‚³ãƒ”ー"
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Jupyter ã®ãƒ›ã‚¹ãƒˆåをクリップボードã¸ã‚³ãƒ”ー"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
-msgstr ""
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
+msgstr "Knative ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’クリップボードã«ã‚³ãƒ”ー"
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Kubernetes クラスターåをコピー"
-msgid "ClusterIntegration|Copy Token"
-msgstr "トークンをコピー"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Kubernetes クラスターを作æˆ"
@@ -2014,7 +2396,7 @@ msgid "ClusterIntegration|Did you know?"
msgstr "ã”存知ã§ã™ã‹ï¼Ÿ"
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
-msgstr ""
+msgstr "Kubernetes クラスターã¸ã® GitLab 接続を有効ã¾ãŸã¯ç„¡åŠ¹ã«ã—ã¾ã™ã€‚"
msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
msgstr ""
@@ -2052,7 +2434,7 @@ msgstr "GitLabã‚’çµ±åˆ"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2079,14 +2461,14 @@ msgstr "éžè¡¨ç¤º"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "クラスターã®ç¨¼åƒçŠ¶æ³ã‚’表示ã™ã‚‹ã«ã¯ã€å¿…須事項をåŽé›†ã™ã‚‹ãŸã‚ã® Prometheus を設置ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress 㮠IP アドレス"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr "Ingress エンドãƒã‚¤ãƒ³ãƒˆ"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
@@ -2094,15 +2476,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr "インストール"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Prometheus をインストール"
-
msgid "ClusterIntegration|Installed"
msgstr "インストール済ã¿"
msgid "ClusterIntegration|Installing"
msgstr "インストール中"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr "Ingress をインストールã™ã‚‹ã¨è¿½åŠ ã®ã‚³ã‚¹ãƒˆãŒã‹ã‹ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚ 詳細ã«ã¤ã„ã¦ã¯ %{pricingLink} ã‚’å‚ç…§"
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr "Knative をインストールã™ã‚‹ã¨è¿½åŠ ã®ã‚³ã‚¹ãƒˆãŒã‹ã‹ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚ 詳細ã«ã¤ã„ã¦ã¯ %{pricingLink} ã‚’å‚ç…§"
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Kubernetes クラスターを自動統åˆ"
@@ -2130,8 +2515,8 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Knativeドメインå:"
-msgid "ClusterIntegration|Knative IP Address:"
-msgstr ""
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr "Knative エンドãƒã‚¤ãƒ³ãƒˆ:"
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
msgstr ""
@@ -2142,9 +2527,6 @@ msgstr "Kubernetes クラスター"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Kubernetes クラスターã®è©³ç´°"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Kubernetes クラスターã®ç¨¼åƒçŠ¶æ…‹"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes クラスターを Google Kubernetes Engine 上ã«ä½œæˆã—ã¦ã„ã¾ã™..."
@@ -2155,7 +2537,7 @@ msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google
msgstr "Kubernetes クラスター ã¯æ­£å¸¸ã« Google Kubernetes Engine ã«ä½œæˆã•ã‚Œã¾ã—ãŸã€‚クラスター ã®è©³ç´°ã‚’表示ã™ã‚‹ã«ã¯ãƒšãƒ¼ã‚¸ã‚’æ›´æ–°ã—ã¦ãã ã•ã„。"
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
-msgstr ""
+msgstr "Kubernetes クラスター㯠Review Apps ã®ä½¿ç”¨ã€ã‚¢ãƒ—リケーションã®ãƒ‡ãƒ—ロイã€ãƒ‘イプラインã®å®Ÿè¡Œã‚„より簡å˜ãªå‡¦ç†ã‚’è¡Œã†è¨±å¯ã‚’求ã‚ã¦ã„ã¾ã™ã€‚"
msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
msgstr "Kubernetes クラスターã¯ã€ã“ã®ãƒ—ロジェクトã«ã‚¢ãƒ—リケーションã®ãƒ‡ãƒ—ロイや Review Apps ã®ç’°å¢ƒã‚’æä¾›ã™ã‚‹ã®ã«ä½¿ç”¨ã§ãã¾ã™ã€‚"
@@ -2173,7 +2555,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "グループ Kubernetes クラスターã®è©³ç´°"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "マシンタイプ"
@@ -2187,9 +2569,6 @@ msgstr "管ç†"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "%{link_gke} ã«ã‚¢ã‚¯ã‚»ã‚¹ã—㦠Kubernetes クラスターを管ç†"
-msgid "ClusterIntegration|More information"
-msgstr "詳細情報"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "検索æ¡ä»¶ã«ä¸€è‡´ã™ã‚‹ãƒžã‚·ãƒ³ã‚¿ã‚¤ãƒ—ã¯ã‚ã‚Šã¾ã›ã‚“"
@@ -2202,27 +2581,24 @@ msgstr "検索æ¡ä»¶ã«ä¸€è‡´ã™ã‚‹ãƒ—ロジェクトã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "ClusterIntegration|No zones matched your search"
msgstr "検索æ¡ä»¶ã«ä¸€è‡´ã™ã‚‹ã‚¾ãƒ¼ãƒ³ã¯ã‚ã‚Šã¾ã›ã‚“"
-msgid "ClusterIntegration|Note:"
-msgstr "注æ„:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "ノード数"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Kubernetes クラスターã®ã‚¢ã‚¯ã‚»ã‚¹æƒ…報を入力ã—ã¦ãã ã•ã„。ä¸æ˜Žç‚¹ã¯ %{link_to_help_page} ã® Kubernetes ã®é …目をã”覧ãã ã•ã„。"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Google アカウントãŒæ¬¡ã®è¦ä»¶ã‚’満ãŸã—ã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "プロジェクトクラスター"
-msgid "ClusterIntegration|Project namespace"
-msgstr "プロジェクトã®åå‰ç©ºé–“"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "プロジェクトã®åå‰ç©ºé–“ (çœç•¥å¯èƒ½ã€ä¸€æ„)"
@@ -2233,7 +2609,7 @@ msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{
msgstr ""
msgid "ClusterIntegration|RBAC-enabled cluster"
-msgstr ""
+msgstr "RBAC 有効クラスター"
msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
msgstr "Kubernetes クラスターã®çµ±åˆã«ã¤ã„ã¦ã¯ã€%{link_to_help_page} ã‚’ãŠèª­ã¿ãã ã•ã„。"
@@ -2248,13 +2624,13 @@ msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from th
msgstr "プロジェクトã‹ã‚‰ Kubernetes クラスターã®è¨­å®šã‚’削除ã—ã¾ã™ã€‚ãªãŠã€å®Ÿéš›ã® Kubernetes クラスターã¯å‰Šé™¤ã•ã‚Œã¾ã›ã‚“。"
msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
-msgstr ""
+msgstr "å¿…è¦ã«å¿œã˜ã¦ã“れをã‚ãªãŸã®ãƒ›ã‚¹ãƒˆåã«ç½®ãæ›ãˆã¾ã™ã€‚ãã†ã™ã‚‹å ´åˆã€hostname ã¯ä¸Šã® Ingress IP アドレスを指ã—ã¾ã™ã€‚"
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "インストール開始ã«å¤±æ•—ã—ã¾ã—ãŸ"
-msgid "ClusterIntegration|Retry upgrade"
-msgstr ""
+msgid "ClusterIntegration|Retry update"
+msgstr "アップデートをå†è©¦è¡Œ"
msgid "ClusterIntegration|Save changes"
msgstr "変更をä¿å­˜"
@@ -2289,8 +2665,11 @@ msgstr "ゾーンをé¸æŠž"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "ゾーンをé¸æŠžã—ã¦ãƒžã‚·ãƒ³ã‚¿ã‚¤ãƒ—ã‚’é¸æŠž"
-msgid "ClusterIntegration|Service token"
-msgstr "サービストークン"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "表示"
@@ -2298,9 +2677,6 @@ msgstr "表示"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Google Kubernetes Engine 上㮠Kubernetes クラスターを作æˆä¸­ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
@@ -2310,47 +2686,53 @@ msgstr "%{title} ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ä¸­ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ %{link_to_container_project} 㧠Kubernetes クラスターを作æˆã™ã‚‹ã®ã«ä»¥ä¸‹ã®æ¨©é™ãŒå¿…è¦ã§ã™"
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr "ã“ã®ã‚ªãƒ—ションを使用ã™ã‚‹ã¨ã€ã‚ãªãŸã¯ã‚¢ãƒ—リケーションを RBAC クラスター上ã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã§ãã¾ã™ã€‚"
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
msgstr ""
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Kubernetes クラスターを切り替ãˆ"
-msgid "ClusterIntegration|Token"
-msgstr "トークン"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr "アップデート中"
msgid "ClusterIntegration|Upgrade"
-msgstr ""
+msgstr "アップグレード"
msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
+msgstr "アップグレードã«å¤±æ•—ã—ã¾ã—ãŸ"
msgid "ClusterIntegration|Upgraded"
-msgstr ""
+msgstr "アップグレード済ã¿"
msgid "ClusterIntegration|Upgrading"
-msgstr ""
+msgstr "アップグレード中"
msgid "ClusterIntegration|Validating project billing status"
msgstr "プロジェクトã®è«‹æ±‚ステータスを検証ã—ã¦ã„ã¾ã™"
msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
-msgstr ""
+msgstr "GCP 上ã®ãƒ—ロジェクトã®è¦æ±‚ãŒæœ‰åŠ¹ã«ãªã£ã¦ã„ã‚‹ã“ã¨ã‚’確èªã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr "ã“ã®ãƒ—ロジェクト㫠Kubernetes クラスターを関連付ã‘ã‚‹ã“ã¨ã§ã€Review Apps ã®ä½¿ç”¨ã€ã‚¢ãƒ—リケーションã®ãƒ‡ãƒ—ロイã€ãƒ‘イプラインã®å®Ÿè¡Œãªã©ã‚’ç°¡å˜ã«è¡Œã†ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
-msgstr ""
+msgstr "次ã®ã‚¢ãƒ—リケーションをインストールã™ã‚‹å‰ã«ã€Helm Tiller をインストールã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“"
msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
-msgstr ""
+msgstr "Knative をインストールã™ã‚‹ã«ã¯ã€RBAC 対応ã®ã‚¯ãƒ©ã‚¹ã‚¿ãƒ¼ãŒå¿…è¦ã§ã™ã€‚"
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr "アカウント㫠%{link_to_kubernetes_engine} ãŒå¿…è¦ã§ã™ã€‚"
@@ -2361,9 +2743,6 @@ msgstr "ゾーン"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "Google Kubernetes Engineã«ã‚¢ã‚¯ã‚»ã‚¹"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "価格を確èªã™ã‚‹"
-
msgid "ClusterIntegration|documentation"
msgstr "ドキュメント"
@@ -2373,6 +2752,9 @@ msgstr "ヘルプ ページ"
msgid "ClusterIntegration|meets the requirements"
msgstr "å¿…è¦æ¡ä»¶"
+msgid "ClusterIntegration|pricing"
+msgstr "価格"
+
msgid "ClusterIntegration|properly configured"
msgstr "æ­£ã—ã設定ã•ã‚Œã¦ã„ã‚‹"
@@ -2380,31 +2762,43 @@ msgid "ClusterIntegration|sign up"
msgstr "æ–°è¦ç™»éŒ²"
msgid "Code"
+msgstr "コード"
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
msgstr ""
msgid "Code owners"
msgstr "コードオーナー"
+msgid "CodeOwner|Pattern"
+msgstr "パターン"
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr "折りãŸãŸã‚€"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "サイドãƒãƒ¼ã‚’éš ã™"
msgid "Command line instructions"
-msgstr ""
+msgstr "コマンドライン命令"
msgid "Comment"
msgstr "コメント"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "コメントã—ã¦é–‰ã˜ã‚‹ %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "コメントã—ã¦å†é–‹ %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "ã“ã®æ¤œè¨Žã«ã‚³ãƒ¡ãƒ³ãƒˆã—解決ã™ã‚‹"
@@ -2423,13 +2817,13 @@ msgid_plural "Commits"
msgstr[0] "コミット"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "コミット %{commit_id}"
msgid "Commit Message"
msgstr "コミットメッセージ"
msgid "Commit deleted"
-msgstr ""
+msgstr "コミットを削除ã—ã¾ã—ãŸ"
msgid "Commit duration in minutes for last 30 commits"
msgstr "ç›´è¿‘30コミットã®æ‰€è¦æ™‚é–“(分)"
@@ -2485,6 +2879,9 @@ msgstr "コミット担当者: "
msgid "Commit…"
msgstr "コミット"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "比較"
@@ -2495,7 +2892,7 @@ msgid "Compare Revisions"
msgstr "リビジョンを比較"
msgid "Compare changes"
-msgstr ""
+msgstr "変更を比較"
msgid "Compare changes with the last commit"
msgstr "最後ã®ã‚³ãƒŸãƒƒãƒˆã¨å¤‰æ›´ã‚’比較"
@@ -2546,14 +2943,20 @@ msgid "Configure push mirrors."
msgstr "プッシュミラーを構æˆã—ã¾ã™ã€‚"
msgid "Configure storage path settings."
-msgstr ""
+msgstr "ストレージパス設定を構æˆã—ã¾ã™ã€‚"
msgid "Configure the %{link} integration."
-msgstr ""
+msgstr "%{link} ã®çµ±åˆã‚’設定ã—ã¾ã™ 。"
msgid "Configure the way a user creates a new account."
msgstr "ユーザーãŒæ–°ã—ã„アカウントを作æˆã™ã‚‹æ–¹æ³•ã‚’設定ã—ã¾ã™ã€‚"
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "接続"
@@ -2569,12 +2972,24 @@ msgstr "外部リãƒã‚¸ãƒˆãƒªã«æŽ¥ç¶šã—ã¦ã€æ–°ã—ã„コミットãŒã‚ã‚‹ã¨
msgid "Connecting..."
msgstr "接続ã—ã¦ã„ã¾ã™..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
+msgstr "アップグレードã®å•ã„åˆã‚ã›"
+
+msgid "Contact your Administrator to upgrade your license."
msgstr ""
msgid "Container Registry"
msgstr "コンテナレジストリ"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "作æˆæ¸ˆã¿"
@@ -2621,7 +3036,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr "レジストリイメージã¸ã®èª­ã¿å–り専用アクセスã®ãŸã‚ã« %{deploy_token} を使用ã§ãã¾ã™ã€‚"
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr ".gitlab-ci.yml ã®å†…容"
msgid "Continue"
msgstr "続行"
@@ -2638,11 +3053,14 @@ msgstr "GitLab ã«è²¢çŒ®ã™ã‚‹"
msgid "Contribution"
msgstr "貢献度"
+msgid "Contribution Analytics"
+msgstr "貢献度分æž"
+
msgid "Contribution Charts"
-msgstr ""
+msgstr "貢献ãƒãƒ£ãƒ¼ãƒˆ"
msgid "Contributions for <strong>%{calendar_date}</strong>"
-msgstr ""
+msgstr "<strong>%{calendar_date}</strong>ã®è²¢çŒ®"
msgid "Contributions per group member"
msgstr "グループメンãƒãƒ¼ã®è²¢çŒ®åº¦"
@@ -2663,25 +3081,16 @@ msgid "ContributorsPage|Please wait a moment, this page will automatically refre
msgstr "ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„ã€ã“ã®ãƒšãƒ¼ã‚¸ã¯æº–å‚™ãŒæ•´ã†ã¨è‡ªå‹•çš„ã«æ›´æ–°ã•ã‚Œã¾ã™ã€‚"
msgid "Control the display of third party offers."
-msgstr ""
-
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "ã“ã®ã‚»ã‚«ãƒ³ãƒ€ãƒªãƒŽãƒ¼ãƒ‰ã® LFS / attachment ãƒãƒƒã‚¯ãƒ•ã‚£ãƒ«ã®æœ€å¤§ä¸¦è¡Œæ€§ã‚’制御ã™ã‚‹"
+msgstr "サードパーティã®ã‚ªãƒ•ã‚¡ãƒ¼ã®è¡¨ç¤ºã‚’管ç†ã—ã¾ã™ã€‚"
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "ã“ã®ã‚»ã‚«ãƒ³ãƒ€ãƒªãƒŽãƒ¼ãƒ‰ã®ãƒªãƒã‚¸ãƒˆãƒªãƒãƒƒã‚¯ãƒ•ã‚£ãƒ«ã®æœ€å¤§åŒæ™‚実行性を制御ã™ã‚‹"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "ã“ã® Geo ノードã®æ¤œè¨¼æ“作ã®æœ€å¤§åŒæ™‚実行数を制御ã™ã‚‹"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "ConvDev インデックス"
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "%{http_label} クローン URL をコピー"
msgid "Copy %{protocol} clone URL"
msgstr "%{protocol} クローン URL をコピー"
@@ -2689,11 +3098,14 @@ msgstr "%{protocol} クローン URL をコピー"
msgid "Copy ID to clipboard"
msgstr "クリップボード㫠ID をコピー"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "SSH 用クローン URL をコピー"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "SSH 公開éµã‚’コピー"
msgid "Copy SSH public key to clipboard"
msgstr "SSH 公開éµã‚’クリップボードã«ã‚³ãƒ”ー"
@@ -2713,9 +3125,6 @@ msgstr "コミットã®SHAをクリップボードã«ã‚³ãƒ”ー"
msgid "Copy file path to clipboard"
msgstr "ファイルパスをクリップボードã«ã‚³ãƒ”ーã™ã‚‹"
-msgid "Copy incoming email address to clipboard"
-msgstr "å—信用メールアドレスをクリップボードã«ã‚³ãƒ”ーã—ã¾ã™ã€‚"
-
msgid "Copy link"
msgstr "リンクをコピー"
@@ -2731,9 +3140,33 @@ msgstr "クリップボードã«ã‚³ãƒ”ー"
msgid "Copy token to clipboard"
msgstr "トークンをクリップボードã«ã‚³ãƒ”ーã—ã¾ã™"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
+msgstr "パイプラインステータスをå–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚トラブルシューティングã®æ‰‹é †ã«ã¤ã„ã¦ã¯ã€ %{linkStart} マニュアルをå‚ç…§ã—ã¦ãã ã•ã„。%{linkEnd}"
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr ""
+msgid "Coverage"
+msgstr "ã‚«ãƒãƒ¬ãƒƒã‚¸"
+
msgid "Create"
msgstr "作æˆ"
@@ -2741,23 +3174,26 @@ msgid "Create New Directory"
msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’作æˆ"
msgid "Create New Domain"
+msgstr "æ–°ã—ã„ドメインを作æˆ"
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
msgid "Create a new branch"
msgstr "æ–°ã—ã„ブランãƒã‚’作æˆ"
-msgid "Create a new branch and merge request"
-msgstr "ブランãƒã®æ–°è¦ä½œæˆã¨ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
-
msgid "Create a new issue"
msgstr "課題ã®æ–°è¦ä½œæˆ"
msgid "Create a new repository"
-msgstr ""
+msgstr "æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’作æˆ"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "%{protocol} ã§ãƒ—ッシュやプルã™ã‚‹ãŸã‚ã®ã‚ãªãŸå€‹äººç”¨ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã‚’作æˆ"
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr "課題を作æˆã™ã‚‹ã€‚課題ã¯ç™ºç”Ÿã—ãŸå„アラート毎ã«ä½œæˆã•ã‚Œã¾ã™ã€‚"
+
msgid "Create branch"
msgstr "ブランãƒä½œæˆ"
@@ -2782,9 +3218,6 @@ msgstr "グループを作æˆ"
msgid "Create group label"
msgstr "グループラベルを作æˆ"
-msgid "Create issue"
-msgstr "課題を作æˆ"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "ラベルã‹ã‚‰ãƒªã‚¹ãƒˆã‚’作æˆã€‚ãã®ãƒ©ãƒ™ãƒ«ã®èª²é¡ŒãŒãƒªã‚¹ãƒˆã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚"
@@ -2795,7 +3228,7 @@ msgid "Create merge request and branch"
msgstr "マージリクエストã¨ãƒ–ランãƒã‚’作æˆ"
msgid "Create milestone"
-msgstr ""
+msgstr "マイルストーンを作æˆ"
msgid "Create new branch"
msgstr "æ–°ã—ã„ブランãƒã‚’作æˆ"
@@ -2831,7 +3264,7 @@ msgid "Created"
msgstr "作æˆæ¸ˆã¿"
msgid "Created At"
-msgstr ""
+msgstr "作æˆæ—¥"
msgid "Created by me"
msgstr "自分ãŒä½œæˆ"
@@ -2854,6 +3287,9 @@ msgstr "Cron ã®æ§‹æ–‡"
msgid "Current Branch"
msgstr "ç¾åœ¨ã®ãƒ–ランãƒ"
+msgid "Current Project"
+msgstr "ç¾åœ¨ã®ãƒ—ロジェクト"
+
msgid "Current node"
msgstr "ç¾åœ¨ã®ãƒŽãƒ¼ãƒ‰"
@@ -2867,7 +3303,7 @@ msgid "Custom CI config path"
msgstr "カスタム CI config パス"
msgid "Custom hostname (for private commit emails)"
-msgstr ""
+msgstr "カスタムホストå (プライベートコミット用メールアドレス)"
msgid "Custom notification events"
msgstr "カスタム通知設定"
@@ -2879,7 +3315,7 @@ msgid "Custom project templates"
msgstr "カスタムプロジェクトテンプレート"
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
-msgstr ""
+msgstr "カスタムプロジェクトテンプレートã¯ã€ã‚ãªãŸãŒãƒ¡ãƒ³ãƒãƒ¼ã«ãªã£ã¦ã„るグループã«ã¯è¨­å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。ãれらã¯ã‚°ãƒ«ãƒ¼ãƒ—ã®è¨­å®šãƒšãƒ¼ã‚¸ã‹ã‚‰æœ‰åŠ¹ã«ãªã‚Šã¾ã™ã€‚グループã®ã‚ªãƒ¼ãƒŠãƒ¼ã¾ãŸã¯ãƒ¡ãƒ³ãƒ†ãƒŠãƒ¼ã«é€£çµ¡ã—ã¦ã€ã‚«ã‚¹ã‚¿ãƒ ãƒ—ロジェクトテンプレートを設定ã—ã¦ãã ã•ã„。"
msgid "Customize colors"
msgstr "カスタムカラー"
@@ -2893,9 +3329,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Runner トークンã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã€ãƒ‘イプラインã®è¨­å®šã‚’カスタマイズã€ãã—ã¦ãƒ‘イプラインã®çŠ¶æ…‹ã¨ã‚«ãƒãƒ¬ãƒƒã‚¸ãƒ¬ãƒãƒ¼ãƒˆã‚’閲覧ã—ã¾ã™ã€‚"
@@ -2927,7 +3360,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "テスト"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "ダッシュボード"
@@ -2938,11 +3371,14 @@ msgstr "ã™ã¹ã¦"
msgid "DashboardProjects|Personal"
msgstr "個人"
-msgid "Data is still calculating..."
+msgid "Dashboards"
msgstr ""
+msgid "Data is still calculating..."
+msgstr "データã¯ã¾ã è¨ˆç®—中ã§ã™..."
+
msgid "Date picker"
-msgstr ""
+msgstr "日付é¸æŠž"
msgid "Debug"
msgstr "デãƒãƒƒã‚°"
@@ -2954,7 +3390,7 @@ msgid "December"
msgstr "12月"
msgid "Decline"
-msgstr ""
+msgstr "許å¯ã—ãªã„"
msgid "Decline and sign out"
msgstr "辞退ã—ã¦ã‚µã‚¤ãƒ³ã‚¢ã‚¦ãƒˆ"
@@ -2962,13 +3398,22 @@ msgstr "辞退ã—ã¦ã‚µã‚¤ãƒ³ã‚¢ã‚¦ãƒˆ"
msgid "Default Branch"
msgstr "デフォルトブランãƒ"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "デフォルト分類ラベル"
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
+msgstr "カレンダーã¨æ—¥ä»˜ã®è¨­å®šã§ä½¿ç”¨ã™ã‚‹ã€é€±ã®æœ€åˆã®æ—¥(デフォルト)"
+
+msgid "Default issue template"
msgstr ""
msgid "Default: Directly import the Google Code email address or username"
@@ -2987,10 +3432,10 @@ msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwis
msgstr ""
msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
-msgstr ""
+msgstr "%{job_name} ã‚’ã™ãã«å®Ÿè¡Œã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿã“ã®ã‚¸ãƒ§ãƒ–ã¯ã‚¿ã‚¤ãƒžãƒ¼çµ‚了後自動的ã«å®Ÿè¡Œã•ã‚Œã¾ã™ã€‚"
msgid "DelayedJobs|Start now"
-msgstr ""
+msgstr "今ã™ã始ã‚ã‚‹"
msgid "DelayedJobs|Unschedule"
msgstr ""
@@ -3002,7 +3447,7 @@ msgid "Delete"
msgstr "削除"
msgid "Delete Package"
-msgstr ""
+msgstr "パッケージを削除"
msgid "Delete Snippet"
msgstr "スニペットを削除"
@@ -3010,18 +3455,36 @@ msgstr "スニペットを削除"
msgid "Delete comment"
msgstr "コメントを削除"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "リストを削除ã™ã‚‹"
msgid "Delete source branch"
-msgstr ""
+msgstr "ソースブランãƒã‚’削除"
msgid "Delete this attachment"
-msgstr ""
+msgstr "ã“ã®æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚’削除"
msgid "Deleted"
msgstr "削除完了"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "æ‹’å¦"
@@ -3032,6 +3495,9 @@ msgstr[0] "デプロイ"
msgid "Deploy Keys"
msgstr "デプロイキー"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+ %{count} プロジェクト"
@@ -3153,13 +3619,13 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "æ–°ã—ã„プロジェクトデプロイトークンãŒä½œæˆã•ã‚Œã¾ã—ãŸã€‚"
msgid "Deployed"
-msgstr ""
+msgstr "デプロイ済ã¿"
msgid "Deployed to"
-msgstr ""
+msgstr "デプロイ先"
msgid "Deploying to"
-msgstr ""
+msgstr "デプロイ先"
msgid "Deprioritize label"
msgstr "éžå„ªå…ˆãƒ©ãƒ™ãƒ«"
@@ -3170,12 +3636,45 @@ msgstr "é™é †"
msgid "Description"
msgstr "説明"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "Description テンプレートを使用ã™ã‚‹ã¨ã€ãƒ—ロジェクトã®èª²é¡ŒãŠã‚ˆã³ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆèª¬æ˜Žã«å¯¾ã™ã‚‹ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆå›ºæœ‰ã®ãƒ†ãƒ³ãƒ—レートを定義ã§ãã¾ã™ã€‚"
msgid "Description:"
msgstr "説明:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr "デザイン"
+
msgid "Destroy"
msgstr "破棄"
@@ -3183,16 +3682,16 @@ msgid "Details"
msgstr "詳細"
msgid "Details (default)"
-msgstr ""
+msgstr "詳細 (デフォルト)"
msgid "Detect host keys"
-msgstr ""
+msgstr "ホストキーã®æ¤œå‡º"
msgid "Diff content limits"
-msgstr ""
+msgstr "差分コンテンツã®åˆ¶é™"
msgid "Diff limits"
-msgstr ""
+msgstr "Diff ã®åˆ¶é™"
msgid "Diffs|No file name available"
msgstr "使用å¯èƒ½ãªãƒ•ã‚¡ã‚¤ãƒ«åãŒã‚ã‚Šã¾ã›ã‚“"
@@ -3216,19 +3715,19 @@ msgid "Disable group Runners"
msgstr "グループ Runner を無効ã«ã™ã‚‹"
msgid "Disable shared Runners"
-msgstr ""
+msgstr "共有 Runner を無効化"
msgid "Disabled"
-msgstr ""
+msgstr "無効"
msgid "Discard"
msgstr "破棄"
msgid "Discard all changes"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®å¤‰æ›´ã‚’破棄"
msgid "Discard all unstaged changes?"
-msgstr ""
+msgstr "ステージã•ã‚Œã¦ã„ãªã„ã™ã¹ã¦ã®å¤‰æ›´ã‚’破棄ã—ã¾ã™ã‹ï¼Ÿ"
msgid "Discard changes"
msgstr "変更を破棄ã™ã‚‹"
@@ -3240,7 +3739,7 @@ msgid "Discard draft"
msgstr "下書ãを破棄"
msgid "Discard review"
-msgstr ""
+msgstr "レビューを破棄"
msgid "Discover GitLab Geo"
msgstr ""
@@ -3254,6 +3753,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr "検討"
+
msgid "Dismiss"
msgstr "解除"
@@ -3264,10 +3766,10 @@ msgid "Dismiss Cycle Analytics introduction box"
msgstr "サイクル分æžã®ç´¹ä»‹ã‚’é–‰ã˜ã‚‹"
msgid "Dismiss Merge Request promotion"
-msgstr ""
+msgstr "マージリクエストã®æ˜‡æ ¼ã‚’å´ä¸‹ã—ã¾ã™"
msgid "Dismiss trial promotion"
-msgstr ""
+msgstr "トライアルプロモーションを閉ã˜ã‚‹"
msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
msgstr "Google コードã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ GitLab ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã™ã‚‹æ–¹æ³•ã‚’カスタマイズã—ã¾ã™ã‹ï¼Ÿ"
@@ -3288,25 +3790,31 @@ msgid "Download"
msgstr "ダウンロード"
msgid "Download artifacts"
-msgstr ""
+msgstr "æˆæžœç‰©ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
msgid "Download asset"
+msgstr "アセットをダウンロード"
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
msgstr ""
msgid "Download tar"
-msgstr "tarå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "tar.bz2å½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+msgstr ""
msgid "Download tar.gz"
-msgstr "tar.gzå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+msgstr ""
msgid "Download zip"
-msgstr "zipå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "ダウンロード"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "パッãƒã‚’メールã§é€ä¿¡"
@@ -3318,7 +3826,7 @@ msgid "DownloadSource|Download"
msgstr "ダウンロード"
msgid "Downstream"
-msgstr ""
+msgstr "ダウンストリーム"
msgid "Downvotes"
msgstr "イマイãƒ"
@@ -3336,13 +3844,16 @@ msgid "Edit"
msgstr "編集"
msgid "Edit %{name}"
-msgstr ""
+msgstr "%{name} を編集"
+
+msgid "Edit Deploy Key"
+msgstr "デプロイキーã®ç·¨é›†"
msgid "Edit Label"
msgstr "ラベルã®ç·¨é›†"
msgid "Edit Milestone"
-msgstr ""
+msgstr "マイルストーンを編集"
msgid "Edit Pipeline Schedule %{id}"
msgstr "パイプラインスケジュール %{id} を編集"
@@ -3354,10 +3865,13 @@ msgid "Edit application"
msgstr "アプリケーションã®ç·¨é›†"
msgid "Edit comment"
-msgstr ""
+msgstr "コメントを編集"
msgid "Edit environment"
-msgstr ""
+msgstr "環境を編集"
+
+msgid "Edit file"
+msgstr "ファイルã®ç·¨é›†"
msgid "Edit files in the editor and commit changes here"
msgstr "エディターã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’編集ã—ã€ã“ã“ã§å¤‰æ›´ã‚’コミットã—ã¾ã™"
@@ -3369,20 +3883,56 @@ msgid "Edit identity for %{user_name}"
msgstr "%{user_name} ã® ID を編集ã™ã‚‹"
msgid "Edit issues"
-msgstr ""
+msgstr "課題を編集"
+
+msgid "Edit public deploy key"
+msgstr "公開デプロイキーã®ç·¨é›†"
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr "Elasticsearch ã®çµ±åˆã€‚Elasticsearch AWS IAM。"
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
msgstr ""
msgid "Email"
msgstr "メール"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "パッãƒã‚’メールã™ã‚‹"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "メール"
@@ -3390,7 +3940,7 @@ msgid "Embed"
msgstr "埋ã‚è¾¼ã¿"
msgid "Empty file"
-msgstr ""
+msgstr "空ã®ãƒ•ã‚¡ã‚¤ãƒ«"
msgid "Enable"
msgstr "有効化ã™ã‚‹"
@@ -3398,6 +3948,9 @@ msgstr "有効化ã™ã‚‹"
msgid "Enable Auto DevOps"
msgstr "Auto DevOps を有効ã«ã™ã‚‹"
+msgid "Enable HTML emails"
+msgstr "HTML メールã®æœ‰åŠ¹åŒ–"
+
msgid "Enable Pseudonymizer data collection"
msgstr "匿å化データã®åŽé›†ã‚’有効ã«ã—ã¾ã™"
@@ -3417,7 +3970,7 @@ msgid "Enable classification control using an external service"
msgstr "外部サービスを使用ã—ã¦ã€åˆ†é¡žåˆ¶å¾¡ã‚’有効ã«ã™ã‚‹ã€‚"
msgid "Enable error tracking"
-msgstr ""
+msgstr "エラートラッキングを有効ã«ã™ã‚‹"
msgid "Enable for this project"
msgstr "ã“ã®ãƒ—ロジェクトã§ã¯æœ‰åŠ¹ã«ã™ã‚‹"
@@ -3425,6 +3978,12 @@ msgstr "ã“ã®ãƒ—ロジェクトã§ã¯æœ‰åŠ¹ã«ã™ã‚‹"
msgid "Enable group Runners"
msgstr "グループ Runner を有効ã«ã™ã‚‹"
+msgid "Enable header and footer in emails"
+msgstr "メールã®ãƒ˜ãƒƒãƒ€ãƒ¼ã¨ãƒ•ãƒƒã‚¿ãƒ¼ã‚’有効ã«ã™ã‚‹"
+
+msgid "Enable mirror configuration"
+msgstr "ミラー構æˆã‚’有効化"
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3434,34 +3993,43 @@ msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãƒã‚§ãƒƒã‚¯ã¨ä½¿ç”¨çŠ¶æ³ã® ping を有効ã«ã™ã‚‹ã€‚
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "reCAPTCHA ã¾ãŸã¯ Akismet ã®æœ‰åŠ¹ã€ãŠã‚ˆã³ IP 制é™ã‚’設定ã™ã‚‹ã€‚"
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
-msgstr ""
+msgstr "共有 Runner を有効化"
msgid "Enable the Performance Bar for a given group."
msgstr "指定グループã®ãƒ‘フォーマンスãƒãƒ¼ã‚’有効ã«ã™ã‚‹ã€‚"
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "2è¦ç´ èªè¨¼ã‚’有効ã«ã™ã‚‹"
msgid "Enable usage ping"
msgstr "利用状æ³ã®é€ä¿¡ã‚’有効ã«ã™ã‚‹"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr "Ping ã®ä½¿ç”¨ã‚’有効ã«ã™ã‚‹ã¨ã€æ©Ÿèƒ½ãƒ‘ースペクティブã‹ã‚‰ GitLab ã‚’ã©ã®ã‚ˆã†ã«ä½¿ç”¨ã—ã¦ã„ã‚‹ã‹ã‚’知るã“ã¨ãŒã§ãã¾ã™ã€‚"
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
msgstr ""
msgid "Enabled"
msgstr "有効"
-msgid "Ends at (UTC)"
-msgstr "終了時刻 (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr "終了時刻 (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3471,34 +4039,28 @@ msgid "Enter the issue description"
msgstr "課題ã®èª¬æ˜Žã‚’入力ã—ã¦ãã ã•ã„"
msgid "Enter the issue title"
-msgstr ""
+msgstr "課題ã®ã‚¿ã‚¤ãƒˆãƒ«ã‚’入力ã—ã¦ãã ã•ã„"
msgid "Enter the merge request description"
-msgstr ""
+msgstr "マージリクエストã®èª¬æ˜Žã‚’入力ã—ã¦ãã ã•ã„"
msgid "Enter the merge request title"
-msgstr ""
-
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
+msgstr "マージリクエストã®ã‚¿ã‚¤ãƒˆãƒ«ã‚’入力ã—ã¦ãã ã•ã„"
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
msgid "Environment:"
-msgstr ""
+msgstr "環境:"
msgid "Environments"
msgstr "環境"
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
-msgstr ""
+msgstr "環境ã¯ã‚¢ãƒ—リケーションã®ãƒ‡ãƒ—ロイを追加ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ %{link_to_read_more}"
msgid "Environments|An error occurred while fetching the environments."
msgstr "環境をå–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -3506,17 +4068,23 @@ msgstr "環境をå–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "Environments|An error occurred while making the request."
msgstr "リクエスト作æˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
-msgid "Environments|An error occurred while stopping the environment, please try again"
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
msgstr ""
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr "環境ã®ãƒ­ãƒ¼ãƒ«ãƒãƒƒã‚¯ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„"
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr "環境ã®åœæ­¢ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„"
+
msgid "Environments|Are you sure you want to stop this environment?"
-msgstr ""
+msgstr "ã“ã®ç’°å¢ƒã‚’åœæ­¢ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Environments|Commit"
msgstr "コミット"
msgid "Environments|Deploy to..."
-msgstr ""
+msgstr "デプロイã—ã¾ã™"
msgid "Environments|Deployment"
msgstr "デプロイ"
@@ -3534,7 +4102,7 @@ msgid "Environments|Job"
msgstr "ジョブ"
msgid "Environments|Learn more about stopping environments"
-msgstr ""
+msgstr "環境ã®åœæ­¢ã«ã¤ã„ã¦"
msgid "Environments|New environment"
msgstr "æ–°ã—ã„環境"
@@ -3552,18 +4120,36 @@ msgid "Environments|Note that this action will stop the environment, but it will
msgstr ""
msgid "Environments|Open live environment"
-msgstr ""
+msgstr "ライブ環境を開ã"
msgid "Environments|Pod logs from"
msgstr " 㮠pod ログ"
-msgid "Environments|Re-deploy to environment"
+msgid "Environments|Re-deploy"
+msgstr "å†ãƒ‡ãƒ—ロイ"
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
msgstr ""
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr "環境ã«å†ãƒ‡ãƒ—ロイ"
+
msgid "Environments|Read more about environments"
msgstr "環境ã®è©³ç´°ã«ã¤ã„ã¦èª­ã‚€"
+msgid "Environments|Rollback"
+msgstr "ロールãƒãƒƒã‚¯"
+
msgid "Environments|Rollback environment"
+msgstr "環境ã®ãƒ­ãƒ¼ãƒ«ãƒãƒƒã‚¯"
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
msgstr ""
msgid "Environments|Show all"
@@ -3576,6 +4162,18 @@ msgid "Environments|Stop environment"
msgstr "環境ã®åœæ­¢"
msgid "Environments|Stopping"
+msgstr "åœæ­¢ä¸­"
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|Updated"
@@ -3600,10 +4198,10 @@ msgid "Epics let you manage your portfolio of projects more efficiently and with
msgstr "エピックを使用ã™ã‚‹ã¨ã€ãƒ—ロジェクトã®ãƒãƒ¼ãƒˆãƒ•ã‚©ãƒªã‚ªã‚’より効率的ã‹ã¤å°‘ãªã„労力ã§ç®¡ç†ã§ãã¾ã™"
msgid "Epics|An error occurred while saving the %{epicDateType} date"
-msgstr ""
+msgstr "%{epicDateType} ã®ä¿å­˜ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "Epics|How can I solve this?"
-msgstr ""
+msgstr "ã©ã†ã™ã‚Œã°è§£æ±ºã§ãã¾ã™ã‹ï¼Ÿ"
msgid "Epics|More information"
msgstr "詳ã—ã„情報"
@@ -3612,10 +4210,10 @@ msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from
msgstr ""
msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
-msgstr ""
+msgstr "マイルストーンã«åŸºã¥ã„ãŸã‚¨ãƒ”ック㮠%{epicDateType} をスケジューリングã™ã‚‹ã«ã¯ã€ã‚¨ãƒ”ックã®èª²é¡Œã« %{epicDateType} ã®ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã‚’割り当ã¦ã¾ã™ã€‚"
msgid "Epics|due"
-msgstr ""
+msgstr "期日"
msgid "Epics|start"
msgstr "開始"
@@ -3627,6 +4225,9 @@ msgid "Error Reporting and Logging"
msgstr "エラー報告ã¨ãƒ­ã‚°"
msgid "Error Tracking"
+msgstr "エラートラッキング"
+
+msgid "Error creating a new path"
msgstr ""
msgid "Error creating epic"
@@ -3677,6 +4278,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr "通知購読ã®åˆ‡ã‚Šæ›¿ãˆã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3692,15 +4305,51 @@ msgstr "ã™ã¹ã¦ã® TODO ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’更新中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—
msgid "Error updating todo status."
msgstr "TODO ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’更新中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
+msgstr "マージリクエストã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
msgstr ""
msgid "Error:"
+msgstr "エラー:"
+
+msgid "ErrorTracking|Active"
msgstr ""
-msgid "Errors"
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
msgstr ""
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr "エラー"
+
msgid "Estimated"
msgstr "見ç©"
@@ -3723,7 +4372,7 @@ msgid "EventFilterBy|Filter by team"
msgstr "ãƒãƒ¼ãƒ ã§ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼"
msgid "Events"
-msgstr ""
+msgstr "イベント"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -3738,7 +4387,7 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "毎週 (日曜日ã®åˆå‰4:00)"
msgid "Everyone"
-msgstr ""
+msgstr "全員"
msgid "Everyone can contribute"
msgstr ""
@@ -3759,16 +4408,10 @@ msgid "Everything you need to create a GitLab Pages site using plain HTML."
msgstr ""
msgid "Except policy:"
-msgstr ""
-
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
+msgstr "除外ãƒãƒªã‚·ãƒ¼:"
msgid "Existing members and groups"
-msgstr ""
+msgstr "既存ã®ãƒ¡ãƒ³ãƒãƒ¼ã¨ã‚°ãƒ«ãƒ¼ãƒ—"
msgid "Expand"
msgstr "展開"
@@ -3776,20 +4419,26 @@ msgstr "展開"
msgid "Expand all"
msgstr "ã™ã¹ã¦å±•é–‹"
+msgid "Expand approvers"
+msgstr "承èªè€…を展開"
+
msgid "Expand sidebar"
msgstr "サイドãƒãƒ¼ã‚’é–‹ã"
msgid "Expiration date"
msgstr "有効期é™"
-msgid "Expired %{expiredOn}"
+msgid "Expired"
msgstr ""
+msgid "Expired %{expiredOn}"
+msgstr "%{expiredOn} ã«æœŸé™åˆ‡ã‚Œ"
+
msgid "Expires in %{expires_at}"
-msgstr ""
+msgstr "%{expires_at} ã§æœŸé™åˆ‡ã‚Œ"
msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
-msgstr ""
+msgstr "å•é¡Œã‚’説明ã—ã¦ãã ã•ã„。必è¦ã«å¿œã˜ã¦ã€è©²å½“ã™ã‚‹èª²é¡Œã¾ãŸã¯ã‚³ãƒ¡ãƒ³ãƒˆã¸ã®ãƒªãƒ³ã‚¯ã‚’æä¾›ã—ã¦ãã ã•ã„。"
msgid "Explore"
msgstr ""
@@ -3810,19 +4459,25 @@ msgid "Explore public groups"
msgstr "公開グループを検索"
msgid "Export as CSV"
-msgstr ""
+msgstr "CSV å½¢å¼ã§ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ"
msgid "Export issues"
+msgstr "課題をエクスãƒãƒ¼ãƒˆ"
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
msgstr ""
msgid "External Classification Policy Authorization"
msgstr "外部分類èªè¨¼ãƒãƒªã‚·ãƒ¼"
msgid "External URL"
-msgstr ""
+msgstr "外部 URL"
msgid "External Wiki"
-msgstr ""
+msgstr "外部 Wiki"
msgid "External authentication"
msgstr "外部èªè¨¼"
@@ -3833,9 +4488,6 @@ msgstr "ã“ã®ãƒ—ロジェクトã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’æ‹’å¦ã™ã‚‹å¤–部èªè¨¼"
msgid "External authorization request timeout"
msgstr "外部èªè¨¼å‡¦ç†ãŒã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸ"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "分類ラベル"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "分類ラベル"
@@ -3857,48 +4509,75 @@ msgstr "オーナーを変更ã§ãã¾ã›ã‚“ã§ã—ãŸ"
msgid "Failed to check related branches."
msgstr "関連ã™ã‚‹ãƒ–ランãƒã®ç¢ºèªã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
msgstr "ボードã®èª²é¡Œã®å‰Šé™¤ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
msgid "Failed to remove mirror."
-msgstr ""
+msgstr "ミラーã®å‰Šé™¤ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
msgid "Failed to remove the pipeline schedule"
msgstr "パイプラインスケジュールを削除ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
-msgid "Failed to signing using smartcard authentication"
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
msgstr ""
+msgid "Failed to signing using smartcard authentication"
+msgstr "スマートカードèªè¨¼ã‚’使用ã—ã¦ã®ç½²åã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
msgid "Failed to update issues, please try again."
msgstr "課題ã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
msgid "Failed to upload object map file"
-msgstr ""
+msgstr "オブジェクトマップファイルをアップロードã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
msgid "Failure"
msgstr "失敗"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
-msgid "Feature Flags"
+msgid "Favicon was successfully removed."
msgstr ""
+msgid "Feature Flags"
+msgstr "機能フラグ"
+
msgid "FeatureFlags|* (All Environments)"
msgstr ""
@@ -3909,7 +4588,7 @@ msgid "FeatureFlags|API URL"
msgstr "API URL"
msgid "FeatureFlags|Active"
-msgstr ""
+msgstr "アクティブ"
msgid "FeatureFlags|Configure"
msgstr ""
@@ -3918,10 +4597,10 @@ msgid "FeatureFlags|Configure feature flags"
msgstr ""
msgid "FeatureFlags|Create feature flag"
-msgstr ""
+msgstr "機能フラグを作æˆ"
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "%{name} を削除ã—ã¾ã™ã‹ï¼Ÿ"
msgid "FeatureFlags|Delete feature flag"
msgstr ""
@@ -3929,9 +4608,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "説明"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "%{feature_flag_name} を編集"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "機能フラグを編集"
@@ -3948,19 +4624,19 @@ msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules
msgstr ""
msgid "FeatureFlags|Feature Flags"
-msgstr ""
+msgstr "機能フラグ"
msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
-msgstr ""
+msgstr "機能フラグを有効ã«ã™ã‚‹ã¨ã€ç‰¹å®šã®æ©Ÿèƒ½ã¸ã®å‹•çš„切り替ãˆã«ã‚ˆã£ã¦ã‚³ãƒ¼ãƒ‰ã«ç•°ãªã‚‹ãƒ•ãƒ¬ãƒ¼ãƒãƒ¼ã‚’設定ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
-msgstr ""
+msgstr "機能フラグ%{name} ãŒã€å‰Šé™¤ã•ã‚Œã¾ã™ã€‚本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ"
msgid "FeatureFlags|Get started with Feature Flags"
-msgstr ""
+msgstr "機能フラグを使ã„ã¯ã˜ã‚ã‚‹"
msgid "FeatureFlags|Inactive"
-msgstr ""
+msgstr "無効"
msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr ""
@@ -3972,10 +4648,10 @@ msgid "FeatureFlags|Instance ID"
msgstr "インスタンス ID"
msgid "FeatureFlags|Loading Feature Flags"
-msgstr ""
+msgstr "機能フラグを読ã¿è¾¼ã‚“ã§ã„ã¾ã™"
msgid "FeatureFlags|More Information"
-msgstr ""
+msgstr "詳細情報"
msgid "FeatureFlags|More information"
msgstr "詳細情報"
@@ -3987,10 +4663,10 @@ msgid "FeatureFlags|New"
msgstr ""
msgid "FeatureFlags|New Feature Flag"
-msgstr ""
+msgstr "æ–°ã—ã„機能フラグ"
-msgid "FeatureFlags|Save changes"
-msgstr "変更をä¿å­˜"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "ステータス"
@@ -3999,10 +4675,10 @@ msgid "FeatureFlags|Target environments"
msgstr ""
msgid "FeatureFlags|There are no active Feature Flags"
-msgstr ""
+msgstr "有効ãªæ©Ÿèƒ½ãƒ•ãƒ©ã‚°ã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "FeatureFlags|There are no inactive Feature Flags"
-msgstr ""
+msgstr "無効ãªæ©Ÿèƒ½ãƒ•ãƒ©ã‚°ã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
@@ -4016,33 +4692,45 @@ msgstr "2月"
msgid "February"
msgstr "2月"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã®é …ç›®ã¯ç·¨é›†ã§ããªã„設定ã§ã™ã€‚次㮠Kubernetes クラスターを設定ã§ãã¾ã™ã€‚"
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
+msgstr[0] "ファイル"
msgid "File added"
-msgstr ""
+msgstr "ファイルã®è¿½åŠ "
msgid "File browser"
-msgstr ""
+msgstr "ファイルブラウザー"
msgid "File deleted"
-msgstr ""
+msgstr "ファイルを削除ã—ã¾ã—ãŸ"
msgid "File mode changed from %{a_mode} to %{b_mode}"
msgstr ""
msgid "File moved"
-msgstr ""
+msgstr "ファイルを移動ã—ã¾ã—ãŸã€‚"
msgid "File templates"
msgstr "ファイルテンプレート"
msgid "File upload error."
-msgstr ""
+msgstr "ファイルアップロードエラー"
msgid "Files"
msgstr "ファイル"
@@ -4077,38 +4765,38 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "パスã§æ¤œç´¢"
msgid "Find existing members by name"
-msgstr ""
+msgstr "既存ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’åå‰ã§æŽ¢ã™"
msgid "Find file"
msgstr "ファイルを検索"
msgid "Find the downloaded ZIP file and decompress it."
-msgstr ""
+msgstr "ダウンロードã—㟠ZIP ファイルを展開ã—ã¾ã™ã€‚"
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr "フィンガープリント"
+
msgid "Fingerprints"
-msgstr ""
+msgstr "フィンガープリント"
msgid "Finish editing this message first!"
msgstr ""
msgid "Finish review"
-msgstr ""
+msgstr "レビューを完了ã™ã‚‹"
msgid "Finished"
msgstr "完了"
msgid "First day of the week"
-msgstr ""
+msgstr "一週間ã®é–‹å§‹æ›œæ—¥"
msgid "FirstPushedBy|First"
msgstr "åˆå›ž"
@@ -4126,7 +4814,7 @@ msgid "Fixed start date"
msgstr ""
msgid "Fixed:"
-msgstr ""
+msgstr "修正:"
msgid "FogBugz Email"
msgstr "FogBugz メール"
@@ -4144,7 +4832,7 @@ msgid "FogBugz import"
msgstr "FogBugz ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
msgid "Follow the steps below to export your Google Code project data."
-msgstr ""
+msgstr "Google コードã®ãƒ—ロジェクトデータをエクスãƒãƒ¼ãƒˆã™ã‚‹ã«ã¯ã€ä»¥ä¸‹ã®æ‰‹é †ã«å¾“ã£ã¦ãã ã•ã„。"
msgid "Font Color"
msgstr "フォントã®è‰²"
@@ -4156,7 +4844,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr ""
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "詳細ã«ã¤ã„ã¦ã¯ã€ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’読んã§ãã ã•ã„。"
msgid "For more information, go to the "
msgstr ""
@@ -4183,19 +4871,25 @@ msgid "Forking in progress"
msgstr "フォーク中ã§ã™"
msgid "Forks"
-msgstr ""
+msgstr "フォーク"
msgid "Format"
msgstr "フォーマット"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ".gitlab-ci.yml ã«ã‚¨ãƒ©ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ:"
msgid "Free Trial of GitLab.com Gold"
-msgstr ""
+msgstr "GitLab.com Gold プランã®ç„¡å„Ÿè©¦ç”¨"
-msgid "From %{provider_title}"
-msgstr "%{provider_title}ã‹ã‚‰"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "Bitbucket ã‹ã‚‰"
@@ -4224,12 +4918,21 @@ msgstr "マイルストーンã‹ã‚‰:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Kubernetes クラスターã®è©³ç´°ç”»é¢ã‚’介ã—ã¦ã€ã‚¢ãƒ—リケーションリストã‹ã‚‰ Runner をインストールã—ã¾ã™ã€‚"
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG キー"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "一般"
+msgid "General Settings"
+msgstr "全般設定"
+
msgid "General pipelines"
msgstr "一般ã®ãƒ‘イプライン"
@@ -4237,6 +4940,9 @@ msgid "Generate a default set of labels"
msgstr "åˆæœŸè¨­å®šãƒ©ãƒ™ãƒ«ã‚»ãƒƒãƒˆã‚’生æˆã™ã‚‹"
msgid "Generate key"
+msgstr "éµã‚’生æˆ"
+
+msgid "Generate new export"
msgstr ""
msgid "Geo"
@@ -4263,9 +4969,6 @@ msgstr "データ㯠%{timeago} ã‹ã‚‰å¤ããªã£ã¦ã„ã¾ã™"
msgid "GeoNodes|Data replication lag"
msgstr "データ レプリケーションã®é…延"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "ノードを無効ã«ã™ã‚‹ã¨åŒæœŸå‡¦ç†ã‚‚åœæ­¢ã—ã¾ã™ã€‚本当ã«ç„¡åŠ¹ã«ã—ã¾ã™ã‹ï¼Ÿ"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "プライマリストレージ構æˆã¨ä¸€è‡´ã—ã¾ã›ã‚“"
@@ -4284,6 +4987,9 @@ msgstr "GitLab ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒå„ªå…ˆãƒŽãƒ¼ãƒ‰ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¨ä¸€è‡´ã
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "カーソルã§å‡¦ç†ã•ã‚ŒãŸæœ€çµ‚イベント ID"
@@ -4329,6 +5035,9 @@ msgstr "ãƒã‚§ãƒƒã‚¯ã‚µãƒ ãªã—"
msgid "GeoNodes|Out of sync"
msgstr "åŒæœŸå¯¾è±¡å¤–"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4405,7 +5114,7 @@ msgid "GeoNodes|Wikis checksummed for verification with their counterparts on Se
msgstr "セカンダリノードã®å¯¾å¿œ Wiki ã¨ã®ãƒã‚§ãƒƒã‚¯ã‚µãƒ ãŒæ¤œè¨¼æ¸ˆã¿ã® Wiki"
msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
-msgstr ""
+msgstr "プライマリノードã®å¯¾å¿œ Wiki ã¨æ¤œè¨¼ã•ã‚ŒãŸ Wiki"
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
msgstr ""
@@ -4414,13 +5123,13 @@ msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection.
msgstr "安全ã§ãªã„ HTTP 接続を使用ã—㦠Geo ノードを設定ã—ã¾ã—ãŸã€‚HTTPS 通信ã®ä½¿ç”¨ã‚’推奨ã—ã¾ã™ã€‚"
msgid "Geo|%{name} is scheduled for forced re-download"
-msgstr ""
+msgstr "%{name} ã¯å¼·åˆ¶çš„ã«å†ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã•ã‚Œã‚‹äºˆå®šã§ã™"
msgid "Geo|%{name} is scheduled for re-check"
-msgstr ""
+msgstr "%{name} ã¯å†ãƒã‚§ãƒƒã‚¯ãŒäºˆå®šã•ã‚Œã¦ã„ã¾ã™"
msgid "Geo|%{name} is scheduled for re-sync"
-msgstr ""
+msgstr "%{name} ã¯å†åŒæœŸãŒäºˆå®šã•ã‚Œã¦ã„ã¾ã™"
msgid "Geo|All"
msgstr "ã™ã¹ã¦"
@@ -4429,15 +5138,30 @@ msgid "Geo|All projects"
msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクト"
msgid "Geo|All projects are being scheduled for re-check"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクトã®å†ãƒã‚§ãƒƒã‚¯ãŒäºˆå®šã•ã‚Œã¦ã„ã¾ã™"
msgid "Geo|All projects are being scheduled for re-sync"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクトã®å†åŒæœŸãŒäºˆå®šã•ã‚Œã¦ã„ã¾ã™"
msgid "Geo|Batch operations"
msgstr "ãƒãƒƒãƒå‡¦ç†"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr "既存ã®ãƒ—ロジェクトã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã‚¨ãƒ³ãƒˆãƒªã‚’削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
msgstr ""
msgid "Geo|Failed"
@@ -4447,25 +5171,28 @@ msgid "Geo|File sync capacity"
msgstr "ファイルåŒæœŸå®¹é‡"
msgid "Geo|Geo Status"
-msgstr ""
+msgstr "ジオステータス"
msgid "Geo|Groups to synchronize"
msgstr "åŒæœŸã‚°ãƒ«ãƒ¼ãƒ—"
msgid "Geo|In sync"
+msgstr "åŒæœŸ"
+
+msgid "Geo|Internal URL"
msgstr ""
msgid "Geo|Last repository check run"
msgstr ""
msgid "Geo|Last successful sync"
-msgstr ""
+msgstr "最後ã«æˆåŠŸã—ãŸåŒæœŸ"
msgid "Geo|Last sync attempt"
-msgstr ""
+msgstr "最後ã®åŒæœŸè©¦è¡Œ"
msgid "Geo|Last time verified"
-msgstr ""
+msgstr "å‰å›žã®ç¢ºèªæ—¥æ™‚"
msgid "Geo|Never"
msgstr ""
@@ -4474,7 +5201,7 @@ msgid "Geo|Next sync scheduled at"
msgstr ""
msgid "Geo|Not synced yet"
-msgstr ""
+msgstr "ã¾ã åŒæœŸã—ã¦ã„ã¾ã›ã‚“。"
msgid "Geo|Pending"
msgstr ""
@@ -4485,6 +5212,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4492,31 +5225,31 @@ msgid "Geo|Projects in certain groups"
msgstr "特定グループã®ãƒ—ロジェクト"
msgid "Geo|Projects in certain storage shards"
-msgstr ""
+msgstr "特定ストレージシャード内ã®ãƒ—ロジェクト"
msgid "Geo|Re-verification interval"
-msgstr ""
+msgstr "å†èªè¨¼ã®å®Ÿæ–½é–“éš”"
msgid "Geo|Recheck"
-msgstr ""
+msgstr "å†ãƒã‚§ãƒƒã‚¯"
msgid "Geo|Recheck all projects"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクトをå†ãƒã‚§ãƒƒã‚¯"
msgid "Geo|Redownload"
-msgstr ""
+msgstr "å†ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
msgid "Geo|Remove"
-msgstr ""
+msgstr "削除"
msgid "Geo|Repository sync capacity"
msgstr "リãƒã‚¸ãƒˆãƒªåŒæœŸå®¹é‡"
msgid "Geo|Resync"
-msgstr ""
+msgstr "å†åŒæœŸ"
msgid "Geo|Resync all projects"
-msgstr ""
+msgstr "ã™ã¹ã¦ã®ãƒ—ロジェクトをå†åŒæœŸ"
msgid "Geo|Retry count"
msgstr "リトライ回数"
@@ -4524,32 +5257,53 @@ msgstr "リトライ回数"
msgid "Geo|Select groups to replicate."
msgstr "é¸æŠžã—ãŸã‚°ãƒ«ãƒ¼ãƒ—を複製ã™ã‚‹"
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "シャードã®åŒæœŸ"
msgid "Geo|Status"
+msgstr "状態"
+
+msgid "Geo|Sync"
msgstr ""
msgid "Geo|Synced"
+msgstr "åŒæœŸæ¸ˆã¿"
+
+msgid "Geo|Synced at"
msgstr ""
msgid "Geo|Synchronization failed - %{error}"
+msgstr "åŒæœŸã«å¤±æ•—ã—ã¾ã—㟠- %{error}"
+
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
msgstr ""
+msgid "Geo|This is a primary node"
+msgstr "ã“ã‚Œã¯ãƒ—ライマリノードã§ã™"
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr "プロジェクト(%{project_id})ã®ãƒˆãƒ©ãƒƒã‚­ãƒ³ã‚°ã‚¨ãƒ³ãƒˆãƒªãŒæ­£å¸¸ã«å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚"
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr ""
msgid "Geo|Tracking entry will be removed. Are you sure?"
-msgstr ""
+msgstr "トラッキングエントリã¯å‰Šé™¤ã•ã‚Œã¾ã™ã€‚本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ"
+
+msgid "Geo|URL"
+msgstr "URL"
msgid "Geo|Unknown state"
-msgstr ""
+msgstr "ä¸æ˜ŽãªçŠ¶æ…‹"
msgid "Geo|Verification capacity"
msgstr "検証能力"
msgid "Geo|Verification failed - %{error}"
-msgstr ""
+msgstr "åŒæœŸã«å¤±æ•—ã—ã¾ã—㟠- %{error}"
msgid "Geo|Waiting for scheduler"
msgstr ""
@@ -4560,23 +5314,20 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
-msgstr ""
+msgstr "設定ãŒé–“é•ã£ã¦ã„ã¾ã™"
msgid "Geo|primary"
-msgstr ""
+msgstr "プライマリー"
msgid "Geo|secondary"
-msgstr ""
+msgstr "セカンダリー"
msgid "Get a free instance review"
-msgstr ""
+msgstr "ç„¡æ–™ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ãƒ¬ãƒ“ューã®å–å¾—"
msgid "Get started with error tracking"
-msgstr ""
+msgstr "エラー追跡を開始"
msgid "Getting started with releases"
msgstr ""
@@ -4584,9 +5335,12 @@ msgstr ""
msgid "Git"
msgstr "Git"
-msgid "Git global setup"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
+msgid "Git global setup"
+msgstr "Git ã®ã‚°ãƒ­ãƒ¼ãƒãƒ«è¨­å®š"
+
msgid "Git repository URL"
msgstr "Git リãƒã‚¸ãƒˆãƒª URL"
@@ -4605,6 +5359,9 @@ msgstr "GitHub インãƒãƒ¼ãƒˆ"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter ãŒç§»å‹•ã—ã¾ã—ãŸ"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4612,7 +5369,7 @@ msgid "GitLab Group Runners can execute code for all the projects in this group.
msgstr "GitLab グループ Runner ã¯ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—内ã®å…¨ã¦ã®ãƒ—ロジェクトã®ã‚³ãƒ¼ãƒ‰ã‚’実行ã§ãã¾ã™ã€‚"
msgid "GitLab Import"
-msgstr ""
+msgstr "GitLab インãƒãƒ¼ãƒˆ"
msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
msgstr ""
@@ -4620,12 +5377,18 @@ msgstr ""
msgid "GitLab User"
msgstr "GitLab ユーザー"
-msgid "GitLab metadata URL"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
msgstr ""
+msgid "GitLab metadata URL"
+msgstr "GitLab メタデータ URL"
+
msgid "GitLab project export"
msgstr "GitLab プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ"
+msgid "GitLab restart is required to apply changes"
+msgstr "変更をé©ç”¨ã™ã‚‹ã«ã¯ GitLab ã®å†èµ·å‹•ãŒå¿…è¦ã§ã™"
+
msgid "GitLab single sign on URL"
msgstr "GitLab シングルサインオン㮠URL"
@@ -4654,25 +5417,34 @@ msgid "Gitea Import"
msgstr "Gitea インãƒãƒ¼ãƒˆ"
msgid "Given access %{time_ago}"
-msgstr ""
+msgstr "%{time_ago} ã«ã‚¢ã‚¯ã‚»ã‚¹è¨±å¯"
msgid "Go Back"
msgstr "戻る"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "å‰ã«æˆ»ã‚‹"
+msgid "Go full screen"
+msgstr "全画é¢è¡¨ç¤º"
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr "プロジェクトã«ç§»å‹•"
+
msgid "Google Code import"
msgstr "Google コードã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
msgid "Google Takeout"
-msgstr ""
+msgstr "Google テイクアウト"
msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
msgstr "Google èªè¨¼ã¯ %{link_to_documentation} ã§ã¯ã‚ã‚Šã¾ã›ã‚“。ã“ã®ã‚µãƒ¼ãƒ“スã«ã¤ã„ã¦ã¯ GitLab 管ç†è€…ã«å•ã„åˆã‚ã›ã¦ãã ã•ã„。"
@@ -4681,14 +5453,23 @@ msgid "Got it!"
msgstr "入手ã—ã¾ã—ょã†ï¼"
msgid "Grant access"
-msgstr ""
+msgstr "アクセス許å¯"
msgid "Graph"
msgstr "グラフ"
+msgid "Gravatar enabled"
+msgstr "Gravatar を有効化"
+
msgid "Group"
msgstr "グループ"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "CI/CD グループ設定"
@@ -4698,11 +5479,14 @@ msgstr "グループ Git LFS ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹:"
msgid "Group ID"
msgstr "グループ ID"
+msgid "Group ID: %{group_id}"
+msgstr "グループID:%{group_id}"
+
msgid "Group Runners"
msgstr "グループ Runner"
msgid "Group SAML must be enabled to test"
-msgstr ""
+msgstr "グループ SAML を有効ã«ã—ã¦ãƒ†ã‚¹ãƒˆã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™"
msgid "Group URL"
msgstr "グループ URL"
@@ -4714,7 +5498,7 @@ msgid "Group description"
msgstr "グループã®èª¬æ˜Ž"
msgid "Group description (optional)"
-msgstr ""
+msgstr "グループã®èª¬æ˜Ž(ä»»æ„)"
msgid "Group details"
msgstr "グループã®è©³ç´°"
@@ -4729,10 +5513,13 @@ msgid "Group name"
msgstr "グループå"
msgid "Group overview content"
+msgstr "グループ概è¦ã‚³ãƒ³ãƒ†ãƒ³ãƒ„"
+
+msgid "Group was successfully updated."
msgstr ""
msgid "Group:"
-msgstr ""
+msgstr "グループ:"
msgid "Group: %{group_name}"
msgstr "グループ:%{group_name}"
@@ -4749,6 +5536,9 @@ msgstr "申ã—訳ã‚ã‚Šã¾ã›ã‚“ã€æ¤œç´¢ã«ãƒžãƒƒãƒã™ã‚‹ã‚¨ãƒ”ックã¯ã‚ã‚Š
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "ロードマップã¯æ™‚é–“ã®çµŒéŽã¨ã¨ã‚‚ã«ã‚¨ãƒ”ックã®é€²è¡Œã‚’表示ã—ã¾ã™"
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4758,25 +5548,103 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "%{dateWord} ã¾ã§"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "ãƒãƒƒã‚¸"
msgid "GroupSettings|Custom project templates"
-msgstr ""
+msgstr "カスタムプロジェクトテンプレート"
msgid "GroupSettings|Customize your group badges."
msgstr "グループãƒãƒƒã‚¸ã®ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚º"
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "ãƒãƒƒã‚¸ã®è©³ç´°ã€‚"
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "%{group} 内ã®ãƒ—ロジェクトを他ã®ã‚°ãƒ«ãƒ¼ãƒ—ã¨å…±æœ‰ã—ãªã„よã†ã«ã™ã‚‹"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr "ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—ã®ã‚«ã‚¹ã‚¿ãƒ ãƒ—ロジェクトテンプレートソースã¨ã—ã¦ã‚µãƒ–グループをé¸æŠž"
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
@@ -4804,7 +5672,7 @@ msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroup
msgstr "グループã¯ã€%{subgroup_docs_link_start}サブグループ%{subgroup_docs_link_end}を作æˆã™ã‚‹ã“ã¨ã§ãƒã‚¹ãƒˆã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
msgid "Groups with access to <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "<strong>%{project_name}</strong>ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©ã‚’æŒã¤ã‚°ãƒ«ãƒ¼ãƒ—"
msgid "GroupsDropdown|Frequently visited"
msgstr "よã使ã†ãƒ—ロジェクト"
@@ -4816,7 +5684,7 @@ msgid "GroupsDropdown|Loading groups"
msgstr "グループã®èª­ã¿è¾¼ã¿ä¸­"
msgid "GroupsDropdown|Search your groups"
-msgstr ""
+msgstr "ã‚ãªãŸã®ã‚°ãƒ«ãƒ¼ãƒ—を検索"
msgid "GroupsDropdown|Something went wrong on our end."
msgstr ""
@@ -4840,7 +5708,7 @@ msgid "GroupsEmptyState|You can manage your group member’s permissions and acc
msgstr "グループメンãƒãƒ¼ã®æ¨©é™ç®¡ç†ã€ãŠã‚ˆã³ã‚°ãƒ«ãƒ¼ãƒ—内ã®å„プロジェクトã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ã‚’管ç†ã§ãã¾ã™ã€‚"
msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
-msgstr ""
+msgstr "\"%{fullName}\" グループã‹ã‚‰é›¢è„±ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "GroupsTree|Create a project in this group."
msgstr "ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—ã«ãƒ—ロジェクトを作æˆã™ã‚‹ã€‚"
@@ -4867,11 +5735,14 @@ msgid "GroupsTree|No groups or projects matched your search"
msgstr ""
msgid "GroupsTree|Search by name"
-msgstr ""
+msgstr "åå‰ã§æ¤œç´¢"
msgid "Have your users email"
msgstr "ユーザーã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "ヘッダーメッセージ"
@@ -4903,32 +5774,50 @@ msgid "Help page text and support page url."
msgstr "ヘルプページテキストã¨ã‚µãƒãƒ¼ãƒˆãƒšãƒ¼ã‚¸URL。"
msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
-msgstr ""
+msgstr "ã“ã“ã«ã¯ã€ãƒªãƒ¢ãƒ¼ãƒˆã‚µãƒ¼ãƒãƒ¼ã«è¿½åŠ ã™ã‚‹å¿…è¦ã®ã‚る公開 SSH éµãŒã‚ã‚Šã¾ã™ã€‚詳細ã¯ã€æ–‡æ›¸ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
msgid "Hide file browser"
-msgstr ""
+msgstr "ファイルブラウザをéžè¡¨ç¤º"
msgid "Hide host keys manual input"
+msgstr "ホストキーã®ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«å…¥åŠ›ã‚’éš ã™"
+
+msgid "Hide marketing-related entries from help"
msgstr ""
msgid "Hide payload"
-msgstr ""
+msgstr "ペイロードを隠ã™"
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] "éžè¡¨ç¤º"
msgid "Hide values"
+msgstr "éžè¡¨ç¤º"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr "履歴"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ãƒã‚¦ã‚¹ã‚­ãƒ¼ãƒ”ングã¯æ­£å¸¸ã«èµ·å‹•ã—ã¾ã—ãŸã€‚"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr "ãƒã‚¦ã‚¹ã‚­ãƒ¼ãƒ”ングã€ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã€ãƒ‘スã€è»¢é€ã€å‰Šé™¤ã€ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–。"
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
-msgstr ""
+msgstr "ã—ã‹ã—ã€ã‚ãªãŸã¯ã™ã§ã«ã“ã® %{member_source} ã®ãƒ¡ãƒ³ãƒãƒ¼ã§ã™ã€‚招待をå—ã‘入れるã«ã¯ã€åˆ¥ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’使用ã—ã¦ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã—ã¦ãã ã•ã„。"
msgid "I accept the %{terms_link}"
msgstr "%{terms_link} ã«åŒæ„ã™ã‚‹"
@@ -4940,13 +5829,13 @@ msgid "ID"
msgstr "ID"
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation."
-msgstr ""
+msgstr "CodeSandbox クライアントサイド評価を使用ã—ã¦ã€Web IDE 㧠JavaScript プロジェクトã®ãƒ©ã‚¤ãƒ–プレビューを許å¯ã—ã¾ã™ã€‚"
msgid "IDE|Back"
-msgstr ""
+msgstr "戻る"
msgid "IDE|Client side evaluation"
-msgstr ""
+msgstr "クライアントサイド評価"
msgid "IDE|Commit"
msgstr "コミット"
@@ -4955,28 +5844,28 @@ msgid "IDE|Edit"
msgstr "編集"
msgid "IDE|Get started with Live Preview"
-msgstr ""
+msgstr "ライブプレビューã®é–‹å§‹"
msgid "IDE|Go to project"
-msgstr ""
+msgstr "プロジェクトã«ç§»å‹•"
msgid "IDE|Live Preview"
-msgstr ""
+msgstr "ライブプレビュー"
msgid "IDE|Open in file view"
msgstr "ファイルビューã§é–‹ã"
msgid "IDE|Preview your web application using Web IDE client-side evaluation."
-msgstr ""
+msgstr "Web IDE クライアントサイド評価を使用ã—ã¦ã€ã‚ãªãŸã® Web アプリケーションをプレビューã—ã¾ã™ã€‚"
msgid "IDE|Refresh preview"
-msgstr ""
+msgstr "プレビューã®æ›´æ–°"
msgid "IDE|Review"
msgstr "レビュー"
msgid "IP Address"
-msgstr ""
+msgstr "IP アドレス"
msgid "Identifier"
msgstr "識別å­"
@@ -4984,15 +5873,18 @@ msgstr "識別å­"
msgid "Identities"
msgstr "ID"
-msgid "Identity provider single sign on URL"
-msgstr "アイデンティティプロãƒã‚¤ãƒ€ã®ã‚·ãƒ³ã‚°ãƒ«ã‚µã‚¤ãƒ³ã‚ªãƒ³ URL"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr "無効ã«ã™ã‚‹ã¨ã€ç®¡ç†è€…ã ã‘ãŒãƒ—ロジェクトã«ãƒŸãƒ©ãƒ¼ã‚’設定ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚"
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5000,13 +5892,10 @@ msgid "If enabled"
msgstr ""
msgid "If enabled, access to projects will be validated on an external service using their classification label."
-msgstr ""
+msgstr "有効ã«è¨­å®šã—ãŸå ´åˆã€å¤–部サービスã‹ã‚‰ãƒ—ロジェクトã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ãŒåˆ†é¡žãƒ©ãƒ™ãƒ«ã‚’使用ã—ã¦åˆ¶å¾¡ã•ã‚Œã¾ã™ã€‚"
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
-msgstr "GitHub を使用ã—ã¦ã„ã‚‹å ´åˆã€GitHub 上ã®ã‚³ãƒŸãƒƒãƒˆã‚„プルリクエスã‹ã‚‰ãƒ‘イプラインã®çŠ¶æ…‹ã‚’確èªã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚%{more_info_link}"
-
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "ã™ã§ã«ãƒ•ã‚¡ã‚¤ãƒ«ãŒå­˜åœ¨ã™ã‚‹ã¨ãã¯ã€æ¬¡ã® %{link_to_cli} を使用ã—ã¦ãƒ•ã‚¡ã‚¤ãƒ«ã‚’プッシュã§ãã¾ã™ã€‚"
+msgstr "GitHub を使用ã—ã¦ã„ã‚‹å ´åˆã€GitHub 上ã®ã‚³ãƒŸãƒƒãƒˆã‚„プルリクエストã‹ã‚‰ãƒ‘イプラインã®çŠ¶æ…‹ã‚’確èªã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚%{more_info_link}"
msgid "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>."
msgstr "HTTP リãƒã‚¸ãƒˆãƒªãŒå…¬é–‹è¨­å®šã•ã‚Œã¦ã„ãªã„ã¨ãã¯ã€èªè¨¼æƒ…報を URL ã«è¿½åŠ ã—ã¦ãã ã•ã„(URL: <code>https://username:password@gitlab.company.com/group/project.git</code>)。"
@@ -5027,7 +5916,7 @@ msgid "Import"
msgstr "インãƒãƒ¼ãƒˆ"
msgid "Import CSV"
-msgstr ""
+msgstr "CSV ã®å–ã‚Šè¾¼ã¿"
msgid "Import Projects from Gitea"
msgstr "Gitea ã‹ã‚‰ãƒ—ロジェクトã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
@@ -5048,13 +5937,13 @@ msgid "Import in progress"
msgstr "インãƒãƒ¼ãƒˆä¸­ã§ã™"
msgid "Import issues"
-msgstr ""
+msgstr "課題ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
msgid "Import members"
-msgstr ""
+msgstr "メンãƒãƒ¼ã‚’インãƒãƒ¼ãƒˆ"
msgid "Import members from another project"
-msgstr ""
+msgstr "ä»–ã®ãƒ—ロジェクトã‹ã‚‰ãƒ¡ãƒ³ãƒãƒ¼ã‚’インãƒãƒ¼ãƒˆ"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "manifest ファイルã®ã‚¢ãƒƒãƒ—ロードã«ã‚ˆã‚‹è¤‡æ•°ãƒªãƒã‚¸ãƒˆãƒªã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
@@ -5063,10 +5952,10 @@ msgid "Import project"
msgstr "プロジェクトã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
msgid "Import project members"
-msgstr ""
+msgstr "プロジェクトメンãƒãƒ¼ã‚’インãƒãƒ¼ãƒˆ"
msgid "Import projects from Bitbucket"
-msgstr ""
+msgstr "Bitbucket ã‹ã‚‰ãƒ—ロジェクトã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
msgid "Import projects from Bitbucket Server"
msgstr "Bitbucket Server ã‹ã‚‰ãƒ—ロジェクトã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
@@ -5090,17 +5979,35 @@ msgid "Import repository"
msgstr "リãƒã‚¸ãƒˆãƒªã‚’インãƒãƒ¼ãƒˆ"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr "インãƒãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸã€‚インãƒãƒ¼ãƒˆã« %{import_jobs_expiration} 秒以上ã‹ã‹ã‚Šã¾ã—ãŸ"
+
+msgid "Import/Export illustration"
msgstr ""
msgid "ImportButtons|Connect repositories from"
msgstr "リãƒã‚¸ãƒˆãƒªã¸æŽ¥ç¶š"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "GitLab エンタープライズエディションを使用ã™ã‚‹ã¨ã€èª²é¡Œãƒœãƒ¼ãƒ‰ã®æ©Ÿèƒ½ãŒå¼·åŒ–ã•ã‚Œã¾ã™ã€‚"
-msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr "GitLab エンタープライズエディションを使用ã™ã‚‹ã¨ã€èª²é¡Œç®¡ç†æ©Ÿèƒ½ãŒå¼·åŒ–ã•ã‚Œã€èª²é¡Œã«ã‚¦ã‚§ã‚¤ãƒˆã‚’設定ã§ãã¾ã™ã€‚"
+
msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
msgstr "GitLab エンタープライズエディションを使用ã™ã‚‹ã¨ã€æ¤œç´¢æ©Ÿèƒ½ãŒå¼·åŒ–ã•ã‚Œã€é«˜åº¦ãªã‚°ãƒ­ãƒ¼ãƒãƒ«æ¤œç´¢ãŒå¯èƒ½ã«ãªã‚Šã¾ã™ã€‚"
@@ -5113,13 +6020,22 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr "インシデント"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒåŒæ„ã—ãªã‘ã‚Œã°ãªã‚‰ãªã„利用è¦ç´„ã¨ãƒ—ライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼ã‚’å«ã‚ã¾ã™ã€‚"
+msgid "Include author name in notification email body"
+msgstr "通知メールã®æœ¬æ–‡ã«ä½œæˆè€…ã®åå‰ã‚’å«ã‚ã‚‹"
+
msgid "Include merge request description"
-msgstr ""
+msgstr "マージリクエストã®èª¬æ˜Žã‚’å«ã‚ã‚‹"
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr "å¿…è¦ãªå ´åˆã€URL ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’å«ã‚ã¾ã™ <code>https://username@gitlab.company.com/group/project.git</code>。"
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
msgstr ""
msgid "Includes an MVC structure to help you get started."
@@ -5137,20 +6053,32 @@ msgstr "互æ›æ€§ã®ãªã„プロジェクト"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr "SSH éµã‚’アップロードã—ã¦ã„ãªã„ユーザーã«ã€è¿½åŠ ã•ã‚Œã‚‹ã¾ã§ SSH 経由ã§ãƒ—ッシュã§ããªã„ã“ã¨ã‚’通知"
+
msgid "Inline"
msgstr "インライン"
msgid "Input host keys manually"
-msgstr ""
+msgstr "ホストキーã®ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«å…¥åŠ›"
msgid "Input your repository URL"
+msgstr "リãƒã‚¸ãƒˆãƒªã® URL を入力ã—ã¦ãã ã•ã„"
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
msgstr ""
msgid "Insert suggestion"
+msgstr "候補を挿入ã™ã‚‹"
+
+msgid "Insights"
msgstr ""
msgid "Install GitLab Runner"
-msgstr ""
+msgstr "GitLab Runner ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«"
msgid "Install Runner on Kubernetes"
msgstr "Kubernetes 㫠Runner をインストール"
@@ -5163,11 +6091,14 @@ msgid "Instance Statistics"
msgstr "インスタンス統計"
msgid "Instance Statistics visibility"
-msgstr ""
+msgstr "インスタンス統計ã®å¯è¦–性"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "インスタンスã¯ãƒžãƒ«ãƒ Kubernetes クラスターをサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "インテグレーション"
@@ -5178,16 +6109,16 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr "貢献をã—ãŸã„関係者ã¯ã€ã‚³ãƒŸãƒƒãƒˆã‚’プッシュã™ã‚‹ã“ã¨ã§è²¢çŒ®ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
msgid "Internal"
-msgstr ""
+msgstr "内部"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "内部 - グループãŠã‚ˆã³å†…部プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¦‹ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "Internal - The project can be accessed by any logged in user."
-msgstr ""
+msgstr "内部 - プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„ã‚‹ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚"
msgid "Internal users"
-msgstr ""
+msgstr "内部ユーザー"
msgid "Interval Pattern"
msgstr "é–“éš”ã®ãƒ‘ターン"
@@ -5198,22 +6129,40 @@ msgstr "サイクル分æžã®ã”紹介"
msgid "Introducing Your Conversational Development Index"
msgstr ""
-msgid "Invitation"
+msgid "Invalid Login or password"
msgstr ""
-msgid "Invite"
+msgid "Invalid file."
msgstr ""
-msgid "Invite group"
+msgid "Invalid input, please avoid emojis"
msgstr ""
-msgid "Invite member"
+msgid "Invalid pin code"
msgstr ""
-msgid "Invoke Count"
+msgid "Invalid two-factor code."
msgstr ""
+msgid "Invitation"
+msgstr "招待状"
+
+msgid "Invite"
+msgstr "招待"
+
+msgid "Invite group"
+msgstr "グループã«æ‹›å¾…ã™ã‚‹"
+
+msgid "Invite member"
+msgstr "メンãƒãƒ¼ã‚’招待ã™ã‚‹"
+
+msgid "Invoke Count"
+msgstr "呼ã³å‡ºã—回数"
+
msgid "Invoke Time"
+msgstr "呼ã³å‡ºã—時間"
+
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
msgstr ""
msgid "Issue"
@@ -5228,6 +6177,9 @@ msgstr "課題ボード フォーカス モード"
msgid "Issue events"
msgstr "課題イベント"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "ボード"
@@ -5235,19 +6187,19 @@ msgid "IssueBoards|Boards"
msgstr "ボード"
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "æ–°ã—ã„ボードを作æˆ"
msgid "IssueBoards|Delete board"
-msgstr ""
+msgstr "ボードã®å‰Šé™¤"
msgid "IssueBoards|No matching boards found"
-msgstr ""
+msgstr "該当ã™ã‚‹ãƒœãƒ¼ãƒ‰ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
-msgstr ""
+msgstr "ã„ãã¤ã‹ã®ãƒœãƒ¼ãƒ‰ã¯éžè¡¨ç¤ºã«ãªã£ã¦ã„ã¾ã™ã€‚å†åº¦è¡¨ç¤ºã™ã‚‹ãŸã‚ã«ã¯ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’有効ã«ã—ã¦ãã ã•ã„。"
msgid "IssueBoards|Switch board"
-msgstr ""
+msgstr "ボードを切り替ãˆã‚‹"
msgid "Issues"
msgstr "課題"
@@ -5258,41 +6210,44 @@ msgstr "課題ã¨ã¯ãƒã‚°ã€ã‚¿ã‚¹ã‚¯ã€ã¾ãŸã¯è­°è«–ã®å¿…è¦ãªã‚¢ã‚¤ãƒ‡ã‚¢
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
+msgid "Issues, merge requests, pushes, and comments."
+msgstr "課題ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã€ãƒ—ッシュã€ã‚³ãƒ¡ãƒ³ãƒˆ"
+
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr ""
msgid "IssuesAnalytics|Issues Created"
-msgstr ""
+msgstr "課題ãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
msgid "IssuesAnalytics|Issues created per month"
-msgstr ""
+msgstr "課題ã®ä½œæˆï¼æœˆ"
msgid "IssuesAnalytics|Last 12 months"
-msgstr ""
+msgstr "éŽåŽ»12ヶ月間"
msgid "IssuesAnalytics|Sorry, your filter produced no results"
-msgstr ""
+msgstr "申ã—訳ã‚ã‚Šã¾ã›ã‚“。ã‚ãªãŸãŒæŒ‡å®šã—ãŸãƒ•ã‚£ãƒ«ã‚¿ãƒ¼ã§ã¯è©²å½“ã™ã‚‹çµæžœãŒã‚ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚"
msgid "IssuesAnalytics|There are no issues for the projects in your group"
-msgstr ""
+msgstr "ã‚ãªãŸã®ã‚°ãƒ«ãƒ¼ãƒ—ã®ãƒ—ロジェクトã«ã¯ã€èª²é¡ŒãŒã‚ã‚Šã¾ã›ã‚“。"
msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
-msgstr ""
+msgstr "検索を拡大ã™ã‚‹ã«ã¯ã€ä¸Šè¨˜ã®ãƒ•ã‚£ãƒ«ã‚¿ãƒãƒ¼ã§ãƒ•ã‚£ãƒ«ã‚¿ã‚’変更ã¾ãŸã¯å‰Šé™¤ã—ã¾ã™"
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
-msgstr ""
+msgstr "ヘッダー行ã¨å°‘ãªãã¨ã‚‚2ã¤ã®åˆ—ãŒå¿…è¦ã§ã™ã€‚最åˆã®åˆ—ã¯èª²é¡Œã®ã‚¿ã‚¤ãƒˆãƒ«ã€2番目ã®åˆ—ã¯èª²é¡Œã®èª¬æ˜Žã§ã™ã€‚区切り文字ã¯è‡ªå‹•çš„ã«æ¤œå‡ºã•ã‚Œã¾ã™ã€‚"
msgid "It's you"
msgstr ""
msgid "Jaeger URL"
-msgstr ""
+msgstr "Jaeger URL"
msgid "Jaeger tracing"
-msgstr ""
+msgstr "Jaeger トレーシング"
msgid "Jan"
msgstr "1月"
@@ -5303,58 +6258,91 @@ msgstr "1月"
msgid "Job"
msgstr "ジョブ"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr "ジョブ ID"
+
msgid "Job has been erased"
msgstr "ジョブãŒæ¶ˆåŽ»ã•ã‚Œã¾ã—ãŸ"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
+msgstr "ジョブãŒæºœã¾ã£ã¦ã„ã¾ã™ã€‚Runner を確èªã—ã¦ãã ã•ã„。"
+
+msgid "Job traces and artifacts"
msgstr ""
msgid "Job was retried"
-msgstr ""
+msgstr "ジョブãŒå†è©¦è¡Œã•ã‚Œã¾ã—ãŸ"
msgid "Jobs"
msgstr "ジョブ"
msgid "Job|Browse"
-msgstr ""
+msgstr "ブラウズ"
msgid "Job|Complete Raw"
-msgstr ""
+msgstr "完全㪠Raw"
msgid "Job|Download"
-msgstr ""
+msgstr "ダウンロード"
msgid "Job|Erase job log"
-msgstr ""
+msgstr "ジョブログã®æ¶ˆåŽ»"
msgid "Job|Job artifacts"
-msgstr ""
+msgstr "ジョブã®æˆæžœç‰©"
msgid "Job|Job has been erased"
-msgstr ""
+msgstr "ジョブãŒæ¶ˆåŽ»ã•ã‚Œã¾ã—ãŸ"
msgid "Job|Job has been erased by"
-msgstr ""
+msgstr "ジョブãŒæ¶ˆåŽ»ã•ã‚Œã¾ã—ãŸ:"
msgid "Job|Keep"
+msgstr "維æŒ"
+
+msgid "Job|Pipeline"
msgstr ""
msgid "Job|Scroll to bottom"
-msgstr ""
+msgstr "最下部ã«ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«"
msgid "Job|Scroll to top"
-msgstr ""
+msgstr "最上部ã«ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«"
msgid "Job|Show complete raw"
-msgstr ""
+msgstr "完全㪠Raw を表示ã™ã‚‹"
msgid "Job|The artifacts were removed"
msgstr ""
msgid "Job|The artifacts will be removed"
+msgstr "æˆæžœç‰©ã¯å‰Šé™¤ã•ã‚Œã¾ã™"
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr "プロジェクトã«ã‚ªãƒ³ãƒ©ã‚¤ãƒ³ã® Runner ãŒå‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ãªã„ãŸã‚ã€ã‚¸ãƒ§ãƒ–ã¯åœæ­¢ã—ã¦ã„ã¾ã™ã€‚"
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
msgstr ""
msgid "Jul"
@@ -5370,7 +6358,7 @@ msgid "June"
msgstr "6月"
msgid "Key (PEM)"
-msgstr ""
+msgstr "キー (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5379,7 +6367,7 @@ msgid "Kubernetes Cluster"
msgstr "Kubernetes クラスター"
msgid "Kubernetes Clusters"
-msgstr ""
+msgstr "Kubernetes クラスター"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "Kubernetes クラスターã®ä½œæˆä¸­ã«ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã—ãŸ:%{timeout}"
@@ -5399,9 +6387,15 @@ msgstr "Kubernetes 構æˆæ¸ˆã¿"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "Kubernetes サービスã®çµ±åˆã¯å»ƒæ­¢ã•ã‚Œã¾ã—ãŸã€‚%{deprecated_message_content} æ–°ã—ã„<a href=\"%{url}\"/>Kubernetes クラスター</a> ã®ãƒšãƒ¼ã‚¸ã‚’使用ã—ã¦ãã ã•ã„"
+msgid "LDAP settings"
+msgstr "LDAP 設定"
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "無効"
@@ -5412,11 +6406,20 @@ msgid "Label"
msgstr "ラベル"
msgid "Label actions dropdown"
-msgstr ""
+msgstr "ラベルアクションドロップダウン"
msgid "Label lists show all issues with the selected label."
msgstr "ラベル一覧ã«ã¯ã€é¸æŠžã—ãŸãƒ©ãƒ™ãƒ«ãŒä»˜ã„ãŸã™ã¹ã¦ã®èª²é¡ŒãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚"
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5445,10 +6448,13 @@ msgid "Labels|Promote Label"
msgstr "ラベルã®æ˜‡æ ¼"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
-msgstr ""
+msgstr "%{labelTitle} を宣言ã™ã‚‹ã¨ %{groupName} 内ã®ã™ã¹ã¦ã®ãƒ—ロジェクト内ã§åˆ©ç”¨å¯èƒ½ã«ãªã‚Šã¾ã™ã€‚åŒã˜ã‚¿ã‚¤ãƒˆãƒ«ã®æ—¢å­˜ã®ãƒ—ロジェクトラベルã¯ãƒžãƒ¼ã‚¸ã•ã‚Œã¾ã™ã€‚ã“ã®æ“作ã¯å…ƒã«æˆ»ã™ã“ã¨ã¯ã§ãã¾ã›ã‚“。"
+
+msgid "Language"
+msgstr "言語"
msgid "Large File Storage"
-msgstr ""
+msgstr "Large File Storage"
msgid "Last %d day"
msgid_plural "Last %d days"
@@ -5458,7 +6464,7 @@ msgid "Last Pipeline"
msgstr "最新パイプライン"
msgid "Last activity"
-msgstr ""
+msgstr "最後ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティ"
msgid "Last commit"
msgstr "最新コミット"
@@ -5476,7 +6482,7 @@ msgid "Last reply by"
msgstr ""
msgid "Last seen"
-msgstr ""
+msgstr "最終閲覧"
msgid "Last update"
msgstr "最終更新"
@@ -5494,10 +6500,10 @@ msgid "Latest changes"
msgstr "最新ã®å¤‰æ›´"
msgid "Latest pipeline for this branch"
-msgstr ""
+msgstr "ã“ã®ãƒ–ランãƒã®æœ€æ–°ã®ãƒ‘イプライン"
msgid "Lead"
-msgstr ""
+msgstr "リード"
msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
msgstr ""
@@ -5506,28 +6512,28 @@ msgid "Learn more"
msgstr "詳細"
msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
-msgstr ""
+msgstr "ラベルã€æ‹…当者ã€ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã‚’使用ã—ã€è¤‡æ•°ãƒªã‚¹ãƒˆã®èª²é¡Œã‚’追跡ã™ã‚‹ã«ã¯ %{issue_boards_url} ã‹ã‚‰ç¢ºèªã§ãã¾ã™ã€‚課題ボードã«è¶³ã‚Šãªã„ã‚‚ã®ãŒã‚ã‚‹å ´åˆã€ %{gitlab_issues_url} ã‹ã‚‰èª²é¡Œã‚’作æˆã—ã¦ãã ã•ã„。"
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "Auto DevOps ã®è©³ç´°"
msgid "Learn more about Kubernetes"
msgstr "Kubernetes ã®è©³ç´°"
msgid "Learn more about Web Terminal"
-msgstr ""
+msgstr "Web Terminalã®è©³ç´°"
+
+msgid "Learn more about approvals."
+msgstr "承èªã®è©³ç´°"
msgid "Learn more about custom project templates"
-msgstr ""
+msgstr "カスタムプロジェクトテンプレートã®è©³ç´°"
msgid "Learn more about group-level project templates"
-msgstr ""
+msgstr "グループレベルプロジェクトテンプレートã®è©³ç´°"
-msgid "Learn more about incoming email addresses"
-msgstr ""
-
-msgid "Learn more about protected branches"
-msgstr "ä¿è­·ã•ã‚ŒãŸãƒ–ランãƒã«ã¤ã„ã¦ã®è©³ç´°"
+msgid "Learn more about signing commits"
+msgstr "コミットã¸ã®ç½²åã®è©³ç´°"
msgid "Learn more in the"
msgstr "詳ã—ã見る:"
@@ -5538,6 +6544,9 @@ msgstr "詳ã—ãã¯ãƒ‘イプラインスケジュールã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’
msgid "Leave"
msgstr "離れる"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "グループを離脱"
@@ -5551,28 +6560,28 @@ msgid "License"
msgstr "ライセンス"
msgid "LicenseManagement|Add a license"
-msgstr ""
+msgstr "ライセンスを追加"
msgid "LicenseManagement|Add licenses manually to approve or blacklist"
-msgstr ""
+msgstr "ライセンスを手動ã§è¿½åŠ ã—ã¦æ‰¿èªã¾ãŸã¯ãƒ–ラックリストã«ç™»éŒ²ã™ã‚‹"
msgid "LicenseManagement|Approve"
-msgstr ""
+msgstr "承èª"
msgid "LicenseManagement|Approve license"
-msgstr ""
+msgstr "ライセンスã®æ‰¿èª"
msgid "LicenseManagement|Approve license?"
-msgstr ""
+msgstr "ライセンスを承èªã—ã¾ã™ã‹ï¼Ÿ"
msgid "LicenseManagement|Approved"
-msgstr ""
+msgstr "承èªæ¸ˆã¿"
msgid "LicenseManagement|Blacklist"
-msgstr ""
+msgstr "ブラックリスト"
msgid "LicenseManagement|Blacklist license"
-msgstr ""
+msgstr "ブラックリストライセンス"
msgid "LicenseManagement|Blacklist license?"
msgstr ""
@@ -5581,19 +6590,19 @@ msgid "LicenseManagement|Blacklisted"
msgstr ""
msgid "LicenseManagement|Cancel"
-msgstr ""
+msgstr "キャンセル"
msgid "LicenseManagement|License"
msgstr "ライセンス"
msgid "LicenseManagement|License Management"
-msgstr ""
+msgstr "ライセンス管ç†"
msgid "LicenseManagement|License details"
-msgstr ""
+msgstr "ライセンスã®è©³ç´°"
msgid "LicenseManagement|License name"
-msgstr ""
+msgstr "ライセンスå"
msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
msgstr ""
@@ -5602,29 +6611,35 @@ msgid "LicenseManagement|Packages"
msgstr "パッケージ"
msgid "LicenseManagement|Remove license"
-msgstr ""
+msgstr "ライセンスを削除"
msgid "LicenseManagement|Remove license?"
-msgstr ""
+msgstr "ライセンスを削除ã—ã¾ã™ã‹ï¼Ÿ"
msgid "LicenseManagement|Submit"
-msgstr ""
+msgstr "é€ä¿¡"
msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
msgstr ""
msgid "LicenseManagement|This license already exists in this project."
-msgstr ""
+msgstr "ã“ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã¯ã™ã§ã«ã“ã®ãƒ—ロジェクトã«å­˜åœ¨ã—ã¾ã™ã€‚"
msgid "LicenseManagement|URL"
msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
+msgstr "ã“ã®ãƒ—ロジェクトã‹ã‚‰ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ %{name} を削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚"
+
+msgid "Licensed to"
msgstr ""
msgid "Licenses"
msgstr "ライセンス"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5636,25 +6651,28 @@ msgid "List"
msgstr "リスト"
msgid "List Your Gitea Repositories"
-msgstr ""
+msgstr "ã‚ãªãŸã® Gitea リãƒã‚¸ãƒˆãƒªã®ä¸€è¦§"
msgid "List available repositories"
+msgstr "利用å¯èƒ½ãªãƒªãƒã‚¸ãƒˆãƒªã®ä¸€è¦§"
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
msgstr ""
msgid "List view"
-msgstr ""
+msgstr "一覧表示"
msgid "List your Bitbucket Server repositories"
-msgstr ""
+msgstr "ã‚ãªãŸã®ã€Bitbucket Server ã®ãƒªãƒã‚¸ãƒˆãƒªã‚’一覧表示ã™ã‚‹"
msgid "List your GitHub repositories"
msgstr "GitHub リãƒã‚¸ãƒˆãƒªã‚’一覧表示"
msgid "Live preview"
-msgstr ""
+msgstr "ライブプレビュー"
msgid "Loading contribution stats for group members"
-msgstr ""
+msgstr "グループメンãƒãƒ¼ã®è²¢çŒ®åº¦æƒ…報を読ã¿è¾¼ã¿ä¸­"
msgid "Loading the GitLab IDE..."
msgstr "GitLab IDE ã®èª­ã¿è¾¼ã¿ä¸­..."
@@ -5663,7 +6681,7 @@ msgid "Loading..."
msgstr "読ã¿è¾¼ã¿ä¸­..."
msgid "Loading…"
-msgstr ""
+msgstr "読ã¿è¾¼ã¿ä¸­â€¦"
msgid "Localization"
msgstr ""
@@ -5678,7 +6696,7 @@ msgid "Lock not found"
msgstr "ロックãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
-msgstr ""
+msgstr "%{issuableDisplayName} をロックã—ã¾ã™ã‹ï¼Ÿ<strong>プロジェクトメンãƒãƒ¼</strong> ã®ã¿ã‚³ãƒ¡ãƒ³ãƒˆã§ãã¾ã™ã€‚"
msgid "Lock to current projects"
msgstr "ç¾åœ¨ã®ãƒ—ロジェクトをロックã™ã‚‹"
@@ -5693,25 +6711,46 @@ msgid "Locked to current projects"
msgstr "ç¾åœ¨ã®ãƒ—ロジェクトã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™"
msgid "Locks give the ability to lock specific file or folder."
-msgstr ""
+msgstr "ロックã¯ã€ç‰¹å®šã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚„フォルダをロックã™ã‚‹æ©Ÿèƒ½ã‚’æä¾›ã—ã¾ã™ã€‚"
msgid "Login with smartcard"
+msgstr "スマートカードã§ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹"
+
+msgid "Logo was successfully removed."
msgstr ""
msgid "Logs"
msgstr "ログ"
-msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgid "MRApprovals|Approved by"
msgstr ""
+msgid "MRApprovals|Approvers"
+msgstr "承èªè€…"
+
+msgid "MRApprovals|Pending approvals"
+msgstr "承èªå¾…ã¡"
+
+msgid "MRDiff|Show changes only"
+msgstr "変更ã®ã¿è¡¨ç¤º"
+
+msgid "MRDiff|Show full file"
+msgstr "ã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’表示"
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr "ãƒãƒ¼ãƒ å…¨å“¡ãŒå ´æ‰€ã«é–¢ä¿‚ãªãより生産的ã«æ´»å‹•ã§ãã¾ã™ã€‚GitLab Geo ã¯ã€å¤§ããªã‚µã‚¤ã‚ºã®ãƒªãƒã‚¸ãƒˆãƒªã®å–得やクローンã«ã‹ã‹ã‚‹æ™‚間を削減ã™ã‚‹ãŸã‚ã«ã€èª­ã¿è¾¼ã¿å°‚用ã®ãƒŸãƒ©ãƒ¼ã‚’ GitLab インスタンス上ã«ä½œæˆã—ã¾ã™ã€‚"
+
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
-msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgid "Manage"
msgstr ""
+msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgstr "ã‚ãªãŸã®ã‚³ãƒ¼ãƒ‰ã‚’安全ã«ä¿ã¤ãŸã‚ã«ã€ã‚¢ã‚¯ã‚»ã‚¹ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ã§ Git リãƒã‚¸ãƒˆãƒªã‚’管ç†ã—ã¾ã™ã€‚コードレビューを実行ã—ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¨ã®ã‚³ãƒ©ãƒœãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’強化ã—ã¾ã™ã€‚å„プロジェクトã«ã¯ã€èª²é¡Œãƒˆãƒ©ãƒƒã‚«ãƒ¼ã¨ã‚¦ã‚£ã‚­ã‚‚ã‚ã‚Šã¾ã™ã€‚"
+
msgid "Manage Web IDE features"
-msgstr ""
+msgstr "Web IDE 機能ã®ç®¡ç†"
msgid "Manage access"
msgstr ""
@@ -5735,28 +6774,28 @@ msgid "Manage project labels"
msgstr "プロジェクトラベルã®ç®¡ç†"
msgid "Manage two-factor authentication"
-msgstr ""
-
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "グループã®ãƒ¡ãƒ³ãƒãƒ¼ã‚·ãƒƒãƒ—を管ç†ã—ãªãŒã‚‰ã€SAML ã§åˆ¥ã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£ãƒ¬ãƒ™ãƒ«ã‚’追加ã—ã¾ã™ã€‚"
+msgstr "2è¦ç´ èªè¨¼ã®ç®¡ç†"
msgid "Manifest"
-msgstr ""
+msgstr "マニフェスト"
msgid "Manifest file import"
+msgstr "マニフェストファイルã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ"
+
+msgid "Manual job"
msgstr ""
msgid "Map a FogBugz account ID to a GitLab user"
-msgstr ""
+msgstr "FogBugz アカウントIDã‚’ GitLab ユーザーã«ãƒžãƒƒãƒ—ã™ã‚‹"
msgid "Map a Google Code user to a GitLab user"
-msgstr ""
+msgstr "Google コードユーザーを GitLab ユーザーã«ãƒžãƒƒãƒ—ã™ã‚‹"
msgid "Map a Google Code user to a full email address"
-msgstr ""
+msgstr "Google コードユーザーをフルã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«ãƒžãƒƒãƒ—ã™ã‚‹"
msgid "Map a Google Code user to a full name"
-msgstr ""
+msgstr "Google コードユーザーをåå‰ã«ãƒžãƒƒãƒ—ã™ã‚‹"
msgid "Mar"
msgstr "3月"
@@ -5768,48 +6807,45 @@ msgid "Mark todo as done"
msgstr "Todo を完了ã«ã™ã‚‹"
msgid "Markdown"
+msgstr "Markdown"
+
+msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr "マークダウンを使用ã§ãã¾ã™"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
-msgstr ""
+msgid "Maven Metadata"
+msgstr "Maven メタデータ"
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr ""
+msgid "Max access level"
+msgstr "最大アクセスレベル"
-msgid "MarkdownToolbar|Add a table"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr ""
+msgid "Maximum attachment size (MB)"
+msgstr "添付ファイルサイズã®ä¸Šé™ (MB)"
-msgid "MarkdownToolbar|Add italic text"
-msgstr ""
+msgid "Maximum capacity"
+msgstr "最大容é‡"
-msgid "MarkdownToolbar|Go full screen"
-msgstr ""
+msgid "Maximum delay (Minutes)"
+msgstr "最大é…延時間(分)"
-msgid "MarkdownToolbar|Insert a quote"
-msgstr ""
-
-msgid "MarkdownToolbar|Insert code"
-msgstr ""
+msgid "Maximum job timeout"
+msgstr "ジョブタイムアウトã®æœ€å¤§å€¤"
-msgid "Maven Metadata"
-msgstr ""
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr "åŒæ™‚ã«åŒæœŸã§ãるミラーã®æœ€å¤§æ•°ã€‚"
-msgid "Max access level"
-msgstr ""
+msgid "Maximum push size (MB)"
+msgstr "最大プッシュサイズ (MB)"
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5831,38 +6867,53 @@ msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
-msgstr ""
-
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "メンãƒãƒ¼ãŒã‚°ãƒ«ãƒ¼ãƒ—ã«ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã™ã‚‹ã¨ã€ã“ã“ã«ãƒ•ã‚©ãƒ¯ãƒ¼ãƒ‰ã•ã‚Œã¾ã™ã€‚ã“ã‚Œã¯IDプロãƒã‚¤ãƒ€ãƒ¼ã‹ã‚‰å–å¾—ã§ãã¾ã™ã€‚IDプロãƒã‚¤ãƒ€ãƒ¼ã¯\"SSO Service Location\"ã€\"SAML Token Issuance Endpoint\"ã€\"SAML 2.0/W-Federation URL\"ãªã©ã¨ã‚‚呼ã°ã‚Œã¾ã™ã€‚"
+msgstr "<strong>%{project_name}</strong>ã®ãƒ¡ãƒ³ãƒãƒ¼"
msgid "Merge Request"
msgstr "マージリクエスト"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "マージリクエスト"
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
+msgstr "マージコミットメッセージ"
+
+msgid "Merge commit with semi-linear history"
msgstr ""
msgid "Merge events"
msgstr "マージイベント"
msgid "Merge immediately"
-msgstr ""
+msgstr "今ã™ãマージã™ã‚‹"
msgid "Merge in progress"
+msgstr "進行中ã®ãƒžãƒ¼ã‚¸"
+
+msgid "Merge method"
msgstr ""
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr "マージパイプラインã¯ãƒžãƒ¼ã‚¸å®Ÿè¡Œå‰ã«ã€äº‹å‰ãƒžãƒ¼ã‚¸ã®çµæžœã®æ¤œè¨¼ã‚’試ã¿ã¾ã™"
+
msgid "Merge request"
msgstr "マージリクエスト"
msgid "Merge request approvals"
msgstr "マージリクエスト承èª"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "マージリクエスト"
@@ -5870,40 +6921,40 @@ msgid "Merge requests are a place to propose changes you've made to a project an
msgstr "マージリクエストã¨ã¯ã€ãƒ—ロジェクトã«åŠ ãˆãŸå¤‰æ›´ã‚’æ示ã—ã€ãã®å¤‰æ›´ã«ã¤ã„ã¦ä»–ã®ãƒ¡ãƒ³ãƒãƒ¼ã¨è©±ã—åˆã†ãŸã‚ã®å ´æ‰€ã§ã™"
msgid "Merge when pipeline succeeds"
-msgstr ""
+msgstr "パイプラインãŒæˆåŠŸã—ãŸã¨ãã«ãƒžãƒ¼ã‚¸"
msgid "MergeRequests|Add a reply"
-msgstr ""
+msgstr "返信を追加"
msgid "MergeRequests|An error occurred while saving the draft comment."
-msgstr ""
+msgstr "下書ãコメントã®ä¿å­˜ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "MergeRequests|Discussion stays resolved"
-msgstr ""
+msgstr "ディスカッションã¯è§£æ±ºã•ã‚ŒãŸã¾ã¾ã§ã™ã€‚"
msgid "MergeRequests|Discussion stays unresolved"
-msgstr ""
+msgstr "ディスカッションã¯æœªè§£æ±ºã®ã¾ã¾ã§ã™ã€‚"
msgid "MergeRequests|Discussion will be resolved"
-msgstr ""
+msgstr "ディスカッションã¯è§£æ±ºã•ã‚Œã¾ã™ã€‚"
msgid "MergeRequests|Discussion will be unresolved"
-msgstr ""
+msgstr "ディスカッションã¯è§£æ±ºã•ã‚Œã¾ã›ã‚“。"
msgid "MergeRequests|Jump to next unresolved discussion"
msgstr ""
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "返信..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr "æ–°ã—ã„課題ã§ã“ã®æ¤œè¨Žã‚’解決ã™ã‚‹"
msgid "MergeRequests|Saving the comment failed"
-msgstr ""
+msgstr "コメントã®ä¿å­˜ã«å¤±æ•—ã—ã¾ã—ãŸ"
msgid "MergeRequests|Toggle comments for this file"
-msgstr ""
+msgstr "ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’切り替ãˆã¾ã™"
msgid "MergeRequests|View file @ %{commitId}"
msgstr ""
@@ -5932,18 +6983,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
-msgstr ""
+msgid "MergeRequest|Filter files"
+msgstr "ファイルã®ãƒ•ã‚£ãƒ«ã‚¿ãƒªãƒ³ã‚°"
-msgid "MergeRequest|Search files"
-msgstr ""
+msgid "MergeRequest|No files found"
+msgstr "ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "Merged"
msgstr "マージ済ã¿"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "メッセージ"
@@ -5957,13 +7011,13 @@ msgid "Metrics - Prometheus"
msgstr "メトリクス - Prometheus"
msgid "Metrics and profiling"
-msgstr ""
+msgstr "メトリクスã¨ãƒ—ロファイリング"
msgid "Metrics for environment"
msgstr ""
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
-msgstr ""
+msgstr "環境ã¸ã®ãƒ‡ãƒ—ロイã«ã¤ã„ã¦ã¯ã€CI / CD ã®æ–‡æ›¸ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
msgid "Metrics|Create metric"
msgstr "メトリクスを作æˆ"
@@ -6001,20 +7055,23 @@ msgstr "æ–°è¦ãƒ¡ãƒˆãƒªã‚¯ã‚¹"
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr "Prometheus クエリã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆ"
-msgid "Metrics|System"
-msgstr "システム"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
msgid "Metrics|There was an error getting deployment information."
-msgstr ""
+msgstr "デプロイ情報ã®å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "Metrics|There was an error getting environments information."
msgstr ""
@@ -6029,7 +7086,7 @@ msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
msgid "Metrics|Unexpected metrics data response from prometheus endpoint"
-msgstr ""
+msgstr "Prometheus ã‹ã‚‰ã®äºˆæœŸã—ãªã„メトリクスデータ応答"
msgid "Metrics|Unit label"
msgstr "ユニットラベル"
@@ -6049,11 +7106,17 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "例:スループット"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "マイルストーン"
msgid "Milestone lists not available with your current license"
-msgstr ""
+msgstr "ç¾åœ¨ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã§ã¯ マイルストーンリストを利用ã§ãã¾ã›ã‚“"
msgid "Milestone lists show all issues from the selected milestone."
msgstr ""
@@ -6068,7 +7131,7 @@ msgid "Milestones| You’re about to permanently delete the milestone %{mileston
msgstr ""
msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
-msgstr ""
+msgstr "<p>%{milestonePromotion}</p> %{finalWarning}"
msgid "Milestones|Delete milestone"
msgstr "マイルストーンã®å‰Šé™¤"
@@ -6089,11 +7152,14 @@ msgid "Milestones|Promote Milestone"
msgstr "マイルストーンã®æ˜‡æ ¼"
msgid "Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}. Existing project milestones with the same name will be merged. "
-msgstr ""
+msgstr "%{milestone} を宣言ã™ã‚‹ã¨ã€ %{groupName} 内ã®ã™ã¹ã¦ã®ãƒ—ロジェクトã§åˆ©ç”¨ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚åŒã˜åå‰ã®æ—¢å­˜ã®ãƒ—ロジェクトマイルストーンãŒãƒžãƒ¼ã‚¸ã•ã‚Œã¾ã™ã€‚ "
msgid "Milestones|This action cannot be reversed."
msgstr "ã“ã®æ“作ã¯å…ƒã«æˆ»ã›ã¾ã›ã‚“。"
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6101,7 +7167,7 @@ msgid "Mirror direction"
msgstr ""
msgid "Mirror repository"
-msgstr ""
+msgstr "ミラーリãƒã‚¸ãƒˆãƒª"
msgid "Mirror user"
msgstr ""
@@ -6112,6 +7178,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "SSH éµã‚’追加"
@@ -6121,14 +7190,17 @@ msgstr "キャンセル"
msgid "Modal|Close"
msgstr "é–‰ã˜ã‚‹"
-msgid "Modify commit messages"
+msgid "Modify commit message"
msgstr ""
+msgid "Modify commit messages"
+msgstr "コミットメッセージを修正"
+
msgid "Modify merge commit"
-msgstr ""
+msgstr "マージコミットを変更"
msgid "Monday"
-msgstr ""
+msgstr "月曜日"
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
@@ -6143,10 +7215,10 @@ msgid "More"
msgstr ""
msgid "More actions"
-msgstr ""
+msgstr "ãã®ä»–ã®æ“作"
msgid "More info"
-msgstr ""
+msgstr "詳細情報"
msgid "More information"
msgstr "詳ã—ã„情報"
@@ -6154,7 +7226,13 @@ msgstr "詳ã—ã„情報"
msgid "More information is available|here"
msgstr "ã“ã“ã‚’å‚ç…§"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
+msgstr "スター数ã®å¤šã„é †"
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
msgstr ""
msgid "Move"
@@ -6166,6 +7244,12 @@ msgstr "課題を移動"
msgid "Multiple issue boards"
msgstr "マルãƒèª²é¡Œãƒœãƒ¼ãƒ‰"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "åå‰"
@@ -6173,11 +7257,17 @@ msgid "Name new label"
msgstr "æ–°ã—ã„ラベルã«åå‰ã‚’ã¤ã‘ã‚‹"
msgid "Name your individual key via a title"
-msgstr ""
+msgstr "ã‚ãªãŸã®å€‹äººã‚­ãƒ¼ã«å‘½åã™ã‚‹"
msgid "Name:"
msgstr "åå‰:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6194,7 +7284,7 @@ msgid "Nav|Sign out and sign in with a different account"
msgstr "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã™ã‚‹"
msgid "Need help?"
-msgstr ""
+msgstr "ãŠå›°ã‚Šã§ã™ã‹?"
msgid "Network"
msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯"
@@ -6209,7 +7299,7 @@ msgid "New Application"
msgstr "æ–°ã—ã„アプリケーション"
msgid "New Environment"
-msgstr ""
+msgstr "æ–°ã—ã„環境"
msgid "New Group"
msgstr "æ–°ã—ã„グループ"
@@ -6225,10 +7315,13 @@ msgid "New Label"
msgstr "æ–°ã—ã„ラベル"
msgid "New Milestone"
-msgstr ""
+msgstr "æ–°ã—ã„マイルストーン"
msgid "New Pages Domain"
-msgstr ""
+msgstr "æ–°ã—ã„ページã®ãƒ‰ãƒ¡ã‚¤ãƒ³"
+
+msgid "New Password"
+msgstr "æ–°ã—ã„パスワード"
msgid "New Pipeline Schedule"
msgstr "æ–°è¦ãƒ‘イプラインスケジュール"
@@ -6236,20 +7329,20 @@ msgstr "æ–°è¦ãƒ‘イプラインスケジュール"
msgid "New Snippet"
msgstr "æ–°è¦ã‚¹ãƒ‹ãƒšãƒƒãƒˆ"
-msgid "New Snippets"
-msgstr "æ–°è¦ã‚¹ãƒ‹ãƒšãƒƒãƒˆ"
-
msgid "New branch"
msgstr "æ–°è¦ãƒ–ランãƒ"
msgid "New branch unavailable"
msgstr "æ–°ã—ã„ブランãƒã¯åˆ©ç”¨ã§ãã¾ã›ã‚“"
+msgid "New deploy key"
+msgstr "æ–°ã—ã„デプロイキー"
+
msgid "New directory"
msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒª"
msgid "New environment"
-msgstr ""
+msgstr "æ–°ã—ã„環境"
msgid "New epic"
msgstr "æ–°ã—ã„エピック"
@@ -6260,6 +7353,9 @@ msgstr "æ–°è¦ãƒ•ã‚¡ã‚¤ãƒ«"
msgid "New group"
msgstr "æ–°è¦ã‚°ãƒ«ãƒ¼ãƒ—"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "æ–°ã—ã„ ID"
@@ -6273,7 +7369,7 @@ msgid "New merge request"
msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
msgid "New milestone"
-msgstr ""
+msgstr "æ–°ã—ã„マイルストーン"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr ""
@@ -6281,6 +7377,9 @@ msgstr ""
msgid "New project"
msgstr "æ–°è¦ãƒ—ロジェクト"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "æ–°è¦ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«"
@@ -6293,20 +7392,29 @@ msgstr "æ–°è¦ã‚µãƒ–グループ"
msgid "New tag"
msgstr "æ–°è¦ã‚¿ã‚°"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "æ–°è¦...\t"
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "ã„ã„ãˆ"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "ラベルãŒã‚ã‚Šã¾ã›ã‚“"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "担当者ãªã—"
+msgid "No activities found"
+msgstr "アクティビティã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "No branches found"
msgstr ""
@@ -6327,25 +7435,25 @@ msgid "No contributions were found"
msgstr ""
msgid "No credit card required."
-msgstr ""
+msgstr "クレジット カードã¯å¿…è¦ã‚ã‚Šã¾ã›ã‚“"
msgid "No details available"
-msgstr ""
+msgstr "詳細ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No due date"
msgstr "期é™ãªã—"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
-msgstr ""
+msgstr "見ç©ã¨å®Ÿç¸¾ã¯æœªå…¥åŠ›"
msgid "No file chosen"
msgstr "ファイルãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“"
msgid "No file selected"
-msgstr ""
+msgstr "ファイルãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“"
msgid "No files found."
msgstr "ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
@@ -6353,13 +7461,22 @@ msgstr "ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
msgid "No issues for the selected time period."
msgstr ""
-msgid "No labels with such name or description"
+msgid "No job trace"
msgstr ""
+msgid "No labels with such name or description"
+msgstr "ãã®åå‰ã¾ãŸã¯èª¬æ˜Žã®ãƒ©ãƒ™ãƒ«ãŒã‚ã‚Šã¾ã›ã‚“"
+
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
+msgstr "一致ã™ã‚‹çµæžœãŒã‚ã‚Šã¾ã›ã‚“"
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
msgid "No merge requests for the selected time period."
@@ -6372,10 +7489,10 @@ msgid "No messages were logged"
msgstr "メッセージã¯è¨˜éŒ²ã•ã‚Œã¦ã„ã¾ã›ã‚“"
msgid "No milestones to show"
-msgstr ""
+msgstr "表示ã™ã‚‹ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No other labels with such name or description"
-msgstr ""
+msgstr "ãã®ä»–ã«ã€ãã®åå‰ã¾ãŸã¯èª¬æ˜Žã®ãƒ©ãƒ™ãƒ«ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No preview for this file type"
msgstr ""
@@ -6393,26 +7510,23 @@ msgid "No repository"
msgstr "リãƒã‚¸ãƒˆãƒªãŒã‚ã‚Šã¾ã›ã‚“"
msgid "No runners found"
-msgstr ""
+msgstr "Runner ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
msgid "No schedules"
msgstr "スケジュールãªã—"
msgid "No start date"
-msgstr ""
+msgstr "開始日ãªã—"
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
msgid "Nodes"
-msgstr ""
+msgstr "ノード"
msgid "None"
msgstr "ãªã—"
-msgid "Not allowed to merge"
-msgstr "マージã¯è¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚“"
-
msgid "Not available"
msgstr "利用ã§ãã¾ã›ã‚“"
@@ -6428,11 +7542,17 @@ msgstr "機密ã§ã¯ãªã„"
msgid "Not enough data"
msgstr "データä¸è¶³"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
+msgstr "後ã§"
+
+msgid "Not ready yet. Try again later."
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "マスターブランãƒã¯è‡ªå‹•çš„ã«ä¿è­·ã•ã‚Œã‚‹ã“ã¨ã«ç•™æ„ã—ã¦ãã ã•ã„。%{link_to_protected_branches}"
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6450,7 +7570,7 @@ msgid "Note: Consider asking your GitLab administrator to configure %{github_int
msgstr "注: GitLab ã®ç®¡ç†è€…ã«%{github_integration_link} を設定ã—ã¦ã€GitHub 経由ã®ãƒ­ã‚°ã‚¤ãƒ³ãŒè¨±å¯ã—ã€å€‹äººç”¨ã®ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã‚’生æˆã›ãšã«ãƒªãƒã‚¸ãƒˆãƒªã‚’インãƒãƒ¼ãƒˆã§ããªã„ã‹å•ã„åˆã‚ã›ãã ã•ã„。"
msgid "Notes|Are you sure you want to cancel creating this comment?"
-msgstr ""
+msgstr "本当ã«ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã®ä½œæˆã‚’キャンセルã—ã¾ã™ã‹ï¼Ÿ"
msgid "Notes|Collapse replies"
msgstr ""
@@ -6459,19 +7579,19 @@ msgid "Notes|Show all activity"
msgstr ""
msgid "Notes|Show comments only"
-msgstr ""
+msgstr "コメントã®ã¿è¡¨ç¤º"
msgid "Notes|Show history only"
-msgstr ""
+msgstr "履歴ã®ã¿è¡¨ç¤º"
msgid "Notification events"
msgstr "イベント通知"
msgid "Notification setting"
-msgstr ""
+msgstr "通知設定"
msgid "Notification setting - %{notification_title}"
-msgstr ""
+msgstr "通知設定 - %{notification_title}"
msgid "NotificationEvent|Close issue"
msgstr "課題をクローズ"
@@ -6545,6 +7665,9 @@ msgstr "11月"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "10月"
@@ -6557,17 +7680,32 @@ msgstr "フィルター"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "インãƒãƒ¼ãƒˆã™ã‚‹ã¨ãƒªãƒã‚¸ãƒˆãƒªã¯ SSH ã§ãƒŸãƒ©ãƒ¼ãƒªãƒ³ã‚°ã§ãã¾ã™ã€‚詳細㯠%{ssh_link} ã‚’å‚ç…§ã—ã¦ãã ã•ã„。"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
-msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgid "One or more groups that you don't have access to."
msgstr ""
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr "1ã¤ä»¥ä¸Šã® Bitbucket プロジェクトを GitLab ã«ç›´æŽ¥ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã›ã‚“。ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã«ã€Git ã§ã¯ãªãã€Subversion ã‚„ Mercurial を使用ã—ã¦ã„ã‚‹ã‹ã‚‰ã§ã™ã€‚"
+
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
-msgstr ""
+msgstr "1ã¤ä»¥ä¸Šã® Google コードプロジェクトを GitLab ã«ç›´æŽ¥ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã›ã‚“。ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã«ã€Git ã§ã¯ãªãã€Subversion ã‚„ Mercurial を使用ã—ã¦ã„ã‚‹ã‹ã‚‰ã§ã™ã€‚"
msgid "Only admins"
+msgstr "管ç†è€…ã®ã¿"
+
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
msgstr ""
msgid "Only mirror protected branches"
@@ -6583,16 +7721,16 @@ msgid "Only project members can comment."
msgstr "プロジェクトメンãƒãƒ¼ã®ã¿ã‚³ãƒ¡ãƒ³ãƒˆã§ãã¾ã™"
msgid "Only project members will be imported. Group members will be skipped."
-msgstr ""
+msgstr "プロジェクトメンãƒãƒ¼ã®ã¿ãŒã‚¤ãƒ³ãƒãƒ¼ãƒˆã•ã‚Œã¾ã™ã€‚グループメンãƒãƒ¼ã¯ã‚¹ã‚­ãƒƒãƒ—ã•ã‚Œã¾ã™ã€‚"
msgid "Oops, are you sure?"
msgstr ""
msgid "Open"
-msgstr ""
+msgstr "é–‹ã"
msgid "Open Documentation"
-msgstr ""
+msgstr "ドキュメントを開ã"
msgid "Open comment type dropdown"
msgstr ""
@@ -6604,6 +7742,9 @@ msgid "Open in Xcode"
msgstr "Xcode ã§é–‹ã"
msgid "Open projects"
+msgstr "プロジェクトを開ã"
+
+msgid "Open raw"
msgstr ""
msgid "Open sidebar"
@@ -6616,7 +7757,7 @@ msgid "Opened"
msgstr ""
msgid "Opened MR"
-msgstr ""
+msgstr "オープン中ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
msgid "Opened issues"
msgstr ""
@@ -6633,20 +7774,35 @@ msgstr "é‹ç”¨"
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr "オプション"
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
-msgstr ""
+msgstr "オプションã¨ã—ã¦ã€ã‚ãªãŸã¯ã€%{link_to_customize} 㧠FogBugz ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ GitLab ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã™ã€‚"
msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
-msgstr ""
+msgstr "オプションã¨ã—ã¦ã€ã‚ãªãŸã¯ã€%{link_to_customize} 㧠Google コードã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ GitLab ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã™ã€‚"
msgid "Options"
msgstr "オプション"
@@ -6660,9 +7816,6 @@ msgstr "ãã®ä»–ã®ãƒ©ãƒ™ãƒ«"
msgid "Other information"
msgstr "ãã®ä»–ã®æƒ…å ±"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "ãã†ã§ãªã„å ´åˆã¯ã€æ¬¡ã®ã„ãšã‚Œã‹ã®ã‚ªãƒ—ションã§é–‹å§‹ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚"
-
msgid "Outbound requests"
msgstr ""
@@ -6676,14 +7829,20 @@ msgid "Owner"
msgstr "オーナー"
msgid "Package information"
-msgstr ""
+msgstr "パッケージ情報"
msgid "Package was removed"
-msgstr ""
+msgstr "パッケージãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
msgid "Packages"
msgstr "パッケージ"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Pages"
@@ -6706,10 +7865,7 @@ msgid "Pagination|« First"
msgstr "« 最åˆ"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "パラメーター"
msgid "Part of merge request changes"
msgstr "マージリクエストã§ã®å¤‰æ›´ç®‡æ‰€"
@@ -6717,7 +7873,13 @@ msgstr "マージリクエストã§ã®å¤‰æ›´ç®‡æ‰€"
msgid "Password"
msgstr "パスワード"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
+msgstr "期é™åˆ‡ã‚Œ"
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
msgid "Paste epic link"
@@ -6729,6 +7891,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "ã‚ãªãŸã® SSH 公開éµã‚’貼り付ã‘ã¾ã™ã€‚ã“ã®éµã¯é€šå¸¸ã€ãƒ•ã‚¡ã‚¤ãƒ« '~/.ssh/id_rsa.pub' 内ã«ã‚ã‚Šã€'ssh-rsa' ã¨ã„ã†æ–‡å­—列ã‹ã‚‰å§‹ã¾ã‚Šã¾ã™ã€‚SSH ã®ç§˜å¯†éµã‚’使用ã—ãªã„よã†ã«ã—ã¦ãã ã•ã„。"
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6738,6 +7903,9 @@ msgstr "パス:"
msgid "Pause"
msgstr "åœæ­¢"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6745,7 +7913,7 @@ msgid "Pending"
msgstr "ä¿ç•™ä¸­"
msgid "People without permission will never get a notification and won't be able to comment."
-msgstr ""
+msgstr "権é™ã®ãªã„ユーザーã¯ä¸€åˆ‡é€šçŸ¥ã‚’å—ã‘ã‚‹ã“ã¨ãŒãªãã€ã¾ãŸã‚³ãƒ¡ãƒ³ãƒˆã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã›ã‚“。"
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr "パスã®å¤‰æ›´ã€è»¢é€ã€ã‚°ãƒ«ãƒ¼ãƒ—ã®å‰Šé™¤ãªã©ã®é«˜åº¦ãªã‚ªãƒ—ションを実行ã—ã¾ã™ã€‚"
@@ -6756,9 +7924,12 @@ msgstr "パフォーマンスã®æœ€é©åŒ–"
msgid "Permissions"
msgstr "権é™"
-msgid "Permissions, LFS, 2FA"
+msgid "Permissions Help"
msgstr ""
+msgid "Permissions, LFS, 2FA"
+msgstr "パーミッションã€LFSã€2FA"
+
msgid "Personal Access Token"
msgstr "個人ã®ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³"
@@ -6766,24 +7937,27 @@ msgid "Personal project creation is not allowed. Please contact your administrat
msgstr ""
msgid "Pick a name"
-msgstr ""
+msgstr "åå‰ã‚’é¸æŠž"
msgid "Pipeline"
msgstr "パイプライン"
-msgid "Pipeline Health"
-msgstr "パイプラインã®é€²æ—状æ³"
-
msgid "Pipeline Schedule"
msgstr "パイプラインスケジュール"
msgid "Pipeline Schedules"
msgstr "パイプラインスケジュール"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "パイプラインã®ã‚¯ã‚©ãƒ¼ã‚¿"
msgid "Pipeline triggers"
+msgstr "パイプラインã®ãƒˆãƒªã‚¬ãƒ¼"
+
+msgid "Pipeline: %{status}"
msgstr ""
msgid "PipelineCharts|Failed:"
@@ -6834,6 +8008,12 @@ msgstr "変数"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "カスタム"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr "コミット:%{ci_status}"
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr "パイプライン:%{ci_status}"
+
msgid "Pipelines"
msgstr "パイプライン"
@@ -6849,6 +8029,12 @@ msgstr "先週ã®ãƒ‘イプライン"
msgid "Pipelines for last year"
msgstr "昨年ã®ãƒ‘イプライン"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr "ã“ã®æ©Ÿèƒ½ã‚’有効ã«ã™ã‚‹ã«ã¯ã€ãƒ‘イプラインを設定ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6883,32 +8069,32 @@ msgid "Pipelines|There are currently no pipelines."
msgstr "パイプラインã¯ç¾åœ¨ã‚ã‚Šã¾ã›ã‚“。"
msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
-msgstr ""
+msgstr "パイプラインをフェッãƒã™ã‚‹éš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ã„ãŸã ãã‹ã€ã‚µãƒãƒ¼ãƒˆãƒãƒ¼ãƒ ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。"
msgid "Pipelines|This project is not currently set up to run pipelines."
msgstr "ã“ã®ãƒ—ロジェクトã¯ç¾åœ¨ãƒ‘イプラインを実行ã™ã‚‹ã‚ˆã†ã«è¨­å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。"
msgid "Pipeline|Commit"
-msgstr ""
-
-msgid "Pipeline|Create for"
-msgstr "実行対象"
+msgstr "コミット"
-msgid "Pipeline|Create pipeline"
-msgstr "パイプラインを作æˆ"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
-msgstr ""
+msgstr "期間"
msgid "Pipeline|Existing branch name or tag"
msgstr ""
msgid "Pipeline|Pipeline"
-msgstr ""
+msgstr "パイプライン"
msgid "Pipeline|Run Pipeline"
msgstr "パイプラインを実行"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "ブランãƒã®æ¤œç´¢"
@@ -6916,10 +8102,10 @@ msgid "Pipeline|Specify variable values to be used in this run. The values speci
msgstr "ã“ã®å®Ÿè¡Œã§ä½¿ç”¨ã•ã‚Œã‚‹å¤‰æ•°ã®å€¤ã‚’指定ã—ã¾ã™ã€‚ %{settings_link} ã§æŒ‡å®šã•ã‚ŒãŸå€¤ãŒãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚"
msgid "Pipeline|Stages"
-msgstr ""
+msgstr "ステージ"
msgid "Pipeline|Status"
-msgstr ""
+msgstr "ステータス"
msgid "Pipeline|Stop pipeline"
msgstr "パイプラインã®åœæ­¢"
@@ -6936,9 +8122,21 @@ msgstr "%{pipelineId} パイプラインをåœæ­¢ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚"
msgid "Pipeline|all"
msgstr "全件"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "æˆåŠŸ"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "ステージã‚ã‚Š"
@@ -6955,7 +8153,7 @@ msgid "Play"
msgstr "実行"
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
-msgstr ""
+msgstr "コメントã™ã‚‹ã«ã¯ %{link_to_register} ã¾ãŸã¯ %{link_to_sign_in} ã‚’ã—ã¦ãã ã•ã„"
msgid "Please accept the Terms of Service before continuing."
msgstr "続ã‘ã‚‹å‰ã«ã€åˆ©ç”¨è¦ç´„ã«åŒæ„ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -6963,15 +8161,33 @@ msgstr "続ã‘ã‚‹å‰ã«ã€åˆ©ç”¨è¦ç´„ã«åŒæ„ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr "ãれらを Google コード上㧠Git ã«å¤‰æ›ã—ã¦ã€å†ã³ %{link_to_import_flow} ã‚’è¡Œã£ã¦ãã ã•ã„。"
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
msgstr ""
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr "有効ãªæ•°å€¤ã‚’入力ã—ã¦ãã ã•ã„"
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -6979,11 +8195,23 @@ msgid "Please migrate all existing projects to hashed storage to avoid security
msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr "ã“ã®ã‚¢ãƒ—リケーション㯠GitLab ãŒæä¾›ã—ã¦ã„ã¾ã›ã‚“。ãã®ãŸã‚ã‚ãªãŸãŒã‚¢ã‚¯ã‚»ã‚¹ã‚’許å¯ã™ã‚‹å‰ã«ãã®èªè¨¼ã‚’確èªã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
+
+msgid "Please provide a name"
+msgstr "åå‰ã‚’入力ã—ã¦ãã ã•ã„"
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
msgstr ""
msgid "Please select at least one filter to see results"
msgstr "çµæžœã‚’表示ã™ã‚‹ã«ã¯ã€å°‘ãªãã¨ã‚‚1ã¤ã®ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼ã‚’é¸æŠžã—ã¦ãã ã•ã„"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "reCAPTCHA を解決ã—ã¦ãã ã•ã„"
@@ -6996,6 +8224,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "リãƒã‚¸ãƒˆãƒªã«æŽ¥ç¶šã§ãã‚‹ã¾ã§ã€ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„。完了ã™ã‚‹ã¨ã€ç”»é¢ãŒè‡ªå‹•çš„ã«æ›´æ–°ã•ã‚Œã¾ã™ã€‚"
@@ -7005,23 +8236,38 @@ msgstr "リãƒã‚¸ãƒˆãƒªã‚’インãƒãƒ¼ãƒˆã—ã¦ã„ã‚‹ã®ã§ã€ã—ã°ã‚‰ããŠå¾…
msgid "Preferences"
msgstr "基本設定"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "ナビゲーションテーマ"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr "ã“ã®æ©Ÿèƒ½ã¯å®Ÿé¨“çš„ãªã‚‚ã®ã§ã™ã€‚ã¾ãŸç¿»è¨³ã¯ã¾ã å®Œäº†ã—ã¦ã„ã¾ã›ã‚“"
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
+msgstr "プレビュー"
+
+msgid "Preview changes"
msgstr ""
msgid "Preview payload"
msgstr ""
msgid "Primary"
-msgstr ""
+msgstr "プライマリ"
msgid "Prioritize"
msgstr "優先順ä½ã‚’付ã‘ã‚‹"
@@ -7036,7 +8282,7 @@ msgid "Prioritized label"
msgstr "優先ラベル"
msgid "Private"
-msgstr ""
+msgstr "プライベート"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "プライベート - å„ユーザーã«ãƒ—ロジェクトã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ã‚’明示的ã«è¨±å¯ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
@@ -7044,6 +8290,9 @@ msgstr "プライベート - å„ユーザーã«ãƒ—ロジェクトã®ã‚¢ã‚¯ã‚»ã‚¹
msgid "Private - The group and its projects can only be viewed by members."
msgstr "プライベート - グループã¨ãƒ—ロジェクトã¯ãƒ¡ãƒ³ãƒãƒ¼ã®ã¿ãŒé–²è¦§ã§ãã¾ã™ã€‚"
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "ã‚ãªãŸå€‹äººã®åå‰ç©ºé–“ã«ãƒ—ライベートプロジェクトを作æˆã§ãã¾ã™:"
@@ -7051,7 +8300,7 @@ msgid "Profile"
msgstr "プロフィール"
msgid "Profile Settings"
-msgstr ""
+msgstr "プロファイルã®è¨­å®š"
msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
msgstr ""
@@ -7060,7 +8309,7 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr ""
msgid "Profiles|@username"
-msgstr ""
+msgstr "@username"
msgid "Profiles|Account scheduled for removal."
msgstr "削除予定ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã™ã€‚"
@@ -7069,19 +8318,19 @@ msgid "Profiles|Activate signin with one of the following services"
msgstr ""
msgid "Profiles|Active"
-msgstr ""
+msgstr "アクティブ"
msgid "Profiles|Add key"
msgstr "キーを追加"
msgid "Profiles|Add status emoji"
-msgstr ""
+msgstr "ステータス絵文字を追加"
msgid "Profiles|Avatar cropper"
msgstr ""
msgid "Profiles|Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "ã‚¢ãƒã‚¿ãƒ¼ã‚’削除ã—ã¾ã™ã€‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid "Profiles|Change username"
msgstr "ユーザーåã®å¤‰æ›´"
@@ -7090,16 +8339,16 @@ msgid "Profiles|Changing your username can have unintended side effects."
msgstr ""
msgid "Profiles|Choose file..."
-msgstr ""
+msgstr "ファイルをé¸æŠž..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
-msgstr ""
+msgstr "プロジェクトã€ãƒªãƒã‚¸ãƒˆãƒªã¾ãŸã¯çµ„織情報ãªã—ã§ã€å…¬é–‹ãƒ—ロファイルã«éžå…¬é–‹ãƒ—ロジェクトã®æŠ•ç¨¿ã‚’表示ã™ã‚‹ã“ã¨ã‚’é¸æŠžã—ã¾ã™"
msgid "Profiles|City, country"
msgstr ""
msgid "Profiles|Clear status"
-msgstr ""
+msgstr "ステータスをクリア"
msgid "Profiles|Click on icon to activate signin with one of the following services"
msgstr ""
@@ -7114,7 +8363,7 @@ msgid "Profiles|Current path: %{path}"
msgstr "ç¾åœ¨ã®ãƒ‘ス: %{path}"
msgid "Profiles|Current status"
-msgstr ""
+msgstr "ç¾åœ¨ã®çŠ¶æ…‹"
msgid "Profiles|Delete Account"
msgstr "アカウント削除"
@@ -7132,13 +8381,13 @@ msgid "Profiles|Disconnect"
msgstr ""
msgid "Profiles|Do not show on profile"
-msgstr ""
+msgstr "プロフィールã«è¡¨ç¤ºã—ãªã„"
msgid "Profiles|Don't display activity-related personal information on your profiles"
msgstr ""
msgid "Profiles|Edit Profile"
-msgstr ""
+msgstr "プロフィールを編集"
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
@@ -7180,7 +8429,7 @@ msgid "Profiles|Remove avatar"
msgstr ""
msgid "Profiles|Set new profile picture"
-msgstr ""
+msgstr "æ–°ã—ã„プロフィール画åƒã‚’設定ã™ã‚‹"
msgid "Profiles|Social sign-in"
msgstr ""
@@ -7194,21 +8443,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7240,7 +8489,7 @@ msgid "Profiles|Username successfully changed"
msgstr "ユーザーåã¯æ­£å¸¸ã«å¤‰æ›´ã•ã‚Œã¾ã—ãŸ"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "åå‰ã«çµµæ–‡å­—を使ã†ã®ã¯æ¥½ã—ãã†ã§ã™ãŒã€ä»£ã‚ã‚Šã«ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’設定ã—ã¦ã¿ã¦ãã ã•ã„"
msgid "Profiles|What's your status?"
msgstr ""
@@ -7285,10 +8534,10 @@ msgid "Profiles|Your status"
msgstr ""
msgid "Profiles|e.g. My MacBook key"
-msgstr ""
+msgstr "例:MacBook ã®ã‚­ãƒ¼"
msgid "Profiles|username"
-msgstr ""
+msgstr "ユーザーå"
msgid "Profiles|website.com"
msgstr ""
@@ -7308,6 +8557,15 @@ msgstr "進行状æ³"
msgid "Project"
msgstr "プロジェクト"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "プロジェクト '%{project_name}' ã¯å‰Šé™¤ä¸­ã§ã™ã€‚"
@@ -7323,17 +8581,26 @@ msgstr "'%{project_name}' プロジェクトã¯æ­£å¸¸ã«æ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚
msgid "Project Badges"
msgstr "プロジェクトãƒãƒƒã‚¸"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr "ユーザーã”ã¨ã«ãƒ—ロジェクトアクセスã®æ¨©é™ã‚’指定ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "プロジェクトアãƒã‚¿ãƒ¼"
-msgid "Project avatar in repository: %{link}"
-msgstr "リãƒã‚¸ãƒˆãƒªå†…ã®ãƒ—ロジェクトアãƒã‚¿ãƒ¼: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "プロジェクトã®è©³ç´°"
@@ -7341,6 +8608,9 @@ msgstr "プロジェクトã®è©³ç´°"
msgid "Project export could not be deleted."
msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’削除ã—ã¾ã—ãŸã€‚"
@@ -7350,18 +8620,27 @@ msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãƒªãƒ³ã‚¯ã¯æœŸé™åˆ‡ã‚Œã«ãªã‚Š
msgid "Project export started. A download link will be sent by email."
msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’開始ã—ã¾ã—ãŸã€‚ダウンロードã®ãƒªãƒ³ã‚¯ã¯ãƒ¡ãƒ¼ãƒ«ã§é€ä¿¡ã—ã¾ã™"
-msgid "Project members"
+msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project members"
+msgstr "プロジェクトメンãƒãƒ¼"
+
msgid "Project name"
msgstr "プロジェクトå"
msgid "Project slug"
msgstr ""
-msgid "Project:"
+msgid "Project uploads"
msgstr ""
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project:"
+msgstr "プロジェクト:"
+
msgid "ProjectActivityRSS|Subscribe"
msgstr "講読"
@@ -7449,12 +8728,21 @@ msgstr ""
msgid "Projects"
msgstr "プロジェクト"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "よã使ã†ãƒ—ロジェクト"
@@ -7503,6 +8791,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7605,6 +8896,9 @@ msgstr "ã“ã®æ©Ÿèƒ½ã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚"
msgid "Promotions|Upgrade plan"
msgstr "プランをアップグレード"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7624,31 +8918,31 @@ msgid "ProtectedEnvironment|Environment"
msgstr ""
msgid "ProtectedEnvironment|Protect"
-msgstr ""
+msgstr "ä¿è­·"
msgid "ProtectedEnvironment|Protect Environments in order to restrict who can execute deployments."
msgstr ""
msgid "ProtectedEnvironment|Protect an environment"
-msgstr ""
+msgstr "環境をä¿è­·"
msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
-msgstr ""
+msgstr "ä¿è­·ã•ã‚ŒãŸç’°å¢ƒ (%{protected_environments_count})"
msgid "ProtectedEnvironment|Select an environment"
-msgstr ""
+msgstr "環境をé¸æŠž"
msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
-msgstr ""
+msgstr "ç¾åœ¨ä¿è­·ã•ã‚ŒãŸç’°å¢ƒã¯ã‚ã‚Šã¾ã›ã‚“。上ã®ãƒ•ã‚©ãƒ¼ãƒ ã§ç’°å¢ƒã‚’ä¿è­·ã—ã¦ãã ã•ã„。"
msgid "ProtectedEnvironment|Unprotect"
-msgstr ""
+msgstr "ä¿è­·ã®è§£é™¤"
msgid "ProtectedEnvironment|Your environment can't be unprotected"
-msgstr ""
+msgstr "ã‚ãªãŸã®ç’°å¢ƒã®ä¿è­·ã‚’解除ã§ãã¾ã›ã‚“。"
msgid "ProtectedEnvironment|Your environment has been protected."
-msgstr ""
+msgstr "ã‚ãªãŸã®ç’°å¢ƒã¯ä¿è­·ã•ã‚Œã¦ã„ã¾ã™ã€‚"
msgid "ProtectedEnvironment|Your environment has been unprotected"
msgstr ""
@@ -7657,13 +8951,13 @@ msgid "Protip:"
msgstr ""
msgid "Provider"
-msgstr ""
+msgstr "プロãƒã‚¤ãƒ€ãƒ¼"
msgid "Pseudonymizer data collection"
msgstr ""
msgid "Public"
-msgstr ""
+msgstr "公開"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "公開- グループãŠã‚ˆã³å…¬é–‹ãƒ—ロジェクトã¯èªè¨¼ç„¡ã—ã§é–²è¦§ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™"
@@ -7671,16 +8965,28 @@ msgstr "公開- グループãŠã‚ˆã³å…¬é–‹ãƒ—ロジェクトã¯èªè¨¼ç„¡ã—ã§é
msgid "Public - The project can be accessed without any authentication."
msgstr "公開 - プロジェクトã¯èªè¨¼ç„¡ã—ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™"
-msgid "Public pipelines"
+msgid "Public Access Help"
msgstr ""
-msgid "Pull"
+msgid "Public deploy keys (%{deploy_keys_count})"
msgstr ""
+msgid "Public pipelines"
+msgstr "公開パイプライン"
+
+msgid "Pull"
+msgstr "プル"
+
msgid "Push"
-msgstr ""
+msgstr "プッシュ"
msgid "Push Rules"
+msgstr "プッシュルール"
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
msgstr ""
msgid "Push events"
@@ -7702,15 +9008,18 @@ msgid "Pushes"
msgstr "プッシュ"
msgid "Quarters"
-msgstr ""
+msgstr "クォーター"
msgid "Query"
-msgstr ""
+msgstr "クエリ"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "クイックアクションã¯èª²é¡Œã®èª¬æ˜Žã¨ã‚³ãƒ¡ãƒ³ãƒˆæ¬„ã§ä½¿ç”¨ã§ãã¾ã™ã€‚"
msgid "README"
+msgstr "README"
+
+msgid "Rake Tasks Help"
msgstr ""
msgid "Read more"
@@ -7720,7 +9029,7 @@ msgid "Read more about environments"
msgstr ""
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
-msgstr ""
+msgstr "プロジェクトã®æ¨©é™ã«ã¤ã„ã¦ã¯ <strong>%{link_to_help}</strong> ã‚’å‚ç…§"
msgid "Real-time features"
msgstr "リアルタイム機能"
@@ -7728,9 +9037,15 @@ msgstr "リアルタイム機能"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
-msgid "Recent searches"
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
msgstr ""
+msgid "Recent searches"
+msgstr "最近ã®æ¤œç´¢"
+
msgid "Redirect to SAML provider to test configuration"
msgstr ""
@@ -7762,6 +9077,9 @@ msgstr "ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—ã® Runner ã®ç™»éŒ²ã¨ç¢ºèª"
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "レジストリ"
@@ -7787,7 +9105,7 @@ msgid "Related merge requests"
msgstr "関連ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
msgid "Releases"
-msgstr ""
+msgstr "リリース"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
@@ -7802,14 +9120,23 @@ msgid "Remove Runner"
msgstr "Runner を削除"
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
-msgstr ""
+msgstr "æ–°ã—ã„コミットãŒã‚½ãƒ¼ã‚¹ãƒ–ランãƒã«ãƒ—ッシュã•ã‚ŒãŸã¨ãã«ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ã™ã¹ã¦ã®æ‰¿èªã‚’削除ã™ã‚‹"
msgid "Remove approver"
+msgstr "承èªè€…を削除"
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
msgstr ""
msgid "Remove avatar"
msgstr "ã‚¢ãƒã‚¿ãƒ¼ã‚’削除"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7819,12 +9146,33 @@ msgstr "優先度を削除"
msgid "Remove project"
msgstr "プロジェクトを削除"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7838,14 +9186,17 @@ msgid "Reopen epic"
msgstr ""
msgid "Reopen milestone"
-msgstr ""
+msgstr "マイルストーンをå†é–‹"
msgid "Repair authentication"
msgstr "èªè¨¼ã®ä¿®å¾©"
-msgid "Reply to comment"
+msgid "Replace"
msgstr ""
+msgid "Reply to comment"
+msgstr "コメントã«è¿”ä¿¡"
+
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr ""
@@ -7856,7 +9207,7 @@ msgid "Report abuse to GitLab"
msgstr ""
msgid "Reporting"
-msgstr ""
+msgstr "レãƒãƒ¼ãƒˆ"
msgid "Reports|%{failedString} and %{resolvedString}"
msgstr ""
@@ -7876,6 +9227,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7894,6 +9257,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7906,11 +9275,14 @@ msgstr ""
msgid "Repository URL"
msgstr ""
-msgid "Repository cleanup"
+msgid "Repository check was triggered."
msgstr ""
+msgid "Repository cleanup"
+msgstr "リãƒã‚¸ãƒˆãƒªã®ã‚¯ãƒªãƒ¼ãƒ³ã‚¢ãƒƒãƒ—"
+
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
-msgstr ""
+msgstr "リãƒã‚¸ãƒˆãƒªã®ã‚¯ãƒªãƒ¼ãƒ³ã‚¢ãƒƒãƒ—ãŒé–‹å§‹ã•ã‚Œã¾ã—ãŸã€‚クリーンアップãŒå®Œäº†ã™ã‚‹ã¨ã€ãƒ¡ãƒ¼ãƒ«ãŒå±Šãã¾ã™ã€‚"
msgid "Repository has no locks."
msgstr "リãƒã‚¸ãƒˆãƒªã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã›ã‚“。"
@@ -7942,14 +9314,25 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "GitLab ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹éš›ã«ã€ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒåˆ©ç”¨è¦ç´„ã«åŒæ„ã™ã‚‹ã“ã¨ã‚’è¦æ±‚ã—ã¾ã™ã€‚"
-msgid "Resend invite"
+msgid "Require approval from code owners"
msgstr ""
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
+msgid "Resend invite"
+msgstr "招待をå†é€ä¿¡"
+
msgid "Reset authorization key"
-msgstr ""
+msgstr "èªè¨¼ã‚­ãƒ¼ã‚’リセット"
msgid "Reset authorization key?"
-msgstr ""
+msgstr "èªè¨¼ã‚­ãƒ¼ã‚’リセットã—ã¾ã™ã‹ï¼Ÿ"
msgid "Reset health check access token"
msgstr "正常性ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã‚’リセット"
@@ -7975,9 +9358,18 @@ msgstr "検討を解決"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -7997,17 +9389,23 @@ msgid "Response metrics (NGINX)"
msgstr ""
msgid "Restart Terminal"
-msgstr ""
+msgstr "ターミナルをå†èµ·å‹•"
msgid "Resume"
msgstr "å†é–‹"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "å†è©¦è¡Œ"
msgid "Retry this job"
msgstr "ジョブをå†è©¦è¡Œã—ã¦ãã ã•ã„"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "検証をå†è©¦è¡Œã—ã¦ãã ã•ã„"
@@ -8016,7 +9414,7 @@ msgid_plural "Reveal values"
msgstr[0] "値を表示ã™ã‚‹"
msgid "Reveal values"
-msgstr ""
+msgstr "値を表示ã™ã‚‹"
msgid "Revert this commit"
msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’リãƒãƒ¼ãƒˆ"
@@ -8039,12 +9437,21 @@ msgstr "レビュー中 (マージリクエスト !%{mergeRequestId})"
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "ロードマップ"
msgid "Run CI/CD pipelines for external repositories"
msgstr "外部リãƒã‚¸ãƒˆãƒªç”¨ CI/CD パイプラインを実行"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8066,6 +9473,12 @@ msgstr ""
msgid "Runner token"
msgstr "Runner トークン"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8085,7 +9498,7 @@ msgid "Runners can be placed on separate users, servers, even on your local mach
msgstr ""
msgid "Runners currently online: %{active_runners_count}"
-msgstr ""
+msgstr "ç¾åœ¨ã‚ªãƒ³ãƒ©ã‚¤ãƒ³ã®Runner: %{active_runners_count}"
msgid "Runners page"
msgstr ""
@@ -8099,27 +9512,27 @@ msgstr ""
msgid "Running"
msgstr "稼åƒä¸­"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "%{group_name} 用㮠SAML SSO"
-msgid "SAML Single Sign On"
-msgstr "SAML シングル サインオン"
-
-msgid "SAML Single Sign On Settings"
-msgstr "SAML シングル サインオンã®è¨­å®š"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "SAML トークン㮠SHA1 フィンガープリントã§è¨¼æ˜Žæ›¸ã«ç½²åã—ã¾ã™ã€‚ã“ã‚Œã¯ã‚µãƒ ãƒ—リントã¨å‘¼ã°ã‚Œã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ã‹ã‚‰å–å¾—ã§ãã¾ã™ã€‚"
-
msgid "SSH Keys"
msgstr "SSH éµ"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH ホストキー"
@@ -8129,11 +9542,14 @@ msgstr "SSH 公開éµ"
msgid "SSL Verification"
msgstr "SSL ã®æ¤œè¨¼"
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "ä¿å­˜"
msgid "Save Changes"
-msgstr ""
+msgstr "変更をä¿å­˜"
msgid "Save application"
msgstr "アプリケーションã®ä¿å­˜"
@@ -8145,7 +9561,7 @@ msgid "Save changes before testing"
msgstr ""
msgid "Save comment"
-msgstr ""
+msgstr "コメントをä¿å­˜"
msgid "Save pipeline schedule"
msgstr "パイプラインスケジュールをä¿å­˜"
@@ -8153,6 +9569,9 @@ msgstr "パイプラインスケジュールをä¿å­˜"
msgid "Save variables"
msgstr "変数をä¿å­˜ã™ã‚‹"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "æ–°ã—ã„パイプラインã®ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’作æˆ"
@@ -8162,15 +9581,24 @@ msgstr "スケジュール済"
msgid "Schedules"
msgstr "スケジュール"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "パイプラインスケジューリング"
msgid "Scope"
msgstr "スコープ"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "スコープ付課題ボード"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8199,7 +9627,7 @@ msgid "Search for projects, issues, etc."
msgstr "プロジェクトや課題ãªã©ã‚’検索ã™ã‚‹ã€‚"
msgid "Search groups"
-msgstr ""
+msgstr "グループを検索"
msgid "Search merge requests"
msgstr "マージリクエストを検索"
@@ -8217,14 +9645,17 @@ msgid "Search project"
msgstr "プロジェクトを検索"
msgid "Search projects"
-msgstr ""
+msgstr "プロジェクトを検索"
msgid "Search users"
msgstr "ユーザーを検索"
-msgid "Search your projects"
+msgid "Search users or groups"
msgstr ""
+msgid "Search your projects"
+msgstr "プロジェクトを検索"
+
msgid "SearchAutocomplete|All GitLab"
msgstr ""
@@ -8249,6 +9680,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8270,9 +9710,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8288,10 +9725,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8342,6 +9779,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr "プロジェクトをフォークã™ã‚‹åå‰ç©ºé–“ã‚’é¸æŠžã—ã¦ãã ã•ã„"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8351,9 +9797,6 @@ msgstr "タイムゾーンをé¸æŠž"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "既存㮠Kubernetes クラスターをé¸æŠžã™ã‚‹ã‹ã€æ–°ã—ã„ã‚‚ã®ã‚’作æˆ"
-msgid "Select assignee"
-msgstr "担当者をé¸æŠž"
-
msgid "Select branch/tag"
msgstr "ブランãƒãƒ»ã‚¿ã‚°é¸æŠž"
@@ -8384,11 +9827,14 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "GitLab ユーザーをé¸æŠžã™ã‚‹ã¨ã€GitLab ユーザーã¸ã®ãƒªãƒ³ã‚¯ãŒèª²é¡Œã‚„コメントã®èª¬æ˜Žã«è¿½åŠ ã•ã‚Œã¾ã™ (例:\"By <a href=\"#\">@johnsmith</a>\")。ã¾ãŸã€ã“れらã®èª²é¡Œã‚„コメントをé¸æŠžã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é–¢é€£ä»˜ã‘や割り当ã¦ãŒã§ãã¾ã™ã€‚"
-msgid "Selective synchronization"
-msgstr "é¸æŠžçš„ãªåŒæœŸ"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "メールをé€ä¿¡"
@@ -8405,18 +9851,39 @@ msgstr ""
msgid "Sep"
msgstr "9月"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "9月"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "サーãƒãƒ¼ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8432,9 +9899,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8462,9 +9926,18 @@ msgstr "サービス テンプレート"
msgid "Service URL"
msgstr "サービス URL"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "セッションã®æœ‰åŠ¹æœŸé™ã€ãƒ—ロジェクトã®ä¸Šé™ã€æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ã®ã‚µã‚¤ã‚º"
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "%{protocol} プロコトル経由ã§ãƒ—ルã€ãƒ—ッシュã™ã‚‹ãŸã‚ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードを設定。"
@@ -8480,6 +9953,9 @@ msgstr "インスタンス全体レベルã®ãƒ†ãƒ³ãƒ—レートリãƒã‚¸ãƒˆãƒªã‚’
msgid "Set max session time for web terminal."
msgstr "ウェブターミナルã®æœ€å¤§ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚¿ã‚¤ãƒ ã‚’設定ã™ã‚‹ã€‚"
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "迷惑行為レãƒãƒ¼ãƒˆã®é€šçŸ¥ãƒ¡ãƒ¼ãƒ«ã‚’設定ã™ã‚‹ã€‚"
@@ -8489,6 +9965,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "ユーザーãŒã‚µã‚¤ãƒ³ã‚¤ãƒ³ã™ã‚‹ãŸã‚ã®è¦ä»¶ã‚’設定ã—ã¾ã™ã€‚å¿…é ˆã®äºŒè¦ç´ èªè¨¼ã‚’有効ã«ã—ã¾ã™ã€‚"
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "CI/CD を設定"
@@ -8504,6 +9992,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8561,9 +10052,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr "シャーロックトランザクション"
+msgid "Show all activity"
+msgstr "ã™ã¹ã¦ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティを表示"
+
msgid "Show command"
msgstr "コマンドを表示"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "完全ãªç”Ÿãƒ­ã‚°ã‚’表示ã™ã‚‹"
@@ -8573,6 +10070,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "親ページを表示"
@@ -8631,6 +10131,12 @@ msgstr "サインインã®åˆ¶é™"
msgid "Sign-up restrictions"
msgstr "サインアップã®åˆ¶é™"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8640,6 +10146,9 @@ msgstr "サイズ"
msgid "Size and domain settings for static websites"
msgstr "é™çš„ãªã‚¦ã‚§ãƒ–サイトã®ã‚µã‚¤ã‚ºã¨ãƒ‰ãƒ¡ã‚¤ãƒ³ã®è¨­å®š"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Slack アプリケーション"
@@ -8656,11 +10165,32 @@ msgid "Smartcard authentication failed: client certificate header is missing."
msgstr ""
msgid "Snippet Contents"
-msgstr ""
+msgstr "スニペットã®å†…容"
msgid "Snippets"
msgstr "スニペット"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr "一部ã®ãƒ¡ãƒ¼ãƒ«ã‚µãƒ¼ãƒãƒ¼ã¯ã€ãƒ¡ãƒ¼ãƒ«é€ä¿¡è€…åã®ä¸Šæ›¸ãをサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“。ã“ã®ã‚ªãƒ—ションを有効ã«ã™ã‚‹ã¨ã€ä»£ã‚ã‚Šã«ãƒ¡ãƒ¼ãƒ«æœ¬æ–‡ã«èª²é¡Œã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã€ã¾ãŸã¯ã‚³ãƒ¡ãƒ³ãƒˆã®ä½œæˆè€…ã®åå‰ã‚’å«ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8706,6 +10236,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr "グループメンãƒãƒ¼ã®è²¢çŒ®åº¦ã‚’å–得中ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸ"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8733,6 +10266,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚å†è©¦è¡Œã—ã¦ãã ã•ã„。"
@@ -8883,6 +10419,9 @@ msgstr "スパム ログ"
msgid "Spam and Anti-bot Protection"
msgstr "スパムã¨ã‚¢ãƒ³ãƒãƒœãƒƒãƒˆä¿è­·"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8931,20 +10470,32 @@ msgstr "スター付ãプロジェクトã®æ´»å‹•"
msgid "Starred projects"
msgstr "スター付ãプロジェクト"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "ã“ã®å¤‰æ›´ã§ %{new_merge_request} を作æˆã™ã‚‹"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
msgid "Start and due date"
-msgstr ""
+msgstr "開始日ã¨çµ‚了日"
msgid "Start cleanup"
msgstr ""
@@ -8973,6 +10524,9 @@ msgstr "開始済ã¿"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -8992,7 +10546,7 @@ msgid "Status:"
msgstr ""
msgid "Stop Terminal"
-msgstr ""
+msgstr "ターミナルをåœæ­¢"
msgid "Stop environment"
msgstr ""
@@ -9010,7 +10564,7 @@ msgid "Stopping this environment is currently not possible as a deployment is in
msgstr ""
msgid "Stopping..."
-msgstr ""
+msgstr "åœæ­¢ä¸­..."
msgid "Storage"
msgstr "ストレージ"
@@ -9028,7 +10582,7 @@ msgid "Submit as spam"
msgstr "スパムã¨ã—ã¦å ±å‘Š"
msgid "Submit feedback"
-msgstr ""
+msgstr "フィードãƒãƒƒã‚¯ã‚’é€ä¿¡"
msgid "Submit review"
msgstr ""
@@ -9046,7 +10600,7 @@ msgid "Subscribe at project level"
msgstr "プロジェクトレベルã§è³¼èª­ã™ã‚‹"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "RSS フィードを購読"
msgid "Subscribe to calendar"
msgstr ""
@@ -9126,24 +10680,54 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
msgid "Sunday"
-msgstr ""
+msgstr "日曜日"
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "ブランãƒãƒ»ã‚¿ã‚°åˆ‡ã‚Šæ›¿ãˆ"
msgid "Sync information"
msgstr "åŒæœŸæƒ…å ±"
+msgid "System"
+msgstr "システム"
+
msgid "System Hooks"
msgstr "システムフック"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "システム情報"
@@ -9153,6 +10737,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "システムメトリクス(カスタム)"
@@ -9160,10 +10747,10 @@ msgid "System metrics (Kubernetes)"
msgstr ""
msgid "Tag"
-msgstr ""
+msgstr "ã‚¿ã‚°"
msgid "Tag list:"
-msgstr ""
+msgstr "タグ一覧:"
msgid "Tags"
msgstr "ã‚¿ã‚°"
@@ -9259,7 +10846,7 @@ msgid "Templates"
msgstr "テンプレート"
msgid "Terminal"
-msgstr ""
+msgstr "ターミナル"
msgid "Terminal for environment"
msgstr ""
@@ -9276,12 +10863,22 @@ msgstr ""
msgid "Test coverage parsing"
msgstr "テストカãƒãƒ¬ãƒƒã‚¸è§£æž"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "ã‚ã‚ŠãŒã¨ã†ï¼ä»Šå¾Œè¡¨ç¤ºã—ãªã„"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "GitLab ã®é«˜åº¦ãªã‚°ãƒ­ãƒ¼ãƒãƒ«æ¤œç´¢ã¯ã€æ™‚間を節約ã§ãる強力ãªæ¤œç´¢ã‚µãƒ¼ãƒ“スã§ã™ã€‚é‡è¤‡ã—ãŸã‚³ãƒ¼ãƒ‰ã®ä½œæˆã‚’ã—ã¦æ™‚間を無駄ã«ã™ã‚‹ã“ã¨ãªãã€è‡ªèº«ã®ãƒ—ロジェクトã®åŠ©ã‘ã¨ãªã‚‹å¤–ã®ãƒãƒ¼ãƒ ã®ã‚³ãƒ¼ãƒ‰ã‚’検索ã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚"
@@ -9300,6 +10897,12 @@ msgstr "課題トラッカーã¯ã€ãƒ—ロジェクトを改善ã—ãŸã‚Šè§£æ±ºã—
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "外部èªè¨¼ã‚µãƒ¼ãƒ“スã¨ã®é€šä¿¡ã«ç›¸äº’ TLS ãŒå¿…è¦ãªå ´åˆã«ä½¿ç”¨ã™ã‚‹ X509 証明書。空白ã®ã¾ã¾ã«ã™ã‚‹ã¨ã€HTTPS 経由ã§ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã¨ãã«ã‚µãƒ¼ãƒè¨¼æ˜Žæ›¸ã®æ¤œè¨¼ãŒè¡Œã‚ã‚Œã¾ã™ã€‚"
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9312,15 +10915,63 @@ msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã§è¨ˆæ¸¬ãƒ‡ãƒ¼ã‚¿ã«è¿½åŠ ã•ã‚ŒãŸã‚¤ãƒ™ãƒ³ãƒˆãƒªã‚¹
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "コãƒã‚¯ã‚·ãƒ§ãƒ³ã¯ %{timeout} ã§ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã™ã€‚タイムアウトã™ã‚‹ãƒªãƒã‚¸ãƒˆãƒªã§ã¯ã€clone/push を組ã¿åˆã‚ã›ã¦ä½¿ç”¨ã—ã¦ãã ã•ã„。"
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "フォークã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚"
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "インãƒãƒ¼ãƒˆã¯ %{timeout} ã§ã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆã—ã¾ã™ã€‚タイムアウトã™ã‚‹ãƒªãƒã‚¸ãƒˆãƒªã§ã¯ã€clone/push を組ã¿åˆã‚ã›ã¦ä½¿ç”¨ã—ã¦ãã ã•ã„。"
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "課題ステージã§ã¯ã€èª²é¡ŒãŒç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã‚‹ã‹ã€èª²é¡Œãƒœãƒ¼ãƒ‰ã®ãƒªã‚¹ãƒˆã«è¿½åŠ ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒªã‚¹ãƒˆã«è¡¨ç¤ºã™ã‚‹ã«ã¯èª²é¡Œã‚’最åˆã«ä½œæˆã—ã¦ãã ã•ã„。"
@@ -9330,6 +10981,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "許å¯ã•ã‚Œã‚‹æœ€å¤§ãƒ•ã‚¡ã‚¤ãƒ«ã‚µã‚¤ã‚ºã¯ 200KB ã§ã™ã€‚"
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "秘密éµã®å¾©å·ã«å¿…è¦ã¨ãªã‚‹ãƒ‘スフレーズ。ã“ã‚Œã¯ã‚ªãƒ—ションã§ã€å€¤ã¯æš—å·åŒ–ã—ã¦ä¿å­˜ã•ã‚Œã¾ã™ã€‚"
@@ -9357,9 +11020,18 @@ msgstr "プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã‚ã‚Œã°èª°ã§ã‚‚ã‚¢
msgid "The project can be accessed without any authentication."
msgstr "プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ãªã—ã«èª°ã§ã‚‚アクセスã§ãã¾ã™ã€‚"
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "匿å化データコレクションã¯ç„¡åŠ¹ã§ã™ã€‚有効ã«ã—ãŸã¨ãã€GitLab ã¯ã€ã‚ãªãŸãŒè¨­å®šã—ãŸã‚ªãƒ–ジェクトストレージディレクトリã«ã‚¢ãƒƒãƒ—ロードã™ã‚‹ GitLab データベースã®åŒ¿å化ã—㟠CSV を生æˆã™ã‚‹ã‚¸ãƒ§ãƒ–ã‚’ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã§å®Ÿè¡Œã—ã¾ã™ã€‚"
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "ã“ã®ãƒ—ロジェクトã«ãƒªãƒã‚¸ãƒˆãƒªã¯ã‚ã‚Šã¾ã›ã‚“。"
@@ -9378,6 +11050,21 @@ msgstr "レビューステージã¨ã¯ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’作æˆã—ã¦
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "ロードマップã«ã¯ã€ã‚¿ã‚¤ãƒ ãƒ©ã‚¤ãƒ³ã«æ²¿ã£ãŸã‚¨ãƒ”ックã®é€²æ—ãŒè¡¨ç¤ºã•ã‚Œã¾ã™"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "ステージングステージã§ã¯ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã‹ã‚‰ã‚³ãƒ¼ãƒ‰ãŒãƒ—ロダクション環境ã«ãƒ‡ãƒ—ロイã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒ‡ãƒ¼ã‚¿ã¯æœ€åˆã«ãƒ—ロダクションã«ãƒ‡ãƒ—ロイã—ãŸã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
@@ -9393,9 +11080,18 @@ msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã«åŽé›†ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿æ¯Žã®æ™‚é–“"
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "ユーザーマップã¯ã€ã‚ãªãŸã®ãƒ—ロジェクトã«å‚加ã—㟠Google コードã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼åã‚’ GitLab ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã™ã‚‹ã¨ãã«ãƒžãƒƒãƒ”ングã™ã‚‹ JSON ファイルã§ã™ã€‚ã“れを変更ã™ã‚‹ã«ã¯ã€ <code>:</code> ã®å³å´ã®å€¤ã‚’変更ã—ã¾ã™ã€‚å·¦å´ã®äºŒé‡å¼•ç”¨ç¬¦ã€ãã®ä»–ã®å¥èª­ç‚¹ã€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¾ãŸã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼åãŒä¿å­˜ã•ã‚Œã¾ã™ã€‚"
@@ -9406,16 +11102,16 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "得られãŸä¸€é€£ã®ãƒ‡ãƒ¼ã‚¿ã‚’å°ã•ã„é †ã«ä¸¦ã¹ãŸã¨ãã«ä¸­å¤®ã«ä½ç½®ã™ã‚‹å€¤ã€‚例ãˆã°ã€3, 5, 9 ã®ä¸­å¤®å€¤ã¯ 5。3, 5, 7, 8 ã®ä¸­å¤®å€¤ã¯ (5+7)/2 = 6。"
msgid "There are no approvers"
-msgstr ""
+msgstr "承èªè€…ã¯ã„ã¾ã›ã‚“"
msgid "There are no archived projects yet"
msgstr ""
msgid "There are no closed issues"
-msgstr ""
+msgstr "クローズã•ã‚ŒãŸèª²é¡Œã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "There are no closed merge requests"
-msgstr ""
+msgstr "クローズã•ã‚ŒãŸãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
msgstr ""
@@ -9427,10 +11123,10 @@ msgid "There are no labels yet"
msgstr "ã¾ã ãƒ©ãƒ™ãƒ«ãŒã‚ã‚Šã¾ã›ã‚“"
msgid "There are no open issues"
-msgstr ""
+msgstr "オープン中ã®èª²é¡Œã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "There are no open merge requests"
-msgstr ""
+msgstr "オープン中ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "There are no packages yet"
msgstr ""
@@ -9450,9 +11146,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "ユーザーã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティカレンダーã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "通知設定をä¿å­˜ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -9468,6 +11176,9 @@ msgstr "ラベルã®è³¼èª­ã™ã‚‹ã¨ãã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "There was an error when unsubscribing from this label."
msgstr "ラベルã®è³¼èª­è§£é™¤ã™ã‚‹ã¨ãã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9486,6 +11197,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "ã“ã® GitLab インスタンスã¯ã¾ã å…±æœ‰ Runner ã‚’æä¾›ã—ã¦ã„ã¾ã›ã‚“。インスタンス管ç†è€…ã¯ç®¡ç†è€…エリアã§å…±æœ‰ Runner を登録ã§ãã¾ã™ã€‚"
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "ã“ã®ã‚¢ãƒ—リケーション㯠%{link_to_owner} ã«ã‚ˆã£ã¦ä½œæˆã•ã‚Œã¾ã—ãŸã€‚"
@@ -9498,6 +11212,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "編集を開始ã—ã¦ã‹ã‚‰ãƒ–ランãƒãŒæ›´æ–°ã•ã‚Œã¦ã„ã¾ã™ã€‚æ–°ã—ã„ブランãƒã‚’作æˆã—ã¾ã™ã‹ï¼Ÿ"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9508,7 +11237,7 @@ msgid "This date is before the start date, so this epic won't appear in the road
msgstr ""
msgid "This diff is collapsed."
-msgstr "ã“ã®å·®åˆ†ã¯å´©å£Šã—ã¦ã„ã¾ã™ã€‚"
+msgstr "ã“ã®å·®åˆ†ã¯æŠ˜ã‚ŠãŸãŸã¾ã‚Œã¦ã„ã¾ã™ã€‚"
msgid "This diff was suppressed by a .gitattributes entry."
msgstr ""
@@ -9519,6 +11248,9 @@ msgstr "ディレクトリ"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—"
@@ -9534,6 +11266,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr "ã“ã®ãƒ—ロジェクトã«å¯¾ã™ã‚‹ã€ä½œæˆè€…ã®æœ€åˆã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã§ã™ã€‚"
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "ã“ã®èª²é¡Œã¯éžå…¬é–‹è¨­å®šã§ã™"
@@ -9582,6 +11317,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "ã“ã®ã‚¸ãƒ§ãƒ–ã¯ä¿ç•™ä¸­ã§ Runner ãŒå‹•ä½œã™ã‚‹ã®ã‚’å¾…ã£ã¦ã„ã¾ã™ã€‚"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9603,19 +11344,25 @@ msgstr "空リãƒã‚¸ãƒˆãƒªã‚’作æˆã¾ãŸã¯æ—¢å­˜ãƒªãƒã‚¸ãƒˆãƒªã‚’インãƒãƒ¼
msgid "This merge request is locked."
msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã™ã€‚"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "ã‚ãªãŸã¯ç¾åœ¨ã®ãƒ–ランãƒã«å¯¾ã—ã¦æ›¸ãè¾¼ã¿æ¨©é™ãŒãªã„ã®ã§ã€ã“ã®ã‚ªãƒ—ションã¯ç„¡åŠ¹ã§ã™"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "ã¾ã ã‚¹ãƒ†ãƒ¼ã‚¸ãƒ³ã‚°ã•ã‚Œã¦ã„ãªã„変更ãŒã‚ã‚‹ã¨ã€ã“ã®ã‚ªãƒ—ションã¯ç„¡åŠ¹ã«ãªã‚Šã¾ã™"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "複数ã®ãƒ—ロジェクト間ã§èª­ã¿è¾¼ã¿ãŒè¨±å¯ã•ã‚Œã¦ã„ãªã„ãŸã‚ã€ã“ã®ãƒšãƒ¼ã‚¸ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。"
msgid "This page will be removed in a future release."
msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã¯ã€å°†æ¥ã®ãƒªãƒªãƒ¼ã‚¹ã§å‰Šé™¤ã•ã‚Œã‚‹äºˆå®šã§ã™ã€‚"
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9639,6 +11386,9 @@ msgstr ""
msgid "This repository"
msgstr "リãƒã‚¸ãƒˆãƒª"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9648,12 +11398,12 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
-msgstr "差分ãŒå¤§ãã™ãŽã‚‹ãŸã‚ã€ã‚½ãƒ¼ã‚¹å·®åˆ†ã‚’表示ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¯ ID ãŒã‚ã‚Šã¾ã›ã‚“"
@@ -9666,6 +11416,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "ã“ã“ã«ãƒªã‚¹ãƒˆã‚¢ãƒƒãƒ—ã•ã‚Œã¦ã„ã‚‹ã“れらã®ãƒ¡ãƒ¼ãƒ«ã¯è‡ªå‹•çš„ã«èª²é¡Œã«ãªã‚Šã¾ã™ã€‚(メールã®ã‚„ã‚Šå–ã‚Šã¯èª²é¡Œã®ã‚³ãƒ¡ãƒ³ãƒˆã«ãªã‚Šã¾ã™ï¼‰"
@@ -9732,9 +11485,6 @@ msgstr "%sヶ月å‰"
msgid "Timeago|%s months remaining"
msgstr "残り %sヶ月"
-msgid "Timeago|%s seconds ago"
-msgstr "%s秒å‰"
-
msgid "Timeago|%s seconds remaining"
msgstr "残り%s秒"
@@ -9962,6 +11712,9 @@ msgstr "今日"
msgid "Todo"
msgstr "Todo"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -9975,7 +11728,7 @@ msgid "Toggle commit description"
msgstr ""
msgid "Toggle commit list"
-msgstr ""
+msgstr "コミットリストを切り替ãˆ"
msgid "Toggle discussion"
msgstr "検討ã®è¡¨ç¤ºãƒ»éžè¡¨ç¤ºã‚’切り替ãˆã‚‹"
@@ -10001,6 +11754,12 @@ msgstr "明日"
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "ç·è²¢çŒ®åº¦"
@@ -10025,6 +11784,9 @@ msgstr "プロジェクトãŠã‚ˆã³ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³å…¨ä½“ã§ã€ãƒ†ãƒ¼ãƒžã‚’
msgid "Track time with quick actions"
msgstr "クイックアクションã§ä½œæ¥­æ™‚間を記録"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10037,6 +11799,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "ã“ã®æ‰‹å‹•ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’トリガーã™ã‚‹"
@@ -10046,6 +11811,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "トリガーã¯ã€ç‰¹å®šã®ãƒ–ランãƒã¾ãŸã¯ã‚¿ã‚°ã‚’ API 呼ã³å‡ºã—ã§å¼·åˆ¶çš„ã«å†æ§‹ç¯‰ã•ã›ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ã“れらã®ãƒˆãƒ¼ã‚¯ãƒ³ã¯ã€ãƒ—ロジェクトã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚„プロジェクトã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©ãªã©ã€é–¢é€£ã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’å½è£…ã—ã¾ã™ã€‚"
@@ -10056,7 +11833,7 @@ msgid "Try again"
msgstr "å†è©¦è¡Œ"
msgid "Try again?"
-msgstr ""
+msgstr "å†è©¦è¡Œã—ã¾ã™ã‹ï¼Ÿ"
msgid "Try all GitLab has to offer for 30 days."
msgstr ""
@@ -10070,6 +11847,9 @@ msgstr "サービスデスクを有効ã«ã™ã‚‹"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10077,20 +11857,32 @@ msgid "Type"
msgstr "タイプ"
msgid "URL"
+msgstr "URL"
+
+msgid "Unable to connect to server: %{error}"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr "差分を読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“。%{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "\"%{reason}\"ã®ãŸã‚ SAML ã§ã‚°ãƒ«ãƒ¼ãƒ—ã«ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã§ãã¾ã›ã‚“"
msgid "Unable to update this epic at this time."
msgstr ""
-msgid "Unblock"
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
msgstr ""
+msgid "Unblock"
+msgstr "ブロック解除"
+
msgid "Undo"
msgstr ""
@@ -10100,6 +11892,9 @@ msgstr ""
msgid "Unknown"
msgstr "ä¸æ˜Ž"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "アンロック"
@@ -10160,21 +11955,39 @@ msgstr ""
msgid "Update"
msgstr "アップデート"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "今ã™ãæ›´æ–°"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "更新中"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10190,6 +12003,9 @@ msgstr "課題ã®ã‚¦ã‚§ã‚¤ãƒˆã‚’有効ã«ã™ã‚‹ãŸã‚ã«ã€ãƒ—ランをアップ
msgid "Upgrade your plan to improve Issue boards."
msgstr "課題ボードã®æ©Ÿèƒ½ã‚’強化ã™ã‚‹ãŸã‚ã«ã€ãƒ—ランをアップグレード"
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "<code>GoogleCodeProjectHosting.json</code> をアップロードã—ã¾ã™:"
@@ -10214,18 +12030,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "クリックã—ã¦ã‚¢ãƒƒãƒ—ロード"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "ã„ã„ã­"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr "使用状æ³ã®çµ±è¨ˆ"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "ローカルテスト㫠<code>%{native_redirect_uri}</code> を使用"
@@ -10256,18 +12084,54 @@ msgstr "メンãƒãƒ¼ãŒ GitLab ã®ã‚°ãƒ«ãƒ¼ãƒ—ã«ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã™ã‚‹ãŸã‚ã«ä
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "ユーザー設定"
msgid "User and IP Rate Limits"
msgstr "ユーザーã¨IPレートã®åˆ¶é™"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "ユーザーマップ"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10296,7 +12160,7 @@ msgid "UserProfile|Most Recent Activity"
msgstr ""
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "スニペットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
msgid "UserProfile|Overview"
msgstr "概è¦"
@@ -10346,18 +12210,39 @@ msgstr ""
msgid "Users"
msgstr "ユーザー"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "å„種コンテナレジストリã®è¨­å®šã€‚"
@@ -10382,9 +12267,6 @@ msgstr "検証済ã¿"
msgid "Version"
msgstr "ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10394,7 +12276,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10406,12 +12288,15 @@ msgstr "ファイルを表示 @ "
msgid "View group labels"
msgstr "グループラベルを表示"
-msgid "View issue"
-msgstr "課題を表示"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "GitLab ã§è¡¨ç¤º"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "ジョブを表示"
@@ -10445,6 +12330,9 @@ msgstr "å¯è¦–性レベル"
msgid "Visibility level:"
msgstr "å¯è¦–性レベル:"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10481,12 +12369,18 @@ msgstr "ファイル"
msgid "Vulnerability|Identifiers"
msgstr "識別å­"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "インスタンス"
msgid "Vulnerability|Links"
msgstr "リンク"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "プロジェクト"
@@ -10535,6 +12429,12 @@ msgstr ""
msgid "Web terminal"
msgstr "ウェブターミナル"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10553,9 +12453,16 @@ msgstr "Runner ãŒãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã‚‹å ´åˆã€ä»–ã®ãƒ—ロジェクトã«å‰
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "有効ã«ã—ãŸå ´åˆã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¦ç´„ã«åŒæ„ã•ã‚Œã‚‹ã¾ã§ GitLab を使用ã§ãã¾ã›ã‚“。"
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "URL を空白ã®ã¾ã¾ã«ã™ã‚‹ã¨ã€ãƒ—ロジェクト間ã®æ©Ÿèƒ½ã‚’無効ã«ã—ãŸã‚Šã€å¤–部èªè¨¼ãƒã‚§ãƒƒã‚¯ã‚’実行ã—ãŸã‚Šã›ãšã«åˆ†é¡žãƒ©ãƒ™ãƒ«ã‚’指定ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
msgstr ""
@@ -10568,6 +12475,9 @@ msgstr ""
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Wiki をクローン"
@@ -10679,6 +12589,9 @@ msgstr "ページを作æˆ"
msgid "Wiki|Create page"
msgstr "ページを作æˆ"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "ページを編集"
@@ -10697,6 +12610,9 @@ msgstr "ページã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
msgid "Wiki|Pages"
msgstr "Pages"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Wikiページ"
@@ -10704,15 +12620,27 @@ msgid "Will deploy to"
msgstr ""
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
-msgstr ""
+msgstr "貢献分æžã‚’使用ã™ã‚‹ã¨ã€ã‚ãªãŸã®çµ„ç¹”ã¨ãã®ãƒ¡ãƒ³ãƒã®èª²é¡Œã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŠã‚ˆã³ãƒ—ッシュイベントã®æ¦‚è¦ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚"
msgid "Withdraw Access Request"
msgstr "アクセスリクエストをå–り消ã™"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
+msgstr "コメントを書ãã‹ã€ã“ã“ã«ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ドラッグ…"
+
+msgid "Write access allowed"
msgstr ""
msgid "Write milestone description..."
+msgstr "マイルストーンã®èª¬æ˜Žã‚’書ã..."
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
@@ -10728,7 +12656,7 @@ msgid "Yesterday"
msgstr "昨日"
msgid "You"
-msgstr ""
+msgstr "ã‚ãªãŸ"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr ""
@@ -10745,6 +12673,9 @@ msgstr "å…ƒã®ãƒ—ロジェクト (%{forked_from_project}) ã¨ã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ã
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "%{project_full_name} を別ã®ã‚ªãƒ¼ãƒŠãƒ¼ã«å§”è­²ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "読ã¿å–り専用 GitLab インスタンスをå‚照中ã§ã™ã€‚"
@@ -10760,6 +12691,15 @@ msgstr "コマンドラインã‹ã‚‰ãƒ—ロジェクトを作æˆã™ã‚‹ã“ã¨ã‚‚ã§
msgid "You can also star a label to make it a priority label."
msgstr "ラベルã«ã‚¹ã‚¿ãƒ¼ã‚’付ã‘ã¦å„ªå…ˆãƒ©ãƒ™ãƒ«ã«ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚"
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "ã“れらã®ã‚°ãƒ«ãƒ¼ãƒ—ã¸ã®å‚加をリクエストã™ã‚‹ã“ã¨ã§ã€ç°¡å˜ã«è²¢çŒ®ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
@@ -10769,6 +12709,12 @@ msgstr "Kubernetes クラスター㫠Runner ã‚’ç°¡å˜ã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã§ã
msgid "You can move around the graph by using the arrow keys."
msgstr "矢å°ã‚­ãƒ¼ã‚’使用ã—ã¦ã‚°ãƒ©ãƒ•ã‚’移動ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "ファイルを追加ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“"
@@ -10778,6 +12724,9 @@ msgstr "ファイルを編集ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10787,17 +12736,35 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "読ã¿å–り専用ã®ã‚»ã‚«ãƒ³ãƒ€ãƒª GitLab Geo インスタンスã«æ›¸ã込むã“ã¨ã¯ã§ãã¾ã›ã‚“。代ã‚ã‚Šã« %{link_to_primary_node} を使用ã—ã¦ãã ã•ã„。"
msgid "You cannot write to this read-only GitLab instance."
msgstr "ã“ã®èª­ã¿å–り専用 GitLab インスタンスã«ã¯æ›¸ã込むã“ã¨ãŒã§ãã¾ã›ã‚“。"
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
-msgstr ""
+msgstr "Web ターミナルを実行ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。プロジェクト管ç†è€…ã«é€£çµ¡ã—ã¦ãã ã•ã„。"
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr "LDAP グループã®åŒæœŸã®è¨­å®šã‚’上書ãã™ã‚‹ãŸã‚ã®é©åˆ‡ãªæ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。"
@@ -10811,12 +12778,27 @@ msgstr "承èªã•ã‚ŒãŸã‚¢ãƒ—リケーションãŒã‚ã‚Šã¾ã›ã‚“"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "権é™ãŒã‚ã‚Šã¾ã›ã‚“"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "プロジェクト数ã®ä¸Šé™ã«é”ã—ã¦ã„ã¾ã™"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10829,7 +12811,7 @@ msgstr "ロックを強制的ã«å‰Šé™¤ã™ã‚‹ã«ã¯ã€Maintainer ã®ã‚¢ã‚¯ã‚»ã‚¹æ
msgid "You need a different license to enable FileLocks feature"
msgstr "ファイルロック機能を有効ã«ã™ã‚‹ã«ã¯åˆ¥ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ãŒå¿…è¦ã§ã™"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10841,12 +12823,24 @@ msgstr "権é™ãŒå¿…è¦ã§ã™"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "通知メールをé€ä¿¡ã—ã¾ã›ã‚“"
@@ -10874,6 +12868,15 @@ msgstr "SSH éµã‚’プロフィールã«è¿½åŠ ã—ãªã„é™ã‚Šã€SSH 経由ã§ãƒ—ã
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "有効ãªæ¯”較を行ã†ãŸã‚ã«ã¯ã€ç•°ãªã‚‹ãƒ–ランãƒåを使用ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10907,6 +12910,15 @@ msgstr "ã‚ãªãŸã®Todo"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10925,9 +12937,15 @@ msgstr "ã™ã§ã«å¤‰æ›´ã‚’コミット済ã¿ã§ã™ã€‚コミット %{commitId} %{
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã¯ä¸€èˆ¬ã«ã¯è¡¨ç¤ºã•ã‚Œã¾ã›ã‚“。"
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10943,15 +12961,30 @@ msgstr ""
msgid "Your name"
msgstr "åå‰"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "ã‚ãªãŸã®ãƒ—ロジェクト"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "削除ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "å‰"
@@ -10961,12 +12994,15 @@ msgstr ""
msgid "among other things"
msgstr "ãã®ä»–ã®ã‚‚ã®"
-msgid "assign yourself"
-msgstr "自分ã«å‰²ã‚Šå½“ã¦"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "ブランãƒå"
@@ -10991,14 +13027,34 @@ msgstr "%{namespace} 㯠%{vulnerability} ã®å½±éŸ¿ã‚’å—ã‘ã¾ã™ã€‚"
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "他 %{remainingPackagesCount} 件"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11028,6 +13084,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11037,6 +13096,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "クラス"
@@ -11046,12 +13114,24 @@ msgstr "コードã®å“質"
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "コンテナスキャンã¯ã€Docker イメージã«å­˜åœ¨ã™ã‚‹æ—¢çŸ¥ã®è„†å¼±æ€§ã‚’検出ã—ã¾ã™ã€‚"
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11094,9 +13174,18 @@ msgstr "修正済ã¿:"
msgid "ciReport|Identifiers"
msgstr "識別å­"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "インスタンス"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "セキュリティレãƒãƒ¼ãƒˆ (Alpha) ã¨ã®ç›¸äº’作用ã®è©³ç´°ã‚’ã”覧ãã ã•ã„。"
@@ -11138,9 +13227,6 @@ msgstr "パーフォーマンスメトリクスã«å¤‰æ›´ã¯ã‚ã‚Šã¾ã›ã‚“"
msgid "ciReport|Performance metrics"
msgstr "パフォーマンスメトリクス"
-msgid "ciReport|Revert dismissal"
-msgstr "無視ã®å–り消ã—"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11162,6 +13248,9 @@ msgstr "é™çš„アプリケーションセキュリティテスト(SAST)ã¯ã€
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "課題を作æˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "脆弱性を無視ã™ã‚‹éš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
@@ -11180,9 +13269,18 @@ msgstr "ä¾å­˜é–¢ä¿‚スキャンレãƒãƒ¼ãƒˆã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™º
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "無視ã®å–り消ã—中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "%{name} ã‚’ %{version} ã‹ã‚‰ %{fixed} ã¸ã‚¢ãƒƒãƒ—グレードã—ã¦ãã ã•ã„。"
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11193,12 +13291,33 @@ msgstr "レãƒãƒ¼ãƒˆå…¨ä½“を見る"
msgid "ciReport|on pipeline"
msgstr "パイプライン"
-msgid "command line instructions"
-msgstr "コマンドライン"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr "ã‚ãªãŸã¯å…¬é–‹è¨­å®šã«å¤‰æ›´ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ã“ã‚Œã¯<strong>ã™ã¹ã¦ã®äºº</strong> ãŒé–²è¦§å¯èƒ½ã«ãªã‚Šã€èª²é¡Œã«å¯¾ã—ã¦ã‚³ãƒ¡ãƒ³ãƒˆã‚’残ã™ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹ã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚"
@@ -11224,6 +13343,9 @@ msgstr ""
msgid "deploy token"
msgstr "デプロイトークン"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "無効"
@@ -11245,14 +13367,29 @@ msgid "epic"
msgstr ""
msgid "error"
-msgstr ""
+msgstr "エラー"
msgid "error code:"
-msgstr ""
+msgstr "エラー コード:"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} コマンドã§è¦‹ç©æ™‚é–“ã‚’æ›´æ–°ã§ãã¾ã™ã€‚"
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "ã“ã®ãƒ—ロジェクトã§ã¯"
@@ -11260,17 +13397,11 @@ msgid "from"
msgstr ""
msgid "group"
-msgstr ""
-
-msgid "help"
-msgstr "ヘルプ"
+msgstr "グループ"
msgid "here"
msgstr "ã“ã“"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
@@ -11332,6 +13463,9 @@ msgstr "最新ã®ãƒ‡ãƒ—ロイ"
msgid "latest version"
msgstr "最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "ライセンス管ç†"
@@ -11351,6 +13485,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11376,10 +13513,10 @@ msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr "ターゲットブランãƒã«ãƒžãƒ¼ã‚¸ã§ãるメンãƒãƒ¼ã‹ã‚‰ã®ã‚³ãƒŸãƒƒãƒˆã‚’許å¯ã™ã‚‹"
msgid "mrWidget|An error occurred while removing your approval."
-msgstr ""
+msgstr "承èªã‚’削除中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
-msgstr ""
+msgstr "マージリクエストã®æ‰¿èªæƒ…報をå–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "mrWidget|An error occurred while submitting your approval."
msgstr "承èªã‚’é€ä¿¡ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
@@ -11387,6 +13524,9 @@ msgstr "承èªã‚’é€ä¿¡ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
msgid "mrWidget|Approve"
msgstr "承èª"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "承èªè€…"
@@ -11459,16 +13599,19 @@ msgstr "ローカルã§ãƒžãƒ¼ã‚¸"
msgid "mrWidget|Merge request approved"
msgstr "マージリクエストãŒæ‰¿èªã•ã‚Œã¾ã—ãŸ"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr "マージ作業者"
-msgid "mrWidget|No Approval required"
-msgstr "承èªã¯ä¸è¦"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr "承èªã¯ä¸è¦ã§ã™ãŒã€æ‰¿èªã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™"
msgid "mrWidget|Open in Web IDE"
@@ -11518,6 +13661,9 @@ msgstr "リãƒãƒ¼ãƒˆ"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "æ–°ã—ã„マージリクエストã§ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’リãƒãƒ¼ãƒˆã™ã‚‹"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "設定者"
@@ -11537,19 +13683,19 @@ msgid "mrWidget|The source branch HEAD has recently changed. Please reload the p
msgstr "ソースブランãƒã® HEAD ãŒæœ€è¿‘変更ã•ã‚Œã¾ã—ãŸã€‚ページをå†èª­ã¿è¾¼ã¿ã—ã¦ãƒžãƒ¼ã‚¸å‰ã«å¤‰æ›´ã‚’確èªã—ã¦ãã ã•ã„"
msgid "mrWidget|The source branch has been deleted"
-msgstr ""
+msgstr "ã“ã®ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¯å‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
msgstr ""
msgid "mrWidget|The source branch is being deleted"
-msgstr ""
+msgstr "ã“ã®ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¯å‰Šé™¤ã•ã‚Œã‚ˆã†ã¨ã—ã¦ã„ã¾ã™"
msgid "mrWidget|The source branch will be deleted"
-msgstr ""
+msgstr "ã“ã®ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¯å‰Šé™¤ã•ã‚Œã¾ã™"
msgid "mrWidget|The source branch will not be deleted"
-msgstr ""
+msgstr "ã“ã®ã‚½ãƒ¼ã‚¹ãƒ–ランãƒã¯å‰Šé™¤ã•ã‚Œã¾ã›ã‚“"
msgid "mrWidget|There are merge conflicts"
msgstr "マージã®ç«¶åˆãŒã‚ã‚Šã¾ã™"
@@ -11573,7 +13719,7 @@ msgid "mrWidget|You are not allowed to edit this project directly. Please fork t
msgstr ""
msgid "mrWidget|You can delete the source branch now"
-msgstr ""
+msgstr "ソースブランãƒã‚’削除ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸ"
msgid "mrWidget|You can merge this merge request manually using the"
msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’手動ã§ãƒžãƒ¼ã‚¸ã§ãã¾ã™"
@@ -11628,12 +13774,19 @@ msgstr "パスワード"
msgid "personal access token"
msgstr "個人ã®ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr "秘密éµãŒè¨¼æ˜Žæ›¸ã¨ä¸€è‡´ã—ã¾ã›ã‚“。"
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "プロジェクト"
@@ -11663,7 +13816,34 @@ msgid "reply"
msgid_plural "replies"
msgstr[0] "返信"
+msgid "reset it."
+msgstr ""
+
msgid "score"
+msgstr "スコア"
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
msgstr ""
msgid "should be higher than %{access} inherited membership from group %{group_name}"
@@ -11673,13 +13853,13 @@ msgid "show less"
msgstr ""
msgid "sign in"
-msgstr ""
+msgstr "サインイン"
msgid "source"
msgstr "ソース"
msgid "source diff"
-msgstr ""
+msgstr "ソース差分"
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr "%{slash_command} ã¯ç´¯è¨ˆçµŒéŽæ™‚é–“ã‚’æ›´æ–°ã—ã¾ã™"
@@ -11690,6 +13870,9 @@ msgstr "開始"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/ka_GE/gitlab.po b/locale/ka_GE/gitlab.po
new file mode 100644
index 00000000000..392e35c5b88
--- /dev/null
+++ b/locale/ka_GE/gitlab.po
@@ -0,0 +1,13998 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab-ee\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: gitlab <community@gitlab.com>\n"
+"Language-Team: Georgian\n"
+"Language: ka_GE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.com\n"
+"X-Crowdin-Project: gitlab-ee\n"
+"X-Crowdin-Language: ka\n"
+"X-Crowdin-File: /master/locale/gitlab.pot\n"
+"PO-Revision-Date: 2019-04-12 12:39\n"
+
+msgid " Please sign in."
+msgstr ""
+
+msgid " Status"
+msgstr ""
+
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
+msgid " and"
+msgstr ""
+
+msgid " degraded on %d point"
+msgid_plural " degraded on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " improved on %d point"
+msgid_plural " improved on %d points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid " or "
+msgstr ""
+
+msgid " or <#epic id>"
+msgstr ""
+
+msgid " or <#issue id>"
+msgstr ""
+
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commit behind"
+msgid_plural "%d commits behind"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d commits"
+msgstr ""
+
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d failed test result"
+msgid_plural "%d failed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d fixed test result"
+msgid_plural "%d fixed test results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d issue selected"
+msgid_plural "%d issues selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d layer"
+msgid_plural "%d layers"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d staged change"
+msgid_plural "%d staged changes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%d unstaged change"
+msgid_plural "%d unstaged changes"
+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 "%{actionText} & %{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{authorsName}'s discussion"
+msgstr ""
+
+msgid "%{commit_author_link} authored %{commit_timeago}"
+msgstr ""
+
+msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
+msgstr ""
+
+msgid "%{count} %{alerts}"
+msgstr ""
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} more"
+msgstr ""
+
+msgid "%{count} more assignees"
+msgstr ""
+
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
+msgid "%{count} participant"
+msgid_plural "%{count} participants"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} pending comment"
+msgid_plural "%{count} pending comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{filePath} deleted"
+msgstr ""
+
+msgid "%{firstLabel} +%{labelCount} more"
+msgstr ""
+
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr ""
+
+msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
+msgstr ""
+
+msgid "%{issuableType} will be removed! Are you sure?"
+msgstr ""
+
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
+msgid "%{loadingIcon} Started"
+msgstr ""
+
+msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr ""
+
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
+msgid "%{name}'s avatar"
+msgstr ""
+
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
+msgid "%{openOrClose} %{noteable}"
+msgstr ""
+
+msgid "%{percent}%% complete"
+msgstr ""
+
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
+msgid "%{state} epics"
+msgstr ""
+
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{text} is available"
+msgstr ""
+
+msgid "%{title} changes"
+msgstr ""
+
+msgid "%{unstaged} unstaged and %{staged} staged changes"
+msgstr ""
+
+msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
+msgstr ""
+
+msgid "%{user_name} profile page"
+msgstr ""
+
+msgid "(No changes)"
+msgstr ""
+
+msgid "(external source)"
+msgstr ""
+
+msgid "+ %{count} more"
+msgstr ""
+
+msgid "+ %{moreCount} more"
+msgstr ""
+
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
+msgid ", or "
+msgstr ""
+
+msgid "- Runner is active and can process any new jobs"
+msgstr ""
+
+msgid "- Runner is paused and will not receive any new jobs"
+msgstr ""
+
+msgid "- show less"
+msgstr ""
+
+msgid "1 %{type} addition"
+msgid_plural "%{count} %{type} additions"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 %{type} modification"
+msgid_plural "%{count} %{type} modifications"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed issue"
+msgid_plural "%d closed issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 closed merge request"
+msgid_plural "%d closed merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 day"
+msgstr ""
+
+msgid "1 group"
+msgid_plural "%d groups"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 merged merge request"
+msgid_plural "%d merged merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open issue"
+msgid_plural "%d open issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 open merge request"
+msgid_plural "%d open merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 role"
+msgid_plural "%d roles"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 user"
+msgid_plural "%d users"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "1st contribution!"
+msgstr ""
+
+msgid "2FA"
+msgstr ""
+
+msgid "2FA enabled"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr ""
+
+msgid "403|You don't have the permission to access this page."
+msgstr ""
+
+msgid "404|Make sure the address is correct and the page hasn't moved."
+msgstr ""
+
+msgid "404|Page Not Found"
+msgstr ""
+
+msgid "404|Please contact your GitLab administrator if you think this is a mistake."
+msgstr ""
+
+msgid "8 hours"
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy."
+msgstr ""
+
+msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
+msgstr ""
+
+msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
+msgstr ""
+
+msgid "<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
+msgstr ""
+
+msgid "<strong>%{group_name}</strong> group members"
+msgstr ""
+
+msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
+msgstr ""
+
+msgid "<strong>Deletes</strong> source branch"
+msgstr ""
+
+msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
+msgstr ""
+
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr ""
+
+msgid "A deleted user"
+msgstr ""
+
+msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgstr ""
+
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
+msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr ""
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
+msgstr ""
+
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgstr ""
+
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
+msgid "API Help"
+msgstr ""
+
+msgid "About GitLab"
+msgstr ""
+
+msgid "About GitLab CE"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr ""
+
+msgid "About this feature"
+msgstr ""
+
+msgid "Abuse Reports"
+msgstr ""
+
+msgid "Abuse reports"
+msgstr ""
+
+msgid "Accept invitation"
+msgstr ""
+
+msgid "Accept terms"
+msgstr ""
+
+msgid "Accepted MR"
+msgstr ""
+
+msgid "Access Tokens"
+msgstr ""
+
+msgid "Access denied for your LDAP account."
+msgstr ""
+
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr ""
+
+msgid "Access expiration date"
+msgstr ""
+
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
+msgid "Access to '%{classification_label}' not allowed"
+msgstr ""
+
+msgid "Account"
+msgstr ""
+
+msgid "Account and limit"
+msgstr ""
+
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Active Sessions"
+msgstr ""
+
+msgid "Activity"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add CHANGELOG"
+msgstr ""
+
+msgid "Add CONTRIBUTING"
+msgstr ""
+
+msgid "Add Group Webhooks and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Add Jaeger URL"
+msgstr ""
+
+msgid "Add Kubernetes cluster"
+msgstr ""
+
+msgid "Add README"
+msgstr ""
+
+msgid "Add a bullet list"
+msgstr ""
+
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
+msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
+msgstr ""
+
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
+msgid "Add a table"
+msgstr ""
+
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
+msgstr ""
+
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add comment now"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
+msgid "Add image comment"
+msgstr ""
+
+msgid "Add italic text"
+msgstr ""
+
+msgid "Add license"
+msgstr ""
+
+msgid "Add list"
+msgstr ""
+
+msgid "Add new application"
+msgstr ""
+
+msgid "Add new directory"
+msgstr ""
+
+msgid "Add reaction"
+msgstr ""
+
+msgid "Add to project"
+msgstr ""
+
+msgid "Add to review"
+msgstr ""
+
+msgid "Add todo"
+msgstr ""
+
+msgid "Add user(s) to the group:"
+msgstr ""
+
+msgid "Add users or groups who are allowed to approve every merge request"
+msgstr ""
+
+msgid "Add users to group"
+msgstr ""
+
+msgid "Added at"
+msgstr ""
+
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr ""
+
+msgid "Additional text"
+msgstr ""
+
+msgid "Aditional minutes"
+msgstr ""
+
+msgid "Admin Area"
+msgstr ""
+
+msgid "Admin Overview"
+msgstr ""
+
+msgid "Admin Section"
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
+msgstr ""
+
+msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "AdminProjects|Delete"
+msgstr ""
+
+msgid "AdminProjects|Delete Project %{projectName}?"
+msgstr ""
+
+msgid "AdminProjects|Delete project"
+msgstr ""
+
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
+msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
+msgstr ""
+
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
+msgid "AdminUsers|2FA Disabled"
+msgstr ""
+
+msgid "AdminUsers|2FA Enabled"
+msgstr ""
+
+msgid "AdminUsers|Active"
+msgstr ""
+
+msgid "AdminUsers|Admin"
+msgstr ""
+
+msgid "AdminUsers|Admins"
+msgstr ""
+
+msgid "AdminUsers|Block user"
+msgstr ""
+
+msgid "AdminUsers|Blocked"
+msgstr ""
+
+msgid "AdminUsers|Cannot unblock LDAP blocked users"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username} and contributions?"
+msgstr ""
+
+msgid "AdminUsers|Delete User %{username}?"
+msgstr ""
+
+msgid "AdminUsers|Delete user"
+msgstr ""
+
+msgid "AdminUsers|Delete user and contributions"
+msgstr ""
+
+msgid "AdminUsers|External"
+msgstr ""
+
+msgid "AdminUsers|It's you!"
+msgstr ""
+
+msgid "AdminUsers|New user"
+msgstr ""
+
+msgid "AdminUsers|No users found"
+msgstr ""
+
+msgid "AdminUsers|Search by name, email or username"
+msgstr ""
+
+msgid "AdminUsers|Search users"
+msgstr ""
+
+msgid "AdminUsers|Send email to users"
+msgstr ""
+
+msgid "AdminUsers|Sort by"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{projectName}"
+msgstr ""
+
+msgid "AdminUsers|To confirm, type %{username}"
+msgstr ""
+
+msgid "AdminUsers|User will be blocked"
+msgstr ""
+
+msgid "AdminUsers|Without projects"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
+msgstr ""
+
+msgid "Advanced search functionality"
+msgstr ""
+
+msgid "Advanced settings"
+msgstr ""
+
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
+msgid "Alert"
+msgid_plural "Alerts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Alerts"
+msgstr ""
+
+msgid "All"
+msgstr ""
+
+msgid "All Members"
+msgstr ""
+
+msgid "All changes are committed"
+msgstr ""
+
+msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
+msgstr ""
+
+msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr ""
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
+msgid "All users"
+msgstr ""
+
+msgid "All users must have a name."
+msgstr ""
+
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr ""
+
+msgid "Allow commits from members who can merge to the target branch."
+msgstr ""
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
+msgid "Allow public access to pipelines and job details, including output logs and artifacts"
+msgstr ""
+
+msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
+msgstr ""
+
+msgid "Allow requests to the local network from hooks and services."
+msgstr ""
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
+msgid "Allow users to request access"
+msgstr ""
+
+msgid "Allow users to request access if visibility is public or internal."
+msgstr ""
+
+msgid "Allowed to fail"
+msgstr ""
+
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
+msgid "Also called \"Issuer\" or \"Relying party trust identifier\""
+msgstr ""
+
+msgid "Also called \"Relying party service URL\" or \"Reply URL\""
+msgstr ""
+
+msgid "Alternate support URL for help page"
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
+msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "An application called %{link_to_client} is requesting access to your GitLab account."
+msgstr ""
+
+msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
+msgstr ""
+
+msgid "An error has occurred"
+msgstr ""
+
+msgid "An error occurred adding a draft to the discussion."
+msgstr ""
+
+msgid "An error occurred adding a new draft."
+msgstr ""
+
+msgid "An error occurred creating the new branch."
+msgstr ""
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
+msgid "An error occurred previewing the blob"
+msgstr ""
+
+msgid "An error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "An error occurred when updating the issue weight"
+msgstr ""
+
+msgid "An error occurred while adding approver"
+msgstr ""
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
+msgid "An error occurred while deleting the comment"
+msgstr ""
+
+msgid "An error occurred while detecting host keys"
+msgstr ""
+
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while dismissing the alert. Refresh the page and try again."
+msgstr ""
+
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
+msgid "An error occurred while fetching pending comments"
+msgstr ""
+
+msgid "An error occurred while fetching sidebar data"
+msgstr ""
+
+msgid "An error occurred while fetching stages."
+msgstr ""
+
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
+msgid "An error occurred while fetching the pipeline."
+msgstr ""
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
+msgid "An error occurred while getting projects"
+msgstr ""
+
+msgid "An error occurred while importing project: %{details}"
+msgstr ""
+
+msgid "An error occurred while initializing path locks"
+msgstr ""
+
+msgid "An error occurred while loading chart data"
+msgstr ""
+
+msgid "An error occurred while loading commit signatures"
+msgstr ""
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
+msgid "An error occurred while loading diff"
+msgstr ""
+
+msgid "An error occurred while loading filenames"
+msgstr ""
+
+msgid "An error occurred while loading the file"
+msgstr ""
+
+msgid "An error occurred while loading the subscription details."
+msgstr ""
+
+msgid "An error occurred while making the request."
+msgstr ""
+
+msgid "An error occurred while removing approver"
+msgstr ""
+
+msgid "An error occurred while removing epics."
+msgstr ""
+
+msgid "An error occurred while removing issues."
+msgstr ""
+
+msgid "An error occurred while rendering KaTeX"
+msgstr ""
+
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
+msgid "An error occurred while retrieving calendar activity"
+msgstr ""
+
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
+msgid "An error occurred while saving LDAP override status. Please try again."
+msgstr ""
+
+msgid "An error occurred while saving assignees"
+msgstr ""
+
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
+msgid "An error occurred while subscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while unsubscribing to notifications."
+msgstr ""
+
+msgid "An error occurred while updating approvers"
+msgstr ""
+
+msgid "An error occurred while updating the comment"
+msgstr ""
+
+msgid "An error occurred while validating username"
+msgstr ""
+
+msgid "An error occurred whilst committing your changes."
+msgstr ""
+
+msgid "An error occurred whilst fetching the job trace."
+msgstr ""
+
+msgid "An error occurred whilst fetching the latest pipeline."
+msgstr ""
+
+msgid "An error occurred whilst loading all the files."
+msgstr ""
+
+msgid "An error occurred whilst loading the file content."
+msgstr ""
+
+msgid "An error occurred whilst loading the file."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request changes."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request version data."
+msgstr ""
+
+msgid "An error occurred whilst loading the merge request."
+msgstr ""
+
+msgid "An error occurred whilst loading the pipelines jobs."
+msgstr ""
+
+msgid "An error occurred. Please try again."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project environment."
+msgstr ""
+
+msgid "An unexpected error occurred while checking the project runners."
+msgstr ""
+
+msgid "An unexpected error occurred while communicating with the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while starting the Web Terminal."
+msgstr ""
+
+msgid "An unexpected error occurred while stopping the Web Terminal."
+msgstr ""
+
+msgid "Analytics"
+msgstr ""
+
+msgid "Ancestors"
+msgstr ""
+
+msgid "Anonymous"
+msgstr ""
+
+msgid "Anti-spam verification"
+msgstr ""
+
+msgid "Any"
+msgstr ""
+
+msgid "Any Label"
+msgstr ""
+
+msgid "Any encrypted tokens"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application ID"
+msgstr ""
+
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
+msgid "Application: %{name}"
+msgstr ""
+
+msgid "Applications"
+msgstr ""
+
+msgid "Applied"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
+msgid "Approvals"
+msgstr ""
+
+msgid "Approvals required"
+msgstr ""
+
+msgid "Approvers"
+msgstr ""
+
+msgid "Apr"
+msgstr ""
+
+msgid "April"
+msgstr ""
+
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr ""
+
+msgid "Archived projects"
+msgstr ""
+
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
+msgid "Are you sure"
+msgstr ""
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr ""
+
+msgid "Are you sure you want to erase this build?"
+msgstr ""
+
+msgid "Are you sure you want to lose unsaved changes?"
+msgstr ""
+
+msgid "Are you sure you want to lose your issue information?"
+msgstr ""
+
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
+msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
+msgstr ""
+
+msgid "Are you sure you want to remove %{group_name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove approver %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
+msgid "Are you sure you want to remove group %{name}?"
+msgstr ""
+
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
+msgid "Are you sure you want to remove this identity?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Are you sure you want to unlock %{path_lock_path}?"
+msgstr ""
+
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Artifact ID"
+msgstr ""
+
+msgid "Artifacts"
+msgstr ""
+
+msgid "Ascending"
+msgstr ""
+
+msgid "Ask your group maintainer to set up a group Runner."
+msgstr ""
+
+msgid "Assertion consumer service URL"
+msgstr ""
+
+msgid "Assets"
+msgstr ""
+
+msgid "Assign custom color like #FF0000"
+msgstr ""
+
+msgid "Assign labels"
+msgstr ""
+
+msgid "Assign milestone"
+msgstr ""
+
+msgid "Assign some issues to this milestone."
+msgstr ""
+
+msgid "Assign to"
+msgstr ""
+
+msgid "Assigned Issues"
+msgstr ""
+
+msgid "Assigned Merge Requests"
+msgstr ""
+
+msgid "Assigned to me"
+msgstr ""
+
+msgid "Assignee"
+msgstr ""
+
+msgid "Assignee lists not available with your current license"
+msgstr ""
+
+msgid "Assignee lists show all issues assigned to the selected user."
+msgstr ""
+
+msgid "Assignee(s)"
+msgstr ""
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
+msgid "Attach a file"
+msgstr ""
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+
+msgid "Audit Events"
+msgstr ""
+
+msgid "Aug"
+msgstr ""
+
+msgid "August"
+msgstr ""
+
+msgid "Authentication Log"
+msgstr ""
+
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
+msgid "Authentication log"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
+msgid "Author"
+msgstr ""
+
+msgid "Authorization code:"
+msgstr ""
+
+msgid "Authorization key"
+msgstr ""
+
+msgid "Authorization was granted by entering your username and password in the application."
+msgstr ""
+
+msgid "Authorize"
+msgstr ""
+
+msgid "Authorize %{link_to_client} to use your account?"
+msgstr ""
+
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
+msgid "Authorized At"
+msgstr ""
+
+msgid "Authorized applications (%{size})"
+msgstr ""
+
+msgid "Authors: %{authors}"
+msgstr ""
+
+msgid "Auto DevOps"
+msgstr ""
+
+msgid "Auto DevOps enabled"
+msgstr ""
+
+msgid "Auto DevOps, runners and job artifacts"
+msgstr ""
+
+msgid "Auto-cancel redundant, pending pipelines"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps"
+msgstr ""
+
+msgid "AutoDevOps|Auto DevOps documentation"
+msgstr ""
+
+msgid "AutoDevOps|Enable in settings"
+msgstr ""
+
+msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgstr ""
+
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
+msgstr ""
+
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
+msgid "Automatically marked as default internal user"
+msgstr ""
+
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
+msgid "Automatically resolved"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Available group Runners: %{runners}"
+msgstr ""
+
+msgid "Available shared Runners:"
+msgstr ""
+
+msgid "Available specific runners"
+msgstr ""
+
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
+msgid "Avatar for %{name}"
+msgstr ""
+
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Average per day: %{average}"
+msgstr ""
+
+msgid "Background Color"
+msgstr ""
+
+msgid "Background Jobs"
+msgstr ""
+
+msgid "Background color"
+msgstr ""
+
+msgid "Badges"
+msgstr ""
+
+msgid "Badges|A new badge was added."
+msgstr ""
+
+msgid "Badges|Add badge"
+msgstr ""
+
+msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|Badge image URL"
+msgstr ""
+
+msgid "Badges|Badge image preview"
+msgstr ""
+
+msgid "Badges|Delete badge"
+msgstr ""
+
+msgid "Badges|Delete badge?"
+msgstr ""
+
+msgid "Badges|Deleting the badge failed, please try again."
+msgstr ""
+
+msgid "Badges|Group Badge"
+msgstr ""
+
+msgid "Badges|Link"
+msgstr ""
+
+msgid "Badges|No badge image"
+msgstr ""
+
+msgid "Badges|No image to preview"
+msgstr ""
+
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
+msgid "Badges|Project Badge"
+msgstr ""
+
+msgid "Badges|Reload badge image"
+msgstr ""
+
+msgid "Badges|Save changes"
+msgstr ""
+
+msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
+msgstr ""
+
+msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
+msgstr ""
+
+msgid "Badges|The badge was deleted."
+msgstr ""
+
+msgid "Badges|The badge was saved."
+msgstr ""
+
+msgid "Badges|This group has no badges"
+msgstr ""
+
+msgid "Badges|This project has no badges"
+msgstr ""
+
+msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
+msgstr ""
+
+msgid "Badges|Your badges"
+msgstr ""
+
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
+msgid "BatchComments|Delete all pending comments"
+msgstr ""
+
+msgid "BatchComments|Discard review?"
+msgstr ""
+
+msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
+msgstr ""
+
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
+msgid "Begin with the selected commit"
+msgstr ""
+
+msgid "Below are examples of regex for existing tools:"
+msgstr ""
+
+msgid "Below you will find all the groups that are public."
+msgstr ""
+
+msgid "Billing"
+msgstr ""
+
+msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
+msgstr ""
+
+msgid "BillingPlans|Current plan"
+msgstr ""
+
+msgid "BillingPlans|Customer Support"
+msgstr ""
+
+msgid "BillingPlans|Downgrade"
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
+msgstr ""
+
+msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
+msgstr ""
+
+msgid "BillingPlans|Manage plan"
+msgstr ""
+
+msgid "BillingPlans|Please contact %{customer_support_link} in that case."
+msgstr ""
+
+msgid "BillingPlans|See all %{plan_name} features"
+msgstr ""
+
+msgid "BillingPlans|This group uses the plan associated with its parent group."
+msgstr ""
+
+msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
+msgstr ""
+
+msgid "BillingPlans|Upgrade"
+msgstr ""
+
+msgid "BillingPlans|You are currently on the %{plan_link} plan."
+msgstr ""
+
+msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}"
+msgstr ""
+
+msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
+msgstr ""
+
+msgid "BillingPlans|features"
+msgstr ""
+
+msgid "BillingPlans|frequently asked questions"
+msgstr ""
+
+msgid "BillingPlans|monthly"
+msgstr ""
+
+msgid "BillingPlans|paid annually at %{price_per_year}"
+msgstr ""
+
+msgid "BillingPlans|per user"
+msgstr ""
+
+msgid "Bitbucket Server Import"
+msgstr ""
+
+msgid "Bitbucket import"
+msgstr ""
+
+msgid "Block"
+msgstr ""
+
+msgid "Blocked"
+msgstr ""
+
+msgid "Blog"
+msgstr ""
+
+msgid "Boards"
+msgstr ""
+
+msgid "Branch %{branchName} was not found in this project's repository."
+msgstr ""
+
+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 ""
+
+msgid "Branch has changed"
+msgstr ""
+
+msgid "Branch is already taken"
+msgstr ""
+
+msgid "Branch name"
+msgstr ""
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr ""
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr ""
+
+msgid "Branches"
+msgstr ""
+
+msgid "Branches|Active"
+msgstr ""
+
+msgid "Branches|Active branches"
+msgstr ""
+
+msgid "Branches|All"
+msgstr ""
+
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project maintainer or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Overview"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}."
+msgstr ""
+
+msgid "Branches|Show active branches"
+msgstr ""
+
+msgid "Branches|Show all branches"
+msgstr ""
+
+msgid "Branches|Show more active branches"
+msgstr ""
+
+msgid "Branches|Show more stale branches"
+msgstr ""
+
+msgid "Branches|Show overview of the branches"
+msgstr ""
+
+msgid "Branches|Show stale branches"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|Stale"
+msgstr ""
+
+msgid "Branches|Stale branches"
+msgstr ""
+
+msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
+msgid "Branches|This branch hasn’t been merged into %{default_branch}."
+msgstr ""
+
+msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
+msgstr ""
+
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|diverged from upstream"
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
+msgid "Browse Directory"
+msgstr ""
+
+msgid "Browse File"
+msgstr ""
+
+msgid "Browse Files"
+msgstr ""
+
+msgid "Browse files"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Business"
+msgstr ""
+
+msgid "Business metrics (Custom)"
+msgstr ""
+
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
+msgid "By %{user_name}"
+msgstr ""
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
+msgid "ByAuthor|by"
+msgstr ""
+
+msgid "CHANGELOG"
+msgstr ""
+
+msgid "CI / CD"
+msgstr ""
+
+msgid "CI / CD Charts"
+msgstr ""
+
+msgid "CI / CD Settings"
+msgstr ""
+
+msgid "CI Lint"
+msgstr ""
+
+msgid "CI variables"
+msgstr ""
+
+msgid "CI will run using the credentials assigned above."
+msgstr ""
+
+msgid "CI/CD"
+msgstr ""
+
+msgid "CI/CD configuration"
+msgstr ""
+
+msgid "CI/CD for external repo"
+msgstr ""
+
+msgid "CI/CD settings"
+msgstr ""
+
+msgid "CICD|Auto DevOps"
+msgstr ""
+
+msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
+msgstr ""
+
+msgid "CICD|Automatic deployment to staging, manual deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production"
+msgstr ""
+
+msgid "CICD|Continuous deployment to production using timed incremental rollout"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline"
+msgstr ""
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
+msgid "CICD|Deployment strategy"
+msgstr ""
+
+msgid "CICD|Jobs"
+msgstr ""
+
+msgid "CICD|Learn more about Auto DevOps"
+msgstr ""
+
+msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
+msgstr ""
+
+msgid "CICD|group enabled"
+msgstr ""
+
+msgid "CICD|instance enabled"
+msgstr ""
+
+msgid "CONTRIBUTING"
+msgstr ""
+
+msgid "Callback URL"
+msgstr ""
+
+msgid "Can override approvers and approvals required per merge request"
+msgstr ""
+
+msgid "Can't find HEAD commit for this branch"
+msgstr ""
+
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Cancel this job"
+msgstr ""
+
+msgid "Cannot be merged automatically"
+msgstr ""
+
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certificate"
+msgstr ""
+
+msgid "Certificate (PEM)"
+msgstr ""
+
+msgid "Change Weight"
+msgstr ""
+
+msgid "Change path"
+msgstr ""
+
+msgid "Change permissions"
+msgstr ""
+
+msgid "Change template"
+msgstr ""
+
+msgid "Change this value to influence how frequently the GitLab UI polls for updates."
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr ""
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr ""
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr ""
+
+msgid "ChangeTypeAction|Revert"
+msgstr ""
+
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
+msgid "Changes"
+msgstr ""
+
+msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
+msgstr ""
+
+msgid "Changes suppressed. Click to show."
+msgstr ""
+
+msgid "Charts"
+msgstr ""
+
+msgid "Chat"
+msgstr ""
+
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
+msgstr ""
+
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
+msgid "Checking %{text} availability…"
+msgstr ""
+
+msgid "Checking approval status"
+msgstr ""
+
+msgid "Checking branch availability..."
+msgstr ""
+
+msgid "Cherry-pick this commit"
+msgstr ""
+
+msgid "Cherry-pick this merge request"
+msgstr ""
+
+msgid "Choose <strong>Create archive</strong> and wait for archiving to complete."
+msgstr ""
+
+msgid "Choose <strong>Next</strong> at the bottom of the page."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose a file"
+msgstr ""
+
+msgid "Choose a role permission"
+msgstr ""
+
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
+msgid "Choose any color."
+msgstr ""
+
+msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
+msgstr ""
+
+msgid "Choose file…"
+msgstr ""
+
+msgid "Choose the top-level group for your repository imports."
+msgstr ""
+
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr ""
+
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
+
+msgid "Choose which repositories you want to connect and run CI/CD pipelines."
+msgstr ""
+
+msgid "Choose which shards you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
+msgid "CiStatusLabel|canceled"
+msgstr ""
+
+msgid "CiStatusLabel|created"
+msgstr ""
+
+msgid "CiStatusLabel|delayed"
+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|preparing"
+msgstr ""
+
+msgid "CiStatusLabel|skipped"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for delayed job"
+msgstr ""
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr ""
+
+msgid "CiStatusText|blocked"
+msgstr ""
+
+msgid "CiStatusText|canceled"
+msgstr ""
+
+msgid "CiStatusText|created"
+msgstr ""
+
+msgid "CiStatusText|delayed"
+msgstr ""
+
+msgid "CiStatusText|failed"
+msgstr ""
+
+msgid "CiStatusText|manual"
+msgstr ""
+
+msgid "CiStatusText|passed"
+msgstr ""
+
+msgid "CiStatusText|pending"
+msgstr ""
+
+msgid "CiStatusText|preparing"
+msgstr ""
+
+msgid "CiStatusText|skipped"
+msgstr ""
+
+msgid "CiStatus|running"
+msgstr ""
+
+msgid "CiVariables|Input variable key"
+msgstr ""
+
+msgid "CiVariables|Input variable value"
+msgstr ""
+
+msgid "CiVariables|Remove variable row"
+msgstr ""
+
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
+msgid "CiVariable|* (All environments)"
+msgstr ""
+
+msgid "CiVariable|All environments"
+msgstr ""
+
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
+msgid "CiVariable|Error occurred while saving variables"
+msgstr ""
+
+msgid "CiVariable|Masked"
+msgstr ""
+
+msgid "CiVariable|New environment"
+msgstr ""
+
+msgid "CiVariable|Protected"
+msgstr ""
+
+msgid "CiVariable|Search environments"
+msgstr ""
+
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
+msgid "CiVariable|Toggle protected"
+msgstr ""
+
+msgid "CiVariable|Validation failed"
+msgstr ""
+
+msgid "Classification Label (optional)"
+msgstr ""
+
+msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Clear input"
+msgstr ""
+
+msgid "Clear search"
+msgstr ""
+
+msgid "Clear search input"
+msgstr ""
+
+msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
+msgstr ""
+
+msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
+msgstr ""
+
+msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
+msgstr ""
+
+msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
+msgstr ""
+
+msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
+msgstr ""
+
+msgid "Click to expand it."
+msgstr ""
+
+msgid "Click to expand text"
+msgstr ""
+
+msgid "Client authentication certificate"
+msgstr ""
+
+msgid "Client authentication key"
+msgstr ""
+
+msgid "Client authentication key password"
+msgstr ""
+
+msgid "Clients"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Clone repository"
+msgstr ""
+
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with KRB5"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close epic"
+msgstr ""
+
+msgid "Close milestone"
+msgstr ""
+
+msgid "Closed"
+msgstr ""
+
+msgid "Closed issues"
+msgstr ""
+
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+msgstr ""
+
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|%{title} upgraded successfully."
+msgstr ""
+
+msgid "ClusterIntegration|API URL"
+msgstr ""
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
+msgid "ClusterIntegration|Add Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Add a Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
+msgstr ""
+
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
+msgstr ""
+
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
+msgstr ""
+
+msgid "ClusterIntegration|Applications"
+msgstr ""
+
+msgid "ClusterIntegration|Apply for credit"
+msgstr ""
+
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
+msgid "ClusterIntegration|CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager"
+msgstr ""
+
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
+msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
+msgstr ""
+
+msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
+msgstr ""
+
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
+msgid "ClusterIntegration|Copy API URL"
+msgstr ""
+
+msgid "ClusterIntegration|Copy CA Certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Create Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Did you know?"
+msgstr ""
+
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
+msgstr ""
+
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Environment scope"
+msgstr ""
+
+msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
+msgstr ""
+
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching projects"
+msgstr ""
+
+msgid "ClusterIntegration|Fetching zones"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Integration"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner"
+msgstr ""
+
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
+
+msgid "ClusterIntegration|Google Cloud Platform project"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgstr ""
+
+msgid "ClusterIntegration|Group cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Helm Tiller"
+msgstr ""
+
+msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
+msgstr ""
+
+msgid "ClusterIntegration|Hide"
+msgstr ""
+
+msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
+
+msgid "ClusterIntegration|Ingress"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
+
+msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
+msgstr ""
+
+msgid "ClusterIntegration|Install"
+msgstr ""
+
+msgid "ClusterIntegration|Installed"
+msgstr ""
+
+msgid "ClusterIntegration|Installing"
+msgstr ""
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr ""
+
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgstr ""
+
+msgid "ClusterIntegration|Jupyter Hostname"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub"
+msgstr ""
+
+msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
+msgstr ""
+
+msgid "ClusterIntegration|Knative"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Domain Name:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr ""
+
+msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster name"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Let's Encrypt"
+msgstr ""
+
+msgid "ClusterIntegration|Machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
+msgstr ""
+
+msgid "ClusterIntegration|Manage"
+msgstr ""
+
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
+msgstr ""
+
+msgid "ClusterIntegration|No machine types matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No projects found"
+msgstr ""
+
+msgid "ClusterIntegration|No projects matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|No zones matched your search"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes"
+msgstr ""
+
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
+msgstr ""
+
+msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
+msgstr ""
+
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
+
+msgid "ClusterIntegration|Project cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Project namespace (optional, unique)"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
+msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
+msgstr ""
+
+msgid "ClusterIntegration|RBAC-enabled cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
+msgstr ""
+
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove integration"
+msgstr ""
+
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
+msgstr ""
+
+msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above."
+msgstr ""
+
+msgid "ClusterIntegration|Request to begin installing failed"
+msgstr ""
+
+msgid "ClusterIntegration|Retry update"
+msgstr ""
+
+msgid "ClusterIntegration|Save changes"
+msgstr ""
+
+msgid "ClusterIntegration|Search machine types"
+msgstr ""
+
+msgid "ClusterIntegration|Search projects"
+msgstr ""
+
+msgid "ClusterIntegration|Search zones"
+msgstr ""
+
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Select machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project"
+msgstr ""
+
+msgid "ClusterIntegration|Select project and zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Select project to choose zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone"
+msgstr ""
+
+msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr ""
+
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
+
+msgid "ClusterIntegration|Show"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong on our end."
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|Something went wrong while installing %{title}"
+msgstr ""
+
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
+
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
+msgstr ""
+
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
+msgstr ""
+
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade failed"
+msgstr ""
+
+msgid "ClusterIntegration|Upgraded"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrading"
+msgstr ""
+
+msgid "ClusterIntegration|Validating project billing status"
+msgstr ""
+
+msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgstr ""
+
+msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
+msgstr ""
+
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
+msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
+msgstr ""
+
+msgid "ClusterIntegration|Zone"
+msgstr ""
+
+msgid "ClusterIntegration|access to Google Kubernetes Engine"
+msgstr ""
+
+msgid "ClusterIntegration|documentation"
+msgstr ""
+
+msgid "ClusterIntegration|help page"
+msgstr ""
+
+msgid "ClusterIntegration|meets the requirements"
+msgstr ""
+
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
+msgid "ClusterIntegration|properly configured"
+msgstr ""
+
+msgid "ClusterIntegration|sign up"
+msgstr ""
+
+msgid "Code"
+msgstr ""
+
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
+msgid "Code owners"
+msgstr ""
+
+msgid "CodeOwner|Pattern"
+msgstr ""
+
+msgid "Cohorts"
+msgstr ""
+
+msgid "Collapse"
+msgstr ""
+
+msgid "Collapse approvers"
+msgstr ""
+
+msgid "Collapse sidebar"
+msgstr ""
+
+msgid "Command line instructions"
+msgstr ""
+
+msgid "Comment"
+msgstr ""
+
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Comment & resolve discussion"
+msgstr ""
+
+msgid "Comment & unresolve discussion"
+msgstr ""
+
+msgid "Comment form position"
+msgstr ""
+
+msgid "Comments"
+msgstr ""
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Commit %{commit_id}"
+msgstr ""
+
+msgid "Commit Message"
+msgstr ""
+
+msgid "Commit deleted"
+msgstr ""
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr ""
+
+msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
+msgstr ""
+
+msgid "Commit to %{branchName} branch"
+msgstr ""
+
+msgid "CommitBoxTitle|Commit"
+msgstr ""
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr ""
+
+msgid "CommitWidget|authored"
+msgstr ""
+
+msgid "Commits"
+msgstr ""
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits per day hour (UTC)"
+msgstr ""
+
+msgid "Commits per day of month"
+msgstr ""
+
+msgid "Commits per weekday"
+msgstr ""
+
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
+msgid "Commits|Commit: %{commitText}"
+msgstr ""
+
+msgid "Commits|History"
+msgstr ""
+
+msgid "Commits|No related merge requests found"
+msgstr ""
+
+msgid "Committed by"
+msgstr ""
+
+msgid "Commit…"
+msgstr ""
+
+msgid "Company"
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "Compare changes"
+msgstr ""
+
+msgid "Compare changes with the last commit"
+msgstr ""
+
+msgid "Compare changes with the merge request target branch"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
+msgid "Confidential"
+msgstr ""
+
+msgid "Confidentiality"
+msgstr ""
+
+msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure Gitaly timeouts."
+msgstr ""
+
+msgid "Configure Tracing"
+msgstr ""
+
+msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
+msgstr ""
+
+msgid "Configure automatic git checks and housekeeping on repositories."
+msgstr ""
+
+msgid "Configure limits for web and API requests."
+msgstr ""
+
+msgid "Configure push mirrors."
+msgstr ""
+
+msgid "Configure storage path settings."
+msgstr ""
+
+msgid "Configure the %{link} integration."
+msgstr ""
+
+msgid "Configure the way a user creates a new account."
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Connect"
+msgstr ""
+
+msgid "Connect all repositories"
+msgstr ""
+
+msgid "Connect repositories from GitHub"
+msgstr ""
+
+msgid "Connect your external repositories, and CI/CD pipelines will run for new commits. A GitLab project will be created with only CI/CD features enabled."
+msgstr ""
+
+msgid "Connecting..."
+msgstr ""
+
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
+msgid "Contact sales to upgrade"
+msgstr ""
+
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
+msgid "Container Registry"
+msgstr ""
+
+msgid "Container registry images"
+msgstr ""
+
+msgid "ContainerRegistry|Created"
+msgstr ""
+
+msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
+msgstr ""
+
+msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
+msgstr ""
+
+msgid "ContainerRegistry|How to use the Container Registry"
+msgstr ""
+
+msgid "ContainerRegistry|Learn more about"
+msgstr ""
+
+msgid "ContainerRegistry|No tags in Container Registry for this container image."
+msgstr ""
+
+msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
+msgstr ""
+
+msgid "ContainerRegistry|Remove repository"
+msgstr ""
+
+msgid "ContainerRegistry|Remove tag"
+msgstr ""
+
+msgid "ContainerRegistry|Size"
+msgstr ""
+
+msgid "ContainerRegistry|Tag"
+msgstr ""
+
+msgid "ContainerRegistry|Tag ID"
+msgstr ""
+
+msgid "ContainerRegistry|Use different image names"
+msgstr ""
+
+msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
+msgstr ""
+
+msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images."
+msgstr ""
+
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
+msgid "Continue"
+msgstr ""
+
+msgid "Continue to the next step"
+msgstr ""
+
+msgid "Continuous Integration and Deployment"
+msgstr ""
+
+msgid "Contribute to GitLab"
+msgstr ""
+
+msgid "Contribution"
+msgstr ""
+
+msgid "Contribution Analytics"
+msgstr ""
+
+msgid "Contribution Charts"
+msgstr ""
+
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
+msgid "Contributions per group member"
+msgstr ""
+
+msgid "Contributors"
+msgstr ""
+
+msgid "ContributorsPage|%{startDate} – %{endDate}"
+msgstr ""
+
+msgid "ContributorsPage|Building repository graph."
+msgstr ""
+
+msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
+msgstr ""
+
+msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Control the display of third party offers."
+msgstr ""
+
+msgid "Control the maximum concurrency of repository backfill for this secondary node"
+msgstr ""
+
+msgid "ConvDev Index"
+msgstr ""
+
+msgid "Copy %{http_label} clone URL"
+msgstr ""
+
+msgid "Copy %{protocol} clone URL"
+msgstr ""
+
+msgid "Copy ID to clipboard"
+msgstr ""
+
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
+msgid "Copy SSH clone URL"
+msgstr ""
+
+msgid "Copy SSH public key"
+msgstr ""
+
+msgid "Copy SSH public key to clipboard"
+msgstr ""
+
+msgid "Copy URL to clipboard"
+msgstr ""
+
+msgid "Copy branch name to clipboard"
+msgstr ""
+
+msgid "Copy command to clipboard"
+msgstr ""
+
+msgid "Copy commit SHA to clipboard"
+msgstr ""
+
+msgid "Copy file path to clipboard"
+msgstr ""
+
+msgid "Copy link"
+msgstr ""
+
+msgid "Copy reference to clipboard"
+msgstr ""
+
+msgid "Copy secret to clipboard"
+msgstr ""
+
+msgid "Copy to clipboard"
+msgstr ""
+
+msgid "Copy token to clipboard"
+msgstr ""
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
+msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
+msgstr ""
+
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
+msgid "Create"
+msgstr ""
+
+msgid "Create New Directory"
+msgstr ""
+
+msgid "Create New Domain"
+msgstr ""
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
+msgid "Create a new branch"
+msgstr ""
+
+msgid "Create a new issue"
+msgstr ""
+
+msgid "Create a new repository"
+msgstr ""
+
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
+msgid "Create branch"
+msgstr ""
+
+msgid "Create commit"
+msgstr ""
+
+msgid "Create directory"
+msgstr ""
+
+msgid "Create empty repository"
+msgstr ""
+
+msgid "Create epic"
+msgstr ""
+
+msgid "Create file"
+msgstr ""
+
+msgid "Create group"
+msgstr ""
+
+msgid "Create group label"
+msgstr ""
+
+msgid "Create lists from labels. Issues with that label appear in that list."
+msgstr ""
+
+msgid "Create merge request"
+msgstr ""
+
+msgid "Create merge request and branch"
+msgstr ""
+
+msgid "Create milestone"
+msgstr ""
+
+msgid "Create new branch"
+msgstr ""
+
+msgid "Create new directory"
+msgstr ""
+
+msgid "Create new file"
+msgstr ""
+
+msgid "Create new file or directory"
+msgstr ""
+
+msgid "Create new label"
+msgstr ""
+
+msgid "Create new..."
+msgstr ""
+
+msgid "Create project label"
+msgstr ""
+
+msgid "Create your first page"
+msgstr ""
+
+msgid "CreateTag|Tag"
+msgstr ""
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Created At"
+msgstr ""
+
+msgid "Created by me"
+msgstr ""
+
+msgid "Created on"
+msgstr ""
+
+msgid "Created on:"
+msgstr ""
+
+msgid "Creating epic"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr ""
+
+msgid "Cron syntax"
+msgstr ""
+
+msgid "Current Branch"
+msgstr ""
+
+msgid "Current Project"
+msgstr ""
+
+msgid "Current node"
+msgstr ""
+
+msgid "CurrentUser|Profile"
+msgstr ""
+
+msgid "CurrentUser|Settings"
+msgstr ""
+
+msgid "Custom CI config path"
+msgstr ""
+
+msgid "Custom hostname (for private commit emails)"
+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 ""
+
+msgid "Custom project templates"
+msgstr ""
+
+msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
+msgstr ""
+
+msgid "Customize colors"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Customize language and region related settings."
+msgstr ""
+
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
+msgid "Cycle Analytics"
+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 ""
+
+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 "DNS"
+msgstr ""
+
+msgid "Dashboard"
+msgstr ""
+
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
+msgid "Dashboards"
+msgstr ""
+
+msgid "Data is still calculating..."
+msgstr ""
+
+msgid "Date picker"
+msgstr ""
+
+msgid "Debug"
+msgstr ""
+
+msgid "Dec"
+msgstr ""
+
+msgid "December"
+msgstr ""
+
+msgid "Decline"
+msgstr ""
+
+msgid "Decline and sign out"
+msgstr ""
+
+msgid "Default Branch"
+msgstr ""
+
+msgid "Default artifacts expiration"
+msgstr ""
+
+msgid "Default classification label"
+msgstr ""
+
+msgid "Default description template for issues"
+msgstr ""
+
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
+msgid "Default issue template"
+msgstr ""
+
+msgid "Default: Directly import the Google Code email address or username"
+msgstr ""
+
+msgid "Default: Map a FogBugz account ID to a full name"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr ""
+
+msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes."
+msgstr ""
+
+msgid "DelayedJobs|Start now"
+msgstr ""
+
+msgid "DelayedJobs|Unschedule"
+msgstr ""
+
+msgid "DelayedJobs|delayed"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete Package"
+msgstr ""
+
+msgid "Delete Snippet"
+msgstr ""
+
+msgid "Delete comment"
+msgstr ""
+
+msgid "Delete license"
+msgstr ""
+
+msgid "Delete list"
+msgstr ""
+
+msgid "Delete source branch"
+msgstr ""
+
+msgid "Delete this attachment"
+msgstr ""
+
+msgid "Deleted"
+msgstr ""
+
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
+msgid "Deny"
+msgstr ""
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Deploy Keys"
+msgstr ""
+
+msgid "Deploy key was successfully updated."
+msgstr ""
+
+msgid "DeployKeys|+%{count} others"
+msgstr ""
+
+msgid "DeployKeys|Current project"
+msgstr ""
+
+msgid "DeployKeys|Deploy key"
+msgstr ""
+
+msgid "DeployKeys|Enabled deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error enabling deploy key"
+msgstr ""
+
+msgid "DeployKeys|Error getting deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Error removing deploy key"
+msgstr ""
+
+msgid "DeployKeys|Expand %{count} other projects"
+msgstr ""
+
+msgid "DeployKeys|Loading deploy keys"
+msgstr ""
+
+msgid "DeployKeys|No deploy keys found. Create one with the form above."
+msgstr ""
+
+msgid "DeployKeys|Privately accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Project usage"
+msgstr ""
+
+msgid "DeployKeys|Publicly accessible deploy keys"
+msgstr ""
+
+msgid "DeployKeys|Read access only"
+msgstr ""
+
+msgid "DeployKeys|Write access allowed"
+msgstr ""
+
+msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
+msgstr ""
+
+msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
+msgstr ""
+
+msgid "DeployTokens|Add a deploy token"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the registry images"
+msgstr ""
+
+msgid "DeployTokens|Allows read-only access to the repository"
+msgstr ""
+
+msgid "DeployTokens|Copy deploy token to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Copy username to clipboard"
+msgstr ""
+
+msgid "DeployTokens|Create deploy token"
+msgstr ""
+
+msgid "DeployTokens|Created"
+msgstr ""
+
+msgid "DeployTokens|Deploy Tokens"
+msgstr ""
+
+msgid "DeployTokens|Deploy tokens allow read-only access to your repository and registry images."
+msgstr ""
+
+msgid "DeployTokens|Expires"
+msgstr ""
+
+msgid "DeployTokens|Name"
+msgstr ""
+
+msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
+msgstr ""
+
+msgid "DeployTokens|Revoke"
+msgstr ""
+
+msgid "DeployTokens|Revoke %{name}"
+msgstr ""
+
+msgid "DeployTokens|Scopes"
+msgstr ""
+
+msgid "DeployTokens|This action cannot be undone."
+msgstr ""
+
+msgid "DeployTokens|This project has no active Deploy Tokens."
+msgstr ""
+
+msgid "DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again."
+msgstr ""
+
+msgid "DeployTokens|Use this username as a login."
+msgstr ""
+
+msgid "DeployTokens|Username"
+msgstr ""
+
+msgid "DeployTokens|You are about to revoke"
+msgstr ""
+
+msgid "DeployTokens|Your New Deploy Token"
+msgstr ""
+
+msgid "DeployTokens|Your new project deploy token has been created."
+msgstr ""
+
+msgid "Deployed"
+msgstr ""
+
+msgid "Deployed to"
+msgstr ""
+
+msgid "Deploying to"
+msgstr ""
+
+msgid "Deprioritize label"
+msgstr ""
+
+msgid "Descending"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
+msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
+msgstr ""
+
+msgid "Description:"
+msgstr ""
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
+msgid "Destroy"
+msgstr ""
+
+msgid "Details"
+msgstr ""
+
+msgid "Details (default)"
+msgstr ""
+
+msgid "Detect host keys"
+msgstr ""
+
+msgid "Diff content limits"
+msgstr ""
+
+msgid "Diff limits"
+msgstr ""
+
+msgid "Diffs|No file name available"
+msgstr ""
+
+msgid "Diffs|Something went wrong while fetching diff lines."
+msgstr ""
+
+msgid "Direction"
+msgstr ""
+
+msgid "Directory name"
+msgstr ""
+
+msgid "Disable"
+msgstr ""
+
+msgid "Disable for this project"
+msgstr ""
+
+msgid "Disable group Runners"
+msgstr ""
+
+msgid "Disable shared Runners"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Discard"
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all unstaged changes?"
+msgstr ""
+
+msgid "Discard changes"
+msgstr ""
+
+msgid "Discard changes to %{path}?"
+msgstr ""
+
+msgid "Discard draft"
+msgstr ""
+
+msgid "Discard review"
+msgstr ""
+
+msgid "Discover GitLab Geo"
+msgstr ""
+
+msgid "Discover projects, groups and snippets. Share your projects with others"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
+msgid "Discussion"
+msgstr ""
+
+msgid "Dismiss"
+msgstr ""
+
+msgid "Dismiss ConvDev introduction"
+msgstr ""
+
+msgid "Dismiss Cycle Analytics introduction box"
+msgstr ""
+
+msgid "Dismiss Merge Request promotion"
+msgstr ""
+
+msgid "Dismiss trial promotion"
+msgstr ""
+
+msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
+msgstr ""
+
+msgid "Documentation for popular identity providers"
+msgstr ""
+
+msgid "Domain"
+msgstr ""
+
+msgid "Don't show again"
+msgstr ""
+
+msgid "Done"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download artifacts"
+msgstr ""
+
+msgid "Download asset"
+msgstr ""
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
+msgid "Download tar"
+msgstr ""
+
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
+msgstr ""
+
+msgid "DownloadCommit|Email Patches"
+msgstr ""
+
+msgid "DownloadCommit|Plain Diff"
+msgstr ""
+
+msgid "DownloadSource|Download"
+msgstr ""
+
+msgid "Downstream"
+msgstr ""
+
+msgid "Downvotes"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
+msgstr ""
+
+msgid "Each Runner can be in one of the following states:"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit %{name}"
+msgstr ""
+
+msgid "Edit Deploy Key"
+msgstr ""
+
+msgid "Edit Label"
+msgstr ""
+
+msgid "Edit Milestone"
+msgstr ""
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr ""
+
+msgid "Edit Snippet"
+msgstr ""
+
+msgid "Edit application"
+msgstr ""
+
+msgid "Edit comment"
+msgstr ""
+
+msgid "Edit environment"
+msgstr ""
+
+msgid "Edit file"
+msgstr ""
+
+msgid "Edit files in the editor and commit changes here"
+msgstr ""
+
+msgid "Edit group: %{group_name}"
+msgstr ""
+
+msgid "Edit identity for %{user_name}"
+msgstr ""
+
+msgid "Edit issues"
+msgstr ""
+
+msgid "Edit public deploy key"
+msgstr ""
+
+msgid "Elasticsearch"
+msgstr ""
+
+msgid "Elasticsearch integration. Elasticsearch AWS IAM."
+msgstr ""
+
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
+msgid "Email"
+msgstr ""
+
+msgid "Email address"
+msgstr ""
+
+msgid "Email patch"
+msgstr ""
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "Emails"
+msgstr ""
+
+msgid "Embed"
+msgstr ""
+
+msgid "Empty file"
+msgstr ""
+
+msgid "Enable"
+msgstr ""
+
+msgid "Enable Auto DevOps"
+msgstr ""
+
+msgid "Enable HTML emails"
+msgstr ""
+
+msgid "Enable Pseudonymizer data collection"
+msgstr ""
+
+msgid "Enable SAML authentication for this group"
+msgstr ""
+
+msgid "Enable Sentry for error reporting and logging."
+msgstr ""
+
+msgid "Enable and configure InfluxDB metrics."
+msgstr ""
+
+msgid "Enable and configure Prometheus metrics."
+msgstr ""
+
+msgid "Enable classification control using an external service"
+msgstr ""
+
+msgid "Enable error tracking"
+msgstr ""
+
+msgid "Enable for this project"
+msgstr ""
+
+msgid "Enable group Runners"
+msgstr ""
+
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
+msgid "Enable or disable the Pseudonymizer data collection."
+msgstr ""
+
+msgid "Enable or disable version check and usage ping."
+msgstr ""
+
+msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgstr ""
+
+msgid "Enable shared Runners"
+msgstr ""
+
+msgid "Enable the Performance Bar for a given group."
+msgstr ""
+
+msgid "Enable two-factor authentication"
+msgstr ""
+
+msgid "Enable usage ping"
+msgstr ""
+
+msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
+msgstr ""
+
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
+msgid "Enabled"
+msgstr ""
+
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
+msgstr ""
+
+msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgstr ""
+
+msgid "Enter the issue description"
+msgstr ""
+
+msgid "Enter the issue title"
+msgstr ""
+
+msgid "Enter the merge request description"
+msgstr ""
+
+msgid "Enter the merge request title"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
+msgid "Environment:"
+msgstr ""
+
+msgid "Environments"
+msgstr ""
+
+msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
+msgstr ""
+
+msgid "Environments|An error occurred while fetching the environments."
+msgstr ""
+
+msgid "Environments|An error occurred while making the request."
+msgstr ""
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while stopping the environment, please try again"
+msgstr ""
+
+msgid "Environments|Are you sure you want to stop this environment?"
+msgstr ""
+
+msgid "Environments|Commit"
+msgstr ""
+
+msgid "Environments|Deploy to..."
+msgstr ""
+
+msgid "Environments|Deployment"
+msgstr ""
+
+msgid "Environments|Environment"
+msgstr ""
+
+msgid "Environments|Environments"
+msgstr ""
+
+msgid "Environments|Environments are places where code gets deployed, such as staging or production."
+msgstr ""
+
+msgid "Environments|Job"
+msgstr ""
+
+msgid "Environments|Learn more about stopping environments"
+msgstr ""
+
+msgid "Environments|New environment"
+msgstr ""
+
+msgid "Environments|No deployments yet"
+msgstr ""
+
+msgid "Environments|No pod name has been specified"
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
+msgstr ""
+
+msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action†being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
+msgstr ""
+
+msgid "Environments|Open live environment"
+msgstr ""
+
+msgid "Environments|Pod logs from"
+msgstr ""
+
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy to environment"
+msgstr ""
+
+msgid "Environments|Read more about environments"
+msgstr ""
+
+msgid "Environments|Rollback"
+msgstr ""
+
+msgid "Environments|Rollback environment"
+msgstr ""
+
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
+msgid "Environments|Show all"
+msgstr ""
+
+msgid "Environments|Stop"
+msgstr ""
+
+msgid "Environments|Stop environment"
+msgstr ""
+
+msgid "Environments|Stopping"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr ""
+
+msgid "Environments|You don't have any environments right now"
+msgstr ""
+
+msgid "Environments|protected"
+msgstr ""
+
+msgid "Epic"
+msgstr ""
+
+msgid "Epics"
+msgstr ""
+
+msgid "Epics Roadmap"
+msgstr ""
+
+msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
+msgstr ""
+
+msgid "Epics|An error occurred while saving the %{epicDateType} date"
+msgstr ""
+
+msgid "Epics|How can I solve this?"
+msgstr ""
+
+msgid "Epics|More information"
+msgstr ""
+
+msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
+msgstr ""
+
+msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
+msgstr ""
+
+msgid "Epics|due"
+msgstr ""
+
+msgid "Epics|start"
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error Reporting and Logging"
+msgstr ""
+
+msgid "Error Tracking"
+msgstr ""
+
+msgid "Error creating a new path"
+msgstr ""
+
+msgid "Error creating epic"
+msgstr ""
+
+msgid "Error deleting %{issuableType}"
+msgstr ""
+
+msgid "Error fetching contributors data."
+msgstr ""
+
+msgid "Error fetching labels."
+msgstr ""
+
+msgid "Error fetching network graph."
+msgstr ""
+
+msgid "Error fetching refs"
+msgstr ""
+
+msgid "Error fetching usage ping data."
+msgstr ""
+
+msgid "Error loading branch data. Please try again."
+msgstr ""
+
+msgid "Error loading branches."
+msgstr ""
+
+msgid "Error loading last commit."
+msgstr ""
+
+msgid "Error loading markdown preview"
+msgstr ""
+
+msgid "Error loading merge requests."
+msgstr ""
+
+msgid "Error loading project data. Please try again."
+msgstr ""
+
+msgid "Error loading template types."
+msgstr ""
+
+msgid "Error loading template."
+msgstr ""
+
+msgid "Error occurred when toggling the notification subscription"
+msgstr ""
+
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr ""
+
+msgid "Error saving label update."
+msgstr ""
+
+msgid "Error updating %{issuableType}"
+msgstr ""
+
+msgid "Error updating status for all todos."
+msgstr ""
+
+msgid "Error updating todo status."
+msgstr ""
+
+msgid "Error uploading file"
+msgstr ""
+
+msgid "Error while loading the merge request. Please try again."
+msgstr ""
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
+msgid "Error:"
+msgstr ""
+
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
+msgid "Estimated"
+msgstr ""
+
+msgid "EventFilterBy|Filter by all"
+msgstr ""
+
+msgid "EventFilterBy|Filter by comments"
+msgstr ""
+
+msgid "EventFilterBy|Filter by issue events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by merge events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by push events"
+msgstr ""
+
+msgid "EventFilterBy|Filter by team"
+msgstr ""
+
+msgid "Events"
+msgstr ""
+
+msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
+msgstr ""
+
+msgid "Every day (at 4:00am)"
+msgstr ""
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr ""
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr ""
+
+msgid "Everyone"
+msgstr ""
+
+msgid "Everyone can contribute"
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
+msgid "Except policy:"
+msgstr ""
+
+msgid "Existing members and groups"
+msgstr ""
+
+msgid "Expand"
+msgstr ""
+
+msgid "Expand all"
+msgstr ""
+
+msgid "Expand approvers"
+msgstr ""
+
+msgid "Expand sidebar"
+msgstr ""
+
+msgid "Expiration date"
+msgstr ""
+
+msgid "Expired"
+msgstr ""
+
+msgid "Expired %{expiredOn}"
+msgstr ""
+
+msgid "Expires in %{expires_at}"
+msgstr ""
+
+msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgstr ""
+
+msgid "Explore"
+msgstr ""
+
+msgid "Explore GitLab"
+msgstr ""
+
+msgid "Explore Groups"
+msgstr ""
+
+msgid "Explore groups"
+msgstr ""
+
+msgid "Explore projects"
+msgstr ""
+
+msgid "Explore public groups"
+msgstr ""
+
+msgid "Export as CSV"
+msgstr ""
+
+msgid "Export issues"
+msgstr ""
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "External Classification Policy Authorization"
+msgstr ""
+
+msgid "External URL"
+msgstr ""
+
+msgid "External Wiki"
+msgstr ""
+
+msgid "External authentication"
+msgstr ""
+
+msgid "External authorization denied access to this project"
+msgstr ""
+
+msgid "External authorization request timeout"
+msgstr ""
+
+msgid "ExternalAuthorizationService|Classification label"
+msgstr ""
+
+msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
+msgstr ""
+
+msgid "Facebook"
+msgstr ""
+
+msgid "Failed"
+msgstr ""
+
+msgid "Failed Jobs"
+msgstr ""
+
+msgid "Failed to change the owner"
+msgstr ""
+
+msgid "Failed to check related branches."
+msgstr ""
+
+msgid "Failed to create resources"
+msgstr ""
+
+msgid "Failed to deploy to"
+msgstr ""
+
+msgid "Failed to load emoji list."
+msgstr ""
+
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
+msgid "Failed to remove issue from board, please try again."
+msgstr ""
+
+msgid "Failed to remove mirror."
+msgstr ""
+
+msgid "Failed to remove the pipeline schedule"
+msgstr ""
+
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
+msgid "Failed to signing using smartcard authentication"
+msgstr ""
+
+msgid "Failed to update issues, please try again."
+msgstr ""
+
+msgid "Failed to upload object map file"
+msgstr ""
+
+msgid "Failure"
+msgstr ""
+
+msgid "Fast-forward merge"
+msgstr ""
+
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
+msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
+msgstr ""
+
+msgid "Favicon was successfully removed."
+msgstr ""
+
+msgid "Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|* (All Environments)"
+msgstr ""
+
+msgid "FeatureFlags|* (All environments)"
+msgstr ""
+
+msgid "FeatureFlags|API URL"
+msgstr ""
+
+msgid "FeatureFlags|Active"
+msgstr ""
+
+msgid "FeatureFlags|Configure"
+msgstr ""
+
+msgid "FeatureFlags|Configure feature flags"
+msgstr ""
+
+msgid "FeatureFlags|Create feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Delete %{name}?"
+msgstr ""
+
+msgid "FeatureFlags|Delete feature flag"
+msgstr ""
+
+msgid "FeatureFlags|Description"
+msgstr ""
+
+msgid "FeatureFlags|Edit Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Environment Spec"
+msgstr ""
+
+msgid "FeatureFlags|Environment Specs"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
+msgstr ""
+
+msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
+msgstr ""
+
+msgid "FeatureFlags|Get started with Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|Inactive"
+msgstr ""
+
+msgid "FeatureFlags|Inactive flag for %{scope}"
+msgstr ""
+
+msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
+msgstr ""
+
+msgid "FeatureFlags|Instance ID"
+msgstr ""
+
+msgid "FeatureFlags|Loading Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|More Information"
+msgstr ""
+
+msgid "FeatureFlags|More information"
+msgstr ""
+
+msgid "FeatureFlags|Name"
+msgstr ""
+
+msgid "FeatureFlags|New"
+msgstr ""
+
+msgid "FeatureFlags|New Feature Flag"
+msgstr ""
+
+msgid "FeatureFlags|Protected"
+msgstr ""
+
+msgid "FeatureFlags|Status"
+msgstr ""
+
+msgid "FeatureFlags|Target environments"
+msgstr ""
+
+msgid "FeatureFlags|There are no active Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There are no inactive Feature Flags"
+msgstr ""
+
+msgid "FeatureFlags|There was an error fetching the feature flags."
+msgstr ""
+
+msgid "FeatureFlags|Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Feb"
+msgstr ""
+
+msgid "February"
+msgstr ""
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
+msgid "File"
+msgid_plural "Files"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "File added"
+msgstr ""
+
+msgid "File browser"
+msgstr ""
+
+msgid "File deleted"
+msgstr ""
+
+msgid "File mode changed from %{a_mode} to %{b_mode}"
+msgstr ""
+
+msgid "File moved"
+msgstr ""
+
+msgid "File templates"
+msgstr ""
+
+msgid "File upload error."
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
+msgstr ""
+
+msgid "Filter"
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently closed."
+msgstr ""
+
+msgid "Filter by %{issuable_type} that are currently opened."
+msgstr ""
+
+msgid "Filter by commit message"
+msgstr ""
+
+msgid "Filter by milestone name"
+msgstr ""
+
+msgid "Filter by two-factor authentication"
+msgstr ""
+
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
+msgid "Filter..."
+msgstr ""
+
+msgid "Find by path"
+msgstr ""
+
+msgid "Find existing members by name"
+msgstr ""
+
+msgid "Find file"
+msgstr ""
+
+msgid "Find the downloaded ZIP file and decompress it."
+msgstr ""
+
+msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
+msgstr ""
+
+msgid "Fingerprint"
+msgstr ""
+
+msgid "Fingerprints"
+msgstr ""
+
+msgid "Finish editing this message first!"
+msgstr ""
+
+msgid "Finish review"
+msgstr ""
+
+msgid "Finished"
+msgstr ""
+
+msgid "First day of the week"
+msgstr ""
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fixed date"
+msgstr ""
+
+msgid "Fixed due date"
+msgstr ""
+
+msgid "Fixed start date"
+msgstr ""
+
+msgid "Fixed:"
+msgstr ""
+
+msgid "FogBugz Email"
+msgstr ""
+
+msgid "FogBugz Import"
+msgstr ""
+
+msgid "FogBugz Password"
+msgstr ""
+
+msgid "FogBugz URL"
+msgstr ""
+
+msgid "FogBugz import"
+msgstr ""
+
+msgid "Follow the steps below to export your Google Code project data."
+msgstr ""
+
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
+msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For more info, read the documentation."
+msgstr ""
+
+msgid "For more information, go to the "
+msgstr ""
+
+msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
+msgstr ""
+
+msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
+msgstr ""
+
+msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr ""
+
+msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
+msgstr ""
+
+msgid "Forking in progress"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Format"
+msgstr ""
+
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
+msgid "Found errors in your .gitlab-ci.yml:"
+msgstr ""
+
+msgid "Free Trial of GitLab.com Gold"
+msgstr ""
+
+msgid "From %{providerTitle}"
+msgstr ""
+
+msgid "From Bitbucket"
+msgstr ""
+
+msgid "From Bitbucket Server"
+msgstr ""
+
+msgid "From FogBugz"
+msgstr ""
+
+msgid "From GitLab.com"
+msgstr ""
+
+msgid "From Google Code"
+msgstr ""
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "From milestones:"
+msgstr ""
+
+msgid "From the Kubernetes cluster details view, install Runner from the applications list"
+msgstr ""
+
+msgid "GPG Key ID:"
+msgstr ""
+
+msgid "GPG Keys"
+msgstr ""
+
+msgid "GPG signature (loading...)"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "General pipelines"
+msgstr ""
+
+msgid "Generate a default set of labels"
+msgstr ""
+
+msgid "Generate key"
+msgstr ""
+
+msgid "Generate new export"
+msgstr ""
+
+msgid "Geo"
+msgstr ""
+
+msgid "Geo Nodes"
+msgstr ""
+
+msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is failing or broken."
+msgstr ""
+
+msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
+msgstr ""
+
+msgid "GeoNodes|Checksummed"
+msgstr ""
+
+msgid "GeoNodes|Data is out of date from %{timeago}"
+msgstr ""
+
+msgid "GeoNodes|Data replication lag"
+msgstr ""
+
+msgid "GeoNodes|Does not match the primary storage configuration"
+msgstr ""
+
+msgid "GeoNodes|Failed"
+msgstr ""
+
+msgid "GeoNodes|Full"
+msgstr ""
+
+msgid "GeoNodes|GitLab version"
+msgstr ""
+
+msgid "GeoNodes|GitLab version does not match the primary node version"
+msgstr ""
+
+msgid "GeoNodes|Health status"
+msgstr ""
+
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
+msgid "GeoNodes|Last event ID processed by cursor"
+msgstr ""
+
+msgid "GeoNodes|Last event ID seen from primary"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Repository verification"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Learn more about Wiki verification"
+msgstr ""
+
+msgid "GeoNodes|Loading nodes"
+msgstr ""
+
+msgid "GeoNodes|Local LFS objects"
+msgstr ""
+
+msgid "GeoNodes|Local attachments"
+msgstr ""
+
+msgid "GeoNodes|Local job artifacts"
+msgstr ""
+
+msgid "GeoNodes|New node"
+msgstr ""
+
+msgid "GeoNodes|Node Authentication was successfully repaired."
+msgstr ""
+
+msgid "GeoNodes|Node was successfully removed."
+msgstr ""
+
+msgid "GeoNodes|Not checksummed"
+msgstr ""
+
+msgid "GeoNodes|Out of sync"
+msgstr ""
+
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
+msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
+msgstr ""
+
+msgid "GeoNodes|Replication slot WAL"
+msgstr ""
+
+msgid "GeoNodes|Replication slots"
+msgstr ""
+
+msgid "GeoNodes|Repositories"
+msgstr ""
+
+msgid "GeoNodes|Repositories checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Repositories verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|Repository checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Repository verification progress"
+msgstr ""
+
+msgid "GeoNodes|Selective"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while changing node status"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while fetching nodes"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while removing node"
+msgstr ""
+
+msgid "GeoNodes|Something went wrong while repairing node"
+msgstr ""
+
+msgid "GeoNodes|Storage config"
+msgstr ""
+
+msgid "GeoNodes|Sync settings"
+msgstr ""
+
+msgid "GeoNodes|Synced"
+msgstr ""
+
+msgid "GeoNodes|Unused slots"
+msgstr ""
+
+msgid "GeoNodes|Unverified"
+msgstr ""
+
+msgid "GeoNodes|Used slots"
+msgstr ""
+
+msgid "GeoNodes|Verified"
+msgstr ""
+
+msgid "GeoNodes|Wiki checksum progress"
+msgstr ""
+
+msgid "GeoNodes|Wiki verification progress"
+msgstr ""
+
+msgid "GeoNodes|Wikis"
+msgstr ""
+
+msgid "GeoNodes|Wikis checksummed for verification with their counterparts on Secondary nodes"
+msgstr ""
+
+msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
+msgstr ""
+
+msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
+msgstr ""
+
+msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for forced re-download"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-check"
+msgstr ""
+
+msgid "Geo|%{name} is scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|All"
+msgstr ""
+
+msgid "Geo|All projects"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-check"
+msgstr ""
+
+msgid "Geo|All projects are being scheduled for re-sync"
+msgstr ""
+
+msgid "Geo|Batch operations"
+msgstr ""
+
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing project."
+msgstr ""
+
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
+msgid "Geo|Failed"
+msgstr ""
+
+msgid "Geo|File sync capacity"
+msgstr ""
+
+msgid "Geo|Geo Status"
+msgstr ""
+
+msgid "Geo|Groups to synchronize"
+msgstr ""
+
+msgid "Geo|In sync"
+msgstr ""
+
+msgid "Geo|Internal URL"
+msgstr ""
+
+msgid "Geo|Last repository check run"
+msgstr ""
+
+msgid "Geo|Last successful sync"
+msgstr ""
+
+msgid "Geo|Last sync attempt"
+msgstr ""
+
+msgid "Geo|Last time verified"
+msgstr ""
+
+msgid "Geo|Never"
+msgstr ""
+
+msgid "Geo|Next sync scheduled at"
+msgstr ""
+
+msgid "Geo|Not synced yet"
+msgstr ""
+
+msgid "Geo|Pending"
+msgstr ""
+
+msgid "Geo|Pending synchronization"
+msgstr ""
+
+msgid "Geo|Pending verification"
+msgstr ""
+
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
+msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
+msgstr ""
+
+msgid "Geo|Projects in certain groups"
+msgstr ""
+
+msgid "Geo|Projects in certain storage shards"
+msgstr ""
+
+msgid "Geo|Re-verification interval"
+msgstr ""
+
+msgid "Geo|Recheck"
+msgstr ""
+
+msgid "Geo|Recheck all projects"
+msgstr ""
+
+msgid "Geo|Redownload"
+msgstr ""
+
+msgid "Geo|Remove"
+msgstr ""
+
+msgid "Geo|Repository sync capacity"
+msgstr ""
+
+msgid "Geo|Resync"
+msgstr ""
+
+msgid "Geo|Resync all projects"
+msgstr ""
+
+msgid "Geo|Retry count"
+msgstr ""
+
+msgid "Geo|Select groups to replicate."
+msgstr ""
+
+msgid "Geo|Selective synchronization"
+msgstr ""
+
+msgid "Geo|Shards to synchronize"
+msgstr ""
+
+msgid "Geo|Status"
+msgstr ""
+
+msgid "Geo|Sync"
+msgstr ""
+
+msgid "Geo|Synced"
+msgstr ""
+
+msgid "Geo|Synced at"
+msgstr ""
+
+msgid "Geo|Synchronization failed - %{error}"
+msgstr ""
+
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
+msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
+msgid "Geo|Tracking entry will be removed. Are you sure?"
+msgstr ""
+
+msgid "Geo|URL"
+msgstr ""
+
+msgid "Geo|Unknown state"
+msgstr ""
+
+msgid "Geo|Verification capacity"
+msgstr ""
+
+msgid "Geo|Verification failed - %{error}"
+msgstr ""
+
+msgid "Geo|Waiting for scheduler"
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}."
+msgstr ""
+
+msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
+msgstr ""
+
+msgid "Geo|misconfigured"
+msgstr ""
+
+msgid "Geo|primary"
+msgstr ""
+
+msgid "Geo|secondary"
+msgstr ""
+
+msgid "Get a free instance review"
+msgstr ""
+
+msgid "Get started with error tracking"
+msgstr ""
+
+msgid "Getting started with releases"
+msgstr ""
+
+msgid "Git"
+msgstr ""
+
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
+msgid "Git global setup"
+msgstr ""
+
+msgid "Git repository URL"
+msgstr ""
+
+msgid "Git revision"
+msgstr ""
+
+msgid "Git strategy for pipelines"
+msgstr ""
+
+msgid "Git version"
+msgstr ""
+
+msgid "GitHub import"
+msgstr ""
+
+msgid "GitLab CI Linter has been moved"
+msgstr ""
+
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
+msgid "GitLab Geo"
+msgstr ""
+
+msgid "GitLab Group Runners can execute code for all the projects in this group."
+msgstr ""
+
+msgid "GitLab Import"
+msgstr ""
+
+msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
+msgstr ""
+
+msgid "GitLab User"
+msgstr ""
+
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
+msgid "GitLab metadata URL"
+msgstr ""
+
+msgid "GitLab project export"
+msgstr ""
+
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
+msgid "GitLab single sign on URL"
+msgstr ""
+
+msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "GitLab.com import"
+msgstr ""
+
+msgid "GitLab’s issue tracker"
+msgstr ""
+
+msgid "Gitaly"
+msgstr ""
+
+msgid "Gitaly Servers"
+msgstr ""
+
+msgid "Gitaly|Address"
+msgstr ""
+
+msgid "Gitea Host URL"
+msgstr ""
+
+msgid "Gitea Import"
+msgstr ""
+
+msgid "Given access %{time_ago}"
+msgstr ""
+
+msgid "Go Back"
+msgstr ""
+
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
+msgid "Go back"
+msgstr ""
+
+msgid "Go full screen"
+msgstr ""
+
+msgid "Go to"
+msgstr ""
+
+msgid "Go to %{link_to_google_takeout}."
+msgstr ""
+
+msgid "Go to project"
+msgstr ""
+
+msgid "Google Code import"
+msgstr ""
+
+msgid "Google Takeout"
+msgstr ""
+
+msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
+msgstr ""
+
+msgid "Got it!"
+msgstr ""
+
+msgid "Grant access"
+msgstr ""
+
+msgid "Graph"
+msgstr ""
+
+msgid "Gravatar enabled"
+msgstr ""
+
+msgid "Group"
+msgstr ""
+
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
+msgid "Group CI/CD settings"
+msgstr ""
+
+msgid "Group Git LFS status:"
+msgstr ""
+
+msgid "Group ID"
+msgstr ""
+
+msgid "Group ID: %{group_id}"
+msgstr ""
+
+msgid "Group Runners"
+msgstr ""
+
+msgid "Group SAML must be enabled to test"
+msgstr ""
+
+msgid "Group URL"
+msgstr ""
+
+msgid "Group avatar"
+msgstr ""
+
+msgid "Group description"
+msgstr ""
+
+msgid "Group description (optional)"
+msgstr ""
+
+msgid "Group details"
+msgstr ""
+
+msgid "Group info:"
+msgstr ""
+
+msgid "Group maintainers can register group runners in the %{link}"
+msgstr ""
+
+msgid "Group name"
+msgstr ""
+
+msgid "Group overview content"
+msgstr ""
+
+msgid "Group was successfully updated."
+msgstr ""
+
+msgid "Group:"
+msgstr ""
+
+msgid "Group: %{group_name}"
+msgstr ""
+
+msgid "GroupRoadmap|From %{dateWord}"
+msgstr ""
+
+msgid "GroupRoadmap|Something went wrong while fetching epics"
+msgstr ""
+
+msgid "GroupRoadmap|Sorry, no epics matched your search"
+msgstr ""
+
+msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
+msgstr ""
+
+msgid "GroupRoadmap|Until %{dateWord}"
+msgstr ""
+
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Custom project templates"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about group-level project templates."
+msgstr ""
+
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
+msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
+msgstr ""
+
+msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr ""
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
+msgstr ""
+
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
+msgstr ""
+
+msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
+msgstr ""
+
+msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
+msgstr ""
+
+msgid "Groups"
+msgstr ""
+
+msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
+msgstr ""
+
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "GroupsDropdown|Frequently visited"
+msgstr ""
+
+msgid "GroupsDropdown|Groups you visit often will appear here"
+msgstr ""
+
+msgid "GroupsDropdown|Loading groups"
+msgstr ""
+
+msgid "GroupsDropdown|Search your groups"
+msgstr ""
+
+msgid "GroupsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "GroupsDropdown|Sorry, no groups matched your search"
+msgstr ""
+
+msgid "GroupsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "GroupsEmptyState|A group is a collection of several projects."
+msgstr ""
+
+msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder."
+msgstr ""
+
+msgid "GroupsEmptyState|No groups found"
+msgstr ""
+
+msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
+msgstr ""
+
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
+msgid "GroupsTree|Create a project in this group."
+msgstr ""
+
+msgid "GroupsTree|Create a subgroup in this group."
+msgstr ""
+
+msgid "GroupsTree|Edit group"
+msgstr ""
+
+msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
+msgstr ""
+
+msgid "GroupsTree|Leave this group"
+msgstr ""
+
+msgid "GroupsTree|Loading groups"
+msgstr ""
+
+msgid "GroupsTree|No groups matched your search"
+msgstr ""
+
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
+msgstr ""
+
+msgid "Have your users email"
+msgstr ""
+
+msgid "Header logo was successfully removed."
+msgstr ""
+
+msgid "Header message"
+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 "Help"
+msgstr ""
+
+msgid "Help page"
+msgstr ""
+
+msgid "Help page text and support page url."
+msgstr ""
+
+msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
+msgstr ""
+
+msgid "Hide file browser"
+msgstr ""
+
+msgid "Hide host keys manual input"
+msgstr ""
+
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
+msgid "Hide payload"
+msgstr ""
+
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Hide values"
+msgstr ""
+
+msgid "Highest role:"
+msgstr ""
+
+msgid "History"
+msgstr ""
+
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
+msgid "Housekeeping successfully started"
+msgstr ""
+
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr ""
+
+msgid "I accept the %{terms_link}"
+msgstr ""
+
+msgid "I accept the|Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation."
+msgstr ""
+
+msgid "IDE|Back"
+msgstr ""
+
+msgid "IDE|Client side evaluation"
+msgstr ""
+
+msgid "IDE|Commit"
+msgstr ""
+
+msgid "IDE|Edit"
+msgstr ""
+
+msgid "IDE|Get started with Live Preview"
+msgstr ""
+
+msgid "IDE|Go to project"
+msgstr ""
+
+msgid "IDE|Live Preview"
+msgstr ""
+
+msgid "IDE|Open in file view"
+msgstr ""
+
+msgid "IDE|Preview your web application using Web IDE client-side evaluation."
+msgstr ""
+
+msgid "IDE|Refresh preview"
+msgstr ""
+
+msgid "IDE|Review"
+msgstr ""
+
+msgid "IP Address"
+msgstr ""
+
+msgid "Identifier"
+msgstr ""
+
+msgid "Identities"
+msgstr ""
+
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
+msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
+msgstr ""
+
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
+msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgstr ""
+
+msgid "If enabled"
+msgstr ""
+
+msgid "If enabled, access to projects will be validated on an external service using their classification label."
+msgstr ""
+
+msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
+msgstr ""
+
+msgid "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>."
+msgstr ""
+
+msgid "ImageDiffViewer|2-up"
+msgstr ""
+
+msgid "ImageDiffViewer|Onion skin"
+msgstr ""
+
+msgid "ImageDiffViewer|Swipe"
+msgstr ""
+
+msgid "Impersonation has been disabled"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "Import CSV"
+msgstr ""
+
+msgid "Import Projects from Gitea"
+msgstr ""
+
+msgid "Import all compatible projects"
+msgstr ""
+
+msgid "Import all projects"
+msgstr ""
+
+msgid "Import all repositories"
+msgstr ""
+
+msgid "Import an exported GitLab project"
+msgstr ""
+
+msgid "Import in progress"
+msgstr ""
+
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
+msgid "Import multiple repositories by uploading a manifest file."
+msgstr ""
+
+msgid "Import project"
+msgstr ""
+
+msgid "Import project members"
+msgstr ""
+
+msgid "Import projects from Bitbucket"
+msgstr ""
+
+msgid "Import projects from Bitbucket Server"
+msgstr ""
+
+msgid "Import projects from FogBugz"
+msgstr ""
+
+msgid "Import projects from GitLab.com"
+msgstr ""
+
+msgid "Import projects from Google Code"
+msgstr ""
+
+msgid "Import repositories from Bitbucket Server"
+msgstr ""
+
+msgid "Import repositories from GitHub"
+msgstr ""
+
+msgid "Import repository"
+msgstr ""
+
+msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
+msgstr ""
+
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportButtons|Connect repositories from"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
+msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
+msgstr ""
+
+msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
+msgstr ""
+
+msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
+msgstr ""
+
+msgid "In the next step, you'll be able to select the projects you want to import."
+msgstr ""
+
+msgid "Incidents"
+msgstr ""
+
+msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
+msgstr ""
+
+msgid "Include author name in notification email body"
+msgstr ""
+
+msgid "Include merge request description"
+msgstr ""
+
+msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
+msgstr ""
+
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
+msgid "Incompatible Project"
+msgstr ""
+
+msgid "Indicates whether this runner can pick jobs without tags"
+msgstr ""
+
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
+msgid "Inline"
+msgstr ""
+
+msgid "Input host keys manually"
+msgstr ""
+
+msgid "Input your repository URL"
+msgstr ""
+
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
+msgid "Insert suggestion"
+msgstr ""
+
+msgid "Insights"
+msgstr ""
+
+msgid "Install GitLab Runner"
+msgstr ""
+
+msgid "Install Runner on Kubernetes"
+msgstr ""
+
+msgid "Instance"
+msgid_plural "Instances"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Instance Statistics"
+msgstr ""
+
+msgid "Instance Statistics visibility"
+msgstr ""
+
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
+msgid "Instance license"
+msgstr ""
+
+msgid "Integrations"
+msgstr ""
+
+msgid "Integrations Settings"
+msgstr ""
+
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
+msgid "Internal"
+msgstr ""
+
+msgid "Internal - The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "Internal - The project can be accessed by any logged in user."
+msgstr ""
+
+msgid "Internal users"
+msgstr ""
+
+msgid "Interval Pattern"
+msgstr ""
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Introducing Your Conversational Development Index"
+msgstr ""
+
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr ""
+
+msgid "Invite"
+msgstr ""
+
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
+msgid "Invoke Count"
+msgstr ""
+
+msgid "Invoke Time"
+msgstr ""
+
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
+msgid "Issue"
+msgstr ""
+
+msgid "Issue Boards"
+msgstr ""
+
+msgid "Issue board focus mode"
+msgstr ""
+
+msgid "Issue events"
+msgstr ""
+
+msgid "Issue template (optional)"
+msgstr ""
+
+msgid "IssueBoards|Board"
+msgstr ""
+
+msgid "IssueBoards|Boards"
+msgstr ""
+
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
+msgid "Issues closed"
+msgstr ""
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
+
+msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues Created"
+msgstr ""
+
+msgid "IssuesAnalytics|Issues created per month"
+msgstr ""
+
+msgid "IssuesAnalytics|Last 12 months"
+msgstr ""
+
+msgid "IssuesAnalytics|Sorry, your filter produced no results"
+msgstr ""
+
+msgid "IssuesAnalytics|There are no issues for the projects in your group"
+msgstr ""
+
+msgid "IssuesAnalytics|To widen your search, change or remove filters in the filter bar above"
+msgstr ""
+
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
+msgid "It's you"
+msgstr ""
+
+msgid "Jaeger URL"
+msgstr ""
+
+msgid "Jaeger tracing"
+msgstr ""
+
+msgid "Jan"
+msgstr ""
+
+msgid "January"
+msgstr ""
+
+msgid "Job"
+msgstr ""
+
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
+msgid "Job has been erased"
+msgstr ""
+
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job traces and artifacts"
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
+msgid "Jobs"
+msgstr ""
+
+msgid "Job|Browse"
+msgstr ""
+
+msgid "Job|Complete Raw"
+msgstr ""
+
+msgid "Job|Download"
+msgstr ""
+
+msgid "Job|Erase job log"
+msgstr ""
+
+msgid "Job|Job artifacts"
+msgstr ""
+
+msgid "Job|Job has been erased"
+msgstr ""
+
+msgid "Job|Job has been erased by"
+msgstr ""
+
+msgid "Job|Keep"
+msgstr ""
+
+msgid "Job|Pipeline"
+msgstr ""
+
+msgid "Job|Scroll to bottom"
+msgstr ""
+
+msgid "Job|Scroll to top"
+msgstr ""
+
+msgid "Job|Show complete raw"
+msgstr ""
+
+msgid "Job|The artifacts were removed"
+msgstr ""
+
+msgid "Job|The artifacts will be removed"
+msgstr ""
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
+msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr ""
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
+msgid "Jul"
+msgstr ""
+
+msgid "July"
+msgstr ""
+
+msgid "Jun"
+msgstr ""
+
+msgid "June"
+msgstr ""
+
+msgid "Key (PEM)"
+msgstr ""
+
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes Clusters"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes configured"
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
+msgid "LDAP settings"
+msgstr ""
+
+msgid "LFS"
+msgstr ""
+
+msgid "LFS objects"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr ""
+
+msgid "LFSStatus|Enabled"
+msgstr ""
+
+msgid "Label"
+msgstr ""
+
+msgid "Label actions dropdown"
+msgstr ""
+
+msgid "Label lists show all issues with the selected label."
+msgstr ""
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
+msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|%{labelsString}, and %{remainingLabelCount} more"
+msgstr ""
+
+msgid "LabelSelect|Labels"
+msgstr ""
+
+msgid "Labels"
+msgstr ""
+
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
+msgid "Labels can be applied to issues and merge requests."
+msgstr ""
+
+msgid "Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>"
+msgstr ""
+
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Large File Storage"
+msgstr ""
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Last Pipeline"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid "Last commit"
+msgstr ""
+
+msgid "Last contact"
+msgstr ""
+
+msgid "Last edited %{date}"
+msgstr ""
+
+msgid "Last edited by %{name}"
+msgstr ""
+
+msgid "Last reply by"
+msgstr ""
+
+msgid "Last seen"
+msgstr ""
+
+msgid "Last update"
+msgstr ""
+
+msgid "Last updated"
+msgstr ""
+
+msgid "LastPushEvent|You pushed to"
+msgstr ""
+
+msgid "LastPushEvent|at"
+msgstr ""
+
+msgid "Latest changes"
+msgstr ""
+
+msgid "Latest pipeline for this branch"
+msgstr ""
+
+msgid "Lead"
+msgstr ""
+
+msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
+msgstr ""
+
+msgid "Learn more"
+msgstr ""
+
+msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
+msgstr ""
+
+msgid "Learn more about Auto DevOps"
+msgstr ""
+
+msgid "Learn more about Kubernetes"
+msgstr ""
+
+msgid "Learn more about Web Terminal"
+msgstr ""
+
+msgid "Learn more about approvals."
+msgstr ""
+
+msgid "Learn more about custom project templates"
+msgstr ""
+
+msgid "Learn more about group-level project templates"
+msgstr ""
+
+msgid "Learn more about signing commits"
+msgstr ""
+
+msgid "Learn more in the"
+msgstr ""
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr ""
+
+msgid "Leave"
+msgstr ""
+
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
+msgid "Leave group"
+msgstr ""
+
+msgid "Leave project"
+msgstr ""
+
+msgid "Leave the \"File type\" and \"Delivery method\" options on their default values."
+msgstr ""
+
+msgid "License"
+msgstr ""
+
+msgid "LicenseManagement|Add a license"
+msgstr ""
+
+msgid "LicenseManagement|Add licenses manually to approve or blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Approve"
+msgstr ""
+
+msgid "LicenseManagement|Approve license"
+msgstr ""
+
+msgid "LicenseManagement|Approve license?"
+msgstr ""
+
+msgid "LicenseManagement|Approved"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license"
+msgstr ""
+
+msgid "LicenseManagement|Blacklist license?"
+msgstr ""
+
+msgid "LicenseManagement|Blacklisted"
+msgstr ""
+
+msgid "LicenseManagement|Cancel"
+msgstr ""
+
+msgid "LicenseManagement|License"
+msgstr ""
+
+msgid "LicenseManagement|License Management"
+msgstr ""
+
+msgid "LicenseManagement|License details"
+msgstr ""
+
+msgid "LicenseManagement|License name"
+msgstr ""
+
+msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
+msgstr ""
+
+msgid "LicenseManagement|Packages"
+msgstr ""
+
+msgid "LicenseManagement|Remove license"
+msgstr ""
+
+msgid "LicenseManagement|Remove license?"
+msgstr ""
+
+msgid "LicenseManagement|Submit"
+msgstr ""
+
+msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
+msgstr ""
+
+msgid "LicenseManagement|This license already exists in this project."
+msgstr ""
+
+msgid "LicenseManagement|URL"
+msgstr ""
+
+msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
+msgstr ""
+
+msgid "Licensed to"
+msgstr ""
+
+msgid "Licenses"
+msgstr ""
+
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "LinkedIn"
+msgstr ""
+
+msgid "List"
+msgstr ""
+
+msgid "List Your Gitea Repositories"
+msgstr ""
+
+msgid "List available repositories"
+msgstr ""
+
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
+msgid "List view"
+msgstr ""
+
+msgid "List your Bitbucket Server repositories"
+msgstr ""
+
+msgid "List your GitHub repositories"
+msgstr ""
+
+msgid "Live preview"
+msgstr ""
+
+msgid "Loading contribution stats for group members"
+msgstr ""
+
+msgid "Loading the GitLab IDE..."
+msgstr ""
+
+msgid "Loading..."
+msgstr ""
+
+msgid "Loading…"
+msgstr ""
+
+msgid "Localization"
+msgstr ""
+
+msgid "Lock"
+msgstr ""
+
+msgid "Lock %{issuableDisplayName}"
+msgstr ""
+
+msgid "Lock not found"
+msgstr ""
+
+msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment."
+msgstr ""
+
+msgid "Lock to current projects"
+msgstr ""
+
+msgid "Locked"
+msgstr ""
+
+msgid "Locked Files"
+msgstr ""
+
+msgid "Locked to current projects"
+msgstr ""
+
+msgid "Locks give the ability to lock specific file or folder."
+msgstr ""
+
+msgid "Login with smartcard"
+msgstr ""
+
+msgid "Logo was successfully removed."
+msgstr ""
+
+msgid "Logs"
+msgstr ""
+
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
+msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
+msgstr ""
+
+msgid "Make sure you're logged into the account that owns the projects you'd like to import."
+msgstr ""
+
+msgid "Manage"
+msgstr ""
+
+msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
+msgstr ""
+
+msgid "Manage Web IDE features"
+msgstr ""
+
+msgid "Manage access"
+msgstr ""
+
+msgid "Manage all notifications"
+msgstr ""
+
+msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage applications that you've authorized to use your account."
+msgstr ""
+
+msgid "Manage group labels"
+msgstr ""
+
+msgid "Manage labels"
+msgstr ""
+
+msgid "Manage project labels"
+msgstr ""
+
+msgid "Manage two-factor authentication"
+msgstr ""
+
+msgid "Manifest"
+msgstr ""
+
+msgid "Manifest file import"
+msgstr ""
+
+msgid "Manual job"
+msgstr ""
+
+msgid "Map a FogBugz account ID to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a GitLab user"
+msgstr ""
+
+msgid "Map a Google Code user to a full email address"
+msgstr ""
+
+msgid "Map a Google Code user to a full name"
+msgstr ""
+
+msgid "Mar"
+msgstr ""
+
+msgid "March"
+msgstr ""
+
+msgid "Mark todo as done"
+msgstr ""
+
+msgid "Markdown"
+msgstr ""
+
+msgid "Markdown Help"
+msgstr ""
+
+msgid "Markdown enabled"
+msgstr ""
+
+msgid "Maven Metadata"
+msgstr ""
+
+msgid "Max access level"
+msgstr ""
+
+msgid "Max seats used"
+msgstr ""
+
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
+msgid "Maximum capacity"
+msgstr ""
+
+msgid "Maximum delay (Minutes)"
+msgstr ""
+
+msgid "Maximum job timeout"
+msgstr ""
+
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
+
+msgid "Maximum push size (MB)"
+msgstr ""
+
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
+
+msgid "May"
+msgstr ""
+
+msgid "Median"
+msgstr ""
+
+msgid "Member lock"
+msgstr ""
+
+msgid "Member since %{date}"
+msgstr ""
+
+msgid "Members"
+msgstr ""
+
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
+msgid "Merge Request"
+msgstr ""
+
+msgid "Merge Request Approvals"
+msgstr ""
+
+msgid "Merge Requests"
+msgstr ""
+
+msgid "Merge Requests created"
+msgstr ""
+
+msgid "Merge commit"
+msgstr ""
+
+msgid "Merge commit message"
+msgstr ""
+
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
+msgid "Merge events"
+msgstr ""
+
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
+msgid "Merge request"
+msgstr ""
+
+msgid "Merge request approvals"
+msgstr ""
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
+msgid "Merge requests"
+msgstr ""
+
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
+msgid "MergeRequests|Add a reply"
+msgstr ""
+
+msgid "MergeRequests|An error occurred while saving the draft comment."
+msgstr ""
+
+msgid "MergeRequests|Discussion stays resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion stays unresolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be resolved"
+msgstr ""
+
+msgid "MergeRequests|Discussion will be unresolved"
+msgstr ""
+
+msgid "MergeRequests|Jump to next unresolved discussion"
+msgstr ""
+
+msgid "MergeRequests|Reply..."
+msgstr ""
+
+msgid "MergeRequests|Resolve this discussion in a new issue"
+msgstr ""
+
+msgid "MergeRequests|Saving the comment failed"
+msgstr ""
+
+msgid "MergeRequests|Toggle comments for this file"
+msgstr ""
+
+msgid "MergeRequests|View file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|View replaced file @ %{commitId}"
+msgstr ""
+
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
+msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
+msgstr ""
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
+msgid "MergeRequest|Filter files"
+msgstr ""
+
+msgid "MergeRequest|No files found"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Metrics"
+msgstr ""
+
+msgid "Metrics - Influx"
+msgstr ""
+
+msgid "Metrics - Prometheus"
+msgstr ""
+
+msgid "Metrics and profiling"
+msgstr ""
+
+msgid "Metrics for environment"
+msgstr ""
+
+msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
+msgstr ""
+
+msgid "Metrics|Create metric"
+msgstr ""
+
+msgid "Metrics|Delete metric"
+msgstr ""
+
+msgid "Metrics|Delete metric?"
+msgstr ""
+
+msgid "Metrics|Edit metric"
+msgstr ""
+
+msgid "Metrics|Environment"
+msgstr ""
+
+msgid "Metrics|For grouping similar metrics"
+msgstr ""
+
+msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
+msgstr ""
+
+msgid "Metrics|Learn about environments"
+msgstr ""
+
+msgid "Metrics|Legend label (optional)"
+msgstr ""
+
+msgid "Metrics|Must be a valid PromQL query."
+msgstr ""
+
+msgid "Metrics|New metric"
+msgstr ""
+
+msgid "Metrics|No deployed environments"
+msgstr ""
+
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
+msgid "Metrics|PromQL query is valid"
+msgstr ""
+
+msgid "Metrics|Prometheus Query Documentation"
+msgstr ""
+
+msgid "Metrics|Show last"
+msgstr ""
+
+msgid "Metrics|There was an error fetching the environments data, please try again"
+msgstr ""
+
+msgid "Metrics|There was an error getting deployment information."
+msgstr ""
+
+msgid "Metrics|There was an error getting environments information."
+msgstr ""
+
+msgid "Metrics|There was an error trying to validate your query"
+msgstr ""
+
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
+msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unexpected metrics data response from prometheus endpoint"
+msgstr ""
+
+msgid "Metrics|Unit label"
+msgstr ""
+
+msgid "Metrics|Used as a title for the chart"
+msgstr ""
+
+msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
+msgstr ""
+
+msgid "Metrics|Y-axis label"
+msgstr ""
+
+msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
+msgstr ""
+
+msgid "Metrics|e.g. Throughput"
+msgstr ""
+
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
+msgid "Milestone"
+msgstr ""
+
+msgid "Milestone lists not available with your current license"
+msgstr ""
+
+msgid "Milestone lists show all issues from the selected milestone."
+msgstr ""
+
+msgid "Milestones"
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
+msgstr ""
+
+msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
+msgstr ""
+
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
+msgid "Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}. Existing project milestones with the same name will be merged. "
+msgstr ""
+
+msgid "Milestones|This action cannot be reversed."
+msgstr ""
+
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
+msgid "Mirror a repository"
+msgstr ""
+
+msgid "Mirror direction"
+msgstr ""
+
+msgid "Mirror repository"
+msgstr ""
+
+msgid "Mirror user"
+msgstr ""
+
+msgid "Mirrored repositories"
+msgstr ""
+
+msgid "Mirroring repositories"
+msgstr ""
+
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr ""
+
+msgid "Modal|Cancel"
+msgstr ""
+
+msgid "Modal|Close"
+msgstr ""
+
+msgid "Modify commit message"
+msgstr ""
+
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
+msgid "Monitor your errors by integrating with Sentry"
+msgstr ""
+
+msgid "Monitoring"
+msgstr ""
+
+msgid "Months"
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "More actions"
+msgstr ""
+
+msgid "More info"
+msgstr ""
+
+msgid "More information"
+msgstr ""
+
+msgid "More information is available|here"
+msgstr ""
+
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
+msgid "Most stars"
+msgstr ""
+
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
+msgid "Move"
+msgstr ""
+
+msgid "Move issue"
+msgstr ""
+
+msgid "Multiple issue boards"
+msgstr ""
+
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name new label"
+msgstr ""
+
+msgid "Name your individual key via a title"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
+msgid "Naming, visibility"
+msgstr ""
+
+msgid "Nav|Help"
+msgstr ""
+
+msgid "Nav|Home"
+msgstr ""
+
+msgid "Nav|Sign In / Register"
+msgstr ""
+
+msgid "Nav|Sign out and sign in with a different account"
+msgstr ""
+
+msgid "Need help?"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
+msgid "Never"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "New Application"
+msgstr ""
+
+msgid "New Environment"
+msgstr ""
+
+msgid "New Group"
+msgstr ""
+
+msgid "New Identity"
+msgstr ""
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "New Label"
+msgstr ""
+
+msgid "New Milestone"
+msgstr ""
+
+msgid "New Pages Domain"
+msgstr ""
+
+msgid "New Password"
+msgstr ""
+
+msgid "New Pipeline Schedule"
+msgstr ""
+
+msgid "New Snippet"
+msgstr ""
+
+msgid "New branch"
+msgstr ""
+
+msgid "New branch unavailable"
+msgstr ""
+
+msgid "New deploy key"
+msgstr ""
+
+msgid "New directory"
+msgstr ""
+
+msgid "New environment"
+msgstr ""
+
+msgid "New epic"
+msgstr ""
+
+msgid "New file"
+msgstr ""
+
+msgid "New group"
+msgstr ""
+
+msgid "New health check access token has been generated!"
+msgstr ""
+
+msgid "New identity"
+msgstr ""
+
+msgid "New issue"
+msgstr ""
+
+msgid "New label"
+msgstr ""
+
+msgid "New merge request"
+msgstr ""
+
+msgid "New milestone"
+msgstr ""
+
+msgid "New pipelines will cancel older, pending pipelines on the same branch"
+msgstr ""
+
+msgid "New project"
+msgstr ""
+
+msgid "New runners registration token has been generated!"
+msgstr ""
+
+msgid "New schedule"
+msgstr ""
+
+msgid "New snippet"
+msgstr ""
+
+msgid "New subgroup"
+msgstr ""
+
+msgid "New tag"
+msgstr ""
+
+msgid "New users set to external"
+msgstr ""
+
+msgid "New..."
+msgstr ""
+
+msgid "Newly registered users will by default be external"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
+msgid "No Label"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
+msgid "No activities found"
+msgstr ""
+
+msgid "No branches found"
+msgstr ""
+
+msgid "No changes"
+msgstr ""
+
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No container images stored for this project. Add one by following the instructions above."
+msgstr ""
+
+msgid "No contributions were found"
+msgstr ""
+
+msgid "No credit card required."
+msgstr ""
+
+msgid "No details available"
+msgstr ""
+
+msgid "No due date"
+msgstr ""
+
+msgid "No errors to display."
+msgstr ""
+
+msgid "No estimate or time spent"
+msgstr ""
+
+msgid "No file chosen"
+msgstr ""
+
+msgid "No file selected"
+msgstr ""
+
+msgid "No files found."
+msgstr ""
+
+msgid "No issues for the selected time period."
+msgstr ""
+
+msgid "No job trace"
+msgstr ""
+
+msgid "No labels with such name or description"
+msgstr ""
+
+msgid "No license. All rights reserved"
+msgstr ""
+
+msgid "No licenses found."
+msgstr ""
+
+msgid "No matching results"
+msgstr ""
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "No merge requests for the selected time period."
+msgstr ""
+
+msgid "No merge requests found"
+msgstr ""
+
+msgid "No messages were logged"
+msgstr ""
+
+msgid "No milestones to show"
+msgstr ""
+
+msgid "No other labels with such name or description"
+msgstr ""
+
+msgid "No preview for this file type"
+msgstr ""
+
+msgid "No prioritised labels with such name or description"
+msgstr ""
+
+msgid "No public groups"
+msgstr ""
+
+msgid "No pushes for the selected time period."
+msgstr ""
+
+msgid "No repository"
+msgstr ""
+
+msgid "No runners found"
+msgstr ""
+
+msgid "No schedules"
+msgstr ""
+
+msgid "No start date"
+msgstr ""
+
+msgid "No, directly import the existing email addresses and usernames."
+msgstr ""
+
+msgid "Nodes"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not available for private projects"
+msgstr ""
+
+msgid "Not available for protected branches"
+msgstr ""
+
+msgid "Not confidential"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Not found."
+msgstr ""
+
+msgid "Not now"
+msgstr ""
+
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
+
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
+msgstr ""
+
+msgid "Notes|Are you sure you want to cancel creating this comment?"
+msgstr ""
+
+msgid "Notes|Collapse replies"
+msgstr ""
+
+msgid "Notes|Show all activity"
+msgstr ""
+
+msgid "Notes|Show comments only"
+msgstr ""
+
+msgid "Notes|Show history only"
+msgstr ""
+
+msgid "Notification events"
+msgstr ""
+
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+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 epic"
+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 ""
+
+msgid "Notifications"
+msgstr ""
+
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
+msgid "Nov"
+msgstr ""
+
+msgid "November"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
+msgid "Oct"
+msgstr ""
+
+msgid "October"
+msgstr ""
+
+msgid "OfSearchInADropdown|Filter"
+msgstr ""
+
+msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
+msgstr ""
+
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
+msgid "One more item"
+msgid_plural "%d more items"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
+msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
+msgstr ""
+
+msgid "Only admins"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
+msgid "Only mirror protected branches"
+msgstr ""
+
+msgid "Only policy:"
+msgstr ""
+
+msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
+msgstr ""
+
+msgid "Only project members can comment."
+msgstr ""
+
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
+msgid "Oops, are you sure?"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open Documentation"
+msgstr ""
+
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
+msgid "Open in Xcode"
+msgstr ""
+
+msgid "Open projects"
+msgstr ""
+
+msgid "Open raw"
+msgstr ""
+
+msgid "Open sidebar"
+msgstr ""
+
+msgid "Open source software to collaborate on code"
+msgstr ""
+
+msgid "Opened"
+msgstr ""
+
+msgid "Opened MR"
+msgstr ""
+
+msgid "Opened issues"
+msgstr ""
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr ""
+
+msgid "Opens in a new window"
+msgstr ""
+
+msgid "Operations"
+msgstr ""
+
+msgid "Operations Dashboard"
+msgstr ""
+
+msgid "Operations Settings"
+msgstr ""
+
+msgid "OperationsDashboard|Add a project to the dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
+msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
+msgstr ""
+
+msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab."
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Or you can choose one of the suggested colors below"
+msgstr ""
+
+msgid "Other Labels"
+msgstr ""
+
+msgid "Other information"
+msgstr ""
+
+msgid "Outbound requests"
+msgstr ""
+
+msgid "Overview"
+msgstr ""
+
+msgid "Overwrite diverged branches"
+msgstr ""
+
+msgid "Owner"
+msgstr ""
+
+msgid "Package information"
+msgstr ""
+
+msgid "Package was removed"
+msgstr ""
+
+msgid "Packages"
+msgstr ""
+
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
+msgid "Pages"
+msgstr ""
+
+msgid "Pages Domain"
+msgstr ""
+
+msgid "Pages Domains"
+msgstr ""
+
+msgid "Pagination|Last »"
+msgstr ""
+
+msgid "Pagination|Next"
+msgstr ""
+
+msgid "Pagination|Prev"
+msgstr ""
+
+msgid "Pagination|« First"
+msgstr ""
+
+msgid "Parameter"
+msgstr ""
+
+msgid "Part of merge request changes"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Password authentication is unavailable."
+msgstr ""
+
+msgid "Past due"
+msgstr ""
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
+msgid "Paste epic link"
+msgstr ""
+
+msgid "Paste issue link"
+msgstr ""
+
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
+msgstr ""
+
+msgid "Path"
+msgstr ""
+
+msgid "Path, transfer, remove"
+msgstr ""
+
+msgid "Path:"
+msgstr ""
+
+msgid "Pause"
+msgstr ""
+
+msgid "Pause replication"
+msgstr ""
+
+msgid "Paused Runners don't accept new jobs"
+msgstr ""
+
+msgid "Pending"
+msgstr ""
+
+msgid "People without permission will never get a notification and won't be able to comment."
+msgstr ""
+
+msgid "Perform advanced options such as changing path, transferring, or removing the group."
+msgstr ""
+
+msgid "Performance optimization"
+msgstr ""
+
+msgid "Permissions"
+msgstr ""
+
+msgid "Permissions Help"
+msgstr ""
+
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
+msgid "Personal Access Token"
+msgstr ""
+
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
+msgid "Pick a name"
+msgstr ""
+
+msgid "Pipeline"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr ""
+
+msgid "Pipeline Schedules"
+msgstr ""
+
+msgid "Pipeline minutes quota"
+msgstr ""
+
+msgid "Pipeline quota"
+msgstr ""
+
+msgid "Pipeline triggers"
+msgstr ""
+
+msgid "Pipeline: %{status}"
+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|Next Run"
+msgstr ""
+
+msgid "PipelineSchedules|None"
+msgstr ""
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr ""
+
+msgid "PipelineSchedules|Take ownership"
+msgstr ""
+
+msgid "PipelineSchedules|Target"
+msgstr ""
+
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipelines for last month"
+msgstr ""
+
+msgid "Pipelines for last week"
+msgstr ""
+
+msgid "Pipelines for last year"
+msgstr ""
+
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|CI Lint"
+msgstr ""
+
+msgid "Pipelines|Clear Runner Caches"
+msgstr ""
+
+msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment."
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
+msgid "Pipelines|Loading Pipelines"
+msgstr ""
+
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
+msgid "Pipelines|Run Pipeline"
+msgstr ""
+
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
+msgid "Pipelines|There are currently no %{scope} pipelines."
+msgstr ""
+
+msgid "Pipelines|There are currently no pipelines."
+msgstr ""
+
+msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
+msgstr ""
+
+msgid "Pipelines|This project is not currently set up to run pipelines."
+msgstr ""
+
+msgid "Pipeline|Commit"
+msgstr ""
+
+msgid "Pipeline|Coverage"
+msgstr ""
+
+msgid "Pipeline|Duration"
+msgstr ""
+
+msgid "Pipeline|Existing branch name or tag"
+msgstr ""
+
+msgid "Pipeline|Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run Pipeline"
+msgstr ""
+
+msgid "Pipeline|Run for"
+msgstr ""
+
+msgid "Pipeline|Search branches"
+msgstr ""
+
+msgid "Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default."
+msgstr ""
+
+msgid "Pipeline|Stages"
+msgstr ""
+
+msgid "Pipeline|Status"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline"
+msgstr ""
+
+msgid "Pipeline|Stop pipeline #%{pipelineId}?"
+msgstr ""
+
+msgid "Pipeline|Variables"
+msgstr ""
+
+msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr ""
+
+msgid "Pipeline|with stages"
+msgstr ""
+
+msgid "Plain diff"
+msgstr ""
+
+msgid "PlantUML"
+msgstr ""
+
+msgid "Play"
+msgstr ""
+
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
+msgid "Please accept the Terms of Service before continuing."
+msgstr ""
+
+msgid "Please choose a group URL with no special characters."
+msgstr ""
+
+msgid "Please complete your profile with email address"
+msgstr ""
+
+msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
+msgstr ""
+
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
+msgid "Please fill in a descriptive name for your group."
+msgstr ""
+
+msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr ""
+
+msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
+msgstr ""
+
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
+msgid "Please select at least one filter to see results"
+msgstr ""
+
+msgid "Please set a new password before proceeding."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
+msgid "Please try again"
+msgstr ""
+
+msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
+msgstr ""
+
+msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgstr ""
+
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "Please wait while we connect to your repository. Refresh at will."
+msgstr ""
+
+msgid "Please wait while we import the repository for you. Refresh at will."
+msgstr ""
+
+msgid "Preferences"
+msgstr ""
+
+msgid "Preferences saved."
+msgstr ""
+
+msgid "Preferences|Navigation theme"
+msgstr ""
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
+msgid "Press Enter or click to search"
+msgstr ""
+
+msgid "Prevent adding new members to project membership within this group"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Preview changes"
+msgstr ""
+
+msgid "Preview payload"
+msgstr ""
+
+msgid "Primary"
+msgstr ""
+
+msgid "Prioritize"
+msgstr ""
+
+msgid "Prioritize label"
+msgstr ""
+
+msgid "Prioritized Labels"
+msgstr ""
+
+msgid "Prioritized label"
+msgstr ""
+
+msgid "Private"
+msgstr ""
+
+msgid "Private - Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Private - The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "Private group(s)"
+msgstr ""
+
+msgid "Private projects can be created in your personal namespace with:"
+msgstr ""
+
+msgid "Profile"
+msgstr ""
+
+msgid "Profile Settings"
+msgstr ""
+
+msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
+msgstr ""
+
+msgid "Profiles|@username"
+msgstr ""
+
+msgid "Profiles|Account scheduled for removal."
+msgstr ""
+
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
+msgid "Profiles|Add key"
+msgstr ""
+
+msgid "Profiles|Add status emoji"
+msgstr ""
+
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
+msgid "Profiles|Change username"
+msgstr ""
+
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
+msgstr ""
+
+msgid "Profiles|Clear status"
+msgstr ""
+
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
+msgid "Profiles|Current path: %{path}"
+msgstr ""
+
+msgid "Profiles|Current status"
+msgstr ""
+
+msgid "Profiles|Delete Account"
+msgstr ""
+
+msgid "Profiles|Delete account"
+msgstr ""
+
+msgid "Profiles|Delete your account?"
+msgstr ""
+
+msgid "Profiles|Deleting an account has the following effects:"
+msgstr ""
+
+msgid "Profiles|Disconnect"
+msgstr ""
+
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
+msgid "Profiles|Invalid password"
+msgstr ""
+
+msgid "Profiles|Invalid username"
+msgstr ""
+
+msgid "Profiles|Learn more"
+msgstr ""
+
+msgid "Profiles|Made a private contribution"
+msgstr ""
+
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
+msgid "Profiles|Path"
+msgstr ""
+
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Social sign-in"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
+msgstr ""
+
+msgid "Profiles|This email will be displayed on your public profile"
+msgstr ""
+
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
+
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile"
+msgstr ""
+
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
+msgid "Profiles|Type your %{confirmationValue} to confirm:"
+msgstr ""
+
+msgid "Profiles|Typically starts with \"ssh-rsa …\""
+msgstr ""
+
+msgid "Profiles|Update profile settings"
+msgstr ""
+
+msgid "Profiles|Update username"
+msgstr ""
+
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
+msgid "Profiles|Use a private email - %{email}"
+msgstr ""
+
+msgid "Profiles|Username change failed - %{message}"
+msgstr ""
+
+msgid "Profiles|Username successfully changed"
+msgstr ""
+
+msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
+msgstr ""
+
+msgid "Profiles|What's your status?"
+msgstr ""
+
+msgid "Profiles|Who you represent or work for"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You don't have access to delete this user."
+msgstr ""
+
+msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
+msgstr ""
+
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
+msgid "Profiles|Your account is currently an owner in these groups:"
+msgstr ""
+
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
+msgstr ""
+
+msgid "Profiles|Your status"
+msgstr ""
+
+msgid "Profiles|e.g. My MacBook key"
+msgstr ""
+
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
+msgid "Profiles|your account"
+msgstr ""
+
+msgid "Profiling - Performance bar"
+msgstr ""
+
+msgid "Programming languages used in this repository"
+msgstr ""
+
+msgid "Progress"
+msgstr ""
+
+msgid "Project"
+msgstr ""
+
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
+msgid "Project '%{project_name}' is in the process of being deleted."
+msgstr ""
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr ""
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr ""
+
+msgid "Project Badges"
+msgstr ""
+
+msgid "Project ID"
+msgstr ""
+
+msgid "Project URL"
+msgstr ""
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+
+msgid "Project and wiki repositories"
+msgstr ""
+
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
+msgid "Project details"
+msgstr ""
+
+msgid "Project export could not be deleted."
+msgstr ""
+
+msgid "Project export enabled"
+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 has too many %{label_for_message} to search"
+msgstr ""
+
+msgid "Project members"
+msgstr ""
+
+msgid "Project name"
+msgstr ""
+
+msgid "Project slug"
+msgstr ""
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project:"
+msgstr ""
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr ""
+
+msgid "ProjectCreationLevel|Allowed to create projects"
+msgstr ""
+
+msgid "ProjectCreationLevel|Default project creation protection"
+msgstr ""
+
+msgid "ProjectCreationLevel|Developers + Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|Maintainers"
+msgstr ""
+
+msgid "ProjectCreationLevel|No one"
+msgstr ""
+
+msgid "ProjectFileTree|Name"
+msgstr ""
+
+msgid "ProjectLastActivity|Never"
+msgstr ""
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectOverview|Fork"
+msgstr ""
+
+msgid "ProjectOverview|Forks"
+msgstr ""
+
+msgid "ProjectOverview|Go to your fork"
+msgstr ""
+
+msgid "ProjectOverview|Star"
+msgstr ""
+
+msgid "ProjectOverview|Unstar"
+msgstr ""
+
+msgid "ProjectOverview|You have reached your project limit"
+msgstr ""
+
+msgid "ProjectOverview|You must sign in to star a project"
+msgstr ""
+
+msgid "ProjectPage|Project ID: %{project_id}"
+msgstr ""
+
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Contact an admin to change this setting."
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Failed to protect the tag"
+msgstr ""
+
+msgid "ProjectSettings|Failed to update tag!"
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
+msgid "ProjectSettings|Only signed commits can be pushed to this repository."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project."
+msgstr ""
+
+msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin."
+msgstr ""
+
+msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
+msgstr ""
+
+msgid "Projects"
+msgstr ""
+
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
+msgid "Projects shared with %{group_name}"
+msgstr ""
+
+msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
+msgstr ""
+
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
+msgid "ProjectsDropdown|Frequently visited"
+msgstr ""
+
+msgid "ProjectsDropdown|Loading projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Projects you visit often will appear here"
+msgstr ""
+
+msgid "ProjectsDropdown|Search your projects"
+msgstr ""
+
+msgid "ProjectsDropdown|Something went wrong on our end."
+msgstr ""
+
+msgid "ProjectsDropdown|Sorry, no projects matched your search"
+msgstr ""
+
+msgid "ProjectsDropdown|This feature requires browser localStorage support"
+msgstr ""
+
+msgid "PrometheusAlerts|Add alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Edit alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error creating alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error deleting alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error fetching alert"
+msgstr ""
+
+msgid "PrometheusAlerts|Error saving alert"
+msgstr ""
+
+msgid "PrometheusAlerts|No alert set"
+msgstr ""
+
+msgid "PrometheusAlerts|Operator"
+msgstr ""
+
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
+msgid "PrometheusAlerts|Threshold"
+msgstr ""
+
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
+msgid "PrometheusService|Active"
+msgstr ""
+
+msgid "PrometheusService|Auto configuration"
+msgstr ""
+
+msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments"
+msgstr ""
+
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
+msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
+msgstr ""
+
+msgid "PrometheusService|Custom metrics"
+msgstr ""
+
+msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
+msgstr ""
+
+msgid "PrometheusService|Finding and configuring metrics..."
+msgstr ""
+
+msgid "PrometheusService|Finding custom metrics..."
+msgstr ""
+
+msgid "PrometheusService|Install Prometheus on clusters"
+msgstr ""
+
+msgid "PrometheusService|Manage clusters"
+msgstr ""
+
+msgid "PrometheusService|Manual configuration"
+msgstr ""
+
+msgid "PrometheusService|Metrics"
+msgstr ""
+
+msgid "PrometheusService|Missing environment variable"
+msgstr ""
+
+msgid "PrometheusService|More information"
+msgstr ""
+
+msgid "PrometheusService|New metric"
+msgstr ""
+
+msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
+msgstr ""
+
+msgid "PrometheusService|Prometheus is being automatically managed on your clusters"
+msgstr ""
+
+msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
+msgstr ""
+
+msgid "PrometheusService|Time-series monitoring service"
+msgstr ""
+
+msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters"
+msgstr ""
+
+msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
+msgstr ""
+
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote these project milestones into a group milestone."
+msgstr ""
+
+msgid "Promote to Group Milestone"
+msgstr ""
+
+msgid "Promote to group label"
+msgstr ""
+
+msgid "Promotions|Don't show me this again"
+msgstr ""
+
+msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
+msgstr ""
+
+msgid "Promotions|This feature is locked."
+msgstr ""
+
+msgid "Promotions|Upgrade plan"
+msgstr ""
+
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
+msgid "Protected"
+msgstr ""
+
+msgid "Protected Environments"
+msgstr ""
+
+msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?"
+msgstr ""
+
+msgid "ProtectedEnvironment|Allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Choose who is allowed to deploy"
+msgstr ""
+
+msgid "ProtectedEnvironment|Environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect Environments in order to restrict who can execute deployments."
+msgstr ""
+
+msgid "ProtectedEnvironment|Protect an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|Protected Environment (%{protected_environments_count})"
+msgstr ""
+
+msgid "ProtectedEnvironment|Select an environment"
+msgstr ""
+
+msgid "ProtectedEnvironment|There are currently no protected environments, protect an environment with the form above."
+msgstr ""
+
+msgid "ProtectedEnvironment|Unprotect"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment can't be unprotected"
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been protected."
+msgstr ""
+
+msgid "ProtectedEnvironment|Your environment has been unprotected"
+msgstr ""
+
+msgid "Protip:"
+msgstr ""
+
+msgid "Provider"
+msgstr ""
+
+msgid "Pseudonymizer data collection"
+msgstr ""
+
+msgid "Public"
+msgstr ""
+
+msgid "Public - The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "Public - The project can be accessed without any authentication."
+msgstr ""
+
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
+msgid "Public pipelines"
+msgstr ""
+
+msgid "Pull"
+msgstr ""
+
+msgid "Push"
+msgstr ""
+
+msgid "Push Rules"
+msgstr ""
+
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
+msgid "Push events"
+msgstr ""
+
+msgid "Push project from command line"
+msgstr ""
+
+msgid "Push to create a project"
+msgstr ""
+
+msgid "PushRule|Committer restriction"
+msgstr ""
+
+msgid "Pushed"
+msgstr ""
+
+msgid "Pushes"
+msgstr ""
+
+msgid "Quarters"
+msgstr ""
+
+msgid "Query"
+msgstr ""
+
+msgid "Quick actions can be used in the issues description and comment boxes."
+msgstr ""
+
+msgid "README"
+msgstr ""
+
+msgid "Rake Tasks Help"
+msgstr ""
+
+msgid "Read more"
+msgstr ""
+
+msgid "Read more about environments"
+msgstr ""
+
+msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
+msgstr ""
+
+msgid "Real-time features"
+msgstr ""
+
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
+msgid "Recent searches"
+msgstr ""
+
+msgid "Redirect to SAML provider to test configuration"
+msgstr ""
+
+msgid "Reference:"
+msgstr ""
+
+msgid "Refresh"
+msgstr ""
+
+msgid "Refreshing in a second to show the updated status..."
+msgid_plural "Refreshing in %d seconds to show the updated status..."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Regenerate key"
+msgstr ""
+
+msgid "Regex pattern"
+msgstr ""
+
+msgid "Register / Sign In"
+msgstr ""
+
+msgid "Register U2F device"
+msgstr ""
+
+msgid "Register and see your runners for this group."
+msgstr ""
+
+msgid "Register and see your runners for this project."
+msgstr ""
+
+msgid "Registration"
+msgstr ""
+
+msgid "Registry"
+msgstr ""
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr ""
+
+msgid "Related Merged Requests"
+msgstr ""
+
+msgid "Related merge requests"
+msgstr ""
+
+msgid "Releases"
+msgstr ""
+
+msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
+msgstr ""
+
+msgid "Remind later"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove Runner"
+msgstr ""
+
+msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
+msgstr ""
+
+msgid "Remove approver"
+msgstr ""
+
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
+msgid "Remove avatar"
+msgstr ""
+
+msgid "Remove fork relationship"
+msgstr ""
+
+msgid "Remove group"
+msgstr ""
+
+msgid "Remove priority"
+msgstr ""
+
+msgid "Remove project"
+msgstr ""
+
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
+msgid "Removing group will cause all child projects and resources to be removed."
+msgstr ""
+
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Rename file"
+msgstr ""
+
+msgid "Rename folder"
+msgstr ""
+
+msgid "Reopen epic"
+msgstr ""
+
+msgid "Reopen milestone"
+msgstr ""
+
+msgid "Repair authentication"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Reply to comment"
+msgstr ""
+
+msgid "Reply to this email directly or %{view_it_on_gitlab}."
+msgstr ""
+
+msgid "Repo by URL"
+msgstr ""
+
+msgid "Report abuse to GitLab"
+msgstr ""
+
+msgid "Reporting"
+msgstr ""
+
+msgid "Reports|%{failedString} and %{resolvedString}"
+msgstr ""
+
+msgid "Reports|Actions"
+msgstr ""
+
+msgid "Reports|Class"
+msgstr ""
+
+msgid "Reports|Confidence"
+msgstr ""
+
+msgid "Reports|Execution time"
+msgstr ""
+
+msgid "Reports|Failure"
+msgstr ""
+
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
+msgid "Reports|Severity"
+msgstr ""
+
+msgid "Reports|System output"
+msgstr ""
+
+msgid "Reports|Test summary"
+msgstr ""
+
+msgid "Reports|Test summary failed loading results"
+msgstr ""
+
+msgid "Reports|Test summary results are being parsed"
+msgstr ""
+
+msgid "Reports|Vulnerability"
+msgstr ""
+
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
+msgid "Reports|no changed test results"
+msgstr ""
+
+msgid "Repository"
+msgstr ""
+
+msgid "Repository Settings"
+msgstr ""
+
+msgid "Repository URL"
+msgstr ""
+
+msgid "Repository check was triggered."
+msgstr ""
+
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
+msgid "Repository has no locks."
+msgstr ""
+
+msgid "Repository maintenance"
+msgstr ""
+
+msgid "Repository mirror"
+msgstr ""
+
+msgid "Repository storage"
+msgstr ""
+
+msgid "RepositorySettingsAccessLevel|Select"
+msgstr ""
+
+msgid "Request Access"
+msgstr ""
+
+msgid "Requested %{time_ago}"
+msgstr ""
+
+msgid "Requests Profiles"
+msgstr ""
+
+msgid "Require all users in this group to setup Two-factor authentication"
+msgstr ""
+
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgstr ""
+
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Resend invite"
+msgstr ""
+
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset key"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
+msgid "Resolve all discussions in new issue"
+msgstr ""
+
+msgid "Resolve conflicts on source branch"
+msgstr ""
+
+msgid "Resolve discussion"
+msgstr ""
+
+msgid "Resolved"
+msgstr ""
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
+msgid "Response"
+msgstr ""
+
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
+msgid "Restart Terminal"
+msgstr ""
+
+msgid "Resume"
+msgstr ""
+
+msgid "Resume replication"
+msgstr ""
+
+msgid "Retry"
+msgstr ""
+
+msgid "Retry this job"
+msgstr ""
+
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
+msgid "Retry verification"
+msgstr ""
+
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Reveal values"
+msgstr ""
+
+msgid "Revert this commit"
+msgstr ""
+
+msgid "Revert this merge request"
+msgstr ""
+
+msgid "Review"
+msgstr ""
+
+msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"."
+msgstr ""
+
+msgid "Reviewing"
+msgstr ""
+
+msgid "Reviewing (merge request !%{mergeRequestId})"
+msgstr ""
+
+msgid "Revoke"
+msgstr ""
+
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
+msgid "Roadmap"
+msgstr ""
+
+msgid "Run CI/CD pipelines for external repositories"
+msgstr ""
+
+msgid "Run housekeeping"
+msgstr ""
+
+msgid "Run tests against your code live using the Web Terminal"
+msgstr ""
+
+msgid "Run untagged jobs"
+msgstr ""
+
+msgid "Runner cannot be assigned to other projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects"
+msgstr ""
+
+msgid "Runner runs jobs from all unassigned projects in its group"
+msgstr ""
+
+msgid "Runner runs jobs from assigned projects"
+msgstr ""
+
+msgid "Runner token"
+msgstr ""
+
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
+msgid "Runner will not receive any new jobs"
+msgstr ""
+
+msgid "Runners"
+msgstr ""
+
+msgid "Runners API"
+msgstr ""
+
+msgid "Runners activated for this project"
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, and even on your local machine."
+msgstr ""
+
+msgid "Runners can be placed on separate users, servers, even on your local machine."
+msgstr ""
+
+msgid "Runners currently online: %{active_runners_count}"
+msgstr ""
+
+msgid "Runners page"
+msgstr ""
+
+msgid "Runners page."
+msgstr ""
+
+msgid "Runners|You have used all your shared Runners pipeline minutes."
+msgstr ""
+
+msgid "Running"
+msgstr ""
+
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
+msgid "SAML SSO"
+msgstr ""
+
+msgid "SAML SSO for %{group_name}"
+msgstr ""
+
+msgid "SAML for %{group_name}"
+msgstr ""
+
+msgid "SSH Keys"
+msgstr ""
+
+msgid "SSH Keys Help"
+msgstr ""
+
+msgid "SSH host keys"
+msgstr ""
+
+msgid "SSH public key"
+msgstr ""
+
+msgid "SSL Verification"
+msgstr ""
+
+msgid "Saturday"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save Changes"
+msgstr ""
+
+msgid "Save application"
+msgstr ""
+
+msgid "Save changes"
+msgstr ""
+
+msgid "Save changes before testing"
+msgstr ""
+
+msgid "Save comment"
+msgstr ""
+
+msgid "Save pipeline schedule"
+msgstr ""
+
+msgid "Save variables"
+msgstr ""
+
+msgid "Saving project."
+msgstr ""
+
+msgid "Schedule a new pipeline"
+msgstr ""
+
+msgid "Scheduled"
+msgstr ""
+
+msgid "Schedules"
+msgstr ""
+
+msgid "Scheduling"
+msgstr ""
+
+msgid "Scheduling Pipelines"
+msgstr ""
+
+msgid "Scope"
+msgstr ""
+
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
+msgid "Scoped issue boards"
+msgstr ""
+
+msgid "Scoped label"
+msgstr ""
+
+msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
+msgstr ""
+
+msgid "Scroll to bottom"
+msgstr ""
+
+msgid "Scroll to top"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Search an environment spec"
+msgstr ""
+
+msgid "Search branches"
+msgstr ""
+
+msgid "Search branches and tags"
+msgstr ""
+
+msgid "Search files"
+msgstr ""
+
+msgid "Search for projects, issues, etc."
+msgstr ""
+
+msgid "Search groups"
+msgstr ""
+
+msgid "Search merge requests"
+msgstr ""
+
+msgid "Search milestones"
+msgstr ""
+
+msgid "Search or filter results..."
+msgstr ""
+
+msgid "Search or jump to…"
+msgstr ""
+
+msgid "Search project"
+msgstr ""
+
+msgid "Search projects"
+msgstr ""
+
+msgid "Search users"
+msgstr ""
+
+msgid "Search users or groups"
+msgstr ""
+
+msgid "Search your projects"
+msgstr ""
+
+msgid "SearchAutocomplete|All GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Issues assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests I've created"
+msgstr ""
+
+msgid "SearchAutocomplete|Merge requests assigned to me"
+msgstr ""
+
+msgid "SearchAutocomplete|in all GitLab"
+msgstr ""
+
+msgid "SearchAutocomplete|in this group"
+msgstr ""
+
+msgid "SearchAutocomplete|in this project"
+msgstr ""
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
+msgid "Secret"
+msgstr ""
+
+msgid "Security"
+msgstr ""
+
+msgid "Security Dashboard"
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the dashboard data. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again."
+msgstr ""
+
+msgid "Security Dashboard|Issue Created"
+msgstr ""
+
+msgid "Security Reports|Create issue"
+msgstr ""
+
+msgid "Security Reports|Dismiss vulnerability"
+msgstr ""
+
+msgid "Security Reports|Learn more about setting up your dashboard"
+msgstr ""
+
+msgid "Security Reports|More info"
+msgstr ""
+
+msgid "Security Reports|No Vulnerabilities"
+msgstr ""
+
+msgid "Security Reports|There was an error creating the issue."
+msgstr ""
+
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
+msgid "Security Reports|There was an error dismissing the vulnerability."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting the dismissal."
+msgstr ""
+
+msgid "Security Reports|There was an error reverting this dismissal."
+msgstr ""
+
+msgid "Security Reports|Undo dismiss"
+msgstr ""
+
+msgid "Security Reports|We've found no vulnerabilities for your group"
+msgstr ""
+
+msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
+msgstr ""
+
+msgid "Security dashboard"
+msgstr ""
+
+msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
+msgstr ""
+
+msgid "SecurityDashboard|Monitor vulnerabilities in your code"
+msgstr ""
+
+msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
+msgstr ""
+
+msgid "See metrics"
+msgstr ""
+
+msgid "See the affected projects in the GitLab admin panel"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select Archive Format"
+msgstr ""
+
+msgid "Select a group to invite"
+msgstr ""
+
+msgid "Select a namespace to fork the project"
+msgstr ""
+
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
+msgid "Select a template repository"
+msgstr ""
+
+msgid "Select a timezone"
+msgstr ""
+
+msgid "Select an existing Kubernetes cluster or create a new one"
+msgstr ""
+
+msgid "Select branch/tag"
+msgstr ""
+
+msgid "Select members to invite"
+msgstr ""
+
+msgid "Select project"
+msgstr ""
+
+msgid "Select project and zone to choose machine type"
+msgstr ""
+
+msgid "Select project to choose zone"
+msgstr ""
+
+msgid "Select projects you want to import."
+msgstr ""
+
+msgid "Select source branch"
+msgstr ""
+
+msgid "Select target branch"
+msgstr ""
+
+msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
+msgstr ""
+
+msgid "Select the custom project template source group."
+msgstr ""
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgstr ""
+
+msgid "Send an email notification to Developers."
+msgstr ""
+
+msgid "Send email"
+msgstr ""
+
+msgid "Send report"
+msgstr ""
+
+msgid "Send usage data"
+msgstr ""
+
+msgid "Sentry API URL"
+msgstr ""
+
+msgid "Sep"
+msgstr ""
+
+msgid "Separate topics with commas."
+msgstr ""
+
+msgid "September"
+msgstr ""
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
+msgid "Server version"
+msgstr ""
+
+msgid "Serverless"
+msgstr ""
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|More information"
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
+msgid "ServerlessURL|Copy URL to clipboard"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
+msgid "Service Desk"
+msgstr ""
+
+msgid "Service Templates"
+msgstr ""
+
+msgid "Service URL"
+msgstr ""
+
+msgid "Session duration (minutes)"
+msgstr ""
+
+msgid "Session expiration, projects limit and attachment size."
+msgstr ""
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+
+msgid "Set a template repository for projects in this group"
+msgstr ""
+
+msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
+msgstr ""
+
+msgid "Set instance-wide template repository"
+msgstr ""
+
+msgid "Set max session time for web terminal."
+msgstr ""
+
+msgid "Set new password"
+msgstr ""
+
+msgid "Set notification email for abuse reports."
+msgstr ""
+
+msgid "Set number of approvers required before open merge requests can be merged"
+msgstr ""
+
+msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
+msgstr ""
+
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
+msgid "Set up CI/CD"
+msgstr ""
+
+msgid "Set up a %{type} Runner manually"
+msgstr ""
+
+msgid "Set up a specific Runner automatically"
+msgstr ""
+
+msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
+msgstr ""
+
+msgid "Set up new U2F device"
+msgstr ""
+
+msgid "Set up new password"
+msgstr ""
+
+msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
+msgstr ""
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr ""
+
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Share"
+msgstr ""
+
+msgid "Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider"
+msgstr ""
+
+msgid "Shared Runners"
+msgstr ""
+
+msgid "Shared projects"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero."
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset pipeline minutes"
+msgstr ""
+
+msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
+msgstr ""
+
+msgid "Sherlock Transactions"
+msgstr ""
+
+msgid "Show all activity"
+msgstr ""
+
+msgid "Show command"
+msgstr ""
+
+msgid "Show comments only"
+msgstr ""
+
+msgid "Show complete raw log"
+msgstr ""
+
+msgid "Show file browser"
+msgstr ""
+
+msgid "Show latest version"
+msgstr ""
+
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
+msgid "Show parent pages"
+msgstr ""
+
+msgid "Show parent subgroups"
+msgstr ""
+
+msgid "Show whitespace changes"
+msgstr ""
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Side-by-side"
+msgstr ""
+
+msgid "Sidebar|Change weight"
+msgstr ""
+
+msgid "Sidebar|None"
+msgstr ""
+
+msgid "Sidebar|Only numeral characters allowed"
+msgstr ""
+
+msgid "Sidebar|Weight"
+msgstr ""
+
+msgid "Sign in"
+msgstr ""
+
+msgid "Sign in / Register"
+msgstr ""
+
+msgid "Sign in to \"%{group_name}\""
+msgstr ""
+
+msgid "Sign in using smart card"
+msgstr ""
+
+msgid "Sign in via 2FA code"
+msgstr ""
+
+msgid "Sign in with Single Sign-On"
+msgstr ""
+
+msgid "Sign in with smart card"
+msgstr ""
+
+msgid "Sign out"
+msgstr ""
+
+msgid "Sign-in restrictions"
+msgstr ""
+
+msgid "Sign-up restrictions"
+msgstr ""
+
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Size and domain settings for static websites"
+msgstr ""
+
+msgid "Size limit per repository (MB)"
+msgstr ""
+
+msgid "Slack application"
+msgstr ""
+
+msgid "Slack integration allows you to interact with GitLab via slash commands in a chat window."
+msgstr ""
+
+msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
+msgstr ""
+
+msgid "Smartcard"
+msgstr ""
+
+msgid "Smartcard authentication failed: client certificate header is missing."
+msgstr ""
+
+msgid "Snippet Contents"
+msgstr ""
+
+msgid "Snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr ""
+
+msgid "Something went wrong on our end"
+msgstr ""
+
+msgid "Something went wrong on our end."
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again!"
+msgstr ""
+
+msgid "Something went wrong on our end. Please try again."
+msgstr ""
+
+msgid "Something went wrong trying to change the confidentiality of this issue"
+msgstr ""
+
+msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
+msgstr ""
+
+msgid "Something went wrong when toggling the button"
+msgstr ""
+
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
+msgid "Something went wrong while closing the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching %{listType} list"
+msgstr ""
+
+msgid "Something went wrong while fetching comments. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching group member contributions"
+msgstr ""
+
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
+msgid "Something went wrong while fetching the environments for this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while fetching the projects."
+msgstr ""
+
+msgid "Something went wrong while fetching the registry list."
+msgstr ""
+
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
+msgid "Something went wrong while reopening the %{issuable}. Please try again later"
+msgstr ""
+
+msgid "Something went wrong while resolving this discussion. Please try again."
+msgstr ""
+
+msgid "Something went wrong, unable to add %{project} to dashboard"
+msgstr ""
+
+msgid "Something went wrong, unable to get operations projects"
+msgstr ""
+
+msgid "Something went wrong, unable to remove project"
+msgstr ""
+
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
+msgid "Something went wrong. Please try again."
+msgstr ""
+
+msgid "Sorry, no epics matched your search"
+msgstr ""
+
+msgid "Sorry, no projects matched your search"
+msgstr ""
+
+msgid "Sorry, your filter produced no results"
+msgstr ""
+
+msgid "Sort by"
+msgstr ""
+
+msgid "Sort direction"
+msgstr ""
+
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last Contact"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Less weight"
+msgstr ""
+
+msgid "SortOptions|Milestone due date"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|More weight"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Most stars"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest last activity"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent last activity"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start date"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
+msgid "SortOptions|Weight"
+msgstr ""
+
+msgid "Source"
+msgstr ""
+
+msgid "Source (branch or tag)"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Source is not available"
+msgstr ""
+
+msgid "Source project cannot be found."
+msgstr ""
+
+msgid "Spam Logs"
+msgstr ""
+
+msgid "Spam and Anti-bot Protection"
+msgstr ""
+
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
+msgid "Specific Runners"
+msgstr ""
+
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
+msgid "Squash commit message"
+msgstr ""
+
+msgid "Squash commits"
+msgstr ""
+
+msgid "Stage"
+msgstr ""
+
+msgid "Stage & Commit"
+msgstr ""
+
+msgid "Stage all changes"
+msgstr ""
+
+msgid "Stage changes"
+msgstr ""
+
+msgid "Staged"
+msgstr ""
+
+msgid "Staged %{type}"
+msgstr ""
+
+msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
+msgstr ""
+
+msgid "StarProject|Star"
+msgstr ""
+
+msgid "Starred Projects"
+msgstr ""
+
+msgid "Starred Projects' Activity"
+msgstr ""
+
+msgid "Starred projects"
+msgstr ""
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
+msgid "Stars"
+msgstr ""
+
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
+msgid "Start Web Terminal"
+msgstr ""
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr ""
+
+msgid "Start a new merge request"
+msgstr ""
+
+msgid "Start a review"
+msgstr ""
+
+msgid "Start and due date"
+msgstr ""
+
+msgid "Start cleanup"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start discussion"
+msgstr ""
+
+msgid "Start discussion & close %{noteable_name}"
+msgstr ""
+
+msgid "Start discussion & reopen %{noteable_name}"
+msgstr ""
+
+msgid "Start the Runner!"
+msgstr ""
+
+msgid "Start your trial"
+msgstr ""
+
+msgid "Started"
+msgstr ""
+
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
+msgid "Starting..."
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
+msgid "Starts at (UTC)"
+msgstr ""
+
+msgid "State your message to activate"
+msgstr ""
+
+msgid "Status"
+msgstr ""
+
+msgid "Status:"
+msgstr ""
+
+msgid "Stop Terminal"
+msgstr ""
+
+msgid "Stop environment"
+msgstr ""
+
+msgid "Stop impersonation"
+msgstr ""
+
+msgid "Stop this environment"
+msgstr ""
+
+msgid "Stopped"
+msgstr ""
+
+msgid "Stopping this environment is currently not possible as a deployment is in progress"
+msgstr ""
+
+msgid "Stopping..."
+msgstr ""
+
+msgid "Storage"
+msgstr ""
+
+msgid "Storage:"
+msgstr ""
+
+msgid "Subgroups"
+msgstr ""
+
+msgid "Subgroups and projects"
+msgstr ""
+
+msgid "Submit as spam"
+msgstr ""
+
+msgid "Submit feedback"
+msgstr ""
+
+msgid "Submit review"
+msgstr ""
+
+msgid "Submit search"
+msgstr ""
+
+msgid "Subscribe"
+msgstr ""
+
+msgid "Subscribe at group level"
+msgstr ""
+
+msgid "Subscribe at project level"
+msgstr ""
+
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
+msgid "Subscribed"
+msgstr ""
+
+msgid "SubscriptionTable|Billing"
+msgstr ""
+
+msgid "SubscriptionTable|Free"
+msgstr ""
+
+msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
+msgstr ""
+
+msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
+msgstr ""
+
+msgid "SubscriptionTable|Last invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Loading subscriptions"
+msgstr ""
+
+msgid "SubscriptionTable|Manage"
+msgstr ""
+
+msgid "SubscriptionTable|Max seats used"
+msgstr ""
+
+msgid "SubscriptionTable|Next invoice"
+msgstr ""
+
+msgid "SubscriptionTable|Seats currently in use"
+msgstr ""
+
+msgid "SubscriptionTable|Seats in subscription"
+msgstr ""
+
+msgid "SubscriptionTable|Seats owed"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription end date"
+msgstr ""
+
+msgid "SubscriptionTable|Subscription start date"
+msgstr ""
+
+msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
+msgstr ""
+
+msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
+msgstr ""
+
+msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
+msgstr ""
+
+msgid "SubscriptionTable|Trial"
+msgstr ""
+
+msgid "SubscriptionTable|Trial end date"
+msgstr ""
+
+msgid "SubscriptionTable|Trial start date"
+msgstr ""
+
+msgid "SubscriptionTable|Upgrade"
+msgstr ""
+
+msgid "SubscriptionTable|Usage"
+msgstr ""
+
+msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr ""
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Suggested change"
+msgstr ""
+
+msgid "Sunday"
+msgstr ""
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr ""
+
+msgid "Support page URL"
+msgstr ""
+
+msgid "Switch branch/tag"
+msgstr ""
+
+msgid "Sync information"
+msgstr ""
+
+msgid "System"
+msgstr ""
+
+msgid "System Hooks"
+msgstr ""
+
+msgid "System Hooks Help"
+msgstr ""
+
+msgid "System Info"
+msgstr ""
+
+msgid "System default (%{default})"
+msgstr ""
+
+msgid "System header and footer"
+msgstr ""
+
+msgid "System hook was successfully updated."
+msgstr ""
+
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
+msgid "Tag"
+msgstr ""
+
+msgid "Tag list:"
+msgstr ""
+
+msgid "Tags"
+msgstr ""
+
+msgid "Tags feed"
+msgstr ""
+
+msgid "Tags:"
+msgstr ""
+
+msgid "TagsPage|Browse commits"
+msgstr ""
+
+msgid "TagsPage|Browse files"
+msgstr ""
+
+msgid "TagsPage|Can't find HEAD commit for this tag"
+msgstr ""
+
+msgid "TagsPage|Cancel"
+msgstr ""
+
+msgid "TagsPage|Create tag"
+msgstr ""
+
+msgid "TagsPage|Delete tag"
+msgstr ""
+
+msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "TagsPage|Edit release notes"
+msgstr ""
+
+msgid "TagsPage|Existing branch name, tag, or commit SHA"
+msgstr ""
+
+msgid "TagsPage|Filter by tag name"
+msgstr ""
+
+msgid "TagsPage|New Tag"
+msgstr ""
+
+msgid "TagsPage|New tag"
+msgstr ""
+
+msgid "TagsPage|Optionally, add a message to the tag."
+msgstr ""
+
+msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
+msgstr ""
+
+msgid "TagsPage|Release notes"
+msgstr ""
+
+msgid "TagsPage|Repository has no tags yet."
+msgstr ""
+
+msgid "TagsPage|Sort by"
+msgstr ""
+
+msgid "TagsPage|Tags"
+msgstr ""
+
+msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
+msgstr ""
+
+msgid "TagsPage|This tag has no release notes."
+msgstr ""
+
+msgid "TagsPage|Use git tag command to add a new one:"
+msgstr ""
+
+msgid "TagsPage|Write your release notes or drag files here…"
+msgstr ""
+
+msgid "TagsPage|protected"
+msgstr ""
+
+msgid "Target Branch"
+msgstr ""
+
+msgid "Target branch"
+msgstr ""
+
+msgid "Team"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Templates"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal for environment"
+msgstr ""
+
+msgid "Terms of Service Agreement and Privacy Policy"
+msgstr ""
+
+msgid "Terms of Service and Privacy Policy"
+msgstr ""
+
+msgid "Test SAML SSO"
+msgstr ""
+
+msgid "Test coverage parsing"
+msgstr ""
+
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
+msgid "Thanks! Don't show me this again"
+msgstr ""
+
+msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
+msgstr ""
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
+msgstr ""
+
+msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
+msgstr ""
+
+msgid "The Git LFS objects will <strong>not</strong> be synced."
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
+msgstr ""
+
+msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
+msgstr ""
+
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
+msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
+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 ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
+msgid "The deployment of this job to %{environmentLink} did not succeed."
+msgstr ""
+
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr ""
+
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
+msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
+msgstr ""
+
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 maximum file size allowed is %{size}."
+msgstr ""
+
+msgid "The maximum file size allowed is 200KB."
+msgstr ""
+
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
+msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
+msgstr ""
+
+msgid "The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>"
+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 private key to use when a client certificate is provided. This value is encrypted at rest."
+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 project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
+msgid "The repository for this project does not exist."
+msgstr ""
+
+msgid "The repository for this project is empty"
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
+msgstr ""
+
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+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 ""
+
+msgid "The roadmap shows the progress of your epics along a timeline"
+msgstr ""
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
+
+msgid "The tabs below will be removed in a future version"
+msgstr ""
+
+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 ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
+msgstr ""
+
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
+msgid "The usage ping is disabled, and cannot be configured through this form."
+msgstr ""
+
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
+msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
+msgstr ""
+
+msgid "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."
+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 no approvers"
+msgstr ""
+
+msgid "There are no archived projects yet"
+msgstr ""
+
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
+msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
+msgstr ""
+
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no labels yet"
+msgstr ""
+
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
+msgid "There are no packages yet"
+msgstr ""
+
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no staged changes"
+msgstr ""
+
+msgid "There are no unstaged changes"
+msgstr ""
+
+msgid "There was an error adding a todo."
+msgstr ""
+
+msgid "There was an error deleting the todo."
+msgstr ""
+
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
+msgid "There was an error saving your notification settings."
+msgstr ""
+
+msgid "There was an error subscribing to this label."
+msgstr ""
+
+msgid "There was an error when reseting email token."
+msgstr ""
+
+msgid "There was an error when subscribing to this label."
+msgstr ""
+
+msgid "There was an error when unsubscribing from this label."
+msgstr ""
+
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr ""
+
+msgid "They can be managed using the %{link}."
+msgstr ""
+
+msgid "Third party offers"
+msgstr ""
+
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
+msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
+msgstr ""
+
+msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
+msgstr ""
+
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
+msgid "This application was created by %{link_to_owner}."
+msgstr ""
+
+msgid "This application will be able to:"
+msgstr ""
+
+msgid "This board's scope is reduced"
+msgstr ""
+
+msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgstr ""
+
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
+msgid "This container registry has been scheduled for deletion."
+msgstr ""
+
+msgid "This date is after the due date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This date is before the start date, so this epic won't appear in the roadmap."
+msgstr ""
+
+msgid "This diff is collapsed."
+msgstr ""
+
+msgid "This diff was suppressed by a .gitattributes entry."
+msgstr ""
+
+msgid "This directory"
+msgstr ""
+
+msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr ""
+
+msgid "This field is required."
+msgstr ""
+
+msgid "This group"
+msgstr ""
+
+msgid "This group does not provide any group Runners yet."
+msgstr ""
+
+msgid "This is a confidential issue."
+msgstr ""
+
+msgid "This is a delayed job to run in %{remainingTime}"
+msgstr ""
+
+msgid "This is the author's first Merge Request to this project."
+msgstr ""
+
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
+msgid "This issue is confidential"
+msgstr ""
+
+msgid "This issue is confidential and locked."
+msgstr ""
+
+msgid "This issue is locked."
+msgstr ""
+
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job does not have a trace."
+msgstr ""
+
+msgid "This job has been canceled"
+msgstr ""
+
+msgid "This job has been skipped"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
+msgstr ""
+
+msgid "This job is archived. Only the complete pipeline can be retried."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink}."
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgstr ""
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr ""
+
+msgid "This job is the most recent deployment to %{link}."
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
+msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
+msgstr ""
+
+msgid "This means you can not push code until you create an empty repository or import existing one."
+msgstr ""
+
+msgid "This merge request is locked."
+msgstr ""
+
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
+msgstr ""
+
+msgid "This page is unavailable because you are not allowed to read information across multiple projects."
+msgstr ""
+
+msgid "This page will be removed in a future release."
+msgstr ""
+
+msgid "This pipeline is run on the source branch"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
+msgid "This project"
+msgstr ""
+
+msgid "This project does not belong to a group and can therefore not make use of group Runners."
+msgstr ""
+
+msgid "This project does not have a wiki homepage yet"
+msgstr ""
+
+msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
+msgid "This runner will only run on pipelines triggered on protected branches"
+msgstr ""
+
+msgid "This setting can be overridden in each project."
+msgstr ""
+
+msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
+msgstr ""
+
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr ""
+
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
+msgid "This user has no identities"
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
+msgstr ""
+
+msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user."
+msgstr ""
+
+msgid "This will redirect you to an external sign in page."
+msgstr ""
+
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
+msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
+msgstr ""
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time before enforced"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr ""
+
+msgid "Time estimate"
+msgstr ""
+
+msgid "Time in seconds GitLab will wait for a response from the external service. When the service does not respond in time, access will be denied."
+msgstr ""
+
+msgid "Time remaining"
+msgstr ""
+
+msgid "Time spent"
+msgstr ""
+
+msgid "Time tracking"
+msgstr ""
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "TimeTrackingEstimated|Est"
+msgstr ""
+
+msgid "TimeTracking|Estimated:"
+msgstr ""
+
+msgid "TimeTracking|Spent"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr ""
+
+msgid "Timeago|%s days remaining"
+msgstr ""
+
+msgid "Timeago|%s hours ago"
+msgstr ""
+
+msgid "Timeago|%s hours remaining"
+msgstr ""
+
+msgid "Timeago|%s minutes ago"
+msgstr ""
+
+msgid "Timeago|%s minutes remaining"
+msgstr ""
+
+msgid "Timeago|%s months ago"
+msgstr ""
+
+msgid "Timeago|%s months remaining"
+msgstr ""
+
+msgid "Timeago|%s seconds remaining"
+msgstr ""
+
+msgid "Timeago|%s weeks ago"
+msgstr ""
+
+msgid "Timeago|%s weeks remaining"
+msgstr ""
+
+msgid "Timeago|%s years ago"
+msgstr ""
+
+msgid "Timeago|%s years remaining"
+msgstr ""
+
+msgid "Timeago|1 day ago"
+msgstr ""
+
+msgid "Timeago|1 day remaining"
+msgstr ""
+
+msgid "Timeago|1 hour ago"
+msgstr ""
+
+msgid "Timeago|1 hour remaining"
+msgstr ""
+
+msgid "Timeago|1 minute ago"
+msgstr ""
+
+msgid "Timeago|1 minute remaining"
+msgstr ""
+
+msgid "Timeago|1 month ago"
+msgstr ""
+
+msgid "Timeago|1 month remaining"
+msgstr ""
+
+msgid "Timeago|1 week ago"
+msgstr ""
+
+msgid "Timeago|1 week remaining"
+msgstr ""
+
+msgid "Timeago|1 year ago"
+msgstr ""
+
+msgid "Timeago|1 year remaining"
+msgstr ""
+
+msgid "Timeago|Past due"
+msgstr ""
+
+msgid "Timeago|in %s days"
+msgstr ""
+
+msgid "Timeago|in %s hours"
+msgstr ""
+
+msgid "Timeago|in %s minutes"
+msgstr ""
+
+msgid "Timeago|in %s months"
+msgstr ""
+
+msgid "Timeago|in %s seconds"
+msgstr ""
+
+msgid "Timeago|in %s weeks"
+msgstr ""
+
+msgid "Timeago|in %s years"
+msgstr ""
+
+msgid "Timeago|in 1 day"
+msgstr ""
+
+msgid "Timeago|in 1 hour"
+msgstr ""
+
+msgid "Timeago|in 1 minute"
+msgstr ""
+
+msgid "Timeago|in 1 month"
+msgstr ""
+
+msgid "Timeago|in 1 week"
+msgstr ""
+
+msgid "Timeago|in 1 year"
+msgstr ""
+
+msgid "Timeago|just now"
+msgstr ""
+
+msgid "Timeago|right now"
+msgstr ""
+
+msgid "Timeout"
+msgstr ""
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Time|s"
+msgstr ""
+
+msgid "Tip:"
+msgstr ""
+
+msgid "Title"
+msgstr ""
+
+msgid "Titles and Filenames"
+msgstr ""
+
+msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
+msgstr ""
+
+msgid "To GitLab"
+msgstr ""
+
+msgid "To access this domain create a new DNS record"
+msgstr ""
+
+msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
+msgstr ""
+
+msgid "To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
+msgstr ""
+
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To connect an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
+msgid "To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}."
+msgstr ""
+
+msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
+msgstr ""
+
+msgid "To get started, link this page to your Jaeger server, or find out how to %{link_start_tag}install Jaeger%{link_end_tag}"
+msgstr ""
+
+msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}."
+msgstr ""
+
+msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
+msgstr ""
+
+msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
+msgstr ""
+
+msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
+msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
+msgstr ""
+
+msgid "To import an SVN repository, check out %{svn_link}."
+msgstr ""
+
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
+msgstr ""
+
+msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
+msgstr ""
+
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
+msgstr ""
+
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
+msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
+msgstr ""
+
+msgid "To start serving your jobs you can add Runners to your group"
+msgstr ""
+
+msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
+msgstr ""
+
+msgid "To this GitLab instance"
+msgstr ""
+
+msgid "To validate your GitLab CI configurations, go to 'CI/CD → Pipelines' inside your project, and click on the 'CI Lint' button."
+msgstr ""
+
+msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
+msgstr ""
+
+msgid "To widen your search, change or remove filters above"
+msgstr ""
+
+msgid "To widen your search, change or remove filters."
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Todo"
+msgstr ""
+
+msgid "Todo was successfully marked as done."
+msgstr ""
+
+msgid "Todos"
+msgstr ""
+
+msgid "Toggle Sidebar"
+msgstr ""
+
+msgid "Toggle comments for this file"
+msgstr ""
+
+msgid "Toggle commit description"
+msgstr ""
+
+msgid "Toggle commit list"
+msgstr ""
+
+msgid "Toggle discussion"
+msgstr ""
+
+msgid "Toggle navigation"
+msgstr ""
+
+msgid "Toggle sidebar"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
+msgid "Token"
+msgstr ""
+
+msgid "Tomorrow"
+msgstr ""
+
+msgid "Too many changes to show."
+msgstr ""
+
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
+msgid "Total Contributions"
+msgstr ""
+
+msgid "Total Time"
+msgstr ""
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Total: %{total}"
+msgstr ""
+
+msgid "Tracing"
+msgstr ""
+
+msgid "Track activity with Contribution Analytics."
+msgstr ""
+
+msgid "Track groups of issues that share a theme, across projects and milestones"
+msgstr ""
+
+msgid "Track time with quick actions"
+msgstr ""
+
+msgid "Transfer project"
+msgstr ""
+
+msgid "Tree view"
+msgstr ""
+
+msgid "Trending"
+msgstr ""
+
+msgid "Trigger pipelines for mirror updates"
+msgstr ""
+
+msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
+msgstr ""
+
+msgid "Trigger removed."
+msgstr ""
+
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
+msgstr ""
+
+msgid "Trigger variables:"
+msgstr ""
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
+msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
+msgstr ""
+
+msgid "Troubleshoot and monitor your application with tracing"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Try again?"
+msgstr ""
+
+msgid "Try all GitLab has to offer for 30 days."
+msgstr ""
+
+msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
+msgstr ""
+
+msgid "Turn on Service Desk"
+msgstr ""
+
+msgid "Twitter"
+msgstr ""
+
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
+msgid "Two-factor authentication"
+msgstr ""
+
+msgid "Type"
+msgstr ""
+
+msgid "URL"
+msgstr ""
+
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
+msgid "Unable to load the diff. %{button_try_again}"
+msgstr ""
+
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
+msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
+msgstr ""
+
+msgid "Unable to update this epic at this time."
+msgstr ""
+
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
+msgid "Unblock"
+msgstr ""
+
+msgid "Undo"
+msgstr ""
+
+msgid "Unfortunately, your email message to GitLab could not be processed."
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unlimited"
+msgstr ""
+
+msgid "Unlock"
+msgstr ""
+
+msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
+msgstr ""
+
+msgid "Unlocked"
+msgstr ""
+
+msgid "Unresolve discussion"
+msgstr ""
+
+msgid "Unschedule job"
+msgstr ""
+
+msgid "Unstage"
+msgstr ""
+
+msgid "Unstage all changes"
+msgstr ""
+
+msgid "Unstage changes"
+msgstr ""
+
+msgid "Unstaged"
+msgstr ""
+
+msgid "Unstaged %{type}"
+msgstr ""
+
+msgid "Unstaged and staged %{type}"
+msgstr ""
+
+msgid "Unstar"
+msgstr ""
+
+msgid "Unsubscribe"
+msgstr ""
+
+msgid "Unsubscribe at group level"
+msgstr ""
+
+msgid "Unsubscribe at project level"
+msgstr ""
+
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
+msgid "Unverified"
+msgstr ""
+
+msgid "Up to date"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Update approvers"
+msgstr ""
+
+msgid "Update failed"
+msgstr ""
+
+msgid "Update now"
+msgstr ""
+
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
+msgid "Update your group name, description, avatar, and visibility."
+msgstr ""
+
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updating"
+msgstr ""
+
+msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr ""
+
+msgid "Upgrade your plan"
+msgstr ""
+
+msgid "Upgrade your plan to activate Advanced Global Search."
+msgstr ""
+
+msgid "Upgrade your plan to activate Contribution Analytics."
+msgstr ""
+
+msgid "Upgrade your plan to activate Group Webhooks."
+msgstr ""
+
+msgid "Upgrade your plan to activate Issue weight."
+msgstr ""
+
+msgid "Upgrade your plan to improve Issue boards."
+msgstr ""
+
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
+msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
+msgstr ""
+
+msgid "Upload CSV file"
+msgstr ""
+
+msgid "Upload New File"
+msgstr ""
+
+msgid "Upload a certificate for your domain with all intermediates"
+msgstr ""
+
+msgid "Upload a private key for your certificate"
+msgstr ""
+
+msgid "Upload file"
+msgstr ""
+
+msgid "Upload object map"
+msgstr ""
+
+msgid "UploadLink|click to upload"
+msgstr ""
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
+msgid "Upstream"
+msgstr ""
+
+msgid "Upvotes"
+msgstr ""
+
+msgid "Usage"
+msgstr ""
+
+msgid "Usage ping is not enabled"
+msgstr ""
+
+msgid "Usage statistics"
+msgstr ""
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
+msgid "Use <code>%{native_redirect_uri}</code> for local tests"
+msgstr ""
+
+msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab"
+msgstr ""
+
+msgid "Use group milestones to manage issues from multiple projects in the same milestone."
+msgstr ""
+
+msgid "Use one line per URI"
+msgstr ""
+
+msgid "Use template"
+msgstr ""
+
+msgid "Use the following registration token during setup:"
+msgstr ""
+
+msgid "Use your global notification setting"
+msgstr ""
+
+msgid "Use your smart card to authenticate with the LDAP server."
+msgstr ""
+
+msgid "Used by members to sign in to your group in GitLab"
+msgstr ""
+
+msgid "Used to help configure your identity provider"
+msgstr ""
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
+msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
+msgstr ""
+
+msgid "User OAuth applications"
+msgstr ""
+
+msgid "User Settings"
+msgstr ""
+
+msgid "User and IP Rate Limits"
+msgstr ""
+
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
+msgid "User map"
+msgstr ""
+
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Explore public groups to find projects to contribute to."
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Groups are the best way to manage projects and members."
+msgstr ""
+
+msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|No snippets found."
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|This user hasn't contributed to any projects"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
+msgid "UserProfile|You can create a group for several dependent projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any personal projects."
+msgstr ""
+
+msgid "UserProfile|You haven't created any snippets."
+msgstr ""
+
+msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
+msgstr ""
+
+msgid "Users"
+msgstr ""
+
+msgid "Users outside of license"
+msgstr ""
+
+msgid "Users requesting access to"
+msgstr ""
+
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
+msgid "Variables"
+msgstr ""
+
+msgid "Various container registry settings."
+msgstr ""
+
+msgid "Various email settings."
+msgstr ""
+
+msgid "Various localization settings."
+msgstr ""
+
+msgid "Various settings that affect GitLab performance."
+msgstr ""
+
+msgid "Verification information"
+msgstr ""
+
+msgid "Verification status"
+msgstr ""
+
+msgid "Verified"
+msgstr ""
+
+msgid "Version"
+msgstr ""
+
+msgid "View app"
+msgstr ""
+
+msgid "View deployment"
+msgstr ""
+
+msgid "View details: %{details_url}"
+msgstr ""
+
+msgid "View eligible approvers"
+msgstr ""
+
+msgid "View epics list"
+msgstr ""
+
+msgid "View file @ "
+msgstr ""
+
+msgid "View group labels"
+msgstr ""
+
+msgid "View in Sentry"
+msgstr ""
+
+msgid "View it on GitLab"
+msgstr ""
+
+msgid "View job trace"
+msgstr ""
+
+msgid "View jobs"
+msgstr ""
+
+msgid "View labels"
+msgstr ""
+
+msgid "View log"
+msgstr ""
+
+msgid "View open merge request"
+msgstr ""
+
+msgid "View project labels"
+msgstr ""
+
+msgid "View replaced file @ "
+msgstr ""
+
+msgid "View the documentation"
+msgstr ""
+
+msgid "Viewing commit"
+msgstr ""
+
+msgid "Visibility and access controls"
+msgstr ""
+
+msgid "Visibility level"
+msgstr ""
+
+msgid "Visibility level:"
+msgstr ""
+
+msgid "Visibility, project features, permissions"
+msgstr ""
+
+msgid "Visibility:"
+msgstr ""
+
+msgid "VisibilityLevel|Internal"
+msgstr ""
+
+msgid "VisibilityLevel|Private"
+msgstr ""
+
+msgid "VisibilityLevel|Public"
+msgstr ""
+
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
+msgid "Vulnerability Chart"
+msgstr ""
+
+msgid "Vulnerability List"
+msgstr ""
+
+msgid "Vulnerability|Class"
+msgstr ""
+
+msgid "Vulnerability|Confidence"
+msgstr ""
+
+msgid "Vulnerability|Description"
+msgstr ""
+
+msgid "Vulnerability|File"
+msgstr ""
+
+msgid "Vulnerability|Identifiers"
+msgstr ""
+
+msgid "Vulnerability|Image"
+msgstr ""
+
+msgid "Vulnerability|Instances"
+msgstr ""
+
+msgid "Vulnerability|Links"
+msgstr ""
+
+msgid "Vulnerability|Namespace"
+msgstr ""
+
+msgid "Vulnerability|Project"
+msgstr ""
+
+msgid "Vulnerability|Report Type"
+msgstr ""
+
+msgid "Vulnerability|Severity"
+msgstr ""
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We can't find an epic that matches what you are looking for."
+msgstr ""
+
+msgid "We can't find an issue that matches what you are looking for."
+msgstr ""
+
+msgid "We could not determine the path to remove the epic"
+msgstr ""
+
+msgid "We could not determine the path to remove the issue"
+msgstr ""
+
+msgid "We couldn't find any results matching"
+msgstr ""
+
+msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "We heard back from your U2F device. You have been authenticated."
+msgstr ""
+
+msgid "We want to be sure it is you, please confirm you are not a robot."
+msgstr ""
+
+msgid "Web IDE"
+msgstr ""
+
+msgid "Web Terminal"
+msgstr ""
+
+msgid "Web terminal"
+msgstr ""
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
+msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
+msgstr ""
+
+msgid "Weeks"
+msgstr ""
+
+msgid "Weight"
+msgstr ""
+
+msgid "Weight %{weight}"
+msgstr ""
+
+msgid "When a runner is locked, it cannot be assigned to other projects"
+msgstr ""
+
+msgid "When enabled, users cannot use GitLab until the terms have been accepted."
+msgstr ""
+
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
+msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
+msgstr ""
+
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "When:"
+msgstr ""
+
+msgid "Who can see this group?"
+msgstr ""
+
+msgid "Who will be able to see this group?"
+msgstr ""
+
+msgid "Wiki"
+msgstr ""
+
+msgid "Wiki was successfully updated."
+msgstr ""
+
+msgid "WikiClone|Clone your wiki"
+msgstr ""
+
+msgid "WikiClone|Git Access"
+msgstr ""
+
+msgid "WikiClone|Install Gollum"
+msgstr ""
+
+msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
+msgstr ""
+
+msgid "WikiClone|Start Gollum and edit locally"
+msgstr ""
+
+msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
+msgstr ""
+
+msgid "WikiEdit|There is already a page with the same title in that path."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}."
+msgstr ""
+
+msgid "WikiEmptyIssueMessage|issue tracker"
+msgstr ""
+
+msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
+msgstr ""
+
+msgid "WikiEmpty|Create your first page"
+msgstr ""
+
+msgid "WikiEmpty|Suggest wiki improvement"
+msgstr ""
+
+msgid "WikiEmpty|The wiki lets you write documentation for your project"
+msgstr ""
+
+msgid "WikiEmpty|This project has no wiki pages"
+msgstr ""
+
+msgid "WikiEmpty|You must be a project member in order to add wiki pages."
+msgstr ""
+
+msgid "WikiHistoricalPage|This is an old version of this page."
+msgstr ""
+
+msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}."
+msgstr ""
+
+msgid "WikiHistoricalPage|history"
+msgstr ""
+
+msgid "WikiHistoricalPage|most recent version"
+msgstr ""
+
+msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
+msgstr ""
+
+msgid "WikiMarkdownDocs|documentation"
+msgstr ""
+
+msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
+msgstr ""
+
+msgid "WikiNewPagePlaceholder|how-to-setup"
+msgstr ""
+
+msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
+msgstr ""
+
+msgid "WikiNewPageTitle|New Wiki Page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page"
+msgstr ""
+
+msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
+msgstr ""
+
+msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
+msgid "WikiPageConflictMessage|the page"
+msgstr ""
+
+msgid "WikiPageCreate|Create %{page_title}"
+msgstr ""
+
+msgid "WikiPageEdit|Update %{page_title}"
+msgstr ""
+
+msgid "WikiPage|Page slug"
+msgstr ""
+
+msgid "WikiPage|Write your content or drag files here…"
+msgstr ""
+
+msgid "Wiki|Create Page"
+msgstr ""
+
+msgid "Wiki|Create page"
+msgstr ""
+
+msgid "Wiki|Created date"
+msgstr ""
+
+msgid "Wiki|Edit Page"
+msgstr ""
+
+msgid "Wiki|More Pages"
+msgstr ""
+
+msgid "Wiki|New page"
+msgstr ""
+
+msgid "Wiki|Page history"
+msgstr ""
+
+msgid "Wiki|Page version"
+msgstr ""
+
+msgid "Wiki|Pages"
+msgstr ""
+
+msgid "Wiki|Title"
+msgstr ""
+
+msgid "Wiki|Wiki Pages"
+msgstr ""
+
+msgid "Will deploy to"
+msgstr ""
+
+msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr ""
+
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
+msgid "Write access allowed"
+msgstr ""
+
+msgid "Write milestone description..."
+msgstr ""
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "Yes, add it"
+msgstr ""
+
+msgid "Yes, let me map Google Code users to full names or GitLab users."
+msgstr ""
+
+msgid "Yesterday"
+msgstr ""
+
+msgid "You"
+msgstr ""
+
+msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
+msgstr ""
+
+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_full_name}. 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?"
+msgstr ""
+
+msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid "You are now impersonating %{username}"
+msgstr ""
+
+msgid "You are on a read-only GitLab instance."
+msgstr ""
+
+msgid "You are receiving this message because you are a GitLab administrator for %{url}."
+msgstr ""
+
+msgid "You can %{linkStart}view the blob%{linkEnd} instead."
+msgstr ""
+
+msgid "You can also create a project from the command line."
+msgstr ""
+
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr ""
+
+msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
+msgstr ""
+
+msgid "You can move around the graph by using the arrow keys."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
+msgid "You can only add files when you are on a branch"
+msgstr ""
+
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
+msgid "You can only merge once the items above are resolved"
+msgstr ""
+
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
+msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
+msgstr ""
+
+msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
+msgstr ""
+
+msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
+msgstr ""
+
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
+msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
+msgstr ""
+
+msgid "You cannot write to this read-only GitLab instance."
+msgstr ""
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
+msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
+msgstr ""
+
+msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
+msgstr ""
+
+msgid "You don't have any applications"
+msgstr ""
+
+msgid "You don't have any authorized applications"
+msgstr ""
+
+msgid "You don't have any deployments right now."
+msgstr ""
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
+msgid "You have no permissions"
+msgstr ""
+
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
+msgid "You have reached your project limit"
+msgstr ""
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
+msgid "You must accept our Terms of Service and privacy policy in order to register an account"
+msgstr ""
+
+msgid "You must have maintainer access to force delete a lock"
+msgstr ""
+
+msgid "You need a different license to enable FileLocks feature"
+msgstr ""
+
+msgid "You need a different license to use Geo replication."
+msgstr ""
+
+msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
+msgstr ""
+
+msgid "You need permission."
+msgstr ""
+
+msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr ""
+
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr ""
+
+msgid "You will need to update your local repositories to point to the new location."
+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 ""
+
+msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
+msgstr ""
+
+msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
+msgstr ""
+
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
+msgid "You're receiving this email because %{reason}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}."
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgstr ""
+
+msgid "YouTube"
+msgstr ""
+
+msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
+msgstr ""
+
+msgid "Your Groups"
+msgstr ""
+
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
+msgid "Your Projects (default)"
+msgstr ""
+
+msgid "Your Projects' Activity"
+msgstr ""
+
+msgid "Your Todos"
+msgstr ""
+
+msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr ""
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
+msgid "Your applications (%{size})"
+msgstr ""
+
+msgid "Your authorized applications"
+msgstr ""
+
+msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
+msgstr ""
+
+msgid "Your changes can be committed to %{branch_name} because a merge request is open."
+msgstr ""
+
+msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
+msgstr ""
+
+msgid "Your changes have been saved"
+msgstr ""
+
+msgid "Your changes have been successfully committed."
+msgstr ""
+
+msgid "Your comment will not be visible to the public."
+msgstr ""
+
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr ""
+
+msgid "Your groups"
+msgstr ""
+
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your name"
+msgstr ""
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
+msgid "Your projects"
+msgstr ""
+
+msgid "Your request for access has been queued for review."
+msgstr ""
+
+msgid "a deleted user"
+msgstr ""
+
+msgid "added %{created_at_timeago}"
+msgstr ""
+
+msgid "ago"
+msgstr ""
+
+msgid "allowed to fail"
+msgstr ""
+
+msgid "among other things"
+msgstr ""
+
+msgid "at"
+msgstr ""
+
+msgid "attach a new file"
+msgstr ""
+
+msgid "authored"
+msgstr ""
+
+msgid "branch name"
+msgstr ""
+
+msgid "by"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about DAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about Dependency Scanning %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{linkStartTag}Learn more about SAST %{linkEndTag}"
+msgstr ""
+
+msgid "ciReport|%{namespace} is affected by %{vulnerability}."
+msgstr ""
+
+msgid "ciReport|%{remainingPackagesCount} more"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected no new vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected no vulnerabilities for the source branch only"
+msgstr ""
+
+msgid "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerability"
+msgid_plural "ciReport|%{reportType} detected %{vulnerabilityCount} vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} detected no vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} is loading"
+msgstr ""
+
+msgid "ciReport|%{reportType}: Loading resulted in an error"
+msgstr ""
+
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
+msgid "ciReport|(errors when loading results)"
+msgstr ""
+
+msgid "ciReport|(is loading)"
+msgstr ""
+
+msgid "ciReport|(is loading, errors when loading results)"
+msgstr ""
+
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
+msgid "ciReport|Class"
+msgstr ""
+
+msgid "ciReport|Code quality"
+msgstr ""
+
+msgid "ciReport|Confidence"
+msgstr ""
+
+msgid "ciReport|Container Scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning"
+msgstr ""
+
+msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
+msgstr ""
+
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
+msgid "ciReport|DAST"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning"
+msgstr ""
+
+msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
+msgstr ""
+
+msgid "ciReport|Dependency scanning"
+msgstr ""
+
+msgid "ciReport|Description"
+msgstr ""
+
+msgid "ciReport|Dismiss vulnerability"
+msgstr ""
+
+msgid "ciReport|Dismissed by"
+msgstr ""
+
+msgid "ciReport|Download and apply the patch to fix this vulnerability."
+msgstr ""
+
+msgid "ciReport|Download patch"
+msgstr ""
+
+msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
+msgstr ""
+
+msgid "ciReport|Failed to load %{reportName} report"
+msgstr ""
+
+msgid "ciReport|File"
+msgstr ""
+
+msgid "ciReport|Fixed:"
+msgstr ""
+
+msgid "ciReport|Identifiers"
+msgstr ""
+
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
+msgid "ciReport|Instances"
+msgstr ""
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
+msgid "ciReport|Learn more about interacting with security reports (Alpha)."
+msgstr ""
+
+msgid "ciReport|License management detected %d license for the source branch only"
+msgid_plural "ciReport|License management detected %d licenses for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected %d new license"
+msgid_plural "ciReport|License management detected %d new licenses"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|License management detected no licenses for the source branch only"
+msgstr ""
+
+msgid "ciReport|License management detected no new licenses"
+msgstr ""
+
+msgid "ciReport|Links"
+msgstr ""
+
+msgid "ciReport|Loading %{reportName} report"
+msgstr ""
+
+msgid "ciReport|Manage licenses"
+msgstr ""
+
+msgid "ciReport|Method"
+msgstr ""
+
+msgid "ciReport|Namespace"
+msgstr ""
+
+msgid "ciReport|No changes to code quality"
+msgstr ""
+
+msgid "ciReport|No changes to performance metrics"
+msgstr ""
+
+msgid "ciReport|Performance metrics"
+msgstr ""
+
+msgid "ciReport|SAST"
+msgstr ""
+
+msgid "ciReport|Security scanning"
+msgstr ""
+
+msgid "ciReport|Security scanning failed loading any results"
+msgstr ""
+
+msgid "ciReport|Severity"
+msgstr ""
+
+msgid "ciReport|Solution"
+msgstr ""
+
+msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
+msgstr ""
+
+msgid "ciReport|There was an error creating the issue. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
+msgstr ""
+
+msgid "ciReport|There was an error loading DAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading SAST report"
+msgstr ""
+
+msgid "ciReport|There was an error loading container scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error loading dependency scanning report"
+msgstr ""
+
+msgid "ciReport|There was an error reverting the dismissal. Please try again."
+msgstr ""
+
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Used by %{packagesString}"
+msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|View full report"
+msgstr ""
+
+msgid "ciReport|on pipeline"
+msgstr ""
+
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "confidentiality|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."
+msgstr ""
+
+msgid "connecting"
+msgstr ""
+
+msgid "could not read private key, is the passphrase correct?"
+msgstr ""
+
+msgid "customize"
+msgstr ""
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "deleted"
+msgstr ""
+
+msgid "deploy token"
+msgstr ""
+
+msgid "detached"
+msgstr ""
+
+msgid "disabled"
+msgstr ""
+
+msgid "discussion resolved"
+msgid_plural "discussions resolved"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "done"
+msgstr ""
+
+msgid "draft"
+msgid_plural "drafts"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "enabled"
+msgstr ""
+
+msgid "epic"
+msgstr ""
+
+msgid "error"
+msgstr ""
+
+msgid "error code:"
+msgstr ""
+
+msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
+msgstr ""
+
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
+msgstr ""
+
+msgid "https://your-bitbucket-server"
+msgstr ""
+
+msgid "image diff"
+msgstr ""
+
+msgid "import flow"
+msgstr ""
+
+msgid "importing"
+msgstr ""
+
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
+msgid "index"
+msgstr ""
+
+msgid "instance completed"
+msgid_plural "instances completed"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "is invalid because there is downstream lock"
+msgstr ""
+
+msgid "is invalid because there is upstream lock"
+msgstr ""
+
+msgid "is not a valid X509 certificate."
+msgstr ""
+
+msgid "is out of the hierarchy of the Group owning the template"
+msgstr ""
+
+msgid "issue"
+msgstr ""
+
+msgid "issue boards"
+msgstr ""
+
+msgid "it is stored externally"
+msgstr ""
+
+msgid "it is stored in LFS"
+msgstr ""
+
+msgid "it is too large"
+msgstr ""
+
+msgid "latest"
+msgstr ""
+
+msgid "latest deployment"
+msgstr ""
+
+msgid "latest version"
+msgstr ""
+
+msgid "leave %{group_name}"
+msgstr ""
+
+msgid "license management"
+msgstr ""
+
+msgid "locked by %{path_lock_user_name} %{created_at}"
+msgstr ""
+
+msgid "manual"
+msgstr ""
+
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "missing"
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
+msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
+msgstr ""
+
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
+msgstr ""
+
+msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB"
+msgstr ""
+
+msgid "mrWidget|Add approval"
+msgstr ""
+
+msgid "mrWidget|Allows commits from members who can merge to the target branch"
+msgstr ""
+
+msgid "mrWidget|An error occurred while removing your approval."
+msgstr ""
+
+msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
+msgstr ""
+
+msgid "mrWidget|An error occurred while submitting your approval."
+msgstr ""
+
+msgid "mrWidget|Approve"
+msgstr ""
+
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
+msgid "mrWidget|Approved by"
+msgstr ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Check out branch"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Closes"
+msgstr ""
+
+msgid "mrWidget|Create an issue to resolve them later"
+msgstr ""
+
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
+msgid "mrWidget|Deployment statistics are not available currently"
+msgstr ""
+
+msgid "mrWidget|Did not close"
+msgstr ""
+
+msgid "mrWidget|Email patches"
+msgstr ""
+
+msgid "mrWidget|Failed to load deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally."
+msgstr ""
+
+msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line"
+msgstr ""
+
+msgid "mrWidget|Loading deployment statistics"
+msgstr ""
+
+msgid "mrWidget|Mentions"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merge request approved"
+msgstr ""
+
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
+msgid "mrWidget|Merge request approved; you can approve additionally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|No approval required"
+msgstr ""
+
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
+
+msgid "mrWidget|Open in Web IDE"
+msgstr ""
+
+msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
+msgstr ""
+
+msgid "mrWidget|Plain diff"
+msgstr ""
+
+msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove your approval"
+msgstr ""
+
+msgid "mrWidget|Request to merge"
+msgstr ""
+
+msgid "mrWidget|Requires 1 more approval"
+msgid_plural "mrWidget|Requires %d more approvals"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Requires 1 more approval by"
+msgid_plural "mrWidget|Requires %d more approvals by"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure"
+msgstr ""
+
+msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
+msgstr ""
+
+msgid "mrWidget|The source branch has been deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
+msgstr ""
+
+msgid "mrWidget|The source branch is being deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will be deleted"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be deleted"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
+msgstr ""
+
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
+msgid "mrWidget|You can delete the source branch now"
+msgstr ""
+
+msgid "mrWidget|You can merge this merge request manually using the"
+msgstr ""
+
+msgid "mrWidget|branch does not exist."
+msgstr ""
+
+msgid "mrWidget|command line"
+msgstr ""
+
+msgid "mrWidget|into"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
+msgid "n/a"
+msgstr ""
+
+msgid "new merge request"
+msgstr ""
+
+msgid "none"
+msgstr ""
+
+msgid "notification emails"
+msgstr ""
+
+msgid "nounSeries|%{firstItem} and %{lastItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, %{nextItem}"
+msgstr ""
+
+msgid "nounSeries|%{item}, and %{lastItem}"
+msgstr ""
+
+msgid "or"
+msgstr ""
+
+msgid "out of %d total test"
+msgid_plural "out of %d total tests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "password"
+msgstr ""
+
+msgid "personal access token"
+msgstr ""
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "private"
+msgstr ""
+
+msgid "private key does not match certificate."
+msgstr ""
+
+msgid "processing"
+msgstr ""
+
+msgid "project"
+msgid_plural "projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
+msgid "remaining"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "remove due date"
+msgstr ""
+
+msgid "remove weight"
+msgstr ""
+
+msgid "rendered diff"
+msgstr ""
+
+msgid "reply"
+msgid_plural "replies"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "reset it."
+msgstr ""
+
+msgid "score"
+msgstr ""
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr ""
+
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
+msgid "source"
+msgstr ""
+
+msgid "source diff"
+msgstr ""
+
+msgid "spendCommand|%{slash_command} will update the sum of the time spent."
+msgstr ""
+
+msgid "started"
+msgstr ""
+
+msgid "stuck"
+msgstr ""
+
+msgid "success"
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
+msgid "this document"
+msgstr ""
+
+msgid "to help your contributors communicate effectively!"
+msgstr ""
+
+msgid "triggered"
+msgstr ""
+
+msgid "updated"
+msgstr ""
+
+msgid "username"
+msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
+
+msgid "verify ownership"
+msgstr ""
+
+msgid "view it on GitLab"
+msgstr ""
+
+msgid "view the blob"
+msgstr ""
+
+msgid "with %{additions} additions, %{deletions} deletions."
+msgstr ""
+
+msgid "within %d minute "
+msgid_plural "within %d minutes "
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "yaml invalid"
+msgstr ""
+
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 369ca42cb2f..75e53196f93 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ko\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 10:21\n"
+"PO-Revision-Date: 2019-04-12 12:29\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " ìƒíƒœ"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " 그리고"
@@ -30,16 +39,17 @@ msgid_plural " improved on %d points"
msgstr[0] " %d í¬ì¸íŠ¸ í–¥ìƒ"
msgid " or "
-msgstr ""
+msgstr " ë˜ëŠ” "
msgid " or <#epic id>"
-msgstr ""
+msgstr " ë˜ëŠ” <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " ë˜ëŠ” <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "프로ì íŠ¸ì—ì„œ \"%{query}\""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%dê°œì˜ ëŒ“ê¸€"
msgid "%d commit"
msgid_plural "%d commits"
@@ -50,7 +60,7 @@ msgid_plural "%d commits behind"
msgstr[0] "%d 커밋 behind"
msgid "%d commits"
-msgstr ""
+msgstr "%dê°œì˜ ì»¤ë°‹"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -70,7 +80,7 @@ msgstr[0] "%d ì´ìŠˆ"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
+msgstr[0] "%d ì´ìŠˆê°€ ì„ íƒë˜ì—ˆìŠµë‹ˆë‹¤."
msgid "%d layer"
msgid_plural "%d layers"
@@ -111,12 +121,25 @@ msgstr "%{counter_storage} (%{counter_repositories} 저장소, %{counter_build_a
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
-msgid "%{count} more"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+
+msgid "%{count} approvals from %{name}"
msgstr ""
+msgid "%{count} more"
+msgstr "%{count} ê°œ ë”보기"
+
msgid "%{count} more assignees"
msgstr "%{count}ëª…ì˜ ë‹´ë‹¹ìž"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr "%{total} 중 %{count}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} ëª…ì˜ ì°¸ì—¬ìž"
@@ -131,7 +154,7 @@ msgstr "%{filePath} ì‚­ì œë¨"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} ë”보기"
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -140,15 +163,24 @@ msgstr "%{group_docs_link_start}그룹%{group_docs_link_end}ì„ ì‚¬ìš©í•˜ë©´ ì—¬
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType}ì´ ì‚­ì œë©ë‹ˆë‹¤! 확실합니까?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}ì—­í•  ê¶Œí•œì— ëŒ€í•´ ìžì„¸ížˆ 알아보기%{link_end}"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} 시작ë¨"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} 경로는 GitLab User %{lock_user_id} ì— ì˜í•´ 잠겼습니다."
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "%{name} ì˜ ì•„ë°”íƒ€"
@@ -161,23 +193,32 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% 완료"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
+msgid "%{state} epics"
+msgstr "%{state} ì—픽"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} 브랜치"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} 커밋"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} 파ì¼"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} 태그"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -196,10 +237,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "GitLab Inc와 공유ë˜ëŠ” ì •ë³´ì— ëŒ€í•´ %{usage_ping_link_start}ë” ì•Œì•„ë³´ê¸°%{usage_ping_link_end}"
msgid "%{user_name} profile page"
+msgstr "%{user_name} 프로필 페ì´ì§€"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(외부 소스)"
msgid "+ %{count} more"
msgstr "+ %{count} ë”보기"
@@ -207,9 +251,12 @@ msgstr "+ %{count} ë”보기"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} ë”"
-msgid ", or "
+msgid "+%{extraOptionCount} more"
msgstr ""
+msgid ", or "
+msgstr ", ë˜ëŠ” "
+
msgid "- Runner is active and can process any new jobs"
msgstr "- Runner ê°€ 활성화ë˜ì—ˆê³ , 새로운 ìž‘ì—…ì„ ì²˜ë¦¬í•  수 있습니다."
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] "%{count} ê°œ %{type} ì˜ ìˆ˜ì •ì‚¬í•­"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "%dì¼"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "%d ê°œì˜ ì´ìŠˆ closed"
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] "%dê±´ì˜ ë¨¸ì§€ë¦¬í€˜ìŠ¤íŠ¸ 닫힘"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "%d 그룹"
@@ -263,18 +317,30 @@ msgid "1 user"
msgid_plural "%d users"
msgstr[0] "%dëª…ì˜ ì‚¬ìš©ìž"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "첫번째 기여!"
msgid "2FA"
-msgstr ""
+msgstr "2단계 ì¸ì¦(2FA)"
msgid "2FA enabled"
msgstr "2FA 사용"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
msgstr ""
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "403|ê¶Œí•œì„ ì–»ìœ¼ë ¤ë©´ GitLab 관리ìžì—게 문ì˜í•˜ì‹­ì‹œì˜¤."
+
msgid "403|You don't have the permission to access this page."
msgstr "ì´ íŽ˜ì´ì§€ì— 대한 액세스 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤."
@@ -287,6 +353,9 @@ msgstr "페ì´ì§€ë¥¼ ì°¾ì„ ìˆ˜ 없습니다"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "ì´ê²ƒì´ ì‹¤ìˆ˜ì— ì˜í•œ 것ì´ë¼ê³  ìƒê°í•œë‹¤ë©´ GitLab 관리ìžì—게 문ì˜í•˜ì„¸ìš”."
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> ì€ ì›ëž˜ johnsmith@example.comì´ ìƒì„±í•œ 모든 ì´ìŠˆì™€ ì˜ê²¬ì— \"By <a href=\"#\">@johnsmith</a>\"ì„ ì¶”ê°€í•˜ê³  ì›ëž˜ johnsmith@example.comì— í• ë‹¹ëœ ëª¨ë“  ì´ìŠˆì— 양수ì¸ìœ¼ë¡œ <a href=\"#\">@ johnsmith</a> ë¡œ 설정합니다."
@@ -315,13 +384,25 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong> íšŒì˜ í‘¸ì‹œ, <strong>%{commits}</strong> 회 ì´ìƒì˜ ì»¤ë°‹ì´ <strong>%{people}</strong> 기여ìžì— ì˜í•´ ì¼ì–´ 났습니다. "
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "소스 브랜치 <strong>삭제</strong>"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "'러너(Runner)'는 ìž‘ì—…ì„ ì‹¤í–‰í•˜ëŠ” 프로세스입니다. 필요한 ë§Œí¼ ëŸ¬ë„ˆë¥¼ ì…‹ì—…í•  수 있습니다."
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "지ì†ì ì¸ í†µí•©ì— ê´€í•œ 그래프 모ìŒ"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr "빈 프로ì íŠ¸ì—서는 기본 브랜치를 ì„ íƒí•  수 없습니다."
@@ -332,18 +413,39 @@ msgstr "ì‚­ì œëœ ì‚¬ìš©ìž"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "GitLabì˜ ì‚¬ì´ë²„ í­ë ¥ ë°©ì§€íŒ€ì´ ìµœëŒ€í•œ 빨리 ê·€í•˜ì˜ ë¦¬í¬íŠ¸ë¥¼ 검토 í•  것 입니다."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "í¬í¬ì™€ 새 머지 리퀘스트(MR)ê°€ 시작ë˜ë©´ 새로운 브랜치가 만들어질 것입니다."
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "프로ì íŠ¸ëŠ” 파ì¼ì„ 저장하고 (저장소), ìž‘ì—… 계íšì„ 세우며 (ì´ìŠˆ), 문서를 게시하는 ê³³ (위키) 입니다, %{among_other_things_link}."
-msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
msgstr ""
+msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgstr "ìž‘ì—… 트레ì´ìŠ¤ì˜ 테스트 커버리지 결과를 알아 보기 위해 사용ë˜ëŠ” 정규표현ì‹ìž…니다. ë¬´íš¨ì¼ ê²½ìš° 공백입니다."
+
msgid "A user with write access to the source branch selected this option"
msgstr "소스 ë¸Œëžœì¹˜ì— ëŒ€í•œ 쓰기 ê¶Œí•œì´ ìžˆëŠ” 사용ìžê°€ ì´ ì˜µì…˜ì„ ì„ íƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "GitLabì— ëŒ€í•˜ì—¬"
@@ -363,7 +465,7 @@ msgid "Abuse reports"
msgstr "악용 사례 보고서"
msgid "Accept invitation"
-msgstr ""
+msgstr "초대 수ë½"
msgid "Accept terms"
msgstr "약관ë™ì˜"
@@ -374,12 +476,18 @@ msgstr "승ì¸ëœ MR"
msgid "Access Tokens"
msgstr "액세스 토í°"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "ì ‘ê·¼ì´ ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤! ì´ ì €ìž¥ì†Œì— ë°°í¬ í‚¤ë¥¼ 추가 í•  수 있는지 확ì¸í•´ 주세요."
msgid "Access expiration date"
msgstr "액세스 만료ì¼"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "%{classification_label} ì— ëŒ€í•œ 액세스가 허용ë˜ì§€ 않습니다."
@@ -389,6 +497,12 @@ msgstr "계정"
msgid "Account and limit"
msgstr "계정과 제한"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "활성"
@@ -402,7 +516,7 @@ msgid "Add"
msgstr "추가"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "ë³€ê²½ëœ ë¡œê·¸ 추가"
msgid "Add CONTRIBUTING"
msgstr ""
@@ -417,43 +531,70 @@ msgid "Add Kubernetes cluster"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° 추가"
msgid "Add README"
+msgstr "README 추가"
+
+msgid "Add a bullet list"
msgstr ""
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "ì´ %{noteable_name} ì— ì¼ë°˜ì ì¸ 코멘트를 추가 합니다."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "프로ì íŠ¸ì— 관한 ì •ë³´ê°€ 담긴 홈페ì´ì§€ë¥¼ wikiì— ì¶”ê°€í•˜ë©´ GitLabì´ ë©”ì‹œì§€ 대신 ì—¬ê¸°ì— í‘œì‹œ 합니다."
+msgid "Add a link"
+msgstr "ë§í¬ 추가"
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr "í…Œì´ë¸” 추가"
+msgid "Add a task list"
+msgstr "ìž‘ì—… ëª©ë¡ ì¶”ê°€"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "모든 ì´ë©”ì¼ì— í‘œì‹œë  í…스트를 추가합니다. %{character_limit} ìž ì œí•œì´ ìžˆìŠµë‹ˆë‹¤."
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr "댓글 추가"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "ì´ë¯¸ì§€ 댓글 추가"
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr "ë¼ì´ì„ ìŠ¤ 추가"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "새 애플리케ì´ì…˜ 추가"
msgid "Add new directory"
msgstr "새 디렉토리 추가"
-msgid "Add projects"
-msgstr "프로ì íŠ¸ 추가"
-
msgid "Add reaction"
msgstr "ë°˜ì‘ ì¶”ê°€"
msgid "Add to project"
-msgstr ""
+msgstr "프로ì íŠ¸ì— 추가"
msgid "Add to review"
msgstr "리뷰 추가"
@@ -465,23 +606,32 @@ msgid "Add user(s) to the group:"
msgstr "ê·¸ë£¹ì— ì‚¬ìš©ìž ì¶”ê°€:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "모든 머지요청(MR) ì„ ìŠ¹ì¸í•  수 있는 ì‚¬ìš©ìž ë˜ëŠ” 그룹 추가"
msgid "Add users to group"
msgstr "ê·¸ë£¹ì— ì‚¬ìš©ìž ì¶”ê°€"
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "GitLab ì¸ìŠ¤í„´ìŠ¤ì— 새 애플리케ì´ì…˜ì„ 추가 í•  수 없습니다. ê¶Œí•œì„ ì–»ìœ¼ë ¤ë©´ GitLab 관리ìžì—게 문ì˜í•˜ì‹­ì‹œì˜¤."
msgid "Additional text"
msgstr "추가 í…스트"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "ê´€ë¦¬ìž ì˜ì—­"
msgid "Admin Overview"
msgstr "ê´€ë¦¬ìž ê°œìš”"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "%{username}ì„ ì˜êµ¬ì ìœ¼ë¡œ 삭제하려고 합니다. ì´ìŠˆ, 머지 리퀘스트(MR) ë° ì—°ê²°ëœ ê·¸ë£¹ì´ \"Ghost-user\"으로 변경ë©ë‹ˆë‹¤. ë°ì´í„° ì†ì‹¤ì„ 방지하기 위해 %{strong_start}차단 사용ìž%{strong_end} ê¸°ëŠ¥ì„ ì‚¬ìš©í•´ë³´ì„¸ìš”. %{strong_start}ì‚¬ìš©ìž ì‚­ì œ%{strong_end}ì‹œ ë˜ëŒë¦¬ê±°ë‚˜, 복구할 수 없습니다."
@@ -516,40 +666,43 @@ msgid "AdminProjects|Delete project"
msgstr "프로ì íŠ¸ ì‚­ì œ"
msgid "AdminSettings|Auto DevOps domain"
+msgstr "Auto DevOps ë„ë©”ì¸"
+
+msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "환경 변수는 기본ì ìœ¼ë¡œ 보호ë©ë‹ˆë‹¤."
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "모든 프로ì íŠ¸ì˜ ìžë™ 앱 리뷰 ë° ìžë™ ë°°í¬ ë‹¨ê³„ì—ì„œ 기본ì ìœ¼ë¡œ 사용할 ë„ë©”ì¸ì„ 지정하십시오."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "새 환경 변수를 작성할 ë•Œ 기본ì ìœ¼ë¡œ 보호ë©ë‹ˆë‹¤."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA 사용 중지"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA 사용"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "활성"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "관리ìž"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "관리ìžë“¤"
msgid "AdminUsers|Block user"
msgstr "ì‚¬ìš©ìž ì°¨ë‹¨"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "차단ë¨"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "LDAP ì°¨ë‹¨ëœ ì‚¬ìš©ìžì˜ ì°¨ë‹¨ì„ í•´ì œí•  수 없습니다."
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr " ì‚¬ìš©ìž %{username}와 기여를 삭제하시겠습니까?"
@@ -564,28 +717,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "사용ìžì™€ 기여 ì‚­ì œ"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "외부"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "ì´ê²ƒì€ 나!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "새로운 유저"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "유저를 ì°¾ì„ ìˆ˜ ì—†ìŒ"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "ì´ë¦„, ì´ë©”ì¼, ì‚¬ìš©ìž ì´ë¦„으로 검색"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "유저 검색"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "유저ì—게 ì´ë©”ì¼ ë³´ë‚´ê¸°"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "정렬 기준"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "확ì¸ì„ 위해 %{projectName} 를 입력해주세요."
@@ -594,27 +747,39 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "확ì¸ì„ 위해 %{username} ì„ ìž…ë ¥í•˜ì„¸ìš”"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "사용ìžê°€ 차단 ë©ë‹ˆë‹¤."
msgid "AdminUsers|Without projects"
+msgstr "프로ì íŠ¸ ì—†ì´"
+
+msgid "Advanced"
msgstr ""
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "고급 사용 권한, 대용량 íŒŒì¼ ì €ìž¥ì†Œì™€ ì´ì¤‘-ì¸ì¦ 설정"
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "고급 설정"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "알림"
msgid "Alerts"
-msgstr ""
+msgstr "알림"
msgid "All"
msgstr "ì „ì²´"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr "모든 ë³€ê²½ì‚¬í•­ì´ ì»¤ë°‹ë˜ì—ˆìŠµë‹ˆë‹¤."
@@ -622,17 +787,35 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "모든 ê¸°ëŠ¥ì€ ë¹„ì–´ìžˆëŠ” 프로ì íŠ¸, 템플릿, 가져올 ë•Œ 사용할 수 있지만 ë‚˜ì¤‘ì— í”„ë¡œì íŠ¸ 설정ì—ì„œ 비활성화 í•  수 있습니다."
msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr "현재 마ì¼ìŠ¤í†¤ì— ê´€ë ¨ëœ ëª¨ë“  ì´ìŠˆë“¤ì´ 마ê°ë˜ì—ˆìŠµë‹ˆë‹¤. 현재 마ì¼ìŠ¤í†¤ì„ 마ê°í•  수 있습니다."
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
msgstr ""
msgid "All users"
msgstr "모든 사용ìž"
-msgid "Allow \"%{group_name}\" to sign you in"
+msgid "All users must have a name."
msgstr ""
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr "\"%{group_name}\"ì„ ìž…ë ¥í•˜ë©´ ë¡œê·¸ì¸ í•  수 있습니다."
+
msgid "Allow commits from members who can merge to the target branch."
msgstr "ëŒ€ìƒ ë¸Œëžœì¹˜ì— ë¨¸ì§€í•  수 있는 ë©¤ë²„ì˜ ì»¤ë°‹ì„ í—ˆìš©í•©ë‹ˆë‹¤."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr "ì´ ê·¸ë£¹ì˜ í”„ë¡œì íŠ¸ë“¤ì´ Git LFS를 사용하ë„ë¡ í—ˆìš©"
@@ -645,6 +828,12 @@ msgstr "Asciidoc 문서ì—ì„œ PlantUML 다ì´ì–´ê·¸ëž¨ ë Œë”ë§ì„ 허용합니
msgid "Allow requests to the local network from hooks and services."
msgstr "후í¬ì™€ 서비스ì—ì„œ 로컬 네트워í¬ì— 대한 ìš”ì²­ì„ í—ˆìš©í•©ë‹ˆë‹¤."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "사용ìžê°€ 액세스를 요청하ë„ë¡ í—ˆìš©"
@@ -663,6 +852,9 @@ msgstr "\"issuer\" ë˜ëŠ” \"Relying party trust identifier\" ë¼ê³ ë„ 합니다
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "\"Relying party service URL\" ë˜ëŠ” \"Reply URL\"ì´ë¼ê³ ë„ 합니다."
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "대신, %{personal_access_token_link} (ì„) 를 사용할 수 있습니다. ê°œì¸ ì•¡ì„¸ìŠ¤ 토í°ì„ ìƒì„±í•  ë•Œ, <code>저장소</code> 범위를 ì„ íƒí•˜ë©´ ì—°ê²°ì´ ê°€ëŠ¥í•œ 공개, 비공개 저장소를 표시할 수 있습니다."
@@ -684,9 +876,6 @@ msgstr "비어 있는 GitLab User 필드는 FogBugzì˜ ì‚¬ìš©ìž ì „ì²´ ì´ë¦„ (
msgid "An error has occurred"
msgstr "ì—러가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "í† ë¡ ì— ì´ˆì•ˆì„ ì¶”ê°€í•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -696,6 +885,15 @@ msgstr "새 드래프트를 추가하는 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr "BLOB 미리보기 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -708,18 +906,27 @@ msgstr "ì´ìŠˆ ì¤‘ìš”ë„ ì—…ë°ì´íŠ¸ 중 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while adding approver"
msgstr "승ì¸ìžë¥¼ ì¶”ê°€í•˜ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "ëŒ“ê¸€ì„ ì‚­ì œí•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while detecting host keys"
msgstr "호스트 í‚¤ë“¤ì„ ì‚­ì œí•˜ë˜ ì¤‘ 오류가 ë°œìƒ í–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Alertì„ í•´ì œí•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 페ì´ì§€ë¥¼ 새로 고친 후 다시 ì‹œë„하십시오."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "기능 ê°•ì¡° 표시를 해제하는 ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 페ì´ì§€ë¥¼ 새로고침하고, 다시 ì‹œë„해주세요."
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "마í¬ë‹¤ìš´ 미리보기를 가져 오는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -732,6 +939,9 @@ msgstr "사ì´ë“œë°” ë°ì´í„°ë¥¼ 가져오는 중 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤.
msgid "An error occurred while fetching stages."
msgstr "스테ì´ì§€ë¥¼ 가져 오는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "ìž‘ì—… 로그를 가져 오는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -744,6 +954,9 @@ msgstr "ìž‘ì—…ì„ ê°€ì ¸ 오는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while fetching the pipeline."
msgstr "파ì´í”„ë¼ì¸ì„ ë°˜ì˜í•˜ë˜ 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "프로ì íŠ¸ë¥¼ 가져오는 ë™ì•ˆ 오류가 ë°œìƒ í–ˆìŠµë‹ˆë‹¤."
@@ -759,6 +972,9 @@ msgstr "차트 ë°ì´í„°ë¥¼ ë¡œë“œí•˜ë˜ ë„중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while loading commit signatures"
msgstr "커밋 시그너ì³ë¥¼ 불러오는 ë„ì¤‘ì— ì—러가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "ì°¨ì´ì ì„ 로드하는 ë™ì•ˆ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
@@ -801,12 +1017,18 @@ msgstr "LDAP 무시 ìƒíƒœë¥¼ 저장하는 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 다
msgid "An error occurred while saving assignees"
msgstr "담당ìžë¥¼ 저장하는 중 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "ì•Œë¦¼ì„ êµ¬ë…하는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "An error occurred while unsubscribing to notifications."
msgstr "알림 구ë…ì„ í•´ì œí•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "ëŒ“ê¸€ì„ ì—…ë°ì´íŠ¸í•˜ëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -864,6 +1086,9 @@ msgstr ""
msgid "Analytics"
msgstr "분ì„"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "ìµëª…"
@@ -876,15 +1101,33 @@ msgstr "Any"
msgid "Any Label"
msgstr "모든 ë¼ë²¨"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "외관"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "어플리케ì´ì…˜"
msgid "Application ID"
msgstr "애플리케ì´ì…˜ ID"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "애플리케ì´ì…˜: %{name}"
@@ -897,6 +1140,40 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -912,15 +1189,30 @@ msgstr "4ì›”"
msgid "April"
msgstr "4ì›”"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "ë³´ê´€ëœ í”„ë¡œì íŠ¸! 저장소 ë° ê¸°íƒ€ 프로ì íŠ¸ 리소스는 ì½ê¸° 전용입니다."
msgid "Archived projects"
msgstr "ì•„ì¹´ì´ë¸Œëœ 프로ì íŠ¸"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "ì´ íŒŒì´í”„ë¼ì¸ ìŠ¤ì¼€ì¥´ì„ ì‚­ì œ 하시겠습니까?"
@@ -933,15 +1225,24 @@ msgstr "저장ë˜ì§€ ì•Šì€ ë³€ê²½ì‚¬í•­ì„ ì‚­ì œí•˜ê² ìŠµë‹ˆê¹Œ?"
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "공개키를 재ìƒì„±í•˜ì‹œê² ìŠµë‹ˆê¹Œ? 미러ë§ì´ 다시 ë™ìž‘하기 위해서 remote ì„œë²„ì˜ ê³µê°œí‚¤ë¥¼ ì—…ë°ì´íŠ¸í•´ì•¼ í•  것입니다."
msgid "Are you sure you want to remove %{group_name}?"
msgstr "ì •ë§ë¡œ %{group_name}(ì„)를 ì‚­ì œ 하시겠습니까?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -954,6 +1255,9 @@ msgstr "ì •ë§ë¡œ 해당 í•­ëª©ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?"
msgid "Are you sure you want to reset registration token?"
msgstr "ë“±ë¡ í† í°ì„ 초기화 하시겠습니까?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "헬스 ì²´í¬ í† í°ì„ 초기화 하시겠습니까?"
@@ -1008,9 +1312,6 @@ msgstr "í• ë‹¹ëœ ì´ìŠˆ"
msgid "Assigned Merge Requests"
msgstr "í• ë‹¹ëœ ë¨¸ì§€ 리퀘스트(MR)"
-msgid "Assigned to :name"
-msgstr "할당 ëŒ€ìƒ :ì´ë¦„"
-
msgid "Assigned to me"
msgstr "나ì—게 할당 ë¨"
@@ -1026,6 +1327,9 @@ msgstr "ë‹´ë‹¹ìž ëª©ë¡ì€ ì„ íƒëœ 사용ìžì—게 할당 ëœ ëª¨ë“  ì´ìŠˆê°
msgid "Assignee(s)"
msgstr "담당ìž"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1041,18 +1345,24 @@ msgstr "8ì›”"
msgid "August"
msgstr "8ì›”"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "ì¸ì¦ 로그"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "ì¸ì¦ 로그"
msgid "Authentication method"
msgstr "ì¸ì¦ 방법"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "작성ìž"
@@ -1071,6 +1381,9 @@ msgstr "권한 부여"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "%{link_to_client} ì„ ë‚´ 계정으로 ì¸ì¦í•˜ì‹œê² ì–´ìš”?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "권한 부여ë¨: "
@@ -1110,18 +1423,12 @@ msgstr "ë” ì•Œì•„ë³´ê¸° %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Auto DevOps 파ì´í”„ ë¼ì¸ì´ 활성화ë˜ì—ˆìœ¼ë©° 대체 CI 구성 파ì¼ì´ 없는 ê²½ìš°ì— ì‚¬ìš©ë©ë‹ˆë‹¤. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "ì´ í”„ë¡œì íŠ¸ê°€ %{link_to_auto_devops_settings} ì¸ ê²½ìš° 애플리케ì´ì…˜ì„ ìžë™ìœ¼ë¡œ 빌드하고 테스트 í•  수 있습니다. %{link_to_add_kubernetes_cluster} ì¸ ê²½ìš°ì—는 ìžë™ìœ¼ë¡œ ë°°í¬ê¹Œì§€ í•  수 있습니다."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "Kubernetes cluster 추가"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "Auto DevOps 활성화"
-
msgid "Automatically marked as default internal user"
msgstr "기본 내부 사용ìžë¡œ ìžë™ 표시"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "Automatically resolved"
msgstr ""
@@ -1245,6 +1552,12 @@ msgstr "리뷰를 취소 하시겠습니까?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "리뷰를 취소하시려고 합니다. 리뷰를 취소 하시면 보류 ì¤‘ì— ìžˆëŠ” 모든 ëŒ“ê¸€ì´ ì‚­ì œë©ë‹ˆë‹¤. %{strong_start} 여기부터%{strong_end} ì—¬ê¸°ê¹Œì§€ì˜ ì‚­ì œëœ ëŒ“ê¸€ì€ ë³µì› í•˜ì‹¤ 수 없습니다."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "ì„ íƒí•œ 커밋으로 시작"
@@ -1482,6 +1795,12 @@ msgstr "프로ì íŠ¸ 설정"
msgid "Branches|protected"
msgstr "보호ë¨"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "디렉토리 찾아보기"
@@ -1503,25 +1822,37 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr "비즈니스 지표 (ì‚¬ìš©ìž ì •ì˜)"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "작성ìž"
msgid "CHANGELOG"
-msgstr ""
+msgstr "변경 로그"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "CI/CD 차트"
msgid "CI / CD Settings"
msgstr "CI/CD 설정"
msgid "CI Lint"
+msgstr "CI 린트"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
@@ -1557,6 +1888,9 @@ msgstr "ì ì§„ì  ë¡¤ì•„ì›ƒì„ ì‚¬ìš©í•˜ì—¬ ìš´ì˜ í™˜ê²½ì— ì§€ì†ì  ë°°í¬ (C
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "Auto DevOps 파ì´í”„ë¼ì¸ì„ 기본으로 사용"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "ë°°í¬ ì „ëžµ"
@@ -1572,6 +1906,9 @@ msgstr "대체 CI 구성 파ì¼ì´ì—†ëŠ” 경우 Auto DevOps 파ì´í”„ë¼ì¸ì´ ì
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "ì¸ìŠ¤í„´ìŠ¤ 사용"
@@ -1587,6 +1924,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr "ì´ ë¸Œëžœì¹˜ì—ì„œ HEAD ì»¤ë°‹ì„ ì°¾ì„ ìˆ˜ 없습니다."
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1599,21 +1939,36 @@ msgstr "ì´ ìž‘ì—… 취소"
msgid "Cannot be merged automatically"
msgstr "ìžë™ìœ¼ë¡œ 머지할 수 없습니다."
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "ì´ë¯¸ êµ¬ì„±ëœ Kubernetes í´ëŸ¬ìŠ¤í„°ëŠ” 수정할 수 없습니다"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "ì¸ì¦ì„œ 지문"
-
msgid "Change Weight"
msgstr "ì¤‘ìš”ë„ ë³€ê²½"
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1638,6 +1993,9 @@ msgstr "Revert"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "기존 변경 ì‚¬í•­ì„ ë˜ëŒë¦¬ê¸° 위해 새로운 ì»¤ë°‹ì„ ë§Œë“­ë‹ˆë‹¤."
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "변경 ì‚¬í•­ì€ <b>source</b> ë¦¬ë¹„ì „ì´ <b>target</b> ë¦¬ë¹„ì „ì— ë¨¸ì§€ëœ ê²ƒì²˜ëŸ¼ 표시ë©ë‹ˆë‹¤."
@@ -1650,6 +2008,12 @@ msgstr "차트"
msgid "Chat"
msgstr "채팅"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "%{docs_link_start} 여기부터 %{docs_link_end} 여기까지 확ì¸."
@@ -1677,9 +2041,6 @@ msgstr "<strong>ë³´ê´€ íŒŒì¼ ìƒì„±</strong>ì„ ì„ íƒí•˜ê³  ë³´ê´€ì´ ì™„ë£Œ ë
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "페ì´ì§€ì˜ ì•„ëž˜ì˜ <strong>다ìŒ</strong> ì„ íƒ"
-msgid "Choose File ..."
-msgstr "íŒŒì¼ ì„ íƒ ..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "브랜치/태그 (예. %{master})를 ì„ íƒí•˜ê±°ë‚˜ 커밋 (예. %{sha})ì„ ìž…ë ¥í•˜ì—¬ 변경 ì‚¬í•­ì„ í™•ì¸í•˜ê±°ë‚˜ 머지 리퀘스트(MR)ì„ ë§Œë“­ë‹ˆë‹¤."
@@ -1701,27 +2062,27 @@ msgstr "아무 색ìƒì„ ì„ íƒí•´ 주세요."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "최근 ì‘ìš© 프로그램 코드를 얻으려면 <code>í´ë¡ </code> ë˜ëŠ” <code>fetch</code>를 ì„ íƒí•˜ì‹­ì‹œì˜¤."
-msgid "Choose file..."
-msgstr "íŒŒì¼ ì„ íƒâ€¦"
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "저장소 ê°€ì ¸ì˜¤ê¸°ì— ëŒ€í•œ 최ìƒìœ„ ê·¸ë£¹ì„ ì„ íƒí•˜ì„¸ìš”."
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "ì´ ì„¸ì»¨ë”리 ë…¸ë“œì— ë™ê¸°í™” í•  ê·¸ë£¹ì„ ì„ íƒí•˜ì„¸ìš”."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Ci/CD 파ì´í”„ë¼ì¸ì´ ì—°ê²°ë˜ê³  실행할 저장소를 ì„ íƒí•´ 주세요."
-msgid "Choose which repositories you want to import."
-msgstr "가져올 저장소를 ì„ íƒí•´ì£¼ì„¸ìš”."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "ì´ ì„¸ì»¨ë”리 ë…¸ë“œì— ì–´ë–¤ 샤드 (shards)를 ë™ê¸°í™” 할지 ì„ íƒí•˜ì„¸ìš”."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "취소ë¨"
@@ -1746,6 +2107,9 @@ msgstr "경고와 함께 성공"
msgid "CiStatusLabel|pending"
msgstr "대기"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "건너 뜀"
@@ -1779,6 +2143,9 @@ msgstr "성공"
msgid "CiStatusText|pending"
msgstr "보류 중"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "건너 뜀"
@@ -1794,6 +2161,9 @@ msgstr "입력 변수 값"
msgid "CiVariables|Remove variable row"
msgstr "변수 행 제거"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (모든 환경)"
@@ -1806,6 +2176,9 @@ msgstr "와ì¼ë“œ ì¹´ë“œ 만들기"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr "새로운 환경"
@@ -1815,12 +2188,18 @@ msgstr "보호ë¨"
msgid "CiVariable|Search environments"
msgstr "환경 검색"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Toggle 보호ë¨"
msgid "CiVariable|Validation failed"
msgstr "유효성 검사 실패"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "사용할 수 없습니다: %{reason}"
@@ -1878,6 +2257,9 @@ msgstr "저장소 í´ë¡ "
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1893,9 +2275,6 @@ msgstr ""
msgid "Closed"
msgstr "닫힘"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "닫힌 ì´ìŠˆ"
@@ -1911,15 +2290,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList}ê°€ Kubernetes í´ëŸ¬ìŠ¤í„°ì— 설치ë˜ì—ˆìŠµë‹ˆë‹¤"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} ì´ê²ƒì€ 로드 밸러서와 ê°™ì€ ë¦¬ì†ŒìŠ¤ë¥¼ 추가하게 ë  ê²ƒ 입니다. Kubernetes í´ëŸ¬ìŠ¤í„°ê°€ ì„¤ì¹˜ëœ í˜¸ìŠ¤íŒ… 제공 ì—…ì²´ì— ë”°ë¼ ì¶”ê°€ ë¹„ìš©ì´ ë°œìƒ í•  수 있습니다. Google Kubernetes Engineì„ ì‚¬ìš©í•˜ì‹¤ 경우ì—는 ì´ê³³ %{pricingLink} ì„ ì°¸ì¡° 하십시오."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° 추가"
@@ -1935,18 +2314,15 @@ msgstr "ê·¸ë£¹ì— ì—°ë™ì„ 하시면 모든 프로ì íŠ¸ì—ì„œ í´ëŸ¬ìŠ¤í„°ê°€
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° í†µí•©ì— ëŒ€í•œ 고급 옵션"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "프로ì íŠ¸ ì˜ì—­ì„ 가져 오는 ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Google Cloud APIì— ì—°ê²°ì„ ì‹œë„하는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. ë‚˜ì¤‘ì— ë‹¤ì‹œ ì‹œë„하십시오."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -1983,6 +2359,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1992,20 +2374,20 @@ msgstr "API URL 복사"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "CA ì¸ì¦ì„œ 복사"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Ingress IP 주소를 í´ë¦½ë³´ë“œë¡œ 복사"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ì´ë¦„ 복사"
-msgid "ClusterIntegration|Copy Token"
-msgstr "í† í° ë³µì‚¬"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ìƒì„±"
@@ -2052,8 +2434,8 @@ msgstr "GitLab 통합"
msgid "ClusterIntegration|GitLab Runner"
msgstr "ClusterIntegration|GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner를 ì´ í”„ë¡œì íŠ¸ì˜ ì €ìž¥ì†Œì— ì—°ê²°í•˜ê³ , CI/CD ìž‘ì—…ì„ ì‹¤í–‰í•˜ê³ , 결과를 다시 가져오고, ì‘ìš© í”„ë¡œê·¸ëž¨ì„ ìš´ì˜í™˜ê²½ì— ë°°í¬í•©ë‹ˆë‹¤."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Google Cloud Platform 프로ì íŠ¸"
@@ -2079,14 +2461,14 @@ msgstr "숨기기"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress ì•„ì´í”¼ 주소"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
@@ -2094,15 +2476,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr "설치"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr "설치ë¨"
msgid "ClusterIntegration|Installing"
msgstr "설치중"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ìžë™í™” 통합"
@@ -2130,7 +2515,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2142,9 +2527,6 @@ msgstr "Kubernetes í´ëŸ¬ìŠ¤í„°"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ìƒì„¸"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° ìƒíƒœ"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„°ê°€ Google Kubernetes Engineì—ì„œ ìƒì„± 중입니다..."
@@ -2173,7 +2555,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "그룹 Kubernetes í´ëŸ¬ìŠ¤í„°ì— 대해 ìžì„¸ížˆ 알아보기."
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "머신 타입"
@@ -2187,9 +2569,6 @@ msgstr "관리"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "%{link_gke}ì— ë°©ë¬¸í•˜ì—¬ Kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼ 관리하세요."
-msgid "ClusterIntegration|More information"
-msgstr "ë” ë§Žì€ ì •ë³´"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2202,27 +2581,24 @@ msgstr "검색과 ì¼ì¹˜í•˜ëŠ” 프로ì íŠ¸ê°€ 없습니다."
msgid "ClusterIntegration|No zones matched your search"
msgstr "검색과 ì¼ì¹˜í•˜ëŠ” ì˜ì—­ì´ 없습니다."
-msgid "ClusterIntegration|Note:"
-msgstr "Note:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "노드 수"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„°ì— 대한 액세스 정보를 입력하십시오. ë„ì›€ì´ í•„ìš”í•˜ì‹œë©´, Kubernetesì— ëŒ€í•œ %{link_to_help_page} 를 참고하세요."
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "ë‹¹ì‹ ì˜ Google ê³„ì •ì´ ë‹¤ìŒì˜ 요구 ì‚¬í•­ì„ ì¶©ì¡±í•˜ëŠ”ì§€ í™•ì¸ í•˜ì„¸ìš”."
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "프로ì íŠ¸ í´ëŸ¬ìŠ¤í„°"
-msgid "ClusterIntegration|Project namespace"
-msgstr "프로ì íŠ¸ 네임스페ì´ìŠ¤"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "프로ì íŠ¸ 네임 스페ì´ìŠ¤ (optional, unique)"
@@ -2253,7 +2629,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "설치 시작 요청 실패"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2289,8 +2665,11 @@ msgstr "ClusterIntegration|지역 ì„ íƒ"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
-msgstr "서비스 토í°"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "표시"
@@ -2298,9 +2677,6 @@ msgstr "표시"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "구글 Kubernetes ì—”ì§„ì— Kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼ 만드는 ë™ì•ˆ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -2310,7 +2686,7 @@ msgstr "설치하는 ë™ì•ˆ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. %{title}"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2319,11 +2695,17 @@ msgstr "%{link_to_container_project} 프로ì íŠ¸ì— Kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° 토글"
-msgid "ClusterIntegration|Token"
-msgstr "토í°"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2361,9 +2743,6 @@ msgstr "Zone"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "구글 Kubernetes 컨테ì´ë„ˆ ì—”ì§„ì— ì—‘ì„¸ìŠ¤"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "여기서 ê°€ê²©ì„ í™•ì¸í•˜ì„¸ìš”"
-
msgid "ClusterIntegration|documentation"
msgstr "문서"
@@ -2373,6 +2752,9 @@ msgstr "ë„ì›€ë§ íŽ˜ì´ì§€"
msgid "ClusterIntegration|meets the requirements"
msgstr "요구 ì‚¬í•­ì„ ì¶©ì¡±"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "ì •ìƒì ìœ¼ë¡œ 구성ë˜ì—ˆìŒ"
@@ -2382,15 +2764,27 @@ msgstr "ClusterIntegration|가입"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr "코드 소유ìž"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Cohorts"
msgid "Collapse"
msgstr "ê°ì¶”기"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "사ì´ë“œ ë°” 축소"
@@ -2485,6 +2879,9 @@ msgstr "커밋한 사용ìž"
msgid "Commit…"
msgstr "커밋..."
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "비êµ"
@@ -2554,6 +2951,12 @@ msgstr "%{link} ì—°ë™ êµ¬ì„±"
msgid "Configure the way a user creates a new account."
msgstr "사용ìžê°€ 새로운 ê³„ì •ì„ ë§Œë“œëŠ” ë°©ë²•ì„ ì„¤ì •í•©ë‹ˆë‹¤."
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "ì—°ê²°"
@@ -2569,12 +2972,24 @@ msgstr "ë‹¹ì‹ ì˜ ì™¸ë¶€ 저장소를 연결하면 CI/CD 파ì´í”„ë¼ì¸ì´ 새ë
msgid "Connecting..."
msgstr "연결중..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "컨테ì´ë„ˆ 레지스트리"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "만든"
@@ -2638,6 +3053,9 @@ msgstr "GitLabì— ê¸°ì—¬í•˜ì„¸ìš”"
msgid "Contribution"
msgstr "기여"
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2665,18 +3083,9 @@ msgstr "잠시만 기다려주십시오, ì´ íŽ˜ì´ì§€ëŠ” 준비가 완료ë˜ë©´
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "ConvDev ì¸ë±ìŠ¤"
@@ -2689,6 +3098,9 @@ msgstr "%{protocol} í´ë¡  URL 복사"
msgid "Copy ID to clipboard"
msgstr "í´ë¦½ ë³´ë“œì— ID 복사"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "SSH í´ë¡  URL 복사"
@@ -2713,9 +3125,6 @@ msgstr "ì»¤ë°‹ì˜ SHA를 í´ë¦½ë³´ë“œë¡œ 복사합니다"
msgid "Copy file path to clipboard"
msgstr "íŒŒì¼ ê²½ë¡œë¥¼ í´ë¦½ë³´ë“œì— 복사"
-msgid "Copy incoming email address to clipboard"
-msgstr "수신 ì´ë©”ì¼ ì£¼ì†Œë¥¼ í´ë¦½ ë³´ë“œì— ë³µì‚¬"
-
msgid "Copy link"
msgstr "ë§í¬ 복사"
@@ -2731,9 +3140,33 @@ msgstr "í´ë¦½ë³´ë“œì— 복사"
msgid "Copy token to clipboard"
msgstr "í´ë¦½ë³´ë“œì— í† í° ë³µì‚¬"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr "만들기"
@@ -2743,12 +3176,12 @@ msgstr "새 디렉토리 만들기"
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
msgid "Create a new branch"
msgstr "새 브랜치 ìƒì„±"
-msgid "Create a new branch and merge request"
-msgstr "새 브랜치 ë° ë¨¸ì§€ 리퀘스트(MR) ìƒì„±"
-
msgid "Create a new issue"
msgstr "새 ì´ìŠˆ 만들기"
@@ -2758,6 +3191,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "%{protocol}ì„ (를) 통해 Pull 하거나 Push í•  ê°œì¸ ì•¡ì„¸ìŠ¤ 토í°ì„ 만드십시오."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr "브랜치 ìƒì„±"
@@ -2782,9 +3218,6 @@ msgstr "그룹 만들기"
msgid "Create group label"
msgstr "새로운 그룹 ë¼ë²¨"
-msgid "Create issue"
-msgstr "ì´ìŠˆ 만들기"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "ë¼ë²¨ë¡œë¶€í„° 목ë¡ì„ ìƒì„±í•©ë‹ˆë‹¤. ì´ìŠˆì™€ 함께 ë¼ë²¨ì´ 목ë¡ì— 표시ë©ë‹ˆë‹¤."
@@ -2854,6 +3287,9 @@ msgstr "í¬ë¡  구문"
msgid "Current Branch"
msgstr "현재 브랜치"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "현재 노드"
@@ -2893,9 +3329,6 @@ msgstr "Google Code ì´ë©”ì¼ê³¼ 사용ìžëª…ì„ GitLab으로 가져오는 ë°©ë²
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "파ì´í”„ë¼ì¸ ì„¤ì •ì„ ë³€ê²½í•˜ê³ , ë‚˜ì˜ íŒŒì´í”„ë¼ì¸ìƒíƒœì™€ ì ìš© 범위 보고서를 보세요."
@@ -2938,6 +3371,9 @@ msgstr "모든"
msgid "DashboardProjects|Personal"
msgstr "ê°œì¸"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2962,15 +3398,24 @@ msgstr "취소 ë° ë¡œê·¸ì•„ì›ƒ"
msgid "Default Branch"
msgstr "기본 브랜치"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "기본 분류 ë¼ë²¨"
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr "기본값: Google 코드 ì´ë©”ì¼ ì£¼ì†Œ ë˜ëŠ” ì‚¬ìš©ìž ì´ë¦„ì„ ì§ì ‘ 가져옵니다."
@@ -3010,6 +3455,9 @@ msgstr "스니펫 삭제"
msgid "Delete comment"
msgstr "댓글 삭제"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "ëª©ë¡ ì‚­ì œ"
@@ -3022,6 +3470,21 @@ msgstr ""
msgid "Deleted"
msgstr "ì‚­ì œë¨"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "거부"
@@ -3032,6 +3495,9 @@ msgstr[0] "ë°°í¬"
msgid "Deploy Keys"
msgstr "ë°°í¬ í‚¤"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} 기타"
@@ -3170,12 +3636,45 @@ msgstr "내림차순"
msgid "Description"
msgstr "설명"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr "설명:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "제거"
@@ -3254,6 +3753,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr "닫기"
@@ -3293,20 +3795,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "tar 다운로드"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "tar.bz2 다운로드"
+msgstr ""
msgid "Download tar.gz"
-msgstr "tar.gz 다운로드"
+msgstr ""
msgid "Download zip"
-msgstr "zip 다운로드"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "다운로드"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "ì´ë©”ì¼ íŒ¨ì¹˜"
@@ -3338,6 +3846,9 @@ msgstr "편집"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr "ë¼ë²¨ 편집"
@@ -3359,6 +3870,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr "ì—디터ì—ì„œ 파ì¼ì„ 편집하고 여기서 ë³€ê²½ì‚¬í•­ì„ ì»¤ë°‹í•˜ì„¸ìš”."
@@ -3371,18 +3885,54 @@ msgstr "%{user_name}ì˜ ì‹ ì› íŽ¸ì§‘"
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Elasticsearch ì—°ë™, Elasticsearch AWS IAM"
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "ì´ë©”ì¼"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "패치를 ì´ë©”ì¼ë¡œ 보냄"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "ì´ë©”ì¼"
@@ -3398,6 +3948,9 @@ msgstr "사용"
msgid "Enable Auto DevOps"
msgstr "ìžë™ DevOps 활성화"
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3425,6 +3978,12 @@ msgstr "ì´ í”„ë¡œì íŠ¸ì— 대해 사용"
msgid "Enable group Runners"
msgstr "그룹 Runner 사용"
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3434,9 +3993,6 @@ msgstr "버전 í™•ì¸ ë° ì‚¬ìš© í•‘ì„ ì‚¬ìš©í•˜ê±°ë‚˜ 사용하지 ì•Šë„ë¡ ì
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "reCAPTCHA ë˜ëŠ” Akismetì„ í™œì„±í™”í•˜ê³  IP ì œí•œì„ ì„¤ì •í•©ë‹ˆë‹¤."
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3452,16 +4008,28 @@ msgstr "ping 사용 활성화"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "활성화ë¨"
-msgid "Ends at (UTC)"
-msgstr "(UTC)ì— ì¢…ë£Œë©ë‹ˆë‹¤"
+msgid "Enabled Git access protocols"
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr "(UTC)ì— ì¢…ë£Œë©ë‹ˆë‹¤"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3479,13 +4047,7 @@ msgstr "머지 리퀘스트(MR) ì„¤ëª…ì„ ìž…ë ¥ 해주세요"
msgid "Enter the merge request title"
msgstr "머지 리퀘스트(MR) ì œëª©ì„ ìž…ë ¥ 해주세요"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3506,6 +4068,12 @@ msgstr "í™˜ê²½ê°’ë“¤ì„ ë°˜ì˜í•˜ëŠ” ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "Environments|An error occurred while making the request."
msgstr "ìš”ì²­ì„ ë§Œë“œëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3557,15 +4125,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr "í™˜ê²½ì— ëŒ€í•œ 추가 ì •ë³´"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "ëª¨ë‘ ë³´ê¸°"
@@ -3578,6 +4164,18 @@ msgstr "중지 환경"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "갱신ë¨"
@@ -3629,6 +4227,9 @@ msgstr "오류 ë³´ê³  ë° ë¡œê¹…"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr "ì—í”½ì„ ë§Œë“œëŠ” 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
@@ -3677,6 +4278,18 @@ msgstr "템플릿 로딩 오류."
msgid "Error occurred when toggling the notification subscription"
msgstr "êµ¬ë… ì•Œë¦¼ì„ ì „í™˜í•  ë•Œ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3692,12 +4305,48 @@ msgstr "모든 해야할 ì¼ì˜ ìƒíƒœë¥¼ ë³€ê²½í•˜ë˜ ì¤‘ 오류"
msgid "Error updating todo status."
msgstr "해야할 ì¼ ìƒíƒœ 갱신 중 오류 ë°œìƒ"
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr "머지 리퀘스트(MR) 요청 중 오류 ë°œìƒ. 다시 ì‹œë„하십시오."
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3761,12 +4410,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3776,12 +4419,18 @@ msgstr "펼치기"
msgid "Expand all"
msgstr "ëª¨ë‘ í™•ìž¥"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "사ì´ë“œë°” 확장"
msgid "Expiration date"
msgstr "만료ì¼"
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3815,6 +4464,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr "외부 분류 ì •ì±… ì¸ì¦"
@@ -3833,9 +4488,6 @@ msgstr "외부 ì¸ì¦ì´ ì´ í”„ë¡œì íŠ¸ì— 대한 액세스를 거부했습니
msgid "External authorization request timeout"
msgstr "외부 ì¸ì¦ 요청 시간 초과"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3857,13 +4509,19 @@ msgstr "소유ìžë¥¼ 변경하지 못했습니다"
msgid "Failed to check related branches."
msgstr "관련 브랜치를 확ì¸í•˜ëŠ”ë° ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr "ì´ëª¨ì§€ 목ë¡ì„ 불러올 수 없습니다"
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3875,9 +4533,24 @@ msgstr "미러를 제거하지 못했습니다."
msgid "Failed to remove the pipeline schedule"
msgstr "파ì´í”„ë¼ì¸ ìŠ¤ì¼€ì¤„ì„ ì œê±°í•˜ì§€ 못했습니다."
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3890,12 +4563,18 @@ msgstr ""
msgid "Failure"
msgstr "실패"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3929,9 +4608,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "설명"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "%{feature_flag_name} 수정"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "Feature 플래그 수정"
@@ -3989,8 +4665,8 @@ msgstr "새로운"
msgid "FeatureFlags|New Feature Flag"
msgstr "새로운 Feature 플래그"
-msgid "FeatureFlags|Save changes"
-msgstr "변경사항 저장"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "ìƒíƒœ"
@@ -4016,6 +4692,18 @@ msgstr "2ì›”"
msgid "February"
msgstr "2ì›”"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "ì´ íŽ˜ì´ì§€ì˜ ìž…ë ¥ëž€ì€ ì´ì œ 변경할 수 없습니다, 설정할 수 있습니다."
@@ -4077,9 +4765,6 @@ msgstr ""
msgid "Filter..."
msgstr "í•„í„°..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "경로로 찾기"
@@ -4095,6 +4780,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "지문"
@@ -4188,14 +4876,20 @@ msgstr ""
msgid "Format"
msgstr "í¬ë§·"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ".gitlab-ci.ymlì—ì„œ 오류를 발견했습니다:"
msgid "Free Trial of GitLab.com Gold"
msgstr "GitLab.com Gold 무료 ì²´í—˜íŒ"
-msgid "From %{provider_title}"
-msgstr "%{provider_title}ì—ì„œ"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "Bitbucketì—ì„œ"
@@ -4224,12 +4918,21 @@ msgstr "마ì¼ìŠ¤í†¤ì—ì„œ:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Kubernetes í´ëŸ¬ìŠ¤í„° 세부사항 ë³´ê¸°ì˜ ì• í”Œë¦¬ì¼€ì´ì…˜ 목ë¡ì—ì„œ Runner를 설치하십시오."
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG 키"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "ì¼ë°˜"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr "ì¼ë°˜ 파ì´í”„ë¼ì¸"
@@ -4239,6 +4942,9 @@ msgstr " 초기설정 ë ˆì´ë¸” 세트 ìƒì„±"
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr "Geo"
@@ -4263,9 +4969,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr "ë°ì´í„° 복제 지연"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "노드를 비활성화하면 ë™ê¸°í™” 프로세스가 중지ë©ë‹ˆë‹¤. ì •ë§ë¡œ 진행하시겠습니까?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "ë§ˆìŠ¤í„°ì˜ ìŠ¤í† ë¦¬ì§€ 구성과 ì¼ì¹˜í•˜ì§€ 않습니다."
@@ -4284,6 +4987,9 @@ msgstr "GitLab ë²„ì „ì´ ì£¼ìš” 노드 버전과 ì¼ì¹˜í•˜ì§€ 않습니다."
msgid "GeoNodes|Health status"
msgstr "ìš´ì˜ ìƒíƒœ"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "커서로 ì²˜ë¦¬ëœ ë§ˆì§€ë§‰ ì´ë²¤íŠ¸ ID"
@@ -4329,6 +5035,9 @@ msgstr "ì²´í¬ì„¬ ë˜ì§€ ì•ŠìŒ"
msgid "GeoNodes|Out of sync"
msgstr "ë™ê¸°í™”ë˜ì§€ ì•ŠìŒ"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4437,9 +5146,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "실패"
@@ -4455,6 +5179,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4485,6 +5212,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4524,24 +5257,45 @@ msgstr "ìž¬ì‹œë„ íšŸìˆ˜"
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr "ìƒíƒœ"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Geo|ë™ê¸°í™” ë¨"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "Geo|ë™ê¸°í™” 실패 - %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "ì•Œ 수 없는 ìƒíƒœ"
@@ -4560,9 +5314,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4584,6 +5335,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4605,6 +5359,9 @@ msgstr "GitHub 가져오기"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linterê°€ ì´ë™ë˜ì—ˆìŠµë‹ˆë‹¤."
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4620,12 +5377,18 @@ msgstr ""
msgid "GitLab User"
msgstr "GitLab 사용ìž"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "GitLab 프로ì íŠ¸ 내보내기"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "GitLab 싱글 사ì¸ì˜¨ URL"
@@ -4659,15 +5422,24 @@ msgstr ""
msgid "Go Back"
msgstr "ì´ì „으로"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "뒤로 가기"
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr "ì´ë™"
msgid "Go to %{link_to_google_takeout}."
msgstr "%{link_to_google_takeout}ë¡œ ì´ë™í•˜ì‹­ì‹œì˜¤."
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr "Google Code 가져오기"
@@ -4686,9 +5458,18 @@ msgstr ""
msgid "Graph"
msgstr "그래프"
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "그룹"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "그룹 CI/CD 설정"
@@ -4698,6 +5479,9 @@ msgstr "그룹 Git LFS ìƒíƒœ:"
msgid "Group ID"
msgstr "그룹 ID"
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "그룹 Runner"
@@ -4731,6 +5515,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4749,6 +5536,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4758,6 +5548,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "배지"
@@ -4767,18 +5623,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr "그룹 배지를 ì‚¬ìš©ìž ì •ì˜í•©ë‹ˆë‹¤."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "ë°°ì§€ì— ëŒ€í•´ ìžì„¸ížˆ 알아보세요."
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "%{group} ë‚´ì˜ í”„ë¡œì íŠ¸ë¥¼ 다른 ê·¸ë£¹ì— ê³µìœ í•  수 없게 합니다."
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "ì´ ì„¤ì •ì€ %{ancestor_group} ì— ì ìš©ë˜ê³  ì´ í•˜ìœ„ 그룹ì—ì„œ ìž¬ì •ì˜ ë˜ì—ˆìŠµë‹ˆë‹¤."
@@ -4872,6 +5740,9 @@ msgstr "ì´ë¦„으로 검색"
msgid "Have your users email"
msgstr "사용ìžì—게 ì´ë©”ì¼ ë°œì‹ "
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "머릿글 메시지"
@@ -4911,6 +5782,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "호스트 키 ìˆ˜ë™ ìž…ë ¥ 숨기기"
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr "페ì´ë¡œë“œ 숨기기"
@@ -4921,12 +5795,27 @@ msgstr[0] "값 숨기기"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "ì´ë ¥"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Housekeepingì´ ì„±ê³µì ìœ¼ë¡œ 시작ë˜ì—ˆìŠµë‹ˆë‹¤"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4984,15 +5873,18 @@ msgstr "ì‹ë³„ìž"
msgid "Identities"
msgstr "ID"
-msgid "Identity provider single sign on URL"
-msgstr "ID ì œê³µìž ì‹±ê¸€ì‚¬ì¸ì˜¨ URL"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5005,9 +5897,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "ì´ë¯¸ 파ì¼ì´ 있는 경우 아래 %{link_to_cli} 를 사용하여 파ì¼ì„ 푸시 í•  수 있습니다."
-
msgid "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>."
msgstr "HTTP 저장소를 공개ì ìœ¼ë¡œ 액세스할 수 없는 경우, URLì— ì¸ì¦ 정보를 추가하십시오:<code>https://username:password@gitlab.company.com/group/project.git</code>."
@@ -5092,12 +5981,30 @@ msgstr "저장소 가져 오기"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "GitLab Enterprise Editionì„ í†µí•´ ì´ìŠˆ 보드를 í–¥ìƒ ì‹œí‚µë‹ˆë‹¤."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "ì´ìŠˆ 가중치와 GitLab Enterprise Editionì„ í†µí•´ ì´ìŠˆ 관리를 í–¥ìƒ ì‹œí‚¤ì„¸ìš”."
@@ -5113,15 +6020,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "모든 사용ìžê°€ 수ë½í•´ì•¼ 하는 서비스 약관 ë° ê°œì¸ ì •ë³´ 보호 ì •ì±…ì„ í¬í•¨í•©ë‹ˆë‹¤."
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5137,6 +6053,9 @@ msgstr "호환ë˜ì§€ 않는 프로ì íŠ¸"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "ì¸ë¼ì¸"
@@ -5146,9 +6065,18 @@ msgstr "호스트 키를 수ë™ìœ¼ë¡œ ìž…ë ¥"
msgid "Input your repository URL"
msgstr "저장소 URLì„ ìž…ë ¥ 하세요"
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr "GitLab Runner 설치"
@@ -5168,6 +6096,9 @@ msgstr "ì¸ìŠ¤í„´ìŠ¤ 통계 가시성"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "ì¸ìŠ¤í„´ìŠ¤ê°€ 여러 Kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼ 지ì›í•˜ì§€ 않습니다."
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "ì—°ë™"
@@ -5198,6 +6129,21 @@ msgstr "Cycle Analytics 소개"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5216,6 +6162,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "ì´ìŠˆ"
@@ -5228,6 +6177,9 @@ msgstr "ì´ìŠˆ ë³´ë“œ í¬ì»¤ìŠ¤ 모드"
msgid "Issue events"
msgstr "ì´ìŠˆ ì´ë²¤íŠ¸"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "보드"
@@ -5258,7 +6210,10 @@ msgstr "ì´ìŠˆëŠ” 버그, ìž‘ì—… í˜¹ì€ ë…¼ì˜í•  ì•„ì´ë””ì–´ì¼ ìˆ˜ 있습니ë
msgid "Issues closed"
msgstr "ì´ìŠˆ 닫힘"
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5303,12 +6258,30 @@ msgstr "1ì›”"
msgid "Job"
msgstr "Job"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "ìž‘ì—…ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5339,6 +6312,9 @@ msgstr "ìž‘ì—…|ìž‘ì—…ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. by"
msgid "Job|Keep"
msgstr "유지"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "맨 아래로 스í¬ë¡¤"
@@ -5354,9 +6330,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "7ì›”"
@@ -5399,9 +6387,15 @@ msgstr "Kubernetes 설정ë¨"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "Kubernetes 서비스 í†µí•©ì€ ë” ì´ìƒ 사용ë˜ì§€ 않습니다. %{deprecated_message_content} 새로운 <a href=\"%{url}\"/>Kubernetes í´ëŸ¬ìŠ¤í„°</a> 페ì´ì§€"
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Disabled"
@@ -5417,6 +6411,15 @@ msgstr "ë¼ë²¨ ìž‘ì—… 드롭 다운"
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} ì´ìƒ"
@@ -5447,6 +6450,9 @@ msgstr "ë ˆì´ë¸” 승격"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5517,18 +6523,18 @@ msgstr "Kubernetesì— ëŒ€í•´ ë” ì•Œì•„ë³´ê¸°"
msgid "Learn more about Web Terminal"
msgstr ""
+msgid "Learn more about approvals."
+msgstr ""
+
msgid "Learn more about custom project templates"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about signing commits"
msgstr ""
-msgid "Learn more about protected branches"
-msgstr "ë³´í˜¸ëœ ë¸Œëžœì¹˜ë“¤ì— ëŒ€í•´ ë” ì•Œì•„ë³´ê¸°"
-
msgid "Learn more in the"
msgstr "ë” ìžì„¸ížˆ 알아보기"
@@ -5538,6 +6544,9 @@ msgstr "파ì´í”„ë¼ì¸ 스케쥴 문서로부터 ë” ì•Œì•„ë³´ê¸°"
msgid "Leave"
msgstr "나가기"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "그룹 떠나기"
@@ -5622,9 +6631,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "ì´ í”„ë¡œì íŠ¸ì—ì„œ ë¼ì´ì„¼ìŠ¤ %{name}(ì„)를 제거하려고합니다."
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "ë¼ì´ì„¼ìŠ¤"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "최대 %d ì´ë²¤íŠ¸ 만 표시하는 것으로 제한ë©ë‹ˆë‹¤."
@@ -5641,6 +6656,9 @@ msgstr "Gitea 저장소 목ë¡"
msgid "List available repositories"
msgstr "사용 가능한 저장소 목ë¡"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5698,15 +6716,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr "스마트 카드로 로그ì¸"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "로그"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5737,15 +6776,15 @@ msgstr "프로ì íŠ¸ ë¼ë²¨ 관리"
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifest íŒŒì¼ ê°€ì ¸ì˜¤ê¸°"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5770,47 +6809,44 @@ msgstr "í•  ì¼ì„ 완료로 표시"
msgid "Markdown"
msgstr ""
+msgid "Markdown Help"
+msgstr ""
+
msgid "Markdown enabled"
msgstr "마í¬ë‹¤ìš´ 활성화ë¨"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
+msgid "Maven Metadata"
+msgstr "Maven 메타 ë°ì´í„°"
-msgid "MarkdownToolbar|Add a link"
-msgstr ""
+msgid "Max access level"
+msgstr "최대 액세스 수준"
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
-msgstr ""
+msgid "Maximum job timeout"
+msgstr "최대 작업 시간 초과"
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maven Metadata"
-msgstr "Maven 메타 ë°ì´í„°"
-
-msgid "Max access level"
-msgstr "최대 액세스 수준"
-
-msgid "Maximum job timeout"
-msgstr "최대 작업 시간 초과"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "5ì›”"
@@ -5833,21 +6869,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr ""
-
msgid "Merge Request"
msgstr "머지 리퀘스트(MR)"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "머지 리퀘스트(MR)"
msgid "Merge Requests created"
msgstr "머지 리퀘스트(MR) ìƒì„±ë¨"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "머지 ì´ë²¤íŠ¸"
@@ -5857,12 +6899,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "머지 리퀘스트(MR)"
msgid "Merge request approvals"
msgstr "머지 리퀘스트(MR) 승ì¸"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "머지 리퀘스트(MR)"
@@ -5932,18 +6983,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
+msgid "MergeRequest|Filter files"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "MergeRequest|No files found"
msgstr ""
msgid "Merged"
msgstr "머지ë¨"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "메시지"
@@ -6001,14 +7055,17 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
-msgstr "시스템"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
@@ -6049,6 +7106,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "마ì¼ìŠ¤í†¤"
@@ -6094,6 +7157,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr "ì´ ë™ìž‘ì€ ë˜ëŒë¦´ 수 없습니다."
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "저장소 미러하기"
@@ -6112,6 +7178,9 @@ msgstr "미러ë§ëœ 저장소"
msgid "Mirroring repositories"
msgstr "ë¯¸ëŸ¬ë§ ì €ìž¥ì†Œ"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "SSH 키 추가"
@@ -6121,6 +7190,9 @@ msgstr "취소"
msgid "Modal|Close"
msgstr "닫기"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6154,9 +7226,15 @@ msgstr "ë” ë§Žì€ ì •ë³´"
msgid "More information is available|here"
msgstr "여기"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "ë§Žì€ ë³„"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "ì´ë™"
@@ -6166,6 +7244,12 @@ msgstr "ì´ìŠˆ ì´ë™"
msgid "Multiple issue boards"
msgstr "여러 ì´ìŠˆ ë³´ë“œ"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "ì´ë¦„"
@@ -6178,6 +7262,12 @@ msgstr "ì œëª©ì„ í†µí•´ 개별 키 ì´ë¦„ 지정"
msgid "Name:"
msgstr "ì´ë¦„:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6230,21 +7320,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "새로운 파ì´í”„ë¼ì¸ ì¼ì •"
msgid "New Snippet"
msgstr "새 스니펫"
-msgid "New Snippets"
-msgstr "새 스니펫"
-
msgid "New branch"
msgstr "새 브랜치"
msgid "New branch unavailable"
msgstr "새로운 브랜치 사용 불가능"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "새 디렉토리"
@@ -6260,6 +7353,9 @@ msgstr "새 파ì¼"
msgid "New group"
msgstr "새 그룹"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "신규 ID"
@@ -6281,6 +7377,9 @@ msgstr "새로운 파ì´í”„ ë¼ì¸ì€ ë™ì¼í•œ ë¸Œëžœì¹˜ì— ìžˆëŠ” ì´ì „ì˜ ëŒ
msgid "New project"
msgstr "새 프로ì íŠ¸"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "새 ì¼ì •"
@@ -6293,20 +7392,29 @@ msgstr "새 서브그룹"
msgid "New tag"
msgstr "새 태그 "
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "새로 만들기..."
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "아니오"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "ë¼ë²¨ ì—†ìŒ"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "ë‹´ë‹¹ìž ì—†ìŒ"
+msgid "No activities found"
+msgstr ""
msgid "No branches found"
msgstr "브랜치가 존재하지 않습니다."
@@ -6335,7 +7443,7 @@ msgstr ""
msgid "No due date"
msgstr "기한 ì—†ìŒ"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6353,15 +7461,24 @@ msgstr "파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다."
msgid "No issues for the selected time period."
msgstr "ì„ íƒí•œ 기간 ë™ì•ˆ ì´ìŠˆê°€ 없습니다."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "그런 ì´ë¦„ ë˜ëŠ” 설명ì´ìžˆëŠ” ë ˆì´ë¸”ì´ ì—†ìŠµë‹ˆë‹¤."
msgid "No license. All rights reserved"
msgstr "ë¼ì´ì„¼ìŠ¤ê°€ 없습니다. All rights reserved"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr "ì„ íƒí•œ 기간 ë™ì•ˆ 머지 리퀘스트(MR)ê°€ 없습니다."
@@ -6410,9 +7527,6 @@ msgstr "노드"
msgid "None"
msgstr "ì—†ìŒ"
-msgid "Not allowed to merge"
-msgstr "머지가 허용ë˜ì§€ ì•ŠìŒ"
-
msgid "Not available"
msgstr "사용할 수 ì—†ìŒ"
@@ -6428,11 +7542,17 @@ msgstr "비밀 아님"
msgid "Not enough data"
msgstr "ë°ì´í„°ê°€ 충분하지 않습니다."
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "나중ì—"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "마스터 브랜치는 ìžë™ìœ¼ë¡œ 보호ë©ë‹ˆë‹¤. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6545,6 +7665,9 @@ msgstr "11ì›”"
msgid "OK"
msgstr "확ì¸"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "10ì›”"
@@ -6557,10 +7680,19 @@ msgstr "í•„í„°"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "가져오기가 완료ë˜ë©´ 저장소를 SSH를 통해 ë¯¸ëŸ¬ë§ í•  수 있습니다. ë” ì•Œì•„ë³´ê¸° %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "%dê°œì˜ ì¶”ê°€ ì•„ì´í…œ"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6570,6 +7702,12 @@ msgstr ""
msgid "Only admins"
msgstr "관리ìžë§Œ 가능"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6606,6 +7744,9 @@ msgstr "Xcodeì—ì„œ 열기"
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "사ì´ë“œë°” 열기"
@@ -6633,15 +7774,30 @@ msgstr "ìš´ì˜ìž"
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6660,9 +7816,6 @@ msgstr "기타 ë¼ë²¨"
msgid "Other information"
msgstr "다른 정보"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "그렇지 않다면 아래 옵션 중 하나를 ì„ íƒí•˜ëŠ” ê²ƒì´ ì¢‹ìŠµë‹ˆë‹¤."
-
msgid "Outbound requests"
msgstr "외부로 가는 요청"
@@ -6684,6 +7837,12 @@ msgstr "패키지가 제거ë˜ì—ˆìŠµë‹ˆë‹¤."
msgid "Packages"
msgstr "패키지"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "페ì´ì§€"
@@ -6708,18 +7867,21 @@ msgstr "« 처ìŒ"
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr "머지 리퀘스트(MR) 변경 사항 중 ì¼ë¶€"
msgid "Password"
msgstr "패스워드"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6729,6 +7891,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6738,6 +7903,9 @@ msgstr "경로:"
msgid "Pause"
msgstr "중지"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "ì¼ì‹œì •ì§€ëœ 러너는 새로운 ìž‘ì—…ì„ í—ˆìš©í•˜ì§€ 않습니다."
@@ -6756,6 +7924,9 @@ msgstr "성능 최ì í™”"
msgid "Permissions"
msgstr "권한"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6771,21 +7942,24 @@ msgstr ""
msgid "Pipeline"
msgstr "파ì´í”„ë¼ì¸"
-msgid "Pipeline Health"
-msgstr "파ì´í”„ë¼ì¸ ìƒíƒœ"
-
msgid "Pipeline Schedule"
msgstr "파ì´í”„ë¼ì¸ 스케쥴"
msgid "Pipeline Schedules"
msgstr "파ì´í”„ë¼ì¸ 스케쥴"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "파ì´í”„ë¼ì¸ 할당량"
msgid "Pipeline triggers"
msgstr "파ì´í”„ë¼ì¸ 트리거"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "실패 :"
@@ -6834,6 +8008,12 @@ msgstr "변수"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "ì‚¬ìš©ìž ì •ì˜"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "파ì´í”„ë¼ì¸"
@@ -6849,6 +8029,12 @@ msgstr "ì§€ë‚œì£¼ì˜ íŒŒì´í”„ë¼ì¸"
msgid "Pipelines for last year"
msgstr "ì§€ë‚œí•´ì˜ íŒŒì´í”„ë¼ì¸"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "ìžì‹ ìžˆê²Œ 빌드하세요"
@@ -6891,12 +8077,9 @@ msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” 현재 파ì´í”„ë¼ì¸ì„ 실행하ë„ë¡ ì„¤ì •ë˜ì
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
+msgid "Pipeline|Coverage"
msgstr ""
-msgid "Pipeline|Create pipeline"
-msgstr "파ì´í”„ë¼ì¸ ìƒì„±"
-
msgid "Pipeline|Duration"
msgstr ""
@@ -6909,6 +8092,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr "파ì´í”„ë¼ì¸ 실행"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "브랜치 검색"
@@ -6936,9 +8122,21 @@ msgstr "%{pipelineId} 파ì´í”„ë¼ì¸ì„ 중지하고 있습니다."
msgid "Pipeline|all"
msgstr "모ë‘"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "성공"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "스테ì´ì§•"
@@ -6963,15 +8161,33 @@ msgstr "계ì†í•˜ê¸° ì „ì— ì„œë¹„ìŠ¤ ì•½ê´€ì— ë™ì˜í•˜ì‹­ì‹œì˜¤."
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -6981,9 +8197,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "결과를 보려면 최소 í•˜ë‚˜ì˜ í•„í„°ë¥¼ ì„ íƒí•˜ì‹­ì‹œì˜¤"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "ReCAPTCHA를 풀어 주십시오."
@@ -6996,6 +8224,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7005,18 +8236,33 @@ msgstr "저장소를 가져오는 ë™ì•ˆ 기다려주십시오. 완료ë˜ë©´ ìž
msgid "Preferences"
msgstr "환경 설정"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "테마 íƒìƒ‰"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "미리보기"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7044,6 +8290,9 @@ msgstr "Private - 프로ì íŠ¸ ì ‘ê·¼ì€ ê° ì‚¬ìš©ìžë³„ë¡œ 명시ì ìœ¼ë¡œ í—ˆ
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Private - 그룹과 ê·¸ë£¹ì˜ í”„ë¡œì íŠ¸ë“¤ì€ ë©¤ë²„ë“¤ì´ ì—´ëžŒí•  수 있습니다."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "비공개 프로ì íŠ¸ëŠ” ê°œì¸ ë„¤ìž„ìŠ¤íŽ˜ì´ìŠ¤ì—ì„œ 만들어집니다:"
@@ -7066,7 +8315,7 @@ msgid "Profiles|Account scheduled for removal."
msgstr "ê³„ì •ì´ ì‚­ì œë  ì˜ˆì •ìž…ë‹ˆë‹¤."
msgid "Profiles|Activate signin with one of the following services"
-msgstr ""
+msgstr "ë‹¤ìŒ ì„œë¹„ìŠ¤ 중 하나로 ë¡œê·¸ì¸ í™œì„±í™”"
msgid "Profiles|Active"
msgstr ""
@@ -7087,7 +8336,7 @@ msgid "Profiles|Change username"
msgstr "사용ìžëª… 변경"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "ì‚¬ìš©ìž ì´ë¦„ì„ ë³€ê²½í•˜ë©´ ì˜ë„하지 ì•Šì€ ë¬¸ì œê°€ ìƒê¸¸ 수 있습니다."
msgid "Profiles|Choose file..."
msgstr "íŒŒì¼ ì„ íƒ.."
@@ -7194,21 +8443,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "최대 허용ë˜ëŠ” 파ì¼ì˜ í¬ê¸°ëŠ” 200KB입니다."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "ì´ê²ƒì€ 공개 SSH 키가 아닌것 같습니다. ì •ë§ë¡œ 추가 하시겠어요?"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "ì´ ì´ë©”ì¼ì€ 웹 기반 ìž‘ì—…, 수정ì´ë‚˜ mergeì— ì‚¬ìš©ë©ë‹ˆë‹¤. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "ì´ ê·¸ë¦¼ê³¼ 메시지는 프로필과 ì¸í„°íŽ˜ì´ìŠ¤ ì „ì²´ì— ë‚˜íƒ€ë‚©ë‹ˆë‹¤."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7308,6 +8557,15 @@ msgstr "진행률"
msgid "Project"
msgstr "프로ì íŠ¸"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "프로ì íŠ¸ '%{project_name}' ì‚­ì œ 중입니다."
@@ -7323,17 +8581,26 @@ msgstr "'%{project_name}'프로ì íŠ¸ê°€ 성공ì ìœ¼ë¡œ ì—…ë°ì´íŠ¸ë˜ì—ˆìŠµë‹
msgid "Project Badges"
msgstr "프로ì íŠ¸ 배지"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "프로ì íŠ¸ URL"
msgid "Project access must be granted explicitly to each user."
msgstr "프로ì íŠ¸ 액세스는 ê° ì‚¬ìš©ìžì—게 명시ì ìœ¼ë¡œ 부여ë˜ì–´ì•¼í•©ë‹ˆë‹¤."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "프로ì íŠ¸ 아바타"
-msgid "Project avatar in repository: %{link}"
-msgstr "ì €ìž¥ì†Œì˜ í”„ë¡œì íŠ¸ 아바타: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "프로ì íŠ¸ ìƒì„¸"
@@ -7341,6 +8608,9 @@ msgstr "프로ì íŠ¸ ìƒì„¸"
msgid "Project export could not be deleted."
msgstr "프로ì íŠ¸ 내보내기를 삭제할 수 없습니다."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "프로ì íŠ¸ 내보내기가 ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤."
@@ -7350,6 +8620,9 @@ msgstr "프로ì íŠ¸ 내보내기 ë§í¬ê°€ 만료ë˜ì—ˆìŠµë‹ˆë‹¤. 프로ì íŠ¸
msgid "Project export started. A download link will be sent by email."
msgstr "프로ì íŠ¸ 내보내기가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. 다운로드 ë§í¬ëŠ” ì´ë©”ì¼ë¡œ 전송ë©ë‹ˆë‹¤."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7357,6 +8630,12 @@ msgid "Project name"
msgstr "프로ì íŠ¸ ì´ë¦„"
msgid "Project slug"
+msgstr "프로ì íŠ¸ 슬러그"
+
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
msgid "Project:"
@@ -7449,12 +8728,21 @@ msgstr "사용ìžëŠ” ì´ ì €ìž¥ì†Œì— ì¸ì¦ëœ ì´ë©”ì¼ë¡œ ì»¤ë°‹ëœ ì»¤ë°‹ë§Œ
msgid "Projects"
msgstr "프로ì íŠ¸"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "%{group_name}ê³¼ 공유ë˜ëŠ” 프로ì íŠ¸"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "ìžì£¼ 방문"
@@ -7503,6 +8791,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7605,6 +8896,9 @@ msgstr "ì´ ê¸°ëŠ¥ì€ ìž ê²¨ìžˆìŠµë‹ˆë‹¤."
msgid "Promotions|Upgrade plan"
msgstr "업그레ì´ë“œ 플랜"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "보호ë¨"
@@ -7671,6 +8965,12 @@ msgstr "공개 - ì´ ê·¸ë£¹ê³¼ 모든 공개 프로ì íŠ¸ë“¤ì€ ì–´ë–¤ ì¸ì¦ ì—
msgid "Public - The project can be accessed without any authentication."
msgstr "공개 - ì´ í”„ë¡œì íŠ¸ëŠ” ì–´ë–¤ ì¸ì¦ ì—†ì´ë„ 접근할 수 있습니다."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "공용 파ì´í”„ ë¼ì¸"
@@ -7683,6 +8983,12 @@ msgstr "Push"
msgid "Push Rules"
msgstr "Push 규칙"
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "푸쉬 ì´ë²¤íŠ¸"
@@ -7713,6 +9019,9 @@ msgstr "Quick actionì€ ì´ìŠˆ 설명ì´ë‚˜ 댓글 박스ì—ì„œ 사용 가능합
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "ë” ì½ê¸°"
@@ -7728,6 +9037,12 @@ msgstr "실시간 기능"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr "최근 검색"
@@ -7762,6 +9077,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "레지스트리"
@@ -7807,9 +9125,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "아바타 제거"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7819,12 +9146,33 @@ msgstr "우선 순위 삭제"
msgid "Remove project"
msgstr "프로ì íŠ¸ ì‚­ì œ"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "ì´ë¦„ 바꾸기"
@@ -7843,6 +9191,9 @@ msgstr ""
msgid "Repair authentication"
msgstr "ì¸ì¦ 복구"
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7876,6 +9227,18 @@ msgstr "실행 시간"
msgid "Reports|Failure"
msgstr "실패"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7894,6 +9257,12 @@ msgstr "테스트 요약 ê²°ê³¼ 분ì„중"
msgid "Reports|Vulnerability"
msgstr "취약ì "
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "ë³€ê²½ëœ í…ŒìŠ¤íŠ¸ ê²°ê³¼ ì—†ìŒ"
@@ -7906,6 +9275,9 @@ msgstr "저장소 설정"
msgid "Repository URL"
msgstr "저장소 URL"
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7942,6 +9314,17 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "GitLabì— ì•¡ì„¸ìŠ¤ í•  ë•Œ 모든 사용ìžê°€ 서비스 약관 ë° ê°œì¸ ì •ë³´ 취급 ë°©ì¹¨ì— ë™ì˜í•˜ë„ë¡í•˜ì‹­ì‹œì˜¤."
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
msgid "Resend invite"
msgstr ""
@@ -7975,9 +9358,18 @@ msgstr "토론 해결"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8002,12 +9394,18 @@ msgstr ""
msgid "Resume"
msgstr "재개"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "재시ë„"
msgid "Retry this job"
msgstr "ì´ ìž‘ì—… 재시ë„"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "ê²€ì¦ ìž¬ì‹œë„"
@@ -8039,12 +9437,21 @@ msgstr "검토 중 (머지 리퀘스트(MR) !%{mergeRequestId})"
msgid "Revoke"
msgstr "í기"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "로드맵"
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8066,6 +9473,12 @@ msgstr ""
msgid "Runner token"
msgstr "Runner 토í°"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8099,27 +9512,27 @@ msgstr "모든 공유 Runners 파ì´í”„ë¼ì¸ ì‹œê°„ì„ ì‚¬ìš©í–ˆìŠµë‹ˆë‹¤."
msgid "Running"
msgstr "실행중"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "%{group_name}ì˜ SAML SSO"
-msgid "SAML Single Sign On"
-msgstr "SAML Single Sign On"
-
-msgid "SAML Single Sign On Settings"
-msgstr "SAML Single Sign On 설정"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr ""
-
msgid "SSH Keys"
msgstr "SSH 키"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH 호스트 키"
@@ -8129,6 +9542,9 @@ msgstr "SSH 공개키"
msgid "SSL Verification"
msgstr "SSL ê²€ì¦"
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "저장"
@@ -8153,6 +9569,9 @@ msgstr "파ì´í”„ë¼ì¸ 스케줄 저장"
msgid "Save variables"
msgstr "변수 저장"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "새로운 파ì´í”„ë¼ì¸ 스케줄 잡기"
@@ -8162,15 +9581,24 @@ msgstr "예정ë¨"
msgid "Schedules"
msgstr "ì¼ì •"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "파ì´í”„ë¼ì¸ 스케줄ë§"
msgid "Scope"
+msgstr "스코프"
+
+msgid "Scope not supported with disabled 'users_search' feature!"
msgstr ""
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8222,6 +9650,9 @@ msgstr "프로ì íŠ¸ 검색"
msgid "Search users"
msgstr "ì‚¬ìš©ìž ê²€ìƒ‰"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr "프로ì íŠ¸ 검색"
@@ -8249,6 +9680,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "비밀"
@@ -8270,9 +9710,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "ì´ìŠˆ ìƒì„±"
@@ -8288,12 +9725,12 @@ msgstr "ë” ë§Žì€ ì •ë³´"
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
-msgstr "보안 대시보드 문서"
-
msgid "Security Reports|There was an error creating the issue."
msgstr "ì´ìŠˆë¥¼ ìƒì„± í•˜ë˜ ì¤‘ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr ""
@@ -8342,6 +9779,15 @@ msgstr "초대 í•  그룹 ì„ íƒ"
msgid "Select a namespace to fork the project"
msgstr "프로ì íŠ¸ë¥¼ í¬í¬ë¥¼ 위한 ì´ë¦„ ì„ íƒ"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr "저장소 템플릿 ì„ íƒ"
@@ -8351,9 +9797,6 @@ msgstr "시간대 ì„ íƒ"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "기존 Kubernetes í´ëŸ¬ìŠ¤í„°ë¥¼ ì„ íƒí•˜ê±°ë‚˜ 새로 만듭니다."
-msgid "Select assignee"
-msgstr "ë‹´ë‹¹ìž ì„ íƒ"
-
msgid "Select branch/tag"
msgstr "브랜치/태그 ì„ íƒ"
@@ -8384,11 +9827,14 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
-msgstr "ì„ íƒì  ë™ê¸°í™”"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "ì´ë©”ì¼ ì „ì†¡"
@@ -8405,18 +9851,39 @@ msgstr ""
msgid "Sep"
msgstr "9ì›”"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "9ì›”"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "서버 버전"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8432,9 +9899,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8462,9 +9926,18 @@ msgstr "서비스 템플릿"
msgid "Service URL"
msgstr "서비스 URL"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "세션 만료, 프로ì íŠ¸ 제한 ë° ì²¨ë¶€ íŒŒì¼ í¬ê¸°."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "%{protocol} í”„ë¡œí† ì½œì„ í†µí•´ Pull 하거나 Push하려면 ê³„ì •ì— íŒ¨ìŠ¤ì›Œë“œë¥¼ 설정하십시오."
@@ -8480,6 +9953,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr "웹 터미ë„ì˜ ìµœëŒ€ 세션 ì‹œê°„ì„ ì„¤ì •í•©ë‹ˆë‹¤."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "남용 ë³´ê³ ì„œì— ëŒ€í•œ 알림 ì´ë©”ì¼ì„ 설정합니다."
@@ -8489,6 +9965,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "사용ìžì˜ ë¡œê·¸ì¸ ìš”êµ¬ ì‚¬í•­ì„ ì„¤ì •í•©ë‹ˆë‹¤. 반드시 ì´ì¤‘ ì¸ì¦ 하ë„ë¡ ì„¤ì •í•©ë‹ˆë‹¤."
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "CI/CD 설정"
@@ -8504,6 +9992,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8561,9 +10052,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr "ì…œë¡ íŠ¸ëžœì ì…˜"
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "명령 보기"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "완료 로그 표시"
@@ -8573,6 +10070,9 @@ msgstr ""
msgid "Show latest version"
msgstr "최신 버전 보기"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "부모 페ì´ì§€ 보기"
@@ -8631,6 +10131,12 @@ msgstr "ë¡œê·¸ì¸ ì œí•œ"
msgid "Sign-up restrictions"
msgstr "가입 제한"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8640,6 +10146,9 @@ msgstr "í¬ê¸°"
msgid "Size and domain settings for static websites"
msgstr "ì •ì  ì›¹ 사ì´íŠ¸ì˜ í¬ê¸° ë° ë„ë©”ì¸ ì„¤ì •"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Slack 어플리케ì´ì…˜"
@@ -8653,7 +10162,7 @@ msgid "Smartcard"
msgstr "스마트카드"
msgid "Smartcard authentication failed: client certificate header is missing."
-msgstr ""
+msgstr "스마트카드 ì¸ì¦ 실패: í´ë¼ì´ì–¸íŠ¸ ì¸ì¦ì„œ í—¤ë”ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤."
msgid "Snippet Contents"
msgstr ""
@@ -8661,6 +10170,27 @@ msgstr ""
msgid "Snippets"
msgstr "스니펫"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8706,6 +10236,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8733,6 +10266,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 다시 ì‹œë„해주세요."
@@ -8883,6 +10419,9 @@ msgstr "스팸 로그"
msgid "Spam and Anti-bot Protection"
msgstr "스팸 ë° ì•ˆí‹°ë´‡ 보호"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8931,20 +10470,32 @@ msgstr "별표 í‘œì‹œëœ í”„ë¡œì íŠ¸ 활ë™"
msgid "Starred projects"
msgstr "ë³„í‘œëœ í”„ë¡œì íŠ¸"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "ì´ ë³€ê²½ 사항으로 %{new_merge_request} ì„ ì‹œìž‘í•˜ì‹­ì‹œì˜¤."
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
msgid "Start and due date"
-msgstr ""
+msgstr "시작ì¼ê³¼ 마ê°ì¼"
msgid "Start cleanup"
msgstr ""
@@ -8973,6 +10524,9 @@ msgstr "시작ë¨"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9126,6 +10680,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9135,15 +10710,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "스위치 브랜치/태그"
msgid "Sync information"
msgstr "ì •ë³´ ë™ê¸°í™”"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "시스템 훅"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "시스템 정보"
@@ -9153,6 +10737,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9276,12 +10863,22 @@ msgstr ""
msgid "Test coverage parsing"
msgstr "테스트 커버리지 파싱"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "ê°ì‚¬í•©ë‹ˆë‹¤! 다시 보지 않겠습니다."
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9300,6 +10897,12 @@ msgstr "ì´ìŠˆ 트래커는 프로ì íŠ¸ì—ì„œ 개선해야하거나 í•´ê²°í•´ì•
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9312,15 +10915,63 @@ msgstr "해당 단계ì—ì„œ 수집 ëœ ë°ì´í„°ê°€ ì´ë²¤íŠ¸ 모ìŒì— 추가ë
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "í¬í¬ 관계가 제거ë˜ì—ˆìŠµë‹ˆë‹¤."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "%{timeout} ì´í›„ 가져오기 ì‹œê°„ì´ ì´ˆê³¼ë˜ì—ˆìŠµë‹ˆë‹¤. 저장소가 ëŠë¦° 경우 복제/푸시 ì¡°í•©ì„ ì‚¬ìš©í•©ì‹œì˜¤."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "ì´ìŠˆ 단계ì—는 ì´ìŠˆë¥¼ 작성하여 마ì¼ìŠ¤í†¤ìœ¼ë¡œ 지정하는 ë° ê±¸ë¦¬ëŠ” 시간 ë˜ëŠ” ì´ìŠˆ ë³´ë“œì˜ ëª©ë¡ì— ì´ìŠˆë¥¼ 추가하는 ì‹œê°„ì´ í‘œì‹œë©ë‹ˆë‹¤. ì´ ë‹¨ê³„ì˜ ë°ì´í„°ë¥¼ 보기 위해서는 ì´ìŠˆë¥¼ 먼저 작성해야 합니다."
@@ -9330,6 +10981,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "허용ë˜ëŠ” 최대 íŒŒì¼ í¬ê¸°ëŠ” 200KB입니다."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9357,9 +11020,18 @@ msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” ë¡œê·¸ì¸ í•œ 사용ìžê°€ë§Œ 액세스 í•  수 있ì
msgid "The project can be accessed without any authentication."
msgstr "ì´ í”„ë¡œì íŠ¸ëŠ” ì¸ì¦ì—†ì´ 액세스 í•  수 있습니다."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "ì´ í”„ë¡œì íŠ¸ì˜ 저장소가 존재하지 않습니다."
@@ -9378,6 +11050,21 @@ msgstr "Review 단계ì—서는 머지 리퀘스트(MR)를 작성한 후 머지í•
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "Staging 단계ì—서는 MR 머지과 프로ë•ì…˜ í™˜ê²½ì— ì½”ë“œ ë°°í¬ ì‚¬ì´ì˜ ì‹œê°„ì„ ë³´ì—¬ì¤ë‹ˆë‹¤. ë°ì´í„°ë¥¼ Production í™˜ê²½ì— ì²˜ìŒ ë°°í¬í•˜ë©´ ë°ì´í„°ê°€ ìžë™ìœ¼ë¡œ 추가ë©ë‹ˆë‹¤."
@@ -9393,9 +11080,18 @@ msgstr "해당 단계ì—ì„œ 수집 í•œ ê° ë°ì´í„° ìž…ë ¥ì— ì†Œìš” ëœ ì‹œê°„
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9450,9 +11146,21 @@ msgstr "í•  ì¼ì„ 추가하는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
msgid "There was an error deleting the todo."
msgstr "í•  ì¼ì„ 삭제하는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "ì‚¬ìš©ìž í™œë™ ìº˜ë¦°ë”를 로딩하는 ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "알림 ì„¤ì •ì„ ì €ìž¥í•˜ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
@@ -9468,6 +11176,9 @@ msgstr "ì´ ë¼ë²¨ì„ 구ë…í•˜ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
msgid "There was an error when unsubscribing from this label."
msgstr "ì´ ë¼ë²¨ì„ 구ë…ì·¨ì†Œí•˜ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9486,6 +11197,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "ì´ GitLab ì¸ìŠ¤í„´ìŠ¤ëŠ” 공유 Runnersê°€ 제공ë˜ì§€ 않습니다. ì¸ìŠ¤í„´ìŠ¤ 관리ìžëŠ” 관리 ì˜ì—­ì— 공유 Runners를 ë“±ë¡ í•  수 있습니다."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9498,11 +11212,26 @@ msgstr "ì´ ë³´ë“œì˜ ë²”ìœ„ê°€ 축소ë˜ì—ˆìŠµë‹ˆë‹¤."
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
msgid "This date is after the due date, so this epic won't appear in the roadmap."
-msgstr ""
+msgstr "ì´ ë‚ ì§œëŠ” 마ê°ì¼ ì´í›„ì´ë¯€ë¡œ ì´ ì—í”½ì€ ë¡œë“œë§µì— ë‚˜íƒ€ë‚˜ì§€ 않습니다."
msgid "This date is before the start date, so this epic won't appear in the roadmap."
msgstr ""
@@ -9519,6 +11248,9 @@ msgstr "ì´ ë””ë ‰í† ë¦¬"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "ì´ ê·¸ë£¹"
@@ -9534,6 +11266,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr "ì´ ì €ìžì˜ ì´ í”„ë¡œì íŠ¸ì— 대한 첫번째 머지 리퀘스트(MR) 입니다."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "ì´ ì´ìŠˆëŠ” 비밀입니다."
@@ -9582,6 +11317,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "ì´ ìž‘ì—…ì€ ëŒ€ê¸° ìƒíƒœì´ë©° Runnerê°€ 실행하기를 기다리고 있습니다."
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9603,19 +11344,25 @@ msgstr "즉, 빈 저장소를 만들거나 기존 저장소를 가져올 때까ì
msgid "This merge request is locked."
msgstr "ì´ ë¨¸ì§€ 리퀘스트(MR)는 잠겨있습니다."
-msgid "This option is disabled as you don't have write permissions for the current branch"
-msgstr "현재 ë¸Œëžœì¹˜ì— ëŒ€í•œ 쓰기 ê¶Œí•œì´ ì—†ìœ¼ë¯€ë¡œ ì´ ì˜µì…˜ì´ ë¹„í™œì„±í™”ë©ë‹ˆë‹¤."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
+msgstr "현재 ë¸Œëžœì¹˜ì— ëŒ€í•œ 쓰기 ê¶Œí•œì´ ì—†ìœ¼ë¯€ë¡œ ì´ ì˜µì…˜ì´ ë¹„í™œì„±í™”ë©ë‹ˆë‹¤."
+
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "여러 프로ì íŠ¸ì—ì„œ 정보를 ì½ì„ 수 없으므로 ì´ íŽ˜ì´ì§€ë¥¼ 사용할 수 없습니다."
msgid "This page will be removed in a future release."
msgstr "ì´ íŽ˜ì´ì§€ëŠ” 앞으로 릴리스ì—ì„œ ì œê±°ë  ì˜ˆì •ìž…ë‹ˆë‹¤."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9639,6 +11386,9 @@ msgstr ""
msgid "This repository"
msgstr "ì´ ì €ìž¥ì†Œ"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9648,10 +11398,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9666,6 +11416,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9732,9 +11485,6 @@ msgstr "%s 개월 전"
msgid "Timeago|%s months remaining"
msgstr "%s 개월 남ìŒ"
-msgid "Timeago|%s seconds ago"
-msgstr "%sì´ˆ ì „"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s ì´ˆ 남ìŒ"
@@ -9962,6 +11712,9 @@ msgstr "오늘"
msgid "Todo"
msgstr "Todo"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "í• ì¼ë“¤"
@@ -10001,6 +11754,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr "표시 í•  변경 ì‚¬í•­ì´ ë„ˆë¬´ 많습니다."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "ì´ ê¸°ì—¬ë„"
@@ -10025,6 +11784,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr "퀵 ì•¡ì…˜ 시간 추ì "
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10037,6 +11799,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "수ë™ìœ¼ë¡œ 실행해주세요."
@@ -10046,6 +11811,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10070,6 +11847,9 @@ msgstr "서비스 ë°ìŠ¤í¬ 켜기"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "2단계 ì¸ì¦"
@@ -10079,15 +11859,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr "ì°¨ì´ì ì„ ì½ì–´ë“¤ì¼ 수 없습니다. %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10100,6 +11892,9 @@ msgstr ""
msgid "Unknown"
msgstr "ì•Œ 수 ì—†ìŒ"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "잠금 해제"
@@ -10160,21 +11955,39 @@ msgstr ""
msgid "Update"
msgstr "ì—…ë°ì´íŠ¸"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "지금 ì—…ë°ì´íŠ¸"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "ì—…ë°ì´íŠ¸ì¤‘..."
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "고급 글로벌 ê²€ìƒ‰ì„ í™œì„±í™”í•˜ê¸° 위해 í”Œëžœì„ ì—…ê·¸ë ˆì´ë“œí•˜ì„¸ìš”."
@@ -10190,6 +12003,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10214,18 +12030,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "업로드하려면 í´ë¦­í•˜ì‹­ì‹œì˜¤."
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "추천"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "ping ì‚¬ìš©ì´ í™œì„±í™” ë˜ì§€ 않았습니다."
msgid "Usage statistics"
msgstr "사용 통계"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10256,18 +12084,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "ì‚¬ìš©ìž ì„¤ì •"
msgid "User and IP Rate Limits"
msgstr "ì‚¬ìš©ìž ë° IP ì†ë„ 제한"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "활ë™"
@@ -10346,18 +12210,39 @@ msgstr ""
msgid "Users"
msgstr "사용ìžë“¤"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "여러가지 컨테ì´ë„ˆ 레지스트리 설정."
@@ -10382,9 +12267,6 @@ msgstr "ê²€ì¦ë¨"
msgid "Version"
msgstr "버전"
-msgid "View %{alerts}"
-msgstr "%{alerts} 보기"
-
msgid "View app"
msgstr "앱 보기"
@@ -10394,8 +12276,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "문서 보기"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "ì—픽 ëª©ë¡ ë³´ê¸°"
@@ -10406,12 +12288,15 @@ msgstr "파ì¼ë³´ê¸° @ "
msgid "View group labels"
msgstr "그룹 ë¼ë²¨ 보기"
-msgid "View issue"
-msgstr "ì´ìŠˆ 보기"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "GitLabì—ì„œ 보기"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "작업 보기"
@@ -10445,6 +12330,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10481,12 +12369,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10535,6 +12429,12 @@ msgstr ""
msgid "Web terminal"
msgstr "웹 터미ë„"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10553,9 +12453,16 @@ msgstr "Runnerê°€ 잠겨 있으면 다른 프로ì íŠ¸ì— 할당 í•  수 없습ë
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "사용 설정ë˜ë©´ 사용ìžëŠ” GitLab ì•½ê´€ì´ ìˆ˜ë½ ë  ë•Œê¹Œì§€ GitLabì„ ì‚¬ìš©í•  수 없습니다."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
msgstr ""
@@ -10568,6 +12475,9 @@ msgstr "누가 ì´ ê·¸ë£¹ì„ ë³¼ 수 있게 ë˜ë‚˜ìš”?"
msgid "Wiki"
msgstr "위키"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "위키 Clone"
@@ -10679,6 +12589,9 @@ msgstr "페ì´ì§€ 만들기"
msgid "Wiki|Create page"
msgstr "페ì´ì§€ 만들기"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "페ì´ì§€ 편집"
@@ -10697,6 +12610,9 @@ msgstr "페ì´ì§€ 버전"
msgid "Wiki|Pages"
msgstr "페ì´ì§€"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "위키 페ì´ì§€"
@@ -10709,12 +12625,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "액세스 요청 철회"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "예"
@@ -10745,6 +12673,9 @@ msgstr "í¬í¬ 관계를 소스 프로ì íŠ¸ %{forked_from_project}ì— ëŒ€í•´ ì 
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "%{project_full_name} (ì„) 를 다른 소유ìžì—게 ì´ì „하려고합니다. \"ì •ë§ë¡œ\" 확실합니까?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "ì½ê¸°ì „ìš© GitLab ì¸ìŠ¤í„´ìŠ¤ë¥¼ 사용중입니다."
@@ -10760,6 +12691,15 @@ msgstr "ëª…ë ¹ì¤„ì„ í†µí•´ 프로ì íŠ¸ë¥¼ ìƒì„±í•  수 있습니다."
msgid "You can also star a label to make it a priority label."
msgstr "ë¼ë²¨ì— 별표를 붙여 우선순위 ë¼ë²¨ì„ 만들 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10769,6 +12709,12 @@ msgstr "Kubernetes í´ëŸ¬ìŠ¤í„°ì— Runner를 쉽게 설치할 수 있습니다.
msgid "You can move around the graph by using the arrow keys."
msgstr "화살표 키를 사용하여 그래프를 움ì§ì¼ 수 있습니다."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œì—만 파ì¼ì„ 추가 í•  수 있습니다."
@@ -10778,6 +12724,9 @@ msgstr "ë¸Œëžœì¹˜ì— ìžˆì„ ë•Œë§Œ 파ì¼ì„ 편집할 수 있습니다."
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10787,12 +12736,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr "ì½ê¸° ì „ìš© GitLab ì¸ìŠ¤í„´ìŠ¤ì—는 쓰기가 불가능합니다."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10811,12 +12778,27 @@ msgstr "ìŠ¹ì¸ ëœ ì• í”Œë¦¬ì¼€ì´ì…˜ì´ 없습니다."
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤."
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "프로ì íŠ¸ ìˆ«ìž í•œë„ì— ë„달했습니다."
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10829,7 +12811,7 @@ msgstr "강제로 ìž ê¸ˆì„ ì œê±°í•˜ë ¤ë©´ ê´€ë¦¬ìž ê¶Œí•œì´ ìžˆì–´ì•¼í•©ë‹ˆë
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10841,12 +12823,24 @@ msgstr "ê¶Œí•œì´ í•„ìš”í•©ë‹ˆë‹¤."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "ì´ë©”ì¼ë¡œ ì•Œë¦¼ì„ ë°›ì§€ 않습니다."
@@ -10874,6 +12868,15 @@ msgstr "ë‹¹ì‹ ì˜ í”„ë¡œí•„ì— SSH 키를 등ë¡í•˜ê¸° 전까지 SSH를 통해 í
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "올바른 ë³€ê²½ì‚¬í•­ì„ ê°€ì ¸ì˜¤ë ¤ë©´ 다른 ì´ë¦„ì˜ ë¸Œëžœì¹˜ë¥¼ 사용해야합니다."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10907,6 +12910,15 @@ msgstr "ë‹¹ì‹ ì˜ í• ì¼"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "승ì¸ëœ 애플리케ì´ì…˜ (%{size})"
@@ -10925,9 +12937,15 @@ msgstr "ë³€ê²½ì‚¬í•­ì´ ì»¤ë°‹ë˜ì—ˆìŠµë‹ˆë‹¤. 커밋 %{commitId} %{commitStats}
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "ëŒ“ê¸€ì´ ê³µê°œì— í‘œì‹œë˜ì§€ 않습니다."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10943,15 +12961,30 @@ msgstr ""
msgid "Your name"
msgstr "ê·€í•˜ì˜ ì´ë¦„"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "ë‚´ 프로ì íŠ¸"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "ì‚­ì œëœ ì‚¬ìš©ìž"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "ì „"
@@ -10961,12 +12994,15 @@ msgstr ""
msgid "among other things"
msgstr "다른 것들 중"
-msgid "assign yourself"
-msgstr "ìžì‹ ì„ 담당ìžë¡œ 지정"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "브랜치 ì´ë¦„"
@@ -10991,14 +13027,34 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11028,6 +13084,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11037,6 +13096,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11046,12 +13114,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11094,9 +13174,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11138,9 +13227,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11162,6 +13248,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11180,9 +13269,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11193,12 +13291,33 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
-msgstr "명령줄 지침"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr ""
@@ -11224,6 +13343,9 @@ msgstr ""
msgid "deploy token"
msgstr "ë°°í¬ í† í°"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "사용 안 함"
@@ -11253,6 +13375,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} ëª…ë ¹ì€ ì˜ˆìƒì‹œê°„ì„ ê°€ìž¥ 마지막 명령으로 ì—…ë°ì´íŠ¸ 합니다."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "ì´ í”„ë¡œì íŠ¸ì— 대해"
@@ -11262,15 +13399,9 @@ msgstr ""
msgid "group"
msgstr ""
-msgid "help"
-msgstr ""
-
msgid "here"
msgstr "여기"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://나ì˜-bitbucket-server"
@@ -11332,6 +13463,9 @@ msgstr ""
msgid "latest version"
msgstr "최신 버전"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "ë¼ì´ì„ ìŠ¤ 관리"
@@ -11351,6 +13485,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11387,6 +13524,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "ë‹¤ìŒ ì‚¬ìš©ìžì— ì˜í•´ 승ì¸ë¨: "
@@ -11459,17 +13599,20 @@ msgstr "로컬ì—ì„œ 머지"
msgid "mrWidget|Merge request approved"
msgstr "머지 리퀘스트(MR) 승ì¸ë¨"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "머지 리퀘스트(MR)승ì¸ë¨; 추가ì ìœ¼ë¡œ 승ì¸í•  수 있습니다."
msgid "mrWidget|Merged by"
msgstr "머지:"
-msgid "mrWidget|No Approval required"
-msgstr "ìŠ¹ì¸ í•„ìš” ì—†ìŒ"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "ìŠ¹ì¸ í•„ìš” ì—†ìŒ; 여전히 승ì¸í•  수 있습니다."
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "Web IDEì—ì„œ 열기"
@@ -11518,6 +13661,9 @@ msgstr "ë˜ëŒë¦¬ê¸°"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "새로운 머지 리퀘스트(MR)ì—ì„œ ì´ ë¨¸ì§€ 리퀘스트(MR)ë¡œ ë˜ëŒë¦¬ê¸°"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "설정:"
@@ -11628,12 +13774,19 @@ msgstr "패스워드"
msgid "personal access token"
msgstr "ê°œì¸ ì—‘ì„¸ìŠ¤ 토í°"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr "ê°œì¸ í‚¤ì™€ ì¸ì¦ì„œê°€ ì¼ì¹˜í•˜ì§€ 않습니다."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "프로ì íŠ¸"
@@ -11663,9 +13816,36 @@ msgid "reply"
msgid_plural "replies"
msgstr[0] "답변"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11690,6 +13870,9 @@ msgstr "시작ë¨"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po
index 01bbcaaa9c6..9018bdd7cb2 100644
--- a/locale/mn_MN/gitlab.po
+++ b/locale/mn_MN/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: mn\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:07\n"
+"PO-Revision-Date: 2019-04-12 12:29\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po
index ef5040725b3..e6e7657d028 100644
--- a/locale/nb_NO/gitlab.po
+++ b/locale/nb_NO/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: nb\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:07\n"
+"PO-Revision-Date: 2019-04-12 12:29\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index 17bc141ae0c..b1cb8543b19 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:11\n"
+"PO-Revision-Date: 2019-04-12 12:37\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " Status"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " en"
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" in projecten"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr "%{counter_storage} (%{counter_repositories} repositories, %{counter_buil
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr "%{count} andere toebedeelden"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} deelnemer"
@@ -148,7 +173,7 @@ msgstr "%{filePath} verwijderd"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} meer"
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr "%{group_docs_link_start}Groepen%{group_docs_link_end} stellen u in staat
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} wordt verwijderd! Weet je het zeker?"
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr "%{loadingIcon} Gestart"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} is vergrendeld door GitLab Gebruiker %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "%{name};'s avatar"
@@ -178,6 +212,15 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% compleet"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr "%{usage_ping_link_start}Lees meer%{usage_ping_link_end} over welke infor
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr "+ %{count} meer"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} meer"
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] "1 %{type} aanpassing"
msgstr[1] "%{count} %{type} aanpassingen"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 gesloten issue"
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] "1 gesloten merge request"
msgstr[1] "%d gesloten merge requests"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 groep"
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] "1 gebruiker"
msgstr[1] "%d gebruikers"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "1e bijdrage!"
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "2FA ingeschakeld"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "Over GitLab"
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr "Toegangstokens"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr "Account"
msgid "Account and limit"
msgstr "Account en limiet"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Actief"
@@ -451,36 +568,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr "Nieuwe map toevoegen"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
msgstr ""
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr "Alles"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Uiterlijk"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Applicatie"
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Auteur"
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Bladeren in map"
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "door"
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr "Grafieken"
msgid "Chat"
msgstr "Chat"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "overgeslagen"
@@ -1812,6 +2183,9 @@ msgstr "geslaagd"
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "overgeslagen"
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr "Gecommit door"
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Vergelijk"
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3338,7 +3853,7 @@ msgid "Download tar.gz"
msgstr ""
msgid "Download zip"
-msgstr "Download zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/pa_IN/gitlab.po b/locale/pa_IN/gitlab.po
index 1f1f71a35e9..3fe2383be2c 100644
--- a/locale/pa_IN/gitlab.po
+++ b/locale/pa_IN/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: pa-IN\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:08\n"
+"PO-Revision-Date: 2019-04-12 12:28\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 39d0494790f..cb99176aef0 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: pl\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:08\n"
+"PO-Revision-Date: 2019-04-12 12:30\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -44,8 +53,12 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -156,12 +169,28 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr "%{count} więcej beneficjentów"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -182,7 +211,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -191,6 +220,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -200,6 +235,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -212,6 +250,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -261,6 +308,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -270,6 +320,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -296,6 +349,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -310,6 +370,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -359,6 +422,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -368,6 +434,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -383,6 +458,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -416,7 +494,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -428,18 +518,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -470,12 +581,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -485,6 +602,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -515,34 +638,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -566,18 +716,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -614,6 +773,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -695,12 +857,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Zaawansowane pozwolenia, Magazyn Dużych Plików i Dwuczynnikowe ustawienia autoryzacji."
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -714,6 +885,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -723,15 +897,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -744,6 +936,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Zezwalaj użytkownikom na żądanie dostępu"
@@ -762,6 +960,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -783,9 +984,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Wystąpił błąd podczas dodawania wersji roboczej do dyskusji."
@@ -795,6 +993,15 @@ msgstr "Wystąpił błąd podczas dodawania nowej wersji roboczej."
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -807,18 +1014,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "Wystąpił błąd podczas usuwania komentarza"
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -831,6 +1047,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -843,6 +1062,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -858,6 +1080,9 @@ msgstr "Wystąpił błąd podczas ładowania danych wykresu"
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -900,12 +1125,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "Wystąpił błąd podczas aktualizacji komentarza"
@@ -963,6 +1194,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -975,15 +1209,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -996,6 +1248,52 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1011,15 +1309,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1032,15 +1345,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1053,6 +1375,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1107,9 +1432,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1125,6 +1447,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1140,10 +1465,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1152,6 +1477,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1170,6 +1501,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1209,16 +1543,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Proces Automatyczny DevOps został włączony i będzie używany, jeśli nie zostanie znaleziony alternatywny plik konfiguracyjny CI. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1344,6 +1672,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "Zamierzasz odrzucić swoją recenzję, co usunie wszystkie Twoje oczekujące komentarze. Usunięte komentarze %{strong_start}nie mogą%{strong_end} zostać przywrócone."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1581,6 +1915,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1602,9 +1942,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1623,6 +1972,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1656,6 +2008,9 @@ msgstr "Ciągłe wdrażanie do produkcji przy użyciu stopniowego wdrażania prz
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1671,6 +2026,9 @@ msgstr "Proces Automatyczny DevOps zostanie uruchomiony, jeśli nie zostanie zna
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1686,6 +2044,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1698,21 +2059,36 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Certyfikat odcisku palca"
-
msgid "Change Weight"
msgstr ""
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1737,6 +2113,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1749,6 +2128,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1776,9 +2161,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1800,25 +2182,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "Wybierz pomiędzy <code>klonem</code> lub <code>pobierz</code> aby uzyskać najnowszy kod aplikacji"
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1845,6 +2227,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1878,6 +2263,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1893,6 +2281,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1905,6 +2296,9 @@ msgstr "Utwórz symbole wieloznaczne"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1914,12 +2308,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr "Szukaj środowisk"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "jest niedostępna: %{reason}"
@@ -1977,6 +2377,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1992,9 +2395,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} To spowoduje dodanie dodatkowych zasobów, takich jak moduł równoważenia obciążenia, co może wiązać się z dodatkowymi kosztami w zależności od dostawcy hostingu, na którym jest zainstalowany Twój klaster Kubernetes. Jeśli korzystasz z Google Kubernetes Engine, możesz %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2034,18 +2434,15 @@ msgstr "Dodanie integracji do Twojej grupy spowoduje udostępnienie klastra we w
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "Po zainstalowaniu Ingress będziesz musiał wskazać swoje symbole wieloznaczne DNS na wygenerowanym zewnętrzny adresie IP, aby zobaczyć swoją aplikację po jej wdrożeniu. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Wystąpił błąd podczas próby pobrania stref projektu: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Wystąpił błąd podczas próby skontaktowania się z Google Cloud API. Spróbuj ponownie później."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -2082,6 +2479,12 @@ msgstr "Wybierz aplikacje do zainstalowania w Twoim klastrze Kubernetes. Do zain
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Wybierz, które z Twoich środowisk będą używać tego klastra."
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,19 +2494,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2151,8 +2554,8 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner łączy się z repozytorium tego projektu i wykonuje zadania CI/CD, przesyłając wyniki i wdrażając aplikacje do produkcji."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Projekt Platformy Google Cloud"
@@ -2178,14 +2581,14 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Jeśli konfigurujesz wiele klastrów i używasz Auto DevOps, %{help_link_start}zapoznaj się najpierw z tym%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Aby pokazać stan zdrowia klastra, musimy zaopatrzyć Twój klaster w Prometheus, aby zebrać wymagane dane."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Wprowadź Address IP"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Wprowadzanie umożliwia kierowanie żądań do usług na podstawie hosta lub ścieżki żądania, centralizując szereg usług w jeden punkt początkowy."
@@ -2193,15 +2596,18 @@ msgstr "Wprowadzanie umożliwia kierowanie żądań do usług na podstawie hosta
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Zainstaluj Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2229,7 +2635,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Nazwa Domeny Knative:"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Kondycja klastra Kubernetes"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2286,9 +2689,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2301,10 +2701,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2313,15 +2713,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2352,7 +2749,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,16 +2785,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2409,7 +2806,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2418,10 +2815,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2460,9 +2863,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2472,6 +2872,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2481,15 +2884,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2587,6 +3002,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2656,6 +3074,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2671,12 +3095,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2740,6 +3176,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2767,18 +3206,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2791,6 +3221,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2815,9 +3248,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2833,9 +3263,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2845,10 +3299,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2860,6 +3314,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2884,9 +3341,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2956,6 +3410,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2995,9 +3452,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3040,6 +3494,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3064,15 +3521,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3112,6 +3578,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3124,6 +3593,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3137,6 +3621,9 @@ msgstr[3] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3275,12 +3762,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3359,6 +3879,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3398,6 +3921,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3443,6 +3972,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3464,6 +3996,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3476,18 +4011,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3503,6 +4074,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3530,6 +4104,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3539,9 +4119,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3557,16 +4134,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,13 +4173,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3611,6 +4194,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3662,15 +4251,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3683,6 +4290,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3734,6 +4353,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3782,6 +4404,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3797,12 +4431,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3866,12 +4536,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3881,12 +4545,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3920,6 +4590,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3938,9 +4614,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3962,13 +4635,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3995,12 +4689,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4034,9 +4734,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4094,7 +4791,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4121,6 +4818,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4185,9 +4894,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4203,6 +4909,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4296,13 +5005,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4332,12 +5047,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4347,6 +5071,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4371,9 +5098,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4392,6 +5116,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4437,6 +5164,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4545,9 +5275,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4563,6 +5308,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4593,6 +5341,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4632,24 +5386,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4668,9 +5443,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4692,6 +5464,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4713,6 +5488,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4767,15 +5551,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4794,9 +5587,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4806,6 +5608,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4839,6 +5644,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4857,6 +5665,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4866,6 +5677,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4875,18 +5752,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4980,6 +5869,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5019,6 +5911,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5032,12 +5927,27 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5095,15 +6005,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5116,9 +6029,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5203,12 +6113,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5224,15 +6152,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5248,6 +6185,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5257,9 +6197,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5282,6 +6231,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5312,6 +6264,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5342,6 +6312,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5372,7 +6345,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5417,12 +6393,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5453,6 +6447,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5468,9 +6465,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5513,9 +6522,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5531,6 +6546,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5561,6 +6585,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5634,16 +6661,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5655,6 +6682,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5739,9 +6769,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5761,6 +6797,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5818,15 +6857,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5857,15 +6917,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5890,46 +6950,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5953,10 +7010,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5965,9 +7022,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5977,12 +7040,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6052,16 +7124,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6121,13 +7196,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6169,6 +7247,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6214,6 +7298,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6232,6 +7319,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6241,6 +7331,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6274,9 +7367,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6286,6 +7385,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6298,6 +7403,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6353,13 +7464,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6368,6 +7479,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6383,6 +7497,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6404,6 +7521,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6416,19 +7536,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6458,7 +7587,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6476,15 +7605,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6533,9 +7671,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6551,10 +7686,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6668,6 +7809,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6680,6 +7824,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6687,6 +7837,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6696,6 +7849,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6732,6 +7891,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6759,15 +7921,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6786,9 +7963,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6810,6 +7984,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6834,18 +8014,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6855,6 +8038,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6864,6 +8050,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6882,6 +8071,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6897,21 +8089,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6960,6 +8155,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6975,6 +8176,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7017,10 +8224,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7035,6 +8239,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7062,9 +8269,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7089,15 +8308,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7122,6 +8371,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7131,18 +8383,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7170,6 +8437,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7320,21 +8590,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7434,6 +8704,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7449,16 +8728,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7467,6 +8755,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7476,6 +8767,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7485,6 +8779,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7575,12 +8875,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7629,6 +8938,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7731,6 +9043,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7797,6 +9112,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7809,6 +9130,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7839,6 +9166,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7854,6 +9184,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7891,6 +9227,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7936,9 +9275,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7948,12 +9296,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7972,6 +9341,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8005,6 +9377,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8023,6 +9407,12 @@ msgstr "Wyniki podsumowania testu sÄ… analizowane"
msgid "Reports|Vulnerability"
msgstr "Wrażliwość"
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "brak zmienionych wyników testów"
@@ -8035,6 +9425,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8071,6 +9464,23 @@ msgstr "Wymagaj od wszystkich użytkowników w tej grupie do ustawienia uwierzyt
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Wymagaj od wszystkich użytkowników akceptacji Warunków Usługi i Polityki Prywatności, gdy będą chcieli korzystać z GitLab."
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
msgstr ""
@@ -8104,9 +9514,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr "Metryki odpowiedzi (AWS ELB)"
@@ -8131,12 +9550,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8171,12 +9596,21 @@ msgstr "Recenzowanie (żądanie scalenia !%{mergeRequestId})"
msgid "Revoke"
msgstr "Cofnij"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "Harmonogram"
msgid "Run CI/CD pipelines for external repositories"
msgstr "Uruchom procesy CI/CD dla zewnętrznych repozytoriów"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8198,6 +9632,12 @@ msgstr "Runner uruchamia zadania z przypisanych projektów"
msgid "Runner token"
msgstr "Token robotnika"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "Robotnik nie otrzyma żadnych nowych zadań"
@@ -8231,25 +9671,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8261,6 +9701,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8285,6 +9728,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8294,15 +9740,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8354,6 +9809,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8381,6 +9839,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8402,9 +9869,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8420,10 +9884,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8474,6 +9938,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8483,9 +9956,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8516,10 +9986,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8537,18 +10010,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8564,9 +10058,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8594,9 +10085,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8612,6 +10112,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8621,6 +10124,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8636,6 +10151,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8693,9 +10211,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8705,6 +10229,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8766,6 +10293,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8775,6 +10308,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8796,6 +10332,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8841,6 +10398,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr "Coś poszło nie tak podczas pobierania kontrybucji członków grupy"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Coś poszło nie tak podczas pobierania środowisk dla tego żądania scalenia. Spróbuj ponownie."
@@ -8868,6 +10428,9 @@ msgstr "Coś poszło nie tak, nie można uzyskać projektów operacyjnych"
msgid "Something went wrong, unable to remove project"
msgstr "Coś poszło nie tak, nie można usunąć projektu"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9018,6 +10581,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr "Ochrona przed spamem i przed botami"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Określeni Wykonawcy"
@@ -9066,15 +10632,27 @@ msgstr "Aktywność Projektów oznaczonych gwiazdką"
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Rozpocznij recenzjÄ™"
@@ -9108,6 +10686,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9261,6 +10842,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9270,15 +10872,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr "Synchronizuj informacje"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9288,6 +10899,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "Metryki systemowe (Niestandardowe)"
@@ -9411,12 +11025,25 @@ msgstr "Przetestuj Logowanie Jednokrotne SAML"
msgid "Test coverage parsing"
msgstr "Przetwarzanie pokrycia testowego"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9435,6 +11062,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Certyfikat X509 służy do użycia w przypadku, gdy do komunikacji z zewnętrzną usługą autoryzacji jest wymagany wspólny TLS. Jeśli pozostanie puste, certyfikat serwera będzie nadal sprawdzany podczas uzyskiwania dostępu za pośrednictwem protokołu HTTPS."
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "Zakreślacz znaków pomaga ci utrzymać linię tematyczną do %{titleLength} znaków i zawinąć body do %{bodyLength}, aby można je było odczytać w git."
@@ -9447,15 +11080,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Połączenie upłynie po %{timeout}. W przypadku repozytoriów, którym zajmuje to dłużej, użyj kombinacji klonuj/pchnij."
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "Wdrożenie tego zadania do %{environmentLink} nie powiodło się."
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9465,6 +11146,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9492,9 +11185,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "Zbiór danych pseudonimizera jest wyłączony. Po włączeniu GitLab uruchomi zadanie w tle, które będzie wytwarzać pseudonimiczne pliki CSV bazy danych GitLab, które zostaną przesłane do skonfigurowanego katalogu przechowywania obiektów."
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9513,6 +11215,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9528,9 +11245,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Akcja aktualizacji zakończy się po upływie %{number_of_minutes} minut. W przypadku dużych repozytoriów użyj kombinacji klonuj/pchnij."
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "Polecenie ping jest wyłączone i nie można go skonfigurować za pomocą tego formularza."
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "Mapa użytkownika to dokument JSON mapujący użytkowników Google Code, którzy uczestniczyli w Twoich projektach, w celu zaimportowania ich adresów e-mail i nazw użytkowników do GitLab. Możesz to zmienić poprzez zmianę wartości po prawej stronie <code>:</code>. Pamiętaj, aby zachować występujące podwójne cudzysłowy, inną interpunkcję i adres e-mail lub nazwę użytkownika po lewej stronie."
@@ -9585,9 +11311,21 @@ msgstr "Wystąpił błąd podczas ładowania zadań do wykonania."
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9603,6 +11341,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9621,6 +11362,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9633,6 +11377,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9654,6 +11413,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9669,6 +11431,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9717,6 +11482,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9738,10 +11509,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9750,7 +11527,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9774,6 +11551,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9783,10 +11563,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9801,6 +11581,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9867,9 +11650,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10103,6 +11883,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10142,6 +11925,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10166,6 +11955,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10178,6 +11970,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10187,6 +11982,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10211,6 +12018,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10220,15 +12030,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10241,6 +12063,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10301,21 +12126,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10331,6 +12174,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10397,18 +12255,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10487,18 +12381,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10523,9 +12438,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10535,7 +12447,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10547,12 +12459,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10586,6 +12501,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10622,12 +12540,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10676,6 +12600,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10694,9 +12624,19 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
msgstr ""
@@ -10709,6 +12649,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10820,6 +12763,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10838,6 +12784,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10850,12 +12799,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10886,6 +12847,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10901,6 +12865,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10910,6 +12883,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10919,6 +12898,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10928,12 +12910,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10952,12 +12952,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10970,7 +12985,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10982,12 +12997,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11015,6 +13042,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11048,6 +13084,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11066,9 +13111,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11084,15 +13135,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11102,12 +13168,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11132,6 +13201,20 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11139,6 +13222,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11146,6 +13232,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11181,6 +13276,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11190,6 +13288,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11199,12 +13306,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11247,9 +13366,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11297,9 +13425,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11321,6 +13446,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11339,9 +13467,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11355,10 +13492,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11389,6 +13547,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11424,22 +13585,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11506,6 +13676,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11528,6 +13701,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11564,6 +13740,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11636,16 +13815,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11701,6 +13883,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11817,12 +14002,22 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11858,9 +14053,36 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11885,6 +14107,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index c331c2e5cc6..41d12896559 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: pt-BR\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:08\n"
+"PO-Revision-Date: 2019-04-13 15:37\n"
+
+msgid " Please sign in."
+msgstr " Por favor, entre usando sua conta."
msgid " Status"
msgstr " Status"
+msgid " Try to %{action} this file again."
+msgstr " Tentar %{action} este arquivo novamente."
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr " Você precisa fazer isso antes de %{grace_period_deadline}."
+
msgid " and"
msgstr " e"
@@ -32,16 +41,18 @@ msgstr[0] " melhorado em %d ponto"
msgstr[1] " melhorado em %d pontos"
msgid " or "
-msgstr ""
+msgstr " ou "
msgid " or <#epic id>"
-msgstr ""
+msgstr " ou <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " ou <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" em projetos"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d comentário"
+msgstr[1] "%d comentários"
msgid "%d commit"
msgid_plural "%d commits"
@@ -54,7 +65,7 @@ msgstr[0] "%d commit atrás"
msgstr[1] "%d commits atrás"
msgid "%d commits"
-msgstr ""
+msgstr "%d commits"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -78,8 +89,8 @@ msgstr[1] "%d issues"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d issue selecionada"
+msgstr[1] "%d issues selecionadas"
msgid "%d layer"
msgid_plural "%d layers"
@@ -126,12 +137,26 @@ msgstr "%{counter_storage} (%{counter_repositories} repositórios, %{counter_bui
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "%{count} aprovação necessária de %{name}"
+msgstr[1] "%{count} aprovações necessárias de %{name}"
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{count} aprovações de %{name}"
+
msgid "%{count} more"
-msgstr ""
+msgstr "mais %{count}"
msgid "%{count} more assignees"
msgstr "mais %{count} responsáveis"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{count} de %{required} aprovações de %{name}"
+
+msgid "%{count} of %{total}"
+msgstr "%{count} de %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} participante"
@@ -148,8 +173,8 @@ msgstr "%{filePath} excluído"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} mais"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} não encontrado neste commit"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}Grupos%{group_docs_link_end} permitem que você gerencie e colabore em vários projetos. Os membros de um grupo têm acesso a todos os seus projetos."
@@ -157,8 +182,14 @@ msgstr "%{group_docs_link_start}Grupos%{group_docs_link_end} permitem que você
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} será removido! Você tem certeza?"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} indisponível"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr "A visibilidade de %{level_name} foi restringida pelo administrador."
+
msgid "%{link_start}Read more%{link_end} about role permissions"
-msgstr ""
+msgstr "%{link_start}Saiba mais%{link_end} sobre permissões"
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} Iniciado"
@@ -166,6 +197,9 @@ msgstr "%{loadingIcon} Iniciado"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} está bloqueado pelo usuário do GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr "%{mrText}, essa issue foi fechada automaticamente."
+
msgid "%{name}'s avatar"
msgstr "Avatar de %{name}"
@@ -178,26 +212,35 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% completado"
+msgid "%{service_title} activated."
+msgstr "%{service_title} ativado."
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr "Configurações de %{service_title} salvadas, mas não ativadas."
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr "%{spammable_titlecase} foi enviado para Akismet com sucesso."
+
msgid "%{state} epics"
-msgstr ""
+msgstr "%{state} épicos"
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgstr[1] "%{strong_start}%{branch_count}%{strong_end} Branches"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgstr[1] "%{strong_start}%{commit_count}%{strong_end} Commits"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} Arquivos"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgstr[1] "%{strong_start}%{tag_count}%{strong_end} Tags"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -217,10 +260,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "%{usage_ping_link_start}Saiba mais%{usage_ping_link_end} sobre quais informações são compartilhadas com o GitLab Inc."
msgid "%{user_name} profile page"
-msgstr ""
+msgstr "Página de perfil de %{user_name}"
+
+msgid "(No changes)"
+msgstr "(Nenhuma mudança)"
msgid "(external source)"
-msgstr ""
+msgstr "(fonte externa)"
msgid "+ %{count} more"
msgstr "+ %{count} mais"
@@ -228,8 +274,11 @@ msgstr "+ %{count} mais"
msgid "+ %{moreCount} more"
msgstr "%{moreCount} mais"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} mais"
+
msgid ", or "
-msgstr ""
+msgstr ", ou "
msgid "- Runner is active and can process any new jobs"
msgstr "- O runner está ativo e pode processar novas tarefas"
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] "1 mudança de %{type}"
msgstr[1] "%{count} mudanças de %{type}"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "1 dia"
+msgstr[1] "%d dias"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 issue fechada"
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] "1 merge request fechado"
msgstr[1] "%d merge requests fechados"
+msgid "1 day"
+msgstr "1 dia"
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 grupo"
@@ -295,17 +352,29 @@ msgid_plural "%d users"
msgstr[0] "1 usuário"
msgstr[1] "%d usuários"
+msgid "1 week"
+msgstr "1 semana"
+
msgid "1st contribution!"
msgstr "1ª contribuição!"
msgid "2FA"
-msgstr ""
+msgstr "A2F"
msgid "2FA enabled"
msgstr "Autenticação de 2 passos ativada"
+msgid "3 days"
+msgstr "3 dias"
+
+msgid "3 hours"
+msgstr "3 horas"
+
+msgid "30 minutes"
+msgstr "30 minutos"
+
msgid "403|Please contact your GitLab administrator to get permission."
-msgstr ""
+msgstr "Por favor entre em contato com o seu administrador do GitLab para obter permissão."
msgid "403|You don't have the permission to access this page."
msgstr "Você não tem permissão para acessar essa página."
@@ -319,6 +388,9 @@ msgstr "Página não encontrada"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "Por favor, contacte o administrador do GitLab se você acha que isso é um erro."
+msgid "8 hours"
+msgstr "8 horas"
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> adicionará \"Por <a href=\"#\">@johnsmith</a>\" a todas as issues e comentários originalmente criados por johnsmith@example.com e definirá <a href=\"#\">@johnsmith</a> como o responsável em todas as issues originalmente atribuídas a johnsmith@example.com."
@@ -347,13 +419,25 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong> pushes, mais que <strong>%{commits}</strong> commits por <strong>%{people}</strong> contribuidores."
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>Apagar</strong> branch de origem"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "'Runner' é um processo que executa uma tarefa. Você pode configurar quantos Runners você precisar."
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Uma coleção de gráficos sobre Integração Contínua"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr "Um template de aplicação console .NET Core customizável para qualquer projeto .NET Code"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Um site Gitbook que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Um site Hexo que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Um site Hugo que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Um site Jekyll que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
msgid "A default branch cannot be chosen for an empty project."
msgstr "Um branch padrão não pode ser escolhido para um projeto vazio."
@@ -364,18 +448,39 @@ msgstr "Um usuário excluído"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "Um membro da equipe de abusos GitLab irá rever a sua avaliação assim que possível."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr "Um commit de merge é criado para cada merge, e só é permitido o merge desde que não haja conflito."
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr "Um commit de merge é criado para cada merge, mas só é permitido o merge se merge de fast-forward for possível. Dessa forma, você pode se certificar de que, se essa merge request compilaria com sucesso, após o merge para o branch destino ela também compilaria."
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "Um novo \"branch\" será criado no seu \"fork\" e um novo merge request será iniciado."
+msgid "A new impersonation token has been created."
+msgstr "Um novo token de representação foi criado."
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Um site HTML simples que usa Netlify para CI/CD em vez do Gitlab, mas ainda com todas as outras ótimas funcionalidades do Gitlab."
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "Um projeto é onde você armazena seus arquivos (repositório), planeja seu trabalho (issues), e publica sua documentação (wiki), %{among_other_things_link}."
+msgid "A ready-to-go template for use with Android apps."
+msgstr "Um modelo pronto para uso com aplicativos Android."
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "Um modelo pronto para uso com aplicativos iOS Swift."
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "Uma expressão regular que será usada para encontrar a saída do test coverage no rastreador de tarefa. Deixe em branco para desativar"
msgid "A user with write access to the source branch selected this option"
msgstr "Um usuário com permissão de escrita no branch de origem selecionou esta opção"
+msgid "API Help"
+msgstr "Ajuda da API"
+
msgid "About GitLab"
msgstr "Sobre o GitLab"
@@ -395,7 +500,7 @@ msgid "Abuse reports"
msgstr "Relatórios de abuso"
msgid "Accept invitation"
-msgstr ""
+msgstr "Aceitar convite"
msgid "Accept terms"
msgstr "Aceitar os temos"
@@ -406,12 +511,18 @@ msgstr "MR aceito"
msgid "Access Tokens"
msgstr "Tokens de acesso"
+msgid "Access denied for your LDAP account."
+msgstr "Acesso negado para sua conta LDAP."
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "Acesso negado! Por favor, verifique se você pode adicionar chaves de deploy neste repositório."
msgid "Access expiration date"
msgstr "Data de expiração do acesso"
+msgid "Access forbidden. Check your access level."
+msgstr "Acesso proibido. Verifique seu nível de acesso."
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "Acesso a '%{classification_label}' não permitido"
@@ -421,6 +532,12 @@ msgstr "Conta"
msgid "Account and limit"
msgstr "Conta e limites"
+msgid "Action to take when receiving an alert."
+msgstr "Ação a ser tomada ao receber um alerta."
+
+msgid "Activate Service Desk"
+msgstr "Ativar Central de Serviços"
+
msgid "Active"
msgstr "Ativo"
@@ -434,10 +551,10 @@ msgid "Add"
msgstr "Adicionar"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Adicionar CHANGELOG"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "Adicionar CONTRIBUTING"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "Adicione Webhooks de grupo e GitLab Enterprise Edition."
@@ -449,43 +566,70 @@ msgid "Add Kubernetes cluster"
msgstr "Adicionar cluster Kubernetes"
msgid "Add README"
-msgstr ""
+msgstr "Adicionar README"
+
+msgid "Add a bullet list"
+msgstr "Adicionar uma lista de marcadores"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Adicionar um comentário geral para este %{noteable_name}."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "Adicione uma homepage ao seu wiki que contenha informações sobre o seu projeto e o GitLab irá exibi-lo aqui ao invés desta mensagem."
+msgid "Add a link"
+msgstr "Adicionar um link"
+
+msgid "Add a numbered list"
+msgstr "Adicionar uma lista numerada"
+
msgid "Add a table"
msgstr "Adicionar uma tabela"
+msgid "Add a task list"
+msgstr "Adicionar uma lista de tarefas"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "Coloque um texto adicional para aparecer em todas as comunicações por email. Limite de %{character_limit} caracteres"
+msgid "Add approver(s)"
+msgstr "Adicionar aprovador(es)"
+
+msgid "Add approvers"
+msgstr "Adicionar aprovadores"
+
+msgid "Add bold text"
+msgstr "Adicionar texto em negrito"
+
msgid "Add comment now"
msgstr "Adicionar comentário"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "Adicione cabeçalho e rodapé aos emails. Por favor, note que as configurações de cor só serão aplicadas dentro da interface do aplicativo"
+
msgid "Add image comment"
msgstr "Adicionar comentário de imagem"
+msgid "Add italic text"
+msgstr "Adicionar texto em itálico"
+
msgid "Add license"
msgstr "Adicionar licença"
+msgid "Add list"
+msgstr "Adicionar lista"
+
msgid "Add new application"
msgstr "Adicionar novo aplicativo"
msgid "Add new directory"
msgstr "Adicionar novo diretório"
-msgid "Add projects"
-msgstr "Adicionar projetos"
-
msgid "Add reaction"
msgstr "Adicionar reação"
msgid "Add to project"
-msgstr ""
+msgstr "Adicionar ao projeto"
msgid "Add to review"
msgstr "Adicionar à revisão"
@@ -497,23 +641,32 @@ msgid "Add user(s) to the group:"
msgstr "Adicionar usuário(s) ao grupo:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Adicionar usuários ou grupos com permissão para aprovar todos os merge request"
msgid "Add users to group"
msgstr "Adicionar usuários ao grupo"
+msgid "Added at"
+msgstr "Adicionado em"
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "A adição de novos aplicativos está desativada na sua instância do GitLab. Entre em contato com seu administrador do GitLab para obter a permissão"
msgid "Additional text"
msgstr "Texto adicional"
+msgid "Aditional minutes"
+msgstr "Minutos adicionais"
+
msgid "Admin Area"
msgstr "Ãrea do Administrador"
msgid "Admin Overview"
msgstr "Visão Geral do Administrador"
+msgid "Admin Section"
+msgstr "Seção de Admin"
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "Você está prestes a excluir permanentemente o usuário %{username}. Issues, merge requests, e grupos vinculados a ele serão transferidos para um \"usuário fantasma\" em todo o sistema. Para evitar a perda de dados, considere o uso do recurso %{strong_start}bloquear usuário%{strong_end}. Depois de %{strong_start}Deletar o usuário%{strong_end}, não será possível desfazer ou recuperá-lo."
@@ -548,40 +701,43 @@ msgid "AdminProjects|Delete project"
msgstr "Excluir projeto"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Domínio de Auto DevOps"
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr "Ativar runners compartilhados para novos projetos"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Variáveis de ambiente são protegidas por padrão"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "Especifique um domínio a ser usado por padrão para os estágios de Auto Review Application e Auto Deploy de cada projeto."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "Ao criar uma nova variável de ambiente, ela será protegida por padrão."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "A2F desativada"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "A2F ativada"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Ativo"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "Administrador"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "Administradores"
msgid "AdminUsers|Block user"
msgstr "Bloquear usuário"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Bloqueado"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "Não é possível desbloquear usuários bloqueados pelo LDAP"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "Excluir o usuário %{username} e suas contribuições?"
@@ -596,28 +752,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "Excluir o usuário e suas contribuições"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Externo"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "É você!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Novo usuário"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "Nenhum usuário encontrado"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "Procure por nome, e-mail ou nome de usuário"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "Procurar usuários"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Enviar e-mail para usuários"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Ordenar por"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "Para confirmar, digite %{projectName}"
@@ -626,46 +782,76 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "Para confirmar, digite %{username}"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "Usuário será bloqueado"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "Sem projetos"
+
+msgid "Advanced"
+msgstr "Avançado"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Permissões avançadas, armazenamento de arquivos grandes e configurações de autenticação de dois fatores."
+msgid "Advanced search functionality"
+msgstr "Funcionalidade de pesquisa avançada"
+
msgid "Advanced settings"
msgstr "Configurações avançadas"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "Após uma atualização de senha bem-sucedida, você será redirecionado para a tela de login."
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Alerta"
msgstr[1] "Alertas"
msgid "Alerts"
-msgstr ""
+msgstr "Alertas"
msgid "All"
msgstr "Todos"
+msgid "All Members"
+msgstr "Todos os membros"
+
msgid "All changes are committed"
msgstr "Houve commit com todas as mudanças"
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
-msgstr "Todas as funcionalidades estão habilitadas para projetos em branco, a partir de templates ou ao importar, mas você pode desativá-los posteriormente nas configurações do projeto."
+msgstr "Todos os recursos estão habilitados para projetos em branco, a partir de modelos ou ao importar, mas você pode desativá-los posteriormente nas configurações do projeto."
msgid "All issues for this milestone are closed. You may close this milestone now."
-msgstr ""
+msgstr "Todas as issues para este marco estão fechadas. Você pode fechar este marco agora."
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr "Todos os conflitos de merge foram resolvidos. A merge request pode agora ter o merge realizado."
+
+msgid "All todos were marked as done."
+msgstr "Todos os afazeres foram marcados como concluídos."
msgid "All users"
msgstr "Todos os usuários"
+msgid "All users must have a name."
+msgstr "Todos os usuários devem ter um nome."
+
msgid "Allow \"%{group_name}\" to sign you in"
-msgstr ""
+msgstr "Permitir que \"%{group_name}\" adicione você"
msgid "Allow commits from members who can merge to the target branch."
msgstr "Permitir commits de membros que podem fazer merge ao branch de destino."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr "Permitir que proprietários de grupos gerenciem configurações relacionadas ao LDAP"
+
+msgid "Allow mirrors to be set up for projects"
+msgstr "Permitir espelhos de projetos"
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr "Permitir que apenas os protocolos selecionados sejam usados para acesso ao Git."
+
msgid "Allow projects within this group to use Git LFS"
msgstr "Permitir que projetos dentro deste grupo usem o Git LFS"
@@ -678,6 +864,12 @@ msgstr "Permitir renderização de diagramas PlantUML em documentos Asciidoc."
msgid "Allow requests to the local network from hooks and services."
msgstr "Permitir requisições de hooks e serviços para a rede local."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr "Permitir que esta chave também possa fazer push para o repositório? (Padrão permite apenas acesso para pull.)"
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr "Permitir que os usuários registrem qualquer aplicativo para usar o GitLab como um provedor OAuth"
+
msgid "Allow users to request access"
msgstr "Permitir que os usuários solicitem acesso"
@@ -685,7 +877,7 @@ msgid "Allow users to request access if visibility is public or internal."
msgstr "Permitir que os usuários solicitem acesso se a visibilidade for pública ou interna."
msgid "Allowed to fail"
-msgstr ""
+msgstr "Permitido falhar"
msgid "Allows you to add and manage Kubernetes clusters."
msgstr "Permite adicionar e gerenciar clusters do Kubernetes."
@@ -696,6 +888,9 @@ msgstr "Também chamado de \"Emissor\" ou \"Identificador de confiança em terce
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "Também chamado de \"URL de serviços terceiros\" ou \"URL de resposta\""
+msgid "Alternate support URL for help page"
+msgstr "Suporte alternativo URL para a página de ajuda"
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "Alternativamente, você pode usar um %{personal_access_token_link}. Quando você cria seu Token de Acesso Pessoal, você precisará selecionar o escopo do <code>repositório</code>, para que possamos exibir uma lista de seus repositórios públicos e privados que estão disponíveis para se conectar."
@@ -717,9 +912,6 @@ msgstr "Um campo vazio do usuário do GitLab adicionará o nome completo do usuÃ
msgid "An error has occurred"
msgstr "Ocorreu um erro"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Ocorreu um erro ao adicionar um rascunho à discussão."
@@ -727,7 +919,16 @@ msgid "An error occurred adding a new draft."
msgstr "Ocorreu um erro ao adicionar um novo rascunho."
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "Um erro ocorreu ao criar o novo branch."
+
+msgid "An error occurred fetching the approval rules."
+msgstr "Erro ao buscar as regras de aprovação."
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "Ocorreu um erro ao buscar os aprovadores da nova regra."
+
+msgid "An error occurred fetching the dropdown data."
+msgstr "Erro ao buscar os dados do dropdown."
msgid "An error occurred previewing the blob"
msgstr "Erro ao pré-visualizar o blob"
@@ -741,18 +942,27 @@ msgstr "Ocorreu um erro ao atualizar o peso do issue"
msgid "An error occurred while adding approver"
msgstr "Ocorreu um erro ao adicionar o aprovador"
+msgid "An error occurred while deleting the approvers group"
+msgstr "Ocorreu um erro ao excluir o grupo de aprovadores"
+
msgid "An error occurred while deleting the comment"
msgstr "Ocorreu um erro ao remover o comentário"
msgid "An error occurred while detecting host keys"
msgstr "Ocorreu um erro ao detectar as chaves do host"
+msgid "An error occurred while disabling Service Desk."
+msgstr "Um erro ocorreu ao desabilitar a Central de Serviços."
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Erro ao ignorar alerta. Atualize a página e tente novamente."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "Erro ao remover alerta. Atualize a página e tente novamente."
+msgid "An error occurred while enabling Service Desk."
+msgstr "Um erro ocorreu ao habilitar a Central de Serviços."
+
msgid "An error occurred while fetching markdown preview"
msgstr "Erro ao gerar pré-visualização do markdown"
@@ -765,6 +975,9 @@ msgstr "Erro ao recuperar informações da barra lateral"
msgid "An error occurred while fetching stages."
msgstr "Ocorreu um erro o enquanto busca estágios."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr "Um erro ocorreu ao consultar o endereço da Central de Serviços."
+
msgid "An error occurred while fetching the job log."
msgstr "Ocorreu um erro na recuperação de logs da tarefa."
@@ -777,6 +990,9 @@ msgstr "Ocorreu um erro ao recuperar as tarefas."
msgid "An error occurred while fetching the pipeline."
msgstr "Erro ao recuperar informações da pipeline."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr "Ocorreu um erro ao buscar as releases. Por favor, tente novamente."
+
msgid "An error occurred while getting projects"
msgstr "Erro ao recuperar projetos"
@@ -792,6 +1008,9 @@ msgstr "Ocorreu um erro ao carregar os dados do gráfico"
msgid "An error occurred while loading commit signatures"
msgstr "Erro ao carregar as assinaturas de commit"
+msgid "An error occurred while loading designs. Please try again."
+msgstr "Ocorreu um erro ao carregar designs. Por favor, tente novamente."
+
msgid "An error occurred while loading diff"
msgstr "Erro ao carregar o Diff"
@@ -802,7 +1021,7 @@ msgid "An error occurred while loading the file"
msgstr "Erro ao carregar o arquivo"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "Um erro ocorreu ao carregar os detalhes da inscrição."
msgid "An error occurred while making the request."
msgstr "Erro ao fazer a requisição."
@@ -811,10 +1030,10 @@ msgid "An error occurred while removing approver"
msgstr "Ocorreu um erro ao remover o aprovador"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "Ocorreu um erro ao remover épicos."
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "Um erro ocorreu ao remover issues."
msgid "An error occurred while rendering KaTeX"
msgstr "Erro ao renderizar o KaTeX"
@@ -834,12 +1053,18 @@ msgstr "Ocorreu um erro ao salvar o status de substituição do LDAP. Por favor,
msgid "An error occurred while saving assignees"
msgstr "Erro ao salvar assignees"
+msgid "An error occurred while saving the approval settings"
+msgstr "Ocorreu um erro ao salvar as configurações de aprovação"
+
msgid "An error occurred while subscribing to notifications."
msgstr "Ocorreu um erro ao inscrever às notificações."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Ocorreu um erro ao desinscrever às notificações."
+msgid "An error occurred while updating approvers"
+msgstr "Ocorreu um erro ao atualizar os aprovadores"
+
msgid "An error occurred while updating the comment"
msgstr "Ocorreu um erro durante a atualização do comentário"
@@ -847,56 +1072,59 @@ msgid "An error occurred while validating username"
msgstr "Erro ao validar o nome de usuário"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "Ocorreu um erro ao confirmar suas alterações."
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "Ocorreu um erro ao buscar o rastreamento do trabalho."
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "Ocorreu um erro ao recuperar o último pipeline."
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "Erro ao carregar todos os arquivos."
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "Erro ao carregar o conteúdo do arquivo."
msgid "An error occurred whilst loading the file."
-msgstr ""
+msgstr "Erro ao carregar o arquivo."
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "Ocorreu um erro ao carregar as alterações do merge request."
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "Ocorreu um erro ao carregar dados de versão do merge request."
msgid "An error occurred whilst loading the merge request."
-msgstr ""
+msgstr "Ocorreu um erro ao carregar o merge request."
msgid "An error occurred whilst loading the pipelines jobs."
-msgstr ""
+msgstr "Ocorreu um erro ao carregar os trabalhos de pipelines."
msgid "An error occurred. Please try again."
msgstr "Ocorreu um erro. Tente novamente."
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "Um erro inesperado ocorreu enquanto verificava o ambiente de projeto."
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "Ocorreu um erro inesperado ao verificar os runners do projeto."
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "Ocorreu um erro inesperado durante a comunicação com o terminal da web."
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "Ocorreu um erro inesperado ao iniciar o terminal da web."
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "Ocorreu um erro inesperado ao parar o terminal da web."
msgid "Analytics"
msgstr "Telemetria"
+msgid "Ancestors"
+msgstr "Ancestrais"
+
msgid "Anonymous"
msgstr "Anônimo"
@@ -909,15 +1137,33 @@ msgstr "Qualquer um"
msgid "Any Label"
msgstr "Qualquer etiqueta"
+msgid "Any encrypted tokens"
+msgstr "Qualquer tokens criptografados"
+
msgid "Appearance"
msgstr "Aparência"
+msgid "Appearance was successfully created."
+msgstr "Aparência criada com sucesso."
+
+msgid "Appearance was successfully updated."
+msgstr "A aparência foi atualizada com sucesso."
+
msgid "Application"
msgstr "Aplicativo"
msgid "Application ID"
msgstr "ID da aplicação"
+msgid "Application settings saved successfully"
+msgstr "As configurações do aplicativo foram salvas com sucesso"
+
+msgid "Application was successfully destroyed."
+msgstr "Aplicação foi destruída com sucesso."
+
+msgid "Application was successfully updated."
+msgstr "Aplicação foi atualizada com sucesso."
+
msgid "Application: %{name}"
msgstr "Aplicativo: %{name}"
@@ -925,19 +1171,57 @@ msgid "Applications"
msgstr "Aplicações"
msgid "Applied"
-msgstr ""
+msgstr "Aplicado"
msgid "Apply suggestion"
-msgstr ""
+msgstr "Aplicar sugestão"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] "Aprovações deste membro não são revogadas."
+msgstr[1] "Aprovações destes membros não são revogadas."
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr "Você está prestes a remover o grupo de aprovação %{name} que tem %{nMembers}."
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d membro"
+msgstr[1] "%d membros"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] "%{count} aprovação obrigatória de %{membersCount}"
+msgstr[1] "%{count} aprovações obrigatórias de %{membersCount}"
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr "Todos os membros com a função de Desenvolvedor ou superior e proprietários código (qualquer um dos)"
+
+msgid "ApprovalRule|Members"
+msgstr "Membros"
+
+msgid "ApprovalRule|Name"
+msgstr "Nome"
+
+msgid "ApprovalRule|No. approvals required"
+msgstr "No. de aprovações requeridas"
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr "por exemplo, QA, segurança, etc."
msgid "Approvals"
-msgstr ""
+msgstr "Aprovações"
msgid "Approvals required"
-msgstr ""
+msgstr "Aprovações necessárias"
msgid "Approvers"
-msgstr ""
+msgstr "Aprovadores"
msgid "Apr"
msgstr "Abr"
@@ -945,14 +1229,29 @@ msgstr "Abr"
msgid "April"
msgstr "Abril"
+msgid "Archive jobs"
+msgstr "Arquivar tarefas"
+
+msgid "Archive project"
+msgstr "Arquivar projeto"
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "Projeto arquivado! Repositório e outros recursos de projeto são somente leitura"
msgid "Archived projects"
msgstr "Projetos arquivados"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr "Arquivar o projeto fará com que ele seja inteiramente de leitura. Estará oculto no painel e não aparecerá nas pesquisas. <strong>O repositório não poderá receber novos commits e nenhum issue, comentário ou outras entidades poderão ser criados.</strong>"
+
msgid "Are you sure"
-msgstr ""
+msgstr "Você tem certeza"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr "Tem certeza que deseja arquivar este projeto?"
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr "Tem certeza que deseja desarquivar este projeto?"
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Tem certeza que deseja excluir este agendamento de pipeline?"
@@ -964,6 +1263,9 @@ msgid "Are you sure you want to lose unsaved changes?"
msgstr "Você tem certeza de que quer perder as alterações não salvas?"
msgid "Are you sure you want to lose your issue information?"
+msgstr "Você deseja mesmo perder as informações da sua issue?"
+
+msgid "Are you sure you want to permanently delete this license?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
@@ -972,14 +1274,20 @@ msgstr "Tem certeza de que deseja regenerar a chave pública? Você precisará a
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Você tem certeza que quer remover %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "Você deseja mesmo remover o aprovador %{name}"
+
msgid "Are you sure you want to remove approver %{name}?"
-msgstr ""
+msgstr "Você deseja mesmo remover o aprovador %{name}?"
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr "Você deseja mesmo remover o grupo %{name}"
msgid "Are you sure you want to remove group %{name}?"
-msgstr ""
+msgstr "Você deseja mesmo remover o grupo %{name}?"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "Você deseja mesmo remover o anexo?"
msgid "Are you sure you want to remove this identity?"
msgstr "Você tem certeza de que deseja excluir este item?"
@@ -987,6 +1295,9 @@ msgstr "Você tem certeza de que deseja excluir este item?"
msgid "Are you sure you want to reset registration token?"
msgstr "Você tem certeza que quer recriar o token de registro?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Você tem certeza que quer reiniciar o token de status de saúde?"
@@ -997,7 +1308,7 @@ msgid "Are you sure you want to unlock %{path_lock_path}?"
msgstr "Você tem certeza que quer destravar %{path_lock_path}?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "Tem certeza de que deseja cancelar o recebimento do %{type}: %{link_to_noteable_text}?"
msgid "Are you sure?"
msgstr "Você tem certeza?"
@@ -1018,7 +1329,7 @@ msgid "Assertion consumer service URL"
msgstr "URL de serviço do consumidor de asserção"
msgid "Assets"
-msgstr ""
+msgstr "Recursos"
msgid "Assign custom color like #FF0000"
msgstr "Coloque uma cor personalizada, como #FF0000"
@@ -1030,7 +1341,7 @@ msgid "Assign milestone"
msgstr "Atribuir marco"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "Atribua alguns issues a este marco."
msgid "Assign to"
msgstr "Atribuir à"
@@ -1041,9 +1352,6 @@ msgstr "Problemas Atribuídos"
msgid "Assigned Merge Requests"
msgstr "Merge requests com responsável"
-msgid "Assigned to :name"
-msgstr "Atribuído a :name"
-
msgid "Assigned to me"
msgstr "Atribuído a mim"
@@ -1059,8 +1367,11 @@ msgstr "Listas de responsáveis mostram todas as issues atribuídas ao usuário
msgid "Assignee(s)"
msgstr "Responsável(is)"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr "Pelo menos uma aprovação de um proprietário do código é necessária para alterar arquivos que coincidem com as respectivas regras CODEOWNER."
+
msgid "Attach a file"
-msgstr ""
+msgstr "Anexar um arquivo"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}"
@@ -1074,18 +1385,24 @@ msgstr "Ago"
msgid "August"
msgstr "Agosto"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Log de autenticação"
+msgid "Authentication failed: %{error_message}"
+msgstr "Autenticação falhou: %{error_message}"
+
msgid "Authentication log"
msgstr "Log de autenticação"
msgid "Authentication method"
msgstr "Método de autenticação"
+msgid "Authentication method updated"
+msgstr "Método de autenticação atualizado"
+
+msgid "Authentication via U2F device failed."
+msgstr "Autenticação via dispositivo U2F falhou."
+
msgid "Author"
msgstr "Autor"
@@ -1093,7 +1410,7 @@ msgid "Authorization code:"
msgstr "Código de autorização:"
msgid "Authorization key"
-msgstr ""
+msgstr "Chave de autorização"
msgid "Authorization was granted by entering your username and password in the application."
msgstr "A autorização foi concedida digitando seu nome de usuário e senha no aplicativo."
@@ -1104,6 +1421,9 @@ msgstr "Autorizar"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "Autorizar %{link_to_client} usar sua conta?"
+msgid "Authorized %{new_chat_name}"
+msgstr "%{new_chat_name} autorizado"
+
msgid "Authorized At"
msgstr "Autorizado em"
@@ -1143,38 +1463,32 @@ msgstr "Saiba mais em %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "A pipeline de Auto DevOps foi ativada e será usada se não for encontrada configuração CI alternativa nos arquivos. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "Você pode automaticamente construir e testar sua aplicação, se você %{link_to_auto_devops_settings} para este projeto. Você pode também fazer o deploy automaticamente, se você %{link_to_add_kubernetes_cluster}."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "adicionar um cluster Kubernetes"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "ativar Auto DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "Marcado automaticamente como interno de usuário padrão"
-msgid "Automatically resolved"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
+msgid "Automatically resolved"
+msgstr "Resolvido automaticamente"
+
msgid "Available"
msgstr "Disponível"
msgid "Available group Runners: %{runners}"
-msgstr ""
+msgstr "Runners de grupo disponíveis: %{runners}"
msgid "Available shared Runners:"
-msgstr ""
+msgstr "Runners compartilhados disponíveis:"
msgid "Available specific runners"
-msgstr ""
+msgstr "Runners específicos disponíveis"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "Imagem de perfil para %{assigneeName}"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "Imagem de perfil para %{name}"
msgid "Avatar will be removed. Are you sure?"
msgstr "Foto de perfil será removida. Tem certeza?"
@@ -1278,6 +1592,12 @@ msgstr "Descartar revisão?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "Você está prestes a descartar sua revisão, o que excluirá todos os seus comentários pendentes. Os comentários excluídos %{strong_start}não podem%{strong_end} ser restaurados."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr "Cuidado. Alterar o namespace do projeto pode ter efeitos colaterais indesejados."
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr "Tenha cuidado. Renomear o repositório de um projeto pode ter efeitos colaterais indesejados."
+
msgid "Begin with the selected commit"
msgstr "Comece com o commit selecionado"
@@ -1360,10 +1680,10 @@ msgid "Bitbucket import"
msgstr "Importar do Bitbucket"
msgid "Block"
-msgstr ""
+msgstr "Bloquear"
msgid "Blocked"
-msgstr ""
+msgstr "Bloqueado"
msgid "Blog"
msgstr "Blog"
@@ -1515,6 +1835,12 @@ msgstr "configurações do projeto"
msgid "Branches|protected"
msgstr "protegido"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "Navegar no Diretório"
@@ -1528,34 +1854,46 @@ msgid "Browse files"
msgstr "Navegar pelos arquivos"
msgid "Built-in"
-msgstr ""
+msgstr "Embutido"
msgid "Business"
-msgstr ""
+msgstr "Negócios"
msgid "Business metrics (Custom)"
msgstr "Métricas de negócios (personalizadas)"
+msgid "Buy EE"
+msgstr "Comprar EE"
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr "Comprar GitLab Edição Enterprise"
+
msgid "By %{user_name}"
-msgstr ""
+msgstr "Por %{user_name}"
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr "Por padrão, o GitLab envia e-mails em formatos HTML e texto plano para que os clientes possam escolher o formato a ser utilizado. Desative esta opção se você quiser apenas enviar e-mails em formato de texto plano."
msgid "ByAuthor|by"
msgstr "por"
msgid "CHANGELOG"
-msgstr ""
+msgstr "CHANGELOG"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "Gráficos CI / CD"
msgid "CI / CD Settings"
msgstr "Configurações de CI / CD"
msgid "CI Lint"
-msgstr ""
+msgstr "Checar syntaxe de CI"
+
+msgid "CI variables"
+msgstr "Variáveis de CI"
msgid "CI will run using the credentials assigned above."
msgstr "O CI será executado usando as credenciais atribuídas acima."
@@ -1590,6 +1928,9 @@ msgstr "Entrega contínua para a produção usando deploy incremental cronometra
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "Padrão para o pipeline Auto DevOps"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "Estratégia de deploy"
@@ -1605,23 +1946,29 @@ msgstr "O pipeline de Auto DevOps será executado se nenhum arquivo de configura
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr "grupo habilitado"
+
msgid "CICD|instance enabled"
msgstr "Instância habilitada"
msgid "CONTRIBUTING"
-msgstr ""
+msgstr "CONTRIBUINDO"
msgid "Callback URL"
msgstr "URL de Retorno"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "Pode substituir aprovadores e aprovações necessárias por merge request"
msgid "Can't find HEAD commit for this branch"
msgstr "Não é possível encontrar o commit HEAD para este branch"
+msgid "Can't remove group members without group managed account"
+msgstr "Não é possível remover membros do grupo sem uma conta gerenciada do grupo"
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
-msgstr ""
+msgstr "Canary Deployments é uma estratégia popular em CI onde uma pequena porção da frota é atualizada para a nova versão do seu aplicativo."
msgid "Cancel"
msgstr "Cancelar"
@@ -1632,23 +1979,38 @@ msgstr "Cancelar essa tarefa"
msgid "Cannot be merged automatically"
msgstr "Não pode ser feito o merge automaticamente"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Não se pode modificar um cluster Kubernetes gerenciado"
-msgid "Certificate"
-msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr "Não é possível renderizar a imagem. A contagem máxima de caracteres (%{charLimit}) foi excedida."
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Impressão digital do certificado"
+msgid "Capacity threshold"
+msgstr "Limite de capacidade"
+
+msgid "Certificate"
+msgstr "Certificado"
+
+msgid "Certificate (PEM)"
+msgstr "Certificado (PEM)"
msgid "Change Weight"
msgstr "Alterar Peso"
+msgid "Change path"
+msgstr "Alterar caminho"
+
msgid "Change permissions"
-msgstr ""
+msgstr "Alterar permissões"
msgid "Change template"
msgstr "Mudar modelo"
@@ -1671,11 +2033,14 @@ msgstr "Reverter"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Isso criará um novo commit para reverter as mudanças existentes."
+msgid "Changes"
+msgstr "Alterações"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "Mudanças serão mostradas se revisão de <b>origem</b> tiver sofrido merge na revisão <b>alvo</b>."
msgid "Changes suppressed. Click to show."
-msgstr ""
+msgstr "Alterações suprimidas. Clique para mostrar."
msgid "Charts"
msgstr "Gráficos"
@@ -1683,17 +2048,23 @@ msgstr "Gráficos"
msgid "Chat"
msgstr "Bate-papo"
+msgid "Check again"
+msgstr "Verifique Novamente"
+
+msgid "Check feature availability on namespace plan"
+msgstr "Verificar disponibilidade de funcionalidades no plano de namespace"
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "Verifique a %{docs_link_start}documentação%{docs_link_end}."
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr "Verifique o seu .gitlab-ci.yml"
msgid "Checking %{text} availability…"
msgstr "Verificando disponibilidade de %{text}…"
msgid "Checking approval status"
-msgstr ""
+msgstr "Verificando status de aprovação"
msgid "Checking branch availability..."
msgstr "Verificando disponibilidade de branch..."
@@ -1710,17 +2081,14 @@ msgstr "Escolha <strong>Criar arquivo</strong> e aguarde até que o arquivamento
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "Escolha <strong>Próximo</strong> na parte inferior da página."
-msgid "Choose File ..."
-msgstr "Escolha o arquivo ..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Escolha a branch/tag (ex: %{master}) ou número do commit (ex: %{sha}) para ver o que mudou ou para criar um merge request."
msgid "Choose a file"
-msgstr ""
+msgstr "Escolha um arquivo"
msgid "Choose a role permission"
-msgstr ""
+msgstr "Escolha uma permissão de cargo"
msgid "Choose a template..."
msgstr "Escolha um modelo..."
@@ -1734,27 +2102,27 @@ msgstr "Escolha qualquer cor."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "Escolha entre <code>clone</code> ou <code>fetch</code> para obter o código do aplicativo recente"
-msgid "Choose file..."
-msgstr "Escolha o arquivo..."
+msgid "Choose file…"
+msgstr "Escolher arquivo…"
msgid "Choose the top-level group for your repository imports."
msgstr "Escolha o grupo principal para importar seus repositórios."
-msgid "Choose what content you want to see on a group’s overview page"
-msgstr ""
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr "Escolha o nível de visibilidade, ative/desative os recursos do projeto (issues, repositório, wiki, snippets) e defina as permissões."
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "Escolha quais grupos você deseja sincronizar nesse nó secundário."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "Escolha o conteúdo que você deseja ver na página de visão geral de um grupo"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Escolha quais repositórios você deseja se conectar e executar pipelines de CI/CD."
-msgid "Choose which repositories you want to import."
-msgstr "Escolha quais repositórios você deseja importar."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "Escolha quais shards você deseja sincronizar nesse nó secundário."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr "Escolha o seu método de merge, configure um modelo de descrição do merge request padrão."
+
msgid "CiStatusLabel|canceled"
msgstr "cancelado"
@@ -1779,6 +2147,9 @@ msgstr "bem sucedido com avisos"
msgid "CiStatusLabel|pending"
msgstr "pendente"
+msgid "CiStatusLabel|preparing"
+msgstr "preparando"
+
msgid "CiStatusLabel|skipped"
msgstr "ignorado"
@@ -1812,6 +2183,9 @@ msgstr "passou"
msgid "CiStatusText|pending"
msgstr "pendente"
+msgid "CiStatusText|preparing"
+msgstr "preparando"
+
msgid "CiStatusText|skipped"
msgstr "ignorado"
@@ -1827,6 +2201,9 @@ msgstr "Digite o valor da variável"
msgid "CiVariables|Remove variable row"
msgstr "Remover a variável"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (Todos os ambientes)"
@@ -1839,6 +2216,9 @@ msgstr "Criar curinga"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr "Novo ambiente"
@@ -1848,20 +2228,26 @@ msgstr "Protegido"
msgid "CiVariable|Search environments"
msgstr "Pesquisar ambientes"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Alternar proteção"
msgid "CiVariable|Validation failed"
msgstr "Falha na validação"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "está indisponível: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "Limpar"
msgid "Clear input"
-msgstr ""
+msgstr "Limpar entrada"
msgid "Clear search"
msgstr "Limpar Pesquisa"
@@ -1903,16 +2289,19 @@ msgid "Clients"
msgstr "Clientes"
msgid "Clone"
-msgstr ""
+msgstr "Clonar"
msgid "Clone repository"
msgstr "Clonar repositório"
msgid "Clone with %{http_label}"
-msgstr ""
+msgstr "Clonar com %{http_label}"
+
+msgid "Clone with KRB5"
+msgstr "Clonar com KRB5"
msgid "Clone with SSH"
-msgstr ""
+msgstr "Clonar com SSH"
msgid "Close"
msgstr "Fechar"
@@ -1921,22 +2310,19 @@ msgid "Close epic"
msgstr "Fechar épico"
msgid "Close milestone"
-msgstr ""
+msgstr "Fechar marco"
msgid "Closed"
msgstr "Fechado"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Issues Fechadas"
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
-msgstr ""
+msgstr " %{custom_domain_start}Mais informações%{custom_domain_end}."
msgid "ClusterIntegration| can be used instead of a custom domain."
-msgstr ""
+msgstr " pode ser usado em vez de um domínio personalizado."
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} foi instalado com sucesso no seu cluster Kubernetes"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} Isso adicionará alguns recursos extras, como um balanceador de carga, o que pode acarretar custos adicionais, dependendo do provedor de hospedagem em que o cluster do Kubernetes está instalado. Se você estiver usando o Google Kubernetes Engine, você pode %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Adicionar cluster Kubernetes"
@@ -1968,18 +2354,15 @@ msgstr "Adicionando uma integração ao seu grupo irá compartilhar o cluster en
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Opções avançadas na integração deste cluster Kubernetes"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "Depois de instalar o Ingress, você precisará apontar seu DNS curinga para o endereço IP externo gerado, a fim de visualizar o aplicativo depois que ele for implantado. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
-msgstr ""
-
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Erro ao recuperar zonas de projeto: %{error}"
+msgstr "Alternativamente"
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Ocorreu um erro ao entrar em contato com a Google Cloud API. Por favor, tente novamente mais tarde."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -1990,13 +2373,13 @@ msgid "ClusterIntegration|Applications"
msgstr "Aplicações"
msgid "ClusterIntegration|Apply for credit"
-msgstr ""
+msgstr "Inscreva-se para receber créditos"
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr "Tem certeza de que deseja remover a integração deste cluster do Kubernetes? Isso não excluirá o seu cluster atual do Kubernetes."
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "Domínio base"
msgid "ClusterIntegration|CA Certificate"
msgstr "Certificado CA"
@@ -2016,6 +2399,12 @@ msgstr "Escolha quais aplicativos instalar em seu cluster do Kubernetes. O Helm
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Escolha quais dos seus ambientes usarão esse cluster."
+msgid "ClusterIntegration|Cluster health"
+msgstr "Saúde do cluster"
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,20 +2414,20 @@ msgstr "Copiar URL da API"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Copiar certificado CA"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Copiar o endereço IP de entrada para a área de transferência"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Copiar nome do host Jupyter para a área de transferência"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Copiar nome do cluster Kubernetes"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Copiar token"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Integração de Clusters | Criar cluster Kubernetes"
@@ -2085,8 +2474,8 @@ msgstr "Integração GitLab"
msgid "ClusterIntegration|GitLab Runner"
msgstr "Gitlab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner se conecta ao repositório deste projeto e executa tarefas de CI/CD, retornando resultados e implementando, aplicativos para produção."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Google Cloud Platform projeto"
@@ -2112,14 +2501,14 @@ msgstr "Ocultar"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Se você está configurando múltiplos clusters e você está usando auto DevOps, %{help_link_start}leia isso primeiro%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Para mostrar a saúde do cluster, nós precisamos provisionar seu cluster com Prometheus para coletar os dados necessários."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingressar"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Endereço IP de entrada"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress oferece uma maneira de rotear solicitações para serviços com base no host ou caminho da solicitação, centralizando diversos serviços em um único ponto de entrada."
@@ -2127,15 +2516,18 @@ msgstr "Ingress oferece uma maneira de rotear solicitações para serviços com
msgid "ClusterIntegration|Install"
msgstr "Instalar"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Instalar Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "Instalado"
msgid "ClusterIntegration|Installing"
msgstr "Instalando"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Integrar automação de cluster Kubernetes"
@@ -2163,7 +2555,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Nome de domínio Knative:"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr "Cluter Kubernetes"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Detalhes do cluster Kubernetes"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Saúde do cluster Kubernetes"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "O cluster Kubernetes está sendo criado no Google Kubernetes Engine..."
@@ -2206,7 +2595,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "Saiba mais sobre grupo de cluster Kubernetes"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "Tipo de máquina"
@@ -2220,9 +2609,6 @@ msgstr "Gerenciar"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Gerenciar seu cluster Kubernetes visitando %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Mais informações"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Nenhum tipo de máquina corresponde à sua pesquisa"
@@ -2235,27 +2621,24 @@ msgstr "Nenhum projeto corresponde à sua pesquisa"
msgid "ClusterIntegration|No zones matched your search"
msgstr "Nenhuma zona corresponde à sua pesquisa"
-msgid "ClusterIntegration|Note:"
-msgstr "Nota:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Número de nós"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Por favor, entre com as informações de acesso para seu cluter Kubernetes. Se precisar de ajuda, você pode ler nosso %{link_to_help_page} em Kubernetes"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Por favor, tenha certeza que sua conta no Google cumpre com os requisitos:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "Aponte um DNS curinga para esse endereço IP gerado para acessar sua aplicação depois que ele for lançado."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "Cluster do Projeto"
-msgid "ClusterIntegration|Project namespace"
-msgstr "Namespace do projeto"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "Namespace do projeto (opcional, único)"
@@ -2286,7 +2669,7 @@ msgstr "Substitua isso por seu próprio nome de host, se desejar. Se você fizer
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Solicitação para início de instalação falhou"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,8 +2705,11 @@ msgstr "Selecione a zona"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Selecione a zone para escolher o tipo de máquina"
-msgid "ClusterIntegration|Service token"
-msgstr "Token de serviço"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Mostrar"
@@ -2331,9 +2717,6 @@ msgstr "Mostrar"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Alguma coisa deu errado do nosso lado."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Erro ao criar cluster Kubernetes no Google Kubernetes Engine"
@@ -2343,8 +2726,8 @@ msgstr "Algo deu errado ao instalar %{title}"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "O endereço IP está em processo de atribuição. Verifique seu cluster ou cotas do Kubernetes no Google Kubernetes Engine se demorar muito."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "Essa conta precisa de permissões para criar um cluster Kubernetes no %{link_to_container_project} especificado"
@@ -2352,23 +2735,29 @@ msgstr "Essa conta precisa de permissões para criar um cluster Kubernetes no %{
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "Esta opção permite-lhe instalar aplicações em clusters RBAC."
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Alternar cluster Kubernetes"
-msgid "ClusterIntegration|Token"
-msgstr "Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr "Atualizando"
msgid "ClusterIntegration|Upgrade"
-msgstr ""
+msgstr "Atualizar"
msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
+msgstr "Falha na atualização"
msgid "ClusterIntegration|Upgraded"
-msgstr ""
+msgstr "Atualizado"
msgid "ClusterIntegration|Upgrading"
-msgstr ""
+msgstr "Atualizando"
msgid "ClusterIntegration|Validating project billing status"
msgstr "Validando status de faturamento do projeto"
@@ -2394,9 +2783,6 @@ msgstr "Zona"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "acesso ao Google Container Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "verifique os preços aqui"
-
msgid "ClusterIntegration|documentation"
msgstr "documentação"
@@ -2406,6 +2792,9 @@ msgstr "ajuda"
msgid "ClusterIntegration|meets the requirements"
msgstr "atende aos requisitos"
+msgid "ClusterIntegration|pricing"
+msgstr "preços"
+
msgid "ClusterIntegration|properly configured"
msgstr "configurado corretamente"
@@ -2413,31 +2802,43 @@ msgid "ClusterIntegration|sign up"
msgstr "cadastrar"
msgid "Code"
-msgstr ""
+msgstr "Código"
+
+msgid "Code Owners"
+msgstr "Proprietários de código"
+
+msgid "Code owner approval is required"
+msgstr "A aprovação do proprietário do código é obrigatória"
msgid "Code owners"
msgstr "Proprietários de código"
+msgid "CodeOwner|Pattern"
+msgstr "Padrão"
+
msgid "Cohorts"
msgstr "Cohorts"
msgid "Collapse"
msgstr "Recolher"
+msgid "Collapse approvers"
+msgstr "Recolher aprovadores"
+
msgid "Collapse sidebar"
msgstr "Minimizar barra lateral"
msgid "Command line instructions"
-msgstr ""
+msgstr "Instruções de linha de comando"
msgid "Comment"
msgstr "Comentário"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "Comentar e fechar %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "Comentar e reabrir %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "Comentar e marcar a discussão como resolvida"
@@ -2457,13 +2858,13 @@ msgstr[0] "Commit"
msgstr[1] "Commits"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "Commit %{commit_id}"
msgid "Commit Message"
msgstr "Mensagem de Commit"
msgid "Commit deleted"
-msgstr ""
+msgstr "Commit excluído"
msgid "Commit duration in minutes for last 30 commits"
msgstr "Duração do commit em minutos para os últimos 30 commits"
@@ -2519,6 +2920,9 @@ msgstr "Commit feito por"
msgid "Commit…"
msgstr "Commit…"
+msgid "Company"
+msgstr "Empresa"
+
msgid "Compare"
msgstr "Comparar"
@@ -2529,7 +2933,7 @@ msgid "Compare Revisions"
msgstr "Comparar revisões"
msgid "Compare changes"
-msgstr ""
+msgstr "Comparar alterações"
msgid "Compare changes with the last commit"
msgstr "Compare as mudanças do último commit"
@@ -2568,7 +2972,7 @@ msgid "Configure Tracing"
msgstr "Configurar rastreamento"
msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "Configure um arquivo <code>.gitlab-webide.yml</code> no diretório <code>.gitlab</code> para começar a usar o Terminal Web. %{helpStart}Saiba mais.%{helpEnd}"
msgid "Configure automatic git checks and housekeeping on repositories."
msgstr "Configurar housekeeping e checagens do git nos repositórios."
@@ -2588,6 +2992,12 @@ msgstr "Configure a integração de %{link}."
msgid "Configure the way a user creates a new account."
msgstr "Configurar a forma como o usuário cria uma nova conta."
+msgid "Confirm"
+msgstr "Confirmar"
+
+msgid "Confirmation required"
+msgstr "Confirmação necessária"
+
msgid "Connect"
msgstr "Conectar"
@@ -2603,12 +3013,24 @@ msgstr "Conecte seus repositórios externos e pipelines CI/CD vão ser executado
msgid "Connecting..."
msgstr "Conectando..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
+msgstr "Entre em contato com o departamento de vendas para atualizar"
+
+msgid "Contact your Administrator to upgrade your license."
msgstr ""
msgid "Container Registry"
msgstr "Container Registry"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Criado"
@@ -2655,7 +3077,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr "Você pode usar também um %{deploy_token} para acesso somente-leitura às imagens do registry."
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr "Conteúdo de .gitlab-ci.yml"
msgid "Continue"
msgstr "Continuar"
@@ -2672,8 +3094,11 @@ msgstr "Contribua para o GitLab"
msgid "Contribution"
msgstr "Contribuições"
+msgid "Contribution Analytics"
+msgstr "Gráficos de contribuição"
+
msgid "Contribution Charts"
-msgstr ""
+msgstr "Gráficos de contribuição"
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr "Contribuições para <strong>%{calendar_date}</strong>"
@@ -2699,23 +3124,14 @@ msgstr "Por favor, espere um momento, essa página será atualizada automaticame
msgid "Control the display of third party offers."
msgstr "Controle a exibição de ofertas de terceiros."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Controlar a concorrência máxima do enchimento de anexo/LFS para esse nó secundário"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "Controlar a concorrência máxima do enchimento do repositório para esse nó secundário"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "Controlar a concorrência máxima de operações de verificação para esse nó Geo"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "Ãndice ConvDev"
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "Copiar URL de clone do %{http_label}"
msgid "Copy %{protocol} clone URL"
msgstr "Copiar URL de Clone do %{protocol}"
@@ -2723,11 +3139,14 @@ msgstr "Copiar URL de Clone do %{protocol}"
msgid "Copy ID to clipboard"
msgstr "Copiar ID para área de transferência"
+msgid "Copy KRB5 clone URL"
+msgstr "Copiar URL de clone do KRB5"
+
msgid "Copy SSH clone URL"
msgstr "Copiar URL de Clone do SSH"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "Copiar chave pública SSH"
msgid "Copy SSH public key to clipboard"
msgstr "Copiar chave pública SSH para área de transferência"
@@ -2747,9 +3166,6 @@ msgstr "Copiar SHA do commit para a área de transferência"
msgid "Copy file path to clipboard"
msgstr "Copiar o local do arquivo para a área de transferência"
-msgid "Copy incoming email address to clipboard"
-msgstr "Copiar endereço de e-mail para a área de transferência"
-
msgid "Copy link"
msgstr "Copiar link"
@@ -2765,9 +3181,33 @@ msgstr "Copiar para área de transferência"
msgid "Copy token to clipboard"
msgstr "Copiar token para a área de transferência"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr "Não foi possível remover o gatilho."
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "Não foi possível recuperar o status do pipeline. Para solução de problemas, leia a %{linkStart}documentação.%{linkEnd}"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr "Cobertura"
+
msgid "Create"
msgstr "Criar"
@@ -2775,23 +3215,26 @@ msgid "Create New Directory"
msgstr "Criar Novo Diretório"
msgid "Create New Domain"
+msgstr "Criar novo domínio"
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
msgid "Create a new branch"
msgstr "Criar uma nova branch"
-msgid "Create a new branch and merge request"
-msgstr "Criar um novo branch e abrir merge request"
-
msgid "Create a new issue"
msgstr "Criar uma nova issue"
msgid "Create a new repository"
-msgstr ""
+msgstr "Criar um novo repositório"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Crie um token de acesso pessoal na sua conta para dar pull ou push via %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr "Crie um issue. Os Issues são criados para cada alerta acionado."
+
msgid "Create branch"
msgstr "Criar a branch"
@@ -2816,9 +3259,6 @@ msgstr "Criar grupo"
msgid "Create group label"
msgstr "Criar etiqueta de grupo"
-msgid "Create issue"
-msgstr "Criar issue"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Criar lista a partir de etiquetas. Issues com etiquetas aparecem nestas listas."
@@ -2829,7 +3269,7 @@ msgid "Create merge request and branch"
msgstr "Abrir merge request e criar branch"
msgid "Create milestone"
-msgstr ""
+msgstr "Criar marco"
msgid "Create new branch"
msgstr "Criar novo branch"
@@ -2888,6 +3328,9 @@ msgstr "Sintaxe do cron"
msgid "Current Branch"
msgstr "Branch atual"
+msgid "Current Project"
+msgstr "Projeto atual"
+
msgid "Current node"
msgstr "Nó atual"
@@ -2913,7 +3356,7 @@ msgid "Custom project templates"
msgstr "Modelos de projetos personalizados"
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
-msgstr ""
+msgstr "Modelos de projeto personalizados não foram configurados para grupos dos quais você é membro. Eles são ativados na página de configurações de um grupo. Entre em contato com o proprietário ou mantenedor do seu grupo para configurar modelos de projeto personalizados."
msgid "Customize colors"
msgstr "Personalizar cores"
@@ -2925,10 +3368,7 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr "Personalize como os endereços de e-mail e nomes de usuário do Google Code são importados para o GitLab. Na próxima etapa, você poderá selecionar os projetos que deseja importar."
msgid "Customize language and region related settings."
-msgstr ""
-
-msgid "Customize your merge request approval settings."
-msgstr ""
+msgstr "Personalize as configurações relacionadas a idioma e região."
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Personalize a sua configuração, visualize o status da pipeline e relatório de cobertura."
@@ -2961,7 +3401,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "Teste"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "Dashboard"
@@ -2972,8 +3412,11 @@ msgstr "Todos"
msgid "DashboardProjects|Personal"
msgstr "Pessoal"
+msgid "Dashboards"
+msgstr "Dashboards"
+
msgid "Data is still calculating..."
-msgstr ""
+msgstr "Os dados ainda estão a ser calculados..."
msgid "Date picker"
msgstr "Seletor de data"
@@ -2988,7 +3431,7 @@ msgid "December"
msgstr "Dezembro"
msgid "Decline"
-msgstr ""
+msgstr "Recusar"
msgid "Decline and sign out"
msgstr "Recusar e sair"
@@ -2996,13 +3439,22 @@ msgstr "Recusar e sair"
msgid "Default Branch"
msgstr "Branch padrão"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "Etiqueta de classificação padrão"
-msgid "Default first day of the week"
+msgid "Default description template for issues"
msgstr ""
+msgid "Default first day of the week"
+msgstr "Primeiro dia da semana padrão"
+
msgid "Default first day of the week in calendars and date pickers."
+msgstr "Primeiro dia da semana padrão em calendários e selecionadores de data."
+
+msgid "Default issue template"
msgstr ""
msgid "Default: Directly import the Google Code email address or username"
@@ -3044,18 +3496,36 @@ msgstr "Excluir Snippet"
msgid "Delete comment"
msgstr "Excluir comentário"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Excluir lista"
msgid "Delete source branch"
-msgstr ""
+msgstr "Excluir branch de origem"
msgid "Delete this attachment"
-msgstr ""
+msgstr "Excluir este anexo"
msgid "Deleted"
msgstr "Excluído"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "Recusar"
@@ -3067,6 +3537,9 @@ msgstr[1] "Implantações"
msgid "Deploy Keys"
msgstr "Chaves para deploy"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} outros"
@@ -3188,7 +3661,7 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "Seu novo token de deploy de projeto foi criado."
msgid "Deployed"
-msgstr ""
+msgstr "Feito Deploy"
msgid "Deployed to"
msgstr "Deploy para"
@@ -3205,12 +3678,45 @@ msgstr "Decrescente"
msgid "Description"
msgstr "Descrição"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "Os modelos de descrição permitem que você defina modelos específicos de contexto para os campos de descrição de merge request e emissão para seu projeto."
msgid "Description:"
msgstr "Descrição:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr "Designs"
+
msgid "Destroy"
msgstr "Destruir"
@@ -3218,7 +3724,7 @@ msgid "Details"
msgstr "Detalhes"
msgid "Details (default)"
-msgstr ""
+msgstr "Detalhes (padrão)"
msgid "Detect host keys"
msgstr "Detectar chaves de host"
@@ -3251,10 +3757,10 @@ msgid "Disable group Runners"
msgstr "Desabilitar runners de grupo"
msgid "Disable shared Runners"
-msgstr ""
+msgstr "Desativar Runners compartilhados"
msgid "Disabled"
-msgstr ""
+msgstr "Desativado"
msgid "Discard"
msgstr "Descartar"
@@ -3278,22 +3784,25 @@ msgid "Discard review"
msgstr "Descartar revisão"
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "Descubra o GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr "Descubra projetos, grupos e snippets. Compartilhe seus projetos com outras pessoas"
msgid "Discuss a specific suggestion or question"
-msgstr ""
+msgstr "Discuta uma sugestão ou pergunta específica"
msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
+msgstr "Discuta uma sugestão ou pergunta específica que precisa ser resolvida"
+
+msgid "Discussion"
+msgstr "Discussão"
msgid "Dismiss"
msgstr "Ignorar"
msgid "Dismiss ConvDev introduction"
-msgstr ""
+msgstr "Descartar a introdução do ConvDev"
msgid "Dismiss Cycle Analytics introduction box"
msgstr "Ignorar introdução do Cycle Analytics"
@@ -3323,25 +3832,31 @@ msgid "Download"
msgstr "Baixar"
msgid "Download artifacts"
-msgstr ""
+msgstr "Baixar artefatos"
msgid "Download asset"
+msgstr "Baixar recurso"
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
msgstr ""
msgid "Download tar"
-msgstr "Baixar tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Baixar tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Baixar tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Baixar zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Baixar"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Email com as mudanças"
@@ -3353,7 +3868,7 @@ msgid "DownloadSource|Download"
msgstr "Baixar"
msgid "Downstream"
-msgstr ""
+msgstr "Downstream"
msgid "Downvotes"
msgstr "Votos negativos"
@@ -3371,13 +3886,16 @@ msgid "Edit"
msgstr "Alterar"
msgid "Edit %{name}"
-msgstr ""
+msgstr "Editar %{name}"
+
+msgid "Edit Deploy Key"
+msgstr "Editar chave de deploy"
msgid "Edit Label"
msgstr "Editar etiqueta"
msgid "Edit Milestone"
-msgstr ""
+msgstr "Editar marco"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Alterar Agendamento do Pipeline %{id}"
@@ -3389,10 +3907,13 @@ msgid "Edit application"
msgstr "Editar aplicativo"
msgid "Edit comment"
-msgstr ""
+msgstr "Editar comentário"
msgid "Edit environment"
-msgstr ""
+msgstr "Editar ambiente"
+
+msgid "Edit file"
+msgstr "Editar arquivo"
msgid "Edit files in the editor and commit changes here"
msgstr "Alterar arquivos no editor e fazer commit das alterações aqui"
@@ -3404,7 +3925,10 @@ msgid "Edit identity for %{user_name}"
msgstr "Editar identidade para %{user_name}"
msgid "Edit issues"
-msgstr ""
+msgstr "Editar issues"
+
+msgid "Edit public deploy key"
+msgstr "Editar chave de deploy pública"
msgid "Elasticsearch"
msgstr "Elasticsearch"
@@ -3412,12 +3936,45 @@ msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Integração com Elasticsearch. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "E-mail"
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
msgid "Email patch"
msgstr "Patch de email"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "E-mails"
@@ -3425,7 +3982,7 @@ msgid "Embed"
msgstr "Embutido"
msgid "Empty file"
-msgstr ""
+msgstr "Arquivo vazio"
msgid "Enable"
msgstr "Ativar"
@@ -3433,6 +3990,9 @@ msgstr "Ativar"
msgid "Enable Auto DevOps"
msgstr "Ativar Auto DevOps"
+msgid "Enable HTML emails"
+msgstr "Habilitar e-mails em HTML"
+
msgid "Enable Pseudonymizer data collection"
msgstr "Ativar coleção de dados Pseudonymizer"
@@ -3452,7 +4012,7 @@ msgid "Enable classification control using an external service"
msgstr "Ativar controle de classificação usando um serviço externo"
msgid "Enable error tracking"
-msgstr ""
+msgstr "Ativar rastreamento de erros"
msgid "Enable for this project"
msgstr "Ativar para este projeto"
@@ -3460,6 +4020,12 @@ msgstr "Ativar para este projeto"
msgid "Enable group Runners"
msgstr "Ativar grupo de runners"
+msgid "Enable header and footer in emails"
+msgstr "Ativar cabeçalho e rodapé em e-mails"
+
+msgid "Enable mirror configuration"
+msgstr "Ativar configuração de espelho"
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "Ative ou desative a coleção de dados Pseudonymizer"
@@ -3469,17 +4035,14 @@ msgstr "Ativar ou desativar checagem de versão e uso de ping."
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "Ativar reCAPTCHA ou Akismet e definir seus limites de IP."
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
-msgstr ""
+msgstr "Ativar Runners partilhados"
msgid "Enable the Performance Bar for a given group."
msgstr "Ative a barra de desempenho para um determinado grupo."
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "Ativar autenticação de dois fatores"
msgid "Enable usage ping"
msgstr "Ativar dados de uso"
@@ -3487,16 +4050,28 @@ msgstr "Ativar dados de uso"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "Ativar a dados de uso para obter uma visão geral de como você está usando GitLab a partir de uma perspectiva de recurso."
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Habilitado"
-msgid "Ends at (UTC)"
-msgstr "Termina em (UTC)"
+msgid "Enabled Git access protocols"
+msgstr "Protocolos de acesso do Git habilitados"
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr "As fontes para importação de código durante a criação de projetos foram ativadas. O OmniAuth deve ser configurado para o GitHub."
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr "Ativar isto apenas disponibilizará os recursos EE licenciados para projetos se o plano do namespace do projeto incluir o recurso ou se o projeto for público."
+
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr "Termina em (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,26 +4089,20 @@ msgstr "Digite a descrição do merge request"
msgid "Enter the merge request title"
msgstr "Digite o título do merge request"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
-msgstr ""
+msgstr "As variáveis de ambiente são configuradas pelo seu administrador para serem %{link_start}protegidas%{link_end} por padrão"
msgid "Environment:"
-msgstr ""
+msgstr "Ambiente:"
msgid "Environments"
msgstr "Ambientes"
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
-msgstr ""
+msgstr "Os ambientes permitem que você acompanhe as implantações do seu aplicativo %{link_to_read_more}."
msgid "Environments|An error occurred while fetching the environments."
msgstr "Um erro ocorreu ao recuperar ambientes."
@@ -3541,6 +4110,12 @@ msgstr "Um erro ocorreu ao recuperar ambientes."
msgid "Environments|An error occurred while making the request."
msgstr "Um erro ocorreu ao fazer a requisição."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr "Ocorreu um erro ao re-implantar o ambiente. Por favor, tente novamente"
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "Ocorreu um erro ao parar o ambiente, por favor, tente novamente"
@@ -3592,15 +4167,33 @@ msgstr "Abrir ambiente ao vivo"
msgid "Environments|Pod logs from"
msgstr "Logs de pod de"
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr "Reimplantar no ambiente"
msgid "Environments|Read more about environments"
msgstr "Ler mais sobre ambiente"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr "Reverter ambiente"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Mostrar tudo"
@@ -3613,6 +4206,18 @@ msgstr "Parar ambiente"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Atualizado"
@@ -3662,13 +4267,16 @@ msgid "Error Reporting and Logging"
msgstr "Relatório e registro de erros"
msgid "Error Tracking"
-msgstr ""
+msgstr "Acompanhamento de erros"
+
+msgid "Error creating a new path"
+msgstr "Erro ao criar um novo caminho"
msgid "Error creating epic"
msgstr "Erro ao criar epic"
msgid "Error deleting %{issuableType}"
-msgstr ""
+msgstr "Erro ao excluir %{issuableType}"
msgid "Error fetching contributors data."
msgstr "Erro ao recuperar informações de contribuintes."
@@ -3712,14 +4320,26 @@ msgstr "Erro ao carregar o modelo."
msgid "Error occurred when toggling the notification subscription"
msgstr "Erro ao alterar configuração de notificação de assinatura"
-msgid "Error rendering markdown preview"
+msgid "Error occurred. User was not blocked"
+msgstr "Ocorreu um erro. O usuário não foi bloqueado"
+
+msgid "Error occurred. User was not confirmed"
+msgstr "Ocorreu um erro. O usuário não foi confirmado"
+
+msgid "Error occurred. User was not unblocked"
+msgstr "Ocorreu um erro. O usuário não foi desbloqueado"
+
+msgid "Error occurred. User was not unlocked"
msgstr ""
+msgid "Error rendering markdown preview"
+msgstr "Erro ao renderizar a pré-visualização do markdown"
+
msgid "Error saving label update."
msgstr "Erro ao salvar alteração de etiqueta."
msgid "Error updating %{issuableType}"
-msgstr ""
+msgstr "Erro ao atualizar %{issuableType}"
msgid "Error updating status for all todos."
msgstr "Erro ao atualizar status para todas as tarefas."
@@ -3727,15 +4347,51 @@ msgstr "Erro ao atualizar status para todas as tarefas."
msgid "Error updating todo status."
msgstr "Erro ao atualizar status das tarefas."
+msgid "Error uploading file"
+msgstr "Erro ao enviar o arquivo"
+
msgid "Error while loading the merge request. Please try again."
msgstr "Erro ao carregar o merge request. Por favor, tente novamente."
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
+msgstr "Erro:"
+
+msgid "ErrorTracking|Active"
+msgstr "Ativo"
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr "Depois de adicionar o seu Token de Autenticação, use o botão \"conectar\" para carregar os projetos"
+
+msgid "ErrorTracking|Auth Token"
+msgstr "Token de autenticação"
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr "Clique em \"Conectar\" para restabelecer a conexão com o Sentry e ativar o menu suspenso."
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr "A conexão falhou. Re-verifique o Token de Autenticação e tente novamente."
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
msgstr ""
-msgid "Errors"
+msgid "ErrorTracking|No projects available"
+msgstr "Não há projetos disponíveis"
+
+msgid "ErrorTracking|Select project"
+msgstr "Selecionar projeto"
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
msgstr ""
+msgid "Errors"
+msgstr "Erros"
+
msgid "Estimated"
msgstr "Estimativa"
@@ -3758,10 +4414,10 @@ msgid "EventFilterBy|Filter by team"
msgstr "EventFilterBy|Filtrar por equipe"
msgid "Events"
-msgstr ""
+msgstr "Eventos"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
-msgstr ""
+msgstr "Cada tentativa de %{action} falhou: %{job_error_message}. Por favor, tente novamente."
msgid "Every day (at 4:00am)"
msgstr "Todos os dias (às 4:00)"
@@ -3773,37 +4429,31 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "Toda semana (domingos às 4:00)"
msgid "Everyone"
-msgstr ""
+msgstr "Todos"
msgid "Everyone can contribute"
msgstr "Todos podem contribuir"
msgid "Everything you need to create a GitLab Pages site using GitBook."
-msgstr ""
+msgstr "Tudo o que você precisa para criar um site do GitLab Pages usando o GitBook."
msgid "Everything you need to create a GitLab Pages site using Hexo."
-msgstr ""
+msgstr "Tudo o que você precisa para criar um site do GitLab Pages usando o Hexo."
msgid "Everything you need to create a GitLab Pages site using Hugo."
-msgstr ""
+msgstr "Tudo o que você precisa para criar um site do GitLab Pages usando o Hugo."
msgid "Everything you need to create a GitLab Pages site using Jekyll."
-msgstr ""
+msgstr "Tudo o que você precisa para criar um site do GitLab Pages usando o Jekyll."
msgid "Everything you need to create a GitLab Pages site using plain HTML."
-msgstr ""
+msgstr "Tudo o que você precisa para criar um site do GitLab Pages usando HTML simples."
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
-msgstr ""
+msgstr "Membros e grupos existentes"
msgid "Expand"
msgstr "Expandir"
@@ -3811,17 +4461,23 @@ msgstr "Expandir"
msgid "Expand all"
msgstr "Expandir tudo"
+msgid "Expand approvers"
+msgstr "Expandir aprovadores"
+
msgid "Expand sidebar"
msgstr "Expandir barra lateral"
msgid "Expiration date"
msgstr "Data de validade"
-msgid "Expired %{expiredOn}"
+msgid "Expired"
msgstr ""
+msgid "Expired %{expiredOn}"
+msgstr "Expirou %{expiredOn}"
+
msgid "Expires in %{expires_at}"
-msgstr ""
+msgstr "Expira em %{expires_at}"
msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
msgstr "Explique o problema. Se necessário, forneça um link para a issue ou comentário relevante."
@@ -3845,19 +4501,25 @@ msgid "Explore public groups"
msgstr "Explorar grupos públicos"
msgid "Export as CSV"
-msgstr ""
+msgstr "Exportar como CSV"
msgid "Export issues"
+msgstr "Exportar issues"
+
+msgid "Export project"
+msgstr "Exportar projeto"
+
+msgid "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."
msgstr ""
msgid "External Classification Policy Authorization"
msgstr "Autorização de Política de Classificação Externa"
msgid "External URL"
-msgstr ""
+msgstr "URL externo"
msgid "External Wiki"
-msgstr ""
+msgstr "Wiki externo"
msgid "External authentication"
msgstr "Autenticação externa"
@@ -3868,9 +4530,6 @@ msgstr "Autorização externa negou acesso a este projeto"
msgid "External authorization request timeout"
msgstr "A requisição de autorização externa esgotou o tempo limite"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "Etiqueta de classificação"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "Etiqueta de classificação"
@@ -3892,13 +4551,19 @@ msgstr "Erro ao alterar o proprietário"
msgid "Failed to check related branches."
msgstr "Falha ao procurar por branches relacionadas."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "Falha ao fazer deploy para"
msgid "Failed to load emoji list."
msgstr "Falha ao carregar a lista de emojis."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,7 +4575,22 @@ msgstr "Falha ao remover a espelho."
msgid "Failed to remove the pipeline schedule"
msgstr "Erro ao excluir o agendamento do pipeline"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
+msgstr "Falha ao redefinir a chave. Por favor, tente novamente."
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
msgstr ""
msgid "Failed to signing using smartcard authentication"
@@ -3925,20 +4605,26 @@ msgstr ""
msgid "Failure"
msgstr "Falha"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "Mais rápido, uma vez que reutiliza o espaço de trabalho do projeto (voltando a clonar, se não existir)"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
-msgstr "Sinalizadores de recurso"
+msgstr "Feature flag"
msgid "FeatureFlags|* (All Environments)"
-msgstr ""
+msgstr "* (Todos os Ambientes)"
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "* (Todos os ambientes)"
msgid "FeatureFlags|API URL"
msgstr "URL da API"
@@ -3950,55 +4636,52 @@ msgid "FeatureFlags|Configure"
msgstr "Configurar"
msgid "FeatureFlags|Configure feature flags"
-msgstr "Configurar sinalizadores de recursos"
+msgstr "Configurar feature flag"
msgid "FeatureFlags|Create feature flag"
-msgstr "Criar sinalizador de recurso"
+msgstr "Criar feature flag"
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "Excluir %{name}?"
msgid "FeatureFlags|Delete feature flag"
-msgstr ""
+msgstr "Excluir feature flag"
msgid "FeatureFlags|Description"
msgstr "Descrição"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "Editar %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
-msgstr "Editar sinalizador de recurso"
+msgstr "Editar feature flag"
msgid "FeatureFlags|Environment Spec"
-msgstr ""
+msgstr "Especificação do Ambiente"
msgid "FeatureFlags|Environment Specs"
-msgstr ""
+msgstr "Especificações do Ambiente"
msgid "FeatureFlags|Feature Flag"
-msgstr "Sinalizador de recurso"
+msgstr "Feature flag"
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
msgstr ""
msgid "FeatureFlags|Feature Flags"
-msgstr ""
+msgstr "Feature flags"
msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
-msgstr ""
+msgstr "Feature flags permitem que você configure o seu código em diferentes versões alterando dinamicamente determinadas funcionalidades."
msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
-msgstr ""
+msgstr "A feature flag %{name} será removido. Você tem certeza?"
msgid "FeatureFlags|Get started with Feature Flags"
-msgstr ""
+msgstr "Comece a usar as feature flags"
msgid "FeatureFlags|Inactive"
msgstr "Inativo"
msgid "FeatureFlags|Inactive flag for %{scope}"
-msgstr ""
+msgstr "Sinalizador inativo para %{scope}"
msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
msgstr "Instale uma %{docs_link_start}biblioteca de cliente compatível%{docs_link_end} e especifique o URL da API, o nome do aplicativo e o ID da instância durante a configuração."
@@ -4007,10 +4690,10 @@ msgid "FeatureFlags|Instance ID"
msgstr "ID da instância"
msgid "FeatureFlags|Loading Feature Flags"
-msgstr ""
+msgstr "Carregando feature flag"
msgid "FeatureFlags|More Information"
-msgstr ""
+msgstr "Mais informações"
msgid "FeatureFlags|More information"
msgstr "Mais informações"
@@ -4022,28 +4705,28 @@ msgid "FeatureFlags|New"
msgstr "Novo"
msgid "FeatureFlags|New Feature Flag"
-msgstr "Novo sinalizador de recurso"
+msgstr "Nova feature flag"
-msgid "FeatureFlags|Save changes"
-msgstr "Salvar alterações"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "Status"
msgid "FeatureFlags|Target environments"
-msgstr ""
+msgstr "Ambientes alvo"
msgid "FeatureFlags|There are no active Feature Flags"
-msgstr ""
+msgstr "Não há feature flags ativas"
msgid "FeatureFlags|There are no inactive Feature Flags"
-msgstr ""
+msgstr "Não há feature flag inativas"
msgid "FeatureFlags|There was an error fetching the feature flags."
-msgstr ""
+msgstr "Ocorreu um erro ao buscar as feature flag."
msgid "FeatureFlags|Try again in a few moments or contact your support team."
-msgstr ""
+msgstr "Tente novamente daqui a pouco ou entre em contato com a sua equipe de suporte."
msgid "Feb"
msgstr "Fev"
@@ -4051,34 +4734,46 @@ msgstr "Fev"
msgid "February"
msgstr "Fevereiro"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "Campos nessa página não são mais editáveis, você pode configurar"
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Arquivo"
+msgstr[1] "Arquivos"
msgid "File added"
-msgstr ""
+msgstr "Arquivo adicionado"
msgid "File browser"
-msgstr ""
+msgstr "Navegador de arquivos"
msgid "File deleted"
-msgstr ""
+msgstr "Arquivo excluído"
msgid "File mode changed from %{a_mode} to %{b_mode}"
-msgstr ""
+msgstr "Modo de arquivo alterado de %{a_mode} para %{b_mode}"
msgid "File moved"
-msgstr ""
+msgstr "Arquivo movido"
msgid "File templates"
msgstr "Modelos de arquivos"
msgid "File upload error."
-msgstr ""
+msgstr "Erro ao enviar arquivo."
msgid "Files"
msgstr "Arquivos"
@@ -4099,28 +4794,25 @@ msgid "Filter by commit message"
msgstr "Filtrar por mensagem de commit"
msgid "Filter by milestone name"
-msgstr ""
+msgstr "Filtrar por nome de marco"
msgid "Filter by two-factor authentication"
-msgstr ""
+msgstr "Filtrar por autenticação de dois fatores"
msgid "Filter results by group"
-msgstr ""
+msgstr "Filtrar resultados por grupo"
msgid "Filter results by project"
-msgstr ""
+msgstr "Filtrar resultados por projeto"
msgid "Filter..."
msgstr "Filtro..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Localizar por caminho"
msgid "Find existing members by name"
-msgstr ""
+msgstr "Encontre membros existentes por nome"
msgid "Find file"
msgstr "Localizar arquivo"
@@ -4131,11 +4823,14 @@ msgstr "Encontre o arquivo ZIP baixado e extraia-o."
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Encontre o arquivo recém-extraído <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
+msgid "Fingerprint"
+msgstr "Impressão digital"
+
msgid "Fingerprints"
msgstr "Impressões digitais"
msgid "Finish editing this message first!"
-msgstr ""
+msgstr "Conclua a edição desta mensagem primeiro!"
msgid "Finish review"
msgstr "Concluir a revisão"
@@ -4144,7 +4839,7 @@ msgid "Finished"
msgstr "Finalizado"
msgid "First day of the week"
-msgstr ""
+msgstr "Primeiro dia da semana"
msgid "FirstPushedBy|First"
msgstr "Primeiro"
@@ -4192,7 +4887,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr "Para projetos internos, qualquer usuário conectado pode visualizar pipelines e acessar detalhes da tarefa (logs de saída e artefatos)"
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "Para mais informações, leia a documentação."
msgid "For more information, go to the "
msgstr "Para mais informações, vá para o "
@@ -4219,19 +4914,25 @@ msgid "Forking in progress"
msgstr "Fork em andamento"
msgid "Forks"
-msgstr ""
+msgstr "Forks"
msgid "Format"
msgstr "Formato"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr "Erros encontrados em seu %{gitlab_ci_yml}:"
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "Erros encontrados em seu .gitlab-ci.yml:"
msgid "Free Trial of GitLab.com Gold"
msgstr "Avaliação gratuita do GitLab.com Gold"
-msgid "From %{provider_title}"
-msgstr "De %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr "Do %{providerTitle}"
msgid "From Bitbucket"
msgstr "Do Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr "A partir de marcos:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Na visualização de detalhes do cluster do Kubernetes, instale o Runner pela lista de aplicativos"
+msgid "GPG Key ID:"
+msgstr "ID da chave GPG:"
+
msgid "GPG Keys"
msgstr "Chaves GPG"
+msgid "GPG signature (loading...)"
+msgstr "Assinatura GPG (carregando...)"
+
msgid "General"
msgstr "Geral"
+msgid "General Settings"
+msgstr "Configurações gerais"
+
msgid "General pipelines"
msgstr "Pipelines Gerais"
@@ -4273,6 +4983,9 @@ msgid "Generate a default set of labels"
msgstr "Gerar etiquetas padrão"
msgid "Generate key"
+msgstr "Gerar chave"
+
+msgid "Generate new export"
msgstr ""
msgid "Geo"
@@ -4299,9 +5012,6 @@ msgstr "Os dados estão desatualizados de %{timeago}"
msgid "GeoNodes|Data replication lag"
msgstr "Atraso de replicação de dados"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "A desabilitação de um nó interrompe o processo de sincronização. Você tem certeza?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "Não corresponde à configuração de armazenamento primário"
@@ -4320,6 +5030,9 @@ msgstr "A versão do GitLab não corresponde à versão do nó primário"
msgid "GeoNodes|Health status"
msgstr "Status de saúde"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "O ID do último evento processado por cursor"
@@ -4365,6 +5078,9 @@ msgstr "Soma de verificação não conferida"
msgid "GeoNodes|Out of sync"
msgstr "Fora de sincronia"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr "Todos os projetos estão sendo agendados para re-sincronização"
msgid "Geo|Batch operations"
msgstr "Operações em lote"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "Não foi possível remover o registro de rastreamento de um projeto existente."
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "Falha"
@@ -4491,6 +5222,9 @@ msgstr "Grupos para sincronizar"
msgid "Geo|In sync"
msgstr "Em sincronia"
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr "Sincronização pendente"
msgid "Geo|Pending verification"
msgstr "Verificação pendente"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "Projeto (ID: %{project_id}) não existe mais no primário. É seguro remover esse registro, pois isso não removerá nenhum dado no disco."
@@ -4560,24 +5300,45 @@ msgstr "Contagem de retentativas"
msgid "Geo|Select groups to replicate."
msgstr "Selecione grupos para replicar."
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "Shards para sincronizar"
msgid "Geo|Status"
msgstr "Status"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Sincronizado"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "Falha na sincronização - %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "O registro de rastreamento do projeto (%{project_id}) foi removido com êxito."
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "Registro de rastreamento será removido. Tem a certeza?"
+msgid "Geo|URL"
+msgstr "URL"
+
msgid "Geo|Unknown state"
msgstr "Estado desconhecido"
@@ -4596,9 +5357,6 @@ msgstr "Você está em um nó Geo secundário <b>somente para leitura</b>. Se vo
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "Você está em um nó Geo secundário <b>somente para leitura </b>. Você pode ser capaz de fazer um número limitado de mudanças ou executar uma quantidade limitada de ações nessa página."
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "Você precisa de uma licença diferente para usar a replicação geográfica"
-
msgid "Geo|misconfigured"
msgstr "mal configurado"
@@ -4612,17 +5370,20 @@ msgid "Get a free instance review"
msgstr "Obter gratuitamente uma instância de revisão"
msgid "Get started with error tracking"
-msgstr ""
+msgstr "Comece com o rastreamento de erros"
msgid "Getting started with releases"
-msgstr ""
+msgstr "Introdução às versões"
msgid "Git"
msgstr "Git"
-msgid "Git global setup"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
+msgid "Git global setup"
+msgstr "Configuração global do Git"
+
msgid "Git repository URL"
msgstr "URL do repositório Git"
@@ -4641,6 +5402,9 @@ msgstr "Importação do GitHub"
msgid "GitLab CI Linter has been moved"
msgstr "Linter do GitLab CI foi movido"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr "Usuário GitLab"
-msgid "GitLab metadata URL"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
msgstr ""
+msgid "GitLab metadata URL"
+msgstr "URL de metadados do GitLab"
+
msgid "GitLab project export"
msgstr "Exportação do projeto GitLab"
+msgid "GitLab restart is required to apply changes"
+msgstr "É necessário reiniciar o GitLab para aplicar as alterações"
+
msgid "GitLab single sign on URL"
msgstr "URL de logon único do GitLab"
@@ -4690,20 +5460,29 @@ msgid "Gitea Import"
msgstr "Importação do Gitea"
msgid "Given access %{time_ago}"
-msgstr ""
+msgstr "Acesso concedido %{time_ago}"
msgid "Go Back"
msgstr "Voltar"
+msgid "Go Micro is a framework for micro service development."
+msgstr "Go Micro é um framework para desenvolvimento de microsserviços."
+
msgid "Go back"
msgstr "Voltar"
+msgid "Go full screen"
+msgstr "Tela cheia"
+
msgid "Go to"
msgstr "Ir para"
msgid "Go to %{link_to_google_takeout}."
msgstr "Ir para %{link_to_google_takeout}."
+msgid "Go to project"
+msgstr "Ir para o projeto"
+
msgid "Google Code import"
msgstr "Importação do Google Code"
@@ -4717,14 +5496,23 @@ msgid "Got it!"
msgstr "Entendi!"
msgid "Grant access"
-msgstr ""
+msgstr "Conceder acesso"
msgid "Graph"
msgstr "Gráfico"
+msgid "Gravatar enabled"
+msgstr "Gravatar habilitado"
+
msgid "Group"
msgstr "Grupo"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "Configurações de CI/CD do grupo"
@@ -4734,6 +5522,9 @@ msgstr "Status do LFS do grupo Git:"
msgid "Group ID"
msgstr "ID do grupo"
+msgid "Group ID: %{group_id}"
+msgstr "ID do grupo: %{group_id}"
+
msgid "Group Runners"
msgstr "Group Runners"
@@ -4765,10 +5556,13 @@ msgid "Group name"
msgstr "Nome do grupo"
msgid "Group overview content"
+msgstr "Conteúdo da visão geral do grupo"
+
+msgid "Group was successfully updated."
msgstr ""
msgid "Group:"
-msgstr ""
+msgstr "Grupo:"
msgid "Group: %{group_name}"
msgstr "Grupo: %{group_name}"
@@ -4785,6 +5579,9 @@ msgstr "Desculpe, nenhum epic corresponde à sua pesquisa"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "O roadmap mostra o progresso de seus epics ao longo de uma linha do tempo"
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "Até %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "Selos"
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr "Personalize os selos do seu grupo."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "Saiba mais sobre os selos."
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Bloquear compartilhamento de projetos do grupo %{group} com outros grupos"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "Essa configuração é aplicada no grupo %{ancestor_group} e foi sobrescrita nesse subgrupo."
@@ -4840,7 +5715,7 @@ msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroup
msgstr "Grupos também podem ser aninhados criando %{subgroup_docs_link_start}subgrupos%{subgroup_docs_link_end}."
msgid "Groups with access to <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "Grupos com acesso a <strong>%{project_name}</strong>"
msgid "GroupsDropdown|Frequently visited"
msgstr "Visitados frequentemente"
@@ -4908,6 +5783,9 @@ msgstr "Procura por nome"
msgid "Have your users email"
msgstr "Tem o e-mail de seus usuários"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "Mensagem de cabeçalho"
@@ -4942,11 +5820,14 @@ msgid "Here is the public SSH key that needs to be added to the remote server. F
msgstr "Aqui está a chave SSH pública que precisa ser adicionada ao servidor remoto. Para mais informações, consulte a documentação."
msgid "Hide file browser"
-msgstr ""
+msgstr "Ocultar navegador de arquivos"
msgid "Hide host keys manual input"
msgstr "Ocultar entrada manual das teclas do host"
+msgid "Hide marketing-related entries from help"
+msgstr "Ocultar as entradas relacionadas ao marketing da ajuda"
+
msgid "Hide payload"
msgstr "Ocultar carga"
@@ -4956,17 +5837,32 @@ msgstr[0] "Ocultar valor"
msgstr[1] "Ocultar valores"
msgid "Hide values"
+msgstr "Ocultar valores"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr "Histórico"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "Manutenção iniciada com sucesso"
-msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgid "Housekeeping, export, path, transfer, remove, archive."
msgstr ""
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr "No entanto, você já é um membro deste %{member_source}. Faça login usando uma conta diferente para aceitar o convite."
+
msgid "I accept the %{terms_link}"
msgstr "Eu aceito o %{terms_link}"
@@ -5021,15 +5917,18 @@ msgstr "Identificador"
msgid "Identities"
msgstr "Identidades"
-msgid "Identity provider single sign on URL"
-msgstr "URL de logon único de provedor de identidade"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "Se desativada, um branch local divergente não será atualizada automaticamente com commits de sua contraparte remota, para evitar a perda de dados locais. Se o branch padrão (%{default_branch}) tiver divergido e não puder ser atualizado, o espelhamento falhará. Outras branches divergentes são silenciosamente ignoradas."
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr "Se desabilitada, somente administradores serão capazes de configurar espelhos em projetos."
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "Se desativado, o nível de acesso irá depender das permissões do usuário no projeto."
@@ -5042,9 +5941,6 @@ msgstr "Se ativado, o acesso aos projetos será validado em um serviço externo
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "Se estiver usando o GitHub, você verá os status do pipeline no GitHub para seus commits e pull requests. %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "Se você já tem arquivos, pode realizar um push usando o %{link_to_cli} abaixo."
-
msgid "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>."
msgstr "Se o seu repositório HTTP não estiver acessível publicamente, adicione informações de autenticação à URL:<code>https://username:password@gitlab.company.com/group/project.git</code>."
@@ -5064,7 +5960,7 @@ msgid "Import"
msgstr "Importar"
msgid "Import CSV"
-msgstr ""
+msgstr "Importar CSV"
msgid "Import Projects from Gitea"
msgstr "Importar projetos do Gitea"
@@ -5085,13 +5981,13 @@ msgid "Import in progress"
msgstr "Importação em andamento"
msgid "Import issues"
-msgstr ""
+msgstr "Importar issues"
msgid "Import members"
-msgstr ""
+msgstr "Importar membros"
msgid "Import members from another project"
-msgstr ""
+msgstr "Importar membros de outro projeto"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "Importar vários repositórios fazendo o upload de um arquivo manifest."
@@ -5100,7 +5996,7 @@ msgid "Import project"
msgstr "Importar projeto"
msgid "Import project members"
-msgstr ""
+msgstr "Importar membros do projeto"
msgid "Import projects from Bitbucket"
msgstr "Importar projetos do Bitbucket"
@@ -5127,14 +6023,32 @@ msgid "Import repository"
msgstr "Importar repositório"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
-msgstr ""
+msgstr "Importação expirou. A importação demorou mais de %{import_jobs_expiration} segundos"
+
+msgid "Import/Export illustration"
+msgstr "Importar/exportar ilustração"
msgid "ImportButtons|Connect repositories from"
msgstr "Conectar repositórios de"
+msgid "ImportProjects|Importing the project failed"
+msgstr "Falha ao importar o projeto"
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr "Falha ao solicitar seus repositórios do %{provider}"
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr "Selecione os projetos que você deseja importar"
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr "Falha ao atualizar os projetos importados"
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "Melhore os painéis com GitLab Enterprise Edition."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "Melhore o gerenciamento de issues com o peso de issues e GitLab Enterprise Edition."
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "Na próxima etapa, você poderá selecionar os projetos que deseja importar."
+msgid "Incidents"
+msgstr "Incidentes"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "Inclua um contrato de Termos de Serviço e uma Política de Privacidade que todos os usuários devem aceitar."
+msgid "Include author name in notification email body"
+msgstr "Incluir o nome do autor no corpo do e-mail de notificação"
+
msgid "Include merge request description"
-msgstr ""
+msgstr "Incluir a descrição do merge request"
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "Inclua o nome de usuário no URL, se necessário: <code>https: //username@gitlab.company.com/group/project.git</code>."
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr "Projeto Incompatível"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "Indica se este runner pode escolher tarefas sem tags"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "Em linha"
@@ -5183,8 +6109,17 @@ msgstr "Insira as chaves do host manualmente"
msgid "Input your repository URL"
msgstr "Insira seu URL do repositório"
+msgid "Insert a quote"
+msgstr "Inserir uma citação"
+
+msgid "Insert code"
+msgstr "Inserir código"
+
msgid "Insert suggestion"
-msgstr ""
+msgstr "Inserir sugestão"
+
+msgid "Insights"
+msgstr "Insights"
msgid "Install GitLab Runner"
msgstr "Instalar o GitLab Runner"
@@ -5206,6 +6141,9 @@ msgstr "Visibilidade de Estatísticas de Instância"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "A instância não suporta múltiplos clusters Kubernetes"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "Integrações"
@@ -5216,7 +6154,7 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr "As partes interessadas podem até contribuir enviando commits, caso queiram."
msgid "Internal"
-msgstr ""
+msgstr "Interno"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "Interno - O grupo e projetos internos podem ser visualizados por qualquer usuário autenticado."
@@ -5236,17 +6174,32 @@ msgstr "Apresentando a Análise de Ciclo"
msgid "Introducing Your Conversational Development Index"
msgstr ""
-msgid "Invitation"
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
msgstr ""
+msgid "Invalid input, please avoid emojis"
+msgstr "Entrada inválida, por favor, evite emojis"
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr "Convite"
+
msgid "Invite"
msgstr "Convidar"
msgid "Invite group"
-msgstr ""
+msgstr "Convidar grupo"
msgid "Invite member"
-msgstr ""
+msgstr "Convidar membro"
msgid "Invoke Count"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Issue"
@@ -5266,6 +6222,9 @@ msgstr "Modo de foco nos painéis"
msgid "Issue events"
msgstr "Eventos de issue"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "Painel"
@@ -5273,10 +6232,10 @@ msgid "IssueBoards|Boards"
msgstr "Painéis"
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "Criar novo painel"
msgid "IssueBoards|Delete board"
-msgstr ""
+msgstr "Excluir painel"
msgid "IssueBoards|No matching boards found"
msgstr ""
@@ -5285,7 +6244,7 @@ msgid "IssueBoards|Some of your boards are hidden, activate a license to see the
msgstr ""
msgid "IssueBoards|Switch board"
-msgstr ""
+msgstr "Alternar painel"
msgid "Issues"
msgstr "Issues"
@@ -5296,7 +6255,10 @@ msgstr "Issues podem ser bugs, tarefas ou ideias a serem discutidas. Além disso
msgid "Issues closed"
msgstr "Issues fechadas"
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr "Issues, merge requests, pushes e comentários."
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5321,10 +6283,10 @@ msgid "IssuesAnalytics|To widen your search, change or remove filters in the fil
msgstr "Para ampliar sua pesquisa, altere ou remova filtros na barra de filtros acima"
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
-msgstr ""
+msgstr "Deve ter uma linha de cabeçalho e pelo menos duas colunas: a primeira coluna é o título da issue e a segunda coluna é a descrição da issue. O separador é detectado automaticamente."
msgid "It's you"
-msgstr ""
+msgstr "É você"
msgid "Jaeger URL"
msgstr "URL Jaeger"
@@ -5341,10 +6303,28 @@ msgstr "Janeiro"
msgid "Job"
msgstr "Trabalho"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "A tarefa foi apagada"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
+msgstr "A tarefa travou. Verifique os runners."
+
+msgid "Job traces and artifacts"
msgstr ""
msgid "Job was retried"
@@ -5377,6 +6357,9 @@ msgstr "A tarefa foi apagada por"
msgid "Job|Keep"
msgstr "Manter"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "Rolar para baixo"
@@ -5392,9 +6375,21 @@ msgstr "Os artefatos foram removidos"
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "Jul"
@@ -5408,7 +6403,7 @@ msgid "June"
msgstr "Junho"
msgid "Key (PEM)"
-msgstr ""
+msgstr "Chave (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5437,9 +6432,15 @@ msgstr "Kubernetes configurado"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "Integração de serviço Kubernetes foi depreciada. %{deprecated_message_content} seus clusters Kubernetes usando a nova página <a href=\"%{url}\"/>Clusters Kubernetes</a>"
+msgid "LDAP settings"
+msgstr "Configurações LDAP"
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Desabilitado"
@@ -5455,6 +6456,15 @@ msgstr "Dropdown de ações de etiqueta"
msgid "Label lists show all issues with the selected label."
msgstr "Listas de etiqueta mostram todas as issues com a etiqueta selecionada."
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} mais"
@@ -5485,6 +6495,9 @@ msgstr "Promover etiqueta"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "Promover %{labelTitle} irá disponibilizá-la para todos os projetos dentro de %{groupName}. Etiquetas de projetos existentes com o mesmo título serão mescladas. Esta ação não pode ser revertida."
+msgid "Language"
+msgstr "Idioma"
+
msgid "Large File Storage"
msgstr "Armazenamento de arquivos grandes"
@@ -5497,7 +6510,7 @@ msgid "Last Pipeline"
msgstr "Último Pipeline"
msgid "Last activity"
-msgstr ""
+msgstr "Última atividade"
msgid "Last commit"
msgstr "Último commit"
@@ -5515,7 +6528,7 @@ msgid "Last reply by"
msgstr "Última resposta de"
msgid "Last seen"
-msgstr ""
+msgstr "Visto pela última vez"
msgid "Last update"
msgstr "Última atualização"
@@ -5539,7 +6552,7 @@ msgid "Lead"
msgstr ""
msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
-msgstr ""
+msgstr "Saiba como %{no_packages_link_start}publicar e compartilhar seus pacotes%{no_packages_link_end} com o GitLab."
msgid "Learn more"
msgstr "Saiba mais"
@@ -5548,25 +6561,25 @@ msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple
msgstr "Saiba mais sobre %{issue_boards_url}, para acompanhar issues em diversas listas, usando etiquetas, atribuições e marcos. Se você notar algo faltando nos painéis, por favor, crie uma issue em %{gitlab_issues_url}."
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "Saiba mais sobre o Auto DevOps"
msgid "Learn more about Kubernetes"
msgstr "Saiba mais sobre o Kubernetes"
msgid "Learn more about Web Terminal"
-msgstr ""
+msgstr "Saiba mais sobre o Terminal Web"
+
+msgid "Learn more about approvals."
+msgstr "Saiba mas sobre aprovações."
msgid "Learn more about custom project templates"
-msgstr ""
+msgstr "Saiba mais sobre os modelos de projetos personalizados"
msgid "Learn more about group-level project templates"
-msgstr ""
+msgstr "Saiba mais sobre os modelos de projeto de nível de grupo"
-msgid "Learn more about incoming email addresses"
-msgstr ""
-
-msgid "Learn more about protected branches"
-msgstr "Saiba mais sobre branches protegidos"
+msgid "Learn more about signing commits"
+msgstr "Saiba mais sobre assinar commits"
msgid "Learn more in the"
msgstr "Saiba mais em"
@@ -5577,6 +6590,9 @@ msgstr "documentação de agendamento de pipeline"
msgid "Leave"
msgstr "Sair"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr "Sair do modo de edição? Todas as alterações não salvas serão perdidas."
+
msgid "Leave group"
msgstr "Sair do grupo"
@@ -5661,9 +6677,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "Você está prestes a remover a licença %{name} deste projeto."
+msgid "Licensed to"
+msgstr "Licenciado para"
+
msgid "Licenses"
msgstr "Licenças"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limitado a mostrar %d evento, no máximo"
@@ -5681,6 +6703,9 @@ msgstr "Listar os seus Repositórios do Gitea"
msgid "List available repositories"
msgstr "Listar repositórios disponíveis"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "Exibir em lista"
@@ -5703,10 +6728,10 @@ msgid "Loading..."
msgstr "Carregando..."
msgid "Loading…"
-msgstr ""
+msgstr "Carregando…"
msgid "Localization"
-msgstr ""
+msgstr "Localização"
msgid "Lock"
msgstr "Bloquear"
@@ -5738,15 +6763,36 @@ msgstr "Travas possibilitam travar um arquivo ou uma pasta específica."
msgid "Login with smartcard"
msgstr "Entrar com cartão inteligente"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Logs"
+msgid "MRApprovals|Approved by"
+msgstr "Aprovado por"
+
+msgid "MRApprovals|Approvers"
+msgstr "Aprovadores"
+
+msgid "MRApprovals|Pending approvals"
+msgstr "Aprovações pendentes"
+
+msgid "MRDiff|Show changes only"
+msgstr "Mostrar apenas alterações"
+
+msgid "MRDiff|Show full file"
+msgstr "Mostrar arquivo completo"
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Faça com que todos em sua equipe sejam mais produtivos, independentemente da localização deles. O GitLab Geo cria espelhos somente leitura de sua instância do GitLab para que você possa reduzir o tempo necessário para clonar e buscar grandes repositórios."
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "Certifique-se de que você está conectado à conta que possui os projetos que você deseja importar."
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr "Gerencie repositórios Git com controles de acesso refinados que mantêm seu código seguro. Realize revisões de código e aprimore a colaboração com merge requests. Cada projeto também pode ter um rastreador de issue e um wiki."
@@ -5775,10 +6821,7 @@ msgid "Manage project labels"
msgstr "Gerenciar etiquetas de projetos"
msgid "Manage two-factor authentication"
-msgstr ""
-
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "Gerencie a qualidade de membros do seu grupo ao adicionar outro nível de segurança com o SAML."
+msgstr "Gerenciar a autenticação de dois fatores"
msgid "Manifest"
msgstr "Manifesto"
@@ -5786,6 +6829,9 @@ msgstr "Manifesto"
msgid "Manifest file import"
msgstr "Importação de arquivo de manifesto"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Associar um ID de conta do FogBugz para um usuário do GitLab"
@@ -5808,49 +6854,46 @@ msgid "Mark todo as done"
msgstr "Marcar como concluído"
msgid "Markdown"
+msgstr "Markdown"
+
+msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr "Markdown habilitado"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "Adicionar uma lista de marcadores"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "Adicionar um link"
+msgid "Maven Metadata"
+msgstr "Metadado do Maven"
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "Adicionar uma lista numerada"
+msgid "Max access level"
+msgstr "Nível máximo de acesso"
-msgid "MarkdownToolbar|Add a table"
-msgstr "Adicionar uma tabela"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add a task list"
-msgstr "Adicionar uma lista de tarefas"
+msgid "Maximum artifacts size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr "Adicionar texto em negrito"
+msgid "Maximum attachment size (MB)"
+msgstr "Tamanho máximo de anexos (MB)"
-msgid "MarkdownToolbar|Add italic text"
-msgstr "Adicionar texto em itálico"
+msgid "Maximum capacity"
+msgstr "Capacidade máxima"
-msgid "MarkdownToolbar|Go full screen"
-msgstr "Ir para tela cheia"
+msgid "Maximum delay (Minutes)"
+msgstr "Atraso máximo (minutos)"
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "Inserir uma citação"
-
-msgid "MarkdownToolbar|Insert code"
-msgstr "Inserir código"
+msgid "Maximum job timeout"
+msgstr "Tempo limite máximo da tarefa"
-msgid "Maven Metadata"
-msgstr "Metadado do Maven"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
-msgid "Max access level"
-msgstr "Nível máximo de acesso"
+msgid "Maximum push size (MB)"
+msgstr ""
-msgid "Maximum job timeout"
-msgstr "Tempo limite máximo da tarefa"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "Mai"
@@ -5868,33 +6911,45 @@ msgid "Members"
msgstr "Membros"
msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
-msgstr ""
+msgstr "Os membros podem ser adicionados pelos <i>mantenedores</i> ou <i>proprietários</i> do projeto"
msgid "Members of <strong>%{project_name}</strong>"
-msgstr ""
-
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "Membros serão encaminhados quando fizerem login no seu grupo. Obtenha isso pelo seu provedor de identidade, também chamado de \"SSO Service Location\", \"SAM Token Issuance Endpoint\", ou \"SAML 2.0/W-Federation URL\"."
+msgstr "Membros de <strong>%{project_name}</strong>"
msgid "Merge Request"
msgstr "Merge Request"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Merge Requests"
msgid "Merge Requests created"
msgstr "Merge Requests criadas"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
+msgstr "Mensagem do commit de merge"
+
+msgid "Merge commit with semi-linear history"
msgstr ""
msgid "Merge events"
msgstr "Eventos de merge"
msgid "Merge immediately"
-msgstr ""
+msgstr "Fazer merge imediatamente"
msgid "Merge in progress"
+msgstr "Merge em andamento"
+
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
msgstr ""
msgid "Merge request"
@@ -5903,6 +6958,9 @@ msgstr "Merge requests"
msgid "Merge request approvals"
msgstr "Aprovações de merge request"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Merge requests"
@@ -5913,28 +6971,28 @@ msgid "Merge when pipeline succeeds"
msgstr ""
msgid "MergeRequests|Add a reply"
-msgstr ""
+msgstr "Adicionar uma resposta"
msgid "MergeRequests|An error occurred while saving the draft comment."
msgstr "Ocorreu um erro ao salvar o rascunho do comentário."
msgid "MergeRequests|Discussion stays resolved"
-msgstr ""
+msgstr "Discussão permanece resolvida"
msgid "MergeRequests|Discussion stays unresolved"
-msgstr ""
+msgstr "Discussão permanece não resolvida"
msgid "MergeRequests|Discussion will be resolved"
-msgstr ""
+msgstr "Discussão será resolvida"
msgid "MergeRequests|Discussion will be unresolved"
-msgstr ""
+msgstr "Discussão não será resolvida"
msgid "MergeRequests|Jump to next unresolved discussion"
-msgstr ""
+msgstr "Ir para a próxima discussão não resolvida"
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "Responder..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr "Resolver essa discussão em um novo issue"
@@ -5952,38 +7010,41 @@ msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr "Ver arquivo substituído @ %{commitId}"
msgid "MergeRequests|commented on commit %{commitLink}"
-msgstr ""
+msgstr "comentou no commit %{commitLink}"
msgid "MergeRequests|started a discussion"
-msgstr ""
+msgstr "iniciou uma discussão"
msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
-msgstr ""
+msgstr "iniciou uma discussão sobre %{linkStart}uma versão antiga do diff%{linkEnd}"
msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
-msgstr ""
+msgstr "iniciou uma discussão sobre %{linkStart}o diff%{linkEnd}"
msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "iniciou uma discussão sobre uma alteração desatualizada no commit %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "iniciou uma discussão no commit %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart}alterou a descrição %{descriptionChangedTimes} vezes %{timeDifferenceMinutes}%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr "Erro ao carregar o diff completo. Por favor, tente novamente."
+
msgid "MergeRequest|Filter files"
msgstr "Filtrar arquivos"
msgid "MergeRequest|No files found"
msgstr "Nenhum arquivo encontrado"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "Merge realizado"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "Mensagens"
@@ -6041,14 +7102,17 @@ msgstr "Nova métrica"
msgid "Metrics|No deployed environments"
msgstr "Nenhum ambiente implantado"
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr "Documentação de consulta do Prometheus"
-msgid "Metrics|System"
-msgstr "Sistema"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "Houve um erro ao obter os dados do ambiente; por favor, tente novamente"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "ex., Taxa de transferência"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Marco"
@@ -6134,6 +7204,9 @@ msgstr "Promover %{milestone} irá disponibilizá-lo para todos os projetos dent
msgid "Milestones|This action cannot be reversed."
msgstr "Essa ação não pode ser revertida."
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "Espelhar um repositório"
@@ -6152,6 +7225,9 @@ msgstr "Repositórios espelhados"
msgid "Mirroring repositories"
msgstr "Espelhando repositórios"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "adicione uma chave SSH"
@@ -6161,17 +7237,20 @@ msgstr "Cancelar"
msgid "Modal|Close"
msgstr "Fechar"
+msgid "Modify commit message"
+msgstr "Modificar mensagem de commit"
+
msgid "Modify commit messages"
-msgstr ""
+msgstr "Modificar mensagens de commit"
msgid "Modify merge commit"
msgstr ""
msgid "Monday"
-msgstr ""
+msgstr "Segunda-feira"
msgid "Monitor your errors by integrating with Sentry"
-msgstr ""
+msgstr "Monitore seus erros através da integração com o Sentry"
msgid "Monitoring"
msgstr "Monitoramento"
@@ -6194,9 +7273,15 @@ msgstr "Mais informações"
msgid "More information is available|here"
msgstr "Mais informações estão disponíveis|aqui"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Mais estrelas"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "Mover"
@@ -6206,6 +7291,12 @@ msgstr "Mover issue"
msgid "Multiple issue boards"
msgstr "Múltiplos painéis"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Nome"
@@ -6218,6 +7309,12 @@ msgstr "Nomeie sua chave individual por meio de um título"
msgid "Name:"
msgstr "Nome:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "Nomenclatura, visibilidade"
@@ -6234,7 +7331,7 @@ msgid "Nav|Sign out and sign in with a different account"
msgstr "Saia e faça login com uma conta diferente"
msgid "Need help?"
-msgstr ""
+msgstr "Precisa de ajuda?"
msgid "Network"
msgstr "Rede"
@@ -6266,26 +7363,29 @@ msgid "New Label"
msgstr "Nova etiqueta"
msgid "New Milestone"
-msgstr ""
+msgstr "Novo Marco"
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr "Nova senha"
+
msgid "New Pipeline Schedule"
msgstr "Novo Agendamento de Pipeline"
msgid "New Snippet"
msgstr "Novo Snippet"
-msgid "New Snippets"
-msgstr "Novos Snippets"
-
msgid "New branch"
msgstr "Novo branch"
msgid "New branch unavailable"
msgstr "Novo branch indisponível"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Novo diretório"
@@ -6301,6 +7401,9 @@ msgstr "Novo arquivo"
msgid "New group"
msgstr "Novo grupo"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "Nova identidade"
@@ -6314,7 +7417,7 @@ msgid "New merge request"
msgstr "Novo merge request"
msgid "New milestone"
-msgstr ""
+msgstr "Novo marco"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "Novos pipelines cancelarão pipelines pendentes mais antigos no mesmo branch"
@@ -6322,6 +7425,9 @@ msgstr "Novos pipelines cancelarão pipelines pendentes mais antigos no mesmo br
msgid "New project"
msgstr "Novo projeto"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Novo agendamento"
@@ -6334,20 +7440,29 @@ msgstr "Novo subgrupo"
msgid "New tag"
msgstr "Nova tag"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "Novo..."
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "Não"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "Sem etiqueta"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "Sem responsável"
+msgid "No activities found"
+msgstr "Nenhuma atividade encontrada"
msgid "No branches found"
msgstr "Nenhuma branch encontrada"
@@ -6371,13 +7486,13 @@ msgid "No credit card required."
msgstr "Não é necessário cartão de crédito."
msgid "No details available"
-msgstr ""
+msgstr "Sem detalhes disponíveis"
msgid "No due date"
msgstr "Sem validade"
-msgid "No errors to display"
-msgstr ""
+msgid "No errors to display."
+msgstr "Nenhum erro para exibir."
msgid "No estimate or time spent"
msgstr "Sem estimativa de tempo gasto"
@@ -6386,7 +7501,7 @@ msgid "No file chosen"
msgstr "Nenhum arquivo escolhido"
msgid "No file selected"
-msgstr ""
+msgstr "Nenhum arquivo selecionado"
msgid "No files found."
msgstr "Nenhum arquivo encontrado."
@@ -6394,13 +7509,22 @@ msgstr "Nenhum arquivo encontrado."
msgid "No issues for the selected time period."
msgstr "Sem issues para o período de tempo selecionado."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "Sem etiquetas com esse nome ou descrição"
msgid "No license. All rights reserved"
msgstr "Nenhuma licença. Todos os direitos reservados"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
+msgstr "Nenhum resultado correspondente"
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
msgid "No merge requests for the selected time period."
@@ -6413,7 +7537,7 @@ msgid "No messages were logged"
msgstr "Nenhuma mensagem foi registrada"
msgid "No milestones to show"
-msgstr ""
+msgstr "Sem marcos para mostrar"
msgid "No other labels with such name or description"
msgstr "Sem outras etiquetas com esse nome ou descrição"
@@ -6451,9 +7575,6 @@ msgstr "Nós"
msgid "None"
msgstr "Nenhum"
-msgid "Not allowed to merge"
-msgstr "Merge não permitido"
-
msgid "Not available"
msgstr "Não disponível"
@@ -6469,11 +7590,17 @@ msgstr "Não confidencial"
msgid "Not enough data"
msgstr "Dados insuficientes"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "Agora não"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Observe que o branch master é automaticamente protegido. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6509,10 +7636,10 @@ msgid "Notification events"
msgstr "Eventos de notificação"
msgid "Notification setting"
-msgstr ""
+msgstr "Configuração de notificação"
msgid "Notification setting - %{notification_title}"
-msgstr ""
+msgstr "Configuração de notificação - %{notification_title}"
msgid "NotificationEvent|Close issue"
msgstr "Fechar issue"
@@ -6586,6 +7713,9 @@ msgstr "Novembro"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "Out"
@@ -6598,11 +7728,20 @@ msgstr "Filtrar"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "Uma vez importados, repositórios podem ser espelhados por SSH. Leia mais em %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Mais um item"
msgstr[1] "Mais %d itens"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Um ou mais dos seus projetos do Bitbucket não podem ser importados diretamente no GitLab porque eles usam Subversion ou Mercurial para o controle de versão, ao invés de Git."
@@ -6612,6 +7751,12 @@ msgstr "Um ou mais dos seus projetos do Google Code não podem ser importados di
msgid "Only admins"
msgstr "Apenas administradores"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "Apenas branches protegidos por espelho"
@@ -6625,7 +7770,7 @@ msgid "Only project members can comment."
msgstr "Somente membros do projeto podem comentar."
msgid "Only project members will be imported. Group members will be skipped."
-msgstr ""
+msgstr "Apenas membros do projeto serão importados. Membros do grupo serão ignorados."
msgid "Oops, are you sure?"
msgstr "Oops, você tem certeza?"
@@ -6634,13 +7779,13 @@ msgid "Open"
msgstr "Abrir"
msgid "Open Documentation"
-msgstr ""
+msgstr "Abrir documentação"
msgid "Open comment type dropdown"
msgstr ""
msgid "Open errors"
-msgstr ""
+msgstr "Abrir erros"
msgid "Open in Xcode"
msgstr "Abrir no Xcode"
@@ -6648,6 +7793,9 @@ msgstr "Abrir no Xcode"
msgid "Open projects"
msgstr "Abrir projetos"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "Abrir barra lateral"
@@ -6675,15 +7823,30 @@ msgstr "Operações"
msgid "Operations Dashboard"
msgstr "Painel de Operações"
+msgid "Operations Settings"
+msgstr "Configurações de operações"
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Adicione um projeto ao painel"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr "O painel de operações fornece um resumo da integridade operacional de cada projeto, incluindo status de pipeline e alerta."
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr "Opcional"
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr "Opcionalmente, você pode %{link_to_customize} como os endereços de e-mail e nomes de usuários do FogBugz são importados para o GitLab."
@@ -6702,9 +7865,6 @@ msgstr "Outras etiquetas"
msgid "Other information"
msgstr "Outra informação"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "Caso contrário, é recomendado que você inicie com uma das opções abaixo."
-
msgid "Outbound requests"
msgstr "Pedidos de saída"
@@ -6726,14 +7886,20 @@ msgstr "O pacote foi removido"
msgid "Packages"
msgstr "Pacotes"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Páginas"
msgid "Pages Domain"
-msgstr ""
+msgstr "Domínio de páginas"
msgid "Pages Domains"
-msgstr ""
+msgstr "Domínios de páginas"
msgid "Pagination|Last »"
msgstr "Último >>"
@@ -6748,10 +7914,7 @@ msgid "Pagination|« First"
msgstr "<< Primeiro"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "Parâmetro"
msgid "Part of merge request changes"
msgstr "Parte das mudanças do merge request"
@@ -6759,7 +7922,13 @@ msgstr "Parte das mudanças do merge request"
msgid "Password"
msgstr "Senha"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
+msgstr "Atrasado"
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
msgid "Paste epic link"
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "Cole a sua chave SSH pública, que geralmente é encontrada no arquivo '~/.ssh/id_rsa.pub' e começa com 'ssh-rsa'. Não use a sua chave SSH privada."
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "Caminho, transferir, remover"
@@ -6780,6 +7952,9 @@ msgstr "Caminho:"
msgid "Pause"
msgstr "Pausar"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "Runners pausados não aceitam novas tarefas"
@@ -6798,6 +7973,9 @@ msgstr "Otimização de performance"
msgid "Permissions"
msgstr "Permissões"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "Permissões, LFS, 2FA"
@@ -6808,26 +7986,29 @@ msgid "Personal project creation is not allowed. Please contact your administrat
msgstr ""
msgid "Pick a name"
-msgstr ""
+msgstr "Escolha um nome"
msgid "Pipeline"
msgstr "Pipeline"
-msgid "Pipeline Health"
-msgstr "Saúde da Pipeline"
-
msgid "Pipeline Schedule"
msgstr "Agendamento da Pipeline"
msgid "Pipeline Schedules"
msgstr "Agendamentos da Pipeline"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "Cota de pipeline"
msgid "Pipeline triggers"
msgstr "Gatilho de pipeline"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Falhou:"
@@ -6876,6 +8057,12 @@ msgstr "Variáveis"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizado"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Pipelines"
@@ -6891,6 +8078,12 @@ msgstr "Pipelines para a última semana"
msgid "Pipelines for last year"
msgstr "Pipelines para o último ano"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "Construa com confiança"
@@ -6933,11 +8126,8 @@ msgstr "Este projeto não está atualmente configurado para executar pipelines."
msgid "Pipeline|Commit"
msgstr "Commit"
-msgid "Pipeline|Create for"
-msgstr "Criar para"
-
-msgid "Pipeline|Create pipeline"
-msgstr "Criar pipeline"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "Duração"
@@ -6951,6 +8141,9 @@ msgstr "Pipeline"
msgid "Pipeline|Run Pipeline"
msgstr "Executar Pipeline"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "Pesquisar branches"
@@ -6978,9 +8171,21 @@ msgstr "Você está prestes a interromper o pipeline %{pipelineId}."
msgid "Pipeline|all"
msgstr "todos"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "sucesso"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "com etapa"
@@ -6997,7 +8202,7 @@ msgid "Play"
msgstr "Iniciar"
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
-msgstr ""
+msgstr "Por favor, %{link_to_register} ou %{link_to_sign_in} para comentar"
msgid "Please accept the Terms of Service before continuing."
msgstr "Por favor, aceite os Termos de Serviço antes de continuar."
@@ -7005,15 +8210,33 @@ msgstr "Por favor, aceite os Termos de Serviço antes de continuar."
msgid "Please choose a group URL with no special characters."
msgstr "Por favor, escolha um URL de grupo sem caracteres especiais."
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Por favor, converta-os para %{link_to_git} e passe pelo %{link_to_import_flow} novamente."
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "Por favor, converta-os em Git no Google Code e passe pelo %{link_to_import_flow} novamente."
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr "Crie um nome de usuário com apenas caracteres alfanuméricos."
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr "Digite um número não-negativo"
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr "Digite um número válido"
+
msgid "Please fill in a descriptive name for your group."
msgstr "Por favor, preencha um nome descritivo para o seu grupo."
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "Por favor, note que esse aplicativo não é fornecido pelo GitLab e você deve verificar a sua autenticidade antes de permitir o acesso."
+msgid "Please provide a name"
+msgstr "Por favor, forneça um nome"
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr "Por favor, selecione e adicione um membro"
+
msgid "Please select at least one filter to see results"
msgstr "Por favor selecione pelo menos um filtro para ver os resultados"
+msgid "Please set a new password before proceeding."
+msgstr "Defina uma nova senha antes de continuar."
+
msgid "Please solve the reCAPTCHA"
msgstr "Por favor, resolva o reCAPTCHA"
@@ -7033,11 +8268,14 @@ msgid "Please try again"
msgstr "Por favor, tente novamente"
msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
+msgstr "Por favor, atualize o PostgreSQL para a versão 9.6 ou superior. O status da replicação não pode ser determinado de maneira confiável com a versão atual."
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "Use este formulário para denunciar usuários ao GitLab que criam spam de issues, comentários ou se comportam de maneira inapropriada."
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "Por favor, aguarde enquanto conectamos ao seu repositório. Atualize à vontade."
@@ -7047,18 +8285,33 @@ msgstr "Por favor, aguarde enquanto importamos o repositório para você. Atuali
msgid "Preferences"
msgstr "Preferências"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "Tema de navegação"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr "Este recurso é experimental e as traduções ainda não estão completas"
+
msgid "Press Enter or click to search"
msgstr "Pressione Enter ou clique para pesquisar"
msgid "Prevent adding new members to project membership within this group"
msgstr "Impede a adição de novos membros ao projeto pertencentes a este grupo"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Pré-visualizar"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "Pré-visualização de carga"
@@ -7078,7 +8331,7 @@ msgid "Prioritized label"
msgstr "Etiqueta priorizada"
msgid "Private"
-msgstr ""
+msgstr "Privado"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cada usuário."
@@ -7086,6 +8339,9 @@ msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cad
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Privado - O grupo e seus projetos só podem ser vistos por seus membros."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Projetos privados podem ser criados em seu namespace pessoal com:"
@@ -7102,16 +8358,16 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr "Você vai alterar o nome de usuário %{currentUsernameBold} para %{newUsernameBold}. O perfil e os projetos serão redirecionados para %{newUsername} mas esse redirecionamento expirará quando %{currentUsername} for registrado por outro usuário ou grupo. Por favor, atualize seus repositórios remotos Git o mais rápido possível."
msgid "Profiles|@username"
-msgstr ""
+msgstr "@nomedeusuário"
msgid "Profiles|Account scheduled for removal."
msgstr "Conta agendada para remoção."
msgid "Profiles|Activate signin with one of the following services"
-msgstr ""
+msgstr "Ative o login com um dos seguintes serviços"
msgid "Profiles|Active"
-msgstr ""
+msgstr "Ativo"
msgid "Profiles|Add key"
msgstr "Adicionar chave"
@@ -7129,28 +8385,28 @@ msgid "Profiles|Change username"
msgstr "Alterar nome de usuário"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "Alterar o seu nome de usuário pode ter efeitos colaterais indesejados."
msgid "Profiles|Choose file..."
msgstr "Escolher arquivo..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
-msgstr ""
+msgstr "Opte por mostrar contribuições de projetos privados em seu perfil público sem quaisquer informações do projeto, repositório ou da organização"
msgid "Profiles|City, country"
-msgstr ""
+msgstr "Cidade, país"
msgid "Profiles|Clear status"
msgstr "Limpar status"
msgid "Profiles|Click on icon to activate signin with one of the following services"
-msgstr ""
+msgstr "Clique no ícone para ativar o login com um dos seguintes serviços"
msgid "Profiles|Connect"
-msgstr ""
+msgstr "Conectar"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "Contas conectadas"
msgid "Profiles|Current path: %{path}"
msgstr "Caminho atual: %{path}"
@@ -7171,7 +8427,7 @@ msgid "Profiles|Deleting an account has the following effects:"
msgstr "A exclusão de uma conta tem os seguintes efeitos:"
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "Desconectar"
msgid "Profiles|Do not show on profile"
msgstr "Não mostrar no perfil"
@@ -7183,10 +8439,10 @@ msgid "Profiles|Edit Profile"
msgstr "Editar perfil"
msgid "Profiles|Enter your name, so people you know can recognize you"
-msgstr ""
+msgstr "Digite seu nome, então as pessoas que você conhece podem reconhecê-lo"
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
-msgstr ""
+msgstr "Aumente a segurança da sua conta ativando a autenticação de dois fatores (A2F)"
msgid "Profiles|Invalid password"
msgstr "Senha inválida"
@@ -7225,37 +8481,37 @@ msgid "Profiles|Set new profile picture"
msgstr "Definir nova foto de perfil"
msgid "Profiles|Social sign-in"
-msgstr ""
+msgstr "Login social"
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr "Algumas opções estão indisponíveis para contas LDAP"
msgid "Profiles|Tell us about yourself in fewer than 250 characters"
-msgstr ""
+msgstr "Conte-nos sobre você em menos de 250 caracteres"
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "O tamanho máximo de arquivo permitido é de 200KB."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "Isso não se parece com uma chave pública SSH, você tem certeza que gostaria de adicioná-la?"
msgid "Profiles|This email will be displayed on your public profile"
-msgstr ""
+msgstr "Este e-mail será exibido no seu perfil público"
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "Este e-mail será usado para operações baseadas na web, como edições e merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr "Este e-mail será usado para operações baseadas na web, como edições e merges. %{commit_email_link_start}Saiba mais%{commit_email_link_end}"
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "Este emoji e mensagem aparecerão no seu perfil e em toda a interface."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
-msgstr ""
+msgstr "Esta informação aparecerá no seu perfil"
msgid "Profiles|Two-Factor Authentication"
-msgstr ""
+msgstr "Autenticação de dois fatores"
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "Escreva sua %{confirmationValue} para confirmar:"
@@ -7282,13 +8538,13 @@ msgid "Profiles|Username successfully changed"
msgstr "Alteração de nome de usuário realizada com sucesso"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "Usar emojis em nomes parece divertido, mas tente definir uma mensagem de status"
msgid "Profiles|What's your status?"
msgstr "Qual é o seu status?"
msgid "Profiles|Who you represent or work for"
-msgstr ""
+msgstr "Por quem você representa ou trabalha"
msgid "Profiles|You can change your avatar here"
msgstr "Você pode alterar o seu avatar aqui"
@@ -7309,7 +8565,7 @@ msgid "Profiles|You must transfer ownership or delete these groups before you ca
msgstr "Você precisa delegar outro usuário para ser dono ou apagar esses grupos antes de excluir sua conta."
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
-msgstr ""
+msgstr "Seu nome de perfil no LinkedIn em linkedin.com/in/nomedeperfil"
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr "Sua conta é atualmente proprietária dos seguintes grupos:"
@@ -7318,10 +8574,10 @@ msgid "Profiles|Your email address was automatically set based on your %{provide
msgstr ""
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "A sua localização foi definida automaticamente com base na sua conta do %{provider_label}"
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
-msgstr ""
+msgstr "O seu nome foi definido automaticamente com base na sua conta do %{provider_label} para que as pessoas que conhece possam reconhecê-lo"
msgid "Profiles|Your status"
msgstr "Seu status"
@@ -7330,10 +8586,10 @@ msgid "Profiles|e.g. My MacBook key"
msgstr "por exemplo, Chave do meu MacBook"
msgid "Profiles|username"
-msgstr ""
+msgstr "nome de usuário"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "sitedaweb.com"
msgid "Profiles|your account"
msgstr "sua conta"
@@ -7350,6 +8606,15 @@ msgstr "Progresso"
msgid "Project"
msgstr "Projeto"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr "O projeto \"%{name}\" não está mais disponível. Selecione um outro projeto para continuar."
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "O projeto '%{project_name}' está sendo excluído."
@@ -7365,17 +8630,26 @@ msgstr "Projeto '%{project_name}' atualizado com sucesso."
msgid "Project Badges"
msgstr "Selos de projeto"
+msgid "Project ID"
+msgstr "ID do projeto"
+
msgid "Project URL"
msgstr "URL do projeto"
msgid "Project access must be granted explicitly to each user."
msgstr "Acesso ao projeto deve ser concedido explicitamente para cada usuário."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Imagem do projeto"
-msgid "Project avatar in repository: %{link}"
-msgstr "Imagem do projeto no repositório: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr "Descrição do projeto (opcional)"
msgid "Project details"
msgstr "Detalhes do projeto"
@@ -7383,6 +8657,9 @@ msgstr "Detalhes do projeto"
msgid "Project export could not be deleted."
msgstr "A exportação do projeto não pôde ser excluída."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "Exportação do projeto excluída."
@@ -7392,18 +8669,27 @@ msgstr "O link para a exportação do projeto expirou. Favor gerar uma nova expo
msgid "Project export started. A download link will be sent by email."
msgstr "Exportação do projeto iniciada. Um link para baixá-la será enviado por email."
-msgid "Project members"
+msgid "Project has too many %{label_for_message} to search"
msgstr ""
+msgid "Project members"
+msgstr "Membros do projeto"
+
msgid "Project name"
msgstr "Nome do projeto"
msgid "Project slug"
msgstr "Slug do projeto"
-msgid "Project:"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
+msgid "Project:"
+msgstr "Projeto:"
+
msgid "ProjectActivityRSS|Subscribe"
msgstr "Inscreva-se"
@@ -7491,12 +8777,21 @@ msgstr "Usuários só podem fazer push para este repositório com commits que co
msgid "Projects"
msgstr "Projetos"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "Projetos compartilhados com %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "Projetos que pertencem a um grupo são prefixados com o namespace do grupo. Projetos existentes podem ser movidos para um grupo."
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr "Projetos com acesso de gravação"
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "Visitados frequentemente"
@@ -7545,6 +8840,9 @@ msgstr "Nenhum alerta definido"
msgid "PrometheusAlerts|Operator"
msgstr "Operador"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "Tolerância"
@@ -7647,6 +8945,9 @@ msgstr "Esse recurso está bloqueado."
msgid "Promotions|Upgrade plan"
msgstr "Aprimorar plano"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "Protegido"
@@ -7705,7 +9006,7 @@ msgid "Pseudonymizer data collection"
msgstr "Coleção de dados Pseudonymizer"
msgid "Public"
-msgstr ""
+msgstr "Público"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "Público - O grupo e seus projetos podem ser visualizados por todos sem autenticação."
@@ -7713,6 +9014,12 @@ msgstr "Público - O grupo e seus projetos podem ser visualizados por todos sem
msgid "Public - The project can be accessed without any authentication."
msgstr "Público - O projeto pode ser acessado sem nenhuma autenticação."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "Pipelines públicos"
@@ -7725,6 +9032,12 @@ msgstr "Push"
msgid "Push Rules"
msgstr "Regras de push"
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "Eventos de push"
@@ -7747,12 +9060,15 @@ msgid "Quarters"
msgstr "Trimestres"
msgid "Query"
-msgstr ""
+msgstr "Consulta"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "Ações rápidas podem ser usadas nas descrições das issues e nas caixas de comentário."
msgid "README"
+msgstr "README"
+
+msgid "Rake Tasks Help"
msgstr ""
msgid "Read more"
@@ -7770,6 +9086,12 @@ msgstr "Recursos em tempo real"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr "Recente"
+
+msgid "Recent Project Activity"
+msgstr "Atividade recente do projeto"
+
msgid "Recent searches"
msgstr "Pesquisas recentes"
@@ -7797,7 +9119,7 @@ msgid "Register / Sign In"
msgstr "Registrar/Login"
msgid "Register U2F device"
-msgstr ""
+msgstr "Registrar dispositivo U2F"
msgid "Register and see your runners for this group."
msgstr "Registre-se e veja seus runners para este grupo."
@@ -7805,8 +9127,11 @@ msgstr "Registre-se e veja seus runners para este grupo."
msgid "Register and see your runners for this project."
msgstr "Registre-se e veja seus runners para este projeto."
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
-msgstr "Registro"
+msgstr "Container Registry"
msgid "Related Commits"
msgstr "Commits Relacionados"
@@ -7830,10 +9155,10 @@ msgid "Related merge requests"
msgstr "Merge requests relacionados"
msgid "Releases"
-msgstr ""
+msgstr "Versões"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
-msgstr ""
+msgstr "As versões marcam pontos específicos no histórico de desenvolvimento de um projeto, comunicam informações sobre o tipo de mudança e fornecem versões preparadas, muitas vezes compiladas do software para serem reutilizadas em outro lugar. Atualmente, as versões só podem ser criadas por meio da API."
msgid "Remind later"
msgstr "Lembrar mais tarde"
@@ -7848,11 +9173,20 @@ msgid "Remove all approvals in a merge request when new commits are pushed to it
msgstr ""
msgid "Remove approver"
-msgstr ""
+msgstr "Remover aprovador"
+
+msgid "Remove approvers"
+msgstr "Remover aprovadores"
+
+msgid "Remove approvers?"
+msgstr "Remover aprovadores?"
msgid "Remove avatar"
msgstr "Remover imagem"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "Remover grupo"
@@ -7862,12 +9196,33 @@ msgstr "Remover prioridade"
msgid "Remove project"
msgstr "Remover projeto"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "O grupo removido não pode ser restaurado!"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "A remoção do grupo fará com que todos os projetos e recursos filhos sejam removidos."
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "Renomear"
@@ -7881,14 +9236,17 @@ msgid "Reopen epic"
msgstr "Reabrir epic"
msgid "Reopen milestone"
-msgstr ""
+msgstr "Reabrir marco"
msgid "Repair authentication"
msgstr "Corrigir autenticação"
-msgid "Reply to comment"
+msgid "Replace"
msgstr ""
+msgid "Reply to comment"
+msgstr "Responder ao comentário"
+
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr "Responda a este e-mail diretamente ou %{view_it_on_gitlab}."
@@ -7919,6 +9277,18 @@ msgstr "Tempo de execução"
msgid "Reports|Failure"
msgstr "Falha"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "Gravidade"
@@ -7937,6 +9307,12 @@ msgstr "Os resultados do resumo de teste estão sendo analisados"
msgid "Reports|Vulnerability"
msgstr "Vulnerabilidade"
+msgid "Reports|issue"
+msgstr "issue"
+
+msgid "Reports|merge request"
+msgstr "merge request"
+
msgid "Reports|no changed test results"
msgstr "sem resultados de teste alterados"
@@ -7949,6 +9325,9 @@ msgstr "Configurações do Repositório"
msgid "Repository URL"
msgstr "URL do repositório"
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7974,7 +9353,7 @@ msgid "Request Access"
msgstr "Solicitar acesso"
msgid "Requested %{time_ago}"
-msgstr ""
+msgstr "Solicitado %{time_ago}"
msgid "Requests Profiles"
msgstr "Solicita Perfis"
@@ -7985,26 +9364,39 @@ msgstr "Exigir que todos os usuários deste grupo configurem a autenticação de
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Exija que todos os usuários aceitem Termos de Serviço e Política de Privacidade quando acessarem o GitLab."
-msgid "Resend invite"
+msgid "Require approval from code owners"
msgstr ""
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Resend invite"
+msgstr "Reenviar convite"
+
msgid "Reset authorization key"
-msgstr ""
+msgstr "Redefinir chave de autorização"
msgid "Reset authorization key?"
-msgstr ""
+msgstr "Redefinir chave de autorização?"
msgid "Reset health check access token"
msgstr "Recriar o token de status de saúde"
msgid "Reset key"
-msgstr ""
+msgstr "Redefinir chave"
msgid "Reset runners registration token"
msgstr "Recriar o token de registro de runners"
msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
-msgstr ""
+msgstr "Redefinir a chave de autorização invalidará a chave anterior. As configurações de alerta existentes terão de ser atualizadas com a nova chave."
msgid "Resolve all discussions in new issue"
msgstr "Resolver todas discussões em novo issue"
@@ -8016,9 +9408,18 @@ msgid "Resolve discussion"
msgstr "Resolver discussão"
msgid "Resolved"
+msgstr "Resolvido"
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
msgstr ""
msgid "Response"
+msgstr "Resposta"
+
+msgid "Response didn't include `service_desk_address`"
msgstr ""
msgid "Response metrics (AWS ELB)"
@@ -8031,7 +9432,7 @@ msgid "Response metrics (HA Proxy)"
msgstr "Métricas de resposta (HA Proxy)"
msgid "Response metrics (NGINX Ingress VTS)"
-msgstr ""
+msgstr "Métricas de resposta (NGINX Ingress VTS)"
msgid "Response metrics (NGINX Ingress)"
msgstr "Métricas de resposta (NGINX Ingress)"
@@ -8040,17 +9441,23 @@ msgid "Response metrics (NGINX)"
msgstr "Métricas de resposta (NGINX)"
msgid "Restart Terminal"
-msgstr ""
+msgstr "Reiniciar terminal"
msgid "Resume"
msgstr "Continuar"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Tentar novamente"
msgid "Retry this job"
msgstr "Refazer tarefa"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "Tentar novamente a verificação"
@@ -8083,12 +9490,21 @@ msgstr "Revisando (merge request !%{mergeRequestId})"
msgid "Revoke"
msgstr "Revogar"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "Roadmap"
msgid "Run CI/CD pipelines for external repositories"
msgstr "Executar pipelines CI/CD para repositórios externos"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr "O runner executa tarefas de projetos atribuídos"
msgid "Runner token"
msgstr "Token de runner"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "O runner não receberá nenhuma tarefa nova"
@@ -8143,27 +9565,27 @@ msgstr "Você usou todos os seus minutos compartilhados para executores."
msgid "Running"
msgstr "Executando"
+msgid "Running…"
+msgstr "Executando…"
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "SAML SSO para %{group_name}"
-msgid "SAML Single Sign On"
-msgstr "SAML Single Sign On"
-
-msgid "SAML Single Sign On Settings"
-msgstr "Configurações de SAML Single Sign On"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "Impressão digital SHA1 do certificado de assinatura de token SAML. Obtenha isso do seu provedor de identidade, onde ele também pode ser chamado de \"Thumbprint\"."
-
msgid "SSH Keys"
msgstr "Chaves SSH"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "Chaves SSH do host"
@@ -8173,11 +9595,14 @@ msgstr "Chave SSH pública"
msgid "SSL Verification"
msgstr "Verificação SSL"
+msgid "Saturday"
+msgstr "Sábado"
+
msgid "Save"
msgstr "Salvar"
msgid "Save Changes"
-msgstr ""
+msgstr "Salvar alterações"
msgid "Save application"
msgstr "Salvar aplicativo"
@@ -8189,7 +9614,7 @@ msgid "Save changes before testing"
msgstr "Salvar alterações antes de testar"
msgid "Save comment"
-msgstr ""
+msgstr "Salvar comentário"
msgid "Save pipeline schedule"
msgstr "Salvar agendamento da pipeline"
@@ -8197,6 +9622,9 @@ msgstr "Salvar agendamento da pipeline"
msgid "Save variables"
msgstr "Salvar variáveis"
+msgid "Saving project."
+msgstr "Salvando projeto."
+
msgid "Schedule a new pipeline"
msgstr "Agendar nova pipeline"
@@ -8206,15 +9634,24 @@ msgstr "Agendado"
msgid "Schedules"
msgstr "Agendamentos"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Agendando pipelines"
msgid "Scope"
msgstr "Escopo"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "Painéis de escopo"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "Role para baixo até <strong>Google Code Project Hosting</strong> e ative a opção à direita."
@@ -8243,7 +9680,7 @@ msgid "Search for projects, issues, etc."
msgstr "Pesquise por projetos, issues, etc."
msgid "Search groups"
-msgstr ""
+msgstr "Procurar grupos"
msgid "Search merge requests"
msgstr "Pesquisar merge requests"
@@ -8266,6 +9703,9 @@ msgstr "Pesquisar projetos"
msgid "Search users"
msgstr "Procurar usuários"
+msgid "Search users or groups"
+msgstr "Procurar usuários ou grupos"
+
msgid "Search your projects"
msgstr "Pesquisar nos seus projetos"
@@ -8293,6 +9733,15 @@ msgstr "neste grupo"
msgid "SearchAutocomplete|in this project"
msgstr "neste projeto"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "Secreto"
@@ -8314,9 +9763,6 @@ msgstr "Erro ao recuperar lista de vulnerabilidades. Por favor, verifique sua co
msgid "Security Dashboard|Issue Created"
msgstr "Issue criado"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "Criar issue"
@@ -8332,12 +9778,12 @@ msgstr "Mais informações"
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
-msgstr "Documentação do painel de segurança"
-
msgid "Security Reports|There was an error creating the issue."
msgstr "Ocorreu um erro ao criar a issue."
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "Ocorreu um erro ao ignorar a vulnerabilidade."
@@ -8369,10 +9815,10 @@ msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
msgstr "Pipeline %{pipelineLink} acionado"
msgid "See metrics"
-msgstr ""
+msgstr "Ver métricas"
msgid "See the affected projects in the GitLab admin panel"
-msgstr ""
+msgstr "Veja os projetos afetados no painel de administração do GitLab"
msgid "Select"
msgstr "Selecionar"
@@ -8386,6 +9832,15 @@ msgstr "Selecione um grupo para convidar"
msgid "Select a namespace to fork the project"
msgstr "Selecione um namespace para realizar o fork do projeto"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr "Selecione um repositório"
+
msgid "Select a template repository"
msgstr "Selecione um repositório de modelos"
@@ -8395,14 +9850,11 @@ msgstr "Selecionar fuso horário"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Selecione um cluster existente do Kubernetes ou crie um novo"
-msgid "Select assignee"
-msgstr "Selecione o responsável"
-
msgid "Select branch/tag"
msgstr "Selecionar o branch/tag"
msgid "Select members to invite"
-msgstr ""
+msgstr "Selecione membros para convidar"
msgid "Select project"
msgstr "Selecionar projeto"
@@ -8428,17 +9880,20 @@ msgstr "Selecione o branch que você deseja definir como o padrão para este pro
msgid "Select the custom project template source group."
msgstr "Selecione o grupo de origem dos modelos customizados de projeto."
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "A seleção de um usuário do GitLab adicionará um link para o usuário do GitLab nas descrições de issues e comentários (por exemplo, \"Por <a href=\"#\">@johnsmith</a>\"). Isto também associará e/ou atribuirá esses issues e comentários ao usuário selecionado."
-msgid "Selective synchronization"
-msgstr "Sincronização seletiva"
+msgid "Send an email notification to Developers."
+msgstr "Enviar um e-mail de notificação para os desenvolvedores."
msgid "Send email"
msgstr "Enviar e-mail"
msgid "Send report"
-msgstr ""
+msgstr "Enviar relatório"
msgid "Send usage data"
msgstr "Enviar dados de uso"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr "Set"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "Setembro"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "Versão do servidor"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr "Modelos de serviço"
msgid "Service URL"
msgstr "URL de serviço"
+msgid "Session duration (minutes)"
+msgstr "Duração da sessão (minutos)"
+
msgid "Session expiration, projects limit and attachment size."
msgstr "Expiração de sessão, limite de projetos e tamanho de anexo."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Defina uma senha para sua conta para aceitar ou entregar código via %{protocol}."
@@ -8524,6 +10006,9 @@ msgstr "Definir repositório de modelos para toda a instância"
msgid "Set max session time for web terminal."
msgstr "Defina o tempo máximo da sessão para o terminal da web."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "Definir notificação por e-mail para relatórios de abuso."
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "Definir requisitos para um usuário entrar. Ative a autenticação obrigatória de dois fatores."
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "Configurar CI/CD"
@@ -8548,6 +10045,9 @@ msgstr "Configurar asserções/atributos/alegações (email, first_name, last_na
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "Configure seu projeto para fazer push e/ou pull de um repositório para outro automaticamente. Branches, tags e commits serão sincronizados automaticamente."
@@ -8605,9 +10105,15 @@ msgstr "Redefinir minutos usados de pipeline"
msgid "Sherlock Transactions"
msgstr "Transações de Sherlock"
+msgid "Show all activity"
+msgstr "Mostrar todas as atividades"
+
msgid "Show command"
msgstr "Exibir comando"
+msgid "Show comments only"
+msgstr "Mostrar apenas comentários"
+
msgid "Show complete raw log"
msgstr "Visualizar raw log completo"
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr "Mostrar a versão mais recente"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Mostrar páginas acima"
@@ -8653,19 +10162,19 @@ msgid "Sign in / Register"
msgstr "Entrar / Criar conta"
msgid "Sign in to \"%{group_name}\""
-msgstr ""
+msgstr "Entrar em \"%{group_name}\""
msgid "Sign in using smart card"
-msgstr ""
+msgstr "Entrar usando o cartão inteligente"
msgid "Sign in via 2FA code"
-msgstr ""
+msgstr "Entrar via código da A2F"
msgid "Sign in with Single Sign-On"
msgstr "Entre com logon único"
msgid "Sign in with smart card"
-msgstr ""
+msgstr "Entrar com cartão inteligente"
msgid "Sign out"
msgstr "Sair"
@@ -8676,15 +10185,24 @@ msgstr "Restrições de login"
msgid "Sign-up restrictions"
msgstr "Restrições de cadastro"
-msgid "Similar issues"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
msgstr ""
+msgid "Similar issues"
+msgstr "Issues semelhantes"
+
msgid "Size"
msgstr "Tamanho"
msgid "Size and domain settings for static websites"
msgstr "Configurações de tamanho e domínio para sites estáticos"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Aplicativo Slack"
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr "Snippets"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr "Algo deu errado ao buscar os comentários. Por favor, tente novamente."
msgid "Something went wrong while fetching group member contributions"
msgstr "Algo deu errado ao obter as contribuições de membro do grupo"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Algo deu errado ao buscar os ambientes para esse merge request. Por favor, tente novamente."
@@ -8778,6 +10320,9 @@ msgstr "Algo deu errado, não é possível obter os projetos de operações"
msgid "Something went wrong, unable to remove project"
msgstr "Algo deu errado, não é possível remover o projeto"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Algo deu errado. Por favor, tente novamente."
@@ -8928,6 +10473,9 @@ msgstr "Logs de spam"
msgid "Spam and Anti-bot Protection"
msgstr "Proteção contra spam e anti-bot"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Runners Específicos"
@@ -8976,15 +10524,27 @@ msgstr "Atividade dos projetos favoritos"
msgid "Starred projects"
msgstr "Projetos favoritos"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Iniciar um %{new_merge_request} a partir dessas alterações"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Iniciar uma revisão"
@@ -8998,13 +10558,13 @@ msgid "Start date"
msgstr "Data de início"
msgid "Start discussion"
-msgstr ""
+msgstr "Iniciar discussão"
msgid "Start discussion & close %{noteable_name}"
-msgstr ""
+msgstr "Iniciar discussão e fechar %{noteable_name}"
msgid "Start discussion & reopen %{noteable_name}"
-msgstr ""
+msgstr "Iniciar discussão e reabrir %{noteable_name}"
msgid "Start the Runner!"
msgstr "Inicie o Runner!"
@@ -9016,13 +10576,16 @@ msgid "Started"
msgstr "Iniciado"
msgid "Started %{startsIn}"
+msgstr "Iniciado %{startsIn}"
+
+msgid "Started asynchronous removal of all repository check states."
msgstr ""
msgid "Starting..."
-msgstr ""
+msgstr "Iniciando..."
msgid "Starts %{startsIn}"
-msgstr ""
+msgstr "Inicia %{startsIn}"
msgid "Starts at (UTC)"
msgstr "Começa em (UTC)"
@@ -9034,10 +10597,10 @@ msgid "Status"
msgstr "Status"
msgid "Status:"
-msgstr ""
+msgstr "Status:"
msgid "Stop Terminal"
-msgstr ""
+msgstr "Parar terminal"
msgid "Stop environment"
msgstr "Parar o ambiente"
@@ -9055,7 +10618,7 @@ msgid "Stopping this environment is currently not possible as a deployment is in
msgstr "Não é possível parar esse ambiente no momento pois um deploy está em andamento"
msgid "Stopping..."
-msgstr ""
+msgstr "Parando..."
msgid "Storage"
msgstr "Armazenamento"
@@ -9073,7 +10636,7 @@ msgid "Submit as spam"
msgstr "Enviar como spam"
msgid "Submit feedback"
-msgstr ""
+msgstr "Enviar feedback"
msgid "Submit review"
msgstr "Enviar análise"
@@ -9091,7 +10654,7 @@ msgid "Subscribe at project level"
msgstr "Inscrever-se no nível do projeto"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "Inscrever-se no feed RSS"
msgid "Subscribe to calendar"
msgstr ""
@@ -9100,16 +10663,16 @@ msgid "Subscribed"
msgstr "Inscrito"
msgid "SubscriptionTable|Billing"
-msgstr ""
+msgstr "Cobrança"
msgid "SubscriptionTable|Free"
-msgstr ""
+msgstr "Grátis"
msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
msgstr ""
msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
-msgstr ""
+msgstr "GitLab.com %{planName} %{suffix}"
msgid "SubscriptionTable|Last invoice"
msgstr ""
@@ -9154,7 +10717,7 @@ msgid "SubscriptionTable|This is the number of seats you will be required to pur
msgstr ""
msgid "SubscriptionTable|Trial"
-msgstr ""
+msgstr "Avaliação"
msgid "SubscriptionTable|Trial end date"
msgstr ""
@@ -9171,31 +10734,64 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
msgid "Sunday"
-msgstr ""
+msgstr "Domingo"
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr "URL da página de suporte"
+
msgid "Switch branch/tag"
msgstr "Trocar branch/tag"
msgid "Sync information"
msgstr "Informação de sincronização"
+msgid "System"
+msgstr "Sistema"
+
msgid "System Hooks"
msgstr "Hooks do sistema"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Informações do Sistema"
msgid "System default (%{default})"
-msgstr ""
+msgstr "Padrão do sistema (%{default})"
msgid "System header and footer"
+msgstr "Cabeçalho e rodapé do sistema"
+
+msgid "System hook was successfully updated."
msgstr ""
msgid "System metrics (Custom)"
@@ -9205,7 +10801,7 @@ msgid "System metrics (Kubernetes)"
msgstr "Métricas do sistema (Kubernetes)"
msgid "Tag"
-msgstr ""
+msgstr "Tag"
msgid "Tag list:"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr "Teste de SSO SAML"
msgid "Test coverage parsing"
msgstr "Análise de cobertura de teste"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "Obrigado! Não me mostre isso novamente"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "O Advanced Global Search no GitLab é um poderoso serviço de pesquisa que economiza seu tempo. Em vez de criar código duplicado e perder tempo, você pode agora procurar código dentro de outras equipes que podem ajudar seu próprio projeto."
@@ -9345,6 +10952,12 @@ msgstr "O Rastreador de Issue é o lugar para adicionar coisas que precisam ser
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "O Certificado X509 a ser usado quando o TLS mútuo é necessário para se comunicar com o serviço de autorização externa. Se deixado em branco, o certificado do servidor ainda será validado ao acessar por HTTPS."
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "O realçador de caracteres ajuda a manter a linha de assunto com %{titleLength} caracteres e quebra a linha do corpo em %{bodyLength} para que sejam legíveis no git."
@@ -9357,15 +10970,63 @@ msgstr "A coleção de eventos adicionados aos dados coletados para essa etapa."
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "A conexão expirará após %{timeout}. Para repositórios que demoram mais tempo, use a combinação clone/push."
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "O deploy desta tarefa para o ambiente %{environmentLink} foi mal sucedida."
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "O relacionamento como fork foi removido."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "A importação expirará após %{timeout}. Para repositórios que demoram mais tempo, use a combinação clone/push."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "A etapa de planejamento mostra o tempo que se leva desde a criação de uma issue até sua atribuição à um marco, ou sua adição a uma lista no seu painel. Comece a criar issues para ver dados para esta etapa."
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "O tamanho máximo do arquivo é de 200KB."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "A senha necessária para descriptografar a chave privada. Isso é opcional e o seu valor é criptografado."
@@ -9402,9 +11075,18 @@ msgstr "O projeto pode ser acessado por qualquer usuário autenticado."
msgid "The project can be accessed without any authentication."
msgstr "O projeto pode ser acessado sem a necessidade de autenticação."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "A coleção de dados pseudonymizer está desativada. Quando ativada, o GitLab executará uma tarefa em segundo plano que produzirá CSVs com pseudônimos do banco de dados do GitLab que serão carregados no diretório de armazenamento de objetos configurado."
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Não existe repositório para este projeto."
@@ -9423,6 +11105,21 @@ msgstr "A etapa de revisão mostra o tempo de criação de uma solicitação de
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "O roadmap mostra o progresso de seus epics ao longo de uma linha do tempo"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "A etapa de homologação mostra o tempo entre o aceite da solicitação de incorporação e a implantação do código no ambiente de produção. Os dados serão automaticamente adicionados depois que você implantar em produção pela primeira vez."
@@ -9438,9 +11135,18 @@ msgstr "O tempo necessário por cada entrada de dados reunida por essa etapa."
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "A ação de atualização irá expirar depois de %{number_of_minutes} minutos. Para grandes repositórios, use uma combinação de clone/push."
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "Os dados de uso estão desabilitados, e não podem ser configurados por meio deste formulário."
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "O mapa do usuário é um documento JSON que mapeia os usuários do Google Code que participaram de seus projetos para a maneira como seus endereços de e-mail e nomes de usuários são importados para o GitLab. Você pode mudar isso alterando o valor no lado direito de <code>:</code>. Certifique-se de preservar as aspas duplas adjacentes, outros sinais de pontuação e o endereço de e-mail ou nome de usuário no lado esquerdo."
@@ -9495,9 +11201,21 @@ msgstr "Houve um erro ao adicionar uma tarefa."
msgid "There was an error deleting the todo."
msgstr "Houve um erro ao excluir a tarefa."
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "Erro ao carregar calendário de atividades."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Erro ao salvar suas configurações de notificação."
@@ -9513,6 +11231,9 @@ msgstr "Erro ao se inscrever nessa etiqueta."
msgid "There was an error when unsubscribing from this label."
msgstr "Erro ao se anular a inscrição dessa etiqueta."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Esta instância do GitLab ainda não fornece nenhum Runner compartilhado. Os administradores da instância podem registrar os Runners compartilhados na área de administração."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "Esse aplicativo foi criado por %{link_to_owner}."
@@ -9543,6 +11267,21 @@ msgstr "O escopo deste painel está reduzido"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Esse branch mudou desde quando você começou sua edição. Você quer criar um novo branch?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr "Esse container registry foi agendado para exclusão."
@@ -9564,6 +11303,9 @@ msgstr "Esse diretório"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "Esse grupo"
@@ -9579,6 +11321,9 @@ msgstr "Esta tarefa foi atrasada para executar em %{remainingTime}"
msgid "This is the author's first Merge Request to this project."
msgstr "Esse é o autor do primeiro merge request desse projeto."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Essa issue é confidencial"
@@ -9627,6 +11372,12 @@ msgstr "Esta tarefa está criando um deploy para %{environmentLink}."
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Esta tarefa está em estado pendente e está esperando para ser escolhido por um runner"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,19 +11399,25 @@ msgstr "Isto significa que você não pode entregar código até que crie um rep
msgid "This merge request is locked."
msgstr "Esse merge request está bloqueado."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "Esta opção está desativada porque você não tem permissões de escrita para o branch atual"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "Esta opção ficará desativada enquanto você ainda tiver alterações fora da lista para commit"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "Esta página não está disponível porque você não tem permissão para ler informações de vários projetos."
msgid "This page will be removed in a future release."
msgstr "Esta página será removida em uma versão futura."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr "Este projeto não possui faturamento ativado. Para criar um cluster, <a
msgid "This repository"
msgstr "Esse repositório"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "Este Runner somente será executado em Pipelines acionados em branches protegidos"
@@ -9693,12 +11453,12 @@ msgstr "Essa configuração pode ser substituída em cada projeto."
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "Essa configuração atualizará o hostname usado para gerar e-mails de commit privados. %{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "Esse diff de origem não pôde ser exibido porque é muito grande."
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "Esse usuário não tem identidades"
@@ -9711,6 +11471,9 @@ msgstr "Esse usuário será o autor de todos os eventos no feed de atividades qu
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "Esses e-mails se tornam automaticamente issues (com os comentários se tornando a conversa por e-mail) listados aqui."
@@ -9777,9 +11540,6 @@ msgstr "%s meses atrás"
msgid "Timeago|%s months remaining"
msgstr "%s meses restantes"
-msgid "Timeago|%s seconds ago"
-msgstr "%s segundos atrás"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s segundos restantes"
@@ -10009,6 +11769,9 @@ msgstr "Hoje"
msgid "Todo"
msgstr "Pendente"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "Afazeres"
@@ -10048,6 +11811,12 @@ msgstr "Amanhã"
msgid "Too many changes to show."
msgstr "Alterações demais para mostrar."
+msgid "Topics"
+msgstr "Tópicos"
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Contribuições totais"
@@ -10072,6 +11841,9 @@ msgstr "Acompanhe grupos de issues que compartilhem um tema, em projetos e marco
msgid "Track time with quick actions"
msgstr "Acompanhe o tempo com ações rápidas"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "Visão em árvore"
@@ -10084,6 +11856,9 @@ msgstr "Pipelines de disparo para atualizações de espelho"
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "Ative os pipelines quando os branches ou tags forem atualizados a partir do repositório upstream. Dependendo da atividade do repositório upstream, isso pode aumentar bastante a carga nos seus runners de CI. Ative isso somente se você sabe que eles podem manipular a carga."
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "Acionar esta ação manual"
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "Os gatilhos podem forçar uma build numa branch ou tag específica através da API. Esses tokens representarão seu usuário associado, incluindo seu acesso a projetos e suas permissões de projeto."
@@ -10103,7 +11890,7 @@ msgid "Try again"
msgstr "Tente novamente"
msgid "Try again?"
-msgstr ""
+msgstr "Tentar novamente?"
msgid "Try all GitLab has to offer for 30 days."
msgstr "Use tudo que o GitLab tem para oferecer por 30 dias."
@@ -10117,6 +11904,9 @@ msgstr "Ativar o Balcão de Atendimento"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "Autenticação de dois fatores"
@@ -10124,17 +11914,29 @@ msgid "Type"
msgstr "Tipo"
msgid "URL"
+msgstr "URL"
+
+msgid "Unable to connect to server: %{error}"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Não é possível carregar o diff. %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Não é possível inscrever você no grupo com SAML devido a \"%{reason}\""
msgid "Unable to update this epic at this time."
msgstr "Não é possível atualizar este épico neste momento."
+msgid "Unarchive project"
+msgstr "Desarquivar projeto"
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr "Desconhecido"
+msgid "Unlimited"
+msgstr "Ilimitado"
+
msgid "Unlock"
msgstr "Desbloquear"
@@ -10207,21 +12012,39 @@ msgstr "Em breve"
msgid "Update"
msgstr "Atualizar"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "Atualizar agora"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Atualize o nome do seu grupo, descrição, avatar e visibilidade."
+msgid "Update your project name, topics, description and avatar."
+msgstr "Atualize o nome do seu projeto, tópicos, a descrição e o avatar."
+
+msgid "Updated"
+msgstr "Atualizado"
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "Atualizando"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Aprimore seu plano para ativar a Pesquisa Global Avançada."
@@ -10237,6 +12060,9 @@ msgstr "Faça upgrade de plano para ativar o peso nas Issues."
msgid "Upgrade your plan to improve Issue boards."
msgstr "Faça upgrade de plano para melhorar os painéis."
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "Envie o <code>GoogleCodeProjectHosting.json</code> aqui:"
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "clique para fazer upload"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "Votos positivos"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "Dados de uso não está habilitado"
msgid "Usage statistics"
msgstr "Estatísticas de uso"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "Use <code>%{native_redirect_uri}</code> para testes locais"
@@ -10303,18 +12141,54 @@ msgstr "Utilizado pelos membros para entrar em seu grupo no GitLab"
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "GrupoCoortes de usuário são mostradas somente quando os %{usage_ping_link_start}dados de uso%{usage_ping_link_end} são habilitados."
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "Configurações do Usuário"
msgid "User and IP Rate Limits"
msgstr "Limites de Taxa de Usuário e IP"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "Mapa do usuário"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "Atividade"
@@ -10343,7 +12217,7 @@ msgid "UserProfile|Most Recent Activity"
msgstr "Atividade mais recente"
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "Nenhum snippet encontrado."
msgid "UserProfile|Overview"
msgstr "Visão geral"
@@ -10358,19 +12232,19 @@ msgid "UserProfile|Snippets"
msgstr "Snippets"
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
-msgstr ""
+msgstr "Snippets no GitLab podem ser privados, internos ou públicos."
msgid "UserProfile|Subscribe"
msgstr "Inscrever-se"
msgid "UserProfile|This user doesn't have any personal projects"
-msgstr ""
+msgstr "Este usuário não tem nenhum projeto pessoal"
msgid "UserProfile|This user has a private profile"
msgstr "Este usuário tem um perfil privado"
msgid "UserProfile|This user hasn't contributed to any projects"
-msgstr ""
+msgstr "Este usuário não contribuiu para nenhum projeto"
msgid "UserProfile|View all"
msgstr "Ver tudo"
@@ -10385,26 +12259,47 @@ msgid "UserProfile|You haven't created any personal projects."
msgstr ""
msgid "UserProfile|You haven't created any snippets."
-msgstr ""
+msgstr "Você não criou nenhum snippet."
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
-msgstr ""
+msgstr "Seus projetos podem estar disponíveis publicamente, internamente ou privadamente, à sua escolha."
msgid "Users"
msgstr "Usuários"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "Várias configurações de registry container."
@@ -10412,7 +12307,7 @@ msgid "Various email settings."
msgstr "Várias configurações de email."
msgid "Various localization settings."
-msgstr ""
+msgstr "Várias configurações de localização."
msgid "Various settings that affect GitLab performance."
msgstr "Várias configurações que afetam o desempenho do GitLab."
@@ -10429,9 +12324,6 @@ msgstr "Verificado"
msgid "Version"
msgstr "Versão"
-msgid "View %{alerts}"
-msgstr "Visualizar %{alerts}"
-
msgid "View app"
msgstr "Exibir aplicativo"
@@ -10441,8 +12333,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "Ver documentação"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "Ve lista de épicos"
@@ -10453,12 +12345,15 @@ msgstr "Ver arquivo @ "
msgid "View group labels"
msgstr "Visualizar etiquetas de grupo"
-msgid "View issue"
-msgstr "Ver issue"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "Ver no GitLab"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "Visualizar tarefas"
@@ -10492,6 +12387,9 @@ msgstr "Nível de visibilidade"
msgid "Visibility level:"
msgstr "Nível de visibilidade:"
+msgid "Visibility, project features, permissions"
+msgstr "Visibilidade, recursos de projeto, permissões"
+
msgid "Visibility:"
msgstr "Visibilidade:"
@@ -10528,12 +12426,18 @@ msgstr "Arquivo"
msgid "Vulnerability|Identifiers"
msgstr "Identificadores"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "Instâncias"
msgid "Vulnerability|Links"
msgstr "Links"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "Projeto"
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr "Terminal Web"
+msgid "Webhooks"
+msgstr "Webhooks"
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Webhooks permitem que você acione um URL se, por exemplo, um novo código for feito push ou se uma nova issue for criada. Você pode configurar os webhooks para interagir com eventos específicos como pushes, issues ou merge requests. Webhooks de grupos serão aplicados para todos os projetos em um grupo, permitindo que você padronize o funcionamento em todo o grupo."
@@ -10600,9 +12510,17 @@ msgstr "Quando um runner está bloqueado, não pode ser atribuído a outros proj
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "Quando ativado, os usuários não podem usar o GitLab até que os termos tenham sido aceitos."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "Ao deixar o URL em branco, a classificação das etiquetas ainda podem ser especificadas sem desativar os recursos do projeto ou executar verificações de autorização externas."
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr "Quem poderá ver esse grupo?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Clonar sua wiki"
@@ -10726,6 +12647,9 @@ msgstr "Criar página"
msgid "Wiki|Create page"
msgstr "Criar página"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Editar página"
@@ -10744,6 +12668,9 @@ msgstr "Versão da página"
msgid "Wiki|Pages"
msgstr "Páginas"
+msgid "Wiki|Title"
+msgstr "Título"
+
msgid "Wiki|Wiki Pages"
msgstr "Páginas Wiki"
@@ -10756,12 +12683,24 @@ msgstr "Com a análise de contribuições, você pode ter uma visão geral da at
msgid "Withdraw Access Request"
msgstr "Remover Requisição de Acesso"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "Sim"
@@ -10775,7 +12714,7 @@ msgid "Yesterday"
msgstr "Ontem"
msgid "You"
-msgstr ""
+msgstr "Você"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr "Você é um administrador, o que significa que conceder acesso a <strong>%{client_name}</strong> permitirá que eles também interajam com o GitLab como administrador. Prossiga com cuidado."
@@ -10792,6 +12731,9 @@ msgstr "Você está prestes a remover a relação de fork do projeto original %{
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "Você irá transferir %{project_full_name} para outro proprietário. Tem certeza ABSOLUTA?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "Você está em uma instância somente-leitura do GitLab."
@@ -10807,6 +12749,15 @@ msgstr "Você também pode criar um projeto a partir da linha de comando."
msgid "You can also star a label to make it a priority label."
msgstr "Você também pode marcar uma etiqueta para torná-la uma etiqueta de prioridade."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Você pode facilmente contribuir para eles pedindo para se juntar nesses grupos."
@@ -10816,6 +12767,12 @@ msgstr "Você pode instalar facilmente um Runner em um cluster Kubernetes. %{lin
msgid "You can move around the graph by using the arrow keys."
msgstr "Você pode mover o gráfico usando as setas do teclado."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Você somente pode adicionar arquivos quando estiver em um branch"
@@ -10825,6 +12782,9 @@ msgstr "Você só pode editar arquivos quando estiver em um branch"
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr "Você pode resolver o conflito de merge usando o modo Interativo, escolhendo os botões %{use_ours} ou %{use_theirs} ou editando os arquivos diretamente. Confirme essas alterações em %{branch_name}"
@@ -10834,12 +12794,30 @@ msgstr "Você pode configurar tarefas para só usar os runners com tags específ
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "Você pode testar o seu .gitlab-ci.yml no %{linkStart}CI Lint%{linkEnd}."
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "Você não pode escrever numa instância secundária de somente leitura do GitLab Geo. Por favor use %{link_to_primary_node} em vez disso."
msgid "You cannot write to this read-only GitLab instance."
msgstr "Você não pode escrever nesta instância somente-leitura do GitLab."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "Você ainda não tem nenhuma inscrição"
@@ -10858,12 +12836,27 @@ msgstr "Você não tem nenhum aplicativo autorizado"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "Você não tem permissão"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Você atingiu o limite de seu projeto"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr "Você deve ter o acesso de mantenedor para apagar um bloqueio"
msgid "You need a different license to enable FileLocks feature"
msgstr "Você precisa de uma licença diferente para ativar o recurso FileLocks"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr "Você precisa de permissão."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "Você não será notificado por email"
@@ -10921,6 +12926,15 @@ msgstr "Você não poderá fazer push ou pull do código via SSH enquanto não a
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Você precisará usar nomes de branch diferentes para obter uma comparação válida."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "Você está recebendo este e-mail porque %{reason}."
@@ -10954,6 +12968,15 @@ msgstr "Seus lembretes"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "Seus aplicativos (%{size})"
@@ -10972,9 +12995,15 @@ msgstr "Commit com suas alterações realizado. Commit %{commitId} %{commitStats
msgid "Your changes have been saved"
msgstr "Suas alterações foram salvas"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Seu comentário não estará visível ao público."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr "Seu nome"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "Seus projetos"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "um usuário excluído"
+msgid "added %{created_at_timeago}"
+msgstr "adicionado %{created_at_timeago}"
+
msgid "ago"
msgstr "atrás"
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr "entre outras coisas"
-msgid "assign yourself"
-msgstr "atribuir a si mesmo"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "nome da branch"
@@ -11038,16 +13085,38 @@ msgstr "%{namespace} é afetado por %{vulnerability}."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "mais %{remainingPackagesCount}"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} detectado de %{fixedCount} vulnerabilidade corrigida"
msgstr[1] "%{reportType} %{status} detectado de %{fixedCount} vulnerabilidades corrigidas"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} detectado de %{newCount} nova vulnerabilidade"
msgstr[1] "%{reportType} %{status} detectado de %{newCount} novas vulnerabilidades"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} detectado de %{newCount} nova, e %{fixedCount} vulnerabilidades concertadas"
@@ -11079,6 +13148,9 @@ msgstr "%{reportType} está carregando"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType}: Carregamento resultou em um erro"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(erros ao carregar resultados)"
@@ -11088,6 +13160,15 @@ msgstr "(está carregando)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(está carregando, erros ao carregar resultados)"
+msgid "ciReport|All projects"
+msgstr "Todos os projetos"
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "Classe"
@@ -11097,12 +13178,24 @@ msgstr "Qualidade do código"
msgid "ciReport|Confidence"
msgstr "Confiança"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "Fazer scan no container"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "A varredura de contêiner detectou vulnerabilidades conhecidas em suas imagens docker."
+msgid "ciReport|Create issue"
+msgstr "Criar issue"
+
+msgid "ciReport|Create merge request"
+msgstr "Criar merge request"
+
+msgid "ciReport|Created %{eventType}"
+msgstr "Criado %{eventType}"
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11145,9 +13238,18 @@ msgstr "Corrigido:"
msgid "ciReport|Identifiers"
msgstr "Identificadores"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "Instâncias"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "Saiba mais sobre como interagir com relatórios de segurança (Alpha)."
@@ -11191,9 +13293,6 @@ msgstr "Sem mudanças nas métricas de desempenho"
msgid "ciReport|Performance metrics"
msgstr "Métricas de desempenho"
-msgid "ciReport|Revert dismissal"
-msgstr "Reverter ignorar"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11215,6 +13314,9 @@ msgstr "O Teste de Segurança de Aplicativos Estáticos (SAST) detecta vulnerabi
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "Ocorreu um erro ao criar a issue. Por favor, tente novamente."
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "Ocorreu um erro ao descartar a vulnerabilidade. Por favor, tente novamente."
@@ -11233,9 +13335,18 @@ msgstr "Ocorreu um erro ao carregar o relatório de verificação de dependênci
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "Houve um erro ao reverter o descarte. Por favor, tente novamente."
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "Atualizar %{name} de %{version} para %{fixed}."
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "Usado por %{packagesString}"
@@ -11247,12 +13358,33 @@ msgstr "Visualizar relatório completo"
msgid "ciReport|on pipeline"
msgstr "na pipeline"
-msgid "command line instructions"
-msgstr "instruções da linha de comando"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr "Você está prestes a desligar a confidencialidade. Isso significa que <strong>todos</strong> serão capazes de ver e deixar comentários nesse issue."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr "token de deploy"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "desabilitado"
@@ -11310,6 +13445,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} irá atualizar o tempo estimado com o último comando."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "para este projeto"
@@ -11319,15 +13469,9 @@ msgstr "de"
msgid "group"
msgstr ""
-msgid "help"
-msgstr "ajuda"
-
msgid "here"
msgstr "aqui"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://seu-servidor-do-bitbucket"
@@ -11390,6 +13534,9 @@ msgstr "deploy mais recente"
msgid "latest version"
msgstr "versão mais recente"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "gerenciamento de licenças"
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr "%{commitCount} será adicionado à %{targetBranch}."
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,11 +13596,14 @@ msgstr "Ocorreu um erro ao enviar sua aprovação."
msgid "mrWidget|Approve"
msgstr "Aprovar"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Aprovado por"
msgid "mrWidget|Cancel automatic merge"
-msgstr "Cancelar merge request automático"
+msgstr "Cancelar merge automático"
msgid "mrWidget|Check out branch"
msgstr "Checkout branch"
@@ -11518,16 +13671,19 @@ msgstr "Fazer merge localmente"
msgid "mrWidget|Merge request approved"
msgstr "Merge request aprovado"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Merge request aprovado; você pode adicionalmente"
msgid "mrWidget|Merged by"
msgstr "Merge realizado por"
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr "Nenhuma aprovação necessária"
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr "Nenhuma aprovação necessária; você ainda pode aprovar"
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr "Reverter"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "Reverter esse merge request com um novo merge request"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "Definir por"
@@ -11658,7 +13817,7 @@ msgid "new merge request"
msgstr "novo merge request"
msgid "none"
-msgstr ""
+msgstr "nenhum"
msgid "notification emails"
msgstr "e-mails de notificação"
@@ -11691,22 +13850,30 @@ msgstr "senha"
msgid "personal access token"
msgstr "token de acesso pessoal"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
-msgstr ""
+msgstr "privado"
msgid "private key does not match certificate."
msgstr "chave privada não corresponde ao certificado."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "projeto"
msgstr[1] "projetos"
msgid "quick actions"
-msgstr ""
+msgstr "ações rápidas"
msgid "register"
-msgstr ""
+msgstr "registrar"
msgid "remaining"
msgstr "restante"
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] "resposta"
msgstr[1] "respostas"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11738,7 +13932,7 @@ msgid "show less"
msgstr ""
msgid "sign in"
-msgstr ""
+msgstr "entrar"
msgid "source"
msgstr "origem"
@@ -11755,6 +13949,9 @@ msgstr "iniciado"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
@@ -11771,7 +13968,7 @@ msgid "triggered"
msgstr ""
msgid "updated"
-msgstr ""
+msgstr "atualizado"
msgid "username"
msgstr "nome do usuário"
@@ -11780,7 +13977,7 @@ msgid "uses Kubernetes clusters to deploy your code!"
msgstr "use clusters Kubernetes para deploy do seu código!"
msgid "verify ownership"
-msgstr ""
+msgstr "verificar propriedade"
msgid "view it on GitLab"
msgstr "ver no GitLab"
diff --git a/locale/pt_PT/gitlab.po b/locale/pt_PT/gitlab.po
index 1584a4159cc..3a2d4ebb37f 100644
--- a/locale/pt_PT/gitlab.po
+++ b/locale/pt_PT/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:08\n"
+"PO-Revision-Date: 2019-04-12 12:30\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index 17805691aff..c508597c9cc 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ro\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:07\n"
+"PO-Revision-Date: 2019-04-12 12:30\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -42,8 +51,11 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -141,12 +153,27 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -165,7 +192,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -174,6 +201,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -183,6 +216,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -195,6 +231,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -240,6 +285,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -249,6 +297,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -273,6 +324,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -285,6 +342,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -327,6 +387,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -336,6 +399,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -351,6 +423,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -384,7 +459,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -396,18 +483,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -438,12 +546,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -453,6 +567,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -483,34 +603,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -534,18 +681,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -582,6 +738,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -663,12 +822,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -681,6 +849,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -690,15 +861,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -711,6 +900,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -729,6 +924,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -750,9 +948,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -762,6 +957,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -774,18 +978,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -798,6 +1011,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -810,6 +1026,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -825,6 +1044,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -867,12 +1089,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -930,6 +1158,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -942,15 +1173,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -963,6 +1212,48 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -978,15 +1269,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -999,15 +1305,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1020,6 +1335,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1074,9 +1392,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1092,6 +1407,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1107,10 +1425,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1119,6 +1437,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1137,6 +1461,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1176,16 +1503,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1311,6 +1632,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1548,6 +1875,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1569,9 +1902,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1590,6 +1932,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1623,6 +1968,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1638,6 +1986,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1653,6 +2004,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1665,19 +2019,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1704,6 +2073,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1716,6 +2088,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1743,9 +2121,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1767,25 +2142,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1812,6 +2187,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1845,6 +2223,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1860,6 +2241,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1872,6 +2256,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1881,12 +2268,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1944,6 +2337,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1959,9 +2355,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1977,15 +2370,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2001,16 +2394,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2049,6 +2439,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2058,19 +2454,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2118,7 +2514,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2145,13 +2541,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2160,15 +2556,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2196,7 +2595,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2208,9 +2607,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2253,9 +2649,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2268,10 +2661,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2280,15 +2673,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2319,7 +2709,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2355,16 +2745,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2376,7 +2766,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2385,10 +2775,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2427,9 +2823,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2439,6 +2832,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2448,15 +2844,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2553,6 +2961,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2622,6 +3033,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2637,12 +3054,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2706,6 +3135,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2733,18 +3165,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2757,6 +3180,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2781,9 +3207,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2799,9 +3222,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2811,10 +3258,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2826,6 +3273,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2850,9 +3300,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2922,6 +3369,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2961,9 +3411,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3006,6 +3453,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3030,15 +3480,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3078,6 +3537,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3090,6 +3552,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3102,6 +3579,9 @@ msgstr[2] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3240,12 +3720,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3324,6 +3837,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3363,6 +3879,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3408,6 +3930,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3429,6 +3954,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3441,18 +3969,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3468,6 +4032,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3495,6 +4062,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3504,9 +4077,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3522,16 +4092,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3549,13 +4131,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3576,6 +4152,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3627,15 +4209,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3648,6 +4248,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3699,6 +4311,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3747,6 +4362,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3762,12 +4389,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3831,12 +4494,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3846,12 +4503,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3885,6 +4548,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3903,9 +4572,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3927,13 +4593,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3945,9 +4617,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3960,12 +4647,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3999,9 +4692,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4059,7 +4749,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4086,6 +4776,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4149,9 +4851,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4167,6 +4866,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4260,13 +4962,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4296,12 +5004,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4311,6 +5028,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4335,9 +5055,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4356,6 +5073,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4401,6 +5121,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4509,9 +5232,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4527,6 +5265,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4557,6 +5298,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4596,24 +5343,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4632,9 +5400,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4656,6 +5421,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4677,6 +5445,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4692,12 +5463,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4731,15 +5508,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4758,9 +5544,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4770,6 +5565,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4803,6 +5601,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4821,6 +5622,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4830,6 +5634,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4839,18 +5709,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4944,6 +5826,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4983,6 +5868,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4995,12 +5883,27 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5058,15 +5961,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5079,9 +5985,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5166,12 +6069,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5187,15 +6108,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5211,6 +6141,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5220,9 +6153,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5244,6 +6186,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5274,6 +6219,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5292,6 +6252,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5304,6 +6267,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5334,7 +6300,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5379,12 +6348,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5415,6 +6402,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5430,9 +6420,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5475,9 +6477,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5493,6 +6501,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5523,6 +6540,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5595,16 +6615,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5616,6 +6636,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5700,9 +6723,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5721,6 +6750,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5778,15 +6810,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5817,15 +6870,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5850,46 +6903,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5913,10 +6963,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5925,9 +6975,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5937,12 +6993,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6012,16 +7077,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6081,13 +7149,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6129,6 +7200,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6174,6 +7251,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6192,6 +7272,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6201,6 +7284,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6234,9 +7320,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6246,6 +7338,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6258,6 +7356,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6312,13 +7416,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6327,6 +7431,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6342,6 +7449,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6363,6 +7473,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6375,19 +7488,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6417,7 +7539,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6435,15 +7557,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6492,9 +7623,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6510,10 +7638,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6627,6 +7761,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6639,12 +7776,21 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6654,6 +7800,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6690,6 +7842,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6717,15 +7872,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6744,9 +7914,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6768,6 +7935,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6792,18 +7965,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6813,6 +7989,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6822,6 +8001,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6840,6 +8022,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6855,21 +8040,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6918,6 +8106,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6933,6 +8127,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6975,10 +8175,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6993,6 +8190,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7020,9 +8220,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7047,15 +8259,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7065,9 +8295,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7080,6 +8322,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7089,18 +8334,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7128,6 +8388,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7278,21 +8541,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7392,6 +8655,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7407,16 +8679,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7425,6 +8706,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7434,6 +8718,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7443,6 +8730,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7533,12 +8826,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7587,6 +8889,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7689,6 +8994,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7755,6 +9063,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7767,6 +9081,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7797,6 +9117,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7812,6 +9135,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7848,6 +9177,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7893,9 +9225,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7905,12 +9246,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7929,6 +9291,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7962,6 +9327,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7980,6 +9357,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7992,6 +9375,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8028,6 +9414,21 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "Resend invite"
msgstr ""
@@ -8061,9 +9462,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8088,12 +9498,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8127,12 +9543,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8154,6 +9579,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8187,25 +9618,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8217,6 +9648,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8241,6 +9675,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8250,15 +9687,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8310,6 +9756,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8337,6 +9786,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8358,9 +9816,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8376,10 +9831,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8430,6 +9885,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8439,9 +9903,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8472,10 +9933,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8493,18 +9957,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8520,9 +10005,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8550,9 +10032,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8568,6 +10059,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8577,6 +10071,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8592,6 +10098,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8649,9 +10158,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8661,6 +10176,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8721,6 +10239,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8730,6 +10254,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8751,6 +10278,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8796,6 +10344,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8823,6 +10374,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8973,6 +10527,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9021,15 +10578,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9063,6 +10632,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9216,6 +10788,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9225,15 +10818,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9243,6 +10845,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9366,12 +10971,24 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9390,6 +11007,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9402,15 +11025,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9420,6 +11091,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9447,9 +11130,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9468,6 +11160,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9483,9 +11190,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9540,9 +11256,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9558,6 +11286,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9576,6 +11307,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9588,6 +11322,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9609,6 +11358,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9624,6 +11376,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9672,6 +11427,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9693,10 +11454,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9705,7 +11472,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9729,6 +11496,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9738,10 +11508,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9756,6 +11526,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9822,9 +11595,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10056,6 +11826,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10095,6 +11868,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10119,6 +11898,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10131,6 +11913,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10140,6 +11925,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10164,6 +11961,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10173,15 +11973,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10194,6 +12006,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10254,21 +12069,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10284,6 +12117,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10308,18 +12144,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10350,18 +12198,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10440,18 +12324,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10476,9 +12381,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10488,7 +12390,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10500,12 +12402,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10539,6 +12444,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10575,12 +12483,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10629,6 +12543,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10647,9 +12567,18 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "When:"
msgstr ""
@@ -10662,6 +12591,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10773,6 +12705,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10791,6 +12726,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10803,12 +12741,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10839,6 +12789,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10854,6 +12807,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10863,6 +12825,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10872,6 +12840,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10881,12 +12852,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10905,12 +12894,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10923,7 +12927,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10935,12 +12939,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10968,6 +12984,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11001,6 +13026,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11019,9 +13053,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11037,15 +13077,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11055,12 +13110,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11085,18 +13143,42 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11130,6 +13212,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11139,6 +13224,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11148,12 +13242,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11196,9 +13302,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11244,9 +13359,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11268,6 +13380,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11286,9 +13401,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11301,10 +13425,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11334,6 +13479,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11367,22 +13515,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11448,6 +13605,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11469,6 +13629,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11505,6 +13668,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11577,16 +13743,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11640,6 +13809,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11754,12 +13926,21 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11793,9 +13974,36 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11820,6 +14028,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index b5db3291862..8298adf4526 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:09\n"
+"PO-Revision-Date: 2019-04-13 17:27\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " СтатуÑ"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " и"
@@ -36,16 +45,20 @@ msgstr[2] " улучшилоÑÑŒ на %d пунктов"
msgstr[3] " улучшилоÑÑŒ на %d пунктов"
msgid " or "
-msgstr ""
+msgstr " или "
msgid " or <#epic id>"
-msgstr ""
+msgstr " или <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " или <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" в проектах"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d комментарий"
+msgstr[1] "%d комментариÑ"
+msgstr[2] "%d комментариев"
+msgstr[3] "%d комментариев"
msgid "%d commit"
msgid_plural "%d commits"
@@ -62,7 +75,7 @@ msgstr[2] "на %d коммитов позади"
msgstr[3] "на %d коммитов позади"
msgid "%d commits"
-msgstr ""
+msgstr "%d коммитов"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -156,12 +169,28 @@ msgstr "%{counter_storage} (%{counter_repositories} репозиториев, %{
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{count} одобрений от %{name}"
+
msgid "%{count} more"
-msgstr ""
+msgstr "ещё %{count}"
msgid "%{count} more assignees"
msgstr "Ещё %{count} иÑполнителей"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{count} из %{required} одобрений от %{name}"
+
+msgid "%{count} of %{total}"
+msgstr "%{count} из %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} учаÑтник"
@@ -182,24 +211,33 @@ msgstr "%{filePath} удален"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} + ещё %{labelCount}"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} не найден в Ñтом коммите"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
-msgstr ""
+msgstr "%{group_docs_link_start}Группы%{group_docs_link_end} позволÑÑŽÑ‚ управлÑÑ‚ÑŒ неÑколькими проектами и Ñотрудничать Ñ Ð½Ð¸Ð¼Ð¸. Члены группы имеют доÑтуп ко вÑем ее проектам."
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} будет удален! Вы уверены?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} недоÑтупно"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}Подробнее%{link_end} о ролевом доÑтупе"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} Запущена"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} заблокирован пользователем GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Ðватар Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{name}"
@@ -212,6 +250,15 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% выполнено"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -259,10 +306,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr ""
msgid "%{user_name} profile page"
+msgstr "Ñтраница Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ %{user_name}"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(внешний иÑточник)"
msgid "+ %{count} more"
msgstr "+ ещё %{count}"
@@ -270,8 +320,11 @@ msgstr "+ ещё %{count}"
msgid "+ %{moreCount} more"
msgstr "+ ещё %{moreCount}"
+msgid "+%{extraOptionCount} more"
+msgstr "+ещё %{extraOptionCount}"
+
msgid ", or "
-msgstr ""
+msgstr ", или "
msgid "- Runner is active and can process any new jobs"
msgstr "- Runner активен и может обрабатывать любые новые заданиÑ"
@@ -296,6 +349,13 @@ msgstr[1] "%{count} модификаций типа %{type}"
msgstr[2] "%{count} модификаций типа %{type}"
msgstr[3] "%{count} модификаций типа %{type}"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 закрытое обÑуждение"
@@ -310,6 +370,9 @@ msgstr[1] "%d закрытых запроÑов на ÑлиÑние"
msgstr[2] "%d закрытых запроÑов на ÑлиÑние"
msgstr[3] "%d закрытых запроÑов на ÑлиÑние"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 группа"
@@ -359,18 +422,30 @@ msgstr[1] "%d пользователей"
msgstr[2] "%d пользователей"
msgstr[3] "%d пользователей"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "Первый вклад!"
msgid "2FA"
-msgstr ""
+msgstr "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ"
msgid "2FA enabled"
msgstr "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð°"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
msgstr ""
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "ПожалуйÑта обратитеÑÑŒ к вашему админиÑтратору GitLab Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ."
+
msgid "403|You don't have the permission to access this page."
msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ права доÑтупа к Ñтой Ñтранице."
@@ -383,6 +458,9 @@ msgstr "Страница Ðе Ðайдена"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "ПожалуйÑта ÑвÑжитеÑÑŒ Ñ Ð²Ð°ÑˆÐ¸Ð¼ админиÑтратором GitLab Ñервера, еÑли вы Ñчитаете данное поведение ошибкой."
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -399,25 +477,37 @@ msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFiles
msgstr ""
msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
-msgstr ""
+msgstr "<strong>%{created_count}</strong> Ñоздано, <strong>%{accepted_count}</strong> принÑто."
msgid "<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
-msgstr ""
+msgstr "<strong>%{created_count}</strong> Ñоздано, <strong>%{closed_count}</strong> закрыто."
msgid "<strong>%{group_name}</strong> group members"
-msgstr ""
+msgstr "<strong>%{group_name}</strong> учаÑтников группы"
msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
msgstr ""
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>УдалÑет</strong> иÑходную ветку"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Графики непрерывной интеграции (CI)"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr "Шаблон конÑольного Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ .NET Core, наÑтраиваемый Ð´Ð»Ñ Ð»ÑŽÐ±Ð¾Ð³Ð¾ проекта .NET Core"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr "Ð”Ð»Ñ Ð¿ÑƒÑтого проекта Ð½ÐµÐ»ÑŒÐ·Ñ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ ветку по умолчанию."
@@ -428,18 +518,39 @@ msgstr "Удаленный пользователь"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "Сотрудник группы по борьбе Ñ Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñми GitLab раÑÑмотрит ваш отчет так быÑтро, наÑколько Ñто возможно."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "Ð’ вашем ответвлении будет Ñоздана Ð½Ð¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ° и запущен новый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние."
+
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "Ð’ проекте вы размещаете Ñвои файлы (репозиторий), планируете Ñвою работу (обÑуждениÑ), и публикуете документацию (wiki), %{among_other_things_link}."
-msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgid "A ready-to-go template for use with Android apps."
msgstr ""
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
+msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
+msgstr "РегулÑрное выражение, которое будет иÑпользоватьÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка результатов Ð¿Ð¾ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñ‚ÐµÑтами в задании. ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ"
+
msgid "A user with write access to the source branch selected this option"
msgstr "Пользователь Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸ÐµÐ¼ на запиÑÑŒ в ветку иÑточника выбрал Ñтот вариант"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "О GitLab"
@@ -456,10 +567,10 @@ msgid "Abuse Reports"
msgstr "Отчёты о Жалобах"
msgid "Abuse reports"
-msgstr ""
+msgstr "Жалобы"
msgid "Accept invitation"
-msgstr ""
+msgstr "ПринÑÑ‚ÑŒ приглашение"
msgid "Accept terms"
msgstr "ПринÑÑ‚ÑŒ уÑловиÑ"
@@ -470,12 +581,18 @@ msgstr "ПринÑтый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
msgid "Access Tokens"
msgstr "Токены ДоÑтупа"
-msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgid "Access denied for your LDAP account."
msgstr ""
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr "ДоÑтуп запрещен! ПожалуйÑта, убедитеÑÑŒ, что вы можете добавить ключи Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð² Ñтот репозиторий."
+
msgid "Access expiration date"
msgstr "Дата Ð¿Ñ€ÐµÐºÑ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾Ñтупа"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "ДоÑтуп к '%{classification_label}' не разрешён"
@@ -485,6 +602,12 @@ msgstr "Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ"
msgid "Account and limit"
msgstr "Ðккаунт и ограничениÑ"
+msgid "Action to take when receiving an alert."
+msgstr "ДейÑтвиÑ, предпринимаемые при получении предупреждениÑ."
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивный"
@@ -498,13 +621,13 @@ msgid "Add"
msgstr "Добавить"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Добавить CHANGELOG"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "Добавить CONTRIBUTING"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
-msgstr ""
+msgstr "Добавить групповые веб-обработчики и GitLab Enterprise Edition."
msgid "Add Jaeger URL"
msgstr "Добавить URL-Ð°Ð´Ñ€ÐµÑ Jaeger"
@@ -513,43 +636,70 @@ msgid "Add Kubernetes cluster"
msgstr "Добавить Kubernetes клаÑтер"
msgid "Add README"
-msgstr ""
+msgstr "Добавить README"
+
+msgid "Add a bullet list"
+msgstr "Добавить маркированный ÑпиÑок"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Добавить общий комментарий к Ñтому %{noteable_name}."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
-msgstr ""
+msgstr "Добавьте домашнюю Ñтраницу в Ñвою вики, в которой ÑодержитÑÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ вашем проекте, и GitLab отобразит ее здеÑÑŒ вмеÑто Ñтого ÑообщениÑ."
+
+msgid "Add a link"
+msgstr "Добавить ÑÑылку"
+
+msgid "Add a numbered list"
+msgstr "Добавить нумерованный ÑпиÑок"
msgid "Add a table"
msgstr "Добавить таблицу"
+msgid "Add a task list"
+msgstr "Добавить ÑпиÑок задач"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
-msgstr ""
+msgstr "Добавить дополнительный текÑÑ‚, который будет отображатьÑÑ Ð²Ð¾ вÑех ÑообщениÑÑ… Ñлектронной почты. МакÑимум %{character_limit} Ñимволов"
+
+msgid "Add approver(s)"
+msgstr "Добавить утверждающего(их)"
+
+msgid "Add approvers"
+msgstr "Добавить утверждающих"
+
+msgid "Add bold text"
+msgstr "Добавить жирный текÑÑ‚"
msgid "Add comment now"
msgstr "Добавить комментарий"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "Добавить комментарий к изображению"
+msgid "Add italic text"
+msgstr "Добавить курÑив"
+
msgid "Add license"
msgstr "Добавить лицензию"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Добавить новое приложение"
msgid "Add new directory"
msgstr "Добавить новый каталог"
-msgid "Add projects"
-msgstr "Добавить проекты"
-
msgid "Add reaction"
msgstr "Добавить реакцию"
msgid "Add to project"
-msgstr ""
+msgstr "Добавить в проект"
msgid "Add to review"
msgstr "Добавить к рецензированию"
@@ -561,28 +711,37 @@ msgid "Add user(s) to the group:"
msgstr "Добавить пользовател(Ñ/ей) в группу:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Добавить пользователей или группы, которые имеют право утверждать каждый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
msgid "Add users to group"
msgstr "Добавить пользователей в группу"
-msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgid "Added at"
msgstr ""
+msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
+msgstr "Добавление новых приложений отключено в вашем ÑкземплÑре GitLab. ОбратитеÑÑŒ к админиÑтратору GitLab, чтобы получить разрешение"
+
msgid "Additional text"
msgstr "Дополнительный текÑÑ‚"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "Панель админиÑтрированиÑ"
msgid "Admin Overview"
msgstr "Обзор ÐдминиÑтратора"
-msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgid "Admin Section"
msgstr ""
+msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
+msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ окончательно удалить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{username}. ОбÑуждениÑ, запроÑÑ‹ ÑлиÑÐ½Ð¸Ñ Ð¸ ÑвÑзанные Ñ Ð½Ð¸Ð¼Ð¸ группы будут переданы общеÑиÑтемному «Призрачному пользователю». Чтобы избежать потери данных, раÑÑмотрите возможноÑÑ‚ÑŒ иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ %{strong_start}блокировки пользователÑ%{strong_end}. ПоÑле %{strong_start}ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ%{strong_end} его будет невозможно воÑÑтановить или отменить удаление."
+
msgid "AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
-msgstr ""
+msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ окончательно удалить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{username}. Это удалит вÑе его обÑуждениÑ, запроÑÑ‹ ÑлиÑÐ½Ð¸Ñ Ð¸ ÑвÑзанные Ñ Ð½Ð¸Ð¼Ð¸ группы. Чтобы избежать потери данных, раÑÑмотрите возможноÑÑ‚ÑŒ иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ %{strong_start}блокировки пользователÑ%{strong_end}. ПоÑле %{strong_start}ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ%{strong_end} его будет невозможно воÑÑтановить или отменить удаление."
msgid "AdminArea|Stop all jobs"
msgstr "ОÑтановить вÑе заданиÑ"
@@ -600,7 +759,7 @@ msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs
msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ оÑтановить вÑе заданиÑ. Это дейÑтвие оÑтановит вÑе текущие заданиÑ, находÑщиеÑÑ Ð² процеÑÑе выполнениÑ."
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
-msgstr ""
+msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ окончательно удалить проект %{projectName}, его репозиторий и вÑе ÑвÑзанные реÑурÑÑ‹, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð¾Ð±ÑуждениÑ, запроÑÑ‹ ÑлиÑÐ½Ð¸Ñ Ð¸ Ñ‚.д. ПоÑле Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ %{strong_start}ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°%{strong_end} его будет невозможно воÑÑтановить или отменить удаление."
msgid "AdminProjects|Delete"
msgstr "Удалить"
@@ -612,40 +771,43 @@ msgid "AdminProjects|Delete project"
msgstr "Удалить проект"
msgid "AdminSettings|Auto DevOps domain"
+msgstr "Домен Auto DevOps"
+
+msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Переменные Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð·Ð°Ñ‰Ð¸Ñ‰ÐµÐ½Ñ‹ по умолчанию"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "Укажите домен, который будет иÑпользоватьÑÑ Ð¿Ð¾ умолчанию Ð´Ð»Ñ Ð²Ñех проектов в Auto Review приложениÑÑ… и ÑтадиÑÑ… Auto Deploy."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "При Ñоздании новой переменной Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð¾Ð½Ð° будет защищена по умолчанию."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA отключена"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA включена"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Ðктивные"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "ÐдминиÑтратор"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "ÐдминиÑтраторы"
msgid "AdminUsers|Block user"
msgstr "Заблокировать пользователÑ"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Заблокированые"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "Ðе удалоÑÑŒ разблокировать заблокированных пользователей LDAP"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "Удалить ÐŸÐ¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{username} и внеÑённые им Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ?"
@@ -660,28 +822,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "Удалить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ внеÑённые им изменениÑ"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Внешние"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "Это вы!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Ðовый пользователь"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "Пользователи не найдены"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "ПоиÑк по имени, Ñлектронной почте или по имени пользователÑ"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "ПоиÑк пользователей"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Отправить Ñлектронные пиÑьма пользователÑм"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Сортировать по"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "Ð”Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ, введите %{projectName}"
@@ -690,17 +852,26 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "Ð”Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ, введите %{username}"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "Пользователь будет заблокирован"
msgid "AdminUsers|Without projects"
+msgstr "Без проектов"
+
+msgid "Advanced"
msgstr ""
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "РаÑширенные наÑтройки"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Предупреждение"
@@ -709,11 +880,14 @@ msgstr[2] "Предупреждений"
msgstr[3] "Предупреждений"
msgid "Alerts"
-msgstr ""
+msgstr "ОповещениÑ"
msgid "All"
msgstr "Ð’Ñе"
+msgid "All Members"
+msgstr "Ð’Ñе УчаÑтники"
+
msgid "All changes are committed"
msgstr "Ð’Ñе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð·Ð°Ñ„Ð¸ÐºÑированы"
@@ -723,15 +897,33 @@ msgstr "Ð’Ñе функции ÑиÑтемы включаютÑÑ Ð´Ð»Ñ Ð¿ÑƒÑÑ
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "Ð’Ñе пользователи"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr "Разрешить коммиты от учаÑтников, которые могут выполнÑÑ‚ÑŒ ÑлиÑние Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð¹ веткой."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -744,6 +936,12 @@ msgstr "Разрешить рендеринг диаграмм PlantUML в доÐ
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Разрешить пользователÑм запрашивать доÑтуп"
@@ -762,6 +960,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -769,13 +970,13 @@ msgid "Alternatively, you can use a %{personal_access_token_link}. When you crea
msgstr ""
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
-msgstr ""
+msgstr "Ð’Ñ€ÐµÐ¼Ñ (в чаÑах), на которое пользователÑм позволено пропуÑкать принудительную наÑтройку двухфакторной аутентификации"
msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
-msgstr ""
+msgstr "SSH ключ автоматичеÑки ÑгенерируетÑÑ Ð¿Ð¾Ñле отправки формы. ОбратитеÑÑŒ к документации Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ информации."
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
-msgstr ""
+msgstr "Приложение под названием %{link_to_client} запрашивает доÑтуп к вашей учетной запиÑи GitLab."
msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
msgstr ""
@@ -783,9 +984,6 @@ msgstr ""
msgid "An error has occurred"
msgstr "Произошла ошибка"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Произошла ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ€Ð½Ð¾Ð²Ð¸ÐºÐ° ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ Ðº диÑкуÑÑии."
@@ -795,6 +993,15 @@ msgstr "Произошла ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ чер
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr "Произошла ошибка при предварительном проÑмотре объекта"
@@ -805,7 +1012,10 @@ msgid "An error occurred when updating the issue weight"
msgstr "Произошла ошибка при обновлении веÑа обÑуждениÑ"
msgid "An error occurred while adding approver"
-msgstr "Произошла ошибка при добавлении одобрÑющего"
+msgstr "Произошла ошибка при удалении утверждающего"
+
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
msgid "An error occurred while deleting the comment"
msgstr "Во Ð²Ñ€ÐµÐ¼Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° ошибка"
@@ -813,12 +1023,18 @@ msgstr "Во Ð²Ñ€ÐµÐ¼Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»
msgid "An error occurred while detecting host keys"
msgstr "Произошла ошибка при обнаружении ключей хоÑта"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "При отключении Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° ошибка. Обновите Ñтраницу и повторите попытку."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "Произошла ошибка при отключении ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ функции. Обновите Ñтраницу и повторите попытку."
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "Произошла ошибка при предварительном проÑмотре markdown"
@@ -831,6 +1047,9 @@ msgstr "Произошла ошибка при получении денег дÐ
msgid "An error occurred while fetching stages."
msgstr "Произошла ошибка при извлечении Ñтадий."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -843,6 +1062,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr "Произошла ошибка при получении Ñборочной линии."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "Произошла ошибка при получении ÑпиÑка проектов"
@@ -858,6 +1080,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "Произошла ошибка при загрузке различий"
@@ -874,7 +1099,7 @@ msgid "An error occurred while making the request."
msgstr "Произошла ошибка при выполнении запроÑа."
msgid "An error occurred while removing approver"
-msgstr "Произошла ошибка при удалении одобрÑющего"
+msgstr "Произошла ошибка при удалении утверждающего"
msgid "An error occurred while removing epics."
msgstr ""
@@ -900,12 +1125,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr "Произошла ошибка при Ñохранении ответÑтвенных"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -963,9 +1194,12 @@ msgstr ""
msgid "Analytics"
msgstr "Ðналитика"
-msgid "Anonymous"
+msgid "Ancestors"
msgstr ""
+msgid "Anonymous"
+msgstr "Ðноним"
+
msgid "Anti-spam verification"
msgstr ""
@@ -975,15 +1209,33 @@ msgstr "Любой"
msgid "Any Label"
msgstr "Ð›ÑŽÐ±Ð°Ñ ÐœÐµÑ‚ÐºÐ°"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Оформление"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Приложение"
msgid "Application ID"
msgstr "Идентификатор приложениÑ"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Приложение: %{name}"
@@ -994,16 +1246,62 @@ msgid "Applied"
msgstr ""
msgid "Apply suggestion"
+msgstr "Применить предложение"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
msgstr ""
-msgid "Approvals"
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
+msgid "Approvals"
+msgstr "УтверждениÑ"
+
msgid "Approvals required"
msgstr ""
msgid "Approvers"
-msgstr ""
+msgstr "Утверждающие"
msgid "Apr"
msgstr "Ðпр."
@@ -1011,13 +1309,28 @@ msgstr "Ðпр."
msgid "April"
msgstr "Ðпрель"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "Ðрхивный проект! Репозиторий и другие реÑурÑÑ‹ проекта доÑтупны только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ"
msgid "Archived projects"
msgstr "Ðрхивные проекты"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
+msgstr "Вы уверены"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
@@ -1032,15 +1345,24 @@ msgstr "Ð’Ñ‹ уверены, что вы хотите потерÑÑ‚ÑŒ не Ñо
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "Ð’Ñ‹ уверены, что вы хотите заново Ñгенерировать публичный ключ? Вам нужно будет обновить публичный ключ на удаленном Ñервере, прежде чем зеркалирование Ñнова будет работать."
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Вы уверены, что вы хотите удалить %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1053,6 +1375,9 @@ msgstr "Ð’Ñ‹ уверены, что вы хотите удалить Ñту ид
msgid "Are you sure you want to reset registration token?"
msgstr "Ð’Ñ‹ уверены, что вы хотите ÑброÑить Ñтот региÑтрационный токен?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Ð’Ñ‹ уверены, что хотите ÑброÑить Ñтот токен проверки работоÑпоÑобноÑти?"
@@ -1107,9 +1432,6 @@ msgstr "Ðазначенные обÑуждениÑ"
msgid "Assigned Merge Requests"
msgstr "Ðазначенные запроÑÑ‹ на ÑлиÑние"
-msgid "Assigned to :name"
-msgstr "Ðазначено на :name"
-
msgid "Assigned to me"
msgstr "Ðазначить мне"
@@ -1125,6 +1447,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr "ОтветÑтвенный(ные)"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1140,18 +1465,24 @@ msgstr "Ðвг."
msgid "August"
msgstr "ÐвгуÑÑ‚"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Журнал аутентификации"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "Журнал аутентификации"
msgid "Authentication method"
msgstr "Метод аутентификации"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Ðвтор"
@@ -1170,6 +1501,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1207,20 +1541,14 @@ msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
msgstr "Подробнее по ÑÑылке %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
-msgstr ""
-
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "Ð’Ñ‹ можете автоматичеÑки Ñобирать и теÑтировать Ñвое приложение, еÑли Ð´Ð»Ñ Ñтого проекта %{link_to_auto_devops_settings}. Ð’Ñ‹ также можете автоматичеÑки развернуть Ñвое приложение, еÑли вы %{link_to_add_kubernetes_cluster}."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "добавите клаÑтер Kubernetes"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr " включить ÐвтоматичеÑкий DevOps"
+msgstr "Ð¡Ð±Ð¾Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ Auto DevOps была включена и будет иÑпользоватьÑÑ, еÑли не найден альтернативный файл конфигурации CI. %{more_information_link}"
msgid "Automatically marked as default internal user"
msgstr ""
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "Automatically resolved"
msgstr ""
@@ -1344,6 +1672,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "Ðачать Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð³Ð¾ коммита"
@@ -1581,6 +1915,12 @@ msgstr "наÑтройках проекта"
msgid "Branches|protected"
msgstr "защищена"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "ПроÑмотр каталога"
@@ -1602,14 +1942,23 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "по автору"
msgid "CHANGELOG"
-msgstr ""
+msgstr "CHANGELOG"
msgid "CI / CD"
msgstr "CI / CD"
@@ -1623,6 +1972,9 @@ msgstr "ÐаÑтройки CI / CD"
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1642,40 +1994,46 @@ msgid "CICD|Auto DevOps"
msgstr "ÐвтоматичеÑкий DevOps"
msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
-msgstr ""
+msgstr "\"Auto DevOps\" будет автоматичеÑки Ñоздавать, теÑтировать и разворачивать Ваше приложение на оÑнове предопределенных наÑтроек CI/CD."
msgid "CICD|Automatic deployment to staging, manual deployment to production"
msgstr ""
msgid "CICD|Continuous deployment to production"
-msgstr ""
+msgstr "Ðепрерывное развёртывание в рабочий контур"
msgid "CICD|Continuous deployment to production using timed incremental rollout"
-msgstr ""
+msgstr "CICD| Ðепрерывное развёртывание в рабочий контур Ñ Ð¸Ñпользованием пошагового развёртываниÑ"
msgid "CICD|Default to Auto DevOps pipeline"
+msgstr "По умолчанию Ð´Ð»Ñ Ñборочной линии Auto DevOps"
+
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
msgstr ""
msgid "CICD|Deployment strategy"
-msgstr ""
+msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ñ€Ð°Ð·Ð²Ñ‘Ñ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ"
msgid "CICD|Jobs"
msgstr "ЗаданиÑ"
msgid "CICD|Learn more about Auto DevOps"
-msgstr ""
+msgstr "Узнайте больше об \"Auto DevOps\""
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
-msgstr ""
+msgstr "Сборка Auto DevOps будет запущена, еÑли не будет найден иной файл конфигурации CI."
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
-msgid "CICD|instance enabled"
+msgid "CICD|group enabled"
msgstr ""
+msgid "CICD|instance enabled"
+msgstr "ЭкземплÑÑ€ включен"
+
msgid "CONTRIBUTING"
-msgstr ""
+msgstr "CONTRIBUTING"
msgid "Callback URL"
msgstr "URL обратного вызова"
@@ -1684,6 +2042,9 @@ msgid "Can override approvers and approvals required per merge request"
msgstr ""
msgid "Can't find HEAD commit for this branch"
+msgstr "Ðевозможно найти HEAD-коммит Ñтой ветки"
+
+msgid "Can't remove group members without group managed account"
msgstr ""
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
@@ -1698,21 +2059,36 @@ msgstr "Отменить Ñто задание"
msgid "Cannot be merged automatically"
msgstr "Ðет возможноÑти объединить автоматичеÑки"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Ðевозможно изменить управлÑемый клаÑтер Kubernetes"
-msgid "Certificate"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Отпечаток Ñертификата"
+msgid "Capacity threshold"
+msgstr ""
+
+msgid "Certificate"
+msgstr "Сертификат"
+
+msgid "Certificate (PEM)"
+msgstr "Сертификат (PEM)"
msgid "Change Weight"
msgstr "Изменить ВеÑ"
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1737,6 +2113,9 @@ msgstr "Отменить"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Это ÑоздаÑÑ‚ новый коммит Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы откатить ÑущеÑтвующие изменениÑ."
+msgid "Changes"
+msgstr "ИзменениÑ"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1749,6 +2128,12 @@ msgstr "Диаграммы"
msgid "Chat"
msgstr "Чат"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1776,9 +2161,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr "Выберите Файл..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Выберите ветку/тег (например, %{master}) или введите коммит (например, %{sha}), чтобы увидеть, что изменилоÑÑŒ или Ñоздать Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние."
@@ -1800,27 +2182,27 @@ msgstr "Выберите любой цвет."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
-msgstr "Выберите файл..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
-msgstr "Выберите, какие репозитории вы хотите импортировать."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "отменено"
@@ -1845,6 +2227,9 @@ msgstr "пройдено Ñ Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñми"
msgid "CiStatusLabel|pending"
msgstr "в ожидании"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "пропущено"
@@ -1878,6 +2263,9 @@ msgstr "пройдено"
msgid "CiStatusText|pending"
msgstr "в ожидании"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "пропущено"
@@ -1893,6 +2281,9 @@ msgstr "Значение входной переменной"
msgid "CiVariables|Remove variable row"
msgstr "Удалить Ñтроку переменных"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr " * (Ð’Ñе Ñреды)"
@@ -1905,6 +2296,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1914,12 +2308,18 @@ msgstr "Защищено"
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Включить защиту"
msgid "CiVariable|Validation failed"
msgstr "Проверка не удалаÑÑŒ"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1977,6 +2377,9 @@ msgstr "Клонировать репозиторий"
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1992,9 +2395,6 @@ msgstr ""
msgid "Closed"
msgstr "Закрыто"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Закрытые обÑуждениÑ"
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} уÑпешно уÑтановлены на вашем клаÑтере Kubernetes"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "ÐÐ´Ñ€ÐµÑ API"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Добавить клаÑтер Kubernetes"
@@ -2034,18 +2434,15 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Дополнительные опции Ð´Ð»Ñ Ñтой интеграции Ñ ÐºÐ»Ð°Ñтером Kubernetes"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Произошла ошибка при попытке Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð·Ð¾Ð½ проекта: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -2082,6 +2479,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,20 +2494,20 @@ msgstr "Скопировать Ð°Ð´Ñ€ÐµÑ API"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Копировать Сертификат УдоÑтоверÑющего Центра"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Скопировать IP-Ð°Ð´Ñ€ÐµÑ Ingress в буфер обмена"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Скопировать Ð¸Ð¼Ñ ÐºÐ»Ð°Ñтера Kubernetes"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Скопировать Токен"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Создать клаÑтер Kubernetes"
@@ -2151,7 +2554,7 @@ msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ GitLab"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2176,16 +2579,16 @@ msgid "ClusterIntegration|Hide"
msgstr "Скрыть"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
-msgstr ""
+msgstr "ЕÑли вы наÑтраиваете неÑколько клаÑтеров и вы иÑпользуете Auto DevOps, %{help_link_start}прочтите Ñто%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "IP-Ð°Ð´Ñ€ÐµÑ Ingress"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
@@ -2193,15 +2596,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr "УÑтановить"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr "УÑтановлен"
msgid "ClusterIntegration|Installing"
msgstr "УÑтановка"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ð·Ð°Ñ†Ð¸Ð¸ клаÑтеров Kubernetes"
@@ -2229,7 +2635,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr "КлаÑтер Kubernetes"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ клаÑтере Kubernetes"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "КлаÑтер Kubernetes ÑоздаётÑÑ Ð² Google Kubernetes Engine..."
@@ -2286,9 +2689,6 @@ msgstr "Управление"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "УправлÑйте вашему клаÑтером Kubernetes, Ð¿ÐµÑ€ÐµÐ¹Ð´Ñ Ð¿Ð¾ ÑÑылке %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Ðет типов машин, ÑоответÑтвующих вашему поиÑковому запроÑу"
@@ -2301,27 +2701,24 @@ msgstr "Ðет проектов, ÑоответÑтвующих вашему пÐ
msgid "ClusterIntegration|No zones matched your search"
msgstr "Ðет зон, ÑоответÑтвующих вашему поиÑковому запроÑу"
-msgid "ClusterIntegration|Note:"
-msgstr "Примечание:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "КоличеÑтво узлов"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "ПожалуйÑта, укажите параметры доÑтупа к вашему клаÑтеру Kubernetes. ЕÑли вам необходима помощь, вы можете ознакомитьÑÑ Ñ Ð½Ð°ÑˆÐµÐ¹ Ñтраницей %{link_to_help_page} по Kubernetes"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "ПожалуйÑта, убедитеÑÑŒ, что ваш аккаунт Google отвечает Ñледующим требованиÑм:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr "ПроÑтранÑтво имён проекта"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "ПроÑтранÑтво имен проекта (необÑзательное, уникальное)"
@@ -2352,7 +2749,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Ðе удалоÑÑŒ выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° запуÑк процеÑÑа уÑтановки"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,8 +2785,11 @@ msgstr "Выберете зону"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Выберите зону, чтобы выбрать тип машины"
-msgid "ClusterIntegration|Service token"
-msgstr "Служебный токен"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Показать"
@@ -2397,9 +2797,6 @@ msgstr "Показать"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr " У Ð½Ð°Ñ Ñ‡Ñ‚Ð¾-то пошло не так."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Что-то пошло не так при Ñоздании клаÑтера Kubernetes в Google Kubernetes Engine"
@@ -2409,7 +2806,7 @@ msgstr "Произошли ошибки во Ð²Ñ€ÐµÐ¼Ñ ÑƒÑтановки %{tit
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2418,11 +2815,17 @@ msgstr " У Ñтой учетной запиÑи должны быть разрÐ
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Переключить клаÑтер Kubernetes"
-msgid "ClusterIntegration|Token"
-msgstr "Токен"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2460,9 +2863,6 @@ msgstr "Зона"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "доÑтуп к Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "поÑмотрите цены здеÑÑŒ"
-
msgid "ClusterIntegration|documentation"
msgstr "документациÑ"
@@ -2472,6 +2872,9 @@ msgstr "Ñтраница Ñправки"
msgid "ClusterIntegration|meets the requirements"
msgstr "отвечает требованиÑм"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "правильно наÑтроен"
@@ -2481,15 +2884,27 @@ msgstr "зарегиÑтрироватьÑÑ"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr "Владельцы кода"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Когорты"
msgid "Collapse"
msgstr "Свернуть"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "Свернуть боковую панель"
@@ -2587,6 +3002,9 @@ msgstr "ЗафикÑировано автором"
msgid "Commit…"
msgstr "Коммит…"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "Сравнить"
@@ -2656,6 +3074,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Подключить"
@@ -2671,12 +3095,24 @@ msgstr ""
msgid "Connecting..."
msgstr "Подключение..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "РееÑÑ‚Ñ€ Контейнеров"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Создан"
@@ -2740,6 +3176,9 @@ msgstr "ВнеÑти вклад в GitLab"
msgid "Contribution"
msgstr "УчаÑтие"
+msgid "Contribution Analytics"
+msgstr "Ðналитика вкладов"
+
msgid "Contribution Charts"
msgstr ""
@@ -2767,18 +3206,9 @@ msgstr "ПожалуйÑта подождите, Ñта Ñтраница автÐ
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2791,6 +3221,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2815,9 +3248,6 @@ msgstr "Копировать SHA коммита в буфер обмена"
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr "Копировать ÑÑылку"
@@ -2833,9 +3263,33 @@ msgstr "Скопировать в буфер обмена"
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr "Покрытие"
+
msgid "Create"
msgstr "Создать"
@@ -2845,12 +3299,12 @@ msgstr "Создать Ðовый каталог"
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
msgid "Create a new branch"
msgstr "Создать новую ветку"
-msgid "Create a new branch and merge request"
-msgstr "Создать новую ветку и Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
-
msgid "Create a new issue"
msgstr "Создать новое обÑуждение"
@@ -2860,6 +3314,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Создать личный токен на аккаунте Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ отправки через %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr "Создать обÑуждение. ОбÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ ÑоздаютÑÑ Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ Ñработавшего предупреждениÑ."
+
msgid "Create branch"
msgstr "Создать ветку"
@@ -2884,9 +3341,6 @@ msgstr "Создать группу"
msgid "Create group label"
msgstr "Создать метку группы"
-msgid "Create issue"
-msgstr "Создать обÑуждение"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Создать ÑпиÑок из меток. ОбÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ñ Ñтой меткой поÑвлÑÑŽÑ‚ÑÑ Ð² Ñтом ÑпиÑке."
@@ -2915,7 +3369,7 @@ msgid "Create new label"
msgstr "Создать новую метку"
msgid "Create new..."
-msgstr "Ðовый"
+msgstr "Создать новый..."
msgid "Create project label"
msgstr "Создать метку проекта"
@@ -2956,6 +3410,9 @@ msgstr "СинтакÑÐ¸Ñ Cron"
msgid "Current Branch"
msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð’ÐµÑ‚ÐºÐ°"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "Текущий узел"
@@ -2995,9 +3452,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3040,6 +3494,9 @@ msgstr "Ð’Ñе"
msgid "DashboardProjects|Personal"
msgstr "Личные"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3064,15 +3521,24 @@ msgstr "Отклонить и выйти"
msgid "Default Branch"
msgstr "Ветка по умолчанию"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3112,6 +3578,9 @@ msgstr "Удалить пример кода"
msgid "Delete comment"
msgstr "Удалить комментарий"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Удалить ÑпиÑок"
@@ -3124,6 +3593,21 @@ msgstr ""
msgid "Deleted"
msgstr "Удалено"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3137,6 +3621,9 @@ msgstr[3] "РазвертываниÑ"
msgid "Deploy Keys"
msgstr "Ключи РазвертываниÑ"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} других"
@@ -3275,12 +3762,45 @@ msgstr "По убыванию"
msgid "Description"
msgstr "ОпиÑание"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr "ОпиÑание:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3359,6 +3879,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3398,20 +3921,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "Скачать tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Скачать tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Скачать tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Скачать zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Скачать"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Email-патчи"
@@ -3443,6 +3972,9 @@ msgstr "Редактировать"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr "Редактировать метку"
@@ -3464,6 +3996,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr "Редактируйте файлы в редакторе и зафикÑируйте Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð·Ð´ÐµÑÑŒ"
@@ -3476,18 +4011,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Elasticsearch. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "Email-адреÑа"
@@ -3503,6 +4074,9 @@ msgstr "Включить"
msgid "Enable Auto DevOps"
msgstr "Включить Auto DevOps"
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3530,6 +4104,12 @@ msgstr "Включить Ð´Ð»Ñ Ñтого проекта"
msgid "Enable group Runners"
msgstr "Включить групповые обработчики заданий"
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3539,9 +4119,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3557,16 +4134,28 @@ msgstr "Включить Ñбор ÑтатиÑтики иÑпользованиÑ
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Включено"
-msgid "Ends at (UTC)"
-msgstr "ЗаканчиваетÑÑ Ð² (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr "ЗаканчиваетÑÑ Ð² (UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,13 +4173,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr "Введите заголовок запроÑа на ÑлиÑние"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3611,6 +4194,12 @@ msgstr "Произошла ошибка при получении окружен
msgid "Environments|An error occurred while making the request."
msgstr "Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3662,15 +4251,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr "Подробнее об окружениÑÑ…"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Показать вÑе"
@@ -3683,6 +4290,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "Обновлено"
@@ -3734,6 +4353,9 @@ msgstr "Отчеты об ошибках и журналирование"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3782,6 +4404,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr "Произошла ошибка при переключении подпиÑки на оповещение"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3797,12 +4431,48 @@ msgstr "Ошибка при обновлении ÑтатуÑа Ð´Ð»Ñ Ð²Ñех
msgid "Error updating todo status."
msgstr "Ошибка при обновлении ÑтатуÑа дела."
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3866,12 +4536,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3881,12 +4545,18 @@ msgstr "Развернуть"
msgid "Expand all"
msgstr "Развернуть вÑе"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "Развернуть боковую панель"
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3920,6 +4590,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3938,9 +4614,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3962,13 +4635,19 @@ msgstr "Ðе удалоÑÑŒ изменить владельца"
msgid "Failed to check related branches."
msgstr "Ðе удалоÑÑŒ проверить ÑвÑзанные ветки."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr "Ðе удалоÑÑŒ удалить раÑпиÑание Ñборочной линии"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3995,12 +4689,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "Функциональные опции"
@@ -4034,9 +4734,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "ОпиÑание"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4094,7 +4791,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4121,6 +4818,18 @@ msgstr "Фев."
msgid "February"
msgstr "Февраль"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "ÐŸÐ¾Ð»Ñ Ð½Ð° Ñтой Ñтранице ÑÐµÐ¹Ñ‡Ð°Ñ Ð½ÐµÐ´Ð¾Ñтупны Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ, вы можете наÑтроить"
@@ -4185,9 +4894,6 @@ msgstr ""
msgid "Filter..."
msgstr "Фильтр..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "ПоиÑк по пути"
@@ -4203,6 +4909,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4296,14 +5005,20 @@ msgstr ""
msgid "Format"
msgstr "Формат"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
-msgstr "Из %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr ""
@@ -4332,12 +5047,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG Ключи"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "ОÑновныe"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4347,6 +5071,9 @@ msgstr "Создать Ñтандартный набор меток"
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4371,9 +5098,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4392,6 +5116,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4437,6 +5164,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4545,9 +5275,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4563,6 +5308,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4593,6 +5341,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4632,24 +5386,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4668,9 +5443,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4692,6 +5464,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4713,6 +5488,9 @@ msgstr "Импорт из GitHub"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter перемещен"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4767,15 +5551,24 @@ msgstr ""
msgid "Go Back"
msgstr "ВернутьÑÑ Ð½Ð°Ð·Ð°Ð´"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "ВернутьÑÑ"
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4794,9 +5587,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "Группа"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4806,6 +5608,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "Групповые Обработчики Заданий (GitLab Group Runner)"
@@ -4839,6 +5644,9 @@ msgstr "Ðазвание группы"
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4857,6 +5665,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4866,6 +5677,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4875,18 +5752,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Запретить публикацию проектов из %{group} в других группах"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "Эта наÑтройка применена в %{ancestor_group} и была переопределена в Ñтой подгруппе."
@@ -4915,7 +5804,7 @@ msgid "Groups with access to <strong>%{project_name}</strong>"
msgstr ""
msgid "GroupsDropdown|Frequently visited"
-msgstr ""
+msgstr "ЧаÑто поÑещаемые"
msgid "GroupsDropdown|Groups you visit often will appear here"
msgstr ""
@@ -4980,6 +5869,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5019,6 +5911,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5032,12 +5927,27 @@ msgstr[3] "Скрыть значениÑ"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "ИÑториÑ"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ОчиÑтка уÑпешно запущена"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5095,15 +6005,18 @@ msgstr "Идентификатор"
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5116,9 +6029,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "ЕÑли у Ð²Ð°Ñ ÑƒÐ¶Ðµ еÑÑ‚ÑŒ файлы иÑходных кодов вы можете отправить их Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÑÑылки %{link_to_cli} ниже."
-
msgid "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>."
msgstr ""
@@ -5203,12 +6113,30 @@ msgstr "Импорт репозиториÑ"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5224,15 +6152,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr "Инциденты"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5248,6 +6185,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5257,9 +6197,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5274,14 +6223,17 @@ msgstr[2] "ЭкземплÑры"
msgstr[3] "ЭкземплÑры"
msgid "Instance Statistics"
-msgstr ""
+msgstr "СтатиÑтика ЭкземплÑра"
msgid "Instance Statistics visibility"
-msgstr ""
+msgstr "ВидимоÑÑ‚ÑŒ СтатиÑтики ЭкземплÑра"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "ЭкземплÑÑ€ не поддерживает неÑколько клаÑтеров Kubernetes"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "Интеграции"
@@ -5312,6 +6264,21 @@ msgstr "Внедрение Цикла Ðналитик"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "ОбÑуждение"
@@ -5342,6 +6312,9 @@ msgstr ""
msgid "Issue events"
msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¾Ð±Ñуждений"
+msgid "Issue template (optional)"
+msgstr "Шаблон обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ (необÑзательно)"
+
msgid "IssueBoards|Board"
msgstr "ДоÑка"
@@ -5372,8 +6345,11 @@ msgstr "ОбÑуждениÑми могут быть ошибки, задачи
msgid "Issues closed"
msgstr "ОбÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð·Ð°ÐºÑ€Ñ‹Ñ‚Ñ‹"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "ОбÑуждениÑ, запроÑÑ‹ на ÑлиÑниÑ, отправки изменений и комментарии."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr ""
@@ -5382,10 +6358,10 @@ msgid "IssuesAnalytics|Issues Created"
msgstr ""
msgid "IssuesAnalytics|Issues created per month"
-msgstr ""
+msgstr "Создано обÑуждений за меÑÑц"
msgid "IssuesAnalytics|Last 12 months"
-msgstr ""
+msgstr "ПоÑледние 12 меÑÑцев"
msgid "IssuesAnalytics|Sorry, your filter produced no results"
msgstr ""
@@ -5417,12 +6393,30 @@ msgstr "Январь"
msgid "Job"
msgstr "Задание"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "Фоновое задание было удалено"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5453,6 +6447,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5468,9 +6465,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "Июл."
@@ -5513,9 +6522,15 @@ msgstr "Kubernetes наÑтроен"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Отключено"
@@ -5531,6 +6546,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} + ещё %{remainingLabelCount}"
@@ -5561,6 +6585,9 @@ msgstr "ПеренеÑти Метку"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5634,16 +6661,16 @@ msgstr "Подробнее о Kubernates"
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5655,6 +6682,9 @@ msgstr "Подробнее в|документации по раÑпиÑаниÑ
msgid "Leave"
msgstr "Покинуть"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Покинуть группу"
@@ -5739,9 +6769,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "Лицензии"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5761,6 +6797,9 @@ msgstr "СпиÑок репозиториев Gitea"
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5818,15 +6857,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Журналы"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5857,15 +6917,15 @@ msgstr "Управление метками проекта"
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr "МанифеÑÑ‚"
msgid "Manifest file import"
msgstr "Импорт файла манифеÑта"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5890,46 +6950,43 @@ msgstr "Отметить как Ñделанное"
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr "Включен режим Markdown"
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
-msgstr ""
+msgid "Markdown enabled"
+msgstr "Включен режим Markdown"
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5942,7 +6999,7 @@ msgid "Member lock"
msgstr ""
msgid "Member since %{date}"
-msgstr ""
+msgstr "УчаÑтник Ñ %{date}"
msgid "Members"
msgstr "УчаÑтники"
@@ -5953,21 +7010,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr ""
-
msgid "Merge Request"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "ЗапроÑÑ‹ на СлиÑние"
msgid "Merge Requests created"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние Ñоздан"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ ÑлиÑний"
@@ -5977,12 +7040,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "ЗапроÑÑ‹ на ÑлиÑние"
@@ -6052,18 +7124,21 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart}изменил опиÑание %{descriptionChangedTimes} раз, за поÑледние %{timeDifferenceMinutes}%{paragraphEnd}"
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
+msgid "MergeRequest|Filter files"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "MergeRequest|No files found"
msgstr ""
msgid "Merged"
msgstr "Слито"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "СообщениÑ"
@@ -6121,13 +7196,16 @@ msgstr "ÐÐ¾Ð²Ð°Ñ Ð¼ÐµÑ‚Ñ€Ð¸ÐºÐ°"
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6169,6 +7247,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Этап"
@@ -6214,6 +7298,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6232,6 +7319,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "добавить ключ SSH"
@@ -6241,6 +7331,9 @@ msgstr "Отмена"
msgid "Modal|Close"
msgstr "Закрыть"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6274,9 +7367,15 @@ msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ"
msgid "More information is available|here"
msgstr "Больше информации доÑтупно|тут"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "ПеремеÑтить"
@@ -6286,6 +7385,12 @@ msgstr "ПеремеÑтить обÑуждение"
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "ИмÑ"
@@ -6298,6 +7403,12 @@ msgstr ""
msgid "Name:"
msgstr "Ðаименование:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6353,21 +7464,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "Ðовое РаÑпиÑание Сборочной Линии"
msgid "New Snippet"
msgstr "Ðовый пример кода"
-msgid "New Snippets"
-msgstr "Ðовые примеры кода"
-
msgid "New branch"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°"
msgid "New branch unavailable"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ° недоÑтупна"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Ðовый каталог"
@@ -6383,6 +7497,9 @@ msgstr "Ðовый файл"
msgid "New group"
msgstr "ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6404,6 +7521,9 @@ msgstr ""
msgid "New project"
msgstr "Ðовый проект"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Ðовое раÑпиÑание"
@@ -6416,20 +7536,29 @@ msgstr "ÐÐ¾Ð²Ð°Ñ Ð¿Ð¾Ð´Ð³Ñ€ÑƒÐ¿Ð¿Ð°"
msgid "New tag"
msgstr "Ðовый тег"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
+msgstr "Ðовый..."
+
+msgid "Newly registered users will by default be external"
msgstr ""
msgid "No"
msgstr "Ðет"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "Ðет ответÑтвенного"
+msgid "No activities found"
+msgstr ""
msgid "No branches found"
msgstr ""
@@ -6458,7 +7587,7 @@ msgstr ""
msgid "No due date"
msgstr "Плановый Ñрок не указан"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6476,15 +7605,24 @@ msgstr "Файлы не найдены."
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "Ðет меток Ñ Ñ‚Ð°ÐºÐ¸Ð¼ наименованием или опиÑанием"
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6533,9 +7671,6 @@ msgstr ""
msgid "None"
msgstr "ПуÑто"
-msgid "Not allowed to merge"
-msgstr "СлиÑние не допуÑкаетÑÑ"
-
msgid "Not available"
msgstr "ÐедоÑтупно"
@@ -6551,11 +7686,17 @@ msgstr "Ðе конфиденциально"
msgid "Not enough data"
msgstr "ÐедоÑтаточно данных"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Обратите внимание, что маÑтер ветка автоматичеÑки защищена. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6668,6 +7809,9 @@ msgstr "ÐоÑбрь"
msgid "OK"
msgstr "ОК"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "Окт."
@@ -6680,6 +7824,12 @@ msgstr "Фильтр"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Еще один Ñлемент"
@@ -6687,6 +7837,9 @@ msgstr[1] "Еще %d Ñлементов"
msgstr[2] "Еще %d Ñлементов"
msgstr[3] "Еще %d Ñлементов"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Один или неÑколько проектов Bitbucket не могут быть импортировать в GitLab, потому что они иÑпользуют Subversion или Mercurial Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€ÑиÑми, а не Git."
@@ -6696,6 +7849,12 @@ msgstr "Один или неÑколько проектов Google Code не мÐ
msgid "Only admins"
msgstr "Только админиÑтраторы"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6732,6 +7891,9 @@ msgstr "Открыть в Xcode"
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6759,15 +7921,30 @@ msgstr "Операции"
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6786,9 +7963,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "Ð’ противном Ñлучае рекомендуетÑÑ Ð½Ð°Ñ‡Ð°Ñ‚ÑŒ Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ из перечиÑленных ниже вариантов."
-
msgid "Outbound requests"
msgstr "ИÑходÑщие запроÑÑ‹"
@@ -6810,6 +7984,12 @@ msgstr "Пакет был удален"
msgid "Packages"
msgstr "Пакеты"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Страницы"
@@ -6834,18 +8014,21 @@ msgstr "« ПерваÑ"
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr "ЧаÑÑ‚ÑŒ изменений запроÑа на ÑлиÑние"
msgid "Password"
msgstr "Пароль"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6855,6 +8038,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "Путь, перемещение, удаление"
@@ -6864,6 +8050,9 @@ msgstr "Путь:"
msgid "Pause"
msgstr "ПриоÑтановить"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6882,6 +8071,9 @@ msgstr "ÐžÐ¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ñти"
msgid "Permissions"
msgstr "Права доÑтупа"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "РазрешениÑ, LFS, 2FA"
@@ -6897,21 +8089,24 @@ msgstr ""
msgid "Pipeline"
msgstr "Ð¡Ð±Ð¾Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ"
-msgid "Pipeline Health"
-msgstr "РаботоÑпоÑобноÑÑ‚ÑŒ Сборочной Линии"
-
msgid "Pipeline Schedule"
msgstr "РаÑпиÑание Сборочной Линии"
msgid "Pipeline Schedules"
msgstr "РаÑпиÑÐ°Ð½Ð¸Ñ Ð¡Ð±Ð¾Ñ€Ð¾Ñ‡Ð½Ñ‹Ñ… Линий"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Ðеудача:"
@@ -6960,6 +8155,12 @@ msgstr "Переменные"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "ÐаÑтраиваемый"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "Сборочные линии"
@@ -6975,6 +8176,12 @@ msgstr "Сборочные линии за поÑледнюю неделю"
msgid "Pipelines for last year"
msgstr "Сборочные линии за поÑледний год"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7017,10 +8224,7 @@ msgstr "Этот проект в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð½Ðµ наÑтро
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7035,6 +8239,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr "ЗапуÑтить Ñборочную линию"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "ПоиÑк веток"
@@ -7062,9 +8269,21 @@ msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ оÑтановить Ñборочную ли
msgid "Pipeline|all"
msgstr "вÑе"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "уÑпех"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "Ñо Ñтадией"
@@ -7089,15 +8308,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "ПожалуйÑта, выберите по крайней мере один фильтр, чтобы увидеть результаты"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "ПожалуйÑта, решите reCAPTCHA"
@@ -7122,6 +8371,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7131,18 +8383,33 @@ msgstr "ПожалуйÑта, подождите пока мы импортирÑ
msgid "Preferences"
msgstr "ПредпочтениÑ"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Предварительный проÑмотр"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7170,6 +8437,9 @@ msgstr "Приватный - ДоÑтуп к проекту должен преÐ
msgid "Private - The group and its projects can only be viewed by members."
msgstr "ÐŸÑ€Ð¸Ð²Ð°Ñ‚Ð½Ð°Ñ - Группу и включённые в неё проекты могут видеть только члены группы."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Личные проекты могут быть Ñозданы в вашем перÑональном проÑтранÑтве Ñ:"
@@ -7320,21 +8590,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "Это не выглÑдит как публичный SSH-ключ, вы уверены, что вы хотите добавить его?"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7434,6 +8704,15 @@ msgstr ""
msgid "Project"
msgstr "Проект"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Проект '%{project_name}' находитÑÑ Ð² процеÑÑе удалениÑ."
@@ -7449,17 +8728,26 @@ msgstr "Проект '%{project_name}' уÑпешно обновлен."
msgid "Project Badges"
msgstr "Значки Проекта"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "ÐÐ´Ñ€ÐµÑ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°"
msgid "Project access must be granted explicitly to each user."
msgstr "ДоÑтуп к проекту должен предоÑтавлÑÑ‚ÑŒÑÑ Ñвно каждому пользователю."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Ðватар проекта"
-msgid "Project avatar in repository: %{link}"
-msgstr "Ðватар проекта в репозитории: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "Детали проекта"
@@ -7467,6 +8755,9 @@ msgstr "Детали проекта"
msgid "Project export could not be deleted."
msgstr "Ðевозможно удалить ÑкÑпорт проекта."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "ЭкÑпорт проекта удален."
@@ -7476,6 +8767,9 @@ msgstr "ИÑтек Ñрок дейÑÑ‚Ð²Ð¸Ñ ÑÑылки на проект. СÐ
msgid "Project export started. A download link will be sent by email."
msgstr "Ðачат ÑкÑпорт проекта. СÑылка Ð´Ð»Ñ ÑÐºÐ°Ñ‡Ð¸Ð²Ð°Ð½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ отправлена по Ñлектронной почте."
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7485,6 +8779,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7575,12 +8875,21 @@ msgstr ""
msgid "Projects"
msgstr "Проекты"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "ЧаÑто поÑещаемые"
@@ -7629,6 +8938,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7731,6 +9043,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7797,6 +9112,12 @@ msgstr "Публичный - Группу и включённые в неё пр
msgid "Public - The project can be accessed without any authentication."
msgstr "Публичный - ДоÑтуп к проекту возможен без какой-либо проверки подлинноÑти."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7809,6 +9130,12 @@ msgstr "Отправить"
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr "Отправить ÑущеÑтвующий репозиторий Git"
+
+msgid "Push an existing folder"
+msgstr "Отправить ÑущеÑтвующую папку"
+
msgid "Push events"
msgstr "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸"
@@ -7839,6 +9166,9 @@ msgstr "БыÑтрые дейÑÑ‚Ð²Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользоватьÑÑ
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "Подробнее"
@@ -7854,6 +9184,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7891,6 +9227,9 @@ msgstr "ЗарегиÑтрируйте и проÑмотрите ваши обр
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "РееÑÑ‚Ñ€"
@@ -7936,9 +9275,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "Удалить аватар"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7948,12 +9296,33 @@ msgstr ""
msgid "Remove project"
msgstr "Удалить проект"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7972,6 +9341,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8005,6 +9377,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8023,6 +9407,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8035,6 +9425,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8071,6 +9464,23 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
msgstr ""
@@ -8104,9 +9514,18 @@ msgstr "Закрыть диÑкуÑÑию"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr "Метрики откликов (AWS ELB)"
@@ -8131,12 +9550,18 @@ msgstr ""
msgid "Resume"
msgstr "Продолжить"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Повторить"
msgid "Retry this job"
msgstr "Повторить Ñто фоновое задание"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8171,12 +9596,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8198,6 +9632,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8231,27 +9671,27 @@ msgstr ""
msgid "Running"
msgstr "ВыполнÑетÑÑ"
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr ""
-
msgid "SSH Keys"
msgstr "SSH-ключи"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr ""
@@ -8261,6 +9701,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "Сохранить"
@@ -8285,6 +9728,9 @@ msgstr "Сохранить раÑпиÑание Ñборочной лини"
msgid "Save variables"
msgstr "Сохранить переменные"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "РаÑпиÑание новой Ñборочной линии"
@@ -8294,15 +9740,24 @@ msgstr "Запланировано"
msgid "Schedules"
msgstr "РаÑпиÑаниÑ"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Планирование Сборочных Линий"
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8343,7 +9798,7 @@ msgid "Search or filter results..."
msgstr ""
msgid "Search or jump to…"
-msgstr ""
+msgstr "Ðайти или перейти к…"
msgid "Search project"
msgstr "ПоиÑк проекта"
@@ -8354,6 +9809,9 @@ msgstr ""
msgid "Search users"
msgstr "ПоиÑк пользователей"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8381,6 +9839,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8402,9 +9869,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8420,10 +9884,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8474,6 +9938,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8483,9 +9956,6 @@ msgstr "Выбор временной зоны"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Выбрать ÑущеÑтвующий клаÑтер Kubernetes или Ñоздать новый"
-msgid "Select assignee"
-msgstr "Выбрать ответÑтвенного"
-
msgid "Select branch/tag"
msgstr "Выбрать ветку/тег"
@@ -8516,12 +9986,15 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
-msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
msgstr ""
-msgid "Selective synchronization"
+msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
+msgid "Send an email notification to Developers."
+msgstr "Отправить email уведомление Разработчикам."
+
msgid "Send email"
msgstr "Отправить Ñлектронное пиÑьмо"
@@ -8537,18 +10010,39 @@ msgstr ""
msgid "Sep"
msgstr "Сент."
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "СентÑбрь"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "ВерÑÐ¸Ñ Ñервера"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8564,9 +10058,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8594,9 +10085,18 @@ msgstr "Шаблоны Служб"
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr "ПродолжительноÑÑ‚ÑŒ ÑеÑÑии (в минутах)"
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "УÑтановите пароль в Ñвоем аккаунте, чтобы отправлÑÑ‚ÑŒ или получать код через %{protocol}."
@@ -8612,6 +10112,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr "УÑтановить макÑимальное Ð²Ñ€ÐµÐ¼Ñ ÑеанÑа Ð´Ð»Ñ Ð²ÐµÐ±-терминала."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "ÐаÑтроить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной почте Ð´Ð»Ñ ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾ злоупотреблениÑÑ…."
@@ -8621,6 +10124,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "ÐаÑтройка CI/CD"
@@ -8636,6 +10151,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8643,16 +10161,16 @@ msgid "SetPasswordToCloneLink|set a password"
msgstr "уÑтановите пароль"
msgid "SetStatusModal|Add status emoji"
-msgstr ""
+msgstr "Добавить Ñмайл к ÑтатуÑу"
msgid "SetStatusModal|Clear status"
-msgstr ""
+msgstr "ОчиÑтить ÑтатуÑ"
msgid "SetStatusModal|Edit status"
-msgstr ""
+msgstr "Изменить ÑтатуÑ"
msgid "SetStatusModal|Remove status"
-msgstr "Удалить ÑтатуÑ"
+msgstr ""
msgid "SetStatusModal|Set a status"
msgstr ""
@@ -8693,9 +10211,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "Показать команду"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8705,6 +10229,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Показать родительÑкие Ñтраницы"
@@ -8766,6 +10293,12 @@ msgstr "ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð²Ñ…Ð¾Ð´Ð°"
msgid "Sign-up restrictions"
msgstr "ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ñ€ÐµÐ³Ð¸Ñтрации"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8775,6 +10308,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr "ÐаÑтройки размера и доменных имён Ð´Ð»Ñ ÑтатичеÑких веб-Ñайтов"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8796,6 +10332,27 @@ msgstr ""
msgid "Snippets"
msgstr "Примеры кода"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8841,6 +10398,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8868,6 +10428,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "Произошла ошибка. Попробуйте позже."
@@ -9018,6 +10581,9 @@ msgstr "Спам Логи"
msgid "Spam and Anti-bot Protection"
msgstr "Защита от Ñпама и ботов"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Конкретные обработчики заданий"
@@ -9066,15 +10632,27 @@ msgstr "ÐктивноÑÑ‚ÑŒ в избранных проектах"
msgid "Starred projects"
msgstr "Избранные проекты"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "Ðачать %{new_merge_request} Ñ Ñтих изменений"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Ðачать рецензию"
@@ -9108,6 +10686,9 @@ msgstr "Запущен"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9261,6 +10842,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9270,15 +10872,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Переключить ветка/тег"
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "СиÑтемные Обработчики"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ ÑиÑтеме"
@@ -9288,6 +10899,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9411,12 +11025,25 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9435,6 +11062,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9447,15 +11080,63 @@ msgstr "ÐšÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ñобытий добавленных в данные
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "СвÑзь Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ удалена."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Импорт будет отключен поÑле %{timeout}. Ð”Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸ÐµÐ², которые импортируютÑÑ Ð·Ð° большее времÑ, иÑпользуйте комбинацию команд clone/push."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "Ð¡Ñ‚Ð°Ð´Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°ÐµÑ‚ времÑ, которое потребуетÑÑ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð¾ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñуждению вехи, или Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ð±ÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð½Ð° вашу доÑку задач. Ðачните Ñоздавать обÑуждениÑ, чтобы увидеть ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтой Ñтадии."
@@ -9465,6 +11146,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "МакÑимально допуÑтимый размер файла ÑоÑтавлÑет 200 Кб."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9492,9 +11185,18 @@ msgstr "ДоÑтуп к проекту возможен любым зарегиÑ
msgid "The project can be accessed without any authentication."
msgstr "ДоÑтуп к проекту возможен без какой-либо проверки подлинноÑти."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Репозиторий Ð´Ð»Ñ Ñтого проекта не ÑущеÑтвует."
@@ -9513,6 +11215,21 @@ msgstr "Этап обзора показывает Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ ÑозданÐ
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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\" и развертыванием кода в производÑтвенной Ñреде. Данные будут автоматичеÑки добавлены поÑле Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð² производÑтве первый раз."
@@ -9528,9 +11245,18 @@ msgstr "ВремÑ, затраченное каждым Ñлементом, Ñо
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9585,9 +11311,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Произошла ошибка при Ñохранении наÑтроек уведомлений."
@@ -9603,6 +11341,9 @@ msgstr "При подпиÑке на Ñту метку произошла оши
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9621,6 +11362,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Этот ÑкземплÑÑ€ GitLab пока не предоÑтавлÑет никаких общих обработчиков заданий. ÐдминиÑтраторы ÑкземплÑров могут региÑтрировать общие обработчики заданий в Ñекции админиÑтрированиÑ."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9633,6 +11377,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9654,6 +11413,9 @@ msgstr "Этот каталог"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9669,6 +11431,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr "Это первый Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние от автора в Ñтот проект."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Это обÑуждение ÑвлÑетÑÑ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ñ‹Ð¼"
@@ -9717,6 +11482,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Это фоновое задание находитÑÑ Ð² ÑоÑтоÑнии Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð¸ ожидает запуÑка процеÑÑа иÑполнениÑ"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9738,10 +11509,16 @@ msgstr "Это означает, что вы не можете отправитÑ
msgid "This merge request is locked."
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние заблокирован."
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9750,7 +11527,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9774,6 +11551,9 @@ msgstr ""
msgid "This repository"
msgstr "Этот репозиторий"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9783,10 +11563,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9801,6 +11581,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9867,9 +11650,6 @@ msgstr "%s меÑÑцев назад"
msgid "Timeago|%s months remaining"
msgstr "ОÑталоÑÑŒ %s меÑÑцев"
-msgid "Timeago|%s seconds ago"
-msgstr "%s Ñекунд назад"
-
msgid "Timeago|%s seconds remaining"
msgstr "ОÑталоÑÑŒ %s Ñекунд(Ñ‹)"
@@ -10103,6 +11883,9 @@ msgstr "СегоднÑ"
msgid "Todo"
msgstr "Дела"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10142,6 +11925,12 @@ msgstr "Завтра"
msgid "Too many changes to show."
msgstr "Слишком много изменений Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Общий вклад"
@@ -10166,6 +11955,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10178,6 +11970,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "ЗапуÑтить Ñто дейÑтвие вручную"
@@ -10187,6 +11982,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10211,6 +12018,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10220,15 +12030,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Ðе удаетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ отличиÑ. %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10241,6 +12063,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "Разблокировать"
@@ -10301,21 +12126,39 @@ msgstr ""
msgid "Update"
msgstr "Обновить"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Обновить наименование вашей группы, её опиÑание, аватар и видимоÑÑ‚ÑŒ."
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10331,6 +12174,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "кликните Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "ГолоÑа \"за\""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr "СтатиÑтика иÑпользованиÑ"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10397,35 +12255,71 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
-msgid "UserProfile|Activity"
+msgid "User settings"
msgstr ""
-msgid "UserProfile|Already reported for abuse"
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
msgstr ""
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
+msgid "UserProfile|Activity"
+msgstr "ÐктивноÑÑ‚ÑŒ"
+
+msgid "UserProfile|Already reported for abuse"
+msgstr "Уже Ñообщили о нарушении"
+
msgid "UserProfile|Contributed projects"
msgstr "Вклад в проекты"
msgid "UserProfile|Edit profile"
-msgstr ""
+msgstr "Изменить профиль"
msgid "UserProfile|Explore public groups to find projects to contribute to."
msgstr ""
msgid "UserProfile|Groups"
-msgstr ""
+msgstr "Группы"
msgid "UserProfile|Groups are the best way to manage projects and members."
msgstr ""
@@ -10434,19 +12328,19 @@ msgid "UserProfile|Join or create a group to start contributing by commenting on
msgstr ""
msgid "UserProfile|Most Recent Activity"
-msgstr ""
+msgstr "ÐедавнÑÑ ÐктивноÑÑ‚ÑŒ"
msgid "UserProfile|No snippets found."
msgstr ""
msgid "UserProfile|Overview"
-msgstr ""
+msgstr "Обзор"
msgid "UserProfile|Personal projects"
-msgstr ""
+msgstr "ПерÑональные проекты"
msgid "UserProfile|Report abuse"
-msgstr ""
+msgstr "Сообщить о нарушении"
msgid "UserProfile|Snippets"
msgstr "Примеры кода"
@@ -10455,22 +12349,22 @@ msgid "UserProfile|Snippets in GitLab can either be private, internal, or public
msgstr ""
msgid "UserProfile|Subscribe"
-msgstr ""
+msgstr "ПодпиÑатьÑÑ"
msgid "UserProfile|This user doesn't have any personal projects"
msgstr ""
msgid "UserProfile|This user has a private profile"
-msgstr ""
+msgstr "У Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ñ‹Ð¹ профиль"
msgid "UserProfile|This user hasn't contributed to any projects"
msgstr ""
msgid "UserProfile|View all"
-msgstr ""
+msgstr "Показать вÑе"
msgid "UserProfile|View user in admin area"
-msgstr ""
+msgstr "ПроÑмотр Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² панели админиÑтрированиÑ"
msgid "UserProfile|You can create a group for several dependent projects."
msgstr ""
@@ -10487,18 +12381,39 @@ msgstr ""
msgid "Users"
msgstr "Пользователи"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10523,9 +12438,6 @@ msgstr "Проверено"
msgid "Version"
msgstr "ВерÑиÑ"
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10535,7 +12447,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10547,12 +12459,15 @@ msgstr "ПроÑмотр файла @ "
msgid "View group labels"
msgstr "ПроÑмотр меток группы"
-msgid "View issue"
-msgstr "ПроÑмотр обÑуждениÑ"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10586,6 +12501,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10622,12 +12540,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10676,6 +12600,12 @@ msgstr ""
msgid "Web terminal"
msgstr "Web терминал"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Веб-обработчики позволÑÑŽÑ‚ вам вызывать Ð°Ð´Ñ€ÐµÑ URL еÑли, например, отправлен новый код или Ñоздано новое обÑуждение. Ð’Ñ‹ можете наÑтроить веб-обработчики так, чтобы они реагировали на определённые ÑобытиÑ, такие как отправки кода, обÑÑƒÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ запроÑÑ‹ на ÑлиÑние. Групповые веб-обработчики применÑÑŽÑ‚ÑÑ ÐºÐ¾ вÑем проектам в группе и позволÑÑŽÑ‚ вам Ñтандартизовать функциональноÑÑ‚ÑŒ веб-обработчиков Ð´Ð»Ñ Ð²Ñей вашей группы."
@@ -10694,9 +12624,19 @@ msgstr "Когда процеÑÑ Runner заблокирован, он не мÐ
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
msgstr ""
@@ -10709,6 +12649,9 @@ msgstr ""
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Клонировать wiki"
@@ -10820,6 +12763,9 @@ msgstr "Создать Страницу"
msgid "Wiki|Create page"
msgstr "Создать Ñтраницу"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Редактировать Ñтраницу"
@@ -10838,6 +12784,9 @@ msgstr "ВерÑÐ¸Ñ Ñтраницы"
msgid "Wiki|Pages"
msgstr "Страницы"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Вики Страницы"
@@ -10850,12 +12799,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "Да"
@@ -10886,6 +12847,9 @@ msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ удалить ÑвÑзь ответвлен
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ передать проект %{project_full_name} другому владельцу. Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "Ð’Ñ‹ иÑпользуете GitLab в режиме только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ."
@@ -10901,6 +12865,15 @@ msgstr "Ð’Ñ‹ также можете Ñоздать проект из коман
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr "Ð’Ñ‹ также можете загрузить ÑущеÑтвующие файлы Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¿Ñ€Ð¸Ð²ÐµÐ´Ñ‘Ð½Ð½Ñ‹Ðµ ниже инÑтрукции."
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr "Ð’Ñ‹ можете Ñоздавать файлы непоÑредÑтвенно в GitLab, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¾Ð´Ð½Ñƒ из Ñледующих возможноÑтей."
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Ð’Ñ‹ можете легко внеÑти Ñвой вклад в них, попроÑив приÑоединить Ð²Ð°Ñ Ðº Ñтим группам."
@@ -10910,6 +12883,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr "Ð’Ñ‹ можете перемещатьÑÑ Ð¿Ð¾ диаграмме Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ клавиш Ñо Ñтрелками."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Ð’Ñ‹ можете добавлÑÑ‚ÑŒ только файлы, когда находитеÑÑŒ в ветке"
@@ -10919,6 +12898,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10928,12 +12910,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr "Ð’Ñ‹ не можете запиÑывать на Ñтот ÑкземплÑÑ€ \"только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ\" клаÑтера GitLab."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10952,12 +12952,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "Ð’Ñ‹ доÑтигли Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð² вашем проекте"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10970,7 +12985,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10982,12 +12997,24 @@ msgstr "Вам нужно разрешение."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "Ð’Ñ‹ не получите никаких уведомлений по Ñлектронной почте"
@@ -11015,6 +13042,15 @@ msgstr "Ð’Ñ‹ не Ñможете работать Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð¾Ð¼ через
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð³Ð¾ ÑÑ€Ð°Ð²Ð½ÐµÐ½Ð¸Ñ Ð²Ð°Ð¼ нужно иÑпользовать разные имена веток."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11048,6 +13084,15 @@ msgstr "Ваши дела"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11066,9 +13111,15 @@ msgstr "Ваши Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð¸ зафикÑированы. КомÐ
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Ваш комментарий не будет виден вÑем."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11084,15 +13135,30 @@ msgstr ""
msgid "Your name"
msgstr "Ваше имÑ"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "Ваши проекты"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11102,12 +13168,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
-msgstr "назначить ÑебÑ"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "Ð¸Ð¼Ñ Ð²ÐµÑ‚Ð²Ð¸"
@@ -11132,6 +13201,20 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11139,6 +13222,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11146,6 +13232,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11181,6 +13276,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11190,6 +13288,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11199,12 +13306,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11247,9 +13366,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11297,9 +13425,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11321,6 +13446,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11339,9 +13467,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11355,10 +13492,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11379,7 +13537,7 @@ msgstr ""
msgid "day"
msgid_plural "days"
msgstr[0] "день"
-msgstr[1] "дней"
+msgstr[1] "днÑ"
msgstr[2] "дней"
msgstr[3] "дней"
@@ -11389,6 +13547,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "отключено"
@@ -11424,6 +13585,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr ""
@@ -11433,15 +13609,9 @@ msgstr ""
msgid "group"
msgstr ""
-msgid "help"
-msgstr "помощь"
-
msgid "here"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr ""
@@ -11506,6 +13676,9 @@ msgstr ""
msgid "latest version"
msgstr "поÑледнÑÑ Ð²ÐµÑ€ÑиÑ"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11528,6 +13701,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11564,6 +13740,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr "Одобрить"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Одобрено"
@@ -11636,17 +13815,20 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние одобрен"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние одобрен; вы можете дополнительно одобрить"
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "Одобрение не требуетÑÑ, Ñ…Ð¾Ñ‚Ñ Ð²Ñ‹ можете Ñто делать"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr ""
@@ -11701,6 +13883,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11817,12 +14002,22 @@ msgstr "пароль"
msgid "personal access token"
msgstr "токен Ð´Ð»Ñ Ð¿ÐµÑ€Ñонального доÑтупа"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "проект"
@@ -11858,9 +14053,36 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11885,6 +14107,9 @@ msgstr "запущено"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sk_SK/gitlab.po b/locale/sk_SK/gitlab.po
index a76359352a9..9a02c0d0f60 100644
--- a/locale/sk_SK/gitlab.po
+++ b/locale/sk_SK/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sk\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:09\n"
+"PO-Revision-Date: 2019-04-12 12:32\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -44,8 +53,12 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -156,12 +169,28 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -182,7 +211,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -191,6 +220,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -200,6 +235,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -212,6 +250,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -261,6 +308,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -270,6 +320,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -296,6 +349,13 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -310,6 +370,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -359,6 +422,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -368,6 +434,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -383,6 +458,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -416,7 +494,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -428,18 +518,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -470,12 +581,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -485,6 +602,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -515,34 +638,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -566,18 +716,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -614,6 +773,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -695,12 +857,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -714,6 +885,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -723,15 +897,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -744,6 +936,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -762,6 +960,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -783,9 +984,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -795,6 +993,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -807,18 +1014,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -831,6 +1047,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -843,6 +1062,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -858,6 +1080,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -900,12 +1125,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -963,6 +1194,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -975,15 +1209,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -996,6 +1248,52 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -1011,15 +1309,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1032,15 +1345,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1053,6 +1375,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1107,9 +1432,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1125,6 +1447,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1140,10 +1465,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1152,6 +1477,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1170,6 +1501,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1209,16 +1543,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1344,6 +1672,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1581,6 +1915,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1602,9 +1942,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1623,6 +1972,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1656,6 +2008,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1671,6 +2026,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1686,6 +2044,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1698,19 +2059,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1737,6 +2113,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1749,6 +2128,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1776,9 +2161,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1800,25 +2182,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1845,6 +2227,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1878,6 +2263,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1893,6 +2281,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1905,6 +2296,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1914,12 +2308,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1977,6 +2377,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1992,9 +2395,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2034,16 +2434,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2082,6 +2479,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,19 +2494,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2151,7 +2554,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2178,13 +2581,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2193,15 +2596,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2229,7 +2635,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2286,9 +2689,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2301,10 +2701,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2313,15 +2713,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2352,7 +2749,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,16 +2785,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2409,7 +2806,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2418,10 +2815,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2460,9 +2863,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2472,6 +2872,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2481,15 +2884,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2587,6 +3002,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2656,6 +3074,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2671,12 +3095,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2740,6 +3176,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2767,18 +3206,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2791,6 +3221,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2815,9 +3248,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2833,9 +3263,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2845,10 +3299,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2860,6 +3314,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2884,9 +3341,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2956,6 +3410,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2995,9 +3452,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3040,6 +3494,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3064,15 +3521,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3112,6 +3578,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3124,6 +3593,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3137,6 +3621,9 @@ msgstr[3] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3275,12 +3762,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3359,6 +3879,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3398,6 +3921,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3443,6 +3972,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3464,6 +3996,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3476,18 +4011,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3503,6 +4074,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3530,6 +4104,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3539,9 +4119,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3557,16 +4134,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,13 +4173,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3611,6 +4194,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3662,15 +4251,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3683,6 +4290,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3734,6 +4353,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3782,6 +4404,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3797,12 +4431,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3866,12 +4536,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3881,12 +4545,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3920,6 +4590,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3938,9 +4614,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3962,13 +4635,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3995,12 +4689,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -4034,9 +4734,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4094,7 +4791,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4121,6 +4818,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4185,9 +4894,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4203,6 +4909,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4296,13 +5005,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4332,12 +5047,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4347,6 +5071,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4371,9 +5098,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4392,6 +5116,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4437,6 +5164,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4545,9 +5275,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4563,6 +5308,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4593,6 +5341,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4632,24 +5386,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4668,9 +5443,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4692,6 +5464,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4713,6 +5488,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4767,15 +5551,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4794,9 +5587,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4806,6 +5608,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4839,6 +5644,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4857,6 +5665,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4866,6 +5677,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4875,18 +5752,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4980,6 +5869,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -5019,6 +5911,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -5032,12 +5927,27 @@ msgstr[3] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5095,15 +6005,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5116,9 +6029,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5203,12 +6113,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5224,15 +6152,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5248,6 +6185,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5257,9 +6197,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5282,6 +6231,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5312,6 +6264,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5342,6 +6312,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5372,7 +6345,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5417,12 +6393,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5453,6 +6447,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5468,9 +6465,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5513,9 +6522,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5531,6 +6546,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5561,6 +6585,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5634,16 +6661,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5655,6 +6682,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5739,9 +6769,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5761,6 +6797,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5818,15 +6857,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5857,15 +6917,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5890,46 +6950,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5953,10 +7010,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5965,9 +7022,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5977,12 +7040,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6052,16 +7124,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6121,13 +7196,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6169,6 +7247,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6214,6 +7298,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6232,6 +7319,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6241,6 +7331,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6274,9 +7367,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6286,6 +7385,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6298,6 +7403,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6353,13 +7464,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6368,6 +7479,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6383,6 +7497,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6404,6 +7521,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6416,19 +7536,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6458,7 +7587,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6476,15 +7605,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6533,9 +7671,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6551,10 +7686,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6668,6 +7809,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6680,6 +7824,12 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -6687,6 +7837,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6696,6 +7849,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6732,6 +7891,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6759,15 +7921,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6786,9 +7963,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6810,6 +7984,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6834,18 +8014,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6855,6 +8038,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6864,6 +8050,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6882,6 +8071,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6897,21 +8089,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6960,6 +8155,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6975,6 +8176,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -7017,10 +8224,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -7035,6 +8239,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7062,9 +8269,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7089,15 +8308,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7122,6 +8371,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7131,18 +8383,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7170,6 +8437,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7320,21 +8590,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7434,6 +8704,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7449,16 +8728,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7467,6 +8755,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7476,6 +8767,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7485,6 +8779,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7575,12 +8875,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7629,6 +8938,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7731,6 +9043,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7797,6 +9112,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7809,6 +9130,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7839,6 +9166,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7854,6 +9184,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7891,6 +9227,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7936,9 +9275,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7948,12 +9296,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7972,6 +9341,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8005,6 +9377,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -8023,6 +9407,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -8035,6 +9425,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8071,6 +9464,23 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
msgstr ""
@@ -8104,9 +9514,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8131,12 +9550,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8171,12 +9596,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8198,6 +9632,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8231,25 +9671,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8261,6 +9701,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8285,6 +9728,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8294,15 +9740,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8354,6 +9809,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8381,6 +9839,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8402,9 +9869,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8420,10 +9884,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8474,6 +9938,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8483,9 +9956,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8516,10 +9986,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8537,18 +10010,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8564,9 +10058,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8594,9 +10085,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8612,6 +10112,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8621,6 +10124,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8636,6 +10151,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8693,9 +10211,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8705,6 +10229,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8766,6 +10293,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8775,6 +10308,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8796,6 +10332,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8841,6 +10398,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8868,6 +10428,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -9018,6 +10581,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9066,15 +10632,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9108,6 +10686,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9261,6 +10842,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9270,15 +10872,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9288,6 +10899,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9411,12 +11025,25 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9435,6 +11062,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9447,15 +11080,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9465,6 +11146,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9492,9 +11185,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9513,6 +11215,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9528,9 +11245,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9585,9 +11311,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9603,6 +11341,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9621,6 +11362,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9633,6 +11377,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9654,6 +11413,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9669,6 +11431,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9717,6 +11482,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9738,10 +11509,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9750,7 +11527,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9774,6 +11551,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9783,10 +11563,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9801,6 +11581,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9867,9 +11650,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10103,6 +11883,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10142,6 +11925,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10166,6 +11955,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10178,6 +11970,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10187,6 +11982,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10211,6 +12018,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10220,15 +12030,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10241,6 +12063,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10301,21 +12126,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10331,6 +12174,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10397,18 +12255,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10487,18 +12381,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10523,9 +12438,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10535,7 +12447,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10547,12 +12459,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10586,6 +12501,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10622,12 +12540,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10676,6 +12600,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10694,9 +12624,19 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
msgstr ""
@@ -10709,6 +12649,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10820,6 +12763,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10838,6 +12784,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10850,12 +12799,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10886,6 +12847,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10901,6 +12865,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10910,6 +12883,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10919,6 +12898,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10928,12 +12910,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10952,12 +12952,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10970,7 +12985,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10982,12 +12997,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -11015,6 +13042,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11048,6 +13084,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11066,9 +13111,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11084,15 +13135,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11102,12 +13168,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11132,6 +13201,20 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
@@ -11139,6 +13222,9 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
@@ -11146,6 +13232,15 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11181,6 +13276,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11190,6 +13288,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11199,12 +13306,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11247,9 +13366,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11297,9 +13425,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11321,6 +13446,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11339,9 +13467,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11355,10 +13492,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11389,6 +13547,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11424,22 +13585,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11506,6 +13676,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11528,6 +13701,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11564,6 +13740,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11636,16 +13815,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11701,6 +13883,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11817,12 +14002,22 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11858,9 +14053,36 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11885,6 +14107,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index 3774bd3ad14..4ef535ebf6f 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sq\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:07\n"
+"PO-Revision-Date: 2019-04-12 12:28\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sr_CS/gitlab.po b/locale/sr_CS/gitlab.po
index 85059aa0f90..6a7a71426ef 100644
--- a/locale/sr_CS/gitlab.po
+++ b/locale/sr_CS/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sr-CS\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:09\n"
+"PO-Revision-Date: 2019-04-12 12:31\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -42,8 +51,11 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -141,12 +153,27 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -165,7 +192,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -174,6 +201,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -183,6 +216,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -195,6 +231,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -240,6 +285,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -249,6 +297,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -273,6 +324,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -285,6 +342,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -327,6 +387,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -336,6 +399,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -351,6 +423,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -384,7 +459,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -396,18 +483,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -438,12 +546,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -453,6 +567,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -483,34 +603,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -534,18 +681,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -582,6 +738,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -663,12 +822,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -681,6 +849,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -690,15 +861,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -711,6 +900,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -729,6 +924,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -750,9 +948,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -762,6 +957,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -774,18 +978,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -798,6 +1011,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -810,6 +1026,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -825,6 +1044,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -867,12 +1089,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -930,6 +1158,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -942,15 +1173,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -963,6 +1212,48 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -978,15 +1269,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -999,15 +1305,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1020,6 +1335,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1074,9 +1392,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1092,6 +1407,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1107,10 +1425,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1119,6 +1437,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1137,6 +1461,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1176,16 +1503,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1311,6 +1632,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1548,6 +1875,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1569,9 +1902,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1590,6 +1932,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1623,6 +1968,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1638,6 +1986,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1653,6 +2004,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1665,19 +2019,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1704,6 +2073,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1716,6 +2088,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1743,9 +2121,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1767,25 +2142,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1812,6 +2187,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1845,6 +2223,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1860,6 +2241,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1872,6 +2256,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1881,12 +2268,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1944,6 +2337,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1959,9 +2355,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1977,15 +2370,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2001,16 +2394,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2049,6 +2439,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2058,19 +2454,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2118,7 +2514,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2145,13 +2541,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2160,15 +2556,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2196,7 +2595,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2208,9 +2607,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2253,9 +2649,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2268,10 +2661,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2280,15 +2673,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2319,7 +2709,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2355,16 +2745,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2376,7 +2766,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2385,10 +2775,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2427,9 +2823,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2439,6 +2832,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2448,15 +2844,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2553,6 +2961,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2622,6 +3033,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2637,12 +3054,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2706,6 +3135,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2733,18 +3165,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2757,6 +3180,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2781,9 +3207,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2799,9 +3222,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2811,10 +3258,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2826,6 +3273,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2850,9 +3300,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2922,6 +3369,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2961,9 +3411,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3006,6 +3453,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3030,15 +3480,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3078,6 +3537,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3090,6 +3552,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3102,6 +3579,9 @@ msgstr[2] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3240,12 +3720,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3324,6 +3837,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3363,6 +3879,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3408,6 +3930,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3429,6 +3954,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3441,18 +3969,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3468,6 +4032,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3495,6 +4062,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3504,9 +4077,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3522,16 +4092,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3549,13 +4131,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3576,6 +4152,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3627,15 +4209,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3648,6 +4248,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3699,6 +4311,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3747,6 +4362,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3762,12 +4389,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3831,12 +4494,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3846,12 +4503,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3885,6 +4548,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3903,9 +4572,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3927,13 +4593,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3945,9 +4617,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3960,12 +4647,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3999,9 +4692,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4059,7 +4749,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4086,6 +4776,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4149,9 +4851,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4167,6 +4866,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4260,13 +4962,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4296,12 +5004,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4311,6 +5028,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4335,9 +5055,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4356,6 +5073,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4401,6 +5121,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4509,9 +5232,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4527,6 +5265,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4557,6 +5298,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4596,24 +5343,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4632,9 +5400,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4656,6 +5421,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4677,6 +5445,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4692,12 +5463,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4731,15 +5508,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4758,9 +5544,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4770,6 +5565,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4803,6 +5601,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4821,6 +5622,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4830,6 +5634,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4839,18 +5709,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4944,6 +5826,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4983,6 +5868,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4995,12 +5883,27 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5058,15 +5961,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5079,9 +5985,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5166,12 +6069,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5187,15 +6108,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5211,6 +6141,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5220,9 +6153,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5244,6 +6186,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5274,6 +6219,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5292,6 +6252,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5304,6 +6267,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5334,7 +6300,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5379,12 +6348,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5415,6 +6402,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5430,9 +6420,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5475,9 +6477,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5493,6 +6501,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5523,6 +6540,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5595,16 +6615,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5616,6 +6636,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5700,9 +6723,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5721,6 +6750,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5778,15 +6810,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5817,15 +6870,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5850,46 +6903,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5913,10 +6963,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5925,9 +6975,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5937,12 +6993,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6012,16 +7077,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6081,13 +7149,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6129,6 +7200,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6174,6 +7251,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6192,6 +7272,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6201,6 +7284,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6234,9 +7320,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6246,6 +7338,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6258,6 +7356,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6312,13 +7416,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6327,6 +7431,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6342,6 +7449,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6363,6 +7473,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6375,19 +7488,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6417,7 +7539,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6435,15 +7557,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6492,9 +7623,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6510,10 +7638,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6627,6 +7761,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6639,12 +7776,21 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6654,6 +7800,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6690,6 +7842,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6717,15 +7872,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6744,9 +7914,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6768,6 +7935,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6792,18 +7965,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6813,6 +7989,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6822,6 +8001,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6840,6 +8022,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6855,21 +8040,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6918,6 +8106,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6933,6 +8127,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6975,10 +8175,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6993,6 +8190,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7020,9 +8220,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7047,15 +8259,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7065,9 +8295,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7080,6 +8322,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7089,18 +8334,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7128,6 +8388,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7278,21 +8541,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7392,6 +8655,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7407,16 +8679,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7425,6 +8706,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7434,6 +8718,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7443,6 +8730,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7533,12 +8826,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7587,6 +8889,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7689,6 +8994,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7755,6 +9063,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7767,6 +9081,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7797,6 +9117,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7812,6 +9135,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7848,6 +9177,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7893,9 +9225,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7905,12 +9246,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7929,6 +9291,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7962,6 +9327,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7980,6 +9357,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7992,6 +9375,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8028,6 +9414,21 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "Resend invite"
msgstr ""
@@ -8061,9 +9462,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8088,12 +9498,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8127,12 +9543,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8154,6 +9579,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8187,25 +9618,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8217,6 +9648,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8241,6 +9675,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8250,15 +9687,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8310,6 +9756,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8337,6 +9786,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8358,9 +9816,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8376,10 +9831,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8430,6 +9885,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8439,9 +9903,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8472,10 +9933,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8493,18 +9957,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8520,9 +10005,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8550,9 +10032,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8568,6 +10059,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8577,6 +10071,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8592,6 +10098,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8649,9 +10158,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8661,6 +10176,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8721,6 +10239,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8730,6 +10254,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8751,6 +10278,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8796,6 +10344,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8823,6 +10374,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8973,6 +10527,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9021,15 +10578,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9063,6 +10632,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9216,6 +10788,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9225,15 +10818,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9243,6 +10845,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9366,12 +10971,24 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9390,6 +11007,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9402,15 +11025,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9420,6 +11091,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9447,9 +11130,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9468,6 +11160,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9483,9 +11190,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9540,9 +11256,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9558,6 +11286,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9576,6 +11307,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9588,6 +11322,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9609,6 +11358,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9624,6 +11376,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9672,6 +11427,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9693,10 +11454,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9705,7 +11472,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9729,6 +11496,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9738,10 +11508,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9756,6 +11526,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9822,9 +11595,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10056,6 +11826,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10095,6 +11868,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10119,6 +11898,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10131,6 +11913,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10140,6 +11925,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10164,6 +11961,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10173,15 +11973,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10194,6 +12006,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10254,21 +12069,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10284,6 +12117,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10308,18 +12144,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10350,18 +12198,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10440,18 +12324,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10476,9 +12381,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10488,7 +12390,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10500,12 +12402,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10539,6 +12444,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10575,12 +12483,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10629,6 +12543,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10647,9 +12567,18 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "When:"
msgstr ""
@@ -10662,6 +12591,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10773,6 +12705,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10791,6 +12726,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10803,12 +12741,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10839,6 +12789,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10854,6 +12807,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10863,6 +12825,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10872,6 +12840,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10881,12 +12852,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10905,12 +12894,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10923,7 +12927,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10935,12 +12939,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10968,6 +12984,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11001,6 +13026,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11019,9 +13053,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11037,15 +13077,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11055,12 +13110,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11085,18 +13143,42 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11130,6 +13212,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11139,6 +13224,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11148,12 +13242,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11196,9 +13302,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11244,9 +13359,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11268,6 +13380,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11286,9 +13401,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11301,10 +13425,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11334,6 +13479,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11367,22 +13515,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11448,6 +13605,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11469,6 +13629,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11505,6 +13668,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11577,16 +13743,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11640,6 +13809,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11754,12 +13926,21 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11793,9 +13974,36 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11820,6 +14028,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sr_SP/gitlab.po b/locale/sr_SP/gitlab.po
index 1a877ebde6e..b8596ea8a38 100644
--- a/locale/sr_SP/gitlab.po
+++ b/locale/sr_SP/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:09\n"
+"PO-Revision-Date: 2019-04-12 12:31\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -42,8 +51,11 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -141,12 +153,27 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -165,7 +192,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -174,6 +201,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -183,6 +216,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -195,6 +231,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -240,6 +285,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -249,6 +297,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -273,6 +324,12 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -285,6 +342,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -327,6 +387,9 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -336,6 +399,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -351,6 +423,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -384,7 +459,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -396,18 +483,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -438,12 +546,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -453,6 +567,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -483,34 +603,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -534,18 +681,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -582,6 +738,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -663,12 +822,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -681,6 +849,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -690,15 +861,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -711,6 +900,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -729,6 +924,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -750,9 +948,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -762,6 +957,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -774,18 +978,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -798,6 +1011,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -810,6 +1026,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -825,6 +1044,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -867,12 +1089,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -930,6 +1158,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -942,15 +1173,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -963,6 +1212,48 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -978,15 +1269,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -999,15 +1305,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -1020,6 +1335,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1074,9 +1392,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1092,6 +1407,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1107,10 +1425,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1119,6 +1437,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1137,6 +1461,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1176,16 +1503,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1311,6 +1632,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1548,6 +1875,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1569,9 +1902,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1590,6 +1932,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1623,6 +1968,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1638,6 +1986,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1653,6 +2004,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1665,19 +2019,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1704,6 +2073,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1716,6 +2088,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1743,9 +2121,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1767,25 +2142,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1812,6 +2187,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1845,6 +2223,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1860,6 +2241,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1872,6 +2256,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1881,12 +2268,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1944,6 +2337,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1959,9 +2355,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1977,15 +2370,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -2001,16 +2394,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2049,6 +2439,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2058,19 +2454,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2118,7 +2514,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2145,13 +2541,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2160,15 +2556,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2196,7 +2595,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2208,9 +2607,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2253,9 +2649,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2268,10 +2661,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2280,15 +2673,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2319,7 +2709,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2355,16 +2745,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2376,7 +2766,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2385,10 +2775,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2427,9 +2823,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2439,6 +2832,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2448,15 +2844,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2553,6 +2961,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2622,6 +3033,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2637,12 +3054,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2706,6 +3135,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2733,18 +3165,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2757,6 +3180,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2781,9 +3207,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2799,9 +3222,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2811,10 +3258,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2826,6 +3273,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2850,9 +3300,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2922,6 +3369,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2961,9 +3411,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3006,6 +3453,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -3030,15 +3480,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3078,6 +3537,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3090,6 +3552,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3102,6 +3579,9 @@ msgstr[2] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3240,12 +3720,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3324,6 +3837,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3363,6 +3879,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3408,6 +3930,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3429,6 +3954,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3441,18 +3969,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3468,6 +4032,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3495,6 +4062,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3504,9 +4077,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3522,16 +4092,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3549,13 +4131,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3576,6 +4152,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3627,15 +4209,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3648,6 +4248,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3699,6 +4311,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3747,6 +4362,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3762,12 +4389,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3831,12 +4494,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3846,12 +4503,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3885,6 +4548,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3903,9 +4572,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3927,13 +4593,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3945,9 +4617,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3960,12 +4647,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3999,9 +4692,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4059,7 +4749,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4086,6 +4776,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4149,9 +4851,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4167,6 +4866,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4260,13 +4962,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4296,12 +5004,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4311,6 +5028,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4335,9 +5055,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4356,6 +5073,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4401,6 +5121,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4509,9 +5232,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4527,6 +5265,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4557,6 +5298,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4596,24 +5343,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4632,9 +5400,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4656,6 +5421,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4677,6 +5445,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4692,12 +5463,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4731,15 +5508,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4758,9 +5544,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4770,6 +5565,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4803,6 +5601,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4821,6 +5622,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4830,6 +5634,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4839,18 +5709,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4944,6 +5826,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4983,6 +5868,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4995,12 +5883,27 @@ msgstr[2] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5058,15 +5961,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5079,9 +5985,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5166,12 +6069,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5187,15 +6108,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5211,6 +6141,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5220,9 +6153,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5244,6 +6186,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5274,6 +6219,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5292,6 +6252,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5304,6 +6267,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5334,7 +6300,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5379,12 +6348,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5415,6 +6402,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5430,9 +6420,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5475,9 +6477,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5493,6 +6501,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5523,6 +6540,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5595,16 +6615,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5616,6 +6636,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5700,9 +6723,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5721,6 +6750,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5778,15 +6810,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5817,15 +6870,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5850,46 +6903,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5913,10 +6963,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5925,9 +6975,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5937,12 +6993,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -6012,16 +7077,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6081,13 +7149,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6129,6 +7200,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6174,6 +7251,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6192,6 +7272,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6201,6 +7284,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6234,9 +7320,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6246,6 +7338,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6258,6 +7356,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6312,13 +7416,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6327,6 +7431,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6342,6 +7449,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6363,6 +7473,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6375,19 +7488,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6417,7 +7539,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6435,15 +7557,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6492,9 +7623,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6510,10 +7638,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6627,6 +7761,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6639,12 +7776,21 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6654,6 +7800,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6690,6 +7842,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6717,15 +7872,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6744,9 +7914,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6768,6 +7935,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6792,18 +7965,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6813,6 +7989,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6822,6 +8001,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6840,6 +8022,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6855,21 +8040,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6918,6 +8106,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6933,6 +8127,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6975,10 +8175,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6993,6 +8190,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -7020,9 +8220,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7047,15 +8259,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7065,9 +8295,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7080,6 +8322,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7089,18 +8334,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7128,6 +8388,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7278,21 +8541,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7392,6 +8655,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7407,16 +8679,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7425,6 +8706,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7434,6 +8718,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7443,6 +8730,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7533,12 +8826,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7587,6 +8889,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7689,6 +8994,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7755,6 +9063,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7767,6 +9081,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7797,6 +9117,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7812,6 +9135,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7848,6 +9177,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7893,9 +9225,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7905,12 +9246,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7929,6 +9291,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7962,6 +9327,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7980,6 +9357,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7992,6 +9375,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -8028,6 +9414,21 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "Resend invite"
msgstr ""
@@ -8061,9 +9462,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8088,12 +9498,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8127,12 +9543,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8154,6 +9579,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8187,25 +9618,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8217,6 +9648,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8241,6 +9675,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8250,15 +9687,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8310,6 +9756,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8337,6 +9786,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8358,9 +9816,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8376,10 +9831,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8430,6 +9885,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8439,9 +9903,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8472,10 +9933,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8493,18 +9957,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8520,9 +10005,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8550,9 +10032,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8568,6 +10059,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8577,6 +10071,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8592,6 +10098,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8649,9 +10158,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8661,6 +10176,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8721,6 +10239,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8730,6 +10254,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8751,6 +10278,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8796,6 +10344,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8823,6 +10374,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8973,6 +10527,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -9021,15 +10578,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9063,6 +10632,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9216,6 +10788,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9225,15 +10818,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9243,6 +10845,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9366,12 +10971,24 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9390,6 +11007,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9402,15 +11025,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9420,6 +11091,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9447,9 +11130,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9468,6 +11160,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9483,9 +11190,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9540,9 +11256,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9558,6 +11286,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9576,6 +11307,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9588,6 +11322,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9609,6 +11358,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9624,6 +11376,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9672,6 +11427,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9693,10 +11454,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9705,7 +11472,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9729,6 +11496,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9738,10 +11508,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9756,6 +11526,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9822,9 +11595,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10056,6 +11826,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10095,6 +11868,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10119,6 +11898,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10131,6 +11913,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10140,6 +11925,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10164,6 +11961,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10173,15 +11973,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10194,6 +12006,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10254,21 +12069,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10284,6 +12117,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10308,18 +12144,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10350,18 +12198,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10440,18 +12324,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10476,9 +12381,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10488,7 +12390,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10500,12 +12402,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10539,6 +12444,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10575,12 +12483,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10629,6 +12543,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10647,9 +12567,18 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "When:"
msgstr ""
@@ -10662,6 +12591,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10773,6 +12705,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10791,6 +12726,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10803,12 +12741,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10839,6 +12789,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10854,6 +12807,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10863,6 +12825,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10872,6 +12840,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10881,12 +12852,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10905,12 +12894,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10923,7 +12927,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10935,12 +12939,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10968,6 +12984,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -11001,6 +13026,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -11019,9 +13053,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -11037,15 +13077,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11055,12 +13110,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11085,18 +13143,42 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11130,6 +13212,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11139,6 +13224,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11148,12 +13242,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11196,9 +13302,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11244,9 +13359,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11268,6 +13380,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11286,9 +13401,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11301,10 +13425,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11334,6 +13479,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11367,22 +13515,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11448,6 +13605,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11469,6 +13629,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11505,6 +13668,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11577,16 +13743,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11640,6 +13809,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11754,12 +13926,21 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11793,9 +13974,36 @@ msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11820,6 +14028,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sv_SE/gitlab.po b/locale/sv_SE/gitlab.po
index 1d66cc3af44..5504a0d8cf4 100644
--- a/locale/sv_SE/gitlab.po
+++ b/locale/sv_SE/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sv-SE\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:10\n"
+"PO-Revision-Date: 2019-04-12 12:32\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/sw_KE/gitlab.po b/locale/sw_KE/gitlab.po
index d6f57772b1b..c56880bae2a 100644
--- a/locale/sw_KE/gitlab.po
+++ b/locale/sw_KE/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: sw\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:10\n"
+"PO-Revision-Date: 2019-04-12 12:32\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -40,8 +49,10 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -126,12 +137,26 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -148,7 +173,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -157,6 +182,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -166,6 +197,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -178,6 +212,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -219,6 +262,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -228,6 +274,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -250,6 +299,11 @@ msgid_plural "%{count} %{type} modifications"
msgstr[0] ""
msgstr[1] ""
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -260,6 +314,9 @@ msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -295,6 +352,9 @@ msgid_plural "%d users"
msgstr[0] ""
msgstr[1] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -304,6 +364,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -319,6 +388,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -352,7 +424,19 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A default branch cannot be chosen for an empty project."
@@ -364,18 +448,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -406,12 +511,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -421,6 +532,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -451,34 +568,61 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
-msgid "Add new application"
+msgid "Add list"
msgstr ""
-msgid "Add new directory"
+msgid "Add new application"
msgstr ""
-msgid "Add projects"
+msgid "Add new directory"
msgstr ""
msgid "Add reaction"
@@ -502,18 +646,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -550,6 +703,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -631,12 +787,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -648,6 +813,9 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -657,15 +825,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -678,6 +864,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -717,9 +912,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -729,6 +921,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -741,18 +942,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -765,6 +975,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -777,6 +990,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -792,6 +1008,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -834,12 +1053,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -897,6 +1122,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -909,15 +1137,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -930,6 +1176,44 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -945,15 +1229,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -966,15 +1265,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -987,6 +1295,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@@ -1041,9 +1352,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1059,6 +1367,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1074,10 +1385,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1086,6 +1397,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1104,6 +1421,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1143,16 +1463,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1278,6 +1592,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1515,6 +1835,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1536,9 +1862,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1557,6 +1892,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1590,6 +1928,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1620,6 +1964,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1632,19 +1979,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1671,6 +2033,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1683,6 +2048,12 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1710,9 +2081,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1734,25 +2102,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1779,6 +2147,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1812,6 +2183,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -1827,6 +2201,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1839,6 +2216,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1848,12 +2228,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1911,6 +2297,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1926,9 +2315,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1944,15 +2330,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1968,16 +2354,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2016,6 +2399,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2025,19 +2414,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2085,7 +2474,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2112,13 +2501,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2127,15 +2516,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2163,7 +2555,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2175,9 +2567,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2220,9 +2609,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,15 +2633,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2322,16 +2705,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2343,7 +2726,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2394,9 +2783,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2406,6 +2792,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2415,15 +2804,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2519,6 +2920,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr ""
@@ -2588,6 +2992,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2603,12 +3013,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2672,6 +3094,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2699,18 +3124,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,6 +3139,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2747,9 +3166,6 @@ msgstr ""
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2765,9 +3181,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2777,10 +3217,10 @@ msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2792,6 +3232,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2816,9 +3259,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2888,6 +3328,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2927,9 +3370,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2972,6 +3412,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2996,15 +3439,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3044,6 +3496,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3056,6 +3511,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,6 +3537,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3205,12 +3678,45 @@ msgstr ""
msgid "Description"
msgstr ""
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3373,6 +3888,9 @@ msgstr ""
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3394,6 +3912,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3406,18 +3927,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3487,16 +4050,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr ""
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3514,13 +4089,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3541,6 +4110,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3592,15 +4167,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3613,6 +4206,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3712,6 +4320,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3727,12 +4347,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3850,6 +4506,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3892,13 +4551,19 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3964,9 +4650,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4051,6 +4734,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4113,9 +4808,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4224,13 +4919,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,6 +4985,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4299,9 +5012,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4320,6 +5030,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4521,6 +5255,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4560,24 +5300,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4722,9 +5501,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4767,6 +5558,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,6 +5591,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4803,18 +5666,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4947,6 +5825,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4958,12 +5839,27 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5129,12 +6025,30 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5266,6 +6222,9 @@ msgstr ""
msgid "Issue events"
msgstr ""
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5296,7 +6255,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5341,12 +6303,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,9 +6375,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5437,9 +6432,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,6 +6590,9 @@ msgstr ""
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr ""
@@ -5661,9 +6677,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5810,46 +6856,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5873,10 +6916,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5885,9 +6928,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,12 +6946,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5972,16 +7030,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,6 +7225,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -6161,6 +7237,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6194,9 +7273,15 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6206,6 +7291,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6218,6 +7309,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6271,13 +7368,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -6286,6 +7383,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -6301,6 +7401,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6322,6 +7425,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -6334,19 +7440,28 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6376,7 +7491,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6394,15 +7509,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6451,9 +7575,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6586,6 +7713,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6648,6 +7793,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6675,15 +7823,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,9 +7865,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,6 +7973,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6978,9 +8171,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7047,18 +8285,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7086,6 +8339,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7236,21 +8492,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7365,16 +8630,25 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7383,6 +8657,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,6 +8669,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7401,6 +8681,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7491,12 +8777,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,6 +9086,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7805,6 +9127,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7919,6 +9277,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7937,6 +9307,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7949,6 +9325,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,25 +9565,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8173,6 +9595,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8266,6 +9703,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8293,6 +9733,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8314,9 +9763,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8428,10 +9880,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8449,18 +9904,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8506,9 +9979,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,6 +10045,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8676,6 +10185,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8685,6 +10200,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8706,6 +10224,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,6 +10320,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9171,6 +10734,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9495,9 +11201,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9564,6 +11303,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9579,6 +11321,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9648,10 +11399,16 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9684,6 +11441,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9693,10 +11453,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9777,9 +11540,6 @@ msgstr ""
msgid "Timeago|%s months remaining"
msgstr ""
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr ""
@@ -10009,6 +11769,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10048,6 +11811,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10117,6 +11904,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10126,15 +11916,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10147,6 +11949,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10261,18 +12087,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10303,18 +12141,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10393,18 +12267,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10429,9 +12324,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10441,7 +12333,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10453,12 +12345,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10492,6 +12387,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10528,12 +12426,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10582,6 +12486,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10615,6 +12533,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10726,6 +12647,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10744,6 +12668,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10756,12 +12683,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,6 +12749,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10816,6 +12767,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,12 +12794,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10858,12 +12836,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10876,7 +12869,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10888,12 +12881,24 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -10921,6 +12926,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10954,6 +12968,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10972,9 +12995,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10990,15 +13019,30 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -11008,12 +13052,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,6 +13160,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11097,12 +13178,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11145,9 +13238,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11191,9 +13293,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,9 +13335,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11247,10 +13358,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
+msgstr ""
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11279,6 +13411,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11310,22 +13445,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11390,6 +13534,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11446,6 +13596,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11518,16 +13671,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11579,6 +13735,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11691,12 +13850,20 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11728,9 +13895,36 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11755,6 +13949,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index dcdc049d9a6..37fefca2afd 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -13,125 +13,150 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:10\n"
+"PO-Revision-Date: 2019-04-13 08:55\n"
+
+msgid " Please sign in."
+msgstr " Lütfen oturum açın."
msgid " Status"
+msgstr " Durum"
+
+msgid " Try to %{action} this file again."
+msgstr " Bu dosyaya yine %{action} deneyin."
+
+msgid " You need to do this before %{grace_period_deadline}."
msgstr ""
msgid " and"
-msgstr ""
+msgstr " ve"
msgid " degraded on %d point"
msgid_plural " degraded on %d points"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] " %d noktasında bozuldu"
+msgstr[1] " %d noktasında bozuldu"
msgid " improved on %d point"
msgid_plural " improved on %d points"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] " %d noktasında geliştirildi"
+msgstr[1] " %d noktalasında geliştirildi"
msgid " or "
-msgstr ""
+msgstr " veya "
msgid " or <#epic id>"
-msgstr ""
+msgstr " veya <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " veya <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d yorum"
+msgstr[1] "%d yorum"
msgid "%d commit"
msgid_plural "%d commits"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d iÅŸlem"
+msgstr[1] "%d iÅŸlem"
msgid "%d commit behind"
msgid_plural "%d commits behind"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d 'e bağlı işlem"
+msgstr[1] "%d 'e bağlı işlem"
msgid "%d commits"
-msgstr ""
+msgstr "%d iÅŸlem"
msgid "%d exporter"
msgid_plural "%d exporters"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d dışa aktaran"
+msgstr[1] "%d dışa aktaran"
msgid "%d failed test result"
msgid_plural "%d failed test results"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d başarısız test sonucu"
+msgstr[1] "%d başarısız test sonucu"
msgid "%d fixed test result"
msgid_plural "%d fixed test results"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d sabit test sonucu"
+msgstr[1] "%d sabit test sonucu"
msgid "%d issue"
msgid_plural "%d issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d sorun"
+msgstr[1] "%d soru"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d sorun seçili"
+msgstr[1] "%d sorun seçili"
msgid "%d layer"
msgid_plural "%d layers"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d katman"
+msgstr[1] "%d katmanlar"
msgid "%d merge request"
msgid_plural "%d merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d birleÅŸtirme isteÄŸi"
+msgstr[1] "%d birleÅŸtirme isteÄŸi"
msgid "%d metric"
msgid_plural "%d metrics"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d metrik"
+msgstr[1] "%d metrik"
msgid "%d staged change"
msgid_plural "%d staged changes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d aşamalı değişiklik"
+msgstr[1] "%d aşamalı değişiklik"
msgid "%d unstaged change"
msgid_plural "%d unstaged changes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d grupsuz deÄŸiÅŸiklik"
+msgstr[1] "%d grupsuz deÄŸiÅŸiklik"
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] ""
+msgstr[0] "%s performans sorunlarını önlemek için ek işlem konulmuştur."
+msgstr[1] "%s performans sorunlarını önlemek için ek işlem konulmuştur."
msgid "%{actionText} & %{openOrClose} %{noteable}"
-msgstr ""
+msgstr "%{actionText} ve %{openOrClose} %{noteable}"
msgid "%{authorsName}'s discussion"
-msgstr ""
+msgstr "%{authorsName} kişisinin tartışması"
msgid "%{commit_author_link} authored %{commit_timeago}"
-msgstr ""
+msgstr "%{commit_author_link} %{commit_timeago} yazdı"
msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
-msgstr ""
+msgstr "%{counter_storage} (%{counter_repositories} depo, %{counter_build_artifacts} yapı eseri, %{counter_lfs_objects} LFS)"
msgid "%{count} %{alerts}"
-msgstr ""
+msgstr "%{count} %{alerts}"
+
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "%{name} kiÅŸisinden %{count} onay gerekli"
+msgstr[1] "%{name} kiÅŸisinden %{count} onay gerekli"
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{name} kiÅŸisinden %{count} onay"
msgid "%{count} more"
-msgstr ""
+msgstr "%{count} daha"
msgid "%{count} more assignees"
+msgstr "%{count} daha fazla atanan"
+
+msgid "%{count} of %{required} approvals from %{name}"
msgstr ""
+msgid "%{count} of %{total}"
+msgstr "%{count} / %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} katılımcı"
@@ -143,181 +168,228 @@ msgstr[0] ""
msgstr[1] ""
msgid "%{filePath} deleted"
-msgstr ""
+msgstr "%{filePath} silindi"
msgid "%{firstLabel} +%{labelCount} more"
-msgstr ""
+msgstr "%{firstLabel} +%{labelCount} daha"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} bu işlemde bulunamadı"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
+msgstr "%{issuableType} kaldırılacak! Emin misiniz?"
+
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} kullanılamaz"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
-msgstr ""
+msgstr "Rol izinleri hakkında %{link_start}daha fazla bilgi edinin%{link_end}"
msgid "%{loadingIcon} Started"
-msgstr ""
+msgstr "%{loadingIcon} Başladı"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
+msgstr "%{lock_path} dizini %{lock_user_id} GitLab kullanıcısı tarafından kilitlendi"
+
+msgid "%{mrText}, this issue will be closed automatically."
msgstr ""
msgid "%{name}'s avatar"
-msgstr ""
+msgstr "%{name} kullanıcısının avatarı"
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
msgid "%{openOrClose} %{noteable}"
-msgstr ""
+msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
+msgstr "%{percent}%% tamamlandı"
+
+msgid "%{service_title} activated."
+msgstr "%{service_title} etkinleÅŸtirildi."
+
+msgid "%{service_title} settings saved, but not activated."
msgstr ""
-msgid "%{state} epics"
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
+msgid "%{state} epics"
+msgstr "%{state} epik"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} Dal"
+msgstr[1] "%{strong_start}%{branch_count}%{strong_end} Dal"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} Ä°ÅŸlem"
+msgstr[1] "%{strong_start}%{commit_count}%{strong_end} Ä°ÅŸlem"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} Dosya"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} Etiket"
+msgstr[1] "%{strong_start}%{tag_count}%{strong_end} Etiket"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%{text} %{files}"
+msgstr[1] "%{text} %{files} dosyaları"
msgid "%{text} is available"
-msgstr ""
+msgstr "%{text} kullanılabilir"
msgid "%{title} changes"
-msgstr ""
+msgstr "%{title} deÄŸiÅŸiklik"
msgid "%{unstaged} unstaged and %{staged} staged changes"
-msgstr ""
+msgstr "%{unstaged} aşamasız ve %{staged} aşamalı değişiklik"
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr ""
msgid "%{user_name} profile page"
-msgstr ""
+msgstr "%{user_name} profil sayfası"
+
+msgid "(No changes)"
+msgstr "(DeÄŸiÅŸiklik yok)"
msgid "(external source)"
-msgstr ""
+msgstr "(dış kaynak)"
msgid "+ %{count} more"
-msgstr ""
+msgstr "+ %{count} daha"
msgid "+ %{moreCount} more"
msgstr "+ %{moreCount} daha fazla"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} daha fazla"
+
msgid ", or "
-msgstr ""
+msgstr ", veya "
msgid "- Runner is active and can process any new jobs"
-msgstr ""
+msgstr "- Çalıştırıcı aktif ve yeni işler işleyebilir"
msgid "- Runner is paused and will not receive any new jobs"
-msgstr ""
+msgstr "- Çalıştırıcı duraklatıldı ve yeni işler almayacak"
msgid "- show less"
msgstr "- daha az göster"
msgid "1 %{type} addition"
msgid_plural "%{count} %{type} additions"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 %{type} ek"
+msgstr[1] "%{count} %{type} ek"
msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 %{type} deÄŸiÅŸiklik"
+msgstr[1] "%{count} %{type} deÄŸiÅŸiklik"
+
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "1 gün"
+msgstr[1] "%d gün"
msgid "1 closed issue"
msgid_plural "%d closed issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 kapatılmış sorun"
+msgstr[1] "%d kapatılmış sorun"
msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 kapatılmış birleştirme talebi"
+msgstr[1] "%d kapatılmış birleştirme talebi"
+
+msgid "1 day"
+msgstr "1 gün"
msgid "1 group"
msgid_plural "%d groups"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 grup"
+msgstr[1] "%d grup"
msgid "1 merged merge request"
msgid_plural "%d merged merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 birleÅŸtirilmiÅŸ birleÅŸtirme talebi"
+msgstr[1] "%d birleÅŸtirilmiÅŸ birleÅŸtirme talebi"
msgid "1 open issue"
msgid_plural "%d open issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 açık sorun"
+msgstr[1] "%d açık sorun"
msgid "1 open merge request"
msgid_plural "%d open merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 açık birleştirme talebi"
+msgstr[1] "%d açık birleştirme talebi"
msgid "1 pipeline"
msgid_plural "%d pipelines"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 iş hattı"
+msgstr[1] "%d iş hattı"
msgid "1 role"
msgid_plural "%d roles"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 rol"
+msgstr[1] "%d rol"
msgid "1 user"
msgid_plural "%d users"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "1 kullanıcı"
+msgstr[1] "%d kullanıcı"
+
+msgid "1 week"
+msgstr "1 hafta"
msgid "1st contribution!"
msgstr "İlk katkı!"
msgid "2FA"
-msgstr ""
+msgstr "2FA"
msgid "2FA enabled"
-msgstr ""
+msgstr "2FA etkin"
+
+msgid "3 days"
+msgstr "3 gün"
+
+msgid "3 hours"
+msgstr "3 saat"
+
+msgid "30 minutes"
+msgstr "30 dakika"
msgid "403|Please contact your GitLab administrator to get permission."
-msgstr ""
+msgstr "403|İzin almak için lütfen GitLab yöneticinize başvurun."
msgid "403|You don't have the permission to access this page."
-msgstr ""
+msgstr "403 | Bu sayfaya eriÅŸim izniniz yok."
msgid "404|Make sure the address is correct and the page hasn't moved."
-msgstr ""
+msgstr "404|Adresin doğru olduğundan ve sayfanın taşınmadığından emin olun."
msgid "404|Page Not Found"
-msgstr ""
+msgstr "404|Sayfa Bulunamadı"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
-msgstr ""
+msgstr "404|Lütfen bunun bir hata olduğunu düşünüyorsanız, GitLab yöneticinizle görüşün."
+
+msgid "8 hours"
+msgstr "8 saat"
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -335,184 +407,265 @@ msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFiles
msgstr ""
msgid "<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted."
-msgstr ""
+msgstr "<strong>%{created_count}</strong> oluÅŸturuldu, <strong>%{accepted_count}</strong> kabul edildi."
msgid "<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed."
-msgstr ""
+msgstr "<strong>%{created_count}</strong> oluşturuldu, <strong>%{closed_count}</strong> kapatıldı."
msgid "<strong>%{group_name}</strong> group members"
-msgstr ""
+msgstr "<strong>%{group_name}</strong> grup üyeleri"
msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors."
msgstr ""
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "Kaynak dalı <strong>siler</strong>"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
-msgid "A default branch cannot be chosen for an empty project."
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab yerine CI/CD için Netlify kullanan, ancak diğer tüm GitLab özelliklerine sahip bir Hexo sitesi."
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab yerine CI/CD için Netlify kullanan, ancak diğer tüm GitLab özelliklerine sahip bir Hugo sitesi."
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab yerine CI/CD için Netlify kullanan, ancak diğer tüm GitLab özelliklerine sahip bir Jekyll sitesi."
+
+msgid "A default branch cannot be chosen for an empty project."
+msgstr "Boş bir proje için bir varsayılan dal seçilemez."
+
msgid "A deleted user"
-msgstr ""
+msgstr "Silinmiş kullanıcı"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgstr "GitLab'ın suistimal ekibinin bir üyesi raporunuzu en kısa sürede gözden geçirecek."
+
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
+msgstr "Çatalınızdan yeni bir dal oluşturulacak ve yeni bir birleştirme talebi başlatılacak."
+
+msgid "A new impersonation token has been created."
msgstr ""
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "GitLab yerine CI/CD için Netlify kullanan, ancak diğer tüm GitLab özelliklerine sahip bir düz bir HTML sitesi."
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr "Android uygulamalarıyla kullanım için kullanıma-hazır bir şablon."
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "iOS Swift uygulamalarıyla kullanım için kullanıma-hazır bir şablon."
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
-msgstr ""
+msgstr "İş izlemede test kapsamı çıktısını bulmak için kullanılacak düzenli bir ifade. Devre dışı bırakmak için boş bırakın"
msgid "A user with write access to the source branch selected this option"
-msgstr ""
+msgstr "Bu dala yazma yetkisi olan kullanıcı bu seçeneği seçti"
+
+msgid "API Help"
+msgstr "API Yardımı"
msgid "About GitLab"
-msgstr ""
+msgstr "GitLab Hakkında"
msgid "About GitLab CE"
-msgstr ""
+msgstr "GitLab CE hakkında"
msgid "About auto deploy"
-msgstr ""
+msgstr "Otomatik dağıtım hakkında"
msgid "About this feature"
-msgstr ""
+msgstr "Bu özellik hakkında"
msgid "Abuse Reports"
msgstr "Kötüye Kullanım Raporları"
msgid "Abuse reports"
-msgstr ""
+msgstr "Kötüye kullanım raporları"
msgid "Accept invitation"
-msgstr ""
+msgstr "Daveti kabul et"
msgid "Accept terms"
-msgstr ""
+msgstr "Şartları kabul et"
msgid "Accepted MR"
-msgstr ""
+msgstr "Kabul edilen MR"
msgid "Access Tokens"
-msgstr "Erişim anahtarları"
+msgstr "Erişim Anahtarları"
-msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgid "Access denied for your LDAP account."
msgstr ""
+msgid "Access denied! Please verify you can add deploy keys to this repository."
+msgstr "Erişim reddedildi! Lütfen bu depoya dağıtım anahtarlarını ekleyebileceğinizi doğrulayın."
+
msgid "Access expiration date"
+msgstr "EriÅŸim bitiÅŸ tarihi"
+
+msgid "Access forbidden. Check your access level."
msgstr ""
msgid "Access to '%{classification_label}' not allowed"
-msgstr ""
+msgstr "'%{classification_label}' eriÅŸimine izin verilmedi"
msgid "Account"
msgstr "Hesap"
msgid "Account and limit"
-msgstr ""
+msgstr "Hesap ve sınırı"
+
+msgid "Action to take when receiving an alert."
+msgstr "Bir uyarı alırken yapılması gerekenler."
+
+msgid "Activate Service Desk"
+msgstr "Servis Masasını Etkinleştir"
msgid "Active"
msgstr "Etkin"
msgid "Active Sessions"
-msgstr ""
+msgstr "Etkin Oturumlar"
msgid "Activity"
msgstr "Etkinlik"
msgid "Add"
-msgstr ""
+msgstr "Ekle"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "CHANGELOG ekle"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "CONTRIBUTING ekle"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
-msgstr ""
+msgstr "Grup web kancalarını ve GitLab Kurumsal Sürüm'ü ekleyin."
msgid "Add Jaeger URL"
-msgstr ""
+msgstr "Jaeger URL ekle"
msgid "Add Kubernetes cluster"
-msgstr ""
+msgstr "Kubernetes kümesi ekle"
msgid "Add README"
-msgstr ""
+msgstr "BENÄ°OKU ekle"
+
+msgid "Add a bullet list"
+msgstr "Madde iÅŸareti listesi ekle"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Buna %{noteable_name} genel bir yorum ekle."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
-msgstr ""
+msgstr "Wiki'nize projeniz hakkında bilgi içeren bir ana sayfa ekleyin. GitLab bu mesaj yerine onu burada görüntüleyecektir."
+
+msgid "Add a link"
+msgstr "Bağlantı ekle"
+
+msgid "Add a numbered list"
+msgstr "Numaralı liste ekle"
msgid "Add a table"
-msgstr ""
+msgstr "Tablo ekle"
+
+msgid "Add a task list"
+msgstr "Görev listesi ekle"
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
-msgstr ""
+msgstr "Tüm e-posta iletişiminde görünecek ek metin ekleyin. %{character_limit} karakter sınırı"
+
+msgid "Add approver(s)"
+msgstr "Onaylayan(ları) ekle"
+
+msgid "Add approvers"
+msgstr "Onaylayanları ekle"
+
+msgid "Add bold text"
+msgstr "Kalın metin ekle"
msgid "Add comment now"
-msgstr ""
+msgstr "Åžimdi yorum ekle"
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "E-postalar için üstbilgi ve altbilgi ekleyin. Renk ayarları sadece uygulama arabirimi içinde uygulanacak unutmayın"
msgid "Add image comment"
-msgstr ""
+msgstr "Resim yorumu ekle"
+
+msgid "Add italic text"
+msgstr "EÄŸik metin ekle"
msgid "Add license"
-msgstr ""
+msgstr "Lisans ekle"
+
+msgid "Add list"
+msgstr "Liste ekle"
msgid "Add new application"
-msgstr ""
+msgstr "Yeni uygulama ekle"
msgid "Add new directory"
msgstr "Yeni dizin ekle"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
-msgstr ""
+msgstr "Tepki ekle"
msgid "Add to project"
-msgstr ""
+msgstr "Projeye ekle"
msgid "Add to review"
-msgstr ""
+msgstr "Ä°ncelemeye ekle"
msgid "Add todo"
msgstr "Yapılacaklara Ekle"
msgid "Add user(s) to the group:"
-msgstr ""
+msgstr "Gruba kullanıcı(lar) ekleyin:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Her bir birleştirme isteğini onaylamasına izin verilen kullanıcıları veya grupları ekle"
msgid "Add users to group"
-msgstr ""
+msgstr "Kullanıcıları gruba ekle"
+
+msgid "Added at"
+msgstr "Tarihinde eklendi"
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
-msgstr ""
+msgstr "GitLab örneğinizde yeni uygulamalar eklemek devre dışı bırakıldı. İzin almak için lütfen GitLab yöneticinize başvurun"
msgid "Additional text"
-msgstr ""
+msgstr "Ek metin"
+
+msgid "Aditional minutes"
+msgstr "Ekstra dakika"
msgid "Admin Area"
-msgstr ""
+msgstr "Yönetici alanı"
msgid "Admin Overview"
-msgstr ""
+msgstr "Yönetim Genel Bakış"
+
+msgid "Admin Section"
+msgstr "Yönetici Bölümü"
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -530,144 +683,177 @@ msgid "AdminArea|Stop jobs"
msgstr "Ä°ÅŸleri durdur"
msgid "AdminArea|Stopping jobs failed"
-msgstr ""
+msgstr "AdminArea|Durdurma işleri başarısız oldu"
msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
-msgstr ""
+msgstr "AdminArea|Bütün işleri durdurmak üzeresiniz. Bu işlem çalışan tüm mevcut işleri durduracaktır."
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
msgstr ""
msgid "AdminProjects|Delete"
-msgstr ""
+msgstr "AdminProjects|Sil"
msgid "AdminProjects|Delete Project %{projectName}?"
-msgstr ""
+msgstr "AdminProjects|%{projectName} projesi silinsin mi?"
msgid "AdminProjects|Delete project"
-msgstr ""
+msgstr "AdminProjects|Projeyi sil"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Otomatik DevOps etki alanı"
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr "Yeni projeler için paylaşılan çalıştırıcıları etkinleştir"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Ortam değişkenleri öntanımlı olarak korunur"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
-msgstr ""
+msgstr "Her projenin Otomatik İnceleme Uygulamaları ve Otomatik Dağıtma aşamaları için varsayılan olarak kullanılacak bir etki alanı belirleyin."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "Yeni bir ortam değişkeni oluştururken, öntanımlı olarak korunacaktır."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA Devre dışı"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA Etkin"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Etkin"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "Yönetici"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "Yöneticiler"
msgid "AdminUsers|Block user"
-msgstr ""
+msgstr "Kullanıcıyı engelle"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Engellendi"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "LDAP engelli kullanıcıların engellemesini kaldıramıyor"
msgid "AdminUsers|Delete User %{username} and contributions?"
-msgstr ""
+msgstr "%{username} kullanıcısı ve katkıları silinsin mi?"
msgid "AdminUsers|Delete User %{username}?"
-msgstr ""
+msgstr "%{username} kullanıcısı silinsin mi?"
msgid "AdminUsers|Delete user"
-msgstr ""
+msgstr "Kullanıcıyı sil"
msgid "AdminUsers|Delete user and contributions"
-msgstr ""
+msgstr "Kullanıcıyı ve katkılarını sil"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Harici"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "Bu sensin!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Yeni kullanıcı"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "Kullanıcı bulunamadı"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "İsme, e-postaya veya kullanıcı adına göre ara"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "Kullanıcıları ara"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Kullanıcılara e-posta gönder"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Buna göre sırala"
msgid "AdminUsers|To confirm, type %{projectName}"
-msgstr ""
+msgstr "Onaylamak için, %{projectName} yazın"
msgid "AdminUsers|To confirm, type %{username}"
-msgstr ""
+msgstr "Onaylamak için, %{username} yazın"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "Kullanıcı engellenecek"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "Projeler olmadan"
+
+msgid "Advanced"
+msgstr "GeliÅŸmiÅŸ"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
-msgstr ""
+msgstr "Gelişmiş izinler, Büyük Dosya Depolama ve İki Adımlı Kimlik Doğrulama ayarları."
+
+msgid "Advanced search functionality"
+msgstr "GeliÅŸmiÅŸ arama iÅŸlevi"
msgid "Advanced settings"
msgstr "GeliÅŸmiÅŸ ayarlar"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "Başarılı bir şifre güncellemesinden sonra giriş ekranına yönlendirileceksiniz."
+
msgid "Alert"
msgid_plural "Alerts"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Uyarı"
+msgstr[1] "Uyarı"
msgid "Alerts"
-msgstr ""
+msgstr "Uyarılar"
msgid "All"
msgstr "Tümü"
+msgid "All Members"
+msgstr "Tüm Kullanıcılar"
+
msgid "All changes are committed"
-msgstr ""
+msgstr "Tüm değişiklikler işlendi"
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
-msgstr ""
+msgstr "Tüm özellikler şablonlardan veya içe aktarırken boş projeler için etkindir, ancak daha sonra proje ayarlarında bunları devre dışı bırakabilirsiniz."
msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr "Bu dönüm noktası için tüm konular kapalı. Şimdi bu dönüm noktasını kapatabilirsiniz."
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
msgstr ""
+msgid "All todos were marked as done."
+msgstr "Tüm yapılacaklar yapıldı olarak işaretlendi."
+
msgid "All users"
-msgstr ""
+msgstr "Tüm kullanıcılar"
+
+msgid "All users must have a name."
+msgstr "Tüm kullanıcıların bir adı olmalıdır."
msgid "Allow \"%{group_name}\" to sign you in"
-msgstr ""
+msgstr "\"%{group_name}\" grubunun oturum açmasına izin ver"
msgid "Allow commits from members who can merge to the target branch."
-msgstr ""
+msgstr "Hedef dala bağlanabilecek üyelerin işlemlerine izin verin."
+
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr "Grup sahiplerinin LDAP ile ilgili ayarları yönetmesine izin ver"
+
+msgid "Allow mirrors to be set up for projects"
+msgstr "Projeler için yansımaların ayarlanmasına izin ver"
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr "Git erişimi için yalnızca seçili protokollerin kullanılmasına izin ver."
msgid "Allow projects within this group to use Git LFS"
-msgstr ""
+msgstr "Bu gruptaki projelerin Git LFS'yi kullanmasına izin verin"
msgid "Allow public access to pipelines and job details, including output logs and artifacts"
msgstr ""
@@ -676,16 +862,22 @@ msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
msgstr ""
msgid "Allow requests to the local network from hooks and services."
+msgstr "Yerel aÄŸa kanca ve servislerden gelen isteklere izin ver."
+
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr "Bu anahtarın depoya yollanmasına izin verilsin mi? (Varsayılan yalnızca alma erişimine izin verir.)"
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
msgstr ""
msgid "Allow users to request access"
-msgstr ""
+msgstr "Kullanıcıların erişim isteğinde bulunmasına izin ver"
msgid "Allow users to request access if visibility is public or internal."
-msgstr ""
+msgstr "Görünürlük herkese açık veya dahili ise kullanıcıların erişim isteğine izin ver."
msgid "Allowed to fail"
-msgstr ""
+msgstr "Ä°zin verilmedi"
msgid "Allows you to add and manage Kubernetes clusters."
msgstr "Kubernetes kümelerini eklemeye ve yönetmenize olanak tanır."
@@ -696,6 +888,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr "Yardım sayfası için alternatif destek bağlantısı"
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -706,115 +901,139 @@ msgid "Amount of time (in hours) that users are allowed to skip forced configura
msgstr ""
msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
-msgstr ""
+msgstr "Form gönderildiğinde otomatik SSH anahtarı oluşturur. Daha fazla bilgi için belgeleri inceleyin."
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
-msgstr ""
+msgstr "%{link_to_client} adlı uygulama, GitLab hesabınıza erişim talep ediyor."
msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator."
msgstr ""
msgid "An error has occurred"
-msgstr ""
-
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
+msgstr "Bir hata oluÅŸtu"
msgid "An error occurred adding a draft to the discussion."
-msgstr ""
+msgstr "Tartışmaya bir taslak eklenirken bir hata oluştu."
msgid "An error occurred adding a new draft."
-msgstr ""
+msgstr "Yeni taslak eklenirken bir hata oluÅŸtu."
msgid "An error occurred creating the new branch."
+msgstr "Yeni dal oluÅŸturulurken bir hata oluÅŸtu."
+
+msgid "An error occurred fetching the approval rules."
+msgstr "Onay kurallarını alırken bir hata oluştu."
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "Yeni kuralın onaylayanları alınırken bir hata oluştu."
+
+msgid "An error occurred fetching the dropdown data."
msgstr ""
msgid "An error occurred previewing the blob"
-msgstr ""
+msgstr "Blob datanın öngösteriminde, bir hata meydana geldi"
msgid "An error occurred when toggling the notification subscription"
-msgstr ""
+msgstr "Bildirim aboneliÄŸini deÄŸiÅŸtirirken bir sorun meydana geldi"
msgid "An error occurred when updating the issue weight"
-msgstr ""
+msgstr "Sorun ağırlığı güncellenirken bir hata oluştu"
msgid "An error occurred while adding approver"
-msgstr ""
+msgstr "Onaylayıcı eklenirken bir hata oluştu"
+
+msgid "An error occurred while deleting the approvers group"
+msgstr "Onaylayanlar grubu silinirken bir hata oluÅŸtu"
msgid "An error occurred while deleting the comment"
-msgstr ""
+msgstr "Yorum silinirken bir hata oluÅŸtu"
msgid "An error occurred while detecting host keys"
+msgstr "Ana bilgisayar anahtarları algılanırken bir hata oluştu"
+
+msgid "An error occurred while disabling Service Desk."
msgstr ""
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
-msgstr ""
+msgstr "Uyarıyı kaldırırken bir sorun oluştu. Sayfayı yenileyin ve yeniden deneyin."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr "Özellik vurgusunu gönderirken bir hata oluştu. Sayfayı yenileyin ve tekrar gönderin."
+
+msgid "An error occurred while enabling Service Desk."
msgstr ""
msgid "An error occurred while fetching markdown preview"
msgstr "Markdown ön izlemesi yüklenirken hata oluştu"
msgid "An error occurred while fetching pending comments"
-msgstr ""
+msgstr "Bekleyen yorumlar alınırken bir hata oluştu"
msgid "An error occurred while fetching sidebar data"
-msgstr ""
+msgstr "Kenar çubuğu verileri getirilirken bir hata oluştu"
msgid "An error occurred while fetching stages."
+msgstr "Aşamalar alınırken bir hata oluştu."
+
+msgid "An error occurred while fetching the Service Desk address."
msgstr ""
msgid "An error occurred while fetching the job log."
-msgstr ""
+msgstr "İş kayıtları alınırken bir hata oluştu."
msgid "An error occurred while fetching the job."
-msgstr ""
+msgstr "İş alınırken bir hata oluştu."
msgid "An error occurred while fetching the jobs."
-msgstr ""
+msgstr "İşler alınırken bir hata oluştu."
msgid "An error occurred while fetching the pipeline."
-msgstr ""
+msgstr "İş hattı alınırken bir hata oluştu."
+
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr "Sürümler alınırken bir hata oluştu. Lütfen tekrar deneyin."
msgid "An error occurred while getting projects"
msgstr "Projeler yüklenirken bir hata oluştu"
msgid "An error occurred while importing project: %{details}"
-msgstr ""
+msgstr "Projeyi içe aktarırken bir sorun oluştu: %{details}"
msgid "An error occurred while initializing path locks"
-msgstr ""
+msgstr "Yol kilitlerini başlatırken bir hata oluştu"
msgid "An error occurred while loading chart data"
-msgstr ""
+msgstr "Tablo verileri alınırken bir hata oluştu"
msgid "An error occurred while loading commit signatures"
-msgstr ""
+msgstr "İşlem imzaları yüklenirken bir hata oluştu"
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr "Tasarımlar yüklenirken bir hata oluştu. Lütfen tekrar deneyin."
msgid "An error occurred while loading diff"
-msgstr ""
+msgstr "Değişiklikler uygulanırken bir hata oluştu"
msgid "An error occurred while loading filenames"
msgstr "Dosya isimleri yüklenirken bir hata oluştu"
msgid "An error occurred while loading the file"
-msgstr ""
+msgstr "Dosya yüklenirken bir hata oluştu"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "Abonelik ayrıntıları yüklenirken bir hata oluştu."
msgid "An error occurred while making the request."
-msgstr ""
+msgstr "Talep edilirken bir hata oluÅŸtu."
msgid "An error occurred while removing approver"
-msgstr ""
+msgstr "Onaylayan kaldırılırken bir hata oluştu"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "Epikler kaldırılırken bir hata oluştu."
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "Sorunlar kaldırılırken bir hata oluştu."
msgid "An error occurred while rendering KaTeX"
msgstr "KaTeX'i iÅŸlerken bir hata oluÅŸtu"
@@ -823,7 +1042,7 @@ msgid "An error occurred while rendering preview broadcast message"
msgstr "Önizleme yayını iletisi oluşturulurken bir hata oluştu"
msgid "An error occurred while retrieving calendar activity"
-msgstr ""
+msgstr "Takvim etkinlikleri getirilirken bir hata meydana geldi"
msgid "An error occurred while retrieving diff"
msgstr "Değişiklikler yüklenirken bir hata oluştu"
@@ -832,112 +1051,177 @@ msgid "An error occurred while saving LDAP override status. Please try again."
msgstr ""
msgid "An error occurred while saving assignees"
-msgstr ""
+msgstr "Atanmış kişiler kaydedilirken bir hata oluştu"
+
+msgid "An error occurred while saving the approval settings"
+msgstr "Onay ayarları kaydedilirken bir hata oluştu"
msgid "An error occurred while subscribing to notifications."
-msgstr ""
+msgstr "Bildirimlere abone olunurken bir hata oluÅŸtu."
msgid "An error occurred while unsubscribing to notifications."
-msgstr ""
+msgstr "Bildirim aboneliÄŸi iptal edilirken bir hata oluÅŸtu."
+
+msgid "An error occurred while updating approvers"
+msgstr "Onaylama sırasında güncelleme yapılırken bir hata oluştu"
msgid "An error occurred while updating the comment"
-msgstr ""
+msgstr "Yorum güncellenirken bir hata oluştu"
msgid "An error occurred while validating username"
msgstr "Kullanıcı adı doğrulanırken bir hata oluştu"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "DeÄŸiÅŸiklikleriniz iÅŸlenirken bir hata oluÅŸtu."
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "İş izi alınırken bir hata oluştu."
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "En son iş hattı alınırken bir hata oluştu."
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "Tüm dosyalar yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "Dosya içeriği yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the file."
-msgstr ""
+msgstr "Dosya yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "Birleştirme isteği değişiklikleri yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "Birleştirme isteği sürüm verileri yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the merge request."
-msgstr ""
+msgstr "Birleştirme isteği yüklenirken bir hata oluştu."
msgid "An error occurred whilst loading the pipelines jobs."
-msgstr ""
+msgstr "İş hattı işleri yüklenirken bir hata oluştu."
msgid "An error occurred. Please try again."
msgstr "Bir hata oluştu. Lütfen tekrar deneyin."
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "Proje ortamı kontrol edilirken beklenmeyen bir hata oluştu."
msgid "An unexpected error occurred while checking the project runners."
msgstr ""
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "Web Terminali ile iletiÅŸim kurulurken beklenmeyen bir hata oluÅŸtu."
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "Web Terminali başlatılırken beklenmeyen bir hata oluştu."
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "Web Terminali durdurulurken beklenmeyen bir hata oluÅŸtu."
msgid "Analytics"
+msgstr "Analizler"
+
+msgid "Ancestors"
msgstr ""
msgid "Anonymous"
-msgstr ""
+msgstr "Anonim"
msgid "Anti-spam verification"
-msgstr ""
+msgstr "Anti-spam doÄŸrulama"
msgid "Any"
-msgstr ""
+msgstr "Herhangi"
msgid "Any Label"
+msgstr "Herhangi bir etiket"
+
+msgid "Any encrypted tokens"
msgstr ""
msgid "Appearance"
msgstr "Görünüm"
-msgid "Application"
+msgid "Appearance was successfully created."
msgstr ""
+msgid "Appearance was successfully updated."
+msgstr ""
+
+msgid "Application"
+msgstr "Uygulama"
+
msgid "Application ID"
+msgstr "Uygulama KimliÄŸi"
+
+msgid "Application settings saved successfully"
msgstr ""
-msgid "Application: %{name}"
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
msgstr ""
+msgid "Application: %{name}"
+msgstr "Uygulama: %{name}"
+
msgid "Applications"
msgstr "Uygulamalar"
msgid "Applied"
-msgstr ""
+msgstr "Uygulandı"
msgid "Apply suggestion"
+msgstr "Öneriyi uygula"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d üye"
+msgstr[1] "%d üye"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] "Bu üyenin onayları iptal edilmez."
+msgstr[1] "Bu üyenin onayları iptal edilmez."
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
msgstr ""
-msgid "Approvals"
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d üye"
+msgstr[1] "%d üye"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
msgstr ""
+msgid "ApprovalRule|Members"
+msgstr "Kullanıcılar"
+
+msgid "ApprovalRule|Name"
+msgstr "Ä°sim"
+
+msgid "ApprovalRule|No. approvals required"
+msgstr "Onay gerekli deÄŸil"
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr "örneğin. QA, Güvenlik vb."
+
+msgid "Approvals"
+msgstr "Onaylar"
+
msgid "Approvals required"
-msgstr ""
+msgstr "Onaylar gerekli"
msgid "Approvers"
-msgstr ""
+msgstr "Onaylayanlar"
msgid "Apr"
msgstr "Nis"
@@ -945,36 +1229,60 @@ msgstr "Nis"
msgid "April"
msgstr "Nisan"
-msgid "Archived project! Repository and other project resources are read-only"
+msgid "Archive jobs"
+msgstr "ArÅŸiv iÅŸleri"
+
+msgid "Archive project"
msgstr ""
+msgid "Archived project! Repository and other project resources are read-only"
+msgstr "Proje arşivlendi! Depo ve diğer proje kaynakları sadece okunur"
+
msgid "Archived projects"
+msgstr "ArÅŸivlenmiÅŸ projeler"
+
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
msgstr ""
msgid "Are you sure"
+msgstr "Emin misiniz"
+
+msgid "Are you sure that you want to archive this project?"
msgstr ""
-msgid "Are you sure you want to delete this pipeline schedule?"
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Bu iş hattı planını silmek istediğinizden emin misiniz?"
+
msgid "Are you sure you want to erase this build?"
-msgstr ""
+msgstr "Bu yapıyı silmek istediğinizden emin misiniz?"
msgid "Are you sure you want to lose unsaved changes?"
-msgstr ""
+msgstr "KaydedilmemiÅŸ deÄŸiÅŸiklikleri kaybetmek istediÄŸinizden emin misiniz?"
msgid "Are you sure you want to lose your issue information?"
+msgstr "Sorun bilgilerinizi kaybetmek istediÄŸinizden emin misiniz?"
+
+msgid "Are you sure you want to permanently delete this license?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
-msgstr ""
+msgstr "Genel anahtarı yeniden oluşturmak istediğinizden emin misiniz? Yansıtma işleminin tekrar çalışması için önce uzak sunucudaki ortak anahtarı güncellemeniz gerekecektir."
msgid "Are you sure you want to remove %{group_name}?"
+msgstr "%{group_name} grubunu silmek istediÄŸinizden emin misiniz?"
+
+msgid "Are you sure you want to remove approver %{name}"
msgstr ""
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -982,19 +1290,22 @@ msgid "Are you sure you want to remove the attachment?"
msgstr ""
msgid "Are you sure you want to remove this identity?"
-msgstr ""
+msgstr "Bu kimliği kaldırmak istediğinizden emin misiniz?"
msgid "Are you sure you want to reset registration token?"
+msgstr "Kayıt erişim anahtarını sıfırlamak istediğinizden emin misiniz?"
+
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
msgstr ""
msgid "Are you sure you want to reset the health check token?"
-msgstr ""
+msgstr "Sağlık kontrolü erişim anahtarını sıfırlamak istediğinizden emin misiniz?"
msgid "Are you sure you want to stop this environment?"
msgstr ""
msgid "Are you sure you want to unlock %{path_lock_path}?"
-msgstr ""
+msgstr "%{path_lock_path} silmek istediÄŸinizden emin misiniz?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
msgstr ""
@@ -1003,13 +1314,13 @@ msgid "Are you sure?"
msgstr "Emin misiniz?"
msgid "Artifact ID"
-msgstr ""
+msgstr "Yapı Kimliği"
msgid "Artifacts"
-msgstr ""
+msgstr "Yapılar"
msgid "Ascending"
-msgstr ""
+msgstr "Artan"
msgid "Ask your group maintainer to set up a group Runner."
msgstr ""
@@ -1018,7 +1329,7 @@ msgid "Assertion consumer service URL"
msgstr ""
msgid "Assets"
-msgstr ""
+msgstr "Varlıklar"
msgid "Assign custom color like #FF0000"
msgstr "#FF0000 gibi özel renk ata"
@@ -1027,46 +1338,46 @@ msgid "Assign labels"
msgstr "Etiket tanımla"
msgid "Assign milestone"
-msgstr ""
+msgstr "Kilometre taşı ata"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "Bu dönüm noktasına bazı sorunları atayın."
msgid "Assign to"
msgstr "Ata"
msgid "Assigned Issues"
-msgstr ""
+msgstr "Atanan sorunlar"
msgid "Assigned Merge Requests"
-msgstr ""
-
-msgid "Assigned to :name"
-msgstr ""
+msgstr "Atanan Karşılaştırma İstekleri"
msgid "Assigned to me"
-msgstr ""
+msgstr "Bana atanan"
msgid "Assignee"
-msgstr ""
+msgstr "Atanan"
msgid "Assignee lists not available with your current license"
-msgstr ""
+msgstr "Atanan listeleri mevcut lisansınızla birlikte kullanılamıyor"
msgid "Assignee lists show all issues assigned to the selected user."
-msgstr ""
+msgstr "Atanan listeleri, seçilen kullanıcıya atanan tüm sorunları gösterir."
msgid "Assignee(s)"
+msgstr "Atanan(lar)"
+
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
msgstr ""
msgid "Attach a file"
-msgstr ""
+msgstr "Bir dosya ekle"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Sürükleyip bırakarak bir dosya ekle veya %{upload_link}"
msgid "Audit Events"
-msgstr ""
+msgstr "Denetim Etkinlikleri"
msgid "Aug"
msgstr "AÄŸustos"
@@ -1074,65 +1385,74 @@ msgstr "AÄŸustos"
msgid "August"
msgstr "AÄŸustos"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Kimlik Doğrulama Günlüğü"
-msgid "Authentication log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
+msgid "Authentication log"
+msgstr "Kimlik Doğrulama Günlüğü"
+
msgid "Authentication method"
+msgstr "Kimlik doğrulama yöntemi"
+
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
msgstr ""
msgid "Author"
msgstr "Yazar"
msgid "Authorization code:"
-msgstr ""
+msgstr "Yetkilendirme kodu:"
msgid "Authorization key"
-msgstr ""
+msgstr "Yetkilendirme anahtarı"
msgid "Authorization was granted by entering your username and password in the application."
-msgstr ""
+msgstr "Uygulamaya kullanıcı adınız ve şifreniz girilererek yetki verildi."
msgid "Authorize"
-msgstr ""
+msgstr "Yetki Ver"
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
-msgid "Authorized At"
+msgid "Authorized %{new_chat_name}"
msgstr ""
+msgid "Authorized At"
+msgstr "Yetkili"
+
msgid "Authorized applications (%{size})"
-msgstr ""
+msgstr "Yetkili uygulamalar (%{size})"
msgid "Authors: %{authors}"
msgstr "Yazarlar: %{authors}"
msgid "Auto DevOps"
-msgstr ""
+msgstr "Otomatik DevOps"
msgid "Auto DevOps enabled"
-msgstr ""
+msgstr "Auto DevOps etkinleÅŸtirildi"
msgid "Auto DevOps, runners and job artifacts"
msgstr ""
msgid "Auto-cancel redundant, pending pipelines"
-msgstr ""
+msgstr "Yedekli, bekleyen iş hatlarını otomatik olarak iptal et"
msgid "AutoDevOps|Auto DevOps"
-msgstr ""
+msgstr "Otomatik DevOps"
msgid "AutoDevOps|Auto DevOps documentation"
-msgstr ""
+msgstr "Otomatik DevOps belgeleleri"
msgid "AutoDevOps|Enable in settings"
-msgstr ""
+msgstr "AutoDevOps|Ayarlardan etkinleÅŸtirin"
msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
msgstr ""
@@ -1143,20 +1463,14 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
msgid "Automatically marked as default internal user"
+msgstr "Otomatik olarak varsayılan kullanıcı olarak işaretlendi"
+
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
-msgstr ""
+msgstr "Otomatik olarak çözüldü"
msgid "Available"
msgstr "Kullanılabilir"
@@ -1180,104 +1494,110 @@ msgid "Avatar will be removed. Are you sure?"
msgstr "Avatar kaldırılacak. Emin misiniz?"
msgid "Average per day: %{average}"
-msgstr ""
+msgstr "Günlük ortalama: %{average}"
msgid "Background Color"
-msgstr ""
+msgstr "Arkaplan Rengi"
msgid "Background Jobs"
-msgstr ""
+msgstr "Arka plan iÅŸleri"
msgid "Background color"
-msgstr ""
+msgstr "Arkaplan rengi"
msgid "Badges"
-msgstr ""
+msgstr "Rozetler"
msgid "Badges|A new badge was added."
-msgstr ""
+msgstr "Badges|Yeni bir rozet eklendi."
msgid "Badges|Add badge"
-msgstr ""
+msgstr "Badges|Rozet ekle"
msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
-msgstr ""
+msgstr "Badges|Rozetin eklenmesi başarısız oldu, lütfen girilen bağlantıları kontrol edin ve tekrar deneyin."
msgid "Badges|Badge image URL"
-msgstr ""
+msgstr "Badges|Rozet resim bağlantısı"
msgid "Badges|Badge image preview"
-msgstr ""
+msgstr "Rozet resmi önizlemesi"
msgid "Badges|Delete badge"
-msgstr ""
+msgstr "Rozeti sil"
msgid "Badges|Delete badge?"
-msgstr ""
+msgstr "Rozet silinsin mi?"
msgid "Badges|Deleting the badge failed, please try again."
-msgstr ""
+msgstr "Rozet resmi silinemedi, lütfen daha sonra tekrar deneyin."
msgid "Badges|Group Badge"
-msgstr ""
+msgstr "Grup Rozeti"
msgid "Badges|Link"
-msgstr ""
+msgstr "Bağlantı"
msgid "Badges|No badge image"
-msgstr ""
+msgstr "Rozet resmi yok"
msgid "Badges|No image to preview"
-msgstr ""
+msgstr "Önizleme için resim yok"
msgid "Badges|Please fill in a valid URL"
-msgstr ""
+msgstr "Lütfen geçerli bir URL girin"
msgid "Badges|Project Badge"
-msgstr ""
+msgstr "Proje Rozeti"
msgid "Badges|Reload badge image"
-msgstr ""
+msgstr "Rozet resmini yeniden yükle"
msgid "Badges|Save changes"
-msgstr ""
+msgstr "DeÄŸiÅŸiklikleri kaydet"
msgid "Badges|Saving the badge failed, please check the entered URLs and try again."
-msgstr ""
+msgstr "Rozetin kaydedilmesi başarısız oldu, lütfen girilen URL'leri kontrol edin ve tekrar deneyin."
msgid "Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}"
msgstr ""
msgid "Badges|The badge was deleted."
-msgstr ""
+msgstr "Rozet silindi."
msgid "Badges|The badge was saved."
-msgstr ""
+msgstr "Rozet kaydedildi."
msgid "Badges|This group has no badges"
-msgstr ""
+msgstr "Bu grubun rozeti yok"
msgid "Badges|This project has no badges"
-msgstr ""
+msgstr "Bu projenin rozeti yok"
msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored."
-msgstr ""
+msgstr "Bu rozeti sileceksiniz. Silinen rozetler geri <strong>yüklenemez</strong>."
msgid "Badges|Your badges"
-msgstr ""
+msgstr "Rozetleriniz"
msgid "Badges|e.g. %{exampleUrl}"
-msgstr ""
+msgstr "örn. %{exampleUrl}"
msgid "BatchComments|Delete all pending comments"
-msgstr ""
+msgstr "Bekleyen tüm yorumları sil"
msgid "BatchComments|Discard review?"
-msgstr ""
+msgstr "İnceleme atılsın mı?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1288,70 +1608,70 @@ msgid "Below you will find all the groups that are public."
msgstr ""
msgid "Billing"
-msgstr ""
+msgstr "Fatura"
msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
-msgstr ""
+msgstr "%{group_name} şu an %{plan_link} planında."
msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available."
-msgstr ""
+msgstr "Bazı planlar için şu anda otomatik olarak düşürme ve yükseltme yapılamaz."
msgid "BillingPlans|Current plan"
-msgstr ""
+msgstr "Mevcut plan"
msgid "BillingPlans|Customer Support"
-msgstr ""
+msgstr "Müşteri Desteği"
msgid "BillingPlans|Downgrade"
-msgstr ""
+msgstr "Mevcut Planı Düşür"
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
-msgstr ""
+msgstr "%{faq_link}'i okuyarak her plan hakkında daha fazla bilgi edinin veya GitLab.com Altın paketinin ücretsiz 30 günlük deneme sürümünü başlatın."
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
-msgstr ""
+msgstr "%{faq_link}'i okuyarak her bir ödeme planı hakkında daha fazla bilgi edinin."
msgid "BillingPlans|Manage plan"
-msgstr ""
+msgstr "Planı yönet"
msgid "BillingPlans|Please contact %{customer_support_link} in that case."
-msgstr ""
+msgstr "%{customer_support_link} bu durumda lütfen irtibat kurun."
msgid "BillingPlans|See all %{plan_name} features"
-msgstr ""
+msgstr "%{plan_name} planı için bütün özellikleri görüntüle"
msgid "BillingPlans|This group uses the plan associated with its parent group."
-msgstr ""
+msgstr "Bu grup üst grupla ilişkili planı kullanır."
msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
-msgstr ""
+msgstr "Bu grubun planını yönetmek için, %{parent_billing_page_link} faturalandırma bölümünü ziyaret edin."
msgid "BillingPlans|Upgrade"
-msgstr ""
+msgstr "Yükselt"
msgid "BillingPlans|You are currently on the %{plan_link} plan."
-msgstr ""
+msgstr "Şu an %{plan_link} plan kullanıyorsunuz."
msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}"
-msgstr ""
+msgstr "GitLab.com deneme sürümünüzün süresi %{expiration_date} tarihinde doldu. %{learn_more_text}"
msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
-msgstr ""
+msgstr "Altın paket deneme planınız <strong>%{expiration_date} tarihinden sonra sona erecek</strong>. %{features_link}'i okuyarak GitLab.com Altın paket hakkında daha fazla bilgi edinebilirsiniz."
msgid "BillingPlans|features"
-msgstr ""
+msgstr "özellikler"
msgid "BillingPlans|frequently asked questions"
-msgstr ""
+msgstr "sık sorulan sorular"
msgid "BillingPlans|monthly"
-msgstr ""
+msgstr "aylık"
msgid "BillingPlans|paid annually at %{price_per_year}"
-msgstr ""
+msgstr "yıllık ücret %{price_per_year}"
msgid "BillingPlans|per user"
-msgstr ""
+msgstr "kullanıcı başına"
msgid "Bitbucket Server Import"
msgstr ""
@@ -1360,16 +1680,16 @@ msgid "Bitbucket import"
msgstr ""
msgid "Block"
-msgstr ""
+msgstr "Engelle"
msgid "Blocked"
-msgstr ""
+msgstr "EngellenmiÅŸ"
msgid "Blog"
-msgstr ""
+msgstr "Blog"
msgid "Boards"
-msgstr ""
+msgstr "Panolar"
msgid "Branch %{branchName} was not found in this project's repository."
msgstr ""
@@ -1378,202 +1698,220 @@ msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy
msgstr ""
msgid "Branch has changed"
-msgstr ""
+msgstr "Dal deÄŸiÅŸti"
msgid "Branch is already taken"
-msgstr ""
+msgstr "Dal zaten alınmış"
msgid "Branch name"
-msgstr ""
+msgstr "Dal adı"
msgid "BranchSwitcherPlaceholder|Search branches"
-msgstr ""
+msgstr "Dallarda ara"
msgid "BranchSwitcherTitle|Switch branch"
-msgstr ""
+msgstr "Dalı değiştir"
msgid "Branches"
-msgstr ""
+msgstr "Dallar"
msgid "Branches|Active"
-msgstr ""
+msgstr "Etkin"
msgid "Branches|Active branches"
-msgstr ""
+msgstr "Etkin dallar"
msgid "Branches|All"
-msgstr ""
+msgstr "Tümü"
msgid "Branches|Cant find HEAD commit for this branch"
-msgstr ""
+msgstr "Bu dal için HEAD işlemi bulunamadı"
msgid "Branches|Compare"
-msgstr ""
+msgstr "Karşılaştır"
msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
-msgstr ""
+msgstr "'%{default_branch}' ile birleştirilmiş tüm dalları sil"
msgid "Branches|Delete branch"
-msgstr ""
+msgstr "Dalı sil"
msgid "Branches|Delete merged branches"
-msgstr ""
+msgstr "Birleştirilmiş dalları sil"
msgid "Branches|Delete protected branch"
-msgstr ""
+msgstr "Korumalı dalı sil"
msgid "Branches|Delete protected branch '%{branch_name}'?"
-msgstr ""
+msgstr "Korumalı '%{branch_name}' dalı silinsin mi?"
msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
-msgstr ""
+msgstr "'%{branch_name}' dalını siliyorsunuz ve bu geri alınamaz. Emin misiniz?"
msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
-msgstr ""
+msgstr "Birleştirilmiş dalı siliyorsunuz ve bu geri alınamaz. Emin misiniz?"
msgid "Branches|Filter by branch name"
-msgstr ""
+msgstr "Dal adına göre süz"
msgid "Branches|Merged into %{default_branch}"
-msgstr ""
+msgstr "%{default_branch} dalıyla birleştir"
msgid "Branches|New branch"
-msgstr ""
+msgstr "Yeni dal"
msgid "Branches|No branches to show"
-msgstr ""
+msgstr "Gösterilecek dal yok"
msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
-msgstr ""
+msgstr "Kabul edip, %{delete_protected_branch} düğmesine tıkladığınızda yapılan işlem geri alınıp kurtarılamaz."
msgid "Branches|Only a project maintainer or owner can delete a protected branch"
-msgstr ""
+msgstr "Sadece bir proje yöneticisi veya sahibi korunan bir dalı silebilir"
msgid "Branches|Overview"
-msgstr ""
+msgstr "Genel Bakış"
msgid "Branches|Protected branches can be managed in %{project_settings_link}."
-msgstr ""
+msgstr "Korumalı dallar %{project_settings_link} bağlantısından yönetilebilir."
msgid "Branches|Show active branches"
-msgstr ""
+msgstr "Etkin dalları göster"
msgid "Branches|Show all branches"
-msgstr ""
+msgstr "Tüm dalları göster"
msgid "Branches|Show more active branches"
-msgstr ""
+msgstr "Daha fazla etkin dalı göster"
msgid "Branches|Show more stale branches"
-msgstr ""
+msgstr "Daha fazla eskimiş dalı göster"
msgid "Branches|Show overview of the branches"
-msgstr ""
+msgstr "Dalların genel bakışını göster"
msgid "Branches|Show stale branches"
-msgstr ""
+msgstr "Eskimiş dalları göster"
msgid "Branches|Sort by"
-msgstr ""
+msgstr "Sıralama"
msgid "Branches|Stale"
-msgstr ""
+msgstr "EskimiÅŸ"
msgid "Branches|Stale branches"
-msgstr ""
+msgstr "EskimiÅŸ dallar"
msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart."
-msgstr ""
+msgstr "Dal otomatik olarak güncellenemedi çünkü akışta uyuşmazlıklar var."
msgid "Branches|The default branch cannot be deleted"
-msgstr ""
+msgstr "Öntanımlı dal silinemez"
msgid "Branches|This branch hasn’t been merged into %{default_branch}."
-msgstr ""
+msgstr "Bu dal %{default_branch} ile birleÅŸtirilmedi."
msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
-msgstr ""
+msgstr "Veri kaybını önlemek için, bu dalı silmeden önce birleştirmeyi düşünün."
msgid "Branches|To confirm, type %{branch_name_confirmation}:"
-msgstr ""
+msgstr "Kabul etmek için %{branch_name_confirmation} yazın:"
msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above."
-msgstr ""
+msgstr "Yerel değişikliklerden vazgeçip akış sürümüyle dalın üzerine yazmak için, buradan silin ve yukarıdan 'Şimdi Güncelle'yi seçin."
msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
-msgstr ""
+msgstr "Korumalı olan %{branch_name} dalını kalıcı olarak silmek üzeresiniz."
msgid "Branches|diverged from upstream"
-msgstr ""
+msgstr "akıştan sapmış"
msgid "Branches|merged"
-msgstr ""
+msgstr "birleÅŸtirildi"
msgid "Branches|project settings"
-msgstr ""
+msgstr "proje ayarları"
msgid "Branches|protected"
+msgstr "korumalı"
+
+msgid "Broadcast Message was successfully created."
msgstr ""
-msgid "Browse Directory"
+msgid "Broadcast Message was successfully updated."
msgstr ""
+msgid "Browse Directory"
+msgstr "Dizine Gözat"
+
msgid "Browse File"
-msgstr ""
+msgstr "Dosyaya Gözat"
msgid "Browse Files"
-msgstr ""
+msgstr "Dosyalara Gözat"
msgid "Browse files"
-msgstr ""
+msgstr "Dosyalara gözat"
msgid "Built-in"
-msgstr ""
+msgstr "Dahili"
msgid "Business"
-msgstr ""
+msgstr "Ä°ÅŸ"
msgid "Business metrics (Custom)"
+msgstr "İş ölçümleri (Özel)"
+
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr "Öntanımlı olarak GitLab, e-postaları HTML ve düz metin biçimlerinde gönderir, böylece posta istemcileri hangi biçimi kullanacaklarını seçebilir. Yalnızca düz metin biçiminde e-postalar göndermek istiyorsanız bu seçeneği devre dışı bırakın."
+
msgid "ByAuthor|by"
-msgstr ""
+msgstr "tarafından"
msgid "CHANGELOG"
-msgstr ""
+msgstr "DEĞİŞİKLİKGÜNLÜĞÜ"
msgid "CI / CD"
-msgstr ""
+msgstr "CI/CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "CI / CD Çizelgeleri"
msgid "CI / CD Settings"
-msgstr ""
+msgstr "CI / CD Ayarları"
msgid "CI Lint"
+msgstr "CI Lint"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
-msgstr ""
+msgstr "CI, yukarda belirtilen kimlik bilgileri ile çalışacak."
msgid "CI/CD"
-msgstr ""
+msgstr "CI/CD"
msgid "CI/CD configuration"
-msgstr ""
+msgstr "CI/CD ayarları"
msgid "CI/CD for external repo"
-msgstr ""
+msgstr "CI/CD için harici depo"
msgid "CI/CD settings"
-msgstr ""
+msgstr "CI / CD ayarları"
msgid "CICD|Auto DevOps"
-msgstr ""
+msgstr "Otomatik DevOps"
msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
msgstr ""
@@ -1582,7 +1920,7 @@ msgid "CICD|Automatic deployment to staging, manual deployment to production"
msgstr ""
msgid "CICD|Continuous deployment to production"
-msgstr ""
+msgstr "Üretimde sürekli dağıtım"
msgid "CICD|Continuous deployment to production using timed incremental rollout"
msgstr ""
@@ -1590,14 +1928,17 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
-msgid "CICD|Deployment strategy"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
msgstr ""
+msgid "CICD|Deployment strategy"
+msgstr "Dağıtım stratejisi"
+
msgid "CICD|Jobs"
-msgstr ""
+msgstr "Ä°ÅŸler"
msgid "CICD|Learn more about Auto DevOps"
-msgstr ""
+msgstr "Otomatik DevOps hakkında daha fazla bilgi edinin"
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
msgstr ""
@@ -1605,6 +1946,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1612,75 +1956,102 @@ msgid "CONTRIBUTING"
msgstr ""
msgid "Callback URL"
-msgstr ""
+msgstr "Geridönüş bağlantısı"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "Birleştirme isteği için gereken onaylayıcıları ve onayları geçersiz kılabilir"
msgid "Can't find HEAD commit for this branch"
+msgstr "Bu dal için HEAD işlemi bulunamadı"
+
+msgid "Can't remove group members without group managed account"
msgstr ""
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
msgid "Cancel"
-msgstr ""
+msgstr "Ä°ptal"
msgid "Cancel this job"
-msgstr ""
+msgstr "Bu iÅŸi iptal et"
msgid "Cannot be merged automatically"
-msgstr ""
+msgstr "Otomatik olarak birleÅŸtirilemez"
-msgid "Cannot modify managed Kubernetes cluster"
+msgid "Cannot create the abuse report. The user has been deleted."
msgstr ""
-msgid "Certificate"
+msgid "Cannot create the abuse report. This user has been blocked."
msgstr ""
-msgid "Certificate (PEM)"
+msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr "Görüntü oluşturulamıyor. En yüksek karakter sayısı (%{charLimit}) aşıldı."
+
+msgid "Cannot skip two factor authentication setup"
msgstr ""
+msgid "Capacity threshold"
+msgstr "Kapasite eÅŸiÄŸi"
+
+msgid "Certificate"
+msgstr "Sertifika"
+
+msgid "Certificate (PEM)"
+msgstr "Sertifika (PEM)"
+
msgid "Change Weight"
+msgstr "Ağırlığı değiştir"
+
+msgid "Change path"
msgstr ""
msgid "Change permissions"
-msgstr ""
+msgstr "Ä°zinleri deÄŸiÅŸtir"
msgid "Change template"
-msgstr ""
+msgstr "Åžablonu deÄŸiÅŸtir"
msgid "Change this value to influence how frequently the GitLab UI polls for updates."
-msgstr ""
+msgstr "GitLab UI'nin güncellemeler için ne sıklıkla sorguladığını etkilemek için bu değeri değiştirin."
msgid "ChangeTypeActionLabel|Pick into branch"
-msgstr ""
+msgstr "Dalı seç"
msgid "ChangeTypeActionLabel|Revert in branch"
-msgstr ""
+msgstr "Dalı geri al"
msgid "ChangeTypeAction|Cherry-pick"
msgstr ""
msgid "ChangeTypeAction|Revert"
-msgstr ""
+msgstr "Geri döndür"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr "DeÄŸiÅŸiklikler"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
msgid "Changes suppressed. Click to show."
-msgstr ""
+msgstr "Değişiklikler bastırıldı. Göstermek için tıkla."
msgid "Charts"
-msgstr ""
+msgstr "Grafikler"
msgid "Chat"
+msgstr "Sohbet"
+
+msgid "Check again"
+msgstr "Tekrar kontrol et"
+
+msgid "Check feature availability on namespace plan"
msgstr ""
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
@@ -1693,10 +2064,10 @@ msgid "Checking %{text} availability…"
msgstr ""
msgid "Checking approval status"
-msgstr ""
+msgstr "Onay durumunu kontrol et"
msgid "Checking branch availability..."
-msgstr ""
+msgstr "Dal uygunluÄŸu kontrol ediliyor..."
msgid "Cherry-pick this commit"
msgstr ""
@@ -1710,17 +2081,14 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
msgid "Choose a file"
-msgstr ""
+msgstr "Bir dosya seç"
msgid "Choose a role permission"
-msgstr ""
+msgstr "İzin için bir rol seç"
msgid "Choose a template..."
msgstr ""
@@ -1729,43 +2097,43 @@ msgid "Choose a type..."
msgstr ""
msgid "Choose any color."
-msgstr ""
+msgstr "Herhangi bir renk seçin."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
-msgstr ""
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr "Görünürlük seviyesini seçin, proje özelliklerini etkinleştirin/devre dışı bırakın (sorunlar, depo, wiki, parçacıklar) ve izinleri ayarlayın."
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr ""
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "Bir grubun genel bakış sayfasında hangi içeriği görmek istediğinizi seçin"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
-msgstr ""
+msgstr "iptal edildi"
msgid "CiStatusLabel|created"
-msgstr ""
+msgstr "oluÅŸturuldu"
msgid "CiStatusLabel|delayed"
msgstr ""
msgid "CiStatusLabel|failed"
-msgstr ""
+msgstr "başarısız"
msgid "CiStatusLabel|manual action"
msgstr ""
@@ -1777,10 +2145,13 @@ msgid "CiStatusLabel|passed with warnings"
msgstr ""
msgid "CiStatusLabel|pending"
+msgstr "bekliyor"
+
+msgid "CiStatusLabel|preparing"
msgstr ""
msgid "CiStatusLabel|skipped"
-msgstr ""
+msgstr "atlandı"
msgid "CiStatusLabel|waiting for delayed job"
msgstr ""
@@ -1792,82 +2163,97 @@ msgid "CiStatusText|blocked"
msgstr ""
msgid "CiStatusText|canceled"
-msgstr ""
+msgstr "iptal edildi"
msgid "CiStatusText|created"
-msgstr ""
+msgstr "oluÅŸturuldu"
msgid "CiStatusText|delayed"
msgstr ""
msgid "CiStatusText|failed"
-msgstr ""
+msgstr "başarısız"
msgid "CiStatusText|manual"
-msgstr ""
+msgstr "kılavuz"
msgid "CiStatusText|passed"
-msgstr ""
+msgstr "geçti"
msgid "CiStatusText|pending"
+msgstr "bekliyor"
+
+msgid "CiStatusText|preparing"
msgstr ""
msgid "CiStatusText|skipped"
-msgstr ""
+msgstr "atlandı"
msgid "CiStatus|running"
-msgstr ""
+msgstr "çalışıyor"
msgid "CiVariables|Input variable key"
-msgstr ""
+msgstr "Değişken anahtarını girin"
msgid "CiVariables|Input variable value"
-msgstr ""
+msgstr "DeÄŸiÅŸken deÄŸerini girin"
msgid "CiVariables|Remove variable row"
+msgstr "Değişken satırı kaldırın"
+
+msgid "CiVariables|This variable will not be masked"
msgstr ""
msgid "CiVariable|* (All environments)"
-msgstr ""
+msgstr "* (Tüm ortamlar)"
msgid "CiVariable|All environments"
-msgstr ""
+msgstr "Tüm ortamlar"
msgid "CiVariable|Create wildcard"
-msgstr ""
+msgstr "Joker oluÅŸtur"
msgid "CiVariable|Error occurred while saving variables"
+msgstr "DeÄŸiÅŸkenler kaydedilirken hata oluÅŸtu"
+
+msgid "CiVariable|Masked"
msgstr ""
msgid "CiVariable|New environment"
-msgstr ""
+msgstr "Yeni ortam"
msgid "CiVariable|Protected"
-msgstr ""
+msgstr "Korumalı"
msgid "CiVariable|Search environments"
+msgstr "Ortamları ara"
+
+msgid "CiVariable|Toggle masked"
msgstr ""
msgid "CiVariable|Toggle protected"
-msgstr ""
+msgstr "Korumayı değiştir"
msgid "CiVariable|Validation failed"
+msgstr "Doğrulama başarısız"
+
+msgid "Classification Label (optional)"
msgstr ""
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
-msgstr ""
+msgstr "kullanılamıyor: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "Temizle"
msgid "Clear input"
-msgstr ""
+msgstr "GiriÅŸi temizle"
msgid "Clear search"
-msgstr ""
+msgstr "Aramayı temizle"
msgid "Clear search input"
-msgstr ""
+msgstr "Arama giriÅŸini temizle"
msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
msgstr ""
@@ -1885,7 +2271,7 @@ msgid "Click the button below to begin the install process by navigating to the
msgstr ""
msgid "Click to expand it."
-msgstr ""
+msgstr "Genişletmek için tıklayın."
msgid "Click to expand text"
msgstr ""
@@ -1903,7 +2289,7 @@ msgid "Clients"
msgstr ""
msgid "Clone"
-msgstr ""
+msgstr "Klonla"
msgid "Clone repository"
msgstr ""
@@ -1911,8 +2297,11 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr "KRB5 ile klonla"
+
msgid "Clone with SSH"
-msgstr ""
+msgstr "SSH ile klonla"
msgid "Close"
msgstr ""
@@ -1924,13 +2313,10 @@ msgid "Close milestone"
msgstr ""
msgid "Closed"
-msgstr ""
-
-msgid "Closed (moved)"
-msgstr ""
+msgstr "Kapalı"
msgid "Closed issues"
-msgstr ""
+msgstr "Kapalı sorunlar"
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -1944,13 +2330,13 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
+msgstr "API adresi"
+
+msgid "ClusterIntegration|API URL should be a valid http/https url."
msgstr ""
msgid "ClusterIntegration|Add Kubernetes cluster"
@@ -1968,18 +2354,15 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
-msgstr ""
-
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr ""
+msgstr "Alternatifler"
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr "Proje alanlarını almaya çalışırken hata oluştu: %{error}"
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -1987,7 +2370,7 @@ msgid "ClusterIntegration|An error occurred while trying to fetch zone machine t
msgstr ""
msgid "ClusterIntegration|Applications"
-msgstr ""
+msgstr "Uygulamalar"
msgid "ClusterIntegration|Apply for credit"
msgstr ""
@@ -1996,13 +2379,13 @@ msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluste
msgstr ""
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "Temel etki alanı"
msgid "ClusterIntegration|CA Certificate"
-msgstr ""
+msgstr "CA Sertifikası"
msgid "ClusterIntegration|Cert-Manager"
-msgstr ""
+msgstr "Sertifika-Yöneticisi"
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
msgstr ""
@@ -2016,35 +2399,41 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
msgid "ClusterIntegration|Copy API URL"
-msgstr ""
+msgstr "API URL'sini Kopyala"
msgid "ClusterIntegration|Copy CA Certificate"
-msgstr ""
+msgstr "CA Sertifikasını Kopyala"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
-msgstr ""
+msgstr "Jupyter Ana Bilgisayar Adını panoya kopyala"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
-msgstr ""
+msgstr "Kubernetes kümesi oluştur"
msgid "ClusterIntegration|Did you know?"
-msgstr ""
+msgstr "Biliyor muydunuz?"
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr ""
@@ -2056,7 +2445,7 @@ msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|Environment scope"
-msgstr ""
+msgstr "Ortam kapsamı"
msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
msgstr ""
@@ -2071,76 +2460,79 @@ msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
msgstr ""
msgid "ClusterIntegration|Fetching machine types"
-msgstr ""
+msgstr "Makine türleri getiriliyor"
msgid "ClusterIntegration|Fetching projects"
-msgstr ""
+msgstr "Projeler getiriliyor"
msgid "ClusterIntegration|Fetching zones"
-msgstr ""
+msgstr "Alanlar getiriliyor"
msgid "ClusterIntegration|GitLab Integration"
-msgstr ""
+msgstr "GitLab Bütünleşmesi"
msgid "ClusterIntegration|GitLab Runner"
-msgstr ""
+msgstr "GitLab Çalıştırıcı"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
-msgstr ""
+msgstr "Google Cloud Platform projesi"
msgid "ClusterIntegration|Google Kubernetes Engine"
-msgstr ""
+msgstr "Google Kubernetes Motoru"
msgid "ClusterIntegration|Google Kubernetes Engine project"
-msgstr ""
+msgstr "Google Kubernetes Motoru projesi"
msgid "ClusterIntegration|Group cluster"
-msgstr ""
+msgstr "Grup kümesi"
msgid "ClusterIntegration|Helm Tiller"
-msgstr ""
+msgstr "Helm Tiller"
msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
msgstr ""
msgid "ClusterIntegration|Hide"
-msgstr ""
+msgstr "Gizle"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
-msgstr ""
+msgstr "Birden fazla küme oluşturuyorsanız ve Otomatik DevOps kullanıyorsanız, %{help_link_start}ilk önce bunu okuyunuz %{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr ""
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr "Kümenizin sağlığını görmek için önce aşağıdan Prometheus'u yüklemelisiniz."
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
msgid "ClusterIntegration|Install"
-msgstr ""
-
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
+msgstr "Yükle"
msgid "ClusterIntegration|Installed"
-msgstr ""
+msgstr "Yüklendi"
msgid "ClusterIntegration|Installing"
+msgstr "Yükleniyor"
+
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
-msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
+msgstr "Kubernetes küme otomasyonunu bütünleştir"
+
msgid "ClusterIntegration|Integration status"
-msgstr ""
+msgstr "Bütünleşme durumu"
msgid "ClusterIntegration|Issuer Email"
msgstr ""
@@ -2149,10 +2541,10 @@ msgid "ClusterIntegration|Issuers represent a certificate authority. You must pr
msgstr ""
msgid "ClusterIntegration|Jupyter Hostname"
-msgstr ""
+msgstr "Jupyter Ana Bilgisayar Adı"
msgid "ClusterIntegration|JupyterHub"
-msgstr ""
+msgstr "JupyterHub"
msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group."
msgstr ""
@@ -2163,26 +2555,23 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
msgstr ""
msgid "ClusterIntegration|Kubernetes cluster"
-msgstr ""
+msgstr "Kubernetes kümesi"
msgid "ClusterIntegration|Kubernetes cluster details"
-msgstr ""
-
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
+msgstr "Kubernetes kümesi detayları"
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
-msgstr ""
+msgstr "Kubernetes kümesi, Google Kubernetes Motoru'nda oluşturuluyor..."
msgid "ClusterIntegration|Kubernetes cluster name"
-msgstr ""
+msgstr "Kubernetes kümesi adı"
msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
msgstr ""
@@ -2209,25 +2598,22 @@ msgid "ClusterIntegration|Let's Encrypt"
msgstr ""
msgid "ClusterIntegration|Machine type"
-msgstr ""
+msgstr "Makine türü"
msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
msgid "ClusterIntegration|Manage"
-msgstr ""
+msgstr "Yönet"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
msgid "ClusterIntegration|No projects found"
-msgstr ""
+msgstr "Proje bulunamadı"
msgid "ClusterIntegration|No projects matched your search"
msgstr ""
@@ -2235,10 +2621,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2247,20 +2633,17 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
msgid "ClusterIntegration|Prometheus"
-msgstr ""
+msgstr "Prometheus"
msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications."
msgstr ""
@@ -2286,7 +2669,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2296,54 +2679,54 @@ msgid "ClusterIntegration|Search machine types"
msgstr ""
msgid "ClusterIntegration|Search projects"
-msgstr ""
+msgstr "Projeleri ara"
msgid "ClusterIntegration|Search zones"
-msgstr ""
+msgstr "Bölgeleri ara"
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|Select machine type"
-msgstr ""
+msgstr "Makine türlerini seç"
msgid "ClusterIntegration|Select project"
-msgstr ""
+msgstr "Proje seç"
msgid "ClusterIntegration|Select project and zone to choose machine type"
-msgstr ""
+msgstr "Makine türü seçimine göre alan ve proje seç"
msgid "ClusterIntegration|Select project to choose zone"
-msgstr ""
+msgstr "Seçili alan için proje seç"
msgid "ClusterIntegration|Select zone"
-msgstr ""
+msgstr "Alan seç"
msgid "ClusterIntegration|Select zone to choose machine type"
+msgstr "Makine türünü seçmek için alan seç"
+
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
msgid "ClusterIntegration|Show"
-msgstr ""
+msgstr "Göster"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
-msgstr ""
+msgstr "%{title} yüklenirken bir sorun oluştu"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2352,10 +2735,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2371,7 +2760,7 @@ msgid "ClusterIntegration|Upgrading"
msgstr ""
msgid "ClusterIntegration|Validating project billing status"
-msgstr ""
+msgstr "Proje fiyatlandırma durumunu doğrula"
msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again."
msgstr ""
@@ -2389,49 +2778,61 @@ msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
msgid "ClusterIntegration|Zone"
-msgstr ""
+msgstr "Alan"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
-msgstr ""
+msgstr "belgeleme"
msgid "ClusterIntegration|help page"
-msgstr ""
+msgstr "yardım sayfası"
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr "fiyatlandırma"
+
msgid "ClusterIntegration|properly configured"
-msgstr ""
+msgstr "düzgün yapılandırılmış"
msgid "ClusterIntegration|sign up"
-msgstr ""
+msgstr "kaydol"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr "Kod Sahipleri"
+
+msgid "Code owner approval is required"
+msgstr "Kod sahibi onayı gerekiyor"
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
-msgstr ""
+msgstr "Daralt"
+
+msgid "Collapse approvers"
+msgstr "Onaylananları daralt"
msgid "Collapse sidebar"
-msgstr ""
+msgstr "Kenar çubuğunu daralt"
msgid "Command line instructions"
-msgstr ""
+msgstr "Komut satırı talimatları"
msgid "Comment"
-msgstr ""
+msgstr "Yorum"
msgid "Comment & close %{noteable_name}"
msgstr ""
@@ -2440,21 +2841,21 @@ msgid "Comment & reopen %{noteable_name}"
msgstr ""
msgid "Comment & resolve discussion"
-msgstr ""
+msgstr "Yorumla ve tartışmayı çöz"
msgid "Comment & unresolve discussion"
-msgstr ""
+msgstr "Yorumla ve tartışmayı devam ettir"
msgid "Comment form position"
-msgstr ""
+msgstr "Yorum formu konumu"
msgid "Comments"
-msgstr ""
+msgstr "Yorumlar"
msgid "Commit"
msgid_plural "Commits"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Ä°ÅŸlem"
+msgstr[1] "Ä°ÅŸlem"
msgid "Commit %{commit_id}"
msgstr ""
@@ -2469,28 +2870,28 @@ msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message"
-msgstr ""
+msgstr "İşlem mesajı"
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
-msgstr ""
+msgstr "%{ref} %{start_time} -%{end_time} için işlem istatistikleri"
msgid "Commit to %{branchName} branch"
msgstr ""
msgid "CommitBoxTitle|Commit"
-msgstr ""
+msgstr "Ä°ÅŸlem"
msgid "CommitMessage|Add %{file_name}"
-msgstr ""
+msgstr "%{file_name} ekle"
msgid "CommitWidget|authored"
msgstr ""
msgid "Commits"
-msgstr ""
+msgstr "Ä°ÅŸlemler"
msgid "Commits feed"
-msgstr ""
+msgstr "İşlem akışı"
msgid "Commits per day hour (UTC)"
msgstr ""
@@ -2508,10 +2909,10 @@ msgid "Commits|Commit: %{commitText}"
msgstr ""
msgid "Commits|History"
-msgstr ""
+msgstr "Geçmiş"
msgid "Commits|No related merge requests found"
-msgstr ""
+msgstr "İlgili birleştirme isteği bulunamadı"
msgid "Committed by"
msgstr ""
@@ -2519,38 +2920,41 @@ msgstr ""
msgid "Commit…"
msgstr ""
-msgid "Compare"
+msgid "Company"
msgstr ""
+msgid "Compare"
+msgstr "Karşılaştır"
+
msgid "Compare Git revisions"
-msgstr ""
+msgstr "Git düzeltmelerini karşılaştır"
msgid "Compare Revisions"
-msgstr ""
+msgstr "Düzeltmeleri karşılaştır"
msgid "Compare changes"
-msgstr ""
+msgstr "Değişiklikleri karşılaştır"
msgid "Compare changes with the last commit"
-msgstr ""
+msgstr "Son işlem ile değişiklikleri karşılaştır"
msgid "Compare changes with the merge request target branch"
msgstr ""
msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
-msgstr ""
+msgstr "%{source_branch} ve %{target_branch} aynıdır."
msgid "CompareBranches|Compare"
-msgstr ""
+msgstr "Karşılaştır"
msgid "CompareBranches|Source"
-msgstr ""
+msgstr "Kaynak"
msgid "CompareBranches|Target"
-msgstr ""
+msgstr "Hedef"
msgid "CompareBranches|There isn't anything to compare."
-msgstr ""
+msgstr "Karşılaştırılacak bir şey bulunamadı."
msgid "Confidential"
msgstr ""
@@ -2580,7 +2984,7 @@ msgid "Configure push mirrors."
msgstr ""
msgid "Configure storage path settings."
-msgstr ""
+msgstr "Depolama dizini ayarlarını yapılandırın."
msgid "Configure the %{link} integration."
msgstr ""
@@ -2588,9 +2992,15 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
-msgid "Connect"
+msgid "Confirm"
msgstr ""
+msgid "Confirmation required"
+msgstr ""
+
+msgid "Connect"
+msgstr "BaÄŸlan"
+
msgid "Connect all repositories"
msgstr ""
@@ -2601,17 +3011,29 @@ msgid "Connect your external repositories, and CI/CD pipelines will run for new
msgstr ""
msgid "Connecting..."
+msgstr "Bağlanıyor…"
+
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
msgstr ""
msgid "Contact sales to upgrade"
+msgstr "Yükseltmek için satıcı ile iletişim kurun"
+
+msgid "Contact your Administrator to upgrade your license."
msgstr ""
msgid "Container Registry"
msgstr ""
-msgid "ContainerRegistry|Created"
+msgid "Container registry images"
msgstr ""
+msgid "ContainerRegistry|Created"
+msgstr "OluÅŸturuldu"
+
msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
msgstr ""
@@ -2622,7 +3044,7 @@ msgid "ContainerRegistry|How to use the Container Registry"
msgstr ""
msgid "ContainerRegistry|Learn more about"
-msgstr ""
+msgstr "Hakkında daha fazlasını öğren"
msgid "ContainerRegistry|No tags in Container Registry for this container image."
msgstr ""
@@ -2631,22 +3053,22 @@ msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload
msgstr ""
msgid "ContainerRegistry|Remove repository"
-msgstr ""
+msgstr "Depoyu kaldır"
msgid "ContainerRegistry|Remove tag"
-msgstr ""
+msgstr "Etiketi kaldır"
msgid "ContainerRegistry|Size"
-msgstr ""
+msgstr "Boyut"
msgid "ContainerRegistry|Tag"
-msgstr ""
+msgstr "Etiket"
msgid "ContainerRegistry|Tag ID"
-msgstr ""
+msgstr "Etiket KimliÄŸi"
msgid "ContainerRegistry|Use different image names"
-msgstr ""
+msgstr "Farklı resim adları kullan"
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
msgstr ""
@@ -2667,13 +3089,16 @@ msgid "Continuous Integration and Deployment"
msgstr ""
msgid "Contribute to GitLab"
-msgstr ""
+msgstr "GitLab'a katkıda bulunun"
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr "Katkı Analizi"
+
msgid "Contribution Charts"
-msgstr ""
+msgstr "Katkı Grafikleri"
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr ""
@@ -2682,10 +3107,10 @@ msgid "Contributions per group member"
msgstr ""
msgid "Contributors"
-msgstr ""
+msgstr "Katkıda Bulunanlar"
msgid "ContributorsPage|%{startDate} – %{endDate}"
-msgstr ""
+msgstr "ContributorsPage|%{startDate} – %{endDate}"
msgid "ContributorsPage|Building repository graph."
msgstr ""
@@ -2694,23 +3119,14 @@ msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limi
msgstr ""
msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
-msgstr ""
+msgstr "ContributorsPage|Lütfen bekleyin, hazır olduğunda bu sayfa otomatik olarak yenilenir."
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2723,35 +3139,35 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr "KRB5 klon URL'sini kopyala"
+
msgid "Copy SSH clone URL"
msgstr ""
msgid "Copy SSH public key"
-msgstr ""
+msgstr "SSH ortak anahtarını kopyala"
msgid "Copy SSH public key to clipboard"
msgstr ""
msgid "Copy URL to clipboard"
-msgstr ""
+msgstr "URL'yi panoya kopyala"
msgid "Copy branch name to clipboard"
-msgstr ""
+msgstr "Dal adını panoya kopyala"
msgid "Copy command to clipboard"
msgstr ""
msgid "Copy commit SHA to clipboard"
-msgstr ""
+msgstr "İşlem SHA'yı panoya kopyala"
msgid "Copy file path to clipboard"
-msgstr ""
-
-msgid "Copy incoming email address to clipboard"
-msgstr ""
+msgstr "Dosya yolunu panoya kopyala"
msgid "Copy link"
-msgstr ""
+msgstr "Bağlantıyı kopyala"
msgid "Copy reference to clipboard"
msgstr ""
@@ -2763,118 +3179,142 @@ msgid "Copy to clipboard"
msgstr ""
msgid "Copy token to clipboard"
+msgstr "Erişim anahtarını panoya kopyala"
+
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
msgstr ""
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
-msgid "Create"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr ""
+msgid "Coverage"
+msgstr "Kapsam"
+
+msgid "Create"
+msgstr "OluÅŸtur"
+
msgid "Create New Directory"
msgstr ""
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
-msgstr ""
+msgid "Create a new branch"
+msgstr "Yeni bir dal oluÅŸtur"
msgid "Create a new issue"
-msgstr ""
+msgstr "Yeni bir sorun oluÅŸtur"
msgid "Create a new repository"
-msgstr ""
+msgstr "Yeni bir depo oluÅŸtur"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
-msgid "Create branch"
+msgid "Create an issue. Issues are created for each alert triggered."
msgstr ""
+msgid "Create branch"
+msgstr "Dal oluÅŸtur"
+
msgid "Create commit"
-msgstr ""
+msgstr "Ä°ÅŸlem oluÅŸtur"
msgid "Create directory"
msgstr ""
msgid "Create empty repository"
-msgstr ""
+msgstr "BoÅŸ depo oluÅŸtur"
msgid "Create epic"
-msgstr ""
+msgstr "Epik oluÅŸtur"
msgid "Create file"
-msgstr ""
+msgstr "Dosya oluÅŸtur"
msgid "Create group"
-msgstr ""
+msgstr "Grup oluÅŸtur"
msgid "Create group label"
-msgstr ""
-
-msgid "Create issue"
-msgstr ""
+msgstr "Grup etiketi oluÅŸtur"
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
msgid "Create merge request"
-msgstr ""
+msgstr "BirleÅŸtirme isteÄŸi oluÅŸtur"
msgid "Create merge request and branch"
-msgstr ""
+msgstr "BirleÅŸtirme isteÄŸi ve dal oluÅŸtur"
msgid "Create milestone"
-msgstr ""
+msgstr "Dönüm noktası oluştur"
msgid "Create new branch"
-msgstr ""
+msgstr "Yeni dal oluÅŸtur"
msgid "Create new directory"
-msgstr ""
+msgstr "Yeni dizin oluÅŸtur"
msgid "Create new file"
-msgstr ""
+msgstr "Yeni dosya oluÅŸtur"
msgid "Create new file or directory"
-msgstr ""
+msgstr "Yeni dosya veya dizin oluÅŸtur"
msgid "Create new label"
-msgstr ""
+msgstr "Yeni etiket oluÅŸtur"
msgid "Create new..."
-msgstr ""
+msgstr "Yeni oluÅŸtur..."
msgid "Create project label"
-msgstr ""
+msgstr "Proje etiketi oluÅŸtur"
msgid "Create your first page"
msgstr ""
msgid "CreateTag|Tag"
-msgstr ""
+msgstr "Etiket"
msgid "CreateTokenToCloneLink|create a personal access token"
-msgstr ""
+msgstr "kiÅŸisel eriÅŸim belirteci oluÅŸtur"
msgid "Created"
-msgstr ""
+msgstr "OluÅŸturdu"
msgid "Created At"
-msgstr ""
+msgstr "OluÅŸturma tarihi"
msgid "Created by me"
msgstr ""
msgid "Created on"
-msgstr ""
+msgstr "OluÅŸturulma tarihi"
msgid "Created on:"
-msgstr ""
+msgstr "OluÅŸturulma tarihi:"
msgid "Creating epic"
msgstr ""
@@ -2888,23 +3328,26 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
msgid "CurrentUser|Profile"
-msgstr ""
+msgstr "Profil"
msgid "CurrentUser|Settings"
-msgstr ""
+msgstr "Ayarlar"
msgid "Custom CI config path"
msgstr ""
msgid "Custom hostname (for private commit emails)"
-msgstr ""
+msgstr "Özel ana bilgisayar adı (özel işleme e-postaları için)"
msgid "Custom notification events"
-msgstr ""
+msgstr "Özel bildirim etkinlikleri"
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 ""
@@ -2916,7 +3359,7 @@ msgid "Custom project templates have not been set up for groups that you are a m
msgstr ""
msgid "Customize colors"
-msgstr ""
+msgstr "Özel renkler"
msgid "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."
msgstr ""
@@ -2925,55 +3368,55 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr ""
msgid "Customize language and region related settings."
-msgstr ""
-
-msgid "Customize your merge request approval settings."
-msgstr ""
+msgstr "Dil ve bölgeyle ilgili ayarları özelleştirin."
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Cycle Analytics"
-msgstr ""
+msgstr "AÅŸama Analizi"
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 ""
+msgstr "Kod"
msgid "CycleAnalyticsStage|Issue"
-msgstr ""
+msgstr "Sorun"
msgid "CycleAnalyticsStage|Plan"
-msgstr ""
+msgstr "Plan"
msgid "CycleAnalyticsStage|Production"
-msgstr ""
+msgstr "Yapım"
msgid "CycleAnalyticsStage|Review"
-msgstr ""
+msgstr "CycleAnalyticsStage|Ä°nceleme"
msgid "CycleAnalyticsStage|Staging"
-msgstr ""
+msgstr "Hazırlama"
msgid "CycleAnalyticsStage|Test"
-msgstr ""
+msgstr "CycleAnalyticsStage|Test"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
-msgstr ""
+msgstr "Kontrol paneli"
msgid "DashboardProjects|All"
-msgstr ""
+msgstr "Tümü"
msgid "DashboardProjects|Personal"
+msgstr "DashboardProjects|KiÅŸisel"
+
+msgid "Dashboards"
msgstr ""
msgid "Data is still calculating..."
-msgstr ""
+msgstr "Veri hala hesaplanıyor..."
msgid "Date picker"
msgstr ""
@@ -2982,10 +3425,10 @@ msgid "Debug"
msgstr ""
msgid "Dec"
-msgstr ""
+msgstr "Ara"
msgid "December"
-msgstr ""
+msgstr "Aralık"
msgid "Decline"
msgstr ""
@@ -2994,17 +3437,26 @@ msgid "Decline and sign out"
msgstr ""
msgid "Default Branch"
+msgstr "Öntanımlı Dal"
+
+msgid "Default artifacts expiration"
msgstr ""
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3039,23 +3491,41 @@ msgid "Delete Package"
msgstr ""
msgid "Delete Snippet"
-msgstr ""
+msgstr "Parçacığı Sil"
msgid "Delete comment"
+msgstr "Yorumu sil"
+
+msgid "Delete license"
msgstr ""
msgid "Delete list"
msgstr ""
msgid "Delete source branch"
-msgstr ""
+msgstr "Kaynak dalı sil"
msgid "Delete this attachment"
-msgstr ""
+msgstr "Bu eki sil"
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3067,14 +3537,17 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
-msgid "DeployKeys|+%{count} others"
+msgid "Deploy key was successfully updated."
msgstr ""
+msgid "DeployKeys|+%{count} others"
+msgstr "+%{count} diÄŸer"
+
msgid "DeployKeys|Current project"
-msgstr ""
+msgstr "Mevcut proje"
msgid "DeployKeys|Deploy key"
-msgstr ""
+msgstr "Dağıtım anahtarı"
msgid "DeployKeys|Enabled deploy keys"
msgstr ""
@@ -3101,7 +3574,7 @@ msgid "DeployKeys|Privately accessible deploy keys"
msgstr ""
msgid "DeployKeys|Project usage"
-msgstr ""
+msgstr "Proje kullanımı"
msgid "DeployKeys|Publicly accessible deploy keys"
msgstr ""
@@ -3110,7 +3583,7 @@ msgid "DeployKeys|Read access only"
msgstr ""
msgid "DeployKeys|Write access allowed"
-msgstr ""
+msgstr "Yazma eriÅŸimine izin verildi"
msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
msgstr ""
@@ -3137,7 +3610,7 @@ msgid "DeployTokens|Create deploy token"
msgstr ""
msgid "DeployTokens|Created"
-msgstr ""
+msgstr "OluÅŸturuldu"
msgid "DeployTokens|Deploy Tokens"
msgstr ""
@@ -3149,13 +3622,13 @@ msgid "DeployTokens|Expires"
msgstr ""
msgid "DeployTokens|Name"
-msgstr ""
+msgstr "Ä°sim"
msgid "DeployTokens|Pick a name for the application, and we'll give you a unique deploy token."
msgstr ""
msgid "DeployTokens|Revoke"
-msgstr ""
+msgstr "Yoksay"
msgid "DeployTokens|Revoke %{name}"
msgstr ""
@@ -3164,7 +3637,7 @@ msgid "DeployTokens|Scopes"
msgstr ""
msgid "DeployTokens|This action cannot be undone."
-msgstr ""
+msgstr "DeployTokens|Bu işlem geri alınamaz."
msgid "DeployTokens|This project has no active Deploy Tokens."
msgstr ""
@@ -3203,22 +3676,55 @@ msgid "Descending"
msgstr ""
msgid "Description"
+msgstr "Açıklama"
+
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
msgstr ""
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
+msgstr "Açıklama:"
+
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
msgstr ""
+msgid "Designs"
+msgstr "Tasarımlar"
+
msgid "Destroy"
msgstr ""
msgid "Details"
-msgstr ""
+msgstr "Ayrıntılar"
msgid "Details (default)"
-msgstr ""
+msgstr "Ayrıntılar (varsayılan)"
msgid "Detect host keys"
msgstr ""
@@ -3230,10 +3736,10 @@ msgid "Diff limits"
msgstr ""
msgid "Diffs|No file name available"
-msgstr ""
+msgstr "Kullanılabilir dosya adı yok"
msgid "Diffs|Something went wrong while fetching diff lines."
-msgstr ""
+msgstr "Değişiklik çizgileri alırken bir şeyler ters gitti."
msgid "Direction"
msgstr ""
@@ -3242,7 +3748,7 @@ msgid "Directory name"
msgstr ""
msgid "Disable"
-msgstr ""
+msgstr "EtkisizleÅŸtir"
msgid "Disable for this project"
msgstr ""
@@ -3289,6 +3795,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3302,7 +3811,7 @@ msgid "Dismiss Merge Request promotion"
msgstr ""
msgid "Dismiss trial promotion"
-msgstr ""
+msgstr "Deneme promosyonunu reddet"
msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
msgstr ""
@@ -3314,10 +3823,10 @@ msgid "Domain"
msgstr ""
msgid "Don't show again"
-msgstr ""
+msgstr "Bir daha gösterme"
msgid "Done"
-msgstr ""
+msgstr "Tamamlandı"
msgid "Download"
msgstr ""
@@ -3328,6 +3837,12 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -3344,13 +3859,13 @@ msgid "DownloadArtifacts|Download"
msgstr ""
msgid "DownloadCommit|Email Patches"
-msgstr ""
+msgstr "E-posta Yamaları"
msgid "DownloadCommit|Plain Diff"
msgstr ""
msgid "DownloadSource|Download"
-msgstr ""
+msgstr "Ä°ndir"
msgid "Downstream"
msgstr ""
@@ -3371,10 +3886,13 @@ msgid "Edit"
msgstr ""
msgid "Edit %{name}"
+msgstr "%{name} düzenle"
+
+msgid "Edit Deploy Key"
msgstr ""
msgid "Edit Label"
-msgstr ""
+msgstr "Etiketi Düzenle"
msgid "Edit Milestone"
msgstr ""
@@ -3383,7 +3901,7 @@ msgid "Edit Pipeline Schedule %{id}"
msgstr ""
msgid "Edit Snippet"
-msgstr ""
+msgstr "Parçacığı Düzenle"
msgid "Edit application"
msgstr ""
@@ -3392,7 +3910,10 @@ msgid "Edit comment"
msgstr ""
msgid "Edit environment"
-msgstr ""
+msgstr "Ortamı düzenle"
+
+msgid "Edit file"
+msgstr "Dosyayı düzenle"
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3404,28 +3925,64 @@ msgid "Edit identity for %{user_name}"
msgstr ""
msgid "Edit issues"
-msgstr ""
+msgstr "Sorunları düzenle"
+
+msgid "Edit public deploy key"
+msgstr "Genel dağıtım anahtarını düzenle"
msgid "Elasticsearch"
-msgstr ""
+msgstr "Esnek arama"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
+msgstr "E-posta"
+
+msgid "Email address"
msgstr ""
msgid "Email patch"
+msgstr "E-posta yaması"
+
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
msgstr ""
-msgid "Emails"
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
msgstr ""
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "Emails"
+msgstr "E-postalar"
+
msgid "Embed"
msgstr ""
msgid "Empty file"
-msgstr ""
+msgstr "BoÅŸ dosya"
msgid "Enable"
msgstr ""
@@ -3433,6 +3990,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr "HTML e-postalarını etkinleştir"
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3452,7 +4012,7 @@ msgid "Enable classification control using an external service"
msgstr ""
msgid "Enable error tracking"
-msgstr ""
+msgstr "Hata izlemeyi etkinleÅŸtir"
msgid "Enable for this project"
msgstr ""
@@ -3460,6 +4020,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr "E-postalarda üstbilgi ve altbilgiyi etkinleştir"
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3469,9 +4035,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3479,7 +4042,7 @@ msgid "Enable the Performance Bar for a given group."
msgstr ""
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "İki aşamalı doğrulamayı etkinleştir"
msgid "Enable usage ping"
msgstr ""
@@ -3487,40 +4050,46 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
-msgid "Enabled"
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled Git access protocols"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enter in your Bitbucket Server URL and personal access token below"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enter the issue description"
+msgid "End date"
msgstr ""
-msgid "Enter the issue title"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enter the merge request description"
+msgid "Enter at least three characters to search"
msgstr ""
-msgid "Enter the merge request title"
+msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
-msgid "Enter your Sentry API URL"
+msgid "Enter the issue description"
msgstr ""
-msgid "Environment variables"
+msgid "Enter the issue title"
+msgstr "Sorun başlığını girin"
+
+msgid "Enter the merge request description"
msgstr ""
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Enter the merge request title"
+msgstr "Birleştirme isteğinin başlığını girin"
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3536,19 +4105,25 @@ msgid "Environments allow you to track deployments of your application %{link_to
msgstr ""
msgid "Environments|An error occurred while fetching the environments."
-msgstr ""
+msgstr "Ortamlar getirilirken bir hata oluÅŸtu."
msgid "Environments|An error occurred while making the request."
-msgstr ""
+msgstr "İstek yapılırken bir hata oluştu."
+
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr "Ortam yeniden dağıtılırken bir hata oluştu, lütfen tekrar deneyin"
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr "Ortamı geri alırken bir hata oluştu, lütfen tekrar deneyin"
msgid "Environments|An error occurred while stopping the environment, please try again"
-msgstr ""
+msgstr "Ortam durdurulurken bir hata oluştu, lütfen tekrar deneyin"
msgid "Environments|Are you sure you want to stop this environment?"
-msgstr ""
+msgstr "Bu ortamı durdurmak istediğinizden emin misiniz?"
msgid "Environments|Commit"
-msgstr ""
+msgstr "Ä°ÅŸlem"
msgid "Environments|Deploy to..."
msgstr ""
@@ -3566,7 +4141,7 @@ msgid "Environments|Environments are places where code gets deployed, such as st
msgstr ""
msgid "Environments|Job"
-msgstr ""
+msgstr "Ä°ÅŸ"
msgid "Environments|Learn more about stopping environments"
msgstr ""
@@ -3592,30 +4167,60 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
-msgid "Environments|Show all"
+msgid "Environments|Rollback environment %{environment_name}?"
msgstr ""
-msgid "Environments|Stop"
+msgid "Environments|Rollback environment %{name}?"
msgstr ""
+msgid "Environments|Show all"
+msgstr "Tümünü göster"
+
+msgid "Environments|Stop"
+msgstr "Durdur"
+
msgid "Environments|Stop environment"
-msgstr ""
+msgstr "Ortamı durdur"
msgid "Environments|Stopping"
msgstr ""
-msgid "Environments|Updated"
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|Updated"
+msgstr "Environments|Güncellendi:"
+
msgid "Environments|You don't have any environments right now"
msgstr ""
@@ -3664,6 +4269,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3671,7 +4279,7 @@ msgid "Error deleting %{issuableType}"
msgstr ""
msgid "Error fetching contributors data."
-msgstr ""
+msgstr "Katkıda bulunanlar verileri alınırken hata oluştu."
msgid "Error fetching labels."
msgstr ""
@@ -3695,10 +4303,10 @@ msgid "Error loading last commit."
msgstr ""
msgid "Error loading markdown preview"
-msgstr ""
+msgstr "Markdown önizlemesi yüklenirken hata oluştu"
msgid "Error loading merge requests."
-msgstr ""
+msgstr "Birleştirme isteği yüklenirken hata oluştu."
msgid "Error loading project data. Please try again."
msgstr ""
@@ -3710,11 +4318,23 @@ msgid "Error loading template."
msgstr ""
msgid "Error occurred when toggling the notification subscription"
+msgstr "Bildirim aboneliÄŸini deÄŸiÅŸtirirken bir sorun meydana geldi"
+
+msgid "Error occurred. User was not blocked"
msgstr ""
-msgid "Error rendering markdown preview"
+msgid "Error occurred. User was not confirmed"
msgstr ""
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr "Marka oluşturma önizlemesi yapılırken hata oluştu"
+
msgid "Error saving label update."
msgstr ""
@@ -3722,17 +4342,53 @@ msgid "Error updating %{issuableType}"
msgstr ""
msgid "Error updating status for all todos."
-msgstr ""
+msgstr "Bütün yapılacaklar için durum güncellenirken hata oluştu."
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
+msgstr "Birleştirme isteğini yüklerken hata oluştu. Lütfen tekrar deneyin."
+
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
msgstr ""
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr "Etkin"
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr "Proje seç"
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3758,7 +4414,7 @@ msgid "EventFilterBy|Filter by team"
msgstr ""
msgid "Events"
-msgstr ""
+msgstr "Etkinlikler"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -3796,12 +4452,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3811,12 +4461,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3833,23 +4489,29 @@ msgid "Explore GitLab"
msgstr ""
msgid "Explore Groups"
-msgstr ""
+msgstr "Grupları Keşfet"
msgid "Explore groups"
-msgstr ""
+msgstr "Grupları keşfet"
msgid "Explore projects"
-msgstr ""
+msgstr "Projeleri keÅŸfet"
msgid "Explore public groups"
-msgstr ""
+msgstr "Genel grupları keşfet"
msgid "Export as CSV"
-msgstr ""
+msgstr "CSV olarak dışa aktar"
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3857,7 +4519,7 @@ msgid "External URL"
msgstr ""
msgid "External Wiki"
-msgstr ""
+msgstr "Harici Viki"
msgid "External authentication"
msgstr ""
@@ -3868,9 +4530,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3887,18 +4546,24 @@ msgid "Failed Jobs"
msgstr ""
msgid "Failed to change the owner"
-msgstr ""
+msgstr "Sahibi değiştirme başarısız"
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3910,9 +4575,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3925,12 +4605,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3938,7 +4624,7 @@ msgid "FeatureFlags|* (All Environments)"
msgstr ""
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "* (Tüm ortamlar)"
msgid "FeatureFlags|API URL"
msgstr ""
@@ -3953,7 +4639,7 @@ msgid "FeatureFlags|Configure feature flags"
msgstr ""
msgid "FeatureFlags|Create feature flag"
-msgstr ""
+msgstr "Özellik bayrağı oluştur"
msgid "FeatureFlags|Delete %{name}?"
msgstr ""
@@ -3962,10 +4648,7 @@ msgid "FeatureFlags|Delete feature flag"
msgstr ""
msgid "FeatureFlags|Description"
-msgstr ""
-
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
+msgstr "Açıklama"
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -4024,7 +4707,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4046,9 +4729,21 @@ msgid "FeatureFlags|Try again in a few moments or contact your support team."
msgstr ""
msgid "Feb"
-msgstr ""
+msgstr "Åžub"
msgid "February"
+msgstr "Åžubat"
+
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
msgstr ""
msgid "Fields on this page are now uneditable, you can configure"
@@ -4056,8 +4751,8 @@ msgstr ""
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Dosya"
+msgstr[1] "Dosya"
msgid "File added"
msgstr ""
@@ -4075,19 +4770,19 @@ msgid "File moved"
msgstr ""
msgid "File templates"
-msgstr ""
+msgstr "Dosya şablonları"
msgid "File upload error."
msgstr ""
msgid "Files"
-msgstr ""
+msgstr "Dosyalar"
msgid "Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>"
msgstr ""
msgid "Filter"
-msgstr ""
+msgstr "Süzgeç"
msgid "Filter by %{issuable_type} that are currently closed."
msgstr ""
@@ -4096,7 +4791,7 @@ msgid "Filter by %{issuable_type} that are currently opened."
msgstr ""
msgid "Filter by commit message"
-msgstr ""
+msgstr "İşlem mesajına göre süzgeçle"
msgid "Filter by milestone name"
msgstr ""
@@ -4111,13 +4806,10 @@ msgid "Filter results by project"
msgstr ""
msgid "Filter..."
-msgstr ""
-
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
+msgstr "Süzgeç..."
msgid "Find by path"
-msgstr ""
+msgstr "Yolu bul"
msgid "Find existing members by name"
msgstr ""
@@ -4131,6 +4823,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr "Parmak izi"
+
msgid "Fingerprints"
msgstr ""
@@ -4153,7 +4848,7 @@ msgid "FirstPushedBy|pushed by"
msgstr ""
msgid "Fixed date"
-msgstr ""
+msgstr "Düzeltilme zamanı"
msgid "Fixed due date"
msgstr ""
@@ -4183,10 +4878,10 @@ msgid "Follow the steps below to export your Google Code project data."
msgstr ""
msgid "Font Color"
-msgstr ""
+msgstr "Yazı Tipi Rengi"
msgid "Footer message"
-msgstr ""
+msgstr "Altbilgi mesajı"
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
msgstr ""
@@ -4219,18 +4914,24 @@ msgid "Forking in progress"
msgstr ""
msgid "Forks"
-msgstr ""
+msgstr "Çatallar"
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
-msgstr ""
+msgstr "GitLab.com Gold Ãœcretsiz Deneme"
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4260,12 +4961,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
+msgstr "GPG Anahtarları"
+
+msgid "GPG signature (loading...)"
msgstr ""
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4275,9 +4985,12 @@ msgstr ""
msgid "Generate key"
msgstr ""
-msgid "Geo"
+msgid "Generate new export"
msgstr ""
+msgid "Geo"
+msgstr "CoÄŸrafi"
+
msgid "Geo Nodes"
msgstr ""
@@ -4285,39 +4998,39 @@ msgid "Geo allows you to replicate your GitLab instance to other geographical lo
msgstr ""
msgid "GeoNodeSyncStatus|Node is failing or broken."
-msgstr ""
+msgstr "Düğüm başarısız veya bozuk."
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
-msgstr ""
+msgstr "Düğüm yavaş, aşırı yüklü veya bir kesintiden sonra kurtarıldı."
msgid "GeoNodes|Checksummed"
-msgstr ""
+msgstr "Sağlama yapıldı"
msgid "GeoNodes|Data is out of date from %{timeago}"
-msgstr ""
+msgstr "%{timeago} tarih verileri güncel değil"
msgid "GeoNodes|Data replication lag"
-msgstr ""
-
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
+msgstr "Veri çoğaltma gecikmesi"
msgid "GeoNodes|Does not match the primary storage configuration"
-msgstr ""
+msgstr "Birincil depolama yapılandırmasıyla eşleşmiyor"
msgid "GeoNodes|Failed"
-msgstr ""
+msgstr "Başarısız"
msgid "GeoNodes|Full"
-msgstr ""
+msgstr "Dolu"
msgid "GeoNodes|GitLab version"
-msgstr ""
+msgstr "GitLab sürümü"
msgid "GeoNodes|GitLab version does not match the primary node version"
-msgstr ""
+msgstr "GitLab sürümü birincil düğüm sürümü ile uyuşmuyor"
msgid "GeoNodes|Health status"
+msgstr "Sağlık durumu"
+
+msgid "GeoNodes|Internal URL"
msgstr ""
msgid "GeoNodes|Last event ID processed by cursor"
@@ -4351,7 +5064,7 @@ msgid "GeoNodes|Local job artifacts"
msgstr ""
msgid "GeoNodes|New node"
-msgstr ""
+msgstr "Yeni düğüm"
msgid "GeoNodes|Node Authentication was successfully repaired."
msgstr ""
@@ -4365,6 +5078,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4378,7 +5094,7 @@ msgid "GeoNodes|Replication slots"
msgstr ""
msgid "GeoNodes|Repositories"
-msgstr ""
+msgstr "Depolar"
msgid "GeoNodes|Repositories checksummed for verification with their counterparts on Secondary nodes"
msgstr ""
@@ -4462,7 +5178,7 @@ msgid "Geo|All"
msgstr ""
msgid "Geo|All projects"
-msgstr ""
+msgstr "Geo|Tüm projeler"
msgid "Geo|All projects are being scheduled for re-check"
msgstr ""
@@ -4473,9 +5189,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4491,6 +5222,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4504,10 +5238,10 @@ msgid "Geo|Last time verified"
msgstr ""
msgid "Geo|Never"
-msgstr ""
+msgstr "Asla"
msgid "Geo|Next sync scheduled at"
-msgstr ""
+msgstr "Sonraki senkronizasyon planlandı"
msgid "Geo|Not synced yet"
msgstr ""
@@ -4521,11 +5255,17 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
msgid "Geo|Projects in certain groups"
-msgstr ""
+msgstr "Geo|Belli gruplardaki projeler"
msgid "Geo|Projects in certain storage shards"
msgstr ""
@@ -4540,16 +5280,16 @@ msgid "Geo|Recheck all projects"
msgstr ""
msgid "Geo|Redownload"
-msgstr ""
+msgstr "Tekrar indir"
msgid "Geo|Remove"
-msgstr ""
+msgstr "Kaldır"
msgid "Geo|Repository sync capacity"
msgstr ""
msgid "Geo|Resync"
-msgstr ""
+msgstr "Yeniden senkronize et"
msgid "Geo|Resync all projects"
msgstr ""
@@ -4558,31 +5298,52 @@ msgid "Geo|Retry count"
msgstr ""
msgid "Geo|Select groups to replicate."
+msgstr "Çoğaltılacak grupları seçin."
+
+msgid "Geo|Selective synchronization"
msgstr ""
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
+msgstr "Geo|Durum"
+
+msgid "Geo|Sync"
msgstr ""
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr "URL"
+
msgid "Geo|Unknown state"
msgstr ""
msgid "Geo|Verification capacity"
-msgstr ""
+msgstr "DoÄŸrulama kapasitesi"
msgid "Geo|Verification failed - %{error}"
msgstr ""
@@ -4596,9 +5357,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4620,6 +5378,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4641,6 +5402,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4656,12 +5420,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4681,7 +5451,7 @@ msgid "Gitaly Servers"
msgstr ""
msgid "Gitaly|Address"
-msgstr ""
+msgstr "Adres"
msgid "Gitea Host URL"
msgstr ""
@@ -4695,15 +5465,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4720,11 +5499,20 @@ msgid "Grant access"
msgstr ""
msgid "Graph"
+msgstr "Çizelge"
+
+msgid "Gravatar enabled"
msgstr ""
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4734,6 +5522,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4747,13 +5538,13 @@ msgid "Group avatar"
msgstr ""
msgid "Group description"
-msgstr ""
+msgstr "Grubun açıklaması"
msgid "Group description (optional)"
-msgstr ""
+msgstr "Grup açıklaması (isteğe bağlı)"
msgid "Group details"
-msgstr ""
+msgstr "Grup ayrıntıları"
msgid "Group info:"
msgstr ""
@@ -4767,9 +5558,12 @@ msgstr ""
msgid "Group overview content"
msgstr ""
-msgid "Group:"
+msgid "Group was successfully updated."
msgstr ""
+msgid "Group:"
+msgstr "Grup:"
+
msgid "Group: %{group_name}"
msgstr ""
@@ -4785,6 +5579,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4794,27 +5591,105 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
-msgid "GroupSettings|Badges"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
msgstr ""
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
+msgid "GroupSettings|Badges"
+msgstr "Rozetler"
+
msgid "GroupSettings|Custom project templates"
msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4834,7 +5709,7 @@ msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name
msgstr ""
msgid "Groups"
-msgstr ""
+msgstr "Gruplar"
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -4870,7 +5745,7 @@ msgid "GroupsEmptyState|If you organize your projects under a group, it works li
msgstr ""
msgid "GroupsEmptyState|No groups found"
-msgstr ""
+msgstr "GroupsEmptyState|Grup bulunamadı"
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
@@ -4885,7 +5760,7 @@ msgid "GroupsTree|Create a subgroup in this group."
msgstr ""
msgid "GroupsTree|Edit group"
-msgstr ""
+msgstr "Grubu düzenle"
msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
msgstr ""
@@ -4908,6 +5783,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4930,23 +5808,26 @@ msgid "HealthCheck|Unhealthy"
msgstr ""
msgid "Help"
-msgstr ""
+msgstr "Yardım"
msgid "Help page"
-msgstr ""
+msgstr "Yardım sayfası"
msgid "Help page text and support page url."
-msgstr ""
+msgstr "Yardım sayfası metni ve destek sayfası bağlantısı."
msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
msgstr ""
msgid "Hide file browser"
-msgstr ""
+msgstr "Dosya tarayıcısını gizle"
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4956,14 +5837,29 @@ msgstr[0] ""
msgstr[1] ""
msgid "Hide values"
+msgstr "DeÄŸerleri gizle"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4980,7 +5876,7 @@ msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeS
msgstr ""
msgid "IDE|Back"
-msgstr ""
+msgstr "Geri"
msgid "IDE|Client side evaluation"
msgstr ""
@@ -4992,13 +5888,13 @@ msgid "IDE|Edit"
msgstr ""
msgid "IDE|Get started with Live Preview"
-msgstr ""
+msgstr "Canlı Önizlemeye başlarken"
msgid "IDE|Go to project"
msgstr ""
msgid "IDE|Live Preview"
-msgstr ""
+msgstr "Canlı Önizleme"
msgid "IDE|Open in file view"
msgstr ""
@@ -5007,10 +5903,10 @@ msgid "IDE|Preview your web application using Web IDE client-side evaluation."
msgstr ""
msgid "IDE|Refresh preview"
-msgstr ""
+msgstr "Önizlemeyi yenile"
msgid "IDE|Review"
-msgstr ""
+msgstr "Ä°nceleme"
msgid "IP Address"
msgstr ""
@@ -5021,15 +5917,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5042,9 +5941,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5073,7 +5969,7 @@ msgid "Import all compatible projects"
msgstr ""
msgid "Import all projects"
-msgstr ""
+msgstr "Tüm projeleri içe aktar"
msgid "Import all repositories"
msgstr ""
@@ -5103,19 +5999,19 @@ msgid "Import project members"
msgstr ""
msgid "Import projects from Bitbucket"
-msgstr ""
+msgstr "Bitbucket'ten projeleri içe aktar"
msgid "Import projects from Bitbucket Server"
msgstr ""
msgid "Import projects from FogBugz"
-msgstr ""
+msgstr "FogBugz'dan projeleri içe aktar"
msgid "Import projects from GitLab.com"
-msgstr ""
+msgstr "GitLab.com'dan projeleri içe aktar"
msgid "Import projects from Google Code"
-msgstr ""
+msgstr "Google Code'dan projeler içe aktar"
msgid "Import repositories from Bitbucket Server"
msgstr ""
@@ -5129,10 +6025,28 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr "Almak istediğiniz projeleri seçin"
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
+msgstr "GitLab Kurumsal Sürüm ile sorun panolarını iyileştirin."
+
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
msgstr ""
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
@@ -5150,15 +6064,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
-msgid "Include merge request description"
+msgid "Include author name in notification email body"
msgstr ""
+msgid "Include merge request description"
+msgstr "Birleştirme isteği açıklamasını dahil et"
+
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5174,6 +6097,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5183,9 +6109,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5206,6 +6141,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5216,13 +6154,13 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr ""
msgid "Internal"
-msgstr ""
+msgstr "Dahili"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr ""
msgid "Internal - The project can be accessed by any logged in user."
-msgstr ""
+msgstr "Dahili - Projeye giriş yapan herhangi bir kullanıcı tarafından erişilebilir."
msgid "Internal users"
msgstr ""
@@ -5236,6 +6174,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5254,6 +6207,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5264,6 +6220,9 @@ msgid "Issue board focus mode"
msgstr ""
msgid "Issue events"
+msgstr "Sorun etkinlikleri"
+
+msgid "Issue template (optional)"
msgstr ""
msgid "IssueBoards|Board"
@@ -5273,7 +6232,7 @@ msgid "IssueBoards|Boards"
msgstr ""
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "Yeni pano oluÅŸtur"
msgid "IssueBoards|Delete board"
msgstr ""
@@ -5288,15 +6247,18 @@ msgid "IssueBoards|Switch board"
msgstr ""
msgid "Issues"
-msgstr ""
+msgstr "Sorunlar"
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
msgid "Issues closed"
+msgstr "Sorunlar kapalı"
+
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5333,20 +6295,38 @@ msgid "Jaeger tracing"
msgstr ""
msgid "Jan"
-msgstr ""
+msgstr "Oca"
msgid "January"
-msgstr ""
+msgstr "Ocak"
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5354,7 +6334,7 @@ msgid "Jobs"
msgstr ""
msgid "Job|Browse"
-msgstr ""
+msgstr "Gözat"
msgid "Job|Complete Raw"
msgstr ""
@@ -5377,6 +6357,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5392,20 +6375,32 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
-msgid "Jul"
+msgid "Job|for"
msgstr ""
-msgid "July"
+msgid "Job|into"
msgstr ""
-msgid "Jun"
+msgid "Job|with"
msgstr ""
+msgid "Jul"
+msgstr "Tem"
+
+msgid "July"
+msgstr "Temmuz"
+
+msgid "Jun"
+msgstr "Haz"
+
msgid "June"
-msgstr ""
+msgstr "Haziran"
msgid "Key (PEM)"
msgstr ""
@@ -5437,14 +6432,20 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
-msgid "LFSStatus|Disabled"
+msgid "LFS objects"
msgstr ""
+msgid "LFSStatus|Disabled"
+msgstr "LFSStatus|Kapalı"
+
msgid "LFSStatus|Enabled"
-msgstr ""
+msgstr "LFSStatus|Etkin"
msgid "Label"
msgstr ""
@@ -5455,6 +6456,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5465,7 +6475,7 @@ msgid "LabelSelect|Labels"
msgstr ""
msgid "Labels"
-msgstr ""
+msgstr "Etiketler"
msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
msgstr ""
@@ -5485,6 +6495,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr "Dil"
+
msgid "Large File Storage"
msgstr ""
@@ -5497,7 +6510,7 @@ msgid "Last Pipeline"
msgstr ""
msgid "Last activity"
-msgstr ""
+msgstr "Son etkinlik"
msgid "Last commit"
msgstr ""
@@ -5521,7 +6534,7 @@ msgid "Last update"
msgstr ""
msgid "Last updated"
-msgstr ""
+msgstr "Son güncelleme"
msgid "LastPushEvent|You pushed to"
msgstr ""
@@ -5556,16 +6569,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5577,9 +6590,12 @@ msgstr ""
msgid "Leave"
msgstr ""
-msgid "Leave group"
+msgid "Leave edit mode? All unsaved changes will be lost."
msgstr ""
+msgid "Leave group"
+msgstr "Gruptan ayrıl"
+
msgid "Leave project"
msgstr ""
@@ -5620,7 +6636,7 @@ msgid "LicenseManagement|Blacklisted"
msgstr ""
msgid "LicenseManagement|Cancel"
-msgstr ""
+msgstr "Ä°ptal"
msgid "LicenseManagement|License"
msgstr ""
@@ -5638,7 +6654,7 @@ msgid "LicenseManagement|Manage approved and blacklisted licenses for this proje
msgstr ""
msgid "LicenseManagement|Packages"
-msgstr ""
+msgstr "Paketler"
msgid "LicenseManagement|Remove license"
msgstr ""
@@ -5647,7 +6663,7 @@ msgid "LicenseManagement|Remove license?"
msgstr ""
msgid "LicenseManagement|Submit"
-msgstr ""
+msgstr "Gönder"
msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
msgstr ""
@@ -5661,7 +6677,13 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
+msgstr "Lisanslar"
+
+msgid "Limit namespaces and projects that can be indexed"
msgstr ""
msgid "Limited to showing %d event at most"
@@ -5673,7 +6695,7 @@ msgid "LinkedIn"
msgstr ""
msgid "List"
-msgstr ""
+msgstr "Liste"
msgid "List Your Gitea Repositories"
msgstr ""
@@ -5681,6 +6703,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5691,7 +6716,7 @@ msgid "List your GitHub repositories"
msgstr ""
msgid "Live preview"
-msgstr ""
+msgstr "Canlı önizleme"
msgid "Loading contribution stats for group members"
msgstr ""
@@ -5703,10 +6728,10 @@ msgid "Loading..."
msgstr ""
msgid "Loading…"
-msgstr ""
+msgstr "Yükleniyor…"
msgid "Localization"
-msgstr ""
+msgstr "YerelleÅŸtirme"
msgid "Lock"
msgstr ""
@@ -5727,7 +6752,7 @@ msgid "Locked"
msgstr ""
msgid "Locked Files"
-msgstr ""
+msgstr "Kilitli Dosyalar"
msgid "Locked to current projects"
msgstr ""
@@ -5738,15 +6763,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5757,7 +6803,7 @@ msgid "Manage access"
msgstr ""
msgid "Manage all notifications"
-msgstr ""
+msgstr "Tüm bildirimleri yönet"
msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
msgstr ""
@@ -5777,15 +6823,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5799,61 +6845,58 @@ msgid "Map a Google Code user to a full name"
msgstr ""
msgid "Mar"
-msgstr ""
+msgstr "Mar"
msgid "March"
-msgstr ""
+msgstr "Mart"
msgid "Mark todo as done"
-msgstr ""
+msgstr "Yapılacağı tamamlandı olarak işaretle"
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a bullet list"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
-msgstr ""
+msgstr "May"
msgid "Median"
msgstr ""
@@ -5862,10 +6905,10 @@ msgid "Member lock"
msgstr ""
msgid "Member since %{date}"
-msgstr ""
+msgstr "%{date} tarihinden beri üye"
msgid "Members"
-msgstr ""
+msgstr "Ãœyeler"
msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
msgstr ""
@@ -5873,21 +6916,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr ""
-
msgid "Merge Request"
+msgstr "BirleÅŸtirme Ä°steÄŸi"
+
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
-msgstr ""
+msgstr "BirleÅŸtirme Ä°stekleri"
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr ""
@@ -5897,14 +6946,23 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
-msgid "Merge request"
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
msgstr ""
+msgid "Merge request"
+msgstr "BirleÅŸtirme isteÄŸi"
+
msgid "Merge request approvals"
+msgstr "Birleştirme isteği onayları"
+
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
msgstr ""
msgid "Merge requests"
-msgstr ""
+msgstr "BirleÅŸtirme istekleri"
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
@@ -5937,10 +6995,10 @@ msgid "MergeRequests|Reply..."
msgstr ""
msgid "MergeRequests|Resolve this discussion in a new issue"
-msgstr ""
+msgstr "Bu tartışmayı yeni bir konuyla çöz"
msgid "MergeRequests|Saving the comment failed"
-msgstr ""
+msgstr "Yorum kaydedilemedi"
msgid "MergeRequests|Toggle comments for this file"
msgstr ""
@@ -5972,20 +7030,23 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
-msgid "MergeRequest|Filter files"
+msgid "MergeRequest|Error loading full diff. Please try again."
msgstr ""
-msgid "MergeRequest|No files found"
+msgid "MergeRequest|Filter files"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "MergeRequest|No files found"
msgstr ""
msgid "Merged"
+msgstr "BirleÅŸtirildi"
+
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
-msgstr ""
+msgstr "Mesajlar"
msgid "Metrics"
msgstr ""
@@ -6006,7 +7067,7 @@ msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr ""
msgid "Metrics|Create metric"
-msgstr ""
+msgstr "Ölçüt oluştur"
msgid "Metrics|Delete metric"
msgstr ""
@@ -6015,7 +7076,7 @@ msgid "Metrics|Delete metric?"
msgstr ""
msgid "Metrics|Edit metric"
-msgstr ""
+msgstr "Ölçütü düzenle"
msgid "Metrics|Environment"
msgstr ""
@@ -6041,13 +7102,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6075,7 +7139,7 @@ msgid "Metrics|Unit label"
msgstr ""
msgid "Metrics|Used as a title for the chart"
-msgstr ""
+msgstr "Grafik için başlık olarak kullanılır"
msgid "Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response."
msgstr ""
@@ -6089,6 +7153,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6099,7 +7169,7 @@ msgid "Milestone lists show all issues from the selected milestone."
msgstr ""
msgid "Milestones"
-msgstr ""
+msgstr "Dönüm Noktaları"
msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered."
msgstr ""
@@ -6111,10 +7181,10 @@ msgid "Milestones|<p>%{milestonePromotion}</p> %{finalWarning}"
msgstr ""
msgid "Milestones|Delete milestone"
-msgstr ""
+msgstr "Dönüm noktasını sil"
msgid "Milestones|Delete milestone %{milestoneTitle}?"
-msgstr ""
+msgstr "%{milestoneTitle} adındaki dönüm noktasını silmek istiyor musunuz?"
msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
msgstr ""
@@ -6134,6 +7204,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6152,13 +7225,19 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
msgid "Modal|Cancel"
-msgstr ""
+msgstr "Modal|Ä°ptal"
msgid "Modal|Close"
+msgstr "Kapat"
+
+msgid "Modify commit message"
msgstr ""
msgid "Modify commit messages"
@@ -6168,7 +7247,7 @@ msgid "Modify merge commit"
msgstr ""
msgid "Monday"
-msgstr ""
+msgstr "Pazartesi"
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
@@ -6194,18 +7273,30 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
msgid "Move issue"
-msgstr ""
+msgstr "Konuyu taşı"
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6213,28 +7304,34 @@ msgid "Name new label"
msgstr ""
msgid "Name your individual key via a title"
-msgstr ""
+msgstr "Bireysel anahtarınızı bir başlık ile adlandırın"
msgid "Name:"
+msgstr "Ä°sim:"
+
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
msgstr ""
msgid "Naming, visibility"
msgstr ""
msgid "Nav|Help"
-msgstr ""
+msgstr "Yardım"
msgid "Nav|Home"
-msgstr ""
+msgstr "GiriÅŸ"
msgid "Nav|Sign In / Register"
-msgstr ""
+msgstr "Oturum Aç / Kaydol"
msgid "Nav|Sign out and sign in with a different account"
-msgstr ""
+msgstr "Çıkış yapın ve farklı bir hesapla oturum açın"
msgid "Need help?"
-msgstr ""
+msgstr "Yardım ister misiniz?"
msgid "Network"
msgstr ""
@@ -6243,42 +7340,42 @@ msgid "Never"
msgstr ""
msgid "New"
-msgstr ""
+msgstr "Yeni"
msgid "New Application"
-msgstr ""
+msgstr "Yeni Uygulama"
msgid "New Environment"
-msgstr ""
+msgstr "Yeni Ortam"
msgid "New Group"
-msgstr ""
+msgstr "Yeni Grup"
msgid "New Identity"
-msgstr ""
+msgstr "Yeni Kimlik"
msgid "New Issue"
msgid_plural "New Issues"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Yeni Sorun"
+msgstr[1] "Yeni Sorunlar"
msgid "New Label"
-msgstr ""
+msgstr "Yeni Etiket"
msgid "New Milestone"
-msgstr ""
+msgstr "Yeni dönüm noktası"
msgid "New Pages Domain"
-msgstr ""
+msgstr "Yeni sayfa etki alanı"
+
+msgid "New Password"
+msgstr "Yeni Åžifre"
msgid "New Pipeline Schedule"
msgstr ""
msgid "New Snippet"
-msgstr ""
-
-msgid "New Snippets"
-msgstr ""
+msgstr "Yeni Parçacık"
msgid "New branch"
msgstr ""
@@ -6286,68 +7383,86 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr "Yeni dağıtım anahtarı"
+
msgid "New directory"
-msgstr ""
+msgstr "Yeni dizin"
msgid "New environment"
-msgstr ""
+msgstr "Yeni ortam"
msgid "New epic"
-msgstr ""
+msgstr "Yeni epik"
msgid "New file"
-msgstr ""
+msgstr "Yeni dosya"
msgid "New group"
+msgstr "Yeni grup"
+
+msgid "New health check access token has been generated!"
msgstr ""
msgid "New identity"
-msgstr ""
+msgstr "Yeni kimlik"
msgid "New issue"
-msgstr ""
+msgstr "Yeni sorun"
msgid "New label"
-msgstr ""
+msgstr "Yeni etiket"
msgid "New merge request"
-msgstr ""
+msgstr "Yeni birleÅŸtirme isteÄŸi"
msgid "New milestone"
-msgstr ""
+msgstr "Yeni dönüm noktası"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
-msgstr ""
+msgstr "Yeni iş hatları, aynı daldaki daha eski iş hatlarını iptal edecek"
msgid "New project"
+msgstr "Yeni proje"
+
+msgid "New runners registration token has been generated!"
msgstr ""
msgid "New schedule"
msgstr ""
msgid "New snippet"
-msgstr ""
+msgstr "Yeni parçacık"
msgid "New subgroup"
-msgstr ""
+msgstr "Yeni alt grup"
msgid "New tag"
-msgstr ""
+msgstr "Yeni etiket"
+
+msgid "New users set to external"
+msgstr "Yeni kullanıcılar harici olarak ayarlandı"
msgid "New..."
+msgstr "Yeni..."
+
+msgid "Newly registered users will by default be external"
msgstr ""
msgid "No"
msgstr ""
-msgid "No Label"
+msgid "No %{providerTitle} repositories available to import"
msgstr ""
-msgid "No activities found"
+msgid "No Label"
msgstr ""
-msgid "No assignee"
-msgstr ""
+msgid "No Tag"
+msgstr "Etiket Yok"
+
+msgid "No activities found"
+msgstr "Etkinlik bulunamadı"
msgid "No branches found"
msgstr ""
@@ -6371,12 +7486,12 @@ msgid "No credit card required."
msgstr ""
msgid "No details available"
-msgstr ""
+msgstr "Ayrıntı yok"
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6386,28 +7501,37 @@ msgid "No file chosen"
msgstr ""
msgid "No file selected"
-msgstr ""
+msgstr "Dosya seçili değil"
msgid "No files found."
-msgstr ""
+msgstr "Dosya bulunamadı."
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
msgid "No merge requests found"
-msgstr ""
+msgstr "Birleştirme isteği bulunamadı"
msgid "No messages were logged"
msgstr ""
@@ -6419,13 +7543,13 @@ msgid "No other labels with such name or description"
msgstr ""
msgid "No preview for this file type"
-msgstr ""
+msgstr "Bu dosya türü için önizleme yok"
msgid "No prioritised labels with such name or description"
msgstr ""
msgid "No public groups"
-msgstr ""
+msgstr "Genel grup yok"
msgid "No pushes for the selected time period."
msgstr ""
@@ -6449,10 +7573,7 @@ msgid "Nodes"
msgstr ""
msgid "None"
-msgstr ""
-
-msgid "Not allowed to merge"
-msgstr ""
+msgstr "Yok"
msgid "Not available"
msgstr ""
@@ -6469,10 +7590,16 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6491,13 +7618,13 @@ msgid "Note: Consider asking your GitLab administrator to configure %{github_int
msgstr ""
msgid "Notes|Are you sure you want to cancel creating this comment?"
-msgstr ""
+msgstr "Notlar|Bu yorumu oluşturmayı iptal etmek istediğinizden emin misiniz?"
msgid "Notes|Collapse replies"
msgstr ""
msgid "Notes|Show all activity"
-msgstr ""
+msgstr "Tüm etkinliği göster"
msgid "Notes|Show comments only"
msgstr ""
@@ -6509,7 +7636,7 @@ msgid "Notification events"
msgstr ""
msgid "Notification setting"
-msgstr ""
+msgstr "Bildirim ayarı"
msgid "Notification setting - %{notification_title}"
msgstr ""
@@ -6536,7 +7663,7 @@ msgid "NotificationEvent|New merge request"
msgstr ""
msgid "NotificationEvent|New note"
-msgstr ""
+msgstr "Yeni not"
msgid "NotificationEvent|Reassign issue"
msgstr ""
@@ -6551,7 +7678,7 @@ msgid "NotificationEvent|Successful pipeline"
msgstr ""
msgid "NotificationLevel|Custom"
-msgstr ""
+msgstr "Özel"
msgid "NotificationLevel|Disabled"
msgstr ""
@@ -6569,28 +7696,31 @@ msgid "NotificationLevel|Watch"
msgstr ""
msgid "Notifications"
-msgstr ""
+msgstr "Bildirimler"
msgid "Notifications off"
-msgstr ""
+msgstr "Bildirimler kapalı"
msgid "Notifications on"
-msgstr ""
+msgstr "Bildirimler açık"
msgid "Nov"
-msgstr ""
+msgstr "Kas"
msgid "November"
-msgstr ""
+msgstr "Kasım"
msgid "OK"
msgstr ""
-msgid "Oct"
+msgid "Object does not exist on the server or you don't have permissions to access it"
msgstr ""
+msgid "Oct"
+msgstr "Eki"
+
msgid "October"
-msgstr ""
+msgstr "Ekim"
msgid "OfSearchInADropdown|Filter"
msgstr ""
@@ -6598,11 +7728,20 @@ msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
msgstr[1] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6612,6 +7751,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6631,37 +7776,40 @@ msgid "Oops, are you sure?"
msgstr ""
msgid "Open"
-msgstr ""
+msgstr "Açık"
msgid "Open Documentation"
-msgstr ""
+msgstr "Belgeleri Aç"
msgid "Open comment type dropdown"
-msgstr ""
+msgstr "Yorum türü açılır penceresini aç"
msgid "Open errors"
-msgstr ""
+msgstr "Açık hatalar"
msgid "Open in Xcode"
msgstr ""
msgid "Open projects"
+msgstr "Projeleri aç"
+
+msgid "Open raw"
msgstr ""
msgid "Open sidebar"
-msgstr ""
+msgstr "Kenar çubuğunu aç"
msgid "Open source software to collaborate on code"
-msgstr ""
+msgstr "Kod üzerinde ortak çalışmak için açık kaynaklı yazılım"
msgid "Opened"
-msgstr ""
+msgstr "Açıldı"
msgid "Opened MR"
msgstr ""
msgid "Opened issues"
-msgstr ""
+msgstr "Açılan sorunlar"
msgid "OpenedNDaysAgo|Opened"
msgstr ""
@@ -6673,17 +7821,32 @@ msgid "Operations"
msgstr ""
msgid "Operations Dashboard"
+msgstr "Ä°ÅŸlemler Panosu"
+
+msgid "Operations Settings"
msgstr ""
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6702,14 +7865,11 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
msgid "Overview"
-msgstr ""
+msgstr "Genel bakış"
msgid "Overwrite diverged branches"
msgstr ""
@@ -6726,6 +7886,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6750,18 +7916,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
+msgstr "Åžifre"
+
+msgid "Password authentication is unavailable."
msgstr ""
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6771,6 +7940,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6780,6 +7952,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6798,11 +7973,14 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
msgid "Personal Access Token"
-msgstr ""
+msgstr "Kişisel Erişim Anahtarı"
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
msgstr ""
@@ -6813,21 +7991,24 @@ msgstr ""
msgid "Pipeline"
msgstr ""
-msgid "Pipeline Health"
-msgstr ""
-
msgid "Pipeline Schedule"
msgstr ""
msgid "Pipeline Schedules"
msgstr ""
-msgid "Pipeline quota"
+msgid "Pipeline minutes quota"
msgstr ""
+msgid "Pipeline quota"
+msgstr "İş Hattı Kotası"
+
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr ""
@@ -6876,6 +8057,12 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr ""
@@ -6891,6 +8078,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6933,10 +8126,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6951,6 +8141,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6976,9 +8169,21 @@ msgid "Pipeline|You’re about to stop pipeline %{pipelineId}."
msgstr ""
msgid "Pipeline|all"
+msgstr "tümü"
+
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
msgstr ""
msgid "Pipeline|success"
+msgstr "başarılı"
+
+msgid "Pipeline|with"
msgstr ""
msgid "Pipeline|with stage"
@@ -7005,15 +8210,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -7023,9 +8246,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -7038,6 +8273,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7045,22 +8283,37 @@ msgid "Please wait while we import the repository for you. Refresh at will."
msgstr ""
msgid "Preferences"
+msgstr "Tercihler"
+
+msgid "Preferences saved."
msgstr ""
msgid "Preferences|Navigation theme"
+msgstr "Preferences|Gezinme teması"
+
+msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr ""
msgid "Press Enter or click to search"
-msgstr ""
+msgstr "Aramak için Enter tuşuna basın veya tıklayın"
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
+msgstr "Önizleme"
+
+msgid "Preview changes"
msgstr ""
msgid "Preview payload"
-msgstr ""
+msgstr "Önizleme yükü"
msgid "Primary"
msgstr ""
@@ -7078,22 +8331,25 @@ msgid "Prioritized label"
msgstr ""
msgid "Private"
-msgstr ""
+msgstr "Özel"
msgid "Private - Project access must be granted explicitly to each user."
msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
+msgstr "Özel - Grup ve grubun projeleri sadece üyeler tarafından görülebilir."
+
+msgid "Private group(s)"
msgstr ""
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
msgid "Profile"
-msgstr ""
+msgstr "Profil"
msgid "Profile Settings"
-msgstr ""
+msgstr "Profil Ayarları"
msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
msgstr ""
@@ -7102,130 +8358,130 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr ""
msgid "Profiles|@username"
-msgstr ""
+msgstr "@kullanıcıadı"
msgid "Profiles|Account scheduled for removal."
-msgstr ""
+msgstr "Hesabın kaldırılması planlandı."
msgid "Profiles|Activate signin with one of the following services"
msgstr ""
msgid "Profiles|Active"
-msgstr ""
+msgstr "Etkin"
msgid "Profiles|Add key"
-msgstr ""
+msgstr "Anahtar ekle"
msgid "Profiles|Add status emoji"
-msgstr ""
+msgstr "Durum ifadesi ekle"
msgid "Profiles|Avatar cropper"
-msgstr ""
+msgstr "Profil resmi kırpıcı"
msgid "Profiles|Avatar will be removed. Are you sure?"
-msgstr ""
+msgstr "Profil resmi kaldırılacak. Emin misiniz?"
msgid "Profiles|Change username"
-msgstr ""
+msgstr "Kullanıcı adını değiştir"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "Kullanıcı adınızı değiştirmenin istenmeyen yan etkileri olabilir."
msgid "Profiles|Choose file..."
-msgstr ""
+msgstr "Dosya seç..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
msgstr ""
msgid "Profiles|City, country"
-msgstr ""
+msgstr "Şehir, ülke"
msgid "Profiles|Clear status"
-msgstr ""
+msgstr "Durumu temizle"
msgid "Profiles|Click on icon to activate signin with one of the following services"
msgstr ""
msgid "Profiles|Connect"
-msgstr ""
+msgstr "BaÄŸlan"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "Bağlı Hesaplar"
msgid "Profiles|Current path: %{path}"
-msgstr ""
+msgstr "Geçerli dizin: %{path}"
msgid "Profiles|Current status"
-msgstr ""
+msgstr "Mevcut durum"
msgid "Profiles|Delete Account"
-msgstr ""
+msgstr "Hesabı Sil"
msgid "Profiles|Delete account"
-msgstr ""
+msgstr "Hesabı sil"
msgid "Profiles|Delete your account?"
-msgstr ""
+msgstr "Hesabınız silinsin mi?"
msgid "Profiles|Deleting an account has the following effects:"
-msgstr ""
+msgstr "Bir hesabı silmek aşağıdaki etkilere sahiptir:"
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "Bağlantıyı kes"
msgid "Profiles|Do not show on profile"
-msgstr ""
+msgstr "Profilde gösterme"
msgid "Profiles|Don't display activity-related personal information on your profiles"
-msgstr ""
+msgstr "Etkinliklerle ile ilgili kişisel bilgileri profiliniz üstünde göstermeyin"
msgid "Profiles|Edit Profile"
-msgstr ""
+msgstr "Profili Düzenle"
msgid "Profiles|Enter your name, so people you know can recognize you"
-msgstr ""
+msgstr "Adınızı girin, böylece tanıdığınız kişiler sizi tanıyabilir"
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
msgstr ""
msgid "Profiles|Invalid password"
-msgstr ""
+msgstr "Geçersiz şifre"
msgid "Profiles|Invalid username"
-msgstr ""
+msgstr "Geçersiz kullanıcı adı"
msgid "Profiles|Learn more"
-msgstr ""
+msgstr "Daha fazla bilgi edin"
msgid "Profiles|Made a private contribution"
-msgstr ""
+msgstr "Özel bir katkı yaptı"
msgid "Profiles|Main settings"
-msgstr ""
+msgstr "Ana ayarlar"
msgid "Profiles|No file chosen"
-msgstr ""
+msgstr "Dosya seçili değil"
msgid "Profiles|Path"
-msgstr ""
+msgstr "Yol"
msgid "Profiles|Position and size your new avatar"
-msgstr ""
+msgstr "Yeni profil resminizi konumlandırın ve boyutlandırın"
msgid "Profiles|Private contributions"
-msgstr ""
+msgstr "Özel katkılar"
msgid "Profiles|Public Avatar"
-msgstr ""
+msgstr "Genel Profil Resmi"
msgid "Profiles|Remove avatar"
-msgstr ""
+msgstr "Profil resmini kaldır"
msgid "Profiles|Set new profile picture"
-msgstr ""
+msgstr "Yeni profil resmini ayarla"
msgid "Profiles|Social sign-in"
-msgstr ""
+msgstr "Sosyal oturum açma"
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
@@ -7236,26 +8492,26 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
-msgstr ""
-
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
+msgstr "Bu ifade ve mesaj profilinizde ve arayüz boyunca görünecektir."
msgid "Profiles|This information will appear on your profile"
-msgstr ""
+msgstr "Bu bilgiler profilinizde görünecek"
msgid "Profiles|Two-Factor Authentication"
-msgstr ""
+msgstr "İki Aşamalı Kimlik Doğrulama"
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
@@ -7264,40 +8520,40 @@ msgid "Profiles|Typically starts with \"ssh-rsa …\""
msgstr ""
msgid "Profiles|Update profile settings"
-msgstr ""
+msgstr "Profil ayarlarını güncelle"
msgid "Profiles|Update username"
-msgstr ""
+msgstr "Kullanıcı adını güncelle"
msgid "Profiles|Upload new avatar"
-msgstr ""
+msgstr "Yeni profil resmi yükle"
msgid "Profiles|Use a private email - %{email}"
-msgstr ""
+msgstr "Özel bir e-posta kullan - %{email}"
msgid "Profiles|Username change failed - %{message}"
msgstr ""
msgid "Profiles|Username successfully changed"
-msgstr ""
+msgstr "Kullanıcı adı başarıyla değiştirildi"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "İsimlerdeki ifadeleri kullanmak eğlenceli görünüyor. Ancak bunun yerine durum mesajı ayarlamayı deneyin"
msgid "Profiles|What's your status?"
-msgstr ""
+msgstr "Durumunuz nedir?"
msgid "Profiles|Who you represent or work for"
-msgstr ""
+msgstr "Kimleri temsil ediyor veya kimin için çalışıyorsunuz?"
msgid "Profiles|You can change your avatar here"
-msgstr ""
+msgstr "Profil resminizi buradan deÄŸiÅŸtirebilirsiniz"
msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
msgstr ""
msgid "Profiles|You can upload your avatar here"
-msgstr ""
+msgstr "Profil resminizi buradan yükleyebilirsiniz"
msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
msgstr ""
@@ -7309,40 +8565,40 @@ msgid "Profiles|You must transfer ownership or delete these groups before you ca
msgstr ""
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
-msgstr ""
+msgstr "LinkedIn profil adınız linkedin.com/in/profiladı"
msgid "Profiles|Your account is currently an owner in these groups:"
-msgstr ""
+msgstr "Hesabınız şu anda bu gruplara sahip:"
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "E-posta adresiniz, %{provider_label} hesabınıza göre otomatik olarak ayarlandı"
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "Konumunuz, %{provider_label} hesabınıza göre otomatik olarak ayarlandı"
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
-msgstr ""
+msgstr "Adınız, %{provider_label} hesabınıza göre otomatik olarak ayarlandı. Böylece tanıdığınız kişiler sizi tanıyabilir"
msgid "Profiles|Your status"
-msgstr ""
+msgstr "Durumunuz"
msgid "Profiles|e.g. My MacBook key"
-msgstr ""
+msgstr "Örneğin. MacBook anahtarım"
msgid "Profiles|username"
-msgstr ""
+msgstr "kullanıcıadı"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "website.com"
msgid "Profiles|your account"
-msgstr ""
+msgstr "hesabınız"
msgid "Profiling - Performance bar"
msgstr ""
msgid "Programming languages used in this repository"
-msgstr ""
+msgstr "Bu depoda kullanılan programlama dilleri"
msgid "Progress"
msgstr ""
@@ -7350,6 +8606,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7357,7 +8622,7 @@ msgid "Project '%{project_name}' queued for deletion."
msgstr ""
msgid "Project '%{project_name}' was successfully created."
-msgstr ""
+msgstr "'%{project_name}' projesi başarıyla oluşturuldu."
msgid "Project '%{project_name}' was successfully updated."
msgstr ""
@@ -7365,24 +8630,36 @@ msgstr ""
msgid "Project Badges"
msgstr ""
-msgid "Project URL"
+msgid "Project ID"
msgstr ""
+msgid "Project URL"
+msgstr "Proje URL'si"
+
msgid "Project access must be granted explicitly to each user."
+msgstr "Proje erişimi her kullanıcıya açıkça verilmelidir."
+
+msgid "Project and wiki repositories"
msgstr ""
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
msgstr ""
-msgid "Project details"
+msgid "Project description (optional)"
msgstr ""
+msgid "Project details"
+msgstr "Proje ayrıntıları"
+
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -7392,41 +8669,50 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
msgid "Project name"
-msgstr ""
+msgstr "Proje adı"
msgid "Project slug"
msgstr ""
-msgid "Project:"
+msgid "Project uploads"
msgstr ""
-msgid "ProjectActivityRSS|Subscribe"
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
+msgid "Project:"
+msgstr "Proje:"
+
+msgid "ProjectActivityRSS|Subscribe"
+msgstr "Abone ol"
+
msgid "ProjectCreationLevel|Allowed to create projects"
-msgstr ""
+msgstr "Proje oluÅŸturma izni var"
msgid "ProjectCreationLevel|Default project creation protection"
-msgstr ""
+msgstr "Öntanımlı proje oluşturma koruması"
msgid "ProjectCreationLevel|Developers + Maintainers"
-msgstr ""
+msgstr "ProjectCreationLevel|GeliÅŸtirici + Sorumlu"
msgid "ProjectCreationLevel|Maintainers"
-msgstr ""
+msgstr "ProjectCreationLevel|Sorumlu"
msgid "ProjectCreationLevel|No one"
-msgstr ""
+msgstr "Hiç kimse"
msgid "ProjectFileTree|Name"
-msgstr ""
+msgstr "Ä°sim"
msgid "ProjectLastActivity|Never"
-msgstr ""
+msgstr "Asla"
msgid "ProjectLifecycle|Stage"
msgstr ""
@@ -7444,7 +8730,7 @@ msgid "ProjectOverview|Star"
msgstr ""
msgid "ProjectOverview|Unstar"
-msgstr ""
+msgstr "Yıldızı kaldır"
msgid "ProjectOverview|You have reached your project limit"
msgstr ""
@@ -7489,6 +8775,9 @@ msgid "ProjectSettings|Users can only push commits to this repository that were
msgstr ""
msgid "Projects"
+msgstr "Projeler"
+
+msgid "Projects Successfully Retrieved"
msgstr ""
msgid "Projects shared with %{group_name}"
@@ -7497,6 +8786,12 @@ msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7545,6 +8840,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7647,6 +8945,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7713,6 +9014,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7725,6 +9032,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -7755,6 +9068,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -7770,9 +9086,15 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
-msgid "Recent searches"
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
msgstr ""
+msgid "Recent searches"
+msgstr "Son aramalar"
+
msgid "Redirect to SAML provider to test configuration"
msgstr ""
@@ -7805,9 +9127,12 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
-msgid "Registry"
+msgid "Registration"
msgstr ""
+msgid "Registry"
+msgstr "Kayıt Defteri"
+
msgid "Related Commits"
msgstr ""
@@ -7824,13 +9149,13 @@ msgid "Related Merge Requests"
msgstr ""
msgid "Related Merged Requests"
-msgstr ""
+msgstr "Ä°lgili BirleÅŸtirme Talepleri"
msgid "Related merge requests"
msgstr ""
msgid "Releases"
-msgstr ""
+msgstr "Sürümler"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
@@ -7850,9 +9175,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7862,12 +9196,33 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7886,6 +9241,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7908,7 +9266,7 @@ msgid "Reports|Actions"
msgstr ""
msgid "Reports|Class"
-msgstr ""
+msgstr "Sınıf"
msgid "Reports|Confidence"
msgstr ""
@@ -7919,14 +9277,26 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
msgid "Reports|System output"
-msgstr ""
+msgstr "Sistem çıkışı"
msgid "Reports|Test summary"
-msgstr ""
+msgstr "Test özeti"
msgid "Reports|Test summary failed loading results"
msgstr ""
@@ -7937,16 +9307,25 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
msgid "Repository"
-msgstr ""
+msgstr "Depo"
msgid "Repository Settings"
-msgstr ""
+msgstr "Depo Ayarları"
msgid "Repository URL"
+msgstr "Depo URL'si"
+
+msgid "Repository check was triggered."
msgstr ""
msgid "Repository cleanup"
@@ -7977,7 +9356,7 @@ msgid "Requested %{time_ago}"
msgstr ""
msgid "Requests Profiles"
-msgstr ""
+msgstr "Profil Ä°stekleri"
msgid "Require all users in this group to setup Two-factor authentication"
msgstr ""
@@ -7985,6 +9364,19 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Resend invite"
msgstr ""
@@ -7998,7 +9390,7 @@ msgid "Reset health check access token"
msgstr ""
msgid "Reset key"
-msgstr ""
+msgstr "Anahtarı sıfırla"
msgid "Reset runners registration token"
msgstr ""
@@ -8018,9 +9410,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8045,12 +9446,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8083,12 +9490,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8110,6 +9526,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8143,29 +9565,29 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr ""
-
msgid "SSH Keys"
+msgstr "SSH Anahtarları"
+
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
-msgstr ""
+msgstr "SSH ana bilgisayar anahtarları"
msgid "SSH public key"
msgstr ""
@@ -8173,14 +9595,17 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
msgid "Save Changes"
-msgstr ""
+msgstr "DeÄŸiÅŸiklikleri Kaydet"
msgid "Save application"
-msgstr ""
+msgstr "Uygulamayı kaydet"
msgid "Save changes"
msgstr ""
@@ -8197,6 +9622,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -8206,15 +9634,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8225,72 +9662,84 @@ msgid "Scroll to top"
msgstr ""
msgid "Search"
-msgstr ""
+msgstr "Ara"
msgid "Search an environment spec"
-msgstr ""
+msgstr "Ortam özelliklerini ara"
msgid "Search branches"
-msgstr ""
+msgstr "Dalları ara"
msgid "Search branches and tags"
-msgstr ""
+msgstr "Dalları ve etiketleri ara"
msgid "Search files"
-msgstr ""
+msgstr "Dosyaları ara"
msgid "Search for projects, issues, etc."
-msgstr ""
+msgstr "Projeleri, sorunları vb. arayın."
msgid "Search groups"
-msgstr ""
+msgstr "Grupları ara"
msgid "Search merge requests"
-msgstr ""
+msgstr "Birleştirme isteklerini arayın"
msgid "Search milestones"
-msgstr ""
+msgstr "Dönüm noktalarını ara"
msgid "Search or filter results..."
-msgstr ""
+msgstr "Ara veya sonuçları filtrele..."
msgid "Search or jump to…"
-msgstr ""
+msgstr "Ara veya atla…"
msgid "Search project"
-msgstr ""
+msgstr "Proje ara"
msgid "Search projects"
-msgstr ""
+msgstr "Projeleri ara"
msgid "Search users"
-msgstr ""
+msgstr "Kullanıcıları ara"
+
+msgid "Search users or groups"
+msgstr "Kullanıcıları veya grupları ara"
msgid "Search your projects"
-msgstr ""
+msgstr "Projelerinizi arayın"
msgid "SearchAutocomplete|All GitLab"
msgstr ""
msgid "SearchAutocomplete|Issues I've created"
-msgstr ""
+msgstr "OluÅŸturduÄŸum Sorunlar"
msgid "SearchAutocomplete|Issues assigned to me"
-msgstr ""
+msgstr "Bana atanan sorunlar"
msgid "SearchAutocomplete|Merge requests I've created"
-msgstr ""
+msgstr "OluÅŸturduÄŸum birleÅŸtirme istekleri"
msgid "SearchAutocomplete|Merge requests assigned to me"
-msgstr ""
+msgstr "Bana atanan birleÅŸtirme istekleri"
msgid "SearchAutocomplete|in all GitLab"
-msgstr ""
+msgstr "Tüm GitLab'da"
msgid "SearchAutocomplete|in this group"
-msgstr ""
+msgstr "Bu grupta"
msgid "SearchAutocomplete|in this project"
+msgstr "Bu projede"
+
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
msgstr ""
msgid "Secret"
@@ -8314,11 +9763,8 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
-msgstr ""
+msgstr "Sorun oluÅŸtur"
msgid "Security Reports|Dismiss vulnerability"
msgstr ""
@@ -8332,10 +9778,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8386,6 +9832,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8395,9 +9850,6 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8405,13 +9857,13 @@ msgid "Select members to invite"
msgstr ""
msgid "Select project"
-msgstr ""
+msgstr "Proje seç"
msgid "Select project and zone to choose machine type"
-msgstr ""
+msgstr "Makine tipini seçmek için proje ve bölge seçin"
msgid "Select project to choose zone"
-msgstr ""
+msgstr "Bölge seçmek için proje seçin"
msgid "Select projects you want to import."
msgstr ""
@@ -8420,18 +9872,21 @@ msgid "Select source branch"
msgstr ""
msgid "Select target branch"
-msgstr ""
+msgstr "Hedef dalı seç"
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
msgid "Select the custom project template source group."
+msgstr "Özel proje şablonu kaynak grubunu seçin."
+
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8447,9 +9902,15 @@ msgid "Sentry API URL"
msgstr ""
msgid "Sep"
+msgstr "Eyl"
+
+msgid "Separate topics with commas."
msgstr ""
msgid "September"
+msgstr "Eylül"
+
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
msgstr ""
msgid "Server version"
@@ -8458,9 +9919,24 @@ msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8476,9 +9952,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8498,7 +9971,7 @@ msgid "Serverless|There is currently no function data available from Knative. Th
msgstr ""
msgid "Service Desk"
-msgstr ""
+msgstr "Servis Masası"
msgid "Service Templates"
msgstr ""
@@ -8506,17 +9979,26 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
+msgstr "Oturum zaman aşımı, proje sınırı ve ek boyutu."
+
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set a template repository for projects in this group"
-msgstr ""
+msgstr "Bu gruptaki projeler için bir şablon deposu ayarlayın"
msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
-msgstr ""
+msgstr "Varsayılanı ayarlayın ve görünürlük seviyelerini sınırlayın. İçe aktarma kaynaklarını ve git erişim protokolünü yapılandırın."
msgid "Set instance-wide template repository"
msgstr ""
@@ -8524,6 +10006,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr "Yeni parola ayarla"
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8533,6 +10018,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8548,35 +10045,38 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr "Yeni ÅŸifre ayarla"
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
msgid "SetPasswordToCloneLink|set a password"
-msgstr ""
+msgstr "ÅŸifre ayarla"
msgid "SetStatusModal|Add status emoji"
-msgstr ""
+msgstr "SetStatusModal|Durum ifadesi ekle"
msgid "SetStatusModal|Clear status"
-msgstr ""
+msgstr "SetStatusModal|Durumu temizle"
msgid "SetStatusModal|Edit status"
-msgstr ""
+msgstr "SetStatusModal|Durumu düzenle"
msgid "SetStatusModal|Remove status"
-msgstr ""
+msgstr "SetStatusModal|Durumu kaldır"
msgid "SetStatusModal|Set a status"
-msgstr ""
+msgstr "SetStatusModal|Bir durum ayarla"
msgid "SetStatusModal|Set status"
-msgstr ""
+msgstr "SetStatusModal|Durum ayarla"
msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
msgstr ""
msgid "SetStatusModal|What's your status?"
-msgstr ""
+msgstr "SetStatusModal|Durumunuz nedir?"
msgid "Settings"
msgstr ""
@@ -8605,9 +10105,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8617,6 +10123,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8628,8 +10137,8 @@ msgstr ""
msgid "Showing %d event"
msgid_plural "Showing %d events"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d etkinliği gösteriliyor"
+msgstr[1] "%d etkinlikleri gösteriliyor"
msgid "Side-by-side"
msgstr ""
@@ -8668,7 +10177,7 @@ msgid "Sign in with smart card"
msgstr ""
msgid "Sign out"
-msgstr ""
+msgstr "Oturumu kapat"
msgid "Sign-in restrictions"
msgstr ""
@@ -8676,15 +10185,24 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
-msgid "Similar issues"
+msgid "Signing in using %{label} has been disabled"
msgstr ""
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr "Benzer sorunlar"
+
msgid "Size"
msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8704,6 +10222,27 @@ msgid "Snippet Contents"
msgstr ""
msgid "Snippets"
+msgstr "Parçacıklar"
+
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr "Genel parçacıkları keşfedin"
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr "Yeni parçacık"
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr "Parçacık bulunamadı"
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr "Parçacıklar, saklamak istediğiniz küçük kod parçaları veya notlardır."
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr "Gösterilecek parçacık yok."
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr "Genel veya özel olabilirler."
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
msgstr ""
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
@@ -8751,6 +10290,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8778,71 +10320,74 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
msgid "Sorry, no epics matched your search"
-msgstr ""
+msgstr "Üzgünüz, aramanızla eşleşen epik yok"
msgid "Sorry, no projects matched your search"
-msgstr ""
+msgstr "Üzgünüz, aramanızla eşleşen proje yok"
msgid "Sorry, your filter produced no results"
msgstr ""
msgid "Sort by"
-msgstr ""
+msgstr "Şuna göre sırala"
msgid "Sort direction"
-msgstr ""
+msgstr "Sıralama yönü"
msgid "SortOptions|Access level, ascending"
-msgstr ""
+msgstr "EriÅŸim seviyesi, artan"
msgid "SortOptions|Access level, descending"
-msgstr ""
+msgstr "EriÅŸim seviyesi, azalan"
msgid "SortOptions|Created date"
-msgstr ""
+msgstr "OluÅŸturulma tarihi"
msgid "SortOptions|Due date"
-msgstr ""
+msgstr "Son tarih"
msgid "SortOptions|Due later"
-msgstr ""
+msgstr "Daha sonra"
msgid "SortOptions|Due soon"
-msgstr ""
+msgstr "Yakında"
msgid "SortOptions|Label priority"
-msgstr ""
+msgstr "Etiket önceliği"
msgid "SortOptions|Largest group"
-msgstr ""
+msgstr "En büyük grup"
msgid "SortOptions|Largest repository"
-msgstr ""
+msgstr "En büyük depo"
msgid "SortOptions|Last Contact"
msgstr ""
msgid "SortOptions|Last created"
-msgstr ""
+msgstr "Son oluÅŸturulan"
msgid "SortOptions|Last joined"
-msgstr ""
+msgstr "Son katılan"
msgid "SortOptions|Last updated"
-msgstr ""
+msgstr "Son güncelleme"
msgid "SortOptions|Least popular"
msgstr ""
msgid "SortOptions|Less weight"
-msgstr ""
+msgstr "Daha az ağırlık"
msgid "SortOptions|Milestone due date"
-msgstr ""
+msgstr "Dönüm noktası bitiş tarihi"
msgid "SortOptions|Milestone due later"
msgstr ""
@@ -8851,7 +10396,7 @@ msgid "SortOptions|Milestone due soon"
msgstr ""
msgid "SortOptions|More weight"
-msgstr ""
+msgstr "Daha fazla ağırlık"
msgid "SortOptions|Most popular"
msgstr ""
@@ -8860,61 +10405,61 @@ msgid "SortOptions|Most stars"
msgstr ""
msgid "SortOptions|Name"
-msgstr ""
+msgstr "Ä°sim"
msgid "SortOptions|Name, ascending"
-msgstr ""
+msgstr "Ä°sim, artan"
msgid "SortOptions|Name, descending"
-msgstr ""
+msgstr "Ä°sim, azalan"
msgid "SortOptions|Oldest created"
msgstr ""
msgid "SortOptions|Oldest joined"
-msgstr ""
+msgstr "En eski katılan"
msgid "SortOptions|Oldest last activity"
-msgstr ""
+msgstr "En son etkinlik"
msgid "SortOptions|Oldest sign in"
msgstr ""
msgid "SortOptions|Oldest updated"
-msgstr ""
+msgstr "En eski güncelleme"
msgid "SortOptions|Popularity"
msgstr ""
msgid "SortOptions|Priority"
-msgstr ""
+msgstr "Öncelik"
msgid "SortOptions|Recent last activity"
-msgstr ""
+msgstr "Son etkinlik"
msgid "SortOptions|Recent sign in"
-msgstr ""
+msgstr "Son giriÅŸ"
msgid "SortOptions|Start date"
-msgstr ""
+msgstr "Başlangıç ​​tarihi"
msgid "SortOptions|Start later"
-msgstr ""
+msgstr "Daha sonra baÅŸlat"
msgid "SortOptions|Start soon"
-msgstr ""
+msgstr "Yakında başlat"
msgid "SortOptions|Weight"
-msgstr ""
+msgstr "Ağırlık"
msgid "Source"
msgstr ""
msgid "Source (branch or tag)"
-msgstr ""
+msgstr "Kaynak (dal veya etiket)"
msgid "Source code"
-msgstr ""
+msgstr "Kaynak kodu"
msgid "Source is not available"
msgstr ""
@@ -8928,6 +10473,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8944,7 +10492,7 @@ msgid "Squash commits"
msgstr ""
msgid "Stage"
-msgstr ""
+msgstr "AÅŸama"
msgid "Stage & Commit"
msgstr ""
@@ -8976,15 +10524,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -9010,7 +10570,7 @@ msgid "Start the Runner!"
msgstr ""
msgid "Start your trial"
-msgstr ""
+msgstr "Denemenizi başlatın"
msgid "Started"
msgstr ""
@@ -9018,6 +10578,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9034,7 +10597,7 @@ msgid "Status"
msgstr ""
msgid "Status:"
-msgstr ""
+msgstr "Durum:"
msgid "Stop Terminal"
msgstr ""
@@ -9064,40 +10627,40 @@ msgid "Storage:"
msgstr ""
msgid "Subgroups"
-msgstr ""
+msgstr "Alt gruplar"
msgid "Subgroups and projects"
-msgstr ""
+msgstr "Alt gruplar ve projeler"
msgid "Submit as spam"
-msgstr ""
+msgstr "Spam olarak gönder"
msgid "Submit feedback"
-msgstr ""
+msgstr "Geri bildirim gönder"
msgid "Submit review"
-msgstr ""
+msgstr "İnceleme gönder"
msgid "Submit search"
-msgstr ""
+msgstr "Aramayı gönder"
msgid "Subscribe"
-msgstr ""
+msgstr "Abone ol"
msgid "Subscribe at group level"
-msgstr ""
+msgstr "Grup düzeyinde abone olun"
msgid "Subscribe at project level"
-msgstr ""
+msgstr "Proje düzeyinde abone olun"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "RSS beslemesine abone olun"
msgid "Subscribe to calendar"
-msgstr ""
+msgstr "Takvime abone ol"
msgid "Subscribed"
-msgstr ""
+msgstr "Abone"
msgid "SubscriptionTable|Billing"
msgstr ""
@@ -9157,18 +10720,39 @@ msgid "SubscriptionTable|Trial"
msgstr ""
msgid "SubscriptionTable|Trial end date"
-msgstr ""
+msgstr "Deneme bitiÅŸ tarihi"
msgid "SubscriptionTable|Trial start date"
-msgstr ""
+msgstr "Deneme başlangıç tarihi"
msgid "SubscriptionTable|Upgrade"
-msgstr ""
+msgstr "Yükselt"
msgid "SubscriptionTable|Usage"
-msgstr ""
+msgstr "Kullanım"
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr "Kullanım sayımı günde bir kez 12:00 ÖS yapılır."
+
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
msgstr ""
msgid "Suggested change"
@@ -9180,15 +10764,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr "Sistem"
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9198,6 +10791,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9211,49 +10807,49 @@ msgid "Tag list:"
msgstr ""
msgid "Tags"
-msgstr ""
+msgstr "Etiketler"
msgid "Tags feed"
msgstr ""
msgid "Tags:"
-msgstr ""
+msgstr "Etiketler:"
msgid "TagsPage|Browse commits"
msgstr ""
msgid "TagsPage|Browse files"
-msgstr ""
+msgstr "Dosyalara gözat"
msgid "TagsPage|Can't find HEAD commit for this tag"
msgstr ""
msgid "TagsPage|Cancel"
-msgstr ""
+msgstr "TagsPage|Ä°ptal"
msgid "TagsPage|Create tag"
msgstr ""
msgid "TagsPage|Delete tag"
-msgstr ""
+msgstr "Etiketi sil"
msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
msgstr ""
msgid "TagsPage|Edit release notes"
-msgstr ""
+msgstr "Sürüm notlarını düzenle"
msgid "TagsPage|Existing branch name, tag, or commit SHA"
-msgstr ""
+msgstr "Mevcut dal adını, etiketi veya SHA'yı taahhüt et"
msgid "TagsPage|Filter by tag name"
-msgstr ""
+msgstr "Etiket adına göre süz"
msgid "TagsPage|New Tag"
-msgstr ""
+msgstr "Yeni Etiket"
msgid "TagsPage|New tag"
-msgstr ""
+msgstr "Yeni etiket"
msgid "TagsPage|Optionally, add a message to the tag."
msgstr ""
@@ -9268,10 +10864,10 @@ msgid "TagsPage|Repository has no tags yet."
msgstr ""
msgid "TagsPage|Sort by"
-msgstr ""
+msgstr "Sırala"
msgid "TagsPage|Tags"
-msgstr ""
+msgstr "Etiketler"
msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
msgstr ""
@@ -9283,37 +10879,37 @@ msgid "TagsPage|Use git tag command to add a new one:"
msgstr ""
msgid "TagsPage|Write your release notes or drag files here…"
-msgstr ""
+msgstr "Sürüm notlarınızı yazın veya dosyaları buraya sürükleyin…"
msgid "TagsPage|protected"
msgstr ""
msgid "Target Branch"
-msgstr ""
+msgstr "Hedef Dal"
msgid "Target branch"
-msgstr ""
+msgstr "Hedef dal"
msgid "Team"
-msgstr ""
+msgstr "Ekip"
msgid "Template"
-msgstr ""
+msgstr "Åžablon"
msgid "Templates"
-msgstr ""
+msgstr "Åžablonlar"
msgid "Terminal"
-msgstr ""
+msgstr "Uçbirim"
msgid "Terminal for environment"
msgstr ""
msgid "Terms of Service Agreement and Privacy Policy"
-msgstr ""
+msgstr "Hizmet Koşulları Sözleşmesi ve Gizlilik Politikası"
msgid "Terms of Service and Privacy Policy"
-msgstr ""
+msgstr "Hizmet Koşulları ve Gizlilik Politikası"
msgid "Test SAML SSO"
msgstr ""
@@ -9321,12 +10917,23 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
-msgid "Thanks! Don't show me this again"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
msgstr ""
+msgid "Thanks! Don't show me this again"
+msgstr "Teşekkürler! Bu mesajı tekrar gösterme"
+
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9345,6 +10952,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9357,15 +10970,63 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 ""
@@ -9375,6 +11036,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9402,9 +11075,18 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -9423,6 +11105,21 @@ msgstr ""
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 ""
@@ -9438,9 +11135,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9454,28 +11160,28 @@ msgid "There are no approvers"
msgstr ""
msgid "There are no archived projects yet"
-msgstr ""
+msgstr "Henüz arşivlenmiş proje yok"
msgid "There are no closed issues"
msgstr ""
msgid "There are no closed merge requests"
-msgstr ""
+msgstr "Kapalı bir birleştirme isteği yok"
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
msgstr ""
msgid "There are no issues to show"
-msgstr ""
+msgstr "Gösterilecek bir sorun yok"
msgid "There are no labels yet"
-msgstr ""
+msgstr "Henüz etiket yok"
msgid "There are no open issues"
msgstr ""
msgid "There are no open merge requests"
-msgstr ""
+msgstr "Açık birleştirme isteği yok"
msgid "There are no packages yet"
msgstr ""
@@ -9495,14 +11201,26 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
+msgstr "Kullanıcı etkinlik takvimi yüklenirken bir hata oluştu."
+
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
msgstr ""
msgid "There was an error saving your notification settings."
msgstr ""
msgid "There was an error subscribing to this label."
-msgstr ""
+msgstr "Bu etikete abone olurken bir hata oluÅŸtu."
msgid "There was an error when reseting email token."
msgstr ""
@@ -9513,6 +11231,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9520,7 +11241,7 @@ msgid "They can be managed using the %{link}."
msgstr ""
msgid "Third party offers"
-msgstr ""
+msgstr "Üçüncü taraf teklifleri"
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
@@ -9531,6 +11252,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9543,6 +11267,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9559,19 +11298,22 @@ msgid "This diff was suppressed by a .gitattributes entry."
msgstr ""
msgid "This directory"
-msgstr ""
+msgstr "Bu dizin"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
-msgid "This group"
+msgid "This field is required."
msgstr ""
+msgid "This group"
+msgstr "Bu grup"
+
msgid "This group does not provide any group Runners yet."
msgstr ""
msgid "This is a confidential issue."
-msgstr ""
+msgstr "Bu gizli bir sorundur."
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
@@ -9579,14 +11321,17 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
-msgid "This issue is confidential"
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
msgstr ""
+msgid "This issue is confidential"
+msgstr "Bu sorun gizlidir"
+
msgid "This issue is confidential and locked."
-msgstr ""
+msgstr "Bu sorun gizli ve kilitli."
msgid "This issue is locked."
-msgstr ""
+msgstr "Bu sorun kilitlendi."
msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
msgstr ""
@@ -9598,7 +11343,7 @@ msgid "This job does not have a trace."
msgstr ""
msgid "This job has been canceled"
-msgstr ""
+msgstr "Bu iÅŸ iptal edildi"
msgid "This job has been skipped"
msgstr ""
@@ -9627,6 +11372,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9646,12 +11397,18 @@ msgid "This means you can not push code until you create an empty repository or
msgstr ""
msgid "This merge request is locked."
+msgstr "Bu birleÅŸtirme isteÄŸi kilitlendi."
+
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9660,7 +11417,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9670,7 +11427,7 @@ msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b
msgstr ""
msgid "This project"
-msgstr ""
+msgstr "Bu proje"
msgid "This project does not belong to a group and can therefore not make use of group Runners."
msgstr ""
@@ -9682,6 +11439,9 @@ msgid "This project does not have billing enabled. To create a cluster, <a href=
msgstr ""
msgid "This repository"
+msgstr "Bu depo"
+
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
@@ -9693,14 +11453,14 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
-msgstr ""
+msgstr "Bu kullanıcının kimliği yok"
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
@@ -9711,6 +11471,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9733,16 +11496,16 @@ msgid "Time in seconds GitLab will wait for a response from the external service
msgstr ""
msgid "Time remaining"
-msgstr ""
+msgstr "Kalan süre"
msgid "Time spent"
-msgstr ""
+msgstr "Harcanan zaman"
msgid "Time tracking"
-msgstr ""
+msgstr "Zaman takibi"
msgid "Time until first merge request"
-msgstr ""
+msgstr "İlk birleştirme isteğine kadar geçen süre"
msgid "TimeTrackingEstimated|Est"
msgstr ""
@@ -9754,155 +11517,152 @@ msgid "TimeTracking|Spent"
msgstr ""
msgid "Timeago|%s days ago"
-msgstr ""
+msgstr "%s gün önce"
msgid "Timeago|%s days remaining"
-msgstr ""
+msgstr "%s gün kaldı"
msgid "Timeago|%s hours ago"
-msgstr ""
+msgstr "%s saat önce"
msgid "Timeago|%s hours remaining"
-msgstr ""
+msgstr "%s saat kaldı"
msgid "Timeago|%s minutes ago"
-msgstr ""
+msgstr "%s dakika önce"
msgid "Timeago|%s minutes remaining"
-msgstr ""
+msgstr "%s dakika kaldı"
msgid "Timeago|%s months ago"
-msgstr ""
+msgstr "%s ay önce"
msgid "Timeago|%s months remaining"
-msgstr ""
-
-msgid "Timeago|%s seconds ago"
-msgstr ""
+msgstr "%s ay kaldı"
msgid "Timeago|%s seconds remaining"
-msgstr ""
+msgstr "%s saniye kaldı"
msgid "Timeago|%s weeks ago"
-msgstr ""
+msgstr "%s hafta önce"
msgid "Timeago|%s weeks remaining"
-msgstr ""
+msgstr "%s hafta kaldı"
msgid "Timeago|%s years ago"
-msgstr ""
+msgstr "%s yıl önce"
msgid "Timeago|%s years remaining"
-msgstr ""
+msgstr "%s yıl kaldı"
msgid "Timeago|1 day ago"
-msgstr ""
+msgstr "1 gün önce"
msgid "Timeago|1 day remaining"
-msgstr ""
+msgstr "1 gün kaldı"
msgid "Timeago|1 hour ago"
-msgstr ""
+msgstr "1 saat önce"
msgid "Timeago|1 hour remaining"
-msgstr ""
+msgstr "1 saat kaldı"
msgid "Timeago|1 minute ago"
-msgstr ""
+msgstr "1 dakika önce"
msgid "Timeago|1 minute remaining"
-msgstr ""
+msgstr "1 dakika kaldı"
msgid "Timeago|1 month ago"
-msgstr ""
+msgstr "1 ay önce"
msgid "Timeago|1 month remaining"
-msgstr ""
+msgstr "1 ay kaldı"
msgid "Timeago|1 week ago"
-msgstr ""
+msgstr "1 hafta önce"
msgid "Timeago|1 week remaining"
-msgstr ""
+msgstr "1 hafta kaldı"
msgid "Timeago|1 year ago"
-msgstr ""
+msgstr "1 yıl önce"
msgid "Timeago|1 year remaining"
-msgstr ""
+msgstr "1 yıl kaldı"
msgid "Timeago|Past due"
-msgstr ""
+msgstr "Vadesi geçmiş"
msgid "Timeago|in %s days"
-msgstr ""
+msgstr "%s gün içinde"
msgid "Timeago|in %s hours"
-msgstr ""
+msgstr "%s saat içinde"
msgid "Timeago|in %s minutes"
-msgstr ""
+msgstr "%s dakika içinde"
msgid "Timeago|in %s months"
-msgstr ""
+msgstr "%s ay içinde"
msgid "Timeago|in %s seconds"
-msgstr ""
+msgstr "%s saniye içinde"
msgid "Timeago|in %s weeks"
-msgstr ""
+msgstr "%s hafta içinde"
msgid "Timeago|in %s years"
-msgstr ""
+msgstr "%s yıl içinde"
msgid "Timeago|in 1 day"
-msgstr ""
+msgstr "1 gün içinde"
msgid "Timeago|in 1 hour"
-msgstr ""
+msgstr "1 saat içinde"
msgid "Timeago|in 1 minute"
-msgstr ""
+msgstr "1 dakika içinde"
msgid "Timeago|in 1 month"
-msgstr ""
+msgstr "1 ay içinde"
msgid "Timeago|in 1 week"
-msgstr ""
+msgstr "1 hafta içinde"
msgid "Timeago|in 1 year"
-msgstr ""
+msgstr "1 yıl içinde"
msgid "Timeago|just now"
-msgstr ""
+msgstr "ÅŸimdi"
msgid "Timeago|right now"
-msgstr ""
+msgstr "hemen ÅŸimdi"
msgid "Timeout"
-msgstr ""
+msgstr "Zaman aşımı"
msgid "Time|hr"
msgid_plural "Time|hrs"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "saat"
+msgstr[1] "saat"
msgid "Time|min"
msgid_plural "Time|mins"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dak"
+msgstr[1] "dak"
msgid "Time|s"
-msgstr ""
+msgstr "sn"
msgid "Tip:"
-msgstr ""
+msgstr "Ä°pucu:"
msgid "Title"
-msgstr ""
+msgstr "Başlık"
msgid "Titles and Filenames"
-msgstr ""
+msgstr "Başlıklar ve dosya adları"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
@@ -9956,7 +11716,7 @@ msgid "To import an SVN repository, check out %{svn_link}."
msgstr ""
msgid "To keep this project going, create a new issue"
-msgstr ""
+msgstr "Bu projenin devam etmesini sağlamak için yeni bir fikir verin"
msgid "To keep this project going, create a new merge request"
msgstr ""
@@ -10004,34 +11764,37 @@ msgid "To widen your search, change or remove filters."
msgstr ""
msgid "Today"
-msgstr ""
+msgstr "Bugün"
msgid "Todo"
+msgstr "Yapılacaklar"
+
+msgid "Todo was successfully marked as done."
msgstr ""
msgid "Todos"
-msgstr ""
+msgstr "Yapılacaklar"
msgid "Toggle Sidebar"
-msgstr ""
+msgstr "Kenar çubuğunu değiştir"
msgid "Toggle comments for this file"
msgstr ""
msgid "Toggle commit description"
-msgstr ""
+msgstr "İşlem açıklamasını göster"
msgid "Toggle commit list"
msgstr ""
msgid "Toggle discussion"
-msgstr ""
+msgstr "Tartışmayı değiştir"
msgid "Toggle navigation"
-msgstr ""
+msgstr "Gezinmeyi deÄŸiÅŸtir"
msgid "Toggle sidebar"
-msgstr ""
+msgstr "Kenar çubuğunu göster/gizle"
msgid "ToggleButton|Toggle Status: OFF"
msgstr ""
@@ -10040,25 +11803,31 @@ msgid "ToggleButton|Toggle Status: ON"
msgstr ""
msgid "Token"
-msgstr ""
+msgstr "Erişim anahtarı"
msgid "Tomorrow"
-msgstr ""
+msgstr "Yarın"
msgid "Too many changes to show."
msgstr ""
-msgid "Total Contributions"
+msgid "Topics"
msgstr ""
-msgid "Total Time"
+msgid "Total"
msgstr ""
+msgid "Total Contributions"
+msgstr "Toplam Katkı"
+
+msgid "Total Time"
+msgstr "Toplam Zaman"
+
msgid "Total test time for all commits/merges"
msgstr ""
msgid "Total: %{total}"
-msgstr ""
+msgstr "Toplam: %{total}"
msgid "Tracing"
msgstr ""
@@ -10072,6 +11841,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10084,6 +11856,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10093,6 +11868,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10100,10 +11887,10 @@ msgid "Troubleshoot and monitor your application with tracing"
msgstr ""
msgid "Try again"
-msgstr ""
+msgstr "Tekrar dene"
msgid "Try again?"
-msgstr ""
+msgstr "Tekrar denensin mi?"
msgid "Try all GitLab has to offer for 30 days."
msgstr ""
@@ -10112,29 +11899,44 @@ msgid "Trying to communicate with your device. Plug it in (if you haven't alread
msgstr ""
msgid "Turn on Service Desk"
-msgstr ""
+msgstr "Servis Masasını Aç"
msgid "Twitter"
+msgstr "Twitter"
+
+msgid "Two-factor Authentication has been disabled for this user"
msgstr ""
msgid "Two-factor authentication"
-msgstr ""
+msgstr "İki adımlı kimlik doğrulama"
msgid "Type"
-msgstr ""
+msgstr "Tür"
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10145,19 +11947,22 @@ msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
msgid "Unknown"
+msgstr "Bilinmeyen"
+
+msgid "Unlimited"
msgstr ""
msgid "Unlock"
-msgstr ""
+msgstr "Kilidi aç"
msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment."
msgstr ""
msgid "Unlocked"
-msgstr ""
+msgstr "Kilitli"
msgid "Unresolve discussion"
-msgstr ""
+msgstr "Çözülmeyen tartışma"
msgid "Unschedule job"
msgstr ""
@@ -10166,7 +11971,7 @@ msgid "Unstage"
msgstr ""
msgid "Unstage all changes"
-msgstr ""
+msgstr "Tüm değişiklikleri kaldır"
msgid "Unstage changes"
msgstr ""
@@ -10181,25 +11986,25 @@ msgid "Unstaged and staged %{type}"
msgstr ""
msgid "Unstar"
-msgstr ""
+msgstr "Yıldızı kaldır"
msgid "Unsubscribe"
-msgstr ""
+msgstr "Abonelikten çık"
msgid "Unsubscribe at group level"
-msgstr ""
+msgstr "Grup düzeyinde abonelikten çık"
msgid "Unsubscribe at project level"
-msgstr ""
+msgstr "Proje düzeyinde abonelikten çık"
msgid "Unsubscribe from %{type}"
msgstr ""
msgid "Unverified"
-msgstr ""
+msgstr "Doğrulanmamış"
msgid "Up to date"
-msgstr ""
+msgstr "Güncel"
msgid "Upcoming"
msgstr ""
@@ -10207,21 +12012,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
-msgid "Updating"
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
msgstr ""
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
+msgid "Updating"
+msgstr "Güncelleniyor"
+
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10237,6 +12060,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10244,21 +12070,27 @@ msgid "Upload CSV file"
msgstr ""
msgid "Upload New File"
-msgstr ""
+msgstr "Yeni Dosya Yükle"
msgid "Upload a certificate for your domain with all intermediates"
msgstr ""
msgid "Upload a private key for your certificate"
-msgstr ""
+msgstr "Sertifikanız için özel bir anahtar yükleyin"
msgid "Upload file"
-msgstr ""
+msgstr "Dosya yükle"
msgid "Upload object map"
msgstr ""
msgid "UploadLink|click to upload"
+msgstr "yüklemek için tıklayın"
+
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
msgstr ""
msgid "Upstream"
@@ -10267,10 +12099,16 @@ msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
+msgstr "Kullanım istatistikleri"
+
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
msgstr ""
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
@@ -10286,7 +12124,7 @@ msgid "Use one line per URI"
msgstr ""
msgid "Use template"
-msgstr ""
+msgstr "Åžablonu kullan"
msgid "Use the following registration token during setup:"
msgstr ""
@@ -10303,106 +12141,163 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
-msgid "User Settings"
+msgid "User OAuth applications"
msgstr ""
+msgid "User Settings"
+msgstr "Kullanıcı Ayarları"
+
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
+msgstr "Kullanıcı haritası"
+
+msgid "User settings"
msgstr ""
-msgid "UserProfile|Activity"
+msgid "User was successfully created."
msgstr ""
-msgid "UserProfile|Already reported for abuse"
+msgid "User was successfully removed from group and any subresources."
msgstr ""
-msgid "UserProfile|Contributed projects"
+msgid "User was successfully removed from project."
msgstr ""
-msgid "UserProfile|Edit profile"
+msgid "User was successfully updated."
msgstr ""
+msgid "UserProfile|Activity"
+msgstr "Etkinlik"
+
+msgid "UserProfile|Already reported for abuse"
+msgstr "Suistimal için zaten rapor edildi"
+
+msgid "UserProfile|Contributed projects"
+msgstr "Katıldığı projeler"
+
+msgid "UserProfile|Edit profile"
+msgstr "Profili düzenle"
+
msgid "UserProfile|Explore public groups to find projects to contribute to."
-msgstr ""
+msgstr "Katkıda bulunacak projeler bulmak için ortak grupları keşfedin."
msgid "UserProfile|Groups"
-msgstr ""
+msgstr "Gruplar"
msgid "UserProfile|Groups are the best way to manage projects and members."
-msgstr ""
+msgstr "Gruplar, projeleri ve kullanıcıları yönetmenin en iyi yoludur."
msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
-msgstr ""
+msgstr "Konularda yorum veya birleştirme istekleri göndererek katkıda bulunmak için bir grup oluşturun veya katılın!"
msgid "UserProfile|Most Recent Activity"
-msgstr ""
+msgstr "En Son Etkinlik"
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "Parçacık bulunamadı."
msgid "UserProfile|Overview"
-msgstr ""
+msgstr "Genel bakış"
msgid "UserProfile|Personal projects"
-msgstr ""
+msgstr "KiÅŸisel projeler"
msgid "UserProfile|Report abuse"
-msgstr ""
+msgstr "Kötüye kullanımı bildir"
msgid "UserProfile|Snippets"
-msgstr ""
+msgstr "Parçacıklar"
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
-msgstr ""
+msgstr "GitLab'daki parçacıklar özel, dahili veya genel olabilir."
msgid "UserProfile|Subscribe"
-msgstr ""
+msgstr "Abone"
msgid "UserProfile|This user doesn't have any personal projects"
-msgstr ""
+msgstr "Bu kullanıcının herhangi bir kişisel projesi yok"
msgid "UserProfile|This user has a private profile"
-msgstr ""
+msgstr "Bu kullanıcı özel bir profile sahip"
msgid "UserProfile|This user hasn't contributed to any projects"
-msgstr ""
+msgstr "Bu kullanıcı hiçbir projeye katkıda bulunmadı"
msgid "UserProfile|View all"
-msgstr ""
+msgstr "Tümünü görüntüle"
msgid "UserProfile|View user in admin area"
-msgstr ""
+msgstr "Kullanıcıyı yönetici alanında görüntüle"
msgid "UserProfile|You can create a group for several dependent projects."
-msgstr ""
+msgstr "Birkaç bağımlı proje için bir grup oluşturabilirsiniz."
msgid "UserProfile|You haven't created any personal projects."
-msgstr ""
+msgstr "Herhangi bir kişisel proje oluşturmadınız."
msgid "UserProfile|You haven't created any snippets."
-msgstr ""
+msgstr "Herhangi bir parçacık oluşturmadınız."
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
-msgstr ""
+msgstr "Projeleriniz tercihen, genel ve dahili olarak yada anonim olarak kullanılabilir."
msgid "Users"
+msgstr "Kullanıcılar"
+
+msgid "Users outside of license"
msgstr ""
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
+msgstr "DeÄŸer"
+
+msgid "Variables"
msgstr ""
msgid "Various container registry settings."
@@ -10418,19 +12313,16 @@ msgid "Various settings that affect GitLab performance."
msgstr ""
msgid "Verification information"
-msgstr ""
+msgstr "DoÄŸrulama bilgileri"
msgid "Verification status"
msgstr ""
msgid "Verified"
-msgstr ""
+msgstr "Doğrulanmış"
msgid "Version"
-msgstr ""
-
-msgid "View %{alerts}"
-msgstr ""
+msgstr "Sürüm"
msgid "View app"
msgstr ""
@@ -10441,41 +12333,44 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
msgstr ""
msgid "View file @ "
-msgstr ""
+msgstr "Dosyayı görüntüle @ "
msgid "View group labels"
-msgstr ""
+msgstr "Grup etiketlerini görüntüle"
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
+msgstr "GitLab'da görüntüle"
+
+msgid "View job trace"
msgstr ""
msgid "View jobs"
-msgstr ""
+msgstr "İşleri görüntüle"
msgid "View labels"
-msgstr ""
+msgstr "Etiketleri göster"
msgid "View log"
-msgstr ""
+msgstr "Günlüğü görüntüle"
msgid "View open merge request"
-msgstr ""
+msgstr "Açık birleştirme isteğini görüntüle"
msgid "View project labels"
-msgstr ""
+msgstr "Proje etiketlerini görüntüle"
msgid "View replaced file @ "
-msgstr ""
+msgstr "Değiştirilen @ dosyayı görüntüle "
msgid "View the documentation"
msgstr ""
@@ -10484,28 +12379,31 @@ msgid "Viewing commit"
msgstr ""
msgid "Visibility and access controls"
-msgstr ""
+msgstr "Görünürlük ve erişim kontrolleri"
msgid "Visibility level"
-msgstr ""
+msgstr "Görünürlük seviyesi"
msgid "Visibility level:"
+msgstr "Görünürlük seviyesi:"
+
+msgid "Visibility, project features, permissions"
msgstr ""
msgid "Visibility:"
-msgstr ""
+msgstr "Görünürlük:"
msgid "VisibilityLevel|Internal"
-msgstr ""
+msgstr "Dahili"
msgid "VisibilityLevel|Private"
-msgstr ""
+msgstr "Gizli"
msgid "VisibilityLevel|Public"
-msgstr ""
+msgstr "Genel"
msgid "VisibilityLevel|Unknown"
-msgstr ""
+msgstr "Bilinmeyen"
msgid "Vulnerability Chart"
msgstr ""
@@ -10520,25 +12418,31 @@ msgid "Vulnerability|Confidence"
msgstr ""
msgid "Vulnerability|Description"
-msgstr ""
+msgstr "Açıklama"
msgid "Vulnerability|File"
-msgstr ""
+msgstr "Dosya"
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
-msgid "Vulnerability|Project"
+msgid "Vulnerability|Namespace"
msgstr ""
+msgid "Vulnerability|Project"
+msgstr "Proje"
+
msgid "Vulnerability|Report Type"
-msgstr ""
+msgstr "Rapor Türü"
msgid "Vulnerability|Severity"
msgstr ""
@@ -10574,25 +12478,31 @@ msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
msgid "Web IDE"
-msgstr ""
+msgstr "Web IDE"
msgid "Web Terminal"
msgstr ""
msgid "Web terminal"
+msgstr "Web terminali"
+
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
msgstr ""
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
msgid "Weeks"
-msgstr ""
+msgstr "Haftalar"
msgid "Weight"
-msgstr ""
+msgstr "Ağırlık"
msgid "Weight %{weight}"
-msgstr ""
+msgstr "Ağırlık %{weight}"
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
@@ -10600,9 +12510,17 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "When:"
msgstr ""
@@ -10613,22 +12531,25 @@ msgid "Who will be able to see this group?"
msgstr ""
msgid "Wiki"
+msgstr "Wiki"
+
+msgid "Wiki was successfully updated."
msgstr ""
msgid "WikiClone|Clone your wiki"
-msgstr ""
+msgstr "Wiki'nizi kopyalayın"
msgid "WikiClone|Git Access"
-msgstr ""
+msgstr "Git EriÅŸimi"
msgid "WikiClone|Install Gollum"
-msgstr ""
+msgstr "Gollum'u yükleyin"
msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:"
msgstr ""
msgid "WikiClone|Start Gollum and edit locally"
-msgstr ""
+msgstr "Gollum'u başlat ve yerel olarak düzenle"
msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
msgstr ""
@@ -10670,16 +12591,16 @@ msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{h
msgstr ""
msgid "WikiHistoricalPage|history"
-msgstr ""
+msgstr "geçmiş"
msgid "WikiHistoricalPage|most recent version"
-msgstr ""
+msgstr "en son sürüm"
msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
msgstr ""
msgid "WikiMarkdownDocs|documentation"
-msgstr ""
+msgstr "belgeleme"
msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
msgstr ""
@@ -10691,16 +12612,16 @@ msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We wi
msgstr ""
msgid "WikiNewPageTitle|New Wiki Page"
-msgstr ""
+msgstr "Yeni Viki Sayfası"
msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
msgstr ""
msgid "WikiPageConfirmDelete|Delete page"
-msgstr ""
+msgstr "Sayfayı sil"
msgid "WikiPageConfirmDelete|Delete page %{pageTitle}?"
-msgstr ""
+msgstr "%{pageTitle} sayfası silinsin mi?"
msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs."
msgstr ""
@@ -10709,10 +12630,10 @@ msgid "WikiPageConflictMessage|the page"
msgstr ""
msgid "WikiPageCreate|Create %{page_title}"
-msgstr ""
+msgstr "%{page_title} oluÅŸtur"
msgid "WikiPageEdit|Update %{page_title}"
-msgstr ""
+msgstr "%{page_title} güncelle"
msgid "WikiPage|Page slug"
msgstr ""
@@ -10721,31 +12642,37 @@ msgid "WikiPage|Write your content or drag files here…"
msgstr ""
msgid "Wiki|Create Page"
-msgstr ""
+msgstr "Sayfa OluÅŸtur"
msgid "Wiki|Create page"
+msgstr "Sayfa oluÅŸtur"
+
+msgid "Wiki|Created date"
msgstr ""
msgid "Wiki|Edit Page"
-msgstr ""
+msgstr "Sayfayı Düzenle"
msgid "Wiki|More Pages"
-msgstr ""
+msgstr "Daha Fazla Sayfa"
msgid "Wiki|New page"
-msgstr ""
+msgstr "Yeni sayfa"
msgid "Wiki|Page history"
-msgstr ""
+msgstr "Sayfa geçmişi"
msgid "Wiki|Page version"
-msgstr ""
+msgstr "Sayfa sürümü"
msgid "Wiki|Pages"
+msgstr "Sayfalar"
+
+msgid "Wiki|Title"
msgstr ""
msgid "Wiki|Wiki Pages"
-msgstr ""
+msgstr "Viki Sayfaları"
msgid "Will deploy to"
msgstr ""
@@ -10756,17 +12683,29 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
-msgid "Write a comment or drag your files here…"
+msgid "Workflow Help"
msgstr ""
+msgid "Write"
+msgstr "Yaz"
+
+msgid "Write a comment or drag your files here…"
+msgstr "Bir yorum yazın veya dosyalarınızı buraya sürükleyin…"
+
+msgid "Write access allowed"
+msgstr "Yazma eriÅŸimine izin verildi"
+
msgid "Write milestone description..."
+msgstr "Dönüm noktası açıklaması yazın..."
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
-msgstr ""
+msgstr "Evet"
msgid "Yes, add it"
-msgstr ""
+msgstr "Evet, bunu ekle"
msgid "Yes, let me map Google Code users to full names or GitLab users."
msgstr ""
@@ -10792,6 +12731,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10807,15 +12749,30 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
-msgid "You can easily contribute to them by requesting to join these groups."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
msgstr ""
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
+msgid "You can easily contribute to them by requesting to join these groups."
+msgstr "Bu gruplara katılmayı talep ederek onlara kolayca katkıda bulunabilirsiniz."
+
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -10825,6 +12782,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10834,10 +12794,28 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
+msgstr "Bu salt okunur GitLab örneğine yazamazsınız."
+
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
msgstr ""
msgid "You do not have any subscriptions yet"
@@ -10850,18 +12828,33 @@ msgid "You do not have the correct permissions to override the settings from the
msgstr ""
msgid "You don't have any applications"
-msgstr ""
+msgstr "Herhangi bir uygulamanız yok"
msgid "You don't have any authorized applications"
-msgstr ""
+msgstr "Herhangi bir yetkili uygulamanız yok"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
+msgstr "Ä°zinleriniz yok"
+
+msgid "You have not added any approvers. Start by adding users or groups."
msgstr ""
msgid "You have reached your project limit"
+msgstr "Proje sınırınıza ulaştınız"
+
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
msgstr ""
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
@@ -10876,35 +12869,47 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
msgstr ""
msgid "You need permission."
-msgstr ""
+msgstr "İzin almanız gerekli."
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
-msgid "You will not get any notifications via email"
+msgid "You will need to update your local repositories to point to the new location."
msgstr ""
+msgid "You will not get any notifications via email"
+msgstr "E-posta yoluyla herhangi bir bildirim almazsınız "
+
msgid "You will only receive notifications for the events you choose"
-msgstr ""
+msgstr "Yalnızca seçtiğiniz etkinlikler için bildirim alacaksınız"
msgid "You will only receive notifications for threads you have participated in"
msgstr ""
msgid "You will receive notifications for any activity"
-msgstr ""
+msgstr "Herhangi bir etkinlik için bildirim alacaksınız"
msgid "You will receive notifications only for comments in which you were @mentioned"
msgstr ""
@@ -10913,14 +12918,23 @@ msgid "You won't be able to pull or push project code via %{protocol} until you
msgstr ""
msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile"
-msgstr ""
+msgstr "Profilinize %{add_ssh_key_link} kadar proje kodunu SSH üzerinden çekemez veya zorlayamazsınız"
msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
-msgstr ""
+msgstr "Profilinize bir SSH anahtarı ekleyene kadar proje kodunu SSH üzerinden çekemez veya yollayamazsınız"
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10931,35 +12945,44 @@ msgid "You're receiving this email because of your account on %{host}. %{manage_
msgstr ""
msgid "YouTube"
-msgstr ""
+msgstr "YouTube"
msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
msgstr ""
msgid "Your Groups"
-msgstr ""
+msgstr "Gruplarınız"
msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
msgstr ""
msgid "Your Projects (default)"
-msgstr ""
+msgstr "Projeleriniz (öntanımlı)"
msgid "Your Projects' Activity"
-msgstr ""
+msgstr "Projelerinizin EtkinliÄŸi"
msgid "Your Todos"
-msgstr ""
+msgstr "Yapacaklarınız"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
-msgid "Your applications (%{size})"
+msgid "Your U2F device was registered!"
msgstr ""
-msgid "Your authorized applications"
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr ""
+msgid "Your applications (%{size})"
+msgstr "Uygulamalarınız (%{size})"
+
+msgid "Your authorized applications"
+msgstr "Yetkili uygulamalarınız"
+
msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
msgstr ""
@@ -10972,14 +12995,20 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
msgid "Your groups"
-msgstr ""
+msgstr "Gruplarınız"
msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
msgstr ""
@@ -10988,19 +13017,34 @@ msgid "Your issues will be imported in the background. Once finished, you'll get
msgstr ""
msgid "Your name"
+msgstr "Adınız"
+
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
msgstr ""
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
+msgstr "Projeleriniz"
+
+msgid "Your request for access has been queued for review."
msgstr ""
msgid "a deleted user"
+msgstr "silinmiş bir kullanıcı"
+
+msgid "added %{created_at_timeago}"
msgstr ""
msgid "ago"
-msgstr ""
+msgstr "önce"
msgid "allowed to fail"
msgstr ""
@@ -11008,17 +13052,20 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
-msgid "branch name"
+msgid "authored"
msgstr ""
+msgid "branch name"
+msgstr "dal adı"
+
msgid "by"
-msgstr ""
+msgstr "tarafından"
msgid "ciReport|%{linkStartTag}Learn more about Container Scanning %{linkEndTag}"
msgstr ""
@@ -11038,16 +13085,38 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
msgstr[1] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11079,6 +13148,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11088,13 +13160,25 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
-msgid "ciReport|Class"
+msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|Code quality"
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
msgstr ""
+msgid "ciReport|Class"
+msgstr "Sınıf"
+
+msgid "ciReport|Code quality"
+msgstr "Kod kalitesi"
+
msgid "ciReport|Confidence"
+msgstr "Güven"
+
+msgid "ciReport|Container Scanning"
msgstr ""
msgid "ciReport|Container scanning"
@@ -11103,6 +13187,15 @@ msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11116,13 +13209,13 @@ msgid "ciReport|Dependency scanning"
msgstr ""
msgid "ciReport|Description"
-msgstr ""
+msgstr "Açıklama"
msgid "ciReport|Dismiss vulnerability"
-msgstr ""
+msgstr "Güvenlik açığını reddet"
msgid "ciReport|Dismissed by"
-msgstr ""
+msgstr "Reddeden:"
msgid "ciReport|Download and apply the patch to fix this vulnerability."
msgstr ""
@@ -11134,18 +13227,27 @@ msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulner
msgstr ""
msgid "ciReport|Failed to load %{reportName} report"
-msgstr ""
+msgstr "%{reportName} raporu yüklenemedi"
msgid "ciReport|File"
-msgstr ""
+msgstr "Dosya"
msgid "ciReport|Fixed:"
msgstr ""
msgid "ciReport|Identifiers"
+msgstr "Tanımlayıcılar"
+
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
msgstr ""
msgid "ciReport|Instances"
+msgstr "Örnekler"
+
+msgid "ciReport|Investigate this vulnerability by creating an issue"
msgstr ""
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
@@ -11168,46 +13270,43 @@ msgid "ciReport|License management detected no new licenses"
msgstr ""
msgid "ciReport|Links"
-msgstr ""
+msgstr "Bağlantılar"
msgid "ciReport|Loading %{reportName} report"
-msgstr ""
+msgstr "%{reportName} raporu yükleniyor"
msgid "ciReport|Manage licenses"
msgstr ""
msgid "ciReport|Method"
-msgstr ""
+msgstr "Yöntem"
msgid "ciReport|Namespace"
-msgstr ""
+msgstr "İsim alanları"
msgid "ciReport|No changes to code quality"
-msgstr ""
+msgstr "Kod kalitesinde deÄŸiÅŸiklik yok"
msgid "ciReport|No changes to performance metrics"
-msgstr ""
+msgstr "Performans ölçütlerinde değişiklik yok"
msgid "ciReport|Performance metrics"
-msgstr ""
-
-msgid "ciReport|Revert dismissal"
-msgstr ""
+msgstr "Performans ölçütleri"
msgid "ciReport|SAST"
msgstr ""
msgid "ciReport|Security scanning"
-msgstr ""
+msgstr "Güvenlik taraması"
msgid "ciReport|Security scanning failed loading any results"
msgstr ""
msgid "ciReport|Severity"
-msgstr ""
+msgstr "Önem derecesi"
msgid "ciReport|Solution"
-msgstr ""
+msgstr "Çözüm"
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr ""
@@ -11215,6 +13314,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11233,24 +13335,54 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
msgstr[1] ""
msgid "ciReport|View full report"
-msgstr ""
+msgstr "Raporun tamamını görüntüle"
msgid "ciReport|on pipeline"
+msgstr "iş hattında"
+
+msgid "commented on %{link_to_project}"
+msgstr "Yorumladı: %{link_to_project}"
+
+msgid "confidence|Confirmed"
msgstr ""
-msgid "command line instructions"
+msgid "confidence|Experimental"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11260,18 +13392,18 @@ msgid "confidentiality|You are going to turn on the confidentiality. This means
msgstr ""
msgid "connecting"
-msgstr ""
+msgstr "bağlanılıyor"
msgid "could not read private key, is the passphrase correct?"
-msgstr ""
+msgstr "özel anahtar okunamadı, şifre doğru mu?"
msgid "customize"
-msgstr ""
+msgstr "özelleştir"
msgid "day"
msgid_plural "days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "gün"
+msgstr[1] "gün"
msgid "deleted"
msgstr ""
@@ -11279,16 +13411,19 @@ msgstr ""
msgid "deploy token"
msgstr ""
-msgid "disabled"
+msgid "detached"
msgstr ""
+msgid "disabled"
+msgstr "devre dışı"
+
msgid "discussion resolved"
msgid_plural "discussions resolved"
msgstr[0] ""
msgstr[1] ""
msgid "done"
-msgstr ""
+msgstr "tamamlandı"
msgid "draft"
msgid_plural "drafts"
@@ -11296,49 +13431,58 @@ msgstr[0] ""
msgstr[1] ""
msgid "enabled"
-msgstr ""
+msgstr "etkin"
msgid "epic"
msgstr ""
msgid "error"
-msgstr ""
+msgstr "hata"
msgid "error code:"
-msgstr ""
+msgstr "hata kodu:"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr "bu proje için"
+
+msgid "from"
msgstr ""
+msgid "group"
+msgstr "grup"
+
+msgid "here"
+msgstr "burada"
+
msgid "https://your-bitbucket-server"
-msgstr ""
+msgstr "https://sizin-bitbucket-sunucunuz"
msgid "image diff"
msgstr ""
msgid "import flow"
-msgstr ""
+msgstr "içe aktarım akışı"
msgid "importing"
-msgstr ""
+msgstr "içe aktarılıyor"
msgid "in group %{link_to_group}"
msgstr ""
@@ -11370,7 +13514,7 @@ msgid "issue"
msgstr ""
msgid "issue boards"
-msgstr ""
+msgstr "sorun panoları"
msgid "it is stored externally"
msgstr ""
@@ -11385,13 +13529,16 @@ msgid "latest"
msgstr ""
msgid "latest deployment"
-msgstr ""
+msgstr "son dağıtım"
msgid "latest version"
+msgstr "son sürüm"
+
+msgid "leave %{group_name}"
msgstr ""
msgid "license management"
-msgstr ""
+msgstr "lisans yönetimi"
msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr ""
@@ -11401,8 +13548,8 @@ msgstr ""
msgid "merge request"
msgid_plural "merge requests"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "birleÅŸtirme isteÄŸi"
+msgstr[1] "birleÅŸtirme isteÄŸi"
msgid "missing"
msgstr ""
@@ -11410,6 +13557,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11429,7 +13579,7 @@ msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasis
msgstr ""
msgid "mrWidget|Add approval"
-msgstr ""
+msgstr "Onay ekle"
msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr ""
@@ -11441,22 +13591,25 @@ msgid "mrWidget|An error occurred while retrieving approval data for this merge
msgstr ""
msgid "mrWidget|An error occurred while submitting your approval."
-msgstr ""
+msgstr "Onayınızı gönderirken bir hata oluştu."
msgid "mrWidget|Approve"
+msgstr "Onayla"
+
+msgid "mrWidget|Approve additionally"
msgstr ""
msgid "mrWidget|Approved by"
-msgstr ""
+msgstr "Onaylandı"
msgid "mrWidget|Cancel automatic merge"
-msgstr ""
+msgstr "mrWidget|Otomatik birleÅŸtirmeyi iptal et"
msgid "mrWidget|Check out branch"
-msgstr ""
+msgstr "Dalı kontrol et"
msgid "mrWidget|Checking ability to merge automatically"
-msgstr ""
+msgstr "Otomatik olarak birleştirme özelliği kontrol ediliyor"
msgid "mrWidget|Cherry-pick"
msgstr ""
@@ -11465,10 +13618,10 @@ msgid "mrWidget|Cherry-pick this merge request in a new merge request"
msgstr ""
msgid "mrWidget|Closed"
-msgstr ""
+msgstr "Kapalı"
msgid "mrWidget|Closed by"
-msgstr ""
+msgstr "Tarafından kapatıldı"
msgid "mrWidget|Closes"
msgstr ""
@@ -11483,10 +13636,10 @@ msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
msgid "mrWidget|Did not close"
-msgstr ""
+msgstr "Kapatılmadı"
msgid "mrWidget|Email patches"
-msgstr ""
+msgstr "E-posta yamaları"
msgid "mrWidget|Failed to load deployment statistics"
msgstr ""
@@ -11504,34 +13657,37 @@ msgid "mrWidget|Loading deployment statistics"
msgstr ""
msgid "mrWidget|Mentions"
-msgstr ""
+msgstr "Bahsetmeler"
msgid "mrWidget|Merge"
-msgstr ""
+msgstr "BirleÅŸtir"
msgid "mrWidget|Merge failed."
-msgstr ""
+msgstr "Birleştirme başarısız."
msgid "mrWidget|Merge locally"
-msgstr ""
+msgstr "Yerel olarak birleÅŸtir"
msgid "mrWidget|Merge request approved"
+msgstr "Birleştirme isteği onaylandı"
+
+msgid "mrWidget|Merge request approved."
msgstr ""
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
-msgstr ""
+msgstr "BirleÅŸtiren"
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
-msgstr ""
+msgstr "Web IDE'de aç"
msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed"
msgstr ""
@@ -11543,19 +13699,19 @@ msgid "mrWidget|Ready to be merged automatically. Ask someone with write access
msgstr ""
msgid "mrWidget|Refresh"
-msgstr ""
+msgstr "Yenile"
msgid "mrWidget|Refresh now"
-msgstr ""
+msgstr "Åžimdi yenile"
msgid "mrWidget|Refreshing now"
-msgstr ""
+msgstr "Åžimdi yenileniyor"
msgid "mrWidget|Remove your approval"
-msgstr ""
+msgstr "Onayınızı kaldırın"
msgid "mrWidget|Request to merge"
-msgstr ""
+msgstr "BirleÅŸtirme isteÄŸi"
msgid "mrWidget|Requires 1 more approval"
msgid_plural "mrWidget|Requires %d more approvals"
@@ -11574,11 +13730,14 @@ msgid "mrWidget|Resolve these conflicts or ask someone with write access to this
msgstr ""
msgid "mrWidget|Revert"
-msgstr ""
+msgstr "Geri al"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11628,7 +13787,7 @@ msgid "mrWidget|This merge request is in the process of being merged"
msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled"
-msgstr ""
+msgstr "Bu proje arşivlendi, yazma erişimi devre dışı bırakıldı"
msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
msgstr ""
@@ -11640,10 +13799,10 @@ msgid "mrWidget|You can merge this merge request manually using the"
msgstr ""
msgid "mrWidget|branch does not exist."
-msgstr ""
+msgstr "dal mevcut deÄŸil."
msgid "mrWidget|command line"
-msgstr ""
+msgstr "komut satırı"
msgid "mrWidget|into"
msgstr ""
@@ -11655,13 +13814,13 @@ msgid "n/a"
msgstr ""
msgid "new merge request"
-msgstr ""
+msgstr "yeni birleÅŸtirme isteÄŸi"
msgid "none"
-msgstr ""
+msgstr "yok"
msgid "notification emails"
-msgstr ""
+msgstr "bildirim e-postaları"
msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr ""
@@ -11673,7 +13832,7 @@ msgid "nounSeries|%{item}, and %{lastItem}"
msgstr ""
msgid "or"
-msgstr ""
+msgstr "veya"
msgid "out of %d total test"
msgid_plural "out of %d total tests"
@@ -11682,25 +13841,33 @@ msgstr[1] ""
msgid "parent"
msgid_plural "parents"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "ebeveyn"
+msgstr[1] "ebeveyn"
msgid "password"
-msgstr ""
+msgstr "ÅŸifre"
msgid "personal access token"
-msgstr ""
+msgstr "kişisel erişim anahtarı"
+
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
msgid "private"
-msgstr ""
+msgstr "özel"
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "proje"
+msgstr[1] "proje"
msgid "quick actions"
msgstr ""
@@ -11712,7 +13879,7 @@ msgid "remaining"
msgstr ""
msgid "remove"
-msgstr ""
+msgstr "kaldır"
msgid "remove due date"
msgstr ""
@@ -11725,12 +13892,39 @@ msgstr ""
msgid "reply"
msgid_plural "replies"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "cevap"
+msgstr[1] "cevap"
+
+msgid "reset it."
+msgstr ""
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11741,7 +13935,7 @@ msgid "sign in"
msgstr ""
msgid "source"
-msgstr ""
+msgstr "kaynak"
msgid "source diff"
msgstr ""
@@ -11750,11 +13944,14 @@ msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr ""
msgid "started"
-msgstr ""
+msgstr "başladı"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
@@ -11762,19 +13959,19 @@ msgid "syntax is incorrect"
msgstr ""
msgid "this document"
-msgstr ""
+msgstr "bu belge"
msgid "to help your contributors communicate effectively!"
-msgstr ""
+msgstr "katkıda bulunanlara yardım etmek için etkili şekilde iletişim kurun!"
msgid "triggered"
msgstr ""
msgid "updated"
-msgstr ""
+msgstr "güncellendi"
msgid "username"
-msgstr ""
+msgstr "kullanıcı adı"
msgid "uses Kubernetes clusters to deploy your code!"
msgstr ""
@@ -11783,7 +13980,7 @@ msgid "verify ownership"
msgstr ""
msgid "view it on GitLab"
-msgstr ""
+msgstr "GitLab'da görüntüle"
msgid "view the blob"
msgstr ""
@@ -11793,8 +13990,8 @@ msgstr ""
msgid "within %d minute "
msgid_plural "within %d minutes "
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d dakika içinde "
+msgstr[1] "%d dakika içinde "
msgid "yaml invalid"
msgstr ""
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 67d9a556621..c99fee40c16 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: uk\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:11\n"
+"PO-Revision-Date: 2019-04-13 17:07\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " СтатуÑ"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " Ñ–"
@@ -36,16 +45,20 @@ msgstr[2] " покращилоÑÑ Ð½Ð° %d одиниць"
msgstr[3] " покращилоÑÑ Ð½Ð° %d одиниць"
msgid " or "
-msgstr ""
+msgstr " або "
msgid " or <#epic id>"
-msgstr ""
+msgstr " або <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " або <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "\"%{query}\" в проектах"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d коментар"
+msgstr[1] "%d коментарі"
+msgstr[2] "%d коментарів"
+msgstr[3] "%d коментарів"
msgid "%d commit"
msgid_plural "%d commits"
@@ -62,7 +75,7 @@ msgstr[2] "%d комітів позаду"
msgstr[3] "%d комітів позаду"
msgid "%d commits"
-msgstr ""
+msgstr "%d комітів"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -94,10 +107,10 @@ msgstr[3] "%d задач"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d вибрана задача"
+msgstr[1] "%d вибрані задачі"
+msgstr[2] "%d вибраних задач"
+msgstr[3] "%d вибраних задач"
msgid "%d layer"
msgid_plural "%d layers"
@@ -156,12 +169,28 @@ msgstr "%{counter_storage} (%{counter_repositories} репозиторій, %{co
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "Ðеобхідно %{count} Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %{name}"
+msgstr[1] "Ðеобхідно %{count} Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %{name}"
+msgstr[2] "Ðеобхідно %{count} затверджень від %{name}"
+msgstr[3] "Ðеобхідно %{count} затверджень від %{name}"
+
+msgid "%{count} approvals from %{name}"
+msgstr "%{count} Ñхвалень від %{name}"
+
msgid "%{count} more"
-msgstr ""
+msgstr "%{count} більше"
msgid "%{count} more assignees"
msgstr "%{count} більше виконавців"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{count} з %{required} Ñхвалень від %{name}"
+
+msgid "%{count} of %{total}"
+msgstr "%{count} з %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} учаÑтник"
@@ -182,8 +211,8 @@ msgstr "%{filePath} видалено"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} більше"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "%{gitlab_ci_yml} не знайдено в цьому коміті"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}Групи%{group_docs_link_end} дозволÑÑŽÑ‚ÑŒ вам керувати Ñ– взаємодіÑти між кількома проектами. Члени групи мають доÑтуп до уÑÑ–Ñ… Ñ—Ñ— проектів."
@@ -191,15 +220,24 @@ msgstr "%{group_docs_link_start}Групи%{group_docs_link_end} дозволÑÑ
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} буде видалено! Ви впевнені?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message} недоÑтупний"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}Читати більше%{link_end} про дозволи ролей"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} Початок"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} заблоковано кориÑтувачем GitLab %{lock_user_id}"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "Ðватар %{name}"
@@ -212,32 +250,41 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "%{percent}%% завершено"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
+msgid "%{state} epics"
+msgstr "%{state} епіки"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} Гілка"
+msgstr[1] "%{strong_start}%{branch_count}%{strong_end} Гілки"
+msgstr[2] "%{strong_start}%{branch_count}%{strong_end} Гілок"
+msgstr[3] "%{strong_start}%{branch_count}%{strong_end} Гілок"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} Коміт"
+msgstr[1] "%{strong_start}%{commit_count}%{strong_end} Коміти"
+msgstr[2] "%{strong_start}%{commit_count}%{strong_end} Комітів"
+msgstr[3] "%{strong_start}%{commit_count}%{strong_end} Комітів"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} Файлів"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} Тег"
+msgstr[1] "%{strong_start}%{tag_count}%{strong_end} Теги"
+msgstr[2] "%{strong_start}%{tag_count}%{strong_end} Тегів"
+msgstr[3] "%{strong_start}%{tag_count}%{strong_end} Тегів"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -259,10 +306,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "%{usage_ping_link_start}ДовідатиÑÑŒ більше%{usage_ping_link_end} про те, Ñкою інформацією Ви ділитеÑÑŒ із GitLab Inc."
msgid "%{user_name} profile page"
+msgstr "%{user_name} Ñторінка профілю"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(зовнішнє джерело)"
msgid "+ %{count} more"
msgstr "+ ще %{count}"
@@ -270,8 +320,11 @@ msgstr "+ ще %{count}"
msgid "+ %{moreCount} more"
msgstr "+ ще %{moreCount}"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} більше"
+
msgid ", or "
-msgstr ""
+msgstr ", або "
msgid "- Runner is active and can process any new jobs"
msgstr "- Runner активний Ñ– може виконувати нові завданнÑ"
@@ -296,6 +349,13 @@ msgstr[1] "%{count} %{type} зміни"
msgstr[2] "%{count} %{type} змін"
msgstr[3] "%{count} %{type} змін"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "1 день"
+msgstr[1] "%d дні"
+msgstr[2] "%d днів"
+msgstr[3] "%d днів"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 закрита задача"
@@ -310,6 +370,9 @@ msgstr[1] "%d закритих запити на злиттÑ"
msgstr[2] "%d закритих запитів на злиттÑ"
msgstr[3] "%d закритих запитів на злиттÑ"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "1 група"
@@ -359,18 +422,30 @@ msgstr[1] "%d кориÑтувачі"
msgstr[2] "%d кориÑтувачів"
msgstr[3] "%d кориÑтувачів"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "Перший внеÑок!"
msgid "2FA"
-msgstr ""
+msgstr "двофакторна автентифікаціÑ"
msgid "2FA enabled"
-msgstr "Двоетапна Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð°"
+msgstr "Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð°"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
msgstr ""
+msgid "30 minutes"
+msgstr ""
+
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора GitLab, щоб отримати дозвіл."
+
msgid "403|You don't have the permission to access this page."
msgstr "У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до цієї Ñторінки."
@@ -383,6 +458,9 @@ msgstr "Сторінка не знайдена"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора GitLab, Ñкщо ви вважаєте, що це помилка."
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> додаÑÑ‚ÑŒ \"<a href=\"#\">@johnsmith</a>\" до вÑÑ–Ñ… задач та коментарів, що були Ñтворені johnsmith@example.com, а також призначить на <a href=\"#\">@johnsmith</a> уÑÑ– задачі, Ñкі були призначені на на johnsmith@example.com."
@@ -411,13 +489,25 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong> відправок (push), більше ніж <strong>%{commits}</strong> зафікÑовано<strong>%{people}</strong> учаÑниками."
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>ВидалÑÑ”</strong> гілку-джерело"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "'Runner' — це процеÑ, Ñкий виконує завданнÑ. Ви можете Ñтворити потрібну кількіÑÑ‚ÑŒ Runner'ів."
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Ðабір графіків відноÑно безперервної інтеграції"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr "Шаблон конÑольного заÑтоÑунку .NET Core, Ñкий налаштовуєтьÑÑ Ð´Ð»Ñ Ð±ÑƒÐ´ÑŒ-Ñкого проекту .NET Core"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Сайт GitBook, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Сайт Hexo, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Сайт Hugo, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Сайт Jekyll, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
msgid "A default branch cannot be chosen for an empty project."
msgstr "Гілку за замовчуваннÑм не може бути обрано Ð´Ð»Ñ Ð¿Ð¾Ñ€Ð¾Ð¶Ð½ÑŒÐ¾Ð³Ð¾ проекту."
@@ -428,18 +518,39 @@ msgstr "Видалений кориÑтувач"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "УчаÑник команди GitLab по боротьбі з порушеннÑми розглÑне ваш звіт Ñкомога швидше."
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "У вашому форку буде Ñтворено нову гілку, а також буде ініційований новий запит на злиттÑ."
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "Сайт на HTML, Ñкий викориÑтовує Netlify Ð´Ð»Ñ CI/CD заміÑÑ‚ÑŒ GitLab, але вÑе ще з уÑіма іншими чудовими функціÑми GitLab."
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "Проект — це міÑце де ви можете розміщувати Ñвої файли (репозиторій), планувати роботу (задачі) Ñ– публікувати документацію (вікі), %{among_other_things_link}."
+msgid "A ready-to-go template for use with Android apps."
+msgstr "Готовий шаблон Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð· Android заÑтоÑунками."
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "Готовий шаблон Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð· iOS Swift заÑтоÑунками."
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "РегулÑрний вираз, Ñкий буде викориÑтовуватиÑÑ Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ результатів Ð¿Ð¾ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ‚ÐµÑтами в завданні. Залиште пуÑтим Ð´Ð»Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ"
msgid "A user with write access to the source branch selected this option"
msgstr "КориÑтувач із правом запиÑу в гілку-джерело вибрав цей варіант"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "Про GitLab"
@@ -459,7 +570,7 @@ msgid "Abuse reports"
msgstr "Звіти про зловживаннÑ"
msgid "Accept invitation"
-msgstr ""
+msgstr "ПрийнÑти запрошеннÑ"
msgid "Accept terms"
msgstr "ПрийнÑти умови"
@@ -470,12 +581,18 @@ msgstr "ЗаÑтоÑований запит на злиттÑ"
msgid "Access Tokens"
msgstr "Токени доÑтупу"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "ДоÑтуп заборонено! Будь-лаÑка, перевірте, чи ви можете додавати ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾ цього Ñховища."
msgid "Access expiration date"
msgstr "Дата Ð¿Ñ€Ð¸Ð¿Ð¸Ð½ÐµÐ½Ð½Ñ Ð´Ð¾Ñтупу"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "ДоÑтуп до \"%{classification_label}\" заборонено"
@@ -485,6 +602,12 @@ msgstr "Обліковий запиÑ"
msgid "Account and limit"
msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ‚Ð° ліміт"
+msgid "Action to take when receiving an alert."
+msgstr "Дії Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸ отриманні попередженнÑ."
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "Ðктивний"
@@ -498,86 +621,122 @@ msgid "Add"
msgstr "Додати"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Додати ÑпиÑок змін (CHANGELOG)"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "Додати CONTRIBUTING"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "Додайте групові веб-гуки та GitLab Enterprise Edition."
msgid "Add Jaeger URL"
-msgstr "Додати URL-Ð°Ð´Ñ€ÐµÑ Jaeger"
+msgstr "Додати URL-адреÑу Jaeger"
msgid "Add Kubernetes cluster"
msgstr "Додати Kubernetes-клаÑтер"
msgid "Add README"
-msgstr ""
+msgstr "Додати інÑтрукцію (README)"
+
+msgid "Add a bullet list"
+msgstr "Додати ненумерований ÑпиÑок"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Додайте загальний коментар до цього %{noteable_name}."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "Додати домашню Ñторінку в вікі, Ñка міÑтить інформацію про ваш проект, Ñ– GitLab відображатиме його тут заміÑÑ‚ÑŒ цього повідомленнÑ."
+msgid "Add a link"
+msgstr "Додати поÑиланнÑ"
+
+msgid "Add a numbered list"
+msgstr "Додати нумерований ÑпиÑок"
+
msgid "Add a table"
msgstr "Додати таблицю"
+msgid "Add a task list"
+msgstr "Додати ÑпиÑок завдань"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "Створіть додатковий текÑÑ‚, Ñкий буде приÑутній у вÑÑ–Ñ… повідомленнÑÑ… електронної пошти. МакÑимальна кількіÑÑ‚ÑŒ Ñимволів — %{character_limit}"
+msgid "Add approver(s)"
+msgstr "Додати затверджуючих оÑіб"
+
+msgid "Add approvers"
+msgstr "Додати затверджуючих оÑіб"
+
+msgid "Add bold text"
+msgstr "Додати жирний текÑÑ‚"
+
msgid "Add comment now"
msgstr "Додати коментар"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "Додати заголовок Ñ– футер в електронні лиÑти. Будь лаÑка, зверніть увагу, що Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð»ÑŒÐ¾Ñ€Ñƒ будуть викориÑтовуватиÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ в інтерфейÑÑ– заÑтоÑунку"
+
msgid "Add image comment"
msgstr "Додати коментар до зображеннÑ"
+msgid "Add italic text"
+msgstr "Додати курÑивний текÑÑ‚"
+
msgid "Add license"
msgstr "Додати ліцензію"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "Додати новий додаток"
msgid "Add new directory"
msgstr "Додати новий каталог"
-msgid "Add projects"
-msgstr "Додати проекти"
-
msgid "Add reaction"
msgstr "Додати реакцію"
msgid "Add to project"
-msgstr ""
+msgstr "Додати до проекту"
msgid "Add to review"
msgstr "Додати до перевірки"
msgid "Add todo"
-msgstr "Додати задачу"
+msgstr "Додати нагадуваннÑ"
msgid "Add user(s) to the group:"
msgstr "Додати кориÑтувачів до групу:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Додайте кориÑтувачів або групи, Ñким дозволено затверджувати будь-Ñкий запит на злиттÑ"
msgid "Add users to group"
msgstr "Додати кориÑтувача до групи"
+msgid "Added at"
+msgstr "Додано"
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… проектів Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ інÑтанÑу GitLab заборонено. ЗвернітьÑÑ Ð´Ð¾ Ñвого адмініÑтратора GitLab, щоб отримати дозвіл"
msgid "Additional text"
msgstr "Додатковий текÑÑ‚"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "ОблаÑÑ‚ÑŒ адмініÑтратора"
msgid "Admin Overview"
msgstr "ОглÑд адмініÑтратора"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "Ви збираєтеÑÑ Ð¾Ñтаточно видалити кориÑтувача %{username}. Пов’Ñзані з ним задачі, запити на злиттÑ, Ñ– групи будуть переведені на ÑиÑтемного кориÑтувача \"Ghost-user\". Щоб уникнути втрати даних, розглÑньте можливіÑÑ‚ÑŒ %{strong_start}Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача%{strong_end} заміÑÑ‚ÑŒ видаленнÑ. ПіÑÐ»Ñ %{strong_start}Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача%{strong_end}, його неможливо буде відновити."
@@ -612,40 +771,43 @@ msgid "AdminProjects|Delete project"
msgstr "Видалити проект"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Домен Auto DevOps"
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr "Увімкнути загальні runner'и Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… проектів"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Змінні Ñередовища Ñ” захищеними за замовчуваннÑм"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "Вкажіть домен, Ñкий буде викориÑтовуватиÑÑ Ð² проекті за замовчуваннÑм Ð´Ð»Ñ Ñтадій Auto Review Apps Ñ– Auto Deploy."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "При Ñтворенні нової змінної Ñередовища вона буде захищена за замовчуваннÑм."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA вимкнено"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA увімкнено"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Ðктивні"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "ÐдмініÑтратор"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "ÐдмініÑтратори"
msgid "AdminUsers|Block user"
msgstr "Заблоквати кориÑтувача"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Заблоковано"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "Ðеможливо розблокувати кориÑтувачів заблокованих в LDAP"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "Видалити кориÑтувача %{username} та його внеÑки?"
@@ -660,28 +822,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "Видалити кориÑтувача Ñ– його внеÑки"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Зовнішні"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "Це ви!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Ðовий кориÑтувач"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "КориÑтувачів не знайдено"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "Шукати за іменем, електронною поштою або іменем кориÑтувача"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "Пошук кориÑтувачів"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Відправити Ð¿Ð¾Ð²Ñ–Ð´Ð¼Ð¾Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти кориÑтувачам"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Сортувати за"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "Ð”Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð²ÐµÐ´Ñ–Ñ‚ÑŒ %{projectName}"
@@ -690,17 +852,26 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "Ð”Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð²ÐµÐ´Ñ–Ñ‚ÑŒ %{username}"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "КориÑтувач буде заблокований"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "Без проектів"
+
+msgid "Advanced"
+msgstr "Розширений"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "Додаткові дозволи, Ñховище великих файлів (LFS) Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації."
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "Додаткові параметри"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "ПіÑÐ»Ñ ÑƒÑпішного Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð²Ð¸ перейдете на екран входу."
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "ПопередженнÑ"
@@ -709,11 +880,14 @@ msgstr[2] "Попереджень"
msgstr[3] "Попереджень"
msgid "Alerts"
-msgstr ""
+msgstr "ПопередженнÑ"
msgid "All"
msgstr "Ð’ÑÑ–"
+msgid "All Members"
+msgstr "Ð’ÑÑ– учаÑники"
+
msgid "All changes are committed"
msgstr "Ð’ÑÑ– зміни закомічені"
@@ -721,17 +895,35 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "Ð’ÑÑ– функції Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… проектів берутьÑÑ Ñ–Ð· шаблонів або під Ñ‡Ð°Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ, але ви можете вимикати Ñ—Ñ… пізніше в налаштуваннÑÑ… проекту."
msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr "Ð’ÑÑ– задачі Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ етапу закриті. Ви можете закрити цей етап."
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
msgstr ""
msgid "All users"
msgstr "Ð’ÑÑ– кориÑтувачі"
-msgid "Allow \"%{group_name}\" to sign you in"
+msgid "All users must have a name."
msgstr ""
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr "Дозволити вхід через \"%{group_name}\""
+
msgid "Allow commits from members who can merge to the target branch."
msgstr "Дозволити коміти від учаÑників, Ñкі можуть зливати в цільову гілку."
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr "Дозволити влаÑникам груп керувати налаштуваннÑми LDAP"
+
+msgid "Allow mirrors to be set up for projects"
+msgstr "Дозволити вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð» Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñ–Ð²"
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr "Дозволити викориÑÑ‚Ð°Ð½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ вибраних протоколів Ð´Ð»Ñ Ð´Ð¾Ñтупу до Git."
+
msgid "Allow projects within this group to use Git LFS"
msgstr "Дозволити проектам в цій групі викориÑтовувати Git LFS"
@@ -744,6 +936,12 @@ msgstr "Дозволити Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ñ–Ð°Ð³Ñ€Ð°Ð¼ PlantUML в Ð
msgid "Allow requests to the local network from hooks and services."
msgstr "Дозволити запити до локальної мережі із гуків та ÑервіÑів."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr "Дозволити цьому ключеві також відправлÑти зміни в репозиторій? (За замовчуваннÑм дозволÑєтьÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ отримувати.)"
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr "Дозволити кориÑтувачам реєÑтрувати будь-Ñкі заÑтоÑунки Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ GitLab Ñк провайдера OAuth"
+
msgid "Allow users to request access"
msgstr "Дозволити кориÑтувачам запитувати доÑтуп"
@@ -751,7 +949,7 @@ msgid "Allow users to request access if visibility is public or internal."
msgstr "Дозволити кориÑтувачам запрошувати доÑтуп, Ñкщо видиміÑÑ‚ÑŒ загальнодоÑтупна або внутрішнÑ."
msgid "Allowed to fail"
-msgstr ""
+msgstr "Ðевдача дозволена"
msgid "Allows you to add and manage Kubernetes clusters."
msgstr "ДозволÑÑ” додавати та керувати клаÑтерами Kubernetes."
@@ -762,6 +960,9 @@ msgstr "Також відомий Ñк «Емітент» або «ІдентиÑ
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "Також називаєтьÑÑ \"URL-адреÑа Ñлужби перевірÑючої Ñторони\" або \"URL-адреÑа відповіді\""
+msgid "Alternate support URL for help page"
+msgstr "Ðльтернативна URL-адреÑа Ð´Ð»Ñ Ð´Ð¾Ð²Ñ–Ð´ÐºÐ¸"
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "Крім того, ви можете викориÑтовувати %{personal_access_token_link}. Коли ви Ñтворюватимете Ñвій перÑональний токен доÑтупу, вам потрібно буде вибрати облаÑÑ‚ÑŒ дії <code>repo</code>, щоб ми могли відобразити ÑпиÑок ваших публічних та приватних репозиторіїв, доÑтупних Ð´Ð»Ñ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ."
@@ -783,9 +984,6 @@ msgstr "Порожнє поле Gitlab-кориÑтувача буде запоÐ
msgid "An error has occurred"
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€Ð½ÐµÑ‚ÐºÐ¸ до обговореннÑ."
@@ -793,7 +991,16 @@ msgid "An error occurred adding a new draft."
msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¾Ñ— чернетки."
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "Помилка при Ñтворенні нової гілки."
+
+msgid "An error occurred fetching the approval rules."
+msgstr "Помилка під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð» затвердженнÑ."
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "Помилка при отриманні затверджуючих оÑіб Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ правила."
+
+msgid "An error occurred fetching the dropdown data."
+msgstr "Помилка при отриманні даних Ð´Ð»Ñ Ð²Ð¸Ð¿Ð°Ð´Ð°ÑŽÑ‡Ð¾Ð³Ð¾ ÑпиÑку."
msgid "An error occurred previewing the blob"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð½ÑŒÐ¾Ð³Ð¾ переглÑду об'єкта"
@@ -805,7 +1012,10 @@ msgid "An error occurred when updating the issue weight"
msgstr "Збій під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°Ð³Ð¸ задачі"
msgid "An error occurred while adding approver"
-msgstr "Помилка при додаванні учаÑника Ð´Ð»Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ"
+msgstr "Помилка при додаванні затверджуючої оÑоби"
+
+msgid "An error occurred while deleting the approvers group"
+msgstr "Помилка при видаленні групи затверджуючих оÑіб"
msgid "An error occurred while deleting the comment"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ"
@@ -813,12 +1023,18 @@ msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼ÐµÐ
msgid "An error occurred while detecting host keys"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при виÑвленні ключів хоÑта"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "Помилка при відхиленні попередженнÑ. Оновіть Ñторінку та Ñпробуйте знову."
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при відхиленні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ функцію. Оновіть Ñторінку Ñ– Ñпробуйте знову."
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при попередньому переглÑді markdown"
@@ -831,6 +1047,9 @@ msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð
msgid "An error occurred while fetching stages."
msgstr "Помилка при отриманні Ñтадій."
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "Помилка при отриманні логів завданнÑ."
@@ -843,6 +1062,9 @@ msgstr "Помилка при отриманні завдань."
msgid "An error occurred while fetching the pipeline."
msgstr "Помилка при отриманні данних конвеєра."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr "Помилка при отриманні релізів. Будь лаÑка, Ñпробуйте знову."
+
msgid "An error occurred while getting projects"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при отриманні проектів"
@@ -856,7 +1078,10 @@ msgid "An error occurred while loading chart data"
msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… діаграми"
msgid "An error occurred while loading commit signatures"
-msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при завантаженні Ñигнатур коміту"
+msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при завантаженні підпиÑів комітів"
+
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
msgid "An error occurred while loading diff"
msgstr "Помилка при завантаженні разниці (diff)"
@@ -868,19 +1093,19 @@ msgid "An error occurred while loading the file"
msgstr "Помилка при завантаженні файлу"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "Помилка при завантаженні даних підпиÑки."
msgid "An error occurred while making the request."
msgstr "Помилка при Ñтворенні запиту."
msgid "An error occurred while removing approver"
-msgstr "Помилка при видаленні учаÑника Ð´Ð»Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ"
+msgstr "Помилка при видаленні затверджуючої оÑоби"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "Помилка при видаленні епіків."
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "Помилка при видаленні задач."
msgid "An error occurred while rendering KaTeX"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при рендерингу KaTeX"
@@ -900,12 +1125,18 @@ msgstr "Помилка при збереженні ÑтатуÑу перевиз
msgid "An error occurred while saving assignees"
msgstr "Помилка при збереженні виконавців"
+msgid "An error occurred while saving the approval settings"
+msgstr "Помилка під Ñ‡Ð°Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½ÑŒ затверджень"
+
msgid "An error occurred while subscribing to notifications."
msgstr "Помилка при підпиÑці на ÑповіщеннÑ."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Помилка при відпиÑці від Ñповіщень."
+msgid "An error occurred while updating approvers"
+msgstr "Помилка при оновленні затверджуючих оÑіб"
+
msgid "An error occurred while updating the comment"
msgstr "Під Ñ‡Ð°Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°"
@@ -913,56 +1144,59 @@ msgid "An error occurred while validating username"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ імені кориÑтувача"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "Помилка при коміті ваших змін."
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "Помилка при отриманні логу завданнÑ."
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "Помилка при отриманні оÑтаннього конвеєра."
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "Помилка при завантаженні вÑÑ–Ñ… файлів."
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "Помилка при завантаженні вміÑту файлу."
msgid "An error occurred whilst loading the file."
-msgstr ""
+msgstr "Помилка при завантаженні файлу."
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "Помилка при завантаженні змін запиту на злиттÑ."
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "Помилка при завантаженні даних верÑÑ–Ñ— запиту на злиттÑ."
msgid "An error occurred whilst loading the merge request."
-msgstr ""
+msgstr "Помилка при завантаженні запиту на злиттÑ."
msgid "An error occurred whilst loading the pipelines jobs."
-msgstr ""
+msgstr "Помилка при завантаженні завдань конвеєру."
msgid "An error occurred. Please try again."
msgstr "СталаÑÑŒ помилка. Спробуйте ще раз."
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "Ðеочікувана помилка при перевірці Ñередовища проекту."
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "Ðеочікувана помилка при перевірці runner'ів проекту."
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "Ðеочікувана помилка з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ–Ð· Веб-терміналом."
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "Ðеочікувана помилка при запуÑку Веб-терміналу."
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "Ðеочікувана помилка при зупинці Веб-терміналу."
msgid "Analytics"
msgstr "Ðналітика"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Ðнонімно"
@@ -975,15 +1209,33 @@ msgstr "Будь-Ñкий"
msgid "Any Label"
msgstr "Будь-Ñка мітка"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "Зовнішній виглÑд"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "Додаток"
msgid "Application ID"
msgstr "Ідентифікатор заÑтоÑунку"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "Додаток: %{name}"
@@ -991,19 +1243,65 @@ msgid "Applications"
msgstr "ЗаÑтоÑунки"
msgid "Applied"
-msgstr ""
+msgstr "ЗаÑтоÑовано"
msgid "Apply suggestion"
-msgstr ""
+msgstr "ЗаÑтоÑувати пропозицію"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d учаÑник"
+msgstr[1] "%d учаÑника"
+msgstr[2] "%d учаÑників"
+msgstr[3] "%d учаÑників"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ цього учаÑника не знÑто."
+msgstr[1] "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ цього учаÑника не знÑто."
+msgstr[2] "Затверджень від цього учаÑника не знÑто."
+msgstr[3] "Затверджень від цього учаÑника не знÑто."
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr "Ви збираєтеÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ групу затверджуючих оÑіб %{name} Ñка налічує %{nMembers} учаÑників."
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d учаÑник"
+msgstr[1] "%d учаÑника"
+msgstr[2] "%d учаÑників"
+msgstr[3] "%d учаÑників"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] "Потрібно %{count} Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %{membersCount}"
+msgstr[1] "Потрібно %{count} Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %{membersCount}"
+msgstr[2] "Потрібно %{count} затверджень від %{membersCount}"
+msgstr[3] "Потрібно %{count} затверджень від %{membersCount}"
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr "Ð’ÑÑ– учаÑники з роллю Розробник або вище та влаÑники коду (Ñкщо Ñ”)"
+
+msgid "ApprovalRule|Members"
+msgstr "УчаÑники"
+
+msgid "ApprovalRule|Name"
+msgstr "Ім'Ñ"
+
+msgid "ApprovalRule|No. approvals required"
+msgstr "Ðеобхідна кількіÑÑ‚ÑŒ затверджень"
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr "наприклад QA, безпека і т. д."
msgid "Approvals"
-msgstr ""
+msgstr "ЗатвердженнÑ"
msgid "Approvals required"
-msgstr ""
+msgstr "Потрібні затвердженнÑ"
msgid "Approvers"
-msgstr ""
+msgstr "Затверджуючі оÑоби"
msgid "Apr"
msgstr "квіт."
@@ -1011,13 +1309,28 @@ msgstr "квіт."
msgid "April"
msgstr "квітень"
+msgid "Archive jobs"
+msgstr "Ðрхівувати завданнÑ"
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "Ðрхівований проект! Репозиторій та інші реÑурÑи проекту доÑтупні лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
msgid "Archived projects"
msgstr "Заархівовані проекти"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
+msgstr "Ви впевнені"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
@@ -1030,6 +1343,9 @@ msgid "Are you sure you want to lose unsaved changes?"
msgstr "Ви впевнені, що бажаєте втратити незбережені зміни?"
msgid "Are you sure you want to lose your issue information?"
+msgstr "Ви впевнені, що хочете втратити інформацію про задачу?"
+
+msgid "Are you sure you want to permanently delete this license?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
@@ -1038,14 +1354,20 @@ msgstr "Ви впевнені, що хочете повторно згенеру
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Ви впевнені, що хочете видалити %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "Ви впевнені, що хочете видалити затверджуючу оÑобу %{name}"
+
msgid "Are you sure you want to remove approver %{name}?"
-msgstr ""
+msgstr "Ви впевнені, що хочете видалити затверджуючу оÑобу %{name}?"
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr "Ви впевнені, що хочете видалити групу %{name}"
msgid "Are you sure you want to remove group %{name}?"
-msgstr ""
+msgstr "Ви впевнені, що хочете видалити групу %{name}?"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "Ви впевнені, що бажаєте видалити вкладеннÑ?"
msgid "Are you sure you want to remove this identity?"
msgstr "Ви впевнені, що хочете видалити цю ідентифікацію?"
@@ -1053,6 +1375,9 @@ msgstr "Ви впевнені, що хочете видалити цю іденÑ
msgid "Are you sure you want to reset registration token?"
msgstr "Ви впевнені, що бажаєте перегенерувати реєÑтраційний токен?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "Ви впевнені, що Ви хочете перегенерувати цей ключ перевірки працездатноÑÑ‚Ñ–?"
@@ -1063,7 +1388,7 @@ msgid "Are you sure you want to unlock %{path_lock_path}?"
msgstr "Ви впевнені, що хочете розблокувати %{path_lock_path}?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "Ви дійÑно бажаєте ÑкаÑувати підпиÑку на %{type}: %{link_to_noteable_text}?"
msgid "Are you sure?"
msgstr "Ви впевнені?"
@@ -1084,7 +1409,7 @@ msgid "Assertion consumer service URL"
msgstr "URL-адреÑа Ñлужби обробника тверджень"
msgid "Assets"
-msgstr ""
+msgstr "РеÑурÑи"
msgid "Assign custom color like #FF0000"
msgstr "Призначити влаÑний колір типу #FF0000"
@@ -1096,7 +1421,7 @@ msgid "Assign milestone"
msgstr "Призначити етап"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "Призначити деÑкі задачі до цього етапу."
msgid "Assign to"
msgstr "Призначити"
@@ -1107,9 +1432,6 @@ msgstr "Призначені задачі"
msgid "Assigned Merge Requests"
msgstr "Призначені запити на злиттÑ"
-msgid "Assigned to :name"
-msgstr "Призначено :name"
-
msgid "Assigned to me"
msgstr "Призначено мені"
@@ -1125,8 +1447,11 @@ msgstr "СпиÑки виконавців показують уÑÑ– задачі
msgid "Assignee(s)"
msgstr "Виконавець(ці)"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr "Потрібне щонайменше одне Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ влаÑника коду Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½Ð¸ файлів, що задовільнÑÑŽÑ‚ÑŒ відповідним правилам CODEOWNER."
+
msgid "Attach a file"
-msgstr ""
+msgstr "Прикріпити файл"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Прикріпити файл за допомогою перетÑÐ³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ %{upload_link}"
@@ -1140,18 +1465,24 @@ msgstr "Ñерп."
msgid "August"
msgstr "Ñерпень"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Журнал автентифікації"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "Журнал автентифікації"
msgid "Authentication method"
msgstr "Метод автентифікації"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "Ðвтор"
@@ -1159,7 +1490,7 @@ msgid "Authorization code:"
msgstr "Код авторизації:"
msgid "Authorization key"
-msgstr ""
+msgstr "Ключ авторизації"
msgid "Authorization was granted by entering your username and password in the application."
msgstr "ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ Ð²Ñ–Ð´Ð±ÑƒÐ»Ð°ÑÑ Ð¿Ñ–ÑÐ»Ñ Ð²Ð²Ð¾Ð´Ñƒ вашого імені та паролю у заÑтоÑунку."
@@ -1170,6 +1501,9 @@ msgstr "ÐвторизаціÑ"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "ÐвторизуватиÑÑ %{link_to_client} викориÑтовуючи ваш аккаунт?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "Ðвторизовано у"
@@ -1209,38 +1543,32 @@ msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Конвеєр Auto DevOps увімкнено Ñ– буде викориÑтовуватиÑÑ, Ñкщо не знайдено жодного альтернативного файлу конфігурації CI. %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "Ви можете автоматично збирати й теÑтувати ваш заÑтоÑунок, Ñкщо %{link_to_auto_devops_settings} Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту. Ви також можете його автоматично розгортати, Ñкщо %{link_to_add_kubernetes_cluster}."
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "додати Kubernetes-клаÑтер"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "Увімкнути Auto DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "Ðвтоматично позначено Ñк внутрішній кориÑтувач за замовчуваннÑм"
-msgid "Automatically resolved"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
+msgid "Automatically resolved"
+msgstr "Вирішено автоматично"
+
msgid "Available"
msgstr "ДоÑтупно"
msgid "Available group Runners: %{runners}"
-msgstr ""
+msgstr "ДоÑтупні групові Runner'и: %{runners}"
msgid "Available shared Runners:"
-msgstr ""
+msgstr "ДоÑтупні загальні Runner'и:"
msgid "Available specific runners"
-msgstr ""
+msgstr "ДоÑтупні Ñпеціальні Runner’и"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "Ðватар Ð´Ð»Ñ %{assigneeName}"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "Ðватар Ð´Ð»Ñ %{name}"
msgid "Avatar will be removed. Are you sure?"
msgstr "Ðватар буде видалено. Ви впевнені?"
@@ -1344,6 +1672,12 @@ msgstr "Відхилити відгук?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "Ви збираєтеÑÑ Ð²Ñ–Ð´Ñ…Ð¸Ð»Ð¸Ñ‚Ð¸ ваш відгук, що призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… ваших коментарів в очікуванні. Видалені коментарі %{strong_start}не можливо%{strong_end} буде відновити."
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "Почати із виділеного коміту"
@@ -1426,10 +1760,10 @@ msgid "Bitbucket import"
msgstr "Імпорт з Bitbucket"
msgid "Block"
-msgstr ""
+msgstr "Заблокувати"
msgid "Blocked"
-msgstr ""
+msgstr "Заблокований"
msgid "Blog"
msgstr "Блог"
@@ -1581,6 +1915,12 @@ msgstr "ÐалаштуваннÑÑ… проекту"
msgid "Branches|protected"
msgstr "захищена"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "ПереглÑнути каталог"
@@ -1594,33 +1934,45 @@ msgid "Browse files"
msgstr "ПереглÑд файлів"
msgid "Built-in"
-msgstr ""
+msgstr "Вбудований"
msgid "Business"
-msgstr ""
+msgstr "БізнеÑ"
msgid "Business metrics (Custom)"
msgstr "Ð‘Ñ–Ð·Ð½ÐµÑ Ð¼ÐµÑ‚Ñ€Ð¸ÐºÐ¸ (ВлаÑні)"
-msgid "By %{user_name}"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
msgstr ""
+msgid "By %{user_name}"
+msgstr "Від %{user_name}"
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr "За замовчуваннÑм GitLab відправлÑÑ” електронні лиÑти в форматі HTML та у звичайному текÑтовому форматі, щоб поштові клієнти могли вибирати Ñкий з форматів викориÑтовувати. Вимкніть це налаштуваннÑ, Ñкщо ви хочете відправлÑти лиÑти лише у звичайному текÑтовому форматі."
+
msgid "ByAuthor|by"
msgstr "від"
msgid "CHANGELOG"
-msgstr ""
+msgstr "СпиÑок змін (CHANGELOG)"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "СтатиÑтика CI / CD"
msgid "CI / CD Settings"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD"
msgid "CI Lint"
+msgstr "Перевірка CI конфігурації"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
@@ -1656,6 +2008,9 @@ msgstr "Безперервне Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° production за доÐ
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "За замовчуваннÑм Ð´Ð»Ñ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ð° Auto DevOps"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ñ–Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
@@ -1669,7 +2024,10 @@ msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration
msgstr "Конвеєр Auto DevOps буде запущено, Ñкщо не буде знайдено альтернативного файлу конфігуріції CI."
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
-msgstr ""
+msgstr "Ви повинні додати %{kubernetes_cluster_start}клаÑтерну інтеграцію Kubernetes%{kubernetes_cluster_end} до цього проекту з доменом Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ñ— роботи Ñтратегії розгортаннÑ."
+
+msgid "CICD|group enabled"
+msgstr "група увімкнена"
msgid "CICD|instance enabled"
msgstr "ІнÑÑ‚Ð°Ð½Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð¾"
@@ -1681,14 +2039,17 @@ msgid "Callback URL"
msgstr "URL зворотнього виклику"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "Можна змінювати необхідні Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° затверджуючих оÑіб Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на злиттÑ"
msgid "Can't find HEAD commit for this branch"
msgstr "Ðе можу знайти HEAD-коміт Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки"
-msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgid "Can't remove group members without group managed account"
msgstr ""
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr "Canary Deployments — це популÑрна ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ñ–Ñ Ð±ÐµÐ·Ð¿ÐµÑ€ÐµÑ€Ð²Ð½Ð¾Ñ— інтеграції, коли нова верÑÑ–Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ заÑтоÑунку вÑтановлюєтьÑÑ Ð½Ð° невелику кількіÑÑ‚ÑŒ машин."
+
msgid "Cancel"
msgstr "СкаÑувати"
@@ -1698,24 +2059,39 @@ msgstr "СкаÑувати це завданнÑ"
msgid "Cannot be merged automatically"
msgstr "Ðеможливо злити автоматично"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Ðеможливо змінити керований клаÑтер Kubernetes"
-msgid "Certificate"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "Відбиток Ñертифіката"
+msgid "Certificate"
+msgstr "Сертифікат"
+
+msgid "Certificate (PEM)"
+msgstr "Сертифікат (PEM)"
msgid "Change Weight"
msgstr "Змінити вагу"
-msgid "Change permissions"
+msgid "Change path"
msgstr ""
+msgid "Change permissions"
+msgstr "Змінити права доÑтупу"
+
msgid "Change template"
msgstr "Змінити шаблон"
@@ -1737,6 +2113,9 @@ msgstr "Ðнулювати коміт"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Це Ñтворить новий коміт, щоб анулювати Ñ–Ñнуючі зміни."
+msgid "Changes"
+msgstr "Зміни"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "Зміни відображаютьÑÑ Ñ‚Ð°Ðº, ніби <b>редакціÑ-джерело</b> була злита в <b>цільову редакцію</b>."
@@ -1749,17 +2128,23 @@ msgstr "СтатиÑтика"
msgid "Chat"
msgstr "Чат"
+msgid "Check again"
+msgstr "Перевірити знову"
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "ПереглÑньте %{docs_link_start}документацію%{docs_link_end}."
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr "Перевірте Ñвій .gitlab-ci.yml"
msgid "Checking %{text} availability…"
msgstr "Перевірка доÑтупноÑÑ‚Ñ– %{text}…"
msgid "Checking approval status"
-msgstr ""
+msgstr "Перевірка ÑтатуÑу затвердженнÑ"
msgid "Checking branch availability..."
msgstr "Перевірка доÑтупноÑÑ‚Ñ– гілки..."
@@ -1776,17 +2161,14 @@ msgstr "Оберіть <strong>Створити архів</strong> Ñ– чекаÐ
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "Оберіть <strong>Далі</strong> внизу Ñторінки."
-msgid "Choose File ..."
-msgstr "Виберіть файл ..."
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "Виберіть гілку чи тег (напр. %{master}) або введіть коміт (напр. %{sha}) Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду змін або Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на злиттÑ."
msgid "Choose a file"
-msgstr ""
+msgstr "Виберіть файл"
msgid "Choose a role permission"
-msgstr ""
+msgstr "Вибрати роль"
msgid "Choose a template..."
msgstr "Виберіть тему-шаблон..."
@@ -1800,27 +2182,27 @@ msgstr "Вибрати будь-Ñкий колір."
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "Оберіть між <code>clone</code> та<code>fetch</code> щоб отримати найновіший код програми"
-msgid "Choose file..."
-msgstr "Виберіть файл..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "Оберіть групу найвищого Ñ€Ñ–Ð²Ð½Ñ Ð´Ð»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ репозиторіїв."
-msgid "Choose what content you want to see on a group’s overview page"
-msgstr ""
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr "Виберіть рівень видимоÑÑ‚Ñ–, увімкніть/вимкніть функції Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ (задачі, репозиторій, вікі, Ñніпети) та вÑтановіть дозволи."
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "Виберіть групи Ð´Ð»Ñ Ñинхронізації на цей вторинний вузол."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "Вибрати вміÑÑ‚ оглÑдової Ñторінки групи"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Виберіть, Ñкі репозиторії ви хочете підключити Ñ– запуÑтити конвеєри CI/CD."
-msgid "Choose which repositories you want to import."
-msgstr "Виберіть, Ñкі репозиторії ви хочете імпортувати."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "Виберіть Ñегменти Ð´Ð»Ñ Ñинхронізації на цей вторинний вузол."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "ÑкаÑовано"
@@ -1845,6 +2227,9 @@ msgstr "виконано з попередженнÑми"
msgid "CiStatusLabel|pending"
msgstr "в очікуванні"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "пропущено"
@@ -1878,6 +2263,9 @@ msgstr "виконано"
msgid "CiStatusText|pending"
msgstr "в очікуванні"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "пропущено"
@@ -1893,6 +2281,9 @@ msgstr "Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ñ…Ñ–Ð´Ð½Ð¾Ñ— змінної"
msgid "CiVariables|Remove variable row"
msgstr "Видалити Ñ€Ñдок змінних"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (Ð’ÑÑ– Ñередовища)"
@@ -1903,6 +2294,9 @@ msgid "CiVariable|Create wildcard"
msgstr "Створити шаблон"
msgid "CiVariable|Error occurred while saving variables"
+msgstr "Помилка при збереженні змінних"
+
+msgid "CiVariable|Masked"
msgstr ""
msgid "CiVariable|New environment"
@@ -1914,20 +2308,26 @@ msgstr "Захищений"
msgid "CiVariable|Search environments"
msgstr "Пошук Ñередовищ"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "Ввімкнути/вимкнути захиÑÑ‚"
msgid "CiVariable|Validation failed"
msgstr "Перевірка невдала"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "не доÑтупно: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "ОчиÑтити"
msgid "Clear input"
-msgstr ""
+msgstr "ОчиÑтити ввід"
msgid "Clear search"
msgstr "ОчиÑтити пошук"
@@ -1963,22 +2363,25 @@ msgid "Client authentication key"
msgstr "Ключ автентифікації клієнта"
msgid "Client authentication key password"
-msgstr "Пароль ключа аутентифікації клієнта"
+msgstr "Пароль ключа автентифікації клієнта"
msgid "Clients"
msgstr "Клієнти"
msgid "Clone"
-msgstr ""
+msgstr "Клонувати"
msgid "Clone repository"
msgstr "Клонувати репозиторій"
msgid "Clone with %{http_label}"
-msgstr ""
+msgstr "Клонувати з %{http_label}"
+
+msgid "Clone with KRB5"
+msgstr "Клонувати з KRB5"
msgid "Clone with SSH"
-msgstr ""
+msgstr "Клонувати з SSH"
msgid "Close"
msgstr "Закрити"
@@ -1987,22 +2390,19 @@ msgid "Close epic"
msgstr "Закрити епік"
msgid "Close milestone"
-msgstr ""
+msgstr "Закрити етап"
msgid "Closed"
msgstr "Закрито"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Закриті задачі"
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
-msgstr ""
+msgstr "%{custom_domain_start}Детальніше%{custom_domain_end}."
msgid "ClusterIntegration| can be used instead of a custom domain."
-msgstr ""
+msgstr "може викориÑтовуватиÑÑ Ð·Ð°Ð¼Ñ–ÑÑ‚ÑŒ влаÑного домену."
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
msgstr ""
@@ -2010,15 +2410,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} були уÑпішно вÑтановлені на ваш Kubernetes-клаÑтер"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} Це додаÑÑ‚ÑŒ додаткові реÑурÑи, такі Ñк баланÑувальник навантаженнÑ, Ñкий може збільшити витрати в залежноÑÑ‚Ñ– від провайдера хоÑтингу, на Ñкому вÑтановлено клаÑтер Kubernetes. Якщо ви викориÑтовуєте Google Kubernetes Engine, ви можете %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
-msgstr ""
+msgstr "%{title} уÑпішно оновлено."
msgid "ClusterIntegration|API URL"
msgstr "API URL"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "Додати Kubernetes клаÑтер"
@@ -2034,23 +2434,20 @@ msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— до вашої групи наÐ
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Детальні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— із цим Kubernetes-клаÑтером"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "ПіÑÐ»Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ingress, вам необхідно направити Ñвій DNS на згенеровану зовнішню IP-адреÑу, щоб переглÑнути ваш заÑтоÑунок піÑÐ»Ñ Ð¹Ð¾Ð³Ð¾ розгортаннÑ. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Помилка при отриманні зон проекту: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Помилка під Ñ‡Ð°Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Google Cloud API. Будь лаÑка, Ñпробуйте знову пізніше."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr "Помилка при отриманні зон проекту: %{error}"
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
-msgstr ""
+msgstr "Помилка при отриманні ваших проектів: %{error}"
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
-msgstr ""
+msgstr "Помилка при отриманні типів машин зони: %{error}"
msgid "ClusterIntegration|Applications"
msgstr "ЗаÑтоÑунки"
@@ -2062,13 +2459,13 @@ msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluste
msgstr "Ви впевнені, що хочете видалити інтеграцію із цим Kubernetes-клаÑтером? Це не призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñамого клаÑтера."
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "ОÑновний домен"
msgid "ClusterIntegration|CA Certificate"
msgstr "Сертифікат центру Ñертифікації"
msgid "ClusterIntegration|Cert-Manager"
-msgstr ""
+msgstr "Cert-Manager"
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
msgstr ""
@@ -2082,6 +2479,12 @@ msgstr "Виберіть, Ñкі заÑтоÑунки необхідно вÑÑ‚Ð
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Виберіть, Ñке із ваших Ñередовищ буде викориÑтовувати цей клаÑтер."
+msgid "ClusterIntegration|Cluster health"
+msgstr "Стан клаÑтера"
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,20 +2494,20 @@ msgstr "Скопіювати API URL"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Скопіювати Ñертифікат центру Ñертифікації"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Копіювати IP-адреÑу Ingress в буфер обміну"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Скопіювати Ñ–Ð¼â€™Ñ Ñ…Ð¾Ñта Jupyter в буфер обміну"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "Скопіювати Ñ–Ð¼â€™Ñ Kubernetes-клаÑтера"
-msgid "ClusterIntegration|Copy Token"
-msgstr "Скопіювати Токен"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "Створити Kubernetes-клаÑтер"
@@ -2151,8 +2554,8 @@ msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ð· GitLab"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner з'єднаєтьÑÑ Ð· цим проектом Ñ– виконає CI/CD завданнÑ, відправить результати Ñ– розгорне заÑтоÑунки на production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Проект Google Cloud Platform"
@@ -2178,14 +2581,14 @@ msgstr "Приховати"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Якщо ви налаштовуєте декілька клаÑтерів Ñ– викориÑтовуєте Auto DevOps, %{help_link_start}прочитайте Ð´Ð»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ це%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб показати працездатніÑÑ‚ÑŒ клаÑтера, нам потрібно буде забезпечити ваш клаÑтер з Prometheus Ð´Ð»Ñ Ð·Ð±Ð¾Ñ€Ñƒ необхідних даних."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress IP-адреÑа"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress дозволÑÑ” вам маршрутизувати запити до Ñлужб на оÑнові запитаного хоÑта або шлÑху, об'єднуючи Ñ€Ñд ÑервіÑів в одну точку входу."
@@ -2193,15 +2596,18 @@ msgstr "Ingress дозволÑÑ” вам маршрутизувати запитÐ
msgid "ClusterIntegration|Install"
msgstr "Ð’Ñтановити"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Ð’Ñтановити Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "Ð’Ñтановлений"
msgid "ClusterIntegration|Installing"
msgstr "Ð’ÑтановленнÑ"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ ÐºÐ»Ð°Ñтерної автоматизації Kubernetes"
@@ -2209,7 +2615,7 @@ msgid "ClusterIntegration|Integration status"
msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ—"
msgid "ClusterIntegration|Issuer Email"
-msgstr ""
+msgstr "Електронна пошта емітента"
msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
msgstr ""
@@ -2229,7 +2635,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Доменне ім'Ñ Knative:"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2647,6 @@ msgstr "Kubernetes-клаÑтер"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Параметри Kubernetes-клаÑтера"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Стан Kubernetes-клаÑтера"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes-клаÑтер ÑтворюєтьÑÑ Ð½Ð° Google Kubernetes Engine..."
@@ -2272,7 +2675,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про групові клаÑтери Kubernetes"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "Тип машини"
@@ -2286,9 +2689,6 @@ msgstr "УправліннÑ"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Керуйте вашим Kubernetes-клаÑтером за допомогою %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Додаткова інформаціÑ"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Жоден тип машин не відповідає вашому пошуку"
@@ -2301,27 +2701,24 @@ msgstr "Жоден проект не відповідає вашому пошуÐ
msgid "ClusterIntegration|No zones matched your search"
msgstr "Жодна зона не відповідає вашому пошуку"
-msgid "ClusterIntegration|Note:"
-msgstr "Примітка:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "КількіÑÑ‚ÑŒ вузлів"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "Введіть інформацію про доÑтуп до Ñвого Kubernetes-клаÑтера. Якщо вам потрібна допомога, ви можете прочитати наші %{link_to_help_page} про Kubernetes"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Будь-лаÑка впевнітьÑÑ, що ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Google задовольнÑÑ” наÑтупним вимогам:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "Ðаправте ваш DNS на цю згенеровану IP-адреÑу, щоб отримати доÑтуп до вашого додатку, піÑÐ»Ñ Ð¹Ð¾Ð³Ð¾ розгортаннÑ."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "КлаÑтер проекту"
-msgid "ClusterIntegration|Project namespace"
-msgstr "ПроÑÑ‚Ñ–Ñ€ імен проекту"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "ПроÑÑ‚Ñ–Ñ€ імен проекту (не обов’Ñзковий, унікальний)"
@@ -2352,7 +2749,7 @@ msgstr "При бажанні ви можете замінити це на ваÑ
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Запит про початок вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ виконано"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2388,8 +2785,11 @@ msgstr "Вибрати зону"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "Виберіть зону, щоб вибрати тип машин"
-msgid "ClusterIntegration|Service token"
-msgstr "Токен СервіÑа"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "Показати"
@@ -2397,9 +2797,6 @@ msgstr "Показати"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "ЩоÑÑŒ пішло не так з нашого боку."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Помилка при Ñтворенні вашого Kubernetes-клаÑтера на Google Kubernetes Engine"
@@ -2407,10 +2804,10 @@ msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "Під Ñ‡Ð°Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %{title} ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
-msgstr ""
+msgstr "Ð—Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´Ð¾Ð¼ÐµÐ½Ñƒ дозволить вам викориÑтовувати фази Auto Review Apps та Auto Deploy Ð´Ð»Ñ %{auto_devops_start}Auto DevOps%{auto_devops_end}. Домен повинен мати шаблон DNS, що задовільнÑÑ” цей домен."
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "ВідбуваєтьÑÑ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ IP-адреÑи. Будь лаÑка, перевірте квоти вашого Kubernetes клаÑтера на Google Kubernetes Engine, Ñкщо це займає багато чаÑу."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ мати наÑтупні права Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Kubernetes-клаÑтера в %{link_to_container_project}"
@@ -2418,20 +2815,26 @@ msgstr "Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ мати наÑтуÐ
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "Цей параметр дозволить вам вÑтановлювати заÑтоÑунки на клаÑтери RBAC."
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Увімкнути/вимкнути Kubernetes-клаÑтер"
-msgid "ClusterIntegration|Token"
-msgstr "Токен"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr "ОновленнÑ"
msgid "ClusterIntegration|Upgrade"
-msgstr ""
+msgstr "Оновити"
msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
+msgstr "Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸"
msgid "ClusterIntegration|Upgraded"
-msgstr ""
+msgstr "Оновлено"
msgid "ClusterIntegration|Upgrading"
msgstr ""
@@ -2460,9 +2863,6 @@ msgstr "Зона"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "доÑтуп до Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "переглÑнути ціни тут"
-
msgid "ClusterIntegration|documentation"
msgstr "документації"
@@ -2472,6 +2872,9 @@ msgstr "Ñторінка допомоги"
msgid "ClusterIntegration|meets the requirements"
msgstr "задовольнÑÑ” вимогам"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "правильно налаштований"
@@ -2479,31 +2882,43 @@ msgid "ClusterIntegration|sign up"
msgstr "реєÑтрації"
msgid "Code"
-msgstr ""
+msgstr "Код"
+
+msgid "Code Owners"
+msgstr "ВлаÑники коду"
+
+msgid "Code owner approval is required"
+msgstr "Потрібне Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð»Ð°Ñником коду"
msgid "Code owners"
msgstr "ВлаÑники коду"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Когорти"
msgid "Collapse"
msgstr "Згорнути"
+msgid "Collapse approvers"
+msgstr "Згорнути ÑпиÑок затверджуючих оÑіб"
+
msgid "Collapse sidebar"
msgstr "Згорнути панель"
msgid "Command line instructions"
-msgstr ""
+msgstr "ІнÑтрукції Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð³Ð¾ Ñ€Ñдка"
msgid "Comment"
msgstr "Коментар"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "Коментувати та закрити %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "Коментувати та повторно відкрити %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "Залишити коментар Ñ– завершити обговореннÑ"
@@ -2525,19 +2940,19 @@ msgstr[2] "Комітів"
msgstr[3] "Комітів"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "Коміт %{commit_id}"
msgid "Commit Message"
-msgstr "Коміт-повідомелннÑ"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ"
msgid "Commit deleted"
-msgstr ""
+msgstr "Коміт видалено"
msgid "Commit duration in minutes for last 30 commits"
msgstr "ТриваліÑÑ‚ÑŒ оÑтанніх 30 комітів у хвилинах"
msgid "Commit message"
-msgstr "Коміт-повідомленнÑ"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ"
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
msgstr "СтатиÑтика комітів Ð´Ð»Ñ %{ref} %{start_time} - %{end_time}"
@@ -2587,6 +3002,9 @@ msgstr "Коміт від"
msgid "Commit…"
msgstr "Коміт…"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "ПорівнÑти"
@@ -2597,7 +3015,7 @@ msgid "Compare Revisions"
msgstr "ПорівнÑÐ½Ð½Ñ Ñ€ÐµÐ´Ð°ÐºÑ†Ñ–Ð¹"
msgid "Compare changes"
-msgstr ""
+msgstr "ПорівнÑти зміни"
msgid "Compare changes with the last commit"
msgstr "ПорівнÑти зміни з оÑтаннім комітом"
@@ -2627,7 +3045,7 @@ msgid "Confidentiality"
msgstr "КонфіденційніÑÑ‚ÑŒ"
msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "Ðалаштувати runner'ів GitLab Ð´Ð»Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ викориÑÑ‚Ð°Ð½Ð½Ñ Ð’ÐµÐ±-терміналу. %{helpStart}Докладніше.%{helpEnd}"
msgid "Configure Gitaly timeouts."
msgstr "Ðалаштувати таймаути Gitaly."
@@ -2636,7 +3054,7 @@ msgid "Configure Tracing"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð’Ñ–Ð´ÑтеженнÑ"
msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "Ðалаштуйте файл <code>.gitlab-webide.yml</code> у директорії <code>.gitlab</code>, щоб почати викориÑтовувати Веб-термінал. %{helpStart}Докладніше.%{helpEnd}"
msgid "Configure automatic git checks and housekeeping on repositories."
msgstr "Ðалаштувати автоматичні перевірки git Ñ– Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð² репозиторіÑÑ…."
@@ -2656,6 +3074,12 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— з %{link}."
msgid "Configure the way a user creates a new account."
msgstr "Ðалаштувати ÑпоÑіб ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачем нового облікового запиÑу."
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "Підключити"
@@ -2671,12 +3095,24 @@ msgstr "Підключіть ваші зовнішні репозиторії, Ñ
msgid "Connecting..."
msgstr "З'єднаннÑ..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
+msgstr "ЗвернітьÑÑ Ð´Ð¾ відділу продажів Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ на вищий тарифний план"
+
+msgid "Contact your Administrator to upgrade your license."
msgstr ""
msgid "Container Registry"
msgstr "РеєÑÑ‚Ñ€ Контейнерів"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "Створений"
@@ -2723,7 +3159,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr "Ви також можете викориÑтовувати %{deploy_token} Ð´Ð»Ñ Ð´Ð¾Ñтупу тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾ образів у реєÑтрі."
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr "ВміÑÑ‚ .gitlab-ci.yml"
msgid "Continue"
msgstr "Продовжити"
@@ -2740,8 +3176,11 @@ msgstr "Зробити внеÑок в GitLab"
msgid "Contribution"
msgstr "ВнеÑок"
+msgid "Contribution Analytics"
+msgstr "Ðналітика внеÑків"
+
msgid "Contribution Charts"
-msgstr ""
+msgstr "СтатиÑтика внеÑків"
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr "ВнеÑки за <strong>%{calendar_date}</strong>"
@@ -2767,23 +3206,14 @@ msgstr "Будь лаÑка, зачекайте, Ñ†Ñ Ñторінка автоÐ
msgid "Control the display of third party offers."
msgstr "Керувати відображеннÑм Ñторонніх пропозицій."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Задати макÑимальну кількіÑÑ‚ÑŒ потоків Ð´Ð»Ñ Ñ„Ð¾Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ LFS/вкладень Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вторинного вузла"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "Задати макÑимальну кількіÑÑ‚ÑŒ потоків Ð´Ð»Ñ Ñ„Ð¾Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð² Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вторинного вузла"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "Ð’Ñтановіть макÑимальну кількіÑÑ‚ÑŒ паралельних операцій перевірки Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Geo-вузла"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "Ð†Ð½Ð´ÐµÐºÑ ConvDev"
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "Скопіювати URL Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· %{http_label}"
msgid "Copy %{protocol} clone URL"
msgstr "Скопіювати URL Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· %{protocol}"
@@ -2791,11 +3221,14 @@ msgstr "Скопіювати URL Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· %{protoc
msgid "Copy ID to clipboard"
msgstr "Скопіювати ID в буфер обміну"
+msgid "Copy KRB5 clone URL"
+msgstr "Скопіювати URL Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· KRB5"
+
msgid "Copy SSH clone URL"
msgstr "Скопіювати URL Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· SSH"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "Скопіювати публічний ключ SSH"
msgid "Copy SSH public key to clipboard"
msgstr "Скопіюйте відкритий SSH-ключ в буфер обміну"
@@ -2815,9 +3248,6 @@ msgstr "Скопіювати ідентифікатор в буфер обмін
msgid "Copy file path to clipboard"
msgstr "Скопіювати шлÑÑ… до файлу в буфер обміну"
-msgid "Copy incoming email address to clipboard"
-msgstr "Копіювати адреÑу електронної пошти у буфер обміну"
-
msgid "Copy link"
msgstr "Скопіювати поÑиланнÑ"
@@ -2833,9 +3263,33 @@ msgstr "Копіювати в буфер обміну"
msgid "Copy token to clipboard"
msgstr "Скопіювати токен в буфер обміну"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ ÑÑ‚Ð°Ñ‚ÑƒÑ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ð°. Ð”Ð»Ñ ÑƒÑÑƒÐ½ÐµÐ½Ð½Ñ Ð½ÐµÐ¿Ð¾Ð»Ð°Ð´Ð¾Ðº, прочитайте %{linkStart}документацію%{linkEnd}"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr "ПокриттÑ"
+
msgid "Create"
msgstr "Створити"
@@ -2843,23 +3297,26 @@ msgid "Create New Directory"
msgstr "Створити новий каталог"
msgid "Create New Domain"
+msgstr "Створити новий домен"
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
msgid "Create a new branch"
msgstr "Створити нову гілку"
-msgid "Create a new branch and merge request"
-msgstr "Створити нову гілку Ñ– запит на злиттÑ"
-
msgid "Create a new issue"
msgstr "Створити нову задачу"
msgid "Create a new repository"
-msgstr ""
+msgstr "Створити новий репозиторій"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Створіть токен доÑтупу Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ аккаунта, щоб відправлÑти та отримувати через %{protocol}."
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr "Створити задачу. Задачі будуть Ñтворені Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ попередженнÑ."
+
msgid "Create branch"
msgstr "Створити гілку"
@@ -2884,9 +3341,6 @@ msgstr "Створити групу"
msgid "Create group label"
msgstr "Створити мітку групи"
-msgid "Create issue"
-msgstr "Створити задачу"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Створити ÑпиÑок на оÑнові міток. Ð’ ньому будуть задачі з такими мітками."
@@ -2897,7 +3351,7 @@ msgid "Create merge request and branch"
msgstr "Створити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° гілку"
msgid "Create milestone"
-msgstr ""
+msgstr "Створити етап"
msgid "Create new branch"
msgstr "Створити нову гілку"
@@ -2956,6 +3410,9 @@ msgstr "СинтакÑÐ¸Ñ Cron"
msgid "Current Branch"
msgstr "Поточна гілка"
+msgid "Current Project"
+msgstr "Поточний проект"
+
msgid "Current node"
msgstr "Поточний вузол"
@@ -2981,7 +3438,7 @@ msgid "Custom project templates"
msgstr "ВлаÑні шаблони проектів"
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
-msgstr ""
+msgstr "ВлаÑні шаблони проектів не налаштовані Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿, до Ñких ви входите. Вони активуютьÑÑ Ð½Ð° Ñторінці налаштувань групи. ЗвернітьÑÑ Ð´Ð¾ влаÑника або керівника вашої групи Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð»Ð°Ñних шаблонів проектів."
msgid "Customize colors"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð»ÑŒÐ¾Ñ€Ñ–Ð²"
@@ -2993,10 +3450,7 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr "Ðалаштуйте, Ñк адреÑи електронної пошти та імена кориÑтувачів Google Code імпортуютьÑÑ Ð² GitLab. Ðа наÑтупному кроці ви зможете вибрати проекти, Ñкі потрібно імпортувати."
msgid "Customize language and region related settings."
-msgstr ""
-
-msgid "Customize your merge request approval settings."
-msgstr ""
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð²Ð¸ Ñ– параметрів, пов'Ñзаних із регіоном."
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Ðалаштуйте конфігурацію конвеєра, переглÑньте Ñтан Ñвого конвеєра Ñ– звіт про покриттÑ."
@@ -3029,7 +3483,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "ТеÑтуваннÑ"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "Панель керуваннÑ"
@@ -3040,9 +3494,12 @@ msgstr "Ð’ÑÑ–"
msgid "DashboardProjects|Personal"
msgstr "ОÑобиÑÑ‚Ñ–"
-msgid "Data is still calculating..."
+msgid "Dashboards"
msgstr ""
+msgid "Data is still calculating..."
+msgstr "Дані вÑе ще обчиÑлюютьÑÑ..."
+
msgid "Date picker"
msgstr "Вибір дати"
@@ -3056,7 +3513,7 @@ msgid "December"
msgstr "грудень"
msgid "Decline"
-msgstr ""
+msgstr "Відхилити"
msgid "Decline and sign out"
msgstr "Відхити та вийти"
@@ -3064,13 +3521,22 @@ msgstr "Відхити та вийти"
msgid "Default Branch"
msgstr "Гілка за замовчуваннÑм"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "Мітка клаÑифікації за замовчуваннÑм"
-msgid "Default first day of the week"
+msgid "Default description template for issues"
msgstr ""
+msgid "Default first day of the week"
+msgstr "Перший день Ñ‚Ð¸Ð¶Ð½Ñ Ð·Ð° замовчуваннÑм"
+
msgid "Default first day of the week in calendars and date pickers."
+msgstr "Перший день Ñ‚Ð¸Ð¶Ð½Ñ Ð·Ð° замовчуваннÑм в календарÑÑ… та при виборі дати."
+
+msgid "Default issue template"
msgstr ""
msgid "Default: Directly import the Google Code email address or username"
@@ -3083,7 +3549,7 @@ msgid "Define a custom pattern with cron syntax"
msgstr "Визначте влаÑний шаблон за допомогою ÑинтакÑиÑу cron"
msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
-msgstr ""
+msgstr "Визначте Ñередовища на Ñтадії Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñƒ <code>.gitlab-ci.yml</code> щоб відÑтежувати Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ‚ÑƒÑ‚."
msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
msgstr "Ви впевнені, що ви хочете запуÑтити %{jobName} відразу? Ð’ іншому випадку це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ виконано автоматично по завершенню таймера."
@@ -3112,18 +3578,36 @@ msgstr "Видалити Ñніпет"
msgid "Delete comment"
msgstr "Видалити коментар"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "Видалити ÑпиÑок"
msgid "Delete source branch"
-msgstr ""
+msgstr "Видалити гілку-джерело"
msgid "Delete this attachment"
-msgstr ""
+msgstr "Видалити це вкладеннÑ"
msgid "Deleted"
msgstr "Видалено"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "Заборонити"
@@ -3137,6 +3621,9 @@ msgstr[3] "Розгортань"
msgid "Deploy Keys"
msgstr "Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} інших"
@@ -3258,7 +3745,7 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "Створено ваш новий токен Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ."
msgid "Deployed"
-msgstr ""
+msgstr "Розгорнуто"
msgid "Deployed to"
msgstr "Розгорнуто на"
@@ -3275,12 +3762,45 @@ msgstr "За ÑпаданнÑм"
msgid "Description"
msgstr "ОпиÑ"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "Шаблони опиÑу дозволÑÑŽÑ‚ÑŒ визначити конкретні шаблони задач та запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проекту."
msgid "Description:"
msgstr "ОпиÑ:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "Знищити"
@@ -3288,7 +3808,7 @@ msgid "Details"
msgstr "Деталі"
msgid "Details (default)"
-msgstr ""
+msgstr "Деталі (за замовчуваннÑм)"
msgid "Detect host keys"
msgstr "ВиÑÐ²Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ñ–Ð² хоÑта"
@@ -3321,10 +3841,10 @@ msgid "Disable group Runners"
msgstr "Вимкнути групові Runner'и"
msgid "Disable shared Runners"
-msgstr ""
+msgstr "Вимкнути загальні Runner'и"
msgid "Disabled"
-msgstr ""
+msgstr "Вимкнено"
msgid "Discard"
msgstr "Відхилити"
@@ -3348,22 +3868,25 @@ msgid "Discard review"
msgstr "Відхилити перевірку"
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "Відкрийте GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr "Відкрийте Ð´Ð»Ñ Ñебе групи, проекти та фрагменти коду. ПоділітьÑÑ Ñвоїми проектами з іншими"
msgid "Discuss a specific suggestion or question"
-msgstr ""
+msgstr "Обговорити конкретну пропозицію чи питаннÑ"
msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
+msgstr "Обговорити конкретну пропозицію або питаннÑ, що необхідно вирішити"
+
+msgid "Discussion"
+msgstr "ОбговореннÑ"
msgid "Dismiss"
msgstr "Відхилити"
msgid "Dismiss ConvDev introduction"
-msgstr ""
+msgstr "Відхилити вÑтуп до ConvDev"
msgid "Dismiss Cycle Analytics introduction box"
msgstr "Відхилити блок вÑтупу до Ðналитики Циклу"
@@ -3393,25 +3916,31 @@ msgid "Download"
msgstr "Завантажити"
msgid "Download artifacts"
-msgstr ""
+msgstr "Завантажити артефакти"
msgid "Download asset"
+msgstr "Завантажити реÑурÑ"
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
msgstr ""
msgid "Download tar"
-msgstr "Завантажити tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "Завантажити tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "Завантажити tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "Завантажити zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "Завантажити"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "Email-патчі"
@@ -3423,7 +3952,7 @@ msgid "DownloadSource|Download"
msgstr "Завантажити"
msgid "Downstream"
-msgstr ""
+msgstr "ÐаÑтупний"
msgid "Downvotes"
msgstr "Дизлайки"
@@ -3441,13 +3970,16 @@ msgid "Edit"
msgstr "Редагувати"
msgid "Edit %{name}"
-msgstr ""
+msgstr "Редагувати %{name}"
+
+msgid "Edit Deploy Key"
+msgstr "Редагувати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ"
msgid "Edit Label"
msgstr "Редагувати мітку"
msgid "Edit Milestone"
-msgstr ""
+msgstr "Редагувати етап"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Редагувати Розклад Конвеєра %{id}"
@@ -3459,10 +3991,13 @@ msgid "Edit application"
msgstr "Редагувати заÑтоÑунок"
msgid "Edit comment"
-msgstr ""
+msgstr "Редагувати коментар"
msgid "Edit environment"
-msgstr ""
+msgstr "Редагувати Ñередовище"
+
+msgid "Edit file"
+msgstr "Редагувати файл"
msgid "Edit files in the editor and commit changes here"
msgstr "Редагуйте файли в редакторі і закомітьте зміни тут"
@@ -3474,7 +4009,10 @@ msgid "Edit identity for %{user_name}"
msgstr "Редагувати ідентифікацію Ð´Ð»Ñ %{user_name}"
msgid "Edit issues"
-msgstr ""
+msgstr "Редагувати задачі"
+
+msgid "Edit public deploy key"
+msgstr "Редагувати публічний ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
msgid "Elasticsearch"
msgstr "Elasticsearch"
@@ -3482,12 +4020,45 @@ msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Ð†Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ Ð· Elasticsearch. Elasticsearch AWS IAM."
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "Електронна пошта"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "Email-патч"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "ÐдреÑи електронної пошти"
@@ -3495,7 +4066,7 @@ msgid "Embed"
msgstr "Вбудувати"
msgid "Empty file"
-msgstr ""
+msgstr "Порожній файл"
msgid "Enable"
msgstr "Увімкнути"
@@ -3503,6 +4074,9 @@ msgstr "Увімкнути"
msgid "Enable Auto DevOps"
msgstr "Увімкнути Auto DevOps"
+msgid "Enable HTML emails"
+msgstr "Увімкнути HTML лиÑти"
+
msgid "Enable Pseudonymizer data collection"
msgstr "Увімкнути збір даних Ð´Ð»Ñ Pseudonymizer"
@@ -3522,7 +4096,7 @@ msgid "Enable classification control using an external service"
msgstr "Увімкнути контроль за клаÑифікацією за допомогою зовнішньої Ñлужби"
msgid "Enable error tracking"
-msgstr ""
+msgstr "Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»Ð¾Ðº"
msgid "Enable for this project"
msgstr "Увімкнути Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту"
@@ -3530,6 +4104,12 @@ msgstr "Увімкнути Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту"
msgid "Enable group Runners"
msgstr "Увімкнути групові Runner'и"
+msgid "Enable header and footer in emails"
+msgstr "Увімкнути заголовок та футер в електронних лиÑтах"
+
+msgid "Enable mirror configuration"
+msgstr "Увімкнути Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÐµÐ½Ð½Ñ"
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "Увімкнути чи вимкнути збір даних Ð´Ð»Ñ Pseudonymizer."
@@ -3539,34 +4119,43 @@ msgstr "Увімкнути чи вимкнути перевірку верÑÑ–Ñ—
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "Увімкнути reCAPTCHA або Akismet та вÑтановити Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾ IP."
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
-msgstr ""
+msgstr "Увімкнути загальні Runner'и"
msgid "Enable the Performance Bar for a given group."
msgstr "Увімкнути панель продуктивноÑÑ‚Ñ– Ð´Ð»Ñ Ð´Ð°Ð½Ð¾Ñ— групи."
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "Увімкнути двофакторну автентифікацію"
msgid "Enable usage ping"
-msgstr "Увімкнути викориÑÑ‚Ð°Ð½Ð½Ñ ping"
+msgstr "Увімкнути збір даних про викориÑтаннÑ"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "Увімкніть збір даних про викориÑтаннÑ, щоб отримати загальне уÑÐ²Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ те, Ñк ви викориÑтовуєте GitLab з точки зору функціоналу."
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "Увімкнено"
-msgid "Ends at (UTC)"
-msgstr "ЗавершуєтьÑÑ Ð¾ (за Грінвічем)"
+msgid "Enabled Git access protocols"
+msgstr "Увімкнути протоколи доÑтупу до Git"
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr "ЗавершуєтьÑÑ Ð¾ (за Грінвічем)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3584,26 +4173,20 @@ msgstr "Введіть Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на злиттÑ"
msgid "Enter the merge request title"
msgstr "Введіть назву запиту на злиттÑ"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
-msgstr ""
+msgstr "Змінні Ñередовища налаштовані адмініÑтратором бути %{link_start}захищеними%{link_end} за замовчуваннÑм"
msgid "Environment:"
-msgstr ""
+msgstr "Середовище:"
msgid "Environments"
msgstr "Середовища"
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
-msgstr ""
+msgstr "Середовища дозволÑÑŽÑ‚ÑŒ відÑтежувати Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ заÑтоÑунку %{link_to_read_more}."
msgid "Environments|An error occurred while fetching the environments."
msgstr "Виникла помилка при завантаженні Ñередовищ."
@@ -3611,6 +4194,12 @@ msgstr "Виникла помилка при завантаженні Ñеред
msgid "Environments|An error occurred while making the request."
msgstr "Під Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¾Ð¼Ñƒ розгортанні Ñередовища, будь лаÑка, Ñпробуйте ще раз"
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·ÑƒÐ¿Ð¸Ð½ÐºÐ¸ Ñередовища, будь лаÑка, Ñпробуйте ще раз"
@@ -3662,15 +4251,33 @@ msgstr "Відкрити працююче Ñередовище"
msgid "Environments|Pod logs from"
msgstr "Журнал Pod’а"
+msgid "Environments|Re-deploy"
+msgstr "Повторно розгорнути"
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr "Повторно розгорнути в Ñередовищі %{name}?"
+
msgid "Environments|Re-deploy to environment"
msgstr "Повторно розгорнути в Ñередовищі"
msgid "Environments|Read more about environments"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ñередовища"
+msgid "Environments|Rollback"
+msgstr "Відкотити"
+
msgid "Environments|Rollback environment"
msgstr "Відкотити Ñередовище"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr "Відкотити Ñередовище %{environment_name}?"
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr "Відкотити Ñередовище %{name}?"
+
msgid "Environments|Show all"
msgstr "Показати вÑÑ–"
@@ -3681,6 +4288,18 @@ msgid "Environments|Stop environment"
msgstr "Зупинити Ñередовище"
msgid "Environments|Stopping"
+msgstr "Зупинка"
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|Updated"
@@ -3705,7 +4324,7 @@ msgid "Epics let you manage your portfolio of projects more efficiently and with
msgstr "Епіки дозволÑÑŽÑ‚ÑŒ керувати вашим портфелем проектів ефективніше та з меншими зуÑиллÑми"
msgid "Epics|An error occurred while saving the %{epicDateType} date"
-msgstr ""
+msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при збереженні дати %{epicDateType}"
msgid "Epics|How can I solve this?"
msgstr "Як Ñ Ð¼Ð¾Ð¶Ñƒ це вирішити?"
@@ -3732,13 +4351,16 @@ msgid "Error Reporting and Logging"
msgstr "Звіти про помилки та логуваннÑ"
msgid "Error Tracking"
-msgstr ""
+msgstr "ВідÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»Ð¾Ðº"
+
+msgid "Error creating a new path"
+msgstr "Помилка при Ñтворенні нового шлÑху"
msgid "Error creating epic"
msgstr "Помилка при Ñтворенні епіку"
msgid "Error deleting %{issuableType}"
-msgstr ""
+msgstr "Помилка при видаленні %{issuableType}"
msgid "Error fetching contributors data."
msgstr "Помилка Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… учаÑників."
@@ -3782,30 +4404,78 @@ msgstr "Помилка при завантаженні шаблону."
msgid "Error occurred when toggling the notification subscription"
msgstr "СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки на ÑповіщеннÑ"
-msgid "Error rendering markdown preview"
+msgid "Error occurred. User was not blocked"
msgstr ""
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr "Помилка при попередньому переглÑді markdown"
+
msgid "Error saving label update."
msgstr "Помилка при збереженні мітки."
msgid "Error updating %{issuableType}"
-msgstr ""
+msgstr "Помилка при оновленні %{issuableType}"
msgid "Error updating status for all todos."
-msgstr "Помилка Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑтатуÑу Ð´Ð»Ñ Ð²ÑÑ–Ñ… задач."
+msgstr "Помилка Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ ÑтатуÑу Ð´Ð»Ñ Ð²ÑÑ–Ñ… нагадувань."
msgid "Error updating todo status."
-msgstr "Помилка при оновленні ÑтатуÑу задачі."
+msgstr "Помилка при оновленні ÑтатуÑу нагадуваннÑ."
+
+msgid "Error uploading file"
+msgstr ""
msgid "Error while loading the merge request. Please try again."
msgstr "Помилка при завантаженні запита на злиттÑ. Будь лаÑка, Ñпробуйте знову."
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
+msgstr "Помилка:"
+
+msgid "ErrorTracking|Active"
+msgstr "Ðктивовано"
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
msgstr ""
-msgid "Errors"
+msgid "ErrorTracking|Auth Token"
+msgstr "Токен автентифікації"
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
msgstr ""
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr "Виберіть проект"
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr "Помилки"
+
msgid "Estimated"
msgstr "За оцінками"
@@ -3828,10 +4498,10 @@ msgid "EventFilterBy|Filter by team"
msgstr "Фільтрувати по команді"
msgid "Events"
-msgstr ""
+msgstr "Події"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
-msgstr ""
+msgstr "УÑÑ– Ñпроби %{action} закінчилиÑÑ Ð½ÐµÐ²Ð´Ð°Ñ‡ÐµÑŽ: %{job_error_message}. Будь лаÑка, Ñпробуйте знову."
msgid "Every day (at 4:00am)"
msgstr "Кожен день (в 4:00 ранку)"
@@ -3843,37 +4513,31 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "Ð©Ð¾Ñ‚Ð¸Ð¶Ð½Ñ (в неділю о 4:00 ранку)"
msgid "Everyone"
-msgstr ""
+msgstr "Будь-хто"
msgid "Everyone can contribute"
msgstr "Кожен може зробити Ñвій внеÑок"
msgid "Everything you need to create a GitLab Pages site using GitBook."
-msgstr ""
+msgstr "Ð’Ñе, що потрібно Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñайту на GitLab Pages за допомогою GitBook."
msgid "Everything you need to create a GitLab Pages site using Hexo."
-msgstr ""
+msgstr "Ð’Ñе, що потрібно Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñайту на GitLab Pages за допомогою Hexo."
msgid "Everything you need to create a GitLab Pages site using Hugo."
-msgstr ""
+msgstr "Ð’Ñе, що потрібно Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñайту на GitLab Pages за допомогою Hugo."
msgid "Everything you need to create a GitLab Pages site using Jekyll."
-msgstr ""
+msgstr "Ð’Ñе, що потрібно Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñайту на GitLab Pages за допомогою Jekyll."
msgid "Everything you need to create a GitLab Pages site using plain HTML."
-msgstr ""
+msgstr "Ð’Ñе, що потрібно Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñайту на GitLab Pages за допомогою проÑтого HTML."
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
-msgstr ""
+msgstr "ІÑнуючі учаÑники та групи"
msgid "Expand"
msgstr "Розгорнути"
@@ -3881,15 +4545,21 @@ msgstr "Розгорнути"
msgid "Expand all"
msgstr "Розгорнути вÑе"
+msgid "Expand approvers"
+msgstr "Розгорнути ÑпиÑок затверджуючих оÑіб"
+
msgid "Expand sidebar"
msgstr "Розгорніть бічну панель"
msgid "Expiration date"
msgstr "Термін дії"
-msgid "Expired %{expiredOn}"
+msgid "Expired"
msgstr ""
+msgid "Expired %{expiredOn}"
+msgstr "Минув(-ло) %{expiredOn}"
+
msgid "Expires in %{expires_at}"
msgstr ""
@@ -3915,22 +4585,28 @@ msgid "Explore public groups"
msgstr "ПереглÑнути публічні групи"
msgid "Export as CSV"
-msgstr ""
+msgstr "ЕкÑпортувати Ñк CSV"
msgid "Export issues"
+msgstr "ЕкÑпортувати задачі"
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
msgstr ""
msgid "External Classification Policy Authorization"
msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ ÐšÐ»Ð°ÑÐ¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ ÐŸÐ¾Ð»Ñ–Ñ‚Ð¸ÐºÐ¸ Ðвторизації"
msgid "External URL"
-msgstr ""
+msgstr "Зовнішній URL"
msgid "External Wiki"
-msgstr ""
+msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð²Ñ–ÐºÑ–"
msgid "External authentication"
-msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ"
+msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ"
msgid "External authorization denied access to this project"
msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ Ð·Ð°Ð±Ð¾Ñ€Ð¾Ð½Ð¸Ð»Ð° доÑтуп до цього проекту"
@@ -3938,9 +4614,6 @@ msgstr "Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ Ð·Ð°Ð±Ð¾Ñ€Ð¾Ð½Ð¸Ð»Ð° доÑтуÐ
msgid "External authorization request timeout"
msgstr "Ð§Ð°Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на зовнішню авторизацію"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "Мітка клаÑифікації"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "Мітка клаÑифікації"
@@ -3962,13 +4635,19 @@ msgstr "Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ влаÑника"
msgid "Failed to check related branches."
msgstr "Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ пов’Ñзані гілки."
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "Ðе вдалоÑÑ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ð½ÑƒÑ‚Ð¸ до"
msgid "Failed to load emoji list."
msgstr "Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ ÑпиÑок Ñмайликів."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3980,9 +4659,24 @@ msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ дзеркало."
msgid "Failed to remove the pipeline schedule"
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ розклад конвеєра"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "Ðе вдалоÑÑ Ð¿Ñ–Ð´Ð¿Ð¸Ñати з викориÑтаннÑм перевірки автентичноÑÑ‚Ñ– Ñмарт-карти"
@@ -3995,20 +4689,26 @@ msgstr ""
msgid "Failure"
msgstr "Помилка"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "Швидше, бо повтоно викориÑтовує робочу облаÑÑ‚ÑŒ проекту (викориÑтовуючи clone, Ñкщо та відÑутнÑ)"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "Перемикачі функцій"
msgid "FeatureFlags|* (All Environments)"
-msgstr ""
+msgstr "* (УÑÑ– Ñередовища)"
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "* (вÑÑ– Ñередовища)"
msgid "FeatureFlags|API URL"
msgstr "URL-адреÑа API"
@@ -4026,49 +4726,46 @@ msgid "FeatureFlags|Create feature flag"
msgstr "Створити перемикач функції"
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "Видалити %{name}?"
msgid "FeatureFlags|Delete feature flag"
-msgstr ""
+msgstr "Вимкнути перемикач функції"
msgid "FeatureFlags|Description"
msgstr "ОпиÑ"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "Редагувати %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "Редагувати перемикач функції"
msgid "FeatureFlags|Environment Spec"
-msgstr ""
+msgstr "Ð¡Ð¿ÐµÑ†Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñередовища"
msgid "FeatureFlags|Environment Specs"
-msgstr ""
+msgstr "Специфікації Ñередовищ"
msgid "FeatureFlags|Feature Flag"
msgstr "Перемикач функції"
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
-msgstr ""
+msgstr "Поведінка перемикачів функцій ÑтворюєтьÑÑ ÑˆÐ»Ñхом ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð°Ð±Ð¾Ñ€Ñƒ правил Ð´Ð»Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñтану цільових Ñередовищ. Правило за замовчуваннÑм %{codeStart}*%{codeEnd} Ð´Ð»Ñ %{boldStart}Ð’ÑÑ–Ñ… Ñередовищ%{boldEnd} вже налаштовано, Ñ– ви можете додати Ñтільки правил, Ñкільки вам потрібно, шлÑхом Ð·Ð°Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ñпецифікацій Ñередовищ нижче. Ви можете перемикати Ñтан Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ з ваших правил, щоб зробити Ñ—Ñ… %{boldStart}Ðктивними%{boldEnd} або %{boldStart}Ðеактивними%{boldEnd}."
msgid "FeatureFlags|Feature Flags"
-msgstr ""
+msgstr "Перемикачі функцій"
msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
-msgstr ""
+msgstr "Перемикачі функцій дозволÑÑŽÑ‚ÑŒ налаштовувати ваш код по-різному за допомогою динамічного ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ‡Ð¸ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð¿ÐµÐ²Ð½Ð¾Ñ— функціональноÑÑ‚Ñ–."
msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
-msgstr ""
+msgstr "Перемикач функції %{name} буде видалено. Ви впевнені?"
msgid "FeatureFlags|Get started with Feature Flags"
-msgstr ""
+msgstr "Розпочати роботу з перемикачами функцій"
msgid "FeatureFlags|Inactive"
msgstr "Ðеактивний"
msgid "FeatureFlags|Inactive flag for %{scope}"
-msgstr ""
+msgstr "Ðеактивний перемикач функції Ð´Ð»Ñ %{scope}"
msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
msgstr "Ð’Ñтановіть %{docs_link_start}ÑуміÑну клієнтÑьку бібліотеку%{docs_link_end} Ñ– вкажіть URL адреÑу API, назву заÑтоÑунку та ідентифікатор інÑтанÑа під Ñ‡Ð°Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ."
@@ -4077,10 +4774,10 @@ msgid "FeatureFlags|Instance ID"
msgstr "Ідентифікатор ІнÑтанÑу"
msgid "FeatureFlags|Loading Feature Flags"
-msgstr ""
+msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ñ‡Ñ–Ð² функцій"
msgid "FeatureFlags|More Information"
-msgstr ""
+msgstr "Більше інформації"
msgid "FeatureFlags|More information"
msgstr "Більше інформації"
@@ -4094,26 +4791,26 @@ msgstr "Ðовий"
msgid "FeatureFlags|New Feature Flag"
msgstr "Ðовий перемикач функції"
-msgid "FeatureFlags|Save changes"
-msgstr "Зберегти зміни"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "СтатуÑ"
msgid "FeatureFlags|Target environments"
-msgstr ""
+msgstr "Цільові Ñередовища"
msgid "FeatureFlags|There are no active Feature Flags"
-msgstr ""
+msgstr "Ðемає активних перемикачів функцій"
msgid "FeatureFlags|There are no inactive Feature Flags"
-msgstr ""
+msgstr "Ðемає неактивних перемикачів функцій"
msgid "FeatureFlags|There was an error fetching the feature flags."
-msgstr ""
+msgstr "Помилка при отриманні перемикачів функцій."
msgid "FeatureFlags|Try again in a few moments or contact your support team."
-msgstr ""
+msgstr "Повторіть Ñпробу через деÑкий Ñ‡Ð°Ñ Ð°Ð±Ð¾ звернітьÑÑ Ð´Ð¾ вашої Ñлужби підтримки."
msgid "Feb"
msgstr "лют."
@@ -4121,36 +4818,48 @@ msgstr "лют."
msgid "February"
msgstr "лютий"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "ÐŸÐ¾Ð»Ñ Ð½Ð° цій Ñторінці зараз недоÑтупні Ð´Ð»Ñ Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ, ви можете налаштувати"
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Файл"
+msgstr[1] "Файли"
+msgstr[2] "Файлів"
+msgstr[3] "Файлів"
msgid "File added"
-msgstr ""
+msgstr "Файл додано"
msgid "File browser"
-msgstr ""
+msgstr "Файловий менеджер"
msgid "File deleted"
-msgstr ""
+msgstr "Файл видалено"
msgid "File mode changed from %{a_mode} to %{b_mode}"
-msgstr ""
+msgstr "Режим файлу змінено з %{a_mode} на %{b_mode}"
msgid "File moved"
-msgstr ""
+msgstr "Файл переміщено"
msgid "File templates"
msgstr "Шаблони файлів"
msgid "File upload error."
-msgstr ""
+msgstr "Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ."
msgid "Files"
msgstr "Файли"
@@ -4168,31 +4877,28 @@ msgid "Filter by %{issuable_type} that are currently opened."
msgstr "Фільтрувати відкриті за %{issuable_type}."
msgid "Filter by commit message"
-msgstr "Фільтрувати за коміт-повідомленнÑм"
+msgstr "Фільтрувати за повідомленнÑм Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ"
msgid "Filter by milestone name"
-msgstr ""
+msgstr "Фільтрувати за назвою етапу"
msgid "Filter by two-factor authentication"
-msgstr ""
+msgstr "Фільтрувати за двофакторною автентифікацією"
msgid "Filter results by group"
-msgstr ""
+msgstr "Фільтрувати результати за групою"
msgid "Filter results by project"
-msgstr ""
+msgstr "Фільтрувати результати за проектом"
msgid "Filter..."
msgstr "Фільтр..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Пошук по шлÑху"
msgid "Find existing members by name"
-msgstr ""
+msgstr "Знайти Ñ–Ñнуючих учаÑників за ім'Ñм"
msgid "Find file"
msgstr "Знайти файл"
@@ -4203,11 +4909,14 @@ msgstr "Знайдіть завантажений ZIP-файл і розпаку
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Знайдіть щойно розпакований <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> файл."
+msgid "Fingerprint"
+msgstr "Відбиток"
+
msgid "Fingerprints"
msgstr "Відбитки пальців"
msgid "Finish editing this message first!"
-msgstr ""
+msgstr "Спочатку завершіть Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ повідомленнÑ!"
msgid "Finish review"
msgstr "Завершити перевірку"
@@ -4216,7 +4925,7 @@ msgid "Finished"
msgstr "Завершено"
msgid "First day of the week"
-msgstr ""
+msgstr "Перший день тижнÑ"
msgid "FirstPushedBy|First"
msgstr "Перший"
@@ -4264,7 +4973,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr "Ð”Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ–Ñ… проектів будь-Ñкий зареєÑтрований кориÑтувач може переглÑдати конвеєри та отримати доÑтуп до інформації про роботу (логи та артефакти)"
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації читайте документацію."
msgid "For more information, go to the "
msgstr "Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації, відвідайте "
@@ -4291,19 +5000,25 @@ msgid "Forking in progress"
msgstr "ВідбуваєтьÑÑ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ„Ð¾Ñ€ÐºÑƒ"
msgid "Forks"
-msgstr ""
+msgstr "Форки"
msgid "Format"
msgstr "Формат"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr "Знайдено помилки у вашому %{gitlab_ci_yml}:"
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "Знайдено помилки у вашому .gitlab-ci.yml:"
msgid "Free Trial of GitLab.com Gold"
msgstr "Безкоштовна пробна верÑÑ–Ñ GitLab.com Gold"
-msgid "From %{provider_title}"
-msgstr "З %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr "З %{providerTitle}"
msgid "From Bitbucket"
msgstr "З Bitbucket"
@@ -4332,12 +5047,21 @@ msgstr "З етапів:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Із Ñторінки деталей Kubernetes-клаÑтера, вÑтановіть runner зі ÑпиÑку заÑтоÑунків"
+msgid "GPG Key ID:"
+msgstr "Ідентифікатор ключа GPG:"
+
msgid "GPG Keys"
msgstr "GPG ключі"
+msgid "GPG signature (loading...)"
+msgstr "ÐŸÑ–Ð´Ð¿Ð¸Ñ GPG (завантаженнÑ...)"
+
msgid "General"
msgstr "Загальні"
+msgid "General Settings"
+msgstr "Загальні налаштуваннÑ"
+
msgid "General pipelines"
msgstr "Загальні конвеєри"
@@ -4345,6 +5069,9 @@ msgid "Generate a default set of labels"
msgstr "Створити Ñтандартний набір міток"
msgid "Generate key"
+msgstr "Згенерувати ключ"
+
+msgid "Generate new export"
msgstr ""
msgid "Geo"
@@ -4371,9 +5098,6 @@ msgstr "Дані заÑтаріли на %{timeago}"
msgid "GeoNodes|Data replication lag"
msgstr "Затримка реплікації даних"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "Ð’Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð²ÑƒÐ·Ð»Ð° зупинÑÑ” Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñинхронізації. Ви впевнені?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "Ðе відповідає оÑновній конфігурації Ñховища"
@@ -4392,6 +5116,9 @@ msgstr "ВерÑÑ–Ñ GitLab не відповідає верÑÑ–Ñ— оÑновнÐ
msgid "GeoNodes|Health status"
msgstr "Стан працездатноÑÑ‚Ñ–"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "ОÑтанній ідентифікатор події, оброблений курÑором"
@@ -4426,7 +5153,7 @@ msgid "GeoNodes|New node"
msgstr "Ðовий вузол"
msgid "GeoNodes|Node Authentication was successfully repaired."
-msgstr "Ðутентифікацію вузла уÑпішно полагоджено."
+msgstr "Ðвтентифікацію вузла уÑпішно полагоджено."
msgid "GeoNodes|Node was successfully removed."
msgstr "Вузол уÑпішно видалено."
@@ -4437,11 +5164,14 @@ msgstr "Без контрольної Ñуми"
msgid "GeoNodes|Out of sync"
msgstr "РозÑинхронізовані"
-msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgid "GeoNodes|Pausing replication stops the sync process."
msgstr ""
+msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ñновного вузла зупинÑÑ” Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñинхронізації Ð´Ð»Ñ Ð²ÑÑ–Ñ… вузлів. Синхронізацію неможливо буде відновити без втрати деÑких даних на вÑÑ–Ñ… вторинних вузлах. У цьому випадку рекомендовано налаштувати вÑÑ– вузли з нулÑ. Ви впевнені?"
+
msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
-msgstr ""
+msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñ‚Ð¾Ñ€Ð¸Ð½Ð½Ð¾Ð³Ð¾ вузла зупинÑÑ” Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñинхронізації. Ðаразі неможливо додати назад той Ñамий вузол без втрати деÑких даних. У цьому випадку рекомендовано Ñтворити новий вторинний вузол. Ви впевнені?"
msgid "GeoNodes|Replication slot WAL"
msgstr "Слот реплікації WAL"
@@ -4516,7 +5246,7 @@ msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
msgstr "Вікі перевірено із їхніми копіÑми на первинному вузлі"
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
-msgstr ""
+msgstr "З %{geo} ви можете вÑтановити будь-де Ñпеціальній реплікований інÑÑ‚Ð°Ð½Ñ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ. Перед тим, Ñк додавати вузли, дотримайтеÑÑ %{instructions} в точному порÑдку."
msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
msgstr "Ви налаштували Geo-вузли через незахищене HTTP-з’єднаннÑ. Ми рекомендуємо викориÑтовувати HTTPS."
@@ -4545,9 +5275,24 @@ msgstr "Ð’ÑÑ– проекти плануютьÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¾Ñ— ÑÐ
msgid "Geo|Batch operations"
msgstr "Групові операції"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr "Виберіть групи, Ñкі ви хотіли б Ñинхронізувати на цей вторинний вузол."
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr "Задати макÑимальну кількіÑÑ‚ÑŒ потоків Ð´Ð»Ñ Ñ„Ð¾Ð½Ð¾Ð²Ð¾Ð³Ð¾ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ LFS/вкладень Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вторинного вузла"
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr "Ð’Ñтановіть макÑимальну кількіÑÑ‚ÑŒ паралельних операцій перевірки Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Geo-вузла"
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr "Задати мінімальний інтервал у днÑÑ… Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¸Ñ… перевірок репозиторіїв на цьому оÑновному вузлі"
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ Ð·Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´ÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñ–Ñнуючого проекту."
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "Ðевдало"
@@ -4563,9 +5308,12 @@ msgstr "Групи Ð´Ð»Ñ Ñинхронізації"
msgid "Geo|In sync"
msgstr "Синхронізовано"
-msgid "Geo|Last repository check run"
+msgid "Geo|Internal URL"
msgstr ""
+msgid "Geo|Last repository check run"
+msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Ð·Ð°Ð¿ÑƒÑк перевірки репозиторію"
+
msgid "Geo|Last successful sync"
msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ ÑƒÑпішна ÑинхронізаціÑ"
@@ -4593,6 +5341,12 @@ msgstr "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñинхронізації"
msgid "Geo|Pending verification"
msgstr "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "Проект (ID: %{project_id}) більше не Ñ–Ñнує на оÑновному вузлі. Поточний Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð¾Ð¶Ð½Ð° безпечно видалити, оÑкільки це не видалÑÑ” жодних даних на диÑку."
@@ -4603,7 +5357,7 @@ msgid "Geo|Projects in certain storage shards"
msgstr "Проекти в певних Ñегментах Ñховищ"
msgid "Geo|Re-verification interval"
-msgstr ""
+msgstr "Інтервал повторної перевірки"
msgid "Geo|Recheck"
msgstr "Повторна перевірка"
@@ -4632,24 +5386,45 @@ msgstr "КількіÑÑ‚ÑŒ Ñпроб"
msgid "Geo|Select groups to replicate."
msgstr "Виберіть групи Ð´Ð»Ñ Ñ€ÐµÐ¿Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ—."
+msgid "Geo|Selective synchronization"
+msgstr "Вибіркова ÑинхронізаціÑ"
+
msgid "Geo|Shards to synchronize"
msgstr "Сегменти Ð´Ð»Ñ Ñинхронізації"
msgid "Geo|Status"
msgstr "СтатуÑ"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "Синхронізовано"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð½ÐµÐ²Ð´Ð°Ð»Ð°: %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr "Це — первинний вузол"
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "Ð—Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´ÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ (%{project_id}) уÑпішно видалено."
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "Буде видалено Ð·Ð°Ð¿Ð¸Ñ Ð¿Ñ€Ð¾ відÑтеженнÑ. Ви впевнені?"
+msgid "Geo|URL"
+msgstr "URL"
+
msgid "Geo|Unknown state"
msgstr "Ðевідомий Ñтан"
@@ -4668,9 +5443,6 @@ msgstr "Ви знаходитеÑÑŒ на вторинному <b>лише длÑ
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "Ви знаходитеÑÑŒ на вторинному <b>лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ</b> Geo-вузлі. Ви зможете вноÑити лише обмежену кількіÑÑ‚ÑŒ змін та виконувати обмежений набір операцій з цієї Ñторінки."
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "Вам потрібна інша Ð»Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ð½Ð° викориÑÑ‚Ð°Ð½Ð½Ñ Ð³ÐµÐ¾Ð³Ñ€Ð°Ñ„Ñ–Ñ‡Ð½Ð¾Ñ— реплікації"
-
msgid "Geo|misconfigured"
msgstr "неправильно налаштований"
@@ -4684,17 +5456,20 @@ msgid "Get a free instance review"
msgstr "Отримайте безкоштовну оцінку інÑтанÑа"
msgid "Get started with error tracking"
-msgstr ""
+msgstr "Розпочати роботу з відÑтеженнÑм помилок"
msgid "Getting started with releases"
-msgstr ""
+msgstr "Розпочати роботу з релізами"
msgid "Git"
msgstr "Git"
-msgid "Git global setup"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
+msgid "Git global setup"
+msgstr "Глобальні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Git"
+
msgid "Git repository URL"
msgstr "URL Git-репозиторіÑ"
@@ -4713,6 +5488,9 @@ msgstr "GitHub-імпорт"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter був перенеÑений"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4728,12 +5506,18 @@ msgstr ""
msgid "GitLab User"
msgstr "GitLab КориÑтувач"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "ЕкÑпорт проекту GitLab"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "URL єдиного входу GitLab"
@@ -4762,20 +5546,29 @@ msgid "Gitea Import"
msgstr "Імпорт з Gitea"
msgid "Given access %{time_ago}"
-msgstr ""
+msgstr "Ðадано доÑтуп %{time_ago}"
msgid "Go Back"
msgstr "ПовернутиÑÑ"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "ПовернутиÑÑ"
+msgid "Go full screen"
+msgstr "Ðа повний екран"
+
msgid "Go to"
msgstr "Перейти до"
msgid "Go to %{link_to_google_takeout}."
msgstr "Перейти до %{link_to_google_takeout}."
+msgid "Go to project"
+msgstr "Перейти до проекту"
+
msgid "Google Code import"
msgstr "Імпорт з Google Code"
@@ -4783,20 +5576,29 @@ msgid "Google Takeout"
msgstr "Google Takeout"
msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
-msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Google не %{link_to_documentation}. ПопроÑÑ–Ñ‚ÑŒ Ñвого адмініÑтратора GitLab, Ñкщо ви хочете ÑкориÑтатиÑÑ Ñ†Ð¸Ð¼ ÑервіÑом."
+msgstr "ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Google не %{link_to_documentation}. ПопроÑÑ–Ñ‚ÑŒ Ñвого адмініÑтратора GitLab, Ñкщо ви хочете ÑкориÑтатиÑÑ Ñ†Ð¸Ð¼ ÑервіÑом."
msgid "Got it!"
msgstr "Зрозуміло!"
msgid "Grant access"
-msgstr ""
+msgstr "Ðадати доÑтуп"
msgid "Graph"
msgstr "Графік"
+msgid "Gravatar enabled"
+msgstr "Gravatar увімкнено"
+
msgid "Group"
msgstr "Група"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD групи"
@@ -4806,6 +5608,9 @@ msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð³Ñ€ÑƒÐ¿Ð¸ Git LFS:"
msgid "Group ID"
msgstr "Ідентифікатор групи"
+msgid "Group ID: %{group_id}"
+msgstr "ID групи: %{group_id}"
+
msgid "Group Runners"
msgstr "Групові Runner'и"
@@ -4837,10 +5642,13 @@ msgid "Group name"
msgstr "Ðазва групи"
msgid "Group overview content"
+msgstr "ВміÑÑ‚ оглÑдової Ñторінки групи"
+
+msgid "Group was successfully updated."
msgstr ""
msgid "Group:"
-msgstr ""
+msgstr "Група:"
msgid "Group: %{group_name}"
msgstr "Група: %{group_name}"
@@ -4857,34 +5665,115 @@ msgstr "Вибачте, жоден епік не задовольнÑÑ” крит
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "План-графік епіків відображає Ñтан ваших епіків у чаÑÑ–"
-msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr "Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду плану-графіку, додайте дату початку чи Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ð´Ð¾ одного з ваших епіків в цій групі або Ñ—Ñ— підгрупах; від %{startDate} до %{endDate}."
+
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
-msgstr ""
+msgstr "Щоб розширити пошук, змініть або видаліть фільтри; від %{startDate} до %{endDate}."
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "До %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "Значки"
msgid "GroupSettings|Custom project templates"
-msgstr ""
+msgstr "ВлаÑні шаблони проектів"
msgid "GroupSettings|Customize your group badges."
msgstr "Ðалаштувати значки групи."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про значки."
msgid "GroupSettings|Learn more about group-level project templates."
+msgstr "Докладніше про шаблони проектів на рівні групи."
+
+msgid "GroupSettings|New runners registration token has been generated!"
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Заборонити Ñпільний доÑтуп до проекту в рамках %{group} з іншими групами"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr "Виберіть підгрупу Ñк джерело влаÑних шаблонів проектів Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— групи."
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr "Конвеєр Auto DevOps буде запущено, Ñкщо не буде знайдено альтернативного файлу конфігуріції CI."
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
@@ -4912,7 +5801,7 @@ msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroup
msgstr "Групи також можуть бути вкладеними при викориÑтанні %{subgroup_docs_link_start}підгруп%{subgroup_docs_link_end}."
msgid "Groups with access to <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "Групи з доÑтупом до <strong>%{project_name}</strong>"
msgid "GroupsDropdown|Frequently visited"
msgstr "ЧаÑто відвідувані"
@@ -4980,6 +5869,9 @@ msgstr "Пошук за іменем"
msgid "Have your users email"
msgstr "Електронна пошта Ð´Ð»Ñ Ð·Ð²ÐµÑ€Ñ‚Ð°Ð½ÑŒ кориÑтувачів"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ°"
@@ -5014,11 +5906,14 @@ msgid "Here is the public SSH key that needs to be added to the remote server. F
msgstr "Це відкритий (публічний) SSH ключ, Ñкий потрібно додати на віддалений Ñервер. Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації, звернітьÑÑ Ð´Ð¾ документації."
msgid "Hide file browser"
-msgstr ""
+msgstr "Сховати файловий менеджер"
msgid "Hide host keys manual input"
msgstr "Сховати ввід ключів хоÑта"
+msgid "Hide marketing-related entries from help"
+msgstr "Сховати маркетингові розділи із допомоги"
+
msgid "Hide payload"
msgstr "Приховати кориÑне навантаженнÑ"
@@ -5030,17 +5925,32 @@ msgstr[2] "Сховати значень"
msgstr[3] "Сховати значень"
msgid "Hide values"
+msgstr "Сховати значеннÑ"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr "ІÑторіÑ"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ ÑƒÑпішно розпочато"
-msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgid "Housekeeping, export, path, transfer, remove, archive."
msgstr ""
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr "Проте ви вже Ñ” учаÑником цього %{member_source}. Увійдіть, викориÑтовуючи інший обліковий запиÑ, щоб прийнÑти запрошеннÑ."
+
msgid "I accept the %{terms_link}"
msgstr "Я приймаю %{terms_link}"
@@ -5095,15 +6005,18 @@ msgstr "Ідентифікатор"
msgid "Identities"
msgstr "ІдентифікаціÑ"
-msgid "Identity provider single sign on URL"
-msgstr "URL єдиного входу провайдера ідентифікації"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr "Якщо увімкнено, тоді влаÑники груп зможуть керувати зв'Ñзками груп LDAP та перевизначеннÑм членів LDAP"
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "Якщо вимкнено, локальна гілка зі змінами не буде автоматично підтÑгувати коміти з віддаленої гілки, щоб уникнути втрати локальних даних. Якщо гілка за замовчуваннÑм (%{default_branch}) міÑтить зміни Ñ– не може бути оновлена, тоді Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ неможливим. Інші гілки зі змінами автоматично ігноруютьÑÑ."
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "Якщо це відключено, то рівень доÑтупу буде залежати від дозволів кориÑтувача в проекті."
@@ -5116,11 +6029,8 @@ msgstr "Якщо це дозволено, доÑтуп до проектів бÑ
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "При викориÑтанні GitHub, ви побачите ÑтатуÑи конвеєрів Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð² Ñ– запитів на злиттÑ. %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "Якщо у Ð²Ð°Ñ ÑƒÐ¶Ðµ Ñ” файли, ви можете відправити Ñ—Ñ… за допомогою %{link_to_cli} нижче."
-
msgid "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>."
-msgstr "Якщо ваш HTTP-репозиторій не Ñ” публічним, додайте дані Ð´Ð»Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— до URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr "Якщо ваш HTTP-репозиторій не Ñ” публічним, додайте дані Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— до URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
msgid "ImageDiffViewer|2-up"
msgstr "2 поруч"
@@ -5132,13 +6042,13 @@ msgid "ImageDiffViewer|Swipe"
msgstr "Ðакладені (проведеннÑ)"
msgid "Impersonation has been disabled"
-msgstr ""
+msgstr "УоÑÐ¾Ð±Ð»ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ вимкнено"
msgid "Import"
msgstr "Імпорт"
msgid "Import CSV"
-msgstr ""
+msgstr "Імпортувати CSV"
msgid "Import Projects from Gitea"
msgstr "Імпортувати проекти з Gitea"
@@ -5159,13 +6069,13 @@ msgid "Import in progress"
msgstr "Імпорт триває"
msgid "Import issues"
-msgstr ""
+msgstr "Імпорт задач"
msgid "Import members"
-msgstr ""
+msgstr "Імпортувати учаÑників"
msgid "Import members from another project"
-msgstr ""
+msgstr "Імпортувати учаÑників з іншого проекту"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "Імпортувати кілька репозиторіїв, надіÑлавши файл маніфеÑту."
@@ -5174,7 +6084,7 @@ msgid "Import project"
msgstr "Імпорт проекту"
msgid "Import project members"
-msgstr ""
+msgstr "Імпортувати учаÑників проекту"
msgid "Import projects from Bitbucket"
msgstr "Імпортувати проекти з Bitbucket"
@@ -5203,12 +6113,30 @@ msgstr "Імпорт репозиторію"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "Підключити репозиторії із"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "Покращити дошки обговорень за допомогою верÑÑ–Ñ— GitLab Enterprise Edition."
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "Покращити ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°Ð¼Ð¸ з можливіÑÑ‚ÑŽ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð°Ð³Ð¸ задач за допомогою GitLab Enterprise Edition."
@@ -5224,23 +6152,32 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "Ðа наÑтупному кроці ви зможете вибрати проекти, Ñкі хочете імпортувати."
+msgid "Incidents"
+msgstr "Інциденти"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "Включити угоду про Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ñлуг та правила конфіденційноÑÑ‚Ñ–, Ñкі повинні прийнÑти вÑÑ– кориÑтувачі."
-msgid "Include merge request description"
+msgid "Include author name in notification email body"
msgstr ""
+msgid "Include merge request description"
+msgstr "Додайте Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на злиттÑ"
+
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "Якщо необхідно додайте ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача в URL: <code>https: //username@gitlab.company.com/group/project.git</code>."
-msgid "Includes an MVC structure to help you get started."
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
msgstr ""
+msgid "Includes an MVC structure to help you get started."
+msgstr "Включає Ñтруктуру MVC, щоб допомогти вам розпочати роботу."
+
msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
-msgstr ""
+msgstr "Включає Ñтруктуру MVC, Gemfile, Rakefile, а також багато іншого, щоб допомогти вам розпочати роботу."
msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
-msgstr ""
+msgstr "Включає Ñтруктуру MVC, mvnw Ñ– pom.xml, щоб допомогти вам розпочати роботу."
msgid "Incompatible Project"
msgstr "ÐеÑуміÑний проект"
@@ -5248,6 +6185,9 @@ msgstr "ÐеÑуміÑний проект"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "Вказує на те, чи може даний runner виконувати Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÐµÐ· тегів"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "Вбудований"
@@ -5257,8 +6197,17 @@ msgstr "Введіть ключі хоÑта вручну"
msgid "Input your repository URL"
msgstr "Введіть ваш URL репозиторію"
+msgid "Insert a quote"
+msgstr "Ð’Ñтавити цитату"
+
+msgid "Insert code"
+msgstr "Ð’Ñтавити код"
+
msgid "Insert suggestion"
-msgstr ""
+msgstr "Додати пропозицію"
+
+msgid "Insights"
+msgstr "СтатиÑтика (Insights)"
msgid "Install GitLab Runner"
msgstr "Ð’Ñтановити GitLab Runner"
@@ -5282,6 +6231,9 @@ msgstr "СтатиÑтика видимоÑÑ‚Ñ– інÑтанÑа"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "Цей інÑÑ‚Ð°Ð½Ñ Ð½Ðµ підтримує декілька Kubernetes-клаÑтерів"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "Інтеграції"
@@ -5292,7 +6244,7 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr "Зацікавлені Ñторони за бажаннÑм можуть навіть робити внеÑки шлÑхом Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð²."
msgid "Internal"
-msgstr ""
+msgstr "Внутрішній"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ â€” будь-Ñкий автентифікований кориÑтувач має доÑтуп до цієї групи та уÑÑ–Ñ… Ñ—Ñ— внутрішніх проектів."
@@ -5312,17 +6264,32 @@ msgstr "ПредÑтавлÑємо аналітику циклу"
msgid "Introducing Your Conversational Development Index"
msgstr ""
-msgid "Invitation"
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
msgstr ""
+msgid "Invalid input, please avoid emojis"
+msgstr "Ðекорректний ввід, будь лаÑка, уникайте Ñмайликів"
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
+msgid "Invitation"
+msgstr "ЗапрошеннÑ"
+
msgid "Invite"
msgstr "ЗапрошеннÑ"
msgid "Invite group"
-msgstr ""
+msgstr "ЗапроÑити групу"
msgid "Invite member"
-msgstr ""
+msgstr "ЗапроÑити учаÑника"
msgid "Invoke Count"
msgstr ""
@@ -5330,6 +6297,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Задача"
@@ -5342,6 +6312,9 @@ msgstr "Режим фокуÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð´Ð¾ÑˆÐºÐ¸ задач"
msgid "Issue events"
msgstr "Задачі"
+msgid "Issue template (optional)"
+msgstr "Шаблон задачі (необов’Ñзково)"
+
msgid "IssueBoards|Board"
msgstr "Дошка"
@@ -5349,19 +6322,19 @@ msgid "IssueBoards|Boards"
msgstr "Дошки"
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "Створити нову дошку"
msgid "IssueBoards|Delete board"
-msgstr ""
+msgstr "Видалити дошку"
msgid "IssueBoards|No matching boards found"
-msgstr ""
+msgstr "Ðе знайдено відповідних дошок"
msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
-msgstr ""
+msgstr "ДеÑкі з ваших дошок Ñ” прихованими, активуйте ліцензію, щоб побачити Ñ—Ñ… знову."
msgid "IssueBoards|Switch board"
-msgstr ""
+msgstr "Перемкнути дошку"
msgid "Issues"
msgstr "Задачі"
@@ -5372,7 +6345,10 @@ msgstr "Задачі можуть бути помилками, нагадуваÐ
msgid "Issues closed"
msgstr "Задачі закриті"
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr "Задачі, запити на злиттÑ, Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð´Ñƒ Ñ– коментарі."
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5400,7 +6376,7 @@ msgid "It must have a header row and at least two columns: the first column is t
msgstr ""
msgid "It's you"
-msgstr ""
+msgstr "Це ви"
msgid "Jaeger URL"
msgstr "URL-адреÑа Jaeger"
@@ -5417,14 +6393,32 @@ msgstr "Ñічень"
msgid "Job"
msgstr "ЗавданнÑ"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr "Ідентифікатор завданнÑ"
+
msgid "Job has been erased"
msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ Ñтерте"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
+msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ðµ. Перевірте runner'и."
+
+msgid "Job traces and artifacts"
msgstr ""
msgid "Job was retried"
-msgstr ""
+msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ перезапущене"
msgid "Jobs"
msgstr "ЗавданнÑ"
@@ -5453,6 +6447,9 @@ msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ Ñтерте"
msgid "Job|Keep"
msgstr "Залишити"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "Прокрутити вниз"
@@ -5466,9 +6463,21 @@ msgid "Job|The artifacts were removed"
msgstr "Ðртефакти були видалені"
msgid "Job|The artifacts will be removed"
+msgstr "Ðртефакти будуть видалені"
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ðµ, тому що цей проект не має жодних runner'ів призначених Ð´Ð»Ñ Ð½ÑŒÐ¾Ð³Ð¾."
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
msgstr ""
msgid "Jul"
@@ -5484,7 +6493,7 @@ msgid "June"
msgstr "червень"
msgid "Key (PEM)"
-msgstr ""
+msgstr "Ключ (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5493,7 +6502,7 @@ msgid "Kubernetes Cluster"
msgstr "КлаÑтер Kubernetes"
msgid "Kubernetes Clusters"
-msgstr ""
+msgstr "КлаÑтери Kubernetes"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "ÐŸÐµÑ€ÐµÐ²Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð»Ñ–Ð¼Ñ–Ñ‚Ñƒ чаÑу при Ñтворенні Kubernetes-клаÑтера; %{timeout}"
@@ -5513,9 +6522,15 @@ msgstr "Kubernetes налаштовано"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "СпоÑіб Інтеграції Kubernetes Ñк ÑервіÑа заÑтарів. %{deprecated_message_content} ваші Kubernetes-клаÑтери за допомогою нової Ñторінки <a href=\"%{url}\"/>КлаÑтери Kubernetes</a>"
+msgid "LDAP settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ LDAP"
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "Вимкнено"
@@ -5531,6 +6546,15 @@ msgstr "Випадаючий ÑпиÑок дій із мітками"
msgid "Label lists show all issues with the selected label."
msgstr "Ð’ ÑпиÑках на оÑнові міток відображаютьÑÑ Ð»Ð¸ÑˆÐµ Ñ‚Ñ– задачі, Ñкі мають вибрану мітку."
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} + %{remainingLabelCount} ще"
@@ -5561,6 +6585,9 @@ msgstr "ПеренеÑти мітку"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "ПеренеÑÐµÐ½Ð½Ñ %{labelTitle} на рівень групи зробить Ñ—Ñ— доÑтупною Ð´Ð»Ñ Ð²ÑÑ–Ñ… проектів в групі %{groupName}. ІÑнуючі проектні мітки із такими ж іменами будуть об'єднані. Дана Ð´Ñ–Ñ Ð½Ðµ може бути ÑкаÑована."
+msgid "Language"
+msgstr "Мова"
+
msgid "Large File Storage"
msgstr "Сховище великих файлів (LFS)"
@@ -5575,7 +6602,7 @@ msgid "Last Pipeline"
msgstr "ОÑтанній Конвеєр"
msgid "Last activity"
-msgstr ""
+msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ"
msgid "Last commit"
msgstr "ОÑтанній коміт"
@@ -5611,7 +6638,7 @@ msgid "Latest changes"
msgstr "ОÑтанні зміни"
msgid "Latest pipeline for this branch"
-msgstr ""
+msgstr "ОÑтанній конвеєр Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки"
msgid "Lead"
msgstr ""
@@ -5626,25 +6653,25 @@ msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про %{issue_boards_url}, щоб Ñтежити за задачами в кількох ÑпиÑках, викориÑтовуючи мітки, виконавців та етапи. Якщо вам чогоÑÑŒ не виÑтачає в дошках обговорень задач, Ñтворіть задачу на %{gitlab_issues_url}."
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Auto DevOps"
msgid "Learn more about Kubernetes"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Kubernetes"
msgid "Learn more about Web Terminal"
-msgstr ""
+msgstr "ДізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Веб-термінал"
+
+msgid "Learn more about approvals."
+msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про затвердженнÑ."
msgid "Learn more about custom project templates"
-msgstr ""
+msgstr "Докладніше про влаÑні шаблони проектів"
msgid "Learn more about group-level project templates"
-msgstr ""
-
-msgid "Learn more about incoming email addresses"
-msgstr ""
+msgstr "Докладніше про шаблони проектів на рівні групи"
-msgid "Learn more about protected branches"
-msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про захищені гілки"
+msgid "Learn more about signing commits"
+msgstr "Докладніше про підпиÑÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð²"
msgid "Learn more in the"
msgstr "ДізнайтеÑÑŒ більше"
@@ -5655,6 +6682,9 @@ msgstr "Детальніше в документації по розкладам
msgid "Leave"
msgstr "Вийти"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "Залишити групу"
@@ -5739,9 +6769,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "Ви збираєтеÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ ліцензію %{name} із цього проекту."
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "Ліцензії"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Показ обмежено тільки %d подією"
@@ -5761,6 +6797,9 @@ msgstr "СпиÑок ваших репозиторіїв Gitea"
msgid "List available repositories"
msgstr "СпиÑок доÑтупних репозиторіїв"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "Режим ÑпиÑку"
@@ -5783,10 +6822,10 @@ msgid "Loading..."
msgstr "ЗавантаженнÑ..."
msgid "Loading…"
-msgstr ""
+msgstr "ЗавантаженнÑ…"
msgid "Localization"
-msgstr ""
+msgstr "Регіональні налаштуваннÑ"
msgid "Lock"
msgstr "Блокувати"
@@ -5818,15 +6857,36 @@ msgstr "Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð¾Ð¶Ðµ бути заÑтоÑоване до коÐ
msgid "Login with smartcard"
msgstr "Вхід за допомогою Ñмарт-картки"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "Логи"
+msgid "MRApprovals|Approved by"
+msgstr "Затверджено"
+
+msgid "MRApprovals|Approvers"
+msgstr "Затверджуючі оÑоби"
+
+msgid "MRApprovals|Pending approvals"
+msgstr "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð² очікуванні"
+
+msgid "MRDiff|Show changes only"
+msgstr "Показувати лише зміни"
+
+msgid "MRDiff|Show full file"
+msgstr "Показувати файл повніÑÑ‚ÑŽ"
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Зробіть кожного учаÑника команди більш продуктивним незалежно від його міÑцезнаходженнÑ. GitLab Geo Ñтворює копії \"тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ\" вашого GitLab Ñервера, щоб Ñкоротити Ñ‡Ð°Ñ Ð´Ð»Ñ ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ з великих репозиторіїв."
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "ПереконайтеÑÑ, що ви ввійшли за допомогою облікового запиÑу, Ñкому належать проекти, що потрібно імпортувати."
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñми Git за допомогою детального контролю доÑтупу збереже ваш код в безпеці. Виконуйте переглÑд коду та покращуйте Ñпівпрацю за допомогою запитів на злиттÑ. Кожен проект може також мати трекер задач та вікі."
@@ -5855,10 +6915,7 @@ msgid "Manage project labels"
msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ°Ð¼Ð¸ проекту"
msgid "Manage two-factor authentication"
-msgstr ""
-
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "Керуйте членÑтвом у вашій групі додаючи ще один рівень безпеки із SAML."
+msgstr "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾ÑŽ автентифікацією"
msgid "Manifest"
msgstr "МаніфеÑÑ‚"
@@ -5866,6 +6923,9 @@ msgstr "МаніфеÑÑ‚"
msgid "Manifest file import"
msgstr "Імпортувати файл маніфеÑту"
+msgid "Manual job"
+msgstr "Ручне завданнÑ"
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Зв’Ñзати обліковий Ð·Ð°Ð¿Ð¸Ñ FogBugz з кориÑтувачем GitLab"
@@ -5888,49 +6948,46 @@ msgid "Mark todo as done"
msgstr "Відмітити Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð¸Ð¼"
msgid "Markdown"
+msgstr "Markdown"
+
+msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr "Markdown увімкнено"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "Додати ненумерований ÑпиÑок"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "Додати поÑиланнÑ"
+msgid "Maven Metadata"
+msgstr "Maven-метадані"
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "Додати нумерований ÑпиÑок"
+msgid "Max access level"
+msgstr "МакÑимальний рівень доÑтупу"
-msgid "MarkdownToolbar|Add a table"
-msgstr "Додати таблицю"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add a task list"
-msgstr "Додати ÑпиÑок завдань"
+msgid "Maximum artifacts size (MB)"
+msgstr "МакÑимальний розмір артефактів (МБ)"
-msgid "MarkdownToolbar|Add bold text"
-msgstr "Додати жирний текÑÑ‚"
+msgid "Maximum attachment size (MB)"
+msgstr "МакÑимальний розмір Ð²ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ (МБ)"
-msgid "MarkdownToolbar|Add italic text"
-msgstr "Додати курÑивний текÑÑ‚"
+msgid "Maximum capacity"
+msgstr ""
-msgid "MarkdownToolbar|Go full screen"
-msgstr "Повний екран"
+msgid "Maximum delay (Minutes)"
+msgstr "МакÑимальна затримка (хвилини)"
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "Ð’Ñтавити цитату"
-
-msgid "MarkdownToolbar|Insert code"
-msgstr "Ð’Ñтавити код"
+msgid "Maximum job timeout"
+msgstr "МакÑимальний Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ"
-msgid "Maven Metadata"
-msgstr "Maven-метадані"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
-msgid "Max access level"
-msgstr "МакÑимальний рівень доÑтупу"
+msgid "Maximum push size (MB)"
+msgstr "МакÑимальний розмір Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ (МБ)"
-msgid "Maximum job timeout"
-msgstr "МакÑимальний Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "травень"
@@ -5948,33 +7005,45 @@ msgid "Members"
msgstr "КориÑтувачі"
msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
-msgstr ""
+msgstr "УчаÑники можуть будуть додані <i>Керівниками</i> або <i>ВлаÑниками</i> проекту"
msgid "Members of <strong>%{project_name}</strong>"
-msgstr ""
-
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "УчаÑники будуть перенаправлені Ñюди, коли будуть заходити до вашої групи. Отримайте його від Ñвого провайдера ідентифікації, де вона також може називатиÑÑ \"SSO Service Location\", \"SAML Token Issuance Endpoint\", або \"SAML 2.0/W-Federation URL\"."
+msgstr "УчаÑники <strong>%{project_name}</strong>"
msgid "Merge Request"
msgstr "Запит на злиттÑ"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "Запити на злиттÑ"
msgid "Merge Requests created"
msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ»Ð¾ Ñтворено"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ-злиттÑ"
+
+msgid "Merge commit with semi-linear history"
msgstr ""
msgid "Merge events"
msgstr "Події злиттÑ"
msgid "Merge immediately"
-msgstr ""
+msgstr "Злити негайно"
msgid "Merge in progress"
+msgstr "Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð² процеÑÑ–"
+
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
msgstr ""
msgid "Merge request"
@@ -5983,6 +7052,9 @@ msgstr "Запит на злиттÑ"
msgid "Merge request approvals"
msgstr "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на злиттÑ"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "Запити на злиттÑ"
@@ -5990,31 +7062,31 @@ msgid "Merge requests are a place to propose changes you've made to a project an
msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ â€” це ÑпоÑіб запропонувати Ñвої зміни до проекту Ñ– обговорити Ñ—Ñ… із іншими"
msgid "Merge when pipeline succeeds"
-msgstr ""
+msgstr "Злити, коли конвеєр уÑпішно завершитьÑÑ"
msgid "MergeRequests|Add a reply"
-msgstr ""
+msgstr "Додати відповідь"
msgid "MergeRequests|An error occurred while saving the draft comment."
msgstr "Виникла помилка під Ñ‡Ð°Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡ÐµÑ€Ð½ÐµÑ‚ÐºÐ¸ коментарÑ."
msgid "MergeRequests|Discussion stays resolved"
-msgstr ""
+msgstr "ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð»Ð¸ÑˆÐ°Ñ”Ñ‚ÑŒÑÑ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð¸Ð¼"
msgid "MergeRequests|Discussion stays unresolved"
-msgstr ""
+msgstr "ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð»Ð¸ÑˆÐ°Ñ”Ñ‚ÑŒÑÑ Ð½ÐµÐ²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð¸Ð¼"
msgid "MergeRequests|Discussion will be resolved"
-msgstr ""
+msgstr "ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ðµ вирішеним"
msgid "MergeRequests|Discussion will be unresolved"
-msgstr ""
+msgstr "ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ðµ невирішеним"
msgid "MergeRequests|Jump to next unresolved discussion"
-msgstr ""
+msgstr "Перейти до наÑтупного невирішеного обговореннÑ"
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "ВідповіÑти..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr "Вирішити це Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð² новій задачі"
@@ -6032,38 +7104,41 @@ msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr "ПереглÑнути замінений файл Ñтаном на %{commitId}"
msgid "MergeRequests|commented on commit %{commitLink}"
-msgstr ""
+msgstr "прокоментував (-ла) коміт %{commitLink}"
msgid "MergeRequests|started a discussion"
-msgstr ""
+msgstr "розпочав (-ла) обговореннÑ"
msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %{linkStart}Ñтарої верÑÑ–Ñ— порівнÑÐ½Ð½Ñ (diff)%{linkEnd}"
msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %{linkStart}порівнÑÐ½Ð½Ñ (diff)%{linkEnd}"
msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ñтарілих змін в коміті %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) коміту %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart} Ð¾Ð¿Ð¸Ñ Ð·Ð¼Ñ–Ð½ÐµÐ½Ð¾ %{descriptionChangedTimes} раз(а,ів) %{timeDifferenceMinutes}%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr "Помилка при заванаженні повного порівнÑннÑ. Будь лаÑка, Ñпробуйте знову."
+
msgid "MergeRequest|Filter files"
msgstr "Фільтр файлів"
msgid "MergeRequest|No files found"
msgstr "Файлів не знайдено"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "Злито"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "ПовідомленнÑ"
@@ -6080,7 +7155,7 @@ msgid "Metrics and profiling"
msgstr "Метрики та профілюваннÑ"
msgid "Metrics for environment"
-msgstr ""
+msgstr "Метрики Ð´Ð»Ñ Ñередовища"
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr "Перевірте документацію CI/CD щодо Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð² Ñередовищі"
@@ -6089,10 +7164,10 @@ msgid "Metrics|Create metric"
msgstr "Створити метрику"
msgid "Metrics|Delete metric"
-msgstr ""
+msgstr "Видалити метрику"
msgid "Metrics|Delete metric?"
-msgstr ""
+msgstr "Видалити метрику?"
msgid "Metrics|Edit metric"
msgstr "Редагувати метрику"
@@ -6104,7 +7179,7 @@ msgid "Metrics|For grouping similar metrics"
msgstr "Ð”Ð»Ñ Ð³Ñ€ÑƒÐ¿ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð´Ñ–Ð±Ð½Ð¸Ñ… метрик"
msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
-msgstr ""
+msgstr "Мітка оÑÑ– y (зазвичай Ð¾Ð´Ð¸Ð½Ð¸Ñ†Ñ Ð²Ð¸Ð¼Ñ–Ñ€ÑŽÐ²Ð°Ð½Ð½Ñ). Ð’Ñ–ÑÑŒ x завжди позначає чаÑ."
msgid "Metrics|Learn about environments"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ñередовища"
@@ -6121,14 +7196,17 @@ msgstr "Ðова метрика"
msgid "Metrics|No deployed environments"
msgstr "Ðемає розгорнутих Ñередовищ"
-msgid "Metrics|PromQL query is valid"
+msgid "Metrics|Not enough data to display"
msgstr ""
+msgid "Metrics|PromQL query is valid"
+msgstr "Запит PromQL Ñ” дійÑним"
+
msgid "Metrics|Prometheus Query Documentation"
msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ Ð¿Ð¾ запитам Prometheus"
-msgid "Metrics|System"
-msgstr "СиÑтема"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про Ñередовища. Будь лаÑка, Ñпробуйте ще раз"
@@ -6140,7 +7218,7 @@ msgid "Metrics|There was an error getting environments information."
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про Ñередовища."
msgid "Metrics|There was an error trying to validate your query"
-msgstr ""
+msgstr "Помилка при перевірці вашого запиту"
msgid "Metrics|There was an error while retrieving metrics"
msgstr "ТрапилаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¼ÐµÑ‚Ñ€Ð¸Ðº"
@@ -6164,11 +7242,17 @@ msgid "Metrics|Y-axis label"
msgstr "Ðазва оÑÑ– Y"
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
-msgstr ""
+msgstr "Ви збираєтеÑÑ Ð¾Ñтаточно видалити цю метрику. Це не можна відмінити."
msgid "Metrics|e.g. Throughput"
msgstr "напр. пропуÑкна здатніÑÑ‚ÑŒ"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "Етап"
@@ -6214,6 +7298,9 @@ msgstr "ПеренеÑÐµÐ½Ð½Ñ %{milestone} на рівень групи зроÐ
msgid "Milestones|This action cannot be reversed."
msgstr "Цю дію не можна ÑкаÑувати."
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "Віддзеркалити репозиторій"
@@ -6232,6 +7319,9 @@ msgstr "Віддзеркалені репозиторії"
msgid "Mirroring repositories"
msgstr "Ð’Ñ–Ð´Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð²"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "не додаÑте SSH ключ"
@@ -6241,17 +7331,20 @@ msgstr "СкаÑувати"
msgid "Modal|Close"
msgstr "Закрити"
+msgid "Modify commit message"
+msgstr "Змінити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ"
+
msgid "Modify commit messages"
-msgstr ""
+msgstr "Змінити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð²"
msgid "Modify merge commit"
-msgstr ""
+msgstr "Змінити коміт-злиттÑ"
msgid "Monday"
-msgstr ""
+msgstr "Понеділок"
msgid "Monitor your errors by integrating with Sentry"
-msgstr ""
+msgstr "Моніторинг ваших помилок шлÑхом інтеграції із Sentry"
msgid "Monitoring"
msgstr "Моніторинг"
@@ -6274,9 +7367,15 @@ msgstr "Детальніше"
msgid "More information is available|here"
msgstr "тут"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Ðайбільше в обраних"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "ПереміÑтити"
@@ -6286,6 +7385,12 @@ msgstr "ПереміÑтити задачу"
msgid "Multiple issue boards"
msgstr "Кілька дошок обговореннÑ"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "Ім’Ñ"
@@ -6298,6 +7403,12 @@ msgstr "Ðазвіть ваш індивідуальний ключ за допÐ
msgid "Name:"
msgstr "Ім’Ñ:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "ÐайменуваннÑ, видиміÑÑ‚ÑŒ"
@@ -6314,7 +7425,7 @@ msgid "Nav|Sign out and sign in with a different account"
msgstr "Вийти Ñ– зайти під іншим обліковим запиÑом"
msgid "Need help?"
-msgstr ""
+msgstr "Потрібна допомога?"
msgid "Network"
msgstr "Мережа"
@@ -6329,7 +7440,7 @@ msgid "New Application"
msgstr "Ðовий додаток"
msgid "New Environment"
-msgstr ""
+msgstr "Ðове Ñередовище"
msgid "New Group"
msgstr "Ðова група"
@@ -6348,10 +7459,13 @@ msgid "New Label"
msgstr "Ðова мітка"
msgid "New Milestone"
-msgstr ""
+msgstr "Ðовий етап"
msgid "New Pages Domain"
-msgstr ""
+msgstr "Ðовий домен Pages"
+
+msgid "New Password"
+msgstr "Ðовий Пароль"
msgid "New Pipeline Schedule"
msgstr "Ðовий розклад Конвеєра"
@@ -6359,20 +7473,20 @@ msgstr "Ðовий розклад Конвеєра"
msgid "New Snippet"
msgstr "Ðовий Ñніпет"
-msgid "New Snippets"
-msgstr "Ðові Ñніпети"
-
msgid "New branch"
msgstr "Ðова гілка"
msgid "New branch unavailable"
msgstr "Ðова гілка недоÑтупна"
+msgid "New deploy key"
+msgstr "Ðовий ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
+
msgid "New directory"
msgstr "Ðовий каталог"
msgid "New environment"
-msgstr ""
+msgstr "Ðове Ñередовище"
msgid "New epic"
msgstr "Ðовий епік"
@@ -6383,6 +7497,9 @@ msgstr "Ðовий файл"
msgid "New group"
msgstr "Ðова група"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "Ðова ідентифікаціÑ"
@@ -6396,7 +7513,7 @@ msgid "New merge request"
msgstr "Ðовий запит на злиттÑ"
msgid "New milestone"
-msgstr ""
+msgstr "Ðовий етап"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "Ðові конвеєри ÑкаÑують Ñтарі, що очікують на тій же гілці"
@@ -6404,6 +7521,9 @@ msgstr "Ðові конвеєри ÑкаÑують Ñтарі, що очікую
msgid "New project"
msgstr "Ðовий проект"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "Ðовий Розклад"
@@ -6416,20 +7536,29 @@ msgstr "Ðова підгрупа"
msgid "New tag"
msgstr "Ðовий тег"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "Ðовий..."
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "ÐÑ–"
+msgid "No %{providerTitle} repositories available to import"
+msgstr "Ðемає репозиторіїв %{providerTitle} Ð´Ð»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ"
+
msgid "No Label"
msgstr "Без Мітки"
-msgid "No activities found"
-msgstr ""
+msgid "No Tag"
+msgstr "Ðемає тегів"
-msgid "No assignee"
-msgstr "Ðемає виконавцÑ"
+msgid "No activities found"
+msgstr "Ðе знайднено активноÑтей"
msgid "No branches found"
msgstr "Гілок не знайдено"
@@ -6438,7 +7567,7 @@ msgid "No changes"
msgstr "Ðемає змін"
msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
-msgstr ""
+msgstr "Ðемає змін між %{ref_start}%{source_branch}%{ref_end} та %{ref_start}%{target_branch}%{ref_end}"
msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr "Ðеможливо з'єднатиÑÑŒ із Ñервером Gitaly, будь лаÑка, перевірте логи!"
@@ -6453,13 +7582,13 @@ msgid "No credit card required."
msgstr "Ðе потрібна кредитна картка."
msgid "No details available"
-msgstr ""
+msgstr "Ðемає деталей Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
msgid "No due date"
msgstr "Ðемає"
-msgid "No errors to display"
-msgstr ""
+msgid "No errors to display."
+msgstr "Ðемає помилок Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ."
msgid "No estimate or time spent"
msgstr "Ðемає запланованого або витраченого чаÑу"
@@ -6468,7 +7597,7 @@ msgid "No file chosen"
msgstr "Файл не вибрано"
msgid "No file selected"
-msgstr ""
+msgstr "Файл не вибраний"
msgid "No files found."
msgstr "Ðе знайдено жодного файлу."
@@ -6476,13 +7605,22 @@ msgstr "Ðе знайдено жодного файлу."
msgid "No issues for the selected time period."
msgstr "Ðемає задач за вибраний період чаÑу."
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "Ðемає міток з таким іменем або опиÑом"
msgid "No license. All rights reserved"
msgstr "Ðемає ліцензії. Ð’ÑÑ– права захищені"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
+msgstr "Ðемає відповідних результатів"
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
msgid "No merge requests for the selected time period."
@@ -6495,13 +7633,13 @@ msgid "No messages were logged"
msgstr "Ðемає повідомлень у журналі"
msgid "No milestones to show"
-msgstr ""
+msgstr "Ðемає етапів Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ"
msgid "No other labels with such name or description"
msgstr "Ðемає інших міток з таким іменем або опиÑом"
msgid "No preview for this file type"
-msgstr ""
+msgstr "Попереднього переглÑду Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ типу файлів немає"
msgid "No prioritised labels with such name or description"
msgstr "Ðемає пріоритетних міток з таким іменем або опиÑом"
@@ -6522,7 +7660,7 @@ msgid "No schedules"
msgstr "Ðемає розкладів"
msgid "No start date"
-msgstr ""
+msgstr "Ðемає дати початку"
msgid "No, directly import the existing email addresses and usernames."
msgstr "ÐÑ–, безпоÑередньо імпортувати Ñ–Ñнуючі адреÑи електронної пошти та імена кориÑтувачів."
@@ -6533,9 +7671,6 @@ msgstr "Вузли"
msgid "None"
msgstr "Ðемає"
-msgid "Not allowed to merge"
-msgstr "Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ допуÑкаєтьÑÑ"
-
msgid "Not available"
msgstr "ÐедоÑтупний"
@@ -6551,15 +7686,21 @@ msgstr "Ðе конфіденційно"
msgid "Not enough data"
msgstr "ÐедоÑтатньо даних"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "Пізніше"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "Майте на увазі, що гілка master захищена автоматично. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
-msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgid "Not started"
msgstr ""
+msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
+msgstr "Зауважте, що це Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ надіÑлано на %{mail_to_invite_email}, але ви увійшли Ñк %{link_to_current_user} з електронною поштою %{mail_to_current_user}."
+
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
msgstr "Примітка: Ñк адмініÑтратор ви можете налаштувати %{github_integration_link}, що дозволить входити через GitHub Ñ– підключати репозиторії без ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾ÑобиÑтого токену доÑтупу."
@@ -6591,10 +7732,10 @@ msgid "Notification events"
msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ події"
msgid "Notification setting"
-msgstr ""
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñповіщень"
msgid "Notification setting - %{notification_title}"
-msgstr ""
+msgstr "Параметр ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ - %{notification_title}"
msgid "NotificationEvent|Close issue"
msgstr "Задача закрита"
@@ -6668,6 +7809,9 @@ msgstr "лиÑтопад"
msgid "OK"
msgstr "OK"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "жовт."
@@ -6680,6 +7824,12 @@ msgstr "Фільтр"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "ПіÑÐ»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ репозиторії можуть бути віддзеркалені через SSH. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "Ще один елемент"
@@ -6687,6 +7837,9 @@ msgstr[1] "Ще %d елементи"
msgstr[2] "Ще %d елементів"
msgstr[3] "Ще %d елементів"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Один або декілька ваших проектів Bitbucket не можна імпортувати безпоÑередньо в GitLab, оÑкільки вони викориÑтовують Subversion або Mercurial Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŽ верÑій заміÑÑ‚ÑŒ Git."
@@ -6696,6 +7849,12 @@ msgstr "Один або декілька ваших проектів Google Code
msgid "Only admins"
msgstr "Тільки Ðдміни"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "Віддзеркалювати лише захищені гілки"
@@ -6703,13 +7862,13 @@ msgid "Only policy:"
msgstr ""
msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
-msgstr ""
+msgstr "Продовжуйте тільки Ñкщо ви довірÑєте %{idp_url} контроль над входом до вашого облікового запиÑу GitLab."
msgid "Only project members can comment."
msgstr "Тільки учаÑники проекту можуть залишати коментарі."
msgid "Only project members will be imported. Group members will be skipped."
-msgstr ""
+msgstr "Лише учаÑника проекту будуть імпортовані. УчаÑники групи будуть пропущені."
msgid "Oops, are you sure?"
msgstr "Ой, а ви впевнені?"
@@ -6718,13 +7877,13 @@ msgid "Open"
msgstr "Відкриті"
msgid "Open Documentation"
-msgstr ""
+msgstr "Відкрити документацію"
msgid "Open comment type dropdown"
-msgstr ""
+msgstr "Випадаючий ÑпиÑок типу коментарів"
msgid "Open errors"
-msgstr ""
+msgstr "Відкрити помилки"
msgid "Open in Xcode"
msgstr "Відкрити в Xcode"
@@ -6732,6 +7891,9 @@ msgstr "Відкрити в Xcode"
msgid "Open projects"
msgstr "Відкриті проекти"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "Розгорніть бічну панель"
@@ -6759,13 +7921,28 @@ msgstr "Операції"
msgid "Operations Dashboard"
msgstr "Панель ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñми"
+msgid "Operations Settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ð¹"
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Додайте проект до панелі керуваннÑ"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr "Панель ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñми міÑтить інформацію про Ñтан кожного з проектів разом зі Ñтаном його конвеєрів та попереджень."
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
+msgstr "Ðеможливо додати %{invalidProjects}. Панель ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñми доÑтупна Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ñ–Ñ‡Ð½Ð¸Ñ… та приватних проектів в групах із тарифним планом Gold."
+
+msgid "Optional"
msgstr ""
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
@@ -6786,9 +7963,6 @@ msgstr "Інші мітки"
msgid "Other information"
msgstr "Інша інформаціÑ"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "Ð’ іншому разі рекомендуєтьÑÑ Ð¿Ð¾Ñ‡Ð°Ñ‚Ð¸ з одного з наведених нижче варіантів."
-
msgid "Outbound requests"
msgstr "Вихідні запити"
@@ -6810,14 +7984,20 @@ msgstr "Пакет був видалений"
msgid "Packages"
msgstr "Пакети"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Сторінки"
msgid "Pages Domain"
-msgstr ""
+msgstr "Домен Pages"
msgid "Pages Domains"
-msgstr ""
+msgstr "Домени Pages"
msgid "Pagination|Last »"
msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Â»"
@@ -6832,10 +8012,7 @@ msgid "Pagination|« First"
msgstr "« Перша"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "Параметр"
msgid "Part of merge request changes"
msgstr "ЧаÑтина змін у запиті на злиттÑ"
@@ -6843,18 +8020,27 @@ msgstr "ЧаÑтина змін у запиті на злиттÑ"
msgid "Password"
msgstr "Пароль"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
+msgstr "ПроÑтрочені"
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
msgid "Paste epic link"
-msgstr ""
+msgstr "Ð’Ñтавити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° епік"
msgid "Paste issue link"
-msgstr ""
+msgstr "Ð’Ñтавити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° задачу"
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "Ð’Ñтавте Ñвій відкритий ключ SSH, Ñкий зазвичай знаходитьÑÑ Ñƒ файлі '~/.ssh/id_rsa.pub' Ñ– починаєтьÑÑ Ð· 'ssh-rsa'. Ðе викориÑтовуйте Ñвій приватний ключ SSH."
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "ШлÑÑ…, передача, видаленнÑ"
@@ -6864,6 +8050,9 @@ msgstr "ШлÑÑ…:"
msgid "Pause"
msgstr "Призупинити"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "Призупинені Runner'и не приймають нові завданнÑ"
@@ -6882,6 +8071,9 @@ msgstr "ÐžÐ¿Ñ‚Ð¸Ð¼Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾Ð´ÑƒÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚Ñ–"
msgid "Permissions"
msgstr "Права доÑтупу"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "Дозволи, LFS, 2FA"
@@ -6889,29 +8081,32 @@ msgid "Personal Access Token"
msgstr "Токену перÑонального доÑтупу"
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
-msgstr ""
+msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿ÐµÑ€Ñональних проектів не дозволено. Будь лаÑка, звернітьÑÑ Ð´Ð¾ Ñвого адмініÑтратор із питаннÑми"
msgid "Pick a name"
-msgstr ""
+msgstr "Виберіть ім'Ñ"
msgid "Pipeline"
msgstr "Конвеєр"
-msgid "Pipeline Health"
-msgstr "Стан Конвеєра"
-
msgid "Pipeline Schedule"
msgstr "Розклад Конвеєра"
msgid "Pipeline Schedules"
msgstr "Розклади Конвеєрів"
+msgid "Pipeline minutes quota"
+msgstr "Квота хвилин Ð´Ð»Ñ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ñ–Ð²"
+
msgid "Pipeline quota"
msgstr "Квота на конвеєри"
msgid "Pipeline triggers"
msgstr "Тригери конвеєру"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "Ðевдалі:"
@@ -6960,11 +8155,17 @@ msgstr "Змінні"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Спеціальні"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr "Коміт: %{ci_status}"
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr "Конвеєр: %{ci_status}"
+
msgid "Pipelines"
msgstr "Конвеєри"
msgid "Pipelines charts"
-msgstr "Чарти Конвеєрів"
+msgstr "СтатиÑтика конвеєрів"
msgid "Pipelines for last month"
msgstr "Конвеєри за оÑтанній міÑÑць"
@@ -6975,6 +8176,12 @@ msgstr "Конвеєри за оÑтанній тиждень"
msgid "Pipelines for last year"
msgstr "Конвеєри за оÑтанній рік"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr "Ðеобхідно налаштувати конвеєри щоб увімкнути цю функцію."
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "Виконуйте збірки із впевненіÑÑ‚ÑŽ"
@@ -7017,11 +8224,8 @@ msgstr "Цей проект в даний Ñ‡Ð°Ñ Ð½Ðµ налаштований Ð
msgid "Pipeline|Commit"
msgstr "Коміт"
-msgid "Pipeline|Create for"
-msgstr "Створити длÑ"
-
-msgid "Pipeline|Create pipeline"
-msgstr "Створити конвеєр"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "ТриваліÑÑ‚ÑŒ"
@@ -7035,6 +8239,9 @@ msgstr "Конвеєр"
msgid "Pipeline|Run Pipeline"
msgstr "ЗапуÑтити Конвеєр"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "Пошук гілки"
@@ -7062,9 +8269,21 @@ msgstr "Зараз ви зупинете конвеєр %{pipelineId}."
msgid "Pipeline|all"
msgstr "вÑÑ–"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "уÑпіх"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "зі Ñтадією"
@@ -7081,7 +8300,7 @@ msgid "Play"
msgstr "Відтворити"
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
-msgstr ""
+msgstr "Будь лаÑка, %{link_to_register} або %{link_to_sign_in} щоб прокоментувати"
msgid "Please accept the Terms of Service before continuing."
msgstr "Будь лаÑка, Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð¹Ð¼Ñ–Ñ‚ÑŒ умови Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ñлуг."
@@ -7089,15 +8308,33 @@ msgstr "Будь лаÑка, Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð¹Ð¼Ñ–Ñ‚ÑŒ ум
msgid "Please choose a group URL with no special characters."
msgstr "Будь лаÑка задайте URL групи без Ñпеціальних Ñимволів."
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "Будь лаÑка Ñконвертуйте Ñ—Ñ… в %{link_to_git} Ñ– виконайте знову %{link_to_import_flow}."
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "Будь лаÑка Ñконвертуйте Ñ—Ñ… в Git на Google Code, Ñ– виконайте знову %{link_to_import_flow}."
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr "Будь лаÑка, Ñтворіть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача лише із буквено-цифрових Ñимволів."
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr "Будь лаÑка, введіть невід'ємне чиÑло"
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr "Будь лаÑка, введіть чиÑло більше за %{number} (із налаштувань проекту)"
+
+msgid "Please enter a valid number"
+msgstr "Будь лаÑка, введіть дійÑний номер"
+
msgid "Please fill in a descriptive name for your group."
msgstr "Введіть опиÑове ім'Ñ Ð³Ñ€ÑƒÐ¿Ð¸."
@@ -7107,9 +8344,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "Зверніть увагу, що Ñ†Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð° не Ñ” чаÑтиною GitLab, Ñ– ви повинні впевнитиÑÑ Ñƒ Ñ—Ñ— безпеці, перш ніж надавати доÑтуп."
+msgid "Please provide a name"
+msgstr "Будь лаÑка, задайте ім’Ñ"
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr "Будь лаÑка, виберіть Ñ– додайте учаÑника"
+
msgid "Please select at least one filter to see results"
msgstr "Будь лаÑка виберіть хоча б один фільтр, щоб побачити результати"
+msgid "Please set a new password before proceeding."
+msgstr "Будь лаÑка, вÑтановіть пароль перед продовженнÑм."
+
msgid "Please solve the reCAPTCHA"
msgstr "Будь лаÑка, пройдіть reCAPTCHA"
@@ -7117,11 +8366,14 @@ msgid "Please try again"
msgstr "Будь лаÑка, Ñпробуйте ще раз"
msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
+msgstr "Будь лаÑка, оновіть PostgreSQL до верÑÑ–Ñ— 9.6 або вище. Стан реплікації не може бути доÑтовірно визначений у поточній верÑÑ–Ñ—."
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "Будь лаÑка, викориÑтовуйте цю форму, щоб повідомлÑти GitLab про кориÑтувачів, Ñкі Ñтворюють задачі Ñ– коментарі зі Ñпамом, або поводÑÑ‚ÑŒÑÑ Ð½ÐµÐ²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾."
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "Будь лаÑка, почекайте поки ми з’єднуємоÑÑ Ñ–Ð· вашим репозиторієм. Оновлюйте Ñторінку за бажаннÑм."
@@ -7131,18 +8383,33 @@ msgstr "Будь лаÑка, почекайте поки ми імпортуєм
msgid "Preferences"
msgstr "ÐалаштуваннÑ"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "Тема навігації"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr "Ð¦Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ñ” екÑпериментальною Ñ– переклади ще не завершені"
+
msgid "Press Enter or click to search"
msgstr "Ð”Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ натиÑніть Enter або клікніть"
msgid "Prevent adding new members to project membership within this group"
msgstr "Заборонити Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… учаÑників до членÑтва в цій групі"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "Попередній переглÑд"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "Попередній переглÑд кориÑного навантаженнÑ"
@@ -7162,7 +8429,7 @@ msgid "Prioritized label"
msgstr "Пріоритетні мітки"
msgid "Private"
-msgstr ""
+msgstr "Приватний"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "Приватний — доÑтуп до проекту повинен надаватиÑÑ ÐºÐ¾Ð¶Ð½Ð¾Ð¼Ñƒ кориÑтувачеві."
@@ -7170,6 +8437,9 @@ msgstr "Приватний — доÑтуп до проекту повинен Ð
msgid "Private - The group and its projects can only be viewed by members."
msgstr "Приватна — цю групу та Ñ—Ñ— проекти можуть бачити тільки Ñ—Ñ— кориÑтувачі."
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "Приватні проекти можуть бути Ñтворені у вашому перÑональному проÑторі імен з:"
@@ -7186,16 +8456,16 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr "Ви збираєтеÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача %{currentUsernameBold} на %{newUsernameBold}. Профіль та проекти будуть перенаправлÑтиÑÑ Ð½Ð° проÑÑ‚Ñ–Ñ€ імен %{newUsername}, але таке Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡Ð¸Ñ‚ÑŒÑÑ, коли проÑÑ‚Ñ–Ñ€ імен %{currentUsername} буде зареєÑтровано на іншого кориÑтувача або групу. Будь лаÑка, оновіть віддалені адреÑи в репозиторіÑÑ… Git Ñкомога швидше."
msgid "Profiles|@username"
-msgstr ""
+msgstr "@ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
msgid "Profiles|Account scheduled for removal."
msgstr "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð¿Ð»Ð°Ð½Ð¾Ð²Ð°Ð½Ð¸Ð¹ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ."
msgid "Profiles|Activate signin with one of the following services"
-msgstr ""
+msgstr "Ðктивуйте вхід за допомогою однієї з наÑтупних Ñлужб"
msgid "Profiles|Active"
-msgstr ""
+msgstr "Ðктивний"
msgid "Profiles|Add key"
msgstr "Додати ключ"
@@ -7213,28 +8483,28 @@ msgid "Profiles|Change username"
msgstr "Змінити ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "Зміна імені кориÑтувача може мати небажані побічні ефекти."
msgid "Profiles|Choose file..."
msgstr "Вибрати файл..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
-msgstr ""
+msgstr "Виберіть Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ внеÑків до приватних репозиторіїв у вашому публічному профілі без інформації про проекти, репозиторії або організації"
msgid "Profiles|City, country"
-msgstr ""
+msgstr "МіÑто, країна"
msgid "Profiles|Clear status"
msgstr "ОчиÑтити ÑтатуÑ"
msgid "Profiles|Click on icon to activate signin with one of the following services"
-msgstr ""
+msgstr "Клікніть на іконку, щоб активувати вхід за допомогою одного із наÑтупних ÑервіÑів"
msgid "Profiles|Connect"
-msgstr ""
+msgstr "Приєднати"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "Підключені облікові запиÑи"
msgid "Profiles|Current path: %{path}"
msgstr "Поточний шлÑÑ…: %{path}"
@@ -7255,7 +8525,7 @@ msgid "Profiles|Deleting an account has the following effects:"
msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу неÑе наÑтупні наÑлідки:"
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "Від'єднати"
msgid "Profiles|Do not show on profile"
msgstr "Ðе відображати у профілі"
@@ -7267,10 +8537,10 @@ msgid "Profiles|Edit Profile"
msgstr "Редагувати профіль"
msgid "Profiles|Enter your name, so people you know can recognize you"
-msgstr ""
+msgstr "Введіть ваше ім'Ñ, щоб люди, Ñких ви знаєте, могли Ð²Ð°Ñ ÑƒÐ¿Ñ–Ð·Ð½Ð°Ñ‚Ð¸"
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
-msgstr ""
+msgstr "Підвищити рівень безпеки вашого облікового запиÑу за допомогою ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації (2FA)"
msgid "Profiles|Invalid password"
msgstr "Ðеправильний пароль"
@@ -7309,37 +8579,37 @@ msgid "Profiles|Set new profile picture"
msgstr "Ð’Ñтановити нове Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ"
msgid "Profiles|Social sign-in"
-msgstr ""
+msgstr "Вхід через Ñоціальні мережі"
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr "ДеÑкі параметри недоÑтупні Ð´Ð»Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¸Ñ… запиÑів LDAP"
msgid "Profiles|Tell us about yourself in fewer than 250 characters"
-msgstr ""
+msgstr "Розкажіть про Ñебе в межах 250 Ñимволів"
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "МакÑимальний розмір файлу 200КБ."
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "Це не Ñхоже на публічниц ключ SSH. Ви впевнені, що хочете його додати?"
msgid "Profiles|This email will be displayed on your public profile"
-msgstr ""
+msgstr "Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа буде відображатиÑÑ Ñƒ вашому публічному профілі"
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти буде викориÑтовуватиÑÑ Ð´Ð»Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð½Ð¸Ñ… операцій, таких Ñк Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° злиттÑ. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "Цей Ñмайлик та Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð±ÑƒÐ´ÑƒÑ‚ÑŒ показані у вашому профілі та в інтерфейÑÑ–."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
-msgstr ""
+msgstr "Ð¦Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ відображатиÑÑ Ñƒ вашому профілі"
msgid "Profiles|Two-Factor Authentication"
-msgstr ""
+msgstr "Двофакторна автентифікаціÑ"
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "Введіть ваш %{confirmationValue} Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ:"
@@ -7366,13 +8636,13 @@ msgid "Profiles|Username successfully changed"
msgstr "Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача уÑпішно збережено"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "ВикориÑÑ‚Ð°Ð½Ð½Ñ Ñмайликів в іменах виглÑдає дотепно, але, будь лаÑка, краще викориÑтовуйте Ñ—Ñ… в повідомленнÑÑ… про ÑтатуÑ"
msgid "Profiles|What's your status?"
msgstr "Який ваш ÑтатуÑ?"
msgid "Profiles|Who you represent or work for"
-msgstr ""
+msgstr "Кого ви предÑтавлÑєте або на кого працюєте"
msgid "Profiles|You can change your avatar here"
msgstr "Тут ви можете змінити Ñвій аватар"
@@ -7393,19 +8663,19 @@ msgid "Profiles|You must transfer ownership or delete these groups before you ca
msgstr "Вам необхідно змінити влаÑника або видалити ці групи перед тим Ñк видалити ваш обліковий запиÑ."
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
-msgstr ""
+msgstr "Ваше ім'Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ LinkedIn з linkedin.com/in/profilename"
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr "Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ” влаÑником в цих групах:"
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "Ваша адреÑа електронної пошти була автоматично вÑтановлена на оÑнові вашого облікового запиÑу %{provider_label}"
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "Ваше міÑÑ†ÐµÐ·Ð½Ð°Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ автоматично вÑтановлено на оÑнові вашого облікового запиÑу %{provider_label}"
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
-msgstr ""
+msgstr "Ваше Ñ–Ð¼â€™Ñ Ð±ÑƒÐ»Ð¾ автоматично вÑтановлено на оÑнові вашого облікового запиÑу %{provider_label} щоб люди могли Ð²Ð°Ñ Ð²Ð¿Ñ–Ð·Ð½Ð°Ñ‚Ð¸"
msgid "Profiles|Your status"
msgstr "Ваш ÑтатуÑ"
@@ -7414,10 +8684,10 @@ msgid "Profiles|e.g. My MacBook key"
msgstr "наприклад, мій ключ MacBook"
msgid "Profiles|username"
-msgstr ""
+msgstr "ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "Ñайт.укр"
msgid "Profiles|your account"
msgstr "ваш обліковий запиÑ"
@@ -7434,6 +8704,15 @@ msgstr "ПрогреÑ"
msgid "Project"
msgstr "Проект"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr "Проект \"%{name}\" більше не доÑтупний. Щоб продовжити виберіть інший проект."
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Проект '%{project_name}' перебуває в процеÑÑ– видаленнÑ."
@@ -7449,17 +8728,26 @@ msgstr "Проект '%{project_name}' уÑпішно оновлено."
msgid "Project Badges"
msgstr "Значки проекту"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "URL-адреÑа проекту"
msgid "Project access must be granted explicitly to each user."
msgstr "ДоÑтуп до проекту повинен надаватиÑÑ ÐºÐ¾Ð¶Ð½Ð¾Ð¼Ñƒ кориÑтувачеві."
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "Ðватар проекту"
-msgid "Project avatar in repository: %{link}"
-msgstr "Ðватар проекту в репозиторії: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "Деталі проекту"
@@ -7467,6 +8755,9 @@ msgstr "Деталі проекту"
msgid "Project export could not be deleted."
msgstr "Ðеможливо видалити екÑпорт проекту."
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "ЕкÑпорт проекту видалений."
@@ -7476,8 +8767,11 @@ msgstr "ЗакінчивÑÑ Ñ‚ÐµÑ€Ð¼Ñ–Ð½ дії поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° проÐ
msgid "Project export started. A download link will be sent by email."
msgstr "Розпочато екÑпорт проекту. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÑÐºÐ°Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ надіÑлана електронною поштою."
+msgid "Project has too many %{label_for_message} to search"
+msgstr "Ð’ проекті занадто багато %{label_for_message} Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ"
+
msgid "Project members"
-msgstr ""
+msgstr "УчаÑники проекту"
msgid "Project name"
msgstr "Ðазва проекту"
@@ -7485,9 +8779,15 @@ msgstr "Ðазва проекту"
msgid "Project slug"
msgstr "ШлÑÑ… проекту"
-msgid "Project:"
+msgid "Project uploads"
msgstr ""
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
+msgid "Project:"
+msgstr "Проект:"
+
msgid "ProjectActivityRSS|Subscribe"
msgstr "ПідпиÑатиÑÑ"
@@ -7575,12 +8875,21 @@ msgstr "КориÑтувачі можуть відправлÑти в цей Ñ€Ð
msgid "Projects"
msgstr "Проекти"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "Спільні проекти з %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "Проекти, що належать до групи, викориÑтовують Ñ—Ñ— ім'Ñ Ñк префікÑ. ІÑнуючі проекти можуть бути переміщені в групу."
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "ЧаÑто відвідувані"
@@ -7629,6 +8938,9 @@ msgstr "Ðе вÑтановлено попередженнÑ"
msgid "PrometheusAlerts|Operator"
msgstr "Оператор"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "Поріг"
@@ -7657,7 +8969,7 @@ msgid "PrometheusService|Custom metrics"
msgstr "ВлаÑні метрики"
msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
-msgstr ""
+msgstr "Увімкнути Prometheus Ð´Ð»Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð»Ð°Ñних метрик, викориÑтовуючи будь-Ñкий з варіантів вище"
msgid "PrometheusService|Finding and configuring metrics..."
msgstr "Пошук та Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼ÐµÑ‚Ñ€Ð¸Ðº..."
@@ -7731,6 +9043,9 @@ msgstr "Ð¦Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð°."
msgid "Promotions|Upgrade plan"
msgstr "Перейти на вищий тарифний план"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "Захищено"
@@ -7789,7 +9104,7 @@ msgid "Pseudonymizer data collection"
msgstr "Збір даних Pseudonymizer"
msgid "Public"
-msgstr ""
+msgstr "Публічний"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "Публічна — група та вÑÑ– публічні проекти можуть переглÑдатиÑÑ Ð±ÐµÐ· автентифікації."
@@ -7797,6 +9112,12 @@ msgstr "Публічна — група та вÑÑ– публічні проекÑ
msgid "Public - The project can be accessed without any authentication."
msgstr "Публічний — проект может переглÑдатиÑÑ Ð±ÐµÐ· автентифікації."
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "Публічні конвеєри"
@@ -7809,6 +9130,12 @@ msgstr "Відправити (push)"
msgid "Push Rules"
msgstr "Push-правила"
+msgid "Push an existing Git repository"
+msgstr "Відправити Ñ–Ñнуючий репозиторій Git"
+
+msgid "Push an existing folder"
+msgstr "Відправити Ñ–Ñнуючу папку"
+
msgid "Push events"
msgstr "Події Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ (push)"
@@ -7831,19 +9158,22 @@ msgid "Quarters"
msgstr "Квартали"
msgid "Query"
-msgstr ""
+msgstr "Запит"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "Швидкі дії можна викориÑтовувати в опиÑах задач Ñ– коментарÑÑ…."
msgid "README"
+msgstr "ІнÑÑ‚Ñ€ÑƒÐºÑ†Ñ–Ñ (README)"
+
+msgid "Rake Tasks Help"
msgstr ""
msgid "Read more"
msgstr "Докладніше"
msgid "Read more about environments"
-msgstr ""
+msgstr "Читати більше про Ñередовища"
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про права доÑтупу в проекті <strong>%{link_to_help}</strong>"
@@ -7852,6 +9182,12 @@ msgid "Real-time features"
msgstr "Фунції реального чаÑу"
msgid "Receive alerts from manually configured Prometheus servers."
+msgstr "Отримувати Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ налаштованого вручну Ñервера Prometheus."
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
msgstr ""
msgid "Recent searches"
@@ -7883,7 +9219,7 @@ msgid "Register / Sign In"
msgstr "ЗареєÑтруватиÑÑ / Увійти"
msgid "Register U2F device"
-msgstr ""
+msgstr "ЗареєÑтрувати приÑтрій U2F"
msgid "Register and see your runners for this group."
msgstr "ЗареєÑтруйте Ñ– переглÑдайте ваші Runner’и Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— групи."
@@ -7891,6 +9227,9 @@ msgstr "ЗареєÑтруйте Ñ– переглÑдайте ваші Runner’Ð
msgid "Register and see your runners for this project."
msgstr "ЗареєÑтруйте Ñ– переглÑдайте ваші Runner’и Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту."
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "РеєÑÑ‚Ñ€"
@@ -7916,7 +9255,7 @@ msgid "Related merge requests"
msgstr "Пов'Ñзані запити на злиттÑ"
msgid "Releases"
-msgstr ""
+msgstr "Релізи"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
@@ -7931,14 +9270,23 @@ msgid "Remove Runner"
msgstr "Видалити Runner"
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
-msgstr ""
+msgstr "ВидалÑти вÑÑ– Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñƒ запитах на злиттÑ, коли у гілці-джерелі з'ÑвлÑÑŽÑ‚ÑŒÑÑ Ð½Ð¾Ð²Ñ– коміти"
msgid "Remove approver"
-msgstr ""
+msgstr "Видалити затверджуючу оÑобу"
+
+msgid "Remove approvers"
+msgstr "Видалити затверджуючих оÑіб"
+
+msgid "Remove approvers?"
+msgstr "Видалити затверджуючих оÑіб?"
msgid "Remove avatar"
msgstr "Видалити аватар"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "Видалити групу"
@@ -7948,12 +9296,33 @@ msgstr "Видалити пріоритет"
msgid "Remove project"
msgstr "Видалити проект"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "Видалену групу неможливо відновити!"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð³Ñ€ÑƒÐ¿Ð¸ призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… Ñ—Ñ— дочірніх проектів Ñ– реÑурÑів."
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "Перейменувати"
@@ -7967,14 +9336,17 @@ msgid "Reopen epic"
msgstr "Повторне Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ ÐµÐ¿Ñ–ÐºÑƒ"
msgid "Reopen milestone"
-msgstr ""
+msgstr "Повторне Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ ÐµÑ‚Ð°Ð¿Ñƒ"
msgid "Repair authentication"
-msgstr "Відновити аутентифікацію"
+msgstr "Відновити автентифікацію"
-msgid "Reply to comment"
+msgid "Replace"
msgstr ""
+msgid "Reply to comment"
+msgstr "ВідповіÑти на коментар"
+
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr "ВідповіÑти на це електронне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð±ÐµÐ·Ð¿Ð¾Ñередньо або %{view_it_on_gitlab}."
@@ -8005,6 +9377,18 @@ msgstr "Ð§Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ"
msgid "Reports|Failure"
msgstr "Помилка"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "СерйозніÑÑ‚ÑŒ"
@@ -8023,6 +9407,12 @@ msgstr "Результати Ð´Ð»Ñ Ñ‚ÐµÑтового звіту оброблÑ
msgid "Reports|Vulnerability"
msgstr "ВразливіÑÑ‚ÑŒ"
+msgid "Reports|issue"
+msgstr "задача"
+
+msgid "Reports|merge request"
+msgstr "запит на злиттÑ"
+
msgid "Reports|no changed test results"
msgstr "результати теÑтів не змінилиÑÑ"
@@ -8035,11 +9425,14 @@ msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ"
msgid "Repository URL"
msgstr "URL репозиторіÑ"
-msgid "Repository cleanup"
+msgid "Repository check was triggered."
msgstr ""
+msgid "Repository cleanup"
+msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ"
+
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
-msgstr ""
+msgstr "ОчиÑтку репозиторію розпочато. Ви отримаєте Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ електронній пошті піÑÐ»Ñ Ñ—Ñ— завершеннÑ."
msgid "Repository has no locks."
msgstr "Репозиторій не має блокувань."
@@ -8071,20 +9464,37 @@ msgstr "Вимагати від вÑÑ–Ñ… кориÑтувачів цієї грÑ
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Вимагати від уÑÑ–Ñ… кориÑтувачів приймати умови Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ñлуг та політику конфіденційноÑÑ‚Ñ–, коли вони отримують доÑтуп до GitLab."
+msgid "Require approval from code owners"
+msgstr "Вимагати Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ влаÑників коду"
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "Resend invite"
-msgstr ""
+msgstr "Повторно надіÑлати запрошеннÑ"
msgid "Reset authorization key"
-msgstr ""
+msgstr "Скинути ключ авторизації"
msgid "Reset authorization key?"
-msgstr ""
+msgstr "Скинути ключ авторизації?"
msgid "Reset health check access token"
msgstr "Оновити токен доÑтупу Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ працездатноÑÑ‚Ñ–"
msgid "Reset key"
-msgstr ""
+msgstr "Скинути ключ"
msgid "Reset runners registration token"
msgstr "Перегенерувати реєÑтраційний токен runner-ів"
@@ -8102,9 +9512,18 @@ msgid "Resolve discussion"
msgstr "Завершити обговореннÑ"
msgid "Resolved"
+msgstr "Вирішено"
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
msgstr ""
msgid "Response"
+msgstr "Відповідь"
+
+msgid "Response didn't include `service_desk_address`"
msgstr ""
msgid "Response metrics (AWS ELB)"
@@ -8117,7 +9536,7 @@ msgid "Response metrics (HA Proxy)"
msgstr "Метрики відповідей (HA Proxy)"
msgid "Response metrics (NGINX Ingress VTS)"
-msgstr ""
+msgstr "Метрики відповідей (NGINX Ingress VTS)"
msgid "Response metrics (NGINX Ingress)"
msgstr "Метрики відповідей (NGINX Ingress)"
@@ -8126,17 +9545,23 @@ msgid "Response metrics (NGINX)"
msgstr "Метрики відповідей (NGINX)"
msgid "Restart Terminal"
-msgstr ""
+msgstr "ПерезапуÑтити термінал"
msgid "Resume"
msgstr "Продовжити"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "Спробувати знову"
msgid "Retry this job"
msgstr "Повторити це завданнÑ"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "Повторити перевірку"
@@ -8148,7 +9573,7 @@ msgstr[2] "Показати значень"
msgstr[3] "Показати значень"
msgid "Reveal values"
-msgstr ""
+msgstr "Показати значеннÑ"
msgid "Revert this commit"
msgstr "Ðнулювати цей коміт"
@@ -8171,15 +9596,24 @@ msgstr "Перевірка (запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ !%{mergeRequestId})"
msgid "Revoke"
msgstr "Відкликати"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "План-графік"
msgid "Run CI/CD pipelines for external repositories"
msgstr "ЗапуÑтити CI/CD конвеєри Ð´Ð»Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… репозиторіїв"
-msgid "Run tests against your code live using the Web Terminal"
+msgid "Run housekeeping"
msgstr ""
+msgid "Run tests against your code live using the Web Terminal"
+msgstr "ПротеÑтувати ваш запущений код за допомогою Веб-терміналу"
+
msgid "Run untagged jobs"
msgstr "Виконати Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÐµÐ· тегів"
@@ -8198,6 +9632,12 @@ msgstr "Runner виконує завданні із призначених прÐ
msgid "Runner token"
msgstr "Токен Runner'а"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "Runner не отримуватиме нових завдань"
@@ -8208,7 +9648,7 @@ msgid "Runners API"
msgstr "API Runner’ів"
msgid "Runners activated for this project"
-msgstr ""
+msgstr "Runner'и активовані Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту"
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr "Runner’и можуть розміщуватиÑÑ Ñƒ різних кориÑтувачів, на Ñерверах Ñ– навіть на вашій локальній машині."
@@ -8231,27 +9671,27 @@ msgstr "Ви викориÑтали уÑÑ– виділенні хвилини дÐ
msgid "Running"
msgstr "ВиконуєтьÑÑ"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "Єдиний вхід SAML"
msgid "SAML SSO for %{group_name}"
msgstr "Єдиний вхід SAML Ð´Ð»Ñ %{group_name}"
-msgid "SAML Single Sign On"
-msgstr "Єдиний вхід SAML"
-
-msgid "SAML Single Sign On Settings"
-msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ”Ð´Ð¸Ð½Ð¾Ð³Ð¾ входу SAML"
-
msgid "SAML for %{group_name}"
-msgstr ""
-
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "Відбиток SHA1 Ñертифікату Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ñ–Ð² SAML. Отримайте його від провайдера ідентифікації, де він також може називатиÑÑ \"Thumbprint\"."
+msgstr "SAML Ð´Ð»Ñ %{group_name}"
msgid "SSH Keys"
msgstr "Ключі SSH"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH-ключі хоÑта"
@@ -8261,11 +9701,14 @@ msgstr "Відкритий SSH-ключ"
msgid "SSL Verification"
msgstr "Перевірка SSL"
+msgid "Saturday"
+msgstr "Субота"
+
msgid "Save"
msgstr "Зберегти"
msgid "Save Changes"
-msgstr ""
+msgstr "Зберегти зміни"
msgid "Save application"
msgstr "Зберегти заÑтоÑунок"
@@ -8277,7 +9720,7 @@ msgid "Save changes before testing"
msgstr "Зберегти зміни перед теÑтуваннÑм"
msgid "Save comment"
-msgstr ""
+msgstr "Зберегти коментар"
msgid "Save pipeline schedule"
msgstr "Зберегти розклад конвеєра"
@@ -8285,6 +9728,9 @@ msgstr "Зберегти розклад конвеєра"
msgid "Save variables"
msgstr "Зберегти змінні"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "Розклад нового конвеєра"
@@ -8294,15 +9740,24 @@ msgstr "Заплановано"
msgid "Schedules"
msgstr "Розклади"
+msgid "Scheduling"
+msgstr "ПлануваннÑ"
+
msgid "Scheduling Pipelines"
msgstr "ÐŸÐ»Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ñ–Ð²"
msgid "Scope"
msgstr "ОбÑÑг"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "Тематичні дошки задач"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "Прокрутіть вниз до <strong>Google Code Project Hosting</strong> і увімкніть перемикач праворуч."
@@ -8316,7 +9771,7 @@ msgid "Search"
msgstr "Пошук"
msgid "Search an environment spec"
-msgstr ""
+msgstr "Пошук Ñпецифікації Ñередовища"
msgid "Search branches"
msgstr "Пошук у гілках"
@@ -8331,7 +9786,7 @@ msgid "Search for projects, issues, etc."
msgstr "Пошук в проектах, задачах і т. д."
msgid "Search groups"
-msgstr ""
+msgstr "Пошук в групах"
msgid "Search merge requests"
msgstr "Пошук у запитах на злиттÑ"
@@ -8354,6 +9809,9 @@ msgstr "Пошук проектів"
msgid "Search users"
msgstr "Пошук кориÑтувачів"
+msgid "Search users or groups"
+msgstr "Пошук кориÑтувачів або групи"
+
msgid "Search your projects"
msgstr "Пошук у ваших проектах"
@@ -8381,6 +9839,15 @@ msgstr "в цій групі"
msgid "SearchAutocomplete|in this project"
msgstr "в цьому проекті"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "Секрет"
@@ -8402,9 +9869,6 @@ msgstr "Помилка під Ñ‡Ð°Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÑпиÑку вразлÐ
msgid "Security Dashboard|Issue Created"
msgstr "Створено задачу"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "Створити задачу"
@@ -8412,20 +9876,20 @@ msgid "Security Reports|Dismiss vulnerability"
msgstr "Відхилити вразливіÑÑ‚ÑŒ"
msgid "Security Reports|Learn more about setting up your dashboard"
-msgstr ""
+msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð°ÑˆÐ¾Ñ— панелі"
msgid "Security Reports|More info"
msgstr "Детальніше"
msgid "Security Reports|No Vulnerabilities"
-msgstr ""
-
-msgid "Security Reports|Security dashboard documentation"
-msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ Ð´Ð¾ панелі безпеки"
+msgstr "Ðемає вразливоÑтей"
msgid "Security Reports|There was an error creating the issue."
msgstr "Помилка при Ñтворенні задачі."
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "Помилка при відхиленні вразливоÑÑ‚Ñ–."
@@ -8436,16 +9900,16 @@ msgid "Security Reports|There was an error reverting this dismissal."
msgstr "Помилка при анулюванні цього відхиленнÑ."
msgid "Security Reports|Undo dismiss"
-msgstr ""
+msgstr "Відмінити відхиленнÑ"
msgid "Security Reports|We've found no vulnerabilities for your group"
-msgstr ""
+msgstr "Ми не виÑвили вразливоÑтей Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— групи"
msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
-msgstr ""
+msgstr "Хоча й рідко, але можливо, що ваша група не має вразливоÑтей. Ð’ будь-Ñкому разі, ми проÑимо Ð²Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ваші налаштуваннÑ, щоб впевнитиÑÑ, що ваша панель налаштована правильно."
msgid "Security dashboard"
-msgstr ""
+msgstr "Панель безпеки"
msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr "Ðа панелі безпеки відображаєтьÑÑ Ð¾Ñтанній звіт про безпеку. ВикориÑтовуйте його Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ та Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ñ€Ð°Ð·Ð»Ð¸Ð²Ð¾Ñтей."
@@ -8457,7 +9921,7 @@ msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
msgstr "Конвеєр %{pipelineLink} запущено"
msgid "See metrics"
-msgstr ""
+msgstr "ПереглÑнути метрики"
msgid "See the affected projects in the GitLab admin panel"
msgstr ""
@@ -8474,6 +9938,15 @@ msgstr "Виберіть групу Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ"
msgid "Select a namespace to fork the project"
msgstr "Виберіть проÑÑ‚Ñ–Ñ€ імен Ð´Ð»Ñ Ñ„Ð¾Ñ€ÐºÑƒ проекту"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr "Вибрати проект, щоб прочитати файл конфігурації ÑтатиÑтики (Insights)"
+
+msgid "Select a repository"
+msgstr "Виберіть репозиторій"
+
msgid "Select a template repository"
msgstr "Вибрати шаблон репозиторію"
@@ -8483,14 +9956,11 @@ msgstr "Вибрати чаÑовий поÑÑ"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "Виберіть Ñ–Ñнуючий клаÑтер Kubernetes або Ñтворіть новий"
-msgid "Select assignee"
-msgstr "Виберіть виконавцÑ"
-
msgid "Select branch/tag"
msgstr "Виберіть гілку або тег"
msgid "Select members to invite"
-msgstr ""
+msgstr "Виберіть учаÑників Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ"
msgid "Select project"
msgstr "Вибрати проект"
@@ -8516,74 +9986,95 @@ msgstr "Виберіть гілку по замовчанню Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾
msgid "Select the custom project template source group."
msgstr "Вкажіть групу, де розміщені влаÑні шаблони проектів."
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr "Вибрані рівні не можуть викориÑтовуватиÑÑ Ð½ÐµÐ°Ð´Ð¼Ñ–Ð½Ñ–Ñтраторами Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿, проектів або Ñніпетів. Якщо публічний рівень обмежений, профілі кориÑтувачів відображаютьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð·Ð°Ñ€ÐµÑ”Ñтрованих кориÑтувачів."
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "При виборі кориÑтувача Gitlab поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° нього буде додане до опиÑу задачі та коментарів (напр. \"<a href=\"#\"> @johnsmith</a>\"). Також це призведе до аÑоціації та/або Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ†Ð¸Ñ… задач та коментарів на вибраного кориÑтувача."
-msgid "Selective synchronization"
-msgstr "Вибіркова ÑинхронізаціÑ"
+msgid "Send an email notification to Developers."
+msgstr "Відправити ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ°Ð¼Ð¸ електронною поштою."
msgid "Send email"
msgstr "ÐадіÑлати лиÑта"
msgid "Send report"
-msgstr ""
+msgstr "ÐадіÑлати звіт"
msgid "Send usage data"
msgstr "Відправити дані про викориÑтаннÑ"
msgid "Sentry API URL"
-msgstr ""
+msgstr "URL-адреÑа Sentry API"
msgid "Sep"
msgstr "вер."
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "вереÑень"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "ВерÑÑ–Ñ Ñервера"
msgid "Serverless"
+msgstr "Serverless"
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
msgstr ""
msgid "ServerlessDetails|Kubernetes Pods"
+msgstr "Pod'и Kubernetes"
+
+msgid "ServerlessDetails|More information"
msgstr ""
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
-msgstr ""
+msgstr "КількіÑÑ‚ÑŒ pod'ів Kubernetes у викориÑтанні на оÑнові необхідноÑÑ‚Ñ– протÑгом періоду чаÑу."
msgid "ServerlessDetails|pod in use"
-msgstr ""
+msgstr "pod у викориÑтанні"
msgid "ServerlessDetails|pods in use"
-msgstr ""
+msgstr "pod'и у викориÑтанні"
msgid "ServerlessURL|Copy URL to clipboard"
-msgstr ""
+msgstr "Скопіювати URL-адреÑу в буфер обміну"
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
-msgstr ""
-
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
+msgstr "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб почати викориÑтовувати функції Ñк ÑервіÑ, необхідно Ñпочатку вÑтановити Knative на ваш клаÑтер Kubernetes."
msgid "Serverless|Getting started with serverless"
-msgstr ""
+msgstr "Початок роботи із serverless"
msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
-msgstr ""
+msgstr "Якщо ви вважаєте, що жодна з них не підходить, будь лаÑка перевірте пізніше, тому що дані про функції можуть бути в процеÑÑ– отриманнÑ."
msgid "Serverless|Install Knative"
-msgstr ""
+msgstr "Ð’Ñтановити Knative"
msgid "Serverless|Learn more about Serverless"
-msgstr ""
+msgstr "ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Serverless"
msgid "Serverless|No functions available"
-msgstr ""
+msgstr "Ðемає доÑтупних функцій"
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
-msgstr ""
+msgstr "Ðаразі немає даних про функції від Knative. Це може бути викликано різними причинами, зокрема:"
msgid "Service Desk"
msgstr "Service Desk"
@@ -8594,9 +10085,18 @@ msgstr "Шаблони ÑервіÑів"
msgid "Service URL"
msgstr "URL ÑервіÑу"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "Термін дії ÑеÑÑ–Ñ—, проектні ліміти та розміри вкладень."
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr "Ð’Ñтановіть кількіÑÑ‚ÑŒ необхідних затверджень, затверджуючих оÑіб та інші параметри затвердженнÑ."
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Ð’Ñтановіть пароль Ð´Ð»Ñ Ñвого облікового запиÑу, щоб мати можливіÑÑ‚ÑŒ відправлÑти та отримувати через %{protocol}."
@@ -8612,15 +10112,30 @@ msgstr "Ð’Ñтановити репозиторій шаблонів Ð´Ð»Ñ Ð²Ñ
msgid "Set max session time for web terminal."
msgstr "МакÑимальний термін дії ÑеÑÑ–Ñ— Ð´Ð»Ñ Ð²ÐµÐ±-терміналу."
+msgid "Set new password"
+msgstr "Ð’Ñтановити новий пароль"
+
msgid "Set notification email for abuse reports."
msgstr "Ðалаштувати ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ð¾ електронній пошті Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ про зловживаннÑ."
msgid "Set number of approvers required before open merge requests can be merged"
-msgstr ""
+msgstr "Ð’Ñтановити необхідну кількіÑÑ‚ÑŒ затверджуючих оÑіб перед тим, Ñк відкритий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð¾Ð¶Ðµ бути злитий"
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "Ð’Ñтановіть вимоги Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ кориÑтувачів. Увімкніть обов’Ñзкову двофакторну автентифікацію."
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD"
@@ -8634,7 +10149,10 @@ msgid "Set up assertions/attributes/claims (email, first_name, last_name) and Na
msgstr "Ðалаштуйте твердженнÑ/атрибути (email, ім'Ñ, прізвище) Ñ– NameID відповідно до %{docsLinkStart} документації %{icon}%{docsLinkEnd}"
msgid "Set up new U2F device"
-msgstr ""
+msgstr "Ðалаштувати новий приÑтрій U2F"
+
+msgid "Set up new password"
+msgstr "Ð’Ñтановити новий пароль"
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "Ðалаштуйте Ñвій проект, щоб автоматично відправлÑти/отримувати зміни з іншого репозиторію. Гілки, теги та коміти автоматично будуть ÑинхронізуватиÑÑ."
@@ -8693,18 +10211,27 @@ msgstr "Обнулити викориÑтані хвилини в конвеєр
msgid "Sherlock Transactions"
msgstr "Sherlock транзакції"
+msgid "Show all activity"
+msgstr "Показати вÑÑŽ активніÑÑ‚ÑŒ"
+
msgid "Show command"
msgstr "Показати команду"
+msgid "Show comments only"
+msgstr "Показати тільки коментарі"
+
msgid "Show complete raw log"
msgstr "Показати повний неформатований журнал"
msgid "Show file browser"
-msgstr ""
+msgstr "Показати файловий менеджер"
msgid "Show latest version"
msgstr "Показати оÑтанню верÑÑ–ÑŽ"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "Показати батьківÑькі Ñторінки"
@@ -8743,19 +10270,19 @@ msgid "Sign in / Register"
msgstr "Увійти або зареєÑтруватиÑÑ"
msgid "Sign in to \"%{group_name}\""
-msgstr ""
+msgstr "Увійти до \"%{group_name}\""
msgid "Sign in using smart card"
-msgstr ""
+msgstr "Увійти за допомогою Ñмарт-карти"
msgid "Sign in via 2FA code"
-msgstr ""
+msgstr "Увійти за допомогою коду двофакторної автентифікції"
msgid "Sign in with Single Sign-On"
msgstr "Увійти за допомогою єдиного входу"
msgid "Sign in with smart card"
-msgstr ""
+msgstr "Увійти за допомогою Ñмарт-карти"
msgid "Sign out"
msgstr "Вийти"
@@ -8766,15 +10293,24 @@ msgstr "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ"
msgid "Sign-up restrictions"
msgstr "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації"
-msgid "Similar issues"
+msgid "Signing in using %{label} has been disabled"
msgstr ""
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr "Подібні задачі"
+
msgid "Size"
msgstr "Розмір"
msgid "Size and domain settings for static websites"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ð¾Ð·Ð¼Ñ–Ñ€Ñƒ та домену Ð´Ð»Ñ Ñтатичних веб-Ñайтів"
+msgid "Size limit per repository (MB)"
+msgstr "МакÑимальний розмір Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ репозиторію (МБ)"
+
msgid "Slack application"
msgstr "заÑтоÑунок Slack"
@@ -8791,16 +10327,37 @@ msgid "Smartcard authentication failed: client certificate header is missing."
msgstr "Ðе вдалоÑÑ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÑƒÐ²Ð°Ñ‚Ð¸ Ñмарт-карту: відÑутній заголовок в Ñертифікаті клієнта."
msgid "Snippet Contents"
-msgstr ""
+msgstr "ВміÑÑ‚ Ñніпета"
msgid "Snippets"
msgstr "Сніпети"
-msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr "ОглÑд публічних Ñніпетів"
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr "Ðовий Ñніпет"
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr "Сніпетів не знайдено"
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr "Сніпети — це маленькі фрагменти коду або нотаток, Ñкі ви хотіли б зберегти."
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr "Ðемає Ñніпетів Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ."
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr "Вони можуть бути публічними або приватними."
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
msgstr ""
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr "ХтоÑÑŒ відредагував цю %{issueType} одночаÑно з вами. ÐžÐ¿Ð¸Ñ Ð±ÑƒÐ»Ð¾ оновлено, тому вам знову треба внеÑти Ñвої зміни."
+
msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
-msgstr ""
+msgstr "ХтоÑÑŒ відредагував цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¾Ð´Ð½Ð¾Ñ‡Ð°Ñно з вами. Будь лаÑка, оновіть Ñторінку, щоб побачити зміни."
msgid "Something went wrong on our end"
msgstr "ЩоÑÑŒ пішло не так з нашого боку"
@@ -8812,7 +10369,7 @@ msgid "Something went wrong on our end. Please try again!"
msgstr "ЩоÑÑŒ пішло не так на нашій Ñтороні. Будь-лаÑка, Ñпробуйте ще раз!"
msgid "Something went wrong on our end. Please try again."
-msgstr ""
+msgstr "ЩоÑÑŒ пішло не так на нашій Ñтороні. Будь-лаÑка, Ñпробуйте ще раз."
msgid "Something went wrong trying to change the confidentiality of this issue"
msgstr "Помилка при зміні конфіденційноÑÑ‚Ñ– цієї задачі"
@@ -8824,13 +10381,13 @@ msgid "Something went wrong when toggling the button"
msgstr "Помилка при перемиканні кнопки"
msgid "Something went wrong while applying the suggestion. Please try again."
-msgstr ""
+msgstr "Помилка при заÑтоÑуванні пропозиції. Будь лаÑка, Ñпробуйте знову."
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr "Помилка при закритті %{issuable}. Будь лаÑка, Ñпробуйте пізніше"
msgid "Something went wrong while deleting the source branch. Please try again."
-msgstr ""
+msgstr "Помилка при видаленні гілки-джерела. Будь лаÑка, Ñпробуйте знову."
msgid "Something went wrong while fetching %{listType} list"
msgstr "Помилка при отриманні ÑпиÑку %{listType}"
@@ -8841,6 +10398,9 @@ msgstr "Проблема при отриманні коментарів. БудÑ
msgid "Something went wrong while fetching group member contributions"
msgstr "Помилка при завантаженні внеÑків учаÑників групи"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "Проблема при отриманні Ñередовищ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ. Будь лаÑка, Ñпробуйте знову."
@@ -8851,7 +10411,7 @@ msgid "Something went wrong while fetching the registry list."
msgstr "ЩоÑÑŒ пішло не так при отриманні ÑпиÑку із реєÑтру."
msgid "Something went wrong while merging this merge request. Please try again."
-msgstr ""
+msgstr "Помилка при заÑтоÑуванні цього запиту на злиттÑ. Будь лаÑка, Ñпробуйте знову."
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr "Помилка при повторному відкритті %{issuable}. Будь лаÑка, Ñпробуйте пізніше"
@@ -8868,6 +10428,9 @@ msgstr "Проблема, не вдаєтьÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ операціÐ
msgid "Something went wrong, unable to remove project"
msgstr "Проблема, не вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ проект"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "ЩоÑÑŒ пішло не так. Будь лаÑка Ñпробуйте ще раз."
@@ -8878,13 +10441,13 @@ msgid "Sorry, no projects matched your search"
msgstr "Ðа жаль жоден проект не задовольнÑÑ” критеріÑм вашого пошуку"
msgid "Sorry, your filter produced no results"
-msgstr ""
+msgstr "Ðа жаль, немає результатів, Ñкі відповідають фільтру"
msgid "Sort by"
msgstr "Сортувати за"
msgid "Sort direction"
-msgstr ""
+msgstr "ПорÑдок ÑортуваннÑ"
msgid "SortOptions|Access level, ascending"
msgstr "Рівень доÑтупу, в порÑдку зроÑтаннÑ"
@@ -8932,7 +10495,7 @@ msgid "SortOptions|Less weight"
msgstr "Менша вага"
msgid "SortOptions|Milestone due date"
-msgstr ""
+msgstr "Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ"
msgid "SortOptions|Milestone due later"
msgstr "Етап запланований на пізніше"
@@ -8965,7 +10528,7 @@ msgid "SortOptions|Oldest joined"
msgstr "Приєднаний найраніше"
msgid "SortOptions|Oldest last activity"
-msgstr ""
+msgstr "ÐайÑтаріша оÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ"
msgid "SortOptions|Oldest sign in"
msgstr "Залогінений найраніше"
@@ -8980,7 +10543,7 @@ msgid "SortOptions|Priority"
msgstr "Пріоритет"
msgid "SortOptions|Recent last activity"
-msgstr ""
+msgstr "Ðайновіша оÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ"
msgid "SortOptions|Recent sign in"
msgstr "Ðещодавно зареєÑтровані"
@@ -9010,7 +10573,7 @@ msgid "Source is not available"
msgstr "Джерело недоÑтупне"
msgid "Source project cannot be found."
-msgstr ""
+msgstr "Проект-джерело не знайдено."
msgid "Spam Logs"
msgstr "Спам-журнал"
@@ -9018,6 +10581,9 @@ msgstr "Спам-журнал"
msgid "Spam and Anti-bot Protection"
msgstr "ЗахиÑÑ‚ від Ñпаму Ñ– ботів"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "Спеціальні Runner’и"
@@ -9028,7 +10594,7 @@ msgid "Specify the following URL during the Runner setup:"
msgstr "Зазначте наÑтупний URL під Ñ‡Ð°Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Runner-а:"
msgid "Squash commit message"
-msgstr ""
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð¾Ð±'єднаного (squash) коміту"
msgid "Squash commits"
msgstr "Виконати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (squash) комітів"
@@ -9066,15 +10632,27 @@ msgstr "ÐктивніÑÑ‚ÑŒ в обраних проектах"
msgid "Starred projects"
msgstr "Обрані проекти"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
+msgstr "У обраному"
+
+msgid "Start GitLab Ultimate trial"
msgstr ""
msgid "Start Web Terminal"
-msgstr ""
+msgstr "ЗапуÑтити Веб-Термінал"
msgid "Start a %{new_merge_request} with these changes"
msgstr "Почати %{new_merge_request} з цими змінами"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "Розпочати перевірку"
@@ -9082,19 +10660,19 @@ msgid "Start and due date"
msgstr "Дата початку та завершеннÑ"
msgid "Start cleanup"
-msgstr ""
+msgstr "Почати очищеннÑ"
msgid "Start date"
msgstr "Дата початку"
msgid "Start discussion"
-msgstr ""
+msgstr "Розпочати диÑкуÑÑ–ÑŽ"
msgid "Start discussion & close %{noteable_name}"
-msgstr ""
+msgstr "Розпочати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð° закрити %{noteable_name}"
msgid "Start discussion & reopen %{noteable_name}"
-msgstr ""
+msgstr "Розпочати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ– повторно відкрити %{noteable_name}"
msgid "Start the Runner!"
msgstr "ЗапуÑÑ‚Ñ–Ñ‚ÑŒ Runner!"
@@ -9106,13 +10684,16 @@ msgid "Started"
msgstr "Запущений"
msgid "Started %{startsIn}"
+msgstr "Запущено %{startsIn}"
+
+msgid "Started asynchronous removal of all repository check states."
msgstr ""
msgid "Starting..."
-msgstr ""
+msgstr "ЗапуÑк..."
msgid "Starts %{startsIn}"
-msgstr ""
+msgstr "Буде запущено %{startsIn}"
msgid "Starts at (UTC)"
msgstr "ПочинаєтьÑÑ Ð¾ (за Грінвічем)"
@@ -9124,10 +10705,10 @@ msgid "Status"
msgstr "СтатуÑ"
msgid "Status:"
-msgstr ""
+msgstr "СтатуÑ:"
msgid "Stop Terminal"
-msgstr ""
+msgstr "Зупинити термінал"
msgid "Stop environment"
msgstr "Зупинити Ñередовище"
@@ -9145,7 +10726,7 @@ msgid "Stopping this environment is currently not possible as a deployment is in
msgstr "Зупинка Ñередовища наразі неможлива, тому що відбуваєтьÑÑ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
msgid "Stopping..."
-msgstr ""
+msgstr "ЗупиненнÑ..."
msgid "Storage"
msgstr "Сховище"
@@ -9163,7 +10744,7 @@ msgid "Submit as spam"
msgstr "Позначити Ñк Ñпам"
msgid "Submit feedback"
-msgstr ""
+msgstr "ÐадіÑлати відгук"
msgid "Submit review"
msgstr "ÐадіÑлати перевірку"
@@ -9181,40 +10762,40 @@ msgid "Subscribe at project level"
msgstr "ПідпиÑатиÑÑ Ð½Ð° рівні проекту"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "ПідпиÑатиÑÑ Ð½Ð° RSS-канал"
msgid "Subscribe to calendar"
-msgstr ""
+msgstr "ПідпиÑатиÑÑ Ð½Ð° календар"
msgid "Subscribed"
msgstr "Ви підпиÑані"
msgid "SubscriptionTable|Billing"
-msgstr ""
+msgstr "Білінг"
msgid "SubscriptionTable|Free"
-msgstr ""
+msgstr "Безкоштовно"
msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
msgstr ""
msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
-msgstr ""
+msgstr "GitLab.com %{planName} %{suffix}"
msgid "SubscriptionTable|Last invoice"
-msgstr ""
+msgstr "ОÑтанній рахунок-фактура"
msgid "SubscriptionTable|Loading subscriptions"
-msgstr ""
+msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñок"
msgid "SubscriptionTable|Manage"
-msgstr ""
+msgstr "УправліннÑ"
msgid "SubscriptionTable|Max seats used"
msgstr ""
msgid "SubscriptionTable|Next invoice"
-msgstr ""
+msgstr "ÐаÑтупний рахунок-фактура"
msgid "SubscriptionTable|Seats currently in use"
msgstr ""
@@ -9226,59 +10807,89 @@ msgid "SubscriptionTable|Seats owed"
msgstr ""
msgid "SubscriptionTable|Subscription end date"
-msgstr ""
+msgstr "Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñки"
msgid "SubscriptionTable|Subscription start date"
-msgstr ""
+msgstr "Дата початку підпиÑки"
msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
-msgstr ""
+msgstr "Це — оÑтанній момент, коли команда GitLab.com зв'ÑзувалаÑÑ Ð· вами Ð´Ð»Ñ Ñ€Ð¾Ð·Ñ€Ð°Ñ…ÑƒÐ½ÐºÑ–Ð² по непогашеним залишкам."
msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
-msgstr ""
+msgstr "Це — макÑимальна кількіÑÑ‚ÑŒ кориÑтувачів, Ñкі одночаÑно Ñ–Ñнували із початку поточної підпиÑки."
msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
-msgstr ""
+msgstr "Це — наÑтупний момент, коли команда GitLab.com зв'ÑжетьÑÑ Ð· вами Ð´Ð»Ñ Ñ€Ð¾Ð·Ñ€Ð°Ñ…ÑƒÐ½ÐºÑ–Ð²."
msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
msgstr ""
msgid "SubscriptionTable|Trial"
-msgstr ""
+msgstr "Пробна верÑÑ–Ñ"
msgid "SubscriptionTable|Trial end date"
-msgstr ""
+msgstr "Дата Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð½Ð¾Ñ— верÑÑ–Ñ—"
msgid "SubscriptionTable|Trial start date"
-msgstr ""
+msgstr "Дата початку пробної верÑÑ–Ñ—"
msgid "SubscriptionTable|Upgrade"
-msgstr ""
+msgstr "Підвищити"
msgid "SubscriptionTable|Usage"
-msgstr ""
+msgstr "ВикориÑтаннÑ"
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
-msgid "Suggested change"
+msgid "Successfully blocked"
msgstr ""
-msgid "Sunday"
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
msgstr ""
+msgid "Suggested change"
+msgstr "Пропонована зміна"
+
+msgid "Sunday"
+msgstr "ÐеділÑ"
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "Перейти в гілку/тег"
msgid "Sync information"
msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñинхронізацію"
+msgid "System"
+msgstr "СиÑтемні"
+
msgid "System Hooks"
msgstr "СиÑтемні гуки"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ÑиÑтему"
@@ -9286,6 +10897,9 @@ msgid "System default (%{default})"
msgstr ""
msgid "System header and footer"
+msgstr "Заголовок Ñ– футер ÑиÑтеми"
+
+msgid "System hook was successfully updated."
msgstr ""
msgid "System metrics (Custom)"
@@ -9295,10 +10909,10 @@ msgid "System metrics (Kubernetes)"
msgstr "СиÑтемні метрики (Kubernetes)"
msgid "Tag"
-msgstr ""
+msgstr "Тег"
msgid "Tag list:"
-msgstr ""
+msgstr "СпиÑок тегів:"
msgid "Tags"
msgstr "Теги"
@@ -9394,10 +11008,10 @@ msgid "Templates"
msgstr "Шаблони"
msgid "Terminal"
-msgstr ""
+msgstr "Термінал"
msgid "Terminal for environment"
-msgstr ""
+msgstr "Термінал Ð´Ð»Ñ Ñередовища"
msgid "Terms of Service Agreement and Privacy Policy"
msgstr "Угода про Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ñлуг Ñ– політика конфіденційноÑÑ‚Ñ–"
@@ -9411,17 +11025,30 @@ msgstr "ТеÑтувати SAML SSO"
msgid "Test coverage parsing"
msgstr "Пошук результатів Ð¿Ð¾ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ‚ÐµÑтами"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "ДÑкую! Більше не показувати це повідомленнÑ"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "Розширений глобальний пошук в GitLab — це потужний інÑтрумент Ñкий заощаджує ваш чаÑ. ЗаміÑÑ‚ÑŒ Ð´ÑƒÐ±Ð»ÑŽÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ Ñ– витрати чаÑу, ви можете шукати код інших команд, Ñкий може допомогти у вашому проекті."
msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
-msgstr ""
+msgstr "ЕкÑпорт CSV буде Ñтворено у фоновому режимі. ПіÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¹Ð¾Ð³Ð¾ буде надіÑлано на <strong>%{email}</strong> у вкладенні."
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr "Об'єкти Git LFS <strong>не</strong> будуть ÑинхронізуватиÑÑ."
@@ -9435,6 +11062,12 @@ msgstr "Трекер задач — це міÑце, де можна додатÐ
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Сертифікат X509 викориÑтовуєтьÑÑ Ð´Ð»Ñ Ð²Ð·Ð°Ñ”Ð¼Ð½Ð¾Ñ— перевірки автентичноÑÑ‚Ñ– TLS Ñ– необхідний Ð´Ð»Ñ Ð·Ð²'Ñзку з зовнішньою Ñлужбою авторизації. Якщо залишити порожнім, Ñертифікат Ñервера буде перевірÑтиÑÑŒ при доÑтупі через HTTPS."
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "ПідÑвітка Ñимволів дозволÑÑ” обмежувати заголовок до %{titleLength} Ñимволів Ñ– обмежувати довжину Ñ€Ñдків тіла %{bodyLength} Ñимволами Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб вони залишаютьÑÑ Ñ‡Ð¸Ñ‚Ð°Ð±ÐµÐ»ÑŒÐ½Ð¸Ð¼Ð¸ в git."
@@ -9447,24 +11080,84 @@ msgstr "ÐšÐ¾Ð»ÐµÐºÑ†Ñ–Ñ Ð¿Ð¾Ð´Ñ–Ð¹ додана до даних, зібрани
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Ð—â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ припинено піÑÐ»Ñ %{timeout}. Ð”Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð², Ñким потрібно більше чаÑу, викориÑтовуйте комбінацію clone/push."
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "Ð Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð½Ð° %{environmentLink} не уÑпішне."
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "Зв'Ñзок форку видалено."
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "Імпорт буде припинено піÑÐ»Ñ %{timeout}. Ð”Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð², Ñким потрібно більше чаÑу, викориÑтовуйте комбінацію clone/push."
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 maximum file size allowed is %{size}."
-msgstr ""
+msgstr "МакÑимальний розмір файлу — %{size}."
msgid "The maximum file size allowed is 200KB."
msgstr "МакÑимальний розмір файлу — 200 Кб."
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "Пароль, Ñкий потрібен Ð´Ð»Ñ Ð´ÐµÑˆÐ¸Ñ„Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð¾Ð³Ð¾ ключа. Він Ñ” необов’Ñзковим Ñ– зберігаєтьÑÑ Ñƒ зашифрованому виглÑді."
@@ -9492,9 +11185,18 @@ msgstr "ДоÑтуп до проекту можливий будь-Ñким за
msgid "The project can be accessed without any authentication."
msgstr "ДоÑтуп до проекту можливий без будь-Ñкої перевірки автентичноÑÑ‚Ñ–."
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "Збір даних пÑевдонімізованих данних вимкнено. Коли його буде увімкнено GitLab запуÑтить фонове завданнÑ, Ñке буде Ñтворювати пÑевдонімізовані CSV-файли бази даних GitLab, Ñкі будуть надÑилатиÑÑ Ð´Ð¾ вашого налаштованого Ñховища об'єктів."
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "Репозиторій Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту не Ñ–Ñнує."
@@ -9513,6 +11215,21 @@ msgstr "Ð¡Ñ‚Ð°Ð´Ñ–Ñ ÐŸÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° показує Ñ‡Ð°Ñ Ð²Ñ–Ð´ ÑтворÐ
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "План-графік показує Ñтан ваших епіків у чаÑÑ–"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "Ð¡Ñ‚Ð°Ð´Ñ–Ñ Staging показує Ñ‡Ð°Ñ Ð¼Ñ–Ð¶ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° розгортаннÑм коду у production. Дані автоматично додаютьÑÑ Ð¿Ñ–ÑÐ»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñƒ production вперше."
@@ -9528,9 +11245,18 @@ msgstr "ЧаÑ, витрачений на кожен елемент, зібраÐ
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "Ð§Ð°Ñ Ð´Ñ–Ñ— Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñплине через %{number_of_minutes} хвилин. Ð”Ð»Ñ Ð²ÐµÐ»Ð¸ÐºÐ¸Ñ… репозиторіїв викориÑтовуйте комбінацію clone/push."
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "Збір даних про викориÑÑ‚Ð°Ð½Ð½Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾, Ñ– це не можна налаштувати через цю форму."
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "Мапа кориÑтувачів — це JSON-документ, Ñкий задає Ñк адреÑи електронної пошти та імена кориÑтувачів Google Code, що приймали учаÑÑ‚ÑŒ у ваших проектах будуть імпортовані у GitLab. Ви можете змінити його шлÑхом зміни значень, що ÑтоÑÑ‚ÑŒ Ñправа від <code>:</code>. Ðе видалÑйте лапки та інші знаки пунктуації, а також не змінюйте адреÑи електронної пошти чи імена кориÑтувачів зліва."
@@ -9541,19 +11267,19 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "Середнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð² Ñ€Ñдку. Приклад: між 3, 5, 9, Ñередніми 5, між 3, 5, 7, 8, Ñередніми (5 + 7) / 2 = 6."
msgid "There are no approvers"
-msgstr ""
+msgstr "Ðемає затверджуючих оÑіб"
msgid "There are no archived projects yet"
msgstr "Ðаразі немає жодного архівованого проекту"
msgid "There are no closed issues"
-msgstr ""
+msgstr "Закритих задач немає"
msgid "There are no closed merge requests"
-msgstr ""
+msgstr "Закритих запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð°Ñ”"
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
-msgstr ""
+msgstr "ВлаÑні шаблони проектів не налаштовані Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñерверу GitLab. Вони активуютьÑÑ Ð½Ð° Ñторінці адмініÑÑ‚Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ GitLab. ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора GitLab Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð»Ð°Ñних шаблонів проектів."
msgid "There are no issues to show"
msgstr "Ðемає задач Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
@@ -9562,10 +11288,10 @@ msgid "There are no labels yet"
msgstr "Тут ще немає міток"
msgid "There are no open issues"
-msgstr ""
+msgstr "Відкритих задач немає"
msgid "There are no open merge requests"
-msgstr ""
+msgstr "Відкритих запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð°Ñ”"
msgid "There are no packages yet"
msgstr ""
@@ -9580,14 +11306,26 @@ msgid "There are no unstaged changes"
msgstr "Ðемає неіндекÑованих змін"
msgid "There was an error adding a todo."
-msgstr "Помилка при додаванні задачі."
+msgstr "Помилка при додаванні нагадуваннÑ."
msgid "There was an error deleting the todo."
msgstr "Помилка при видаленні задачі."
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "Помилка при завантаженні ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚Ñ– кориÑтувачів."
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Помилка при збереженні ваших налаштувань Ñповіщень."
@@ -9603,9 +11341,12 @@ msgstr "Помилка при підпиÑці на цю мітку."
msgid "There was an error when unsubscribing from this label."
msgstr "Помилка при відпиÑці від цієї мітки."
-msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
msgstr ""
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr "Ці Ñ–Ñнуючі проблеми мають подібні заголовки. Можливо, краще додати коментар до однієї з них заміÑÑ‚ÑŒ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¾Ñ—."
+
msgid "They can be managed using the %{link}."
msgstr "Ðими можна керувати за допомогою %{link}."
@@ -9613,14 +11354,17 @@ msgid "Third party offers"
msgstr "Сторонні пропозиції"
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
-msgstr ""
+msgstr "Ð¦Ñ %{issuable} заблокована. Лише <strong>учаÑники проекту</strong> можуть коментувати."
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
-msgstr ""
+msgstr "Цей %{viewer} не може бути відображено через %{reason}. ЗаміÑÑ‚ÑŒ цього можна %{options}."
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Цей інÑÑ‚Ð°Ð½Ñ GitLab ще немає загальних Runner'ів. ÐдмініÑтратори можуть Ñ—Ñ… зареєÑтрувати у Ñпеціальному розділі конфігурації."
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "Цей заÑтоÑунок було Ñтворено %{link_to_owner}."
@@ -9633,6 +11377,21 @@ msgstr "ВидиміÑÑ‚ÑŒ цієї дошки обмежена"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° була змінена піÑÐ»Ñ Ñ‚Ð¾Ð³Ð¾ моменту, коли ви почали Ñ—Ñ— редагувати. Ви хотіли б Ñтворити нову?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr "Цей коміт підпиÑано <strong>перевіреним</strong> підпиÑом Ñ– адреÑа електронної пошти комітера гарантовано належить тому Ñамому кориÑтувачу."
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr "Цей коміт підпиÑано перевіреним підпиÑом іншого кориÑтувача."
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr "Цей коміт підпиÑано перевіреним підпиÑом але адреÑа електронної пошти комітера <strong>не гарантовано</strong> належить тому Ñамому кориÑтувачу."
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr "Цей коміт підпиÑано <strong>неперевіреним</strong> підпиÑом."
+
msgid "This container registry has been scheduled for deletion."
msgstr "Заплановане Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ реєÑтру контейнерів."
@@ -9652,7 +11411,10 @@ msgid "This directory"
msgstr "Цей каталог"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
-msgstr ""
+msgstr "Цей домен не підтверджено. Щоб увімкнути доÑтуп, потрібно підтвердити право влаÑноÑÑ‚Ñ–."
+
+msgid "This field is required."
+msgstr "Це поле Ñ” обов'Ñзковим."
msgid "This group"
msgstr "Ð¦Ñ Ð³Ñ€ÑƒÐ¿Ð°"
@@ -9669,6 +11431,9 @@ msgstr "Це завданнÑ, відкладено на %{remainingTime}"
msgid "This is the author's first Merge Request to this project."
msgstr "Це перший запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ цього автора Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту."
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° Ñ” конфіденційною"
@@ -9717,12 +11482,18 @@ msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ñтворює Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° %{envir
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ±ÑƒÐ²Ð°Ñ” в Ñтані Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñ– чекає на запуÑк Runner"
-msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgid "This job is performing tasks that must complete before it can start"
msgstr ""
-msgid "This job is stuck because you don't have any active runners that can run this job."
+msgid "This job is preparing to start"
msgstr ""
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ðµ, тому що немає жодних runner'ів з будь Ñким із цих тегів:"
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ðµ, тому що немає активних runner'ів, Ñкі могли б його виконати."
+
msgid "This job is the most recent deployment to %{link}."
msgstr "Це Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ñ” оÑтаннім розгортаннÑм на %{link}."
@@ -9730,7 +11501,7 @@ msgid "This job requires a manual action"
msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ” ручних дій"
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
-msgstr ""
+msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ запущено автоматично піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ‚Ð°Ð¹Ð¼ÐµÑ€Ð°. Зазвичай вони викориÑтовуютьÑÑ Ð´Ð»Ñ Ñ–Ð½ÐºÑ€ÐµÐ¼ÐµÐ½Ñ‚Ð°Ð»ÑŒÐ½Ð¾Ð³Ð¾ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð² production Ñередовище. У разі ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¾Ð½Ð¾ перетворитьÑÑ Ð½Ð° ручну операцію."
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "Це означає, що ви не можете відправлÑти код, поки не Ñтворите порожній репозиторій або не імпортуєте Ñ–Ñнуючий."
@@ -9738,26 +11509,32 @@ msgstr "Це означає, що ви не можете відправлÑти
msgid "This merge request is locked."
msgstr "Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr "Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð°Ñ” бути затверджений учаÑниками цих груп. Ви можете змінити проектні налаштуваннÑ, вÑтановивши ваш влаÑний ÑпиÑок затверджуючих оÑіб."
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr "Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð°Ñ” бути затверджений цими кориÑтувачами. Ви можете змінити проектні налаштуваннÑ, вÑтановивши ваш влаÑний ÑпиÑок затверджуючих оÑіб."
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "Цей параметр вимкнено, тому що у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу на Ð·Ð°Ð¿Ð¸Ñ Ñƒ поточну гілку"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "Ð¦Ñ Ð¾Ð¿Ñ†Ñ–Ñ Ð½ÐµÐ´Ð¾Ñтупна, поки у Ð²Ð°Ñ Ñ” неіндекÑовані зміни"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "Ð¦Ñ Ñторінка недоÑтупна, тому що ви не можете переглÑдати інформацію по кількох проектах."
msgid "This page will be removed in a future release."
msgstr "Цю Ñторінку буде видалено у майбутній верÑÑ–Ñ—."
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
-msgstr ""
+msgstr "Цей конвеєр викориÑтовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою %{strongStart}Auto DevOps.%{strongEnd}"
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
-msgstr ""
+msgstr "Цей конвеєр викориÑтовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою <b>Auto DevOps</b>"
msgid "This project"
msgstr "Цей проект"
@@ -9774,6 +11551,9 @@ msgstr "Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту вимкнено білінг. Щоб Ñ
msgid "This repository"
msgstr "Цей репозиторій"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "Цей runner буде виконуватиÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ на тих конвеєрах, Ñкі Ñпрацьовують на захищених гілках"
@@ -9783,12 +11563,12 @@ msgstr "Цей параметр можна перевизначати Ð´Ð»Ñ ÐºÐ
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "Цей параметр оновить назву хоÑта, Ñка викориÑтовуєтьÑÑ Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ñ–Ñ— приватних email-Ð°Ð´Ñ€ÐµÑ Ð² підпиÑах комітів. %{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ порівнÑннÑ, оÑкільки воно завелике."
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "Цей кориÑтувач не має ідентифікацій"
@@ -9799,6 +11579,9 @@ msgid "This user will be the author of all events in the activity feed that are
msgstr "Цей кориÑтувач буде автором вÑÑ–Ñ… подій в каналі активноÑÑ‚Ñ–, Ñкі Ñ” результатом оновленнÑ, наприклад ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… гілок або Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… комітів до Ñ–Ñнуючих гілок. При Ñтворенні або перепризначенні ви зможете призначити лише Ñебе кориÑтувачем Ð´Ð»Ñ Ð²Ñ–Ð´Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÐµÐ½Ð½Ñ."
msgid "This will redirect you to an external sign in page."
+msgstr "Це перенаправить Ð²Ð°Ñ Ð½Ð° зовнішню Ñторінку входу."
+
+msgid "This will remove the fork relationship to source project"
msgstr ""
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
@@ -9867,9 +11650,6 @@ msgstr "%s міÑÑці(в) тому"
msgid "Timeago|%s months remaining"
msgstr "залишилоÑÑ %s міÑÑці(в)"
-msgid "Timeago|%s seconds ago"
-msgstr "%s Ñекунд тому"
-
msgid "Timeago|%s seconds remaining"
msgstr "%s Ñекунд, що залишаютьÑÑ"
@@ -9996,7 +11776,7 @@ msgid "Title"
msgstr "Заголовок"
msgid "Titles and Filenames"
-msgstr ""
+msgstr "Заголовки та імена файлів"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
@@ -10005,7 +11785,7 @@ msgid "To GitLab"
msgstr "Ð’ GitLab"
msgid "To access this domain create a new DNS record"
-msgstr ""
+msgstr "Щоб отримати доÑтуп до цього домену, Ñтворіть новий Ð·Ð°Ð¿Ð¸Ñ DNS"
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr "Ð”Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб додати ключ SSH вам необхідно %{generate_link_start}згенерувати його%{link_end} або викориÑтати %{existing_link_start}Ñ–Ñнуючий ключ%{link_end}."
@@ -10056,7 +11836,7 @@ msgid "To keep this project going, create a new merge request"
msgstr ""
msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
-msgstr ""
+msgstr "Ð”Ð»Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— Sentry із Gitlab введіть URL-адреÑу Sentry та токен автентифікації."
msgid "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."
msgstr "Щоб переміÑтити або Ñкопіювати веÑÑŒ проект GitLab з іншої інÑталÑції GitLab до цього, перейдіть на Ñторінку налаштувань оригіналу проекту, Ñтворіть файл екÑпорту та надішліть його Ñюди."
@@ -10068,19 +11848,19 @@ msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page
msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
-msgstr ""
+msgstr "Ð”Ð»Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ ÑˆÐ²Ð¸Ð´ÐºÐ¾Ð´Ñ–Ñ— відображаютьÑÑ Ð»Ð¸ÑˆÐµ <strong>%{display_size} із %{real_size}</strong> файлів."
msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
msgstr ""
msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
-msgstr "Щоб налаштувати аутентифікацію SAML Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— групи через провайдера ідентифікації такої Ñк Azure, Okta, Onelogin, Ping Identity або вашого влаÑного поÑтачальника SAML 2.0:"
+msgstr "Щоб налаштувати автентифікацію SAML Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— групи через провайдера ідентифікації такої Ñк Azure, Okta, Onelogin, Ping Identity або вашого влаÑного поÑтачальника SAML 2.0:"
msgid "To start serving your jobs you can add Runners to your group"
msgstr "Ð”Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð²Ð°ÑˆÐ¸Ñ… завдань ви можете додати Runner’и до вашої групи"
msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
-msgstr ""
+msgstr "Щоб почати ваші Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸ÐºÐ¾Ð½ÑƒÐ²Ð°Ð»Ð¸ÑÑ, ви можете або додати Ñпеціальні Runner’и до вашого проекту, або викориÑтовувати загальні"
msgid "To this GitLab instance"
msgstr "До цього інÑтанÑу GitLab"
@@ -10092,7 +11872,7 @@ msgid "To view the roadmap, add a start or due date to one of your epics in this
msgstr "Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду плану-графіку, додайте дату початку чи Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ð´Ð¾ одного з ваших епіків в цій групі або Ñ—Ñ— підгрупах. При поміÑÑчному переглÑді показуютьÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ епіки за попередній, поточний, та наÑтупні 5 міÑÑців."
msgid "To widen your search, change or remove filters above"
-msgstr ""
+msgstr "Щоб розширити пошук, змініть або видаліть фільтри вище"
msgid "To widen your search, change or remove filters."
msgstr "Щоб розширити пошук, змініть або видаліть фільтри."
@@ -10101,22 +11881,25 @@ msgid "Today"
msgstr "Сьогодні"
msgid "Todo"
-msgstr "Задача"
+msgstr "ÐагдуваннÑ"
+
+msgid "Todo was successfully marked as done."
+msgstr ""
msgid "Todos"
-msgstr "Задачі"
+msgstr "ÐагадуваннÑ"
msgid "Toggle Sidebar"
msgstr "Перемикач бічної панелі"
msgid "Toggle comments for this file"
-msgstr ""
+msgstr "Увімкнути або вимкнути коментарі Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ файлу"
msgid "Toggle commit description"
msgstr "Перемкнути Ð¾Ð¿Ð¸Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð²"
msgid "Toggle commit list"
-msgstr ""
+msgstr "Відкрити або закрити ÑпиÑок комітів"
msgid "Toggle discussion"
msgstr "Перемикач диÑкуÑÑ–Ñ—"
@@ -10142,6 +11925,12 @@ msgstr "Завтра"
msgid "Too many changes to show."
msgstr "Забагато змін Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ."
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "Загальна кількіÑÑ‚ÑŒ внеÑків"
@@ -10166,6 +11955,9 @@ msgstr "ВідÑтежуйте групи задач зі Ñпільною теÐ
msgid "Track time with quick actions"
msgstr "ВідÑтежуйте Ñ‡Ð°Ñ Ð·Ð° допомогою швидких дій"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "У виглÑді дерева"
@@ -10178,13 +11970,28 @@ msgstr "ЗапуÑкати конвеєри Ð´Ð»Ñ Ð²Ñ–Ð´Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÐµÐ½Ð¸Ñ…
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "Викликати конвеєри, коли гілки або теги оновлюютьÑÑ Ð² репозиторії upstream. Ð’ залежноÑÑ‚Ñ– від активноÑÑ‚Ñ– в репозиторії upstream, це може значно збільшити Ð½Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ð° CI runner'и. Вмикайте це тільки тоді, коли знаєте, що вони впораютьÑÑ Ð· навантаженнÑм."
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "ЗапуÑтити цю ручну дію"
msgid "Trigger token:"
-msgstr ""
+msgstr "Токен тригера:"
msgid "Trigger variables:"
+msgstr "Змінні тригера:"
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
msgstr ""
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
@@ -10197,13 +12004,13 @@ msgid "Try again"
msgstr "Спробуйте ще раз"
msgid "Try again?"
-msgstr ""
+msgstr "Спробуйте ще раз?"
msgid "Try all GitLab has to offer for 30 days."
msgstr "Спробуйте вÑÑ– функції GitLab протÑгом 30 днів."
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
-msgstr ""
+msgstr "ВідбуваєтьÑÑ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ–Ð· вашим приÑтроєм. Підключіть його (Ñкщо ви цього ще не зробили) Ñ– натиÑніть кнопку на ньому зараз."
msgid "Turn on Service Desk"
msgstr "Ввімкнути Service Desk"
@@ -10211,6 +12018,9 @@ msgstr "Ввімкнути Service Desk"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "Двофакторна автентифікаціÑ"
@@ -10218,29 +12028,44 @@ msgid "Type"
msgstr "Тип"
msgid "URL"
+msgstr "URL"
+
+msgid "Unable to connect to server: %{error}"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Ðеможливо завантажити порівнÑÐ½Ð½Ñ (diff). %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "Ðеможливо увійти до групи за допомогою SAML через \"%{reason}\""
msgid "Unable to update this epic at this time."
msgstr "Ðеможливо оновити цей епік в даний момент."
-msgid "Unblock"
+msgid "Unarchive project"
msgstr ""
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
+msgid "Unblock"
+msgstr "Розблокувати"
+
msgid "Undo"
msgstr "СкаÑувати"
msgid "Unfortunately, your email message to GitLab could not be processed."
-msgstr ""
+msgstr "Ðа жаль, ваше Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти до GitLab не може бути оброблено."
msgid "Unknown"
msgstr "Ðевідомо"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "Розблокувати"
@@ -10287,7 +12112,7 @@ msgid "Unsubscribe at project level"
msgstr "ВідпиÑатиÑÑ Ð½Ð° рівні проекту"
msgid "Unsubscribe from %{type}"
-msgstr ""
+msgstr "ВідпиÑатиÑÑ Ð²Ñ–Ð´ %{type}"
msgid "Unverified"
msgstr "Ðепідтверджено"
@@ -10301,19 +12126,37 @@ msgstr "Ðезабаром"
msgid "Update"
msgstr "Оновити"
+msgid "Update approvers"
+msgstr "Оновити затверджуючих оÑіб"
+
msgid "Update failed"
-msgstr ""
+msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ"
msgid "Update now"
msgstr "Оновити зараз"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "Оновіть Ñ–Ð¼â€™Ñ Ð³Ñ€ÑƒÐ¿Ð¸, опиÑ, аватар та видиміÑÑ‚ÑŒ."
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr "Оновлено"
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "ОновленнÑ"
msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr "Перейдіть на вищий тарифний план, щоб отримати доÑтуп до функціональноÑÑ‚Ñ– Canary Deployments"
+
+msgid "Upgrade your plan"
msgstr ""
msgid "Upgrade your plan to activate Advanced Global Search."
@@ -10331,20 +12174,23 @@ msgstr "Перейдіть на вищий тарифний план щоб ак
msgid "Upgrade your plan to improve Issue boards."
msgstr "Перейдіть на вищий тарифний план щоб покращити дошки задач."
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "ÐадіÑлати <code>GoogleCodeProjectHosting.json</code> тут:"
msgid "Upload CSV file"
-msgstr ""
+msgstr "Завантажити CSV файл"
msgid "Upload New File"
msgstr "ÐадіÑлати новий файл"
msgid "Upload a certificate for your domain with all intermediates"
-msgstr ""
+msgstr "Завантажити Ñертифікат Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ домену з уÑіма проміжними"
msgid "Upload a private key for your certificate"
-msgstr ""
+msgstr "Завантажити приватний ключ Ð´Ð»Ñ Ñертифіката"
msgid "Upload file"
msgstr "ÐадіÑлати файл"
@@ -10355,18 +12201,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "ÐатиÑніть, щоб надіÑлати"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "Лайки"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "Збір даних про викориÑÑ‚Ð°Ð½Ð½Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾"
msgid "Usage statistics"
msgstr "СтатиÑтика викориÑтаннÑ"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "ВикориÑтовувати <code>%{native_redirect_uri}</code> Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¸Ñ… теÑтів"
@@ -10389,26 +12247,62 @@ msgid "Use your global notification setting"
msgstr "ВикориÑтовуютьÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ"
msgid "Use your smart card to authenticate with the LDAP server."
-msgstr ""
+msgstr "ВикориÑтовуйте Ñмарт-карту Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— на Ñервері LDAP."
msgid "Used by members to sign in to your group in GitLab"
msgstr "ВикориÑтовуєтьÑÑ ÑƒÑ‡Ð°Ñниками Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ у вашу групу в GitLab"
msgid "Used to help configure your identity provider"
+msgstr "ВикориÑтовуєтьÑÑ Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ð¸ в налаштуванні провайдера ідентифікації"
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
msgstr ""
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "Когорти КориÑтувачів показуютьÑÑ Ð»Ð¸ÑˆÐµ тоді, коли увімкнено %{usage_ping_link_start}збір даних про викориÑтаннÑ%{usage_ping_link_end}."
+msgid "User OAuth applications"
+msgstr "OAuth заÑтоÑунки кориÑтувача"
+
msgid "User Settings"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
msgid "User and IP Rate Limits"
msgstr "Ліміти чаÑтоти Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів та IP"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "Мапа кориÑтувачів"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "ÐктивніÑÑ‚ÑŒ"
@@ -10422,22 +12316,22 @@ msgid "UserProfile|Edit profile"
msgstr "Редагувати профіль"
msgid "UserProfile|Explore public groups to find projects to contribute to."
-msgstr ""
+msgstr "ПереглÑдайте публічні групи та знаходьте проекти Ð´Ð»Ñ Ñвоїх внеÑків."
msgid "UserProfile|Groups"
msgstr "Групи"
msgid "UserProfile|Groups are the best way to manage projects and members."
-msgstr ""
+msgstr "Групи — це найкращий ÑпоÑіб керувати проектами та учаÑниками."
msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
-msgstr ""
+msgstr "ПриєднуйтеÑÑŒ до або Ñтворіть групу, щоб почати робити внеÑки через коментарі до задач, а також надÑилаючи запити на злиттÑ!"
msgid "UserProfile|Most Recent Activity"
msgstr "ОÑÑ‚Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–ÑÑ‚ÑŒ"
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "Сніпетів не знайдено."
msgid "UserProfile|Overview"
msgstr "ОглÑд"
@@ -10452,19 +12346,19 @@ msgid "UserProfile|Snippets"
msgstr "Сніпети"
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
-msgstr ""
+msgstr "Сніпети в GitLab можуть бути приватними, внутрішніми та публічними."
msgid "UserProfile|Subscribe"
msgstr "ПідпиÑатиÑÑ"
msgid "UserProfile|This user doesn't have any personal projects"
-msgstr ""
+msgstr "Цей кориÑтувач не має оÑобиÑтих проектів"
msgid "UserProfile|This user has a private profile"
msgstr "Цей кориÑтувач має приватний профіль"
msgid "UserProfile|This user hasn't contributed to any projects"
-msgstr ""
+msgstr "Цей кориÑтувач не робив внеÑків до жодного із проектів"
msgid "UserProfile|View all"
msgstr "ПереглÑнути вÑе"
@@ -10473,30 +12367,51 @@ msgid "UserProfile|View user in admin area"
msgstr "ПереглÑнути кориÑтувача в адмінці"
msgid "UserProfile|You can create a group for several dependent projects."
-msgstr ""
+msgstr "Ви можете Ñтворити групу Ð´Ð»Ñ Ð´ÐµÐºÑ–Ð»ÑŒÐºÐ¾Ñ… залежних проектів."
msgid "UserProfile|You haven't created any personal projects."
-msgstr ""
+msgstr "Ви ще не Ñтворили жодного оÑобиÑтого проекту."
msgid "UserProfile|You haven't created any snippets."
-msgstr ""
+msgstr "Ви ще не Ñтворили жодних Ñніпетів."
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
-msgstr ""
+msgstr "За вашим вибором ваші проекти можуть бути доÑтупні публічно, внутрішньо або приватно."
msgid "Users"
msgstr "КориÑтувачі"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
+msgstr "КориÑтувачі, Ñкі запитують доÑтуп до"
+
+msgid "Users were successfully added."
msgstr ""
-msgid "Validate"
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
msgstr ""
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
+msgid "Validate"
+msgstr "Перевірити"
+
msgid "Validate your GitLab CI configuration file"
+msgstr "Перевірити ваш файл конфігурації Gitlab CI"
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
msgstr ""
msgid "Value"
+msgstr "ЗначеннÑ"
+
+msgid "Variables"
msgstr ""
msgid "Various container registry settings."
@@ -10506,7 +12421,7 @@ msgid "Various email settings."
msgstr "Різноманітні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸."
msgid "Various localization settings."
-msgstr ""
+msgstr "Різні параметри локалізації."
msgid "Various settings that affect GitLab performance."
msgstr "Різноманітні налаштуваннÑ, що впливають на продуктивніÑÑ‚ÑŒ GitLab."
@@ -10515,7 +12430,7 @@ msgid "Verification information"
msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ перевірку"
msgid "Verification status"
-msgstr ""
+msgstr "Стан перевірки"
msgid "Verified"
msgstr "Підтверджено"
@@ -10523,20 +12438,17 @@ msgstr "Підтверджено"
msgid "Version"
msgstr "ВерÑÑ–Ñ"
-msgid "View %{alerts}"
-msgstr "ПереглÑнути %{alerts}"
-
msgid "View app"
msgstr "ПереглÑнути заÑтоÑунок"
msgid "View deployment"
-msgstr ""
+msgstr "ПереглÑнути розгортаннÑ"
msgid "View details: %{details_url}"
-msgstr ""
+msgstr "ПереглÑнути деталі: %{details_url}"
-msgid "View documentation"
-msgstr "ПереглÑнути документацію"
+msgid "View eligible approvers"
+msgstr "ПереглÑнути доÑтупних оÑіб Ð´Ð»Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ"
msgid "View epics list"
msgstr "ПереглÑнути ÑпиÑок епіків"
@@ -10547,12 +12459,15 @@ msgstr "ПереглÑд файла @ "
msgid "View group labels"
msgstr "ПереглÑнути мітки групи"
-msgid "View issue"
-msgstr "ПереглÑнути задачу"
+msgid "View in Sentry"
+msgstr "ПереглÑнути в Sentry"
msgid "View it on GitLab"
msgstr "ПереглÑнути це на GitLab"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "ПереглÑнути завданнÑ"
@@ -10575,7 +12490,7 @@ msgid "View the documentation"
msgstr "ПереглÑнути документацію"
msgid "Viewing commit"
-msgstr ""
+msgstr "ПереглÑд коміту"
msgid "Visibility and access controls"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð¸Ð¼Ð¾ÑÑ‚Ñ– та доÑтупу"
@@ -10586,6 +12501,9 @@ msgstr "Рівень видимоÑÑ‚Ñ–"
msgid "Visibility level:"
msgstr "Рівень видимоÑÑ‚Ñ–:"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "ВидиміÑÑ‚ÑŒ:"
@@ -10602,10 +12520,10 @@ msgid "VisibilityLevel|Unknown"
msgstr "Ðевідомий"
msgid "Vulnerability Chart"
-msgstr ""
+msgstr "СтатиÑтика вразливоÑтей"
msgid "Vulnerability List"
-msgstr ""
+msgstr "СпиÑок вразливоÑтей"
msgid "Vulnerability|Class"
msgstr "КлаÑ"
@@ -10622,17 +12540,23 @@ msgstr "Файл"
msgid "Vulnerability|Identifiers"
msgstr "Ідентифікатори"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "ІнÑтанÑи"
msgid "Vulnerability|Links"
msgstr "ПоÑиланнÑ"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "Проект"
msgid "Vulnerability|Report Type"
-msgstr ""
+msgstr "Тип звіту"
msgid "Vulnerability|Severity"
msgstr "Рівень"
@@ -10641,19 +12565,19 @@ msgid "Want to see the data? Please ask an administrator for access."
msgstr "Хочете побачити дані? Будь лаÑка, попроÑить у адмініÑтратора доÑтуп."
msgid "We can't find an epic that matches what you are looking for."
-msgstr ""
+msgstr "Ми не можемо знайти епік, Ñкий відповідає тому, що ви шукаєте."
msgid "We can't find an issue that matches what you are looking for."
-msgstr ""
+msgstr "Ми не можемо знайти задачу, Ñка відповідає тому, що ви шукаєте."
msgid "We could not determine the path to remove the epic"
-msgstr ""
+msgstr "Ми не змогли визначити шлÑÑ… до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÐ¿Ñ–ÐºÑƒ"
msgid "We could not determine the path to remove the issue"
-msgstr ""
+msgstr "Ми не змогли визначити шлÑÑ… до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–"
msgid "We couldn't find any results matching"
-msgstr ""
+msgstr "Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ відповідні результати"
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Ми виÑвили потенційний Ñпам у %{humanized_resource_name}. Будь лаÑка, введіть цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ reCAPTCHA, щоб продовжити."
@@ -10662,7 +12586,7 @@ msgid "We don't have enough data to show this stage."
msgstr "Ми не маємо доÑтатньо даних Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— Ñтадії."
msgid "We heard back from your U2F device. You have been authenticated."
-msgstr ""
+msgstr "Ми отримали відповідь від вашого приÑтрою U2F. Ви пройшли автентифікацію."
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr "Ми хочемо бути впевнені, що це ви, будь лаÑка, підтвердіть, що ви не робот."
@@ -10671,11 +12595,17 @@ msgid "Web IDE"
msgstr "Веб-IDE"
msgid "Web Terminal"
-msgstr ""
+msgstr "Веб-термінал"
msgid "Web terminal"
msgstr "Веб-термінал"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Веб-гук дозволÑÑ” вам викликати URL Ñкщо, наприклад, був відправлений новий код або Ñтворено нову задачу. Ви можете налаштувати його так, щоб він реагував на певні події (відправки коду, задачі або запити на злиттÑ). Групові веб-гуки заÑтоÑовуютьÑÑ Ð´Ð¾ вÑÑ–Ñ… проектів в групі Ñ– дозволÑÑŽÑ‚ÑŒ вам Ñтандартизувати Ñ—Ñ… Ð´Ð»Ñ Ð²Ñієї вашої групи."
@@ -10694,11 +12624,21 @@ msgstr "Коли runner закріплений (за проектами), йог
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "Якщо увімкнено, кориÑтувачі не можуть викориÑтовувати GitLab, поки вони не приймуть умови."
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "Якщо залишити URL порожнім, можна вÑтановлювати мітки клаÑифікації без Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ð¹ проекту та Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ñ— авторизації."
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "When:"
-msgstr ""
+msgstr "Коли:"
msgid "Who can see this group?"
msgstr "Хто може бачити цю групу?"
@@ -10709,6 +12649,9 @@ msgstr "Хто зможе побачити цю групу?"
msgid "Wiki"
msgstr "Вікі"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "Клонувати ваш вікі"
@@ -10820,6 +12763,9 @@ msgstr "Створити Ñторінку"
msgid "Wiki|Create page"
msgstr "Створити Ñторінку"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ cторінки"
@@ -10838,11 +12784,14 @@ msgstr "ВерÑÑ–Ñ Ñторінки"
msgid "Wiki|Pages"
msgstr "Сторінки"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Вікі-Ñторінки"
msgid "Will deploy to"
-msgstr ""
+msgstr "Розгорне на"
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
msgstr "З аналітикою контриб’юторів ви може вивчати активніÑÑ‚ÑŒ в задачах, запитах на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– подій відправки коду Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— організації Ñ– Ñ—Ñ— учаÑників."
@@ -10850,10 +12799,22 @@ msgstr "З аналітикою контриб’юторів ви може ви
msgid "Withdraw Access Request"
msgstr "СкаÑувати запит доÑтупу"
-msgid "Write a comment or drag your files here…"
+msgid "Workflow Help"
msgstr ""
+msgid "Write"
+msgstr "ÐапиÑати"
+
+msgid "Write a comment or drag your files here…"
+msgstr "Ðапишіть коментар або перетÑгніть файли Ñюди…"
+
+msgid "Write access allowed"
+msgstr "ДоÑтуп на Ð·Ð°Ð¿Ð¸Ñ Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¾"
+
msgid "Write milestone description..."
+msgstr "Створити Ð¾Ð¿Ð¸Ñ ÐµÑ‚Ð°Ð¿Ñƒ..."
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
@@ -10869,7 +12830,7 @@ msgid "Yesterday"
msgstr "Вчора"
msgid "You"
-msgstr ""
+msgstr "Ви"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr "Ви — адмініÑтратор, а це означає, що Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ <strong>%{client_name}</strong> дозволить їм взаємодіÑти з GitLab Ñк адмініÑтратору. Продовжуйте обережно."
@@ -10886,11 +12847,14 @@ msgstr "Ви збираєтеÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ зв'Ñзок з форка Ð
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "Ви збираєтеÑÑ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‚Ð¸ проект %{project_full_name} іншому влаÑнику. Ви ÐБСОЛЮТÐО впевнені?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "Ви знаходитеÑÑ Ð½Ð° інÑтанÑÑ– Gitlab \"тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ\"."
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
-msgstr ""
+msgstr "Ви отримуєте це повідомленнÑ, бо ви Ñ” адмініÑтратором GitLab Ð´Ð»Ñ %{url}."
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
msgstr "ЗаміÑÑ‚ÑŒ цього ви можете %{linkStart}переглÑнути бінарні дані%{linkEnd}."
@@ -10901,6 +12865,15 @@ msgstr "Ви також можете Ñтворити проект із кома
msgid "You can also star a label to make it a priority label."
msgstr "Ви можете додати мітку в обрані, щоб зробити її пріоритетною."
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr "Ви також можете протеÑтувати ваш %{gitlab_ci_yml} за допомогою %{lint_link_start}перевірки CI конфігурації%{lint_link_end}"
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr "Також ви можете завантажувати файли з вашого комп'ютера за допомогою нижченаведених інÑтрукцій."
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr "Ви можете Ñтворювати файли безпоÑередньо в GitLab за допомогою одного з наÑтупних варіантів."
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "Ви можете легко робити внеÑки до них, запроÑивши доÑтуп до цих груп."
@@ -10910,6 +12883,12 @@ msgstr "Ви можете легко вÑтановити Runner на клаÑÑ‚
msgid "You can move around the graph by using the arrow keys."
msgstr "Ви можете переÑуватиÑÑ Ð¿Ð¾ графіку за допомогою клавіш зі Ñтрілками."
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "Ви можете додавати файли тільки коли перебуваєте в гілці"
@@ -10917,6 +12896,9 @@ msgid "You can only edit files when you are on a branch"
msgstr "Ви можете редагувати файли, лише перебуваючи у ÑкійÑÑŒ гілці"
msgid "You can only merge once the items above are resolved"
+msgstr "Ви можете зливати лише коли вищезгадані пункти будуть вирішені"
+
+msgid "You can only transfer the project to namespaces you manage."
msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
@@ -10928,17 +12910,35 @@ msgstr "Ви можете налаштувати Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð½Ð° викоÑ
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "Ви можете перевірити Ñвій .gitlab-ci.yml у %{linkStart}CI Lint%{linkEnd}."
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "Ви не можете запиÑувати на вторинні інÑтанÑи \"тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ\" GitLab Geo. Будь лаÑка викориÑтовуйте %{link_to_primary_node}."
msgid "You cannot write to this read-only GitLab instance."
msgstr "Ви не можете запиÑувати на цей \"тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ\" інÑÑ‚Ð°Ð½Ñ GitLab."
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "У Ð²Ð°Ñ Ñ‰Ðµ немає підпиÑок"
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
-msgstr ""
+msgstr "Ви не маєте дозволу запуÑкати Веб-термінал. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора проекту."
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr "У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” необхідних прав доÑтупу, щоб перевизначити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñинхронізації LDAP-груп."
@@ -10950,17 +12950,32 @@ msgid "You don't have any authorized applications"
msgstr "Ви не маєте ніÑких авторизованих заÑтоÑунків"
msgid "You don't have any deployments right now."
+msgstr "Ðаразі у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” ніÑких розгортань."
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
msgstr ""
msgid "You have no permissions"
msgstr "У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” прав доÑтупу"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr "Ви не додали жодної затверджуючої оÑоби. Почніть з Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів або груп."
+
msgid "You have reached your project limit"
msgstr "Ви доÑÑгли Ñвого ліміту по кількоÑÑ‚Ñ– проектів"
-msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
msgstr ""
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr "Ви також можете додати змінні, що будуть доÑтупними Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑ‰ÐµÐ½Ð¾Ð³Ð¾ заÑтоÑунку шлÑхом Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð¿Ñ€ÐµÑ„Ñ–ÐºÑу <code>K8S_SECRET_</code> до Ñ—Ñ… імен."
+
msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr "Ви повинні прийнÑти правила кориÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑервіÑом Ñ– політику конфіденційноÑÑ‚Ñ– Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, щоб Ñтворити обліковий запиÑ"
@@ -10970,7 +12985,7 @@ msgstr "Ви повинні мати доÑтуп керівника Ð´Ð»Ñ Ð¿Ñ€
msgid "You need a different license to enable FileLocks feature"
msgstr "Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— функції Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¤Ð°Ð¹Ð»Ñ–Ð² вам потрібна інша ліцензіÑ"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10980,12 +12995,24 @@ msgid "You need permission."
msgstr "Вам потрібен дозвіл"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr "Вам треба зареєÑтрувати програму Ð´Ð»Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації перед налаштуваннÑм приÑтрою U2F."
+
+msgid "You need to specify both an Access Token and a Host URL."
msgstr ""
-msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
msgstr ""
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr "Ви втратите вÑÑ– зміни, внеÑені вами в цей файл. Цю дію не можна ÑкаÑувати."
+
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr "Ви втратите вÑÑ– неіндекÑовані зміни, внеÑені вами в цей проект. Цю дію не можна ÑкаÑувати."
+
+msgid "You will need to update your local repositories to point to the new location."
msgstr ""
msgid "You will not get any notifications via email"
@@ -11015,6 +13042,15 @@ msgstr "Ви не зможете відправлÑти та отримуватÐ
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Вам необхідно викориÑтовувати різні імена гілок Ð´Ð»Ñ ÐºÐ¾Ñ€ÐµÐºÑ‚Ð½Ð¾Ð³Ð¾ порівнÑннÑ."
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "Ви отримали цей електронний лиÑÑ‚, оÑкільки %{reason}."
@@ -11043,9 +13079,18 @@ msgid "Your Projects' Activity"
msgstr "ÐктивніÑÑ‚ÑŒ ваших проектів"
msgid "Your Todos"
-msgstr "Ваші Задачі"
+msgstr "Ваші ÐагадуваннÑ"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr "Ваш приÑтрій U2F має бути налаштований. Підключіть його (Ñкщо ви цього ще не зробили) Ñ– натиÑніть кнопку зліва."
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr ""
msgid "Your applications (%{size})"
@@ -11055,7 +13100,7 @@ msgid "Your authorized applications"
msgstr "Ваші авторизовані заÑтоÑунки"
msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
-msgstr ""
+msgstr "Ваш браузер не підтримує U2F. Будь лаÑка, викориÑтовйте Google Chrome Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¿'ютера (верÑÑ–ÑŽ 41 або новішу)."
msgid "Your changes can be committed to %{branch_name} because a merge request is open."
msgstr "Ваші зміни можуть бути закомічені до %{branch_name}, оÑкільки запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸Ð¹."
@@ -11066,46 +13111,70 @@ msgstr "Ваші зміни закомічено. Коміт %{commitId} %{commi
msgid "Your changes have been saved"
msgstr "Ваші зміни було збережено"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "Ваш коментар не буде видимим Ð´Ð»Ñ Ð²ÑÑ–Ñ…."
-msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
msgstr ""
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr "Ваш приÑтрій уÑпішно налаштовано! Дайте йому ім'Ñ Ñ‚Ð° зареєÑтруйте його на Ñервері GitLab."
+
msgid "Your groups"
msgstr "Ваші групи"
msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "Ваші задачі імпортуютьÑÑ. ПіÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð²Ð¸ отримаєте Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ електронній пошті із підтвердженнÑм."
msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "Ваші задачі будуть імпортовані в фоні. ПіÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð²Ð¸ отримаєте Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ електронній пошті із підтвердженнÑм."
msgid "Your name"
msgstr "Ваше ім'Ñ"
-msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
msgstr ""
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr "Ваш ліміт проектів Ñкладає %{limit}! Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора, щоб його збільшити"
+
msgid "Your projects"
msgstr "Ваші проекти"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "видалений кориÑтувач"
+msgid "added %{created_at_timeago}"
+msgstr "додано %{created_at_timeago}"
+
msgid "ago"
msgstr "тому"
msgid "allowed to fail"
-msgstr ""
+msgstr "невдача дозволена"
msgid "among other things"
msgstr "тощо"
-msgid "assign yourself"
-msgstr "призначити Ñебе"
+msgid "at"
+msgstr "в"
msgid "attach a new file"
+msgstr "прикріпити новий файл"
+
+msgid "authored"
msgstr ""
msgid "branch name"
@@ -11132,6 +13201,20 @@ msgstr "%{vulnerability} впливає на %{namespace}."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} більше"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] "%{reportType} %{status} виÑвив %{dismissedCount} відхилену вразливіÑÑ‚ÑŒ"
+msgstr[1] "%{reportType} %{status} виÑвив %{dismissedCount} відхилені вразливоÑÑ‚Ñ–"
+msgstr[2] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей"
+msgstr[3] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей"
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] "%{reportType} %{status} виÑвив %{dismissedCount} відхилену вразливіÑÑ‚ÑŒ лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
+msgstr[1] "%{reportType} %{status} виÑвив %{dismissedCount} відхилені вразливоÑÑ‚Ñ– лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
+msgstr[2] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
+msgstr[3] "%{reportType} %{status} виÑвив %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} виÑвив %{fixedCount} виправлену вразливіÑÑ‚ÑŒ"
@@ -11139,6 +13222,9 @@ msgstr[1] "%{reportType} %{status} виÑвив %{fixedCount} виправлен
msgstr[2] "%{reportType} %{status} виÑвив %{fixedCount} виправлених вразливоÑтей"
msgstr[3] "%{reportType} %{status} виÑвив %{fixedCount} виправлених вразливоÑтей"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} виÑвив %{fixedCount} вирішених та %{dismissedCount} відхилених вразливоÑтей"
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} виÑвив %{newCount} нову вразливіÑÑ‚ÑŒ"
@@ -11146,6 +13232,15 @@ msgstr[1] "%{reportType} %{status} виÑвив %{newCount} нові вразлÐ
msgstr[2] "%{reportType} %{status} виÑвив %{newCount} нових вразливоÑтей"
msgstr[3] "%{reportType} %{status} виÑвив %{newCount} нових вразливоÑтей"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} виÑвив %{newCount} нову, %{fixedCount} вирішених та %{dismissedCount} відхилених вразливоÑтей"
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} виÑвив %{newCount} нових та %{dismissedCount} відхилених вразливоÑтей"
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr "%{reportType} %{status} виÑвив %{newCount} новий та %{dismissedCount} відхилених вразливоÑтей лише Ð´Ð»Ñ Ð³Ñ–Ð»ÐºÐ¸-джерела"
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} виÑвив %{newCount} нових та %{fixedCount} виправлених вразливоÑтей"
@@ -11181,6 +13276,9 @@ msgstr "%{reportType} завантажуєтьÑÑ"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType}: Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð·Ð²ÐµÐ»Ð¾ до помилки"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(помилки під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ñ–Ð²)"
@@ -11190,6 +13288,15 @@ msgstr "(завантажуєтьÑÑ)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(завантажуєтьÑÑ, помилки під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ñ–Ð²)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "КлаÑ"
@@ -11199,17 +13306,29 @@ msgstr "ЯкіÑÑ‚ÑŒ коду"
msgid "ciReport|Confidence"
msgstr "ВпевненіÑÑ‚ÑŒ"
+msgid "ciReport|Container Scanning"
+msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ð°"
+
msgid "ciReport|Container scanning"
msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ð°"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ¹Ð½ÐµÑ€Ñ–Ð² виÑвлÑÑ” відомі вразливоÑÑ‚Ñ– у ваших Docker образах."
+msgid "ciReport|Create issue"
+msgstr "Створити задачу"
+
+msgid "ciReport|Create merge request"
+msgstr "Створити запит на злиттÑ"
+
+msgid "ciReport|Created %{eventType}"
+msgstr "Створено %{eventType}"
+
msgid "ciReport|DAST"
msgstr "DAST"
msgid "ciReport|Dependency Scanning"
-msgstr ""
+msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñтей"
msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
msgstr "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñтей виÑвлÑÑ” відомі вразливоÑÑ‚Ñ– у залежноÑÑ‚ÑÑ… вашого коду."
@@ -11227,10 +13346,10 @@ msgid "ciReport|Dismissed by"
msgstr "Відхилено"
msgid "ciReport|Download and apply the patch to fix this vulnerability."
-msgstr ""
+msgstr "Завантажити або заÑтоÑувати патч, щоб виправити цю вразливіÑÑ‚ÑŒ."
msgid "ciReport|Download patch"
-msgstr ""
+msgstr "Завантажити патч"
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgstr "Динамічне теÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸ заÑтоÑунків (DAST) виÑвлÑÑ” відомі вразливоÑÑ‚Ñ– у вашому веб-заÑтоÑунку."
@@ -11247,9 +13366,18 @@ msgstr "Виправлено:"
msgid "ciReport|Identifiers"
msgstr "Ідентифікатори"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr "Реалізувати це рішеннÑ, Ñтворивши запит на злиттÑ"
+
msgid "ciReport|Instances"
msgstr "ІнÑтанÑи"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr "ДоÑлідити цю вразливіÑÑ‚ÑŒ, Ñтворивши задачу"
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "ДізнатиÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про взаємодію з звітами безпеки (Ðльфа)."
@@ -11297,9 +13425,6 @@ msgstr "Ðемає змін у показниках продуктивноÑÑ‚Ñ–
msgid "ciReport|Performance metrics"
msgstr "Показники продуктивноÑÑ‚Ñ–"
-msgid "ciReport|Revert dismissal"
-msgstr "Відмінити відхиленнÑ"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11321,6 +13446,9 @@ msgstr "Статичне теÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸ заÑтоÑункÑ
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "Помилка при Ñтворенні задачі. Будь лаÑка Ñпробуйте знову."
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr "Помилка при Ñтворенні запиту на злиттÑ. Будь лаÑка, Ñпробуйт знову."
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "Помилка при відхиленні вразливоÑÑ‚Ñ–. Будь лаÑка, Ñпробуйте знову."
@@ -11339,9 +13467,18 @@ msgstr "Помилка при завантаженні звіту по ÑканÑ
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "Помилка при відміні відхиленнÑ. Будь лаÑка, Ñпробуйте знову."
+msgid "ciReport|Undo dismiss"
+msgstr "Відмінити відхиленнÑ"
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "Оновити %{name} з %{version} до %{fixed}."
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "ВикориÑтовуєтьÑÑ %{packagesString}"
@@ -11355,10 +13492,31 @@ msgstr "ПереглÑнути повний звіт"
msgid "ciReport|on pipeline"
msgstr "в конвеєрі"
-msgid "command line instructions"
-msgstr "інÑтрукції Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð³Ð¾ Ñ€Ñдка"
-
msgid "commented on %{link_to_project}"
+msgstr "прокоментовано в %{link_to_project}"
+
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11384,20 +13542,23 @@ msgstr[2] "днів"
msgstr[3] "днів"
msgid "deleted"
-msgstr ""
+msgstr "видалено"
msgid "deploy token"
msgstr "токен Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "вимкнено"
msgid "discussion resolved"
msgid_plural "discussions resolved"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¾"
+msgstr[1] "Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¾"
+msgstr[2] "обговорень завершено"
+msgstr[3] "обговорень завершено"
msgid "done"
msgstr "готово"
@@ -11413,17 +13574,32 @@ msgid "enabled"
msgstr "увімкнено"
msgid "epic"
-msgstr ""
+msgstr "епік"
msgid "error"
-msgstr ""
+msgstr "помилка"
msgid "error code:"
-msgstr ""
+msgstr "код помилки:"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} перезапиÑує запланований Ñ‡Ð°Ñ Ð¾Ñтаннім значеннÑм."
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту"
@@ -11431,22 +13607,16 @@ msgid "from"
msgstr "від"
msgid "group"
-msgstr ""
-
-msgid "help"
-msgstr "допомога"
+msgstr "група"
msgid "here"
msgstr "тут"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
msgid "image diff"
-msgstr ""
+msgstr "Ñ€Ñ–Ð·Ð½Ð¸Ñ†Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ"
msgid "import flow"
msgstr "процедура імпорту"
@@ -11455,13 +13625,13 @@ msgid "importing"
msgstr "імпорт"
msgid "in group %{link_to_group}"
-msgstr ""
+msgstr "в групі %{link_to_group}"
msgid "in project %{link_to_project}"
-msgstr ""
+msgstr "в проекті %{link_to_project}"
msgid "index"
-msgstr ""
+msgstr "індекÑ"
msgid "instance completed"
msgid_plural "instances completed"
@@ -11483,7 +13653,7 @@ msgid "is out of the hierarchy of the Group owning the template"
msgstr ""
msgid "issue"
-msgstr ""
+msgstr "задача"
msgid "issue boards"
msgstr "дошки Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡"
@@ -11492,13 +13662,13 @@ msgid "it is stored externally"
msgstr ""
msgid "it is stored in LFS"
-msgstr ""
+msgstr "зберігаєтьÑÑ Ð² LFS"
msgid "it is too large"
msgstr ""
msgid "latest"
-msgstr ""
+msgstr "оÑтанній"
msgid "latest deployment"
msgstr "оÑтаннє розгортаннÑ"
@@ -11506,6 +13676,9 @@ msgstr "оÑтаннє розгортаннÑ"
msgid "latest version"
msgstr "оÑÑ‚Ð°Ð½Ð½Ñ Ð²ÐµÑ€ÑÑ–Ñ"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "керувати ліцензіÑми"
@@ -11523,19 +13696,22 @@ msgstr[2] "запитів на злиттÑ"
msgstr[3] "запитів на злиттÑ"
msgid "missing"
-msgstr ""
+msgstr "відÑутні"
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
-msgstr ""
+msgstr "%{commitCount} і %{mergeCommitCount} буде додано до %{targetBranch}."
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr "%{commitCount} буде додано в %{targetBranch}."
msgid "mrWidgetCommitsAdded|1 merge commit"
-msgstr ""
+msgstr "1 коміт-злиттÑ"
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr "Будь лаÑка відновіть Ñ—Ñ— або викориÑтовуйте іншу %{missingBranchName} гілку"
msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
-msgstr ""
+msgstr "%{link_start}Докладніше про Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚Ñ–Ð²%{link_end}"
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr "ВикориÑÑ‚Ð°Ð½Ð½Ñ %{metricsLinkStart} пам’ÑÑ‚Ñ– %{metricsLinkEnd} %{emphasisStart} впало %{emphasisEnd} з %{memoryFrom}Мб до %{memoryTo}Мб"
@@ -11553,10 +13729,10 @@ msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr "ДозволÑÑ” коміти від учаÑників, Ñкі можуть зливати до цільової гілки"
msgid "mrWidget|An error occurred while removing your approval."
-msgstr ""
+msgstr "Під Ñ‡Ð°Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°."
msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
-msgstr ""
+msgstr "Помилка при отриманні даних про Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ."
msgid "mrWidget|An error occurred while submitting your approval."
msgstr "Помилка при обробці вашого затвердженнÑ."
@@ -11564,6 +13740,9 @@ msgstr "Помилка при обробці вашого затвердженн
msgid "mrWidget|Approve"
msgstr "Затвердити"
+msgid "mrWidget|Approve additionally"
+msgstr "Затвердити додатково"
+
msgid "mrWidget|Approved by"
msgstr "Затверджено"
@@ -11595,7 +13774,7 @@ msgid "mrWidget|Create an issue to resolve them later"
msgstr "Створіть задачу, щоб вирішити їх пізніше"
msgid "mrWidget|Delete source branch"
-msgstr ""
+msgstr "Видалити гілку-джерело"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "СтатиÑтика Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– недоÑтупна"
@@ -11636,16 +13815,19 @@ msgstr "Злити локально"
msgid "mrWidget|Merge request approved"
msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¾"
+msgid "mrWidget|Merge request approved."
+msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¾."
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ»Ð¾ затверджено; Ви можете затвердити додатково"
msgid "mrWidget|Merged by"
msgstr "Злито"
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ потрібне"
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr "Ð—Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ Ñ” обов’Ñзковим; але ви вÑе одно можете це зробити"
msgid "mrWidget|Open in Web IDE"
@@ -11701,6 +13883,9 @@ msgstr "Ðнулювати"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "Ðнулювати цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð° допомогою нового запиту на злиттÑ"
+msgid "mrWidget|Revoke approval"
+msgstr "СкаÑувати затвердженнÑ"
+
msgid "mrWidget|Set by"
msgstr "Ð’Ñтановлено"
@@ -11720,19 +13905,19 @@ msgid "mrWidget|The source branch HEAD has recently changed. Please reload the p
msgstr "HEAD гілки-джерела нещодавно було змінено. Будь лаÑка оновіть Ñторінку Ñ– переглÑньте зміни перед злиттÑм"
msgid "mrWidget|The source branch has been deleted"
-msgstr ""
+msgstr "Гілку-джерело видалено"
msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
msgstr "Гілка-джерело на %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} цільової гілки"
msgid "mrWidget|The source branch is being deleted"
-msgstr ""
+msgstr "Гілка-джерело в процеÑÑ– видаленнÑ"
msgid "mrWidget|The source branch will be deleted"
-msgstr ""
+msgstr "Гілку-джерело буде видалено"
msgid "mrWidget|The source branch will not be deleted"
-msgstr ""
+msgstr "Гілку-джерело не буде видалено"
msgid "mrWidget|There are merge conflicts"
msgstr "Ñ–Ñнують конфлікти при злитті"
@@ -11741,7 +13926,7 @@ msgid "mrWidget|There are unresolved discussions. Please resolve these discussio
msgstr "ПриÑутні незавершені обговореннÑ. Будь лаÑка завершіть Ñ—Ñ…"
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
-msgstr ""
+msgstr "Ð¦Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð·Ð»Ð¸Ð²Ð°Ñ” зміни з цільової гілки до гілки-джерела. Її не можна викориÑтовувати, оÑкільки гілка-джерело Ñ” захищеною."
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "ВідбулаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при автоматичному злитті цього запиту"
@@ -11756,7 +13941,7 @@ msgid "mrWidget|You are not allowed to edit this project directly. Please fork t
msgstr "Ви не можете безпоÑередньо редагувати цей проект. Будь лаÑка, зробіть форк, щоб внеÑти зміни."
msgid "mrWidget|You can delete the source branch now"
-msgstr ""
+msgstr "Тепер ви можете видалити гілку-джерело"
msgid "mrWidget|You can merge this merge request manually using the"
msgstr "Ви можете прийнÑти цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ€ÑƒÑ‡Ð½Ñƒ за допомогою"
@@ -11780,19 +13965,19 @@ msgid "new merge request"
msgstr "Ðовий запит на злиттÑ"
msgid "none"
-msgstr ""
+msgstr "немає"
msgid "notification emails"
msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою"
msgid "nounSeries|%{firstItem} and %{lastItem}"
-msgstr ""
+msgstr "%{firstItem} Ñ– %{lastItem}"
msgid "nounSeries|%{item}, %{nextItem}"
-msgstr ""
+msgstr "%{item}, %{nextItem}"
msgid "nounSeries|%{item}, and %{lastItem}"
-msgstr ""
+msgstr "%{item}, Ñ– %{lastItem}"
msgid "or"
msgstr "або"
@@ -11817,12 +14002,22 @@ msgstr "пароль"
msgid "personal access token"
msgstr "оÑобиÑтий токен доÑтупу"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "private"
-msgstr ""
+msgstr "приватний"
msgid "private key does not match certificate."
msgstr "приватний ключ не відповідає Ñертифікату."
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "проект"
@@ -11831,10 +14026,10 @@ msgstr[2] "проектів"
msgstr[3] "проектів"
msgid "quick actions"
-msgstr ""
+msgstr "швидкі дії"
msgid "register"
-msgstr ""
+msgstr "зареєÑтруватиÑÑ"
msgid "remaining"
msgstr "залишилоÑÑŒ"
@@ -11858,18 +14053,45 @@ msgstr[1] "відповіді"
msgstr[2] "відповідей"
msgstr[3] "відповідей"
+msgid "reset it."
+msgstr ""
+
msgid "score"
+msgstr "результат"
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr "помилка при Ñтворенні запиту на злиттÑ"
+
+msgid "severity|Critical"
msgstr ""
-msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgid "severity|High"
msgstr ""
-msgid "show less"
+msgid "severity|Info"
msgstr ""
-msgid "sign in"
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
msgstr ""
+msgid "severity|Unknown"
+msgstr ""
+
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr "має бути вищим за %{access}, уÑпадкованого з групи %{group_name}"
+
+msgid "show less"
+msgstr "показати менше"
+
+msgid "sign in"
+msgstr "увійти"
+
msgid "source"
msgstr "джерело"
@@ -11883,13 +14105,16 @@ msgid "started"
msgstr "розпочато"
msgid "stuck"
+msgstr "заблоковано"
+
+msgid "success"
msgstr ""
msgid "syntax is correct"
-msgstr ""
+msgstr "ÑинтакÑÐ¸Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¸Ð¹"
msgid "syntax is incorrect"
-msgstr ""
+msgstr "ÑинтакÑÐ¸Ñ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¸Ð¹"
msgid "this document"
msgstr "цей документ"
@@ -11898,10 +14123,10 @@ msgid "to help your contributors communicate effectively!"
msgstr "щоб допомогти вашим контриб’юторам ефективно ÑпілкуватиÑÑ!"
msgid "triggered"
-msgstr ""
+msgstr "запущено"
msgid "updated"
-msgstr ""
+msgstr "оновлено"
msgid "username"
msgstr "ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
@@ -11910,13 +14135,13 @@ msgid "uses Kubernetes clusters to deploy your code!"
msgstr "викориÑтовує клаÑтери Kubernetes Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð´Ñƒ!"
msgid "verify ownership"
-msgstr ""
+msgstr "перевірити право влаÑноÑÑ‚Ñ–"
msgid "view it on GitLab"
msgstr "переглÑнути це на GitLab"
msgid "view the blob"
-msgstr ""
+msgstr "переглÑнути бінарні дані"
msgid "with %{additions} additions, %{deletions} deletions."
msgstr "з %{additions} додаваннÑми Ñ– %{deletions} видаленнÑми."
@@ -11929,5 +14154,5 @@ msgstr[2] "протÑгом %d хвилин "
msgstr[3] "протÑгом %d хвилин "
msgid "yaml invalid"
-msgstr ""
+msgstr "yaml недійÑний"
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 522946d739f..7ab1b5ecfe7 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:12\n"
+"PO-Revision-Date: 2019-04-12 12:36\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr "状æ€"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr "和"
@@ -30,16 +39,17 @@ msgid_plural " improved on %d points"
msgstr[0] " æ高 %d 点"
msgid " or "
-msgstr ""
+msgstr " 或 "
msgid " or <#epic id>"
-msgstr ""
+msgstr " 或<#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr "或 <#issue id>"
-msgid "\"%{query}\" in projects"
-msgstr "在项目中\"%{query}\""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%dæ¡è¯„论"
msgid "%d commit"
msgid_plural "%d commits"
@@ -50,7 +60,7 @@ msgid_plural "%d commits behind"
msgstr[0] "è½åŽ %d 个æ交"
msgid "%d commits"
-msgstr ""
+msgstr "%d 次æ交"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -70,7 +80,7 @@ msgstr[0] "%d 个议题"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
+msgstr[0] "已选择%d个议题"
msgid "%d layer"
msgid_plural "%d layers"
@@ -111,12 +121,25 @@ msgstr "%{counter_storage} (%{counter_repositories} 个存储库, %{counter_bu
msgid "%{count} %{alerts}"
msgstr "%{count}个%{alerts}"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] "éœ€è¦ %{name} çš„ %{count} 个核准"
+
+msgid "%{count} approvals from %{name}"
+msgstr "æ¥è‡ª %{name} çš„ %{count} 个核准"
+
msgid "%{count} more"
-msgstr ""
+msgstr "其余 %{count} 项"
msgid "%{count} more assignees"
msgstr "åŠå…¶ä»–%{count}å指派人"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr "%{name} 的 %{count}/%{required} 个核准"
+
+msgid "%{count} of %{total}"
+msgstr "%{count} / %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} ä½å‚与者"
@@ -131,8 +154,8 @@ msgstr "%{filePath} 已删除"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} 更多"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr "在此æ交中找ä¸åˆ° %{gitlab_ci_yml}"
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}群组%{group_docs_link_end} å…许您管ç†ã€å作多个项目。群组的æˆå‘˜å¯ä»¥è®¿é—®ç¾¤ç»„下的所有项目。"
@@ -140,15 +163,24 @@ msgstr "%{group_docs_link_start}群组%{group_docs_link_end} å…许您管ç†ã€å
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} 将被删除ï¼æ‚¨ç¡®å®šå—?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
+msgstr "%{label_for_message}ä¸å¯ç”¨"
+
+msgid "%{level_name} visibility has been restricted by the administrator."
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}查看更多%{link_end} 关于角色æƒé™"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} 已开始"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} 被GitLab用户 %{lock_user_id} é”定"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "%{name} 的头åƒ"
@@ -161,23 +193,32 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "å·²å®Œæˆ %{percent}%%"
-msgid "%{state} epics"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
+msgid "%{state} epics"
+msgstr "epic %{state}"
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} 个分支"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} 次æ交"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} 个文件"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} 个标签"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -196,10 +237,13 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "%{usage_ping_link_start}了解更多%{usage_ping_link_end}关于GitLab Inc.的共享信æ¯ã€‚"
msgid "%{user_name} profile page"
+msgstr "%{user_name}的个人资料"
+
+msgid "(No changes)"
msgstr ""
msgid "(external source)"
-msgstr ""
+msgstr "(外部资æº)"
msgid "+ %{count} more"
msgstr "+ 其余 %{count} 项"
@@ -207,8 +251,11 @@ msgstr "+ 其余 %{count} 项"
msgid "+ %{moreCount} more"
msgstr "+ 其余 %{moreCount} 项"
+msgid "+%{extraOptionCount} more"
+msgstr "+其余 %{extraOptionCount} 项"
+
msgid ", or "
-msgstr ""
+msgstr ",或"
msgid "- Runner is active and can process any new jobs"
msgstr "- Runnerå·²å¯ç”¨ï¼Œéšæ—¶å¯ä»¥å¤„ç†æ–°ä½œä¸š"
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] "%{count} 个 %{type} 的修改"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "%d天"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "%d 个已关闭的议题"
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] "%d 个已关闭的åˆå¹¶è¯·æ±‚"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "%d 个群组"
@@ -263,18 +317,30 @@ msgid "1 user"
msgid_plural "%d users"
msgstr[0] "%d ä½ç”¨æˆ·"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "最高贡献"
msgid "2FA"
-msgstr ""
+msgstr "åŒå› å­éªŒè¯"
msgid "2FA enabled"
msgstr "å¯ç”¨åŒé‡è®¤è¯"
-msgid "403|Please contact your GitLab administrator to get permission."
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
msgstr ""
+msgid "403|Please contact your GitLab administrator to get permission."
+msgstr "请è”系您的 GitLab 管ç†å‘˜èŽ·å–访问æƒé™ã€‚"
+
msgid "403|You don't have the permission to access this page."
msgstr "您没有æƒé™è®¿é—®æ­¤é¡µé¢ã€‚"
@@ -287,6 +353,9 @@ msgstr "页é¢æœªæ‰¾åˆ°"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "如果您觉得这是一个错误的æ示信æ¯ï¼Œè¯·è”系您的 GitLab 管ç†å‘˜ã€‚"
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>“johnsmith@example.comâ€ï¼šâ€œ@ johnsmithâ€</code> 将会把“By <a href=\"#\">@johnsmith</a>â€æ·»åŠ åˆ°åŽŸæœ¬ç”±johnsmith@example.com创建的所有议题和评论中,并将 <a href=\"#\">@johnsmith</a> 设为原本分é…ç»™johnsmith@example.com所有问题的å—让人。"
@@ -315,13 +384,25 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong>次推é€ï¼Œå«æ¥è‡ª<strong>%{people}</strong> å贡献者的<strong>%{commits}</strong>次以上æ交。"
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>删除</strong>æºåˆ†æ”¯"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "Runner是一个执行任务的进程。您å¯ä»¥æ ¹æ®éœ€è¦é…置任æ„æ•°é‡çš„Runner。"
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "æŒç»­é›†æˆæ•°æ®å›¾"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ".NET Core控制å°åº”用程åºæ¨¡æ¿ï¼Œå¯é’ˆå¯¹ä»»ä½•.NET Core项目进行自定义"
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "一个GitBook站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "一个Hexo站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "一个Hugo站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "一个Jekyll站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
msgid "A default branch cannot be chosen for an empty project."
msgstr "无法为空项目选择默认分支。"
@@ -332,18 +413,39 @@ msgstr "已删除的用户"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "GitLab滥用审查团队将会尽快查看您的报告。"
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "将在派生(fork)项目中中创建一个新的分支, 并开å¯ä¸€ä¸ªæ–°çš„åˆå¹¶è¯·æ±‚。"
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr "一个纯HTML站点,它使用NetLifyæ¥ä»£æ›¿Gitlabçš„CI/CD,但ä»ç„¶å…·æœ‰æ‰€æœ‰å…¶ä»–主è¦çš„Gitlab功能。"
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "项目å¯ä»¥ç”¨äºŽå­˜æ”¾æ–‡ä»¶(仓库),安排计划(议题),并å‘布文档(wiki), %{among_other_things_link}。"
+msgid "A ready-to-go template for use with Android apps."
+msgstr "适用于Android应用的éšæ—¶å¯ç”¨çš„模æ¿ã€‚"
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr "适用于iOS Swift应用的éšæ—¶å¯ç”¨çš„模æ¿ã€‚"
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "用于在作业跟踪中查找匹é…测试覆盖率输出的正则表达å¼ã€‚留空则ç¦ç”¨"
msgid "A user with write access to the source branch selected this option"
msgstr "具有对æºåˆ†æ”¯çš„写入æƒé™çš„用户选择了此选项"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "关于 GitLab"
@@ -363,7 +465,7 @@ msgid "Abuse reports"
msgstr "滥用报告"
msgid "Accept invitation"
-msgstr ""
+msgstr "接å—邀请"
msgid "Accept terms"
msgstr "接å—æ¡æ¬¾"
@@ -374,12 +476,18 @@ msgstr "接å—åˆå¹¶è¯·æ±‚"
msgid "Access Tokens"
msgstr "访问令牌"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "æ‹’ç»è®¿é—®ï¼è¯·æ ¸æŸ¥æ‚¨æ˜¯å¦æœ‰æƒé™å°†éƒ¨ç½²å¯†é’¥æ·»åŠ åˆ°æ­¤ä»“库。"
msgid "Access expiration date"
msgstr "访问到期日期"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "ä¸å…许访问%{classification_label}"
@@ -389,6 +497,12 @@ msgstr "å¸å·"
msgid "Account and limit"
msgstr "å¸æˆ·å’Œé™åˆ¶"
+msgid "Action to take when receiving an alert."
+msgstr "接收警报时è¦æ‰§è¡Œçš„æ“作。"
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "å¯ç”¨"
@@ -396,16 +510,16 @@ msgid "Active Sessions"
msgstr "活动会è¯"
msgid "Activity"
-msgstr "活动"
+msgstr "动æ€"
msgid "Add"
msgstr "添加"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "添加更新日志"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "添加贡献信æ¯"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "添加组 Webhooks å’Œ GitLab ä¼ä¸šç‰ˆã€‚"
@@ -417,43 +531,70 @@ msgid "Add Kubernetes cluster"
msgstr "添加 Kubernetes 集群"
msgid "Add README"
-msgstr ""
+msgstr "添加自述文件"
+
+msgid "Add a bullet list"
+msgstr "添加一个符å·åˆ—表"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "添加一般评论 %{noteable_name}。"
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "在wiki中添加一个主页,其中包å«æœ‰å…³é¡¹ç›®çš„ä¿¡æ¯ï¼ŒGitLab将在此处显示该主页,而ä¸æ˜¯æ­¤æ¶ˆæ¯ã€‚"
+msgid "Add a link"
+msgstr "添加链接"
+
+msgid "Add a numbered list"
+msgstr "添加编å·åˆ—表"
+
msgid "Add a table"
msgstr "添加表格"
+msgid "Add a task list"
+msgstr "添加任务列表"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "添加包å«åœ¨æ‰€æœ‰ç”µå­é‚®ä»¶ä¸­çš„附加文本。 长度ä¸è¶…过%{character_limit} 字符"
+msgid "Add approver(s)"
+msgstr "添加核准人"
+
+msgid "Add approvers"
+msgstr "添加核准人"
+
+msgid "Add bold text"
+msgstr "添加粗体文本"
+
msgid "Add comment now"
msgstr "ç«‹å³æ·»åŠ è¯„论"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr "添加页眉和页脚到电å­é‚®ä»¶ã€‚请注æ„,颜色设置仅适用于应用程åºç•Œé¢"
+
msgid "Add image comment"
msgstr "添加图片评论"
+msgid "Add italic text"
+msgstr "添加斜体文本"
+
msgid "Add license"
msgstr "添加许å¯è¯"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "新建应用"
msgid "Add new directory"
msgstr "添加目录"
-msgid "Add projects"
-msgstr "添加项目"
-
msgid "Add reaction"
msgstr "添加回应"
msgid "Add to project"
-msgstr ""
+msgstr "添加到项目"
msgid "Add to review"
msgstr "添加到评审"
@@ -465,23 +606,32 @@ msgid "Add user(s) to the group:"
msgstr "å‘群组添加用户"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "添加å…许批准æ¯ä¸ªåˆå¹¶è¯·æ±‚的用户或群组"
msgid "Add users to group"
msgstr "将用户加入群组"
+msgid "Added at"
+msgstr "添加于"
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "当å‰GitLab实例ç¦æ­¢æ·»åŠ æ–°åº”用程åºã€‚请è”系您的GitLab管ç†å‘˜ä»¥èŽ·å¾—相关æƒé™ã€‚"
msgid "Additional text"
msgstr "附加文本"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "管ç†ä¸­å¿ƒ"
msgid "Admin Overview"
msgstr "管ç†æ¦‚览"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "您å³å°†æ°¸ä¹…删除用户 %{username}。该用户的议题ã€åˆå¹¶è¯·æ±‚以åŠé“¾æŽ¥åˆ°è¯¥ç”¨æˆ·çš„群组将被转移到系统的“Ghost用户â€ã€‚为é¿å…æ•°æ®ä¸¢å¤±ï¼Œå»ºè®®æ‚¨ä½¿ç”¨ %{strong_start}ç¦ç”¨ç”¨æˆ·%{strong_end} 功能。一旦您 %{strong_start}删除用户%{strong_end},将无法撤消或æ¢å¤ã€‚"
@@ -516,40 +666,43 @@ msgid "AdminProjects|Delete project"
msgstr "删除项目"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Auto DevOps 域"
+
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr "为新项目å¯ç”¨å…±äº«è¿è¡Œå™¨"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "环境å˜é‡é»˜è®¤å—ä¿æŠ¤"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "为æ¯ä¸ªé¡¹ç›®çš„自动审阅应用 (Auto Review Apps) 和自动部署 (Auto Deploy) 阶段指定一个默认使用的域。"
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "创建一个新的环境å˜é‡æ—¶ï¼Œå®ƒä¼šè¢«é»˜è®¤ä¿æŠ¤ã€‚"
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "ç¦ç”¨åŒå› å­éªŒè¯"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "å¯ç”¨åŒå› å­éªŒè¯"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "活跃"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "管ç†å‘˜"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "管ç†å‘˜"
msgid "AdminUsers|Block user"
msgstr "ç¦ç”¨ç”¨æˆ·"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "å·²å±è”½"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "无法解除已å±è”½çš„ LDAP 用户"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "删除用户 %{username} 以åŠç›¸å…³è´¡çŒ®å—?"
@@ -564,28 +717,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "删除用户åŠç›¸å…³è´¡çŒ®"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "外部"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "自己ï¼"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "新用户"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "未找到用户"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "按åå­—ã€ç”µå­é‚®ä»¶æˆ–用户åæœç´¢"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "æœç´¢ç”¨æˆ·"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "å‘用户å‘é€ç”µå­é‚®ä»¶"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "排åºæ–¹å¼"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "请输入 %{projectName} æ¥ç¡®è®¤"
@@ -594,27 +747,39 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "请输入 %{username} æ¥ç¡®è®¤"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "用户将被å±è”½"
msgid "AdminUsers|Without projects"
-msgstr ""
+msgstr "没有项目"
+
+msgid "Advanced"
+msgstr "高级"
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "高级æƒé™ï¼Œå¤§æ–‡ä»¶å­˜å‚¨å’ŒåŒé‡è®¤è¯è®¾ç½®ã€‚"
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "高级设置"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr "密ç æ›´æ–°æˆåŠŸåŽï¼Œæ‚¨å°†è¢«é‡å®šå‘到登录页é¢ã€‚"
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "警报"
msgid "Alerts"
-msgstr ""
+msgstr "警报"
msgid "All"
msgstr "全部"
+msgid "All Members"
+msgstr "所有æˆå‘˜"
+
msgid "All changes are committed"
msgstr "所有更改å‡å·²æ交"
@@ -622,17 +787,35 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "从模æ¿æˆ–导入时为空白项目将å¯ç”¨æ‰€æœ‰åŠŸèƒ½ï¼Œä½†å¯ä»¥åœ¨é¡¹ç›®è®¾ç½®ä¸­å°†å…¶ç¦ç”¨ã€‚"
msgid "All issues for this milestone are closed. You may close this milestone now."
+msgstr "这个里程碑的所有议题都已关闭。现在您å¯ä»¥å…³é—­è¿™ä¸ªé‡Œç¨‹ç¢‘。"
+
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
msgstr ""
msgid "All users"
msgstr "所有用户"
-msgid "Allow \"%{group_name}\" to sign you in"
+msgid "All users must have a name."
msgstr ""
+msgid "Allow \"%{group_name}\" to sign you in"
+msgstr "å…许“%{group_name}â€ä»¥æ‚¨çš„身份登录"
+
msgid "Allow commits from members who can merge to the target branch."
msgstr "å…许具有åˆå¹¶åˆ°ç›®æ ‡åˆ†æ”¯æƒé™çš„æˆå‘˜æ交"
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr "å…è®¸ç¾¤ç»„æ‰€æœ‰è€…ç®¡ç† LDAP 相关的设置"
+
+msgid "Allow mirrors to be set up for projects"
+msgstr "å…许为项目设置镜åƒ"
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr "ä»…å…许所选å议用于 Git 访问。"
+
msgid "Allow projects within this group to use Git LFS"
msgstr "å…许该群组中的项目使用Git LFS"
@@ -645,6 +828,12 @@ msgstr "å…许在Asciidoc文档中渲染PlantUML图。"
msgid "Allow requests to the local network from hooks and services."
msgstr "å…许æ¥è‡ªé’©å­å’ŒæœåŠ¡çš„对本地网络的请求。"
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr "åŒæ—¶å…许此秘钥推é€æ–‡ä»¶åˆ°å­˜å‚¨åº“å—?(默认åªæ‹¥æœ‰æ‹‰å–æƒé™)"
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr "å…许用户注册任何应用程åºï¼Œä½¿ç”¨ GitLab 作为 OAuth æ供者"
+
msgid "Allow users to request access"
msgstr "å…许用户请求访问æƒé™"
@@ -652,7 +841,7 @@ msgid "Allow users to request access if visibility is public or internal."
msgstr "如果是公开或内部å¯è§æ€§ï¼Œåˆ™å…许用户请求访问æƒé™ã€‚"
msgid "Allowed to fail"
-msgstr ""
+msgstr "å…许失败"
msgid "Allows you to add and manage Kubernetes clusters."
msgstr "这里å¯ä»¥æ·»åŠ å’Œç®¡ç† Kubernetes 集群。"
@@ -663,6 +852,9 @@ msgstr "也称为“签å‘者â€æˆ–“ä¾èµ–方信任标识符â€"
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "也称为“ä¾èµ–æ–¹æœåŠ¡URLâ€æˆ–“回å¤URLâ€"
+msgid "Alternate support URL for help page"
+msgstr "帮助页é¢çš„å¤‡ç”¨æ”¯æŒ URL"
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "此外,也å¯ä»¥ä½¿ç”¨ %{personal_access_token_link}。创建 Personal Access Token 时,在范围中需选择 <code>repo</code> ,以便显示å¯ä¾›è¿žæŽ¥çš„公开和ç§æœ‰çš„仓库列表。"
@@ -684,9 +876,6 @@ msgstr "空GitLab用户字段将在所有问题和注释的æ述中添加FogBug
msgid "An error has occurred"
msgstr "å‘生错误"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "å‘讨论添加è‰ç¨¿æ—¶å‡ºé”™ã€‚"
@@ -694,7 +883,16 @@ msgid "An error occurred adding a new draft."
msgstr "添加新è‰ç¨¿æ—¶å‡ºé”™ã€‚"
msgid "An error occurred creating the new branch."
-msgstr ""
+msgstr "创建新分支时å‘生错误。"
+
+msgid "An error occurred fetching the approval rules."
+msgstr "获å–核准规则时出错。"
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "获å–新规则的核准人时出错。"
+
+msgid "An error occurred fetching the dropdown data."
+msgstr "获å–下拉数æ®æ—¶å‡ºé”™ã€‚"
msgid "An error occurred previewing the blob"
msgstr "预览 blob 时出错"
@@ -708,18 +906,27 @@ msgstr "更新议题æƒé‡æ—¶å‘生错误"
msgid "An error occurred while adding approver"
msgstr "添加审批者时出错"
+msgid "An error occurred while deleting the approvers group"
+msgstr "删除核准人群组时å‘生错误"
+
msgid "An error occurred while deleting the comment"
msgstr "删除评论时å‘生错误"
msgid "An error occurred while detecting host keys"
msgstr "检测主机密钥时å‘生错误"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "消除警告时å‘生错误。请刷新页é¢å¹¶å†æ¬¡å°è¯•ã€‚"
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "关闭功能çªå‡ºæ˜¾ç¤ºæ—¶å‘生错误。请刷新页é¢å¹¶å†æ¬¡å°è¯•ã€‚"
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "èŽ·å– markdown 预览时出错"
@@ -732,6 +939,9 @@ msgstr "获å–侧边æ æ•°æ®æ—¶å‘生错误"
msgid "An error occurred while fetching stages."
msgstr "获å–阶段时å‘生错误"
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "获å–作业日志时å‘生错误。"
@@ -744,6 +954,9 @@ msgstr "获å–作业列表时å‘生错误"
msgid "An error occurred while fetching the pipeline."
msgstr "获å–æµæ°´çº¿æ—¶å‘生错误"
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr "获å–版本时å‘生错误。请é‡è¯•ã€‚"
+
msgid "An error occurred while getting projects"
msgstr "获å–项目时å‘生错误"
@@ -759,6 +972,9 @@ msgstr "加载图表数æ®æ—¶å‘生错误"
msgid "An error occurred while loading commit signatures"
msgstr "加载æ交签åæ—¶å‘生错误"
+msgid "An error occurred while loading designs. Please try again."
+msgstr "加载设计时出错。请é‡è¯•ã€‚"
+
msgid "An error occurred while loading diff"
msgstr "加载差异时å‘生错误"
@@ -769,7 +985,7 @@ msgid "An error occurred while loading the file"
msgstr "加载文件时å‘生错误"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "加载订阅信æ¯æ—¶å‘生错误。"
msgid "An error occurred while making the request."
msgstr "å‘é€è¯·æ±‚æ—¶å‘生错误。"
@@ -778,10 +994,10 @@ msgid "An error occurred while removing approver"
msgstr "删除批准者时å‘生错误"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "删除 epic æ—¶å‘生错误。"
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "删除议题时å‘生错误。"
msgid "An error occurred while rendering KaTeX"
msgstr "渲染KaTeXæ—¶å‘生错误"
@@ -790,7 +1006,7 @@ msgid "An error occurred while rendering preview broadcast message"
msgstr "渲染广播消æ¯æ—¶å‘生错误"
msgid "An error occurred while retrieving calendar activity"
-msgstr "获å–日历活动时å‘生错误"
+msgstr "获å–日历动æ€æ—¶å‘生错误"
msgid "An error occurred while retrieving diff"
msgstr "获å–差异时å‘生错误"
@@ -801,12 +1017,18 @@ msgstr "ä¿å­˜LDAP覆盖状æ€æ—¶å‘生错误。请å†è¯•ä¸€æ¬¡ã€‚"
msgid "An error occurred while saving assignees"
msgstr "ä¿å­˜è¢«æŒ‡æ´¾äººæ—¶å‡ºçŽ°é”™è¯¯ã€‚"
+msgid "An error occurred while saving the approval settings"
+msgstr "ä¿å­˜æ ¸å‡†è®¾ç½®æ—¶å‘生错误"
+
msgid "An error occurred while subscribing to notifications."
msgstr "订阅通知时å‘生错误。"
msgid "An error occurred while unsubscribing to notifications."
msgstr "å–消订阅通知时å‘生错误。"
+msgid "An error occurred while updating approvers"
+msgstr "更新核准人时å‘生错误"
+
msgid "An error occurred while updating the comment"
msgstr "更新评论时å‘生错误"
@@ -814,56 +1036,59 @@ msgid "An error occurred while validating username"
msgstr "验è¯ç”¨æˆ·åæ—¶å‘生错误"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "æ交更改时å‘生错误。"
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "获å–作业跟踪时å‘生错误。"
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "获å–最新æµæ°´çº¿æ—¶å‘生错误。"
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "加载所有文件时å‘生错误。"
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "加载文件内容时å‘生错误。"
msgid "An error occurred whilst loading the file."
-msgstr ""
+msgstr "加载文件时å‘生错误。"
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "加载åˆå¹¶è¯·æ±‚çš„å˜æ›´å†…容时å‘生错误。"
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "加载åˆå¹¶è¯·æ±‚的版本数æ®æ—¶å‘生错误。"
msgid "An error occurred whilst loading the merge request."
-msgstr ""
+msgstr "加载åˆå¹¶è¯·æ±‚æ—¶å‘生错误。"
msgid "An error occurred whilst loading the pipelines jobs."
-msgstr ""
+msgstr "加载æµæ°´çº¿ä½œä¸šæ—¶å‘生错误。"
msgid "An error occurred. Please try again."
msgstr "å‘生了错误,请å†è¯•ä¸€æ¬¡ã€‚"
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "检查项目环境时å‘生æ„外错误。"
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "检查项目è¿è¡Œå™¨æ—¶å‘生æ„外错误。"
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "与Web终端通信时å‘生æ„外错误。"
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "å¯åŠ¨Web终端时å‘生æ„外错误。"
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "åœæ­¢Web终端时å‘生æ„外错误。"
msgid "Analytics"
msgstr "分æž"
+msgid "Ancestors"
+msgstr "祖先"
+
msgid "Anonymous"
msgstr "匿å"
@@ -876,15 +1101,33 @@ msgstr "任何"
msgid "Any Label"
msgstr "任何标记"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "外观"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "应用"
msgid "Application ID"
msgstr "应用程åºID"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "应用:%{name}"
@@ -892,19 +1135,53 @@ msgid "Applications"
msgstr "应用"
msgid "Applied"
-msgstr ""
+msgstr "已应用"
msgid "Apply suggestion"
-msgstr ""
+msgstr "应用建议"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d æˆå‘˜"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] "这些æˆå‘˜çš„核准ä¸ä¼šè¢«æ’¤é”€ã€‚"
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr "您å³å°†åˆ é™¤å…·æœ‰ %{nMembers} çš„ %{name} 核准人群组。"
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d æˆå‘˜"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] "%{membersCount} éœ€è¦ %{count} 个核准"
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr "具有开å‘人员或更高级别角色的所有æˆå‘˜ä»¥åŠä»£ç æ‰€æœ‰è€…(如果有)"
+
+msgid "ApprovalRule|Members"
+msgstr "æˆå‘˜"
+
+msgid "ApprovalRule|Name"
+msgstr "å称"
+
+msgid "ApprovalRule|No. approvals required"
+msgstr "需è¦æ ¸å‡†çš„æ•°é‡"
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr "例如质é‡ä¿è¯ã€å®‰å…¨ç­‰ç­‰ã€‚"
msgid "Approvals"
-msgstr ""
+msgstr "核准"
msgid "Approvals required"
-msgstr ""
+msgstr "需è¦æ ¸å‡†"
msgid "Approvers"
-msgstr ""
+msgstr "核准人"
msgid "Apr"
msgstr "4月"
@@ -912,13 +1189,28 @@ msgstr "4月"
msgid "April"
msgstr "4月"
+msgid "Archive jobs"
+msgstr "存档作业"
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "已归档项目ï¼ä»“库和其他项目资æºå‡ä¸ºåªè¯»"
msgid "Archived projects"
msgstr "归档项目"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
+msgstr "您确定å—"
+
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
@@ -931,6 +1223,9 @@ msgid "Are you sure you want to lose unsaved changes?"
msgstr "确定è¦æ”¾å¼ƒæœªä¿å­˜çš„更改å—?"
msgid "Are you sure you want to lose your issue information?"
+msgstr "您确定è¦ä¸¢å¼ƒè®®é¢˜ä¿¡æ¯å—?"
+
+msgid "Are you sure you want to permanently delete this license?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
@@ -939,21 +1234,30 @@ msgstr "您确定è¦é‡æ–°ç”Ÿæˆå…¬é’¥å—?在镜åƒå†æ¬¡è¿è¡Œä¹‹å‰ï¼Œæ‚¨å¿…
msgid "Are you sure you want to remove %{group_name}?"
msgstr "确定移除群组 %{group_name} å—?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "您确定è¦åˆ é™¤æ ¸å‡†è€… %{name} å—"
+
msgid "Are you sure you want to remove approver %{name}?"
-msgstr ""
+msgstr "您确定è¦åˆ é™¤æ ¸å‡†è€… %{name} å—?"
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr "您确定è¦åˆ é™¤ %{name} 群组å—"
msgid "Are you sure you want to remove group %{name}?"
-msgstr ""
+msgstr "您确定è¦åˆ é™¤ %{name} 群组å—?"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "您确定è¦åˆ é™¤æ­¤é™„件?"
msgid "Are you sure you want to remove this identity?"
-msgstr "你确定è¦åˆ é™¤è¿™ä¸ªèº«ä»½æ ‡è¯†å—?"
+msgstr "您确定è¦åˆ é™¤è¿™ä¸ªèº«ä»½æ ‡è¯†å—?"
msgid "Are you sure you want to reset registration token?"
msgstr "确定è¦é‡ç½®æ³¨å†Œä»¤ç‰Œå—?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "确定è¦é‡ç½®è¿è¡ŒçŠ¶å†µæ£€æŸ¥ä»¤ç‰Œå—?"
@@ -961,10 +1265,10 @@ msgid "Are you sure you want to stop this environment?"
msgstr "是å¦ç¡®å®šç»ˆæ­¢å½“å‰çŽ¯å¢ƒï¼Ÿ"
msgid "Are you sure you want to unlock %{path_lock_path}?"
-msgstr "你确定è¦è§£é” %{path_lock_path} å—?"
+msgstr "您确定è¦è§£é” %{path_lock_path} å—?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "您确定è¦å–消订阅 %{type}:%{link_to_noteable_text}å—?"
msgid "Are you sure?"
msgstr "确定å—?"
@@ -985,7 +1289,7 @@ msgid "Assertion consumer service URL"
msgstr "断言消费者æœåŠ¡ URL"
msgid "Assets"
-msgstr ""
+msgstr "资æº"
msgid "Assign custom color like #FF0000"
msgstr "分é…自定义颜色,如FF0000"
@@ -997,7 +1301,7 @@ msgid "Assign milestone"
msgstr "分é…里程碑"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "为此里程碑分é…一些议题。"
msgid "Assign to"
msgstr "分é…到"
@@ -1008,9 +1312,6 @@ msgstr "已分é…议题"
msgid "Assigned Merge Requests"
msgstr "已分é…åˆå¹¶è¯·æ±‚"
-msgid "Assigned to :name"
-msgstr "已分é…至 :name"
-
msgid "Assigned to me"
msgstr "已分派给我"
@@ -1026,8 +1327,11 @@ msgstr "指派列表显示分é…给选定用户的所有议题。"
msgid "Assignee(s)"
msgstr "指派"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr "至少需è¦ä¸€ä¸ªä»£ç æ‰€æœ‰è€…批准,以便更改符åˆç›¸åº”çš„ CODEEWNER 规则的文件。"
+
msgid "Attach a file"
-msgstr ""
+msgstr "附加文件"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "拖放文件到此处或者 %{upload_link}"
@@ -1041,18 +1345,24 @@ msgstr "8月"
msgid "August"
msgstr "8 月"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "认è¯æ—¥å¿—"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "认è¯æ—¥å¿—"
msgid "Authentication method"
msgstr "验è¯æ–¹å¼"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "作者"
@@ -1060,7 +1370,7 @@ msgid "Authorization code:"
msgstr "授æƒç ï¼š"
msgid "Authorization key"
-msgstr ""
+msgstr "授æƒå¯†é’¥"
msgid "Authorization was granted by entering your username and password in the application."
msgstr "在应用中输入您的用户å和密ç å³å®ŒæˆæŽˆæƒã€‚"
@@ -1071,6 +1381,9 @@ msgstr "授æƒ"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "æŽˆæƒ %{link_to_client} 使用您的å¸æˆ·ï¼Ÿ"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "授æƒäºŽ"
@@ -1110,38 +1423,32 @@ msgstr "想了解更多请访问 %{link_to_documentation}"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "Auto DevOpsæµæ°´çº¿å·²å¯ç”¨ã€‚如果未找到CIé…置文件,将使用该æµæ°´çº¿ã€‚ %{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "如果当å‰é¡¹ç›®%{link_to_auto_devops_settings}, å¯ä»¥è‡ªåŠ¨çš„构建和测试应用。如果已%{link_to_add_kubernetes_cluster},则也å¯ä»¥å®žçŽ°è‡ªåŠ¨éƒ¨ç½²ã€‚"
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "添加Kubernetes集群"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "å¯ç”¨Auto DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "自动标记为默认内部用户"
-msgid "Automatically resolved"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
+msgid "Automatically resolved"
+msgstr "自动解决"
+
msgid "Available"
msgstr "å¯ç”¨çš„"
msgid "Available group Runners: %{runners}"
-msgstr ""
+msgstr "å¯ç”¨çš„群组 Runner: %{runners}"
msgid "Available shared Runners:"
-msgstr ""
+msgstr "å¯ç”¨çš„共享è¿è¡Œå™¨ï¼š"
msgid "Available specific runners"
-msgstr ""
+msgstr "å¯ç”¨çš„特定è¿è¡Œå™¨"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "%{assigneeName} 头åƒ"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "%{name} 头åƒ"
msgid "Avatar will be removed. Are you sure?"
msgstr "å³å°†åˆ é™¤å¤´åƒã€‚确定继续å—?"
@@ -1245,6 +1552,12 @@ msgstr "放弃评审?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "您å³å°†æ”¾å¼ƒæ‚¨çš„评审,该æ“作将删除所有待处ç†çš„评论。请注æ„已删除评论 %{strong_start}ä¸èƒ½%{strong_end} æ¢å¤ã€‚"
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "从选定的æ交开始"
@@ -1327,10 +1640,10 @@ msgid "Bitbucket import"
msgstr "从 Bitbucket 导入"
msgid "Block"
-msgstr ""
+msgstr "å±è”½"
msgid "Blocked"
-msgstr ""
+msgstr "å·²å±è”½"
msgid "Blog"
msgstr "åšå®¢"
@@ -1482,6 +1795,12 @@ msgstr "项目设置"
msgid "Branches|protected"
msgstr "å—ä¿æŠ¤"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "æµè§ˆç›®å½•"
@@ -1495,33 +1814,45 @@ msgid "Browse files"
msgstr "æµè§ˆæ–‡ä»¶"
msgid "Built-in"
-msgstr ""
+msgstr "内置"
msgid "Business"
-msgstr ""
+msgstr "商业"
msgid "Business metrics (Custom)"
msgstr "业务指标(自定义)"
-msgid "By %{user_name}"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
msgstr ""
+msgid "By %{user_name}"
+msgstr "ç”± %{user_name}"
+
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr "默认情况下,GitLab 以 HTML 和纯文本格å¼å‘é€ç”µå­é‚®ä»¶ï¼Œå› æ­¤é‚®ä»¶å®¢æˆ·ç«¯å¯ä»¥é€‰æ‹©ä½¿ç”¨å“ªç§æ ¼å¼ã€‚如果您åªæƒ³ä»¥çº¯æ–‡æœ¬æ ¼å¼å‘é€ç”µå­é‚®ä»¶ï¼Œè¯·ç¦ç”¨æ­¤é€‰é¡¹ã€‚"
+
msgid "ByAuthor|by"
msgstr "作者:"
msgid "CHANGELOG"
-msgstr ""
+msgstr "更新日志"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "CI/CD 统计图"
msgid "CI / CD Settings"
msgstr "CI/CD 设置"
msgid "CI Lint"
+msgstr "CI Lint"
+
+msgid "CI variables"
msgstr ""
msgid "CI will run using the credentials assigned above."
@@ -1557,6 +1888,9 @@ msgstr "使用已定时的增é‡éƒ¨ç½²è¿›è¡ŒæŒç»­éƒ¨ç½²åˆ°ç”Ÿäº§çŽ¯å¢ƒ"
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "默认为Auto DevOpsæµæ°´çº¿"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr "所有项目默认的 Auto DevOps æµæ°´çº¿"
+
msgid "CICD|Deployment strategy"
msgstr "部署策略"
@@ -1570,25 +1904,31 @@ msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration
msgstr "在未找到备用CIé…置文件时使用Auto DevOpsæµæ°´çº¿ã€‚"
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
-msgstr ""
+msgstr "您必须添加一个%{kubernetes_cluster_start}Kubernetes集群集æˆ%{kubernetes_cluster_end}域å到此项目,以便您的部署策略正常工作。"
+
+msgid "CICD|group enabled"
+msgstr "群组已å¯ç”¨"
msgid "CICD|instance enabled"
msgstr "å·²å¯ç”¨çš„实例"
msgid "CONTRIBUTING"
-msgstr ""
+msgstr "贡献信æ¯"
msgid "Callback URL"
msgstr "回调 URL"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "å¯ä»¥è¦†ç›–æ¯ä¸ªåˆå¹¶è¯·æ±‚所需的核准人和核准信æ¯"
msgid "Can't find HEAD commit for this branch"
msgstr "无法找到此分支的 HEAD æ交"
+msgid "Can't remove group members without group managed account"
+msgstr "当群组没有管ç†å‘˜æ—¶, 无法删除群组æˆå‘˜"
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
-msgstr ""
+msgstr "金ä¸é›€éƒ¨ç½²æ˜¯ä¸€é¡¹æµè¡Œçš„CI策略,其中一å°éƒ¨åˆ†èŠ‚点更新到您的新版本应用。"
msgid "Cancel"
msgstr "å–消"
@@ -1599,24 +1939,39 @@ msgstr "å–消此作业"
msgid "Cannot be merged automatically"
msgstr "无法自动åˆå¹¶"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "无法修改托管的 Kubernetes 集群"
-msgid "Certificate"
-msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr "无法渲染镜åƒã€‚已超出最大字符数(%{charLimit})。"
-msgid "Certificate (PEM)"
+msgid "Cannot skip two factor authentication setup"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "è¯ä¹¦æŒ‡çº¹"
+msgid "Capacity threshold"
+msgstr "容é‡é˜ˆå€¼"
+
+msgid "Certificate"
+msgstr "è¯ä¹¦"
+
+msgid "Certificate (PEM)"
+msgstr "è¯ä¹¦(PEM)"
msgid "Change Weight"
msgstr "更改æƒé‡"
-msgid "Change permissions"
+msgid "Change path"
msgstr ""
+msgid "Change permissions"
+msgstr "更改æƒé™"
+
msgid "Change template"
msgstr "更改模æ¿"
@@ -1638,11 +1993,14 @@ msgstr "还原"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "这将创建一个新的æ交, æ¥è¿˜åŽŸçŽ°æœ‰çš„更改。"
+msgid "Changes"
+msgstr "å˜æ›´"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "差异显示方å¼ä¾<b>æº</b>版本åˆå¹¶åˆ°<b>目标</b>版本的形å¼ã€‚"
msgid "Changes suppressed. Click to show."
-msgstr ""
+msgstr "仅显示部分。点此显示全部。"
msgid "Charts"
msgstr "统计图"
@@ -1650,17 +2008,23 @@ msgstr "统计图"
msgid "Chat"
msgstr "å³æ—¶é€šè®¯"
+msgid "Check again"
+msgstr "å†æ¬¡æ£€æŸ¥"
+
+msgid "Check feature availability on namespace plan"
+msgstr "检查命å空间方案的功能å¯ç”¨æ€§"
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "查看%{docs_link_start}文档%{docs_link_end}。"
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr "检查您的.gitlab-ci.yml"
msgid "Checking %{text} availability…"
msgstr "正在检查%{text}çš„å¯ç”¨æ€§..."
msgid "Checking approval status"
-msgstr ""
+msgstr "检查批准状æ€"
msgid "Checking branch availability..."
msgstr "正在检查分支的å¯ç”¨æ€§..."
@@ -1677,17 +2041,14 @@ msgstr "选择<strong>创建归档</strong> 并等待归档过程完æˆã€‚"
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "选择页é¢åº•éƒ¨çš„<strong>下一步</strong>。"
-msgid "Choose File ..."
-msgstr "选择文件 ……"
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "选择分支/标签(例如%{master})或输入æ交(例如%{sha})以查看更改内容或创建åˆå¹¶è¯·æ±‚。"
msgid "Choose a file"
-msgstr ""
+msgstr "选择一个文件"
msgid "Choose a role permission"
-msgstr ""
+msgstr "选择角色æƒé™"
msgid "Choose a template..."
msgstr "选择模æ¿..."
@@ -1701,27 +2062,27 @@ msgstr "选择任何颜色。"
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "选择 <code>克隆</code> 或 <code>拉å–</code> 以获å–最近的应用程åºä»£ç "
-msgid "Choose file..."
-msgstr "选择文件..."
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "选择存储库导入的顶级群组。"
-msgid "Choose what content you want to see on a group’s overview page"
-msgstr ""
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
+msgstr "选择å¯è§æ€§çº§åˆ«ï¼Œå¯ç”¨/ç¦ç”¨é¡¹ç›®åŠŸèƒ½ï¼ˆè®®é¢˜ï¼Œå­˜å‚¨åº“,wiki,代ç ç‰‡æ®µï¼‰å¹¶è®¾ç½®æƒé™ã€‚"
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "选择您希望与此次è¦èŠ‚点åŒæ­¥çš„群组。"
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "选择è¦åœ¨ç¾¤ç»„概述页é¢ä¸Šçœ‹åˆ°çš„内容"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "请选择è¦è¿žæŽ¥å¹¶è¿è¡Œ CI/CD æµæ°´çº¿çš„代ç ä»“库。"
-msgid "Choose which repositories you want to import."
-msgstr "选择è¦å¯¼å…¥çš„仓库"
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "选择è¦åŒæ­¥åˆ°æ­¤æ¬¡èŠ‚点的切片。"
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr "选择åˆå¹¶æ–¹æ³•ï¼Œè®¾ç½®é»˜è®¤åˆå¹¶è¯·æ±‚æ述模æ¿ã€‚"
+
msgid "CiStatusLabel|canceled"
msgstr "å·²å–消"
@@ -1746,6 +2107,9 @@ msgstr "已通过但有警告"
msgid "CiStatusLabel|pending"
msgstr "等待中"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "已跳过"
@@ -1779,6 +2143,9 @@ msgstr "已通过"
msgid "CiStatusText|pending"
msgstr "等待中"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "已跳过"
@@ -1794,6 +2161,9 @@ msgstr "输入å˜é‡çš„值"
msgid "CiVariables|Remove variable row"
msgstr "删除å˜é‡è¡Œ"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "* (所有环境)"
@@ -1804,6 +2174,9 @@ msgid "CiVariable|Create wildcard"
msgstr "创建通é…符"
msgid "CiVariable|Error occurred while saving variables"
+msgstr "ä¿å­˜å˜é‡æ—¶å‡ºé”™"
+
+msgid "CiVariable|Masked"
msgstr ""
msgid "CiVariable|New environment"
@@ -1815,20 +2188,26 @@ msgstr "å—ä¿æŠ¤"
msgid "CiVariable|Search environments"
msgstr "æœç´¢çŽ¯å¢ƒ"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "开关ä¿æŠ¤çŠ¶æ€"
msgid "CiVariable|Validation failed"
msgstr "验è¯å¤±è´¥"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "ä¸å¯ç”¨: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "清除"
msgid "Clear input"
-msgstr ""
+msgstr "清除输入"
msgid "Clear search"
msgstr "清除æœç´¢"
@@ -1870,56 +2249,56 @@ msgid "Clients"
msgstr "客户端"
msgid "Clone"
-msgstr ""
+msgstr "克隆"
msgid "Clone repository"
msgstr "克隆仓库"
msgid "Clone with %{http_label}"
-msgstr ""
+msgstr "使用 %{http_label} 克隆"
+
+msgid "Clone with KRB5"
+msgstr "通过KRB5克隆"
msgid "Clone with SSH"
-msgstr ""
+msgstr "使用 SSH 克隆"
msgid "Close"
msgstr "关闭"
msgid "Close epic"
-msgstr "关闭å²è¯—故事"
+msgstr "关闭epic"
msgid "Close milestone"
-msgstr ""
+msgstr "关闭里程碑"
msgid "Closed"
msgstr "已关闭"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "已关闭议题"
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
-msgstr ""
+msgstr "%{custom_domain_start}更多信æ¯%{custom_domain_end}。"
msgid "ClusterIntegration| can be used instead of a custom domain."
-msgstr ""
+msgstr "å¯ä»¥ç”¨æ¥ä»£æ›¿è‡ªå®šä¹‰åŸŸã€‚"
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
-msgstr ""
+msgstr "是此群集的默认环境范围。这æ„味ç€æ‰€æœ‰ä½œä¸šéƒ½å°†ä½¿ç”¨æ­¤ç¾¤é›†ã€‚%{environment_scope_start}更多信æ¯%{environment_scope_end}"
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} å·²æˆåŠŸå®‰è£…到Kubernetes集群上"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} 这将添加一些é¢å¤–的资æºï¼Œå¦‚è´Ÿè½½å‡è¡¡å™¨ï¼Œè¿™å¯èƒ½ä¼šäº§ç”Ÿé¢å¤–çš„æˆæœ¬ï¼Œå…·ä½“å–决于您安装Kubernetes集群的托管æœåŠ¡æ供商。如果您使用的是Google Kubernetes Engine,则å¯ä»¥ %{pricingLink}。"
-
msgid "ClusterIntegration|%{title} upgraded successfully."
-msgstr ""
+msgstr "%{title} å‡çº§æˆåŠŸã€‚"
msgid "ClusterIntegration|API URL"
msgstr "API地å€"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "添加 Kubernetes 集群"
@@ -1935,44 +2314,41 @@ msgstr "添加集æˆåˆ°ç¾¤ç»„åŽï¼Œå¯ä»¥åœ¨ç¾¤ç»„中的所有项目中共享集
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Kubernetes集群集æˆçš„高级选项"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "安装IngressåŽï¼Œæ‚¨éœ€è¦åœ¨ç”Ÿæˆçš„外部IP地å€ä¸ŠæŒ‡å‘DNS,以便在部署åŽæŸ¥çœ‹æ‚¨çš„应用程åºã€‚ %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
-msgstr ""
-
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "å°è¯•èŽ·å–项目地域时å‘生错误:%{error}"
+msgstr "除外"
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "å°è¯•è”ç³»Google Cloud APIæ—¶å‘生错误。请ç¨åŽå†è¯•ã€‚"
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr "å°è¯•èŽ·å–项目区域时å‘生错误: %{error}"
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
-msgstr ""
+msgstr "å°è¯•èŽ·å–您的项目时å‘生错误:%{error}"
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
-msgstr ""
+msgstr "å°è¯•èŽ·å–设备类型时å‘生错误:%{error}"
msgid "ClusterIntegration|Applications"
msgstr "应用程åº"
msgid "ClusterIntegration|Apply for credit"
-msgstr ""
+msgstr "申请信用"
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr "确定è¦åˆ é™¤æ­¤Kubernetes集群的集æˆå—?注æ„这并ä¸ä¼šåˆ é™¤å®žé™…çš„Kubernetes集群本身。"
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "基础域"
msgid "ClusterIntegration|CA Certificate"
msgstr "CAè¯ä¹¦"
msgid "ClusterIntegration|Cert-Manager"
-msgstr ""
+msgstr "Cert-Manager"
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
-msgstr ""
+msgstr "Cert-Manager 是一个本地的 Kubernetes è¯ä¹¦ç®¡ç†æŽ§åˆ¶å™¨ï¼Œå¯å¸®åŠ©é¢å‘è¯ä¹¦ã€‚在群集上安装 Cert-Manager 将通过 %{letsEncrypt} é¢å‘è¯ä¹¦ï¼Œå¹¶ç¡®ä¿è¯ä¹¦æœ‰æ•ˆä¸”是最新的。"
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr "è¯ä¹¦æŽˆæƒåŒ…(PEMæ ¼å¼)"
@@ -1983,29 +2359,35 @@ msgstr "选择è¦åœ¨ Kubernetes 群集上安装的应用程åºã€‚安装以下任
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "请选择使用此Kubernetes群集的环境。"
-msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgid "ClusterIntegration|Cluster health"
+msgstr "集群è¿è¡ŒçŠ¶å†µ"
+
+msgid "ClusterIntegration|Cluster name is required."
msgstr ""
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr "通过选择匹é…的环境范围的最近集群。例如,项目群集将优先于群组群集。"
+
msgid "ClusterIntegration|Copy API URL"
msgstr "å¤åˆ¶API地å€"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "å¤åˆ¶CAè¯ä¹¦"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "å¤åˆ¶Ingress IP地å€åˆ°å‰ªè´´æ¿"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr "å¤åˆ¶ Ingress 节点到剪贴æ¿"
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "å°†Jupyter主机åå¤åˆ¶åˆ°å‰ªè´´æ¿"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
-msgstr ""
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
+msgstr "å¤åˆ¶ Knative 节点到剪贴æ¿"
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "å¤åˆ¶Kubernetes集群å称"
-msgid "ClusterIntegration|Copy Token"
-msgstr "å¤åˆ¶ä»¤ç‰Œ"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "创建Kubernetes集群"
@@ -2029,13 +2411,13 @@ msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives
msgstr "æ¯ä¸ªæ–°çš„ Google äº‘å¹³å° (GCP) å¸æˆ·ä½¿ç”¨æ­¤é“¾æŽ¥ %{sign_up_link} å¯ä»¥æ”¶åˆ° 价值300美元的å…è´¹é¢åº¦ã€‚通过与Google çš„åˆä½œï¼ŒGitLabå¯ä»¥ä¸ºæ–°æ³¨å†Œçš„和现有的 GCP å¸æˆ·æä¾›é¢å¤– 200 美元的é¢åº¦ï¼Œä»¥æ–¹ä¾¿å®žçŽ°GitLabå’Œ Google Kubernetes引擎的集æˆã€‚"
msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
-msgstr ""
+msgstr "é…置谷歌Kubernetes引擎(GKE)集群失败: %{message}"
msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
-msgstr ""
+msgstr "请求Google云平å°å¤±è´¥: %{message}"
msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
-msgstr ""
+msgstr "è¿è¡Œ Kubeclient 失败: %{message}"
msgid "ClusterIntegration|Fetching machine types"
msgstr "正在获å–实例类型"
@@ -2052,8 +2434,8 @@ msgstr "GitLab集æˆ"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab Runner"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab Runner连接到该项目的存储库并执行CI / CD作业,将结果推回,并部署应用程åºåˆ°ç”Ÿäº§çŽ¯å¢ƒã€‚"
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Google 云平å°é¡¹ç›®"
@@ -2079,14 +2461,14 @@ msgstr "éšè—"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "如果您正在设置多个群集并使用Auto DevOps,请%{help_link_start}先查阅此处%{help_link_end}。"
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "为了显示集群的å¥åº·çŠ¶å†µï¼Œæ‚¨çš„集群需è¦é…ç½®Prometheus以收集所需的数æ®ã€‚"
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr "è¦æŸ¥çœ‹é›†ç¾¤çš„è¿è¡ŒçŠ¶å†µï¼Œå¿…须先安装下é¢çš„ Prometheus。"
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress IP地å€"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr "Ingress 节点"
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress为您æ供了一ç§åŸºäºŽè¯·æ±‚主机或路径将请求路由到æœåŠ¡çš„方法,将多个æœåŠ¡é›†ä¸­åˆ°ä¸€ä¸ªå…¥å£ç‚¹ã€‚"
@@ -2094,15 +2476,18 @@ msgstr "Ingress为您æ供了一ç§åŸºäºŽè¯·æ±‚主机或路径将请求路由åˆ
msgid "ClusterIntegration|Install"
msgstr "安装"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "安装Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "已安装"
msgid "ClusterIntegration|Installing"
msgstr "安装中"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr "安装 Ingress å¯èƒ½ä¼šäº§ç”Ÿé¢å¤–费用。了解更多 %{pricingLink}。"
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr "安装 Knative å¯èƒ½ä¼šäº§ç”Ÿé¢å¤–费用。了解更多 %{pricingLink}。"
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "集æˆKubernetes集群自动化"
@@ -2110,10 +2495,10 @@ msgid "ClusterIntegration|Integration status"
msgstr "集æˆçŠ¶æ€"
msgid "ClusterIntegration|Issuer Email"
-msgstr ""
+msgstr "ç­¾å‘人电å­é‚®ä»¶"
msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
-msgstr ""
+msgstr "ç­¾å‘人代表è¯ä¹¦é¢å‘机构。您必须为您的签å‘人æ供电å­é‚®ä»¶åœ°å€ã€‚ "
msgid "ClusterIntegration|Jupyter Hostname"
msgstr "Jupyter主机å"
@@ -2130,11 +2515,11 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Knative域å:"
-msgid "ClusterIntegration|Knative IP Address:"
-msgstr ""
+msgid "ClusterIntegration|Knative Endpoint:"
+msgstr "Knative 节点:"
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
-msgstr ""
+msgstr "Knative扩展了Kubernetes,æ供了一组中间件组件,这些组件对于构建å¯åœ¨ä»»ä½•åœ°æ–¹è¿è¡ŒçŽ°ä»£çš„ã€ä»¥å¼€æºä¸ºä¸­å¿ƒå’ŒåŸºäºŽå®¹å™¨çš„应用程åºè‡³å…³é‡è¦ï¼šåœ¨æœ¬åœ°ï¼Œåœ¨äº‘中,甚至在第三方数æ®ä¸­å¿ƒã€‚"
msgid "ClusterIntegration|Kubernetes cluster"
msgstr "Kubernetes 集群"
@@ -2142,9 +2527,6 @@ msgstr "Kubernetes 集群"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Kubernetes集群详细信æ¯"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Kubernetes集群è¿è¡ŒçŠ¶å†µ"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "正在Google Kubernetes Engine上创建Kubernetes集群..."
@@ -2173,7 +2555,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "了解更多的群组级Kubernetes集群信æ¯"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "机器类型"
@@ -2187,9 +2569,6 @@ msgstr "管ç†"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "通过访问 %{link_gke} ç®¡ç† Kubernetes 集群"
-msgid "ClusterIntegration|More information"
-msgstr "更多信æ¯"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "未找到您æœç´¢çš„实例类型"
@@ -2202,27 +2581,24 @@ msgstr "未找到您æœç´¢çš„项目"
msgid "ClusterIntegration|No zones matched your search"
msgstr "未找到您æœç´¢çš„地域"
-msgid "ClusterIntegration|Note:"
-msgstr "注æ„:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "节点数é‡"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "请输入Kubernetes集群的访问信æ¯ã€‚如需帮助,å¯ä»¥é˜…读Kubernetes集群的 %{link_to_help_page}"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "请确ä¿æ‚¨çš„ Google å¸æˆ·ç¬¦åˆä»¥ä¸‹è¦æ±‚:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "å°†DNS指å‘生æˆçš„这个IP地å€ï¼Œä»¥ä¾¿åœ¨éƒ¨ç½²åŽè®¿é—®æ‚¨çš„应用程åºã€‚"
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr "将通é…符 DNS 指å‘此生æˆçš„节点,以便在部署应用程åºåŽè®¿é—®å®ƒã€‚"
msgid "ClusterIntegration|Project cluster"
msgstr "项目级集群"
-msgid "ClusterIntegration|Project namespace"
-msgstr "项目命å空间"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "项目命å空间(å¯é€‰ï¼Œå”¯ä¸€)"
@@ -2253,8 +2629,8 @@ msgstr "如果需è¦ï¼Œå¯å°†å…¶æ›¿æ¢ä¸ºæ‚¨è‡ªå·±çš„主机å。如果这样åš
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "请求安装失败"
-msgid "ClusterIntegration|Retry upgrade"
-msgstr ""
+msgid "ClusterIntegration|Retry update"
+msgstr "é‡è¯•æ›´æ–°"
msgid "ClusterIntegration|Save changes"
msgstr "ä¿å­˜æ›´æ”¹"
@@ -2289,8 +2665,11 @@ msgstr "选择地域"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "按地域选择实例类型"
-msgid "ClusterIntegration|Service token"
-msgstr "æœåŠ¡ä»¤ç‰Œ"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "显示"
@@ -2298,9 +2677,6 @@ msgstr "显示"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "å‘生了内部错误"
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "在 Google Kubernetes Engine 上创建Kubernetes集群时å‘生错误"
@@ -2308,10 +2684,10 @@ msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "安装 %{title} æ—¶å‘生故障"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
-msgstr ""
+msgstr "指定域将å…许您使用自动评审应用程åºå’Œè‡ªåŠ¨éƒ¨ç½²é˜¶æ®µè¿›è¡Œ %{auto_devops_start}Auto DevOps%{auto_devops_end}。域应é…置与域匹é…的通é…符DNS。"
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "IP地å€æ­£åœ¨åˆ†é…中。如果花费时间过长,请检查您的Kubernetes集群或谷歌Kubernetes引擎(GKE) 上的é…é¢ã€‚"
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr "节点正在分é…中。如果需è¦å¾ˆé•¿æ—¶é—´ï¼Œè¯·æ£€æŸ¥æ‚¨çš„ Kubernetes 集群或 Google Kubernetes Engine 上的é…é¢ã€‚"
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "该å¸æˆ·éœ€å…·å¤‡åœ¨å¦‚下指定的%{link_to_container_project}中创建 Kubernetes集群的æƒé™"
@@ -2319,23 +2695,29 @@ msgstr "该å¸æˆ·éœ€å…·å¤‡åœ¨å¦‚下指定的%{link_to_container_project}中创å»
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "此选项å…许您在å¯ç”¨RBAC的群集上安装应用程åºã€‚"
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr "è¦åœ¨éƒ¨ç½²åŽè®¿é—®æ‚¨çš„应用程åºï¼Œè¯·å°†é€šé…符 DNS æŒ‡å‘ Knative 节点。"
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "开关Kubernetes 集群"
-msgid "ClusterIntegration|Token"
-msgstr "令牌"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr "更新失败。请检查日志,然åŽé‡è¯•ã€‚"
+
+msgid "ClusterIntegration|Updating"
+msgstr "更新中"
msgid "ClusterIntegration|Upgrade"
-msgstr ""
+msgstr "å‡çº§"
msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
+msgstr "å‡çº§å¤±è´¥"
msgid "ClusterIntegration|Upgraded"
-msgstr ""
+msgstr "å·²å‡çº§"
msgid "ClusterIntegration|Upgrading"
-msgstr ""
+msgstr "å‡çº§ä¸­"
msgid "ClusterIntegration|Validating project billing status"
msgstr "验è¯é¡¹ç›®è´¦å•çŠ¶æ€"
@@ -2350,7 +2732,7 @@ msgid "ClusterIntegration|You must first install Helm Tiller before installing t
msgstr "在安装以下应用程åºä¹‹å‰ï¼Œå¿…须先安装Helm Tiller"
msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
-msgstr ""
+msgstr "您必须具有已å¯ç”¨RBAC的群集æ‰èƒ½å®‰è£…Knative。"
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr "您的å¸æˆ·å¿…须有æƒé™%{link_to_kubernetes_engine}"
@@ -2361,9 +2743,6 @@ msgstr "地域"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "访问 Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "查看定价"
-
msgid "ClusterIntegration|documentation"
msgstr "文档"
@@ -2373,6 +2752,9 @@ msgstr "帮助页é¢"
msgid "ClusterIntegration|meets the requirements"
msgstr "符åˆç›¸å…³è¦æ±‚"
+msgid "ClusterIntegration|pricing"
+msgstr "定价"
+
msgid "ClusterIntegration|properly configured"
msgstr "正确é…ç½®"
@@ -2380,31 +2762,43 @@ msgid "ClusterIntegration|sign up"
msgstr "注册"
msgid "Code"
-msgstr ""
+msgstr "代ç "
+
+msgid "Code Owners"
+msgstr "代ç æ‰€æœ‰è€…"
+
+msgid "Code owner approval is required"
+msgstr "代ç æ‰€æœ‰è€…的核准是必需的"
msgid "Code owners"
msgstr "代ç æ‰€æœ‰è€…"
+msgid "CodeOwner|Pattern"
+msgstr "模å¼"
+
msgid "Cohorts"
msgstr "世代表"
msgid "Collapse"
msgstr "收起"
+msgid "Collapse approvers"
+msgstr "折å æ ¸å‡†äºº"
+
msgid "Collapse sidebar"
msgstr "收起侧边æ "
msgid "Command line instructions"
-msgstr ""
+msgstr "命令行指引"
msgid "Comment"
msgstr "添加评论"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "评论并关闭 %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "评论并é‡æ–°å¼€å¯ %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "添加评论并解决讨论"
@@ -2423,13 +2817,13 @@ msgid_plural "Commits"
msgstr[0] "æ交"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "æ交 %{commit_id}"
msgid "Commit Message"
msgstr "æ交消æ¯"
msgid "Commit deleted"
-msgstr ""
+msgstr "æ交已删除"
msgid "Commit duration in minutes for last 30 commits"
msgstr "最近30次æ交相应æŒç»­é›†æˆèŠ±è´¹çš„时间(分钟)"
@@ -2485,6 +2879,9 @@ msgstr "æ交者:"
msgid "Commit…"
msgstr "æ交..."
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "比较"
@@ -2495,7 +2892,7 @@ msgid "Compare Revisions"
msgstr "比较版本"
msgid "Compare changes"
-msgstr ""
+msgstr "比较å˜æ›´"
msgid "Compare changes with the last commit"
msgstr "与上个æ交比较å˜æ›´å†…容"
@@ -2525,7 +2922,7 @@ msgid "Confidentiality"
msgstr "ä¿å¯†æ€§"
msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "é…ç½® GitLab Runner 以开始使用Web终端。 %{helpStart}了解更多。%{helpEnd}"
msgid "Configure Gitaly timeouts."
msgstr "é…ç½®Gitaly超时时间。"
@@ -2534,7 +2931,7 @@ msgid "Configure Tracing"
msgstr "é…置跟踪"
msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "在 <code>.gitlab</code> 目录中é…ç½® <code>.gitlab-webide.yml</code> 文件以开始使用Web终端。 %{helpStart}了解更多。%{helpEnd}"
msgid "Configure automatic git checks and housekeeping on repositories."
msgstr "在仓库上é…置自动git检查和仓库整ç†ã€‚"
@@ -2554,6 +2951,12 @@ msgstr "é…ç½® %{link} 集æˆã€‚"
msgid "Configure the way a user creates a new account."
msgstr "é…置用户创建新å¸æˆ·çš„æ–¹å¼ã€‚"
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "连接"
@@ -2569,12 +2972,24 @@ msgstr "连接外部仓库åŽï¼Œæ–°æ交将会å¯åŠ¨CI/CDæµæ°´çº¿ã€‚ä»…å¯ç”¨C
msgid "Connecting..."
msgstr "正在连接..."
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
+msgstr "è”系销售人员进行å‡çº§"
+
+msgid "Contact your Administrator to upgrade your license."
msgstr ""
msgid "Container Registry"
msgstr "容器注册表"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "已创建"
@@ -2621,7 +3036,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr "您也å¯ä»¥ä½¿ç”¨ %{deploy_token} 以åªè¯»æ–¹å¼è®¿é—®é•œåƒåº“çš„é•œåƒã€‚"
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr ".gitlab-ci.yml的内容"
msgid "Continue"
msgstr "继续"
@@ -2638,8 +3053,11 @@ msgstr "为 GitLab æ交贡献"
msgid "Contribution"
msgstr "贡献"
+msgid "Contribution Analytics"
+msgstr "贡献分æž"
+
msgid "Contribution Charts"
-msgstr ""
+msgstr "贡献图表"
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr "<strong>%{calendar_date}</strong>的贡献"
@@ -2665,23 +3083,14 @@ msgstr "请ç¨å€™ï¼Œå›¾è¡¨æž„建完æˆåŽé¡µé¢ä¼šè‡ªåŠ¨åˆ·æ–°ã€‚"
msgid "Control the display of third party offers."
msgstr "控制第三方优惠的显示。"
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "控制此次è¦èŠ‚点的 åŒæ­¥LFS/附件的最大并å‘"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "控制此次è¦èŠ‚点的åŒæ­¥å­˜å‚¨åº“的最大并å‘"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "控制此Geo节点的校验æ“作的最大并å‘性"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "ConvDev指数"
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "å¤åˆ¶ %{http_label} 克隆地å€"
msgid "Copy %{protocol} clone URL"
msgstr "å¤åˆ¶ %{protocol} 克隆URL"
@@ -2689,11 +3098,14 @@ msgstr "å¤åˆ¶ %{protocol} 克隆URL"
msgid "Copy ID to clipboard"
msgstr "å¤åˆ¶ID到剪贴æ¿"
+msgid "Copy KRB5 clone URL"
+msgstr "å¤åˆ¶KRB5克隆URL"
+
msgid "Copy SSH clone URL"
msgstr "å¤åˆ¶SSH克隆URL"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "å¤åˆ¶SSH公钥"
msgid "Copy SSH public key to clipboard"
msgstr "å°† SSH 公钥å¤åˆ¶åˆ°å‰ªè´´æ¿"
@@ -2713,9 +3125,6 @@ msgstr "å¤åˆ¶æ交 SHA 的值到剪贴æ¿"
msgid "Copy file path to clipboard"
msgstr "将文件路径å¤åˆ¶åˆ°å‰ªè´´æ¿"
-msgid "Copy incoming email address to clipboard"
-msgstr "将接收邮件地å€å¤åˆ¶åˆ°å‰ªè´´æ¿"
-
msgid "Copy link"
msgstr "å¤åˆ¶é“¾æŽ¥"
@@ -2731,9 +3140,33 @@ msgstr "å¤åˆ¶åˆ°å‰ªè´´æ¿"
msgid "Copy token to clipboard"
msgstr "将令牌å¤åˆ¶åˆ°å‰ªè´´æ¿"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "无法获å–æµæ°´çº¿çŠ¶æ€ã€‚有关故障排除步骤,请å‚阅 %{linkStart}文档%{linkEnd}。"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr "覆盖率"
+
msgid "Create"
msgstr "创建"
@@ -2741,23 +3174,26 @@ msgid "Create New Directory"
msgstr "创建新目录"
msgid "Create New Domain"
+msgstr "创建新域"
+
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
msgid "Create a new branch"
msgstr "创建一个新分支"
-msgid "Create a new branch and merge request"
-msgstr "创建一个新的分支åŠåˆå¹¶è¯·æ±‚"
-
msgid "Create a new issue"
msgstr "创建新议题"
msgid "Create a new repository"
-msgstr ""
+msgstr "创建一个新存储库"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ %{protocol} æ¥æ‹‰å–或推é€ã€‚"
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr "创建一个议题。为已触å‘çš„æ¯ä¸ªè­¦æŠ¥åˆ›å»ºè®®é¢˜ã€‚"
+
msgid "Create branch"
msgstr "创建分支"
@@ -2771,7 +3207,7 @@ msgid "Create empty repository"
msgstr "创建空的仓库"
msgid "Create epic"
-msgstr "创建å²è¯—故事"
+msgstr "创建epic"
msgid "Create file"
msgstr "创建文件"
@@ -2782,9 +3218,6 @@ msgstr "创建群组"
msgid "Create group label"
msgstr "创建群组标记"
-msgid "Create issue"
-msgstr "创建议题"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "从标记创建列表,å«æœ‰è¯¥æ ‡è®°çš„议题将出现在相应的列中。"
@@ -2795,7 +3228,7 @@ msgid "Create merge request and branch"
msgstr "创建åˆå¹¶è¯·æ±‚åŠåˆ†æ”¯"
msgid "Create milestone"
-msgstr ""
+msgstr "创建里程碑"
msgid "Create new branch"
msgstr "创建新分支"
@@ -2843,7 +3276,7 @@ msgid "Created on:"
msgstr "创建于:"
msgid "Creating epic"
-msgstr "创建å²è¯—故事中"
+msgstr "创建epic中"
msgid "Cron Timezone"
msgstr "Cron 时区"
@@ -2854,6 +3287,9 @@ msgstr "Cron 语法"
msgid "Current Branch"
msgstr "当å‰åˆ†æ”¯"
+msgid "Current Project"
+msgstr "当å‰é¡¹ç›®"
+
msgid "Current node"
msgstr "当å‰èŠ‚点"
@@ -2879,7 +3315,7 @@ msgid "Custom project templates"
msgstr "自定义项目模æ¿"
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
-msgstr ""
+msgstr "尚未为您所属的群组设置自定义项目模æ¿ã€‚请在群组的设置页é¢å¯ç”¨å®ƒã€‚è¦è®¾ç½®è‡ªå®šä¹‰é¡¹ç›®æ¨¡æ¿ï¼Œè¯·è”系群组的所有者或维护者。"
msgid "Customize colors"
msgstr "自定义颜色"
@@ -2891,10 +3327,7 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr "自定义如何将Google Code电å­é‚®ä»¶åœ°å€å’Œç”¨æˆ·å导入GitLab。下一步将选择è¦å¯¼å…¥çš„项目。"
msgid "Customize language and region related settings."
-msgstr ""
-
-msgid "Customize your merge request approval settings."
-msgstr ""
+msgstr "自定义语言和区域相关设置。"
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "自定义æµæ°´çº¿é…置,查看æµæ°´çº¿çŠ¶æ€å’Œè¦†ç›–率报告。"
@@ -2927,7 +3360,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "测试"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "仪表æ¿"
@@ -2938,9 +3371,12 @@ msgstr "所有"
msgid "DashboardProjects|Personal"
msgstr "个人"
-msgid "Data is still calculating..."
+msgid "Dashboards"
msgstr ""
+msgid "Data is still calculating..."
+msgstr "æ•°æ®ä»åœ¨è®¡ç®—中……"
+
msgid "Date picker"
msgstr "日期选择器"
@@ -2954,7 +3390,7 @@ msgid "December"
msgstr "12月"
msgid "Decline"
-msgstr ""
+msgstr "æ‹’ç»"
msgid "Decline and sign out"
msgstr "æ‹’ç»å¹¶é€€å‡º"
@@ -2962,13 +3398,22 @@ msgstr "æ‹’ç»å¹¶é€€å‡º"
msgid "Default Branch"
msgstr "默认分支"
+msgid "Default artifacts expiration"
+msgstr "默认工件过期时间"
+
msgid "Default classification label"
msgstr "默认分类标记"
-msgid "Default first day of the week"
+msgid "Default description template for issues"
msgstr ""
+msgid "Default first day of the week"
+msgstr "æ¯å‘¨çš„默认起始日"
+
msgid "Default first day of the week in calendars and date pickers."
+msgstr "在日期选择器中显示æ¯å‘¨é»˜è®¤çš„起始日。"
+
+msgid "Default issue template"
msgstr ""
msgid "Default: Directly import the Google Code email address or username"
@@ -2981,7 +3426,7 @@ msgid "Define a custom pattern with cron syntax"
msgstr "使用 Cron 语法定义自定义模å¼"
msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
-msgstr ""
+msgstr "在<code>.githab-ci.yml</code>çš„ deploy 阶段中定义 environment æ¥è·Ÿè¸ªéƒ¨ç½²ã€‚"
msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
msgstr "您确定è¦ç«‹å³è¿è¡Œ %{jobName} å—?å¦åˆ™çš„è¯ï¼Œè¯¥ä½œä¸šå°†åœ¨è®¡æ—¶ç»“æŸåŽè‡ªåŠ¨è¿è¡Œã€‚"
@@ -3010,18 +3455,36 @@ msgstr "删除代ç ç‰‡æ®µ"
msgid "Delete comment"
msgstr "删除评论"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "删除列表"
msgid "Delete source branch"
-msgstr ""
+msgstr "删除æºåˆ†æ”¯"
msgid "Delete this attachment"
-msgstr ""
+msgstr "删除此附件"
msgid "Deleted"
msgstr "已删除"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "æ‹’ç»"
@@ -3032,6 +3495,9 @@ msgstr[0] "部署"
msgid "Deploy Keys"
msgstr "部署密钥"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "+%{count} 其他"
@@ -3153,7 +3619,7 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "新项目部署令牌已创建。"
msgid "Deployed"
-msgstr ""
+msgstr "已部署"
msgid "Deployed to"
msgstr "已部署到"
@@ -3170,12 +3636,45 @@ msgstr "é™åº"
msgid "Description"
msgstr "æè¿°"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "æ述模æ¿å…许您为项目的问题和åˆå¹¶è¯·æ±‚定义æ述字段的特定模æ¿ã€‚"
msgid "Description:"
msgstr "æè¿°:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr "设计"
+
msgid "Destroy"
msgstr "删除"
@@ -3183,7 +3682,7 @@ msgid "Details"
msgstr "详情"
msgid "Details (default)"
-msgstr ""
+msgstr "详细信æ¯(默认)"
msgid "Detect host keys"
msgstr "检测主机密钥"
@@ -3216,10 +3715,10 @@ msgid "Disable group Runners"
msgstr "ç¦ç”¨ç¾¤ç»„Runner"
msgid "Disable shared Runners"
-msgstr ""
+msgstr "ç¦ç”¨å…±äº«è¿è¡Œå™¨"
msgid "Disabled"
-msgstr ""
+msgstr "å·²ç¦ç”¨"
msgid "Discard"
msgstr "放弃"
@@ -3243,22 +3742,25 @@ msgid "Discard review"
msgstr "放弃评审"
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "å‘现GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr "æµè§ˆé¡¹ç›®ï¼Œç¾¤ç»„和代ç ç‰‡æ®µã€‚与他人分享您的项目"
msgid "Discuss a specific suggestion or question"
-msgstr ""
+msgstr "讨论具体的建议或议题"
msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
+msgstr "讨论需è¦è§£å†³çš„具体建议或议题"
+
+msgid "Discussion"
+msgstr "讨论"
msgid "Dismiss"
msgstr "忽略"
msgid "Dismiss ConvDev introduction"
-msgstr ""
+msgstr "å–消 ConvDev 导入"
msgid "Dismiss Cycle Analytics introduction box"
msgstr "关闭循环分æžä»‹ç»æ¡†"
@@ -3288,25 +3790,31 @@ msgid "Download"
msgstr "下载"
msgid "Download artifacts"
-msgstr ""
+msgstr "下载工件"
msgid "Download asset"
+msgstr "下载资æº"
+
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
msgstr ""
msgid "Download tar"
-msgstr "下载 tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "下载 tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "下载 tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "下载 zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "下载"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "电å­é‚®ä»¶è¡¥ä¸"
@@ -3318,7 +3826,7 @@ msgid "DownloadSource|Download"
msgstr "下载"
msgid "Downstream"
-msgstr ""
+msgstr "下游"
msgid "Downvotes"
msgstr "踩"
@@ -3336,13 +3844,16 @@ msgid "Edit"
msgstr "编辑"
msgid "Edit %{name}"
-msgstr ""
+msgstr "编辑%{name}"
+
+msgid "Edit Deploy Key"
+msgstr "编辑部署密钥"
msgid "Edit Label"
msgstr "编辑标签"
msgid "Edit Milestone"
-msgstr ""
+msgstr "编辑里程碑"
msgid "Edit Pipeline Schedule %{id}"
msgstr "编辑 %{id} æµæ°´çº¿è®¡åˆ’"
@@ -3354,10 +3865,13 @@ msgid "Edit application"
msgstr "编辑应用"
msgid "Edit comment"
-msgstr ""
+msgstr "编辑评论"
msgid "Edit environment"
-msgstr ""
+msgstr "编辑环境"
+
+msgid "Edit file"
+msgstr "编辑文件"
msgid "Edit files in the editor and commit changes here"
msgstr "在编辑器中编辑文件并在这里​​æ交å˜æ›´å†…容"
@@ -3369,7 +3883,10 @@ msgid "Edit identity for %{user_name}"
msgstr "编辑 %{user_name} 的身份信æ¯"
msgid "Edit issues"
-msgstr ""
+msgstr "编辑议题"
+
+msgid "Edit public deploy key"
+msgstr "编辑公共部署密钥"
msgid "Elasticsearch"
msgstr "Elasticsearch"
@@ -3377,12 +3894,45 @@ msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Elasticsearch集æˆåŠElasticsearch AWS IAM。"
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "电å­é‚®ä»¶"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "电å­é‚®ä»¶è¡¥ä¸"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "电å­é‚®ä»¶"
@@ -3390,7 +3940,7 @@ msgid "Embed"
msgstr "嵌入"
msgid "Empty file"
-msgstr ""
+msgstr "空文件"
msgid "Enable"
msgstr "å¯ç”¨"
@@ -3398,6 +3948,9 @@ msgstr "å¯ç”¨"
msgid "Enable Auto DevOps"
msgstr "å¯ç”¨Auto DevOps"
+msgid "Enable HTML emails"
+msgstr "å¯ç”¨ HTML 电å­é‚®ä»¶"
+
msgid "Enable Pseudonymizer data collection"
msgstr "å¯ç”¨åŒ¿å化的数æ®æ”¶é›†"
@@ -3417,7 +3970,7 @@ msgid "Enable classification control using an external service"
msgstr "使用外部æœåŠ¡å¯ç”¨åˆ†ç±»æŽ§åˆ¶"
msgid "Enable error tracking"
-msgstr ""
+msgstr "å¯ç”¨é”™è¯¯è·Ÿè¸ª"
msgid "Enable for this project"
msgstr "在此项目中å¯ç”¨"
@@ -3425,6 +3978,12 @@ msgstr "在此项目中å¯ç”¨"
msgid "Enable group Runners"
msgstr "å¯ç”¨ç¾¤ç»„Runner"
+msgid "Enable header and footer in emails"
+msgstr "在电å­é‚®ä»¶ä¸­å¯ç”¨é¡µçœ‰å’Œé¡µè„š"
+
+msgid "Enable mirror configuration"
+msgstr "å¯ç”¨é•œåƒé…ç½®"
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "å¯ç”¨æˆ–ç¦ç”¨åŒ¿å化数æ®æ”¶é›†."
@@ -3434,17 +3993,14 @@ msgstr "å¯ç”¨æˆ–ç¦ç”¨ç‰ˆæœ¬æ£€æŸ¥åŠä½¿ç”¨æƒ…况检测(usage ping)。"
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "å¯ç”¨reCAPTCHA或Akismet并设置IPé™åˆ¶ã€‚"
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
-msgstr ""
+msgstr "å¯ç”¨å…±äº«çš„è¿è¡Œå™¨"
msgid "Enable the Performance Bar for a given group."
msgstr "对指定群组å¯ç”¨æ€§èƒ½æ ã€‚"
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "å¯ç”¨åŒå› ç´ èº«ä»½éªŒè¯"
msgid "Enable usage ping"
msgstr "å¯ç”¨ä½¿ç”¨æƒ…况检测(usage ping)"
@@ -3452,16 +4008,28 @@ msgstr "å¯ç”¨ä½¿ç”¨æƒ…况检测(usage ping)"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "å¯ç”¨ä½¿ç”¨æƒ…况检测(usage ping)以从功能角度总体上了解您如何使用GitLab。"
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "å·²å¯ç”¨"
-msgid "Ends at (UTC)"
-msgstr "结æŸäºŽ(UTC)"
+msgid "Enabled Git access protocols"
+msgstr "å¯ç”¨ Git 访问åè®®"
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr "è¦åœ¨é¡¹ç›®åˆ›å»ºæœŸé—´å¯ç”¨ä»£ç å¯¼å…¥æºã€‚必须为 GitHub é…ç½® OmniAuth"
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr "此功能仅在 GitLab EE 许å¯ä¸‹å¯ç”¨ã€‚如果è¦å¯ç”¨ï¼Œè¯·ç¡®è®¤å‘½å空间包å«æ­¤åŠŸèƒ½æˆ–者是公开项目。"
-msgid "Enforce SSO-only authentication for this group"
+msgid "End date"
msgstr ""
-msgid "Enforced SSO"
+msgid "Ends at (UTC)"
+msgstr "结æŸäºŽ(UTC)"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3479,26 +4047,20 @@ msgstr "输入åˆå¹¶è¯·æ±‚说明"
msgid "Enter the merge request title"
msgstr "输入åˆå¹¶è¯·æ±‚标题"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
-msgstr ""
+msgstr "环境å˜é‡å·²è¢«ç®¡ç†å‘˜é…置为%{link_start}å—ä¿æŠ¤çš„(默认)%{link_end}"
msgid "Environment:"
-msgstr ""
+msgstr "环境:"
msgid "Environments"
msgstr "环境"
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
-msgstr ""
+msgstr "环境å…许您跟踪应用程åºçš„部署 %{link_to_read_more}。"
msgid "Environments|An error occurred while fetching the environments."
msgstr "获å–环境时å‘生错误。"
@@ -3506,6 +4068,12 @@ msgstr "获å–环境时å‘生错误。"
msgid "Environments|An error occurred while making the request."
msgstr "å‘é€è¯·æ±‚æ—¶å‘生错误。"
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr "é‡æ–°éƒ¨ç½²çŽ¯å¢ƒæ—¶å‡ºé”™ï¼Œè¯·é‡è¯•"
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr "回滚环境时å‘生错误,请é‡è¯•"
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "终止环境时å‘生错误,请ç¨åŽé‡è¯•"
@@ -3557,15 +4125,33 @@ msgstr "打开è¿è¡Œä¸­çš„环境"
msgid "Environments|Pod logs from"
msgstr "Pod日志æ¥è‡ªäºŽ"
+msgid "Environments|Re-deploy"
+msgstr "é‡æ–°éƒ¨ç½²"
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr "é‡æ–°éƒ¨ç½²çŽ¯å¢ƒ %{environment_name}?"
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr "é‡æ–°éƒ¨ç½²çŽ¯å¢ƒ %{name}?"
+
msgid "Environments|Re-deploy to environment"
msgstr "é‡æ–°éƒ¨ç½²è‡³çŽ¯å¢ƒ"
msgid "Environments|Read more about environments"
msgstr "了解有关环境的更多信æ¯"
+msgid "Environments|Rollback"
+msgstr "回滚"
+
msgid "Environments|Rollback environment"
msgstr "回滚环境"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr "回滚环境 %{environment_name}?"
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr "回滚环境 %{name}?"
+
msgid "Environments|Show all"
msgstr "显示全部"
@@ -3576,7 +4162,19 @@ msgid "Environments|Stop environment"
msgstr "终止环境"
msgid "Environments|Stopping"
-msgstr ""
+msgstr "åœæ­¢ä¸­"
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr "æ­¤æ“作将é‡æ–°å¯åŠ¨æ交%{commit_id}的作业,将环境置于先å‰ç‰ˆæœ¬ä¸­ã€‚你确定你è¦ç»§ç»­å—?"
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr "æ­¤æ“作将é‡æ–°å¯åŠ¨æ交%{linkStart}%{commitId}%{linkEnd}的作业,将环境置于先å‰ç‰ˆæœ¬ä¸­ã€‚你确定你è¦ç»§ç»­å—?"
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr "æ­¤æ“作将è¿è¡Œç”± %{name} 定义的作业,用于æ交 %{linkStart}%{commitId}%{linkEnd} 将环境置于先å‰ç‰ˆæœ¬ä¸­ã€‚您å¯ä»¥é€šè¿‡é‡æ–°éƒ¨ç½²æœ€æ–°ç‰ˆæœ¬çš„应用程åºæ¥è¿˜åŽŸå®ƒã€‚你确定你è¦ç»§ç»­å—?"
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr "æ­¤æ“作将è¿è¡Œç”± staging 定义的作业,用于æ交 %{commit_id} 将环境置于先å‰ç‰ˆæœ¬ä¸­ã€‚您å¯ä»¥é€šè¿‡é‡æ–°éƒ¨ç½²æœ€æ–°ç‰ˆæœ¬çš„应用程åºæ¥è¿˜åŽŸå®ƒã€‚你确定你è¦ç»§ç»­å—?"
msgid "Environments|Updated"
msgstr "更新于"
@@ -3588,19 +4186,19 @@ msgid "Environments|protected"
msgstr "å—ä¿æŠ¤çš„"
msgid "Epic"
-msgstr "å²è¯—故事"
+msgstr "Epic"
msgid "Epics"
-msgstr "å²è¯—故事"
+msgstr "Epic"
msgid "Epics Roadmap"
-msgstr "å²è¯—故事路线图"
+msgstr "Epic路线图"
msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
-msgstr "利用å²è¯—故事(Epics),产å“线管ç†ä¼šå˜å¾—æ›´è½»æ¾ä¸”更高效"
+msgstr "利用 Epic,产å“线管ç†ä¼šå˜å¾—æ›´è½»æ¾ä¸”更高效"
msgid "Epics|An error occurred while saving the %{epicDateType} date"
-msgstr ""
+msgstr "ä¿å­˜ %{epicDateType} 日期时å‘生错误"
msgid "Epics|How can I solve this?"
msgstr "我该如何解决该问题?"
@@ -3609,10 +4207,10 @@ msgid "Epics|More information"
msgstr "更多信æ¯"
msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
-msgstr "这些日期会影å“å²è¯—故事在路线图中的显示方å¼ã€‚里程碑日期æ¥è‡ªäºŽå²è¯—故事中的议题所属的里程碑。您还å¯ä»¥è®¾ç½®å›ºå®šæ—¥æœŸæˆ–完全删除它们。"
+msgstr "è¿™äº›æ—¥æœŸä¼šå½±å“ epic 在路线图中的显示方å¼ã€‚里程碑日期æ¥è‡ªäºŽå²è¯—故事中的议题所属的里程碑。您还å¯ä»¥è®¾ç½®å›ºå®šæ—¥æœŸæˆ–完全删除它们。"
msgid "Epics|To schedule your epic's %{epicDateType} date based on milestones, assign a milestone with a %{epicDateType} date to any issue in the epic."
-msgstr "如需根æ®é‡Œç¨‹ç¢‘æ¥å®‰æŽ’å²è¯—故事的 %{epicDateType} 日期,请为å²è¯—故事中的任一议题指定带有 %{epicDateType} 日期的里程碑。"
+msgstr "如需根æ®é‡Œç¨‹ç¢‘æ¥å®‰æŽ’ epic çš„ %{epicDateType} 日期,请为 epic 中的任一议题指定带有 %{epicDateType} 日期的里程碑。"
msgid "Epics|due"
msgstr "到期"
@@ -3627,13 +4225,16 @@ msgid "Error Reporting and Logging"
msgstr "错误报告和日志记录"
msgid "Error Tracking"
-msgstr ""
+msgstr "错误跟踪"
+
+msgid "Error creating a new path"
+msgstr "创建新路径时出错"
msgid "Error creating epic"
-msgstr "创建å²è¯—故事时出错"
+msgstr "创建 epic 时出错"
msgid "Error deleting %{issuableType}"
-msgstr ""
+msgstr "删除 %{issuableType} 时出错"
msgid "Error fetching contributors data."
msgstr "获å–贡献者数æ®æ—¶å‡ºé”™ã€‚"
@@ -3677,14 +4278,26 @@ msgstr "加载模æ¿æ—¶å‡ºé”™ã€‚"
msgid "Error occurred when toggling the notification subscription"
msgstr "切æ¢é€šçŸ¥è®¢é˜…æ—¶å‘生错误"
-msgid "Error rendering markdown preview"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
msgstr ""
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
+msgid "Error rendering markdown preview"
+msgstr "渲染Markdown预览时出错"
+
msgid "Error saving label update."
msgstr "ä¿å­˜æ ‡è®°æ›´æ–°æ—¶å‡ºé”™ã€‚"
msgid "Error updating %{issuableType}"
-msgstr ""
+msgstr "更新 %{issuableType} 时出错"
msgid "Error updating status for all todos."
msgstr "更新所有待办事项的状æ€æ—¶å‡ºé”™ã€‚"
@@ -3692,15 +4305,51 @@ msgstr "更新所有待办事项的状æ€æ—¶å‡ºé”™ã€‚"
msgid "Error updating todo status."
msgstr "更新待办事项状æ€æ—¶å‡ºé”™ã€‚"
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr "加载åˆå¹¶è¯·æ±‚时出错。请å†è¯•ä¸€æ¬¡ã€‚"
-msgid "Error:"
+msgid "Error while migrating %{upload_id}: %{error_message}"
msgstr ""
-msgid "Errors"
+msgid "Error with Akismet. Please check the logs for more info."
msgstr ""
+msgid "Error:"
+msgstr "错误:"
+
+msgid "ErrorTracking|Active"
+msgstr "å¯ç”¨"
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr "添加验è¯ä»¤ç‰ŒåŽï¼Œä½¿ç”¨â€œè¿žæŽ¥â€æŒ‰é’®åŠ è½½é¡¹ç›®"
+
+msgid "ErrorTracking|Auth Token"
+msgstr "验è¯ä»¤ç‰Œ"
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr "å•å‡»â€œè¿žæŽ¥â€ä»¥é‡æ–°å»ºç«‹ä¸ŽSentry的连接并激活下拉列表。"
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr "连接失败。请å†æ¬¡æ£€æŸ¥éªŒè¯ä»¤ç‰Œï¼Œç„¶åŽé‡è¯•ã€‚"
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr "在Sentryå¸æˆ·è®¾ç½®é¡µé¢ä¸­æ‰¾åˆ°æ‚¨çš„主机å"
+
+msgid "ErrorTracking|No projects available"
+msgstr "æ— å¯ç”¨çš„项目"
+
+msgid "ErrorTracking|Select project"
+msgstr "选择项目"
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr "è¦å¯ç”¨é€‰æ‹©çš„项目,请输入有效的验è¯ä»¤ç‰Œ"
+
+msgid "Errors"
+msgstr "错误"
+
msgid "Estimated"
msgstr "预计"
@@ -3723,10 +4372,10 @@ msgid "EventFilterBy|Filter by team"
msgstr "åªæ˜¾ç¤ºå›¢é˜Ÿäº‹ä»¶"
msgid "Events"
-msgstr ""
+msgstr "事件"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
-msgstr ""
+msgstr "所有 %{action} å°è¯•éƒ½å·²å¤±è´¥ï¼š %{job_error_message}。请å†è¯•ä¸€æ¬¡ã€‚"
msgid "Every day (at 4:00am)"
msgstr "æ¯æ—¥æ‰§è¡Œï¼ˆå‡Œæ™¨ 4 点)"
@@ -3738,37 +4387,31 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "æ¯å‘¨æ‰§è¡Œï¼ˆå‘¨æ—¥å‡Œæ™¨ 4 点)"
msgid "Everyone"
-msgstr ""
+msgstr "所有人"
msgid "Everyone can contribute"
msgstr "人人皆å¯è´¡çŒ®"
msgid "Everything you need to create a GitLab Pages site using GitBook."
-msgstr ""
+msgstr "使用GitBook创建GitLab Pages站点所需的一切。"
msgid "Everything you need to create a GitLab Pages site using Hexo."
-msgstr ""
+msgstr "使用Hexo创建GitLab Pages站点所需的一切。"
msgid "Everything you need to create a GitLab Pages site using Hugo."
-msgstr ""
+msgstr "使用Hugo创建GitLab Pages站点所需的一切。"
msgid "Everything you need to create a GitLab Pages site using Jekyll."
-msgstr ""
+msgstr "使用Jekyll创建GitLab Pages站点所需的一切。"
msgid "Everything you need to create a GitLab Pages site using plain HTML."
-msgstr ""
+msgstr "使用纯HTML创建GitLab Pages网站所需的一切。"
msgid "Except policy:"
-msgstr ""
-
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
+msgstr "策略以外:"
msgid "Existing members and groups"
-msgstr ""
+msgstr "现有的æˆå‘˜å’Œç¾¤ç»„"
msgid "Expand"
msgstr "展开"
@@ -3776,17 +4419,23 @@ msgstr "展开"
msgid "Expand all"
msgstr "展开全部"
+msgid "Expand approvers"
+msgstr "展开核准人"
+
msgid "Expand sidebar"
msgstr "展开侧边æ "
msgid "Expiration date"
msgstr "到期时间"
-msgid "Expired %{expiredOn}"
+msgid "Expired"
msgstr ""
+msgid "Expired %{expiredOn}"
+msgstr "已在 %{expiredOn} 过期"
+
msgid "Expires in %{expires_at}"
-msgstr ""
+msgstr "在 %{expires_at} 过期"
msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
msgstr "请解释此问题。如适用,å¯æ供相关议题或评论的链接。"
@@ -3810,19 +4459,25 @@ msgid "Explore public groups"
msgstr "æœç´¢å…¬å¼€ç¾¤ç»„"
msgid "Export as CSV"
-msgstr ""
+msgstr "导出为 CSV"
msgid "Export issues"
+msgstr "导出议题"
+
+msgid "Export project"
+msgstr ""
+
+msgid "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."
msgstr ""
msgid "External Classification Policy Authorization"
msgstr "外部分类政策授æƒ"
msgid "External URL"
-msgstr ""
+msgstr "外部URL"
msgid "External Wiki"
-msgstr ""
+msgstr "外部Wiki"
msgid "External authentication"
msgstr "外部身份验è¯"
@@ -3833,9 +4488,6 @@ msgstr "外部授æƒæ‹’ç»è®¿é—®æ­¤é¡¹ç›®"
msgid "External authorization request timeout"
msgstr "外部授æƒè¯·æ±‚超时"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "分类标签"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "分类标签"
@@ -3857,13 +4509,19 @@ msgstr "无法å˜æ›´æ‰€æœ‰è€…"
msgid "Failed to check related branches."
msgstr "无法检查相关分支。"
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "无法部署到"
msgid "Failed to load emoji list."
msgstr "无法加载表情列表。"
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr "无法从Sentry加载错误。错误消æ¯: %{errorMessage}"
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3875,7 +4533,22 @@ msgstr "删除镜åƒå¤±è´¥ã€‚"
msgid "Failed to remove the pipeline schedule"
msgstr "无法删除æµæ°´çº¿è®¡åˆ’"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
+msgstr "é‡ç½®å¯†é’¥å¤±è´¥ã€‚请é‡è¯•ã€‚"
+
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
msgstr ""
msgid "Failed to signing using smartcard authentication"
@@ -3885,25 +4558,31 @@ msgid "Failed to update issues, please try again."
msgstr "更新议题失败, 请é‡è¯•"
msgid "Failed to upload object map file"
-msgstr ""
+msgstr "上传对象映射文件失败"
msgid "Failure"
msgstr "失败"
-msgid "Fast-forward merge without a merge commit"
+msgid "Fast-forward merge"
msgstr ""
+msgid "Fast-forward merge without a merge commit"
+msgstr "没有åˆå¹¶æ交的快进åˆå¹¶"
+
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "速度更快,因其é‡ç”¨äº†é¡¹ç›®çš„工作空间(如果它ä¸å­˜åœ¨ï¼Œå°†å›žé€€åˆ°å…‹éš†ï¼‰"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "功能标志"
msgid "FeatureFlags|* (All Environments)"
-msgstr ""
+msgstr "*(所有环境)"
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "*(所有环境)"
msgid "FeatureFlags|API URL"
msgstr "API URL"
@@ -3921,49 +4600,46 @@ msgid "FeatureFlags|Create feature flag"
msgstr "创建功能标志"
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "删除 %{name}?"
msgid "FeatureFlags|Delete feature flag"
-msgstr ""
+msgstr "删除功能标志"
msgid "FeatureFlags|Description"
msgstr "æè¿°"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "编辑 %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "编辑功能标志"
msgid "FeatureFlags|Environment Spec"
-msgstr ""
+msgstr "环境规格"
msgid "FeatureFlags|Environment Specs"
-msgstr ""
+msgstr "环境规格"
msgid "FeatureFlags|Feature Flag"
msgstr "功能标志"
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
-msgstr ""
+msgstr "功能标记是通过创建一组规则æ¥å®šä¹‰ç›®æ ‡çŽ¯å¢ƒçš„状æ€æ¥æž„建的行为。默认的规则%{codeStart} *%{codeEnd}用于%{boldStart}所有环境%{boldEnd},您å¯ä»¥é€šè¿‡ä¸‹é¢çš„环境规范添加任æ„æ•°é‡çš„规则。您å¯ä»¥åˆ‡æ¢æ¯ä¸ªè§„则的行为以设置它们%{boldStart}活动%{boldEnd}或%{boldStart}éžæ´»åŠ¨%{boldEnd}。"
msgid "FeatureFlags|Feature Flags"
-msgstr ""
+msgstr "功能标志"
msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
-msgstr ""
+msgstr "功能标志å…许您通过动æ€åˆ‡æ¢æŸäº›åŠŸèƒ½å°†ä»£ç é…置为ä¸åŒçš„风格。"
msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
-msgstr ""
+msgstr "功能标志 %{name} 将被删除。您确定å—?"
msgid "FeatureFlags|Get started with Feature Flags"
-msgstr ""
+msgstr "功能标志入门"
msgid "FeatureFlags|Inactive"
msgstr "未å¯ç”¨"
msgid "FeatureFlags|Inactive flag for %{scope}"
-msgstr ""
+msgstr "%{scope} çš„éžæ´»åŠ¨æ ‡å¿—"
msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
msgstr "安装 %{docs_link_start}兼容的客户端库%{docs_link_end} ,并在é…置指定API URLã€åº”用å称和实例ID。"
@@ -3972,10 +4648,10 @@ msgid "FeatureFlags|Instance ID"
msgstr "实例ID"
msgid "FeatureFlags|Loading Feature Flags"
-msgstr ""
+msgstr "加载功能标志"
msgid "FeatureFlags|More Information"
-msgstr ""
+msgstr "更多信æ¯"
msgid "FeatureFlags|More information"
msgstr "更多信æ¯"
@@ -3989,26 +4665,26 @@ msgstr "新建"
msgid "FeatureFlags|New Feature Flag"
msgstr "新建功能标志"
-msgid "FeatureFlags|Save changes"
-msgstr "ä¿å­˜æ›´æ”¹"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "状æ€"
msgid "FeatureFlags|Target environments"
-msgstr ""
+msgstr "目标环境"
msgid "FeatureFlags|There are no active Feature Flags"
-msgstr ""
+msgstr "没有活动的功能标志"
msgid "FeatureFlags|There are no inactive Feature Flags"
-msgstr ""
+msgstr "没有éžæ´»åŠ¨çš„功能标志"
msgid "FeatureFlags|There was an error fetching the feature flags."
-msgstr ""
+msgstr "获å–功能标志时出错。"
msgid "FeatureFlags|Try again in a few moments or contact your support team."
-msgstr ""
+msgstr "请ç¨åŽé‡è¯•æˆ–è”系您的支æŒå›¢é˜Ÿã€‚"
msgid "Feb"
msgstr "2月"
@@ -4016,33 +4692,45 @@ msgstr "2月"
msgid "February"
msgstr "2月"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "当å‰é¡µé¢ä¸Šçš„字段ä¸å¯ç¼–辑,å¯ä»¥é…ç½®"
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
+msgstr[0] "文件"
msgid "File added"
-msgstr ""
+msgstr "文件已添加"
msgid "File browser"
-msgstr ""
+msgstr "文件æµè§ˆ"
msgid "File deleted"
-msgstr ""
+msgstr "文件已删除"
msgid "File mode changed from %{a_mode} to %{b_mode}"
-msgstr ""
+msgstr "文件模å¼ä»Ž %{a_mode} 更改为 %{b_mode}"
msgid "File moved"
-msgstr ""
+msgstr "文件已移动"
msgid "File templates"
msgstr "文件模æ¿"
msgid "File upload error."
-msgstr ""
+msgstr "文件上传错误。"
msgid "Files"
msgstr "文件"
@@ -4063,28 +4751,25 @@ msgid "Filter by commit message"
msgstr "按æ交消æ¯è¿‡æ»¤"
msgid "Filter by milestone name"
-msgstr ""
+msgstr "按里程碑å称过滤"
msgid "Filter by two-factor authentication"
-msgstr ""
+msgstr "按åŒé‡èº«ä»½éªŒè¯è¿‡æ»¤"
msgid "Filter results by group"
-msgstr ""
+msgstr "按群组过滤结果"
msgid "Filter results by project"
-msgstr ""
+msgstr "按项目过滤结果"
msgid "Filter..."
msgstr "过滤..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "按路径查找"
msgid "Find existing members by name"
-msgstr ""
+msgstr "按å称查找现有æˆå‘˜"
msgid "Find file"
msgstr "查找文件"
@@ -4095,11 +4780,14 @@ msgstr "找到下载的ZIP文件并解压缩。"
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "查找新æå–çš„ <code>Takeout/Google Code项目托管/GoogleCodeProjectHosting. json</code> 文件。"
+msgid "Fingerprint"
+msgstr "指纹"
+
msgid "Fingerprints"
msgstr "指纹"
msgid "Finish editing this message first!"
-msgstr ""
+msgstr "先完æˆæ­¤æ¶ˆæ¯çš„编辑ï¼"
msgid "Finish review"
msgstr "完æˆè¯„审"
@@ -4108,7 +4796,7 @@ msgid "Finished"
msgstr "已完æˆ"
msgid "First day of the week"
-msgstr ""
+msgstr "æ¯å‘¨çš„起始日"
msgid "FirstPushedBy|First"
msgstr "首次推é€"
@@ -4156,7 +4844,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr "对于内部项目,任何已登录的用户都å¯ä»¥æŸ¥çœ‹æµæ°´çº¿å¹¶è®¿é—®ä½œä¸šè¯¦æƒ…(输出日志和工件)"
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "有关详细信æ¯ï¼Œè¯·é˜…读文档。"
msgid "For more information, go to the "
msgstr "如需了解详细信æ¯ï¼Œè¯·å‚阅"
@@ -4183,19 +4871,25 @@ msgid "Forking in progress"
msgstr "派生(Fork)中"
msgid "Forks"
-msgstr ""
+msgstr "派生"
msgid "Format"
msgstr "æ ¼å¼"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr "在您的 %{gitlab_ci_yml} 中找到错误:"
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "在.gitlab-ci.yml中å‘现错误:"
msgid "Free Trial of GitLab.com Gold"
msgstr "å…费试用gitlab.comçš„Gold计划"
-msgid "From %{provider_title}"
-msgstr "æ¥è‡ª %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr "æ¥è‡ª%{providerTitle}"
msgid "From Bitbucket"
msgstr "æ¥è‡ªBitbucket"
@@ -4224,19 +4918,31 @@ msgstr "æ¥è‡ªé‡Œç¨‹ç¢‘:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "在Kubernetes集群详细信æ¯è§†å›¾ä¸­ï¼Œä»Žåº”用程åºåˆ—表中安装Runner"
+msgid "GPG Key ID:"
+msgstr "GPG密钥ID:"
+
msgid "GPG Keys"
msgstr "GPG 密钥"
+msgid "GPG signature (loading...)"
+msgstr "GPGç­¾å(加载中...)"
+
msgid "General"
msgstr "通用"
+msgid "General Settings"
+msgstr "常规设置"
+
msgid "General pipelines"
-msgstr "一般æµæ°´çº¿"
+msgstr "æµæ°´çº¿é€šç”¨è®¾ç½®"
msgid "Generate a default set of labels"
msgstr "生æˆä¸€ç»„默认的标记"
msgid "Generate key"
+msgstr "生æˆå¯†é’¥"
+
+msgid "Generate new export"
msgstr ""
msgid "Geo"
@@ -4263,9 +4969,6 @@ msgstr "æ•°æ®ä»Ž %{timeago} 起过期"
msgid "GeoNodes|Data replication lag"
msgstr "æ•°æ®åº“åŒæ­¥æ»žåŽ"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "ç¦ç”¨èŠ‚点会åœæ­¢åŒæ­¥ã€‚确定继续?"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "与主存储é…ç½®ä¸ä¸€è‡´"
@@ -4284,6 +4987,9 @@ msgstr "GitLab版本与主节点版本ä¸ä¸€è‡´"
msgid "GeoNodes|Health status"
msgstr "è¿è¡ŒçŠ¶å†µçŠ¶æ€"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "游标处ç†çš„最åŽäº‹ä»¶ID"
@@ -4329,11 +5035,14 @@ msgstr "未校验"
msgid "GeoNodes|Out of sync"
msgstr "ä¸åŒæ­¥"
-msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgid "GeoNodes|Pausing replication stops the sync process."
msgstr ""
+msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
+msgstr "删除主节点会åœæ­¢æ‰€æœ‰èŠ‚点的åŒæ­¥è¿›ç¨‹ã€‚如果ä¸ä¸¢å¤±æ‰€æœ‰è¾…助节点上的æŸäº›æ•°æ®ï¼Œåˆ™æ— æ³•æ¢å¤åŒæ­¥ã€‚在这ç§æƒ…况下,我们建议é‡æ–°è®¾ç½®æ‰€æœ‰èŠ‚点。你确定å—?"
+
msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
-msgstr ""
+msgstr "删除辅助节点会åœæ­¢åŒæ­¥è¿›ç¨‹ã€‚ç›®å‰æ— æ³•åœ¨ä¸ä¸¢å¤±æŸäº›æ•°æ®çš„情况下添加相åŒçš„节点。我们åªå»ºè®®åœ¨è¿™ç§æƒ…况下设置一个新的辅助节点。你确定å—?"
msgid "GeoNodes|Replication slot WAL"
msgstr "å¤åˆ¶æ§½ WAL"
@@ -4408,7 +5117,7 @@ msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
msgstr "已与主节点上对应项验è¯çš„Wiki"
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
-msgstr ""
+msgstr "使用 %{geo} ,您å¯ä»¥åœ¨ä»»ä½•åœ°æ–¹å®‰è£…特殊的åªè¯»å’Œå¤åˆ¶å®žä¾‹ã€‚在添加节点之å‰ï¼Œè¯·æŒ‰ç…§ %{instructions} 出现的确切顺åºæ‰§è¡Œ 。"
msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
msgstr "当å‰Geo节点é…置使用ä¸å®‰å…¨çš„HTTP连接, 建议使用HTTPS。"
@@ -4437,9 +5146,24 @@ msgstr "正在计划所有项目é‡æ–°åŒæ­¥"
msgid "Geo|Batch operations"
msgstr "批é‡æ“作"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr "选择è¦ä¸Žæ­¤è¾…助节点åŒæ­¥çš„群组。"
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr "控制此辅助节点的 LFS/附件 回写的最大并å‘"
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr "控制此 Geo 节点的验è¯æ“作的最大并å‘"
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr "控制此主节点é‡æ–°éªŒè¯å­˜å‚¨åº“的最å°é—´éš”天数"
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "无法删除现有项目的跟踪æ¡ç›®ã€‚"
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "失败"
@@ -4455,9 +5179,12 @@ msgstr "需åŒæ­¥çš„群组"
msgid "Geo|In sync"
msgstr "å·²åŒæ­¥"
-msgid "Geo|Last repository check run"
+msgid "Geo|Internal URL"
msgstr ""
+msgid "Geo|Last repository check run"
+msgstr "上次存储库的è¿è¡Œæ£€æŸ¥"
+
msgid "Geo|Last successful sync"
msgstr "最近一次æˆåŠŸçš„åŒæ­¥"
@@ -4485,6 +5212,12 @@ msgstr "å¾…åŒæ­¥"
msgid "Geo|Pending verification"
msgstr "待验è¯"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "项目(ID: %{project_id})å·²ä¸å†å­˜åœ¨äºŽä¸»èŠ‚点。 当å‰é¡¹å¯ä»¥è¢«å®‰å…¨çš„删除,因为这并ä¸ä¼šåˆ é™¤ä»»ä½•ç£ç›˜ä¸Šçš„有用数æ®ã€‚"
@@ -4495,7 +5228,7 @@ msgid "Geo|Projects in certain storage shards"
msgstr "特定存储片中的项目"
msgid "Geo|Re-verification interval"
-msgstr ""
+msgstr "é‡æ–°éªŒè¯é—´éš”"
msgid "Geo|Recheck"
msgstr "é‡æ–°æ£€æŸ¥"
@@ -4524,24 +5257,45 @@ msgstr "é‡è¯•è®¡æ•°"
msgid "Geo|Select groups to replicate."
msgstr "选择è¦å¤åˆ¶çš„群组。"
+msgid "Geo|Selective synchronization"
+msgstr "选择性åŒæ­¥"
+
msgid "Geo|Shards to synchronize"
msgstr "需åŒæ­¥çš„存储片"
msgid "Geo|Status"
msgstr "状æ€"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "åŒæ­¥"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "åŒæ­¥å¤±è´¥ - %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr "这是主节点"
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "å·²æˆåŠŸåˆ é™¤é¡¹ç›® (%{project_id}) 的跟踪项。"
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "跟踪项将被删除。 确定继续å—?"
+msgid "Geo|URL"
+msgstr "URL"
+
msgid "Geo|Unknown state"
msgstr "未知状æ€"
@@ -4560,9 +5314,6 @@ msgstr "当å‰æ­£åœ¨è®¿é—®Geoæ¬¡è¦ <b>åªè¯» </b>节点。如需进行任何写
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "当å‰æ­£åœ¨è®¿é—®Geoæ¬¡è¦ <b>åªè¯» </b>节点。您å¯ä»¥åœ¨æ­¤é¡µé¢ä¸Šè¿›è¡Œæœ‰é™çš„更改或执行有é™çš„æ“作。"
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "您需è¦ä¸åŒçš„许å¯è¯æ‰èƒ½ä½¿ç”¨ geo å¤åˆ¶"
-
msgid "Geo|misconfigured"
msgstr "é…置错误"
@@ -4576,17 +5327,20 @@ msgid "Get a free instance review"
msgstr "获得å…费的实例评估"
msgid "Get started with error tracking"
-msgstr ""
+msgstr "开始使用错误跟踪"
msgid "Getting started with releases"
-msgstr ""
+msgstr "开始使用版本"
msgid "Git"
msgstr "Git"
-msgid "Git global setup"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
+msgid "Git global setup"
+msgstr "Git 全局设置"
+
msgid "Git repository URL"
msgstr "Git仓库URL"
@@ -4605,6 +5359,9 @@ msgstr "GitHub导入"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter已被转移"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4615,17 +5372,23 @@ msgid "GitLab Import"
msgstr "GitLab导入"
msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
-msgstr ""
+msgstr "GitLab 共享è¿è¡Œå™¨å°†åœ¨åŒä¸€ä¸ªè¿è¡Œå™¨ä¸Šæ‰§è¡Œä¸åŒé¡¹ç›®çš„代ç ï¼Œé™¤éžæ‚¨åœ¨GitLab上设置 MaxBuilds为1å’Œ GitLab è¿è¡Œå™¨è‡ªåŠ¨ç¼©æ”¾ã€‚"
msgid "GitLab User"
msgstr "GitLab用户"
-msgid "GitLab metadata URL"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
msgstr ""
+msgid "GitLab metadata URL"
+msgstr "GitLab å…ƒæ•°æ® URL"
+
msgid "GitLab project export"
msgstr "GitLab项目导出"
+msgid "GitLab restart is required to apply changes"
+msgstr "需è¦é‡å¯ GitLab æ¥åº”用更改"
+
msgid "GitLab single sign on URL"
msgstr "GitLab SSO 地å€"
@@ -4654,20 +5417,29 @@ msgid "Gitea Import"
msgstr "从Gitea导入"
msgid "Given access %{time_ago}"
-msgstr ""
+msgstr "%{time_ago} 之å‰æŽˆæƒè®¿é—®"
msgid "Go Back"
msgstr "返回"
+msgid "Go Micro is a framework for micro service development."
+msgstr "Go Micro是一个微æœåŠ¡å¼€å‘的框架。"
+
msgid "Go back"
msgstr "返回"
+msgid "Go full screen"
+msgstr "å…¨å±æ¨¡å¼"
+
msgid "Go to"
msgstr "转到"
msgid "Go to %{link_to_google_takeout}."
msgstr "转至 %{link_to_google_takeout}。"
+msgid "Go to project"
+msgstr "转到项目"
+
msgid "Google Code import"
msgstr "从Google Code导入"
@@ -4681,14 +5453,23 @@ msgid "Got it!"
msgstr "了解ï¼"
msgid "Grant access"
-msgstr ""
+msgstr "å…许访问"
msgid "Graph"
msgstr "分支图"
+msgid "Gravatar enabled"
+msgstr "å¯ç”¨ Gravatar"
+
msgid "Group"
msgstr "群组"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "群组 CI/CD 设置"
@@ -4698,6 +5479,9 @@ msgstr "群组Git LFS状æ€ï¼š"
msgid "Group ID"
msgstr "群组 ID"
+msgid "Group ID: %{group_id}"
+msgstr "群组 ID:%{group_id}"
+
msgid "Group Runners"
msgstr "群组Runner"
@@ -4729,10 +5513,13 @@ msgid "Group name"
msgstr "群组å称"
msgid "Group overview content"
+msgstr "群组概述内容"
+
+msgid "Group was successfully updated."
msgstr ""
msgid "Group:"
-msgstr ""
+msgstr "群组:"
msgid "Group: %{group_name}"
msgstr "群组:%{group_name}"
@@ -4741,43 +5528,124 @@ msgid "GroupRoadmap|From %{dateWord}"
msgstr "从 %{dateWord} 起"
msgid "GroupRoadmap|Something went wrong while fetching epics"
-msgstr "读å–å²è¯—故事时出错"
+msgstr "è¯»å– epic 时出错"
msgid "GroupRoadmap|Sorry, no epics matched your search"
-msgstr "对ä¸èµ·ï¼Œæœªæœç´¢åˆ°ä»»ä½•ç¬¦åˆæ¡ä»¶çš„å²è¯—故事"
+msgstr "对ä¸èµ·ï¼Œæœªæœç´¢åˆ°ä»»ä½•ç¬¦åˆæ¡ä»¶çš„ epic"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
-msgstr "路线图显示了å²è¯—故事沿ç€æ—¶é—´çº¿çš„进展情况"
+msgstr "路线图显示了epic 沿ç€æ—¶é—´çº¿çš„进展情况"
-msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
+msgstr "è¦æŸ¥çœ‹è·¯çº¿å›¾ï¼Œè¯·åœ¨æ­¤ç¾¤ç»„或其å­ç¾¤ç»„中的一个 epic 中添加开始日期或截止日期;从 %{startDate} 到 %{endDate}。"
+
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
-msgstr ""
+msgstr "è¦æ‰©å¤§æ‚¨çš„æœç´¢ï¼Œè¯·æ›´æ”¹æˆ–移除过滤器,从 %{startDate} 到 %{endDate}。"
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "直到 %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr "已为群组更新 Auto DevOps æµæ°´çº¿"
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr "Auto DevOps 将根æ®é¢„定义的æŒç»­é›†æˆå’Œäº¤ä»˜é…置自动构建ã€æµ‹è¯•å’Œéƒ¨ç½²æ‚¨çš„应用程åºã€‚ %{auto_devops_start}了解有关 Auto DevOps%{auto_devops_end} 的更多信æ¯"
+
msgid "GroupSettings|Badges"
msgstr "徽章"
msgid "GroupSettings|Custom project templates"
-msgstr ""
+msgstr "自定义项目模æ¿"
msgid "GroupSettings|Customize your group badges."
msgstr "自定义群组徽章。"
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr "默认为此群组中所有项目的 Auto DevOps æµæ°´çº¿"
+
msgid "GroupSettings|Learn more about badges."
msgstr "了解有关徽章的更多信æ¯ã€‚"
msgid "GroupSettings|Learn more about group-level project templates."
+msgstr "了解更多关于群组级项目模æ¿"
+
+msgid "GroupSettings|New runners registration token has been generated!"
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "ç¦æ­¢ä¸Žå…¶ä»–群组共享 %{group} 中的项目"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
-msgstr ""
+msgstr "选择一个å­ç¾¤ç»„作为此群组的自定义项目模æ¿æºã€‚"
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr "在未找到备用 CI é…置文件时使用 Auto DevOps æµæ°´çº¿ã€‚"
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr "æ›´æ–° Auto DevOps æµæ°´çº¿æ—¶å‡ºçŽ°é—®é¢˜ï¼š %{error_messages}。"
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "此设置已ç»åº”用于 %{ancestor_group},并已覆盖此å­ç»„的设置。"
@@ -4804,7 +5672,7 @@ msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroup
msgstr "也å¯ä»¥é€šè¿‡åˆ›å»º %{subgroup_docs_link_start}å­ç¾¤ç»„æ¥åµŒå¥—群组%{subgroup_docs_link_end}。"
msgid "Groups with access to <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "å¯ä»¥è®¿é—® <strong>%{project_name}</strong>"
msgid "GroupsDropdown|Frequently visited"
msgstr "ç»å¸¸è®¿é—®çš„群组"
@@ -4872,6 +5740,9 @@ msgstr "按å称æœç´¢"
msgid "Have your users email"
msgstr "请让用户å‘é€ç”µå­é‚®ä»¶è‡³"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "页头消æ¯"
@@ -4906,11 +5777,14 @@ msgid "Here is the public SSH key that needs to be added to the remote server. F
msgstr "以下是需è¦æ·»åŠ åˆ°è¿œç¨‹æœåŠ¡å™¨çš„SSH公钥。有关更多信æ¯ï¼Œè¯·å‚阅文档。"
msgid "Hide file browser"
-msgstr ""
+msgstr "éšè—文件æµè§ˆå™¨"
msgid "Hide host keys manual input"
msgstr "éšè—主机密钥手动输入"
+msgid "Hide marketing-related entries from help"
+msgstr "从帮助中éšè—与è¥é”€æœ‰å…³çš„æ¡ç›®"
+
msgid "Hide payload"
msgstr "éšè—上传数æ®"
@@ -4919,16 +5793,31 @@ msgid_plural "Hide values"
msgstr[0] "éšè—值"
msgid "Hide values"
+msgstr "éšè—值"
+
+msgid "Highest role:"
msgstr ""
msgid "History"
msgstr "历å²"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已开始维护"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr "管家,导出,路径,转移,删除,存档。"
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
-msgstr ""
+msgstr "但是,您已ç»æ˜¯ %{member_source} çš„æˆå‘˜ã€‚请使用其他å¸æˆ·ç™»å½•ä»¥æŽ¥å—邀请。"
msgid "I accept the %{terms_link}"
msgstr "æˆ‘æŽ¥å— %{terms_link}"
@@ -4946,7 +5835,7 @@ msgid "IDE|Back"
msgstr "返回"
msgid "IDE|Client side evaluation"
-msgstr "客户端解算"
+msgstr "客户端计算"
msgid "IDE|Commit"
msgstr "æ交"
@@ -4967,7 +5856,7 @@ msgid "IDE|Open in file view"
msgstr "在文件视图中打开"
msgid "IDE|Preview your web application using Web IDE client-side evaluation."
-msgstr "使用Web IDE客户端解算æ¥é¢„览您的web应用。"
+msgstr "使用Web IDE客户端计算æ¥é¢„览您的web应用。"
msgid "IDE|Refresh preview"
msgstr "刷新预览"
@@ -4984,15 +5873,18 @@ msgstr "身份标识"
msgid "Identities"
msgstr "身份标识"
-msgid "Identity provider single sign on URL"
-msgstr "身份验è¯æ供商å•ç‚¹ç™»å½•URL"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
-msgstr ""
+msgstr "如果任何作业超过这个超时阈值,它将被标记为失败。å¯è¾“入英文语å¥ï¼Œå¦‚ “1 hourâ€ã€‚默认å•ä½ä¸ºç§’。"
+
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr "如果选中,则群组所有者å¯ä»¥ç®¡ç† LDAP 群组链接和 LDAP æˆå‘˜è¦†ç›–"
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "如果ç¦ç”¨ï¼Œåˆ™ä¸ä¼šä½¿ç”¨è¿œç¨‹å‰¯æœ¬çš„æ交自动更新分å‰çš„本地分支,以防止本地数æ®ä¸¢å¤±ã€‚如果默认分支 (%{default_branch}) 已分å‰ä¸”无法更新,则镜åƒå°†å¤±è´¥ã€‚其他分å‰çš„分支默默被忽略。"
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr "如果ç¦ç”¨ï¼Œåˆ™åªæœ‰ç®¡ç†å‘˜æ‰èƒ½åœ¨é¡¹ç›®ä¸­è®¾ç½®é•œåƒã€‚"
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "如果ç¦ç”¨ï¼Œåˆ™è®¿é—®çº§åˆ«å°†å–决于用户在项目中的æƒé™ã€‚"
@@ -5005,9 +5897,6 @@ msgstr "如果å¯ç”¨ï¼Œåˆ™ä½¿ç”¨å¤–部æœåŠ¡ä¸Šçš„分类标签æ¥éªŒè¯å¯¹é¡¹ç›®
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "如使用 GitHub,GitHubçš„æ交(commits)和拉å–请求(pull request) 页é¢å°†æ˜¾ç¤ºæµæ°´çº¿çŠ¶æ€ã€‚ %{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "如果文件已存在,å¯ä»¥ä½¿ç”¨ä¸‹é¢çš„ %{link_to_cli} 推é€å®ƒä»¬ã€‚"
-
msgid "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>."
msgstr "如果HTTP仓库ä¸å¯å…¬å¼€è®¿é—®ï¼Œè¯·å°†èº«ä»½éªŒè¯ä¿¡æ¯æ·»åŠ åˆ°URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
@@ -5021,13 +5910,13 @@ msgid "ImageDiffViewer|Swipe"
msgstr "滑动"
msgid "Impersonation has been disabled"
-msgstr ""
+msgstr "模拟已被ç¦ç”¨"
msgid "Import"
msgstr "导入"
msgid "Import CSV"
-msgstr ""
+msgstr "导入CSV"
msgid "Import Projects from Gitea"
msgstr "从Gitea导入项目"
@@ -5048,13 +5937,13 @@ msgid "Import in progress"
msgstr "正在导入"
msgid "Import issues"
-msgstr ""
+msgstr "导入议题"
msgid "Import members"
-msgstr ""
+msgstr "导入æˆå‘˜"
msgid "Import members from another project"
-msgstr ""
+msgstr "从å¦ä¸€ä¸ªé¡¹ç›®å¯¼å…¥æˆå‘˜"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "通过上传manifest文件导入多个仓库"
@@ -5063,7 +5952,7 @@ msgid "Import project"
msgstr "导入项目"
msgid "Import project members"
-msgstr ""
+msgstr "导入项目æˆå‘˜"
msgid "Import projects from Bitbucket"
msgstr "从Bitbucket导入项目"
@@ -5090,14 +5979,32 @@ msgid "Import repository"
msgstr "导入仓库"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
-msgstr ""
+msgstr "导入超时。耗时已超过 %{import_jobs_expiration} 秒"
+
+msgid "Import/Export illustration"
+msgstr "导入/导出æ’图"
msgid "ImportButtons|Connect repositories from"
msgstr "用以下方å¼è¿žæŽ¥å‚¨å­˜åº“"
+msgid "ImportProjects|Importing the project failed"
+msgstr "导入项目失败"
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr "获å–您的%{provider}存储库失败"
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr "选择è¦å¯¼å…¥çš„项目"
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr "更新已导入的项目失败"
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "使用 GitLab ä¼ä¸šç‰ˆçš„增强议题看æ¿ã€‚"
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "使用GitLab ä¼ä¸šç‰ˆè®®é¢˜æƒé‡å¸¦æ¥çš„增强议题管ç†åŠŸèƒ½ã€‚"
@@ -5108,28 +6015,37 @@ msgid "In order to enable instance-level analytics, please ask an admin to enabl
msgstr "è¦å¯ç”¨å®žä¾‹çº§åˆ†æžï¼Œè¯·è¦æ±‚管ç†å‘˜å¯ç”¨ %{usage_ping_link_start}使用情况检测(usage ping)%{usage_ping_link_end}。"
msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
-msgstr ""
+msgstr "为了收集准确的功能使用数æ®ï¼Œå¯èƒ½éœ€è¦1到2周æ‰èƒ½çœ‹åˆ°æ‚¨çš„指数。"
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "继续下一步,选择想è¦å¯¼å…¥çš„项目"
+msgid "Incidents"
+msgstr "事故"
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "包括所有用户必须接å—çš„æœåŠ¡æ¡æ¬¾å议和éšç§æ”¿ç­–。"
+msgid "Include author name in notification email body"
+msgstr "在通知电å­é‚®ä»¶æ­£æ–‡ä¸­åŒ…å«ä½œè€…姓å"
+
msgid "Include merge request description"
-msgstr ""
+msgstr "包å«åˆå¹¶è¯·æ±‚æè¿°"
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "如果需è¦ï¼Œè¯·åœ¨URL中包å«ç”¨æˆ·å: <code>https://username@gitlab.company.com/group/project.git</code>。"
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr "åŒ…å« LFS 对象。它å¯ä»¥æŒ‰ç¾¤ç»„或项目覆盖。 0 表示无é™åˆ¶ã€‚"
+
msgid "Includes an MVC structure to help you get started."
-msgstr ""
+msgstr "包å«ä¸€ä¸ª MVC 结构æ¥å¸®åŠ©æ‚¨å¼€å§‹ã€‚"
msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
-msgstr ""
+msgstr "包括一个 MVC 结构,Gemfile,Rakefile,以åŠå…¶ä»–许多æ¥å¸®åŠ©æ‚¨å¼€å§‹ã€‚"
msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
-msgstr ""
+msgstr "包括一个MVC 结构,mvw å’Œ pom.xml æ¥å¸®åŠ©æ‚¨å¼€å§‹ã€‚"
msgid "Incompatible Project"
msgstr "ä¸å…¼å®¹çš„项目"
@@ -5137,6 +6053,9 @@ msgstr "ä¸å…¼å®¹çš„项目"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "指示此runner是å¦å¯ä»¥é€‰æ‹©æ— æ ‡è®°çš„作业"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr "通知用户没有上传 SSH 密钥,如果没有 SSH 秘钥,将无法通过 SSH 推é€ã€‚"
+
msgid "Inline"
msgstr "内è”"
@@ -5146,8 +6065,17 @@ msgstr "手动输入主机密钥"
msgid "Input your repository URL"
msgstr "输入您的存储库URL"
+msgid "Insert a quote"
+msgstr "æ’入引用"
+
+msgid "Insert code"
+msgstr "æ’入代ç "
+
msgid "Insert suggestion"
-msgstr ""
+msgstr "æ’入建议"
+
+msgid "Insights"
+msgstr "洞察"
msgid "Install GitLab Runner"
msgstr "安装GitLab Runner"
@@ -5168,6 +6096,9 @@ msgstr "实例统计信æ¯å¯è§æ€§"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "实例ä¸æ”¯æŒå¤šä¸ªKubernetes集群"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "集æˆ"
@@ -5178,7 +6109,7 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr "相关人员甚至å¯ä»¥é€šè¿‡æŽ¨é€æ交æ¥ä¸ºé¡¹ç›®ä½œå‡ºè´¡çŒ®ã€‚"
msgid "Internal"
-msgstr ""
+msgstr "内部"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "内部 - 任何登录的用户都å¯ä»¥æŸ¥çœ‹è¯¥ç¾¤ç»„和任何内部项目。"
@@ -5196,25 +6127,43 @@ msgid "Introducing Cycle Analytics"
msgstr "周期分æžç®€ä»‹"
msgid "Introducing Your Conversational Development Index"
+msgstr "介ç»æ‚¨çš„会è¯å‘展指数"
+
+msgid "Invalid Login or password"
msgstr ""
-msgid "Invitation"
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr "输入无效,请é¿å…使用emoji符å·"
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
msgstr ""
+msgid "Invitation"
+msgstr "邀请"
+
msgid "Invite"
msgstr "邀请"
msgid "Invite group"
-msgstr ""
+msgstr "邀请群组"
msgid "Invite member"
-msgstr ""
+msgstr "邀请æˆå‘˜"
msgid "Invoke Count"
-msgstr ""
+msgstr "调用计数"
msgid "Invoke Time"
-msgstr ""
+msgstr "调用时间"
+
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr "已关闭(%{moved_link_start}已移动%{moved_link_end})"
msgid "Issue"
msgstr "议题"
@@ -5228,6 +6177,9 @@ msgstr "议题看æ¿èšç„¦æ¨¡å¼"
msgid "Issue events"
msgstr "议题事件"
+msgid "Issue template (optional)"
+msgstr "è®®é¢˜æ¨¡æ¿ (å¯é€‰)"
+
msgid "IssueBoards|Board"
msgstr "看æ¿"
@@ -5235,19 +6187,19 @@ msgid "IssueBoards|Boards"
msgstr "看æ¿"
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "创建新看æ¿"
msgid "IssueBoards|Delete board"
-msgstr ""
+msgstr "删除看æ¿"
msgid "IssueBoards|No matching boards found"
-msgstr ""
+msgstr "未找到匹é…的看æ¿"
msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
-msgstr ""
+msgstr "您的一些看æ¿è¢«éšè—,请激活许å¯è¯åŽå†æ¬¡æŸ¥çœ‹ã€‚"
msgid "IssueBoards|Switch board"
-msgstr ""
+msgstr "切æ¢çœ‹æ¿"
msgid "Issues"
msgstr "议题"
@@ -5258,8 +6210,11 @@ msgstr "议题å¯ä»¥æ˜¯ç¼ºé™·ï¼Œä»»åŠ¡æˆ–è¦è®¨è®ºçš„想法。此外,å¯ä»¥é€š
msgid "Issues closed"
msgstr "关闭议题"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "议题,åˆå¹¶è¯·æ±‚,推é€å’Œè¯„论。"
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr "议题,åˆå¹¶è¯·æ±‚,推é€åŠè¯„论。"
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr "在您为项目创建议题åŽï¼Œæˆ‘们就会开始跟踪并显示它们的指标"
@@ -5283,10 +6238,10 @@ msgid "IssuesAnalytics|To widen your search, change or remove filters in the fil
msgstr "è¦æ‰©å¤§æœç´¢èŒƒå›´ï¼Œè¯·æ›´æ”¹æˆ–删除上é¢çš„过滤æ¡ä»¶"
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
-msgstr ""
+msgstr "它必须有标题行和至少有两列:第一æ æ˜¯è®®é¢˜æ ‡é¢˜ï¼Œç¬¬äºŒæ æ˜¯è®®é¢˜æ述。自动检测分隔符。"
msgid "It's you"
-msgstr ""
+msgstr "这是您"
msgid "Jaeger URL"
msgstr "Jaeger 地å€"
@@ -5303,14 +6258,32 @@ msgstr "1月"
msgid "Job"
msgstr "作业"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr "作业 ID"
+
msgid "Job has been erased"
msgstr "作业已被删除"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
+msgstr "作业å¡ä½äº†ã€‚请检查è¿è¡Œå™¨ã€‚"
+
+msgid "Job traces and artifacts"
msgstr ""
msgid "Job was retried"
-msgstr ""
+msgstr "作业已é‡è¯•"
msgid "Jobs"
msgstr "作业"
@@ -5339,6 +6312,9 @@ msgstr "作业已被删除"
msgid "Job|Keep"
msgstr "ä¿æŒ"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "滚动到底部"
@@ -5352,9 +6328,21 @@ msgid "Job|The artifacts were removed"
msgstr "作业产物已被删除"
msgid "Job|The artifacts will be removed"
+msgstr "工件将被删除"
+
+msgid "Job|This job failed because the necessary resources were not successfully created."
msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
+msgstr "此作业å¡ä½äº†ï¼Œå› ä¸ºè¯¥é¡¹ç›®æ²¡æœ‰åœ¨çº¿åˆ†é…任何è¿è¡Œå™¨ã€‚"
+
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
msgstr ""
msgid "Jul"
@@ -5370,7 +6358,7 @@ msgid "June"
msgstr "6月"
msgid "Key (PEM)"
-msgstr ""
+msgstr "秘钥 (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5379,7 +6367,7 @@ msgid "Kubernetes Cluster"
msgstr "Kubernetes集群"
msgid "Kubernetes Clusters"
-msgstr ""
+msgstr "Kubernetes集群"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "Kubernetes集群创建时间超过超时; %{timeout}"
@@ -5399,9 +6387,15 @@ msgstr "Kuberneteså·²é…ç½®"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "KubernetesæœåŠ¡é›†æˆå³å°†è¢«åœç”¨ã€‚ 请使用新的 <a href=\"%{url}\"/>Kubernetes集群</a> 页é¢%{deprecated_message_content} Kubernetes集群"
+msgid "LDAP settings"
+msgstr "LDAP 设置"
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -5417,6 +6411,15 @@ msgstr "标记æ“作下拉èœå•"
msgid "Label lists show all issues with the selected label."
msgstr "标记列表显示具有所选标记的所有议题。"
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} 更多"
@@ -5447,6 +6450,9 @@ msgstr "å‡çº§æ ‡è®°"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "æå‡ %{labelTitle} 将使其å¯ç”¨äºŽ %{groupName} 内的所有项目。现有的åŒå项目标记将被åˆå¹¶ã€‚该æ“作ä¸å¯æ’¤é”€ã€‚"
+msgid "Language"
+msgstr "语言"
+
msgid "Large File Storage"
msgstr "大文件存储"
@@ -5458,7 +6464,7 @@ msgid "Last Pipeline"
msgstr "最新æµæ°´çº¿"
msgid "Last activity"
-msgstr ""
+msgstr "上次活动"
msgid "Last commit"
msgstr "最åŽæ交"
@@ -5476,7 +6482,7 @@ msgid "Last reply by"
msgstr "最åŽå›žå¤æ¥è‡ªäºŽ"
msgid "Last seen"
-msgstr ""
+msgstr "上次查看"
msgid "Last update"
msgstr "最åŽæ›´æ–°"
@@ -5494,13 +6500,13 @@ msgid "Latest changes"
msgstr "最新更改"
msgid "Latest pipeline for this branch"
-msgstr ""
+msgstr "该分支的最新æµæ°´çº¿"
msgid "Lead"
-msgstr ""
+msgstr "超å‰"
msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
-msgstr ""
+msgstr "了解GitLab如何 %{no_packages_link_start}å‘布和共享您的包%{no_packages_link_end}。"
msgid "Learn more"
msgstr "进一步了解"
@@ -5509,25 +6515,25 @@ msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple
msgstr "为了了解更多关于 %{issue_boards_url},在多个列表中ä¿æŒå¯¹è®®é¢˜çš„追踪,您å¯ä»¥ä½¿ç”¨ä½¿ç”¨æ ‡è®°ï¼ŒæŒ‡æ´¾äººï¼Œå’Œé‡Œç¨‹ç¢‘。 如果你å‘现议题看æ¿ä¸Šä¸¢å¤±äº†ä¸€äº›ä¿¡æ¯ï¼Œè¯·åœ¨ %{gitlab_issues_url} 创建一个议题。"
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "了解更多关于Auto DevOps"
msgid "Learn more about Kubernetes"
msgstr "进一步了解关于Kubernetesçš„ä¿¡æ¯"
msgid "Learn more about Web Terminal"
-msgstr ""
+msgstr "了解更多关于 Web 终端"
+
+msgid "Learn more about approvals."
+msgstr "了解有关核准的更多信æ¯ã€‚"
msgid "Learn more about custom project templates"
-msgstr ""
+msgstr "了解更多关于自定义项目模æ¿"
msgid "Learn more about group-level project templates"
-msgstr ""
-
-msgid "Learn more about incoming email addresses"
-msgstr ""
+msgstr "了解更多关于群组级项目模æ¿"
-msgid "Learn more about protected branches"
-msgstr "进一步了解ä¿æŠ¤åˆ†æ”¯"
+msgid "Learn more about signing commits"
+msgstr "了解更多有关签åæ交的详细信æ¯"
msgid "Learn more in the"
msgstr "了解更多"
@@ -5538,6 +6544,9 @@ msgstr "æµæ°´çº¿è®¡åˆ’文档"
msgid "Leave"
msgstr "离开"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "退出群组"
@@ -5622,9 +6631,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "您å³å°†ä»Žé¡¹ç›® %{name} 中删除许å¯è¯ã€‚"
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "许å¯è¯"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "最多显示 %d 个事件"
@@ -5641,6 +6656,9 @@ msgstr "列出Gitea存储库"
msgid "List available repositories"
msgstr "列出å¯ç”¨å­˜å‚¨åº“"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "列表视图"
@@ -5663,10 +6681,10 @@ msgid "Loading..."
msgstr "正在加载..."
msgid "Loading…"
-msgstr ""
+msgstr "正在加载..."
msgid "Localization"
-msgstr ""
+msgstr "本土化"
msgid "Lock"
msgstr "é”定"
@@ -5698,15 +6716,36 @@ msgstr "加é”å¯ä»¥é”定特定的文件或文件夹。"
msgid "Login with smartcard"
msgstr "使用智能å¡ç™»å½•"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "日志"
+msgid "MRApprovals|Approved by"
+msgstr "核准人是"
+
+msgid "MRApprovals|Approvers"
+msgstr "核准人"
+
+msgid "MRApprovals|Pending approvals"
+msgstr "等待核准"
+
+msgid "MRDiff|Show changes only"
+msgstr "åªæŸ¥çœ‹å˜æ›´å†…容"
+
+msgid "MRDiff|Show full file"
+msgstr "显示全部文件"
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "GitLab Geo å¯ä»¥åˆ›å»º GitLab 实例的åªè¯»é•œåƒ, 使得从远端克隆和拉å–大型代ç ä»“库的时间大大缩短,从而æ高团队æˆå‘˜çš„工作效率。"
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "ç¡®ä¿æ‚¨å·²ç™»å½•åˆ°æ¬²å¯¼å…¥é¡¹ç›®æ‰€æœ‰è€…çš„å¸æˆ·ã€‚"
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr "通过细粒度的访问控制æ¥ç®¡ç†Git存储库,确ä¿æ‚¨çš„代ç å®‰å…¨ã€‚执行代ç å®¡æŸ¥å¹¶é€šè¿‡åˆå¹¶è¯·æ±‚的实现更紧密的开å‘å作。æ¯ä¸ªé¡¹ç›®è¿˜å¯ä»¥é…置议题跟踪和wiki。"
@@ -5735,10 +6774,7 @@ msgid "Manage project labels"
msgstr "管ç†é¡¹ç›®æ ‡è®°"
msgid "Manage two-factor authentication"
-msgstr ""
-
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "通过SAML管ç†ç¾¤ç»„æˆå‘˜ï¼Œè¿›ä¸€æ­¥æ高安全性。"
+msgstr "管ç†åŒå› ç´ èº«ä»½éªŒè¯"
msgid "Manifest"
msgstr "Manifest"
@@ -5746,6 +6782,9 @@ msgstr "Manifest"
msgid "Manifest file import"
msgstr "Manifest文件导入"
+msgid "Manual job"
+msgstr "手动作业"
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "å°†FogBugzå¸æˆ·ID映射为GitLab用户"
@@ -5768,49 +6807,46 @@ msgid "Mark todo as done"
msgstr "标记为已完æˆ"
msgid "Markdown"
+msgstr "Markdown"
+
+msgid "Markdown Help"
msgstr ""
msgid "Markdown enabled"
msgstr "支æŒMarkdownæ ¼å¼"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "添加一个符å·åˆ—表"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "添加一个链接"
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "添加一个编å·åˆ—表"
+msgid "Maven Metadata"
+msgstr "Maven 元数æ®"
-msgid "MarkdownToolbar|Add a table"
-msgstr "添加一个表格"
+msgid "Max access level"
+msgstr "最高访问级别"
-msgid "MarkdownToolbar|Add a task list"
-msgstr "添加一个任务列表"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr "添加粗体文本"
+msgid "Maximum artifacts size (MB)"
+msgstr "最大工件大å°ï¼ˆMB)"
-msgid "MarkdownToolbar|Add italic text"
-msgstr "添加斜体文本"
+msgid "Maximum attachment size (MB)"
+msgstr "æœ€å¤§é™„ä»¶å¤§å° (MB)"
-msgid "MarkdownToolbar|Go full screen"
-msgstr "å…¨å±æ˜¾ç¤º"
+msgid "Maximum capacity"
+msgstr "最大容é‡"
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "æ’入一个引用"
+msgid "Maximum delay (Minutes)"
+msgstr "最大延迟 (分钟)"
-msgid "MarkdownToolbar|Insert code"
-msgstr "æ’入代ç "
+msgid "Maximum job timeout"
+msgstr "最大作业超时"
-msgid "Maven Metadata"
-msgstr "Maven 元数æ®"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr "å¯ä»¥åŒæ—¶åŒæ­¥çš„最大镜åƒæ•°ã€‚"
-msgid "Max access level"
-msgstr "最高访问级别"
+msgid "Maximum push size (MB)"
+msgstr "最大推é€å¤§å° (MB)"
-msgid "Maximum job timeout"
-msgstr "最大作业超时"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr "åŒæ­¥é•œåƒè®¡åˆ’的最大时间间隔。"
msgid "May"
msgstr "5月"
@@ -5828,41 +6864,56 @@ msgid "Members"
msgstr "æˆå‘˜"
msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
-msgstr ""
+msgstr "项目 <i>维护者</i> 或 <i>所有者</i>å¯ä»¥æ·»åŠ æˆå‘˜"
msgid "Members of <strong>%{project_name}</strong>"
-msgstr ""
-
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "群组在登录您的群组时会跳转到此处。请从您的身份认è¯æ供商处获得该信æ¯ã€‚它å¯èƒ½å«åšâ€œSSOæœåŠ¡ä½ç½®ï¼ˆSSO Service Location)â€ï¼Œâ€œSAML令牌é¢å‘点(SAML Token Issuance Endpoint)â€æˆ–“SAML 2.0/W-Federation URLâ€ã€‚"
+msgstr "<strong>%{project_name}</strong>çš„æˆå‘˜"
msgid "Merge Request"
msgstr "åˆå¹¶è¯·æ±‚"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "åˆå¹¶è¯·æ±‚"
msgid "Merge Requests created"
msgstr "创建åˆå¹¶è¯·æ±‚"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
+msgstr "åˆå¹¶æ交消æ¯"
+
+msgid "Merge commit with semi-linear history"
msgstr ""
msgid "Merge events"
msgstr "åˆå¹¶äº‹ä»¶"
msgid "Merge immediately"
-msgstr ""
+msgstr "ç«‹å³åˆå¹¶"
msgid "Merge in progress"
+msgstr "正在åˆå¹¶"
+
+msgid "Merge method"
msgstr ""
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr "åˆå¹¶æµæ°´çº¿å°†å°è¯•åœ¨åˆå¹¶æ—¶éªŒè¯åˆå¹¶åŽçš„结果"
+
msgid "Merge request"
msgstr "åˆå¹¶è¯·æ±‚"
msgid "Merge request approvals"
msgstr "åˆå¹¶è¯·æ±‚批准"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "åˆå¹¶è¯·æ±‚"
@@ -5870,31 +6921,31 @@ msgid "Merge requests are a place to propose changes you've made to a project an
msgstr "åˆå¹¶è¯·æ±‚用于æ出对项目的更改与他人讨论"
msgid "Merge when pipeline succeeds"
-msgstr ""
+msgstr "当æµæ°´çº¿æˆåŠŸæ—¶åˆå¹¶"
msgid "MergeRequests|Add a reply"
-msgstr ""
+msgstr "添加回å¤"
msgid "MergeRequests|An error occurred while saving the draft comment."
msgstr "ä¿å­˜è¯„论è‰ç¨¿æ—¶å‘生错误。"
msgid "MergeRequests|Discussion stays resolved"
-msgstr ""
+msgstr "讨论并解决"
msgid "MergeRequests|Discussion stays unresolved"
-msgstr ""
+msgstr "讨论并未解决"
msgid "MergeRequests|Discussion will be resolved"
-msgstr ""
+msgstr "讨论并解决"
msgid "MergeRequests|Discussion will be unresolved"
-msgstr ""
+msgstr "讨论并未解决"
msgid "MergeRequests|Jump to next unresolved discussion"
-msgstr ""
+msgstr "跳转到下一个未解决的讨论"
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "回å¤..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr "在新议题中解决此讨论"
@@ -5912,38 +6963,41 @@ msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr "查看已替æ¢æ–‡ä»¶ @ %{commitId}"
msgid "MergeRequests|commented on commit %{commitLink}"
-msgstr ""
+msgstr "讨论%{commitLink}æ交"
msgid "MergeRequests|started a discussion"
-msgstr ""
+msgstr "开始讨论"
msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
-msgstr ""
+msgstr "开始讨论 %{linkStart}旧版本的差异%{linkEnd}"
msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
-msgstr ""
+msgstr "开始讨论 %{linkStart}差异%{linkEnd}"
msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "开始讨论一个过时的å˜æ›´æ交%{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "开始讨论æ交%{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart}%{timeDifferenceMinutes}%{descriptionChangedTimes}次更改了æè¿°%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr "加载完整差异时出错。请å†è¯•ä¸€æ¬¡ã€‚"
+
msgid "MergeRequest|Filter files"
msgstr "过滤文件"
msgid "MergeRequest|No files found"
msgstr "未找到任何文件"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "å·²åˆå¹¶"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "消æ¯"
@@ -5960,7 +7014,7 @@ msgid "Metrics and profiling"
msgstr "指标和分æž"
msgid "Metrics for environment"
-msgstr ""
+msgstr "环境指标"
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr "查看有关部署到环境的CI/CD文档"
@@ -5969,10 +7023,10 @@ msgid "Metrics|Create metric"
msgstr "创建指标"
msgid "Metrics|Delete metric"
-msgstr ""
+msgstr "删除指标"
msgid "Metrics|Delete metric?"
-msgstr ""
+msgstr "删除指标?"
msgid "Metrics|Edit metric"
msgstr "编辑指标"
@@ -5984,7 +7038,7 @@ msgid "Metrics|For grouping similar metrics"
msgstr "用于分组类似指标"
msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
-msgstr ""
+msgstr "Y轴是标签(通常是å•ä½)。X轴总是代表时间。"
msgid "Metrics|Learn about environments"
msgstr "了解环境"
@@ -6001,14 +7055,17 @@ msgstr "创建指标"
msgid "Metrics|No deployed environments"
msgstr "应用未部署到任何环境"
-msgid "Metrics|PromQL query is valid"
+msgid "Metrics|Not enough data to display"
msgstr ""
+msgid "Metrics|PromQL query is valid"
+msgstr "PromotQL 查询有效"
+
msgid "Metrics|Prometheus Query Documentation"
msgstr "Prometheus查询文档"
-msgid "Metrics|System"
-msgstr "系统"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "获å–环境数æ®æ—¶å‡ºé”™ï¼Œè¯·é‡è¯•"
@@ -6020,7 +7077,7 @@ msgid "Metrics|There was an error getting environments information."
msgstr "获å–环境信æ¯æ—¶å‡ºé”™ã€‚"
msgid "Metrics|There was an error trying to validate your query"
-msgstr ""
+msgstr "å°è¯•éªŒè¯æ‚¨çš„查询时出错"
msgid "Metrics|There was an error while retrieving metrics"
msgstr "读å–指标时出错"
@@ -6044,11 +7101,17 @@ msgid "Metrics|Y-axis label"
msgstr "Y轴标签"
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
-msgstr ""
+msgstr "您å³å°†æ°¸ä¹…删除此指标且无法撤消。"
msgid "Metrics|e.g. Throughput"
msgstr "例如:åžåé‡"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "里程碑"
@@ -6094,6 +7157,9 @@ msgstr "æå‡ %{milestone} 将使其å¯ç”¨äºŽ %{groupName} 内的所有项目ã€
msgid "Milestones|This action cannot be reversed."
msgstr "该æ“作无法撤销。"
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr "在我们预先安排更多镜åƒä¹‹å‰å¯ç”¨çš„最å°å®¹é‡ã€‚"
+
msgid "Mirror a repository"
msgstr "é•œåƒå­˜å‚¨åº“"
@@ -6112,6 +7178,9 @@ msgstr "é•œåƒçš„存储库"
msgid "Mirroring repositories"
msgstr "é•œåƒå­˜å‚¨åº“"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "新建 SSH 公钥"
@@ -6121,17 +7190,20 @@ msgstr "å–消"
msgid "Modal|Close"
msgstr "关闭"
+msgid "Modify commit message"
+msgstr "修改æ交信æ¯"
+
msgid "Modify commit messages"
-msgstr ""
+msgstr "修改æ交消æ¯"
msgid "Modify merge commit"
-msgstr ""
+msgstr "修改åˆå¹¶æ交"
msgid "Monday"
-msgstr ""
+msgstr "星期一"
msgid "Monitor your errors by integrating with Sentry"
-msgstr ""
+msgstr "通过与Sentry集æˆæ¥ç›‘控您的错误"
msgid "Monitoring"
msgstr "监控"
@@ -6154,9 +7226,15 @@ msgstr "更多信æ¯"
msgid "More information is available|here"
msgstr "帮助文档"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr "è¶…å‰ %{number_commits_distance} 个æ交与 %{default_branch} ä¸åŒ"
+
msgid "Most stars"
msgstr "最多星标"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "移动"
@@ -6166,6 +7244,12 @@ msgstr "移动议题"
msgid "Multiple issue boards"
msgstr "多é‡è®®é¢˜çœ‹æ¿"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "å称"
@@ -6178,8 +7262,14 @@ msgstr "通过标题命å您的个人密钥"
msgid "Name:"
msgstr "å称:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
-msgstr "å称,å¯è§æ€§"
+msgstr "å称与å¯è§æ€§"
msgid "Nav|Help"
msgstr "帮助"
@@ -6194,7 +7284,7 @@ msgid "Nav|Sign out and sign in with a different account"
msgstr "退出并登录到其他账å·"
msgid "Need help?"
-msgstr ""
+msgstr "需è¦å¸®åŠ©å—?"
msgid "Network"
msgstr "网络"
@@ -6209,7 +7299,7 @@ msgid "New Application"
msgstr "新建应用"
msgid "New Environment"
-msgstr ""
+msgstr "新环境"
msgid "New Group"
msgstr "新建群组"
@@ -6225,10 +7315,13 @@ msgid "New Label"
msgstr "新标签"
msgid "New Milestone"
-msgstr ""
+msgstr "新里程碑"
msgid "New Pages Domain"
-msgstr ""
+msgstr "新页é¢åŸŸå"
+
+msgid "New Password"
+msgstr "新密ç "
msgid "New Pipeline Schedule"
msgstr "创建æµæ°´çº¿è®¡åˆ’"
@@ -6236,23 +7329,23 @@ msgstr "创建æµæ°´çº¿è®¡åˆ’"
msgid "New Snippet"
msgstr "新建代ç ç‰‡æ®µ"
-msgid "New Snippets"
-msgstr "新建代ç ç‰‡æ®µ"
-
msgid "New branch"
msgstr "新建分支"
msgid "New branch unavailable"
msgstr "新分支ä¸å¯ç”¨"
+msgid "New deploy key"
+msgstr "新的部署密钥"
+
msgid "New directory"
msgstr "新建目录"
msgid "New environment"
-msgstr ""
+msgstr "新环境"
msgid "New epic"
-msgstr "新建å²è¯—故事"
+msgstr "æ–°epic"
msgid "New file"
msgstr "新建文件"
@@ -6260,6 +7353,9 @@ msgstr "新建文件"
msgid "New group"
msgstr "新建群组"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "新建身份标识"
@@ -6273,7 +7369,7 @@ msgid "New merge request"
msgstr "新建åˆå¹¶è¯·æ±‚"
msgid "New milestone"
-msgstr ""
+msgstr "新里程碑"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "æ–°æµæ°´çº¿å°†å–消åŒä¸€åˆ†æ”¯ä¸Šè¾ƒæ—§çš„待处ç†æµæ°´çº¿"
@@ -6281,6 +7377,9 @@ msgstr "æ–°æµæ°´çº¿å°†å–消åŒä¸€åˆ†æ”¯ä¸Šè¾ƒæ—§çš„待处ç†æµæ°´çº¿"
msgid "New project"
msgstr "新建项目"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "新建计划"
@@ -6293,20 +7392,29 @@ msgstr "新建å­ç¾¤ç»„"
msgid "New tag"
msgstr "新建标签"
+msgid "New users set to external"
+msgstr "新用户设置为外部"
+
msgid "New..."
msgstr "新建..."
+msgid "Newly registered users will by default be external"
+msgstr "默认情况下,新注册的用户将是外部用户"
+
msgid "No"
msgstr "å¦"
+msgid "No %{providerTitle} repositories available to import"
+msgstr "æ— %{providerTitle} 存储库å¯ä¾›å¯¼å…¥"
+
msgid "No Label"
msgstr "无标记"
-msgid "No activities found"
-msgstr ""
+msgid "No Tag"
+msgstr "无标签"
-msgid "No assignee"
-msgstr "未指派"
+msgid "No activities found"
+msgstr "没有å‘现任何活动"
msgid "No branches found"
msgstr "未å‘现分支"
@@ -6315,7 +7423,7 @@ msgid "No changes"
msgstr "æ— å˜æ›´å†…容"
msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
-msgstr ""
+msgstr "%{ref_start}%{source_branch}%{ref_end} å’Œ %{ref_start}%{target_branch}%{ref_end} 之间没有产生å˜åŒ–"
msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr "无法连接到GitalyæœåŠ¡å™¨ï¼Œè¯·æ£€æŸ¥ç›¸å…³æ—¥å¿—ï¼"
@@ -6330,13 +7438,13 @@ msgid "No credit card required."
msgstr "无需信用å¡ã€‚"
msgid "No details available"
-msgstr ""
+msgstr "没有å¯ç”¨çš„详细信æ¯"
msgid "No due date"
msgstr "无截止日期"
-msgid "No errors to display"
-msgstr ""
+msgid "No errors to display."
+msgstr "没有è¦æ˜¾ç¤ºçš„错误."
msgid "No estimate or time spent"
msgstr "无预计或已用时间"
@@ -6345,7 +7453,7 @@ msgid "No file chosen"
msgstr "未选定任何文件"
msgid "No file selected"
-msgstr ""
+msgstr "未选择任何文件"
msgid "No files found."
msgstr "未找到文件。"
@@ -6353,13 +7461,22 @@ msgstr "未找到文件。"
msgid "No issues for the selected time period."
msgstr "所选时间段无议题。"
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "没有具有此类å称或æ述的标记"
msgid "No license. All rights reserved"
msgstr "未设定许å¯è¯ã€‚版æƒæ‰€æœ‰ã€‚"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
+msgstr "没有匹é…的结果"
+
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
msgstr ""
msgid "No merge requests for the selected time period."
@@ -6372,13 +7489,13 @@ msgid "No messages were logged"
msgstr "未记录任何消æ¯"
msgid "No milestones to show"
-msgstr ""
+msgstr "没有è¦æ˜¾ç¤ºçš„里程碑"
msgid "No other labels with such name or description"
msgstr "没有其他具有此类å称或æ述的标记"
msgid "No preview for this file type"
-msgstr ""
+msgstr "无法预览此类型文件"
msgid "No prioritised labels with such name or description"
msgstr "无具有此类å称或æ述的优先标记"
@@ -6399,7 +7516,7 @@ msgid "No schedules"
msgstr "无计划"
msgid "No start date"
-msgstr ""
+msgstr "没有开始日期"
msgid "No, directly import the existing email addresses and usernames."
msgstr "å¦, 请直接导入现有电å­é‚®ä»¶åœ°å€å’Œç”¨æˆ·å。"
@@ -6410,9 +7527,6 @@ msgstr "节点"
msgid "None"
msgstr "æ— "
-msgid "Not allowed to merge"
-msgstr "ä¸å…许åˆå¹¶"
-
msgid "Not available"
msgstr "æ•°æ®ä¸è¶³"
@@ -6428,14 +7542,20 @@ msgstr "éžæœºå¯†"
msgid "Not enough data"
msgstr "æ•°æ®ä¸è¶³"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "æš‚ä¸"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "请注æ„,master分支自动å—ä¿æŠ¤ã€‚%{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr "未开始"
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
-msgstr ""
+msgstr "请注æ„,此邀请已å‘é€è‡³%{mail_to_invite_email},但您当å‰ä½¿ç”¨ç”µå­é‚®ä»¶%{mail_to_current_user}以%{link_to_current_user} 登录。"
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
msgstr "æ示:作为GitLab管ç†å‘˜ï¼Œå¯ä»¥é…ç½® %{github_integration_link},这将å…许通过GitHub登录并å…许连接Github代ç ä»“库而ä¸éœ€è¦ä¸ªäººè®¿é—®ä»¤ç‰Œã€‚"
@@ -6468,10 +7588,10 @@ msgid "Notification events"
msgstr "通知事件"
msgid "Notification setting"
-msgstr ""
+msgstr "无法预览此类型文件"
msgid "Notification setting - %{notification_title}"
-msgstr ""
+msgstr "通知设置 - %{notification_title}"
msgid "NotificationEvent|Close issue"
msgstr "关闭议题"
@@ -6486,7 +7606,7 @@ msgid "NotificationEvent|Merge merge request"
msgstr "åˆå¹¶è¯·æ±‚被åˆå¹¶"
msgid "NotificationEvent|New epic"
-msgstr "新建å²è¯—"
+msgstr "æ–°epic"
msgid "NotificationEvent|New issue"
msgstr "新建议题"
@@ -6545,6 +7665,9 @@ msgstr "å一月"
msgid "OK"
msgstr "确定"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "10月"
@@ -6557,10 +7680,19 @@ msgstr "过滤"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "仓库导入åŽï¼Œå¯ä»¥é€šè¿‡ SSH 拉å–é•œåƒã€‚了解更多 %{ssh_link}"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "其余%d项"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "您的一个或多个Bitbucket项目无法直接导入GitLab,因为它们使用Subversion或Mercurial进行版本控制,而ä¸æ˜¯Git。"
@@ -6570,20 +7702,26 @@ msgstr "您的一个或多个Google Code项目无法直接导入GitLab,因为å
msgid "Only admins"
msgstr "仅管ç†å‘˜"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "åªé•œåƒå—ä¿æŠ¤çš„分支"
msgid "Only policy:"
-msgstr ""
+msgstr "仅策略:"
msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
-msgstr ""
+msgstr "åªæœ‰åœ¨æ‚¨ä¿¡ä»» %{idp_url} æ—¶æ‰èƒ½ç»§ç»­æŽ§åˆ¶GitLabå¸æˆ·ç™»å½•ã€‚"
msgid "Only project members can comment."
msgstr "åªæœ‰é¡¹ç›®æˆå‘˜å¯ä»¥å‘表评论。"
msgid "Only project members will be imported. Group members will be skipped."
-msgstr ""
+msgstr "仅导入项目æˆå‘˜ã€‚群组æˆå‘˜å°†è¢«è·³è¿‡ã€‚"
msgid "Oops, are you sure?"
msgstr "å•Š~~, 确定å—?"
@@ -6592,13 +7730,13 @@ msgid "Open"
msgstr "展开"
msgid "Open Documentation"
-msgstr ""
+msgstr "打开文档"
msgid "Open comment type dropdown"
-msgstr ""
+msgstr "打开评论类型下拉列表"
msgid "Open errors"
-msgstr ""
+msgstr "打开错误"
msgid "Open in Xcode"
msgstr "用Xcode打开"
@@ -6606,6 +7744,9 @@ msgstr "用Xcode打开"
msgid "Open projects"
msgstr "打开项目"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "打开侧边æ "
@@ -6633,14 +7774,29 @@ msgstr "è¿ç»´"
msgid "Operations Dashboard"
msgstr "è¿ç»´ä»ªè¡¨æ¿"
+msgid "Operations Settings"
+msgstr "è¿ç»´è®¾ç½®"
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "将项目添加到仪表æ¿"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr "è¿ç»´ä»ªè¡¨æ¿æä¾›æ¯ä¸ªé¡¹ç›®çš„è¿è¡ŒçŠ¶å†µçš„摘è¦ï¼ŒåŒ…括æµæ°´çº¿å’Œè­¦æŠ¥çŠ¶æ€ã€‚"
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
-msgstr ""
+msgstr "无法添加 %{invalidProjects}。 æ“作仪表æ¿å¯ç”¨äºŽå…¬å…±é¡¹ç›®ä»¥åŠå…·æœ‰Gold方案的群组中的ç§æœ‰é¡¹ç›®ã€‚"
+
+msgid "Optional"
+msgstr "å¯é€‰çš„"
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr "您å¯ä»¥é€‰æ‹© %{link_to_customize} FogBugz的电å­é‚®ä»¶åœ°å€å’Œç”¨æˆ·å如何被导入到GitLab。"
@@ -6660,9 +7816,6 @@ msgstr "其他标记"
msgid "Other information"
msgstr "其他信æ¯"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "å¦åˆ™ï¼Œå»ºè®®æ‚¨ä»Žä¸‹é¢çš„一个选项开始。"
-
msgid "Outbound requests"
msgstr "外å‘请求"
@@ -6684,14 +7837,20 @@ msgstr "包已被删除"
msgid "Packages"
msgstr "软件包"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "Pages"
msgid "Pages Domain"
-msgstr ""
+msgstr "Pages域å"
msgid "Pages Domains"
-msgstr ""
+msgstr "Pages域å"
msgid "Pagination|Last »"
msgstr "尾页 »"
@@ -6706,10 +7865,7 @@ msgid "Pagination|« First"
msgstr "« 首页"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "å‚æ•°"
msgid "Part of merge request changes"
msgstr "包å«äºŽåˆå¹¶è¯·æ±‚å˜æ›´ä¸­"
@@ -6717,18 +7873,27 @@ msgstr "包å«äºŽåˆå¹¶è¯·æ±‚å˜æ›´ä¸­"
msgid "Password"
msgstr "密ç "
-msgid "Past due"
+msgid "Password authentication is unavailable."
msgstr ""
+msgid "Past due"
+msgstr "逾期"
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr "在此处粘贴计算机公钥。在%{link_start}这里%{link_end}了解更多关于如何产生公钥"
+
msgid "Paste epic link"
-msgstr ""
+msgstr "粘贴epic链接"
msgid "Paste issue link"
-msgstr ""
+msgstr "粘贴议题链接"
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "粘贴您的 SSH 公钥,通常包å«åœ¨ '~/.ssh/id_rsa.pub' 文件中,并以 'ssh-rsa' 开头。ä¸è¦ä½¿ç”¨æ‚¨çš„ SSH ç§é’¥ã€‚"
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "路径,转移,删除"
@@ -6738,6 +7903,9 @@ msgstr "路径:"
msgid "Pause"
msgstr "æš‚åœ"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "æš‚åœçš„ Runner ä¸æŽ¥å—新作业"
@@ -6756,6 +7924,9 @@ msgstr "性能优化"
msgid "Permissions"
msgstr "æƒé™"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "æƒé™ï¼ŒLFS,2FA"
@@ -6763,29 +7934,32 @@ msgid "Personal Access Token"
msgstr "个人访问凭è¯"
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
-msgstr ""
+msgstr "ä¸å…许创建个人项目。如有疑问,请è”系您的管ç†å‘˜"
msgid "Pick a name"
-msgstr ""
+msgstr "选择一个å称"
msgid "Pipeline"
msgstr "æµæ°´çº¿"
-msgid "Pipeline Health"
-msgstr "æµæ°´çº¿è¿è¡ŒçŠ¶å†µæŒ‡æ ‡"
-
msgid "Pipeline Schedule"
msgstr "æµæ°´çº¿è®¡åˆ’"
msgid "Pipeline Schedules"
msgstr "æµæ°´çº¿è®¡åˆ’"
+msgid "Pipeline minutes quota"
+msgstr "æµæ°´çº¿åˆ†é’Ÿæ•°é…é¢"
+
msgid "Pipeline quota"
msgstr "æµæ°´çº¿é…é¢"
msgid "Pipeline triggers"
msgstr "æµæ°´çº¿è§¦å‘器"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失败:"
@@ -6834,6 +8008,12 @@ msgstr "å˜é‡"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定义"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr "æ交:%{ci_status}"
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr "æµæ°´çº¿ï¼š%{ci_status}"
+
msgid "Pipelines"
msgstr "æµæ°´çº¿"
@@ -6849,6 +8029,12 @@ msgstr "上周的æµæ°´çº¿"
msgid "Pipelines for last year"
msgstr "去年的æµæ°´çº¿"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr "需è¦é…ç½®æµæ°´çº¿ä»¥å¯ç”¨æ­¤åŠŸèƒ½ã€‚"
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "自信地构建"
@@ -6891,11 +8077,8 @@ msgstr "此项目当å‰æœªé…ç½®è¿è¡Œæµæ°´çº¿ã€‚"
msgid "Pipeline|Commit"
msgstr "æ交"
-msgid "Pipeline|Create for"
-msgstr "创建于"
-
-msgid "Pipeline|Create pipeline"
-msgstr "创建æµæ°´çº¿"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "时长"
@@ -6909,6 +8092,9 @@ msgstr "æµæ°´çº¿"
msgid "Pipeline|Run Pipeline"
msgstr "è¿è¡Œæµæ°´çº¿"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "æœç´¢åˆ†æ”¯"
@@ -6936,9 +8122,21 @@ msgstr "å³å°†åœæ­¢æµæ°´çº¿ %{pipelineId}。"
msgid "Pipeline|all"
msgstr "所有"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "æˆåŠŸ"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "于阶段"
@@ -6955,7 +8153,7 @@ msgid "Play"
msgstr "è¿è¡Œ"
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
-msgstr ""
+msgstr "想è¦è¯„论请 %{link_to_register} 或 %{link_to_sign_in}"
msgid "Please accept the Terms of Service before continuing."
msgstr "请接å—æœåŠ¡æ¡æ¬¾ä»¥ç»§ç»­ã€‚"
@@ -6963,27 +8161,57 @@ msgstr "请接å—æœåŠ¡æ¡æ¬¾ä»¥ç»§ç»­ã€‚"
msgid "Please choose a group URL with no special characters."
msgstr "请选择无特殊字符的群组URL。"
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "请将它们先%{link_to_git}, 然åŽå†æ¬¡ä½¿ç”¨%{link_to_import_flow}。"
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "请将它们先在Google Code中转为Git, 然åŽå†æ¬¡ä½¿ç”¨%{link_to_import_flow}。"
-msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgid "Please create a password for your new account."
msgstr ""
+msgid "Please create a username with only alphanumeric characters."
+msgstr "请创建仅包å«å­—æ¯å’Œæ•°å­—的用户å。"
+
+msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
+msgstr "请å¯ç”¨å¹¶è¿ç§»åˆ°æ•£åˆ—存储以é¿å…安全问题并确ä¿æ•°æ®å®Œæ•´æ€§ã€‚ %{migrate_link}"
+
+msgid "Please enter a non-negative number"
+msgstr "请输入一个éžè´Ÿæ•°"
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr "请输入大于%{number}(æ¥è‡ªé¡¹ç›®è®¾ç½®)çš„æ•°å­—"
+
+msgid "Please enter a valid number"
+msgstr "请输入有效的数字"
+
msgid "Please fill in a descriptive name for your group."
msgstr "请为您的群组填写æ述性å称。"
msgid "Please migrate all existing projects to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
-msgstr ""
+msgstr "请将所有现有项目è¿ç§»åˆ°æ•£åˆ—存储,以é¿å…安全问题并确ä¿æ•°æ®å®Œæ•´æ€§ã€‚ %{migrate_link}"
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "请注æ„,GitLabä¸æ供此应用程åºï¼Œæ‚¨åº”该在å…许访问之å‰éªŒè¯å…¶çœŸå®žæ€§ã€‚"
+msgid "Please provide a name"
+msgstr "请æä¾›å称"
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr "请选择并添加一个æˆå‘˜"
+
msgid "Please select at least one filter to see results"
msgstr "请至少选择一个过滤器æ¥æŸ¥çœ‹ç»“æžœ"
+msgid "Please set a new password before proceeding."
+msgstr "请设置新密ç ä»¥ç»§ç»­ä¸‹ä¸€æ­¥ã€‚"
+
msgid "Please solve the reCAPTCHA"
msgstr "请填写验è¯ç ã€‚"
@@ -6991,11 +8219,14 @@ msgid "Please try again"
msgstr "请å†è¯•ä¸€æ¬¡"
msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
+msgstr "请将PostgreSQLå‡çº§åˆ°9.6或更高版本。使用当å‰ç‰ˆæœ¬æ— æ³•å¯é åœ°ç¡®å®šå¤åˆ¶çš„状æ€ã€‚"
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "请使用此表å•å‘GitLab报告创建垃圾议题ã€è¯„论或有ä¸å½“行为的用户。"
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "连接代ç ä»“库中,请ç¨å€™ã€‚å¯åœ¨ä»»æ„时刻刷新以获å–当å‰çŠ¶æ€ã€‚"
@@ -7005,18 +8236,33 @@ msgstr "导入代ç ä»“库中,请ç¨å€™ã€‚å¯åœ¨ä»»æ„时刻刷新以获å–当
msgid "Preferences"
msgstr "å好设置"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "导航主题"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr "此功能当åˆå¤„于实验阶段,翻译尚未全部完æˆã€‚"
+
msgid "Press Enter or click to search"
msgstr "按 回车键或å•å‡»ä»¥æœç´¢"
msgid "Prevent adding new members to project membership within this group"
msgstr "ç¦æ­¢å‘当å‰ç¾¤ç»„添加æˆå‘˜"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "预览"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "预览上传数æ®"
@@ -7036,7 +8282,7 @@ msgid "Prioritized label"
msgstr "优先标记"
msgid "Private"
-msgstr ""
+msgstr "ç§æœ‰"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "ç§äºº - å¿…é¡»å‘æ¯ä¸ªç”¨æˆ·æ˜Žç¡®æŽˆäºˆé¡¹ç›®è®¿é—®æƒé™ã€‚"
@@ -7044,6 +8290,9 @@ msgstr "ç§äºº - å¿…é¡»å‘æ¯ä¸ªç”¨æˆ·æ˜Žç¡®æŽˆäºˆé¡¹ç›®è®¿é—®æƒé™ã€‚"
msgid "Private - The group and its projects can only be viewed by members."
msgstr "ç§äºº - 群组åŠå…¶é¡¹ç›®åªèƒ½ç”±æˆå‘˜æŸ¥çœ‹ã€‚"
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "ç§æœ‰é¡¹ç›®å¯ä»¥åœ¨ä¸ªäººå称空间中创建:"
@@ -7060,16 +8309,16 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr "您将更改用户å %{currentUsernameBold} 为 %{newUsernameBold}。é…置文件和项目将é‡å®šå‘到 %{newUsername} 命å空间,但是一旦 %{currentUsername} 命å空间被å¦ä¸€ä¸ªç”¨æˆ·æˆ–组注册,此é‡å®šå‘将过期。请尽快更新您的远端Git仓库。"
msgid "Profiles|@username"
-msgstr ""
+msgstr "@用户å"
msgid "Profiles|Account scheduled for removal."
msgstr "å¸æˆ·å·²å®‰æŽ’被删除。"
msgid "Profiles|Activate signin with one of the following services"
-msgstr ""
+msgstr "使用以下æœåŠ¡ä¹‹ä¸€æ¿€æ´»ç™»å½•"
msgid "Profiles|Active"
-msgstr ""
+msgstr "活动"
msgid "Profiles|Add key"
msgstr "添加密钥"
@@ -7087,28 +8336,28 @@ msgid "Profiles|Change username"
msgstr "更改用户å"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "更改您的用户åå¯èƒ½ä¼šäº§ç”Ÿæ„想ä¸åˆ°çš„副作用。"
msgid "Profiles|Choose file..."
msgstr "选择文件..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
-msgstr ""
+msgstr "选择在公开个人资料中显示ç§æœ‰é¡¹ç›®çš„贡献,但ä¸æ˜¾ç¤ºä»»ä½•é¡¹ç›®ï¼Œå­˜å‚¨åº“或组织信æ¯"
msgid "Profiles|City, country"
-msgstr ""
+msgstr "城市,国家"
msgid "Profiles|Clear status"
msgstr "清除状æ€"
msgid "Profiles|Click on icon to activate signin with one of the following services"
-msgstr ""
+msgstr "å•å‡»å›¾æ ‡ä»¥ä½¿ç”¨ä»¥ä¸‹æœåŠ¡ä¹‹ä¸€æ¿€æ´»ç™»å½•"
msgid "Profiles|Connect"
-msgstr ""
+msgstr "连接"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "å…³è”账户"
msgid "Profiles|Current path: %{path}"
msgstr "当å‰è·¯å¾„: %{path}"
@@ -7129,7 +8378,7 @@ msgid "Profiles|Deleting an account has the following effects:"
msgstr "删除å¸æˆ·å…·æœ‰ä»¥ä¸‹æ•ˆæžœï¼š"
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "æ–­å¼€"
msgid "Profiles|Do not show on profile"
msgstr "ä¸åœ¨ä¸ªäººèµ„料中显示"
@@ -7141,10 +8390,10 @@ msgid "Profiles|Edit Profile"
msgstr "编辑个人资料"
msgid "Profiles|Enter your name, so people you know can recognize you"
-msgstr ""
+msgstr "输入您的姓å,以便大家认识您"
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
-msgstr ""
+msgstr "å¯ç”¨åŒé‡èº«ä»½è®¤è¯ï¼ˆ2FA),æ高账户安全性"
msgid "Profiles|Invalid password"
msgstr "密ç æ— æ•ˆ"
@@ -7183,37 +8432,37 @@ msgid "Profiles|Set new profile picture"
msgstr "设置新个人资料图片"
msgid "Profiles|Social sign-in"
-msgstr ""
+msgstr "社交登录"
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr "æŸäº›é€‰é¡¹å¯¹äºŽ LDAP å¸æˆ·ä¸å¯ç”¨"
msgid "Profiles|Tell us about yourself in fewer than 250 characters"
-msgstr ""
+msgstr "请用少于250字符æ述您自己"
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "å…许的最大文件大å°ä¸º200KB。"
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "这看起æ¥ä¸åƒ SSH 公钥,确定è¦æ·»åŠ å®ƒå—?"
msgid "Profiles|This email will be displayed on your public profile"
-msgstr ""
+msgstr "此电å­é‚®ä»¶å°†æ˜¾ç¤ºåœ¨æ‚¨çš„公开个人资料中。"
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "此电å­é‚®ä»¶å°†ç”¨äºŽåŸºäºŽWebçš„æ“作,例如编辑和åˆå¹¶ã€‚ %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "此表情符å·å’Œæ¶ˆæ¯ä¼šæ˜¾ç¤ºåœ¨æ‚¨çš„个人资料和界é¢ä¸­ã€‚"
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
-msgstr ""
+msgstr "此信æ¯å°†æ˜¾ç¤ºåœ¨æ‚¨çš„个人资料中"
msgid "Profiles|Two-Factor Authentication"
-msgstr ""
+msgstr "åŒå› ç´ èº«ä»½éªŒè¯"
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "键入您的 %{confirmationValue} 以确认:"
@@ -7240,13 +8489,13 @@ msgid "Profiles|Username successfully changed"
msgstr "用户å更改æˆåŠŸ"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "姓å中使用表情符å·è™½ç„¶æœ‰è¶£ï¼Œä½†è¯·è½¬åˆ°çŠ¶æ€ä¿¡æ¯é‡Œä½¿ç”¨å®ƒ"
msgid "Profiles|What's your status?"
-msgstr "你当å‰çš„状æ€ï¼Ÿ"
+msgstr "您当å‰çš„状æ€ï¼Ÿ"
msgid "Profiles|Who you represent or work for"
-msgstr ""
+msgstr "您代表è°æˆ–为è°å·¥ä½œ"
msgid "Profiles|You can change your avatar here"
msgstr "å¯ä»¥åœ¨è¿™é‡Œä¿®æ”¹æ‚¨çš„头åƒ"
@@ -7267,31 +8516,31 @@ msgid "Profiles|You must transfer ownership or delete these groups before you ca
msgstr "您必须转移所有æƒæˆ–删除这些群组,然åŽæ‰èƒ½åˆ é™¤æ‚¨çš„å¸æˆ·ã€‚"
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
-msgstr ""
+msgstr "您的LinkedInå称,å‚è§linkedin.com/in/profilename"
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr "您的å¸æˆ·ç›®å‰æ˜¯è¿™äº›ç¾¤ç»„的所有者:"
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "您的电å­é‚®ä»¶åœ°å€æ˜¯æ ¹æ®æ‚¨çš„ %{provider_label} å¸æˆ·è‡ªåŠ¨è®¾ç½®çš„"
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "您的ä½ç½®æ˜¯åŸºäºŽæ‚¨çš„ %{provider_label} å¸æˆ·è‡ªåŠ¨è®¾ç½®çš„。"
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
-msgstr ""
+msgstr "您的姓å是根æ®æ‚¨çš„ %{provider_label} å¸æˆ·è‡ªåŠ¨è®¾ç½®çš„,以便大家认识您"
msgid "Profiles|Your status"
-msgstr "你的状æ€"
+msgstr "您当å‰çš„状æ€ï¼Ÿ"
msgid "Profiles|e.g. My MacBook key"
msgstr "例如: My MacBook Key"
msgid "Profiles|username"
-msgstr ""
+msgstr "用户å"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "website.com"
msgid "Profiles|your account"
msgstr "您的å¸æˆ·"
@@ -7308,6 +8557,15 @@ msgstr "进度"
msgid "Project"
msgstr "项目"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr "项目“%{name}â€ä¸å†å¯ç”¨ã€‚选择å¦ä¸€ä¸ªé¡¹ç›®ä»¥ç»§ç»­ã€‚"
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "项目 “%{project_name}†正在被删除。"
@@ -7323,17 +8581,26 @@ msgstr "项目 '%{project_name}' 已更新完æˆã€‚"
msgid "Project Badges"
msgstr "项目徽章"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "项目 URL"
msgid "Project access must be granted explicitly to each user."
msgstr "项目访问æƒé™å¿…须明确授æƒç»™æ¯ä¸ªç”¨æˆ·ã€‚"
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "项目头åƒ"
-msgid "Project avatar in repository: %{link}"
-msgstr "项目头åƒåœ¨ä»“库中:%{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "项目详情"
@@ -7341,6 +8608,9 @@ msgstr "项目详情"
msgid "Project export could not be deleted."
msgstr "无法删除项目导出。"
+msgid "Project export enabled"
+msgstr "项目导出已å¯ç”¨"
+
msgid "Project export has been deleted."
msgstr "项目导出已被删除。"
@@ -7350,8 +8620,11 @@ msgstr "项目导出链接已过期。请从项目设置中é‡æ–°ç”Ÿæˆé¡¹ç›®å¯¼
msgid "Project export started. A download link will be sent by email."
msgstr "项目导出已开始。下载链接将通过电å­é‚®ä»¶å‘é€ã€‚"
+msgid "Project has too many %{label_for_message} to search"
+msgstr "项目有太多个 %{label_for_message} è¦æœç´¢"
+
msgid "Project members"
-msgstr ""
+msgstr "项目æˆå‘˜"
msgid "Project name"
msgstr "项目å称"
@@ -7359,9 +8632,15 @@ msgstr "项目å称"
msgid "Project slug"
msgstr "项目标识串"
-msgid "Project:"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
+msgid "Project:"
+msgstr "项目:"
+
msgid "ProjectActivityRSS|Subscribe"
msgstr "订阅"
@@ -7420,7 +8699,7 @@ msgid "ProjectSettings|Contact an admin to change this setting."
msgstr "è”系管ç†å‘˜æ›´æ”¹æ­¤è®¾ç½®ã€‚"
msgid "ProjectSettings|Customize your project badges."
-msgstr "自定义你的项目徽章。"
+msgstr "自定义您的项目徽章。"
msgid "ProjectSettings|Failed to protect the tag"
msgstr "ä¿æŠ¤æ ‡ç­¾å¤±è´¥"
@@ -7449,12 +8728,21 @@ msgstr "用户åªèƒ½é€šè¿‡è‡ªå·±å·²éªŒè¯çš„电å­é‚®ä»¶åœ°å€å°†æ交到此存
msgid "Projects"
msgstr "项目"
+msgid "Projects Successfully Retrieved"
+msgstr "项目æˆåŠŸè¯»å–"
+
msgid "Projects shared with %{group_name}"
msgstr "与 %{group_name} 共享的项目"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "属于群组的项目以群组å称为å‰ç¼€ã€‚现有项目å¯ä»¥ç§»åŠ¨åˆ°ä¸€ä¸ªç¾¤ç»„中。"
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr "具有写入æƒé™çš„项目"
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "ç»å¸¸è®¿é—®"
@@ -7503,6 +8791,9 @@ msgstr "无警报设定"
msgid "PrometheusAlerts|Operator"
msgstr "æ“作符"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "阈值"
@@ -7531,7 +8822,7 @@ msgid "PrometheusService|Custom metrics"
msgstr "自定义指标"
msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
-msgstr ""
+msgstr "使用上é¢ä»»ä¸€é€‰é¡¹ä»¥å¯ç”¨ Prometheus æ¥å®šä¹‰è‡ªå®šä¹‰åº¦é‡æ ‡å‡†ï¼Œ"
msgid "PrometheusService|Finding and configuring metrics..."
msgstr "查找和é…置指标..."
@@ -7597,7 +8888,7 @@ msgid "Promotions|Don't show me this again"
msgstr "ä¸å†æ˜¾ç¤º"
msgid "Promotions|Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones."
-msgstr "å²è¯—故事(Epics) 让您å¯ä»¥é€šè¿‡è¿½è¸ªè·¨é¡¹ç›®å’Œé‡Œç¨‹ç¢‘共享主题的问题组,更有效地管ç†é¡¹ç›®ç»„åˆã€‚"
+msgstr "Epic 让您å¯ä»¥é€šè¿‡è¿½è¸ªè·¨é¡¹ç›®å’Œé‡Œç¨‹ç¢‘共享主题的问题组,更有效地管ç†é¡¹ç›®ç»„åˆã€‚"
msgid "Promotions|This feature is locked."
msgstr "此功能已é”定"
@@ -7605,6 +8896,9 @@ msgstr "此功能已é”定"
msgid "Promotions|Upgrade plan"
msgstr "å‡çº§è®¢é˜…计划"
+msgid "Prompt users to upload SSH keys"
+msgstr "æ示用户上传 SSH 密钥"
+
msgid "Protected"
msgstr "å—ä¿æŠ¤"
@@ -7663,7 +8957,7 @@ msgid "Pseudonymizer data collection"
msgstr "匿å化数æ®æ”¶é›†"
msgid "Public"
-msgstr ""
+msgstr "公有"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "公开 - 群组和任何公开项目å¯ä»¥å¼€æ”¾æŸ¥çœ‹ï¼Œæ— éœ€èº«ä»½éªŒè¯ã€‚"
@@ -7671,6 +8965,12 @@ msgstr "公开 - 群组和任何公开项目å¯ä»¥å¼€æ”¾æŸ¥çœ‹ï¼Œæ— éœ€èº«ä»½éªŒ
msgid "Public - The project can be accessed without any authentication."
msgstr "公开 - 无需任何身份验è¯å³å¯è®¿é—®è¯¥é¡¹ç›®ã€‚"
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr "公共部署密钥(%{deploy_keys_count})"
+
msgid "Public pipelines"
msgstr "公开æµæ°´çº¿"
@@ -7683,6 +8983,12 @@ msgstr "推é€"
msgid "Push Rules"
msgstr "推é€è§„则"
+msgid "Push an existing Git repository"
+msgstr "推é€çŽ°æœ‰çš„ Git 存储库"
+
+msgid "Push an existing folder"
+msgstr "推é€çŽ°æœ‰æ–‡ä»¶å¤¹"
+
msgid "Push events"
msgstr "推é€äº‹ä»¶"
@@ -7705,19 +9011,22 @@ msgid "Quarters"
msgstr "季度"
msgid "Query"
-msgstr ""
+msgstr "查询"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "快速æ“作å¯ç”¨äºŽè®®é¢˜æ述和评论框。"
msgid "README"
+msgstr "自述文件"
+
+msgid "Rake Tasks Help"
msgstr ""
msgid "Read more"
msgstr "进一步了解"
msgid "Read more about environments"
-msgstr ""
+msgstr "阅读更多有关环境"
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr "了解有关项目æƒé™çš„æ›´å¤šä¿¡æ¯ <strong>%{link_to_help}</strong>"
@@ -7726,7 +9035,13 @@ msgid "Real-time features"
msgstr "实时功能"
msgid "Receive alerts from manually configured Prometheus servers."
-msgstr ""
+msgstr "从手动é…置的 Prometheus æœåŠ¡å™¨æŽ¥æ”¶è­¦æŠ¥ã€‚"
+
+msgid "Recent"
+msgstr "最近"
+
+msgid "Recent Project Activity"
+msgstr "最近的项目活动"
msgid "Recent searches"
msgstr "最近的æœç´¢"
@@ -7754,7 +9069,7 @@ msgid "Register / Sign In"
msgstr "注册/登录"
msgid "Register U2F device"
-msgstr ""
+msgstr "注册U2F设备"
msgid "Register and see your runners for this group."
msgstr "注册并查看当å‰ç¾¤ç»„çš„Runner。"
@@ -7762,6 +9077,9 @@ msgstr "注册并查看当å‰ç¾¤ç»„çš„Runner。"
msgid "Register and see your runners for this project."
msgstr "注册和查看当å‰é¡¹ç›®çš„ Runner"
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "注册表"
@@ -7787,10 +9105,10 @@ msgid "Related merge requests"
msgstr "相关åˆå¹¶è¯·æ±‚"
msgid "Releases"
-msgstr ""
+msgstr "版本"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
-msgstr ""
+msgstr "版本标志ç€é¡¹ç›®çš„å‘展历å²ä¸­çš„具体节点,传达相关的å˜æ›´ä¿¡æ¯ï¼Œå¹¶æä¾›ç»è¿‡ç¼–译并且已准备好交付的软件版本ã€ä»¥ä¾¿åœ¨å…¶å®ƒåœ°æ–¹é‡ç”¨ã€‚ç›®å‰ï¼Œåªèƒ½é€šè¿‡ API 创建版本。"
msgid "Remind later"
msgstr "ç¨åŽæ醒"
@@ -7802,14 +9120,23 @@ msgid "Remove Runner"
msgstr "移除Runner"
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
-msgstr ""
+msgstr "将新的æ交推é€åˆ°å…¶æºåˆ†æ”¯æ—¶ï¼Œåˆ é™¤åˆå¹¶è¯·æ±‚中的所有核准"
msgid "Remove approver"
-msgstr ""
+msgstr "删除核准人"
+
+msgid "Remove approvers"
+msgstr "删除核准人"
+
+msgid "Remove approvers?"
+msgstr "删除核准人?"
msgid "Remove avatar"
msgstr "删除头åƒ"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "删除群组"
@@ -7819,12 +9146,33 @@ msgstr "删除优先级"
msgid "Remove project"
msgstr "删除项目"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "已删除的群组无法æ¢å¤ï¼"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "删除群组将导致所有å­é¡¹ç›®å’Œèµ„æºè¢«åˆ é™¤ã€‚"
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "é‡å‘½å"
@@ -7835,17 +9183,20 @@ msgid "Rename folder"
msgstr "é‡å‘½å文件夹"
msgid "Reopen epic"
-msgstr "é‡æ–°å¼€å¯å²è¯—"
+msgstr "é‡æ–°å¼€å¯epic"
msgid "Reopen milestone"
-msgstr ""
+msgstr "é‡æ–°æ‰“开里程碑"
msgid "Repair authentication"
msgstr "ä¿®å¤è®¤è¯"
-msgid "Reply to comment"
+msgid "Replace"
msgstr ""
+msgid "Reply to comment"
+msgstr "回å¤è¯„论"
+
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr "直接回å¤æ­¤é‚®ä»¶æˆ– %{view_it_on_gitlab}。"
@@ -7876,6 +9227,18 @@ msgstr "执行时间"
msgid "Reports|Failure"
msgstr "失败"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "严é‡æ€§"
@@ -7894,6 +9257,12 @@ msgstr "测试总结报告解æžä¸­"
msgid "Reports|Vulnerability"
msgstr "æ¼æ´ž"
+msgid "Reports|issue"
+msgstr "议题"
+
+msgid "Reports|merge request"
+msgstr "åˆå¹¶è¯·æ±‚"
+
msgid "Reports|no changed test results"
msgstr "未å‘生å˜åŒ–的测试结果"
@@ -7906,11 +9275,14 @@ msgstr "存储库设置"
msgid "Repository URL"
msgstr "仓库地å€"
-msgid "Repository cleanup"
+msgid "Repository check was triggered."
msgstr ""
+msgid "Repository cleanup"
+msgstr "存储库清ç†"
+
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
-msgstr ""
+msgstr "存储库清ç†å·²ç»å¼€å§‹ã€‚一旦清ç†å®Œæˆï¼Œæ‚¨å°†æ”¶åˆ°ä¸€å°ç”µå­é‚®ä»¶ã€‚"
msgid "Repository has no locks."
msgstr "当å‰ä»“库无加é”文件。"
@@ -7931,7 +9303,7 @@ msgid "Request Access"
msgstr "申请æƒé™"
msgid "Requested %{time_ago}"
-msgstr ""
+msgstr "请求的 %{time_ago}"
msgid "Requests Profiles"
msgstr "请求分æž"
@@ -7942,26 +9314,37 @@ msgstr "è¦æ±‚此群组中的所有用户都å¯ç”¨åŒé‡è®¤è¯"
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "è¦æ±‚所有用户在访问GitLab时接å—æœåŠ¡æ¡æ¬¾å’Œéšç§æ”¿ç­–。"
+msgid "Require approval from code owners"
+msgstr "需è¦ä»£ç æ‰€æœ‰è€…的核准"
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] "éœ€è¦ %{count} 个 %{names} 的核准"
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] "éœ€è¦ %d 个核准"
+
msgid "Resend invite"
-msgstr ""
+msgstr "é‡æ–°å‘é€é‚€è¯·"
msgid "Reset authorization key"
-msgstr ""
+msgstr "é‡ç½®æŽˆæƒå¯†é’¥"
msgid "Reset authorization key?"
-msgstr ""
+msgstr "é‡ç½®æŽˆæƒå¯†é’¥ï¼Ÿ"
msgid "Reset health check access token"
msgstr "é‡ç½®è¿è¡ŒçŠ¶å†µæ£€æŸ¥è®¿é—®ä»¤ç‰Œ"
msgid "Reset key"
-msgstr ""
+msgstr "é‡ç½®å¯†é’¥"
msgid "Reset runners registration token"
msgstr "é‡ç½® Runner 注册令牌"
msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
-msgstr ""
+msgstr "é‡ç½®æŽˆæƒå¯†é’¥å°†ä¼šä½¿å…ˆå‰çš„密钥无效。现有警报é…置需è¦é€šè¿‡æ–°çš„密钥更新。"
msgid "Resolve all discussions in new issue"
msgstr "在新议题中解决所有讨论"
@@ -7973,9 +9356,18 @@ msgid "Resolve discussion"
msgstr "解决讨论"
msgid "Resolved"
+msgstr "已解决"
+
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
msgstr ""
msgid "Response"
+msgstr "å“应"
+
+msgid "Response didn't include `service_desk_address`"
msgstr ""
msgid "Response metrics (AWS ELB)"
@@ -7988,7 +9380,7 @@ msgid "Response metrics (HA Proxy)"
msgstr "å“应指标(HA Proxy)"
msgid "Response metrics (NGINX Ingress VTS)"
-msgstr ""
+msgstr "å“应指标(NGINX Ingress VTS)"
msgid "Response metrics (NGINX Ingress)"
msgstr "å“应指标(NGINX Ingress)"
@@ -7997,17 +9389,23 @@ msgid "Response metrics (NGINX)"
msgstr "å“应指标(NGINX)"
msgid "Restart Terminal"
-msgstr ""
+msgstr "é‡å¯ç»ˆç«¯"
msgid "Resume"
msgstr "æ¢å¤"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "é‡è¯•"
msgid "Retry this job"
msgstr "é‡è¯•å½“å‰ä½œä¸š"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "é‡è¯•éªŒè¯"
@@ -8016,7 +9414,7 @@ msgid_plural "Reveal values"
msgstr[0] "显示值"
msgid "Reveal values"
-msgstr ""
+msgstr "显示值"
msgid "Revert this commit"
msgstr "还原此æ交"
@@ -8039,15 +9437,24 @@ msgstr "审核(åˆå¹¶è¯·æ±‚ !%{mergeRequestId})"
msgid "Revoke"
msgstr "撤销"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "路线图"
msgid "Run CI/CD pipelines for external repositories"
msgstr "使用外部仓库的CI/CDæµæ°´çº¿"
-msgid "Run tests against your code live using the Web Terminal"
+msgid "Run housekeeping"
msgstr ""
+msgid "Run tests against your code live using the Web Terminal"
+msgstr "使用Web终端对您的代ç è¿›è¡Œå®žæ—¶æµ‹è¯•"
+
msgid "Run untagged jobs"
msgstr "è¿è¡Œæœªæ ‡è®°çš„作业"
@@ -8066,6 +9473,12 @@ msgstr "Runnerå°†è¿è¡ŒæŒ‡å®šé¡¹ç›®çš„作业"
msgid "Runner token"
msgstr "Runner 令牌"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "Runnerä¸ä¼šæŽ¥å—新的作业"
@@ -8076,7 +9489,7 @@ msgid "Runners API"
msgstr "Runners API"
msgid "Runners activated for this project"
-msgstr ""
+msgstr "此项目已激活的è¿è¡Œå™¨"
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr "Runnerå¯ä»¥æ”¾åœ¨ä¸åŒçš„用户ã€æœåŠ¡å™¨ï¼Œç”šè‡³æœ¬åœ°æœºå™¨ä¸Šã€‚"
@@ -8099,27 +9512,27 @@ msgstr "您已ç»ä½¿ç”¨äº†æ‰€æœ‰å…±äº«Runnerçš„æµæ°´çº¿æ—¶é—´ã€‚"
msgid "Running"
msgstr "è¿è¡Œä¸­"
+msgid "Running…"
+msgstr "è¿è¡Œä¸­..."
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO(å•ç‚¹ç™»å½•)"
msgid "SAML SSO for %{group_name}"
msgstr "群组%{group_name} 的 SAML SSO"
-msgid "SAML Single Sign On"
-msgstr "SAML å•ç‚¹ç™»å½•"
-
-msgid "SAML Single Sign On Settings"
-msgstr "SAML å•ç‚¹ç™»å½•è®¾ç½®"
-
msgid "SAML for %{group_name}"
-msgstr ""
-
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "SAML令牌签åè¯ä¹¦çš„SHA1指纹。请从身份验è¯æ供商处获å–(也å¯ä»¥è¢«ç§°ä¸ºâ€œæŒ‡çº¹â€ï¼‰ã€‚"
+msgstr "%{group_name} çš„ SAML"
msgid "SSH Keys"
msgstr "SSH 密钥"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH主机密钥"
@@ -8129,11 +9542,14 @@ msgstr "SSH公钥"
msgid "SSL Verification"
msgstr "SSL验è¯"
+msgid "Saturday"
+msgstr "星期六"
+
msgid "Save"
msgstr "ä¿å­˜"
msgid "Save Changes"
-msgstr ""
+msgstr "ä¿å­˜ä¿®æ”¹"
msgid "Save application"
msgstr "ä¿å­˜åº”用"
@@ -8145,7 +9561,7 @@ msgid "Save changes before testing"
msgstr "测试å‰ä¿å­˜æ›´æ”¹"
msgid "Save comment"
-msgstr ""
+msgstr "ä¿å­˜è¯„论"
msgid "Save pipeline schedule"
msgstr "ä¿å­˜æµæ°´çº¿è®¡åˆ’"
@@ -8153,6 +9569,9 @@ msgstr "ä¿å­˜æµæ°´çº¿è®¡åˆ’"
msgid "Save variables"
msgstr "ä¿å­˜å˜é‡"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "新建æµæ°´çº¿è®¡åˆ’"
@@ -8162,15 +9581,24 @@ msgstr "已加入日程"
msgid "Schedules"
msgstr "计划"
+msgid "Scheduling"
+msgstr "计划"
+
msgid "Scheduling Pipelines"
msgstr "æµæ°´çº¿è®¡åˆ’"
msgid "Scope"
msgstr "范围"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr "范围内ä¸æ”¯æŒç¦ç”¨ users_search 功能ï¼"
+
msgid "Scoped issue boards"
msgstr "指定范围的议题看æ¿"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "å‘下滚动到 <strong>Google Code项目托管</strong> 并通过å³ä¾§çš„开关å¯ç”¨ã€‚"
@@ -8184,7 +9612,7 @@ msgid "Search"
msgstr "æœç´¢"
msgid "Search an environment spec"
-msgstr ""
+msgstr "æœç´¢çŽ¯å¢ƒè§„范"
msgid "Search branches"
msgstr "æœç´¢åˆ†æ”¯"
@@ -8199,7 +9627,7 @@ msgid "Search for projects, issues, etc."
msgstr "æœç´¢é¡¹ç›®ã€è®®é¢˜ç­‰ç­‰â€¦"
msgid "Search groups"
-msgstr ""
+msgstr "æœç´¢ç¾¤ç»„"
msgid "Search merge requests"
msgstr "æœç´¢åˆå¹¶è¯·æ±‚"
@@ -8222,6 +9650,9 @@ msgstr "æœç´¢é¡¹ç›®"
msgid "Search users"
msgstr "æœç´¢ç”¨æˆ·"
+msgid "Search users or groups"
+msgstr "æœç´¢ç”¨æˆ·æˆ–群组"
+
msgid "Search your projects"
msgstr "æœç´¢æ‚¨çš„项目"
@@ -8249,6 +9680,15 @@ msgstr "在此群组"
msgid "SearchAutocomplete|in this project"
msgstr "在此项目"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "密ç "
@@ -8270,9 +9710,6 @@ msgstr "获å–æ¼æ´žåˆ—表时出错。请检查您的网络连接,然åŽé‡è¯•
msgid "Security Dashboard|Issue Created"
msgstr "已创建议题"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "创建议题"
@@ -8280,20 +9717,20 @@ msgid "Security Reports|Dismiss vulnerability"
msgstr "忽略æ¼æ´ž"
msgid "Security Reports|Learn more about setting up your dashboard"
-msgstr ""
+msgstr "了解更多关于您的仪表æ¿è®¾ç½®"
msgid "Security Reports|More info"
msgstr "更多信æ¯"
msgid "Security Reports|No Vulnerabilities"
-msgstr ""
-
-msgid "Security Reports|Security dashboard documentation"
-msgstr "安全仪表æ¿æ–‡æ¡£"
+msgstr "æ— æ¼æ´ž"
msgid "Security Reports|There was an error creating the issue."
msgstr "创建议题时出错。"
+msgid "Security Reports|There was an error creating the merge request."
+msgstr "创建åˆå¹¶è¯·æ±‚时出错。"
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "忽略æ¼æ´žæ—¶å‡ºé”™ã€‚"
@@ -8304,16 +9741,16 @@ msgid "Security Reports|There was an error reverting this dismissal."
msgstr "å–消忽略时出错。"
msgid "Security Reports|Undo dismiss"
-msgstr ""
+msgstr "撤消解除"
msgid "Security Reports|We've found no vulnerabilities for your group"
-msgstr ""
+msgstr "我们没有在您的群组中å‘现æ¼æ´ž"
msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
-msgstr ""
+msgstr "虽然您的群组中没有æ¼æ´žï¼Œä½†å®ƒå¯èƒ½ä¼šå‡ºçŽ°ã€‚无论如何,我们都建议您仔细检查您的设置以确ä¿æ‚¨å·²æ­£ç¡®çš„设置了您的仪表æ¿ã€‚"
msgid "Security dashboard"
-msgstr ""
+msgstr "安全仪表盘"
msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr "安全仪表æ¿æ˜¾ç¤ºæœ€æ–°çš„安全报告。用它æ¥æŸ¥æ‰¾å’Œä¿®å¤æ¼æ´žã€‚"
@@ -8325,10 +9762,10 @@ msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
msgstr "æµæ°´çº¿ %{pipelineLink} 已触å‘"
msgid "See metrics"
-msgstr ""
+msgstr "查看指标"
msgid "See the affected projects in the GitLab admin panel"
-msgstr ""
+msgstr "查看 GitLab 管ç†é¢æ¿ä¸­çš„å—å½±å“项目"
msgid "Select"
msgstr "选择"
@@ -8342,6 +9779,15 @@ msgstr "选择è¦é‚€è¯·çš„组"
msgid "Select a namespace to fork the project"
msgstr "选择一个命å空间æ¥æ´¾ç”Ÿé¡¹ç›®"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr "选择è¦è¯»å– Insights é…置文件的项目"
+
+msgid "Select a repository"
+msgstr "选择一个存储库"
+
msgid "Select a template repository"
msgstr "选择模æ¿ä»“库"
@@ -8351,14 +9797,11 @@ msgstr "选择时区"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "选择一个既有的Kubernetes集群或者创建一个新的"
-msgid "Select assignee"
-msgstr "选择指派人"
-
msgid "Select branch/tag"
msgstr "选择分支/标签"
msgid "Select members to invite"
-msgstr ""
+msgstr "选择è¦é‚€è¯·çš„æˆå‘˜"
msgid "Select project"
msgstr "选择项目"
@@ -8384,74 +9827,95 @@ msgstr "选择当å‰é¡¹ç›®çš„默认分支。除éžå¦è¡ŒæŒ‡å®šï¼Œå¦åˆ™æ‰€æœ‰åˆ
msgid "Select the custom project template source group."
msgstr "选择自定义项目模æ¿æºç»„。"
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr "éžç®¡ç†å‘˜ç”¨æˆ·æ— æ³•ä¸ºç¾¤ç»„ã€é¡¹ç›®æˆ–代ç ç‰‡æ®µä½¿ç”¨æ‰€é€‰çº§åˆ«ã€‚如果公共级别å—到é™åˆ¶ï¼Œåˆ™ç”¨æˆ·é…置文件仅对登录用户å¯è§ã€‚"
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "选中GitLab用户将在议题和评论的æ述中加入指å‘该GitLab用户的链接(例如“By <a href=\"#\">@johnsmith</a>â€ï¼‰ã€‚它还将与所选用户关è”å’Œ/或分é…这些议题和评论。"
-msgid "Selective synchronization"
-msgstr "选择性åŒæ­¥"
+msgid "Send an email notification to Developers."
+msgstr "å‘å¼€å‘人员å‘é€ç”µå­é‚®ä»¶é€šçŸ¥ã€‚"
msgid "Send email"
msgstr "å‘é€ç”µå­é‚®ä»¶"
msgid "Send report"
-msgstr ""
+msgstr "å‘é€æŠ¥å‘Š"
msgid "Send usage data"
msgstr "å‘é€çš„使用情况数æ®"
msgid "Sentry API URL"
-msgstr ""
+msgstr "Sentry API URL"
msgid "Sep"
msgstr "9月"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "9月"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "æœåŠ¡å™¨ç‰ˆæœ¬"
msgid "Serverless"
+msgstr "Serverless"
+
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
msgstr ""
msgid "ServerlessDetails|Kubernetes Pods"
+msgstr "Kubernestes Pods"
+
+msgid "ServerlessDetails|More information"
msgstr ""
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
-msgstr ""
+msgstr "æ ¹æ®éœ€è¦éšæ—¶é—´æŽ¨ç§»ä½¿ç”¨çš„Kubernetes Podæ•°é‡ã€‚"
msgid "ServerlessDetails|pod in use"
-msgstr ""
+msgstr "Pod正在使用"
msgid "ServerlessDetails|pods in use"
-msgstr ""
+msgstr "Pods正在使用"
msgid "ServerlessURL|Copy URL to clipboard"
-msgstr ""
+msgstr "å¤åˆ¶é“¾æŽ¥åˆ°å‰ªè´´æ¿"
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
-msgstr ""
-
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
+msgstr "è¦å¼€å§‹å°†åŠŸèƒ½ä½œä¸ºä¸€ç§æœåŠ¡ï¼Œå¿…须先在Kubernetes群集上安装Knative。"
msgid "Serverless|Getting started with serverless"
-msgstr ""
+msgstr "开始使用Serverless"
msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
-msgstr ""
+msgstr "如果您认为这些都ä¸é€‚用,请ç¨åŽå†æŸ¥çœ‹ï¼Œå› ä¸ºåŠŸèƒ½æ•°æ®å¯èƒ½æ­£åœ¨å˜ä¸ºå¯ç”¨ã€‚"
msgid "Serverless|Install Knative"
-msgstr ""
+msgstr "安装Knative"
msgid "Serverless|Learn more about Serverless"
-msgstr ""
+msgstr "了解更多关于 Serverless"
msgid "Serverless|No functions available"
-msgstr ""
+msgstr "没有å¯ç”¨çš„功能"
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
-msgstr ""
+msgstr " Knativeç›®å‰æ²¡æœ‰å¯ç”¨çš„功能数æ®ã€‚è¿™å¯èƒ½æœ‰å¤šç§åŽŸå› ï¼ŒåŒ…括:"
msgid "Service Desk"
msgstr "æœåŠ¡å°"
@@ -8462,9 +9926,18 @@ msgstr "æœåŠ¡æ¨¡æ¿"
msgid "Service URL"
msgstr "æœåŠ¡ URL"
+msgid "Session duration (minutes)"
+msgstr "会è¯æŒç»­æ—¶é—´(分钟)"
+
msgid "Session expiration, projects limit and attachment size."
msgstr "会è¯æœ‰æ•ˆæœŸï¼Œé¡¹ç›®é™åˆ¶åŠé™„件大å°ã€‚"
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr "设置所需的核准数é‡ï¼Œæ ¸å‡†è€…和其他核准设置。"
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "为账å·åˆ›å»ºä¸€ä¸ªç”¨äºŽæŽ¨é€æˆ–拉å–çš„ %{protocol} 密ç ã€‚"
@@ -8480,15 +9953,30 @@ msgstr "设置实例范围的模æ¿å­˜å‚¨åº“"
msgid "Set max session time for web terminal."
msgstr "为Web终端设置最长会è¯æ—¶é—´ã€‚"
+msgid "Set new password"
+msgstr "设置新密ç "
+
msgid "Set notification email for abuse reports."
msgstr "为滥用报告设置通知电å­é‚®ä»¶ã€‚"
msgid "Set number of approvers required before open merge requests can be merged"
-msgstr ""
+msgstr "设置åˆå¹¶æœªå…³é—­çš„åˆå¹¶è¯·æ±‚之å‰æ‰€éœ€çš„核准人数é‡"
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "设定用户登录的æ¡ä»¶ã€‚å¯ç”¨å¼ºåˆ¶åŒé‡è®¤è¯ã€‚"
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr "设置æ¯ä¸ªä½œä¸šçš„工件的默认到期时间。 0 表示无é™åˆ¶ã€‚默认以秒为å•ä½ï¼Œä½†æ‚¨å¯ä»¥å®šä¹‰æ›¿ä»£æ–¹æ¡ˆã€‚例如:<code>4 mins 2 sec</code>, <code>2h42min</code>.。"
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr "设置作业的过期时间。一旦逾期,作业将被存档并且ä¸èƒ½é‡è¯•ã€‚留空,则永ä¸è¿‡æœŸã€‚它必须设置为 1 天以上,例如: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>。"
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr "设置æ¯ä¸ªä½œä¸šçš„工件文件的最大大å°"
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr "设置群组æ¯ä¸ªæœˆå¯åœ¨å…±äº«è¿è¡Œå™¨ä¸Šä½¿ç”¨çš„最大æµæ°´çº¿åˆ†é’Ÿæ•°ã€‚ 0 表示无é™åˆ¶ã€‚"
+
msgid "Set up CI/CD"
msgstr "é…ç½® CI/CD"
@@ -8502,7 +9990,10 @@ msgid "Set up assertions/attributes/claims (email, first_name, last_name) and Na
msgstr "æ ¹æ®%{docsLinkStart}文档%{icon}%{docsLinkEnd}设置断言/属性/声明(email,first_name,last_name)和NameID"
msgid "Set up new U2F device"
-msgstr ""
+msgstr "设置新的U2F设备"
+
+msgid "Set up new password"
+msgstr "设置新密ç "
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "设置项目以自动推é€å’Œ/或从å¦ä¸€ä¸ªå­˜å‚¨åº“中æå–更改。分支,标签和æ交将自动åŒæ­¥ã€‚"
@@ -8532,7 +10023,7 @@ msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try agai
msgstr "对ä¸èµ·ï¼Œæˆ‘们无法设置您的状æ€ã€‚请ç¨åŽå†è¯•ã€‚"
msgid "SetStatusModal|What's your status?"
-msgstr "你的状æ€æ˜¯ä»€ä¹ˆï¼Ÿ"
+msgstr "您的状æ€æ˜¯ä»€ä¹ˆï¼Ÿ"
msgid "Settings"
msgstr "设置"
@@ -8561,18 +10052,27 @@ msgstr "é‡ç½®å·²ç”¨æµæ°´çº¿åˆ†é’Ÿæ•°"
msgid "Sherlock Transactions"
msgstr "Sherlock事物"
+msgid "Show all activity"
+msgstr "显示所有活动"
+
msgid "Show command"
msgstr "显示相关命令"
+msgid "Show comments only"
+msgstr "仅显示评论"
+
msgid "Show complete raw log"
msgstr "显示完整的原始日志"
msgid "Show file browser"
-msgstr ""
+msgstr "显示文件æµè§ˆå™¨"
msgid "Show latest version"
msgstr "显示最新版本"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "查看上级页é¢"
@@ -8608,19 +10108,19 @@ msgid "Sign in / Register"
msgstr "登录/注册"
msgid "Sign in to \"%{group_name}\""
-msgstr ""
+msgstr "登录到 \"%{group_name}\""
msgid "Sign in using smart card"
-msgstr ""
+msgstr "使用智能å¡ç™»å½•"
msgid "Sign in via 2FA code"
-msgstr ""
+msgstr "通过2FA代ç ç™»å½•"
msgid "Sign in with Single Sign-On"
msgstr "使用å•ç‚¹ç™»å½•(SSO)进行登录"
msgid "Sign in with smart card"
-msgstr ""
+msgstr "使用智能å¡ç™»å½•"
msgid "Sign out"
msgstr "退出"
@@ -8631,15 +10131,24 @@ msgstr "登录é™åˆ¶"
msgid "Sign-up restrictions"
msgstr "注册é™åˆ¶"
-msgid "Similar issues"
+msgid "Signing in using %{label} has been disabled"
msgstr ""
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
+msgid "Similar issues"
+msgstr "相似议题"
+
msgid "Size"
msgstr "大å°"
msgid "Size and domain settings for static websites"
msgstr "é™æ€ç½‘站的大å°å’ŒåŸŸè®¾ç½®"
+msgid "Size limit per repository (MB)"
+msgstr "æ¯ä¸ªå­˜å‚¨åº“的大å°é™åˆ¶ (MB)"
+
msgid "Slack application"
msgstr "Slack应用"
@@ -8656,16 +10165,37 @@ msgid "Smartcard authentication failed: client certificate header is missing."
msgstr "智能å¡èº«ä»½éªŒè¯å¤±è´¥ï¼šç¼ºå°‘客户端è¯ä¹¦æŠ¥å¤´ã€‚"
msgid "Snippet Contents"
-msgstr ""
+msgstr "代ç æ®µå†…容"
msgid "Snippets"
msgstr "代ç ç‰‡æ®µ"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr "探索公共代ç ç‰‡æ®µ"
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr "新代ç ç‰‡æ®µ"
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr "当å‰æ— ä»£ç ç‰‡æ®µ"
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr "代ç ç‰‡æ®µæ˜¯æ‚¨å¸Œæœ›ä¿ç•™çš„å°æ®µä»£ç æˆ–笔记。"
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr "没有è¦æ˜¾ç¤ºçš„代ç ç‰‡æ®µã€‚"
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr "它们å¯ä»¥è®¾ç½®ä¸ºå…¬å¼€æˆ–ç§æœ‰ã€‚"
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr "æŸäº›ç”µå­é‚®ä»¶æœåŠ¡å™¨ä¸æ”¯æŒè¦†ç›–电å­é‚®ä»¶çš„å‘件人å称。å¯ç”¨æ­¤é€‰é¡¹å¯ä»¥åœ¨ç”µå­é‚®ä»¶æ­£æ–‡ä¸­åŒ…å«è®®é¢˜çš„作者姓åã€åˆå¹¶è¯·æ±‚或评论。"
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
-msgstr ""
+msgstr "有人与您åŒæ—¶ç¼–辑了%{issueType} 。æ述已被更新,您需è¦é‡æ–°ä¿®æ”¹ã€‚"
msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
-msgstr ""
+msgstr "有人您åŒæ—¶ç¼–辑了这一åˆå¹¶è¯·æ±‚。请刷新页é¢æŸ¥çœ‹æ›´æ”¹ã€‚"
msgid "Something went wrong on our end"
msgstr "出错了,抱歉。"
@@ -8677,7 +10207,7 @@ msgid "Something went wrong on our end. Please try again!"
msgstr "æœåŠ¡å™¨ç«¯å‡ºçŽ°é—®é¢˜ï¼Œè¯·é‡è¯•ã€‚"
msgid "Something went wrong on our end. Please try again."
-msgstr ""
+msgstr "æœåŠ¡å™¨é”™è¯¯ã€‚ 请ç¨åŽå†è¯•ã€‚"
msgid "Something went wrong trying to change the confidentiality of this issue"
msgstr "试图改å˜è¿™ä¸ªè®®é¢˜çš„ç§å¯†æ€§æ—¶å‡ºçŽ°é”™è¯¯"
@@ -8689,13 +10219,13 @@ msgid "Something went wrong when toggling the button"
msgstr "点击按钮时出错"
msgid "Something went wrong while applying the suggestion. Please try again."
-msgstr ""
+msgstr "应用åŒæ„时出了点问题。请å†è¯•ä¸€æ¬¡ã€‚"
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr "关闭 %{issuable} 时出错。请ç¨åŽé‡è¯•"
msgid "Something went wrong while deleting the source branch. Please try again."
-msgstr ""
+msgstr "删除æºåˆ†æ”¯æ—¶å‡ºé”™ã€‚请é‡è¯•ã€‚"
msgid "Something went wrong while fetching %{listType} list"
msgstr "åœ¨èŽ·å– %{listType} 列表时出错了"
@@ -8706,6 +10236,9 @@ msgstr "获å–评论时出错。请ç¨åŽé‡è¯•ã€‚"
msgid "Something went wrong while fetching group member contributions"
msgstr "获å–群组æˆå‘˜è´¡çŒ®æ—¶å‡ºé”™"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "获å–æ­¤åˆå¹¶è¯·æ±‚的环境时出错,请ç¨åŽé‡è¯•ã€‚"
@@ -8716,7 +10249,7 @@ msgid "Something went wrong while fetching the registry list."
msgstr "拉å–注册表列表时å‘生错误。"
msgid "Something went wrong while merging this merge request. Please try again."
-msgstr ""
+msgstr "åˆå¹¶æ­¤åˆå¹¶è¯·æ±‚时出错。请é‡è¯•ã€‚"
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr "é‡æ–°å¼€å¯ %{issuable} 时出错。请ç¨åŽå†è¯•"
@@ -8733,23 +10266,26 @@ msgstr "获å–æ“作项目时出错"
msgid "Something went wrong, unable to remove project"
msgstr "删除项目时出错"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "出现错误。请é‡è¯•ã€‚"
msgid "Sorry, no epics matched your search"
-msgstr "对ä¸èµ·ï¼Œæœªæœç´¢åˆ°ä»»ä½•ç¬¦åˆæ¡ä»¶çš„å²è¯—故事"
+msgstr "对ä¸èµ·ï¼Œæœªæœç´¢åˆ°ä»»ä½•ç¬¦åˆæ¡ä»¶çš„epic"
msgid "Sorry, no projects matched your search"
msgstr "对ä¸èµ·ï¼Œæœªæœç´¢åˆ°ä»»ä½•ç¬¦åˆæ¡ä»¶çš„项目"
msgid "Sorry, your filter produced no results"
-msgstr ""
+msgstr "抱歉,您的过滤器没有产生结果"
msgid "Sort by"
msgstr "排åº"
msgid "Sort direction"
-msgstr ""
+msgstr "排åºæ–¹å‘"
msgid "SortOptions|Access level, ascending"
msgstr "访问级别,å‡åºæŽ’列"
@@ -8797,7 +10333,7 @@ msgid "SortOptions|Less weight"
msgstr "é™ä½Žæƒé‡"
msgid "SortOptions|Milestone due date"
-msgstr ""
+msgstr "里程碑截止日期"
msgid "SortOptions|Milestone due later"
msgstr "里程碑截止日期"
@@ -8830,7 +10366,7 @@ msgid "SortOptions|Oldest joined"
msgstr "最早的加入"
msgid "SortOptions|Oldest last activity"
-msgstr ""
+msgstr "最新的活动"
msgid "SortOptions|Oldest sign in"
msgstr "最早的登录"
@@ -8845,7 +10381,7 @@ msgid "SortOptions|Priority"
msgstr "优先"
msgid "SortOptions|Recent last activity"
-msgstr ""
+msgstr "最近一次活动"
msgid "SortOptions|Recent sign in"
msgstr "最近登录"
@@ -8875,7 +10411,7 @@ msgid "Source is not available"
msgstr "æºä¸å¯ç”¨"
msgid "Source project cannot be found."
-msgstr ""
+msgstr "找ä¸åˆ°æºé¡¹ç›®ã€‚"
msgid "Spam Logs"
msgstr "垃圾信æ¯æ—¥å¿—"
@@ -8883,6 +10419,9 @@ msgstr "垃圾信æ¯æ—¥å¿—"
msgid "Spam and Anti-bot Protection"
msgstr "垃圾邮件åŠé˜²æœºå™¨äººä¿æŠ¤"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "专用Runner"
@@ -8893,7 +10432,7 @@ msgid "Specify the following URL during the Runner setup:"
msgstr "在 Runner 设置时指定以下 URL:"
msgid "Squash commit message"
-msgstr ""
+msgstr "Squashæ交消æ¯"
msgid "Squash commits"
msgstr "åˆå¹¶æ交"
@@ -8923,23 +10462,35 @@ msgid "StarProject|Star"
msgstr "星标"
msgid "Starred Projects"
-msgstr "已星标项目"
+msgstr "星标项目"
msgid "Starred Projects' Activity"
-msgstr "已星标项目的活动"
+msgstr "星标项目动æ€"
msgid "Starred projects"
-msgstr "已星标项目"
+msgstr "星标项目"
+
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr "访问æŸä¸€é¡¹ç›®é¡µé¢å¹¶ç‚¹å‡»æ˜Ÿå½¢å›¾æ ‡åŽå¯ä»¥åœ¨æ­¤é¡µé¢ä¸Šæ‰¾åˆ°è¯¥é¡¹ç›®ã€‚"
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr "您还没有已加星标的项目。"
msgid "Stars"
+msgstr "星标"
+
+msgid "Start GitLab Ultimate trial"
msgstr ""
msgid "Start Web Terminal"
-msgstr ""
+msgstr "å¯åŠ¨Web终端"
msgid "Start a %{new_merge_request} with these changes"
msgstr "由此更改 %{new_merge_request}"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "开始一个评审"
@@ -8947,19 +10498,19 @@ msgid "Start and due date"
msgstr "开始和截止日期"
msgid "Start cleanup"
-msgstr ""
+msgstr "开始清ç†"
msgid "Start date"
msgstr "开始日期"
msgid "Start discussion"
-msgstr ""
+msgstr "开始讨论"
msgid "Start discussion & close %{noteable_name}"
-msgstr ""
+msgstr "开始讨论并关闭 %{noteable_name}"
msgid "Start discussion & reopen %{noteable_name}"
-msgstr ""
+msgstr "开始讨论并é‡æ–°æ‰“å¼€ %{noteable_name}"
msgid "Start the Runner!"
msgstr "å¯åŠ¨ Runner!"
@@ -8971,13 +10522,16 @@ msgid "Started"
msgstr "å·²å¯åŠ¨"
msgid "Started %{startsIn}"
+msgstr "已开始 %{startsIn}"
+
+msgid "Started asynchronous removal of all repository check states."
msgstr ""
msgid "Starting..."
-msgstr ""
+msgstr "正在å¯åŠ¨..."
msgid "Starts %{startsIn}"
-msgstr ""
+msgstr "开始 %{startsIn}"
msgid "Starts at (UTC)"
msgstr "开始于(UTC)"
@@ -8989,10 +10543,10 @@ msgid "Status"
msgstr "状æ€"
msgid "Status:"
-msgstr ""
+msgstr "状æ€:"
msgid "Stop Terminal"
-msgstr ""
+msgstr "åœæ­¢ç»ˆç«¯"
msgid "Stop environment"
msgstr "终止环境"
@@ -9010,7 +10564,7 @@ msgid "Stopping this environment is currently not possible as a deployment is in
msgstr "由于部署正在进行,目å‰æ— æ³•ç»ˆæ­¢æ­¤çŽ¯å¢ƒ"
msgid "Stopping..."
-msgstr ""
+msgstr "正在åœæ­¢â€¦â€¦"
msgid "Storage"
msgstr "存储"
@@ -9028,7 +10582,7 @@ msgid "Submit as spam"
msgstr "垃圾信æ¯ä¸¾æŠ¥"
msgid "Submit feedback"
-msgstr ""
+msgstr "æ交å馈"
msgid "Submit review"
msgstr "æ交评审"
@@ -9046,111 +10600,144 @@ msgid "Subscribe at project level"
msgstr "在项目级别订阅"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "订阅 RSS "
msgid "Subscribe to calendar"
-msgstr ""
+msgstr "订阅日历"
msgid "Subscribed"
msgstr "已订阅"
msgid "SubscriptionTable|Billing"
-msgstr ""
+msgstr "è´¦å•"
msgid "SubscriptionTable|Free"
-msgstr ""
+msgstr "å…è´¹"
msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
-msgstr ""
+msgstr "GitLab å…许您继续使用您的订阅,å³ä½¿æ‚¨å·²è¶…过您购买的最大使用人数。您必须在续订时支付这些订阅。"
msgid "SubscriptionTable|GitLab.com %{planName} %{suffix}"
-msgstr ""
+msgstr "GitLab.com %{planName} %{suffix}"
msgid "SubscriptionTable|Last invoice"
-msgstr ""
+msgstr "最åŽä¸€ä¸ªå‘票"
msgid "SubscriptionTable|Loading subscriptions"
-msgstr ""
+msgstr "加载订阅"
msgid "SubscriptionTable|Manage"
-msgstr ""
+msgstr "管ç†"
msgid "SubscriptionTable|Max seats used"
-msgstr ""
+msgstr "最大的使用人数"
msgid "SubscriptionTable|Next invoice"
-msgstr ""
+msgstr "下一张å‘票"
msgid "SubscriptionTable|Seats currently in use"
-msgstr ""
+msgstr "当å‰æ­£åœ¨ä½¿ç”¨çš„人数"
msgid "SubscriptionTable|Seats in subscription"
-msgstr ""
+msgstr "订阅中的人数"
msgid "SubscriptionTable|Seats owed"
-msgstr ""
+msgstr "欠款人数"
msgid "SubscriptionTable|Subscription end date"
-msgstr ""
+msgstr "订阅结æŸæ—¥æœŸ"
msgid "SubscriptionTable|Subscription start date"
-msgstr ""
+msgstr "订阅开始日期"
msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
-msgstr ""
+msgstr "这是 GitLab.com 团队最近一次与您è”系的时间,关于您的欠款。"
msgid "SubscriptionTable|This is the maximum number of users that have existed at the same time since this subscription started."
-msgstr ""
+msgstr "这是自订阅开始以æ¥åŒæ—¶å­˜åœ¨çš„最大用户数é‡ã€‚"
msgid "SubscriptionTable|This is the next date when the GitLab.com team is scheduled to get in contact with you to settle any outstanding balances."
-msgstr ""
+msgstr "这是 GitLab.com 团队下一次与您è”系的时间,关于您的欠款。"
msgid "SubscriptionTable|This is the number of seats you will be required to purchase if you update to a paid plan."
-msgstr ""
+msgstr "这是您更新到付费方案时需è¦è´­ä¹°çš„最大使用人数。"
msgid "SubscriptionTable|Trial"
-msgstr ""
+msgstr "试用"
msgid "SubscriptionTable|Trial end date"
-msgstr ""
+msgstr "试用结æŸæ—¥æœŸ"
msgid "SubscriptionTable|Trial start date"
-msgstr ""
+msgstr "试用开始日期"
msgid "SubscriptionTable|Upgrade"
-msgstr ""
+msgstr "å‡çº§"
msgid "SubscriptionTable|Usage"
-msgstr ""
+msgstr "使用情况"
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
+msgstr "使用次数将在æ¯å¤©ä¸­åˆ12:00进行更新。"
+
+msgid "Successfully blocked"
msgstr ""
-msgid "Suggested change"
+msgid "Successfully confirmed"
msgstr ""
-msgid "Sunday"
+msgid "Successfully deleted U2F device."
msgstr ""
-msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
msgstr ""
+msgid "Successfully unlocked"
+msgstr ""
+
+msgid "Suggested change"
+msgstr "å˜æ›´å»ºè®®"
+
+msgid "Sunday"
+msgstr "星期日"
+
+msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
+msgstr "对自定义è¯ä¹¦çš„支æŒå·²ç¦ç”¨ã€‚请è”系系统管ç†å‘˜æ¥å¯ç”¨å®ƒã€‚"
+
+msgid "Support page URL"
+msgstr "支æŒé¡µé¢ URL"
+
msgid "Switch branch/tag"
msgstr "切æ¢åˆ†æ”¯/标签"
msgid "Sync information"
msgstr "åŒæ­¥ä¿¡æ¯"
+msgid "System"
+msgstr "系统"
+
msgid "System Hooks"
msgstr "系统钩å­"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "系统信æ¯"
msgid "System default (%{default})"
-msgstr ""
+msgstr "系统默认值(%{default})"
msgid "System header and footer"
+msgstr "系统页头和页脚"
+
+msgid "System hook was successfully updated."
msgstr ""
msgid "System metrics (Custom)"
@@ -9160,10 +10747,10 @@ msgid "System metrics (Kubernetes)"
msgstr "系统指标(Kubernetes)"
msgid "Tag"
-msgstr ""
+msgstr "标签"
msgid "Tag list:"
-msgstr ""
+msgstr "标签列表:"
msgid "Tags"
msgstr "标签"
@@ -9259,10 +10846,10 @@ msgid "Templates"
msgstr "模æ¿"
msgid "Terminal"
-msgstr ""
+msgstr "终端"
msgid "Terminal for environment"
-msgstr ""
+msgstr "环境终端"
msgid "Terms of Service Agreement and Privacy Policy"
msgstr "æœåŠ¡æ¡æ¬¾å议和éšç§æ”¿ç­–"
@@ -9276,17 +10863,27 @@ msgstr "测试 SAML SSO"
msgid "Test coverage parsing"
msgstr "测试覆盖率解æž"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "ä¸å†æ˜¾ç¤º"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
-msgstr ""
+msgstr "群组“%{group_path}â€å…许您使用SSO以登录å¸æˆ·"
+
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "GitLab 中的高级全局æœç´¢åŠŸèƒ½æ˜¯ä¸€ä¸ªå¼ºå¤§ä¸”节çœæ‚¨çš„时间的æœç´¢æœåŠ¡ã€‚您å¯ä»¥æœç´¢å…¶ä»–团队的代ç ä»¥å¸®åŠ©æ‚¨å®Œå–„自己项目中的代ç ã€‚从而é¿å…创建é‡å¤çš„代ç æˆ–浪费时间。"
msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
-msgstr ""
+msgstr "CSV导出将在åŽå°åˆ›å»ºã€‚完æˆåŽï¼Œå®ƒå°†ä»¥é™„件形å¼å‘é€åˆ°<strong>%{email}</strong>。"
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr "Git LFS对象将<strong>ä¸ä¼š</strong>被åŒæ­¥ã€‚"
@@ -9300,6 +10897,12 @@ msgstr "议题跟踪用于管ç†éœ€æ±‚改进或者解决的问题。请注册或
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "在需è¦ç›¸äº’ TLS 与外部授æƒæœåŠ¡é€šä¿¡æ—¶ä½¿ç”¨çš„ X509 è¯ä¹¦ã€‚如果ä¿ç•™ä¸ºç©º, 则在访问 HTTPS æ—¶ä»ç„¶éªŒè¯æœåŠ¡å™¨è¯ä¹¦ã€‚"
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr "此项目的分支没有活跃的æµæ°´çº¿é…置。"
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "字符çªå‡ºæ˜¾ç¤ºå™¨å¸®åŠ©æ‚¨å°†ä¸»é¢˜è¡Œä¿æŒä¸º %{titleLength} 字符并将正文包装为 %{bodyLength} 以便它们在git中å¯è¯»ã€‚"
@@ -9312,24 +10915,84 @@ msgstr "与该阶段相关的事件集åˆã€‚"
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "该连接将在 %{timeout}åŽè¶…时。如仓库导入耗时超过该时间,请使用克隆/推é€ç»„åˆã€‚"
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "将此作业部署为 %{environmentLink} 并未æˆåŠŸã€‚"
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "派生关系已被删除。"
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "该导入过程将在 %{timeout}åŽè¶…时。对于需è¦é•¿äºŽè¯¥æ—¶é—´æ‰èƒ½å¯¼å…¥çš„仓库,请使用克隆/推é€ç»„åˆã€‚"
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 maximum file size allowed is %{size}."
-msgstr ""
+msgstr "å…许的最大文件大å°ä¸º %{size}。"
msgid "The maximum file size allowed is 200KB."
msgstr "文件大å°é™åˆ¶ä¸º 200KB。"
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr "å称%{entryName}在此目录中已使用。"
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "解密ç§é’¥æ‰€éœ€çš„密ç çŸ­è¯­ã€‚该项为å¯é€‰é¡¹, 并且内容被加密存储。"
@@ -9352,14 +11015,23 @@ msgid "The production stage shows the total time it takes between creating an is
msgstr "生产阶段概述了从创建一个议题到将代ç éƒ¨ç½²åˆ°ç”Ÿäº§çŽ¯å¢ƒçš„总时间。当完æˆæƒ³æ³•åˆ°éƒ¨ç½²ç”Ÿäº§çš„循环,数æ®å°†è‡ªåŠ¨æ·»åŠ åˆ°æ­¤å¤„。"
msgid "The project can be accessed by any logged in user."
-msgstr "该项目å…许已登录的用户访问。"
+msgstr "该项目å…许所有已登录到当å‰GitLabæœåŠ¡å™¨çš„用户访问。"
msgid "The project can be accessed without any authentication."
msgstr "该项目å…许任何人访问。"
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "匿å化数æ®æœé›†å·²ç¦ç”¨ã€‚该功能å¯ç”¨æ—¶ï¼ŒGitLab会è¿è¡ŒåŽå°ä»»åŠ¡ï¼Œç”Ÿæˆæ•°æ®åº“的匿å化CSV,并上传到预先设定的对象存储目录。"
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "此项目的仓库ä¸å­˜åœ¨ã€‚"
@@ -9376,7 +11048,22 @@ msgid "The review stage shows the time from creating the merge request to mergin
msgstr "审阅阶段概述了从创建åˆå¹¶è¯·æ±‚到被åˆå¹¶çš„时间。当创建第一个åˆå¹¶è¯·æ±‚åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ åˆ°æ­¤å¤„。"
msgid "The roadmap shows the progress of your epics along a timeline"
-msgstr "路线图显示了å²è¯—故事沿ç€æ—¶é—´çº¿çš„进展情况"
+msgstr "路线图显示了 epic 沿ç€æ—¶é—´çº¿çš„进展情况"
+
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "预å‘布阶段概述了从åˆå¹¶è¯·æ±‚被åˆå¹¶åˆ°éƒ¨ç½²è‡³ç”Ÿäº§çŽ¯å¢ƒçš„总时间。首次部署到生产环境åŽï¼Œæ•°æ®å°†è‡ªåŠ¨æ·»åŠ åˆ°æ­¤å¤„。"
@@ -9393,9 +11080,18 @@ msgstr "该阶段æ¯æ¡æ•°æ®æ‰€èŠ±çš„时间"
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "æ›´æ–°æ“作将在 %{number_of_minutes} 分钟åŽè¶…时。对于大型存储库,请使用clone/push组åˆã€‚"
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "使用情况检测(usage ping)å·²ç¦ç”¨ï¼Œæ— æ³•é€šè¿‡æ­¤è¡¨å•è¿›è¡Œé…置。"
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "用户映射是一个JSON文档,将å‚与项目的Google Code用户映射到他们将导入GitLab的电å­é‚®ä»¶åœ°å€å’Œç”¨æˆ·åçš„æ–¹å¼ã€‚您å¯ä»¥é€šè¿‡æ›´æ”¹ <code>:</code>å³ä¾§çš„值æ¥æ›´æ”¹æ­¤å€¼ã€‚请务必在左侧ä¿ç•™å‘¨å›´çš„åŒå¼•å·ï¼Œå…¶ä»–标点符å·ä»¥åŠç”µå­é‚®ä»¶åœ°å€æˆ–用户å。"
@@ -9406,19 +11102,19 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "中ä½æ•°æ˜¯ä¸€ä¸ªæ•°åˆ—中最中间的值。例如在 3ã€5ã€9 之间,中ä½æ•°æ˜¯ 5。在 3ã€5ã€7ã€8 之间,中ä½æ•°æ˜¯ (5 + 7)/ 2 = 6。"
msgid "There are no approvers"
-msgstr ""
+msgstr "没有核准人"
msgid "There are no archived projects yet"
msgstr "当å‰å°šæ— å·²å½’档的项目"
msgid "There are no closed issues"
-msgstr ""
+msgstr "没有已关闭的议题"
msgid "There are no closed merge requests"
-msgstr ""
+msgstr "没有已关闭的åˆå¹¶è¯·æ±‚"
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
-msgstr ""
+msgstr "没有为此GitLab实例设置自定义项目模æ¿ã€‚它们是从GitLab的管ç†åŒºåŸŸå¯ç”¨çš„。请与您的GitLab实例管ç†å‘˜è”系以设置自定义项目模æ¿ã€‚"
msgid "There are no issues to show"
msgstr "当å‰æ— è®®é¢˜"
@@ -9427,13 +11123,13 @@ msgid "There are no labels yet"
msgstr "ç›®å‰æ— æ ‡è®°"
msgid "There are no open issues"
-msgstr ""
+msgstr "没有未关闭的议题"
msgid "There are no open merge requests"
-msgstr ""
+msgstr "没有未关闭的åˆå¹¶è¯·æ±‚"
msgid "There are no packages yet"
-msgstr ""
+msgstr "还没有包"
msgid "There are no projects shared with this group yet"
msgstr "当å‰å°šæ— åˆ†äº«ç»™è¯¥ç¾¤ç»„的项目"
@@ -9450,9 +11146,21 @@ msgstr "添加待办事项时出现错误"
msgid "There was an error deleting the todo."
msgstr "删除待办事项时出现错误。"
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "加载用户活动日历时出错。"
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr "ä¿å­˜å˜æ›´æ—¶å‡ºé”™ã€‚"
+
msgid "There was an error saving your notification settings."
msgstr "ä¿å­˜é€šçŸ¥è®¾ç½®æ—¶å‘生错误。"
@@ -9468,9 +11176,12 @@ msgstr "订阅此标记时出错。"
msgid "There was an error when unsubscribing from this label."
msgstr "å–消订阅此标记时出错。"
-msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
msgstr ""
+msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
+msgstr "这些现有的议题具有类似的标题。在那里评论å¯èƒ½æ›´å¥½ï¼Œè€Œä¸æ˜¯åˆ›å»ºå¦ä¸€ä¸ªç±»ä¼¼çš„问题。"
+
msgid "They can be managed using the %{link}."
msgstr "他们å¯ä»¥é€šè¿‡ %{link} 进行管ç†ã€‚"
@@ -9478,14 +11189,17 @@ msgid "Third party offers"
msgstr "第三方优惠"
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
-msgstr ""
+msgstr "这个 %{issuable} 已被é”定。åªæœ‰ <strong>项目æˆå‘˜</strong> å¯ä»¥å‘表评论。"
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
-msgstr ""
+msgstr "因为 %{reason}无法显示 %{viewer} 。您å¯ä»¥æ”¹ä¸º %{options}。"
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "æ­¤GitLab实例尚未æ供任何共享Runner。管ç†å‘˜å¯ä»¥åœ¨ç®¡ç†åŒºåŸŸä¸­æ³¨å†Œå…±äº«Runner。"
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "这个应用程åºæ˜¯ç”± %{link_to_owner} 创建的。"
@@ -9498,26 +11212,44 @@ msgstr "此看æ¿èŒƒå›´ç¼©å°äº†"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "自您开始编辑åŽ, 此分支已更改。您想创建一个新的分支å—?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr "æ­¤æ交是åˆå¹¶è¯·æ±‚ %{link_to_merge_request} 的一部分。此处创建的评论将在该åˆå¹¶è¯·æ±‚的上下文中创建。"
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr "æ­¤æ交使用 <strong>已验è¯</strong> çš„ç­¾å进行签å,并且已验è¯æ交者的电å­é‚®ä»¶å±žäºŽåŒä¸€ç”¨æˆ·ã€‚"
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr "æ­¤æ交使用其他用户的已验è¯ç­¾å进行签å。"
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr "æ­¤æ交已使用ç»è¿‡éªŒè¯çš„ç­¾å进行签å,但<strong>未验è¯</strong>çš„æ交者电å­é‚®ä»¶å±žäºŽåŒä¸€ç”¨æˆ·ã€‚"
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr "æ­¤æ交使用 <strong>未ç»éªŒè¯çš„</strong> ç­¾å进行签å。"
+
msgid "This container registry has been scheduled for deletion."
msgstr "此容器注册表已安排删除。"
msgid "This date is after the due date, so this epic won't appear in the roadmap."
-msgstr "此日期在截止日期之åŽï¼Œå› æ­¤è¯¥å²è¯—故事ä¸ä¼šå‡ºçŽ°åœ¨è·¯çº¿å›¾ä¸­ã€‚"
+msgstr "此日期在截止日期之åŽï¼Œå› æ­¤è¯¥ epic ä¸ä¼šå‡ºçŽ°åœ¨è·¯çº¿å›¾ä¸­ã€‚"
msgid "This date is before the start date, so this epic won't appear in the roadmap."
-msgstr "此日期在开始日期之å‰ï¼Œå› æ­¤è¯¥å²è¯—故事ä¸ä¼šå‡ºçŽ°åœ¨è·¯çº¿å›¾ä¸­ã€‚"
+msgstr "此日期在开始日期之å‰ï¼Œå› æ­¤è¯¥ epic ä¸ä¼šå‡ºçŽ°åœ¨è·¯çº¿å›¾ä¸­ã€‚"
msgid "This diff is collapsed."
msgstr "此差异已折å ã€‚"
msgid "This diff was suppressed by a .gitattributes entry."
-msgstr ""
+msgstr "此差异由.gitattributes 抑制。"
msgid "This directory"
msgstr "当å‰ç›®å½•"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
-msgstr ""
+msgstr "此域å未ç»è¿‡éªŒè¯ã€‚在å¯ç”¨è®¿é—®æƒé™ä¹‹å‰ï¼Œæ‚¨éœ€è¦éªŒè¯æ‰€æœ‰æƒã€‚"
+
+msgid "This field is required."
+msgstr "该字段是必填字段。"
msgid "This group"
msgstr "当å‰ç¾¤ç»„"
@@ -9534,6 +11266,9 @@ msgstr "这是一个将在%{remainingTime}åŽè¿è¡Œçš„延时作业。"
msgid "This is the author's first Merge Request to this project."
msgstr "这是作者为当å‰é¡¹ç›®è´¡çŒ®çš„第一个åˆå¹¶è¯·æ±‚。"
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "当å‰è®®é¢˜ä¸ºæœºå¯†è®®é¢˜"
@@ -9582,12 +11317,18 @@ msgstr "这项工作正在创建一个 %{environmentLink}的部署。"
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "作业挂起中,等待进入队列"
-msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgid "This job is performing tasks that must complete before it can start"
msgstr ""
-msgid "This job is stuck because you don't have any active runners that can run this job."
+msgid "This job is preparing to start"
msgstr ""
+msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
+msgstr "此作业被å¡ä½äº†ï¼Œå› ä¸ºæ‚¨æ²¡æœ‰ä»»ä½•åœ¨çº¿æ´»è·ƒçš„在è¿è¡Œå™¨ï¼Œå¹¶åˆ†é…了这些标签:"
+
+msgid "This job is stuck because you don't have any active runners that can run this job."
+msgstr "由于您没有任何å¯ä»¥è¿è¡Œæ­¤ä½œä¸šçš„活跃è¿è¡Œå™¨ï¼Œå› æ­¤ä½œä¸šå¡ä½äº†ã€‚"
+
msgid "This job is the most recent deployment to %{link}."
msgstr "此作业最近部署到 %{link}。"
@@ -9595,7 +11336,7 @@ msgid "This job requires a manual action"
msgstr "作业需手动æ“作"
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
-msgstr ""
+msgstr "该作业将在计时器完æˆåŽè‡ªåŠ¨è¿è¡Œã€‚它们通常用于生产环境的增é‡éƒ¨ç½²ã€‚未计划时,它会转æ¢ä¸ºæ‰‹åŠ¨æ“作。"
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "在创建一个空的仓库或导入现有仓库之å‰ï¼Œå°†æ— æ³•æŽ¨é€ä»£ç ã€‚"
@@ -9603,26 +11344,32 @@ msgstr "在创建一个空的仓库或导入现有仓库之å‰ï¼Œå°†æ— æ³•æŽ¨é€
msgid "This merge request is locked."
msgstr "æ­¤åˆå¹¶è¯·æ±‚å·²é”定。"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr "æ­¤åˆå¹¶è¯·æ±‚必须由这些群组的æˆå‘˜æ ¸å‡†ã€‚您å¯ä»¥é€šè¿‡è®¾ç½®è‡ªå·±çš„核准人列表æ¥è¦†ç›–项目设置。"
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr "æ­¤åˆå¹¶è¯·æ±‚必须得到这些用户的核准。您å¯ä»¥é€šè¿‡è®¾ç½®è‡ªå·±çš„核准人列表æ¥è¦†ç›–项目设置。"
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "由于您没有当å‰åˆ†æ”¯çš„写入æƒé™ï¼Œå› æ­¤ç¦ç”¨æ­¤é€‰é¡¹"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "有未暂存更改时此选项会被ç¦ç”¨ã€‚"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "此页é¢ä¸å¯ç”¨ï¼Œæ‚¨æ— æƒè·¨é¡¹ç›®é˜…读相关信æ¯ã€‚"
msgid "This page will be removed in a future release."
msgstr "此页é¢å°†åœ¨å°†æ¥çš„版本中删除。"
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
-msgstr ""
+msgstr "æ­¤æµæ°´çº¿ä½¿ç”¨äº† %{strongStart}Auto DevOps 预先定义的并已å¯ç”¨çš„ CI/CD é…置。%{strongEnd}"
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
-msgstr ""
+msgstr "æ­¤æµæ°´çº¿ä½¿ç”¨äº† <b>Auto DevOps 预先定义的并已å¯ç”¨çš„ CI/CD é…置。</b>"
msgid "This project"
msgstr "当å‰é¡¹ç›®"
@@ -9639,6 +11386,9 @@ msgstr "此项目未å¯ç”¨è´¦å•ã€‚è¦åˆ›å»ºç¾¤é›†ï¼Œè¯· <a href=%{linkToBilling
msgid "This repository"
msgstr "当å‰ä»“库"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "æ­¤Runner仅在å—ä¿æŠ¤åˆ†æ”¯ä¸Šè§¦å‘çš„æµæ°´çº¿ä¸Šè¿è¡Œ"
@@ -9648,10 +11398,10 @@ msgstr "当å‰è®¾ç½®å¯åœ¨æ¯ä¸ªé¡¹ç›®ä¸­è¿›è¡Œæ›´æ”¹è¦†ç›–。"
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "此设置将更新用于生æˆç§æœ‰æ交电å­é‚®ä»¶çš„主机å。 %{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "此代ç å·®å¼‚无法显示,因为它太大了。"
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgstr "当低于项目定义的超时时,此超时将优先,并å¯æŽ¥å—英文语å¥ï¼Œå¦‚“1 hourâ€ã€‚默认å•ä½ä¸ºç§’。"
+
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9664,6 +11414,9 @@ msgid "This user will be the author of all events in the activity feed that are
msgstr "此用户将æˆä¸ºæ´»åŠ¨æµä¸­æ‰€æœ‰äº‹ä»¶çš„作者,例如创建新分支或者推é€æ–°æ交到现有分支。在创建或é‡æ–°æŒ‡å®šæ—¶æ‚¨ä»…å¯å°†è‡ªå·±æŒ‡å®šä¸ºé•œåƒç”¨æˆ·ã€‚"
msgid "This will redirect you to an external sign in page."
+msgstr "这会将您é‡å®šå‘到外部登录页é¢ã€‚"
+
+msgid "This will remove the fork relationship to source project"
msgstr ""
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
@@ -9732,9 +11485,6 @@ msgstr " %s 个月å‰"
msgid "Timeago|%s months remaining"
msgstr "剩余 %s 月"
-msgid "Timeago|%s seconds ago"
-msgstr "%s 秒å‰"
-
msgid "Timeago|%s seconds remaining"
msgstr "剩余 %s 秒"
@@ -9772,7 +11522,7 @@ msgid "Timeago|1 month ago"
msgstr "1个月å‰"
msgid "Timeago|1 month remaining"
-msgstr "剩余 1 个月"
+msgstr "剩余1个月"
msgid "Timeago|1 week ago"
msgstr "1星期å‰"
@@ -9799,7 +11549,7 @@ msgid "Timeago|in %s minutes"
msgstr " %s 分钟åŽ"
msgid "Timeago|in %s months"
-msgstr " %s 个月åŽ"
+msgstr " %s个月åŽ"
msgid "Timeago|in %s seconds"
msgstr " %s 秒åŽ"
@@ -9855,16 +11605,16 @@ msgid "Title"
msgstr "标题"
msgid "Titles and Filenames"
-msgstr ""
+msgstr "标题和文件å称"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
-msgstr ""
+msgstr "è¦%{link_to_help}到您的域å,请将上述密钥添加到DNSé…置中的TXT记录。"
msgid "To GitLab"
msgstr "到GitLab"
msgid "To access this domain create a new DNS record"
-msgstr ""
+msgstr "è¦è®¿é—®æ­¤åŸŸï¼Œè¯·åˆ›å»ºæ–°çš„DNS记录"
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr "è¦æ·»åŠ ä¸€ä¸ª SSH 密钥, æ‚¨éœ€è¦ %{generate_link_start} 生æˆä¸€ä¸ª %{link_end} 或使用一个 %{existing_link_start} 现有的 key%{link_end}。"
@@ -9909,13 +11659,13 @@ msgid "To import an SVN repository, check out %{svn_link}."
msgstr "è¦å¯¼å…¥SVN仓库,请查看 %{svn_link}。"
msgid "To keep this project going, create a new issue"
-msgstr ""
+msgstr "为了ä¿æŒè¿™ä¸ªé¡¹ç›®çš„进行,请创建一个新的议题"
msgid "To keep this project going, create a new merge request"
-msgstr ""
+msgstr "为了ä¿æŒè¿™ä¸ªé¡¹ç›®çš„进行,请创建一个新的åˆå¹¶è¯·æ±‚"
msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
-msgstr ""
+msgstr "è¦å°†Sentry链接到GitLab,请输入您的Sentry URLå’ŒAuth Token。"
msgid "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."
msgstr "如需将整个GitLab项目从å¦ä¸€ä¸ªGitLabæœåŠ¡å™¨ç§»åŠ¨æˆ–å¤åˆ¶åˆ°æ­¤æœåŠ¡å™¨ï¼Œè¯·è®¿é—®åŽŸé¡¹ç›®çš„设置页é¢ï¼Œç”Ÿæˆå¯¼å‡ºæ–‡ä»¶ï¼Œç„¶åŽåœ¨æ­¤å¤„上载。"
@@ -9924,13 +11674,13 @@ msgid "To only use CI/CD features for an external repository, choose <strong>CI/
msgstr "è¦ä»…为外部仓库使用CI / CD功能时,请选择</strong>使用外部仓库è¿è¡ŒCI/CD<strong>。"
msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
-msgstr ""
+msgstr "请将%{link} 页é¢è¿žæŽ¥åˆ°æ‚¨çš„ Jaeger æœåŠ¡å™¨ï¼Œä»¥ä¾¿åœ¨ GitLab 打开并轻æ¾æŸ¥çœ‹è·Ÿè¸ª"
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
-msgstr ""
+msgstr "为了ä¿æŒæ€§èƒ½ï¼Œä»…显示文件中的 <strong>%{display_size}/%{real_size}</strong>。"
msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
-msgstr ""
+msgstr "è¦ä»Žæ‰‹åŠ¨é…置的PrometheusæœåŠ¡æŽ¥æ”¶è­¦æŠ¥ï¼Œè¯·å°†ä»¥ä¸‹URL和授æƒå¯†é’¥æ·»åŠ åˆ°Prometheus webhooké…置文件中。了解更多关于 %{linkStart}é…ç½®Prometheus%{linkEnd} 将警报å‘é€åˆ°GitLab。"
msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
msgstr "通过Azure,Okta,Onelogin,Ping Identity或自定义SAML 2.0等身份验è¯ç¨‹åºä¸ºæ‚¨çš„群组设置SAML身份验è¯ï¼š"
@@ -9939,7 +11689,7 @@ msgid "To start serving your jobs you can add Runners to your group"
msgstr "è¦å¼€å§‹æ‰§è¡Œä»»åŠ¡ï¼Œè¯·æŠŠRunner加到群组中"
msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
-msgstr ""
+msgstr "è¦å¼€å§‹ä½¿ç”¨ä½œä¸šï¼Œæ‚¨å¯ä»¥å‘项目添加特定的è¿è¡Œå™¨æˆ–使用共享的è¿è¡Œå™¨"
msgid "To this GitLab instance"
msgstr "转至此GitLab实例"
@@ -9948,10 +11698,10 @@ msgid "To validate your GitLab CI configurations, go to 'CI/CD → Pipelines' in
msgstr "如需验è¯GitLab CI设置,请访问当å‰é¡¹ç›®çš„'CI/CD → æµæ°´çº¿',然åŽç‚¹å‡»'CI Lint'按钮。"
msgid "To view the roadmap, add a start or due date to one of your epics in this group or its subgroups. In the months view, only epics in the past month, current month, and next 5 months are shown."
-msgstr "如需查看路线图,请将计划的开始或结æŸæ—¥æœŸæ·»åŠ åˆ°å½“å‰ç¾¤ç»„或其å­ç»„中的æŸä¸ªå²è¯—故事。在月视图中,åªæ˜¾ç¤ºä¸Šä¸ªæœˆï¼Œæœ¬æœˆä»¥åŠæŽ¥ä¸‹æ¥5个月的å²è¯—故事."
+msgstr "如需查看路线图,请将计划的开始或结æŸæ—¥æœŸæ·»åŠ åˆ°å½“å‰ç¾¤ç»„或其å­ç»„中的æŸä¸ª epic。在月视图中,åªæ˜¾ç¤ºä¸Šä¸ªæœˆï¼Œæœ¬æœˆä»¥åŠæŽ¥ä¸‹æ¥5个月的 epic."
msgid "To widen your search, change or remove filters above"
-msgstr ""
+msgstr "è¦æ‰©å¤§æœç´¢èŒƒå›´ï¼Œè¯·æ›´æ”¹æˆ–删除上é¢çš„过滤器"
msgid "To widen your search, change or remove filters."
msgstr "需è¦æ‰©å¤§æœç´¢èŒƒå›´ï¼Œè¯·æ›´æ”¹æˆ–移除过滤æ¡ä»¶ã€‚"
@@ -9962,6 +11712,9 @@ msgstr "今日"
msgid "Todo"
msgstr "待办事项"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "待办事项"
@@ -9969,13 +11722,13 @@ msgid "Toggle Sidebar"
msgstr "切æ¢ä¾§è¾¹æ "
msgid "Toggle comments for this file"
-msgstr ""
+msgstr "切æ¢æ­¤æ–‡ä»¶çš„评论"
msgid "Toggle commit description"
msgstr "切æ¢æ交æè¿°"
msgid "Toggle commit list"
-msgstr ""
+msgstr "切æ¢æ交列表"
msgid "Toggle discussion"
msgstr "开关讨论"
@@ -10001,6 +11754,12 @@ msgstr "明日"
msgid "Too many changes to show."
msgstr "è¦æ˜¾ç¤ºçš„å˜æ›´å¤ªå¤šã€‚"
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "贡献总计"
@@ -10025,6 +11784,9 @@ msgstr "在ä¸åŒé¡¹ç›®å’Œé‡Œç¨‹ç¢‘中跟踪具有åŒä¸€ä¸»é¢˜çš„议题组"
msgid "Track time with quick actions"
msgstr "使用快æ·æ“作æ¥ç»Ÿè®¡å·¥æ—¶"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "树形视图"
@@ -10037,15 +11799,30 @@ msgstr "触å‘é•œåƒæ›´æ–°çš„æµæ°´çº¿"
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "从上游仓库更新分支或标记时触å‘æµæ°´çº¿ã€‚如上游仓库更新频ç¹ï¼ŒCI Runnerçš„è´Ÿè·å¯èƒ½ä¼šå¤§å¤§å¢žåŠ ã€‚åªæœ‰æ˜Žç¡®CI Runner的处ç†èƒ½åŠ›èƒ½å¤Ÿæ‰¿å—这样的负è·æ—¶ï¼Œæ‰åº”å¯ç”¨æ­¤åŠŸèƒ½ã€‚"
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "触å‘此手动æ“作"
msgid "Trigger token:"
-msgstr ""
+msgstr "触å‘令牌:"
msgid "Trigger variables:"
+msgstr "触å‘å˜é‡ï¼š"
+
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
msgstr ""
+msgid "Triggerer"
+msgstr "触å‘器"
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "触å‘器å¯ä»¥é€šè¿‡ API 调用使特定的分支或标记被é‡æ–°æž„建,这些 token å¯ä»£è¡¨ä¸Žå…¶å…³è”的用户(包括该用户对项目的访问æƒé™ï¼‰"
@@ -10056,13 +11833,13 @@ msgid "Try again"
msgstr "请é‡è¯•"
msgid "Try again?"
-msgstr ""
+msgstr "é‡è¯•ï¼Ÿ"
msgid "Try all GitLab has to offer for 30 days."
msgstr "30天内体验GitLab的所有功能。"
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
-msgstr ""
+msgstr "å°è¯•ä¸Žæ‚¨çš„设备通信。请将其æ’入并立å³æŒ‰ä¸‹ä¸Šçš„按钮。"
msgid "Turn on Service Desk"
msgstr "å¯ç”¨æœåŠ¡å°"
@@ -10070,6 +11847,9 @@ msgstr "å¯ç”¨æœåŠ¡å°"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "åŒé‡è®¤è¯"
@@ -10077,29 +11857,44 @@ msgid "Type"
msgstr "类型"
msgid "URL"
+msgstr "URL"
+
+msgid "Unable to connect to server: %{error}"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr "无法加载差异。 %{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "由于\"%{reason}\"的原因,您暂时ä¸èƒ½è¿›å…¥é…置了SAML 的群组"
msgid "Unable to update this epic at this time."
-msgstr "当å‰æ— æ³•æ›´æ–°æ­¤å²è¯—故事。"
+msgstr "当å‰æ— æ³•æ›´æ–°æ­¤ epic。"
-msgid "Unblock"
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
msgstr ""
+msgid "Unblock"
+msgstr "解除é”定"
+
msgid "Undo"
msgstr "撤消"
msgid "Unfortunately, your email message to GitLab could not be processed."
-msgstr ""
+msgstr "很é—憾,您å‘é€ç»™GitLab的电å­é‚®ä»¶æ— æ³•å¤„ç†ã€‚"
msgid "Unknown"
msgstr "未知的"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "解é”"
@@ -10146,7 +11941,7 @@ msgid "Unsubscribe at project level"
msgstr "在项目级别å–消订阅"
msgid "Unsubscribe from %{type}"
-msgstr ""
+msgstr "å–消订阅 %{type}"
msgid "Unverified"
msgstr "未验è¯"
@@ -10160,19 +11955,37 @@ msgstr "å³å°†å¼€å§‹"
msgid "Update"
msgstr "æ›´æ–°"
+msgid "Update approvers"
+msgstr "更新核准人"
+
msgid "Update failed"
-msgstr ""
+msgstr "更新失败"
msgid "Update now"
msgstr "ç«‹å³æ›´æ–°"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "更新您的群组å称ã€è¯´æ˜Žã€å¤´åƒä»¥åŠå¯è§æ€§ã€‚"
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr "已更新"
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "更新中"
msgid "Upgrade plan to unlock Canary Deployments feature"
+msgstr "å‡çº§åˆ°å¯ä½¿ç”¨é‡‘ä¸é›€éƒ¨ç½²åŠŸèƒ½çš„方案"
+
+msgid "Upgrade your plan"
msgstr ""
msgid "Upgrade your plan to activate Advanced Global Search."
@@ -10190,42 +12003,57 @@ msgstr "å‡çº§æ‚¨çš„订阅计划以å¯ç”¨è®®é¢˜æƒé‡ã€‚"
msgid "Upgrade your plan to improve Issue boards."
msgstr "å‡çº§æ‚¨çš„订阅计划以使用增强的议题看æ¿ã€‚"
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "在这里上传 <code>GoogleCodeProjectHosting.json</code>:"
msgid "Upload CSV file"
-msgstr ""
+msgstr "上传CSV文件"
msgid "Upload New File"
msgstr "上传新文件"
msgid "Upload a certificate for your domain with all intermediates"
-msgstr ""
+msgstr "通过存储介质上传您的域åè¯ä¹¦"
msgid "Upload a private key for your certificate"
-msgstr ""
+msgstr "为您的è¯ä¹¦ä¸Šä¼ ä¸€ä¸ªç§é’¥"
msgid "Upload file"
msgstr "上传文件"
msgid "Upload object map"
-msgstr ""
+msgstr "上传对象图"
msgid "UploadLink|click to upload"
msgstr "点击上传"
-msgid "Upstream"
+msgid "Uploaded on"
msgstr ""
+msgid "Uploads"
+msgstr ""
+
+msgid "Upstream"
+msgstr "上游"
+
msgid "Upvotes"
msgstr "顶"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "使用情况检测(usage ping)未å¯ç”¨"
msgid "Usage statistics"
msgstr "使用情况统计"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "使用<code>%{native_redirect_uri}</code>进行本地测试"
@@ -10248,26 +12076,62 @@ msgid "Use your global notification setting"
msgstr "使用全局通知设置"
msgid "Use your smart card to authenticate with the LDAP server."
-msgstr ""
+msgstr "使用智能å¡å¯¹LDAPæœåŠ¡å™¨è¿›è¡Œèº«ä»½éªŒè¯ã€‚"
msgid "Used by members to sign in to your group in GitLab"
msgstr "ä¾›æˆå‘˜ç™»å½•æ‚¨çš„GitLab群组"
msgid "Used to help configure your identity provider"
+msgstr "用于帮助é…置您的身份æ供者"
+
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
msgstr ""
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "用户世代表仅在å¯ç”¨ %{usage_ping_link_start}使用情况检测(usage ping)%{usage_ping_link_end} 时显示。"
+msgid "User OAuth applications"
+msgstr "用户的 OAuth 应用程åº"
+
msgid "User Settings"
msgstr "用户设置"
msgid "User and IP Rate Limits"
msgstr "用户和IP频率é™åˆ¶"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "用户映射"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "活动"
@@ -10281,22 +12145,22 @@ msgid "UserProfile|Edit profile"
msgstr "编辑个人资料"
msgid "UserProfile|Explore public groups to find projects to contribute to."
-msgstr ""
+msgstr "æµè§ˆå…¬å…±ç¾¤ç»„以查找è¦è´¡çŒ®çš„项目。"
msgid "UserProfile|Groups"
msgstr "群组"
msgid "UserProfile|Groups are the best way to manage projects and members."
-msgstr ""
+msgstr "群组是管ç†é¡¹ç›®å’Œæˆå‘˜çš„最佳方å¼ã€‚"
msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
-msgstr ""
+msgstr "加入或创建一个群组,通过评论议题或æ交åˆå¹¶è¯·æ±‚æ¥å¼€å§‹è´¡çŒ®ï¼"
msgid "UserProfile|Most Recent Activity"
msgstr "最新活动"
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "未找到代ç ç‰‡æ®µ"
msgid "UserProfile|Overview"
msgstr "概览"
@@ -10311,19 +12175,19 @@ msgid "UserProfile|Snippets"
msgstr "代ç ç‰‡æ®µ"
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
-msgstr ""
+msgstr "GitLab 中的代ç ç‰‡æ®µå¯ä»¥æ˜¯ç§æœ‰çš„ã€å†…部或公开。"
msgid "UserProfile|Subscribe"
msgstr "关注"
msgid "UserProfile|This user doesn't have any personal projects"
-msgstr ""
+msgstr "用户没有任何个人项目"
msgid "UserProfile|This user has a private profile"
msgstr "此用户具有éžå…¬å¼€ä¸ªäººèµ„料设置"
msgid "UserProfile|This user hasn't contributed to any projects"
-msgstr ""
+msgstr "此用户未对任何项目åšå‡ºè´¡çŒ®"
msgid "UserProfile|View all"
msgstr "查看全部"
@@ -10332,30 +12196,51 @@ msgid "UserProfile|View user in admin area"
msgstr "在管ç†åŒºåŸŸä¸­æŸ¥çœ‹ç”¨æˆ·"
msgid "UserProfile|You can create a group for several dependent projects."
-msgstr ""
+msgstr "您å¯ä»¥ä¸ºå¤šä¸ªä¾èµ–项目创建一个群组。"
msgid "UserProfile|You haven't created any personal projects."
-msgstr ""
+msgstr "您还没有创建任何个人项目"
msgid "UserProfile|You haven't created any snippets."
-msgstr ""
+msgstr "您还没有创建任何代ç ç‰‡æ®µ"
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
-msgstr ""
+msgstr "您的项目å¯ä»¥æ ¹æ®æ‚¨çš„选择公开ã€å†…部或ç§ä¸‹æ供。"
msgid "Users"
msgstr "用户"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
+msgstr "请求访问的用户"
+
+msgid "Users were successfully added."
msgstr ""
-msgid "Validate"
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
+msgid "Validate"
+msgstr "验è¯"
+
msgid "Validate your GitLab CI configuration file"
+msgstr "验è¯æ‚¨çš„GitLab CIé…置文件"
+
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
msgstr ""
msgid "Value"
+msgstr "值"
+
+msgid "Variables"
msgstr ""
msgid "Various container registry settings."
@@ -10365,7 +12250,7 @@ msgid "Various email settings."
msgstr "电å­é‚®ä»¶ç›¸å…³è®¾ç½®"
msgid "Various localization settings."
-msgstr ""
+msgstr "å„ç§æœ¬åœ°åŒ–设置。"
msgid "Various settings that affect GitLab performance."
msgstr "å½±å“GitLab性能相关设置。"
@@ -10374,7 +12259,7 @@ msgid "Verification information"
msgstr "验è¯ä¿¡æ¯"
msgid "Verification status"
-msgstr ""
+msgstr "验è¯çŠ¶æ€"
msgid "Verified"
msgstr "已验è¯"
@@ -10382,23 +12267,20 @@ msgstr "已验è¯"
msgid "Version"
msgstr "版本"
-msgid "View %{alerts}"
-msgstr "查看 %{alerts}"
-
msgid "View app"
msgstr "查看应用程åº"
msgid "View deployment"
-msgstr ""
+msgstr "查看部署"
msgid "View details: %{details_url}"
-msgstr ""
+msgstr "查看详细信æ¯: %{details_url}"
-msgid "View documentation"
-msgstr "查看文档"
+msgid "View eligible approvers"
+msgstr "查看符åˆæ¡ä»¶çš„核准人"
msgid "View epics list"
-msgstr "查看å²è¯—故事列表"
+msgstr "查看 epic 列表"
msgid "View file @ "
msgstr "æµè§ˆæ–‡ä»¶ @ "
@@ -10406,12 +12288,15 @@ msgstr "æµè§ˆæ–‡ä»¶ @ "
msgid "View group labels"
msgstr "查看群组标记"
-msgid "View issue"
-msgstr "查看议题"
+msgid "View in Sentry"
+msgstr "在Sentry中查看"
msgid "View it on GitLab"
msgstr "使用GitLab查看"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "查看作业"
@@ -10434,7 +12319,7 @@ msgid "View the documentation"
msgstr "查看文档"
msgid "Viewing commit"
-msgstr ""
+msgstr "查看æ交"
msgid "Visibility and access controls"
msgstr "å¯è§æ€§ä¸Žè®¿é—®æŽ§åˆ¶"
@@ -10445,6 +12330,9 @@ msgstr "å¯è§æ€§çº§åˆ«"
msgid "Visibility level:"
msgstr "å¯è§æ€§çº§åˆ«:"
+msgid "Visibility, project features, permissions"
+msgstr "å¯è§æ€§ï¼Œé¡¹ç›®åŠŸèƒ½ï¼Œæƒé™"
+
msgid "Visibility:"
msgstr "å¯è§æ€§ï¼š"
@@ -10461,10 +12349,10 @@ msgid "VisibilityLevel|Unknown"
msgstr "未知"
msgid "Vulnerability Chart"
-msgstr ""
+msgstr "æ¼æ´žå›¾è¡¨"
msgid "Vulnerability List"
-msgstr ""
+msgstr "æ¼æ´žæ¸…å•"
msgid "Vulnerability|Class"
msgstr "类型"
@@ -10481,17 +12369,23 @@ msgstr "文件"
msgid "Vulnerability|Identifiers"
msgstr "标识"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "实例"
msgid "Vulnerability|Links"
msgstr "链接"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "项目"
msgid "Vulnerability|Report Type"
-msgstr ""
+msgstr "报告类型"
msgid "Vulnerability|Severity"
msgstr "严é‡æ€§"
@@ -10500,19 +12394,19 @@ msgid "Want to see the data? Please ask an administrator for access."
msgstr "æƒé™ä¸è¶³ã€‚如需查看相关数æ®ï¼Œè¯·å‘管ç†å‘˜ç”³è¯·æƒé™ã€‚"
msgid "We can't find an epic that matches what you are looking for."
-msgstr ""
+msgstr "我们找ä¸åˆ°ç¬¦åˆæ‚¨è¦æ±‚çš„epic。"
msgid "We can't find an issue that matches what you are looking for."
-msgstr ""
+msgstr "我们找ä¸åˆ°ä¸Žæ‚¨è¦æŸ¥æ‰¾çš„内容相匹é…的议题。"
msgid "We could not determine the path to remove the epic"
-msgstr ""
+msgstr "我们无法确定删除epic的路径"
msgid "We could not determine the path to remove the issue"
-msgstr ""
+msgstr "我们无法确定删除议题的路径"
msgid "We couldn't find any results matching"
-msgstr ""
+msgstr "我们找ä¸åˆ°ä»»ä½•åŒ¹é…的结果"
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "我们在%{humanized_resource_name}检测到潜在滥用行为。请输入此reCAPTCHA验è¯ç å¹¶ç»§ç»­ã€‚"
@@ -10521,20 +12415,26 @@ msgid "We don't have enough data to show this stage."
msgstr "该阶段的数æ®ä¸è¶³ï¼Œæ— æ³•æ˜¾ç¤ºã€‚"
msgid "We heard back from your U2F device. You have been authenticated."
-msgstr ""
+msgstr "我们从您的U2F设备收到了回å¤ã€‚您已通过身份验è¯ã€‚"
msgid "We want to be sure it is you, please confirm you are not a robot."
-msgstr "我们è¦ç¡®å®šä½ æ˜¯ä¸æ˜¯æœºå™¨äººã€‚"
+msgstr "我们è¦ç¡®å®šæ‚¨æ˜¯ä¸æ˜¯æœºå™¨äººã€‚"
msgid "Web IDE"
msgstr "Web IDE"
msgid "Web Terminal"
-msgstr ""
+msgstr "Web终端"
msgid "Web terminal"
msgstr "Web终端"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "如果有新的推é€æˆ–新的议题,Webhook将自动触å‘您设置URL。 您å¯ä»¥é…ç½® Webhook æ¥ç›‘å¬ç‰¹å®šäº‹ä»¶ï¼Œå¦‚推é€ã€è®®é¢˜æˆ–åˆå¹¶è¯·æ±‚。 群组 Webhook 适用于群组中的所有项目,便于您在整个群组中标准化webhook功能。"
@@ -10553,11 +12453,18 @@ msgstr "当Runner被é”定时,ä¸èƒ½å°†å…¶åˆ†é…给其他项目"
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "该项å¯ç”¨åŽï¼Œç”¨æˆ·åœ¨æŽ¥å—æ¡æ¬¾è¢«å‰å°†ä¸èƒ½ä½¿ç”¨GitLab。"
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "å°†URLä¿ç•™ä¸ºç©ºç™½æ—¶ï¼Œä»å¯æŒ‡å®šåˆ†ç±»æ ‡ç­¾ï¼Œè€Œæ— éœ€ç¦ç”¨è·¨é¡¹ç›®åŠŸèƒ½æˆ–执行外部授æƒæ£€æŸ¥ã€‚"
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
-msgstr ""
+msgstr "当:"
msgid "Who can see this group?"
msgstr "哪些人å¯ä»¥çœ‹åˆ°è¿™ä¸ªç¾¤ç»„?"
@@ -10568,6 +12475,9 @@ msgstr "哪些人å¯ä»¥çœ‹åˆ°è¿™ä¸ªç¾¤ç»„?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "克隆您的 wiki"
@@ -10679,6 +12589,9 @@ msgstr "创建页é¢"
msgid "Wiki|Create page"
msgstr "创建页é¢"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "修改页é¢"
@@ -10697,11 +12610,14 @@ msgstr "页é¢ç‰ˆæœ¬"
msgid "Wiki|Pages"
msgstr "页é¢"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "Wiki 页é¢"
msgid "Will deploy to"
-msgstr ""
+msgstr "将部署到"
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
msgstr "通过贡献分æžï¼Œæ‚¨å¯ä»¥ä»Žæ€»ä½“上了解您的组织åŠå…¶æˆå‘˜çš„议题〠åˆå¹¶è¯·æ±‚和推é€æ´»åŠ¨çš„情况。"
@@ -10709,10 +12625,22 @@ msgstr "通过贡献分æžï¼Œæ‚¨å¯ä»¥ä»Žæ€»ä½“上了解您的组织åŠå…¶æˆå‘˜
msgid "Withdraw Access Request"
msgstr "å–消æƒé™ç”³è¯·"
-msgid "Write a comment or drag your files here…"
+msgid "Workflow Help"
msgstr ""
+msgid "Write"
+msgstr "写"
+
+msgid "Write a comment or drag your files here…"
+msgstr "在此写评论或拖动您的文件到这里…"
+
+msgid "Write access allowed"
+msgstr "å·²å…许写入æƒé™"
+
msgid "Write milestone description..."
+msgstr "写入里程碑æè¿°..."
+
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
msgid "Yes"
@@ -10728,7 +12656,7 @@ msgid "Yesterday"
msgstr "昨天"
msgid "You"
-msgstr ""
+msgstr "您"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr "您是一å管ç†å‘˜ï¼Œè¿™æ„味ç€æŽˆäºˆå¯¹ <strong>%{client_name}</strong> 访问æƒé™å°†å…许他们作为管ç†å‘˜ä¸ŽGitLab进行交互。请谨慎æ“作。"
@@ -10745,11 +12673,14 @@ msgstr "å³å°†åˆ é™¤ä¸Žæºé¡¹ç›® %{forked_from_project} 的派生关系。确定
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "å°†è¦æŠŠ %{project_full_name} 转移给å¦ä¸€ä¸ªæ‰€æœ‰è€…。确定执行此æ“作?"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "当å‰æ­£åœ¨è®¿é—®åªè¯» GitLab 实例。"
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
-msgstr ""
+msgstr "您收到此消æ¯æ˜¯å› ä¸ºæ‚¨æ˜¯ %{url} çš„GitLab管ç†å‘˜ã€‚"
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
msgstr "您å¯ä»¥ %{linkStart}查看BLOB%{linkEnd} 代替。"
@@ -10760,6 +12691,15 @@ msgstr "您也å¯ä»¥é€šè¿‡å‘½ä»¤è¡Œæ¥åˆ›å»ºæ–°é¡¹ç›®ã€‚"
msgid "You can also star a label to make it a priority label."
msgstr "å¯ä»¥é€šè¿‡ä¸ºæ ‡è®°è®¾ç½®æ˜Ÿæ ‡æ¥æ高其优先级。"
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr "您å¯ä»¥åœ¨ %{lint_link_start}CI Lint%{lint_link_end} 中测试 %{gitlab_ci_yml}"
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr "您还å¯ä»¥æŒ‰ç…§ä»¥ä¸‹è¯´æ˜Žä»Žè®¡ç®—机中上传现有文件。"
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr "您å¯ä»¥ä½¿ç”¨ä»¥ä¸‹é€‰é¡¹ä¹‹ä¸€ç›´æŽ¥åœ¨ GitLab 中创建文件。"
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "您å¯ä»¥è¯·æ±‚加入这些群组,以便为群组项目åšå‡ºè´¡çŒ®ã€‚"
@@ -10769,6 +12709,12 @@ msgstr "å¯ä»¥è½»æ¾åœ°åœ¨Kubernetes集群上安装Runner。 %{link_to_help_page
msgid "You can move around the graph by using the arrow keys."
msgstr "å¯ä»¥ä½¿ç”¨æ–¹å‘键移动图形。"
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ æ–‡ä»¶"
@@ -10776,6 +12722,9 @@ msgid "You can only edit files when you are on a branch"
msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šç¼–辑文件"
msgid "You can only merge once the items above are resolved"
+msgstr "åªæœ‰è§£å†³äº†ä¸Šè¿°é¡¹ç›®åŽæ‰èƒ½åˆå¹¶"
+
+msgid "You can only transfer the project to namespaces you manage."
msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
@@ -10787,39 +12736,72 @@ msgstr "您å¯ä»¥å°†ä½œä¸šè®¾ç½®ä¸ºä»…使用具有特定标签的Runners。请ä½
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "您也å¯ä»¥é€šè¿‡%{linkStart}Lint%{linkEnd}测试.gitlab-ci.yml."
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "您ä¸èƒ½å†™å…¥åªè¯»çš„æ¬¡è¦ GitLab Geo 实例。请改用%{link_to_primary_node}。"
msgid "You cannot write to this read-only GitLab instance."
msgstr "您ä¸èƒ½å†™å…¥è¿™ä¸ªåªè¯»çš„ GitLab 实例。"
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "您当å‰å°šæœªè®¢é˜…任何计划"
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
-msgstr ""
+msgstr "您无æƒè¿è¡ŒWeb终端。请è”系项目管ç†å‘˜ã€‚"
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr "您没有更改LDAP组åŒæ­¥ä¸­è®¾ç½®çš„相应æƒé™ã€‚"
msgid "You don't have any applications"
-msgstr "你没有任何应用程åº"
+msgstr "您没有任何应用程åº"
msgid "You don't have any authorized applications"
msgstr "您没有任何授æƒçš„应用"
msgid "You don't have any deployments right now."
+msgstr "您现在没有任何部署。"
+
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
msgstr ""
msgid "You have no permissions"
msgstr "没有æƒé™"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr "你还没有添加任何核准人。 请添加用户或群组。"
+
msgid "You have reached your project limit"
msgstr "您已达到项目数é‡é™åˆ¶"
-msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
msgstr ""
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr "您还å¯ä»¥é€šè¿‡åœ¨å˜é‡é”®å‰é¢åŠ ä¸Š<code>K8S_SECRET_</code>æ¥æ·»åŠ å¯ç”¨äºŽæ­£åœ¨è¿è¡Œçš„应用程åºçš„å˜é‡ã€‚"
+
msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr "您必须接å—我们的æœåŠ¡æ¡æ¬¾å’Œéšç§æ”¿ç­–æ‰èƒ½æ³¨å†Œå¸æˆ·"
@@ -10829,7 +12811,7 @@ msgstr "必须拥有维护者æƒé™æ‰èƒ½å¼ºåˆ¶åˆ é™¤é”"
msgid "You need a different license to enable FileLocks feature"
msgstr "需è¦ä½¿ç”¨ä¸Žå½“å‰ä¸åŒçš„许å¯(license) æ‰èƒ½å¯ç”¨FileLocks功能"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10839,12 +12821,24 @@ msgid "You need permission."
msgstr "需è¦ç›¸å…³çš„æƒé™ã€‚"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
+msgstr "在设置U2F设备之å‰ï¼Œæ‚¨éœ€è¦æ³¨å†ŒåŒå› ç´ èº«ä»½éªŒè¯åº”用程åºã€‚"
+
+msgid "You need to specify both an Access Token and a Host URL."
msgstr ""
-msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
msgstr ""
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
+msgstr "您将丢失对此文件所åšçš„所有更改。此æ“作无法撤消。"
+
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr "您将丢失在此项目中所åšçš„所有未暂存的修改,此æ“作无法撤消。"
+
+msgid "You will need to update your local repositories to point to the new location."
msgstr ""
msgid "You will not get any notifications via email"
@@ -10874,6 +12868,15 @@ msgstr "在您的个人资料中添加SSH密钥之å‰ï¼Œæ‚¨ä¸èƒ½é€šè¿‡SSHæ¥æ‹‰
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "需è¦ä½¿ç”¨ä¸åŒçš„分支æ‰èƒ½è¿›è¡Œæœ‰æ•ˆçš„比较。"
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr "您åªèƒ½åœ¨ä¿¡æ¯æµä¸­çœ‹åˆ° %{startTag}其他活动%{endTag} 。è¦æ·»åŠ è¯„论,请切æ¢åˆ°ä»¥ä¸‹é€‰é¡¹ä¹‹ä¸€ã€‚"
+
msgid "You're receiving this email because %{reason}."
msgstr "您收到此电å­é‚®ä»¶ï¼Œæ˜¯å› ä¸º%{reason}。"
@@ -10887,7 +12890,7 @@ msgid "YouTube"
msgstr "YouTube"
msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
-msgstr ""
+msgstr "您的会è¯å¼€å‘指数概述了从功能角度使用GitLabçš„æ–¹å¼ã€‚查看您与其他组织的比较,å‘现您未使用的功能,并通过åšå®¢æ–‡ç« å’Œç™½çš®ä¹¦äº†è§£æœ€ä½³å®žè·µã€‚"
msgid "Your Groups"
msgstr "您的群组"
@@ -10899,22 +12902,31 @@ msgid "Your Projects (default)"
msgstr "您的项目 (默认值)"
msgid "Your Projects' Activity"
-msgstr "您的项目活动"
+msgstr "您的项目动æ€"
msgid "Your Todos"
msgstr "您的待办事项"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
+msgstr "您的 U2F 设备需è¦è®¾ç½®ã€‚请将将其æ’入,并点击左边的按钮。"
+
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr ""
msgid "Your applications (%{size})"
-msgstr "你的应用程åº(%{size})"
+msgstr "您的应用程åº(%{size})"
msgid "Your authorized applications"
msgstr "您已授æƒçš„应用"
msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
-msgstr ""
+msgstr "您的æµè§ˆå™¨ä¸æ”¯æŒU2F。请使用Google Chromeæ¡Œé¢ç‰ˆï¼ˆ41或更高版本)。"
msgid "Your changes can be committed to %{branch_name} because a merge request is open."
msgstr "åˆå¹¶è¯·æ±‚已开å¯ï¼Œå¯ä»¥æ交å˜æ›´åˆ°%{branch_name}。"
@@ -10925,47 +12937,71 @@ msgstr "更改已æ交。æ交 %{commitId} %{commitStats}"
msgid "Your changes have been saved"
msgstr "您的更改已ä¿å­˜"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "您的评论将ä¸ä¼šå…¬å¼€æ˜¾ç¤ºã€‚"
-msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
msgstr ""
+msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
+msgstr "您的设备已æˆåŠŸè®¾ç½®ï¼è¯·ç»™å®ƒå‘½å并将其注册到GitLabæœåŠ¡å™¨ã€‚"
+
msgid "Your groups"
msgstr "您的群组"
msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "您的议题正在导入。完æˆåŽï¼Œæ‚¨å°†æ”¶åˆ°ä¸€å°ç¡®è®¤ç”µå­é‚®ä»¶ã€‚"
msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "您的议题将在åŽå°å¯¼å…¥ã€‚完æˆåŽï¼Œæ‚¨å°†æ”¶åˆ°ä¸€å°ç¡®è®¤ç”µå­é‚®ä»¶ã€‚"
msgid "Your name"
msgstr "您的åå­—"
-msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgid "Your new SCIM token"
msgstr ""
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr "您的被é™åˆ¶ä¸ºæœ€å¤§ %{limit} 个项目ï¼è¯·ä¸Žæ‚¨çš„管ç†å‘˜è”系以增加它"
+
msgid "Your projects"
msgstr "您的项目"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "已删除的用户"
+msgid "added %{created_at_timeago}"
+msgstr "创建于 %{created_at_timeago}"
+
msgid "ago"
msgstr "å‰"
msgid "allowed to fail"
-msgstr ""
+msgstr "å…许失败"
msgid "among other things"
msgstr "åŠå…¶ä»–功能"
-msgid "assign yourself"
-msgstr "分é…给自己"
+msgid "at"
+msgstr "于"
msgid "attach a new file"
-msgstr ""
+msgstr "附加一个文件"
+
+msgid "authored"
+msgstr "编写于"
msgid "branch name"
msgstr "分支å称"
@@ -10991,14 +13027,34 @@ msgstr "ciReport|%{namespace} å— %{vulnerability} å½±å“。"
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "还有%{remainingPackagesCount} 个"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] "%{reportType} %{status} 检测到 %{dismissedCount} 个已忽略的æ¼æ´ž"
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] "%{reportType} %{status} æºåˆ†æ”¯æ£€æµ‹åˆ° %{dismissedCount} 个已忽略的æ¼æ´ž"
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType}%{status} 检测到%{fixedCount}个安全æ¼æ´žå·²ä¿®å¤ã€‚"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} 检测到 %{fixedCount} 个已修å¤çš„å’Œ %{dismissedCount} 个已忽略的æ¼æ´ž"
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType}%{status} 检测到%{newCount}个新的安全æ¼æ´žã€‚"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} 检测到 %{newCount} 个新增的ã€%{fixedCount} 个已修å¤çš„å’Œ %{dismissedCount} 个已忽略的æ¼æ´ž"
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr "%{reportType} %{status} 检测到 %{newCount} 个新增的和 %{dismissedCount} 个已忽略的æ¼æ´ž"
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr "%{reportType} %{status} æºåˆ†æ”¯æ£€æµ‹åˆ° %{newCount} 个新增的和 %{dismissedCount} 个已忽略的æ¼æ´ž"
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType}%{status}检测到%{newCount}个新的安全æ¼æ´žå’Œ%{fixedCount}个已修å¤æ¼æ´ž"
@@ -11028,6 +13084,9 @@ msgstr "%{reportType} 加载中"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType}:å‘生一个错误"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(加载结果时出错)"
@@ -11037,6 +13096,15 @@ msgstr "(加载中)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(正在加载,加载结果时出错)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "ç±»"
@@ -11046,17 +13114,29 @@ msgstr "代ç è´¨é‡"
msgid "ciReport|Confidence"
msgstr "置信水平"
+msgid "ciReport|Container Scanning"
+msgstr "容器扫æ"
+
msgid "ciReport|Container scanning"
msgstr "容器安全扫æ"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "容器扫æå¯ä»¥æ£€æµ‹Dockeré•œåƒä¸­ä¸­å·²çŸ¥çš„安全æ¼æ´žã€‚"
+msgid "ciReport|Create issue"
+msgstr "创建议题"
+
+msgid "ciReport|Create merge request"
+msgstr "创建åˆå¹¶è¯·æ±‚"
+
+msgid "ciReport|Created %{eventType}"
+msgstr "已创建 %{eventType}"
+
msgid "ciReport|DAST"
msgstr "DAST"
msgid "ciReport|Dependency Scanning"
-msgstr ""
+msgstr "ä¾èµ–扫æ"
msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
msgstr "ä¾èµ–项扫æå¯ä»¥æ£€æµ‹æºä»£ç ä¾èµ–项中已知的æ¼æ´žã€‚"
@@ -11074,10 +13154,10 @@ msgid "ciReport|Dismissed by"
msgstr "忽略æ“作æ¥è‡ª"
msgid "ciReport|Download and apply the patch to fix this vulnerability."
-msgstr ""
+msgstr "下载并应用此修补程åºä»¥ä¿®å¤æ­¤æ¼æ´žã€‚"
msgid "ciReport|Download patch"
-msgstr ""
+msgstr "下载补ä¸"
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgstr "动æ€åº”用程åºå®‰å…¨æµ‹è¯•ï¼ˆDAST)å¯æ£€æµ‹Web应用程åºä¸­çš„已知æ¼æ´žã€‚"
@@ -11094,9 +13174,18 @@ msgstr "已修å¤:"
msgid "ciReport|Identifiers"
msgstr "标识符"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr "通过创建åˆå¹¶è¯·æ±‚æ¥å®žçŽ°æ­¤è§£å†³æ–¹æ¡ˆ"
+
msgid "ciReport|Instances"
msgstr "实例"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr "通过创建议题æ¥è°ƒæŸ¥æ­¤æ¼æ´ž"
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "了解有关安全报告(Alpha)的更多信æ¯ã€‚"
@@ -11138,9 +13227,6 @@ msgstr "性能指标无å˜åŒ–"
msgid "ciReport|Performance metrics"
msgstr "性能指标"
-msgid "ciReport|Revert dismissal"
-msgstr "å–消忽略"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11162,6 +13248,9 @@ msgstr "é™æ€åº”用安全测试(SAST)å¯ä»¥æ£€æµ‹æºä»£ç ä¸­å·²çŸ¥çš„æ¼æ´
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "创建议题时å‘生错误。请å†è¯•ä¸€æ¬¡ã€‚"
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr "创建åˆå¹¶è¯·æ±‚时出错。请å†è¯•ä¸€æ¬¡ã€‚"
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "忽略此æ¼æ´žæ—¶å‘生错误。请å†è¯•ä¸€æ¬¡ã€‚"
@@ -11180,9 +13269,18 @@ msgstr "加载ä¾èµ–项扫æ报告时出错"
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "å–消忽略æ“作时å‘生错误。请å†è¯•ä¸€æ¬¡ã€‚"
+msgid "ciReport|Undo dismiss"
+msgstr "撤消解除"
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "å°† %{name} 从 %{version} å‡çº§åˆ° %{fixed}。"
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "已用于 %{packagesString} 和 %{lastPackage}"
@@ -11193,10 +13291,31 @@ msgstr "查看完整报告"
msgid "ciReport|on pipeline"
msgstr "于æµæ°´çº¿"
-msgid "command line instructions"
-msgstr "命令行指å—"
-
msgid "commented on %{link_to_project}"
+msgstr "评论 %{link_to_project}"
+
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11219,17 +13338,20 @@ msgid_plural "days"
msgstr[0] "天"
msgid "deleted"
-msgstr ""
+msgstr "已删除"
msgid "deploy token"
msgstr "部署令牌"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "å·²ç¦ç”¨"
msgid "discussion resolved"
msgid_plural "discussions resolved"
-msgstr[0] ""
+msgstr[0] "讨论已解决"
msgid "done"
msgstr "完æˆ"
@@ -11242,17 +13364,32 @@ msgid "enabled"
msgstr "å·²å¯ç”¨"
msgid "epic"
-msgstr ""
+msgstr "epic"
msgid "error"
-msgstr ""
+msgstr "错误"
msgid "error code:"
-msgstr ""
+msgstr "错误代ç ï¼š"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "最åŽä¸€æ¬¡%{slash_command} 命令将更新预计时间。"
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "对于这个项目"
@@ -11260,22 +13397,16 @@ msgid "from"
msgstr "æ¥è‡ª"
msgid "group"
-msgstr ""
-
-msgid "help"
-msgstr "帮助"
+msgstr "群组"
msgid "here"
msgstr "此处"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
msgid "image diff"
-msgstr ""
+msgstr "图åƒå·®å¼‚"
msgid "import flow"
msgstr "导入æµç¨‹"
@@ -11284,13 +13415,13 @@ msgid "importing"
msgstr "导入中"
msgid "in group %{link_to_group}"
-msgstr ""
+msgstr "在 %{link_to_group} 群组"
msgid "in project %{link_to_project}"
-msgstr ""
+msgstr "在 %{link_to_project} 项目"
msgid "index"
-msgstr ""
+msgstr "索引"
msgid "instance completed"
msgid_plural "instances completed"
@@ -11306,25 +13437,25 @@ msgid "is not a valid X509 certificate."
msgstr "ä¸æ˜¯æœ‰æ•ˆçš„X509è¯ä¹¦ã€‚"
msgid "is out of the hierarchy of the Group owning the template"
-msgstr ""
+msgstr "ä¸å±žäºŽæ‹¥æœ‰æ¨¡æ¿çš„群组层次结构"
msgid "issue"
-msgstr ""
+msgstr "议题"
msgid "issue boards"
msgstr "议题看æ¿"
msgid "it is stored externally"
-msgstr ""
+msgstr "它是外部存储的"
msgid "it is stored in LFS"
-msgstr ""
+msgstr "它存储在LFS中"
msgid "it is too large"
-msgstr ""
+msgstr "它太大了"
msgid "latest"
-msgstr ""
+msgstr "最新"
msgid "latest deployment"
msgstr "最新部署"
@@ -11332,6 +13463,9 @@ msgstr "最新部署"
msgid "latest version"
msgstr "最新版本"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "许å¯è¯ç®¡ç†"
@@ -11339,26 +13473,29 @@ msgid "locked by %{path_lock_user_name} %{created_at}"
msgstr "被 %{path_lock_user_name} 在 %{created_at} é”定"
msgid "manual"
-msgstr ""
+msgstr "手动"
msgid "merge request"
msgid_plural "merge requests"
msgstr[0] "åˆå¹¶è¯·æ±‚"
msgid "missing"
-msgstr ""
+msgstr "丢失"
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
-msgstr ""
+msgstr "%{commitCount} 和 %{mergeCommitCount} 将被添加到 %{targetBranch}。"
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr "%{commitCount} 将添加到 %{targetBranch}。"
msgid "mrWidgetCommitsAdded|1 merge commit"
-msgstr ""
+msgstr "1个åˆå¹¶æ交"
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr "请æ¢å¤æ­¤åˆ†æ”¯æˆ–使用其他的 %{missingBranchName} 分支"
msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
-msgstr ""
+msgstr "%{link_start}了解更多关于解决冲çª%{link_end}"
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr "%{metricsLinkStart} 内存 %{metricsLinkEnd} å ç”¨ %{emphasisStart} ä¸‹é™ %{emphasisEnd},从 %{memoryFrom}MB 到 %{memoryTo}MB"
@@ -11376,10 +13513,10 @@ msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr "å…许具有åˆå¹¶åˆ°ç›®æ ‡åˆ†æ”¯æƒé™çš„æˆå‘˜æ交"
msgid "mrWidget|An error occurred while removing your approval."
-msgstr ""
+msgstr "删除您的核准时å‘生错误。"
msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
-msgstr ""
+msgstr "检索此åˆå¹¶è¯·æ±‚的核准数æ®æ—¶å‘生错误。"
msgid "mrWidget|An error occurred while submitting your approval."
msgstr "æ交批准时å‘生错误。"
@@ -11387,6 +13524,9 @@ msgstr "æ交批准时å‘生错误。"
msgid "mrWidget|Approve"
msgstr "批准"
+msgid "mrWidget|Approve additionally"
+msgstr "å¦å¤–核准"
+
msgid "mrWidget|Approved by"
msgstr "批准人:"
@@ -11418,7 +13558,7 @@ msgid "mrWidget|Create an issue to resolve them later"
msgstr "创建议题以便åŽç»­å¤„ç†"
msgid "mrWidget|Delete source branch"
-msgstr ""
+msgstr "删除æºåˆ†æ”¯"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "部署统计信æ¯å½“å‰ä¸å¯ç”¨"
@@ -11459,17 +13599,20 @@ msgstr "本地åˆå¹¶"
msgid "mrWidget|Merge request approved"
msgstr "åˆå¹¶è¯·æ±‚已批准"
+msgid "mrWidget|Merge request approved."
+msgstr "åˆå¹¶è¯·æ±‚已核准。"
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "åˆå¹¶è¯·æ±‚已被批准; ä»å¯ä»¥æ·»åŠ é¢å¤–批准"
msgid "mrWidget|Merged by"
msgstr "åˆå¹¶è€…:"
-msgid "mrWidget|No Approval required"
-msgstr "无需批准"
+msgid "mrWidget|No approval required"
+msgstr "无需核准"
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "批准已满足åˆå¹¶è¦æ±‚; 您ä»ç„¶å¯ä»¥ç»§ç»­æ·»åŠ é¢å¤–批准"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr "无需核准;但您ä»ç„¶å¯ä»¥æ ¸å‡†"
msgid "mrWidget|Open in Web IDE"
msgstr "在Web IDE中打开"
@@ -11518,6 +13661,9 @@ msgstr "还原"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "通过新的åˆå¹¶è¯·æ±‚中还原此åˆå¹¶è¯·æ±‚"
+msgid "mrWidget|Revoke approval"
+msgstr "撤销核准"
+
msgid "mrWidget|Set by"
msgstr "设置:"
@@ -11537,19 +13683,19 @@ msgid "mrWidget|The source branch HEAD has recently changed. Please reload the p
msgstr "æºåˆ†æ”¯HEAD最近已更改。请在é‡æ–°åˆå¹¶ä¹‹å‰é‡æ–°åŠ è½½é¡µé¢å¹¶æŸ¥çœ‹æ›´æ”¹"
msgid "mrWidget|The source branch has been deleted"
-msgstr ""
+msgstr "æºåˆ†æ”¯å·²è¢«åˆ é™¤"
msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
msgstr "æºåˆ†æ”¯æ˜¯æ¯”目标分支%{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd}"
msgid "mrWidget|The source branch is being deleted"
-msgstr ""
+msgstr "æºåˆ†æ”¯æ­£åœ¨åˆ é™¤"
msgid "mrWidget|The source branch will be deleted"
-msgstr ""
+msgstr "æºåˆ†æ”¯å°†è¢«åˆ é™¤"
msgid "mrWidget|The source branch will not be deleted"
-msgstr ""
+msgstr "æºåˆ†æ”¯å°†ä¸ä¼šè¢«åˆ é™¤"
msgid "mrWidget|There are merge conflicts"
msgstr "存在åˆå¹¶å†²çª"
@@ -11558,7 +13704,7 @@ msgid "mrWidget|There are unresolved discussions. Please resolve these discussio
msgstr "存在尚未解决的讨论。请先解决这些讨论。"
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
-msgstr ""
+msgstr "此功能是从目标分支åˆå¹¶åˆ°æºåˆ†æ”¯çš„更改。您无法使用此功能,因为æºåˆ†æ”¯å—到ä¿æŠ¤ã€‚"
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "该åˆå¹¶è¯·æ±‚未能自动åˆå¹¶"
@@ -11573,7 +13719,7 @@ msgid "mrWidget|You are not allowed to edit this project directly. Please fork t
msgstr "ä¸å…许直接编辑此项目。请派生(fork)åŽè¿›è¡Œæ›´æ”¹ã€‚"
msgid "mrWidget|You can delete the source branch now"
-msgstr ""
+msgstr "您现在å¯ä»¥åˆ é™¤æºåˆ†æ”¯"
msgid "mrWidget|You can merge this merge request manually using the"
msgstr "æ­¤åˆå¹¶è¯·æ±‚å¯ä»¥æ‰‹åŠ¨åˆå¹¶ï¼Œè¯·ä½¿ç”¨ä»¥ä¸‹"
@@ -11597,19 +13743,19 @@ msgid "new merge request"
msgstr "新建åˆå¹¶è¯·æ±‚"
msgid "none"
-msgstr ""
+msgstr "æ— "
msgid "notification emails"
msgstr "通知邮件"
msgid "nounSeries|%{firstItem} and %{lastItem}"
-msgstr ""
+msgstr "%{firstItem} 和 %{lastItem}"
msgid "nounSeries|%{item}, %{nextItem}"
-msgstr ""
+msgstr "%{item}, %{nextItem}"
msgid "nounSeries|%{item}, and %{lastItem}"
-msgstr ""
+msgstr "%{item},和 %{lastItem}"
msgid "or"
msgstr "或"
@@ -11628,21 +13774,28 @@ msgstr "密ç "
msgid "personal access token"
msgstr "个人访问令牌"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
-msgstr ""
+msgstr "ç§æœ‰"
msgid "private key does not match certificate."
msgstr "ç§é’¥ä¸Žè¯ä¹¦ä¸åŒ¹é…。"
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "项目"
msgid "quick actions"
-msgstr ""
+msgstr "å¿«æ·æ“作"
msgid "register"
-msgstr ""
+msgstr "注册"
msgid "remaining"
msgstr "剩余"
@@ -11657,29 +13810,56 @@ msgid "remove weight"
msgstr "移除æƒé‡"
msgid "rendered diff"
-msgstr ""
+msgstr "渲染差异"
msgid "reply"
msgid_plural "replies"
msgstr[0] "æ¡å›žå¤"
+msgid "reset it."
+msgstr ""
+
msgid "score"
+msgstr "分"
+
+msgid "security Reports|There was an error creating the merge request"
+msgstr "创建åˆå¹¶è¯·æ±‚时出错"
+
+msgid "severity|Critical"
msgstr ""
-msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgid "severity|High"
msgstr ""
-msgid "show less"
+msgid "severity|Info"
msgstr ""
-msgid "sign in"
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
msgstr ""
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr "应该从高于%{group_name} 群组继承æˆå‘˜èº«ä»½%{access}"
+
+msgid "show less"
+msgstr "显示较少"
+
+msgid "sign in"
+msgstr "登录"
+
msgid "source"
msgstr "æº"
msgid "source diff"
-msgstr ""
+msgstr "æºå·®å¼‚"
msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr "%{slash_command} 将会更新消耗的总时长。"
@@ -11688,13 +13868,16 @@ msgid "started"
msgstr "已开始"
msgid "stuck"
+msgstr "å¡ä½"
+
+msgid "success"
msgstr ""
msgid "syntax is correct"
-msgstr ""
+msgstr "语法是正确的"
msgid "syntax is incorrect"
-msgstr ""
+msgstr "语法ä¸æ­£ç¡®"
msgid "this document"
msgstr "此文档"
@@ -11703,10 +13886,10 @@ msgid "to help your contributors communicate effectively!"
msgstr "帮助您的贡献者进行有效沟通ï¼"
msgid "triggered"
-msgstr ""
+msgstr "已触å‘"
msgid "updated"
-msgstr ""
+msgstr "已更新"
msgid "username"
msgstr "用户å"
@@ -11715,13 +13898,13 @@ msgid "uses Kubernetes clusters to deploy your code!"
msgstr "使用 Kubernetes 集群æ¥éƒ¨ç½²ä»£ç ï¼"
msgid "verify ownership"
-msgstr ""
+msgstr "验è¯æ‰€æœ‰æƒ"
msgid "view it on GitLab"
msgstr "使用GitLab查看"
msgid "view the blob"
-msgstr ""
+msgstr "查看blob"
msgid "with %{additions} additions, %{deletions} deletions."
msgstr "å…± %{additions} æ¡æ–°å¢ž, %{deletions} æ¡åˆ é™¤."
@@ -11731,5 +13914,5 @@ msgid_plural "within %d minutes "
msgstr[0] "在 %d 分钟内 "
msgid "yaml invalid"
-msgstr ""
+msgstr "yaml无效"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index a6dfd5f6139..8882be94797 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: zh-HK\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 08:13\n"
+"PO-Revision-Date: 2019-04-12 12:36\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr ""
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr ""
@@ -38,8 +47,9 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr ""
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -111,12 +121,25 @@ msgstr ""
msgid "%{count} %{alerts}"
msgstr ""
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr ""
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
@@ -131,7 +154,7 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -140,6 +163,12 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -149,6 +178,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr ""
@@ -161,6 +193,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -198,6 +239,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -207,6 +251,9 @@ msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] "%{count}%{type} 個變更"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] ""
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] ""
@@ -263,6 +317,9 @@ msgid "1 user"
msgid_plural "%d users"
msgstr[0] ""
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr ""
@@ -272,6 +329,15 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -287,6 +353,9 @@ msgstr ""
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr ""
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr ""
@@ -320,8 +389,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "相關æŒçºŒé›†æˆçš„圖åƒé›†åˆ"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
@@ -332,18 +413,39 @@ msgstr ""
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr ""
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -374,12 +476,18 @@ msgstr ""
msgid "Access Tokens"
msgstr ""
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr ""
@@ -389,6 +497,12 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "啟用"
@@ -419,36 +533,63 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr ""
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr ""
msgid "Add new directory"
msgstr "添加新目錄"
-msgid "Add projects"
-msgstr ""
-
msgid "Add reaction"
msgstr ""
@@ -470,18 +611,27 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
msgid "Additional text"
msgstr ""
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr ""
msgid "Admin Overview"
msgstr ""
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -518,6 +668,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -599,12 +752,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] ""
@@ -615,6 +777,9 @@ msgstr ""
msgid "All"
msgstr "全部"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr ""
@@ -624,15 +789,33 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -645,6 +828,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -663,6 +852,9 @@ msgstr ""
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr ""
@@ -684,9 +876,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr ""
@@ -696,6 +885,15 @@ msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
@@ -708,18 +906,27 @@ msgstr ""
msgid "An error occurred while adding approver"
msgstr ""
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr ""
@@ -732,6 +939,9 @@ msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr ""
@@ -744,6 +954,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -759,6 +972,9 @@ msgstr ""
msgid "An error occurred while loading commit signatures"
msgstr ""
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr ""
@@ -801,12 +1017,18 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr ""
msgid "An error occurred while unsubscribing to notifications."
msgstr ""
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr ""
@@ -864,6 +1086,9 @@ msgstr ""
msgid "Analytics"
msgstr ""
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -876,15 +1101,33 @@ msgstr ""
msgid "Any Label"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -897,6 +1140,40 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -912,15 +1189,30 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "確定è¦åˆªé™¤æ­¤æµæ°´ç·šè¨ˆåŠƒå—Žï¼Ÿ"
@@ -933,15 +1225,24 @@ msgstr ""
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -954,6 +1255,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr "確定è¦é‡ç½®è¨»å†Šä»¤ç‰Œå—Žï¼Ÿ"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "確定è¦é‡ç½®å¥åº·æª¢æŸ¥ä»¤ç‰Œå—Žï¼Ÿ"
@@ -1008,9 +1312,6 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
-msgid "Assigned to :name"
-msgstr ""
-
msgid "Assigned to me"
msgstr ""
@@ -1026,6 +1327,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1041,10 +1345,10 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
+msgid "Authentication Log"
msgstr ""
-msgid "Authentication Log"
+msgid "Authentication failed: %{error_message}"
msgstr ""
msgid "Authentication log"
@@ -1053,6 +1357,12 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1071,6 +1381,9 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr ""
@@ -1110,16 +1423,10 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
+msgid "Automatically marked as default internal user"
msgstr ""
-msgid "Automatically marked as default internal user"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
msgid "Automatically resolved"
@@ -1245,6 +1552,12 @@ msgstr ""
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1482,6 +1795,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "ç€è¦½ç›®éŒ„"
@@ -1503,9 +1822,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "作者:"
@@ -1524,6 +1852,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr ""
@@ -1557,6 +1888,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1572,6 +1906,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1587,6 +1924,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr ""
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1599,19 +1939,34 @@ msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr ""
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
+msgid "Change Weight"
msgstr ""
-msgid "Change Weight"
+msgid "Change path"
msgstr ""
msgid "Change permissions"
@@ -1638,6 +1993,9 @@ msgstr "還原"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1650,6 +2008,12 @@ msgstr "統計圖"
msgid "Chat"
msgstr ""
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
@@ -1677,9 +2041,6 @@ msgstr ""
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr ""
-msgid "Choose File ..."
-msgstr ""
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
@@ -1701,25 +2062,25 @@ msgstr ""
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr ""
-msgid "Choose file..."
+msgid "Choose file…"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
+msgid "Choose what content you want to see on a group’s overview page"
msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr ""
-msgid "Choose which repositories you want to import."
+msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr ""
-msgid "Choose which shards you wish to synchronize to this secondary node."
+msgid "Choose your merge method, set up a default merge request description template."
msgstr ""
msgid "CiStatusLabel|canceled"
@@ -1746,6 +2107,9 @@ msgstr "已通éŽä½†æœ‰è­¦å‘Š"
msgid "CiStatusLabel|pending"
msgstr "等待中"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "已跳éŽ"
@@ -1779,6 +2143,9 @@ msgstr "已通éŽ"
msgid "CiStatusText|pending"
msgstr "等待中"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "已跳éŽ"
@@ -1794,6 +2161,9 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
@@ -1806,6 +2176,9 @@ msgstr ""
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr ""
@@ -1815,12 +2188,18 @@ msgstr ""
msgid "CiVariable|Search environments"
msgstr ""
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -1878,6 +2257,9 @@ msgstr ""
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1893,9 +2275,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr ""
@@ -1911,15 +2290,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
@@ -1935,16 +2314,13 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -1983,6 +2359,12 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1992,19 +2374,19 @@ msgstr ""
msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr ""
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
@@ -2052,7 +2434,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2079,13 +2461,13 @@ msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Ingress IP Address"
+msgid "ClusterIntegration|Ingress Endpoint"
msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
@@ -2094,15 +2476,18 @@ msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install Prometheus"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
msgid "ClusterIntegration|Installing"
msgstr ""
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
@@ -2130,7 +2515,7 @@ msgstr ""
msgid "ClusterIntegration|Knative Domain Name:"
msgstr ""
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2142,9 +2527,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -2187,9 +2569,6 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|More information"
-msgstr ""
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
@@ -2202,10 +2581,10 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
-msgid "ClusterIntegration|Note:"
+msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Number of nodes"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
msgstr ""
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
@@ -2214,15 +2593,12 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr ""
-msgid "ClusterIntegration|Project namespace"
-msgstr ""
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
@@ -2253,7 +2629,7 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2289,16 +2665,16 @@ msgstr ""
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr ""
-msgid "ClusterIntegration|Service token"
+msgid "ClusterIntegration|Service Token"
msgstr ""
-msgid "ClusterIntegration|Show"
+msgid "ClusterIntegration|Service token is required."
msgstr ""
-msgid "ClusterIntegration|Something went wrong on our end."
+msgid "ClusterIntegration|Show"
msgstr ""
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
@@ -2310,7 +2686,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -2319,10 +2695,16 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Token"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
msgstr ""
msgid "ClusterIntegration|Upgrade"
@@ -2361,9 +2743,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|check the pricing here"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -2373,6 +2752,9 @@ msgstr ""
msgid "ClusterIntegration|meets the requirements"
msgstr ""
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr ""
@@ -2382,15 +2764,27 @@ msgstr ""
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr ""
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
msgid "Collapse"
msgstr ""
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr ""
@@ -2485,6 +2879,9 @@ msgstr "æ交者:"
msgid "Commit…"
msgstr ""
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "比較"
@@ -2554,6 +2951,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2569,12 +2972,24 @@ msgstr ""
msgid "Connecting..."
msgstr ""
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -2638,6 +3053,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2665,18 +3083,9 @@ msgstr ""
msgid "Control the display of third party offers."
msgstr ""
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr ""
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr ""
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr ""
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr ""
@@ -2689,6 +3098,9 @@ msgstr ""
msgid "Copy ID to clipboard"
msgstr ""
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr ""
@@ -2713,9 +3125,6 @@ msgstr "複製æ交 SHA 到剪貼æ¿"
msgid "Copy file path to clipboard"
msgstr ""
-msgid "Copy incoming email address to clipboard"
-msgstr ""
-
msgid "Copy link"
msgstr ""
@@ -2731,9 +3140,33 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2743,10 +3176,10 @@ msgstr "創建新目錄"
msgid "Create New Domain"
msgstr ""
-msgid "Create a new branch"
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a new branch and merge request"
+msgid "Create a new branch"
msgstr ""
msgid "Create a new issue"
@@ -2758,6 +3191,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "在帳戶上創建個人訪å•ä»¤ç‰Œï¼Œä»¥é€šéŽ %{protocol} 來拉å–或推é€ã€‚"
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -2782,9 +3218,6 @@ msgstr ""
msgid "Create group label"
msgstr ""
-msgid "Create issue"
-msgstr ""
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -2854,6 +3287,9 @@ msgstr "Cron 語法"
msgid "Current Branch"
msgstr ""
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr ""
@@ -2893,9 +3329,6 @@ msgstr ""
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2938,6 +3371,9 @@ msgstr ""
msgid "DashboardProjects|Personal"
msgstr ""
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2962,15 +3398,24 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr ""
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -3010,6 +3455,9 @@ msgstr ""
msgid "Delete comment"
msgstr ""
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr ""
@@ -3022,6 +3470,21 @@ msgstr ""
msgid "Deleted"
msgstr ""
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -3032,6 +3495,9 @@ msgstr[0] "部署"
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3170,12 +3636,45 @@ msgstr ""
msgid "Description"
msgstr "æè¿°"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr ""
msgid "Description:"
msgstr ""
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3254,6 +3753,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -3293,20 +3795,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "下載 tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "下載 tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "下載 tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "下載 zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "下載"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "é›»å­éƒµä»¶è£œä¸"
@@ -3338,6 +3846,9 @@ msgstr "編輯"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -3359,6 +3870,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr ""
@@ -3371,18 +3885,54 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr ""
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr ""
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr ""
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr ""
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr ""
@@ -3398,6 +3948,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr ""
@@ -3425,6 +3978,12 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@@ -3434,9 +3993,6 @@ msgstr ""
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr ""
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3452,16 +4008,28 @@ msgstr ""
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr ""
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr ""
-msgid "Ends at (UTC)"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
+msgstr ""
+
+msgid "End date"
msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Ends at (UTC)"
msgstr ""
-msgid "Enforced SSO"
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
@@ -3479,13 +4047,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3506,6 +4068,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr ""
@@ -3557,15 +4125,33 @@ msgstr ""
msgid "Environments|Pod logs from"
msgstr ""
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr ""
msgid "Environments|Read more about environments"
msgstr ""
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr ""
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr ""
@@ -3578,6 +4164,18 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr ""
@@ -3629,6 +4227,9 @@ msgstr ""
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr ""
@@ -3677,6 +4278,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3692,12 +4305,48 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3761,12 +4410,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3776,12 +4419,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3815,6 +4464,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3833,9 +4488,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3857,13 +4509,19 @@ msgstr "無法變更所有者"
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3875,9 +4533,24 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr "無法刪除æµæ°´ç·šè¨ˆåŠƒ"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -3890,12 +4563,18 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr ""
@@ -3929,9 +4608,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr ""
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr ""
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
@@ -3989,7 +4665,7 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
-msgid "FeatureFlags|Save changes"
+msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Status"
@@ -4016,6 +4692,18 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
@@ -4077,9 +4765,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "按路徑查找"
@@ -4095,6 +4780,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4188,13 +4876,19 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Free Trial of GitLab.com Gold"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -4224,12 +4918,21 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr ""
@@ -4239,6 +4942,9 @@ msgstr ""
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4263,9 +4969,6 @@ msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
@@ -4284,6 +4987,9 @@ msgstr ""
msgid "GeoNodes|Health status"
msgstr ""
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -4329,6 +5035,9 @@ msgstr ""
msgid "GeoNodes|Out of sync"
msgstr ""
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4437,9 +5146,24 @@ msgstr ""
msgid "Geo|Batch operations"
msgstr ""
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr ""
@@ -4455,6 +5179,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4485,6 +5212,12 @@ msgstr ""
msgid "Geo|Pending verification"
msgstr ""
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr ""
@@ -4524,24 +5257,45 @@ msgstr ""
msgid "Geo|Select groups to replicate."
msgstr ""
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr ""
msgid "Geo|Status"
msgstr ""
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr ""
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr ""
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr ""
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr ""
@@ -4560,9 +5314,6 @@ msgstr ""
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr ""
-msgid "Geo|You need a different license to use Geo replication"
-msgstr ""
-
msgid "Geo|misconfigured"
msgstr ""
@@ -4584,6 +5335,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4605,6 +5359,9 @@ msgstr ""
msgid "GitLab CI Linter has been moved"
msgstr ""
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr ""
@@ -4620,12 +5377,18 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr ""
@@ -4659,15 +5422,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -4686,9 +5458,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -4698,6 +5479,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -4731,6 +5515,9 @@ msgstr ""
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4749,6 +5536,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4758,6 +5548,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
@@ -4767,18 +5623,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -4872,6 +5740,9 @@ msgstr ""
msgid "Have your users email"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -4911,6 +5782,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -4921,12 +5795,27 @@ msgstr[0] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已開始維護"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4984,15 +5873,18 @@ msgstr ""
msgid "Identities"
msgstr ""
-msgid "Identity provider single sign on URL"
+msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
@@ -5005,9 +5897,6 @@ msgstr ""
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr ""
-
msgid "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>."
msgstr ""
@@ -5092,12 +5981,30 @@ msgstr "導入存儲庫"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr ""
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr ""
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr ""
@@ -5113,15 +6020,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5137,6 +6053,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -5146,9 +6065,18 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -5168,6 +6096,9 @@ msgstr ""
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr ""
@@ -5198,6 +6129,21 @@ msgstr "週期分æžç°¡ä»‹"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5216,6 +6162,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -5228,6 +6177,9 @@ msgstr ""
msgid "Issue events"
msgstr "議題事件 (issue event)"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5258,7 +6210,10 @@ msgstr ""
msgid "Issues closed"
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
@@ -5303,12 +6258,30 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5339,6 +6312,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -5354,9 +6330,21 @@ msgstr ""
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -5399,9 +6387,15 @@ msgstr ""
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr ""
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -5417,6 +6411,15 @@ msgstr ""
msgid "Label lists show all issues with the selected label."
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -5447,6 +6450,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -5517,16 +6523,16 @@ msgstr ""
msgid "Learn more about Web Terminal"
msgstr ""
-msgid "Learn more about custom project templates"
+msgid "Learn more about approvals."
msgstr ""
-msgid "Learn more about group-level project templates"
+msgid "Learn more about custom project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about protected branches"
+msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more in the"
@@ -5538,6 +6544,9 @@ msgstr "æµæ°´ç·šè¨ˆåŠƒæ–‡æª”"
msgid "Leave"
msgstr ""
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "退出群組"
@@ -5622,9 +6631,15 @@ msgstr ""
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr ""
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr ""
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@@ -5641,6 +6656,9 @@ msgstr ""
msgid "List available repositories"
msgstr ""
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -5698,15 +6716,36 @@ msgstr ""
msgid "Login with smartcard"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5737,15 +6776,15 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr ""
-
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -5770,46 +6809,43 @@ msgstr ""
msgid "Markdown"
msgstr ""
-msgid "Markdown enabled"
+msgid "Markdown Help"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
+msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a numbered list"
+msgid "Maven Metadata"
msgstr ""
-msgid "MarkdownToolbar|Add a table"
+msgid "Max access level"
msgstr ""
-msgid "MarkdownToolbar|Add a task list"
+msgid "Max seats used"
msgstr ""
-msgid "MarkdownToolbar|Add bold text"
+msgid "Maximum artifacts size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Add italic text"
+msgid "Maximum attachment size (MB)"
msgstr ""
-msgid "MarkdownToolbar|Go full screen"
+msgid "Maximum capacity"
msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
+msgid "Maximum delay (Minutes)"
msgstr ""
-msgid "MarkdownToolbar|Insert code"
+msgid "Maximum job timeout"
msgstr ""
-msgid "Maven Metadata"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
-msgid "Max access level"
+msgid "Maximum push size (MB)"
msgstr ""
-msgid "Maximum job timeout"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
msgstr ""
msgid "May"
@@ -5833,10 +6869,10 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgid "Merge Request"
msgstr ""
-msgid "Merge Request"
+msgid "Merge Request Approvals"
msgstr ""
msgid "Merge Requests"
@@ -5845,9 +6881,15 @@ msgstr ""
msgid "Merge Requests created"
msgstr ""
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "åˆä½µäº‹ä»¶ (merge event)"
@@ -5857,12 +6899,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr ""
msgid "Merge request approvals"
msgstr ""
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr ""
@@ -5932,16 +6983,19 @@ msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr ""
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
+msgid "Merged"
msgstr ""
-msgid "Merged"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
msgid "Messages"
@@ -6001,13 +7055,16 @@ msgstr ""
msgid "Metrics|No deployed environments"
msgstr ""
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr ""
-msgid "Metrics|System"
+msgid "Metrics|Show last"
msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
@@ -6049,6 +7106,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr ""
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr ""
@@ -6094,6 +7157,9 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6112,6 +7178,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "添加壹個 SSH 公鑰"
@@ -6121,6 +7190,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6154,9 +7226,15 @@ msgstr ""
msgid "More information is available|here"
msgstr "幫助文檔"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr ""
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr ""
@@ -6166,6 +7244,12 @@ msgstr ""
msgid "Multiple issue boards"
msgstr ""
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr ""
@@ -6178,6 +7262,12 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr ""
@@ -6230,21 +7320,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "創建æµæ°´ç·šè¨ˆåŠƒ"
msgid "New Snippet"
msgstr ""
-msgid "New Snippets"
-msgstr ""
-
msgid "New branch"
msgstr "新增分支"
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "新增目錄"
@@ -6260,6 +7353,9 @@ msgstr "新增文件"
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -6281,6 +7377,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "新增计划"
@@ -6293,19 +7392,28 @@ msgstr ""
msgid "New tag"
msgstr "新增標籤"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr ""
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
+msgid "No activities found"
msgstr ""
msgid "No branches found"
@@ -6335,7 +7443,7 @@ msgstr ""
msgid "No due date"
msgstr ""
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6353,15 +7461,24 @@ msgstr ""
msgid "No issues for the selected time period."
msgstr ""
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr ""
@@ -6410,9 +7527,6 @@ msgstr ""
msgid "None"
msgstr ""
-msgid "Not allowed to merge"
-msgstr ""
-
msgid "Not available"
msgstr "ä¸å¯ç”¨"
@@ -6428,10 +7542,16 @@ msgstr ""
msgid "Not enough data"
msgstr "數據ä¸è¶³"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -6545,6 +7665,9 @@ msgstr ""
msgid "OK"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -6557,10 +7680,19 @@ msgstr "篩é¸"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@@ -6570,6 +7702,12 @@ msgstr ""
msgid "Only admins"
msgstr ""
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr ""
@@ -6606,6 +7744,9 @@ msgstr ""
msgid "Open projects"
msgstr ""
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr ""
@@ -6633,15 +7774,30 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr ""
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -6660,9 +7816,6 @@ msgstr ""
msgid "Other information"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -6684,6 +7837,12 @@ msgstr ""
msgid "Packages"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -6708,18 +7867,21 @@ msgstr ""
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr ""
msgid "Password"
msgstr ""
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6729,6 +7891,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6738,6 +7903,9 @@ msgstr ""
msgid "Pause"
msgstr ""
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr ""
@@ -6756,6 +7924,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr ""
@@ -6771,21 +7942,24 @@ msgstr ""
msgid "Pipeline"
msgstr "æµæ°´ç·š"
-msgid "Pipeline Health"
-msgstr "æµæ°´ç·šå¥åº·æŒ‡æ¨™"
-
msgid "Pipeline Schedule"
msgstr "æµæ°´ç·šè¨ˆåŠƒ"
msgid "Pipeline Schedules"
msgstr "æµæ°´ç·šè¨ˆåŠƒ"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr ""
msgid "Pipeline triggers"
msgstr ""
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失敗:"
@@ -6834,6 +8008,12 @@ msgstr "變é‡"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定義"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "æµæ°´ç·š"
@@ -6849,6 +8029,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -6891,10 +8077,7 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
-msgid "Pipeline|Create for"
-msgstr ""
-
-msgid "Pipeline|Create pipeline"
+msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Duration"
@@ -6909,6 +8092,9 @@ msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr ""
@@ -6936,9 +8122,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr "所有"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "æˆåŠŸ"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "於階段"
@@ -6963,15 +8161,33 @@ msgstr ""
msgid "Please choose a group URL with no special characters."
msgstr ""
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -6981,9 +8197,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -6996,6 +8224,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr ""
@@ -7005,18 +8236,33 @@ msgstr ""
msgid "Preferences"
msgstr ""
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
msgid "Prevent adding new members to project membership within this group"
msgstr ""
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr ""
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr ""
@@ -7044,6 +8290,9 @@ msgstr ""
msgid "Private - The group and its projects can only be viewed by members."
msgstr ""
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
@@ -7194,21 +8443,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7308,6 +8557,15 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -7323,16 +8581,25 @@ msgstr "é …ç›® '%{project_name}' 已更新完æˆã€‚"
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr "項目訪å•æ¬Šé™å¿…須明確授權給æ¯å€‹ç”¨æˆ¶ã€‚"
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
-msgid "Project avatar in repository: %{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
msgstr ""
msgid "Project details"
@@ -7341,6 +8608,9 @@ msgstr "專案詳情"
msgid "Project export could not be deleted."
msgstr "無法刪除項目導出。"
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "項目導出已被刪除。"
@@ -7350,6 +8620,9 @@ msgstr "項目導出éˆæŽ¥å·²éŽæœŸã€‚請從項目設置中é‡æ–°ç”Ÿæˆé …目導
msgid "Project export started. A download link will be sent by email."
msgstr "項目導出已開始。下載éˆæŽ¥å°‡é€šéŽé›»å­éƒµä»¶ç™¼é€ã€‚"
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7359,6 +8632,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7449,12 +8728,21 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -7503,6 +8791,9 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr ""
@@ -7605,6 +8896,9 @@ msgstr ""
msgid "Promotions|Upgrade plan"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -7671,6 +8965,12 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
@@ -7683,6 +8983,12 @@ msgstr ""
msgid "Push Rules"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "推é€äº‹ä»¶ (push event) "
@@ -7713,6 +9019,9 @@ msgstr ""
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "了解更多"
@@ -7728,6 +9037,12 @@ msgstr ""
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7762,6 +9077,9 @@ msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -7807,9 +9125,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -7819,12 +9146,33 @@ msgstr ""
msgid "Remove project"
msgstr "刪除項目"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7843,6 +9191,9 @@ msgstr ""
msgid "Repair authentication"
msgstr ""
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7876,6 +9227,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr ""
@@ -7894,6 +9257,12 @@ msgstr ""
msgid "Reports|Vulnerability"
msgstr ""
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr ""
@@ -7906,6 +9275,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7942,6 +9314,17 @@ msgstr ""
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
msgid "Resend invite"
msgstr ""
@@ -7975,9 +9358,18 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8002,12 +9394,18 @@ msgstr ""
msgid "Resume"
msgstr ""
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8039,12 +9437,21 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr ""
msgid "Run CI/CD pipelines for external repositories"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8066,6 +9473,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -8099,25 +9512,25 @@ msgstr ""
msgid "Running"
msgstr ""
-msgid "SAML SSO"
+msgid "Running…"
msgstr ""
-msgid "SAML SSO for %{group_name}"
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
-msgid "SAML Single Sign On"
+msgid "SAML SSO"
msgstr ""
-msgid "SAML Single Sign On Settings"
+msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgid "SSH Keys"
msgstr ""
-msgid "SSH Keys"
+msgid "SSH Keys Help"
msgstr ""
msgid "SSH host keys"
@@ -8129,6 +9542,9 @@ msgstr ""
msgid "SSL Verification"
msgstr ""
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr ""
@@ -8153,6 +9569,9 @@ msgstr "ä¿å­˜æµæ°´ç·šè¨ˆåŠƒ"
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "新建æµæ°´ç·šè¨ˆåŠƒ"
@@ -8162,15 +9581,24 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "æµæ°´ç·šè¨ˆåŠƒ"
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr ""
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -8222,6 +9650,9 @@ msgstr ""
msgid "Search users"
msgstr ""
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr ""
@@ -8249,6 +9680,15 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr ""
@@ -8270,9 +9710,6 @@ msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr ""
@@ -8288,10 +9725,10 @@ msgstr ""
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
+msgid "Security Reports|There was an error creating the issue."
msgstr ""
-msgid "Security Reports|There was an error creating the issue."
+msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
@@ -8342,6 +9779,15 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr ""
@@ -8351,9 +9797,6 @@ msgstr "é¸æ“‡æ™‚å€"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
-msgid "Select assignee"
-msgstr ""
-
msgid "Select branch/tag"
msgstr ""
@@ -8384,10 +9827,13 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
-msgid "Selective synchronization"
+msgid "Send an email notification to Developers."
msgstr ""
msgid "Send email"
@@ -8405,18 +9851,39 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8432,9 +9899,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8462,9 +9926,18 @@ msgstr ""
msgid "Service URL"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "為賬號添加壹個用於推é€æˆ–拉å–çš„ %{protocol} 密碼。"
@@ -8480,6 +9953,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -8489,6 +9965,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -8504,6 +9992,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -8561,9 +10052,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8573,6 +10070,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr ""
@@ -8631,6 +10131,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8640,6 +10146,9 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr ""
@@ -8661,6 +10170,27 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8706,6 +10236,9 @@ msgstr ""
msgid "Something went wrong while fetching group member contributions"
msgstr ""
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr ""
@@ -8733,6 +10266,9 @@ msgstr ""
msgid "Something went wrong, unable to remove project"
msgstr ""
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr ""
@@ -8883,6 +10419,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -8931,15 +10470,27 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "由此更改 %{new_merge_request}"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr ""
@@ -8973,6 +10524,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9126,6 +10680,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9135,15 +10710,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "切æ›åˆ†æ”¯/標籤"
msgid "Sync information"
msgstr ""
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr ""
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr ""
@@ -9153,6 +10737,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -9276,12 +10863,22 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
@@ -9300,6 +10897,12 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9312,15 +10915,63 @@ msgstr "與該階段相關的事件。"
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "派生關係已被刪除。"
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "議題階段概述了從創建議題到將議題添加到è£ç¨‹ç¢‘或議題看æ¿æ‰€èŠ±è²»çš„時間。創建第壹個議題後,數據將自動添加到此處.。"
@@ -9330,6 +10981,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr ""
@@ -9357,9 +11020,18 @@ msgstr "該項目å…許已登錄的用戶訪å•ã€‚"
msgid "The project can be accessed without any authentication."
msgstr "該項目å…許任何人訪å•ã€‚"
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "此項目的存儲庫ä¸å­˜åœ¨ã€‚"
@@ -9378,6 +11050,21 @@ msgstr "評審階段概述了從創建åˆä½µè«‹æ±‚到åˆä½µçš„時間。當創建
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr ""
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "é ç™¼å¸ƒéšŽæ®µæ¦‚述了åˆä½µè«‹æ±‚çš„åˆä½µåˆ°éƒ¨ç½²ä»£ç¢¼åˆ°ç”Ÿç”¢ç’°å¢ƒçš„總時間。當首次部署到生產環境後,數據將自動添加到此處。"
@@ -9393,9 +11080,18 @@ msgstr "該階段æ¯æ¢æ•¸æ“šæ‰€èŠ±çš„時間"
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -9450,9 +11146,21 @@ msgstr ""
msgid "There was an error deleting the todo."
msgstr ""
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -9468,6 +11176,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9486,6 +11197,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -9498,6 +11212,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -9519,6 +11248,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -9534,6 +11266,9 @@ msgstr ""
msgid "This is the author's first Merge Request to this project."
msgstr ""
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr ""
@@ -9582,6 +11317,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9603,10 +11344,16 @@ msgstr "在創建壹個空的存儲庫或導入ç¾æœ‰å­˜å„²åº«ä¹‹å‰ï¼Œæ‚¨å°‡ç„¡
msgid "This merge request is locked."
msgstr ""
-msgid "This option is disabled as you don't have write permissions for the current branch"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
msgstr ""
-msgid "This option is disabled while you still have unstaged changes"
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
+msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
@@ -9615,7 +11362,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9639,6 +11386,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -9648,10 +11398,10 @@ msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
-msgid "This source diff could not be displayed because it is too large."
+msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
+msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no identities"
@@ -9666,6 +11416,9 @@ msgstr ""
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr ""
@@ -9732,9 +11485,6 @@ msgstr " %s 個月å‰"
msgid "Timeago|%s months remaining"
msgstr "剩餘 %s 月"
-msgid "Timeago|%s seconds ago"
-msgstr ""
-
msgid "Timeago|%s seconds remaining"
msgstr "剩餘 %s 秒"
@@ -9962,6 +11712,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -10001,6 +11754,12 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr ""
@@ -10025,6 +11784,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -10037,6 +11799,9 @@ msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -10046,6 +11811,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -10070,6 +11847,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -10079,15 +11859,27 @@ msgstr ""
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr ""
msgid "Unable to update this epic at this time."
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10100,6 +11892,9 @@ msgstr ""
msgid "Unknown"
msgstr ""
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -10160,21 +11955,39 @@ msgstr ""
msgid "Update"
msgstr ""
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr ""
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
@@ -10190,6 +12003,9 @@ msgstr ""
msgid "Upgrade your plan to improve Issue boards."
msgstr ""
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
@@ -10214,18 +12030,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "點擊上傳"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr ""
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr ""
msgid "Usage statistics"
msgstr ""
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
@@ -10256,18 +12084,54 @@ msgstr ""
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -10346,18 +12210,39 @@ msgstr ""
msgid "Users"
msgstr ""
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -10382,9 +12267,6 @@ msgstr ""
msgid "Version"
msgstr ""
-msgid "View %{alerts}"
-msgstr ""
-
msgid "View app"
msgstr ""
@@ -10394,7 +12276,7 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
+msgid "View eligible approvers"
msgstr ""
msgid "View epics list"
@@ -10406,12 +12288,15 @@ msgstr ""
msgid "View group labels"
msgstr ""
-msgid "View issue"
+msgid "View in Sentry"
msgstr ""
msgid "View it on GitLab"
msgstr ""
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr ""
@@ -10445,6 +12330,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr ""
@@ -10481,12 +12369,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -10535,6 +12429,12 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr ""
@@ -10553,9 +12453,16 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr ""
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
msgstr ""
@@ -10568,6 +12475,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -10679,6 +12589,9 @@ msgstr ""
msgid "Wiki|Create page"
msgstr ""
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr ""
@@ -10697,6 +12610,9 @@ msgstr ""
msgid "Wiki|Pages"
msgstr ""
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr ""
@@ -10709,12 +12625,24 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è¯·"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -10745,6 +12673,9 @@ msgstr "å³å°‡åˆªé™¤èˆ‡æºé …ç›® %{forked_from_project} 的派生關系。確定
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -10760,6 +12691,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -10769,6 +12709,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šæ·»åŠ æ–‡ä»¶"
@@ -10778,6 +12724,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -10787,12 +12736,30 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -10811,12 +12778,27 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr ""
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "您已é”到項目數é‡é™åˆ¶"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10829,7 +12811,7 @@ msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10841,12 +12823,24 @@ msgstr "需è¦ç›¸é—œçš„權é™ã€‚"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥éƒµä»¶"
@@ -10874,6 +12868,15 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -10907,6 +12910,15 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -10925,9 +12937,15 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10943,15 +12961,30 @@ msgstr ""
msgid "Your name"
msgstr "您的åå­—"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -10961,12 +12994,15 @@ msgstr ""
msgid "among other things"
msgstr ""
-msgid "assign yourself"
+msgid "at"
msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -10991,14 +13027,34 @@ msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] ""
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr ""
@@ -11028,6 +13084,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -11037,6 +13096,15 @@ msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr ""
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr ""
@@ -11046,12 +13114,24 @@ msgstr ""
msgid "ciReport|Confidence"
msgstr ""
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr ""
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr ""
@@ -11094,9 +13174,18 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr ""
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr ""
@@ -11138,9 +13227,6 @@ msgstr ""
msgid "ciReport|Performance metrics"
msgstr ""
-msgid "ciReport|Revert dismissal"
-msgstr ""
-
msgid "ciReport|SAST"
msgstr ""
@@ -11162,6 +13248,9 @@ msgstr ""
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr ""
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr ""
@@ -11180,9 +13269,18 @@ msgstr ""
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr ""
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""
@@ -11193,10 +13291,31 @@ msgstr ""
msgid "ciReport|on pipeline"
msgstr ""
-msgid "command line instructions"
+msgid "commented on %{link_to_project}"
msgstr ""
-msgid "commented on %{link_to_project}"
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
msgstr ""
msgid "confidentiality|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."
@@ -11224,6 +13343,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -11253,22 +13375,31 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
-msgid "for this project"
+msgid "failed"
msgstr ""
-msgid "from"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
-msgid "group"
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
msgstr ""
-msgid "help"
+msgid "for %{link_to_pipeline_ref}"
msgstr ""
-msgid "here"
+msgid "for %{ref}"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgid "for this project"
+msgstr ""
+
+msgid "from"
+msgstr ""
+
+msgid "group"
+msgstr ""
+
+msgid "here"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -11332,6 +13463,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr ""
@@ -11351,6 +13485,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11387,6 +13524,9 @@ msgstr ""
msgid "mrWidget|Approve"
msgstr ""
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr ""
@@ -11459,16 +13599,19 @@ msgstr ""
msgid "mrWidget|Merge request approved"
msgstr ""
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr ""
msgid "mrWidget|Merged by"
msgstr ""
-msgid "mrWidget|No Approval required"
+msgid "mrWidget|No approval required"
msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
+msgid "mrWidget|No approval required; you can still approve"
msgstr ""
msgid "mrWidget|Open in Web IDE"
@@ -11518,6 +13661,9 @@ msgstr ""
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr ""
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr ""
@@ -11628,12 +13774,19 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr ""
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] ""
@@ -11663,9 +13816,36 @@ msgid "reply"
msgid_plural "replies"
msgstr[0] ""
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11690,6 +13870,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index c4a58650dae..1522f1c96ad 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -13,11 +13,20 @@ msgstr ""
"X-Crowdin-Project: gitlab-ee\n"
"X-Crowdin-Language: zh-TW\n"
"X-Crowdin-File: /master/locale/gitlab.pot\n"
-"PO-Revision-Date: 2019-02-11 10:20\n"
+"PO-Revision-Date: 2019-04-12 12:36\n"
+
+msgid " Please sign in."
+msgstr ""
msgid " Status"
msgstr " 狀態"
+msgid " Try to %{action} this file again."
+msgstr ""
+
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " and"
msgstr " 和"
@@ -38,8 +47,9 @@ msgstr ""
msgid " or <#issue id>"
msgstr ""
-msgid "\"%{query}\" in projects"
-msgstr "在專案中查詢「%{query}ã€"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
msgid "%d commit"
msgid_plural "%d commits"
@@ -111,12 +121,25 @@ msgstr "%{counter_storage}ï¼ˆå…§å« %{counter_repositories} 個版本庫ã€%{cou
msgid "%{count} %{alerts}"
msgstr "%{count} 個 %{alerts} æ醒"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+
+msgid "%{count} approvals from %{name}"
+msgstr ""
+
msgid "%{count} more"
msgstr ""
msgid "%{count} more assignees"
msgstr "%{count} å以上的被指派者"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr ""
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} ä½åƒèˆ‡è€…"
@@ -131,7 +154,7 @@ msgstr "已刪除 %{filePath} 檔案"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel}(內å«å‰©é¤˜çš„ %{labelCount} 個)"
-msgid "%{firstOption} +%{extraOptionCount} more"
+msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
@@ -140,6 +163,12 @@ msgstr "%{group_docs_link_start}群組%{group_docs_link_end} 讓您能跨管ç†ä
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "將移除 %{issuableType}ï¼ç¢ºå®šï¼Ÿ"
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
+msgid "%{level_name} visibility has been restricted by the administrator."
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -149,6 +178,9 @@ msgstr "%{loadingIcon} 已開始程å¼"
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr "%{lock_path} 被 GitLab 使用者 %{lock_user_id} 鎖定"
+msgid "%{mrText}, this issue will be closed automatically."
+msgstr ""
+
msgid "%{name}'s avatar"
msgstr "%{name} 的大頭貼"
@@ -161,6 +193,15 @@ msgstr "%{openOrClose} %{noteable}"
msgid "%{percent}%% complete"
msgstr "å·²å®Œæˆ %{percent}%%"
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{state} epics"
msgstr ""
@@ -198,6 +239,9 @@ msgstr "%{usage_ping_link_start}得知更多%{usage_ping_link_end}會分享給 G
msgid "%{user_name} profile page"
msgstr ""
+msgid "(No changes)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
@@ -207,6 +251,9 @@ msgstr "(還有 %{count} 個)"
msgid "+ %{moreCount} more"
msgstr "(還有 %{moreCount} 個)"
+msgid "+%{extraOptionCount} more"
+msgstr ""
+
msgid ", or "
msgstr ""
@@ -227,6 +274,10 @@ msgid "1 %{type} modification"
msgid_plural "%{count} %{type} modifications"
msgstr[0] "%{count} 個 %{type} 變更"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] ""
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "%d 個關閉的議題"
@@ -235,6 +286,9 @@ msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] "%d 個關閉的åˆä½µè«‹æ±‚"
+msgid "1 day"
+msgstr ""
+
msgid "1 group"
msgid_plural "%d groups"
msgstr[0] "%d 個群組"
@@ -263,6 +317,9 @@ msgid "1 user"
msgid_plural "%d users"
msgstr[0] "%d ä½ä½¿ç”¨è€…"
+msgid "1 week"
+msgstr ""
+
msgid "1st contribution!"
msgstr "您的第一次貢ç»ï¼"
@@ -272,6 +329,15 @@ msgstr ""
msgid "2FA enabled"
msgstr "已啟用兩步驟驗證"
+msgid "3 days"
+msgstr ""
+
+msgid "3 hours"
+msgstr ""
+
+msgid "30 minutes"
+msgstr ""
+
msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
@@ -287,6 +353,9 @@ msgstr "未找到é é¢"
msgid "404|Please contact your GitLab administrator if you think this is a mistake."
msgstr "若您èªç‚ºé€™ä¸æ‡‰ç™¼ç”Ÿï¼Œè«‹è¯çµ¡æ‚¨çš„ GitLab 管ç†å“¡ã€‚"
+msgid "8 hours"
+msgstr ""
+
msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com."
msgstr "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> 將會在所有原本由 johnsmith@example.com 建立的議題和留言中加上「來自 <a href=\"#\">@johnsmith</a>ã€ä¸¦å°‡åŽŸæœ¬åˆ†é…給 johnsmith@example.com 的所有議題設定 <a href=\"#\">@johnsmith</a> 為被指派者。"
@@ -320,8 +389,20 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "「執行器ã€æ˜¯å€‹ç”¨ä¾†åŸ·è¡Œä½œæ¥­çš„程å¼ã€‚你能設定你所需的多個執行器。"
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "ä¸é–“æ–·æ•´åˆçš„圖表集åˆ"
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr "無法å°ä¸€å€‹ç©ºå°ˆæ¡ˆé¸æ“‡é è¨­åˆ†æ”¯ã€‚"
@@ -332,18 +413,39 @@ msgstr "已刪除使用者"
msgid "A member of GitLab's abuse team will review your report as soon as possible."
msgstr "GitLab 濫用檢閱團隊的æˆå“¡å°‡æœƒç›¡å¿«æª¢é–±ä½ çš„回報。"
+msgid "A merge commit is created for every merge, and merging is allowed as long as there are no conflicts."
+msgstr ""
+
+msgid "A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible. This way you could make sure that if this merge request would build, after merging to target branch it would also build."
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "將會在您的 fork 中建立一個新分支,並開啟新的åˆä½µè«‹æ±‚。"
+msgid "A new impersonation token has been created."
+msgstr ""
+
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "專案å¯è®“您存放檔案(版本庫)ã€è¨ˆåŠƒå·¥ä½œï¼ˆè­°é¡Œï¼‰ã€ç™¼ä½ˆæª”案(Wiki),還有這些:%{among_other_things_link}。"
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "æ­£è¦è¡¨ç¤ºå¼å°‡æœƒç”¨ä¾†å°‹æ‰¾ä½œæ¥­è¿½è¹¤ä¸­çš„測試涵蓋範åœè¼¸å‡ºã€‚留空å³åœç”¨æ­¤åŠŸèƒ½ã€‚"
msgid "A user with write access to the source branch selected this option"
msgstr "一ä½æœ‰ä¾†æºåˆ†æ”¯å¯«å…¥æ¬Šé™çš„使用者é¸æ“‡äº†é€™å€‹é¸é …"
+msgid "API Help"
+msgstr ""
+
msgid "About GitLab"
msgstr "關於 GitLab"
@@ -357,7 +459,7 @@ msgid "About this feature"
msgstr "關於此功能"
msgid "Abuse Reports"
-msgstr "濫用回報"
+msgstr "濫用報告"
msgid "Abuse reports"
msgstr "濫用回報"
@@ -374,12 +476,18 @@ msgstr "接å—çš„åˆä½µè«‹æ±‚"
msgid "Access Tokens"
msgstr "å­˜å–憑證"
+msgid "Access denied for your LDAP account."
+msgstr ""
+
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr "å­˜å–被拒ï¼è«‹æª¢æŸ¥æ‚¨æ˜¯å¦å¯ä»¥åœ¨æ­¤ç‰ˆæœ¬åº«éƒ¨å±¬é‡‘鑰。"
msgid "Access expiration date"
msgstr "å­˜å–éŽæœŸæ—¥æœŸ"
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Access to '%{classification_label}' not allowed"
msgstr "您ä¸å…許存å–「%{classification_label}ã€"
@@ -389,11 +497,17 @@ msgstr "帳號"
msgid "Account and limit"
msgstr "帳號與é™åˆ¶"
+msgid "Action to take when receiving an alert."
+msgstr ""
+
+msgid "Activate Service Desk"
+msgstr ""
+
msgid "Active"
msgstr "使用中"
msgid "Active Sessions"
-msgstr "使用中階段"
+msgstr "連線中的è£ç½®"
msgid "Activity"
msgstr "活動"
@@ -419,36 +533,63 @@ msgstr "新增 Kubernetes å¢é›†"
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "在 Wiki 中新增包å«æ‚¨å°ˆæ¡ˆç›¸é—œè³‡è¨Šçš„首é ï¼Œä¹‹å¾Œ GitLab 將會顯示 Wiki 首é æ–¼æ­¤è™•è€Œéžé€™å‰‡è¨Šæ¯ã€‚"
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr "新增表格"
+msgid "Add a task list"
+msgstr ""
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "新增顯示於所有電å­éƒµä»¶å°è©±çš„附加文字。最多 %{character_limit} 個字元。"
+msgid "Add approver(s)"
+msgstr ""
+
+msgid "Add approvers"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
msgid "Add comment now"
msgstr "ç«‹å³ç•™è¨€"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "增加圖片留言"
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr "新增授權資訊"
+msgid "Add list"
+msgstr ""
+
msgid "Add new application"
msgstr "新增應用程å¼"
msgid "Add new directory"
msgstr "新增資料夾"
-msgid "Add projects"
-msgstr "新增專案"
-
msgid "Add reaction"
msgstr "新增回應"
@@ -470,18 +611,27 @@ msgstr ""
msgid "Add users to group"
msgstr "將使用者加到群組"
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "您的 GitLab 執行個體已åœç”¨ã€Œæ–°å¢žæ‡‰ç”¨ç¨‹å¼ã€åŠŸèƒ½ã€‚è«‹è¯çµ¡æ‚¨çš„ GitLab 管ç†å“¡å–得新增權é™"
msgid "Additional text"
msgstr "附加文字"
+msgid "Aditional minutes"
+msgstr ""
+
msgid "Admin Area"
msgstr "管ç†å€å¡Š"
msgid "Admin Overview"
msgstr "管ç†æ¦‚覽"
+msgid "Admin Section"
+msgstr ""
+
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr "您正打算永久刪除 %{username} 使用者。連çµåˆ°é€™äº›ä½¿ç”¨è€…çš„è­°é¡Œã€åˆä½µè«‹æ±‚與群組將會移轉到系統全域的使用者「Ghost-userã€ã€‚è‹¥è¦é¿å…資料éºå¤±ï¼Œè«‹è€ƒæ…®æ”¹ç”¨%{strong_start}å°éŽ–使用者%{strong_end}功能,因為%{strong_start}刪除使用者%{strong_end} 動作一執行就無法復原。"
@@ -518,6 +668,9 @@ msgstr "刪除專案"
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -599,12 +752,21 @@ msgstr ""
msgid "AdminUsers|Without projects"
msgstr ""
+msgid "Advanced"
+msgstr ""
+
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr "進階權é™ï¼Œã€ã€Œå¤§åž‹æª”案儲存空間 (LFS)ã€å’Œã€Œå…©æ­¥é©Ÿé©—è­‰ã€è¨­å®šã€‚"
+msgid "Advanced search functionality"
+msgstr ""
+
msgid "Advanced settings"
msgstr "進階設定"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "通知"
@@ -615,6 +777,9 @@ msgstr ""
msgid "All"
msgstr "全部"
+msgid "All Members"
+msgstr ""
+
msgid "All changes are committed"
msgstr "å·²æ交所有變更"
@@ -624,15 +789,33 @@ msgstr "已經å°ç©ºç™½ã€ä¾†è‡ªæ¨¡æ¿ä»¥åŠåŒ¯å…¥çš„專案啟用所有功能,
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr "所有使用者"
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow \"%{group_name}\" to sign you in"
msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr "å…許能åˆä½µè‡³ç›®æ¨™åˆ†æ”¯çš„æˆå“¡æ交"
+msgid "Allow group owners to manage LDAP-related settings"
+msgstr ""
+
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr "å…許該群組中的專案使用 Git LFS"
@@ -645,6 +828,12 @@ msgstr "å…許在 Asciidoc 檔案繪製 PlantUML 圖片。"
msgid "Allow requests to the local network from hooks and services."
msgstr "å…許觸發器和æœå‹™å‘本機網路傳é€è«‹æ±‚。"
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "å…許使用者請求存å–權é™"
@@ -663,6 +852,9 @@ msgstr "也被稱為「議題開啟者ã€æˆ–「信賴憑證者信任識別碼ã€
msgid "Also called \"Relying party service URL\" or \"Reply URL\""
msgstr "也稱為「信賴憑證者æœå‹™ URLã€æˆ–「回覆 URLã€"
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect."
msgstr "或者,你能使用 %{personal_access_token_link} 連çµã€‚當你è¦å»ºç«‹å€‹äººå­˜å–憑證時,將需è¦é¸æ“‡<code>版本庫</code>範åœï¼Œæˆ‘們æ‰èƒ½é¡¯ç¤ºå¯é€£ç·šçš„公開åŠç§äººç‰ˆæœ¬åº«åˆ—表。"
@@ -684,9 +876,6 @@ msgstr "空的 GitLab 使用者欄ä½å°‡æœƒåœ¨æ‰€æœ‰è­°é¡Œèˆ‡èªªæ˜Žçš„說明欄å
msgid "An error has occurred"
msgstr "發生了一個錯誤"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "新增è‰ç¨¿åœ¨è¨Žè«–串時發生錯誤。"
@@ -696,6 +885,15 @@ msgstr "新增è‰ç¨¿æ™‚發生錯誤。"
msgid "An error occurred creating the new branch."
msgstr ""
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr ""
+
+msgid "An error occurred fetching the dropdown data."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr "é è¦½ blob 檔案時發生錯誤"
@@ -703,23 +901,32 @@ msgid "An error occurred when toggling the notification subscription"
msgstr "切æ›è¨‚閱通知時發生錯誤"
msgid "An error occurred when updating the issue weight"
-msgstr "æ›´æ–°å•é¡Œæ¬Šé‡æ™‚發生錯誤"
+msgstr "更新議題權é‡æ™‚發生錯誤"
msgid "An error occurred while adding approver"
msgstr "增加審核者時發生錯誤"
+msgid "An error occurred while deleting the approvers group"
+msgstr ""
+
msgid "An error occurred while deleting the comment"
msgstr "刪除留言時發生錯誤"
msgid "An error occurred while detecting host keys"
msgstr "åµæ¸¬ä¸»æ©Ÿé‡‘鑰時發生錯誤"
+msgid "An error occurred while disabling Service Desk."
+msgstr ""
+
msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "當關閉通知時發生錯誤。請嘗試é‡æ–°æ•´ç†é é¢ä¸¦é‡è©¦ã€‚"
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr "解除çªé¡¯é¡¯ç¤ºæ™‚發生錯誤,請é‡æ–°æ•´ç†é é¢å¾Œå†æ¬¡å˜—試。"
+msgid "An error occurred while enabling Service Desk."
+msgstr ""
+
msgid "An error occurred while fetching markdown preview"
msgstr "å–å¾— Markdown é è¦½çµæžœæ™‚發生錯誤"
@@ -732,6 +939,9 @@ msgstr "讀å–å´é‚Šæ¬„資料時發生錯誤"
msgid "An error occurred while fetching stages."
msgstr "抓å–狀態時發生錯誤。"
+msgid "An error occurred while fetching the Service Desk address."
+msgstr ""
+
msgid "An error occurred while fetching the job log."
msgstr "抓å–工作記錄時發生錯誤。"
@@ -744,6 +954,9 @@ msgstr "抓å–工作時發生錯誤。"
msgid "An error occurred while fetching the pipeline."
msgstr "讀å–管線時發生錯誤"
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "å–得專案時發生錯誤"
@@ -759,6 +972,9 @@ msgstr "讀å–圖表資料時發生錯誤"
msgid "An error occurred while loading commit signatures"
msgstr "載入æ交簽å時發生錯誤"
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "讀å–差異檔時發生錯誤"
@@ -801,12 +1017,18 @@ msgstr "儲存 LDAP 覆蓋狀態時發生錯誤,請é‡è©¦ã€‚"
msgid "An error occurred while saving assignees"
msgstr "儲存被指派人時發生錯誤"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "訂閱通知時發生錯誤。"
msgid "An error occurred while unsubscribing to notifications."
msgstr "當å–消訂閱通知時發生錯誤"
+msgid "An error occurred while updating approvers"
+msgstr ""
+
msgid "An error occurred while updating the comment"
msgstr "更新留言時發生錯誤"
@@ -864,6 +1086,9 @@ msgstr ""
msgid "Analytics"
msgstr "分æž"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "匿å"
@@ -876,15 +1101,33 @@ msgstr "任何"
msgid "Any Label"
msgstr "ä»»æ„標籤"
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr "外觀"
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr "應用程å¼"
msgid "Application ID"
msgstr "æ‡‰ç”¨ç¨‹å¼ ID"
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr "應用程å¼ï¼š%{name}"
@@ -897,6 +1140,40 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr ""
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] ""
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
+msgstr ""
+
+msgid "ApprovalRule|Members"
+msgstr ""
+
+msgid "ApprovalRule|Name"
+msgstr ""
+
+msgid "ApprovalRule|No. approvals required"
+msgstr ""
+
+msgid "ApprovalRule|e.g. QA, Security, etc."
+msgstr ""
+
msgid "Approvals"
msgstr ""
@@ -912,15 +1189,30 @@ msgstr "四月"
msgid "April"
msgstr "四月"
+msgid "Archive jobs"
+msgstr ""
+
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr "這是個已經å°å­˜çš„專案ï¼å…¶ç‰ˆæœ¬åº«èˆ‡å…¶å…¶ä»–專案資æºç‚ºå”¯è®€ç‹€æ…‹ã€‚"
msgid "Archived projects"
msgstr "å·²å°å­˜çš„專案"
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "確定è¦åˆªé™¤æ­¤æŽ’程管線嗎?"
@@ -933,15 +1225,24 @@ msgstr "您確定è¦æ”¾æ£„未儲存的變更嗎?"
msgid "Are you sure you want to lose your issue information?"
msgstr ""
+msgid "Are you sure you want to permanently delete this license?"
+msgstr ""
+
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "確èªé‡æ–°ç”¢ç”Ÿå…¬é‘°ï¼Ÿæ‚¨å°‡éœ€è¦åœ¨é‡æ–°é‹ä½œé¡åƒå‰ï¼Œæ›´æ–°é ç«¯ä¼ºæœå™¨ä¸Šçš„公鑰。"
msgid "Are you sure you want to remove %{group_name}?"
msgstr "確定移除 %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove approver %{name}?"
msgstr ""
+msgid "Are you sure you want to remove group %{name}"
+msgstr ""
+
msgid "Are you sure you want to remove group %{name}?"
msgstr ""
@@ -954,6 +1255,9 @@ msgstr "您確定è¦åˆªé™¤æ­¤èº«ä»½å—Ž?"
msgid "Are you sure you want to reset registration token?"
msgstr "確定è¦é‡è¨­è¨»å†Šæ†‘證嗎?"
+msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
+msgstr ""
+
msgid "Are you sure you want to reset the health check token?"
msgstr "確定è¦é‡è¨­å¥åº·æª¢æŸ¥å­˜å–憑證嗎?"
@@ -1008,9 +1312,6 @@ msgstr "已指派的議題"
msgid "Assigned Merge Requests"
msgstr "已指派的åˆä½µè«‹æ±‚"
-msgid "Assigned to :name"
-msgstr "指派給:人å"
-
msgid "Assigned to me"
msgstr "指派給我"
@@ -1026,6 +1327,9 @@ msgstr "指派列表顯示了分é…給é¸å®šä½¿ç”¨è€…的所有議題"
msgid "Assignee(s)"
msgstr "執行者"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1041,18 +1345,24 @@ msgstr "八月"
msgid "August"
msgstr "八月"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "驗證紀錄"
+msgid "Authentication failed: %{error_message}"
+msgstr ""
+
msgid "Authentication log"
msgstr "èªè­‰è¨˜éŒ„"
msgid "Authentication method"
msgstr "èªè­‰æ–¹æ³•"
+msgid "Authentication method updated"
+msgstr ""
+
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr "作者"
@@ -1071,6 +1381,9 @@ msgstr "授權"
msgid "Authorize %{link_to_client} to use your account?"
msgstr "授權 %{link_to_client} 使用您的帳號?"
+msgid "Authorized %{new_chat_name}"
+msgstr ""
+
msgid "Authorized At"
msgstr "授權於"
@@ -1110,18 +1423,12 @@ msgstr "在 %{link_to_documentation} 了解更多"
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr "自動 DevOps 管線已經啟用,且會在沒有替代 CI 組態檔案å¯ç”¨çš„情æ³ä¸‹ä½¿ç”¨ã€‚%{more_information_link}"
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr "如果 %{link_to_auto_devops_settings} 這個專案,您å¯ä»¥è‡ªå‹•å»ºç½®å’Œæ¸¬è©¦ä½ çš„æ‡‰ç”¨ç¨‹å¼ %{link_to_add_kubernetes_cluster} 則å¯ä»¥è®“你自動部署"
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr "新增 Kubernetes å¢é›†"
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr "啟用自動 DevOps"
-
msgid "Automatically marked as default internal user"
msgstr "自動標記為é è¨­æœ¬æ©Ÿä½¿ç”¨è€…"
+msgid "Automatically resolve merge request diff discussions when they become outdated"
+msgstr ""
+
msgid "Automatically resolved"
msgstr ""
@@ -1245,6 +1552,12 @@ msgstr "放棄檢閱?"
msgid "BatchComments|You're about to discard your review which will delete all of your pending comments. The deleted comments %{strong_start}cannot%{strong_end} be restored."
msgstr "ä½ å³å°‡æ”¾æ£„你的檢閱,這將刪除所有待處ç†çš„留言。刪除的留言 %{strong_start} ä¸èƒ½ %{strong_end} 復原。"
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr "從é¸å®šçš„æ交記錄開始"
@@ -1482,6 +1795,12 @@ msgstr "專案設定"
msgid "Branches|protected"
msgstr "å—ä¿è­·"
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr "ç€è¦½è³‡æ–™å¤¾"
@@ -1503,9 +1822,18 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr "ä¼æ¥­æŒ‡æ¨™ï¼ˆè‡ªè¨‚)"
+msgid "Buy EE"
+msgstr ""
+
+msgid "Buy GitLab Enterprise Edition"
+msgstr ""
+
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr "作者:"
@@ -1524,6 +1852,9 @@ msgstr "CI / CD 設定"
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI will run using the credentials assigned above."
msgstr "CI 將會使用底下指定的憑證執行。"
@@ -1557,6 +1888,9 @@ msgstr "使用定時增é‡éƒ¨ç½²ï¼ŒæŒçºŒéƒ¨ç½²åˆ°æ­£å¼ç’°å¢ƒ"
msgid "CICD|Default to Auto DevOps pipeline"
msgstr "é è¨­ä½¿ç”¨è‡ªå‹• DevOps 管線"
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr "部屬策略"
@@ -1572,6 +1906,9 @@ msgstr "自動 DevOps 管線將會在沒有替代 CI 組態檔案的情æ³ä¸‹åŸ·
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr "已啟用實例"
@@ -1587,6 +1924,9 @@ msgstr ""
msgid "Can't find HEAD commit for this branch"
msgstr "找ä¸åˆ°æ­¤åˆ†æ”¯çš„ HEAD 變更。"
+msgid "Can't remove group members without group managed account"
+msgstr ""
+
msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
msgstr ""
@@ -1599,21 +1939,36 @@ msgstr "å–消此作業"
msgid "Cannot be merged automatically"
msgstr "ä¸èƒ½è‡ªå‹•åˆä½µ"
+msgid "Cannot create the abuse report. The user has been deleted."
+msgstr ""
+
+msgid "Cannot create the abuse report. This user has been blocked."
+msgstr ""
+
msgid "Cannot modify managed Kubernetes cluster"
msgstr "無法變更被管ç†çš„ Kubernetes å¢é›†"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
+msgstr ""
+
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
+msgid "Capacity threshold"
+msgstr ""
+
msgid "Certificate"
msgstr ""
msgid "Certificate (PEM)"
msgstr ""
-msgid "Certificate fingerprint"
-msgstr "憑證指紋"
-
msgid "Change Weight"
msgstr "變更權é‡"
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1638,6 +1993,9 @@ msgstr "復原"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "這將會建立新的æ交記錄,以還原ç¾æœ‰çš„變更。"
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "è‹¥<b>來æº</b>修訂版正è¦åˆä½µåˆ°<b>目標</b>修訂版時顯示變更比較。"
@@ -1650,6 +2008,12 @@ msgstr "統計圖表"
msgid "Chat"
msgstr "å³æ™‚通訊"
+msgid "Check again"
+msgstr ""
+
+msgid "Check feature availability on namespace plan"
+msgstr ""
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "檢查 %{docs_link_start} 檔案 %{docs_link_end}"
@@ -1677,9 +2041,6 @@ msgstr "é¸æ“‡ <strong>建立å°å­˜æª”</strong> 並等待å°å­˜å®Œæˆã€‚"
msgid "Choose <strong>Next</strong> at the bottom of the page."
msgstr "é¸æ“‡é é¢åº•éƒ¨çš„ <strong>下一步</strong>。"
-msgid "Choose File ..."
-msgstr "é¸æ“‡æª”案⋯"
-
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr "é¸æ“‡åˆ†æ”¯æˆ–標籤(例如:%{master}),或者輸入æ交代碼(例如:%{sha})以檢視變更比較或建立åˆä½µè«‹æ±‚。"
@@ -1701,27 +2062,27 @@ msgstr "é¸å–é¡è‰²ã€‚"
msgid "Choose between <code>clone</code> or <code>fetch</code> to get the recent application code"
msgstr "é¸æ“‡ <code>複製</code> 或 <code>抓å–</code> 以å–得最新的應用程å¼åŽŸå§‹ç¢¼"
-msgid "Choose file..."
-msgstr "é¸æ“‡æª”案⋯"
+msgid "Choose file…"
+msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr "é¸æ“‡æ‚¨ç‰ˆæœ¬åº«åŒ¯å…¥é …目的頂級群組。"
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "é¸æ“‡æ‚¨å¸Œæœ›èˆ‡é€™å€‹æ¬¡è¦ç¯€é»žåŒæ­¥çš„群組"
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr ""
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "é¸æ“‡ä½ æƒ³è¦é€£ç·šä¸¦åŸ·è¡Œ CI / CD 管線的版本庫。"
-msgid "Choose which repositories you want to import."
-msgstr "é¸æ“‡ä½ æƒ³åŒ¯å…¥çš„版本庫。"
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "é¸æ“‡ä½ æƒ³è¦ç”¨ä¾†èˆ‡é€™å€‹æ¬¡è¦ç¯€é»žåŒæ­¥çš„碎片 (shards)。"
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "å–消"
@@ -1746,6 +2107,9 @@ msgstr "通éŽï¼Œä½†æœ‰è­¦å‘Šè¨Šæ¯"
msgid "CiStatusLabel|pending"
msgstr "等待中"
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr "已跳éŽ"
@@ -1779,6 +2143,9 @@ msgstr "通éŽ"
msgid "CiStatusText|pending"
msgstr "等待中"
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr "已跳éŽ"
@@ -1794,6 +2161,9 @@ msgstr "輸入變數值"
msgid "CiVariables|Remove variable row"
msgstr "移除變數欄"
+msgid "CiVariables|This variable will not be masked"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr "*(所有環境)"
@@ -1806,6 +2176,9 @@ msgstr "建立è¬ç”¨å­—å…ƒ"
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
+msgid "CiVariable|Masked"
+msgstr ""
+
msgid "CiVariable|New environment"
msgstr "建立新的環境"
@@ -1815,12 +2188,18 @@ msgstr "å—ä¿è­·"
msgid "CiVariable|Search environments"
msgstr "æœå°‹ç’°å¢ƒ"
+msgid "CiVariable|Toggle masked"
+msgstr ""
+
msgid "CiVariable|Toggle protected"
msgstr "切æ›ç‚ºå—ä¿è­·ç‹€æ…‹"
msgid "CiVariable|Validation failed"
msgstr "有效性驗證失敗"
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "無法使用:%{reason}"
@@ -1843,7 +2222,7 @@ msgid "Click the <strong>Download</strong> button and wait for downloading to co
msgstr "é»žé¸ <strong>下載</strong> 按鈕並等待下載完æˆã€‚"
msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone."
-msgstr "點é¸å·¦ä¸Šè§’çš„<strong>å‡ç´š</strong>按鈕,將æå‡è‡³ç¾¤çµ„里程碑。"
+msgstr "點é¸å·¦ä¸Šè§’çš„<strong>æå‡</strong>按鈕,將æå‡è‡³ç¾¤çµ„里程碑。"
msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"."
msgstr "點é¸å³å´çš„ <strong>ä¸é¸æ“‡</strong> 按鈕,因為我們åªéœ€è¦ã€ŒGoogle Code 專案寄存ã€ã€‚"
@@ -1878,6 +2257,9 @@ msgstr "複製版本庫"
msgid "Clone with %{http_label}"
msgstr ""
+msgid "Clone with KRB5"
+msgstr ""
+
msgid "Clone with SSH"
msgstr ""
@@ -1893,9 +2275,6 @@ msgstr ""
msgid "Closed"
msgstr "已關閉"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "已關閉議題"
@@ -1911,15 +2290,15 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} 已順利安è£åˆ°æ‚¨çš„ Kubernetes å¢é›†"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} 這將新增一些é¡å¤–的資æºï¼Œå¦‚負載平衡器,這å¯èƒ½æœƒç”¢ç”Ÿé¡å¤–çš„æˆæœ¬ï¼Œå…·é«”å–決於您安è£Kubernetes å¢é›†çš„æœå‹™æ供商。如果您使用的是 Google Kubernetes Engine,å¯ä»¥åƒè€ƒ %{pricingLink}。"
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr "API 網å€"
+msgid "ClusterIntegration|API URL should be a valid http/https url."
+msgstr ""
+
msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr "新增 Kubernetes å¢é›†"
@@ -1935,18 +2314,15 @@ msgstr "增加整åˆåˆ°ä½ çš„群組,以便在群組中的專案分享å¢é›†ã€‚
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Kubernetes å¢é›†æ•´åˆçš„進階設定"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "å®‰è£ Ingress 後,您需è¦åœ¨ç”Ÿæˆçš„外部 IP ä½ç½®ä¸ŠæŒ‡å‘ DNS,以便在部署後檢視您的應用程å¼ã€‚ %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "當嘗試å–得專案å€åŸŸæ™‚錯誤發生:%{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "嘗試連線到 Google Cloud API 時出錯,請ç¨å€™å†è©¦ã€‚"
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr ""
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
msgstr ""
@@ -1983,6 +2359,12 @@ msgstr "é¸æ“‡è¦å®‰è£åœ¨æ‚¨ Kubernetes å¢é›†ä¸Šçš„應用程å¼ã€‚Helm Tiller
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "é¸æ“‡è¦ä½¿ç”¨åœ¨æ­¤å¢é›†çš„環境。"
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
+msgid "ClusterIntegration|Cluster name is required."
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -1992,20 +2374,20 @@ msgstr "複製 API 連çµ"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "複製 CA 憑證"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "è¤‡è£½å…¥å£ IP ä½ç½®åˆ°å‰ªè²¼è¤²"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "複製 Jupyter 主機å稱至剪貼簿"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr "複製 Kubernetes å¢é›†å稱"
-msgid "ClusterIntegration|Copy Token"
-msgstr "複製憑證"
+msgid "ClusterIntegration|Copy Service Token"
+msgstr ""
msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr "建立 Kubernetes å¢é›†"
@@ -2052,8 +2434,8 @@ msgstr "GitLab æ•´åˆ"
msgid "ClusterIntegration|GitLab Runner"
msgstr "GitLab 執行器"
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
-msgstr "GitLab 執行器連çµå°ˆæ¡ˆçš„版本庫,並執行 CI / CD 作業ã€æŽ¨é€çµæžœä¸¦ä½ˆç½²æ‡‰ç”¨ç¨‹å¼è‡³ç”Ÿç”¢ç’°å¢ƒã€‚"
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
+msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "Google 雲端專案"
@@ -2079,14 +2461,14 @@ msgstr "éš±è—"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "如果你正在設定多個å¢é›†ä¸”正在使用自動 DevOps,%{help_link_start}請先閱讀這個說明%{help_link_end}。"
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "為了è¦é¡¯ç¤ºå¢é›†çš„å¥åº·ç‹€æ³ï¼Œæˆ‘們需è¦ç‚ºæ‚¨çš„å¢é›†è¨­å®š Prometheus 以收集所需的資訊。"
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "輸入"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "å…¥å£ IP ä½ç½®"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress 為您æ供了一種將請求伺æœå™¨æˆ–路徑路由到æœå‹™çš„方法,將多個æœå‹™é›†ä¸­åˆ°ä¸€å€‹å…¥å£é»žã€‚"
@@ -2094,15 +2476,18 @@ msgstr "Ingress 為您æ供了一種將請求伺æœå™¨æˆ–路徑路由到æœå‹™ç
msgid "ClusterIntegration|Install"
msgstr "安è£"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "å®‰è£ Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "已安è£"
msgid "ClusterIntegration|Installing"
msgstr "安è£ä¸­"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "è‡ªå‹•æ•´åˆ Kubernetes å¢é›†"
@@ -2130,7 +2515,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Knative 網域å稱"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2142,9 +2527,6 @@ msgstr "Kubernetes å¢é›†"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Kubernetes å¢é›†è©³ç´°è³‡è¨Š"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Kubernetes å¢é›†å¥åº·ç¨‹åº¦"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes å¢é›†å·²ç¶“在 Google Kubernetes Engine 建立"
@@ -2187,9 +2569,6 @@ msgstr "管ç†"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "請造訪 %{link_gke} 管ç†æ‚¨çš„ Kubernetes å¢é›†"
-msgid "ClusterIntegration|More information"
-msgstr "更多資訊"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "沒有機器類型符åˆæ‚¨çš„æœå°‹"
@@ -2202,27 +2581,24 @@ msgstr "沒有任何專案符åˆæ‚¨çš„æœå°‹"
msgid "ClusterIntegration|No zones matched your search"
msgstr "沒有å€åŸŸç¬¦åˆæ‚¨çš„æœå°‹"
-msgid "ClusterIntegration|Note:"
-msgstr "註:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "節點數"
+msgid "ClusterIntegration|Number of nodes must be a numerical value."
+msgstr ""
+
msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr "請輸入您 Kubernetes å¢é›†çš„å­˜å–資訊,若需幫助,å¯ä»¥é–±è®€é—œæ–¼ GitLab 有關 Kubernetes å¢é›†çš„說明é é¢ï¼š%{link_to_help_page}"
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "請確èªä½ çš„ Google 帳號是å¦ç¬¦åˆé€™äº›æ¢ä»¶ï¼š"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "å°æ‡‰è¬ç”¨å­—å…ƒ DNS 至生æˆçš„ IP ä½å€ï¼Œä»¥åœ¨æ‚¨æ‡‰ç”¨ç¨‹å¼ä½ˆç½²å¾Œï¼Œå­˜å–您的應用程å¼ã€‚"
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "專案å¢é›†"
-msgid "ClusterIntegration|Project namespace"
-msgstr "專案命å空間"
-
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr "專案命å空間(é¸å¡«ï¼Œä¸å¯é‡è¤‡ï¼‰"
@@ -2253,7 +2629,7 @@ msgstr "若想è¦è«‹å–代此為您自己的主機å稱。如果您這麼åšï¼Œ
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "請求開始安è£å¤±æ•—"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2289,8 +2665,11 @@ msgstr "é¸æ“‡å€åŸŸ"
msgid "ClusterIntegration|Select zone to choose machine type"
msgstr "é¸æ“‡å€åŸŸä¾†é¸æ“‡æ©Ÿå™¨é¡žåž‹"
-msgid "ClusterIntegration|Service token"
-msgstr "æœå‹™æ†‘è­‰"
+msgid "ClusterIntegration|Service Token"
+msgstr ""
+
+msgid "ClusterIntegration|Service token is required."
+msgstr ""
msgid "ClusterIntegration|Show"
msgstr "顯示"
@@ -2298,9 +2677,6 @@ msgstr "顯示"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "內部發生錯誤"
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "在 Google Kubernetes Engine 建立å¢é›†æ™‚發生錯誤"
@@ -2310,8 +2686,8 @@ msgstr "å®‰è£ %{title} 時發生錯誤"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "IP ä½å€æ­£åœ¨åˆ†é…當中。如果分é…的時間太長,請檢查您的 Kubernetes å¢é›†èˆ‡ Google Kubernetes Engine 上的é™é¡ã€‚"
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "此帳號必須è¦æœ‰ %{link_to_container_project} 底下è¦æ±‚的權é™æ‰èƒ½å»ºç«‹ Kubernetes å¢é›†"
@@ -2319,11 +2695,17 @@ msgstr "此帳號必須è¦æœ‰ %{link_to_container_project} 底下è¦æ±‚的權é™
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "æ­¤é¸é …å…許您在 RBAC å¢é›†ä¸Šå®‰è£æ‡‰ç”¨ç¨‹å¼ã€‚"
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "åˆ‡æ› Kubernetes å¢é›†"
-msgid "ClusterIntegration|Token"
-msgstr "憑證"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2361,9 +2743,6 @@ msgstr "å€åŸŸ"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "å­˜å– Google Container Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "在這裡檢查價格"
-
msgid "ClusterIntegration|documentation"
msgstr "文件"
@@ -2373,6 +2752,9 @@ msgstr "說明é é¢"
msgid "ClusterIntegration|meets the requirements"
msgstr "é”到需求"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "設定正確"
@@ -2382,15 +2764,27 @@ msgstr "註冊"
msgid "Code"
msgstr ""
+msgid "Code Owners"
+msgstr ""
+
+msgid "Code owner approval is required"
+msgstr ""
+
msgid "Code owners"
msgstr "程å¼ç¢¼æ‰€æœ‰è€…"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Cohorts"
msgid "Collapse"
msgstr "收縮"
+msgid "Collapse approvers"
+msgstr ""
+
msgid "Collapse sidebar"
msgstr "收起å´é‚Šæ¬„"
@@ -2485,6 +2879,9 @@ msgstr "æ交者:"
msgid "Commit…"
msgstr "æ交…"
+msgid "Company"
+msgstr ""
+
msgid "Compare"
msgstr "比較"
@@ -2554,6 +2951,12 @@ msgstr "設定 %{link} å¢é›†ã€‚"
msgid "Configure the way a user creates a new account."
msgstr "設定使用者建立新帳號的方å¼ã€‚"
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr "連線"
@@ -2569,12 +2972,24 @@ msgstr "連çµæ‚¨çš„外部版本庫,CI / CD 管線將會為新的æ交執行ã
msgid "Connecting..."
msgstr "正在連çµâ€¦"
+msgid "Contact an owner of group %{namespace_name} to upgrade the plan."
+msgstr ""
+
+msgid "Contact owner %{link_start}%{owner_name}%{link_end} to upgrade the plan."
+msgstr ""
+
msgid "Contact sales to upgrade"
msgstr ""
+msgid "Contact your Administrator to upgrade your license."
+msgstr ""
+
msgid "Container Registry"
msgstr "容器登錄表"
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr "已建立"
@@ -2638,6 +3053,9 @@ msgstr "è²¢ç»çµ¦GitLab"
msgid "Contribution"
msgstr "è²¢ç»"
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2665,18 +3083,9 @@ msgstr "è«‹ç¨å¾…片刻,這個é é¢åœ¨æº–備好後自動é‡æ–°æ•´ç†ã€‚"
msgid "Control the display of third party offers."
msgstr "控制第三方æ供的顯示方å¼ã€‚"
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "控制此次è¦ç¯€é»žçš„ LFS 和附加檔案的最大併發é‡"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "控制此次è¦ç¯€é»žçš„版本庫最大並行é‡"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "控制此 Geo 節點驗證æ“作的最大並行é‡"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "ConvDev 索引"
@@ -2689,6 +3098,9 @@ msgstr "複製 %{protocol} 的複製 (clone) URL"
msgid "Copy ID to clipboard"
msgstr "複製 ID 到剪貼簿"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "複製 SSH 的複製 (clone) URL"
@@ -2713,9 +3125,6 @@ msgstr "複製æ交記錄的 SHA 值到剪貼簿"
msgid "Copy file path to clipboard"
msgstr "複製檔案路徑到剪貼簿"
-msgid "Copy incoming email address to clipboard"
-msgstr "複製接收到的電å­ä¿¡ç®±ä½å€åˆ°å‰ªè²¼ç°¿"
-
msgid "Copy link"
msgstr "複製連çµ"
@@ -2731,9 +3140,33 @@ msgstr "複製至剪貼簿"
msgid "Copy token to clipboard"
msgstr "複製憑證到剪貼簿"
+msgid "Could not authorize chat nickname. Try again!"
+msgstr ""
+
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not delete chat nickname %{chat_name}."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr "無法讀管線狀態。有關故障排除的步驟,請åƒè€ƒ%{linkStart}檔案%{linkEnd}。"
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Could not revoke personal access token %{personal_access_token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr "建立"
@@ -2743,12 +3176,12 @@ msgstr "建立新資料夾"
msgid "Create New Domain"
msgstr ""
+msgid "Create a GitLab account first, and then connect it to your %{label} account."
+msgstr ""
+
msgid "Create a new branch"
msgstr "建立新分支"
-msgid "Create a new branch and merge request"
-msgstr "建立新分支åŠåˆä½µè«‹æ±‚"
-
msgid "Create a new issue"
msgstr "建立新議題"
@@ -2758,6 +3191,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "建立您帳號的個人存å–憑證以é€éŽ %{protocol} 上傳或下載。"
+msgid "Create an issue. Issues are created for each alert triggered."
+msgstr ""
+
msgid "Create branch"
msgstr "建立分支"
@@ -2782,9 +3218,6 @@ msgstr "建立群組"
msgid "Create group label"
msgstr "建立群組標籤"
-msgid "Create issue"
-msgstr "建立議題"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "建立標籤列表。å«æœ‰æ­¤æ¨™ç±¤çš„議題將會顯示於清單中。"
@@ -2854,6 +3287,9 @@ msgstr "Cron 語法"
msgid "Current Branch"
msgstr "ç›®å‰åˆ†æ”¯"
+msgid "Current Project"
+msgstr ""
+
msgid "Current node"
msgstr "ç›®å‰ç¯€é»ž"
@@ -2893,9 +3329,6 @@ msgstr "自訂把 Google Code çš„é›»å­ä¿¡ç®±ç¶²å€å’Œä½¿ç”¨è€…å稱匯入到 G
msgid "Customize language and region related settings."
msgstr ""
-msgid "Customize your merge request approval settings."
-msgstr ""
-
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "å­˜å–您執行器憑證,自訂您的管線設定,並檢視你的æµæ°´ç¾ç‹€æ…‹åŠæ¸¬è©¦æ¶µè“‹çŽ‡å ±å‘Šã€‚"
@@ -2938,6 +3371,9 @@ msgstr "全部"
msgid "DashboardProjects|Personal"
msgstr "個人"
+msgid "Dashboards"
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
@@ -2962,15 +3398,24 @@ msgstr "拒絕並登出"
msgid "Default Branch"
msgstr "é è¨­åˆ†æ”¯"
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default classification label"
msgstr "é è¨­åˆ†é¡žæ¨™ç±¤"
+msgid "Default description template for issues"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
msgid "Default first day of the week in calendars and date pickers."
msgstr ""
+msgid "Default issue template"
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr "é è¨­å€¼ï¼šç›´æŽ¥åŒ¯å…¥ Google Code çš„é›»å­éƒµä»¶ä½å€æˆ–使用者å稱"
@@ -3010,6 +3455,9 @@ msgstr "刪除片段"
msgid "Delete comment"
msgstr "刪除留言"
+msgid "Delete license"
+msgstr ""
+
msgid "Delete list"
msgstr "刪除清單"
@@ -3022,6 +3470,21 @@ msgstr ""
msgid "Deleted"
msgstr "已刪除"
+msgid "Deleted chat nickname: %{chat_name}!"
+msgstr ""
+
+msgid "Deleting the license failed."
+msgstr ""
+
+msgid "Deleting the license failed. The license was not found."
+msgstr ""
+
+msgid "Deleting the license failed. You are not permitted to perform this action."
+msgstr ""
+
+msgid "Denied authorization of chat nickname %{user_name}."
+msgstr ""
+
msgid "Deny"
msgstr "拒絕"
@@ -3032,6 +3495,9 @@ msgstr[0] "部署"
msgid "Deploy Keys"
msgstr "部署金鑰"
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr "其他 +%{count} 個"
@@ -3170,12 +3636,45 @@ msgstr "é™å†ª"
msgid "Description"
msgstr "æè¿°"
+msgid "Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}"
+msgstr ""
+
msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project."
msgstr "《æ述模æ¿ã€‹å…許你為您專案的議題或åˆä½µè«‹æ±‚之æ述欄ä½å®šç¾©ç¬¦åˆä¸Šä¸‹æ–‡çš„模æ¿ã€‚"
msgid "Description:"
msgstr "說明:"
+msgid "DesignManagement|%{current_design} of %{designs_count}"
+msgstr ""
+
+msgid "DesignManagement|Could not find design, please try again."
+msgstr ""
+
+msgid "DesignManagement|Error uploading a new design. Please try again"
+msgstr ""
+
+msgid "DesignManagement|Go back to designs"
+msgstr ""
+
+msgid "DesignManagement|Go to next design"
+msgstr ""
+
+msgid "DesignManagement|Go to previous design"
+msgstr ""
+
+msgid "DesignManagement|The one place for your designs"
+msgstr ""
+
+msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
+msgstr ""
+
+msgid "DesignManagement|Upload designs"
+msgstr ""
+
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "銷毀"
@@ -3254,6 +3753,9 @@ msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
+msgid "Discussion"
+msgstr ""
+
msgid "Dismiss"
msgstr "忽略"
@@ -3293,20 +3795,26 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download export"
+msgstr ""
+
+msgid "Download license"
+msgstr ""
+
msgid "Download tar"
-msgstr "下載 tar"
+msgstr ""
msgid "Download tar.bz2"
-msgstr "下載 tar.bz2"
+msgstr ""
msgid "Download tar.gz"
-msgstr "下載 tar.gz"
+msgstr ""
msgid "Download zip"
-msgstr "下載 zip"
+msgstr ""
msgid "DownloadArtifacts|Download"
-msgstr "下載"
+msgstr ""
msgid "DownloadCommit|Email Patches"
msgstr "é›»å­éƒµä»¶ä¿®è£œæª”案"
@@ -3338,6 +3846,9 @@ msgstr "編輯"
msgid "Edit %{name}"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr "編輯標籤"
@@ -3359,6 +3870,9 @@ msgstr ""
msgid "Edit environment"
msgstr ""
+msgid "Edit file"
+msgstr ""
+
msgid "Edit files in the editor and commit changes here"
msgstr "在編輯器中編輯檔案,並在此處æ交變更"
@@ -3371,18 +3885,54 @@ msgstr "編輯 %{user_name} 的身份"
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Elasticsearch"
msgstr "Elasticsearch"
msgid "Elasticsearch integration. Elasticsearch AWS IAM."
msgstr "Elasticsearch æ•´åˆã€‚ Elasticsearch AWS IAM。"
+msgid "Elastic|None. Select namespaces to index."
+msgstr ""
+
+msgid "Elastic|None. Select projects to index."
+msgstr ""
+
msgid "Email"
msgstr "é›»å­ä¿¡ç®±"
+msgid "Email address"
+msgstr ""
+
msgid "Email patch"
msgstr "é›»å­éƒµä»¶è£œä¸"
+msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+msgstr ""
+
+msgid "EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+msgstr ""
+
+msgid "EmailError|We couldn't find the project. Please check if there's any typo."
+msgstr ""
+
+msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+msgstr ""
+
+msgid "EmailError|Your account has been blocked. If you believe this is in error, contact a staff member."
+msgstr ""
+
msgid "Emails"
msgstr "é›»å­éƒµä»¶"
@@ -3398,6 +3948,9 @@ msgstr "啟用"
msgid "Enable Auto DevOps"
msgstr "啟用自動 DevOps"
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Pseudonymizer data collection"
msgstr "啟用 Pseudonymizer 的資料收集"
@@ -3425,6 +3978,12 @@ msgstr "為此專案啟用"
msgid "Enable group Runners"
msgstr "啟用群組執行器"
+msgid "Enable header and footer in emails"
+msgstr ""
+
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "啟用或åœç”¨ Pseudonymizer 的資料收集。"
@@ -3434,9 +3993,6 @@ msgstr "啟用或åœç”¨ç‰ˆæœ¬æª¢æŸ¥å’Œä½¿ç”¨ ping。"
msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "啟用 reCAPTCHA 或 Akismet 並設定 IP 上é™ã€‚"
-msgid "Enable self approval of merge requests"
-msgstr ""
-
msgid "Enable shared Runners"
msgstr ""
@@ -3452,26 +4008,38 @@ msgstr "å•Ÿç”¨ä½¿ç”¨æƒ…æ³ Ping"
msgid "Enable usage ping to get an overview of how you are using GitLab from a feature perspective."
msgstr "å•Ÿç”¨ä½¿ç”¨æƒ…æ³ Ping 以從功能角度來看您 GitLab 的使用情形概覽"
+msgid "Enable/disable your service desk. %{link_start}Learn more about service desk%{link_end}."
+msgstr ""
+
msgid "Enabled"
msgstr "已啟用"
-msgid "Ends at (UTC)"
-msgstr "æ–¼ (UTC) çµæŸ"
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
-msgid "Enforce SSO-only authentication for this group"
+msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
-msgid "Enforced SSO"
+msgid "End date"
+msgstr ""
+
+msgid "Ends at (UTC)"
+msgstr "æ–¼ (UTC) çµæŸ"
+
+msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr "在底下輸入您的 BitBucket 伺æœå™¨ URL 和個人存å–憑證"
msgid "Enter the issue description"
-msgstr "請輸入å•é¡Œèªªæ˜Ž"
+msgstr "請輸入議題說明"
msgid "Enter the issue title"
-msgstr "請輸入å•é¡Œæ¨™é¡Œ"
+msgstr "請輸入議題標題"
msgid "Enter the merge request description"
msgstr "請輸入åˆä½µè«‹æ±‚說明"
@@ -3479,13 +4047,7 @@ msgstr "請輸入åˆä½µè«‹æ±‚說明"
msgid "Enter the merge request title"
msgstr "請請輸入åˆä½µè«‹æ±‚標題"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
-msgid "Environment variables"
-msgstr ""
-
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -3506,6 +4068,12 @@ msgstr "抓å–環境時發生錯誤。"
msgid "Environments|An error occurred while making the request."
msgstr "建立請求時發生錯誤"
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "åœæ­¢ç’°å¢ƒæ™‚發生錯誤,請é‡è©¦"
@@ -3557,15 +4125,33 @@ msgstr "打開實æ³ç’°å¢ƒ"
msgid "Environments|Pod logs from"
msgstr "Pod 記錄由"
+msgid "Environments|Re-deploy"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr "é‡æ–°éƒ¨å±¬ç’°å¢ƒ"
msgid "Environments|Read more about environments"
msgstr "了解有關環境的更多資訊"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr "回退環境"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "顯示所有內容"
@@ -3578,6 +4164,18 @@ msgstr "åœæ­¢ç’°å¢ƒ"
msgid "Environments|Stopping"
msgstr ""
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
msgid "Environments|Updated"
msgstr "已更新"
@@ -3629,6 +4227,9 @@ msgstr "錯誤報告與記錄"
msgid "Error Tracking"
msgstr ""
+msgid "Error creating a new path"
+msgstr ""
+
msgid "Error creating epic"
msgstr "建立 epic 時發生錯誤"
@@ -3677,6 +4278,18 @@ msgstr "載入模æ¿æ™‚發生錯誤。"
msgid "Error occurred when toggling the notification subscription"
msgstr "切æ›è¨‚閱通知時發生錯誤"
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3692,12 +4305,48 @@ msgstr "更新所有待辦事項的狀態時發生錯誤"
msgid "Error updating todo status."
msgstr "更新待辦事項狀態時發生錯誤。"
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr "載入åˆä½µè«‹æ±‚時出錯。請é‡è©¦ã€‚"
+msgid "Error while migrating %{upload_id}: %{error_message}"
+msgstr ""
+
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3761,12 +4410,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3776,12 +4419,18 @@ msgstr "展開"
msgid "Expand all"
msgstr "展開全部"
+msgid "Expand approvers"
+msgstr ""
+
msgid "Expand sidebar"
msgstr "展開å´é‚Šæ¬„"
msgid "Expiration date"
msgstr "截止日期"
+msgid "Expired"
+msgstr ""
+
msgid "Expired %{expiredOn}"
msgstr ""
@@ -3815,6 +4464,12 @@ msgstr ""
msgid "Export issues"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "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."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr "外部分類策略èªè­‰"
@@ -3833,9 +4488,6 @@ msgstr "外部èªè­‰æ‹’絕存å–這個專案。"
msgid "External authorization request timeout"
msgstr "外部èªè­‰è«‹æ±‚等待éŽä¹…"
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr "分類標籤"
-
msgid "ExternalAuthorizationService|Classification label"
msgstr "分類標籤"
@@ -3857,17 +4509,23 @@ msgstr "無法變更所有者"
msgid "Failed to check related branches."
msgstr "檢查相關分支失敗。"
+msgid "Failed to create resources"
+msgstr ""
+
msgid "Failed to deploy to"
msgstr "無法部署到"
msgid "Failed to load emoji list."
msgstr "無法讀å–表情符號列表。"
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
+msgstr ""
+
+msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
msgid "Failed to remove issue from board, please try again."
-msgstr "從看æ¿åˆªé™¤è­°é¡Œæ™‚發生錯誤,請ç¨å€™é‡è©¦"
+msgstr "從看æ¿åˆªé™¤è­°é¡Œæ™‚發生錯誤,請ç¨å€™é‡è©¦ã€‚"
msgid "Failed to remove mirror."
msgstr "未能刪除é¡åƒ"
@@ -3875,9 +4533,24 @@ msgstr "未能刪除é¡åƒ"
msgid "Failed to remove the pipeline schedule"
msgstr "無法移除排程管線"
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to reset key. Please try again."
msgstr ""
+msgid "Failed to save new settings"
+msgstr ""
+
+msgid "Failed to save preferences (%{error_message})."
+msgstr ""
+
+msgid "Failed to save preferences."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr "無法é€éŽ smartcard 進行èªè­‰"
@@ -3890,12 +4563,18 @@ msgstr ""
msgid "Failure"
msgstr "失敗"
+msgid "Fast-forward merge"
+msgstr ""
+
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr "較快,因為它å†æ¬¡ä½¿ç”¨å°ˆæ¡ˆå·¥ä½œç©ºé–“ (如果它ä¸å­˜åœ¨ï¼Œå‰‡è¿”回到複製)"
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feature Flags"
msgstr "功能標籤"
@@ -3929,9 +4608,6 @@ msgstr ""
msgid "FeatureFlags|Description"
msgstr "說明"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "編輯 %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "編輯功能標籤"
@@ -3989,8 +4665,8 @@ msgstr "æ–°"
msgid "FeatureFlags|New Feature Flag"
msgstr "新增功能標籤"
-msgid "FeatureFlags|Save changes"
-msgstr "儲存"
+msgid "FeatureFlags|Protected"
+msgstr ""
msgid "FeatureFlags|Status"
msgstr "狀態"
@@ -4016,6 +4692,18 @@ msgstr "二月"
msgid "February"
msgstr "二月"
+msgid "Fetching incoming email"
+msgstr ""
+
+msgid "Fetching licenses failed."
+msgstr ""
+
+msgid "Fetching licenses failed. The request endpoint was not found."
+msgstr ""
+
+msgid "Fetching licenses failed. You are not permitted to perform this action."
+msgstr ""
+
msgid "Fields on this page are now uneditable, you can configure"
msgstr "æ­¤é é¢çš„欄ä½ç›®å‰ç„¡æ³•ç·¨è¼¯ï¼Œä½†ä½ èƒ½è¨­å®š"
@@ -4077,9 +4765,6 @@ msgstr ""
msgid "Filter..."
msgstr "篩é¸â€¦"
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "ä¾è·¯å¾‘æœå°‹"
@@ -4095,6 +4780,9 @@ msgstr "找到下載的ZIP檔案並解壓縮它。"
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "找到新æå–çš„ <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> 檔案。"
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "數ä½æŒ‡ç´‹:"
@@ -4188,14 +4876,20 @@ msgstr ""
msgid "Format"
msgstr "æ ¼å¼"
+msgid "Forward external support email address to"
+msgstr ""
+
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr "在您的 .gitlab-ci.yml 中找到錯誤:"
msgid "Free Trial of GitLab.com Gold"
msgstr "å…費試用 GitLab.com 黃金計畫"
-msgid "From %{provider_title}"
-msgstr "來自 %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "從 Bitbucket"
@@ -4224,12 +4918,21 @@ msgstr "來自里程碑:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "從 Kubernetes å¢é›†è©³ç´°è³‡æ–™é ï¼Œå¾žæ‡‰ç”¨ç¨‹å¼åˆ—表中安è£åŸ·è¡Œå™¨"
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG 金鑰"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "一般"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr "一般管線"
@@ -4239,6 +4942,9 @@ msgstr "產生é è¨­æ¨™ç±¤é›†"
msgid "Generate key"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr "地ç†ä½ç½®"
@@ -4263,9 +4969,6 @@ msgstr "資料已經éŽæœŸäº† %{timeago} 之久"
msgid "GeoNodes|Data replication lag"
msgstr "資料複製延é²"
-msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?"
-msgstr "åœç”¨ç¯€é»žå°‡æœƒåœæ­¢åŒæ­¥ç¨‹å¼ï¼Œæ‚¨ç¢ºå®šè¦é€™éº¼åšå—Žï¼Ÿ"
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "ä¸ç¬¦åˆä¸»è¦å„²å­˜ç©ºé–“設定"
@@ -4284,6 +4987,9 @@ msgstr "GitLab 版本與主è¦ç¯€é»žç‰ˆæœ¬ä¸ç¬¦"
msgid "GeoNodes|Health status"
msgstr "å¥åº·ç‹€æ³"
+msgid "GeoNodes|Internal URL"
+msgstr ""
+
msgid "GeoNodes|Last event ID processed by cursor"
msgstr "利用游標處ç†æœ€å¾Œä¸€å€‹äº‹ä»¶ ID"
@@ -4329,6 +5035,9 @@ msgstr "未檢查雜湊值"
msgid "GeoNodes|Out of sync"
msgstr "ä¸åŒæ­¥"
+msgid "GeoNodes|Pausing replication stops the sync process."
+msgstr ""
+
msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?"
msgstr ""
@@ -4437,9 +5146,24 @@ msgstr "正在計畫é‡æ–°åŒæ­¥æ‰€æœ‰å°ˆæ¡ˆ"
msgid "Geo|Batch operations"
msgstr "批次æ“作"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "無法從存在專案移除追蹤項目。"
+msgid "Geo|Could not remove tracking entry for an existing upload."
+msgstr ""
+
msgid "Geo|Failed"
msgstr "失敗"
@@ -4455,6 +5179,9 @@ msgstr "è¦åŒæ­¥çš„群組"
msgid "Geo|In sync"
msgstr "åŒæ­¥ä¸­"
+msgid "Geo|Internal URL"
+msgstr ""
+
msgid "Geo|Last repository check run"
msgstr ""
@@ -4485,6 +5212,12 @@ msgstr "等待åŒæ­¥ä¸­"
msgid "Geo|Pending verification"
msgstr "等待驗證中"
+msgid "Geo|Please refer to Geo Troubleshooting."
+msgstr ""
+
+msgid "Geo|Project"
+msgstr ""
+
msgid "Geo|Project (ID: %{project_id}) no longer exists on the primary. It is safe to remove this entry, as this will not remove any data on disk."
msgstr "專案 (ID: %{project_id}) 已經在主è¦å€åŸŸä¸Šæ°¸ä¹…消失了。您å¯ä»¥å®‰å…¨ç§»é™¤æ­¤é …目,其將ä¸æœƒç§»é™¤ç¡¬ç¢Ÿä¸Šçš„任何資料。"
@@ -4524,24 +5257,45 @@ msgstr "é‡è©¦æ¬¡æ•¸"
msgid "Geo|Select groups to replicate."
msgstr "é¸æ“‡è¦è¤‡è£½çš„群組。"
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "è¦åŒæ­¥çš„碎片"
msgid "Geo|Status"
msgstr "狀態"
+msgid "Geo|Sync"
+msgstr ""
+
msgid "Geo|Synced"
msgstr "å·²åŒæ­¥"
+msgid "Geo|Synced at"
+msgstr ""
+
msgid "Geo|Synchronization failed - %{error}"
msgstr "åŒæ­¥å¤±æ•— - %{error}"
+msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set"
+msgstr ""
+
+msgid "Geo|This is a primary node"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "專案 (%{project_id}) 的追蹤項目已經順利移除。"
+msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
+msgstr ""
+
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "將會移除追蹤項目,確定?"
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "未知狀態"
@@ -4560,9 +5314,6 @@ msgstr "ç›®å‰æ­£åœ¨ç€è¦½æ¬¡è¦çš„ Geo <b>唯讀</b>節點,如果你想è¦é€²
msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page."
msgstr "ç›®å‰æ­£åœ¨ç€è¦½æ¬¡è¦çš„ Geo <b>唯讀</b>節點,你å¯ä»¥åœ¨æ­¤é é¢é€²è¡Œæœ‰é™çš„變更或執行有é™çš„æ“作。"
-msgid "Geo|You need a different license to use Geo replication"
-msgstr "您需è¦ä¸åŒçš„授權以使用 Geo 副本"
-
msgid "Geo|misconfigured"
msgstr "設定錯誤"
@@ -4584,6 +5335,9 @@ msgstr ""
msgid "Git"
msgstr "Git"
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -4605,6 +5359,9 @@ msgstr "GitHub 匯入"
msgid "GitLab CI Linter has been moved"
msgstr "GitLab CI Linter 已經æ¬ç§»"
+msgid "GitLab Enterprise Edition %{plan}"
+msgstr ""
+
msgid "GitLab Geo"
msgstr "GitLab Geo"
@@ -4620,12 +5377,18 @@ msgstr ""
msgid "GitLab User"
msgstr "GitLab 使用者"
+msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license."
+msgstr ""
+
msgid "GitLab metadata URL"
msgstr ""
msgid "GitLab project export"
msgstr "匯出 GitLab 專案"
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab single sign on URL"
msgstr "GitLab URL 單一登入"
@@ -4636,7 +5399,7 @@ msgid "GitLab.com import"
msgstr "匯入 GitLab.com"
msgid "GitLab’s issue tracker"
-msgstr "GitLab çš„å•é¡Œè¿½è¹¤å™¨"
+msgstr "GitLab 的議題追蹤器"
msgid "Gitaly"
msgstr "Gitaly"
@@ -4659,15 +5422,24 @@ msgstr ""
msgid "Go Back"
msgstr "返回"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "上一é "
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr "å‰å¾€"
msgid "Go to %{link_to_google_takeout}."
msgstr "å‰å¾€ %{link_to_google_takeout}。"
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr "匯入 Google Code"
@@ -4686,9 +5458,18 @@ msgstr ""
msgid "Graph"
msgstr "圖表"
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr "群組"
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr "群組 CI / CD 設定"
@@ -4698,6 +5479,9 @@ msgstr "群組 Git LFS 狀態:"
msgid "Group ID"
msgstr "群組 ID"
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "群組執行器"
@@ -4731,6 +5515,9 @@ msgstr "群組å稱"
msgid "Group overview content"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
@@ -4749,6 +5536,9 @@ msgstr "抱歉,沒有任何 Epic 符åˆæ‚¨çš„æœå°‹ã€‚"
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "開發è—圖顯示時間軸上您的 Epic 進度"
+msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
+msgstr ""
+
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr ""
@@ -4758,6 +5548,72 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "直到 %{dateWord}"
+msgid "GroupSAML|Certificate fingerprint"
+msgstr ""
+
+msgid "GroupSAML|Enable SAML authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce SSO-only authentication for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
+msgid "GroupSAML|Enforced SSO"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token"
+msgstr ""
+
+msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
+msgstr ""
+
+msgid "GroupSAML|Group managed accounts"
+msgstr ""
+
+msgid "GroupSAML|Identity provider single sign on URL"
+msgstr ""
+
+msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
+msgstr ""
+
+msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
+msgstr ""
+
+msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On"
+msgstr ""
+
+msgid "GroupSAML|SAML Single Sign On Settings"
+msgstr ""
+
+msgid "GroupSAML|SCIM API endpoint URL"
+msgstr ""
+
+msgid "GroupSAML|SCIM Token"
+msgstr ""
+
+msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
+msgstr ""
+
+msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
+msgstr ""
+
+msgid "GroupSAML|Toggle SAML authentication"
+msgstr ""
+
+msgid "GroupSAML|Your SCIM token"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "徵章"
@@ -4767,18 +5623,30 @@ msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr "自訂您的群組徵章。"
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "了解關於徵章的更多資訊。"
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "ç¦æ­¢èˆ‡å…¶ä»–群組分享 %{group} 中的專案"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "這個設定已經套用至 %{ancestor_group},並已覆蓋了å­ç¾¤çµ„設定。"
@@ -4872,6 +5740,9 @@ msgstr "ä¾å稱æœå°‹"
msgid "Have your users email"
msgstr "有來自您使用者的信件"
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr "標頭訊æ¯"
@@ -4911,6 +5782,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr "éš±è—主機金鑰手動輸入"
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr "éš±è—酬載"
@@ -4921,12 +5795,27 @@ msgstr[0] "éš±è—數值"
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr "æ­·å²è¨˜éŒ„"
+msgid "Hook was successfully created."
+msgstr ""
+
+msgid "Hook was successfully updated."
+msgstr ""
+
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr "已順利開始維護程åº"
+msgid "Housekeeping, export, path, transfer, remove, archive."
+msgstr ""
+
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
@@ -4984,15 +5873,18 @@ msgstr "識別碼"
msgid "Identities"
msgstr "身份"
-msgid "Identity provider single sign on URL"
-msgstr "身份æ供者的 URL 單一登入"
-
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
+msgstr ""
+
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr "如果åœç”¨ï¼Œå‰‡ä¸æœƒä½¿ç”¨é ç«¯å‰¯æœ¬çš„æ交自動更新分支的本機分支,以防止本機資料被刪除。如果é è¨­åˆ†æ”¯ (%{default_branch}) 已被å†åˆ†æ”¯ä¸”無法更新時,則é¡åƒå°‡æœƒå¤±æ•—。其他å†åˆ†æ”¯çš„分支自動被忽略。"
+msgid "If disabled, only admins will be able to set up mirrors in projects."
+msgstr ""
+
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr "如果ç¦ç”¨ï¼Œå­˜å–等級將å–決於使用者在專案中的權é™ã€‚"
@@ -5005,9 +5897,6 @@ msgstr "如果啟用,將會使用其分類標籤在外部æœå‹™ä¸Šé©—證專案
msgid "If using GitHub, you’ll see pipeline statuses on GitHub for your commits and pull requests. %{more_info_link}"
msgstr "如果使用 GitHub,你將會看到用於æ交與推é€è«‹æ±‚çš„ GitHub 管線狀態。%{more_info_link}"
-msgid "If you already have files you can push them using the %{link_to_cli} below."
-msgstr "如果你已經æ“有檔案,你å¯ä»¥ä½¿ç”¨ %{link_to_cli} 推é€ä»–們。"
-
msgid "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>."
msgstr "如果你的 HTTP 版本庫沒有公開存å–,請增加驗證欄ä½åˆ°ç¶²å€ä¸Šï¼š<code>https://username:password@gitlab.company.com/group/project.git</code>."
@@ -5033,7 +5922,7 @@ msgid "Import Projects from Gitea"
msgstr "從 Gitea 匯入專案"
msgid "Import all compatible projects"
-msgstr "匯入所有相容的項目"
+msgstr "匯入所有相容的專案"
msgid "Import all projects"
msgstr "匯入所有專案"
@@ -5092,12 +5981,30 @@ msgstr "匯入版本庫"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "連線版本庫自"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "改善 GitLab ä¼æ¥­ç‰ˆæœ¬çš„議題看æ¿ã€‚"
+msgid "Improve Merge Requests and customer support with GitLab Enterprise Edition."
+msgstr ""
+
msgid "Improve issues management with Issue weight and GitLab Enterprise Edition."
msgstr "é€éŽè­°é¡Œæ¬Šé‡å’Œ GitLab ä¼æ¥­ç‰ˆæœ¬æ”¹å–„議題管ç†ã€‚"
@@ -5113,15 +6020,24 @@ msgstr ""
msgid "In the next step, you'll be able to select the projects you want to import."
msgstr "在下一步中,你將能é¸æ“‡è¦åŒ¯å…¥çš„專案。"
+msgid "Incidents"
+msgstr ""
+
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr "包括所有使用者必須接å—çš„æœå‹™æ¢æ¬¾å”議和隱ç§æ”¿ç­–。"
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "如果需è¦ï¼Œè«‹åœ¨URL中夾帶使用者å稱: <code>https://username@gitlab.company.com/group/project.git</code>。"
+msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
+msgstr ""
+
msgid "Includes an MVC structure to help you get started."
msgstr ""
@@ -5137,6 +6053,9 @@ msgstr "ä¸ç›¸å®¹çš„專案"
msgid "Indicates whether this runner can pick jobs without tags"
msgstr "指示這個執行器是å¦èƒ½é¸æ“‡æ²’有標籤的工作"
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Inline"
msgstr "內嵌"
@@ -5146,9 +6065,18 @@ msgstr "手動輸入主機金鑰"
msgid "Input your repository URL"
msgstr "輸入您的版本庫URL"
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
+msgid "Insights"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr "å®‰è£ GitLab 執行器"
@@ -5168,6 +6096,9 @@ msgstr "實例統計å¯è¦‹åº¦"
msgid "Instance does not support multiple Kubernetes clusters"
msgstr "主機ä¸æ”¯æ´å¤šå€‹ Kubernetes å¢é›†"
+msgid "Instance license"
+msgstr ""
+
msgid "Integrations"
msgstr "æ•´åˆ"
@@ -5198,6 +6129,21 @@ msgstr "週期分æžç°¡ä»‹"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
+msgid "Invalid pin code"
+msgstr ""
+
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -5216,8 +6162,11 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
-msgstr "å•é¡Œ"
+msgstr "議題"
msgid "Issue Boards"
msgstr "議題看æ¿"
@@ -5228,6 +6177,9 @@ msgstr "議題看æ¿ç„¦é»žæ¨¡å¼"
msgid "Issue events"
msgstr "議題事件"
+msgid "Issue template (optional)"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "看æ¿"
@@ -5253,13 +6205,16 @@ msgid "Issues"
msgstr "議題"
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
-msgstr "è­°é¡Œå¯ä»¥è¨Žè«–臭蟲ã€å·¥ä½œä»¥åŠæƒ³æ³•ã€‚此外,å•é¡Œæ˜¯å¯æœå°‹å’ŒéŽæ¿¾çš„。"
+msgstr "è­°é¡Œå¯ä»¥æ˜¯è‡­èŸ²ã€ä»»å‹™æˆ–者想法,便於使用者討論它。此外,議題是å¯æœå°‹å’ŒéŽæ¿¾çš„。"
msgid "Issues closed"
msgstr "議題已關閉"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "å•é¡Œã€åˆä½µè«‹æ±‚ã€æŽ¨é€å’Œç•™è¨€"
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr "在您為專案建立議題後,我們開始追蹤並顯示它們的指標"
@@ -5303,12 +6258,30 @@ msgstr "一月"
msgid "Job"
msgstr "作業"
+msgid "Job Failed #%{build_id}"
+msgstr ""
+
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr "工作已被抹除"
+msgid "Job has been successfully erased!"
+msgstr ""
+
+msgid "Job has wrong arguments format."
+msgstr ""
+
+msgid "Job is missing the `model_type` argument."
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -5339,6 +6312,9 @@ msgstr "工作|工作已被刪除,刪除者:"
msgid "Job|Keep"
msgstr "工作|ä¿æŒ"
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr "工作|移到底部"
@@ -5354,9 +6330,21 @@ msgstr "已刪除æˆå“"
msgid "Job|The artifacts will be removed"
msgstr ""
+msgid "Job|This job failed because the necessary resources were not successfully created."
+msgstr ""
+
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr "七月"
@@ -5399,9 +6387,15 @@ msgstr "Kubernetes 已設定"
msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
msgstr "Kubernetes æœå‹™æ•´åˆå·²è¢«æ£„用。%{deprecated_message_content} 而ç¾åœ¨æ‚¨çš„ Kubernetes å¢é›†å·²æ”¹ç”¨æ–°çš„ <a href=\"%{url}\"/>Kubernetes å¢é›†</a> é é¢"
+msgid "LDAP settings"
+msgstr ""
+
msgid "LFS"
msgstr "LFS"
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -5415,7 +6409,16 @@ msgid "Label actions dropdown"
msgstr "標籤æ“作下拉é¸å–®"
msgid "Label lists show all issues with the selected label."
-msgstr "標籤列表顯示了é¸å–標籤的所有å•é¡Œã€‚"
+msgstr "標籤列表顯示了被貼上這標籤的所有議題。"
+
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr "%{firstLabelName} +%{remainingLabelCount} 更多"
@@ -5430,7 +6433,7 @@ msgid "Labels"
msgstr "標籤"
msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
-msgstr "標籤å¯ä»¥æ‡‰ç”¨æ–¼ %{features}。群組標籤å¯ä»¥ç”¨æ–¼ä»»ä½•ç¾¤çµ„內的項目。"
+msgstr "標籤å¯ä»¥å¥—用於 %{features}。群組標籤å¯ä»¥ç”¨æ–¼ç¾¤çµ„內的任何專案。"
msgid "Labels can be applied to issues and merge requests to categorize them."
msgstr "標籤å¯ä»¥å¥—用到議題和åˆä½µè«‹æ±‚以分門別類它們。"
@@ -5447,6 +6450,9 @@ msgstr "æå‡æ¨™ç±¤"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "æå‡ %{labelTitle} å°‡å¯ä»¥è®“所有 %{groupName} 內的專案都使用此標籤。ç¾æœ‰ç›¸åŒå稱的標籤將被åˆä½µã€‚該æ“作ä¸å¯å¾©åŽŸã€‚"
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr "大檔案儲存"
@@ -5517,18 +6523,18 @@ msgstr "了解更多端於 Kubernetes 的資訊"
msgid "Learn more about Web Terminal"
msgstr ""
+msgid "Learn more about approvals."
+msgstr ""
+
msgid "Learn more about custom project templates"
msgstr ""
msgid "Learn more about group-level project templates"
msgstr ""
-msgid "Learn more about incoming email addresses"
+msgid "Learn more about signing commits"
msgstr ""
-msgid "Learn more about protected branches"
-msgstr "進一步了解å—ä¿è­·çš„分支"
-
msgid "Learn more in the"
msgstr "進一步了解於"
@@ -5538,6 +6544,9 @@ msgstr "排程管線說明文件"
msgid "Leave"
msgstr "退出"
+msgid "Leave edit mode? All unsaved changes will be lost."
+msgstr ""
+
msgid "Leave group"
msgstr "退出群組"
@@ -5622,9 +6631,15 @@ msgstr "URL"
msgid "LicenseManagement|You are about to remove the license, %{name}, from this project."
msgstr "您正打算從此專案移除此授權:%{name}"
+msgid "Licensed to"
+msgstr ""
+
msgid "Licenses"
msgstr "授權"
+msgid "Limit namespaces and projects that can be indexed"
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "é™åˆ¶æœ€å¤šé¡¯ç¤º %d 個活動"
@@ -5641,6 +6656,9 @@ msgstr "列出您的 Gitea 版本庫"
msgid "List available repositories"
msgstr "列出å¯ç”¨çš„版本庫"
+msgid "List of IPs and CIDRs of allowed secondary nodes. Comma-separated, e.g. \"1.1.1.1, 2.2.2.0/24\""
+msgstr ""
+
msgid "List view"
msgstr "列表顯示"
@@ -5698,17 +6716,38 @@ msgstr "《鎖定》æ供了鎖定特定檔案或資料夾的能力。"
msgid "Login with smartcard"
msgstr "使用 smartcard 登入"
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr "記錄檔"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr ""
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "無論身在何處,都能讓您團隊中的æ¯å€‹äººéƒ½æ›´æœ‰æ•ˆçŽ‡ã€‚GitLab Geo 建立了您 GitLab 主機的唯讀é¡åƒï¼Œæ‰€ä»¥æ‚¨å¯ä»¥ç¸®çŸ­è¤‡è£½å’ŒæŠ“å–大型版本庫的時間。"
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr "請確ä¿æ‚¨å·²ç¶“登入æ“有您è¦åŒ¯å…¥çš„專案的帳號。"
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
-msgstr "使用細粒度的訪å•æŽ§åˆ¶ä¾†ç®¡ç† Git 版本庫,從而確ä¿æ‚¨çš„程å¼ç¢¼å®‰å…¨ã€‚執行程å¼ç¢¼æª¢é–±ä¸¦å¢žå¼·èˆ‡åˆä½µè«‹æ±‚çš„åˆä½œã€‚æ¯å€‹å°ˆæ¡ˆé‚„å¯ä»¥æœ‰å•é¡Œè¿½è¹¤å’Œç¶­åŸºç™¾ç§‘。"
+msgstr "使用細粒度的存å–æŽ§åˆ¶ä¾†ç®¡ç† Git 版本庫以確ä¿æ‚¨çš„程å¼ç¢¼å®‰å…¨ã€‚執行程å¼ç¢¼å¯©é–±ä¸¦å¢žå¼·èˆ‡åˆä½µè«‹æ±‚çš„å”作。æ¯å€‹å°ˆæ¡ˆé‚„å¯ä»¥æœ‰è­°é¡Œè¿½è¹¤å’Œ Wiki。"
msgid "Manage Web IDE features"
msgstr "ç®¡ç† Web IDE 功能"
@@ -5737,15 +6776,15 @@ msgstr "管ç†å°ˆæ¡ˆæ¨™ç±¤"
msgid "Manage two-factor authentication"
msgstr ""
-msgid "Manage your group’s membership while adding another level of security with SAML."
-msgstr "當é€éŽ SAML 增加其他安全性等級時,管ç†æ‚¨çš„群組æˆå“¡èº«ä»½ã€‚"
-
msgid "Manifest"
msgstr "Manifest"
msgid "Manifest file import"
msgstr "匯入 Manifest 檔案"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "å°‡ FogBugz 的帳號å°æ‡‰åˆ° GitLab 使用者"
@@ -5770,47 +6809,44 @@ msgstr "標記「å³å°‡å®Œæˆã€ç‚ºå®Œæˆã€‚"
msgid "Markdown"
msgstr ""
+msgid "Markdown Help"
+msgstr ""
+
msgid "Markdown enabled"
msgstr "已啟用 Markdown"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "增加項目清單"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "增加連çµ"
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "增加åºåˆ—清單"
+msgid "Maven Metadata"
+msgstr "Maven 詮釋資料"
-msgid "MarkdownToolbar|Add a table"
-msgstr "增加表格"
+msgid "Max access level"
+msgstr "最大存å–等級"
-msgid "MarkdownToolbar|Add a task list"
-msgstr "增加作業清單"
+msgid "Max seats used"
+msgstr ""
-msgid "MarkdownToolbar|Add bold text"
-msgstr "增加粗體文字"
+msgid "Maximum artifacts size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Add italic text"
-msgstr "增加斜體文字"
+msgid "Maximum attachment size (MB)"
+msgstr ""
-msgid "MarkdownToolbar|Go full screen"
-msgstr "全螢幕顯示"
+msgid "Maximum capacity"
+msgstr ""
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "æ’入引用"
+msgid "Maximum delay (Minutes)"
+msgstr ""
-msgid "MarkdownToolbar|Insert code"
-msgstr "æ’入程å¼ç¢¼"
+msgid "Maximum job timeout"
+msgstr "最大工作逾時"
-msgid "Maven Metadata"
-msgstr "Maven 詮釋資料"
+msgid "Maximum number of mirrors that can be synchronizing at the same time."
+msgstr ""
-msgid "Max access level"
-msgstr "最大存å–等級"
+msgid "Maximum push size (MB)"
+msgstr ""
-msgid "Maximum job timeout"
-msgstr "最大工作逾時"
+msgid "Maximum time between updates that a mirror can have when scheduled to synchronize."
+msgstr ""
msgid "May"
msgstr "五月"
@@ -5833,21 +6869,27 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
-msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
-msgstr "當登入至您的群組,æˆå“¡å€‘將會被轉移到這裡。從您的身份æä¾›å–得這個,其å¯èƒ½è¢«ç¨±ç‚ºã€ŒSSO æœå‹™ä½ç½®ã€ã€ã€ŒSAML 憑證頒發節點ã€æˆ–「SAML 2.0/W-Federation URLã€ã€‚"
-
msgid "Merge Request"
msgstr "åˆä½µè«‹æ±‚"
+msgid "Merge Request Approvals"
+msgstr ""
+
msgid "Merge Requests"
msgstr "åˆä½µè«‹æ±‚"
msgid "Merge Requests created"
msgstr "已建立åˆä½µè«‹æ±‚"
+msgid "Merge commit"
+msgstr ""
+
msgid "Merge commit message"
msgstr ""
+msgid "Merge commit with semi-linear history"
+msgstr ""
+
msgid "Merge events"
msgstr "åˆä½µäº‹ä»¶"
@@ -5857,12 +6899,21 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge method"
+msgstr ""
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
+msgstr ""
+
msgid "Merge request"
msgstr "åˆä½µè«‹æ±‚"
msgid "Merge request approvals"
msgstr "批准åˆä½µè«‹æ±‚"
+msgid "Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that will need to approve every merge request in a project."
+msgstr ""
+
msgid "Merge requests"
msgstr "åˆä½µè«‹æ±‚"
@@ -5897,7 +6948,7 @@ msgid "MergeRequests|Reply..."
msgstr ""
msgid "MergeRequests|Resolve this discussion in a new issue"
-msgstr "在新議題中解決此討論"
+msgstr "çµæŸè©²è¨Žè«–,並為它å¦å»ºè­°é¡Œã€‚"
msgid "MergeRequests|Saving the comment failed"
msgstr "儲存留言失敗"
@@ -5930,7 +6981,10 @@ msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{lin
msgstr ""
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
-msgstr "%{paragraphStart} 將說明改為 %{descriptionChangedTimes} : %{timeDifferenceMinutes} %{paragraphEnd}"
+msgstr "%{paragraphStart} %{timeDifferenceMinutes} 修改了 %{descriptionChangedTimes} 次說明 %{paragraphEnd}"
+
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
msgid "MergeRequest|Filter files"
msgstr "篩é¸æª”案"
@@ -5938,12 +6992,12 @@ msgstr "篩é¸æª”案"
msgid "MergeRequest|No files found"
msgstr "沒有找到任何檔案"
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr "å·²åˆä½µ"
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr "訊æ¯"
@@ -6001,14 +7055,17 @@ msgstr "建立指標"
msgid "Metrics|No deployed environments"
msgstr "沒有已經佈署的環境"
+msgid "Metrics|Not enough data to display"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
msgid "Metrics|Prometheus Query Documentation"
msgstr "Prometheus 查詢檔案"
-msgid "Metrics|System"
-msgstr "系統"
+msgid "Metrics|Show last"
+msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "å–得環境資訊時發生錯誤,請é‡è©¦ã€‚"
@@ -6049,6 +7106,12 @@ msgstr ""
msgid "Metrics|e.g. Throughput"
msgstr "例如:Throughput"
+msgid "Migrated %{success_count}/%{total_count} files."
+msgstr ""
+
+msgid "Migration successful."
+msgstr ""
+
msgid "Milestone"
msgstr "里程碑"
@@ -6086,7 +7149,7 @@ msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
msgstr "å°‡ %{milestoneTitle} æå‡æˆç¾¤çµ„里程碑?"
msgid "Milestones|Promote Milestone"
-msgstr "推動里程碑"
+msgstr "æå‡é‡Œç¨‹ç¢‘"
msgid "Milestones|Promoting %{milestone} will make it available for all projects inside %{groupName}. Existing project milestones with the same name will be merged. "
msgstr "æå‡ %{milestone} 後,所有 %{groupName} 裡的專案都å¯ä»¥ä½¿ç”¨è©²é‡Œç¨‹ç¢‘。如果已經有相åŒå稱的里程碑將會兩者åˆä½µã€‚ "
@@ -6094,6 +7157,9 @@ msgstr "æå‡ %{milestone} 後,所有 %{groupName} 裡的專案都å¯ä»¥ä½¿ç”
msgid "Milestones|This action cannot be reversed."
msgstr "這動作無法復原。"
+msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
+msgstr ""
+
msgid "Mirror a repository"
msgstr "å°ä¸€å€‹ç‰ˆæœ¬åº«é¡åƒ"
@@ -6112,6 +7178,9 @@ msgstr "å·²é¡åƒçš„版本庫"
msgid "Mirroring repositories"
msgstr "正在é¡åƒç‰ˆæœ¬åº«"
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "新增 SSH 金鑰"
@@ -6121,6 +7190,9 @@ msgstr "å–消"
msgid "Modal|Close"
msgstr "關閉"
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -6154,9 +7226,15 @@ msgstr "更多資訊"
msgid "More information is available|here"
msgstr "此處"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "最多星數"
+msgid "Mount point %{mounted_as} not found in %{model_class}."
+msgstr ""
+
msgid "Move"
msgstr "移動"
@@ -6166,6 +7244,12 @@ msgstr "移動議題"
msgid "Multiple issue boards"
msgstr "多個議題看æ¿"
+msgid "Multiple model types found: %{model_types}"
+msgstr ""
+
+msgid "Multiple uploaders found: %{uploader_types}"
+msgstr ""
+
msgid "Name"
msgstr "å稱"
@@ -6178,6 +7262,12 @@ msgstr "é€éŽæ¨™é¡Œç‚ºæ‚¨çš„個人密鑰命å"
msgid "Name:"
msgstr "å稱:"
+msgid "Namespaces to index"
+msgstr ""
+
+msgid "Naming, topics, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "å稱,å¯è¦‹æ€§"
@@ -6230,21 +7320,24 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
+msgid "New Password"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr "建立排程管線"
msgid "New Snippet"
msgstr "新增片段"
-msgid "New Snippets"
-msgstr "新增片段"
-
msgid "New branch"
msgstr "新分支"
msgid "New branch unavailable"
msgstr "無法使用新分支"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "新增資料夾"
@@ -6260,6 +7353,9 @@ msgstr "新增檔案"
msgid "New group"
msgstr "新增群組"
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr "新增身份"
@@ -6281,11 +7377,14 @@ msgstr "新的管線將å–消åŒä¸€åˆ†æ”¯ä¸Šè¼ƒèˆŠã€æœªè§£æ±ºçš„管線"
msgid "New project"
msgstr "新增專案"
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr "新增排程"
msgid "New snippet"
-msgstr "新增文字片段"
+msgstr "新增程å¼ç¢¼ç‰‡æ®µ"
msgid "New subgroup"
msgstr "新增å­ç¾¤çµ„"
@@ -6293,20 +7392,29 @@ msgstr "新增å­ç¾¤çµ„"
msgid "New tag"
msgstr "新增標籤"
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr "新增…"
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr "å¦"
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
msgid "No Label"
msgstr "沒有標籤"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
-msgid "No assignee"
-msgstr "無被指派人"
+msgid "No activities found"
+msgstr ""
msgid "No branches found"
msgstr "找ä¸åˆ°åˆ†æ”¯"
@@ -6335,7 +7443,7 @@ msgstr ""
msgid "No due date"
msgstr "沒有到期時間"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6353,15 +7461,24 @@ msgstr "找ä¸åˆ°ä»»ä½•æª”案"
msgid "No issues for the selected time period."
msgstr "é¸å–的時間範åœä¸­æ²’有議題。"
+msgid "No job trace"
+msgstr ""
+
msgid "No labels with such name or description"
msgstr "沒有具有此類å稱或æ述的標籤"
msgid "No license. All rights reserved"
msgstr "沒有授權æ¢æ¬¾ã€‚ä¿ç•™ä¸€åˆ‡è‘—作權"
+msgid "No licenses found."
+msgstr ""
+
msgid "No matching results"
msgstr ""
+msgid "No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "No merge requests for the selected time period."
msgstr "é¸å–的時間範åœä¸­æ²’有åˆä½µè«‹æ±‚。"
@@ -6410,9 +7527,6 @@ msgstr "節點"
msgid "None"
msgstr "ç„¡"
-msgid "Not allowed to merge"
-msgstr "ä¸å…許åˆä½µ"
-
msgid "Not available"
msgstr "無法使用"
@@ -6428,11 +7542,17 @@ msgstr "éžæ©Ÿå¯†"
msgid "Not enough data"
msgstr "資料ä¸è¶³"
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr "ä¸æ˜¯ç¾åœ¨"
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr "請注æ„,master 分支é è¨­ç‚ºå—ä¿è­·çš„。 %{link_to_protected_branches}"
+msgid "Not ready yet. Try again later."
+msgstr ""
+
+msgid "Not started"
+msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -6545,6 +7665,9 @@ msgstr "å一月"
msgid "OK"
msgstr "確定"
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr "å月"
@@ -6557,10 +7680,19 @@ msgstr "篩é¸"
msgid "Once imported, repositories can be mirrored over SSH. Read more %{ssh_link}"
msgstr "一旦匯入,就å¯ä»¥é€éŽ SSH é¡åƒç‰ˆæœ¬åº«ã€‚閱讀 %{ssh_link} 以了解更多資訊"
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] "其餘 %d 項"
+msgid "One or more groups that you don't have access to."
+msgstr ""
+
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "您的一個或多個 Bitbucket 專案無法直接匯入 GitLab,因為它們使用 Subversion 或 Mercurial 進行版本控管,而ä¸æ˜¯Git。"
@@ -6570,6 +7702,12 @@ msgstr "您的一個或多個 Google Code 專案無法直接匯入 GitLab,因ç
msgid "Only admins"
msgstr "僅管ç†å“¡"
+msgid "Only allow merge requests to be merged if all discussions are resolved"
+msgstr ""
+
+msgid "Only allow merge requests to be merged if the pipeline succeeds"
+msgstr ""
+
msgid "Only mirror protected branches"
msgstr "僅é¡åƒè¢«ä¿è­·çš„分支"
@@ -6606,6 +7744,9 @@ msgstr "在 Xcode 開啟"
msgid "Open projects"
msgstr "打開專案"
+msgid "Open raw"
+msgstr ""
+
msgid "Open sidebar"
msgstr "é–‹å•Ÿå´é‚Šæ¬„"
@@ -6633,15 +7774,30 @@ msgstr "動作"
msgid "Operations Dashboard"
msgstr "維é‹å„€è¡¨æ¿"
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "新增專案到儀表æ¿"
+msgid "OperationsDashboard|Add projects"
+msgstr ""
+
+msgid "OperationsDashboard|More information"
+msgstr ""
+
+msgid "OperationsDashboard|Operations Dashboard"
+msgstr ""
+
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr "維é‹å„€è¡¨æ¿æä¾›æ¯å€‹å°ˆæ¡ˆçš„執行狀æ³æ‘˜è¦ï¼ŒåŒ…括管線和警示狀態。"
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
msgstr ""
+msgid "Optional"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr "ä½ å¯ä»¥é¸æ“‡ %{link_to_customize} 如何將 FogBugz çš„é›»å­ä¿¡ç®±åŠä½¿ç”¨è€…å稱匯入 GitLab 。"
@@ -6660,9 +7816,6 @@ msgstr "其他標籤"
msgid "Other information"
msgstr "其他資訊"
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr "此外,建議您從下é¢çš„一個é¸é …開始。"
-
msgid "Outbound requests"
msgstr "Outbound 請求"
@@ -6684,6 +7837,12 @@ msgstr "軟體包被移除"
msgid "Packages"
msgstr "軟體包"
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr "é é¢"
@@ -6708,18 +7867,21 @@ msgstr "« 第一é "
msgid "Parameter"
msgstr ""
-msgid "Parent epic"
-msgstr ""
-
msgid "Part of merge request changes"
msgstr "åˆä½µè«‹æ±‚變更的部分"
msgid "Password"
msgstr "密碼"
+msgid "Password authentication is unavailable."
+msgstr ""
+
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste epic link"
msgstr ""
@@ -6729,6 +7891,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "貼上您的SSH公鑰,通常放置在 '~/.ssh/id_rsa.pub',並以 'ssh-rsa' 開頭。ä¸è¦ä½¿ç”¨æ‚¨çš„SSHç§é‘°ã€‚"
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr "路徑,移轉,刪除"
@@ -6738,6 +7903,9 @@ msgstr "ä½ç½®ï¼š"
msgid "Pause"
msgstr "æš«åœ"
+msgid "Pause replication"
+msgstr ""
+
msgid "Paused Runners don't accept new jobs"
msgstr "已暫åœçš„執行器ä¸æœƒæŽ¥å—新工作"
@@ -6756,6 +7924,9 @@ msgstr "效能最佳化"
msgid "Permissions"
msgstr "權é™"
+msgid "Permissions Help"
+msgstr ""
+
msgid "Permissions, LFS, 2FA"
msgstr "權é™, 大檔案儲存(LFS), 兩步驟驗證(2FA)"
@@ -6771,21 +7942,24 @@ msgstr ""
msgid "Pipeline"
msgstr "管線"
-msgid "Pipeline Health"
-msgstr "管線å¥åº·æŒ‡æ•¸"
-
msgid "Pipeline Schedule"
msgstr "排程管線"
msgid "Pipeline Schedules"
msgstr "排程管線"
+msgid "Pipeline minutes quota"
+msgstr ""
+
msgid "Pipeline quota"
msgstr "管線é¡åº¦"
msgid "Pipeline triggers"
msgstr "管線觸發器"
+msgid "Pipeline: %{status}"
+msgstr ""
+
msgid "PipelineCharts|Failed:"
msgstr "失敗:"
@@ -6834,6 +8008,12 @@ msgstr "變數"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自訂"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr ""
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr ""
+
msgid "Pipelines"
msgstr "管線"
@@ -6849,6 +8029,12 @@ msgstr "上週的管線"
msgid "Pipelines for last year"
msgstr "去年的管線"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "信任編譯"
@@ -6891,11 +8077,8 @@ msgstr "這個專案目å‰é‚„沒設定管線。"
msgid "Pipeline|Commit"
msgstr "æ交"
-msgid "Pipeline|Create for"
-msgstr "建立"
-
-msgid "Pipeline|Create pipeline"
-msgstr "建立管線"
+msgid "Pipeline|Coverage"
+msgstr ""
msgid "Pipeline|Duration"
msgstr "耗時"
@@ -6909,6 +8092,9 @@ msgstr "管線"
msgid "Pipeline|Run Pipeline"
msgstr "執行管線"
+msgid "Pipeline|Run for"
+msgstr ""
+
msgid "Pipeline|Search branches"
msgstr "æœå°‹åˆ†æ”¯"
@@ -6936,9 +8122,21 @@ msgstr "ä½ å°‡åœæ­¢ç®¡ç·š %{pipelineId}。"
msgid "Pipeline|all"
msgstr "全部"
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr "æˆåŠŸ"
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr "與階段"
@@ -6963,15 +8161,33 @@ msgstr "在繼續之å‰ï¼Œè«‹åŒæ„æœå‹™æ¢æ¬¾"
msgid "Please choose a group URL with no special characters."
msgstr "è«‹é¸æ“‡æ²’有特殊符號的群組網å€ã€‚"
+msgid "Please complete your profile with email address"
+msgstr ""
+
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "請將它們轉為 %{link_to_git} ,然後å†æ¬¡é€éŽ %{link_to_import_flow} 。"
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "請將它們在 Google Code 上轉為 Git,然後å†æ¬¡é€éŽ %{link_to_import_flow} 。"
+msgid "Please create a password for your new account."
+msgstr ""
+
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr "請為您的群組填寫æ述性å稱。"
@@ -6981,9 +8197,21 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "請注æ„,這個應用程å¼ä¸æ˜¯ç”± GitLab 所æ供的,您應該在å…許訪å•ä¹‹å‰é©—證其真實性。"
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select a group."
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "è«‹é¸æ“‡è‡³å°‘一個篩é¸å™¨ä»¥æª¢è¦–çµæžœ"
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr "請填寫此驗證碼"
@@ -6996,6 +8224,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "è«‹ä½¿ç”¨æ­¤è¡¨å–®å‘ GitLab 報告建立垃圾議題ã€ç•™è¨€æˆ–有ä¸ç•¶è¡Œç‚ºçš„使用者。"
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we connect to your repository. Refresh at will."
msgstr "è«‹ç¨å€™ï¼Œæ­£åœ¨é€£ç·šåˆ°æ‚¨çš„版本庫,ç¨å¾Œè«‹é‡æ–°æ•´ç†ã€‚"
@@ -7005,18 +8236,33 @@ msgstr "è«‹ç¨å€™ï¼Œæ­£åœ¨åŒ¯å…¥æ‚¨çš„版本庫,ç¨å¾Œè«‹é‡æ–°æ•´ç†ã€‚"
msgid "Preferences"
msgstr "å好設定"
+msgid "Preferences saved."
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr "導航主題"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr "按下 Enter 或點é¸ä»¥æœå°‹"
msgid "Prevent adding new members to project membership within this group"
msgstr "ç¦æ­¢å‘ç›®å‰ç¾¤çµ„新增使用者"
+msgid "Prevent approval of merge requests by merge request author"
+msgstr ""
+
+msgid "Prevent approval of merge requests by merge request committers"
+msgstr ""
+
msgid "Preview"
msgstr "é è¦½"
+msgid "Preview changes"
+msgstr ""
+
msgid "Preview payload"
msgstr "é è¦½æœ‰æ•ˆè² è¼‰"
@@ -7044,6 +8290,9 @@ msgstr "ç§äººå°ˆæ¡ˆ - 專案權é™å¿…須明確指派給æ¯ä½ä½¿ç”¨è€…"
msgid "Private - The group and its projects can only be viewed by members."
msgstr "ç§äººå°ˆæ¡ˆ - 群組與å­å°ˆæ¡ˆåªèƒ½è¢«ç¾¤çµ„æˆå“¡æª¢è¦–。"
+msgid "Private group(s)"
+msgstr ""
+
msgid "Private projects can be created in your personal namespace with:"
msgstr "å¯ä»¥åœ¨æ‚¨çš„個人命å空間中建立ç§äººå°ˆæ¡ˆ:"
@@ -7194,21 +8443,21 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "檔案必須è¦åœ¨ 200KB 以下。"
+msgid "Profiles|There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "這看起來ä¸åƒæ˜¯ SSH 公鑰,您確定è¦å¢žåŠ å®ƒå—Žï¼Ÿ"
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
-msgstr "這個電å­éƒµä»¶å°‡ç”¨æ–¼Web的任何æ“作,例如編輯和åˆä½µã€‚%{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "這個表情符號和訊æ¯å°‡é¡¯ç¤ºæ–¼æ‚¨çš„個人資料和整個工作介é¢ä¸­ã€‚"
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
msgstr ""
@@ -7308,6 +8557,15 @@ msgstr "進度"
msgid "Project"
msgstr "專案"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "正在刪除專案「%{project_name}ã€ã€‚"
@@ -7323,17 +8581,26 @@ msgstr "專案「%{project_name}ã€æ›´æ–°æˆåŠŸã€‚"
msgid "Project Badges"
msgstr "專案徽章"
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr "專案 URL"
msgid "Project access must be granted explicitly to each user."
msgstr "專案權é™å¿…須明確指派給æ¯ä½ä½¿ç”¨è€…。"
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr "專案大頭貼"
-msgid "Project avatar in repository: %{link}"
-msgstr "此版本庫的專案大頭貼:%{link}"
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
msgid "Project details"
msgstr "專案詳細資訊"
@@ -7341,6 +8608,9 @@ msgstr "專案詳細資訊"
msgid "Project export could not be deleted."
msgstr "無法刪除匯出專案。"
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr "已刪除匯出專案。"
@@ -7350,6 +8620,9 @@ msgstr "專案匯出連çµå·²å¤±æ•ˆï¼Œè«‹è‡³ã€Œå°ˆæ¡ˆè¨­å®šã€ç”¢ç”Ÿæ–°çš„匯出
msgid "Project export started. A download link will be sent by email."
msgstr "已開始專案匯出程åºã€‚完æˆå¾Œä¸‹è¼‰é€£çµå°‡é€åˆ°æ‚¨çš„信箱。"
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -7359,6 +8632,12 @@ msgstr "專案å稱"
msgid "Project slug"
msgstr "專案識別文字"
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -7408,7 +8687,7 @@ msgid "ProjectOverview|You have reached your project limit"
msgstr "您已到é”專案é™åˆ¶"
msgid "ProjectOverview|You must sign in to star a project"
-msgstr "您必須登入æ‰èƒ½å°å°ˆæ¡ˆåŠ ä¸Šæ”¶è—"
+msgstr "您必須登入之後æ‰èƒ½æ”¶è—專案"
msgid "ProjectPage|Project ID: %{project_id}"
msgstr "專案編號:%{project_id}"
@@ -7449,12 +8728,21 @@ msgstr "使用者åªèƒ½é€éŽä»–們自己通éŽé©—證的電å­ä¿¡ç®±ä¹‹ä¸€æ交
msgid "Projects"
msgstr "專案"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "與 %{group_name} 分享的專案"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "群組中的專案將會以群組å稱當åšå‘½å空間的å‰è¼Ÿã€‚ç¾æœ‰çš„專案å¯ä»¥ç§»å‹•åˆ°ç¾¤çµ„中。"
+msgid "Projects to index"
+msgstr ""
+
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "經常造訪"
@@ -7503,6 +8791,9 @@ msgstr "未設定警報"
msgid "PrometheusAlerts|Operator"
msgstr "æ“作者"
+msgid "PrometheusAlerts|Select query"
+msgstr ""
+
msgid "PrometheusAlerts|Threshold"
msgstr "門檻"
@@ -7582,10 +8873,10 @@ msgid "PrometheusService|Waiting for your first deployment to an environment to
msgstr "等您首次部署到環境後以查詢常用指標"
msgid "Promote"
-msgstr "å‡ç´š"
+msgstr "æå‡"
msgid "Promote these project milestones into a group milestone."
-msgstr "將這些項目里程碑åˆä½µåˆ°åˆ°ä¸€å€‹ç¾¤çµ„里程碑。"
+msgstr "將這些專案里程碑æå‡æˆä¸€å€‹ç¾¤çµ„里程碑。"
msgid "Promote to Group Milestone"
msgstr "æå‡è‡³ç¾¤çµ„里程碑"
@@ -7605,6 +8896,9 @@ msgstr "這個功能已被鎖定。"
msgid "Promotions|Upgrade plan"
msgstr "å‡ç´šæ–¹æ¡ˆ"
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr "å—ä¿è­·"
@@ -7671,6 +8965,12 @@ msgstr "公開專案 - ä¸éœ€èº«ä»½é©—è­‰å³å¯å­˜å–群組以åŠå…¬é–‹å°ˆæ¡ˆ"
msgid "Public - The project can be accessed without any authentication."
msgstr "公開專案 - ä¸éœ€èº«ä»½é©—è­‰å³å¯å­˜å–專案。"
+msgid "Public Access Help"
+msgstr ""
+
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "公共管線"
@@ -7683,6 +8983,12 @@ msgstr "推é€"
msgid "Push Rules"
msgstr "推é€è¦å‰‡"
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr "推é€äº‹ä»¶"
@@ -7713,6 +9019,9 @@ msgstr "「快速æ“作ã€èƒ½ç”¨æ–¼è­°é¡Œæ述和留言框。"
msgid "README"
msgstr ""
+msgid "Rake Tasks Help"
+msgstr ""
+
msgid "Read more"
msgstr "了解更多"
@@ -7728,6 +9037,12 @@ msgstr "å³æ™‚功能"
msgid "Receive alerts from manually configured Prometheus servers."
msgstr ""
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
+msgstr ""
+
msgid "Recent searches"
msgstr "最近æœå°‹"
@@ -7762,6 +9077,9 @@ msgstr "註冊ã€ä¸¦è§€å¯Ÿæ‚¨åœ¨é€™å€‹ç¾¤çµ„的執行器。"
msgid "Register and see your runners for this project."
msgstr "註冊ã€ä¸¦è§€å¯Ÿæ‚¨åœ¨é€™å€‹å°ˆæ¡ˆçš„執行器。"
+msgid "Registration"
+msgstr ""
+
msgid "Registry"
msgstr "註冊表"
@@ -7807,9 +9125,18 @@ msgstr ""
msgid "Remove approver"
msgstr ""
+msgid "Remove approvers"
+msgstr ""
+
+msgid "Remove approvers?"
+msgstr ""
+
msgid "Remove avatar"
msgstr "移除大頭貼"
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr "刪除群組"
@@ -7819,12 +9146,33 @@ msgstr "刪除優先權"
msgid "Remove project"
msgstr "移除專案"
+msgid "Remove this label? Are you sure?"
+msgstr ""
+
+msgid "Remove this label? This will affect all projects within the group. Are you sure?"
+msgstr ""
+
+msgid "Removed"
+msgstr ""
+
+msgid "Removed %{type} with id %{id}"
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr "刪除的群組將無法復原ï¼"
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr "刪除群組將導致所有å­å°ˆæ¡ˆå’Œè³‡æºè¢«åˆªé™¤ã€‚"
+msgid "Removing license…"
+msgstr ""
+
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr "é‡æ–°å‘½å"
@@ -7843,6 +9191,9 @@ msgstr ""
msgid "Repair authentication"
msgstr "修復èªè­‰"
+msgid "Replace"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -7856,7 +9207,7 @@ msgid "Report abuse to GitLab"
msgstr "å‘ GitLab 回報濫用行為"
msgid "Reporting"
-msgstr "正在回報"
+msgstr "報告"
msgid "Reports|%{failedString} and %{resolvedString}"
msgstr "%{failedString} 和 %{resolvedString}"
@@ -7876,6 +9227,18 @@ msgstr "執行時間"
msgid "Reports|Failure"
msgstr "失敗"
+msgid "Reports|Metrics reports are loading"
+msgstr ""
+
+msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
+msgstr ""
+
+msgid "Reports|Metrics reports did not change"
+msgstr ""
+
+msgid "Reports|Metrics reports failed loading results"
+msgstr ""
+
msgid "Reports|Severity"
msgstr "åš´é‡æ€§"
@@ -7894,6 +9257,12 @@ msgstr "正在解æžæ¸¬è©¦æ‘˜è¦çµæžœ"
msgid "Reports|Vulnerability"
msgstr "風險"
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "沒有變更的測試çµæžœ"
@@ -7906,6 +9275,9 @@ msgstr "版本庫設定"
msgid "Repository URL"
msgstr "版本庫 URL"
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -7942,6 +9314,17 @@ msgstr "è¦æ±‚此群組中的所有使用者啟用兩步驟驗證"
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "è¦æ±‚所有使用者在訪å•GitLab時接å—æœå‹™æ¢æ¬¾å’Œéš±ç§æ”¿ç­–。"
+msgid "Require approval from code owners"
+msgstr ""
+
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+
msgid "Resend invite"
msgstr ""
@@ -7975,9 +9358,18 @@ msgstr "關閉討論"
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response"
msgstr ""
+msgid "Response didn't include `service_desk_address`"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr "回應指標 (AWS ELB)"
@@ -8002,12 +9394,18 @@ msgstr ""
msgid "Resume"
msgstr "繼續"
+msgid "Resume replication"
+msgstr ""
+
msgid "Retry"
msgstr "é‡è©¦"
msgid "Retry this job"
msgstr "é‡è©¦æ­¤å·¥ä½œ"
+msgid "Retry this job in order to create the necessary resources."
+msgstr ""
+
msgid "Retry verification"
msgstr "é‡è©¦é©—è­‰"
@@ -8039,12 +9437,21 @@ msgstr "正在檢閱中(åˆä½µè«‹æ±‚ !%{mergeRequestId})"
msgid "Revoke"
msgstr "撤回"
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
+msgid "Revoked personal access token %{personal_access_token_name}!"
+msgstr ""
+
msgid "Roadmap"
msgstr "開發è—圖"
msgid "Run CI/CD pipelines for external repositories"
msgstr "執行外部版本庫的 CI / CD 管線。"
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run tests against your code live using the Web Terminal"
msgstr ""
@@ -8066,6 +9473,12 @@ msgstr "執行器將執行指定專案的工作"
msgid "Runner token"
msgstr "執行器憑證"
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr "執行器將ä¸æœƒæŽ¥å—新的工作"
@@ -8099,27 +9512,27 @@ msgstr "您已經使用了所有分享的執行器多久的時間。"
msgid "Running"
msgstr "執行中"
+msgid "Running…"
+msgstr ""
+
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SAML SSO"
msgstr "SAML SSO"
msgid "SAML SSO for %{group_name}"
msgstr "%{group_name} çš„ SAML SSO"
-msgid "SAML Single Sign On"
-msgstr "SAML 單一登入"
-
-msgid "SAML Single Sign On Settings"
-msgstr "SAML 單一登入設定"
-
msgid "SAML for %{group_name}"
msgstr ""
-msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
-msgstr "SAML 憑證登入憑證的 SHA1 指紋。從您的身份æ供者å–得這個指紋 -- 它å¯èƒ½è¢«ç¨±ç‚ºã€ŒThumbprintã€"
-
msgid "SSH Keys"
msgstr "SSH 金鑰"
+msgid "SSH Keys Help"
+msgstr ""
+
msgid "SSH host keys"
msgstr "SSH 主機金鑰"
@@ -8129,6 +9542,9 @@ msgstr "SSH 公鑰"
msgid "SSL Verification"
msgstr "SSL é©—è­‰"
+msgid "Saturday"
+msgstr ""
+
msgid "Save"
msgstr "儲存"
@@ -8153,6 +9569,9 @@ msgstr "儲存排程管線"
msgid "Save variables"
msgstr "儲存變數"
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr "排程新管線"
@@ -8162,15 +9581,24 @@ msgstr "已排程"
msgid "Schedules"
msgstr "排程"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "排程管線"
msgid "Scope"
msgstr "範åœ"
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scoped issue boards"
msgstr "å€åŸŸæ€§çš„議題看æ¿"
+msgid "Scoped label"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr "å‘下滾動到 <strong>Google Code Project Hosting</strong> 並啟用å³å´çš„開關。"
@@ -8222,6 +9650,9 @@ msgstr "æœå°‹å°ˆæ¡ˆ"
msgid "Search users"
msgstr "æœå°‹ä½¿ç”¨è€…"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr "æœå°‹ä½ çš„專案"
@@ -8249,6 +9680,15 @@ msgstr "在這個群組"
msgid "SearchAutocomplete|in this project"
msgstr "在這個專案"
+msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
+msgstr ""
+
+msgid "Seats currently in use"
+msgstr ""
+
+msgid "Seats in license"
+msgstr ""
+
msgid "Secret"
msgstr "密碼"
@@ -8270,9 +9710,6 @@ msgstr "讀å–æ¼æ´žåˆ—表時發生錯誤,請檢查您的網路連線,然後
msgid "Security Dashboard|Issue Created"
msgstr "已建立å•é¡Œ"
-msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
-
msgid "Security Reports|Create issue"
msgstr "建立å•é¡Œ"
@@ -8288,12 +9725,12 @@ msgstr "更多資訊"
msgid "Security Reports|No Vulnerabilities"
msgstr ""
-msgid "Security Reports|Security dashboard documentation"
-msgstr "安全儀表æ¿æª”案"
-
msgid "Security Reports|There was an error creating the issue."
msgstr "建立議題時發生錯誤。"
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "忽略æ¼æ´žæ™‚發生錯誤。"
@@ -8342,6 +9779,15 @@ msgstr "é¸æ“‡è¦é‚€è«‹çš„群組"
msgid "Select a namespace to fork the project"
msgstr "é¸æ“‡ä¸€å€‹å‘½å空間,以複製這個專案"
+msgid "Select a new namespace"
+msgstr ""
+
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr "é¸æ“‡æ¨¡æ¿ç‰ˆæœ¬åº«"
@@ -8351,9 +9797,6 @@ msgstr "é¸æ“‡æ™‚å€"
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "é¸æ“‡ä¸€å€‹ç¾æœ‰çš„ Kubernetes å¢é›†æˆ–新增一個"
-msgid "Select assignee"
-msgstr "é¸æ“‡è¢«æŒ‡æ´¾äºº"
-
msgid "Select branch/tag"
msgstr "é¸æ“‡åˆ†æ”¯æˆ–標籤"
@@ -8384,11 +9827,14 @@ msgstr "é¸æ“‡æ­¤å°ˆæ¡ˆçš„é è¨­åˆ†æ”¯ã€‚除éžæœ‰é¡å¤–設定,å¦å‰‡æ‰€æœ‰åˆ
msgid "Select the custom project template source group."
msgstr "é¸æ“‡è‡ªè¨‚專案模æ¿ä¾†æºç¾¤çµ„。"
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "é¸æ“‡ GitLab 使用者將在議題和留言的æè¿°ä¸­æ–°å¢žæŒ‡å‘ GitLab 使用者的超連çµï¼ˆä¾‹å¦‚\"ç”± <a href=\"#\">@johnsmith</a>\")。它還將與所é¸ä½¿ç”¨è€…é—œè¯/分é…這些議題和留言。"
-msgid "Selective synchronization"
-msgstr "é¸æ“‡æ€§åŒæ­¥"
+msgid "Send an email notification to Developers."
+msgstr ""
msgid "Send email"
msgstr "傳é€é›»å­éƒµä»¶"
@@ -8405,18 +9851,39 @@ msgstr ""
msgid "Sep"
msgstr "ä¹æœˆ"
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr "ä¹æœˆ"
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr "伺æœå™¨ç‰ˆæœ¬"
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgstr ""
+
+msgid "ServerlessDetails|Install Prometheus"
+msgstr ""
+
+msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
+msgstr ""
+
+msgid "ServerlessDetails|Invocations"
+msgstr ""
+
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
+msgid "ServerlessDetails|More information"
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -8432,9 +9899,6 @@ msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
-msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
-
msgid "Serverless|Getting started with serverless"
msgstr ""
@@ -8462,9 +9926,18 @@ msgstr "æœå‹™æ¨¡æ¿"
msgid "Service URL"
msgstr "æœå‹™ URL"
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr "éŽæœŸå·¥ä½œéšŽæ®µã€å°ˆæ¡ˆä¸Šé™èˆ‡é™„件大å°ã€‚"
+msgid "Set a default template for issue descriptions."
+msgstr ""
+
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "請先設定密碼,æ‰èƒ½ä½¿ç”¨ %{protocol} 上傳或下載。"
@@ -8480,6 +9953,9 @@ msgstr "設定實例範åœçš„模æ¿ç‰ˆæœ¬åº«"
msgid "Set max session time for web terminal."
msgstr "為網é çµ‚端器設定最長工作階段時間"
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "為濫用回報設定通知電å­ä¿¡ç®±ã€‚"
@@ -8489,6 +9965,18 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "設定使用者登入的需求。啟用強制性的兩步驟驗證。"
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
+msgid "Set the maximum number of pipeline minutes that a group can use on shared Runners per month. 0 for unlimited."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr "設定 CI / CD"
@@ -8504,6 +9992,9 @@ msgstr "根據 %{docsLinkStart}這個檔案%{icon}%{docsLinkEnd} 設定斷言 /
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr "設定你的專案以自動推é€å’Œ/或從å¦ä¸€å€‹ç‰ˆæœ¬åº«æ‹‰å–變更。分支ã€æ¨™ç±¤åŠæ交將會自動åŒæ­¥ã€‚"
@@ -8561,9 +10052,15 @@ msgstr "é‡è¨­ç®¡ç·šå·²ç”¨æ™‚é–“"
msgid "Sherlock Transactions"
msgstr "Sherlock 交易"
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "顯示指令"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "顯示完整的原始記錄"
@@ -8573,6 +10070,9 @@ msgstr ""
msgid "Show latest version"
msgstr "顯示最新版本"
+msgid "Show link to create/view merge request when pushing from the command line"
+msgstr ""
+
msgid "Show parent pages"
msgstr "顯示上層é é¢"
@@ -8631,6 +10131,12 @@ msgstr "登入é™åˆ¶"
msgid "Sign-up restrictions"
msgstr "註冊é™åˆ¶"
+msgid "Signing in using %{label} has been disabled"
+msgstr ""
+
+msgid "Signing in using your %{label} account without a pre-existing GitLab account is not allowed."
+msgstr ""
+
msgid "Similar issues"
msgstr ""
@@ -8640,6 +10146,9 @@ msgstr "大å°"
msgid "Size and domain settings for static websites"
msgstr "為éœæ…‹ç¶²ç«™çš„大å°èˆ‡ç¶²åŸŸè¨­å®š"
+msgid "Size limit per repository (MB)"
+msgstr ""
+
msgid "Slack application"
msgstr "Slack 應用程å¼"
@@ -8661,6 +10170,27 @@ msgstr ""
msgid "Snippets"
msgstr "程å¼ç¢¼ç‰‡æ®µ"
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -8706,6 +10236,9 @@ msgstr "讀å–留言時發生錯誤,請ç¨å€™é‡è©¦ã€‚"
msgid "Something went wrong while fetching group member contributions"
msgstr "å–得群組æˆå“¡è²¢ç»æ™‚發生錯誤。"
+msgid "Something went wrong while fetching related merge requests."
+msgstr ""
+
msgid "Something went wrong while fetching the environments for this merge request. Please try again."
msgstr "讀å–åˆä½µè«‹æ±‚的環境時發生錯誤,請ç¨å€™é‡è©¦ã€‚"
@@ -8733,6 +10266,9 @@ msgstr "讀å–專案時發生錯誤"
msgid "Something went wrong, unable to remove project"
msgstr "刪除專案時發生錯誤"
+msgid "Something went wrong, unable to search projects"
+msgstr ""
+
msgid "Something went wrong. Please try again."
msgstr "發生未知錯誤,請ç¨å¾Œå†è©¦ã€‚"
@@ -8883,6 +10419,9 @@ msgstr "垃圾訊æ¯è¨˜éŒ„"
msgid "Spam and Anti-bot Protection"
msgstr "垃圾內容與防機器人ä¿è­·"
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr "指定執行器"
@@ -8931,15 +10470,27 @@ msgstr "已收è—的專案活動"
msgid "Starred projects"
msgstr "收è—專案"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
+msgid "Start GitLab Ultimate trial"
+msgstr ""
+
msgid "Start Web Terminal"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr "建立包å«é€™äº›è®Šå‹•çš„ %{new_merge_request}"
+msgid "Start a new merge request"
+msgstr ""
+
msgid "Start a review"
msgstr "開始檢閱"
@@ -8973,6 +10524,9 @@ msgstr "已開始"
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starting..."
msgstr ""
@@ -9126,6 +10680,27 @@ msgstr ""
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully deleted U2F device."
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -9135,15 +10710,24 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr "切æ›åˆ†æ”¯/標籤"
msgid "Sync information"
msgstr "åŒæ­¥è©³ç´°è³‡è¨Š"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "系統觸發器"
+msgid "System Hooks Help"
+msgstr ""
+
msgid "System Info"
msgstr "系統資訊"
@@ -9153,6 +10737,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr "系統指標 (自訂)"
@@ -9276,12 +10863,22 @@ msgstr "測試 SAML SSO"
msgid "Test coverage parsing"
msgstr "測試覆蓋率分æž"
+msgid "Test failed."
+msgstr ""
+
+msgid "Thank you for your report. A GitLab administrator will look into it shortly."
+msgstr ""
+
msgid "Thanks! Don't show me this again"
msgstr "æ„Ÿè¬ï¼è«‹ä¸è¦å†æ¬¡é¡¯ç¤º"
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
+msgid "The %{type} contains the following error:"
+msgid_plural "The %{type} contains the following errors:"
+msgstr[0] ""
+
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "GitLab 的進階全域æœå°‹åŠŸèƒ½æ˜¯éžå¸¸å¼·å¤§çš„æœå°‹æœå‹™ã€‚您å¯ä»¥ç«‹åˆ»æœå°‹å…¶ä»–團隊的程å¼ç¢¼ä»¥å¹«åŠ©æ‚¨å®Œå–„自己項目中的程å¼ç¢¼ã€‚從而é¿å…建立é‡è¤‡çš„程å¼ç¢¼æµªè²»æ™‚間。"
@@ -9300,6 +10897,12 @@ msgstr "「議題追蹤器ã€èƒ½æ”¾ç½®å°ˆæ¡ˆéœ€è¦æ”¹å–„或解決å•é¡Œçš„é …ç›®
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "當需è¦ç›¸äº’ TLS 與外部èªè­‰æœå‹™è¯çµ¡æ™‚,所使用的 X509 憑證。如果留白,使用 HTTPS 連線時伺æœå™¨çš„憑證ä»ç„¶ç‚ºã€Œå·²èªè­‰ã€ã€‚"
+msgid "The amount of seconds after which a request to get a secondary node status will time out."
+msgstr ""
+
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "文字çªé¡¯å°‡å¹«åŠ©ä½ å°‡ä¸»é¡Œä¿æŒç‚º %{titleLength} 個字,並且將正文包è£ç‚º %{bodyLength} 個字,以便它們å¯ä»¥åœ¨ git 中閱讀。"
@@ -9312,15 +10915,63 @@ msgstr "該階段中的相關事件集åˆã€‚"
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "該連線將在 %{timeout} 之後逾時。å°æ–¼éœ€è¦ä½¿ç”¨æ›´é•·æ™‚間的版本庫,請使用 clone / push 組åˆã€‚"
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr "å° %{environmentLink} 作業的佈署並未æˆåŠŸã€‚"
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr "分支與主幹間的關è¯å·²è¢«åˆªé™¤ã€‚"
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group and any internal projects can be viewed by any logged in user."
+msgstr ""
+
+msgid "The group and any public projects can be viewed without any authentication."
+msgstr ""
+
+msgid "The group and its projects can only be viewed by members."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr "匯入將在 %{timeout} 之後逾時。å°æ–¼éœ€è¦æ›´é•·æ™‚間的存儲庫,請使用 clone / push 組åˆã€‚"
+msgid "The invitation could not be accepted."
+msgstr ""
+
+msgid "The invitation could not be declined."
+msgstr ""
+
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+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 "議題階段顯示從議題建立到設定里程碑所花的時間,或是議題被分類到議題看æ¿ä¸­æ‰€èŠ±çš„時間。建立第一個議題後,資料將自動填入。"
@@ -9330,6 +10981,18 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr "最大檔案大å°ç‚º 200KB"
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "解密ç§é‘°æ‰€éœ€çš„密碼。這是é¸ç”¨ä¸”該值將會被加密儲存。"
@@ -9357,9 +11020,18 @@ msgstr "本專案å¯è®“任何已登入的使用者存å–"
msgid "The project can be accessed without any authentication."
msgstr "本專案å¯è®“任何人存å–"
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
msgid "The pseudonymizer data collection is disabled. When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr "Pseudonymizer 資料è’集已經åœç”¨ã€‚如果啟用,GitLab 將會執行一個背景作業 -- 其將會產生 GitLab 資料庫的å‡å CSV,使其å¯ä»¥ä¸Šå‚³åˆ°æ‚¨è¨­å®šçš„物件儲存資料夾。"
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr "本專案沒有版本庫"
@@ -9378,6 +11050,21 @@ msgstr "檢閱階段顯示從åˆä½µè«‹æ±‚建立後至被åˆä½µçš„時間。當建
msgid "The roadmap shows the progress of your epics along a timeline"
msgstr "開發è—圖顯示了時間軸上您的 Epic 的進度"
+msgid "The schedule time must be in the future!"
+msgstr ""
+
+msgid "The snippet can be accessed without any authentication."
+msgstr ""
+
+msgid "The snippet is visible only to me."
+msgstr ""
+
+msgid "The snippet is visible only to project members."
+msgstr ""
+
+msgid "The snippet is visible to any logged in user."
+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 "試營é‹æ®µé¡¯ç¤ºå¾žåˆä½µè«‹æ±‚被åˆä½µå¾Œè‡³éƒ¨ç½²ç‡Ÿé‹çš„時間。當第一次部署營é‹å¾Œï¼Œè³‡æ–™å°‡è‡ªå‹•å¡«å…¥"
@@ -9393,9 +11080,18 @@ msgstr "該階段中æ¯ä¸€å€‹è³‡æ–™é …目所花的時間。"
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr "更新動作將會在 %{number_of_minutes} 分é˜å¾Œé€¾æ™‚。å°æ–¼å¤§åž‹ç‰ˆæœ¬åº«ï¼Œè«‹ä½¿ç”¨ clone/push 組åˆã€‚"
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr "å·²åœç”¨ä½¿ç”¨æƒ…æ³ Ping 功能,且無法é€éŽæ­¤è¡¨å–®è¨­å®šã€‚"
+msgid "The user is being deleted."
+msgstr ""
+
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr "å°æ‡‰ä½¿ç”¨è€…是一個JSON檔,是將åƒèˆ‡å°ˆæ¡ˆçš„ Google Code 使用者å°æ‡‰åˆ°ä»–們的電å­éƒµä»¶å’Œä½¿ç”¨è€…å稱並匯入GitLab çš„æ–¹å¼ã€‚您å¯ä»¥é€éŽè®Šæ›´ <code>:</code>å³å´çš„值來變更此å°æ‡‰ã€‚請務必在左å´ä¿ç•™å‘¨åœçš„雙引號ã€å…¶ä»–標點符號以åŠé›»å­éƒµä»¶ä½å€æˆ–使用者å稱。"
@@ -9450,9 +11146,21 @@ msgstr "加入待辦事項時出錯。"
msgid "There was an error deleting the todo."
msgstr "刪除待辦事項時出錯。"
+msgid "There was an error fetching configuration for charts"
+msgstr ""
+
+msgid "There was an error gathering the chart data"
+msgstr ""
+
msgid "There was an error loading users activity calendar."
msgstr "讀å–使用者行事曆活動時發生錯誤"
+msgid "There was an error removing the e-mail."
+msgstr ""
+
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "儲存您的通知設定時發生錯誤。"
@@ -9468,6 +11176,9 @@ msgstr "訂閱此標籤時出ç¾éŒ¯èª¤ã€‚"
msgid "There was an error when unsubscribing from this label."
msgstr "å–消訂閱此標籤時出ç¾éŒ¯èª¤ã€‚"
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -9486,6 +11197,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "æ­¤ GitLab 實例ä¸æ供任何分享的執行器。實例管ç†å“¡å¯ä»¥åœ¨ç®¡ç†å“¡å€åŸŸè¨»å†Šåˆ†äº«çš„執行器。"
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr "這個應用程å¼æ˜¯ç”± %{link_to_owner} 建立的。"
@@ -9498,6 +11212,21 @@ msgstr "已縮å°æ­¤çœ‹æ¿ç¯„åœ"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "自上次編輯以來此分支已經變更éŽã€‚是å¦å»ºç«‹æ–°åˆ†æ”¯ï¼Ÿ"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr "此容器登入表已經排定刪除。"
@@ -9519,6 +11248,9 @@ msgstr "這個資料夾"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr "此群組"
@@ -9534,6 +11266,9 @@ msgstr "這這是一個延é²æ€§çš„工作,將在 %{remainingTime} 後執行"
msgid "This is the author's first Merge Request to this project."
msgstr "這是作者第一次åˆä½µè«‹æ±‚至本專案。"
+msgid "This is the maximum number of users that have existed at the same time since the license started. This is the minimum number of seats you will need to buy when you renew your license."
+msgstr ""
+
msgid "This issue is confidential"
msgstr "這個議題是ä¿å¯†çš„"
@@ -9582,6 +11317,12 @@ msgstr "這個工作正在部署到 %{environmentLink}。"
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "這份作業ä½æ–¼ç­‰å¾…狀態,等待執行器來執行"
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -9603,19 +11344,25 @@ msgstr "這代表在您建立一個空的版本庫或是匯入一個ç¾å­˜çš„版
msgid "This merge request is locked."
msgstr "這個åˆä½µè«‹æ±‚已被鎖定。"
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr ""
+
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "由於您沒有此分支的寫入權é™ï¼Œå› æ­¤ç¦ç”¨æ­¤é¸é …。"
-msgid "This option is disabled while you still have unstaged changes"
-msgstr "當您ä»æ“有未暫存的變更時,此é¸é …將會是åœç”¨çš„"
-
msgid "This page is unavailable because you are not allowed to read information across multiple projects."
msgstr "æ­¤é é¢ä¸å¯ç”¨ï¼Œå› ç‚ºæ‚¨ä¸å…許跨多個專案閱讀訊æ¯ã€‚"
msgid "This page will be removed in a future release."
msgstr "æ­¤é é¢å°‡æœƒåœ¨æœªä¾†ç‰ˆæœ¬ä¸­ç§»é™¤ã€‚"
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -9639,6 +11386,9 @@ msgstr "此專案未啟用帳單。如果è¦å»ºç«‹ç¾¤é›†ï¼Œè«‹ <a href=%{linkToB
msgid "This repository"
msgstr "這個版本庫"
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr "此執行器將åªåŸ·è¡Œè¢«ä¿è­·çš„分支所觸發的管線"
@@ -9648,12 +11398,12 @@ msgstr "ç›®å‰è¨­å®šå¯ä»¥åœ¨æ¯å€‹å°ˆæ¡ˆä¸­é€²è¡Œè¤‡å¯«ã€‚"
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "此設定將更新你用來產生ç§äººæ交æ交的電å­éƒµä»¶çš„主機域å。%{learn_more}"
-msgid "This source diff could not be displayed because it is too large."
-msgstr "此原始碼差異無法顯示,因為它太大。"
-
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr "該使用者沒有身份"
@@ -9666,6 +11416,9 @@ msgstr "此使用者將æˆç‚ºæ´»å‹•æ‘˜è¦ä¸­æ‰€æœ‰äº‹ä»¶çš„作者,例如建立
msgid "This will redirect you to an external sign in page."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "這裡列出了那些自動æˆç‚ºè­°é¡Œçš„é›»å­éƒµä»¶ï¼ˆå¾žç•™è¨€è®Šæˆçš„é›»å­éƒµä»¶å°è©±ï¼‰"
@@ -9732,9 +11485,6 @@ msgstr " %s 個月å‰"
msgid "Timeago|%s months remaining"
msgstr "剩下 %s 月"
-msgid "Timeago|%s seconds ago"
-msgstr "%s 秒å‰"
-
msgid "Timeago|%s seconds remaining"
msgstr "剩下 %s 秒"
@@ -9962,6 +11712,9 @@ msgstr "今天"
msgid "Todo"
msgstr "待辦事項"
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr "待辦事項"
@@ -10001,6 +11754,12 @@ msgstr "明天"
msgid "Too many changes to show."
msgstr "太多的變更。"
+msgid "Topics"
+msgstr ""
+
+msgid "Total"
+msgstr ""
+
msgid "Total Contributions"
msgstr "總計貢ç»"
@@ -10025,6 +11784,9 @@ msgstr "跟蹤共通主題ã€è·¨å°ˆæ¡ˆå’Œé‡Œç¨‹ç¢‘的議題群組"
msgid "Track time with quick actions"
msgstr "快速使用時間追蹤工具"
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr "樹狀顯示"
@@ -10037,6 +11799,9 @@ msgstr "觸發é¡åƒæ›´æ–°çš„管線"
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr "從上游版本庫更新分支或標記時觸發管線。如果上游版本庫更新頻ç¹ï¼Œé€™å¯èƒ½æœƒå¤§å¤§å¢žåŠ CI執行器的負擔。如果è¦å•Ÿç”¨æ­¤åŠŸèƒ½ï¼Œè«‹å…ˆç¢ºä¿æ‚¨çš„CI執行器處ç†èƒ½åŠ›èƒ½å¤ æ‰¿å—這樣的負擔。"
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr "啟動此作業"
@@ -10046,6 +11811,18 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
+msgid "Triggerer"
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr "觸發器å¯ä»¥å¼·åˆ¶ä½¿ç”¨API​​呼å«é‡å»ºç‰¹å®šçš„分支或標記。這些令牌將模仿他們的關è¯ä½¿ç”¨è€…,包括他們å°é …目的訪å•æ¬Šé™ã€‚"
@@ -10070,6 +11847,9 @@ msgstr "é–‹å•Ÿæœå‹™å€"
msgid "Twitter"
msgstr "Twitter"
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr "兩步驟驗證"
@@ -10079,15 +11859,27 @@ msgstr "é¡žåž‹"
msgid "URL"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr "無法載入差異。%{button_try_again}"
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unable to sign you in to the group with SAML due to \"%{reason}\""
msgstr "因為「%{reason}ã€ï¼Œæ‰€ä»¥ç„¡æ³•è®“ä½ é€éŽ SAML 登入這個群組。"
msgid "Unable to update this epic at this time."
msgstr "ç›®å‰ç„¡æ³•æ›´æ–°æ­¤ epic。"
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -10100,6 +11892,9 @@ msgstr ""
msgid "Unknown"
msgstr "未知"
+msgid "Unlimited"
+msgstr ""
+
msgid "Unlock"
msgstr "解鎖"
@@ -10160,21 +11955,39 @@ msgstr "å³å°‡é–‹å§‹"
msgid "Update"
msgstr "æ›´æ–°"
+msgid "Update approvers"
+msgstr ""
+
msgid "Update failed"
msgstr ""
msgid "Update now"
msgstr "ç«‹å³æ›´æ–°"
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr "更新你的群組å稱ã€èªªæ˜Žã€é ­åƒåŠå¯è¦‹æ€§ã€‚"
+msgid "Update your project name, topics, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
+msgid "Updated %{updated_at} by %{updated_by}"
+msgstr ""
+
msgid "Updating"
msgstr "更新中"
msgid "Upgrade plan to unlock Canary Deployments feature"
msgstr ""
+msgid "Upgrade your plan"
+msgstr ""
+
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "å‡ç´šæ‚¨çš„方案以啟用進階全域æœå°‹ã€‚"
@@ -10190,6 +12003,9 @@ msgstr "å‡ç´šæ‚¨çš„方案以啟用議題權é‡ã€‚"
msgid "Upgrade your plan to improve Issue boards."
msgstr "å‡ç´šæ‚¨çš„方案以改善議題看æ¿ã€‚"
+msgid "Upgrade your plan to improve Merge Requests."
+msgstr ""
+
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "在這裡上傳 <code>GoogleCodeProjectHosting.json</code>:"
@@ -10214,18 +12030,30 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr "點é¸ä¸Šå‚³"
+msgid "Uploaded on"
+msgstr ""
+
+msgid "Uploads"
+msgstr ""
+
msgid "Upstream"
msgstr ""
msgid "Upvotes"
msgstr "è´Š"
+msgid "Usage"
+msgstr ""
+
msgid "Usage ping is not enabled"
msgstr "使用情æ³åµæ¸¬æœªå•Ÿå‹•"
msgid "Usage statistics"
msgstr "使用情形統計資料"
+msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
+msgstr ""
+
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr "使用 <code>%{native_redirect_uri}</code> 進行本機測試"
@@ -10256,18 +12084,54 @@ msgstr "用來讓æˆå“¡ç™»å…¥æ‚¨åœ¨ GitLab 的群組"
msgid "Used to help configure your identity provider"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "使用者群組åªåœ¨å•Ÿç”¨ %{usage_ping_link_start} 使用情æ³åµæ¸¬ %{usage_ping_link_end} 時顯示。"
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr "使用者設定"
msgid "User and IP Rate Limits"
msgstr "使用者與 IP 速率é™åˆ¶"
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr "使用者地圖"
+msgid "User settings"
+msgstr ""
+
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr "活動"
@@ -10346,18 +12210,39 @@ msgstr ""
msgid "Users"
msgstr "使用者"
+msgid "Users outside of license"
+msgstr ""
+
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
+msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use."
+msgstr ""
+
+msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
+msgstr ""
+
msgid "Validate"
msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
+msgid "Validity"
+msgstr ""
+
msgid "Value"
msgstr ""
+msgid "Variables"
+msgstr ""
+
msgid "Various container registry settings."
msgstr "å„種容器的登入表設定。"
@@ -10382,9 +12267,6 @@ msgstr "已驗證"
msgid "Version"
msgstr "版本"
-msgid "View %{alerts}"
-msgstr "檢視 %{alerts}"
-
msgid "View app"
msgstr "檢視應用程å¼"
@@ -10394,8 +12276,8 @@ msgstr ""
msgid "View details: %{details_url}"
msgstr ""
-msgid "View documentation"
-msgstr "檢視檔案"
+msgid "View eligible approvers"
+msgstr ""
msgid "View epics list"
msgstr "檢視 Epic 列表"
@@ -10406,12 +12288,15 @@ msgstr "ç€è¦½æª”案 @ "
msgid "View group labels"
msgstr "檢視群組標籤"
-msgid "View issue"
-msgstr "檢視議題"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "在 GitLab 上檢視"
+msgid "View job trace"
+msgstr ""
+
msgid "View jobs"
msgstr "檢視工作"
@@ -10445,6 +12330,9 @@ msgstr "å¯è¦–級別"
msgid "Visibility level:"
msgstr "å¯è¦‹ç­‰ç´šï¼š"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "å¯è¦‹æ€§ï¼š"
@@ -10481,12 +12369,18 @@ msgstr "檔案"
msgid "Vulnerability|Identifiers"
msgstr "標示"
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr "實例"
msgid "Vulnerability|Links"
msgstr "連çµ"
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr "專案"
@@ -10535,6 +12429,12 @@ msgstr ""
msgid "Web terminal"
msgstr "網é çµ‚端器"
+msgid "Webhooks"
+msgstr ""
+
+msgid "Webhooks Help"
+msgstr ""
+
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "Webhooks å…許你觸發 URL,例如推é€äº†æ–°çš„程å¼ç¢¼ã€æˆ–是建立了新的議題。您å¯ä»¥è¨­å®š Webhook 以監è½æŒ‡å®šçš„活動,例如推é€ã€è­°é¡Œæˆ–åˆä½µè«‹æ±‚。群組 Webhooks 將套用至群組中的所有專案,å…許你è¦ç¯„整個群組中的 Webhook 的功能。"
@@ -10553,9 +12453,16 @@ msgstr "當執行器被鎖定,其將ä¸èƒ½è¢«å…¶ä»–專案指定。"
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr "當啟用,使用者在接å—æ¢æ¬¾ä¹‹å‰éƒ½ä¸èƒ½ä½¿ç”¨ GitLab。"
+msgid "When fast-forward merge is not possible, the user is given the option to rebase."
+msgstr ""
+
msgid "When leaving the URL blank, classification labels can still be specified without disabling cross project features or performing external authorization checks."
msgstr "當 URL 留空時,還是å¯ä»¥æŒ‡å®šåˆ†é¡žæ¨™ç±¤ï¼Œè€Œä¸”ä¸å¿…åœç”¨è·¨å°ˆæ¡ˆåŠŸèƒ½æˆ–執行外部授權檢查。"
+msgid "When this merge request is accepted"
+msgid_plural "When these merge requests are accepted"
+msgstr[0] ""
+
msgid "When:"
msgstr ""
@@ -10568,6 +12475,9 @@ msgstr "誰å¯ä»¥çœ‹åˆ°é€™å€‹ç¾¤çµ„?"
msgid "Wiki"
msgstr "Wiki"
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr "複製您的 Wiki"
@@ -10679,6 +12589,9 @@ msgstr "建立é é¢"
msgid "Wiki|Create page"
msgstr "建立é é¢"
+msgid "Wiki|Created date"
+msgstr ""
+
msgid "Wiki|Edit Page"
msgstr "編輯é é¢"
@@ -10697,6 +12610,9 @@ msgstr "é é¢ç‰ˆæœ¬"
msgid "Wiki|Pages"
msgstr "é é¢"
+msgid "Wiki|Title"
+msgstr ""
+
msgid "Wiki|Wiki Pages"
msgstr "維基é é¢"
@@ -10709,12 +12625,24 @@ msgstr "é€éŽè²¢ç»åˆ†æžï¼Œæ‚¨å¯ä»¥å–得活動概覽,其包å«äº†æ‚¨çš„組
msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è«‹"
+msgid "Workflow Help"
+msgstr ""
+
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
+msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
+msgstr ""
+
msgid "Yes"
msgstr "是"
@@ -10745,6 +12673,9 @@ msgstr "å°‡è¦åˆªé™¤æœ¬åˆ†æ”¯å°ˆæ¡ˆèˆ‡ä¸»å¹¹ %{forked_from_project} 的所有關
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr "å°‡è¦æŠŠ %{project_full_name} 的所有權轉移給å¦ä¸€å€‹äººã€‚真的「確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr "您在唯讀的 GitLab 主機上。"
@@ -10760,6 +12691,15 @@ msgstr "您也å¯ä»¥å¾žæŒ‡ä»¤æ–°å¢žä¸€å€‹å°ˆæ¡ˆã€‚"
msgid "You can also star a label to make it a priority label."
msgstr "您還å¯ä»¥ç‚ºæŸå€‹æ¨™ç±¤åŠ ä¸Šæ˜Ÿæ˜Ÿï¼Œä½¿å…¶æˆç‚ºå„ªå…ˆæ¨™ç±¤ã€‚"
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr "您å¯ä»¥é€éŽè«‹æ±‚加入這些群組,來輕鬆地為他們åšå‡ºè²¢ç»ã€‚"
@@ -10769,6 +12709,12 @@ msgstr "您å¯ä»¥è¼•é¬†åœ°åœ¨ Kubernetes å¢é›†ä¸Šå®‰è£åŸ·è¡Œå™¨ã€‚ %{link_to_h
msgid "You can move around the graph by using the arrow keys."
msgstr "您å¯ä»¥ä½¿ç”¨ä¸Šä¸‹å·¦å³éµç§»å‹•åœ–形。"
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr "åªèƒ½åœ¨åˆ†æ”¯ä¸Šå»ºç«‹æª”案"
@@ -10778,6 +12724,9 @@ msgstr "您åªèƒ½åœ¨åˆ†æ”¯ä¸Šç·¨è¼¯æª”案"
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr "您å¯ä»¥é€éŽä½¿ç”¨äº’動模å¼é¸æ“‡ %{use_ours} 或 %{use_theirs} 按鈕ã€æˆ–者是直接編輯檔案來解決åˆä½µè¡çªï¼Œä¸¦å°‡é€™äº›è®Šæ›´æ交到 %{branch_name}。"
@@ -10787,12 +12736,30 @@ msgstr "您å¯ä»¥å°‡å·¥ä½œè¨­å®šç‚ºæ­¢ä½¿ç”¨å…·æœ‰ç‰¹åˆ¥æ¨™ç±¤çš„執行器。請
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr "您å¯ä»¥é€éŽ %{linkStart}CI Lint%{linkEnd} 測試 .gitlab-ci.yml"
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead."
msgstr "您ä¸èƒ½å¯«å…¥åˆ°å”¯è®€çš„æ¬¡è¦ GitLab Geo 主機。請改用 %{link_to_primary_node}。"
msgid "You cannot write to this read-only GitLab instance."
msgstr "您ä¸èƒ½ä¿®æ”¹é€™å€‹å”¯è®€çš„ GitLab 主機。"
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr "你還沒有任何訂閱"
@@ -10811,12 +12778,27 @@ msgstr "您沒有任何已授權的應用程å¼"
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You have been granted %{member_human_access} access to %{label}."
+msgstr ""
+
+msgid "You have been unsubscribed from this thread."
+msgstr ""
+
+msgid "You have declined the invitation to join %{label}."
+msgstr ""
+
msgid "You have no permissions"
msgstr "你沒有權é™"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr ""
+
msgid "You have reached your project limit"
msgstr "您已é”到專案數é‡é™åˆ¶"
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -10829,7 +12811,7 @@ msgstr "您必須æ“有維護者存å–æ‰èƒ½å¼·åˆ¶ç§»é™¤éŽ–定"
msgid "You need a different license to enable FileLocks feature"
msgstr "您需è¦ä½¿ç”¨ä¸åŒçš„授權以啟用 FileLocks 功能"
-msgid "You need a different license to enable Geo replication."
+msgid "You need a different license to use Geo replication."
msgstr ""
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
@@ -10841,12 +12823,24 @@ msgstr "需è¦æ¬Šé™æ‰èƒ½é€™éº¼åšã€‚"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr "ä¸æœƒæ”¶åˆ°ä»»ä½•é€šçŸ¥éƒµä»¶"
@@ -10874,6 +12868,15 @@ msgstr "在您的個人資料中新增 SSH 密鑰之å‰ï¼Œä½ ä¸èƒ½é€éŽ SSH ä¾
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "你需è¦é¸æ“‡å…©å€‹ä¸åŒçš„分支,æ‰èƒ½é€²è¡Œæ¯”較。"
+msgid "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."
+msgstr ""
+
+msgid "You're not allowed to make changes to this project directly. A fork of this project is being created that you can make changes in, so you can submit a merge request."
+msgstr ""
+
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "您因為%{reason}收到此電å­éƒµä»¶ã€‚"
@@ -10907,6 +12910,15 @@ msgstr "您的待辦事項"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr "æ‚¨çš„æ‡‰ç”¨ç¨‹å¼ (%{size})"
@@ -10925,9 +12937,15 @@ msgstr "您的變更已被æ交。æ交記錄 %{commitId} %{commitStats}"
msgid "Your changes have been saved"
msgstr "您的修改已經儲存"
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr "你的留言將ä¸æœƒè¢«å…¬é–‹ã€‚"
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
@@ -10943,15 +12961,30 @@ msgstr ""
msgid "Your name"
msgstr "您的åå­—"
+msgid "Your new SCIM token"
+msgstr ""
+
+msgid "Your new personal access token has been created."
+msgstr ""
+
+msgid "Your password reset token has expired."
+msgstr ""
+
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
msgid "Your projects"
msgstr "您的專案"
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr "已刪除的使用者"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "之å‰"
@@ -10961,12 +12994,15 @@ msgstr ""
msgid "among other things"
msgstr "除了其他事情"
-msgid "assign yourself"
-msgstr "指派給自己"
+msgid "at"
+msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr "分支å稱"
@@ -10991,14 +13027,34 @@ msgstr "%{namespace} 被 %{vulnerability} 影響"
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} 更多"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} åµæ¸¬åˆ° %{fixedCount} 個已修復的安全æ¼æ´žã€‚"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} åµæ¸¬åˆ° %{newCount} 個新的安全æ¼æ´ž"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} åµæ¸¬åˆ° %{newCount} 新的安全æ¼æ´žå’Œ %{fixedCount} 個已修復的æ¼æ´ž"
@@ -11028,6 +13084,9 @@ msgstr "%{reportType} 載入中"
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "%{reportType}:因載入導致錯誤"
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr "(載入çµæžœæ™‚發生錯誤)"
@@ -11037,6 +13096,15 @@ msgstr "(正在載入)"
msgid "ciReport|(is loading, errors when loading results)"
msgstr "(正在載入,當載入çµæžœæ™‚發生錯誤)"
+msgid "ciReport|All projects"
+msgstr ""
+
+msgid "ciReport|All report types"
+msgstr ""
+
+msgid "ciReport|All severities"
+msgstr ""
+
msgid "ciReport|Class"
msgstr "類別"
@@ -11046,12 +13114,24 @@ msgstr "程å¼ç¢¼å“質"
msgid "ciReport|Confidence"
msgstr "ä¿¡ä»»"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "正在掃æ容器"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "容器掃æåµæ¸¬åœ¨æ‚¨çš„ Docker 映åƒä¸­çš„已知æ¼æ´žã€‚"
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
@@ -11094,9 +13174,18 @@ msgstr "修正的:"
msgid "ciReport|Identifiers"
msgstr "身份"
+msgid "ciReport|Image"
+msgstr ""
+
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "實例"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "了解關於和安全性報告 (Alpha) 互動的更多資訊。"
@@ -11138,9 +13227,6 @@ msgstr "沒有效能指標的變動"
msgid "ciReport|Performance metrics"
msgstr "效能指標"
-msgid "ciReport|Revert dismissal"
-msgstr "撤回忽略"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11162,6 +13248,9 @@ msgstr "éœæ…‹æ‡‰ç”¨ç¨‹å¼å®‰å…¨æ€§æ¸¬è©¦ (SAST) 在您的原始碼中åµæ¸¬å·²ç
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "建立議題時發生錯誤。請é‡è©¦ã€‚"
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "é§å›žæ­¤æ¼æ´žæ™‚發生錯誤。請é‡è©¦ã€‚"
@@ -11180,9 +13269,18 @@ msgstr "載入相ä¾æ€§æŽƒæ報告時發生錯誤。"
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "撤回é§å›žæ™‚發生錯誤。請é‡è©¦ã€‚"
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "å°‡ %{name} 從 %{version} å‡ç´šåˆ° %{fixed}。"
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "由 %{packagesString} 和 %{lastPackage} 使用"
@@ -11193,12 +13291,33 @@ msgstr "檢視完整報告"
msgid "ciReport|on pipeline"
msgstr "管線上"
-msgid "command line instructions"
-msgstr "指令說明"
-
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "confidence|Confirmed"
+msgstr ""
+
+msgid "confidence|Experimental"
+msgstr ""
+
+msgid "confidence|High"
+msgstr ""
+
+msgid "confidence|Ignore"
+msgstr ""
+
+msgid "confidence|Low"
+msgstr ""
+
+msgid "confidence|Medium"
+msgstr ""
+
+msgid "confidence|Undefined"
+msgstr ""
+
+msgid "confidence|Unknown"
+msgstr ""
+
msgid "confidentiality|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."
msgstr "å³å°‡é—œé–‰éš±ç§æ€§ï¼Œé€™å°‡è®“ <strong>所有使用者</strong> 都å¯ä»¥æª¢è¦–並留言目å‰çš„議題。"
@@ -11224,6 +13343,9 @@ msgstr ""
msgid "deploy token"
msgstr "部署憑證"
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr "å·²åœç”¨"
@@ -11253,6 +13375,21 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} 將更新é ä¼°èŠ±è²»æ™‚間。"
+msgid "failed"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr "為此專案"
@@ -11262,15 +13399,9 @@ msgstr "來自"
msgid "group"
msgstr ""
-msgid "help"
-msgstr "幫助"
-
msgid "here"
msgstr "這裡"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
@@ -11332,6 +13463,9 @@ msgstr "最新的部署"
msgid "latest version"
msgstr "最新版本"
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "license management"
msgstr "授權管ç†"
@@ -11351,6 +13485,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
@@ -11387,6 +13524,9 @@ msgstr "æ交你的請批準時發生錯誤。"
msgid "mrWidget|Approve"
msgstr "批准"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "批准由"
@@ -11459,17 +13599,20 @@ msgstr "本機åˆä½µ"
msgid "mrWidget|Merge request approved"
msgstr "åˆä½µè«‹æ±‚已經批准"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "åˆä½µè«‹æ±‚已經被批准; ä½ å¯ä»¥å¦å¤–批准"
msgid "mrWidget|Merged by"
msgstr "åˆä½µè€…"
-msgid "mrWidget|No Approval required"
-msgstr "無須批准"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "ä¸éœ€è¦æ‰¹å‡†ï¼›ä½†æ‚¨ä»å¯ä»¥æ‰¹å‡†"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "åœ¨ç¶²é  IDE 中開啟"
@@ -11518,6 +13661,9 @@ msgstr "還原"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "é€éŽæ–°çš„åˆä½µè«‹æ±‚還原此åˆä½µè«‹æ±‚變更的項目"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "設定者"
@@ -11628,12 +13774,19 @@ msgstr "密碼"
msgid "personal access token"
msgstr "ç§äººå­˜å–憑證"
+msgid "point"
+msgid_plural "points"
+msgstr[0] ""
+
msgid "private"
msgstr ""
msgid "private key does not match certificate."
msgstr "ç§é‘°ä¸ç¬¦åˆæ†‘證。"
+msgid "processing"
+msgstr ""
+
msgid "project"
msgid_plural "projects"
msgstr[0] "專案"
@@ -11663,9 +13816,36 @@ msgid "reply"
msgid_plural "replies"
msgstr[0] "回應"
+msgid "reset it."
+msgstr ""
+
msgid "score"
msgstr ""
+msgid "security Reports|There was an error creating the merge request"
+msgstr ""
+
+msgid "severity|Critical"
+msgstr ""
+
+msgid "severity|High"
+msgstr ""
+
+msgid "severity|Info"
+msgstr ""
+
+msgid "severity|Low"
+msgstr ""
+
+msgid "severity|Medium"
+msgstr ""
+
+msgid "severity|Undefined"
+msgstr ""
+
+msgid "severity|Unknown"
+msgstr ""
+
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
@@ -11690,6 +13870,9 @@ msgstr "已開始"
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/package.json b/package.json
index 4ec484a5e86..ab7268642ec 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,17 @@
{
"private": true,
"scripts": {
+ "check-dependencies": "yarn check --integrity",
"clean": "rm -rf public/assets tmp/cache/*-loader",
- "dev-server": "nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
+ "dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
- "jest": "BABEL_ENV=jest jest",
+ "prejest": "yarn check-dependencies",
+ "jest": "jest",
+ "jest-debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"jsdoc": "jsdoc -c config/jsdocs.config.js",
+ "prekarma": "yarn check-dependencies",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
@@ -16,27 +20,35 @@
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
- "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.*",
- "webpack": "webpack --config config/webpack.config.js",
- "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
+ "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/** --custom-formatter node_modules/stylelint-error-string-formatter",
+ "stylelint-file": "node node_modules/stylelint/bin/stylelint.js",
+ "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
+ "test": "node scripts/frontend/test",
+ "webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js",
+ "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js",
+ "webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
},
"dependencies": {
- "@babel/core": "^7.2.2",
- "@babel/plugin-proposal-class-properties": "^7.3.0",
+ "@babel/core": "^7.4.4",
+ "@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-proposal-json-strings": "^7.2.0",
- "@babel/plugin-proposal-private-methods": "^7.3.0",
+ "@babel/plugin-proposal-private-methods": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
- "@babel/preset-env": "^7.3.1",
- "@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.54.0",
- "@gitlab/ui": "^2.0.4",
- "apollo-boost": "^0.1.20",
- "apollo-client": "^2.4.5",
+ "@babel/preset-env": "^7.4.4",
+ "@gitlab/csslab": "^1.9.0",
+ "@gitlab/svgs": "^1.63.0",
+ "@gitlab/ui": "^3.11.0",
+ "apollo-cache-inmemory": "^1.5.1",
+ "apollo-client": "^2.5.1",
+ "apollo-link": "^1.2.11",
+ "apollo-link-batch-http": "^1.2.11",
+ "apollo-upload-client": "^10.0.0",
+ "at.js": "^1.5.4",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.5",
- "bootstrap": "4.1.3",
+ "bootstrap": "4.3.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^2.0.1",
"chart.js": "2.7.2",
@@ -44,18 +56,20 @@
"clipboard": "^1.7.1",
"codesandbox-api": "^0.0.20",
"compression-webpack-plugin": "^2.0.0",
- "core-js": "^2.4.1",
+ "core-js": "^3.1.3",
"cropper": "^2.3.0",
"css-loader": "^1.0.0",
"d3": "^4.13.0",
"d3-array": "^1.2.1",
"d3-axis": "^1.0.8",
"d3-brush": "^1.0.4",
+ "d3-ease": "^1.0.3",
"d3-scale": "^1.0.7",
"d3-selection": "^1.2.0",
"d3-shape": "^1.2.0",
"d3-time": "^1.0.8",
"d3-time-format": "^2.1.1",
+ "d3-transition": "^1.1.1",
"dateformat": "^3.0.3",
"deckar01-task_list": "^2.2.0",
"diff": "^3.4.0",
@@ -72,20 +86,23 @@
"graphql": "^14.0.2",
"imports-loader": "^0.8.0",
"jed": "^1.1.1",
+ "jest-transform-graphql": "^2.1.0",
"jquery": "^3.2.1",
"jquery-ujs": "1.2.2",
+ "jquery.caret": "^0.3.1",
"jquery.waitforimages": "^2.2.0",
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"marked": "^0.3.12",
- "mermaid": "^8.0.0-rc.8",
+ "mermaid": "^8.0.0",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
+ "pdfjs-dist": "^2.0.943",
"pikaday": "^1.6.1",
- "popper.js": "^1.14.3",
+ "popper.js": "^1.14.7",
"prismjs": "^1.6.0",
"prosemirror-markdown": "^1.3.0",
"prosemirror-model": "^1.6.4",
@@ -100,6 +117,7 @@
"sql.js": "^0.4.0",
"stickyfilljs": "^2.0.5",
"style-loader": "^0.23.1",
+ "stylelint-error-string-formatter": "1.0.2",
"svg4everybody": "2.1.9",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
@@ -111,14 +129,14 @@
"underscore": "^1.9.0",
"url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
- "vue": "^2.5.21",
- "vue-apollo": "^3.0.0-beta.25",
- "vue-loader": "^15.4.2",
+ "vue": "^2.6.10",
+ "vue-apollo": "^3.0.0-beta.28",
+ "vue-loader": "^15.7.0",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
- "vue-template-compiler": "^2.5.21",
- "vue-virtual-scroll-list": "^1.2.5",
- "vuex": "^3.0.1",
+ "vue-template-compiler": "^2.6.10",
+ "vue-virtual-scroll-list": "^1.3.1",
+ "vuex": "^3.1.0",
"webpack": "^4.29.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.2.1",
@@ -127,19 +145,17 @@
"xterm": "^3.5.0"
},
"devDependencies": {
- "@gitlab/eslint-config": "^1.4.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.2.0",
+ "@gitlab/eslint-config": "^1.6.0",
"@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0",
- "babel-core": "^7.0.0-bridge",
- "babel-jest": "^23.6.0",
+ "babel-jest": "^24.1.0",
"babel-plugin-dynamic-import-node": "^2.2.0",
"babel-plugin-istanbul": "^5.1.0",
"babel-plugin-rewire": "^1.2.0",
- "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
- "babel-template": "^6.26.0",
- "babel-types": "^6.26.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
+ "custom-jquery-matchers": "^2.1.0",
"docdash": "^1.0.2",
"eslint": "~5.9.0",
"eslint-import-resolver-jest": "^2.1.1",
@@ -147,16 +163,18 @@
"eslint-plugin-html": "5.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
- "eslint-plugin-jest": "^22.1.0",
- "gettext-extractor": "^3.3.2",
- "gettext-extractor-vue": "^4.0.1",
+ "eslint-plugin-jest": "^22.3.0",
+ "gettext-extractor": "^3.4.3",
+ "gettext-extractor-vue": "^4.0.2",
"graphql-tag": "^2.10.0",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
- "jest": "^23.6.0",
- "jest-junit": "^5.2.0",
+ "jest": "^24.1.0",
+ "jest-environment-jsdom": "^24.0.0",
+ "jest-junit": "^6.3.0",
+ "jest-util": "^24.0.0",
"jsdoc": "^3.5.5",
"jsdoc-vue": "^1.0.0",
"karma": "^3.0.0",
@@ -167,16 +185,21 @@
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
+ "md5": "^2.2.1",
+ "node-sass": "^4.12.0",
"nodemon": "^1.18.9",
"pixelmatch": "^4.0.2",
"postcss": "^7.0.14",
- "prettier": "1.16.1",
+ "prettier": "1.16.4",
"stylelint": "^9.10.1",
"stylelint-config-recommended": "^2.1.0",
- "stylelint-scss": "^3.5.3",
- "vue-jest": "^3.0.2",
+ "stylelint-scss": "^3.5.4",
+ "vue-jest": "^4.0.0-beta.2",
"webpack-dev-server": "^3.1.14",
- "yarn-deduplicate": "^1.1.0"
+ "yarn-deduplicate": "^1.1.1"
+ },
+ "resolutions": {
+ "vue-jest/ts-jest": "24.0.0"
},
"engines": {
"node": ">=8.10.0",
diff --git a/qa/.gitignore b/qa/.gitignore
index 19ec17d0005..b0ae074ac07 100644
--- a/qa/.gitignore
+++ b/qa/.gitignore
@@ -1,2 +1,3 @@
tmp/
.ruby-version
+urls.yml
diff --git a/qa/Dockerfile b/qa/Dockerfile
index ca7f9accb70..74be373b8e8 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.5-stretch
+FROM ruby:2.6-stretch
LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
ENV DEBIAN_FRONTEND noninteractive
diff --git a/qa/Gemfile b/qa/Gemfile
index f29006617ed..12994b85322 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -7,5 +7,7 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
-gem 'nokogiri', '~> 1.10.1'
+gem 'nokogiri', '~> 1.10.3'
gem 'rspec-retry', '~> 0.6.1'
+gem 'faker', '~> 1.6', '>= 1.6.6'
+gem 'knapsack', '~> 1.17'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index c3d9f558c23..6b0635ed0e2 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -32,11 +32,15 @@ GEM
diff-lcs (1.3)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
+ faker (1.9.3)
+ i18n (>= 0.7)
ffi (1.9.25)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.9.1)
concurrent-ruby (~> 1.0)
+ knapsack (1.17.1)
+ rake
launchy (2.4.3)
addressable (~> 2.3)
method_source (0.9.0)
@@ -47,7 +51,7 @@ GEM
mini_portile2 (2.4.0)
minitest (5.11.1)
netrc (0.11.0)
- nokogiri (1.10.1)
+ nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
pry (0.11.3)
coderay (~> 1.1.0)
@@ -99,7 +103,9 @@ DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
- nokogiri (~> 1.10.1)
+ faker (~> 1.6, >= 1.6.6)
+ knapsack (~> 1.17)
+ nokogiri (~> 1.10.3)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
diff --git a/qa/README.md b/qa/README.md
index 7709db36f8e..ef6f202464d 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -1,6 +1,6 @@
# GitLab QA - End-to-end tests for GitLab
-This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md)
+This directory contains [end-to-end tests](../../../doc/development/testing_guide/end_to_end/index.md)
for GitLab. It includes the test framework and the tests themselves.
The tests can be found in `qa/specs/features` (not to be confused with the unit
@@ -29,7 +29,7 @@ verify coupling between page objects implemented as a part of GitLab QA
and corresponding views / partials / selectors in CE / EE.
Whenever `qa:selectors` job fails in your merge request, you are supposed to
-fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
+fix [page objects](../doc/development/testing_guide/end_to_end/page_objects.md). You should also trigger end-to-end tests
using `package-and-qa` manual action, to test if everything works fine.
## How can I use it?
@@ -39,12 +39,20 @@ following call would login to a local [GDK] instance and run all specs in
`qa/specs/features`:
```
-bin/qa Test::Instance::All http://localhost:3000
+# Make sure to install the dependencies first with `bundle install`
+
+bundle exec bin/qa Test::Instance::All http://localhost:3000
```
+Note: If you want to run tests requiring SSH against GDK, you
+will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
+
### Writing tests
-1. [Using page objects](qa/page/README.md)
+- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/quick_start_guide.md)
+ - [Best practices](../doc/development/testing_guide/best_practices.md)
+ - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
+ - [Guidelines](../doc/development/testing_guide/index.md)
### Running specific tests
@@ -52,16 +60,19 @@ You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/repository/
+bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/repository
```
Since the arguments would be passed to `rspec`, you could use all `rspec`
options there. For example, passing `--backtrace` and also line number:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace
+bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace
```
+Note that the separator `--` is required; all subsequent options will be
+ignored by the QA framework and passed to `rspec`.
+
### Overriding the authenticated user
Unless told otherwise, the QA tests will run as the default `root` user seeded
@@ -71,7 +82,7 @@ If you need to authenticate as a different user, you can provide the
`GITLAB_USERNAME` and `GITLAB_PASSWORD` environment variables:
```
-GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance::All https://gitlab.example.com
+GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
```
If your user doesn't have permission to default sandbox group
@@ -79,7 +90,7 @@ If your user doesn't have permission to default sandbox group
`GITLAB_SANDBOX_NAME`:
```
-GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com
+GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bundle exec bin/qa Test::Instance::All https://gitlab.example.com
```
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
@@ -114,7 +125,7 @@ tests that are expected to fail while a fix is in progress (similar to how
can be used).
```
-bin/qa Test::Instance::All http://localhost --tag quarantine
+bundle exec bin/qa Test::Instance::All http://localhost -- --tag quarantine
```
If `quarantine` is used with other tags, tests will only be run if they have at
@@ -125,3 +136,25 @@ For example, suppose one test has `:smoke` and `:quarantine` metadata, and
another test has `:ldap` and `:quarantine` metadata. If the tests are run with
`--tag smoke --tag quarantine`, only the first test will run. The test with
`:ldap` will not run even though it also has `:quarantine`.
+
+### Running tests with a feature flag enabled
+
+Tests can be run with with a feature flag enabled by using the command-line
+option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag
+that enforces Gitaly request limits, you would use the command:
+
+```
+bundle exec bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits
+```
+
+This will instruct the QA framework to enable the `gitaly_enforce_requests_limits`
+feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)), run
+all the tests in the `Test::Instance::All` scenario, and then disable the
+feature flag again.
+
+Note: the QA framework doesn't currently allow you to easily toggle a feature
+flag during a single test, [as you can in unit tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs),
+but [that capability is planned](https://gitlab.com/gitlab-org/quality/team-tasks/issues/77).
+
+Note also that the `--` separator isn't used because `--enable-feature` is a QA
+framework option, not an `rspec` option.
diff --git a/qa/Rakefile b/qa/Rakefile
index 9a7b9c6bb35..7ac018f7286 100644
--- a/qa/Rakefile
+++ b/qa/Rakefile
@@ -1,5 +1,6 @@
require_relative 'qa/tools/revoke_all_personal_access_tokens'
require_relative 'qa/tools/delete_subgroups'
+require_relative 'qa/tools/generate_perf_testdata'
desc "Revokes all personal access tokens"
task :revoke_personal_access_tokens do
@@ -10,3 +11,30 @@ desc "Deletes subgroups within a provided group"
task :delete_subgroups do
QA::Tools::DeleteSubgroups.new.run
end
+
+desc "Generate Performance Testdata"
+task :generate_perf_testdata do
+ QA::Tools::GeneratePerfTestdata.new.run
+end
+
+desc "Run artillery load tests"
+task :run_artillery_load_tests do
+ unless ENV['HOST_URL'] && ENV['LARGE_ISSUE_URL'] && ENV['LARGE_MR_URL']
+ urls_file = ENV['URLS_FILE_PATH'] || 'urls.yml'
+
+ unless File.exist?(urls_file)
+ raise "\n#{urls_file} file is missing. Please provide correct URLS_FILE_PATH or all of HOST_URL, LARGE_ISSUE_URL and LARGE_MR_URL\n\n"
+ end
+
+ urls = YAML.safe_load(File.read(urls_file))
+ ENV['HOST_URL'] = urls["host"]
+ ENV['LARGE_ISSUE_URL'] = urls["large_issue"]
+ ENV['LARGE_MR_URL'] = urls["large_mr"]
+ end
+
+ sh('artillery run load/artillery.yml -o report.json')
+ sh('artillery report report.json -o report.html && rm report.json')
+end
+
+desc "Generate data and run load tests"
+task generate_data_and_run_load_test: [:generate_perf_testdata, :run_artillery_load_tests]
diff --git a/qa/knapsack/gitlab-ce/review-qa-all_master_report.json b/qa/knapsack/gitlab-ce/review-qa-all_master_report.json
new file mode 100644
index 00000000000..f147346ba0f
--- /dev/null
+++ b/qa/knapsack/gitlab-ce/review-qa-all_master_report.json
@@ -0,0 +1,42 @@
+{
+ "qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 9.697327613830566,
+ "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 46.54227638244629,
+ "qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 10.214765310287476,
+ "qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 7.882027864456177,
+ "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678,
+ "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 12.772682905197144,
+ "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 29.76174831390381,
+ "qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375,
+ "qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 22.320587396621704,
+ "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857,
+ "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 0.9618203639984131,
+ "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 13.403101205825806,
+ "qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 8.810423135757446,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 7.730542182922363,
+ "qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726,
+ "qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771,
+ "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 9.48607873916626,
+ "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 19.552733182907104,
+ "qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854,
+ "qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 8.281434059143066,
+ "qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746,
+ "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.422840595245361,
+ "qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 3.438166856765747,
+ "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.679633855819702,
+ "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 27.943300485610962,
+ "qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 39.17585229873657,
+ "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 40.09336972236633,
+ "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 3.705310821533203,
+ "qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393,
+ "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 92.46774697303772,
+ "qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 100.28881478309631,
+ "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 23.710937023162842,
+ "qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317,
+ "qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 25.460349321365356,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565,
+ "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 6.731764793395996,
+ "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 15.342933893203735,
+ "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 11.280649185180664,
+ "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 57.48992609977722,
+ "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 32.5517954826355
+} \ No newline at end of file
diff --git a/qa/load/artillery.yml b/qa/load/artillery.yml
new file mode 100644
index 00000000000..17d253ec480
--- /dev/null
+++ b/qa/load/artillery.yml
@@ -0,0 +1,25 @@
+config:
+ target: "{{ $processEnvironment.HOST_URL }}"
+ http:
+ pool: 10 # All HTTP requests from all virtual users will be sent over the same <pool> connections.
+ # This also means that there is a limit on the number of requests sent per second.
+ phases:
+ - duration: 30
+ arrivalRate: 10
+ name: "Warm up"
+ - duration: 90
+ arrivalRate: 10
+ rampTo: 100
+ name: "Gradual ramp up"
+ - duration: 90
+ arrivalRate: 100
+ name: "Sustained max load"
+scenarios:
+ - name: "Visit large issue url"
+ flow:
+ - get:
+ url: "{{ $processEnvironment.LARGE_ISSUE_URL }}"
+ - name: "Visit large MR url"
+ flow:
+ - get:
+ url: "{{ $processEnvironment.LARGE_MR_URL }}"
diff --git a/qa/qa.rb b/qa/qa.rb
index 2b3ffabbbaa..944dcc31917 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -17,6 +17,7 @@ module QA
autoload :Env, 'qa/runtime/env'
autoload :Address, 'qa/runtime/address'
autoload :Path, 'qa/runtime/path'
+ autoload :Feature, 'qa/runtime/feature'
autoload :Fixtures, 'qa/runtime/fixtures'
autoload :Logger, 'qa/runtime/logger'
@@ -61,6 +62,7 @@ module QA
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
+ autoload :Snippet, 'qa/resource/snippet'
module Events
autoload :Base, 'qa/resource/events/base'
@@ -88,6 +90,7 @@ module QA
autoload :Bootable, 'qa/scenario/bootable'
autoload :Actable, 'qa/scenario/actable'
autoload :Template, 'qa/scenario/template'
+ autoload :SharedAttributes, 'qa/scenario/shared_attributes'
##
# Test scenario entrypoints.
@@ -127,6 +130,7 @@ module QA
autoload :View, 'qa/page/view'
autoload :Element, 'qa/page/element'
autoload :Validator, 'qa/page/validator'
+ autoload :Validatable, 'qa/page/validatable'
module Main
autoload :Login, 'qa/page/main/login'
@@ -142,6 +146,12 @@ module QA
module Dashboard
autoload :Projects, 'qa/page/dashboard/projects'
autoload :Groups, 'qa/page/dashboard/groups'
+
+ module Snippet
+ autoload :New, 'qa/page/dashboard/snippet/new'
+ autoload :Index, 'qa/page/dashboard/snippet/index'
+ autoload :Show, 'qa/page/dashboard/snippet/show'
+ end
end
module Group
@@ -266,6 +276,7 @@ module QA
module Layout
autoload :Banner, 'qa/page/layout/banner'
+ autoload :PerformanceBar, 'qa/page/layout/performance_bar'
end
module Label
@@ -284,10 +295,12 @@ module QA
module Settings
autoload :Repository, 'qa/page/admin/settings/repository'
autoload :General, 'qa/page/admin/settings/general'
+ autoload :MetricsAndProfiling, 'qa/page/admin/settings/metrics_and_profiling'
module Component
autoload :RepositoryStorage, 'qa/page/admin/settings/component/repository_storage'
autoload :AccountAndLimit, 'qa/page/admin/settings/component/account_and_limit'
+ autoload :PerformanceBar, 'qa/page/admin/settings/component/performance_bar'
end
end
end
@@ -342,6 +355,10 @@ module QA
module Specs
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
+
+ module Helpers
+ autoload :Quarantine, 'qa/specs/helpers/quarantine'
+ end
end
##
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index e0fbbed2567..6c1820ffdc8 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module CE
module Strategy
@@ -8,10 +10,18 @@ module QA
end
def perform_before_hooks
+ retries ||= 0
+
# The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests.
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
- QA::Page::Main::Login.perform(&:assert_page_loaded)
+ rescue QA::Page::Validatable::PageValidationError
+ if (retries += 1) < 3
+ Runtime::Logger.warn("The login page did not appear as expected. Retrying... (attempt ##{retries})")
+ retry
+ end
+
+ raise
end
end
end
diff --git a/qa/qa/fixtures/auto_devops_rack/Dockerfile b/qa/qa/fixtures/auto_devops_rack/Dockerfile
new file mode 100644
index 00000000000..1f59c23ea88
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Dockerfile
@@ -0,0 +1,9 @@
+FROM ruby:2.6.3-alpine
+ADD ./ /app/
+WORKDIR /app
+ENV RACK_ENV production
+ENV PORT 5000
+EXPOSE 5000
+
+RUN bundle install
+CMD ["bundle","exec", "rackup", "-p", "5000"]
diff --git a/qa/qa/git/location.rb b/qa/qa/git/location.rb
index b74f38f3ae3..032c6837db1 100644
--- a/qa/qa/git/location.rb
+++ b/qa/qa/git/location.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'uri'
require 'forwardable'
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 0aa94101098..84353e3d0c7 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -12,6 +12,7 @@ module QA
module Git
class Repository
include Scenario::Actable
+ RepositoryCommandError = Class.new(StandardError)
attr_writer :use_lfs
attr_accessor :env_vars
@@ -205,6 +206,10 @@ module QA
output.chomp!
Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
+ unless status.success?
+ raise RepositoryCommandError, "The command #{command} failed (#{status.exitstatus}) with the following output:\n#{output}"
+ end
+
Result.new(status.exitstatus == 0, output)
end
@@ -246,7 +251,7 @@ module QA
end
def netrc_already_contains_content?
- read_netrc_content.grep(/^#{netrc_content}$/).any?
+ read_netrc_content.grep(/^#{Regexp.escape(netrc_content)}$/).any?
end
end
end
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
deleted file mode 100644
index d0de33892c4..00000000000
--- a/qa/qa/page/README.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Page objects in GitLab QA
-
-In GitLab QA we are using a known pattern, called _Page Objects_.
-
-This means that we have built an abstraction for all GitLab pages that we use
-to drive GitLab QA scenarios. Whenever we do something on a page, like filling
-in a form, or clicking a button, we do that only through a page object
-associated with this area of GitLab.
-
-For example, when GitLab QA test harness signs in into GitLab, it needs to fill
-in a user login and user password. In order to do that, we have a class, called
-`Page::Main::Login` and `sign_in_using_credentials` methods, that is the only
-piece of the code, that has knowledge about `user_login` and `user_password`
-fields.
-
-## Why do we need that?
-
-We need page objects, because we need to reduce duplication and avoid problems
-whenever someone changes some selectors in GitLab's source code.
-
-Imagine that we have a hundred specs in GitLab QA, and we need to sign into
-GitLab each time, before we make assertions. Without a page object one would
-need to rely on volatile helpers or invoke Capybara methods directly. Imagine
-invoking `fill_in :user_login` in every `*_spec.rb` file / test example.
-
-When someone later changes `t.text_field :login` in the view associated with
-this page to `t.text_field :username` it will generate a different field
-identifier, what would effectively break all tests.
-
-Because we are using `Page::Main::Login.act { sign_in_using_credentials }`
-everywhere, when we want to sign into GitLab, the page object is the single
-source of truth, and we will need to update `fill_in :user_login`
-to `fill_in :user_username` only in a one place.
-
-## What problems did we have in the past?
-
-We do not run QA tests for every commit, because of performance reasons, and
-the time it would take to build packages and test everything.
-
-That is why when someone changes `t.text_field :login` to
-`t.text_field :username` in the _new session_ view we won't know about this
-change until our GitLab QA nightly pipeline fails, or until someone triggers
-`package-and-qa` action in their merge request.
-
-Obviously such a change would break all tests. We call this problem a _fragile
-tests problem_.
-
-In order to make GitLab QA more reliable and robust, we had to solve this
-problem by introducing coupling between GitLab CE / EE views and GitLab QA.
-
-## How did we solve fragile tests problem?
-
-Currently, when you add a new `Page::Base` derived class, you will also need to
-define all selectors that your page objects depends on.
-
-Whenever you push your code to CE / EE repository, `qa:selectors` sanity test
-job is going to be run as a part of a CI pipeline.
-
-This test is going to validate all page objects that we have implemented in
-`qa/page` directory. When it fails, you will be notified about missing
-or invalid views / selectors definition.
-
-## How to properly implement a page object?
-
-We have built a DSL to define coupling between a page object and GitLab views
-it is actually implemented by. See an example below.
-
-```ruby
-module Page
- module Main
- class Login < Page::Base
- view 'app/views/devise/passwords/edit.html.haml' do
- element :password_field
- element :password_confirmation
- element :change_password_button
- end
-
- view 'app/views/devise/sessions/_new_base.html.haml' do
- element :login_field
- element :password_field
- element :sign_in_button
- end
-
- # ...
- end
-end
-```
-
-The `view` DSL method declares the filename of the view where an
-`element` is implemented.
-
-The `element` DSL method in turn declares an element for which a corresponding
-`qa-element-name-dasherized` CSS class need to be added to the view file.
-
-You can also define a value (String or Regexp) to match to the actual view
-code but **this is deprecated** in favor of the above method for two reasons:
-
-- Consistency: there is only one way to define an element
-- Separation of concerns: QA uses dedicated CSS classes instead of reusing code
- or classes used by other components (e.g. `js-*` classes etc.)
-
-```ruby
-view 'app/views/my/view.html.haml' do
- # Implicitly require `.qa-logout-button` CSS class to be present in the view
- element :logout_button
-
- ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
- # Require `f.submit "Sign in"` to be present in `my/view.html.haml
- element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
-
- ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
- # Match every line in `my/view.html.haml` against
- # `/link_to .* "My Profile"/` regexp.
- element :profile_link, /link_to .* "My Profile"/ # rubocop:disable QA/ElementWithPattern
-end
-```
-
-## Running the test locally
-
-During development, you can run the `qa:selectors` test by running
-
-```shell
-bin/qa Test::Sanity::Selectors
-```
-
-from within the `qa` directory.
-
-## Where to ask for help?
-
-If you need more information, ask for help on `#quality` channel on Slack
-(internal, GitLab Team only).
-
-If you are not a Team Member, and you still need help to contribute, please
-open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index 25564f2dc6e..eea5717f5a7 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -10,6 +10,7 @@ module QA
element :admin_settings_item
element :admin_settings_repository_item
element :admin_settings_general_item
+ element :admin_settings_metrics_and_profiling_item
end
def go_to_repository_settings
@@ -28,6 +29,14 @@ module QA
end
end
+ def go_to_metrics_and_profiling_settings
+ hover_settings do
+ within_submenu do
+ click_element :admin_settings_metrics_and_profiling_item
+ end
+ end
+ end
+
private
def hover_settings
diff --git a/qa/qa/page/admin/settings/component/performance_bar.rb b/qa/qa/page/admin/settings/component/performance_bar.rb
new file mode 100644
index 00000000000..bc29efb64c0
--- /dev/null
+++ b/qa/qa/page/admin/settings/component/performance_bar.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ module Component
+ class PerformanceBar < Page::Base
+ view 'app/views/admin/application_settings/_performance_bar.html.haml' do
+ element :enable_performance_bar_checkbox
+ element :save_changes_button
+ end
+
+ def enable_performance_bar
+ click_element :enable_performance_bar_checkbox
+ Capybara.current_session.driver.browser.manage.add_cookie(name: 'perf_bar_enabled', value: 'true')
+ end
+
+ def save_settings
+ click_element :save_changes_button
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/admin/settings/metrics_and_profiling.rb b/qa/qa/page/admin/settings/metrics_and_profiling.rb
new file mode 100644
index 00000000000..e10a92d7a54
--- /dev/null
+++ b/qa/qa/page/admin/settings/metrics_and_profiling.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ class MetricsAndProfiling < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/admin/application_settings/metrics_and_profiling.html.haml' do
+ element :performance_bar_settings
+ end
+
+ def expand_performance_bar(&block)
+ expand_section(:performance_bar_settings) do
+ Component::PerformanceBar.perform(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 05e38aba77d..d247a273637 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -8,6 +8,7 @@ module QA
prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL
include Scenario::Actable
+ extend Validatable
extend SingleForwardable
ElementNotFound = Class.new(RuntimeError)
@@ -77,8 +78,12 @@ module QA
page.evaluate_script('xhr.status') == 200
end
- def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time)
- find(element_selector_css(name), wait: wait, text: text_filter)
+ def find_element(name, **kwargs)
+ find(element_selector_css(name), kwargs)
+ end
+
+ def active_element?(name)
+ find_element(name, class: 'active')
end
def all_elements(name)
@@ -93,8 +98,10 @@ module QA
find_element(name).set(false)
end
- def click_element(name)
+ # replace with (..., page = self.class)
+ def click_element(name, page = nil)
find_element(name).click
+ page.validate_elements_present! if page
end
def fill_element(name, content)
@@ -109,12 +116,12 @@ module QA
element.select value.to_s.capitalize
end
- def has_element?(name, wait: Capybara.default_max_wait_time)
- has_css?(element_selector_css(name), wait: wait)
+ def has_element?(name, text: nil, wait: Capybara.default_max_wait_time)
+ has_css?(element_selector_css(name), wait: wait, text: text)
end
- def has_no_element?(name, wait: Capybara.default_max_wait_time)
- has_no_css?(element_selector_css(name), wait: wait)
+ def has_no_element?(name, text: nil, wait: Capybara.default_max_wait_time)
+ has_no_css?(element_selector_css(name), wait: wait, text: text)
end
def has_text?(text)
@@ -129,8 +136,17 @@ module QA
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
end
- def within_element(name)
- page.within(element_selector_css(name)) do
+ def wait_for_animated_element(name)
+ # It would be ideal if we could detect when the animation is complete
+ # but in some cases there's nothing we can easily access via capybara
+ # so instead we wait for the element, and then we wait a little longer
+ raise ElementNotFound, %Q(Couldn't find element named "#{name}") unless has_element?(name)
+
+ sleep 1
+ end
+
+ def within_element(name, text: nil)
+ page.within(element_selector_css(name), text: text) do
yield
end
end
@@ -157,6 +173,10 @@ module QA
find('body').click
end
+ def visit_link_in_element(name)
+ visit find_element(name)['href']
+ end
+
def self.path
raise NotImplementedError
end
diff --git a/qa/qa/page/component/dropzone.rb b/qa/qa/page/component/dropzone.rb
index fd44c57123a..757111f240b 100644
--- a/qa/qa/page/component/dropzone.rb
+++ b/qa/qa/page/component/dropzone.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Component
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
index f5add6bc9b5..07e191f1c9b 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -15,7 +15,7 @@ module QA
element :reply_comment_button
end
- base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do
+ base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do
element :discussion_reply
end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 98bcb96b92c..e40bc4b1d3e 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module QA
module Page
module Component
module Select2
def select_item(item_text)
- find('.select2-result-label', text: item_text).click
+ find('.select2-result-label', text: item_text, match: :prefer_exact).click
end
def clear_current_selection_if_present
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index 7a07515de62..52853376f17 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -19,11 +19,13 @@ module QA
has_filtered_group?(name)
end
- def go_to_group(name)
+ def click_group(name)
+ raise "Group with name #{name} not found!" unless has_group?(name)
+
click_link name
end
- def go_to_new_group
+ def click_new_group
click_on 'New group'
end
end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 0f434577b3b..7ab8ee39f72 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -1,9 +1,9 @@
+# frozen_string_literal: true
+
module QA
module Page
module Dashboard
class Projects < Page::Base
- view 'app/views/dashboard/projects/index.html.haml'
-
view 'app/views/shared/projects/_search_form.html.haml' do
element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/ # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/dashboard/snippet/index.rb b/qa/qa/page/dashboard/snippet/index.rb
new file mode 100644
index 00000000000..1f467fda9e1
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/index.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class Index < Page::Base
+ view 'app/views/layouts/header/_new_dropdown.haml' do
+ element :new_menu_toggle
+ element :global_new_snippet_link
+ end
+
+ def go_to_new_snippet_page
+ click_element :new_menu_toggle
+ click_element :global_new_snippet_link
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb
new file mode 100644
index 00000000000..4a8e65e20af
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class New < Page::Base
+ view 'app/views/shared/form_elements/_description.html.haml' do
+ element :issuable_form_description
+ end
+
+ view 'app/views/shared/snippets/_form.html.haml' do
+ element :snippet_title
+ element :snippet_file_name
+ element :create_snippet_button
+ end
+
+ def fill_title(title)
+ fill_element :snippet_title, title
+ end
+
+ def fill_description(description)
+ fill_element :issuable_form_description, description
+ end
+
+ def set_visibility(visibility)
+ choose visibility
+ end
+
+ def fill_file_name(name)
+ finished_loading?
+ fill_element :snippet_file_name, name
+ end
+
+ def fill_file_content(content)
+ finished_loading?
+ text_area.set content
+ end
+
+ def click_create_snippet_button
+ click_element :create_snippet_button
+ end
+
+ private
+
+ def text_area
+ find('#editor>textarea', visible: false)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb
new file mode 100644
index 00000000000..a75ea63eca7
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/show.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class Show < Page::Base
+ view 'app/views/shared/snippets/_header.html.haml' do
+ element :snippet_title
+ element :snippet_description
+ element :embed_type
+ element :snippet_box
+ end
+
+ view 'app/views/projects/blob/_header_content.html.haml' do
+ element :file_title_name
+ end
+
+ view 'app/views/shared/_file_highlight.html.haml' do
+ element :file_content
+ end
+
+ def has_snippet_title?(snippet_title)
+ within_element(:snippet_title) do
+ has_text?(snippet_title)
+ end
+ end
+
+ def has_snippet_description?(snippet_description)
+ within_element(:snippet_description) do
+ has_text?(snippet_description)
+ end
+ end
+
+ def has_embed_type?(embed_type)
+ within_element(:embed_type) do
+ has_text?(embed_type)
+ end
+ end
+
+ def has_visibility_type?(visibility_type)
+ within_element(:snippet_box) do
+ has_text?(visibility_type)
+ end
+ end
+
+ def has_file_name?(file_name)
+ within_element(:file_title_name) do
+ has_text?(file_name)
+ end
+ end
+
+ def has_file_content?(file_content)
+ finished_loading?
+ within_element(:file_content) do
+ has_text?(file_content)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb
index 9944a39ce07..7a01320901d 100644
--- a/qa/qa/page/element.rb
+++ b/qa/qa/page/element.rb
@@ -1,26 +1,41 @@
+# frozen_string_literal: true
+
+require 'active_support/core_ext/array/extract_options'
+
module QA
module Page
class Element
- attr_reader :name
+ attr_reader :name, :attributes
- def initialize(name, pattern = nil)
+ def initialize(name, *options)
@name = name
- @pattern = pattern || selector
+ @attributes = options.extract_options!
+ @attributes[:pattern] ||= selector
+
+ options.each do |option|
+ if option.is_a?(String) || option.is_a?(Regexp)
+ @attributes[:pattern] = option
+ end
+ end
end
def selector
"qa-#{@name.to_s.tr('_', '-')}"
end
+ def required?
+ !!@attributes[:required]
+ end
+
def selector_css
".#{selector}"
end
def expression
- if @pattern.is_a?(String)
- @_regexp ||= Regexp.new(Regexp.escape(@pattern))
+ if @attributes[:pattern].is_a?(String)
+ @_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern]))
else
- @pattern
+ @attributes[:pattern]
end
end
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index a1534231691..e42de7d65c5 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module File
@@ -36,6 +38,8 @@ module QA
def commit_changes
click_on 'Commit changes'
+
+ finished_loading?
end
def select_template(template_type, template)
diff --git a/qa/qa/page/file/shared/commit_message.rb b/qa/qa/page/file/shared/commit_message.rb
index aa1bb081cd3..ce3b1e9939c 100644
--- a/qa/qa/page/file/shared/commit_message.rb
+++ b/qa/qa/page/file/shared/commit_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module File
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index abd8ebb089f..eaf88c6e69e 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module File
@@ -6,7 +8,7 @@ module QA
view 'app/helpers/blob_helper.rb' do
element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern
- element :delete_button, /label:\s+"Delete"/ # rubocop:disable QA/ElementWithPattern
+ element :delete_button, '_("Delete")' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/blob/_remove.html.haml' do
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index 39584daf334..9e37d14fbf3 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Group
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 9d6bd338027..d4c4be0d6ca 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -18,7 +18,7 @@ module QA
element :no_result_text, 'No groups or projects matched your search' # rubocop:disable QA/ElementWithPattern
end
- def go_to_subgroup(name)
+ def click_subgroup(name)
click_link name
end
@@ -45,7 +45,7 @@ module QA
private
def select_kind(kind)
- retry_on_exception(sleep_interval: 1.0) do
+ QA::Support::Retrier.retry_on_exception(sleep_interval: 1.0) do
within_element(:new_project_or_subgroup_dropdown) do
# May need to click again because it is possible to click the button quicker than the JS is bound
wait(reload: false) do
diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb
index d3751b712c9..9bb1c702576 100644
--- a/qa/qa/page/issuable/sidebar.rb
+++ b/qa/qa/page/issuable/sidebar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Issuable
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index de0cfa9f293..e73d40b37ac 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -18,7 +18,7 @@ module QA
element :label_svg
end
- def go_to_new_label
+ def click_new_label_button
# The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit
# This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?)
# before clicking the button.
diff --git a/qa/qa/page/label/new.rb b/qa/qa/page/label/new.rb
index b5422dc9400..a40179489c1 100644
--- a/qa/qa/page/label/new.rb
+++ b/qa/qa/page/label/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Label
@@ -9,7 +11,7 @@ module QA
element :label_create_button
end
- def create_label
+ def click_label_create_button
click_element :label_create_button
end
diff --git a/qa/qa/page/layout/banner.rb b/qa/qa/page/layout/banner.rb
index 2223f2adec8..aebeae97090 100644
--- a/qa/qa/page/layout/banner.rb
+++ b/qa/qa/page/layout/banner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Layout
diff --git a/qa/qa/page/layout/performance_bar.rb b/qa/qa/page/layout/performance_bar.rb
new file mode 100644
index 00000000000..0cfef6a5705
--- /dev/null
+++ b/qa/qa/page/layout/performance_bar.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Layout
+ class PerformanceBar < Page::Base
+ view 'app/assets/javascripts/performance_bar/components/performance_bar_app.vue' do
+ element :performance_bar
+ end
+
+ view 'app/assets/javascripts/performance_bar/components/detailed_metric.vue' do
+ element :performance_bar_detailed_metric
+ end
+
+ view 'app/assets/javascripts/performance_bar/components/request_selector.vue' do
+ element :performance_bar_request
+ end
+
+ def has_performance_bar?
+ has_element?(:performance_bar)
+ end
+
+ def has_detailed_metrics?
+ all_elements(:performance_bar_detailed_metric).all? do |metric|
+ metric.has_text?(%r{\d+ms / \d+})
+ end
+ end
+
+ def has_request_for?(path)
+ has_element?(:performance_bar_request, text: path)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index e03fe9ab83a..8970eeb6678 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Main
@@ -37,19 +39,7 @@ module QA
end
view 'app/views/layouts/devise.html.haml' do
- element :login_page
- end
-
- def assert_page_loaded
- unless page_loaded?
- raise QA::Runtime::Browser::NotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
- end
- end
-
- def page_loaded?
- wait(max: 60) do
- has_element?(:login_page)
- end
+ element :login_page, required: true
end
def sign_in_using_credentials(user = nil)
@@ -157,7 +147,7 @@ module QA
fill_element :login_field, user.username
fill_element :password_field, user.password
- click_element :sign_in_button
+ click_element :sign_in_button, Page::Main::Menu
end
def set_initial_password_if_present
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 55500e831c6..5eb24d2d2ba 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -10,15 +10,16 @@ module QA
end
view 'app/views/layouts/header/_default.html.haml' do
- element :navbar
- element :user_avatar
+ element :navbar, required: true
+ element :user_avatar, required: true
element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
element :admin_area_link
- element :projects_dropdown
- element :groups_dropdown
+ element :projects_dropdown, required: true
+ element :groups_dropdown, required: true
+ element :snippets_link
end
view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do
@@ -46,7 +47,7 @@ module QA
end
end
- def go_to_admin_area
+ def click_admin_area
within_top_menu { click_element :admin_area_link }
end
@@ -56,7 +57,7 @@ module QA
end
end
- def go_to_profile_settings
+ def click_settings_link
retry_until(reload: false) do
within_user_menu do
click_link 'Settings'
@@ -66,6 +67,10 @@ module QA
end
end
+ def click_snippets_link
+ click_element :snippets_link
+ end
+
def has_personal_area?(wait: Capybara.default_max_wait_time)
has_element?(:user_avatar, wait: wait)
end
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index bc44d274314..5f6ddb9a114 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Main
diff --git a/qa/qa/page/mattermost/login.rb b/qa/qa/page/mattermost/login.rb
index 9b21300ea3c..38a88b42992 100644
--- a/qa/qa/page/mattermost/login.rb
+++ b/qa/qa/page/mattermost/login.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Mattermost
diff --git a/qa/qa/page/mattermost/main.rb b/qa/qa/page/mattermost/main.rb
index bc2f9acc729..e531ace8529 100644
--- a/qa/qa/page/mattermost/main.rb
+++ b/qa/qa/page/mattermost/main.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Mattermost
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index 20d9c336367..67459208c8d 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module MergeRequest
@@ -26,7 +28,7 @@ module QA
element :issuable_label
end
- view 'app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml' do
+ view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do
element :assign_to_me_link
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 976e431186d..5aef868a805 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -27,6 +27,12 @@ module QA
element :squash_checkbox
end
+ view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do
+ element :dropdown_toggle
+ element :download_email_patches
+ element :download_plain_diff
+ end
+
view 'app/views/projects/merge_requests/show.html.haml' do
element :notes_tab
element :diffs_tab
@@ -139,11 +145,11 @@ module QA
click_element :squash_checkbox
end
- def go_to_discussions_tab
+ def click_discussions_tab
click_element :notes_tab
end
- def go_to_diffs_tab
+ def click_diffs_tab
click_element :diffs_tab
end
@@ -159,6 +165,16 @@ module QA
def edit!
click_element :edit_button
end
+
+ def view_email_patches
+ click_element :dropdown_toggle
+ visit_link_in_element(:download_email_patches)
+ end
+
+ def view_plain_diff
+ click_element :dropdown_toggle
+ visit_link_in_element(:download_plain_diff)
+ end
end
end
end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 8c12eff5cf1..6108fb4d93b 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Profile
@@ -26,7 +28,7 @@ module QA
check_element(:api_radio)
end
- def create_token
+ def click_create_token_button
click_element(:create_token_button)
end
diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb
index 56fbaa90790..bbefb29f78e 100644
--- a/qa/qa/page/project/activity.rb
+++ b/qa/qa/page/project/activity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -6,7 +8,7 @@ module QA
element :push_events, "event_filter_link EventFilter::PUSH, _('Push events')" # rubocop:disable QA/ElementWithPattern
end
- def go_to_push_events
+ def click_push_events
click_on 'Push events'
end
end
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
index 922a6ddb086..480fc7d78cb 100644
--- a/qa/qa/page/project/branches/show.rb
+++ b/qa/qa/page/project/branches/show.rb
@@ -7,6 +7,7 @@ module QA
class Show < Page::Base
view 'app/views/projects/branches/_branch.html.haml' do
element :remove_btn
+ element :branch_name
end
view 'app/views/projects/branches/_panel.html.haml' do
element :all_branches
@@ -27,10 +28,10 @@ module QA
finished_loading?
end
- def has_branch_title?(branch_title)
- within_element(:all_branches) do
- within(".item-title") do
- has_text?(branch_title)
+ def has_no_branch?(branch_name, reload: false)
+ wait(reload: reload) do
+ within_element(:all_branches) do
+ has_no_element?(:branch_name, text: branch_name)
end
end
end
@@ -48,15 +49,6 @@ module QA
click_element(:delete_merged_branches)
end
end
-
- def wait_for_texts_not_to_be_visible(texts)
- text_not_visible = wait do
- texts.all? do |text|
- has_no_text?(text)
- end
- end
- raise "Expected text(s) #{texts} not to be visible" unless text_not_visible
- end
end
end
end
diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb
index 140c004b458..1a52c61551d 100644
--- a/qa/qa/page/project/fork/new.rb
+++ b/qa/qa/page/project/fork/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index 488157d9878..5973a5a958e 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -29,12 +31,19 @@ module QA
choose_test_namespace(full_path)
set_path(full_path, name)
import_project(full_path)
+ wait_for_success
end
private
def within_repo_path(full_path)
- page.within(%Q(tr[data-qa-repo-path="#{full_path}"])) do
+ wait(reload: false) do
+ has_element?(:project_import_row, text: full_path)
+ end
+
+ project_import_row = find_element(:project_import_row, text: full_path)
+
+ within(project_import_row) do
yield
end
end
@@ -44,18 +53,24 @@ module QA
click_element :project_namespace_select
end
- select_item(Runtime::Namespace.path)
+ search_and_select(Runtime::Namespace.path)
end
def set_path(full_path, name)
within_repo_path(full_path) do
- fill_in 'path', with: name
+ fill_element(:project_path_field, name)
end
end
def import_project(full_path)
within_repo_path(full_path) do
- click_button 'Import'
+ click_element(:import_button)
+ end
+ end
+
+ def wait_for_success
+ wait(max: 60, interval: 1.0, reload: false) do
+ page.has_content?('Done', wait: 1.0)
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index 1035bf74a43..c4383951ec4 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -7,7 +9,7 @@ module QA
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
- def go_to_issue(title)
+ def click_issue_link(title)
click_link(title)
end
end
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 03b605ab24b..0d138417176 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 1c25be5fd0c..9df3db1bba0 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -23,6 +23,10 @@ module QA
element :filter_options
end
+ view 'app/assets/javascripts/notes/components/noteable_note.vue' do
+ element :noteable_note_item
+ end
+
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil)
@@ -36,6 +40,12 @@ module QA
click_element :comment_button
end
+ def has_comment?(comment_text)
+ wait(reload: false) do
+ has_element?(:noteable_note_item, text: comment_text)
+ end
+ end
+
def select_comments_only_filter
select_filter_with_text('Show comments only')
end
@@ -54,7 +64,7 @@ module QA
retry_on_exception do
click_body
click_element :discussion_filter
- find_element(:filter_options, text).click
+ find_element(:filter_options, text: text).click
end
end
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 9c218f4ed8b..5853f487f0b 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA::Page
module Project::Job
class Show < QA::Page::Base
@@ -31,7 +33,9 @@ module QA::Page
private
def loaded?(wait: 60)
- has_element?(:build_trace, wait: wait)
+ wait(reload: true, max: wait, interval: 1) do
+ has_element?(:build_trace, wait: 1)
+ end
end
def completed?(timeout: 60)
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 46dfe87fe25..3fe048f752a 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -30,7 +30,7 @@ module QA
end
end
- def go_to_activity
+ def click_activity
within_sidebar do
click_element(:activity_link)
end
diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb
index a1519c9ef1c..6895c44f72f 100644
--- a/qa/qa/page/project/milestone/index.rb
+++ b/qa/qa/page/project/milestone/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb
index 992ef89004b..751fb141684 100644
--- a/qa/qa/page/project/milestone/new.rb
+++ b/qa/qa/page/project/milestone/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -17,7 +19,7 @@ module QA
fill_element :milestone_description, description
end
- def create_new_milestone
+ def click_milestone_create_button
click_element :milestone_create_button
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 9f1867ef8a5..defd85a5740 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -10,6 +12,7 @@ module QA
end
view 'app/views/projects/_new_project_fields.html.haml' do
+ element :initialize_with_readme_checkbox
element :project_namespace_select
element :project_namespace_field, 'namespaces_options' # rubocop:disable QA/ElementWithPattern
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
@@ -24,12 +27,18 @@ module QA
end
def choose_test_namespace
- click_element :project_namespace_select
+ choose_namespace(Runtime::Namespace.path)
+ end
- search_and_select(Runtime::Namespace.path)
+ def choose_namespace(namespace)
+ retry_on_exception do
+ click_body
+ click_element :project_namespace_select
+ search_and_select(namespace)
+ end
end
- def go_to_import_project
+ def click_import_project
click_on 'Import project'
end
@@ -45,7 +54,7 @@ module QA
click_on 'Create project'
end
- def go_to_create_from_template
+ def click_create_from_template_tab
click_element(:project_create_from_template_tab)
end
@@ -53,9 +62,13 @@ module QA
choose visibility
end
- def go_to_github_import
+ def click_github_link
click_link 'GitHub'
end
+
+ def enable_initialize_with_readme
+ check_element :initialize_with_readme_checkbox
+ end
end
end
end
diff --git a/qa/qa/page/project/operations/environments/index.rb b/qa/qa/page/project/operations/environments/index.rb
index 63965a57edd..610a34385b1 100644
--- a/qa/qa/page/project/operations/environments/index.rb
+++ b/qa/qa/page/project/operations/environments/index.rb
@@ -10,7 +10,7 @@ module QA
element :environment_link
end
- def go_to_environment(environment_name)
+ def click_environment_link(environment_name)
wait(reload: false) do
find(element_selector_css(:environment_link), text: environment_name).click
end
diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb
index 939f912ea85..785791652ba 100644
--- a/qa/qa/page/project/operations/kubernetes/add.rb
+++ b/qa/qa/page/project/operations/kubernetes/add.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index ffd5b36e1ae..9f47841366e 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -6,7 +8,7 @@ module QA
class AddExisting < Page::Base
view 'app/views/clusters/clusters/user/_form.html.haml' do
element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern
+ element :api_url, 'url_field :api_url' # rubocop:disable QA/ElementWithPattern
element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern
element :token, 'text_field :token' # rubocop:disable QA/ElementWithPattern
element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 67a74af1cd2..de54319596d 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index d4e1679b6bf..c81e13e9b91 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -6,12 +8,12 @@ module QA
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/application_row.vue' do
element :application_row, 'js-cluster-application-row-${this.id}' # rubocop:disable QA/ElementWithPattern
- element :install_button, "s__('ClusterIntegration|Install')" # rubocop:disable QA/ElementWithPattern
- element :installed_button, "s__('ClusterIntegration|Installed')" # rubocop:disable QA/ElementWithPattern
+ element :install_button, "__('Install')" # rubocop:disable QA/ElementWithPattern
+ element :installed_button, "__('Installed')" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/clusters/components/applications.vue' do
- element :ingress_ip_address, 'id="ingress-ip-address"' # rubocop:disable QA/ElementWithPattern
+ element :ingress_ip_address, 'id="ingress-endpoint"' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/clusters/clusters/_form.html.haml' do
@@ -35,7 +37,7 @@ module QA
def ingress_ip
# We need to wait longer since it can take some time before the
# ip address is assigned for the ingress controller
- page.find('#ingress-ip-address', wait: 1200).value
+ page.find('#ingress-endpoint', wait: 1200).value
end
def set_domain(domain)
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 19d83ecc4f4..68850d989b1 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA::Page
module Project::Pipeline
class Index < QA::Page::Base
@@ -5,7 +7,7 @@ module QA::Page
element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern
end
- def go_to_latest_pipeline
+ def click_on_latest_pipeline
css = '.js-pipeline-url-link'
link = wait(reload: false) do
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 22b0a168964..284d0957eb8 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA::Page
module Project::Pipeline
class Show < QA::Page::Base
@@ -42,11 +44,11 @@ module QA::Page
end
end
- def go_to_job(job_name)
- find_element(:job_link, job_name).click
+ def click_job(job_name)
+ find_element(:job_link, text: job_name).click
end
- def go_to_first_job
+ def click_on_first_job
css = '.js-pipeline-graph-job-link'
wait(reload: false) do
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 578f097e2dc..75530832860 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -1,30 +1,26 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
module Settings
class Advanced < Page::Base
view 'app/views/projects/edit.html.haml' do
- element :project_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
- element :project_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :rename_project_button, "submit 'Rename project'" # rubocop:disable QA/ElementWithPattern
+ element :project_path_field
+ element :change_path_button
end
- def rename_to(path)
- fill_project_name(path)
+ def update_project_path_to(path)
fill_project_path(path)
- rename_project!
+ click_change_path_button
end
def fill_project_path(path)
- fill_in :project_path, with: path
- end
-
- def fill_project_name(name)
- fill_in :project_name, with: name
+ fill_element :project_path_field, path
end
- def rename_project!
- click_on 'Rename project'
+ def click_change_path_button
+ click_element :change_path_button
end
end
end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 2de39b8ebf5..b8c5c563da6 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -1,4 +1,6 @@
-module QA # rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+module QA
module Page
module Project
module Settings
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index e7a6e4bf628..3621e618bf2 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -9,6 +11,7 @@ module QA
element :variable_row, '.ci-variable-row-body' # rubocop:disable QA/ElementWithPattern
element :variable_key, '.qa-ci-variable-input-key' # rubocop:disable QA/ElementWithPattern
element :variable_value, '.qa-ci-variable-input-value' # rubocop:disable QA/ElementWithPattern
+ element :variable_masked
end
view 'app/views/ci/variables/_index.html.haml' do
@@ -16,7 +19,7 @@ module QA
element :reveal_values, '.js-secret-value-reveal-button' # rubocop:disable QA/ElementWithPattern
end
- def fill_variable(key, value)
+ def fill_variable(key, value, masked)
keys = all_elements(:ci_variable_input_key)
index = keys.size - 1
@@ -30,6 +33,9 @@ module QA
# The code was inspired from:
# https://github.com/teamcapybara/capybara/blob/679548cea10773d45e32808f4d964377cfe5e892/lib/capybara/selenium/node.rb#L217
execute_script("arguments[0].value = #{value.to_json}", node)
+
+ masked_node = all_elements(:variable_masked)[index]
+ toggle_masked(masked_node, masked)
end
def save_variables
@@ -45,6 +51,24 @@ module QA
find('.qa-ci-variable-input-value').value
end
end
+
+ private
+
+ def toggle_masked(masked_node, masked)
+ wait(reload: false) do
+ masked_node.click
+
+ masked ? masked_enabled?(masked_node) : masked_disabled?(masked_node)
+ end
+ end
+
+ def masked_enabled?(masked_node)
+ masked_node[:class].include?('is-checked')
+ end
+
+ def masked_disabled?(masked_node)
+ !masked_enabled?(masked_node)
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index f3b217677f2..f5f22623060 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -1,17 +1,11 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
module Settings
module Common
include QA::Page::Settings::Common
-
- def self.included(base)
- base.class_eval do
- view 'app/views/projects/edit.html.haml' do
- element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'" # rubocop:disable QA/ElementWithPattern
- end
- end
- end
end
end
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 5da8d352e74..b8d961274a9 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -33,14 +35,14 @@ module QA
def find_fingerprint(title)
within_project_deploy_keys do
- find_element(:key, title)
+ find_element(:key, text: title)
.find(element_selector_css(:key_fingerprint)).text
end
end
def has_key?(title, fingerprint)
within_project_deploy_keys do
- find_element(:key, title)
+ find_element(:key, text: title)
.has_css?(element_selector_css(:key_fingerprint), text: fingerprint)
end
end
diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb
index 2d42372cbc5..ad34ebc13c2 100644
--- a/qa/qa/page/project/settings/deploy_tokens.rb
+++ b/qa/qa/page/project/settings/deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index d8cf1d49dd2..d1f3b15f950 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -9,6 +11,24 @@ module QA
element :advanced_settings
end
+ view 'app/views/projects/settings/_general.html.haml' do
+ element :project_name_field
+ element :save_naming_topics_avatar_button
+ end
+
+ def rename_project_to(name)
+ fill_project_name(name)
+ click_save_changes
+ end
+
+ def fill_project_name(name)
+ fill_element :project_name_field, name
+ end
+
+ def click_save_changes
+ click_element :save_naming_topics_avatar_button
+ end
+
def expand_advanced_settings(&block)
expand_section(:advanced_settings) do
Advanced.perform(&block)
diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb
index d044d3715a9..f92528c4262 100644
--- a/qa/qa/page/project/settings/merge_request.rb
+++ b/qa/qa/page/project/settings/merge_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 76591a4e3fe..903b0979614 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -32,22 +34,22 @@ module QA
end
def allow_no_one_to_push
- click_allow(:push, 'No one')
+ go_to_allow(:push, 'No one')
end
def allow_devs_and_maintainers_to_push
- click_allow(:push, 'Developers + Maintainers')
+ go_to_allow(:push, 'Developers + Maintainers')
end
# @deprecated
alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push
def allow_no_one_to_merge
- click_allow(:merge, 'No one')
+ go_to_allow(:merge, 'No one')
end
def allow_devs_and_maintainers_to_merge
- click_allow(:merge, 'Developers + Maintainers')
+ go_to_allow(:merge, 'Developers + Maintainers')
end
# @deprecated
@@ -59,7 +61,7 @@ module QA
private
- def click_allow(action, text)
+ def go_to_allow(action, text)
click_element :"allowed_to_#{action}_select"
within_element(:"allowed_to_#{action}_dropdown") do
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index ac0b87aca5e..437a945aceb 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb
index ac930f5385a..adc92b4acaf 100644
--- a/qa/qa/page/project/settings/runners.rb
+++ b/qa/qa/page/project/settings/runners.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 9c21d9ddbfa..1a9a2fd413f 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -76,13 +76,13 @@ module QA
click_on 'Fork'
end
- def go_to_file(filename)
+ def click_file(filename)
within_element(:file_tree) do
click_on filename
end
end
- def go_to_commit(commit_msg)
+ def click_commit(commit_msg)
within_element(:file_tree) do
click_on commit_msg
end
diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb
index cf9fc453565..24a99a9464c 100644
--- a/qa/qa/page/project/sub_menus/operations.rb
+++ b/qa/qa/page/project/sub_menus/operations.rb
@@ -14,7 +14,7 @@ module QA
end
end
- def click_operations_environments
+ def go_to_operations_environments
hover_operations do
within_submenu do
click_element(:operations_environments_link)
@@ -22,7 +22,7 @@ module QA
end
end
- def click_operations_kubernetes
+ def go_to_operations_kubernetes
hover_operations do
within_submenu do
click_link('Kubernetes')
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
index 29eaa9a74de..4cc73a6b25a 100644
--- a/qa/qa/page/project/sub_menus/repository.rb
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -20,7 +20,7 @@ module QA
end
end
- def click_repository_branches
+ def go_to_repository_branches
hover_repository do
within_submenu do
click_element(:branches_link)
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index 62c594c0210..22743ebd0a1 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -14,7 +14,7 @@ module QA
end
end
- def click_ci_cd_settings
+ def go_to_ci_cd_settings
hover_settings do
within_submenu do
click_link('CI / CD')
@@ -22,7 +22,7 @@ module QA
end
end
- def click_members_settings
+ def go_to_members_settings
hover_settings do
within_submenu do
click_element :link_members_settings
@@ -30,7 +30,7 @@ module QA
end
end
- def click_repository_settings
+ def go_to_repository_settings
hover_settings do
within_submenu do
click_link('Repository')
@@ -38,7 +38,7 @@ module QA
end
end
- def go_to_settings
+ def click_settings
within_sidebar do
click_on 'Settings'
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 2b6c01888d5..b5a36862389 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -7,6 +7,10 @@ module QA
class Edit < Page::Base
include Page::Component::DropdownFilter
+ view 'app/assets/javascripts/ide/components/activity_bar.vue' do
+ element :commit_mode_tab
+ end
+
view 'app/assets/javascripts/ide/components/ide_tree.vue' do
element :new_file
end
@@ -17,6 +21,7 @@ module QA
view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
element :full_file_path
+ element :new_file_modal
element :template_list
end
@@ -42,14 +47,19 @@ module QA
def create_new_file_from_template(file_name, template)
click_element :new_file
+
+ # Wait for the modal animation to complete before clicking on the file name
+ wait_for_animated_element(:new_file_modal)
+
within_element(:template_list) do
- begin
- click_on file_name
- rescue Capybara::ElementNotFound
- raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
- end
+ click_on file_name
+ rescue Capybara::ElementNotFound
+ raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
end
+ # Wait for the modal to fade out too
+ has_no_element?(:new_file_modal)
+
wait(reload: false) do
within_element(:file_templates_bar) do
click_element :file_template_dropdown
@@ -65,10 +75,16 @@ module QA
end
def commit_changes
+ # Clicking :begin_commit_button the first time switches from the
+ # edit to the commit view
+ click_element :begin_commit_button
+ active_element? :commit_mode_tab
+
+ # We need to click :begin_commit_button again
click_element :begin_commit_button
- # After clicking :begin_commit_button there is an animation that
- # hides :begin_commit_button and shows :commit_button
+ # After clicking :begin_commit_button the 2nd time there is an
+ # animation that hides :begin_commit_button and shows :commit_button
#
# Wait for the animation to complete before clicking :commit_button
# otherwise the click will quietly do nothing.
@@ -77,7 +93,10 @@ module QA
has_element?(:commit_button)
end
- # Retry the attempt to click :commit_button just in case part of the
+ # At this point we're ready to commit and the button should be
+ # labelled "Stage & Commit"
+ #
+ # Click :commit_button and keep retrying just in case part of the
# animation is still in process even when the buttons have the
# expected visibility.
commit_success_msg_shown = retry_until do
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index 8d0eafa1818..f6edc28c41a 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -9,15 +11,7 @@ module QA
element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
end
- def go_to_new_page
- click_on 'New page'
- end
-
- def got_to_view_history_page
- click_on 'Page history'
- end
-
- def go_to_edit_page
+ def click_edit
click_on 'Edit'
end
end
diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb
index b90e03be36a..792eba4bab7 100644
--- a/qa/qa/page/project/wiki/new.rb
+++ b/qa/qa/page/project/wiki/new.rb
@@ -23,7 +23,7 @@ module QA
element :svg_content
end
- def go_to_create_first_page
+ def click_create_your_first_page_button
# The svg takes a fraction of a second to load after which the
# "Create your first page" button shifts up a bit. This can cause
# webdriver to miss the hit so we wait for the svg to load before
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index dffbc5d60a2..f79ad510084 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -11,7 +11,7 @@ module QA
element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
end
- def go_to_clone_repository
+ def click_clone_repository
click_on 'Clone repository'
end
end
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index 9fea74eabc9..8cd0b6bb49c 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Settings
diff --git a/qa/qa/page/validatable.rb b/qa/qa/page/validatable.rb
new file mode 100644
index 00000000000..8467d261285
--- /dev/null
+++ b/qa/qa/page/validatable.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Validatable
+ PageValidationError = Class.new(StandardError)
+
+ def validate_elements_present!
+ base_page = self.new
+
+ elements.each do |element|
+ next unless element.required?
+
+ # TODO: this wait needs to be replaced by the wait class
+ unless base_page.has_element?(element.name, wait: 60)
+ raise Validatable::PageValidationError, "#{element.name} did not appear on #{self.name} as expected"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb
index 117d8d4db67..edd12665f1e 100644
--- a/qa/qa/page/validator.rb
+++ b/qa/qa/page/validator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
class Validator
diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb
index c59fad2e223..613059b2d32 100644
--- a/qa/qa/page/view.rb
+++ b/qa/qa/page/view.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'pathname'
module QA
@@ -48,8 +50,8 @@ module QA
@elements = []
end
- def element(name, pattern = nil)
- @elements.push(Page::Element.new(name, pattern))
+ def element(name, *args)
+ @elements.push(Page::Element.new(name, *args))
end
end
end
diff --git a/qa/qa/resource/README.md b/qa/qa/resource/README.md
deleted file mode 100644
index 4cdeb3f42a2..00000000000
--- a/qa/qa/resource/README.md
+++ /dev/null
@@ -1,392 +0,0 @@
-# Resource class in GitLab QA
-
-Resources are primarily created using Browser UI steps, but can also
-be created via the API.
-
-## How to properly implement a resource class?
-
-All resource classes should inherit from [`Resource::Base`](./base.rb).
-
-There is only one mandatory method to implement to define a resource class.
-This is the `#fabricate!` method, which is used to build the resource via the
-browser UI. Note that you should only use [Page objects](../page/README.md) to
-interact with a Web page in this method.
-
-Here is an imaginary example:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- def fabricate!
- Page::Dashboard::Index.perform do |dashboard_index|
- dashboard_index.go_to_new_shirt
- end
-
- Page::Shirt::New.perform do |shirt_new|
- shirt_new.set_name(name)
- shirt_new.create_shirt!
- end
- end
- end
- end
-end
-```
-
-### Define API implementation
-
-A resource class may also implement the three following methods to be able to
-create the resource via the public GitLab API:
-
-- `#api_get_path`: The `GET` path to fetch an existing resource.
-- `#api_post_path`: The `POST` path to create a new resource.
-- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource.
-
-Let's take the `Shirt` resource class, and add these three API methods:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- def fabricate!
- # ... same as before
- end
-
- def api_get_path
- "/shirt/#{name}"
- end
-
- def api_post_path
- "/shirts"
- end
-
- def api_post_body
- {
- name: name
- }
- end
- end
- end
-end
-```
-
-The [`Project` resource](./project.rb) is a good real example of Browser
-UI and API implementations.
-
-#### Resource attributes
-
-A resource may need another resource to exist first. For instance, a project
-needs a group to be created in.
-
-To define a resource attribute, you can use the `attribute` method with a
-block using the other resource class to fabricate the resource.
-
-That will allow access to the other resource from your resource object's
-methods. You would usually use it in `#fabricate!`, `#api_get_path`,
-`#api_post_path`, `#api_post_body`.
-
-Let's take the `Shirt` resource class, and add a `project` attribute to it:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- Project.fabricate! do |resource|
- resource.name = 'project-to-create-a-shirt'
- end
- end
-
- def fabricate!
- project.visit!
-
- Page::Project::Show.perform do |project_show|
- project_show.go_to_new_shirt
- end
-
- Page::Shirt::New.perform do |shirt_new|
- shirt_new.set_name(name)
- shirt_new.create_shirt!
- end
- end
-
- def api_get_path
- "/project/#{project.path}/shirt/#{name}"
- end
-
- def api_post_path
- "/project/#{project.path}/shirts"
- end
-
- def api_post_body
- {
- name: name
- }
- end
- end
- end
-end
-```
-
-**Note that all the attributes are lazily constructed. This means if you want
-a specific attribute to be fabricated first, you'll need to call the
-attribute method first even if you're not using it.**
-
-#### Product data attributes
-
-Once created, you may want to populate a resource with attributes that can be
-found in the Web page, or in the API response.
-For instance, once you create a project, you may want to store its repository
-SSH URL as an attribute.
-
-Again we could use the `attribute` method with a block, using a page object
-to retrieve the data on the page.
-
-Let's take the `Shirt` resource class, and define a `:brand` attribute:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- Project.fabricate! do |resource|
- resource.name = 'project-to-create-a-shirt'
- end
- end
-
- # Attribute populated from the Browser UI (using the block)
- attribute :brand do
- Page::Shirt::Show.perform do |shirt_show|
- shirt_show.fetch_brand_from_page
- end
- end
-
- # ... same as before
- end
- end
-end
-```
-
-**Note again that all the attributes are lazily constructed. This means if
-you call `shirt.brand` after moving to the other page, it'll not properly
-retrieve the data because we're no longer on the expected page.**
-
-Consider this:
-
-```ruby
-shirt =
- QA::Resource::Shirt.fabricate! do |resource|
- resource.name = "GitLab QA"
- end
-
-shirt.project.visit!
-
-shirt.brand # => FAIL!
-```
-
-The above example will fail because now we're on the project page, trying to
-construct the brand data from the shirt page, however we moved to the project
-page already. There are two ways to solve this, one is that we could try to
-retrieve the brand before visiting the project again:
-
-```ruby
-shirt =
- QA::Resource::Shirt.fabricate! do |resource|
- resource.name = "GitLab QA"
- end
-
-shirt.brand # => OK!
-
-shirt.project.visit!
-
-shirt.brand # => OK!
-```
-
-The attribute will be stored in the instance therefore all the following calls
-will be fine, using the data previously constructed. If we think that this
-might be too brittle, we could eagerly construct the data right before
-ending fabrication:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- # ... same as before
-
- def fabricate!
- project.visit!
-
- Page::Project::Show.perform do |project_show|
- project_show.go_to_new_shirt
- end
-
- Page::Shirt::New.perform do |shirt_new|
- shirt_new.set_name(name)
- shirt_new.create_shirt!
- end
-
- populate(:brand) # Eagerly construct the data
- end
- end
- end
-end
-```
-
-The `populate` method will iterate through its arguments and call each
-attribute respectively. Here `populate(:brand)` has the same effect as
-just `brand`. Using the populate method makes the intention clearer.
-
-With this, it will make sure we construct the data right after we create the
-shirt. The drawback is that this will always construct the data when the
-resource is fabricated even if we don't need to use the data.
-
-Alternatively, we could just make sure we're on the right page before
-constructing the brand data:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- attr_accessor :name
-
- attribute :project do
- Project.fabricate! do |resource|
- resource.name = 'project-to-create-a-shirt'
- end
- end
-
- # Attribute populated from the Browser UI (using the block)
- attribute :brand do
- back_url = current_url
- visit!
-
- Page::Shirt::Show.perform do |shirt_show|
- shirt_show.fetch_brand_from_page
- end
-
- visit(back_url)
- end
-
- # ... same as before
- end
- end
-end
-```
-
-This will make sure it's on the shirt page before constructing brand, and
-move back to the previous page to avoid breaking the state.
-
-#### Define an attribute based on an API response
-
-Sometimes, you want to define a resource attribute based on the API response
-from its `GET` or `POST` request. For instance, if the creation of a shirt via
-the API returns
-
-```ruby
-{
- brand: 'a-brand-new-brand',
- style: 't-shirt',
- materials: [[:cotton, 80], [:polyamide, 20]]
-}
-```
-
-you may want to store `style` as-is in the resource, and fetch the first value
-of the first `materials` item in a `main_fabric` attribute.
-
-Let's take the `Shirt` resource class, and define a `:style` and a
-`:main_fabric` attributes:
-
-```ruby
-module QA
- module Resource
- class Shirt < Base
- # ... same as before
-
- # @style from the instance if present,
- # or fetched from the API response if present,
- # or a QA::Resource::Base::NoValueError is raised otherwise
- attribute :style
-
- # If @main_fabric is not present,
- # and if the API does not contain this field, this block will be
- # used to construct the value based on the API response, and
- # store the result in @main_fabric
- attribute :main_fabric do
- api_response.&dig(:materials, 0, 0)
- end
-
- # ... same as before
- end
- end
-end
-```
-
-**Notes on attributes precedence:**
-
-- resource instance variables have the highest precedence
-- attributes from the API response take precedence over attributes from the
- block (usually from Browser UI)
-- attributes without a value will raise a `QA::Resource::Base::NoValueError` error
-
-## Creating resources in your tests
-
-To create a resource in your tests, you can call the `.fabricate!` method on
-the resource class.
-Note that if the resource class supports API fabrication, this will use this
-fabrication by default.
-
-Here is an example that will use the API fabrication method under the hood
-since it's supported by the `Shirt` resource class:
-
-```ruby
-my_shirt = Resource::Shirt.fabricate! do |shirt|
- shirt.name = 'my-shirt'
-end
-
-expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
-expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
-expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response
-expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
-```
-
-If you explicitly want to use the Browser UI fabrication method, you can call
-the `.fabricate_via_browser_ui!` method instead:
-
-```ruby
-my_shirt = Resource::Shirt.fabricate_via_browser_ui! do |shirt|
- shirt.name = 'my-shirt'
-end
-
-expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
-expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block
-expect(page).to have_text(my_shirt.style) # => QA::Resource::Base::NoValueError will be raised because no API response nor a block is provided
-expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
-```
-
-You can also explicitly use the API fabrication method, by calling the
-`.fabricate_via_api!` method:
-
-```ruby
-my_shirt = Resource::Shirt.fabricate_via_api! do |shirt|
- shirt.name = 'my-shirt'
-end
-```
-
-In this case, the result will be similar to calling
-`Resource::Shirt.fabricate!`.
-
-## Where to ask for help?
-
-If you need more information, ask for help on `#quality` channel on Slack
-(internal, GitLab Team only).
-
-If you are not a Team Member, and you still need help to contribute, please
-open an issue in GitLab CE issue tracker with the `~QA` label.
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 98eebac0880..de04467ff5b 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -8,9 +8,6 @@ module QA
module ApiFabricator
include Capybara::DSL
- HTTP_STATUS_OK = 200
- HTTP_STATUS_CREATED = 201
-
ResourceNotFoundError = Class.new(RuntimeError)
ResourceFabricationFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 523d92c7ef3..283fc6cdbcb 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -15,6 +15,38 @@ module QA
def_delegators :evaluator, :attribute
+ def self.fabricate!(*args, &prepare_block)
+ fabricate_via_api!(*args, &prepare_block)
+ rescue NotImplementedError
+ fabricate_via_browser_ui!(*args, &prepare_block)
+ end
+
+ def self.fabricate_via_browser_ui!(*args, &prepare_block)
+ options = args.extract_options!
+ resource = options.fetch(:resource) { new }
+ parents = options.fetch(:parents) { [] }
+
+ do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
+
+ current_url
+ end
+ end
+
+ def self.fabricate_via_api!(*args, &prepare_block)
+ options = args.extract_options!
+ resource = options.fetch(:resource) { new }
+ parents = options.fetch(:parents) { [] }
+
+ raise NotImplementedError unless resource.api_support?
+
+ resource.eager_load_api_client!
+
+ do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
+ end
+ end
+
def fabricate!(*_args)
raise NotImplementedError
end
@@ -55,38 +87,6 @@ module QA
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
end
- def self.fabricate!(*args, &prepare_block)
- fabricate_via_api!(*args, &prepare_block)
- rescue NotImplementedError
- fabricate_via_browser_ui!(*args, &prepare_block)
- end
-
- def self.fabricate_via_browser_ui!(*args, &prepare_block)
- options = args.extract_options!
- resource = options.fetch(:resource) { new }
- parents = options.fetch(:parents) { [] }
-
- do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
-
- current_url
- end
- end
-
- def self.fabricate_via_api!(*args, &prepare_block)
- options = args.extract_options!
- resource = options.fetch(:resource) { new }
- parents = options.fetch(:parents) { [] }
-
- raise NotImplementedError unless resource.api_support?
-
- resource.eager_load_api_client!
-
- do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
- end
- end
-
def self.do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
diff --git a/qa/qa/resource/branch.rb b/qa/qa/resource/branch.rb
index bd52c4abe02..a45dd030625 100644
--- a/qa/qa/resource/branch.rb
+++ b/qa/qa/resource/branch.rb
@@ -46,7 +46,7 @@ module QA
# to `allow_to_push` variable.
return branch unless @protected
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb
index 0570c47d41c..b178a64b72d 100644
--- a/qa/qa/resource/ci_variable.rb
+++ b/qa/qa/resource/ci_variable.rb
@@ -3,7 +3,7 @@
module QA
module Resource
class CiVariable < Base
- attr_accessor :key, :value
+ attr_accessor :key, :value, :masked
attribute :project do
Project.fabricate! do |resource|
@@ -15,16 +15,44 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
setting.expand_ci_variables do |page|
- page.fill_variable(key, value)
+ page.fill_variable(key, value, masked)
page.save_variables
end
end
end
+
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/variables/#{key}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/variables"
+ end
+
+ def api_post_body
+ {
+ key: key,
+ value: value,
+ masked: masked
+ }
+ end
end
end
end
diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb
index 9565598efb0..869e2a71e47 100644
--- a/qa/qa/resource/deploy_key.rb
+++ b/qa/qa/resource/deploy_key.rb
@@ -23,7 +23,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
diff --git a/qa/qa/resource/deploy_token.rb b/qa/qa/resource/deploy_token.rb
index cee4422f6b4..fca5ed83c87 100644
--- a/qa/qa/resource/deploy_token.rb
+++ b/qa/qa/resource/deploy_token.rb
@@ -32,7 +32,7 @@ module QA
project.visit!
Page::Project::Menu.act do
- click_repository_settings
+ go_to_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb
index 57e82ac19ad..ca74654bf90 100644
--- a/qa/qa/resource/file.rb
+++ b/qa/qa/resource/file.rb
@@ -3,9 +3,12 @@
module QA
module Resource
class File < Base
- attr_accessor :name,
+ attr_accessor :author_email,
+ :author_name,
+ :branch,
:content,
- :commit_message
+ :commit_message,
+ :name
attribute :project do
Project.fabricate! do |resource|
@@ -31,6 +34,30 @@ module QA
page.commit_changes
end
end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "/projects/#{CGI.escape(project.path_with_namespace)}/repository/files/#{CGI.escape(@name)}"
+ end
+
+ def api_post_path
+ api_get_path
+ end
+
+ def api_post_body
+ {
+ branch: @branch || "master",
+ author_email: @author_email || Runtime::User.default_email,
+ author_name: @author_name || Runtime::User.username,
+ content: content,
+ commit_message: commit_message
+ }
+ end
end
end
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index d7f9ec6a836..0b567a474c8 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -21,7 +21,7 @@ module QA
Page::Group::Show.perform do |group_show|
if group_show.has_subgroup?(path)
- group_show.go_to_subgroup(path)
+ group_show.click_subgroup(path)
else
group_show.go_to_new_subgroup
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 93a06be6818..27ab7b60211 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -16,7 +16,7 @@ module QA
@project.visit!
Page::Project::Menu.perform(
- &:click_operations_kubernetes)
+ &:go_to_operations_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb
index c0869cb1f2a..7c899db31f3 100644
--- a/qa/qa/resource/label.rb
+++ b/qa/qa/resource/label.rb
@@ -25,13 +25,13 @@ module QA
project.visit!
Page::Project::Menu.perform(&:go_to_labels)
- Page::Label::Index.perform(&:go_to_new_label)
+ Page::Label::Index.perform(&:click_new_label_button)
Page::Label::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.fill_color(@color)
- page.create_label
+ page.click_label_create_button
end
end
end
diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb
index b8dd0a3562f..f5c632cd8d2 100644
--- a/qa/qa/resource/personal_access_token.rb
+++ b/qa/qa/resource/personal_access_token.rb
@@ -13,13 +13,13 @@ module QA
end
def fabricate!
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page|
page.fill_token_name(name || 'api-test-token')
page.check_api
- page.create_token
+ page.click_create_token_button
end
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index de1e9f04c36..d706439a891 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -7,6 +7,9 @@ module QA
class Project < Base
include Events::Project
+ attr_writer :initialize_with_readme
+
+ attribute :id
attribute :name
attribute :description
@@ -32,6 +35,7 @@ module QA
def initialize
@description = 'My awesome project'
+ @initialize_with_readme = false
end
def name=(raw_name)
@@ -48,6 +52,7 @@ module QA
page.choose_name(@name)
page.add_description(@description)
page.set_visibility('Public')
+ page.enable_initialize_with_readme if @initialize_with_readme
page.create_new_project
end
end
@@ -72,7 +77,8 @@ module QA
path: name,
name: name,
description: description,
- visibility: 'public'
+ visibility: 'public',
+ initialize_with_readme: @initialize_with_readme
}
end
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index 3f02fe885a9..a160cdb3273 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -4,7 +4,7 @@ require 'securerandom'
module QA
module Resource
- class ProjectImportedFromGithub < Project
+ class ProjectImportedFromGithub < Base
attr_accessor :name
attr_writer :personal_access_token, :github_repository_path
@@ -18,11 +18,11 @@ module QA
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
- page.go_to_import_project
+ page.click_import_project
end
Page::Project::New.perform do |page|
- page.go_to_github_import
+ page.click_github_link
end
Page::Project::Import::Github.perform do |page|
diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb
index a4d6657caff..8ace75f695a 100644
--- a/qa/qa/resource/project_milestone.rb
+++ b/qa/qa/resource/project_milestone.rb
@@ -28,7 +28,7 @@ module QA
Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title)
milestone_new.set_description(@description)
- milestone_new.create_new_milestone
+ milestone_new.click_milestone_create_button
end
end
end
diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb
index cad89ebb0bb..e98880ce195 100644
--- a/qa/qa/resource/repository/project_push.rb
+++ b/qa/qa/resource/repository/project_push.rb
@@ -32,8 +32,8 @@ module QA
def fabricate!
super
- project.visit!
project.wait_for_push @commit_message if @wait_for_push
+ project.visit!
end
end
end
diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb
index 77c4c8a514d..95712300854 100644
--- a/qa/qa/resource/repository/wiki_push.rb
+++ b/qa/qa/resource/repository/wiki_push.rb
@@ -24,7 +24,7 @@ module QA
@repository_http_uri ||= begin
wiki.visit!
Page::Project::Wiki::Show.act do
- go_to_clone_repository
+ click_clone_repository
choose_repository_clone_http
repository_location.uri
end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 08ae3f22117..3344ad3360a 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -29,7 +29,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings|
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 41ce857a8b8..942eea5cc40 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -20,9 +20,9 @@ module QA
Page::Dashboard::Groups.perform do |page|
if page.has_group?(path)
- page.go_to_group(path)
+ page.click_group(path)
else
- page.go_to_new_group
+ page.click_new_group
Page::Group::New.perform do |group|
group.set_path(path)
diff --git a/qa/qa/resource/settings/hashed_storage.rb b/qa/qa/resource/settings/hashed_storage.rb
index 40c06768ffe..08bb95cfd4b 100644
--- a/qa/qa/resource/settings/hashed_storage.rb
+++ b/qa/qa/resource/settings/hashed_storage.rb
@@ -8,7 +8,7 @@ module QA
raise ArgumentError unless traits.include?(:enabled)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Main::Menu.perform(&:click_admin_area)
Page::Admin::Menu.perform(&:go_to_repository_settings)
Page::Admin::Settings::Repository.perform do |setting|
diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb
new file mode 100644
index 00000000000..f58d7b5113f
--- /dev/null
+++ b/qa/qa/resource/snippet.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class Snippet < Base
+ attr_accessor :title, :description, :file_content, :visibility, :file_name
+
+ def initialize
+ @title = 'New snippet title'
+ @description = 'The snippet description'
+ @visibility = 'Public'
+ @file_content = 'The snippet content'
+ @file_name = 'New snippet file name'
+ end
+
+ def fabricate!
+ Page::Dashboard::Snippet::Index.perform(&:go_to_new_snippet_page)
+
+ Page::Dashboard::Snippet::New.perform do |page|
+ page.fill_title(@title)
+ page.fill_description(@description)
+ page.set_visibility(@visibility)
+ page.fill_file_name(@file_name)
+ page.fill_file_content(@file_content)
+ page.click_create_snippet_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb
index c6c97c8532f..4e399ae730e 100644
--- a/qa/qa/resource/ssh_key.rb
+++ b/qa/qa/resource/ssh_key.rb
@@ -14,7 +14,7 @@ module QA
end
def fabricate!
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page|
diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb
index e942e9718a0..0a776f22e25 100644
--- a/qa/qa/resource/wiki.rb
+++ b/qa/qa/resource/wiki.rb
@@ -18,7 +18,7 @@ module QA
Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
Page::Project::Wiki::New.perform do |wiki_new|
- wiki_new.go_to_create_first_page
+ wiki_new.click_create_your_first_page_button
wiki_new.set_title(@title)
wiki_new.set_content(@content)
wiki_new.set_message(@message)
diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb
index ffad3974b02..98d042fb43a 100644
--- a/qa/qa/runtime/address.rb
+++ b/qa/qa/runtime/address.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
class Address
@@ -15,6 +17,13 @@ module QA
@instance.to_s
end
end
+
+ def self.valid?(value)
+ uri = URI.parse(value)
+ uri.is_a?(URI::HTTP) && !uri.host.nil?
+ rescue URI::InvalidURIError
+ false
+ end
end
end
end
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index aff84c89f0e..40a3bc85195 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'airborne'
module QA
@@ -14,7 +16,7 @@ module QA
def personal_access_token
@personal_access_token ||= begin
- # you can set the environment variable PERSONAL_ACCESS_TOKEN
+ # you can set the environment variable GITLAB_QA_ACCESS_TOKEN
# to use a specific access token rather than create one from the UI
Runtime::Env.personal_access_token ||= create_personal_access_token
end
@@ -23,15 +25,12 @@ module QA
private
def create_personal_access_token
- if @is_new_session
- Runtime::Browser.visit(@address, Page::Main::Login) { do_create_personal_access_token }
- else
- do_create_personal_access_token
- end
+ Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session
+ do_create_personal_access_token
end
def do_create_personal_access_token
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::PersonalAccessToken.fabricate!.access_token
end
end
diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb
index ff9f0004524..310c1dfeeb4 100644
--- a/qa/qa/runtime/api/request.rb
+++ b/qa/qa/runtime/api/request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module API
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 0b805b855ac..3bf4b3bbbfb 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rspec/core'
require 'capybara/rspec'
require 'capybara-screenshot/rspec'
@@ -31,6 +33,7 @@ module QA
def self.visit(address, page = nil, &block)
new.visit(address, page, &block)
+ page.validate_elements_present!
end
def self.configure!
@@ -74,6 +77,9 @@ module QA
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
+
+ # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
+ options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci?
end
# Use the same profile on QA runs if CHROME_REUSE_PROFILE is true.
@@ -83,9 +89,6 @@ module QA
options.add_argument("user-data-dir=#{qa_profile_dir}")
end
- # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
- options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci?
-
selenium_options = {
browser: QA::Runtime::Env.browser,
clear_local_storage: true,
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index dd0ddbdbd6b..82510dfa03c 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -53,7 +53,7 @@ module QA
# specifies token that can be used for the api
def personal_access_token
- @personal_access_token ||= ENV['PERSONAL_ACCESS_TOKEN']
+ @personal_access_token ||= ENV['GITLAB_QA_ACCESS_TOKEN']
end
def remote_grid
@@ -136,6 +136,10 @@ module QA
ENV['GITLAB_QA_PASSWORD_2']
end
+ def knapsack?
+ !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
+ end
+
def ldap_username
@ldap_username ||= ENV['GITLAB_LDAP_USERNAME']
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
new file mode 100644
index 00000000000..1b4ae7adbbe
--- /dev/null
+++ b/qa/qa/runtime/feature.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Feature
+ extend self
+ extend Support::Api
+
+ SetFeatureError = Class.new(RuntimeError)
+
+ def enable(key)
+ QA::Runtime::Logger.info("Enabling feature: #{key}")
+ set_feature(key, true)
+ end
+
+ def disable(key)
+ QA::Runtime::Logger.info("Disabling feature: #{key}")
+ set_feature(key, false)
+ end
+
+ private
+
+ def api_client
+ @api_client ||= Runtime::API::Client.new(:gitlab)
+ end
+
+ def set_feature(key, value)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = post(request.url, { value: value })
+ unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
+ raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/key/base.rb b/qa/qa/runtime/key/base.rb
index 67a992e2115..1281eceaff0 100644
--- a/qa/qa/runtime/key/base.rb
+++ b/qa/qa/runtime/key/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module Key
diff --git a/qa/qa/runtime/key/ecdsa.rb b/qa/qa/runtime/key/ecdsa.rb
index 20adad45913..46a1e5f54cf 100644
--- a/qa/qa/runtime/key/ecdsa.rb
+++ b/qa/qa/runtime/key/ecdsa.rb
@@ -1,4 +1,5 @@
-# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
module QA
module Runtime
module Key
diff --git a/qa/qa/runtime/key/ed25519.rb b/qa/qa/runtime/key/ed25519.rb
index 63865c1cee5..3a3567d55da 100644
--- a/qa/qa/runtime/key/ed25519.rb
+++ b/qa/qa/runtime/key/ed25519.rb
@@ -1,4 +1,5 @@
-# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
module QA
module Runtime
module Key
diff --git a/qa/qa/runtime/key/rsa.rb b/qa/qa/runtime/key/rsa.rb
index d94bde52325..d2bd72e5710 100644
--- a/qa/qa/runtime/key/rsa.rb
+++ b/qa/qa/runtime/key/rsa.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module Key
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index 9d7c1aea508..565bfd43f12 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module Namespace
diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb
index b1f7ec482c8..4f96e0cf44b 100644
--- a/qa/qa/runtime/release.rb
+++ b/qa/qa/runtime/release.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
##
diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb
index 15d4112d347..5067322804b 100644
--- a/qa/qa/runtime/scenario.rb
+++ b/qa/qa/runtime/scenario.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
##
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
index e8bcb8a9f50..011e4a548a5 100644
--- a/qa/qa/runtime/user.rb
+++ b/qa/qa/runtime/user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Runtime
module User
diff --git a/qa/qa/scenario/actable.rb b/qa/qa/scenario/actable.rb
index 6cdbd24780e..d7004215751 100644
--- a/qa/qa/scenario/actable.rb
+++ b/qa/qa/scenario/actable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Actable
diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb
index dd12ea6d492..841ac4dd560 100644
--- a/qa/qa/scenario/bootable.rb
+++ b/qa/qa/scenario/bootable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'optparse'
module QA
@@ -23,7 +25,7 @@ module QA
arguments.parse!(argv)
- self.perform(Runtime::Scenario.attributes, *arguments.default_argv)
+ self.perform(Runtime::Scenario.attributes, *argv)
end
private
@@ -33,7 +35,13 @@ module QA
end
def options
- @options ||= []
+ # Scenario options/attributes are global. There's only ever one
+ # scenario at a time, but they can be inherited and we want scenarios
+ # to share the attributes of their ancestors. For example, `Mattermost`
+ # inherits from `Test::Instance::All` but if this were an instance
+ # variable then `Mattermost` wouldn't have access to the attributes
+ # in `All`
+ @@options ||= [] # rubocop:disable Style/ClassVars
end
def has_attributes?
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
new file mode 100644
index 00000000000..40d5c6b1ff1
--- /dev/null
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module SharedAttributes
+ include Bootable
+
+ attribute :gitlab_address, '--address URL', 'Address of the instance to test'
+ attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
+ end
+ end
+end
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index cb1a1de6b9a..74d4c8f8757 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
class Template
@@ -18,19 +20,44 @@ module QA
end
end
- def perform(address, *rspec_options)
- Runtime::Scenario.define(:gitlab_address, address)
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
+ Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
+
Specs::Runner.perform do |specs|
specs.tty = true
specs.tags = self.class.focus
- specs.options = rspec_options if rspec_options.any?
+ specs.options = args if args.any?
end
+ ensure
+ Runtime::Feature.disable(options[:enable_feature]) if options.key?(:enable_feature)
+ end
+
+ def extract_option(name, options, args)
+ option = if options.key?(name)
+ options[name]
+ else
+ args.shift
+ end
+
+ Runtime::Scenario.define(name, option)
+
+ option
+ end
+
+ # For backwards-compatibility, if the gitlab instance address is not
+ # specified as an option parsed by OptionParser, it can be specified as
+ # the first argument
+ def extract_address(name, options, args)
+ address = extract_option(name, options, args)
+
+ raise ::ArgumentError, "The address provided for `#{name}` is not valid: #{address}" unless Runtime::Address.valid?(address)
end
end
end
diff --git a/qa/qa/scenario/test/instance/all.rb b/qa/qa/scenario/test/instance/all.rb
index a07c26431bd..42a158737fe 100644
--- a/qa/qa/scenario/test/instance/all.rb
+++ b/qa/qa/scenario/test/instance/all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
@@ -8,6 +10,7 @@ module QA
module Instance
class All < Template
include Bootable
+ include SharedAttributes
end
end
end
diff --git a/qa/qa/scenario/test/instance/smoke.rb b/qa/qa/scenario/test/instance/smoke.rb
index a7d2cb27f27..84d83bd860d 100644
--- a/qa/qa/scenario/test/instance/smoke.rb
+++ b/qa/qa/scenario/test/instance/smoke.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
@@ -8,6 +10,7 @@ module QA
#
class Smoke < Template
include Bootable
+ include SharedAttributes
tags :smoke
end
diff --git a/qa/qa/scenario/test/integration/github.rb b/qa/qa/scenario/test/integration/github.rb
index 76c3923d68a..857a1f00bd5 100644
--- a/qa/qa/scenario/test/integration/github.rb
+++ b/qa/qa/scenario/test/integration/github.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
diff --git a/qa/qa/scenario/test/integration/kubernetes.rb b/qa/qa/scenario/test/integration/kubernetes.rb
index 405962caeed..d82d0c09a90 100644
--- a/qa/qa/scenario/test/integration/kubernetes.rb
+++ b/qa/qa/scenario/test/integration/kubernetes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index ece6fba75c9..48ebb51966c 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
@@ -9,10 +11,13 @@ module QA
class Mattermost < Test::Instance::All
tags :mattermost
- def perform(address, mattermost, *rspec_options)
- Runtime::Scenario.define(:mattermost_address, mattermost)
+ attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server'
+
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
+ extract_address(:mattermost_address, options, args)
- super(address, *rspec_options)
+ super(options, *args)
end
end
end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index cff320cb751..e05269e8d55 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Scenario
module Test
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index c5f12255d72..16a89591eb2 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'securerandom'
require 'mkmf'
require 'pathname'
@@ -9,7 +11,7 @@ module QA
attr_reader :api_url, :ca_certificate, :token, :rbac
- def initialize(rbac: false)
+ def initialize(rbac: true)
@rbac = rbac
end
diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb
index b5c06874e5c..b54fd5628f2 100644
--- a/qa/qa/service/omnibus.rb
+++ b/qa/qa/service/omnibus.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Service
class Omnibus
diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb
index 9417c707105..03b234f7a56 100644
--- a/qa/qa/service/runner.rb
+++ b/qa/qa/service/runner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'securerandom'
module QA
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 43dc0851571..7065ab0e7f3 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'open3'
module QA
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index bc0b5ebfe10..f6f020da472 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'securerandom'
module QA
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index 8f24a27b26f..cf225a639b6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
context 'Manage', :smoke do
describe 'basic user login' do
@@ -5,16 +7,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- # TODO, since `Signed in successfully` message was removed
- # this is the only way to tell if user is signed in correctly.
- #
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
end
Page::Main::Menu.perform do |menu|
menu.sign_out
- expect(menu).not_to have_personal_area
end
Page::Main::Login.perform do |form|
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index a397df03bd2..72dde4e5bd8 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -7,9 +7,6 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- # TODO, since `Signed in successfully` message was removed
- # this is the only way to tell if user is signed in correctly.
- #
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index b1d641b507f..67610b62ed7 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -4,16 +4,14 @@ module QA
context 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login) do
- Page::Main::Login.act { sign_in_using_credentials }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
- Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) do
- Page::Mattermost::Login.act { sign_in_using_oauth }
+ Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
+ Page::Mattermost::Login.perform(&:sign_in_using_oauth)
- Page::Mattermost::Main.perform do |page|
- expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
- end
- end
+ Page::Mattermost::Main.perform do |page|
+ expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
index a118176eb8a..15cd59f041b 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :oauth do
+ # https://gitlab.com/gitlab-org/quality/nightly/issues/100
+ context 'Manage', :orchestrated, :oauth, :quarantine do
describe 'OAuth login' do
it 'User logs in to GitLab with GitHub OAuth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index 185837edacf..a0e3fe0d91a 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -7,9 +7,6 @@ module QA
Resource::User.fabricate_via_browser_ui!
- # TODO, since `Signed in successfully` message was removed
- # this is the only way to tell if user is signed in correctly.
- #
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 4070a225260..d1747280227 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -14,12 +14,12 @@ module QA
end
project.visit!
- Page::Project::Menu.perform(&:click_members_settings)
+ Page::Project::Menu.perform(&:go_to_members_settings)
Page::Project::Settings::Members.perform do |page|
page.add_member(user.username)
end
- expect(page).to have_content("#{user.name} @#{user.username} Given access")
+ expect(page).to have_content(/#{user.name} (. )?@#{user.username} Given access/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index 3ce48de2c25..a9eafd61a91 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :github do
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58158
+ context 'Manage', :github, :quarantine do
describe 'Project import from GitHub' do
let(:imported_project) do
Resource::ProjectImportedFromGithub.fabricate! do |project|
@@ -48,20 +49,26 @@ module QA
end
def verify_issues_import
- Page::Project::Menu.act { click_issues }
- expect(page).to have_content('This is a sample issue')
+ QA::Support::Retrier.retry_on_exception do
+ Page::Project::Menu.act { click_issues }
+ expect(page).to have_content('This is a sample issue')
- click_link 'This is a sample issue'
+ click_link 'This is a sample issue'
- expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
+ expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
- # Comments
- expect(page).to have_content('This is a comment from @rymai.')
+ # Comments
+ comment_text = 'This is a comment from @rymai.'
- Page::Issuable::Sidebar.perform do |issuable|
- expect(issuable).to have_label('enhancement')
- expect(issuable).to have_label('help wanted')
- expect(issuable).to have_label('good first issue')
+ Page::Project::Issue::Show.perform do |issue_page|
+ expect(issue_page).to have_comment(comment_text)
+ end
+
+ Page::Issuable::Sidebar.perform do |issuable|
+ expect(issuable).to have_label('enhancement')
+ expect(issuable).to have_label('help wanted')
+ expect(issuable).to have_label('good first issue')
+ end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index e172206eb88..fe92fbd3ffe 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/21
- context 'Manage', :quarantine do
+ context 'Manage' do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -15,8 +14,8 @@ module QA
end
project_push.project.visit!
- Page::Project::Menu.perform(&:go_to_activity)
- Page::Project::Activity.perform(&:go_to_push_events)
+ Page::Project::Menu.perform(&:click_activity)
+ Page::Project::Activity.perform(&:click_push_events)
expect(page).to have_content('pushed new branch master')
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 7145b950b6c..9b083d59a5e 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -5,15 +5,6 @@ module QA
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
- def create_issue
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- Resource::Issue.fabricate! do |issue|
- issue.title = issue_title
- end
- end
-
it 'user creates an issue' do
create_issue
@@ -22,7 +13,8 @@ module QA
expect(page).to have_content(issue_title)
end
- context 'when using attachments in comments', :object_storage do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/101
+ context 'when using attachments in comments', :object_storage, :quarantine do
let(:file_to_attach) do
File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif'))
end
@@ -46,6 +38,15 @@ module QA
end
end
end
+
+ def create_issue
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Resource::Issue.fabricate! do |issue|
+ issue.title = issue_title
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index ac34f72bb8f..c0d597af076 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'filter issue comments activities' do
let(:issue_title) { 'issue title' }
- it 'user filters comments and activites in an issue' do
+ it 'user filters comments and activities in an issue' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index 7e8b42e286f..530fc684437 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -9,12 +9,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- project = Resource::Project.fabricate! do |resource|
+ project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'project-for-issue-suggestions'
resource.description = 'project for issue suggestions'
end
- Resource::Issue.fabricate! do |issue|
+ Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = issue_title
issue.project = project
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index c06f13ee204..0ff71baed90 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -1,9 +1,32 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/50
+ context 'Create', :quarantine do
describe 'Merge request creation' do
- it 'user creates a new merge request' do
+ it 'user creates a new merge request', :smoke do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ current_project = Resource::Project.fabricate! do |project|
+ project.name = 'project-with-merge-request'
+ end
+
+ merge_request_title = 'This is a merge request'
+ merge_request_description = 'Great feature'
+
+ Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
+ merge_request.project = current_project
+ end
+
+ expect(page).to have_content(merge_request_title)
+ expect(page).to have_content(merge_request_description)
+ expect(page).to have_content(/Opened [\w\s]+ ago/)
+ end
+
+ it 'user creates a new merge request with a milestone and label' do
gitlab_account_username = "@#{Runtime::User.username}"
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -24,9 +47,12 @@ module QA
label.description = 'Merge Request label'
end
+ merge_request_title = 'This is a merge request with a milestone and a label'
+ merge_request_description = 'Great feature with milestone'
+
Resource::MergeRequest.fabricate! do |merge_request|
- merge_request.title = 'This is a merge request with a milestone'
- merge_request.description = 'Great feature with milestone'
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
merge_request.project = current_project
merge_request.milestone = current_milestone
merge_request.assignee = 'me'
@@ -34,8 +60,8 @@ module QA
end
Page::MergeRequest::Show.perform do |merge_request|
- expect(merge_request).to have_content('This is a merge request with a milestone')
- expect(merge_request).to have_content('Great feature with milestone')
+ expect(merge_request).to have_content(merge_request_title)
+ expect(merge_request).to have_content(merge_request_description)
expect(merge_request).to have_content(/Opened [\w\s]+ ago/)
expect(merge_request).to have_assignee(gitlab_account_username)
expect(merge_request).to have_label(new_label.title)
@@ -47,25 +73,4 @@ module QA
end
end
end
-
- describe 'creates a merge request', :smoke do
- it 'user creates a new merge request' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- current_project = Resource::Project.fabricate! do |project|
- project.name = 'project-with-merge-request'
- end
-
- Resource::MergeRequest.fabricate! do |merge_request|
- merge_request.title = 'This is a merge request'
- merge_request.description = 'Great feature'
- merge_request.project = current_project
- end
-
- expect(page).to have_content('This is a merge request')
- expect(page).to have_content('Great feature')
- expect(page).to have_content(/Opened [\w\s]+ ago/)
- end
- end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 6ca7af8a3af..c7db595284e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/94
+ context 'Create', :quarantine do
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index 3fbcd77dac6..7a36f9ea420 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -12,7 +12,7 @@ module QA
end
project.visit!
- Page::Project::Menu.perform(&:go_to_settings)
+ Page::Project::Menu.perform(&:click_settings)
Page::Project::Settings::MergeRequest.perform(&:enable_ff_only)
merge_request = Resource::MergeRequest.fabricate! do |merge_request|
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index f146636c49a..86692623790 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/31
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/93
+ context 'Create', :quarantine do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
new file mode 100644
index 00000000000..db33c6330ff
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ # https://gitlab.com/gitlab-org/quality/staging/issues/55
+ context 'Create', :quarantine do
+ describe 'Download merge request patch and diff' do
+ before(:context) do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @merge_request = Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = 'This is a merge request'
+ merge_request.description = 'For downloading patches and diffs'
+ end
+ end
+
+ it 'user views merge request email patches' do
+ @merge_request.visit!
+ Page::MergeRequest::Show.perform(&:view_email_patches)
+
+ expect(page.text).to start_with('From')
+ expect(page).to have_content('Subject: [PATCH] This is a test commit')
+ expect(page).to have_content('diff --git a/added_file.txt b/added_file.txt')
+ end
+
+ it 'user views merge request plain diff' do
+ @merge_request.visit!
+ Page::MergeRequest::Show.perform(&:view_plain_diff)
+
+ expect(page.text).to start_with('diff --git a/added_file.txt b/added_file.txt')
+ expect(page).to have_content('+File Added')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index de5c535c757..2750b171a85 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -2,7 +2,8 @@
module QA
context 'Create' do
- describe 'File templates' do
+ # Issue: https://gitlab.com/gitlab-org/quality/nightly/issues/97
+ describe 'File templates', :quarantine do
include Runtime::Fixtures
def login
@@ -16,14 +17,7 @@ module QA
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Files view'
- end
-
- # There's no 'New File' dropdown when the project is blank, so we first
- # add a dummy file so that the dropdown will appear
- Resource::File.fabricate! do |file|
- file.project = @project
- file.name = 'README.md'
- file.content = '# Readme'
+ project.initialize_with_readme = true
end
Page::Main::Menu.perform(&:sign_out)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
index 8e181eb28c6..37a784248d4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/62
+ context 'Create', :quarantine do
describe 'Create, list, and delete branches via web' do
master_branch = 'master'
second_branch = 'second-branch'
@@ -57,7 +58,7 @@ module QA
end
it 'branches are correctly listed after CRUD operations' do
- Page::Project::Menu.perform(&:click_repository_branches)
+ Page::Project::Menu.perform(&:go_to_repository_branches)
expect(page).to have_content(master_branch)
expect(page).to have_content(second_branch)
@@ -72,10 +73,9 @@ module QA
Page::Project::Branches::Show.perform do |branches_view|
branches_view.delete_branch(third_branch)
+ expect(branches_view).to have_no_branch(third_branch)
end
- expect(page).not_to have_content(third_branch)
-
Page::Project::Branches::Show.perform(&:delete_merged_branches)
expect(page).to have_content(
@@ -84,8 +84,7 @@ module QA
page.refresh
Page::Project::Branches::Show.perform do |branches_view|
- branches_view.wait_for_texts_not_to_be_visible([commit_message_of_second_branch])
- expect(branches_view).not_to have_branch_title(second_branch)
+ expect(branches_view).to have_no_branch(second_branch, reload: true)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index ff879fdeb16..f41240b7605 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -16,7 +16,7 @@ module QA
expect(page).to have_content("Title: #{key_title}")
expect(page).to have_content(key.fingerprint)
- Page::Main::Menu.act { go_to_profile_settings }
+ Page::Main::Menu.act { click_settings_link }
Page::Profile::Menu.act { click_ssh_keys }
Page::Profile::SSHKeys.perform do |ssh_keys|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 46346d1b984..d345fbfe995 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -12,7 +12,7 @@ module QA
file_content = 'QA Test - File content'
commit_message_for_create = 'QA Test - Create new file'
- Resource::File.fabricate! do |file|
+ Resource::File.fabricate_via_browser_ui! do |file|
file.name = file_name
file.content = file_content
file.commit_message = commit_message_for_create
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
index d1535d6519d..01a367fceac 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Git protocol v2 is temporarily disabled
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55769 (confidential)
+ context 'Create', :quarantine do
describe 'Push over HTTP using Git protocol version 2', :requires_git_protocol_v2 do
it 'user pushes to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
index 48800cc81e5..dc42191b448 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Git protocol v2 is temporarily disabled
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55769 (confidential)
+ context 'Create', :quarantine do
describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do
# Note: If you run this test against GDK make sure you've enabled sshd and
# enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to
@@ -29,7 +31,7 @@ module QA
# Remove the SSH key
login
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |ssh_keys|
ssh_keys.remove_key(key_title)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
index 2e4915b898d..23008a58af8 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # https://gitlab.com/gitlab-org/quality/staging/issues/40
+ context 'Create', :quarantine do
describe 'Push mirror a repository over HTTP' do
it 'configures and syncs a (push) mirrored repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -20,7 +21,7 @@ module QA
end
source_project_push.project.visit!
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |settings|
settings.expand_mirroring_repositories do |mirror_settings|
# Configure the source project to push to the target project
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 243f0b83b77..5bfafdfa041 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -26,6 +26,8 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials)
set_file_size_limit('')
+
+ Page::Main::Menu.perform(&:sign_out)
end
it 'push successful when the file size is under the limit' do
@@ -40,12 +42,12 @@ module QA
set_file_size_limit(1)
expect(page).to have_content("Application settings saved successfully")
- push = push_new_file('oversize_file_2.bin', wait_for_push: false)
- expect(push.output).to have_content 'remote: fatal: pack exceeds maximum allowed size'
+ expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
+ .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
end
def set_file_size_limit(limit)
- Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Main::Menu.perform(&:click_admin_area)
Page::Admin::Menu.perform(&:go_to_general_settings)
Page::Admin::Settings::General.perform do |setting|
@@ -65,6 +67,7 @@ module QA
p.file_content = SecureRandom.random_bytes(2000000)
p.commit_message = 'Adding a new file'
p.wait_for_push = wait_for_push
+ p.new_branch = false
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index 4464fb812b7..6aebd04af03 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -37,12 +37,7 @@ module QA
it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
- push = push_new_file(branch_name)
-
- expect(push.output)
- .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
- expect(push.output)
- .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
+ expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 7223831d96f..a0251e1c385 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -28,7 +28,7 @@ module QA
expect(page).to have_content('README.md')
expect(page).to have_content('Test Use SSH Key')
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |ssh_keys|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
new file mode 100644
index 00000000000..680c5e21fa4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ # failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/42
+ describe 'Commit data', :quarantine do
+ before(:context) do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Get the user's details to confirm they're included in the email patch
+ @user = Resource::User.fabricate_via_api! do |user|
+ user.username = Runtime::User.username
+ end
+
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
+ end
+ @project = project_push.project
+
+ # first file added has no parent commit, thus no diff data
+ # add second file to repo to enable diff from initial commit
+ @commit_message = 'Add second file'
+
+ Resource::File.fabricate_via_api! do |file|
+ file.project = @project
+ file.name = 'second'
+ file.content = 'second file content'
+ file.commit_message = @commit_message
+ file.author_name = @user.name
+ file.author_email = @user.public_email
+ end
+ end
+
+ def view_commit
+ @project.visit!
+ Page::Project::Show.perform do |page|
+ page.click_commit(@commit_message)
+ end
+ end
+
+ def raw_content
+ find('pre').text
+ end
+
+ it 'user views raw email patch' do
+ view_commit
+
+ Page::Project::Commit::Show.perform(&:select_email_patches)
+
+ expect(page).to have_content("From: #{@user.name} <#{@user.public_email}>")
+ expect(page).to have_content('Subject: [PATCH] Add second file')
+ expect(page).to have_content('diff --git a/second b/second')
+ end
+
+ it 'user views raw commit diff' do
+ view_commit
+
+ Page::Project::Commit::Show.perform(&:select_plain_diff)
+
+ expect(raw_content).to start_with('diff --git a/second b/second')
+ expect(page).to have_content('+second file content')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
deleted file mode 100644
index b862a7bd1ed..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Create' do
- # failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/42
- # also failing in staging until the fix is picked into the next release:
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24533
- describe 'Commit data', :quarantine do
- before(:context) do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
-
- project_push = Resource::Repository::ProjectPush.fabricate! do |push|
- push.file_name = 'README.md'
- push.file_content = '# This is a test project'
- push.commit_message = 'Add README.md'
- end
- @project = project_push.project
-
- # first file added has no parent commit, thus no diff data
- # add second file to repo to enable diff from initial commit
- @commit_message = 'Add second file'
-
- Page::Project::Show.perform(&:create_new_file!)
- Page::File::Form.perform do |f|
- f.add_name('second')
- f.add_content('second file content')
- f.add_commit_message(@commit_message)
- f.commit_changes
- end
- end
-
- def view_commit
- @project.visit!
- Page::Project::Show.perform do |page|
- page.go_to_commit(@commit_message)
- end
- end
-
- def raw_content
- find('pre').text
- end
-
- it 'user views raw email patch' do
- user = Resource::User.fabricate_via_api! do |user|
- user.username = Runtime::User.username
- end
-
- view_commit
-
- Page::Project::Commit::Show.perform(&:select_email_patches)
-
- expect(page).to have_content("From: #{user.name} <#{user.public_email}>")
- expect(page).to have_content('Subject: [PATCH] Add second file')
- expect(page).to have_content('diff --git a/second b/second')
- end
-
- it 'user views raw commit diff' do
- view_commit
-
- Page::Project::Commit::Show.perform(&:select_plain_diff)
-
- expect(raw_content).to start_with('diff --git a/second b/second')
- expect(page).to have_content('+second file content')
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
new file mode 100644
index 00000000000..f6f0468e76e
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module QA
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/49
+ context 'Create', :smoke, :quarantine do
+ describe 'Snippet creation' do
+ it 'User creates a snippet' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ Page::Main::Menu.perform(&:click_snippets_link)
+
+ Resource::Snippet.fabricate_via_browser_ui! do |snippet|
+ snippet.title = 'Snippet title'
+ snippet.description = 'Snippet description'
+ snippet.visibility = 'Public'
+ snippet.file_name = 'New snippet file name'
+ snippet.file_content = 'Snippet file text'
+ end
+
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ expect(snippet).to have_snippet_title('Snippet title')
+ expect(snippet).to have_snippet_description('Snippet description')
+ expect(snippet).to have_embed_type('Embed')
+ expect(snippet).to have_visibility_type('Public')
+ expect(snippet).to have_file_name('New snippet file name')
+ expect(snippet).to have_file_content('Snippet file text')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index f176ec31abd..078d3b2b5b1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/46
+ context 'Create', :quarantine do
describe 'Web IDE file templates' do
include Runtime::Fixtures
@@ -16,16 +17,7 @@ module QA
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Web IDE'
- end
- @project.visit!
-
- # Add a file via the regular Files view because the Web IDE isn't
- # available unless there is a file present
- Page::Project::Show.perform(&:create_first_new_file!)
- Page::File::Form.perform do |page|
- page.add_name('dummy')
- page.add_content('Enable the Web IDE')
- page.commit_changes
+ project.initialize_with_readme = true
end
Page::Main::Menu.perform(&:sign_out)
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 29589ec870a..e689ba4c69c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -3,11 +3,6 @@
module QA
context 'Create' do
describe 'Wiki management' do
- def validate_content(content)
- expect(page).to have_content('Wiki was successfully updated')
- expect(page).to have_content(/#{content}/)
- end
-
it 'user creates, edits, clones, and pushes to the wiki' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
@@ -20,7 +15,7 @@ module QA
validate_content('My First Wiki Content')
- Page::Project::Wiki::Edit.perform(&:go_to_edit_page)
+ Page::Project::Wiki::Edit.perform(&:click_edit)
Page::Project::Wiki::New.perform do |page|
page.set_content("My Second Wiki Content")
page.save_changes
@@ -38,6 +33,11 @@ module QA
expect(page).to have_content('My Third Wiki Content')
end
+
+ def validate_content(content)
+ expect(page).to have_content('Wiki was successfully updated')
+ expect(page).to have_content(/#{content}/)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index e444bc7ef1b..b060f15168c 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -1,26 +1,36 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/30
- context 'Verify', :quarantine do
+ context 'Verify' do
describe 'CI variable support' do
- it 'user adds a CI variable' do
+ it 'user adds a CI variable', :smoke do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'project-with-ci-variables'
+ project.description = 'project with CI variables'
+ end
Resource::CiVariable.fabricate! do |resource|
+ resource.project = project
resource.key = 'VARIABLE_KEY'
- resource.value = 'some CI variable'
+ resource.value = 'some_CI_variable'
+ resource.masked = false
end
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
+
Page::Project::Settings::CICD.perform do |settings|
settings.expand_ci_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
- expect(page).not_to have_field(with: 'some CI variable')
+ expect(page).not_to have_field(with: 'some_CI_variable')
page.reveal_variables
- expect(page).to have_field(with: 'some CI variable')
+ expect(page).to have_field(with: 'some_CI_variable')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 2238d6c382e..7c1d4489c47 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -70,7 +70,7 @@ module QA
puts 'Waiting for the runner to process the pipeline'
sleep 15 # Runner should process all jobs within 15 seconds.
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to be_running
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 3f65eabc756..2fe4e4d9d1f 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -60,6 +60,7 @@ module QA
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
+ resource.masked = false
end
gitlab_ci = <<~YAML
@@ -91,8 +92,8 @@ module QA
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
- Page::Project::Pipeline::Show.perform(&:go_to_first_job)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+ Page::Project::Pipeline::Show.perform(&:click_on_first_job)
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index 9f34e4218c1..caa9be341b4 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -8,11 +8,11 @@ module QA
Page::Main::Login.act { sign_in_using_credentials }
deploy_token_name = 'deploy token name'
- deploy_token_expires_at = Date.today + 7 # 1 Week from now
+ one_week_from_now = Date.today + 7
deploy_token = Resource::DeployToken.fabricate! do |resource|
resource.name = deploy_token_name
- resource.expires_at = deploy_token_expires_at
+ resource.expires_at = one_week_from_now
end
expect(deploy_token.username.length).to be > 0
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 02bd378acfc..86ba5e819ba 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -3,171 +3,104 @@
require 'pathname'
module QA
- # Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68
context 'Configure' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
end
- describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do
- [true, false].each do |rbac|
- context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
- before(:all) do
- login
-
- @project = Resource::Project.fabricate! do |p|
- p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
- p.description = 'Project with Auto DevOps'
- end
-
- # Disable code_quality check in Auto DevOps pipeline as it takes
- # too long and times out the test
- Resource::CiVariable.fabricate! do |resource|
- resource.project = @project
- resource.key = 'CODE_QUALITY_DISABLED'
- resource.value = '1'
- end
+ describe 'Auto DevOps support', :orchestrated, :kubernetes do
+ context 'when rbac is enabled' do
+ before(:all) do
+ @cluster = Service::KubernetesCluster.new.create!
+ end
- # Create Auto DevOps compatible repo
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = @project
- push.directory = Pathname
- .new(__dir__)
- .join('../../../../../fixtures/auto_devops_rack')
- push.commit_message = 'Create Auto DevOps compatible rack application'
- end
+ after(:all) do
+ @cluster&.remove!
+ end
- # Create and connect K8s cluster
- @cluster = Service::KubernetesCluster.new(rbac: rbac).create!
- Resource::KubernetesCluster.fabricate! do |cluster|
- cluster.project = @project
- cluster.cluster = @cluster
- cluster.install_helm_tiller = true
- cluster.install_ingress = true
- cluster.install_prometheus = true
- cluster.install_runner = true
- end
+ it 'runs auto devops' do
+ login
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
- Page::Project::Settings::CICD.perform do |p|
- p.enable_auto_devops
- end
+ @project = Resource::Project.fabricate! do |p|
+ p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
+ p.description = 'Project with Auto DevOps'
end
- after(:all) do
- @cluster&.remove!
+ # Disable code_quality check in Auto DevOps pipeline as it takes
+ # too long and times out the test
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'CODE_QUALITY_DISABLED'
+ resource.value = '1'
+ resource.masked = false
end
- before do
- login
+ # Set an application secret CI variable (prefixed with K8S_SECRET_)
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
+ resource.value = 'you_can_see_this_variable'
+ resource.masked = false
end
- it 'runs auto devops' do
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
-
- job.click_element(:pipeline_path)
- end
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
-
- job.click_element(:pipeline_path)
- end
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 1200)
-
- job.click_element(:pipeline_path)
- end
-
- Page::Project::Menu.perform(&:click_operations_environments)
- Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
- end
- Page::Project::Operations::Environments::Show.perform do |show|
- show.view_deployment do
- expect(page).to have_content('Hello World!')
- end
- end
+ # Connect K8s cluster
+ Resource::KubernetesCluster.fabricate! do |cluster|
+ cluster.project = @project
+ cluster.cluster = @cluster
+ cluster.install_helm_tiller = true
+ cluster.install_ingress = true
+ cluster.install_prometheus = true
+ cluster.install_runner = true
end
- it 'user sets application secret variable and Auto DevOps passes it to container' do
- # Set an application secret CI variable (prefixed with K8S_SECRET_)
- Resource::CiVariable.fabricate! do |resource|
- resource.project = @project
- resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
- resource.value = 'You can see this application secret'
- end
-
- # Our current Auto DevOps implementation won't update the production
- # app if we only update a CI variable with no code change.
- #
- # Workaround: push new code and use the resultant pipeline.
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = @project
- push.commit_message = 'Force a Deployment change by pushing new code'
- push.file_name = 'new_file.txt'
- push.file_content = 'new file contents'
- end
-
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
+ # Create Auto DevOps compatible repo
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.directory = Pathname
+ .new(__dir__)
+ .join('../../../../../fixtures/auto_devops_rack')
+ push.commit_message = 'Create Auto DevOps compatible rack application'
+ end
- job.click_element(:pipeline_path)
- end
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('build')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- job.click_element(:pipeline_path)
- end
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 1200)
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('test')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- job.click_element(:pipeline_path)
- end
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Menu.perform(&:click_operations_environments)
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('production')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 1200)
- Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
- end
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Operations::Environments::Show.perform do |show|
- show.view_deployment do
- expect(page).to have_content('Hello World!')
- expect(page).to have_content('You can see this application secret')
- end
+ Page::Project::Menu.perform(&:go_to_operations_environments)
+ Page::Project::Operations::Environments::Index.perform do |index|
+ index.click_environment_link('production')
+ end
+ Page::Project::Operations::Environments::Show.perform do |show|
+ show.view_deployment do
+ expect(page).to have_content('Hello World!')
+ expect(page).to have_content('you_can_see_this_variable')
end
end
end
@@ -183,12 +116,6 @@ module QA
p.description = 'Project with AutoDevOps'
end
- project.visit!
-
- Page::Alert::AutoDevopsAlert.perform do |alert|
- expect(alert).to have_text(/.*The Auto DevOps pipeline has been enabled.*/)
- end
-
# Create AutoDevOps repo
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
@@ -199,7 +126,7 @@ module QA
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_tag('Auto DevOps')
diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
index 7096864e011..8383dcdb983 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
@@ -9,7 +9,7 @@ module QA
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
- page.go_to_new_group
+ page.click_new_group
expect(page).to have_content(
/Create a Mattermost team for this group/
diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
new file mode 100644
index 00000000000..fc4ff364fd4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Performance bar' do
+ context 'when logged in as an admin user' do
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+ Page::Main::Menu.perform(&:click_admin_area)
+ Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
+
+ Page::Admin::Settings::MetricsAndProfiling.perform do |setting|
+ setting.expand_performance_bar do |page|
+ page.enable_performance_bar
+ page.save_settings
+ end
+ end
+ end
+
+ it 'shows results for the original request and AJAX requests' do
+ # Issue pages always make AJAX requests
+ Resource::Issue.fabricate! do |issue|
+ issue.title = 'Performance bar test'
+ end
+
+ Page::Layout::PerformanceBar.perform do |page|
+ expect(page).to have_performance_bar
+ expect(page).to have_detailed_metrics
+ expect(page).to have_request_for('realtime_changes') # Always requested on issue pages
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
new file mode 100644
index 00000000000..ca0ce32e74f
--- /dev/null
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+
+module QA::Specs::Helpers
+ module Quarantine
+ include RSpec::Core::Pending
+
+ extend self
+
+ def configure_rspec
+ RSpec.configure do |config|
+ config.before(:context, :quarantine) do
+ Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
+ end
+
+ config.before do |example|
+ Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
+ end
+ end
+ end
+
+ # Skip the entire context if a context is quarantined. This avoids running
+ # before blocks unnecessarily.
+ def skip_or_run_quarantined_contexts(filters, example)
+ return unless example.metadata.key?(:quarantine)
+
+ skip_or_run_quarantined_tests_or_contexts(filters, example)
+ end
+
+ # Skip tests in quarantine unless we explicitly focus on them.
+ def skip_or_run_quarantined_tests_or_contexts(filters, example)
+ if filters.key?(:quarantine)
+ included_filters = filters_other_than_quarantine(filters)
+
+ # If :quarantine is focused, skip the test/context unless its metadata
+ # includes quarantine and any other filters
+ # E.g., Suppose a test is tagged :smoke and :quarantine, and another is tagged
+ # :ldap and :quarantine. If we wanted to run just quarantined smoke tests
+ # using `--tag quarantine --tag smoke`, without this check we'd end up
+ # running that ldap test as well because of the :quarantine metadata.
+ # We could use an exclusion filter, but this way the test report will list
+ # the quarantined tests when they're not run so that we're aware of them
+ skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
+ else
+ skip('In quarantine') if example.metadata.key?(:quarantine)
+ end
+ end
+
+ def filters_other_than_quarantine(filter)
+ filter.reject { |key, _| key == :quarantine }
+ end
+
+ # Checks if a test or context should be skipped.
+ #
+ # Returns true if
+ # - the metadata does not includes the :quarantine tag
+ # or if
+ # - the metadata includes the :quarantine tag
+ # - and the filter includes other tags that aren't in the metadata
+ def should_skip_when_focused?(metadata, included_filters)
+ return true unless metadata.key?(:quarantine)
+ return false if included_filters.empty?
+
+ (metadata.keys & included_filters.keys).empty?
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 1bd8101c36d..f1cb9378de8 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -1,4 +1,8 @@
+# frozen_string_literal: true
+
require 'rspec/core'
+require 'rspec/expectations'
+require 'knapsack'
module QA
module Specs
@@ -30,10 +34,25 @@ module QA
end
args.push(options)
- args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
Runtime::Browser.configure!
+ if Runtime::Env.knapsack?
+ allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
+
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Report specs:'
+ QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Leftover specs:'
+ QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+
+ args.push(['--', allocator.node_tests])
+ else
+ args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
+ end
+
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 8aa7d6812ac..a5c86425465 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -1,6 +1,11 @@
+# frozen_string_literal: true
+
module QA
module Support
module Api
+ HTTP_STATUS_OK = 200
+ HTTP_STATUS_CREATED = 201
+
def post(url, payload)
RestClient::Request.execute(
method: :post,
@@ -20,6 +25,16 @@ module QA
e.response
end
+ def put(url, payload)
+ RestClient::Request.execute(
+ method: :put,
+ url: url,
+ payload: payload,
+ verify_ssl: false)
+ rescue RestClient::ExceptionWithResponse => e
+ e.response
+ end
+
def delete(url)
RestClient::Request.execute(
method: :delete,
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index 5e97a92a6e3..02ebd96ad49 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -33,11 +33,8 @@ module QA
exists
end
- def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time)
- msg = ["finding :#{name}"]
- msg << %Q(with text_filter "#{text_filter}") if text_filter
- msg << "(wait: #{wait})"
- log(msg.compact.join(' '))
+ def find_element(name, **kwargs)
+ log("finding :#{name} with args #{kwargs}")
element = super
@@ -56,8 +53,11 @@ module QA
elements
end
- def click_element(name)
- log("clicking :#{name}")
+ def click_element(name, page = nil)
+ msg = ["clicking :#{name}"]
+ msg << ", expecting to be at #{page.class}" if page
+
+ log(msg.compact.join(' '))
super
end
@@ -76,18 +76,18 @@ module QA
super
end
- def has_element?(name, wait: Capybara.default_max_wait_time)
+ def has_element?(name, **kwargs)
found = super
- log("has_element? :#{name} returned #{found}")
+ log_has_element_or_not('has_element?', name, found, **kwargs)
found
end
- def has_no_element?(name, wait: Capybara.default_max_wait_time)
+ def has_no_element?(name, **kwargs)
found = super
- log("has_no_element? :#{name} returned #{found}")
+ log_has_element_or_not('has_no_element?', name, found, **kwargs)
found
end
@@ -119,6 +119,12 @@ module QA
loaded
end
+ def wait_for_animated_element(name)
+ log("waiting for animated element: #{name}")
+
+ super
+ end
+
def within_element(name)
log("within element :#{name}")
@@ -144,6 +150,15 @@ module QA
def log(msg)
QA::Runtime::Logger.debug(msg)
end
+
+ def log_has_element_or_not(method, name, found, **kwargs)
+ msg = ["#{method} :#{name}"]
+ msg << %Q(with text "#{kwargs[:text]}") if kwargs[:text]
+ msg << "(wait: #{kwargs[:wait] || Capybara.default_max_wait_time})"
+ msg << "returned: #{found}"
+
+ log(msg.compact.join(' '))
+ end
end
end
end
diff --git a/qa/qa/support/waiter.rb b/qa/qa/support/waiter.rb
index 21a399b4a5f..fdcf2d7e157 100644
--- a/qa/qa/support/waiter.rb
+++ b/qa/qa/support/waiter.rb
@@ -3,9 +3,11 @@
module QA
module Support
module Waiter
+ DEFAULT_MAX_WAIT_TIME = 60
+
module_function
- def wait(max: 60, interval: 0.1)
+ def wait(max: DEFAULT_MAX_WAIT_TIME, interval: 0.1)
QA::Runtime::Logger.debug("with wait: max #{max}; interval #{interval}")
start = Time.now
diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb
index c5c48e77ade..3f752adbe6f 100644
--- a/qa/qa/tools/delete_subgroups.rb
+++ b/qa/qa/tools/delete_subgroups.rb
@@ -3,7 +3,7 @@
require_relative '../../qa'
# This script deletes all subgroups of a group specified by ENV['GROUP_NAME_OR_PATH']
-# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS
+# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# Optional environment variable: GROUP_NAME_OR_PATH (defaults to 'gitlab-qa-sandbox-group')
# Run `rake delete_subgroups`
@@ -14,9 +14,9 @@ module QA
def initialize
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
- raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
- @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN'])
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
end
def run
diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb
new file mode 100644
index 00000000000..de8cfa1aed9
--- /dev/null
+++ b/qa/qa/tools/generate_perf_testdata.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require 'securerandom'
+require 'faker'
+require 'yaml'
+require_relative '../../qa'
+# This script generates testdata for Performance Testing.
+# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
+# This job creates a urls.txt which contains a hash of all the URLs needed for Performance Testing
+# Run `rake generate_perf_testdata`
+
+module QA
+ module Tools
+ class GeneratePerfTestdata
+ include Support::Api
+
+ def initialize
+ raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
+
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
+ @group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}"
+ @project_name = "my-test-project-#{SecureRandom.hex(8)}"
+ @visibility = "public"
+ @urls = { host: ENV['GITLAB_ADDRESS'] }
+ end
+
+ def run
+ STDOUT.puts 'Running...'
+ group_id = create_group
+ create_project(group_id)
+ create_branch
+ add_new_file
+ methods_arr = [
+ method(:create_issues),
+ method(:create_labels),
+ method(:create_todos),
+ method(:create_merge_requests),
+ method(:create_issue_with_500_discussions),
+ method(:create_mr_with_large_files)
+ ]
+ threads_arr = []
+
+ methods_arr.each do |m|
+ threads_arr << Thread.new { m.call }
+ end
+
+ threads_arr.each(&:join)
+ STDOUT.puts "\nURLs: #{@urls}"
+ File.open("urls.yml", "w") { |file| file.puts @urls.stringify_keys.to_yaml }
+ STDOUT.puts "\nDone"
+ end
+
+ private
+
+ def create_group
+ group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}&visibility=#{@visibility}"
+ group = JSON.parse(group_search_response.body)
+ @urls[:group_page] = group["web_url"]
+ group["id"]
+ end
+
+ def create_project(group_id)
+ create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}&visibility=#{@visibility}"
+ @urls[:project_page] = JSON.parse(create_project_response.body)["web_url"]
+ end
+
+ def create_issues
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues").url, "title=issue#{i}&description=desc#{i}"
+ end
+ @urls[:issues_list_page] = @urls[:project_page] + "/issues"
+ STDOUT.puts "Created Issues"
+ end
+
+ def create_todos
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{i + 1}/todo").url, nil
+ end
+ @urls[:todos_page] = ENV['GITLAB_ADDRESS'] + "/dashboard/todos"
+ STDOUT.puts "Created todos"
+ end
+
+ def create_labels
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/labels").url,
+ "name=label#{i}&color=#{Faker::Color.hex_color}"
+ end
+ @urls[:labels_page] = @urls[:project_page] + "/labels"
+ STDOUT.puts "Created labels"
+ end
+
+ def create_merge_requests
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests").url, "source_branch=branch#{i}&target_branch=master&title=MR#{i}"
+ end
+ @urls[:mr_list_page] = @urls[:project_page] + "/merge_requests"
+ STDOUT.puts "Created MRs"
+ end
+
+ def add_new_file
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello.txt").url, "branch=master&commit_message=\"hello\"&content=\"my new content\""
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=branch#{i}&commit_message=\"hello\"&content=\"my new content\""
+ end
+ STDOUT.puts "Added Files"
+ end
+
+ def create_branch
+ 30.times do |i|
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, "branch=branch#{i}&ref=master"
+ end
+ STDOUT.puts "Created branches"
+ end
+
+ def create_issue_with_500_discussions
+ issue_id = 1
+ 500.times do
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}/discussions").url, "body=\"Let us discuss\""
+ end
+
+ labels_list = (0..15).map {|i| "label#{i}"}.join(',')
+ # Add description and labels
+ put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}").url, "description=#{Faker::Lorem.sentences(500).join(" ")}&labels=#{labels_list}"
+ @urls[:large_issue] = @urls[:project_page] + "/issues/#{issue_id}"
+ STDOUT.puts "Created Issue with 500 Discussions"
+ end
+
+ def create_mr_with_large_files
+ content_arr = []
+ 16.times do |i|
+ faker_line_arr = Faker::Lorem.sentences(1500)
+ content = faker_line_arr.join("\n\r")
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url,
+ "branch=master&commit_message=\"Add hello#{i}.txt\"&content=#{content}"
+ content_arr[i] = faker_line_arr
+ end
+
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url,
+ "branch=performance&ref=master"
+
+ 16.times do |i|
+ missed_line_array = content_arr[i].each_slice(2).map(&:first)
+ content = missed_line_array.join("\n\rIm new!:D \n\r ")
+ put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url,
+ "branch=performance&commit_message=\"Update hello#{i}.txt\"&content=#{content}"
+ end
+
+ create_mr_response = post Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests""").url,
+ "source_branch=performance&target_branch=master&title=Large_MR"
+
+ iid = JSON.parse(create_mr_response.body)["iid"]
+ diff_refs = JSON.parse(create_mr_response.body)["diff_refs"]
+
+ # Add discussions to diff tab and resolve a few!
+ should_resolve = false
+ 16.times do |i|
+ 1.upto(9) do |j|
+ create_diff_note(iid, i, j, diff_refs["head_sha"], diff_refs["start_sha"], diff_refs["base_sha"], "new_line")
+ create_diff_note_response = create_diff_note(iid, i, j, diff_refs["head_sha"], diff_refs["start_sha"], diff_refs["base_sha"], "old_line")
+
+ if should_resolve
+ discussion_id = JSON.parse(create_diff_note_response.body)["id"]
+ put Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions/#{discussion_id}""").url,
+ "resolved=true"
+ end
+
+ should_resolve ^= true
+ end
+ end
+
+ # Add discussions to main tab
+ 100.times do
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url,
+ "body=\"Let us discuss\""
+ end
+ @urls[:large_mr] = JSON.parse(create_mr_response.body)["web_url"]
+ STDOUT.puts "Created MR with 500 Discussions and 20 Very Large Files"
+ end
+
+ def create_diff_note(iid, file_count, line_count, head_sha, start_sha, base_sha, line_type)
+ post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url,
+ """body=\"Let us discuss\"&
+ position[position_type]=text&
+ position[new_path]=hello#{file_count}.txt&
+ position[old_path]=hello#{file_count}.txt&
+ position[#{line_type}]=#{line_count * 100}&
+ position[head_sha]=#{head_sha}&
+ position[start_sha]=#{start_sha}&
+ position[base_sha]=#{base_sha}"""
+ end
+ end
+ end
+end
diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb
index 7484b633bf6..8a74f52324d 100644
--- a/qa/qa/tools/revoke_all_personal_access_tokens.rb
+++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb
@@ -27,7 +27,7 @@ module QA
Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_access_tokens)
token_name = 'api-test-token'
diff --git a/qa/qa/vendor/github/page/login.rb b/qa/qa/vendor/github/page/login.rb
index 6d8f9aa7c12..120ba6e6c06 100644
--- a/qa/qa/vendor/github/page/login.rb
+++ b/qa/qa/vendor/github/page/login.rb
@@ -12,9 +12,7 @@ module QA
fill_in 'password', with: QA::Runtime::Env.github_password
click_on 'Sign in'
- unless has_no_text?("Authorize GitLab-OAuth")
- click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa')
- end
+ click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa')
end
end
end
diff --git a/qa/spec/git/location_spec.rb b/qa/spec/git/location_spec.rb
index aef906ee836..0c57291666f 100644
--- a/qa/spec/git/location_spec.rb
+++ b/qa/spec/git/location_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Git::Location do
describe '.new' do
context 'when URI starts with ssh://' do
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 62c81050bd9..5198eb6f58b 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Git::Repository do
include Helpers::StubENV
@@ -39,7 +41,7 @@ describe QA::Git::Repository do
describe '#clone' do
it 'is unable to resolve host' do
- expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
+ expect { repository.clone }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(128\) with the following output/)
end
end
@@ -49,7 +51,7 @@ describe QA::Git::Repository do
end
it 'fails to push changes' do
- expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
+ expect { repository.push_changes }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(1\) with the following output/)
end
end
@@ -114,6 +116,15 @@ describe QA::Git::Repository do
expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
.to eq("machine foo login user password foo\n")
end
+
+ it 'adds credentials with special characters' do
+ password = %q[!"#$%&')(*+,-./:;<=>?]
+ repository.username = 'user'
+ repository.password = password
+
+ expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
+ .to eq("machine foo login user password #{password}\n")
+ end
end
end
end
diff --git a/qa/spec/helpers/stub_env.rb b/qa/spec/helpers/stub_env.rb
index 62b4a1df787..8ad864dbec8 100644
--- a/qa/spec/helpers/stub_env.rb
+++ b/qa/spec/helpers/stub_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module Helpers
module StubENV
diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb
index 32a350f2154..9e3f143ea5b 100644
--- a/qa/spec/page/base_spec.rb
+++ b/qa/spec/page/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Page::Base do
describe 'page helpers' do
it 'exposes helpful page helpers' do
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index 8598c57ad34..f746fe06e40 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Page::Element do
describe '#selector' do
it 'transforms element name into QA-specific selector' do
@@ -48,4 +50,60 @@ describe QA::Page::Element do
expect(subject.matches?('some_name selector')).to be false
end
end
+
+ describe 'attributes' do
+ context 'element with no args' do
+ subject { described_class.new(:something) }
+
+ it 'defaults pattern to #selector' do
+ expect(subject.attributes[:pattern]).to eq 'qa-something'
+ expect(subject.attributes[:pattern]).to eq subject.selector
+ end
+
+ it 'is not required by default' do
+ expect(subject.required?).to be false
+ end
+ end
+
+ context 'element with a pattern' do
+ subject { described_class.new(:something, /link_to 'something'/) }
+
+ it 'has an attribute[pattern] of the pattern' do
+ expect(subject.attributes[:pattern]).to eq /link_to 'something'/
+ end
+
+ it 'is not required by default' do
+ expect(subject.required?).to be false
+ end
+ end
+
+ context 'element with requirement; no pattern' do
+ subject { described_class.new(:something, required: true) }
+
+ it 'has an attribute[pattern] of the selector' do
+ expect(subject.attributes[:pattern]).to eq 'qa-something'
+ expect(subject.attributes[:pattern]).to eq subject.selector
+ end
+
+ it 'is required' do
+ expect(subject.required?).to be true
+ end
+ end
+
+ context 'element with requirement and pattern' do
+ subject { described_class.new(:something, /link_to 'something_else_entirely'/, required: true) }
+
+ it 'has an attribute[pattern] of the passed pattern' do
+ expect(subject.attributes[:pattern]).to eq /link_to 'something_else_entirely'/
+ end
+
+ it 'is required' do
+ expect(subject.required?).to be true
+ end
+
+ it 'has a selector of the name' do
+ expect(subject.selector).to eq 'qa-something'
+ end
+ end
+ end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index a6e9601cee4..092c6a17c9c 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -62,13 +62,23 @@ describe QA::Support::Page::Logging do
.to output(/found :element/).to_stdout_from_any_process
end
- it 'logs find_element with text_filter' do
- expect { subject.find_element(:element, 'foo') }
- .to output(/finding :element with text_filter "foo"/).to_stdout_from_any_process
- expect { subject.find_element(:element, 'foo') }
+ it 'logs find_element with text' do
+ expect { subject.find_element(:element, text: 'foo') }
+ .to output(/finding :element with args {:text=>"foo"}/).to_stdout_from_any_process
+ expect { subject.find_element(:element, text: 'foo') }
.to output(/found :element/).to_stdout_from_any_process
end
+ it 'logs find_element with wait' do
+ expect { subject.find_element(:element, wait: 0) }
+ .to output(/finding :element with args {:wait=>0}/).to_stdout_from_any_process
+ end
+
+ it 'logs find_element with class' do
+ expect { subject.find_element(:element, class: 'active') }
+ .to output(/finding :element with args {:class=>\"active\"}/).to_stdout_from_any_process
+ end
+
it 'logs click_element' do
expect { subject.click_element(:element) }
.to output(/clicking :element/).to_stdout_from_any_process
@@ -81,14 +91,26 @@ describe QA::Support::Page::Logging do
it 'logs has_element?' do
expect { subject.has_element?(:element) }
- .to output(/has_element\? :element returned true/).to_stdout_from_any_process
+ .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ end
+
+ it 'logs has_element? with text' do
+ expect { subject.has_element?(:element, text: "some text") }
+ .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element?' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element) }
- .to output(/has_no_element\? :element returned true/).to_stdout_from_any_process
+ .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ end
+
+ it 'logs has_no_element? with text' do
+ allow(page).to receive(:has_no_css?).and_return(true)
+
+ expect { subject.has_no_element?(:element, text: "more text") }
+ .to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
end
it 'logs has_text?' do
diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb
index 0ae6e66d767..bdff96e267c 100644
--- a/qa/spec/page/validator_spec.rb
+++ b/qa/spec/page/validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Page::Validator do
describe '#constants' do
subject do
diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb
index d7b3ccd316d..3cb64dcd9c2 100644
--- a/qa/spec/page/view_spec.rb
+++ b/qa/spec/page/view_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Page::View do
let(:element) do
double('element', name: :something, pattern: /some element/)
diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb
index af43facc73d..cf19b52700b 100644
--- a/qa/spec/runtime/api/client_spec.rb
+++ b/qa/spec/runtime/api/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::API::Client do
include Helpers::StubENV
diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb
index 08233e3c1d6..a20f1cf8559 100644
--- a/qa/spec/runtime/api/request_spec.rb
+++ b/qa/spec/runtime/api/request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::API::Request do
let(:client) { QA::Runtime::API::Client.new('http://example.com') }
let(:request) { described_class.new(client, '/users') }
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index fc51f45c3a1..2560695ef2e 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -90,13 +90,13 @@ describe QA::Runtime::Env do
described_class.instance_variable_set(:@personal_access_token, nil)
end
- context 'when PERSONAL_ACCESS_TOKEN is set' do
+ context 'when GITLAB_QA_ACCESS_TOKEN is set' do
before do
- stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ stub_env('GITLAB_QA_ACCESS_TOKEN', 'a_token_too')
end
it 'returns specified token from env' do
- expect(described_class.personal_access_token).to eq 'a_token'
+ expect(described_class.personal_access_token).to eq 'a_token_too'
end
end
@@ -168,6 +168,30 @@ describe QA::Runtime::Env do
end
end
+ describe '.knapsack?' do
+ it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do
+ stub_env('KNAPSACK_GENERATE_REPORT', 'true')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_REPORT_PATH is defined' do
+ stub_env('KNAPSACK_REPORT_PATH', '/a/path')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do
+ stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do
+ expect(described_class.knapsack?).to be_falsey
+ end
+ end
+
describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
new file mode 100644
index 00000000000..192299b7857
--- /dev/null
+++ b/qa/spec/runtime/feature_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Feature do
+ let(:api_client) { double('QA::Runtime::API::Client') }
+ let(:request) { Struct.new(:url).new('http://api') }
+ let(:response) { Struct.new(:code).new(201) }
+
+ before do
+ allow(described_class).to receive(:api_client).and_return(api_client)
+ end
+
+ describe '.enable' do
+ it 'enables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: true })
+ .and_return(response)
+
+ subject.enable('a-flag')
+ end
+ end
+
+ describe '.disable' do
+ it 'disables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: false })
+ .and_return(response)
+
+ subject.disable('a-flag')
+ end
+ end
+end
diff --git a/qa/spec/runtime/key/ecdsa_spec.rb b/qa/spec/runtime/key/ecdsa_spec.rb
index 8951e82b9bb..3f9718e62c5 100644
--- a/qa/spec/runtime/key/ecdsa_spec.rb
+++ b/qa/spec/runtime/key/ecdsa_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Key::ECDSA do
describe '#public_key' do
[256, 384, 521].each do |bits|
diff --git a/qa/spec/runtime/key/ed25519_spec.rb b/qa/spec/runtime/key/ed25519_spec.rb
index 4844e7affdf..08f232260af 100644
--- a/qa/spec/runtime/key/ed25519_spec.rb
+++ b/qa/spec/runtime/key/ed25519_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Key::ED25519 do
describe '#public_key' do
subject { described_class.new.public_key }
diff --git a/qa/spec/runtime/key/rsa_spec.rb b/qa/spec/runtime/key/rsa_spec.rb
index fbcc7ffdcb4..fcb52f541bf 100644
--- a/qa/spec/runtime/key/rsa_spec.rb
+++ b/qa/spec/runtime/key/rsa_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Key::RSA do
describe '#public_key' do
subject { described_class.new.public_key }
diff --git a/qa/spec/runtime/release_spec.rb b/qa/spec/runtime/release_spec.rb
index e6b5a8dc315..b5a7dd5269d 100644
--- a/qa/spec/runtime/release_spec.rb
+++ b/qa/spec/runtime/release_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Release do
context 'when release version has extension strategy' do
let(:strategy) { spy('strategy') }
diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb
index 7009192bcc0..30ada4529ed 100644
--- a/qa/spec/runtime/scenario_spec.rb
+++ b/qa/spec/runtime/scenario_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Runtime::Scenario do
subject do
Module.new.extend(described_class)
@@ -13,6 +15,14 @@ describe QA::Runtime::Scenario do
.to eq(my_attribute: 'some-value', another_attribute: 'another-value')
end
+ it 'replaces an existing attribute' do
+ subject.define(:my_attribute, 'some-value')
+ subject.define(:my_attribute, 'another-value')
+
+ expect(subject.my_attribute).to eq 'another-value'
+ expect(subject.attributes).to eq(my_attribute: 'another-value')
+ end
+
it 'raises error when attribute is not known' do
expect { subject.invalid_accessor }
.to raise_error ArgumentError, /invalid_accessor/
diff --git a/qa/spec/scenario/actable_spec.rb b/qa/spec/scenario/actable_spec.rb
index 422763910e4..589d0c61993 100644
--- a/qa/spec/scenario/actable_spec.rb
+++ b/qa/spec/scenario/actable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Actable do
subject do
Class.new do
diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb
index 273aac7677e..e8accb45518 100644
--- a/qa/spec/scenario/bootable_spec.rb
+++ b/qa/spec/scenario/bootable_spec.rb
@@ -1,17 +1,26 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Bootable do
subject do
Class.new(QA::Scenario::Template)
.include(described_class)
end
+ before do
+ allow(subject).to receive(:options).and_return([])
+ allow(QA::Runtime::Scenario).to receive(:attributes).and_return({})
+ end
+
it 'makes it possible to define the scenario attribute' do
subject.class_eval do
attribute :something, '--something SOMETHING', 'Some attribute'
attribute :another, '--another ANOTHER', 'Some other attribute'
end
+ # If we run just this test from the command line it fails unless
+ # we include the command line args that we use to select this test.
expect(subject).to receive(:perform)
- .with(something: 'test', another: 'other')
+ .with({ something: 'test', another: 'other' })
subject.launch!(%w[--another other --something test])
end
diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb
new file mode 100644
index 00000000000..f97fc22daf9
--- /dev/null
+++ b/qa/spec/scenario/template_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+describe QA::Scenario::Template do
+ let(:feature) { spy('Runtime::Feature') }
+ let(:release) { spy('Runtime::Release') }
+
+ before do
+ stub_const('QA::Runtime::Release', release)
+ stub_const('QA::Runtime::Feature', feature)
+ allow(QA::Specs::Runner).to receive(:perform)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
+ end
+
+ it 'allows a feature to be enabled' do
+ subject.perform({ enable_feature: 'a-feature' })
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ end
+
+ it 'ensures an enabled feature is disabled afterwards' do
+ allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
+
+ expect { subject.perform({ enable_feature: 'a-feature' }) }.to raise_error('failed test')
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ expect(feature).to have_received(:disable).with('a-feature')
+ end
+end
diff --git a/qa/spec/scenario/test/instance/all_spec.rb b/qa/spec/scenario/test/instance/all_spec.rb
index 9311d1d8199..8acd56914c5 100644
--- a/qa/spec/scenario/test/instance/all_spec.rb
+++ b/qa/spec/scenario/test/instance/all_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Test::Instance::All do
it_behaves_like 'a QA scenario class'
end
diff --git a/qa/spec/scenario/test/instance/smoke_spec.rb b/qa/spec/scenario/test/instance/smoke_spec.rb
index b5db9783af3..6cc71699be9 100644
--- a/qa/spec/scenario/test/instance/smoke_spec.rb
+++ b/qa/spec/scenario/test/instance/smoke_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Test::Instance::Smoke do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:smoke] }
diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb
index c2aeb1ded1d..6112ba7c694 100644
--- a/qa/spec/scenario/test/integration/github_spec.rb
+++ b/qa/spec/scenario/test/integration/github_spec.rb
@@ -12,7 +12,7 @@ describe QA::Scenario::Test::Integration::Github do
let(:tags) { [:github] }
it 'requires a GitHub access token' do
- subject.perform('gitlab_address')
+ subject.perform(args)
expect(env).to have_received(:require_github_access_token!)
end
diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb
index 59caf2ba2cd..4e75e72f4d2 100644
--- a/qa/spec/scenario/test/integration/mattermost_spec.rb
+++ b/qa/spec/scenario/test/integration/mattermost_spec.rb
@@ -4,14 +4,21 @@ describe QA::Scenario::Test::Integration::Mattermost do
context '#perform' do
it_behaves_like 'a QA scenario class' do
let(:args) { %w[gitlab_address mattermost_address] }
+ let(:args) do
+ {
+ gitlab_address: 'http://gitlab_address',
+ mattermost_address: 'http://mattermost_address'
+ }
+ end
+ let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] }
let(:tags) { [:mattermost] }
let(:options) { ['path1']}
it 'requires a GitHub access token' do
- subject.perform(*args)
+ subject.perform(args)
expect(attributes).to have_received(:define)
- .with(:mattermost_address, 'mattermost_address')
+ .with(:mattermost_address, 'http://mattermost_address')
end
end
end
diff --git a/qa/spec/scenario/test/sanity/framework_spec.rb b/qa/spec/scenario/test/sanity/framework_spec.rb
index 44ac780556e..a63c59e2995 100644
--- a/qa/spec/scenario/test/sanity/framework_spec.rb
+++ b/qa/spec/scenario/test/sanity/framework_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Test::Sanity::Framework do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:framework] }
diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb
index 45d21d54955..e18babaed63 100644
--- a/qa/spec/scenario/test/sanity/selectors_spec.rb
+++ b/qa/spec/scenario/test/sanity/selectors_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe QA::Scenario::Test::Sanity::Selectors do
let(:validator) { spy('validator') }
diff --git a/qa/spec/shared_examples/scenario_shared_examples.rb b/qa/spec/shared_examples/scenario_shared_examples.rb
index 5fd55d7d96b..697e6cb39c8 100644
--- a/qa/spec/shared_examples/scenario_shared_examples.rb
+++ b/qa/spec/shared_examples/scenario_shared_examples.rb
@@ -2,19 +2,23 @@
shared_examples 'a QA scenario class' do
let(:attributes) { spy('Runtime::Scenario') }
- let(:release) { spy('Runtime::Release') }
let(:runner) { spy('Specs::Runner') }
+ let(:release) { spy('Runtime::Release') }
+ let(:feature) { spy('Runtime::Feature') }
- let(:args) { ['gitlab_address'] }
+ let(:args) { { gitlab_address: 'http://gitlab_address' } }
+ let(:named_options) { %w[--address http://gitlab_address] }
let(:tags) { [] }
let(:options) { %w[path1 path2] }
before do
+ stub_const('QA::Specs::Runner', runner)
stub_const('QA::Runtime::Release', release)
stub_const('QA::Runtime::Scenario', attributes)
- stub_const('QA::Specs::Runner', runner)
+ stub_const('QA::Runtime::Feature', feature)
allow(runner).to receive(:perform).and_yield(runner)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
end
it 'responds to perform' do
@@ -22,28 +26,48 @@ shared_examples 'a QA scenario class' do
end
it 'sets an address of the subject' do
- subject.perform(*args)
+ subject.perform(args)
- expect(attributes).to have_received(:define).with(:gitlab_address, 'gitlab_address')
+ expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once)
end
it 'performs before hooks' do
- subject.perform(*args)
+ subject.perform(args)
expect(release).to have_received(:perform_before_hooks)
end
it 'sets tags on runner' do
- subject.perform(*args)
+ subject.perform(args)
expect(runner).to have_received(:tags=).with(tags)
end
context 'specifying RSpec options' do
it 'sets options on runner' do
- subject.perform(*args, *options)
+ subject.perform(args, *options)
expect(runner).to have_received(:options=).with(options)
end
end
+
+ context 'with named command-line options' do
+ it 'converts options to attributes' do
+ described_class.launch!(named_options)
+
+ args do |k, v|
+ expect(attributes).to have_received(:define).with(k, v)
+ end
+ end
+
+ it 'raises an error if the option is invalid' do
+ expect { described_class.launch!(['--foo']) }.to raise_error(OptionParser::InvalidOption)
+ end
+
+ it 'passes on options after --' do
+ expect(described_class).to receive(:perform).with(attributes, *%w[--tag quarantine])
+
+ described_class.launch!(named_options.push(*%w[-- --tag quarantine]))
+ end
+ end
end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index cbdd6e881b1..f25dbf3a8ab 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -1,21 +1,22 @@
+# frozen_string_literal: true
+
require_relative '../qa'
require 'rspec/retry'
+if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
+ require 'knapsack'
+ Knapsack::Adapters::RSpecAdapter.bind
+end
+
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
RSpec.configure do |config|
- config.before(:context) do
- if self.class.metadata.keys.include?(:quarantine)
- skip_or_run_quarantined_tests(self.class.metadata.keys, config.inclusion_filter.rules.keys)
- end
- end
+ QA::Specs::Helpers::Quarantine.configure_rspec
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
-
- skip_or_run_quarantined_tests(example.metadata.keys, config.inclusion_filter.rules.keys)
end
config.expect_with :rspec do |expectations|
@@ -39,47 +40,10 @@ RSpec.configure do |config|
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
- config.around do |example|
- retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 3
- example.run_with_retry retry: retry_times
- end
-end
-
-# Skip tests in quarantine unless we explicitly focus on them.
-# Skip the entire context if a context is tagged. This avoids running before
-# blocks unnecessarily.
-# If quarantine is focussed, skip tests/contexts that have other metadata
-# unless they're also focussed. This lets us run quarantined tests in a
-# particular category without running tests in other categories.
-# E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
-# 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
-# using `--tag quarantine --tag smoke`, without this check we'd end up
-# running that ldap test as well.
-# We could use an exclusion filter, but this way the test report will list
-# the quarantined tests when they're not run so that we're aware of them
-def skip_or_run_quarantined_tests(metadata_keys, filter_keys)
- included_filters = filters_other_than_quarantine(filter_keys)
-
- if filter_keys.include?(:quarantine)
- skip("Only running tests tagged with :quarantine and any of #{included_filters}") unless quarantine_and_optional_other_tag?(metadata_keys, included_filters)
- else
- skip('In quarantine') if metadata_keys.include?(:quarantine)
+ if ENV['CI']
+ config.around do |example|
+ retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 2
+ example.run_with_retry retry: retry_times
+ end
end
end
-
-def filters_other_than_quarantine(filter_keys)
- filter_keys.reject { |key| key == :quarantine }
-end
-
-# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
-#
-# Returns true if
-# - the metadata includes the quarantine tag
-# - and the metadata and inclusion filter both have any other tag
-# - or no other tags are in the inclusion filter
-def quarantine_and_optional_other_tag?(metadata_keys, included_filters)
- return false unless metadata_keys.include? :quarantine
- return true if included_filters.empty?
-
- included_filters.any? { |key| metadata_keys.include? key }
-end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
deleted file mode 100644
index 27ec1ec80fe..00000000000
--- a/qa/spec/spec_helper_spec.rb
+++ /dev/null
@@ -1,355 +0,0 @@
-# frozen_string_literal: true
-
-describe 'rspec config tests' do
- let(:group) do
- RSpec.describe do
- shared_examples 'passing tests' do
- example 'not in quarantine' do
- end
- example 'in quarantine', :quarantine do
- end
- end
-
- context 'default' do
- it_behaves_like 'passing tests'
- end
-
- context 'foo', :foo do
- it_behaves_like 'passing tests'
- end
-
- context 'quarantine', :quarantine do
- it_behaves_like 'passing tests'
- end
-
- context 'bar quarantine', :bar, :quarantine do
- it_behaves_like 'passing tests'
- end
- end
- end
-
- let(:group_2) do
- RSpec.describe do
- before(:all) do
- @expectations = [1, 2, 3]
- end
-
- example 'not in quarantine' do
- expect(@expectations.shift).to be(3)
- end
-
- example 'in quarantine', :quarantine do
- expect(@expectations.shift).to be(3)
- end
- end
- end
-
- context 'with no tags focussed' do
- before do
- group.run
- end
-
- context 'in a context tagged :foo' do
- it 'skips tests in quarantine' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in an untagged context' do
- it 'skips tests in quarantine' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
- end
-
- context 'with :quarantine focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :quarantine
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'runs all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
- end
-
- context 'with a non-quarantine tag (:foo) focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :foo
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'runs no tests' do
- context = group.children.find { |c| c.description == "default" }
- expect(context.descendant_filtered_examples.count).to eq(0)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'skips quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'runs no tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- expect(context.descendant_filtered_examples.count).to eq(0)
- end
- end
- end
-
- context 'with :quarantine and a non-quarantine tag (:foo) focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = { quarantine: true, foo: true }
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'ignores untagged tests and skips tests even if in quarantine' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :bar and :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
- end
-
- context 'with :quarantine and multiple non-quarantine tags focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = { bar: true, foo: true, quarantine: true }
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- end
- end
-
- context 'in a context tagged :bar and :quarantine' do
- it 'runs all tests' do
- context = group.children.find { |c| c.description == "bar quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
- end
-
- context 'rspec retry' do
- context 'in an untagged context' do
- before do
- group_2.run
- end
-
- it 'should run example :retry times' do
- examples = group_2.descendant_filtered_examples
- ex = examples.find { |e| e.description == 'not in quarantine' }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'with :quarantine focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :quarantine
- end
- group_2.run
- end
-
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- it 'should run example once only' do
- examples = group_2.descendant_filtered_examples
- ex = examples.find { |e| e.description == 'in quarantine' }
- expect(ex.execution_result.status).to eq(:failed)
- end
- end
- end
-end
diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb
new file mode 100644
index 00000000000..78beda39b5e
--- /dev/null
+++ b/qa/spec/specs/helpers/quarantine_spec.rb
@@ -0,0 +1,271 @@
+# frozen_string_literal: true
+
+require 'rspec/core/sandbox'
+
+# We need a reporter for internal tests that's different from the reporter for
+# external tests otherwise the results will be mixed up. We don't care about
+# most reporting, but we do want to know if a test fails
+class RaiseOnFailuresReporter < RSpec::Core::NullReporter
+ def self.example_failed(example)
+ raise example.exception
+ end
+end
+
+# We use an example group wrapper to prevent the state of internal tests
+# expanding into the global state
+# See: https://github.com/rspec/rspec-core/issues/2603
+def describe_successfully(*args, &describe_body)
+ example_group = RSpec.describe(*args, &describe_body)
+ ran_successfully = example_group.run RaiseOnFailuresReporter
+ expect(ran_successfully).to eq true
+ example_group
+end
+
+RSpec.configure do |c|
+ c.around do |ex|
+ RSpec::Core::Sandbox.sandboxed do |config|
+ # If there is an example-within-an-example, we want to make sure the inner example
+ # does not get a reference to the outer example (the real spec) if it calls
+ # something like `pending`
+ config.before(:context) { RSpec.current_example = nil }
+
+ config.color_mode = :off
+
+ # Load airborne again to avoid "undefined method `match_expected_default?'" errors
+ # that happen because a hook calls a method added via a custom RSpec setting
+ # that is removed when the RSpec configuration is sandboxed.
+ # If this needs to be changed (e.g., to load other libraries as well), see
+ # this discussion for alternative solutions:
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25223#note_143392053
+ load 'airborne.rb'
+
+ ex.run
+ end
+ end
+end
+
+describe QA::Specs::Helpers::Quarantine do
+ describe '.skip_or_run_quarantined_contexts' do
+ context 'with no tag focused' do
+ before do
+ described_class.configure_rspec
+ end
+
+ it 'skips before hooks of quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully('quarantine', :quarantine) do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq []
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:pending)
+ expect(group.descendant_filtered_examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+
+ it 'executes before hooks of non-quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq [:before_all, :before]
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'with :quarantine focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :quarantine
+ end
+ end
+
+ it 'executes before hooks of quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully('quarantine', :quarantine) do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq [:before_all, :before]
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:passed)
+ end
+
+ it 'skips before hooks of non-quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq []
+ expect(group.descendant_filtered_examples.first).to be_nil
+ end
+ end
+ end
+
+ describe '.skip_or_run_quarantined_tests' do
+ context 'with no tag focused' do
+ before do
+ described_class.configure_rspec
+ end
+
+ it 'skips quarantined tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+
+ it 'executes non-quarantined tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'with :quarantine focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :quarantine
+ end
+ end
+
+ it 'executes quarantined tests' do
+ group = describe_successfully do
+ it('passes', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:passed)
+ end
+
+ it 'ignores non-quarantined tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+ end
+
+ context 'with a non-quarantine tag focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :foo
+ end
+ end
+
+ it 'ignores non-quarantined non-focused tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'executes non-quarantined focused tests' do
+ group = describe_successfully do
+ it('passes', :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:passed)
+ end
+
+ it 'ignores quarantined tests' do
+ group = describe_successfully do
+ it('is ignored', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'skips quarantined focused tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine, :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+ end
+
+ context 'with :quarantine and non-quarantine tags focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :foo, :bar, :quarantine
+ end
+ end
+
+ it 'ignores non-quarantined non-focused tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'skips non-quarantined focused tests' do
+ group = describe_successfully do
+ it('is pending', :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
+ end
+
+ it 'skips quarantined non-focused tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ end
+
+ it 'executes quarantined focused tests' do
+ group = describe_successfully do
+ it('passes', :quarantine, :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:passed)
+ end
+ end
+ end
+end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 741821ddf8c..5c86c102105 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -69,7 +69,7 @@ describe QA::Specs::Runner do
subject { described_class.new }
- it 'it includes default args and excludes the skip_signup_disabled tag' do
+ it 'includes default args and excludes the skip_signup_disabled tag' do
expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
@@ -83,7 +83,7 @@ describe QA::Specs::Runner do
subject { described_class.new }
- it 'it includes default args and excludes the requires_git_protocol_v2 tag' do
+ it 'includes default args and excludes the requires_git_protocol_v2 tag' do
expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
diff --git a/rubocop/cop/active_record_association_reload.rb b/rubocop/cop/active_record_association_reload.rb
new file mode 100644
index 00000000000..dc241cab7d0
--- /dev/null
+++ b/rubocop/cop/active_record_association_reload.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ # Cop that blacklists the use of `reload`.
+ class ActiveRecordAssociationReload < RuboCop::Cop::Cop
+ MSG = 'Use reset instead of reload. ' \
+ 'For more details check the https://gitlab.com/gitlab-org/gitlab-ce/issues/60218.'
+
+ def_node_matcher :reload?, <<~PATTERN
+ (send _ :reload ...)
+ PATTERN
+
+ def on_send(node)
+ return unless reload?(node)
+
+ add_offense(node, location: :selector)
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/code_reuse/active_record.rb b/rubocop/cop/code_reuse/active_record.rb
index 2be8f7c11aa..1e650abfceb 100644
--- a/rubocop/cop/code_reuse/active_record.rb
+++ b/rubocop/cop/code_reuse/active_record.rb
@@ -9,7 +9,8 @@ module RuboCop
class ActiveRecord < RuboCop::Cop::Cop
include CodeReuseHelpers
- MSG = 'This method can only be used inside an ActiveRecord model'
+ MSG = 'This method can only be used inside an ActiveRecord model: ' \
+ 'https://gitlab.com/gitlab-org/gitlab-ce/issues/49653'
# Various methods from ActiveRecord::Querying that are blacklisted. We
# exclude some generic ones such as `any?` and `first`, as these may
diff --git a/rubocop/cop/gitlab/finder_with_find_by.rb b/rubocop/cop/gitlab/finder_with_find_by.rb
index f45a37ddc06..764a5073143 100644
--- a/rubocop/cop/gitlab/finder_with_find_by.rb
+++ b/rubocop/cop/gitlab/finder_with_find_by.rb
@@ -2,7 +2,7 @@ module RuboCop
module Cop
module Gitlab
class FinderWithFindBy < RuboCop::Cop::Cop
- FIND_PATTERN = /\Afind(_by\!?)?\z/
+ FIND_PATTERN = /\Afind(_by\!?)?\z/.freeze
ALLOWED_MODULES = ['FinderMethods'].freeze
def message(used_method)
@@ -36,7 +36,7 @@ module RuboCop
def find_on_execute?(node)
chained_on_node = node.descendants.first
node.method_name.to_s =~ FIND_PATTERN &&
- chained_on_node&.method_name == :execute
+ chained_on_node.is_a?(RuboCop::AST::SendNode) && chained_on_node.method_name == :execute
end
def allowed_module?(node)
diff --git a/rubocop/cop/include_action_view_context.rb b/rubocop/cop/include_action_view_context.rb
new file mode 100644
index 00000000000..14662a33e95
--- /dev/null
+++ b/rubocop/cop/include_action_view_context.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative '../spec_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that makes sure workers include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`.
+ class IncludeActionViewContext < RuboCop::Cop::Cop
+ include SpecHelpers
+
+ MSG = 'Include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`, for Rails 5.'.freeze
+
+ def_node_matcher :includes_action_view_context?, <<~PATTERN
+ (send nil? :include (const (const nil? :ActionView) :Context))
+ PATTERN
+
+ def on_send(node)
+ return if in_spec?(node)
+ return unless includes_action_view_context?(node)
+
+ add_offense(node.arguments.first, location: :expression)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(node.source_range, '::Gitlab::ActionViewOutput::Context')
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb
index 5461abc5ee0..b1c43393f6a 100644
--- a/rubocop/cop/migration/update_column_in_batches.rb
+++ b/rubocop/cop/migration/update_column_in_batches.rb
@@ -28,7 +28,7 @@ module RuboCop
source_name = node.location.expression.source_buffer.name
path = Pathname.new(source_name).relative_path_from(rails_root)
dirname = File.dirname(path)
- .sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations')
+ .sub(%r{db/(migrate|post_migrate)}, 'spec/migrations')
filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '')
File.join(dirname, "#{filename}_spec.rb")
diff --git a/rubocop/cop/qa/element_with_pattern.rb b/rubocop/cop/qa/element_with_pattern.rb
index 9d80946f1ba..d14eeaaeaf3 100644
--- a/rubocop/cop/qa/element_with_pattern.rb
+++ b/rubocop/cop/qa/element_with_pattern.rb
@@ -1,18 +1,21 @@
+# frozen_string_literal: true
+
require_relative '../../qa_helpers'
module RuboCop
module Cop
module QA
- # This cop checks for the usage of factories in migration specs
+ # This cop checks for the usage of patterns in QA elements
#
# @example
#
# # bad
- # let(:user) { create(:user) }
+ # element :some_element, "link_to 'something'"
+ # element :some_element, /link_to 'something'/
#
# # good
- # let(:users) { table(:users) }
- # let(:user) { users.create!(name: 'User 1', username: 'user1') }
+ # element :some_element
+ # element :some_element, required: true
class ElementWithPattern < RuboCop::Cop::Cop
include QAHelpers
@@ -22,10 +25,13 @@ module RuboCop
return unless in_qa_file?(node)
return unless method_name(node).to_s == 'element'
- element_name, pattern = node.arguments
- return unless pattern
+ element_name, *args = node.arguments
+
+ return if args.first.nil?
- add_offense(node, location: pattern.source_range, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ args.first.each_node(:str) do |arg|
+ add_offense(arg, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ end
end
private
diff --git a/rubocop/qa_helpers.rb b/rubocop/qa_helpers.rb
index f4adf7f4e9f..95875d64727 100644
--- a/rubocop/qa_helpers.rb
+++ b/rubocop/qa_helpers.rb
@@ -5,7 +5,7 @@ module RuboCop
def in_qa_file?(node)
path = node.location.expression.source_buffer.name
- path.start_with?(File.join(Dir.pwd, 'qa'))
+ path.start_with?(File.join(RuboCop::PathUtil.pwd, 'qa'))
end
end
end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 3e33419eb2e..e2a19978839 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,11 +1,12 @@
-# rubocop:disable Naming/FileName
require_relative 'cop/gitlab/module_with_instance_variables'
require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/gitlab/httparty'
require_relative 'cop/gitlab/finder_with_find_by'
require_relative 'cop/gitlab/union'
+require_relative 'cop/include_action_view_context'
require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/safe_params'
+require_relative 'cop/active_record_association_reload'
require_relative 'cop/avoid_return_from_blocks'
require_relative 'cop/avoid_break_from_strong_memoize'
require_relative 'cop/avoid_route_redirect_leading_slash'
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index 63c1b975a65..ecd77c4351d 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -6,14 +6,16 @@ module RuboCop
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
path = node.location.expression.source_buffer.name
+ pwd = RuboCop::PathUtil.pwd
!SPEC_HELPERS.include?(File.basename(path)) &&
- path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
+ path.start_with?(File.join(pwd, 'spec'), File.join(pwd, 'ee', 'spec'))
end
def migration_directories
@migration_directories ||= MIGRATION_SPEC_DIRECTORIES.map do |dir|
- [File.join(Dir.pwd, dir), File.join(Dir.pwd, 'ee', dir)]
+ pwd = RuboCop::PathUtil.pwd
+ [File.join(pwd, dir), File.join(pwd, 'ee', dir)]
end.flatten
end
diff --git a/scripts/build_assets_image b/scripts/build_assets_image
index 9afada244c8..25b6060b6c4 100755
--- a/scripts/build_assets_image
+++ b/scripts/build_assets_image
@@ -7,14 +7,11 @@ then
fi
# Generate the image name based on the project this is being run in
-ASSETS_IMAGE_NAME=$(echo ${CI_PROJECT_NAME} |
- awk '{
- split($1, p, "-");
- interim = sprintf("%s-assets-%s", p[1], p[2]);
- sub(/-$/, "", interim);
- print interim
- }'
-)
+ASSETS_IMAGE_NAME="gitlab-assets-ce"
+if [[ "${CI_PROJECT_NAME}" == "gitlab-ee" ]]
+then
+ ASSETS_IMAGE_NAME="gitlab-assets-ee"
+fi
ASSETS_IMAGE_PATH=${CI_REGISTRY}/${CI_PROJECT_PATH}/${ASSETS_IMAGE_NAME}
@@ -27,3 +24,9 @@ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG}
docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_SHA}
+# Also tag the image with GitLab version, if running on a tag pipeline, so
+# other projects can simply use that instead of computing the slug.
+if [ -n "$CI_COMMIT_TAG" ]; then
+ docker tag ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG} ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME}
+ docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME}
+fi
diff --git a/scripts/clean-old-cached-assets b/scripts/clean-old-cached-assets
index 7a3a62a477a..8bdd3a9cdb6 100755
--- a/scripts/clean-old-cached-assets
+++ b/scripts/clean-old-cached-assets
@@ -1,6 +1,6 @@
#!/bin/bash
-# Clean up cached files that are older than 1 week
-find tmp/cache/assets/sprockets/ -type f -mtime +7 -execdir rm -- "{}" \;
+# Clean up cached files that are older than 4 days
+find tmp/cache/assets/sprockets/ -type f -mtime +4 -execdir rm -- "{}" \;
du -d 0 -h tmp/cache/assets/sprockets | cut -f1 | xargs -I % echo "tmp/cache/assets/sprockets/ is currently %"
diff --git a/scripts/frontend/postinstall.js b/scripts/frontend/postinstall.js
index 682039a41b3..94977e459e3 100644
--- a/scripts/frontend/postinstall.js
+++ b/scripts/frontend/postinstall.js
@@ -13,7 +13,7 @@ if (process.platform === 'darwin') {
ensure that it is supported by the fsevents library.
You can try installing again with \`${chalk.cyan('yarn install --force')}\`
- `)
+ `),
);
process.exit(1);
}
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index ffb09ea9779..bf0e98da139 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -32,7 +32,7 @@ let globDir = process.argv[3] || '';
if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/';
console.log(
- `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`
+ `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`,
);
const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`);
@@ -105,7 +105,7 @@ Promise.all(matchedFiles.map(checkFileWithPrettierConfig))
.then(() => {
const failAction = shouldSave ? 'fixed' : 'failed';
console.log(
- `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`
+ `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`,
);
if (didWarn) process.exit(1);
diff --git a/scripts/frontend/stylelint/stylelint-duplicate-selectors.js b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js
new file mode 100644
index 00000000000..4b46ee21a7a
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js
@@ -0,0 +1,23 @@
+const stylelint = require('stylelint');
+const utils = require('./stylelint-utils');
+const ruleName = 'stylelint-gitlab/duplicate-selectors';
+
+const messages = stylelint.utils.ruleMessages(ruleName, {
+ expected: (selector1, selector2) => {
+ return `"${selector1}" and "${selector2}" have the same properties.`;
+ },
+});
+
+module.exports = stylelint.createPlugin(ruleName, function(enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ return function(root, result) {
+ const selectorGroups = {};
+ utils.createPropertiesHashmap(root, result, ruleName, messages, selectorGroups, true);
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint-utility-classes.js b/scripts/frontend/stylelint/stylelint-utility-classes.js
new file mode 100644
index 00000000000..8a1cfdbf302
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utility-classes.js
@@ -0,0 +1,24 @@
+const stylelint = require('stylelint');
+const utils = require('./stylelint-utils');
+const utilityClasses = require('./utility-classes-map.js');
+
+const ruleName = 'stylelint-gitlab/utility-classes';
+
+const messages = stylelint.utils.ruleMessages(ruleName, {
+ expected: (selector1, selector2) => {
+ return `"${selector1}" has the same properties as our BS4 utility class "${selector2}" so please use that instead.`;
+ },
+});
+
+module.exports = stylelint.createPlugin(ruleName, function(enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ return function(root, result) {
+ utils.createPropertiesHashmap(root, result, ruleName, messages, utilityClasses, false);
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint-utility-map.js b/scripts/frontend/stylelint/stylelint-utility-map.js
new file mode 100644
index 00000000000..941198e82a4
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utility-map.js
@@ -0,0 +1,54 @@
+const sass = require('node-sass');
+const postcss = require('postcss');
+const fs = require('fs');
+const path = require('path');
+const prettier = require('prettier');
+
+const utils = require('./stylelint-utils');
+const ROOT_PATH = path.resolve(__dirname, '../../..');
+const hashMapPath = path.resolve(__dirname, './utility-classes-map.js');
+
+//
+// This creates a JS based hash map (saved in utility-classes-map.js) of the different values in the utility classes
+//
+sass.render(
+ {
+ data: `
+ @import './functions';
+ @import './variables';
+ @import './mixins';
+ @import './utilities';
+ `,
+ includePaths: [path.resolve(ROOT_PATH, 'node_modules/bootstrap/scss')],
+ },
+ (err, result) => {
+ if (err) console.error('Error ', err);
+
+ const cssResult = result.css.toString();
+
+ // We just use postcss to create a CSS tree
+ postcss([])
+ .process(cssResult, {
+ // This suppresses a postcss warning
+ from: undefined,
+ })
+ .then(result => {
+ const selectorGroups = {};
+ utils.createPropertiesHashmap(result.root, result, null, null, selectorGroups, true);
+
+ const prettierOptions = prettier.resolveConfig.sync(hashMapPath);
+ const prettyHashmap = prettier.format(
+ `module.exports = ${JSON.stringify(selectorGroups)};`,
+ prettierOptions,
+ );
+
+ fs.writeFile(hashMapPath, prettyHashmap, function(err) {
+ if (err) {
+ return console.log(err);
+ }
+
+ console.log('The file was saved!');
+ });
+ });
+ },
+);
diff --git a/scripts/frontend/stylelint/stylelint-utils.js b/scripts/frontend/stylelint/stylelint-utils.js
new file mode 100644
index 00000000000..2d931c1c4c2
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utils.js
@@ -0,0 +1,77 @@
+const stylelint = require('stylelint');
+const md5 = require('md5');
+
+module.exports.createPropertiesHashmap = (
+ ruleRoot,
+ result,
+ ruleName,
+ messages,
+ selectorGroups,
+ addSelectors,
+) => {
+ ruleRoot.walkRules(rule => {
+ const selector = rule.selector.replace(/(?:\r\n|\r|\n)/g, ' ');
+
+ if (
+ rule &&
+ rule.parent &&
+ rule.parent.type != 'atrule' &&
+ !(
+ selector.includes('-webkit-') ||
+ selector.includes('-moz-') ||
+ selector.includes('-o-') ||
+ selector.includes('-ms-') ||
+ selector.includes(':')
+ )
+ ) {
+ let cssArray = [];
+ rule.nodes.forEach(function(property) {
+ const { prop, value } = property;
+ if (property && value) {
+ const propval = `${prop}${value}${property.important ? '!important' : ''}`;
+ cssArray.push(propval);
+ }
+ });
+
+ cssArray = cssArray.sort();
+ const cssContent = cssArray.toString();
+
+ if (cssContent) {
+ const hashValue = md5(cssContent);
+ const selObj = selectorGroups[hashValue];
+
+ const selectorLine = `${selector} (${
+ rule.source.input.file ? rule.source.input.file + ' -' : ''
+ }${rule.source.start.line}:${rule.source.start.column})`;
+
+ if (selObj) {
+ if (selectorGroups[hashValue].selectors.indexOf(selector) == -1) {
+ let lastSelector =
+ selectorGroups[hashValue].selectors[selectorGroups[hashValue].selectors.length - 1];
+
+ // So we have nicer formatting if it is the same file, we remove the filename
+ lastSelector = lastSelector.replace(`${rule.source.input.file} - `, '');
+
+ if (messages) {
+ stylelint.utils.report({
+ result,
+ ruleName,
+ message: messages.expected(selector, lastSelector),
+ node: rule,
+ word: rule.node,
+ });
+ }
+
+ if (addSelectors) {
+ selectorGroups[hashValue].selectors.push(selectorLine);
+ }
+ }
+ } else if (addSelectors) {
+ selectorGroups[hashValue] = {
+ selectors: [selectorLine],
+ };
+ }
+ }
+ }
+ });
+};
diff --git a/scripts/frontend/stylelint/utility-classes-map.js b/scripts/frontend/stylelint/utility-classes-map.js
new file mode 100644
index 00000000000..1929f950f6c
--- /dev/null
+++ b/scripts/frontend/stylelint/utility-classes-map.js
@@ -0,0 +1,216 @@
+module.exports = {
+ '99097f29a9473b56eacdb9ff0681c366': { selectors: ['.align-baseline (1:1)'] },
+ d969b318bb994e104e8c965006d71cb7: { selectors: ['.align-top (4:1)'] },
+ '8cd54ab97b9cc43cb9d13d2ea7c601c7': { selectors: ['.align-middle (7:1)'] },
+ dd06eb6c49e979b7a9fdaa7119aa0a0b: { selectors: ['.align-bottom (10:1)'] },
+ '0af1e90cbc468615e299ec9f49e97c4a': { selectors: ['.align-text-bottom (13:1)'] },
+ '50af706df238cf59bdc634fc684ba0c9': { selectors: ['.align-text-top (16:1)'] },
+ c968922e6e47445362129a684b5913c0: { selectors: ['.bg-primary (19:1)'] },
+ '3c397f9786c24cff4779a11cf5b3d7e7': { selectors: ['.bg-secondary (27:1)'] },
+ '659677469a4477267fabc1788f7cad4e': { selectors: ['.bg-success (35:1)'] },
+ '56d246d5b6a708a4c6f78dbd2444106c': { selectors: ['.bg-info (43:1)'] },
+ '6bec0a33df3a6380c30103db5c273455': { selectors: ['.bg-warning (51:1)'] },
+ '0ce5d074c8667ce6c32360658f428d5d': { selectors: ['.bg-danger (59:1)'] },
+ '0d0269c62a01e97caa9039d227a25d12': { selectors: ['.bg-light (67:1)'] },
+ '3a56309ad8c5b46ebcc3b13fe1987ac1': { selectors: ['.bg-dark (75:1)'] },
+ '0e252f8dd392a33343d3d5efc1e3194a': { selectors: ['.bg-white (83:1)'] },
+ '3af6f52f0ed4f98e797d5f10a35ca6bc': { selectors: ['.bg-transparent (86:1)'] },
+ '16da7fdce74577ceab356509db565612': { selectors: ['.border (89:1)'] },
+ '929622517ca05efde3b51e5f1a57064e': { selectors: ['.border-top (92:1)'] },
+ '7283090353df54f1d515a6ceddfb9693': { selectors: ['.border-right (95:1)'] },
+ bd5670d71332c652b46db82949042e31: { selectors: ['.border-bottom (98:1)'] },
+ fa71e003d04734a898a85cc5285e3cbb: { selectors: ['.border-left (101:1)'] },
+ ed482cea071e316f29d78fd93c3f3644: { selectors: ['.border-0 (104:1)'] },
+ '90cb661baf21e10be6e479cb0544b1a7': { selectors: ['.border-top-0 (107:1)'] },
+ '8a32707eaa09fc998bf8cc915710b60c': { selectors: ['.border-right-0 (110:1)'] },
+ a6f01957e142a000e7742b31ac6c2331: { selectors: ['.border-bottom-0 (113:1)'] },
+ c740fe952cc1985ee14f7d1c7a359a29: { selectors: ['.border-left-0 (116:1)'] },
+ af9dd93e9780306ffa4bb25a6384902f: { selectors: ['.border-primary (119:1)'] },
+ afa290dfe58cca06be5924ceae1b019b: { selectors: ['.border-secondary (122:1)'] },
+ '9b1ac460bdddf1e0164d7bf988cc2da8': { selectors: ['.border-success (125:1)'] },
+ '091cbf41d6be12061382fa571ee1ce82': { selectors: ['.border-info (128:1)'] },
+ '3ada321d4a387901dad6d80e1b6be3fd': { selectors: ['.border-warning (131:1)'] },
+ '13b4713dd52c1e359d1b43dd658cb249': { selectors: ['.border-danger (134:1)'] },
+ '0048e110875ea22b04104d55e764a367': { selectors: ['.border-light (137:1)'] },
+ a900b6b567c9a911326cdd0e19f40f8e: { selectors: ['.border-dark (140:1)'] },
+ '78bcd867ac9677c743c2bc33b872f27b': { selectors: ['.border-white (143:1)'] },
+ e0fc10c49c7b7f4d1924336d21a4f64e: { selectors: ['.rounded (146:1)'] },
+ '1b74b9d0a7d6a59281b5b5cae43c859a': { selectors: ['.rounded-top (149:1)'] },
+ '20b75f55f39e662e038d51a6442c03df': { selectors: ['.rounded-right (153:1)'] },
+ '83ea6db794873239c21f44af25618677': { selectors: ['.rounded-bottom (157:1)'] },
+ '8464e9e8001e65dfc06397436a5eebd7': { selectors: ['.rounded-left (161:1)'] },
+ '59c2f788287fa43caf5891adfc5c796e': { selectors: ['.rounded-circle (165:1)'] },
+ '31a632ba94f8c41558bd6044458f1459': { selectors: ['.rounded-0 (168:1)'] },
+ '16aaf53ab29d6b248b0257f2fa413914': { selectors: ['.d-none (176:1)'] },
+ '4f42736ac9217039ed791b4306e60aeb': { selectors: ['.d-inline (179:1)'] },
+ '067efa04b76649e8afcdceb9f5f7e870': { selectors: ['.d-inline-block (182:1)'] },
+ de54f49149fb9b512aa79ad9ada838f2: { selectors: ['.d-block (185:1)'] },
+ '80fc32acbc0c28ee890a160c23529d26': { selectors: ['.d-table (188:1)'] },
+ '6a87b1db48298ca94cbe5dee79a6eed1': { selectors: ['.d-table-row (191:1)'] },
+ b9896f0d94760bf5920f47904e9f7512: { selectors: ['.d-table-cell (194:1)'] },
+ d25c51f38c4d057209b96c664de68c44: { selectors: ['.d-flex (197:1)'] },
+ e72d46b636d5b8e17e771daa95793f33: { selectors: ['.d-inline-flex (200:1)'] },
+ '2c433b7c14a5ae32cfa8ec7867ee8526': { selectors: ['.embed-responsive (303:1)'] },
+ '56b318b8d8eb845b769d60cefcd131bb': {
+ selectors: [
+ '.embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video (312:3)',
+ ],
+ },
+ c5009af89633c4d2f71a0a9fa333630d: { selectors: ['.flex-row (337:1)'] },
+ '7b06a3d956579cd64b6f5b1a57255369': { selectors: ['.flex-column (340:1)'] },
+ '21744a8c4dc6ed1519903b4236b00af4': { selectors: ['.flex-row-reverse (343:1)'] },
+ '18d903735f9c71070b6d8166aa1112f1': { selectors: ['.flex-column-reverse (346:1)'] },
+ e2a57aa8196347d4da84f33a4f551325: { selectors: ['.flex-wrap (349:1)'] },
+ b6b29f75b174b5609f9e7d5eef457b70: { selectors: ['.flex-nowrap (352:1)'] },
+ '839230fc7c0abacb6418b49d8f10b27f': { selectors: ['.flex-wrap-reverse (355:1)'] },
+ '924d9b261944a8e8ff684d5b519062bb': { selectors: ['.flex-fill (358:1)'] },
+ '5ed396aeb08464b7df8fc37d29455319': { selectors: ['.flex-grow-0 (361:1)'] },
+ '94251dc4d012339a3e37df6196fc79bb': { selectors: ['.flex-grow-1 (364:1)'] },
+ '0eeed7dabca0452a46574776a4485e6e': { selectors: ['.flex-shrink-0 (367:1)'] },
+ c69e60d5e51a1b74d22b172ab98ef9d5: { selectors: ['.flex-shrink-1 (370:1)'] },
+ '03401c1a81eb6d4639f020f76dd60176': { selectors: ['.justify-content-start (373:1)'] },
+ '39e3d2c344e78869c98ef099249e717d': { selectors: ['.justify-content-end (376:1)'] },
+ '3b2d00c0bea857ab78a71b0872842980': { selectors: ['.justify-content-center (379:1)'] },
+ b1c3c9edd20ed7c08b43863d38ebee40: { selectors: ['.justify-content-between (382:1)'] },
+ a11b4b1d983c5fa75777f273e998f73e: { selectors: ['.justify-content-around (385:1)'] },
+ '50e33f29f65bfffa6a3591fcb6045ca9': { selectors: ['.align-items-start (388:1)'] },
+ e44b276e47415ec19b74cc16740ada1d: { selectors: ['.align-items-end (391:1)'] },
+ '4a9d2716bca651758564059dceed1271': { selectors: ['.align-items-center (394:1)'] },
+ fd970b017f7f558f30cb273bf71ede7d: { selectors: ['.align-items-baseline (397:1)'] },
+ '7204b6b00b69f8af1e4a24c9b6e7f7f9': { selectors: ['.align-items-stretch (400:1)'] },
+ '350fbb74eb70bd05f9438067c3990e9f': { selectors: ['.align-content-start (403:1)'] },
+ '74d61397b4fcbf608f3dba39ab3b2a1b': { selectors: ['.align-content-end (406:1)'] },
+ eed1ab4ee9e5b327a434512176741548: { selectors: ['.align-content-center (409:1)'] },
+ '19878ab832978ef7e1746ac2fe4084b2': { selectors: ['.align-content-between (412:1)'] },
+ dded333d0522692809517039f5a727c1: { selectors: ['.align-content-around (415:1)'] },
+ '5cbb83ea2af7e9db8ef13f4c7d6db875': { selectors: ['.align-content-stretch (418:1)'] },
+ dc3df46d3c023184d375a63a71916646: { selectors: ['.align-self-auto (421:1)'] },
+ '0c6d2d8c9732c571f9cf61a4b1d2877f': { selectors: ['.align-self-start (424:1)'] },
+ fe7c6071e3e17214df1bdd38850d9ff0: { selectors: ['.align-self-end (427:1)'] },
+ '9611bbad74d72d50cf238088576a5089': { selectors: ['.align-self-center (430:1)'] },
+ '4bc5edddf5981866946175bfedb7247f': { selectors: ['.align-self-baseline (433:1)'] },
+ '4fffdd27ec60120ec9ed16fd7feef801': { selectors: ['.align-self-stretch (436:1)'] },
+ '39e658501f5502b35919f02fa9591360': { selectors: ['.float-left (719:1)'] },
+ b51436c537ffc4269b1533e44d7c3467: { selectors: ['.float-right (722:1)'] },
+ c4597a87d2c793a6d696cfe06f6c95ce: { selectors: ['.float-none (725:1)'] },
+ aaf8dc6e0768e59f3098a98a5c144d66: { selectors: ['.position-static (760:1)'] },
+ '79592de2383045d15ab57d35aa1dab95': { selectors: ['.position-relative (763:1)'] },
+ a7c272f53d0368730bde4c2740ffb5c3: { selectors: ['.position-absolute (766:1)'] },
+ dad0bb92d53f17cf8affc10f77824b7f: { selectors: ['.position-fixed (769:1)'] },
+ '6da0f6a7354a75fe6c95b08a4cabc06f': { selectors: ['.position-sticky (772:1)'] },
+ '66602e21eea7b673883155c8f42b9590': { selectors: ['.fixed-top (775:1)'] },
+ '33ea70eb6db7f6ab3469680f182abb19': { selectors: ['.fixed-bottom (782:1)'] },
+ '405e9dd7c9919943af14658313fd31e4': { selectors: ['.sr-only (795:1)'] },
+ '9220ad156a70c2586b15fe2b9b6108b2': { selectors: ['.shadow-sm (813:1)'] },
+ b1b8c0ff70767ca2160811a026766982: { selectors: ['.shadow (816:1)'] },
+ da0792abe99964acb6692a03c61d6dd8: { selectors: ['.shadow-lg (819:1)'] },
+ bb2173057af1cf20e687469b2d9cbb3c: { selectors: ['.shadow-none (822:1)'] },
+ '6d8abb6519a186483b25429ab8b9765e': { selectors: ['.w-25 (825:1)'] },
+ a087c1ffdf8ead76cdd37445b414d63e: { selectors: ['.w-50 (828:1)'] },
+ '28180742013a90275be5633e6ec0dd51': { selectors: ['.w-75 (831:1)'] },
+ '195a03bc95a0af0ba6c8824db97a0b2f': { selectors: ['.w-100 (834:1)'] },
+ e67c74b650d6236b03be9dfc10c78e32: { selectors: ['.w-auto (837:1)'] },
+ c1b6262b3ee069addc1fbe46f64aac4e: { selectors: ['.h-25 (840:1)'] },
+ a520396ae349bef86145e0761aa0699e: { selectors: ['.h-50 (843:1)'] },
+ '7c53b57d54beb087fd7ab8b669c5fe60': { selectors: ['.h-75 (846:1)'] },
+ ad74f1972cb745b7a78b03e16a387f21: { selectors: ['.h-100 (849:1)'] },
+ '2cd49c3d63d260ba4f0b23c559ad05e0': { selectors: ['.h-auto (852:1)'] },
+ '0b43071a67efc45ee1735fdc2491313c': { selectors: ['.mw-100 (855:1)'] },
+ eac31a6f08e5c935e24b97df0fdad579: { selectors: ['.mh-100 (858:1)'] },
+ cfdb4f497b16074959bfd3deb7ea9c42: { selectors: ['.m-0 (861:1)'] },
+ '4d666c270ba50524312d97c4b937d153': { selectors: ['.mt-0, .my-0 (864:1)'] },
+ eccf47ccd76ceffb4b139cb6c080b5ac: { selectors: ['.mr-0, .mx-0 (868:1)'] },
+ '9bc513e73c0bdc6efdf170cb31de16d1': { selectors: ['.mb-0, .my-0 (872:1)'] },
+ e99cfc55b03f0e67f11628b19889ad7b: { selectors: ['.ml-0, .mx-0 (876:1)'] },
+ e1c39484d90d2acaa00973531f47f738: { selectors: ['.m-1 (880:1)'] },
+ '63791bc02eccfdfa2c01621a801e565f': { selectors: ['.mt-1, .my-1 (883:1)'] },
+ bcb27ab9d7dcfdd0d7cacad02709966c: { selectors: ['.mr-1, .mx-1 (887:1)'] },
+ cb5d1c4328e25b5bc93be9a252973690: { selectors: ['.mb-1, .my-1 (891:1)'] },
+ b80b1010c7dcfbb30bed9015c4f2e969: { selectors: ['.ml-1, .mx-1 (895:1)'] },
+ ecab1c9cdf8a562e3c0f70307aeafa89: { selectors: ['.m-2 (899:1)'] },
+ '6505fe17fbbd88b1884113a754aa82ab': { selectors: ['.mt-2, .my-2 (902:1)'] },
+ '6f0c7d09d1e729f332c4671ccc2b48c0': { selectors: ['.mr-2, .mx-2 (906:1)'] },
+ '70ef7b668b382b3c747b2d73e08cdbed': { selectors: ['.mb-2, .my-2 (910:1)'] },
+ '2d7f277cc78ed324a8fc1f71ab281e1f': { selectors: ['.ml-2, .mx-2 (914:1)'] },
+ '8ebcfe52fd4024861082ffb1735747a7': { selectors: ['.m-3 (918:1)'] },
+ '9965fb516bdb72b87023a533123a8035': { selectors: ['.mt-3, .my-3 (921:1)'] },
+ b1fcbbb1dc6226f6da6000830088e051: { selectors: ['.mr-3, .mx-3 (925:1)'] },
+ '02204826cfbe3da98535c0d802870940': { selectors: ['.mb-3, .my-3 (929:1)'] },
+ '0259859060250ae6b730218733e7a437': { selectors: ['.ml-3, .mx-3 (933:1)'] },
+ '8cf300dab2a4994a105eeddda826f2e6': { selectors: ['.m-4 (937:1)'] },
+ '1ba62fdddd3349f52a452050688905c7': { selectors: ['.mt-4, .my-4 (940:1)'] },
+ '66a104129fa13db5a0829567fba6ee41': { selectors: ['.mr-4, .mx-4 (944:1)'] },
+ eefcc4c10b79e70e8e8a5a66fb2b7aa1: { selectors: ['.mb-4, .my-4 (948:1)'] },
+ eb1503656dc920d15a31116956fdffa4: { selectors: ['.ml-4, .mx-4 (952:1)'] },
+ '79cbb6e5c9b73fd0be29d4fc5733a099': { selectors: ['.m-5 (956:1)'] },
+ '67d8671699df706a428e7da42a7141cb': { selectors: ['.mt-5, .my-5 (959:1)'] },
+ e9cb4a0a8a60ff018c87a0b7efa9de29: { selectors: ['.mr-5, .mx-5 (963:1)'] },
+ '93f579214354dbd8cb60209c068f0086': { selectors: ['.mb-5, .my-5 (967:1)'] },
+ '2a789d4af97d2b87fd0bf2b4626120cd': { selectors: ['.ml-5, .mx-5 (971:1)'] },
+ '64a89d28e8287c1a0ac153001082644c': { selectors: ['.p-0 (975:1)'] },
+ b03aa6db5ddf110bbdbefbbec43fda30: { selectors: ['.pt-0, .py-0 (978:1)'] },
+ e38192ca32a98888d4c4876880f4fece: { selectors: ['.pr-0, .px-0 (982:1)'] },
+ '70fe8ef50e999ddd29506f672c107069': { selectors: ['.pb-0, .py-0 (986:1)'] },
+ '9355e8cd9109049726475ba356661bcf': { selectors: ['.pl-0, .px-0 (990:1)'] },
+ '0d4c53468c2658c5324b9ec7a8ca6de2': { selectors: ['.p-1 (994:1)'] },
+ d74e430b2a56b3a4e20065c972b7fa3f: { selectors: ['.pt-1, .py-1 (997:1)'] },
+ '21e4644967aedd19888b6f4a700b629b': { selectors: ['.pr-1, .px-1 (1001:1)'] },
+ e315a7b9b7a1d0df3ea7d95af5203a0b: { selectors: ['.pb-1, .py-1 (1005:1)'] },
+ '14630ca122e1d9830a9ef5591c4097d0': { selectors: ['.pl-1, .px-1 (1009:1)'] },
+ '5b1c65e5139e86e5f4755824f8b77d13': { selectors: ['.p-2 (1013:1)'] },
+ '244af70950a1e200d3849f75ce51d707': { selectors: ['.pt-2, .py-2 (1016:1)'] },
+ b583832738cad724c7c23e5c14ac9bfb: { selectors: ['.pr-2, .px-2 (1020:1)'] },
+ e1e633c4f1375e8276154192d8899e39: { selectors: ['.pb-2, .py-2 (1024:1)'] },
+ '676b01e25f0dbb3f7d2f2529231cda08': { selectors: ['.pl-2, .px-2 (1028:1)'] },
+ '9b5165e3333b22801f2287f7983d7516': { selectors: ['.p-3 (1032:1)'] },
+ '5bcaa9df87a507f6cd14659ea176bdc5': { selectors: ['.pt-3, .py-3 (1035:1)'] },
+ f706637180776c5589385599705a2409: { selectors: ['.pr-3, .px-3 (1039:1)'] },
+ '41157cfbcf47990b383b5b0379386ab2': { selectors: ['.pb-3, .py-3 (1043:1)'] },
+ cac1e7a204bb6a1f42707b684ad46238: { selectors: ['.pl-3, .px-3 (1047:1)'] },
+ '43e0671cd41a4b7590284888b607a134': { selectors: ['.p-4 (1051:1)'] },
+ '116b0f95ebde1ff8907e488413a88854': { selectors: ['.pt-4, .py-4 (1054:1)'] },
+ ecb06765fe691d892df000eebbb23dcc: { selectors: ['.pr-4, .px-4 (1058:1)'] },
+ '1331503a48d36025c861e660bc615048': { selectors: ['.pb-4, .py-4 (1062:1)'] },
+ f8665f7e547e499abd7ac63813b274f5: { selectors: ['.pl-4, .px-4 (1066:1)'] },
+ '4160a315459f1b5a98255863f42136fe': { selectors: ['.p-5 (1070:1)'] },
+ f55a6b2de6a434ec7b4375f06f4fad75: { selectors: ['.pt-5, .py-5 (1073:1)'] },
+ '19391dc45c8d7730a86d521c28f52c3f': { selectors: ['.pr-5, .px-5 (1077:1)'] },
+ '15898bcb7ff74a60006f9931422b4ad3': { selectors: ['.pb-5, .py-5 (1081:1)'] },
+ '6290bdc6355aed1e9b27379003aa4828': { selectors: ['.pl-5, .px-5 (1085:1)'] },
+ e57ec4fe9e8ed36e38f1c50041fc9f47: { selectors: ['.m-auto (1089:1)'] },
+ f10380665932186d1effe0674a74ba12: { selectors: ['.mt-auto, .my-auto (1092:1)'] },
+ '2ce71a27023eb50a47c24a99399faa28': { selectors: ['.mr-auto, .mx-auto (1096:1)'] },
+ '196c77d357d314678cd3a99cfacbea96': { selectors: ['.mb-auto, .my-auto (1100:1)'] },
+ ca007ce268b463a6bf42145cf5ce3685: { selectors: ['.ml-auto, .mx-auto (1104:1)'] },
+ cb431b84034f2e710509c7656b3c6f16: { selectors: ['.text-monospace (1844:1)'] },
+ a8fc5ca823f51d72673577064387a029: { selectors: ['.text-justify (1847:1)'] },
+ '0bb94dfab7ca2c9892ebbd993b2baf0f': { selectors: ['.text-nowrap (1850:1)'] },
+ aea4958ce85ddc0cbffca1015c3a7eba: { selectors: ['.text-truncate (1853:1)'] },
+ '52b9443947b6b94a5c7e1b837da115e2': { selectors: ['.text-left (1858:1)'] },
+ baaf5136fc6e1c54ba29b6040f166d5f: { selectors: ['.text-right (1861:1)'] },
+ '282aa4319bee75af06cc2632b7124e26': { selectors: ['.text-center (1864:1)'] },
+ '1cb1c8ad9b560eca25ebcefe95c1b7fa': { selectors: ['.text-lowercase (1899:1)'] },
+ '45234533eac658ba2857e9c4d3bc78a5': { selectors: ['.text-uppercase (1902:1)'] },
+ f9e3f64237f2e81b6aed84223a0ceb1d: { selectors: ['.text-capitalize (1905:1)'] },
+ '09caca3d36aa9f3ef815e0da7e1a16b4': { selectors: ['.font-weight-light (1908:1)'] },
+ '25189f4fad18eaeef19e349c6680834c': { selectors: ['.font-weight-normal (1911:1)'] },
+ b2a9507678ec557603eb8ec077f0eb1f: { selectors: ['.font-weight-bold (1914:1)'] },
+ '7d2da06b621a98a8599e5ec82e39eac8': { selectors: ['.font-italic (1917:1)'] },
+ '0020d10e4fce033b418aace7c3143b82': { selectors: ['.text-white (1920:1)'] },
+ '34ad81e372a038e6f78ae4f22bd4813d': { selectors: ['.text-primary (1923:1)'] },
+ '9fde9a179d24755438ace2a874dda817': {
+ selectors: ['.text-secondary (1929:1)', '.text-muted (1974:1)'],
+ },
+ '9ffcb1532b3fb397c0e818850683da29': { selectors: ['.text-success (1935:1)'] },
+ f28fd089809bcd15d5684b158a0af98d: { selectors: ['.text-info (1941:1)'] },
+ '6cac1cb5ee5149e91e45d15d0bdae310': { selectors: ['.text-warning (1947:1)'] },
+ '2faab1e0abf22b20fdf05b9b01fff29b': { selectors: ['.text-danger (1953:1)'] },
+ '46b52fea531aaaf29b63c40be2356849': { selectors: ['.text-light (1959:1)'] },
+ '78f31d1ab6529decf28e0366a8ee81aa': { selectors: ['.text-dark (1965:1)'] },
+ '45330b41b77e8880ad7680c51e0f61c4': { selectors: ['.text-body (1971:1)'] },
+ '60d93588f62b5e85eb4f11dfd3461897': { selectors: ['.text-black-50 (1977:1)'] },
+ '7dea35658553032ff7b7cc0287613b7c': { selectors: ['.text-white-50 (1980:1)'] },
+ '61bf92980cac3d51d0cf1ba24c948fa1': { selectors: ['.text-hide (1983:1)'] },
+ '7dcad258820769677bc60871fafe9b93': { selectors: ['.visible (1990:1)'] },
+ '0f8833af4e2f4a6fc785bd7edc1e75b3': { selectors: ['.invisible (1993:1)'] },
+};
diff --git a/scripts/frontend/test.js b/scripts/frontend/test.js
new file mode 100755
index 00000000000..dab7176f8c1
--- /dev/null
+++ b/scripts/frontend/test.js
@@ -0,0 +1,114 @@
+#!/usr/bin/env node
+
+const { spawn } = require('child_process');
+const { EOL } = require('os');
+const program = require('commander');
+const chalk = require('chalk');
+
+const JEST_ROUTE = 'spec/frontend';
+const KARMA_ROUTE = 'spec/javascripts';
+const COMMON_ARGS = ['--colors'];
+const JEST_ARGS = ['--passWithNoTests'];
+const KARMA_ARGS = ['--no-fail-on-empty-test-suite'];
+const SUCCESS_CODE = 0;
+
+program
+ .version('0.1.0')
+ .usage('[options] <file ...>')
+ .option('-p, --parallel', 'Run tests suites in parallel')
+ .parse(process.argv);
+
+const isSuccess = code => code === SUCCESS_CODE;
+
+const combineExitCodes = codes => {
+ const firstFail = codes.find(x => !isSuccess(x));
+
+ return firstFail === undefined ? SUCCESS_CODE : firstFail;
+};
+
+const skipIfFail = fn => code => (isSuccess(code) ? fn() : code);
+
+const endWithEOL = str => (str[str.length - 1] === '\n' ? str : `${str}${EOL}`);
+
+const runTests = paths => {
+ if (program.parallel) {
+ return Promise.all([runJest(paths), runKarma(paths)]).then(combineExitCodes);
+ } else {
+ return runJest(paths).then(skipIfFail(() => runKarma(paths)));
+ }
+};
+
+const spawnYarnScript = (cmd, args) => {
+ return new Promise((resolve, reject) => {
+ const proc = spawn('yarn', ['run', cmd, ...args]);
+ const output = data => {
+ const text = data
+ .toString()
+ .split(/\r?\n/g)
+ .map((line, idx, { length }) =>
+ idx === length - 1 && !line ? line : `${chalk.gray(cmd)}: ${line}`,
+ )
+ .join(EOL);
+
+ return endWithEOL(text);
+ };
+
+ proc.stdout.on('data', data => {
+ process.stdout.write(output(data));
+ });
+
+ proc.stderr.on('data', data => {
+ process.stderr.write(output(data));
+ });
+
+ proc.on('close', code => {
+ process.stdout.write(output(`exited with code ${code}`));
+
+ // We resolve even on a failure code because a `reject` would cause
+ // Promise.all to reject immediately (without waiting for other promises)
+ // to finish.
+ resolve(code);
+ });
+ });
+};
+
+const runJest = args => {
+ return spawnYarnScript('jest', [...JEST_ARGS, ...COMMON_ARGS, ...toJestArgs(args)]);
+};
+
+const runKarma = args => {
+ return spawnYarnScript('karma', [...KARMA_ARGS, ...COMMON_ARGS, ...toKarmaArgs(args)]);
+};
+
+const replacePath = to => path =>
+ path
+ .replace(JEST_ROUTE, to)
+ .replace(KARMA_ROUTE, to)
+ .replace('app/assets/javascripts', to);
+
+const replacePathForJest = replacePath(JEST_ROUTE);
+
+const replacePathForKarma = replacePath(KARMA_ROUTE);
+
+const toJestArgs = paths => paths.map(replacePathForJest);
+
+const toKarmaArgs = paths =>
+ paths.reduce((acc, path) => acc.concat('-f', replacePathForKarma(path)), []);
+
+const main = paths => {
+ runTests(paths).then(code => {
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
+ if (isSuccess(code)) {
+ console.log(chalk.bgGreen(chalk.black('All tests passed :)')));
+ } else {
+ console.log(chalk.bgRed(chalk.white(`Some tests failed :(`)));
+ }
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
+
+ if (!isSuccess(code)) {
+ process.exit(code);
+ }
+ });
+};
+
+main(program.args);
diff --git a/scripts/gitaly_test.rb b/scripts/gitaly_test.rb
index dee4c2eba7e..b5d3facd18a 100644
--- a/scripts/gitaly_test.rb
+++ b/scripts/gitaly_test.rb
@@ -79,15 +79,13 @@ module GitalyTest
socket = read_socket_path
Integer(timeout / delay).times do
- begin
- UNIXSocket.new(socket)
- puts ' OK'
-
- return
- rescue Errno::ENOENT, Errno::ECONNREFUSED
- print '.'
- sleep delay
- end
+ UNIXSocket.new(socket)
+ puts ' OK'
+
+ return
+ rescue Errno::ENOENT, Errno::ECONNREFUSED
+ print '.'
+ sleep delay
end
puts ' FAILED'
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
new file mode 100755
index 00000000000..b34379764e0
--- /dev/null
+++ b/scripts/insert-rspec-profiling-data
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+
+require 'csv'
+require 'rspec_profiling'
+require 'postgres-copy'
+
+module RspecProfiling
+ module Collectors
+ class PSQL
+ def establish_connection
+ # This disables the automatic creation of the database and
+ # table. In the future, we may want a way to specify the host of
+ # the database to connect so that we can call #install.
+ Result.establish_connection(results_url)
+ end
+
+ def prepared?
+ connection.data_source_exists?(table)
+ end
+
+ def results_url
+ ENV['RSPEC_PROFILING_POSTGRES_URL']
+ end
+
+ class Result < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
+ acts_as_copy_target
+ end
+ end
+ end
+end
+
+def insert_data(path)
+ puts "#{Time.now} Inserting CI stats..."
+
+ collector = RspecProfiling::Collectors::PSQL.new
+ collector.install
+
+ files = Dir[File.join(path, "*.csv")]
+
+ files.each do |filename|
+ puts "#{Time.now} Inserting #{filename}..."
+ result = RspecProfiling::Collectors::PSQL::Result.copy_from(filename)
+ puts "#{Time.now} Inserted #{result.cmd_tuples} lines in #{filename}, DB response: #{result.cmd_status}"
+ end
+end
+
+insert_data('rspec_profiling') if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index bc73225c1bf..2055ce7f09d 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -35,7 +35,7 @@ fi
# Do not use 'README.md', instead use 'index.md'
# Number of 'README.md's as of 2018-03-26
-NUMBER_READMES_CE=43
+NUMBER_READMES_CE=46
NUMBER_READMES_EE=46
FIND_READMES=$(find doc/ -name "README.md" | wc -l)
echo '=> Checking for new README.md files...'
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index 22e3e1f1505..9466c62a415 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -5,12 +5,18 @@ ALLOWED = [
'lib/gitlab/bare_repository_import/repository.rb',
# Needed to avoid using the git binary to validate a branch name
- 'lib/gitlab/git_ref_validator.rb'
+ 'lib/gitlab/git_ref_validator.rb',
+
+ # Reverted Rugged calls due to Gitaly atop NFS performance
+ # See https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code.
+ 'lib/gitlab/git/rugged_impl/',
+ 'lib/gitlab/gitaly_client/storage_settings.rb'
].freeze
rugged_lines = IO.popen(%w[git grep -i -n rugged -- app config lib], &:read).lines
rugged_lines = rugged_lines.select { |l| /^[^:]*\.rb:/ =~ l }
rugged_lines = rugged_lines.reject { |l| l.start_with?(*ALLOWED) }
+rugged_lines = rugged_lines.reject { |l| /(include|prepend) Gitlab::Git::RuggedImpl/ =~ l}
rugged_lines = rugged_lines.reject do |line|
code, _comment = line.split('# ', 2)
code !~ /rugged/i
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
index 65f93f8830b..746be3317a7 100755
--- a/scripts/merge-simplecov
+++ b/scripts/merge-simplecov
@@ -12,11 +12,9 @@ module SimpleCov
def resultset_hashes
resultset_files.map do |path|
- begin
- JSON.parse(File.read(path))
- rescue
- {}
- end
+ JSON.parse(File.read(path))
+ rescue
+ {}
end
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 58b74f2f07d..9b0d5d4f719 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -5,6 +5,7 @@ export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
+ bundle --version
bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
fi
@@ -16,12 +17,10 @@ cp config/gitlab.yml.example config/gitlab.yml
sed -i 's/bin_path: \/usr\/bin\/git/bin_path: \/usr\/local\/bin\/git/' config/gitlab.yml
# Determine the database by looking at the job name.
-# For example, we'll get pg if the job is `rspec-pg 19 20`
-export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f1 -d' ' | cut -f2 -d-)
-
-# This would make the default database postgresql, and we could also use
-# pg to mean postgresql.
-if [ "$GITLAB_DATABASE" != 'mysql' ]; then
+# This would make the default database postgresql.
+if [[ "${CI_JOB_NAME#*mysql}" != "$CI_JOB_NAME" ]]; then
+ export GITLAB_DATABASE='mysql'
+else
export GITLAB_DATABASE='postgresql'
fi
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index f610485a700..9455e462617 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -1,10 +1,102 @@
[[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE"
-function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }
-function echoinfo() { printf "\033[0;33m%s\n\033[0m" "$*" >&2; }
+function deployExists() {
+ local namespace="${1}"
+ local deploy="${2}"
+ echoinfo "Checking if ${deploy} exists in the ${namespace} namespace..." true
+
+ helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
+ local deploy_exists=$?
+
+ echoinfo "Deployment status for ${deploy} is ${deploy_exists}"
+ return $deploy_exists
+}
+
+function previousDeployFailed() {
+ set +e
+ local deploy="${1}"
+ echoinfo "Checking for previous deployment of ${deploy}" true
+
+ helm status "${deploy}" >/dev/null 2>&1
+ local status=$?
+
+ # if `status` is `0`, deployment exists, has a status
+ if [ $status -eq 0 ]; then
+ echoinfo "Previous deployment found, checking status..."
+ deployment_status=$(helm status "${deploy}" | grep ^STATUS | cut -d' ' -f2)
+ echoinfo "Previous deployment state: ${deployment_status}"
+ if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
+ status=0;
+ else
+ status=1;
+ fi
+ else
+ echoerr "Previous deployment NOT found."
+ fi
+ set -e
+ return $status
+}
+
+function delete() {
+ if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
+ echoerr "No release given, aborting the delete!"
+ return
+ fi
+
+ local name="$CI_ENVIRONMENT_SLUG"
+
+ echoinfo "Deleting release '$name'..." true
+
+ helm delete --purge "$name"
+}
+
+function cleanup() {
+ if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
+ echoerr "No release given, aborting the delete!"
+ return
+ fi
+
+ echoinfo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." true
+
+ kubectl -n "$KUBE_NAMESPACE" delete \
+ ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
+ --now --ignore-not-found --include-uninitialized \
+ -l release="$CI_ENVIRONMENT_SLUG"
+}
+
+function get_pod() {
+ local app_name="${1}"
+ local status="${2-Running}"
+ get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
+ echoinfo "Running '${get_pod_cmd}'" true
+
+ while true; do
+ local pod_name
+ pod_name="$(eval "${get_pod_cmd}")"
+ [[ "${pod_name}" == "" ]] || break
+
+ echoinfo "Waiting till '${app_name}' pod is ready";
+ sleep 5;
+ done
+
+ echoinfo "The pod name is '${pod_name}'."
+ echo "${pod_name}"
+}
+
+function perform_review_app_deployment() {
+ check_kube_domain
+ ensure_namespace
+ install_tiller
+ install_external_dns
+ time deploy
+ wait_for_review_app_to_be_accessible
+ add_license
+}
function check_kube_domain() {
+ echoinfo "Checking that Kube domain exists..." true
+
if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set"
echo "You can do it in Auto DevOps project settings or defining a variable at group or project level"
@@ -15,84 +107,81 @@ function check_kube_domain() {
fi
}
-function download_gitlab_chart() {
- curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2
- tar -xjf gitlab.tar.bz2
- cd gitlab-$GITLAB_HELM_CHART_REF
-
- helm init --client-only
- helm repo add gitlab https://charts.gitlab.io
- helm dependency update
- helm dependency build
-}
-
function ensure_namespace() {
+ echoinfo "Ensuring the ${KUBE_NAMESPACE} namespace exists..." true
+
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
function install_tiller() {
- echo "Checking Tiller..."
+ echoinfo "Checking deployment/tiller-deploy status in the ${TILLER_NAMESPACE} namespace..." true
+
+ echoinfo "Initiating the Helm client..."
+ helm init --client-only
+
helm init \
--upgrade \
--replicas 2
+
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
+
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
fi
- echo ""
+}
+
+function install_external_dns() {
+ local release_name="dns-gitlab-review-app"
+ local domain
+ domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
+ echoinfo "Installing external DNS for domain ${domain}..." true
+
+ if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
+ echoinfo "Installing external-dns Helm chart"
+ helm repo update
+ helm install stable/external-dns \
+ -n "${release_name}" \
+ --namespace "${KUBE_NAMESPACE}" \
+ --set provider="aws" \
+ --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
+ --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
+ --set aws.zoneType="public" \
+ --set domainFilters[0]="${domain}" \
+ --set txtOwnerId="${KUBE_NAMESPACE}" \
+ --set rbac.create="true" \
+ --set policy="sync"
+ else
+ echoinfo "The external-dns Helm chart is already successfully deployed."
+ fi
}
function create_secret() {
- echo "Create secret..."
+ echoinfo "Creating the ${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password secret in the ${KUBE_NAMESPACE} namespace..." true
kubectl create secret generic -n "$KUBE_NAMESPACE" \
- $CI_ENVIRONMENT_SLUG-gitlab-initial-root-password \
- --from-literal=password=$REVIEW_APPS_ROOT_PASSWORD \
+ "${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password" \
+ --from-literal="password=${REVIEW_APPS_ROOT_PASSWORD}" \
--dry-run -o json | kubectl apply -f -
}
-function deployExists() {
- local namespace="${1}"
- local deploy="${2}"
- helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
- return $?
-}
+function download_gitlab_chart() {
+ echoinfo "Downloading the GitLab chart..." true
-function previousDeployFailed() {
- set +e
- deploy="${1}"
- echo "Checking for previous deployment of ${deploy}"
- deployment_status=$(helm status ${deploy} >/dev/null 2>&1)
- status=$?
- # if `status` is `0`, deployment exists, has a status
- if [ $status -eq 0 ]; then
- echo "Previous deployment found, checking status"
- deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2)
- echo "Previous deployment state: $deployment_status"
- if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
- status=0;
- else
- status=1;
- fi
- else
- echo "Previous deployment NOT found."
- fi
- set -e
- return $status
-}
+ curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2"
+ tar -xjf gitlab.tar.bz2
+ cd "gitlab-${GITLAB_HELM_CHART_REF}"
-function deploy() {
- track="${1-stable}"
- name="$CI_ENVIRONMENT_SLUG"
+ echoinfo "Adding the gitlab repo to Helm..."
+ helm repo add gitlab https://charts.gitlab.io
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
+ echoinfo "Building the gitlab chart's dependencies..."
+ helm dependency build .
+}
- replicas="1"
- service_enabled="false"
- postgres_enabled="$POSTGRES_ENABLED"
+function deploy() {
+ local name="$CI_ENVIRONMENT_SLUG"
+ echoinfo "Deploying ${name}..." true
IMAGE_REPOSITORY="registry.gitlab.com/gitlab-org/build/cng-mirror"
IMAGE_VERSION="${CI_PROJECT_NAME#gitlab-}"
@@ -104,24 +193,6 @@ function deploy() {
gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
- # canary uses stable db
- [[ "$track" == "canary" ]] && postgres_enabled="false"
-
- env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
- env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
-
- if [[ "$track" == "stable" ]]; then
- # for stable track get number of replicas from `PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_slug}_REPLICAS
- service_enabled="true"
- else
- # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
- fi
- if [[ -n "$new_replicas" ]]; then
- replicas="$new_replicas"
- fi
-
# Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then
echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
@@ -130,9 +201,7 @@ function deploy() {
fi
create_secret
-
- helm repo add gitlab https://charts.gitlab.io/
- helm dep update .
+ download_gitlab_chart
HELM_CMD=$(cat << EOF
helm upgrade --install \
@@ -147,6 +216,7 @@ HELM_CMD=$(cat << EOF
--set global.ingress.configureCertmanager=false \
--set global.ingress.tls.secretName=tls-cert \
--set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10"
+ --set nginx-ingress.controller.service.enableHttp=false \
--set nginx-ingress.defaultBackend.resources.requests.memory=7Mi \
--set nginx-ingress.controller.resources.requests.memory=440M \
--set nginx-ingress.controller.replicaCount=2 \
@@ -164,9 +234,9 @@ HELM_CMD=$(cat << EOF
--set gitlab.unicorn.image.tag="$CI_COMMIT_REF_SLUG" \
--set gitlab.task-runner.image.repository="$gitlab_task_runner_image_repository" \
--set gitlab.task-runner.image.tag="$CI_COMMIT_REF_SLUG" \
- --set gitlab.gitaly.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitaly" \
+ --set gitlab.gitaly.image.repository="$gitlab_gitaly_image_repository" \
--set gitlab.gitaly.image.tag="v$GITALY_VERSION" \
- --set gitlab.gitlab-shell.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-shell" \
+ --set gitlab.gitlab-shell.image.repository="$gitlab_shell_image_repository" \
--set gitlab.gitlab-shell.image.tag="v$GITLAB_SHELL_VERSION" \
--set gitlab.unicorn.workhorse.image="$gitlab_workhorse_image_repository" \
--set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_SLUG" \
@@ -178,84 +248,35 @@ HELM_CMD=$(cat << EOF
EOF
)
- echo "Deploying with:"
- echo $HELM_CMD
+ echoinfo "Deploying with:"
+ echoinfo "${HELM_CMD}"
- eval $HELM_CMD
+ eval $HELM_CMD || true
}
-function delete() {
- track="${1-stable}"
- name="$CI_ENVIRONMENT_SLUG"
-
- if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
- echo "No release given, aborting the delete!"
- return
- fi
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- if ! deployExists "${KUBE_NAMESPACE}" "${name}"; then
- echo "The release $name doesn't exist, aborting the cleanup!"
- return
- fi
-
- echo "Deleting release '$name'..."
- helm delete --purge "$name" || true
-}
-
-function cleanup() {
- if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
- echo "No release given, aborting the delete!"
- return
- fi
-
- echo "Cleaning up '$CI_ENVIRONMENT_SLUG'..."
- kubectl -n "$KUBE_NAMESPACE" delete \
- ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
- -l release="$CI_ENVIRONMENT_SLUG" \
- || true
-}
-
-function install_external_dns() {
- local release_name="dns-gitlab-review-app"
- local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
-
- if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
- echo "Installing external-dns helm chart"
- helm repo update
- helm install stable/external-dns \
- -n "${release_name}" \
- --namespace "${KUBE_NAMESPACE}" \
- --set provider="aws" \
- --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
- --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
- --set aws.zoneType="public" \
- --set domainFilters[0]="${domain}" \
- --set txtOwnerId="${KUBE_NAMESPACE}" \
- --set rbac.create="true" \
- --set policy="sync"
- fi
-}
-
-function get_pod() {
- local app_name="${1}"
- local status="${2-Running}"
- get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
- echoinfo "Running '${get_pod_cmd}'"
-
+function wait_for_review_app_to_be_accessible() {
+ # In case the Review App isn't completely available yet. Keep trying for 5 minutes.
+ local interval=5
+ local elapsed_seconds=0
+ local max_seconds=$((5 * 60))
while true; do
- local pod_name="$(eval $get_pod_cmd)"
- [[ "${pod_name}" == "" ]] || break
+ local review_app_http_code
+ review_app_http_code=$(curl --silent --output /dev/null --max-time 5 --write-out "%{http_code}" "${CI_ENVIRONMENT_URL}/users/sign_in")
+ if [[ "${review_app_http_code}" -eq "200" ]] || [[ "${elapsed_seconds}" -gt "${max_seconds}" ]]; then
+ break
+ fi
- echoinfo "Waiting till '${app_name}' pod is ready";
- sleep 5;
+ printf "."
+ let "elapsed_seconds+=interval"
+ sleep ${interval}
done
- echoinfo "The pod name is '${pod_name}'."
- echo "${pod_name}"
+ if [[ "${review_app_http_code}" == "200" ]]; then
+ echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready!"
+ else
+ echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after 5 minutes of polling..."
+ exit 1
+ fi
}
function add_license() {
@@ -264,11 +285,13 @@ function add_license() {
task_runner_pod=$(get_pod "task-runner");
if [ -z "${task_runner_pod}" ]; then echo "Task runner pod not found" && return; fi
+ echoinfo "Installing license..." true
+
echo "${REVIEW_APPS_EE_LICENSE}" > /tmp/license.gitlab
- kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab ${task_runner_pod}:/tmp/license.gitlab
+ kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab "${task_runner_pod}":/tmp/license.gitlab
rm /tmp/license.gitlab
- kubectl -n "$KUBE_NAMESPACE" exec -it ${task_runner_pod} -- /srv/gitlab/bin/rails runner -e production \
+ kubectl -n "$KUBE_NAMESPACE" exec -it "${task_runner_pod}" -- /srv/gitlab/bin/rails runner -e production \
'
content = File.read("/tmp/license.gitlab").strip;
FileUtils.rm_f("/tmp/license.gitlab");
@@ -286,75 +309,3 @@ function add_license() {
puts "License added";
'
}
-
-function get_job_id() {
- local job_name="${1}"
- local query_string="${2:+&${2}}"
-
- local max_page=3
- local page=1
-
- while true; do
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
- echoinfo "GET ${url}"
-
- local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
- [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
-
- let "page++"
- done
-
- if [[ "${job_id}" == "" ]]; then
- echoerr "The '${job_name}' job ID couldn't be retrieved!"
- else
- echoinfo "The '${job_name}' job ID is ${job_id}"
- echo "${job_id}"
- fi
-}
-
-function play_job() {
- local job_name="${1}"
- local job_id=$(get_job_id "${job_name}" "scope=manual");
- if [ -z "${job_id}" ]; then return; fi
-
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
- echoinfo "POST ${url}"
-
- local job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".web_url")
- echoinfo "Manual job '${job_name}' started at: ${job_url}"
-}
-
-function wait_for_job_to_be_done() {
- local job_name="${1}"
- local query_string="${2}"
- local job_id=$(get_job_id "${job_name}" "${query_string}");
- if [ -z "${job_id}" ]; then return; fi
-
- echoinfo "Waiting for the '${job_name}' job to finish..."
-
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
- echoinfo "GET ${url}"
-
- # In case the job hasn't finished yet. Keep trying until the job times out.
- local interval=30
- local elapsed_seconds=0
- while true; do
- local job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".status" | sed -e s/\"//g)
- [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
-
- printf "."
- let "elapsed_seconds+=interval"
- sleep ${interval}
- done
-
- local elapsed_minutes=$((elapsed_seconds / 60))
- echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
-
- if [[ "${job_status}" == "failed" ]]; then
- echoerr "The '${job_name}' failed."
- elif [[ "${job_status}" == "manual" ]]; then
- echoinfo "The '${job_name}' is manual."
- else
- echoinfo "The '${job_name}' passed."
- fi
-}
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 9dbafffddfc..9c5fc3c76a5 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -141,7 +141,7 @@ module Trigger
"GITLAB_#{edition}_VERSION" => ENV['CI_COMMIT_REF_NAME'],
"GITLAB_VERSION" => ENV['CI_COMMIT_REF_NAME'],
"GITLAB_TAG" => ENV['CI_COMMIT_TAG'],
- "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_REF_SLUG'],
+ "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_REF_SLUG'],
"#{edition}_PIPELINE" => 'true'
}
end
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 2d2ba115563..4a6567b8a62 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -1,4 +1,4 @@
-retry() {
+function retry() {
if eval "$@"; then
return 0
fi
@@ -13,15 +13,15 @@ retry() {
return 1
}
-setup_db_user_only() {
+function setup_db_user_only() {
if [ "$GITLAB_DATABASE" = "postgresql" ]; then
- . scripts/create_postgres_user.sh
+ source scripts/create_postgres_user.sh
else
- . scripts/create_mysql_user.sh
+ source scripts/create_mysql_user.sh
fi
}
-setup_db() {
+function setup_db() {
setup_db_user_only
bundle exec rake db:drop db:create db:schema:load db:migrate
@@ -30,3 +30,129 @@ setup_db() {
bundle exec rake add_limits_mysql
fi
}
+
+function install_api_client_dependencies_with_apk() {
+ apk add --update openssl curl jq
+}
+
+function install_api_client_dependencies_with_apt() {
+ apt update && apt install jq -y
+}
+
+function install_gitlab_gem() {
+ gem install gitlab --no-document
+}
+
+function echoerr() {
+ local header="${2}"
+
+ if [ -n "${header}" ]; then
+ printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
+ else
+ printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
+ fi
+}
+
+function echoinfo() {
+ local header="${2}"
+
+ if [ -n "${header}" ]; then
+ printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
+ else
+ printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
+ fi
+}
+
+function get_job_id() {
+ local job_name="${1}"
+ local query_string="${2:+&${2}}"
+ local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
+ if [ -z "${api_token}" ]; then
+ echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
+ return
+ fi
+
+ local max_page=3
+ local page=1
+
+ while true; do
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
+ echoinfo "GET ${url}"
+
+ local job_id
+ job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
+ [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
+
+ let "page++"
+ done
+
+ if [[ "${job_id}" == "" ]]; then
+ echoerr "The '${job_name}' job ID couldn't be retrieved!"
+ else
+ echoinfo "The '${job_name}' job ID is ${job_id}"
+ echo "${job_id}"
+ fi
+}
+
+function play_job() {
+ local job_name="${1}"
+ local job_id
+ job_id=$(get_job_id "${job_name}" "scope=manual");
+ if [ -z "${job_id}" ]; then return; fi
+
+ local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
+ if [ -z "${api_token}" ]; then
+ echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
+ return
+ fi
+
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
+ echoinfo "POST ${url}"
+
+ local job_url
+ job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url")
+ echoinfo "Manual job '${job_name}' started at: ${job_url}"
+}
+
+function wait_for_job_to_be_done() {
+ local job_name="${1}"
+ local query_string="${2}"
+ local job_id
+ job_id=$(get_job_id "${job_name}" "${query_string}")
+ if [ -z "${job_id}" ]; then return; fi
+
+ local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
+ if [ -z "${api_token}" ]; then
+ echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
+ return
+ fi
+
+ echoinfo "Waiting for the '${job_name}' job to finish..."
+
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
+ echoinfo "GET ${url}"
+
+ # In case the job hasn't finished yet. Keep trying until the job times out.
+ local interval=30
+ local elapsed_seconds=0
+ while true; do
+ local job_status
+ job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".status" | sed -e s/\"//g)
+ [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
+
+ printf "."
+ let "elapsed_seconds+=interval"
+ sleep ${interval}
+ done
+
+ local elapsed_minutes=$((elapsed_seconds / 60))
+ echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
+
+ if [[ "${job_status}" == "failed" ]]; then
+ echoerr "The '${job_name}' failed."
+ elif [[ "${job_status}" == "manual" ]]; then
+ echoinfo "The '${job_name}' is manual."
+ else
+ echoinfo "The '${job_name}' passed."
+ fi
+}
diff --git a/security.txt b/security.txt
new file mode 100644
index 00000000000..f7adb43fda6
--- /dev/null
+++ b/security.txt
@@ -0,0 +1,6 @@
+Contact: security@gitlab.com
+Acknowledgments: https://about.gitlab.com/security/vulnerability-acknowledgements/
+Preferred-Languages: en
+Canonical: https://about.gitlab.com/security/disclosure/
+Policy: https://hackerone.com/gitlab
+Hiring: https://about.gitlab.com/jobs/
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index b2c2fb810e8..84bbe0525e5 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
load File.expand_path('../../bin/changelog', __dir__)
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 74634dac713..94b29b89f24 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'mail_room.yml' do
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb
index b1ada3c99db..c38910cff0a 100644
--- a/spec/config/object_store_settings_spec.rb
+++ b/spec/config/object_store_settings_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require Rails.root.join('config', 'object_store_settings.rb')
describe ObjectStoreSettings do
describe '.parse' do
- it 'should set correct default values' do
+ it 'sets correct default values' do
settings = described_class.parse(nil)
expect(settings['enabled']).to be false
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index c89b5f48dc0..26d92593a08 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Settings do
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 7104305e9d2..e360ab68cf2 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AbuseReportsController do
diff --git a/spec/controllers/acme_challenges_controller_spec.rb b/spec/controllers/acme_challenges_controller_spec.rb
new file mode 100644
index 00000000000..cee06bed27b
--- /dev/null
+++ b/spec/controllers/acme_challenges_controller_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AcmeChallengesController do
+ describe '#show' do
+ let!(:acme_order) { create(:pages_domain_acme_order) }
+
+ def make_request(domain, token)
+ get(:show, params: { domain: domain, token: token })
+ end
+
+ before do
+ make_request(domain, token)
+ end
+
+ context 'with right domain and token' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders acme challenge file content' do
+ expect(response.body).to eq(acme_order.challenge_file_content)
+ end
+ end
+
+ context 'when domain is invalid' do
+ let(:domain) { 'somewrongdomain.com' }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { 'wrongtoken' }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/appearances_controller_spec.rb b/spec/controllers/admin/appearances_controller_spec.rb
index 4ddd0953267..621aa148301 100644
--- a/spec/controllers/admin/appearances_controller_spec.rb
+++ b/spec/controllers/admin/appearances_controller_spec.rb
@@ -1,15 +1,17 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::AppearancesController do
let(:admin) { create(:admin) }
- let(:header_message) { "Header message" }
- let(:footer_message) { "Footer" }
+ let(:header_message) { 'Header message' }
+ let(:footer_message) { 'Footer' }
describe 'POST #create' do
let(:create_params) do
{
- title: "Foo",
- description: "Bar",
+ title: 'Foo',
+ description: 'Bar',
header_message: header_message,
footer_message: footer_message
}
@@ -24,9 +26,26 @@ describe Admin::AppearancesController do
expect(Appearance.current).to have_attributes(
header_message: header_message,
- footer_message: footer_message
+ footer_message: footer_message,
+ email_header_and_footer_enabled: false,
+ message_background_color: '#E75E40',
+ message_font_color: '#FFFFFF'
)
end
+
+ context 'when enabling header and footer in email' do
+ it 'creates appearance with enabled flag' do
+ create_params[:email_header_and_footer_enabled] = true
+
+ post :create, params: { appearance: create_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: true
+ )
+ end
+ end
end
describe 'PUT #update' do
@@ -48,8 +67,25 @@ describe Admin::AppearancesController do
expect(Appearance.current).to have_attributes(
header_message: header_message,
- footer_message: footer_message
+ footer_message: footer_message,
+ email_header_and_footer_enabled: false,
+ message_background_color: '#E75E40',
+ message_font_color: '#FFFFFF'
)
end
+
+ context 'when enabling header and footer in email' do
+ it 'updates appearance with enabled flag' do
+ update_params[:email_header_and_footer_enabled] = true
+
+ post :update, params: { appearance: update_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: true
+ )
+ end
+ end
end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 9af472df74e..b89348b7a7e 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ApplicationSettingsController do
@@ -85,6 +87,35 @@ describe Admin::ApplicationSettingsController do
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
end
+
+ it 'updates the default_project_creation for string value' do
+ put :update, params: { application_setting: { default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } }
+
+ expect(response).to redirect_to(admin_application_settings_path)
+ expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ end
+
+ context 'external policy classification settings' do
+ let(:settings) do
+ {
+ external_authorization_service_enabled: true,
+ external_authorization_service_url: 'https://custom.service/',
+ external_authorization_service_default_label: 'default',
+ external_authorization_service_timeout: 3,
+ external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'),
+ external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'),
+ external_auth_client_key_pass: "5iveL!fe"
+ }
+ end
+
+ it 'updates settings when the feature is available' do
+ put :update, params: { application_setting: settings }
+
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+ end
end
describe 'PUT #reset_registration_token' do
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
index 7e1ce70dc7d..9c9f658a0bd 100644
--- a/spec/controllers/admin/applications_controller_spec.rb
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ApplicationsController do
diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb
new file mode 100644
index 00000000000..76f261e7d3f
--- /dev/null
+++ b/spec/controllers/admin/clusters/applications_controller_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::Clusters::ApplicationsController do
+ include AccessMatchersForController
+
+ def current_application
+ Clusters::Cluster::APPLICATIONS[application]
+ end
+
+ shared_examples 'a secure endpoint' do
+ it { expect { subject }.to be_allowed_for(:admin) }
+ it { expect { subject }.to be_denied_for(:user) }
+ it { expect { subject }.to be_denied_for(:external) }
+
+ context 'when instance clusters are disabled' do
+ before do
+ stub_feature_flags(instance_clusters: false)
+ end
+
+ it 'returns 404' do
+ is_expected.to have_http_status(:not_found)
+ end
+ end
+ end
+
+ let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
+
+ describe 'POST create' do
+ subject do
+ post :create, params: params
+ end
+
+ let(:application) { 'helm' }
+ let(:params) { { application: application, id: cluster.id } }
+
+ describe 'functionality' do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'schedule an application installation' do
+ expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
+
+ expect { subject }.to change { current_application.count }
+ expect(response).to have_http_status(:no_content)
+ expect(cluster.application_helm).to be_scheduled
+ end
+
+ context 'when cluster do not exists' do
+ before do
+ cluster.destroy!
+ end
+
+ it 'return 404' do
+ expect { subject }.not_to change { current_application.count }
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'when application is unknown' do
+ let(:application) { 'unkwnown-app' }
+
+ it 'return 404' do
+ is_expected.to have_http_status(:not_found)
+ end
+ end
+
+ context 'when application is already installing' do
+ before do
+ create(:clusters_applications_helm, :installing, cluster: cluster)
+ end
+
+ it 'returns 400' do
+ is_expected.to have_http_status(:bad_request)
+ end
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ end
+
+ it_behaves_like 'a secure endpoint'
+ end
+ end
+
+ describe 'PATCH update' do
+ subject do
+ patch :update, params: params
+ end
+
+ let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let(:application_name) { application.name }
+ let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
+
+ describe 'functionality' do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ context "when cluster and app exists" do
+ it "schedules an application update" do
+ expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
+
+ is_expected.to have_http_status(:no_content)
+
+ expect(cluster.application_cert_manager).to be_scheduled
+ end
+ end
+
+ context 'when cluster do not exists' do
+ before do
+ cluster.destroy!
+ end
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is unknown' do
+ let(:application_name) { 'unkwnown-app' }
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is already scheduled' do
+ before do
+ application.make_scheduled!
+ end
+
+ it { is_expected.to have_http_status(:bad_request) }
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow(ClusterPatchAppWorker).to receive(:perform_async)
+ end
+
+ it_behaves_like 'a secure endpoint'
+ end
+ end
+end
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
new file mode 100644
index 00000000000..7b77cb186a4
--- /dev/null
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -0,0 +1,540 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::ClustersController do
+ include AccessMatchersForController
+ include GoogleApi::CloudPlatformHelpers
+
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'GET #index' do
+ def get_index(params = {})
+ get :index, params: params
+ end
+
+ context 'when feature flag is not enabled' do
+ before do
+ stub_feature_flags(instance_clusters: false)
+ end
+
+ it 'responds with not found' do
+ get_index
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when feature flag is enabled' do
+ before do
+ stub_feature_flags(instance_clusters: true)
+ end
+
+ describe 'functionality' do
+ context 'when instance has one or more clusters' do
+ let!(:enabled_cluster) do
+ create(:cluster, :provided_by_gcp, :instance)
+ end
+
+ let!(:disabled_cluster) do
+ create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
+ end
+
+ it 'lists available clusters' do
+ get_index
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
+ end
+
+ context 'when page is specified' do
+ let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
+
+ before do
+ allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
+ create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
+ end
+
+ it 'redirects to the page' do
+ get_index(page: last_page)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:clusters).current_page).to eq(last_page)
+ end
+ end
+ end
+
+ context 'when instance does not have a cluster' do
+ it 'returns an empty state page' do
+ get_index
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index, partial: :empty_state)
+ expect(assigns(:clusters)).to eq([])
+ end
+ end
+ end
+ end
+
+ describe 'security' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ it { expect { get_index }.to be_allowed_for(:admin) }
+ it { expect { get_index }.to be_denied_for(:user) }
+ it { expect { get_index }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'GET #new' do
+ def get_new
+ get :new
+ end
+
+ describe 'functionality for new cluster' do
+ context 'when omniauth has been configured' do
+ let(:key) { 'secret-key' }
+ let(:session_key_for_redirect_uri) do
+ GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
+ end
+
+ before do
+ allow(SecureRandom).to receive(:hex).and_return(key)
+ end
+
+ it 'has authorize_url' do
+ get_new
+
+ expect(assigns(:authorize_url)).to include(key)
+ expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path)
+ end
+ end
+
+ context 'when omniauth has not configured' do
+ before do
+ stub_omniauth_setting(providers: [])
+ end
+
+ it 'does not have authorize_url' do
+ get_new
+
+ expect(assigns(:authorize_url)).to be_nil
+ end
+ end
+
+ context 'when access token is valid' do
+ before do
+ stub_google_api_validate_token
+ end
+
+ it 'has new object' do
+ get_new
+
+ expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
+ end
+ end
+
+ context 'when access token is expired' do
+ before do
+ stub_google_api_expired_token
+ end
+
+ it { expect(@valid_gcp_token).to be_falsey }
+ end
+
+ context 'when access token is not stored in session' do
+ it { expect(@valid_gcp_token).to be_falsey }
+ end
+ end
+
+ describe 'functionality for existing cluster' do
+ it 'has new object' do
+ get_new
+
+ expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
+ end
+ end
+
+ describe 'security' do
+ it { expect { get_new }.to be_allowed_for(:admin) }
+ it { expect { get_new }.to be_denied_for(:user) }
+ it { expect { get_new }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'POST #create_gcp' do
+ let(:legacy_abac_param) { 'true' }
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project-12345',
+ legacy_abac: legacy_abac_param
+ }
+ }
+ }
+ end
+
+ def post_create_gcp
+ post :create_gcp, params: params
+ end
+
+ describe 'functionality' do
+ context 'when access token is valid' do
+ before do
+ stub_google_api_validate_token
+ end
+
+ it 'creates a new cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { post_create_gcp }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Providers::Gcp.count }
+
+ cluster = Clusters::Cluster.instance_type.first
+
+ expect(response).to redirect_to(admin_cluster_path(cluster))
+ expect(cluster).to be_gcp
+ expect(cluster).to be_kubernetes
+ expect(cluster.provider_gcp).to be_legacy_abac
+ end
+
+ context 'when legacy_abac param is false' do
+ let(:legacy_abac_param) { 'false' }
+
+ it 'creates a new cluster with legacy_abac_disabled' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { post_create_gcp }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Providers::Gcp.count }
+ expect(Clusters::Cluster.instance_type.first.provider_gcp).not_to be_legacy_abac
+ end
+ end
+ end
+
+ context 'when access token is expired' do
+ before do
+ stub_google_api_expired_token
+ end
+
+ it { expect(@valid_gcp_token).to be_falsey }
+ end
+
+ context 'when access token is not stored in session' do
+ it { expect(@valid_gcp_token).to be_falsey }
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:token_in_session).and_return('token')
+ allow_any_instance_of(described_class)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_zones_clusters_create) do
+ OpenStruct.new(
+ self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
+ status: 'RUNNING'
+ )
+ end
+
+ allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+ end
+
+ it { expect { post_create_gcp }.to be_allowed_for(:admin) }
+ it { expect { post_create_gcp }.to be_denied_for(:user) }
+ it { expect { post_create_gcp }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'POST #create_user' do
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ platform_kubernetes_attributes: {
+ api_url: 'http://my-url',
+ token: 'test'
+ }
+ }
+ }
+ end
+
+ def post_create_user
+ post :create_user, params: params
+ end
+
+ describe 'functionality' do
+ context 'when creates a cluster' do
+ it 'creates a new cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { post_create_user }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Platforms::Kubernetes.count }
+
+ cluster = Clusters::Cluster.instance_type.first
+
+ expect(response).to redirect_to(admin_cluster_path(cluster))
+ expect(cluster).to be_user
+ expect(cluster).to be_kubernetes
+ end
+ end
+
+ context 'when creates a RBAC-enabled cluster' do
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ platform_kubernetes_attributes: {
+ api_url: 'http://my-url',
+ token: 'test',
+ authorization_type: 'rbac'
+ }
+ }
+ }
+ end
+
+ it 'creates a new cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { post_create_user }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Platforms::Kubernetes.count }
+
+ cluster = Clusters::Cluster.instance_type.first
+
+ expect(response).to redirect_to(admin_cluster_path(cluster))
+ expect(cluster).to be_user
+ expect(cluster).to be_kubernetes
+ expect(cluster).to be_platform_kubernetes_rbac
+ end
+ end
+ end
+
+ describe 'security' do
+ it { expect { post_create_user }.to be_allowed_for(:admin) }
+ it { expect { post_create_user }.to be_denied_for(:user) }
+ it { expect { post_create_user }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'GET #cluster_status' do
+ let(:cluster) { create(:cluster, :providing_by_gcp, :instance) }
+
+ def get_cluster_status
+ get :cluster_status,
+ params: {
+ id: cluster
+ },
+ format: :json
+ end
+
+ describe 'functionality' do
+ it 'responds with matching schema' do
+ get_cluster_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('cluster_status')
+ end
+
+ it 'invokes schedule_status_update on each application' do
+ expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update)
+
+ get_cluster_status
+ end
+ end
+
+ describe 'security' do
+ it { expect { get_cluster_status }.to be_allowed_for(:admin) }
+ it { expect { get_cluster_status }.to be_denied_for(:user) }
+ it { expect { get_cluster_status }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'GET #show' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ def get_show
+ get :show,
+ params: {
+ id: cluster
+ }
+ end
+
+ describe 'functionality' do
+ it 'responds successfully' do
+ get_show
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:cluster)).to eq(cluster)
+ end
+ end
+
+ describe 'security' do
+ it { expect { get_show }.to be_allowed_for(:admin) }
+ it { expect { get_show }.to be_denied_for(:user) }
+ it { expect { get_show }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'PUT #update' do
+ def put_update(format: :html)
+ put :update, params: params.merge(
+ id: cluster,
+ format: format
+ )
+ end
+
+ let(:cluster) { create(:cluster, :provided_by_user, :instance) }
+ let(:domain) { 'test-domain.com' }
+
+ let(:params) do
+ {
+ cluster: {
+ enabled: false,
+ name: 'my-new-cluster-name',
+ base_domain: domain
+ }
+ }
+ end
+
+ it 'updates and redirects back to show page' do
+ put_update
+
+ cluster.reload
+ expect(response).to redirect_to(admin_cluster_path(cluster))
+ expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
+ expect(cluster.enabled).to be_falsey
+ expect(cluster.name).to eq('my-new-cluster-name')
+ expect(cluster.domain).to eq('test-domain.com')
+ end
+
+ context 'when domain is invalid' do
+ let(:domain) { 'http://not-a-valid-domain' }
+
+ it 'does not update cluster attributes' do
+ put_update
+
+ cluster.reload
+ expect(response).to render_template(:show)
+ expect(cluster.name).not_to eq('my-new-cluster-name')
+ expect(cluster.domain).not_to eq('test-domain.com')
+ end
+ end
+
+ context 'when format is json' do
+ context 'when changing parameters' do
+ context 'when valid parameters are used' do
+ let(:params) do
+ {
+ cluster: {
+ enabled: false,
+ name: 'my-new-cluster-name',
+ domain: domain
+ }
+ }
+ end
+
+ it 'updates and redirects back to show page' do
+ put_update(format: :json)
+
+ cluster.reload
+ expect(response).to have_http_status(:no_content)
+ expect(cluster.enabled).to be_falsey
+ expect(cluster.name).to eq('my-new-cluster-name')
+ end
+ end
+
+ context 'when invalid parameters are used' do
+ let(:params) do
+ {
+ cluster: {
+ enabled: false,
+ name: ''
+ }
+ }
+ end
+
+ it 'rejects changes' do
+ put_update(format: :json)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+ end
+ end
+
+ describe 'security' do
+ set(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ it { expect { put_update }.to be_allowed_for(:admin) }
+ it { expect { put_update }.to be_denied_for(:user) }
+ it { expect { put_update }.to be_denied_for(:external) }
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) }
+
+ def delete_destroy
+ delete :destroy,
+ params: {
+ id: cluster
+ }
+ end
+
+ describe 'functionality' do
+ context 'when cluster is provided by GCP' do
+ context 'when cluster is created' do
+ it 'destroys and redirects back to clusters list' do
+ expect { delete_destroy }
+ .to change { Clusters::Cluster.count }.by(-1)
+ .and change { Clusters::Platforms::Kubernetes.count }.by(-1)
+ .and change { Clusters::Providers::Gcp.count }.by(-1)
+
+ expect(response).to redirect_to(admin_clusters_path)
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
+ end
+ end
+
+ context 'when cluster is being created' do
+ let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, :instance) }
+
+ it 'destroys and redirects back to clusters list' do
+ expect { delete_destroy }
+ .to change { Clusters::Cluster.count }.by(-1)
+ .and change { Clusters::Providers::Gcp.count }.by(-1)
+
+ expect(response).to redirect_to(admin_clusters_path)
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
+ end
+ end
+ end
+
+ context 'when cluster is provided by user' do
+ let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, :instance) }
+
+ it 'destroys and redirects back to clusters list' do
+ expect { delete_destroy }
+ .to change { Clusters::Cluster.count }.by(-1)
+ .and change { Clusters::Platforms::Kubernetes.count }.by(-1)
+ .and change { Clusters::Providers::Gcp.count }.by(0)
+
+ expect(response).to redirect_to(admin_clusters_path)
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
+ end
+ end
+ end
+
+ describe 'security' do
+ set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) }
+
+ it { expect { delete_destroy }.to be_allowed_for(:admin) }
+ it { expect { delete_destroy }.to be_denied_for(:user) }
+ it { expect { delete_destroy }.to be_denied_for(:external) }
+ end
+ end
+end
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
index 6eb9f7867d5..4de69a9aea1 100644
--- a/spec/controllers/admin/dashboard_controller_spec.rb
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::DashboardController do
diff --git a/spec/controllers/admin/gitaly_servers_controller_spec.rb b/spec/controllers/admin/gitaly_servers_controller_spec.rb
index b7378aa37d0..c75418a9ad4 100644
--- a/spec/controllers/admin/gitaly_servers_controller_spec.rb
+++ b/spec/controllers/admin/gitaly_servers_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::GitalyServersController do
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 647fce0ecef..509d8944e3a 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::GroupsController do
@@ -60,5 +62,11 @@ describe Admin::GroupsController do
expect(response).to redirect_to(admin_group_path(group))
expect(group.users).not_to include group_user
end
+
+ it 'updates the project_creation_level successfully' do
+ expect do
+ post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } }
+ end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS)
+ end
end
end
diff --git a/spec/controllers/admin/health_check_controller_spec.rb b/spec/controllers/admin/health_check_controller_spec.rb
index e13401fc06b..cf5b27156c0 100644
--- a/spec/controllers/admin/health_check_controller_spec.rb
+++ b/spec/controllers/admin/health_check_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::HealthCheckController do
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index 9bc58344e4e..3c3a16ef9d5 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::HooksController do
diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb
index e5428c8ddeb..68695afdb61 100644
--- a/spec/controllers/admin/identities_controller_spec.rb
+++ b/spec/controllers/admin/identities_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::IdentitiesController do
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
index 944680b3f42..b44797b23e5 100644
--- a/spec/controllers/admin/impersonations_controller_spec.rb
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ImpersonationsController do
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 8166657f674..6b996798b74 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ProjectsController do
@@ -43,6 +45,16 @@ describe Admin::ProjectsController do
end
end
+ describe 'GET /projects.json' do
+ render_views
+
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
describe 'GET /projects/:id' do
render_views
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 4cf14030ca1..78c5e2a2656 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -1,18 +1,37 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::RunnersController do
- let(:runner) { create(:ci_runner) }
+ let!(:runner) { create(:ci_runner) }
before do
sign_in(create(:admin))
end
describe '#index' do
+ render_views
+
it 'lists all runners' do
get :index
expect(response).to have_gitlab_http_status(200)
end
+
+ it 'avoids N+1 queries', :request_store do
+ get :index
+
+ control_count = ActiveRecord::QueryRecorder.new { get :index }.count
+
+ create(:ci_runner, :tagged_only)
+
+ # There is still an N+1 query for `runner.builds.count`
+ expect { get :index }.not_to exceed_query_limit(control_count + 1)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to have_content('tag1')
+ expect(response.body).to have_content('tag2')
+ end
end
describe '#show' do
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
index ec161b92245..1c518dab11e 100644
--- a/spec/controllers/admin/services_controller_spec.rb
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::ServicesController do
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
index 2b946ec1c68..3bc49023357 100644
--- a/spec/controllers/admin/spam_logs_controller_spec.rb
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::SpamLogsController do
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index cb24a6ef142..89a0eba66f7 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Admin::UsersController do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index c9e520317e8..40669ec5451 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+# frozen_string_literal: true
require 'spec_helper'
describe ApplicationController do
@@ -205,8 +206,19 @@ describe ApplicationController do
describe '#check_two_factor_requirement' do
subject { controller.send :check_two_factor_requirement }
+ it 'does not redirect if user has temporary oauth email' do
+ oauth_user = create(:user, email: 'temp-email-for-oauth@email.com')
+ allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
+ allow(controller).to receive(:current_user).and_return(oauth_user)
+
+ expect(controller).not_to receive(:redirect_to)
+
+ subject
+ end
+
it 'does not redirect if 2FA is not required' do
allow(controller).to receive(:two_factor_authentication_required?).and_return(false)
+
expect(controller).not_to receive(:redirect_to)
subject
@@ -215,6 +227,7 @@ describe ApplicationController do
it 'does not redirect if user is not logged in' do
allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
allow(controller).to receive(:current_user).and_return(nil)
+
expect(controller).not_to receive(:redirect_to)
subject
@@ -222,8 +235,9 @@ describe ApplicationController do
it 'does not redirect if user has 2FA enabled' do
allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
- allow(controller).to receive(:current_user).twice.and_return(user)
+ allow(controller).to receive(:current_user).thrice.and_return(user)
allow(user).to receive(:two_factor_enabled?).and_return(true)
+
expect(controller).not_to receive(:redirect_to)
subject
@@ -231,9 +245,10 @@ describe ApplicationController do
it 'does not redirect if 2FA setup can be skipped' do
allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
- allow(controller).to receive(:current_user).twice.and_return(user)
+ allow(controller).to receive(:current_user).thrice.and_return(user)
allow(user).to receive(:two_factor_enabled?).and_return(false)
allow(controller).to receive(:skip_two_factor?).and_return(true)
+
expect(controller).not_to receive(:redirect_to)
subject
@@ -241,10 +256,11 @@ describe ApplicationController do
it 'redirects to 2FA setup otherwise' do
allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
- allow(controller).to receive(:current_user).twice.and_return(user)
+ allow(controller).to receive(:current_user).thrice.and_return(user)
allow(user).to receive(:two_factor_enabled?).and_return(false)
allow(controller).to receive(:skip_two_factor?).and_return(false)
allow(controller).to receive(:profile_two_factor_auth_path)
+
expect(controller).to receive(:redirect_to)
subject
@@ -461,7 +477,7 @@ describe ApplicationController do
end
it 'does log correlation id' do
- Gitlab::CorrelationId.use_id('new-id') do
+ Labkit::Correlation::CorrelationId.use_id('new-id') do
get :index
end
@@ -665,6 +681,48 @@ describe ApplicationController do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store'
end
+
+ it 'does not set the "no-store" header for XHR requests' do
+ sign_in(user)
+
+ get :index, xhr: true
+
+ expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate'
+ end
+ end
+ end
+
+ context 'Gitlab::Session' do
+ controller(described_class) do
+ prepend_before_action do
+ authenticate_sessionless_user!(:rss)
+ end
+
+ def index
+ if Gitlab::Session.current
+ head :created
+ else
+ head :not_found
+ end
+ end
+ end
+
+ it 'is set on web requests' do
+ sign_in(user)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ context 'with sessionless user' do
+ it 'is not set' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ get :index, format: :atom, params: { private_token: personal_access_token.token }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 4458a7223bf..3f1c0ae8ac4 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AutocompleteController do
@@ -371,5 +373,36 @@ describe AutocompleteController do
expect(json_response[3]).to match('name' => 'thumbsdown')
end
end
+
+ context 'Get merge_request_target_branches' do
+ let(:user2) { create(:user) }
+ let!(:merge_request1) { create(:merge_request, source_project: project, target_branch: 'feature') }
+
+ context 'unauthorized user' do
+ it 'returns empty json' do
+ get :merge_request_target_branches
+
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'sign in as user without any accesible merge requests' do
+ it 'returns empty json' do
+ sign_in(user2)
+ get :merge_request_target_branches
+
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'sign in as user with a accesible merge request' do
+ it 'returns json' do
+ sign_in(user)
+ get :merge_request_target_branches
+
+ expect(json_response).to contain_exactly({ 'title' => 'feature' })
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 5eb05f01b8d..c84bb913cad 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::IssuesController do
+ include ExternalAuthorizationServiceHelpers
+
let(:project) { create(:project, :private) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
@@ -136,6 +140,30 @@ describe Boards::IssuesController do
end
end
+ context 'with external authorization' do
+ before do
+ sign_in(user)
+ enable_external_authorization_service_check
+ end
+
+ it 'returns a 403 for group boards' do
+ group = create(:group)
+ group_board = create(:board, group: group)
+
+ list_issues(user: user, board: group_board)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'is successful for project boards' do
+ project_board = create(:board, project: project)
+
+ list_issues(user: user, board: project_board)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
def list_issues(user:, board:, list: nil)
sign_in(user)
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index e5b8aa2e678..e1f75fa3395 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::ListsController do
diff --git a/spec/controllers/concerns/checks_collaboration_spec.rb b/spec/controllers/concerns/checks_collaboration_spec.rb
index d7f110e11f3..7187e239486 100644
--- a/spec/controllers/concerns/checks_collaboration_spec.rb
+++ b/spec/controllers/concerns/checks_collaboration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChecksCollaboration do
diff --git a/spec/controllers/concerns/continue_params_spec.rb b/spec/controllers/concerns/continue_params_spec.rb
index e2f683ae393..5e47f5e9f28 100644
--- a/spec/controllers/concerns/continue_params_spec.rb
+++ b/spec/controllers/concerns/continue_params_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContinueParams do
diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
index 9b2d054f4fc..7a56f7203b0 100644
--- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
+++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ControllerWithCrossProjectAccessCheck do
diff --git a/spec/controllers/concerns/enforces_admin_authentication_spec.rb b/spec/controllers/concerns/enforces_admin_authentication_spec.rb
new file mode 100644
index 00000000000..e6a6702fdea
--- /dev/null
+++ b/spec/controllers/concerns/enforces_admin_authentication_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe EnforcesAdminAuthentication do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ controller(ApplicationController) do
+ # `described_class` is not available in this context
+ include EnforcesAdminAuthentication # rubocop:disable RSpec/DescribedClass
+
+ def index
+ head :ok
+ end
+ end
+
+ describe 'authenticate_admin!' do
+ context 'as an admin' do
+ let(:user) { create(:admin) }
+
+ it 'renders ok' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'as a user' do
+ it 'renders a 404' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb
index 9fe17210d50..aa3cd690e3f 100644
--- a/spec/controllers/concerns/group_tree_spec.rb
+++ b/spec/controllers/concerns/group_tree_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupTree do
diff --git a/spec/controllers/concerns/import_url_params_spec.rb b/spec/controllers/concerns/import_url_params_spec.rb
new file mode 100644
index 00000000000..adbe6e5d3bf
--- /dev/null
+++ b/spec/controllers/concerns/import_url_params_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ImportUrlParams do
+ let(:import_url_params) do
+ controller = OpenStruct.new(params: params).extend(described_class)
+ controller.import_url_params
+ end
+
+ context 'empty URL' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ title: 'Test'
+ })
+ end
+
+ it 'returns empty hash' do
+ expect(import_url_params).to eq({})
+ end
+ end
+
+ context 'url and password separately provided' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ import_url: 'https://url.com',
+ import_url_user: 'user', import_url_password: 'password'
+ })
+ end
+
+ describe '#import_url_params' do
+ it 'returns hash with import_url' do
+ expect(import_url_params).to eq(
+ import_url: "https://user:password@url.com"
+ )
+ end
+ end
+ end
+
+ context 'url with provided empty credentials' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ import_url: 'https://user:password@url.com',
+ import_url_user: '', import_url_password: ''
+ })
+ end
+
+ describe '#import_url_params' do
+ it 'does not change the url' do
+ expect(import_url_params).to eq(
+ import_url: "https://user:password@url.com"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb
index 7e23b56356e..97119438ca1 100644
--- a/spec/controllers/concerns/internal_redirect_spec.rb
+++ b/spec/controllers/concerns/internal_redirect_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InternalRedirect do
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index 8580900215c..fb2cd5ca955 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssuableCollections do
@@ -106,51 +108,77 @@ describe IssuableCollections do
end
describe '#finder_options' do
- let(:params) do
- {
- assignee_id: '1',
- assignee_username: 'user1',
- author_id: '2',
- author_username: 'user2',
- authorized_only: 'yes',
- confidential: true,
- due_date: '2017-01-01',
- group_id: '3',
- iids: '4',
- label_name: 'foo',
- milestone_title: 'bar',
- my_reaction_emoji: 'thumbsup',
- non_archived: 'true',
- project_id: '5',
- scope: 'all',
- search: 'baz',
- sort: 'priority',
- state: 'opened',
- invalid_param: 'invalid_param'
- }
- end
-
- it 'only allows whitelisted params' do
+ before do
allow(controller).to receive(:cookies).and_return({})
allow(controller).to receive(:current_user).and_return(nil)
+ end
- finder_options = controller.send(:finder_options)
-
- expect(finder_options).to eq(ActionController::Parameters.new({
- 'assignee_id' => '1',
- 'assignee_username' => 'user1',
- 'author_id' => '2',
- 'author_username' => 'user2',
- 'confidential' => true,
- 'label_name' => 'foo',
- 'milestone_title' => 'bar',
- 'my_reaction_emoji' => 'thumbsup',
- 'due_date' => '2017-01-01',
- 'scope' => 'all',
- 'search' => 'baz',
- 'sort' => 'priority',
- 'state' => 'opened'
- }).permit!)
+ subject { controller.send(:finder_options).to_h }
+
+ context 'scalar params' do
+ let(:params) do
+ {
+ assignee_id: '1',
+ assignee_username: 'user1',
+ author_id: '2',
+ author_username: 'user2',
+ authorized_only: 'yes',
+ confidential: true,
+ due_date: '2017-01-01',
+ group_id: '3',
+ iids: '4',
+ label_name: 'foo',
+ milestone_title: 'bar',
+ my_reaction_emoji: 'thumbsup',
+ non_archived: 'true',
+ project_id: '5',
+ scope: 'all',
+ search: 'baz',
+ sort: 'priority',
+ state: 'opened',
+ invalid_param: 'invalid_param'
+ }
+ end
+
+ it 'only allows whitelisted params' do
+ is_expected.to include({
+ 'assignee_id' => '1',
+ 'assignee_username' => 'user1',
+ 'author_id' => '2',
+ 'author_username' => 'user2',
+ 'confidential' => true,
+ 'label_name' => 'foo',
+ 'milestone_title' => 'bar',
+ 'my_reaction_emoji' => 'thumbsup',
+ 'due_date' => '2017-01-01',
+ 'scope' => 'all',
+ 'search' => 'baz',
+ 'sort' => 'priority',
+ 'state' => 'opened'
+ })
+
+ is_expected.not_to include('invalid_param')
+ end
+ end
+
+ context 'array params' do
+ let(:params) do
+ {
+ assignee_username: %w[user1 user2],
+ label_name: %w[label1 label2],
+ invalid_param: 'invalid_param',
+ invalid_array: ['param']
+ }
+ end
+
+ it 'only allows whitelisted params' do
+ is_expected.to include({
+ 'label_name' => %w[label1 label2],
+ 'assignee_username' => %w[user1 user2]
+ })
+
+ is_expected.not_to include('invalid_param', 'invalid_array')
+ end
end
end
end
diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb
index 7b49d4b6a3a..cb8c0b8f71c 100644
--- a/spec/controllers/concerns/lfs_request_spec.rb
+++ b/spec/controllers/concerns/lfs_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LfsRequest do
diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb
new file mode 100644
index 00000000000..5834b1ef37f
--- /dev/null
+++ b/spec/controllers/concerns/project_unauthorized_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectUnauthorized do
+ include ExternalAuthorizationServiceHelpers
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
+ end
+
+ render_views
+
+ describe '.on_routable_not_found' do
+ controller(::Projects::ApplicationController) do
+ def show
+ head :ok
+ end
+ end
+
+ let(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'renders a 200 when the service allows access to the project' do
+ external_service_allow_access(user, project)
+
+ get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'renders a 403 when the service denies access to the project' do
+ external_service_deny_access(user, project)
+
+ get :show, params: { namespace_id: project.namespace.to_param, id: project.to_param }
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(response.body).to match("External authorization denied access to this project")
+ end
+
+ it 'renders a 404 when the user cannot see the project at all' do
+ other_project = create(:project, :private)
+
+ get :show, params: { namespace_id: other_project.namespace.to_param, id: other_project.to_param }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/controllers/concerns/routable_actions_spec.rb b/spec/controllers/concerns/routable_actions_spec.rb
new file mode 100644
index 00000000000..59d48c68b9c
--- /dev/null
+++ b/spec/controllers/concerns/routable_actions_spec.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe RoutableActions do
+ controller(::ApplicationController) do
+ include RoutableActions # rubocop:disable RSpec/DescribedClass
+
+ before_action :routable
+
+ def routable
+ @klass = params[:type].constantize
+ @routable = find_routable!(params[:type].constantize, params[:id])
+ end
+
+ def show
+ head :ok
+ end
+ end
+
+ def get_routable(routable)
+ get :show, params: { id: routable.full_path, type: routable.class }
+ end
+
+ describe '#find_routable!' do
+ context 'when signed in' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'with a project' do
+ let(:routable) { create(:project) }
+
+ context 'when authorized' do
+ before do
+ routable.add_guest(user)
+ end
+
+ it 'returns the project' do
+ get_routable(routable)
+
+ expect(assigns[:routable]).to be_a(Project)
+ end
+
+ it 'allows access' do
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'prevents access when not authorized' do
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'with a group' do
+ let(:routable) { create(:group, :private) }
+
+ context 'when authorized' do
+ before do
+ routable.add_guest(user)
+ end
+
+ it 'returns the group' do
+ get_routable(routable)
+
+ expect(assigns[:routable]).to be_a(Group)
+ end
+
+ it 'allows access' do
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'prevents access when not authorized' do
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'with a user' do
+ let(:routable) { user }
+
+ it 'allows access when authorized' do
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'prevents access when unauthorized' do
+ allow(subject).to receive(:can?).and_return(false)
+
+ get_routable(user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to sign in for private resouces' do
+ routable = create(:project, :private)
+
+ get_routable(routable)
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(response.location).to end_with('/users/sign_in')
+ end
+ end
+ end
+
+ describe '#perform_not_found_actions' do
+ let(:routable) { create(:project) }
+
+ before do
+ sign_in(create(:user))
+ end
+
+ it 'performs multiple checks' do
+ last_check_called = false
+ checks = [proc {}, proc { last_check_called = true }]
+ allow(subject).to receive(:not_found_actions).and_return(checks)
+
+ get_routable(routable)
+
+ expect(last_check_called).to eq(true)
+ end
+
+ it 'performs checks in the context of the controller' do
+ check = lambda { |routable| redirect_to routable }
+ allow(subject).to receive(:not_found_actions).and_return([check])
+
+ get_routable(routable)
+
+ expect(response.location).to end_with(routable.to_param)
+ end
+
+ it 'skips checks once one has resulted in a render/redirect' do
+ first_check = proc { render plain: 'first' }
+ second_check = proc { render plain: 'second' }
+ allow(subject).to receive(:not_found_actions).and_return([first_check, second_check])
+
+ get_routable(routable)
+
+ expect(response.body).to eq('first')
+ end
+ end
+end
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb
index cf3b24f50a3..a3ce08f736c 100644
--- a/spec/controllers/concerns/send_file_upload_spec.rb
+++ b/spec/controllers/concerns/send_file_upload_spec.rb
@@ -1,3 +1,6 @@
+# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe SendFileUpload do
@@ -11,7 +14,7 @@ describe SendFileUpload do
# user/:id
def dynamic_segment
- File.join(model.class.to_s.underscore, model.id.to_s)
+ File.join(model.class.underscore, model.id.to_s)
end
end
end
@@ -112,7 +115,7 @@ describe SendFileUpload do
it 'sends a file with a custom type' do
headers = double
- expected_headers = %r(response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application/ecmascript)
+ expected_headers = /response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application%2Fecmascript/
expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/)
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index c8d99f79277..48373d29412 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Dashboard::GroupsController do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
before do
@@ -11,33 +15,43 @@ describe Dashboard::GroupsController do
expect(described_class).to include(GroupTree)
end
- it 'only includes projects the user is a member of' do
- member_of_group = create(:group)
- member_of_group.add_developer(user)
- create(:group, :public)
+ describe '#index' do
+ it 'only includes projects the user is a member of' do
+ member_of_group = create(:group)
+ member_of_group.add_developer(user)
+ create(:group, :public)
- get :index
+ get :index
- expect(assigns(:groups)).to contain_exactly(member_of_group)
- end
+ expect(assigns(:groups)).to contain_exactly(member_of_group)
+ end
- context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
- let!(:top_level_result) { create(:group, name: 'chef-top') }
- let!(:top_level_a) { create(:group, name: 'top-a') }
- let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
- let!(:other_group) { create(:group, name: 'other') }
+ context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
+ let!(:top_level_result) { create(:group, name: 'chef-top') }
+ let!(:top_level_a) { create(:group, name: 'top-a') }
+ let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
+ let!(:other_group) { create(:group, name: 'other') }
- before do
- top_level_result.add_maintainer(user)
- top_level_a.add_maintainer(user)
+ before do
+ top_level_result.add_maintainer(user)
+ top_level_a.add_maintainer(user)
+ end
+
+ it 'renders only groups the user is a member of when searching hierarchy correctly' do
+ get :index, params: { filter: 'chef' }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ all_groups = [top_level_result, top_level_a, sub_level_result_a]
+ expect(assigns(:groups)).to contain_exactly(*all_groups)
+ end
end
- it 'renders only groups the user is a member of when searching hierarchy correctly' do
- get :index, params: { filter: 'chef' }, format: :json
+ it 'works when the external authorization service is enabled' do
+ enable_external_authorization_service_check
+
+ get :index
expect(response).to have_gitlab_http_status(200)
- all_groups = [top_level_result, top_level_a, sub_level_result_a]
- expect(assigns(:groups)).to contain_exactly(*all_groups)
end
end
end
diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb
index a3bfb2f3a87..cb9c3660b9b 100644
--- a/spec/controllers/dashboard/labels_controller_spec.rb
+++ b/spec/controllers/dashboard/labels_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Dashboard::LabelsController do
@@ -13,13 +15,17 @@ describe Dashboard::LabelsController do
describe "#index" do
let!(:unrelated_label) { create(:label, project: create(:project, :public)) }
+ subject { get :index, format: :json }
+
it 'returns global labels for projects the user has a relationship with' do
- get :index, format: :json
+ subject
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1)
expect(json_response[0]["id"]).to be_nil
expect(json_response[0]["title"]).to eq(label.title)
end
+
+ it_behaves_like 'disabled when using an external authorization service'
end
end
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 4b164d0aa6b..4de537ae6f8 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Dashboard::MilestonesController do
@@ -13,7 +15,7 @@ describe Dashboard::MilestonesController do
)
end
let(:issue) { create(:issue, project: project, milestone: project_milestone) }
- let(:group_issue) { create(:issue, milestone: group_milestone) }
+ let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) }
let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) }
@@ -75,11 +77,17 @@ describe Dashboard::MilestonesController do
expect(response.body).not_to include(project_milestone.title)
end
- it 'should show counts of group and project milestones to which the user belongs to' do
+ it 'shows counts of group and project milestones to which the user belongs to' do
get :index
expect(response.body).to include("Open\n<span class=\"badge badge-pill\">2</span>")
expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
end
+
+ context 'external authorization' do
+ subject { get :index }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
end
end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index 2975205e09c..ea68eae12ed 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -1,5 +1,55 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Dashboard::ProjectsController do
- it_behaves_like 'authenticates sessionless user', :index, :atom
+ include ExternalAuthorizationServiceHelpers
+
+ describe '#index' do
+ context 'user not logged in' do
+ it_behaves_like 'authenticates sessionless user', :index, :atom
+ end
+
+ context 'user logged in' do
+ before do
+ sign_in create(:user)
+ end
+
+ context 'external authorization' do
+ it 'works when the external authorization service is enabled' do
+ enable_external_authorization_service_check
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+ end
+
+ context 'json requests' do
+ render_views
+
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET /projects.json' do
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET /starred.json' do
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+ end
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index d88beaff0e1..6243ddc03c0 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Dashboard::TodosController do
@@ -105,6 +107,12 @@ describe Dashboard::TodosController do
end
end
end
+
+ context 'external authorization' do
+ subject { get :index }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
end
describe 'PATCH #restore' do
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index c857a78d5e8..a733c3ecaa1 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DashboardController do
@@ -23,4 +25,37 @@ describe DashboardController do
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
+
+ describe "#check_filters_presence!" do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ get :merge_requests, params: params
+ end
+
+ context "no filters" do
+ let(:params) { {} }
+
+ it 'sets @no_filters_set to false' do
+ expect(assigns[:no_filters_set]).to eq(true)
+ end
+ end
+
+ context "scalar filters" do
+ let(:params) { { author_id: user.id } }
+
+ it 'sets @no_filters_set to false' do
+ expect(assigns[:no_filters_set]).to eq(false)
+ end
+ end
+
+ context "array filters" do
+ let(:params) { { label_name: ['bug'] } }
+
+ it 'sets @no_filters_set to false' do
+ expect(assigns[:no_filters_set]).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/controllers/explore/groups_controller_spec.rb b/spec/controllers/explore/groups_controller_spec.rb
index 9e0ad9ea86f..5a32d8ca0d3 100644
--- a/spec/controllers/explore/groups_controller_spec.rb
+++ b/spec/controllers/explore/groups_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Explore::GroupsController do
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index d57367e931e..463586ee422 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -1,6 +1,38 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Explore::ProjectsController do
+ describe 'GET #index.json' do
+ render_views
+
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #trending.json' do
+ render_views
+
+ before do
+ get :trending, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #starred.json' do
+ render_views
+
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
describe 'GET #trending' do
context 'sorting by update date' do
let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb
index 1e8e82da4f3..940bf9c6828 100644
--- a/spec/controllers/google_api/authorizations_controller_spec.rb
+++ b/spec/controllers/google_api/authorizations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GoogleApi::AuthorizationsController do
@@ -6,7 +8,7 @@ describe GoogleApi::AuthorizationsController do
let(:token) { 'token' }
let(:expires_at) { 1.hour.since.strftime('%s') }
- subject { get :callback, params: { code: 'xxx', state: @state } }
+ subject { get :callback, params: { code: 'xxx', state: state } }
before do
sign_in(user)
@@ -15,35 +17,57 @@ describe GoogleApi::AuthorizationsController do
.to receive(:get_token).and_return([token, expires_at])
end
- it 'sets token and expires_at in session' do
- subject
+ shared_examples_for 'access denied' do
+ it 'returns a 404' do
+ subject
- expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token])
- .to eq(token)
- expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at])
- .to eq(expires_at)
+ expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
+ expect(response).to have_http_status(:not_found)
+ end
end
- context 'when redirect uri key is stored in state' do
- set(:project) { create(:project) }
- let(:redirect_uri) { project_clusters_url(project).to_s }
+ context 'session key is present' do
+ let(:session_key) { 'session-key' }
+ let(:redirect_uri) { 'example.com' }
before do
- @state = GoogleApi::CloudPlatform::Client
- .new_session_key_for_redirect_uri do |key|
- session[key] = redirect_uri
+ session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri
+ end
+
+ context 'session key matches state param' do
+ let(:state) { session_key }
+
+ it 'sets token and expires_at in session' do
+ subject
+
+ expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token])
+ .to eq(token)
+ expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at])
+ .to eq(expires_at)
+ end
+
+ it 'redirects to the URL stored in state param' do
+ expect(subject).to redirect_to(redirect_uri)
end
end
- it 'redirects to the URL stored in state param' do
- expect(subject).to redirect_to(redirect_uri)
+ context 'session key does not match state param' do
+ let(:state) { 'bad-key' }
+
+ it_behaves_like 'access denied'
end
- end
- context 'when redirection url is not stored in state' do
- it 'redirects to root_path' do
- expect(subject).to redirect_to(root_path)
+ context 'state param is blank' do
+ let(:state) { '' }
+
+ it_behaves_like 'access denied'
end
end
+
+ context 'state param is present, but session key is blank' do
+ let(:state) { 'session-key' }
+
+ it_behaves_like 'access denied'
+ end
end
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index a0f40874db1..c19a752b07b 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -1,112 +1,45 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GraphqlController do
- describe 'execute' do
- let(:user) { nil }
-
- before do
- sign_in(user) if user
-
- run_test_query!
- end
-
- subject { query_response }
+ before do
+ stub_feature_flags(graphql: true)
+ end
- context 'graphql is disabled by feature flag' do
- let(:user) { nil }
+ describe 'POST #execute' do
+ context 'when user is logged in' do
+ let(:user) { create(:user) }
before do
- stub_feature_flags(graphql: false)
- end
-
- it 'returns 404' do
- run_test_query!
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- context 'signed out' do
- let(:user) { nil }
-
- it 'runs the query with current_user: nil' do
- is_expected.to eq('echo' => 'nil says: test success')
- end
- end
-
- context 'signed in' do
- let(:user) { create(:user, username: 'Simon') }
-
- it 'runs the query with current_user set' do
- is_expected.to eq('echo' => '"Simon" says: test success')
- end
- end
-
- context 'invalid variables' do
- it 'returns an error' do
- run_test_query!(variables: "This is not JSON")
-
- expect(response).to have_gitlab_http_status(422)
- expect(json_response['errors'].first['message']).not_to be_nil
+ sign_in(user)
end
- end
- end
-
- context 'token authentication' do
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- let(:user) { create(:user, username: 'Simon') }
- let(:personal_access_token) { create(:personal_access_token, user: user) }
-
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it 'logs the user in' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
- run_test_query!(private_token: personal_access_token.token)
+ it 'returns 200 when user can access API' do
+ post :execute
expect(response).to have_gitlab_http_status(200)
- expect(query_response).to eq('echo' => '"Simon" says: test success')
end
- end
-
- context 'when the personal access token has no api scope' do
- it 'does not log the user in' do
- personal_access_token.update(scopes: [:read_user])
- run_test_query!(private_token: personal_access_token.token)
+ it 'returns access denied template when user cannot access API' do
+ # User cannot access API in a couple of cases
+ # * When user is internal(like ghost users)
+ # * When user is blocked
+ expect(Ability).to receive(:allowed?).with(user, :access_api, :global).and_return(false)
- expect(response).to have_gitlab_http_status(200)
+ post :execute
- expect(query_response).to eq('echo' => 'nil says: test success')
+ expect(response.status).to eq(403)
+ expect(response).to render_template('errors/access_denied')
end
end
- context 'without token' do
- it 'shows public data' do
- run_test_query!
+ context 'when user is not logged in' do
+ it 'returns 200' do
+ post :execute
- expect(query_response).to eq('echo' => 'nil says: test success')
+ expect(response).to have_gitlab_http_status(200)
end
end
end
-
- # Chosen to exercise all the moving parts in GraphqlController#execute
- def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil)
- query = <<~QUERY
- query Echo($text: String) {
- echo(text: $text)
- }
- QUERY
-
- post :execute, params: { query: query, operationName: 'Echo', variables: variables, private_token: private_token }
- end
-
- def query_response
- json_response['data']
- end
end
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
index 772d1d0c1dd..7fffafaa2d4 100644
--- a/spec/controllers/groups/avatars_controller_spec.rb
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::AvatarsController do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
@@ -15,4 +19,12 @@ describe Groups::AvatarsController do
expect(@group.avatar.present?).to be_falsey
expect(@group).to be_valid
end
+
+ it 'works when external authorization service is enabled' do
+ enable_external_authorization_service_check
+
+ delete :destroy, params: { group_id: group }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
end
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 4228e727b52..881d0018b79 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::BoardsController do
@@ -22,28 +24,6 @@ describe Groups::BoardsController do
expect(response.content_type).to eq 'text/html'
end
- it 'redirects to latest visited board' do
- board = create(:board, group: group)
- create(:board_group_recent_visit, group: board.group, board: board, user: user)
-
- list_boards
-
- expect(response).to redirect_to(group_board_path(id: board.id))
- end
-
- it 'renders template if visited board is not found' do
- temporary_board = create(:board, group: group)
- visited = create(:board_group_recent_visit, group: temporary_board.group, board: temporary_board, user: user)
- temporary_board.delete
-
- allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
-
- list_boards
-
- expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
- end
-
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
@@ -104,6 +84,10 @@ describe Groups::BoardsController do
end
end
+ it_behaves_like 'disabled when using an external authorization service' do
+ subject { list_boards }
+ end
+
def list_boards(format: :html)
get :index, params: { group_id: group }, format: format
end
@@ -182,6 +166,10 @@ describe Groups::BoardsController do
end
end
+ it_behaves_like 'disabled when using an external authorization service' do
+ subject { read_board board: board }
+ end
+
def read_board(board:, format: :html)
get :show, params: {
group_id: group,
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index e1b97013408..02fb971bd9a 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::ChildrenController do
+ include ExternalAuthorizationServiceHelpers
+
let(:group) { create(:group, :public) }
let(:user) { create(:user) }
let!(:group_member) { create(:group_member, group: group, user: user) }
@@ -317,5 +321,15 @@ describe Groups::ChildrenController do
end
end
end
+
+ context 'external authorization' do
+ it 'works when external authorization service is enabled' do
+ enable_external_authorization_service_check
+
+ get :index, params: { group_id: group }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
end
diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb
index dd5263b077c..16a63536ea6 100644
--- a/spec/controllers/groups/clusters/applications_controller_spec.rb
+++ b/spec/controllers/groups/clusters/applications_controller_spec.rb
@@ -9,9 +9,25 @@ describe Groups::Clusters::ApplicationsController do
Clusters::Cluster::APPLICATIONS[application]
end
+ shared_examples 'a secure endpoint' do
+ it { expect { subject }.to be_allowed_for(:admin) }
+ it { expect { subject }.to be_allowed_for(:owner).of(group) }
+ it { expect { subject }.to be_allowed_for(:maintainer).of(group) }
+ it { expect { subject }.to be_denied_for(:developer).of(group) }
+ it { expect { subject }.to be_denied_for(:reporter).of(group) }
+ it { expect { subject }.to be_denied_for(:guest).of(group) }
+ it { expect { subject }.to be_denied_for(:user) }
+ it { expect { subject }.to be_denied_for(:external) }
+ end
+
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+ let(:group) { cluster.group }
+
describe 'POST create' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { cluster.group }
+ subject do
+ post :create, params: params.merge(group_id: group)
+ end
+
let(:application) { 'helm' }
let(:params) { { application: application, id: cluster.id } }
@@ -26,7 +42,7 @@ describe Groups::Clusters::ApplicationsController do
it 'schedule an application installation' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
- expect { go }.to change { current_application.count }
+ expect { subject }.to change { current_application.count }
expect(response).to have_http_status(:no_content)
expect(cluster.application_helm).to be_scheduled
end
@@ -37,7 +53,7 @@ describe Groups::Clusters::ApplicationsController do
end
it 'return 404' do
- expect { go }.not_to change { current_application.count }
+ expect { subject }.not_to change { current_application.count }
expect(response).to have_http_status(:not_found)
end
end
@@ -46,9 +62,7 @@ describe Groups::Clusters::ApplicationsController do
let(:application) { 'unkwnown-app' }
it 'return 404' do
- go
-
- expect(response).to have_http_status(:not_found)
+ is_expected.to have_http_status(:not_found)
end
end
@@ -58,9 +72,7 @@ describe Groups::Clusters::ApplicationsController do
end
it 'returns 400' do
- go
-
- expect(response).to have_http_status(:bad_request)
+ is_expected.to have_http_status(:bad_request)
end
end
end
@@ -70,18 +82,66 @@ describe Groups::Clusters::ApplicationsController do
allow(ClusterInstallAppWorker).to receive(:perform_async)
end
- it { expect { go }.to be_allowed_for(:admin) }
- it { expect { go }.to be_allowed_for(:owner).of(group) }
- it { expect { go }.to be_allowed_for(:maintainer).of(group) }
- it { expect { go }.to be_denied_for(:developer).of(group) }
- it { expect { go }.to be_denied_for(:reporter).of(group) }
- it { expect { go }.to be_denied_for(:guest).of(group) }
- it { expect { go }.to be_denied_for(:user) }
- it { expect { go }.to be_denied_for(:external) }
+ it_behaves_like 'a secure endpoint'
end
+ end
- def go
- post :create, params: params.merge(group_id: group)
+ describe 'PATCH update' do
+ subject do
+ patch :update, params: params.merge(group_id: group)
+ end
+
+ let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let(:application_name) { application.name }
+ let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
+
+ describe 'functionality' do
+ let(:user) { create(:user) }
+
+ before do
+ group.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context "when cluster and app exists" do
+ it "schedules an application update" do
+ expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
+
+ is_expected.to have_http_status(:no_content)
+
+ expect(cluster.application_cert_manager).to be_scheduled
+ end
+ end
+
+ context 'when cluster do not exists' do
+ before do
+ cluster.destroy!
+ end
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is unknown' do
+ let(:application_name) { 'unkwnown-app' }
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is already scheduled' do
+ before do
+ application.make_scheduled!
+ end
+
+ it { is_expected.to have_http_status(:bad_request) }
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow(ClusterPatchAppWorker).to receive(:perform_async)
+ end
+
+ it_behaves_like 'a secure endpoint'
end
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index ef23ffaa843..7349cb7094c 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -189,6 +189,7 @@ describe Groups::ClustersController do
{
cluster: {
name: 'new-cluster',
+ managed: '1',
provider_gcp_attributes: {
gcp_project_id: 'gcp-project-12345',
legacy_abac: legacy_abac_param
@@ -218,6 +219,7 @@ describe Groups::ClustersController do
expect(cluster).to be_gcp
expect(cluster).to be_kubernetes
expect(cluster.provider_gcp).to be_legacy_abac
+ expect(cluster).to be_managed
end
context 'when legacy_abac param is false' do
@@ -278,6 +280,7 @@ describe Groups::ClustersController do
{
cluster: {
name: 'new-cluster',
+ managed: '1',
platform_kubernetes_attributes: {
api_url: 'http://my-url',
token: 'test'
@@ -303,6 +306,7 @@ describe Groups::ClustersController do
expect(response).to redirect_to(group_cluster_path(group, cluster))
expect(cluster).to be_user
expect(cluster).to be_kubernetes
+ expect(cluster).to be_managed
end
end
@@ -334,6 +338,29 @@ describe Groups::ClustersController do
expect(cluster).to be_platform_kubernetes_rbac
end
end
+
+ context 'when creates a user-managed cluster' do
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ managed: '0',
+ platform_kubernetes_attributes: {
+ api_url: 'http://my-url',
+ token: 'test',
+ authorization_type: 'rbac'
+ }
+ }
+ }
+ end
+
+ it 'creates a new user-managed cluster' do
+ go
+
+ cluster = group.clusters.first
+ expect(cluster.managed?).to be_falsy
+ end
+ end
end
describe 'security' do
@@ -455,7 +482,7 @@ describe Groups::ClustersController do
context 'when domain is invalid' do
let(:domain) { 'http://not-a-valid-domain' }
- it 'should not update cluster attributes' do
+ it 'does not update cluster attributes' do
go
cluster.reload
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 3a801fabafc..413598ddde0 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -1,8 +1,13 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::GroupMembersController do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
+ let(:membership) { create(:group_member, group: group) }
describe 'GET index' do
it 'renders index with 200 status code' do
@@ -263,4 +268,87 @@ describe Groups::GroupMembersController do
end
end
end
+
+ context 'with external authorization enabled' do
+ before do
+ enable_external_authorization_service_check
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ it 'is successful' do
+ get :index, params: { group_id: group }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'POST #create' do
+ it 'is successful' do
+ post :create, params: { group_id: group, users: user, access_level: Gitlab::Access::GUEST }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'PUT #update' do
+ it 'is successful' do
+ put :update,
+ params: {
+ group_member: { access_level: Gitlab::Access::GUEST },
+ group_id: group,
+ id: membership
+ },
+ format: :js
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ it 'is successful' do
+ delete :destroy, params: { group_id: group, id: membership }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'POST #destroy' do
+ it 'is successful' do
+ sign_in(create(:user))
+
+ post :request_access, params: { group_id: group }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'POST #approve_request_access' do
+ it 'is successful' do
+ access_request = create(:group_member, :access_request, group: group)
+ post :approve_access_request, params: { group_id: group, id: access_request }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'DELETE #leave' do
+ it 'is successful' do
+ group.add_owner(create(:user))
+
+ delete :leave, params: { group_id: group }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'POST #resend_invite' do
+ it 'is successful' do
+ post :resend_invite, params: { group_id: group, id: membership }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index fa664a29066..3cc6fc6f066 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::LabelsController do
@@ -37,6 +39,12 @@ describe Groups::LabelsController do
expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title])
end
end
+
+ context 'external authorization' do
+ subject { get :index, params: { group_id: group.to_param } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
end
describe 'POST #toggle_subscription' do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 043cf28514b..19b18091aef 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::MilestonesController do
@@ -80,6 +82,12 @@ describe Groups::MilestonesController do
expect(response.content_type).to eq 'application/json'
end
end
+
+ context 'external authorization' do
+ subject { get :index, params: { group_id: group.to_param } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
end
describe '#show' do
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 469459bfc02..91f9e2c7832 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::RunnersController do
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index 40673d10b91..70b3a5fb496 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::Settings::CiCdController do
+ include ExternalAuthorizationServiceHelpers
+
let(:group) { create(:group) }
let(:user) { create(:user) }
@@ -33,6 +37,19 @@ describe Groups::Settings::CiCdController do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ context 'external authorization' do
+ before do
+ enable_external_authorization_service_check
+ group.add_owner(user)
+ end
+
+ it 'renders show with 200 status code' do
+ get :show, params: { group_id: group }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
describe 'PUT #reset_registration_token' do
@@ -66,4 +83,77 @@ describe Groups::Settings::CiCdController do
end
end
end
+
+ describe 'PATCH #update_auto_devops' do
+ let(:auto_devops_param) { '1' }
+
+ subject do
+ patch :update_auto_devops, params: {
+ group_id: group,
+ group: { auto_devops_enabled: auto_devops_param }
+ }
+ end
+
+ context 'when user does not have enough permission' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when user has enough privileges' do
+ before do
+ group.add_owner(user)
+ end
+
+ it { is_expected.to redirect_to(group_settings_ci_cd_path) }
+
+ context 'when service execution went wrong' do
+ before do
+ allow_any_instance_of(Groups::AutoDevopsService).to receive(:execute).and_return(false)
+ allow_any_instance_of(Group).to receive_message_chain(:errors, :full_messages)
+ .and_return(['Error 1'])
+
+ subject
+ end
+
+ it 'returns a flash alert' do
+ expect(response).to set_flash[:alert]
+ .to eq("There was a problem updating Auto DevOps pipeline: [\"Error 1\"].")
+ end
+ end
+
+ context 'when service execution was successful' do
+ it 'returns a flash notice' do
+ subject
+
+ expect(response).to set_flash[:notice]
+ .to eq('Auto DevOps pipeline was updated for the group')
+ end
+ end
+
+ context 'when changing auto devops value' do
+ before do
+ subject
+
+ group.reload
+ end
+
+ context 'when explicitly enabling auto devops' do
+ it 'updates group attribute' do
+ expect(group.auto_devops_enabled).to eq(true)
+ end
+ end
+
+ context 'when explicitly disabling auto devops' do
+ let(:auto_devops_param) { '0' }
+
+ it 'updates group attribute' do
+ expect(group.auto_devops_enabled).to eq(false)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb
index dab7700cf64..9f6c558c931 100644
--- a/spec/controllers/groups/shared_projects_controller_spec.rb
+++ b/spec/controllers/groups/shared_projects_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::SharedProjectsController do
@@ -6,6 +8,8 @@ describe Groups::SharedProjectsController do
end
def share_project(project)
+ group.add_developer(user)
+
Projects::GroupLinks::CreateService.new(
project,
user,
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 0104ba827da..0f99a957581 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::UploadsController do
diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb
index 29ec3588316..2d9c5c9d799 100644
--- a/spec/controllers/groups/variables_controller_spec.rb
+++ b/spec/controllers/groups/variables_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::VariablesController do
+ include ExternalAuthorizationServiceHelpers
+
let(:group) { create(:group) }
let(:user) { create(:user) }
@@ -34,4 +38,36 @@ describe Groups::VariablesController do
include_examples 'PATCH #update updates variables'
end
+
+ context 'with external authorization enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ describe 'GET #show' do
+ let!(:variable) { create(:ci_group_variable, group: group) }
+
+ it 'is successful' do
+ get :show, params: { group_id: group }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'PATCH #update' do
+ let!(:variable) { create(:ci_group_variable, group: group) }
+ let(:owner) { group }
+
+ it 'is successful' do
+ patch :update,
+ params: {
+ group_id: group,
+ variables_attributes: [{ id: variable.id, key: 'hello' }]
+ },
+ format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 7d87b33e503..47d7e278183 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupsController do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:group) { create(:group, :public) }
@@ -32,21 +36,46 @@ describe GroupsController do
end
end
+ shared_examples 'details view' do
+ it { is_expected.to render_template('groups/show') }
+
+ context 'as atom' do
+ let!(:event) { create(:event, project: project) }
+ let(:format) { :atom }
+
+ it { is_expected.to render_template('groups/show') }
+
+ it 'assigns events for all the projects in the group' do
+ subject
+ expect(assigns(:events)).to contain_exactly(event)
+ end
+ end
+ end
+
describe 'GET #show' do
before do
sign_in(user)
project
end
- context 'as atom' do
- it 'assigns events for all the projects in the group' do
- create(:event, project: project)
+ let(:format) { :html }
- get :show, params: { id: group.to_param }, format: :atom
+ subject { get :show, params: { id: group.to_param }, format: format }
- expect(assigns(:events)).not_to be_empty
- end
+ it_behaves_like 'details view'
+ end
+
+ describe 'GET #details' do
+ before do
+ sign_in(user)
+ project
end
+
+ let(:format) { :html }
+
+ subject { get :details, params: { id: group.to_param }, format: format }
+
+ it_behaves_like 'details view'
end
describe 'GET edit' do
@@ -112,6 +141,28 @@ describe GroupsController do
end
describe 'POST #create' do
+ it 'allows creating a group' do
+ sign_in(user)
+
+ expect do
+ post :create, params: { group: { name: 'new_group', path: "new_group" } }
+ end.to change { Group.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ context 'authorization' do
+ it 'allows an admin to create a group' do
+ sign_in(create(:admin))
+
+ expect do
+ post :create, params: { group: { name: 'new_group', path: "new_group" } }
+ end.to change { Group.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
@@ -227,9 +278,7 @@ describe GroupsController do
context 'searching' do
before do
- # Remove in https://gitlab.com/gitlab-org/gitlab-ce/issues/54643
- stub_feature_flags(use_cte_for_group_issues_search: false)
- stub_feature_flags(use_subquery_for_group_issues_search: true)
+ stub_feature_flags(attempt_group_search_optimizations: true)
end
it 'works with popularity sort' do
@@ -326,6 +375,13 @@ describe GroupsController do
expect(assigns(:group).errors).not_to be_empty
expect(assigns(:group).path).not_to eq('new_path')
end
+
+ it 'updates the project_creation_level successfully' do
+ post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ end
end
describe '#ensure_canonical_path' do
@@ -543,11 +599,11 @@ describe GroupsController do
}
end
- it 'should return a notice' do
+ it 'returns a notice' do
expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.")
end
- it 'should redirect to the new path' do
+ it 'redirects to the new path' do
expect(response).to redirect_to("/#{new_parent_group.path}/#{group.path}")
end
end
@@ -564,11 +620,11 @@ describe GroupsController do
}
end
- it 'should return a notice' do
+ it 'returns a notice' do
expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.")
end
- it 'should redirect to the new path' do
+ it 'redirects to the new path' do
expect(response).to redirect_to("/#{group.path}")
end
end
@@ -588,12 +644,12 @@ describe GroupsController do
}
end
- it 'should return an alert' do
+ it 'returns an alert' do
expect(flash[:alert]).to eq "Transfer failed: namespace directory cannot be moved"
end
- it 'should redirect to the current path' do
- expect(response).to render_template(:edit)
+ it 'redirects to the current path' do
+ expect(response).to redirect_to(edit_group_path(group))
end
end
@@ -610,7 +666,7 @@ describe GroupsController do
}
end
- it 'should be denied' do
+ it 'is denied' do
expect(response).to have_gitlab_http_status(404)
end
end
@@ -635,4 +691,98 @@ describe GroupsController do
end
end
end
+
+ describe 'external authorization' do
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ context 'with external authorization service enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ describe 'GET #show' do
+ it 'is successful' do
+ get :show, params: { id: group.to_param }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'does not allow other formats' do
+ get :show, params: { id: group.to_param }, format: :atom
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ describe 'GET #edit' do
+ it 'is successful' do
+ get :edit, params: { id: group.to_param }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'GET #new' do
+ it 'is successful' do
+ get :new
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'GET #index' do
+ it 'is successful' do
+ get :index
+
+ # Redirects to the dashboard
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ describe 'POST #create' do
+ it 'creates a group' do
+ expect do
+ post :create, params: { group: { name: 'a name', path: 'a-name' } }
+ end.to change { Group.count }.by(1)
+ end
+ end
+
+ describe 'PUT #update' do
+ it 'updates a group' do
+ expect do
+ put :update, params: { id: group.to_param, group: { name: 'world' } }
+ end.to change { group.reload.name }
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ it 'deletes the group' do
+ delete :destroy, params: { id: group.to_param }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+ end
+
+ describe 'GET #activity' do
+ subject { get :activity, params: { id: group.to_param } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
+
+ describe 'GET #issues' do
+ subject { get :issues, params: { id: group.to_param } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
+
+ describe 'GET #merge_requests' do
+ subject { get :merge_requests, params: { id: group.to_param } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
+ end
end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 29e159ad5d7..19d739fcf4f 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HealthCheckController do
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index f685f2b41c0..fc62a8310aa 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HealthController do
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index dca67c18caa..dbfacf4e42e 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HelpController do
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 0bc09c86939..64a66502732 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::BitbucketController do
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index a125e6ed16d..b89d7317b9c 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::BitbucketServerController do
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index 5f0f6dea821..f1e0923f316 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::FogbugzController do
diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb
index 8cbec79095f..b7bdfcc3dc6 100644
--- a/spec/controllers/import/gitea_controller_spec.rb
+++ b/spec/controllers/import/gitea_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::GiteaController do
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 162dff98ec5..059354870b5 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::GithubController do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index a874a7d36f6..5af7572e74e 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::GitlabController do
diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb
index 55bd8ae7182..51b398895bc 100644
--- a/spec/controllers/import/gitlab_projects_controller_spec.rb
+++ b/spec/controllers/import/gitlab_projects_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::GitlabProjectsController do
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 3e5ed2afd93..17be91c0bbb 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Import::GoogleCodeController do
diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb
new file mode 100644
index 00000000000..85085a8e996
--- /dev/null
+++ b/spec/controllers/import/phabricator_controller_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Import::PhabricatorController do
+ let(:current_user) { create(:user) }
+
+ before do
+ sign_in current_user
+ end
+
+ describe 'GET #new' do
+ subject { get :new }
+
+ context 'when the import source is not available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: [])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(phabricator_import: false)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the import is available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(200) }
+ end
+ end
+
+ describe 'POST #create' do
+ subject(:post_create) { post :create, params: params }
+
+ context 'with valid params' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'creates a project to import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ expect { post_create }.to change { current_user.namespace.projects.reload.size }.from(0).to(1)
+
+ expect(current_user.namespace.projects.last).to be_import
+ end
+ end
+
+ context 'when an import param is missing' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: nil,
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+
+ context 'when a project param is missing' do
+ let(:params) do
+ { phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 7bbaf36e4df..ac0adcd06a3 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InvitesController do
diff --git a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
index c9d36a16008..6d588c8f915 100644
--- a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ldap::OmniauthCallbacksController do
@@ -43,7 +45,7 @@ describe Ldap::OmniauthCallbacksController do
end
context 'sign up' do
- let(:user) { double(email: 'new@example.com') }
+ let(:user) { double(email: +'new@example.com') }
before do
stub_omniauth_setting(block_auto_created_users: false)
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index c7c83369d7c..ee454a7818c 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MetricsController do
diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb
index cf52ce834b6..46328148eff 100644
--- a/spec/controllers/notification_settings_controller_spec.rb
+++ b/spec/controllers/notification_settings_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotificationSettingsController do
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index caf2b87428b..228c97d591d 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Oauth::ApplicationsController do
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index cc8fa2c01b4..41f7684051e 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Oauth::AuthorizationsController do
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index 232a5e2793b..6e374a8daa7 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe OmniauthCallbacksController, type: :controller do
@@ -7,10 +9,14 @@ describe OmniauthCallbacksController, type: :controller do
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
before do
- mock_auth_hash(provider.to_s, extern_uid, user.email)
+ @original_env_config_omniauth_auth = mock_auth_hash(provider.to_s, +extern_uid, user.email)
stub_omniauth_provider(provider, context: request)
end
+ after do
+ Rails.application.env_config['omniauth.auth'] = @original_env_config_omniauth_auth
+ end
+
context 'when the user is on the last sign in attempt' do
let(:extern_uid) { 'my-uid' }
@@ -55,7 +61,7 @@ describe OmniauthCallbacksController, type: :controller do
allow(@routes).to receive(:generate_extras) { [path, []] }
end
- it 'it calls through to the failure handler' do
+ it 'calls through to the failure handler' do
request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch")
request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil)
stub_route_as('/users/auth/saml/callback')
@@ -113,8 +119,35 @@ describe OmniauthCallbacksController, type: :controller do
expect(request.env['warden']).to be_authenticated
end
+ context 'when user has no linked provider' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
+ end
+
+ it 'links identity' do
+ expect do
+ post provider
+ user.reload
+ end.to change { user.identities.count }.by(1)
+ end
+
+ context 'and is not allowed to link the provider' do
+ before do
+ allow_any_instance_of(IdentityProviderPolicy).to receive(:can?).with(:link).and_return(false)
+ end
+
+ it 'returns 403' do
+ post provider
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
shared_context 'sign_up' do
- let(:user) { double(email: 'new@example.com') }
+ let(:user) { double(email: +'new@example.com') }
before do
stub_omniauth_setting(block_auto_created_users: false)
@@ -193,7 +226,7 @@ describe OmniauthCallbacksController, type: :controller do
before do
stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
providers: [saml_config] })
- mock_auth_hash('saml', 'my-uid', user.email, mock_saml_response)
+ mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
request.env["devise.mapping"] = Devise.mappings[:user]
request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
post :saml, params: { SAMLResponse: mock_saml_response }
diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb
index 0af55cf3408..f2db024804d 100644
--- a/spec/controllers/passwords_controller_spec.rb
+++ b/spec/controllers/passwords_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PasswordsController do
diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb
index bb2ab27e2dd..f481b5078f2 100644
--- a/spec/controllers/profiles/accounts_controller_spec.rb
+++ b/spec/controllers/profiles/accounts_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::AccountsController do
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index 1ee0bf44e92..1a64cb72265 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::AvatarsController do
diff --git a/spec/controllers/profiles/emails_controller_spec.rb b/spec/controllers/profiles/emails_controller_spec.rb
index a8a1f96befe..7c6b1863202 100644
--- a/spec/controllers/profiles/emails_controller_spec.rb
+++ b/spec/controllers/profiles/emails_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::EmailsController do
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 5e2cc82bd8c..753eb432c5e 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::KeysController do
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
index 1b76446a0cf..f69847119d4 100644
--- a/spec/controllers/profiles/notifications_controller_spec.rb
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::NotificationsController do
diff --git a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
index 021bf2429e3..b467ecc4af9 100644
--- a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::PersonalAccessTokensController do
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index ee881f85233..929d57ebaec 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::PreferencesController do
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index 0151a434998..bcda8573468 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Profiles::TwoFactorAuthsController do
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 11cb59aa12a..08681c0341a 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe ProfilesController, :request_store do
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 29df00e6bb0..6ea82785e98 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ArtifactsController do
diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
index 4bc72042710..a9a058e7e17 100644
--- a/spec/controllers/projects/autocomplete_sources_controller_spec.rb
+++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
@@ -35,4 +35,35 @@ describe Projects::AutocompleteSourcesController do
avatar_url: user.avatar_url)
end
end
+
+ describe 'GET milestones' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, namespace: group) }
+ let!(:project_milestone) { create(:milestone, project: project) }
+ let!(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'lists milestones' do
+ group.add_owner(user)
+
+ get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
+
+ milestone_titles = json_response.map { |milestone| milestone["title"] }
+ expect(milestone_titles).to match_array([project_milestone.title, group_milestone.title])
+ end
+
+ context 'when user cannot read project issues and merge requests' do
+ it 'renders 404' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+
+ get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 95b7ae5885a..d463619ad0b 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::AvatarsController do
@@ -37,7 +39,7 @@ describe Projects::AvatarsController do
end
context 'when the avatar is stored in lfs' do
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 8eac3d9a459..5ec8d8d41d7 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::BadgesController do
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index eb110ea0002..f901fd45604 100644
--- a/spec/controllers/projects/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::BlameController do
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 38957e96798..44500d3cde3 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Projects::BlobController do
@@ -10,6 +12,8 @@ describe Projects::BlobController do
context 'with file path' do
before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
get(:show,
params: {
namespace_id: project.namespace,
@@ -144,54 +148,34 @@ describe Projects::BlobController do
end
context 'when rendering for merge request' do
- it 'renders diff context lines Gitlab::Diff::Line array' do
- do_get(since: 1, to: 5, offset: 10, from_merge_request: true)
-
- lines = JSON.parse(response.body)
-
- expect(lines.first).to have_key('type')
- expect(lines.first).to have_key('rich_text')
- expect(lines.first).to have_key('rich_text')
+ let(:presenter) { double(:presenter, diff_lines: diff_lines) }
+ let(:diff_lines) do
+ Array.new(3, Gitlab::Diff::Line.new('plain', nil, nil, nil, nil, rich_text: 'rich'))
end
- context 'when rendering match lines' do
- it 'adds top match line when "since" is less than 1' do
- do_get(since: 5, to: 10, offset: 10, from_merge_request: true)
-
- match_line = JSON.parse(response.body).first
-
- expect(match_line['type']).to eq('match')
- expect(match_line['meta_data']).to have_key('old_pos')
- expect(match_line['meta_data']).to have_key('new_pos')
- end
-
- it 'does not add top match line when "since" is equal 1' do
- do_get(since: 1, to: 10, offset: 10, from_merge_request: true)
-
- match_line = JSON.parse(response.body).first
-
- expect(match_line['type']).to be_nil
- end
+ before do
+ allow(Blobs::UnfoldPresenter).to receive(:new).and_return(presenter)
+ end
- it 'adds bottom match line when "t"o is less than blob size' do
- do_get(since: 1, to: 5, offset: 10, from_merge_request: true, bottom: true)
+ it 'renders diff context lines Gitlab::Diff::Line array' do
+ do_get(since: 1, to: 2, offset: 0, from_merge_request: true)
- match_line = JSON.parse(response.body).last
+ lines = JSON.parse(response.body)
- expect(match_line['type']).to eq('match')
- expect(match_line['meta_data']).to have_key('old_pos')
- expect(match_line['meta_data']).to have_key('new_pos')
+ expect(lines.size).to eq(diff_lines.size)
+ lines.each do |line|
+ expect(line).to have_key('type')
+ expect(line['text']).to eq('plain')
+ expect(line['rich_text']).to eq('rich')
end
+ end
- it 'does not add bottom match line when "to" is less than blob size' do
- commit_id = project.repository.commit('master').id
- blob = project.repository.blob_at(commit_id, 'CHANGELOG')
- do_get(since: 1, to: blob.lines.count, offset: 10, from_merge_request: true, bottom: true)
+ it 'handles full being true' do
+ do_get(full: true, from_merge_request: true)
- match_line = JSON.parse(response.body).last
+ lines = JSON.parse(response.body)
- expect(match_line['type']).to be_nil
- end
+ expect(lines.size).to eq(diff_lines.size)
end
end
end
@@ -305,7 +289,7 @@ describe Projects::BlobController do
merge_request.update!(source_project: other_project, target_project: other_project)
end
- it "it redirect to blob" do
+ it "redirects to blob" do
put :update, params: mr_params
expect(response).to redirect_to(blob_after_edit_path)
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 09199067024..ae85000b4e0 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::BoardsController do
@@ -28,28 +30,6 @@ describe Projects::BoardsController do
expect(response.content_type).to eq 'text/html'
end
- it 'redirects to latest visited board' do
- board = create(:board, project: project)
- create(:board_project_recent_visit, project: board.project, board: board, user: user)
-
- list_boards
-
- expect(response).to redirect_to(namespace_project_board_path(id: board.id))
- end
-
- it 'renders template if visited board is not found' do
- temporary_board = create(:board, project: project)
- visited = create(:board_project_recent_visit, project: temporary_board.project, board: temporary_board, user: user)
- temporary_board.delete
-
- allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
-
- list_boards
-
- expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
- end
-
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
@@ -120,6 +100,10 @@ describe Projects::BoardsController do
end
end
+ it_behaves_like 'unauthorized when external service denies access' do
+ subject { list_boards }
+ end
+
def list_boards(format: :html)
get :index, params: {
namespace_id: project.namespace,
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 52a20fa8d07..c778b7888dc 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::BranchesController do
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index cfa010c2d1c..cc6ac83ca38 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Ci::LintsController do
+ include StubRequests
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
@@ -16,15 +20,15 @@ describe Projects::Ci::LintsController do
get :show, params: { namespace_id: project.namespace, project_id: project }
end
- it 'should be success' do
+ it 'is success' do
expect(response).to be_success
end
- it 'should render show page' do
+ it 'renders show page' do
expect(response).to render_template :show
end
- it 'should retrieve project' do
+ it 'retrieves project' do
expect(assigns(:project)).to eq(project)
end
end
@@ -36,7 +40,7 @@ describe Projects::Ci::LintsController do
get :show, params: { namespace_id: project.namespace, project_id: project }
end
- it 'should respond with 404' do
+ it 'responds with 404' do
expect(response).to have_gitlab_http_status(404)
end
end
@@ -68,13 +72,13 @@ describe Projects::Ci::LintsController do
context 'with a valid gitlab-ci.yml' do
before do
- WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content)
+ stub_full_request(remote_file_path).to_return(body: remote_file_content)
project.add_developer(user)
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
- it 'should be success' do
+ it 'is success' do
expect(response).to be_success
end
@@ -82,7 +86,7 @@ describe Projects::Ci::LintsController do
expect(response).to render_template :show
end
- it 'should retrieve project' do
+ it 'retrieves project' do
expect(assigns(:project)).to eq(project)
end
end
@@ -102,7 +106,7 @@ describe Projects::Ci::LintsController do
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
- it 'should assign errors' do
+ it 'assigns errors' do
expect(assigns[:error]).to eq('jobs:rubocop config contains unknown keys: scriptt')
end
end
@@ -114,7 +118,7 @@ describe Projects::Ci::LintsController do
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
- it 'should respond with 404' do
+ it 'responds with 404' do
expect(response).to have_gitlab_http_status(404)
end
end
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index cb558259225..70b34f071c8 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -9,7 +9,22 @@ describe Projects::Clusters::ApplicationsController do
Clusters::Cluster::APPLICATIONS[application]
end
+ shared_examples 'a secure endpoint' do
+ it { expect { subject }.to be_allowed_for(:admin) }
+ it { expect { subject }.to be_allowed_for(:owner).of(project) }
+ it { expect { subject }.to be_allowed_for(:maintainer).of(project) }
+ it { expect { subject }.to be_denied_for(:developer).of(project) }
+ it { expect { subject }.to be_denied_for(:reporter).of(project) }
+ it { expect { subject }.to be_denied_for(:guest).of(project) }
+ it { expect { subject }.to be_denied_for(:user) }
+ it { expect { subject }.to be_denied_for(:external) }
+ end
+
describe 'POST create' do
+ subject do
+ post :create, params: params.merge(namespace_id: project.namespace, project_id: project)
+ end
+
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
let(:application) { 'helm' }
@@ -26,7 +41,7 @@ describe Projects::Clusters::ApplicationsController do
it 'schedule an application installation' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
- expect { go }.to change { current_application.count }
+ expect { subject }.to change { current_application.count }
expect(response).to have_http_status(:no_content)
expect(cluster.application_helm).to be_scheduled
end
@@ -37,7 +52,7 @@ describe Projects::Clusters::ApplicationsController do
end
it 'return 404' do
- expect { go }.not_to change { current_application.count }
+ expect { subject }.not_to change { current_application.count }
expect(response).to have_http_status(:not_found)
end
end
@@ -46,9 +61,7 @@ describe Projects::Clusters::ApplicationsController do
let(:application) { 'unkwnown-app' }
it 'return 404' do
- go
-
- expect(response).to have_http_status(:not_found)
+ is_expected.to have_http_status(:not_found)
end
end
@@ -58,9 +71,7 @@ describe Projects::Clusters::ApplicationsController do
end
it 'returns 400' do
- go
-
- expect(response).to have_http_status(:bad_request)
+ is_expected.to have_http_status(:bad_request)
end
end
end
@@ -70,18 +81,130 @@ describe Projects::Clusters::ApplicationsController do
allow(ClusterInstallAppWorker).to receive(:perform_async)
end
- it { expect { go }.to be_allowed_for(:admin) }
- it { expect { go }.to be_allowed_for(:owner).of(project) }
- it { expect { go }.to be_allowed_for(:maintainer).of(project) }
- it { expect { go }.to be_denied_for(:developer).of(project) }
- it { expect { go }.to be_denied_for(:reporter).of(project) }
- it { expect { go }.to be_denied_for(:guest).of(project) }
- it { expect { go }.to be_denied_for(:user) }
- it { expect { go }.to be_denied_for(:external) }
+ it_behaves_like 'a secure endpoint'
+ end
+ end
+
+ describe 'PATCH update' do
+ subject do
+ patch :update, params: params.merge(namespace_id: project.namespace, project_id: project)
end
- def go
- post :create, params: params.merge(namespace_id: project.namespace, project_id: project)
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
+ let!(:application) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+ let(:application_name) { application.name }
+ let(:params) { { application: application_name, id: cluster.id, hostname: "new.example.com" } }
+
+ describe 'functionality' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context "when cluster and app exists" do
+ it "schedules an application update" do
+ expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
+
+ is_expected.to have_http_status(:no_content)
+
+ expect(cluster.application_knative).to be_scheduled
+ end
+ end
+
+ context 'when cluster do not exists' do
+ before do
+ cluster.destroy!
+ end
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is unknown' do
+ let(:application_name) { 'unkwnown-app' }
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is already scheduled' do
+ before do
+ application.make_scheduled!
+ end
+
+ it { is_expected.to have_http_status(:bad_request) }
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow(ClusterPatchAppWorker).to receive(:perform_async)
+ end
+
+ it_behaves_like 'a secure endpoint'
+ end
+ end
+
+ describe 'DELETE destroy' do
+ subject do
+ delete :destroy, params: params.merge(namespace_id: project.namespace, project_id: project)
+ end
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
+ let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+ let(:application_name) { application.name }
+ let(:params) { { application: application_name, id: cluster.id } }
+ let(:worker_class) { Clusters::Applications::UninstallWorker }
+
+ describe 'functionality' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context "when cluster and app exists" do
+ it "schedules an application update" do
+ expect(worker_class).to receive(:perform_async).with(application.name, application.id).once
+
+ is_expected.to have_http_status(:no_content)
+
+ expect(cluster.application_prometheus).to be_scheduled
+ end
+ end
+
+ context 'when cluster do not exists' do
+ before do
+ cluster.destroy!
+ end
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is unknown' do
+ let(:application_name) { 'unkwnown-app' }
+
+ it { is_expected.to have_http_status(:not_found) }
+ end
+
+ context 'when application is already scheduled' do
+ before do
+ application.make_scheduled!
+ end
+
+ it { is_expected.to have_http_status(:bad_request) }
+ end
+ end
+
+ describe 'security' do
+ before do
+ allow(worker_class).to receive(:perform_async)
+ end
+
+ it_behaves_like 'a secure endpoint'
end
end
end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index d94c18ddc02..8d37bd82d21 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -165,6 +165,7 @@ describe Projects::ClustersController do
{
cluster: {
name: 'new-cluster',
+ managed: '1',
provider_gcp_attributes: {
gcp_project_id: 'gcp-project-12345',
legacy_abac: legacy_abac_param
@@ -191,6 +192,7 @@ describe Projects::ClustersController do
expect(project.clusters.first).to be_gcp
expect(project.clusters.first).to be_kubernetes
expect(project.clusters.first.provider_gcp).to be_legacy_abac
+ expect(project.clusters.first.managed?).to be_truthy
end
context 'when legacy_abac param is false' do
@@ -251,6 +253,7 @@ describe Projects::ClustersController do
{
cluster: {
name: 'new-cluster',
+ managed: '1',
platform_kubernetes_attributes: {
api_url: 'http://my-url',
token: 'test',
@@ -302,9 +305,35 @@ describe Projects::ClustersController do
expect(response).to redirect_to(project_cluster_path(project, project.clusters.first))
- expect(project.clusters.first).to be_user
- expect(project.clusters.first).to be_kubernetes
- expect(project.clusters.first).to be_platform_kubernetes_rbac
+ cluster = project.clusters.first
+
+ expect(cluster).to be_user
+ expect(cluster).to be_kubernetes
+ expect(cluster).to be_platform_kubernetes_rbac
+ end
+ end
+
+ context 'when creates a user-managed cluster' do
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ managed: '0',
+ platform_kubernetes_attributes: {
+ api_url: 'http://my-url',
+ token: 'test',
+ namespace: 'aaa',
+ authorization_type: 'rbac'
+ }
+ }
+ }
+ end
+
+ it 'creates a new user-managed cluster' do
+ go
+ cluster = project.clusters.first
+
+ expect(cluster.managed?).to be_falsy
end
end
end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 19cac47325c..b5c6382a26d 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CommitController do
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 8cb9130b834..9db1ac2a46c 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CommitsController do
@@ -15,7 +17,7 @@ describe Projects::CommitsController do
describe "GET commits_root" do
context "no ref is provided" do
- it 'should redirect to the default branch of the project' do
+ it 'redirects to the default branch of the project' do
get(:commits_root,
params: {
namespace_id: project.namespace,
@@ -113,6 +115,8 @@ describe Projects::CommitsController do
render_views
before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original unless id.include?(' ')
+
get(:signatures,
params: {
namespace_id: project.namespace,
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index cfd70e93efb..92380a2bf09 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CompareController do
@@ -80,7 +82,7 @@ describe Projects::CompareController do
show_request
expect(response).to be_success
- expect(assigns(:diffs).diff_files.to_a).to eq([])
+ expect(assigns(:diffs)).to eq([])
expect(assigns(:commits)).to eq([])
end
end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 6a63cbdf8e2..2dc97e18113 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CycleAnalyticsController do
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index e54cf3e8181..fcd14f13863 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DeployKeysController do
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 5c33098fd31..95417936df4 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DeploymentsController do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 0b9f336cf13..4c29162cd0f 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DiscussionsController do
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
new file mode 100644
index 00000000000..d232408b775
--- /dev/null
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Environments::PrometheusApiController do
+ set(:project) { create(:project) }
+ set(:environment) { create(:environment, project: project) }
+ set(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+ end
+
+ describe 'GET #proxy' do
+ let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
+
+ let(:expected_params) do
+ ActionController::Parameters.new(
+ environment_params(
+ proxy_path: 'query',
+ controller: 'projects/environments/prometheus_api',
+ action: 'proxy'
+ )
+ ).permit!
+ end
+
+ context 'with valid requests' do
+ before do
+ allow(Prometheus::ProxyService).to receive(:new)
+ .with(environment, 'GET', 'query', expected_params)
+ .and_return(prometheus_proxy_service)
+
+ allow(prometheus_proxy_service).to receive(:execute)
+ .and_return(service_result)
+ end
+
+ context 'with success result' do
+ let(:service_result) { { status: :success, body: prometheus_body } }
+ let(:prometheus_body) { '{"status":"success"}' }
+ let(:prometheus_json_body) { JSON.parse(prometheus_body) }
+
+ it 'returns prometheus response' do
+ get :proxy, params: environment_params
+
+ expect(Prometheus::ProxyService).to have_received(:new)
+ .with(environment, 'GET', 'query', expected_params)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(prometheus_json_body)
+ end
+
+ context 'with format string' do
+ before do
+ expected_params[:query] = %{up{environment="#{environment.slug}"}}
+ end
+
+ it 'replaces variables with values' do
+ get :proxy, params: environment_params.merge(query: 'up{environment="%{ci_environment_slug}"}')
+
+ expect(Prometheus::ProxyService).to have_received(:new)
+ .with(environment, 'GET', 'query', expected_params)
+ end
+
+ context 'with nil query' do
+ let(:params_without_query) do
+ params = environment_params
+ params.delete(:query)
+ params
+ end
+
+ before do
+ expected_params.delete(:query)
+ end
+
+ it 'does not raise error' do
+ get :proxy, params: params_without_query
+
+ expect(Prometheus::ProxyService).to have_received(:new)
+ .with(environment, 'GET', 'query', expected_params)
+ end
+ end
+ end
+ end
+
+ context 'with nil result' do
+ let(:service_result) { nil }
+
+ it 'returns 202 accepted' do
+ get :proxy, params: environment_params
+
+ expect(json_response['status']).to eq('processing')
+ expect(json_response['message']).to eq('Not ready yet. Try again later.')
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+
+ context 'with 404 result' do
+ let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
+
+ it 'returns body' do
+ get :proxy, params: environment_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['body']).to eq('value')
+ end
+ end
+
+ context 'with error result' do
+ context 'with http_status' do
+ let(:service_result) do
+ { http_status: :service_unavailable, status: :error, message: 'error message' }
+ end
+
+ it 'sets the http response status code' do
+ get :proxy, params: environment_params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+
+ context 'without http_status' do
+ let(:service_result) { { status: :error, message: 'error message' } }
+
+ it 'returns bad_request' do
+ get :proxy, params: environment_params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+ end
+ end
+
+ context 'with inappropriate requests' do
+ context 'with anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signin page' do
+ get :proxy, params: environment_params
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context 'without correct permissions' do
+ before do
+ project.team.truncate
+ end
+
+ it 'returns 404' do
+ get :proxy, params: environment_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with invalid environment id' do
+ let(:other_environment) { create(:environment) }
+
+ it 'returns 404' do
+ get :proxy, params: environment_params(id: other_environment.id)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ private
+
+ def environment_params(params = {})
+ {
+ id: environment.id.to_s,
+ namespace_id: project.namespace.name,
+ project_id: project.name,
+ proxy_path: 'query',
+ query: '1'
+ }.merge(params)
+ end
+end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index aa97a417a98..9699f2952f2 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::EnvironmentsController do
@@ -54,9 +56,9 @@ describe Projects::EnvironmentsController do
it 'responds with a flat payload describing available environments' do
expect(environments.count).to eq 3
- expect(environments.first['name']).to eq 'production'
- expect(environments.second['name']).to eq 'staging/review-1'
- expect(environments.third['name']).to eq 'staging/review-2'
+ expect(environments.first).to include('name' => 'production', 'name_without_type' => 'production')
+ expect(environments.second).to include('name' => 'staging/review-1', 'name_without_type' => 'review-1')
+ expect(environments.third).to include('name' => 'staging/review-2', 'name_without_type' => 'review-2')
expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1
end
@@ -155,9 +157,9 @@ describe Projects::EnvironmentsController do
expect(response).to be_ok
expect(response).not_to render_template 'folder'
expect(json_response['environments'][0])
- .to include('name' => 'staging-1.0/review')
+ .to include('name' => 'staging-1.0/review', 'name_without_type' => 'review')
expect(json_response['environments'][1])
- .to include('name' => 'staging-1.0/zzz')
+ .to include('name' => 'staging-1.0/zzz', 'name_without_type' => 'zzz')
end
end
end
@@ -283,7 +285,7 @@ describe Projects::EnvironmentsController do
.and_return([:fake_terminal])
expect(Gitlab::Workhorse)
- .to receive(:terminal_websocket)
+ .to receive(:channel_websocket)
.with(:fake_terminal)
.and_return(workhorse: :response)
@@ -340,11 +342,9 @@ describe Projects::EnvironmentsController do
end
context 'when environment has no metrics' do
- before do
- expect(environment).to receive(:metrics).and_return(nil)
- end
-
it 'returns a metrics page' do
+ expect(environment).not_to receive(:metrics)
+
get :metrics, params: environment_params
expect(response).to be_ok
@@ -352,6 +352,8 @@ describe Projects::EnvironmentsController do
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
+ expect(environment).to receive(:metrics).and_return(nil)
+
get :metrics, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(204)
@@ -381,6 +383,8 @@ describe Projects::EnvironmentsController do
end
describe 'GET #additional_metrics' do
+ let(:window_params) { { start: '1554702993.5398998', end: '1554717396.996232' } }
+
before do
allow(controller).to receive(:environment).and_return(environment)
end
@@ -392,7 +396,7 @@ describe Projects::EnvironmentsController do
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
- get :additional_metrics, params: environment_params(format: :json)
+ additional_metrics(window_params)
expect(response).to have_gitlab_http_status(204)
expect(json_response).to eq({})
@@ -412,7 +416,7 @@ describe Projects::EnvironmentsController do
end
it 'returns a metrics JSON document' do
- get :additional_metrics, params: environment_params(format: :json)
+ additional_metrics(window_params)
expect(response).to be_ok
expect(json_response['success']).to be(true)
@@ -420,6 +424,139 @@ describe Projects::EnvironmentsController do
expect(json_response['last_update']).to eq(42)
end
end
+
+ context 'when time params are missing' do
+ it 'raises an error when window params are missing' do
+ expect { additional_metrics }
+ .to raise_error(ActionController::ParameterMissing)
+ end
+ end
+
+ context 'when only one time param is provided' do
+ it 'raises an error when start is missing' do
+ expect { additional_metrics(end: '1552647300.651094') }
+ .to raise_error(ActionController::ParameterMissing)
+ end
+
+ it 'raises an error when end is missing' do
+ expect { additional_metrics(start: '1552647300.651094') }
+ .to raise_error(ActionController::ParameterMissing)
+ end
+ end
+ end
+
+ describe 'metrics_dashboard' do
+ context 'when prometheus endpoint is disabled' do
+ before do
+ stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
+ end
+
+ it 'responds with status code 403' do
+ get :metrics_dashboard, params: environment_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ shared_examples_for '200 response' do |contains_all_dashboards: false|
+ let(:expected_keys) { %w(dashboard status) }
+
+ before do
+ expected_keys << 'all_dashboards' if contains_all_dashboards
+ end
+
+ it 'returns a json representation of the environment dashboard' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.keys).to contain_exactly(*expected_keys)
+ expect(json_response['dashboard']).to be_an_instance_of(Hash)
+ end
+ end
+
+ shared_examples_for 'error response' do |status_code, contains_all_dashboards: false|
+ let(:expected_keys) { %w(message status) }
+
+ before do
+ expected_keys << 'all_dashboards' if contains_all_dashboards
+ end
+
+ it 'returns an error response' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
+
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(json_response.keys).to contain_exactly(*expected_keys)
+ end
+ end
+
+ shared_examples_for 'has all dashboards' do
+ it 'includes an index of all available dashboards' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
+
+ expect(json_response.keys).to include('all_dashboards')
+ expect(json_response['all_dashboards']).to be_an_instance_of(Array)
+ expect(json_response['all_dashboards']).to all( include('path', 'default') )
+ end
+ end
+
+ context 'when multiple dashboards is disabled' do
+ before do
+ stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
+ end
+
+ let(:dashboard_params) { { format: :json } }
+
+ it_behaves_like '200 response'
+
+ context 'when the dashboard could not be provided' do
+ before do
+ allow(YAML).to receive(:safe_load).and_return({})
+ end
+
+ it_behaves_like 'error response', :unprocessable_entity
+ end
+
+ context 'when a dashboard param is specified' do
+ let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } }
+
+ it_behaves_like '200 response'
+ end
+ end
+
+ context 'when multiple dashboards is enabled' do
+ let(:dashboard_params) { { format: :json } }
+
+ it_behaves_like '200 response', contains_all_dashboards: true
+ it_behaves_like 'has all dashboards'
+
+ context 'when a dashboard could not be provided' do
+ before do
+ allow(YAML).to receive(:safe_load).and_return({})
+ end
+
+ it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true
+ it_behaves_like 'has all dashboards'
+ end
+
+ context 'when a dashboard param is specified' do
+ let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+
+ context 'when the dashboard is available' do
+ let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
+ let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } }
+ let(:project) { create(:project, :custom_repo, files: dashboard_file) }
+ let(:environment) { create(:environment, name: 'production', project: project) }
+
+ it_behaves_like '200 response', contains_all_dashboards: true
+ it_behaves_like 'has all dashboards'
+ end
+
+ context 'when the dashboard does not exist' do
+ it_behaves_like 'error response', :not_found, contains_all_dashboards: true
+ it_behaves_like 'has all dashboards'
+ end
+ end
+ end
end
describe 'GET #search' do
@@ -500,4 +637,8 @@ describe Projects::EnvironmentsController do
project_id: project,
id: environment.id)
end
+
+ def additional_metrics(opts = {})
+ get :additional_metrics, params: environment_params(format: :json, **opts)
+ end
end
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 9072d67af07..538dbb5ad0b 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::FindFileController do
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 0e1663c8585..3423fdf4c41 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ForksController do
diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb
new file mode 100644
index 00000000000..bf099e8deeb
--- /dev/null
+++ b/spec/controllers/projects/git_http_controller_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::GitHttpController do
+ describe 'HEAD #info_refs' do
+ it 'returns 403' do
+ project = create(:project, :public, :repository)
+
+ head :info_refs, params: { namespace_id: project.namespace.to_param, project_id: project.path + '.git' }
+
+ expect(response.status).to eq(403)
+ end
+ end
+end
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 8decd8f1382..b5248c7f0c8 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::GraphsController do
@@ -26,7 +28,23 @@ describe Projects::GraphsController do
end
describe 'charts' do
+ context 'with an anonymous user' do
+ let(:project) { create(:project, :repository, :public) }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'renders charts with 200 status code' do
+ get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' })
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:charts)
+ end
+ end
+
context 'when languages were previously detected' do
+ let(:project) { create(:project, :repository, detected_repository_languages: true) }
let!(:repository_language) { create(:repository_language, project: project) }
it 'sets the languages properly' do
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 675eeff8d12..d0cb3a74b78 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::GroupLinksController do
@@ -65,8 +67,24 @@ describe Projects::GroupLinksController do
end
end
+ context 'when user does not have access to the public group' do
+ let(:group) { create(:group, :public) }
+
+ include_context 'link project to group'
+
+ it 'renders 404' do
+ expect(response.status).to eq 404
+ end
+
+ it 'does not share project with that group' do
+ expect(group.shared_projects).not_to include project
+ end
+ end
+
context 'when project group id equal link group id' do
before do
+ group2.add_developer(user)
+
post(:create, params: {
namespace_id: project.namespace,
project_id: project,
@@ -102,5 +120,26 @@ describe Projects::GroupLinksController do
expect(flash[:alert]).to eq('Please select a group.')
end
end
+
+ context 'when link is not persisted in the database' do
+ before do
+ allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute)
+ .and_return({ status: :error, http_status: 409, message: 'error' })
+
+ post(:create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ link_group_id: group.id,
+ link_group_access: ProjectGroupLink.default_access
+ })
+ end
+
+ it 'redirects to project group links page' do
+ expect(response).to redirect_to(
+ project_project_members_path(project)
+ )
+ expect(flash[:alert]).to eq('error')
+ end
+ end
end
end
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index 3037c922b68..137296b4f19 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::HooksController do
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 3ebfe4b0918..bdc81efe3bc 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ImportsController do
@@ -120,4 +122,19 @@ describe Projects::ImportsController do
end
end
end
+
+ describe 'POST #create' do
+ let(:params) { { import_url: 'https://github.com/vim/vim.git', import_url_user: 'user', import_url_password: 'password' } }
+ let(:project) { create(:project) }
+
+ before do
+ allow(RepositoryImportWorker).to receive(:perform_async)
+
+ post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project }
+ end
+
+ it 'sets import_url to the project' do
+ expect(project.reload.import_url).to eq('https://user:password@github.com/vim/vim.git')
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index c34d7c13d57..32607fc5f56 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::IssuesController do
@@ -127,6 +129,48 @@ describe Projects::IssuesController do
expect(assigns(:issues).size).to eq(2)
end
end
+
+ context 'with relative_position sorting' do
+ let!(:issue_list) { create_list(:issue, 2, project: project) }
+
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ end
+
+ it 'overrides the number allowed on the page' do
+ get :index,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ sort: 'relative_position'
+ }
+
+ expect(assigns(:issues).count).to eq 2
+ end
+
+ it 'allows the default number on the page' do
+ get :index,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(assigns(:issues).count).to eq 1
+ end
+ end
+
+ context 'external authorization' do
+ before do
+ sign_in user
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'unauthorized when external service denies access' do
+ subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
+ end
+ end
end
describe 'GET #new' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index d8a331b3cf0..490e9841492 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+# frozen_string_literal: true
require 'spec_helper'
describe Projects::JobsController, :clean_gitlab_redis_shared_state do
@@ -100,7 +101,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'GET show' do
+ describe 'GET show', :request_store do
let!(:job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:second_job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:third_job) { create(:ci_build, :failed) }
@@ -142,13 +143,24 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
+ end
+
+ it 'does not serialize builds in exposed stages' do
+ get_show_json
- get_show(id: job.id, format: :json)
+ json_response.dig('pipeline', 'details', 'stages').tap do |stages|
+ expect(stages.map(&:keys).flatten)
+ .to eq %w[name title status path dropdown_path]
+ end
end
context 'when job failed' do
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
@@ -158,6 +170,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job is running' do
+ before do
+ get_show_json
+ end
+
context 'job is cancelable' do
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
@@ -180,6 +196,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job has artifacts' do
+ before do
+ get_show_json
+ end
+
context 'with not expiry date' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
@@ -211,6 +231,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
it 'exposes empty state illustrations' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['status']['illustration']).to have_key('image')
@@ -223,6 +245,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
it 'does not exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['deployment_status']).to be_nil
end
@@ -233,11 +257,20 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:environment) { create(:environment, project: project, name: 'staging', state: :available) }
let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) }
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+ end
+
it 'exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_schema('job/job_details')
- expect(json_response['deployment_status']["status"]).to eq 'creating'
- expect(json_response['deployment_status']["environment"]).not_to be_nil
+ expect(json_response.dig('deployment_status', 'status')).to eq 'creating'
+ expect(json_response.dig('deployment_status', 'environment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment'))
+ .not_to include('commit')
end
end
@@ -249,11 +282,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).to have_key('edit_path')
@@ -269,11 +302,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -288,11 +321,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -305,6 +338,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -318,6 +353,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -327,6 +364,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'settings_path' do
+ before do
+ get_show_json
+ end
+
context 'when user is developer' do
it 'settings_path is not available' do
expect(response).to have_gitlab_http_status(:ok)
@@ -353,6 +394,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when no trace is available' do
it 'has_trace is false' do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be false
end
@@ -362,17 +405,21 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
it "has_trace is true" do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be true
end
end
it 'exposes the stage the job belongs to' do
+ get_show_json
+
expect(json_response['stage']).to eq('test')
end
end
- context 'when requesting JSON job is triggered' do
+ context 'when requesting triggered job JSON' do
let!(:merge_request) { create(:merge_request, source_project: project) }
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
@@ -382,15 +429,15 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
end
context 'with no variables' do
- before do
- get_show(id: job.id, format: :json)
- end
-
it 'exposes trigger information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['trigger']['short_token']).to eq 'toke'
@@ -407,7 +454,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -431,7 +478,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'user is not a mantainer' do
before do
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -455,6 +502,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
+ def get_show_json
+ expect { get_show(id: job.id, format: :json) }
+ .not_to change { Gitlab::GitalyClient.get_request_count }
+ end
+
def get_show(**extra_params)
params = {
namespace_id: project.namespace.to_param,
@@ -789,8 +841,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
it 'erases artifacts' do
- expect(job.artifacts_file.exists?).to be_falsey
- expect(job.artifacts_metadata.exists?).to be_falsey
+ expect(job.artifacts_file.present?).to be_falsey
+ expect(job.artifacts_metadata.present?).to be_falsey
end
it 'erases trace' do
@@ -989,7 +1041,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'and valid id' do
it 'returns the terminal for the job' do
expect(Gitlab::Workhorse)
- .to receive(:terminal_websocket)
+ .to receive(:channel_websocket)
.and_return(workhorse: :response)
get_terminal_websocket(id: job.id)
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index 32897a0f1b4..ff089df37f7 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::LabelsController do
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 6c8c7cd8f2b..45125385d9e 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MattermostsController do
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
index 039f35875d2..8e4ac64f7b0 100644
--- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MergeRequests::ConflictsController do
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index f031a74c5bd..5fefad86ef3 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MergeRequests::CreationsController do
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index e85f32d6e30..13a28b738ca 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MergeRequests::DiffsController do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 79f97aa4170..f8c0ab55eb4 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MergeRequestsController do
@@ -60,6 +62,8 @@ describe Projects::MergeRequestsController do
end
it "renders merge request page" do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
go(format: :html)
expect(response).to be_success
@@ -86,6 +90,10 @@ describe Projects::MergeRequestsController do
end
describe 'as json' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ end
+
context 'with basic serializer param' do
it 'renders basic MR entity as json' do
go(serializer: 'basic', format: :json)
@@ -232,11 +240,11 @@ describe Projects::MergeRequestsController do
assignee = create(:user)
project.add_developer(assignee)
- update_merge_request({ assignee_id: assignee.id }, format: :json)
+ update_merge_request({ assignee_ids: [assignee.id] }, format: :json)
+
body = JSON.parse(response.body)
- expect(body['assignee'].keys)
- .to match_array(%w(name username avatar_url id state web_url))
+ expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
end
end
@@ -421,8 +429,9 @@ describe Projects::MergeRequestsController do
it 'sets the MR to merge when the pipeline succeeds' do
service = double(:merge_when_pipeline_succeeds_service)
+ allow(service).to receive(:available_for?) { true }
- expect(MergeRequests::MergeWhenPipelineSucceedsService)
+ expect(AutoMerge::MergeWhenPipelineSucceedsService)
.to receive(:new).with(project, anything, anything)
.and_return(service)
expect(service).to receive(:execute).with(merge_request)
@@ -705,9 +714,9 @@ describe Projects::MergeRequestsController do
end
end
- describe 'POST cancel_merge_when_pipeline_succeeds' do
+ describe 'POST cancel_auto_merge' do
subject do
- post :cancel_merge_when_pipeline_succeeds,
+ post :cancel_auto_merge,
params: {
format: :json,
namespace_id: merge_request.project.namespace.to_param,
@@ -717,14 +726,15 @@ describe Projects::MergeRequestsController do
xhr: true
end
- it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do
- mwps_service = double
+ it 'calls AutoMergeService' do
+ auto_merge_service = double
- allow(MergeRequests::MergeWhenPipelineSucceedsService)
+ allow(AutoMergeService)
.to receive(:new)
- .and_return(mwps_service)
+ .and_return(auto_merge_service)
- expect(mwps_service).to receive(:cancel).with(merge_request)
+ allow(auto_merge_service).to receive(:available_strategies).with(merge_request)
+ expect(auto_merge_service).to receive(:cancel).with(merge_request)
subject
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index ac54b3c3952..767cee7d54a 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MilestonesController do
@@ -173,6 +175,40 @@ describe Projects::MilestonesController do
end
end
+ describe '#labels' do
+ render_views
+
+ context 'as json' do
+ let!(:guest) { create(:user, username: 'guest1') }
+ let!(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :public, group: group) }
+ let!(:label) { create(:label, title: 'test_label_on_private_issue', project: project) }
+ let!(:confidential_issue) { create(:labeled_issue, confidential: true, project: project, milestone: milestone, labels: [label]) }
+
+ it 'does not render labels of private issues if user has no access' do
+ sign_in(guest)
+
+ get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+
+ expect(json_response['html']).not_to include(label.title)
+ end
+
+ it 'does render labels of private issues if user has access' do
+ sign_in(user)
+
+ get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+
+ expect(json_response['html']).to include(label.title)
+ end
+ end
+ end
+
context 'promotion succeeds' do
before do
group.add_developer(user)
diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb
index 86a12a5e903..51ce9e2544f 100644
--- a/spec/controllers/projects/mirrors_controller_spec.rb
+++ b/spec/controllers/projects/mirrors_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MirrorsController do
@@ -65,7 +67,7 @@ describe Projects::MirrorsController do
expect(flash[:notice]).to match(/successfully updated/)
end
- it 'should create a RemoteMirror object' do
+ it 'creates a RemoteMirror object' do
expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.to change(RemoteMirror, :count).by(1)
end
end
@@ -79,10 +81,10 @@ describe Projects::MirrorsController do
do_put(project, remote_mirrors_attributes: remote_mirror_attributes)
expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings'))
- expect(flash[:alert]).to match(/Only allowed protocols are/)
+ expect(flash[:alert]).to match(/Only allowed schemes are/)
end
- it 'should not create a RemoteMirror object' do
+ it 'does not create a RemoteMirror object' do
expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.not_to change(RemoteMirror, :count)
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 0b0f5117784..6ec84f5c528 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::NotesController do
@@ -413,6 +415,37 @@ describe Projects::NotesController do
end
end
end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+
+ it 'includes changes in commands_changes ' do
+ post :create, params: request_params.merge(note: { note: note_text }, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index d6eece47804..f80bbf0d78f 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PagesController do
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index ffb9867a203..ff3afd51cd8 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PagesDomainsController do
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index fa732437fc1..850ef9c92fb 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PipelineSchedulesController do
@@ -89,7 +91,7 @@ describe Projects::PipelineSchedulesController do
context 'when variables_attributes has one variable' do
let(:schedule) do
basic_param.merge({
- variables_attributes: [{ key: 'AAA', secret_value: 'AAA123' }]
+ variables_attributes: [{ key: 'AAA', secret_value: 'AAA123', variable_type: 'file' }]
})
end
@@ -103,6 +105,7 @@ describe Projects::PipelineSchedulesController do
Ci::PipelineScheduleVariable.last.tap do |v|
expect(v.key).to eq("AAA")
expect(v.value).to eq("AAA123")
+ expect(v.variable_type).to eq("file")
end
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index ece8532cb84..9a50ea79f5e 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PipelinesController do
@@ -28,6 +30,8 @@ describe Projects::PipelinesController do
end
it 'returns serialized pipelines', :request_store do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
queries = ActiveRecord::QueryRecorder.new do
get_pipelines_index_json
end
@@ -95,6 +99,8 @@ describe Projects::PipelinesController do
RequestStore.clear!
RequestStore.begin!
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
expect { get_pipelines_index_json }
.to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb
index 269f105bed2..3656b4e3771 100644
--- a/spec/controllers/projects/pipelines_settings_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PipelinesSettingsController do
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 3cc3fe69fba..4141e41c7a7 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe Projects::ProjectMembersController do
@@ -5,7 +7,7 @@ describe Projects::ProjectMembersController do
let(:project) { create(:project, :public, :access_requestable) }
describe 'GET index' do
- it 'should have the project_members address with a 200 status code' do
+ it 'has the project_members address with a 200 status code' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
index 635763ce1d3..17f9483be98 100644
--- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Prometheus::MetricsController do
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index 483d3bbc37c..0ebbb4b581f 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe Projects::ProtectedBranchesController do
diff --git a/spec/controllers/projects/protected_tags_controller_spec.rb b/spec/controllers/projects/protected_tags_controller_spec.rb
index 1553e081dee..a900947d82e 100644
--- a/spec/controllers/projects/protected_tags_controller_spec.rb
+++ b/spec/controllers/projects/protected_tags_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe Projects::ProtectedTagsController do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index cffdf30da6b..97acd47b4da 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::RawController do
@@ -40,7 +42,7 @@ describe Projects::RawController do
end
end
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index 62f2af947e4..6db98f2428b 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::RefsController do
@@ -44,11 +46,15 @@ describe Projects::RefsController do
end
it 'renders JS' do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
xhr_get(:js)
expect(response).to be_success
end
it 'renders JSON' do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
xhr_get(:json)
expect(response).to be_success
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index eca187af33d..63d84b366d3 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Registry::RepositoriesController do
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index 74ed89ba1c3..ff35139ae2e 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Registry::TagsController do
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 5f4f6f8558f..8fca9e680dd 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Projects::RepositoriesController do
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
index 0baaa4e7192..279b4f360c5 100644
--- a/spec/controllers/projects/runners_controller_spec.rb
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::RunnersController do
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 276cf340962..18c594acae0 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -8,9 +8,8 @@ describe Projects::Serverless::FunctionsController do
let(:user) { create(:user) }
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
let(:service) { cluster.platform_kubernetes }
- let(:project) { cluster.project}
+ let(:project) { cluster.project }
let(:namespace) do
create(:cluster_kubernetes_namespace,
@@ -30,17 +29,69 @@ describe Projects::Serverless::FunctionsController do
end
describe 'GET #index' do
- context 'empty cache' do
- it 'has no data' do
+ let(:expected_json) { { 'knative_installed' => knative_state, 'functions' => functions } }
+
+ context 'when cache is being read' do
+ let(:knative_state) { 'checking' }
+ let(:functions) { [] }
+
+ before do
get :index, params: params({ format: :json })
+ end
- expect(response).to have_gitlab_http_status(204)
+ it 'returns checking' do
+ expect(json_response).to eq expected_json
end
- it 'renders an html page' do
- get :index, params: params
+ it { expect(response).to have_gitlab_http_status(200) }
+ end
+
+ context 'when cache is ready' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:knative_state) { true }
- expect(response).to have_gitlab_http_status(200)
+ before do
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ stub_kubeclient_service_pods(
+ kube_response({ "kind" => "PodList", "items" => [] }),
+ namespace: namespace.namespace
+ )
+ end
+
+ context 'when no functions were found' do
+ let(:functions) { [] }
+
+ before do
+ stub_kubeclient_knative_services(
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ )
+ get :index, params: params({ format: :json })
+ end
+
+ it 'returns checking' do
+ expect(json_response).to eq expected_json
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
+ end
+
+ context 'when functions were found' do
+ let(:functions) { ["asdf"] }
+
+ before do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ get :index, params: params({ format: :json })
+ end
+
+ it 'returns functions' do
+ expect(json_response["functions"]).not_to be_empty
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
end
end
end
@@ -56,11 +107,12 @@ describe Projects::Serverless::FunctionsController do
context 'valid data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
end
it 'has a valid function name' do
@@ -76,14 +128,24 @@ describe Projects::Serverless::FunctionsController do
end
end
+ describe 'GET #metrics' do
+ context 'invalid data' do
+ it 'has a bad function name' do
+ get :metrics, params: params({ format: :json, environment_id: "*", id: "foo" })
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+ end
+
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
end
it 'has data' do
@@ -91,11 +153,16 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200)
- expect(json_response).to contain_exactly(
- a_hash_including(
- "name" => project.name,
- "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
- )
+ expect(json_response).to match(
+ {
+ "knative_installed" => "checking",
+ "functions" => [
+ a_hash_including(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
+ )
+ ]
+ }
)
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 601a292bf54..3608d175d50 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ServicesController do
@@ -147,7 +149,7 @@ describe Projects::ServicesController do
params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } }
end
- it 'should not update the service' do
+ it 'does not update the service' do
service.reload
expect(service.namespace).not_to eq('updated_namespace')
end
@@ -172,7 +174,7 @@ describe Projects::ServicesController do
context 'with approved services' do
let(:service_id) { 'jira' }
- it 'should render edit page' do
+ it 'renders edit page' do
expect(response).to be_success
end
end
@@ -180,7 +182,7 @@ describe Projects::ServicesController do
context 'with a deprecated service' do
let(:service_id) { 'kubernetes' }
- it 'should render edit page' do
+ it 'renders edit page' do
expect(response).to be_success
end
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 41cc0607cee..117b9cf7915 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe Projects::Settings::CiCdController do
@@ -107,7 +109,7 @@ describe Projects::Settings::CiCdController do
end
context 'when updating the auto_devops settings' do
- let(:params) { { auto_devops_attributes: { enabled: '', domain: 'mepmep.md' } } }
+ let(:params) { { auto_devops_attributes: { enabled: '' } } }
context 'following the instance default' do
let(:params) { { auto_devops_attributes: { enabled: '' } } }
@@ -189,6 +191,30 @@ describe Projects::Settings::CiCdController do
expect(project.build_timeout).to eq(5400)
end
end
+
+ context 'when build_timeout_human_readable is invalid' do
+ let(:params) { { build_timeout_human_readable: '5m' } }
+
+ it 'set specified timeout' do
+ expect(subject).to set_flash[:alert]
+ expect(response).to redirect_to(namespace_project_settings_ci_cd_path)
+ end
+ end
+
+ context 'when default_git_depth is not specified' do
+ let(:params) { { ci_cd_settings_attributes: { default_git_depth: 10 } } }
+
+ before do
+ project.ci_cd_settings.update!(default_git_depth: nil)
+ end
+
+ it 'set specified git depth' do
+ subject
+
+ project.reload
+ expect(project.default_git_depth).to eq(10)
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb
index 8624eb4d1a0..93e8d03098a 100644
--- a/spec/controllers/projects/settings/integrations_controller_spec.rb
+++ b/spec/controllers/projects/settings/integrations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Settings::IntegrationsController do
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index d989ec22481..aa9cd41ed19 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -11,137 +11,172 @@ describe Projects::Settings::OperationsController do
project.add_maintainer(user)
end
- context 'error tracking' do
- describe 'GET #show' do
- it 'renders show template' do
- get :show, params: project_params(project)
+ shared_examples 'PATCHable' do
+ let(:operations_update_service) { instance_double(::Projects::Operations::UpdateService) }
+ let(:operations_url) { project_settings_operations_url(project) }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:show)
- end
+ let(:permitted_params) do
+ ActionController::Parameters.new(params).permit!
+ end
- context 'with existing setting' do
- let!(:error_tracking_setting) do
- create(:project_error_tracking_setting, project: project)
- end
+ context 'format json' do
+ context 'when update succeeds' do
+ it 'returns success status' do
+ stub_operations_update_service_returning(status: :success)
- it 'loads existing setting' do
- get :show, params: project_params(project)
+ patch :update,
+ params: project_params(project, params),
+ format: :json
- expect(controller.helpers.error_tracking_setting)
- .to eq(error_tracking_setting)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq('status' => 'success')
+ expect(flash[:notice]).to eq('Your changes have been saved')
end
end
- context 'without an existing setting' do
- it 'builds a new setting' do
- get :show, params: project_params(project)
-
- expect(controller.helpers.error_tracking_setting).to be_new_record
+ context 'when update fails' do
+ it 'returns error' do
+ stub_operations_update_service_returning(
+ status: :error,
+ message: 'error message'
+ )
+
+ patch :update,
+ params: project_params(project, params),
+ format: :json
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('error message')
end
end
+ end
- context 'with insufficient permissions' do
- before do
- project.add_reporter(user)
- end
+ private
- it 'renders 404' do
- get :show, params: project_params(project)
+ def stub_operations_update_service_returning(return_value = {})
+ expect(::Projects::Operations::UpdateService)
+ .to receive(:new).with(project, user, permitted_params)
+ .and_return(operations_update_service)
+ expect(operations_update_service).to receive(:execute)
+ .and_return(return_value)
+ end
+ end
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
+ describe 'GET #show' do
+ it 'renders show template' do
+ get :show, params: project_params(project)
- context 'as an anonymous user' do
- before do
- sign_out(user)
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
- it 'redirects to signup page' do
- get :show, params: project_params(project)
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
- expect(response).to redirect_to(new_user_session_path)
- end
+ it 'renders 404' do
+ get :show, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
- describe 'PATCH #update' do
- let(:operations_update_service) { spy(:operations_update_service) }
- let(:operations_url) { project_settings_operations_url(project) }
-
- let(:error_tracking_params) do
- {
- error_tracking_setting_attributes: {
- enabled: '1',
- api_url: 'http://url',
- token: 'token'
- }
- }
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
end
- let(:error_tracking_permitted) do
- ActionController::Parameters.new(error_tracking_params).permit!
+
+ it 'redirects to signup page' do
+ get :show, params: project_params(project)
+
+ expect(response).to redirect_to(new_user_session_path)
end
+ end
+ end
- context 'when update succeeds' do
- before do
- stub_operations_update_service_returning(status: :success)
- end
+ describe 'PATCH #update' do
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
- it 'shows a notice' do
- patch :update, params: project_params(project, error_tracking_params)
+ it 'renders 404' do
+ patch :update, params: project_params(project)
- expect(response).to redirect_to(operations_url)
- expect(flash[:notice]).to eq _('Your changes have been saved')
- end
+ expect(response).to have_gitlab_http_status(:not_found)
end
+ end
- context 'when update fails' do
- before do
- stub_operations_update_service_returning(status: :error)
- end
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
- it 'renders show page' do
- patch :update, params: project_params(project, error_tracking_params)
+ it 'redirects to signup page' do
+ patch :update, params: project_params(project)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:show)
- end
+ expect(response).to redirect_to(new_user_session_path)
end
+ end
+ end
- context 'with insufficient permissions' do
- before do
- project.add_reporter(user)
+ context 'error tracking' do
+ describe 'GET #show' do
+ context 'with existing setting' do
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
end
- it 'renders 404' do
- patch :update, params: project_params(project)
+ it 'loads existing setting' do
+ get :show, params: project_params(project)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(controller.helpers.error_tracking_setting)
+ .to eq(error_tracking_setting)
end
end
- context 'as an anonymous user' do
- before do
- sign_out(user)
- end
-
- it 'redirects to signup page' do
- patch :update, params: project_params(project)
+ context 'without an existing setting' do
+ it 'builds a new setting' do
+ get :show, params: project_params(project)
- expect(response).to redirect_to(new_user_session_path)
+ expect(controller.helpers.error_tracking_setting).to be_new_record
end
end
end
- private
+ describe 'PATCH #update' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: '1',
+ api_host: 'http://url',
+ token: 'token',
+ project: {
+ slug: 'sentry-project',
+ name: 'Sentry Project',
+ organization_slug: 'sentry-org',
+ organization_name: 'Sentry Org'
+ }
+ }
+ }
+ end
- def stub_operations_update_service_returning(return_value = {})
- expect(::Projects::Operations::UpdateService)
- .to receive(:new).with(project, user, error_tracking_permitted)
- .and_return(operations_update_service)
- expect(operations_update_service).to receive(:execute)
- .and_return(return_value)
+ it_behaves_like 'PATCHable'
+ end
+ end
+
+ context 'metrics dashboard setting' do
+ describe 'PATCH #update' do
+ let(:params) do
+ {
+ metrics_setting_attributes: {
+ external_dashboard_url: 'https://gitlab.com'
+ }
+ }
+ end
+
+ it_behaves_like 'PATCHable'
end
end
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
index 638cce60a25..b34053fc993 100644
--- a/spec/controllers/projects/settings/repository_controller_spec.rb
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Settings::RepositoryController do
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 8d9cb2c8ac0..9b5d7317c11 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::SnippetsController do
diff --git a/spec/controllers/projects/stages_controller_spec.rb b/spec/controllers/projects/stages_controller_spec.rb
new file mode 100644
index 00000000000..a91e3523fd7
--- /dev/null
+++ b/spec/controllers/projects/stages_controller_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::StagesController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST #play_manual.json' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:stage_name) { 'test' }
+
+ before do
+ create_manual_build(pipeline, 'test', 'rspec 1/2')
+ create_manual_build(pipeline, 'test', 'rspec 2/2')
+
+ pipeline.reload
+ end
+
+ context 'when user does not have access' do
+ it 'returns not authorized' do
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when user has access' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when the stage does not exists' do
+ let(:stage_name) { 'deploy' }
+
+ it 'fails to play all manual' do
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the stage exists' do
+ it 'starts all manual jobs' do
+ expect(pipeline.builds.manual.count).to eq(2)
+
+ play_manual_stage!
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(pipeline.builds.manual.count).to eq(0)
+ end
+ end
+ end
+
+ def play_manual_stage!
+ post :play_manual, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id,
+ stage_name: stage_name
+ }, format: :json
+ end
+
+ def create_manual_build(pipeline, stage, name)
+ create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name)
+ end
+ end
+end
diff --git a/spec/controllers/projects/tags/releases_controller_spec.rb b/spec/controllers/projects/tags/releases_controller_spec.rb
index 29f206c574b..66eff4844c2 100644
--- a/spec/controllers/projects/tags/releases_controller_spec.rb
+++ b/spec/controllers/projects/tags/releases_controller_spec.rb
@@ -18,40 +18,85 @@ describe Projects::Tags::ReleasesController do
tag_id = release.tag
project.releases.destroy_all # rubocop: disable DestroyAll
- get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id }
+ response = get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id }
release = assigns(:release)
expect(release).not_to be_nil
expect(release).not_to be_persisted
+ expect(response).to have_http_status(:ok)
end
it 'retrieves an existing release' do
- get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag }
+ response = get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag }
release = assigns(:release)
expect(release).not_to be_nil
expect(release).to be_persisted
+ expect(response).to have_http_status(:ok)
end
end
describe 'PUT #update' do
it 'updates release note description' do
- update_release('description updated')
+ response = update_release(release.tag, "description updated")
- release = project.releases.find_by_tag(tag)
+ release = project.releases.find_by(tag: tag)
expect(release.description).to eq("description updated")
+ expect(response).to have_http_status(:found)
end
- it 'deletes release note when description is null' do
- expect { update_release('') }.to change(project.releases, :count).by(-1)
+ it 'creates a release if one does not exist' do
+ tag_without_release = create_new_tag
+
+ expect do
+ update_release(tag_without_release.name, "a new release")
+ end.to change { project.releases.count }.by(1)
+
+ expect(response).to have_http_status(:found)
+ end
+
+ it 'sets the release name, sha, and author for a new release' do
+ tag_without_release = create_new_tag
+
+ response = update_release(tag_without_release.name, "a new release")
+
+ release = project.releases.find_by(tag: tag_without_release.name)
+ expect(release.name).to eq(tag_without_release.name)
+ expect(release.sha).to eq(tag_without_release.target_commit.sha)
+ expect(release.author.id).to eq(user.id)
+ expect(response).to have_http_status(:found)
+ end
+
+ it 'deletes release when description is empty' do
+ initial_releases_count = project.releases.count
+
+ response = update_release(release.tag, "")
+
+ expect(initial_releases_count).to eq(1)
+ expect(project.releases.count).to eq(0)
+ expect(response).to have_http_status(:found)
+ end
+
+ it 'does nothing when description is empty and the tag does not have a release' do
+ tag_without_release = create_new_tag
+
+ expect do
+ update_release(tag_without_release.name, "")
+ end.not_to change { project.releases.count }
+
+ expect(response).to have_http_status(:found)
end
end
- def update_release(description)
+ def create_new_tag
+ project.repository.add_tag(user, 'mytag', 'master')
+ end
+
+ def update_release(tag_id, description)
put :update, params: {
namespace_id: project.namespace.to_param,
project_id: project,
- tag_id: release.tag,
+ tag_id: tag_id,
release: { description: description }
}
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 379430bff3b..b99b5d611fc 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::TagsController do
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 01e53669627..bebf17728c0 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::TemplatesController do
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index 987772f38aa..c12019fed5e 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe Projects::TodosController do
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index b15a2bc84a5..7f7cabe3b0c 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::TreeController do
@@ -16,6 +18,8 @@ describe Projects::TreeController do
render_views
before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
get(:show,
params: {
namespace_id: project.namespace.to_param,
@@ -70,6 +74,26 @@ describe Projects::TreeController do
end
end
+ describe 'GET show with whitespace in ref' do
+ render_views
+
+ let(:id) { "this ref/api/responses" }
+
+ it 'does not call make a Gitaly request' do
+ allow(::Gitlab::GitalyClient).to receive(:call).and_call_original
+ expect(::Gitlab::GitalyClient).not_to receive(:call).with(anything, :commit_service, :find_commit, anything, anything)
+
+ get(:show,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
describe 'GET show with blob path' do
render_views
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index cfa67683dd3..776c1270977 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::UploadsController do
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 8cceda72c28..a2a09e2580f 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::VariablesController do
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index b2f40231796..f2e0b5e5c1d 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::WikisController do
@@ -17,6 +19,18 @@ describe Projects::WikisController do
destroy_page(wiki_title)
end
+ describe 'GET #pages' do
+ subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
+
+ it 'does not load the pages content' do
+ expect(controller).to receive(:load_wiki).and_return(project_wiki)
+
+ expect(project_wiki).to receive(:list_pages).twice.and_call_original
+
+ subject
+ end
+ end
+
describe 'GET #show' do
render_views
@@ -26,9 +40,9 @@ describe Projects::WikisController do
expect(controller).to receive(:load_wiki).and_return(project_wiki)
# empty? call
- expect(project_wiki).to receive(:pages).with(limit: 1).and_call_original
+ expect(project_wiki).to receive(:list_pages).with(limit: 1).and_call_original
# Sidebar entries
- expect(project_wiki).to receive(:pages).with(limit: 15).and_call_original
+ expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
subject
@@ -102,7 +116,7 @@ describe Projects::WikisController do
subject
- expect(response).to redirect_to(project_wiki_path(project, project_wiki.pages.first))
+ expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first))
end
end
@@ -136,7 +150,7 @@ describe Projects::WikisController do
allow(controller).to receive(:valid_encoding?).and_return(false)
subject
- expect(response).to redirect_to(project_wiki_path(project, project_wiki.pages.first))
+ expect(response).to redirect_to(project_wiki_path(project, project_wiki.list_pages.first))
end
end
@@ -146,7 +160,7 @@ describe Projects::WikisController do
it 'updates the page' do
subject
- wiki_page = project_wiki.pages.first
+ wiki_page = project_wiki.list_pages(load_content: true).first
expect(wiki_page.title).to eq new_title
expect(wiki_page.content).to eq new_content
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a1662658ade..8d2412f97ef 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require('spec_helper')
describe ProjectsController do
+ include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
let(:project) { create(:project) }
@@ -77,6 +80,10 @@ describe ProjectsController do
end
context "user has access to project" do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ end
+
context "and does not have notification setting" do
it "initializes notification as disabled" do
get :show, params: { namespace_id: public_project.namespace, id: public_project }
@@ -285,6 +292,18 @@ describe ProjectsController do
end
describe 'GET edit' do
+ it 'allows an admin user to access the page' do
+ sign_in(create(:user, :admin))
+
+ get :edit,
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'sets the badge API endpoint' do
sign_in(user)
project.add_maintainer(user)
@@ -369,6 +388,23 @@ describe ProjectsController do
end
end
+ it 'does not update namespace' do
+ controller.instance_variable_set(:@project, project)
+
+ params = {
+ namespace_id: 'test'
+ }
+
+ expect do
+ put :update,
+ params: {
+ namespace_id: project.namespace,
+ id: project.id,
+ project: params
+ }
+ end.not_to change { project.namespace.reload }
+ end
+
def update_project(**parameters)
put :update,
params: {
@@ -390,6 +426,37 @@ describe ProjectsController do
it_behaves_like 'updating a project'
end
+
+ context 'as maintainer' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it_behaves_like 'unauthorized when external service denies access' do
+ subject do
+ put :update,
+ params: {
+ namespace_id: project.namespace,
+ id: project,
+ project: { description: 'Hello world' }
+ }
+ project.reload
+ end
+
+ it 'updates when the service allows access' do
+ external_service_allow_access(user, project)
+
+ expect { subject }.to change(project, :description)
+ end
+
+ it 'does not update when the service rejects access' do
+ external_service_deny_access(user, project)
+
+ expect { subject }.not_to change(project, :description)
+ end
+ end
+ end
end
describe '#transfer' do
@@ -686,6 +753,16 @@ describe ProjectsController do
expect(JSON.parse(response.body).keys).to match_array(%w(body references))
end
+ context 'when not authorized' do
+ let(:private_project) { create(:project, :private) }
+
+ it 'returns 404' do
+ post :preview_markdown, params: { namespace_id: private_project.namespace, id: private_project, text: '*Markdown* text' }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context 'state filter on references' do
let(:issue) { create(:issue, :closed, project: public_project) }
let(:merge_request) { create(:merge_request, :closed, target_project: public_project) }
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index fd151e8a298..9a598790ff2 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RegistrationsController do
@@ -15,7 +17,7 @@ describe RegistrationsController do
context 'when send_user_confirmation_email is false' do
it 'signs the user in' do
- allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false)
+ stub_application_setting(send_user_confirmation_email: false)
expect { post(:create, params: user_params) }.not_to change { ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
@@ -24,7 +26,7 @@ describe RegistrationsController do
context 'when send_user_confirmation_email is true' do
it 'does not authenticate user and sends confirmation email' do
- allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
+ stub_application_setting(send_user_confirmation_email: true)
post(:create, params: user_params)
@@ -35,7 +37,7 @@ describe RegistrationsController do
context 'when signup_enabled? is false' do
it 'redirects to sign_in' do
- allow_any_instance_of(ApplicationSetting).to receive(:signup_enabled?).and_return(false)
+ stub_application_setting(signup_enabled: false)
expect { post(:create, params: user_params) }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
@@ -44,13 +46,17 @@ describe RegistrationsController do
end
context 'when reCAPTCHA is enabled' do
+ def fail_recaptcha
+ # Without this, `verify_recaptcha` arbitrarily returns true in test env
+ Recaptcha.configuration.skip_verify_env.delete('test')
+ end
+
before do
stub_application_setting(recaptcha_enabled: true)
end
it 'displays an error when the reCAPTCHA is not solved' do
- # Without this, `verify_recaptcha` arbitrarily returns true in test env
- Recaptcha.configuration.skip_verify_env.delete('test')
+ fail_recaptcha
post(:create, params: user_params)
@@ -68,6 +74,17 @@ describe RegistrationsController do
expect(flash[:notice]).to include 'Welcome! You have signed up successfully.'
end
+
+ it 'does not require reCAPTCHA if disabled by feature flag' do
+ stub_feature_flags(registrations_recaptcha: false)
+ fail_recaptcha
+
+ post(:create, params: user_params)
+
+ expect(controller).not_to receive(:verify_recaptcha)
+ expect(flash[:alert]).to be_nil
+ expect(flash[:notice]).to include 'Welcome! You have signed up successfully.'
+ end
end
context 'when terms are enforced' do
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index 995f803d757..4892ff43086 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RootController do
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 02a0cfe0272..4634d1d4bb3 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -1,12 +1,40 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SearchController do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
before do
sign_in(user)
end
+ context 'uses the right partials depending on scope' do
+ using RSpec::Parameterized::TableSyntax
+ render_views
+
+ set(:project) { create(:project, :public, :repository, :wiki_repo) }
+
+ subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
+
+ where(:partial, :scope) do
+ '_blob' | :blobs
+ '_wiki_blob' | :wiki_blobs
+ '_commit' | :commits
+ end
+
+ with_them do
+ it do
+ project_wiki = create(:project_wiki, project: project, user: user)
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' })
+
+ expect(subject).to render_template("search/results/#{partial}")
+ end
+ end
+ end
+
it 'finds issue comments' do
project = create(:project, :public)
note = create(:note_on_issue, project: project)
@@ -76,4 +104,41 @@ describe SearchController do
expect(assigns[:search_objects].count).to eq(0)
end
end
+
+ context 'with external authorization service enabled' do
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ describe 'GET #show' do
+ it 'renders a 403 when no project is given' do
+ get :show, params: { scope: 'notes', search: note.note }
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe 'GET #autocomplete' do
+ it 'renders a 403 when no project is given' do
+ get :autocomplete, params: { term: 'hello' }
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get :autocomplete, params: { project_id: project.id, term: 'hello' }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 75c91dd8607..89857a9d21b 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -1,16 +1,34 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe SentNotificationsController do
let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) }
+ let(:project) { create(:project, :public) }
+ let(:private_project) { create(:project, :private) }
+ let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: user) }
let(:issue) do
- create(:issue, project: project, author: user) do |issue|
- issue.subscriptions.create(user: user, project: project, subscribed: true)
+ create(:issue, project: target_project) do |issue|
+ issue.subscriptions.create(user: user, project: target_project, subscribed: true)
+ end
+ end
+
+ let(:confidential_issue) do
+ create(:issue, project: target_project, confidential: true) do |issue|
+ issue.subscriptions.create(user: user, project: target_project, subscribed: true)
end
end
+ let(:merge_request) do
+ create(:merge_request, source_project: target_project, target_project: target_project) do |mr|
+ mr.subscriptions.create(user: user, project: target_project, subscribed: true)
+ end
+ end
+
+ let(:noteable) { issue }
+ let(:target_project) { project }
+
describe 'GET unsubscribe' do
context 'when the user is not logged in' do
context 'when the force param is passed' do
@@ -32,20 +50,93 @@ describe SentNotificationsController do
end
context 'when the force param is not passed' do
+ render_views
+
before do
get(:unsubscribe, params: { id: sent_notification.reply_key })
end
- it 'does not unsubscribe the user' do
- expect(issue.subscribed?(user, project)).to be_truthy
+ shared_examples 'unsubscribing as anonymous' do
+ it 'does not unsubscribe the user' do
+ expect(noteable.subscribed?(user, target_project)).to be_truthy
+ end
+
+ it 'does not set the flash message' do
+ expect(controller).not_to set_flash[:notice]
+ end
+
+ it 'renders unsubscribe page' do
+ expect(response.status).to eq(200)
+ expect(response).to render_template :unsubscribe
+ end
end
- it 'does not set the flash message' do
- expect(controller).not_to set_flash[:notice]
+ context 'when project is public' do
+ context 'when unsubscribing from issue' do
+ let(:noteable) { issue }
+
+ it 'shows issue title' do
+ expect(response.body).to include(issue.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from confidential issue' do
+ let(:noteable) { confidential_issue }
+
+ it 'does not show issue title' do
+ expect(response.body).not_to include(confidential_issue.title)
+ expect(response.body).to include(confidential_issue.to_reference)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from merge request' do
+ let(:noteable) { merge_request }
+
+ it 'shows merge request title' do
+ expect(response.body).to include(merge_request.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
end
- it 'redirects to the login page' do
- expect(response).to render_template :unsubscribe
+ context 'when project is not public' do
+ let(:target_project) { private_project }
+
+ context 'when unsubscribing from issue' do
+ let(:noteable) { issue }
+
+ it 'shows issue title' do
+ expect(response.body).not_to include(issue.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from confidential issue' do
+ let(:noteable) { confidential_issue }
+
+ it 'does not show issue title' do
+ expect(response.body).not_to include(confidential_issue.title)
+ expect(response.body).to include(confidential_issue.to_reference)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from merge request' do
+ let(:noteable) { merge_request }
+
+ it 'shows merge request title' do
+ expect(response.body).not_to include(merge_request.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
end
end
end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index ea7242c1aa8..9c4ddce5409 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SessionsController do
@@ -56,7 +58,26 @@ describe SessionsController do
it 'authenticates user correctly' do
post(:create, params: { user: user_params })
- expect(subject.current_user). to eq user
+ expect(subject.current_user).to eq user
+ end
+
+ context 'with password authentication disabled' do
+ before do
+ stub_application_setting(password_authentication_enabled_for_web: false)
+ end
+
+ it 'does not sign in the user' do
+ post(:create, params: { user: user_params })
+
+ expect(@request.env['warden']).not_to be_authenticated
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'returns status 403' do
+ post(:create, params: { user: user_params })
+
+ expect(response.status).to eq 403
+ end
end
it 'creates an audit log record' do
@@ -151,6 +172,19 @@ describe SessionsController do
end
end
+ context 'with password authentication disabled' do
+ before do
+ stub_application_setting(password_authentication_enabled_for_web: false)
+ end
+
+ it 'allows 2FA stage of non-password login' do
+ authenticate_2fa(otp_attempt: user.current_otp)
+
+ expect(@request.env['warden']).to be_authenticated
+ expect(subject.current_user).to eq user
+ end
+ end
+
##
# See #14900 issue
#
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 6efbd1f6c71..936d7c7dae4 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Snippets::NotesController do
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 5c6858dc7b2..f8666a1986f 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SnippetsController do
@@ -205,6 +207,8 @@ describe SnippetsController do
end
context 'when the snippet description contains a file' do
+ include FileMoverHelpers
+
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
let(:text_file) { '/-/system/temp/secret78/text.txt' }
let(:description) do
@@ -215,6 +219,8 @@ describe SnippetsController do
before do
allow(FileUtils).to receive(:mkdir_p)
allow(FileUtils).to receive(:move)
+ stub_file_mover(text_file)
+ stub_file_mover(picture_file)
end
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index e52a5fe42f2..d27658e02cb 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'content not cached without revalidation' do
it 'ensures content will not be cached without revalidation' do
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
index c71d75a3e7f..babc93a83e5 100644
--- a/spec/controllers/user_callouts_controller_spec.rb
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserCalloutsController do
@@ -14,11 +16,11 @@ describe UserCalloutsController do
let(:feature_name) { UserCallout.feature_names.keys.first }
context 'when callout entry does not exist' do
- it 'should create a callout entry with dismissed state' do
+ it 'creates a callout entry with dismissed state' do
expect { subject }.to change { UserCallout.count }.by(1)
end
- it 'should return success' do
+ it 'returns success' do
subject
expect(response).to have_gitlab_http_status(:ok)
@@ -28,7 +30,7 @@ describe UserCalloutsController do
context 'when callout entry already exists' do
let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
- it 'should return success' do
+ it 'returns success' do
subject
expect(response).to have_gitlab_http_status(:ok)
@@ -39,7 +41,7 @@ describe UserCalloutsController do
context 'with invalid feature name' do
let(:feature_name) { 'bogus_feature_name' }
- it 'should return bad request' do
+ it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
diff --git a/spec/controllers/users/terms_controller_spec.rb b/spec/controllers/users/terms_controller_spec.rb
index cbfd2b17864..e0bdec3df1d 100644
--- a/spec/controllers/users/terms_controller_spec.rb
+++ b/spec/controllers/users/terms_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::TermsController do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index af61026098b..c3d6ea9cbcd 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UsersController do
@@ -185,13 +187,32 @@ describe UsersController do
context 'for user' do
context 'with public profile' do
- it 'renders calendar_activities' do
- push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
- EventCreateService.new.push(project, public_user, push_data)
+ let(:issue) { create(:issue, project: project, author: user) }
+ let(:note) { create(:note, noteable: issue, author: user, project: project) }
+ render_views
+
+ before do
+ create_push_event
+ create_note_event
+ end
+
+ it 'renders calendar_activities' do
get :calendar_activities, params: { username: public_user.username }
+
expect(assigns[:events]).not_to be_empty
end
+
+ it 'avoids N+1 queries', :request_store do
+ get :calendar_activities, params: { username: public_user.username }
+
+ control = ActiveRecord::QueryRecorder.new { get :calendar_activities, params: { username: public_user.username } }
+
+ create_push_event
+ create_note_event
+
+ expect { get :calendar_activities, params: { username: public_user.username } }.not_to exceed_query_limit(control)
+ end
end
context 'with private profile' do
@@ -203,6 +224,21 @@ describe UsersController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ context 'external authorization' do
+ subject { get :calendar_activities, params: { username: user.username } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
+
+ def create_push_event
+ push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
+ EventCreateService.new.push(project, public_user, push_data)
+ end
+
+ def create_note_event
+ EventCreateService.new.leave_note(note, public_user)
+ end
end
end
@@ -258,6 +294,12 @@ describe UsersController do
expect(JSON.parse(response.body)).to have_key('html')
end
end
+
+ context 'external authorization' do
+ subject { get :snippets, params: { username: user.username } }
+
+ it_behaves_like 'disabled when using an external authorization service'
+ end
end
describe 'GET #exists' do
diff --git a/spec/db/importers/common_metrics_importer_spec.rb b/spec/db/importers/common_metrics_importer_spec.rb
index 6133b17ac61..a717c8cd04d 100644
--- a/spec/db/importers/common_metrics_importer_spec.rb
+++ b/spec/db/importers/common_metrics_importer_spec.rb
@@ -23,10 +23,10 @@ describe Importers::CommonMetricsImporter do
subject { described_class.new }
context "does import common_metrics.yml" do
- let(:groups) { subject.content }
- let(:metrics) { groups.map { |group| group['metrics'] }.flatten }
- let(:queries) { metrics.map { |group| group['queries'] }.flatten }
- let(:query_ids) { queries.map { |query| query['id'] } }
+ let(:groups) { subject.content['panel_groups'] }
+ let(:panels) { groups.map { |group| group['panels'] }.flatten }
+ let(:metrics) { panels.map { |group| group['metrics'] }.flatten }
+ let(:metric_ids) { metrics.map { |metric| metric['id'] } }
before do
subject.execute
@@ -36,20 +36,20 @@ describe Importers::CommonMetricsImporter do
expect(PrometheusMetric.common.group(:group).count.count).to eq(groups.count)
end
- it "has the same amount of metrics" do
- expect(PrometheusMetric.common.group(:group, :title).count.count).to eq(metrics.count)
+ it "has the same amount of panels" do
+ expect(PrometheusMetric.common.group(:group, :title).count.count).to eq(panels.count)
end
- it "has the same amount of queries" do
- expect(PrometheusMetric.common.count).to eq(queries.count)
+ it "has the same amount of metrics" do
+ expect(PrometheusMetric.common.count).to eq(metrics.count)
end
it "does not have duplicate IDs" do
- expect(query_ids).to eq(query_ids.uniq)
+ expect(metric_ids).to eq(metric_ids.uniq)
end
it "imports all IDs" do
- expect(PrometheusMetric.common.pluck(:identifier)).to contain_exactly(*query_ids)
+ expect(PrometheusMetric.common.pluck(:identifier)).to contain_exactly(*metric_ids)
end
end
@@ -65,24 +65,26 @@ describe Importers::CommonMetricsImporter do
context 'does import properly all fields' do
let(:query_identifier) { 'response-metric' }
- let(:group) do
+ let(:dashboard) do
{
- group: 'Response metrics (NGINX Ingress)',
- metrics: [{
- title: "Throughput",
- y_label: "Requests / Sec",
- queries: [{
- id: query_identifier,
- query_range: 'my-query',
- unit: 'my-unit',
- label: 'status code'
+ panel_groups: [{
+ group: 'Response metrics (NGINX Ingress)',
+ panels: [{
+ title: "Throughput",
+ y_label: "Requests / Sec",
+ metrics: [{
+ id: query_identifier,
+ query_range: 'my-query',
+ unit: 'my-unit',
+ label: 'status code'
+ }]
}]
}]
}
end
before do
- expect(subject).to receive(:content) { [group.deep_stringify_keys] }
+ expect(subject).to receive(:content) { dashboard.deep_stringify_keys }
end
shared_examples 'stores metric' do
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 897b4411055..40c3a6d90d0 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -25,12 +25,12 @@ describe 'Database schema' do
events: %w[target_id],
forked_project_links: %w[forked_from_project_id],
identities: %w[user_id],
- issues: %w[last_edited_by_id],
+ issues: %w[last_edited_by_id state_id],
keys: %w[user_id],
label_links: %w[target_id],
lfs_objects_projects: %w[lfs_object_id project_id],
members: %w[source_id created_by_id],
- merge_requests: %w[last_edited_by_id],
+ merge_requests: %w[last_edited_by_id state_id],
namespaces: %w[owner_id parent_id],
notes: %w[author_id commit_id noteable_id updated_by_id resolved_by_id discussion_id],
notification_settings: %w[source_id],
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0b3e67b4987..a473136b57b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -75,6 +75,10 @@ FactoryBot.define do
status 'created'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :scheduled do
schedulable
status 'scheduled'
@@ -244,17 +248,6 @@ FactoryBot.define do
runner factory: :ci_runner
end
- trait :legacy_artifacts do
- after(:create) do |build, _|
- build.update!(
- legacy_artifacts_file: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip'),
- legacy_artifacts_metadata: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
- )
- end
- end
-
trait :artifacts do
after(:create) do |build|
create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at)
@@ -332,6 +325,11 @@ FactoryBot.define do
failure_reason 2
end
+ trait :prerequisite_failure do
+ failed
+ failure_reason 10
+ end
+
trait :with_runner_session do
after(:build) do |build|
build.build_runner_session(url: 'https://localhost')
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
index 64716842b12..9bf520a2c0a 100644
--- a/spec/factories/ci/group_variables.rb
+++ b/spec/factories/ci/group_variables.rb
@@ -2,6 +2,7 @@ FactoryBot.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ masked false
trait(:protected) do
protected true
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 2c76c22ba69..542fa9775cd 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -45,9 +45,12 @@ FactoryBot.define do
file_type :archive
file_format :zip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
@@ -61,9 +64,12 @@ FactoryBot.define do
file_type :metadata
file_format :gzip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb
index b2b79807429..4b83ba2ac1b 100644
--- a/spec/factories/ci/pipeline_schedule.rb
+++ b/spec/factories/ci/pipeline_schedule.rb
@@ -7,6 +7,16 @@ FactoryBot.define do
description "pipeline schedule"
project
+ trait :every_minute do
+ cron '*/1 * * * *'
+ cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
+ end
+
+ trait :hourly do
+ cron '* */1 * * *'
+ cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
+ end
+
trait :nightly do
cron '0 1 * * *'
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
index 8d29118e310..c85b97fbfc7 100644
--- a/spec/factories/ci/pipeline_schedule_variables.rb
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -2,6 +2,7 @@ FactoryBot.define do
factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ variable_type 'env_var'
pipeline_schedule factory: :ci_pipeline_schedule
end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 8a44ce52849..aa5ccbda6cd 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -50,6 +50,14 @@ FactoryBot.define do
failure_reason :config_error
end
+ trait :created do
+ status :created
+ end
+
+ trait :preparing do
+ status :preparing
+ end
+
trait :blocked do
status :manual
end
@@ -82,6 +90,12 @@ FactoryBot.define do
end
end
+ trait :with_job do
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :auto_devops_source do
config_source { Ci::Pipeline.config_sources[:auto_devops_source] }
end
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 3d014b9b54f..97a7c9ba252 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -2,6 +2,7 @@ FactoryBot.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ masked false
trait(:protected) do
protected true
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index fe56ac5b71d..d78f01828d7 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -6,6 +6,11 @@ FactoryBot.define do
status(-2)
end
+ trait :errored do
+ status(-1)
+ status_reason 'something went wrong'
+ end
+
trait :installable do
status 0
end
@@ -30,17 +35,21 @@ FactoryBot.define do
status 5
end
- trait :errored do
- status(-1)
+ trait :update_errored do
+ status(6)
status_reason 'something went wrong'
end
- trait :update_errored do
- status(6)
+ trait :uninstalling do
+ status 7
+ end
+
+ trait :uninstall_errored do
+ status(8)
status_reason 'something went wrong'
end
- trait :timeouted do
+ trait :timed_out do
installing
updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index a2e5f4862db..6eb0194b710 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -3,6 +3,7 @@ FactoryBot.define do
user
name 'test-cluster'
cluster_type :project_type
+ managed true
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
@@ -12,7 +13,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator|
- cluster.projects << create(:project, :repository)
+ cluster.projects << create(:project, :repository) unless cluster.projects.present?
end
end
@@ -20,7 +21,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:group_type] }
before(:create) do |cluster, evalutor|
- cluster.groups << create(:group)
+ cluster.groups << create(:group) unless cluster.groups.present?
end
end
@@ -63,5 +64,9 @@ FactoryBot.define do
trait :with_domain do
domain 'example.com'
end
+
+ trait :not_managed do
+ managed false
+ end
end
end
diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb
index a002ab28519..186c7c8027c 100644
--- a/spec/factories/clusters/providers/gcp.rb
+++ b/spec/factories/clusters/providers/gcp.rb
@@ -28,5 +28,9 @@ FactoryBot.define do
gcp.make_errored('Something wrong')
end
end
+
+ trait :abac_enabled do
+ legacy_abac true
+ end
end
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 381bf07f6a0..848a31e96c1 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -33,6 +33,10 @@ FactoryBot.define do
status 'pending'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :created do
status 'created'
end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 011c98599a3..db438ad32d3 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -1,6 +1,6 @@
FactoryBot.define do
factory :deployment, class: Deployment do
- sha '97de212e80737a608d939f648d959671fb0a0142'
+ sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
ref 'master'
tag false
user nil
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 3b354c0d96b..18a0c2ec731 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -4,6 +4,7 @@ FactoryBot.define do
path { name.downcase.gsub(/\s/, '_') }
type 'Group'
owner nil
+ project_creation_level ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS
after(:create) do |group|
if group.owner
@@ -36,5 +37,13 @@ FactoryBot.define do
trait :nested do
parent factory: :group
end
+
+ trait :auto_devops_enabled do
+ auto_devops_enabled true
+ end
+
+ trait :auto_devops_disabled do
+ auto_devops_enabled false
+ end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 2392bfc4a53..0b6a43b13a9 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -46,10 +46,26 @@ FactoryBot.define do
target_branch "improve/awesome"
end
+ trait :merged_last_month do
+ merged
+
+ after(:build) do |merge_request|
+ merge_request.build_metrics.merged_at = 1.month.ago
+ end
+ end
+
trait :closed do
state :closed
end
+ trait :closed_last_month do
+ closed
+
+ after(:build) do |merge_request|
+ merge_request.build_metrics.latest_closed_at = 1.month.ago
+ end
+ end
+
trait :opened do
state :opened
end
@@ -79,7 +95,8 @@ FactoryBot.define do
end
trait :merge_when_pipeline_succeeds do
- merge_when_pipeline_succeeds true
+ auto_merge_enabled true
+ auto_merge_strategy AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
merge_user { author }
end
@@ -101,6 +118,47 @@ FactoryBot.define do
end
end
+ trait :with_legacy_detached_merge_request_pipeline do
+ after(:create) do |merge_request|
+ merge_request.pipelines_for_merge_request << create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.source_branch_sha)
+ end
+ end
+
+ trait :with_detached_merge_request_pipeline do
+ after(:create) do |merge_request|
+ merge_request.pipelines_for_merge_request << create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project,
+ ref: merge_request.ref_path,
+ sha: merge_request.source_branch_sha)
+ end
+ end
+
+ trait :with_merge_request_pipeline do
+ transient do
+ merge_sha { 'test-merge-sha' }
+ source_sha { source_branch_sha }
+ target_sha { target_branch_sha }
+ end
+
+ after(:create) do |merge_request, evaluator|
+ merge_request.pipelines_for_merge_request << create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project,
+ ref: merge_request.merge_ref_path,
+ sha: evaluator.merge_sha,
+ source_sha: evaluator.source_sha,
+ target_sha: evaluator.target_sha)
+ end
+ end
+
trait :deployed_review_app do
target_branch 'pages-deploy-target'
diff --git a/spec/factories/pages_domain_acme_orders.rb b/spec/factories/pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..7f9ee1c8f9c
--- /dev/null
+++ b/spec/factories/pages_domain_acme_orders.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :pages_domain_acme_order do
+ pages_domain
+ url { 'https://example.com/' }
+ expires_at { 1.day.from_now }
+ challenge_token { 'challenge_token' }
+ challenge_file_content { 'filecontent' }
+
+ private_key { OpenSSL::PKey::RSA.new(4096).to_pem }
+
+ trait :expired do
+ expires_at { 1.day.ago }
+ end
+ end
+end
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 20671da016e..db8384877b0 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -41,6 +41,14 @@ nNp/xedE1YxutQ==
enabled_until nil
end
+ trait :scheduled_for_removal do
+ remove_at { 1.day.from_now }
+ end
+
+ trait :should_be_removed do
+ remove_at { 1.day.ago }
+ end
+
trait :unverified do
verified_at nil
end
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
index 36e54cf44b4..8cac666069c 100644
--- a/spec/factories/pool_repositories.rb
+++ b/spec/factories/pool_repositories.rb
@@ -5,6 +5,7 @@ FactoryBot.define do
before(:create) do |pool|
pool.source_project = create(:project, :repository)
+ pool.source_project.update!(pool_repository: pool)
end
trait :scheduled do
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 75ac7cc7687..1de42512402 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -2,7 +2,6 @@ FactoryBot.define do
factory :project_auto_devops do
project
enabled true
- domain "example.com"
deploy_strategy :continuous
trait :continuous_deployment do
diff --git a/spec/factories/project_daily_statistics.rb b/spec/factories/project_daily_statistics.rb
new file mode 100644
index 00000000000..7e4142fa401
--- /dev/null
+++ b/spec/factories/project_daily_statistics.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_daily_statistic do
+ project
+ fetch_count 1
+ end
+end
diff --git a/spec/factories/project_metrics_settings.rb b/spec/factories/project_metrics_settings.rb
new file mode 100644
index 00000000000..234753f9b87
--- /dev/null
+++ b/spec/factories/project_metrics_settings.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_metrics_setting, class: ProjectMetricsSetting do
+ project
+ external_dashboard_url 'https://grafana.com'
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index f7ef34d773b..743ec322885 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -260,6 +260,7 @@ FactoryBot.define do
trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED }
trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED }
trait(:merge_requests_private) { merge_requests_access_level ProjectFeature::PRIVATE }
+ trait(:merge_requests_public) { merge_requests_access_level ProjectFeature::PUBLIC }
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
@@ -271,6 +272,10 @@ FactoryBot.define do
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
end
+
+ trait :auto_devops_disabled do
+ association :auto_devops, factory: [:project_auto_devops, :disabled]
+ end
end
# Project with empty repository
@@ -313,6 +318,20 @@ FactoryBot.define do
end
end
+ factory :youtrack_project, parent: :project do
+ has_external_issue_tracker true
+
+ after :create do |project|
+ project.create_youtrack_service(
+ active: true,
+ properties: {
+ 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
+ 'issues_url' => 'http://youtrack/issues/:id'
+ }
+ )
+ end
+ end
+
factory :jira_project, parent: :project do
has_external_issue_tracker true
jira_service
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 70c34f8640b..0d8c26a2ee9 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -62,4 +62,10 @@ FactoryBot.define do
project_key: 'jira-key'
)
end
+
+ factory :hipchat_service do
+ project
+ type 'HipchatService'
+ token 'test_token'
+ end
end
diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb
index 307523cc061..b1427e0211f 100644
--- a/spec/factories/suggestions.rb
+++ b/spec/factories/suggestions.rb
@@ -16,5 +16,11 @@ FactoryBot.define do
applied true
commit_id { RepoHelpers.sample_commit.id }
end
+
+ trait :content_from_repo do
+ after(:build) do |suggestion, evaluator|
+ suggestion.from_content = suggestion.fetch_from_content
+ end
+ end
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 7256f785e1f..52f6962f16b 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -13,7 +13,7 @@ FactoryBot.define do
end
# this needs to comply with RecordsUpload::Concern#upload_path
- path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
+ path { File.join("uploads/-/system", model.class.underscore, mount_point.to_s, 'avatar.jpg') }
trait :personal_snippet_upload do
uploader "PersonalFileUploader"
@@ -54,10 +54,7 @@ FactoryBot.define do
end
trait :attachment_upload do
- transient do
- mount_point :attachment
- end
-
+ mount_point :attachment
model { build(:note) }
uploader "AttachmentUploader"
end
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 83cd686818c..f6c498f7a4c 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Appearance' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index 02f50d7e27f..1f83d04d9aa 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -13,5 +13,6 @@ describe 'Admin browses logs' do
expect(page).to have_link 'test.log'
expect(page).to have_link 'sidekiq.log'
expect(page).to have_link 'repocheck.log'
+ expect(page).to have_link 'kubernetes.log'
end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 25ed3bdc88e..ce780789f5a 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -73,22 +73,26 @@ describe 'Admin::Hooks' do
end
describe 'Remove existing hook', :js do
+ let(:hook_url) { generate(:url) }
+
before do
- create(:system_hook)
+ create(:system_hook, url: hook_url)
end
context 'removes existing hook' do
it 'from hooks list page' do
visit admin_hooks_path
- expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1)
+ accept_confirm { click_link 'Remove' }
+ expect(page).not_to have_content(hook_url)
end
it 'from hook edit page' do
visit admin_hooks_path
click_link 'Edit'
- expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1)
+ accept_confirm { click_link 'Remove' }
+ expect(page).not_to have_content(hook_url)
end
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index ed9c0ea9ac0..97b432a6751 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -141,6 +141,56 @@ describe "Admin Runners" do
end
end
+ describe 'filter by tag', :js do
+ it 'shows correct runner when tag matches' do
+ create :ci_runner, description: 'runner-blue', tag_list: ['blue']
+ create :ci_runner, description: 'runner-red', tag_list: ['red']
+
+ visit admin_runners_path
+
+ expect(page).to have_content 'runner-blue'
+ expect(page).to have_content 'runner-red'
+
+ input_filtered_search_keys('tag:blue')
+
+ expect(page).to have_content 'runner-blue'
+ expect(page).not_to have_content 'runner-red'
+ end
+
+ it 'shows no runner when tag does not match' do
+ create :ci_runner, description: 'runner-blue', tag_list: ['blue']
+ create :ci_runner, description: 'runner-red', tag_list: ['blue']
+
+ visit admin_runners_path
+
+ input_filtered_search_keys('tag:red')
+
+ expect(page).not_to have_content 'runner-blue'
+ expect(page).not_to have_content 'runner-blue'
+ expect(page).to have_text 'No runners found'
+ end
+
+ it 'shows correct runner when tag is selected and search term is entered' do
+ create :ci_runner, description: 'runner-a-1', tag_list: ['blue']
+ create :ci_runner, description: 'runner-a-2', tag_list: ['red']
+ create :ci_runner, description: 'runner-b-1', tag_list: ['blue']
+
+ visit admin_runners_path
+
+ input_filtered_search_keys('tag:blue')
+
+ expect(page).to have_content 'runner-a-1'
+ expect(page).to have_content 'runner-b-1'
+ expect(page).not_to have_content 'runner-a-2'
+
+ input_filtered_search_keys('tag:blue runner-a')
+
+ expect(page).to have_content 'runner-a-1'
+ expect(page).not_to have_content 'runner-b-1'
+ expect(page).not_to have_content 'runner-a-2'
+ end
+ end
+
it 'sorts by last contact date', :js do
create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
new file mode 100644
index 00000000000..b5323a1c76d
--- /dev/null
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe "Admin > Admin sees project statistics" do
+ let(:current_user) { create(:admin) }
+
+ before do
+ sign_in(current_user)
+
+ visit admin_project_path(project)
+ end
+
+ context 'when project has statistics' do
+ let(:project) { create(:project, :repository) }
+
+ it "shows project statistics" do
+ expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)")
+ end
+ end
+
+ context 'when project has no statistics' do
+ let(:project) { create(:project, :repository) { |project| project.statistics.destroy } }
+
+ it "shows 'Storage: Unknown'" do
+ expect(page).to have_content("Storage: Unknown")
+ end
+ end
+end
diff --git a/spec/features/admin/admin_sees_projects_statistics_spec.rb b/spec/features/admin/admin_sees_projects_statistics_spec.rb
new file mode 100644
index 00000000000..6a6f369ac7c
--- /dev/null
+++ b/spec/features/admin/admin_sees_projects_statistics_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe "Admin > Admin sees projects statistics" do
+ let(:current_user) { create(:admin) }
+
+ before do
+ create(:project, :repository)
+ create(:project, :repository) { |project| project.statistics.destroy }
+
+ sign_in(current_user)
+
+ visit admin_projects_path
+ end
+
+ it "shows project statistics for projects that have them" do
+ expect(page.all('.stats').map(&:text)).to contain_exactly("0 Bytes", "Unknown")
+ end
+end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 04f39b807d7..c4dbe23f6b4 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -230,6 +230,13 @@ describe 'Admin updates settings' do
expect(find_field('Username').value).to eq 'test_user'
expect(find('#service_push_channel').value).to eq '#test_channel'
end
+
+ it 'defaults Deployment events to false for chat notification template settings' do
+ first(:link, 'Service Templates').click
+ click_link 'Slack notifications'
+
+ expect(find_field('Deployment')).not_to be_checked
+ end
end
context 'CI/CD page' do
@@ -325,16 +332,19 @@ describe 'Admin updates settings' do
end
context 'Network page' do
- it 'Enable outbound requests' do
+ it 'Changes Outbound requests settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
check 'Allow requests to the local network from hooks and services'
+ # Enabled by default
+ uncheck 'Enforce DNS rebinding attack protection'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true
+ expect(Gitlab::CurrentSettings.dns_rebinding_protection_enabled).to be false
end
end
@@ -368,15 +378,50 @@ describe 'Admin updates settings' do
expect(Gitlab::CurrentSettings.pages_domain_verification_enabled?).to be_truthy
expect(page).to have_content "Application settings saved successfully"
end
+
+ context 'When pages_auto_ssl is enabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: true)
+ visit preferences_admin_application_settings_path
+ end
+
+ it "Change Pages Let's Encrypt settings" do
+ page.within('.as-pages') do
+ fill_in 'Email', with: 'my@test.example.com'
+ check "I have read and agree to the Let's Encrypt Terms of Service"
+ click_button 'Save changes'
+ end
+
+ expect(Gitlab::CurrentSettings.lets_encrypt_notification_email).to eq 'my@test.example.com'
+ expect(Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted).to eq true
+ end
+ end
+
+ context 'When pages_auto_ssl is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+ visit preferences_admin_application_settings_path
+ end
+
+ it "Doesn't show Let's Encrypt options" do
+ page.within('.as-pages') do
+ expect(page).not_to have_content('Email')
+ end
+ end
+ end
end
def check_all_events
page.check('Active')
page.check('Push')
- page.check('Tag push')
- page.check('Note')
page.check('Issue')
+ page.check('Confidential issue')
page.check('Merge request')
+ page.check('Note')
+ page.check('Confidential note')
+ page.check('Tag push')
page.check('Pipeline')
+ page.check('Wiki page')
+ page.check('Deployment')
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index b1c6f308bc6..29545779a34 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -34,14 +34,11 @@ describe "Admin::Users" do
expect(page).to have_button('Delete user and contributions')
end
- describe "view extra user information", :js do
- it 'does not have the user popover open' do
+ describe "view extra user information" do
+ it 'shows the user popover on hover', :js, :quarantine do
expect(page).not_to have_selector('#__BV_popover_1__')
- end
- it 'shows the user popover on hover' do
first_user_link = page.first('.js-user-link')
-
first_user_link.hover
expect(page).to have_selector('#__BV_popover_1__')
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index dfa1c92ea49..d523e2992db 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard Issues Feed" do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index ea69ec0319b..4c6175f5590 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -345,7 +345,7 @@ describe 'Issue Boards', :js do
click_link 'Create project label'
- fill_in('new_label_name', with: 'Testing New Label')
+ fill_in('new_label_name', with: 'Testing New Label - with list')
first('.suggest-colors a').click
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index ee38e756f9e..b1798c11361 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Issue Boards', :js do
include BoardHelpers
+ include FilteredSearchHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
@@ -129,9 +130,10 @@ describe 'Issue Boards', :js do
click_link 'Unassigned'
end
+ close_dropdown_menu_if_visible
wait_for_requests
- expect(page).to have_content('No assignee')
+ expect(page).to have_content('None')
end
expect(card_two).not_to have_selector('.avatar')
@@ -141,7 +143,7 @@ describe 'Issue Boards', :js do
click_card(card)
page.within(find('.assignee')) do
- expect(page).to have_content('No assignee')
+ expect(page).to have_content('None')
click_button 'assign yourself'
@@ -220,6 +222,21 @@ describe 'Issue Boards', :js do
end
end
+ context 'time tracking' do
+ before do
+ issue2.timelogs.create(time_spent: 14400, user: user)
+ issue2.update!(time_estimate: 28800)
+ end
+
+ it 'shows time tracking progress bar' do
+ click_card(card)
+
+ page.within('.time-tracking') do
+ expect(find('.time-tracking-content .compare-meter')['data-original-title']).to eq('Time remaining: 4h')
+ end
+ end
+ end
+
context 'due date' do
it 'updates due date' do
click_card(card)
@@ -335,14 +352,36 @@ describe 'Issue Boards', :js do
page.within('.labels') do
click_link 'Edit'
+ wait_for_requests
+
+ click_link 'Create project label'
+ fill_in 'new_label_name', with: 'test label'
+ first('.suggest-colors-dropdown a').click
+ click_button 'Create'
+ wait_for_requests
+
+ expect(page).to have_link 'test label'
+ end
+ expect(page).to have_selector('.board', count: 3)
+ end
+
+ it 'creates project label and list' do
+ click_card(card)
+
+ page.within('.labels') do
+ click_link 'Edit'
+ wait_for_requests
+
click_link 'Create project label'
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
+ first('.js-add-list').click
click_button 'Create'
wait_for_requests
expect(page).to have_link 'test label'
end
+ expect(page).to have_selector('.board', count: 4)
end
end
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index 0a9c4bcaf12..683c57a97f8 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -3,7 +3,11 @@
require 'spec_helper'
describe 'Clusterable > Show page' do
+ include KubernetesHelpers
+
let(:current_user) { create(:user) }
+ let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' }
+ let(:hide_modifier_selector) { '.hide' }
before do
sign_in(current_user)
@@ -35,7 +39,7 @@ describe 'Clusterable > Show page' do
it 'shows help text with the domain as an alternative to custom domain' do
within '#cluster-integration' do
- expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain')
+ expect(find(cluster_ingress_help_text_selector)).not_to match_css(hide_modifier_selector)
end
end
end
@@ -45,18 +49,87 @@ describe 'Clusterable > Show page' do
visit cluster_path
within '#cluster-integration' do
- expect(page).not_to have_content('can be used instead of a custom domain.')
+ expect(find(cluster_ingress_help_text_selector)).to match_css(hide_modifier_selector)
end
end
end
end
+ shared_examples 'editing a GCP cluster' do
+ before do
+ clusterable.add_maintainer(current_user)
+ visit cluster_path
+ end
+
+ it 'is not able to edit the name, API url, CA certificate nor token' do
+ within('#js-cluster-details') do
+ cluster_name_field = find('.cluster-name')
+ api_url_field = find('#cluster_platform_kubernetes_attributes_api_url')
+ ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert')
+ token_field = find('#cluster_platform_kubernetes_attributes_token')
+
+ expect(cluster_name_field).to be_readonly
+ expect(api_url_field).to be_readonly
+ expect(ca_certificate_field).to be_readonly
+ expect(token_field).to be_readonly
+ end
+ end
+
+ it 'displays GKE information' do
+ within('#advanced-settings-section') do
+ expect(page).to have_content('Google Kubernetes Engine')
+ expect(page).to have_content('Manage your Kubernetes cluster by visiting')
+ end
+ end
+ end
+
+ shared_examples 'editing a user-provided cluster' do
+ before do
+ stub_kubeclient_discover(cluster.platform.api_url)
+ clusterable.add_maintainer(current_user)
+ visit cluster_path
+ end
+
+ it 'is able to edit the name, API url, CA certificate and token' do
+ within('#js-cluster-details') do
+ cluster_name_field = find('#cluster_name')
+ api_url_field = find('#cluster_platform_kubernetes_attributes_api_url')
+ ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert')
+ token_field = find('#cluster_platform_kubernetes_attributes_token')
+
+ expect(cluster_name_field).not_to be_readonly
+ expect(api_url_field).not_to be_readonly
+ expect(ca_certificate_field).not_to be_readonly
+ expect(token_field).not_to be_readonly
+ end
+ end
+
+ it 'does not display GKE information' do
+ within('#advanced-settings-section') do
+ expect(page).not_to have_content('Google Kubernetes Engine')
+ expect(page).not_to have_content('Manage your Kubernetes cluster by visiting')
+ end
+ end
+ end
+
context 'when clusterable is a project' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
+
+ it_behaves_like 'editing a GCP cluster' do
+ let(:clusterable) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
+ let(:cluster_path) { project_cluster_path(clusterable, cluster) }
+ end
+
+ it_behaves_like 'editing a user-provided cluster' do
+ let(:clusterable) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [clusterable]) }
+ let(:cluster_path) { project_cluster_path(clusterable, cluster) }
+ end
end
context 'when clusterable is a group' do
@@ -65,5 +138,17 @@ describe 'Clusterable > Show page' do
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
+
+ it_behaves_like 'editing a GCP cluster' do
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
+ let(:cluster_path) { group_cluster_path(clusterable, cluster) }
+ end
+
+ it_behaves_like 'editing a user-provided cluster' do
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster, :provided_by_user, :group, groups: [clusterable]) }
+ let(:cluster_path) { group_cluster_path(clusterable, cluster) }
+ end
end
end
diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb
index 9a4b7bd2444..4b7e7465df1 100644
--- a/spec/features/commits/user_uses_quick_actions_spec.rb
+++ b/spec/features/commits/user_uses_quick_actions_spec.rb
@@ -22,27 +22,6 @@ describe 'Commit > User uses quick actions', :js do
let(:tag_message) { 'Stable release' }
let(:truncated_commit_sha) { Commit.truncate_sha(commit.sha) }
- it 'tags this commit' do
- add_note("/tag #{tag_name} #{tag_message}")
-
- expect(page).to have_content 'Commands applied'
- expect(page).to have_content "tagged commit #{truncated_commit_sha}"
- expect(page).to have_content tag_name
-
- visit project_tag_path(project, tag_name)
- expect(page).to have_content tag_name
- expect(page).to have_content tag_message
- expect(page).to have_content truncated_commit_sha
- end
-
- describe 'preview', :js do
- it 'removes quick action from note and explains it' do
- preview_note("/tag #{tag_name} #{tag_message}")
-
- expect(page).not_to have_content '/tag'
- expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"}
- expect(page).to have_content tag_name
- end
- end
+ it_behaves_like 'tag quick action'
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5c6c1c4fd15..2adeb37c98a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -89,7 +89,7 @@ describe 'Commits' do
context 'Download artifacts' do
before do
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
end
it do
@@ -119,7 +119,7 @@ describe 'Commits' do
context "when logged as reporter" do
before do
project.add_reporter(user)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
@@ -141,7 +141,7 @@ describe 'Commits' do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index f4b2b9033ab..48edc764a8e 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -90,25 +90,6 @@ describe 'Cycle Analytics', :js do
end
end
end
-
- context "when my preferred language is Spanish" do
- before do
- user.update_attribute(:preferred_language, 'es')
-
- project.add_maintainer(user)
- sign_in(user)
- visit project_cycle_analytics_path(project)
- wait_for_requests
- end
-
- it 'shows the content in Spanish' do
- expect(page).to have_content('Estado del Pipeline')
- end
-
- it 'resets the language to English' do
- expect(I18n.locale).to eq(:en)
- end
- end
end
context "as a guest" do
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index bf91dc121d8..c55dc4523f7 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -39,6 +39,8 @@ describe 'Dashboard > Activity' do
event
end
+ let(:issue) { create(:issue, project: project) }
+
let!(:merged_event) do
create(:event, :merged, project: project, target: merge_request, author: user)
end
@@ -59,6 +61,10 @@ describe 'Dashboard > Activity' do
create(:event, :closed, project: project, target: milestone, author: user)
end
+ let!(:issue_event) do
+ create(:event, :created, project: project, target: issue, author: user)
+ end
+
before do
project.add_maintainer(user)
@@ -74,6 +80,7 @@ describe 'Dashboard > Activity' do
expect(page).to have_content('closed')
expect(page).to have_content('commented on')
expect(page).to have_content('closed milestone')
+ expect(page).to have_content('opened issue')
end
end
@@ -87,6 +94,7 @@ describe 'Dashboard > Activity' do
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
+ expect(page).not_to have_content('opened issue')
end
end
@@ -100,6 +108,7 @@ describe 'Dashboard > Activity' do
expect(page).to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
+ expect(page).not_to have_content('opened issue')
end
end
@@ -111,9 +120,10 @@ describe 'Dashboard > Activity' do
expect(page).not_to have_content('pushed new branch')
expect(page).not_to have_content('joined')
expect(page).not_to have_content('accepted')
- expect(page).to have_content('closed')
+ expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
- expect(page).to have_content('closed milestone')
+ expect(page).not_to have_content('closed milestone')
+ expect(page).to have_content('opened issue')
end
end
@@ -127,6 +137,7 @@ describe 'Dashboard > Activity' do
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).to have_content('commented on')
+ expect(page).not_to have_content('opened issue')
end
end
@@ -140,6 +151,7 @@ describe 'Dashboard > Activity' do
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
+ expect(page).not_to have_content('opened issue')
end
end
@@ -155,6 +167,7 @@ describe 'Dashboard > Activity' do
expect(page).not_to have_content('accepted')
expect(page).not_to have_content('closed')
expect(page).not_to have_content('commented on')
+ expect(page).not_to have_content('opened issue')
end
end
end
diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb
new file mode 100644
index 00000000000..4098dd02141
--- /dev/null
+++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe 'The group dashboard' do
+ include ExternalAuthorizationServiceHelpers
+
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
+ end
+
+ describe 'The top navigation' do
+ it 'has all the expected links' do
+ visit dashboard_groups_path
+
+ within('.navbar') do
+ expect(page).to have_button('Projects')
+ expect(page).to have_button('Groups')
+ expect(page).to have_link('Activity')
+ expect(page).to have_link('Milestones')
+ expect(page).to have_link('Snippets')
+ end
+ end
+
+ it 'hides some links when an external authorization service is enabled' do
+ enable_external_authorization_service_check
+ visit dashboard_groups_path
+
+ within('.navbar') do
+ expect(page).to have_button('Projects')
+ expect(page).to have_button('Groups')
+ expect(page).not_to have_link('Activity')
+ expect(page).not_to have_link('Milestones')
+ expect(page).to have_link('Snippets')
+ end
+ end
+ end
+end
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index fbc2e5cc3d3..50b71368e13 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -8,7 +8,7 @@ describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do
before do
issue.assignees = [user]
- merge_request.update(assignee: user)
+ merge_request.update(assignees: [user])
sign_in(user)
end
@@ -33,7 +33,7 @@ describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do
expect_counters('merge_requests', '1')
- merge_request.update(assignee: nil)
+ merge_request.update(assignees: [])
user.invalidate_cache_counts
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 9ffa75aee47..0c6713f623c 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -44,16 +44,18 @@ describe 'Dashboard Merge Requests' do
end
context 'merge requests exist' do
+ let(:label) { create(:label) }
+
let!(:assigned_merge_request) do
create(:merge_request,
- assignee: current_user,
+ assignees: [current_user],
source_project: project,
author: create(:user))
end
let!(:assigned_merge_request_from_fork) do
create(:merge_request,
- source_branch: 'markdown', assignee: current_user,
+ source_branch: 'markdown', assignees: [current_user],
target_project: public_project, source_project: forked_project,
author: create(:user))
end
@@ -72,6 +74,14 @@ describe 'Dashboard Merge Requests' do
target_project: public_project, source_project: forked_project)
end
+ let!(:labeled_merge_request) do
+ create(:labeled_merge_request,
+ source_branch: 'labeled',
+ labels: [label],
+ author: current_user,
+ source_project: project)
+ end
+
let!(:other_merge_request) do
create(:merge_request,
source_branch: 'fix',
@@ -90,6 +100,7 @@ describe 'Dashboard Merge Requests' do
expect(page).not_to have_content(authored_merge_request.title)
expect(page).not_to have_content(authored_merge_request_from_fork.title)
expect(page).not_to have_content(other_merge_request.title)
+ expect(page).not_to have_content(labeled_merge_request.title)
end
it 'shows authored merge requests', :js do
@@ -98,7 +109,21 @@ describe 'Dashboard Merge Requests' do
expect(page).to have_content(authored_merge_request.title)
expect(page).to have_content(authored_merge_request_from_fork.title)
+ expect(page).to have_content(labeled_merge_request.title)
+
+ expect(page).not_to have_content(assigned_merge_request.title)
+ expect(page).not_to have_content(assigned_merge_request_from_fork.title)
+ expect(page).not_to have_content(other_merge_request.title)
+ end
+
+ it 'shows labeled merge requests', :js do
+ reset_filters
+ input_filtered_search("label:#{label.name}")
+ expect(page).to have_content(labeled_merge_request.title)
+
+ expect(page).not_to have_content(authored_merge_request.title)
+ expect(page).not_to have_content(authored_merge_request_from_fork.title)
expect(page).not_to have_content(assigned_merge_request.title)
expect(page).not_to have_content(assigned_merge_request_from_fork.title)
expect(page).not_to have_content(other_merge_request.title)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 9d1c1e3acc7..d1ed64cce7f 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -112,6 +112,14 @@ describe 'Dashboard Projects' do
expect(first('.project-row')).to have_content(project_with_most_stars.title)
end
+
+ it 'shows tabs to filter by all projects or personal' do
+ visit dashboard_projects_path
+ segmented_button = page.find('.filtered-search-nav .button-filter-group')
+
+ expect(segmented_button).to have_content 'All'
+ expect(segmented_button).to have_content 'Personal'
+ end
end
context 'when on Starred projects tab', :js do
@@ -134,6 +142,12 @@ describe 'Dashboard Projects' do
expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1)
end
+
+ it 'does not show tabs to filter by all projects or personal' do
+ visit(starred_dashboard_projects_path)
+
+ expect(page).not_to have_content '.filtered-search-nav'
+ end
end
describe 'with a pipeline', :clean_gitlab_redis_shared_state do
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index fd8677feab5..d58e3b2841e 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -17,6 +17,26 @@ describe 'Dashboard Todos' do
end
end
+ context 'when the todo references a merge request' do
+ let(:referenced_mr) { create(:merge_request, source_project: project) }
+ let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}") }
+ let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note) }
+
+ before do
+ sign_in(user)
+ visit dashboard_todos_path
+ end
+
+ it 'renders the mr link with the extra attributes' do
+ link = page.find_link(referenced_mr.to_reference)
+
+ expect(link).not_to be_nil
+ expect(link['data-iid']).to eq(referenced_mr.iid.to_s)
+ expect(link['data-project-path']).to eq(referenced_mr.project.full_path)
+ expect(link['data-mr-title']).to eq(referenced_mr.title)
+ end
+ end
+
context 'User has a todo', :js do
before do
create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index cc86114e436..4410c8f887f 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe 'Dashboard > User filters projects' do
let(:user) { create(:user) }
- let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) }
+ let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace, created_at: 2.seconds.ago, updated_at: 2.seconds.ago) }
let(:user2) { create(:user) }
- let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) }
+ let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace, created_at: 1.second.ago, updated_at: 1.second.ago) }
before do
project.add_maintainer(user)
@@ -14,6 +14,7 @@ describe 'Dashboard > User filters projects' do
describe 'filtering personal projects' do
before do
+ stub_feature_flags(project_list_filter_bar: false)
project2.add_developer(user)
visit dashboard_projects_path
@@ -30,6 +31,7 @@ describe 'Dashboard > User filters projects' do
describe 'filtering starred projects', :js do
before do
+ stub_feature_flags(project_list_filter_bar: false)
user.toggle_star(project)
visit dashboard_projects_path
@@ -42,4 +44,187 @@ describe 'Dashboard > User filters projects' do
expect(page).not_to have_content('You don\'t have starred projects yet')
end
end
+
+ describe 'without search bar', :js do
+ before do
+ stub_feature_flags(project_list_filter_bar: false)
+
+ project2.add_developer(user)
+ visit dashboard_projects_path
+ end
+
+ it 'autocompletes searches upon typing', :js do
+ expect(page).to have_content 'Victorialand'
+ expect(page).to have_content 'Treasure'
+
+ fill_in 'project-filter-form-field', with: 'Lord beerus\n'
+
+ expect(page).not_to have_content 'Victorialand'
+ expect(page).not_to have_content 'Treasure'
+ end
+ end
+
+ describe 'with search bar', :js do
+ before do
+ stub_feature_flags(project_list_filter_bar: true)
+
+ project2.add_developer(user)
+ visit dashboard_projects_path
+ end
+
+ # TODO: move these helpers somewhere more useful
+ def click_sort_direction
+ page.find('.filtered-search-block #filtered-search-sorting-dropdown .reverse-sort-btn').click
+ end
+
+ def select_dropdown_option(selector, label)
+ dropdown = page.find(selector)
+ dropdown.click
+
+ dropdown.find('.dropdown-menu a', text: label, match: :first).click
+ end
+
+ def expect_to_see_projects(sorted_projects)
+ list = page.all('.projects-list .project-name').map(&:text)
+ expect(list).to match(sorted_projects)
+ end
+
+ describe 'Search' do
+ it 'executes when the search button is clicked' do
+ expect(page).to have_content 'Victorialand'
+ expect(page).to have_content 'Treasure'
+
+ fill_in 'project-filter-form-field', with: 'Lord vegeta\n'
+ find('.filtered-search .btn').click
+
+ expect(page).not_to have_content 'Victorialand'
+ expect(page).not_to have_content 'Treasure'
+ end
+
+ it 'will execute when i press enter' do
+ expect(page).to have_content 'Victorialand'
+ expect(page).to have_content 'Treasure'
+
+ fill_in 'project-filter-form-field', with: 'Lord frieza\n'
+ find('#project-filter-form-field').native.send_keys :enter
+
+ expect(page).not_to have_content 'Victorialand'
+ expect(page).not_to have_content 'Treasure'
+ end
+ end
+
+ describe 'Filter' do
+ before do
+ private_project = create(:project, :private, name: 'Private project', namespace: user.namespace)
+ internal_project = create(:project, :internal, name: 'Internal project', namespace: user.namespace)
+
+ private_project.add_maintainer(user)
+ internal_project.add_maintainer(user)
+ end
+
+ it 'filters private projects only' do
+ select_dropdown_option '#filtered-search-visibility-dropdown', 'Private'
+
+ expect(current_url).to match(/visibility_level=0/)
+
+ list = page.all('.projects-list .project-name').map(&:text)
+
+ expect(list).to contain_exactly("Private project", "Treasure", "Victorialand")
+ end
+
+ it 'filters internal projects only' do
+ select_dropdown_option '#filtered-search-visibility-dropdown', 'Internal'
+
+ expect(current_url).to match(/visibility_level=10/)
+
+ list = page.all('.projects-list .project-name').map(&:text)
+
+ expect(list).to contain_exactly('Internal project')
+ end
+
+ it 'filters any project' do
+ select_dropdown_option '#filtered-search-visibility-dropdown', 'Any'
+ list = page.all('.projects-list .project-name').map(&:text)
+
+ expect(list).to contain_exactly("Internal project", "Private project", "Treasure", "Victorialand")
+ end
+ end
+
+ describe 'Sorting' do
+ let(:desc_sorted_project_names) { %w[Treasure Victorialand] }
+
+ before do
+ user.toggle_star(project)
+ user.toggle_star(project2)
+ user2.toggle_star(project2)
+ end
+
+ it 'has all sorting options', :js do
+ sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown')
+
+ expect(sorting_dropdown).to have_css '.reverse-sort-btn'
+
+ sorting_dropdown.click
+
+ ['Last updated', 'Created date', 'Name', 'Stars'].each do |label|
+ expect(sorting_dropdown).to have_content(label)
+ end
+ end
+
+ it 'defaults to "Last updated"', :js do
+ page.find('.filtered-search-block #filtered-search-sorting-dropdown').click
+ active_sorting_option = page.first('.filtered-search-block #filtered-search-sorting-dropdown .is-active')
+
+ expect(active_sorting_option).to have_content 'Last updated'
+ end
+
+ context 'Sorting by name' do
+ it 'sorts the project list' do
+ select_dropdown_option '#filtered-search-sorting-dropdown', 'Name'
+
+ expect_to_see_projects(desc_sorted_project_names)
+
+ click_sort_direction
+
+ expect_to_see_projects(desc_sorted_project_names.reverse)
+ end
+ end
+
+ context 'Sorting by Last updated' do
+ it 'sorts the project list' do
+ select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated'
+
+ expect_to_see_projects(desc_sorted_project_names)
+
+ click_sort_direction
+
+ expect_to_see_projects(desc_sorted_project_names.reverse)
+ end
+ end
+
+ context 'Sorting by Created date' do
+ it 'sorts the project list' do
+ select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date'
+
+ expect_to_see_projects(desc_sorted_project_names)
+
+ click_sort_direction
+
+ expect_to_see_projects(desc_sorted_project_names.reverse)
+ end
+ end
+
+ context 'Sorting by Stars' do
+ it 'sorts the project list' do
+ select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars'
+
+ expect_to_see_projects(desc_sorted_project_names)
+
+ click_sort_direction
+
+ expect_to_see_projects(desc_sorted_project_names.reverse)
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 8d801161148..b2b3382666a 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -34,7 +34,7 @@ describe 'Expand and collapse diffs', :js do
define_method(file.split('.').first) { file_container(file) }
end
- it 'should show the diff content with a highlighted line when linking to line' do
+ it 'shows the diff content with a highlighted line when linking to line' do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
@@ -48,7 +48,7 @@ describe 'Expand and collapse diffs', :js do
expect(large_diff).to have_selector('.hll')
end
- it 'should show the diff content when linking to file' do
+ it 'shows the diff content when linking to file' do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 8ed4051856e..56f6b1f7eaf 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -68,17 +68,17 @@ describe 'Explore Groups page', :js do
end
describe 'landing component' do
- it 'should show a landing component' do
+ it 'shows a landing component' do
expect(page).to have_content('Below you will find all the groups that are public.')
end
- it 'should be dismissable' do
+ it 'is dismissable' do
find('.dismiss-button').click
expect(page).not_to have_content('Below you will find all the groups that are public.')
end
- it 'should persistently not show once dismissed' do
+ it 'does not show persistently once dismissed' do
find('.dismiss-button').click
visit explore_groups_path
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index d7692181453..f2ab5373d3d 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -25,4 +25,18 @@ describe 'Global search' do
expect(page).to have_selector('.gl-pagination .next')
end
end
+
+ it 'closes the dropdown on blur', :js do
+ visit dashboard_projects_path
+
+ fill_in 'search', with: "a"
+ dropdown = find('.js-dashboard-search-options')
+
+ expect(dropdown[:class]).to include 'show'
+
+ find('#search').send_keys(:backspace)
+ find('body').click
+
+ expect(dropdown[:class]).not_to include 'show'
+ end
end
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index 57e3ddfb39c..fc5777e8c7c 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Group variables', :js do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) }
+ let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', masked: true, group: group) }
let(:page_path) { group_settings_ci_cd_path(group) }
before do
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
index 2410cd92e3f..84a8691a7f2 100644
--- a/spec/features/groups/clusters/user_spec.rb
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -14,6 +14,7 @@ describe 'User Cluster', :js do
allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
@@ -69,7 +70,7 @@ describe 'User Cluster', :js do
end
it 'user sees a validation error' do
- expect(page).to have_css('#error_explanation')
+ expect(page).to have_css('.gl-field-error')
end
end
end
diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb
new file mode 100644
index 00000000000..c05c3f4f3d6
--- /dev/null
+++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'The group page' do
+ include ExternalAuthorizationServiceHelpers
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ before do
+ sign_in user
+ group.add_owner(user)
+ end
+
+ def expect_all_sidebar_links
+ within('.nav-sidebar') do
+ expect(page).to have_link('Overview')
+ expect(page).to have_link('Details')
+ expect(page).to have_link('Activity')
+ expect(page).to have_link('Issues')
+ expect(page).to have_link('Merge Requests')
+ expect(page).to have_link('Members')
+ end
+ end
+
+ describe 'The sidebar' do
+ it 'has all the expected links' do
+ visit group_path(group)
+
+ expect_all_sidebar_links
+ end
+
+ it 'shows all project features when policy control is enabled' do
+ stub_application_setting(external_authorization_service_enabled: true)
+
+ visit group_path(group)
+
+ expect_all_sidebar_links
+ end
+
+ it 'hides some links when an external authorization service configured with an url' do
+ enable_external_authorization_service_check
+ visit group_path(group)
+
+ within('.nav-sidebar') do
+ expect(page).to have_link('Overview')
+ expect(page).to have_link('Details')
+ expect(page).not_to have_link('Activity')
+ expect(page).not_to have_link('Contribution Analytics')
+
+ expect(page).not_to have_link('Issues')
+ expect(page).not_to have_link('Merge Requests')
+ expect(page).to have_link('Members')
+ end
+ end
+ end
+end
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 378e4d5febc..5cef5f0521f 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -77,6 +77,14 @@ describe 'Edit group settings' do
end
end
+ describe 'project creation level menu' do
+ it 'shows the selection menu' do
+ visit edit_group_path(group)
+
+ expect(page).to have_content('Allowed to create projects')
+ end
+ end
+
describe 'edit group avatar' do
before do
visit edit_group_path(group)
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
index 7a91c64d7db..439803f9255 100644
--- a/spec/features/groups/members/leave_group_spec.rb
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -21,6 +21,20 @@ describe 'Groups > Members > Leave group' do
expect(group.users).not_to include(user)
end
+ it 'guest leaves the group by url param', :js do
+ group.add_guest(user)
+ group.add_owner(other_user)
+
+ visit group_path(group, leave: 1)
+
+ page.accept_confirm
+
+ expect(find('.flash-notice')).to have_content "You left the \"#{group.full_name}\" group"
+ expect(page).to have_content left_group_message(group)
+ expect(current_path).to eq(dashboard_groups_path)
+ expect(group.users).not_to include(user)
+ end
+
it 'guest leaves the group as last member' do
group.add_guest(user)
@@ -32,7 +46,7 @@ describe 'Groups > Members > Leave group' do
expect(group.users).not_to include(user)
end
- it 'owner leaves the group if they is not the last owner' do
+ it 'owner leaves the group if they are not the last owner' do
group.add_owner(user)
group.add_owner(other_user)
@@ -44,7 +58,7 @@ describe 'Groups > Members > Leave group' do
expect(group.users).not_to include(user)
end
- it 'owner can not leave the group if they is a last owner' do
+ it 'owner can not leave the group if they are the last owner' do
group.add_owner(user)
visit group_path(group)
@@ -56,6 +70,14 @@ describe 'Groups > Members > Leave group' do
expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove')
end
+ it 'owner can not leave the group by url param if they are the last owner', :js do
+ group.add_owner(user)
+
+ visit group_path(group, leave: 1)
+
+ expect(find('.flash-alert')).to have_content 'You do not have permission to leave this group'
+ end
+
def left_group_message(group)
"You left the \"#{group.name}\""
end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 54a8016c157..59230d6891a 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group merge requests page' do
@@ -38,7 +40,7 @@ describe 'Group merge requests page' do
context 'when merge request assignee to user' do
before do
- issuable.update!(assignee: user)
+ issuable.update!(assignees: [user])
visit path
end
diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb
index d422fd18346..5b1a9512c55 100644
--- a/spec/features/groups/settings/ci_cd_spec.rb
+++ b/spec/features/groups/settings/ci_cd_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
describe 'Group CI/CD settings' do
include WaitForRequests
- let(:user) {create(:user)}
- let(:group) {create(:group)}
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
before do
group.add_owner(user)
@@ -36,4 +36,45 @@ describe 'Group CI/CD settings' do
end
end
end
+
+ describe 'Auto DevOps form' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ context 'as owner first visiting group settings' do
+ it 'sees instance enabled badge' do
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).to have_content('instance enabled')
+ end
+ end
+ end
+
+ context 'when Auto DevOps group has been enabled' do
+ it 'sees group enabled badge' do
+ group.update!(auto_devops_enabled: true)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).to have_content('group enabled')
+ end
+ end
+ end
+
+ context 'when Auto DevOps group has been disabled' do
+ it 'does not see a badge' do
+ group.update!(auto_devops_enabled: false)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).not_to have_content('instance enabled')
+ expect(page).not_to have_content('group enabled')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index c2f32c76422..8e7f78cab81 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -237,7 +237,7 @@ describe 'Group' do
let!(:project) { create(:project, namespace: group) }
let!(:path) { group_path(group) }
- it 'it renders projects and groups on the page' do
+ it 'renders projects and groups on the page' do
visit path
wait_for_requests
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index e24b1f4349d..bcd2b90d3bb 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -82,15 +82,15 @@ describe 'Help Pages' do
visit help_path
end
- it 'should display custom help page text' do
+ it 'displays custom help page text' do
expect(page).to have_text "My Custom Text"
end
- it 'should hide marketing content when enabled' do
+ it 'hides marketing content when enabled' do
expect(page).not_to have_link "Get a support subscription"
end
- it 'should use a custom support url' do
+ it 'uses a custom support url' do
expect(page).to have_link "See our website for getting help", href: "http://example.com/help"
end
end
diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb
new file mode 100644
index 00000000000..185349219a7
--- /dev/null
+++ b/spec/features/ide/user_opens_merge_request_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'IDE merge request', :js do
+ let(:merge_request) { create(:merge_request, :with_diffs, :simple, source_project: project) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.owner }
+
+ before do
+ sign_in(user)
+
+ visit(merge_request_path(merge_request))
+ end
+
+ it 'user opens merge request' do
+ click_link 'Open in Web IDE'
+
+ wait_for_requests
+
+ expect(page).to have_selector('.monaco-diff-editor')
+ end
+end
diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb
index d8be554d734..713cd944f8c 100644
--- a/spec/features/instance_statistics/conversational_development_index_spec.rb
+++ b/spec/features/instance_statistics/conversational_development_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Conversational Development Index' do
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 7b6e9cd66b2..225b858742d 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -76,7 +76,7 @@ describe 'issuable list' do
create(:issue, project: project, author: user)
else
create(:merge_request, source_project: project, source_branch: generate(:branch))
- source_branch = FFaker::Name.name
+ source_branch = FFaker::Lorem.characters(8)
pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any')
create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline)
end
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index 23385ba65fc..870e92b8de8 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -70,7 +70,7 @@ describe "Internal references", :js do
page.within("#merge-requests ul") do
expect(page).to have_content(private_project_merge_request.title)
- expect(page).to have_css(".merge-request-status")
+ expect(page).to have_css(".ic-issue-open-m")
end
expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}")
diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb
index a0ae6720a9f..a19101366a0 100644
--- a/spec/features/issuables/shortcuts_issuable_spec.rb
+++ b/spec/features/issuables/shortcuts_issuable_spec.rb
@@ -13,7 +13,7 @@ describe 'Blob shortcuts', :js do
end
shared_examples "quotes the selected text" do
- it "quotes the selected text" do
+ it "quotes the selected text", :quarantine do
select_element('.note-text')
find('body').native.send_key('r')
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 0601dd47c03..3a46a4e0167 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -86,26 +86,26 @@ describe 'Sort Issuable List' do
expect(last_merge_request).to include(first_created_issuable.title)
end
end
+ end
- context 'custom sorting' do
- let(:issuable_type) { :merge_request }
+ context 'custom sorting' do
+ let(:issuable_type) { :merge_request }
- it 'supports sorting in asc and desc order' do
- visit_merge_requests_with_state(project, 'open')
+ it 'supports sorting in asc and desc order' do
+ visit_merge_requests_with_state(project, 'open')
- page.within('.issues-other-filters') do
- click_button('Created date')
- click_link('Last updated')
- end
+ page.within('.issues-other-filters') do
+ click_button('Created date')
+ click_link('Last updated')
+ end
- expect(first_merge_request).to include(last_updated_issuable.title)
- expect(last_merge_request).to include(first_updated_issuable.title)
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
- expect(first_merge_request).to include(first_updated_issuable.title)
- expect(last_merge_request).to include(last_updated_issuable.title)
- end
+ expect(first_merge_request).to include(first_updated_issuable.title)
+ expect(last_merge_request).to include(last_updated_issuable.title)
end
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index e0b1e286dee..75313442b65 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -42,7 +42,7 @@ describe 'Dropdown assignee', :js do
expect(page).to have_css(js_dropdown_assignee, visible: false)
end
- it 'should show loading indicator when opened' do
+ it 'shows loading indicator when opened' do
slow_requests do
# We aren't using `input_filtered_search` because we want to see the loading indicator
filtered_search.set('assignee:')
@@ -51,13 +51,13 @@ describe 'Dropdown assignee', :js do
end
end
- it 'should hide loading indicator when loaded' do
+ it 'hides loading indicator when loaded' do
input_filtered_search('assignee:', submit: false, extra_space: false)
expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
end
- it 'should load all the assignees when opened' do
+ it 'loads all the assignees when opened' do
input_filtered_search('assignee:', submit: false, extra_space: false)
expect(dropdown_assignee_size).to eq(4)
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index bedc61b9eed..bc8d9bc8450 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -50,7 +50,7 @@ describe 'Dropdown author', :js do
expect(page).to have_css(js_dropdown_author, visible: false)
end
- it 'should show loading indicator when opened' do
+ it 'shows loading indicator when opened' do
slow_requests do
filtered_search.set('author:')
@@ -58,13 +58,13 @@ describe 'Dropdown author', :js do
end
end
- it 'should hide loading indicator when loaded' do
+ it 'hides loading indicator when loaded' do
send_keys_to_filtered_search('author:')
expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading')
end
- it 'should load all the authors when opened' do
+ it 'loads all the authors when opened' do
send_keys_to_filtered_search('author:')
expect(dropdown_author_size).to eq(4)
diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
index f36d4e8f23f..a5c3ab7e7d0 100644
--- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
@@ -69,7 +69,7 @@ describe 'Dropdown emoji', :js do
expect(page).to have_css(js_dropdown_emoji, visible: false)
end
- it 'should show loading indicator when opened' do
+ it 'shows loading indicator when opened' do
slow_requests do
filtered_search.set('my-reaction:')
@@ -77,13 +77,13 @@ describe 'Dropdown emoji', :js do
end
end
- it 'should hide loading indicator when loaded' do
+ it 'hides loading indicator when loaded' do
send_keys_to_filtered_search('my-reaction:')
expect(page).not_to have_css('#js-dropdown-my-reaction .filter-dropdown-loading')
end
- it 'should load all the emojis when opened' do
+ it 'loads all the emojis when opened' do
send_keys_to_filtered_search('my-reaction:')
expect(dropdown_emoji_size).to eq(4)
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 096756f19cc..1f4e9e79179 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -80,7 +80,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-author', visible: true)
- expect_tokens([{ name: 'author' }])
+ expect_tokens([{ name: 'Author' }])
expect_filtered_search_input_empty
end
@@ -89,7 +89,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-assignee', visible: true)
- expect_tokens([{ name: 'assignee' }])
+ expect_tokens([{ name: 'Assignee' }])
expect_filtered_search_input_empty
end
@@ -98,7 +98,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-milestone', visible: true)
- expect_tokens([{ name: 'milestone' }])
+ expect_tokens([{ name: 'Milestone' }])
expect_filtered_search_input_empty
end
@@ -107,7 +107,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-label', visible: true)
- expect_tokens([{ name: 'label' }])
+ expect_tokens([{ name: 'Label' }])
expect_filtered_search_input_empty
end
@@ -116,7 +116,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
- expect_tokens([{ name: 'my-reaction' }])
+ expect_tokens([{ name: 'My-reaction' }])
expect_filtered_search_input_empty
end
@@ -125,7 +125,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-confidential', visible: true)
- expect_tokens([{ name: 'confidential' }])
+ expect_tokens([{ name: 'Confidential' }])
expect_filtered_search_input_empty
end
end
@@ -137,7 +137,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-author', visible: true)
- expect_tokens([{ name: 'author' }])
+ expect_tokens([{ name: 'Author' }])
expect_filtered_search_input_empty
end
@@ -147,7 +147,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-assignee', visible: true)
- expect_tokens([{ name: 'assignee' }])
+ expect_tokens([{ name: 'Assignee' }])
expect_filtered_search_input_empty
end
@@ -157,7 +157,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-milestone', visible: true)
- expect_tokens([{ name: 'milestone' }])
+ expect_tokens([{ name: 'Milestone' }])
expect_filtered_search_input_empty
end
@@ -167,7 +167,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-label', visible: true)
- expect_tokens([{ name: 'label' }])
+ expect_tokens([{ name: 'Label' }])
expect_filtered_search_input_empty
end
@@ -177,7 +177,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
- expect_tokens([{ name: 'my-reaction' }])
+ expect_tokens([{ name: 'My-reaction' }])
expect_filtered_search_input_empty
end
end
@@ -189,7 +189,7 @@ describe 'Dropdown hint', :js do
filtered_search.send_keys(:backspace)
click_hint('author')
- expect_tokens([{ name: 'author' }])
+ expect_tokens([{ name: 'Author' }])
expect_filtered_search_input_empty
end
@@ -199,7 +199,7 @@ describe 'Dropdown hint', :js do
filtered_search.send_keys(:backspace)
click_hint('assignee')
- expect_tokens([{ name: 'assignee' }])
+ expect_tokens([{ name: 'Assignee' }])
expect_filtered_search_input_empty
end
@@ -209,7 +209,7 @@ describe 'Dropdown hint', :js do
filtered_search.send_keys(:backspace)
click_hint('milestone')
- expect_tokens([{ name: 'milestone' }])
+ expect_tokens([{ name: 'Milestone' }])
expect_filtered_search_input_empty
end
@@ -219,7 +219,7 @@ describe 'Dropdown hint', :js do
filtered_search.send_keys(:backspace)
click_hint('label')
- expect_tokens([{ name: 'label' }])
+ expect_tokens([{ name: 'Label' }])
expect_filtered_search_input_empty
end
@@ -229,7 +229,7 @@ describe 'Dropdown hint', :js do
filtered_search.send_keys(:backspace)
click_hint('my-reaction')
- expect_tokens([{ name: 'my-reaction' }])
+ expect_tokens([{ name: 'My-reaction' }])
expect_filtered_search_input_empty
end
end
@@ -247,7 +247,7 @@ describe 'Dropdown hint', :js do
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-wip', visible: true)
- expect_tokens([{ name: 'wip' }])
+ expect_tokens([{ name: 'WIP' }])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index b330eafe1d1..7a6f76cb382 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -49,7 +49,7 @@ describe 'Dropdown milestone', :js do
expect(page).to have_css(js_dropdown_milestone, visible: false)
end
- it 'should show loading indicator when opened' do
+ it 'shows loading indicator when opened' do
slow_requests do
filtered_search.set('milestone:')
@@ -57,13 +57,13 @@ describe 'Dropdown milestone', :js do
end
end
- it 'should hide loading indicator when loaded' do
+ it 'hides loading indicator when loaded' do
filtered_search.set('milestone:')
expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading')
end
- it 'should load all the milestones when opened' do
+ it 'loads all the milestones when opened' do
filtered_search.set('milestone:')
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6)
@@ -139,7 +139,7 @@ describe 'Dropdown milestone', :js do
expect_filtered_search_input_empty
end
- it 'fills in the milestone name when the milestone is partially filled' do
+ it 'fills in the milestone name when the milestone is partially filled', :quarantine do
filtered_search.send_keys('v')
click_milestone(milestone.title)
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index c4468922883..da23aea1fc9 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -86,7 +86,7 @@ describe 'Search bar', :js do
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end
- it 'resets the dropdown filters' do
+ it 'resets the dropdown filters', :quarantine do
filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index a4c34ce85f0..9fd661d80ae 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -59,13 +59,6 @@ describe 'Visual tokens', :js do
expect(page).to have_css('#js-dropdown-author', visible: false)
end
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-author', visible: false)
- end
-
describe 'selecting different author from dropdown' do
before do
filter_author_dropdown.find('.filter-dropdown-item .dropdown-light-content', text: "@#{user_rock.username}").click
@@ -109,13 +102,6 @@ describe 'Visual tokens', :js do
expect(page).to have_css('#js-dropdown-assignee', visible: false)
end
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-assignee', visible: false)
- end
-
describe 'selecting static option from dropdown' do
before do
find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click
@@ -167,13 +153,6 @@ describe 'Visual tokens', :js do
expect_filtered_search_input_empty
expect(page).to have_css('#js-dropdown-milestone', visible: false)
end
-
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-milestone', visible: false)
- end
end
describe 'editing label token' do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index f2e4c5779df..597af566f9c 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -13,6 +13,8 @@ describe 'New/edit issue', :js do
let!(:issue) { create(:issue, project: project, assignees: [user], milestone: milestone) }
before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+
project.add_maintainer(user)
project.add_maintainer(user2)
sign_in(user)
@@ -30,8 +32,8 @@ describe 'New/edit issue', :js do
# the original method, resulting in infinite recursion when called.
# This is likely a bug with helper modules included into dynamically generated view classes.
# To work around this, we have to hold on to and call to the original implementation manually.
- original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
- allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
+ original_issue_dropdown_options = FormHelper.instance_method(:assignees_dropdown_options)
+ allow_any_instance_of(FormHelper).to receive(:assignees_dropdown_options).and_wrap_original do |original, *args|
options = original_issue_dropdown_options.bind(original.receiver).call(*args)
options[:data][:per_page] = 2
@@ -45,7 +47,7 @@ describe 'New/edit issue', :js do
wait_for_requests
end
- it 'should display selected users even if they are not part of the original API call' do
+ it 'displays selected users even if they are not part of the original API call' do
find('.dropdown-input-field').native.send_keys user2.name
page.within '.dropdown-menu-user' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 986f3823275..8eb413bdd8d 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -278,12 +278,7 @@ describe 'GFM autocomplete', :js do
end
end
- # This context has just one example in each contexts in order to improve spec performance.
- context 'labels', :quarantine do
- let!(:backend) { create(:label, project: project, title: 'backend') }
- let!(:bug) { create(:label, project: project, title: 'bug') }
- let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') }
-
+ context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
create(:label, project: project, title: label_xss_title)
@@ -298,83 +293,6 @@ describe 'GFM autocomplete', :js do
expect(find('.atwho-view-ul').text).to have_content('alert label')
end
end
-
- context 'when no labels are assigned' do
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
- end
- end
-
- context 'when some labels are assigned' do
- before do
- issue.labels << [backend]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only unset labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [bug, feature_proposal], not_shown: [backend])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only set labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend], not_shown: [bug, feature_proposal])
- end
- end
-
- context 'when all labels are assigned' do
- before do
- issue.labels << [backend, bug, feature_proposal]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/label ~".
- type(note, '/label ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
- end
- end
end
shared_examples 'autocomplete suggestions' do
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index 76bc93e9766..791bd003597 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -26,7 +26,7 @@ describe 'Issue Detail', :js do
wait_for_requests
end
- it 'should encode the description to prevent xss issues' do
+ it 'encodes the description to prevent xss issues' do
page.within('.issuable-details .detail-page-description') do
expect(page).to have_selector('img', count: 1)
expect(find('img')['onerror']).to be_nil
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 3050f23c130..321da8f44d7 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -130,7 +130,7 @@ describe 'Issue Sidebar' do
end
end
- context 'creating a project label', :js do
+ context 'creating a project label', :js, :quarantine do
before do
page.within('.block.labels') do
click_link 'Create project'
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index 693ad89069c..b69fba0db00 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'User creates branch and merge request on issue page', :js do
+ let(:membership_level) { :developer }
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') }
@@ -17,7 +18,7 @@ describe 'User creates branch and merge request on issue page', :js do
context 'when signed in' do
before do
- project.add_developer(user)
+ project.add_user(user, membership_level)
sign_in(user)
end
@@ -28,7 +29,7 @@ describe 'User creates branch and merge request on issue page', :js do
end
# In order to improve tests performance, all UI checks are placed in this test.
- it 'shows elements' do
+ it 'shows elements', :quarantine do
button_create_merge_request = find('.js-create-merge-request')
button_toggle_dropdown = find('.create-mr-dropdown-wrap .dropdown-toggle')
@@ -138,7 +139,7 @@ describe 'User creates branch and merge request on issue page', :js do
visit project_issue_path(project, issue)
end
- it 'disables the create branch button' do
+ it 'disables the create branch button', :quarantine do
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
expect(page).to have_content /Related merge requests/
@@ -167,6 +168,39 @@ describe 'User creates branch and merge request on issue page', :js do
expect(page).not_to have_css('.create-mr-dropdown-wrap')
end
end
+
+ context 'when related branch exists' do
+ let!(:project) { create(:project, :repository, :private) }
+ let(:branch_name) { "#{issue.iid}-foo" }
+
+ before do
+ project.repository.create_branch(branch_name, 'master')
+
+ visit project_issue_path(project, issue)
+ end
+
+ context 'when user is developer' do
+ it 'shows related branches' do
+ expect(page).to have_css('#related-branches')
+
+ wait_for_requests
+
+ expect(page).to have_content(branch_name)
+ end
+ end
+
+ context 'when user is guest' do
+ let(:membership_level) { :guest }
+
+ it 'does not show related branches' do
+ expect(page).not_to have_css('#related-branches')
+
+ wait_for_requests
+
+ expect(page).not_to have_content(branch_name)
+ end
+ end
+ end
end
private
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index e60486f6dcb..0f604db870f 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -93,4 +93,22 @@ describe "User creates issue" do
end
end
end
+
+ context "when signed in as user with special characters in their name" do
+ let(:user_special) { create(:user, name: "Jon O'Shea") }
+
+ before do
+ project.add_developer(user_special)
+ sign_in(user_special)
+
+ visit(new_project_issue_path(project))
+ end
+
+ it "will correctly escape user names with an apostrophe when clicking 'Assign to me'", :js do
+ first('.assign-to-me-link').click
+
+ expect(page).to have_content(user_special.name)
+ expect(page.find('input[name="issue[assignee_ids][]"]', visible: false)['data-meta']).to eq(user_special.name)
+ end
+ end
end
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index afa425c2cec..d117620a2b1 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -14,7 +14,7 @@ describe 'User interacts with awards' do
visit(project_issue_path(project, issue))
end
- it 'toggles the thumbsup award emoji' do
+ it 'toggles the thumbsup award emoji', :quarantine do
page.within('.awards') do
thumbsup = page.first('.award-control')
thumbsup.click
@@ -75,7 +75,7 @@ describe 'User interacts with awards' do
end
end
- it 'shows the list of award emoji categories' do
+ it 'shows the list of award emoji categories', :quarantine do
page.within('.awards') do
page.find('.js-add-award').click
end
diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb
index 27cffdc5f8b..86b15b1d980 100644
--- a/spec/features/issues/user_uses_quick_actions_spec.rb
+++ b/spec/features/issues/user_uses_quick_actions_spec.rb
@@ -1,365 +1,68 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issues > User uses quick actions', :js do
include Spec::Support::Helpers::Features::NotesHelpers
- it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
+ context "issuable common quick actions" do
+ let(:new_url_opts) { {} }
+ let(:maintainer) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let!(:label_bug) { create(:label, project: project, title: 'bug') }
+ let!(:label_feature) { create(:label, project: project, title: 'feature') }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:issue, project: project) }
+ let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
+
+ it_behaves_like 'assign quick action', :issue
+ it_behaves_like 'unassign quick action', :issue
+ it_behaves_like 'close quick action', :issue
+ it_behaves_like 'reopen quick action', :issue
+ it_behaves_like 'title quick action', :issue
+ it_behaves_like 'todo quick action', :issue
+ it_behaves_like 'done quick action', :issue
+ it_behaves_like 'subscribe quick action', :issue
+ it_behaves_like 'unsubscribe quick action', :issue
+ it_behaves_like 'lock quick action', :issue
+ it_behaves_like 'unlock quick action', :issue
+ it_behaves_like 'milestone quick action', :issue
+ it_behaves_like 'remove_milestone quick action', :issue
+ it_behaves_like 'label quick action', :issue
+ it_behaves_like 'unlabel quick action', :issue
+ it_behaves_like 'relabel quick action', :issue
+ it_behaves_like 'award quick action', :issue
+ it_behaves_like 'estimate quick action', :issue
+ it_behaves_like 'remove_estimate quick action', :issue
+ it_behaves_like 'spend quick action', :issue
+ it_behaves_like 'remove_time_spent quick action', :issue
+ it_behaves_like 'shrug quick action', :issue
+ it_behaves_like 'tableflip quick action', :issue
+ it_behaves_like 'copy_metadata quick action', :issue
+ it_behaves_like 'issuable time tracker', :issue
end
describe 'issue-only commands' do
let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
before do
project.add_maintainer(user)
sign_in(user)
visit project_issue_path(project, issue)
+ wait_for_all_requests
end
after do
wait_for_requests
end
- describe 'time tracking' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- visit project_issue_path(project, issue)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
- describe 'adding a due date from note' do
- let(:issue) { create(:issue, project: project) }
-
- context 'when the current user can update the due date' do
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/due 2016-08-28")
-
- expect(page).not_to have_content '/due 2016-08-28'
- expect(page).to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
- end
- end
-
- context 'when the current user cannot update the due date' do
- let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- gitlab_sign_out
- sign_in(guest)
- visit project_issue_path(project, issue)
- end
-
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/due 2016-08-28")
-
- expect(page).not_to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to be_nil
- end
- end
- end
-
- describe 'removing a due date from note' do
- let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
-
- context 'when the current user can update the due date' do
- it 'does not create a note, and removes the due date accordingly' do
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
-
- add_note("/remove_due_date")
-
- expect(page).not_to have_content '/remove_due_date'
- expect(page).to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to be_nil
- end
- end
-
- context 'when the current user cannot update the due date' do
- let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- gitlab_sign_out
- sign_in(guest)
- visit project_issue_path(project, issue)
- end
-
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/remove_due_date")
-
- expect(page).not_to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
- end
- end
- end
-
- describe 'toggling the WIP prefix from the title from note' do
- let(:issue) { create(:issue, project: project) }
-
- it 'does not recognize the command nor create a note' do
- add_note("/wip")
-
- expect(page).not_to have_content '/wip'
- end
- end
-
- describe 'mark issue as duplicate' do
- let(:issue) { create(:issue, project: project) }
- let(:original_issue) { create(:issue, project: project) }
-
- context 'when the current user can update issues' do
- it 'does not create a note, and marks the issue as a duplicate' do
- add_note("/duplicate ##{original_issue.to_reference}")
-
- expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
- expect(page).to have_content 'Commands applied'
- expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
-
- expect(issue.reload).to be_closed
- end
- end
-
- context 'when the current user cannot update the issue' do
- let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- gitlab_sign_out
- sign_in(guest)
- visit project_issue_path(project, issue)
- end
-
- it 'does not create a note, and does not mark the issue as a duplicate' do
- add_note("/duplicate ##{original_issue.to_reference}")
-
- expect(page).not_to have_content 'Commands applied'
- expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
-
- expect(issue.reload).to be_open
- end
- end
- end
-
- describe 'make issue confidential' do
- let(:issue) { create(:issue, project: project) }
- let(:original_issue) { create(:issue, project: project) }
-
- context 'when the current user can update issues' do
- it 'does not create a note, and marks the issue as confidential' do
- add_note("/confidential")
-
- expect(page).not_to have_content "/confidential"
- expect(page).to have_content 'Commands applied'
- expect(page).to have_content "made the issue confidential"
-
- expect(issue.reload).to be_confidential
- end
- end
-
- context 'when the current user cannot update the issue' do
- let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- gitlab_sign_out
- sign_in(guest)
- visit project_issue_path(project, issue)
- end
-
- it 'does not create a note, and does not mark the issue as confidential' do
- add_note("/confidential")
-
- expect(page).not_to have_content 'Commands applied'
- expect(page).not_to have_content "made the issue confidential"
-
- expect(issue.reload).not_to be_confidential
- end
- end
- end
-
- describe 'move the issue to another project' do
- let(:issue) { create(:issue, project: project) }
-
- context 'when the project is valid' do
- let(:target_project) { create(:project, :public) }
-
- before do
- target_project.add_maintainer(user)
- gitlab_sign_out
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'moves the issue' do
- add_note("/move #{target_project.full_path}")
-
- expect(page).to have_content 'Commands applied'
- expect(issue.reload).to be_closed
-
- visit project_issue_path(target_project, issue)
-
- expect(page).to have_content 'Issues 1'
- end
- end
-
- context 'when the project is valid but the user not authorized' do
- let(:project_unauthorized) { create(:project, :public) }
-
- before do
- gitlab_sign_out
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'does not move the issue' do
- add_note("/move #{project_unauthorized.full_path}")
-
- wait_for_requests
-
- expect(page).to have_content 'Commands applied'
- expect(issue.reload).to be_open
- end
- end
-
- context 'when the project is invalid' do
- before do
- gitlab_sign_out
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'does not move the issue' do
- add_note("/move not/valid")
-
- expect(page).not_to have_content 'Commands applied'
- expect(issue.reload).to be_open
- end
- end
-
- context 'when the user issues multiple commands' do
- let(:target_project) { create(:project, :public) }
- let(:milestone) { create(:milestone, title: '1.0', project: project) }
- let(:target_milestone) { create(:milestone, title: '1.0', project: target_project) }
- let(:bug) { create(:label, project: project, title: 'bug') }
- let(:wontfix) { create(:label, project: project, title: 'wontfix') }
- let(:bug_target) { create(:label, project: target_project, title: 'bug') }
- let(:wontfix_target) { create(:label, project: target_project, title: 'wontfix') }
-
- before do
- target_project.add_maintainer(user)
- gitlab_sign_out
- sign_in(user)
- visit project_issue_path(project, issue)
- end
-
- it 'applies the commands to both issues and moves the issue' do
- add_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}")
-
- expect(page).to have_content 'Commands applied'
- expect(issue.reload).to be_closed
-
- visit project_issue_path(target_project, issue)
-
- expect(page).to have_content 'bug'
- expect(page).to have_content 'wontfix'
- expect(page).to have_content '1.0'
-
- visit project_issue_path(project, issue)
- expect(page).to have_content 'Closed'
- expect(page).to have_content 'bug'
- expect(page).to have_content 'wontfix'
- expect(page).to have_content '1.0'
- end
-
- it 'moves the issue and applies the commands to both issues' do
- add_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
-
- expect(page).to have_content 'Commands applied'
- expect(issue.reload).to be_closed
-
- visit project_issue_path(target_project, issue)
-
- expect(page).to have_content 'bug'
- expect(page).to have_content 'wontfix'
- expect(page).to have_content '1.0'
-
- visit project_issue_path(project, issue)
- expect(page).to have_content 'Closed'
- expect(page).to have_content 'bug'
- expect(page).to have_content 'wontfix'
- expect(page).to have_content '1.0'
- end
- end
- end
-
- describe 'create a merge request starting from an issue' do
- let(:project) { create(:project, :public, :repository) }
- let(:issue) { create(:issue, project: project) }
-
- def expect_mr_quickaction(success)
- expect(page).to have_content 'Commands applied'
-
- if success
- expect(page).to have_content 'created merge request'
- else
- expect(page).not_to have_content 'created merge request'
- end
- end
-
- it "doesn't create a merge request when the branch name is invalid" do
- add_note("/create_merge_request invalid branch name")
-
- wait_for_requests
-
- expect_mr_quickaction(false)
- end
-
- it "doesn't create a merge request when a branch with that name already exists" do
- add_note("/create_merge_request feature")
-
- wait_for_requests
-
- expect_mr_quickaction(false)
- end
-
- it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
- add_note("/create_merge_request")
-
- wait_for_requests
-
- expect_mr_quickaction(true)
-
- created_mr = project.merge_requests.last
- expect(created_mr.source_branch).to eq(issue.to_branch_name)
-
- visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
- end
-
- it 'creates a merge request using the given branch name' do
- branch_name = '1-feature'
- add_note("/create_merge_request #{branch_name}")
-
- expect_mr_quickaction(true)
-
- created_mr = project.merge_requests.last
- expect(created_mr.source_branch).to eq(branch_name)
-
- visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
- end
- end
+ it_behaves_like 'confidential quick action'
+ it_behaves_like 'remove_due_date quick action'
+ it_behaves_like 'duplicate quick action'
+ it_behaves_like 'create_merge_request quick action'
+ it_behaves_like 'due quick action'
+ it_behaves_like 'move quick action'
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 406e80e91aa..5ee9425c491 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issues' do
@@ -91,7 +93,7 @@ describe 'Issues' do
click_button 'Save changes'
page.within('.assignee') do
- expect(page).to have_content 'No assignee - assign yourself'
+ expect(page).to have_content 'None - assign yourself'
end
expect(issue.reload.assignees).to be_empty
@@ -233,8 +235,8 @@ describe 'Issues' do
created_at: Time.now - (index * 60))
end
end
- let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
- let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
+ let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') }
+ let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') }
it 'sorts by newest' do
visit project_issues_path(project, sort: sort_value_created_date)
@@ -465,7 +467,7 @@ describe 'Issues' do
click_link 'Edit'
click_link 'Unassigned'
first('.title').click
- expect(page).to have_content 'No assignee'
+ expect(page).to have_content 'None'
end
# wait_for_requests does not work with vue-resource at the moment
@@ -479,7 +481,7 @@ describe 'Issues' do
visit project_issue_path(project, issue2)
page.within('.assignee') do
- expect(page).to have_content "No assignee"
+ expect(page).to have_content "None"
end
page.within '.assignee' do
@@ -497,12 +499,21 @@ describe 'Issues' do
it 'allows user to unselect themselves', :js do
issue2 = create(:issue, project: project, author: user)
+
visit project_issue_path(project, issue2)
+ def close_dropdown_menu_if_visible
+ find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
+ toggle.click if toggle.visible?
+ end
+ end
+
page.within '.assignee' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .author' do
expect(page).to have_content user.name
end
@@ -510,8 +521,10 @@ describe 'Issues' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .assign-yourself' do
- expect(page).to have_content "No assignee"
+ expect(page).to have_content "None"
end
end
end
@@ -764,10 +777,10 @@ describe 'Issues' do
wait_for_requests
- expect(page).to have_no_content 'No due date'
+ expect(page).to have_no_content 'None'
click_link 'remove due date'
- expect(page).to have_content 'No due date'
+ expect(page).to have_content 'None'
end
end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 7c31e67a7fa..489651fea15 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -21,7 +21,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do
end
shared_examples 'assigning labels from sidebar' do
- it 'can assign all ancestors labels' do
+ it 'can assign all ancestors labels', :quarantine do
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
page.within('.block.labels') do
find('.edit-link').click
@@ -145,7 +145,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do
visit new_project_issue_path(project_1)
end
- it 'should be able to assign ancestor group labels' do
+ it 'is able to assign ancestor group labels' do
fill_in 'issue_title', with: 'new created issue'
fill_in 'issue_description', with: 'new issue description'
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 60ddb02da2c..c30ac9c4ae2 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -55,15 +55,10 @@ describe 'Copy as GFM', :js do
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
* Manage Git repositories with fine grained access controls that keep your code secure
-
* Perform code reviews and enhance collaboration with merge requests
-
* Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
-
* Each project can also have an issue tracker, issue board, and a wiki
-
* Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
-
* Completely free and open source (MIT Expat license)
GFM
)
@@ -116,13 +111,11 @@ describe 'Copy as GFM', :js do
<<~GFM,
* [ ] Unchecked task
-
* [x] Checked task
GFM
<<~GFM
1. [ ] Unchecked ordered task
-
1. [x] Checked ordered task
GFM
)
@@ -551,7 +544,6 @@ describe 'Copy as GFM', :js do
<<~GFM,
* List item
-
* List item 2
GFM
@@ -565,7 +557,6 @@ describe 'Copy as GFM', :js do
# nested lists
<<~GFM,
* Nested
-
* Lists
GFM
@@ -578,7 +569,6 @@ describe 'Copy as GFM', :js do
<<~GFM,
1. Ordered list item
-
1. Ordered list item 2
GFM
@@ -592,7 +582,6 @@ describe 'Copy as GFM', :js do
# nested ordered list
<<~GFM,
1. Nested
-
1. Ordered lists
GFM
diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
index 6997ca48427..8fda3c7193e 100644
--- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
@@ -20,8 +20,7 @@ describe "GitLab Flavored Markdown" do
let(:commit) { project.commit }
before do
- allow_any_instance_of(Commit).to receive(:title)
- .and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
+ create_commit("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details", project, user, 'master')
end
it "renders title in commits#index" do
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 7839b97122c..b35f985126c 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -18,13 +18,15 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
end
before do
+ stub_feature_flags(web_ide_default: false)
+
target_project.add_maintainer(user)
sign_in(user)
visit project_merge_request_path(target_project, merge_request)
click_link 'Changes'
wait_for_requests
- first('.js-file-title').click_link 'Edit'
+ first('.js-file-title').find('.js-edit-blob').click
wait_for_requests
end
diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
index 0ccab5b2fac..b8c4a78e24f 100644
--- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
+++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
@@ -76,7 +76,7 @@ describe 'create a merge request, allowing commits from members who can merge to
sign_in(member)
end
- it 'it hides the option from members' do
+ it 'hides the option from members' do
visit edit_project_merge_request_path(target_project, merge_request)
expect(page).not_to have_content('Allows commits from members who can merge to the target branch')
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index c837a6752f9..5db54f42264 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -113,7 +113,7 @@ describe 'Merge request > User creates image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', :quarantine do
indicator = find('.js-image-badge', match: :first)
badge = find('.user-avatar-link .badge', match: :first)
@@ -191,29 +191,119 @@ describe 'Merge request > User creates image diff notes', :js do
end
end
- describe 'image view modes' do
- before do
- visit project_commit_path(project, '2f63565e7aac07bcdadb654e253078b727143ec4')
+ shared_examples 'swipe view' do
+ it 'moves the swipe handle' do
+ # Simulate dragging swipe view slider
+ expect { drag_and_drop_by(find('.swipe-bar'), 20, 0) }
+ .to change { find('.swipe-bar')['style'] }
+ .from(a_string_matching('left: 1px'))
end
- it 'resizes image in onion skin view mode' do
- find('.view-modes-menu .onion-skin').click
+ it 'shows both images at the same position' do
+ drag_and_drop_by(find('.swipe-bar'), 40, 0)
- expect(find('.onion-skin-frame')['style']).to match('width: 228px; height: 240px;')
+ expect(left_position('.frame.added img'))
+ .to eq(left_position('.frame.deleted img'))
end
+ end
- it 'resets onion skin view mode opacity when toggling between view modes' do
- find('.view-modes-menu .onion-skin').click
-
+ shared_examples 'onion skin' do
+ it 'resets opacity when toggling between view modes' do
# Simulate dragging onion-skin slider
drag_and_drop_by(find('.dragger'), -30, 0)
expect(find('.onion-skin-frame .frame.added', visible: false)['style']).not_to match('opacity: 1;')
+ switch_to_swipe_view
+ switch_to_onion_skin
+
+ expect(find('.onion-skin-frame .frame.added', visible: false)['style']).to match('opacity: 1;')
+ end
+ end
+
+ describe 'changes tab image diff' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, target_branch: 'master', source_branch: 'deleted-image-test', author: user) }
+
+ before do
+ visit diffs_project_merge_request_path(project, merge_request)
+ click_link "Changes"
+ end
+
+ def set_image_diff_sources
+ # set path of added and deleted images to something the spec can view
+ page.execute_script("document.querySelector('.frame.added img').src = '/apple-touch-icon.png';")
+ page.execute_script("document.querySelector('.frame.deleted img').src = '/favicon.png';")
+
+ wait_for_requests
+
+ expect(find('.frame.added img', visible: false)['src']).to match('/apple-touch-icon.png')
+ expect(find('.frame.deleted img', visible: false)['src']).to match('/favicon.png')
+ end
+
+ def switch_to_swipe_view
+ # it isn't given the .swipe class in the merge request diff
+ find('.view-modes-menu li:nth-child(2)').click
+ expect(find('.view-modes-menu li.active')).to have_content('Swipe')
+
+ set_image_diff_sources
+ end
+
+ def switch_to_onion_skin
+ # it isn't given the .onion-skin class in the merge request diff
+ find('.view-modes-menu li:nth-child(3)').click
+ expect(find('.view-modes-menu li.active')).to have_content('Onion skin')
+
+ set_image_diff_sources
+ end
+
+ describe 'onion skin' do
+ before do
+ switch_to_onion_skin
+ end
+
+ it_behaves_like 'onion skin'
+ end
+
+ describe 'swipe view' do
+ before do
+ switch_to_swipe_view
+ end
+
+ it_behaves_like 'swipe view'
+ end
+ end
+
+ describe 'image view modes' do
+ before do
+ visit project_commit_path(project, '2f63565e7aac07bcdadb654e253078b727143ec4')
+ end
+
+ def switch_to_swipe_view
find('.view-modes-menu .swipe').click
+ end
+
+ def switch_to_onion_skin
find('.view-modes-menu .onion-skin').click
+ end
- expect(find('.onion-skin-frame .frame.added', visible: false)['style']).to match('opacity: 1;')
+ describe 'onion skin' do
+ before do
+ switch_to_onion_skin
+ end
+
+ it 'resizes image' do
+ expect(find('.onion-skin-frame')['style']).to match('width: 228px; height: 240px;')
+ end
+
+ it_behaves_like 'onion skin'
+ end
+
+ describe 'swipe view' do
+ before do
+ switch_to_swipe_view
+ end
+
+ it_behaves_like 'swipe view'
end
end
@@ -232,4 +322,8 @@ describe 'Merge request > User creates image diff notes', :js do
click_button 'Comment'
wait_for_requests
end
+
+ def left_position(element)
+ page.evaluate_script("document.querySelectorAll('#{element}')[0].getBoundingClientRect().left;")
+ end
end
diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb
index ea2bb1503bb..d05ef2a8f12 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -8,8 +8,6 @@ describe "User creates a merge request", :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(approval_rules: false)
-
project.add_maintainer(user)
sign_in(user)
end
@@ -68,15 +66,15 @@ describe "User creates a merge request", :js do
fill_in("Title", with: title)
end
- click_button("Assignee")
-
expect(find(".js-assignee-search")["data-project-id"]).to eq(project.id.to_s)
+ find('.js-assignee-search').click
page.within(".dropdown-menu-user") do
expect(page).to have_content("Unassigned")
.and have_content(user.name)
.and have_content(project.users.first.name)
end
+ find('.js-assignee-search').click
click_button("Submit merge request")
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index c169a68cd1c..c9dedab048a 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -1,11 +1,18 @@
require 'rails_helper'
describe 'Merge request > User creates MR' do
- it_behaves_like 'a creatable merge request'
+ include ProjectForksHelper
- context 'from a forked project' do
- include ProjectForksHelper
+ before do
+ stub_licensed_features(multiple_merge_request_assignees: false)
+ end
+ context 'non-fork merge request' do
+ include_context 'merge request create context'
+ it_behaves_like 'a creatable merge request'
+ end
+
+ context 'from a forked project' do
let(:canonical_project) { create(:project, :public, :repository) }
let(:source_project) do
@@ -15,6 +22,7 @@ describe 'Merge request > User creates MR' do
end
context 'to canonical project' do
+ include_context 'merge request create context'
it_behaves_like 'a creatable merge request'
end
@@ -25,6 +33,7 @@ describe 'Merge request > User creates MR' do
namespace: user.namespace)
end
+ include_context 'merge request create context'
it_behaves_like 'a creatable merge request'
end
end
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 3152707136c..25979513ead 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -1,13 +1,21 @@
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User edits MR' do
include ProjectForksHelper
- it_behaves_like 'an editable merge request'
+ before do
+ stub_licensed_features(multiple_merge_request_assignees: false)
+ end
+
+ context 'non-fork merge request' do
+ include_context 'merge request edit context'
+ it_behaves_like 'an editable merge request'
+ end
context 'for a forked project' do
- it_behaves_like 'an editable merge request' do
- let(:source_project) { fork_project(target_project, nil, repository: true) }
- end
+ let(:source_project) { fork_project(target_project, nil, repository: true) }
+
+ include_context 'merge request edit context'
+ it_behaves_like 'an editable merge request'
end
end
diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb
index 6539e6e9208..da15a4bda4b 100644
--- a/spec/features/merge_request/user_merges_merge_request_spec.rb
+++ b/spec/features/merge_request/user_merges_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User merges a merge request", :js do
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 6e54aa6006b..586b3ba170d 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -52,7 +52,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# so we have to wait for asynchronous call to reload it
# and have_content expectation handles that.
#
- expect(page).to have_content "Pipeline ##{pipeline.id} running"
+ expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) running"
end
it_behaves_like 'Merge when pipeline succeeds activator'
@@ -74,11 +74,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
source_project: project,
title: 'Bug NS-04',
author: user,
- merge_user: user,
- merge_params: { force_remove_source_branch: '1' })
+ merge_user: user)
end
before do
+ merge_request.merge_params['force_remove_source_branch'] = '0'
+ merge_request.save!
click_link "Cancel automatic merge"
end
@@ -102,11 +103,11 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
context 'when merge when pipeline succeeds is enabled' do
let(:merge_request) do
- create(:merge_request_with_diffs, :simple, source_project: project,
- author: user,
- merge_user: user,
- title: 'MepMep',
- merge_when_pipeline_succeeds: true)
+ create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds,
+ source_project: project,
+ author: user,
+ merge_user: user,
+ title: 'MepMep')
end
let!(:build) do
create(:ci_build, pipeline: pipeline)
@@ -158,8 +159,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
@@ -177,8 +178,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 51b78d3e7d1..19edce1b562 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -178,7 +178,7 @@ describe 'Merge request > User posts diff notes', :js do
end
end
- describe 'with muliple note forms' do
+ describe 'with multiple note forms' do
before do
visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index 1bbcf455ac7..e5770905dbd 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -67,20 +67,8 @@ describe 'Merge request > User posts notes', :js do
end
end
- describe 'when reply_to_individual_notes feature flag is not set' do
+ describe 'reply button' do
before do
- stub_feature_flags(reply_to_individual_notes: false)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not show a reply button' do
- expect(page).to have_no_selector('.js-reply-button')
- end
- end
-
- describe 'when reply_to_individual_notes feature flag is set' do
- before do
- stub_feature_flags(reply_to_individual_notes: true)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 16c058ab6bd..8fd44b87e5a 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -164,6 +164,21 @@ describe 'Merge request > User resolves conflicts', :js do
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
end
end
+
+ context "with malicious branch name" do
+ let(:bad_branch_name) { "malicious-branch-{{toString.constructor('alert(/xss/)')()}}" }
+ let(:branch) { project.repository.create_branch(bad_branch_name, 'conflict-resolvable') }
+ let(:merge_request) { create_merge_request(branch.name) }
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+ click_link('conflicts', href: %r{/conflicts\Z})
+ end
+
+ it "renders bad name without xss issues" do
+ expect(find('.resolve-conflicts-form .resolve-info')).to have_content(bad_branch_name)
+ end
+ end
end
UNRESOLVABLE_CONFLICTS = {
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 8c2599615cb..2f7d359575e 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -5,9 +5,7 @@ describe 'Merge request > User scrolls to note on load', :js do
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
- let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
let(:fragment_id) { "#note_#{note.id}" }
- let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
before do
sign_in(user)
@@ -45,13 +43,35 @@ describe 'Merge request > User scrolls to note on load', :js do
end
end
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'expands collapsed notes' do
- visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
- note_element = find(collapsed_fragment_id)
- note_container = note_element.ancestor('.timeline-content')
+ context 'resolved notes' do
+ let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
- expect(note_element.visible?).to eq true
- expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true
+ context 'when diff note' do
+ let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
+
+ it 'expands collapsed notes' do
+ visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+
+ note_element = find(collapsed_fragment_id)
+ diff_container = note_element.ancestor('.diff-content')
+
+ expect(note_element.visible?).to eq(true)
+ expect(diff_container.visible?).to eq(true)
+ end
+ end
+
+ context 'when non-diff note' do
+ let(:non_diff_discussion) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
+ let(:resolved_note) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project, in_reply_to: non_diff_discussion) }
+
+ it 'expands collapsed replies' do
+ visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+
+ note_element = find(collapsed_fragment_id)
+
+ expect(note_element.visible?).to eq(true)
+ expect(note_element.sibling('.replies-toggle')[:class]).to include('expanded')
+ end
+ end
end
end
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 7b473faa884..28f88718ec1 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'rails_helper'
-describe 'Merge request > User sees merge request pipelines', :js do
+describe 'Merge request > User sees pipelines triggered by merge request', :js do
include ProjectForksHelper
include TestReportsHelper
@@ -47,9 +47,9 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
@@ -60,16 +60,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -79,9 +79,9 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
@@ -92,15 +92,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -110,25 +110,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
end
@@ -140,16 +140,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
@@ -218,9 +218,9 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
let(:forked_project) { fork_project(project, user2, repository: true) }
@@ -236,16 +236,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -261,9 +261,9 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
@@ -274,15 +274,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -292,25 +292,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
@@ -328,16 +328,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index afb978d7c45..0066e985fbb 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -145,6 +145,119 @@ describe 'Merge request > User sees merge widget', :js do
end
end
+ context 'when merge request has a branch pipeline as the head pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline,
+ ref: merge_request.source_branch,
+ sha: merge_request.source_branch_sha,
+ project: merge_request.source_project)
+ end
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{pipeline.ref}")
+ end
+ end
+ end
+
+ context 'when merge request has a detached merge request pipeline as the head pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch}")
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch}")
+ end
+ end
+ end
+ end
+
+ context 'when merge request has a merge request pipeline as the head pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: merge_sha)
+ end
+
+ let!(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:merge_sha) { project.commit.sha }
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:merge_sha) { source_project.commit.sha }
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ end
+ end
+ end
+ end
+
context 'view merge request with MWBS button' do
before do
commit_status = create(:commit_status, project: project, status: 'pending')
@@ -189,7 +302,7 @@ describe 'Merge request > User sees merge widget', :js do
visit project_merge_request_path(project_only_mwps, merge_request_in_only_mwps_project)
end
- it 'should be allowed to merge' do
+ it 'is allowed to merge' do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
@@ -201,7 +314,8 @@ describe 'Merge request > User sees merge widget', :js do
context 'view merge request with MWPS enabled but automatically merge fails' do
before do
merge_request.update(
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: merge_request.author,
merge_error: 'Something went wrong'
)
@@ -213,8 +327,8 @@ describe 'Merge request > User sees merge widget', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
@@ -234,8 +348,8 @@ describe 'Merge request > User sees merge widget', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
@@ -557,4 +671,26 @@ describe 'Merge request > User sees merge widget', :js do
end
end
end
+
+ context 'when MR has pipeline but user does not have permission' do
+ let(:sha) { project.commit(merge_request.source_branch).sha }
+ let!(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) }
+
+ before do
+ project.update(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ public_builds: false
+ )
+ merge_request.update!(head_pipeline: pipeline)
+ sign_out(:user)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'renders a CI pipeline error' do
+ within '.ci-widget' do
+ expect(page).to have_content('Could not retrieve the pipeline status.')
+ end
+ end
+ end
end
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index 5188dc3625f..dd8900a3698 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -27,7 +27,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
before do
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file1, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
@@ -35,7 +36,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
xit 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file2, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index aa91ade46ca..6eae3fd4676 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,7 +1,11 @@
require 'rails_helper'
describe 'Merge request > User sees versions', :js do
- let(:merge_request) { create(:merge_request, importing: true) }
+ let(:merge_request) do
+ create(:merge_request).tap do |mr|
+ mr.merge_request_diff.destroy
+ end
+ end
let(:project) { merge_request.source_project }
let(:user) { project.creator }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
@@ -226,7 +230,7 @@ describe 'Merge request > User sees versions', :js do
wait_for_requests
end
- it 'should only show diffs from the commit' do
+ it 'only shows diffs from the commit' do
diff_commit_ids = find_all('.diff-file [data-commit-id]').map {|diff| diff['data-commit-id']}
expect(diff_commit_ids).not_to be_empty
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index c19e299097e..04c7f4b6c76 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -6,6 +6,14 @@ describe 'User comments on a diff', :js do
include MergeRequestDiffHelpers
include RepoHelpers
+ def expect_suggestion_has_content(element, expected_changing_content, expected_suggested_content)
+ changing_content = element.all(:css, '.line_holder.old').map(&:text)
+ suggested_content = element.all(:css, '.line_holder.new').map(&:text)
+
+ expect(changing_content).to eq(expected_changing_content)
+ expect(suggested_content).to eq(expected_suggested_content)
+ end
+
let(:project) { create(:project, :repository) }
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
@@ -33,8 +41,18 @@ describe 'User comments on a diff', :js do
page.within('.diff-discussions') do
expect(page).to have_button('Apply suggestion')
expect(page).to have_content('Suggested change')
- expect(page).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
- expect(page).to have_content('# change to a comment')
+ end
+
+ page.within('.md-suggestion-diff') do
+ expected_changing_content = [
+ "6 url = https://github.com/gitlabhq/gitlab-shell.git"
+ ]
+
+ expected_suggested_content = [
+ "6 # change to a comment"
+ ]
+
+ expect_suggestion_has_content(page, expected_changing_content, expected_suggested_content)
end
end
@@ -64,7 +82,7 @@ describe 'User comments on a diff', :js do
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
page.within('.js-discussion-note-form') do
- fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion\n# or that\n```")
+ fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion:-2\n# or that\n# heh\n```")
click_button('Comment')
end
@@ -74,11 +92,94 @@ describe 'User comments on a diff', :js do
suggestion_1 = page.all(:css, '.md-suggestion-diff')[0]
suggestion_2 = page.all(:css, '.md-suggestion-diff')[1]
- expect(suggestion_1).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
- expect(suggestion_1).to have_content('# change to a comment')
+ suggestion_1_expected_changing_content = [
+ "6 url = https://github.com/gitlabhq/gitlab-shell.git"
+ ]
+ suggestion_1_expected_suggested_content = [
+ "6 # change to a comment"
+ ]
+
+ suggestion_2_expected_changing_content = [
+ "4 [submodule \"gitlab-shell\"]",
+ "5 path = gitlab-shell",
+ "6 url = https://github.com/gitlabhq/gitlab-shell.git"
+ ]
+ suggestion_2_expected_suggested_content = [
+ "4 # or that",
+ "5 # heh"
+ ]
+
+ expect_suggestion_has_content(suggestion_1,
+ suggestion_1_expected_changing_content,
+ suggestion_1_expected_suggested_content)
+
+ expect_suggestion_has_content(suggestion_2,
+ suggestion_2_expected_changing_content,
+ suggestion_2_expected_suggested_content)
+ end
+ end
+ end
+
+ context 'multi-line suggestions' do
+ before do
+ click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```")
+ click_button('Comment')
+ end
+
+ wait_for_requests
+ end
+
+ it 'suggestion is presented' do
+ page.within('.diff-discussions') do
+ expect(page).to have_button('Apply suggestion')
+ expect(page).to have_content('Suggested change')
+ end
+
+ page.within('.md-suggestion-diff') do
+ expected_changing_content = [
+ "3 url = git://github.com/randx/six.git",
+ "4 [submodule \"gitlab-shell\"]",
+ "5 path = gitlab-shell",
+ "6 url = https://github.com/gitlabhq/gitlab-shell.git",
+ "7 [submodule \"gitlab-grack\"]",
+ "8 path = gitlab-grack",
+ "9 url = https://gitlab.com/gitlab-org/gitlab-grack.git"
+ ]
+
+ expected_suggested_content = [
+ "3 # change to a",
+ "4 # comment",
+ "5 # with",
+ "6 # broken",
+ "7 # lines"
+ ]
+
+ expect_suggestion_has_content(page, expected_changing_content, expected_suggested_content)
+ end
+ end
+
+ it 'suggestion is appliable' do
+ page.within('.diff-discussions') do
+ expect(page).not_to have_content('Applied')
+
+ click_button('Apply suggestion')
+ wait_for_requests
+
+ expect(page).to have_content('Applied')
+ end
+ end
+
+ it 'resolves discussion when applied' do
+ page.within('.diff-discussions') do
+ expect(page).not_to have_content('Unresolve discussion')
+
+ click_button('Apply suggestion')
+ wait_for_requests
- expect(suggestion_2).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
- expect(suggestion_2).to have_content('# or that')
+ expect(page).to have_content('Unresolve discussion')
end
end
end
diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index b81478a481f..988a8302527 100644
--- a/spec/features/merge_request/user_uses_quick_actions_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User uses quick actions', :js do
@@ -9,9 +11,41 @@ describe 'Merge request > User uses quick actions', :js do
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
+ context "issuable common quick actions" do
+ let!(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
+ let(:maintainer) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let!(:label_bug) { create(:label, project: project, title: 'bug') }
+ let!(:label_feature) { create(:label, project: project, title: 'feature') }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:merge_request, source_project: project) }
- let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
+ let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
+
+ it_behaves_like 'assign quick action', :merge_request
+ it_behaves_like 'unassign quick action', :merge_request
+ it_behaves_like 'close quick action', :merge_request
+ it_behaves_like 'reopen quick action', :merge_request
+ it_behaves_like 'title quick action', :merge_request
+ it_behaves_like 'todo quick action', :merge_request
+ it_behaves_like 'done quick action', :merge_request
+ it_behaves_like 'subscribe quick action', :merge_request
+ it_behaves_like 'unsubscribe quick action', :merge_request
+ it_behaves_like 'lock quick action', :merge_request
+ it_behaves_like 'unlock quick action', :merge_request
+ it_behaves_like 'milestone quick action', :merge_request
+ it_behaves_like 'remove_milestone quick action', :merge_request
+ it_behaves_like 'label quick action', :merge_request
+ it_behaves_like 'unlabel quick action', :merge_request
+ it_behaves_like 'relabel quick action', :merge_request
+ it_behaves_like 'award quick action', :merge_request
+ it_behaves_like 'estimate quick action', :merge_request
+ it_behaves_like 'remove_estimate quick action', :merge_request
+ it_behaves_like 'spend quick action', :merge_request
+ it_behaves_like 'remove_time_spent quick action', :merge_request
+ it_behaves_like 'shrug quick action', :merge_request
+ it_behaves_like 'tableflip quick action', :merge_request
+ it_behaves_like 'copy_metadata quick action', :merge_request
+ it_behaves_like 'issuable time tracker', :merge_request
end
describe 'merge-request-only commands' do
@@ -24,200 +58,8 @@ describe 'Merge request > User uses quick actions', :js do
project.add_maintainer(user)
end
- describe 'time tracking' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
- describe 'toggling the WIP prefix in the title from note' do
- context 'when the current user can toggle the WIP prefix' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'adds the WIP: prefix to the title' do
- add_note("/wip")
-
- expect(page).not_to have_content '/wip'
- expect(page).to have_content 'Commands applied'
-
- expect(merge_request.reload.work_in_progress?).to eq true
- end
-
- it 'removes the WIP: prefix from the title' do
- merge_request.title = merge_request.wip_title
- merge_request.save
- add_note("/wip")
-
- expect(page).not_to have_content '/wip'
- expect(page).to have_content 'Commands applied'
-
- expect(merge_request.reload.work_in_progress?).to eq false
- end
- end
-
- context 'when the current user cannot toggle the WIP prefix' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not change the WIP prefix' do
- add_note("/wip")
-
- expect(page).not_to have_content '/wip'
- expect(page).not_to have_content 'Commands applied'
-
- expect(merge_request.reload.work_in_progress?).to eq false
- end
- end
- end
-
- describe 'merging the MR from the note' do
- context 'when the current user can merge the MR' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'merges the MR' do
- add_note("/merge")
-
- expect(page).to have_content 'Commands applied'
-
- expect(merge_request.reload).to be_merged
- end
- end
-
- context 'when the head diff changes in the meanwhile' do
- before do
- merge_request.source_branch = 'another_branch'
- merge_request.save
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not merge the MR' do
- add_note("/merge")
-
- expect(page).not_to have_content 'Your commands have been executed!'
-
- expect(merge_request.reload).not_to be_merged
- end
- end
-
- context 'when the current user cannot merge the MR' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not merge the MR' do
- add_note("/merge")
-
- expect(page).not_to have_content 'Your commands have been executed!'
-
- expect(merge_request.reload).not_to be_merged
- end
- end
- end
-
- describe 'adding a due date from note' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not recognize the command nor create a note' do
- add_note('/due 2016-08-28')
-
- expect(page).not_to have_content '/due 2016-08-28'
- end
- end
-
- describe '/target_branch command in merge request' do
- let(:another_project) { create(:project, :public, :repository) }
- let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
-
- before do
- another_project.add_maintainer(user)
- sign_in(user)
- end
-
- it 'changes target_branch in new merge_request' do
- visit project_new_merge_request_path(another_project, new_url_opts)
-
- fill_in "merge_request_title", with: 'My brand new feature'
- fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:"
- click_button "Submit merge request"
-
- merge_request = another_project.merge_requests.first
- expect(merge_request.description).to eq "le feature \nFeature description:"
- expect(merge_request.target_branch).to eq 'fix'
- end
-
- it 'does not change target branch when merge request is edited' do
- new_merge_request = create(:merge_request, source_project: another_project)
-
- visit edit_project_merge_request_path(another_project, new_merge_request)
- fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n"
- click_button "Save changes"
-
- new_merge_request = another_project.merge_requests.first
- expect(new_merge_request.description).to include('/target_branch')
- expect(new_merge_request.target_branch).not_to eq('fix')
- end
- end
-
- describe '/target_branch command from note' do
- context 'when the current user can change target branch' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'changes target branch from a note' do
- add_note("message start \n/target_branch merge-test\n message end.")
-
- wait_for_requests
- expect(page).not_to have_content('/target_branch')
- expect(page).to have_content('message start')
- expect(page).to have_content('message end.')
-
- expect(merge_request.reload.target_branch).to eq 'merge-test'
- end
-
- it 'does not fail when target branch does not exists' do
- add_note('/target_branch totally_not_existing_branch')
-
- expect(page).not_to have_content('/target_branch')
-
- expect(merge_request.target_branch).to eq 'feature'
- end
- end
-
- context 'when current user can not change target branch' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
- end
-
- it 'does not change target branch' do
- add_note('/target_branch merge-test')
-
- expect(page).not_to have_content '/target_branch merge-test'
-
- expect(merge_request.target_branch).to eq 'feature'
- end
- end
- end
+ it_behaves_like 'merge quick action'
+ it_behaves_like 'target_branch quick action'
+ it_behaves_like 'wip quick action'
end
end
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 0434db04113..74342b16cb2 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -34,6 +34,16 @@ describe 'User views diffs', :js do
expect(page).not_to have_selector('.mr-loading-status .loading', visible: true)
end
+ it 'expands all diffs' do
+ first('#a5cc2925ca8258af241be7e5b0381edf30266302 .js-file-title').click
+
+ expect(page).to have_button('Expand all')
+
+ click_button 'Expand all'
+
+ expect(page).not_to have_button('Expand all')
+ end
+
context 'when in the inline view' do
include_examples 'unfold diffs'
end
diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 71022c6bb08..849fab62fc6 100644
--- a/spec/features/merge_request/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
@@ -13,7 +13,7 @@ describe 'User views an open merge request' do
end
it 'renders both the title and the description' do
- node = find('.wiki h1 a#user-content-description-header')
+ node = find('.md h1 a#user-content-description-header')
expect(node[:href]).to end_with('#description-header')
# Work around a weird Capybara behavior where calling `parent` on a node
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
index d6c770c93f1..0cbf1bcae30 100644
--- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -7,7 +7,7 @@ describe 'Merge Requests > User filters by assignees', :js do
let(:user) { project.creator }
before do
- create(:merge_request, assignee: user, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1')
+ create(:merge_request, assignees: [user], title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1')
create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2')
sign_in(user)
diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
index 1615899a047..4627931f26a 100644
--- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -10,7 +10,7 @@ describe 'Merge requests > User filters by multiple criteria', :js do
before do
sign_in(user)
- mr = create(:merge_request, title: 'Bugfix2', author: user, assignee: user, source_project: project, target_project: project, milestone: milestone)
+ mr = create(:merge_request, title: 'Bugfix2', author: user, assignees: [user], source_project: project, target_project: project, milestone: milestone)
mr.labels << wontfix
visit project_merge_requests_path(project)
diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
new file mode 100644
index 00000000000..ffbdacc68f6
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by target branch', :js do
+ include FilteredSearchHelpers
+
+ let!(:project) { create(:project, :public, :repository) }
+ let!(:user) { project.creator }
+ let!(:mr1) { create(:merge_request, source_project: project, target_project: project, source_branch: 'feature', target_branch: 'master') }
+ let!(:mr2) { create(:merge_request, source_project: project, target_project: project, source_branch: 'feature', target_branch: 'merged-target') }
+
+ before do
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by target-branch:master' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:master')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content mr1.title
+ expect(page).not_to have_content mr2.title
+ end
+ end
+
+ context 'filtering by target-branch:merged-target' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:merged-target')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).not_to have_content mr1.title
+ expect(page).to have_content mr2.title
+ end
+ end
+
+ context 'filtering by target-branch:feature' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:feature')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).not_to have_content mr1.title
+ expect(page).not_to have_content mr2.title
+ end
+ end
+end
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 ef7ae490b0f..bd91fae1453 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -12,16 +12,16 @@ describe 'Merge requests > User lists merge requests' do
title: 'fix',
source_project: project,
source_branch: 'fix',
- assignee: user,
- milestone: create(:milestone, due_date: '2013-12-11'),
+ assignees: [user],
+ milestone: create(:milestone, project: project, due_date: '2013-12-11'),
created_at: 1.minute.ago,
updated_at: 1.minute.ago)
create(:merge_request,
title: 'markdown',
source_project: project,
source_branch: 'markdown',
- assignee: user,
- milestone: create(:milestone, due_date: '2013-12-12'),
+ assignees: [user],
+ milestone: create(:milestone, project: project, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
updated_at: 2.minutes.ago)
create(:merge_request,
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index e535c7e5811..c2dd105324d 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -54,8 +54,7 @@ describe 'Merge requests > User mass updates', :js do
describe 'remove assignee' do
before do
- merge_request.assignee = user
- merge_request.save
+ merge_request.assignees = [user]
visit project_merge_requests_path(project)
end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index 6e349395017..adac59b89ef 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -122,4 +122,32 @@ describe 'Milestone' do
expect(page).to have_selector('.popover')
end
end
+
+ describe 'reopen closed milestones' do
+ before do
+ create(:milestone, :closed, project: project)
+ end
+
+ describe 'group milestones page' do
+ it 'reopens the milestone' do
+ visit group_milestones_path(group, { state: 'closed' })
+
+ click_link 'Reopen Milestone'
+
+ expect(page).not_to have_selector('.status-box-closed')
+ expect(page).to have_selector('.status-box-open')
+ end
+ end
+
+ describe 'project milestones page' do
+ it 'reopens the milestone' do
+ visit project_milestones_path(project, { state: 'closed' })
+
+ click_link 'Reopen Milestone'
+
+ expect(page).not_to have_selector('.status-box-closed')
+ expect(page).to have_selector('.status-box-open')
+ end
+ end
+ end
end
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index f4105730402..5ebfc32952d 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -14,7 +14,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
- :facebook, :cas3, :auth0, :authentiq]
+ :facebook, :cas3, :auth0, :authentiq, :salesforce]
before(:all) do
# The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index d3050760c06..2aa0177af5d 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -7,6 +7,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
end
end
+ let(:admin) { create(:admin) }
+
around do |example|
Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
example.run
@@ -16,6 +18,7 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
it 'User sees their active sessions' do
Capybara::Session.new(:session1)
Capybara::Session.new(:session2)
+ Capybara::Session.new(:session3)
# note: headers can only be set on the non-js (aka. rack-test) driver
using_session :session1 do
@@ -37,9 +40,27 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
gitlab_sign_in(user)
end
+ # set an admin session impersonating the user
+ using_session :session3 do
+ Capybara.page.driver.header(
+ 'User-Agent',
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
+ )
+
+ gitlab_sign_in(admin)
+
+ visit admin_user_path(user)
+
+ click_link 'Impersonate'
+ end
+
using_session :session1 do
visit profile_active_sessions_path
+ expect(page).to(
+ have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
+ count: 2 }))
+
expect(page).to have_content(
'127.0.0.1 ' \
'This is your current session ' \
@@ -57,33 +78,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
)
expect(page).to have_selector '[title="Smartphone"]', count: 1
- end
- end
-
- it 'User can revoke a session', :js, :redis_session_store do
- Capybara::Session.new(:session1)
- Capybara::Session.new(:session2)
-
- # set an additional session in another browser
- using_session :session2 do
- gitlab_sign_in(user)
- end
-
- using_session :session1 do
- gitlab_sign_in(user)
- visit profile_active_sessions_path
-
- expect(page).to have_link('Revoke', count: 1)
-
- accept_confirm { click_on 'Revoke' }
-
- expect(page).not_to have_link('Revoke')
- end
-
- using_session :session2 do
- visit profile_active_sessions_path
- expect(page).to have_content('You need to sign in or sign up before continuing.')
+ expect(page).not_to have_content('Chrome on Windows')
end
end
end
diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb
new file mode 100644
index 00000000000..2d2da222998
--- /dev/null
+++ b/spec/features/profiles/user_edit_preferences_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'User edit preferences profile' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_feature_flags(user_time_settings: true)
+ sign_in(user)
+ visit(profile_preferences_path)
+ end
+
+ it 'allows the user to toggle their time format preference' do
+ field = page.find_field("user[time_format_in_24h]")
+
+ expect(field).not_to be_checked
+
+ field.click
+
+ expect(field).to be_checked
+ end
+
+ it 'allows the user to toggle their time display preference' do
+ field = page.find_field("user[time_display_relative]")
+
+ expect(field).to be_checked
+
+ field.click
+
+ expect(field).not_to be_checked
+ end
+end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index b43711f6ef6..a53da94ef7d 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -327,5 +327,37 @@ describe 'User edit profile' do
end
end
end
+
+ context 'User time preferences', :js do
+ let(:issue) { create(:issue, project: project)}
+ let(:project) { create(:project) }
+
+ before do
+ stub_feature_flags(user_time_settings: true)
+ end
+
+ it 'shows the user time preferences form' do
+ expect(page).to have_content('Time settings')
+ end
+
+ it 'allows the user to select a time zone from a dropdown list of options' do
+ expect(page.find('.user-time-preferences .dropdown')).not_to have_css('.show')
+
+ page.find('.user-time-preferences .js-timezone-dropdown').click
+
+ expect(page.find('.user-time-preferences .dropdown')).to have_css('.show')
+
+ page.find("a", text: "Nuku'alofa").click
+
+ tz = page.find('.user-time-preferences #user_timezone', visible: false)
+
+ expect(tz.value).to eq('Pacific/Tongatapu')
+ end
+
+ it 'timezone defaults to servers default' do
+ timezone_name = Time.zone.tzinfo.name
+ expect(page.find('.user-time-preferences #user_timezone', visible: false).value).to eq(timezone_name)
+ end
+ end
end
end
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 9909bfb5904..1b3718968b9 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -63,7 +63,7 @@ describe 'User visits the profile preferences page' do
end
describe 'User changes their language', :js do
- it 'creates a flash message' do
+ it 'creates a flash message', :quarantine do
select2('en', from: '#user_preferred_language')
click_button 'Save'
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index a93df3696d2..95685a3c7ff 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project variables', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
+ let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value', masked: true) }
let(:page_path) { project_settings_ci_cd_path(project) }
before do
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index 5f630c9ffa4..a1fcd4024c0 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -19,6 +19,12 @@ describe "User browses artifacts" do
visit(browse_project_job_artifacts_path(project, job))
end
+ it "renders a link to the job in the breadcrumbs" do
+ page.within('.js-breadcrumbs-list') do
+ expect(page).to have_link("##{job.id}", href: project_job_path(project, job))
+ end
+ end
+
it "shows artifacts" do
expect(page).not_to have_selector(".build-sidebar")
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
index dee81898928..4ac4e8f0fcb 100644
--- a/spec/features/projects/badges/pipeline_badge_spec.rb
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -41,6 +41,25 @@ describe 'Pipeline Badge' do
end
end
+ context 'when the pipeline is preparing' do
+ let!(:job) { create(:ci_build, status: 'created', pipeline: pipeline) }
+
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+ allow(job).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'displays the preparing badge' do
+ job.enqueue
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('preparing')
+ end
+ end
+
context 'when the pipeline is running' do
it 'shows displays so on the badge' do
create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run')
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 3edcc7ac2cd..aa2e538cc8e 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -548,10 +548,7 @@ describe 'File blob', :js do
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows names of dependency manager and package
- expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.')
-
- # shows a link to the gem
- expect(page).to have_link('activerecord', href: 'https://rubygems.org/gems/activerecord')
+ expect(page).to have_content('This project manages its dependencies using RubyGems.')
# shows a learn more link
expect(page).to have_link('Learn more', href: 'https://rubygems.org/')
@@ -575,7 +572,7 @@ describe 'File blob', :js do
visit_blob('files/ruby/test.rb', ref: 'feature')
end
- it 'should show the realtime pipeline status' do
+ it 'shows the realtime pipeline status' do
page.within('.commit-actions') do
expect(page).to have_css('.ci-status-icon')
expect(page).to have_css('.ci-status-icon-running')
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 1522a3361a1..57d21f3e182 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -9,6 +9,10 @@ describe 'Editing file blob', :js do
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
let(:readme_file_path) { 'README.md' }
+ before do
+ stub_feature_flags(web_ide_default: false)
+ end
+
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
@@ -45,6 +49,15 @@ describe 'Editing file blob', :js do
end
end
+ it 'updates the content of file with a number as file path' do
+ project.repository.create_file(user, '1', 'test', message: 'testing', branch_name: branch)
+ visit project_blob_path(project, tree_join(branch, '1'))
+
+ edit_and_commit
+
+ expect(page).to have_content 'NextFeature'
+ end
+
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index c8dc72a34ec..3e75890725e 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -35,7 +35,7 @@ describe 'Download buttons in branches page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/features/projects/classification_label_on_project_pages_spec.rb b/spec/features/projects/classification_label_on_project_pages_spec.rb
new file mode 100644
index 00000000000..92f8aa8eb8d
--- /dev/null
+++ b/spec/features/projects/classification_label_on_project_pages_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Classification label on project pages' do
+ let(:project) do
+ create(:project, external_authorization_classification_label: 'authorized label')
+ end
+ let(:user) { create(:user) }
+
+ before do
+ stub_application_setting(external_authorization_service_enabled: true)
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'shows the classification label on the project page' do
+ visit project_path(project)
+
+ expect(page).to have_content('authorized label')
+ end
+end
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 2c8d014c36d..527508b3519 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -17,7 +17,7 @@ describe 'Clusters Applications', :js do
end
context 'when cluster is being created' do
- let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project])}
+ let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
it 'user is unable to install applications' do
page.within('.js-cluster-application-row-helm') do
@@ -28,9 +28,11 @@ describe 'Clusters Applications', :js do
end
context 'when cluster is created' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])}
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
it 'user can install applications' do
+ wait_for_requests
+
page.within('.js-cluster-application-row-helm') do
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
@@ -44,6 +46,8 @@ describe 'Clusters Applications', :js do
page.within('.js-cluster-application-row-helm') do
page.find(:css, '.js-cluster-application-install-button').click
end
+
+ wait_for_requests
end
it 'they see status transition' do
@@ -52,8 +56,6 @@ describe 'Clusters Applications', :js do
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
- wait_until_helm_created!
-
Clusters::Cluster.last.application_helm.make_installing!
# FE starts polling and update the buttons to "Installing"
@@ -76,25 +78,79 @@ describe 'Clusters Applications', :js do
end
context 'on an abac cluster' do
- let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project])}
+ let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project]) }
- it 'should show info block and not be installable' do
+ it 'shows info block and not be installable' do
page.within('.js-cluster-application-row-knative') do
- expect(page).to have_css('.bs-callout-info')
+ expect(page).to have_css('.rbac-notice')
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
end
end
end
context 'on an rbac cluster' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])}
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- it 'should not show callout block and be installable' do
+ it 'does not show callout block and be installable' do
page.within('.js-cluster-application-row-knative') do
- expect(page).not_to have_css('.bs-callout-info')
+ expect(page).not_to have_css('.rbac-notice')
expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
end
end
+
+ describe 'when user clicks install button' do
+ def domainname_form_value
+ page.find('.js-knative-domainname').value
+ end
+
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
+
+ page.find('.js-knative-domainname').set("domain.example.org")
+
+ click_button 'Install'
+
+ wait_for_requests
+
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_knative.make_installing!
+ Clusters::Cluster.last.application_knative.make_installed!
+ Clusters::Cluster.last.application_knative.update_attribute(:external_ip, '127.0.0.1')
+ end
+ end
+
+ it 'shows status transition' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ end
+
+ expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
+ expect(page).to have_css('.js-knative-save-domain-button'), exact_text: 'Save changes'
+ end
+
+ it 'can then update the domain' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(ClusterPatchAppWorker).to receive(:perform_async)
+
+ expect(domainname_form_value).to eq('domain.example.org')
+
+ page.find('.js-knative-domainname').set("new.domain.example.org")
+
+ click_button 'Save changes'
+
+ wait_for_requests
+
+ expect(domainname_form_value).to eq('new.domain.example.org')
+ end
+ end
+ end
end
end
@@ -148,6 +204,8 @@ describe 'Clusters Applications', :js do
page.within('.js-cluster-application-row-ingress') do
expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
page.find(:css, '.js-cluster-application-install-button').click
+
+ wait_for_requests
end
end
@@ -168,14 +226,14 @@ describe 'Clusters Applications', :js do
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
expect(page).to have_css('.js-cluster-application-install-button[disabled]')
- expect(page).to have_selector('.js-no-ip-message')
- expect(page.find('.js-ip-address').value).to eq('?')
+ expect(page).to have_selector('.js-no-endpoint-message')
+ expect(page).to have_selector('.js-ingress-ip-loading-icon')
# We receive the external IP address and display
Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100')
- expect(page).not_to have_selector('.js-no-ip-message')
- expect(page.find('.js-ip-address').value).to eq('192.168.1.100')
+ expect(page).not_to have_selector('.js-no-endpoint-message')
+ expect(page.find('.js-endpoint').value).to eq('192.168.1.100')
end
expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster')
@@ -184,14 +242,4 @@ describe 'Clusters Applications', :js do
end
end
end
-
- def wait_until_helm_created!
- retries = 0
-
- while Clusters::Cluster.last.application_helm.nil?
- raise "Timed out waiting for helm application to be created in DB" if (retries += 1) > 3
-
- sleep(1)
- end
- end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 9322e29d744..83e582c34f0 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -92,7 +92,7 @@ describe 'Gcp Cluster', :js do
end
it 'user sees a validation error' do
- expect(page).to have_css('#error_explanation')
+ expect(page).to have_css('.gl-field-error')
end
end
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 1f2f7592d8b..31cc09ae911 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -12,6 +12,7 @@ describe 'User Cluster', :js do
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
@@ -53,7 +54,7 @@ describe 'User Cluster', :js do
end
it 'user sees a validation error' do
- expect(page).to have_css('#error_explanation')
+ expect(page).to have_css('.gl-field-error')
end
end
end
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index a85e7333ba8..ce382c19fc1 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Clusters', :js do
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 29442a58ea4..586e2e33112 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -138,7 +138,7 @@ describe "User adds a comment on a commit", :js do
click_button("Comment")
end
- page.within(".diff-file:nth-of-type(1) .notes_content.parallel.old") do
+ page.within(".diff-file:nth-of-type(1) .notes-content.parallel.old") do
expect(page).to have_content(old_comment)
end
@@ -152,7 +152,7 @@ describe "User adds a comment on a commit", :js do
wait_for_requests
- expect(all(".diff-file:nth-of-type(1) .notes_content.parallel.new")[1].text).to have_content(new_comment)
+ expect(all(".diff-file:nth-of-type(1) .notes-content.parallel.new")[1].text).to have_content(new_comment)
end
end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 19f6ebf2c1a..614f11c8392 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -43,7 +43,7 @@ describe 'Mini Pipeline Graph in Commit View', :js do
visit project_commit_path(project, project.commit.id)
end
- it 'should not display a mini pipeline graph' do
+ it 'does not display a mini pipeline graph' do
expect(page).not_to have_selector('.mr-widget-pipeline-graph')
end
end
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 574a8aefd63..953517cdff9 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -61,7 +61,7 @@ describe 'User browses commits' do
it 'renders commit ci info' do
visit project_commit_path(project, sample_commit.id)
- expect(page).to have_content "Pipeline ##{pipeline.id} pending"
+ expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) pending"
end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index a8a3b6910fb..da4ef6428d4 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -120,7 +120,7 @@ describe 'Environment' do
end
it 'does show a play button' do
- expect(page).to have_link(action.name.humanize)
+ expect(page).to have_link(action.name)
end
it 'does allow to play manual action', :js do
@@ -128,7 +128,7 @@ describe 'Environment' do
find('button.dropdown').click
- expect { click_link(action.name.humanize) }
+ expect { click_link(action.name) }
.not_to change { Ci::Pipeline.count }
wait_for_all_requests
@@ -140,7 +140,7 @@ describe 'Environment' do
context 'when user has no ability to trigger a deployment' do
it 'does not show a play button' do
- expect(page).not_to have_link(action.name.humanize)
+ expect(page).not_to have_link(action.name)
end
end
@@ -159,7 +159,7 @@ describe 'Environment' do
context 'for project maintainer' do
let(:role) { :maintainer }
- it 'it shows the terminal button' do
+ it 'shows the terminal button' do
expect(page).to have_terminal_button
end
@@ -319,7 +319,7 @@ describe 'Environment' do
yield
- GitPushService.new(project, user, params).execute
+ Git::BranchPushService.new(project, user, params).execute
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 0c517d5f490..7b7e45312d9 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -30,7 +30,7 @@ describe 'Environments page', :js do
end
describe 'in available tab page' do
- it 'should show one environment' do
+ it 'shows one environment' do
visit_environments(project, scope: 'available')
expect(page).to have_css('.environments-container')
@@ -38,8 +38,25 @@ describe 'Environments page', :js do
end
end
+ describe 'with environments spanning multiple pages', :js do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(3)
+ create_list(:environment, 4, project: project, state: :available)
+ end
+
+ it 'renders second page of pipelines' do
+ visit_environments(project, scope: 'available')
+
+ find('.js-next-button').click
+ wait_for_requests
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2")
+ end
+ end
+
describe 'in stopped tab page' do
- it 'should show no environments' do
+ it 'shows no environments' do
visit_environments(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
@@ -55,7 +72,7 @@ describe 'Environments page', :js do
allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
end
- it 'should show one environment without error' do
+ it 'shows one environment without error' do
visit_environments(project, scope: 'available')
expect(page).to have_css('.environments-container')
@@ -70,7 +87,7 @@ describe 'Environments page', :js do
end
describe 'in available tab page' do
- it 'should show no environments' do
+ it 'shows no environments' do
visit_environments(project, scope: 'available')
expect(page).to have_css('.environments-container')
@@ -79,7 +96,7 @@ describe 'Environments page', :js do
end
describe 'in stopped tab page' do
- it 'should show one environment' do
+ it 'shows one environment' do
visit_environments(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
@@ -166,14 +183,14 @@ describe 'Environments page', :js do
it 'shows a play button' do
find('.js-environment-actions-dropdown').click
- expect(page).to have_content(action.name.humanize)
+ expect(page).to have_content(action.name)
end
it 'allows to play a manual action', :js do
expect(action).to be_manual
find('.js-environment-actions-dropdown').click
- expect(page).to have_content(action.name.humanize)
+ expect(page).to have_content(action.name)
expect { find('.js-manual-action-link').click }
.not_to change { Ci::Pipeline.count }
@@ -294,7 +311,7 @@ describe 'Environments page', :js do
it "has link to the delayed job's action" do
find('.js-environment-actions-dropdown').click
- expect(page).to have_button('Delayed job')
+ expect(page).to have_button('delayed job')
expect(page).to have_content(/\d{2}:\d{2}:\d{2}/)
end
@@ -316,7 +333,7 @@ describe 'Environments page', :js do
context 'when user played a delayed job immediately' do
before do
find('.js-environment-actions-dropdown').click
- page.accept_confirm { click_button('Delayed job') }
+ page.accept_confirm { click_button('delayed job') }
wait_for_requests
end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 03cb3530e2b..111972a6b00 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -30,7 +30,7 @@ describe 'Projects > Files > Download buttons in files tree' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index b6dbf76bc9b..51c884201a6 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -5,6 +5,8 @@ describe 'Projects > Files > User views files page' do
let(:user) { project.owner }
before do
+ stub_feature_flags(vue_file_list: false)
+
sign_in user
visit project_tree_path(project, project.repository.root_ref)
end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 6762460971f..44715261b8b 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
let(:project_maintainer) { project.owner }
before do
+ stub_feature_flags(vue_file_list: false)
project.repository.delete_file(project_maintainer, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
sign_in(project_maintainer)
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index 66268355345..a5d849db8a3 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -11,6 +11,7 @@ describe "User browses files" do
let(:user) { project.owner }
before do
+ stub_feature_flags(vue_file_list: false)
stub_feature_flags(csslab: false)
sign_in(user)
end
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
index d56476adb05..d5cb8f9212d 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -5,6 +5,8 @@ describe 'Projects > Files > User browses LFS files' do
let(:user) { project.owner }
before do
+ stub_feature_flags(vue_file_list: false)
+
sign_in(user)
end
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
index 847b5f0860f..e29e867492e 100644
--- a/spec/features/projects/files/user_creates_directory_spec.rb
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -11,6 +11,8 @@ describe 'Projects > Files > User creates a directory', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
+
project.add_developer(user)
sign_in(user)
visit project_tree_path(project, 'master')
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index a4f94b7a76d..69f8bd4d319 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -12,6 +12,9 @@ describe 'Projects > Files > User creates files' do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
+ stub_feature_flags(web_ide_default: false)
+
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 614b11fa5c8..11ee87f245b 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -12,6 +12,8 @@ describe 'Projects > Files > User deletes files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
+
sign_in(user)
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 9eb65ec159c..26efb5e6787 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -9,6 +9,9 @@ describe 'Projects > Files > User edits files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(web_ide_default: false)
+ stub_feature_flags(vue_file_list: false)
+
sign_in(user)
end
@@ -169,7 +172,7 @@ describe 'Projects > Files > User edits files', :js do
wait_for_requests
end
- it 'links to the forked project for editing' do
+ it 'links to the forked project for editing', :quarantine do
click_link('.gitignore')
find('.js-edit-blob').click
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index e3da28d73c3..bfd612e4cc8 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -14,6 +14,8 @@ describe 'Projects > Files > User replaces files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
+
sign_in(user)
end
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index af3fc528a20..25ff3fdf411 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -14,6 +14,8 @@ describe 'Projects > Files > User uploads files' do
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
before do
+ stub_feature_flags(vue_file_list: false)
+
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb
new file mode 100644
index 00000000000..2c41c61a660
--- /dev/null
+++ b/spec/features/projects/forks/fork_list_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'listing forks of a project' do
+ include ProjectForksHelper
+ include ExternalAuthorizationServiceHelpers
+
+ let(:source) { create(:project, :public, :repository) }
+ let!(:fork) { fork_project(source, nil, repository: true) }
+ let(:user) { create(:user) }
+
+ before do
+ source.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'shows the forked project in the list with commit as description' do
+ visit project_forks_path(source)
+
+ page.within('li.project-row') do
+ expect(page).to have_content(fork.full_name)
+ expect(page).to have_css('a.commit-row-message')
+ end
+ end
+
+ it 'does not show the commit message when an external authorization service is used' do
+ enable_external_authorization_service_check
+
+ visit project_forks_path(source)
+
+ page.within('li.project-row') do
+ expect(page).to have_content(fork.full_name)
+ expect(page).not_to have_css('a.commit-row-message')
+ end
+ end
+end
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index 9665f1755d6..e1bc18519a2 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -6,6 +6,8 @@ describe 'Project Graph', :js do
let(:branch_name) { 'master' }
before do
+ ::Projects::DetectRepositoryLanguagesService.new(project, user).execute
+
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index f76f9ba7577..9d74a96ab3d 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -12,7 +12,7 @@ describe 'Import/Export - project export integration test', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
- let(:sensitive_words) { %w[pass secret token key encrypted] }
+ let(:sensitive_words) { %w[pass secret token key encrypted html] }
let(:safe_list) do
{
token: [ProjectHook, Ci::Trigger, CommitStatus],
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 28ae90bc0de..8d2b1fc7e30 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -47,7 +47,6 @@ describe 'Import/Export - project import integration test', :js do
expect(project.description).to eq("Foo Bar")
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
- expect(project_hook_exists?(project)).to be true
expect(wiki_exists?(project)).to be true
expect(project.import_state.status).to eq('finished')
end
diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
new file mode 100644
index 00000000000..a8612d77a5e
--- /dev/null
+++ b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+describe 'viewing an issue with cross project references' do
+ include ExternalAuthorizationServiceHelpers
+ include Gitlab::Routing.url_helpers
+
+ let(:user) { create(:user) }
+ let(:other_project) do
+ create(:project, :public,
+ external_authorization_classification_label: 'other_label')
+ end
+ let(:other_issue) do
+ create(:issue, :closed,
+ title: 'I am in another project',
+ project: other_project)
+ end
+ let(:other_confidential_issue) do
+ create(:issue, :confidential, :closed,
+ title: 'I am in another project and confidential',
+ project: other_project)
+ end
+ let(:other_merge_request) do
+ create(:merge_request, :closed,
+ title: 'I am a merge request in another project',
+ source_project: other_project)
+ end
+ let(:description_referencing_other_issue) do
+ "Referencing: #{other_issue.to_reference(project)}, "\
+ "a confidential issue #{confidential_issue.to_reference}, "\
+ "a cross project confidential issue #{other_confidential_issue.to_reference(project)}, and "\
+ "a cross project merge request #{other_merge_request.to_reference(project)}"
+ end
+ let(:project) { create(:project) }
+ let(:issue) do
+ create(:issue,
+ project: project,
+ description: description_referencing_other_issue )
+ end
+ let(:confidential_issue) do
+ create(:issue, :confidential, :closed,
+ title: "I am in the same project and confidential",
+ project: project)
+ end
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'shows all information related to the cross project reference' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{other_issue.to_reference(project)} (#{other_issue.state})")
+ expect(page).to have_xpath("//a[@title='#{other_issue.title}']")
+ end
+
+ it 'shows a link to the confidential issue in the same project' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})")
+ expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']")
+ end
+
+ it 'does not show the link to a cross project confidential issue when the user does not have access' do
+ visit project_issue_path(project, issue)
+
+ expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})")
+ expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
+ end
+
+ it 'shows the link to a cross project confidential issue when the user has access' do
+ other_project.add_developer(user)
+
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})")
+ expect(page).to have_xpath("//a[@title='#{other_confidential_issue.title}']")
+ end
+
+ context 'when an external authorization service is enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'only hits the external service for the project the user is viewing' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'default_label', any_args).at_least(1).and_return(true)
+ expect(::Gitlab::ExternalAuthorization)
+ .not_to receive(:access_allowed?).with(user, 'other_label', any_args)
+
+ visit project_issue_path(project, issue)
+ end
+
+ it 'shows only the link to the cross project references' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{other_issue.to_reference(project)}")
+ expect(page).to have_link("#{other_merge_request.to_reference(project)}")
+ expect(page).not_to have_content("#{other_issue.to_reference(project)} (#{other_issue.state})")
+ expect(page).not_to have_xpath("//a[@title='#{other_issue.title}']")
+ expect(page).not_to have_content("#{other_merge_request.to_reference(project)} (#{other_merge_request.state})")
+ expect(page).not_to have_xpath("//a[@title='#{other_merge_request.title}']")
+ end
+
+ it 'does not link a cross project confidential issue if the user does not have access' do
+ visit project_issue_path(project, issue)
+
+ expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)}")
+ expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
+ end
+
+ it 'links a cross project confidential issue without exposing information when the user has access' do
+ other_project.add_developer(user)
+
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{other_confidential_issue.to_reference(project)}")
+ expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
+ end
+
+ it 'shows a link to the confidential issue in the same project' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})")
+ expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']")
+ end
+ end
+end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 6ce37297a7e..b5e711997a0 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -90,7 +90,7 @@ describe 'Project Jobs Permissions' do
before do
archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip')
- job.update(legacy_artifacts_file: archive)
+ create(:ci_job_artifact, :archive, file: archive, job: job)
end
context 'when public access for jobs is disabled' do
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 908c616f2fc..54b462da87a 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -28,8 +28,8 @@ describe 'User browses a job', :js do
expect(page).to have_no_css('.artifacts')
expect(build).not_to have_trace
- expect(build.artifacts_file.exists?).to be_falsy
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
expect(page).to have_content('Job has been erased')
end
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
index ebc20d15d67..bd6c73f4b85 100644
--- a/spec/features/projects/jobs/user_browses_jobs_spec.rb
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -16,6 +16,12 @@ describe 'User browses jobs' do
visit(project_jobs_path(project))
end
+ it 'shows pipeline id and IID' do
+ page.within('td.pipeline-link') do
+ expect(page).to have_content("##{pipeline.id} (##{pipeline.iid})")
+ end
+ end
+
it 'shows the coverage' do
page.within('td.coverage') do
expect(page).to have_content('99.9%')
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 65ce872363f..03562bd382e 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -2,6 +2,9 @@ require 'spec_helper'
require 'tempfile'
describe 'Jobs', :clean_gitlab_redis_shared_state do
+ include Gitlab::Routing
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project, :repository) }
@@ -121,6 +124,112 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
+ context 'pipeline info block', :js do
+ it 'shows pipeline id and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) for #{pipeline.ref}")
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+ end
+ end
+ end
+
context 'sidebar', :js do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') }
@@ -205,7 +314,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Download artifacts", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
end
@@ -229,8 +338,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context 'Artifacts expire date', :js do
before do
- job.update(legacy_artifacts_file: artifacts_file,
- artifacts_expire_at: expire_at)
+ create(:ci_job_artifact, :archive, file: artifacts_file, expire_at: expire_at, job: job)
+ job.update!(artifacts_expire_at: expire_at)
visit project_job_path(project, job)
end
@@ -872,7 +981,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe "GET /:project/jobs/:id/download", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
click_link 'Download'
@@ -880,7 +989,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Build from other project" do
before do
- job2.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job2)
end
it do
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index d36f043f880..f32b155790f 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -138,29 +138,41 @@ describe 'Prioritize labels' do
end
context 'as a guest' do
- it 'does not prioritize labels' do
+ before do
+ create(:label_priority, project: project, label: bug, priority: 1)
+ create(:label_priority, project: project, label: feature, priority: 2)
+
guest = create(:user)
sign_in guest
visit project_labels_path(project)
+ end
+ it 'cannot prioritize labels' do
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
expect(page).to have_content 'feature'
- expect(page).not_to have_css('.prioritized-labels')
expect(page).not_to have_content 'Star a label'
end
+
+ it 'cannot sort prioritized labels', :js do
+ drag_to(selector: '.prioritized-labels .label-list-item', from_index: 1, to_index: 2)
+
+ page.within('.prioritized-labels') do
+ expect(first('.label-list-item')).to have_content('bug')
+ expect(page.all('.label-list-item').last).to have_content('feature')
+ end
+ end
end
context 'as a non signed in user' do
- it 'does not prioritize labels' do
+ it 'cannot prioritize labels' do
visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
expect(page).to have_content 'feature'
- expect(page).not_to have_css('.prioritized-labels')
expect(page).not_to have_content 'Star a label'
end
end
diff --git a/spec/features/projects/labels/user_promotes_label_spec.rb b/spec/features/projects/labels/user_promotes_label_spec.rb
new file mode 100644
index 00000000000..fdecafd4c50
--- /dev/null
+++ b/spec/features/projects/labels/user_promotes_label_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User promotes label' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:label) { create(:label, project: project) }
+
+ context 'when user can admin group labels' do
+ before do
+ group.add_developer(user)
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it "shows label promote button" do
+ expect(page).to have_selector('.js-promote-project-label-button')
+ end
+ end
+
+ context 'when user cannot admin group labels' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it "does not show label promote button" do
+ expect(page).not_to have_selector('.js-promote-project-label-button')
+ end
+ end
+end
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
index b0ce03a1c31..c231e54decd 100644
--- a/spec/features/projects/labels/user_removes_labels_spec.rb
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -21,8 +21,11 @@ describe "User removes labels" do
page.first(".label-list-item") do
first('.js-label-options-dropdown').click
first(".remove-row").click
- first(:link, "Delete label").click
end
+
+ expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.")
+
+ first(:link, "Delete label").click
end
expect(page).to have_content("Label was removed").and have_no_content(label.title)
diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb
index 2c8267764bd..a6f7968c535 100644
--- a/spec/features/projects/labels/user_views_labels_spec.rb
+++ b/spec/features/projects/labels/user_views_labels_spec.rb
@@ -7,6 +7,7 @@ describe "User views labels" do
set(:user) { create(:user) }
let(:label_titles) { %w[bug enhancement feature] }
+ let!(:prioritized_label) { create(:label, project: project, title: 'prioritized-label-name', priority: 1) }
before do
label_titles.each { |title| create(:label, project: project, title: title) }
@@ -18,6 +19,10 @@ describe "User views labels" do
end
it "shows all labels" do
+ page.within('.prioritized-labels .manage-labels-list') do
+ expect(page).to have_content('prioritized-label-name')
+ end
+
page.within('.other-labels .manage-labels-list') do
label_titles.each { |title| expect(page).to have_content(title) }
end
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index 0ab29660189..a645b917568 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -8,10 +8,17 @@ describe 'Projects > Members > Group member cannot leave group project' do
before do
group.add_developer(user)
sign_in(user)
- visit project_path(project)
end
it 'user does not see a "Leave project" link' do
+ visit project_path(project)
+
expect(page).not_to have_content 'Leave project'
end
+
+ it 'renders a flash message if attempting to leave by url', :js do
+ visit project_path(project, leave: 1)
+
+ expect(find('.flash-alert')).to have_content 'You do not have permission to leave this project'
+ end
end
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index fceead0b45e..7432c600c1e 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -27,6 +27,7 @@ describe 'Project > Members > Invite group', :js do
before do
project.add_maintainer(maintainer)
+ group_to_share_with.add_guest(maintainer)
sign_in(maintainer)
end
@@ -112,6 +113,7 @@ describe 'Project > Members > Invite group', :js do
before do
project.add_maintainer(maintainer)
+ group.add_guest(maintainer)
sign_in(maintainer)
visit project_settings_members_path(project)
@@ -157,7 +159,7 @@ describe 'Project > Members > Invite group', :js do
open_select2 '#link_group_id'
end
- it 'should infinitely scroll' do
+ it 'infinitely scrolls' do
expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1)
scroll_select2_to_bottom('.select2-drop .select2-results:visible')
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index 94b29de4686..bd2ef9c07c4 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -7,13 +7,24 @@ describe 'Projects > Members > Member leaves project' do
before do
project.add_developer(user)
sign_in(user)
- visit project_path(project)
end
it 'user leaves project' do
+ visit project_path(project)
+
click_link 'Leave project'
expect(current_path).to eq(dashboard_projects_path)
expect(project.users.exists?(user.id)).to be_falsey
end
+
+ it 'user leaves project by url param', :js do
+ visit project_path(project, leave: 1)
+
+ page.accept_confirm
+
+ expect(find('.flash-notice')).to have_content "You left the \"#{project.full_name}\" project"
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(project.users.exists?(user.id)).to be_falsey
+ end
end
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 50ba67f0ffc..f26941ab567 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -62,8 +62,9 @@ describe 'Projects > Members > User requests access', :js do
accept_confirm { click_link 'Withdraw Access Request' }
- expect(project.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the project has been withdrawn.'
+ expect(page).not_to have_content 'Withdraw Access Request'
+ expect(page).to have_content 'Request Access'
end
def open_project_settings_menu
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 75c72a68069..033e1afe866 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -63,6 +63,36 @@ describe 'New project' do
end
end
end
+
+ context 'when group visibility is private but default is internal' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ visit new_project_path(namespace_id: group.id)
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
+
+ context 'when group visibility is public but user requests private' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
end
context 'Readme selector' do
@@ -252,4 +282,23 @@ describe 'New project' do
end
end
end
+
+ context 'Namespace selector' do
+ context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+
+ before do
+ group.add_developer(user)
+ visit new_project_path(namespace_id: group.id)
+ end
+
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
+
+ expect(namespace.text).to eq group.full_path
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb
new file mode 100644
index 00000000000..baa217cbe58
--- /dev/null
+++ b/spec/features/projects/pages_lets_encrypt_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe "Pages with Let's Encrypt", :https_pages_enabled do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:role) { :maintainer }
+ let(:certificate_pem) do
+ <<~PEM
+ -----BEGIN CERTIFICATE-----
+ MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
+ LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
+ MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+ gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
+ SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
+ nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
+ DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
+ VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
+ IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
+ joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
+ 5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
+ YHi2yesCrOvVXt+lgPTd
+ -----END CERTIFICATE-----
+ PEM
+ end
+
+ let(:certificate_key) do
+ <<~KEY
+ -----BEGIN PRIVATE KEY-----
+ MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
+ SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
+ PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
+ kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
+ j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
+ uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
+ 5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
+ AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
+ EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
+ Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
+ m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
+ EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
+ 63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
+ nNp/xedE1YxutQ==
+ -----END PRIVATE KEY-----
+ KEY
+ end
+
+ before do
+ allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
+ project.add_role(user, role)
+ sign_in(user)
+ project.namespace.update(owner: user)
+ allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
+ end
+
+ context 'when the page_auto_ssl feature flag is enabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: true)
+ end
+
+ context 'when the auto SSL management is initially disabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: false, project: project)
+ end
+
+ it 'enables auto SSL and dynamically updates the form accordingly', :js do
+ visit edit_project_pages_domain_path(project, domain)
+
+ expect(domain.auto_ssl_enabled).to eq false
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).to have_field 'Key (PEM)', type: 'textarea'
+
+ find('.js-auto-ssl-toggle-container .project-feature-toggle').click
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true'
+ expect(page).not_to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).not_to have_field 'Key (PEM)', type: 'textarea'
+ expect(page).to have_content "The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete."
+
+ click_on 'Save Changes'
+
+ expect(domain.reload.auto_ssl_enabled).to eq true
+ end
+ end
+
+ context 'when the auto SSL management is initially enabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: true, project: project)
+ end
+
+ it 'disables auto SSL and dynamically updates the form accordingly', :js do
+ visit edit_project_pages_domain_path(project, domain)
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea', disabled: true
+ expect(page).not_to have_field 'Key (PEM)', type: 'textarea'
+
+ find('.js-auto-ssl-toggle-container .project-feature-toggle').click
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).to have_field 'Key (PEM)', type: 'textarea'
+
+ fill_in 'Certificate (PEM)', with: certificate_pem
+ fill_in 'Key (PEM)', with: certificate_key
+
+ click_on 'Save Changes'
+
+ expect(domain.reload.auto_ssl_enabled).to eq false
+ end
+ end
+ end
+
+ context 'when the page_auto_ssl feature flag is disabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: false, project: project)
+ end
+
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+
+ visit edit_project_pages_domain_path(project, domain)
+ end
+
+ it "does not render the Let's Encrypt field", :js do
+ expect(page).not_to have_selector '.js-auto-ssl-toggle-container'
+ end
+ end
+end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 72faeba92ee..9bb0ba81ef5 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -1,6 +1,7 @@
+# frozen_string_literal: true
require 'spec_helper'
-describe 'Pages' do
+shared_examples 'pages domain editing' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:role) { :maintainer }
@@ -212,7 +213,7 @@ describe 'Pages' do
it 'tries to change the setting' do
visit project_pages_path(project)
- expect(page).to have_content("Force domains with SSL certificates to use HTTPS")
+ expect(page).to have_content("Force HTTPS (requires valid certificates)")
uncheck :project_pages_https_only
@@ -261,7 +262,7 @@ describe 'Pages' do
visit project_pages_path(project)
expect(page).not_to have_field(:project_pages_https_only)
- expect(page).not_to have_content('Force domains with SSL certificates to use HTTPS')
+ expect(page).not_to have_content('Force HTTPS (requires valid certificates)')
expect(page).not_to have_button('Save')
end
end
@@ -287,10 +288,17 @@ describe 'Pages' do
:ci_build,
project: project,
pipeline: pipeline,
- ref: 'HEAD',
- legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
- legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
- )
+ ref: 'HEAD')
+ end
+
+ let!(:artifact) do
+ create(:ci_job_artifact, :archive,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build)
+ end
+
+ let!(:metadata) do
+ create(:ci_job_artifact, :metadata,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build)
end
before do
@@ -311,3 +319,21 @@ describe 'Pages' do
end
end
end
+
+describe 'Pages' do
+ context 'when pages_auto_ssl feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+ end
+
+ include_examples 'pages domain editing'
+ end
+
+ context 'when pages_auto_ssl feature flag is enabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: true)
+ end
+
+ include_examples 'pages domain editing'
+ end
+end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index ee6b67b2188..24041a51383 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -93,14 +93,14 @@ describe 'Pipeline Schedules', :js do
expect(page).to have_button('UTC')
end
- it 'it creates a new scheduled pipeline' do
+ it 'creates a new scheduled pipeline' do
fill_in_schedule_form
save_pipeline_schedule
expect(page).to have_content('my fancy description')
end
- it 'it prevents an invalid form from being submitted' do
+ it 'prevents an invalid form from being submitted' do
save_pipeline_schedule
expect(page).to have_content('This field is required')
@@ -112,7 +112,7 @@ describe 'Pipeline Schedules', :js do
edit_pipeline_schedule
end
- it 'it displays existing properties' do
+ it 'displays existing properties' do
description = find_field('schedule_description').value
expect(description).to eq('pipeline schedule')
expect(page).to have_button('master')
@@ -225,7 +225,7 @@ describe 'Pipeline Schedules', :js do
context 'when active is true and next_run_at is NULL' do
before 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
+ pipeline_schedule.update_attribute(:next_run_at, nil) # Consequently next_run_at will be nil
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 72ef460d315..1de153db41c 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,6 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline', :js do
+ include RoutesHelpers
+ include ProjectForksHelper
+
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:role) { :developer }
@@ -21,6 +26,11 @@ describe 'Pipeline', :js do
pipeline: pipeline, stage: 'test', name: 'test')
end
+ let!(:build_preparing) do
+ create(:ci_build, :preparing,
+ pipeline: pipeline, stage: 'deploy', name: 'prepare')
+ end
+
let!(:build_running) do
create(:ci_build, :running,
pipeline: pipeline, stage: 'deploy', name: 'deploy')
@@ -51,11 +61,11 @@ describe 'Pipeline', :js do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
- before do
- visit project_pipeline_path(project, pipeline)
- end
+ subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) }
it 'shows the pipeline graph' do
+ visit_pipeline
+
expect(page).to have_selector('.pipeline-visualization')
expect(page).to have_content('Build')
expect(page).to have_content('Test')
@@ -65,13 +75,28 @@ describe 'Pipeline', :js do
end
it 'shows Pipeline tab pane as active' do
+ visit_pipeline
+
expect(page).to have_css('#js-tab-pipeline.active')
end
it 'shows link to the pipeline ref' do
+ visit_pipeline
+
expect(page).to have_link(pipeline.ref)
end
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for #{pipeline.ref} ")
+ expect(page).to have_link(pipeline.ref,
+ href: project_commits_path(pipeline.project, pipeline.ref))
+ end
+ end
+
it_behaves_like 'showing user status' do
let(:user_with_status) { pipeline.user }
@@ -79,6 +104,10 @@ describe 'Pipeline', :js do
end
describe 'pipeline graph' do
+ before do
+ visit_pipeline
+ end
+
context 'when pipeline has running builds' do
it 'shows a running icon and a cancel action for the running build' do
page.within('#ci-badge-deploy') do
@@ -97,6 +126,24 @@ describe 'Pipeline', :js do
end
end
+ context 'when pipeline has preparing builds' do
+ it 'shows a preparing icon and a cancel action' do
+ page.within('#ci-badge-prepare') do
+ expect(page).to have_selector('.js-ci-status-icon-preparing')
+ expect(page).to have_selector('.js-icon-cancel')
+ expect(page).to have_content('prepare')
+ end
+ end
+
+ it 'cancels the preparing build and shows retry button' do
+ find('#ci-badge-deploy .ci-action-icon-container').click
+
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_css('.js-icon-retry')
+ end
+ end
+ end
+
context 'when pipeline has successful builds' do
it 'shows the success icon and a retry action for the successful build' do
page.within('#ci-badge-build') do
@@ -109,7 +156,7 @@ describe 'Pipeline', :js do
end
end
- it 'should be possible to retry the success job' do
+ it 'is possible to retry the success job' do
find('#ci-badge-build .ci-action-icon-container').click
expect(page).not_to have_content('Retry job')
@@ -149,13 +196,13 @@ describe 'Pipeline', :js do
end
end
- it 'should be possible to retry the failed build' do
+ it 'is possible to retry the failed build' do
find('#ci-badge-test .ci-action-icon-container').click
expect(page).not_to have_content('Retry job')
end
- it 'should include the failure reason' do
+ it 'includes the failure reason' do
page.within('#ci-badge-test') do
build_link = page.find('.js-pipeline-graph-job-link')
expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)')
@@ -175,7 +222,7 @@ describe 'Pipeline', :js do
end
end
- it 'should be possible to play the manual job' do
+ it 'is possible to play the manual job' do
find('#ci-badge-manual-build .ci-action-icon-container').click
expect(page).not_to have_content('Play job')
@@ -191,7 +238,25 @@ describe 'Pipeline', :js do
end
end
+ context 'when the pipeline has manual stage' do
+ before do
+ create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'CentOS')
+ create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'Debian')
+ create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'OpenSUDE')
+
+ visit_pipeline
+ end
+
+ it 'displays play all button' do
+ expect(page).to have_selector('.js-stage-action')
+ end
+ end
+
context 'page tabs' do
+ before do
+ visit_pipeline
+ end
+
it 'shows Pipeline, Jobs and Failed Jobs tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
@@ -218,6 +283,10 @@ describe 'Pipeline', :js do
end
context 'retrying jobs' do
+ before do
+ visit_pipeline
+ end
+
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
@@ -230,6 +299,10 @@ describe 'Pipeline', :js do
end
context 'canceling jobs' do
+ before do
+ visit_pipeline
+ end
+
it { expect(page).not_to have_selector('.ci-canceled') }
context 'when canceling' do
@@ -249,10 +322,161 @@ describe 'Pipeline', :js do
user: user)
end
+ before do
+ visit_pipeline
+ end
+
it 'does not render link to the pipeline ref' do
expect(page).not_to have_link(pipeline.ref)
expect(page).to have_content(pipeline.ref)
end
+
+ it 'does not render render raw HTML to the pipeline ref' do
+ page.within '.pipeline-info' do
+ expect(page).not_to have_content('<span class="ref-name"')
+ end
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source branch does not exist' do
+ before do
+ project.repository.rm_branch(user, merge_request.source_branch)
+ end
+
+ it 'does not link to the source branch commit path' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).not_to have_link(merge_request.source_branch)
+ expect(page).to have_content(merge_request.source_branch)
+ end
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: project.commit.id)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ before do
+ pipeline.update(user: user)
+ end
+
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+
+ context 'when target branch does not exist' do
+ before do
+ project.repository.rm_branch(user, merge_request.target_branch)
+ end
+
+ it 'does not link to the target branch commit path' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).not_to have_link(merge_request.target_branch)
+ expect(page).to have_content(merge_request.target_branch)
+ end
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+ end
end
end
@@ -280,7 +504,7 @@ describe 'Pipeline', :js do
expect(page).to have_content('Cancel running')
end
- it 'should not link to job' do
+ it 'does not link to job' do
expect(page).not_to have_selector('.js-pipeline-graph-job-link')
end
end
@@ -666,7 +890,7 @@ describe 'Pipeline', :js do
let(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: merge_request.source_project,
ref: 'feature',
sha: merge_request.diff_head_sha,
@@ -686,9 +910,9 @@ describe 'Pipeline', :js do
visit project_pipeline_path(project, pipeline)
end
- it 'contains badge that indicates merge request pipeline' do
+ it 'contains badge that indicates detached merge request pipeline' do
page.within(all('.well-segment')[1]) do
- expect(page).to have_content 'merge request'
+ expect(page).to have_content 'detached'
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index ffa165c5440..de780f13681 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Pipelines', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project) }
context 'when user is logged in' do
@@ -165,6 +167,99 @@ describe 'Pipelines', :js do
end
end
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'showing detached merge request pipeline information' do
+ it 'shows detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'showing detached merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'showing detached merge request pipeline information'
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: target_project.commit.sha)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'Correct merge request pipeline information' do
+ it 'does not show detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).not_to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'Correct merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'Correct merge request pipeline information'
+ end
+ end
+
context 'when pipeline has configuration errors' do
let(:pipeline) do
create(:ci_pipeline, :invalid, project: project)
@@ -282,6 +377,30 @@ describe 'Pipelines', :js do
end
context 'for generic statuses' do
+ context 'when preparing' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ status: 'preparing', project: project)
+ end
+
+ let!(:status) do
+ create(:generic_commit_status,
+ :preparing, pipeline: pipeline)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'is cancelable' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ end
+
+ it 'shows the pipeline as preparing' do
+ expect(page).to have_selector('.ci-preparing')
+ end
+ end
+
context 'when running' do
let!(:running) do
create(:generic_commit_status,
@@ -423,19 +542,19 @@ describe 'Pipelines', :js do
visit_project_pipelines
end
- it 'should render a mini pipeline graph' do
+ it 'renders a mini pipeline graph' do
expect(page).to have_selector('.js-mini-pipeline-graph')
expect(page).to have_selector('.js-builds-dropdown-button')
end
context 'when clicking a stage badge' do
- it 'should open a dropdown' do
+ it 'opens a dropdown' do
find('.js-builds-dropdown-button').click
expect(page).to have_link build.name
end
- it 'should be possible to cancel pending build' do
+ it 'is possible to cancel pending build' do
find('.js-builds-dropdown-button').click
find('.js-ci-action').click
wait_for_requests
@@ -451,7 +570,7 @@ describe 'Pipelines', :js do
name: 'build')
end
- it 'should display the failure reason' do
+ it 'displays the failure reason' do
find('.js-builds-dropdown-button').click
within('.js-builds-dropdown-list') do
@@ -468,24 +587,24 @@ describe 'Pipelines', :js do
create(:ci_empty_pipeline, project: project)
end
- it 'should render pagination' do
+ it 'renders pagination' do
visit project_pipelines_path(project)
wait_for_requests
expect(page).to have_selector('.gl-pagination')
end
- it 'should render second page of pipelines' do
+ it 'renders second page of pipelines' do
visit project_pipelines_path(project, page: '2')
wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
- it 'should show updated content' do
+ it 'shows updated content' do
visit project_pipelines_path(project)
wait_for_requests
- page.find('.js-next-button a').click
+ page.find('.js-next-button .page-link').click
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
@@ -566,7 +685,7 @@ describe 'Pipelines', :js do
end
it 'creates a new pipeline' do
- expect { click_on 'Create pipeline' }
+ expect { click_on 'Run Pipeline' }
.to change { Ci::Pipeline.count }.by(1)
expect(Ci::Pipeline.last).to be_web
@@ -579,7 +698,7 @@ describe 'Pipelines', :js do
fill_in "Input variable value", with: "value"
end
- expect { click_on 'Create pipeline' }
+ expect { click_on 'Run Pipeline' }
.to change { Ci::Pipeline.count }.by(1)
expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) })
@@ -590,7 +709,7 @@ describe 'Pipelines', :js do
context 'without gitlab-ci.yml' do
before do
- click_on 'Create pipeline'
+ click_on 'Run Pipeline'
end
it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
@@ -603,14 +722,14 @@ describe 'Pipelines', :js do
click_link 'master'
end
- expect { click_on 'Create pipeline' }
+ expect { click_on 'Run Pipeline' }
.to change { Ci::Pipeline.count }.by(1)
end
end
end
end
- describe 'Create pipelines' do
+ describe 'Run Pipelines' do
let(:project) { create(:project, :repository) }
before do
@@ -621,7 +740,7 @@ describe 'Pipelines', :js do
it 'has field to add a new pipeline' do
expect(page).to have_selector('.js-branch-select')
expect(find('.js-branch-select')).to have_content project.default_branch
- expect(page).to have_content('Create for')
+ expect(page).to have_content('Run for')
end
end
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
index aa71669de98..9865dbbfb3c 100644
--- a/spec/features/projects/serverless/functions_spec.rb
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe 'Functions', :js do
include KubernetesHelpers
+ include ReactiveCachingHelpers
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -13,44 +14,70 @@ describe 'Functions', :js do
gitlab_sign_in(user)
end
- context 'when user does not have a cluster and visits the serverless page' do
+ shared_examples "it's missing knative installation" do
before do
visit project_serverless_functions_path(project)
end
- it 'sees an empty state' do
+ it 'sees an empty state require Knative installation' do
expect(page).to have_link('Install Knative')
expect(page).to have_selector('.empty-state')
end
end
+ context 'when user does not have a cluster and visits the serverless page' do
+ it_behaves_like "it's missing knative installation"
+ end
+
context 'when the user does have a cluster and visits the serverless page' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- before do
- visit project_serverless_functions_path(project)
- end
-
- it 'sees an empty state' do
- expect(page).to have_link('Install Knative')
- expect(page).to have_selector('.empty-state')
- end
+ it_behaves_like "it's missing knative installation"
end
context 'when the user has a cluster and knative installed and visits the serverless page' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
- let(:project) { knative.cluster.project }
+ let(:project) { cluster.project }
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
before do
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ stub_kubeclient_knative_services(stub_get_services_options)
+ stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
visit project_serverless_functions_path(project)
end
- it 'sees an empty listing of serverless functions' do
- expect(page).to have_selector('.gl-responsive-table-row')
+ context 'when there are no functions' do
+ let(:stub_get_services_options) do
+ {
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ }
+ end
+
+ it 'sees an empty listing of serverless functions' do
+ expect(page).to have_selector('.empty-state')
+ expect(page).not_to have_selector('.content-list')
+ end
+ end
+
+ context 'when there are functions' do
+ let(:stub_get_services_options) { { namespace: namespace.namespace } }
+
+ it 'does not see an empty listing of serverless functions' do
+ expect(page).not_to have_selector('.empty-state')
+ expect(page).to have_selector('.content-list')
+ end
end
end
end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 65b597da269..1a13fe03a67 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -14,11 +14,10 @@ describe 'Disable individual triggers' do
end
context 'service has multiple supported events' do
- let(:service_name) { 'JIRA' }
+ let(:service_name) { 'HipChat' }
it 'shows trigger checkboxes' do
- event_count = JiraService.supported_events.count
- expect(event_count).to be > 1
+ event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb
new file mode 100644
index 00000000000..d6b69a5bd68
--- /dev/null
+++ b/spec/features/projects/services/user_activates_hipchat_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User activates HipChat' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(project_settings_integrations_path(project))
+
+ click_link('HipChat')
+ end
+
+ context 'with standart settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab')
+ fill_in('Token', with: 'verySecret')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+
+ context 'with custom settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab_custom')
+ fill_in('Token', with: 'secretCustom')
+ fill_in('Server', with: 'https://chat.example.com')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+end
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 7cd5b12802b..74b9a2b20cd 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -6,11 +6,17 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
- def fill_form(active = true)
+ def fill_short_form(active = true)
check 'Active' if active
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
+ end
+
+ def fill_full_form(active = true)
+ fill_short_form(active)
+ check 'Active' if active
+
fill_in 'service_new_issue_url', with: url
end
@@ -21,14 +27,20 @@ describe 'User activates issue tracker', :js do
visit project_settings_integrations_path(project)
end
- shared_examples 'external issue tracker activation' do |tracker:|
+ shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false|
describe 'user sets and activates the Service' do
context 'when the connection test succeeds' do
before do
stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
click_link(tracker)
- fill_form
+
+ if skip_new_issue_url
+ fill_short_form
+ else
+ fill_full_form
+ end
+
click_button('Test settings and save changes')
wait_for_requests
end
@@ -50,7 +62,13 @@ describe 'User activates issue tracker', :js do
stub_request(:head, url).to_raise(HTTParty::Error)
click_link(tracker)
- fill_form
+
+ if skip_new_issue_url
+ fill_short_form
+ else
+ fill_full_form
+ end
+
click_button('Test settings and save changes')
wait_for_requests
@@ -69,7 +87,13 @@ describe 'User activates issue tracker', :js do
describe 'user sets the service but keeps it disabled' do
before do
click_link(tracker)
- fill_form(false)
+
+ if skip_new_issue_url
+ fill_short_form(false)
+ else
+ fill_full_form(false)
+ end
+
click_button('Save changes')
end
@@ -87,6 +111,7 @@ describe 'User activates issue tracker', :js do
end
it_behaves_like 'external issue tracker activation', tracker: 'Redmine'
+ it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true
it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla'
it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker'
end
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
new file mode 100644
index 00000000000..bb6a030c1cf
--- /dev/null
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe 'User activates issue tracker', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ let(:url) { 'http://tracker.example.com' }
+
+ def fill_form(active = true)
+ check 'Active' if active
+
+ fill_in 'service_project_url', with: url
+ fill_in 'service_issues_url', with: "#{url}/:id"
+ end
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_settings_integrations_path(project)
+ end
+
+ shared_examples 'external issue tracker activation' do |tracker:|
+ describe 'user sets and activates the Service' do
+ context 'when the connection test succeeds' do
+ before do
+ stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
+
+ click_link(tracker)
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+ end
+
+ it 'activates the service' do
+ expect(page).to have_content("#{tracker} activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+
+ it 'shows the link in the menu' do
+ page.within('.nav-sidebar') do
+ expect(page).to have_link(tracker, href: url)
+ end
+ end
+ end
+
+ context 'when the connection test fails' do
+ it 'activates the service' do
+ stub_request(:head, url).to_raise(HTTParty::Error)
+
+ click_link(tracker)
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(find('.flash-container-page')).to have_content 'Test failed.'
+ expect(find('.flash-container-page')).to have_content 'Save anyway'
+
+ find('.flash-alert .flash-action').click
+ wait_for_requests
+
+ expect(page).to have_content("#{tracker} activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+ end
+ end
+
+ describe 'user sets the service but keeps it disabled' do
+ before do
+ click_link(tracker)
+ fill_form(false)
+ click_button('Save changes')
+ end
+
+ it 'saves but does not activate the service' do
+ expect(page).to have_content("#{tracker} settings saved, but not activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+
+ it 'does not show the external tracker link in the menu' do
+ page.within('.nav-sidebar') do
+ expect(page).not_to have_link(tracker, href: url)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'external issue tracker activation', tracker: 'YouTrack'
+end
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index b0a838a7d2b..e9c8cf0fe34 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -14,6 +14,7 @@ describe 'User views services' do
it 'shows the list of available services' do
expect(page).to have_content('Project services')
expect(page).to have_content('Campfire')
+ expect(page).to have_content('HipChat')
expect(page).to have_content('Assembla')
expect(page).to have_content('Pushover')
expect(page).to have_content('Atlassian Bamboo')
@@ -21,7 +22,5 @@ describe 'User views services' do
expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)')
expect(page).to have_content('Packagist')
- expect(page).to have_content('Mattermost')
- expect(page).to have_content('Slack')
end
end
diff --git a/spec/features/projects/settings/external_authorization_service_settings_spec.rb b/spec/features/projects/settings/external_authorization_service_settings_spec.rb
new file mode 100644
index 00000000000..31b2892cf6f
--- /dev/null
+++ b/spec/features/projects/settings/external_authorization_service_settings_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Settings > External Authorization Classification Label setting' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project_empty_repo) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'shows the field to set a classification label' do
+ stub_application_setting(external_authorization_service_enabled: true)
+
+ visit edit_project_path(project)
+
+ expect(page).to have_selector('#project_external_authorization_classification_label')
+ end
+end
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index dc0278370aa..df33d215602 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -7,7 +7,6 @@ describe 'Projects > Settings > For a forked project', :js do
let(:forked_project) { fork_project(original_project, user) }
before do
- stub_feature_flags(approval_rules: false)
original_project.add_maintainer(user)
forked_project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index 06290c67c70..d96e243d96b 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -20,4 +20,87 @@ describe 'Projects > Settings > For a forked project', :js do
expect(page).to have_selector('a[title="Operations"]', visible: false)
end
end
+
+ describe 'Settings > Operations' do
+ context 'error tracking settings form' do
+ let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' }
+
+ context 'success path' do
+ let(:projects_sample_response) do
+ Gitlab::Utils.deep_indifferent_access(
+ JSON.parse(fixture_file('sentry/list_projects_sample_response.json'))
+ )
+ end
+
+ before do
+ WebMock.stub_request(:get, sentry_list_projects_url)
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: projects_sample_response.to_json
+ )
+ end
+
+ it 'successfully fills and submits the form' do
+ visit project_settings_operations_path(project)
+
+ wait_for_requests
+
+ within '.js-error-tracking-settings' do
+ click_button('Expand')
+ end
+ expect(page).to have_content('Sentry API URL')
+ expect(page.body).to include('Error Tracking')
+ expect(page).to have_button('Connect')
+
+ check('Active')
+ fill_in('error-tracking-api-host', with: 'http://sentry.example.com')
+ fill_in('error-tracking-token', with: 'token')
+
+ click_button('Connect')
+
+ within('div#project-dropdown') do
+ click_button('Select project')
+ click_button('Sentry | Internal')
+ end
+
+ click_button('Save changes')
+
+ wait_for_requests
+
+ assert_text('Your changes have been saved')
+ end
+ end
+
+ context 'project dropdown fails to load' do
+ before do
+ WebMock.stub_request(:get, sentry_list_projects_url)
+ .to_return(
+ status: 400,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ message: 'Sentry response code: 401'
+ }.to_json
+ )
+ end
+
+ it 'displays error message' do
+ visit project_settings_operations_path(project)
+
+ wait_for_requests
+
+ within '.js-error-tracking-settings' do
+ click_button('Expand')
+ end
+ check('Active')
+ fill_in('error-tracking-api-host', with: 'http://sentry.example.com')
+ fill_in('error-tracking-token', with: 'token')
+
+ click_button('Connect')
+
+ assert_text('Connection has failed. Re-check Auth Token and try again.')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index 4c85abe9971..bf0c0de89b2 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -110,6 +110,37 @@ describe "Projects > Settings > Pipelines settings" do
expect(page).not_to have_content('instance enabled')
end
end
+
+ context 'when auto devops is turned on group level' do
+ before do
+ project.update!(namespace: create(:group, :auto_devops_enabled))
+ end
+
+ it 'renders group enabled badge' do
+ visit project_settings_ci_cd_path(project)
+
+ page.within '#autodevops-settings' do
+ expect(page).to have_content('group enabled')
+ expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
+ end
+ end
+ end
+
+ context 'when auto devops is turned on group parent level', :nested_groups do
+ before do
+ group = create(:group, parent: create(:group, :auto_devops_enabled))
+ project.update!(namespace: group)
+ end
+
+ it 'renders group enabled badge' do
+ visit project_settings_ci_cd_path(project)
+
+ page.within '#autodevops-settings' do
+ expect(page).to have_content('group enabled')
+ expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
+ end
+ end
+ end
end
end
diff --git a/spec/features/projects/settings/project_settings_spec.rb b/spec/features/projects/settings/project_settings_spec.rb
new file mode 100644
index 00000000000..7afddc0e712
--- /dev/null
+++ b/spec/features/projects/settings/project_settings_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects settings' do
+ set(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:panel) { find('.general-settings', match: :first) }
+ let(:button) { panel.find('.btn.js-settings-toggle') }
+ let(:title) { panel.find('.settings-title') }
+
+ before do
+ sign_in(user)
+ visit edit_project_path(project)
+ end
+
+ it 'can toggle sections by clicking the title or button', :js do
+ expect_toggle_state(:expanded)
+
+ button.click
+
+ expect_toggle_state(:collapsed)
+
+ button.click
+
+ expect_toggle_state(:expanded)
+
+ title.click
+
+ expect_toggle_state(:collapsed)
+
+ title.click
+
+ expect_toggle_state(:expanded)
+ end
+
+ def expect_toggle_state(state)
+ is_collapsed = state == :collapsed
+
+ expect(button).to have_content(is_collapsed ? 'Expand' : 'Collapse')
+ expect(panel[:class]).send(is_collapsed ? 'not_to' : 'to', include('expanded'))
+ end
+end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 1259ad45791..8c7bc192c50 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -217,5 +217,36 @@ describe 'Projects > Settings > Repository settings' do
expect(RepositoryCleanupWorker.jobs.count).to eq(1)
end
end
+
+ context 'with an existing mirror', :js do
+ let(:mirrored_project) { create(:project, :repository, :remote_mirror) }
+
+ before do
+ mirrored_project.add_maintainer(user)
+
+ visit project_settings_repository_path(mirrored_project)
+ end
+
+ it 'delete remote mirrors' do
+ expect(mirrored_project.remote_mirrors.count).to eq(1)
+
+ find('.js-delete-mirror').click
+ wait_for_requests
+
+ expect(mirrored_project.remote_mirrors.count).to eq(0)
+ end
+ end
+
+ it 'shows a disabled mirror' do
+ create(:remote_mirror, project: project, enabled: false)
+
+ visit project_settings_repository_path(project)
+
+ mirror = find('.qa-mirrored-repository-row')
+
+ expect(mirror).to have_selector('.qa-delete-mirror')
+ expect(mirror).to have_selector('.qa-disabled-mirror-badge')
+ expect(mirror).not_to have_selector('.qa-update-now-button')
+ end
end
end
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index 676659b90c3..e5a58c44e41 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -10,6 +10,7 @@ describe 'Projects > Settings > User manages group links' do
before do
project.add_maintainer(user)
+ group_market.add_guest(user)
sign_in(user)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 84de6858d5f..0739726f52c 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require 'spec_helper'
describe 'Projects > Settings > User manages merge request settings' do
@@ -30,16 +31,16 @@ describe 'Projects > Settings > User manages merge request settings' do
context 'when Merge Request and Pipelines are initially enabled', :js do
context 'when Pipelines are initially enabled' do
it 'shows the Merge Requests settings' do
- 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')
+ expect(page).to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'All discussions must be resolved'
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
find('input[value="Save changes"]').send_keys(:return)
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')
+ expect(page).not_to have_content 'Pipelines must succeed'
+ expect(page).not_to have_content 'All discussions must be resolved'
end
end
@@ -50,16 +51,16 @@ describe 'Projects > Settings > User manages merge request settings' do
end
it 'shows the Merge Requests settings that do not depend on Builds feature' do
- expect(page).not_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')
+ expect(page).to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'All discussions must be resolved'
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
find('input[value="Save changes"]').send_keys(:return)
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')
+ expect(page).to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'All discussions must be resolved'
end
end
end
@@ -71,16 +72,16 @@ describe 'Projects > Settings > User manages merge request settings' do
end
it 'does not show the Merge Requests settings' do
- 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')
+ expect(page).not_to have_content 'Pipelines must succeed'
+ expect(page).not_to have_content 'All discussions must be resolved'
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
find('input[value="Save changes"]').send_keys(:return)
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')
+ expect(page).to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'All discussions must be resolved'
end
end
@@ -93,11 +94,13 @@ describe 'Projects > Settings > User manages merge request settings' do
it 'when unchecked sets :printing_merge_request_link_enabled to false' do
uncheck('project_printing_merge_request_link_enabled')
within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
click_on('Save changes')
end
- # Wait for save to complete and page to reload
+ find('.flash-notice')
checkbox = find_field('project_printing_merge_request_link_enabled')
+
expect(checkbox).not_to be_checked
project.reload
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
index 64c9af4b706..d3979b79910 100644
--- a/spec/features/projects/settings/user_renames_a_project_spec.rb
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -9,24 +9,33 @@ describe 'Projects > Settings > User renames a project' do
visit edit_project_path(project)
end
- def rename_project(project, name: nil, path: nil)
- fill_in('project_name', with: name) if name
- fill_in('Path', with: path) if path
- click_button('Rename project')
+ def change_path(project, path)
+ within('.advanced-settings') do
+ fill_in('Path', with: path)
+ click_button('Change path')
+ end
+ project.reload
wait_for_edit_project_page_reload
+ end
+
+ def change_name(project, name)
+ within('.general-settings') do
+ fill_in('Project name', with: name)
+ click_button('Save changes')
+ end
project.reload
+ wait_for_edit_project_page_reload
end
def wait_for_edit_project_page_reload
- expect(find('.project-edit-container')).to have_content('Rename repository')
+ expect(find('.advanced-settings')).to have_content('Change path')
end
context 'with invalid characters' do
- it 'shows errors for invalid project path/name' do
- rename_project(project, name: 'foo&bar', path: 'foo&bar')
- expect(page).to have_field 'Project name', with: 'foo&bar'
+ it 'shows errors for invalid project path' do
+ change_path(project, 'foo&bar')
+
expect(page).to have_field 'Path', 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_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
end
end
@@ -42,13 +51,13 @@ describe 'Projects > Settings > User renames a project' do
context 'when changing project name' do
it 'renames the repository' do
- rename_project(project, name: 'bar')
+ change_name(project, 'bar')
expect(find('.breadcrumbs')).to have_content(project.name)
end
context 'with emojis' do
it 'shows error for invalid project name' do
- rename_project(project, name: '🚀 foo bar â˜ï¸')
+ change_name(project, '🚀 foo bar â˜ï¸')
expect(page).to have_field 'Project name', with: '🚀 foo bar â˜ï¸'
expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
end
@@ -67,7 +76,7 @@ describe 'Projects > Settings > User renames a project' do
end
it 'the project is accessible via the new path' do
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit new_path
@@ -77,7 +86,7 @@ describe 'Projects > Settings > User renames a project' do
it 'the project is accessible via a redirect from the old path' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
@@ -88,7 +97,7 @@ describe 'Projects > Settings > User renames a project' do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index 3a2dcc5aa55..fee5f8001b0 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -35,11 +35,10 @@ describe 'Projects > Show > Download buttons' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
it 'download links have download attribute' do
- expect(page).to have_selector('a', text: 'Download')
page.all('a', text: 'Download').each do |link|
expect(link[:download]).to eq ''
end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index 88f3397608f..e9dd1dc0f66 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -20,6 +20,16 @@ describe 'Projects > Show > User manages notifications', :js do
click_notifications_button
expect(find('.update-notification.is-active')).to have_content('On mention')
+ expect(find('.notifications-icon use')[:'xlink:href']).to end_with('#notifications')
+ end
+
+ it 'changes the notification setting to disabled' do
+ click_notifications_button
+ click_link 'Disabled'
+
+ wait_for_requests
+
+ expect(find('.notifications-icon use')[:'xlink:href']).to end_with('#notifications-off')
end
context 'custom notification settings' do
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 24777788248..46586b891e7 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
project.add_developer(user)
sign_in(user)
end
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index ffa80235083..0d59ef4a727 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -13,7 +13,7 @@ describe 'Projects > Show > User sees Git instructions' do
it 'shows Git command line instructions' do
click_link 'Create empty repository'
- page.within '.empty_wrapper' do
+ page.within '.empty-wrapper' do
expect(page).to have_content('Command line instructions')
end
end
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index dcca1d388c7..58bd20d7551 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -20,18 +20,18 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
end
- it 'no Auto DevOps button if can not manage pipelines' do
- page.within('.project-buttons') do
- expect(page).not_to have_link('Enable Auto DevOps')
- expect(page).not_to have_link('Auto DevOps enabled')
- end
- end
-
- it '"Auto DevOps enabled" button not linked' do
+ it 'Project buttons are not visible' do
visit project_path(project)
page.within('.project-buttons') do
- expect(page).to have_text('Auto DevOps enabled')
+ expect(page).not_to have_link('New file')
+ expect(page).not_to have_link('Add README')
+ expect(page).not_to have_link('Add CHANGELOG')
+ expect(page).not_to have_link('Add CONTRIBUTING')
+ expect(page).not_to have_link('Enable Auto DevOps')
+ expect(page).not_to have_link('Auto DevOps enabled')
+ expect(page).not_to have_link('Add Kubernetes cluster')
+ expect(page).not_to have_link('Kubernetes configured')
end
end
end
@@ -61,46 +61,6 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).to have_link('Add license', href: presenter.add_license_path)
end
end
-
- describe 'Auto DevOps button' do
- context 'when Auto DevOps is enabled' do
- it '"Auto DevOps enabled" anchor linked to settings page' do
- visit project_path(project)
-
- page.within('.project-buttons') do
- expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
- end
- end
- end
-
- context 'when Auto DevOps is not enabled' do
- let(:project) { create(:project, :public, :empty_repo, auto_devops_attributes: { enabled: false }) }
-
- it '"Enable Auto DevOps" button linked to settings page' do
- page.within('.project-buttons') do
- expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
- end
- end
- end
- end
-
- describe 'Kubernetes cluster button' do
- it '"Add Kubernetes cluster" button linked to clusters page' do
- page.within('.project-buttons') do
- expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
- end
- end
-
- it '"Kubernetes cluster" anchor linked to cluster page' do
- cluster = create(:cluster, :provided_by_gcp, projects: [project])
-
- visit project_path(project)
-
- page.within('.project-buttons') do
- expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
- end
- end
- end
end
end
diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
index 9c1ef78b0ca..4e1e2f330ec 100644
--- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
@@ -23,14 +23,14 @@ describe 'Projects > Snippets > User comments on a snippet', :js do
expect(page).to have_content('Good snippet!')
end
- it 'should have autocomplete' do
+ it 'has autocomplete' do
find('#note_note').native.send_keys('')
fill_in 'note[note]', with: '@'
expect(page).to have_selector('.atwho-view')
end
- it 'should have zen mode' do
+ it 'has zen mode' do
find('.js-zen-enter').click
expect(page).to have_selector('.fullscreen')
end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index fbfd8cee7aa..4c8ec53836a 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -36,7 +36,7 @@ describe 'Download buttons in tags page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
- expect(page).to have_link "Download '#{build.name}'", href: href
+ expect(page).to have_link build.name, href: href
end
end
end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 45e81e1c040..3ccea2db705 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -8,6 +8,7 @@ describe 'Projects tree', :js do
let(:test_sha) { '7975be0116940bf2ad4321f79d02a55c5f7779aa' }
before do
+ stub_feature_flags(vue_file_list: false)
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index 8d7e2883b2a..c0932539131 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -54,4 +54,31 @@ describe 'User creates a project', :js do
expect(project.namespace).to eq(subgroup)
end
end
+
+ context 'in a group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'creates a new project' do
+ visit(new_project_path)
+
+ fill_in :project_name, with: 'a-new-project'
+ fill_in :project_path, with: 'a-new-project'
+
+ page.find('.js-select-namespace').click
+ page.find("div[role='option']", text: group.full_path).click
+
+ page.within('#content-body') do
+ click_button('Create project')
+ end
+
+ expect(page).to have_content("Project 'a-new-project' was successfully created")
+
+ project = Project.find_by(name: 'a-new-project')
+ expect(project.namespace).to eq(group)
+ end
+ end
end
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
index ee5734a9bf1..383e8824b7b 100644
--- a/spec/features/projects/user_sees_sidebar_spec.rb
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -4,6 +4,108 @@ describe 'Projects > User sees sidebar' do
let(:user) { create(:user) }
let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) }
+ # NOTE: See documented behaviour https://design.gitlab.com/regions/navigation#contextual-navigation
+ context 'on different viewports', :js do
+ include MobileHelpers
+
+ before do
+ sign_in(user)
+ end
+
+ shared_examples 'has a expanded nav sidebar' do
+ it 'has a expanded desktop nav-sidebar on load' do
+ expect(page).to have_content('Collapse sidebar')
+ expect(page).not_to have_selector('.sidebar-collapsed-desktop')
+ expect(page).not_to have_selector('.sidebar-expanded-mobile')
+ end
+
+ it 'can collapse the nav-sidebar' do
+ page.find('.nav-sidebar .js-toggle-sidebar').click
+ expect(page).to have_selector('.sidebar-collapsed-desktop')
+ expect(page).not_to have_content('Collapse sidebar')
+ expect(page).not_to have_selector('.sidebar-expanded-mobile')
+ end
+ end
+
+ shared_examples 'has a collapsed nav sidebar' do
+ it 'has a collapsed desktop nav-sidebar on load' do
+ expect(page).not_to have_content('Collapse sidebar')
+ expect(page).not_to have_selector('.sidebar-expanded-mobile')
+ end
+
+ it 'can expand the nav-sidebar' do
+ page.find('.nav-sidebar .js-toggle-sidebar').click
+ expect(page).to have_selector('.sidebar-expanded-mobile')
+ expect(page).to have_content('Collapse sidebar')
+ end
+ end
+
+ shared_examples 'has a mobile nav-sidebar' do
+ it 'has a hidden nav-sidebar on load' do
+ expect(page).not_to have_content('.mobile-nav-open')
+ expect(page).not_to have_selector('.sidebar-expanded-mobile')
+ end
+
+ it 'can expand the nav-sidebar' do
+ page.find('.toggle-mobile-nav').click
+ expect(page).to have_selector('.mobile-nav-open')
+ expect(page).to have_selector('.sidebar-expanded-mobile')
+ end
+ end
+
+ context 'with a extra small viewport' do
+ before do
+ resize_screen_xs
+ visit project_path(project)
+ expect(page).to have_selector('.nav-sidebar')
+ expect(page).to have_selector('.toggle-mobile-nav')
+ end
+
+ it_behaves_like 'has a mobile nav-sidebar'
+ end
+
+ context 'with a small size viewport' do
+ before do
+ resize_screen_sm
+ visit project_path(project)
+ expect(page).to have_selector('.nav-sidebar')
+ expect(page).to have_selector('.toggle-mobile-nav')
+ end
+
+ it_behaves_like 'has a mobile nav-sidebar'
+ end
+
+ context 'with medium size viewport' do
+ before do
+ resize_window(768, 800)
+ visit project_path(project)
+ expect(page).to have_selector('.nav-sidebar')
+ end
+
+ it_behaves_like 'has a collapsed nav sidebar'
+ end
+
+ context 'with viewport size 1199px' do
+ before do
+ resize_window(1199, 800)
+ visit project_path(project)
+ expect(page).to have_selector('.nav-sidebar')
+ end
+
+ it_behaves_like 'has a collapsed nav sidebar'
+ end
+
+ context 'with a extra large viewport' do
+ before do
+ resize_window(1200, 800)
+ visit project_path(project)
+ expect(page).to have_selector('.nav-sidebar')
+ end
+
+ it_behaves_like 'has a expanded nav sidebar'
+ end
+ end
+
context 'as owner' do
before do
sign_in(user)
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index b1a7f167977..aac095bfa6b 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -43,7 +43,7 @@ describe "User creates wiki page" do
expect(page).to have_content("Create Page")
end
- it "shows non-escaped link in the pages list", :js do
+ it "shows non-escaped link in the pages list", :js, :quarantine do
fill_in(:wiki_title, with: "one/two/three-test")
page.within(".wiki-form") do
@@ -136,12 +136,12 @@ describe "User creates wiki page" do
click_button("Create page")
end
- page.within ".wiki" do
+ page.within ".md" do
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
end
end
- it_behaves_like 'wiki file attachments'
+ it_behaves_like 'wiki file attachments', :quarantine
end
context "in a group namespace", :js do
@@ -151,7 +151,7 @@ describe "User creates wiki page" do
expect(page).to have_field("wiki[message]", with: "Create home")
end
- it "creates a page from the home page" do
+ it "creates a page from the home page", :quarantine do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "My awesome wiki!")
diff --git a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
new file mode 100644
index 00000000000..5c16d7783f0
--- /dev/null
+++ b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User views wiki pages' do
+ include WikiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
+
+ let!(:wiki_page1) do
+ create(:wiki_page, wiki: project.wiki, attrs: { title: '3 home', content: '3' })
+ end
+ let!(:wiki_page2) do
+ create(:wiki_page, wiki: project.wiki, attrs: { title: '1 home', content: '1' })
+ end
+ let!(:wiki_page3) do
+ create(:wiki_page, wiki: project.wiki, attrs: { title: '2 home', content: '2' })
+ end
+
+ let(:pages) do
+ page.find('.wiki-pages-list').all('li').map { |li| li.find('a') }
+ end
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ visit(project_wikis_pages_path(project))
+ end
+
+ context 'ordered by title' do
+ let(:pages_ordered_by_title) { [wiki_page2, wiki_page3, wiki_page1] }
+
+ context 'asc' do
+ it 'pages are displayed in direct order' do
+ pages.each.with_index do |page_title, index|
+ expect(page_title.text).to eq(pages_ordered_by_title[index].title)
+ end
+ end
+ end
+
+ context 'desc' do
+ before do
+ page.within('.wiki-sort-dropdown') do
+ page.find('.qa-reverse-sort').click
+ end
+ end
+
+ it 'pages are displayed in reversed order' do
+ pages.reverse_each.with_index do |page_title, index|
+ expect(page_title.text).to eq(pages_ordered_by_title[index].title)
+ end
+ end
+ end
+ end
+
+ context 'ordered by created_at' do
+ let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] }
+
+ before do
+ page.within('.wiki-sort-dropdown') do
+ click_button('Title')
+ click_link('Created date')
+ end
+ end
+
+ context 'asc' do
+ it 'pages are displayed in direct order' do
+ pages.each.with_index do |page_title, index|
+ expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
+ end
+ end
+ end
+
+ context 'desc' do
+ before do
+ page.within('.wiki-sort-dropdown') do
+ page.find('.qa-reverse-sort').click
+ end
+ end
+
+ it 'pages are displayed in reversed order' do
+ pages.reverse_each.with_index do |page_title, index|
+ expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index dbf0d427976..b5112758475 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project' do
@@ -5,7 +7,7 @@ describe 'Project' do
include MobileHelpers
before do
- stub_feature_flags(approval_rules: false)
+ stub_feature_flags(vue_file_list: false)
end
describe 'creating from template' do
@@ -373,6 +375,21 @@ describe 'Project' do
end
end
+ describe 'edit' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:path) { edit_project_path(project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ visit path
+ end
+
+ it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
+ { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ 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/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 0aff916ec83..0dbff5a2701 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Protected Branches', :js do
+ include ProtectedBranchHelpers
+
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) }
@@ -150,27 +152,11 @@ describe 'Protected Branches', :js do
end
describe "access control" do
- include_examples "protected branches > access control > CE"
- end
- end
-
- def set_protected_branch_name(branch_name)
- find(".js-protected-branch-select").click
- find(".dropdown-input-field").set(branch_name)
- click_on("Create wildcard #{branch_name}")
- end
-
- def set_defaults
- find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
- expect(first("li")).to have_content("Roles")
- find(:link, 'No one').click
- end
+ before do
+ stub_licensed_features(protected_refs_for_users: false)
+ end
- find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
- expect(first("li")).to have_content("Roles")
- find(:link, 'No one').click
+ include_examples "protected branches > access control > CE"
end
end
end
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index c8e92cd1c07..652542b1719 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Protected Tags', :js do
+ include ProtectedTagHelpers
+
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
@@ -8,13 +10,6 @@ describe 'Protected Tags', :js do
sign_in(user)
end
- def set_protected_tag_name(tag_name)
- find(".js-protected-tag-select").click
- find(".dropdown-input-field").set(tag_name)
- click_on("Create wildcard #{tag_name}")
- find('.protected-tags-dropdown .dropdown-menu', visible: false)
- end
-
describe "explicit protected tags" do
it "allows creating explicit protected tags" do
visit project_protected_tags_path(project)
@@ -92,6 +87,10 @@ describe 'Protected Tags', :js do
end
describe "access control" do
+ before do
+ stub_licensed_features(protected_refs_for_users: false)
+ end
+
include_examples "protected tags > access control > CE"
end
end
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
index b0923b451ee..9a049764dec 100644
--- a/spec/features/raven_js_spec.rb
+++ b/spec/features/raven_js_spec.rb
@@ -3,13 +3,13 @@ require 'spec_helper'
describe 'RavenJS' do
let(:raven_path) { '/raven.chunk.js' }
- it 'should not load raven if sentry is disabled' do
+ it 'does not load raven if sentry is disabled' do
visit new_user_session_path
expect(has_requested_raven).to eq(false)
end
- it 'should load raven if sentry is enabled' do
+ it 'loads raven if sentry is enabled' do
stub_application_setting(clientside_sentry_dsn: 'https://key@domain.com/id', clientside_sentry_enabled: true)
visit new_user_session_path
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
new file mode 100644
index 00000000000..3725143291d
--- /dev/null
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe 'User searches for users' do
+ context 'when on the dashboard' do
+ it 'finds the user' do
+ create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+
+ sign_in(create(:user))
+
+ visit dashboard_projects_path
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Users 1')
+
+ click_on('Users 1')
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+ end
+ end
+
+ context 'when on the project page' do
+ it 'finds the user belonging to the project' do
+ project = create(:project)
+
+ user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ create(:project_member, :developer, user: user1, project: project)
+
+ user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
+ create(:project_member, :developer, user: user2, project: project)
+
+ create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
+
+ sign_in(user1)
+
+ visit projects_path(project)
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
+
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
+ end
+
+ context 'when on the group page' do
+ it 'finds the user belonging to the group' do
+ group = create(:group)
+
+ user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
+ create(:group_member, :developer, user: user2, group: group)
+
+ create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
+
+ sign_in(user1)
+
+ visit group_path(group)
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
+
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 7225ca65492..6d4facd0649 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -14,22 +14,36 @@ describe 'User searches for wiki pages', :js do
include_examples 'top right search form'
- it 'finds a page' do
- find('.js-search-project-dropdown').click
+ shared_examples 'search wiki blobs' do
+ it 'finds a page' do
+ find('.js-search-project-dropdown').click
- page.within('.project-filter') do
- click_link(project.full_name)
- end
+ page.within('.project-filter') do
+ click_link(project.full_name)
+ end
+
+ fill_in('dashboard_search', with: 'content')
+ find('.btn-search').click
+
+ page.within('.search-filter') do
+ click_link('Wiki')
+ end
- fill_in('dashboard_search', with: 'content')
- find('.btn-search').click
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(wiki_page.title, href: project_wiki_path(project, wiki_page.slug))
+ end
+ end
+ end
- page.within('.search-filter') do
- click_link('Wiki')
+ context 'when searching by content' do
+ it_behaves_like 'search wiki blobs' do
+ let(:search_term) { 'content' }
end
+ end
- page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(wiki_page.title)
+ context 'when searching by title' do
+ it_behaves_like 'search wiki blobs' do
+ let(:search_term) { 'test_wiki' }
end
end
end
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 444de26733f..1cc47cd6bd1 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -36,7 +36,7 @@ describe 'User uses header search field' do
end
context 'when clicking merge requests' do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
+ let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
@@ -100,7 +100,7 @@ describe 'User uses header search field' do
end
context 'when clicking merge requests' do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
+ let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
find('.dropdown-menu').click_link('Merge requests assigned to me')
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 4705cd12d23..a776169a8e5 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -93,4 +93,25 @@ describe 'Private Group access' do
it { is_expected.to be_denied_for(:visitor) }
it { is_expected.to be_denied_for(:external) }
end
+
+ describe 'GET /groups/:path for shared projects' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ subject { group_path(group) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(group) }
+ it { is_expected.to be_allowed_for(:maintainer).of(group) }
+ it { is_expected.to be_allowed_for(:developer).of(group) }
+ it { is_expected.to be_allowed_for(:reporter).of(group) }
+ it { is_expected.to be_allowed_for(:guest).of(group) }
+ it { is_expected.to be_denied_for(project_guest) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:external) }
+ it { is_expected.to be_denied_for(:visitor) }
+ end
end
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index a198e65046f..044a47567be 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Profile access" do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index eeacaf5f72a..78e0a43ce6d 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -70,7 +70,7 @@ describe 'Comments on personal snippets', :js do
fill_in 'note[note]', with: 'This is **awesome**!'
find('.js-md-preview-button').click
- page.within('.new-note .md-preview') do
+ page.within('.new-note .md-preview-holder') do
expect(page).to have_content('This is awesome!')
expect(page).to have_selector('strong')
end
@@ -83,7 +83,7 @@ describe 'Comments on personal snippets', :js do
expect(find('div#notes')).to have_content('This is awesome!')
end
- it 'should not have autocomplete' do
+ it 'does not have autocomplete' do
wait_for_requests
find('#note_note').native.send_keys('')
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 879c46d7c4e..1c97d5ec5b4 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -37,7 +37,7 @@ describe 'User creates snippet', :js do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
find('.js-md-preview-button').click
- page.within('#new_personal_snippet .md-preview') do
+ page.within('#new_personal_snippet .md-preview-holder') do
expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 8d567e925ef..bdbbe645779 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -37,7 +37,7 @@ describe 'Maintainer deletes tag' do
context 'when pre-receive hook fails', :js do
before do
allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag)
- .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags')
+ .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: Do not delete tags')
end
it 'shows the error message' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 6fe840dccf6..33d9c10f5e8 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -79,7 +79,7 @@ describe 'Task Lists' do
visit_issue(project, issue)
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
end
@@ -87,14 +87,14 @@ describe 'Task Lists' do
visit_issue(project, issue)
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
logout(:user)
login_as(user2)
visit current_path
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
end
it 'provides a summary on Issues#index' do
@@ -231,7 +231,7 @@ describe 'Task Lists' do
container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
- expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .md .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index ae9b65d1a39..ea02f36d9d0 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -246,26 +246,6 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
end
end
end
-
- describe "when two-factor authentication is disabled" do
- let(:user) { create(:user) }
-
- before do
- user = gitlab_sign_in(:user)
- user.update_attribute(:otp_required_for_login, true)
- visit profile_account_path
- manage_two_factor_authentication
- expect(page).to have_content("Your U2F device needs to be set up.")
- register_u2f_device
- end
-
- it "deletes u2f registrations" do
- visit profile_two_factor_auth_path
- expect do
- accept_confirm { click_on "Disable" }
- end.to change { U2fRegistration.count }.by(-1)
- end
- end
end
describe 'fallback code authentication' do
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment.rb
new file mode 100644
index 00000000000..f1e07e55799
--- /dev/null
+++ b/spec/features/user_opens_link_to_comment.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User opens link to comment', :js do
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ context 'authenticated user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'switches to all activity and does not show error message' do
+ create(:user_preference, user: user, issue_notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+
+ context 'anonymous user' do
+ it 'does not show error message' do
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+end
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index 3b48ea4786d..d2cdade88d1 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -17,12 +17,14 @@ describe 'Merge request > User sees revert modal', :js do
end
it 'shows the revert modal' do
- expect(page).to have_content('Revert this merge request')
+ page.within('.modal-header') do
+ expect(page).to have_content 'Revert this merge request'
+ end
end
it 'closes the revert modal with escape keypress' do
find('#modal-revert-commit').send_keys(:escape)
- expect(page).not_to have_content('Revert this merge request')
+ expect(page).not_to have_selector('#modal-revert-commit', visible: true)
end
end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index ad856bd062e..efba303033b 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -137,7 +137,7 @@ describe 'Login' do
enter_code(user.current_otp)
- expect(page).not_to have_content('You are already signed in.')
+ expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
context 'using one-time code' do
@@ -317,7 +317,17 @@ describe 'Login' do
gitlab_sign_in(user)
expect(current_path).to eq root_path
- expect(page).not_to have_content('You are already signed in.')
+ expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
+ end
+
+ it 'does not show already signed in message when opening sign in page after login' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+ visit new_user_session_path
+
+ expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
end
@@ -434,16 +444,22 @@ describe 'Login' do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
-
- gitlab_sign_in(user)
-
- expect(current_path).to eq profile_two_factor_auth_path
- expect(page).to have_content(
- 'The group settings for Group 1 and Group 2 require you to enable ' \
- 'Two-Factor Authentication for your account. You need to do this ' \
- 'before ')
+ Timecop.freeze do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq profile_two_factor_auth_path
+ expect(page).to have_content(
+ 'The group settings for Group 1 and Group 2 require you to enable '\
+ 'Two-Factor Authentication for your account. '\
+ 'You can leave Group 1 and leave Group 2. '\
+ 'You need to do this '\
+ 'before '\
+ "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}"
+ )
+ end
end
it 'allows skipping two-factor configuration', :js do
@@ -500,7 +516,8 @@ describe 'Login' do
expect(current_path).to eq profile_two_factor_auth_path
expect(page).to have_content(
'The group settings for Group 1 and Group 2 require you to enable ' \
- 'Two-Factor Authentication for your account.'
+ 'Two-Factor Authentication for your account. '\
+ 'You can leave Group 1 and leave Group 2.'
)
end
end
@@ -572,7 +589,7 @@ describe 'Login' do
click_button 'Accept terms'
expect(current_path).to eq(root_path)
- expect(page).not_to have_content('You are already signed in.')
+ expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
it 'does not ask for terms when the user already accepted them' do
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index 3db9ae7a951..bfa85696e19 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -93,7 +93,7 @@ describe 'Overview tab on a user profile', :js do
describe 'user has no personal projects' do
include_context 'visit overview tab'
- it 'it shows an empty project list with an info message' do
+ it 'shows an empty project list with an info message' do
page.within('.projects-block') do
expect(page).to have_selector('.loading', visible: false)
expect(page).to have_content('You haven\'t created any personal projects.')
@@ -113,7 +113,7 @@ describe 'Overview tab on a user profile', :js do
include_context 'visit overview tab'
- it 'it shows one entry in the list of projects' do
+ it 'shows one entry in the list of projects' do
page.within('.projects-block') do
expect(page).to have_selector('.project-row', count: 1)
end
@@ -139,7 +139,7 @@ describe 'Overview tab on a user profile', :js do
include_context 'visit overview tab'
- it 'it shows max. ten entries in the list of projects' do
+ it 'shows max. ten entries in the list of projects' do
page.within('.projects-block') do
expect(page).to have_selector('.project-row', count: 10)
end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index 86379164cf0..351750c0179 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'User page' do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
context 'with public profile' do
@@ -86,4 +88,24 @@ describe 'User page' do
end
end
end
+
+ context 'most recent activity' do
+ it 'shows the most recent activity' do
+ visit(user_path(user))
+
+ expect(page).to have_content('Most Recent Activity')
+ end
+
+ context 'when external authorization is enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'hides the most recent activity' do
+ visit(user_path(user))
+
+ expect(page).not_to have_content('Most Recent Activity')
+ end
+ end
+ end
end
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 957c3cfc583..1a9caf0ffbb 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -25,6 +25,13 @@ describe 'Signup' do
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
+ it 'does not show an error border if the username length is not longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 255
+ wait_for_requests
+
+ expect(find('.username')).not_to have_css '.gl-field-error-outline'
+ end
+
it 'shows an error border if the username already exists' do
existing_user = create(:user)
@@ -41,6 +48,20 @@ describe 'Signup' do
expect(find('.username')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error border if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(find('.username')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error message if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(page).to have_content("Username is too long (maximum is 255 characters).")
+ end
+
it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests
@@ -67,14 +88,35 @@ describe 'Signup' do
before do
visit root_path
click_link 'Register'
- simulate_input('#new_user_name', 'Ehsan 🦋')
+ end
+
+ it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do
+ fill_in 'new_user_name', with: 'u' * 128
+
+ expect(find('.name')).not_to have_css '.gl-field-error-outline'
end
it 'shows an error border if the user\'s fullname contains an emoji' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
+ expect(find('.name')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error border if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
expect(find('.name')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error message if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
+ expect(page).to have_content("Name is too long (maximum is 128 characters).")
+ end
+
it 'shows an error message if the username contains emojis' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
expect(page).to have_content("Invalid input, please avoid emojis")
end
end
diff --git a/spec/finders/admin/runners_finder_spec.rb b/spec/finders/admin/runners_finder_spec.rb
index 0b2325cc7ca..94ccb398801 100644
--- a/spec/finders/admin/runners_finder_spec.rb
+++ b/spec/finders/admin/runners_finder_spec.rb
@@ -37,6 +37,14 @@ describe Admin::RunnersFinder do
end
end
+ context 'filter by tag_name' do
+ it 'calls the corresponding scope on Ci::Runner' do
+ expect(Ci::Runner).to receive(:tagged_with).with(%w[tag1 tag2]).and_call_original
+
+ described_class.new(params: { tag_name: %w[tag1 tag2] }).execute
+ end
+ end
+
context 'sort' do
context 'without sort param' do
it 'sorts by created_at' do
diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
new file mode 100644
index 00000000000..79d2f9cdb45
--- /dev/null
+++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Autocomplete::ActsAsTaggableOn::TagsFinder do
+ describe '#execute' do
+ context 'with empty params' do
+ it 'returns all tags' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: {}).execute
+
+ expect(tags).to match_array [tag1, tag2]
+ end
+ end
+
+ context 'filter by search' do
+ context 'with an empty search term' do
+ it 'returns an empty collection' do
+ ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: { search: '' }).execute
+
+ expect(tags).to be_empty
+ end
+ end
+
+ context 'with a search containing 2 characters' do
+ it 'returns the tag that strictly matches the search term' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 't1')
+ ActsAsTaggableOn::Tag.create!(name: 't11')
+
+ tags = described_class.new(params: { search: 't1' }).execute
+
+ expect(tags).to match_array [tag1]
+ end
+ end
+
+ context 'with a search containing 3 characters' do
+ it 'returns the tag that partially matches the search term' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag11')
+
+ tags = described_class.new(params: { search: 'ag1' }).execute
+
+ expect(tags).to match_array [tag1, tag2]
+ end
+ end
+ end
+
+ context 'limit' do
+ it 'limits the result set by the limit constant' do
+ stub_const("#{described_class}::LIMIT", 1)
+
+ ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: { search: 'tag' }).execute
+
+ expect(tags.count).to eq 1
+ end
+ end
+ end
+end
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index abd0d6b5185..bcde115b1a6 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -26,9 +26,17 @@ describe Autocomplete::UsersFinder do
it { is_expected.to match_array([project.owner]) }
context 'when author_id passed' do
- let(:params) { { author_id: user2.id } }
+ context 'and author is active' do
+ let(:params) { { author_id: user1.id } }
- it { is_expected.to match_array([project.owner, user2]) }
+ it { is_expected.to match_array([project.owner, user1]) }
+ end
+
+ context 'and author is blocked' do
+ let(:params) { { author_id: user2.id } }
+
+ it { is_expected.to match_array([project.owner]) }
+ end
end
end
@@ -104,9 +112,9 @@ describe Autocomplete::UsersFinder do
end
context 'when filtered by author_id' do
- let(:params) { { author_id: user2.id } }
+ let(:params) { { author_id: user1.id } }
- it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) }
+ it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) }
end
end
end
diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb
index 332086c42e2..750042b6b54 100644
--- a/spec/finders/cluster_ancestors_finder_spec.rb
+++ b/spec/finders/cluster_ancestors_finder_spec.rb
@@ -8,11 +8,15 @@ describe ClusterAncestorsFinder, '#execute' do
let(:user) { create(:user) }
let!(:project_cluster) do
- create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project])
+ create(:cluster, :provided_by_user, :project, projects: [project])
end
let!(:group_cluster) do
- create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group])
+ create(:cluster, :provided_by_user, :group, groups: [group])
+ end
+
+ let!(:instance_cluster) do
+ create(:cluster, :provided_by_user, :instance)
end
subject { described_class.new(clusterable, user).execute }
@@ -25,7 +29,7 @@ describe ClusterAncestorsFinder, '#execute' do
end
it 'returns the project clusters followed by group clusters' do
- is_expected.to eq([project_cluster, group_cluster])
+ is_expected.to eq([project_cluster, group_cluster, instance_cluster])
end
context 'nested groups', :nested_groups do
@@ -33,11 +37,11 @@ describe ClusterAncestorsFinder, '#execute' do
let(:parent_group) { create(:group) }
let!(:parent_group_cluster) do
- create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
+ create(:cluster, :provided_by_user, :group, groups: [parent_group])
end
it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do
- is_expected.to eq([project_cluster, group_cluster, parent_group_cluster])
+ is_expected.to eq([project_cluster, group_cluster, parent_group_cluster, instance_cluster])
end
end
end
@@ -58,7 +62,7 @@ describe ClusterAncestorsFinder, '#execute' do
end
it 'returns the list of group clusters' do
- is_expected.to eq([group_cluster])
+ is_expected.to eq([group_cluster, instance_cluster])
end
context 'nested groups', :nested_groups do
@@ -66,12 +70,21 @@ describe ClusterAncestorsFinder, '#execute' do
let(:parent_group) { create(:group) }
let!(:parent_group_cluster) do
- create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
+ create(:cluster, :provided_by_user, :group, groups: [parent_group])
end
it 'returns the list of group clusters ordered ascending the hierarchy' do
- is_expected.to eq([group_cluster, parent_group_cluster])
+ is_expected.to eq([group_cluster, parent_group_cluster, instance_cluster])
end
end
end
+
+ context 'for an instance' do
+ let(:clusterable) { Clusters::Instance.new }
+ let(:user) { create(:admin) }
+
+ it 'returns the list of instance clusters' do
+ is_expected.to eq([instance_cluster])
+ end
+ end
end
diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb
new file mode 100644
index 00000000000..b731c2bd6bf
--- /dev/null
+++ b/spec/finders/clusters/knative_services_finder_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::KnativeServicesFinder do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:service) { cluster.platform_kubernetes }
+ let(:project) { cluster.cluster_project.project }
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: project)
+ end
+
+ before do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ stub_kubeclient_service_pods(
+ kube_response(
+ kube_knative_pods_body(
+ project.name, namespace.namespace
+ )
+ ),
+ namespace: namespace.namespace
+ )
+ end
+
+ shared_examples 'a cached data' do
+ it 'has an unintialized cache' do
+ is_expected.to be_blank
+ end
+
+ context 'when using synchronous reactive cache' do
+ before do
+ synchronous_reactive_cache(cluster.knative_services_finder(project))
+ end
+
+ context 'when there are functions for cluster namespace' do
+ it { is_expected.not_to be_blank }
+ end
+
+ context 'when there are no functions for cluster namespace' do
+ before do
+ stub_kubeclient_knative_services(
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ )
+ stub_kubeclient_service_pods(
+ kube_response({ "kind" => "PodList", "items" => [] }),
+ namespace: namespace.namespace
+ )
+ end
+
+ it { is_expected.to be_blank }
+ end
+ end
+ end
+
+ describe '#service_pod_details' do
+ subject { cluster.knative_services_finder(project).service_pod_details(project.name) }
+
+ it_behaves_like 'a cached data'
+ end
+
+ describe '#services' do
+ subject { cluster.knative_services_finder(project).services }
+
+ it_behaves_like 'a cached data'
+ end
+
+ describe '#knative_detected' do
+ subject { cluster.knative_services_finder(project).knative_detected }
+ before do
+ synchronous_reactive_cache(cluster.knative_services_finder(project))
+ end
+
+ context 'when knative is installed' do
+ before do
+ stub_kubeclient_discover(service.api_url)
+ end
+
+ it { is_expected.to be_truthy }
+ it "discovers knative installation" do
+ expect { subject }
+ .to change { cluster.kubeclient.knative_client.discovered }
+ .from(false)
+ .to(true)
+ end
+ end
+
+ context 'when knative is not installed' do
+ before do
+ stub_kubeclient_discover_knative_not_found(service.api_url)
+ end
+
+ it { is_expected.to be_falsy }
+ it "does not discover knative installation" do
+ expect { subject }.not_to change { cluster.kubeclient.knative_client.discovered }
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index d6d95906f5e..f8fcc2d0e40 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -1,26 +1,7 @@
require 'spec_helper'
describe GroupProjectsFinder do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:current_user) { create(:user) }
- let(:options) { {} }
-
- let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
-
- let!(:public_project) { create(:project, :public, group: group, path: '1') }
- let!(:private_project) { create(:project, :private, group: group, path: '2') }
- let!(:shared_project_1) { create(:project, :public, path: '3') }
- let!(:shared_project_2) { create(:project, :private, path: '4') }
- let!(:shared_project_3) { create(:project, :internal, path: '5') }
- let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
- let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
-
- before do
- shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- end
+ include_context 'GroupProjectsFinder context'
subject { finder.execute }
@@ -144,6 +125,24 @@ describe GroupProjectsFinder do
end
end
+ describe 'with an admin current user' do
+ let(:current_user) { create(:admin) }
+
+ context "only shared" do
+ let(:options) { { only_shared: true } }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ end
+
+ context "only owned" do
+ let(:options) { { only_owned: true } }
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+
+ context "all" do
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
+ end
+
describe "no user" do
context "only shared" do
let(:options) { { only_shared: true } }
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 47e2548c3d6..89fdaceaa9f 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,45 +1,10 @@
require 'spec_helper'
describe IssuesFinder do
- set(:user) { create(:user) }
- set(:user2) { create(:user) }
- set(:group) { create(:group) }
- set(:subgroup) { create(:group, parent: group) }
- set(:project1) { create(:project, group: group) }
- set(:project2) { create(:project) }
- set(:project3) { create(:project, group: subgroup) }
- set(:milestone) { create(:milestone, project: project1) }
- set(:label) { create(:label, project: project2) }
- set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
- set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
- set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
- set(:issue4) { create(:issue, project: project3) }
- set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
- set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
- set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
+ include_context 'IssuesFinder context'
describe '#execute' do
- let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
- let!(:label_link) { create(:label_link, label: label, target: issue2) }
- let(:search_user) { user }
- let(:params) { {} }
- let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
-
- before(:context) do
- project1.add_maintainer(user)
- project2.add_developer(user)
- project2.add_developer(user2)
- project3.add_developer(user)
-
- issue1
- issue2
- issue3
- issue4
-
- award_emoji1
- award_emoji2
- award_emoji3
- end
+ include_context 'IssuesFinder#execute context'
context 'scope: all' do
let(:scope) { 'all' }
@@ -48,45 +13,32 @@ describe IssuesFinder do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
- context 'filtering by assignee ID' do
- let(:params) { { assignee_id: user.id } }
-
- it 'returns issues assigned to that user' do
- expect(issues).to contain_exactly(issue1, issue2)
- end
- end
-
- context 'filtering by no assignee' do
- let(:params) { { assignee_id: 'None' } }
+ context 'assignee filtering' do
+ let(:issuables) { issues }
- it 'returns issues not assigned to any assignee' do
- expect(issues).to contain_exactly(issue4)
+ it_behaves_like 'assignee ID filter' do
+ let(:params) { { assignee_id: user.id } }
+ let(:expected_issuables) { [issue1, issue2] }
end
- it 'returns issues not assigned to any assignee' do
- params[:assignee_id] = 0
-
- expect(issues).to contain_exactly(issue4)
- end
-
- it 'returns issues not assigned to any assignee' do
- params[:assignee_id] = 'none'
+ it_behaves_like 'assignee username filter' do
+ before do
+ project2.add_developer(user3)
+ issue3.assignees = [user2, user3]
+ end
- expect(issues).to contain_exactly(issue4)
+ set(:user3) { create(:user) }
+ let(:params) { { assignee_username: [user2.username, user3.username] } }
+ let(:expected_issuables) { [issue3] }
end
- end
-
- context 'filtering by any assignee' do
- let(:params) { { assignee_id: 'Any' } }
- it 'returns issues assigned to any assignee' do
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ it_behaves_like 'no assignee filter' do
+ set(:user3) { create(:user) }
+ let(:expected_issuables) { [issue4] }
end
- it 'returns issues assigned to any assignee' do
- params[:assignee_id] = 'any'
-
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ it_behaves_like 'any assignee filter' do
+ let(:expected_issuables) { [issue1, issue2, issue3] }
end
end
@@ -220,6 +172,7 @@ describe IssuesFinder do
let(:yesterday) { Date.today - 1.day }
let(:tomorrow) { Date.today + 1.day }
let(:two_days_ago) { Date.today - 2.days }
+ let(:three_days_ago) { Date.today - 3.days }
let(:milestones) do
[
@@ -227,6 +180,8 @@ describe IssuesFinder do
create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
+ create(:milestone, :closed, project: project_started_1_and_2, title: '4.0', start_date: three_days_ago),
+ create(:milestone, :closed, project: project_started_8, title: '6.0', start_date: three_days_ago),
create(:milestone, project: project_started_8, title: '7.0'),
create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
@@ -576,6 +531,13 @@ describe IssuesFinder do
expect(issues.count).to eq 0
end
end
+
+ context 'external authorization' do
+ it_behaves_like 'a finder with external authorization service' do
+ let!(:subject) { create(:issue, project: project) }
+ let(:project_params) { { project_id: project.id } }
+ end
+ end
end
describe '#row_count', :request_store do
@@ -640,6 +602,16 @@ describe IssuesFinder do
expect(subject).to include(public_issue, confidential_issue)
end
end
+
+ context 'for an admin' do
+ let(:admin_user) { create(:user, :admin) }
+
+ subject { described_class.new(admin_user, params).with_confidentiality_access_check }
+
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
+ end
end
context 'when searching within a specific project' do
@@ -669,9 +641,7 @@ describe IssuesFinder do
end
it 'filters by confidentiality' do
- expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
-
- subject
+ expect(subject.to_sql).to match("issues.confidential")
end
end
@@ -688,9 +658,7 @@ describe IssuesFinder do
end
it 'filters by confidentiality' do
- expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything)
-
- subject
+ expect(subject.to_sql).to match("issues.confidential")
end
end
@@ -707,62 +675,21 @@ describe IssuesFinder do
subject
end
end
- end
- end
-
- describe '#use_subquery_for_search?' do
- let(:finder) { described_class.new(nil, params) }
-
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- stub_feature_flags(use_subquery_for_group_issues_search: true)
- end
-
- context 'when there is no search param' do
- let(:params) { { attempt_group_search_optimizations: true } }
-
- it 'returns false' do
- expect(finder.use_subquery_for_search?).to be_falsey
- end
- end
-
- context 'when the database is not Postgres' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
-
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns false' do
- expect(finder.use_subquery_for_search?).to be_falsey
- end
- end
-
- context 'when the attempt_group_search_optimizations param is falsey' do
- let(:params) { { search: 'foo' } }
-
- it 'returns false' do
- expect(finder.use_subquery_for_search?).to be_falsey
- end
- end
- context 'when the use_subquery_for_group_issues_search flag is disabled' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ context 'for an admin' do
+ let(:admin_user) { create(:user, :admin) }
- before do
- stub_feature_flags(use_subquery_for_group_issues_search: false)
- end
+ subject { described_class.new(admin_user, params).with_confidentiality_access_check }
- it 'returns false' do
- expect(finder.use_subquery_for_search?).to be_falsey
- end
- end
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
- context 'when all conditions are met' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ it 'does not filter by confidentiality' do
+ expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
- it 'returns true' do
- expect(finder.use_subquery_for_search?).to be_truthy
+ subject
+ end
end
end
end
@@ -772,8 +699,7 @@ describe IssuesFinder do
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- stub_feature_flags(use_cte_for_group_issues_search: true)
- stub_feature_flags(use_subquery_for_group_issues_search: false)
+ stub_feature_flags(attempt_group_search_optimizations: true)
end
context 'when there is no search param' do
@@ -796,7 +722,7 @@ describe IssuesFinder do
end
end
- context 'when the attempt_group_search_optimizations param is falsey' do
+ context 'when the force_cte param is falsey' do
let(:params) { { search: 'foo' } }
it 'returns false' do
@@ -804,11 +730,11 @@ describe IssuesFinder do
end
end
- context 'when the use_cte_for_group_issues_search flag is disabled' do
+ context 'when the attempt_group_search_optimizations flag is disabled' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
- stub_feature_flags(use_cte_for_group_issues_search: false)
+ stub_feature_flags(attempt_group_search_optimizations: false)
end
it 'returns false' do
@@ -816,15 +742,27 @@ describe IssuesFinder do
end
end
- context 'when use_subquery_for_search? is true' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ context 'when attempt_group_search_optimizations is unset and attempt_project_search_optimizations is set' do
+ let(:params) { { search: 'foo', attempt_project_search_optimizations: true } }
- before do
- stub_feature_flags(use_subquery_for_group_issues_search: true)
+ context 'and the corresponding feature flag is disabled' do
+ before do
+ stub_feature_flags(attempt_project_search_optimizations: false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
end
- it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
+ context 'and the corresponding feature flag is enabled' do
+ before do
+ stub_feature_flags(attempt_project_search_optimizations: true)
+ end
+
+ it 'returns true' do
+ expect(finder.use_cte_for_search?).to be_truthy
+ end
end
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 9abc52aa664..98b4933fef6 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -209,6 +209,12 @@ describe LabelsFinder do
expect(finder.execute).to eq [project_label_1]
end
+
+ it 'returns labels matching a single character' do
+ finder = described_class.new(user, search: '(')
+
+ expect(finder.execute).to eq [group_label_1]
+ end
end
context 'filter by subscription' do
@@ -220,5 +226,12 @@ describe LabelsFinder do
expect(finder.execute).to eq [project_label_1]
end
end
+
+ context 'external authorization' do
+ it_behaves_like 'a finder with external authorization service' do
+ let!(:subject) { create(:label, project: project) }
+ let(:project_params) { { project_id: project.id } }
+ end
+ end
end
end
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index db48f00cd74..83348457caa 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
describe MembersFinder, '#execute' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, :access_requestable, parent: group) }
- let(:project) { create(:project, namespace: nested_group) }
- let(:user1) { create(:user) }
- let(:user2) { create(:user) }
- let(:user3) { create(:user) }
- let(:user4) { create(:user) }
+ set(:group) { create(:group) }
+ set(:nested_group) { create(:group, :access_requestable, parent: group) }
+ set(:project) { create(:project, namespace: nested_group) }
+ set(:user1) { create(:user) }
+ set(:user2) { create(:user) }
+ set(:user3) { create(:user) }
+ set(:user4) { create(:user) }
it 'returns members for project and parent groups', :nested_groups do
nested_group.request_access(user1)
@@ -31,4 +31,34 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
+
+ context 'when include_invited_groups_members == true', :nested_groups do
+ subject { described_class.new(project, user2).execute(include_invited_groups_members: true) }
+
+ set(:linked_group) { create(:group, :public, :access_requestable) }
+ set(:nested_linked_group) { create(:group, parent: linked_group) }
+ set(:linked_group_member) { linked_group.add_developer(user1) }
+ set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
+
+ it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do
+ create(:project_group_link, project: project, group: nested_linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member)
+ end
+
+ it 'includes all the invited_groups members' do
+ create(:project_group_link, project: project, group: linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member)
+ end
+
+ it 'excludes group_members not visible to the user' do
+ create(:project_group_link, project: project, group: linked_group)
+ private_linked_group = create(:group, :private)
+ private_linked_group.add_developer(user3)
+ create(:project_group_link, project: project, group: private_linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member)
+ end
+ end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 107da08a0a9..da5e9dab058 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -1,287 +1,416 @@
require 'spec_helper'
describe MergeRequestsFinder do
- include ProjectForksHelper
-
- # We need to explicitly permit Gitaly N+1s because of the specs that use
- # :request_store. Gitaly N+1 detection is only enabled when :request_store is,
- # but we don't care about potential N+1s when we're just creating several
- # projects in the setup phase.
- def create_project_without_n_plus_1(*args)
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- create(:project, :public, *args)
- end
- end
+ context "multiple projects with merge requests" do
+ include_context 'MergeRequestsFinder multiple projects with merge requests context'
- let(:user) { create :user }
- let(:user2) { create :user }
+ describe '#execute' do
+ it 'filters by scope' do
+ params = { scope: 'authored', state: 'opened' }
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:project1) { create_project_without_n_plus_1(group: group) }
- let(:project2) do
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- fork_project(project1, user)
- end
- end
- let(:project3) do
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- p = fork_project(project1, user)
- p.update!(archived: true)
- p
- end
- end
- let(:project4) { create_project_without_n_plus_1(group: subgroup) }
- let(:project5) { create_project_without_n_plus_1(group: subgroup) }
- let(:project6) { create_project_without_n_plus_1(group: subgroup) }
-
- let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
- let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') }
- let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
- let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
- let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
- let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') }
- let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') }
- let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') }
- let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') }
-
- before do
- project1.add_maintainer(user)
- project2.add_developer(user)
- project3.add_developer(user)
- project2.add_developer(user2)
- project4.add_developer(user)
- project5.add_developer(user)
- project6.add_developer(user)
- end
+ merge_requests = described_class.new(user, params).execute
- describe "#execute" do
- it 'filters by scope' do
- params = { scope: 'authored', state: 'opened' }
- merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(7)
- end
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
- it 'filters by project' do
- params = { project_id: project1.id, scope: 'authored', state: 'opened' }
- merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(2)
- end
+ it 'filters by project' do
+ params = { project_id: project1.id, scope: 'authored', state: 'opened' }
- context 'filtering by group' do
- it 'includes all merge requests when user has access' do
- params = { group_id: group.id }
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1)
+ end
+
+ it 'filters by commit sha' do
+ merge_requests = described_class.new(
+ user,
+ commit_sha: merge_request5.merge_request_diff.last_commit_sha
+ ).execute
+
+ expect(merge_requests).to contain_exactly(merge_request5)
+ end
+
+ context 'filtering by group' do
+ it 'includes all merge requests when user has access excluding merge requests from projects the user does not have access to' do
+ private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) }
+ private_project.add_guest(user)
+ create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project)
+ params = { group_id: group.id }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
+ end
+
+ it 'filters by group including subgroups', :nested_groups do
+ params = { group_id: group.id, include_subgroups: true }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request5)
+ end
+ end
+
+ it 'filters by non_archived' do
+ params = { non_archived: true }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request5)
end
- it 'excludes merge requests from projects the user does not have access to' do
- private_project = create_project_without_n_plus_1(:private, group: group)
- private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project)
- params = { group_id: group.id }
+ it 'filters by iid' do
+ params = { project_id: project1.id, iids: merge_request1.iid }
- private_project.add_guest(user)
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
- expect(merge_requests).not_to include(private_mr)
+ expect(merge_requests).to contain_exactly(merge_request1)
end
- it 'filters by group including subgroups', :nested_groups do
- params = { group_id: group.id, include_subgroups: true }
+ it 'filters by source branch' do
+ params = { source_branch: merge_request2.source_branch }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(6)
+ expect(merge_requests).to contain_exactly(merge_request2)
end
- end
- it 'filters by non_archived' do
- params = { non_archived: true }
- merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(8)
- end
+ it 'filters by target branch' do
+ params = { target_branch: merge_request2.target_branch }
- it 'filters by iid' do
- params = { project_id: project1.id, iids: merge_request1.iid }
+ merge_requests = described_class.new(user, params).execute
- merge_requests = described_class.new(user, params).execute
+ expect(merge_requests).to contain_exactly(merge_request2)
+ end
- expect(merge_requests).to contain_exactly(merge_request1)
- end
+ it 'filters by source project id' do
+ params = { source_project_id: merge_request2.source_project_id }
- it 'filters by source branch' do
- params = { source_branch: merge_request2.source_branch }
+ merge_requests = described_class.new(user, params).execute
- merge_requests = described_class.new(user, params).execute
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
+ end
- expect(merge_requests).to contain_exactly(merge_request2)
- end
+ it 'filters by state' do
+ params = { state: 'locked' }
- it 'filters by target branch' do
- params = { target_branch: merge_request2.target_branch }
+ merge_requests = described_class.new(user, params).execute
- merge_requests = described_class.new(user, params).execute
+ expect(merge_requests).to contain_exactly(merge_request3)
+ end
- expect(merge_requests).to contain_exactly(merge_request2)
- end
+ describe 'WIP state' do
+ let!(:wip_merge_request1) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') }
+ let!(:wip_merge_request2) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') }
+ let!(:wip_merge_request3) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') }
+ let!(:wip_merge_request4) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') }
- it 'filters by state' do
- params = { state: 'locked' }
+ it 'filters by wip' do
+ params = { wip: 'yes' }
- merge_requests = described_class.new(user, params).execute
+ merge_requests = described_class.new(user, params).execute
- expect(merge_requests).to contain_exactly(merge_request3)
- end
+ expect(merge_requests).to contain_exactly(merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4)
+ end
- it 'filters by wip' do
- params = { wip: 'yes' }
+ it 'filters by not wip' do
+ params = { wip: 'no' }
- merge_requests = described_class.new(user, params).execute
+ merge_requests = described_class.new(user, params).execute
- expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
- end
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
+ end
- it 'filters by not wip' do
- params = { wip: 'no' }
+ it 'returns all items if no valid wip param exists' do
+ params = { wip: '' }
- merge_requests = described_class.new(user, params).execute
+ merge_requests = described_class.new(user, params).execute
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
- end
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4)
+ end
- it 'returns all items if no valid wip param exists' do
- params = { wip: '' }
+ it 'adds wip to scalar params' do
+ scalar_params = described_class.scalar_params
- merge_requests = described_class.new(user, params).execute
+ expect(scalar_params).to include(:wip, :assignee_id)
+ end
+ end
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
- end
+ context 'assignee filtering' do
+ let(:issuables) { described_class.new(user, params).execute }
- it 'adds wip to scalar params' do
- scalar_params = described_class.scalar_params
+ it_behaves_like 'assignee ID filter' do
+ let(:params) { { assignee_id: user.id } }
+ let(:expected_issuables) { [merge_request1, merge_request2] }
+ end
- expect(scalar_params).to include(:wip, :assignee_id)
- end
+ it_behaves_like 'assignee username filter' do
+ before do
+ project2.add_developer(user3)
+ merge_request3.assignees = [user2, user3]
+ end
- context 'filtering by group milestone' do
- let!(:group) { create(:group, :public) }
- let(:group_milestone) { create(:milestone, group: group) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- let(:params) { { milestone_title: group_milestone.title } }
+ set(:user3) { create(:user) }
+ let(:params) { { assignee_username: [user2.username, user3.username] } }
+ let(:expected_issuables) { [merge_request3] }
+ end
- before do
- project2.update(namespace: group)
- merge_request2.update(milestone: group_milestone)
- merge_request3.update(milestone: group_milestone)
- end
+ it_behaves_like 'no assignee filter' do
+ set(:user3) { create(:user) }
+ let(:expected_issuables) { [merge_request4, merge_request5] }
+ end
- it 'returns issues assigned to that group milestone' do
- merge_requests = described_class.new(user, params).execute
+ it_behaves_like 'any assignee filter' do
+ let(:expected_issuables) { [merge_request1, merge_request2, merge_request3] }
+ end
- expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
- end
- end
+ context 'filtering by group milestone' do
+ let(:group_milestone) { create(:milestone, group: group) }
- context 'filtering by created_at/updated_at' do
- let(:new_project) { create(:project, forked_from_project: project1) }
-
- let!(:new_merge_request) do
- create(:merge_request,
- :simple,
- author: user,
- created_at: 1.week.from_now,
- updated_at: 1.week.from_now,
- source_project: new_project,
- target_project: new_project)
- end
+ before do
+ project2.update(namespace: group)
+ merge_request2.update(milestone: group_milestone)
+ merge_request3.update(milestone: group_milestone)
+ end
- let!(:old_merge_request) do
- create(:merge_request,
- :simple,
- author: user,
- source_branch: 'feature_1',
- created_at: 1.week.ago,
- updated_at: 1.week.ago,
- source_project: new_project,
- target_project: new_project)
- end
+ it 'returns merge requests assigned to that group milestone' do
+ params = { milestone_title: group_milestone.title }
- before do
- new_project.add_maintainer(user)
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
+ end
+ end
end
- it 'filters by created_after' do
- params = { project_id: new_project.id, created_after: new_merge_request.created_at }
+ context 'filtering by created_at/updated_at' do
+ let(:new_project) { create(:project, forked_from_project: project1) }
- merge_requests = described_class.new(user, params).execute
+ let!(:new_merge_request) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ created_at: 1.week.from_now,
+ updated_at: 1.week.from_now,
+ source_project: new_project,
+ target_project: new_project)
+ end
- expect(merge_requests).to contain_exactly(new_merge_request)
- end
+ let!(:old_merge_request) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_branch: 'feature_1',
+ created_at: 1.week.ago,
+ updated_at: 1.week.ago,
+ source_project: new_project,
+ target_project: new_project)
+ end
- it 'filters by created_before' do
- params = { project_id: new_project.id, created_before: old_merge_request.created_at }
+ before do
+ new_project.add_maintainer(user)
+ end
- merge_requests = described_class.new(user, params).execute
+ it 'filters by created_after' do
+ params = { project_id: new_project.id, created_after: new_merge_request.created_at }
- expect(merge_requests).to contain_exactly(old_merge_request)
- end
+ merge_requests = described_class.new(user, params).execute
- it 'filters by created_after and created_before' do
- params = {
- project_id: new_project.id,
- created_after: old_merge_request.created_at,
- created_before: new_merge_request.created_at
- }
+ expect(merge_requests).to contain_exactly(new_merge_request)
+ end
- merge_requests = described_class.new(user, params).execute
+ it 'filters by created_before' do
+ params = { project_id: new_project.id, created_before: old_merge_request.created_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request)
+ end
+
+ it 'filters by created_after and created_before' do
+ params = {
+ project_id: new_project.id,
+ created_after: old_merge_request.created_at,
+ created_before: new_merge_request.created_at
+ }
- expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
+
+ it 'filters by updated_after' do
+ params = { project_id: new_project.id, updated_after: new_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(new_merge_request)
+ end
+
+ it 'filters by updated_before' do
+ params = { project_id: new_project.id, updated_before: old_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request)
+ end
+
+ it 'filters by updated_after and updated_before' do
+ params = {
+ project_id: new_project.id,
+ updated_after: old_merge_request.updated_at,
+ updated_before: new_merge_request.updated_at
+ }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
end
+ end
- it 'filters by updated_after' do
- params = { project_id: new_project.id, updated_after: new_merge_request.updated_at }
+ describe '#row_count', :request_store do
+ it 'returns the number of rows for the default state' do
+ finder = described_class.new(user)
- merge_requests = described_class.new(user, params).execute
+ expect(finder.row_count).to eq(3)
+ end
+
+ it 'returns the number of rows for a given state' do
+ finder = described_class.new(user, state: 'closed')
- expect(merge_requests).to contain_exactly(new_merge_request)
+ expect(finder.row_count).to eq(1)
end
+ end
- it 'filters by updated_before' do
- params = { project_id: new_project.id, updated_before: old_merge_request.updated_at }
+ context 'external authorization' do
+ it_behaves_like 'a finder with external authorization service' do
+ let!(:subject) { create(:merge_request, source_project: project) }
+ let(:project_params) { { project_id: project.id } }
+ end
+ end
+ end
- merge_requests = described_class.new(user, params).execute
+ context 'when projects require different access levels for merge requests' do
+ let(:user) { create(:user) }
+
+ let(:public_project) { create(:project, :public) }
+ let(:internal) { create(:project, :internal) }
+ let(:private_project) { create(:project, :private) }
+ let(:public_with_private_repo) { create(:project, :public, :repository, :repository_private) }
+ let(:internal_with_private_repo) { create(:project, :internal, :repository, :repository_private) }
+
+ let(:merge_requests) { described_class.new(user, {}).execute }
+
+ let!(:mr_public) { create(:merge_request, source_project: public_project) }
+ let!(:mr_private) { create(:merge_request, source_project: private_project) }
+ let!(:mr_internal) { create(:merge_request, source_project: internal) }
+ let!(:mr_private_repo_access) { create(:merge_request, source_project: public_with_private_repo) }
+ let!(:mr_internal_private_repo_access) { create(:merge_request, source_project: internal_with_private_repo) }
- expect(merge_requests).to contain_exactly(old_merge_request)
+ context 'with admin user' do
+ let(:user) { create(:user, :admin) }
+
+ it 'returns all merge requests' do
+ expect(merge_requests).to eq(
+ [mr_internal_private_repo_access, mr_private_repo_access, mr_internal, mr_private, mr_public]
+ )
end
+ end
- it 'filters by updated_after and updated_before' do
- params = {
- project_id: new_project.id,
- updated_after: old_merge_request.updated_at,
- updated_before: new_merge_request.updated_at
- }
+ context 'when project restricts merge requests' do
+ let(:non_member) { create(:user) }
+ let(:project) { create(:project, :repository, :public, :merge_requests_private) }
+ let!(:merge_request) { create(:merge_request, source_project: project) }
- merge_requests = described_class.new(user, params).execute
+ it "returns nothing to to non members" do
+ merge_requests = described_class.new(
+ non_member,
+ project_id: project.id
+ ).execute
- expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ expect(merge_requests).to be_empty
end
end
- end
- describe '#row_count', :request_store do
- it 'returns the number of rows for the default state' do
- finder = described_class.new(user)
+ context 'with external user' do
+ let(:user) { create(:user, :external) }
- expect(finder.row_count).to eq(7)
+ it 'returns only public merge requests' do
+ expect(merge_requests).to eq([mr_public])
+ end
end
- it 'returns the number of rows for a given state' do
- finder = described_class.new(user, state: 'closed')
+ context 'with authenticated user' do
+ it 'returns public and internal merge requests' do
+ expect(merge_requests).to eq([mr_internal, mr_public])
+ end
- expect(finder.row_count).to eq(1)
+ context 'being added to the private project' do
+ context 'as a guest' do
+ before do
+ private_project.add_guest(user)
+ end
+
+ it 'does not return merge requests from the private project' do
+ expect(merge_requests).to eq([mr_internal, mr_public])
+ end
+ end
+
+ context 'as a developer' do
+ before do
+ private_project.add_developer(user)
+ end
+
+ it 'returns merge requests from the private project' do
+ expect(merge_requests).to eq([mr_internal, mr_private, mr_public])
+ end
+ end
+ end
+
+ context 'being added to the public project with private repo access' do
+ context 'as a guest' do
+ before do
+ public_with_private_repo.add_guest(user)
+ end
+
+ it 'returns merge requests from the project' do
+ expect(merge_requests).to eq([mr_internal, mr_public])
+ end
+ end
+
+ context 'as a reporter' do
+ before do
+ public_with_private_repo.add_reporter(user)
+ end
+
+ it 'returns merge requests from the project' do
+ expect(merge_requests).to eq([mr_private_repo_access, mr_internal, mr_public])
+ end
+ end
+ end
+
+ context 'being added to the internal project with private repo access' do
+ context 'as a guest' do
+ before do
+ internal_with_private_repo.add_guest(user)
+ end
+
+ it 'returns merge requests from the project' do
+ expect(merge_requests).to eq([mr_internal, mr_public])
+ end
+ end
+
+ context 'as a reporter' do
+ before do
+ internal_with_private_repo.add_reporter(user)
+ end
+
+ it 'returns merge requests from the project' do
+ expect(merge_requests).to eq([mr_internal_private_repo_access, mr_internal, mr_public])
+ end
+ end
+ end
end
end
end
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
index ecffbb9e197..34c7b508c56 100644
--- a/spec/finders/milestones_finder_spec.rb
+++ b/spec/finders/milestones_finder_spec.rb
@@ -9,7 +9,7 @@ describe MilestonesFinder do
let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) }
let!(:milestone_4) { create(:milestone, project: project_2, state: 'active') }
- it 'it returns milestones for projects' do
+ it 'returns milestones for projects' do
result = described_class.new(project_ids: [project_1.id, project_2.id], state: 'all').execute
expect(result).to contain_exactly(milestone_3, milestone_4)
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 35279906854..8aea45b457c 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -4,12 +4,13 @@ require 'spec_helper'
describe Projects::Serverless::FunctionsFinder do
include KubernetesHelpers
+ include PrometheusHelpers
include ReactiveCachingHelpers
let(:user) { create(:user) }
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
- let(:project) { cluster.project}
+ let(:project) { cluster.project }
let(:namespace) do
create(:cluster_kubernetes_namespace,
@@ -22,14 +23,50 @@ describe Projects::Serverless::FunctionsFinder do
project.add_maintainer(user)
end
+ describe '#installed' do
+ it 'when reactive_caching is still fetching data' do
+ expect(described_class.new(project).knative_installed).to eq 'checking'
+ end
+
+ context 'when reactive_caching has finished' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+
+ before do
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ end
+
+ context 'when knative is not installed' do
+ it 'returns false' do
+ stub_kubeclient_discover_knative_not_found(service.api_url)
+
+ expect(described_class.new(project).knative_installed).to eq false
+ end
+ end
+
+ context 'reactive_caching is finished and knative is installed' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+
+ it 'returns true' do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
+
+ expect(described_class.new(project).knative_installed).to be true
+ end
+ end
+ end
+ end
+
describe 'retrieve data from knative' do
- it 'does not have knative installed' do
- expect(described_class.new(project.clusters).execute).to be_empty
+ context 'does not have knative installed' do
+ it { expect(described_class.new(project).execute).to be_empty }
end
context 'has knative installed' do
let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
- let(:finder) { described_class.new(project.clusters) }
+ let(:finder) { described_class.new(project) }
it 'there are no functions' do
expect(finder.execute).to be_empty
@@ -37,42 +74,51 @@ describe Projects::Serverless::FunctionsFinder do
it 'there are functions', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
expect(finder.execute).not_to be_empty
end
it 'has a function', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
result = finder.service(cluster.environment_scope, cluster.project.name)
expect(result).not_to be_empty
expect(result["metadata"]["name"]).to be_eql(cluster.project.name)
end
- end
- end
- describe 'verify if knative is installed' do
- context 'knative is not installed' do
- it 'does not have knative installed' do
- expect(described_class.new(project.clusters).installed?).to be false
+ it 'has metrics', :use_clean_rails_memory_store_caching do
end
end
- context 'knative is installed' do
+ context 'has prometheus' do
+ let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+ let(:finder) { described_class.new(project) }
+
+ before do
+ allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix'))
+ end
+
+ it 'is available' do
+ expect(finder.has_prometheus?("*")).to be true
+ end
- it 'does have knative installed' do
- expect(described_class.new(project.clusters).installed?).to be true
+ it 'has query data' do
+ expect(finder.invocation_metrics("*", cluster.project.name)).not_to be_nil
end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 134fb5f2c04..d367f9015c7 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe SnippetsFinder do
+ include ExternalAuthorizationServiceHelpers
include Gitlab::Allowable
- using RSpec::Parameterized::TableSyntax
describe '#initialize' do
it 'raises ArgumentError when a project and author are given' do
@@ -14,174 +14,142 @@ describe SnippetsFinder do
end
end
- context 'filter by scope' do
- let(:user) { create :user }
- let!(:snippet1) { create(:personal_snippet, :private, author: user) }
- let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
- let!(:snippet3) { create(:personal_snippet, :public, author: user) }
-
- it "returns all snippets for 'all' scope" do
- snippets = described_class.new(user, scope: :all).execute
-
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
-
- it "returns all snippets for 'are_private' scope" do
- snippets = described_class.new(user, scope: :are_private).execute
+ describe '#execute' do
+ set(:user) { create(:user) }
+ set(:private_personal_snippet) { create(:personal_snippet, :private, author: user) }
+ set(:internal_personal_snippet) { create(:personal_snippet, :internal, author: user) }
+ set(:public_personal_snippet) { create(:personal_snippet, :public, author: user) }
- expect(snippets).to include(snippet1)
- expect(snippets).not_to include(snippet2, snippet3)
- end
+ context 'filter by scope' do
+ it "returns all snippets for 'all' scope" do
+ snippets = described_class.new(user, scope: :all).execute
- it "returns all snippets for 'are_internal' scope" do
- snippets = described_class.new(user, scope: :are_internal).execute
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
- expect(snippets).to include(snippet2)
- expect(snippets).not_to include(snippet1, snippet3)
- end
+ it "returns all snippets for 'are_private' scope" do
+ snippets = described_class.new(user, scope: :are_private).execute
- it "returns all snippets for 'are_private' scope" do
- snippets = described_class.new(user, scope: :are_public).execute
+ expect(snippets).to contain_exactly(private_personal_snippet)
+ end
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet1, snippet2)
- end
- end
+ it "returns all snippets for 'are_internal' scope" do
+ snippets = described_class.new(user, scope: :are_internal).execute
- context 'filter by author' do
- let(:user) { create :user }
- let(:user1) { create :user }
- let!(:snippet1) { create(:personal_snippet, :private, author: user) }
- let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
- let!(:snippet3) { create(:personal_snippet, :public, author: user) }
+ expect(snippets).to contain_exactly(internal_personal_snippet)
+ end
- it "returns all public and internal snippets" do
- snippets = described_class.new(user1, author: user).execute
+ it "returns all snippets for 'are_private' scope" do
+ snippets = described_class.new(user, scope: :are_public).execute
- expect(snippets).to include(snippet2, snippet3)
- expect(snippets).not_to include(snippet1)
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
end
- it "returns internal snippets" do
- snippets = described_class.new(user, author: user, scope: :are_internal).execute
+ context 'filter by author' do
+ it 'returns all public and internal snippets' do
+ snippets = described_class.new(create(:user), author: user).execute
- expect(snippets).to include(snippet2)
- expect(snippets).not_to include(snippet1, snippet3)
- end
+ expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet)
+ end
- it "returns private snippets" do
- snippets = described_class.new(user, author: user, scope: :are_private).execute
+ it 'returns internal snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_internal).execute
- expect(snippets).to include(snippet1)
- expect(snippets).not_to include(snippet2, snippet3)
- end
+ expect(snippets).to contain_exactly(internal_personal_snippet)
+ end
- it "returns public snippets" do
- snippets = described_class.new(user, author: user, scope: :are_public).execute
+ it 'returns private snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_private).execute
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet1, snippet2)
- end
+ expect(snippets).to contain_exactly(private_personal_snippet)
+ end
- it "returns all snippets" do
- snippets = described_class.new(user, author: user).execute
+ it 'returns public snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_public).execute
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
- it "returns only public snippets if unauthenticated user" do
- snippets = described_class.new(nil, author: user).execute
+ it 'returns all snippets' do
+ snippets = described_class.new(user, author: user).execute
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet2, snippet1)
- end
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
- it 'returns all snippets for an admin' do
- admin = create(:user, :admin)
- snippets = described_class.new(admin, author: user).execute
+ it 'returns only public snippets if unauthenticated user' do
+ snippets = described_class.new(nil, author: user).execute
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
- end
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
- context 'filter by project' do
- let(:user) { create :user }
- let(:group) { create :group, :public }
- let(:project1) { create(:project, :public, group: group) }
+ it 'returns all snippets for an admin' do
+ admin = create(:user, :admin)
+ snippets = described_class.new(admin, author: user).execute
- before do
- @snippet1 = create(:project_snippet, :private, project: project1)
- @snippet2 = create(:project_snippet, :internal, project: project1)
- @snippet3 = create(:project_snippet, :public, project: project1)
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
end
- it "returns public snippets for unauthorized user" do
- snippets = described_class.new(nil, project: project1).execute
+ context 'project snippets' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, group: group) }
+ let!(:private_project_snippet) { create(:project_snippet, :private, project: project) }
+ let!(:internal_project_snippet) { create(:project_snippet, :internal, project: project) }
+ let!(:public_project_snippet) { create(:project_snippet, :public, project: project) }
- expect(snippets).to include(@snippet3)
- expect(snippets).not_to include(@snippet1, @snippet2)
- end
-
- it "returns public and internal snippets for non project members" do
- snippets = described_class.new(user, project: project1).execute
+ it 'returns public personal and project snippets for unauthorized user' do
+ snippets = described_class.new(nil, project: project).execute
- expect(snippets).to include(@snippet2, @snippet3)
- expect(snippets).not_to include(@snippet1)
- end
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
- it "returns public snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_public).execute
+ it 'returns public and internal snippets for non project members' do
+ snippets = described_class.new(user, project: project).execute
- expect(snippets).to include(@snippet3)
- expect(snippets).not_to include(@snippet1, @snippet2)
- end
+ expect(snippets).to contain_exactly(internal_project_snippet, public_project_snippet)
+ end
- it "returns internal snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_internal).execute
+ it 'returns public snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_public).execute
- expect(snippets).to include(@snippet2)
- expect(snippets).not_to include(@snippet1, @snippet3)
- end
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
- it "does not return private snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_private).execute
+ it 'returns internal snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_internal).execute
- expect(snippets).not_to include(@snippet1, @snippet2, @snippet3)
- end
+ expect(snippets).to contain_exactly(internal_project_snippet)
+ end
- it "returns all snippets for project members" do
- project1.add_developer(user)
+ it 'does not return private snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_private).execute
- snippets = described_class.new(user, project: project1).execute
+ expect(snippets).to be_empty
+ end
- expect(snippets).to include(@snippet1, @snippet2, @snippet3)
- end
+ it 'returns all snippets for project members' do
+ project.add_developer(user)
- it "returns private snippets for project members" do
- project1.add_developer(user)
+ snippets = described_class.new(user, project: project).execute
- snippets = described_class.new(user, project: project1, scope: :are_private).execute
+ expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet)
+ end
- expect(snippets).to include(@snippet1)
- end
+ it 'returns private snippets for project members' do
+ project.add_developer(user)
- it 'returns all snippets for an admin' do
- admin = create(:user, :admin)
- snippets = described_class.new(admin, project: project1).execute
+ snippets = described_class.new(user, project: project, scope: :are_private).execute
- expect(snippets).to include(@snippet1, @snippet2, @snippet3)
- end
- end
+ expect(snippets).to contain_exactly(private_project_snippet)
+ end
- describe '#execute' do
- let(:project) { create(:project, :public) }
- let!(:project_snippet) { create(:project_snippet, :public, project: project) }
- let!(:personal_snippet) { create(:personal_snippet, :public) }
- let(:user) { create(:user) }
- subject(:finder) { described_class.new(user) }
+ it 'returns all snippets for an admin' do
+ admin = create(:user, :admin)
+ snippets = described_class.new(admin, project: project).execute
- it 'returns project- and personal snippets' do
- expect(finder.execute).to contain_exactly(project_snippet, personal_snippet)
+ expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet)
+ end
end
context 'when the user cannot read cross project' do
@@ -191,10 +159,41 @@ describe SnippetsFinder do
end
it 'returns only personal snippets when the user cannot read cross project' do
- expect(finder.execute).to contain_exactly(personal_snippet)
+ expect(described_class.new(user).execute).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
end
end
end
it_behaves_like 'snippet visibility'
+
+ context 'external authorization' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:snippet) { create(:project_snippet, :public, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'a finder with external authorization service' do
+ let!(:subject) { create(:project_snippet, project: project) }
+ let(:project_params) { { project: project } }
+ end
+
+ it 'includes the result if the external service allows access' do
+ external_service_allow_access(user, project)
+
+ results = described_class.new(user, project: project).execute
+
+ expect(results).to contain_exactly(snippet)
+ end
+
+ it 'does not include any results if the external service denies access' do
+ external_service_deny_access(user, project)
+
+ results = described_class.new(user, project: project).execute
+
+ expect(results).to be_empty
+ end
+ end
end
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index d4ed41d54f0..22318a9946a 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -47,6 +47,13 @@ describe TodosFinder do
end
end
end
+
+ context 'external authorization' do
+ it_behaves_like 'a finder with external authorization service' do
+ let!(:subject) { create(:todo, project: project, user: user) }
+ let(:project_params) { { project_id: project.id } }
+ end
+ end
end
describe '#sort' do
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index fecf97dc641..d71d3c99272 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -2,10 +2,7 @@ require 'spec_helper'
describe UsersFinder do
describe '#execute' do
- let!(:user1) { create(:user, username: 'johndoe') }
- let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
- let!(:external_user) { create(:user, :external) }
- let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+ include_context 'UsersFinder#execute filter by project context'
context 'with a normal user' do
let(:user) { create(:user) }
@@ -13,43 +10,43 @@ describe UsersFinder do
it 'returns all users' do
users = described_class.new(user).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
it 'filters by username' do
users = described_class.new(user, username: 'johndoe').execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
it 'filters by username (case insensitive)' do
users = described_class.new(user, username: 'joHNdoE').execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
it 'filters by search' do
users = described_class.new(user, search: 'orando').execute
- expect(users).to contain_exactly(user2)
+ expect(users).to contain_exactly(blocked_user)
end
it 'filters by blocked users' do
users = described_class.new(user, blocked: true).execute
- expect(users).to contain_exactly(user2)
+ expect(users).to contain_exactly(blocked_user)
end
it 'filters by active users' do
users = described_class.new(user, active: true).execute
- expect(users).to contain_exactly(user, user1, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, omniauth_user)
end
it 'returns no external users' do
users = described_class.new(user, external: true).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
it 'filters by created_at' do
@@ -69,7 +66,7 @@ describe UsersFinder do
custom_attributes: { foo: 'bar' }
).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
end
@@ -85,20 +82,20 @@ describe UsersFinder do
it 'returns all users' do
users = described_class.new(admin).execute
- expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user)
+ expect(users).to contain_exactly(admin, normal_user, blocked_user, external_user, omniauth_user)
end
it 'filters by custom attributes' do
- create :user_custom_attribute, user: user1, key: 'foo', value: 'foo'
- create :user_custom_attribute, user: user1, key: 'bar', value: 'bar'
- create :user_custom_attribute, user: user2, key: 'foo', value: 'foo'
+ create :user_custom_attribute, user: normal_user, key: 'foo', value: 'foo'
+ create :user_custom_attribute, user: normal_user, key: 'bar', value: 'bar'
+ create :user_custom_attribute, user: blocked_user, key: 'foo', value: 'foo'
users = described_class.new(
admin,
custom_attributes: { foo: 'foo', bar: 'bar' }
).execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
end
end
diff --git a/spec/fixtures/api/graphql/introspection.graphql b/spec/fixtures/api/graphql/introspection.graphql
new file mode 100644
index 00000000000..7b712068fcd
--- /dev/null
+++ b/spec/fixtures/api/graphql/introspection.graphql
@@ -0,0 +1,92 @@
+# pulled from GraphiQL query
+query IntrospectionQuery {
+ __schema {
+ queryType { name }
+ mutationType { name }
+ subscriptionType { name }
+ types {
+ ...FullType
+ }
+ directives {
+ name
+ description
+ locations
+ args {
+ ...InputValue
+ }
+ }
+ }
+}
+
+fragment FullType on __Type {
+ kind
+ name
+ description
+ fields(includeDeprecated: true) {
+ name
+ description
+ args {
+ ...InputValue
+ }
+ type {
+ ...TypeRef
+ }
+ isDeprecated
+ deprecationReason
+ }
+ inputFields {
+ ...InputValue
+ }
+ interfaces {
+ ...TypeRef
+ }
+ enumValues(includeDeprecated: true) {
+ name
+ description
+ isDeprecated
+ deprecationReason
+ }
+ possibleTypes {
+ ...TypeRef
+ }
+}
+
+fragment InputValue on __InputValue {
+ name
+ description
+ type { ...TypeRef }
+ defaultValue
+}
+
+fragment TypeRef on __Type {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ ofType {
+ kind
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json
index 03aca4a3cc0..7c146647948 100644
--- a/spec/fixtures/api/schemas/board.json
+++ b/spec/fixtures/api/schemas/board.json
@@ -6,6 +6,5 @@
"properties" : {
"id": { "type": "integer" },
"name": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json
index 5ebc09a96dc..695175689b9 100644
--- a/spec/fixtures/api/schemas/cluster_status.json
+++ b/spec/fixtures/api/schemas/cluster_status.json
@@ -33,9 +33,11 @@
"version": { "type": "string" },
"status_reason": { "type": ["string", "null"] },
"external_ip": { "type": ["string", "null"] },
+ "external_hostname": { "type": ["string", "null"] },
"hostname": { "type": ["string", "null"] },
"email": { "type": ["string", "null"] },
- "update_available": { "type": ["boolean", "null"] }
+ "update_available": { "type": ["boolean", "null"] },
+ "can_uninstall": { "type": "boolean" }
},
"required" : [ "name", "status" ]
}
diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json
index 00abe73ec8a..9898819ef75 100644
--- a/spec/fixtures/api/schemas/entities/issue.json
+++ b/spec/fixtures/api/schemas/entities/issue.json
@@ -5,7 +5,7 @@
"iid": { "type": "integer" },
"author_id": { "type": "integer" },
"description": { "type": ["string", "null"] },
- "lock_version": { "type": ["string", "null"] },
+ "lock_version": { "type": ["integer", "null"] },
"milestone_id": { "type": ["string", "null"] },
"title": { "type": "string" },
"moved_to_id": { "type": ["integer", "null"] },
@@ -38,6 +38,5 @@
"items": { "$ref": "label.json" }
},
"assignees": { "type": ["array", "null"] }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/issue_board.json b/spec/fixtures/api/schemas/entities/issue_board.json
index f7b270ffa8d..7cb65e1f2f5 100644
--- a/spec/fixtures/api/schemas/entities/issue_board.json
+++ b/spec/fixtures/api/schemas/entities/issue_board.json
@@ -9,6 +9,9 @@
"project_id": { "type": "integer" },
"relative_position": { "type": ["integer", "null"] },
"time_estimate": { "type": "integer" },
+ "total_time_spent": { "type": "integer" },
+ "human_time_estimate": { "type": ["string", "null"] },
+ "human_total_time_spent": { "type": ["string", "null"] },
"weight": { "type": ["integer", "null"] },
"project": {
"type": "object",
diff --git a/spec/fixtures/api/schemas/entities/issue_boards.json b/spec/fixtures/api/schemas/entities/issue_boards.json
index 0ac1d9468c8..742f7be5485 100644
--- a/spec/fixtures/api/schemas/entities/issue_boards.json
+++ b/spec/fixtures/api/schemas/entities/issue_boards.json
@@ -10,6 +10,5 @@
"items": { "$ref": "issue_board.json" }
},
"size": { "type": "integer" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json
index 3006b482d41..ac0a0314455 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_basic.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json
@@ -6,14 +6,14 @@
"source_branch_exists": { "type": "boolean" },
"merge_error": { "type": ["string", "null"] },
"rebase_in_progress": { "type": "boolean" },
- "assignee_id": { "type": ["integer", "null"] },
"allow_collaboration": { "type": "boolean"},
"allow_maintainer_to_push": { "type": "boolean"},
- "assignee": {
- "oneOf": [
- { "type": "null" },
- { "$ref": "user.json" }
- ]
+ "assignees": {
+ "type": ["array"],
+ "items": {
+ "type": "object",
+ "$ref": "../public_api/v4/user/basic.json"
+ }
},
"milestone": {
"type": [ "object", "null" ]
@@ -23,7 +23,7 @@
},
"task_status": { "type": "string" },
"task_status_short": { "type": "string" },
- "lock_version": { "type": ["string", "null"] }
+ "lock_version": { "type": ["integer", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
index 7e9e048a9fd..214b67a9a0f 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
@@ -51,6 +51,5 @@
"toggle_subscription_path": { "type": "string" },
"move_issue_path": { "type": "string" },
"projects_autocomplete_path": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 67c209f3fc3..7018cb9a305 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -52,6 +52,7 @@
"mergeable_discussions_state": { "type": "boolean" },
"conflicts_can_be_resolved_in_ui": { "type": "boolean" },
"branch_missing": { "type": "boolean" },
+ "commits_count": { "type": ["integer", "null"] },
"has_conflicts": { "type": "boolean" },
"can_be_merged": { "type": "boolean" },
"mergeable": { "type": "boolean" },
@@ -124,7 +125,7 @@
"test_reports_path": { "type": ["string", "null"] },
"can_receive_suggestion": { "type": "boolean" },
"source_branch_protected": { "type": "boolean" },
- "conflicts_docs_path": { "type": ["string", "null"] }
- },
- "additionalProperties": false
+ "conflicts_docs_path": { "type": ["string", "null"] },
+ "merge_request_pipelines_docs_path": { "type": ["string", "null"] }
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/test_case.json b/spec/fixtures/api/schemas/entities/test_case.json
index c9ba1f3ad18..70f6edeeeb7 100644
--- a/spec/fixtures/api/schemas/entities/test_case.json
+++ b/spec/fixtures/api/schemas/entities/test_case.json
@@ -7,6 +7,7 @@
"properties": {
"status": { "type": "string" },
"name": { "type": "string" },
+ "classname": { "type": "string" },
"execution_time": { "type": "float" },
"system_output": { "type": ["string", "null"] },
"stack_trace": { "type": ["string", "null"] }
diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json
index f1d33e3ce7b..5b1e3c049fa 100644
--- a/spec/fixtures/api/schemas/environment.json
+++ b/spec/fixtures/api/schemas/environment.json
@@ -20,6 +20,7 @@
"state": { "type": "string" },
"external_url": { "$ref": "types/nullable_string.json" },
"environment_type": { "$ref": "types/nullable_string.json" },
+ "name_without_type": { "type": "string" },
"has_stop_action": { "type": "boolean" },
"environment_path": { "type": "string" },
"stop_path": { "type": "string" },
@@ -30,7 +31,11 @@
"last_deployment": {
"oneOf": [
{ "type": "null" },
- { "$ref": "deployment.json" }
+ { "$ref": "deployment.json" },
+ {
+ "name": { "type": "string" },
+ "build_path": { "type": "string" }
+ }
]
}
},
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index a83ec55cede..77de9ae4f9f 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -28,7 +28,7 @@
"items": { "$ref": "entities/label.json" }
},
"assignee": {
- "id": { "type": "integet" },
+ "id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"avatar_url": { "type": "uri" }
@@ -52,6 +52,5 @@
}
},
"subscribed": { "type": ["boolean", "null"] }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/issues.json b/spec/fixtures/api/schemas/issues.json
index 70771b21c96..fbcd9eea389 100644
--- a/spec/fixtures/api/schemas/issues.json
+++ b/spec/fixtures/api/schemas/issues.json
@@ -10,6 +10,5 @@
"items": { "$ref": "issue.json" }
},
"size": { "type": "integer" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
index c76c6945117..690c4a7d4e8 100644
--- a/spec/fixtures/api/schemas/pipeline_schedule.json
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -33,7 +33,7 @@
"additionalProperties": false
},
"variables": {
- "type": ["array", "null"],
+ "type": "array",
"items": { "$ref": "pipeline_schedule_variable.json" }
}
},
diff --git a/spec/fixtures/api/schemas/pipeline_schedule_variable.json b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
index f7ccb2d44a0..022d36cb88c 100644
--- a/spec/fixtures/api/schemas/pipeline_schedule_variable.json
+++ b/spec/fixtures/api/schemas/pipeline_schedule_variable.json
@@ -1,8 +1,14 @@
{
- "type": ["object", "null"],
+ "type": "object",
+ "required": [
+ "key",
+ "value",
+ "variable_type"
+ ],
"properties": {
"key": { "type": "string" },
- "value": { "type": "string" }
+ "value": { "type": "string" },
+ "variable_type": { "type": "string" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/artifact.json b/spec/fixtures/api/schemas/public_api/v4/artifact.json
new file mode 100644
index 00000000000..9df957b1498
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/artifact.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "required": [
+ "file_type",
+ "size",
+ "filename",
+ "file_format"
+ ],
+ "properties": {
+ "file_type": { "type": "string"},
+ "size": { "type": "integer"},
+ "filename": { "type": "string"},
+ "file_format": { "type": "string"}
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/artifact_file.json b/spec/fixtures/api/schemas/public_api/v4/artifact_file.json
new file mode 100644
index 00000000000..4017e6bdabc
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/artifact_file.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "filename",
+ "size"
+ ],
+ "properties": {
+ "filename": { "type": "string"},
+ "size": { "type": "integer"}
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/deployment.json b/spec/fixtures/api/schemas/public_api/v4/deployment.json
new file mode 100644
index 00000000000..3af2dc27d55
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/deployment.json
@@ -0,0 +1,32 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "iid",
+ "ref",
+ "sha",
+ "created_at",
+ "user",
+ "deployable"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "ref": { "type": "string" },
+ "sha": { "type": "string" },
+ "created_at": { "type": "string" },
+ "user": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "user/basic.json" }
+ ]
+ },
+ "deployable": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "job.json" }
+ ]
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/environment.json b/spec/fixtures/api/schemas/public_api/v4/environment.json
new file mode 100644
index 00000000000..242e90fb7ac
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/environment.json
@@ -0,0 +1,23 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "slug",
+ "external_url",
+ "last_deployment"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "slug": { "type": "string" },
+ "external_url": { "$ref": "../../types/nullable_string.json" },
+ "last_deployment": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "deployment.json" }
+ ]
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/job.json b/spec/fixtures/api/schemas/public_api/v4/job.json
new file mode 100644
index 00000000000..c038ae0a664
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/job.json
@@ -0,0 +1,64 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "status",
+ "stage",
+ "name",
+ "ref",
+ "tag",
+ "coverage",
+ "created_at",
+ "started_at",
+ "finished_at",
+ "duration",
+ "user",
+ "commit",
+ "pipeline",
+ "web_url",
+ "artifacts",
+ "artifacts_expire_at",
+ "runner"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "status": { "type": "string" },
+ "stage": { "type": "string" },
+ "name": { "type": "string" },
+ "ref": { "type": "string" },
+ "tag": { "type": "boolean" },
+ "coverage": { "type": ["number", "null"] },
+ "allow_failure": { "type": "boolean" },
+ "created_at": { "type": "string" },
+ "started_at": { "type": ["null", "string"] },
+ "finished_at": { "type": ["null", "string"] },
+ "duration": { "type": ["null", "number"] },
+ "user": { "$ref": "user/basic.json" },
+ "commit": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "commit/basic.json" }
+ ]
+ },
+ "pipeline": { "$ref": "pipeline/basic.json" },
+ "web_url": { "type": "string" },
+ "artifacts": {
+ "type": "array",
+ "items": { "$ref": "artifact.json" }
+ },
+ "artifacts_file": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "artifact_file.json" }
+ ]
+ },
+ "artifacts_expire_at": { "type": ["null", "string"] },
+ "runner": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "runner.json" }
+ ]
+ }
+ },
+ "additionalProperties":false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/label_basic.json b/spec/fixtures/api/schemas/public_api/v4/label_basic.json
new file mode 100644
index 00000000000..37bbdcb14fe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/label_basic.json
@@ -0,0 +1,24 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "color",
+ "description",
+ "text_color"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "color": {
+ "type": "string",
+ "pattern": "^#[0-9A-Fa-f]{3}{1,2}$"
+ },
+ "description": { "type": ["string", "null"] },
+ "text_color": {
+ "type": "string",
+ "pattern": "^#[0-9A-Fa-f]{3}{1,2}$"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
new file mode 100644
index 00000000000..a423bf70b69
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
@@ -0,0 +1,135 @@
+{
+ "type": "object",
+ "properties" : {
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "merged_by": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "merged_at": { "type": ["date", "null"] },
+ "closed_by": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "closed_at": { "type": ["date", "null"] },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "target_branch": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "assignees": {
+ "items": {
+ "$ref": "./merge_request.json"
+ }
+ },
+ "source_project_id": { "type": "integer" },
+ "target_project_id": { "type": "integer" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "work_in_progress": { "type": "boolean" },
+ "milestone": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "merge_when_pipeline_succeeds": { "type": "boolean" },
+ "merge_status": { "type": "string" },
+ "sha": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "user_notes_count": { "type": "integer" },
+ "changes_count": { "type": "string" },
+ "should_remove_source_branch": { "type": ["boolean", "null"] },
+ "force_remove_source_branch": { "type": ["boolean", "null"] },
+ "discussion_locked": { "type": ["boolean", "null"] },
+ "web_url": { "type": "uri" },
+ "squash": { "type": "boolean" },
+ "time_stats": {
+ "time_estimate": { "type": "integer" },
+ "total_time_spent": { "type": "integer" },
+ "human_time_estimate": { "type": ["string", "null"] },
+ "human_total_time_spent": { "type": ["string", "null"] }
+ },
+ "allow_collaboration": { "type": ["boolean", "null"] },
+ "allow_maintainer_to_push": { "type": ["boolean", "null"] }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "target_branch",
+ "source_branch", "upvotes", "downvotes", "author",
+ "assignee", "source_project_id", "target_project_id",
+ "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
+ "merge_status", "sha", "merge_commit_sha", "user_notes_count",
+ "should_remove_source_branch", "force_remove_source_branch",
+ "web_url", "squash"
+ ],
+ "head_pipeline": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "pipeline/detail.json" }
+ ]
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index 6df27bf32b9..b35c83950e8 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -1,126 +1,6 @@
{
"type": "array",
"items": {
- "type": "object",
- "properties" : {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "project_id": { "type": "integer" },
- "title": { "type": "string" },
- "description": { "type": ["string", "null"] },
- "state": { "type": "string" },
- "merged_by": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "merged_at": { "type": ["date", "null"] },
- "closed_by": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "closed_at": { "type": ["date", "null"] },
- "created_at": { "type": "date" },
- "updated_at": { "type": "date" },
- "target_branch": { "type": "string" },
- "source_branch": { "type": "string" },
- "upvotes": { "type": "integer" },
- "downvotes": { "type": "integer" },
- "author": {
- "type": "object",
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "assignee": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "source_project_id": { "type": "integer" },
- "target_project_id": { "type": "integer" },
- "labels": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "work_in_progress": { "type": "boolean" },
- "milestone": {
- "type": ["object", "null"],
- "properties": {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "project_id": { "type": ["integer", "null"] },
- "group_id": { "type": ["integer", "null"] },
- "title": { "type": "string" },
- "description": { "type": ["string", "null"] },
- "state": { "type": "string" },
- "created_at": { "type": "date" },
- "updated_at": { "type": "date" },
- "due_date": { "type": "date" },
- "start_date": { "type": "date" }
- },
- "additionalProperties": false
- },
- "merge_when_pipeline_succeeds": { "type": "boolean" },
- "merge_status": { "type": "string" },
- "sha": { "type": "string" },
- "merge_commit_sha": { "type": ["string", "null"] },
- "user_notes_count": { "type": "integer" },
- "changes_count": { "type": "string" },
- "should_remove_source_branch": { "type": ["boolean", "null"] },
- "force_remove_source_branch": { "type": ["boolean", "null"] },
- "discussion_locked": { "type": ["boolean", "null"] },
- "web_url": { "type": "uri" },
- "squash": { "type": "boolean" },
- "time_stats": {
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["string", "null"] },
- "human_total_time_spent": { "type": ["string", "null"] }
- },
- "allow_collaboration": { "type": ["boolean", "null"] },
- "allow_maintainer_to_push": { "type": ["boolean", "null"] }
- },
- "required": [
- "id", "iid", "project_id", "title", "description",
- "state", "created_at", "updated_at", "target_branch",
- "source_branch", "upvotes", "downvotes", "author",
- "assignee", "source_project_id", "target_project_id",
- "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
- "merge_status", "sha", "merge_commit_sha", "user_notes_count",
- "should_remove_source_branch", "force_remove_source_branch",
- "web_url", "squash"
- ],
- "additionalProperties": false
+ "$ref": "./merge_request.json"
}
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
index 56f86856dd4..a7207d2d991 100644
--- a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
@@ -13,6 +13,5 @@
"ref": { "type": "string" },
"status": { "type": "string" },
"web_url": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json
new file mode 100644
index 00000000000..63e130d4055
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/detail.json
@@ -0,0 +1,32 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "basic.json" },
+ {
+ "properties": {
+ "before_sha": { "type": ["string", "null"] },
+ "tag": { "type": ["boolean"] },
+ "yaml_errors": { "type": ["string", "null"] },
+ "user": {
+ "anyOf": [
+ { "type": ["object", "null"] },
+ { "$ref": "../user/basic.json" }
+ ]
+ },
+ "created_at": { "type": ["date", "null"] },
+ "updated_at": { "type": ["date", "null"] },
+ "started_at": { "type": ["date", "null"] },
+ "finished_at": { "type": ["date", "null"] },
+ "committed_at": { "type": ["date", "null"] },
+ "duration": { "type": ["number", "null"] },
+ "coverage": { "type": ["string", "null"] },
+ "detailed_status": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "../../../status/ci_detailed_status.json" }
+ ]
+ }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index 6612c2a9911..6ea0781c1ed 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -1,12 +1,33 @@
{
"type": "object",
- "required" : [
- "tag_name",
- "description"
- ],
- "properties" : {
- "tag_name": { "type": ["string", "null"] },
- "description": { "type": "string" }
+ "required": ["name", "tag_name", "commit"],
+ "properties": {
+ "name": { "type": "string" },
+ "tag_name": { "type": "string" },
+ "description": { "type": "string" },
+ "description_html": { "type": "string" },
+ "created_at": { "type": "date" },
+ "commit": {
+ "oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }]
+ },
+ "author": {
+ "oneOf": [{ "type": "null" }, { "$ref": "user/basic.json" }]
+ },
+ "assets": {
+ "required": ["count", "links", "sources"],
+ "properties": {
+ "count": { "type": "integer" },
+ "links": { "$ref": "../../release/links.json" },
+ "sources": {
+ "type": "array",
+ "items": {
+ "format": "zip",
+ "url": "string"
+ }
+ }
+ },
+ "additionalProperties": false
+ }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
new file mode 100644
index 00000000000..e78398ad1d5
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "required": ["name"],
+ "properties": {
+ "name": { "type": "string" },
+ "description": { "type": "string" },
+ "description_html": { "type": "string" },
+ "created_at": { "type": "date" },
+ "author": {
+ "oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }]
+ },
+ "assets": {
+ "required": ["count", "links"],
+ "properties": {
+ "count": { "type": "integer" },
+ "links": { "$ref": "../../../release/links.json" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json
new file mode 100644
index 00000000000..c13966b28e9
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/release/releases_for_guest.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "release_for_guest.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
new file mode 100644
index 00000000000..6612c2a9911
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/release/tag_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/releases.json b/spec/fixtures/api/schemas/public_api/v4/releases.json
new file mode 100644
index 00000000000..e26215707fe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/releases.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "release.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/runner.json b/spec/fixtures/api/schemas/public_api/v4/runner.json
new file mode 100644
index 00000000000..d97d74a93f2
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/runner.json
@@ -0,0 +1,24 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "description",
+ "ip_address",
+ "active",
+ "is_shared",
+ "name",
+ "online",
+ "status"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "description": { "type": "string" },
+ "ip_address": { "type": "string" },
+ "active": { "type": "boolean" },
+ "is_shared": { "type": "boolean" },
+ "name": { "type": ["null", "string"] },
+ "online": { "type": "boolean" },
+ "status": { "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
index 10d4edb7ffb..5713ea1f526 100644
--- a/spec/fixtures/api/schemas/public_api/v4/tag.json
+++ b/spec/fixtures/api/schemas/public_api/v4/tag.json
@@ -14,7 +14,7 @@
"release": {
"oneOf": [
{ "type": "null" },
- { "$ref": "release.json" }
+ { "$ref": "release/tag_release.json" }
]
}
},
diff --git a/spec/fixtures/api/schemas/variable.json b/spec/fixtures/api/schemas/variable.json
index 6f6b044115b..305071a6b3f 100644
--- a/spec/fixtures/api/schemas/variable.json
+++ b/spec/fixtures/api/schemas/variable.json
@@ -4,12 +4,14 @@
"id",
"key",
"value",
+ "masked",
"protected"
],
"properties": {
"id": { "type": "integer" },
"key": { "type": "string" },
"value": { "type": "string" },
+ "masked": { "type": "boolean" },
"protected": { "type": "boolean" },
"environment_scope": { "type": "string", "optional": true }
},
diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md
index 2652a842c0e..555905bf07e 100644
--- a/spec/fixtures/blockquote_fence_after.md
+++ b/spec/fixtures/blockquote_fence_after.md
@@ -18,10 +18,13 @@ Double `>>>` inside code block:
Blockquote outside code block:
+
> Quote
+
Code block inside blockquote:
+
> Quote
>
> ```
@@ -30,8 +33,10 @@ Code block inside blockquote:
>
> Quote
+
Single `>>>` inside code block inside blockquote:
+
> Quote
>
> ```
@@ -42,8 +47,10 @@ Single `>>>` inside code block inside blockquote:
>
> Quote
+
Double `>>>` inside code block inside blockquote:
+
> Quote
>
> ```
@@ -56,6 +63,7 @@ Double `>>>` inside code block inside blockquote:
>
> Quote
+
Single `>>>` inside HTML:
<pre>
@@ -76,10 +84,13 @@ Double `>>>` inside HTML:
Blockquote outside HTML:
+
> Quote
+
HTML inside blockquote:
+
> Quote
>
> <pre>
@@ -88,8 +99,10 @@ HTML inside blockquote:
>
> Quote
+
Single `>>>` inside HTML inside blockquote:
+
> Quote
>
> <pre>
@@ -100,8 +113,10 @@ Single `>>>` inside HTML inside blockquote:
>
> Quote
+
Double `>>>` inside HTML inside blockquote:
+
> Quote
>
> <pre>
@@ -113,3 +128,4 @@ Double `>>>` inside HTML inside blockquote:
> </pre>
>
> Quote
+
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
new file mode 100644
index 00000000000..638ecbcc11f
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
@@ -0,0 +1,36 @@
+dashboard: 'Test Dashboard'
+priority: 1
+panel_groups:
+- group: Group A
+ priority: 1
+ panels:
+ - title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ metrics:
+ - id: metric_a1
+ query_range: 'query'
+ unit: unit
+ label: Legend Label
+ - title: "Super Chart A2"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 2
+ metrics:
+ - id: metric_a2
+ query_range: 'query'
+ label: Legend Label
+ unit: unit
+- group: Group B
+ priority: 10
+ panels:
+ - title: "Super Chart B"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ metrics:
+ - id: metric_b
+ query_range: 'query'
+ unit: unit
+ label: Legend Label
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
new file mode 100644
index 00000000000..1ee1205e29a
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
@@ -0,0 +1,13 @@
+{
+ "type": "object",
+ "required": ["dashboard", "priority", "panel_groups"],
+ "properties": {
+ "dashboard": { "type": "string" },
+ "priority": { "type": "number" },
+ "panel_groups": {
+ "type": "array",
+ "items": { "$ref": "spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
new file mode 100644
index 00000000000..2d0af57ec2c
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
@@ -0,0 +1,20 @@
+{
+ "type": "object",
+ "required": [
+ "unit",
+ "label"
+ ],
+ "oneOf": [
+ { "required": ["query"] },
+ { "required": ["query_range"] }
+ ],
+ "properties": {
+ "id": { "type": "string" },
+ "query_range": { "type": "string" },
+ "query": { "type": "string" },
+ "unit": { "type": "string" },
+ "label": { "type": "string" },
+ "track": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json
new file mode 100644
index 00000000000..d7a390adcdc
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json
@@ -0,0 +1,17 @@
+{
+ "type": "object",
+ "required": [
+ "group",
+ "priority",
+ "panels"
+ ],
+ "properties": {
+ "group": { "type": "string" },
+ "priority": { "type": "number" },
+ "panels": {
+ "type": "array",
+ "items": { "$ref": "panels.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
new file mode 100644
index 00000000000..1548daacd64
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
@@ -0,0 +1,20 @@
+{
+ "type": "object",
+ "required": [
+ "title",
+ "y_label",
+ "weight",
+ "metrics"
+ ],
+ "properties": {
+ "title": { "type": "string" },
+ "type": { "type": "string" },
+ "y_label": { "type": "string" },
+ "weight": { "type": "number" },
+ "metrics": {
+ "type": "array",
+ "items": { "$ref": "metrics.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/passphrase_x509_certificate.crt b/spec/fixtures/passphrase_x509_certificate.crt
new file mode 100644
index 00000000000..6973163b79e
--- /dev/null
+++ b/spec/fixtures/passphrase_x509_certificate.crt
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEpTCCAo0CAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5
+MB4XDTE4MDMyMzE0MDIwOFoXDTE5MDMyMzE0MDIwOFowHTEbMBkGA1UEAwwSZ2l0
+bGFiLXBhc3NwaHJhc2VkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
+zpsWHOewP/khfDsLUWxaRCinrBzVJm2C01bVahKVR3g/JD4vEH901Wod9Pvbh/9e
+PEfE+YZmgSUUopbL3JUheMnyW416F43HKE/fPW4+QeuIEceuhCXg20eOXmvnWWNM
+0hXZh4hq69rwvMPREC/LkZy/QkTDKhJNLNAqAQu2AJ3C7Yga8hFQYEhx1hpfGtwD
+z/Nf3efat9WN/d6yW9hfJ98NCmImTm5l9Pc0YPNWCAf96vsqsNHBrTkFy6CQwkhH
+K1ynVYuqnHYxSc4FPCT5SAleD9gR/xFBAHb7pPy4yGxMSEmiWaMjjZCVPsghj1jM
+Ej77MTDL3U9LeDfiILhvZ+EeQxqPiFwwG2eaIn3ZEs2Ujvw7Z2VpG9VMcPTnB4jK
+ot6qPM1YXnkGWQ6iT0DTPS3h7zg1xIJXI5N2sI6GXuKrXXwZ1wPqzFLKPv+xBjp8
+P6dih+EImfReFi9zIO1LqGMY+XmRcqodsb6jzsmBimJkqBtatJM7FuUUUN56wiaj
+q9+BWbm+ZdQ2lvqndMljjUjTh6pNERfGAJgkNuLn3X9hXVE0TSpmn0nOgaL5izP3
+7FWUt0PTyGgK2zq9SEhZmK2TKckLkKMk/ZBBBVM/nrnjs72IlbsqdcVoTnApytZr
+xVYTj1hV7QlAfaU3w/M534qXDiy8+HfX5ksWQMtSklECAwEAATANBgkqhkiG9w0B
+AQUFAAOCAgEAMMhzSRq9PqCpui74nwjhmn8Dm2ky7A+MmoXNtk70cS/HWrjzaacb
+B/rxsAUp7f0pj4QMMM0ETMFpbNs8+NPd2FRY0PfWE4yyDpvZO2Oj1HZKLHX72Gjn
+K5KB9DYlVsXhGPfuFWXpxGWF2Az9hDWnj58M3DOAps+6tHuAtudQUuwf5ENQZWwE
+ySpr7yoHm1ykgl0Tsb9ZHi9qLrWRRMNYXRT+gvwP1bba8j9jOtjO/xYiIskwMPLM
+W8SFmQxbg0Cvi8Q89PB6zoTNOhPQyoyeSlw9meeZJHAMK2zxeglEm8C4EQ+I9Y6/
+yylM5/Sc55TjWAvRFgbsq+OozgMvffk/Q2fzcGF44J9DEQ7nrhmJxJ+X4enLknR5
+Hw4+WhdYA+bwjx3YZBNTh9/YMgNPYwQhf5gtcZGTd6X4j6qZfJ6CXBmhkC1Cbfyl
+yM7B7i4JAqPWMeDP50pXCgyKlwgw1JuFW+xkbkYQAj7wtggQ6z1Vjb5W8R8kYn9q
+LXClVtThEeSV5KkVwNX21aFcUs8qeQ+zsgKqpEyM5oILQQ1gDSxLTtrr2KuN+WJN
+wM0acwD45X7gA/aZYpCGkIgHIBq0zIDP1s6IqeebFJjW8lWofhRxOEWomWdRweJG
+N7qQ1WCTQxAPGAkDI8QPjaspvnAhFKmpBG/mR5IXLFKDbttu7WNdYDo=
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/passphrase_x509_certificate_pk.key b/spec/fixtures/passphrase_x509_certificate_pk.key
new file mode 100644
index 00000000000..f9760dfe70e
--- /dev/null
+++ b/spec/fixtures/passphrase_x509_certificate_pk.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,79CCB506B0FD42A6F1BAE6D72E1CB20C
+
+EuZQOfgaO6LVCNytTHNJmbiq1rbum9xg6ohfBTVt7Cw4+8yLezWva/3sJQtnEk2P
+M2yEQYWIiCX+clPkRiRL8WLjRfLTNcYS6QxxuJdpOrowPrBYr4Aig8jBUUBI4VQf
+w1ZEUQd0mxQGnyzkKpsudFOntCtZbvbrBsIAQUNLcrKEFk3XW/BqE1Q/ja6WfWqX
+b6EKg6DoXi92V90O6sLDfpmTKZq3ThvVDFuWeJ2K/GVp2cs+MkBIBJ8XX+NT1nWg
+g+Ok+yaSI/N9ILX4XDgXunJGwcooI8PhHSjkDWRusi8vbo7RFqIKiSF+h6tIwktF
+Uss3JESKgXZCQ7upCnHSzK/aWFtwHtXxqOi7esqEZd+1sB0LY+XMnbaxweCMx2Kj
+czktKYvoXUs69Whln+yyXULtl5XhJ8lbvlbIG2FbZ9y+/hHOyBqZyeUyCnXDzv8/
+0U0iZwreP3XPVMsy578pIdcdL27q+r05j4yjrJfbX3T9xp2u3F9uVubCa4euEBwV
+yrFdsxJLKON8pFeDS49m5gHNsHmeZ0sUeTPZVGNXdabVetkOA0eAAGK4zAoqG79L
+hEN7cDenz+E4XHp8gMzwwMiVyU4FuAb6SXkfSodctmSTWVbzNBja0FBek3UXy+pn
+9qq7cIpe7NY5gzcbyoy9lSkyYVkAm8j6BIYtY1ZUAmtCklC2ADWARTjd7dI7aEbO
+QbXxNIq2+O/zMOXfougSPoDP8SLyLuE1p6SwfWV7Dwf119hn+mjWlGzAZDxxHhsR
+yYUQCUe0NIKzuUp3WYIx8xIb7/WFwit/JaFaxurjBnhkkEviBn+TgXiuFBO3tv/d
+URpZ39rH0mrDsR61pCiIcoNVkQkynHcAFPd5VtaeSJPvZP280uOCPPS31cr6/0LB
+1JX3lZoWWCuA+JQjxtZDaDTcvEUbfOQ2rexQQo4uylNkBF9F5WOdQBkKG/AfqBq8
+S/TdubYzvpcKhFAlXsI67JdbxGlU4HCsxOLwWzSUYclN4W3l7s7KZ5zxt+MU03Uf
+vara9uuZHiKUjZohjXeqcXTc+UyC8VH1dF19M3Cj9RNrwl2xEDUMtIiALBjbGp1E
+pu2nPj9NhWf9Vw5MtSszutesxXba2nPmvvGvvZ7N3h/k4NsKL7JdENF7XqkI0D2K
+jpO1t6d3cazS1VpMWLZS45kWaM3Y07tVR3V+4Iv9Vo1e9H2u/Z5U4YeJ44sgMsct
+dBOAhHdUAI5+P+ocLXiCKo+EcS0cKvz+CC4ux0vvcF3JrTqZJN1U/JxRka2EyJ1B
+2Xtu3DF36XpBJcs+MJHjJ+kUn6DHYoYxZa+bB8LX6+FQ+G7ue+Dx/RsGlP7if1nq
+DAaM6kZg7/FbFzOZyl5xhwAJMxfgNNU7nSbk9lrvQ4mdwgFjvgGu3jlER4+TcleE
+4svXInxp1zK6ES44tI9fXkhPaFkafxAL7eUSyjjEwMC06h+FtqK3mmoKLo5NrGJE
+zVl69r2WdoSQEylVN1Kbp+U4YbfncInLJqBq2q5w9ASL/8Rhe8b52q6PuVX/bjoz
+0pkSu+At4jVbAhRpER5NGlzG884IaqqvBvMYR5zFJeRroIijyUyH0KslK37/sXRk
+ty0yKrkm31De9gDa3+XlgAVDAgbEQmGVwVVcV0IYYJbjIf36lUdGh4+3krwxolr/
+vZct5Z7QxfJlBtdOstjz5U9o05yOhjoNrPZJXuKMmWOQjSwr7rRSdqmAABF9IrBf
+Pa/ChF1y5j3gJESAFMyiea3kvLq1EbZRaKoybsQE2ctBQ8EQjzUz+OOxVO6GJ4W9
+XHyfcviFrpsVcJEpXQlEtGtKdfKLp48cytob1Fu1JOYPDCrafUQINCZP4H3Nt892
+zZiTmdwux7pbgf4KbONImN5XkpvdCGjQHSkYMmm5ETRK8s7Fmvt2aBPtlyXxJDOq
+iJUqwDV5HZXOnQVE/v/yESKgo2Cb8BWqPZ4/8Ubgu/OADYyv/dtjQel8QQ2FMhO4
+2tnwWbBBJk8VpR/vjFHkGSnj+JJfW/vUVQ+06D3wHYhNp7mh4M+37AngwzGCp7k+
+9aFwb2FBGghArB03E4lIO/959T0cX95WZ6tZtLLEsf3+ug7PPOSswCqsoPsXzFJH
+MgXVGKFXccNSsWol7VvrX/uja7LC1OE+pZNXxCRzSs4aljJBpvQ6Mty0lk2yBC0R
+MdujMoZH9PG9U6stwFd+P17tlGrQdRD3H2uimn82Ck+j2l0z0pzN0JB2WBYEyK0O
+1MC36wLICWjgIPLPOxDEEBeZPbc24DCcYfs/F/hSCHv/XTJzVVILCX11ShGPSXlI
+FL9qyq6jTNh/pVz6NiN/WhUPBFfOSzLRDyU0MRsSHM8b/HPpf3NOI3Ywmmj65c2k
+2kle1F2M5ZTL+XvLS61qLJ/8AgXWvDHP3xWuKGG/pM40CRTUkRW6NAokMr2/pEFw
+IHTE2+84dOKnUIEczzMY3aqzNmYDCmhOY0jD/Ieb4hy9tN+1lbQ/msYMIJ1w7CFR
+38yB/UbDD90NcuDhjrMbzVUv1At2rW7GM9lSbxGOlYDmtMNEL63md1pQ724v4gSE
+mzoFcMkqdh+hjFvv11o4H32lF3mPYcXuL+po76tqxGOiUrLKe/ZqkT5XAclYV/7H
+k3Me++PCh4ZqXBRPvR8Xr90NETtiFCkBQXLdhNWXrRe2v0EbSX+cYAWk68FQKCHa
+HKTz9T7wAvB6QWBXFhH9iCP8rnQLCEhLEhdrt+4v2KFkIVzBgOlMoHsZsMp0sBeq
+c5ZVbJdiKik3P/8ZQTn4jmOnQXCEyWx+LU4acks8Aho4lqq9yKq2DZpwbIRED47E
+r7R/NUevhqqzEHZ2SGD6EDqRN+bHJEi64vq0ryaEielusYXZqlnFXDHJcfLCmR5X
+3bj5pCwQF4ScTukrGQB/c4henG4vlF4CaD0CIIK3W6tH+AoDohYJts6YK49LGxmK
+yXiyKNak8zHYBBoRvd2avRHyGuR5yC9KrN8cbC/kZqMDvAyM65pIK+U7exJwYJhv
+ezCcbiH3bK3anpiRpdeNOot2ba/Y+/ks+DRC+xs4QDIhrmSEBCsLv1JbcWjtHSaG
+lm+1DSVduUk/kN+fBnlfif+TQV9AP3/wb8ekk8jjKXsL7H1tJKHsLLIIvrgrpxjw
+-----END RSA PRIVATE KEY-----
diff --git a/spec/fixtures/phabricator_responses/auth_failed.json b/spec/fixtures/phabricator_responses/auth_failed.json
new file mode 100644
index 00000000000..50e57c0ba49
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/auth_failed.json
@@ -0,0 +1 @@
+{"result":null,"error_code":"ERR-INVALID-AUTH","error_info":"API token \"api-token\" has the wrong length. API tokens should be 32 characters long."}
diff --git a/spec/fixtures/phabricator_responses/maniphest.search.json b/spec/fixtures/phabricator_responses/maniphest.search.json
new file mode 100644
index 00000000000..6a965007d0c
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/maniphest.search.json
@@ -0,0 +1,98 @@
+{
+ "result": {
+ "data": [
+ {
+ "id": 283,
+ "type": "TASK",
+ "phid": "PHID-TASK-fswfs3wkowjb6cyyxtyx",
+ "fields": {
+ "name": "Things are slow",
+ "description": {
+ "raw": "Things are slow but should be fast!"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589934592,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "dateClosed": 1374657042,
+ "spacePHID": null,
+ "dateCreated": 1374616241,
+ "dateModified": 1374657044,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ },
+ {
+ "id": 284,
+ "type": "TASK",
+ "phid": "PHID-TASK-5f73nyq5sjeh4cbmcsnb",
+ "fields": {
+ "name": "Things are broken",
+ "description": {
+ "raw": "Things are broken and should be fixed"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589803520,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "dateClosed": 1375049556,
+ "spacePHID": null,
+ "dateCreated": 1374616578,
+ "dateModified": 1375049556,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ }
+ ],
+ "maps": {},
+ "query": {
+ "queryKey": null
+ },
+ "cursor": {
+ "limit": "2",
+ "after": "284",
+ "before": null,
+ "order": null
+ }
+ },
+ "error_code": null,
+ "error_info": null
+}
diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..1e62d020026
--- /dev/null
+++ b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
@@ -0,0 +1,422 @@
+{
+ "version": "2.1",
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "Vulnerabilities in libxml2",
+ "message": "Vulnerabilities in libxml2 in nokogiri",
+ "description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
+ "value": "06565b64-486d-4326-b906-890d9915804d",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "usn",
+ "name": "USN-3424-1",
+ "value": "USN-3424-1",
+ "url": "https://usn.ubuntu.com/3424-1/"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Infinite recursion in parameter entities",
+ "message": "Infinite recursion in parameter entities in nokogiri",
+ "description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "value": "6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-16932",
+ "value": "CVE-2017-16932",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16932.html"
+ },
+ {
+ "url": "https://usn.ubuntu.com/usn/usn-3504-1/"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Denial of Service",
+ "message": "Denial of Service in nokogiri",
+ "description": "libxml2 incorrectly handles certain files. An attacker can use this issue with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.\r\n\r\n",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:78658378-bd8f-4d79-81c8-07c419302426",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-78658378-bd8f-4d79-81c8-07c419302426",
+ "value": "78658378-bd8f-4d79-81c8-07c419302426",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-15412",
+ "value": "CVE-2017-15412",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-15412.html"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Bypass of a protection mechanism in libxslt",
+ "message": "Bypass of a protection mechanism in libxslt in nokogiri",
+ "description": "libxslt through 1.1.33 allows bypass of a protection mechanism because callers of xsltCheckRead and xsltCheckWrite permit access even upon receiving a -1 error code. xsltCheckRead can return -1 for a crafted URL that is not actually invalid and is subsequently loaded. Vendored version of libxslt has been patched to remediate this vulnerability. Note that this patch is not yet (as of 2019-04-22) in an upstream release of libxslt.",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version if using vendored version of libxslt OR update the system library libxslt to a fixed version",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "value": "1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2019-11068",
+ "value": "CVE-2019-11068",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1892"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/CVE-2019-11068"
+ },
+ {
+ "url": "https://security-tracker.debian.org/tracker/CVE-2019-11068"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Regular Expression Denial of Service",
+ "message": "Regular Expression Denial of Service in debug",
+ "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
+ "cve": "yarn/yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest versions.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn/yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/visionmedia/debug/issues/501"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/pull/504"
+ },
+ {
+ "url": "https://nodesecurity.io/advisories/534"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
+ "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
+ "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
+ "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
+ "severity": "Unknown",
+ "solution": "Upgrade to fixed version.\r\n",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn/yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
+ "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
+ "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-11429",
+ "value": "CVE-2017-11429",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
+ },
+ {
+ "url": "https://github.com/Clever/saml2/issues/127"
+ },
+ {
+ "url": "https://www.kb.cert.org/vuls/id/475445"
+ }
+ ]
+ }
+ ],
+ "remediations": [],
+ "dependency_files": [
+ {
+ "path": "rails/Gemfile.lock",
+ "package_manager": "bundler",
+ "dependencies": [
+ {
+ "package": {
+ "name": "mini_portile2"
+ },
+ "version": "2.2.0"
+ },
+ {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ ]
+ },
+ {
+ "path": "yarn/yarn.lock",
+ "package_manager": "yarn",
+ "dependencies": [
+ {
+ "package": {
+ "name": "async"
+ },
+ "version": "0.2.10"
+ },
+ {
+ "package": {
+ "name": "async"
+ },
+ "version": "1.5.2"
+ },
+ {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ },
+ {
+ "package": {
+ "name": "ejs"
+ },
+ "version": "0.8.8"
+ },
+ {
+ "package": {
+ "name": "ms"
+ },
+ "version": "2.0.0"
+ },
+ {
+ "package": {
+ "name": "node-forge"
+ },
+ "version": "0.2.24"
+ },
+ {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ },
+ {
+ "package": {
+ "name": "sax"
+ },
+ "version": "1.2.4"
+ },
+ {
+ "package": {
+ "name": "underscore"
+ },
+ "version": "1.9.1"
+ },
+ {
+ "package": {
+ "name": "underscore"
+ },
+ "version": "1.6.0"
+ },
+ {
+ "package": {
+ "name": "xml-crypto"
+ },
+ "version": "0.8.5"
+ },
+ {
+ "package": {
+ "name": "xml-encryption"
+ },
+ "version": "0.7.4"
+ },
+ {
+ "package": {
+ "name": "xml2js"
+ },
+ "version": "0.4.19"
+ },
+ {
+ "package": {
+ "name": "xmlbuilder"
+ },
+ "version": "2.1.0"
+ },
+ {
+ "package": {
+ "name": "xmlbuilder"
+ },
+ "version": "9.0.7"
+ },
+ {
+ "package": {
+ "name": "xmldom"
+ },
+ "version": "0.1.19"
+ },
+ {
+ "package": {
+ "name": "xmldom"
+ },
+ "version": "0.1.27"
+ },
+ {
+ "package": {
+ "name": "xpath.js"
+ },
+ "version": "1.1.0"
+ },
+ {
+ "package": {
+ "name": "xpath"
+ },
+ "version": "0.0.5"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json
index 3a308bf047e..df459d9419d 100644
--- a/spec/fixtures/security-reports/master/gl-dast-report.json
+++ b/spec/fixtures/security-reports/master/gl-dast-report.json
@@ -1,40 +1,42 @@
{
- "site": {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
+ "site": [
+ {
+ "alerts": [
+ {
+ "sourceid": "3",
+ "wascid": "15",
+ "cweid": "16",
+ "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
+ "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
+ "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
+ "count": "2",
+ "pluginid": "10021",
+ "alert": "X-Content-Type-Options Header Missing",
+ "name": "X-Content-Type-Options Header Missing",
+ "riskcode": "1",
+ "confidence": "2",
+ "riskdesc": "Low (Medium)",
+ "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
+ "instances": [
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
+ }
+ ]
+ }
+ ],
+ "@ssl": "false",
+ "@port": "80",
+ "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
+ "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ }
+ ],
"@generated": "Fri, 13 Apr 2018 09:22:01",
"@version": "2.7.0"
}
diff --git a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..c96e831b027
--- /dev/null
+++ b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
@@ -0,0 +1,104 @@
+{
+ "version": "2.0",
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "Regular Expression Denial of Service",
+ "message": "Regular Expression Denial of Service in debug",
+ "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
+ "cve": "yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest versions.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://nodesecurity.io/advisories/534"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/issues/501"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/pull/504"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
+ "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
+ "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
+ "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
+ "severity": "Unknown",
+ "solution": "Upgrade to fixed version.\r\n",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
+ "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
+ "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-11429",
+ "value": "CVE-2017-11429",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
+ },
+ {
+ "url": "https://github.com/Clever/saml2/issues/127"
+ },
+ {
+ "url": "https://www.kb.cert.org/vuls/id/475445"
+ }
+ ]
+ }
+ ],
+ "remediations": [
+ {
+ "fixes": [
+ {
+ "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"
+ }
+ ],
+ "summary": "Upgrade saml2-js",
+ "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95YXJuLmxvY2sKKysrIGIveWFybi5sb2NrCkBAIC0yLDEwMyArMiwxMjQgQEAKICMgeWFybiBsb2NrZmlsZSB2MQogCiAKLWFzeW5jQH4wLjIuNzoKLSAgdmVyc2lvbiAiMC4yLjEwIgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy9hc3luYy8tL2FzeW5jLTAuMi4xMC50Z3ojYjZiYmUwYjA2NzRiOWQ3MTk3MDhjYTM4ZGU4YzIzN2NiNTI2YzNkMSIKLQotYXN5bmNAfjEuNS4yOgotICB2ZXJzaW9uICIxLjUuMiIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvYXN5bmMvLS9hc3luYy0xLjUuMi50Z3ojZWM2YTYxYWU1NjQ4MGMwYzNjYjI0MWM5NTYxOGUyMDg5MmY5NjcyYSIKK2FzeW5jQF4yLjEuNSwgYXN5bmNAXjIuNS4wOgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vYXN5bmMvLS9hc3luYy0yLjYuMS50Z3ojYjI0NWEyM2NhNzE5MzAwNDRlYzUzZmE0NmFhMDBhM2U4N2M2YTYxMCIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mTkVpTDIrQVp0NkFsQXcvMjlDcjBVRGU0c1JBSENwRUhoNTRXTXorQmI3UWZOY0Z3NGgzbG9vZnlKcExlUXM0WXg3eXVxdS8yZExnTTVoS09zNkhsUT09CisgIGRlcGVuZGVuY2llczoKKyAgICBsb2Rhc2ggIl40LjE3LjEwIgogCi1kZWJ1Z0BeMS4wLjQ6Ci0gIHZlcnNpb24gIjEuMC41IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9kZWJ1Zy8tL2RlYnVnLTEuMC41LnRneiNmNzI0MTIxNzQzMGY5OWRlYzRjMmI0NzNlYWI5MjIyOGU4NzRjMmFjIgorZGVidWdAXjIuNi4wOgorICB2ZXJzaW9uICIyLjYuOSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZGVidWcvLS9kZWJ1Zy0yLjYuOS50Z3ojNWQxMjg1MTVkZjEzNGZmMzI3ZTkwYTRjOTNmNGUwNzdhNTM2MzQxZiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1iQzdFbHJkSmFKblBiQVArMUVvdFl2cVpzYjNlY2w1d2k2QmZpNkJKVFVjTm93cDZjdnNwZzBqWHpuUlRLRGptL0U3QWRnRkJWZUFQVk1OY0tHc0hNQT09CiAgIGRlcGVuZGVuY2llczoKICAgICBtcyAiMi4wLjAiCiAKLWVqc0B+MC44LjM6Ci0gIHZlcnNpb24gIjAuOC44IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9lanMvLS9lanMtMC44LjgudGd6I2ZmZGM1NmRjYzM1ZDAyOTI2ZGQ1MGFkMTM0MzliYmM1NDA2MWQ1OTgiCitlanNAXjIuNS42OgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZWpzLy0vZWpzLTIuNi4xLnRneiM0OThlYzBkNDk1NjU1YWJjNmYyM2NkNjE4NjhkOTI2NDY0MDcxYWEwIgorICBpbnRlZ3JpdHkgc2hhNTEyLTB4eTRBL3R3ZnJSQ25raGZrOEVyRGk1RHFkQXNBcWVHeGh0NHhrQ1Vyc3ZoaGJRTnM3RSs0alYwQ043K05LSVkwYUhFNzIrWHZxdEJJWHpEMzFaYlhRPT0KKworbG9kYXNoLW5vZGVAfjIuNC4xOgorICB2ZXJzaW9uICIyLjQuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbG9kYXNoLW5vZGUvLS9sb2Rhc2gtbm9kZS0yLjQuMS50Z3ojZWE4MmY3YjEwMGM3MzNkMWE0MmFmNzY4MDFlNTA2MTA1ZTJhODBlYyIKKyAgaW50ZWdyaXR5IHNoYTEtNm9MM3NRREhNOUdrS3Zkb0FlVUdFRjRxZ093PQorCitsb2Rhc2hAXjQuMTcuMTA6CisgIHZlcnNpb24gIjQuMTcuMTEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL2xvZGFzaC8tL2xvZGFzaC00LjE3LjExLnRneiNiMzllYTYyMjllZjYwN2VjZDg5ZTJjOGRmMTI1MzY4OTFjYWM5YjhkIgorICBpbnRlZ3JpdHkgc2hhNTEyLWNRS2g4aWdvNVFVaFo3bGczOERZV0F4TXZqU0FLRzBBOHdHU1ZpbVAwN1NJVUVLMlVPK2FyU1JLYlJaV3RlbE10TjVWMEhrd2g1cnlPdG8vU3NoWUlnPT0KIAogbXNAMi4wLjA6CiAgIHZlcnNpb24gIjIuMC4wIgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9tcy8tL21zLTIuMC4wLnRneiM1NjA4YWVhZGZjMDBiZTZjMjkwMWRmNWY5ODYxNzg4ZGUwZDU5N2M4IgorICBpbnRlZ3JpdHkgc2hhMS1WZ2l1cmZ3QXZtd3BBZDlmbUdGNGplRFZsOGc9CiAKLW5vZGUtZm9yZ2VAMC4yLjI0OgotICB2ZXJzaW9uICIwLjIuMjQiCi0gIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL25vZGUtZm9yZ2UvLS9ub2RlLWZvcmdlLTAuMi4yNC50Z3ojZmE2Zjg0NmY0MmZhOTNmNjNhMGEzMGM5ZmJmZjdiNGUxMzBlMDg1OCIKK25vZGUtZm9yZ2VAXjAuNy4wOgorICB2ZXJzaW9uICIwLjcuNiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbm9kZS1mb3JnZS8tL25vZGUtZm9yZ2UtMC43LjYudGd6I2ZkZjNiNDE4YWVlMWY5NGYwZWY2NDJjZDYzNDg2Yzc3Y2E5NzI0YWMiCisgIGludGVncml0eSBzaGE1MTItc29sMzBMVXB6MWpRRkJqT0t3Ymp4aWppRTNiNnBqZDc0WXdmRDBmSk9LUGpGK2ZPTktiMllnOHJZZ1M2K2JLNlZEbCsvd2ZyNElZcEM3akR6TFVJZnc9PQogCiBzYW1sMi1qc0BeMS41LjA6Ci0gIHZlcnNpb24gIjEuNS4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYW1sMi1qcy8tL3NhbWwyLWpzLTEuNS4wLnRneiNjMGQyMjY4YTE3OWU3MzI5ZDI5ZWIyNWFhODJkZjU1MDM3NzRiMGQ5IgorICB2ZXJzaW9uICIxLjEyLjQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3NhbWwyLWpzLy0vc2FtbDItanMtMS4xMi40LnRneiNjMjg4ZjIwYmRhNmQyYjkxMDczYjE2Yzk0ZWE3MmYyMjM0OWFjM2IzIgorICBpbnRlZ3JpdHkgc2hhMS13b2p5QzlwdEs1RUhPeGJKVHFjdklqU2F3N009CiAgIGRlcGVuZGVuY2llczoKLSAgICBhc3luYyAifjEuNS4yIgotICAgIGRlYnVnICJeMS4wLjQiCi0gICAgdW5kZXJzY29yZSAifjEuNi4wIgotICAgIHhtbC1jcnlwdG8gIl4wLjguMSIKLSAgICB4bWwtZW5jcnlwdGlvbiAifjAuNy40IgotICAgIHhtbDJqcyAifjAuNC4xIgotICAgIHhtbGJ1aWxkZXIgIn4yLjEuMCIKLSAgICB4bWxkb20gIn4wLjEuMTkiCisgICAgYXN5bmMgIl4yLjUuMCIKKyAgICBkZWJ1ZyAiXjIuNi4wIgorICAgIHVuZGVyc2NvcmUgIl4xLjguMCIKKyAgICB4bWwtY3J5cHRvICJeMC4xMC4wIgorICAgIHhtbC1lbmNyeXB0aW9uICJeMC4xMS4wIgorICAgIHhtbDJqcyAiXjAuNC4wIgorICAgIHhtbGJ1aWxkZXIgIn4yLjIuMCIKKyAgICB4bWxkb20gIl4wLjEuMCIKIAogc2F4QD49MC42LjA6CiAgIHZlcnNpb24gIjEuMi40IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYXgvLS9zYXgtMS4yLjQudGd6IzI4MTYyMzRlMjM3OGJkZGM0ZTUzNTRmYWI1Y2FhODk1ZGY3MTAwZDkiCisgIGludGVncml0eSBzaGE1MTItTnFWRHY5VHBBTlVqRm0wTjh1TTVHeEwzNlVnS2k5L2F0WncreDdZRm5ROGNrd0ZHS3JsNHhYNHlXdHJleTNVSm01blAxa1VibllnTG9wcVdOU1JoV3c9PQogCi11bmRlcnNjb3JlQD49MS41Lng6Cit1bmRlcnNjb3JlQF4xLjguMDoKICAgdmVyc2lvbiAiMS45LjEiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3VuZGVyc2NvcmUvLS91bmRlcnNjb3JlLTEuOS4xLnRneiMwNmRjZTM0YTBlNjhhN2JhYmMyOWIzNjViOGU3NGI4OTI1MjAzOTYxIgorICBpbnRlZ3JpdHkgc2hhNTEyLTUvNGV0bkNrZDljOGd3Z293aTUvb20vbVlPNWFqQ2FPZ2R6ai9vVyswZVFWOVd4S0JEWnc1K3ljbUttZWFUWGpJblMvVzBCenBHTG8yeFIyYUJ3WmRnPT0KIAotdW5kZXJzY29yZUB+MS42LjA6Ci0gIHZlcnNpb24gIjEuNi4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS91bmRlcnNjb3JlLy0vdW5kZXJzY29yZS0xLjYuMC50Z3ojOGIzOGIxMGNhY2RlZjYzMzM3YjhiMjRlNGZmODZkNDVhZWE1MjlhOCIKLQoteG1sLWNyeXB0b0BeMC44LjE6Ci0gIHZlcnNpb24gIjAuOC41IgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjguNS50Z3ojMmJiY2ZiM2ViMzNmM2E4MmEyMThiODIyYmY2NzJiNmIxYzIwZTUzOCIKK3htbC1jcnlwdG9AXjAuMTAuMDoKKyAgdmVyc2lvbiAiMC4xMC4xIgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjEwLjEudGd6I2Y4MzJmNzRjY2Y1NmYyNGFmY2FlMTE2M2ExZmNhYjQ0ZDk2Nzc0YTgiCisgIGludGVncml0eSBzaGExLStETDNUTTlXOGtyOHJoRmpvZnlyUk5sbmRLZz0KICAgZGVwZW5kZW5jaWVzOgogICAgIHhtbGRvbSAiPTAuMS4xOSIKICAgICB4cGF0aC5qcyAiPj0wLjAuMyIKIAoteG1sLWVuY3J5cHRpb25AfjAuNy40OgotICB2ZXJzaW9uICIwLjcuNCIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjcuNC50Z3ojNDI3OTFlYzY0ZDU1NmQyNDU1ZGNiOWRhMGE1NDEyMzY2NWFjNjVjNyIKK3htbC1lbmNyeXB0aW9uQF4wLjExLjA6CisgIHZlcnNpb24gIjAuMTEuMiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjExLjIudGd6I2MyMTdmNTUwOTU0N2UzNGI1MDBiODI5ZjJjMGJjYTg1Y2NhNzNhMjEiCisgIGludGVncml0eSBzaGE1MTItalZ2RVM3aTVvdmRPN04rTmpnbmNBMzI2eFlLamhxZUFubnZJZ1JuWTdST0xDZkZxRURMd1AwU3hwLzMwU0hHMEFYUVYxMDQ4VDV5aW5PRnl2d0dGemc9PQogICBkZXBlbmRlbmNpZXM6Ci0gICAgYXN5bmMgIn4wLjIuNyIKLSAgICBlanMgIn4wLjguMyIKLSAgICBub2RlLWZvcmdlICIwLjIuMjQiCisgICAgYXN5bmMgIl4yLjEuNSIKKyAgICBlanMgIl4yLjUuNiIKKyAgICBub2RlLWZvcmdlICJeMC43LjAiCiAgICAgeG1sZG9tICJ+MC4xLjE1IgotICAgIHhwYXRoICIwLjAuNSIKKyAgICB4cGF0aCAiMC4wLjI3IgogCi14bWwyanNAfjAuNC4xOgoreG1sMmpzQF4wLjQuMDoKICAgdmVyc2lvbiAiMC40LjE5IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwyanMvLS94bWwyanMtMC40LjE5LnRneiM2ODZjMjBmMjEzMjA5ZTk0YWJmMGQxYmNmMWVmYWEyOTFjNzgyN2E3IgorICBpbnRlZ3JpdHkgc2hhNTEyLWVzWm5KWkpPaUpSOXdXS015dXZTRTF5NkRxNUxDdUphbnFoeHNsSDJieE02ZHVhaE5aK0hNcENMaEJRR1prYlg2eFJmOHgxWTJlSmxndDJxM3FvNDlRPT0KICAgZGVwZW5kZW5jaWVzOgogICAgIHNheCAiPj0wLjYuMCIKICAgICB4bWxidWlsZGVyICJ+OS4wLjEiCiAKLXhtbGJ1aWxkZXJAfjIuMS4wOgotICB2ZXJzaW9uICIyLjEuMCIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItMi4xLjAudGd6IzZkZGFlMzE2ODNiNmRmMTIxMDBiMjlmYzhhMGQ0ZjQ2MzQ5YWJiZWQiCit4bWxidWlsZGVyQH4yLjIuMDoKKyAgdmVyc2lvbiAiMi4yLjEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTIuMi4xLnRneiM5MzI2NDMwZjEzMGQ4NzQzNWQ0YzQwODY2NDNhYTI5MjZlMTA1YTMyIgorICBpbnRlZ3JpdHkgc2hhMS1reVpERHhNTmgwTmRURUNHWkRxaWttNFFXakk9CiAgIGRlcGVuZGVuY2llczoKLSAgICB1bmRlcnNjb3JlICI+PTEuNS54IgorICAgIGxvZGFzaC1ub2RlICJ+Mi40LjEiCiAKIHhtbGJ1aWxkZXJAfjkuMC4xOgogICB2ZXJzaW9uICI5LjAuNyIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItOS4wLjcudGd6IzEzMmVlNjNkMmVjNTU2NWM1NTdlMjBmNGMyMmRmOWFjYTY4NmIxMGQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTkuMC43LnRneiMxMzJlZTYzZDJlYzU1NjVjNTU3ZTIwZjRjMjJkZjlhY2E2ODZiMTBkIgorICBpbnRlZ3JpdHkgc2hhMS1FeTdtUFM3RlZseFZmaUQwd2kzNXJLYUdzUTA9CiAKIHhtbGRvbUA9MC4xLjE5OgogICB2ZXJzaW9uICIwLjEuMTkiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGRvbS8tL3htbGRvbS0wLjEuMTkudGd6IzYzMWZjMDc3NzZlZmQ4NDExOGJmMjUxNzFiMzdlZDRkMDc1YTBhYmMiCisgIGludGVncml0eSBzaGExLVl4L0FkM2J2MkVFWXZ5VVhHemZ0VFFkYUNydz0KIAoteG1sZG9tQH4wLjEuMTUsIHhtbGRvbUB+MC4xLjE5OgoreG1sZG9tQF4wLjEuMCwgeG1sZG9tQH4wLjEuMTU6CiAgIHZlcnNpb24gIjAuMS4yNyIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sZG9tLy0veG1sZG9tLTAuMS4yNy50Z3ojZDUwMWY5N2IzYmRiNDAzYWY4ZWY5ZWNjMjA1NzMxODdhYWRhYzBlOSIKKyAgaW50ZWdyaXR5IHNoYTEtMVFINWV6dmJRRHI0NzU3TUlGY3hoNnJhd09rPQogCiB4cGF0aC5qc0A+PTAuMC4zOgogICB2ZXJzaW9uICIxLjEuMCIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGguanMvLS94cGF0aC5qcy0xLjEuMC50Z3ojMzgxNmE0NGVkNGJiMzUyMDkxMDgzZDAwMmEzODNkZDUxMDRhNWZmMSIKKyAgaW50ZWdyaXR5IHNoYTUxMi1qZytxa2ZTNEs4RTc5NjVzcWFVbDhtUm5nWGlLYjNXWkdmT05nRTE4cHIwM0ZVUWl1U1Y2RytFajR0UzU1QitySVFTRkVJdzNwaGRWQVE0cFBxTldmUT09CiAKLXhwYXRoQDAuMC41OgotICB2ZXJzaW9uICIwLjAuNSIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGgvLS94cGF0aC0wLjAuNS50Z3ojNDU0MDM2ZjZlZjBmM2RmNWFmNWQ0YmE0YTExOWZiNzU2NzRiM2U2YyIKK3hwYXRoQDAuMC4yNzoKKyAgdmVyc2lvbiAiMC4wLjI3IgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94cGF0aC8tL3hwYXRoLTAuMC4yNy50Z3ojZGQzNDIxZmJkY2M1NjQ2YWMzMmM0ODUzMWI0ZDdlOWQwYzJjZmE5MiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mZzAzV1J4dGtDVjZvaENsZVBOQUVDWXNtcEtLVHY1TDh5L1gzRG4xaFFyZWMzUE94MmpIWi8wUDJxUTZIdnNyVTFCbWVxWGNvZjNOR0d1ZUc2THh3UT09Cg=="
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/remediations/remediation.patch b/spec/fixtures/security-reports/remediations/remediation.patch
new file mode 100644
index 00000000000..bbfb6874627
--- /dev/null
+++ b/spec/fixtures/security-reports/remediations/remediation.patch
@@ -0,0 +1,180 @@
+diff --git a/yarn.lock b/yarn.lock
+index 0ecc92f..7fa4554 100644
+--- a/yarn.lock
++++ b/yarn.lock
+@@ -2,103 +2,124 @@
+ # yarn lockfile v1
+
+
+-async@~0.2.7:
+- version "0.2.10"
+- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+-
+-async@~1.5.2:
+- version "1.5.2"
+- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
++async@^2.1.5, async@^2.5.0:
++ version "2.6.1"
++ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
++ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
++ dependencies:
++ lodash "^4.17.10"
+
+-debug@^1.0.4:
+- version "1.0.5"
+- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
++debug@^2.6.0:
++ version "2.6.9"
++ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
++ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+-ejs@~0.8.3:
+- version "0.8.8"
+- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
++ejs@^2.5.6:
++ version "2.6.1"
++ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
++ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
++
++lodash-node@~2.4.1:
++ version "2.4.1"
++ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec"
++ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=
++
++lodash@^4.17.10:
++ version "4.17.11"
++ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
++ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+
+ ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
++ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+-node-forge@0.2.24:
+- version "0.2.24"
+- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
++node-forge@^0.7.0:
++ version "0.7.6"
++ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
++ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
+
+ saml2-js@^1.5.0:
+- version "1.5.0"
+- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
++ version "1.12.4"
++ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3"
++ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M=
+ dependencies:
+- async "~1.5.2"
+- debug "^1.0.4"
+- underscore "~1.6.0"
+- xml-crypto "^0.8.1"
+- xml-encryption "~0.7.4"
+- xml2js "~0.4.1"
+- xmlbuilder "~2.1.0"
+- xmldom "~0.1.19"
++ async "^2.5.0"
++ debug "^2.6.0"
++ underscore "^1.8.0"
++ xml-crypto "^0.10.0"
++ xml-encryption "^0.11.0"
++ xml2js "^0.4.0"
++ xmlbuilder "~2.2.0"
++ xmldom "^0.1.0"
+
+ sax@>=0.6.0:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
++ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+
+-underscore@>=1.5.x:
++underscore@^1.8.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
++ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
+
+-underscore@~1.6.0:
+- version "1.6.0"
+- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+-
+-xml-crypto@^0.8.1:
+- version "0.8.5"
+- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
++xml-crypto@^0.10.0:
++ version "0.10.1"
++ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8"
++ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=
+ dependencies:
+ xmldom "=0.1.19"
+ xpath.js ">=0.0.3"
+
+-xml-encryption@~0.7.4:
+- version "0.7.4"
+- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
++xml-encryption@^0.11.0:
++ version "0.11.2"
++ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21"
++ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==
+ dependencies:
+- async "~0.2.7"
+- ejs "~0.8.3"
+- node-forge "0.2.24"
++ async "^2.1.5"
++ ejs "^2.5.6"
++ node-forge "^0.7.0"
+ xmldom "~0.1.15"
+- xpath "0.0.5"
++ xpath "0.0.27"
+
+-xml2js@~0.4.1:
++xml2js@^0.4.0:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
++ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
+ dependencies:
+ sax ">=0.6.0"
+ xmlbuilder "~9.0.1"
+
+-xmlbuilder@~2.1.0:
+- version "2.1.0"
+- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
++xmlbuilder@~2.2.0:
++ version "2.2.1"
++ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32"
++ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=
+ dependencies:
+- underscore ">=1.5.x"
++ lodash-node "~2.4.1"
+
+ xmlbuilder@~9.0.1:
+ version "9.0.7"
+- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
++ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
++ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
+
+ xmldom@=0.1.19:
+ version "0.1.19"
+ resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
++ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
+
+-xmldom@~0.1.15, xmldom@~0.1.19:
++xmldom@^0.1.0, xmldom@~0.1.15:
+ version "0.1.27"
+ resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
++ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
+
+ xpath.js@>=0.0.3:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
++ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==
+
+-xpath@0.0.5:
+- version "0.0.5"
+- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
++xpath@0.0.27:
++ version "0.0.27"
++ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"
++ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
diff --git a/spec/fixtures/security-reports/remediations/yarn.lock b/spec/fixtures/security-reports/remediations/yarn.lock
new file mode 100644
index 00000000000..0ecc92fb711
--- /dev/null
+++ b/spec/fixtures/security-reports/remediations/yarn.lock
@@ -0,0 +1,104 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+async@~0.2.7:
+ version "0.2.10"
+ resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
+async@~1.5.2:
+ version "1.5.2"
+ resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+debug@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
+ dependencies:
+ ms "2.0.0"
+
+ejs@~0.8.3:
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+node-forge@0.2.24:
+ version "0.2.24"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
+
+saml2-js@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
+ dependencies:
+ async "~1.5.2"
+ debug "^1.0.4"
+ underscore "~1.6.0"
+ xml-crypto "^0.8.1"
+ xml-encryption "~0.7.4"
+ xml2js "~0.4.1"
+ xmlbuilder "~2.1.0"
+ xmldom "~0.1.19"
+
+sax@>=0.6.0:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+
+underscore@>=1.5.x:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
+
+underscore@~1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+
+xml-crypto@^0.8.1:
+ version "0.8.5"
+ resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
+ dependencies:
+ xmldom "=0.1.19"
+ xpath.js ">=0.0.3"
+
+xml-encryption@~0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
+ dependencies:
+ async "~0.2.7"
+ ejs "~0.8.3"
+ node-forge "0.2.24"
+ xmldom "~0.1.15"
+ xpath "0.0.5"
+
+xml2js@~0.4.1:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
+ dependencies:
+ sax ">=0.6.0"
+ xmlbuilder "~9.0.1"
+
+xmlbuilder@~2.1.0:
+ version "2.1.0"
+ resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
+ dependencies:
+ underscore ">=1.5.x"
+
+xmlbuilder@~9.0.1:
+ version "9.0.7"
+ resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
+
+xmldom@=0.1.19:
+ version "0.1.19"
+ resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
+
+xmldom@~0.1.15, xmldom@~0.1.19:
+ version "0.1.27"
+ resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
+
+xpath.js@>=0.0.3:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
+
+xpath@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace
index 3d8beb0dec2..8f9747f8143 100644
--- a/spec/fixtures/trace/sample_trace
+++ b/spec/fixtures/trace/sample_trace
@@ -1795,7 +1795,7 @@ GroupsController
when requesting a redirected path
returns not found
PUT transfer
- when transfering to a subgroup goes right
+ when transferring to a subgroup goes right
should return a notice (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should redirect to the new path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
when converting to a root group goes right
@@ -2299,7 +2299,7 @@ Groups::TransferService
should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
- when transfering a group with nested groups and projects
+ when transferring a group with nested groups and projects
should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
@@ -2426,9 +2426,9 @@ Groups::MilestonesController
lists legacy group milestones and group milestones
#show
when there is a title parameter
- searchs for a legacy group milestone
+ searches for a legacy group milestone
when there is not a title parameter
- searchs for a group milestone
+ searches for a group milestone
behaves like milestone tabs
#merge_requests
as html
@@ -3109,11 +3109,11 @@ Pending: (Failures listed here are expected and do not affect your suite's statu
# around hook at ./spec/spec_helper.rb:186 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:129
- 15) GroupsController PUT transfer when transfering to a subgroup goes right should return a notice
+ 15) GroupsController PUT transfer when transferring to a subgroup goes right should return a notice
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:516
- 16) GroupsController PUT transfer when transfering to a subgroup goes right should redirect to the new path
+ 16) GroupsController PUT transfer when transferring to a subgroup goes right should redirect to the new path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:520
@@ -3301,15 +3301,15 @@ Pending: (Failures listed here are expected and do not affect your suite's statu
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:341
- 63) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update subgroups path
+ 63) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update subgroups path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:363
- 64) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update projects path
+ 64) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update projects path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:375
- 65) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should create redirect for the subgroups and projects
+ 65) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should create redirect for the subgroups and projects
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:383
diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po
index dbe2f952bad..155b6cbb95d 100644
--- a/spec/fixtures/valid.po
+++ b/spec/fixtures/valid.po
@@ -35,9 +35,6 @@ msgid_plural "%d pipelines"
msgstr[0] "1 pipeline"
msgstr[1] "%d pipelines"
-msgid "A collection of graphs regarding Continuous Integration"
-msgstr "Una colección de gráficos sobre Integración Continua"
-
msgid "About auto deploy"
msgstr "Acerca del auto despliegue"
diff --git a/spec/fixtures/x509_certificate.crt b/spec/fixtures/x509_certificate.crt
new file mode 100644
index 00000000000..8a84890b928
--- /dev/null
+++ b/spec/fixtures/x509_certificate.crt
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEnDCCAoQCAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJYXV0aG9yaXR5
+MB4XDTE4MDMxOTE1MjYzMloXDTE5MDMxOTE1MjYzMlowFDESMBAGA1UEAwwJbG9j
+YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+tcM7iphsLlR
+ccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzAn/eVU4jyVWkaBym6MHa8CiDOro9H
+OXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/2FAgFWzrB2HnYSShiN8tBeeDI5cJ
+ii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UTK37k2kbDQZ41rv1ng2w0AUZt0LRA
+NWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ
++1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNkI+cyv0Gle6tk+CkOfE1m0CvNWlNg
+b8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5xMXpdUCsh22CZZHe/4SeFE64amkf
+1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q/nLdY8haMC6KOtpbAWvKX/Jqq0z1
+nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVrVef0pb2mfdtzjzUrYCP0PtnQExPB
+rocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8RSvAoEUs9VbPiUfN7WAyU1K1rTYH
+KV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRlq07Q5LDz33h9KXw1LZT8MWRinVJf
+RePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA
+Skp0tbvVsg3RG2pX0GP25j0ix+f78zG0+BJ6LiKGMoCIBtGKitfUjBg83ru/ILpa
+fpgrQpNQVUnGQ9tmpnqV605ZBBRUC1CRDsvUnyN6p7+yQAq6Fl+2ZKONHpPk+Bl4
+CIewgdkHjTwTpvIM/1DFVCz4R1FxNjY3uqOVcNDczMYEk2Pn2GZNNN35hUHHxWh4
+89ZvI+XKuRFZq3cDPA60PySeJJpCRScWGgnkdEX1gTtWH3WUlq9llxIvRexyNyzZ
+Yqvcfx5UT75/Pp+JPh9lpUCcKLHeUiadjkiLxu3IcrYa4gYx4lA8jgm7adNEahd0
+oMAHoO9DU6XMo7o6tnQH3xQv9RAbQanjuyJR9N7mwmc59bQ6mW+pxCk843GwT73F
+slseJ1nE1fQQQD7mn/KGjmeWtxY2ElUjTay9ff9/AgJeQYRW+oH0cSdo8WCpc2+G
++LZtLWfBgFLHseRlmarSe2pP8KmbaTd3q7Bu0GekVQOxYcNX59Pj4muQZDVLh8aX
+mSQ+Ifts/ljT649MISHn2AZMR4+BUx63tFcatQhbAGGH5LeFdbaGcaVdsUVyZ9a2
+HBmFWNsgEPtcC+WmNzCXbv7jQsLAJXufKG5MnurJgNf/n5uKCmpGsEJDT/KF1k/3
+x9YnqM7zTyV6un+LS3HjEJvwQmqPWe+vFAeXWGCoWxE=
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/x509_certificate_pk.key b/spec/fixtures/x509_certificate_pk.key
new file mode 100644
index 00000000000..c02a3cf6189
--- /dev/null
+++ b/spec/fixtures/x509_certificate_pk.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEA+tcM7iphsLlRccUph2ixabRYnw1HeLCiA4O9a4O31oVUBuzA
+n/eVU4jyVWkaBym6MHa8CiDOro9HOXodITMw+3G1sG/yQZ8Y/5dsOP2hEoSfs63/
+2FAgFWzrB2HnYSShiN8tBeeDI5cJii4JVMfpfi9cvXZUXFR8+P0XR1HDxx6or6UT
+K37k2kbDQZ41rv1ng2w0AUZt0LRANWVE48zvUWIU0y+2JLP1yhrKj85RRjQc5cMK
+88zzWSZBcSjDGGeJ4C8B5Zh2gFlQ+1aJkyyklORR3v/RyYO9prTeXPqQ3x/nNsNk
+I+cyv0Gle6tk+CkOfE1m0CvNWlNgb8LdQ0XZsOYLZvxfpHk3gHA5GrHXvn5StkM5
+xMXpdUCsh22CZZHe/4SeFE64amkf1/LuqY0LYc5UdG2SeJ0SDauPRAIuAr4OV7+Q
+/nLdY8haMC6KOtpbAWvKX/Jqq0z1nUXzQn1JWCNw1QMdq9Uz8wiWOjLTr2D/mIVr
+Vef0pb2mfdtzjzUrYCP0PtnQExPBrocP6BDXN7Ragcdis5/IfLuCOD6pAkmzy6o8
+RSvAoEUs9VbPiUfN7WAyU1K1rTYHKV+zPfWF254nZ2SBeReN9CMKbMJE+TX2chRl
+q07Q5LDz33h9KXw1LZT8MWRinVJfRePsQiyHpRBWRG0AhbD+YpiGKHzsat0CAwEA
+AQKCAgBf1urJ1Meeji/gGETVx9qBWLbDjn9QTayZSyyEd78155tDShIPDLmxQRHW
+MGIReo/5FGSkOgS+DWBZRZ77oGOGrtuMnjkheXhDr8dZvw5b1PBv5ntqWrLnfMYP
+/Ag7xZMyiJLbPqmMX5j1gsFt8zPzUoVMnnl9DYryV0Edrs/utHgfJCM+6yzleUQB
+PkGkqo1yWVVFZ3Nt2nDt9dNsdlC594+dYQ1m2JuArNvYNiw3dpHT98GnhRc1aLh4
+U+q22FiFn3BKGQat43JdlaLa6KO5f8MIQRYWuI8tss2DGPlhRv9AnUcVsLBjAuIH
+bmUVrBosxCYUQ6giatjd2sZPfdC+VIDCbIWRthxkXJ9I/Ap8R98xx/7qIcPFc+XA
+hcK1xOM7zIq2xgAOFeeh8O8Wq9cH8NmUhMCgzIE0WT32Zo0JAW6l0kZc82Y/Yofz
+U+TJKo0NOFZe687HOhanOHbbQSG29XOqxMYTABZ7Ixf+4RZPD5+yQgZWP1BhLluy
+PxZhsLl67xvbfB2i9VVorMN7PbFx5hbni3C7/p63Z0rG5q4/uJBbX3Uuh6KdhIo+
+Zh9UC6u29adIthdxz+ZV5wBccTOgaeHB9wRL9Hbp6ZxyqesQB4RTsFtPNXxZ7K43
+fmJgHZvHhF5gSbeB8JAeBf0cy3pytJM49ZxplifeGVzUJP2gAQKCAQEA/1T9quz5
+sOD03FxV//oRWD1kqfunq3v56sIBG4ZMVZKUqc6wLjTmeklLYKq85AWX8gnCHi0g
+nmG/xDh/rt1/IngMWP98WVuD67hFbrj87g7A7YGIiwZ2gi6hqhqmALN+5JjCSTPp
+XOiPvNnXP0XM4gIHBXV8diHq5rF9NsSh4vx3OExr8KQqVzWoDcnnWNfnDlrFB8cq
+ViII+UqdovXp59hAVOsc+pYAe+8JeQDX17H3U/NMkUw4gU2aWUCvUVjxi9oBG/CW
+ncIdYuW8zne4qXbX7YLC0QUUIDVOWzhLauAUBduTqRTldJo0KAxu887tf+uStXs8
+RACLGIaBQw7BXQKCAQEA+38NFnpflKquU92xRtmqWAVaW7rm865ZO6EIaS4JII/N
+/Ebu1YZrAhT0ruGJQaolYj8w79BEZRF2CYDPZxKFv/ye0O7rWCAGtCdWQ0BXcrIU
+7SdlsdfTNXO1R3WbwCyVxyjg6YF7FjbTaaOAoTiosTjDs2ZOgkbdh/sMeWkSN5HB
+aQz4c8rqq0kkYucLqp4nWYSWSJn88bL8ctwEwW77MheJiSpo1ohNRP3ExHnbCbYw
+RIj7ATSz74ebpd9NMauB5clvMMh4jRG0EQyt7KCoOyfPRFc3fddvTr03LlgFfX/n
+qoxd2nejgAS3NnG1XMxdcUa7cPannt46Sef1uZo3gQKCAQB454zquCYQDKXGBu8u
+NAKsjv2wxBqESENyV4VgvDo/NxawRdAFQUV12GkaEB87ti5aDSbfVS0h8lV1G+/S
+JM5DyybFqcz/Hyebofk20d/q9g+DJ5g5hMjvIhepTc8Xe+d1ZaRyN2Oke/c8TMbx
+DiNTTfR3MEfMRIlPzfHl0jx6GGR3wzBFleb6vsyiIt4qoqmlkXPFGBlDCgDH0v5M
+ITgucacczuw8+HSoOut4Yd7TI1FjbkzubHJBQDb7VnbuBTjzqTpnOYiIkVeK8hBy
+kBxgGodqz0Vi5o2+Jp/A8Co+JHc2wt/r65ovmali4WhUiMLLlQg2aXGDHeK/rUle
+MIl9AoIBAQCPKCYSCnyHypRK5uG3W8VsLzfdCUnXogHnQGXiQTMu1szA8ruWzdnx
+qG4TcgxIVYrMHv5DNAEKquLOzATDPjbmLu1ULvvGAQzv1Yhz5ZchkZ7507g+gIUY
+YxHoaFjNDlP/txQ3tt2SqoizFD/vBap4nsA/SVgdLiuB8PSL07Rr70rx+lEe0H2+
+HHda2Pu6FiZ9/Uvybb0e8+xhkT4fwYW5YM6IRpzAqXuabv1nfZmiMJPPH04JxK88
+BKwjwjVVtbPOUlg5o5ODcXVXUylZjaXVbna8Bw1uU4hngKt9dNtDMeB0I0x1RC7M
+e2Ky2g0LksUJ6uJdjfmiJAt38FLeYJuBAoIBAC2oqaqr86Dug5v8xHpgFoC5u7z7
+BRhaiHpVrUr+wnaNJEXfAEmyKf4xF5xDJqldnYG3c9ETG/7bLcg1dcrMPzXx94Si
+MI3ykwiPeI/sVWYmUlq4U8zCIC7MY6sWzWt3oCBNoCN/EeYx9e7+eLNBB+fADAXq
+v9RMGlUIy7beX0uac8Bs771dsxIb/RrYw58wz+jrwGlzuDmcPWiu+ARu7hnBqCAV
+AITlCV/tsEk7u08oBuv47+rVGCh1Qb19pNswyTtTZARAGErJO0Q+39BNuu0M2TIn
+G3M8eNmGHC+mNsZTVgKRuyk9Ye0s4Bo0KcqSndiPFGHjcrF7/t+RqEOXr/E=
+-----END RSA PRIVATE KEY-----
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 046215e4c93..e78a38d31f5 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -2,8 +2,15 @@
env:
jest/globals: true
plugins:
-- jest
+ - jest
+extends:
+ - 'plugin:jest/recommended'
settings:
import/resolver:
jest:
- jestConfigFile: "jest.config.js"
+ jestConfigFile: 'jest.config.js'
+globals:
+ getJSONFixture: false
+ loadFixtures: false
+ preloadFixtures: false
+ setFixtures: false
diff --git a/spec/frontend/__mocks__/file_mock.js b/spec/frontend/__mocks__/file_mock.js
new file mode 100644
index 00000000000..08d725cd4e4
--- /dev/null
+++ b/spec/frontend/__mocks__/file_mock.js
@@ -0,0 +1 @@
+export default '';
diff --git a/spec/frontend/activities_spec.js b/spec/frontend/activities_spec.js
new file mode 100644
index 00000000000..d14be3a1f26
--- /dev/null
+++ b/spec/frontend/activities_spec.js
@@ -0,0 +1,70 @@
+/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */
+
+import $ from 'jquery';
+import Activities from '~/activities';
+import Pager from '~/pager';
+
+describe('Activities', () => {
+ window.gon || (window.gon = {});
+ const fixtureTemplate = 'static/event_filter.html';
+ const filters = [
+ {
+ id: 'all',
+ },
+ {
+ id: 'push',
+ name: 'push events',
+ },
+ {
+ id: 'merged',
+ name: 'merge events',
+ },
+ {
+ id: 'comments',
+ },
+ {
+ id: 'team',
+ },
+ ];
+
+ function getEventName(index) {
+ const filter = filters[index];
+ return filter.hasOwnProperty('name') ? filter.name : filter.id;
+ }
+
+ function getSelector(index) {
+ const filter = filters[index];
+ return `#${filter.id}_event_filter`;
+ }
+
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ jest.spyOn(Pager, 'init').mockImplementation(() => {});
+ new Activities();
+ });
+
+ for (let i = 0; i < filters.length; i += 1) {
+ (i => {
+ describe(`when selecting ${getEventName(i)}`, () => {
+ beforeEach(() => {
+ $(getSelector(i)).click();
+ });
+
+ for (let x = 0; x < filters.length; x += 1) {
+ (x => {
+ const shouldHighlight = i === x;
+ const testName = shouldHighlight ? 'should highlight' : 'should not highlight';
+
+ it(`${testName} ${getEventName(x)}`, () => {
+ expect(
+ $(getSelector(x))
+ .parent()
+ .hasClass('active'),
+ ).toEqual(shouldHighlight);
+ });
+ })(x);
+ }
+ });
+ })(i);
+ }
+});
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
new file mode 100644
index 00000000000..6010488d9e0
--- /dev/null
+++ b/spec/frontend/api_spec.js
@@ -0,0 +1,477 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import Api from '~/api';
+
+describe('Api', () => {
+ const dummyApiVersion = 'v3000';
+ const dummyUrlRoot = '/gitlab';
+ const dummyGon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
+ let originalGon;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ originalGon = window.gon;
+ window.gon = Object.assign({}, dummyGon);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ window.gon = originalGon;
+ });
+
+ describe('buildUrl', () => {
+ it('adds URL root and fills in API version', () => {
+ const input = '/api/:version/foo/bar';
+ const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`;
+
+ const builtUrl = Api.buildUrl(input);
+
+ expect(builtUrl).toEqual(expectedOutput);
+ });
+
+ [null, '', '/'].forEach(root => {
+ it(`works when relative_url_root is ${root}`, () => {
+ window.gon.relative_url_root = root;
+ const input = '/api/:version/foo/bar';
+ const expectedOutput = `/api/${dummyApiVersion}/foo/bar`;
+
+ const builtUrl = Api.buildUrl(input);
+
+ expect(builtUrl).toEqual(expectedOutput);
+ });
+ });
+ });
+
+ describe('group', () => {
+ it('fetches a group', done => {
+ const groupId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ name: 'test',
+ });
+
+ Api.group(groupId, response => {
+ expect(response.name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('groupMembers', () => {
+ it('fetches group members', done => {
+ const groupId = '54321';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`;
+ const expectedData = [{ id: 7 }];
+ mock.onGet(expectedUrl).reply(200, expectedData);
+
+ Api.groupMembers(groupId)
+ .then(({ data }) => {
+ expect(data).toEqual(expectedData);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('groups', () => {
+ it('fetches groups', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.groups(query, options, response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('namespaces', () => {
+ it('fetches namespaces', done => {
+ const query = 'dummy query';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.namespaces(query, response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('projects', () => {
+ it('fetches projects with membership when logged in', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
+ window.gon.current_user_id = 1;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.projects(query, options, response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ done();
+ });
+ });
+
+ it('fetches projects without membership when not logged in', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.projects(query, options, response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('projectMergeRequests', () => {
+ const projectPath = 'abc';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`;
+
+ it('fetches all merge requests for a project', done => {
+ const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }];
+ mock.onGet(expectedUrl).reply(200, mockData);
+ Api.projectMergeRequests(projectPath)
+ .then(({ data }) => {
+ expect(data.length).toEqual(2);
+ expect(data[0].source_branch).toBe('foo');
+ expect(data[1].source_branch).toBe('bar');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('fetches merge requests filtered with passed params', done => {
+ const params = {
+ source_branch: 'bar',
+ };
+ const mockData = [{ source_branch: 'bar' }];
+ mock.onGet(expectedUrl, { params }).reply(200, mockData);
+
+ Api.projectMergeRequests(projectPath, params)
+ .then(({ data }) => {
+ expect(data.length).toEqual(1);
+ expect(data[0].source_branch).toBe('bar');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('projectMergeRequest', () => {
+ it('fetches a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ title: 'test',
+ });
+
+ Api.projectMergeRequest(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.title).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('projectMergeRequestChanges', () => {
+ it('fetches the changes of a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
+ mock.onGet(expectedUrl).reply(200, {
+ title: 'test',
+ });
+
+ Api.projectMergeRequestChanges(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.title).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('projectMergeRequestVersions', () => {
+ it('fetches the versions of a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ id: 123,
+ },
+ ]);
+
+ Api.projectMergeRequestVersions(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].id).toBe(123);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('projectRunners', () => {
+ it('fetches the runners of a project', done => {
+ const projectPath = 7;
+ const params = { scope: 'active' };
+ const mockData = [{ id: 4 }];
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`;
+ mock.onGet(expectedUrl, { params }).reply(200, mockData);
+
+ Api.projectRunners(projectPath, { params })
+ .then(({ data }) => {
+ expect(data).toEqual(mockData);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('newLabel', () => {
+ it('creates a new label', done => {
+ const namespace = 'some namespace';
+ const project = 'some project';
+ const labelData = { some: 'data' };
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`;
+ const expectedData = {
+ label: labelData,
+ };
+ mock.onPost(expectedUrl).reply(config => {
+ expect(config.data).toBe(JSON.stringify(expectedData));
+
+ return [
+ 200,
+ {
+ name: 'test',
+ },
+ ];
+ });
+
+ Api.newLabel(namespace, project, labelData, response => {
+ expect(response.name).toBe('test');
+ done();
+ });
+ });
+
+ it('creates a group label', done => {
+ const namespace = 'group/subgroup';
+ const labelData = { some: 'data' };
+ const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace);
+ const expectedData = {
+ label: labelData,
+ };
+ mock.onPost(expectedUrl).reply(config => {
+ expect(config.data).toBe(JSON.stringify(expectedData));
+
+ return [
+ 200,
+ {
+ name: 'test',
+ },
+ ];
+ });
+
+ Api.newLabel(namespace, undefined, labelData, response => {
+ expect(response.name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('groupProjects', () => {
+ it('fetches group projects', done => {
+ const groupId = '123456';
+ const query = 'dummy query';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.groupProjects(groupId, query, {}, response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('issueTemplate', () => {
+ it('fetches an issue template', done => {
+ const namespace = 'some namespace';
+ const project = 'some project';
+ const templateKey = ' template #%?.key ';
+ const templateType = 'template type';
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
+ templateKey,
+ )}`;
+ mock.onGet(expectedUrl).reply(200, 'test');
+
+ Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
+ expect(response).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('projectTemplates', () => {
+ it('fetches a list of templates', done => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`;
+
+ mock.onGet(expectedUrl).reply(200, 'test');
+
+ Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => {
+ expect(response).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('projectTemplate', () => {
+ it('fetches a single template', done => {
+ const data = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`;
+
+ mock.onGet(expectedUrl).reply(200, 'test');
+
+ Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => {
+ expect(response).toBe('test');
+ done();
+ });
+ });
+ });
+
+ describe('users', () => {
+ it('fetches users', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.users(query, options)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].name).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('user', () => {
+ it('fetches single user', done => {
+ const userId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ name: 'testuser',
+ });
+
+ Api.user(userId)
+ .then(({ data }) => {
+ expect(data.name).toBe('testuser');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('user status', () => {
+ it('fetches single user status', done => {
+ const userId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`;
+ mock.onGet(expectedUrl).reply(200, {
+ message: 'testmessage',
+ });
+
+ Api.userStatus(userId)
+ .then(({ data }) => {
+ expect(data.message).toBe('testmessage');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('commitPipelines', () => {
+ it('fetches pipelines for a given commit', done => {
+ const projectId = 'example/foobar';
+ const commitSha = 'abc123def';
+ const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.commitPipelines(projectId, commitSha)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].name).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('createBranch', () => {
+ it('creates new branch', done => {
+ const ref = 'master';
+ const branch = 'new-branch-name';
+ const dummyProjectPath = 'gitlab-org/gitlab-ce';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
+ dummyProjectPath,
+ )}/repository/branches`;
+
+ jest.spyOn(axios, 'post');
+
+ mock.onPost(expectedUrl).replyOnce(200, {
+ name: branch,
+ });
+
+ Api.createBranch(dummyProjectPath, { ref, branch })
+ .then(({ data }) => {
+ expect(data.name).toBe(branch);
+ expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js
new file mode 100644
index 00000000000..4d9c8f96d62
--- /dev/null
+++ b/spec/frontend/autosave_spec.js
@@ -0,0 +1,151 @@
+import $ from 'jquery';
+import Autosave from '~/autosave';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+
+describe('Autosave', () => {
+ useLocalStorageSpy();
+
+ let autosave;
+ const field = $('<textarea></textarea>');
+ const key = 'key';
+
+ describe('class constructor', () => {
+ beforeEach(() => {
+ jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
+ jest.spyOn(Autosave.prototype, 'restore').mockImplementation(() => {});
+ });
+
+ it('should set .isLocalStorageAvailable', () => {
+ autosave = new Autosave(field, key);
+
+ expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
+ expect(autosave.isLocalStorageAvailable).toBe(true);
+ });
+ });
+
+ describe('restore', () => {
+ beforeEach(() => {
+ autosave = {
+ field,
+ key,
+ };
+ });
+
+ describe('if .isLocalStorageAvailable is `false`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = false;
+
+ Autosave.prototype.restore.call(autosave);
+ });
+
+ it('should not call .getItem', () => {
+ expect(window.localStorage.getItem).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('if .isLocalStorageAvailable is `true`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = true;
+ });
+
+ it('should call .getItem', () => {
+ Autosave.prototype.restore.call(autosave);
+
+ expect(window.localStorage.getItem).toHaveBeenCalledWith(key);
+ });
+
+ it('triggers jquery event', () => {
+ jest.spyOn(autosave.field, 'trigger').mockImplementation(() => {});
+
+ Autosave.prototype.restore.call(autosave);
+
+ expect(field.trigger).toHaveBeenCalled();
+ });
+
+ it('triggers native event', done => {
+ autosave.field.get(0).addEventListener('change', () => {
+ done();
+ });
+
+ Autosave.prototype.restore.call(autosave);
+ });
+ });
+
+ describe('if field gets deleted from DOM', () => {
+ beforeEach(() => {
+ autosave.field = $('.not-a-real-element');
+ });
+
+ it('does not trigger event', () => {
+ jest.spyOn(field, 'trigger');
+
+ expect(field.trigger).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('save', () => {
+ beforeEach(() => {
+ autosave = { reset: jest.fn() };
+ autosave.field = field;
+ field.val('value');
+ });
+
+ describe('if .isLocalStorageAvailable is `false`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = false;
+
+ Autosave.prototype.save.call(autosave);
+ });
+
+ it('should not call .setItem', () => {
+ expect(window.localStorage.setItem).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('if .isLocalStorageAvailable is `true`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = true;
+
+ Autosave.prototype.save.call(autosave);
+ });
+
+ it('should call .setItem', () => {
+ expect(window.localStorage.setItem).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('reset', () => {
+ beforeEach(() => {
+ autosave = {
+ key,
+ };
+ });
+
+ describe('if .isLocalStorageAvailable is `false`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = false;
+
+ Autosave.prototype.reset.call(autosave);
+ });
+
+ it('should not call .removeItem', () => {
+ expect(window.localStorage.removeItem).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('if .isLocalStorageAvailable is `true`', () => {
+ beforeEach(() => {
+ autosave.isLocalStorageAvailable = true;
+
+ Autosave.prototype.reset.call(autosave);
+ });
+
+ it('should call .removeItem', () => {
+ expect(window.localStorage.removeItem).toHaveBeenCalledWith(key);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/frontend/behaviors/secret_values_spec.js
index 5aaab093c0c..5aaab093c0c 100644
--- a/spec/javascripts/behaviors/secret_values_spec.js
+++ b/spec/frontend/behaviors/secret_values_spec.js
diff --git a/spec/javascripts/blob/blob_fork_suggestion_spec.js b/spec/frontend/blob/blob_fork_suggestion_spec.js
index 9b81b7e6f92..9b81b7e6f92 100644
--- a/spec/javascripts/blob/blob_fork_suggestion_spec.js
+++ b/spec/frontend/blob/blob_fork_suggestion_spec.js
diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js
new file mode 100644
index 00000000000..4dd27e94d97
--- /dev/null
+++ b/spec/frontend/boards/modal_store_spec.js
@@ -0,0 +1,134 @@
+/* global ListIssue */
+
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/issue';
+import '~/boards/models/list';
+import Store from '~/boards/stores/modal_store';
+
+describe('Modal store', () => {
+ let issue;
+ let issue2;
+
+ beforeEach(() => {
+ // Set up default state
+ Store.store.issues = [];
+ Store.store.selectedIssues = [];
+
+ issue = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ });
+ issue2 = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 2,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ });
+ Store.store.issues.push(issue);
+ Store.store.issues.push(issue2);
+ });
+
+ it('returns selected count', () => {
+ expect(Store.selectedCount()).toBe(0);
+ });
+
+ it('toggles the issue as selected', () => {
+ Store.toggleIssue(issue);
+
+ expect(issue.selected).toBe(true);
+ expect(Store.selectedCount()).toBe(1);
+ });
+
+ it('toggles the issue as un-selected', () => {
+ Store.toggleIssue(issue);
+ Store.toggleIssue(issue);
+
+ expect(issue.selected).toBe(false);
+ expect(Store.selectedCount()).toBe(0);
+ });
+
+ it('toggles all issues as selected', () => {
+ Store.toggleAll();
+
+ expect(issue.selected).toBe(true);
+ expect(issue2.selected).toBe(true);
+ expect(Store.selectedCount()).toBe(2);
+ });
+
+ it('toggles all issues as un-selected', () => {
+ Store.toggleAll();
+ Store.toggleAll();
+
+ expect(issue.selected).toBe(false);
+ expect(issue2.selected).toBe(false);
+ expect(Store.selectedCount()).toBe(0);
+ });
+
+ it('toggles all if a single issue is selected', () => {
+ Store.toggleIssue(issue);
+ Store.toggleAll();
+
+ expect(issue.selected).toBe(true);
+ expect(issue2.selected).toBe(true);
+ expect(Store.selectedCount()).toBe(2);
+ });
+
+ it('adds issue to selected array', () => {
+ issue.selected = true;
+ Store.addSelectedIssue(issue);
+
+ expect(Store.selectedCount()).toBe(1);
+ });
+
+ it('removes issue from selected array', () => {
+ Store.addSelectedIssue(issue);
+ Store.removeSelectedIssue(issue);
+
+ expect(Store.selectedCount()).toBe(0);
+ });
+
+ it('returns selected issue index if present', () => {
+ Store.toggleIssue(issue);
+
+ expect(Store.selectedIssueIndex(issue)).toBe(0);
+ });
+
+ it('returns -1 if issue is not selected', () => {
+ expect(Store.selectedIssueIndex(issue)).toBe(-1);
+ });
+
+ it('finds the selected issue', () => {
+ Store.toggleIssue(issue);
+
+ expect(Store.findSelectedIssue(issue)).toBe(issue);
+ });
+
+ it('does not find a selected issue', () => {
+ expect(Store.findSelectedIssue(issue)).toBe(undefined);
+ });
+
+ it('does not remove from selected issue if tab is not all', () => {
+ Store.store.activeTab = 'selected';
+
+ Store.toggleIssue(issue);
+ Store.toggleIssue(issue);
+
+ expect(Store.store.selectedIssues.length).toBe(1);
+ expect(Store.selectedCount()).toBe(0);
+ });
+
+ it('gets selected issue array with only selected issues', () => {
+ Store.toggleIssue(issue);
+ Store.toggleIssue(issue2);
+ Store.toggleIssue(issue2);
+
+ expect(Store.getSelectedIssues().length).toBe(1);
+ });
+});
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
new file mode 100644
index 00000000000..d23393db60d
--- /dev/null
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -0,0 +1,67 @@
+import actions from '~/boards/stores/actions';
+
+const expectNotImplemented = action => {
+ it('is not implemented', () => {
+ expect(action).toThrow(new Error('Not implemented!'));
+ });
+};
+
+describe('setEndpoints', () => {
+ expectNotImplemented(actions.setEndpoints);
+});
+
+describe('fetchLists', () => {
+ expectNotImplemented(actions.fetchLists);
+});
+
+describe('generateDefaultLists', () => {
+ expectNotImplemented(actions.generateDefaultLists);
+});
+
+describe('createList', () => {
+ expectNotImplemented(actions.createList);
+});
+
+describe('updateList', () => {
+ expectNotImplemented(actions.updateList);
+});
+
+describe('deleteList', () => {
+ expectNotImplemented(actions.deleteList);
+});
+
+describe('fetchIssuesForList', () => {
+ expectNotImplemented(actions.fetchIssuesForList);
+});
+
+describe('moveIssue', () => {
+ expectNotImplemented(actions.moveIssue);
+});
+
+describe('createNewIssue', () => {
+ expectNotImplemented(actions.createNewIssue);
+});
+
+describe('fetchBacklog', () => {
+ expectNotImplemented(actions.fetchBacklog);
+});
+
+describe('bulkUpdateIssues', () => {
+ expectNotImplemented(actions.bulkUpdateIssues);
+});
+
+describe('fetchIssue', () => {
+ expectNotImplemented(actions.fetchIssue);
+});
+
+describe('toggleIssueSubscription', () => {
+ expectNotImplemented(actions.toggleIssueSubscription);
+});
+
+describe('showPage', () => {
+ expectNotImplemented(actions.showPage);
+});
+
+describe('toggleEmptyState', () => {
+ expectNotImplemented(actions.toggleEmptyState);
+});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
new file mode 100644
index 00000000000..aa477766978
--- /dev/null
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -0,0 +1,91 @@
+import mutations from '~/boards/stores/mutations';
+
+const expectNotImplemented = action => {
+ it('is not implemented', () => {
+ expect(action).toThrow(new Error('Not implemented!'));
+ });
+};
+
+describe('SET_ENDPOINTS', () => {
+ expectNotImplemented(mutations.SET_ENDPOINTS);
+});
+
+describe('REQUEST_ADD_LIST', () => {
+ expectNotImplemented(mutations.REQUEST_ADD_LIST);
+});
+
+describe('RECEIVE_ADD_LIST_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_ADD_LIST_SUCCESS);
+});
+
+describe('RECEIVE_ADD_LIST_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
+});
+
+describe('REQUEST_UPDATE_LIST', () => {
+ expectNotImplemented(mutations.REQUEST_UPDATE_LIST);
+});
+
+describe('RECEIVE_UPDATE_LIST_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_SUCCESS);
+});
+
+describe('RECEIVE_UPDATE_LIST_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_ERROR);
+});
+
+describe('REQUEST_REMOVE_LIST', () => {
+ expectNotImplemented(mutations.REQUEST_REMOVE_LIST);
+});
+
+describe('RECEIVE_REMOVE_LIST_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_SUCCESS);
+});
+
+describe('RECEIVE_REMOVE_LIST_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
+});
+
+describe('REQUEST_ADD_ISSUE', () => {
+ expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
+});
+
+describe('RECEIVE_ADD_ISSUE_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS);
+});
+
+describe('RECEIVE_ADD_ISSUE_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR);
+});
+
+describe('REQUEST_MOVE_ISSUE', () => {
+ expectNotImplemented(mutations.REQUEST_MOVE_ISSUE);
+});
+
+describe('RECEIVE_MOVE_ISSUE_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_SUCCESS);
+});
+
+describe('RECEIVE_MOVE_ISSUE_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_ERROR);
+});
+
+describe('REQUEST_UPDATE_ISSUE', () => {
+ expectNotImplemented(mutations.REQUEST_UPDATE_ISSUE);
+});
+
+describe('RECEIVE_UPDATE_ISSUE_SUCCESS', () => {
+ expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_SUCCESS);
+});
+
+describe('RECEIVE_UPDATE_ISSUE_ERROR', () => {
+ expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
+});
+
+describe('SET_CURRENT_PAGE', () => {
+ expectNotImplemented(mutations.SET_CURRENT_PAGE);
+});
+
+describe('TOGGLE_EMPTY_STATE', () => {
+ expectNotImplemented(mutations.TOGGLE_EMPTY_STATE);
+});
diff --git a/spec/frontend/boards/stores/state_spec.js b/spec/frontend/boards/stores/state_spec.js
new file mode 100644
index 00000000000..35490a63567
--- /dev/null
+++ b/spec/frontend/boards/stores/state_spec.js
@@ -0,0 +1,11 @@
+import createState from '~/boards/stores/state';
+
+describe('createState', () => {
+ it('is a function', () => {
+ expect(createState).toEqual(expect.any(Function));
+ });
+
+ it('returns an object', () => {
+ expect(createState()).toEqual(expect.any(Object));
+ });
+});
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
new file mode 100644
index 00000000000..6de06a9e2d5
--- /dev/null
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -0,0 +1,387 @@
+import Clusters from '~/clusters/clusters_bundle';
+import {
+ APPLICATION_STATUS,
+ INGRESS_DOMAIN_SUFFIX,
+ APPLICATIONS,
+ RUNNER,
+} from '~/clusters/constants';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { loadHTMLFixture } from 'helpers/fixtures';
+import { setTestTimeout } from 'helpers/timeout';
+import $ from 'jquery';
+
+const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
+
+describe('Clusters', () => {
+ setTestTimeout(1000);
+
+ let cluster;
+ let mock;
+
+ const mockGetClusterStatusRequest = () => {
+ const { statusPath } = document.querySelector('.js-edit-cluster-form').dataset;
+
+ mock = new MockAdapter(axios);
+
+ mock.onGet(statusPath).reply(200);
+ };
+
+ beforeEach(() => {
+ loadHTMLFixture('clusters/show_cluster.html');
+ });
+
+ beforeEach(() => {
+ mockGetClusterStatusRequest();
+ });
+
+ beforeEach(() => {
+ cluster = new Clusters();
+ });
+
+ afterEach(() => {
+ cluster.destroy();
+ mock.restore();
+ });
+
+ describe('toggle', () => {
+ it('should update the button and the input field on click', done => {
+ const toggleButton = document.querySelector(
+ '.js-cluster-enable-toggle-area .js-project-feature-toggle',
+ );
+ const toggleInput = document.querySelector(
+ '.js-cluster-enable-toggle-area .js-project-feature-toggle-input',
+ );
+
+ $(toggleInput).one('trigger-change', () => {
+ expect(toggleButton.classList).not.toContain('is-checked');
+ expect(toggleInput.getAttribute('value')).toEqual('false');
+ done();
+ });
+
+ toggleButton.click();
+ });
+ });
+
+ describe('showToken', () => {
+ it('should update token field type', () => {
+ cluster.showTokenButton.click();
+
+ expect(cluster.tokenField.getAttribute('type')).toEqual('text');
+
+ cluster.showTokenButton.click();
+
+ expect(cluster.tokenField.getAttribute('type')).toEqual('password');
+ });
+
+ it('should update show token button text', () => {
+ cluster.showTokenButton.click();
+
+ expect(cluster.showTokenButton.textContent).toEqual('Hide');
+
+ cluster.showTokenButton.click();
+
+ expect(cluster.showTokenButton.textContent).toEqual('Show');
+ });
+ });
+
+ describe('checkForNewInstalls', () => {
+ const INITIAL_APP_MAP = {
+ helm: { status: null, title: 'Helm Tiller' },
+ ingress: { status: null, title: 'Ingress' },
+ runner: { status: null, title: 'GitLab Runner' },
+ };
+
+ it('does not show alert when things transition from initial null state to something', () => {
+ cluster.checkForNewInstalls(INITIAL_APP_MAP, {
+ ...INITIAL_APP_MAP,
+ helm: { status: INSTALLABLE, title: 'Helm Tiller' },
+ });
+
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+
+ expect(flashMessage).toBeNull();
+ });
+
+ it('shows an alert when something gets newly installed', () => {
+ cluster.checkForNewInstalls(
+ {
+ ...INITIAL_APP_MAP,
+ helm: { status: INSTALLING, title: 'Helm Tiller' },
+ },
+ {
+ ...INITIAL_APP_MAP,
+ helm: { status: INSTALLED, title: 'Helm Tiller' },
+ },
+ );
+
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+
+ expect(flashMessage).not.toBeNull();
+ expect(flashMessage.textContent.trim()).toEqual(
+ 'Helm Tiller was successfully installed on your Kubernetes cluster',
+ );
+ });
+
+ it('shows an alert when multiple things gets newly installed', () => {
+ cluster.checkForNewInstalls(
+ {
+ ...INITIAL_APP_MAP,
+ helm: { status: INSTALLING, title: 'Helm Tiller' },
+ ingress: { status: INSTALLABLE, title: 'Ingress' },
+ },
+ {
+ ...INITIAL_APP_MAP,
+ helm: { status: INSTALLED, title: 'Helm Tiller' },
+ ingress: { status: INSTALLED, title: 'Ingress' },
+ },
+ );
+
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+
+ expect(flashMessage).not.toBeNull();
+ expect(flashMessage.textContent.trim()).toEqual(
+ 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster',
+ );
+ });
+ });
+
+ describe('updateContainer', () => {
+ describe('when creating cluster', () => {
+ it('should show the creating container', () => {
+ cluster.updateContainer(null, 'creating');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ });
+
+ it('should continue to show `creating` banner with subsequent updates of the same status', () => {
+ cluster.updateContainer('creating', 'creating');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ });
+ });
+
+ describe('when cluster is created', () => {
+ it('should show the success container and fresh the page', () => {
+ cluster.updateContainer(null, 'created');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ });
+
+ it('should not show a banner when status is already `created`', () => {
+ cluster.updateContainer('created', 'created');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ });
+ });
+
+ describe('when cluster has error', () => {
+ it('should show the error container', () => {
+ cluster.updateContainer(null, 'errored', 'this is an error');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
+
+ expect(cluster.errorReasonContainer.textContent).toContain('this is an error');
+ });
+
+ it('should show `error` banner when previously `creating`', () => {
+ cluster.updateContainer('creating', 'errored');
+
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
+ });
+ });
+
+ describe('when cluster is unreachable', () => {
+ it('should show the unreachable warning container', () => {
+ cluster.updateContainer(null, 'unreachable');
+
+ expect(cluster.unreachableContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
+
+ describe('when cluster has an authentication failure', () => {
+ it('should show the authentication failure warning container', () => {
+ cluster.updateContainer(null, 'authentication_failure');
+
+ expect(cluster.authenticationFailureContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
+ });
+
+ describe('installApplication', () => {
+ it.each(APPLICATIONS)('tries to install %s', applicationId => {
+ jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
+
+ cluster.store.state.applications[applicationId].status = INSTALLABLE;
+
+ cluster.installApplication({ id: applicationId });
+
+ expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING);
+ expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
+ expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, undefined);
+ });
+
+ it('sets error request status when the request fails', () => {
+ jest
+ .spyOn(cluster.service, 'installApplication')
+ .mockRejectedValueOnce(new Error('STUBBED ERROR'));
+
+ cluster.store.state.applications.helm.status = INSTALLABLE;
+
+ const promise = cluster.installApplication({ id: 'helm' });
+
+ return promise.then(() => {
+ expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE);
+ expect(cluster.store.state.applications.helm.installFailed).toBe(true);
+
+ expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
+ });
+ });
+ });
+
+ describe('uninstallApplication', () => {
+ it.each(APPLICATIONS)('tries to uninstall %s', applicationId => {
+ jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce();
+
+ cluster.store.state.applications[applicationId].status = INSTALLED;
+
+ cluster.uninstallApplication({ id: applicationId });
+
+ expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING);
+ expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
+ expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId);
+ });
+
+ it('sets error request status when the uninstall request fails', () => {
+ jest
+ .spyOn(cluster.service, 'uninstallApplication')
+ .mockRejectedValueOnce(new Error('STUBBED ERROR'));
+
+ cluster.store.state.applications.helm.status = INSTALLED;
+
+ const promise = cluster.uninstallApplication({ id: 'helm' });
+
+ return promise.then(() => {
+ expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED);
+ expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true);
+
+ expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
+ });
+ });
+ });
+
+ describe('handleSuccess', () => {
+ beforeEach(() => {
+ jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis();
+ jest.spyOn(cluster, 'toggleIngressDomainHelpText').mockReturnThis();
+ jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis();
+ jest.spyOn(cluster, 'updateContainer').mockReturnThis();
+
+ cluster.handleSuccess({ data: {} });
+ });
+
+ it('updates clusters store', () => {
+ expect(cluster.store.updateStateFromServer).toHaveBeenCalled();
+ });
+
+ it('checks for new installable apps', () => {
+ expect(cluster.checkForNewInstalls).toHaveBeenCalled();
+ });
+
+ it('toggles ingress domain help text', () => {
+ expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled();
+ });
+
+ it('updates message containers', () => {
+ expect(cluster.updateContainer).toHaveBeenCalled();
+ });
+ });
+
+ describe('toggleIngressDomainHelpText', () => {
+ let ingressPreviousState;
+ let ingressNewState;
+
+ beforeEach(() => {
+ ingressPreviousState = { externalIp: null };
+ ingressNewState = { externalIp: '127.0.0.1' };
+ });
+
+ describe(`when ingress have an external ip assigned`, () => {
+ beforeEach(() => {
+ cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
+ });
+
+ it('displays custom domain help text', () => {
+ expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false);
+ });
+
+ it('updates ingress external ip address', () => {
+ expect(cluster.ingressDomainSnippet.textContent).toEqual(
+ `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`,
+ );
+ });
+ });
+
+ describe(`when ingress does not have an external ip assigned`, () => {
+ it('hides custom domain help text', () => {
+ ingressPreviousState.externalIp = '127.0.0.1';
+ ingressNewState.externalIp = null;
+ cluster.ingressDomainHelpText.classList.remove('hide');
+
+ cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
+
+ expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
+ });
+ });
+ });
+
+ describe('updateApplication', () => {
+ const params = { version: '1.0.0' };
+ let storeUpdateApplication;
+ let installApplication;
+
+ beforeEach(() => {
+ storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication');
+ installApplication = jest.spyOn(cluster.service, 'installApplication');
+
+ cluster.updateApplication({ id: RUNNER, params });
+ });
+
+ afterEach(() => {
+ storeUpdateApplication.mockRestore();
+ installApplication.mockRestore();
+ });
+
+ it('calls store updateApplication method', () => {
+ expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER);
+ });
+
+ it('sends installApplication request', () => {
+ expect(installApplication).toHaveBeenCalledWith(RUNNER, params);
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
new file mode 100644
index 00000000000..9f127ccb690
--- /dev/null
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -0,0 +1,489 @@
+import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import eventHub from '~/clusters/event_hub';
+import { APPLICATION_STATUS } from '~/clusters/constants';
+import applicationRow from '~/clusters/components/application_row.vue';
+import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
+
+describe('Application Row', () => {
+ let vm;
+ let ApplicationRow;
+
+ beforeEach(() => {
+ ApplicationRow = Vue.extend(applicationRow);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Title', () => {
+ it('shows title', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ titleLink: null,
+ });
+ const title = vm.$el.querySelector('.js-cluster-application-title');
+
+ expect(title.tagName).toEqual('SPAN');
+ expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
+ });
+
+ it('shows title link', () => {
+ expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined();
+
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ });
+ const title = vm.$el.querySelector('.js-cluster-application-title');
+
+ expect(title.tagName).toEqual('A');
+ expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
+ });
+ });
+
+ describe('Install button', () => {
+ it('has indeterminate state on page load', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: null,
+ });
+
+ expect(vm.installButtonLabel).toBeUndefined();
+ });
+
+ it('has install button', () => {
+ const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ expect(installationBtn).not.toBe(null);
+ });
+
+ it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.NOT_INSTALLABLE,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Install');
+ expect(vm.installButtonLoading).toEqual(false);
+ expect(vm.installButtonDisabled).toEqual(true);
+ });
+
+ it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLABLE,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Install');
+ expect(vm.installButtonLoading).toEqual(false);
+ expect(vm.installButtonDisabled).toEqual(false);
+ });
+
+ it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLING,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Installing');
+ expect(vm.installButtonLoading).toEqual(true);
+ expect(vm.installButtonDisabled).toEqual(true);
+ });
+
+ it('has disabled "Installed" when application is installed and not uninstallable', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ installed: true,
+ uninstallable: false,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Installed');
+ expect(vm.installButtonLoading).toEqual(false);
+ expect(vm.installButtonDisabled).toEqual(true);
+ });
+
+ it('hides when application is installed and uninstallable', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ installed: true,
+ uninstallable: true,
+ });
+ const installBtn = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ expect(installBtn).toBe(null);
+ });
+
+ it('has enabled "Install" when install fails', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLABLE,
+ installFailed: true,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Install');
+ expect(vm.installButtonLoading).toEqual(false);
+ expect(vm.installButtonDisabled).toEqual(false);
+ });
+
+ it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLABLE,
+ });
+
+ expect(vm.installButtonLabel).toEqual('Install');
+ expect(vm.installButtonLoading).toEqual(false);
+ expect(vm.installButtonDisabled).toEqual(false);
+ });
+
+ it('clicking install button emits event', () => {
+ jest.spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLABLE,
+ });
+ const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ installButton.click();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: {},
+ });
+ });
+
+ it('clicking install button when installApplicationRequestParams are provided emits event', () => {
+ jest.spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLABLE,
+ installApplicationRequestParams: { hostname: 'jupyter' },
+ });
+ const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ installButton.click();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: { hostname: 'jupyter' },
+ });
+ });
+
+ it('clicking disabled install button emits nothing', () => {
+ jest.spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLING,
+ });
+ const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ expect(vm.installButtonDisabled).toEqual(true);
+
+ installButton.click();
+
+ expect(eventHub.$emit).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Uninstall button', () => {
+ it('displays button when app is installed and uninstallable', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ installed: true,
+ uninstallable: true,
+ status: APPLICATION_STATUS.NOT_INSTALLABLE,
+ });
+ const uninstallButton = vm.$el.querySelector('.js-cluster-application-uninstall-button');
+
+ expect(uninstallButton).toBeTruthy();
+ });
+
+ it('displays a success toast message if application uninstall was successful', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ title: 'GitLab Runner',
+ uninstallSuccessful: false,
+ });
+
+ vm.$toast = { show: jest.fn() };
+ vm.uninstallSuccessful = true;
+
+ return vm.$nextTick(() => {
+ expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner uninstalled successfully.');
+ });
+ });
+ });
+
+ describe('when confirmation modal triggers confirm event', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(ApplicationRow, {
+ propsData: {
+ ...DEFAULT_APPLICATION_STATE,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('triggers uninstallApplication event', () => {
+ jest.spyOn(eventHub, '$emit');
+ wrapper.find(UninstallApplicationConfirmationModal).vm.$emit('confirm');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('uninstallApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ });
+ });
+ });
+
+ describe('Update button', () => {
+ it('has indeterminate state on page load', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: null,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ expect(updateBtn).toBe(null);
+ });
+
+ it('has enabled "Update" when "updateAvailable" is true', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ updateAvailable: true,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Update');
+ });
+
+ it('has enabled "Retry update" when update process fails', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ updateFailed: true,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Retry update');
+ });
+
+ it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATING,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ expect(updateBtn).not.toBe(null);
+ expect(vm.isUpdating).toBe(true);
+ expect(updateBtn.innerHTML).toContain('Updating');
+ });
+
+ it('clicking update button emits event', () => {
+ jest.spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ updateAvailable: true,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ updateBtn.click();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: {},
+ });
+ });
+
+ it('clicking disabled update button emits nothing', () => {
+ jest.spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATING,
+ });
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
+
+ updateBtn.click();
+
+ expect(eventHub.$emit).not.toHaveBeenCalled();
+ });
+
+ it('displays an error message if application update failed', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ title: 'GitLab Runner',
+ status: APPLICATION_STATUS.INSTALLED,
+ updateFailed: true,
+ });
+ const failureMessage = vm.$el.querySelector('.js-cluster-application-update-details');
+
+ expect(failureMessage).not.toBe(null);
+ expect(failureMessage.innerHTML).toContain(
+ 'Update failed. Please check the logs and try again.',
+ );
+ });
+
+ it('displays a success toast message if application update was successful', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ title: 'GitLab Runner',
+ updateSuccessful: false,
+ });
+
+ vm.$toast = { show: jest.fn() };
+ vm.updateSuccessful = true;
+
+ return vm.$nextTick(() => {
+ expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner updated successfully.');
+ });
+ });
+ });
+
+ describe('Version', () => {
+ it('displays a version number if application has been updated', () => {
+ const version = '0.1.45';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ updateSuccessful: true,
+ version,
+ });
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
+
+ expect(updateDetails.innerHTML).toContain('Updated');
+ expect(versionEl).not.toBe(null);
+ expect(versionEl.innerHTML).toContain(version);
+ });
+
+ it('contains a link to the chart repo if application has been updated', () => {
+ const version = '0.1.45';
+ const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ updateSuccessful: true,
+ chartRepo,
+ version,
+ });
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
+
+ expect(versionEl.href).toEqual(chartRepo);
+ expect(versionEl.target).toEqual('_blank');
+ });
+
+ it('does not display a version number if application update failed', () => {
+ const version = '0.1.45';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.INSTALLED,
+ updateFailed: true,
+ version,
+ });
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
+
+ expect(updateDetails.innerHTML).toContain('failed');
+ expect(versionEl).toBe(null);
+ });
+ });
+
+ describe('Error block', () => {
+ describe('when nothing fails', () => {
+ it('does not show error block', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ });
+ const generalErrorMessage = vm.$el.querySelector(
+ '.js-cluster-application-general-error-message',
+ );
+
+ expect(generalErrorMessage).toBeNull();
+ });
+ });
+
+ describe('when install or uninstall fails', () => {
+ const statusReason = 'We broke it 0.0';
+ const requestReason = 'We broke the request 0.0';
+
+ beforeEach(() => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.ERROR,
+ statusReason,
+ requestReason,
+ installFailed: true,
+ });
+ });
+
+ it('shows status reason if it is available', () => {
+ const statusErrorMessage = vm.$el.querySelector(
+ '.js-cluster-application-status-error-message',
+ );
+
+ expect(statusErrorMessage.textContent.trim()).toEqual(statusReason);
+ });
+
+ it('shows request reason if it is available', () => {
+ const requestErrorMessage = vm.$el.querySelector(
+ '.js-cluster-application-request-error-message',
+ );
+
+ expect(requestErrorMessage.textContent.trim()).toEqual(requestReason);
+ });
+ });
+
+ describe('when install fails', () => {
+ beforeEach(() => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.ERROR,
+ installFailed: true,
+ });
+ });
+
+ it('shows a general message indicating the installation failed', () => {
+ const generalErrorMessage = vm.$el.querySelector(
+ '.js-cluster-application-general-error-message',
+ );
+
+ expect(generalErrorMessage.textContent.trim()).toEqual(
+ `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
+ );
+ });
+ });
+
+ describe('when uninstall fails', () => {
+ beforeEach(() => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.ERROR,
+ uninstallFailed: true,
+ });
+ });
+
+ it('shows a general message indicating the uninstalling failed', () => {
+ const generalErrorMessage = vm.$el.querySelector(
+ '.js-cluster-application-general-error-message',
+ );
+
+ expect(generalErrorMessage.textContent.trim()).toEqual(
+ `Something went wrong while uninstalling ${DEFAULT_APPLICATION_STATE.title}`,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js
new file mode 100644
index 00000000000..221ebb143be
--- /dev/null
+++ b/spec/frontend/clusters/components/applications_spec.js
@@ -0,0 +1,327 @@
+import Vue from 'vue';
+import applications from '~/clusters/components/applications.vue';
+import { CLUSTER_TYPE } from '~/clusters/constants';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
+import eventHub from '~/clusters/event_hub';
+import { shallowMount } from '@vue/test-utils';
+import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
+
+describe('Applications', () => {
+ let vm;
+ let Applications;
+
+ beforeEach(() => {
+ Applications = Vue.extend(applications);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Project cluster applications', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ applications: APPLICATIONS_MOCK_STATE,
+ type: CLUSTER_TYPE.PROJECT,
+ });
+ });
+
+ it('renders a row for Helm Tiller', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
+ });
+
+ it('renders a row for Ingress', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
+ });
+
+ it('renders a row for Cert-Manager', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
+ });
+
+ it('renders a row for Prometheus', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
+ });
+
+ it('renders a row for GitLab Runner', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
+ });
+
+ it('renders a row for Jupyter', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull();
+ });
+
+ it('renders a row for Knative', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
+ });
+ });
+
+ describe('Group cluster applications', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ type: CLUSTER_TYPE.GROUP,
+ applications: APPLICATIONS_MOCK_STATE,
+ });
+ });
+
+ it('renders a row for Helm Tiller', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
+ });
+
+ it('renders a row for Ingress', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
+ });
+
+ it('renders a row for Cert-Manager', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
+ });
+
+ it('renders a row for Prometheus', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
+ });
+
+ it('renders a row for GitLab Runner', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
+ });
+
+ it('renders a row for Jupyter', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull();
+ });
+
+ it('renders a row for Knative', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull();
+ });
+ });
+
+ describe('Ingress application', () => {
+ describe('when installed', () => {
+ describe('with ip address', () => {
+ it('renders ip address with a clipboard button', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ ingress: {
+ title: 'Ingress',
+ status: 'installed',
+ externalIp: '0.0.0.0',
+ },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-endpoint').value).toEqual('0.0.0.0');
+
+ expect(
+ vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
+ ).toEqual('0.0.0.0');
+ });
+ });
+
+ describe('with hostname', () => {
+ it('renders hostname with a clipboard button', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ingress: {
+ title: 'Ingress',
+ status: 'installed',
+ externalHostname: 'localhost.localdomain',
+ },
+ helm: { title: 'Helm Tiller' },
+ cert_manager: { title: 'Cert-Manager' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '' },
+ knative: { title: 'Knative', hostname: '' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-endpoint').value).toEqual('localhost.localdomain');
+
+ expect(
+ vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
+ ).toEqual('localhost.localdomain');
+ });
+ });
+
+ describe('without ip address', () => {
+ it('renders an input text with a loading icon and an alert text', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ ingress: {
+ title: 'Ingress',
+ status: 'installed',
+ },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null);
+ });
+ });
+ });
+
+ describe('before installing', () => {
+ it('does not render the IP address', () => {
+ vm = mountComponent(Applications, {
+ applications: APPLICATIONS_MOCK_STATE,
+ });
+
+ expect(vm.$el.textContent).not.toContain('Ingress IP Address');
+ expect(vm.$el.querySelector('.js-endpoint')).toBe(null);
+ });
+ });
+
+ describe('Cert-Manager application', () => {
+ describe('when not installed', () => {
+ it('renders email & allows editing', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ cert_manager: {
+ title: 'Cert-Manager',
+ email: 'before@example.com',
+ status: 'installable',
+ },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com');
+ expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null);
+ });
+ });
+
+ describe('when installed', () => {
+ it('renders email in readonly', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ cert_manager: {
+ title: 'Cert-Manager',
+ email: 'after@example.com',
+ status: 'installed',
+ },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com');
+ expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly');
+ });
+ });
+ });
+
+ describe('Jupyter application', () => {
+ describe('with ingress installed with ip & jupyter installable', () => {
+ it('renders hostname active input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ ingress: {
+ title: 'Ingress',
+ status: 'installed',
+ externalIp: '1.1.1.1',
+ },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null);
+ });
+ });
+
+ describe('with ingress installed without external ip', () => {
+ it('does not render hostname input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ ingress: { title: 'Ingress', status: 'installed' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname')).toBe(null);
+ });
+ });
+
+ describe('with ingress & jupyter installed', () => {
+ it('renders readonly input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly');
+ });
+ });
+
+ describe('without ingress installed', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ applications: APPLICATIONS_MOCK_STATE,
+ });
+ });
+
+ it('does not render input', () => {
+ expect(vm.$el.querySelector('.js-hostname')).toBe(null);
+ });
+
+ it('renders disabled install button', () => {
+ expect(
+ vm.$el
+ .querySelector(
+ '.js-cluster-application-row-jupyter .js-cluster-application-install-button',
+ )
+ .getAttribute('disabled'),
+ ).toEqual('disabled');
+ });
+ });
+ });
+ });
+
+ describe('Knative application', () => {
+ const propsData = {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ knative: {
+ title: 'Knative',
+ hostname: 'example.com',
+ status: 'installed',
+ externalIp: '1.1.1.1',
+ installed: true,
+ },
+ },
+ };
+ const newHostname = 'newhostname.com';
+ let wrapper;
+ let knativeDomainEditor;
+
+ beforeEach(() => {
+ wrapper = shallowMount(Applications, { propsData });
+ jest.spyOn(eventHub, '$emit');
+
+ knativeDomainEditor = wrapper.find(KnativeDomainEditor);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('emits saveKnativeDomain event when knative domain editor emits save event', () => {
+ knativeDomainEditor.vm.$emit('save', newHostname);
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
+ id: 'knative',
+ params: { hostname: newHostname },
+ });
+ });
+
+ it('emits setKnativeHostname event when knative domain editor emits change event', () => {
+ wrapper.find(KnativeDomainEditor).vm.$emit('set', newHostname);
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeHostname', {
+ id: 'knative',
+ hostname: newHostname,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/knative_domain_editor_spec.js b/spec/frontend/clusters/components/knative_domain_editor_spec.js
new file mode 100644
index 00000000000..242b5701f8b
--- /dev/null
+++ b/spec/frontend/clusters/components/knative_domain_editor_spec.js
@@ -0,0 +1,141 @@
+import { shallowMount } from '@vue/test-utils';
+import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { UPDATING } = APPLICATION_STATUS;
+
+describe('KnativeDomainEditor', () => {
+ let wrapper;
+ let knative;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(KnativeDomainEditor, {
+ propsData: { ...props },
+ });
+ };
+
+ beforeEach(() => {
+ knative = {
+ title: 'Knative',
+ hostname: 'example.com',
+ installed: true,
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('knative has an assigned IP address', () => {
+ beforeEach(() => {
+ knative.externalIp = '1.1.1.1';
+ createComponent({ knative });
+ });
+
+ it('renders ip address with a clipboard button', () => {
+ expect(wrapper.find('.js-knative-endpoint').exists()).toBe(true);
+ expect(wrapper.find('.js-knative-endpoint').element.value).toEqual(knative.externalIp);
+ });
+
+ it('displays ip address clipboard button', () => {
+ expect(wrapper.find('.js-knative-endpoint-clipboard-btn').attributes('text')).toEqual(
+ knative.externalIp,
+ );
+ });
+
+ it('renders domain & allows editing', () => {
+ const domainNameInput = wrapper.find('.js-knative-domainname');
+
+ expect(domainNameInput.element.value).toEqual(knative.hostname);
+ expect(domainNameInput.attributes('readonly')).toBeFalsy();
+ });
+
+ it('renders an update/save Knative domain button', () => {
+ expect(wrapper.find('.js-knative-save-domain-button').exists()).toBe(true);
+ });
+ });
+
+ describe('knative without ip address', () => {
+ beforeEach(() => {
+ knative.externalIp = null;
+ createComponent({ knative });
+ });
+
+ it('renders an input text with a loading icon', () => {
+ expect(wrapper.find('.js-knative-ip-loading-icon').exists()).toBe(true);
+ });
+
+ it('renders message indicating there is not IP address assigned', () => {
+ expect(wrapper.find('.js-no-knative-endpoint-message').exists()).toBe(true);
+ });
+ });
+
+ describe('clicking save changes button', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('triggers save event and pass current knative hostname', () => {
+ wrapper.find(LoadingButton).vm.$emit('click');
+ expect(wrapper.emitted('save')[0]).toEqual([knative.hostname]);
+ });
+ });
+
+ describe('when knative domain name was saved successfully', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('displays toast indicating a successful update', () => {
+ wrapper.vm.$toast = { show: jest.fn() };
+ wrapper.setProps({ knative: Object.assign({ updateSuccessful: true }, knative) });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(
+ 'Knative domain name was updated successfully.',
+ );
+ });
+ });
+ });
+
+ describe('when knative domain name input changes', () => {
+ it('emits "set" event with updated domain name', () => {
+ const newHostname = 'newhostname.com';
+
+ wrapper.setData({ knativeHostname: newHostname });
+
+ expect(wrapper.emitted('set')[0]).toEqual([newHostname]);
+ });
+ });
+
+ describe('when updating knative domain name failed', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('displays an error banner indicating the operation failure', () => {
+ wrapper.setProps({ knative: { updateFailed: true, ...knative } });
+
+ expect(wrapper.find('.js-cluster-knative-domain-name-failure-message').exists()).toBe(true);
+ });
+ });
+
+ describe(`when knative status is ${UPDATING}`, () => {
+ beforeEach(() => {
+ createComponent({ knative: { status: UPDATING, ...knative } });
+ });
+
+ it('renders loading spinner in save button', () => {
+ expect(wrapper.find(LoadingButton).props('loading')).toBe(true);
+ });
+
+ it('renders disabled save button', () => {
+ expect(wrapper.find(LoadingButton).props('disabled')).toBe(true);
+ });
+
+ it('renders save button with "Saving" label', () => {
+ expect(wrapper.find(LoadingButton).props('label')).toBe('Saving');
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/uninstall_application_button_spec.js b/spec/frontend/clusters/components/uninstall_application_button_spec.js
new file mode 100644
index 00000000000..9f9397d4d41
--- /dev/null
+++ b/spec/frontend/clusters/components/uninstall_application_button_spec.js
@@ -0,0 +1,32 @@
+import { shallowMount } from '@vue/test-utils';
+import UninstallApplicationButton from '~/clusters/components/uninstall_application_button.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { INSTALLED, UPDATING, UNINSTALLING } = APPLICATION_STATUS;
+
+describe('UninstallApplicationButton', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(UninstallApplicationButton, {
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each`
+ status | loading | disabled | label
+ ${INSTALLED} | ${false} | ${false} | ${'Uninstall'}
+ ${UPDATING} | ${false} | ${true} | ${'Uninstall'}
+ ${UNINSTALLING} | ${true} | ${true} | ${'Uninstalling'}
+ `('when app status is $status', ({ loading, disabled, status, label }) => {
+ it(`renders a loading=${loading}, disabled=${disabled} button with label="${label}"`, () => {
+ createComponent({ status });
+ expect(wrapper.find(LoadingButton).props()).toMatchObject({ loading, disabled, label });
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js
new file mode 100644
index 00000000000..04808864fc0
--- /dev/null
+++ b/spec/frontend/clusters/components/uninstall_application_confirmation_modal_spec.js
@@ -0,0 +1,56 @@
+import { shallowMount } from '@vue/test-utils';
+import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
+import { GlModal } from '@gitlab/ui';
+import { INGRESS } from '~/clusters/constants';
+
+describe('UninstallApplicationConfirmationModal', () => {
+ let wrapper;
+ const appTitle = 'Ingress';
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(UninstallApplicationConfirmationModal, {
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ createComponent({ application: INGRESS, applicationTitle: appTitle });
+ });
+
+ it(`renders a modal with a title "Uninstall ${appTitle}"`, () => {
+ expect(wrapper.find(GlModal).attributes('title')).toEqual(`Uninstall ${appTitle}`);
+ });
+
+ it(`renders a modal with an ok button labeled "Uninstall ${appTitle}"`, () => {
+ expect(wrapper.find(GlModal).attributes('ok-title')).toEqual(`Uninstall ${appTitle}`);
+ });
+
+ describe('when ok button is clicked', () => {
+ beforeEach(() => {
+ jest.spyOn(wrapper.vm, 'trackUninstallButtonClick');
+ wrapper.find(GlModal).vm.$emit('ok');
+ });
+
+ it('emits confirm event', () => {
+ expect(wrapper.emitted('confirm')).toBeTruthy();
+ });
+
+ it('calls track uninstall button click mixin', () => {
+ expect(wrapper.vm.trackUninstallButtonClick).toHaveBeenCalledWith(INGRESS);
+ });
+ });
+
+ it('displays a warning text indicating the app will be uninstalled', () => {
+ expect(wrapper.text()).toContain(`You are about to uninstall ${appTitle} from your cluster.`);
+ });
+
+ it('displays a custom warning text depending on the application', () => {
+ expect(wrapper.text()).toContain(
+ `The associated load balancer and IP will be deleted and cannot be restored.`,
+ );
+ });
+});
diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js
new file mode 100644
index 00000000000..c146ef79be7
--- /dev/null
+++ b/spec/frontend/clusters/services/application_state_machine_spec.js
@@ -0,0 +1,162 @@
+import transitionApplicationState from '~/clusters/services/application_state_machine';
+import {
+ APPLICATION_STATUS,
+ UNINSTALL_EVENT,
+ UPDATE_EVENT,
+ INSTALL_EVENT,
+} from '~/clusters/constants';
+
+const {
+ NO_STATUS,
+ SCHEDULED,
+ NOT_INSTALLABLE,
+ INSTALLABLE,
+ INSTALLING,
+ INSTALLED,
+ ERROR,
+ UPDATING,
+ UPDATED,
+ UPDATE_ERRORED,
+ UNINSTALLING,
+ UNINSTALL_ERRORED,
+} = APPLICATION_STATUS;
+
+const NO_EFFECTS = 'no effects';
+
+describe('applicationStateMachine', () => {
+ const noEffectsToEmptyObject = effects => (typeof effects === 'string' ? {} : effects);
+
+ describe(`current state is ${NO_STATUS}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
+ ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
+ ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
+ ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
+ ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
+ ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
+ ${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
+ ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
+ ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
+ ${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
+ ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: NO_STATUS,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...noEffectsToEmptyObject(effects),
+ });
+ });
+ });
+
+ describe(`current state is ${NOT_INSTALLABLE}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: NOT_INSTALLABLE,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...noEffectsToEmptyObject(effects),
+ });
+ });
+ });
+
+ describe(`current state is ${INSTALLABLE}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLING} | ${INSTALL_EVENT} | ${{ installFailed: false }}
+ ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: INSTALLABLE,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...noEffectsToEmptyObject(effects),
+ });
+ });
+ });
+
+ describe(`current state is ${INSTALLING}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
+ ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: INSTALLING,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...noEffectsToEmptyObject(effects),
+ });
+ });
+ });
+
+ describe(`current state is ${INSTALLED}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }}
+ ${UNINSTALLING} | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: INSTALLED,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...effects,
+ });
+ });
+ });
+
+ describe(`current state is ${UPDATING}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true }}
+ ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: UPDATING,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...effects,
+ });
+ });
+ });
+
+ describe(`current state is ${UNINSTALLING}`, () => {
+ it.each`
+ expectedState | event | effects
+ ${INSTALLABLE} | ${INSTALLABLE} | ${{ uninstallSuccessful: true }}
+ ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
+ `(`transitions to $expectedState on $event event and applies $effects`, data => {
+ const { expectedState, event, effects } = data;
+ const currentAppState = {
+ status: UNINSTALLING,
+ };
+
+ expect(transitionApplicationState(currentAppState, event)).toEqual({
+ status: expectedState,
+ ...effects,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js
new file mode 100644
index 00000000000..41ad398e924
--- /dev/null
+++ b/spec/frontend/clusters/services/mock_data.js
@@ -0,0 +1,136 @@
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const CLUSTERS_MOCK_DATA = {
+ GET: {
+ '/gitlab-org/gitlab-shell/clusters/1/status.json': {
+ data: {
+ status: 'errored',
+ status_reason: 'Failed to request to CloudPlatform.',
+ applications: [
+ {
+ name: 'helm',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ status_reason: null,
+ can_uninstall: false,
+ },
+ {
+ name: 'ingress',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ external_ip: null,
+ external_hostname: null,
+ can_uninstall: false,
+ },
+ {
+ name: 'runner',
+ status: APPLICATION_STATUS.INSTALLING,
+ status_reason: null,
+ can_uninstall: false,
+ },
+ {
+ name: 'prometheus',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ can_uninstall: false,
+ },
+ {
+ name: 'jupyter',
+ status: APPLICATION_STATUS.INSTALLING,
+ status_reason: 'Cannot connect',
+ can_uninstall: false,
+ },
+ {
+ name: 'knative',
+ status: APPLICATION_STATUS.INSTALLING,
+ status_reason: 'Cannot connect',
+ can_uninstall: false,
+ },
+ {
+ name: 'cert_manager',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ email: 'test@example.com',
+ can_uninstall: false,
+ },
+ ],
+ },
+ },
+ '/gitlab-org/gitlab-shell/clusters/2/status.json': {
+ data: {
+ status: 'errored',
+ status_reason: 'Failed to request to CloudPlatform.',
+ applications: [
+ {
+ name: 'helm',
+ status: APPLICATION_STATUS.INSTALLED,
+ status_reason: null,
+ },
+ {
+ name: 'ingress',
+ status: APPLICATION_STATUS.INSTALLED,
+ status_reason: 'Cannot connect',
+ external_ip: '1.1.1.1',
+ external_hostname: null,
+ },
+ {
+ name: 'runner',
+ status: APPLICATION_STATUS.INSTALLING,
+ status_reason: null,
+ },
+ {
+ name: 'prometheus',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ },
+ {
+ name: 'jupyter',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ status_reason: 'Cannot connect',
+ },
+ {
+ name: 'knative',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ status_reason: 'Cannot connect',
+ },
+ {
+ name: 'cert_manager',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ email: 'test@example.com',
+ },
+ ],
+ },
+ },
+ },
+ POST: {
+ '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {},
+ },
+};
+
+const DEFAULT_APPLICATION_STATE = {
+ id: 'some-app',
+ title: 'My App',
+ titleLink: 'https://about.gitlab.com/',
+ description: 'Some description about this interesting application!',
+ status: null,
+ statusReason: null,
+ requestReason: null,
+};
+
+const APPLICATIONS_MOCK_STATE = {
+ helm: { title: 'Helm Tiller', status: 'installable' },
+ ingress: { title: 'Ingress', status: 'installable' },
+ cert_manager: { title: 'Cert-Manager', status: 'installable' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' },
+ knative: { title: 'Knative ', status: 'installable', hostname: '' },
+};
+
+export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE };
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
new file mode 100644
index 00000000000..f2cc413512d
--- /dev/null
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -0,0 +1,179 @@
+import ClustersStore from '~/clusters/stores/clusters_store';
+import { APPLICATION_INSTALLED_STATUSES, APPLICATION_STATUS, RUNNER } from '~/clusters/constants';
+import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
+
+describe('Clusters Store', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new ClustersStore();
+ });
+
+ describe('updateStatus', () => {
+ it('should store new status', () => {
+ expect(store.state.status).toEqual(null);
+
+ const newStatus = 'errored';
+ store.updateStatus(newStatus);
+
+ expect(store.state.status).toEqual(newStatus);
+ });
+ });
+
+ describe('updateStatusReason', () => {
+ it('should store new reason', () => {
+ expect(store.state.statusReason).toEqual(null);
+
+ const newReason = 'Something went wrong!';
+ store.updateStatusReason(newReason);
+
+ expect(store.state.statusReason).toEqual(newReason);
+ });
+ });
+
+ describe('updateAppProperty', () => {
+ it('should store new request reason', () => {
+ expect(store.state.applications.helm.requestReason).toEqual(null);
+
+ const newReason = 'We broke it.';
+ store.updateAppProperty('helm', 'requestReason', newReason);
+
+ expect(store.state.applications.helm.requestReason).toEqual(newReason);
+ });
+ });
+
+ describe('updateStateFromServer', () => {
+ it('should store new polling data from server', () => {
+ const mockResponseData =
+ CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data;
+ store.updateStateFromServer(mockResponseData);
+
+ expect(store.state).toEqual({
+ helpPath: null,
+ ingressHelpPath: null,
+ status: mockResponseData.status,
+ statusReason: mockResponseData.status_reason,
+ rbac: false,
+ applications: {
+ helm: {
+ title: 'Helm Tiller',
+ status: mockResponseData.applications[0].status,
+ statusReason: mockResponseData.applications[0].status_reason,
+ requestReason: null,
+ installed: false,
+ installFailed: false,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ ingress: {
+ title: 'Ingress',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ statusReason: mockResponseData.applications[1].status_reason,
+ requestReason: null,
+ externalIp: null,
+ externalHostname: null,
+ installed: false,
+ installFailed: true,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ runner: {
+ title: 'GitLab Runner',
+ status: mockResponseData.applications[2].status,
+ statusReason: mockResponseData.applications[2].status_reason,
+ requestReason: null,
+ version: mockResponseData.applications[2].version,
+ updateAvailable: mockResponseData.applications[2].update_available,
+ chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ installed: false,
+ installFailed: false,
+ updateFailed: false,
+ updateSuccessful: false,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ prometheus: {
+ title: 'Prometheus',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ statusReason: mockResponseData.applications[3].status_reason,
+ requestReason: null,
+ installed: false,
+ installFailed: true,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ jupyter: {
+ title: 'JupyterHub',
+ status: mockResponseData.applications[4].status,
+ statusReason: mockResponseData.applications[4].status_reason,
+ requestReason: null,
+ hostname: '',
+ installed: false,
+ installFailed: false,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ knative: {
+ title: 'Knative',
+ status: mockResponseData.applications[5].status,
+ statusReason: mockResponseData.applications[5].status_reason,
+ requestReason: null,
+ hostname: null,
+ isEditingHostName: false,
+ externalIp: null,
+ externalHostname: null,
+ installed: false,
+ installFailed: false,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ updateSuccessful: false,
+ updateFailed: false,
+ },
+ cert_manager: {
+ title: 'Cert-Manager',
+ status: APPLICATION_STATUS.INSTALLABLE,
+ installFailed: true,
+ statusReason: mockResponseData.applications[6].status_reason,
+ requestReason: null,
+ email: mockResponseData.applications[6].email,
+ installed: false,
+ uninstallable: false,
+ uninstallSuccessful: false,
+ uninstallFailed: false,
+ },
+ },
+ });
+ });
+
+ describe.each(APPLICATION_INSTALLED_STATUSES)('given the current app status is %s', status => {
+ it('marks application as installed', () => {
+ const mockResponseData =
+ CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
+ const runnerAppIndex = 2;
+
+ mockResponseData.applications[runnerAppIndex].status = status;
+
+ store.updateStateFromServer(mockResponseData);
+
+ expect(store.state.applications[RUNNER].installed).toBe(true);
+ });
+ });
+
+ it('sets default hostname for jupyter when ingress has a ip address', () => {
+ const mockResponseData =
+ CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
+
+ store.updateStateFromServer(mockResponseData);
+
+ expect(store.state.applications.jupyter.hostname).toEqual(
+ `jupyter.${store.state.applications.ingress.externalIp}.nip.io`,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
index 13e9fe00a00..13e9fe00a00 100644
--- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
diff --git a/spec/javascripts/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js
index 984b3026209..984b3026209 100644
--- a/spec/javascripts/diffs/components/diff_stats_spec.js
+++ b/spec/frontend/diffs/components/diff_stats_spec.js
diff --git a/spec/frontend/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js
new file mode 100644
index 00000000000..ccdae4cb312
--- /dev/null
+++ b/spec/frontend/diffs/components/edit_button_spec.js
@@ -0,0 +1,61 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import EditButton from '~/diffs/components/edit_button.vue';
+
+const localVue = createLocalVue();
+const editPath = 'test-path';
+
+describe('EditButton', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(EditButton, {
+ localVue,
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has correct href attribute', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: false,
+ });
+
+ expect(wrapper.attributes('href')).toBe(editPath);
+ });
+
+ it('emits a show fork message event if current user can fork', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: true,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeTruthy();
+ });
+
+ it('doesnt emit a show fork message event if current user cannot fork', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: false,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeFalsy();
+ });
+
+ it('doesnt emit a show fork message event if current user can modify blob', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: true,
+ canModifyBlob: true,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeFalsy();
+ });
+});
diff --git a/spec/frontend/diffs/components/hidden_files_warning_spec.js b/spec/frontend/diffs/components/hidden_files_warning_spec.js
new file mode 100644
index 00000000000..5bf5ddd27bd
--- /dev/null
+++ b/spec/frontend/diffs/components/hidden_files_warning_spec.js
@@ -0,0 +1,48 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
+
+const localVue = createLocalVue();
+const propsData = {
+ total: '10',
+ visible: 5,
+ plainDiffPath: 'plain-diff-path',
+ emailPatchPath: 'email-patch-path',
+};
+
+describe('HiddenFilesWarning', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(HiddenFilesWarning, {
+ localVue,
+ sync: false,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has a correct plain diff URL', () => {
+ const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0];
+
+ expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath);
+ });
+
+ it('has a correct email patch URL', () => {
+ const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0];
+
+ expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath);
+ });
+
+ it('has a correct visible/total files text', () => {
+ const filesText = wrapper.find('strong');
+
+ expect(filesText.text()).toBe('5 of 10');
+ });
+});
diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index e45d34bf9d5..e45d34bf9d5 100644
--- a/spec/javascripts/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
new file mode 100644
index 00000000000..a8c8688441d
--- /dev/null
+++ b/spec/frontend/environment.js
@@ -0,0 +1,67 @@
+/* eslint-disable import/no-commonjs */
+
+const { ErrorWithStack } = require('jest-util');
+const JSDOMEnvironment = require('jest-environment-jsdom');
+
+class CustomEnvironment extends JSDOMEnvironment {
+ constructor(config, context) {
+ super(config, context);
+
+ Object.assign(context.console, {
+ error(...args) {
+ throw new ErrorWithStack(
+ `Unexpected call of console.error() with:\n\n${args.join(', ')}`,
+ this.error,
+ );
+ },
+
+ warn(...args) {
+ throw new ErrorWithStack(
+ `Unexpected call of console.warn() with:\n\n${args.join(', ')}`,
+ this.warn,
+ );
+ },
+ });
+
+ const { testEnvironmentOptions } = config;
+ const { IS_EE } = testEnvironmentOptions;
+ this.global.gon = {
+ ee: IS_EE,
+ };
+
+ this.rejectedPromises = [];
+
+ this.global.promiseRejectionHandler = error => {
+ this.rejectedPromises.push(error);
+ };
+
+ this.global.fixturesBasePath = `${process.cwd()}/${
+ IS_EE ? 'ee/' : ''
+ }spec/javascripts/fixtures`;
+
+ // Not yet supported by JSDOM: https://github.com/jsdom/jsdom/issues/317
+ this.global.document.createRange = () => ({
+ setStart: () => {},
+ setEnd: () => {},
+ commonAncestorContainer: {
+ nodeName: 'BODY',
+ ownerDocument: this.global.document,
+ },
+ });
+ }
+
+ async teardown() {
+ await new Promise(setImmediate);
+
+ if (this.rejectedPromises.length > 0) {
+ throw new ErrorWithStack(
+ `Unhandled Promise rejections: ${this.rejectedPromises.join(', ')}`,
+ this.teardown,
+ );
+ }
+
+ await super.teardown();
+ }
+}
+
+module.exports = CustomEnvironment;
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
new file mode 100644
index 00000000000..67e5dc399ac
--- /dev/null
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -0,0 +1,118 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
+import { GlButton, GlEmptyState, GlLoadingIcon, GlTable, GlLink } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('ErrorTrackingList', () => {
+ let store;
+ let wrapper;
+ let actions;
+
+ function mountComponent({ errorTrackingEnabled = true } = {}) {
+ wrapper = shallowMount(ErrorTrackingList, {
+ localVue,
+ store,
+ propsData: {
+ indexPath: '/path',
+ enableErrorTrackingLink: '/link',
+ errorTrackingEnabled,
+ illustrationPath: 'illustration/path',
+ },
+ stubs: {
+ 'gl-link': GlLink,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ actions = {
+ getErrorList: () => {},
+ startPolling: () => {},
+ restartPolling: jest.fn().mockName('restartPolling'),
+ };
+
+ const state = {
+ errors: [],
+ loading: true,
+ };
+
+ store = new Vuex.Store({
+ actions,
+ state,
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('loading', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('shows spinner', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+
+ describe('results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+ });
+
+ describe('no results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows empty table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+
+ it('shows a message prompting to refresh', () => {
+ const refreshLink = wrapper.vm.$refs.empty.querySelector('a');
+
+ expect(refreshLink.textContent.trim()).toContain('Check again');
+ });
+
+ it('restarts polling', () => {
+ wrapper.find('.js-try-again').trigger('click');
+
+ expect(actions.restartPolling).toHaveBeenCalled();
+ });
+ });
+
+ describe('error tracking feature disabled', () => {
+ beforeEach(() => {
+ mountComponent({ errorTrackingEnabled: false });
+ });
+
+ it('shows empty state', () => {
+ expect(wrapper.find(GlEmptyState).exists()).toBeTruthy();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/frontend/error_tracking/store/mutation_spec.js
index 8117104bdbc..8117104bdbc 100644
--- a/spec/javascripts/error_tracking/store/mutation_spec.js
+++ b/spec/frontend/error_tracking/store/mutation_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js
index d1fea18dea8..d1fea18dea8 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js
diff --git a/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js
new file mode 100644
index 00000000000..0e62bc94517
--- /dev/null
+++ b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js
@@ -0,0 +1,18 @@
+import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
+
+describe('RecentSearchesServiceError', () => {
+ let recentSearchesServiceError;
+
+ beforeEach(() => {
+ recentSearchesServiceError = new RecentSearchesServiceError();
+ });
+
+ it('instantiates an instance of RecentSearchesServiceError and not an Error', () => {
+ expect(recentSearchesServiceError).toEqual(expect.any(RecentSearchesServiceError));
+ expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError');
+ });
+
+ it('should set a default message', () => {
+ expect(recentSearchesServiceError.message).toBe('Recent Searches Service is unavailable');
+ });
+});
diff --git a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
index 56bb82ae941..56bb82ae941 100644
--- a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js
+++ b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
diff --git a/spec/javascripts/frequent_items/store/getters_spec.js b/spec/frontend/frequent_items/store/getters_spec.js
index 1cd12eb6832..1cd12eb6832 100644
--- a/spec/javascripts/frequent_items/store/getters_spec.js
+++ b/spec/frontend/frequent_items/store/getters_spec.js
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index c7008c780d6..8af49fd47a2 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -1,10 +1,15 @@
/* eslint no-param-reassign: "off" */
import $ from 'jquery';
-import GfmAutoComplete from '~/gfm_auto_complete';
+import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
-import 'vendor/jquery.caret';
-import 'vendor/jquery.atwho';
+import 'jquery.caret';
+import 'at.js';
+
+import { TEST_HOST } from 'helpers/test_constants';
+import { getJSONFixture } from 'helpers/fixtures';
+
+const labelsFixture = getJSONFixture('autocomplete_sources/labels.json');
describe('GfmAutoComplete', () => {
const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
@@ -12,11 +17,12 @@ describe('GfmAutoComplete', () => {
});
let atwhoInstance;
- let items;
let sorterValue;
describe('DefaultOptions.sorter', () => {
describe('assets loading', () => {
+ let items;
+
beforeEach(() => {
jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true);
@@ -61,7 +67,7 @@ describe('GfmAutoComplete', () => {
atwhoInstance = { setting: {} };
const query = 'query';
- items = [];
+ const items = [];
const searchKey = 'searchKey';
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey);
@@ -85,7 +91,7 @@ describe('GfmAutoComplete', () => {
});
it('should quote if value contains any non-alphanumeric characters', () => {
- expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label\\-20"');
+ expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label-20"');
expect(beforeInsert(atwhoInstance, '~label 20')).toBe('~"label 20"');
});
@@ -93,12 +99,21 @@ describe('GfmAutoComplete', () => {
expect(beforeInsert(atwhoInstance, '~1234')).toBe('~"1234"');
});
- it('should escape Markdown emphasis characters, except in the first character', () => {
- expect(beforeInsert(atwhoInstance, '@_group')).toEqual('@\\_group');
- expect(beforeInsert(atwhoInstance, '~_bug')).toEqual('~\\_bug');
+ it('escapes Markdown strikethroughs when needed', () => {
+ expect(beforeInsert(atwhoInstance, '~a~bug')).toEqual('~"a~bug"');
+ expect(beforeInsert(atwhoInstance, '~a~~bug~~')).toEqual('~"a\\~~bug\\~~"');
+ });
+
+ it('escapes Markdown emphasis when needed', () => {
+ expect(beforeInsert(atwhoInstance, '~a_bug_')).toEqual('~a_bug\\_');
+ expect(beforeInsert(atwhoInstance, '~a _bug_')).toEqual('~"a \\_bug\\_"');
+ expect(beforeInsert(atwhoInstance, '~a*bug*')).toEqual('~"a\\*bug\\*"');
+ expect(beforeInsert(atwhoInstance, '~a *bug*')).toEqual('~"a \\*bug\\*"');
+ });
+
+ it('escapes Markdown code spans when needed', () => {
+ expect(beforeInsert(atwhoInstance, '~a`bug`')).toEqual('~"a\\`bug\\`"');
expect(beforeInsert(atwhoInstance, '~a `bug`')).toEqual('~"a \\`bug\\`"');
- expect(beforeInsert(atwhoInstance, '~a ~bug')).toEqual('~"a \\~bug"');
- expect(beforeInsert(atwhoInstance, '~a **bug')).toEqual('~"a \\*\\*bug"');
});
});
@@ -191,6 +206,38 @@ describe('GfmAutoComplete', () => {
});
});
+ describe('DefaultOptions.highlighter', () => {
+ beforeEach(() => {
+ atwhoInstance = { setting: {} };
+ });
+
+ it('should return li if no query is given', () => {
+ const liTag = '<li></li>';
+
+ const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag);
+
+ expect(highlightedTag).toEqual(liTag);
+ });
+
+ it('should highlight search query in li element', () => {
+ const liTag = '<li><img src="" />string</li>';
+ const query = 's';
+
+ const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query);
+
+ expect(highlightedTag).toEqual('<li><img src="" /> <strong>s</strong>tring </li>');
+ });
+
+ it('should highlight search query with special char in li element', () => {
+ const liTag = '<li><img src="" />te.st</li>';
+ const query = '.';
+
+ const highlightedTag = gfmAutoCompleteCallbacks.highlighter.call(atwhoInstance, liTag, query);
+
+ expect(highlightedTag).toEqual('<li><img src="" /> te<strong>.</strong>st </li>');
+ });
+ });
+
describe('isLoading', () => {
it('should be true with loading data object item', () => {
expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
@@ -250,4 +297,90 @@ describe('GfmAutoComplete', () => {
).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
});
});
+
+ describe('labels', () => {
+ const dataSources = {
+ labels: `${TEST_HOST}/autocomplete_sources/labels`,
+ };
+
+ const allLabels = labelsFixture;
+ const assignedLabels = allLabels.filter(label => label.set);
+ const unassignedLabels = allLabels.filter(label => !label.set);
+
+ let autocomplete;
+ let $textarea;
+
+ beforeEach(() => {
+ autocomplete = new GfmAutoComplete(dataSources);
+ $textarea = $('<textarea></textarea>');
+ autocomplete.setup($textarea, { labels: true });
+ });
+
+ afterEach(() => {
+ autocomplete.destroy();
+ });
+
+ const triggerDropdown = text => {
+ $textarea
+ .trigger('focus')
+ .val(text)
+ .caret('pos', -1);
+ $textarea.trigger('keyup');
+
+ return new Promise(window.requestAnimationFrame);
+ };
+
+ const getDropdownItems = () => {
+ const dropdown = document.getElementById('at-view-labels');
+ const items = dropdown.getElementsByTagName('li');
+ return [].map.call(items, item => item.textContent.trim());
+ };
+
+ const expectLabels = ({ input, output }) =>
+ triggerDropdown(input).then(() => {
+ expect(getDropdownItems()).toEqual(output.map(label => label.title));
+ });
+
+ describe('with no labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...unassignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${unassignedLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${unassignedLabels}
+ ${'/unlabel ~'} | ${[]}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with some labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = allLabels;
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${allLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${allLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with all labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...assignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${assignedLabels}
+ ${'/label ~'} | ${[]}
+ ${'/relabel ~'} | ${assignedLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+ });
});
diff --git a/spec/frontend/helpers/class_spec_helper.js b/spec/frontend/helpers/class_spec_helper.js
new file mode 100644
index 00000000000..7a60d33b471
--- /dev/null
+++ b/spec/frontend/helpers/class_spec_helper.js
@@ -0,0 +1,9 @@
+export default class ClassSpecHelper {
+ static itShouldBeAStaticMethod(base, method) {
+ return it('should be a static method', () => {
+ expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy();
+ });
+ }
+}
+
+window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js
new file mode 100644
index 00000000000..b77bcd6266e
--- /dev/null
+++ b/spec/frontend/helpers/fixtures.js
@@ -0,0 +1,34 @@
+import fs from 'fs';
+import path from 'path';
+
+import { ErrorWithStack } from 'jest-util';
+
+export function getFixture(relativePath) {
+ const absolutePath = path.join(global.fixturesBasePath, relativePath);
+ if (!fs.existsSync(absolutePath)) {
+ throw new ErrorWithStack(
+ `Fixture file ${relativePath} does not exist.
+
+Did you run bin/rake karma:fixtures?`,
+ getFixture,
+ );
+ }
+
+ return fs.readFileSync(absolutePath, 'utf8');
+}
+
+export const getJSONFixture = relativePath => JSON.parse(getFixture(relativePath));
+
+export const resetHTMLFixture = () => {
+ document.body.textContent = '';
+};
+
+export const setHTMLFixture = (htmlContent, resetHook = afterEach) => {
+ document.body.outerHTML = htmlContent;
+ resetHook(resetHTMLFixture);
+};
+
+export const loadHTMLFixture = (relativePath, resetHook = afterEach) => {
+ const fileContent = getFixture(relativePath);
+ setHTMLFixture(fileContent, resetHook);
+};
diff --git a/spec/frontend/helpers/jest_helpers.js b/spec/frontend/helpers/jest_helpers.js
new file mode 100644
index 00000000000..4a150be9935
--- /dev/null
+++ b/spec/frontend/helpers/jest_helpers.js
@@ -0,0 +1,24 @@
+/* eslint-disable import/prefer-default-export */
+
+/*
+@module
+
+This method provides convenience functions to help migrating from Karma/Jasmine to Jest.
+
+Try not to use these in new tests - this module is provided primarily for convenience of migrating tests.
+ */
+
+/**
+ * Creates a plain JS object pre-populated with Jest spy functions. Useful for making simple mocks classes.
+ *
+ * @see https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3EcreateSpyObj%3C/code%3E
+ * @param {string} baseName Human-readable name of the object. This is used for reporting purposes.
+ * @param methods {string[]} List of method names that will be added to the spy object.
+ */
+export function createSpyObj(baseName, methods) {
+ const obj = {};
+ methods.forEach(method => {
+ obj[method] = jest.fn().mockName(`${baseName}#${method}`);
+ });
+ return obj;
+}
diff --git a/spec/frontend/helpers/jquery.js b/spec/frontend/helpers/jquery.js
new file mode 100644
index 00000000000..6421a592c0c
--- /dev/null
+++ b/spec/frontend/helpers/jquery.js
@@ -0,0 +1,6 @@
+import $ from 'jquery';
+
+global.$ = $;
+global.jQuery = $;
+
+export default $;
diff --git a/spec/frontend/helpers/local_storage_helper.js b/spec/frontend/helpers/local_storage_helper.js
new file mode 100644
index 00000000000..48e66b11767
--- /dev/null
+++ b/spec/frontend/helpers/local_storage_helper.js
@@ -0,0 +1,41 @@
+/**
+ * Manage the instance of a custom `window.localStorage`
+ *
+ * This only encapsulates the setup / teardown logic so that it can easily be
+ * reused with different implementations (i.e. a spy or a [fake][1])
+ *
+ * [1]: https://stackoverflow.com/a/41434763/1708147
+ *
+ * @param {() => any} fn Function that returns the object to use for localStorage
+ */
+const useLocalStorage = fn => {
+ const origLocalStorage = window.localStorage;
+ let currentLocalStorage;
+
+ Object.defineProperty(window, 'localStorage', {
+ get: () => currentLocalStorage,
+ });
+
+ beforeEach(() => {
+ currentLocalStorage = fn();
+ });
+
+ afterEach(() => {
+ currentLocalStorage = origLocalStorage;
+ });
+};
+
+/**
+ * Create an object with the localStorage interface but `jest.fn()` implementations.
+ */
+export const createLocalStorageSpy = () => ({
+ clear: jest.fn(),
+ getItem: jest.fn(),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+});
+
+/**
+ * Before each test, overwrite `window.localStorage` with a spy implementation.
+ */
+export const useLocalStorageSpy = () => useLocalStorage(createLocalStorageSpy);
diff --git a/spec/frontend/helpers/locale_helper.js b/spec/frontend/helpers/locale_helper.js
new file mode 100644
index 00000000000..80047b06003
--- /dev/null
+++ b/spec/frontend/helpers/locale_helper.js
@@ -0,0 +1,11 @@
+/* eslint-disable import/prefer-default-export */
+
+export const setLanguage = languageCode => {
+ const htmlElement = document.querySelector('html');
+
+ if (languageCode) {
+ htmlElement.setAttribute('lang', languageCode);
+ } else {
+ htmlElement.removeAttribute('lang');
+ }
+};
diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js
new file mode 100644
index 00000000000..2e8bff298c4
--- /dev/null
+++ b/spec/frontend/helpers/monitor_helper_spec.js
@@ -0,0 +1,45 @@
+import * as monitorHelper from '~/helpers/monitor_helper';
+
+describe('monitor helper', () => {
+ const defaultConfig = { default: true, name: 'default name' };
+ const name = 'data name';
+ const series = [[1, 1], [2, 2], [3, 3]];
+ const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }];
+
+ describe('makeDataSeries', () => {
+ const expectedDataSeries = [
+ {
+ ...defaultConfig,
+ data: series,
+ },
+ ];
+
+ it('converts query results to data series', () => {
+ expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual(
+ expectedDataSeries,
+ );
+ });
+
+ it('returns an empty array if no query results exist', () => {
+ expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]);
+ });
+
+ it('handles multi-series query results', () => {
+ const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' };
+
+ expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([
+ expectedData,
+ expectedData,
+ ]);
+ });
+
+ it('excludes NaN values', () => {
+ expect(
+ monitorHelper.makeDataSeries(
+ data({ metric: {}, values: [[1, 1], [2, NaN]] }),
+ defaultConfig,
+ ),
+ ).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]);
+ });
+ });
+});
diff --git a/spec/frontend/helpers/scroll_into_view_promise.js b/spec/frontend/helpers/scroll_into_view_promise.js
new file mode 100644
index 00000000000..0edea2103da
--- /dev/null
+++ b/spec/frontend/helpers/scroll_into_view_promise.js
@@ -0,0 +1,28 @@
+export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) {
+ return new Promise((resolve, reject) => {
+ let intersectionObserver;
+ let retry = 0;
+
+ const intervalId = setInterval(() => {
+ if (retry >= maxTries) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`));
+ }
+ retry += 1;
+ intersectionTarget.scrollIntoView();
+ }, timeout);
+
+ intersectionObserver = new IntersectionObserver(entries => {
+ if (entries[0].isIntersecting) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ resolve();
+ }
+ });
+
+ intersectionObserver.observe(intersectionTarget);
+
+ intersectionTarget.scrollIntoView();
+ });
+}
diff --git a/spec/frontend/helpers/set_timeout_promise_helper.js b/spec/frontend/helpers/set_timeout_promise_helper.js
new file mode 100644
index 00000000000..47087619187
--- /dev/null
+++ b/spec/frontend/helpers/set_timeout_promise_helper.js
@@ -0,0 +1,4 @@
+export default (time = 0) =>
+ new Promise(resolve => {
+ setTimeout(resolve, time);
+ });
diff --git a/spec/javascripts/helpers/vue_component_helper.js b/spec/frontend/helpers/text_helper.js
index e0fe18e5560..e0fe18e5560 100644
--- a/spec/javascripts/helpers/vue_component_helper.js
+++ b/spec/frontend/helpers/text_helper.js
diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js
new file mode 100644
index 00000000000..702ef0be5aa
--- /dev/null
+++ b/spec/frontend/helpers/timeout.js
@@ -0,0 +1,59 @@
+const NS_PER_SEC = 1e9;
+const NS_PER_MS = 1e6;
+const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk');
+
+let testTimeoutNS;
+
+export const setTestTimeout = newTimeoutMS => {
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ testTimeoutNS = newTimeoutNS;
+ jest.setTimeout(newTimeoutMS);
+};
+
+// Allows slow tests to set their own timeout.
+// Useful for tests with jQuery, which is very slow in big DOMs.
+let temporaryTimeoutNS = null;
+export const setTestTimeoutOnce = newTimeoutMS => {
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ temporaryTimeoutNS = newTimeoutNS;
+};
+
+export const initializeTestTimeout = defaultTimeoutMS => {
+ setTestTimeout(defaultTimeoutMS);
+
+ let testStartTime;
+
+ // https://github.com/facebook/jest/issues/6947
+ beforeEach(() => {
+ testStartTime = process.hrtime();
+ });
+
+ afterEach(() => {
+ let timeoutNS = testTimeoutNS;
+ if (Number.isFinite(temporaryTimeoutNS)) {
+ timeoutNS = temporaryTimeoutNS;
+ temporaryTimeoutNS = null;
+ }
+
+ const [seconds, remainingNs] = process.hrtime(testStartTime);
+ const elapsedNS = seconds * NS_PER_SEC + remainingNs;
+
+ // Disable the timeout error when debugging. It is meaningless because
+ // debugging always takes longer than the test timeout.
+ if (elapsedNS > timeoutNS && !IS_DEBUGGING) {
+ throw new Error(
+ `Test took too long (${elapsedNS / NS_PER_MS}ms > ${timeoutNS / NS_PER_MS}ms)!`,
+ );
+ }
+ });
+};
diff --git a/spec/frontend/helpers/user_mock_data_helper.js b/spec/frontend/helpers/user_mock_data_helper.js
new file mode 100644
index 00000000000..6999fa1f8a1
--- /dev/null
+++ b/spec/frontend/helpers/user_mock_data_helper.js
@@ -0,0 +1,14 @@
+export default {
+ createNumberRandomUsers(numberUsers) {
+ const users = [];
+ for (let i = 0; i < numberUsers; i += 1) {
+ users.push({
+ avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: i + 1,
+ name: `GitLab User ${i}`,
+ username: `gitlab${i}`,
+ });
+ }
+ return users;
+ },
+};
diff --git a/spec/frontend/helpers/vue_mount_component_helper.js b/spec/frontend/helpers/vue_mount_component_helper.js
new file mode 100644
index 00000000000..6848c95d95d
--- /dev/null
+++ b/spec/frontend/helpers/vue_mount_component_helper.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+
+const mountComponent = (Component, props = {}, el = null) =>
+ new Component({
+ propsData: props,
+ }).$mount(el);
+
+export const createComponentWithStore = (Component, store, propsData = {}) =>
+ new Component({
+ store,
+ propsData,
+ });
+
+export const mountComponentWithStore = (Component, { el, props, store }) =>
+ new Component({
+ store,
+ propsData: props || {},
+ }).$mount(el);
+
+export const mountComponentWithSlots = (Component, { props, slots }) => {
+ const component = new Component({
+ propsData: props || {},
+ });
+
+ component.$slots = slots;
+
+ return component.$mount();
+};
+
+/**
+ * Mount a component with the given render method.
+ *
+ * This helps with inserting slots that need to be compiled.
+ */
+export const mountComponentWithRender = (render, el = null) =>
+ mountComponent(Vue.extend({ render }), {}, el);
+
+export default mountComponent;
diff --git a/spec/frontend/helpers/vue_resource_helper.js b/spec/frontend/helpers/vue_resource_helper.js
new file mode 100644
index 00000000000..0f58af09933
--- /dev/null
+++ b/spec/frontend/helpers/vue_resource_helper.js
@@ -0,0 +1,11 @@
+// eslint-disable-next-line import/prefer-default-export
+export const headersInterceptor = (request, next) => {
+ next(response => {
+ const headers = {};
+ response.headers.forEach((value, key) => {
+ headers[key] = value;
+ });
+ // eslint-disable-next-line no-param-reassign
+ response.headers = headers;
+ });
+};
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
new file mode 100644
index 00000000000..121e99c9783
--- /dev/null
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -0,0 +1,21 @@
+/* eslint-disable import/prefer-default-export */
+
+const vNodeContainsText = (vnode, text) =>
+ (vnode.text && vnode.text.includes(text)) ||
+ (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
+
+/**
+ * Determines whether a `shallowMount` Wrapper contains text
+ * within one of it's slots. This will also work on Wrappers
+ * acquired with `find()`, but only if it's parent Wrapper
+ * was shallowMounted.
+ * NOTE: Prefer checking the rendered output of a component
+ * wherever possible using something like `text()` instead.
+ * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
+ * @param {String} slotName
+ * @param {String} text
+ */
+export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
+ Boolean(
+ shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
+ );
diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js
new file mode 100644
index 00000000000..88652202a8e
--- /dev/null
+++ b/spec/frontend/helpers/vuex_action_helper.js
@@ -0,0 +1,104 @@
+const noop = () => {};
+
+/**
+ * Helper for testing action with expected mutations inspired in
+ * https://vuex.vuejs.org/en/testing.html
+ *
+ * @param {Function} action to be tested
+ * @param {Object} payload will be provided to the action
+ * @param {Object} state will be provided to the action
+ * @param {Array} [expectedMutations=[]] mutations expected to be committed
+ * @param {Array} [expectedActions=[]] actions expected to be dispatched
+ * @param {Function} [done=noop] to be executed after the tests
+ * @return {Promise}
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked payload
+ * state, //state
+ * // expected mutations
+ * [
+ * { type: types.MUTATION}
+ * { type: types.MUTATION_1, payload: jasmine.any(Number)}
+ * ],
+ * // expected actions
+ * [
+ * { type: 'actionName', payload: {param: 'foobar'}},
+ * { type: 'actionName1'}
+ * ]
+ * done,
+ * );
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked payload
+ * state, //state
+ * [ { type: types.MUTATION} ], // expected mutations
+ * [], // expected actions
+ * ).then(done)
+ * .catch(done.fail);
+ */
+export default (
+ action,
+ payload,
+ state,
+ expectedMutations = [],
+ expectedActions = [],
+ done = noop,
+) => {
+ const mutations = [];
+ const actions = [];
+
+ // mock commit
+ const commit = (type, mutationPayload) => {
+ const mutation = { type };
+
+ if (typeof mutationPayload !== 'undefined') {
+ mutation.payload = mutationPayload;
+ }
+
+ mutations.push(mutation);
+ };
+
+ // mock dispatch
+ const dispatch = (type, actionPayload) => {
+ const dispatchedAction = { type };
+
+ if (typeof actionPayload !== 'undefined') {
+ dispatchedAction.payload = actionPayload;
+ }
+
+ actions.push(dispatchedAction);
+ };
+
+ const validateResults = () => {
+ expect({
+ mutations,
+ actions,
+ }).toEqual({
+ mutations: expectedMutations,
+ actions: expectedActions,
+ });
+ done();
+ };
+
+ const result = action(
+ { commit, state, dispatch, rootState: state, rootGetters: state, getters: state },
+ payload,
+ );
+
+ return new Promise(resolve => {
+ setImmediate(resolve);
+ })
+ .then(() => result)
+ .catch(error => {
+ validateResults();
+ throw error;
+ })
+ .then(data => {
+ validateResults();
+ return data;
+ });
+};
diff --git a/spec/frontend/helpers/wait_for_attribute_change.js b/spec/frontend/helpers/wait_for_attribute_change.js
new file mode 100644
index 00000000000..8f22d569222
--- /dev/null
+++ b/spec/frontend/helpers/wait_for_attribute_change.js
@@ -0,0 +1,16 @@
+export default (domElement, attributes, timeout = 1500) =>
+ new Promise((resolve, reject) => {
+ let observer;
+ const timeoutId = setTimeout(() => {
+ observer.disconnect();
+ reject(new Error(`Could not see an attribute update within ${timeout} ms`));
+ }, timeout);
+
+ observer = new MutationObserver(() => {
+ clearTimeout(timeoutId);
+ observer.disconnect();
+ resolve();
+ });
+
+ observer.observe(domElement, { attributes: true, attributeFilter: attributes });
+ });
diff --git a/spec/frontend/helpers/wait_for_promises.js b/spec/frontend/helpers/wait_for_promises.js
new file mode 100644
index 00000000000..1d2b53fc770
--- /dev/null
+++ b/spec/frontend/helpers/wait_for_promises.js
@@ -0,0 +1 @@
+export default () => new Promise(resolve => requestAnimationFrame(resolve));
diff --git a/spec/frontend/ide/lib/common/disposable_spec.js b/spec/frontend/ide/lib/common/disposable_spec.js
new file mode 100644
index 00000000000..8596642eb7a
--- /dev/null
+++ b/spec/frontend/ide/lib/common/disposable_spec.js
@@ -0,0 +1,44 @@
+import Disposable from '~/ide/lib/common/disposable';
+
+describe('Multi-file editor library disposable class', () => {
+ let instance;
+ let disposableClass;
+
+ beforeEach(() => {
+ instance = new Disposable();
+
+ disposableClass = {
+ dispose: jest.fn().mockName('dispose'),
+ };
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ });
+
+ describe('add', () => {
+ it('adds disposable classes', () => {
+ instance.add(disposableClass);
+
+ expect(instance.disposers.size).toBe(1);
+ });
+ });
+
+ describe('dispose', () => {
+ beforeEach(() => {
+ instance.add(disposableClass);
+ });
+
+ it('calls dispose on all cached disposers', () => {
+ instance.dispose();
+
+ expect(disposableClass.dispose).toHaveBeenCalled();
+ });
+
+ it('clears cached disposers', () => {
+ instance.dispose();
+
+ expect(instance.disposers.size).toBe(0);
+ });
+ });
+});
diff --git a/spec/frontend/ide/lib/diff/diff_spec.js b/spec/frontend/ide/lib/diff/diff_spec.js
new file mode 100644
index 00000000000..d9b088e2c12
--- /dev/null
+++ b/spec/frontend/ide/lib/diff/diff_spec.js
@@ -0,0 +1,77 @@
+import { computeDiff } from '~/ide/lib/diff/diff';
+
+describe('Multi-file editor library diff calculator', () => {
+ describe('computeDiff', () => {
+ it('returns empty array if no changes', () => {
+ const diff = computeDiff('123', '123');
+
+ expect(diff).toEqual([]);
+ });
+
+ describe('modified', () => {
+ it.each`
+ originalContent | newContent | lineNumber
+ ${'123'} | ${'1234'} | ${1}
+ ${'123\n123\n123'} | ${'123\n1234\n123'} | ${2}
+ `(
+ 'marks line $lineNumber as added and modified but not removed',
+ ({ originalContent, newContent, lineNumber }) => {
+ const diff = computeDiff(originalContent, newContent)[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeTruthy();
+ expect(diff.removed).toBeUndefined();
+ expect(diff.lineNumber).toBe(lineNumber);
+ },
+ );
+ });
+
+ describe('added', () => {
+ it.each`
+ originalContent | newContent | lineNumber
+ ${'123'} | ${'123\n123'} | ${1}
+ ${'123\n123\n123'} | ${'123\n123\n1234\n123'} | ${3}
+ `(
+ 'marks line $lineNumber as added but not modified and not removed',
+ ({ originalContent, newContent, lineNumber }) => {
+ const diff = computeDiff(originalContent, newContent)[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeUndefined();
+ expect(diff.removed).toBeUndefined();
+ expect(diff.lineNumber).toBe(lineNumber);
+ },
+ );
+ });
+
+ describe('removed', () => {
+ it.each`
+ originalContent | newContent | lineNumber | modified
+ ${'123'} | ${''} | ${1} | ${undefined}
+ ${'123\n123\n123'} | ${'123\n123'} | ${2} | ${true}
+ `(
+ 'marks line $lineNumber as removed',
+ ({ originalContent, newContent, lineNumber, modified }) => {
+ const diff = computeDiff(originalContent, newContent)[0];
+
+ expect(diff.added).toBeUndefined();
+ expect(diff.modified).toBe(modified);
+ expect(diff.removed).toBeTruthy();
+ expect(diff.lineNumber).toBe(lineNumber);
+ },
+ );
+ });
+
+ it('includes line number of change', () => {
+ const diff = computeDiff('123', '')[0];
+
+ expect(diff.lineNumber).toBe(1);
+ });
+
+ it('includes end line number of change', () => {
+ const diff = computeDiff('123', '')[0];
+
+ expect(diff.endLineNumber).toBe(1);
+ });
+ });
+});
diff --git a/spec/frontend/ide/lib/editor_options_spec.js b/spec/frontend/ide/lib/editor_options_spec.js
new file mode 100644
index 00000000000..b07a583b7c8
--- /dev/null
+++ b/spec/frontend/ide/lib/editor_options_spec.js
@@ -0,0 +1,11 @@
+import editorOptions from '~/ide/lib/editor_options';
+
+describe('Multi-file editor library editor options', () => {
+ it('returns an array', () => {
+ expect(editorOptions).toEqual(expect.any(Array));
+ });
+
+ it('contains readOnly option', () => {
+ expect(editorOptions[0].readOnly).toBeDefined();
+ });
+});
diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
new file mode 100644
index 00000000000..aa1fa0373db
--- /dev/null
+++ b/spec/frontend/ide/lib/files_spec.js
@@ -0,0 +1,78 @@
+import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
+import { decorateFiles, splitParent, escapeFileUrl } from '~/ide/lib/files';
+import { decorateData } from '~/ide/stores/utils';
+
+const TEST_BRANCH_ID = 'lorem-ipsum';
+const TEST_PROJECT_ID = 10;
+
+const createEntries = paths => {
+ const createEntry = (acc, { path, type, children }) => {
+ // Sometimes we need to end the url with a '/'
+ const createUrl = base => (type === 'tree' ? `${base}/` : base);
+
+ const { name, parent } = splitParent(path);
+ const parentEntry = acc[parent];
+
+ acc[path] = {
+ ...decorateData({
+ projectId: TEST_PROJECT_ID,
+ branchId: TEST_BRANCH_ID,
+ id: path,
+ name,
+ path,
+ url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${escapeFileUrl(path)}`),
+ type,
+ previewMode: viewerInformationForPath(path),
+ parentPath: parent,
+ parentTreeUrl: parentEntry
+ ? parentEntry.url
+ : createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}`),
+ }),
+ tree: children.map(childName => expect.objectContaining({ name: childName })),
+ };
+
+ return acc;
+ };
+
+ const entries = paths.reduce(createEntry, {});
+
+ // Wrap entries in expect.objectContaining.
+ // We couldn't do this earlier because we still need to select properties from parent entries.
+ return Object.keys(entries).reduce((acc, key) => {
+ acc[key] = expect.objectContaining(entries[key]);
+
+ return acc;
+ }, {});
+};
+
+describe('IDE lib decorate files', () => {
+ it('creates entries and treeList', () => {
+ const data = ['app/assets/apples/foo.js', 'app/bugs.js', 'app/#weird#file?.txt', 'README.md'];
+ const expectedEntries = createEntries([
+ { path: 'app', type: 'tree', children: ['assets', '#weird#file?.txt', 'bugs.js'] },
+ { path: 'app/assets', type: 'tree', children: ['apples'] },
+ { path: 'app/assets/apples', type: 'tree', children: ['foo.js'] },
+ { path: 'app/assets/apples/foo.js', type: 'blob', children: [] },
+ { path: 'app/bugs.js', type: 'blob', children: [] },
+ { path: 'app/#weird#file?.txt', type: 'blob', children: [] },
+ { path: 'README.md', type: 'blob', children: [] },
+ ]);
+
+ const { entries, treeList } = decorateFiles({
+ data,
+ branchId: TEST_BRANCH_ID,
+ projectId: TEST_PROJECT_ID,
+ });
+
+ // Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)`
+ // was taking a very long time for some reason. Probably due to large objects and nested `expect.objectContaining`.
+ const entryKeys = Object.keys(entries);
+
+ expect(entryKeys).toEqual(Object.keys(expectedEntries));
+ entryKeys.forEach(key => {
+ expect(entries[key]).toEqual(expectedEntries[key]);
+ });
+
+ expect(treeList).toEqual([expectedEntries.app, expectedEntries['README.md']]);
+ });
+});
diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
new file mode 100644
index 00000000000..246500a2f34
--- /dev/null
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -0,0 +1,73 @@
+import commitState from '~/ide/stores/modules/commit/state';
+import mutations from '~/ide/stores/modules/commit/mutations';
+
+describe('IDE commit module mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = commitState();
+ });
+
+ describe('UPDATE_COMMIT_MESSAGE', () => {
+ it('updates commitMessage', () => {
+ mutations.UPDATE_COMMIT_MESSAGE(state, 'testing');
+
+ expect(state.commitMessage).toBe('testing');
+ });
+ });
+
+ describe('UPDATE_COMMIT_ACTION', () => {
+ it('updates commitAction', () => {
+ mutations.UPDATE_COMMIT_ACTION(state, { commitAction: 'testing' });
+
+ expect(state.commitAction).toBe('testing');
+ });
+ });
+
+ describe('UPDATE_NEW_BRANCH_NAME', () => {
+ it('updates newBranchName', () => {
+ mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing');
+
+ expect(state.newBranchName).toBe('testing');
+ });
+ });
+
+ describe('UPDATE_LOADING', () => {
+ it('updates submitCommitLoading', () => {
+ mutations.UPDATE_LOADING(state, true);
+
+ expect(state.submitCommitLoading).toBeTruthy();
+ });
+ });
+
+ describe('TOGGLE_SHOULD_CREATE_MR', () => {
+ it('changes shouldCreateMR to true when initial state is false', () => {
+ state.shouldCreateMR = false;
+ mutations.TOGGLE_SHOULD_CREATE_MR(state);
+
+ expect(state.shouldCreateMR).toBe(true);
+ });
+
+ it('changes shouldCreateMR to false when initial state is true', () => {
+ state.shouldCreateMR = true;
+ mutations.TOGGLE_SHOULD_CREATE_MR(state);
+
+ expect(state.shouldCreateMR).toBe(false);
+ });
+
+ it('sets shouldCreateMR to given value when passed in', () => {
+ state.shouldCreateMR = false;
+ mutations.TOGGLE_SHOULD_CREATE_MR(state, false);
+
+ expect(state.shouldCreateMR).toBe(false);
+ });
+ });
+
+ describe('INTERACT_WITH_NEW_MR', () => {
+ it('sets interactedWithNewMR to true', () => {
+ mutations.INTERACT_WITH_NEW_MR(state);
+
+ expect(state.interactedWithNewMR).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
index 17cb457881f..17cb457881f 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
diff --git a/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
new file mode 100644
index 00000000000..6a1a826093c
--- /dev/null
+++ b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
@@ -0,0 +1,88 @@
+import createState from '~/ide/stores/modules/file_templates/state';
+import * as types from '~/ide/stores/modules/file_templates/mutation_types';
+import mutations from '~/ide/stores/modules/file_templates/mutations';
+
+const mockFileTemplates = [['MIT'], ['CC']];
+const mockTemplateType = 'test';
+
+describe('IDE file templates mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe(`${types.REQUEST_TEMPLATE_TYPES}`, () => {
+ it('sets loading to true', () => {
+ state.isLoading = false;
+
+ mutations[types.REQUEST_TEMPLATE_TYPES](state);
+
+ expect(state.isLoading).toBe(true);
+ });
+
+ it('sets templates to an empty array', () => {
+ state.templates = mockFileTemplates;
+
+ mutations[types.REQUEST_TEMPLATE_TYPES](state);
+
+ expect(state.templates).toEqual([]);
+ });
+ });
+
+ describe(`${types.RECEIVE_TEMPLATE_TYPES_ERROR}`, () => {
+ it('sets isLoading', () => {
+ state.isLoading = true;
+
+ mutations[types.RECEIVE_TEMPLATE_TYPES_ERROR](state);
+
+ expect(state.isLoading).toBe(false);
+ });
+ });
+
+ describe(`${types.RECEIVE_TEMPLATE_TYPES_SUCCESS}`, () => {
+ it('sets isLoading to false', () => {
+ state.isLoading = true;
+
+ mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, mockFileTemplates);
+
+ expect(state.isLoading).toBe(false);
+ });
+
+ it('sets templates to payload', () => {
+ state.templates = ['test'];
+
+ mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, mockFileTemplates);
+
+ expect(state.templates).toEqual(mockFileTemplates);
+ });
+ });
+
+ describe(`${types.SET_SELECTED_TEMPLATE_TYPE}`, () => {
+ it('sets templates type to selected type', () => {
+ state.selectedTemplateType = '';
+
+ mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, mockTemplateType);
+
+ expect(state.selectedTemplateType).toBe(mockTemplateType);
+ });
+
+ it('sets templates to empty array', () => {
+ state.templates = mockFileTemplates;
+
+ mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, mockTemplateType);
+
+ expect(state.templates).toEqual([]);
+ });
+ });
+
+ describe(`${types.SET_UPDATE_SUCCESS}`, () => {
+ it('sets updateSuccess', () => {
+ state.updateSuccess = false;
+
+ mutations[types.SET_UPDATE_SUCCESS](state, true);
+
+ expect(state.updateSuccess).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/pane/getters_spec.js b/spec/frontend/ide/stores/modules/pane/getters_spec.js
index 8a213323de0..8a213323de0 100644
--- a/spec/javascripts/ide/stores/modules/pane/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/pane/getters_spec.js
diff --git a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
index b5fcd35912e..b5fcd35912e 100644
--- a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js
index 4514896b5ea..4514896b5ea 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js
diff --git a/spec/frontend/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js
new file mode 100644
index 00000000000..0900b25d5d3
--- /dev/null
+++ b/spec/frontend/ide/stores/mutations/branch_spec.js
@@ -0,0 +1,75 @@
+import mutations from '~/ide/stores/mutations/branch';
+import state from '~/ide/stores/state';
+
+describe('Multi-file store branch mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('SET_CURRENT_BRANCH', () => {
+ it('sets currentBranch', () => {
+ mutations.SET_CURRENT_BRANCH(localState, 'master');
+
+ expect(localState.currentBranchId).toBe('master');
+ });
+ });
+
+ describe('SET_BRANCH_COMMIT', () => {
+ it('sets the last commit on current project', () => {
+ localState.projects = {
+ Example: {
+ branches: {
+ master: {},
+ },
+ },
+ };
+
+ mutations.SET_BRANCH_COMMIT(localState, {
+ projectId: 'Example',
+ branchId: 'master',
+ commit: {
+ title: 'Example commit',
+ },
+ });
+
+ expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
+ });
+ });
+
+ describe('SET_BRANCH_WORKING_REFERENCE', () => {
+ beforeEach(() => {
+ localState.projects = {
+ Foo: {
+ branches: {
+ bar: {},
+ },
+ },
+ };
+ });
+
+ it('sets workingReference for existing branch', () => {
+ mutations.SET_BRANCH_WORKING_REFERENCE(localState, {
+ projectId: 'Foo',
+ branchId: 'bar',
+ reference: 'foo-bar-ref',
+ });
+
+ expect(localState.projects.Foo.branches.bar.workingReference).toBe('foo-bar-ref');
+ });
+
+ it('does not fail on non-existent just yet branch', () => {
+ expect(localState.projects.Foo.branches.unknown).toBeUndefined();
+
+ mutations.SET_BRANCH_WORKING_REFERENCE(localState, {
+ projectId: 'Foo',
+ branchId: 'unknown',
+ reference: 'fun-fun-ref',
+ });
+
+ expect(localState.projects.Foo.branches.unknown).not.toBeUndefined();
+ expect(localState.projects.Foo.branches.unknown.workingReference).toBe('fun-fun-ref');
+ });
+ });
+});
diff --git a/spec/frontend/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js
new file mode 100644
index 00000000000..afbe6770c0d
--- /dev/null
+++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js
@@ -0,0 +1,85 @@
+import mutations from '~/ide/stores/mutations/merge_request';
+import state from '~/ide/stores/state';
+
+describe('IDE store merge request mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ localState.projects = { abcproject: { mergeRequests: {} } };
+
+ mutations.SET_MERGE_REQUEST(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ mergeRequest: {
+ title: 'mr',
+ },
+ });
+ });
+
+ describe('SET_CURRENT_MERGE_REQUEST', () => {
+ it('sets current merge request', () => {
+ mutations.SET_CURRENT_MERGE_REQUEST(localState, 2);
+
+ expect(localState.currentMergeRequestId).toBe(2);
+ });
+ });
+
+ describe('SET_MERGE_REQUEST', () => {
+ it('setsmerge request data', () => {
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+
+ expect(newMr.title).toBe('mr');
+ expect(newMr.active).toBeTruthy();
+ });
+
+ it('keeps original data', () => {
+ const versions = ['change'];
+ const mergeRequest = localState.projects.abcproject.mergeRequests[1];
+
+ mergeRequest.versions = versions;
+
+ mutations.SET_MERGE_REQUEST(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ mergeRequest: {
+ title: ['change'],
+ },
+ });
+
+ expect(mergeRequest.title).toBe('mr');
+ expect(mergeRequest.versions).toEqual(versions);
+ });
+ });
+
+ describe('SET_MERGE_REQUEST_CHANGES', () => {
+ it('sets merge request changes', () => {
+ mutations.SET_MERGE_REQUEST_CHANGES(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ changes: {
+ diff: 'abc',
+ },
+ });
+
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+
+ expect(newMr.changes.diff).toBe('abc');
+ });
+ });
+
+ describe('SET_MERGE_REQUEST_VERSIONS', () => {
+ it('sets merge request versions', () => {
+ mutations.SET_MERGE_REQUEST_VERSIONS(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ versions: [{ id: 123 }],
+ });
+
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+
+ expect(newMr.versions.length).toBe(1);
+ expect(newMr.versions[0].id).toBe(123);
+ });
+ });
+});
diff --git a/spec/frontend/ide/stores/mutations/project_spec.js b/spec/frontend/ide/stores/mutations/project_spec.js
new file mode 100644
index 00000000000..b3ce39c33d2
--- /dev/null
+++ b/spec/frontend/ide/stores/mutations/project_spec.js
@@ -0,0 +1,23 @@
+import mutations from '~/ide/stores/mutations/project';
+import state from '~/ide/stores/state';
+
+describe('Multi-file store branch mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ localState.projects = { abcproject: { empty_repo: true } };
+ });
+
+ describe('TOGGLE_EMPTY_STATE', () => {
+ it('sets empty_repo for project to passed value', () => {
+ mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: false });
+
+ expect(localState.projects.abcproject.empty_repo).toBe(false);
+
+ mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: true });
+
+ expect(localState.projects.abcproject.empty_repo).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/frontend/image_diff/view_types_spec.js
index e9639f46497..e9639f46497 100644
--- a/spec/javascripts/image_diff/view_types_spec.js
+++ b/spec/frontend/image_diff/view_types_spec.js
diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js
new file mode 100644
index 00000000000..17a998d0174
--- /dev/null
+++ b/spec/frontend/import_projects/components/import_projects_table_spec.js
@@ -0,0 +1,185 @@
+import Vuex from 'vuex';
+import { createLocalVue, mount } from '@vue/test-utils';
+import { state, actions, getters, mutations } from '~/import_projects/store';
+import importProjectsTable from '~/import_projects/components/import_projects_table.vue';
+import STATUS_MAP from '~/import_projects/constants';
+
+describe('ImportProjectsTable', () => {
+ let vm;
+ const providerTitle = 'THE PROVIDER';
+ const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
+ const importedProject = {
+ id: 1,
+ fullPath: 'fullPath',
+ importStatus: 'started',
+ providerLink: 'providerLink',
+ importSource: 'importSource',
+ };
+
+ function initStore() {
+ const stubbedActions = Object.assign({}, actions, {
+ fetchJobs: jest.fn(),
+ fetchRepos: jest.fn(actions.requestRepos),
+ fetchImport: jest.fn(actions.requestImport),
+ });
+
+ const store = new Vuex.Store({
+ state: state(),
+ actions: stubbedActions,
+ mutations,
+ getters,
+ });
+
+ return store;
+ }
+
+ function mountComponent() {
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const store = initStore();
+
+ const component = mount(importProjectsTable, {
+ localVue,
+ store,
+ propsData: {
+ providerTitle,
+ },
+ sync: false,
+ });
+
+ return component.vm;
+ }
+
+ beforeEach(() => {
+ vm = mountComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a loading icon whilst repos are loading', () =>
+ vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull();
+ }));
+
+ it('renders a table with imported projects and provider repos', () => {
+ vm.$store.dispatch('receiveReposSuccess', {
+ importedProjects: [importedProject],
+ providerRepos: [providerRepo],
+ namespaces: [{ path: 'path' }],
+ });
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
+ expect(vm.$el.querySelector('.table')).not.toBeNull();
+ expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch(
+ `From ${providerTitle}`,
+ );
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
+ });
+ });
+
+ it('renders an empty state if there are no imported projects or provider repos', () => {
+ vm.$store.dispatch('receiveReposSuccess', {
+ importedProjects: [],
+ providerRepos: [],
+ namespaces: [],
+ });
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
+ expect(vm.$el.querySelector('.table')).toBeNull();
+ expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`);
+ });
+ });
+
+ it('shows loading spinner when bulk import button is clicked', () => {
+ vm.$store.dispatch('receiveReposSuccess', {
+ importedProjects: [],
+ providerRepos: [providerRepo],
+ namespaces: [{ path: 'path' }],
+ });
+
+ return vm
+ .$nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-imported-project')).toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
+
+ vm.$el.querySelector('.js-import-all').click();
+ })
+ .then(() => vm.$nextTick())
+ .then(() => {
+ expect(vm.$el.querySelector('.js-import-all .js-loading-button-icon')).not.toBeNull();
+ });
+ });
+
+ it('imports provider repos if bulk import button is clicked', () => {
+ mountComponent();
+
+ vm.$store.dispatch('receiveReposSuccess', {
+ importedProjects: [],
+ providerRepos: [providerRepo],
+ namespaces: [{ path: 'path' }],
+ });
+
+ return vm
+ .$nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-imported-project')).toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
+
+ vm.$store.dispatch('receiveImportSuccess', { importedProject, repoId: providerRepo.id });
+ })
+ .then(() => vm.$nextTick())
+ .then(() => {
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).toBeNull();
+ });
+ });
+
+ it('polls to update the status of imported projects', () => {
+ const updatedProjects = [
+ {
+ id: importedProject.id,
+ importStatus: 'finished',
+ },
+ ];
+
+ vm.$store.dispatch('receiveReposSuccess', {
+ importedProjects: [importedProject],
+ providerRepos: [],
+ namespaces: [{ path: 'path' }],
+ });
+
+ return vm
+ .$nextTick()
+ .then(() => {
+ const statusObject = STATUS_MAP[importedProject.importStatus];
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+
+ vm.$store.dispatch('receiveJobsSuccess', updatedProjects);
+ })
+ .then(() => vm.$nextTick())
+ .then(() => {
+ const statusObject = STATUS_MAP[updatedProjects[0].importStatus];
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/import_projects/components/imported_project_table_row_spec.js b/spec/frontend/import_projects/components/imported_project_table_row_spec.js
new file mode 100644
index 00000000000..f95acc1edd7
--- /dev/null
+++ b/spec/frontend/import_projects/components/imported_project_table_row_spec.js
@@ -0,0 +1,58 @@
+import Vuex from 'vuex';
+import createStore from '~/import_projects/store';
+import { createLocalVue, mount } from '@vue/test-utils';
+import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
+import STATUS_MAP from '~/import_projects/constants';
+
+describe('ImportedProjectTableRow', () => {
+ let vm;
+ const project = {
+ id: 1,
+ fullPath: 'fullPath',
+ importStatus: 'finished',
+ providerLink: 'providerLink',
+ importSource: 'importSource',
+ };
+
+ function mountComponent() {
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const component = mount(importedProjectTableRow, {
+ localVue,
+ store: createStore(),
+ propsData: {
+ project: {
+ ...project,
+ },
+ },
+ sync: false,
+ });
+
+ return component.vm;
+ }
+
+ beforeEach(() => {
+ vm = mountComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders an imported project table row', () => {
+ const providerLink = vm.$el.querySelector('.js-provider-link');
+ const statusObject = STATUS_MAP[project.importStatus];
+
+ expect(vm.$el.classList.contains('js-imported-project')).toBe(true);
+ expect(providerLink.href).toMatch(project.providerLink);
+ expect(providerLink.textContent).toMatch(project.importSource);
+ expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath);
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath);
+ });
+});
diff --git a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
new file mode 100644
index 00000000000..02c786d8d0b
--- /dev/null
+++ b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
@@ -0,0 +1,104 @@
+import Vuex from 'vuex';
+import { createLocalVue, mount } from '@vue/test-utils';
+import { state, actions, getters, mutations } from '~/import_projects/store';
+import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
+import STATUS_MAP, { STATUSES } from '~/import_projects/constants';
+
+describe('ProviderRepoTableRow', () => {
+ let vm;
+ const fetchImport = jest.fn((context, data) => actions.requestImport(context, data));
+ const importPath = '/import-path';
+ const defaultTargetNamespace = 'user';
+ const ciCdOnly = true;
+ const repo = {
+ id: 10,
+ sanitizedName: 'sanitizedName',
+ fullName: 'fullName',
+ providerLink: 'providerLink',
+ };
+
+ function initStore() {
+ const stubbedActions = Object.assign({}, actions, {
+ fetchImport,
+ });
+
+ const store = new Vuex.Store({
+ state: state(),
+ actions: stubbedActions,
+ mutations,
+ getters,
+ });
+
+ return store;
+ }
+
+ function mountComponent() {
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const store = initStore();
+ store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly });
+
+ const component = mount(providerRepoTableRow, {
+ localVue,
+ store,
+ propsData: {
+ repo,
+ },
+ sync: false,
+ });
+
+ return component.vm;
+ }
+
+ beforeEach(() => {
+ vm = mountComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a provider repo table row', () => {
+ const providerLink = vm.$el.querySelector('.js-provider-link');
+ const statusObject = STATUS_MAP[STATUSES.NONE];
+
+ expect(vm.$el.classList.contains('js-provider-repo')).toBe(true);
+ expect(providerLink.href).toMatch(repo.providerLink);
+ expect(providerLink.textContent).toMatch(repo.fullName);
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ expect(vm.$el.querySelector('.js-import-button')).not.toBeNull();
+ });
+
+ it('renders a select2 namespace select', () => {
+ const dropdownTrigger = vm.$el.querySelector('.js-namespace-select');
+
+ expect(dropdownTrigger).not.toBeNull();
+ expect(dropdownTrigger.classList.contains('select2-container')).toBe(true);
+
+ dropdownTrigger.click();
+
+ expect(vm.$el.querySelector('.select2-drop')).not.toBeNull();
+ });
+
+ it('imports repo when clicking import button', () => {
+ vm.$el.querySelector('.js-import-button').click();
+
+ return vm.$nextTick().then(() => {
+ const { calls } = fetchImport.mock;
+
+ // Not using .toBeCalledWith because it expects
+ // an unmatchable and undefined 3rd argument.
+ expect(calls.length).toBe(1);
+ expect(calls[0][1]).toEqual({
+ repo,
+ newName: repo.sanitizedName,
+ targetNamespace: defaultTargetNamespace,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js
new file mode 100644
index 00000000000..6a7b90788dd
--- /dev/null
+++ b/spec/frontend/import_projects/store/actions_spec.js
@@ -0,0 +1,284 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import {
+ SET_INITIAL_DATA,
+ REQUEST_REPOS,
+ RECEIVE_REPOS_SUCCESS,
+ RECEIVE_REPOS_ERROR,
+ REQUEST_IMPORT,
+ RECEIVE_IMPORT_SUCCESS,
+ RECEIVE_IMPORT_ERROR,
+ RECEIVE_JOBS_SUCCESS,
+} from '~/import_projects/store/mutation_types';
+import {
+ setInitialData,
+ requestRepos,
+ receiveReposSuccess,
+ receiveReposError,
+ fetchRepos,
+ requestImport,
+ receiveImportSuccess,
+ receiveImportError,
+ fetchImport,
+ receiveJobsSuccess,
+ fetchJobs,
+ clearJobsEtagPoll,
+ stopJobsPolling,
+} from '~/import_projects/store/actions';
+import state from '~/import_projects/store/state';
+import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+
+describe('import_projects store actions', () => {
+ let localState;
+ const repoId = 1;
+ const repos = [{ id: 1 }, { id: 2 }];
+ const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } };
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('setInitialData', () => {
+ it(`commits ${SET_INITIAL_DATA} mutation`, done => {
+ const initialData = {
+ reposPath: 'reposPath',
+ provider: 'provider',
+ jobsPath: 'jobsPath',
+ importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath',
+ defaultTargetNamespace: 'defaultTargetNamespace',
+ ciCdOnly: 'ciCdOnly',
+ canSelectNamespace: 'canSelectNamespace',
+ };
+
+ testAction(
+ setInitialData,
+ initialData,
+ localState,
+ [{ type: SET_INITIAL_DATA, payload: initialData }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestRepos', () => {
+ it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => {
+ testAction(
+ requestRepos,
+ null,
+ localState,
+ [{ type: REQUEST_REPOS, payload: null }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReposSuccess', () => {
+ it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => {
+ testAction(
+ receiveReposSuccess,
+ repos,
+ localState,
+ [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReposError', () => {
+ it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => {
+ testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done);
+ });
+ });
+
+ describe('fetchRepos', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.reposPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => {
+ const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] };
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload);
+
+ testAction(
+ fetchRepos,
+ null,
+ localState,
+ [],
+ [
+ { type: 'requestRepos' },
+ {
+ type: 'receiveReposSuccess',
+ payload: convertObjectPropsToCamelCase(payload, { deep: true }),
+ },
+ {
+ type: 'fetchJobs',
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+
+ testAction(
+ fetchRepos,
+ null,
+ localState,
+ [],
+ [{ type: 'requestRepos' }, { type: 'receiveReposError' }],
+ done,
+ );
+ });
+ });
+
+ describe('requestImport', () => {
+ it(`commits ${REQUEST_IMPORT} mutation`, done => {
+ testAction(
+ requestImport,
+ repoId,
+ localState,
+ [{ type: REQUEST_IMPORT, payload: repoId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveImportSuccess', () => {
+ it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => {
+ const payload = { importedProject: { name: 'imported/project' }, repoId: 2 };
+
+ testAction(
+ receiveImportSuccess,
+ payload,
+ localState,
+ [{ type: RECEIVE_IMPORT_SUCCESS, payload }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveImportError', () => {
+ it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => {
+ testAction(
+ receiveImportError,
+ repoId,
+ localState,
+ [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchImport', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.importPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => {
+ const importedProject = { name: 'imported/project' };
+ const importRepoId = importPayload.repo.id;
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject);
+
+ testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [],
+ [
+ { type: 'requestImport', payload: importRepoId },
+ {
+ type: 'receiveImportSuccess',
+ payload: {
+ importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }),
+ repoId: importRepoId,
+ },
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => {
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
+
+ testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [],
+ [
+ { type: 'requestImport', payload: importPayload.repo.id },
+ { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('receiveJobsSuccess', () => {
+ it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => {
+ testAction(
+ receiveJobsSuccess,
+ repos,
+ localState,
+ [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchJobs', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.jobsPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ stopJobsPolling();
+ clearJobsEtagPoll();
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => {
+ const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }];
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects);
+
+ testAction(
+ fetchJobs,
+ null,
+ localState,
+ [],
+ [
+ {
+ type: 'receiveJobsSuccess',
+ payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }),
+ },
+ ],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js
index e5e4a95f473..e5e4a95f473 100644
--- a/spec/javascripts/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_projects/store/getters_spec.js
diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js
new file mode 100644
index 00000000000..505545f7aa5
--- /dev/null
+++ b/spec/frontend/import_projects/store/mutations_spec.js
@@ -0,0 +1,34 @@
+import * as types from '~/import_projects/store/mutation_types';
+import mutations from '~/import_projects/store/mutations';
+
+describe('import_projects store mutations', () => {
+ describe(`${types.RECEIVE_IMPORT_SUCCESS}`, () => {
+ it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => {
+ const repoId = 1;
+ const state = {
+ reposBeingImported: [repoId],
+ providerRepos: [{ id: repoId }],
+ importedProjects: [],
+ };
+ const importedProject = { id: repoId };
+
+ mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId });
+
+ expect(state.reposBeingImported.includes(repoId)).toBe(false);
+ expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false);
+ expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true);
+ });
+ });
+
+ describe(`${types.RECEIVE_JOBS_SUCCESS}`, () => {
+ it('updates importStatus of existing importedProjects', () => {
+ const repoId = 1;
+ const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] };
+ const updatedProjects = [{ id: repoId, importStatus: 'finished' }];
+
+ mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects);
+
+ expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus);
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js
index 7bb8e26b81a..7bb8e26b81a 100644
--- a/spec/javascripts/issuable_suggestions/components/app_spec.js
+++ b/spec/frontend/issuable_suggestions/components/app_spec.js
diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js
index 7bd1fe678f4..7bd1fe678f4 100644
--- a/spec/javascripts/issuable_suggestions/components/item_spec.js
+++ b/spec/frontend/issuable_suggestions/components/item_spec.js
diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js
index 4f0f9ef8d62..4f0f9ef8d62 100644
--- a/spec/javascripts/issuable_suggestions/mock_data.js
+++ b/spec/frontend/issuable_suggestions/mock_data.js
diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/frontend/jobs/components/empty_state_spec.js
index a2df79bdda0..a2df79bdda0 100644
--- a/spec/javascripts/jobs/components/empty_state_spec.js
+++ b/spec/frontend/jobs/components/empty_state_spec.js
diff --git a/spec/javascripts/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/erased_block_spec.js
index 8e0433d3fb7..8e0433d3fb7 100644
--- a/spec/javascripts/jobs/components/erased_block_spec.js
+++ b/spec/frontend/jobs/components/erased_block_spec.js
diff --git a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
index 42d11266dad..42d11266dad 100644
--- a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js
+++ b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
diff --git a/spec/javascripts/jobs/components/stuck_block_spec.js b/spec/frontend/jobs/components/stuck_block_spec.js
index c320793b2be..c320793b2be 100644
--- a/spec/javascripts/jobs/components/stuck_block_spec.js
+++ b/spec/frontend/jobs/components/stuck_block_spec.js
diff --git a/spec/frontend/jobs/store/getters_spec.js b/spec/frontend/jobs/store/getters_spec.js
new file mode 100644
index 00000000000..379114c3737
--- /dev/null
+++ b/spec/frontend/jobs/store/getters_spec.js
@@ -0,0 +1,243 @@
+import * as getters from '~/jobs/store/getters';
+import state from '~/jobs/store/state';
+
+describe('Job Store Getters', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('headerTime', () => {
+ describe('when the job has started key', () => {
+ it('returns started key', () => {
+ const started = '2018-08-31T16:20:49.023Z';
+ localState.job.started = started;
+
+ expect(getters.headerTime(localState)).toEqual(started);
+ });
+ });
+
+ describe('when the job does not have started key', () => {
+ it('returns created_at key', () => {
+ const created = '2018-08-31T16:20:49.023Z';
+ localState.job.created_at = created;
+
+ expect(getters.headerTime(localState)).toEqual(created);
+ });
+ });
+ });
+
+ describe('shouldRenderCalloutMessage', () => {
+ describe('with status and callout message', () => {
+ it('returns true', () => {
+ localState.job.callout_message = 'Callout message';
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true);
+ });
+ });
+
+ describe('without status & with callout message', () => {
+ it('returns false', () => {
+ localState.job.callout_message = 'Callout message';
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+
+ describe('with status & without callout message', () => {
+ it('returns false', () => {
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('shouldRenderTriggeredLabel', () => {
+ describe('when started equals null', () => {
+ it('returns false', () => {
+ localState.job.started = null;
+
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false);
+ });
+ });
+
+ describe('when started equals string', () => {
+ it('returns true', () => {
+ localState.job.started = '2018-08-31T16:20:49.023Z';
+
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true);
+ });
+ });
+ });
+
+ describe('hasEnvironment', () => {
+ describe('without `deployment_status`', () => {
+ it('returns false', () => {
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+
+ describe('with an empty object for `deployment_status`', () => {
+ it('returns false', () => {
+ localState.job.deployment_status = {};
+
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+
+ describe('when `deployment_status` is defined and not empty', () => {
+ it('returns true', () => {
+ localState.job.deployment_status = {
+ status: 'creating',
+ environment: {
+ last_deployment: {},
+ },
+ };
+
+ expect(getters.hasEnvironment(localState)).toEqual(true);
+ });
+ });
+ });
+
+ describe('hasTrace', () => {
+ describe('when has_trace is true', () => {
+ it('returns true', () => {
+ localState.job.has_trace = true;
+ localState.job.status = {};
+
+ expect(getters.hasTrace(localState)).toEqual(true);
+ });
+ });
+
+ describe('when job is running', () => {
+ it('returns true', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'running' };
+
+ expect(getters.hasTrace(localState)).toEqual(true);
+ });
+ });
+
+ describe('when has_trace is false and job is not running', () => {
+ it('returns false', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'pending' };
+
+ expect(getters.hasTrace(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('emptyStateIllustration', () => {
+ describe('with defined illustration', () => {
+ it('returns the state illustration object', () => {
+ localState.job.status = {
+ illustration: {
+ path: 'foo',
+ },
+ };
+
+ expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' });
+ });
+ });
+
+ describe('when illustration is not defined', () => {
+ it('returns an empty object', () => {
+ expect(getters.emptyStateIllustration(localState)).toEqual({});
+ });
+ });
+ });
+
+ describe('shouldRenderSharedRunnerLimitWarning', () => {
+ describe('without runners information', () => {
+ it('returns false', () => {
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false);
+ });
+ });
+
+ describe('with runners information', () => {
+ describe('when used quota is less than limit', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ quota: {
+ used: 33,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false);
+ });
+ });
+
+ describe('when used quota is equal to limit', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ quota: {
+ used: 2000,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true);
+ });
+ });
+
+ describe('when used quota is bigger than limit', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ quota: {
+ used: 2002,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true);
+ });
+ });
+ });
+ });
+
+ describe('hasRunnersForProject', () => {
+ describe('with available and offline runners', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ available: true,
+ online: false,
+ };
+
+ expect(getters.hasRunnersForProject(localState)).toEqual(true);
+ });
+ });
+
+ describe('with non available runners', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ available: false,
+ online: false,
+ };
+
+ expect(getters.hasRunnersForProject(localState)).toEqual(false);
+ });
+ });
+
+ describe('with online runners', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ available: false,
+ online: true,
+ };
+
+ expect(getters.hasRunnersForProject(localState)).toEqual(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
new file mode 100644
index 00000000000..343301b8716
--- /dev/null
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -0,0 +1,194 @@
+import state from '~/jobs/store/state';
+import mutations from '~/jobs/store/mutations';
+import * as types from '~/jobs/store/mutation_types';
+
+describe('Jobs Store Mutations', () => {
+ let stateCopy;
+
+ const html =
+ 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('SET_JOB_ENDPOINT', () => {
+ it('should set jobEndpoint', () => {
+ mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json');
+
+ expect(stateCopy.jobEndpoint).toEqual('job/21312321.json');
+ });
+ });
+
+ describe('HIDE_SIDEBAR', () => {
+ it('should set isSidebarOpen to false', () => {
+ mutations[types.HIDE_SIDEBAR](stateCopy);
+
+ expect(stateCopy.isSidebarOpen).toEqual(false);
+ });
+ });
+
+ describe('SHOW_SIDEBAR', () => {
+ it('should set isSidebarOpen to true', () => {
+ mutations[types.SHOW_SIDEBAR](stateCopy);
+
+ expect(stateCopy.isSidebarOpen).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_TRACE_SUCCESS', () => {
+ describe('when trace has state', () => {
+ it('sets traceState', () => {
+ const stateLog =
+ 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0=';
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
+ state: stateLog,
+ });
+
+ expect(stateCopy.traceState).toEqual(stateLog);
+ });
+ });
+
+ describe('when traceSize is smaller than the total size', () => {
+ it('sets isTraceSizeVisible to true', () => {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 });
+
+ expect(stateCopy.isTraceSizeVisible).toEqual(true);
+ });
+ });
+
+ describe('when traceSize is bigger than the total size', () => {
+ it('sets isTraceSizeVisible to false', () => {
+ const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 });
+
+ mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 });
+
+ expect(copy.isTraceSizeVisible).toEqual(false);
+ });
+ });
+
+ it('sets trace, trace size and isTraceComplete', () => {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
+ append: true,
+ html,
+ size: 511846,
+ complete: true,
+ });
+
+ expect(stateCopy.trace).toEqual(html);
+ expect(stateCopy.traceSize).toEqual(511846);
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ });
+ });
+
+ describe('STOP_POLLING_TRACE', () => {
+ it('sets isTraceComplete to true', () => {
+ mutations[types.STOP_POLLING_TRACE](stateCopy);
+
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_TRACE_ERROR', () => {
+ it('resets trace state and sets error to true', () => {
+ mutations[types.RECEIVE_TRACE_ERROR](stateCopy);
+
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ });
+ });
+
+ describe('REQUEST_JOB', () => {
+ it('sets isLoading to true', () => {
+ mutations[types.REQUEST_JOB](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_JOB_SUCCESS', () => {
+ it('sets is loading to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.hasError).toEqual(false);
+ });
+
+ it('sets job data', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.job).toEqual({ id: 1312321 });
+ });
+
+ it('sets selectedStage when the selectedStage is empty', () => {
+ expect(stateCopy.selectedStage).toEqual('');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ });
+
+ it('does not set selectedStage when the selectedStage is not More', () => {
+ stateCopy.selectedStage = 'notify';
+
+ expect(stateCopy.selectedStage).toEqual('notify');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('notify');
+ });
+ });
+
+ describe('RECEIVE_JOB_ERROR', () => {
+ it('resets job data', () => {
+ mutations[types.RECEIVE_JOB_ERROR](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(false);
+ expect(stateCopy.job).toEqual({});
+ });
+ });
+
+ describe('REQUEST_JOBS_FOR_STAGE', () => {
+ it('sets isLoadingJobs to true', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
+
+ expect(stateCopy.isLoadingJobs).toEqual(true);
+ });
+
+ it('sets selectedStage', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('sets jobs', () => {
+ expect(stateCopy.jobs).toEqual([{ name: 'karma' }]);
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('resets jobs', () => {
+ expect(stateCopy.jobs).toEqual([]);
+ });
+ });
+});
diff --git a/spec/frontend/labels_select_spec.js b/spec/frontend/labels_select_spec.js
new file mode 100644
index 00000000000..d54e0eab845
--- /dev/null
+++ b/spec/frontend/labels_select_spec.js
@@ -0,0 +1,116 @@
+import $ from 'jquery';
+import LabelsSelect from '~/labels_select';
+
+const mockUrl = '/foo/bar/url';
+
+const mockLabels = [
+ {
+ id: 26,
+ title: 'Foo Label',
+ description: 'Foobar',
+ color: '#BADA55',
+ text_color: '#FFFFFF',
+ },
+];
+
+const mockScopedLabels = [
+ {
+ id: 27,
+ title: 'Foo::Bar',
+ description: 'Foobar',
+ color: '#333ABC',
+ text_color: '#FFFFFF',
+ },
+];
+
+describe('LabelsSelect', () => {
+ describe('getLabelTemplate', () => {
+ describe('when normal label is present', () => {
+ const label = mockLabels[0];
+ let $labelEl;
+
+ beforeEach(() => {
+ $labelEl = $(
+ LabelsSelect.getLabelTemplate({
+ labels: mockLabels,
+ issueUpdateURL: mockUrl,
+ enableScopedLabels: true,
+ scopedLabelsDocumentationLink: 'docs-link',
+ }),
+ );
+ });
+
+ it('generated label item template has correct label URL', () => {
+ expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label');
+ });
+
+ it('generated label item template has correct label title', () => {
+ expect($labelEl.find('span.label').text()).toBe(label.title);
+ });
+
+ it('generated label item template has label description as title attribute', () => {
+ expect($labelEl.find('span.label').attr('title')).toBe(label.description);
+ });
+
+ it('generated label item template has correct label styles', () => {
+ expect($labelEl.find('span.label').attr('style')).toBe(
+ `background-color: ${label.color}; color: ${label.text_color};`,
+ );
+ });
+
+ it('generated label item has a badge class', () => {
+ expect($labelEl.find('span').hasClass('badge')).toEqual(true);
+ });
+
+ it('generated label item template does not have scoped-label class', () => {
+ expect($labelEl.find('.scoped-label')).toHaveLength(0);
+ });
+ });
+
+ describe('when scoped label is present', () => {
+ const label = mockScopedLabels[0];
+ let $labelEl;
+
+ beforeEach(() => {
+ $labelEl = $(
+ LabelsSelect.getLabelTemplate({
+ labels: mockScopedLabels,
+ issueUpdateURL: mockUrl,
+ enableScopedLabels: true,
+ scopedLabelsDocumentationLink: 'docs-link',
+ }),
+ );
+ });
+
+ it('generated label item template has correct label URL', () => {
+ expect($labelEl.find('a').attr('href')).toBe('/foo/bar?label_name[]=Foo%3A%3ABar');
+ });
+
+ it('generated label item template has correct label title', () => {
+ expect($labelEl.find('span.label').text()).toBe(label.title);
+ });
+
+ it('generated label item template has html flag as true', () => {
+ expect($labelEl.find('span.label').attr('data-html')).toBe('true');
+ });
+
+ it('generated label item template has question icon', () => {
+ expect($labelEl.find('i.fa-question-circle')).toHaveLength(1);
+ });
+
+ it('generated label item template has scoped-label class', () => {
+ expect($labelEl.find('.scoped-label')).toHaveLength(1);
+ });
+
+ it('generated label item template has correct label styles', () => {
+ expect($labelEl.find('span.label').attr('style')).toBe(
+ `background-color: ${label.color}; color: ${label.text_color};`,
+ );
+ });
+
+ it('generated label item has a badge class', () => {
+ expect($labelEl.find('span').hasClass('badge')).toEqual(true);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/autosave_spec.js b/spec/frontend/lib/utils/autosave_spec.js
new file mode 100644
index 00000000000..12e97f6cdec
--- /dev/null
+++ b/spec/frontend/lib/utils/autosave_spec.js
@@ -0,0 +1,64 @@
+import { clearDraft, getDraft, updateDraft } from '~/lib/utils/autosave';
+
+describe('autosave utils', () => {
+ const autosaveKey = 'dummy-autosave-key';
+ const text = 'some dummy text';
+
+ describe('clearDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('removes the draft from localStorage', () => {
+ clearDraft(autosaveKey);
+
+ expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(null);
+ });
+ });
+
+ describe('getDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('returns the draft from localStorage', () => {
+ const result = getDraft(autosaveKey);
+
+ expect(result).toBe(text);
+ });
+
+ it('returns null if no entry exists in localStorage', () => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+
+ const result = getDraft(autosaveKey);
+
+ expect(result).toBe(null);
+ });
+ });
+
+ describe('updateDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('removes the draft from localStorage', () => {
+ const newText = 'new text';
+
+ updateDraft(autosaveKey, newText);
+
+ expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(newText);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/cache_spec.js b/spec/frontend/lib/utils/cache_spec.js
index 2fe02a7592c..2fe02a7592c 100644
--- a/spec/javascripts/lib/utils/cache_spec.js
+++ b/spec/frontend/lib/utils/cache_spec.js
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
new file mode 100644
index 00000000000..9f49e68cfe8
--- /dev/null
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -0,0 +1,436 @@
+import * as datetimeUtility from '~/lib/utils/datetime_utility';
+
+describe('Date time utils', () => {
+ describe('timeFor', () => {
+ it('returns `past due` when in past', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 1);
+
+ expect(datetimeUtility.timeFor(date)).toBe('Past due');
+ });
+
+ it('returns remaining time when in the future', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+
+ // Add a day to prevent a transient error. If date is even 1 second
+ // short of a full year, timeFor will return '11 months remaining'
+ date.setDate(date.getDate() + 1);
+
+ expect(datetimeUtility.timeFor(date)).toBe('1 year remaining');
+ });
+ });
+
+ describe('get day name', () => {
+ it('should return Sunday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/17/2016'));
+
+ expect(day).toBe('Sunday');
+ });
+
+ it('should return Monday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/18/2016'));
+
+ expect(day).toBe('Monday');
+ });
+
+ it('should return Tuesday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/19/2016'));
+
+ expect(day).toBe('Tuesday');
+ });
+
+ it('should return Wednesday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/20/2016'));
+
+ expect(day).toBe('Wednesday');
+ });
+
+ it('should return Thursday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/21/2016'));
+
+ expect(day).toBe('Thursday');
+ });
+
+ it('should return Friday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/22/2016'));
+
+ expect(day).toBe('Friday');
+ });
+
+ it('should return Saturday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/23/2016'));
+
+ expect(day).toBe('Saturday');
+ });
+ });
+
+ describe('formatDate', () => {
+ it('should format date properly', () => {
+ const formattedDate = datetimeUtility.formatDate(new Date('07/23/2016'));
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should format ISO date properly', () => {
+ const formattedDate = datetimeUtility.formatDate('2016-07-23T00:00:00.559Z');
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should throw an error if date is invalid', () => {
+ expect(() => {
+ datetimeUtility.formatDate('2016-07-23 00:00:00 UTC');
+ }).toThrow(new Error('Invalid date'));
+ });
+ });
+
+ describe('get day difference', () => {
+ it('should return 7', () => {
+ const firstDay = new Date('07/01/2016');
+ const secondDay = new Date('07/08/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+
+ expect(difference).toBe(7);
+ });
+
+ it('should return 31', () => {
+ const firstDay = new Date('07/01/2016');
+ const secondDay = new Date('08/01/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+
+ expect(difference).toBe(31);
+ });
+
+ it('should return 365', () => {
+ const firstDay = new Date('07/02/2015');
+ const secondDay = new Date('07/01/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+
+ expect(difference).toBe(365);
+ });
+ });
+});
+
+describe('timeIntervalInWords', () => {
+ it('should return string with number of minutes and seconds', () => {
+ expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
+ expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
+ expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
+ expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
+ });
+});
+
+describe('dateInWords', () => {
+ const date = new Date('07/01/2016');
+
+ it('should return date in words', () => {
+ expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016');
+ });
+
+ it('should return abbreviated month name', () => {
+ expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
+ });
+
+ it('should return date in words without year', () => {
+ expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1');
+ });
+});
+
+describe('monthInWords', () => {
+ const date = new Date('2017-01-20');
+
+ it('returns month name from provided date', () => {
+ expect(datetimeUtility.monthInWords(date)).toBe('January');
+ });
+
+ it('returns abbreviated month name from provided date', () => {
+ expect(datetimeUtility.monthInWords(date, true)).toBe('Jan');
+ });
+});
+
+describe('totalDaysInMonth', () => {
+ it('returns number of days in a month for given date', () => {
+ // 1st Feb, 2016 (leap year)
+ expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29);
+
+ // 1st Feb, 2017
+ expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28);
+
+ // 1st Jan, 2017
+ expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31);
+ });
+});
+
+describe('getSundays', () => {
+ it('returns array of dates representing all Sundays of the month', () => {
+ // December, 2017 (it has 5 Sundays)
+ const dateOfSundays = [3, 10, 17, 24, 31];
+ const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1));
+
+ expect(sundays.length).toBe(5);
+ sundays.forEach((sunday, index) => {
+ expect(sunday.getDate()).toBe(dateOfSundays[index]);
+ });
+ });
+});
+
+describe('getTimeframeWindowFrom', () => {
+ it('returns array of date objects upto provided length (positive number) into the future starting from provided startDate', () => {
+ const startDate = new Date(2018, 0, 1);
+ const mockTimeframe = [
+ new Date(2018, 0, 1),
+ new Date(2018, 1, 1),
+ new Date(2018, 2, 1),
+ new Date(2018, 3, 1),
+ new Date(2018, 4, 31),
+ ];
+ const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5);
+
+ expect(timeframe.length).toBe(5);
+ timeframe.forEach((timeframeItem, index) => {
+ expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
+ expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
+ expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
+ });
+ });
+
+ it('returns array of date objects upto provided length (negative number) into the past starting from provided startDate', () => {
+ const startDate = new Date(2018, 0, 1);
+ const mockTimeframe = [
+ new Date(2018, 0, 1),
+ new Date(2017, 11, 1),
+ new Date(2017, 10, 1),
+ new Date(2017, 9, 1),
+ new Date(2017, 8, 1),
+ ];
+ const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, -5);
+
+ expect(timeframe.length).toBe(5);
+ timeframe.forEach((timeframeItem, index) => {
+ expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
+ expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
+ expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
+ });
+ });
+});
+
+describe('formatTime', () => {
+ const expectedTimestamps = [
+ [0, '00:00:00'],
+ [1000, '00:00:01'],
+ [42000, '00:00:42'],
+ [121000, '00:02:01'],
+ [10921000, '03:02:01'],
+ [108000000, '30:00:00'],
+ ];
+
+ expectedTimestamps.forEach(([milliseconds, expectedTimestamp]) => {
+ it(`formats ${milliseconds}ms as ${expectedTimestamp}`, () => {
+ expect(datetimeUtility.formatTime(milliseconds)).toBe(expectedTimestamp);
+ });
+ });
+});
+
+describe('datefix', () => {
+ describe('pad', () => {
+ it('should add a 0 when length is smaller than 2', () => {
+ expect(datetimeUtility.pad(2)).toEqual('02');
+ });
+
+ it('should not add a zero when length matches the default', () => {
+ expect(datetimeUtility.pad(12)).toEqual('12');
+ });
+
+ it('should add a 0 when length is smaller than the provided', () => {
+ expect(datetimeUtility.pad(12, 3)).toEqual('012');
+ });
+ });
+
+ describe('parsePikadayDate', () => {
+ // removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834
+ });
+
+ describe('pikadayToString', () => {
+ it('should format a UTC date into yyyy-mm-dd format', () => {
+ expect(datetimeUtility.pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
+ });
+ });
+});
+
+describe('prettyTime methods', () => {
+ const assertTimeUnits = (obj, minutes, hours, days, weeks) => {
+ expect(obj.minutes).toBe(minutes);
+ expect(obj.hours).toBe(hours);
+ expect(obj.days).toBe(days);
+ expect(obj.weeks).toBe(weeks);
+ };
+
+ describe('parseSeconds', () => {
+ it('should correctly parse a negative value', () => {
+ const zeroSeconds = datetimeUtility.parseSeconds(-1000);
+
+ assertTimeUnits(zeroSeconds, 16, 0, 0, 0);
+ });
+
+ it('should correctly parse a zero value', () => {
+ const zeroSeconds = datetimeUtility.parseSeconds(0);
+
+ assertTimeUnits(zeroSeconds, 0, 0, 0, 0);
+ });
+
+ it('should correctly parse a small non-zero second values', () => {
+ const subOneMinute = datetimeUtility.parseSeconds(10);
+ const aboveOneMinute = datetimeUtility.parseSeconds(100);
+ const manyMinutes = datetimeUtility.parseSeconds(1000);
+
+ assertTimeUnits(subOneMinute, 0, 0, 0, 0);
+ assertTimeUnits(aboveOneMinute, 1, 0, 0, 0);
+ assertTimeUnits(manyMinutes, 16, 0, 0, 0);
+ });
+
+ it('should correctly parse large second values', () => {
+ const aboveOneHour = datetimeUtility.parseSeconds(4800);
+ const aboveOneDay = datetimeUtility.parseSeconds(110000);
+ const aboveOneWeek = datetimeUtility.parseSeconds(25000000);
+
+ assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
+ assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
+ assertTimeUnits(aboveOneWeek, 26, 0, 3, 173);
+ });
+
+ it('should correctly accept a custom param for hoursPerDay', () => {
+ const config = { hoursPerDay: 24 };
+
+ const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
+ const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
+ const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
+
+ assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
+ assertTimeUnits(aboveOneDay, 33, 6, 1, 0);
+ assertTimeUnits(aboveOneWeek, 26, 8, 4, 57);
+ });
+
+ it('should correctly accept a custom param for daysPerWeek', () => {
+ const config = { daysPerWeek: 7 };
+
+ const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
+ const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
+ const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
+
+ assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
+ assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
+ assertTimeUnits(aboveOneWeek, 26, 0, 0, 124);
+ });
+
+ it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => {
+ const config = { daysPerWeek: 55, hoursPerDay: 14 };
+
+ const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
+ const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
+ const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
+
+ assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
+ assertTimeUnits(aboveOneDay, 33, 2, 2, 0);
+ assertTimeUnits(aboveOneWeek, 26, 0, 1, 9);
+ });
+ });
+
+ describe('stringifyTime', () => {
+ it('should stringify values with all non-zero units', () => {
+ const timeObject = {
+ weeks: 1,
+ days: 4,
+ hours: 7,
+ minutes: 20,
+ };
+
+ const timeString = datetimeUtility.stringifyTime(timeObject);
+
+ expect(timeString).toBe('1w 4d 7h 20m');
+ });
+
+ it('should stringify values with some non-zero units', () => {
+ const timeObject = {
+ weeks: 0,
+ days: 4,
+ hours: 0,
+ minutes: 20,
+ };
+
+ const timeString = datetimeUtility.stringifyTime(timeObject);
+
+ expect(timeString).toBe('4d 20m');
+ });
+
+ it('should stringify values with no non-zero units', () => {
+ const timeObject = {
+ weeks: 0,
+ days: 0,
+ hours: 0,
+ minutes: 0,
+ };
+
+ const timeString = datetimeUtility.stringifyTime(timeObject);
+
+ expect(timeString).toBe('0m');
+ });
+
+ it('should return non-condensed representation of time object', () => {
+ const timeObject = { weeks: 1, days: 0, hours: 1, minutes: 0 };
+
+ expect(datetimeUtility.stringifyTime(timeObject, true)).toEqual('1 week 1 hour');
+ });
+ });
+
+ describe('abbreviateTime', () => {
+ it('should abbreviate stringified times for weeks', () => {
+ const fullTimeString = '1w 3d 4h 5m';
+
+ expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w');
+ });
+
+ it('should abbreviate stringified times for non-weeks', () => {
+ const fullTimeString = '0w 3d 4h 5m';
+
+ expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d');
+ });
+ });
+});
+
+describe('calculateRemainingMilliseconds', () => {
+ beforeEach(() => {
+ jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
+ });
+
+ it('calculates the remaining time for a given end date', () => {
+ const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-04T01:44:03Z');
+
+ expect(milliseconds).toBe(3723000);
+ });
+
+ it('returns 0 if the end date has passed', () => {
+ const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-03T00:00:00Z');
+
+ expect(milliseconds).toBe(0);
+ });
+});
+
+describe('newDate', () => {
+ it('returns new date instance from existing date instance', () => {
+ const initialDate = new Date(2019, 0, 1);
+ const copiedDate = datetimeUtility.newDate(initialDate);
+
+ expect(copiedDate.getTime()).toBe(initialDate.getTime());
+
+ initialDate.setMonth(initialDate.getMonth() + 1);
+
+ expect(copiedDate.getTime()).not.toBe(initialDate.getTime());
+ });
+
+ it('returns date instance when provided date param is not of type date or is undefined', () => {
+ const initialDate = datetimeUtility.newDate();
+
+ expect(initialDate instanceof Date).toBe(true);
+ });
+});
diff --git a/spec/javascripts/lib/utils/grammar_spec.js b/spec/frontend/lib/utils/grammar_spec.js
index 377b2ffb48c..377b2ffb48c 100644
--- a/spec/javascripts/lib/utils/grammar_spec.js
+++ b/spec/frontend/lib/utils/grammar_spec.js
diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/frontend/lib/utils/image_utility_spec.js
index a7eff419fba..a7eff419fba 100644
--- a/spec/javascripts/lib/utils/image_utility_spec.js
+++ b/spec/frontend/lib/utils/image_utility_spec.js
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
new file mode 100644
index 00000000000..77d7478d317
--- /dev/null
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -0,0 +1,112 @@
+import {
+ formatRelevantDigits,
+ bytesToKiB,
+ bytesToMiB,
+ bytesToGiB,
+ numberToHumanSize,
+ sum,
+ isOdd,
+} from '~/lib/utils/number_utils';
+
+describe('Number Utils', () => {
+ describe('formatRelevantDigits', () => {
+ it('returns an empty string when the number is NaN', () => {
+ expect(formatRelevantDigits('fail')).toBe('');
+ });
+
+ it('returns 4 decimals when there is 4 plus digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('1000.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+
+ expect(rightFromDecimal.length).toBe(4);
+ expect(leftFromDecimal.length).toBe(4);
+ });
+
+ it('returns 3 decimals when there is 1 digit to the left', () => {
+ const formattedNumber = formatRelevantDigits('0.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+
+ expect(rightFromDecimal.length).toBe(3);
+ expect(leftFromDecimal.length).toBe(1);
+ });
+
+ it('returns 2 decimals when there is 2 digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('10.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+
+ expect(rightFromDecimal.length).toBe(2);
+ expect(leftFromDecimal.length).toBe(2);
+ });
+
+ it('returns 1 decimal when there is 3 digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('100.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+
+ expect(rightFromDecimal.length).toBe(1);
+ expect(leftFromDecimal.length).toBe(3);
+ });
+ });
+
+ describe('bytesToKiB', () => {
+ it('calculates KiB for the given bytes', () => {
+ expect(bytesToKiB(1024)).toEqual(1);
+ expect(bytesToKiB(1000)).toEqual(0.9765625);
+ });
+ });
+
+ describe('bytesToMiB', () => {
+ it('calculates MiB for the given bytes', () => {
+ expect(bytesToMiB(1048576)).toEqual(1);
+ expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
+ });
+ });
+
+ describe('bytesToGiB', () => {
+ it('calculates GiB for the given bytes', () => {
+ expect(bytesToGiB(1073741824)).toEqual(1);
+ expect(bytesToGiB(10737418240)).toEqual(10);
+ });
+ });
+
+ describe('numberToHumanSize', () => {
+ it('should return bytes', () => {
+ expect(numberToHumanSize(654)).toEqual('654 bytes');
+ });
+
+ it('should return KiB', () => {
+ expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
+ });
+
+ it('should return MiB', () => {
+ expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
+ });
+
+ it('should return GiB', () => {
+ expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
+ });
+ });
+
+ describe('sum', () => {
+ it('should add up two values', () => {
+ expect(sum(1, 2)).toEqual(3);
+ });
+
+ it('should add up all the values in an array when passed to a reducer', () => {
+ expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15);
+ });
+ });
+
+ describe('isOdd', () => {
+ it('should return 0 with a even number', () => {
+ expect(isOdd(2)).toEqual(0);
+ });
+
+ it('should return 1 with a odd number', () => {
+ expect(isOdd(1)).toEqual(1);
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
new file mode 100644
index 00000000000..9e920d59093
--- /dev/null
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -0,0 +1,179 @@
+import * as textUtils from '~/lib/utils/text_utility';
+
+describe('text_utility', () => {
+ describe('addDelimiter', () => {
+ it('should add a delimiter to the given string', () => {
+ expect(textUtils.addDelimiter('1234')).toEqual('1,234');
+ expect(textUtils.addDelimiter('222222')).toEqual('222,222');
+ });
+
+ it('should not add a delimiter if string contains no numbers', () => {
+ expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa');
+ });
+ });
+
+ describe('highCountTrim', () => {
+ it('returns 99+ for count >= 100', () => {
+ expect(textUtils.highCountTrim(105)).toBe('99+');
+ expect(textUtils.highCountTrim(100)).toBe('99+');
+ });
+
+ it('returns exact number for count < 100', () => {
+ expect(textUtils.highCountTrim(45)).toBe(45);
+ });
+ });
+
+ describe('humanize', () => {
+ it('should remove underscores and uppercase the first letter', () => {
+ expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
+ });
+ });
+
+ describe('pluralize', () => {
+ it('should pluralize given string', () => {
+ expect(textUtils.pluralize('test', 2)).toBe('tests');
+ });
+
+ it('should pluralize when count is 0', () => {
+ expect(textUtils.pluralize('test', 0)).toBe('tests');
+ });
+
+ it('should not pluralize when count is 1', () => {
+ expect(textUtils.pluralize('test', 1)).toBe('test');
+ });
+ });
+
+ describe('dasherize', () => {
+ it('should replace underscores with dashes', () => {
+ expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
+ });
+ });
+
+ describe('capitalizeFirstCharacter', () => {
+ it('returns string with first letter capitalized', () => {
+ expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
+ });
+ });
+
+ describe('slugifyWithHyphens', () => {
+ it('should replaces whitespaces with hyphens and convert to lower case', () => {
+ expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
+ });
+ });
+
+ describe('stripHtml', () => {
+ it('replaces html tag with the default replacement', () => {
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
+ 'This is a text with html.',
+ );
+ });
+
+ it('replaces html tags with the provided replacement', () => {
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual(
+ 'This is a text with html .',
+ );
+ });
+
+ it('passes through with null string input', () => {
+ expect(textUtils.stripHtml(null, ' ')).toEqual(null);
+ });
+
+ it('passes through with undefined string input', () => {
+ expect(textUtils.stripHtml(undefined, ' ')).toEqual(undefined);
+ });
+ });
+
+ describe('convertToCamelCase', () => {
+ it('converts snake_case string to camelCase string', () => {
+ expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
+ });
+ });
+
+ describe('convertToSentenceCase', () => {
+ it('converts Sentence Case to Sentence case', () => {
+ expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world');
+ });
+ });
+
+ describe('truncateSha', () => {
+ it('shortens SHAs to 8 characters', () => {
+ expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
+ });
+
+ it('leaves short SHAs as is', () => {
+ expect(textUtils.truncateSha('shortsha')).toBe('shortsha');
+ });
+ });
+
+ describe('splitCamelCase', () => {
+ it('separates a PascalCase word to two', () => {
+ expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World');
+ });
+ });
+
+ describe('getFirstCharacterCapitalized', () => {
+ it('returns the first character capitalized, if first character is alphabetic', () => {
+ expect(textUtils.getFirstCharacterCapitalized('loremIpsumDolar')).toEqual('L');
+ expect(textUtils.getFirstCharacterCapitalized('Sit amit !')).toEqual('S');
+ });
+
+ it('returns the first character, if first character is non-alphabetic', () => {
+ expect(textUtils.getFirstCharacterCapitalized(' lorem')).toEqual(' ');
+ expect(textUtils.getFirstCharacterCapitalized('%#!')).toEqual('%');
+ });
+
+ it('returns an empty string, if string is falsey', () => {
+ expect(textUtils.getFirstCharacterCapitalized('')).toEqual('');
+ expect(textUtils.getFirstCharacterCapitalized(null)).toEqual('');
+ });
+ });
+
+ describe('truncatePathMiddleToLength', () => {
+ it('does not truncate text', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test');
+ });
+
+ it('truncates middle of the path', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff');
+ });
+
+ it('truncates multiple times in the middle of the path', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual(
+ 'app/…/…/diff',
+ );
+ });
+ });
+
+ describe('slugifyWithUnderscore', () => {
+ it('should replaces whitespaces with underscore and convert to lower case', () => {
+ expect(textUtils.slugifyWithUnderscore('My Input String')).toEqual('my_input_string');
+ });
+ });
+
+ describe('truncateNamespace', () => {
+ it(`should return the root namespace if the namespace only includes one level`, () => {
+ expect(textUtils.truncateNamespace('a / b')).toBe('a');
+ });
+
+ it(`should return the first 2 namespaces if the namespace includes exactly 2 levels`, () => {
+ expect(textUtils.truncateNamespace('a / b / c')).toBe('a / b');
+ });
+
+ it(`should return the first and last namespaces, separated by "...", if the namespace includes more than 2 levels`, () => {
+ expect(textUtils.truncateNamespace('a / b / c / d')).toBe('a / ... / c');
+ expect(textUtils.truncateNamespace('a / b / c / d / e / f / g / h / i')).toBe('a / ... / h');
+ });
+
+ it(`should return an empty string for invalid inputs`, () => {
+ [undefined, null, 4, {}, true, new Date()].forEach(input => {
+ expect(textUtils.truncateNamespace(input)).toBe('');
+ });
+ });
+
+ it(`should not alter strings that aren't formatted as namespaces`, () => {
+ ['', ' ', '\t', 'a', 'a \\ b'].forEach(input => {
+ expect(textUtils.truncateNamespace(input)).toBe(input);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
new file mode 100644
index 00000000000..eca240c9c18
--- /dev/null
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -0,0 +1,194 @@
+import * as urlUtils from '~/lib/utils/url_utility';
+
+describe('URL utility', () => {
+ describe('webIDEUrl', () => {
+ afterEach(() => {
+ gon.relative_url_root = '';
+ });
+
+ describe('without relative_url_root', () => {
+ it('returns IDE path with route', () => {
+ expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ '/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
+ );
+ });
+ });
+
+ describe('with relative_url_root', () => {
+ beforeEach(() => {
+ gon.relative_url_root = '/gitlab';
+ });
+
+ it('returns IDE path with route', () => {
+ expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ '/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
+ );
+ });
+ });
+ });
+
+ describe('mergeUrlParams', () => {
+ it('adds w', () => {
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe(
+ 'https://host/path?w=1#frag',
+ );
+
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe(
+ 'https://h/p?k1=v1&w=1#frag',
+ );
+ });
+
+ it('updates w', () => {
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
+ });
+
+ it('adds multiple params', () => {
+ expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
+ });
+
+ it('adds and updates encoded params', () => {
+ expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
+ });
+ });
+
+ describe('removeParams', () => {
+ describe('when url is passed', () => {
+ it('removes query param with encoded ampersand', () => {
+ const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome');
+
+ expect(url).toBe('/mail');
+ });
+
+ it('should remove param when url has no other params', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?size=5');
+
+ expect(url).toBe('/feature/home');
+ });
+
+ it('should remove param when url has other params', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html');
+
+ expect(url).toBe('/feature/home?q=1&f=html');
+ });
+
+ it('should remove param and preserve fragment', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2');
+
+ expect(url).toBe('/feature/home#H2');
+ });
+
+ it('should remove multiple params', () => {
+ const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2');
+
+ expect(url).toBe('/home?l=en_US#H2');
+ });
+ });
+ });
+
+ describe('setUrlFragment', () => {
+ it('should set fragment when url has no fragment', () => {
+ const url = urlUtils.setUrlFragment('/home/feature', 'usage');
+
+ expect(url).toBe('/home/feature#usage');
+ });
+
+ it('should set fragment when url has existing fragment', () => {
+ const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage');
+
+ expect(url).toBe('/home/feature#usage');
+ });
+
+ it('should set fragment when given fragment includes #', () => {
+ const url = urlUtils.setUrlFragment('/home/feature#overview', '#install');
+
+ expect(url).toBe('/home/feature#install');
+ });
+ });
+
+ describe('getBaseURL', () => {
+ beforeEach(() => {
+ global.window = Object.create(window);
+ Object.defineProperty(window, 'location', {
+ value: {
+ host: 'gitlab.com',
+ protocol: 'https:',
+ },
+ });
+ });
+
+ it('returns correct base URL', () => {
+ expect(urlUtils.getBaseURL()).toBe('https://gitlab.com');
+ });
+ });
+
+ describe('isAbsoluteOrRootRelative', () => {
+ const validUrls = ['https://gitlab.com/', 'http://gitlab.com/', '/users/sign_in'];
+
+ const invalidUrls = [' https://gitlab.com/', './file/path', 'notanurl', '<a></a>'];
+
+ it.each(validUrls)(`returns true for %s`, url => {
+ expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(true);
+ });
+
+ it.each(invalidUrls)(`returns false for %s`, url => {
+ expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(false);
+ });
+ });
+
+ describe('isSafeUrl', () => {
+ const absoluteUrls = [
+ 'http://example.org',
+ 'http://example.org:8080',
+ 'https://example.org',
+ 'https://example.org:8080',
+ 'https://192.168.1.1',
+ ];
+
+ const rootRelativeUrls = ['/relative/link'];
+
+ const relativeUrls = ['./relative/link', '../relative/link'];
+
+ const urlsWithoutHost = ['http://', 'https://', 'https:https:https:'];
+
+ /* eslint-disable no-script-url */
+ const nonHttpUrls = [
+ 'javascript:',
+ 'javascript:alert("XSS")',
+ 'jav\tascript:alert("XSS");',
+ ' &#14; javascript:alert("XSS");',
+ 'ftp://192.168.1.1',
+ 'file:///',
+ 'file:///etc/hosts',
+ ];
+ /* eslint-enable no-script-url */
+
+ // javascript:alert('XSS')
+ const encodedJavaScriptUrls = [
+ '&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041',
+ '&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;',
+ '&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29',
+ '\\u006A\\u0061\\u0076\\u0061\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003A\\u0061\\u006C\\u0065\\u0072\\u0074\\u0028\\u0027\\u0058\\u0053\\u0053\\u0027\\u0029',
+ ];
+
+ const safeUrls = [...absoluteUrls, ...rootRelativeUrls];
+ const unsafeUrls = [
+ ...relativeUrls,
+ ...urlsWithoutHost,
+ ...nonHttpUrls,
+ ...encodedJavaScriptUrls,
+ ];
+
+ describe('with URL constructor support', () => {
+ it.each(safeUrls)('returns true for %s', url => {
+ expect(urlUtils.isSafeURL(url)).toBe(true);
+ });
+
+ it.each(unsafeUrls)('returns false for %s', url => {
+ expect(urlUtils.isSafeURL(url)).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/locale/ensure_single_line_spec.js b/spec/frontend/locale/ensure_single_line_spec.js
index 20b04cab9c8..20b04cab9c8 100644
--- a/spec/javascripts/locale/ensure_single_line_spec.js
+++ b/spec/frontend/locale/ensure_single_line_spec.js
diff --git a/spec/javascripts/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js
index 52e903b819f..52e903b819f 100644
--- a/spec/javascripts/locale/sprintf_spec.js
+++ b/spec/frontend/locale/sprintf_spec.js
diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
new file mode 100644
index 00000000000..a2a7d0ee91e
--- /dev/null
+++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MR Popover loaded state matches the snapshot 1`] = `
+<glpopover-stub
+ boundary="viewport"
+ cssclasses=""
+ placement="top"
+ show=""
+ target=""
+>
+ <div
+ class="mr-popover"
+ >
+ <div
+ class="d-flex align-items-center justify-content-between"
+ >
+ <div
+ class="d-inline-flex align-items-center"
+ >
+ <div
+ class="issuable-status-box status-box status-box-open"
+ >
+
+ Open
+
+ </div>
+
+ <span
+ class="text-secondary"
+ >
+ Opened
+ <time>
+ just now
+ </time>
+ </span>
+ </div>
+
+ <ciicon-stub
+ cssclasses=""
+ size="16"
+ status="[object Object]"
+ />
+ </div>
+
+ <h5
+ class="my-2"
+ >
+ MR Title
+ </h5>
+
+ <div
+ class="text-secondary"
+ >
+
+ foo/bar!1
+
+ </div>
+ </div>
+</glpopover-stub>
+`;
+
+exports[`MR Popover shows skeleton-loader while apollo is loading 1`] = `
+<glpopover-stub
+ boundary="viewport"
+ cssclasses=""
+ placement="top"
+ show=""
+ target=""
+>
+ <div
+ class="mr-popover"
+ >
+ <div>
+ <glskeletonloading-stub
+ class="animation-container-small mt-1"
+ lines="1"
+ />
+ </div>
+
+ <h5
+ class="my-2"
+ >
+ MR Title
+ </h5>
+
+ <div
+ class="text-secondary"
+ >
+
+ foo/bar!1
+
+ </div>
+ </div>
+</glpopover-stub>
+`;
diff --git a/spec/frontend/mr_popover/index_spec.js b/spec/frontend/mr_popover/index_spec.js
new file mode 100644
index 00000000000..b9db2342687
--- /dev/null
+++ b/spec/frontend/mr_popover/index_spec.js
@@ -0,0 +1,46 @@
+import * as createDefaultClient from '~/lib/graphql';
+import { setHTMLFixture } from '../helpers/fixtures';
+import initMRPopovers from '~/mr_popover/index';
+
+createDefaultClient.default = jest.fn();
+
+describe('initMRPopovers', () => {
+ let mr1;
+ let mr2;
+ let mr3;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <div id="one" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project">
+ MR1
+ </div>
+ <div id="two" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project">
+ MR2
+ </div>
+ <div id="three" class="gfm-merge_request">
+ MR3
+ </div>
+ `);
+
+ mr1 = document.querySelector('#one');
+ mr2 = document.querySelector('#two');
+ mr3 = document.querySelector('#three');
+
+ mr1.addEventListener = jest.fn();
+ mr2.addEventListener = jest.fn();
+ mr3.addEventListener = jest.fn();
+ });
+
+ it('does not add the same event listener twice', () => {
+ initMRPopovers([mr1, mr1, mr2]);
+
+ expect(mr1.addEventListener).toHaveBeenCalledTimes(1);
+ expect(mr2.addEventListener).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not add listener if it does not have the necessary data attributes', () => {
+ initMRPopovers([mr1, mr2, mr3]);
+
+ expect(mr3.addEventListener).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js
new file mode 100644
index 00000000000..79ed4163010
--- /dev/null
+++ b/spec/frontend/mr_popover/mr_popover_spec.js
@@ -0,0 +1,61 @@
+import MRPopover from '~/mr_popover/components/mr_popover';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MR Popover', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(MRPopover, {
+ propsData: {
+ target: document.createElement('a'),
+ projectPath: 'foo/bar',
+ mergeRequestIID: '1',
+ mergeRequestTitle: 'MR Title',
+ },
+ mocks: {
+ $apollo: {
+ loading: false,
+ },
+ },
+ });
+ });
+
+ it('shows skeleton-loader while apollo is loading', () => {
+ wrapper.vm.$apollo.loading = true;
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('loaded state', () => {
+ it('matches the snapshot', () => {
+ wrapper.setData({
+ mergeRequest: {
+ state: 'opened',
+ createdAt: new Date(),
+ headPipeline: {
+ detailedStatus: {
+ group: 'success',
+ status: 'status_success',
+ },
+ },
+ },
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('does not show CI Icon if there is no pipeline data', () => {
+ wrapper.setData({
+ mergeRequest: {
+ state: 'opened',
+ headPipeline: null,
+ stateHumanName: 'Open',
+ title: 'Merge Request Title',
+ createdAt: new Date(),
+ },
+ });
+
+ expect(wrapper.contains('ciicon-stub')).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/notebook/lib/highlight_spec.js b/spec/frontend/notebook/lib/highlight_spec.js
index d71c5718858..d71c5718858 100644
--- a/spec/javascripts/notebook/lib/highlight_spec.js
+++ b/spec/frontend/notebook/lib/highlight_spec.js
diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js
new file mode 100644
index 00000000000..0a52c81571e
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_actions_spec.js
@@ -0,0 +1,104 @@
+import createStore from '~/notes/stores';
+import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import { discussionMock } from '../../../javascripts/notes/mock_data';
+import DiscussionActions from '~/notes/components/discussion_actions.vue';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import ResolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue';
+import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
+import JumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue';
+
+describe('DiscussionActions', () => {
+ let wrapper;
+ const createComponentFactory = (shallow = true) => props => {
+ const localVue = createLocalVue();
+ const store = createStore();
+ const mountFn = shallow ? shallowMount : mount;
+
+ wrapper = mountFn(DiscussionActions, {
+ localVue,
+ store,
+ propsData: {
+ discussion: discussionMock,
+ isResolving: false,
+ resolveButtonTitle: 'Resolve discussion',
+ resolveWithIssuePath: '/some/issue/path',
+ shouldShowJumpToNextDiscussion: true,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('rendering', () => {
+ const createComponent = createComponentFactory();
+
+ it('renders reply placeholder, resolve discussion button, resolve with issue button and jump to next discussion button', () => {
+ createComponent();
+ expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
+ expect(wrapper.find(ResolveDiscussionButton).exists()).toBe(true);
+ expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(true);
+ expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(true);
+ });
+
+ it('only renders reply placholder if disccusion is not resolvable', () => {
+ const discussion = { ...discussionMock };
+ discussion.resolvable = false;
+ createComponent({ discussion });
+
+ expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
+ expect(wrapper.find(ResolveDiscussionButton).exists()).toBe(false);
+ expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(false);
+ expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(false);
+ });
+
+ it('does not render resolve with issue button if resolveWithIssuePath is falsy', () => {
+ createComponent({ resolveWithIssuePath: '' });
+
+ expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(false);
+ });
+
+ it('does not render jump to next discussion button if shouldShowJumpToNextDiscussion is false', () => {
+ createComponent({ shouldShowJumpToNextDiscussion: false });
+
+ expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(false);
+ });
+ });
+
+ describe('events handling', () => {
+ const createComponent = createComponentFactory(false);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('emits showReplyForm event when clicking on reply placeholder', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+ wrapper
+ .find(ReplyPlaceholder)
+ .find('button')
+ .trigger('click');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('showReplyForm');
+ });
+
+ it('emits resolve event when clicking on resolve button', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+ wrapper
+ .find(ResolveDiscussionButton)
+ .find('button')
+ .trigger('click');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('resolve');
+ });
+
+ it('emits jumpToNextDiscussion event when clicking on jump to next discussion button', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+ wrapper
+ .find(JumpToNextDiscussionButton)
+ .find('button')
+ .trigger('click');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('jumpToNextDiscussion');
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
new file mode 100644
index 00000000000..c3204b3aaa0
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -0,0 +1,139 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import '~/behaviors/markdown/render_gfm';
+import { SYSTEM_NOTE } from '~/notes/constants';
+import DiscussionNotes from '~/notes/components/discussion_notes.vue';
+import NoteableNote from '~/notes/components/noteable_note.vue';
+import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
+import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
+import SystemNote from '~/vue_shared/components/notes/system_note.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import createStore from '~/notes/stores';
+import {
+ noteableDataMock,
+ discussionMock,
+ notesDataMock,
+} from '../../../javascripts/notes/mock_data';
+
+const localVue = createLocalVue();
+
+describe('DiscussionNotes', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ const store = createStore();
+ store.dispatch('setNoteableData', noteableDataMock);
+ store.dispatch('setNotesData', notesDataMock);
+
+ wrapper = shallowMount(DiscussionNotes, {
+ localVue,
+ store,
+ propsData: {
+ discussion: discussionMock,
+ isExpanded: false,
+ shouldGroupReplies: false,
+ ...props,
+ },
+ scopedSlots: {
+ footer: '<p slot-scope="{ showReplies }">showReplies:{{showReplies}}</p>',
+ },
+ slots: {
+ 'avatar-badge': '<span class="avatar-badge-slot-content" />',
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('rendering', () => {
+ it('renders an element for each note in the discussion', () => {
+ createComponent();
+ const notesCount = discussionMock.notes.length;
+ const els = wrapper.findAll(TimelineEntryItem);
+ expect(els.length).toBe(notesCount);
+ });
+
+ it('renders one element if replies groupping is enabled', () => {
+ createComponent({ shouldGroupReplies: true });
+ const els = wrapper.findAll(TimelineEntryItem);
+ expect(els.length).toBe(1);
+ });
+
+ it('uses proper component to render each note type', () => {
+ const discussion = { ...discussionMock };
+ const notesData = [
+ // PlaceholderSystemNote
+ {
+ id: 1,
+ isPlaceholderNote: true,
+ placeholderType: SYSTEM_NOTE,
+ notes: [{ body: 'PlaceholderSystemNote' }],
+ },
+ // PlaceholderNote
+ {
+ id: 2,
+ isPlaceholderNote: true,
+ notes: [{ body: 'PlaceholderNote' }],
+ },
+ // SystemNote
+ {
+ id: 3,
+ system: true,
+ note: 'SystemNote',
+ },
+ // NoteableNote
+ discussion.notes[0],
+ ];
+ discussion.notes = notesData;
+ createComponent({ discussion });
+ const notes = wrapper.findAll('.notes > li');
+
+ expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true);
+ expect(notes.at(1).is(PlaceholderNote)).toBe(true);
+ expect(notes.at(2).is(SystemNote)).toBe(true);
+ expect(notes.at(3).is(NoteableNote)).toBe(true);
+ });
+
+ it('renders footer scoped slot with showReplies === true when expanded', () => {
+ createComponent({ isExpanded: true });
+ expect(wrapper.text()).toMatch('showReplies:true');
+ });
+
+ it('renders footer scoped slot with showReplies === false when collapsed', () => {
+ createComponent({ isExpanded: false });
+ expect(wrapper.text()).toMatch('showReplies:false');
+ });
+
+ it('passes down avatar-badge slot content', () => {
+ createComponent();
+ expect(wrapper.find('.avatar-badge-slot-content').exists()).toBe(true);
+ });
+ });
+
+ describe('componentData', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should return first note object for placeholder note', () => {
+ const data = {
+ isPlaceholderNote: true,
+ notes: [{ body: 'hello world!' }],
+ };
+ const note = wrapper.vm.componentData(data);
+
+ expect(note).toEqual(data.notes[0]);
+ });
+
+ it('should return given note for nonplaceholder notes', () => {
+ const data = {
+ notes: [{ id: 12 }],
+ };
+ const note = wrapper.vm.componentData(data);
+
+ expect(note).toEqual(data);
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
index 07a366cf339..07a366cf339 100644
--- a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js
+++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
diff --git a/spec/javascripts/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js
index 5024f40ec5d..5024f40ec5d 100644
--- a/spec/javascripts/notes/components/discussion_resolve_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js
diff --git a/spec/frontend/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js
new file mode 100644
index 00000000000..ff833d2c899
--- /dev/null
+++ b/spec/frontend/notes/components/note_app_spec.js
@@ -0,0 +1,322 @@
+import $ from 'helpers/jquery';
+import Vue from 'vue';
+import { mount, createLocalVue } from '@vue/test-utils';
+import NotesApp from '~/notes/components/notes_app.vue';
+import service from '~/notes/services/notes_service';
+import createStore from '~/notes/stores';
+import '~/behaviors/markdown/render_gfm';
+import { setTestTimeout } from 'helpers/timeout';
+// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-ce/issues/62491)
+import * as mockData from '../../../javascripts/notes/mock_data';
+
+const originalInterceptors = [...Vue.http.interceptors];
+
+const emptyResponseInterceptor = (request, next) => {
+ next(
+ request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }),
+ );
+};
+
+setTestTimeout(1000);
+
+describe('note_app', () => {
+ let mountComponent;
+ let wrapper;
+ let store;
+
+ /**
+ * waits for fetchNotes() to complete
+ */
+ const waitForDiscussionsRequest = () =>
+ new Promise(resolve => {
+ const { vm } = wrapper.find(NotesApp);
+ const unwatch = vm.$watch('isFetching', isFetching => {
+ if (isFetching) {
+ return;
+ }
+
+ unwatch();
+ resolve();
+ });
+ });
+
+ beforeEach(() => {
+ $('body').attr('data-page', 'projects:merge_requests:show');
+
+ store = createStore();
+ mountComponent = data => {
+ const propsData = data || {
+ noteableData: mockData.noteableDataMock,
+ notesData: mockData.notesDataMock,
+ userData: mockData.userDataMock,
+ };
+ const localVue = createLocalVue();
+
+ return mount(
+ {
+ components: {
+ NotesApp,
+ },
+ template: '<div class="js-vue-notes-event"><notes-app v-bind="$attrs" /></div>',
+ },
+ {
+ attachToDocument: true,
+ propsData,
+ store,
+ localVue,
+ sync: false,
+ },
+ );
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ Vue.http.interceptors = [...originalInterceptors];
+ });
+
+ describe('set data', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(emptyResponseInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('should set notes data', () => {
+ expect(store.state.notesData).toEqual(mockData.notesDataMock);
+ });
+
+ it('should set issue data', () => {
+ expect(store.state.noteableData).toEqual(mockData.noteableDataMock);
+ });
+
+ it('should set user data', () => {
+ expect(store.state.userData).toEqual(mockData.userDataMock);
+ });
+
+ it('should fetch discussions', () => {
+ expect(store.state.discussions).toEqual([]);
+ });
+ });
+
+ describe('render', () => {
+ beforeEach(() => {
+ setFixtures('<div class="js-discussions-count"></div>');
+
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('should render list of notes', () => {
+ const note =
+ mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
+ '/gitlab-org/gitlab-ce/issues/26/discussions.json'
+ ][0].notes[0];
+
+ expect(
+ wrapper
+ .find('.main-notes-list .note-header-author-name')
+ .text()
+ .trim(),
+ ).toEqual(note.author.name);
+
+ expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html);
+ });
+
+ it('should render form', () => {
+ expect(wrapper.find('.js-main-target-form').name()).toEqual('form');
+ expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual(
+ 'Write a comment or drag your files here…',
+ );
+ });
+
+ it('should not render form when commenting is disabled', () => {
+ wrapper.destroy();
+
+ store.state.commentsDisabled = true;
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest().then(() => {
+ expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
+ });
+ });
+
+ it('should render discussion filter note `commentsDisabled` is true', () => {
+ wrapper.destroy();
+
+ store.state.commentsDisabled = true;
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest().then(() => {
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
+ });
+ });
+
+ it('should render form comment button as disabled', () => {
+ expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
+ });
+
+ it('updates discussions badge', () => {
+ expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
+ });
+ });
+
+ describe('while fetching data', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(emptyResponseInterceptor);
+ wrapper = mountComponent();
+ });
+
+ afterEach(() => waitForDiscussionsRequest());
+
+ it('renders skeleton notes', () => {
+ expect(wrapper.find('.animation-container').exists()).toBe(true);
+ });
+
+ it('should render form', () => {
+ expect(wrapper.find('.js-main-target-form').name()).toEqual('form');
+ expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual(
+ 'Write a comment or drag your files here…',
+ );
+ });
+ });
+
+ describe('update note', () => {
+ describe('individual note', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
+ jest.spyOn(service, 'updateNote');
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest().then(() => {
+ wrapper.find('.js-note-edit').trigger('click');
+ });
+ });
+
+ it('renders edit form', () => {
+ expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
+ });
+
+ it('calls the service to update the note', () => {
+ wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
+ wrapper.find('.js-vue-issue-save').trigger('click');
+
+ expect(service.updateNote).toHaveBeenCalled();
+ });
+ });
+
+ describe('discussion note', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(mockData.discussionNoteInterceptor);
+ jest.spyOn(service, 'updateNote');
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest().then(() => {
+ wrapper.find('.js-note-edit').trigger('click');
+ });
+ });
+
+ it('renders edit form', () => {
+ expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
+ });
+
+ it('updates the note and resets the edit form', () => {
+ wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
+ wrapper.find('.js-vue-issue-save').trigger('click');
+
+ expect(service.updateNote).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('new note form', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('should render markdown docs url', () => {
+ const { markdownDocsPath } = mockData.notesDataMock;
+
+ expect(
+ wrapper
+ .find(`a[href="${markdownDocsPath}"]`)
+ .text()
+ .trim(),
+ ).toEqual('Markdown');
+ });
+
+ it('should render quick action docs url', () => {
+ const { quickActionsDocsPath } = mockData.notesDataMock;
+
+ expect(
+ wrapper
+ .find(`a[href="${quickActionsDocsPath}"]`)
+ .text()
+ .trim(),
+ ).toEqual('quick actions');
+ });
+ });
+
+ describe('edit form', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('should render markdown docs url', () => {
+ wrapper.find('.js-note-edit').trigger('click');
+ const { markdownDocsPath } = mockData.notesDataMock;
+
+ return Vue.nextTick().then(() => {
+ expect(
+ wrapper
+ .find(`.edit-note a[href="${markdownDocsPath}"]`)
+ .text()
+ .trim(),
+ ).toEqual('Markdown is supported');
+ });
+ });
+
+ it('should not render quick actions docs url', () => {
+ wrapper.find('.js-note-edit').trigger('click');
+ const { quickActionsDocsPath } = mockData.notesDataMock;
+ expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
+ });
+ });
+
+ describe('emoji awards', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(emptyResponseInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('dispatches toggleAward after toggleAward event', () => {
+ const toggleAwardEvent = new CustomEvent('toggleAward', {
+ detail: {
+ awardName: 'test',
+ noteId: 1,
+ },
+ });
+ const toggleAwardAction = jest.fn().mockName('toggleAward');
+ wrapper.vm.$store.hotUpdate({
+ actions: {
+ toggleAward: toggleAwardAction,
+ stopPolling() {},
+ },
+ });
+
+ wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent);
+
+ expect(toggleAwardAction).toHaveBeenCalledTimes(1);
+ const [, payload] = toggleAwardAction.mock.calls[0];
+
+ expect(payload).toEqual({
+ awardName: 'test',
+ noteId: 1,
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/note_attachment_spec.js b/spec/frontend/notes/components/note_attachment_spec.js
index b14a518b622..b14a518b622 100644
--- a/spec/javascripts/notes/components/note_attachment_spec.js
+++ b/spec/frontend/notes/components/note_attachment_spec.js
diff --git a/spec/javascripts/notes/components/note_edited_text_spec.js b/spec/frontend/notes/components/note_edited_text_spec.js
index e4c8d954d50..e4c8d954d50 100644
--- a/spec/javascripts/notes/components/note_edited_text_spec.js
+++ b/spec/frontend/notes/components/note_edited_text_spec.js
diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
new file mode 100644
index 00000000000..b57041cf4d1
--- /dev/null
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -0,0 +1,1045 @@
+/* eslint-disable import/no-commonjs, no-new */
+
+import $ from 'jquery';
+import _ from 'underscore';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import * as urlUtility from '~/lib/utils/url_utility';
+import '~/behaviors/markdown/render_gfm';
+import { createSpyObj } from 'helpers/jest_helpers';
+import { setTestTimeoutOnce } from 'helpers/timeout';
+import { TEST_HOST } from 'helpers/test_constants';
+
+// These must be imported synchronously because they pull dependencies
+// from the DOM.
+window.jQuery = $;
+require('autosize');
+require('~/commons');
+require('~/notes');
+
+const { Notes } = window;
+const FLASH_TYPE_ALERT = 'alert';
+const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
+const fixture = 'snippets/show.html';
+let mockAxios;
+
+window.project_uploads_path = `${TEST_HOST}/uploads`;
+window.gon = window.gon || {};
+window.gl = window.gl || {};
+gl.utils = gl.utils || {};
+gl.utils.disableButtonIfEmptyField = () => {};
+
+describe('Old Notes (~/notes.js)', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ loadFixtures(fixture);
+
+ // Re-declare this here so that test_setup.js#beforeEach() doesn't
+ // overwrite it.
+ mockAxios = new MockAdapter(axios);
+
+ $.ajax = () => {
+ throw new Error('$.ajax should not be called through!');
+ };
+
+ // These jQuery+DOM tests are super flaky so increase the timeout to avoid
+ // random failures.
+ // It seems that running tests in parallel increases failure rate.
+ jest.setTimeout(4000);
+ setTestTimeoutOnce(4000);
+ });
+
+ afterEach(done => {
+ // The Notes component sets a polling interval. Clear it after every run.
+ // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
+ jest.clearAllTimers();
+
+ setImmediate(() => {
+ // Wait for any requests to resolve, otherwise we get failures about
+ // unmocked requests.
+ mockAxios.restore();
+ done();
+ });
+ });
+
+ it('loads the Notes class into the DOM', () => {
+ expect(Notes).toBeDefined();
+ expect(Notes.name).toBe('Notes');
+ });
+
+ describe('addBinding', () => {
+ it('calls postComment when comment button is clicked', () => {
+ jest.spyOn(Notes.prototype, 'postComment');
+
+ new window.Notes('', []);
+ $('.js-comment-button').click();
+ expect(Notes.prototype.postComment).toHaveBeenCalled();
+ });
+ });
+
+ describe('task lists', () => {
+ beforeEach(() => {
+ mockAxios.onAny().reply(200, {});
+ new Notes('', []);
+ });
+
+ it('modifies the Markdown field', () => {
+ const changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ $('input[type=checkbox]')
+ .attr('checked', true)[0]
+ .dispatchEvent(changeEvent);
+
+ expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
+ });
+
+ it('submits an ajax request on tasklist:changed', () => {
+ jest.spyOn(axios, 'patch');
+
+ const lineNumber = 8;
+ const lineSource = '- [ ] item 8';
+ const index = 3;
+ const checked = true;
+
+ $('.js-task-list-container').trigger({
+ type: 'tasklist:changed',
+ detail: { lineNumber, lineSource, index, checked },
+ });
+
+ expect(axios.patch).toHaveBeenCalledWith(undefined, {
+ note: {
+ note: '',
+ lock_version: undefined,
+ update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
+ },
+ });
+ });
+ });
+
+ describe('comments', () => {
+ let notes;
+ let autosizeSpy;
+ let textarea;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+
+ textarea = $('.js-note-text');
+ textarea.data('autosave', {
+ reset: () => {},
+ });
+ autosizeSpy = jest.fn();
+ $(textarea).on('autosize:update', autosizeSpy);
+
+ jest.spyOn(notes, 'renderNote');
+
+ $('.js-comment-button').on('click', e => {
+ const $form = $(this);
+ e.preventDefault();
+ notes.addNote($form, {});
+ notes.reenableTargetFormSubmitButton(e);
+ notes.resetMainTargetForm(e);
+ });
+ });
+
+ it('autosizes after comment submission', () => {
+ textarea.text('This is an example comment note');
+ expect(autosizeSpy).not.toHaveBeenCalled();
+ $('.js-comment-button').click();
+ expect(autosizeSpy).toHaveBeenCalled();
+ });
+
+ it('should not place escaped text in the comment box in case of error', () => {
+ const deferred = $.Deferred();
+ jest.spyOn($, 'ajax').mockReturnValueOnce(deferred);
+ $(textarea).text('A comment with `markup`.');
+
+ deferred.reject();
+ $('.js-comment-button').click();
+
+ expect($(textarea).val()).toBe('A comment with `markup`.');
+
+ $.ajax.mockRestore();
+ expect($.ajax.mock).toBeUndefined();
+ });
+ });
+
+ describe('updateNote', () => {
+ let notes;
+ let noteEntity;
+ let $notesContainer;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+ window.gon.current_username = 'root';
+ window.gon.current_user_fullname = 'Administrator';
+ const sampleComment = 'foo';
+ noteEntity = {
+ id: 1234,
+ html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
+ <div class="note-text">${sampleComment}</div>
+ </li>`,
+ note: sampleComment,
+ valid: true,
+ };
+
+ $notesContainer = $('ul.main-notes-list');
+ const $form = $('form.js-main-target-form');
+ $form.find('textarea.js-note-text').val(sampleComment);
+
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, noteEntity);
+ });
+
+ it('updates note and resets edit form', () => {
+ jest.spyOn(notes, 'revertNoteEditForm');
+ jest.spyOn(notes, 'setupNewNote');
+
+ $('.js-comment-button').click();
+
+ const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
+ const updatedNote = Object.assign({}, noteEntity);
+ updatedNote.note = 'bar';
+ notes.updateNote(updatedNote, $targetNote);
+
+ expect(notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+ expect(notes.setupNewNote).toHaveBeenCalled();
+ });
+ });
+
+ describe('updateNoteTargetSelector', () => {
+ const hash = 'note_foo';
+ let $note;
+
+ beforeEach(() => {
+ $note = $(`<div id="${hash}"></div>`);
+ jest.spyOn($note, 'filter');
+ jest.spyOn($note, 'toggleClass');
+ });
+
+ afterEach(() => {
+ expect(typeof urlUtility.getLocationHash.mock).toBe('object');
+ urlUtility.getLocationHash.mockRestore();
+ expect(urlUtility.getLocationHash.mock).toBeUndefined();
+ expect(urlUtility.getLocationHash()).toBeNull();
+ });
+
+ // urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally.
+
+ it('sets target when hash matches', () => {
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash);
+
+ Notes.updateNoteTargetSelector($note);
+
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
+ expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
+ expect($note.toggleClass).toHaveBeenCalledWith('target', true);
+ });
+
+ it('unsets target when hash does not match', () => {
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce('note_doesnotexist');
+
+ Notes.updateNoteTargetSelector($note);
+
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
+ expect($note.toggleClass).toHaveBeenCalledWith('target', false);
+ });
+
+ it('unsets target when there is not a hash fragment anymore', () => {
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(null);
+
+ Notes.updateNoteTargetSelector($note);
+
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
+ expect($note.toggleClass).toHaveBeenCalledWith('target', false);
+ });
+ });
+
+ describe('renderNote', () => {
+ let notes;
+ let note;
+ let $notesList;
+
+ beforeEach(() => {
+ note = {
+ id: 1,
+ valid: true,
+ note: 'heya',
+ html: '<div>heya</div>',
+ };
+ $notesList = createSpyObj('$notesList', ['find', 'append']);
+
+ notes = createSpyObj('notes', [
+ 'setupNewNote',
+ 'refresh',
+ 'collapseLongCommitList',
+ 'updateNotesCount',
+ 'putConflictEditWarningInPlace',
+ ]);
+ notes.taskList = createSpyObj('tasklist', ['init']);
+ notes.note_ids = [];
+ notes.updatedNotesTrackingMap = {};
+
+ jest.spyOn(Notes, 'isNewNote');
+ jest.spyOn(Notes, 'isUpdatedNote');
+ jest.spyOn(Notes, 'animateAppendNote');
+ jest.spyOn(Notes, 'animateUpdateNote');
+ });
+
+ describe('when adding note', () => {
+ it('should call .animateAppendNote', () => {
+ Notes.isNewNote.mockReturnValueOnce(true);
+ Notes.prototype.renderNote.call(notes, note, null, $notesList);
+
+ expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
+ });
+ });
+
+ describe('when note was edited', () => {
+ it('should call .animateUpdateNote', () => {
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
+ const $note = $('<div>');
+ $notesList.find.mockReturnValueOnce($note);
+ const $newNote = $(note.html);
+ Notes.animateUpdateNote.mockReturnValueOnce($newNote);
+
+ Notes.prototype.renderNote.call(notes, note, null, $notesList);
+
+ expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note);
+ expect(notes.setupNewNote).toHaveBeenCalledWith($newNote);
+ });
+
+ describe('while editing', () => {
+ it('should update textarea if nothing has been touched', () => {
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
+ const $note = $(`<div class="is-editing">
+ <div class="original-note-content">initial</div>
+ <textarea class="js-note-text">initial</textarea>
+ </div>`);
+ $notesList.find.mockReturnValueOnce($note);
+ Notes.prototype.renderNote.call(notes, note, null, $notesList);
+
+ expect($note.find('.js-note-text').val()).toEqual(note.note);
+ });
+
+ it('should call .putConflictEditWarningInPlace', () => {
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
+ const $note = $(`<div class="is-editing">
+ <div class="original-note-content">initial</div>
+ <textarea class="js-note-text">different</textarea>
+ </div>`);
+ $notesList.find.mockReturnValueOnce($note);
+ Notes.prototype.renderNote.call(notes, note, null, $notesList);
+
+ expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
+ });
+ });
+ });
+ });
+
+ describe('isUpdatedNote', () => {
+ it('should consider same note text as the same', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'initial',
+ },
+ $(`<div>
+ <div class="original-note-content">initial</div>
+ </div>`),
+ );
+
+ expect(result).toEqual(false);
+ });
+
+ it('should consider same note with trailing newline as the same', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'initial\n',
+ },
+ $(`<div>
+ <div class="original-note-content">initial\n</div>
+ </div>`),
+ );
+
+ expect(result).toEqual(false);
+ });
+
+ it('should consider different notes as different', () => {
+ const result = Notes.isUpdatedNote(
+ {
+ note: 'foo',
+ },
+ $(`<div>
+ <div class="original-note-content">bar</div>
+ </div>`),
+ );
+
+ expect(result).toEqual(true);
+ });
+ });
+
+ describe('renderDiscussionNote', () => {
+ let discussionContainer;
+ let note;
+ let notes;
+ let $form;
+ let row;
+
+ beforeEach(() => {
+ note = {
+ html: '<li></li>',
+ discussion_html: '<div></div>',
+ discussion_id: 1,
+ discussion_resolvable: false,
+ diff_discussion_html: false,
+ };
+ $form = createSpyObj('$form', ['closest', 'find']);
+ $form.length = 1;
+ row = createSpyObj('row', ['prevAll', 'first', 'find']);
+
+ notes = createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
+ notes.note_ids = [];
+
+ jest.spyOn(Notes, 'isNewNote');
+ jest.spyOn(Notes, 'animateAppendNote').mockImplementation();
+ Notes.isNewNote.mockReturnValue(true);
+ notes.isParallelView.mockReturnValue(false);
+ row.prevAll.mockReturnValue(row);
+ row.first.mockReturnValue(row);
+ row.find.mockReturnValue(row);
+ });
+
+ describe('Discussion root note', () => {
+ let body;
+
+ beforeEach(() => {
+ body = createSpyObj('body', ['attr']);
+ discussionContainer = { length: 0 };
+
+ $form.closest.mockReturnValueOnce(row).mockReturnValue($form);
+ $form.find.mockReturnValue(discussionContainer);
+ body.attr.mockReturnValue('');
+ });
+
+ it('should call Notes.animateAppendNote', () => {
+ Notes.prototype.renderDiscussionNote.call(notes, note, $form);
+
+ expect(Notes.animateAppendNote).toHaveBeenCalledWith(
+ note.discussion_html,
+ $('.main-notes-list'),
+ );
+ });
+
+ it('should append to row selected with line_code', () => {
+ $form.length = 0;
+ note.discussion_line_code = 'line_code';
+ note.diff_discussion_html = '<tr></tr>';
+
+ const line = document.createElement('div');
+ line.id = note.discussion_line_code;
+ document.body.appendChild(line);
+
+ // Override mocks for this single test
+ $form.closest.mockReset();
+ $form.closest.mockReturnValue($form);
+
+ Notes.prototype.renderDiscussionNote.call(notes, note, $form);
+
+ expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html);
+ });
+ });
+
+ describe('Discussion sub note', () => {
+ beforeEach(() => {
+ discussionContainer = { length: 1 };
+
+ $form.closest.mockReturnValueOnce(row).mockReturnValueOnce($form);
+ $form.find.mockReturnValue(discussionContainer);
+
+ Notes.prototype.renderDiscussionNote.call(notes, note, $form);
+ });
+
+ it('should call Notes.animateAppendNote', () => {
+ expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer);
+ });
+ });
+ });
+
+ describe('animateAppendNote', () => {
+ let noteHTML;
+ let $notesList;
+ let $resultantNote;
+
+ beforeEach(() => {
+ noteHTML = '<div></div>';
+ $notesList = createSpyObj('$notesList', ['append']);
+
+ $resultantNote = Notes.animateAppendNote(noteHTML, $notesList);
+ });
+
+ it('should have `fade-in-full` class', () => {
+ expect($resultantNote.hasClass('fade-in-full')).toEqual(true);
+ });
+
+ it('should append note to the notes list', () => {
+ expect($notesList.append).toHaveBeenCalledWith($resultantNote);
+ });
+ });
+
+ describe('animateUpdateNote', () => {
+ let noteHTML;
+ let $note;
+ let $updatedNote;
+
+ beforeEach(() => {
+ noteHTML = '<div></div>';
+ $note = createSpyObj('$note', ['replaceWith']);
+
+ $updatedNote = Notes.animateUpdateNote(noteHTML, $note);
+ });
+
+ it('should have `fade-in` class', () => {
+ expect($updatedNote.hasClass('fade-in')).toEqual(true);
+ });
+
+ it('should call replaceWith on $note', () => {
+ expect($note.replaceWith).toHaveBeenCalledWith($updatedNote);
+ });
+ });
+
+ describe('putEditFormInPlace', () => {
+ it('should call GLForm with GFM parameter passed through', () => {
+ const notes = new Notes('', []);
+ const $el = $(`
+ <div>
+ <form></form>
+ </div>
+ `);
+
+ notes.putEditFormInPlace($el);
+
+ expect(notes.glForm.enableGFM).toBeTruthy();
+ });
+ });
+
+ describe('postComment & updateComment', () => {
+ const sampleComment = 'foo';
+ const note = {
+ id: 1234,
+ html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
+ <div class="note-text">${sampleComment}</div>
+ </li>`,
+ note: sampleComment,
+ valid: true,
+ };
+ let notes;
+ let $form;
+ let $notesContainer;
+
+ function mockNotesPost() {
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
+ }
+
+ function mockNotesPostError() {
+ mockAxios.onPost(NOTES_POST_PATH).networkError();
+ }
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+ window.gon.current_username = 'root';
+ window.gon.current_user_fullname = 'Administrator';
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').val(sampleComment);
+ });
+
+ it('should show placeholder note while new comment is being posted', () => {
+ mockNotesPost();
+
+ $('.js-comment-button').click();
+
+ expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0);
+ });
+
+ it('should remove placeholder note when new comment is done posting', done => {
+ mockNotesPost();
+
+ $('.js-comment-button').click();
+
+ setImmediate(() => {
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0);
+ done();
+ });
+ });
+
+ describe('postComment', () => {
+ it('disables the submit button', done => {
+ const $submitButton = $form.find('.js-comment-submit-button');
+
+ expect($submitButton).not.toBeDisabled();
+ const dummyEvent = {
+ preventDefault() {},
+ target: $submitButton,
+ };
+ mockAxios.onPost(NOTES_POST_PATH).replyOnce(() => {
+ expect($submitButton).toBeDisabled();
+ return [200, note];
+ });
+
+ notes
+ .postComment(dummyEvent)
+ .then(() => {
+ expect($submitButton).not.toBeDisabled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ it('should show actual note element when new comment is done posting', done => {
+ mockNotesPost();
+
+ $('.js-comment-button').click();
+
+ setImmediate(() => {
+ expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
+ done();
+ });
+ });
+
+ it('should reset Form when new comment is done posting', done => {
+ mockNotesPost();
+
+ $('.js-comment-button').click();
+
+ setImmediate(() => {
+ expect($form.find('textarea.js-note-text').val()).toEqual('');
+ done();
+ });
+ });
+
+ it('should show flash error message when new comment failed to be posted', done => {
+ mockNotesPostError();
+ jest.spyOn(notes, 'addFlash');
+
+ $('.js-comment-button').click();
+
+ setImmediate(() => {
+ expect(notes.addFlash).toHaveBeenCalled();
+ // JSDom doesn't support the :visible selector yet
+ expect(notes.flashContainer.style.display).not.toBe('none');
+ done();
+ });
+ });
+
+ // This is a bad test carried over from the Karma -> Jest migration.
+ // The corresponding test in the Karma suite tests for
+ // elements and methods that don't actually exist, and gives a false
+ // positive pass.
+ /*
+ it('should show flash error message when comment failed to be updated', done => {
+ mockNotesPost();
+ jest.spyOn(notes, 'addFlash').mockName('addFlash');
+
+ $('.js-comment-button').click();
+
+ deferredPromise()
+ .then(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').val(updatedComment);
+
+ mockNotesPostError();
+
+ $noteEl.find('.js-comment-save-button').click();
+ notes.updateComment({preventDefault: () => {}});
+ })
+ .then(() => deferredPromise())
+ .then(() => {
+ const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
+
+ expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
+ expect(
+ $updatedNoteEl
+ .find('.note-text')
+ .text()
+ .trim(),
+ ).toEqual(sampleComment); // See if comment reverted back to original
+
+ expect(notes.addFlash).toHaveBeenCalled();
+ expect(notes.flashContainer.style.display).not.toBe('none');
+ done();
+ })
+ .catch(done.fail);
+ }, 5000);
+ */
+ });
+
+ describe('postComment with Slash commands', () => {
+ const sampleComment = '/assign @root\n/award :100:';
+ const note = {
+ commands_changes: {
+ assignee_id: 1,
+ emoji_award: '100',
+ },
+ errors: {
+ commands_only: ['Commands applied'],
+ },
+ valid: false,
+ };
+ let $form;
+ let $notesContainer;
+
+ beforeEach(() => {
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
+
+ new Notes('', []);
+ window.gon.current_username = 'root';
+ window.gon.current_user_fullname = 'Administrator';
+ gl.awardsHandler = {
+ addAwardToEmojiBar: () => {},
+ scrollToAwards: () => {},
+ };
+ gl.GfmAutoComplete = {
+ dataSources: {
+ commands: '/root/test-project/autocomplete_sources/commands',
+ },
+ };
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').val(sampleComment);
+ });
+
+ it('should remove slash command placeholder when comment with slash commands is done posting', done => {
+ jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
+ $('.js-comment-button').click();
+
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+
+ setImmediate(() => {
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ done();
+ });
+ });
+ });
+
+ describe('update comment with script tags', () => {
+ const sampleComment = '<script></script>';
+ const updatedComment = '<script></script>';
+ const note = {
+ id: 1234,
+ html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
+ <div class="note-text">${sampleComment}</div>
+ </li>`,
+ note: sampleComment,
+ valid: true,
+ };
+ let $form;
+ let $notesContainer;
+
+ beforeEach(() => {
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
+
+ new Notes('', []);
+ window.gon.current_username = 'root';
+ window.gon.current_user_fullname = 'Administrator';
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').html(sampleComment);
+ });
+
+ it('should not render a script tag', done => {
+ $('.js-comment-button').click();
+
+ setImmediate(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').html(updatedComment);
+ $noteEl.find('.js-comment-save-button').click();
+
+ const $updatedNoteEl = $notesContainer
+ .find(`#note_${note.id}`)
+ .find('.js-task-list-container');
+
+ expect(
+ $updatedNoteEl
+ .find('.note-text')
+ .text()
+ .trim(),
+ ).toEqual('');
+
+ done();
+ });
+ });
+ });
+
+ describe('getFormData', () => {
+ let $form;
+ let sampleComment;
+ let notes;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+
+ $form = $('form');
+ sampleComment = 'foobar';
+ });
+
+ it('should return form metadata object from form reference', () => {
+ $form.find('textarea.js-note-text').val(sampleComment);
+ const { formData, formContent, formAction } = notes.getFormData($form);
+
+ expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
+ expect(formContent).toEqual(sampleComment);
+ expect(formAction).toEqual($form.attr('action'));
+ });
+
+ it('should return form metadata with sanitized formContent from form reference', () => {
+ jest.spyOn(_, 'escape');
+
+ sampleComment = '<script>alert("Boom!");</script>';
+ $form.find('textarea.js-note-text').val(sampleComment);
+
+ const { formContent } = notes.getFormData($form);
+
+ expect(_.escape).toHaveBeenCalledWith(sampleComment);
+ expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
+ });
+ });
+
+ describe('hasQuickActions', () => {
+ let notes;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+ });
+
+ it('should return true when comment begins with a quick action', () => {
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
+
+ expect(hasQuickActions).toBeTruthy();
+ });
+
+ it('should return false when comment does NOT begin with a quick action', () => {
+ const sampleComment = 'Hey, /unassign Merging this';
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
+
+ expect(hasQuickActions).toBeFalsy();
+ });
+
+ it('should return false when comment does NOT have any quick actions', () => {
+ const sampleComment = 'Looking good, Awesome!';
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
+
+ expect(hasQuickActions).toBeFalsy();
+ });
+ });
+
+ describe('stripQuickActions', () => {
+ it('should strip quick actions from the comment which begins with a quick action', () => {
+ const notes = new Notes();
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
+ const stripedComment = notes.stripQuickActions(sampleComment);
+
+ expect(stripedComment).toBe('');
+ });
+
+ it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
+ const notes = new Notes();
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
+ const stripedComment = notes.stripQuickActions(sampleComment);
+
+ expect(stripedComment).toBe('Merging this');
+ });
+
+ it('should NOT strip string that has slashes within', () => {
+ const notes = new Notes();
+ const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
+ const stripedComment = notes.stripQuickActions(sampleComment);
+
+ expect(stripedComment).toBe(sampleComment);
+ });
+ });
+
+ describe('getQuickActionDescription', () => {
+ const availableQuickActions = [
+ { name: 'close', description: 'Close this issue', params: [] },
+ { name: 'title', description: 'Change title', params: [{}] },
+ { name: 'estimate', description: 'Set time estimate', params: [{}] },
+ ];
+ let notes;
+
+ beforeEach(() => {
+ notes = new Notes();
+ });
+
+ it('should return executing quick action description when note has single quick action', () => {
+ const sampleComment = '/close';
+
+ expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ 'Applying command to close this issue',
+ );
+ });
+
+ it('should return generic multiple quick action description when note has multiple quick actions', () => {
+ const sampleComment = '/close\n/title [Duplicate] Issue foobar';
+
+ expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ 'Applying multiple commands',
+ );
+ });
+
+ it('should return generic quick action description when available quick actions list is not populated', () => {
+ const sampleComment = '/close\n/title [Duplicate] Issue foobar';
+
+ expect(notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
+ });
+ });
+
+ describe('createPlaceholderNote', () => {
+ const sampleComment = 'foobar';
+ const uniqueId = 'b1234-a4567';
+ const currentUsername = 'root';
+ const currentUserFullname = 'Administrator';
+ const currentUserAvatar = 'avatar_url';
+ let notes;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+ });
+
+ it('should return constructed placeholder element for regular note based on form contents', () => {
+ const $tempNote = notes.createPlaceholderNote({
+ formContent: sampleComment,
+ uniqueId,
+ isDiscussionNote: false,
+ currentUsername,
+ currentUserFullname,
+ currentUserAvatar,
+ });
+ const $tempNoteHeader = $tempNote.find('.note-header');
+
+ expect($tempNote.prop('nodeName')).toEqual('LI');
+ expect($tempNote.attr('id')).toEqual(uniqueId);
+ expect($tempNote.hasClass('being-posted')).toBeTruthy();
+ expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
+ $tempNote.find('.timeline-icon > a, .note-header-info > a').each((i, el) => {
+ expect(el.getAttribute('href')).toEqual(`/${currentUsername}`);
+ });
+
+ expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
+ expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
+ expect(
+ $tempNoteHeader
+ .find('.d-none.d-sm-inline-block')
+ .text()
+ .trim(),
+ ).toEqual(currentUserFullname);
+
+ expect(
+ $tempNoteHeader
+ .find('.note-headline-light')
+ .text()
+ .trim(),
+ ).toEqual(`@${currentUsername}`);
+
+ expect(
+ $tempNote
+ .find('.note-body .note-text p')
+ .text()
+ .trim(),
+ ).toEqual(sampleComment);
+ });
+
+ it('should return constructed placeholder element for discussion note based on form contents', () => {
+ const $tempNote = notes.createPlaceholderNote({
+ formContent: sampleComment,
+ uniqueId,
+ isDiscussionNote: true,
+ currentUsername,
+ currentUserFullname,
+ });
+
+ expect($tempNote.prop('nodeName')).toEqual('LI');
+ expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
+ });
+
+ it('should return a escaped user name', () => {
+ const currentUserFullnameXSS = 'Foo <script>alert("XSS")</script>';
+ const $tempNote = notes.createPlaceholderNote({
+ formContent: sampleComment,
+ uniqueId,
+ isDiscussionNote: false,
+ currentUsername,
+ currentUserFullname: currentUserFullnameXSS,
+ currentUserAvatar,
+ });
+ const $tempNoteHeader = $tempNote.find('.note-header');
+
+ expect(
+ $tempNoteHeader
+ .find('.d-none.d-sm-inline-block')
+ .text()
+ .trim(),
+ ).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
+ });
+ });
+
+ describe('createPlaceholderSystemNote', () => {
+ const sampleCommandDescription = 'Applying command to close this issue';
+ const uniqueId = 'b1234-a4567';
+ let notes;
+
+ beforeEach(() => {
+ notes = new Notes('', []);
+ jest.spyOn(_, 'escape');
+ });
+
+ it('should return constructed placeholder element for system note based on form contents', () => {
+ const $tempNote = notes.createPlaceholderSystemNote({
+ formContent: sampleCommandDescription,
+ uniqueId,
+ });
+
+ expect($tempNote.prop('nodeName')).toEqual('LI');
+ expect($tempNote.attr('id')).toEqual(uniqueId);
+ expect($tempNote.hasClass('being-posted')).toBeTruthy();
+ expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
+ expect(
+ $tempNote
+ .find('.timeline-content i')
+ .text()
+ .trim(),
+ ).toEqual(sampleCommandDescription);
+ });
+ });
+
+ describe('appendFlash', () => {
+ it('shows a flash message', () => {
+ const notes = new Notes('', []);
+ notes.addFlash('Error message', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
+
+ const flash = $('.flash-alert')[0];
+ expect(document.contains(flash)).toBe(true);
+ expect(flash.parentNode.style.display).toBe('block');
+ });
+ });
+
+ describe('clearFlash', () => {
+ beforeEach(() => {
+ $(document).off('ajax:success');
+ });
+
+ it('hides visible flash message', () => {
+ const notes = new Notes('', []);
+ notes.addFlash('Error message 1', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
+ const flash = $('.flash-alert')[0];
+ notes.clearFlash();
+ expect(flash.parentNode.style.display).toBe('none');
+ expect(notes.flashContainer).toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/notes/stores/utils_spec.js b/spec/frontend/notes/stores/utils_spec.js
new file mode 100644
index 00000000000..b31b7491334
--- /dev/null
+++ b/spec/frontend/notes/stores/utils_spec.js
@@ -0,0 +1,17 @@
+import { hasQuickActions } from '~/notes/stores/utils';
+
+describe('hasQuickActions', () => {
+ it.each`
+ input | expected
+ ${'some comment'} | ${false}
+ ${'/quickaction'} | ${true}
+ ${'some comment with\n/quickaction'} | ${true}
+ `('returns $expected for $input', ({ input, expected }) => {
+ expect(hasQuickActions(input)).toBe(expected);
+ });
+
+ it('is stateless', () => {
+ expect(hasQuickActions('some comment')).toBe(hasQuickActions('some comment'));
+ expect(hasQuickActions('/quickaction')).toBe(hasQuickActions('/quickaction'));
+ });
+});
diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js
new file mode 100644
index 00000000000..a881de8fbfe
--- /dev/null
+++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js
@@ -0,0 +1,166 @@
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue';
+import store from '~/operation_settings/store';
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import createFlash from '~/flash';
+import { TEST_HOST } from 'helpers/test_constants';
+
+jest.mock('~/lib/utils/axios_utils');
+jest.mock('~/lib/utils/url_utility');
+jest.mock('~/flash');
+
+describe('operation settings external dashboard component', () => {
+ let wrapper;
+ const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`;
+ const externalDashboardUrl = `http://mock-external-domain.com/external/dashboard/url`;
+ const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`;
+ const localVue = createLocalVue();
+ const mountComponent = (shallow = true) => {
+ const config = [
+ ExternalDashboard,
+ {
+ localVue,
+ store: store({
+ operationsSettingsEndpoint,
+ externalDashboardUrl,
+ externalDashboardHelpPagePath,
+ }),
+ },
+ ];
+ wrapper = shallow ? shallowMount(...config) : mount(...config);
+ };
+
+ afterEach(() => {
+ if (wrapper.destroy) {
+ wrapper.destroy();
+ }
+ axios.patch.mockReset();
+ refreshCurrentPage.mockReset();
+ createFlash.mockReset();
+ });
+
+ it('renders header text', () => {
+ mountComponent();
+ expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard');
+ });
+
+ describe('expand/collapse button', () => {
+ it('renders as an expand button by default', () => {
+ const button = wrapper.find(GlButton);
+
+ expect(button.text()).toBe('Expand');
+ });
+ });
+
+ describe('sub-header', () => {
+ let subHeader;
+
+ beforeEach(() => {
+ mountComponent();
+ subHeader = wrapper.find('.js-section-sub-header');
+ });
+
+ it('renders descriptive text', () => {
+ expect(subHeader.text()).toContain(
+ 'Add a button to the metrics dashboard linking directly to your existing external dashboards.',
+ );
+ });
+
+ it('renders help page link', () => {
+ const link = subHeader.find(GlLink);
+
+ expect(link.text()).toBe('Learn more');
+ expect(link.attributes().href).toBe(externalDashboardHelpPagePath);
+ });
+ });
+
+ describe('form', () => {
+ describe('input label', () => {
+ let formGroup;
+
+ beforeEach(() => {
+ mountComponent();
+ formGroup = wrapper.find(GlFormGroup);
+ });
+
+ it('uses label text', () => {
+ expect(formGroup.attributes().label).toBe('Full dashboard URL');
+ });
+
+ it('uses description text', () => {
+ expect(formGroup.attributes().description).toBe(
+ 'Enter the URL of the dashboard you want to link to',
+ );
+ });
+ });
+
+ describe('input field', () => {
+ let input;
+
+ beforeEach(() => {
+ mountComponent();
+ input = wrapper.find(GlFormInput);
+ });
+
+ it('defaults to externalDashboardUrl', () => {
+ expect(input.attributes().value).toBe(externalDashboardUrl);
+ });
+
+ it('uses a placeholder', () => {
+ expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards');
+ });
+ });
+
+ describe('submit button', () => {
+ const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton);
+
+ const endpointRequest = [
+ operationsSettingsEndpoint,
+ {
+ project: {
+ metrics_setting_attributes: {
+ external_dashboard_url: externalDashboardUrl,
+ },
+ },
+ },
+ ];
+
+ it('renders button label', () => {
+ mountComponent();
+ const submit = findSubmitButton();
+ expect(submit.text()).toBe('Save Changes');
+ });
+
+ it('submits form on click', () => {
+ mountComponent(false);
+ axios.patch.mockResolvedValue();
+ findSubmitButton().trigger('click');
+
+ expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
+
+ return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
+ });
+
+ it('creates flash banner on error', () => {
+ mountComponent(false);
+ const message = 'mockErrorMessage';
+ axios.patch.mockRejectedValue({ response: { data: { message } } });
+ findSubmitButton().trigger('click');
+
+ expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
+
+ return wrapper.vm
+ .$nextTick()
+ .then(jest.runAllTicks)
+ .then(() =>
+ expect(createFlash).toHaveBeenCalledWith(
+ `There was an error saving your changes. ${message}`,
+ 'alert',
+ ),
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/operation_settings/store/mutations_spec.js b/spec/frontend/operation_settings/store/mutations_spec.js
new file mode 100644
index 00000000000..1854142c89a
--- /dev/null
+++ b/spec/frontend/operation_settings/store/mutations_spec.js
@@ -0,0 +1,19 @@
+import mutations from '~/operation_settings/store/mutations';
+import createState from '~/operation_settings/store/state';
+
+describe('operation settings mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = createState();
+ });
+
+ describe('SET_EXTERNAL_DASHBOARD_URL', () => {
+ it('sets externalDashboardUrl', () => {
+ const mockUrl = 'mockUrl';
+ mutations.SET_EXTERNAL_DASHBOARD_URL(localState, mockUrl);
+
+ expect(localState.externalDashboardUrl).toBe(mockUrl);
+ });
+ });
+});
diff --git a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
new file mode 100644
index 00000000000..7e9aec84016
--- /dev/null
+++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -0,0 +1,46 @@
+import $ from 'jquery';
+import '~/lib/utils/text_utility';
+import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
+
+describe('Abuse Reports', () => {
+ const FIXTURE = 'abuse_reports/abuse_reports_list.html';
+ const MAX_MESSAGE_LENGTH = 500;
+
+ let $messages;
+
+ const assertMaxLength = $message => {
+ expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
+ };
+ const findMessage = searchText =>
+ $messages.filter((index, element) => element.innerText.indexOf(searchText) > -1).first();
+
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ new AbuseReports(); // eslint-disable-line no-new
+ $messages = $('.abuse-reports .message');
+ });
+
+ it('should truncate long messages', () => {
+ const $longMessage = findMessage('LONG MESSAGE');
+
+ expect($longMessage.data('originalMessage')).toEqual(expect.anything());
+ assertMaxLength($longMessage);
+ });
+
+ it('should not truncate short messages', () => {
+ const $shortMessage = findMessage('SHORT MESSAGE');
+
+ expect($shortMessage.data('originalMessage')).not.toEqual(expect.anything());
+ });
+
+ it('should allow clicking a truncated message to expand and collapse the full message', () => {
+ const $longMessage = findMessage('LONG MESSAGE');
+ $longMessage.click();
+
+ expect($longMessage.data('originalMessage').length).toEqual($longMessage.text().length);
+ $longMessage.click();
+ assertMaxLength($longMessage);
+ });
+});
diff --git a/spec/frontend/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js
index efc338b36eb..6ac1e83829f 100644
--- a/spec/frontend/pages/profiles/show/emoji_menu_spec.js
+++ b/spec/frontend/pages/profiles/show/emoji_menu_spec.js
@@ -13,7 +13,7 @@ describe('EmojiMenu', () => {
let dummyEmojiList;
beforeEach(() => {
- dummySelectEmojiCallback = jasmine.createSpy('dummySelectEmojiCallback');
+ dummySelectEmojiCallback = jest.fn().mockName('dummySelectEmojiCallback');
dummyEmojiList = {
glEmojiTag() {
return dummyEmojiTag;
@@ -75,19 +75,19 @@ describe('EmojiMenu', () => {
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'one',
- jasmine.anything(),
+ expect.anything(),
'mouseenter focus',
dummyToggleButtonSelector,
'mouseenter focus',
- jasmine.anything(),
+ expect.anything(),
);
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'on',
- jasmine.anything(),
+ expect.anything(),
'click',
dummyToggleButtonSelector,
- jasmine.anything(),
+ expect.anything(),
);
});
@@ -96,10 +96,10 @@ describe('EmojiMenu', () => {
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'on',
- jasmine.anything(),
+ expect.anything(),
'click',
`.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`,
- jasmine.anything(),
+ expect.anything(),
);
});
});
diff --git a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js b/spec/frontend/performance_bar/services/performance_bar_service_spec.js
index cfec4b779e4..cfec4b779e4 100644
--- a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js
+++ b/spec/frontend/performance_bar/services/performance_bar_service_spec.js
diff --git a/spec/javascripts/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index 033bd5ccb73..033bd5ccb73 100644
--- a/spec/javascripts/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index f12950b8fce..f12950b8fce 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
diff --git a/spec/javascripts/pipelines/pipeline_store_spec.js b/spec/frontend/pipelines/pipeline_store_spec.js
index 1d5754d1f05..1d5754d1f05 100644
--- a/spec/javascripts/pipelines/pipeline_store_spec.js
+++ b/spec/frontend/pipelines/pipeline_store_spec.js
diff --git a/spec/javascripts/pipelines/pipelines_store_spec.js b/spec/frontend/pipelines/pipelines_store_spec.js
index ce21f788ed5..ce21f788ed5 100644
--- a/spec/javascripts/pipelines/pipelines_store_spec.js
+++ b/spec/frontend/pipelines/pipelines_store_spec.js
diff --git a/spec/javascripts/registry/getters_spec.js b/spec/frontend/registry/getters_spec.js
index 839aa718997..839aa718997 100644
--- a/spec/javascripts/registry/getters_spec.js
+++ b/spec/frontend/registry/getters_spec.js
diff --git a/spec/frontend/reports/components/report_item_spec.js b/spec/frontend/reports/components/report_item_spec.js
new file mode 100644
index 00000000000..bacbb399513
--- /dev/null
+++ b/spec/frontend/reports/components/report_item_spec.js
@@ -0,0 +1,33 @@
+import { shallowMount } from '@vue/test-utils';
+import { STATUS_SUCCESS } from '~/reports/constants';
+import ReportItem from '~/reports/components/report_item.vue';
+import { componentNames } from '~/reports/components/issue_body';
+
+describe('ReportItem', () => {
+ describe('showReportSectionStatusIcon', () => {
+ it('does not render CI Status Icon when showReportSectionStatusIcon is false', () => {
+ const wrapper = shallowMount(ReportItem, {
+ propsData: {
+ issue: { foo: 'bar' },
+ component: componentNames.TestIssueBody,
+ status: STATUS_SUCCESS,
+ showReportSectionStatusIcon: false,
+ },
+ });
+
+ expect(wrapper.find('issuestatusicon-stub').exists()).toBe(false);
+ });
+
+ it('shows status icon when unspecified', () => {
+ const wrapper = shallowMount(ReportItem, {
+ propsData: {
+ issue: { foo: 'bar' },
+ component: componentNames.TestIssueBody,
+ status: STATUS_SUCCESS,
+ },
+ });
+
+ expect(wrapper.find('issuestatusicon-stub').exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/reports/components/report_link_spec.js b/spec/frontend/reports/components/report_link_spec.js
index f879899e9c5..f879899e9c5 100644
--- a/spec/javascripts/reports/components/report_link_spec.js
+++ b/spec/frontend/reports/components/report_link_spec.js
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
new file mode 100644
index 00000000000..d4a3073374a
--- /dev/null
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -0,0 +1,240 @@
+import Vue from 'vue';
+import reportSection from '~/reports/components/report_section.vue';
+import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
+
+describe('Report section', () => {
+ let vm;
+ const ReportSection = Vue.extend(reportSection);
+
+ const resolvedIssues = [
+ {
+ name: 'Insecure Dependency',
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ path: 'Gemfile.lock',
+ line: 12,
+ urlPath: 'foo/Gemfile.lock',
+ },
+ ];
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ beforeEach(() => {
+ vm = mountComponent(ReportSection, {
+ component: '',
+ status: 'SUCCESS',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'foo',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ resolvedIssues,
+ hasIssues: false,
+ alwaysOpen: false,
+ });
+ });
+
+ describe('isCollapsible', () => {
+ const testMatrix = [
+ { hasIssues: false, alwaysOpen: false, isCollapsible: false },
+ { hasIssues: false, alwaysOpen: true, isCollapsible: false },
+ { hasIssues: true, alwaysOpen: false, isCollapsible: true },
+ { hasIssues: true, alwaysOpen: true, isCollapsible: false },
+ ];
+
+ testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => {
+ const issues = hasIssues ? 'has issues' : 'has no issues';
+ const open = alwaysOpen ? 'is always open' : 'is not always open';
+
+ it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => {
+ vm.hasIssues = hasIssues;
+ vm.alwaysOpen = alwaysOpen;
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.isCollapsible).toBe(isCollapsible);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('isExpanded', () => {
+ const testMatrix = [
+ { isCollapsed: false, alwaysOpen: false, isExpanded: true },
+ { isCollapsed: false, alwaysOpen: true, isExpanded: true },
+ { isCollapsed: true, alwaysOpen: false, isExpanded: false },
+ { isCollapsed: true, alwaysOpen: true, isExpanded: true },
+ ];
+
+ testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => {
+ const issues = isCollapsed ? 'is collapsed' : 'is not collapsed';
+ const open = alwaysOpen ? 'is always open' : 'is not always open';
+
+ it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => {
+ vm.isCollapsed = isCollapsed;
+ vm.alwaysOpen = alwaysOpen;
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.isExpanded).toBe(isExpanded);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+ });
+
+ describe('when it is loading', () => {
+ it('should render loading indicator', () => {
+ vm = mountComponent(ReportSection, {
+ component: '',
+ status: 'LOADING',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'foo',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ hasIssues: false,
+ });
+
+ expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report');
+ });
+ });
+
+ describe('with success status', () => {
+ beforeEach(() => {
+ vm = mountComponent(ReportSection, {
+ component: '',
+ status: 'SUCCESS',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'foo',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ resolvedIssues,
+ hasIssues: true,
+ });
+ });
+
+ it('should render provided data', () => {
+ expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
+ 'Code quality improved on 1 point and degraded on 1 point',
+ );
+
+ expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual(
+ resolvedIssues.length,
+ );
+ });
+
+ describe('toggleCollapsed', () => {
+ const hiddenCss = { display: 'none' };
+
+ it('toggles issues', done => {
+ vm.$el.querySelector('button').click();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
+
+ vm.$el.querySelector('button').click();
+ })
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('is always expanded, if always-open is set to true', done => {
+ vm.alwaysOpen = true;
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('with failed request', () => {
+ it('should render error indicator', () => {
+ vm = mountComponent(ReportSection, {
+ component: '',
+ status: 'ERROR',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'Failed to load codeclimate report',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ hasIssues: false,
+ });
+
+ expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report');
+ });
+ });
+
+ describe('with action buttons passed to the slot', () => {
+ beforeEach(() => {
+ vm = mountComponentWithSlots(ReportSection, {
+ props: {
+ status: 'SUCCESS',
+ successText: 'success',
+ hasIssues: true,
+ },
+ slots: {
+ actionButtons: ['Action!'],
+ },
+ });
+ });
+
+ it('should render the passed button', () => {
+ expect(vm.$el.textContent.trim()).toContain('Action!');
+ });
+
+ it('should still render the expand/collapse button', () => {
+ expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
+ });
+ });
+
+ describe('Success and Error slots', () => {
+ const createComponent = status => {
+ vm = mountComponentWithSlots(ReportSection, {
+ props: {
+ status,
+ hasIssues: true,
+ },
+ slots: {
+ success: ['This is a success'],
+ loading: ['This is loading'],
+ error: ['This is an error'],
+ },
+ });
+ };
+
+ it('only renders success slot when status is "SUCCESS"', () => {
+ createComponent('SUCCESS');
+
+ expect(vm.$el.textContent.trim()).toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders error slot when status is "ERROR"', () => {
+ createComponent('ERROR');
+
+ expect(vm.$el.textContent.trim()).toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders loading slot when status is "LOADING"', () => {
+ createComponent('LOADING');
+
+ expect(vm.$el.textContent.trim()).toContain('This is loading');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ });
+ });
+});
diff --git a/spec/javascripts/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js
index 1679d120db2..1679d120db2 100644
--- a/spec/javascripts/reports/store/utils_spec.js
+++ b/spec/frontend/reports/store/utils_spec.js
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
new file mode 100644
index 00000000000..068fa317a87
--- /dev/null
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -0,0 +1,44 @@
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
+
+let vm;
+
+function factory(currentPath) {
+ vm = shallowMount(Breadcrumbs, {
+ propsData: {
+ currentPath,
+ },
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ });
+}
+
+describe('Repository breadcrumbs component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ path | linkCount
+ ${'/'} | ${1}
+ ${'app'} | ${2}
+ ${'app/assets'} | ${3}
+ ${'app/assets/javascripts'} | ${4}
+ `('renders $linkCount links for path $path', ({ path, linkCount }) => {
+ factory(path);
+
+ expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount);
+ });
+
+ it('renders last link as active', () => {
+ factory('app/assets');
+
+ expect(
+ vm
+ .findAll(RouterLinkStub)
+ .at(2)
+ .attributes('aria-current'),
+ ).toEqual('page');
+ });
+});
diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
new file mode 100644
index 00000000000..1b4564303e4
--- /dev/null
+++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Repository table row component renders table row 1`] = `
+<tr
+ class="tree-item file_1"
+>
+ <td
+ class="tree-item-file-name"
+ >
+ <i
+ aria-label="file"
+ class="fa fa-fw fa-file-text-o"
+ role="img"
+ />
+
+ <a
+ class="str-truncated"
+ >
+
+ test
+
+ </a>
+
+ <!---->
+ </td>
+
+ <td
+ class="d-none d-sm-table-cell tree-commit"
+ />
+
+ <td
+ class="tree-time-ago text-right"
+ />
+</tr>
+`;
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
new file mode 100644
index 00000000000..827927e6d9a
--- /dev/null
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -0,0 +1,80 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import Table from '~/repository/components/table/index.vue';
+
+let vm;
+let $apollo;
+
+function factory(path, data = () => ({})) {
+ $apollo = {
+ query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
+ };
+
+ vm = shallowMount(Table, {
+ propsData: {
+ path,
+ },
+ mocks: {
+ $apollo,
+ },
+ });
+}
+
+describe('Repository table component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ path | ref
+ ${'/'} | ${'master'}
+ ${'app/assets'} | ${'master'}
+ ${'/'} | ${'test'}
+ `('renders table caption for $ref in $path', ({ path, ref }) => {
+ factory(path);
+
+ vm.setData({ ref });
+
+ expect(vm.find('caption').text()).toEqual(
+ `Files, directories, and submodules in the path ${path} for commit reference ${ref}`,
+ );
+ });
+
+ it('shows loading icon', () => {
+ factory('/');
+
+ vm.setData({ isLoadingFiles: true });
+
+ expect(vm.find(GlLoadingIcon).isVisible()).toBe(true);
+ });
+
+ describe('normalizeData', () => {
+ it('normalizes edge nodes', () => {
+ const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
+
+ expect(output).toEqual(['1', '2']);
+ });
+ });
+
+ describe('hasNextPage', () => {
+ it('returns undefined when hasNextPage is false', () => {
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: false } },
+ });
+
+ expect(output).toBe(undefined);
+ });
+
+ it('returns pageInfo object when hasNextPage is true', () => {
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
+ });
+
+ expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js
new file mode 100644
index 00000000000..7020055271f
--- /dev/null
+++ b/spec/frontend/repository/components/table/parent_row_spec.js
@@ -0,0 +1,64 @@
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import ParentRow from '~/repository/components/table/parent_row.vue';
+
+let vm;
+let $router;
+
+function factory(path) {
+ $router = {
+ push: jest.fn(),
+ };
+
+ vm = shallowMount(ParentRow, {
+ propsData: {
+ commitRef: 'master',
+ path,
+ },
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ mocks: {
+ $router,
+ },
+ });
+}
+
+describe('Repository parent row component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ path | to
+ ${'app'} | ${'/tree/master/'}
+ ${'app/assets'} | ${'/tree/master/app'}
+ `('renders link in $path to $to', ({ path, to }) => {
+ factory(path);
+
+ expect(vm.find(RouterLinkStub).props().to).toEqual({
+ path: to,
+ });
+ });
+
+ it('pushes new router when clicking row', () => {
+ factory('app/assets');
+
+ vm.find('td').trigger('click');
+
+ expect($router.push).toHaveBeenCalledWith({
+ path: '/tree/master/app',
+ });
+ });
+
+ // We test that it does not get called when clicking any internal
+ // links as this was causing multipe routes to get pushed
+ it('does not trigger router.push when clicking link', () => {
+ factory('app/assets');
+
+ vm.find('a').trigger('click');
+
+ expect($router.push).not.toHaveBeenCalledWith({
+ path: '/tree/master/app',
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
new file mode 100644
index 00000000000..a70dc7bb866
--- /dev/null
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -0,0 +1,101 @@
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import TableRow from '~/repository/components/table/row.vue';
+
+let vm;
+let $router;
+
+function factory(propsData = {}) {
+ $router = {
+ push: jest.fn(),
+ };
+
+ vm = shallowMount(TableRow, {
+ propsData,
+ mocks: {
+ $router,
+ },
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ });
+
+ vm.setData({ ref: 'master' });
+}
+
+describe('Repository table row component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders table row', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'file',
+ currentPath: '/',
+ });
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it.each`
+ type | component | componentName
+ ${'tree'} | ${RouterLinkStub} | ${'RouterLink'}
+ ${'file'} | ${'a'} | ${'hyperlink'}
+ ${'commit'} | ${'a'} | ${'hyperlink'}
+ `('renders a $componentName for type $type', ({ type, component }) => {
+ factory({
+ id: '1',
+ path: 'test',
+ type,
+ currentPath: '/',
+ });
+
+ expect(vm.find(component).exists()).toBe(true);
+ });
+
+ it.each`
+ type | pushes
+ ${'tree'} | ${true}
+ ${'file'} | ${false}
+ ${'commit'} | ${false}
+ `('pushes new router if type $type is tree', ({ type, pushes }) => {
+ factory({
+ id: '1',
+ path: 'test',
+ type,
+ currentPath: '/',
+ });
+
+ vm.trigger('click');
+
+ if (pushes) {
+ expect($router.push).toHaveBeenCalledWith({ path: '/tree/master/test' });
+ } else {
+ expect($router.push).not.toHaveBeenCalled();
+ }
+ });
+
+ it('renders commit ID for submodule', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'commit',
+ currentPath: '/',
+ });
+
+ expect(vm.find('.commit-sha').text()).toContain('1');
+ });
+
+ it('renders link with href', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'blob',
+ url: 'https://test.com',
+ currentPath: '/',
+ });
+
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ });
+});
diff --git a/spec/frontend/repository/router_spec.js b/spec/frontend/repository/router_spec.js
new file mode 100644
index 00000000000..f61a0ccd1e6
--- /dev/null
+++ b/spec/frontend/repository/router_spec.js
@@ -0,0 +1,23 @@
+import IndexPage from '~/repository/pages/index.vue';
+import TreePage from '~/repository/pages/tree.vue';
+import createRouter from '~/repository/router';
+
+describe('Repository router spec', () => {
+ it.each`
+ path | component | componentName
+ ${'/'} | ${IndexPage} | ${'IndexPage'}
+ ${'/tree/master'} | ${TreePage} | ${'TreePage'}
+ ${'/tree/master/app/assets'} | ${TreePage} | ${'TreePage'}
+ ${'/tree/123/app/assets'} | ${null} | ${'null'}
+ `('sets component as $componentName for path "$path"', ({ path, component }) => {
+ const router = createRouter('', 'master');
+
+ const componentsForRoute = router.getMatchedComponents(path);
+
+ expect(componentsForRoute.length).toBe(component ? 1 : 0);
+
+ if (component) {
+ expect(componentsForRoute).toContain(component);
+ }
+ });
+});
diff --git a/spec/frontend/repository/utils/icon_spec.js b/spec/frontend/repository/utils/icon_spec.js
new file mode 100644
index 00000000000..3d84705f7ea
--- /dev/null
+++ b/spec/frontend/repository/utils/icon_spec.js
@@ -0,0 +1,23 @@
+import { getIconName } from '~/repository/utils/icon';
+
+describe('getIconName', () => {
+ // Tests the returning font awesome icon name
+ // We only test one for each file type to save testing a lot of different
+ // file types
+ it.each`
+ type | path | icon
+ ${'tree'} | ${''} | ${'folder'}
+ ${'commit'} | ${''} | ${'archive'}
+ ${'file'} | ${'test.pdf'} | ${'file-pdf-o'}
+ ${'file'} | ${'test.jpg'} | ${'file-image-o'}
+ ${'file'} | ${'test.zip'} | ${'file-archive-o'}
+ ${'file'} | ${'test.mp3'} | ${'file-audio-o'}
+ ${'file'} | ${'test.flv'} | ${'file-video-o'}
+ ${'file'} | ${'test.dotx'} | ${'file-word-o'}
+ ${'file'} | ${'test.xlsb'} | ${'file-excel-o'}
+ ${'file'} | ${'test.ppam'} | ${'file-powerpoint-o'}
+ ${'file'} | ${'test.js'} | ${'file-text-o'}
+ `('returns $icon for $type with path $path', ({ type, path, icon }) => {
+ expect(getIconName(type, path)).toEqual(icon);
+ });
+});
diff --git a/spec/frontend/repository/utils/title_spec.js b/spec/frontend/repository/utils/title_spec.js
new file mode 100644
index 00000000000..c4879716fd7
--- /dev/null
+++ b/spec/frontend/repository/utils/title_spec.js
@@ -0,0 +1,15 @@
+import { setTitle } from '~/repository/utils/title';
+
+describe('setTitle', () => {
+ it.each`
+ path | title
+ ${'/'} | ${'Files'}
+ ${'app'} | ${'app'}
+ ${'app/assets'} | ${'app/assets'}
+ ${'app/assets/javascripts'} | ${'app/assets/javascripts'}
+ `('sets document title as $title for $path', ({ path, title }) => {
+ setTitle(path, 'master', 'GitLab');
+
+ expect(document.title).toEqual(`${title} · master · GitLab`);
+ });
+});
diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js
new file mode 100644
index 00000000000..62005e1981a
--- /dev/null
+++ b/spec/frontend/serverless/components/area_spec.js
@@ -0,0 +1,122 @@
+import { shallowMount } from '@vue/test-utils';
+import Area from '~/serverless/components/area.vue';
+import { mockNormalizedMetrics } from '../mock_data';
+
+describe('Area component', () => {
+ const mockWidgets = 'mockWidgets';
+ const mockGraphData = mockNormalizedMetrics;
+ let areaChart;
+
+ beforeEach(() => {
+ areaChart = shallowMount(Area, {
+ propsData: {
+ graphData: mockGraphData,
+ containerWidth: 0,
+ },
+ slots: {
+ default: mockWidgets,
+ },
+ sync: false,
+ });
+ });
+
+ afterEach(() => {
+ areaChart.destroy();
+ });
+
+ it('renders chart title', () => {
+ expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title);
+ });
+
+ it('contains graph widgets from slot', () => {
+ expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets);
+ });
+
+ describe('methods', () => {
+ describe('formatTooltipText', () => {
+ const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time;
+ const generateSeriesData = type => ({
+ seriesData: [
+ {
+ componentSubType: type,
+ value: [mockDate, 4],
+ },
+ ],
+ value: mockDate,
+ });
+
+ describe('series is of line type', () => {
+ beforeEach(() => {
+ areaChart.vm.formatTooltipText(generateSeriesData('line'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM');
+ });
+
+ it('formats tooltip content', () => {
+ expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4');
+ });
+ });
+
+ it('verify default interval value of 1', () => {
+ expect(areaChart.vm.getInterval).toBe(1);
+ });
+ });
+
+ describe('onResize', () => {
+ const mockWidth = 233;
+
+ beforeEach(() => {
+ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
+ width: mockWidth,
+ }));
+ areaChart.vm.onResize();
+ });
+
+ it('sets area chart width', () => {
+ expect(areaChart.vm.width).toBe(mockWidth);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('chartData', () => {
+ it('utilizes all data points', () => {
+ expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']);
+ expect(areaChart.vm.chartData.requests.length).toBe(2);
+ });
+
+ it('creates valid data', () => {
+ const data = areaChart.vm.chartData.requests;
+
+ expect(
+ data.filter(
+ datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number',
+ ).length,
+ ).toBe(data.length);
+ });
+ });
+
+ describe('generateSeries', () => {
+ it('utilizes correct time data', () => {
+ expect(areaChart.vm.generateSeries.data).toEqual([
+ ['2019-02-28T11:11:38.756Z', 0],
+ ['2019-02-28T11:12:38.756Z', 0],
+ ]);
+ });
+ });
+
+ describe('xAxisLabel', () => {
+ it('constructs a label for the chart x-axis', () => {
+ expect(areaChart.vm.xAxisLabel).toBe('invocations / minute');
+ });
+ });
+
+ describe('yAxisLabel', () => {
+ it('constructs a label for the chart y-axis', () => {
+ expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js
new file mode 100644
index 00000000000..0ad85e218dc
--- /dev/null
+++ b/spec/frontend/serverless/components/environment_row_spec.js
@@ -0,0 +1,74 @@
+import environmentRowComponent from '~/serverless/components/environment_row.vue';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+
+import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
+import { translate } from '~/serverless/utils';
+
+const createComponent = (localVue, env, envName) =>
+ shallowMount(environmentRowComponent, { localVue, propsData: { env, envName }, sync: false }).vm;
+
+describe('environment row component', () => {
+ describe('default global cluster case', () => {
+ let localVue;
+ let vm;
+
+ beforeEach(() => {
+ localVue = createLocalVue();
+ vm = createComponent(localVue, translate(mockServerlessFunctions.functions)['*'], '*');
+ });
+
+ afterEach(() => vm.$destroy());
+
+ it('has the correct envId', () => {
+ expect(vm.envId).toEqual('env-global');
+ });
+
+ it('is open by default', () => {
+ expect(vm.isOpenClass).toEqual({ 'is-open': true });
+ });
+
+ it('generates correct output', () => {
+ expect(vm.$el.id).toEqual('env-global');
+ expect(vm.$el.classList.contains('is-open')).toBe(true);
+ expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*');
+ });
+
+ it('opens and closes correctly', () => {
+ expect(vm.isOpen).toBe(true);
+
+ vm.toggleOpen();
+
+ expect(vm.isOpen).toBe(false);
+ });
+ });
+
+ describe('default named cluster case', () => {
+ let vm;
+ let localVue;
+
+ beforeEach(() => {
+ localVue = createLocalVue();
+ vm = createComponent(
+ localVue,
+ translate(mockServerlessFunctionsDiffEnv.functions).test,
+ 'test',
+ );
+ });
+
+ afterEach(() => vm.$destroy());
+
+ it('has the correct envId', () => {
+ expect(vm.envId).toEqual('env-test');
+ });
+
+ it('is open by default', () => {
+ expect(vm.isOpenClass).toEqual({ 'is-open': true });
+ });
+
+ it('generates correct output', () => {
+ expect(vm.$el.id).toEqual('env-test');
+ expect(vm.$el.classList.contains('is-open')).toBe(true);
+ expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test');
+ });
+ });
+});
diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js
new file mode 100644
index 00000000000..31348ff1194
--- /dev/null
+++ b/spec/frontend/serverless/components/function_details_spec.js
@@ -0,0 +1,117 @@
+import Vuex from 'vuex';
+
+import functionDetailsComponent from '~/serverless/components/function_details.vue';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { createStore } from '~/serverless/store';
+
+describe('functionDetailsComponent', () => {
+ let localVue;
+ let component;
+ let store;
+
+ beforeEach(() => {
+ localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ store = createStore();
+ });
+
+ afterEach(() => {
+ component.vm.$destroy();
+ });
+
+ describe('Verify base functionality', () => {
+ const serviceStub = {
+ name: 'test',
+ description: 'a description',
+ environment: '*',
+ url: 'http://service.com/test',
+ namespace: 'test-ns',
+ podcount: 0,
+ metricsUrl: '/metrics',
+ };
+
+ it('has a name, description, URL, and no pods loaded', () => {
+ component = shallowMount(functionDetailsComponent, {
+ localVue,
+ store,
+ propsData: {
+ func: serviceStub,
+ hasPrometheus: false,
+ clustersPath: '/clusters',
+ helpPath: '/help',
+ },
+ sync: false,
+ });
+
+ expect(
+ component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(),
+ ).toContain('test');
+
+ expect(
+ component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(),
+ ).toContain('a description');
+
+ expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain(
+ 'No pods loaded at this time.',
+ );
+ });
+
+ it('has a pods loaded', () => {
+ serviceStub.podcount = 1;
+
+ component = shallowMount(functionDetailsComponent, {
+ localVue,
+ store,
+ propsData: {
+ func: serviceStub,
+ hasPrometheus: false,
+ clustersPath: '/clusters',
+ helpPath: '/help',
+ },
+ sync: false,
+ });
+
+ expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use');
+ });
+
+ it('has multiple pods loaded', () => {
+ serviceStub.podcount = 3;
+
+ component = shallowMount(functionDetailsComponent, {
+ localVue,
+ store,
+ propsData: {
+ func: serviceStub,
+ hasPrometheus: false,
+ clustersPath: '/clusters',
+ helpPath: '/help',
+ },
+ sync: false,
+ });
+
+ expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use');
+ });
+
+ it('can support a missing description', () => {
+ serviceStub.description = null;
+
+ component = shallowMount(functionDetailsComponent, {
+ localVue,
+ store,
+ propsData: {
+ func: serviceStub,
+ hasPrometheus: false,
+ clustersPath: '/clusters',
+ helpPath: '/help',
+ },
+ sync: false,
+ });
+
+ expect(
+ component.vm.$el.querySelector('.serverless-function-description').querySelector('div')
+ .innerHTML.length,
+ ).toEqual(0);
+ });
+ });
+});
diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js
new file mode 100644
index 00000000000..979f98c4832
--- /dev/null
+++ b/spec/frontend/serverless/components/function_row_spec.js
@@ -0,0 +1,32 @@
+import functionRowComponent from '~/serverless/components/function_row.vue';
+import { shallowMount } from '@vue/test-utils';
+import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
+
+import { mockServerlessFunction } from '../mock_data';
+
+describe('functionRowComponent', () => {
+ let wrapper;
+
+ const createComponent = func => {
+ wrapper = shallowMount(functionRowComponent, { propsData: { func }, sync: false });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Parses the function details correctly', () => {
+ createComponent(mockServerlessFunction);
+
+ expect(wrapper.find('b').text()).toBe(mockServerlessFunction.name);
+ expect(wrapper.find('span').text()).toBe(mockServerlessFunction.image);
+ expect(wrapper.find(Timeago).attributes('time')).not.toBe(null);
+ });
+
+ it('handles clicks correctly', () => {
+ createComponent(mockServerlessFunction);
+ const { vm } = wrapper;
+
+ expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row
+ });
+});
diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js
new file mode 100644
index 00000000000..d8a80f8031e
--- /dev/null
+++ b/spec/frontend/serverless/components/functions_spec.js
@@ -0,0 +1,130 @@
+import Vuex from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import functionsComponent from '~/serverless/components/functions.vue';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { createStore } from '~/serverless/store';
+import EmptyState from '~/serverless/components/empty_state.vue';
+import EnvironmentRow from '~/serverless/components/environment_row.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import { mockServerlessFunctions } from '../mock_data';
+
+describe('functionsComponent', () => {
+ const statusPath = `${TEST_HOST}/statusPath`;
+
+ let component;
+ let store;
+ let localVue;
+ let axiosMock;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onGet(statusPath).reply(200);
+
+ localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ store = createStore();
+ });
+
+ afterEach(() => {
+ component.vm.$destroy();
+ axiosMock.restore();
+ });
+
+ it('should render empty state when Knative is not installed', () => {
+ store.dispatch('receiveFunctionsSuccess', { knative_installed: false });
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: '',
+ helpPath: '',
+ statusPath: '',
+ },
+ sync: false,
+ });
+
+ expect(component.find(EmptyState).exists()).toBe(true);
+ });
+
+ it('should render a loading component', () => {
+ store.dispatch('requestFunctionsLoading');
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: '',
+ helpPath: '',
+ statusPath: '',
+ },
+ sync: false,
+ });
+
+ expect(component.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('should render empty state when there is no function data', () => {
+ store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true });
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: '',
+ helpPath: '',
+ statusPath: '',
+ },
+ sync: false,
+ });
+
+ expect(
+ component.vm.$el
+ .querySelector('.empty-state, .js-empty-state')
+ .classList.contains('js-empty-state'),
+ ).toBe(true);
+
+ expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual(
+ 'No functions available',
+ );
+ });
+
+ it('should render functions and a loader when functions are partially fetched', () => {
+ store.dispatch('receiveFunctionsPartial', {
+ ...mockServerlessFunctions,
+ knative_installed: 'checking',
+ });
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: '',
+ helpPath: '',
+ statusPath: '',
+ },
+ sync: false,
+ });
+
+ expect(component.find('.js-functions-wrapper').exists()).toBe(true);
+ expect(component.find('.js-functions-loader').exists()).toBe(true);
+ });
+
+ it('should render the functions list', () => {
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: 'clustersPath',
+ helpPath: 'helpPath',
+ statusPath,
+ },
+ sync: false,
+ });
+
+ component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions);
+
+ return component.vm.$nextTick().then(() => {
+ expect(component.find(EnvironmentRow).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js
new file mode 100644
index 00000000000..5dbdccde2de
--- /dev/null
+++ b/spec/frontend/serverless/components/missing_prometheus_spec.js
@@ -0,0 +1,41 @@
+import { GlButton } from '@gitlab/ui';
+import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue';
+import { shallowMount } from '@vue/test-utils';
+
+const createComponent = missingData =>
+ shallowMount(missingPrometheusComponent, {
+ propsData: {
+ clustersPath: '/clusters',
+ helpPath: '/help',
+ missingData,
+ },
+ sync: false,
+ });
+
+describe('missingPrometheusComponent', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render missing prometheus message', () => {
+ wrapper = createComponent(false);
+ const { vm } = wrapper;
+
+ expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
+ 'Function invocation metrics require Prometheus to be installed first.',
+ );
+
+ expect(wrapper.find(GlButton).attributes('variant')).toBe('success');
+ });
+
+ it('should render no prometheus data message', () => {
+ wrapper = createComponent(true);
+ const { vm } = wrapper;
+
+ expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
+ 'Invocation metrics loading or not available at this time.',
+ );
+ });
+});
diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js
new file mode 100644
index 00000000000..d82825d8f62
--- /dev/null
+++ b/spec/frontend/serverless/components/pod_box_spec.js
@@ -0,0 +1,23 @@
+import podBoxComponent from '~/serverless/components/pod_box.vue';
+import { shallowMount } from '@vue/test-utils';
+
+const createComponent = count =>
+ shallowMount(podBoxComponent, {
+ propsData: {
+ count,
+ },
+ sync: false,
+ }).vm;
+
+describe('podBoxComponent', () => {
+ it('should render three boxes', () => {
+ const count = 3;
+ const vm = createComponent(count);
+ const rects = vm.$el.querySelectorAll('rect');
+
+ expect(rects.length).toEqual(3);
+ expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40);
+
+ vm.$destroy();
+ });
+});
diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js
new file mode 100644
index 00000000000..706441e8a8b
--- /dev/null
+++ b/spec/frontend/serverless/components/url_spec.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import urlComponent from '~/serverless/components/url.vue';
+import { shallowMount } from '@vue/test-utils';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+const createComponent = uri =>
+ shallowMount(Vue.extend(urlComponent), {
+ propsData: {
+ uri,
+ },
+ sync: false,
+ });
+
+describe('urlComponent', () => {
+ it('should render correctly', () => {
+ const uri = 'http://testfunc.apps.example.com';
+ const wrapper = createComponent(uri);
+ const { vm } = wrapper;
+
+ expect(vm.$el.classList.contains('clipboard-group')).toBe(true);
+ expect(wrapper.find(ClipboardButton).attributes('text')).toEqual(uri);
+
+ expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri);
+
+ vm.$destroy();
+ });
+});
diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js
new file mode 100644
index 00000000000..ef616ceb37f
--- /dev/null
+++ b/spec/frontend/serverless/mock_data.js
@@ -0,0 +1,142 @@
+export const mockServerlessFunctions = {
+ knative_installed: true,
+ functions: [
+ {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'A test service',
+ image: 'knative-test-container-buildtemplate',
+ },
+ {
+ name: 'testfunc2',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc2.tm-example.apps.example.com',
+ description: 'A second test service\nThis one with additional descriptions',
+ image: 'knative-test-echo-buildtemplate',
+ },
+ ],
+};
+
+export const mockServerlessFunctionsDiffEnv = {
+ knative_installed: true,
+ functions: [
+ {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'A test service',
+ image: 'knative-test-container-buildtemplate',
+ },
+ {
+ name: 'testfunc2',
+ namespace: 'tm-example',
+ environment_scope: 'test',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc2.tm-example.apps.example.com',
+ description: 'A second test service\nThis one with additional descriptions',
+ image: 'knative-test-echo-buildtemplate',
+ },
+ ],
+};
+
+export const mockServerlessFunction = {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: '3',
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'A test service',
+ image: 'knative-test-container-buildtemplate',
+};
+
+export const mockMultilineServerlessFunction = {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: '3',
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'testfunc1\nA test service line\\nWith additional services',
+ image: 'knative-test-container-buildtemplate',
+};
+
+export const mockMetrics = {
+ success: true,
+ last_update: '2019-02-28T19:11:38.926Z',
+ metrics: {
+ id: 22,
+ title: 'Knative function invocations',
+ required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'],
+ weight: 0,
+ y_label: 'Invocations',
+ queries: [
+ {
+ query_range:
+ 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))',
+ unit: 'requests',
+ label: 'invocations / minute',
+ result: [
+ {
+ metric: {},
+ values: [[1551352298.756, '0'], [1551352358.756, '0']],
+ },
+ ],
+ },
+ ],
+ },
+};
+
+export const mockNormalizedMetrics = {
+ id: 22,
+ title: 'Knative function invocations',
+ required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'],
+ weight: 0,
+ y_label: 'Invocations',
+ queries: [
+ {
+ query_range:
+ 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))',
+ unit: 'requests',
+ label: 'invocations / minute',
+ result: [
+ {
+ metric: {},
+ values: [
+ {
+ time: '2019-02-28T11:11:38.756Z',
+ value: 0,
+ },
+ {
+ time: '2019-02-28T11:12:38.756Z',
+ value: 0,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js
new file mode 100644
index 00000000000..aac57c75a4f
--- /dev/null
+++ b/spec/frontend/serverless/store/actions_spec.js
@@ -0,0 +1,90 @@
+import MockAdapter from 'axios-mock-adapter';
+import statusCodes from '~/lib/utils/http_status';
+import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions';
+import { mockServerlessFunctions, mockMetrics } from '../mock_data';
+import axios from '~/lib/utils/axios_utils';
+import testAction from '../../helpers/vuex_action_helper';
+import { adjustMetricQuery } from '../utils';
+
+describe('ServerlessActions', () => {
+ describe('fetchFunctions', () => {
+ it('should successfully fetch functions', done => {
+ const endpoint = '/functions';
+ const mock = new MockAdapter(axios);
+ mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions));
+
+ testAction(
+ fetchFunctions,
+ { functionsPath: endpoint },
+ {},
+ [],
+ [
+ { type: 'requestFunctionsLoading' },
+ { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions },
+ ],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+
+ it('should successfully retry', done => {
+ const endpoint = '/functions';
+ const mock = new MockAdapter(axios);
+ mock
+ .onGet(endpoint)
+ .reply(() => new Promise(resolve => setTimeout(() => resolve(200), Infinity)));
+
+ testAction(
+ fetchFunctions,
+ { functionsPath: endpoint },
+ {},
+ [],
+ [{ type: 'requestFunctionsLoading' }],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('fetchMetrics', () => {
+ it('should return no prometheus', done => {
+ const endpoint = '/metrics';
+ const mock = new MockAdapter(axios);
+ mock.onGet(endpoint).reply(statusCodes.NO_CONTENT);
+
+ testAction(
+ fetchMetrics,
+ { metricsPath: endpoint, hasPrometheus: false },
+ {},
+ [],
+ [{ type: 'receiveMetricsNoPrometheus' }],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+
+ it('should successfully fetch metrics', done => {
+ const endpoint = '/metrics';
+ const mock = new MockAdapter(axios);
+ mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics));
+
+ testAction(
+ fetchMetrics,
+ { metricsPath: endpoint, hasPrometheus: true },
+ {},
+ [],
+ [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+});
diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js
new file mode 100644
index 00000000000..92853fda37c
--- /dev/null
+++ b/spec/frontend/serverless/store/getters_spec.js
@@ -0,0 +1,43 @@
+import serverlessState from '~/serverless/store/state';
+import * as getters from '~/serverless/store/getters';
+import { mockServerlessFunctions } from '../mock_data';
+
+describe('Serverless Store Getters', () => {
+ let state;
+
+ beforeEach(() => {
+ state = serverlessState;
+ });
+
+ describe('hasPrometheusMissingData', () => {
+ it('should return false if Prometheus is not installed', () => {
+ state.hasPrometheus = false;
+
+ expect(getters.hasPrometheusMissingData(state)).toEqual(false);
+ });
+
+ it('should return false if Prometheus is installed and there is data', () => {
+ state.hasPrometheusData = true;
+
+ expect(getters.hasPrometheusMissingData(state)).toEqual(false);
+ });
+
+ it('should return true if Prometheus is installed and there is no data', () => {
+ state.hasPrometheus = true;
+ state.hasPrometheusData = false;
+
+ expect(getters.hasPrometheusMissingData(state)).toEqual(true);
+ });
+ });
+
+ describe('getFunctions', () => {
+ it('should translate the raw function array to group the functions per environment scope', () => {
+ state.functions = mockServerlessFunctions.functions;
+
+ const funcs = getters.getFunctions(state);
+
+ expect(Object.keys(funcs)).toContain('*');
+ expect(funcs['*'].length).toEqual(2);
+ });
+ });
+});
diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js
new file mode 100644
index 00000000000..e2771c7e5fd
--- /dev/null
+++ b/spec/frontend/serverless/store/mutations_spec.js
@@ -0,0 +1,86 @@
+import mutations from '~/serverless/store/mutations';
+import * as types from '~/serverless/store/mutation_types';
+import { mockServerlessFunctions, mockMetrics } from '../mock_data';
+
+describe('ServerlessMutations', () => {
+ describe('Functions List Mutations', () => {
+ it('should ensure loading is true', () => {
+ const state = {};
+
+ mutations[types.REQUEST_FUNCTIONS_LOADING](state);
+
+ expect(state.isLoading).toEqual(true);
+ });
+
+ it('should set proper state once functions are loaded', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions);
+
+ expect(state.isLoading).toEqual(false);
+ expect(state.hasFunctionData).toEqual(true);
+ expect(state.functions).toEqual(mockServerlessFunctions.functions);
+ });
+
+ it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, { knative_installed: true });
+
+ expect(state.isLoading).toEqual(false);
+ expect(state.hasFunctionData).toEqual(false);
+ expect(state.functions).toBe(undefined);
+ });
+
+ it('should ensure loading has stopped, and an error is raised', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error');
+
+ expect(state.isLoading).toEqual(false);
+ expect(state.hasFunctionData).toEqual(false);
+ expect(state.functions).toBe(undefined);
+ expect(state.error).not.toBe(undefined);
+ });
+ });
+
+ describe('Function Details Metrics Mutations', () => {
+ it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics);
+
+ expect(state.isLoading).toEqual(false);
+ expect(state.hasPrometheusData).toEqual(true);
+ expect(state.graphData).toEqual(mockMetrics);
+ });
+
+ it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state);
+
+ expect(state.isLoading).toEqual(false);
+ expect(state.hasPrometheusData).toEqual(false);
+ expect(state.graphData).toBe(undefined);
+ });
+
+ it('should properly indicate an error', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error');
+
+ expect(state.hasPrometheusData).toEqual(false);
+ expect(state.error).not.toBe(undefined);
+ });
+
+ it('should properly indicate when prometheus is installed', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state);
+
+ expect(state.hasPrometheus).toEqual(false);
+ expect(state.hasPrometheusData).toEqual(false);
+ });
+ });
+});
diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js
new file mode 100644
index 00000000000..5ce2e37d493
--- /dev/null
+++ b/spec/frontend/serverless/utils.js
@@ -0,0 +1,20 @@
+export const adjustMetricQuery = data => {
+ const updatedMetric = data.metrics;
+
+ const queries = data.metrics.queries.map(query => ({
+ ...query,
+ result: query.result.map(result => ({
+ ...result,
+ values: result.values.map(([timestamp, value]) => ({
+ time: new Date(timestamp * 1000).toISOString(),
+ value: Number(value),
+ })),
+ })),
+ }));
+
+ updatedMetric.queries = queries;
+ return updatedMetric;
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_buttons_spec.js
index 32da9f83112..32da9f83112 100644
--- a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential_edit_buttons_spec.js
diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js
index 369088cb258..369088cb258 100644
--- a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js
diff --git a/spec/javascripts/sidebar/lock/edit_form_spec.js b/spec/frontend/sidebar/lock/edit_form_spec.js
index ec10a999a40..ec10a999a40 100644
--- a/spec/javascripts/sidebar/lock/edit_form_spec.js
+++ b/spec/frontend/sidebar/lock/edit_form_spec.js
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index d892889b98d..7e7cc1488b8 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,23 +1,21 @@
import Vue from 'vue';
+import * as jqueryMatchers from 'custom-jquery-matchers';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';
+import { initializeTestTimeout } from './helpers/timeout';
+import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures';
-const testTimeoutInMs = 300;
-jest.setTimeout(testTimeoutInMs);
+process.on('unhandledRejection', global.promiseRejectionHandler);
-let testStartTime;
+afterEach(() =>
+ // give Promises a bit more time so they fail the right test
+ new Promise(setImmediate).then(() => {
+ // wait for pending setTimeout()s
+ jest.runAllTimers();
+ }),
+);
-// https://github.com/facebook/jest/issues/6947
-beforeEach(() => {
- testStartTime = Date.now();
-});
-
-afterEach(() => {
- const elapsedTimeInMs = Date.now() - testStartTime;
- if (elapsedTimeInMs > testTimeoutInMs) {
- throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
- }
-});
+initializeTestTimeout(process.env.CI ? 5000 : 500);
// fail tests for unmocked requests
beforeEach(done => {
@@ -31,4 +29,34 @@ beforeEach(done => {
done();
});
+Vue.config.devtools = false;
+Vue.config.productionTip = false;
+
Vue.use(Translate);
+
+// workaround for JSDOM not supporting innerText
+// see https://github.com/jsdom/jsdom/issues/1245
+Object.defineProperty(global.Element.prototype, 'innerText', {
+ get() {
+ return this.textContent;
+ },
+ configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch
+});
+
+// convenience wrapper for migration from Karma
+Object.assign(global, {
+ loadFixtures: loadHTMLFixture,
+ setFixtures: setHTMLFixture,
+
+ // The following functions fill the fixtures cache in Karma.
+ // This is not necessary in Jest because we make no Ajax request.
+ loadJSONFixtures() {},
+ preloadFixtures() {},
+});
+
+// custom-jquery-matchers was written for an old Jest version, we need to make it compatible
+Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
+ expect.extend({
+ [matcherName]: matcherFactory().compare,
+ });
+});
diff --git a/spec/javascripts/u2f/util_spec.js b/spec/frontend/u2f/util_spec.js
index 32cd6891384..32cd6891384 100644
--- a/spec/javascripts/u2f/util_spec.js
+++ b/spec/frontend/u2f/util_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js
index 16c8c939a6f..16c8c939a6f 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
index f7c2376eebf..f7c2376eebf 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
index 994d6255324..994d6255324 100644
--- a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index daf1cc2d98b..daf1cc2d98b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
new file mode 100644
index 00000000000..9ee2f88c78d
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
@@ -0,0 +1,136 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const localVue = createLocalVue();
+
+describe('Commits header component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(CommitsHeader), {
+ localVue,
+ sync: false,
+ propsData: {
+ isSquashEnabled: false,
+ targetBranch: 'master',
+ commitsCount: 5,
+ isFastForwardEnabled: false,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count');
+ const findCommitToggle = () => wrapper.find('.commit-edit-toggle');
+ const findIcon = () => wrapper.find(Icon);
+ const findCommitsCountMessage = () => wrapper.find('.commits-count-message');
+ const findTargetBranchMessage = () => wrapper.find('.label-branch');
+ const findModifyButton = () => wrapper.find('.modify-message-button');
+
+ describe('when fast-forward is enabled', () => {
+ beforeEach(() => {
+ createComponent({
+ isFastForwardEnabled: true,
+ isSquashEnabled: true,
+ });
+ });
+
+ it('has commits count message showing 1 commit', () => {
+ expect(findCommitsCountMessage().text()).toBe('1 commit');
+ });
+
+ it('has button with modify commit message', () => {
+ expect(findModifyButton().text()).toBe('Modify commit message');
+ });
+
+ it('does not have merge commit part of the message', () => {
+ expect(findHeaderWrapper().text()).not.toContain('1 merge commit');
+ });
+ });
+
+ describe('when collapsed', () => {
+ it('toggle has aria-label equal to Expand', () => {
+ createComponent();
+
+ expect(findCommitToggle().attributes('aria-label')).toBe('Expand');
+ });
+
+ it('has a chevron-right icon', () => {
+ createComponent();
+ wrapper.setData({ expanded: false });
+
+ expect(findIcon().props('name')).toBe('chevron-right');
+ });
+
+ describe('when squash is disabled', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('has commits count message showing correct amount of commits', () => {
+ expect(findCommitsCountMessage().text()).toBe('5 commits');
+ });
+
+ it('has button with modify merge commit message', () => {
+ expect(findModifyButton().text()).toBe('Modify merge commit');
+ });
+ });
+
+ describe('when squash is enabled', () => {
+ beforeEach(() => {
+ createComponent({ isSquashEnabled: true });
+ });
+
+ it('has commits count message showing one commit when squash is enabled', () => {
+ expect(findCommitsCountMessage().text()).toBe('1 commit');
+ });
+
+ it('has button with modify commit messages text', () => {
+ expect(findModifyButton().text()).toBe('Modify commit messages');
+ });
+ });
+
+ it('has correct target branch displayed', () => {
+ createComponent();
+
+ expect(findTargetBranchMessage().text()).toBe('master');
+ });
+
+ it('does has merge commit part of the message', () => {
+ expect(findHeaderWrapper().text()).toContain('1 merge commit');
+ });
+ });
+
+ describe('when expanded', () => {
+ beforeEach(() => {
+ createComponent();
+ wrapper.setData({ expanded: true });
+ });
+
+ it('toggle has aria-label equal to collapse', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
+ done();
+ });
+ });
+
+ it('has a chevron-down icon', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findIcon().props('name')).toBe('chevron-down');
+ done();
+ });
+ });
+
+ it('has a collapse text', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findHeaderWrapper().text()).toBe('Collapse');
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
new file mode 100644
index 00000000000..0f5d47b3bfe
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -0,0 +1,103 @@
+import getStateKey from '~/vue_merge_request_widget/stores/get_state_key';
+
+describe('getStateKey', () => {
+ it('should return proper state name', () => {
+ const context = {
+ mergeStatus: 'checked',
+ autoMergeEnabled: false,
+ canMerge: true,
+ onlyAllowMergeIfPipelineSucceeds: false,
+ isPipelineFailed: false,
+ hasMergeableDiscussionsState: false,
+ isPipelineBlocked: false,
+ canBeMerged: false,
+ };
+ const data = {
+ project_archived: false,
+ branch_missing: false,
+ commits_count: 2,
+ has_conflicts: false,
+ work_in_progress: false,
+ };
+ const bound = getStateKey.bind(context, data);
+
+ expect(bound()).toEqual(null);
+
+ context.canBeMerged = true;
+
+ expect(bound()).toEqual('readyToMerge');
+
+ context.canMerge = false;
+
+ expect(bound()).toEqual('notAllowedToMerge');
+
+ context.autoMergeEnabled = true;
+
+ expect(bound()).toEqual('autoMergeEnabled');
+
+ context.isSHAMismatch = true;
+
+ expect(bound()).toEqual('shaMismatch');
+
+ context.isPipelineBlocked = true;
+
+ expect(bound()).toEqual('pipelineBlocked');
+
+ context.hasMergeableDiscussionsState = true;
+
+ expect(bound()).toEqual('unresolvedDiscussions');
+
+ context.onlyAllowMergeIfPipelineSucceeds = true;
+ context.isPipelineFailed = true;
+
+ expect(bound()).toEqual('pipelineFailed');
+
+ data.work_in_progress = true;
+
+ expect(bound()).toEqual('workInProgress');
+
+ data.has_conflicts = true;
+
+ expect(bound()).toEqual('conflicts');
+
+ context.mergeStatus = 'unchecked';
+
+ expect(bound()).toEqual('checking');
+
+ data.commits_count = 0;
+
+ expect(bound()).toEqual('nothingToMerge');
+
+ data.branch_missing = true;
+
+ expect(bound()).toEqual('missingBranch');
+
+ data.project_archived = true;
+
+ expect(bound()).toEqual('archived');
+ });
+
+ it('returns rebased state key', () => {
+ const context = {
+ mergeStatus: 'checked',
+ autoMergeEnabled: false,
+ canMerge: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ isPipelineFailed: true,
+ hasMergeableDiscussionsState: false,
+ isPipelineBlocked: false,
+ canBeMerged: false,
+ shouldBeRebased: true,
+ };
+ const data = {
+ project_archived: false,
+ branch_missing: false,
+ commits_count: 2,
+ has_conflicts: false,
+ work_in_progress: false,
+ };
+ const bound = getStateKey.bind(context, data);
+
+ expect(bound()).toEqual('rebase');
+ });
+});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap
new file mode 100644
index 00000000000..add0c36a120
--- /dev/null
+++ b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Resizable Chart Container renders the component 1`] = `
+<div>
+ <div
+ class="slot"
+ >
+ <span
+ class="width"
+ >
+ 0
+ </span>
+
+ <span
+ class="height"
+ >
+ 0
+ </span>
+ </div>
+</div>
+`;
diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js
index 91208dfb31a..91208dfb31a 100644
--- a/spec/javascripts/vue_shared/components/callout_spec.js
+++ b/spec/frontend/vue_shared/components/callout_spec.js
diff --git a/spec/javascripts/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js
index 6b91a20ff76..6b91a20ff76 100644
--- a/spec/javascripts/vue_shared/components/code_block_spec.js
+++ b/spec/frontend/vue_shared/components/code_block_spec.js
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
index c4358f0d9cb..c4358f0d9cb 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/frontend/vue_shared/components/identicon_spec.js
index 0b3dbb61c96..0b3dbb61c96 100644
--- a/spec/javascripts/vue_shared/components/identicon_spec.js
+++ b/spec/frontend/vue_shared/components/identicon_spec.js
diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
new file mode 100644
index 00000000000..d1de98f4a15
--- /dev/null
+++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
@@ -0,0 +1,114 @@
+import Vue from 'vue';
+
+import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { mockAssigneesList } from '../../../../javascripts/boards/mock_data';
+
+const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
+ const Component = Vue.extend(IssueAssignees);
+
+ return mountComponent(Component, {
+ assignees,
+ cssClass,
+ });
+};
+
+describe('IssueAssigneesComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('data', () => {
+ it('returns default data props', () => {
+ expect(vm.maxVisibleAssignees).toBe(2);
+ expect(vm.maxAssigneeAvatars).toBe(3);
+ expect(vm.maxAssignees).toBe(99);
+ });
+ });
+
+ describe('computed', () => {
+ describe('countOverLimit', () => {
+ it('should return difference between assignees count and maxVisibleAssignees', () => {
+ expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees);
+ });
+ });
+
+ describe('assigneesToShow', () => {
+ it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneesToShow.length).toBe(2);
+ });
+
+ it('should return all assignees as it is when count less than maxAssigneeAvatars', () => {
+ vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
+
+ expect(vm.assigneesToShow.length).toBe(3);
+ });
+ });
+
+ describe('assigneesCounterTooltip', () => {
+ it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneesCounterTooltip).toBe('3 more assignees');
+ });
+ });
+
+ describe('shouldRenderAssigneesCounter', () => {
+ it('should return `false` when assignees count less than maxAssigneeAvatars', () => {
+ vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
+
+ expect(vm.shouldRenderAssigneesCounter).toBe(false);
+ });
+
+ it('should return `true` when assignees count more than maxAssigneeAvatars', () => {
+ expect(vm.shouldRenderAssigneesCounter).toBe(true);
+ });
+ });
+
+ describe('assigneeCounterLabel', () => {
+ it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneeCounterLabel).toBe('+3');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('avatarUrlTitle', () => {
+ it('returns string containing alt text for assignee avatar', () => {
+ expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component root element with class `issue-assignees`', () => {
+ expect(vm.$el.classList.contains('issue-assignees')).toBe(true);
+ });
+
+ it('renders assignee avatars', () => {
+ expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2);
+ });
+
+ it('renders assignee tooltips', () => {
+ const tooltipText = vm.$el
+ .querySelectorAll('.user-avatar-link')[0]
+ .querySelector('.js-assignee-tooltip').innerText;
+
+ expect(tooltipText).toContain('Assignee');
+ expect(tooltipText).toContain('Terrell Graham');
+ expect(tooltipText).toContain('@monserrate.gleichner');
+ });
+
+ it('renders additional assignees count', () => {
+ const avatarCounterEl = vm.$el.querySelector('.avatar-counter');
+
+ expect(avatarCounterEl.innerText.trim()).toBe('+3');
+ expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
new file mode 100644
index 00000000000..2e93ec412b9
--- /dev/null
+++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+
+import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
+
+import { mockMilestone } from '../../../../javascripts/boards/mock_data';
+
+const createComponent = (milestone = mockMilestone) => {
+ const Component = Vue.extend(IssueMilestone);
+
+ return mount(Component, {
+ propsData: {
+ milestone,
+ },
+ sync: false,
+ });
+};
+
+describe('IssueMilestoneComponent', () => {
+ let wrapper;
+ let vm;
+
+ beforeEach(done => {
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
+
+ Vue.nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('computed', () => {
+ describe('isMilestoneStarted', () => {
+ it('should return `false` when milestoneStart prop is not defined', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.isMilestoneStarted).toBe(false);
+ });
+
+ it('should return `true` when milestone start date is past current date', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: '1990-07-22',
+ }),
+ });
+
+ expect(wrapper.vm.isMilestoneStarted).toBe(true);
+ });
+ });
+
+ describe('isMilestonePastDue', () => {
+ it('should return `false` when milestoneDue prop is not defined', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.isMilestonePastDue).toBe(false);
+ });
+
+ it('should return `true` when milestone due is past current date', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ due_date: '1990-07-22',
+ }),
+ });
+
+ expect(wrapper.vm.isMilestonePastDue).toBe(true);
+ });
+ });
+
+ describe('milestoneDatesAbsolute', () => {
+ it('returns string containing absolute milestone due date', () => {
+ expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
+ });
+
+ it('returns string containing absolute milestone start date when due date is not present', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
+ });
+
+ it('returns empty string when both milestone start and due dates are not present', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: '',
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
+ });
+ });
+
+ describe('milestoneDatesHuman', () => {
+ it('returns string containing milestone due date when date is yet to be due', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ due_date: `${new Date().getFullYear() + 10}-01-01`,
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
+ });
+
+ it('returns string containing milestone start date when date has already started and due date is not present', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: '1990-07-22',
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
+ });
+
+ it('returns string containing milestone start date when date is yet to start and due date is not present', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: `${new Date().getFullYear() + 10}-01-01`,
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
+ });
+
+ it('returns empty string when milestone start and due dates are not present', () => {
+ wrapper.setProps({
+ milestone: Object.assign({}, mockMilestone, {
+ start_date: '',
+ due_date: '',
+ }),
+ });
+
+ expect(wrapper.vm.milestoneDatesHuman).toBe('');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component root element with class `issue-milestone-details`', () => {
+ expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
+ });
+
+ it('renders milestone icon', () => {
+ expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
+ });
+
+ it('renders milestone title', () => {
+ expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
+ });
+
+ it('renders milestone tooltip', () => {
+ expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
+ mockMilestone.title,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
new file mode 100644
index 00000000000..63880b85625
--- /dev/null
+++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
@@ -0,0 +1,65 @@
+import Vue from 'vue';
+import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+const IssueWarning = Vue.extend(issueWarning);
+
+function formatWarning(string) {
+ // Replace newlines with a space then replace multiple spaces with one space
+ return string
+ .trim()
+ .replace(/\n/g, ' ')
+ .replace(/\s\s+/g, ' ');
+}
+
+describe('Issue Warning Component', () => {
+ describe('isLocked', () => {
+ it('should render locked issue warning information', () => {
+ const props = {
+ isLocked: true,
+ lockedIssueDocsPath: 'docs/issues/locked',
+ };
+ const vm = mountComponent(IssueWarning, props);
+
+ expect(
+ vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
+ ).toMatch(/lock$/);
+ expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
+ 'This issue is locked. Only project members can comment. Learn more',
+ );
+ expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath);
+ });
+ });
+
+ describe('isConfidential', () => {
+ it('should render confidential issue warning information', () => {
+ const props = {
+ isConfidential: true,
+ confidentialIssueDocsPath: '/docs/issues/confidential',
+ };
+ const vm = mountComponent(IssueWarning, props);
+
+ expect(
+ vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
+ ).toMatch(/eye-slash$/);
+ expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
+ 'This is a confidential issue. People without permission will never get a notification. Learn more',
+ );
+ expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath);
+ });
+ });
+
+ describe('isLocked and isConfidential', () => {
+ it('should render locked and confidential issue warning information', () => {
+ const vm = mountComponent(IssueWarning, {
+ isLocked: true,
+ isConfidential: true,
+ });
+
+ expect(vm.$el.querySelector('.icon')).toBeFalsy();
+ expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
+ "This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.",
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
new file mode 100644
index 00000000000..e43d5301a50
--- /dev/null
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
@@ -0,0 +1,198 @@
+import Vue from 'vue';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import { mount, createLocalVue } from '@vue/test-utils';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
+import {
+ defaultAssignees,
+ defaultMilestone,
+} from '../../../../javascripts/vue_shared/components/issue/related_issuable_mock_data';
+
+describe('RelatedIssuableItem', () => {
+ let wrapper;
+ const props = {
+ idKey: 1,
+ displayReference: 'gitlab-org/gitlab-test#1',
+ pathIdSeparator: '#',
+ path: `${gl.TEST_HOST}/path`,
+ title: 'title',
+ confidential: true,
+ dueDate: '1990-12-31',
+ weight: 10,
+ createdAt: '2018-12-01T00:00:00.00Z',
+ milestone: defaultMilestone,
+ assignees: defaultAssignees,
+ eventNamespace: 'relatedIssue',
+ };
+ const slots = {
+ dueDate: '<div class="js-due-date-slot"></div>',
+ weight: '<div class="js-weight-slot"></div>',
+ };
+
+ beforeEach(() => {
+ const localVue = createLocalVue();
+
+ wrapper = mount(localVue.extend(RelatedIssuableItem), {
+ localVue,
+ slots,
+ sync: false,
+ propsData: props,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('contains issuable-info-container class when canReorder is false', () => {
+ expect(wrapper.props('canReorder')).toBe(false);
+ expect(wrapper.find('.issuable-info-container').exists()).toBe(true);
+ });
+
+ it('does not render token state', () => {
+ expect(wrapper.find('.text-secondary svg').exists()).toBe(false);
+ });
+
+ it('does not render remove button', () => {
+ expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false);
+ });
+
+ describe('token title', () => {
+ it('links to computedPath', () => {
+ expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path'));
+ });
+
+ it('renders confidential icon', () => {
+ expect(wrapper.find('.confidential-icon').exists()).toBe(true);
+ });
+
+ it('renders title', () => {
+ expect(wrapper.find('.item-title a').text()).toEqual(props.title);
+ });
+ });
+
+ describe('token state', () => {
+ let tokenState;
+
+ beforeEach(done => {
+ wrapper.setProps({ state: 'opened' });
+
+ Vue.nextTick(() => {
+ tokenState = wrapper.find('.issue-token-state-icon-open');
+
+ done();
+ });
+ });
+
+ it('renders if hasState', () => {
+ expect(tokenState.exists()).toBe(true);
+ });
+
+ it('renders state title', () => {
+ const stateTitle = tokenState.attributes('data-original-title');
+ const formatedCreateDate = formatDate(props.createdAt);
+
+ expect(stateTitle).toContain('<span class="bold">Opened</span>');
+
+ expect(stateTitle).toContain(`<span class="text-tertiary">${formatedCreateDate}</span>`);
+ });
+
+ it('renders aria label', () => {
+ expect(tokenState.attributes('aria-label')).toEqual('opened');
+ });
+
+ it('renders open icon when open state', () => {
+ expect(tokenState.classes('issue-token-state-icon-open')).toBe(true);
+ });
+
+ it('renders close icon when close state', done => {
+ wrapper.setProps({
+ state: 'closed',
+ closedAt: '2018-12-01T00:00:00.00Z',
+ });
+
+ Vue.nextTick(() => {
+ expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true);
+
+ done();
+ });
+ });
+ });
+
+ describe('token metadata', () => {
+ let tokenMetadata;
+
+ beforeEach(done => {
+ Vue.nextTick(() => {
+ tokenMetadata = wrapper.find('.item-meta');
+
+ done();
+ });
+ });
+
+ it('renders item path and ID', () => {
+ const pathAndID = tokenMetadata.find('.item-path-id').text();
+
+ expect(pathAndID).toContain('gitlab-org/gitlab-test');
+ expect(pathAndID).toContain('#1');
+ });
+
+ it('renders milestone icon and name', () => {
+ const milestoneIcon = tokenMetadata.find('.item-milestone svg use');
+ const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title');
+
+ expect(milestoneIcon.attributes('href')).toContain('clock');
+ expect(milestoneTitle.text()).toContain('Milestone title');
+ });
+
+ it('renders due date component', () => {
+ expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true);
+ });
+
+ it('renders weight component', () => {
+ expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true);
+ });
+ });
+
+ describe('token assignees', () => {
+ it('renders assignees avatars', () => {
+ expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBe(2);
+ expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2');
+ });
+ });
+
+ describe('remove button', () => {
+ let removeBtn;
+
+ beforeEach(done => {
+ wrapper.setProps({ canRemove: true });
+ Vue.nextTick(() => {
+ removeBtn = wrapper.find({ ref: 'removeButton' });
+
+ done();
+ });
+ });
+
+ it('renders if canRemove', () => {
+ expect(removeBtn.exists()).toBe(true);
+ });
+
+ it('renders disabled button when removeDisabled', done => {
+ wrapper.vm.removeDisabled = true;
+
+ Vue.nextTick(() => {
+ expect(removeBtn.attributes('disabled')).toEqual('disabled');
+
+ done();
+ });
+ });
+
+ it('triggers onRemoveRequest when clicked', () => {
+ removeBtn.trigger('click');
+
+ const { relatedIssueRemoveRequest } = wrapper.emitted();
+
+ expect(relatedIssueRemoveRequest.length).toBe(1);
+ expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js
index 2388660b0c2..2388660b0c2 100644
--- a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js
+++ b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
new file mode 100644
index 00000000000..3b6f67457ad
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -0,0 +1,103 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
+
+const localVue = createLocalVue();
+
+const DEFAULT_PROPS = {
+ canApply: true,
+ isApplied: false,
+ helpPagePath: 'path_to_docs',
+};
+
+describe('Suggestion Diff component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(SuggestionDiffHeader), {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...props,
+ },
+ localVue,
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findApplyButton = () => wrapper.find('.qa-apply-btn');
+ const findHeader = () => wrapper.find('.qa-suggestion-diff-header');
+ const findHelpButton = () => wrapper.find('.js-help-btn');
+ const findLoading = () => wrapper.find(GlLoadingIcon);
+
+ it('renders a suggestion header', () => {
+ createComponent();
+
+ const header = findHeader();
+
+ expect(header.exists()).toBe(true);
+ expect(header.html().includes('Suggested change')).toBe(true);
+ });
+
+ it('renders a help button', () => {
+ createComponent();
+
+ expect(findHelpButton().exists()).toBe(true);
+ });
+
+ it('renders an apply button', () => {
+ createComponent();
+
+ const applyBtn = findApplyButton();
+
+ expect(applyBtn.exists()).toBe(true);
+ expect(applyBtn.html().includes('Apply suggestion')).toBe(true);
+ });
+
+ it('does not render an apply button if `canApply` is set to false', () => {
+ createComponent({ canApply: false });
+
+ expect(findApplyButton().exists()).toBe(false);
+ });
+
+ describe('when apply suggestion is clicked', () => {
+ beforeEach(done => {
+ createComponent();
+
+ findApplyButton().vm.$emit('click');
+
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('emits apply', () => {
+ expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]);
+ });
+
+ it('hides apply button', () => {
+ expect(findApplyButton().exists()).toBe(false);
+ });
+
+ it('shows loading', () => {
+ expect(findLoading().exists()).toBe(true);
+ expect(wrapper.text()).toContain('Applying suggestion');
+ });
+
+ it('when callback of apply is called, hides loading', done => {
+ const [callback] = wrapper.emitted().apply[0];
+
+ callback();
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(findApplyButton().exists()).toBe(true);
+ expect(findLoading().exists()).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
new file mode 100644
index 00000000000..c8deac1c086
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
@@ -0,0 +1,98 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import SuggestionDiffRow from '~/vue_shared/components/markdown/suggestion_diff_row.vue';
+
+const oldLine = {
+ can_receive_suggestion: false,
+ line_code: null,
+ meta_data: null,
+ new_line: null,
+ old_line: 5,
+ rich_text: '-oldtext',
+ text: '-oldtext',
+ type: 'old',
+};
+
+const newLine = {
+ can_receive_suggestion: false,
+ line_code: null,
+ meta_data: null,
+ new_line: 6,
+ old_line: null,
+ rich_text: '-newtext',
+ text: '-newtext',
+ type: 'new',
+};
+
+describe('SuggestionDiffRow', () => {
+ let wrapper;
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = shallowMount(SuggestionDiffRow, {
+ localVue,
+ ...options,
+ });
+ };
+
+ const findOldLineWrapper = () => wrapper.find('.old_line');
+ const findNewLineWrapper = () => wrapper.find('.new_line');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders correctly', () => {
+ factory({
+ propsData: {
+ line: oldLine,
+ },
+ });
+
+ expect(wrapper.is('.line_holder')).toBe(true);
+ });
+
+ describe('when passed line has type old', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ line: oldLine,
+ },
+ });
+ });
+
+ it('has old class when line has type old', () => {
+ expect(wrapper.find('td').classes()).toContain('old');
+ });
+
+ it('has old line number rendered', () => {
+ expect(findOldLineWrapper().text()).toBe('5');
+ });
+
+ it('has no new line number rendered', () => {
+ expect(findNewLineWrapper().text()).toBe('');
+ });
+ });
+
+ describe('when passed line has type new', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ line: newLine,
+ },
+ });
+ });
+
+ it('has new class when line has type new', () => {
+ expect(wrapper.find('td').classes()).toContain('new');
+ });
+
+ it('has no old line number rendered', () => {
+ expect(findOldLineWrapper().text()).toBe('');
+ });
+
+ it('has no new line number rendered', () => {
+ expect(findNewLineWrapper().text()).toBe('6');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
new file mode 100644
index 00000000000..f1943861523
--- /dev/null
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import modalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+
+describe('modal copy button', () => {
+ const Component = Vue.extend(modalCopyButton);
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ },
+ });
+ });
+
+ describe('clipboard', () => {
+ it('should fire a `success` event on click', () => {
+ document.execCommand = jest.fn(() => true);
+ window.getSelection = jest.fn(() => ({
+ toString: jest.fn(() => 'test'),
+ removeAllRanges: jest.fn(),
+ }));
+ wrapper.trigger('click');
+ expect(wrapper.emitted().success).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ it("should propagate the clipboard error event if execCommand doesn't work", () => {
+ document.execCommand = jest.fn(() => false);
+ wrapper.trigger('click');
+ expect(wrapper.emitted().error).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js
new file mode 100644
index 00000000000..eafff7f681e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js
@@ -0,0 +1,51 @@
+import Vue from 'vue';
+import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
+import createStore from '~/notes/stores';
+import { userDataMock } from '../../../../javascripts/notes/mock_data';
+
+describe('issue placeholder system note component', () => {
+ let store;
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(issuePlaceholderNote);
+ store = createStore();
+ store.dispatch('setUserData', userDataMock);
+ vm = new Component({
+ store,
+ propsData: { note: { body: 'Foo' } },
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('user information', () => {
+ it('should render user avatar with link', () => {
+ expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual(
+ userDataMock.path,
+ );
+
+ expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
+ `${userDataMock.avatar_url}?width=40`,
+ );
+ });
+ });
+
+ describe('note content', () => {
+ it('should render note header information', () => {
+ expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual(
+ userDataMock.path,
+ );
+
+ expect(
+ vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(),
+ ).toEqual(`@${userDataMock.username}`);
+ });
+
+ it('should render note body', () => {
+ expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js
new file mode 100644
index 00000000000..976e38c15ee
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+describe('placeholder system note component', () => {
+ let PlaceholderSystemNote;
+ let vm;
+
+ beforeEach(() => {
+ PlaceholderSystemNote = Vue.extend(placeholderSystemNote);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render system note placeholder with plain text', () => {
+ vm = mountComponent(PlaceholderSystemNote, {
+ note: { body: 'This is a placeholder' },
+ });
+
+ expect(vm.$el.tagName).toEqual('LI');
+ expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual(
+ 'This is a placeholder',
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js
new file mode 100644
index 00000000000..dc66150ab8d
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js
@@ -0,0 +1,66 @@
+import Vue from 'vue';
+import issueSystemNote from '~/vue_shared/components/notes/system_note.vue';
+import createStore from '~/notes/stores';
+import initMRPopovers from '~/mr_popover/index';
+
+jest.mock('~/mr_popover/index', () => jest.fn());
+
+describe('system note component', () => {
+ let vm;
+ let props;
+
+ beforeEach(() => {
+ props = {
+ note: {
+ id: '1424',
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'path',
+ path: '/root',
+ },
+ note_html: '<p dir="auto">closed</p>',
+ system_note_icon_name: 'status_closed',
+ created_at: '2017-08-02T10:51:58.559Z',
+ },
+ };
+
+ const store = createStore();
+ store.dispatch('setTargetNoteHash', `note_${props.note.id}`);
+
+ const Component = Vue.extend(issueSystemNote);
+ vm = new Component({
+ store,
+ propsData: props,
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render a list item with correct id', () => {
+ expect(vm.$el.getAttribute('id')).toEqual(`note_${props.note.id}`);
+ });
+
+ it('should render target class is note is target note', () => {
+ expect(vm.$el.classList).toContain('target');
+ });
+
+ it('should render svg icon', () => {
+ expect(vm.$el.querySelector('.timeline-icon svg')).toBeDefined();
+ });
+
+ // Redcarpet Markdown renderer wraps text in `<p>` tags
+ // we need to strip them because they break layout of commit lists in system notes:
+ // https://gitlab.com/gitlab-org/gitlab-ce/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png
+ it('removes wrapping paragraph from note HTML', () => {
+ expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('<span>closed</span>');
+ });
+
+ it('should initMRPopovers onMount', () => {
+ expect(initMRPopovers).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
index c15635f2105..be6c58f0683 100644
--- a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
+++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
@@ -1,7 +1,7 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-describe(TimelineEntryItem.name, () => {
+describe(`TimelineEntryItem`, () => {
let wrapper;
const factory = (options = {}) => {
diff --git a/spec/javascripts/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js
index d0cb3731050..d0cb3731050 100644
--- a/spec/javascripts/vue_shared/components/pagination_links_spec.js
+++ b/spec/frontend/vue_shared/components/pagination_links_spec.js
diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js
new file mode 100644
index 00000000000..8f533e8ab24
--- /dev/null
+++ b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
+import $ from 'jquery';
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ debounceByAnimationFrame(callback) {
+ return jest.spyOn({ callback }, 'callback');
+ },
+}));
+
+describe('Resizable Chart Container', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = mount(ResizableChartContainer, {
+ attachToDocument: true,
+ scopedSlots: {
+ default: `
+ <div class="slot" slot-scope="{ width, height }">
+ <span class="width">{{width}}</span>
+ <span class="height">{{height}}</span>
+ </div>
+ `,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the component', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('updates the slot width and height props', () => {
+ const width = 1920;
+ const height = 1080;
+
+ // JSDOM mocks and sets clientWidth/clientHeight to 0 so we set manually
+ wrapper.vm.$refs.chartWrapper = { clientWidth: width, clientHeight: height };
+
+ $(document).trigger('content.resize');
+
+ return Vue.nextTick().then(() => {
+ const widthNode = wrapper.find('.slot > .width');
+ const heightNode = wrapper.find('.slot > .height');
+
+ expect(parseInt(widthNode.text(), 10)).toEqual(width);
+ expect(parseInt(heightNode.text(), 10)).toEqual(height);
+ });
+ });
+
+ it('calls onResize on manual resize', () => {
+ $(document).trigger('content.resize');
+ expect(wrapper.vm.debouncedResize).toHaveBeenCalled();
+ });
+
+ it('calls onResize on page resize', () => {
+ window.dispatchEvent(new Event('resize'));
+ expect(wrapper.vm.debouncedResize).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
new file mode 100644
index 00000000000..691ebe43d6b
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+describe('collapsedCalendarIcon', () => {
+ let vm;
+ beforeEach(() => {
+ const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon);
+ vm = mountComponent(CollapsedCalendarIcon, {
+ containerClass: 'test-class',
+ text: 'text',
+ showIcon: false,
+ });
+ });
+
+ it('should add class to container', () => {
+ expect(vm.$el.classList.contains('test-class')).toEqual(true);
+ });
+
+ it('should hide calendar icon if showIcon', () => {
+ expect(vm.$el.querySelector('.fa-calendar')).toBeNull();
+ });
+
+ it('should render text', () => {
+ expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text');
+ });
+
+ it('should emit click event when container is clicked', () => {
+ const click = jest.fn();
+ vm.$on('click', click);
+
+ vm.$el.click();
+
+ expect(click).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
new file mode 100644
index 00000000000..062ebfa01c9
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -0,0 +1,86 @@
+import Vue from 'vue';
+import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+describe('collapsedGroupedDatePicker', () => {
+ let vm;
+ beforeEach(() => {
+ const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker);
+ vm = mountComponent(CollapsedGroupedDatePicker, {
+ showToggleSidebar: true,
+ });
+ });
+
+ describe('toggleCollapse events', () => {
+ beforeEach(done => {
+ jest.spyOn(vm, 'toggleSidebar').mockImplementation(() => {});
+ vm.minDate = new Date('07/17/2016');
+ Vue.nextTick(done);
+ });
+
+ it('should emit when collapsed-calendar-icon is clicked', () => {
+ vm.$el.querySelector('.sidebar-collapsed-icon').click();
+
+ expect(vm.toggleSidebar).toHaveBeenCalled();
+ });
+ });
+
+ describe('minDate and maxDate', () => {
+ beforeEach(done => {
+ vm.minDate = new Date('07/17/2016');
+ vm.maxDate = new Date('07/17/2017');
+ Vue.nextTick(done);
+ });
+
+ it('should render both collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons.length).toEqual(2);
+ expect(icons[0].innerText.trim()).toEqual('Jul 17 2016');
+ expect(icons[1].innerText.trim()).toEqual('Jul 17 2017');
+ });
+ });
+
+ describe('minDate', () => {
+ beforeEach(done => {
+ vm.minDate = new Date('07/17/2016');
+ Vue.nextTick(done);
+ });
+
+ it('should render minDate in collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016');
+ });
+ });
+
+ describe('maxDate', () => {
+ beforeEach(done => {
+ vm.maxDate = new Date('07/17/2017');
+ Vue.nextTick(done);
+ });
+
+ it('should render maxDate in collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017');
+ });
+ });
+
+ describe('no dates', () => {
+ it('should render None', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('None');
+ });
+
+ it('should have tooltip as `Start and due date`', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+
+ expect(icons[0].dataset.originalTitle).toBe('Start and due date');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
new file mode 100644
index 00000000000..5e2bca6efc9
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
@@ -0,0 +1,121 @@
+import Vue from 'vue';
+import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+describe('sidebarDatePicker', () => {
+ let vm;
+ beforeEach(() => {
+ const SidebarDatePicker = Vue.extend(sidebarDatePicker);
+ vm = mountComponent(SidebarDatePicker, {
+ label: 'label',
+ isLoading: true,
+ });
+ });
+
+ it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
+ const toggleCollapse = jest.fn();
+ vm.$on('toggleCollapse', toggleCollapse);
+
+ vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click();
+
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+
+ it('should render collapsed-calendar-icon', () => {
+ expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined();
+ });
+
+ it('should render label', () => {
+ expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label');
+ });
+
+ it('should render loading-icon when isLoading', () => {
+ expect(vm.$el.querySelector('.fa-spin')).toBeDefined();
+ });
+
+ it('should render value when not editing', () => {
+ expect(vm.$el.querySelector('.value-content')).toBeDefined();
+ });
+
+ it('should render None if there is no selectedDate', () => {
+ expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None');
+ });
+
+ it('should render date-picker when editing', done => {
+ vm.editing = true;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.pika-label')).toBeDefined();
+ done();
+ });
+ });
+
+ describe('editable', () => {
+ beforeEach(done => {
+ vm.editable = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render edit button', () => {
+ expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit');
+ });
+
+ it('should enable editing when edit button is clicked', done => {
+ vm.isLoading = false;
+ Vue.nextTick(() => {
+ vm.$el.querySelector('.title .btn-blank').click();
+
+ expect(vm.editing).toEqual(true);
+ done();
+ });
+ });
+ });
+
+ it('should render date if selectedDate', done => {
+ vm.selectedDate = new Date('07/07/2017');
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017');
+ done();
+ });
+ });
+
+ describe('selectedDate and editable', () => {
+ beforeEach(done => {
+ vm.selectedDate = new Date('07/07/2017');
+ vm.editable = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render remove button if selectedDate and editable', () => {
+ expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove');
+ });
+
+ it('should emit saveDate when remove button is clicked', () => {
+ const saveDate = jest.fn();
+ vm.$on('saveDate', saveDate);
+
+ vm.$el.querySelector('.value-content .btn-blank').click();
+
+ expect(saveDate).toHaveBeenCalled();
+ });
+ });
+
+ describe('showToggleSidebar', () => {
+ beforeEach(done => {
+ vm.showToggleSidebar = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render toggle-sidebar when showToggleSidebar', () => {
+ expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined();
+ });
+
+ it('should emit toggleCollapse when toggle sidebar is clicked', () => {
+ const toggleCollapse = jest.fn();
+ vm.$on('toggleCollapse', toggleCollapse);
+
+ vm.$el.querySelector('.title .gutter-toggle').click();
+
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
new file mode 100644
index 00000000000..6aee616c324
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -0,0 +1,134 @@
+import Vue from 'vue';
+
+import LabelsSelect from '~/labels_select';
+import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
+
+import { mount } from '@vue/test-utils';
+import {
+ mockConfig,
+ mockLabels,
+} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const createComponent = (config = mockConfig) => {
+ const Component = Vue.extend(baseComponent);
+
+ return mount(Component, {
+ propsData: config,
+ sync: false,
+ });
+};
+
+describe('BaseComponent', () => {
+ let wrapper;
+ let vm;
+
+ beforeEach(done => {
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
+
+ Vue.nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('computed', () => {
+ describe('hiddenInputName', () => {
+ it('returns correct string when showCreate prop is `true`', () => {
+ expect(vm.hiddenInputName).toBe('issue[label_names][]');
+ });
+
+ it('returns correct string when showCreate prop is `false`', () => {
+ wrapper.setProps({ showCreate: false });
+
+ expect(vm.hiddenInputName).toBe('label_id[]');
+ });
+ });
+
+ describe('createLabelTitle', () => {
+ it('returns `Create project label` when `isProject` prop is true', () => {
+ expect(vm.createLabelTitle).toBe('Create project label');
+ });
+
+ it('return `Create group label` when `isProject` prop is false', () => {
+ wrapper.setProps({ isProject: false });
+
+ expect(vm.createLabelTitle).toBe('Create group label');
+ });
+ });
+
+ describe('manageLabelsTitle', () => {
+ it('returns `Manage project labels` when `isProject` prop is true', () => {
+ expect(vm.manageLabelsTitle).toBe('Manage project labels');
+ });
+
+ it('return `Manage group labels` when `isProject` prop is false', () => {
+ wrapper.setProps({ isProject: false });
+
+ expect(vm.manageLabelsTitle).toBe('Manage group labels');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handleClick', () => {
+ it('emits onLabelClick event with label and list of labels as params', () => {
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ vm.handleClick(mockLabels[0]);
+
+ expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]);
+ });
+ });
+
+ describe('handleCollapsedValueClick', () => {
+ it('emits toggleCollapse event on component', () => {
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ vm.handleCollapsedValueClick();
+
+ expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse');
+ });
+ });
+
+ describe('handleDropdownHidden', () => {
+ it('emits onDropdownClose event on component', () => {
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ vm.handleDropdownHidden();
+
+ expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose');
+ });
+ });
+ });
+
+ describe('mounted', () => {
+ it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => {
+ expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true);
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `block labels`', () => {
+ expect(vm.$el.classList.contains('block')).toBe(true);
+ expect(vm.$el.classList.contains('labels')).toBe(true);
+ });
+
+ it('renders `.selectbox` element', () => {
+ expect(vm.$el.querySelector('.selectbox')).not.toBeNull();
+ expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;');
+ });
+
+ it('renders `.dropdown` element', () => {
+ expect(vm.$el.querySelector('.dropdown')).not.toBeNull();
+ });
+
+ it('renders `.dropdown-menu` element', () => {
+ const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu');
+
+ expect(dropdownMenuEl).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
new file mode 100644
index 00000000000..bb33dc6ea0f
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -0,0 +1,97 @@
+import Vue from 'vue';
+
+import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import {
+ mockConfig,
+ mockLabels,
+} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const componentConfig = Object.assign({}, mockConfig, {
+ fieldName: 'label_id[]',
+ labels: mockLabels,
+ showExtraOptions: false,
+});
+
+const createComponent = (config = componentConfig) => {
+ const Component = Vue.extend(dropdownButtonComponent);
+
+ return mountComponent(Component, config);
+};
+
+describe('DropdownButtonComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('dropdownToggleText', () => {
+ it('returns text as `Label` when `labels` prop is empty array', () => {
+ const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] });
+ const vmEmptyLabels = createComponent(mockEmptyLabels);
+
+ expect(vmEmptyLabels.dropdownToggleText).toBe('Label');
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns first label name with remaining label count when `labels` prop has more than one item', () => {
+ const mockMoreLabels = Object.assign({}, componentConfig, {
+ labels: mockLabels.concat(mockLabels),
+ });
+ const vmMoreLabels = createComponent(mockMoreLabels);
+
+ expect(vmMoreLabels.dropdownToggleText).toBe(
+ `Foo Label +${mockMoreLabels.labels.length - 1} more`,
+ );
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns first label name when `labels` prop has only one item present', () => {
+ const singleLabel = Object.assign({}, componentConfig, {
+ labels: [mockLabels[0]],
+ });
+ const vmSingleLabel = createComponent(singleLabel);
+
+ expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title);
+
+ vmSingleLabel.$destroy();
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element of type `button`', () => {
+ expect(vm.$el.nodeName).toBe('BUTTON');
+ });
+
+ it('renders component container element with required data attributes', () => {
+ expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
+ expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
+ expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
+ expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
+ expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
+ expect(vm.$el.dataset.showAny).not.toBeDefined();
+ });
+
+ it('renders dropdown toggle text element', () => {
+ const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
+
+ expect(dropdownToggleTextEl).not.toBeNull();
+ expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more');
+ });
+
+ it('renders dropdown button icon', () => {
+ const dropdownIconEl = vm.$el.querySelector('i.fa');
+
+ expect(dropdownIconEl).not.toBeNull();
+ expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
new file mode 100644
index 00000000000..1c25d42682c
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -0,0 +1,107 @@
+import Vue from 'vue';
+
+import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { mockSuggestedColors } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const createComponent = headerTitle => {
+ const Component = Vue.extend(dropdownCreateLabelComponent);
+
+ return mountComponent(Component, {
+ headerTitle,
+ });
+};
+
+describe('DropdownCreateLabelComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ gon.suggested_label_colors = mockSuggestedColors;
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('created', () => {
+ it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => {
+ expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length);
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => {
+ expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true);
+ });
+
+ it('renders `Go back` button on component header', () => {
+ const backButtonEl = vm.$el.querySelector(
+ '.dropdown-title button.dropdown-title-button.dropdown-menu-back',
+ );
+
+ expect(backButtonEl).not.toBe(null);
+ expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null);
+ });
+
+ it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => {
+ const headerEl = vm.$el.querySelector('.dropdown-title');
+
+ expect(headerEl.innerText.trim()).toContain('Create new label');
+ });
+
+ it('renders component header element with value of `headerTitle` prop', () => {
+ const headerTitle = 'Create project label';
+ const vmWithHeaderTitle = createComponent(headerTitle);
+ const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title');
+
+ expect(headerEl.innerText.trim()).toContain(headerTitle);
+ vmWithHeaderTitle.$destroy();
+ });
+
+ it('renders `Close` button on component header', () => {
+ const closeButtonEl = vm.$el.querySelector(
+ '.dropdown-title button.dropdown-title-button.dropdown-menu-close',
+ );
+
+ expect(closeButtonEl).not.toBe(null);
+ expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null);
+ });
+
+ it('renders `Name new label` input element', () => {
+ expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null);
+ expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null);
+ });
+
+ it('renders suggested colors list elements', () => {
+ const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown');
+
+ expect(colorsListContainerEl).not.toBe(null);
+ expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length);
+
+ const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
+
+ expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]);
+ expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);');
+ });
+
+ it('renders color input element', () => {
+ expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null);
+ expect(
+ vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview'),
+ ).not.toBe(null);
+
+ expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null);
+ });
+
+ it('renders component action buttons', () => {
+ const createBtnEl = vm.$el.querySelector('button.js-new-label-btn');
+ const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn');
+
+ expect(createBtnEl).not.toBe(null);
+ expect(createBtnEl.innerText.trim()).toBe('Create');
+ expect(cancelBtnEl.innerText.trim()).toBe('Cancel');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
new file mode 100644
index 00000000000..989901a0012
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
@@ -0,0 +1,75 @@
+import Vue from 'vue';
+
+import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { mockConfig } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const createComponent = (
+ labelsWebUrl = mockConfig.labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
+) => {
+ const Component = Vue.extend(dropdownFooterComponent);
+
+ return mountComponent(Component, {
+ labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
+ });
+};
+
+describe('DropdownFooterComponent', () => {
+ const createLabelTitle = 'Create project label';
+ const manageLabelsTitle = 'Manage project labels';
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => {
+ const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
+
+ expect(createLabelEl).not.toBeNull();
+ expect(createLabelEl.innerText.trim()).toBe('Create new label');
+ });
+
+ it('renders link element with value of `createLabelTitle` prop', () => {
+ const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle);
+ const createLabelEl = vmWithCreateLabelTitle.$el.querySelector(
+ '.dropdown-footer-list .dropdown-toggle-page',
+ );
+
+ expect(createLabelEl.innerText.trim()).toBe(createLabelTitle);
+ vmWithCreateLabelTitle.$destroy();
+ });
+
+ it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => {
+ const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
+
+ expect(manageLabelsEl).not.toBeNull();
+ expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
+ expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
+ });
+
+ it('renders link element with value of `manageLabelsTitle` prop', () => {
+ const vmWithManageLabelsTitle = createComponent(
+ mockConfig.labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
+ );
+ const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector(
+ '.dropdown-footer-list .dropdown-external-link',
+ );
+
+ expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle);
+ vmWithManageLabelsTitle.$destroy();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
new file mode 100644
index 00000000000..c36a82e1a35
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+
+import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(dropdownHeaderComponent);
+
+ return mountComponent(Component);
+};
+
+describe('DropdownHeaderComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders header text element', () => {
+ const headerEl = vm.$el.querySelector('.dropdown-title span');
+
+ expect(headerEl.innerText.trim()).toBe('Assign labels');
+ });
+
+ it('renders `Close` button element', () => {
+ const closeBtnEl = vm.$el.querySelector(
+ '.dropdown-title button.dropdown-title-button.dropdown-menu-close',
+ );
+
+ expect(closeBtnEl).not.toBeNull();
+ expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
new file mode 100644
index 00000000000..2fffb2e495e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+
+import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(dropdownSearchInputComponent);
+
+ return mountComponent(Component);
+};
+
+describe('DropdownSearchInputComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders input element with type `search`', () => {
+ const inputEl = vm.$el.querySelector('input.dropdown-input-field');
+
+ expect(inputEl).not.toBeNull();
+ expect(inputEl.getAttribute('type')).toBe('search');
+ });
+
+ it('renders search icon element', () => {
+ expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+ });
+
+ it('renders clear search icon element', () => {
+ expect(
+ vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
+ ).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
new file mode 100644
index 00000000000..1616e657c81
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+
+import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+const createComponent = (canEdit = true) => {
+ const Component = Vue.extend(dropdownTitleComponent);
+
+ return mountComponent(Component, {
+ canEdit,
+ });
+};
+
+describe('DropdownTitleComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders title text', () => {
+ expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true);
+ expect(vm.$el.innerText.trim()).toContain('Labels');
+ });
+
+ it('renders spinner icon element', () => {
+ expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull();
+ });
+
+ it('renders `Edit` button element', () => {
+ const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle');
+
+ expect(editBtnEl).not.toBeNull();
+ expect(editBtnEl.innerText.trim()).toBe('Edit');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
new file mode 100644
index 00000000000..517f2c01c46
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -0,0 +1,97 @@
+import Vue from 'vue';
+
+import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { mockLabels } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const createComponent = (labels = mockLabels) => {
+ const Component = Vue.extend(dropdownValueCollapsedComponent);
+
+ return mountComponent(Component, {
+ labels,
+ });
+};
+
+describe('DropdownValueCollapsedComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('labelsList', () => {
+ it('returns default text when `labels` prop is empty array', () => {
+ const vmEmptyLabels = createComponent([]);
+
+ expect(vmEmptyLabels.labelsList).toBe('Labels');
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns labels names separated by coma when `labels` prop has more than one item', () => {
+ const labels = mockLabels.concat(mockLabels);
+ const vmMoreLabels = createComponent(labels);
+
+ const expectedText = labels.map(label => label.title).join(', ');
+
+ expect(vmMoreLabels.labelsList).toBe(expectedText);
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
+ const mockMoreLabels = Object.assign([], mockLabels);
+ for (let i = 0; i < 6; i += 1) {
+ mockMoreLabels.unshift(mockLabels[0]);
+ }
+
+ const vmMoreLabels = createComponent(mockMoreLabels);
+
+ const expectedText = `${mockMoreLabels
+ .slice(0, 5)
+ .map(label => label.title)
+ .join(', ')}, and ${mockMoreLabels.length - 5} more`;
+
+ expect(vmMoreLabels.labelsList).toBe(expectedText);
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns first label name when `labels` prop has only one item present', () => {
+ const text = mockLabels.map(label => label.title).join(', ');
+
+ expect(vm.labelsList).toBe(text);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handleClick', () => {
+ it('emits onValueClick event on component', () => {
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ vm.handleClick();
+
+ expect(vm.$emit).toHaveBeenCalledWith('onValueClick');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with tooltip`', () => {
+ expect(vm.$el.dataset.placement).toBe('left');
+ expect(vm.$el.dataset.container).toBe('body');
+ expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList);
+ });
+
+ it('renders tags icon element', () => {
+ expect(vm.$el.querySelector('.fa-tags')).not.toBeNull();
+ });
+
+ it('renders labels count', () => {
+ expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
new file mode 100644
index 00000000000..ec143fec5d9
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -0,0 +1,139 @@
+import Vue from 'vue';
+import $ from 'jquery';
+
+import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
+
+import mountComponent from 'helpers/vue_mount_component_helper';
+import {
+ mockConfig,
+ mockLabels,
+} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+
+const createComponent = (
+ labels = mockLabels,
+ labelFilterBasePath = mockConfig.labelFilterBasePath,
+) => {
+ const Component = Vue.extend(dropdownValueComponent);
+
+ return mountComponent(Component, {
+ labels,
+ labelFilterBasePath,
+ enableScopedLabels: true,
+ });
+};
+
+describe('DropdownValueComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isEmpty', () => {
+ it('returns true if `labels` prop is empty', () => {
+ const vmEmptyLabels = createComponent([]);
+
+ expect(vmEmptyLabels.isEmpty).toBe(true);
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns false if `labels` prop is empty', () => {
+ expect(vm.isEmpty).toBe(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('labelFilterUrl', () => {
+ it('returns URL string starting with labelFilterBasePath and encoded label.title', () => {
+ expect(
+ vm.labelFilterUrl({
+ title: 'Foo bar',
+ }),
+ ).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar');
+ });
+ });
+
+ describe('labelStyle', () => {
+ it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => {
+ const label = {
+ textColor: '#FFFFFF',
+ color: '#BADA55',
+ };
+ const styleObj = vm.labelStyle(label);
+
+ expect(styleObj.color).toBe(label.textColor);
+ expect(styleObj.backgroundColor).toBe(label.color);
+ });
+ });
+
+ describe('scopedLabelsDescription', () => {
+ it('returns html for tooltip', () => {
+ const html = vm.scopedLabelsDescription(mockLabels[1]);
+ const $el = $.parseHTML(html);
+
+ expect($el[0]).toHaveClass('scoped-label-tooltip-title');
+ expect($el[2].textContent).toEqual(mockLabels[1].description);
+ });
+ });
+
+ describe('showScopedLabels', () => {
+ it('returns true if the label is scoped label', () => {
+ expect(vm.showScopedLabels(mockLabels[1])).toBe(true);
+ });
+
+ it('returns false when label is a regular label', () => {
+ expect(vm.showScopedLabels(mockLabels[0])).toBe(false);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => {
+ expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe(
+ true,
+ );
+ });
+
+ it('render slot content inside component when `labels` prop is empty', () => {
+ const vmEmptyLabels = createComponent([]);
+
+ expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe(
+ mockConfig.emptyValueText,
+ );
+ vmEmptyLabels.$destroy();
+ });
+
+ it('renders label element with filter URL', () => {
+ expect(vm.$el.querySelector('a').getAttribute('href')).toBe(
+ '/gitlab-org/my-project/issues?label_name[]=Foo%20Label',
+ );
+ });
+
+ it('renders label element and styles based on label details', () => {
+ const labelEl = vm.$el.querySelector('a span.badge.color-label');
+
+ expect(labelEl).not.toBeNull();
+ expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);');
+ expect(labelEl.innerText.trim()).toBe(mockLabels[0].title);
+ });
+
+ describe('label is of scoped-label type', () => {
+ it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => {
+ expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull();
+ });
+
+ it('renders anchor tag containing question icon', () => {
+ const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label');
+
+ expect(anchor).not.toBeNull();
+ expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
new file mode 100644
index 00000000000..5cf25ca6f81
--- /dev/null
+++ b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+
+describe('toggleSidebar', () => {
+ let vm;
+ beforeEach(() => {
+ const ToggleSidebar = Vue.extend(toggleSidebar);
+ vm = mountComponent(ToggleSidebar, {
+ collapsed: true,
+ });
+ });
+
+ it('should render << when collapsed', () => {
+ expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true);
+ });
+
+ it('should render >> when collapsed', () => {
+ vm.collapsed = false;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true);
+ });
+ });
+
+ it('should emit toggle event when button clicked', () => {
+ const toggle = jest.fn();
+ vm.$on('toggle', toggle);
+ vm.$el.click();
+
+ expect(toggle).toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
index 536bb57b946..536bb57b946 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
diff --git a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
new file mode 100644
index 00000000000..22295721328
--- /dev/null
+++ b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
@@ -0,0 +1,136 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+
+import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue';
+
+const mockActions = [
+ {
+ title: 'Foo',
+ description: 'Some foo action',
+ },
+ {
+ title: 'Bar',
+ description: 'Some bar action',
+ },
+];
+
+const createComponent = ({
+ size = '',
+ dropdownClass = '',
+ actions = mockActions,
+ defaultAction = 0,
+}) => {
+ const localVue = createLocalVue();
+
+ return mount(DroplabDropdownButton, {
+ localVue,
+ propsData: {
+ size,
+ dropdownClass,
+ actions,
+ defaultAction,
+ },
+ });
+};
+
+describe('DroplabDropdownButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = createComponent({});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('data', () => {
+ it('contains `selectedAction` representing value of `defaultAction` prop', () => {
+ expect(wrapper.vm.selectedAction).toBe(0);
+ });
+ });
+
+ describe('computed', () => {
+ describe('selectedActionTitle', () => {
+ it('returns string containing title of selected action', () => {
+ wrapper.setData({ selectedAction: 0 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[0].title);
+
+ wrapper.setData({ selectedAction: 1 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[1].title);
+ });
+ });
+
+ describe('buttonSizeClass', () => {
+ it('returns string containing button sizing class based on `size` prop', done => {
+ const wrapperWithSize = createComponent({
+ size: 'sm',
+ });
+
+ wrapperWithSize.vm.$nextTick(() => {
+ expect(wrapperWithSize.vm.buttonSizeClass).toBe('btn-sm');
+
+ done();
+ wrapperWithSize.destroy();
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handlePrimaryActionClick', () => {
+ it('emits `onActionClick` event on component with selectedAction object as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.setData({ selectedAction: 0 });
+ wrapper.vm.handlePrimaryActionClick();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionClick', mockActions[0]);
+ });
+ });
+
+ describe('handleActionClick', () => {
+ it('emits `onActionSelect` event on component with selectedAction index as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.vm.handleActionClick(1);
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders default action button', () => {
+ const defaultButton = wrapper.findAll('.btn').at(0);
+
+ expect(defaultButton.text()).toBe(mockActions[0].title);
+ });
+
+ it('renders dropdown button', () => {
+ const dropdownButton = wrapper.findAll('.dropdown-toggle').at(0);
+
+ expect(dropdownButton.isVisible()).toBe(true);
+ });
+
+ it('renders dropdown actions', () => {
+ const dropdownActions = wrapper.findAll('.dropdown-menu li button');
+
+ Array(dropdownActions.length)
+ .fill()
+ .forEach((_, index) => {
+ const actionContent = dropdownActions.at(index).find('.description');
+
+ expect(actionContent.find('strong').text()).toBe(mockActions[index].title);
+ expect(actionContent.find('p').text()).toBe(mockActions[index].description);
+ });
+ });
+
+ it('renders divider between dropdown actions', () => {
+ const dropdownDivider = wrapper.find('.dropdown-menu .divider');
+
+ expect(dropdownDivider.isVisible()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vuex_shared/modules/modal/mutations_spec.js b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
new file mode 100644
index 00000000000..eaaf196d1ec
--- /dev/null
+++ b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
@@ -0,0 +1,49 @@
+import mutations from '~/vuex_shared/modules/modal/mutations';
+import * as types from '~/vuex_shared/modules/modal/mutation_types';
+
+describe('Vuex ModalModule mutations', () => {
+ describe(`${types.SHOW}`, () => {
+ it('sets isVisible to true', () => {
+ const state = {
+ isVisible: false,
+ };
+
+ mutations[types.SHOW](state);
+
+ expect(state).toEqual({
+ isVisible: true,
+ });
+ });
+ });
+
+ describe(`${types.HIDE}`, () => {
+ it('sets isVisible to false', () => {
+ const state = {
+ isVisible: true,
+ };
+
+ mutations[types.HIDE](state);
+
+ expect(state).toEqual({
+ isVisible: false,
+ });
+ });
+ });
+
+ describe(`${types.OPEN}`, () => {
+ it('sets data and sets isVisible to true', () => {
+ const data = { id: 7 };
+ const state = {
+ isVisible: false,
+ data: null,
+ };
+
+ mutations[types.OPEN](state, data);
+
+ expect(state).toEqual({
+ isVisible: true,
+ data,
+ });
+ });
+ });
+});
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
new file mode 100644
index 00000000000..c427893f9cc
--- /dev/null
+++ b/spec/graphql/features/authorization_spec.rb
@@ -0,0 +1,335 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Gitlab::Graphql::Authorization' do
+ set(:user) { create(:user) }
+
+ let(:permission_single) { :foo }
+ let(:permission_collection) { [:foo, :bar] }
+ let(:test_object) { double(name: 'My name') }
+ let(:query_string) { '{ object() { name } }' }
+ let(:result) { execute_query(query_type)['data'] }
+
+ subject { result['object'] }
+
+ shared_examples 'authorization with a single permission' do
+ it 'returns the protected field when user has permission' do
+ permit(permission_single)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user is not authorized' do
+ expect(subject).to be_nil
+ end
+ end
+
+ shared_examples 'authorization with a collection of permissions' do
+ it 'returns the protected field when user has all permissions' do
+ permit(*permission_collection)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user only has one of the permissions' do
+ permit(permission_collection.first)
+
+ expect(subject).to be_nil
+ end
+
+ it 'returns nil when user only has none of the permissions' do
+ expect(subject).to be_nil
+ end
+ end
+
+ before do
+ # By default, disallow all permissions.
+ allow(Ability).to receive(:allowed?).and_return(false)
+ end
+
+ describe 'Field authorizations' do
+ let(:type) { type_factory }
+
+ describe 'with a single permission' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
+ end
+ end
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'with a collection of permissions' do
+ let(:query_type) do
+ permissions = permission_collection
+ query_factory do |qt|
+ qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
+ authorize permissions
+ end
+ end
+ end
+
+ include_examples 'authorization with a collection of permissions'
+ end
+ end
+
+ describe 'Field authorizations when field is a built in type' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ end
+ end
+
+ describe 'with a single permission' do
+ let(:type) do
+ type_factory do |type|
+ type.field :name, GraphQL::STRING_TYPE, null: true, authorize: permission_single
+ end
+ end
+
+ it 'returns the protected field when user has permission' do
+ permit(permission_single)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user is not authorized' do
+ expect(subject).to eq('name' => nil)
+ end
+ end
+
+ describe 'with a collection of permissions' do
+ let(:type) do
+ permissions = permission_collection
+ type_factory do |type|
+ type.field :name, GraphQL::STRING_TYPE, null: true do
+ authorize permissions
+ end
+ end
+ end
+
+ it 'returns the protected field when user has all permissions' do
+ permit(*permission_collection)
+
+ expect(subject).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user only has one of the permissions' do
+ permit(permission_collection.first)
+
+ expect(subject).to eq('name' => nil)
+ end
+
+ it 'returns nil when user only has none of the permissions' do
+ expect(subject).to eq('name' => nil)
+ end
+ end
+ end
+
+ describe 'Type authorizations' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ end
+ end
+
+ describe 'with a single permission' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'with a collection of permissions' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_collection
+ end
+ end
+
+ include_examples 'authorization with a collection of permissions'
+ end
+ end
+
+ describe 'type and field authorizations together' do
+ let(:permission_1) { permission_collection.first }
+ let(:permission_2) { permission_collection.last }
+
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_1
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
+ end
+ end
+
+ include_examples 'authorization with a collection of permissions'
+ end
+
+ describe 'type authorizations when applied to a relay connection' do
+ let(:query_string) { '{ object() { edges { node { name } } } }' }
+ let(:second_test_object) { double(name: 'Second thing') }
+
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] }
+ end
+ end
+
+ subject { result.dig('object', 'edges') }
+
+ it 'returns only the elements visible to the user' do
+ permit(permission_single)
+
+ expect(subject.size).to eq 1
+ expect(subject.first['node']).to eq('name' => test_object.name)
+ end
+
+ it 'returns nil when user is not authorized' do
+ expect(subject).to be_empty
+ end
+
+ describe 'limiting connections with multiple objects' do
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) do
+ [test_object, second_test_object]
+ end
+ end
+ end
+
+ let(:query_string) { '{ object(first: 1) { edges { node { name } } } }' }
+
+ it 'only checks permissions for the first object' do
+ expect(Ability).to receive(:allowed?).with(user, permission_single, test_object) { true }
+ expect(Ability).not_to receive(:allowed?).with(user, permission_single, second_test_object)
+
+ expect(subject.size).to eq(1)
+ end
+ end
+ end
+
+ describe 'type authorizations when applied to a basic connection' do
+ let(:type) do
+ type_factory do |type|
+ type.authorize permission_single
+ end
+ end
+
+ let(:query_type) do
+ query_factory do |query|
+ query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
+ end
+ end
+
+ subject { result['object'].first }
+
+ include_examples 'authorization with a single permission'
+ end
+
+ describe 'Authorizations on active record relations' do
+ let!(:visible_project) { create(:project, :private) }
+ let!(:other_project) { create(:project, :private) }
+ let!(:visible_issues) { create_list(:issue, 2, project: visible_project) }
+ let!(:other_issues) { create_list(:issue, 2, project: other_project) }
+ let!(:user) { visible_project.owner }
+
+ let(:issue_type) do
+ type_factory do |type|
+ type.graphql_name 'FakeIssueType'
+ type.authorize :read_issue
+ type.field :id, GraphQL::ID_TYPE, null: false
+ end
+ end
+ let(:project_type) do |type|
+ type_factory do |type|
+ type.graphql_name 'FakeProjectType'
+ type.field :test_issues, issue_type.connection_type, null: false, resolve: -> (_, _, _) { Issue.where(project: [visible_project, other_project]) }
+ end
+ end
+ let(:query_type) do
+ query_factory do |query|
+ query.field :test_project, project_type, null: false, resolve: -> (_, _, _) { visible_project }
+ end
+ end
+ let(:query_string) do
+ <<~QRY
+ { testProject { testIssues(first: 3) { edges { node { id } } } } }
+ QRY
+ end
+
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ end
+
+ it 'renders the issues the user has access to' do
+ issue_edges = result['testProject']['testIssues']['edges']
+ issue_ids = issue_edges.map { |issue_edge| issue_edge['node']&.fetch('id') }
+
+ expect(issue_edges.size).to eq(visible_issues.size)
+ expect(issue_ids).to eq(visible_issues.map { |i| i.to_global_id.to_s })
+ end
+
+ it 'does not check access on fields that will not be rendered' do
+ expect(Ability).not_to receive(:allowed?).with(user, :read_issue, other_issues.last)
+
+ result
+ end
+ end
+
+ private
+
+ def permit(*permissions)
+ permissions.each do |permission|
+ allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true)
+ end
+ end
+
+ def type_factory
+ Class.new(Types::BaseObject) do
+ graphql_name 'TestType'
+
+ field :name, GraphQL::STRING_TYPE, null: true
+
+ yield(self) if block_given?
+ end
+ end
+
+ def query_factory
+ Class.new(Types::BaseObject) do
+ graphql_name 'TestQuery'
+
+ yield(self) if block_given?
+ end
+ end
+
+ def execute_query(query_type)
+ schema = Class.new(GraphQL::Schema) do
+ use Gitlab::Graphql::Authorize
+ use Gitlab::Graphql::Connections
+
+ query(query_type)
+ end
+
+ schema.execute(
+ query_string,
+ context: { current_user: user },
+ variables: {}
+ )
+ end
+end
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index b9ddb427e85..4076c1f824b 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabSchema do
+ let(:user) { build :user }
+
it 'uses batch loading' do
expect(field_instrumenters).to include(BatchLoader::GraphQL)
end
@@ -31,7 +35,137 @@ describe GitlabSchema do
expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection)
end
+ describe '.execute' do
+ context 'for different types of users' do
+ context 'when no context' do
+ it 'returns DEFAULT_MAX_COMPLEXITY' do
+ expect(GraphQL::Schema)
+ .to receive(:execute)
+ .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY))
+
+ described_class.execute('query')
+ end
+ end
+
+ context 'when no user' do
+ it 'returns DEFAULT_MAX_COMPLEXITY' do
+ expect(GraphQL::Schema)
+ .to receive(:execute)
+ .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY))
+
+ described_class.execute('query', context: {})
+ end
+
+ it 'returns DEFAULT_MAX_DEPTH' do
+ expect(GraphQL::Schema)
+ .to receive(:execute)
+ .with('query', hash_including(max_depth: GitlabSchema::DEFAULT_MAX_DEPTH))
+
+ described_class.execute('query', context: {})
+ end
+ end
+
+ context 'when a logged in user' do
+ it 'returns AUTHENTICATED_COMPLEXITY' do
+ expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY))
+
+ described_class.execute('query', context: { current_user: user })
+ end
+
+ it 'returns AUTHENTICATED_MAX_DEPTH' do
+ expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH))
+
+ described_class.execute('query', context: { current_user: user })
+ end
+ end
+
+ context 'when an admin user' do
+ it 'returns ADMIN_COMPLEXITY' do
+ user = build :user, :admin
+
+ expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY))
+
+ described_class.execute('query', context: { current_user: user })
+ end
+ end
+
+ context 'when max_complexity passed on the query' do
+ it 'returns what was passed on the query' do
+ expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: 1234))
+
+ described_class.execute('query', max_complexity: 1234)
+ end
+ end
+
+ context 'when max_depth passed on the query' do
+ it 'returns what was passed on the query' do
+ expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: 1234))
+
+ described_class.execute('query', max_depth: 1234)
+ end
+ end
+ end
+ end
+
+ describe '.id_from_object' do
+ it 'returns a global id' do
+ expect(described_class.id_from_object(build(:project, id: 1))).to be_a(GlobalID)
+ end
+
+ it "raises a meaningful error if a global id couldn't be generated" do
+ expect { described_class.id_from_object(build(:commit)) }
+ .to raise_error(RuntimeError, /include `GlobalID::Identification` into/i)
+ end
+ end
+
+ describe '.object_from_id' do
+ context 'for subclasses of `ApplicationRecord`' do
+ it 'returns the correct record' do
+ user = create(:user)
+
+ result = described_class.object_from_id(user.to_global_id.to_s)
+
+ expect(result.__sync).to eq(user)
+ end
+
+ it 'batchloads the queries' do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ expect do
+ [described_class.object_from_id(user1.to_global_id),
+ described_class.object_from_id(user2.to_global_id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
+ context 'for other classes' do
+ # We cannot use an anonymous class here as `GlobalID` expects `.name` not
+ # to return `nil`
+ class TestGlobalId
+ include GlobalID::Identification
+ attr_accessor :id
+
+ def initialize(id)
+ @id = id
+ end
+ end
+
+ it 'falls back to a regular find' do
+ result = TestGlobalId.new(123)
+
+ expect(TestGlobalId).to receive(:find).with("123").and_return(result)
+
+ expect(described_class.object_from_id(result.to_global_id)).to eq(result)
+ end
+ end
+
+ it 'raises the correct error on invalid input' do
+ expect { described_class.object_from_id("bogus id") }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+ end
+
def field_instrumenters
- described_class.instrumenters[:field]
+ described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
end
end
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index e3a34762b62..c162fdbbb47 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -28,4 +28,21 @@ describe Resolvers::BaseResolver do
expect(result).to eq(test: 1)
end
end
+
+ context 'when field is a connection' do
+ it 'increases complexity based on arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
+
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
+ end
+
+ it 'does not increase complexity when filtering by iids' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
+
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
+ end
+ end
end
diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
index ea7159eacf9..3140af27af5 100644
--- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
@@ -46,6 +46,14 @@ describe ResolvesPipelines do
expect(resolve_pipelines({}, {})).to be_empty
end
+ it 'increases field complexity based on arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: false, max_page_size: 1)
+
+ expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { sha: 'foo' }, 1)).to eq 4
+ expect(field.to_graphql.complexity.call({}, { sha: 'ref' }, 1)).to eq 4
+ end
+
def resolve_pipelines(args = {}, context = { current_user: current_user })
resolve(resolver, obj: project, args: args, ctx: context)
end
diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb
new file mode 100644
index 00000000000..5eb9cd06d15
--- /dev/null
+++ b/spec/graphql/resolvers/group_resolver_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::GroupResolver do
+ include GraphqlHelpers
+
+ set(:group1) { create(:group) }
+ set(:group2) { create(:group) }
+
+ describe '#resolve' do
+ it 'batch-resolves groups by full path' do
+ paths = [group1.full_path, group2.full_path]
+
+ result = batch(max_queries: 1) do
+ paths.map { |path| resolve_group(path) }
+ end
+
+ expect(result).to contain_exactly(group1, group2)
+ end
+
+ it 'resolves an unknown full_path to nil' do
+ result = batch { resolve_group('unknown/project') }
+
+ expect(result).to be_nil
+ end
+ end
+
+ def resolve_group(full_path)
+ resolve(described_class, args: { full_path: full_path })
+ end
+end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 5f9c180cbb7..798fe00de97 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -4,105 +4,127 @@ describe Resolvers::IssuesResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
- set(:project) { create(:project) }
- set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
- set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
- set(:label1) { create(:label, project: project) }
- set(:label2) { create(:label, project: project) }
-
- before do
- project.add_developer(current_user)
- create(:label_link, label: label1, target: issue1)
- create(:label_link, label: label1, target: issue2)
- create(:label_link, label: label2, target: issue2)
- end
- describe '#resolve' do
- it 'finds all issues' do
- expect(resolve_issues).to contain_exactly(issue1, issue2)
+ context "with a project" do
+ set(:project) { create(:project) }
+ set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
+ set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
+ set(:label1) { create(:label, project: project) }
+ set(:label2) { create(:label, project: project) }
+
+ before do
+ project.add_developer(current_user)
+ create(:label_link, label: label1, target: issue1)
+ create(:label_link, label: label1, target: issue2)
+ create(:label_link, label: label2, target: issue2)
end
- it 'filters by state' do
- expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
- expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
- end
+ describe '#resolve' do
+ it 'finds all issues' do
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
+ end
- it 'filters by labels' do
- expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
- expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
- end
+ it 'filters by state' do
+ expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
+ expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
+ end
- describe 'filters by created_at' do
- it 'filters by created_before' do
- expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
+ it 'filters by labels' do
+ expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
+ expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
end
- it 'filters by created_after' do
- expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
+ describe 'filters by created_at' do
+ it 'filters by created_before' do
+ expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by created_after' do
+ expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- end
- describe 'filters by updated_at' do
- it 'filters by updated_before' do
- expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
+ describe 'filters by updated_at' do
+ it 'filters by updated_before' do
+ expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by updated_after' do
+ expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- it 'filters by updated_after' do
- expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
+ describe 'filters by closed_at' do
+ let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
+
+ it 'filters by closed_before' do
+ expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
+ end
+
+ it 'filters by closed_after' do
+ expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- end
- describe 'filters by closed_at' do
- let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
+ it 'searches issues' do
+ expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
+ end
- it 'filters by closed_before' do
- expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
+ it 'sort issues' do
+ expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
end
- it 'filters by closed_after' do
- expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
+ it 'returns issues user can see' do
+ project.add_guest(current_user)
+
+ create(:issue, confidential: true)
+
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
end
- end
- it 'searches issues' do
- expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
- end
+ it 'finds a specific issue with iid' do
+ expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
+ end
- it 'sort issues' do
- expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
- end
+ it 'finds a specific issue with iids' do
+ expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
+ end
- it 'returns issues user can see' do
- project.add_guest(current_user)
+ it 'finds multiple issues with iids' do
+ expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
+ .to contain_exactly(issue1, issue2)
+ end
- create(:issue, confidential: true)
+ it 'finds only the issues within the project we are looking at' do
+ another_project = create(:project)
+ iids = [issue1, issue2].map(&:iid)
- expect(resolve_issues).to contain_exactly(issue1, issue2)
- end
+ iids.each do |iid|
+ create(:issue, project: another_project, iid: iid)
+ end
- it 'finds a specific issue with iid' do
- expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
+ expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
+ end
end
+ end
- it 'finds a specific issue with iids' do
- expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
+ context "when passing a non existent, batch loaded project" do
+ let(:project) do
+ BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ loader.call("non-existent-path", nil)
+ end
end
- it 'finds multiple issues with iids' do
- expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
- .to contain_exactly(issue1, issue2)
+ it "returns nil without breaking" do
+ expect(resolve_issues(iids: ["don't", "break"])).to be_empty
end
+ end
- it 'finds only the issues within the project we are looking at' do
- another_project = create(:project)
- iids = [issue1, issue2].map(&:iid)
-
- iids.each do |iid|
- create(:issue, project: another_project, iid: iid)
- end
+ it 'increases field complexity based on arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
- expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
- end
+ expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4
+ expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
end
def resolve_issues(args = {}, context = { current_user: current_user })
diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb
new file mode 100644
index 00000000000..e662ed127a5
--- /dev/null
+++ b/spec/graphql/resolvers/metadata_resolver_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe Resolvers::MetadataResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ it 'returns version and revision' do
+ expect(resolve(described_class)).to eq(version: Gitlab::VERSION, revision: Gitlab.revision)
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
new file mode 100644
index 00000000000..20e197e9f73
--- /dev/null
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::NamespaceProjectsResolver, :nested_groups do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+
+ context "with a group" do
+ let(:group) { create(:group) }
+ let(:namespace) { group }
+ let(:project1) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, namespace: namespace) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:nested_project) { create(:project, group: nested_group) }
+
+ before do
+ project1.add_developer(current_user)
+ project2.add_developer(current_user)
+ nested_project.add_developer(current_user)
+ end
+
+ describe '#resolve' do
+ it 'finds all projects' do
+ expect(resolve_projects).to contain_exactly(project1, project2)
+ end
+
+ it 'finds all projects including the subgroups' do
+ expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2, nested_project)
+ end
+
+ context 'with an user namespace' do
+ let(:namespace) { current_user.namespace }
+
+ it 'finds all projects' do
+ expect(resolve_projects).to contain_exactly(project1, project2)
+ end
+
+ it 'finds all projects including the subgroups' do
+ expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2)
+ end
+ end
+ end
+ end
+
+ context "when passing a non existent, batch loaded namespace" do
+ let(:namespace) do
+ BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ loader.call("non-existent-path", nil)
+ end
+ end
+
+ it "returns nil without breaking" do
+ expect(resolve_projects).to be_empty
+ end
+ end
+
+ it 'has an high complexity regardless of arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
+
+ expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24
+ expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
+ end
+
+ def resolve_projects(args = { include_subgroups: false }, context = { current_user: current_user })
+ resolve(described_class, obj: namespace, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
index d4990c6492c..4fdbb3aa43e 100644
--- a/spec/graphql/resolvers/project_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -26,6 +26,14 @@ describe Resolvers::ProjectResolver do
end
end
+ it 'does not increase complexity depending on number of load limits' do
+ field1 = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ field2 = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1)
+
+ expect(field1.to_graphql.complexity.call({}, {}, 1)).to eq 2
+ expect(field2.to_graphql.complexity.call({}, {}, 1)).to eq 2
+ end
+
def resolve_project(full_path)
resolve(described_class, args: { full_path: full_path })
end
diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb
new file mode 100644
index 00000000000..9f95b740ab1
--- /dev/null
+++ b/spec/graphql/resolvers/tree_resolver_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Resolvers::TreeResolver do
+ include GraphqlHelpers
+
+ let(:repository) { create(:project, :repository).repository }
+
+ describe '#resolve' do
+ it 'resolves to a tree' do
+ result = resolve_repository({ ref: "master" })
+
+ expect(result).to be_an_instance_of(Tree)
+ end
+
+ it 'resolve to a recursive tree' do
+ result = resolve_repository({ ref: "master", recursive: true })
+
+ expect(result.trees[4].path).to eq('files/html')
+ end
+
+ context 'when repository does not exist' do
+ it 'returns nil' do
+ allow(repository).to receive(:exists?).and_return(false)
+
+ result = resolve_repository({ ref: "master" })
+
+ expect(result).to be(nil)
+ end
+ end
+ end
+
+ def resolve_repository(args)
+ resolve(described_class, obj: repository, args: args)
+ end
+end
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
new file mode 100644
index 00000000000..0d3c3e37daf
--- /dev/null
+++ b/spec/graphql/types/base_field_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::BaseField do
+ context 'when considering complexity' do
+ let(:resolver) do
+ Class.new(described_class) do
+ def self.resolver_complexity(args, child_complexity:)
+ 2 if args[:foo]
+ end
+
+ def self.complexity_multiplier(args)
+ 0.01
+ end
+ end
+ end
+
+ it 'defaults to 1' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
+
+ expect(field.to_graphql.complexity).to eq 1
+ end
+
+ it 'has specified value' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
+
+ expect(field.to_graphql.complexity).to eq 12
+ end
+
+ context 'when field has a resolver proc' do
+ context 'and is a connection' do
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
+
+ it 'sets complexity depending on arguments for resolvers' do
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
+ end
+
+ it 'sets complexity depending on number load limits for resolvers' do
+ expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
+ end
+ end
+
+ context 'and is not a connection' do
+ it 'sets complexity as normal' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
new file mode 100644
index 00000000000..a21162adb42
--- /dev/null
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe Types::Ci::DetailedStatusType do
+ it { expect(described_class.graphql_name).to eq('DetailedStatus') }
+
+ it "has all fields" do
+ expect(described_class).to have_graphql_fields(:group, :icon, :favicon,
+ :details_path, :has_details,
+ :label, :text, :tooltip)
+ end
+end
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
new file mode 100644
index 00000000000..3dd5b602aa2
--- /dev/null
+++ b/spec/graphql/types/group_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Group'] do
+ it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
+
+ it { expect(described_class.graphql_name).to eq('Group') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_group) }
+end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index 63a07647a60..bae560829cc 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -4,4 +4,12 @@ describe GitlabSchema.types['Issue'] do
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
it { expect(described_class.graphql_name).to eq('Issue') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_issue) }
+
+ it 'has specific fields' do
+ %i[relative_position web_path web_url reference].each do |field_name|
+ expect(described_class).to have_graphql_field(field_name)
+ end
+ end
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index c369953e3ea..89c12879074 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -3,14 +3,9 @@ require 'spec_helper'
describe GitlabSchema.types['MergeRequest'] do
it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
- describe 'head pipeline' do
- it 'has a head pipeline field' do
- expect(described_class).to have_graphql_field(:head_pipeline)
- end
+ it { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
- it 'authorizes the field' do
- expect(described_class.fields['headPipeline'])
- .to require_graphql_authorizations(:read_pipeline)
- end
+ describe 'nested head pipeline' do
+ it { expect(described_class).to have_graphql_field(:head_pipeline) }
end
end
diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb
new file mode 100644
index 00000000000..55205bf5b6a
--- /dev/null
+++ b/spec/graphql/types/metadata_type_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Metadata'] do
+ it { expect(described_class.graphql_name).to eq('Metadata') }
+end
diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb
new file mode 100644
index 00000000000..f7ee79eae9f
--- /dev/null
+++ b/spec/graphql/types/milestone_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Milestone'] do
+ it { expect(described_class.graphql_name).to eq('Milestone') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+end
diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb
new file mode 100644
index 00000000000..b4144cc4121
--- /dev/null
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Namespace'] do
+ it { expect(described_class.graphql_name).to eq('Namespace') }
+
+ it { expect(described_class).to have_graphql_field(:projects) }
+end
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
index c3f84629aa2..f0fbeda202f 100644
--- a/spec/graphql/types/permission_types/issue_spec.rb
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -7,6 +7,8 @@ describe Types::PermissionTypes::Issue do
:create_note, :reopen_issue
]
- expect(described_class).to have_graphql_fields(expected_permissions)
+ expected_permissions.each do |permission|
+ expect(described_class).to have_graphql_field(permission)
+ end
end
end
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 4288412eda3..4974995b587 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -13,6 +13,8 @@ describe Types::PermissionTypes::Project do
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content
]
- expect(described_class).to have_graphql_fields(expected_permissions)
+ expected_permissions.each do |permission|
+ expect(described_class).to have_graphql_field(permission)
+ end
end
end
diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb
new file mode 100644
index 00000000000..e9feac57a36
--- /dev/null
+++ b/spec/graphql/types/project_statistics_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ProjectStatistics'] do
+ it "has all the required fields" do
+ is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
+ :build_artifacts_size, :packages_size, :commit_count,
+ :wiki_size)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index e8f1c84f8d6..cb5ac2e3cb1 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -5,19 +5,11 @@ describe GitlabSchema.types['Project'] do
it { expect(described_class.graphql_name).to eq('Project') }
+ it { expect(described_class).to require_graphql_authorizations(:read_project) }
+
describe 'nested merge request' do
it { expect(described_class).to have_graphql_field(:merge_requests) }
it { expect(described_class).to have_graphql_field(:merge_request) }
-
- it 'authorizes the merge request' do
- expect(described_class.fields['mergeRequest'])
- .to require_graphql_authorizations(:read_merge_request)
- end
-
- it 'authorizes the merge requests' do
- expect(described_class.fields['mergeRequests'])
- .to require_graphql_authorizations(:read_merge_request)
- end
end
describe 'nested issues' do
@@ -25,4 +17,8 @@ describe GitlabSchema.types['Project'] do
end
it { is_expected.to have_graphql_field(:pipelines) }
+
+ it { is_expected.to have_graphql_field(:repository) }
+
+ it { is_expected.to have_graphql_field(:statistics) }
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index e1df6f9811d..af1972a2513 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -5,7 +5,17 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query')
end
- it { is_expected.to have_graphql_fields(:project, :echo) }
+ it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata) }
+
+ describe 'namespace field' do
+ subject { described_class.fields['namespace'] }
+
+ it 'finds namespaces by full path' do
+ is_expected.to have_graphql_arguments(:full_path)
+ is_expected.to have_graphql_type(Types::NamespaceType)
+ is_expected.to have_graphql_resolver(Resolvers::NamespaceResolver)
+ end
+ end
describe 'project field' do
subject { described_class.fields['project'] }
@@ -15,9 +25,18 @@ describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::ProjectType)
is_expected.to have_graphql_resolver(Resolvers::ProjectResolver)
end
+ end
+
+ describe 'metadata field' do
+ subject { described_class.fields['metadata'] }
+
+ it 'returns metadata' do
+ is_expected.to have_graphql_type(Types::MetadataType)
+ is_expected.to have_graphql_resolver(Resolvers::MetadataResolver)
+ end
- it 'authorizes with read_project' do
- is_expected.to require_graphql_authorizations(:read_project)
+ it 'authorizes with read_instance_metadata' do
+ is_expected.to require_graphql_authorizations(:read_instance_metadata)
end
end
end
diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb
new file mode 100644
index 00000000000..8a8238f2a2a
--- /dev/null
+++ b/spec/graphql/types/repository_type_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Repository'] do
+ it { expect(described_class.graphql_name).to eq('Repository') }
+
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
+
+ it { is_expected.to have_graphql_field(:root_ref) }
+
+ it { is_expected.to have_graphql_field(:tree) }
+end
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
new file mode 100644
index 00000000000..b12e214ca84
--- /dev/null
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Tree::BlobType do
+ it { expect(described_class.graphql_name).to eq('Blob') }
+
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) }
+end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
new file mode 100644
index 00000000000..bdb3149b41c
--- /dev/null
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Tree::SubmoduleType do
+ it { expect(described_class.graphql_name).to eq('Submodule') }
+
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) }
+end
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
new file mode 100644
index 00000000000..ea1b6426034
--- /dev/null
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Tree::TreeEntryType do
+ it { expect(described_class.graphql_name).to eq('TreeEntry') }
+
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) }
+end
diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb
new file mode 100644
index 00000000000..b9c5570115e
--- /dev/null
+++ b/spec/graphql/types/tree/tree_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Tree::TreeType do
+ it { expect(described_class.graphql_name).to eq('Tree') }
+
+ it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) }
+end
diff --git a/spec/graphql/types/tree/type_enum_spec.rb b/spec/graphql/types/tree/type_enum_spec.rb
new file mode 100644
index 00000000000..4caf9e1c457
--- /dev/null
+++ b/spec/graphql/types/tree/type_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::Tree::TypeEnum do
+ it { expect(described_class.graphql_name).to eq('EntryType') }
+
+ it 'exposes all tree entry types' do
+ expect(described_class.values.keys).to include(*%w[tree blob commit])
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
new file mode 100644
index 00000000000..8134cc13eb4
--- /dev/null
+++ b/spec/graphql/types/user_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['User'] do
+ it { expect(described_class.graphql_name).to eq('User') }
+
+ it { expect(described_class).to require_graphql_authorizations(:read_user) }
+end
diff --git a/spec/haml_lint/linter/no_plain_nodes_spec.rb b/spec/haml_lint/linter/no_plain_nodes_spec.rb
new file mode 100644
index 00000000000..08deb5a4e9e
--- /dev/null
+++ b/spec/haml_lint/linter/no_plain_nodes_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'haml_lint'
+require 'haml_lint/spec'
+require Rails.root.join('haml_lint/linter/no_plain_nodes')
+
+describe HamlLint::Linter::NoPlainNodes do
+ include_context 'linter'
+
+ context 'reports when a tag has an inline plain node' do
+ let(:haml) { '%tag Hello Tanuki' }
+ let(:message) { "`Hello Tanuki` is a plain node. Please use an i18n method like `= _('Hello Tanuki')`" }
+
+ it { is_expected.to report_lint message: message }
+ end
+
+ context 'reports when a tag has multiline plain nodes' do
+ let(:haml) { <<-HAML }
+ %tag
+ Hello
+ Tanuki
+ HAML
+
+ it { is_expected.to report_lint count: 1 }
+ end
+
+ context 'reports when a tag has an inline plain node with interpolation' do
+ let(:haml) { '%tag Hello #{"Tanuki"}!' } # rubocop:disable Lint/InterpolationCheck
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'does not report when a tag has an inline script' do
+ let(:haml) { '%tag= "Hello Tanuki"' }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'does not report when a tag is empty' do
+ let(:haml) { '%tag' }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'reports multiple when a tag has multiline plain nodes split by non-text nodes' do
+ let(:haml) { <<-HAML }
+ %tag
+ Hello
+ .split-node There
+ Tanuki
+ HAML
+
+ it { is_expected.to report_lint count: 3 }
+ end
+end
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
index 8d717b968dd..a3511e078ce 100644
--- a/spec/helpers/appearances_helper_spec.rb
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -65,12 +65,10 @@ describe AppearancesHelper do
end
describe '#brand_title' do
- it 'returns the default CE title when no appearance is present' do
- allow(helper)
- .to receive(:current_appearance)
- .and_return(nil)
+ it 'returns the default title when no appearance is present' do
+ allow(helper).to receive(:current_appearance).and_return(nil)
- expect(helper.brand_title).to eq('GitLab Community Edition')
+ expect(helper.brand_title).to eq(helper.default_brand_title)
end
end
end
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index f0c2e4768ec..aae515def0c 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe AuthHelper do
@@ -97,17 +99,37 @@ describe AuthHelper do
end
end
- describe 'unlink_allowed?' do
- [:saml, :cas3].each do |provider|
- it "returns true if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be false
- end
+ describe '#link_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
- it "returns false if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be true
- end
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:link).and_return('policy_link_result')
+
+ expect(helper.link_provider_allowed?(provider)).to eq 'policy_link_result'
+ end
+ end
+
+ describe '#unlink_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
+ end
+
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:unlink).and_return('policy_unlink_result')
+
+ expect(helper.unlink_provider_allowed?(provider)).to eq 'policy_unlink_result'
end
end
end
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index 223e562238d..d2540696b17 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -29,11 +29,11 @@ describe AutoDevopsHelper do
end
context 'when the banner is disabled by feature flag' do
- it 'allows the feature flag to disable' do
+ before do
Feature.get(:auto_devops_banner_disabled).enable
-
- expect(subject).to be(false)
end
+
+ it { is_expected.to be_falsy }
end
context 'when dismissed' do
@@ -90,4 +90,136 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
end
end
+
+ describe '#badge_for_auto_devops_scope' do
+ subject { helper.badge_for_auto_devops_scope(receiver) }
+
+ context 'when receiver is a group' do
+ context 'when explicitly enabled' do
+ let(:receiver) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when explicitly disabled' do
+ let(:receiver) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is implicitly enabled' do
+ let(:receiver) { create(:group) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it { is_expected.to eq('instance enabled') }
+ end
+
+ context 'with groups', :nested_groups do
+ before do
+ receiver.update(parent: parent)
+ end
+
+ context 'when auto devops is enabled on parent' do
+ let(:parent) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops is enabled on parent group' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:parent) { create(:group, parent: root_parent) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops disabled set on parent group' do
+ let(:root_parent) { create(:group, :auto_devops_disabled) }
+ let(:parent) { create(:group, parent: root_parent) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+ end
+
+ context 'when receiver is a project' do
+ context 'when auto devops is enabled at project level' do
+ let(:receiver) { create(:project, :auto_devops) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is disabled at project level' do
+ let(:receiver) { create(:project, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is implicitly enabled' do
+ let(:receiver) { create(:project) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it { is_expected.to eq('instance enabled') }
+ end
+
+ context 'with groups', :nested_groups do
+ let(:receiver) { create(:project, :repository, namespace: group) }
+
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ context 'when auto devops is enabled on group level' do
+ let(:group) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops is enabled on root group' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:group) { create(:group, parent: root_parent) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+ end
+ end
+
+ context 'when auto devops is implicitly disabled' do
+ let(:receiver) { create(:project) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with groups', :nested_groups do
+ let(:receiver) { create(:project, :repository, namespace: group) }
+
+ context 'when auto devops is disabled on group level' do
+ let(:group) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when root group is enabled and parent disabled' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:group) { create(:group, :auto_devops_disabled, parent: root_parent) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index f709f152c92..6808ed86c9a 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -50,12 +50,20 @@ describe BlobHelper do
end
it 'returns a link with the proper route' do
+ stub_feature_flags(web_ide_default: false)
link = edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md")
end
+ it 'returns a link with a Web IDE route' do
+ link = edit_blob_button(project, 'master', 'README.md')
+
+ expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
+ end
+
it 'returns a link with the passed link_opts on the expected route' do
+ stub_feature_flags(web_ide_default: false)
link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10")
@@ -222,5 +230,18 @@ describe BlobHelper do
expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master")
end
+
+ it 'escapes special characters' do
+ Rails.application.routes.default_url_options[:script_name] = nil
+
+ expect(helper.ide_edit_path(project, "testing/#hashes", "readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/readme.md%23test")
+ expect(helper.ide_edit_path(project, "testing/#hashes", "src#/readme.md#test")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/#hashes/-/src%23/readme.md%23test")
+ end
+
+ it 'does not escape "/" character' do
+ Rails.application.routes.default_url_options[:script_name] = nil
+
+ expect(helper.ide_edit_path(project, "testing/slashes", "readme.md/")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md/")
+ end
end
end
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
new file mode 100644
index 00000000000..4ea0f76fc28
--- /dev/null
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClustersHelper do
+ describe '#has_rbac_enabled?' do
+ context 'when kubernetes platform has been created' do
+ let(:platform_kubernetes) { build_stubbed(:cluster_platform_kubernetes) }
+ let(:cluster) { build_stubbed(:cluster, :provided_by_gcp, platform_kubernetes: platform_kubernetes) }
+
+ it 'returns kubernetes platform value' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_truthy
+ end
+ end
+
+ context 'when kubernetes platform has not been created yet' do
+ let(:cluster) { build_stubbed(:cluster, :providing_by_gcp) }
+
+ it 'delegates to cluster provider' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_truthy
+ end
+
+ context 'when ABAC cluster is created' do
+ let(:provider) { build_stubbed(:cluster_provider_gcp, :abac_enabled) }
+ let(:cluster) { build_stubbed(:cluster, :providing_by_gcp, provider_gcp: provider) }
+
+ it 'delegates to cluster provider' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_falsy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 7ba24ba2956..023238ee0ae 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -21,4 +21,10 @@ describe DashboardHelper do
expect(helper.dashboard_nav_links).not_to include(:activity, :milestones)
end
end
+
+ describe '.has_start_trial?' do
+ subject { helper.has_start_trial? }
+
+ it { is_expected.to eq(false) }
+ end
end
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 23d7e41803e..e6aacb5b92b 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -1,6 +1,45 @@
require 'spec_helper'
describe EmailsHelper do
+ describe 'closure_reason_text' do
+ context 'when given a MergeRequest' do
+ let(:merge_request) { create(:merge_request) }
+ let(:merge_request_presenter) { merge_request.present }
+
+ context "and format is text" do
+ it "returns plain text" do
+ expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
+ end
+
+ context "and format is HTML" do
+ it "returns HTML" do
+ expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ end
+ end
+
+ context "and format is unknown" do
+ it "returns plain text" do
+ expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
+ end
+ end
+
+ context 'when given a String' do
+ let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
+
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ end
+ end
+
+ context 'when not given anything' do
+ it "returns empty string" do
+ expect(closure_reason_text(nil)).to eq("")
+ end
+ end
+ end
+
describe 'sanitize_name' do
context 'when name contains a valid URL string' do
it 'returns name with `.` replaced with `_` to prevent mail clients from auto-linking URLs' do
@@ -142,4 +181,58 @@ describe EmailsHelper do
end
end
end
+
+ describe 'header and footer messages' do
+ context 'when email_header_and_footer_enabled is enabled' do
+ it 'returns header and footer messages' do
+ create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(%{<div class="header-message" style=""><p>Foo</p></div>})
+ expect(html_footer_message).to eq(%{<div class="footer-message" style=""><p>Bar</p></div>})
+ expect(text_header_message).to eq('Foo')
+ expect(text_footer_message).to eq('Bar')
+ end
+ end
+
+ context 'when header and footer messages are empty' do
+ it 'returns nil' do
+ create :appearance, header_message: '', footer_message: '', email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+
+ context 'when header and footer messages are nil' do
+ it 'returns nil' do
+ create :appearance, header_message: nil, footer_message: nil, email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+ end
+
+ context 'when email_header_and_footer_enabled is disabled' do
+ it 'returns header and footer messages' do
+ create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: false
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
new file mode 100644
index 00000000000..0c8a8d2f032
--- /dev/null
+++ b/spec/helpers/environments_helper_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe EnvironmentsHelper do
+ set(:environment) { create(:environment) }
+ set(:project) { environment.project }
+ set(:user) { create(:user) }
+
+ describe '#metrics_data' do
+ before do
+ # This is so that this spec also passes in EE.
+ allow(helper).to receive(:current_user).and_return(user)
+ allow(helper).to receive(:can?).and_return(true)
+ end
+
+ let(:metrics_data) { helper.metrics_data(project, environment) }
+
+ it 'returns data' do
+ expect(metrics_data).to include(
+ 'settings-path' => edit_project_service_path(project, 'prometheus'),
+ 'clusters-path' => project_clusters_path(project),
+ 'current-environment-name': environment.name,
+ 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
+ 'empty-unable-to-connect-svg-path' => match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
+ 'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
+ 'deployment-endpoint' => project_environment_deployments_path(project, environment, format: :json),
+ 'environments-endpoint': project_environments_path(project, format: :json),
+ 'project-path' => project_path(project),
+ 'tags-path' => project_tags_path(project),
+ 'has-metrics' => "#{environment.has_metrics?}",
+ 'external-dashboard-url' => nil
+ )
+ end
+
+ context 'with metrics_setting' do
+ before do
+ create(:project_metrics_setting, project: project, external_dashboard_url: 'http://gitlab.com')
+ end
+
+ it 'adds external_dashboard_url' do
+ expect(metrics_data['external-dashboard-url']).to eq('http://gitlab.com')
+ end
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 143b28728a3..027480143bd 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -101,7 +101,7 @@ describe GitlabRoutingHelper do
it 'returns project milestone edit path when given entity parent is not a Group' do
milestone = create(:milestone, group: nil)
- expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/milestones/#{milestone.iid}/edit")
+ expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit")
end
end
end
diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb
new file mode 100644
index 00000000000..898c330c498
--- /dev/null
+++ b/spec/helpers/groups/group_members_helper_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Groups::GroupMembersHelper do
+ describe '.group_member_select_options' do
+ let(:group) { create(:group) }
+
+ before do
+ helper.instance_variable_set(:@group, group)
+ end
+
+ it 'returns an options hash' do
+ expect(helper.group_member_select_options).to include(multiple: true, scope: :all, email_user: true)
+ end
+ end
+end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 540a8674ec2..1763c46389a 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupsHelper do
@@ -227,4 +229,37 @@ describe GroupsHelper do
expect(helper.group_sidebar_links).not_to include(*cross_project_features)
end
end
+
+ describe 'parent_group_options', :nested_groups do
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, name: 'group') }
+ let(:group2) { create(:group, name: 'group2') }
+
+ before do
+ group.add_owner(current_user)
+ group2.add_owner(current_user)
+ end
+
+ it 'includes explicitly owned groups except self' do
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+
+ it 'excludes parent group' do
+ subgroup = create(:group, parent: group2)
+
+ expect(parent_group_options(subgroup)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+
+ it 'includes subgroups with inherited ownership' do
+ subgroup = create(:group, parent: group)
+
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }, { id: subgroup.id, text: subgroup.human_name }].to_json)
+ end
+
+ it 'excludes own subgroups' do
+ create(:group, parent: group2)
+
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+ end
end
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 4b40d523287..37e9ddadb8c 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -59,19 +59,19 @@ describe IconsHelper do
describe 'non existing icon' do
non_existing = 'non_existing_icon_sprite'
- it 'should raise in development mode' do
+ it 'raises in development mode' do
allow(Rails.env).to receive(:development?).and_return(true)
expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/)
end
- it 'should raise in test mode' do
+ it 'raises in test mode' do
allow(Rails.env).to receive(:test?).and_return(true)
expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/)
end
- it 'should not raise in production mode' do
+ it 'does not raise in production mode' do
allow(Rails.env).to receive(:test?).and_return(false)
allow(Rails.env).to receive(:development?).and_return(false)
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 8b82dea2524..1d1446eaa30 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssuablesHelper do
@@ -176,7 +178,7 @@ describe IssuablesHelper do
stub_commonmark_sourcepos_disabled
end
- it 'returns the correct json for an issue' do
+ it 'returns the correct data for an issue' do
issue = create(:issue, author: user, description: 'issue text')
@project = issue.project
@@ -198,7 +200,7 @@ describe IssuablesHelper do
initialDescriptionText: 'issue text',
initialTaskStatus: '0 of 0 tasks completed'
}
- expect(helper.issuable_initial_data(issue)).to eq(expected_data)
+ expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 012678db9c2..314305d7a8e 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -6,7 +6,7 @@ describe LabelsHelper do
let(:context_project) { project }
context "when asking for a #{issuables_type} link" do
- subject { show_label_issuables_link?(label, issuables_type, project: context_project) }
+ subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type, project: context_project) }
context "when #{issuables_type} are enabled for the project" do
let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) }
@@ -67,27 +67,29 @@ describe LabelsHelper do
describe 'link_to_label' do
let(:project) { create(:project) }
let(:label) { create(:label, project: project) }
+ let(:subject) { nil }
+ let(:label_presenter) { label.present(issuable_subject: subject) }
context 'without subject' do
it "uses the label's project" do
- expect(link_to_label(label)).to match %r{<a href="/#{label.project.full_path}/issues\?label_name%5B%5D=#{label.name}">.*</a>}
+ expect(link_to_label(label_presenter)).to match %r{<a href="/#{label.project.full_path}/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
context 'with a project as subject' do
let(:namespace) { build(:namespace, name: 'foo3') }
- let(:another_project) { build(:project, namespace: namespace, name: 'bar3') }
+ let(:subject) { build(:project, namespace: namespace, name: 'bar3') }
it 'links to project issues page' do
- expect(link_to_label(label, subject: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name%5B%5D=#{label.name}">.*</a>}
+ expect(link_to_label(label_presenter)).to match %r{<a href="/foo3/bar3/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
context 'with a group as subject' do
- let(:group) { build(:group, name: 'bar') }
+ let(:subject) { build(:group, name: 'bar') }
it 'links to group issues page' do
- expect(link_to_label(label, subject: group)).to match %r{<a href="/groups/bar/-/issues\?label_name%5B%5D=#{label.name}">.*</a>}
+ expect(link_to_label(label_presenter)).to match %r{<a href="/groups/bar/-/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
@@ -95,7 +97,7 @@ describe LabelsHelper do
['issue', :issue, 'merge_request', :merge_request].each do |type|
context "set to #{type}" do
it 'links to correct page' do
- expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.full_path}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>}
+ expect(link_to_label(label_presenter, type: type)).to match %r{<a href="/#{label.project.full_path}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
end
@@ -104,14 +106,14 @@ describe LabelsHelper do
context 'with a tooltip argument' do
context 'set to false' do
it 'does not include the has-tooltip class' do
- expect(link_to_label(label, tooltip: false)).not_to match /has-tooltip/
+ expect(link_to_label(label_presenter, tooltip: false)).not_to match /has-tooltip/
end
end
end
context 'with block' do
it 'passes the block to link_to' do
- link = link_to_label(label) { 'Foo' }
+ link = link_to_label(label_presenter) { 'Foo' }
expect(link).to match('Foo')
end
end
@@ -119,8 +121,8 @@ describe LabelsHelper do
context 'without block' do
it 'uses render_colored_label as the link content' do
expect(self).to receive(:render_colored_label)
- .with(label, tooltip: true).and_return('Foo')
- expect(link_to_label(label)).to match('Foo')
+ .with(label_presenter, tooltip: true).and_return('Foo')
+ expect(link_to_label(label_presenter)).to match('Foo')
end
end
end
@@ -237,16 +239,61 @@ describe LabelsHelper do
end
end
- describe 'labels_sorted_by_title' do
+ describe 'presented_labels_sorted_by_title' do
+ let(:labels) do
+ [build(:label, title: 'a'),
+ build(:label, title: 'B'),
+ build(:label, title: 'c'),
+ build(:label, title: 'D')]
+ end
+
it 'sorts labels alphabetically' do
- label1 = double(:label, title: 'a')
- label2 = double(:label, title: 'B')
- label3 = double(:label, title: 'c')
- label4 = double(:label, title: 'D')
- labels = [label1, label2, label3, label4]
-
- expect(labels_sorted_by_title(labels))
- .to match_array([label2, label4, label1, label3])
+ sorted_ids = presented_labels_sorted_by_title(labels, nil).map(&:id)
+
+ expect(sorted_ids)
+ .to match_array([labels[1].id, labels[3].id, labels[0].id, labels[2].id])
+ end
+
+ it 'returns an array of label presenters' do
+ expect(presented_labels_sorted_by_title(labels, nil))
+ .to all(be_a(LabelPresenter))
+ end
+ end
+
+ describe 'label_from_hash' do
+ it 'builds a group label with whitelisted attributes' do
+ label = label_from_hash({ title: 'foo', color: 'bar', id: 1, group_id: 1 })
+
+ expect(label).to be_a(GroupLabel)
+ expect(label.id).to be_nil
+ expect(label.title).to eq('foo')
+ expect(label.color).to eq('bar')
+ end
+
+ it 'builds a project label with whitelisted attributes' do
+ label = label_from_hash({ title: 'foo', color: 'bar', id: 1, project_id: 1 })
+
+ expect(label).to be_a(ProjectLabel)
+ expect(label.id).to be_nil
+ expect(label.title).to eq('foo')
+ expect(label.color).to eq('bar')
+ end
+ end
+
+ describe '#label_status_tooltip' do
+ let(:status) { 'unsubscribed'.inquiry }
+ subject { label_status_tooltip(label.present(issuable_subject: nil), status) }
+
+ context 'with a project label' do
+ let(:label) { create(:label, title: 'bug') }
+
+ it { is_expected.to eq('Subscribe at project level') }
+ end
+
+ context 'with a group label' do
+ let(:label) { create(:group_label, title: 'bug') }
+
+ it { is_expected.to eq('Subscribe at group level') }
end
end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index c3956ba08fd..597c8f836a9 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -78,7 +78,8 @@ describe MarkupHelper do
let(:link) { '/commits/0a1b2c3d' }
let(:issues) { create_list(:issue, 2, project: project) }
- it 'handles references nested in links with all the text' do
+ # Clean the cache to make sure the title is re-rendered from the stubbed one
+ it 'handles references nested in links with all the text', :clean_gitlab_redis_cache do
allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real")
actual = helper.link_to_markdown_field(commit, :title, link)
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 885204062fe..193390d2f2c 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestsHelper do
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 7ccbdcd1332..601f864ef36 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -1,10 +1,38 @@
require 'spec_helper'
-describe NamespacesHelper do
+describe NamespacesHelper, :postgresql do
let!(:admin) { create(:admin) }
- let!(:admin_group) { create(:group, :private) }
+ let!(:admin_project_creation_level) { nil }
+ let!(:admin_group) do
+ create(:group,
+ :private,
+ project_creation_level: admin_project_creation_level)
+ end
let!(:user) { create(:user) }
- let!(:user_group) { create(:group, :private) }
+ let!(:user_project_creation_level) { nil }
+ let!(:user_group) do
+ create(:group,
+ :private,
+ project_creation_level: user_project_creation_level)
+ end
+ let!(:subgroup1) do
+ create(:group,
+ :private,
+ parent: admin_group,
+ project_creation_level: nil)
+ end
+ let!(:subgroup2) do
+ create(:group,
+ :private,
+ parent: admin_group,
+ project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ end
+ let!(:subgroup3) do
+ create(:group,
+ :private,
+ parent: admin_group,
+ project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ end
before do
admin_group.add_owner(admin)
@@ -105,5 +133,43 @@ describe NamespacesHelper do
helper.namespaces_options
end
end
+
+ describe 'include_groups_with_developer_maintainer_access parameter' do
+ context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set for a project' do
+ let!(:admin_project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
+
+ it 'returns groups where user is a developer' do
+ allow(helper).to receive(:current_user).and_return(user)
+ stub_application_setting(default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
+ admin_group.add_user(user, GroupMember::DEVELOPER)
+
+ options = helper.namespaces_options_with_developer_maintainer_access
+
+ expect(options).to include(admin_group.name)
+ expect(options).not_to include(subgroup1.name)
+ expect(options).to include(subgroup2.name)
+ expect(options).not_to include(subgroup3.name)
+ expect(options).to include(user_group.name)
+ expect(options).to include(user.name)
+ end
+ end
+
+ context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set globally' do
+ it 'return groups where default is not overridden' do
+ allow(helper).to receive(:current_user).and_return(user)
+ stub_application_setting(default_project_creation: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ admin_group.add_user(user, GroupMember::DEVELOPER)
+
+ options = helper.namespaces_options_with_developer_maintainer_access
+
+ expect(options).to include(admin_group.name)
+ expect(options).to include(subgroup1.name)
+ expect(options).to include(subgroup2.name)
+ expect(options).not_to include(subgroup3.name)
+ expect(options).to include(user_group.name)
+ expect(options).to include(user.name)
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index e840c927d59..979d89812f5 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -50,4 +50,16 @@ describe NavHelper do
expect(helper.header_links).to contain_exactly(:sign_in, :search)
end
end
+
+ context '.admin_monitoring_nav_links' do
+ subject { helper.admin_monitoring_nav_links }
+
+ it { is_expected.to all(be_a(String)) }
+ end
+
+ context '.group_issues_sub_menu_items' do
+ subject { helper.group_issues_sub_menu_items }
+
+ it { is_expected.to all(be_a(String)) }
+ end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index cf98eed27f1..bf50763d06f 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -38,6 +38,14 @@ describe PageLayoutHelper do
expect(helper.page_description).to eq 'Bold Header'
end
+
+ it 'truncates before sanitizing' do
+ helper.page_description('<b>Bold</b> <img> <img> <img> <h1>Header</h1> ' * 10)
+
+ # 12 words because <img> was counted as a word
+ expect(helper.page_description)
+ .to eq('Bold Header Bold Header Bold Header Bold Header Bold Header Bold Header...')
+ end
end
describe 'page_image' do
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index e0e8ebd0c3c..db0d45c3692 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -36,10 +36,11 @@ describe PreferencesHelper do
end
describe '#first_day_of_week_choices' do
- it 'returns Sunday and Monday as choices' do
+ it 'returns Saturday, Sunday and Monday as choices' do
expect(helper.first_day_of_week_choices).to eq [
['Sunday', 0],
- ['Monday', 1]
+ ['Monday', 1],
+ ['Saturday', 6]
]
end
end
@@ -47,14 +48,21 @@ describe PreferencesHelper do
describe '#first_day_of_week_choices_with_default' do
it 'returns choices including system default' do
expect(helper.first_day_of_week_choices_with_default).to eq [
- ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1]
+ ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6]
]
end
it 'returns choices including system default set to Monday' do
stub_application_setting(first_day_of_week: 1)
expect(helper.first_day_of_week_choices_with_default).to eq [
- ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1]
+ ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6]
+ ]
+ end
+
+ it 'returns choices including system default set to Saturday' do
+ stub_application_setting(first_day_of_week: 6)
+ expect(helper.first_day_of_week_choices_with_default).to eq [
+ ['System default (Saturday)', nil], ['Sunday', 0], ['Monday', 1], ['Saturday', 6]
]
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 49895b0680b..3716879c458 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1,8 +1,60 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectsHelper do
include ProjectForksHelper
+ describe '#error_tracking_setting_project_json' do
+ let(:project) { create(:project) }
+
+ context 'error tracking setting does not exist' do
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns nil' do
+ expect(helper.error_tracking_setting_project_json).to be_nil
+ end
+ end
+
+ context 'error tracking setting exists' do
+ let!(:error_tracking_setting) { create(:project_error_tracking_setting, project: project) }
+
+ context 'api_url present' do
+ let(:json) do
+ {
+ name: error_tracking_setting.project_name,
+ organization_name: error_tracking_setting.organization_name,
+ organization_slug: error_tracking_setting.organization_slug,
+ slug: error_tracking_setting.project_slug
+ }.to_json
+ end
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns error tracking json' do
+ expect(helper.error_tracking_setting_project_json).to eq(json)
+ end
+ end
+
+ context 'api_url not present' do
+ before do
+ project.error_tracking_setting.api_url = nil
+ project.error_tracking_setting.enabled = false
+
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns nil' do
+ expect(helper.error_tracking_setting_project_json).to be_nil
+ end
+ end
+ end
+ end
+
describe "#project_status_css_class" do
it "returns appropriate class" do
expect(project_status_css_class("started")).to eq("table-active")
@@ -393,6 +445,10 @@ describe ProjectsHelper do
Project.all
end
+ before do
+ stub_feature_flags(project_list_filter_bar: false)
+ end
+
it 'returns true when there are projects' do
expect(helper.show_projects?(projects, {})).to eq(true)
end
@@ -743,4 +799,46 @@ describe ProjectsHelper do
it { is_expected.to eq(result) }
end
end
+
+ describe '#can_import_members?' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:owner) { project.owner }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns false if user cannot admin_project_member' do
+ allow(helper).to receive(:current_user) { user }
+ expect(helper.can_import_members?).to eq false
+ end
+
+ it 'returns true if user can admin_project_member' do
+ allow(helper).to receive(:current_user) { owner }
+ expect(helper.can_import_members?).to eq true
+ end
+ end
+
+ describe '#metrics_external_dashboard_url' do
+ let(:project) { create(:project) }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ context 'metrics_setting exists' do
+ it 'returns external_dashboard_url' do
+ metrics_setting = create(:project_metrics_setting, project: project)
+
+ expect(helper.metrics_external_dashboard_url).to eq(metrics_setting.external_dashboard_url)
+ end
+ end
+
+ context 'metrics_setting does not exist' do
+ it 'returns nil' do
+ expect(helper.metrics_external_dashboard_url).to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 9cff0291250..2f59cfda0a0 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -12,7 +12,7 @@ describe SearchHelper do
allow(self).to receive(:current_user).and_return(nil)
end
- it "it returns nil" do
+ it "returns nil" do
expect(search_autocomplete_opts("q")).to be_nil
end
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 03df9deafa1..62c00964524 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -18,4 +18,29 @@ describe StorageHelper do
expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB")
end
end
+
+ describe "#storage_counters_details" do
+ let(:namespace) { create :namespace }
+ let(:project) do
+ create(:project,
+ namespace: namespace,
+ statistics: build(:project_statistics,
+ repository_size: 10.kilobytes,
+ wiki_size: 10.bytes,
+ lfs_objects_size: 20.gigabytes,
+ build_artifacts_size: 30.megabytes))
+ end
+
+ let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' }
+
+ it 'works on ProjectStatistics' do
+ expect(helper.storage_counters_details(project.statistics)).to eq(message)
+ end
+
+ it 'works on Namespace.with_statistics' do
+ namespace_stats = Namespace.with_statistics.find(project.namespace.id)
+
+ expect(helper.storage_counters_details(namespace_stats)).to eq(message)
+ end
+ end
end
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
new file mode 100644
index 00000000000..71505e8ea69
--- /dev/null
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe TrackingHelper do
+ describe '#tracking_attrs' do
+ it 'returns an empty hash' do
+ expect(helper.tracking_attrs('a', 'b', 'c')).to eq({})
+ end
+ end
+end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index bfec7ad4bba..e384e2bf9a0 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe VersionCheckHelper do
describe '#version_status_badge' do
- it 'should return nil if not dev environment and not enabled' do
+ it 'returns nil if not dev environment and not enabled' do
allow(Rails.env).to receive(:production?) { false }
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false }
@@ -16,16 +16,16 @@ describe VersionCheckHelper do
allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
end
- it 'should return an image tag' do
+ it 'returns an image tag' do
expect(helper.version_status_badge).to start_with('<img')
end
- it 'should have a js prefixed css class' do
+ it 'has a js prefixed css class' do
expect(helper.version_status_badge)
.to match(/class="js-version-status-badge lazy"/)
end
- it 'should have a VersionCheck url as the src' do
+ it 'has a VersionCheck url as the src' do
expect(helper.version_status_badge)
.to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"})
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index e565ac8c530..25a2fcf5a81 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -162,4 +162,49 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe "selected_visibility_level" do
+ let(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :internal, group: group) }
+ let!(:forked_project) { fork_project(project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ PUBLIC = Gitlab::VisibilityLevel::PUBLIC
+ INTERNAL = Gitlab::VisibilityLevel::INTERNAL
+ PRIVATE = Gitlab::VisibilityLevel::PRIVATE
+
+ # This is a subset of all the permutations
+ where(:requested_level, :max_allowed, :global_default_level, :restricted_levels, :expected) do
+ PUBLIC | PUBLIC | PUBLIC | [] | PUBLIC
+ PUBLIC | PUBLIC | PUBLIC | [PUBLIC] | INTERNAL
+ INTERNAL | PUBLIC | PUBLIC | [] | INTERNAL
+ INTERNAL | PRIVATE | PRIVATE | [] | PRIVATE
+ PRIVATE | PUBLIC | PUBLIC | [] | PRIVATE
+ PUBLIC | PRIVATE | INTERNAL | [] | PRIVATE
+ PUBLIC | INTERNAL | PUBLIC | [] | INTERNAL
+ PUBLIC | PRIVATE | PUBLIC | [] | PRIVATE
+ PUBLIC | INTERNAL | INTERNAL | [] | INTERNAL
+ PUBLIC | PUBLIC | INTERNAL | [] | PUBLIC
+ end
+
+ before do
+ stub_application_setting(restricted_visibility_levels: restricted_levels,
+ default_project_visibility: global_default_level)
+ end
+
+ with_them do
+ it "provides correct visibility level for forked project" do
+ project.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(forked_project, requested_level)).to eq(expected)
+ end
+
+ it "provides correct visibiility level for project in group" do
+ project.group.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(project, requested_level)).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 92c6f27a867..8eab40aeaf3 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -18,4 +18,56 @@ describe WikiHelper do
end
end
end
+
+ describe '#wiki_sort_controls' do
+ let(:project) { create(:project) }
+ let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) }
+ let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" }
+
+ def expected_link(sort, direction, icon_class)
+ path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
+
+ helper.link_to(path, type: 'button', class: classes, title: 'Sort direction') do
+ helper.sprite_icon("sort-#{icon_class}", size: 16)
+ end
+ end
+
+ context 'initial call' do
+ let(:sort) { nil }
+ let(:direction) { nil }
+
+ it 'renders with default values' do
+ expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest'))
+ end
+ end
+
+ context 'sort by title' do
+ let(:sort) { 'title' }
+ let(:direction) { 'asc' }
+
+ it 'renders a link with opposite direction' do
+ expect(wiki_link).to eq(expected_link('title', 'desc', 'lowest'))
+ end
+ end
+
+ context 'sort by created_at' do
+ let(:sort) { 'created_at' }
+ let(:direction) { 'desc' }
+
+ it 'renders a link with opposite direction' do
+ expect(wiki_link).to eq(expected_link('created_at', 'asc', 'highest'))
+ end
+ end
+ end
+
+ describe '#wiki_sort_title' do
+ it 'returns a title corresponding to a key' do
+ expect(helper.wiki_sort_title('created_at')).to eq('Created date')
+ expect(helper.wiki_sort_title('title')).to eq('Title')
+ end
+
+ it 'defaults to Title if a key is unknown' do
+ expect(helper.wiki_sort_title('unknown')).to eq('Title')
+ end
+ end
end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 6366be30079..726ce07a2d1 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -6,8 +6,8 @@ describe 'create_tokens' do
let(:secrets) { ActiveSupport::OrderedOptions.new }
- HEX_KEY = /\h{128}/
- RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m
+ HEX_KEY = /\h{128}/.freeze
+ RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m.freeze
before do
allow(File).to receive(:write)
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
deleted file mode 100644
index 068b8eb65bc..00000000000
--- a/spec/javascripts/activities_spec.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */
-
-import $ from 'jquery';
-import 'vendor/jquery.endless-scroll';
-import Activities from '~/activities';
-import Pager from '~/pager';
-
-describe('Activities', () => {
- window.gon || (window.gon = {});
- const fixtureTemplate = 'static/event_filter.html.raw';
- const filters = [
- {
- id: 'all',
- },
- {
- id: 'push',
- name: 'push events',
- },
- {
- id: 'merged',
- name: 'merge events',
- },
- {
- id: 'comments',
- },
- {
- id: 'team',
- },
- ];
-
- function getEventName(index) {
- const filter = filters[index];
- return filter.hasOwnProperty('name') ? filter.name : filter.id;
- }
-
- function getSelector(index) {
- const filter = filters[index];
- return `#${filter.id}_event_filter`;
- }
-
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
- spyOn(Pager, 'init').and.stub();
- new Activities();
- });
-
- for (let i = 0; i < filters.length; i += 1) {
- (i => {
- describe(`when selecting ${getEventName(i)}`, () => {
- beforeEach(() => {
- $(getSelector(i)).click();
- });
-
- for (let x = 0; x < filters.length; x += 1) {
- (x => {
- const shouldHighlight = i === x;
- const testName = shouldHighlight ? 'should highlight' : 'should not highlight';
-
- it(`${testName} ${getEventName(x)}`, () => {
- expect(
- $(getSelector(x))
- .parent()
- .hasClass('active'),
- ).toEqual(shouldHighlight);
- });
- })(x);
- }
- });
- })(i);
- }
-});
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index 9389fc94f17..89195a4397f 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
describe('Ajax Loading Spinner', () => {
- const fixtureTemplate = 'static/ajax_loading_spinner.html.raw';
+ const fixtureTemplate = 'static/ajax_loading_spinner.html';
preloadFixtures(fixtureTemplate);
beforeEach(() => {
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
deleted file mode 100644
index 1e9470970ff..00000000000
--- a/spec/javascripts/api_spec.js
+++ /dev/null
@@ -1,431 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Api from '~/api';
-
-describe('Api', () => {
- const dummyApiVersion = 'v3000';
- const dummyUrlRoot = 'http://host.invalid';
- const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
- };
- let originalGon;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = Object.assign({}, dummyGon);
- });
-
- afterEach(() => {
- mock.restore();
- window.gon = originalGon;
- });
-
- describe('buildUrl', () => {
- it('adds URL root and fills in API version', () => {
- const input = '/api/:version/foo/bar';
- const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`;
-
- const builtUrl = Api.buildUrl(input);
-
- expect(builtUrl).toEqual(expectedOutput);
- });
- });
-
- describe('group', () => {
- it('fetches a group', done => {
- const groupId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
- mock.onGet(expectedUrl).reply(200, {
- name: 'test',
- });
-
- Api.group(groupId, response => {
- expect(response.name).toBe('test');
- done();
- });
- });
- });
-
- describe('groupMembers', () => {
- it('fetches group members', done => {
- const groupId = '54321';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`;
- const expectedData = [{ id: 7 }];
- mock.onGet(expectedUrl).reply(200, expectedData);
-
- Api.groupMembers(groupId)
- .then(({ data }) => {
- expect(data).toEqual(expectedData);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('groups', () => {
- it('fetches groups', done => {
- const query = 'dummy query';
- const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.groups(query, options, response => {
- expect(response.length).toBe(1);
- expect(response[0].name).toBe('test');
- done();
- });
- });
- });
-
- describe('namespaces', () => {
- it('fetches namespaces', done => {
- const query = 'dummy query';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.namespaces(query, response => {
- expect(response.length).toBe(1);
- expect(response[0].name).toBe('test');
- done();
- });
- });
- });
-
- describe('projects', () => {
- it('fetches projects with membership when logged in', done => {
- const query = 'dummy query';
- const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
- window.gon.current_user_id = 1;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.projects(query, options, response => {
- expect(response.length).toBe(1);
- expect(response[0].name).toBe('test');
- done();
- });
- });
-
- it('fetches projects without membership when not logged in', done => {
- const query = 'dummy query';
- const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.projects(query, options, response => {
- expect(response.length).toBe(1);
- expect(response[0].name).toBe('test');
- done();
- });
- });
- });
-
- describe('projectMergeRequest', () => {
- it('fetches a merge request', done => {
- const projectPath = 'abc';
- const mergeRequestId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
- mock.onGet(expectedUrl).reply(200, {
- title: 'test',
- });
-
- Api.projectMergeRequest(projectPath, mergeRequestId)
- .then(({ data }) => {
- expect(data.title).toBe('test');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('projectMergeRequestChanges', () => {
- it('fetches the changes of a merge request', done => {
- const projectPath = 'abc';
- const mergeRequestId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
- mock.onGet(expectedUrl).reply(200, {
- title: 'test',
- });
-
- Api.projectMergeRequestChanges(projectPath, mergeRequestId)
- .then(({ data }) => {
- expect(data.title).toBe('test');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('projectMergeRequestVersions', () => {
- it('fetches the versions of a merge request', done => {
- const projectPath = 'abc';
- const mergeRequestId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
- mock.onGet(expectedUrl).reply(200, [
- {
- id: 123,
- },
- ]);
-
- Api.projectMergeRequestVersions(projectPath, mergeRequestId)
- .then(({ data }) => {
- expect(data.length).toBe(1);
- expect(data[0].id).toBe(123);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('projectRunners', () => {
- it('fetches the runners of a project', done => {
- const projectPath = 7;
- const params = { scope: 'active' };
- const mockData = [{ id: 4 }];
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`;
- mock.onGet(expectedUrl, { params }).reply(200, mockData);
-
- Api.projectRunners(projectPath, { params })
- .then(({ data }) => {
- expect(data).toEqual(mockData);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('newLabel', () => {
- it('creates a new label', done => {
- const namespace = 'some namespace';
- const project = 'some project';
- const labelData = { some: 'data' };
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`;
- const expectedData = {
- label: labelData,
- };
- mock.onPost(expectedUrl).reply(config => {
- expect(config.data).toBe(JSON.stringify(expectedData));
-
- return [
- 200,
- {
- name: 'test',
- },
- ];
- });
-
- Api.newLabel(namespace, project, labelData, response => {
- expect(response.name).toBe('test');
- done();
- });
- });
-
- it('creates a group label', done => {
- const namespace = 'group/subgroup';
- const labelData = { some: 'data' };
- const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`;
- const expectedData = {
- label: labelData,
- };
- mock.onPost(expectedUrl).reply(config => {
- expect(config.data).toBe(JSON.stringify(expectedData));
-
- return [
- 200,
- {
- name: 'test',
- },
- ];
- });
-
- Api.newLabel(namespace, undefined, labelData, response => {
- expect(response.name).toBe('test');
- done();
- });
- });
- });
-
- describe('groupProjects', () => {
- it('fetches group projects', done => {
- const groupId = '123456';
- const query = 'dummy query';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.groupProjects(groupId, query, {}, response => {
- expect(response.length).toBe(1);
- expect(response[0].name).toBe('test');
- done();
- });
- });
- });
-
- describe('issueTemplate', () => {
- it('fetches an issue template', done => {
- const namespace = 'some namespace';
- const project = 'some project';
- const templateKey = ' template #%?.key ';
- const templateType = 'template type';
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
- templateKey,
- )}`;
- mock.onGet(expectedUrl).reply(200, 'test');
-
- Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
- expect(response).toBe('test');
- done();
- });
- });
- });
-
- describe('projectTemplates', () => {
- it('fetches a list of templates', done => {
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`;
-
- mock.onGet(expectedUrl).reply(200, 'test');
-
- Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => {
- expect(response).toBe('test');
- done();
- });
- });
- });
-
- describe('projectTemplate', () => {
- it('fetches a single template', done => {
- const data = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`;
-
- mock.onGet(expectedUrl).reply(200, 'test');
-
- Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => {
- expect(response).toBe('test');
- done();
- });
- });
- });
-
- describe('users', () => {
- it('fetches users', done => {
- const query = 'dummy query';
- const options = { unused: 'option' };
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.users(query, options)
- .then(({ data }) => {
- expect(data.length).toBe(1);
- expect(data[0].name).toBe('test');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('user', () => {
- it('fetches single user', done => {
- const userId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`;
- mock.onGet(expectedUrl).reply(200, {
- name: 'testuser',
- });
-
- Api.user(userId)
- .then(({ data }) => {
- expect(data.name).toBe('testuser');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('user status', () => {
- it('fetches single user status', done => {
- const userId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`;
- mock.onGet(expectedUrl).reply(200, {
- message: 'testmessage',
- });
-
- Api.userStatus(userId)
- .then(({ data }) => {
- expect(data.message).toBe('testmessage');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('commitPipelines', () => {
- it('fetches pipelines for a given commit', done => {
- const projectId = 'example/foobar';
- const commitSha = 'abc123def';
- const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`;
- mock.onGet(expectedUrl).reply(200, [
- {
- name: 'test',
- },
- ]);
-
- Api.commitPipelines(projectId, commitSha)
- .then(({ data }) => {
- expect(data.length).toBe(1);
- expect(data[0].name).toBe('test');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('createBranch', () => {
- it('creates new branch', done => {
- const ref = 'master';
- const branch = 'new-branch-name';
- const dummyProjectPath = 'gitlab-org/gitlab-ce';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
- dummyProjectPath,
- )}/repository/branches`;
-
- spyOn(axios, 'post').and.callThrough();
-
- mock.onPost(expectedUrl).replyOnce(200, {
- name: branch,
- });
-
- Api.createBranch(dummyProjectPath, { ref, branch })
- .then(({ data }) => {
- expect(data.name).toBe(branch);
- expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch });
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js
deleted file mode 100644
index dcb1c781591..00000000000
--- a/spec/javascripts/autosave_spec.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import $ from 'jquery';
-import Autosave from '~/autosave';
-import AccessorUtilities from '~/lib/utils/accessor';
-
-describe('Autosave', () => {
- let autosave;
- const field = $('<textarea></textarea>');
- const key = 'key';
-
- describe('class constructor', () => {
- beforeEach(() => {
- spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true);
- spyOn(Autosave.prototype, 'restore');
- });
-
- it('should set .isLocalStorageAvailable', () => {
- autosave = new Autosave(field, key);
-
- expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
- expect(autosave.isLocalStorageAvailable).toBe(true);
- });
- });
-
- describe('restore', () => {
- beforeEach(() => {
- autosave = {
- field,
- key,
- };
-
- spyOn(window.localStorage, 'getItem');
- });
-
- describe('if .isLocalStorageAvailable is `false`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = false;
-
- Autosave.prototype.restore.call(autosave);
- });
-
- it('should not call .getItem', () => {
- expect(window.localStorage.getItem).not.toHaveBeenCalled();
- });
- });
-
- describe('if .isLocalStorageAvailable is `true`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = true;
- });
-
- it('should call .getItem', () => {
- Autosave.prototype.restore.call(autosave);
-
- expect(window.localStorage.getItem).toHaveBeenCalledWith(key);
- });
-
- it('triggers jquery event', () => {
- spyOn(autosave.field, 'trigger').and.callThrough();
-
- Autosave.prototype.restore.call(autosave);
-
- expect(field.trigger).toHaveBeenCalled();
- });
-
- it('triggers native event', done => {
- autosave.field.get(0).addEventListener('change', () => {
- done();
- });
-
- Autosave.prototype.restore.call(autosave);
- });
- });
-
- describe('if field gets deleted from DOM', () => {
- beforeEach(() => {
- autosave.field = $('.not-a-real-element');
- });
-
- it('does not trigger event', () => {
- spyOn(field, 'trigger').and.callThrough();
-
- expect(field.trigger).not.toHaveBeenCalled();
- });
- });
- });
-
- describe('save', () => {
- beforeEach(() => {
- autosave = jasmine.createSpyObj('autosave', ['reset']);
- autosave.field = field;
- field.val('value');
-
- spyOn(window.localStorage, 'setItem');
- });
-
- describe('if .isLocalStorageAvailable is `false`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = false;
-
- Autosave.prototype.save.call(autosave);
- });
-
- it('should not call .setItem', () => {
- expect(window.localStorage.setItem).not.toHaveBeenCalled();
- });
- });
-
- describe('if .isLocalStorageAvailable is `true`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = true;
-
- Autosave.prototype.save.call(autosave);
- });
-
- it('should call .setItem', () => {
- expect(window.localStorage.setItem).toHaveBeenCalled();
- });
- });
- });
-
- describe('reset', () => {
- beforeEach(() => {
- autosave = {
- key,
- };
-
- spyOn(window.localStorage, 'removeItem');
- });
-
- describe('if .isLocalStorageAvailable is `false`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = false;
-
- Autosave.prototype.reset.call(autosave);
- });
-
- it('should not call .removeItem', () => {
- expect(window.localStorage.removeItem).not.toHaveBeenCalled();
- });
- });
-
- describe('if .isLocalStorageAvailable is `true`', () => {
- beforeEach(() => {
- autosave.isLocalStorageAvailable = true;
-
- Autosave.prototype.reset.call(autosave);
- });
-
- it('should call .removeItem', () => {
- expect(window.localStorage.removeItem).toHaveBeenCalledWith(key);
- });
- });
- });
-});
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index ce5d2022441..02200f77ad7 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -19,9 +19,9 @@ const lazyAssert = function(done, assertFn) {
};
describe('AwardsHandler', function() {
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(function(done) {
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
loadAwardsHandler(true)
.then(obj => {
awardsHandler = obj;
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 536671db377..2f72c9ed89d 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.fa-spinner');
+ const loadingIcon = vm.$el.querySelector('.spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 29805408bcf..4e4d1ae2e99 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.fa-spinner'),
+ loadingIcon: vm.$el.querySelector('.spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/badges/store/actions_spec.js b/spec/javascripts/badges/store/actions_spec.js
index 2623465ebd6..e8d5f8c3aac 100644
--- a/spec/javascripts/badges/store/actions_spec.js
+++ b/spec/javascripts/badges/store/actions_spec.js
@@ -411,7 +411,7 @@ describe('Badges store actions', () => {
it('escapes user input', done => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() }));
- badgeInForm.imageUrl = '&make-sandwhich=true';
+ badgeInForm.imageUrl = '&make-sandwich=true';
badgeInForm.linkUrl = '<script>I am dangerous!</script>';
actions
@@ -422,7 +422,7 @@ describe('Badges store actions', () => {
expect(url).toMatch(`^${dummyEndpointUrl}/render?`);
expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&');
- expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$');
+ expect(url).toMatch('&image_url=%26make-sandwich%3Dtrue$');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js
index ca849f75860..d653fca0988 100644
--- a/spec/javascripts/behaviors/copy_as_gfm_spec.js
+++ b/spec/javascripts/behaviors/copy_as_gfm_spec.js
@@ -100,7 +100,7 @@ describe('CopyAsGFM', () => {
simulateCopy();
setTimeout(() => {
- const expectedGFM = '* List Item1\n\n* List Item2';
+ const expectedGFM = '* List Item1\n* List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
done();
@@ -114,7 +114,7 @@ describe('CopyAsGFM', () => {
simulateCopy();
setTimeout(() => {
- const expectedGFM = '1. List Item1\n\n1. List Item2';
+ const expectedGFM = '1. List Item1\n1. List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
done();
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 681463aab66..7af8c984841 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -4,10 +4,10 @@ import '~/behaviors/quick_submit';
describe('Quick Submit behavior', function() {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(() => {
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
$('form').submit(e => {
// Prevent a form submit from moving us off the testing page
e.preventDefault();
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index 1bde2bb3024..617fe49b059 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -3,10 +3,10 @@ import '~/behaviors/requires_input';
describe('requiresInput', () => {
let submitButton;
- preloadFixtures('branches/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html');
beforeEach(() => {
- loadFixtures('branches/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html');
submitButton = $('button[type="submit"]');
});
diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
index 4843a0386b5..5e457a4e823 100644
--- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
describe('ShortcutsIssuable', function() {
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
preloadFixtures(fixtureName);
beforeAll(done => {
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
index 5f027f59fcf..33210794ba1 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
@@ -1,15 +1,17 @@
import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
-import bmprPath from '../../fixtures/blob/balsamiq/test.bmpr';
+import { FIXTURES_PATH } from 'spec/test_constants';
+
+const bmprPath = `${FIXTURES_PATH}/blob/balsamiq/test.bmpr`;
describe('Balsamiq integration spec', () => {
let container;
let endpoint;
let balsamiqViewer;
- preloadFixtures('static/balsamiq_viewer.html.raw');
+ preloadFixtures('static/balsamiq_viewer.html');
beforeEach(() => {
- loadFixtures('static/balsamiq_viewer.html.raw');
+ loadFixtures('static/balsamiq_viewer.html');
container = document.getElementById('js-balsamiq-viewer');
balsamiqViewer = new BalsamiqViewer(container);
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
index 432d8a65b0a..cab06a0a9be 100644
--- a/spec/javascripts/blob/blob_file_dropzone_spec.js
+++ b/spec/javascripts/blob/blob_file_dropzone_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', function() {
- preloadFixtures('blob/show.html.raw');
+ preloadFixtures('blob/show.html');
beforeEach(() => {
- loadFixtures('blob/show.html.raw');
+ loadFixtures('blob/show.html');
const form = $('.js-upload-blob-form');
this.blobFileDropzone = new BlobFileDropzone(form, 'POST');
this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone;
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index 28d3b2f5ea3..6bb5bac007f 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils';
import renderNotebook from '~/blob/notebook';
describe('iPython notebook renderer', () => {
- preloadFixtures('static/notebook_viewer.html.raw');
+ preloadFixtures('static/notebook_viewer.html');
beforeEach(() => {
- loadFixtures('static/notebook_viewer.html.raw');
+ loadFixtures('static/notebook_viewer.html');
});
it('shows loading icon', () => {
diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js
index be917a0613f..6fa3890483c 100644
--- a/spec/javascripts/blob/pdf/index_spec.js
+++ b/spec/javascripts/blob/pdf/index_spec.js
@@ -1,5 +1,7 @@
import renderPDF from '~/blob/pdf';
-import testPDF from '../../fixtures/blob/pdf/test.pdf';
+import { FIXTURES_PATH } from 'spec/test_constants';
+
+const testPDF = `${FIXTURES_PATH}/blob/pdf/test.pdf`;
describe('PDF renderer', () => {
let viewer;
@@ -15,10 +17,10 @@ describe('PDF renderer', () => {
}
};
- preloadFixtures('static/pdf_viewer.html.raw');
+ preloadFixtures('static/pdf_viewer.html');
beforeEach(() => {
- loadFixtures('static/pdf_viewer.html.raw');
+ loadFixtures('static/pdf_viewer.html');
viewer = document.getElementById('js-pdf-viewer');
viewer.dataset.endpoint = testPDF;
});
diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js
index 2b1e81e9cbc..3d3129e10da 100644
--- a/spec/javascripts/blob/sketch/index_spec.js
+++ b/spec/javascripts/blob/sketch/index_spec.js
@@ -13,10 +13,10 @@ describe('Sketch viewer', () => {
});
};
- preloadFixtures('static/sketch_viewer.html.raw');
+ preloadFixtures('static/sketch_viewer.html');
beforeEach(() => {
- loadFixtures('static/sketch_viewer.html.raw');
+ loadFixtures('static/sketch_viewer.html');
});
describe('with error message', () => {
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index 93a942fe8d4..4ac15ca5aa2 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -9,12 +9,12 @@ describe('Blob viewer', () => {
let blob;
let mock;
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(() => {
mock = new MockAdapter(axios);
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index e1017130bed..13b708a03d5 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import eventHub from '~/boards/eventhub';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
diff --git a/spec/javascripts/boards/board_list_common_spec.js b/spec/javascripts/boards/board_list_common_spec.js
new file mode 100644
index 00000000000..cb337e4cc83
--- /dev/null
+++ b/spec/javascripts/boards/board_list_common_spec.js
@@ -0,0 +1,58 @@
+/* global List */
+/* global ListIssue */
+
+import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
+import Sortable from 'sortablejs';
+import BoardList from '~/boards/components/board_list.vue';
+
+import '~/boards/models/issue';
+import '~/boards/models/list';
+import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
+import boardsStore from '~/boards/stores/boards_store';
+
+window.Sortable = Sortable;
+
+export default function createComponent({ done, listIssueProps = {}, componentProps = {} }) {
+ const el = document.createElement('div');
+
+ document.body.appendChild(el);
+ const mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ gl.boardService = mockBoardService();
+ boardsStore.create();
+
+ const BoardListComp = Vue.extend(BoardList);
+ const list = new List(listObj);
+ const issue = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ ...listIssueProps,
+ });
+ list.issuesSize = 1;
+ list.issues.push(issue);
+
+ const component = new BoardListComp({
+ el,
+ propsData: {
+ disabled: false,
+ list,
+ issues: list.issues,
+ loading: false,
+ issueLinkBase: '/issues',
+ rootPath: '/',
+ ...componentProps,
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ done();
+ });
+
+ return { component, mock };
+}
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 2642c8b1bdb..9c9b435d7fd 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -1,60 +1,13 @@
-/* global List */
-/* global ListIssue */
-
import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Sortable from 'sortablejs';
-import BoardList from '~/boards/components/board_list.vue';
import eventHub from '~/boards/eventhub';
-import '~/boards/models/issue';
-import '~/boards/models/list';
-import boardsStore from '~/boards/stores/boards_store';
-import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
-
-window.Sortable = Sortable;
+import createComponent from './board_list_common_spec';
describe('Board list component', () => {
let mock;
let component;
beforeEach(done => {
- const el = document.createElement('div');
-
- document.body.appendChild(el);
- mock = new MockAdapter(axios);
- mock.onAny().reply(boardsMockInterceptor);
- gl.boardService = mockBoardService();
- boardsStore.create();
-
- const BoardListComp = Vue.extend(BoardList);
- const list = new List(listObj);
- const issue = new ListIssue({
- title: 'Testing',
- id: 1,
- iid: 1,
- confidential: false,
- labels: [],
- assignees: [],
- });
- list.issuesSize = 1;
- list.issues.push(issue);
-
- component = new BoardListComp({
- el,
- propsData: {
- disabled: false,
- list,
- issues: list.issues,
- loading: false,
- issueLinkBase: '/issues',
- rootPath: '/',
- },
- }).$mount();
-
- Vue.nextTick(() => {
- done();
- });
+ ({ mock, component } = createComponent({ done }));
});
afterEach(() => {
@@ -195,7 +148,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .fa-spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 22f192bc7f3..e81115e10c9 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -6,12 +6,13 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
import boardsStore from '~/boards/stores/boards_store';
+import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
describe('Store', () => {
@@ -44,6 +45,48 @@ describe('Store', () => {
expect(boardsStore.state.lists.length).toBe(0);
});
+ describe('addList', () => {
+ it('sorts by position', () => {
+ boardsStore.addList({ position: 2 });
+ boardsStore.addList({ position: 1 });
+
+ expect(boardsStore.state.lists[0].position).toBe(1);
+ });
+ });
+
+ describe('toggleFilter', () => {
+ const dummyFilter = 'x=42';
+ let updateTokensSpy;
+
+ beforeEach(() => {
+ updateTokensSpy = jasmine.createSpy('updateTokens');
+ eventHub.$once('updateTokens', updateTokensSpy);
+
+ // prevent using window.history
+ spyOn(boardsStore, 'updateFiltersUrl').and.callFake(() => {});
+ });
+
+ it('adds the filter if it is not present', () => {
+ boardsStore.filter.path = 'something';
+
+ boardsStore.toggleFilter(dummyFilter);
+
+ expect(boardsStore.filter.path).toEqual(`something&${dummyFilter}`);
+ expect(updateTokensSpy).toHaveBeenCalled();
+ expect(boardsStore.updateFiltersUrl).toHaveBeenCalled();
+ });
+
+ it('removes the filter if it is present', () => {
+ boardsStore.filter.path = `something&${dummyFilter}`;
+
+ boardsStore.toggleFilter(dummyFilter);
+
+ expect(boardsStore.filter.path).toEqual('something');
+ expect(updateTokensSpy).toHaveBeenCalled();
+ expect(boardsStore.updateFiltersUrl).toHaveBeenCalled();
+ });
+ });
+
describe('lists', () => {
it('creates new list without persisting to DB', () => {
boardsStore.addList(listObj);
@@ -268,4 +311,48 @@ describe('Store', () => {
});
});
});
+
+ describe('setListDetail', () => {
+ it('sets the list detail', () => {
+ boardsStore.detail.list = 'not a list';
+
+ const dummyValue = 'new list';
+ boardsStore.setListDetail(dummyValue);
+
+ expect(boardsStore.detail.list).toEqual(dummyValue);
+ });
+ });
+
+ describe('clearDetailIssue', () => {
+ it('resets issue details', () => {
+ boardsStore.detail.issue = 'something';
+
+ boardsStore.clearDetailIssue();
+
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+ });
+
+ describe('setIssueDetail', () => {
+ it('sets issue details', () => {
+ boardsStore.detail.issue = 'some details';
+
+ const dummyValue = 'new details';
+ boardsStore.setIssueDetail(dummyValue);
+
+ expect(boardsStore.detail.issue).toEqual(dummyValue);
+ });
+ });
+
+ describe('startMoving', () => {
+ it('stores list and issue', () => {
+ const dummyIssue = 'some issue';
+ const dummyList = 'some list';
+
+ boardsStore.startMoving(dummyList, dummyIssue);
+
+ expect(boardsStore.moving.issue).toEqual(dummyIssue);
+ expect(boardsStore.moving.list).toEqual(dummyList);
+ });
+ });
});
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index dee7841c088..d08ee41802b 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -9,7 +9,7 @@ describe('Board component', () => {
let el;
beforeEach(done => {
- loadFixtures('boards/show.html.raw');
+ loadFixtures('boards/show.html');
el = document.createElement('div');
document.body.appendChild(el);
@@ -103,4 +103,18 @@ describe('Board component', () => {
})
.catch(done.fail);
});
+
+ it('does render add issue button', () => {
+ expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
+ });
+
+ it('does not render add issue button when list type is blank', done => {
+ vm.list.type = 'blank';
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.issue-count-badge-add-button')).toBeNull();
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js b/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js
new file mode 100644
index 00000000000..c62c5b9962d
--- /dev/null
+++ b/spec/javascripts/boards/components/issue_card_inner_scoped_label_spec.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import IssueCardInnerScopedLabel from '~/boards/components/issue_card_inner_scoped_label.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('IssueCardInnerScopedLabel Component', () => {
+ let vm;
+ const Component = Vue.extend(IssueCardInnerScopedLabel);
+ const props = {
+ label: { title: 'Foo::Bar', description: 'Some Random Description' },
+ labelStyle: { background: 'white', color: 'black' },
+ scopedLabelsDocumentationLink: '/docs-link',
+ };
+ const createComponent = () => mountComponent(Component, { ...props });
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render label title', () => {
+ expect(vm.$el.querySelector('.color-label').textContent.trim()).toEqual('Foo::Bar');
+ });
+
+ it('should render question mark symbol', () => {
+ expect(vm.$el.querySelector('.fa-question-circle')).not.toBeNull();
+ });
+
+ it('should render label style provided', () => {
+ const node = vm.$el.querySelector('.color-label');
+
+ expect(node.style.background).toEqual(props.labelStyle.background);
+ expect(node.style.color).toEqual(props.labelStyle.color);
+ });
+
+ it('should render the docs link', () => {
+ expect(vm.$el.querySelector('a.scoped-label').href).toContain(
+ props.scopedLabelsDocumentationLink,
+ );
+ });
+});
diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js
index 054cf8c5b7d..68e26b68f04 100644
--- a/spec/javascripts/boards/components/issue_due_date_spec.js
+++ b/spec/javascripts/boards/components/issue_due_date_spec.js
@@ -43,7 +43,7 @@ describe('Issue Due Date component', () => {
date.setDate(date.getDate() + 5);
vm = createComponent(date);
- expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd', true));
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, 'dddd'));
});
it('should render month and day for other dates', () => {
@@ -53,7 +53,7 @@ describe('Issue Due Date component', () => {
const isDueInCurrentYear = today.getFullYear() === date.getFullYear();
const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy';
- expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format, true));
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format));
});
it('should contain the correct `.text-danger` css class for overdue issue', () => {
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 6eda5047dd0..8a20911cc66 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -4,8 +4,8 @@
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
@@ -285,4 +285,10 @@ describe('Issue card component', () => {
.catch(done.fail);
});
});
+
+ describe('weights', () => {
+ it('not shows weight component', () => {
+ expect(component.$el.querySelector('.board-card-weight')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 54fb0e8228b..bb7abe52eae 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,8 +1,8 @@
/* global ListIssue */
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
@@ -178,6 +178,7 @@ describe('Issue model', () => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([1]);
done();
+ return Promise.resolve();
});
issue.update('url');
@@ -187,6 +188,7 @@ describe('Issue model', () => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([0]);
done();
+ return Promise.resolve();
});
issue.removeAllAssignees();
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 0d462a6f872..15c9ff6dfb4 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -4,8 +4,8 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
@@ -45,6 +45,7 @@ describe('List model', () => {
id: _.random(10000),
title: 'test',
color: 'red',
+ text_color: 'white',
},
});
list.save();
@@ -53,6 +54,8 @@ describe('List model', () => {
expect(list.id).toBe(listObj.id);
expect(list.type).toBe('label');
expect(list.position).toBe(0);
+ expect(list.label.color).toBe('red');
+ expect(list.label.textColor).toBe('white');
done();
}, 0);
});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 14fff9223f4..9854cf49e97 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -16,6 +16,7 @@ export const listObj = {
title: 'Testing',
color: 'red',
description: 'testing;',
+ textColor: 'white',
},
};
@@ -46,7 +47,7 @@ export const BoardsMockData = {
},
],
},
- '/test/issue-boards/milestones.json': [
+ '/test/issue-boards/-/milestones.json': [
{
id: 1,
title: 'test',
@@ -57,10 +58,10 @@ export const BoardsMockData = {
'/test/-/boards/1/lists': listObj,
},
PUT: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
DELETE: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
};
@@ -70,7 +71,7 @@ export const boardsMockInterceptor = config => {
};
export const mockBoardService = (opts = {}) => {
- const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json';
+ const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json';
const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists';
const bulkUpdatePath = opts.bulkUpdatePath || '';
const boardId = opts.boardId || '1';
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
deleted file mode 100644
index 3257a3fb8a3..00000000000
--- a/spec/javascripts/boards/modal_store_spec.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/* global ListIssue */
-
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
-import '~/boards/models/issue';
-import '~/boards/models/list';
-import Store from '~/boards/stores/modal_store';
-
-describe('Modal store', () => {
- let issue;
- let issue2;
-
- beforeEach(() => {
- // Set up default state
- Store.store.issues = [];
- Store.store.selectedIssues = [];
-
- issue = new ListIssue({
- title: 'Testing',
- id: 1,
- iid: 1,
- confidential: false,
- labels: [],
- assignees: [],
- });
- issue2 = new ListIssue({
- title: 'Testing',
- id: 1,
- iid: 2,
- confidential: false,
- labels: [],
- assignees: [],
- });
- Store.store.issues.push(issue);
- Store.store.issues.push(issue2);
- });
-
- it('returns selected count', () => {
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles the issue as selected', () => {
- Store.toggleIssue(issue);
-
- expect(issue.selected).toBe(true);
- expect(Store.selectedCount()).toBe(1);
- });
-
- it('toggles the issue as un-selected', () => {
- Store.toggleIssue(issue);
- Store.toggleIssue(issue);
-
- expect(issue.selected).toBe(false);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles all issues as selected', () => {
- Store.toggleAll();
-
- expect(issue.selected).toBe(true);
- expect(issue2.selected).toBe(true);
- expect(Store.selectedCount()).toBe(2);
- });
-
- it('toggles all issues as un-selected', () => {
- Store.toggleAll();
- Store.toggleAll();
-
- expect(issue.selected).toBe(false);
- expect(issue2.selected).toBe(false);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles all if a single issue is selected', () => {
- Store.toggleIssue(issue);
- Store.toggleAll();
-
- expect(issue.selected).toBe(true);
- expect(issue2.selected).toBe(true);
- expect(Store.selectedCount()).toBe(2);
- });
-
- it('adds issue to selected array', () => {
- issue.selected = true;
- Store.addSelectedIssue(issue);
-
- expect(Store.selectedCount()).toBe(1);
- });
-
- it('removes issue from selected array', () => {
- Store.addSelectedIssue(issue);
- Store.removeSelectedIssue(issue);
-
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('returns selected issue index if present', () => {
- Store.toggleIssue(issue);
-
- expect(Store.selectedIssueIndex(issue)).toBe(0);
- });
-
- it('returns -1 if issue is not selected', () => {
- expect(Store.selectedIssueIndex(issue)).toBe(-1);
- });
-
- it('finds the selected issue', () => {
- Store.toggleIssue(issue);
-
- expect(Store.findSelectedIssue(issue)).toBe(issue);
- });
-
- it('does not find a selected issue', () => {
- expect(Store.findSelectedIssue(issue)).toBe(undefined);
- });
-
- it('does not remove from selected issue if tab is not all', () => {
- Store.store.activeTab = 'selected';
-
- Store.toggleIssue(issue);
- Store.toggleIssue(issue);
-
- expect(Store.store.selectedIssues.length).toBe(1);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('gets selected issue array with only selected issues', () => {
- Store.toggleIssue(issue);
- Store.toggleIssue(issue2);
- Store.toggleIssue(issue2);
-
- expect(Store.getSelectedIssues().length).toBe(1);
- });
-});
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js
index c3e3d78ff63..1d21637ceae 100644
--- a/spec/javascripts/bootstrap_linked_tabs_spec.js
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js
@@ -1,10 +1,10 @@
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('Linked Tabs', () => {
- preloadFixtures('static/linked_tabs.html.raw');
+ preloadFixtures('static/linked_tabs.html');
beforeEach(() => {
- loadFixtures('static/linked_tabs.html.raw');
+ loadFixtures('static/linked_tabs.html');
});
describe('when is initialized', () => {
diff --git a/spec/javascripts/breakpoints_spec.js b/spec/javascripts/breakpoints_spec.js
index 5ee777fee3f..fc0d9eb907a 100644
--- a/spec/javascripts/breakpoints_spec.js
+++ b/spec/javascripts/breakpoints_spec.js
@@ -10,4 +10,18 @@ describe('breakpoints', () => {
expect(bp.getBreakpointSize()).toBe(key);
});
});
+
+ describe('isDesktop', () => {
+ it('returns true when screen size is medium', () => {
+ spyOn(bp, 'windowWidth').and.returnValue(breakpoints.md + 10);
+
+ expect(bp.isDesktop()).toBe(true);
+ });
+
+ it('returns false when screen size is small', () => {
+ spyOn(bp, 'windowWidth').and.returnValue(breakpoints.sm + 10);
+
+ expect(bp.isDesktop()).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 1fc0e206d5e..e6a969bd855 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje
const HIDE_CLASS = 'hide';
describe('AjaxFormVariableList', () => {
- preloadFixtures('projects/ci_cd_settings.html.raw');
- preloadFixtures('projects/ci_cd_settings_with_variables.html.raw');
+ preloadFixtures('projects/ci_cd_settings.html');
+ preloadFixtures('projects/ci_cd_settings_with_variables.html');
let container;
let saveButton;
@@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => {
let ajaxVariableList;
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
container = document.querySelector('.js-ci-variable-list-section');
mock = new MockAdapter(axios);
@@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => {
saveButton,
errorBox,
saveEndpoint: container.dataset.saveEndpoint,
+ maskableRegex: container.dataset.maskableRegex,
});
spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough();
@@ -113,7 +114,7 @@ describe('AjaxFormVariableList', () => {
it('hides secret values', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {});
- const row = container.querySelector('.js-row:first-child');
+ const row = container.querySelector('.js-row');
const valueInput = row.querySelector('.js-ci-variable-input-value');
const valuePlaceholder = row.querySelector('.js-secret-value-placeholder');
@@ -168,7 +169,7 @@ describe('AjaxFormVariableList', () => {
describe('updateRowsWithPersistedVariables', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings_with_variables.html.raw');
+ loadFixtures('projects/ci_cd_settings_with_variables.html');
container = document.querySelector('.js-ci-variable-list-section');
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
@@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => {
expect(row.dataset.isPersisted).toEqual('true');
});
});
+
+ describe('maskableRegex', () => {
+ it('takes in the regex provided by the data attribute', () => {
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
+ });
+ });
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index bef59b86d0c..064113e879a 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
const HIDE_CLASS = 'hide';
describe('VariableList', () => {
- preloadFixtures('pipeline_schedules/edit.html.raw');
- preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
- preloadFixtures('projects/ci_cd_settings.html.raw');
+ preloadFixtures('pipeline_schedules/edit.html');
+ preloadFixtures('pipeline_schedules/edit_with_variables.html');
+ preloadFixtures('projects/ci_cd_settings.html');
let $wrapper;
let variableList;
@@ -15,7 +15,7 @@ describe('VariableList', () => {
describe('with only key/value inputs', () => {
describe('with no variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html.raw');
+ loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -82,7 +82,7 @@ describe('VariableList', () => {
describe('with persisted variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ loadFixtures('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -115,7 +115,7 @@ describe('VariableList', () => {
describe('with all inputs(key, value, protected)', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
$wrapper = $('.js-ci-variable-list-section');
$wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false');
@@ -127,29 +127,93 @@ describe('VariableList', () => {
variableList.init();
});
- it('should add another row when editing the last rows protected checkbox', done => {
+ it('should not add another row when editing the last rows protected checkbox', done => {
const $row = $wrapper.find('.js-row:last-child');
$row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
getSetTimeoutPromise()
.then(() => {
- expect($wrapper.find('.js-row').length).toBe(2);
+ expect($wrapper.find('.js-row').length).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
- // Check for the correct default in the new row
- const $protectedInput = $wrapper
- .find('.js-row:last-child')
- .find('.js-ci-variable-input-protected');
+ it('should not add another row when editing the last rows masked checkbox', done => {
+ const $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
- expect($protectedInput.val()).toBe('false');
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find('.js-row').length).toBe(1);
})
.then(done)
.catch(done.fail);
});
+
+ describe('validateMaskability', () => {
+ let $row;
+
+ const maskingErrorElement = '.js-row:last-child .masking-validation-error';
+
+ beforeEach(() => {
+ $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
+ });
+
+ it('has a regex provided via a data attribute', () => {
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ });
+
+ it('allows values that are 8 characters long', done => {
+ $row.find('.js-ci-variable-input-value').val('looooong');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values that are shorter than 8 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('short');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('allows values with base 64 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('abcABC123_+=/-');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values with other special characters', done => {
+ $row.find('.js-ci-variable-input-value').val('1234567$');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
describe('toggleEnableRow method', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ loadFixtures('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -198,7 +262,7 @@ describe('VariableList', () => {
describe('hideValues', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
index 997d0d54d79..4982b68fa81 100644
--- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -2,12 +2,12 @@ import $ from 'jquery';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
describe('NativeFormVariableList', () => {
- preloadFixtures('pipeline_schedules/edit.html.raw');
+ preloadFixtures('pipeline_schedules/edit.html');
let $wrapper;
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html.raw');
+ loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
setupNativeFormVariableList({
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
deleted file mode 100644
index 7928feeadfa..00000000000
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ /dev/null
@@ -1,268 +0,0 @@
-import Clusters from '~/clusters/clusters_bundle';
-import { REQUEST_SUBMITTED, REQUEST_FAILURE, APPLICATION_STATUS } from '~/clusters/constants';
-import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-
-describe('Clusters', () => {
- let cluster;
- preloadFixtures('clusters/show_cluster.html.raw');
-
- beforeEach(() => {
- loadFixtures('clusters/show_cluster.html.raw');
- cluster = new Clusters();
- });
-
- afterEach(() => {
- cluster.destroy();
- });
-
- describe('toggle', () => {
- it('should update the button and the input field on click', done => {
- const toggleButton = document.querySelector(
- '.js-cluster-enable-toggle-area .js-project-feature-toggle',
- );
- const toggleInput = document.querySelector(
- '.js-cluster-enable-toggle-area .js-project-feature-toggle-input',
- );
-
- toggleButton.click();
-
- getSetTimeoutPromise()
- .then(() => {
- expect(toggleButton.classList).not.toContain('is-checked');
-
- expect(toggleInput.getAttribute('value')).toEqual('false');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('showToken', () => {
- it('should update token field type', () => {
- cluster.showTokenButton.click();
-
- expect(cluster.tokenField.getAttribute('type')).toEqual('text');
-
- cluster.showTokenButton.click();
-
- expect(cluster.tokenField.getAttribute('type')).toEqual('password');
- });
-
- it('should update show token button text', () => {
- cluster.showTokenButton.click();
-
- expect(cluster.showTokenButton.textContent).toEqual('Hide');
-
- cluster.showTokenButton.click();
-
- expect(cluster.showTokenButton.textContent).toEqual('Show');
- });
- });
-
- describe('checkForNewInstalls', () => {
- const INITIAL_APP_MAP = {
- helm: { status: null, title: 'Helm Tiller' },
- ingress: { status: null, title: 'Ingress' },
- runner: { status: null, title: 'GitLab Runner' },
- };
-
- it('does not show alert when things transition from initial null state to something', () => {
- cluster.checkForNewInstalls(INITIAL_APP_MAP, {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' },
- });
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).toBeNull();
- });
-
- it('shows an alert when something gets newly installed', () => {
- cluster.checkForNewInstalls(
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
- },
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
- },
- );
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).not.toBeNull();
- expect(flashMessage.textContent.trim()).toEqual(
- 'Helm Tiller was successfully installed on your Kubernetes cluster',
- );
- });
-
- it('shows an alert when multiple things gets newly installed', () => {
- cluster.checkForNewInstalls(
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' },
- },
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' },
- },
- );
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).not.toBeNull();
- expect(flashMessage.textContent.trim()).toEqual(
- 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster',
- );
- });
- });
-
- describe('updateContainer', () => {
- describe('when creating cluster', () => {
- it('should show the creating container', () => {
- cluster.updateContainer(null, 'creating');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
-
- it('should continue to show `creating` banner with subsequent updates of the same status', () => {
- cluster.updateContainer('creating', 'creating');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
- });
-
- describe('when cluster is created', () => {
- it('should show the success container and fresh the page', () => {
- cluster.updateContainer(null, 'created');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
-
- it('should not show a banner when status is already `created`', () => {
- cluster.updateContainer('created', 'created');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
- });
-
- describe('when cluster has error', () => {
- it('should show the error container', () => {
- cluster.updateContainer(null, 'errored', 'this is an error');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.errorReasonContainer.textContent).toContain('this is an error');
- });
-
- it('should show `error` banner when previously `creating`', () => {
- cluster.updateContainer('creating', 'errored');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
- });
- });
- });
-
- describe('installApplication', () => {
- it('tries to install helm', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'helm' });
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined);
- });
-
- it('tries to install ingress', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'ingress' });
-
- expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.ingress.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined);
- });
-
- it('tries to install runner', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.runner.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'runner' });
-
- expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.runner.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined);
- });
-
- it('tries to install jupyter', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null);
- cluster.installApplication({
- id: 'jupyter',
- params: { hostname: cluster.store.state.applications.jupyter.hostname },
- });
-
- expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', {
- hostname: cluster.store.state.applications.jupyter.hostname,
- });
- });
-
- it('sets error request status when the request fails', done => {
- spyOn(cluster.service, 'installApplication').and.returnValue(
- Promise.reject(new Error('STUBBED ERROR')),
- );
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'helm' });
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalled();
-
- getSetTimeoutPromise()
- .then(() => {
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE);
- expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js
deleted file mode 100644
index 8cb9713964e..00000000000
--- a/spec/javascripts/clusters/components/application_row_spec.js
+++ /dev/null
@@ -1,406 +0,0 @@
-import Vue from 'vue';
-import eventHub from '~/clusters/event_hub';
-import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants';
-import applicationRow from '~/clusters/components/application_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
-
-describe('Application Row', () => {
- let vm;
- let ApplicationRow;
-
- beforeEach(() => {
- ApplicationRow = Vue.extend(applicationRow);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('Title', () => {
- it('shows title', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- titleLink: null,
- });
- const title = vm.$el.querySelector('.js-cluster-application-title');
-
- expect(title.tagName).toEqual('SPAN');
- expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
- });
-
- it('shows title link', () => {
- expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined();
-
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- });
- const title = vm.$el.querySelector('.js-cluster-application-title');
-
- expect(title.tagName).toEqual('A');
- expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
- });
- });
-
- describe('Install button', () => {
- it('has indeterminate state on page load', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- });
-
- expect(vm.installButtonLabel).toBeUndefined();
- });
-
- it('has install button', () => {
- const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button');
-
- expect(installationBtn).not.toBe(null);
- });
-
- it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.NOT_INSTALLABLE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.SCHEDULED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLING,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has loading "Installing" when REQUEST_SUBMITTED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_SUBMITTED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installed');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
-
- expect(vm.installButtonLabel).toEqual('Installed');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.ERROR,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_FAILURE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('clicking install button emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- installButton.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: {},
- });
- });
-
- it('clicking install button when installApplicationRequestParams are provided emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- installApplicationRequestParams: { hostname: 'jupyter' },
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- installButton.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: { hostname: 'jupyter' },
- });
- });
-
- it('clicking disabled install button emits nothing', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLING,
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- expect(vm.installButtonDisabled).toEqual(true);
-
- installButton.click();
-
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
- });
-
- describe('Upgrade button', () => {
- it('has indeterminate state on page load', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).toBe(null);
- });
-
- it('has enabled "Upgrade" when "upgradeAvailable" is true', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- upgradeAvailable: true,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Upgrade');
- });
-
- it('has enabled "Retry upgrade" when APPLICATION_STATUS.UPDATE_ERRORED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(vm.upgradeFailed).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Retry upgrade');
- });
-
- it('has disabled "Retry upgrade" when APPLICATION_STATUS.UPDATING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(vm.isUpgrading).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Upgrading');
- });
-
- it('clicking upgrade button emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- upgradeBtn.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: {},
- });
- });
-
- it('clicking disabled upgrade button emits nothing', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- upgradeBtn.click();
-
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
-
- it('displays an error message if application upgrade failed', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- title: 'GitLab Runner',
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const failureMessage = vm.$el.querySelector(
- '.js-cluster-application-upgrade-failure-message',
- );
-
- expect(failureMessage).not.toBe(null);
- expect(failureMessage.innerHTML).toContain(
- 'Something went wrong when upgrading GitLab Runner. Please check the logs and try again.',
- );
- });
- });
-
- describe('Version', () => {
- it('displays a version number if application has been upgraded', () => {
- const version = '0.1.45';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATED,
- version,
- });
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(upgradeDetails.innerHTML).toContain('Upgraded');
- expect(versionEl).not.toBe(null);
- expect(versionEl.innerHTML).toContain(version);
- });
-
- it('contains a link to the chart repo if application has been upgraded', () => {
- const version = '0.1.45';
- const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATED,
- chartRepo,
- version,
- });
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(versionEl.href).toEqual(chartRepo);
- expect(versionEl.target).toEqual('_blank');
- });
-
- it('does not display a version number if application upgrade failed', () => {
- const version = '0.1.45';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- version,
- });
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(upgradeDetails.innerHTML).toContain('failed');
- expect(versionEl).toBe(null);
- });
- });
-
- describe('Error block', () => {
- it('does not show error block when there is no error', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- requestStatus: null,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
-
- expect(generalErrorMessage).toBeNull();
- });
-
- it('shows status reason when APPLICATION_STATUS.ERROR', () => {
- const statusReason = 'We broke it 0.0';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.ERROR,
- statusReason,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
- const statusErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-status-error-message',
- );
-
- expect(generalErrorMessage.textContent.trim()).toEqual(
- `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
- );
-
- expect(statusErrorMessage.textContent.trim()).toEqual(statusReason);
- });
-
- it('shows request reason when REQUEST_FAILURE', () => {
- const requestReason = 'We broke thre request 0.0';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_FAILURE,
- requestReason,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
- const requestErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-request-error-message',
- );
-
- expect(generalErrorMessage.textContent.trim()).toEqual(
- `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
- );
-
- expect(requestErrorMessage.textContent.trim()).toEqual(requestReason);
- });
- });
-});
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
deleted file mode 100644
index 14ef1193984..00000000000
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ /dev/null
@@ -1,313 +0,0 @@
-import Vue from 'vue';
-import applications from '~/clusters/components/applications.vue';
-import { CLUSTER_TYPE } from '~/clusters/constants';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Applications', () => {
- let vm;
- let Applications;
-
- beforeEach(() => {
- Applications = Vue.extend(applications);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('Project cluster applications', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- type: CLUSTER_TYPE.PROJECT,
- applications: {
- helm: { title: 'Helm Tiller' },
- ingress: { title: 'Ingress' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub' },
- knative: { title: 'Knative' },
- },
- });
- });
-
- it('renders a row for Helm Tiller', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
- });
-
- it('renders a row for Ingress', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
- });
-
- it('renders a row for Cert-Manager', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
- });
-
- it('renders a row for Prometheus', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
- });
-
- it('renders a row for GitLab Runner', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
- });
-
- it('renders a row for Jupyter', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull();
- });
-
- it('renders a row for Knative', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
- });
- });
-
- describe('Group cluster applications', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- type: CLUSTER_TYPE.GROUP,
- applications: {
- helm: { title: 'Helm Tiller' },
- ingress: { title: 'Ingress' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub' },
- knative: { title: 'Knative' },
- },
- });
- });
-
- it('renders a row for Helm Tiller', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
- });
-
- it('renders a row for Ingress', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
- });
-
- it('renders a row for Cert-Manager', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
- });
-
- it('renders a row for Prometheus', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull();
- });
-
- it('renders a row for GitLab Runner', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeNull();
- });
-
- it('renders a row for Jupyter', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull();
- });
-
- it('renders a row for Knative', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull();
- });
- });
-
- describe('Ingress application', () => {
- describe('when installed', () => {
- describe('with ip address', () => {
- it('renders ip address with a clipboard button', () => {
- vm = mountComponent(Applications, {
- applications: {
- ingress: {
- title: 'Ingress',
- status: 'installed',
- externalIp: '0.0.0.0',
- },
- helm: { title: 'Helm Tiller' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '' },
- knative: { title: 'Knative', hostname: '' },
- },
- });
-
- expect(vm.$el.querySelector('.js-ip-address').value).toEqual('0.0.0.0');
-
- expect(
- vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
- ).toEqual('0.0.0.0');
- });
- });
-
- describe('without ip address', () => {
- it('renders an input text with a question mark and an alert text', () => {
- vm = mountComponent(Applications, {
- applications: {
- ingress: {
- title: 'Ingress',
- status: 'installed',
- },
- helm: { title: 'Helm Tiller' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '' },
- knative: { title: 'Knative', hostname: '' },
- },
- });
-
- expect(vm.$el.querySelector('.js-ip-address').value).toEqual('?');
-
- expect(vm.$el.querySelector('.js-no-ip-message')).not.toBe(null);
- });
- });
- });
-
- describe('before installing', () => {
- it('does not render the IP address', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller' },
- ingress: { title: 'Ingress' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '' },
- knative: { title: 'Knative', hostname: '' },
- },
- });
-
- expect(vm.$el.textContent).not.toContain('Ingress IP Address');
- expect(vm.$el.querySelector('.js-ip-address')).toBe(null);
- });
- });
-
- describe('Cert-Manager application', () => {
- describe('when not installed', () => {
- it('renders email & allows editing', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller', status: 'installed' },
- ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
- cert_manager: {
- title: 'Cert-Manager',
- email: 'before@example.com',
- status: 'installable',
- },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
- knative: { title: 'Knative', hostname: '', status: 'installable' },
- },
- });
-
- expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com');
- expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null);
- });
- });
-
- describe('when installed', () => {
- it('renders email in readonly', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller', status: 'installed' },
- ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
- cert_manager: {
- title: 'Cert-Manager',
- email: 'after@example.com',
- status: 'installed',
- },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
- knative: { title: 'Knative', hostname: '', status: 'installable' },
- },
- });
-
- expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com');
- expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly');
- });
- });
- });
-
- describe('Jupyter application', () => {
- describe('with ingress installed with ip & jupyter installable', () => {
- it('renders hostname active input', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller', status: 'installed' },
- ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
- knative: { title: 'Knative', hostname: '', status: 'installable' },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null);
- });
- });
-
- describe('with ingress installed without external ip', () => {
- it('does not render hostname input', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller', status: 'installed' },
- ingress: { title: 'Ingress', status: 'installed' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
- knative: { title: 'Knative', hostname: '', status: 'installable' },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname')).toBe(null);
- });
- });
-
- describe('with ingress & jupyter installed', () => {
- it('renders readonly input', () => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller', status: 'installed' },
- ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
- knative: { title: 'Knative', status: 'installed', hostname: '' },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly');
- });
- });
-
- describe('without ingress installed', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- applications: {
- helm: { title: 'Helm Tiller' },
- ingress: { title: 'Ingress' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', status: 'not_installable' },
- knative: { title: 'Knative' },
- },
- });
- });
-
- it('does not render input', () => {
- expect(vm.$el.querySelector('.js-hostname')).toBe(null);
- });
-
- it('renders disabled install button', () => {
- expect(
- vm.$el
- .querySelector(
- '.js-cluster-application-row-jupyter .js-cluster-application-install-button',
- )
- .getAttribute('disabled'),
- ).toEqual('disabled');
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
deleted file mode 100644
index 3c3d9977ffb..00000000000
--- a/spec/javascripts/clusters/services/mock_data.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import { APPLICATION_STATUS } from '~/clusters/constants';
-
-const CLUSTERS_MOCK_DATA = {
- GET: {
- '/gitlab-org/gitlab-shell/clusters/1/status.json': {
- data: {
- status: 'errored',
- status_reason: 'Failed to request to CloudPlatform.',
- applications: [
- {
- name: 'helm',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: null,
- },
- {
- name: 'ingress',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- external_ip: null,
- },
- {
- name: 'runner',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: null,
- },
- {
- name: 'prometheus',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- },
- {
- name: 'jupyter',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: 'Cannot connect',
- },
- {
- name: 'knative',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: 'Cannot connect',
- },
- {
- name: 'cert_manager',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- email: 'test@example.com',
- },
- ],
- },
- },
- '/gitlab-org/gitlab-shell/clusters/2/status.json': {
- data: {
- status: 'errored',
- status_reason: 'Failed to request to CloudPlatform.',
- applications: [
- {
- name: 'helm',
- status: APPLICATION_STATUS.INSTALLED,
- status_reason: null,
- },
- {
- name: 'ingress',
- status: APPLICATION_STATUS.INSTALLED,
- status_reason: 'Cannot connect',
- external_ip: '1.1.1.1',
- },
- {
- name: 'runner',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: null,
- },
- {
- name: 'prometheus',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- },
- {
- name: 'jupyter',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: 'Cannot connect',
- },
- {
- name: 'knative',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: 'Cannot connect',
- },
- {
- name: 'cert_manager',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- email: 'test@example.com',
- },
- ],
- },
- },
- },
- POST: {
- '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {},
- },
-};
-
-const DEFAULT_APPLICATION_STATE = {
- id: 'some-app',
- title: 'My App',
- titleLink: 'https://about.gitlab.com/',
- description: 'Some description about this interesting application!',
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
-};
-
-export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE };
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
deleted file mode 100644
index 37a4d6614f6..00000000000
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import ClustersStore from '~/clusters/stores/clusters_store';
-import { APPLICATION_STATUS } from '~/clusters/constants';
-import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
-
-describe('Clusters Store', () => {
- let store;
-
- beforeEach(() => {
- store = new ClustersStore();
- });
-
- describe('updateStatus', () => {
- it('should store new status', () => {
- expect(store.state.status).toEqual(null);
-
- const newStatus = 'errored';
- store.updateStatus(newStatus);
-
- expect(store.state.status).toEqual(newStatus);
- });
- });
-
- describe('updateStatusReason', () => {
- it('should store new reason', () => {
- expect(store.state.statusReason).toEqual(null);
-
- const newReason = 'Something went wrong!';
- store.updateStatusReason(newReason);
-
- expect(store.state.statusReason).toEqual(newReason);
- });
- });
-
- describe('updateAppProperty', () => {
- it('should store new request status', () => {
- expect(store.state.applications.helm.requestStatus).toEqual(null);
-
- const newStatus = APPLICATION_STATUS.INSTALLING;
- store.updateAppProperty('helm', 'requestStatus', newStatus);
-
- expect(store.state.applications.helm.requestStatus).toEqual(newStatus);
- });
-
- it('should store new request reason', () => {
- expect(store.state.applications.helm.requestReason).toEqual(null);
-
- const newReason = 'We broke it.';
- store.updateAppProperty('helm', 'requestReason', newReason);
-
- expect(store.state.applications.helm.requestReason).toEqual(newReason);
- });
- });
-
- describe('updateStateFromServer', () => {
- it('should store new polling data from server', () => {
- const mockResponseData =
- CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data;
- store.updateStateFromServer(mockResponseData);
-
- expect(store.state).toEqual({
- helpPath: null,
- ingressHelpPath: null,
- status: mockResponseData.status,
- statusReason: mockResponseData.status_reason,
- rbac: false,
- applications: {
- helm: {
- title: 'Helm Tiller',
- status: mockResponseData.applications[0].status,
- statusReason: mockResponseData.applications[0].status_reason,
- requestStatus: null,
- requestReason: null,
- },
- ingress: {
- title: 'Ingress',
- status: mockResponseData.applications[1].status,
- statusReason: mockResponseData.applications[1].status_reason,
- requestStatus: null,
- requestReason: null,
- externalIp: null,
- },
- runner: {
- title: 'GitLab Runner',
- status: mockResponseData.applications[2].status,
- statusReason: mockResponseData.applications[2].status_reason,
- requestStatus: null,
- requestReason: null,
- version: mockResponseData.applications[2].version,
- upgradeAvailable: mockResponseData.applications[2].update_available,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
- },
- prometheus: {
- title: 'Prometheus',
- status: mockResponseData.applications[3].status,
- statusReason: mockResponseData.applications[3].status_reason,
- requestStatus: null,
- requestReason: null,
- },
- jupyter: {
- title: 'JupyterHub',
- status: mockResponseData.applications[4].status,
- statusReason: mockResponseData.applications[4].status_reason,
- requestStatus: null,
- requestReason: null,
- hostname: '',
- },
- knative: {
- title: 'Knative',
- status: mockResponseData.applications[5].status,
- statusReason: mockResponseData.applications[5].status_reason,
- requestStatus: null,
- requestReason: null,
- hostname: null,
- externalIp: null,
- },
- cert_manager: {
- title: 'Cert-Manager',
- status: mockResponseData.applications[6].status,
- statusReason: mockResponseData.applications[6].status_reason,
- requestStatus: null,
- requestReason: null,
- email: mockResponseData.applications[6].email,
- },
- },
- });
- });
-
- it('sets default hostname for jupyter when ingress has a ip address', () => {
- const mockResponseData =
- CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
-
- store.updateStateFromServer(mockResponseData);
-
- expect(store.state.applications.jupyter.hostname).toEqual(
- `jupyter.${store.state.applications.ingress.externalIp}.nip.io`,
- );
- });
- });
-});
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index dc5737558c0..bb90e53e525 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Issuable right sidebar collapsed todo toggle', () => {
- const fixtureName = 'issues/open-issue.html.raw';
+ const fixtureName = 'issues/open-issue.html';
const jsonFixtureName = 'todos/todos.json';
let mock;
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 04c8ab44405..fec01b1f0a3 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -87,7 +87,7 @@ describe('Pipelines table in Commits and Merge requests', function() {
};
vm.$nextTick(() => {
- vm.$el.querySelector('.js-next-button a').click();
+ vm.$el.querySelector('.js-next-button .page-link').click();
expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
done();
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
index 9cf72d7c55b..a814952faab 100644
--- a/spec/javascripts/create_item_dropdown_spec.js
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [
];
describe('CreateItemDropdown', () => {
- preloadFixtures('static/create_item_dropdown.html.raw');
+ preloadFixtures('static/create_item_dropdown.html');
let $wrapperEl;
let createItemDropdown;
@@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => {
}
beforeEach(() => {
- loadFixtures('static/create_item_dropdown.html.raw');
+ loadFixtures('static/create_item_dropdown.html');
$wrapperEl = $('.js-create-item-dropdown-fixture-root');
});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 5abdfe695d0..1aabf3c2132 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -1,15 +1,24 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
import NoChanges from '~/diffs/components/no_changes.vue';
import DiffFile from '~/diffs/components/diff_file.vue';
+import Mousetrap from 'mousetrap';
+import CompareVersions from '~/diffs/components/compare_versions.vue';
+import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
+import CommitWidget from '~/diffs/components/commit_widget.vue';
+import TreeList from '~/diffs/components/tree_list.vue';
import createDiffsStore from '../create_diffs_store';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+const mergeRequestDiff = { version_index: 1 };
describe('diffs/components/app', () => {
const oldMrTabs = window.mrTabs;
let store;
- let vm;
+ let wrapper;
function createComponent(props = {}, extendStore = () => {}) {
const localVue = createLocalVue();
@@ -21,7 +30,7 @@ describe('diffs/components/app', () => {
extendStore(store);
- vm = shallowMount(localVue.extend(App), {
+ wrapper = shallowMount(localVue.extend(App), {
localVue,
propsData: {
endpoint: `${TEST_HOST}/diff/endpoint`,
@@ -38,7 +47,6 @@ describe('diffs/components/app', () => {
// setup globals (needed for component to mount :/)
window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']);
window.mrTabs.expandViewContainer = jasmine.createSpy();
- window.location.hash = 'ABC_123';
});
afterEach(() => {
@@ -46,25 +54,85 @@ describe('diffs/components/app', () => {
window.mrTabs = oldMrTabs;
// reset component
- vm.destroy();
+ wrapper.destroy();
+ });
+
+ it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.showTreeList = false;
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(true);
+ });
+
+ it('does not add container-limiting classes when showFileTree is false with inline diffs', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.showTreeList = true;
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false);
+ });
+
+ it('does not add container-limiting classes when isFluidLayout', () => {
+ createComponent({ isFluidLayout: true }, ({ state }) => {
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false);
+ });
+
+ it('displays loading icon on loading', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.isLoading = true;
+ });
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
+ it('displays diffs container when not loading', () => {
+ createComponent();
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(false);
+ expect(wrapper.contains('#diffs')).toBe(true);
});
it('does not show commit info', () => {
createComponent();
- expect(vm.contains('.blob-commit-info')).toBe(false);
+ expect(wrapper.contains('.blob-commit-info')).toBe(false);
});
- it('sets highlighted row if hash exists in location object', done => {
- createComponent({
- shouldShow: true,
+ describe('row highlighting', () => {
+ beforeEach(() => {
+ window.location.hash = 'ABC_123';
});
- // Component uses $nextTick so we wait until that has finished
- setTimeout(() => {
- expect(store.state.diffs.highlightedRow).toBe('ABC_123');
+ it('sets highlighted row if hash exists in location object', done => {
+ createComponent({
+ shouldShow: true,
+ });
- done();
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.highlightedRow).toBe('ABC_123');
+
+ done();
+ });
+ });
+
+ it('marks current diff file based on currently highlighted row', done => {
+ createComponent({
+ shouldShow: true,
+ });
+
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.currentDiffFileId).toBe('ABC');
+
+ done();
+ });
});
});
@@ -76,7 +144,7 @@ describe('diffs/components/app', () => {
it('sets initial width when no localStorage has been set', () => {
createComponent();
- expect(vm.vm.treeWidth).toEqual(320);
+ expect(wrapper.vm.treeWidth).toEqual(320);
});
it('sets initial width to localStorage size', () => {
@@ -84,13 +152,26 @@ describe('diffs/components/app', () => {
createComponent();
- expect(vm.vm.treeWidth).toEqual(200);
+ expect(wrapper.vm.treeWidth).toEqual(200);
});
it('sets width of tree list', () => {
createComponent();
- expect(vm.find('.js-diff-tree-list').element.style.width).toEqual('320px');
+ expect(wrapper.find('.js-diff-tree-list').element.style.width).toEqual('320px');
+ });
+ });
+
+ it('marks current diff file based on currently highlighted row', done => {
+ createComponent({
+ shouldShow: true,
+ });
+
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.currentDiffFileId).toBe('ABC');
+
+ done();
});
});
@@ -98,27 +179,305 @@ describe('diffs/components/app', () => {
it('renders empty state when no diff files exist', () => {
createComponent();
- expect(vm.contains(NoChanges)).toBe(true);
+ expect(wrapper.contains(NoChanges)).toBe(true);
});
it('does not render empty state when diff files exist', () => {
- createComponent({}, () => {
- store.state.diffs.diffFiles.push({
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({
id: 1,
});
});
- expect(vm.contains(NoChanges)).toBe(false);
- expect(vm.findAll(DiffFile).length).toBe(1);
+ expect(wrapper.contains(NoChanges)).toBe(false);
+ expect(wrapper.findAll(DiffFile).length).toBe(1);
});
it('does not render empty state when versions match', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.startVersion = mergeRequestDiff;
+ state.diffs.mergeRequestDiff = mergeRequestDiff;
+ });
+
+ expect(wrapper.contains(NoChanges)).toBe(false);
+ });
+ });
+
+ describe('keyboard shortcut navigation', () => {
+ const mappings = {
+ '[': -1,
+ k: -1,
+ ']': +1,
+ j: +1,
+ };
+ let spy;
+
+ describe('visible app', () => {
+ beforeEach(() => {
+ spy = jasmine.createSpy('spy');
+
+ createComponent({
+ shouldShow: true,
+ });
+ wrapper.setMethods({
+ jumpToFile: spy,
+ });
+ });
+
+ it('calls `jumpToFile()` with correct parameter whenever pre-defined key is pressed', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Object.keys(mappings).forEach(function(key) {
+ Mousetrap.trigger(key);
+
+ expect(spy.calls.mostRecent().args).toEqual([mappings[key]]);
+ });
+
+ expect(spy.calls.count()).toEqual(Object.keys(mappings).length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not call `jumpToFile()` when unknown key is pressed', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Mousetrap.trigger('d');
+
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('hideen app', () => {
+ beforeEach(() => {
+ spy = jasmine.createSpy('spy');
+
+ createComponent({
+ shouldShow: false,
+ });
+ wrapper.setMethods({
+ jumpToFile: spy,
+ });
+ });
+
+ it('stops calling `jumpToFile()` when application is hidden', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Object.keys(mappings).forEach(function(key) {
+ Mousetrap.trigger(key);
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('jumpToFile', () => {
+ let spy;
+
+ beforeEach(() => {
+ spy = jasmine.createSpy();
+
createComponent({}, () => {
- store.state.diffs.startVersion = { version_index: 1 };
- store.state.diffs.mergeRequestDiff = { version_index: 1 };
+ store.state.diffs.diffFiles = [
+ { file_hash: '111', file_path: '111.js' },
+ { file_hash: '222', file_path: '222.js' },
+ { file_hash: '333', file_path: '333.js' },
+ ];
+ });
+
+ wrapper.setMethods({
+ scrollToFile: spy,
});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('jumps to next and previous files in the list', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.vm.jumpToFile(+1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['222.js']);
+ store.state.diffs.currentDiffFileId = '222';
+ wrapper.vm.jumpToFile(+1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['333.js']);
+ store.state.diffs.currentDiffFileId = '333';
+ wrapper.vm.jumpToFile(-1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['222.js']);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not jump to previous file from the first one', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ store.state.diffs.currentDiffFileId = '333';
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(2);
+
+ wrapper.vm.jumpToFile(+1);
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(2);
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not jump to next file from the last one', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.currentDiffIndex).toEqual(0);
+
+ wrapper.vm.jumpToFile(-1);
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(0);
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('diffs', () => {
+ it('should render compare versions component', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.mergeRequestDiffs = diffsMockData;
+ state.diffs.targetBranchName = 'target-branch';
+ state.diffs.mergeRequestDiff = mergeRequestDiff;
+ });
+
+ expect(wrapper.contains(CompareVersions)).toBe(true);
+ expect(wrapper.find(CompareVersions).props()).toEqual(
+ jasmine.objectContaining({
+ targetBranch: {
+ branchName: 'target-branch',
+ versionIndex: -1,
+ path: '',
+ },
+ mergeRequestDiffs: diffsMockData,
+ mergeRequestDiff,
+ }),
+ );
+ });
+
+ it('should render hidden files warning if render overflow warning is present', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.renderOverflowWarning = true;
+ state.diffs.realSize = '5';
+ state.diffs.plainDiffPath = 'plain diff path';
+ state.diffs.emailPatchPath = 'email patch path';
+ state.diffs.size = 1;
+ });
+
+ expect(wrapper.contains(HiddenFilesWarning)).toBe(true);
+ expect(wrapper.find(HiddenFilesWarning).props()).toEqual(
+ jasmine.objectContaining({
+ total: '5',
+ plainDiffPath: 'plain diff path',
+ emailPatchPath: 'email patch path',
+ visible: 1,
+ }),
+ );
+ });
+
+ it('should display commit widget if store has a commit', () => {
+ createComponent({}, () => {
+ store.state.diffs.commit = {
+ author: 'John Doe',
+ };
+ });
+
+ expect(wrapper.contains(CommitWidget)).toBe(true);
+ });
+
+ it('should display diff file if there are diff files', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ expect(wrapper.contains(DiffFile)).toBe(true);
+ });
+
+ it('should render tree list', () => {
+ createComponent();
+
+ expect(wrapper.find(TreeList).exists()).toBe(true);
+ });
+ });
+
+ describe('hideTreeListIfJustOneFile', () => {
+ let toggleShowTreeList;
+
+ beforeEach(() => {
+ toggleShowTreeList = jasmine.createSpy('toggleShowTreeList');
+ });
+
+ afterEach(() => {
+ localStorage.removeItem('mr_tree_show');
+ });
+
+ it('calls toggleShowTreeList when only 1 file', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
+
+ expect(toggleShowTreeList).toHaveBeenCalledWith(false);
+ });
+
+ it('does not call toggleShowTreeList when more than 1 file', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ state.diffs.diffFiles.push({ sha: '124' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
+
+ expect(toggleShowTreeList).not.toHaveBeenCalled();
+ });
+
+ it('does not call toggleShowTreeList when localStorage is set', () => {
+ localStorage.setItem('mr_tree_show', 'true');
+
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
- expect(vm.contains(NoChanges)).toBe(false);
+ expect(toggleShowTreeList).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
deleted file mode 100644
index 7237274eb43..00000000000
--- a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
+++ /dev/null
@@ -1 +0,0 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js
index 50e45f48af3..cfe0c4bad71 100644
--- a/spec/javascripts/diffs/components/commit_item_spec.js
+++ b/spec/javascripts/diffs/components/commit_item_spec.js
@@ -1,14 +1,14 @@
import Vue from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { trimText } from 'spec/helpers/vue_component_helper';
+import { trimText } from 'spec/helpers/text_helper';
import { getTimeago } from '~/lib/utils/datetime_utility';
import CommitItem from '~/diffs/components/commit_item.vue';
import getDiffWithCommit from '../mock_data/diff_with_commit';
const TEST_AUTHOR_NAME = 'test';
const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com';
-const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`;
+const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=40`;
const TEST_SIGNATURE_HTML = '<a>Legit commit</a>';
const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`;
@@ -65,7 +65,7 @@ describe('diffs/components/commit_item', () => {
const imgElement = avatarElement.querySelector('img');
expect(avatarElement).toHaveAttr('href', commit.author.web_url);
- expect(imgElement).toHaveClass('s36');
+ expect(imgElement).toHaveClass('s40');
expect(imgElement).toHaveAttr('alt', commit.author.name);
expect(imgElement).toHaveAttr('src', commit.author.avatar_url);
});
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
index 53b9ac22fc0..8a3834d542f 100644
--- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -1,34 +1,161 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
import { shallowMount, createLocalVue } from '@vue/test-utils';
import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue';
import diffsMockData from '../mock_data/merge_request_diffs';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+
+const localVue = createLocalVue();
+const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+const startVersion = { version_index: 4 };
+const mergeRequestVersion = {
+ version_path: '123',
+};
+const baseVersionPath = '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37';
describe('CompareVersionsDropdown', () => {
let wrapper;
- const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
- const factory = (options = {}) => {
- const localVue = createLocalVue();
+ const findSelectedVersion = () => wrapper.find('.dropdown-menu-toggle');
+ const findVersionsListElements = () => wrapper.findAll('li');
+ const findLinkElement = index =>
+ findVersionsListElements()
+ .at(index)
+ .find('a');
+ const findLastLink = () => findLinkElement(findVersionsListElements().length - 1);
- wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options });
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(localVue.extend(CompareVersionsDropdown), {
+ localVue,
+ sync: false,
+ propsData: { ...props },
+ });
};
afterEach(() => {
wrapper.destroy();
});
- it('should render a correct base version link', () => {
- factory({
- propsData: {
- baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ describe('selected version name', () => {
+ it('shows latest version when latest is selected', () => {
+ createComponent({
+ mergeRequestVersion,
+ startVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findSelectedVersion().text()).toBe('latest version');
+ });
+
+ it('shows target branch name for base branch', () => {
+ createComponent({
+ targetBranch,
+ });
+
+ expect(findSelectedVersion().text()).toBe('tmp-wine-dev');
+ });
+
+ it('shows correct version for non-base and non-latest branches', () => {
+ createComponent({
+ startVersion,
+ targetBranch,
+ });
+
+ expect(findSelectedVersion().text()).toBe(`version ${startVersion.version_index}`);
+ });
+ });
+
+ describe('target versions list', () => {
+ it('should have the same length as otherVersions if merge request version is present', () => {
+ createComponent({
+ mergeRequestVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findVersionsListElements().length).toEqual(diffsMockData.length);
+ });
+
+ it('should have an otherVersions length plus 1 if no merge request version is present', () => {
+ createComponent({
+ targetBranch,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findVersionsListElements().length).toEqual(diffsMockData.length + 1);
+ });
+
+ it('should have base branch link as active on base branch', () => {
+ createComponent({
+ targetBranch,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findLastLink().classes()).toContain('is-active');
+ });
+
+ it('should have correct branch link as active if start version present', () => {
+ createComponent({
+ targetBranch,
+ startVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findLinkElement(0).classes()).toContain('is-active');
+ });
+
+ it('should render a correct base version link', () => {
+ createComponent({
+ baseVersionPath,
otherVersions: diffsMockData.slice(1),
targetBranch,
- },
+ });
+
+ expect(findLastLink().attributes('href')).toEqual(baseVersionPath);
+ expect(findLastLink().text()).toContain('(base)');
+ });
+
+ it('should not render commits count if no showCommitsCount is passed', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const commitsCount = diffsMockData[0].commits_count;
+
+ expect(findLinkElement(0).text()).not.toContain(`${commitsCount} commit`);
+ });
+
+ it('should render correct commits count if showCommitsCount is passed', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ showCommitCount: true,
+ });
+
+ const commitsCount = diffsMockData[0].commits_count;
+
+ expect(findLinkElement(0).text()).toContain(`${commitsCount} commit`);
+ });
+
+ it('should render correct commit sha', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const commitShaElement = findLinkElement(0).find('.commit-sha');
+
+ expect(commitShaElement.text()).toBe(diffsMockData[0].short_commit_sha);
});
- const links = wrapper.findAll('a');
- const lastLink = links.wrappers[links.length - 1];
+ it('should render correct time-ago ', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const timeAgoElement = findLinkElement(0).find(TimeAgo);
- expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath'));
+ expect(timeAgoElement.exists()).toBe(true);
+ expect(timeAgoElement.props('time')).toBe(diffsMockData[0].created_at);
+ });
});
});
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index e886f962d2f..77f8352047c 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -66,6 +66,26 @@ describe('CompareVersions', () => {
expect(inlineBtn.innerHTML).toContain('Inline');
expect(parallelBtn.innerHTML).toContain('Side-by-side');
});
+
+ it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
+ vm.isLimitedContainer = true;
+
+ vm.$nextTick(() => {
+ const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width');
+
+ expect(limitedContainer).not.toBeNull();
+ });
+ });
+
+ it('does not add container-limiting classes when showFileTree is false with inline diffs', () => {
+ vm.isLimitedContainer = false;
+
+ vm.$nextTick(() => {
+ const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width');
+
+ expect(limitedContainer).toBeNull();
+ });
+ });
});
describe('setInlineDiffViewType', () => {
diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js
index a1bb51963d6..124bdeea45d 100644
--- a/spec/javascripts/diffs/components/diff_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_content_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffContentComponent from '~/diffs/components/diff_content.vue';
-import { createStore } from '~/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
import '~/behaviors/markdown/render_gfm';
@@ -29,6 +29,10 @@ describe('DiffContent', () => {
});
});
+ afterEach(() => {
+ vm.$destroy();
+ });
+
describe('text based files', () => {
it('should render diff inline view', done => {
vm.$store.state.diffs.diffViewType = 'inline';
@@ -49,6 +53,16 @@ describe('DiffContent', () => {
done();
});
});
+
+ it('renders rendering more lines loading icon', done => {
+ vm.diffFile.renderingLines = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
});
describe('empty files', () => {
diff --git a/spec/javascripts/diffs/components/diff_discussions_spec.js b/spec/javascripts/diffs/components/diff_discussions_spec.js
index 0bc9da5ad0f..f7f0ab83c21 100644
--- a/spec/javascripts/diffs/components/diff_discussions_spec.js
+++ b/spec/javascripts/diffs/components/diff_discussions_spec.js
@@ -1,90 +1,103 @@
-import Vue from 'vue';
+import { mount, createLocalVue } from '@vue/test-utils';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import DiscussionNotes from '~/notes/components/discussion_notes.vue';
+import Icon from '~/vue_shared/components/icon.vue';
import { createStore } from '~/mr_notes/stores';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import '~/behaviors/markdown/render_gfm';
import discussionsMockData from '../mock_data/diff_discussions';
+const localVue = createLocalVue();
+
describe('DiffDiscussions', () => {
- let vm;
+ let store;
+ let wrapper;
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
- function createComponent(props = {}) {
- const store = createStore();
-
- vm = createComponentWithStore(Vue.extend(DiffDiscussions), store, {
- discussions: getDiscussionsMockData(),
- ...props,
- }).$mount();
- }
+ const createComponent = props => {
+ store = createStore();
+ wrapper = mount(localVue.extend(DiffDiscussions), {
+ store,
+ propsData: {
+ discussions: getDiscussionsMockData(),
+ ...props,
+ },
+ localVue,
+ sync: false,
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('template', () => {
it('should have notes list', () => {
createComponent();
- expect(vm.$el.querySelectorAll('.discussion .note.timeline-entry').length).toEqual(5);
+ expect(wrapper.find(NoteableDiscussion).exists()).toBe(true);
+ expect(wrapper.find(DiscussionNotes).exists()).toBe(true);
+ expect(wrapper.find(DiscussionNotes).findAll(TimelineEntryItem).length).toBe(
+ discussionsMockData.notes.length,
+ );
});
});
describe('image commenting', () => {
+ const findDiffNotesToggle = () => wrapper.find('.js-diff-notes-toggle');
+
it('renders collapsible discussion button', () => {
createComponent({ shouldCollapseDiscussions: true });
+ const diffNotesToggle = findDiffNotesToggle();
- expect(vm.$el.querySelector('.js-diff-notes-toggle')).not.toBe(null);
- expect(vm.$el.querySelector('.js-diff-notes-toggle svg')).not.toBe(null);
- expect(vm.$el.querySelector('.js-diff-notes-toggle').classList).toContain(
- 'diff-notes-collapse',
- );
+ expect(diffNotesToggle.exists()).toBe(true);
+ expect(diffNotesToggle.find(Icon).exists()).toBe(true);
+ expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true);
});
it('dispatches toggleDiscussion when clicking collapse button', () => {
createComponent({ shouldCollapseDiscussions: true });
+ spyOn(wrapper.vm.$store, 'dispatch').and.stub();
+ const diffNotesToggle = findDiffNotesToggle();
+ diffNotesToggle.trigger('click');
- spyOn(vm.$store, 'dispatch').and.stub();
-
- vm.$el.querySelector('.js-diff-notes-toggle').click();
-
- expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
- discussionId: vm.discussions[0].id,
+ expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
+ discussionId: discussionsMockData.id,
});
});
- it('renders expand button when discussion is collapsed', done => {
- createComponent({ shouldCollapseDiscussions: true });
-
- vm.discussions[0].expanded = false;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.js-diff-notes-toggle').textContent.trim()).toBe('1');
- expect(vm.$el.querySelector('.js-diff-notes-toggle').className).toContain(
- 'btn-transparent badge badge-pill',
- );
+ it('renders expand button when discussion is collapsed', () => {
+ const discussions = getDiscussionsMockData();
+ discussions[0].expanded = false;
+ createComponent({ discussions, shouldCollapseDiscussions: true });
+ const diffNotesToggle = findDiffNotesToggle();
- done();
- });
+ expect(diffNotesToggle.text().trim()).toBe('1');
+ expect(diffNotesToggle.classes()).toEqual(
+ jasmine.arrayContaining(['btn-transparent', 'badge', 'badge-pill']),
+ );
});
- it('hides discussion when collapsed', done => {
- createComponent({ shouldCollapseDiscussions: true });
+ it('hides discussion when collapsed', () => {
+ const discussions = getDiscussionsMockData();
+ discussions[0].expanded = false;
+ createComponent({ discussions, shouldCollapseDiscussions: true });
- vm.discussions[0].expanded = false;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.note-discussion').style.display).toBe('none');
-
- done();
- });
+ expect(wrapper.find(NoteableDiscussion).isVisible()).toBe(false);
});
it('renders badge on avatar', () => {
- createComponent({ renderAvatarBadge: true, discussions: [{ ...discussionsMockData }] });
-
- expect(vm.$el.querySelector('.user-avatar-link .badge-pill')).not.toBe(null);
- expect(vm.$el.querySelector('.user-avatar-link .badge-pill').textContent.trim()).toBe('1');
+ createComponent({ renderAvatarBadge: true });
+ const noteableDiscussion = wrapper.find(NoteableDiscussion);
+
+ expect(noteableDiscussion.find('.badge-pill').exists()).toBe(true);
+ expect(
+ noteableDiscussion
+ .find('.badge-pill')
+ .text()
+ .trim(),
+ ).toBe('1');
});
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 005a4751ea1..596a1ba5ad2 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -3,7 +3,7 @@ import Vuex from 'vuex';
import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import mountComponent, { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffDiscussionsMockData from '../mock_data/diff_discussions';
import { diffViewerModes } from '~/ide/constants';
@@ -249,6 +249,75 @@ describe('diff_file_header', () => {
expect(vm.$emit).not.toHaveBeenCalled();
});
});
+
+ describe('handleFileNameClick', () => {
+ let e;
+
+ beforeEach(() => {
+ e = { preventDefault: () => {} };
+ spyOn(e, 'preventDefault');
+ });
+
+ describe('when file name links to other page', () => {
+ it('does not call preventDefault if submodule tree url exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ diffFile: { ...props.diffFile, submodule_tree_url: 'foobar.com' },
+ });
+
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does not call preventDefault if submodule_link exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ diffFile: { ...props.diffFile, submodule_link: 'foobar.com' },
+ });
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does not call preventDefault if discussionPath exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ discussionPath: 'Foo bar',
+ });
+
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('scrolling to diff', () => {
+ let scrollToElement;
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ spyOn(document, 'querySelector').and.returnValue(el);
+ scrollToElement = spyOnDependency(DiffFileHeader, 'scrollToElement');
+ vm = mountComponent(Component, props);
+
+ vm.handleFileNameClick(e);
+ });
+
+ it('calls scrollToElement with file content', () => {
+ expect(scrollToElement).toHaveBeenCalledWith(el);
+ });
+
+ it('element adds the content id to the window location', () => {
+ expect(window.location.hash).toContain(props.diffFile.file_hash);
+ });
+
+ it('calls preventDefault when button does not link to other page', () => {
+ expect(e.preventDefault).toHaveBeenCalled();
+ });
+ });
+ });
});
describe('template', () => {
@@ -382,7 +451,7 @@ describe('diff_file_header', () => {
props.diffFile.edit_path = '/';
vm = mountComponentWithStore(Component, { props, store });
- expect(vm.$el.querySelector('.js-edit-blob')).toContainText('Edit');
+ expect(vm.$el.querySelector('.js-edit-blob')).not.toBe(null);
});
it('should not show edit button when file is deleted', () => {
@@ -491,5 +560,154 @@ describe('diff_file_header', () => {
});
});
});
+
+ describe('file actions', () => {
+ it('should not render if diff file has a submodule', () => {
+ props.diffFile.submodule = 'submodule';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).toEqual(null);
+ });
+
+ it('should not render if add merge request buttons is false', () => {
+ props.addMergeRequestButtons = false;
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).toEqual(null);
+ });
+
+ describe('with add merge request buttons enabled', () => {
+ beforeEach(() => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.edit_path = 'edit-path';
+ });
+
+ const viewReplacedFileButton = () => vm.$el.querySelector('.js-view-replaced-file');
+ const viewFileButton = () => vm.$el.querySelector('.js-view-file-button');
+ const externalUrl = () => vm.$el.querySelector('.js-external-url');
+
+ it('should render if add merge request buttons is true and diff file does not have a submodule', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).not.toEqual(null);
+ });
+
+ it('should not render view replaced file button if no replaced view path is present', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewReplacedFileButton()).toEqual(null);
+ });
+
+ it('should render view replaced file button if replaced view path is present', () => {
+ props.diffFile.replaced_view_path = 'replaced-view-path';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewReplacedFileButton()).not.toEqual(null);
+ expect(viewReplacedFileButton().getAttribute('href')).toBe('replaced-view-path');
+ });
+
+ it('should render correct file view button path', () => {
+ props.diffFile.view_path = 'view-path';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewFileButton().getAttribute('href')).toBe('view-path');
+ expect(viewFileButton().getAttribute('data-original-title')).toEqual(
+ `View file @ ${props.diffFile.content_sha.substr(0, 8)}`,
+ );
+ });
+
+ it('should not render external url view link if diff file has no external url', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(externalUrl()).toEqual(null);
+ });
+
+ it('should render external url view link if diff file has external url', () => {
+ props.diffFile.external_url = 'external_url';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(externalUrl()).not.toEqual(null);
+ expect(externalUrl().getAttribute('href')).toBe('external_url');
+ });
+ });
+
+ describe('without file blob', () => {
+ beforeEach(() => {
+ props.diffFile.blob = null;
+ props.addMergeRequestButtons = true;
+ vm = mountComponentWithStore(Component, { props, store });
+ });
+
+ it('should not render toggle discussions button', () => {
+ expect(vm.$el.querySelector('.js-btn-vue-toggle-comments')).toEqual(null);
+ });
+
+ it('should not render edit button', () => {
+ expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
+ });
+ });
+ });
+ });
+
+ describe('expand full file button', () => {
+ beforeEach(() => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.edit_path = '/';
+ });
+
+ it('does not render button', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.js-expand-file')).toBe(null);
+ });
+
+ it('renders button', () => {
+ props.diffFile.is_fully_expanded = false;
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.js-expand-file')).not.toBe(null);
+ });
+
+ it('shows fully expanded text', () => {
+ props.diffFile.is_fully_expanded = false;
+ props.diffFile.isShowingFullFile = true;
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.ic-doc-changes')).not.toBeNull();
+ });
+
+ it('shows expand text', () => {
+ props.diffFile.is_fully_expanded = false;
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.ic-doc-expand')).not.toBeNull();
+ });
+
+ it('renders loading icon', () => {
+ props.diffFile.is_fully_expanded = false;
+ props.diffFile.isLoadingFullFile = true;
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.js-expand-file .loading-container')).not.toBe(null);
+ });
+
+ it('calls toggleFullDiff on click', () => {
+ props.diffFile.is_fully_expanded = false;
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ spyOn(vm.$store, 'dispatch').and.stub();
+
+ vm.$el.querySelector('.js-expand-file').click();
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ 'diffs/toggleFullDiff',
+ props.diffFile.file_path,
+ );
+ });
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 65a1c9b8f15..ef4589ada48 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
-import store from '~/mr_notes/stores';
+import store from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -109,6 +109,31 @@ describe('DiffFile', () => {
done();
});
});
+
+ it('should update store state', done => {
+ spyOn(vm.$store, 'dispatch');
+
+ vm.isCollapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/setFileCollapsed', {
+ filePath: vm.file.file_path,
+ collapsed: true,
+ });
+
+ done();
+ });
+ });
+
+ it('updates local state when changing file state', done => {
+ vm.file.viewer.collapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.isCollapsed).toBe(true);
+
+ done();
+ });
+ });
});
});
@@ -116,18 +141,16 @@ describe('DiffFile', () => {
it('should have too large warning and blob link', done => {
const BLOB_LINK = '/file/view/path';
vm.file.viewer.error = diffViewerErrors.too_large;
+ vm.file.viewer.error_message =
+ 'This source diff could not be displayed because it is too large';
vm.file.view_path = BLOB_LINK;
+ vm.file.renderIt = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain(
'This source diff could not be displayed because it is too large',
);
- expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined();
- expect(
- vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK),
- ).toBeGreaterThan(-1);
-
done();
});
});
diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js
deleted file mode 100644
index 7237274eb43..00000000000
--- a/spec/javascripts/diffs/components/edit_button_spec.js
+++ /dev/null
@@ -1 +0,0 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js
deleted file mode 100644
index 7237274eb43..00000000000
--- a/spec/javascripts/diffs/components/hidden_files_warning_spec.js
+++ /dev/null
@@ -1 +0,0 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
index 2316ee29106..4452106580a 100644
--- a/spec/javascripts/diffs/components/inline_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
-import store from '~/mr_notes/stores';
+import store from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
index 6f6b1c41915..236bda96145 100644
--- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import store from '~/mr_notes/stores';
+import store from 'ee_else_ce/mr_notes/stores';
import * as constants from '~/diffs/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -18,6 +18,10 @@ describe('ParallelDiffView', () => {
}).$mount();
});
+ afterEach(() => {
+ component.$destroy();
+ });
+
describe('assigned', () => {
describe('diffLines', () => {
it('should normalize lines for empty cells', () => {
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index 4a091b4580b..711ab543411 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -288,6 +288,7 @@ export default {
external_storage: null,
old_path_html: 'CHANGELOG_OLD',
new_path_html: 'CHANGELOG',
+ is_fully_expanded: true,
context_lines_path:
'/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
highlighted_diff_lines: [
@@ -495,7 +496,7 @@ export default {
{
text: 'line',
rich_text:
- '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
+ '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
can_receive_suggestion: true,
line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1',
type: 'new',
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 32af9ea8ddd..27428197c1c 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -240,4 +240,5 @@ export default {
},
],
discussions: [],
+ renderingLines: false,
};
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index acff80bca62..f129fbb57a3 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -30,6 +30,13 @@ import actions, {
setRenderTreeList,
setShowWhitespace,
setRenderIt,
+ requestFullDiff,
+ receiveFullDiffSucess,
+ receiveFullDiffError,
+ fetchFullDiff,
+ toggleFullDiff,
+ setFileCollapsed,
+ setExpandedDiffLines,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -75,7 +82,7 @@ describe('DiffsStoreActions', () => {
describe('fetchDiffFiles', () => {
it('should fetch diff files', done => {
- const endpoint = '/fetch/diff/files';
+ const endpoint = '/fetch/diff/files?w=1';
const mock = new MockAdapter(axios);
const res = { diff_files: 1, merge_request_diffs: [] };
mock.onGet(endpoint).reply(200, res);
@@ -100,9 +107,10 @@ describe('DiffsStoreActions', () => {
});
describe('setHighlightedRow', () => {
- it('should set lineHash and fileHash of highlightedRow', () => {
+ it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
testAction(setHighlightedRow, 'ABC_123', {}, [
{ type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' },
]);
});
});
@@ -388,6 +396,7 @@ describe('DiffsStoreActions', () => {
});
describe('loadCollapsedDiff', () => {
+ const state = { showWhitespace: true };
it('should fetch data and call mutation with response and the give parameter', done => {
const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
@@ -395,7 +404,7 @@ describe('DiffsStoreActions', () => {
const commit = jasmine.createSpy('commit');
mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
- loadCollapsedDiff({ commit, getters: { commitId: null } }, file)
+ loadCollapsedDiff({ commit, getters: { commitId: null }, state }, file)
.then(() => {
expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
@@ -413,10 +422,10 @@ describe('DiffsStoreActions', () => {
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- loadCollapsedDiff({ commit() {}, getters }, file);
+ loadCollapsedDiff({ commit() {}, getters, state }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
- params: { commit_id: null },
+ params: { commit_id: null, w: '0' },
});
});
@@ -428,10 +437,10 @@ describe('DiffsStoreActions', () => {
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- loadCollapsedDiff({ commit() {}, getters }, file);
+ loadCollapsedDiff({ commit() {}, getters, state }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
- params: { commit_id: '123' },
+ params: { commit_id: '123', w: '0' },
});
});
});
@@ -713,22 +722,6 @@ describe('DiffsStoreActions', () => {
expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test');
});
-
- it('resets currentDiffId after timeout', () => {
- const state = {
- treeEntries: {
- path: {
- fileHash: 'test',
- },
- },
- };
-
- scrollToFile({ state, commit }, 'path');
-
- jasmine.clock().tick(1000);
-
- expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']);
- });
});
describe('toggleShowTreeList', () => {
@@ -743,6 +736,14 @@ describe('DiffsStoreActions', () => {
expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
});
+
+ it('does not update localStorage', () => {
+ spyOn(localStorage, 'setItem');
+
+ toggleShowTreeList({ commit() {}, state: { showTreeList: true } }, false);
+
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ });
});
describe('renderFileForDiscussionId', () => {
@@ -828,6 +829,10 @@ describe('DiffsStoreActions', () => {
});
describe('setShowWhitespace', () => {
+ beforeEach(() => {
+ spyOn(eventHub, '$emit').and.stub();
+ });
+
it('commits SET_SHOW_WHITESPACE', done => {
testAction(
setShowWhitespace,
@@ -855,6 +860,30 @@ describe('DiffsStoreActions', () => {
expect(window.history.pushState).toHaveBeenCalled();
});
+
+ it('calls history pushState with merged params', () => {
+ const originalPushState = window.history;
+
+ originalPushState.pushState({}, '', '?test=1');
+
+ spyOn(localStorage, 'setItem').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
+
+ setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true });
+
+ expect(window.history.pushState.calls.mostRecent().args[2]).toMatch(/(.*)\?test=1&w=0/);
+
+ originalPushState.pushState({}, '', '?');
+ });
+
+ it('emits eventHub event', () => {
+ spyOn(localStorage, 'setItem').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
+
+ setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true });
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('refetchDiffData');
+ });
});
describe('setRenderIt', () => {
@@ -862,4 +891,193 @@ describe('DiffsStoreActions', () => {
testAction(setRenderIt, 'file', {}, [{ type: types.RENDER_FILE, payload: 'file' }], [], done);
});
});
+
+ describe('requestFullDiff', () => {
+ it('commits REQUEST_FULL_DIFF', done => {
+ testAction(
+ requestFullDiff,
+ 'file',
+ {},
+ [{ type: types.REQUEST_FULL_DIFF, payload: 'file' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveFullDiffSucess', () => {
+ it('commits REQUEST_FULL_DIFF', done => {
+ testAction(
+ receiveFullDiffSucess,
+ { filePath: 'test' },
+ {},
+ [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveFullDiffError', () => {
+ it('commits REQUEST_FULL_DIFF', done => {
+ testAction(
+ receiveFullDiffError,
+ 'file',
+ {},
+ [{ type: types.RECEIVE_FULL_DIFF_ERROR, payload: 'file' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchFullDiff', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(200, ['test']);
+ });
+
+ it('dispatches receiveFullDiffSucess', done => {
+ const file = {
+ context_lines_path: `${gl.TEST_HOST}/context`,
+ file_path: 'test',
+ file_hash: 'test',
+ };
+ testAction(
+ fetchFullDiff,
+ file,
+ null,
+ [],
+ [
+ { type: 'receiveFullDiffSucess', payload: { filePath: 'test' } },
+ { type: 'setExpandedDiffLines', payload: { file, data: ['test'] } },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(500);
+ });
+
+ it('dispatches receiveFullDiffError', done => {
+ testAction(
+ fetchFullDiff,
+ { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
+ null,
+ [],
+ [{ type: 'receiveFullDiffError', payload: 'test' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('toggleFullDiff', () => {
+ let state;
+
+ beforeEach(() => {
+ state = {
+ diffFiles: [{ file_path: 'test', isShowingFullFile: false }],
+ };
+ });
+
+ it('dispatches fetchFullDiff when file is not expanded', done => {
+ testAction(
+ toggleFullDiff,
+ 'test',
+ state,
+ [],
+ [
+ { type: 'requestFullDiff', payload: 'test' },
+ { type: 'fetchFullDiff', payload: state.diffFiles[0] },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('setFileCollapsed', () => {
+ it('commits SET_FILE_COLLAPSED', done => {
+ testAction(
+ setFileCollapsed,
+ { filePath: 'test', collapsed: true },
+ null,
+ [{ type: types.SET_FILE_COLLAPSED, payload: { filePath: 'test', collapsed: true } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setExpandedDiffLines', () => {
+ beforeEach(() => {
+ spyOnDependency(actions, 'idleCallback').and.callFake(cb => {
+ cb({ timeRemaining: () => 50 });
+ });
+ });
+
+ it('commits SET_CURRENT_VIEW_DIFF_FILE_LINES when lines less than MAX_RENDERING_DIFF_LINES', done => {
+ spyOnDependency(actions, 'convertExpandLines').and.callFake(() => ['test']);
+
+ testAction(
+ setExpandedDiffLines,
+ { file: { file_path: 'path' }, data: [] },
+ { diffViewType: 'inline' },
+ [
+ {
+ type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES',
+ payload: { filePath: 'path', lines: ['test'] },
+ },
+ {
+ type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
+ payload: { filePath: 'path', lines: ['test'] },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('commits ADD_CURRENT_VIEW_DIFF_FILE_LINES when lines more than MAX_RENDERING_DIFF_LINES', done => {
+ const lines = new Array(501).fill().map((_, i) => `line-${i}`);
+ spyOnDependency(actions, 'convertExpandLines').and.callFake(() => lines);
+
+ testAction(
+ setExpandedDiffLines,
+ { file: { file_path: 'path' }, data: [] },
+ { diffViewType: 'inline' },
+ [
+ {
+ type: 'SET_HIDDEN_VIEW_DIFF_FILE_LINES',
+ payload: { filePath: 'path', lines },
+ },
+ {
+ type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
+ payload: { filePath: 'path', lines: lines.slice(0, 200) },
+ },
+ { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
+ ...new Array(301).fill().map((_, i) => ({
+ type: 'ADD_CURRENT_VIEW_DIFF_FILE_LINES',
+ payload: { filePath: 'path', line: `line-${i + 200}` },
+ })),
+ { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
+ ],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 0ab88e6b2aa..eab5703dfb2 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -270,4 +270,24 @@ describe('Diffs Module Getters', () => {
expect(getters.diffFilesLength(localState)).toBe(2);
});
});
+
+ describe('currentDiffIndex', () => {
+ it('returns index of currently selected diff in diffList', () => {
+ localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.currentDiffFileId = '222';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(1);
+
+ localState.currentDiffFileId = '333';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(2);
+ });
+
+ it('returns 0 if no diff is selected yet or diff is not found', () => {
+ localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.currentDiffFileId = '';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(0);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 09ee691b602..fa193e1d3b9 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -58,13 +58,15 @@ describe('DiffsStoreMutations', () => {
describe('EXPAND_ALL_FILES', () => {
it('should change the collapsed prop from diffFiles', () => {
const diffFile = {
- collapsed: true,
+ viewer: {
+ collapsed: true,
+ },
};
const state = { expandAllFiles: true, diffFiles: [diffFile] };
mutations[types.EXPAND_ALL_FILES](state);
- expect(state.diffFiles[0].collapsed).toEqual(false);
+ expect(state.diffFiles[0].viewer.collapsed).toEqual(false);
});
});
@@ -680,4 +682,172 @@ describe('DiffsStoreMutations', () => {
expect(state.showWhitespace).toBe(false);
});
});
+
+ describe('REQUEST_FULL_DIFF', () => {
+ it('sets isLoadingFullFile to true', () => {
+ const state = {
+ diffFiles: [{ file_path: 'test', isLoadingFullFile: false }],
+ };
+
+ mutations[types.REQUEST_FULL_DIFF](state, 'test');
+
+ expect(state.diffFiles[0].isLoadingFullFile).toBe(true);
+ });
+ });
+
+ describe('RECEIVE_FULL_DIFF_ERROR', () => {
+ it('sets isLoadingFullFile to false', () => {
+ const state = {
+ diffFiles: [{ file_path: 'test', isLoadingFullFile: true }],
+ };
+
+ mutations[types.RECEIVE_FULL_DIFF_ERROR](state, 'test');
+
+ expect(state.diffFiles[0].isLoadingFullFile).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_FULL_DIFF_SUCCESS', () => {
+ it('sets isLoadingFullFile to false', () => {
+ const state = {
+ diffFiles: [
+ {
+ file_path: 'test',
+ isLoadingFullFile: true,
+ isShowingFullFile: false,
+ highlighted_diff_lines: [],
+ parallel_diff_lines: [],
+ },
+ ],
+ };
+
+ mutations[types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath: 'test', data: [] });
+
+ expect(state.diffFiles[0].isLoadingFullFile).toBe(false);
+ });
+
+ it('sets isShowingFullFile to true', () => {
+ const state = {
+ diffFiles: [
+ {
+ file_path: 'test',
+ isLoadingFullFile: true,
+ isShowingFullFile: false,
+ highlighted_diff_lines: [],
+ parallel_diff_lines: [],
+ },
+ ],
+ };
+
+ mutations[types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath: 'test', data: [] });
+
+ expect(state.diffFiles[0].isShowingFullFile).toBe(true);
+ });
+ });
+
+ describe('SET_FILE_COLLAPSED', () => {
+ it('sets collapsed', () => {
+ const state = {
+ diffFiles: [{ file_path: 'test', viewer: { collapsed: false } }],
+ };
+
+ mutations[types.SET_FILE_COLLAPSED](state, { filePath: 'test', collapsed: true });
+
+ expect(state.diffFiles[0].viewer.collapsed).toBe(true);
+ });
+ });
+
+ describe('SET_HIDDEN_VIEW_DIFF_FILE_LINES', () => {
+ [
+ { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
+ { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
+ ].forEach(({ current, hidden, diffViewType }) => {
+ it(`sets the ${hidden} lines when diff view is ${diffViewType}`, () => {
+ const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
+ const state = {
+ diffFiles: [file],
+ diffViewType,
+ };
+
+ mutations[types.SET_HIDDEN_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ lines: ['test'],
+ });
+
+ expect(file[`${current}_diff_lines`]).toEqual([]);
+ expect(file[`${hidden}_diff_lines`]).toEqual(['test']);
+ });
+ });
+ });
+
+ describe('SET_CURRENT_VIEW_DIFF_FILE_LINES', () => {
+ [
+ { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
+ { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
+ ].forEach(({ current, hidden, diffViewType }) => {
+ it(`sets the ${current} lines when diff view is ${diffViewType}`, () => {
+ const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
+ const state = {
+ diffFiles: [file],
+ diffViewType,
+ };
+
+ mutations[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ lines: ['test'],
+ });
+
+ expect(file[`${current}_diff_lines`]).toEqual(['test']);
+ expect(file[`${hidden}_diff_lines`]).toEqual([]);
+ });
+ });
+ });
+
+ describe('ADD_CURRENT_VIEW_DIFF_FILE_LINES', () => {
+ [
+ { current: 'highlighted', hidden: 'parallel', diffViewType: 'inline' },
+ { current: 'parallel', hidden: 'highlighted', diffViewType: 'parallel' },
+ ].forEach(({ current, hidden, diffViewType }) => {
+ it(`pushes to ${current} lines when diff view is ${diffViewType}`, () => {
+ const file = { file_path: 'test', parallel_diff_lines: [], highlighted_diff_lines: [] };
+ const state = {
+ diffFiles: [file],
+ diffViewType,
+ };
+
+ mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ line: 'test',
+ });
+
+ expect(file[`${current}_diff_lines`]).toEqual(['test']);
+ expect(file[`${hidden}_diff_lines`]).toEqual([]);
+
+ mutations[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, {
+ filePath: 'test',
+ line: 'test2',
+ });
+
+ expect(file[`${current}_diff_lines`]).toEqual(['test', 'test2']);
+ expect(file[`${hidden}_diff_lines`]).toEqual([]);
+ });
+ });
+ });
+
+ describe('TOGGLE_DIFF_FILE_RENDERING_MORE', () => {
+ it('toggles renderingLines on file', () => {
+ const file = { file_path: 'test', renderingLines: false };
+ const state = {
+ diffFiles: [file],
+ };
+
+ mutations[types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, 'test');
+
+ expect(file.renderingLines).toBe(true);
+
+ mutations[types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, 'test');
+
+ expect(file.renderingLines).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 599ea9cd420..1f877910125 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -781,4 +781,49 @@ describe('DiffsStoreUtils', () => {
]);
});
});
+
+ describe('convertExpandLines', () => {
+ it('converts expanded lines to normal lines', () => {
+ const diffLines = [
+ {
+ type: 'match',
+ old_line: 1,
+ new_line: 1,
+ },
+ {
+ type: '',
+ old_line: 2,
+ new_line: 2,
+ },
+ ];
+
+ const lines = utils.convertExpandLines({
+ diffLines,
+ data: [{ text: 'expanded' }],
+ typeKey: 'type',
+ oldLineKey: 'old_line',
+ newLineKey: 'new_line',
+ mapLine: ({ line, oldLine, newLine }) => ({
+ ...line,
+ old_line: oldLine,
+ new_line: newLine,
+ }),
+ });
+
+ expect(lines).toEqual([
+ {
+ text: 'expanded',
+ new_line: 1,
+ old_line: 1,
+ discussions: [],
+ hasForm: false,
+ },
+ {
+ type: '',
+ old_line: 2,
+ new_line: 2,
+ },
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
index ae2a785de52..b1017e0c4f0 100644
--- a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
+++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
import { getInputValue, setInputValue, createForm } from './helper';
@@ -13,44 +14,100 @@ function expectToToggleDisableOnDirtyUpdate(submit, input) {
}
describe('DirtySubmitForm', () => {
- it('disables submit until there are changes', done => {
- const { form, input, submit } = createForm();
+ const originalThrottleDuration = DirtySubmitForm.THROTTLE_DURATION;
- new DirtySubmitForm(form); // eslint-disable-line no-new
+ describe('submit button tests', () => {
+ beforeEach(() => {
+ DirtySubmitForm.THROTTLE_DURATION = 0;
+ });
- return expectToToggleDisableOnDirtyUpdate(submit, input)
- .then(done)
- .catch(done.fail);
- });
+ afterEach(() => {
+ DirtySubmitForm.THROTTLE_DURATION = originalThrottleDuration;
+ });
- it('disables submit until there are changes when initializing with a falsy value', done => {
- const { form, input, submit } = createForm();
- input.value = '';
+ it('disables submit until there are changes', done => {
+ const { form, input, submit } = createForm();
- new DirtySubmitForm(form); // eslint-disable-line no-new
+ new DirtySubmitForm(form); // eslint-disable-line no-new
- return expectToToggleDisableOnDirtyUpdate(submit, input)
- .then(done)
- .catch(done.fail);
- });
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('disables submit until there are changes when initializing with a falsy value', done => {
+ const { form, input, submit } = createForm();
+ input.value = '';
+
+ new DirtySubmitForm(form); // eslint-disable-line no-new
+
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('disables submit until there are changes for radio inputs', done => {
+ const { form, input, submit } = createForm('radio');
+
+ new DirtySubmitForm(form); // eslint-disable-line no-new
+
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
- it('disables submit until there are changes for radio inputs', done => {
- const { form, input, submit } = createForm('radio');
+ it('disables submit until there are changes for checkbox inputs', done => {
+ const { form, input, submit } = createForm('checkbox');
- new DirtySubmitForm(form); // eslint-disable-line no-new
+ new DirtySubmitForm(form); // eslint-disable-line no-new
- return expectToToggleDisableOnDirtyUpdate(submit, input)
- .then(done)
- .catch(done.fail);
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
});
- it('disables submit until there are changes for checkbox inputs', done => {
- const { form, input, submit } = createForm('checkbox');
+ describe('throttling tests', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+ DirtySubmitForm.THROTTLE_DURATION = 100;
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ DirtySubmitForm.THROTTLE_DURATION = originalThrottleDuration;
+ });
+
+ it('throttles updates when rapid changes are made to a single form element', () => {
+ const { form, input } = createForm();
+ const updateDirtyInputSpy = spyOn(new DirtySubmitForm(form), 'updateDirtyInput');
+
+ _.range(10).forEach(i => {
+ setInputValue(input, `change ${i}`, false);
+ });
+
+ jasmine.clock().tick(101);
+
+ expect(updateDirtyInputSpy).toHaveBeenCalledTimes(2);
+ });
+
+ it('does not throttle updates when rapid changes are made to different form elements', () => {
+ const form = document.createElement('form');
+ const range = _.range(10);
+ range.forEach(i => {
+ form.innerHTML += `<input type="text" name="input-${i}" class="js-input-${i}"/>`;
+ });
+
+ const updateDirtyInputSpy = spyOn(new DirtySubmitForm(form), 'updateDirtyInput');
+
+ range.forEach(i => {
+ const input = form.querySelector(`.js-input-${i}`);
+ setInputValue(input, `change`, false);
+ });
- new DirtySubmitForm(form); // eslint-disable-line no-new
+ jasmine.clock().tick(101);
- return expectToToggleDisableOnDirtyUpdate(submit, input)
- .then(done)
- .catch(done.fail);
+ expect(updateDirtyInputSpy).toHaveBeenCalledTimes(range.length);
+ });
});
});
diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/javascripts/environments/confirm_rollback_modal_spec.js
new file mode 100644
index 00000000000..05715bce38f
--- /dev/null
+++ b/spec/javascripts/environments/confirm_rollback_modal_spec.js
@@ -0,0 +1,70 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
+import eventHub from '~/environments/event_hub';
+
+describe('Confirm Rollback Modal Component', () => {
+ let environment;
+
+ beforeEach(() => {
+ environment = {
+ name: 'test',
+ last_deployment: {
+ commit: {
+ short_id: 'abc0123',
+ },
+ },
+ modalId: 'test',
+ };
+ });
+
+ it('should show "Rollback" when isLastDeployment is false', () => {
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment: {
+ ...environment,
+ isLastDeployment: false,
+ },
+ },
+ });
+ const modal = component.find(GlModal);
+
+ expect(modal.attributes('title')).toContain('Rollback');
+ expect(modal.attributes('title')).toContain('test');
+ expect(modal.attributes('ok-title')).toBe('Rollback');
+ expect(modal.text()).toContain('commit abc0123');
+ expect(modal.text()).toContain('Are you sure you want to continue?');
+ });
+
+ it('should show "Re-deploy" when isLastDeployment is true', () => {
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment: {
+ ...environment,
+ isLastDeployment: true,
+ },
+ },
+ });
+ const modal = component.find(GlModal);
+
+ expect(modal.attributes('title')).toContain('Re-deploy');
+ expect(modal.attributes('title')).toContain('test');
+ expect(modal.attributes('ok-title')).toBe('Re-deploy');
+ expect(modal.text()).toContain('commit abc0123');
+ expect(modal.text()).toContain('Are you sure you want to continue?');
+ });
+
+ it('should emit the "rollback" event when "ok" is clicked', () => {
+ environment = { ...environment, isLastDeployment: true };
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment,
+ },
+ });
+ const eventHubSpy = spyOn(eventHub, '$emit');
+ const modal = component.find(GlModal);
+ modal.vm.$emit('ok');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment);
+ });
+});
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index a89e50045da..388d7063d13 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -20,23 +20,23 @@ describe('Environment item', () => {
size: 3,
isFolder: true,
environment_path: 'url',
+ log_path: 'url',
};
component = new EnvironmentItem({
propsData: {
model: mockItem,
canReadEnvironment: true,
- service: {},
},
}).$mount();
});
- it('Should render folder icon and name', () => {
+ it('should render folder icon and name', () => {
expect(component.$el.querySelector('.folder-name').textContent).toContain(mockItem.name);
expect(component.$el.querySelector('.folder-icon')).toBeDefined();
});
- it('Should render the number of children in a badge', () => {
+ it('should render the number of children in a badge', () => {
expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(
mockItem.size,
);
@@ -60,7 +60,7 @@ describe('Environment item', () => {
sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
ref: {
name: 'master',
- ref_path: 'root/ci-folders/tree/master',
+ ref_url: 'root/ci-folders/tree/master',
},
tag: true,
'last?': true,
@@ -109,6 +109,7 @@ describe('Environment item', () => {
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
+ log_path: 'root/ci-folders/environments/31/logs',
created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z',
};
@@ -117,7 +118,6 @@ describe('Environment item', () => {
propsData: {
model: environment,
canReadEnvironment: true,
- service: {},
},
}).$mount();
});
@@ -157,13 +157,13 @@ describe('Environment item', () => {
});
describe('With build url', () => {
- it('Should link to build url provided', () => {
+ it('should link to build url provided', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
- it('Should render deployable name and id', () => {
+ it('should render deployable name and id', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
@@ -178,7 +178,7 @@ describe('Environment item', () => {
});
describe('With manual actions', () => {
- it('Should render actions component', () => {
+ it('should render actions component', () => {
expect(component.$el.querySelector('.js-manual-actions-container')).toBeDefined();
});
});
@@ -190,13 +190,13 @@ describe('Environment item', () => {
});
describe('With stop action', () => {
- it('Should render stop action component', () => {
+ it('should render stop action component', () => {
expect(component.$el.querySelector('.js-stop-component-container')).toBeDefined();
});
});
describe('With retry action', () => {
- it('Should render rollback component', () => {
+ it('should render rollback component', () => {
expect(component.$el.querySelector('.js-rollback-component-container')).toBeDefined();
});
});
diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js
index 79f33c5bc8a..8c47f6a12c0 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js
+++ b/spec/javascripts/environments/environment_rollback_spec.js
@@ -1,8 +1,11 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import eventHub from '~/environments/event_hub';
import rollbackComp from '~/environments/components/environment_rollback.vue';
describe('Rollback Component', () => {
- const retryURL = 'https://gitlab.com/retry';
+ const retryUrl = 'https://gitlab.com/retry';
let RollbackComponent;
beforeEach(() => {
@@ -13,8 +16,9 @@ describe('Rollback Component', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
- retryUrl: retryURL,
+ retryUrl,
isLastDeployment: true,
+ environment: {},
},
}).$mount();
@@ -25,11 +29,33 @@ describe('Rollback Component', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
- retryUrl: retryURL,
+ retryUrl,
isLastDeployment: false,
+ environment: {},
},
}).$mount();
expect(component.$el).toHaveSpriteIcon('redo');
});
+
+ it('should emit a "rollback" event on button click', () => {
+ const eventHubSpy = spyOn(eventHub, '$emit');
+ const component = shallowMount(RollbackComponent, {
+ propsData: {
+ retryUrl,
+ environment: {
+ name: 'test',
+ },
+ },
+ });
+ const button = component.find(GlButton);
+
+ button.vm.$emit('click');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
+ retryUrl,
+ isLastDeployment: true,
+ name: 'test',
+ });
+ });
});
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index 52895f35f3a..a3f34232a85 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -6,6 +6,14 @@ describe('Environment table', () => {
let Component;
let vm;
+ const eeOnlyProps = {
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
+ };
+
beforeEach(() => {
Component = Vue.extend(environmentTableComp);
});
@@ -27,8 +35,234 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
+ ...eeOnlyProps,
});
expect(vm.$el.getAttribute('class')).toContain('ci-table');
});
+
+ describe('sortEnvironments', () => {
+ it('should sort environments by last updated', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'new',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 1, 5).toISOString(),
+ },
+ },
+ {
+ name: 'older',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2018, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'an environment with no deployment',
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ ...eeOnlyProps,
+ });
+
+ const [old, newer, older, noDeploy] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]);
+ });
+
+ it('should push environments with no deployments to the bottom', () => {
+ const mockItems = [
+ {
+ name: 'production',
+ size: 1,
+ id: 2,
+ state: 'available',
+ external_url: 'https://google.com/production',
+ environment_type: null,
+ last_deployment: null,
+ has_stop_action: false,
+ environment_path: '/Commit451/lab-coat/environments/2',
+ stop_path: '/Commit451/lab-coat/environments/2/stop',
+ folder_path: '/Commit451/lab-coat/environments/folders/production',
+ created_at: '2019-01-17T16:26:10.064Z',
+ updated_at: '2019-01-17T16:27:37.717Z',
+ can_stop: true,
+ },
+ {
+ name: 'review/225addcibuildstatus',
+ size: 2,
+ isFolder: true,
+ isLoadingFolderContent: false,
+ folderName: 'review',
+ isOpen: false,
+ children: [],
+ id: 12,
+ state: 'available',
+ external_url: 'https://google.com/review/225addcibuildstatus',
+ environment_type: 'review',
+ last_deployment: null,
+ has_stop_action: false,
+ environment_path: '/Commit451/lab-coat/environments/12',
+ stop_path: '/Commit451/lab-coat/environments/12/stop',
+ folder_path: '/Commit451/lab-coat/environments/folders/review',
+ created_at: '2019-01-17T16:27:37.877Z',
+ updated_at: '2019-01-17T16:27:37.883Z',
+ can_stop: true,
+ },
+ {
+ name: 'staging',
+ size: 1,
+ id: 1,
+ state: 'available',
+ external_url: 'https://google.com/staging',
+ environment_type: null,
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ scheduled_actions: [],
+ },
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ ...eeOnlyProps,
+ });
+
+ const [prod, review, staging] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]);
+ });
+
+ it('should sort environments by folder first', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'new',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 1, 5).toISOString(),
+ },
+ },
+ {
+ name: 'older',
+ size: 3,
+ isFolder: true,
+ children: [],
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ ...eeOnlyProps,
+ });
+
+ const [old, newer, older] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
+ });
+
+ it('should break ties by name', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ isFolder: false,
+ },
+ {
+ name: 'new',
+ isFolder: false,
+ },
+ {
+ folderName: 'older',
+ isFolder: true,
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ ...eeOnlyProps,
+ });
+
+ const [old, newer, older] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
+ });
+ });
+
+ describe('sortedEnvironments', () => {
+ it('it should sort children as well', () => {
+ const mockItems = [
+ {
+ name: 'production',
+ last_deployment: null,
+ },
+ {
+ name: 'review/225addcibuildstatus',
+ isFolder: true,
+ folderName: 'review',
+ isOpen: true,
+ children: [
+ {
+ name: 'review/225addcibuildstatus',
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ },
+ },
+ {
+ name: 'review/master',
+ last_deployment: {
+ created_at: '2019-02-17T16:26:15.125Z',
+ },
+ },
+ ],
+ },
+ {
+ name: 'staging',
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ },
+ },
+ ];
+ const [production, review, staging] = mockItems;
+ const [addcibuildstatus, master] = mockItems[1].children;
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ ...eeOnlyProps,
+ });
+
+ expect(vm.sortedEnvironments.map(env => env.name)).toEqual([
+ review.name,
+ staging.name,
+ production.name,
+ ]);
+
+ expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]);
+ });
+ });
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 9220f7a264f..0dcd8868aba 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -13,6 +13,11 @@ describe('Environment', () => {
cssContainerClass: 'container',
newEnvironmentPath: 'environments/new',
helpPagePath: 'help',
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
};
let EnvironmentsComponent;
@@ -94,7 +99,7 @@ describe('Environment', () => {
it('should make an API request when page is clicked', done => {
spyOn(component, 'updateContent');
setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
done();
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index c3d16f10d72..8abdbcbbe54 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -34,54 +34,46 @@ describe('Store', () => {
expect(store.state.stoppedCounter).toEqual(2);
});
- describe('store environments', () => {
- it('should store environments', () => {
- store.storeEnvironments(serverData);
-
- expect(store.state.environments.length).toEqual(serverData.length);
- });
-
- it('should add folder keys when environment is a folder', () => {
- const environment = {
- name: 'bar',
- size: 3,
- id: 2,
- };
+ it('should add folder keys when environment is a folder', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ };
- store.storeEnvironments([environment]);
+ store.storeEnvironments([environment]);
- expect(store.state.environments[0].isFolder).toEqual(true);
- expect(store.state.environments[0].folderName).toEqual('bar');
- });
-
- it('should extract content of `latest` key when provided', () => {
- const environment = {
- name: 'bar',
- size: 3,
- id: 2,
- latest: {
- last_deployment: {},
- isStoppable: true,
- },
- };
-
- store.storeEnvironments([environment]);
+ expect(store.state.environments[0].isFolder).toEqual(true);
+ expect(store.state.environments[0].folderName).toEqual('bar');
+ });
- expect(store.state.environments[0].last_deployment).toEqual({});
- expect(store.state.environments[0].isStoppable).toEqual(true);
- });
+ it('should extract content of `latest` key when provided', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ latest: {
+ last_deployment: {},
+ isStoppable: true,
+ },
+ };
+
+ store.storeEnvironments([environment]);
+
+ expect(store.state.environments[0].last_deployment).toEqual({});
+ expect(store.state.environments[0].isStoppable).toEqual(true);
+ });
- it('should store latest.name when the environment is not a folder', () => {
- store.storeEnvironments(serverData);
+ it('should store latest.name when the environment is not a folder', () => {
+ store.storeEnvironments(serverData);
- expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
- });
+ expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
+ });
- it('should store root level name when environment is a folder', () => {
- store.storeEnvironments(serverData);
+ it('should store root level name when environment is a folder', () => {
+ store.storeEnvironments(serverData);
- expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
- });
+ expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
});
describe('toggleFolder', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index d9ee7e74e28..f1c323df4be 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { removeBreakLine, removeWhitespace } from 'spec/helpers/text_helper';
import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
@@ -15,6 +16,11 @@ describe('Environments Folder View', () => {
folderName: 'review',
canReadEnvironment: true,
cssContainerClass: 'container',
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
};
beforeEach(() => {
@@ -89,9 +95,11 @@ describe('Environments Folder View', () => {
it('should render parent folder name', done => {
setTimeout(() => {
- expect(component.$el.querySelector('.js-folder-name').textContent.trim()).toContain(
- 'Environments / review',
- );
+ expect(
+ removeBreakLine(
+ removeWhitespace(component.$el.querySelector('.js-folder-name').textContent.trim()),
+ ),
+ ).toContain('Environments / review');
done();
}, 0);
});
@@ -107,7 +115,7 @@ describe('Environments Folder View', () => {
it('should make an API request when changing page', done => {
spyOn(component, 'updateContent');
setTimeout(() => {
- component.$el.querySelector('.gl-pagination .js-last-button a').click();
+ component.$el.querySelector('.gl-pagination .js-last-button .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({
scope: component.scope,
diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
deleted file mode 100644
index 08bbb390993..00000000000
--- a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import Vuex from 'vuex';
-import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
-import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('ErrorTrackingList', () => {
- let store;
- let wrapper;
-
- function mountComponent({ errorTrackingEnabled = true } = {}) {
- wrapper = shallowMount(ErrorTrackingList, {
- localVue,
- store,
- propsData: {
- indexPath: '/path',
- enableErrorTrackingLink: '/link',
- errorTrackingEnabled,
- illustrationPath: 'illustration/path',
- },
- });
- }
-
- beforeEach(() => {
- const actions = {
- getErrorList: () => {},
- };
-
- const state = {
- errors: [],
- loading: true,
- };
-
- store = new Vuex.Store({
- actions,
- state,
- });
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('loading', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('shows spinner', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
- expect(wrapper.find(GlTable).exists()).toBeFalsy();
- expect(wrapper.find(GlButton).exists()).toBeFalsy();
- });
- });
-
- describe('results', () => {
- beforeEach(() => {
- store.state.loading = false;
-
- mountComponent();
- });
-
- it('shows table', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
- expect(wrapper.find(GlTable).exists()).toBeTruthy();
- expect(wrapper.find(GlButton).exists()).toBeTruthy();
- });
- });
-
- describe('no results', () => {
- beforeEach(() => {
- store.state.loading = false;
-
- mountComponent();
- });
-
- it('shows empty table', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
- expect(wrapper.find(GlTable).exists()).toBeTruthy();
- expect(wrapper.find(GlButton).exists()).toBeTruthy();
- });
- });
-
- describe('error tracking feature disabled', () => {
- beforeEach(() => {
- mountComponent({ errorTrackingEnabled: false });
- });
-
- it('shows empty state', () => {
- expect(wrapper.find(GlEmptyState).exists()).toBeTruthy();
- expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
- expect(wrapper.find(GlTable).exists()).toBeFalsy();
- expect(wrapper.find(GlButton).exists()).toBeFalsy();
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/components/app_spec.js b/spec/javascripts/error_tracking_settings/components/app_spec.js
new file mode 100644
index 00000000000..2e52a45fd34
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/app_spec.js
@@ -0,0 +1,63 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
+import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
+import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
+import createStore from '~/error_tracking_settings/store';
+import { TEST_HOST } from 'spec/test_constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings app', () => {
+ let store;
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ErrorTrackingSettings, {
+ localVue,
+ store, // Override the imported store
+ propsData: {
+ initialEnabled: 'true',
+ initialApiHost: TEST_HOST,
+ initialToken: 'someToken',
+ initialProject: null,
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ store = createStore();
+
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('section', () => {
+ it('renders the form and dropdown', () => {
+ expect(wrapper.find(ErrorTrackingForm).exists()).toBeTruthy();
+ expect(wrapper.find(ProjectDropdown).exists()).toBeTruthy();
+ });
+
+ it('renders the Save Changes button', () => {
+ expect(wrapper.find('.js-error-tracking-button').exists()).toBeTruthy();
+ });
+
+ it('enables the button by default', () => {
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy();
+ });
+
+ it('disables the button when saving', () => {
+ store.state.settingsLoading = true;
+
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
new file mode 100644
index 00000000000..23e57c4bbf1
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
@@ -0,0 +1,91 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
+import { defaultProps } from '../mock';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings form', () => {
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ErrorTrackingForm, {
+ localVue,
+ propsData: defaultProps,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('an empty form', () => {
+ it('is rendered', () => {
+ expect(wrapper.findAll(GlFormInput).length).toBe(2);
+ expect(wrapper.find(GlFormInput).attributes('id')).toBe('error-tracking-api-host');
+ expect(
+ wrapper
+ .findAll(GlFormInput)
+ .at(1)
+ .attributes('id'),
+ ).toBe('error-tracking-token');
+
+ expect(wrapper.findAll(GlButton).exists()).toBe(true);
+ });
+
+ it('is rendered with labels and placeholders', () => {
+ const pageText = wrapper.text();
+
+ expect(pageText).toContain('Find your hostname in your Sentry account settings page');
+ expect(pageText).toContain(
+ "After adding your Auth Token, use the 'Connect' button to load projects",
+ );
+
+ expect(pageText).not.toContain('Connection has failed. Re-check Auth Token and try again');
+ expect(
+ wrapper
+ .findAll(GlFormInput)
+ .at(0)
+ .attributes('placeholder'),
+ ).toContain('https://mysentryserver.com');
+ });
+ });
+
+ describe('after a successful connection', () => {
+ beforeEach(() => {
+ wrapper.setProps({ connectSuccessful: true });
+ });
+
+ it('shows the success checkmark', () => {
+ expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(true);
+ });
+
+ it('does not show an error', () => {
+ expect(wrapper.text()).not.toContain(
+ 'Connection has failed. Re-check Auth Token and try again',
+ );
+ });
+ });
+
+ describe('after an unsuccessful connection', () => {
+ beforeEach(() => {
+ wrapper.setProps({ connectError: true });
+ });
+
+ it('does not show the check mark', () => {
+ expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(false);
+ });
+
+ it('shows an error', () => {
+ expect(wrapper.text()).toContain('Connection has failed. Re-check Auth Token and try again');
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
new file mode 100644
index 00000000000..8e5dbe28452
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
@@ -0,0 +1,109 @@
+import _ from 'underscore';
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
+import { defaultProps, projectList, staleProject } from '../mock';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings project dropdown', () => {
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ProjectDropdown, {
+ localVue,
+ propsData: {
+ ..._.pick(
+ defaultProps,
+ 'dropdownLabel',
+ 'invalidProjectLabel',
+ 'projects',
+ 'projectSelectionLabel',
+ 'selectedProject',
+ 'token',
+ ),
+ hasProjects: false,
+ isProjectInvalid: false,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('empty project list', () => {
+ it('renders the dropdown', () => {
+ expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
+ expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
+ });
+
+ it('shows helper text', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeTruthy();
+ expect(wrapper.find('.js-project-dropdown-label').text()).toContain(
+ 'To enable project selection',
+ );
+ });
+
+ it('does not show an error', () => {
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
+ });
+
+ it('does not contain any dropdown items', () => {
+ expect(wrapper.find(GlDropdownItem).exists()).toBeFalsy();
+ expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available');
+ });
+ });
+
+ describe('populated project list', () => {
+ beforeEach(() => {
+ wrapper.setProps({ projects: _.clone(projectList), hasProjects: true });
+ });
+
+ it('renders the dropdown', () => {
+ expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
+ expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
+ });
+
+ it('contains a number of dropdown items', () => {
+ expect(wrapper.find(GlDropdownItem).exists()).toBeTruthy();
+ expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
+ });
+ });
+
+ describe('selected project', () => {
+ const selectedProject = _.clone(projectList[0]);
+
+ beforeEach(() => {
+ wrapper.setProps({ projects: _.clone(projectList), selectedProject, hasProjects: true });
+ });
+
+ it('does not show helper text', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
+ });
+ });
+
+ describe('invalid project selected', () => {
+ beforeEach(() => {
+ wrapper.setProps({
+ projects: _.clone(projectList),
+ selectedProject: staleProject,
+ isProjectInvalid: true,
+ });
+ });
+
+ it('displays a error', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/mock.js b/spec/javascripts/error_tracking_settings/mock.js
new file mode 100644
index 00000000000..32cdba33c14
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/mock.js
@@ -0,0 +1,92 @@
+import createStore from '~/error_tracking_settings/store';
+import { TEST_HOST } from 'spec/test_constants';
+
+const defaultStore = createStore();
+
+export const projectList = [
+ {
+ name: 'name',
+ slug: 'slug',
+ organizationName: 'organizationName',
+ organizationSlug: 'organizationSlug',
+ },
+ {
+ name: 'name2',
+ slug: 'slug2',
+ organizationName: 'organizationName2',
+ organizationSlug: 'organizationSlug2',
+ },
+];
+
+export const staleProject = {
+ name: 'staleName',
+ slug: 'staleSlug',
+ organizationName: 'staleOrganizationName',
+ organizationSlug: 'staleOrganizationSlug',
+};
+
+export const normalizedProject = {
+ name: 'name',
+ slug: 'slug',
+ organizationName: 'organization_name',
+ organizationSlug: 'organization_slug',
+};
+
+export const sampleBackendProject = {
+ name: normalizedProject.name,
+ slug: normalizedProject.slug,
+ organization_name: normalizedProject.organizationName,
+ organization_slug: normalizedProject.organizationSlug,
+};
+
+export const sampleFrontendSettings = {
+ apiHost: 'apiHost',
+ enabled: false,
+ token: 'token',
+ selectedProject: {
+ slug: normalizedProject.slug,
+ name: normalizedProject.name,
+ organizationName: normalizedProject.organizationName,
+ organizationSlug: normalizedProject.organizationSlug,
+ },
+};
+
+export const transformedSettings = {
+ api_host: 'apiHost',
+ enabled: false,
+ token: 'token',
+ project: {
+ slug: normalizedProject.slug,
+ name: normalizedProject.name,
+ organization_name: normalizedProject.organizationName,
+ organization_slug: normalizedProject.organizationSlug,
+ },
+};
+
+export const defaultProps = {
+ ...defaultStore.state,
+ ...defaultStore.getters,
+};
+
+export const initialEmptyState = {
+ apiHost: '',
+ enabled: false,
+ project: null,
+ token: '',
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+};
+
+export const initialPopulatedState = {
+ apiHost: 'apiHost',
+ enabled: true,
+ project: JSON.stringify(projectList[0]),
+ token: 'token',
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+};
+
+export const projectWithHtmlTemplate = {
+ ...projectList[0],
+ name: '<strong>bold</strong>',
+};
diff --git a/spec/javascripts/error_tracking_settings/store/actions_spec.js b/spec/javascripts/error_tracking_settings/store/actions_spec.js
new file mode 100644
index 00000000000..0255b3a7aa4
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/actions_spec.js
@@ -0,0 +1,191 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import actionsDefaultExport, * as actions from '~/error_tracking_settings/store/actions';
+import * as types from '~/error_tracking_settings/store/mutation_types';
+import defaultState from '~/error_tracking_settings/store/state';
+import { projectList } from '../mock';
+
+describe('error tracking settings actions', () => {
+ let state;
+
+ describe('project list actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ state = { ...defaultState(), listProjectsEndpoint: TEST_HOST };
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should request and transform the project list', done => {
+ mock.onPost(TEST_HOST).reply(() => [200, { projects: projectList }]);
+ testAction(
+ actions.fetchProjects,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestProjects' },
+ {
+ type: 'receiveProjectsSuccess',
+ payload: projectList.map(convertObjectPropsToCamelCase),
+ },
+ ],
+ () => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should handle a server error', done => {
+ mock.onPost(`${TEST_HOST}.json`).reply(() => [400]);
+ testAction(
+ actions.fetchProjects,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestProjects' },
+ {
+ type: 'receiveProjectsError',
+ },
+ ],
+ () => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should request projects correctly', done => {
+ testAction(actions.requestProjects, null, state, [{ type: types.RESET_CONNECT }], [], done);
+ });
+
+ it('should receive projects correctly', done => {
+ const testPayload = [];
+ testAction(
+ actions.receiveProjectsSuccess,
+ testPayload,
+ state,
+ [
+ { type: types.UPDATE_CONNECT_SUCCESS },
+ { type: types.RECEIVE_PROJECTS, payload: testPayload },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should handle errors when receiving projects', done => {
+ const testPayload = [];
+ testAction(
+ actions.receiveProjectsError,
+ testPayload,
+ state,
+ [{ type: types.UPDATE_CONNECT_ERROR }, { type: types.CLEAR_PROJECTS }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('save changes actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ state = {
+ operationsSettingsEndpoint: TEST_HOST,
+ };
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should save the page', done => {
+ const refreshCurrentPage = spyOnDependency(actionsDefaultExport, 'refreshCurrentPage');
+ mock.onPatch(TEST_HOST).reply(200);
+ testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }], () => {
+ expect(mock.history.patch.length).toBe(1);
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('should handle a server error', done => {
+ mock.onPatch(TEST_HOST).reply(400);
+ testAction(
+ actions.updateSettings,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestSettings' },
+ {
+ type: 'receiveSettingsError',
+ payload: new Error('Request failed with status code 400'),
+ },
+ ],
+ () => {
+ expect(mock.history.patch.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should request to save the page', done => {
+ testAction(
+ actions.requestSettings,
+ null,
+ state,
+ [{ type: types.UPDATE_SETTINGS_LOADING, payload: true }],
+ [],
+ done,
+ );
+ });
+
+ it('should handle errors when requesting to save the page', done => {
+ testAction(
+ actions.receiveSettingsError,
+ {},
+ state,
+ [{ type: types.UPDATE_SETTINGS_LOADING, payload: false }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('generic actions to update the store', () => {
+ const testData = 'test';
+ it('should reset the `connect success` flag when updating the api host', done => {
+ testAction(
+ actions.updateApiHost,
+ testData,
+ state,
+ [{ type: types.UPDATE_API_HOST, payload: testData }, { type: types.RESET_CONNECT }],
+ [],
+ done,
+ );
+ });
+
+ it('should reset the `connect success` flag when updating the token', done => {
+ testAction(
+ actions.updateToken,
+ testData,
+ state,
+ [{ type: types.UPDATE_TOKEN, payload: testData }, { type: types.RESET_CONNECT }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/store/getters_spec.js b/spec/javascripts/error_tracking_settings/store/getters_spec.js
new file mode 100644
index 00000000000..2c5ff084b8a
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/getters_spec.js
@@ -0,0 +1,93 @@
+import * as getters from '~/error_tracking_settings/store/getters';
+import defaultState from '~/error_tracking_settings/store/state';
+import { projectList, projectWithHtmlTemplate, staleProject } from '../mock';
+
+describe('Error Tracking Settings - Getters', () => {
+ let state;
+
+ beforeEach(() => {
+ state = defaultState();
+ });
+
+ describe('hasProjects', () => {
+ it('should reflect when no projects exist', () => {
+ expect(getters.hasProjects(state)).toEqual(false);
+ });
+
+ it('should reflect when projects exist', () => {
+ state.projects = projectList;
+
+ expect(getters.hasProjects(state)).toEqual(true);
+ });
+ });
+
+ describe('isProjectInvalid', () => {
+ const mockGetters = { hasProjects: true };
+ it('should show when a project is valid', () => {
+ state.projects = projectList;
+ [state.selectedProject] = projectList;
+
+ expect(getters.isProjectInvalid(state, mockGetters)).toEqual(false);
+ });
+
+ it('should show when a project is invalid', () => {
+ state.projects = projectList;
+ state.selectedProject = staleProject;
+
+ expect(getters.isProjectInvalid(state, mockGetters)).toEqual(true);
+ });
+ });
+
+ describe('dropdownLabel', () => {
+ const mockGetters = { hasProjects: false };
+ it('should display correctly when there are no projects available', () => {
+ expect(getters.dropdownLabel(state, mockGetters)).toEqual('No projects available');
+ });
+
+ it('should display correctly when a project is selected', () => {
+ [state.selectedProject] = projectList;
+
+ expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | name');
+ });
+
+ it('should display correctly when no project is selected', () => {
+ state.projects = projectList;
+
+ expect(getters.dropdownLabel(state, { hasProjects: true })).toEqual('Select project');
+ });
+ });
+
+ describe('invalidProjectLabel', () => {
+ it('should display an error containing the project name', () => {
+ [state.selectedProject] = projectList;
+
+ expect(getters.invalidProjectLabel(state)).toEqual(
+ 'Project "name" is no longer available. Select another project to continue.',
+ );
+ });
+
+ it('should properly escape the label text', () => {
+ state.selectedProject = projectWithHtmlTemplate;
+
+ expect(getters.invalidProjectLabel(state)).toEqual(
+ 'Project "&lt;strong&gt;bold&lt;/strong&gt;" is no longer available. Select another project to continue.',
+ );
+ });
+ });
+
+ describe('projectSelectionLabel', () => {
+ it('should show the correct message when the token is empty', () => {
+ expect(getters.projectSelectionLabel(state)).toEqual(
+ 'To enable project selection, enter a valid Auth Token',
+ );
+ });
+
+ it('should show the correct message when token exists', () => {
+ state.token = 'test-token';
+
+ expect(getters.projectSelectionLabel(state)).toEqual(
+ "Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.",
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/store/mutation_spec.js b/spec/javascripts/error_tracking_settings/store/mutation_spec.js
new file mode 100644
index 00000000000..bb1f1da784e
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/mutation_spec.js
@@ -0,0 +1,82 @@
+import { TEST_HOST } from 'spec/test_constants';
+import mutations from '~/error_tracking_settings/store/mutations';
+import defaultState from '~/error_tracking_settings/store/state';
+import * as types from '~/error_tracking_settings/store/mutation_types';
+import {
+ initialEmptyState,
+ initialPopulatedState,
+ projectList,
+ sampleBackendProject,
+ normalizedProject,
+} from '../mock';
+
+describe('error tracking settings mutations', () => {
+ describe('mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = defaultState();
+ });
+
+ it('should create an empty initial state correctly', () => {
+ mutations[types.SET_INITIAL_STATE](state, {
+ ...initialEmptyState,
+ });
+
+ expect(state.apiHost).toEqual('');
+ expect(state.enabled).toEqual(false);
+ expect(state.selectedProject).toEqual(null);
+ expect(state.token).toEqual('');
+ expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
+ expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
+ });
+
+ it('should populate the initial state correctly', () => {
+ mutations[types.SET_INITIAL_STATE](state, {
+ ...initialPopulatedState,
+ });
+
+ expect(state.apiHost).toEqual('apiHost');
+ expect(state.enabled).toEqual(true);
+ expect(state.selectedProject).toEqual(projectList[0]);
+ expect(state.token).toEqual('token');
+ expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
+ expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
+ });
+
+ it('should receive projects successfully', () => {
+ mutations[types.RECEIVE_PROJECTS](state, [sampleBackendProject]);
+
+ expect(state.projects).toEqual([normalizedProject]);
+ });
+
+ it('should strip out unnecessary project properties', () => {
+ mutations[types.RECEIVE_PROJECTS](state, [
+ { ...sampleBackendProject, extra_property: 'extra_property' },
+ ]);
+
+ expect(state.projects).toEqual([normalizedProject]);
+ });
+
+ it('should update state when connect is successful', () => {
+ mutations[types.UPDATE_CONNECT_SUCCESS](state);
+
+ expect(state.connectSuccessful).toBe(true);
+ expect(state.connectError).toBe(false);
+ });
+
+ it('should update state when connect fails', () => {
+ mutations[types.UPDATE_CONNECT_ERROR](state);
+
+ expect(state.connectSuccessful).toBe(false);
+ expect(state.connectError).toBe(true);
+ });
+
+ it('should update state when connect is reset', () => {
+ mutations[types.RESET_CONNECT](state);
+
+ expect(state.connectSuccessful).toBe(false);
+ expect(state.connectError).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/utils_spec.js b/spec/javascripts/error_tracking_settings/utils_spec.js
new file mode 100644
index 00000000000..4b144f7daf1
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/utils_spec.js
@@ -0,0 +1,29 @@
+import { transformFrontendSettings } from '~/error_tracking_settings/utils';
+import { sampleFrontendSettings, transformedSettings } from './mock';
+
+describe('error tracking settings utils', () => {
+ describe('data transform functions', () => {
+ it('should transform settings successfully for the backend', () => {
+ expect(transformFrontendSettings(sampleFrontendSettings)).toEqual(transformedSettings);
+ });
+
+ it('should transform empty values in the settings object to null', () => {
+ const emptyFrontendSettingsObject = {
+ apiHost: '',
+ enabled: false,
+ token: '',
+ selectedProject: null,
+ };
+ const transformedEmptySettingsObject = {
+ api_host: null,
+ enabled: false,
+ token: null,
+ project: null,
+ };
+
+ expect(transformFrontendSettings(emptyFrontendSettingsObject)).toEqual(
+ transformedEmptySettingsObject,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index e8fcc8592eb..f764800fff0 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -72,7 +72,7 @@ describe('Dropdown User', () => {
});
describe('hideCurrentUser', () => {
- const fixtureTemplate = 'issues/issue_list.html.raw';
+ const fixtureTemplate = 'issues/issue_list.html';
preloadFixtures(fixtureTemplate);
let dropdown;
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index cfd0b96ec43..62d1bd69635 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Dropdown Utils', () => {
- const issueListFixture = 'issues/issue_list.html.raw';
+ const issueListFixture = 'issues/issue_list.html';
preloadFixtures(issueListFixture);
describe('getEscapedText', () => {
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 6230da77f49..a72ea6ab547 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,9 +1,4 @@
-import _ from 'underscore';
-import AjaxCache from '~/lib/utils/ajax_cache';
-import UsersCache from '~/lib/utils/users_cache';
-
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
-import DropdownUtils from '~/filtered_search//dropdown_utils';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Visual Tokens', () => {
@@ -298,6 +293,7 @@ describe('Filtered Search Visual Tokens', () => {
subject.addVisualTokenElement('milestone');
const token = tokensContainer.querySelector('.js-visual-token');
+ expect(token.classList.contains('search-token-milestone')).toEqual(true);
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toEqual('milestone');
expect(token.querySelector('.value')).toEqual(null);
@@ -307,6 +303,7 @@ describe('Filtered Search Visual Tokens', () => {
subject.addVisualTokenElement('label', 'Frontend');
const token = tokensContainer.querySelector('.js-visual-token');
+ expect(token.classList.contains('search-token-label')).toEqual(true);
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toEqual('label');
expect(token.querySelector('.value').innerText).toEqual('Frontend');
@@ -322,10 +319,12 @@ describe('Filtered Search Visual Tokens', () => {
const labelToken = tokens[0];
const assigneeToken = tokens[1];
+ expect(labelToken.classList.contains('search-token-label')).toEqual(true);
expect(labelToken.classList.contains('filtered-search-token')).toEqual(true);
expect(labelToken.querySelector('.name').innerText).toEqual('label');
expect(labelToken.querySelector('.value').innerText).toEqual('Frontend');
+ expect(assigneeToken.classList.contains('search-token-assignee')).toEqual(true);
expect(assigneeToken.classList.contains('filtered-search-token')).toEqual(true);
expect(assigneeToken.querySelector('.name').innerText).toEqual('assignee');
expect(assigneeToken.querySelector('.value').innerText).toEqual('@root');
@@ -685,349 +684,21 @@ describe('Filtered Search Visual Tokens', () => {
});
describe('renderVisualTokenValue', () => {
- const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search');
- const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken(
- 'milestone',
- 'upcoming',
- );
-
- let updateLabelTokenColorSpy;
- let updateUserTokenAppearanceSpy;
-
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${authorToken.outerHTML}
${bugLabelToken.outerHTML}
- ${keywordToken.outerHTML}
- ${milestoneToken.outerHTML}
`);
-
- spyOn(subject, 'updateLabelTokenColor');
- updateLabelTokenColorSpy = subject.updateLabelTokenColor;
-
- spyOn(subject, 'updateUserTokenAppearance');
- updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance;
});
it('renders a author token value element', () => {
- const { tokenNameElement, tokenValueContainer, tokenValueElement } = findElements(
- authorToken,
- );
+ const { tokenNameElement, tokenValueElement } = findElements(authorToken);
const tokenName = tokenNameElement.innerText;
const tokenValue = 'new value';
subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
expect(tokenValueElement.innerText).toBe(tokenValue);
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(1);
- const expectedArgs = [tokenValueContainer, tokenValueElement, tokenValue];
-
- expect(updateUserTokenAppearanceSpy.calls.argsFor(0)).toEqual(expectedArgs);
- expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
- });
-
- it('renders a label token value element', () => {
- const { tokenNameElement, tokenValueContainer, tokenValueElement } = findElements(
- bugLabelToken,
- );
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'new value';
-
- subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue);
-
- expect(tokenValueElement.innerText).toBe(tokenValue);
- expect(updateLabelTokenColorSpy.calls.count()).toBe(1);
- const expectedArgs = [tokenValueContainer, tokenValue];
-
- expect(updateLabelTokenColorSpy.calls.argsFor(0)).toEqual(expectedArgs);
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
- });
-
- it('renders a milestone token value element', () => {
- const { tokenNameElement, tokenValueElement } = findElements(milestoneToken);
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'new value';
-
- subject.renderVisualTokenValue(milestoneToken, tokenName, tokenValue);
-
- expect(tokenValueElement.innerText).toBe(tokenValue);
- expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
- });
-
- it('does not update user token appearance for `None` filter', () => {
- const { tokenNameElement } = findElements(authorToken);
-
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'None';
-
- subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
-
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
- });
-
- it('does not update user token appearance for `none` filter', () => {
- const { tokenNameElement } = findElements(authorToken);
-
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'none';
-
- subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
-
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
- });
-
- it('does not update user token appearance for `any` filter', () => {
- const { tokenNameElement } = findElements(authorToken);
-
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'any';
-
- subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
-
- expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
- });
-
- it('does not update label token color for `none` filter', () => {
- const { tokenNameElement } = findElements(bugLabelToken);
-
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'none';
-
- subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue);
-
- expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
- });
-
- it('does not update label token color for `any` filter', () => {
- const { tokenNameElement } = findElements(bugLabelToken);
-
- const tokenName = tokenNameElement.innerText;
- const tokenValue = 'any';
-
- subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue);
-
- expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
- });
- });
-
- describe('updateUserTokenAppearance', () => {
- let usersCacheSpy;
-
- beforeEach(() => {
- spyOn(UsersCache, 'retrieve').and.callFake(username => usersCacheSpy(username));
- });
-
- it('ignores error if UsersCache throws', done => {
- spyOn(window, 'Flash');
- const dummyError = new Error('Earth rotated backwards');
- const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
- const tokenValue = tokenValueElement.innerText;
- usersCacheSpy = username => {
- expect(`@${username}`).toBe(tokenValue);
- return Promise.reject(dummyError);
- };
-
- subject
- .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
- .then(() => {
- expect(window.Flash.calls.count()).toBe(0);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does nothing if user cannot be found', done => {
- const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
- const tokenValue = tokenValueElement.innerText;
- usersCacheSpy = username => {
- expect(`@${username}`).toBe(tokenValue);
- return Promise.resolve(undefined);
- };
-
- subject
- .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
- .then(() => {
- expect(tokenValueElement.innerText).toBe(tokenValue);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('replaces author token with avatar and display name', done => {
- const dummyUser = {
- name: 'Important Person',
- avatar_url: 'https://host.invalid/mypics/avatar.png',
- };
- const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
- const tokenValue = tokenValueElement.innerText;
- usersCacheSpy = username => {
- expect(`@${username}`).toBe(tokenValue);
- return Promise.resolve(dummyUser);
- };
-
- subject
- .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
- .then(() => {
- expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue);
- expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
- const avatar = tokenValueElement.querySelector('img.avatar');
-
- expect(avatar.src).toBe(dummyUser.avatar_url);
- expect(avatar.alt).toBe('');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('escapes user name when creating token', done => {
- const dummyUser = {
- name: '<script>',
- avatar_url: `${gl.TEST_HOST}/mypics/avatar.png`,
- };
- const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
- const tokenValue = tokenValueElement.innerText;
- usersCacheSpy = username => {
- expect(`@${username}`).toBe(tokenValue);
- return Promise.resolve(dummyUser);
- };
-
- subject
- .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
- .then(() => {
- expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
- tokenValueElement.querySelector('.avatar').remove();
-
- expect(tokenValueElement.innerHTML.trim()).toBe(_.escape(dummyUser.name));
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('setTokenStyle', () => {
- let originalTextColor;
-
- beforeEach(() => {
- originalTextColor = bugLabelToken.style.color;
- });
-
- it('should set backgroundColor', () => {
- const originalBackgroundColor = bugLabelToken.style.backgroundColor;
- const token = subject.setTokenStyle(bugLabelToken, 'blue', 'white');
-
- expect(token.style.backgroundColor).toEqual('blue');
- expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
- });
-
- it('should set textColor', () => {
- const token = subject.setTokenStyle(bugLabelToken, 'white', 'black');
-
- expect(token.style.color).toEqual('black');
- expect(token.style.color).not.toEqual(originalTextColor);
- });
-
- it('should add inverted class when textColor is #FFFFFF', () => {
- const token = subject.setTokenStyle(bugLabelToken, 'black', '#FFFFFF');
-
- expect(token.style.color).toEqual('rgb(255, 255, 255)');
- expect(token.style.color).not.toEqual(originalTextColor);
- expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true);
- });
- });
-
- describe('updateLabelTokenColor', () => {
- const jsonFixtureName = 'labels/project_labels.json';
- const dummyEndpoint = '/dummy/endpoint';
-
- preloadFixtures(jsonFixtureName);
-
- let labelData;
-
- beforeAll(() => {
- labelData = getJSONFixture(jsonFixtureName);
- });
-
- const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
- 'label',
- '~doesnotexist',
- );
- const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
- 'label',
- '~"some space"',
- );
-
- beforeEach(() => {
- tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${bugLabelToken.outerHTML}
- ${missingLabelToken.outerHTML}
- ${spaceLabelToken.outerHTML}
- `);
-
- const filteredSearchInput = document.querySelector('.filtered-search');
- filteredSearchInput.dataset.baseEndpoint = dummyEndpoint;
-
- AjaxCache.internalStorage = {};
- AjaxCache.internalStorage[`${dummyEndpoint}/labels.json`] = labelData;
- });
-
- const parseColor = color => {
- const dummyElement = document.createElement('div');
- dummyElement.style.color = color;
- return dummyElement.style.color;
- };
-
- const expectValueContainerStyle = (tokenValueContainer, label) => {
- expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
- expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
- expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
- };
-
- const findLabel = tokenValue =>
- labelData.find(label => tokenValue === `~${DropdownUtils.getEscapedText(label.title)}`);
-
- it('updates the color of a label token', done => {
- const { tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
- const tokenValue = tokenValueElement.innerText;
- const matchingLabel = findLabel(tokenValue);
-
- subject
- .updateLabelTokenColor(tokenValueContainer, tokenValue)
- .then(() => {
- expectValueContainerStyle(tokenValueContainer, matchingLabel);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('updates the color of a label token with spaces', done => {
- const { tokenValueContainer, tokenValueElement } = findElements(spaceLabelToken);
- const tokenValue = tokenValueElement.innerText;
- const matchingLabel = findLabel(tokenValue);
-
- subject
- .updateLabelTokenColor(tokenValueContainer, tokenValue)
- .then(() => {
- expectValueContainerStyle(tokenValueContainer, matchingLabel);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does not change color of a missing label', done => {
- const { tokenValueContainer, tokenValueElement } = findElements(missingLabelToken);
- const tokenValue = tokenValueElement.innerText;
- const matchingLabel = findLabel(tokenValue);
-
- expect(matchingLabel).toBe(undefined);
-
- subject
- .updateLabelTokenColor(tokenValueContainer, tokenValue)
- .then(() => {
- expect(tokenValueContainer.getAttribute('style')).toBe(null);
- })
- .then(done)
- .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js
deleted file mode 100644
index ea7c146fa4f..00000000000
--- a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
-
-describe('RecentSearchesServiceError', () => {
- let recentSearchesServiceError;
-
- beforeEach(() => {
- recentSearchesServiceError = new RecentSearchesServiceError();
- });
-
- it('instantiates an instance of RecentSearchesServiceError and not an Error', () => {
- expect(recentSearchesServiceError).toEqual(jasmine.any(RecentSearchesServiceError));
- expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError');
- });
-
- it('should set a default message', () => {
- expect(recentSearchesServiceError.message).toBe('Recent Searches Service is unavailable');
- });
-});
diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js
new file mode 100644
index 00000000000..14217d460cc
--- /dev/null
+++ b/spec/javascripts/filtered_search/visual_token_value_spec.js
@@ -0,0 +1,383 @@
+import VisualTokenValue from '~/filtered_search/visual_token_value';
+import _ from 'underscore';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import UsersCache from '~/lib/utils/users_cache';
+import DropdownUtils from '~/filtered_search//dropdown_utils';
+import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
+
+describe('Filtered Search Visual Tokens', () => {
+ const findElements = tokenElement => {
+ const tokenNameElement = tokenElement.querySelector('.name');
+ const tokenValueContainer = tokenElement.querySelector('.value-container');
+ const tokenValueElement = tokenValueContainer.querySelector('.value');
+ const tokenType = tokenNameElement.innerText.toLowerCase();
+ const tokenValue = tokenValueElement.innerText;
+ const subject = new VisualTokenValue(tokenValue, tokenType);
+ return { subject, tokenValueContainer, tokenValueElement };
+ };
+
+ let tokensContainer;
+ let authorToken;
+ let bugLabelToken;
+
+ beforeEach(() => {
+ setFixtures(`
+ <ul class="tokens-container">
+ ${FilteredSearchSpecHelper.createInputHTML()}
+ </ul>
+ `);
+ tokensContainer = document.querySelector('.tokens-container');
+
+ authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user');
+ bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~bug');
+ });
+
+ describe('updateUserTokenAppearance', () => {
+ let usersCacheSpy;
+
+ beforeEach(() => {
+ spyOn(UsersCache, 'retrieve').and.callFake(username => usersCacheSpy(username));
+ });
+
+ it('ignores error if UsersCache throws', done => {
+ spyOn(window, 'Flash');
+ const dummyError = new Error('Earth rotated backwards');
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = username => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.reject(dummyError);
+ };
+
+ subject
+ .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(window.Flash.calls.count()).toBe(0);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does nothing if user cannot be found', done => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = username => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.resolve(undefined);
+ };
+
+ subject
+ .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(tokenValueElement.innerText).toBe(tokenValue);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('replaces author token with avatar and display name', done => {
+ const dummyUser = {
+ name: 'Important Person',
+ avatar_url: 'https://host.invalid/mypics/avatar.png',
+ };
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = username => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.resolve(dummyUser);
+ };
+
+ subject
+ .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue);
+ expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
+ const avatar = tokenValueElement.querySelector('img.avatar');
+
+ expect(avatar.src).toBe(dummyUser.avatar_url);
+ expect(avatar.alt).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('escapes user name when creating token', done => {
+ const dummyUser = {
+ name: '<script>',
+ avatar_url: `${gl.TEST_HOST}/mypics/avatar.png`,
+ };
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = username => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.resolve(dummyUser);
+ };
+
+ subject
+ .updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
+ tokenValueElement.querySelector('.avatar').remove();
+
+ expect(tokenValueElement.innerHTML.trim()).toBe(_.escape(dummyUser.name));
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('updateLabelTokenColor', () => {
+ const jsonFixtureName = 'labels/project_labels.json';
+ const dummyEndpoint = '/dummy/endpoint';
+
+ preloadFixtures(jsonFixtureName);
+
+ let labelData;
+
+ beforeAll(() => {
+ labelData = getJSONFixture(jsonFixtureName);
+ });
+
+ const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
+ 'label',
+ '~doesnotexist',
+ );
+ const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
+ 'label',
+ '~"some space"',
+ );
+
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${bugLabelToken.outerHTML}
+ ${missingLabelToken.outerHTML}
+ ${spaceLabelToken.outerHTML}
+ `);
+
+ const filteredSearchInput = document.querySelector('.filtered-search');
+ filteredSearchInput.dataset.baseEndpoint = dummyEndpoint;
+
+ AjaxCache.internalStorage = {};
+ AjaxCache.internalStorage[`${dummyEndpoint}/labels.json`] = labelData;
+ });
+
+ const parseColor = color => {
+ const dummyElement = document.createElement('div');
+ dummyElement.style.color = color;
+ return dummyElement.style.color;
+ };
+
+ const expectValueContainerStyle = (tokenValueContainer, label) => {
+ expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
+ expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
+ expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
+ };
+
+ const findLabel = tokenValue =>
+ labelData.find(label => tokenValue === `~${DropdownUtils.getEscapedText(label.title)}`);
+
+ it('updates the color of a label token', done => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+
+ subject
+ .updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expectValueContainerStyle(tokenValueContainer, matchingLabel);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('updates the color of a label token with spaces', done => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(spaceLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+
+ subject
+ .updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expectValueContainerStyle(tokenValueContainer, matchingLabel);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not change color of a missing label', done => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(missingLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+
+ expect(matchingLabel).toBe(undefined);
+
+ subject
+ .updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expect(tokenValueContainer.getAttribute('style')).toBe(null);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('setTokenStyle', () => {
+ let originalTextColor;
+
+ beforeEach(() => {
+ originalTextColor = bugLabelToken.style.color;
+ });
+
+ it('should set backgroundColor', () => {
+ const originalBackgroundColor = bugLabelToken.style.backgroundColor;
+ const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'blue', 'white');
+
+ expect(token.style.backgroundColor).toEqual('blue');
+ expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
+ });
+
+ it('should set textColor', () => {
+ const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'white', 'black');
+
+ expect(token.style.color).toEqual('black');
+ expect(token.style.color).not.toEqual(originalTextColor);
+ });
+
+ it('should add inverted class when textColor is #FFFFFF', () => {
+ const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'black', '#FFFFFF');
+
+ expect(token.style.color).toEqual('rgb(255, 255, 255)');
+ expect(token.style.color).not.toEqual(originalTextColor);
+ expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true);
+ });
+ });
+
+ describe('render', () => {
+ const setupSpies = subject => {
+ spyOn(subject, 'updateLabelTokenColor'); // eslint-disable-line jasmine/no-unsafe-spy
+ const updateLabelTokenColorSpy = subject.updateLabelTokenColor;
+
+ spyOn(subject, 'updateUserTokenAppearance'); // eslint-disable-line jasmine/no-unsafe-spy
+ const updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance;
+
+ return { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy };
+ };
+
+ const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search');
+ const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken(
+ 'milestone',
+ 'upcoming',
+ );
+
+ beforeEach(() => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
+ ${authorToken.outerHTML}
+ ${bugLabelToken.outerHTML}
+ ${keywordToken.outerHTML}
+ ${milestoneToken.outerHTML}
+ `);
+ });
+
+ it('renders a author token value element', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(1);
+ const expectedArgs = [tokenValueContainer, tokenValueElement];
+
+ expect(updateUserTokenAppearanceSpy.calls.argsFor(0)).toEqual(expectedArgs);
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
+
+ it('renders a label token value element', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+
+ const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(1);
+ const expectedArgs = [tokenValueContainer];
+
+ expect(updateLabelTokenColorSpy.calls.argsFor(0)).toEqual(expectedArgs);
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('renders a milestone token value element', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(milestoneToken);
+
+ const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update user token appearance for `none` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ subject.tokenValue = 'none';
+
+ const { updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update user token appearance for `None` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ subject.tokenValue = 'None';
+
+ const { updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update user token appearance for `any` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ subject.tokenValue = 'any';
+
+ const { updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update label token color for `None` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+
+ subject.tokenValue = 'None';
+
+ const { updateLabelTokenColorSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update label token color for `none` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+
+ subject.tokenValue = 'none';
+
+ const { updateLabelTokenColorSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update label token color for `any` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+
+ subject.tokenValue = 'any';
+
+ const { updateLabelTokenColorSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index 0c35cdd778e..bed020f5b0a 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1,2 +1,5 @@
*.html.raw
+*.html
*.json
+*.pdf
+*.bmpr
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb
index 387858cba77..e0aaecf626a 100644
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ b/spec/javascripts/fixtures/abuse_reports.rb
@@ -18,10 +18,9 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll
sign_in(admin)
end
- it 'abuse_reports/abuse_reports_list.html.raw' do |example|
+ it 'abuse_reports/abuse_reports_list.html' do
get :index
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
index 9989ac4fff2..22a5de66577 100644
--- a/spec/javascripts/fixtures/admin_users.rb
+++ b/spec/javascripts/fixtures/admin_users.rb
@@ -17,13 +17,12 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('admin/users')
end
- it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
+ it 'admin/users/new_with_internal_user_regex.html' do
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
get :new
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/ajax_loading_spinner.html.haml b/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
deleted file mode 100644
index 09d8c9df3b2..00000000000
--- a/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%a.js-ajax-loading-spinner{href: "http://goesnowhere.nothing/whereami", data: {remote: true}}
- %i.fa.fa-trash-o
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
index a9d3043f73d..d4651fa6ece 100644
--- a/spec/javascripts/fixtures/application_settings.rb
+++ b/spec/javascripts/fixtures/application_settings.rb
@@ -23,12 +23,11 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c
remove_repository(project)
end
- it 'application_settings/accounts_and_limit.html.raw' do |example|
+ it 'application_settings/accounts_and_limit.html' do
stub_application_setting(user_default_external: false)
get :show
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb
new file mode 100644
index 00000000000..b20a0159d7d
--- /dev/null
+++ b/spec/javascripts/fixtures/autocomplete_sources.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:admin) { create(:admin) }
+ set(:group) { create(:group, name: 'frontend-fixtures') }
+ set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
+ set(:issue) { create(:issue, project: project) }
+
+ before(:all) do
+ clean_frontend_fixtures('autocomplete_sources/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'autocomplete_sources/labels.json' do
+ issue.labels << create(:label, project: project, title: 'bug')
+ issue.labels << create(:label, project: project, title: 'critical')
+
+ create(:label, project: project, title: 'feature')
+ create(:label, project: project, title: 'documentation')
+
+ get :labels,
+ format: :json,
+ params: {
+ namespace_id: group.path,
+ project_id: project.path,
+ type: issue.class.name,
+ type_id: issue.id
+ }
+
+ expect(response).to be_success
+ end
+end
diff --git a/spec/javascripts/fixtures/balsamiq.rb b/spec/javascripts/fixtures/balsamiq.rb
deleted file mode 100644
index 234e246119a..00000000000
--- a/spec/javascripts/fixtures/balsamiq.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe 'Balsamiq file', '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'balsamiq-project') }
-
- before(:all) do
- clean_frontend_fixtures('blob/balsamiq/')
- end
-
- it 'blob/balsamiq/test.bmpr' do |example|
- blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr')
-
- store_frontend_fixture(blob.data.force_encoding('utf-8'), example.description)
- end
-end
diff --git a/spec/javascripts/fixtures/balsamiq_viewer.html.haml b/spec/javascripts/fixtures/balsamiq_viewer.html.haml
deleted file mode 100644
index 18166ba4901..00000000000
--- a/spec/javascripts/fixtures/balsamiq_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
index cd66d98f92a..07670552cd5 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/javascripts/fixtures/blob.rb
@@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'blob/show.html.raw' do |example|
+ it 'blob/show.html' do
get(:show, params: {
namespace_id: project.namespace,
project_id: project,
@@ -30,6 +30,5 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
})
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
index 1d675e008ba..5835721d3d5 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/javascripts/fixtures/boards.rb
@@ -17,13 +17,12 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
sign_in(admin)
end
- it 'boards/show.html.raw' do |example|
+ it 'boards/show.html' do
get(:index, params: {
namespace_id: project.namespace,
project_id: project
})
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
index 3cc713ef90f..204aa9b7c7a 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/javascripts/fixtures/branches.rb
@@ -21,13 +21,12 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'branches/new_branch.html.raw' do |example|
+ it 'branches/new_branch.html' do
get :new, params: {
namespace_id: project.namespace.to_param,
project_id: project
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index 69dbe54ffc2..1076404e0e3 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'clusters/show_cluster.html.raw' do |example|
+ it 'clusters/show_cluster.html' do
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -30,6 +30,5 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
index 295f13b34a4..ff9a4bc1adc 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/javascripts/fixtures/commit.rb
@@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
- it 'commit/show.html.raw' do |example|
+ it 'commit/show.html' do
params = {
namespace_id: project.namespace,
project_id: project,
@@ -29,6 +29,5 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
get :show, params: params
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/create_item_dropdown.html.haml b/spec/javascripts/fixtures/create_item_dropdown.html.haml
deleted file mode 100644
index d4d91b93caf..00000000000
--- a/spec/javascripts/fixtures/create_item_dropdown.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.js-create-item-dropdown-fixture-root
- %input{ name: 'variable[environment]', type: 'hidden' }
- = dropdown_tag('some label',
- options: { toggle_class: 'js-dropdown-menu-toggle',
- content_class: 'js-dropdown-content',
- filter: true,
- dropdown_class: "dropdown-menu-selectable",
- footer_content: true }) do
- %ul.dropdown-footer-list
- %li
- %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item" }
- Create wildcard
- %code
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
index a333d9c0150..38eab853da2 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/javascripts/fixtures/deploy_keys.rb
@@ -24,7 +24,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
render_views
- it 'deploy_keys/keys.json' do |example|
+ it 'deploy_keys/keys.json' do
create(:rsa_deploy_key_2048, public: true)
project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
@@ -39,6 +39,5 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
}, format: :json
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml
deleted file mode 100644
index 59edc0396d2..00000000000
--- a/spec/javascripts/fixtures/environments/table.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-%table
- %thead
- %tr
- %th Environment
- %th Last deployment
- %th Job
- %th Commit
- %th
- %th
- %tbody
- %tr#environment-row
diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml
deleted file mode 100644
index aa7af61c7eb..00000000000
--- a/spec/javascripts/fixtures/event_filter.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
- %li.active
- %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"}
- %span
- All
- %li
- %a.event-filter-link{ id: "push_event_filter", title: "Filter by push events", href: "/dashboard/activity"}
- %span
- Push events
- %li
- %a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"}
- %span
- Merge events
- %li
- %a.event-filter-link{ id: "issue_event_filter", title: "Filter by issue events", href: "/dashboard/activity"}
- %span
- Issue events
- %li
- %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"}
- %span
- Comments
- %li
- %a.event-filter-link{ id: "team_event_filter", title: "Filter by team", href: "/dashboard/activity"}
- %span
- Team \ No newline at end of file
diff --git a/spec/javascripts/fixtures/gl_dropdown.html.haml b/spec/javascripts/fixtures/gl_dropdown.html.haml
deleted file mode 100644
index 43d57c2c4dc..00000000000
--- a/spec/javascripts/fixtures/gl_dropdown.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%div
- .dropdown.inline
- %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
- .dropdown-toggle-text
- Projects
- %i.fa.fa-chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Go to project
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: 'Close'}}
- %i.fa.fa-times.dropdown-menu-close-icon
- .dropdown-input
- %input.dropdown-input-field{type: 'search', placeholder: 'Filter results'}
- %i.fa.fa-search.dropdown-input-search
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml
deleted file mode 100644
index 69445b61367..00000000000
--- a/spec/javascripts/fixtures/gl_field_errors.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%form.gl-show-field-errors{action: 'submit', method: 'post'}
- .form-group
- %input.required-text{required: true, type: 'text'} Text
- .form-group
- %input.email{type: 'email', title: 'Please provide a valid email address.', required: true } Email
- .form-group
- %input.password{type: 'password', required: true} Password
- .form-group
- %input.alphanumeric{type: 'text', pattern: '[a-zA-Z0-9]', required: true} Alphanumeric
- .form-group
- %input.hidden{ type:'hidden' }
- .form-group
- %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
- .form-group
- %input.submit{type: 'submit'} Submit
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
index 03136f4e661..4d0afc3ce1a 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/javascripts/fixtures/groups.rb
@@ -18,20 +18,18 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
end
describe GroupsController, '(JavaScript fixtures)', type: :controller do
- it 'groups/edit.html.raw' do |example|
+ it 'groups/edit.html' do
get :edit, params: { id: group }
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'groups/ci_cd_settings.html.raw' do |example|
+ it 'groups/ci_cd_settings.html' do
get :show, params: { group_id: group }
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fixtures/issuable_filter.html.haml b/spec/javascripts/fixtures/issuable_filter.html.haml
deleted file mode 100644
index 84fa5395cb8..00000000000
--- a/spec/javascripts/fixtures/issuable_filter.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
- %input{id: 'utf8', name: 'utf8', value: '✓'}
- %input{id: 'check-all-issues', name: 'check-all-issues'}
- %input{id: 'search', name: 'search'}
- %input{id: 'author_id', name: 'author_id'}
- %input{id: 'assignee_id', name: 'assignee_id'}
- %input{id: 'milestone_title', name: 'milestone_title'}
- %input{id: 'label_name', name: 'label_name'}
diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml
deleted file mode 100644
index 06ce248dc9c..00000000000
--- a/spec/javascripts/fixtures/issue_sidebar_label.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.block.labels
- .sidebar-collapsed-icon.js-sidebar-labels-tooltip
- .title.hide-collapsed
- %a.edit-link.float-right{ href: "#" }
- Edit
- .selectbox.hide-collapsed{ style: "display: none;" }
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect{ type: "button", data: { ability_name: "issue", field_name: "issue[label_names][]", issue_update: "/root/test/issues/2.json", labels: "/root/test/labels.json", project_id: "12", show_any: "true", show_no: "true", toggle: "dropdown" } }
- %span.dropdown-toggle-text
- Label
- %i.fa.fa-chevron-down
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 9b8e90c2a43..d8d77f767de 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
remove_repository(project)
end
- it 'issues/open-issue.html.raw' do |example|
- render_issue(example.description, create(:issue, project: project))
+ it 'issues/open-issue.html' do
+ render_issue(create(:issue, project: project))
end
- it 'issues/closed-issue.html.raw' do |example|
- render_issue(example.description, create(:closed_issue, project: project))
+ it 'issues/closed-issue.html' do
+ render_issue(create(:closed_issue, project: project))
end
- it 'issues/issue-with-task-list.html.raw' do |example|
+ it 'issues/issue-with-task-list.html' do
issue = create(:issue, project: project, description: '- [ ] Task List Item')
- render_issue(example.description, issue)
+ render_issue(issue)
end
- it 'issues/issue_with_comment.html.raw' do |example|
+ it 'issues/issue_with_comment.html' do
issue = create(:issue, project: project)
create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
- render_issue(example.description, issue)
+ render_issue(issue)
end
- it 'issues/issue_list.html.raw' do |example|
+ it 'issues/issue_list.html' do
create(:issue, project: project)
get :index, params: {
@@ -49,12 +49,11 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
private
- def render_issue(fixture_file_name, issue)
+ def render_issue(issue)
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -62,6 +61,62 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
}
expect(response).to be_success
- store_frontend_fixture(response, fixture_file_name)
+ end
+end
+
+describe API::Issues, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+ include JavaScriptFixturesHelpers
+
+ def get_related_merge_requests(project_id, issue_iid, user = nil)
+ get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
+ end
+
+ def create_referencing_mr(user, project, issue)
+ attributes = {
+ author: user,
+ source_project: project,
+ target_project: project,
+ source_branch: "master",
+ target_branch: "test",
+ assignee: user,
+ description: "See #{issue.to_reference}"
+ }
+ create(:merge_request, attributes).tap do |merge_request|
+ create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
+ end
+ end
+
+ it 'issues/related_merge_requests.json' do
+ user = create(:user)
+ project = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ issue_title = 'foo'
+ issue_description = 'closed'
+ milestone = create(:milestone, title: '1.0.0', project: project)
+ issue = create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+
+ project.add_reporter(user)
+ create_referencing_mr(user, project, issue)
+
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "Some description")
+ project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline))
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect(response).to be_success
end
end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index 433bb690a1c..46ccd6f8c8a 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'builds/build-with-artifacts.html.raw' do |example|
+ it 'builds/build-with-artifacts.html' do
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -40,10 +40,9 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
- it 'jobs/delayed.json' do |example|
+ it 'jobs/delayed.json' do
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -51,6 +50,5 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
}, format: :json
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb
index 9420194e675..4d1b7317274 100644
--- a/spec/javascripts/fixtures/labels.rb
+++ b/spec/javascripts/fixtures/labels.rb
@@ -30,13 +30,12 @@ describe 'Labels (JavaScript fixtures)' do
sign_in(admin)
end
- it 'labels/group_labels.json' do |example|
+ it 'labels/group_labels.json' do
get :index, params: {
group_id: group
}, format: 'json'
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
@@ -47,14 +46,13 @@ describe 'Labels (JavaScript fixtures)' do
sign_in(admin)
end
- it 'labels/project_labels.json' do |example|
+ it 'labels/project_labels.json' do
get :index, params: {
namespace_id: group,
project_id: project
}, format: 'json'
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
deleted file mode 100644
index 2782c50e298..00000000000
--- a/spec/javascripts/fixtures/line_highlighter.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-.file-holder
- .file-content
- .line-numbers
- - 1.upto(25) do |i|
- %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
- %i.fa.fa-link
- = i
- %pre.code.highlight
- %code
- - 1.upto(25) do |i|
- %span.line{id: "LC#{i}"}= "Line #{i}"
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
deleted file mode 100644
index 632606e0536..00000000000
--- a/spec/javascripts/fixtures/linked_tabs.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%ul.nav.nav-tabs.new-session-tabs.linked-tabs
- %li.nav-item
- %a.nav-link{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
- Tab 1
- %li.nav-item
- %a.nav-link{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
- Tab 2
-
-.tab-content
- #tab1.tab-pane
- Tab 1 Content
- #tab2.tab-pane
- Tab 2 Content
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index eb37be87e1d..05860be2291 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -42,52 +42,52 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
remove_repository(project)
end
- it 'merge_requests/merge_request_of_current_user.html.raw' do |example|
+ it 'merge_requests/merge_request_of_current_user.html' do
merge_request.update(author: admin)
- render_merge_request(example.description, merge_request)
+ render_merge_request(merge_request)
end
- it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
+ it 'merge_requests/merge_request_with_task_list.html' do
create(:ci_build, :pending, pipeline: pipeline)
- render_merge_request(example.description, merge_request)
+ render_merge_request(merge_request)
end
- it 'merge_requests/merged_merge_request.html.raw' do |example|
+ it 'merge_requests/merged_merge_request.html' do
expect_next_instance_of(MergeRequest) do |merge_request|
allow(merge_request).to receive(:source_branch_exists?).and_return(true)
allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
end
- render_merge_request(example.description, merged_merge_request)
+ render_merge_request(merged_merge_request)
end
- it 'merge_requests/diff_comment.html.raw' do |example|
+ it 'merge_requests/diff_comment.html' do
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(example.description, merge_request)
+ render_merge_request(merge_request)
end
- it 'merge_requests/merge_request_with_comment.html.raw' do |example|
+ it 'merge_requests/merge_request_with_comment.html' do
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
- render_merge_request(example.description, merge_request)
+ render_merge_request(merge_request)
end
- it 'merge_requests/discussions.json' do |example|
+ it 'merge_requests/discussions.json' do
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request, example.description)
+ render_discussions_json(merge_request)
end
- it 'merge_requests/diff_discussion.json' do |example|
+ it 'merge_requests/diff_discussion.json' do
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request, example.description)
+ render_discussions_json(merge_request)
end
- it 'merge_requests/resolved_diff_discussion.json' do |example|
+ it 'merge_requests/resolved_diff_discussion.json' do
note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
- render_discussions_json(merge_request, example.description)
+ render_discussions_json(merge_request)
end
context 'with image diff' do
@@ -106,25 +106,23 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
)
end
- it 'merge_requests/image_diff_discussion.json' do |example|
+ it 'merge_requests/image_diff_discussion.json' do
create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position)
- render_discussions_json(merge_request2, example.description)
+ render_discussions_json(merge_request2)
end
end
private
- def render_discussions_json(merge_request, fixture_file_name)
+ def render_discussions_json(merge_request)
get :discussions, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.to_param
}, format: :json
-
- store_frontend_fixture(response, fixture_file_name)
end
- def render_merge_request(fixture_file_name, merge_request)
+ def render_merge_request(merge_request)
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -132,6 +130,5 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
}, format: :html
expect(response).to be_success
- store_frontend_fixture(response, fixture_file_name)
end
end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
index 57462e74bb2..03b9b713fd8 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -34,29 +34,29 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
remove_repository(project)
end
- it 'merge_request_diffs/with_commit.json' do |example|
+ it 'merge_request_diffs/with_commit.json' do
# Create a user that matches the selected commit author
# This is so that the "author" information will be populated
create(:user, email: selected_commit.author_email, name: selected_commit.author_name)
- render_merge_request(example.description, merge_request, commit_id: selected_commit.sha)
+ render_merge_request(merge_request, commit_id: selected_commit.sha)
end
- it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example|
+ it 'merge_request_diffs/inline_changes_tab_with_comments.json' do
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(example.description, merge_request)
+ render_merge_request(merge_request)
end
- it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do |example|
+ it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(example.description, merge_request, view: 'parallel')
+ render_merge_request(merge_request, view: 'parallel')
end
private
- def render_merge_request(fixture_file_name, merge_request, view: 'inline', **extra_params)
+ def render_merge_request(merge_request, view: 'inline', **extra_params)
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -66,6 +66,5 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
}, format: :json
expect(response).to be_success
- store_frontend_fixture(response, fixture_file_name)
end
end
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
deleted file mode 100644
index 8447dfdda32..00000000000
--- a/spec/javascripts/fixtures/merge_requests_show.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%a.btn-close
-
-.detail-page-description
- .description.js-task-list-container
- .wiki
- %ul.task-list
- %li.task-list-item
- %input.task-list-item-checkbox{type: 'checkbox'}
- Task List Item
- %textarea.js-task-list-field
- \- [ ] Task List Item
-
-%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
deleted file mode 100644
index 74584993739..00000000000
--- a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%div.js-builds-dropdown-tests.dropdown.dropdown.js-mini-pipeline-graph
- %button.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar', data: { toggle: 'dropdown'} }
- Dropdown
-
- %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
- %li.js-builds-dropdown-list.scrollable-menu
- %ul
-
- %li.js-builds-dropdown-loading.hidden
- %span.fa.fa-spinner
diff --git a/spec/javascripts/fixtures/notebook_viewer.html.haml b/spec/javascripts/fixtures/notebook_viewer.html.haml
deleted file mode 100644
index 17a7a9d8f31..00000000000
--- a/spec/javascripts/fixtures/notebook_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content#js-notebook-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml
deleted file mode 100644
index a5d7c4e816a..00000000000
--- a/spec/javascripts/fixtures/oauth_remember_me.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-#oauth-container
- %input#remember_me{ type: "checkbox" }
-
- %a.oauth-login.twitter{ href: "http://example.com/" }
- %a.oauth-login.github{ href: "http://example.com/" }
- %a.oauth-login.facebook{ href: "http://example.com/?redirect_fragment=L1" }
diff --git a/spec/javascripts/fixtures/pdf.rb b/spec/javascripts/fixtures/pdf.rb
deleted file mode 100644
index ef9976b9fd3..00000000000
--- a/spec/javascripts/fixtures/pdf.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe 'PDF file', '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'pdf-project') }
-
- before(:all) do
- clean_frontend_fixtures('blob/pdf/')
- end
-
- it 'blob/pdf/test.pdf' do |example|
- blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
-
- store_frontend_fixture(blob.data.force_encoding("utf-8"), example.description)
- end
-end
diff --git a/spec/javascripts/fixtures/pdf_viewer.html.haml b/spec/javascripts/fixtures/pdf_viewer.html.haml
deleted file mode 100644
index 2e57beae54b..00000000000
--- a/spec/javascripts/fixtures/pdf_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content#js-pdf-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml
deleted file mode 100644
index c0b5ab4411e..00000000000
--- a/spec/javascripts/fixtures/pipeline_graph.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%div.pipeline-visualization.js-pipeline-graph
- %ul.stage-column-list
- %li.stage-column
- .stage-name
- %a{:href => "/"}
- Test
- .builds-container
- %ul
- %li.build
- .curve
- %a
- %svg
- .ci-status-text
- stop_review
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
index 05d79ec8de9..aecd56e6198 100644
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ b/spec/javascripts/fixtures/pipeline_schedules.rb
@@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
sign_in(admin)
end
- it 'pipeline_schedules/edit.html.raw' do |example|
+ it 'pipeline_schedules/edit.html' do
get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -29,10 +29,9 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
- it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
+ it 'pipeline_schedules/edit_with_variables.html' do
get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -40,6 +39,5 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml
deleted file mode 100644
index 0161c0550d1..00000000000
--- a/spec/javascripts/fixtures/pipelines.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%div
- #pipelines-list-vue{ data: { endpoint: 'foo',
- "help-page-path" => 'foo',
- "help-auto-devops-path" => 'foo',
- "empty-state-svg-path" => 'foo',
- "error-state-svg-path" => 'foo',
- "new-pipeline-path" => 'foo',
- "can-create-pipeline" => 'true',
- "has-ci" => 'foo',
- "ci-lint-path" => 'foo',
- "reset-cache-path" => 'foo' } }
-
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
index 42b552e81c0..de6fcfe10f4 100644
--- a/spec/javascripts/fixtures/pipelines.rb
+++ b/spec/javascripts/fixtures/pipelines.rb
@@ -23,13 +23,12 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll
sign_in(admin)
end
- it 'pipelines/pipelines.json' do |example|
+ it 'pipelines/pipelines.json' do
get :index, params: {
namespace_id: namespace,
project_id: project
}, format: :json
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml
deleted file mode 100644
index 432cd5fcc74..00000000000
--- a/spec/javascripts/fixtures/project_select_combo_button.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.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', type: 'issues' }, 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 85f02923804..94c59207898 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -28,49 +28,45 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
- it 'projects/dashboard.html.raw' do |example|
+ it 'projects/dashboard.html' do
get :show, params: {
namespace_id: project.namespace.to_param,
id: project
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
- it 'projects/overview.html.raw' do |example|
+ it 'projects/overview.html' do
get :show, params: {
namespace_id: project_with_repo.namespace.to_param,
id: project_with_repo
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
- it 'projects/edit.html.raw' do |example|
+ it 'projects/edit.html' do
get :edit, params: {
namespace_id: project.namespace.to_param,
id: project
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'projects/ci_cd_settings.html.raw' do |example|
+ it 'projects/ci_cd_settings.html' do
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
- it 'projects/ci_cd_settings_with_variables.html.raw' do |example|
+ it 'projects/ci_cd_settings_with_variables.html' do
create(:ci_variable, project: project_variable_populated)
create(:ci_variable, project: project_variable_populated)
@@ -80,7 +76,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index 746fbfd66dd..f3171fdd97b 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'services/prometheus/prometheus_service.html.raw' do |example|
+ it 'services/prometheus/prometheus_service.html' do
get :edit, params: {
namespace_id: namespace,
project_id: project,
@@ -30,6 +30,5 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb
index 82770beb39b..801c80a0112 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/javascripts/fixtures/raw.rb
@@ -1,34 +1,39 @@
require 'spec_helper'
-describe 'Raw files', '(JavaScript fixtures)', type: :controller do
+describe 'Raw files', '(JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') }
+ let(:response) { @blob.data.force_encoding('UTF-8') }
before(:all) do
+ clean_frontend_fixtures('blob/balsamiq/')
clean_frontend_fixtures('blob/notebook/')
+ clean_frontend_fixtures('blob/pdf/')
end
after do
remove_repository(project)
end
- it 'blob/notebook/basic.json' do |example|
- blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
-
- store_frontend_fixture(blob.data, example.description)
+ it 'blob/balsamiq/test.bmpr' do
+ @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr')
end
- it 'blob/notebook/worksheets.json' do |example|
- blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb')
+ it 'blob/notebook/basic.json' do
+ @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
+ end
- store_frontend_fixture(blob.data, example.description)
+ it 'blob/notebook/worksheets.json' do
+ @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb')
end
- it 'blob/notebook/math.json' do |example|
- blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb')
+ it 'blob/notebook/math.json' do
+ @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb')
+ end
- store_frontend_fixture(blob.data, example.description)
+ it 'blob/pdf/test.pdf' do
+ @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
end
end
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
index 703cd3d49fa..22fc546d761 100644
--- a/spec/javascripts/fixtures/search.rb
+++ b/spec/javascripts/fixtures/search.rb
@@ -9,10 +9,9 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('search/')
end
- it 'search/show.html.raw' do |example|
+ it 'search/show.html' do
get :show
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/search_autocomplete.html.haml b/spec/javascripts/fixtures/search_autocomplete.html.haml
deleted file mode 100644
index 4aa54da9411..00000000000
--- a/spec/javascripts/fixtures/search_autocomplete.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.search.search-form
- %form.form-inline
- .search-input-container
- .search-input-wrap
- .dropdown
- %input#search.search-input.dropdown-menu-toggle
- .dropdown-menu.dropdown-select
- .dropdown-content
- %input{ type: "hidden", class: "js-search-project-options" }
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 6ccd74a07ff..2237702ccca 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'services/edit_service.html.raw' do |example|
+ it 'services/edit_service.html' do
get :edit, params: {
namespace_id: namespace,
project_id: project,
@@ -30,6 +30,5 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
}
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb
index e90a58e8c54..92b74c01c89 100644
--- a/spec/javascripts/fixtures/sessions.rb
+++ b/spec/javascripts/fixtures/sessions.rb
@@ -16,11 +16,10 @@ describe 'Sessions (JavaScript fixtures)' do
set_devise_mapping(context: @request)
end
- it 'sessions/new.html.raw' do |example|
+ it 'sessions/new.html' do
get :new
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml
deleted file mode 100644
index 2e00fe7865e..00000000000
--- a/spec/javascripts/fixtures/signin_tabs.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-%ul.nav-links.new-session-tabs
- %li.active
- %a{ href: '#ldap' } LDAP
- %li
- %a{ href: '#login-pane'} Standard
diff --git a/spec/javascripts/fixtures/sketch_viewer.html.haml b/spec/javascripts/fixtures/sketch_viewer.html.haml
deleted file mode 100644
index f01bd00925a..00000000000
--- a/spec/javascripts/fixtures/sketch_viewer.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-.file-content#js-sketch-viewer{ data: { endpoint: '/test_sketch_file.sketch' } }
- .js-loading-icon
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
index bcd6546f3df..ace84b14eb7 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -23,12 +23,11 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'snippets/show.html.raw' do |example|
+ it 'snippets/show.html' do
create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
get(:show, params: { id: snippet.to_param })
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
diff --git a/spec/javascripts/fixtures/static/README.md b/spec/javascripts/fixtures/static/README.md
new file mode 100644
index 00000000000..b5c2f8233bf
--- /dev/null
+++ b/spec/javascripts/fixtures/static/README.md
@@ -0,0 +1,3 @@
+# Please do not add new files here!
+
+Instead use a Ruby file in the fixtures root directory (`spec/javascripts/fixtures/`).
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/javascripts/fixtures/static/ajax_loading_spinner.html
new file mode 100644
index 00000000000..0e1ebb32b1c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html
@@ -0,0 +1,3 @@
+<a class="js-ajax-loading-spinner" data-remote href="http://goesnowhere.nothing/whereami">
+<i class="fa fa-trash-o"></i>
+</a>
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/javascripts/fixtures/static/balsamiq_viewer.html
new file mode 100644
index 00000000000..cdd723d1a84
--- /dev/null
+++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html
@@ -0,0 +1 @@
+<div class="file-content balsamiq-viewer" data-endpoint="/test" id="js-balsamiq-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/javascripts/fixtures/static/create_item_dropdown.html
new file mode 100644
index 00000000000..d2d38370092
--- /dev/null
+++ b/spec/javascripts/fixtures/static/create_item_dropdown.html
@@ -0,0 +1,11 @@
+<div class="js-create-item-dropdown-fixture-root">
+<input name="variable[environment]" type="hidden">
+<div class="dropdown "><button class="dropdown-menu-toggle js-dropdown-menu-toggle" type="button" data-toggle="dropdown"><span class="dropdown-toggle-text ">some label</span><i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i></button><div class="dropdown-menu dropdown-select dropdown-menu-selectable"><div class="dropdown-input"><input type="search" id="" class="dropdown-input-field" autocomplete="off" /><i aria-hidden="true" data-hidden="true" class="fa fa-search dropdown-input-search"></i><i aria-hidden="true" data-hidden="true" role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i></div><div class="dropdown-content js-dropdown-content"></div><div class="dropdown-footer"><ul class="dropdown-footer-list">
+<li>
+<button class="dropdown-create-new-item-button js-dropdown-create-new-item">
+Create wildcard
+<code></code>
+</button>
+</li>
+</ul>
+</div><div class="dropdown-loading"><i aria-hidden="true" data-hidden="true" class="fa fa-spinner fa-spin"></i></div></div></div></div>
diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/javascripts/fixtures/static/environments/table.html
new file mode 100644
index 00000000000..417af564ff1
--- /dev/null
+++ b/spec/javascripts/fixtures/static/environments/table.html
@@ -0,0 +1,15 @@
+<table>
+<thead>
+<tr>
+<th>Environment</th>
+<th>Last deployment</th>
+<th>Job</th>
+<th>Commit</th>
+<th></th>
+<th></th>
+</tr>
+</thead>
+<tbody>
+<tr id="environment-row"></tr>
+</tbody>
+</table>
diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/javascripts/fixtures/static/event_filter.html
new file mode 100644
index 00000000000..8e9b6fb1b5c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/event_filter.html
@@ -0,0 +1,44 @@
+<ul class="nav-links event-filter scrolling-tabs nav nav-tabs">
+<li class="active">
+<a class="event-filter-link" href="/dashboard/activity" id="all_event_filter" title="Filter by all">
+<span>
+All
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="push_event_filter" title="Filter by push events">
+<span>
+Push events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="merged_event_filter" title="Filter by merge events">
+<span>
+Merge events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="issue_event_filter" title="Filter by issue events">
+<span>
+Issue events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="comments_event_filter" title="Filter by comments">
+<span>
+Comments
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="team_event_filter" title="Filter by team">
+<span>
+Team
+</span>
+</a>
+</li>
+</ul>
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/javascripts/fixtures/static/gl_dropdown.html
new file mode 100644
index 00000000000..08f6738414e
--- /dev/null
+++ b/spec/javascripts/fixtures/static/gl_dropdown.html
@@ -0,0 +1,26 @@
+<div>
+<div class="dropdown inline">
+<button class="dropdown-menu-toggle" data-toggle="dropdown" id="js-project-dropdown" type="button">
+<div class="dropdown-toggle-text">
+Projects
+</div>
+<i class="fa fa-chevron-down dropdown-toggle-caret js-projects-dropdown-toggle"></i>
+</button>
+<div class="dropdown-menu dropdown-select dropdown-menu-selectable">
+<div class="dropdown-title">
+<span>Go to project</span>
+<button aria="{:label=&gt;&quot;Close&quot;}" class="dropdown-title-button dropdown-menu-close">
+<i class="fa fa-times dropdown-menu-close-icon"></i>
+</button>
+</div>
+<div class="dropdown-input">
+<input class="dropdown-input-field" placeholder="Filter results" type="search">
+<i class="fa fa-search dropdown-input-search"></i>
+</div>
+<div class="dropdown-content"></div>
+<div class="dropdown-loading">
+<i class="fa fa-spinner fa-spin"></i>
+</div>
+</div>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/javascripts/fixtures/static/gl_field_errors.html
new file mode 100644
index 00000000000..f8470e02b7c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/gl_field_errors.html
@@ -0,0 +1,22 @@
+<form action="submit" class="gl-show-field-errors" method="post">
+<div class="form-group">
+<input class="required-text" required type="text">Text</input>
+</div>
+<div class="form-group">
+<input class="email" required title="Please provide a valid email address." type="email">Email</input>
+</div>
+<div class="form-group">
+<input class="password" required type="password">Password</input>
+</div>
+<div class="form-group">
+<input class="alphanumeric" pattern="[a-zA-Z0-9]" required type="text">Alphanumeric</input>
+</div>
+<div class="form-group">
+<input class="hidden" type="hidden">
+</div>
+<div class="form-group">
+<input class="custom gl-field-error-ignore" type="text">Custom, do not validate</input>
+</div>
+<div class="form-group"></div>
+<input class="submit" type="submit">Submit</input>
+</form>
diff --git a/spec/javascripts/fixtures/images/green_box.png b/spec/javascripts/fixtures/static/images/green_box.png
index cd1ff9f9ade..cd1ff9f9ade 100644
--- a/spec/javascripts/fixtures/images/green_box.png
+++ b/spec/javascripts/fixtures/static/images/green_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/one_white_pixel.png b/spec/javascripts/fixtures/static/images/one_white_pixel.png
index 073fcf40a18..073fcf40a18 100644
--- a/spec/javascripts/fixtures/one_white_pixel.png
+++ b/spec/javascripts/fixtures/static/images/one_white_pixel.png
Binary files differ
diff --git a/spec/javascripts/fixtures/images/red_box.png b/spec/javascripts/fixtures/static/images/red_box.png
index 73b2927da0f..73b2927da0f 100644
--- a/spec/javascripts/fixtures/images/red_box.png
+++ b/spec/javascripts/fixtures/static/images/red_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/javascripts/fixtures/static/issuable_filter.html
new file mode 100644
index 00000000000..06b70fb43f1
--- /dev/null
+++ b/spec/javascripts/fixtures/static/issuable_filter.html
@@ -0,0 +1,9 @@
+<form action="/user/project/issues?scope=all&amp;state=closed" class="js-filter-form">
+<input id="utf8" name="utf8" value="✓">
+<input id="check-all-issues" name="check-all-issues">
+<input id="search" name="search">
+<input id="author_id" name="author_id">
+<input id="assignee_id" name="assignee_id">
+<input id="milestone_title" name="milestone_title">
+<input id="label_name" name="label_name">
+</form>
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/javascripts/fixtures/static/issue_sidebar_label.html
new file mode 100644
index 00000000000..ec8fb30f219
--- /dev/null
+++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html
@@ -0,0 +1,26 @@
+<div class="block labels">
+<div class="sidebar-collapsed-icon js-sidebar-labels-tooltip"></div>
+<div class="title hide-collapsed">
+<a class="edit-link float-right" href="#">
+Edit
+</a>
+</div>
+<div class="selectbox hide-collapsed" style="display: none;">
+<div class="dropdown">
+<button class="dropdown-menu-toggle js-label-select js-multiselect" data-ability-name="issue" data-field-name="issue[label_names][]" data-issue-update="/root/test/issues/2.json" data-labels="/root/test/labels.json" data-project-id="12" data-show-any="true" data-show-no="true" data-toggle="dropdown" type="button">
+<span class="dropdown-toggle-text">
+Label
+</span>
+<i class="fa fa-chevron-down"></i>
+</button>
+<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
+<div class="dropdown-page-one">
+<div class="dropdown-content"></div>
+<div class="dropdown-loading">
+<i class="fa fa-spinner fa-spin"></i>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/javascripts/fixtures/static/line_highlighter.html
new file mode 100644
index 00000000000..897a25d6760
--- /dev/null
+++ b/spec/javascripts/fixtures/static/line_highlighter.html
@@ -0,0 +1,107 @@
+<div class="file-holder">
+<div class="file-content">
+<div class="line-numbers">
+<a data-line-number="1" href="#L1" id="L1">
+<i class="fa fa-link"></i>
+1
+</a>
+<a data-line-number="2" href="#L2" id="L2">
+<i class="fa fa-link"></i>
+2
+</a>
+<a data-line-number="3" href="#L3" id="L3">
+<i class="fa fa-link"></i>
+3
+</a>
+<a data-line-number="4" href="#L4" id="L4">
+<i class="fa fa-link"></i>
+4
+</a>
+<a data-line-number="5" href="#L5" id="L5">
+<i class="fa fa-link"></i>
+5
+</a>
+<a data-line-number="6" href="#L6" id="L6">
+<i class="fa fa-link"></i>
+6
+</a>
+<a data-line-number="7" href="#L7" id="L7">
+<i class="fa fa-link"></i>
+7
+</a>
+<a data-line-number="8" href="#L8" id="L8">
+<i class="fa fa-link"></i>
+8
+</a>
+<a data-line-number="9" href="#L9" id="L9">
+<i class="fa fa-link"></i>
+9
+</a>
+<a data-line-number="10" href="#L10" id="L10">
+<i class="fa fa-link"></i>
+10
+</a>
+<a data-line-number="11" href="#L11" id="L11">
+<i class="fa fa-link"></i>
+11
+</a>
+<a data-line-number="12" href="#L12" id="L12">
+<i class="fa fa-link"></i>
+12
+</a>
+<a data-line-number="13" href="#L13" id="L13">
+<i class="fa fa-link"></i>
+13
+</a>
+<a data-line-number="14" href="#L14" id="L14">
+<i class="fa fa-link"></i>
+14
+</a>
+<a data-line-number="15" href="#L15" id="L15">
+<i class="fa fa-link"></i>
+15
+</a>
+<a data-line-number="16" href="#L16" id="L16">
+<i class="fa fa-link"></i>
+16
+</a>
+<a data-line-number="17" href="#L17" id="L17">
+<i class="fa fa-link"></i>
+17
+</a>
+<a data-line-number="18" href="#L18" id="L18">
+<i class="fa fa-link"></i>
+18
+</a>
+<a data-line-number="19" href="#L19" id="L19">
+<i class="fa fa-link"></i>
+19
+</a>
+<a data-line-number="20" href="#L20" id="L20">
+<i class="fa fa-link"></i>
+20
+</a>
+<a data-line-number="21" href="#L21" id="L21">
+<i class="fa fa-link"></i>
+21
+</a>
+<a data-line-number="22" href="#L22" id="L22">
+<i class="fa fa-link"></i>
+22
+</a>
+<a data-line-number="23" href="#L23" id="L23">
+<i class="fa fa-link"></i>
+23
+</a>
+<a data-line-number="24" href="#L24" id="L24">
+<i class="fa fa-link"></i>
+24
+</a>
+<a data-line-number="25" href="#L25" id="L25">
+<i class="fa fa-link"></i>
+25
+</a>
+</div>
+<pre class="code highlight"><code><span class="line" id="LC1">Line 1</span><span class="line" id="LC2">Line 2</span><span class="line" id="LC3">Line 3</span><span class="line" id="LC4">Line 4</span><span class="line" id="LC5">Line 5</span><span class="line" id="LC6">Line 6</span><span class="line" id="LC7">Line 7</span><span class="line" id="LC8">Line 8</span><span class="line" id="LC9">Line 9</span><span class="line" id="LC10">Line 10</span><span class="line" id="LC11">Line 11</span><span class="line" id="LC12">Line 12</span><span class="line" id="LC13">Line 13</span><span class="line" id="LC14">Line 14</span><span class="line" id="LC15">Line 15</span><span class="line" id="LC16">Line 16</span><span class="line" id="LC17">Line 17</span><span class="line" id="LC18">Line 18</span><span class="line" id="LC19">Line 19</span><span class="line" id="LC20">Line 20</span><span class="line" id="LC21">Line 21</span><span class="line" id="LC22">Line 22</span><span class="line" id="LC23">Line 23</span><span class="line" id="LC24">Line 24</span><span class="line" id="LC25">Line 25</span></code></pre>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/javascripts/fixtures/static/linked_tabs.html
new file mode 100644
index 00000000000..c25463bf1db
--- /dev/null
+++ b/spec/javascripts/fixtures/static/linked_tabs.html
@@ -0,0 +1,20 @@
+<ul class="nav nav-tabs new-session-tabs linked-tabs">
+<li class="nav-item">
+<a class="nav-link" data-action="tab1" data-target="div#tab1" data-toggle="tab" href="foo/bar/1">
+Tab 1
+</a>
+</li>
+<li class="nav-item">
+<a class="nav-link" data-action="tab2" data-target="div#tab2" data-toggle="tab" href="foo/bar/1/context">
+Tab 2
+</a>
+</li>
+</ul>
+<div class="tab-content">
+<div class="tab-pane" id="tab1">
+Tab 1 Content
+</div>
+<div class="tab-pane" id="tab2">
+Tab 2 Content
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/javascripts/fixtures/static/merge_requests_show.html
new file mode 100644
index 00000000000..87e36c9f315
--- /dev/null
+++ b/spec/javascripts/fixtures/static/merge_requests_show.html
@@ -0,0 +1,15 @@
+<a class="btn-close"></a>
+<div class="detail-page-description">
+<div class="description js-task-list-container">
+<div class="md">
+<ul class="task-list">
+<li class="task-list-item">
+<input class="task-list-item-checkbox" type="checkbox">
+Task List Item
+</li>
+</ul>
+<textarea class="js-task-list-field">- [ ] Task List Item</textarea>
+</div>
+</div>
+</div>
+<form action="/foo" class="js-issuable-update"></form>
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/javascripts/fixtures/static/mini_dropdown_graph.html
new file mode 100644
index 00000000000..cd0b8dec3fc
--- /dev/null
+++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html
@@ -0,0 +1,13 @@
+<div class="js-builds-dropdown-tests dropdown dropdown js-mini-pipeline-graph">
+<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
+Dropdown
+</button>
+<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+<li class="js-builds-dropdown-list scrollable-menu">
+<ul></ul>
+</li>
+<li class="js-builds-dropdown-loading hidden">
+<span class="fa fa-spinner"></span>
+</li>
+</ul>
+</div>
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/javascripts/fixtures/static/notebook_viewer.html
new file mode 100644
index 00000000000..4bbb7bf1094
--- /dev/null
+++ b/spec/javascripts/fixtures/static/notebook_viewer.html
@@ -0,0 +1 @@
+<div class="file-content" data-endpoint="/test" id="js-notebook-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/javascripts/fixtures/static/oauth_remember_me.html
new file mode 100644
index 00000000000..9ba1ffc72fe
--- /dev/null
+++ b/spec/javascripts/fixtures/static/oauth_remember_me.html
@@ -0,0 +1,6 @@
+<div id="oauth-container">
+<input id="remember_me" type="checkbox">
+<a class="oauth-login twitter" href="http://example.com/"></a>
+<a class="oauth-login github" href="http://example.com/"></a>
+<a class="oauth-login facebook" href="http://example.com/?redirect_fragment=L1"></a>
+</div>
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/javascripts/fixtures/static/pdf_viewer.html
new file mode 100644
index 00000000000..350d35a262f
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pdf_viewer.html
@@ -0,0 +1 @@
+<div class="file-content" data-endpoint="/test" id="js-pdf-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/javascripts/fixtures/static/pipeline_graph.html
new file mode 100644
index 00000000000..422372bb7d5
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pipeline_graph.html
@@ -0,0 +1,24 @@
+<div class="pipeline-visualization js-pipeline-graph">
+<ul class="stage-column-list">
+<li class="stage-column">
+<div class="stage-name">
+<a href="/">
+Test
+<div class="builds-container">
+<ul>
+<li class="build">
+<div class="curve"></div>
+<a>
+<svg></svg>
+<div class="ci-status-text">
+stop_review
+</div>
+</a>
+</li>
+</ul>
+</div>
+</a>
+</div>
+</li>
+</ul>
+</div>
diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/javascripts/fixtures/static/pipelines.html
new file mode 100644
index 00000000000..42333f94f2f
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pipelines.html
@@ -0,0 +1,3 @@
+<div>
+<div data-can-create-pipeline="true" data-ci-lint-path="foo" data-empty-state-svg-path="foo" data-endpoint="foo" data-error-state-svg-path="foo" data-has-ci="foo" data-help-auto-devops-path="foo" data-help-page-path="foo" data-new-pipeline-path="foo" data-reset-cache-path="foo" id="pipelines-list-vue"></div>
+</div>
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/javascripts/fixtures/static/project_select_combo_button.html
new file mode 100644
index 00000000000..50c826051c0
--- /dev/null
+++ b/spec/javascripts/fixtures/static/project_select_combo_button.html
@@ -0,0 +1,9 @@
+<div class="project-item-select-holder">
+<input class="project-item-select" data-group-id="12345" data-relative-path="issues/new">
+<a class="new-project-item-link" data-label="New issue" data-type="issues" href="">
+<i class="fa fa-spinner spin"></i>
+</a>
+<a class="new-project-item-select-button">
+<i class="fa fa-caret-down"></i>
+</a>
+</div>
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/static/projects.json
index 68a150f602a..68a150f602a 100644
--- a/spec/javascripts/fixtures/projects.json
+++ b/spec/javascripts/fixtures/static/projects.json
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/javascripts/fixtures/static/search_autocomplete.html
new file mode 100644
index 00000000000..29db9020424
--- /dev/null
+++ b/spec/javascripts/fixtures/static/search_autocomplete.html
@@ -0,0 +1,15 @@
+<div class="search search-form">
+<form class="form-inline">
+<div class="search-input-container">
+<div class="search-input-wrap">
+<div class="dropdown">
+<input class="search-input dropdown-menu-toggle" id="search">
+<div class="dropdown-menu dropdown-select">
+<div class="dropdown-content"></div>
+</div>
+</div>
+</div>
+</div>
+<input class="js-search-project-options" type="hidden">
+</form>
+</div>
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/javascripts/fixtures/static/signin_tabs.html
new file mode 100644
index 00000000000..7e66ab9394b
--- /dev/null
+++ b/spec/javascripts/fixtures/static/signin_tabs.html
@@ -0,0 +1,8 @@
+<ul class="nav-links new-session-tabs">
+<li class="active">
+<a href="#ldap">LDAP</a>
+</li>
+<li>
+<a href="#login-pane">Standard</a>
+</li>
+</ul>
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/javascripts/fixtures/static/sketch_viewer.html
new file mode 100644
index 00000000000..e25e554e568
--- /dev/null
+++ b/spec/javascripts/fixtures/static/sketch_viewer.html
@@ -0,0 +1,3 @@
+<div class="file-content" data-endpoint="/test_sketch_file.sketch" id="js-sketch-viewer">
+<div class="js-loading-icon"></div>
+</div>
diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb
deleted file mode 100644
index 4569f16f0ca..00000000000
--- a/spec/javascripts/fixtures/static_fixtures.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-describe ApplicationController, '(Static JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- before(:all) do
- clean_frontend_fixtures('static/')
- end
-
- fixtures_path = File.expand_path(JavaScriptFixturesHelpers::FIXTURE_PATH, Rails.root)
- haml_fixtures = Dir.glob(File.expand_path('**/*.haml', fixtures_path)).map do |file_path|
- file_path.sub(/\A#{fixtures_path}#{File::SEPARATOR}/, '')
- end
-
- haml_fixtures.each do |template_file_name|
- it "static/#{template_file_name.sub(/\.haml\z/, '.raw')}" do |example|
- fixture_file_name = example.description
- rendered = render_template(template_file_name)
- store_frontend_fixture(rendered, fixture_file_name)
- end
- end
-
- private
-
- def render_template(template_file_name)
- fixture_path = JavaScriptFixturesHelpers::FIXTURE_PATH
- controller = ApplicationController.new
- controller.prepend_view_path(fixture_path)
- controller.render_to_string(template: template_file_name, layout: false)
- end
-end
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
index b5f6620873b..d0c8a6eca01 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/javascripts/fixtures/todos.rb
@@ -26,11 +26,10 @@ describe 'Todos (JavaScript fixtures)' do
sign_in(admin)
end
- it 'todos/todos.html.raw' do |example|
+ it 'todos/todos.html' do
get :index
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
@@ -41,7 +40,7 @@ describe 'Todos (JavaScript fixtures)' do
sign_in(admin)
end
- it 'todos/todos.json' do |example|
+ it 'todos/todos.json' do
post :create, params: {
namespace_id: namespace,
project_id: project,
@@ -50,7 +49,6 @@ describe 'Todos (JavaScript fixtures)' do
}, format: 'json'
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index 5cdbadef639..f52832b6efb 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -18,13 +18,12 @@ context 'U2F' do
set_devise_mapping(context: @request)
end
- it 'u2f/authenticate.html.raw' do |example|
+ it 'u2f/authenticate.html' do
allow(controller).to receive(:find_user).and_return(user)
post :create, params: { user: { login: user.username, password: user.password } }
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
@@ -36,11 +35,10 @@ context 'U2F' do
allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
end
- it 'u2f/register.html.raw' do |example|
+ it 'u2f/register.html' do
get :show
expect(response).to be_success
- store_frontend_fixture(response, example.description)
end
end
end
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index 7ef44f29c5b..4772f754937 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -14,6 +14,7 @@ import {
setSidebar,
subItemsMouseLeave,
} from '~/fly_out_nav';
+import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar';
import bp from '~/breakpoints';
describe('Fly out sidebar navigation', () => {
@@ -219,7 +220,7 @@ describe('Fly out sidebar navigation', () => {
it('shows collapsed only sub-items if icon only sidebar', () => {
const subItems = el.querySelector('.sidebar-sub-level-items');
const sidebar = document.createElement('div');
- sidebar.classList.add('sidebar-collapsed-desktop');
+ sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
subItems.classList.add('is-fly-out-only');
setSidebar(sidebar);
@@ -296,7 +297,7 @@ describe('Fly out sidebar navigation', () => {
it('returns true when active & collapsed sidebar', () => {
const sidebar = document.createElement('div');
- sidebar.classList.add('sidebar-collapsed-desktop');
+ sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
el.classList.add('active');
setSidebar(sidebar);
diff --git a/spec/javascripts/frequent_items/components/app_spec.js b/spec/javascripts/frequent_items/components/app_spec.js
index b1cc4d8dc8d..6814f656f5d 100644
--- a/spec/javascripts/frequent_items/components/app_spec.js
+++ b/spec/javascripts/frequent_items/components/app_spec.js
@@ -194,7 +194,7 @@ describe('Frequent Items App Component', () => {
expect(loadingEl).toBeDefined();
expect(loadingEl.classList.contains('prepend-top-20')).toBe(true);
- expect(loadingEl.querySelector('i').getAttribute('aria-label')).toBe('Loading projects');
+ expect(loadingEl.querySelector('span').getAttribute('aria-label')).toBe('Loading projects');
done();
});
});
diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
index 7deed985219..dce8e3be148 100644
--- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
+++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
@@ -1,25 +1,31 @@
import Vue from 'vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/text_helper';
import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here
const createComponent = () => {
const Component = Vue.extend(frequentItemsListItemComponent);
- return mountComponent(Component, {
- itemId: mockProject.id,
- itemName: mockProject.name,
- namespace: mockProject.namespace,
- webUrl: mockProject.webUrl,
- avatarUrl: mockProject.avatarUrl,
+ return shallowMount(Component, {
+ propsData: {
+ itemId: mockProject.id,
+ itemName: mockProject.name,
+ namespace: mockProject.namespace,
+ webUrl: mockProject.webUrl,
+ avatarUrl: mockProject.avatarUrl,
+ },
});
};
describe('FrequentItemsListItemComponent', () => {
+ let wrapper;
let vm;
beforeEach(() => {
- vm = createComponent();
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
});
afterEach(() => {
@@ -29,11 +35,11 @@ describe('FrequentItemsListItemComponent', () => {
describe('computed', () => {
describe('hasAvatar', () => {
it('should return `true` or `false` if whether avatar is present or not', () => {
- vm.avatarUrl = 'path/to/avatar.png';
+ wrapper.setProps({ avatarUrl: 'path/to/avatar.png' });
expect(vm.hasAvatar).toBe(true);
- vm.avatarUrl = null;
+ wrapper.setProps({ avatarUrl: null });
expect(vm.hasAvatar).toBe(false);
});
@@ -41,41 +47,49 @@ describe('FrequentItemsListItemComponent', () => {
describe('highlightedItemName', () => {
it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => {
- vm.matcher = 'lab';
+ wrapper.setProps({ matcher: 'lab' });
- expect(vm.highlightedItemName).toContain('<b>Lab</b>');
+ expect(wrapper.find('.js-frequent-items-item-title').html()).toContain(
+ '<b>L</b><b>a</b><b>b</b>',
+ );
});
it('should return project name as it is if `matcher` is not available', () => {
- vm.matcher = null;
+ wrapper.setProps({ matcher: null });
- expect(vm.highlightedItemName).toBe(mockProject.name);
+ expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe(
+ mockProject.name,
+ );
});
});
describe('truncatedNamespace', () => {
it('should truncate project name from namespace string', () => {
- vm.namespace = 'platform / nokia-3310';
+ wrapper.setProps({ namespace: 'platform / nokia-3310' });
- expect(vm.truncatedNamespace).toBe('platform');
+ expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe('platform');
});
it('should truncate namespace string from the middle if it includes more than two groups in path', () => {
- vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310';
+ wrapper.setProps({
+ namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310',
+ });
- expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset');
+ expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe(
+ 'platform / ... / Mobile Chipset',
+ );
});
});
});
describe('template', () => {
it('should render component element', () => {
- expect(vm.$el.classList.contains('frequent-items-list-item-container')).toBeTruthy();
- expect(vm.$el.querySelectorAll('a').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-avatar-container').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-metadata-container').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-title').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-namespace').length).toBe(1);
+ expect(wrapper.classes()).toContain('frequent-items-list-item-container');
+ expect(wrapper.findAll('a').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-metadata-container').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-title').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-namespace').length).toBe(1);
});
});
});
diff --git a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
index d564292f1ba..ddbbc5c2d29 100644
--- a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
+++ b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
@@ -1,19 +1,22 @@
import Vue from 'vue';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
import eventHub from '~/frequent_items/event_hub';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
const createComponent = (namespace = 'projects') => {
const Component = Vue.extend(searchComponent);
- return mountComponent(Component, { namespace });
+ return shallowMount(Component, { propsData: { namespace } });
};
describe('FrequentItemsSearchInputComponent', () => {
+ let wrapper;
let vm;
beforeEach(() => {
- vm = createComponent();
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
});
afterEach(() => {
@@ -35,7 +38,7 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('mounted', () => {
it('should listen `dropdownOpen` event', done => {
spyOn(eventHub, '$on');
- const vmX = createComponent();
+ const vmX = createComponent().vm;
Vue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith(
@@ -49,7 +52,7 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('beforeDestroy', () => {
it('should unbind event listeners on eventHub', done => {
- const vmX = createComponent();
+ const vmX = createComponent().vm;
spyOn(eventHub, '$off');
vmX.$mount();
@@ -67,12 +70,12 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('template', () => {
it('should render component element', () => {
- const inputEl = vm.$el.querySelector('input.form-control');
-
- expect(vm.$el.classList.contains('search-input-container')).toBeTruthy();
- expect(inputEl).not.toBe(null);
- expect(inputEl.getAttribute('placeholder')).toBe('Search your projects');
- expect(vm.$el.querySelector('.search-icon')).toBeDefined();
+ expect(wrapper.classes()).toContain('search-input-container');
+ expect(wrapper.contains('input.form-control')).toBe(true);
+ expect(wrapper.contains('.search-icon')).toBe(true);
+ expect(wrapper.find('input.form-control').attributes('placeholder')).toBe(
+ 'Search your projects',
+ );
});
});
});
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 85083653db8..8c7820ddb52 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -5,8 +5,8 @@ import GLDropdown from '~/gl_dropdown';
import '~/lib/utils/common_utils';
describe('glDropdown', function describeDropdown() {
- preloadFixtures('static/gl_dropdown.html.raw');
- loadJSONFixtures('projects.json');
+ preloadFixtures('static/gl_dropdown.html');
+ loadJSONFixtures('static/projects.json');
const NON_SELECTABLE_CLASSES =
'.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';
@@ -64,10 +64,10 @@ describe('glDropdown', function describeDropdown() {
}
beforeEach(() => {
- loadFixtures('static/gl_dropdown.html.raw');
+ loadFixtures('static/gl_dropdown.html');
this.dropdownContainerElement = $('.dropdown.inline');
this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
- this.projectsData = getJSONFixture('projects.json');
+ this.projectsData = getJSONFixture('static/projects.json');
});
afterEach(() => {
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index b463c9afbee..294f219d6fe 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -4,10 +4,10 @@ import $ from 'jquery';
import GlFieldErrors from '~/gl_field_errors';
describe('GL Style Field Errors', function() {
- preloadFixtures('static/gl_field_errors.html.raw');
+ preloadFixtures('static/gl_field_errors.html');
beforeEach(function() {
- loadFixtures('static/gl_field_errors.html.raw');
+ loadFixtures('static/gl_field_errors.html');
const $form = $('form.gl-show-field-errors');
this.$form = $form;
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index d832441dc93..31873311e16 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -502,7 +502,7 @@ describe('AppComponent', () => {
vm.isLoading = true;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.loading-animation')).toBeDefined();
- expect(vm.$el.querySelector('i.fa').getAttribute('aria-label')).toBe('Loading groups');
+ expect(vm.$el.querySelector('span').getAttribute('aria-label')).toBe('Loading groups');
done();
});
});
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 2fe34e5a76f..0ddf589f368 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -3,7 +3,7 @@ import initTodoToggle from '~/header';
describe('Header', function() {
const todosPendingCount = '.todos-count';
- const fixtureTemplate = 'issues/open-issue.html.raw';
+ const fixtureTemplate = 'issues/open-issue.html';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index 8933dd5def4..fd06bb1f324 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -5,7 +5,7 @@ export default class FilteredSearchSpecHelper {
static createFilterVisualToken(name, value, isSelected = false) {
const li = document.createElement('li');
- li.classList.add('js-visual-token', 'filtered-search-token');
+ li.classList.add('js-visual-token', 'filtered-search-token', `search-token-${name}`);
li.innerHTML = `
<div class="selectable ${isSelected ? 'selected' : ''}" role="button">
diff --git a/spec/javascripts/helpers/text_helper.js b/spec/javascripts/helpers/text_helper.js
new file mode 100644
index 00000000000..e0fe18e5560
--- /dev/null
+++ b/spec/javascripts/helpers/text_helper.js
@@ -0,0 +1,18 @@
+/**
+ * Replaces line break with an empty space
+ * @param {*} data
+ */
+export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
+
+/**
+ * Removes line breaks, spaces and trims the given text
+ * @param {String} str
+ * @returns {String}
+ */
+export const trimText = str =>
+ str
+ .replace(/\r?\n|\r/g, '')
+ .replace(/\s\s+/g, ' ')
+ .trim();
+
+export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 19e27388eeb..121e99c9783 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -16,4 +16,6 @@ const vNodeContainsText = (vnode, text) =>
* @param {String} text
*/
export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length;
+ Boolean(
+ shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
+ );
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index 3a5d6c8a90b..b903abe63fc 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import store from '~/ide/stores';
+import consts from '~/ide/stores/modules/commit/constants';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from 'spec/ide/helpers';
@@ -7,20 +8,33 @@ import { projectData } from 'spec/ide/mock_data';
describe('IDE commit sidebar actions', () => {
let vm;
-
- beforeEach(done => {
+ const createComponent = ({
+ hasMR = false,
+ commitAction = consts.COMMIT_TO_NEW_BRANCH,
+ mergeRequestsEnabled = true,
+ currentBranchId = 'master',
+ shouldCreateMR = false,
+ } = {}) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
- vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
+ vm.$store.state.commit.commitAction = commitAction;
Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
+ vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled;
+ vm.$store.state.commit.shouldCreateMR = shouldCreateMR;
- vm.$mount();
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
- Vue.nextTick(done);
- });
+ return vm.$mount();
+ };
afterEach(() => {
vm.$destroy();
@@ -28,16 +42,20 @@ describe('IDE commit sidebar actions', () => {
resetStore(vm.$store);
});
- it('renders 3 groups', () => {
- expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3);
+ it('renders 2 groups', () => {
+ createComponent();
+
+ expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
});
it('renders current branch text', () => {
+ createComponent();
+
expect(vm.$el.textContent).toContain('Commit to master branch');
});
it('hides merge request option when project merge requests are disabled', done => {
- vm.$store.state.projects.abcproject.merge_requests_enabled = false;
+ createComponent({ mergeRequestsEnabled: false });
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
@@ -49,9 +67,10 @@ describe('IDE commit sidebar actions', () => {
describe('commitToCurrentBranchText', () => {
it('escapes current branch', () => {
- vm.$store.state.currentBranchId = '<img src="x" />';
+ const injectedSrc = '<img src="x" />';
+ createComponent({ currentBranchId: injectedSrc });
- expect(vm.commitToCurrentBranchText).not.toContain('<img src="x" />');
+ expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
});
});
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
index 9af3c15a4e3..3c7d6192e2c 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js
@@ -3,7 +3,7 @@ import store from '~/ide/stores';
import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file } from '../../helpers';
-import { removeWhitespace } from '../../../helpers/vue_component_helper';
+import { removeWhitespace } from '../../../helpers/text_helper';
describe('Multi-file editor commit sidebar list collapsed', () => {
let vm;
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
new file mode 100644
index 00000000000..7017bfcd6a6
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import consts from '~/ide/stores/modules/commit/constants';
+import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { projectData } from 'spec/ide/mock_data';
+import { resetStore } from 'spec/ide/helpers';
+
+describe('create new MR checkbox', () => {
+ let vm;
+ const createComponent = ({
+ hasMR = false,
+ commitAction = consts.COMMIT_TO_NEW_BRANCH,
+ currentBranchId = 'master',
+ } = {}) => {
+ const Component = Vue.extend(NewMergeRequestOption);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.currentBranchId = currentBranchId;
+ vm.$store.state.currentProjectId = 'abcproject';
+ vm.$store.state.commit.commitAction = commitAction;
+ Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
+
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
+
+ return vm.$mount();
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
+
+ it('is hidden when an MR already exists and committing to current branch', () => {
+ createComponent({
+ hasMR: true,
+ commitAction: consts.COMMIT_TO_CURRENT_BRANCH,
+ currentBranchId: 'feature',
+ });
+
+ expect(vm.$el.textContent).toBe('');
+ });
+
+ it('does not hide checkbox if MR does not exist', () => {
+ createComponent({ hasMR: false });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('does not hide checkbox when creating a new branch', () => {
+ createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
+ createComponent();
+ const el = vm.$el.querySelector('input[type="checkbox"]');
+ spyOn(vm.$store, 'dispatch');
+ el.dispatchEvent(new Event('change'));
+
+ expect(vm.$store.dispatch.calls.allArgs()).toEqual(
+ jasmine.arrayContaining([['commit/toggleShouldCreateMR', jasmine.any(Object)]]),
+ );
+ });
+});
diff --git a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
index ffc2a4c9ddb..db1988be3e1 100644
--- a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
@@ -76,6 +76,7 @@ describe('IDE commit sidebar radio group', () => {
const Component = Vue.extend(radioGroup);
store.state.commit.commitAction = '1';
+ store.state.commit.newBranchName = 'test-123';
vm = createComponentWithStore(Component, store, {
value: '1',
@@ -113,6 +114,12 @@ describe('IDE commit sidebar radio group', () => {
done();
});
});
+
+ it('renders newBranchName if present', () => {
+ const input = vm.$el.querySelector('.form-control');
+
+ expect(input.value).toBe('test-123');
+ });
});
describe('tooltipTitle', () => {
diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js
index 430e8e2baa3..80d6c7fd564 100644
--- a/spec/javascripts/ide/components/error_message_spec.js
+++ b/spec/javascripts/ide/components/error_message_spec.js
@@ -84,7 +84,7 @@ describe('IDE error message component', () => {
expect(vm.isLoading).toBe(true);
- vm.$nextTick(() => {
+ setTimeout(() => {
expect(vm.isLoading).toBe(false);
done();
diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js
index c93a939ad71..d7fed3f0681 100644
--- a/spec/javascripts/ide/components/file_row_extra_spec.js
+++ b/spec/javascripts/ide/components/file_row_extra_spec.js
@@ -20,7 +20,7 @@ describe('IDE extra file row component', () => {
file: {
...file('test'),
},
- mouseOver: false,
+ dropdownOpen: false,
});
spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount);
diff --git a/spec/javascripts/ide/components/ide_review_spec.js b/spec/javascripts/ide/components/ide_review_spec.js
index b9ee22b7c1a..396c5d282d4 100644
--- a/spec/javascripts/ide/components/ide_review_spec.js
+++ b/spec/javascripts/ide/components/ide_review_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import IdeReview from '~/ide/components/ide_review.vue';
import store from '~/ide/stores';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/vue_component_helper';
+import { trimText } from '../../helpers/text_helper';
import { resetStore, file } from '../helpers';
import { projectData } from '../mock_data';
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index dc5790f6562..de4becec1cd 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -5,21 +5,53 @@ import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helpe
import { file, resetStore } from '../helpers';
import { projectData } from '../mock_data';
-describe('ide component', () => {
+function bootstrap(projData) {
+ const Component = Vue.extend(ide);
+
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = Object.assign({}, projData);
+ Vue.set(store.state.trees, 'abcproject/master', {
+ tree: [],
+ loading: false,
+ });
+
+ return createComponentWithStore(Component, store, {
+ emptyStateSvgPath: 'svg',
+ noChangesStateSvgPath: 'svg',
+ committedStateSvgPath: 'svg',
+ });
+}
+
+describe('ide component, empty repo', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(ide);
+ const emptyProjData = Object.assign({}, projectData, { empty_repo: true, branches: {} });
+ vm = bootstrap(emptyProjData);
+ vm.$mount();
+ });
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = Object.assign({}, projectData);
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
- vm = createComponentWithStore(Component, store, {
- emptyStateSvgPath: 'svg',
- noChangesStateSvgPath: 'svg',
- committedStateSvgPath: 'svg',
- }).$mount();
+ it('renders "New file" button in empty repo', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).not.toBeNull();
+ done();
+ });
+ });
+});
+
+describe('ide component, non-empty repo', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = bootstrap(projectData);
+ vm.$mount();
});
afterEach(() => {
@@ -28,17 +60,15 @@ describe('ide component', () => {
resetStore(vm.$store);
});
- it('does not render right when no files open', () => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
- });
+ it('shows error message when set', done => {
+ expect(vm.$el.querySelector('.flash-container')).toBe(null);
- it('renders right panel when files are open', done => {
- vm.$store.state.trees['abcproject/mybranch'] = {
- tree: [file()],
+ vm.$store.state.errorMessage = {
+ text: 'error',
};
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.flash-container')).not.toBe(null);
done();
});
@@ -71,17 +101,25 @@ describe('ide component', () => {
});
});
- it('shows error message when set', done => {
- expect(vm.$el.querySelector('.flash-container')).toBe(null);
-
- vm.$store.state.errorMessage = {
- text: 'error',
- };
+ describe('non-existent branch', () => {
+ it('does not render "New file" button for non-existent branch when repo is not empty', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull();
+ done();
+ });
+ });
+ });
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.flash-container')).not.toBe(null);
+ describe('branch with files', () => {
+ beforeEach(() => {
+ store.state.trees['abcproject/master'].tree = [file()];
+ });
- done();
+ it('does not render "New file" button', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull();
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js
index 4ecbdb8a55e..f63007c7dd2 100644
--- a/spec/javascripts/ide/components/ide_tree_list_spec.js
+++ b/spec/javascripts/ide/components/ide_tree_list_spec.js
@@ -7,25 +7,23 @@ import { projectData } from '../mock_data';
describe('IDE tree list', () => {
const Component = Vue.extend(IdeTreeList);
+ const normalBranchTree = [file('fileName')];
+ const emptyBranchTree = [];
let vm;
- beforeEach(() => {
+ const bootstrapWithTree = (tree = normalBranchTree) => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = Object.assign({}, projectData);
Vue.set(store.state.trees, 'abcproject/master', {
- tree: [file('fileName')],
+ tree,
loading: false,
});
vm = createComponentWithStore(Component, store, {
viewerType: 'edit',
});
-
- spyOn(vm, 'updateViewer').and.callThrough();
-
- vm.$mount();
- });
+ };
afterEach(() => {
vm.$destroy();
@@ -33,22 +31,47 @@ describe('IDE tree list', () => {
resetStore(vm.$store);
});
- it('updates viewer on mount', () => {
- expect(vm.updateViewer).toHaveBeenCalledWith('edit');
- });
+ describe('normal branch', () => {
+ beforeEach(() => {
+ bootstrapWithTree();
+
+ spyOn(vm, 'updateViewer').and.callThrough();
+
+ vm.$mount();
+ });
+
+ it('updates viewer on mount', () => {
+ expect(vm.updateViewer).toHaveBeenCalledWith('edit');
+ });
+
+ it('renders loading indicator', done => {
+ store.state.trees['abcproject/master'].loading = true;
- it('renders loading indicator', done => {
- store.state.trees['abcproject/master'].loading = true;
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
+ expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
- expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
+ done();
+ });
+ });
- done();
+ it('renders list of files', () => {
+ expect(vm.$el.textContent).toContain('fileName');
});
});
- it('renders list of files', () => {
- expect(vm.$el.textContent).toContain('fileName');
+ describe('empty-branch state', () => {
+ beforeEach(() => {
+ bootstrapWithTree(emptyBranchTree);
+
+ spyOn(vm, 'updateViewer').and.callThrough();
+
+ vm.$mount();
+ });
+
+ it('does not load files if the branch is empty', () => {
+ expect(vm.$el.textContent).not.toContain('fileName');
+ expect(vm.$el.textContent).toContain('No files');
+ });
});
});
diff --git a/spec/javascripts/ide/components/nav_dropdown_button_spec.js b/spec/javascripts/ide/components/nav_dropdown_button_spec.js
index 0a58e260280..19b0071567a 100644
--- a/spec/javascripts/ide/components/nav_dropdown_button_spec.js
+++ b/spec/javascripts/ide/components/nav_dropdown_button_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
import store from '~/ide/stores';
-import { trimText } from 'spec/helpers/vue_component_helper';
+import { trimText } from 'spec/helpers/text_helper';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js
index 83e530f0a6a..aaebe88f314 100644
--- a/spec/javascripts/ide/components/new_dropdown/index_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js
@@ -56,11 +56,11 @@ describe('new dropdown component', () => {
});
});
- describe('dropdownOpen', () => {
+ describe('isOpen', () => {
it('scrolls dropdown into view', done => {
spyOn(vm.$refs.dropdownMenu, 'scrollIntoView');
- vm.dropdownOpen = true;
+ vm.isOpen = true;
setTimeout(() => {
expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({
diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
index d94cc1a8faa..0556feae46a 100644
--- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
@@ -18,6 +18,9 @@ describe('new file modal component', () => {
store.state.entryModal = {
type,
path: '',
+ entry: {
+ path: '',
+ },
};
vm = createComponentWithStore(Component, store).$mount();
@@ -74,6 +77,7 @@ describe('new file modal component', () => {
entry: {
name: 'test',
type: 'blob',
+ path: 'test-path',
},
};
@@ -97,7 +101,7 @@ describe('new file modal component', () => {
describe('entryName', () => {
it('returns entries name', () => {
- expect(vm.entryName).toBe('test');
+ expect(vm.entryName).toBe('test-path');
});
it('updated name', () => {
@@ -105,6 +109,67 @@ describe('new file modal component', () => {
expect(vm.entryName).toBe('index.js');
});
+
+ it('removes leading/trailing spaces when found in the new name', () => {
+ vm.entryName = ' index.js ';
+
+ expect(vm.entryName).toBe('index.js');
+ });
+
+ it('does not remove internal spaces in the file name', () => {
+ vm.entryName = ' In Praise of Idleness.txt ';
+
+ expect(vm.entryName).toBe('In Praise of Idleness.txt');
+ });
+ });
+ });
+
+ describe('submitForm', () => {
+ it('throws an error when target entry exists', () => {
+ const store = createStore();
+ store.state.entryModal = {
+ type: 'rename',
+ path: 'test-path/test',
+ entry: {
+ name: 'test',
+ type: 'blob',
+ path: 'test-path/test',
+ },
+ };
+ store.state.entries = {
+ 'test-path/test': {
+ name: 'test',
+ deleted: false,
+ },
+ };
+
+ vm = createComponentWithStore(Component, store).$mount();
+ const flashSpy = spyOnDependency(modal, 'flash');
+ vm.submitForm();
+
+ expect(flashSpy).toHaveBeenCalled();
+ });
+
+ it('calls createTempEntry when target path does not exist', () => {
+ const store = createStore();
+ store.state.entryModal = {
+ type: 'rename',
+ path: 'test-path/test',
+ entry: {
+ name: 'test',
+ type: 'blob',
+ path: 'test-path1/test',
+ },
+ };
+
+ vm = createComponentWithStore(Component, store).$mount();
+ spyOn(vm, 'createTempEntry').and.callFake(() => Promise.resolve());
+ vm.submitForm();
+
+ expect(vm.createTempEntry).toHaveBeenCalledWith({
+ name: 'test-path1',
+ type: 'tree',
+ });
});
});
});
diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
index 878e17ac805..d19af6af2d7 100644
--- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
@@ -78,6 +78,8 @@ describe('new dropdown upload', () => {
type: 'blob',
content: 'plain text',
base64: false,
+ binary: false,
+ rawPath: '',
});
});
@@ -89,6 +91,8 @@ describe('new dropdown upload', () => {
type: 'blob',
content: binaryTarget.result.split('base64,')[1],
base64: true,
+ binary: true,
+ rawPath: binaryTarget.result,
});
});
});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
index 68487733cb9..80829f2358a 100644
--- a/spec/javascripts/ide/components/pipelines/list_spec.js
+++ b/spec/javascripts/ide/components/pipelines/list_spec.js
@@ -11,6 +11,8 @@ describe('IDE pipelines list', () => {
let vm;
let mock;
+ const findLoadingState = () => vm.$el.querySelector('.loading-container');
+
beforeEach(done => {
const store = createStore();
@@ -95,7 +97,7 @@ describe('IDE pipelines list', () => {
describe('empty state', () => {
it('renders pipelines empty state', done => {
- vm.$store.state.pipelines.latestPipeline = false;
+ vm.$store.state.pipelines.latestPipeline = null;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.empty-state')).not.toBe(null);
@@ -106,15 +108,30 @@ describe('IDE pipelines list', () => {
});
describe('loading state', () => {
- it('renders loading state when there is no latest pipeline', done => {
- vm.$store.state.pipelines.latestPipeline = null;
+ beforeEach(() => {
vm.$store.state.pipelines.isLoadingPipeline = true;
+ });
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+ it('does not render when pipeline has loaded before', done => {
+ vm.$store.state.pipelines.hasLoadedPipeline = true;
- done();
- });
+ vm.$nextTick()
+ .then(() => {
+ expect(findLoadingState()).toBe(null);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders loading state when there is no latest pipeline', done => {
+ vm.$store.state.pipelines.hasLoadedPipeline = false;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(findLoadingState()).not.toBe(null);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/ide/lib/common/disposable_spec.js b/spec/javascripts/ide/lib/common/disposable_spec.js
deleted file mode 100644
index af12ca15369..00000000000
--- a/spec/javascripts/ide/lib/common/disposable_spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import Disposable from '~/ide/lib/common/disposable';
-
-describe('Multi-file editor library disposable class', () => {
- let instance;
- let disposableClass;
-
- beforeEach(() => {
- instance = new Disposable();
-
- disposableClass = {
- dispose: jasmine.createSpy('dispose'),
- };
- });
-
- afterEach(() => {
- instance.dispose();
- });
-
- describe('add', () => {
- it('adds disposable classes', () => {
- instance.add(disposableClass);
-
- expect(instance.disposers.size).toBe(1);
- });
- });
-
- describe('dispose', () => {
- beforeEach(() => {
- instance.add(disposableClass);
- });
-
- it('calls dispose on all cached disposers', () => {
- instance.dispose();
-
- expect(disposableClass.dispose).toHaveBeenCalled();
- });
-
- it('clears cached disposers', () => {
- instance.dispose();
-
- expect(instance.disposers.size).toBe(0);
- });
- });
-});
diff --git a/spec/javascripts/ide/lib/diff/diff_spec.js b/spec/javascripts/ide/lib/diff/diff_spec.js
deleted file mode 100644
index 57f3ac3d365..00000000000
--- a/spec/javascripts/ide/lib/diff/diff_spec.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import { computeDiff } from '~/ide/lib/diff/diff';
-
-describe('Multi-file editor library diff calculator', () => {
- describe('computeDiff', () => {
- it('returns empty array if no changes', () => {
- const diff = computeDiff('123', '123');
-
- expect(diff).toEqual([]);
- });
-
- describe('modified', () => {
- it('', () => {
- const diff = computeDiff('123', '1234')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeUndefined();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeUndefined();
- expect(diff.lineNumber).toBe(2);
- });
- });
-
- describe('added', () => {
- it('', () => {
- const diff = computeDiff('123', '123\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeUndefined();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeUndefined();
- expect(diff.lineNumber).toBe(3);
- });
- });
-
- describe('removed', () => {
- it('', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.added).toBeUndefined();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeTruthy();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n123')[0];
-
- expect(diff.added).toBeUndefined();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeTruthy();
- expect(diff.lineNumber).toBe(2);
- });
- });
-
- it('includes line number of change', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.lineNumber).toBe(1);
- });
-
- it('includes end line number of change', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.endLineNumber).toBe(1);
- });
- });
-});
diff --git a/spec/javascripts/ide/lib/editor_options_spec.js b/spec/javascripts/ide/lib/editor_options_spec.js
deleted file mode 100644
index d149a883166..00000000000
--- a/spec/javascripts/ide/lib/editor_options_spec.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import editorOptions from '~/ide/lib/editor_options';
-
-describe('Multi-file editor library editor options', () => {
- it('returns an array', () => {
- expect(editorOptions).toEqual(jasmine.any(Array));
- });
-
- it('contains readOnly option', () => {
- expect(editorOptions[0].readOnly).toBeDefined();
- });
-});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 4fe826943b2..570a396c5e3 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -16,6 +16,7 @@ export const projectData = {
},
mergeRequests: {},
merge_requests_enabled: true,
+ default_branch: 'master',
};
export const pipelines = [
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 7ddc734ff56..dd2313dc800 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -10,11 +10,19 @@ import eventHub from '~/ide/eventhub';
import { file, resetStore } from '../../helpers';
import testAction from '../../../helpers/vuex_action_helper';
+const RELATIVE_URL_ROOT = '/gitlab';
+
describe('IDE store file actions', () => {
let mock;
+ let originalGon;
beforeEach(() => {
mock = new MockAdapter(axios);
+ originalGon = window.gon;
+ window.gon = {
+ ...window.gon,
+ relative_url_root: RELATIVE_URL_ROOT,
+ };
spyOn(router, 'push');
});
@@ -22,6 +30,7 @@ describe('IDE store file actions', () => {
afterEach(() => {
mock.restore();
resetStore(store);
+ window.gon = originalGon;
});
describe('closeFile', () => {
@@ -121,68 +130,48 @@ describe('IDE store file actions', () => {
store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
});
- it('calls scrollToTab', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).toHaveBeenCalled();
+ it('calls scrollToTab', () => {
+ const dispatch = jasmine.createSpy();
- done();
- })
- .catch(done.fail);
- });
-
- it('sets the file active', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(localFile.active).toBeTruthy();
+ actions.setFileActive(
+ { commit() {}, state: store.state, getters: store.getters, dispatch },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(dispatch).toHaveBeenCalledWith('scrollToTab');
});
- it('returns early if file is already active', done => {
- localFile.active = true;
+ it('commits SET_FILE_ACTIVE', () => {
+ const commit = jasmine.createSpy();
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).not.toHaveBeenCalled();
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: true,
+ });
});
- it('sets current active file to not active', done => {
+ it('sets current active file to not active', () => {
const f = file('newActive');
store.state.entries[f.path] = f;
localFile.active = true;
store.state.openFiles.push(localFile);
- store
- .dispatch('setFileActive', f.path)
- .then(() => {
- expect(localFile.active).toBeFalsy();
+ const commit = jasmine.createSpy();
- done();
- })
- .catch(done.fail);
- });
-
- it('resets location.hash for line highlighting', done => {
- window.location.hash = 'test';
-
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(window.location.hash).not.toBe('test');
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ f.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: false,
+ });
});
});
@@ -193,13 +182,13 @@ describe('IDE store file actions', () => {
spyOn(service, 'getFileData').and.callThrough();
localFile = file(`newCreate-${Math.random()}`);
- localFile.url = `${gl.TEST_HOST}/getFileDataURL`;
+ localFile.url = `project/getFileDataURL`;
store.state.entries[localFile.path] = localFile;
});
describe('success', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/getFileDataURL`).replyOnce(
+ mock.onGet(`${RELATIVE_URL_ROOT}/project/getFileDataURL`).replyOnce(
200,
{
blame_path: 'blame_path',
@@ -220,7 +209,9 @@ describe('IDE store file actions', () => {
store
.dispatch('getFileData', { path: localFile.path })
.then(() => {
- expect(service.getFileData).toHaveBeenCalledWith(`${gl.TEST_HOST}/getFileDataURL`);
+ expect(service.getFileData).toHaveBeenCalledWith(
+ `${RELATIVE_URL_ROOT}/project/getFileDataURL`,
+ );
done();
})
@@ -286,7 +277,7 @@ describe('IDE store file actions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/getFileDataURL`).networkError();
+ mock.onGet(`project/getFileDataURL`).networkError();
});
it('dispatches error action', done => {
@@ -728,4 +719,20 @@ describe('IDE store file actions', () => {
.catch(done.fail);
});
});
+
+ describe('triggerFilesChange', () => {
+ beforeEach(() => {
+ spyOn(eventHub, '$emit');
+ });
+
+ it('emits event that files have changed', done => {
+ store
+ .dispatch('triggerFilesChange')
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
index 9bfc7c397b8..4dd0c1150eb 100644
--- a/spec/javascripts/ide/stores/actions/merge_request_spec.js
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -11,13 +11,17 @@ import service from '~/ide/services';
import { activityBarViews } from '~/ide/constants';
import { resetStore } from '../../helpers';
+const TEST_PROJECT = 'abcproject';
+const TEST_PROJECT_ID = 17;
+
describe('IDE store merge request actions', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- store.state.projects.abcproject = {
+ store.state.projects[TEST_PROJECT] = {
+ id: TEST_PROJECT_ID,
mergeRequests: {},
};
});
@@ -27,6 +31,98 @@ describe('IDE store merge request actions', () => {
resetStore(store);
});
+ describe('getMergeRequestsForBranch', () => {
+ describe('success', () => {
+ const mrData = { iid: 2, source_branch: 'bar' };
+ const mockData = [mrData];
+
+ describe('base case', () => {
+ beforeEach(() => {
+ spyOn(service, 'getProjectMergeRequests').and.callThrough();
+ mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, mockData);
+ });
+
+ it('calls getProjectMergeRequests service method', done => {
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
+ .then(() => {
+ expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, {
+ source_branch: 'bar',
+ source_project_id: TEST_PROJECT_ID,
+ order_by: 'created_at',
+ per_page: 1,
+ });
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets the "Merge Request" Object', done => {
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
+ .then(() => {
+ expect(store.state.projects.abcproject.mergeRequests).toEqual({
+ '2': jasmine.objectContaining(mrData),
+ });
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets "Current Merge Request" object to the most recent MR', done => {
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
+ .then(() => {
+ expect(store.state.currentMergeRequestId).toEqual('2');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('no merge requests for branch available case', () => {
+ beforeEach(() => {
+ spyOn(service, 'getProjectMergeRequests').and.callThrough();
+ mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, []);
+ });
+
+ it('does not fail if there are no merge requests for current branch', done => {
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'foo' })
+ .then(() => {
+ expect(store.state.projects[TEST_PROJECT].mergeRequests).toEqual({});
+ expect(store.state.currentMergeRequestId).toEqual('');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).networkError();
+ });
+
+ it('flashes message, if error', done => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
+ .then(() => {
+ fail('Expected getMergeRequestsForBranch to throw an error');
+ })
+ .catch(() => {
+ expect(flashSpy).toHaveBeenCalled();
+ expect(flashSpy.calls.argsFor(0)[0]).toEqual('Error fetching merge requests for bar');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
describe('getMergeRequestData', () => {
describe('success', () => {
beforeEach(() => {
@@ -39,9 +135,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestData service method', done => {
store
- .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1, {
+ expect(service.getProjectMergeRequestData).toHaveBeenCalledWith(TEST_PROJECT, 1, {
render_html: true,
});
@@ -52,10 +148,12 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Object', done => {
store
- .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest');
expect(store.state.currentMergeRequestId).toBe(1);
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].title).toBe(
+ 'mergerequest',
+ );
done();
})
@@ -77,7 +175,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -86,7 +184,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -100,7 +198,7 @@ describe('IDE store merge request actions', () => {
describe('getMergeRequestChanges', () => {
beforeEach(() => {
- store.state.projects.abcproject.mergeRequests['1'] = { changes: [] };
+ store.state.projects[TEST_PROJECT].mergeRequests['1'] = { changes: [] };
});
describe('success', () => {
@@ -114,9 +212,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestChanges service method', done => {
store
- .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1);
+ expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith(TEST_PROJECT, 1);
done();
})
@@ -125,9 +223,9 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Changes Object', done => {
store
- .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe(
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].changes.title).toBe(
'mergerequest',
);
done();
@@ -150,7 +248,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -159,7 +257,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -173,7 +271,7 @@ describe('IDE store merge request actions', () => {
describe('getMergeRequestVersions', () => {
beforeEach(() => {
- store.state.projects.abcproject.mergeRequests['1'] = { versions: [] };
+ store.state.projects[TEST_PROJECT].mergeRequests['1'] = { versions: [] };
});
describe('success', () => {
@@ -186,9 +284,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestVersions service method', done => {
store
- .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1);
+ expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith(TEST_PROJECT, 1);
done();
})
@@ -197,9 +295,9 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Versions Object', done => {
store
- .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1);
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].versions.length).toBe(1);
done();
})
.catch(done.fail);
@@ -220,7 +318,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -229,7 +327,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -243,7 +341,7 @@ describe('IDE store merge request actions', () => {
describe('openMergeRequest', () => {
const mr = {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
targetProjectId: 'defproject',
mergeRequestId: 2,
};
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 7d8c9edd965..8ecb6129c63 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -4,7 +4,7 @@ import {
refreshLastCommitData,
showBranchNotFoundError,
createNewBranchFromDefault,
- getBranchData,
+ showEmptyState,
openBranch,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
@@ -196,39 +196,44 @@ describe('IDE store project actions', () => {
});
});
- describe('getBranchData', () => {
- describe('error', () => {
- it('dispatches branch not found action when response is 404', done => {
- const dispatch = jasmine.createSpy('dispatchSpy');
-
- mock.onGet(/(.*)/).replyOnce(404);
-
- getBranchData(
+ describe('showEmptyState', () => {
+ it('commits proper mutations when supplied error is 404', done => {
+ testAction(
+ showEmptyState,
+ {
+ err: {
+ response: {
+ status: 404,
+ },
+ },
+ projectId: 'abc/def',
+ branchId: 'master',
+ },
+ store.state,
+ [
{
- commit() {},
- dispatch,
- state: store.state,
+ type: 'CREATE_TREE',
+ payload: {
+ treePath: 'abc/def/master',
+ },
},
{
- projectId: 'abc/def',
- branchId: 'master-testing',
+ type: 'TOGGLE_LOADING',
+ payload: {
+ entry: store.state.trees['abc/def/master'],
+ forceValue: false,
+ },
},
- )
- .then(done.fail)
- .catch(() => {
- expect(dispatch.calls.argsFor(0)).toEqual([
- 'showBranchNotFoundError',
- 'master-testing',
- ]);
- done();
- });
- });
+ ],
+ [],
+ done,
+ );
});
});
describe('openBranch', () => {
const branch = {
- projectId: 'feature/lorem-ipsum',
+ projectId: 'abc/def',
branchId: '123-lorem',
};
@@ -238,45 +243,113 @@ describe('IDE store project actions', () => {
'foo/bar-pending': { pending: true },
'foo/bar': { pending: false },
};
-
- spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
});
- it('dispatches branch actions', done => {
- openBranch(store, branch)
- .then(() => {
- expect(store.dispatch.calls.allArgs()).toEqual([
- ['setCurrentBranchId', branch.branchId],
- ['getBranchData', branch],
- ['getFiles', branch],
- ]);
- })
- .then(done)
- .catch(done.fail);
+ describe('empty repo', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+
+ store.state.currentProjectId = 'abc/def';
+ store.state.projects['abc/def'] = {
+ empty_repo: true,
+ };
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ it('dispatches showEmptyState action right away', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['showEmptyState', branch],
+ ]);
+ done();
+ })
+ .catch(done.fail);
+ });
});
- it('handles tree entry action, if basePath is given', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar/' })
- .then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- store.state.entries['foo/bar'],
- );
- })
- .then(done)
- .catch(done.fail);
+ describe('existing branch', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+ });
+
+ it('dispatches branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['getMergeRequestsForBranch', branch],
+ ['getFiles', branch],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('handles tree entry action, if basePath is given', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar/' })
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ store.state.entries['foo/bar'],
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not handle tree entry action, if entry is pending', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ jasmine.anything(),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('creates a new file supplied via URL if the file does not exist yet', done => {
+ openBranch(store, { ...branch, basePath: 'not-existent.md' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ jasmine.anything(),
+ );
+
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: 'not-existent.md',
+ type: 'blob',
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
- it('does not handle tree entry action, if entry is pending', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
- .then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- jasmine.anything(),
- );
- })
- .then(done)
- .catch(done.fail);
+ describe('non-existent branch', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.reject());
+ });
+
+ it('dispatches correct branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['showBranchNotFoundError', branch.branchId],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index bd41e87bf0e..674ecdc6764 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'spec/helpers/vuex_action_helper';
-import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree';
+import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree';
import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
@@ -20,6 +20,7 @@ describe('Multi-file store tree actions', () => {
};
beforeEach(() => {
+ jasmine.clock().install();
spyOn(router, 'push');
mock = new MockAdapter(axios);
@@ -37,6 +38,7 @@ describe('Multi-file store tree actions', () => {
});
afterEach(() => {
+ jasmine.clock().uninstall();
mock.restore();
resetStore(store);
});
@@ -70,6 +72,11 @@ describe('Multi-file store tree actions', () => {
store
.dispatch('getFiles', basicCallParameters)
.then(() => {
+ // The populating of the tree is deferred for performance reasons.
+ // See this merge request for details: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25700
+ jasmine.clock().tick(1);
+ })
+ .then(() => {
projectTree = store.state.trees['abcproject/master'];
expect(projectTree.tree.length).toBe(2);
@@ -86,38 +93,6 @@ describe('Multi-file store tree actions', () => {
});
describe('error', () => {
- it('dispatches branch not found actions when response is 404', done => {
- const dispatch = jasmine.createSpy('dispatchSpy');
-
- store.state.projects = {
- 'abc/def': {
- web_url: `${gl.TEST_HOST}/files`,
- },
- };
-
- mock.onGet(/(.*)/).replyOnce(404);
-
- getFiles(
- {
- commit() {},
- dispatch,
- state: store.state,
- },
- {
- projectId: 'abc/def',
- branchId: 'master-testing',
- },
- )
- .then(done.fail)
- .catch(() => {
- expect(dispatch.calls.argsFor(0)).toEqual([
- 'showBranchNotFoundError',
- 'master-testing',
- ]);
- done();
- });
- });
-
it('dispatches error action', done => {
const dispatch = jasmine.createSpy('dispatchSpy');
@@ -199,4 +174,35 @@ describe('Multi-file store tree actions', () => {
);
});
});
+
+ describe('setDirectoryData', () => {
+ it('sets tree correctly if there are no opened files yet', done => {
+ const treeFile = file({ name: 'README.md' });
+ store.state.trees['abcproject/master'] = {};
+
+ testAction(
+ setDirectoryData,
+ { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] },
+ store.state,
+ [
+ {
+ type: types.SET_DIRECTORY_DATA,
+ payload: {
+ treePath: 'abcproject/master',
+ data: [treeFile],
+ },
+ },
+ {
+ type: types.TOGGLE_LOADING,
+ payload: {
+ entry: {},
+ forceValue: false,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index df291ade3f7..37354283cab 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -9,12 +9,15 @@ import actions, {
setErrorMessage,
deleteEntry,
renameEntry,
+ getBranchData,
} from '~/ide/stores/actions';
+import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router';
import { resetStore, file } from '../helpers';
import testAction from '../../helpers/vuex_action_helper';
+import MockAdapter from 'axios-mock-adapter';
describe('Multi-file store actions', () => {
beforeEach(() => {
@@ -485,7 +488,7 @@ describe('Multi-file store actions', () => {
'path',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }],
- [{ type: 'burstUnusedSeal' }],
+ [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -499,15 +502,15 @@ describe('Multi-file store actions', () => {
testAction(
renameEntry,
- { path: 'test', name: 'new-name' },
+ { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
store.state,
[
{
type: types.RENAME_ENTRY,
- payload: { path: 'test', name: 'new-name', entryPath: null },
+ payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
},
],
- [{ type: 'deleteEntry', payload: 'test' }],
+ [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -527,21 +530,99 @@ describe('Multi-file store actions', () => {
testAction(
renameEntry,
- { path: 'test', name: 'new-name' },
+ { path: 'test', name: 'new-name', parentPath: 'parent-path' },
store.state,
[
{
type: types.RENAME_ENTRY,
- payload: { path: 'test', name: 'new-name', entryPath: null },
+ payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
},
],
[
- { type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-1' } },
- { type: 'renameEntry', payload: { path: 'test', name: 'new-name', entryPath: 'tree-2' } },
+ {
+ type: 'renameEntry',
+ payload: {
+ path: 'test',
+ name: 'new-name',
+ entryPath: 'tree-1',
+ parentPath: 'parent-path/new-name',
+ },
+ },
+ {
+ type: 'renameEntry',
+ payload: {
+ path: 'test',
+ name: 'new-name',
+ entryPath: 'tree-2',
+ parentPath: 'parent-path/new-name',
+ },
+ },
{ type: 'deleteEntry', payload: 'test' },
+ { type: 'triggerFilesChange' },
],
done,
);
});
});
+
+ describe('getBranchData', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('error', () => {
+ let dispatch;
+ const callParams = [
+ {
+ commit() {},
+ state: store.state,
+ },
+ {
+ projectId: 'abc/def',
+ branchId: 'master-testing',
+ },
+ ];
+
+ beforeEach(() => {
+ dispatch = jasmine.createSpy('dispatchSpy');
+ document.body.innerHTML += '<div class="flash-container"></div>';
+ });
+
+ afterEach(() => {
+ document.querySelector('.flash-container').remove();
+ });
+
+ it('passes the error further unchanged without dispatching any action when response is 404', done => {
+ mock.onGet(/(.*)/).replyOnce(404);
+
+ getBranchData(...callParams)
+ .then(done.fail)
+ .catch(e => {
+ expect(dispatch.calls.count()).toEqual(0);
+ expect(e.response.status).toEqual(404);
+ expect(document.querySelector('.flash-alert')).toBeNull();
+ done();
+ });
+ });
+
+ it('does not pass the error further and flashes an alert if error is not 404', done => {
+ mock.onGet(/(.*)/).replyOnce(418);
+
+ getBranchData(...callParams)
+ .then(done.fail)
+ .catch(e => {
+ expect(dispatch.calls.count()).toEqual(0);
+ expect(e.response).toBeUndefined();
+ expect(document.querySelector('.flash-alert')).not.toBeNull();
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index 9c135661997..735bbd47f55 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -180,6 +180,38 @@ describe('IDE store getters', () => {
});
});
+ describe('isOnDefaultBranch', () => {
+ it('returns false when no project exists', () => {
+ const localGetters = {
+ currentProject: undefined,
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+
+ it("returns true when project's default branch matches current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'master',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy();
+ });
+
+ it("returns false when project's default branch doesn't match current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'feature',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+ });
+
describe('packageJson', () => {
it('returns package.json entry', () => {
localState.entries['package.json'] = { name: 'package.json' };
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 06b8b452319..5f7272311c8 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -1,9 +1,13 @@
-import actions from '~/ide/stores/actions';
+import rootActions from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
-import * as consts from '~/ide/stores/modules/commit/constants';
+import consts from '~/ide/stores/modules/commit/constants';
+import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
+import * as actions from '~/ide/stores/modules/commit/actions';
+import testAction from '../../../../helpers/vuex_action_helper';
+import { commitActionTypes } from '~/ide/constants';
import { resetStore, file } from 'spec/ide/helpers';
describe('IDE commit module actions', () => {
@@ -224,7 +228,7 @@ describe('IDE commit module actions', () => {
let visitUrl;
beforeEach(() => {
- visitUrl = spyOnDependency(actions, 'visitUrl');
+ visitUrl = spyOnDependency(rootActions, 'visitUrl');
document.body.innerHTML += '<div class="flash-container"></div>';
@@ -271,6 +275,7 @@ describe('IDE commit module actions', () => {
short_id: '123',
message: 'test message',
committed_date: 'date',
+ parent_ids: '321',
stats: {
additions: '1',
deletions: '2',
@@ -294,7 +299,7 @@ describe('IDE commit module actions', () => {
commit_message: 'testing 123',
actions: [
{
- action: 'update',
+ action: commitActionTypes.update,
file_path: jasmine.anything(),
content: undefined,
encoding: jasmine.anything(),
@@ -321,7 +326,7 @@ describe('IDE commit module actions', () => {
commit_message: 'testing 123',
actions: [
{
- action: 'update',
+ action: commitActionTypes.update,
file_path: jasmine.anything(),
content: undefined,
encoding: jasmine.anything(),
@@ -389,14 +394,15 @@ describe('IDE commit module actions', () => {
it('redirects to new merge request page', done => {
spyOn(eventHub, '$on');
- store.state.commit.commitAction = '3';
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.shouldCreateMR = true;
store
.dispatch('commit/commitChanges')
.then(() => {
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
- store.getters['commit/newBranchName']
+ store.getters['commit/placeholderBranchName']
}&merge_request[target_branch]=master`,
);
@@ -405,6 +411,21 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
+ it('does not redirect to new merge request page when shouldCreateMR is not checked', done => {
+ spyOn(eventHub, '$on');
+
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.shouldCreateMR = false;
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(visitUrl).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
it('resets changed files before redirecting', done => {
spyOn(eventHub, '$on');
@@ -446,5 +467,213 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
});
+
+ describe('first commit of a branch', () => {
+ const COMMIT_RESPONSE = {
+ id: '123456',
+ short_id: '123',
+ message: 'test message',
+ committed_date: 'date',
+ parent_ids: [],
+ stats: {
+ additions: '1',
+ deletions: '2',
+ },
+ };
+
+ it('commits TOGGLE_EMPTY_STATE mutation on empty repo', done => {
+ spyOn(service, 'commit').and.returnValue(
+ Promise.resolve({
+ data: COMMIT_RESPONSE,
+ }),
+ );
+
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(store.commit.calls.allArgs()).toEqual(
+ jasmine.arrayContaining([
+ ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)],
+ ]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not commmit TOGGLE_EMPTY_STATE mutation on existing project', done => {
+ COMMIT_RESPONSE.parent_ids.push('1234');
+ spyOn(service, 'commit').and.returnValue(
+ Promise.resolve({
+ data: COMMIT_RESPONSE,
+ }),
+ );
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(store.commit.calls.allArgs()).not.toEqual(
+ jasmine.arrayContaining([
+ ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)],
+ ]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('toggleShouldCreateMR', () => {
+ it('commits both toggle and interacting with MR checkbox actions', done => {
+ testAction(
+ actions.toggleShouldCreateMR,
+ {},
+ store.state,
+ [
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR },
+ { type: mutationTypes.INTERACT_WITH_NEW_MR },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setShouldCreateMR', () => {
+ beforeEach(() => {
+ store.state.projects = {
+ project: {
+ default_branch: 'master',
+ branches: {
+ master: {
+ name: 'master',
+ },
+ feature: {
+ name: 'feature',
+ },
+ },
+ },
+ };
+
+ store.state.currentProjectId = 'project';
+ });
+
+ it('sets to false when the current branch already has an MR', done => {
+ store.state.commit.currentMergeRequestId = 1;
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.currentBranchId = 'feature';
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to false when current branch is the default branch and user has not interacted', done => {
+ store.state.commit.interactedWithNewMR = false;
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to true when "create new branch" is selected and user has not interacted', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = false;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to new branch', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to branch without MR', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.commit.currentMergeRequestId = null;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('still changes to false if hiding the checkbox', done => {
+ store.state.currentBranchId = 'feature';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change to false when on master and user has interacted even if MR exists', done => {
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 3f4bf407a1f..6e71a790deb 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -1,5 +1,5 @@
import commitState from '~/ide/stores/modules/commit/state';
-import * as consts from '~/ide/stores/modules/commit/constants';
+import consts from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
describe('IDE commit module getters', () => {
@@ -29,11 +29,11 @@ describe('IDE commit module getters', () => {
});
});
- describe('newBranchName', () => {
+ describe('placeholderBranchName', () => {
it('includes username, currentBranchId, patch & random number', () => {
gon.current_username = 'username';
- const branch = getters.newBranchName(state, null, {
+ const branch = getters.placeholderBranchName(state, null, {
currentBranchId: 'testing',
});
@@ -46,7 +46,7 @@ describe('IDE commit module getters', () => {
currentBranchId: 'master',
};
const localGetters = {
- newBranchName: 'newBranchName',
+ placeholderBranchName: 'placeholder-branch-name',
};
beforeEach(() => {
@@ -59,25 +59,28 @@ describe('IDE commit module getters', () => {
expect(getters.branchName(state, null, rootState)).toBe('master');
});
- ['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach(type => {
- describe(type, () => {
- beforeEach(() => {
- Object.assign(state, {
- commitAction: consts[type],
- });
+ describe('COMMIT_TO_NEW_BRANCH', () => {
+ beforeEach(() => {
+ Object.assign(state, {
+ commitAction: consts.COMMIT_TO_NEW_BRANCH,
});
+ });
- it('uses newBranchName when not empty', () => {
- expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName');
+ it('uses newBranchName when not empty', () => {
+ const newBranchName = 'nonempty-branch-name';
+ Object.assign(state, {
+ newBranchName,
});
- it('uses getters newBranchName when state newBranchName is empty', () => {
- Object.assign(state, {
- newBranchName: '',
- });
+ expect(getters.branchName(state, localGetters, rootState)).toBe(newBranchName);
+ });
- expect(getters.branchName(state, localGetters, rootState)).toBe('newBranchName');
+ it('uses placeholderBranchName when state newBranchName is empty', () => {
+ Object.assign(state, {
+ newBranchName: '',
});
+
+ expect(getters.branchName(state, localGetters, rootState)).toBe('placeholder-branch-name');
});
});
});
diff --git a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js b/spec/javascripts/ide/stores/modules/commit/mutations_spec.js
deleted file mode 100644
index 5de7a281d34..00000000000
--- a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import commitState from '~/ide/stores/modules/commit/state';
-import mutations from '~/ide/stores/modules/commit/mutations';
-
-describe('IDE commit module mutations', () => {
- let state;
-
- beforeEach(() => {
- state = commitState();
- });
-
- describe('UPDATE_COMMIT_MESSAGE', () => {
- it('updates commitMessage', () => {
- mutations.UPDATE_COMMIT_MESSAGE(state, 'testing');
-
- expect(state.commitMessage).toBe('testing');
- });
- });
-
- describe('UPDATE_COMMIT_ACTION', () => {
- it('updates commitAction', () => {
- mutations.UPDATE_COMMIT_ACTION(state, 'testing');
-
- expect(state.commitAction).toBe('testing');
- });
- });
-
- describe('UPDATE_NEW_BRANCH_NAME', () => {
- it('updates newBranchName', () => {
- mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing');
-
- expect(state.newBranchName).toBe('testing');
- });
- });
-
- describe('UPDATE_LOADING', () => {
- it('updates submitCommitLoading', () => {
- mutations.UPDATE_LOADING(state, true);
-
- expect(state.submitCommitLoading).toBeTruthy();
- });
- });
-});
diff --git a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
index 734233100ab..548962c7a92 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
@@ -69,18 +69,16 @@ describe('IDE file templates actions', () => {
describe('fetchTemplateTypes', () => {
describe('success', () => {
- let nextPage;
+ const pages = [[{ name: 'MIT' }], [{ name: 'Apache' }], [{ name: 'CC' }]];
beforeEach(() => {
- mock.onGet(/api\/(.*)\/templates\/licenses/).replyOnce(() => [
- 200,
- [
- {
- name: 'MIT',
- },
- ],
- { 'X-NEXT-PAGE': nextPage },
- ]);
+ mock.onGet(/api\/(.*)\/templates\/licenses/).reply(({ params }) => {
+ const pageNum = params.page;
+ const page = pages[pageNum - 1];
+ const hasNextPage = pageNum < pages.length;
+
+ return [200, page, hasNextPage ? { 'X-NEXT-PAGE': pageNum + 1 } : {}];
+ });
});
it('rejects if selectedTemplateType is empty', done => {
@@ -112,43 +110,15 @@ describe('IDE file templates actions', () => {
},
{
type: 'receiveTemplateTypesSuccess',
- payload: [
- {
- name: 'MIT',
- },
- ],
- },
- ],
- done,
- );
- });
-
- it('dispatches actions for next page', done => {
- nextPage = '2';
- state.selectedTemplateType = {
- key: 'licenses',
- };
-
- testAction(
- actions.fetchTemplateTypes,
- null,
- state,
- [],
- [
- {
- type: 'requestTemplateTypes',
+ payload: pages[0],
},
{
type: 'receiveTemplateTypesSuccess',
- payload: [
- {
- name: 'MIT',
- },
- ],
+ payload: pages[0].concat(pages[1]),
},
{
- type: 'fetchTemplateTypes',
- payload: 2,
+ type: 'receiveTemplateTypesSuccess',
+ payload: pages[0].concat(pages[1]).concat(pages[2]),
},
],
done,
diff --git a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
deleted file mode 100644
index 8e0e3ae99a1..00000000000
--- a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import createState from '~/ide/stores/modules/file_templates/state';
-import * as types from '~/ide/stores/modules/file_templates/mutation_types';
-import mutations from '~/ide/stores/modules/file_templates/mutations';
-
-describe('IDE file templates mutations', () => {
- let state;
-
- beforeEach(() => {
- state = createState();
- });
-
- describe(types.REQUEST_TEMPLATE_TYPES, () => {
- it('sets isLoading', () => {
- mutations[types.REQUEST_TEMPLATE_TYPES](state);
-
- expect(state.isLoading).toBe(true);
- });
- });
-
- describe(types.RECEIVE_TEMPLATE_TYPES_ERROR, () => {
- it('sets isLoading', () => {
- state.isLoading = true;
-
- mutations[types.RECEIVE_TEMPLATE_TYPES_ERROR](state);
-
- expect(state.isLoading).toBe(false);
- });
- });
-
- describe(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, () => {
- it('sets isLoading to false', () => {
- state.isLoading = true;
-
- mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, []);
-
- expect(state.isLoading).toBe(false);
- });
-
- it('sets templates', () => {
- mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, ['test']);
-
- expect(state.templates).toEqual(['test']);
- });
- });
-
- describe(types.SET_SELECTED_TEMPLATE_TYPE, () => {
- it('sets selectedTemplateType', () => {
- mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type');
-
- expect(state.selectedTemplateType).toBe('type');
- });
-
- it('clears templates', () => {
- state.templates = ['test'];
-
- mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type');
-
- expect(state.templates).toEqual([]);
- });
- });
-
- describe(types.SET_UPDATE_SUCCESS, () => {
- it('sets updateSuccess', () => {
- mutations[types.SET_UPDATE_SUCCESS](state, true);
-
- expect(state.updateSuccess).toBe(true);
- });
- });
-});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
index eb7346bd5fc..b558c45f574 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
@@ -27,63 +27,71 @@ describe('IDE pipelines mutations', () => {
});
describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => {
- it('sets loading to false on success', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
- mockedState,
- fullPipelinesResponse.data.pipelines[0],
- );
+ const itSetsPipelineLoadingStates = () => {
+ it('sets has loaded to true', () => {
+ expect(mockedState.hasLoadedPipeline).toBe(true);
+ });
- expect(mockedState.isLoadingPipeline).toBe(false);
- });
+ it('sets loading to false on success', () => {
+ expect(mockedState.isLoadingPipeline).toBe(false);
+ });
+ };
+
+ describe('with pipeline', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
+ mockedState,
+ fullPipelinesResponse.data.pipelines[0],
+ );
+ });
- it('sets latestPipeline', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
- mockedState,
- fullPipelinesResponse.data.pipelines[0],
- );
+ itSetsPipelineLoadingStates();
- expect(mockedState.latestPipeline).toEqual({
- id: '51',
- path: 'test',
- commit: { id: '123' },
- details: { status: jasmine.any(Object) },
- yamlError: undefined,
+ it('sets latestPipeline', () => {
+ expect(mockedState.latestPipeline).toEqual({
+ id: '51',
+ path: 'test',
+ commit: { id: '123' },
+ details: { status: jasmine.any(Object) },
+ yamlError: undefined,
+ });
});
- });
- it('does not set latest pipeline if pipeline is null', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null);
-
- expect(mockedState.latestPipeline).toEqual(false);
+ it('sets stages', () => {
+ expect(mockedState.stages.length).toBe(2);
+ expect(mockedState.stages).toEqual([
+ {
+ id: 0,
+ dropdownPath: stages[0].dropdown_path,
+ name: stages[0].name,
+ status: stages[0].status,
+ isCollapsed: false,
+ isLoading: false,
+ jobs: [],
+ },
+ {
+ id: 1,
+ dropdownPath: stages[1].dropdown_path,
+ name: stages[1].name,
+ status: stages[1].status,
+ isCollapsed: false,
+ isLoading: false,
+ jobs: [],
+ },
+ ]);
+ });
});
- it('sets stages', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
- mockedState,
- fullPipelinesResponse.data.pipelines[0],
- );
+ describe('with null', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null);
+ });
- expect(mockedState.stages.length).toBe(2);
- expect(mockedState.stages).toEqual([
- {
- id: 0,
- dropdownPath: stages[0].dropdown_path,
- name: stages[0].name,
- status: stages[0].status,
- isCollapsed: false,
- isLoading: false,
- jobs: [],
- },
- {
- id: 1,
- dropdownPath: stages[1].dropdown_path,
- name: stages[1].name,
- status: stages[1].status,
- isCollapsed: false,
- isLoading: false,
- jobs: [],
- },
- ]);
+ itSetsPipelineLoadingStates();
+
+ it('does not set latest pipeline if pipeline is null', () => {
+ expect(mockedState.latestPipeline).toEqual(null);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js
deleted file mode 100644
index 29eb859ddaf..00000000000
--- a/spec/javascripts/ide/stores/mutations/branch_spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import mutations from '~/ide/stores/mutations/branch';
-import state from '~/ide/stores/state';
-
-describe('Multi-file store branch mutations', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- });
-
- describe('SET_CURRENT_BRANCH', () => {
- it('sets currentBranch', () => {
- mutations.SET_CURRENT_BRANCH(localState, 'master');
-
- expect(localState.currentBranchId).toBe('master');
- });
- });
-
- describe('SET_BRANCH_COMMIT', () => {
- it('sets the last commit on current project', () => {
- localState.projects = {
- Example: {
- branches: {
- master: {},
- },
- },
- };
-
- mutations.SET_BRANCH_COMMIT(localState, {
- projectId: 'Example',
- branchId: 'master',
- commit: {
- title: 'Example commit',
- },
- });
-
- expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
- });
- });
-});
diff --git a/spec/javascripts/ide/stores/mutations/merge_request_spec.js b/spec/javascripts/ide/stores/mutations/merge_request_spec.js
deleted file mode 100644
index e30ca22022f..00000000000
--- a/spec/javascripts/ide/stores/mutations/merge_request_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import mutations from '~/ide/stores/mutations/merge_request';
-import state from '~/ide/stores/state';
-
-describe('IDE store merge request mutations', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- localState.projects = { abcproject: { mergeRequests: {} } };
-
- mutations.SET_MERGE_REQUEST(localState, {
- projectPath: 'abcproject',
- mergeRequestId: 1,
- mergeRequest: {
- title: 'mr',
- },
- });
- });
-
- describe('SET_CURRENT_MERGE_REQUEST', () => {
- it('sets current merge request', () => {
- mutations.SET_CURRENT_MERGE_REQUEST(localState, 2);
-
- expect(localState.currentMergeRequestId).toBe(2);
- });
- });
-
- describe('SET_MERGE_REQUEST', () => {
- it('setsmerge request data', () => {
- const newMr = localState.projects.abcproject.mergeRequests[1];
-
- expect(newMr.title).toBe('mr');
- expect(newMr.active).toBeTruthy();
- });
- });
-
- describe('SET_MERGE_REQUEST_CHANGES', () => {
- it('sets merge request changes', () => {
- mutations.SET_MERGE_REQUEST_CHANGES(localState, {
- projectPath: 'abcproject',
- mergeRequestId: 1,
- changes: {
- diff: 'abc',
- },
- });
-
- const newMr = localState.projects.abcproject.mergeRequests[1];
-
- expect(newMr.changes.diff).toBe('abc');
- });
- });
-
- describe('SET_MERGE_REQUEST_VERSIONS', () => {
- it('sets merge request versions', () => {
- mutations.SET_MERGE_REQUEST_VERSIONS(localState, {
- projectPath: 'abcproject',
- mergeRequestId: 1,
- versions: [{ id: 123 }],
- });
-
- const newMr = localState.projects.abcproject.mergeRequests[1];
-
- expect(newMr.versions.length).toBe(1);
- expect(newMr.versions[0].id).toBe(123);
- });
- });
-});
diff --git a/spec/javascripts/ide/stores/mutations/tree_spec.js b/spec/javascripts/ide/stores/mutations/tree_spec.js
index 67e9f7509da..7f9c978aa46 100644
--- a/spec/javascripts/ide/stores/mutations/tree_spec.js
+++ b/spec/javascripts/ide/stores/mutations/tree_spec.js
@@ -26,17 +26,11 @@ describe('Multi-file store tree mutations', () => {
});
describe('SET_DIRECTORY_DATA', () => {
- const data = [
- {
- name: 'tree',
- },
- {
- name: 'submodule',
- },
- {
- name: 'blob',
- },
- ];
+ let data;
+
+ beforeEach(() => {
+ data = [file('tree'), file('foo'), file('blob')];
+ });
it('adds directory data', () => {
localState.trees['project/master'] = {
@@ -52,7 +46,7 @@ describe('Multi-file store tree mutations', () => {
expect(tree.tree.length).toBe(3);
expect(tree.tree[0].name).toBe('tree');
- expect(tree.tree[1].name).toBe('submodule');
+ expect(tree.tree[1].name).toBe('foo');
expect(tree.tree[2].name).toBe('blob');
});
@@ -65,6 +59,49 @@ describe('Multi-file store tree mutations', () => {
expect(localState.trees['project/master'].loading).toBe(true);
});
+
+ it('does not override tree already in state, but merges the two with correct order', () => {
+ const openedFile = file('new');
+
+ localState.trees['project/master'] = {
+ loading: true,
+ tree: [openedFile],
+ };
+
+ mutations.SET_DIRECTORY_DATA(localState, {
+ data,
+ treePath: 'project/master',
+ });
+
+ const { tree } = localState.trees['project/master'];
+
+ expect(tree.length).toBe(4);
+ expect(tree[0].name).toBe('blob');
+ expect(tree[1].name).toBe('foo');
+ expect(tree[2].name).toBe('new');
+ expect(tree[3].name).toBe('tree');
+ });
+
+ it('returns tree unchanged if the opened file is already in the tree', () => {
+ const openedFile = file('foo');
+ localState.trees['project/master'] = {
+ loading: true,
+ tree: [openedFile],
+ };
+
+ mutations.SET_DIRECTORY_DATA(localState, {
+ data,
+ treePath: 'project/master',
+ });
+
+ const { tree } = localState.trees['project/master'];
+
+ expect(tree.length).toBe(3);
+
+ expect(tree[0].name).toBe('tree');
+ expect(tree[1].name).toBe('foo');
+ expect(tree[2].name).toBe('blob');
+ });
});
describe('REMOVE_ALL_CHANGES_FILES', () => {
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 41dd3d3c67f..5ee098bf17f 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -298,7 +298,12 @@ describe('Multi-file store mutations', () => {
});
it('creates new renamed entry', () => {
- mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+ mutations.RENAME_ENTRY(localState, {
+ path: 'oldPath',
+ name: 'newPath',
+ entryPath: null,
+ parentPath: '',
+ });
expect(localState.entries.newPath).toEqual({
...localState.entries.oldPath,
@@ -335,7 +340,12 @@ describe('Multi-file store mutations', () => {
...file(),
};
- mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+ mutations.RENAME_ENTRY(localState, {
+ path: 'oldPath',
+ name: 'newPath',
+ entryPath: null,
+ parentPath: 'parentPath',
+ });
expect(localState.entries.parentPath.tree.length).toBe(1);
});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index 9f18034f8a3..debe1c4acee 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -1,4 +1,5 @@
import * as utils from '~/ide/stores/utils';
+import { commitActionTypes } from '~/ide/constants';
import { file } from '../helpers';
describe('Multi-file store utils', () => {
@@ -107,7 +108,7 @@ describe('Multi-file store utils', () => {
commit_message: 'commit message',
actions: [
{
- action: 'update',
+ action: commitActionTypes.update,
file_path: 'staged',
content: 'updated file content',
encoding: 'text',
@@ -115,7 +116,7 @@ describe('Multi-file store utils', () => {
previous_path: undefined,
},
{
- action: 'create',
+ action: commitActionTypes.create,
file_path: 'added',
content: 'new file content',
encoding: 'base64',
@@ -123,7 +124,7 @@ describe('Multi-file store utils', () => {
previous_path: undefined,
},
{
- action: 'delete',
+ action: commitActionTypes.delete,
file_path: 'deletedFile',
content: undefined,
encoding: 'text',
@@ -170,7 +171,7 @@ describe('Multi-file store utils', () => {
commit_message: 'prebuilt test commit message',
actions: [
{
- action: 'update',
+ action: commitActionTypes.update,
file_path: 'staged',
content: 'updated file content',
encoding: 'text',
@@ -178,7 +179,7 @@ describe('Multi-file store utils', () => {
previous_path: undefined,
},
{
- action: 'create',
+ action: commitActionTypes.create,
file_path: 'added',
content: 'new file content',
encoding: 'base64',
@@ -193,19 +194,19 @@ describe('Multi-file store utils', () => {
describe('commitActionForFile', () => {
it('returns deleted for deleted file', () => {
- expect(utils.commitActionForFile({ deleted: true })).toBe('delete');
+ expect(utils.commitActionForFile({ deleted: true })).toBe(commitActionTypes.delete);
});
it('returns create for tempFile', () => {
- expect(utils.commitActionForFile({ tempFile: true })).toBe('create');
+ expect(utils.commitActionForFile({ tempFile: true })).toBe(commitActionTypes.create);
});
it('returns move for moved file', () => {
- expect(utils.commitActionForFile({ prevPath: 'test' })).toBe('move');
+ expect(utils.commitActionForFile({ prevPath: 'test' })).toBe(commitActionTypes.move);
});
it('returns update by default', () => {
- expect(utils.commitActionForFile({})).toBe('update');
+ expect(utils.commitActionForFile({})).toBe(commitActionTypes.update);
});
});
@@ -235,4 +236,129 @@ describe('Multi-file store utils', () => {
]);
});
});
+
+ describe('mergeTrees', () => {
+ let fromTree;
+ let toTree;
+
+ beforeEach(() => {
+ fromTree = [file('foo')];
+ toTree = [file('bar')];
+ });
+
+ it('merges simple trees with sorting the result', () => {
+ toTree = [file('beta'), file('alpha'), file('gamma')];
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(4);
+ expect(res[0].name).toEqual('alpha');
+ expect(res[1].name).toEqual('beta');
+ expect(res[2].name).toEqual('foo');
+ expect(res[3].name).toEqual('gamma');
+ expect(res[2]).toBe(fromTree[0]);
+ });
+
+ it('handles edge cases', () => {
+ expect(utils.mergeTrees({}, []).length).toEqual(0);
+
+ let res = utils.mergeTrees({}, toTree);
+
+ expect(res.length).toEqual(1);
+ expect(res[0].name).toEqual('bar');
+
+ res = utils.mergeTrees(fromTree, []);
+
+ expect(res.length).toEqual(1);
+ expect(res[0].name).toEqual('foo');
+ expect(res[0]).toBe(fromTree[0]);
+ });
+
+ it('merges simple trees without producing duplicates', () => {
+ toTree.push(file('foo'));
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(2);
+ expect(res[0].name).toEqual('bar');
+ expect(res[1].name).toEqual('foo');
+ expect(res[1]).not.toBe(fromTree[0]);
+ });
+
+ it('merges nested tree into the main one without duplicates', () => {
+ fromTree[0].tree.push({
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('beta.md'),
+ path: 'foo/alpha/beta.md',
+ },
+ ],
+ });
+
+ toTree.push({
+ ...file('foo'),
+ tree: [
+ {
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('gamma.md'),
+ path: 'foo/alpha/gamma.md',
+ },
+ ],
+ },
+ ],
+ });
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(2);
+ expect(res[1].name).toEqual('foo');
+
+ const finalBranch = res[1].tree[0].tree;
+
+ expect(finalBranch.length).toEqual(2);
+ expect(finalBranch[0].name).toEqual('beta.md');
+ expect(finalBranch[1].name).toEqual('gamma.md');
+ });
+
+ it('marks correct folders as opened as the parsing goes on', () => {
+ fromTree[0].tree.push({
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('beta.md'),
+ path: 'foo/alpha/beta.md',
+ },
+ ],
+ });
+
+ toTree.push({
+ ...file('foo'),
+ tree: [
+ {
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('gamma.md'),
+ path: 'foo/alpha/gamma.md',
+ },
+ ],
+ },
+ ],
+ });
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res[1].name).toEqual('foo');
+ expect(res[1].opened).toEqual(true);
+
+ expect(res[1].tree[0].name).toEqual('alpha');
+ expect(res[1].tree[0].opened).toEqual(true);
+ });
+ });
});
diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js
deleted file mode 100644
index a1ff84ce259..00000000000
--- a/spec/javascripts/import_projects/components/import_projects_table_spec.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import store from '~/import_projects/store';
-import importProjectsTable from '~/import_projects/components/import_projects_table.vue';
-import STATUS_MAP from '~/import_projects/constants';
-import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
-
-describe('ImportProjectsTable', () => {
- let vm;
- let mock;
- const reposPath = '/repos-path';
- const jobsPath = '/jobs-path';
- const providerTitle = 'THE PROVIDER';
- const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
- const importedProject = {
- id: 1,
- fullPath: 'fullPath',
- importStatus: 'started',
- providerLink: 'providerLink',
- importSource: 'importSource',
- };
-
- function createComponent() {
- const ImportProjectsTable = Vue.extend(importProjectsTable);
-
- const component = new ImportProjectsTable({
- store,
- propsData: {
- providerTitle,
- },
- }).$mount();
-
- component.$store.dispatch('stopJobsPolling');
-
- return component;
- }
-
- beforeEach(() => {
- store.dispatch('setInitialData', { reposPath });
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- vm.$destroy();
- mock.restore();
- });
-
- it('renders a loading icon whilst repos are loading', done => {
- mock.restore(); // Stop the mock adapter from responding to the request, keeping the spinner up
-
- vm = createComponent();
-
- setTimeoutPromise()
- .then(() => {
- expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull();
- })
- .then(() => done())
- .catch(() => done.fail());
- });
-
- it('renders a table with imported projects and provider repos', done => {
- const response = {
- importedProjects: [importedProject],
- providerRepos: [providerRepo],
- namespaces: [{ path: 'path' }],
- };
- mock.onGet(reposPath).reply(200, response);
-
- vm = createComponent();
-
- setTimeoutPromise()
- .then(() => {
- expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
- expect(vm.$el.querySelector('.table')).not.toBeNull();
- expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch(
- `From ${providerTitle}`,
- );
-
- expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
- expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
- })
- .then(() => done())
- .catch(() => done.fail());
- });
-
- it('renders an empty state if there are no imported projects or provider repos', done => {
- const response = {
- importedProjects: [],
- providerRepos: [],
- namespaces: [],
- };
- mock.onGet(reposPath).reply(200, response);
-
- vm = createComponent();
-
- setTimeoutPromise()
- .then(() => {
- expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
- expect(vm.$el.querySelector('.table')).toBeNull();
- expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`);
- })
- .then(() => done())
- .catch(() => done.fail());
- });
-
- it('imports provider repos if bulk import button is clicked', done => {
- const importPath = '/import-path';
- const response = {
- importedProjects: [],
- providerRepos: [providerRepo],
- namespaces: [{ path: 'path' }],
- };
-
- mock.onGet(reposPath).replyOnce(200, response);
- mock.onPost(importPath).replyOnce(200, importedProject);
-
- store.dispatch('setInitialData', { importPath });
-
- vm = createComponent();
-
- setTimeoutPromise()
- .then(() => {
- expect(vm.$el.querySelector('.js-imported-project')).toBeNull();
- expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
-
- vm.$el.querySelector('.js-import-all').click();
- })
- .then(() => setTimeoutPromise())
- .then(() => {
- expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
- expect(vm.$el.querySelector('.js-provider-repo')).toBeNull();
- })
- .then(() => done())
- .catch(() => done.fail());
- });
-
- it('polls to update the status of imported projects', done => {
- const importPath = '/import-path';
- const response = {
- importedProjects: [importedProject],
- providerRepos: [],
- namespaces: [{ path: 'path' }],
- };
- const updatedProjects = [
- {
- id: importedProject.id,
- importStatus: 'finished',
- },
- ];
-
- mock.onGet(reposPath).replyOnce(200, response);
-
- store.dispatch('setInitialData', { importPath, jobsPath });
-
- vm = createComponent();
-
- setTimeoutPromise()
- .then(() => {
- const statusObject = STATUS_MAP[importedProject.importStatus];
-
- expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
- );
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
-
- mock.onGet(jobsPath).replyOnce(200, updatedProjects);
- return vm.$store.dispatch('restartJobsPolling');
- })
- .then(() => setTimeoutPromise())
- .then(() => {
- const statusObject = STATUS_MAP[updatedProjects[0].importStatus];
-
- expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
- );
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
- })
- .then(() => done())
- .catch(() => done.fail());
- });
-});
diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js
deleted file mode 100644
index 8af3b5954a9..00000000000
--- a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import Vue from 'vue';
-import store from '~/import_projects/store';
-import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
-import STATUS_MAP from '~/import_projects/constants';
-
-describe('ImportedProjectTableRow', () => {
- let vm;
- const project = {
- id: 1,
- fullPath: 'fullPath',
- importStatus: 'finished',
- providerLink: 'providerLink',
- importSource: 'importSource',
- };
-
- function createComponent() {
- const ImportedProjectTableRow = Vue.extend(importedProjectTableRow);
-
- return new ImportedProjectTableRow({
- store,
- propsData: {
- project: {
- ...project,
- },
- },
- }).$mount();
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders an imported project table row', () => {
- vm = createComponent();
-
- const providerLink = vm.$el.querySelector('.js-provider-link');
- const statusObject = STATUS_MAP[project.importStatus];
-
- expect(vm.$el.classList.contains('js-imported-project')).toBe(true);
- expect(providerLink.href).toMatch(project.providerLink);
- expect(providerLink.textContent).toMatch(project.importSource);
- expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath);
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
- );
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
- expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath);
- });
-});
diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js
deleted file mode 100644
index 69377f8d685..00000000000
--- a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import store from '~/import_projects/store';
-import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
-import STATUS_MAP, { STATUSES } from '~/import_projects/constants';
-import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
-
-describe('ProviderRepoTableRow', () => {
- let vm;
- const repo = {
- id: 10,
- sanitizedName: 'sanitizedName',
- fullName: 'fullName',
- providerLink: 'providerLink',
- };
-
- function createComponent() {
- const ProviderRepoTableRow = Vue.extend(providerRepoTableRow);
-
- return new ProviderRepoTableRow({
- store,
- propsData: {
- repo: {
- ...repo,
- },
- },
- }).$mount();
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders a provider repo table row', () => {
- vm = createComponent();
-
- const providerLink = vm.$el.querySelector('.js-provider-link');
- const statusObject = STATUS_MAP[STATUSES.NONE];
-
- expect(vm.$el.classList.contains('js-provider-repo')).toBe(true);
- expect(providerLink.href).toMatch(repo.providerLink);
- expect(providerLink.textContent).toMatch(repo.fullName);
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
- );
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
- expect(vm.$el.querySelector('.js-import-button')).not.toBeNull();
- });
-
- it('renders a select2 namespace select', () => {
- vm = createComponent();
-
- const dropdownTrigger = vm.$el.querySelector('.js-namespace-select');
-
- expect(dropdownTrigger).not.toBeNull();
- expect(dropdownTrigger.classList.contains('select2-container')).toBe(true);
-
- dropdownTrigger.click();
-
- expect(vm.$el.querySelector('.select2-drop')).not.toBeNull();
- });
-
- it('imports repo when clicking import button', done => {
- const importPath = '/import-path';
- const defaultTargetNamespace = 'user';
- const ciCdOnly = true;
- const mock = new MockAdapter(axios);
-
- store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly });
- mock.onPost(importPath).replyOnce(200);
- spyOn(store, 'dispatch').and.returnValue(new Promise(() => {}));
-
- vm = createComponent();
-
- vm.$el.querySelector('.js-import-button').click();
-
- setTimeoutPromise()
- .then(() => {
- expect(store.dispatch).toHaveBeenCalledWith('fetchImport', {
- repo,
- newName: repo.sanitizedName,
- targetNamespace: defaultTargetNamespace,
- });
- })
- .then(() => mock.restore())
- .then(done)
- .catch(done.fail);
- });
-});
diff --git a/spec/javascripts/import_projects/store/actions_spec.js b/spec/javascripts/import_projects/store/actions_spec.js
deleted file mode 100644
index 77850ee3283..00000000000
--- a/spec/javascripts/import_projects/store/actions_spec.js
+++ /dev/null
@@ -1,284 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import {
- SET_INITIAL_DATA,
- REQUEST_REPOS,
- RECEIVE_REPOS_SUCCESS,
- RECEIVE_REPOS_ERROR,
- REQUEST_IMPORT,
- RECEIVE_IMPORT_SUCCESS,
- RECEIVE_IMPORT_ERROR,
- RECEIVE_JOBS_SUCCESS,
-} from '~/import_projects/store/mutation_types';
-import {
- setInitialData,
- requestRepos,
- receiveReposSuccess,
- receiveReposError,
- fetchRepos,
- requestImport,
- receiveImportSuccess,
- receiveImportError,
- fetchImport,
- receiveJobsSuccess,
- fetchJobs,
- clearJobsEtagPoll,
- stopJobsPolling,
-} from '~/import_projects/store/actions';
-import state from '~/import_projects/store/state';
-import testAction from 'spec/helpers/vuex_action_helper';
-import { TEST_HOST } from 'spec/test_constants';
-
-describe('import_projects store actions', () => {
- let localState;
- const repoId = 1;
- const repos = [{ id: 1 }, { id: 2 }];
- const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } };
-
- beforeEach(() => {
- localState = state();
- });
-
- describe('setInitialData', () => {
- it(`commits ${SET_INITIAL_DATA} mutation`, done => {
- const initialData = {
- reposPath: 'reposPath',
- provider: 'provider',
- jobsPath: 'jobsPath',
- importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath',
- defaultTargetNamespace: 'defaultTargetNamespace',
- ciCdOnly: 'ciCdOnly',
- canSelectNamespace: 'canSelectNamespace',
- };
-
- testAction(
- setInitialData,
- initialData,
- localState,
- [{ type: SET_INITIAL_DATA, payload: initialData }],
- [],
- done,
- );
- });
- });
-
- describe('requestRepos', () => {
- it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => {
- testAction(
- requestRepos,
- null,
- localState,
- [{ type: REQUEST_REPOS, payload: null }],
- [],
- done,
- );
- });
- });
-
- describe('receiveReposSuccess', () => {
- it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => {
- testAction(
- receiveReposSuccess,
- repos,
- localState,
- [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }],
- [],
- done,
- );
- });
- });
-
- describe('receiveReposError', () => {
- it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => {
- testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done);
- });
- });
-
- describe('fetchRepos', () => {
- let mock;
-
- beforeEach(() => {
- localState.reposPath = `${TEST_HOST}/endpoint.json`;
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => mock.restore());
-
- it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => {
- const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] };
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload);
-
- testAction(
- fetchRepos,
- null,
- localState,
- [],
- [
- { type: 'requestRepos' },
- {
- type: 'receiveReposSuccess',
- payload: convertObjectPropsToCamelCase(payload, { deep: true }),
- },
- {
- type: 'fetchJobs',
- },
- ],
- done,
- );
- });
-
- it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
-
- testAction(
- fetchRepos,
- null,
- localState,
- [],
- [{ type: 'requestRepos' }, { type: 'receiveReposError' }],
- done,
- );
- });
- });
-
- describe('requestImport', () => {
- it(`commits ${REQUEST_IMPORT} mutation`, done => {
- testAction(
- requestImport,
- repoId,
- localState,
- [{ type: REQUEST_IMPORT, payload: repoId }],
- [],
- done,
- );
- });
- });
-
- describe('receiveImportSuccess', () => {
- it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => {
- const payload = { importedProject: { name: 'imported/project' }, repoId: 2 };
-
- testAction(
- receiveImportSuccess,
- payload,
- localState,
- [{ type: RECEIVE_IMPORT_SUCCESS, payload }],
- [],
- done,
- );
- });
- });
-
- describe('receiveImportError', () => {
- it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => {
- testAction(
- receiveImportError,
- repoId,
- localState,
- [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }],
- [],
- done,
- );
- });
- });
-
- describe('fetchImport', () => {
- let mock;
-
- beforeEach(() => {
- localState.importPath = `${TEST_HOST}/endpoint.json`;
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => mock.restore());
-
- it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => {
- const importedProject = { name: 'imported/project' };
- const importRepoId = importPayload.repo.id;
- mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject);
-
- testAction(
- fetchImport,
- importPayload,
- localState,
- [],
- [
- { type: 'requestImport', payload: importRepoId },
- {
- type: 'receiveImportSuccess',
- payload: {
- importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }),
- repoId: importRepoId,
- },
- },
- ],
- done,
- );
- });
-
- it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => {
- mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
-
- testAction(
- fetchImport,
- importPayload,
- localState,
- [],
- [
- { type: 'requestImport', payload: importPayload.repo.id },
- { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } },
- ],
- done,
- );
- });
- });
-
- describe('receiveJobsSuccess', () => {
- it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => {
- testAction(
- receiveJobsSuccess,
- repos,
- localState,
- [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }],
- [],
- done,
- );
- });
- });
-
- describe('fetchJobs', () => {
- let mock;
-
- beforeEach(() => {
- localState.jobsPath = `${TEST_HOST}/endpoint.json`;
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- stopJobsPolling();
- clearJobsEtagPoll();
- });
-
- afterEach(() => mock.restore());
-
- it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => {
- const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }];
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects);
-
- testAction(
- fetchJobs,
- null,
- localState,
- [],
- [
- {
- type: 'receiveJobsSuccess',
- payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }),
- },
- ],
- done,
- );
- });
- });
-});
diff --git a/spec/javascripts/import_projects/store/mutations_spec.js b/spec/javascripts/import_projects/store/mutations_spec.js
deleted file mode 100644
index 8db8e9819ba..00000000000
--- a/spec/javascripts/import_projects/store/mutations_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import * as types from '~/import_projects/store/mutation_types';
-import mutations from '~/import_projects/store/mutations';
-
-describe('import_projects store mutations', () => {
- describe(types.RECEIVE_IMPORT_SUCCESS, () => {
- it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => {
- const repoId = 1;
- const state = {
- reposBeingImported: [repoId],
- providerRepos: [{ id: repoId }],
- importedProjects: [],
- };
- const importedProject = { id: repoId };
-
- mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId });
-
- expect(state.reposBeingImported.includes(repoId)).toBe(false);
- expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false);
- expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true);
- });
- });
-
- describe(types.RECEIVE_JOBS_SUCCESS, () => {
- it('updates importStatus of existing importedProjects', () => {
- const repoId = 1;
- const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] };
- const updatedProjects = [{ id: repoId, importStatus: 'finished' }];
-
- mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects);
-
- expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus);
- });
- });
-});
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 4f4c9a7b463..069e2cb07b5 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
describe('IntegrationSettingsForm', () => {
- const FIXTURE = 'services/edit_service.html.raw';
+ const FIXTURE = 'services/edit_service.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 0ccf771c7ef..2770743937e 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -75,7 +75,7 @@ describe('Issuable output', () => {
.then(() => {
expect(document.querySelector('title').innerText).toContain('this is a title (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>');
- expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>');
+ expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>this is a description!</p>');
expect(vm.$el.querySelector('.js-task-list-field').value).toContain(
'this is a description',
);
@@ -92,7 +92,7 @@ describe('Issuable output', () => {
.then(() => {
expect(document.querySelector('title').innerText).toContain('2 (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
- expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
+ expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>42</p>');
expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42');
expect(vm.$el.querySelector('.edited-text')).toBeTruthy();
expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(
@@ -470,4 +470,51 @@ describe('Issuable output', () => {
.catch(done.fail);
});
});
+
+ describe('issueChanged', () => {
+ beforeEach(() => {
+ vm.store.formState.title = '';
+ vm.store.formState.description = '';
+ vm.initialDescriptionText = '';
+ vm.initialTitleText = '';
+ });
+
+ it('returns true when title is changed', () => {
+ vm.store.formState.title = 'RandomText';
+
+ expect(vm.issueChanged).toBe(true);
+ });
+
+ it('returns false when title is empty null', () => {
+ vm.store.formState.title = null;
+
+ expect(vm.issueChanged).toBe(false);
+ });
+
+ it('returns false when `initialTitleText` is null and `formState.title` is empty string', () => {
+ vm.store.formState.title = '';
+ vm.initialTitleText = null;
+
+ expect(vm.issueChanged).toBe(false);
+ });
+
+ it('returns true when description is changed', () => {
+ vm.store.formState.description = 'RandomText';
+
+ expect(vm.issueChanged).toBe(true);
+ });
+
+ it('returns false when description is empty null', () => {
+ vm.store.formState.title = null;
+
+ expect(vm.issueChanged).toBe(false);
+ });
+
+ it('returns false when `initialDescriptionText` is null and `formState.description` is empty string', () => {
+ vm.store.formState.description = '';
+ vm.initialDescriptionText = null;
+
+ expect(vm.issueChanged).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 2eeed6770be..7e00fbf2745 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -43,12 +43,12 @@ describe('Description component', () => {
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.wiki').classList.contains('issue-realtime-pre-pulse'),
+ vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
).toBeTruthy();
setTimeout(() => {
expect(
- vm.$el.querySelector('.wiki').classList.contains('issue-realtime-trigger-pulse'),
+ vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'),
).toBeTruthy();
done();
diff --git a/spec/javascripts/issue_show/components/fields/description_spec.js b/spec/javascripts/issue_show/components/fields/description_spec.js
index 2c3efc8d4d4..f5f87a6bfbf 100644
--- a/spec/javascripts/issue_show/components/fields/description_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_spec.js
@@ -63,4 +63,8 @@ describe('Description field component', () => {
expect(eventHub.$emit).toHaveBeenCalled();
});
+
+ it('has a ref named `textarea`', () => {
+ expect(vm.$refs.textarea).not.toBeNull();
+ });
});
diff --git a/spec/javascripts/issue_show/components/fields/title_spec.js b/spec/javascripts/issue_show/components/fields/title_spec.js
index 4b96a1feb29..62dff983250 100644
--- a/spec/javascripts/issue_show/components/fields/title_spec.js
+++ b/spec/javascripts/issue_show/components/fields/title_spec.js
@@ -41,4 +41,8 @@ describe('Title field component', () => {
expect(eventHub.$emit).toHaveBeenCalled();
});
+
+ it('has a ref named `input`', () => {
+ expect(vm.$refs.input).not.toBeNull();
+ });
});
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index 523954013cf..b0f4ab2b12d 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,10 +1,17 @@
import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue';
+import eventHub from '~/issue_show/event_hub';
describe('Inline edit form component', () => {
let vm;
+ let autosave;
+ let autosaveObj;
beforeEach(done => {
+ autosaveObj = { reset: jasmine.createSpy() };
+
+ autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+
const Component = Vue.extend(formComponent);
vm = new Component({
@@ -53,4 +60,22 @@ describe('Inline edit form component', () => {
done();
});
});
+
+ it('initialized Autosave on mount', () => {
+ expect(autosave).toHaveBeenCalledTimes(2);
+ });
+
+ it('calls reset on autosave when eventHub emits appropriate events', () => {
+ eventHub.$emit('close.form');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
+
+ eventHub.$emit('delete.issuable');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
+
+ eventHub.$emit('update.issuable');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ });
});
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 7be495d1d35..966aee72abb 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -9,9 +9,9 @@ import '~/lib/utils/text_utility';
describe('Issue', function() {
let $boxClosed, $boxOpen, $btn;
- preloadFixtures('issues/closed-issue.html.raw');
- preloadFixtures('issues/issue-with-task-list.html.raw');
- preloadFixtures('issues/open-issue.html.raw');
+ preloadFixtures('issues/closed-issue.html');
+ preloadFixtures('issues/issue-with-task-list.html');
+ preloadFixtures('issues/open-issue.html');
function expectErrorMessage() {
const $flashMessage = $('div.flash-alert');
@@ -105,15 +105,14 @@ describe('Issue', function() {
beforeEach(function() {
if (isIssueInitiallyOpen) {
- loadFixtures('issues/open-issue.html.raw');
+ loadFixtures('issues/open-issue.html');
} else {
- loadFixtures('issues/closed-issue.html.raw');
+ loadFixtures('issues/closed-issue.html');
}
mock = new MockAdapter(axios);
mock.onGet(/(.*)\/related_branches$/).reply(200, {});
- mock.onGet(/(.*)\/referenced_merge_requests$/).reply(200, {});
findElements(isIssueInitiallyOpen);
this.issue = new Issue();
diff --git a/spec/javascripts/jobs/components/artifacts_block_spec.js b/spec/javascripts/jobs/components/artifacts_block_spec.js
index 27d480ef2ea..58998d038e5 100644
--- a/spec/javascripts/jobs/components/artifacts_block_spec.js
+++ b/spec/javascripts/jobs/components/artifacts_block_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
import component from '~/jobs/components/artifacts_block.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/vue_component_helper';
+import { trimText } from '../../helpers/text_helper';
describe('Artifacts block', () => {
const Component = Vue.extend(component);
diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js
index 98eba3ac976..c02f564d01a 100644
--- a/spec/javascripts/jobs/components/commit_block_spec.js
+++ b/spec/javascripts/jobs/components/commit_block_spec.js
@@ -9,6 +9,7 @@ describe('Commit block', () => {
const props = {
commit: {
short_id: '1f0fb84f',
+ id: '1f0fb84fb6770d74d97eee58118fd3909cd4f48c',
commit_path: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c',
title: 'Update README.md',
},
@@ -42,7 +43,7 @@ describe('Commit block', () => {
it('renders clipboard button', () => {
expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual(
- props.commit.short_id,
+ props.commit.id,
);
});
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index ba5d672f189..f28d2c2a882 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -17,6 +17,7 @@ describe('Job App ', () => {
const props = {
endpoint: `${gl.TEST_HOST}jobs/123.json`,
runnerHelpUrl: 'help/runner',
+ deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
@@ -89,9 +90,12 @@ describe('Job App ', () => {
describe('triggered job', () => {
beforeEach(() => {
+ const aYearAgo = new Date();
+ aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
+
mock
.onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: '2017-05-24T10:59:52.000+01:00' }));
+ .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
vm = mountComponentWithStore(Component, { props, store });
});
@@ -253,6 +257,41 @@ describe('Job App ', () => {
});
});
+ describe('unmet prerequisites block', () => {
+ it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
+ mock.onGet(props.endpoint).replyOnce(
+ 200,
+ Object.assign({}, job, {
+ status: {
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ illustration: {
+ content: 'Retry this job in order to create the necessary resources.',
+ image: 'path',
+ size: 'svg-430',
+ title: 'Failed to create resources',
+ },
+ },
+ failure_reason: 'unmet_prerequisites',
+ has_trace: false,
+ runners: {
+ available: true,
+ },
+ tags: [],
+ }),
+ );
+ vm = mountComponentWithStore(Component, { props, store });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ done();
+ }, 0);
+ });
+ });
+
describe('environments block', () => {
it('renders environment block when job has environment', done => {
mock.onGet(props.endpoint).replyOnce(
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index 3a02351460c..740bc3d0491 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
import createStore from '~/jobs/store';
-import job, { stages, jobsInStage } from '../mock_data';
+import job, { jobsInStage } from '../mock_data';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/vue_component_helper';
+import { trimText } from '../../helpers/text_helper';
describe('Sidebar details block', () => {
const SidebarComponent = Vue.extend(sidebarDetailsBlock);
@@ -131,18 +131,8 @@ describe('Sidebar details block', () => {
store.dispatch('receiveJobSuccess', job);
});
- describe('while fetching stages', () => {
- it('it does not render dropdown', () => {
- store.dispatch('requestStages');
- vm = mountComponentWithStore(SidebarComponent, { store });
-
- expect(vm.$el.querySelector('.js-selected-stage')).toBeNull();
- });
- });
-
describe('with stages', () => {
beforeEach(() => {
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -156,7 +146,6 @@ describe('Sidebar details block', () => {
describe('without jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -168,7 +157,6 @@ describe('Sidebar details block', () => {
describe('with jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
vm = mountComponentWithStore(SidebarComponent, { store });
});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index 9c731ae2f68..e98639bf21e 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -1,59 +1,168 @@
import Vue from 'vue';
import component from '~/jobs/components/stages_dropdown.vue';
+import { trimText } from 'spec/helpers/text_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Stages Dropdown', () => {
const Component = Vue.extend(component);
let vm;
- beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: {
- id: 28029444,
- details: {
- status: {
- details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
- group: 'success',
- has_details: true,
- icon: 'status_success',
- label: 'passed',
- text: 'passed',
- tooltip: 'passed',
- },
- },
- path: 'pipeline/28029444',
+ const mockPipelineData = {
+ id: 28029444,
+ iid: 123,
+ details: {
+ status: {
+ details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
+ group: 'success',
+ has_details: true,
+ icon: 'status_success',
+ label: 'passed',
+ text: 'passed',
+ tooltip: 'passed',
},
- stages: [
- {
- name: 'build',
- },
- {
- name: 'test',
- },
- ],
- selectedStage: 'deploy',
+ },
+ path: 'pipeline/28029444',
+ flags: {
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: false,
+ },
+ merge_request: {
+ iid: 1234,
+ path: '/root/detached-merge-request-pipelines/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1234',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
+ ref: {
+ name: 'test-branch',
+ },
+ };
+
+ describe('without a merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ delete pipeline.merge_request;
+ delete pipeline.flags.merge_request_pipeline;
+ delete pipeline.flags.detached_merge_request_pipeline;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [{ name: 'build' }, { name: 'test' }],
+ selectedStage: 'deploy',
+ });
});
- });
- afterEach(() => {
- vm.$destroy();
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
- it('renders pipeline status', () => {
- expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
- });
+ it('renders pipeline status', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
+ });
+
+ it('renders pipeline link', () => {
+ expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
+ 'pipeline/28029444',
+ );
+ });
+
+ it('renders dropdown with stages', () => {
+ expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ });
+
+ it('rendes selected stage', () => {
+ expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ });
- it('renders pipeline link', () => {
- expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
- 'pipeline/28029444',
- );
+ it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
});
- it('renders dropdown with stages', () => {
- expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ describe('with an "attached" merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = true;
+ pipeline.flags.detached_merge_request_pipeline = false;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
+ pipeline.merge_request.iid
+ } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
+
+ it(`renders the correct target branch link`, () => {
+ const actual = vm.$el.querySelector('.js-target-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.target_branch_path);
+ });
});
- it('rendes selected stage', () => {
- expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ describe('with a detached merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = false;
+ pipeline.flags.detached_merge_request_pipeline = true;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
+ pipeline.merge_request.iid
+ } with ${pipeline.merge_request.source_branch}`;
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
+
+ expect(actual).toBe(expected);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
});
});
diff --git a/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js
new file mode 100644
index 00000000000..68fcb321214
--- /dev/null
+++ b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import component from '~/jobs/components/unmet_prerequisites_block.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Unmet Prerequisites Block Job component', () => {
+ const Component = Vue.extend(component);
+ let vm;
+ const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs';
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: true,
+ helpPath,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders an alert with the correct message', () => {
+ const container = vm.$el.querySelector('.js-failed-unmet-prerequisites');
+ const alertMessage =
+ 'This job failed because the necessary resources were not successfully created.';
+
+ expect(container).not.toBeNull();
+ expect(container.innerHTML).toContain(alertMessage);
+ });
+
+ it('renders link to help page', () => {
+ const helpLink = vm.$el.querySelector('.js-help-path');
+
+ expect(helpLink).not.toBeNull();
+ expect(helpLink.innerHTML).toContain('More information');
+ expect(helpLink.getAttribute('href')).toEqual(helpPath);
+ });
+});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 0398f184c0a..88b0bb206ee 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -3,140 +3,6 @@ import { TEST_HOST } from 'spec/test_constants';
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
-export default {
- id: 4757,
- name: 'test',
- build_path: '/root/ci-mock/-/jobs/4757',
- retry_path: '/root/ci-mock/-/jobs/4757/retry',
- cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
- new_issue_path: '/root/ci-mock/issues/new',
- playable: false,
- created_at: threeWeeksAgo.toISOString(),
- updated_at: threeWeeksAgo.toISOString(),
- finished_at: threeWeeksAgo.toISOString(),
- queued: 9.54,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/-/jobs/4757/retry',
- method: 'post',
- },
- },
- coverage: 20,
- erased_at: threeWeeksAgo.toISOString(),
- erased: false,
- duration: 6.785563,
- tags: ['tag'],
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- erase_path: '/root/ci-mock/-/jobs/4757/erase',
- artifacts: [null],
- runner: {
- id: 1,
- description: 'local ci runner',
- edit_path: '/root/ci-mock/runners/1/edit',
- },
- pipeline: {
- id: 140,
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- active: false,
- coverage: null,
- source: 'unknown',
- created_at: '2017-05-24T09:59:58.634Z',
- updated_at: '2017-06-01T17:32:00.062Z',
- path: '/root/ci-mock/pipelines/140',
- flags: {
- latest: true,
- stuck: false,
- yaml_errors: false,
- retryable: false,
- cancelable: false,
- },
- details: {
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/140',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- },
- duration: 6,
- finished_at: '2017-06-01T17:32:00.042Z',
- },
- ref: {
- name: 'abc',
- path: '/root/ci-mock/commits/abc',
- tag: false,
- branch: true,
- },
- commit: {
- id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- short_id: 'c5864777',
- title: 'Add new file',
- created_at: '2017-05-24T10:59:52.000+01:00',
- parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
- message: 'Add new file',
- author_name: 'Root',
- author_email: 'admin@example.com',
- authored_date: '2017-05-24T10:59:52.000+01:00',
- committer_name: 'Root',
- committer_email: 'admin@example.com',
- committed_date: '2017-05-24T10:59:52.000+01:00',
- author: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- author_gravatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- commit_url:
- 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- },
- },
- metadata: {
- timeout_human_readable: '1m 40s',
- timeout_source: 'runner',
- },
- merge_request: {
- iid: 2,
- path: '/root/ci-mock/merge_requests/2',
- },
- raw_path: '/root/ci-mock/builds/4757/raw',
- has_trace: true,
-};
-
export const stages = [
{
name: 'build',
@@ -678,7 +544,7 @@ export const stages = [
icon: 'status_warning',
text: 'failed',
label: 'failed (allowed to fail)',
- group: 'failed_with_warnings',
+ group: 'failed-with-warnings',
tooltip: 'failed - (unknown failure) (allowed to fail)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
@@ -710,7 +576,7 @@ export const stages = [
icon: 'status_warning',
text: 'failed',
label: 'failed (allowed to fail)',
- group: 'failed_with_warnings',
+ group: 'failed-with-warnings',
tooltip: 'failed - (unknown failure) (allowed to fail)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
@@ -738,7 +604,7 @@ export const stages = [
icon: 'status_warning',
text: 'passed',
label: 'passed with warnings',
- group: 'success_with_warnings',
+ group: 'success-with-warnings',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#test',
@@ -1043,6 +909,168 @@ export const stages = [
},
];
+export default {
+ id: 4757,
+ name: 'test',
+ build_path: '/root/ci-mock/-/jobs/4757',
+ retry_path: '/root/ci-mock/-/jobs/4757/retry',
+ cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
+ new_issue_path: '/root/ci-mock/issues/new',
+ playable: false,
+ created_at: threeWeeksAgo.toISOString(),
+ updated_at: threeWeeksAgo.toISOString(),
+ finished_at: threeWeeksAgo.toISOString(),
+ queued: 9.54,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/-/jobs/4757/retry',
+ method: 'post',
+ },
+ },
+ coverage: 20,
+ erased_at: threeWeeksAgo.toISOString(),
+ erased: false,
+ duration: 6.785563,
+ tags: ['tag'],
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ erase_path: '/root/ci-mock/-/jobs/4757/erase',
+ artifacts: [null],
+ runner: {
+ id: 1,
+ description: 'local ci runner',
+ edit_path: '/root/ci-mock/runners/1/edit',
+ },
+ pipeline: {
+ id: 140,
+ iid: 13,
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ active: false,
+ coverage: null,
+ source: 'unknown',
+ created_at: '2017-05-24T09:59:58.634Z',
+ updated_at: '2017-06-01T17:32:00.062Z',
+ path: '/root/ci-mock/pipelines/140',
+ flags: {
+ latest: true,
+ stuck: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: false,
+ },
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/140',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ duration: 6,
+ finished_at: '2017-06-01T17:32:00.042Z',
+ stages: [
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build',
+ name: 'build',
+ path: '/jashkenas/underscore/pipelines/16#build',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ },
+ title: 'build: passed',
+ },
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test',
+ name: 'test',
+ path: '/jashkenas/underscore/pipelines/16#test',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ },
+ title: 'test: passed with warnings',
+ },
+ ],
+ },
+ ref: {
+ name: 'abc',
+ path: '/root/ci-mock/commits/abc',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ short_id: 'c5864777',
+ title: 'Add new file',
+ created_at: '2017-05-24T10:59:52.000+01:00',
+ parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
+ message: 'Add new file',
+ author_name: 'Root',
+ author_email: 'admin@example.com',
+ authored_date: '2017-05-24T10:59:52.000+01:00',
+ committer_name: 'Root',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-05-24T10:59:52.000+01:00',
+ author: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ },
+ },
+ metadata: {
+ timeout_human_readable: '1m 40s',
+ timeout_source: 'runner',
+ },
+ merge_request: {
+ iid: 2,
+ path: '/root/ci-mock/merge_requests/2',
+ },
+ raw_path: '/root/ci-mock/builds/4757/raw',
+ has_trace: true,
+};
+
export const jobsInStage = {
name: 'build',
title: 'build: running',
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
index 77b44995b12..7b96df85b82 100644
--- a/spec/javascripts/jobs/store/actions_spec.js
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -16,10 +16,6 @@ import {
stopPollingTrace,
receiveTraceSuccess,
receiveTraceError,
- requestStages,
- fetchStages,
- receiveStagesSuccess,
- receiveStagesError,
requestJobsForStage,
fetchJobsForStage,
receiveJobsForStageSuccess,
@@ -307,107 +303,6 @@ describe('Job State actions', () => {
});
});
- describe('requestStages', () => {
- it('should commit REQUEST_STAGES mutation ', done => {
- testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done);
- });
- });
-
- describe('fetchStages', () => {
- let mock;
-
- beforeEach(() => {
- mockedState.job.pipeline = {
- path: `${TEST_HOST}/endpoint`,
- };
- mockedState.selectedStage = 'deploy';
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('success', () => {
- it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
- mock
- .onGet(`${TEST_HOST}/endpoint.json`)
- .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } });
-
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- payload: [{ name: 'build' }, { name: 'deploy' }],
- type: 'receiveStagesSuccess',
- },
- {
- payload: { name: 'deploy' },
- type: 'fetchJobsForStage',
- },
- ],
- done,
- );
- });
- });
-
- describe('error', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
- });
-
- it('dispatches requestStages and receiveStagesError ', done => {
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- type: 'receiveStagesError',
- },
- ],
- done,
- );
- });
- });
- });
-
- describe('receiveStagesSuccess', () => {
- it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => {
- testAction(
- receiveStagesSuccess,
- {},
- mockedState,
- [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }],
- [],
- done,
- );
- });
- });
-
- describe('receiveStagesError', () => {
- it('should commit RECEIVE_STAGES_ERROR mutation ', done => {
- testAction(
- receiveStagesError,
- null,
- mockedState,
- [{ type: types.RECEIVE_STAGES_ERROR }],
- [],
- done,
- );
- });
- });
-
describe('requestJobsForStage', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction(
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
deleted file mode 100644
index 7931b2af79f..00000000000
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ /dev/null
@@ -1,188 +0,0 @@
-import * as getters from '~/jobs/store/getters';
-import state from '~/jobs/store/state';
-
-describe('Job Store Getters', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- });
-
- describe('headerTime', () => {
- describe('when the job has started key', () => {
- it('returns started key', () => {
- const started = '2018-08-31T16:20:49.023Z';
- localState.job.started = started;
-
- expect(getters.headerTime(localState)).toEqual(started);
- });
- });
-
- describe('when the job does not have started key', () => {
- it('returns created_at key', () => {
- const created = '2018-08-31T16:20:49.023Z';
- localState.job.created_at = created;
-
- expect(getters.headerTime(localState)).toEqual(created);
- });
- });
- });
-
- describe('shouldRenderCalloutMessage', () => {
- describe('with status and callout message', () => {
- it('returns true', () => {
- localState.job.callout_message = 'Callout message';
- localState.job.status = { icon: 'passed' };
-
- expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true);
- });
- });
-
- describe('without status & with callout message', () => {
- it('returns false', () => {
- localState.job.callout_message = 'Callout message';
-
- expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
- });
- });
-
- describe('with status & without callout message', () => {
- it('returns false', () => {
- localState.job.status = { icon: 'passed' };
-
- expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
- });
- });
- });
-
- describe('shouldRenderTriggeredLabel', () => {
- describe('when started equals null', () => {
- it('returns false', () => {
- localState.job.started = null;
-
- expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false);
- });
- });
-
- describe('when started equals string', () => {
- it('returns true', () => {
- localState.job.started = '2018-08-31T16:20:49.023Z';
-
- expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true);
- });
- });
- });
-
- describe('hasEnvironment', () => {
- describe('without `deployment_status`', () => {
- it('returns false', () => {
- expect(getters.hasEnvironment(localState)).toEqual(false);
- });
- });
-
- describe('with an empty object for `deployment_status`', () => {
- it('returns false', () => {
- localState.job.deployment_status = {};
-
- expect(getters.hasEnvironment(localState)).toEqual(false);
- });
- });
-
- describe('when `deployment_status` is defined and not empty', () => {
- it('returns true', () => {
- localState.job.deployment_status = {
- status: 'creating',
- environment: {
- last_deployment: {},
- },
- };
-
- expect(getters.hasEnvironment(localState)).toEqual(true);
- });
- });
- });
-
- describe('hasTrace', () => {
- describe('when has_trace is true', () => {
- it('returns true', () => {
- localState.job.has_trace = true;
- localState.job.status = {};
-
- expect(getters.hasTrace(localState)).toEqual(true);
- });
- });
-
- describe('when job is running', () => {
- it('returns true', () => {
- localState.job.has_trace = false;
- localState.job.status = { group: 'running' };
-
- expect(getters.hasTrace(localState)).toEqual(true);
- });
- });
-
- describe('when has_trace is false and job is not running', () => {
- it('returns false', () => {
- localState.job.has_trace = false;
- localState.job.status = { group: 'pending' };
-
- expect(getters.hasTrace(localState)).toEqual(false);
- });
- });
- });
-
- describe('emptyStateIllustration', () => {
- describe('with defined illustration', () => {
- it('returns the state illustration object', () => {
- localState.job.status = {
- illustration: {
- path: 'foo',
- },
- };
-
- expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' });
- });
- });
-
- describe('when illustration is not defined', () => {
- it('returns an empty object', () => {
- expect(getters.emptyStateIllustration(localState)).toEqual({});
- });
- });
- });
-
- describe('hasRunnersForProject', () => {
- describe('with available and offline runners', () => {
- it('returns true', () => {
- localState.job.runners = {
- available: true,
- online: false,
- };
-
- expect(getters.hasRunnersForProject(localState)).toEqual(true);
- });
- });
-
- describe('with non available runners', () => {
- it('returns false', () => {
- localState.job.runners = {
- available: false,
- online: false,
- };
-
- expect(getters.hasRunnersForProject(localState)).toEqual(false);
- });
- });
-
- describe('with online runners', () => {
- it('returns false', () => {
- localState.job.runners = {
- available: false,
- online: true,
- };
-
- expect(getters.hasRunnersForProject(localState)).toEqual(false);
- });
- });
- });
-});
diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js
deleted file mode 100644
index d7908efcf13..00000000000
--- a/spec/javascripts/jobs/store/mutations_spec.js
+++ /dev/null
@@ -1,230 +0,0 @@
-import state from '~/jobs/store/state';
-import mutations from '~/jobs/store/mutations';
-import * as types from '~/jobs/store/mutation_types';
-
-describe('Jobs Store Mutations', () => {
- let stateCopy;
-
- const html =
- 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
-
- beforeEach(() => {
- stateCopy = state();
- });
-
- describe('SET_JOB_ENDPOINT', () => {
- it('should set jobEndpoint', () => {
- mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json');
-
- expect(stateCopy.jobEndpoint).toEqual('job/21312321.json');
- });
- });
-
- describe('HIDE_SIDEBAR', () => {
- it('should set isSidebarOpen to false', () => {
- mutations[types.HIDE_SIDEBAR](stateCopy);
-
- expect(stateCopy.isSidebarOpen).toEqual(false);
- });
- });
-
- describe('SHOW_SIDEBAR', () => {
- it('should set isSidebarOpen to true', () => {
- mutations[types.SHOW_SIDEBAR](stateCopy);
-
- expect(stateCopy.isSidebarOpen).toEqual(true);
- });
- });
-
- describe('RECEIVE_TRACE_SUCCESS', () => {
- describe('when trace has state', () => {
- it('sets traceState', () => {
- const stateLog =
- 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0=';
- mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
- state: stateLog,
- });
-
- expect(stateCopy.traceState).toEqual(stateLog);
- });
- });
-
- describe('when traceSize is smaller than the total size', () => {
- it('sets isTraceSizeVisible to true', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 });
-
- expect(stateCopy.isTraceSizeVisible).toEqual(true);
- });
- });
-
- describe('when traceSize is bigger than the total size', () => {
- it('sets isTraceSizeVisible to false', () => {
- const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 });
-
- mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 });
-
- expect(copy.isTraceSizeVisible).toEqual(false);
- });
- });
-
- it('sets trace, trace size and isTraceComplete', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
- append: true,
- html,
- size: 511846,
- complete: true,
- });
-
- expect(stateCopy.trace).toEqual(html);
- expect(stateCopy.traceSize).toEqual(511846);
- expect(stateCopy.isTraceComplete).toEqual(true);
- });
- });
-
- describe('STOP_POLLING_TRACE', () => {
- it('sets isTraceComplete to true', () => {
- mutations[types.STOP_POLLING_TRACE](stateCopy);
-
- expect(stateCopy.isTraceComplete).toEqual(true);
- });
- });
-
- describe('RECEIVE_TRACE_ERROR', () => {
- it('resets trace state and sets error to true', () => {
- mutations[types.RECEIVE_TRACE_ERROR](stateCopy);
-
- expect(stateCopy.isTraceComplete).toEqual(true);
- });
- });
-
- describe('REQUEST_JOB', () => {
- it('sets isLoading to true', () => {
- mutations[types.REQUEST_JOB](stateCopy);
-
- expect(stateCopy.isLoading).toEqual(true);
- });
- });
-
- describe('RECEIVE_JOB_SUCCESS', () => {
- it('sets is loading to false', () => {
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
-
- expect(stateCopy.isLoading).toEqual(false);
- });
-
- it('sets hasError to false', () => {
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
-
- expect(stateCopy.hasError).toEqual(false);
- });
-
- it('sets job data', () => {
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
-
- expect(stateCopy.job).toEqual({ id: 1312321 });
- });
-
- it('sets selectedStage when the selectedStage is empty', () => {
- expect(stateCopy.selectedStage).toEqual('');
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
-
- expect(stateCopy.selectedStage).toEqual('deploy');
- });
-
- it('does not set selectedStage when the selectedStage is not More', () => {
- stateCopy.selectedStage = 'notify';
-
- expect(stateCopy.selectedStage).toEqual('notify');
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
-
- expect(stateCopy.selectedStage).toEqual('notify');
- });
- });
-
- describe('RECEIVE_JOB_ERROR', () => {
- it('resets job data', () => {
- mutations[types.RECEIVE_JOB_ERROR](stateCopy);
-
- expect(stateCopy.isLoading).toEqual(false);
- expect(stateCopy.job).toEqual({});
- });
- });
-
- describe('REQUEST_STAGES', () => {
- it('sets isLoadingStages to true', () => {
- mutations[types.REQUEST_STAGES](stateCopy);
-
- expect(stateCopy.isLoadingStages).toEqual(true);
- });
- });
-
- describe('RECEIVE_STAGES_SUCCESS', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('sets stages', () => {
- expect(stateCopy.stages).toEqual([{ name: 'build' }]);
- });
- });
-
- describe('RECEIVE_STAGES_ERROR', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_ERROR](stateCopy);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('resets stages', () => {
- expect(stateCopy.stages).toEqual([]);
- });
- });
-
- describe('REQUEST_JOBS_FOR_STAGE', () => {
- it('sets isLoadingStages to true', () => {
- mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
-
- expect(stateCopy.isLoadingJobs).toEqual(true);
- });
-
- it('sets selectedStage', () => {
- mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
-
- expect(stateCopy.selectedStage).toEqual('deploy');
- });
- });
-
- describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]);
- });
-
- it('sets isLoadingJobs to false', () => {
- expect(stateCopy.isLoadingJobs).toEqual(false);
- });
-
- it('sets jobs', () => {
- expect(stateCopy.jobs).toEqual([{ name: 'karma' }]);
- });
- });
-
- describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy);
- });
-
- it('sets isLoadingJobs to false', () => {
- expect(stateCopy.isLoadingJobs).toEqual(false);
- });
-
- it('resets jobs', () => {
- expect(stateCopy.jobs).toEqual([]);
- });
- });
-});
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index e5678ee5379..ccf439aac74 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -16,10 +16,10 @@ let saveLabelCount = 0;
let mock;
describe('Issue dropdown sidebar', () => {
- preloadFixtures('static/issue_sidebar_label.html.raw');
+ preloadFixtures('static/issue_sidebar_label.html');
beforeEach(() => {
- loadFixtures('static/issue_sidebar_label.html.raw');
+ loadFixtures('static/issue_sidebar_label.html');
mock = new MockAdapter(axios);
diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js
deleted file mode 100644
index acfdc885032..00000000000
--- a/spec/javascripts/labels_select_spec.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import $ from 'jquery';
-import LabelsSelect from '~/labels_select';
-
-const mockUrl = '/foo/bar/url';
-
-const mockLabels = [
- {
- id: 26,
- title: 'Foo Label',
- description: 'Foobar',
- color: '#BADA55',
- text_color: '#FFFFFF',
- },
-];
-
-describe('LabelsSelect', () => {
- describe('getLabelTemplate', () => {
- const label = mockLabels[0];
- let $labelEl;
-
- beforeEach(() => {
- $labelEl = $(
- LabelsSelect.getLabelTemplate({
- labels: mockLabels,
- issueUpdateURL: mockUrl,
- }),
- );
- });
-
- it('generated label item template has correct label URL', () => {
- expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label');
- });
-
- it('generated label item template has correct label title', () => {
- expect($labelEl.find('span.label').text()).toBe(label.title);
- });
-
- it('generated label item template has label description as title attribute', () => {
- expect($labelEl.find('span.label').attr('title')).toBe(label.description);
- });
-
- it('generated label item template has correct label styles', () => {
- expect($labelEl.find('span.label').attr('style')).toBe(
- `background-color: ${label.color}; color: ${label.text_color};`,
- );
- });
-
- it('generated label item has a badge class', () => {
- expect($labelEl.find('span').hasClass('badge')).toEqual(true);
- });
- });
-});
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
index cbdc1644430..f3fb792c62d 100644
--- a/spec/javascripts/lazy_loader_spec.js
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -11,11 +11,11 @@ const execImmediately = callback => {
describe('LazyLoader', function() {
let lazyLoader = null;
- preloadFixtures('issues/issue_with_comment.html.raw');
+ preloadFixtures('issues/issue_with_comment.html');
describe('without IntersectionObserver', () => {
beforeEach(function() {
- loadFixtures('issues/issue_with_comment.html.raw');
+ loadFixtures('issues/issue_with_comment.html');
lazyLoader = new LazyLoader({
observerNode: 'foobar',
@@ -131,7 +131,7 @@ describe('LazyLoader', function() {
describe('with IntersectionObserver', () => {
beforeEach(function() {
- loadFixtures('issues/issue_with_comment.html.raw');
+ loadFixtures('issues/issue_with_comment.html');
lazyLoader = new LazyLoader({
observerNode: 'foobar',
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0bb43c94f6a..296ee85089f 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
+import breakpointInstance from '~/breakpoints';
const PIXEL_TOLERANCE = 0.2;
@@ -380,6 +381,38 @@ describe('common_utils', () => {
});
});
+ describe('contentTop', () => {
+ it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => {
+ spyOn(breakpointInstance, 'isDesktop').and.returnValue(false);
+
+ setFixtures(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ expect(commonUtils.contentTop()).toBe(0);
+ });
+
+ it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => {
+ spyOn(breakpointInstance, 'isDesktop').and.returnValue(true);
+
+ setFixtures(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ expect(commonUtils.contentTop()).toBe(18);
+ });
+ });
+
describe('parseBoolean', () => {
const { parseBoolean } = commonUtils;
@@ -819,20 +852,20 @@ describe('common_utils', () => {
describe('roundOffFloat', () => {
it('Rounds off decimal places of a float number with provided precision', () => {
- expect(commonUtils.roundOffFloat(3.141592, 3)).toBe(3.142);
+ expect(commonUtils.roundOffFloat(3.141592, 3)).toBeCloseTo(3.142);
});
it('Rounds off a float number to a whole number when provided precision is zero', () => {
- expect(commonUtils.roundOffFloat(3.141592, 0)).toBe(3);
- expect(commonUtils.roundOffFloat(3.5, 0)).toBe(4);
+ expect(commonUtils.roundOffFloat(3.141592, 0)).toBeCloseTo(3);
+ expect(commonUtils.roundOffFloat(3.5, 0)).toBeCloseTo(4);
});
it('Rounds off float number to nearest 0, 10, 100, 1000 and so on when provided precision is below 0', () => {
- expect(commonUtils.roundOffFloat(34567.14159, -1)).toBe(34570);
- expect(commonUtils.roundOffFloat(34567.14159, -2)).toBe(34600);
- expect(commonUtils.roundOffFloat(34567.14159, -3)).toBe(35000);
- expect(commonUtils.roundOffFloat(34567.14159, -4)).toBe(30000);
- expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0);
+ expect(commonUtils.roundOffFloat(34567.14159, -1)).toBeCloseTo(34570);
+ expect(commonUtils.roundOffFloat(34567.14159, -2)).toBeCloseTo(34600);
+ expect(commonUtils.roundOffFloat(34567.14159, -3)).toBeCloseTo(35000);
+ expect(commonUtils.roundOffFloat(34567.14159, -4)).toBeCloseTo(30000);
+ expect(commonUtils.roundOffFloat(34567.14159, -5)).toBeCloseTo(0);
});
});
@@ -861,4 +894,14 @@ describe('common_utils', () => {
expect(commonUtils.isInViewport(el)).toBe(false);
});
});
+
+ describe('isScopedLabel', () => {
+ it('returns true when `::` is present in title', () => {
+ expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true);
+ });
+
+ it('returns false when `::` is not present', () => {
+ expect(commonUtils.isScopedLabel({ title: 'foobar' })).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/javascripts/lib/utils/datetime_utility_spec.js
deleted file mode 100644
index 5327ec9d2a0..00000000000
--- a/spec/javascripts/lib/utils/datetime_utility_spec.js
+++ /dev/null
@@ -1,416 +0,0 @@
-import * as datetimeUtility from '~/lib/utils/datetime_utility';
-
-describe('Date time utils', () => {
- describe('timeFor', () => {
- it('returns `past due` when in past', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() - 1);
-
- expect(datetimeUtility.timeFor(date)).toBe('Past due');
- });
-
- it('returns remaining time when in the future', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() + 1);
-
- // Add a day to prevent a transient error. If date is even 1 second
- // short of a full year, timeFor will return '11 months remaining'
- date.setDate(date.getDate() + 1);
-
- expect(datetimeUtility.timeFor(date)).toBe('1 year remaining');
- });
- });
-
- describe('get day name', () => {
- it('should return Sunday', () => {
- const day = datetimeUtility.getDayName(new Date('07/17/2016'));
-
- expect(day).toBe('Sunday');
- });
-
- it('should return Monday', () => {
- const day = datetimeUtility.getDayName(new Date('07/18/2016'));
-
- expect(day).toBe('Monday');
- });
-
- it('should return Tuesday', () => {
- const day = datetimeUtility.getDayName(new Date('07/19/2016'));
-
- expect(day).toBe('Tuesday');
- });
-
- it('should return Wednesday', () => {
- const day = datetimeUtility.getDayName(new Date('07/20/2016'));
-
- expect(day).toBe('Wednesday');
- });
-
- it('should return Thursday', () => {
- const day = datetimeUtility.getDayName(new Date('07/21/2016'));
-
- expect(day).toBe('Thursday');
- });
-
- it('should return Friday', () => {
- const day = datetimeUtility.getDayName(new Date('07/22/2016'));
-
- expect(day).toBe('Friday');
- });
-
- it('should return Saturday', () => {
- const day = datetimeUtility.getDayName(new Date('07/23/2016'));
-
- expect(day).toBe('Saturday');
- });
- });
-
- describe('get day difference', () => {
- it('should return 7', () => {
- const firstDay = new Date('07/01/2016');
- const secondDay = new Date('07/08/2016');
- const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
-
- expect(difference).toBe(7);
- });
-
- it('should return 31', () => {
- const firstDay = new Date('07/01/2016');
- const secondDay = new Date('08/01/2016');
- const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
-
- expect(difference).toBe(31);
- });
-
- it('should return 365', () => {
- const firstDay = new Date('07/02/2015');
- const secondDay = new Date('07/01/2016');
- const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
-
- expect(difference).toBe(365);
- });
- });
-});
-
-describe('timeIntervalInWords', () => {
- it('should return string with number of minutes and seconds', () => {
- expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
- expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
- expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
- expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
- });
-});
-
-describe('dateInWords', () => {
- const date = new Date('07/01/2016');
-
- it('should return date in words', () => {
- expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016');
- });
-
- it('should return abbreviated month name', () => {
- expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
- });
-
- it('should return date in words without year', () => {
- expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1');
- });
-});
-
-describe('monthInWords', () => {
- const date = new Date('2017-01-20');
-
- it('returns month name from provided date', () => {
- expect(datetimeUtility.monthInWords(date)).toBe('January');
- });
-
- it('returns abbreviated month name from provided date', () => {
- expect(datetimeUtility.monthInWords(date, true)).toBe('Jan');
- });
-});
-
-describe('totalDaysInMonth', () => {
- it('returns number of days in a month for given date', () => {
- // 1st Feb, 2016 (leap year)
- expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29);
-
- // 1st Feb, 2017
- expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28);
-
- // 1st Jan, 2017
- expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31);
- });
-});
-
-describe('getSundays', () => {
- it('returns array of dates representing all Sundays of the month', () => {
- // December, 2017 (it has 5 Sundays)
- const dateOfSundays = [3, 10, 17, 24, 31];
- const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1));
-
- expect(sundays.length).toBe(5);
- sundays.forEach((sunday, index) => {
- expect(sunday.getDate()).toBe(dateOfSundays[index]);
- });
- });
-});
-
-describe('getTimeframeWindowFrom', () => {
- it('returns array of date objects upto provided length (positive number) into the future starting from provided startDate', () => {
- const startDate = new Date(2018, 0, 1);
- const mockTimeframe = [
- new Date(2018, 0, 1),
- new Date(2018, 1, 1),
- new Date(2018, 2, 1),
- new Date(2018, 3, 1),
- new Date(2018, 4, 31),
- ];
- const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5);
-
- expect(timeframe.length).toBe(5);
- timeframe.forEach((timeframeItem, index) => {
- expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
- expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
- expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
- });
- });
-
- it('returns array of date objects upto provided length (negative number) into the past starting from provided startDate', () => {
- const startDate = new Date(2018, 0, 1);
- const mockTimeframe = [
- new Date(2018, 0, 1),
- new Date(2017, 11, 1),
- new Date(2017, 10, 1),
- new Date(2017, 9, 1),
- new Date(2017, 8, 1),
- ];
- const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, -5);
-
- expect(timeframe.length).toBe(5);
- timeframe.forEach((timeframeItem, index) => {
- expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
- expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
- expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
- });
- });
-});
-
-describe('formatTime', () => {
- const expectedTimestamps = [
- [0, '00:00:00'],
- [1000, '00:00:01'],
- [42000, '00:00:42'],
- [121000, '00:02:01'],
- [10921000, '03:02:01'],
- [108000000, '30:00:00'],
- ];
-
- expectedTimestamps.forEach(([milliseconds, expectedTimestamp]) => {
- it(`formats ${milliseconds}ms as ${expectedTimestamp}`, () => {
- expect(datetimeUtility.formatTime(milliseconds)).toBe(expectedTimestamp);
- });
- });
-});
-
-describe('datefix', () => {
- describe('pad', () => {
- it('should add a 0 when length is smaller than 2', () => {
- expect(datetimeUtility.pad(2)).toEqual('02');
- });
-
- it('should not add a zero when length matches the default', () => {
- expect(datetimeUtility.pad(12)).toEqual('12');
- });
-
- it('should add a 0 when length is smaller than the provided', () => {
- expect(datetimeUtility.pad(12, 3)).toEqual('012');
- });
- });
-
- describe('parsePikadayDate', () => {
- // removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834
- });
-
- describe('pikadayToString', () => {
- it('should format a UTC date into yyyy-mm-dd format', () => {
- expect(datetimeUtility.pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
- });
- });
-});
-
-describe('prettyTime methods', () => {
- const assertTimeUnits = (obj, minutes, hours, days, weeks) => {
- expect(obj.minutes).toBe(minutes);
- expect(obj.hours).toBe(hours);
- expect(obj.days).toBe(days);
- expect(obj.weeks).toBe(weeks);
- };
-
- describe('parseSeconds', () => {
- it('should correctly parse a negative value', () => {
- const zeroSeconds = datetimeUtility.parseSeconds(-1000);
-
- assertTimeUnits(zeroSeconds, 16, 0, 0, 0);
- });
-
- it('should correctly parse a zero value', () => {
- const zeroSeconds = datetimeUtility.parseSeconds(0);
-
- assertTimeUnits(zeroSeconds, 0, 0, 0, 0);
- });
-
- it('should correctly parse a small non-zero second values', () => {
- const subOneMinute = datetimeUtility.parseSeconds(10);
- const aboveOneMinute = datetimeUtility.parseSeconds(100);
- const manyMinutes = datetimeUtility.parseSeconds(1000);
-
- assertTimeUnits(subOneMinute, 0, 0, 0, 0);
- assertTimeUnits(aboveOneMinute, 1, 0, 0, 0);
- assertTimeUnits(manyMinutes, 16, 0, 0, 0);
- });
-
- it('should correctly parse large second values', () => {
- const aboveOneHour = datetimeUtility.parseSeconds(4800);
- const aboveOneDay = datetimeUtility.parseSeconds(110000);
- const aboveOneWeek = datetimeUtility.parseSeconds(25000000);
-
- assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
- assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
- assertTimeUnits(aboveOneWeek, 26, 0, 3, 173);
- });
-
- it('should correctly accept a custom param for hoursPerDay', () => {
- const config = { hoursPerDay: 24 };
-
- const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
- const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
- const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
-
- assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
- assertTimeUnits(aboveOneDay, 33, 6, 1, 0);
- assertTimeUnits(aboveOneWeek, 26, 8, 4, 57);
- });
-
- it('should correctly accept a custom param for daysPerWeek', () => {
- const config = { daysPerWeek: 7 };
-
- const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
- const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
- const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
-
- assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
- assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
- assertTimeUnits(aboveOneWeek, 26, 0, 0, 124);
- });
-
- it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => {
- const config = { daysPerWeek: 55, hoursPerDay: 14 };
-
- const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
- const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
- const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
-
- assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
- assertTimeUnits(aboveOneDay, 33, 2, 2, 0);
- assertTimeUnits(aboveOneWeek, 26, 0, 1, 9);
- });
- });
-
- describe('stringifyTime', () => {
- it('should stringify values with all non-zero units', () => {
- const timeObject = {
- weeks: 1,
- days: 4,
- hours: 7,
- minutes: 20,
- };
-
- const timeString = datetimeUtility.stringifyTime(timeObject);
-
- expect(timeString).toBe('1w 4d 7h 20m');
- });
-
- it('should stringify values with some non-zero units', () => {
- const timeObject = {
- weeks: 0,
- days: 4,
- hours: 0,
- minutes: 20,
- };
-
- const timeString = datetimeUtility.stringifyTime(timeObject);
-
- expect(timeString).toBe('4d 20m');
- });
-
- it('should stringify values with no non-zero units', () => {
- const timeObject = {
- weeks: 0,
- days: 0,
- hours: 0,
- minutes: 0,
- };
-
- const timeString = datetimeUtility.stringifyTime(timeObject);
-
- expect(timeString).toBe('0m');
- });
-
- it('should return non-condensed representation of time object', () => {
- const timeObject = { weeks: 1, days: 0, hours: 1, minutes: 0 };
-
- expect(datetimeUtility.stringifyTime(timeObject, true)).toEqual('1 week 1 hour');
- });
- });
-
- describe('abbreviateTime', () => {
- it('should abbreviate stringified times for weeks', () => {
- const fullTimeString = '1w 3d 4h 5m';
-
- expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w');
- });
-
- it('should abbreviate stringified times for non-weeks', () => {
- const fullTimeString = '0w 3d 4h 5m';
-
- expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d');
- });
- });
-});
-
-describe('calculateRemainingMilliseconds', () => {
- beforeEach(() => {
- spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime());
- });
-
- it('calculates the remaining time for a given end date', () => {
- const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-04T01:44:03Z');
-
- expect(milliseconds).toBe(3723000);
- });
-
- it('returns 0 if the end date has passed', () => {
- const milliseconds = datetimeUtility.calculateRemainingMilliseconds('2063-04-03T00:00:00Z');
-
- expect(milliseconds).toBe(0);
- });
-});
-
-describe('newDate', () => {
- it('returns new date instance from existing date instance', () => {
- const initialDate = new Date(2019, 0, 1);
- const copiedDate = datetimeUtility.newDate(initialDate);
-
- expect(copiedDate.getTime()).toBe(initialDate.getTime());
-
- initialDate.setMonth(initialDate.getMonth() + 1);
-
- expect(copiedDate.getTime()).not.toBe(initialDate.getTime());
- });
-
- it('returns date instance when provided date param is not of type date or is undefined', () => {
- const initialDate = datetimeUtility.newDate();
-
- expect(initialDate instanceof Date).toBe(true);
- });
-});
diff --git a/spec/javascripts/lib/utils/higlight_spec.js b/spec/javascripts/lib/utils/higlight_spec.js
new file mode 100644
index 00000000000..638bbf65ae9
--- /dev/null
+++ b/spec/javascripts/lib/utils/higlight_spec.js
@@ -0,0 +1,43 @@
+import highlight from '~/lib/utils/highlight';
+
+describe('highlight', () => {
+ it(`should appropriately surround substring matches`, () => {
+ const expected = 'g<b>i</b><b>t</b>lab';
+
+ expect(highlight('gitlab', 'it')).toBe(expected);
+ });
+
+ it(`should return an empty string in the case of invalid inputs`, () => {
+ [null, undefined].forEach(input => {
+ expect(highlight(input, 'match')).toBe('');
+ });
+ });
+
+ it(`should return the original value if match is null, undefined, or ''`, () => {
+ [null, undefined].forEach(match => {
+ expect(highlight('gitlab', match)).toBe('gitlab');
+ });
+ });
+
+ it(`should highlight matches in non-string inputs`, () => {
+ const expected = '123<b>4</b><b>5</b>6';
+
+ expect(highlight(123456, 45)).toBe(expected);
+ });
+
+ it(`should sanitize the input string before highlighting matches`, () => {
+ const expected = 'hello <b>w</b>orld';
+
+ expect(highlight('hello <b>world</b>', 'w')).toBe(expected);
+ });
+
+ it(`should not highlight anything if no matches are found`, () => {
+ expect(highlight('gitlab', 'hello')).toBe('gitlab');
+ });
+
+ it(`should allow wrapping elements to be customized`, () => {
+ const expected = '1<hello>2</hello>3';
+
+ expect(highlight('123', '2', '<hello>', '</hello>')).toBe(expected);
+ });
+});
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
deleted file mode 100644
index 94c6214c86a..00000000000
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import {
- formatRelevantDigits,
- bytesToKiB,
- bytesToMiB,
- bytesToGiB,
- numberToHumanSize,
-} from '~/lib/utils/number_utils';
-
-describe('Number Utils', () => {
- describe('formatRelevantDigits', () => {
- it('returns an empty string when the number is NaN', () => {
- expect(formatRelevantDigits('fail')).toBe('');
- });
-
- it('returns 4 decimals when there is 4 plus digits to the left', () => {
- const formattedNumber = formatRelevantDigits('1000.1234567');
- const rightFromDecimal = formattedNumber.split('.')[1];
- const leftFromDecimal = formattedNumber.split('.')[0];
-
- expect(rightFromDecimal.length).toBe(4);
- expect(leftFromDecimal.length).toBe(4);
- });
-
- it('returns 3 decimals when there is 1 digit to the left', () => {
- const formattedNumber = formatRelevantDigits('0.1234567');
- const rightFromDecimal = formattedNumber.split('.')[1];
- const leftFromDecimal = formattedNumber.split('.')[0];
-
- expect(rightFromDecimal.length).toBe(3);
- expect(leftFromDecimal.length).toBe(1);
- });
-
- it('returns 2 decimals when there is 2 digits to the left', () => {
- const formattedNumber = formatRelevantDigits('10.1234567');
- const rightFromDecimal = formattedNumber.split('.')[1];
- const leftFromDecimal = formattedNumber.split('.')[0];
-
- expect(rightFromDecimal.length).toBe(2);
- expect(leftFromDecimal.length).toBe(2);
- });
-
- it('returns 1 decimal when there is 3 digits to the left', () => {
- const formattedNumber = formatRelevantDigits('100.1234567');
- const rightFromDecimal = formattedNumber.split('.')[1];
- const leftFromDecimal = formattedNumber.split('.')[0];
-
- expect(rightFromDecimal.length).toBe(1);
- expect(leftFromDecimal.length).toBe(3);
- });
- });
-
- describe('bytesToKiB', () => {
- it('calculates KiB for the given bytes', () => {
- expect(bytesToKiB(1024)).toEqual(1);
- expect(bytesToKiB(1000)).toEqual(0.9765625);
- });
- });
-
- describe('bytesToMiB', () => {
- it('calculates MiB for the given bytes', () => {
- expect(bytesToMiB(1048576)).toEqual(1);
- expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
- });
- });
-
- describe('bytesToGiB', () => {
- it('calculates GiB for the given bytes', () => {
- expect(bytesToGiB(1073741824)).toEqual(1);
- expect(bytesToGiB(10737418240)).toEqual(10);
- });
- });
-
- describe('numberToHumanSize', () => {
- it('should return bytes', () => {
- expect(numberToHumanSize(654)).toEqual('654 bytes');
- });
-
- it('should return KiB', () => {
- expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
- });
-
- it('should return MiB', () => {
- expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
- });
-
- it('should return GiB', () => {
- expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
- });
- });
-});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
deleted file mode 100644
index 0a266b19ea5..00000000000
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import * as textUtils from '~/lib/utils/text_utility';
-
-describe('text_utility', () => {
- describe('addDelimiter', () => {
- it('should add a delimiter to the given string', () => {
- expect(textUtils.addDelimiter('1234')).toEqual('1,234');
- expect(textUtils.addDelimiter('222222')).toEqual('222,222');
- });
-
- it('should not add a delimiter if string contains no numbers', () => {
- expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa');
- });
- });
-
- describe('highCountTrim', () => {
- it('returns 99+ for count >= 100', () => {
- expect(textUtils.highCountTrim(105)).toBe('99+');
- expect(textUtils.highCountTrim(100)).toBe('99+');
- });
-
- it('returns exact number for count < 100', () => {
- expect(textUtils.highCountTrim(45)).toBe(45);
- });
- });
-
- describe('capitalizeFirstCharacter', () => {
- it('returns string with first letter capitalized', () => {
- expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
- expect(textUtils.highCountTrim(105)).toBe('99+');
- expect(textUtils.highCountTrim(100)).toBe('99+');
- });
- });
-
- describe('humanize', () => {
- it('should remove underscores and uppercase the first letter', () => {
- expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
- });
- });
-
- describe('pluralize', () => {
- it('should pluralize given string', () => {
- expect(textUtils.pluralize('test', 2)).toBe('tests');
- });
-
- it('should pluralize when count is 0', () => {
- expect(textUtils.pluralize('test', 0)).toBe('tests');
- });
-
- it('should not pluralize when count is 1', () => {
- expect(textUtils.pluralize('test', 1)).toBe('test');
- });
- });
-
- describe('dasherize', () => {
- it('should replace underscores with dashes', () => {
- expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
- });
- });
-
- describe('slugify', () => {
- it('should remove accents and convert to lower case', () => {
- expect(textUtils.slugify('João')).toEqual('joão');
- });
- });
-
- describe('slugifyWithHyphens', () => {
- it('should replaces whitespaces with hyphens and convert to lower case', () => {
- expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
- });
- });
-
- describe('stripHtml', () => {
- it('replaces html tag with the default replacement', () => {
- expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
- 'This is a text with html.',
- );
- });
-
- it('replaces html tags with the provided replacement', () => {
- expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual(
- 'This is a text with html .',
- );
- });
-
- it('passes through with null string input', () => {
- expect(textUtils.stripHtml(null, ' ')).toEqual(null);
- });
-
- it('passes through with undefined string input', () => {
- expect(textUtils.stripHtml(undefined, ' ')).toEqual(undefined);
- });
- });
-
- describe('convertToCamelCase', () => {
- it('converts snake_case string to camelCase string', () => {
- expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
- });
- });
-
- describe('convertToSentenceCase', () => {
- it('converts Sentence Case to Sentence case', () => {
- expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world');
- });
- });
-
- describe('truncateSha', () => {
- it('shortens SHAs to 8 characters', () => {
- expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
- });
-
- it('leaves short SHAs as is', () => {
- expect(textUtils.truncateSha('shortsha')).toBe('shortsha');
- });
- });
-
- describe('splitCamelCase', () => {
- it('separates a PascalCase word to two', () => {
- expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World');
- });
- });
-
- describe('getFirstCharacterCapitalized', () => {
- it('returns the first character capitalized, if first character is alphabetic', () => {
- expect(textUtils.getFirstCharacterCapitalized('loremIpsumDolar')).toEqual('L');
- expect(textUtils.getFirstCharacterCapitalized('Sit amit !')).toEqual('S');
- });
-
- it('returns the first character, if first character is non-alphabetic', () => {
- expect(textUtils.getFirstCharacterCapitalized(' lorem')).toEqual(' ');
- expect(textUtils.getFirstCharacterCapitalized('%#!')).toEqual('%');
- });
-
- it('returns an empty string, if string is falsey', () => {
- expect(textUtils.getFirstCharacterCapitalized('')).toEqual('');
- expect(textUtils.getFirstCharacterCapitalized(null)).toEqual('');
- });
- });
-
- describe('truncatePathMiddleToLength', () => {
- it('does not truncate text', () => {
- expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test');
- });
-
- it('truncates middle of the path', () => {
- expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff');
- });
-
- it('truncates multiple times in the middle of the path', () => {
- expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual(
- 'app/…/…/diff',
- );
- });
- });
-});
diff --git a/spec/javascripts/lib/utils/url_utility_spec.js b/spec/javascripts/lib/utils/url_utility_spec.js
deleted file mode 100644
index 381c7b2d0a6..00000000000
--- a/spec/javascripts/lib/utils/url_utility_spec.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import * as urlUtils from '~/lib/utils/url_utility';
-
-describe('URL utility', () => {
- describe('webIDEUrl', () => {
- afterEach(() => {
- gon.relative_url_root = '';
- });
-
- describe('without relative_url_root', () => {
- it('returns IDE path with route', () => {
- expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
- '/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
- );
- });
- });
-
- describe('with relative_url_root', () => {
- beforeEach(() => {
- gon.relative_url_root = '/gitlab';
- });
-
- it('returns IDE path with route', () => {
- expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
- '/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
- );
- });
- });
- });
-
- describe('mergeUrlParams', () => {
- it('adds w', () => {
- expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
- expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
- expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
- expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe(
- 'https://host/path?w=1#frag',
- );
-
- expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe(
- 'https://h/p?k1=v1&w=1#frag',
- );
- });
-
- it('updates w', () => {
- expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
- });
-
- it('adds multiple params', () => {
- expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
- });
-
- it('adds and updates encoded params', () => {
- expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
- });
- });
-
- describe('removeParams', () => {
- describe('when url is passed', () => {
- it('removes query param with encoded ampersand', () => {
- const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome');
-
- expect(url).toBe('/mail');
- });
-
- it('should remove param when url has no other params', () => {
- const url = urlUtils.removeParams(['size'], '/feature/home?size=5');
-
- expect(url).toBe('/feature/home');
- });
-
- it('should remove param when url has other params', () => {
- const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html');
-
- expect(url).toBe('/feature/home?q=1&f=html');
- });
-
- it('should remove param and preserve fragment', () => {
- const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2');
-
- expect(url).toBe('/feature/home#H2');
- });
-
- it('should remove multiple params', () => {
- const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2');
-
- expect(url).toBe('/home?l=en_US#H2');
- });
- });
- });
-
- describe('setUrlFragment', () => {
- it('should set fragment when url has no fragment', () => {
- const url = urlUtils.setUrlFragment('/home/feature', 'usage');
-
- expect(url).toBe('/home/feature#usage');
- });
-
- it('should set fragment when url has existing fragment', () => {
- const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage');
-
- expect(url).toBe('/home/feature#usage');
- });
-
- it('should set fragment when given fragment includes #', () => {
- const url = urlUtils.setUrlFragment('/home/feature#overview', '#install');
-
- expect(url).toBe('/home/feature#install');
- });
- });
-});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 4eea364bd69..a75470b4db8 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter';
describe('LineHighlighter', function() {
var clickLine;
- preloadFixtures('static/line_highlighter.html.raw');
+ preloadFixtures('static/line_highlighter.html');
clickLine = function(number, eventData = {}) {
if ($.isEmptyObject(eventData)) {
return $('#L' + number).click();
@@ -15,7 +15,7 @@ describe('LineHighlighter', function() {
}
};
beforeEach(function() {
- loadFixtures('static/line_highlighter.html.raw');
+ loadFixtures('static/line_highlighter.html');
this['class'] = new LineHighlighter();
this.css = this['class'].highlightLineClass;
return (this.spies = {
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 406527b08a3..7d1921cabcf 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -28,7 +28,7 @@ export default {
reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
);
const result = {
- pass: !!matchingIcon,
+ pass: Boolean(matchingIcon),
};
if (result.pass) {
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index ab809930804..cadcc15385f 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -11,9 +11,9 @@ describe('MergeRequest', function() {
describe('task lists', function() {
let mock;
- preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html');
beforeEach(function() {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
spyOn(axios, 'patch').and.callThrough();
mock = new MockAdapter(axios);
@@ -58,7 +58,7 @@ describe('MergeRequest', function() {
{
merge_request: {
description: '- [ ] Task List Item',
- lock_version: undefined,
+ lock_version: 0,
update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
},
},
@@ -125,7 +125,7 @@ describe('MergeRequest', function() {
describe('hideCloseButton', () => {
describe('merge request of another user', () => {
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
this.el = document.querySelector('.js-issuable-actions');
new MergeRequest(); // eslint-disable-line no-new
MergeRequest.hideCloseButton();
@@ -145,7 +145,7 @@ describe('MergeRequest', function() {
describe('merge request of current_user', () => {
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ loadFixtures('merge_requests/merge_request_of_current_user.html');
this.el = document.querySelector('.js-issuable-actions');
MergeRequest.hideCloseButton();
});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index c8df05eccf5..1295d900de7 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() {
};
preloadFixtures(
- 'merge_requests/merge_request_with_task_list.html.raw',
- 'merge_requests/diff_comment.html.raw',
+ 'merge_requests/merge_request_with_task_list.html',
+ 'merge_requests/diff_comment.html',
);
beforeEach(function() {
@@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() {
var windowTarget = '_blank';
beforeEach(function() {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
tabUrl = $('.commits-tab a').attr('href');
});
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 092ca9e1dab..aa4a376caf7 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Mini Pipeline Graph Dropdown', () => {
- preloadFixtures('static/mini_dropdown_graph.html.raw');
+ preloadFixtures('static/mini_dropdown_graph.html');
beforeEach(() => {
- loadFixtures('static/mini_dropdown_graph.html.raw');
+ loadFixtures('static/mini_dropdown_graph.html');
});
describe('When is initialized', () => {
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index d334ef7ba4f..56609665b88 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,28 +1,31 @@
import { shallowMount } from '@vue/test-utils';
-import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import { createStore } from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
import MonitoringMock, { deploymentData } from '../mock_data';
describe('Area component', () => {
const mockWidgets = 'mockWidgets';
+ const mockSvgPathContent = 'mockSvgPathContent';
let mockGraphData;
let areaChart;
let spriteSpy;
beforeEach(() => {
- const store = new MonitoringStore();
- store.storeMetrics(MonitoringMock.data);
- store.storeDeploymentData(deploymentData);
+ const store = createStore();
- [mockGraphData] = store.groups[0].metrics;
+ store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
+ store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+
+ [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
areaChart = shallowMount(Area, {
propsData: {
graphData: mockGraphData,
containerWidth: 0,
- deploymentData: store.deploymentData,
+ deploymentData: store.state.monitoringDashboard.deploymentData,
},
slots: {
default: mockWidgets,
@@ -30,7 +33,7 @@ describe('Area component', () => {
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
- () => new Promise(resolve => resolve()),
+ () => new Promise(resolve => resolve(mockSvgPathContent)),
);
});
@@ -64,7 +67,7 @@ describe('Area component', () => {
expect(props.data).toBe(areaChart.vm.chartData);
expect(props.option).toBe(areaChart.vm.chartOptions);
expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText);
- expect(props.thresholds).toBe(areaChart.props('alertData'));
+ expect(props.thresholds).toBe(areaChart.vm.thresholds);
});
it('recieves a tooltip title', () => {
@@ -74,15 +77,6 @@ describe('Area component', () => {
expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true);
});
- it('recieves tooltip content', () => {
- const mockContent = 'mockContent';
- areaChart.vm.tooltip.content = mockContent;
-
- expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockContent)).toBe(
- true,
- );
- });
-
describe('when tooltip is showing deployment data', () => {
beforeEach(() => {
areaChart.vm.tooltip.isDeployment = true;
@@ -110,14 +104,16 @@ describe('Area component', () => {
const generateSeriesData = type => ({
seriesData: [
{
+ seriesName: areaChart.vm.chartData[0].name,
componentSubType: type,
value: [mockDate, 5.55555],
+ seriesIndex: 0,
},
],
value: mockDate,
});
- describe('series is of line type', () => {
+ describe('when series is of line type', () => {
beforeEach(() => {
areaChart.vm.formatTooltipText(generateSeriesData('line'));
});
@@ -127,11 +123,20 @@ describe('Area component', () => {
});
it('formats tooltip content', () => {
- expect(areaChart.vm.tooltip.content).toBe('CPU 5.556');
+ const name = 'Core Usage';
+ const value = '5.556';
+ const seriesLabel = areaChart.find(GlChartSeriesLabel);
+
+ expect(seriesLabel.vm.color).toBe('');
+ expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+ expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
+ expect(
+ shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value),
+ ).toBe(true);
});
});
- describe('series is of scatter type', () => {
+ describe('when series is of scatter type', () => {
beforeEach(() => {
areaChart.vm.formatTooltipText(generateSeriesData('scatter'));
});
@@ -146,24 +151,31 @@ describe('Area component', () => {
});
});
- describe('getScatterSymbol', () => {
+ describe('setSvg', () => {
+ const mockSvgName = 'mockSvgName';
+
beforeEach(() => {
- areaChart.vm.getScatterSymbol();
+ areaChart.vm.setSvg(mockSvgName);
});
- it('gets rocket svg path content for use as deployment data symbol', () => {
- expect(spriteSpy).toHaveBeenCalledWith('rocket');
+ it('gets svg path content', () => {
+ expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
+ });
+
+ it('sets svg path content', done => {
+ areaChart.vm.$nextTick(() => {
+ expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
+ done();
+ });
});
});
describe('onResize', () => {
const mockWidth = 233;
- const mockHeight = 144;
beforeEach(() => {
spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
width: mockWidth,
- height: mockHeight,
}));
areaChart.vm.onResize();
});
@@ -171,28 +183,35 @@ describe('Area component', () => {
it('sets area chart width', () => {
expect(areaChart.vm.width).toBe(mockWidth);
});
-
- it('sets area chart height', () => {
- expect(areaChart.vm.height).toBe(mockHeight);
- });
});
});
describe('computed', () => {
describe('chartData', () => {
+ let chartData;
+ const seriesData = () => chartData[0];
+
+ beforeEach(() => {
+ ({ chartData } = areaChart.vm);
+ });
+
it('utilizes all data points', () => {
- expect(Object.keys(areaChart.vm.chartData)).toEqual(['Cores']);
- expect(areaChart.vm.chartData.Cores.length).toBe(297);
+ expect(chartData.length).toBe(1);
+ expect(seriesData().data.length).toBe(297);
});
it('creates valid data', () => {
- const data = areaChart.vm.chartData.Cores;
+ const { data } = seriesData();
expect(
data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number')
.length,
).toBe(data.length);
});
+
+ it('formats line width correctly', () => {
+ expect(chartData[0].lineStyle.width).toBe(2);
+ });
});
describe('scatterSeries', () => {
@@ -205,12 +224,6 @@ describe('Area component', () => {
});
});
- describe('xAxisLabel', () => {
- it('constructs a label for the chart x-axis', () => {
- expect(areaChart.vm.xAxisLabel).toBe('Core Usage');
- });
- });
-
describe('yAxisLabel', () => {
it('constructs a label for the chart y-axis', () => {
expect(areaChart.vm.yAxisLabel).toBe('CPU');
diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
new file mode 100644
index 00000000000..12b73002f97
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+
+describe('Single Stat Chart component', () => {
+ let singleStatChart;
+
+ beforeEach(() => {
+ singleStatChart = shallowMount(SingleStatChart, {
+ propsData: {
+ title: 'Time to render',
+ value: 1,
+ unit: 'sec',
+ },
+ });
+ });
+
+ afterEach(() => {
+ singleStatChart.destroy();
+ });
+
+ describe('computed', () => {
+ describe('valueWithUnit', () => {
+ it('should interpolate the value and unit props', () => {
+ expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index b1778029a77..1a371c3adaf 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -1,8 +1,16 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
+import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
+import * as types from '~/monitoring/stores/mutation_types';
+import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
+import {
+ metricsGroupsAPIResponse,
+ mockApiEndpoint,
+ environmentData,
+ singleGroupResponse,
+} from './mock_data';
const propsData = {
hasMetrics: false,
@@ -19,6 +27,9 @@ const propsData = {
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35',
currentEnvironmentName: 'production',
+ customMetricsAvailable: false,
+ customMetricsPath: '',
+ validateQueryPath: '',
};
export default propsData;
@@ -26,6 +37,8 @@ export default propsData;
describe('Dashboard', () => {
let DashboardComponent;
let mock;
+ let store;
+ let component;
beforeEach(() => {
setFixtures(`
@@ -33,23 +46,34 @@ describe('Dashboard', () => {
<div class="layout-page"></div>
`);
+ window.gon = {
+ ...window.gon,
+ ee: false,
+ features: {
+ grafanaDashboardLink: true,
+ },
+ };
+
+ store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
});
afterEach(() => {
+ component.$destroy();
mock.restore();
});
describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData,
+ propsData: { ...propsData },
+ store,
});
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
- expect(component.state).toEqual('gettingStarted');
+ expect(component.emptyState).toEqual('gettingStarted');
});
});
@@ -59,21 +83,27 @@ describe('Dashboard', () => {
});
it('shows up a loading state', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
+ store,
});
Vue.nextTick(() => {
- expect(component.state).toEqual('loading');
+ expect(component.emptyState).toEqual('loading');
done();
});
});
it('hides the legend when showLegend is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showLegend: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showLegend: false,
+ },
+ store,
});
setTimeout(() => {
@@ -85,9 +115,14 @@ describe('Dashboard', () => {
});
it('hides the group panels when showPanels is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
});
setTimeout(() => {
@@ -98,55 +133,210 @@ describe('Dashboard', () => {
});
});
- it('renders the dropdown with a number of environments', done => {
- const component = new DashboardComponent({
+ it('renders the environments dropdown with a number of environments', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
});
- component.store.storeEnvironmentsData(environmentData);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a');
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
- expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length);
+ expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
done();
});
});
- it('hides the dropdown list when there is no environments', done => {
- const component = new DashboardComponent({
+ it('hides the environments dropdown list when there is no environments', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
+
+ expect(dropdownMenuEnvironments.length).toEqual(0);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('renders the environments dropdown with a single active element', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ const dropdownItems = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item[active="true"]',
+ );
+
+ expect(dropdownItems.length).toEqual(1);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('hides the dropdown', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ environmentsEndpoint: '',
+ },
+ store,
+ });
+
+ Vue.nextTick(() => {
+ const dropdownIsActiveElement = component.$el.querySelectorAll('.environments');
+
+ expect(dropdownIsActiveElement.length).toEqual(0);
+ done();
});
+ });
- component.store.storeEnvironmentsData([]);
+ it('renders the time window dropdown with a set of options', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+ const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul');
+ const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+ const timeWindowDropdownEls = component.$el.querySelectorAll(
+ '.js-time-window-dropdown .dropdown-item',
+ );
+
+ expect(timeWindowDropdown).not.toBeNull();
+ expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
- expect(dropdownMenuEnvironments.length).toEqual(0);
done();
});
});
- it('renders the dropdown with a single is-active element', done => {
- const component = new DashboardComponent({
+ it('fetches the metrics data with proper time window', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
});
- component.store.storeEnvironmentsData(environmentData);
+ spyOn(component.$store, 'dispatch').and.stub();
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
+
+ component.$store.commit(
+ `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
+ '/environments',
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+
+ component.$mount();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(component.$store.dispatch).toHaveBeenCalled();
+ expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('shows a specific time window selected from the url params', done => {
+ spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
setTimeout(() => {
- const dropdownIsActiveElement = component.$el.querySelectorAll(
- '.dropdown-menu ul li a.is-active',
+ const selectedTimeWindow = component.$el.querySelector(
+ '.js-time-window-dropdown [active="true"]',
);
- expect(dropdownIsActiveElement.length).toEqual(1);
- expect(dropdownIsActiveElement[0].textContent.trim()).toEqual(
- component.currentEnvironmentName,
- );
+ expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes');
+ done();
+ });
+ });
+
+ it('defaults to the eight hours time window for non valid url parameters', done => {
+ spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
+ '<script>alert("XSS")</script>',
+ ]);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
+
+ Vue.nextTick(() => {
+ expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours);
+
done();
});
});
@@ -163,9 +353,14 @@ describe('Dashboard', () => {
});
it('sets elWidth to page width when the sidebar is resized', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
});
expect(component.elWidth).toEqual(0);
@@ -185,4 +380,57 @@ describe('Dashboard', () => {
.catch(done.fail);
});
});
+
+ describe('external dashboard link', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+ });
+
+ describe('with feature flag enabled', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ externalDashboardUrl: '/mockUrl',
+ },
+ store,
+ });
+ });
+
+ it('shows the link', done => {
+ setTimeout(() => {
+ expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain(
+ 'View full dashboard',
+ );
+ done();
+ });
+ });
+ });
+
+ describe('without feature flage enabled', () => {
+ beforeEach(() => {
+ window.gon.features.grafanaDashboardLink = false;
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ externalDashboardUrl: '',
+ },
+ store,
+ });
+ });
+
+ it('does not show the link', done => {
+ setTimeout(() => {
+ expect(component.$el.querySelector('.js-external-dashboard-link')).toBe(null);
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/monitoring/helpers.js b/spec/javascripts/monitoring/helpers.js
new file mode 100644
index 00000000000..672e3b948c4
--- /dev/null
+++ b/spec/javascripts/monitoring/helpers.js
@@ -0,0 +1,8 @@
+// eslint-disable-next-line import/prefer-default-export
+export const resetStore = store => {
+ store.replaceState({
+ showEmptyState: true,
+ emptyState: 'loading',
+ groups: [],
+ });
+};
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index ffc7148fde2..d9d8cb66749 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -330,6 +330,11 @@ export const metricsGroupsAPIResponse = {
weight: 1,
queries: [
{
+ appearance: {
+ line: {
+ width: 2,
+ },
+ },
query_range:
'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
label: 'Core Usage',
@@ -680,6 +685,47 @@ export const metricsGroupsAPIResponse = {
last_update: '2017-05-25T13:18:34.949Z',
};
+export const singleGroupResponse = [
+ {
+ group: 'System metrics (Kubernetes)',
+ priority: 5,
+ metrics: [
+ {
+ title: 'Memory Usage (Total)',
+ weight: 0,
+ y_label: 'Total Memory Used',
+ queries: [
+ {
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^production-(.*)",namespace="autodevops-deploy-33"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'GB',
+ label: 'Total',
+ result: [
+ {
+ metric: {},
+ values: [
+ [1558453960.079, '0.0357666015625'],
+ [1558454020.079, '0.035675048828125'],
+ [1558454080.079, '0.035152435302734375'],
+ [1558454140.079, '0.035221099853515625'],
+ [1558454200.079, '0.0352325439453125'],
+ [1558454260.079, '0.03479766845703125'],
+ [1558454320.079, '0.034793853759765625'],
+ [1558454380.079, '0.034931182861328125'],
+ [1558454440.079, '0.034816741943359375'],
+ [1558454500.079, '0.034816741943359375'],
+ [1558454560.079, '0.034816741943359375'],
+ ],
+ },
+ ],
+ },
+ ],
+ id: 15,
+ },
+ ],
+ },
+];
+
export default metricsGroupsAPIResponse;
export const deploymentData = [
@@ -733,5836 +779,6 @@ export const statePaths = {
documentationPath: '/help/administration/monitoring/prometheus/index.md',
};
-export const singleRowMetricsMultipleSeries = [
- {
- title: 'Multiple Time Series',
- weight: 1,
- y_label: 'Request Rates',
- queries: [
- {
- query_range:
- 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
- label: 'Requests',
- unit: 'Req/sec',
- result: [
- {
- metric: {
- status_code: '1xx',
- },
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '0',
- },
- ],
- },
- {
- metric: {
- status_code: '2xx',
- },
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '1.3333460318669703',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '1.2952627669098458',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '1.3333079369916765',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '1.3333460318669703',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '1.3142982314117277',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '1.2952504309564854',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '1.580952380952381',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '1.7333333333333334',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '2.057142857142857',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '2.1904761904761902',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '1.8285714285714287',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '2.1142857142857143',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '1.619047619047619',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '1.2952504309564854',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '1.7142857142857142',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '1.7333333333333334',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '1.3904761904761904',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '1.5047619047619047',
- },
- ],
- },
- ],
- when: [
- {
- value: 'hundred(s)',
- color: 'green',
- },
- ],
- },
- ],
- },
- {
- title: 'Throughput',
- weight: 1,
- y_label: 'Requests / Sec',
- queries: [
- {
- query_range:
- "sum(rate(nginx_requests_total{server_zone!='*', server_zone!='_', container_name!='POD',environment='production'}[2m]))",
- label: 'Total',
- unit: 'req / sec',
- result: [
- {
- metric: {},
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '0.49524281183630325',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '0.4761995466580315',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '0.4952286623111941',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '0.49524281183630325',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '0.485718911608682',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '0.47619501138106085',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '0.6190476190476191',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '0.6952380952380952',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '0.857142857142857',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '0.9238095238095239',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '0.7428571428571429',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '0.8857142857142857',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '0.638095238095238',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '0.47619501138106085',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '0.6857142857142856',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '0.6952380952380952',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '0.5238095238095237',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '0.5904761904761905',
- },
- ],
- },
- ],
- },
- ],
- },
-];
-
export const queryWithoutData = {
title: 'HTTP Error rate',
weight: 10,
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
deleted file mode 100644
index d8a980c874d..00000000000
--- a/spec/javascripts/monitoring/monitoring_store_spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import MonitoringStore from '~/monitoring/stores/monitoring_store';
-import MonitoringMock, { deploymentData, environmentData } from './mock_data';
-
-describe('MonitoringStore', () => {
- const store = new MonitoringStore();
- store.storeMetrics(MonitoringMock.data);
-
- it('contains two groups that contains, one of which has two queries sorted by priority', () => {
- expect(store.groups).toBeDefined();
- expect(store.groups.length).toEqual(2);
- expect(store.groups[0].metrics.length).toEqual(2);
- });
-
- it('gets the metrics count for every group', () => {
- expect(store.getMetricsCount()).toEqual(3);
- });
-
- it('contains deployment data', () => {
- store.storeDeploymentData(deploymentData);
-
- expect(store.deploymentData).toBeDefined();
- expect(store.deploymentData.length).toEqual(3);
- expect(typeof store.deploymentData[0]).toEqual('object');
- });
-
- it('only stores environment data that contains deployments', () => {
- store.storeEnvironmentsData(environmentData);
-
- expect(store.environmentsData.length).toEqual(2);
- });
-
- it('removes the data if all the values from a query are not defined', () => {
- expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0);
- });
-});
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
new file mode 100644
index 00000000000..a848cd24fe3
--- /dev/null
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -0,0 +1,158 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import store from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
+import {
+ fetchDeploymentsData,
+ fetchEnvironmentsData,
+ requestMetricsData,
+ setEndpoints,
+ setGettingStartedEmptyState,
+} from '~/monitoring/stores/actions';
+import storeState from '~/monitoring/stores/state';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { resetStore } from '../helpers';
+import { deploymentData, environmentData } from '../mock_data';
+
+describe('Monitoring store actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ mock.restore();
+ });
+
+ describe('requestMetricsData', () => {
+ it('sets emptyState to loading', () => {
+ const commit = jasmine.createSpy();
+ const { state } = store;
+
+ requestMetricsData({ state, commit });
+
+ expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
+ });
+ });
+
+ describe('fetchDeploymentsData', () => {
+ it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.deploymentEndpoint = '/success';
+
+ mock.onGet(state.deploymentEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+
+ fetchDeploymentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.deploymentEndpoint = '/error';
+
+ mock.onGet(state.deploymentEndpoint).reply(500);
+
+ fetchDeploymentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('fetchEnvironmentsData', () => {
+ it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.environmentsEndpoint = '/success';
+
+ mock.onGet(state.environmentsEndpoint).reply(200, {
+ environments: environmentData,
+ });
+
+ fetchEnvironmentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.environmentsEndpoint = '/error';
+
+ mock.onGet(state.environmentsEndpoint).reply(500);
+
+ fetchEnvironmentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('Set endpoints', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+
+ it('should commit SET_ENDPOINTS mutation', done => {
+ testAction(
+ setEndpoints,
+ {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ mockedState,
+ [
+ {
+ type: types.SET_ENDPOINTS,
+ payload: {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('Set empty states', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+
+ it('should commit SET_METRICS_ENDPOINT mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
+ null,
+ mockedState,
+ [{ type: types.SET_GETTING_STARTED_EMPTY_STATE }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
new file mode 100644
index 00000000000..882ee1dec14
--- /dev/null
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -0,0 +1,92 @@
+import mutations from '~/monitoring/stores/mutations';
+import * as types from '~/monitoring/stores/mutation_types';
+import state from '~/monitoring/stores/state';
+import { metricsGroupsAPIResponse, deploymentData } from '../mock_data';
+
+describe('Monitoring mutations', () => {
+ let stateCopy;
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => {
+ beforeEach(() => {
+ stateCopy.groups = [];
+ const groups = metricsGroupsAPIResponse.data;
+
+ mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
+ });
+
+ it('normalizes values', () => {
+ const expectedTimestamp = '2017-05-25T08:22:34.925Z';
+ const expectedValue = 0.0010794445585559514;
+ const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0];
+
+ expect(timestamp).toEqual(expectedTimestamp);
+ expect(value).toEqual(expectedValue);
+ });
+
+ it('contains two groups that contains, one of which has two queries sorted by priority', () => {
+ expect(stateCopy.groups).toBeDefined();
+ expect(stateCopy.groups.length).toEqual(2);
+ expect(stateCopy.groups[0].metrics.length).toEqual(2);
+ });
+
+ it('assigns queries a metric id', () => {
+ expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100');
+ });
+
+ it('removes the data if all the values from a query are not defined', () => {
+ expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0);
+ });
+
+ it('assigns metric id of null if metric has no id', () => {
+ stateCopy.groups = [];
+ const groups = metricsGroupsAPIResponse.data;
+ const noId = groups.map(group => ({
+ ...group,
+ ...{
+ metrics: group.metrics.map(metric => {
+ const { id, ...metricWithoutId } = metric;
+
+ return metricWithoutId;
+ }),
+ },
+ }));
+
+ mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
+
+ stateCopy.groups.forEach(group => {
+ group.metrics.forEach(metric => {
+ expect(metric.queries.every(query => query.metricId === null)).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => {
+ it('stores the deployment data', () => {
+ stateCopy.deploymentData = [];
+ mutations[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](stateCopy, deploymentData);
+
+ expect(stateCopy.deploymentData).toBeDefined();
+ expect(stateCopy.deploymentData.length).toEqual(3);
+ expect(typeof stateCopy.deploymentData[0]).toEqual('object');
+ });
+ });
+
+ describe('SET_ENDPOINTS', () => {
+ it('should set all the endpoints', () => {
+ mutations[types.SET_ENDPOINTS](stateCopy, {
+ metricsEndpoint: 'additional_metrics.json',
+ environmentsEndpoint: 'environments.json',
+ deploymentsEndpoint: 'deployments.json',
+ });
+
+ expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
+ expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
+ expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
new file mode 100644
index 00000000000..e3c455d1686
--- /dev/null
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -0,0 +1,29 @@
+import { getTimeDiff } from '~/monitoring/utils';
+import { timeWindows } from '~/monitoring/constants';
+
+describe('getTimeDiff', () => {
+ it('defaults to an 8 hour (28800s) difference', () => {
+ const params = getTimeDiff();
+
+ expect(params.end - params.start).toEqual(28800);
+ });
+
+ it('accepts time window as an argument', () => {
+ const params = getTimeDiff(timeWindows.thirtyMinutes);
+
+ expect(params.end - params.start).not.toEqual(28800);
+ });
+
+ it('returns a value for every defined time window', () => {
+ const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
+
+ nonDefaultWindows.forEach(window => {
+ const params = getTimeDiff(timeWindows[window]);
+ const diff = params.end - params.start;
+
+ // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
+ expect(diff).not.toEqual(28800);
+ expect(typeof diff).toEqual('number');
+ });
+ });
+});
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 1d7b885e64f..4e3140ce4f1 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form';
describe('Branch', function() {
describe('create a new branch', function() {
- preloadFixtures('branches/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html');
function fillNameWith(value) {
$('.js-branch-name')
@@ -16,7 +16,7 @@ describe('Branch', function() {
}
beforeEach(function() {
- loadFixtures('branches/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html');
$('form').on('submit', function(e) {
return e.preventDefault();
});
diff --git a/spec/javascripts/notes/components/discussion_filter_note_spec.js b/spec/javascripts/notes/components/discussion_filter_note_spec.js
new file mode 100644
index 00000000000..52d2e7ce947
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_filter_note_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
+import eventHub from '~/notes/event_hub';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('DiscussionFilterNote component', () => {
+ let vm;
+
+ const createComponent = () => {
+ const Component = Vue.extend(DiscussionFilterNote);
+
+ return mountComponent(Component);
+ };
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('timelineContent', () => {
+ it('returns string containing instruction for switching feed type', () => {
+ expect(vm.timelineContent).toBe(
+ "You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('selectFilter', () => {
+ it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
+ spyOn(eventHub, '$emit');
+
+ vm.selectFilter(1);
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element', () => {
+ expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
+ });
+
+ it('renders comment icon element', () => {
+ expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
+ 'comment',
+ );
+ });
+
+ it('renders filter information note', () => {
+ expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
+ "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+
+ it('renders filter buttons', () => {
+ const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
+
+ expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
+ 'Show all activity',
+ );
+
+ expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
+ 'Show comments only',
+ );
+ });
+
+ it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(0);
+ });
+
+ it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(1);
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index 91dab58ba7f..1c366aee8e2 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import createStore from '~/notes/stores';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants';
+import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -54,14 +54,18 @@ describe('DiscussionFilter component', () => {
});
it('updates to the selected item', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
+ );
filterItem.click();
expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim());
});
it('only updates when selected filter changes', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
+ );
spyOn(vm, 'filterDiscussion');
filterItem.click();
@@ -70,21 +74,27 @@ describe('DiscussionFilter component', () => {
});
it('disables commenting when "Show history only" filter is applied', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
+ );
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(true);
});
it('enables commenting when "Show history only" filter is not applied', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
+ );
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(false);
});
it('renders a dropdown divider for the default filter', () => {
- const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child');
+ const defaultFilter = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`,
+ );
expect(defaultFilter.lastChild.classList).toContain('dropdown-divider');
});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index d604e90b529..1f2c07385a7 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -58,6 +58,7 @@ describe('noteActions', () => {
it('should render emoji link', () => {
expect(wrapper.find('.js-add-award').exists()).toBe(true);
+ expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right');
});
describe('actions dropdown', () => {
@@ -65,7 +66,7 @@ describe('noteActions', () => {
expect(wrapper.find('.js-note-edit').exists()).toBe(true);
});
- it('should be possible to report abuse to GitLab', () => {
+ it('should be possible to report abuse to admin', () => {
expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true);
});
@@ -128,87 +129,33 @@ describe('noteActions', () => {
});
});
- describe('with feature flag replyToIndividualNotes enabled', () => {
+ describe('for showReply = true', () => {
beforeEach(() => {
- gon.features = {
- replyToIndividualNotes: true,
- };
- });
-
- afterEach(() => {
- gon.features = {};
- });
-
- describe('for showReply = true', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: true,
- });
- });
-
- it('shows a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
-
- expect(replyButton.exists()).toBe(true);
+ wrapper = shallowMountNoteActions({
+ ...props,
+ showReply: true,
});
});
- describe('for showReply = false', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: false,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
+ it('shows a reply button', () => {
+ const replyButton = wrapper.find({ ref: 'replyButton' });
- expect(replyButton.exists()).toBe(false);
- });
+ expect(replyButton.exists()).toBe(true);
});
});
- describe('with feature flag replyToIndividualNotes disabled', () => {
+ describe('for showReply = false', () => {
beforeEach(() => {
- gon.features = {
- replyToIndividualNotes: false,
- };
- });
-
- afterEach(() => {
- gon.features = {};
- });
-
- describe('for showReply = true', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: true,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
-
- expect(replyButton.exists()).toBe(false);
+ wrapper = shallowMountNoteActions({
+ ...props,
+ showReply: false,
});
});
- describe('for showReply = false', () => {
- beforeEach(() => {
- wrapper = shallowMountNoteActions({
- ...props,
- showReply: false,
- });
- });
-
- it('does not show a reply button', () => {
- const replyButton = wrapper.find({ ref: 'replyButton' });
+ it('does not show a reply button', () => {
+ const replyButton = wrapper.find({ ref: 'replyButton' });
- expect(replyButton.exists()).toBe(false);
- });
+ expect(replyButton.exists()).toBe(false);
});
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
deleted file mode 100644
index 82f58dafc78..00000000000
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ /dev/null
@@ -1,324 +0,0 @@
-import $ from 'jquery';
-import _ from 'underscore';
-import Vue from 'vue';
-import { mount, createLocalVue } from '@vue/test-utils';
-import NotesApp from '~/notes/components/notes_app.vue';
-import service from '~/notes/services/notes_service';
-import createStore from '~/notes/stores';
-import '~/behaviors/markdown/render_gfm';
-import * as mockData from '../mock_data';
-
-describe('note_app', () => {
- let mountComponent;
- let wrapper;
- let store;
-
- beforeEach(() => {
- $('body').attr('data-page', 'projects:merge_requests:show');
-
- store = createStore();
- mountComponent = data => {
- const propsData = data || {
- noteableData: mockData.noteableDataMock,
- notesData: mockData.notesDataMock,
- userData: mockData.userDataMock,
- };
- const localVue = createLocalVue();
-
- return mount(
- {
- components: {
- NotesApp,
- },
- template: '<div class="js-vue-notes-event"><notes-app v-bind="$attrs" /></div>',
- },
- {
- propsData,
- store,
- localVue,
- sync: false,
- },
- );
- };
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('set data', () => {
- const responseInterceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify([]), {
- status: 200,
- }),
- );
- };
-
- beforeEach(() => {
- Vue.http.interceptors.push(responseInterceptor);
- wrapper = mountComponent();
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, responseInterceptor);
- });
-
- it('should set notes data', () => {
- expect(store.state.notesData).toEqual(mockData.notesDataMock);
- });
-
- it('should set issue data', () => {
- expect(store.state.noteableData).toEqual(mockData.noteableDataMock);
- });
-
- it('should set user data', () => {
- expect(store.state.userData).toEqual(mockData.userDataMock);
- });
-
- it('should fetch discussions', () => {
- expect(store.state.discussions).toEqual([]);
- });
- });
-
- describe('render', () => {
- beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
-
- Vue.http.interceptors.push(mockData.individualNoteInterceptor);
- wrapper = mountComponent();
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
- });
-
- it('should render list of notes', done => {
- const note =
- mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
- '/gitlab-org/gitlab-ce/issues/26/discussions.json'
- ][0].notes[0];
-
- setTimeout(() => {
- expect(
- wrapper
- .find('.main-notes-list .note-header-author-name')
- .text()
- .trim(),
- ).toEqual(note.author.name);
-
- expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html);
- done();
- }, 0);
- });
-
- it('should render form', () => {
- expect(wrapper.find('.js-main-target-form').name()).toEqual('form');
- expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual(
- 'Write a comment or drag your files here…',
- );
- });
-
- it('should not render form when commenting is disabled', () => {
- store.state.commentsDisabled = true;
- wrapper = mountComponent();
-
- expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
- });
-
- it('should render form comment button as disabled', () => {
- expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
- });
-
- it('updates discussions badge', done => {
- setTimeout(() => {
- expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
-
- done();
- });
- });
- });
-
- describe('while fetching data', () => {
- beforeEach(() => {
- wrapper = mountComponent();
- });
-
- it('renders skeleton notes', () => {
- expect(wrapper.find('.animation-container').exists()).toBe(true);
- });
-
- it('should render form', () => {
- expect(wrapper.find('.js-main-target-form').name()).toEqual('form');
- expect(wrapper.find('.js-main-target-form textarea').attributes('placeholder')).toEqual(
- 'Write a comment or drag your files here…',
- );
- });
- });
-
- describe('update note', () => {
- describe('individual note', () => {
- beforeEach(done => {
- Vue.http.interceptors.push(mockData.individualNoteInterceptor);
- spyOn(service, 'updateNote').and.callThrough();
- wrapper = mountComponent();
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- Vue.nextTick(done);
- }, 0);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors,
- mockData.individualNoteInterceptor,
- );
- });
-
- it('renders edit form', () => {
- expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
- });
-
- it('calls the service to update the note', done => {
- wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
- wrapper.find('.js-vue-issue-save').trigger('click');
-
- expect(service.updateNote).toHaveBeenCalled();
- // Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('discussion note', () => {
- beforeEach(done => {
- Vue.http.interceptors.push(mockData.discussionNoteInterceptor);
- spyOn(service, 'updateNote').and.callThrough();
- wrapper = mountComponent();
-
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- Vue.nextTick(done);
- }, 0);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors,
- mockData.discussionNoteInterceptor,
- );
- });
-
- it('renders edit form', () => {
- expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
- });
-
- it('updates the note and resets the edit form', done => {
- wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
- wrapper.find('.js-vue-issue-save').trigger('click');
-
- expect(service.updateNote).toHaveBeenCalled();
- // Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
- });
- });
- });
-
- describe('new note form', () => {
- beforeEach(() => {
- wrapper = mountComponent();
- });
-
- it('should render markdown docs url', () => {
- const { markdownDocsPath } = mockData.notesDataMock;
-
- expect(
- wrapper
- .find(`a[href="${markdownDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('Markdown');
- });
-
- it('should render quick action docs url', () => {
- const { quickActionsDocsPath } = mockData.notesDataMock;
-
- expect(
- wrapper
- .find(`a[href="${quickActionsDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('quick actions');
- });
- });
-
- describe('edit form', () => {
- beforeEach(() => {
- Vue.http.interceptors.push(mockData.individualNoteInterceptor);
- wrapper = mountComponent();
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
- });
-
- it('should render markdown docs url', done => {
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- const { markdownDocsPath } = mockData.notesDataMock;
-
- Vue.nextTick(() => {
- expect(
- wrapper
- .find(`.edit-note a[href="${markdownDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('Markdown is supported');
- done();
- });
- }, 0);
- });
-
- it('should not render quick actions docs url', done => {
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- const { quickActionsDocsPath } = mockData.notesDataMock;
-
- Vue.nextTick(() => {
- expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
- done();
- });
- }, 0);
- });
- });
-
- describe('emoji awards', () => {
- it('dispatches toggleAward after toggleAward event', () => {
- const toggleAwardEvent = new CustomEvent('toggleAward', {
- detail: {
- awardName: 'test',
- noteId: 1,
- },
- });
- const toggleAwardAction = jasmine.createSpy('toggleAward');
- wrapper.vm.$store.hotUpdate({
- actions: {
- toggleAward: toggleAwardAction,
- },
- });
-
- wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent);
-
- expect(toggleAwardAction).toHaveBeenCalledTimes(1);
- const [, payload] = toggleAwardAction.calls.argsFor(0);
-
- expect(payload).toEqual({
- awardName: 'test',
- noteId: 1,
- });
- });
- });
-});
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 5db20fd285f..b632ee6736d 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -1,16 +1,36 @@
-import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
-import issueNoteForm from '~/notes/components/note_form.vue';
+import NoteForm from '~/notes/components/note_form.vue';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
-import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_note_form component', () => {
+ const dummyAutosaveKey = 'some-autosave-key';
+ const dummyDraft = 'dummy draft content';
+
let store;
- let vm;
+ let wrapper;
let props;
+ const createComponentWrapper = () => {
+ const localVue = createLocalVue();
+ return shallowMount(NoteForm, {
+ store,
+ propsData: props,
+ // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following
+ localVue,
+ sync: false,
+ });
+ };
+
beforeEach(() => {
- const Component = Vue.extend(issueNoteForm);
+ spyOnDependency(NoteForm, 'getDraft').and.callFake(key => {
+ if (key === dummyAutosaveKey) {
+ return dummyDraft;
+ }
+
+ return null;
+ });
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
@@ -21,27 +41,31 @@ describe('issue_note_form component', () => {
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
noteId: '545',
};
-
- vm = new Component({
- store,
- propsData: props,
- }).$mount();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('noteHash', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('returns note hash string based on `noteId`', () => {
- expect(vm.noteHash).toBe(`#note_${props.noteId}`);
+ expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`);
});
it('return note hash as `#` when `noteId` is empty', done => {
- vm.noteId = '';
- Vue.nextTick()
+ wrapper.setProps({
+ ...props,
+ noteId: '',
+ });
+
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.noteHash).toBe('#');
+ expect(wrapper.vm.noteHash).toBe('#');
})
.then(done)
.catch(done.fail);
@@ -49,97 +73,196 @@ describe('issue_note_form component', () => {
});
describe('conflicts editing', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('should show conflict message if note changes outside the component', done => {
- vm.isEditing = true;
- vm.noteBody = 'Foo';
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
+ noteBody: 'Foo',
+ });
+
const message =
'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.';
- Vue.nextTick(() => {
- expect(
- vm.$el
- .querySelector('.js-conflict-edit-warning')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toEqual(message);
- done();
- });
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const conflictWarning = wrapper.find('.js-conflict-edit-warning');
+
+ expect(conflictWarning.exists()).toBe(true);
+ expect(
+ conflictWarning
+ .text()
+ .replace(/\s+/g, ' ')
+ .trim(),
+ ).toBe(message);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('form', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('should render text area with placeholder', () => {
- expect(vm.$el.querySelector('textarea').getAttribute('placeholder')).toEqual(
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.attributes('placeholder')).toEqual(
'Write a comment or drag your files here…',
);
});
it('should link to markdown docs', () => {
const { markdownDocsPath } = notesDataMock;
+ const markdownField = wrapper.find(MarkdownField);
+ const markdownFieldProps = markdownField.props();
- expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual(
- 'Markdown',
- );
+ expect(markdownFieldProps.markdownDocsPath).toBe(markdownDocsPath);
});
describe('keyboard events', () => {
+ let textarea;
+
+ beforeEach(() => {
+ textarea = wrapper.find('textarea');
+ textarea.setValue('Foo');
+ });
+
describe('up', () => {
it('should ender edit mode', () => {
- spyOn(vm, 'editMyLastNote').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(38, true));
+ // TODO: do not spy on vm
+ spyOn(wrapper.vm, 'editMyLastNote').and.callThrough();
- expect(vm.editMyLastNote).toHaveBeenCalled();
+ textarea.trigger('keydown.up');
+
+ expect(wrapper.vm.editMyLastNote).toHaveBeenCalled();
});
});
describe('enter', () => {
it('should save note when cmd+enter is pressed', () => {
- spyOn(vm, 'handleUpdate').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
+ textarea.trigger('keydown.enter', { metaKey: true });
+
+ const { handleFormUpdate } = wrapper.emitted();
- expect(vm.handleUpdate).toHaveBeenCalled();
+ expect(handleFormUpdate.length).toBe(1);
});
it('should save note when ctrl+enter is pressed', () => {
- spyOn(vm, 'handleUpdate').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+ textarea.trigger('keydown.enter', { ctrlKey: true });
- expect(vm.handleUpdate).toHaveBeenCalled();
+ const { handleFormUpdate } = wrapper.emitted();
+
+ expect(handleFormUpdate.length).toBe(1);
});
});
});
describe('actions', () => {
it('should be possible to cancel', done => {
- spyOn(vm, 'cancelHandler').and.callThrough();
- vm.isEditing = true;
+ // TODO: do not spy on vm
+ spyOn(wrapper.vm, 'cancelHandler').and.callThrough();
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
+ });
- Vue.nextTick(() => {
- vm.$el.querySelector('.note-edit-cancel').click();
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const cancelButton = wrapper.find('.note-edit-cancel');
+ cancelButton.trigger('click');
- Vue.nextTick(() => {
- expect(vm.cancelHandler).toHaveBeenCalled();
- done();
- });
- });
+ expect(wrapper.vm.cancelHandler).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should be possible to update the note', done => {
- vm.isEditing = true;
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
+ });
- Vue.nextTick(() => {
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('.js-vue-issue-save').click();
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const textarea = wrapper.find('textarea');
+ textarea.setValue('Foo');
+ const saveButton = wrapper.find('.js-vue-issue-save');
+ saveButton.trigger('click');
- Vue.nextTick(() => {
- expect(vm.isSubmitting).toEqual(true);
- done();
- });
+ expect(wrapper.vm.isSubmitting).toEqual(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('with autosaveKey', () => {
+ describe('with draft', () => {
+ beforeEach(done => {
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
});
+ wrapper = createComponentWrapper();
+
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
});
+
+ it('displays the draft in textarea', () => {
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.element.value).toBe(dummyDraft);
+ });
+ });
+
+ describe('without draft', () => {
+ beforeEach(done => {
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: 'some key without draft',
+ });
+ wrapper = createComponentWrapper();
+
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('leaves the textarea empty', () => {
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.element.value).toBe('');
+ });
+ });
+
+ it('updates the draft if textarea content changes', () => {
+ const updateDraftSpy = spyOnDependency(NoteForm, 'updateDraft').and.stub();
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
+ });
+ wrapper = createComponentWrapper();
+ const textarea = wrapper.find('textarea');
+ const dummyContent = 'some new content';
+
+ textarea.setValue(dummyContent);
+
+ expect(updateDraftSpy).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
});
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 2b93fb9fb45..efa864e7d00 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -3,6 +3,7 @@ import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
+import NoteForm from '~/notes/components/note_form.vue';
import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file';
@@ -72,7 +73,18 @@ describe('noteable_discussion component', () => {
.then(() => wrapper.vm.$nextTick())
.then(() => {
expect(wrapper.vm.isReplying).toEqual(true);
- expect(wrapper.vm.$refs.noteForm).not.toBeNull();
+
+ const noteForm = wrapper.find(NoteForm);
+
+ expect(noteForm.exists()).toBe(true);
+
+ const noteFormProps = noteForm.props();
+
+ expect(noteFormProps.discussion).toBe(discussionMock);
+ expect(noteFormProps.isEditing).toBe(false);
+ expect(noteFormProps.line).toBe(null);
+ expect(noteFormProps.saveButtonTitle).toBe('Comment');
+ expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`);
})
.then(done)
.catch(done.fail);
@@ -118,29 +130,6 @@ describe('noteable_discussion component', () => {
});
});
- describe('componentData', () => {
- it('should return first note object for placeholder note', () => {
- const data = {
- isPlaceholderNote: true,
- notes: [{ body: 'hello world!' }],
- };
-
- const note = wrapper.vm.componentData(data);
-
- expect(note).toEqual(data.notes[0]);
- });
-
- it('should return given note for nonplaceholder notes', () => {
- const data = {
- notes: [{ id: 12 }],
- };
-
- const note = wrapper.vm.componentData(data);
-
- expect(note).toEqual(data);
- });
- });
-
describe('action text', () => {
const commitId = 'razupaltuff';
const truncatedCommitId = commitId.substr(0, 8);
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 348743081eb..1df5cf9ef68 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -44,8 +44,7 @@ export const noteableDataMock = {
milestone: null,
milestone_id: null,
moved_to_id: null,
- preview_note_path:
- '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+ preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue',
project_id: 2,
state: 'opened',
time_estimate: 0,
@@ -347,8 +346,7 @@ export const loggedOutnoteableData = {
},
noteable_note_url: '/group/project/merge_requests/1#note_1',
create_note_path: '/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue',
- preview_note_path:
- '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+ preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?target_id=98&target_type=Issue',
};
export const collapseNotesMock = [
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 94ce6d8e222..7a9f32ddcff 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -3,11 +3,12 @@ import $ from 'jquery';
import _ from 'underscore';
import { TEST_HOST } from 'spec/test_constants';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
-import * as actions from '~/notes/stores/actions';
+import actionsModule, * as actions from '~/notes/stores/actions';
import * as mutationTypes from '~/notes/stores/mutation_types';
import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
+import service from '~/notes/services/notes_service';
import testAction from '../../helpers/vuex_action_helper';
import { resetStore } from '../helpers';
import {
@@ -18,11 +19,21 @@ import {
individualNote,
} from '../mock_data';
+const TEST_ERROR_MESSAGE = 'Test error message';
+
describe('Actions Notes Store', () => {
+ let commit;
+ let dispatch;
+ let state;
let store;
+ let flashSpy;
beforeEach(() => {
store = createStore();
+ commit = jasmine.createSpy('commit');
+ dispatch = jasmine.createSpy('dispatch');
+ state = {};
+ flashSpy = spyOnDependency(actionsModule, 'Flash');
});
afterEach(() => {
@@ -604,21 +615,6 @@ describe('Actions Notes Store', () => {
});
describe('updateOrCreateNotes', () => {
- let commit;
- let dispatch;
- let state;
-
- beforeEach(() => {
- commit = jasmine.createSpy('commit');
- dispatch = jasmine.createSpy('dispatch');
- state = {};
- });
-
- afterEach(() => {
- commit.calls.reset();
- dispatch.calls.reset();
- });
-
it('Updates existing note', () => {
const note = { id: 1234 };
const getters = { notesById: { 1234: note } };
@@ -751,4 +747,151 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('resolveDiscussion', () => {
+ let getters;
+ let discussionId;
+
+ beforeEach(() => {
+ discussionId = discussionMock.id;
+ state.discussions = [discussionMock];
+ getters = {
+ isDiscussionResolved: () => false,
+ };
+ });
+
+ it('when unresolved, dispatches action', done => {
+ testAction(
+ actions.resolveDiscussion,
+ { discussionId },
+ { ...state, ...getters },
+ [],
+ [
+ {
+ type: 'toggleResolveNote',
+ payload: {
+ endpoint: discussionMock.resolve_path,
+ isResolved: false,
+ discussion: true,
+ },
+ },
+ ],
+ done,
+ );
+ });
+
+ it('when resolved, does nothing', done => {
+ getters.isDiscussionResolved = id => id === discussionId;
+
+ testAction(
+ actions.resolveDiscussion,
+ { discussionId },
+ { ...state, ...getters },
+ [],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('saveNote', () => {
+ const payload = { endpoint: TEST_HOST, data: { 'note[note]': 'some text' } };
+
+ describe('if response contains errors', () => {
+ const res = { errors: { something: ['went wrong'] } };
+
+ it('throws an error', done => {
+ actions
+ .saveNote(
+ {
+ commit() {},
+ dispatch: () => Promise.resolve(res),
+ },
+ payload,
+ )
+ .then(() => done.fail('Expected error to be thrown!'))
+ .catch(error => {
+ expect(error.message).toBe('Failed to save comment!');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('if response contains no errors', () => {
+ const res = { valid: true };
+
+ it('returns the response', done => {
+ actions
+ .saveNote(
+ {
+ commit() {},
+ dispatch: () => Promise.resolve(res),
+ },
+ payload,
+ )
+ .then(data => {
+ expect(data).toBe(res);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('submitSuggestion', () => {
+ const discussionId = 'discussion-id';
+ const noteId = 'note-id';
+ const suggestionId = 'suggestion-id';
+ let flashContainer;
+
+ beforeEach(() => {
+ spyOn(service, 'applySuggestion');
+ dispatch.and.returnValue(Promise.resolve());
+ service.applySuggestion.and.returnValue(Promise.resolve());
+ flashContainer = {};
+ });
+
+ const testSubmitSuggestion = (done, expectFn) => {
+ actions
+ .submitSuggestion(
+ { commit, dispatch },
+ { discussionId, noteId, suggestionId, flashContainer },
+ )
+ .then(expectFn)
+ .then(done)
+ .catch(done.fail);
+ };
+
+ it('when service success, commits and resolves discussion', done => {
+ testSubmitSuggestion(done, () => {
+ expect(commit.calls.allArgs()).toEqual([
+ [mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }],
+ ]);
+
+ expect(dispatch.calls.allArgs()).toEqual([['resolveDiscussion', { discussionId }]]);
+ expect(flashSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('when service fails, flashes error message', done => {
+ const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
+
+ service.applySuggestion.and.returnValue(Promise.reject(response));
+
+ testSubmitSuggestion(done, () => {
+ expect(commit).not.toHaveBeenCalled();
+ expect(dispatch).not.toHaveBeenCalled();
+ expect(flashSpy).toHaveBeenCalledWith(`${TEST_ERROR_MESSAGE}.`, 'alert', flashContainer);
+ });
+ });
+
+ it('when resolve discussion fails, fail gracefully', done => {
+ dispatch.and.returnValue(Promise.reject());
+
+ testSubmitSuggestion(done, () => {
+ expect(flashSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index c066975a43b..8f3c493dd4c 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -261,4 +261,12 @@ describe('Getters Notes Store', () => {
expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy();
});
});
+
+ describe('getDiscussion', () => {
+ it('returns discussion by ID', () => {
+ state.discussions.push({ id: '1' });
+
+ expect(getters.getDiscussion(state)('1')).toEqual({ id: '1' });
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index fcad1f245b6..4a640d589fb 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import mutations from '~/notes/stores/mutations';
+import { DISCUSSION_NOTE } from '~/notes/constants';
import {
note,
discussionMock,
@@ -326,6 +327,18 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].notes[0].note).toEqual('Foo');
});
+
+ it('transforms an individual note to discussion', () => {
+ const state = {
+ discussions: [individualNote],
+ };
+
+ const transformedNote = { ...individualNote.notes[0], type: DISCUSSION_NOTE };
+
+ mutations.UPDATE_NOTE(state, transformedNote);
+
+ expect(state.discussions[0].individual_note).toEqual(false);
+ });
});
describe('CLOSE_ISSUE', () => {
@@ -530,7 +543,7 @@ describe('Notes Store mutations', () => {
state = { convertedDisscussionIds: [] };
});
- it('adds a disucssion to convertedDisscussionIds', () => {
+ it('adds a discussion to convertedDisscussionIds', () => {
mutations.CONVERT_TO_DISCUSSION(state, discussion.id);
expect(state.convertedDisscussionIds).toContain(discussion.id);
@@ -549,7 +562,7 @@ describe('Notes Store mutations', () => {
state = { convertedDisscussionIds: [41, 42] };
});
- it('removes a disucssion from convertedDisscussionIds', () => {
+ it('removes a discussion from convertedDisscussionIds', () => {
mutations.REMOVE_CONVERTED_DISCUSSION(state, discussion.id);
expect(state.convertedDisscussionIds).not.toContain(discussion.id);
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
deleted file mode 100644
index 7c869d4c326..00000000000
--- a/spec/javascripts/notes_spec.js
+++ /dev/null
@@ -1,1048 +0,0 @@
-/* eslint-disable no-unused-expressions, no-var, object-shorthand */
-import $ from 'jquery';
-import _ from 'underscore';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import 'autosize';
-import '~/gl_form';
-import '~/lib/utils/text_utility';
-import '~/behaviors/markdown/render_gfm';
-import Notes from '~/notes';
-import timeoutPromise from './helpers/set_timeout_promise_helper';
-
-window.gon || (window.gon = {});
-window.gl = window.gl || {};
-gl.utils = gl.utils || {};
-
-const htmlEscape = comment => {
- const escapedString = comment.replace(/["&'<>]/g, a => {
- const escapedToken = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&quot;',
- "'": '&#x27;',
- '`': '&#x60;',
- }[a];
-
- return escapedToken;
- });
-
- return escapedString;
-};
-
-describe('Notes', function() {
- const FLASH_TYPE_ALERT = 'alert';
- const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
- var fixture = 'snippets/show.html.raw';
- preloadFixtures(fixture);
-
- beforeEach(function() {
- loadFixtures(fixture);
- gl.utils.disableButtonIfEmptyField = _.noop;
- window.project_uploads_path = 'http://test.host/uploads';
- $('body').attr('data-page', 'projects:merge_requets:show');
- });
-
- afterEach(() => {
- // Undo what we did to the shared <body>
- $('body').removeAttr('data-page');
- });
-
- describe('addBinding', () => {
- it('calls postComment when comment button is clicked', () => {
- spyOn(Notes.prototype, 'postComment');
- this.notes = new Notes('', []);
-
- $('.js-comment-button').click();
-
- expect(Notes.prototype.postComment).toHaveBeenCalled();
- });
- });
-
- describe('task lists', function() {
- let mock;
-
- beforeEach(function() {
- spyOn(axios, 'patch').and.callFake(() => new Promise(() => {}));
- mock = new MockAdapter(axios);
- mock.onAny().reply(200, {});
-
- $('.js-comment-button').on('click', function(e) {
- e.preventDefault();
- });
- this.notes = new Notes('', []);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('modifies the Markdown field', function() {
- const changeEvent = document.createEvent('HTMLEvents');
- changeEvent.initEvent('change', true, true);
- $('input[type=checkbox]')
- .attr('checked', true)[0]
- .dispatchEvent(changeEvent);
-
- expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
- });
-
- it('submits an ajax request on tasklist:changed', function(done) {
- const lineNumber = 8;
- const lineSource = '- [ ] item 8';
- const index = 3;
- const checked = true;
-
- $('.js-task-list-container').trigger({
- type: 'tasklist:changed',
- detail: { lineNumber, lineSource, index, checked },
- });
-
- setTimeout(() => {
- expect(axios.patch).toHaveBeenCalledWith(undefined, {
- note: {
- note: '',
- lock_version: undefined,
- update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
- },
- });
-
- done();
- });
- });
- });
-
- describe('comments', function() {
- var textarea = '.js-note-text';
-
- beforeEach(function() {
- this.notes = new Notes('', []);
-
- this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
- spyOn(this.notes, 'renderNote').and.stub();
-
- $(textarea).data('autosave', {
- reset: function() {},
- });
-
- $('.js-comment-button').on('click', e => {
- const $form = $(this);
- e.preventDefault();
- this.notes.addNote($form);
- this.notes.reenableTargetFormSubmitButton(e);
- this.notes.resetMainTargetForm(e);
- });
- });
-
- it('autosizes after comment submission', function() {
- $(textarea).text('This is an example comment note');
-
- expect(this.autoSizeSpy).not.toHaveBeenTriggered();
-
- $('.js-comment-button').click();
-
- expect(this.autoSizeSpy).toHaveBeenTriggered();
- });
-
- it('should not place escaped text in the comment box in case of error', function() {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- $(textarea).text('A comment with `markup`.');
-
- deferred.reject();
- $('.js-comment-button').click();
-
- expect($(textarea).val()).toEqual('A comment with `markup`.');
- });
- });
-
- describe('updateNote', () => {
- let sampleComment;
- let noteEntity;
- let $form;
- let $notesContainer;
- let mock;
-
- beforeEach(() => {
- this.notes = new Notes('', []);
- window.gon.current_username = 'root';
- window.gon.current_user_fullname = 'Administrator';
- sampleComment = 'foo';
- noteEntity = {
- id: 1234,
- html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
- <div class="note-text">${sampleComment}</div>
- </li>`,
- note: sampleComment,
- valid: true,
- };
- $form = $('form.js-main-target-form');
- $notesContainer = $('ul.main-notes-list');
- $form.find('textarea.js-note-text').val(sampleComment);
-
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, noteEntity);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('updates note and resets edit form', done => {
- spyOn(this.notes, 'revertNoteEditForm');
- spyOn(this.notes, 'setupNewNote');
-
- $('.js-comment-button').click();
-
- setTimeout(() => {
- const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
- const updatedNote = Object.assign({}, noteEntity);
- updatedNote.note = 'bar';
- this.notes.updateNote(updatedNote, $targetNote);
-
- expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
- expect(this.notes.setupNewNote).toHaveBeenCalled();
-
- done();
- });
- });
- });
-
- describe('updateNoteTargetSelector', () => {
- const hash = 'note_foo';
- let $note;
-
- beforeEach(() => {
- $note = $(`<div id="${hash}"></div>`);
- spyOn($note, 'filter').and.callThrough();
- spyOn($note, 'toggleClass').and.callThrough();
- });
-
- it('sets target when hash matches', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash);
-
- Notes.updateNoteTargetSelector($note);
-
- expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
- expect($note.toggleClass).toHaveBeenCalledWith('target', true);
- });
-
- it('unsets target when hash does not match', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist');
-
- Notes.updateNoteTargetSelector($note);
-
- expect($note.toggleClass).toHaveBeenCalledWith('target', false);
- });
-
- it('unsets target when there is not a hash fragment anymore', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue(null);
-
- Notes.updateNoteTargetSelector($note);
-
- expect($note.toggleClass).toHaveBeenCalledWith('target', false);
- });
- });
-
- describe('renderNote', () => {
- let notes;
- let note;
- let $notesList;
-
- beforeEach(() => {
- note = {
- id: 1,
- valid: true,
- note: 'heya',
- html: '<div>heya</div>',
- };
- $notesList = jasmine.createSpyObj('$notesList', ['find', 'append']);
-
- notes = jasmine.createSpyObj('notes', [
- 'setupNewNote',
- 'refresh',
- 'collapseLongCommitList',
- 'updateNotesCount',
- 'putConflictEditWarningInPlace',
- ]);
- notes.taskList = jasmine.createSpyObj('tasklist', ['init']);
- notes.note_ids = [];
- notes.updatedNotesTrackingMap = {};
-
- spyOn(Notes, 'isNewNote').and.callThrough();
- spyOn(Notes, 'isUpdatedNote').and.callThrough();
- spyOn(Notes, 'animateAppendNote').and.callThrough();
- spyOn(Notes, 'animateUpdateNote').and.callThrough();
- });
-
- describe('when adding note', () => {
- it('should call .animateAppendNote', () => {
- Notes.isNewNote.and.returnValue(true);
- Notes.prototype.renderNote.call(notes, note, null, $notesList);
-
- expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
- });
- });
-
- describe('when note was edited', () => {
- it('should call .animateUpdateNote', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
- const $note = $('<div>');
- $notesList.find.and.returnValue($note);
- const $newNote = $(note.html);
- Notes.animateUpdateNote.and.returnValue($newNote);
-
- Notes.prototype.renderNote.call(notes, note, null, $notesList);
-
- expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note);
- expect(notes.setupNewNote).toHaveBeenCalledWith($newNote);
- });
-
- describe('while editing', () => {
- it('should update textarea if nothing has been touched', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
- const $note = $(`<div class="is-editing">
- <div class="original-note-content">initial</div>
- <textarea class="js-note-text">initial</textarea>
- </div>`);
- $notesList.find.and.returnValue($note);
- Notes.prototype.renderNote.call(notes, note, null, $notesList);
-
- expect($note.find('.js-note-text').val()).toEqual(note.note);
- });
-
- it('should call .putConflictEditWarningInPlace', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
- const $note = $(`<div class="is-editing">
- <div class="original-note-content">initial</div>
- <textarea class="js-note-text">different</textarea>
- </div>`);
- $notesList.find.and.returnValue($note);
- Notes.prototype.renderNote.call(notes, note, null, $notesList);
-
- expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
- });
- });
- });
- });
-
- describe('isUpdatedNote', () => {
- it('should consider same note text as the same', () => {
- const result = Notes.isUpdatedNote(
- {
- note: 'initial',
- },
- $(`<div>
- <div class="original-note-content">initial</div>
- </div>`),
- );
-
- expect(result).toEqual(false);
- });
-
- it('should consider same note with trailing newline as the same', () => {
- const result = Notes.isUpdatedNote(
- {
- note: 'initial\n',
- },
- $(`<div>
- <div class="original-note-content">initial\n</div>
- </div>`),
- );
-
- expect(result).toEqual(false);
- });
-
- it('should consider different notes as different', () => {
- const result = Notes.isUpdatedNote(
- {
- note: 'foo',
- },
- $(`<div>
- <div class="original-note-content">bar</div>
- </div>`),
- );
-
- expect(result).toEqual(true);
- });
- });
-
- describe('renderDiscussionNote', () => {
- let discussionContainer;
- let note;
- let notes;
- let $form;
- let row;
-
- beforeEach(() => {
- note = {
- html: '<li></li>',
- discussion_html: '<div></div>',
- discussion_id: 1,
- discussion_resolvable: false,
- diff_discussion_html: false,
- };
- $form = jasmine.createSpyObj('$form', ['closest', 'find']);
- $form.length = 1;
- row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
-
- notes = jasmine.createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
- notes.note_ids = [];
-
- spyOn(Notes, 'isNewNote');
- spyOn(Notes, 'animateAppendNote');
- Notes.isNewNote.and.returnValue(true);
- notes.isParallelView.and.returnValue(false);
- row.prevAll.and.returnValue(row);
- row.first.and.returnValue(row);
- row.find.and.returnValue(row);
- });
-
- describe('Discussion root note', () => {
- let body;
-
- beforeEach(() => {
- body = jasmine.createSpyObj('body', ['attr']);
- discussionContainer = { length: 0 };
-
- $form.closest.and.returnValues(row, $form);
- $form.find.and.returnValues(discussionContainer);
- body.attr.and.returnValue('');
- });
-
- it('should call Notes.animateAppendNote', () => {
- Notes.prototype.renderDiscussionNote.call(notes, note, $form);
-
- expect(Notes.animateAppendNote).toHaveBeenCalledWith(
- note.discussion_html,
- $('.main-notes-list'),
- );
- });
-
- it('should append to row selected with line_code', () => {
- $form.length = 0;
- note.discussion_line_code = 'line_code';
- note.diff_discussion_html = '<tr></tr>';
-
- const line = document.createElement('div');
- line.id = note.discussion_line_code;
- document.body.appendChild(line);
-
- $form.closest.and.returnValues($form);
-
- Notes.prototype.renderDiscussionNote.call(notes, note, $form);
-
- expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html);
- });
- });
-
- describe('Discussion sub note', () => {
- beforeEach(() => {
- discussionContainer = { length: 1 };
-
- $form.closest.and.returnValues(row, $form);
- $form.find.and.returnValues(discussionContainer);
-
- Notes.prototype.renderDiscussionNote.call(notes, note, $form);
- });
-
- it('should call Notes.animateAppendNote', () => {
- expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer);
- });
- });
- });
-
- describe('animateAppendNote', () => {
- let noteHTML;
- let $notesList;
- let $resultantNote;
-
- beforeEach(() => {
- noteHTML = '<div></div>';
- $notesList = jasmine.createSpyObj('$notesList', ['append']);
-
- $resultantNote = Notes.animateAppendNote(noteHTML, $notesList);
- });
-
- it('should have `fade-in-full` class', () => {
- expect($resultantNote.hasClass('fade-in-full')).toEqual(true);
- });
-
- it('should append note to the notes list', () => {
- expect($notesList.append).toHaveBeenCalledWith($resultantNote);
- });
- });
-
- describe('animateUpdateNote', () => {
- let noteHTML;
- let $note;
- let $updatedNote;
-
- beforeEach(() => {
- noteHTML = '<div></div>';
- $note = jasmine.createSpyObj('$note', ['replaceWith']);
-
- $updatedNote = Notes.animateUpdateNote(noteHTML, $note);
- });
-
- it('should have `fade-in` class', () => {
- expect($updatedNote.hasClass('fade-in')).toEqual(true);
- });
-
- it('should call replaceWith on $note', () => {
- expect($note.replaceWith).toHaveBeenCalledWith($updatedNote);
- });
- });
-
- describe('putEditFormInPlace', () => {
- it('should call GLForm with GFM parameter passed through', () => {
- const notes = new Notes('', []);
- const $el = $(`
- <div>
- <form></form>
- </div>
- `);
-
- notes.putEditFormInPlace($el);
-
- expect(notes.glForm.enableGFM).toBeTruthy();
- });
- });
-
- describe('postComment & updateComment', () => {
- const sampleComment = 'foo';
- const updatedComment = 'bar';
- const note = {
- id: 1234,
- html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
- <div class="note-text">${sampleComment}</div>
- </li>`,
- note: sampleComment,
- valid: true,
- };
- let $form;
- let $notesContainer;
- let mock;
-
- function mockNotesPost() {
- mock.onPost(NOTES_POST_PATH).reply(200, note);
- }
-
- function mockNotesPostError() {
- mock.onPost(NOTES_POST_PATH).networkError();
- }
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
-
- this.notes = new Notes('', []);
- window.gon.current_username = 'root';
- window.gon.current_user_fullname = 'Administrator';
- $form = $('form.js-main-target-form');
- $notesContainer = $('ul.main-notes-list');
- $form.find('textarea.js-note-text').val(sampleComment);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should show placeholder note while new comment is being posted', () => {
- mockNotesPost();
-
- $('.js-comment-button').click();
-
- expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0);
- });
-
- it('should remove placeholder note when new comment is done posting', done => {
- mockNotesPost();
-
- $('.js-comment-button').click();
-
- setTimeout(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0);
-
- done();
- });
- });
-
- describe('postComment', () => {
- it('disables the submit button', done => {
- const $submitButton = $form.find('.js-comment-submit-button');
-
- expect($submitButton).not.toBeDisabled();
- const dummyEvent = {
- preventDefault() {},
- target: $submitButton,
- };
- mock.onPost(NOTES_POST_PATH).replyOnce(() => {
- expect($submitButton).toBeDisabled();
- return [200, note];
- });
-
- this.notes
- .postComment(dummyEvent)
- .then(() => {
- expect($submitButton).not.toBeDisabled();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- it('should show actual note element when new comment is done posting', done => {
- mockNotesPost();
-
- $('.js-comment-button').click();
-
- setTimeout(() => {
- expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
-
- done();
- });
- });
-
- it('should reset Form when new comment is done posting', done => {
- mockNotesPost();
-
- $('.js-comment-button').click();
-
- setTimeout(() => {
- expect($form.find('textarea.js-note-text').val()).toEqual('');
-
- done();
- });
- });
-
- it('should show flash error message when new comment failed to be posted', done => {
- mockNotesPostError();
-
- $('.js-comment-button').click();
-
- setTimeout(() => {
- expect(
- $notesContainer
- .parent()
- .find('.flash-container .flash-text')
- .is(':visible'),
- ).toEqual(true);
-
- done();
- });
- });
-
- it('should show flash error message when comment failed to be updated', done => {
- mockNotesPost();
-
- $('.js-comment-button').click();
-
- timeoutPromise()
- .then(() => {
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').val(updatedComment);
-
- mock.restore();
-
- mockNotesPostError();
-
- $noteEl.find('.js-comment-save-button').click();
- })
- .then(timeoutPromise)
- .then(() => {
- const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
-
- expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
- expect(
- $updatedNoteEl
- .find('.note-text')
- .text()
- .trim(),
- ).toEqual(sampleComment); // See if comment reverted back to original
-
- expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('postComment with Slash commands', () => {
- const sampleComment = '/assign @root\n/award :100:';
- const note = {
- commands_changes: {
- assignee_id: 1,
- emoji_award: '100',
- },
- errors: {
- commands_only: ['Commands applied'],
- },
- valid: false,
- };
- let $form;
- let $notesContainer;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, note);
-
- this.notes = new Notes('', []);
- window.gon.current_username = 'root';
- window.gon.current_user_fullname = 'Administrator';
- gl.awardsHandler = {
- addAwardToEmojiBar: () => {},
- scrollToAwards: () => {},
- };
- gl.GfmAutoComplete = {
- dataSources: {
- commands: '/root/test-project/autocomplete_sources/commands',
- },
- };
- $form = $('form.js-main-target-form');
- $notesContainer = $('ul.main-notes-list');
- $form.find('textarea.js-note-text').val(sampleComment);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should remove slash command placeholder when comment with slash commands is done posting', done => {
- spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
- $('.js-comment-button').click();
-
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
-
- setTimeout(() => {
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
- done();
- });
- });
- });
-
- describe('update comment with script tags', () => {
- const sampleComment = '<script></script>';
- const updatedComment = '<script></script>';
- const note = {
- id: 1234,
- html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
- <div class="note-text">${sampleComment}</div>
- </li>`,
- note: sampleComment,
- valid: true,
- };
- let $form;
- let $notesContainer;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, note);
-
- this.notes = new Notes('', []);
- window.gon.current_username = 'root';
- window.gon.current_user_fullname = 'Administrator';
- $form = $('form.js-main-target-form');
- $notesContainer = $('ul.main-notes-list');
- $form.find('textarea.js-note-text').html(sampleComment);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should not render a script tag', done => {
- $('.js-comment-button').click();
-
- setTimeout(() => {
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').html(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
-
- const $updatedNoteEl = $notesContainer
- .find(`#note_${note.id}`)
- .find('.js-task-list-container');
-
- expect(
- $updatedNoteEl
- .find('.note-text')
- .text()
- .trim(),
- ).toEqual('');
-
- done();
- });
- });
- });
-
- describe('getFormData', () => {
- let $form;
- let sampleComment;
-
- beforeEach(() => {
- this.notes = new Notes('', []);
-
- $form = $('form');
- sampleComment = 'foobar';
- });
-
- it('should return form metadata object from form reference', () => {
- $form.find('textarea.js-note-text').val(sampleComment);
- const { formData, formContent, formAction } = this.notes.getFormData($form);
-
- expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
- expect(formContent).toEqual(sampleComment);
- expect(formAction).toEqual($form.attr('action'));
- });
-
- it('should return form metadata with sanitized formContent from form reference', () => {
- spyOn(_, 'escape').and.callFake(htmlEscape);
-
- sampleComment = '<script>alert("Boom!");</script>';
- $form.find('textarea.js-note-text').val(sampleComment);
-
- const { formContent } = this.notes.getFormData($form);
-
- expect(_.escape).toHaveBeenCalledWith(sampleComment);
- expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
- });
- });
-
- describe('hasQuickActions', () => {
- beforeEach(() => {
- this.notes = new Notes('', []);
- });
-
- it('should return true when comment begins with a quick action', () => {
- const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
-
- expect(hasQuickActions).toBeTruthy();
- });
-
- it('should return false when comment does NOT begin with a quick action', () => {
- const sampleComment = 'Hey, /unassign Merging this';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
-
- expect(hasQuickActions).toBeFalsy();
- });
-
- it('should return false when comment does NOT have any quick actions', () => {
- const sampleComment = 'Looking good, Awesome!';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
-
- expect(hasQuickActions).toBeFalsy();
- });
- });
-
- describe('stripQuickActions', () => {
- it('should strip quick actions from the comment which begins with a quick action', () => {
- this.notes = new Notes();
- const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
-
- expect(stripedComment).toBe('');
- });
-
- it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
- this.notes = new Notes();
- const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
-
- expect(stripedComment).toBe('Merging this');
- });
-
- it('should NOT strip string that has slashes within', () => {
- this.notes = new Notes();
- const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
-
- expect(stripedComment).toBe(sampleComment);
- });
- });
-
- describe('getQuickActionDescription', () => {
- const availableQuickActions = [
- { name: 'close', description: 'Close this issue', params: [] },
- { name: 'title', description: 'Change title', params: [{}] },
- { name: 'estimate', description: 'Set time estimate', params: [{}] },
- ];
-
- beforeEach(() => {
- this.notes = new Notes();
- });
-
- it('should return executing quick action description when note has single quick action', () => {
- const sampleComment = '/close';
-
- expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
- 'Applying command to close this issue',
- );
- });
-
- it('should return generic multiple quick action description when note has multiple quick actions', () => {
- const sampleComment = '/close\n/title [Duplicate] Issue foobar';
-
- expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
- 'Applying multiple commands',
- );
- });
-
- it('should return generic quick action description when available quick actions list is not populated', () => {
- const sampleComment = '/close\n/title [Duplicate] Issue foobar';
-
- expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
- });
- });
-
- describe('createPlaceholderNote', () => {
- const sampleComment = 'foobar';
- const uniqueId = 'b1234-a4567';
- const currentUsername = 'root';
- const currentUserFullname = 'Administrator';
- const currentUserAvatar = 'avatar_url';
-
- beforeEach(() => {
- this.notes = new Notes('', []);
- });
-
- it('should return constructed placeholder element for regular note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderNote({
- formContent: sampleComment,
- uniqueId,
- isDiscussionNote: false,
- currentUsername,
- currentUserFullname,
- currentUserAvatar,
- });
- const $tempNoteHeader = $tempNote.find('.note-header');
-
- expect($tempNote.prop('nodeName')).toEqual('LI');
- expect($tempNote.attr('id')).toEqual(uniqueId);
- expect($tempNote.hasClass('being-posted')).toBeTruthy();
- expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
- $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
- expect($(this).attr('href')).toEqual(`/${currentUsername}`);
- });
-
- expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
- expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
- expect(
- $tempNoteHeader
- .find('.d-none.d-sm-inline-block')
- .text()
- .trim(),
- ).toEqual(currentUserFullname);
-
- expect(
- $tempNoteHeader
- .find('.note-headline-light')
- .text()
- .trim(),
- ).toEqual(`@${currentUsername}`);
-
- expect(
- $tempNote
- .find('.note-body .note-text p')
- .text()
- .trim(),
- ).toEqual(sampleComment);
- });
-
- it('should return constructed placeholder element for discussion note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderNote({
- formContent: sampleComment,
- uniqueId,
- isDiscussionNote: true,
- currentUsername,
- currentUserFullname,
- });
-
- expect($tempNote.prop('nodeName')).toEqual('LI');
- expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
- });
-
- it('should return a escaped user name', () => {
- const currentUserFullnameXSS = 'Foo <script>alert("XSS")</script>';
- const $tempNote = this.notes.createPlaceholderNote({
- formContent: sampleComment,
- uniqueId,
- isDiscussionNote: false,
- currentUsername,
- currentUserFullname: currentUserFullnameXSS,
- currentUserAvatar,
- });
- const $tempNoteHeader = $tempNote.find('.note-header');
-
- expect(
- $tempNoteHeader
- .find('.d-none.d-sm-inline-block')
- .text()
- .trim(),
- ).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
- });
- });
-
- describe('createPlaceholderSystemNote', () => {
- const sampleCommandDescription = 'Applying command to close this issue';
- const uniqueId = 'b1234-a4567';
-
- beforeEach(() => {
- this.notes = new Notes('', []);
- spyOn(_, 'escape').and.callFake(htmlEscape);
- });
-
- it('should return constructed placeholder element for system note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderSystemNote({
- formContent: sampleCommandDescription,
- uniqueId,
- });
-
- expect($tempNote.prop('nodeName')).toEqual('LI');
- expect($tempNote.attr('id')).toEqual(uniqueId);
- expect($tempNote.hasClass('being-posted')).toBeTruthy();
- expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
- expect(
- $tempNote
- .find('.timeline-content i')
- .text()
- .trim(),
- ).toEqual(sampleCommandDescription);
- });
- });
-
- describe('appendFlash', () => {
- beforeEach(() => {
- this.notes = new Notes();
- });
-
- it('shows a flash message', () => {
- this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
-
- expect($('.flash-alert').is(':visible')).toBeTruthy();
- });
- });
-
- describe('clearFlash', () => {
- beforeEach(() => {
- $(document).off('ajax:success');
- this.notes = new Notes();
- });
-
- it('hides visible flash message', () => {
- this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
-
- this.notes.clearFlash();
-
- expect($('.flash-alert').is(':visible')).toBeFalsy();
- });
- });
-});
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index 4125706a407..381be82697e 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
- preloadFixtures('static/oauth_remember_me.html.raw');
+ preloadFixtures('static/oauth_remember_me.html');
beforeEach(() => {
- loadFixtures('static/oauth_remember_me.html.raw');
+ loadFixtures('static/oauth_remember_me.html');
new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
});
diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
deleted file mode 100644
index 23d07056925..00000000000
--- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import $ from 'jquery';
-import '~/lib/utils/text_utility';
-import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
-
-describe('Abuse Reports', () => {
- const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
- const MAX_MESSAGE_LENGTH = 500;
-
- let $messages;
-
- const assertMaxLength = $message => {
- expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH);
- };
- const findMessage = searchText =>
- $messages.filter((index, element) => element.innerText.indexOf(searchText) > -1).first();
-
- preloadFixtures(FIXTURE);
-
- beforeEach(function() {
- loadFixtures(FIXTURE);
- this.abuseReports = new AbuseReports();
- $messages = $('.abuse-reports .message');
- });
-
- it('should truncate long messages', () => {
- const $longMessage = findMessage('LONG MESSAGE');
-
- expect($longMessage.data('originalMessage')).toEqual(jasmine.anything());
- assertMaxLength($longMessage);
- });
-
- it('should not truncate short messages', () => {
- const $shortMessage = findMessage('SHORT MESSAGE');
-
- expect($shortMessage.data('originalMessage')).not.toEqual(jasmine.anything());
- });
-
- it('should allow clicking a truncated message to expand and collapse the full message', () => {
- const $longMessage = findMessage('LONG MESSAGE');
- $longMessage.click();
-
- expect($longMessage.data('originalMessage').length).toEqual($longMessage.text().length);
- $longMessage.click();
- assertMaxLength($longMessage);
- });
-});
diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
index 561bd2c96cb..6a239e307e9 100644
--- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
+++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
@@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, {
} from '~/pages/admin/application_settings/account_and_limits';
describe('AccountAndLimits', () => {
- const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
+ const FIXTURE = 'application_settings/accounts_and_limit.html';
let $userDefaultExternal;
let $userInternalRegex;
preloadFixtures(FIXTURE);
diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js
index 5a849f34bc3..3896323eef7 100644
--- a/spec/javascripts/pages/admin/users/new/index_spec.js
+++ b/spec/javascripts/pages/admin/users/new/index_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
describe('UserInternalRegexHandler', () => {
- const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
+ const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
let $userExternal;
let $userEmail;
let $warningMessage;
diff --git a/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
new file mode 100644
index 00000000000..5f4dba5ecb9
--- /dev/null
+++ b/spec/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
@@ -0,0 +1,244 @@
+import $ from 'jquery';
+import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars
+import TimezoneDropdown, {
+ formatUtcOffset,
+ formatTimezone,
+ findTimezoneByIdentifier,
+} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown';
+
+describe('Timezone Dropdown', function() {
+ preloadFixtures('pipeline_schedules/edit.html');
+
+ let $inputEl = null;
+ let $dropdownEl = null;
+ let $wrapper = null;
+ const tzListSel = '.dropdown-content ul li a.is-active';
+ const tzDropdownToggleText = '.dropdown-toggle-text';
+
+ describe('Initialize', () => {
+ describe('with dropdown already loaded', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html');
+ $wrapper = $('.dropdown');
+ $inputEl = $('#schedule_cron_timezone');
+ $dropdownEl = $('.js-timezone-dropdown');
+
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ });
+ });
+
+ it('can take an $inputEl in the constructor', () => {
+ const tzStr = '[UTC + 5.5] Sri Jayawardenepura';
+ const tzValue = 'Asia/Colombo';
+
+ expect($inputEl.val()).toBe('UTC');
+
+ $(`${tzListSel}:contains('${tzStr}')`, $wrapper).trigger('click');
+
+ const val = $inputEl.val();
+
+ expect(val).toBe(tzValue);
+ expect(val).not.toBe('UTC');
+ });
+
+ it('will format data array of timezones into a list of offsets', () => {
+ const data = $dropdownEl.data('data');
+ const formatted = $wrapper.find(tzListSel).text();
+
+ data.forEach(item => {
+ expect(formatted).toContain(formatTimezone(item));
+ });
+ });
+
+ it('will default the timezone to UTC', () => {
+ const tz = $inputEl.val();
+
+ expect(tz).toBe('UTC');
+ });
+ });
+
+ describe('without dropdown loaded', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html');
+ $wrapper = $('.dropdown');
+ $inputEl = $('#schedule_cron_timezone');
+ $dropdownEl = $('.js-timezone-dropdown');
+ });
+
+ it('will populate the list of UTC offsets after the dropdown is loaded', () => {
+ expect($wrapper.find(tzListSel).length).toEqual(0);
+
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ });
+
+ expect($wrapper.find(tzListSel).length).toEqual($($dropdownEl).data('data').length);
+ });
+
+ it('will call a provided handler when a new timezone is selected', () => {
+ const onSelectTimezone = jasmine.createSpy('onSelectTimezoneMock');
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ onSelectTimezone,
+ });
+
+ $wrapper
+ .find(tzListSel)
+ .first()
+ .trigger('click');
+
+ expect(onSelectTimezone).toHaveBeenCalled();
+ });
+
+ it('will correctly set the dropdown label if a timezone identifier is set on the inputEl', () => {
+ $inputEl.val('America/St_Johns');
+
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ displayFormat: selectedItem => formatTimezone(selectedItem),
+ });
+
+ expect($wrapper.find(tzDropdownToggleText).html()).toEqual('[UTC - 2.5] Newfoundland');
+ });
+
+ it('will call a provided `displayFormat` handler to format the dropdown value', () => {
+ const displayFormat = jasmine.createSpy('displayFormat');
+ // eslint-disable-next-line no-new
+ new TimezoneDropdown({
+ $inputEl,
+ $dropdownEl,
+ displayFormat,
+ });
+
+ $wrapper
+ .find(tzListSel)
+ .first()
+ .trigger('click');
+
+ expect(displayFormat).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('formatUtcOffset', () => {
+ it('will convert negative utc offsets in seconds to hours and minutes', () => {
+ expect(formatUtcOffset(-21600)).toEqual('- 6');
+ });
+
+ it('will convert positive utc offsets in seconds to hours and minutes', () => {
+ expect(formatUtcOffset(25200)).toEqual('+ 7');
+ expect(formatUtcOffset(49500)).toEqual('+ 13.75');
+ });
+
+ it('will return 0 when given a string', () => {
+ expect(formatUtcOffset('BLAH')).toEqual('0');
+ expect(formatUtcOffset('$%$%')).toEqual('0');
+ });
+
+ it('will return 0 when given an array', () => {
+ expect(formatUtcOffset(['an', 'array'])).toEqual('0');
+ });
+
+ it('will return 0 when given an object', () => {
+ expect(formatUtcOffset({ some: '', object: '' })).toEqual('0');
+ });
+
+ it('will return 0 when given null', () => {
+ expect(formatUtcOffset(null)).toEqual('0');
+ });
+
+ it('will return 0 when given undefined', () => {
+ expect(formatUtcOffset(undefined)).toEqual('0');
+ });
+
+ it('will return 0 when given empty input', () => {
+ expect(formatUtcOffset('')).toEqual('0');
+ });
+ });
+
+ describe('formatTimezone', () => {
+ it('given name: "Chatham Is.", offset: "49500", will format for display as "[UTC + 13.75] Chatham Is."', () => {
+ expect(
+ formatTimezone({
+ name: 'Chatham Is.',
+ offset: 49500,
+ identifier: 'Pacific/Chatham',
+ }),
+ ).toEqual('[UTC + 13.75] Chatham Is.');
+ });
+
+ it('given name: "Saskatchewan", offset: "-21600", will format for display as "[UTC - 6] Saskatchewan"', () => {
+ expect(
+ formatTimezone({
+ name: 'Saskatchewan',
+ offset: -21600,
+ identifier: 'America/Regina',
+ }),
+ ).toEqual('[UTC - 6] Saskatchewan');
+ });
+
+ it('given name: "Accra", offset: "0", will format for display as "[UTC 0] Accra"', () => {
+ expect(
+ formatTimezone({
+ name: 'Accra',
+ offset: 0,
+ identifier: 'Africa/Accra',
+ }),
+ ).toEqual('[UTC 0] Accra');
+ });
+ });
+
+ describe('findTimezoneByIdentifier', () => {
+ const tzList = [
+ {
+ identifier: 'Asia/Tokyo',
+ name: 'Sapporo',
+ offset: 32400,
+ },
+ {
+ identifier: 'Asia/Hong_Kong',
+ name: 'Hong Kong',
+ offset: 28800,
+ },
+ {
+ identifier: 'Asia/Dhaka',
+ name: 'Dhaka',
+ offset: 21600,
+ },
+ ];
+
+ const identifier = 'Asia/Dhaka';
+ it('returns the correct object if the identifier exists', () => {
+ const res = findTimezoneByIdentifier(tzList, identifier);
+
+ expect(res).toBeTruthy();
+ expect(res).toBe(tzList[2]);
+ });
+
+ it('returns null if it doesnt find the identifier', () => {
+ const res = findTimezoneByIdentifier(tzList, 'Australia/Melbourne');
+
+ expect(res).toBeNull();
+ });
+
+ it('returns null if there is no identifier given', () => {
+ expect(findTimezoneByIdentifier(tzList)).toBeNull();
+ expect(findTimezoneByIdentifier(tzList, '')).toBeNull();
+ });
+
+ it('returns null if there is an empty or invalid array given', () => {
+ expect(findTimezoneByIdentifier([], identifier)).toBeNull();
+ expect(findTimezoneByIdentifier(null, identifier)).toBeNull();
+ expect(findTimezoneByIdentifier(undefined, identifier)).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
index 7a8227479d4..1809e92e1d9 100644
--- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
+++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
describe('preserve_url_fragment', () => {
- preloadFixtures('sessions/new.html.raw');
+ preloadFixtures('sessions/new.html');
beforeEach(() => {
- loadFixtures('sessions/new.html.raw');
+ loadFixtures('sessions/new.html');
});
it('adds the url fragment to all login and sign up form actions', () => {
diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js
index 699cf4871aa..c746d5644e8 100644
--- a/spec/javascripts/pdf/index_spec.js
+++ b/spec/javascripts/pdf/index_spec.js
@@ -1,11 +1,13 @@
import Vue from 'vue';
-import { PDFJS } from 'vendor/pdf';
-import workerSrc from 'vendor/pdf.worker.min';
+import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
+import workerSrc from 'pdfjs-dist/build/pdf.worker.min';
import PDFLab from '~/pdf/index.vue';
-import pdf from '../fixtures/blob/pdf/test.pdf';
+import { FIXTURES_PATH } from 'spec/test_constants';
-PDFJS.workerSrc = workerSrc;
+const pdf = `${FIXTURES_PATH}/blob/pdf/test.pdf`;
+
+GlobalWorkerOptions.workerSrc = workerSrc;
const Component = Vue.extend(PDFLab);
describe('PDF component', () => {
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index ef967210b65..6dea570266b 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -1,10 +1,12 @@
import Vue from 'vue';
-import pdfjsLib from 'vendor/pdf';
-import workerSrc from 'vendor/pdf.worker.min';
+import pdfjsLib from 'pdfjs-dist/build/pdf';
+import workerSrc from 'pdfjs-dist/build/pdf.worker.min';
import PageComponent from '~/pdf/page/index.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import testPDF from 'spec/fixtures/blob/pdf/test.pdf';
+import { FIXTURES_PATH } from 'spec/test_constants';
+
+const testPDF = `${FIXTURES_PATH}/blob/pdf/test.pdf`;
describe('Page component', () => {
const Component = Vue.extend(PageComponent);
@@ -12,7 +14,7 @@ describe('Page component', () => {
let testPage;
beforeEach(done => {
- pdfjsLib.PDFJS.workerSrc = workerSrc;
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
.then(pdf => pdf.getPage(1))
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
index e91685e50c5..8a7aa057186 100644
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -27,8 +27,8 @@ describe('detailedMetric', () => {
describe('when the current request has details', () => {
const requestDetails = [
- { duration: '100', feature: 'find_commit', request: 'abcdef' },
- { duration: '23', feature: 'rebase_in_progress', request: '' },
+ { duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
+ { duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
];
beforeEach(() => {
@@ -54,9 +54,11 @@ describe('detailedMetric', () => {
});
it('adds a modal with a table of the details', () => {
- vm.$el.querySelectorAll('.performance-bar-modal td strong').forEach((duration, index) => {
- expect(duration.innerText).toContain(requestDetails[index].duration);
- });
+ vm.$el
+ .querySelectorAll('.performance-bar-modal td:nth-child(1)')
+ .forEach((duration, index) => {
+ expect(duration.innerText).toContain(requestDetails[index].duration);
+ });
vm.$el
.querySelectorAll('.performance-bar-modal td:nth-child(2)')
@@ -65,10 +67,16 @@ describe('detailedMetric', () => {
});
vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(3)')
+ .querySelectorAll('.performance-bar-modal td:nth-child(2)')
.forEach((request, index) => {
- expect(request.innerText).toEqual(requestDetails[index].request);
+ expect(request.innerText).toContain(requestDetails[index].request);
});
+
+ expect(vm.$el.querySelector('.text-expander.js-toggle-button')).not.toBeNull();
+
+ vm.$el.querySelectorAll('.performance-bar-modal td:nth-child(2)').forEach(request => {
+ expect(request.innerText).toContain('world');
+ });
});
it('displays the metric name', () => {
diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js
new file mode 100644
index 00000000000..2fdfff3db03
--- /dev/null
+++ b/spec/javascripts/persistent_user_callout_spec.js
@@ -0,0 +1,88 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import PersistentUserCallout from '~/persistent_user_callout';
+import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
+
+describe('PersistentUserCallout', () => {
+ const dismissEndpoint = '/dismiss';
+ const featureName = 'feature';
+
+ function createFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <div
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ >
+ <button type="button" class="js-close"></button>
+ </div>
+ `;
+
+ return fixture;
+ }
+
+ describe('dismiss', () => {
+ let button;
+ let mockAxios;
+ let persistentUserCallout;
+
+ beforeEach(() => {
+ const fixture = createFixture();
+ const container = fixture.querySelector('.container');
+ button = fixture.querySelector('.js-close');
+ mockAxios = new MockAdapter(axios);
+ persistentUserCallout = new PersistentUserCallout(container);
+ spyOn(persistentUserCallout.container, 'remove');
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('POSTs endpoint and removes container when clicking close', done => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ button.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName }),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('invokes Flash when the dismiss request fails', done => {
+ const Flash = spyOnDependency(PersistentUserCallout, 'Flash');
+ mockAxios.onPost(dismissEndpoint).replyOnce(500);
+
+ button.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledWith(
+ 'An error occurred while dismissing the alert. Refresh the page and try again.',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('factory', () => {
+ it('returns an instance of PersistentUserCallout with the provided container property', () => {
+ const fixture = createFixture();
+
+ expect(PersistentUserCallout.factory(fixture) instanceof PersistentUserCallout).toBe(true);
+ });
+
+ it('returns undefined if container is falsey', () => {
+ expect(PersistentUserCallout.factory()).toBe(undefined);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index 3d2232ff239..321497b35b5 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -55,13 +55,27 @@ describe('pipeline graph action component', () => {
component.$el.click();
- component
- .$nextTick()
- .then(() => {
- expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
+ })
+ .catch(done.fail);
+
+ done();
+ }, 0);
+ });
+
+ it('renders a loading icon while waiting for request', done => {
+ component.$el.click();
+
+ component.$nextTick(() => {
+ expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull();
+ setTimeout(() => {
+ done();
+ });
+ });
});
});
});
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index d0b8f877d6f..5183f8dd2d6 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -35,6 +35,7 @@ describe('stage column component', () => {
component = mountComponent(StageColumnComponent, {
title: 'foo',
groups: mockGroups,
+ hasTriggeredBy: false,
});
});
@@ -54,13 +55,14 @@ describe('stage column component', () => {
id: 4259,
name: '<img src=x onerror=alert(document.domain)>',
status: {
- icon: 'icon_status_success',
+ icon: 'status_success',
label: 'success',
tooltip: '<img src=x onerror=alert(document.domain)>',
},
},
],
title: 'test',
+ hasTriggeredBy: false,
});
expect(component.$el.querySelector('.builds-container li').getAttribute('id')).toEqual(
@@ -68,4 +70,53 @@ describe('stage column component', () => {
);
});
});
+
+ describe('with action', () => {
+ it('renders action button', () => {
+ component = mountComponent(StageColumnComponent, {
+ groups: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ hasTriggeredBy: false,
+ action: {
+ icon: 'play',
+ title: 'Play all',
+ path: 'action',
+ },
+ });
+
+ expect(component.$el.querySelector('.js-stage-action')).not.toBeNull();
+ });
+ });
+
+ describe('without action', () => {
+ it('does not render action button', () => {
+ component = mountComponent(StageColumnComponent, {
+ groups: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ hasTriggeredBy: false,
+ });
+
+ expect(component.$el.querySelector('.js-stage-action')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
index 03ead6cd8ba..8eef9166b8d 100644
--- a/spec/javascripts/pipelines/mock_data.js
+++ b/spec/javascripts/pipelines/mock_data.js
@@ -1,5 +1,6 @@
export const pipelineWithStages = {
id: 20333396,
+ iid: 304399,
user: {
id: 128633,
name: 'Rémy Coutable',
diff --git a/spec/javascripts/pipelines/pipeline_triggerer_spec.js b/spec/javascripts/pipelines/pipeline_triggerer_spec.js
new file mode 100644
index 00000000000..8cf290f2663
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_triggerer_spec.js
@@ -0,0 +1,54 @@
+import { mount } from '@vue/test-utils';
+import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
+
+describe('Pipelines Triggerer', () => {
+ let wrapper;
+
+ const mockData = {
+ pipeline: {
+ user: {
+ name: 'foo',
+ avatar_url: '/avatar',
+ path: '/path',
+ },
+ },
+ };
+
+ const createComponent = () => {
+ wrapper = mount(pipelineTriggerer, {
+ propsData: mockData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a table cell', () => {
+ expect(wrapper.contains('.table-section')).toBe(true);
+ });
+
+ it('should render triggerer information when triggerer is provided', () => {
+ const link = wrapper.find('.js-pipeline-url-user');
+
+ expect(link.attributes('href')).toEqual(mockData.pipeline.user.path);
+ expect(link.find('.js-user-avatar-image-toolip').text()).toEqual(mockData.pipeline.user.name);
+ expect(link.find('img.avatar').attributes('src')).toEqual(
+ `${mockData.pipeline.user.avatar_url}?width=26`,
+ );
+ });
+
+ it('should render "API" when no triggerer is provided', () => {
+ wrapper.setProps({
+ pipeline: {
+ user: null,
+ },
+ });
+
+ expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index ea917b36526..88c0137dc58 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -13,6 +13,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {},
},
@@ -28,6 +29,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {},
},
@@ -42,65 +44,19 @@ describe('Pipeline Url Component', () => {
expect(component.$el.querySelector('.js-pipeline-url-link span').textContent).toEqual('#1');
});
- it('should render user information when a user is provided', () => {
- const mockData = {
- pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
- user: {
- web_url: '/',
- name: 'foo',
- avatar_url: '/',
- path: '/',
- },
- },
- autoDevopsHelpPath: 'foo',
- };
-
- const component = new PipelineUrlComponent({
- propsData: mockData,
- }).$mount();
-
- const image = component.$el.querySelector('.js-pipeline-url-user img');
- const tooltip = component.$el.querySelector(
- '.js-pipeline-url-user .js-user-avatar-image-toolip',
- );
-
- expect(component.$el.querySelector('.js-pipeline-url-user').getAttribute('href')).toEqual(
- mockData.pipeline.user.web_url,
- );
-
- expect(tooltip.textContent.trim()).toEqual(mockData.pipeline.user.name);
- expect(image.getAttribute('src')).toEqual(`${mockData.pipeline.user.avatar_url}?width=20`);
- });
-
- it('should render "API" when no user is provided', () => {
- const component = new PipelineUrlComponent({
- propsData: {
- pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
- },
- autoDevopsHelpPath: 'foo',
- },
- }).$mount();
-
- expect(component.$el.querySelector('.js-pipeline-url-api').textContent).toContain('API');
- });
-
it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => {
const component = new PipelineUrlComponent({
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
latest: true,
yaml_errors: true,
stuck: true,
- merge_request: true,
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: true,
},
},
autoDevopsHelpPath: 'foo',
@@ -108,15 +64,16 @@ describe('Pipeline Url Component', () => {
}).$mount();
expect(component.$el.querySelector('.js-pipeline-url-latest').textContent).toContain('latest');
+
expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain(
'yaml invalid',
);
- expect(component.$el.querySelector('.js-pipeline-url-mergerequest').textContent).toContain(
- 'merge request',
- );
-
expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
+
+ expect(component.$el.querySelector('.js-pipeline-url-detached').textContent).toContain(
+ 'detached',
+ );
});
it('should render a badge for autodevops', () => {
@@ -124,6 +81,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
latest: true,
@@ -146,6 +104,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
failure_reason: true,
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 97ded16db69..78187b69563 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -446,7 +446,7 @@ describe('Pipelines', () => {
};
vm.$nextTick(() => {
- vm.$el.querySelector('.js-next-button a').click();
+ vm.$el.querySelector('.js-next-button .page-link').click();
expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index 4c575536f0e..d47504d2f54 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -80,13 +80,13 @@ describe('Pipelines Table Row', () => {
it('should render user information', () => {
expect(
component.$el
- .querySelector('.table-section:nth-child(2) a:nth-child(3)')
+ .querySelector('.table-section:nth-child(3) .js-pipeline-url-user')
.getAttribute('href'),
).toEqual(pipeline.user.path);
expect(
component.$el
- .querySelector('.table-section:nth-child(2) .js-user-avatar-image-toolip')
+ .querySelector('.table-section:nth-child(3) .js-user-avatar-image-toolip')
.textContent.trim(),
).toEqual(pipeline.user.name);
});
@@ -195,8 +195,10 @@ describe('Pipelines Table Row', () => {
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$once('openConfirmationModal', data => {
+ const { id, ref, commit } = pipeline;
+
expect(data.endpoint).toEqual('/cancel');
- expect(data.pipelineId).toEqual(pipeline.id);
+ expect(data.pipeline).toEqual(jasmine.objectContaining({ id, ref, commit }));
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 3c8b8032de8..19ae7860333 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -120,13 +120,15 @@ describe('Pipelines stage component', () => {
setTimeout(() => {
component.$el.querySelector('.js-ci-action').click();
- component
- .$nextTick()
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ })
+ .then(done)
+ .catch(done.fail);
+ }, 0);
}, 0);
});
});
diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js
index 6b86f9ea437..6d4d634c575 100644
--- a/spec/javascripts/pipelines_spec.js
+++ b/spec/javascripts/pipelines_spec.js
@@ -1,10 +1,10 @@
import Pipelines from '~/pipelines';
describe('Pipelines', () => {
- preloadFixtures('static/pipeline_graph.html.raw');
+ preloadFixtures('static/pipeline_graph.html');
beforeEach(() => {
- loadFixtures('static/pipeline_graph.html.raw');
+ loadFixtures('static/pipeline_graph.html');
});
it('should be defined', () => {
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
index 109a5000f5d..dc85292c23e 100644
--- a/spec/javascripts/project_select_combo_button_spec.js
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import ProjectSelectComboButton from '~/project_select_combo_button';
-const fixturePath = 'static/project_select_combo_button.html.raw';
+const fixturePath = 'static/project_select_combo_button.html';
describe('Project Select Combo Button', function() {
preloadFixtures(fixturePath);
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
index 030662b4d90..1eb7cb4bd5b 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
@@ -53,36 +53,32 @@ describe('GkeProjectIdDropdown', () => {
});
it('returns default toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.DEFAULT);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+
+ done();
+ }));
it('returns project name if project selected', done =>
- vm
- .$nextTick()
- .then(() => {
- expect(vm.toggleText).toBe(selectedProjectMock.name);
- done();
- })
- .catch(done.fail));
+ setTimeout(() => {
+ vm.isLoading = false;
+
+ expect(vm.toggleText).toBe(selectedProjectMock.name);
+
+ done();
+ }));
it('returns empty toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.$store.commit(SET_PROJECTS, null);
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.$store.commit(SET_PROJECTS, null);
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.EMPTY);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.EMPTY);
+
+ done();
+ }));
});
describe('selectItem', () => {
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index b61e0ac872f..106a3ba94e4 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -10,7 +10,17 @@ describe('New Project', () => {
setFixtures(`
<div class='toggle-import-form'>
<div class='import-url-data'>
- <input id="project_import_url" />
+ <div class="form-group">
+ <input id="project_import_url" />
+ </div>
+ <div id="import-url-auth-method">
+ <div class="form-group">
+ <input id="project-import-url-user" />
+ </div>
+ <div class="form-group">
+ <input id="project_import_url_password" />
+ </div>
+ </div>
<input id="project_name" />
<input id="project_path" />
</div>
@@ -119,7 +129,7 @@ describe('New Project', () => {
});
it('changes project path for HTTPS URL in $projectImportUrl', () => {
- $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
+ $projectImportUrl.val('https://gitlab.company.com/group/project.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl);
diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
index 94e2f959d46..dca3e1553b9 100644
--- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
@@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants';
import { metrics, missingVarMetrics } from './mock_data';
describe('PrometheusMetrics', () => {
- const FIXTURE = 'services/prometheus/prometheus_service.html.raw';
+ const FIXTURE = 'services/prometheus/prometheus_service.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
diff --git a/spec/javascripts/raven/index_spec.js b/spec/javascripts/raven/index_spec.js
index a503a54029f..6b9fe923624 100644
--- a/spec/javascripts/raven/index_spec.js
+++ b/spec/javascripts/raven/index_spec.js
@@ -5,19 +5,19 @@ describe('RavenConfig options', () => {
const sentryDsn = 'sentryDsn';
const currentUserId = 'currentUserId';
const gitlabUrl = 'gitlabUrl';
- const isProduction = 'isProduction';
+ const environment = 'test';
const revision = 'revision';
let indexReturnValue;
beforeEach(() => {
window.gon = {
sentry_dsn: sentryDsn,
+ sentry_environment: environment,
current_user_id: currentUserId,
gitlab_url: gitlabUrl,
revision,
};
- process.env.NODE_ENV = isProduction;
process.env.HEAD_COMMIT_SHA = revision;
spyOn(RavenConfig, 'init');
@@ -25,12 +25,12 @@ describe('RavenConfig options', () => {
indexReturnValue = index();
});
- it('should init with .sentryDsn, .currentUserId, .whitelistUrls and .isProduction', () => {
+ it('should init with .sentryDsn, .currentUserId, .whitelistUrls and environment', () => {
expect(RavenConfig.init).toHaveBeenCalledWith({
sentryDsn,
currentUserId,
- whitelistUrls: [gitlabUrl],
- isProduction,
+ whitelistUrls: [gitlabUrl, 'webpack-internal://'],
+ environment,
release: revision,
tags: {
revision,
diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js
index 5cc59cc28d3..af634a0c196 100644
--- a/spec/javascripts/raven/raven_config_spec.js
+++ b/spec/javascripts/raven/raven_config_spec.js
@@ -69,8 +69,8 @@ describe('RavenConfig', () => {
let ravenConfig;
const options = {
sentryDsn: '//sentryDsn',
- whitelistUrls: ['//gitlabUrl'],
- isProduction: true,
+ whitelistUrls: ['//gitlabUrl', 'webpack-internal://'],
+ environment: 'test',
release: 'revision',
tags: {
revision: 'revision',
@@ -95,7 +95,7 @@ describe('RavenConfig', () => {
release: options.release,
tags: options.tags,
whitelistUrls: options.whitelistUrls,
- environment: 'production',
+ environment: 'test',
ignoreErrors: ravenConfig.IGNORE_ERRORS,
ignoreUrls: ravenConfig.IGNORE_URLS,
shouldSendCallback: jasmine.any(Function),
@@ -106,8 +106,8 @@ describe('RavenConfig', () => {
expect(raven.install).toHaveBeenCalled();
});
- it('should set .environment to development if isProduction is false', () => {
- ravenConfig.options.isProduction = false;
+ it('should set environment from options', () => {
+ ravenConfig.options.environment = 'development';
RavenConfig.configure.call(ravenConfig);
diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js
index b1af0f80a50..d1d01272403 100644
--- a/spec/javascripts/read_more_spec.js
+++ b/spec/javascripts/read_more_spec.js
@@ -1,7 +1,7 @@
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
- const fixtureName = 'projects/overview.html.raw';
+ const fixtureName = 'projects/overview.html';
preloadFixtures(fixtureName);
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 67118ac03a5..76a17e6fb31 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -99,7 +99,7 @@ describe('Registry List', () => {
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.spinner')).not.toBe(null);
done();
});
});
diff --git a/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js
new file mode 100644
index 00000000000..29760f79c3c
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js
@@ -0,0 +1,89 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
+import RelatedMergeRequests from '~/related_merge_requests/components/related_merge_requests.vue';
+import createStore from '~/related_merge_requests/store/index';
+
+const FIXTURE_PATH = 'issues/related_merge_requests.json';
+const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests';
+const localVue = createLocalVue();
+
+describe('RelatedMergeRequests', () => {
+ let wrapper;
+ let mock;
+ let mockData;
+
+ beforeEach(done => {
+ loadFixtures(FIXTURE_PATH);
+ mockData = getJSONFixture(FIXTURE_PATH);
+ mock = new MockAdapter(axios);
+ mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });
+
+ wrapper = mount(RelatedMergeRequests, {
+ localVue,
+ sync: false,
+ store: createStore(),
+ propsData: {
+ endpoint: API_ENDPOINT,
+ projectNamespace: 'gitlab-org',
+ projectPath: 'gitlab-ce',
+ },
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('methods', () => {
+ describe('getAssignees', () => {
+ const assignees = [{ name: 'foo' }, { name: 'bar' }];
+
+ describe('when there is assignees array', () => {
+ it('should return assignees array', () => {
+ const mr = { assignees };
+
+ expect(wrapper.vm.getAssignees(mr)).toEqual(assignees);
+ });
+ });
+
+ it('should return an array with single assingee', () => {
+ const mr = { assignee: assignees[0] };
+
+ expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]);
+ });
+
+ it('should return empty array when assignee is not set', () => {
+ expect(wrapper.vm.getAssignees({})).toEqual([]);
+ expect(wrapper.vm.getAssignees({ assignee: null })).toEqual([]);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render related merge request items', () => {
+ expect(wrapper.find('.js-items-count').text()).toEqual('2');
+ expect(wrapper.findAll(RelatedIssuableItem).length).toEqual(2);
+
+ const props = wrapper
+ .findAll(RelatedIssuableItem)
+ .at(1)
+ .props();
+ const data = mockData[1];
+
+ expect(props.idKey).toEqual(data.id);
+ expect(props.pathIdSeparator).toEqual('!');
+ expect(props.pipelineStatus).toBe(data.head_pipeline.detailed_status);
+ expect(props.assignees).toEqual([data.assignee]);
+ expect(props.isMergeRequest).toBe(true);
+ expect(props.confidential).toEqual(false);
+ expect(props.title).toEqual(data.title);
+ expect(props.state).toEqual(data.state);
+ expect(props.createdAt).toEqual(data.created_at);
+ });
+ });
+});
diff --git a/spec/javascripts/related_merge_requests/store/actions_spec.js b/spec/javascripts/related_merge_requests/store/actions_spec.js
new file mode 100644
index 00000000000..65e436fbb17
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/store/actions_spec.js
@@ -0,0 +1,110 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import * as types from '~/related_merge_requests/store/mutation_types';
+import actionsModule, * as actions from '~/related_merge_requests/store/actions';
+import testAction from 'spec/helpers/vuex_action_helper';
+
+describe('RelatedMergeRequest store actions', () => {
+ let state;
+ let flashSpy;
+ let mock;
+
+ beforeEach(() => {
+ state = {
+ apiEndpoint: '/api/related_merge_requests',
+ };
+ flashSpy = spyOnDependency(actionsModule, 'createFlash');
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('setInitialState', () => {
+ it('commits types.SET_INITIAL_STATE with given props', done => {
+ const props = { a: 1, b: 2 };
+
+ testAction(
+ actions.setInitialState,
+ props,
+ {},
+ [{ type: types.SET_INITIAL_STATE, payload: props }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestData', () => {
+ it('commits types.REQUEST_DATA', done => {
+ testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], [], done);
+ });
+ });
+
+ describe('receiveDataSuccess', () => {
+ it('commits types.RECEIVE_DATA_SUCCESS with data', done => {
+ const data = { a: 1, b: 2 };
+
+ testAction(
+ actions.receiveDataSuccess,
+ data,
+ {},
+ [{ type: types.RECEIVE_DATA_SUCCESS, payload: data }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveDataError', () => {
+ it('commits types.RECEIVE_DATA_ERROR', done => {
+ testAction(
+ actions.receiveDataError,
+ null,
+ {},
+ [{ type: types.RECEIVE_DATA_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchMergeRequests', () => {
+ describe('for a successful request', () => {
+ it('should dispatch success action', done => {
+ const data = { a: 1 };
+ mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(200, data, { 'x-total': 2 });
+
+ testAction(
+ actions.fetchMergeRequests,
+ null,
+ state,
+ [],
+ [{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }],
+ done,
+ );
+ });
+ });
+
+ describe('for a failing request', () => {
+ it('should dispatch error action', done => {
+ mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(400);
+
+ testAction(
+ actions.fetchMergeRequests,
+ null,
+ state,
+ [],
+ [{ type: 'requestData' }, { type: 'receiveDataError' }],
+ () => {
+ expect(flashSpy).toHaveBeenCalledTimes(1);
+ expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('Something went wrong'));
+
+ done();
+ },
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/related_merge_requests/store/mutations_spec.js b/spec/javascripts/related_merge_requests/store/mutations_spec.js
new file mode 100644
index 00000000000..21b6e26376b
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/store/mutations_spec.js
@@ -0,0 +1,49 @@
+import mutations from '~/related_merge_requests/store/mutations';
+import * as types from '~/related_merge_requests/store/mutation_types';
+
+describe('RelatedMergeRequests Store Mutations', () => {
+ describe('SET_INITIAL_STATE', () => {
+ it('should set initial state according to given data', () => {
+ const apiEndpoint = '/api';
+ const state = {};
+
+ mutations[types.SET_INITIAL_STATE](state, { apiEndpoint });
+
+ expect(state.apiEndpoint).toEqual(apiEndpoint);
+ });
+ });
+
+ describe('REQUEST_DATA', () => {
+ it('should set loading flag', () => {
+ const state = {};
+
+ mutations[types.REQUEST_DATA](state);
+
+ expect(state.isFetchingMergeRequests).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_DATA_SUCCESS', () => {
+ it('should set loading flag and data', () => {
+ const state = {};
+ const mrs = [1, 2, 3];
+
+ mutations[types.RECEIVE_DATA_SUCCESS](state, { data: mrs, total: mrs.length });
+
+ expect(state.isFetchingMergeRequests).toEqual(false);
+ expect(state.mergeRequests).toEqual(mrs);
+ expect(state.totalCount).toEqual(mrs.length);
+ });
+ });
+
+ describe('RECEIVE_DATA_ERROR', () => {
+ it('should set loading and error flags', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_DATA_ERROR](state);
+
+ expect(state.isFetchingMergeRequests).toEqual(false);
+ expect(state.hasErrorFetchingMergeRequests).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index 69767d9cf1c..a17494966a3 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
diff --git a/spec/javascripts/reports/components/modal_spec.js b/spec/javascripts/reports/components/modal_spec.js
index 6b8471381de..d42c509e5b5 100644
--- a/spec/javascripts/reports/components/modal_spec.js
+++ b/spec/javascripts/reports/components/modal_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/reports/components/modal.vue';
import state from '~/reports/store/state';
import mountComponent from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/vue_component_helper';
+import { trimText } from '../../helpers/text_helper';
describe('Grouped Test Reports Modal', () => {
const Component = Vue.extend(component);
diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js
deleted file mode 100644
index b02af8baaec..00000000000
--- a/spec/javascripts/reports/components/report_section_spec.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import Vue from 'vue';
-import reportSection from '~/reports/components/report_section.vue';
-import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
-
-describe('Report section', () => {
- let vm;
- const ReportSection = Vue.extend(reportSection);
-
- const resolvedIssues = [
- {
- name: 'Insecure Dependency',
- fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
- path: 'Gemfile.lock',
- line: 12,
- urlPath: 'foo/Gemfile.lock',
- },
- ];
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- beforeEach(() => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
- hasIssues: false,
- alwaysOpen: false,
- });
- });
-
- describe('isCollapsible', () => {
- const testMatrix = [
- { hasIssues: false, alwaysOpen: false, isCollapsible: false },
- { hasIssues: false, alwaysOpen: true, isCollapsible: false },
- { hasIssues: true, alwaysOpen: false, isCollapsible: true },
- { hasIssues: true, alwaysOpen: true, isCollapsible: false },
- ];
-
- testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => {
- const issues = hasIssues ? 'has issues' : 'has no issues';
- const open = alwaysOpen ? 'is always open' : 'is not always open';
-
- it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => {
- vm.hasIssues = hasIssues;
- vm.alwaysOpen = alwaysOpen;
-
- Vue.nextTick()
- .then(() => {
- expect(vm.isCollapsible).toBe(isCollapsible);
- })
- .then(done)
- .catch(done.fail);
- });
- });
- });
-
- describe('isExpanded', () => {
- const testMatrix = [
- { isCollapsed: false, alwaysOpen: false, isExpanded: true },
- { isCollapsed: false, alwaysOpen: true, isExpanded: true },
- { isCollapsed: true, alwaysOpen: false, isExpanded: false },
- { isCollapsed: true, alwaysOpen: true, isExpanded: true },
- ];
-
- testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => {
- const issues = isCollapsed ? 'is collapsed' : 'is not collapsed';
- const open = alwaysOpen ? 'is always open' : 'is not always open';
-
- it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => {
- vm.isCollapsed = isCollapsed;
- vm.alwaysOpen = alwaysOpen;
-
- Vue.nextTick()
- .then(() => {
- expect(vm.isExpanded).toBe(isExpanded);
- })
- .then(done)
- .catch(done.fail);
- });
- });
- });
- });
-
- describe('when it is loading', () => {
- it('should render loading indicator', () => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'LOADING',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- hasIssues: false,
- });
-
- expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report');
- });
- });
-
- describe('with success status', () => {
- beforeEach(() => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
- hasIssues: true,
- });
- });
-
- it('should render provided data', () => {
- expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
- 'Code quality improved on 1 point and degraded on 1 point',
- );
-
- expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual(
- resolvedIssues.length,
- );
- });
-
- describe('toggleCollapsed', () => {
- const hiddenCss = { display: 'none' };
-
- it('toggles issues', done => {
- vm.$el.querySelector('button').click();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
-
- vm.$el.querySelector('button').click();
- })
- .then(Vue.nextTick)
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('is always expanded, if always-open is set to true', done => {
- vm.alwaysOpen = true;
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button')).toBeNull();
- })
- .then(done)
- .catch(done.fail);
- });
- });
- });
-
- describe('with failed request', () => {
- it('should render error indicator', () => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'ERROR',
- loadingText: 'Loading codeclimate report',
- errorText: 'Failed to load codeclimate report',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- hasIssues: false,
- });
-
- expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report');
- });
- });
-
- describe('with action buttons passed to the slot', () => {
- beforeEach(() => {
- vm = mountComponentWithSlots(ReportSection, {
- props: {
- status: 'SUCCESS',
- successText: 'success',
- hasIssues: true,
- },
- slots: {
- actionButtons: ['Action!'],
- },
- });
- });
-
- it('should render the passed button', () => {
- expect(vm.$el.textContent.trim()).toContain('Action!');
- });
-
- it('should still render the expand/collapse button', () => {
- expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
- });
- });
-});
diff --git a/spec/javascripts/reports/components/test_issue_body_spec.js b/spec/javascripts/reports/components/test_issue_body_spec.js
index 32baf904ad7..9c1cec4c9bc 100644
--- a/spec/javascripts/reports/components/test_issue_body_spec.js
+++ b/spec/javascripts/reports/components/test_issue_body_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/reports/components/test_issue_body.vue';
import createStore from '~/reports/store';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { trimText } from '../../helpers/vue_component_helper';
+import { trimText } from '../../helpers/text_helper';
import { issue } from '../mock_data/mock_data';
describe('Test Issue body', () => {
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 992e17978c1..9565e3ce546 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -23,7 +23,7 @@ const assertSidebarState = function(state) {
describe('RightSidebar', function() {
describe('fixture tests', () => {
- const fixtureName = 'issues/open-issue.html.raw';
+ const fixtureName = 'issues/open-issue.html';
preloadFixtures(fixtureName);
loadJSONFixtures('todos/todos.json');
let mock;
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 7a4ca587313..ce7fa7a52ae 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => {
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
};
- preloadFixtures('static/search_autocomplete.html.raw');
+ preloadFixtures('static/search_autocomplete.html');
beforeEach(function() {
- loadFixtures('static/search_autocomplete.html.raw');
+ loadFixtures('static/search_autocomplete.html');
window.gon = {};
window.gon.current_user_id = userId;
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
index 40bdbac7451..32f60508fa3 100644
--- a/spec/javascripts/search_spec.js
+++ b/spec/javascripts/search_spec.js
@@ -3,7 +3,7 @@ import Api from '~/api';
import Search from '~/pages/search/show/search';
describe('Search', () => {
- const fixturePath = 'search/show.html.raw';
+ const fixturePath = 'search/show.html';
const searchTerm = 'some search';
const fillDropdownInput = dropdownSelector => {
const dropdownElement = document.querySelector(dropdownSelector).parentNode;
diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js
deleted file mode 100644
index bdf7a714910..00000000000
--- a/spec/javascripts/serverless/components/environment_row_spec.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import Vue from 'vue';
-
-import environmentRowComponent from '~/serverless/components/environment_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import ServerlessStore from '~/serverless/stores/serverless_store';
-
-import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
-
-const createComponent = (env, envName) =>
- mountComponent(Vue.extend(environmentRowComponent), { env, envName });
-
-describe('environment row component', () => {
- describe('default global cluster case', () => {
- let vm;
-
- beforeEach(() => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctions);
- vm = createComponent(store.state.functions['*'], '*');
- });
-
- it('has the correct envId', () => {
- expect(vm.envId).toEqual('env-global');
- vm.$destroy();
- });
-
- it('is open by default', () => {
- expect(vm.isOpenClass).toEqual({ 'is-open': true });
- vm.$destroy();
- });
-
- it('generates correct output', () => {
- expect(vm.$el.querySelectorAll('li').length).toEqual(2);
- expect(vm.$el.id).toEqual('env-global');
- expect(vm.$el.classList.contains('is-open')).toBe(true);
- expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*');
-
- vm.$destroy();
- });
-
- it('opens and closes correctly', () => {
- expect(vm.isOpen).toBe(true);
-
- vm.toggleOpen();
- Vue.nextTick(() => {
- expect(vm.isOpen).toBe(false);
- });
-
- vm.$destroy();
- });
- });
-
- describe('default named cluster case', () => {
- let vm;
-
- beforeEach(() => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctionsDiffEnv);
- vm = createComponent(store.state.functions.test, 'test');
- });
-
- it('has the correct envId', () => {
- expect(vm.envId).toEqual('env-test');
- vm.$destroy();
- });
-
- it('is open by default', () => {
- expect(vm.isOpenClass).toEqual({ 'is-open': true });
- vm.$destroy();
- });
-
- it('generates correct output', () => {
- expect(vm.$el.querySelectorAll('li').length).toEqual(1);
- expect(vm.$el.id).toEqual('env-test');
- expect(vm.$el.classList.contains('is-open')).toBe(true);
- expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test');
-
- vm.$destroy();
- });
- });
-});
diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js
deleted file mode 100644
index 6933a8f6c87..00000000000
--- a/spec/javascripts/serverless/components/function_row_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-
-import functionRowComponent from '~/serverless/components/function_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockServerlessFunction } from '../mock_data';
-
-const createComponent = func => mountComponent(Vue.extend(functionRowComponent), { func });
-
-describe('functionRowComponent', () => {
- it('Parses the function details correctly', () => {
- const vm = createComponent(mockServerlessFunction);
-
- expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name);
- expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image);
- expect(vm.$el.querySelector('time').getAttribute('data-original-title')).not.toBe(null);
- expect(vm.$el.querySelector('div.url-text-field').innerHTML).toEqual(
- mockServerlessFunction.url,
- );
-
- vm.$destroy();
- });
-
- it('handles clicks correctly', () => {
- const vm = createComponent(mockServerlessFunction);
-
- expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row
- expect(vm.checkClass(vm.$el.querySelector('svg'))).toBe(false); // check a button image
- expect(vm.checkClass(vm.$el.querySelector('div.url-text-field'))).toBe(false); // check the url bar
-
- vm.$destroy();
- });
-});
diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js
deleted file mode 100644
index 85cfe71281f..00000000000
--- a/spec/javascripts/serverless/components/functions_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import Vue from 'vue';
-
-import functionsComponent from '~/serverless/components/functions.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import ServerlessStore from '~/serverless/stores/serverless_store';
-
-import { mockServerlessFunctions } from '../mock_data';
-
-const createComponent = (
- functions,
- installed = true,
- loadingData = true,
- hasFunctionData = true,
-) => {
- const component = Vue.extend(functionsComponent);
-
- return mountComponent(component, {
- functions,
- installed,
- clustersPath: '/testClusterPath',
- helpPath: '/helpPath',
- loadingData,
- hasFunctionData,
- });
-};
-
-describe('functionsComponent', () => {
- it('should render empty state when Knative is not installed', () => {
- const vm = createComponent({}, false);
-
- expect(vm.$el.querySelector('div.row').classList.contains('js-empty-state')).toBe(true);
- expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual(
- 'Getting started with serverless',
- );
-
- vm.$destroy();
- });
-
- it('should render a loading component', () => {
- const vm = createComponent({});
-
- expect(vm.$el.querySelector('.gl-responsive-table-row')).not.toBe(null);
- expect(vm.$el.querySelector('div.animation-container')).not.toBe(null);
- });
-
- it('should render empty state when there is no function data', () => {
- const vm = createComponent({}, true, false, false);
-
- expect(
- vm.$el.querySelector('.empty-state, .js-empty-state').classList.contains('js-empty-state'),
- ).toBe(true);
-
- expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual(
- 'No functions available',
- );
-
- vm.$destroy();
- });
-
- it('should render the functions list', () => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctions);
- const vm = createComponent(store.state.functions, true, false);
-
- expect(vm.$el.querySelector('div.groups-list-tree-container')).not.toBe(null);
- expect(vm.$el.querySelector('#env-global').classList.contains('has-children')).toBe(true);
- });
-});
diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js
deleted file mode 100644
index 21a879a49bb..00000000000
--- a/spec/javascripts/serverless/components/url_spec.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue';
-
-import urlComponent from '~/serverless/components/url.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = uri => {
- const component = Vue.extend(urlComponent);
-
- return mountComponent(component, {
- uri,
- });
-};
-
-describe('urlComponent', () => {
- it('should render correctly', () => {
- const uri = 'http://testfunc.apps.example.com';
- const vm = createComponent(uri);
-
- expect(vm.$el.classList.contains('clipboard-group')).toBe(true);
- expect(vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text')).toEqual(
- uri,
- );
-
- expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri);
-
- vm.$destroy();
- });
-});
diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js
deleted file mode 100644
index ecd393b174c..00000000000
--- a/spec/javascripts/serverless/mock_data.js
+++ /dev/null
@@ -1,79 +0,0 @@
-export const mockServerlessFunctions = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
-
-export const mockServerlessFunctionsDiffEnv = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: 'test',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
-
-export const mockServerlessFunction = {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: '3',
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
-};
-
-export const mockMultilineServerlessFunction = {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: '3',
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'testfunc1\nA test service line\\nWith additional services',
- image: 'knative-test-container-buildtemplate',
-};
diff --git a/spec/javascripts/serverless/stores/serverless_store_spec.js b/spec/javascripts/serverless/stores/serverless_store_spec.js
deleted file mode 100644
index 72fd903d7d1..00000000000
--- a/spec/javascripts/serverless/stores/serverless_store_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import ServerlessStore from '~/serverless/stores/serverless_store';
-import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
-
-describe('Serverless Functions Store', () => {
- let store;
-
- beforeEach(() => {
- store = new ServerlessStore(false, '/cluster_path', 'help_path');
- });
-
- describe('#updateFunctionsFromServer', () => {
- it('should pass an empty hash object', () => {
- store.updateFunctionsFromServer();
-
- expect(store.state.functions).toEqual({});
- });
-
- it('should group functions to one global environment', () => {
- const mockServerlessData = mockServerlessFunctions;
- store.updateFunctionsFromServer(mockServerlessData);
-
- expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*']));
- expect(store.state.functions['*'].length).toEqual(2);
- });
-
- it('should group functions to multiple environments', () => {
- const mockServerlessData = mockServerlessFunctionsDiffEnv;
- store.updateFunctionsFromServer(mockServerlessData);
-
- expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*']));
- expect(store.state.functions['*'].length).toEqual(1);
- expect(store.state.functions.test.length).toEqual(1);
- expect(store.state.functions.test[0].name).toEqual('testfunc2');
- });
- });
-});
diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js
index 3b681a9ff28..2c5d91a45bc 100644
--- a/spec/javascripts/settings_panels_spec.js
+++ b/spec/javascripts/settings_panels_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import initSettingsPanels from '~/settings_panels';
describe('Settings Panels', () => {
- preloadFixtures('groups/edit.html.raw');
+ preloadFixtures('groups/edit.html');
beforeEach(() => {
- loadFixtures('groups/edit.html.raw');
+ loadFixtures('groups/edit.html');
});
describe('initSettingsPane', () => {
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
index 3ca6ecaa938..df7012bb659 100644
--- a/spec/javascripts/shortcuts_spec.js
+++ b/spec/javascripts/shortcuts_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
describe('Shortcuts', () => {
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
const createEvent = (type, target) =>
$.Event(type, {
target,
diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js
index eced4925489..4ae2141d5f0 100644
--- a/spec/javascripts/sidebar/assignees_spec.js
+++ b/spec/javascripts/sidebar/assignees_spec.js
@@ -24,12 +24,12 @@ describe('Assignee component', () => {
const collapsed = component.$el.querySelector('.sidebar-collapsed-icon');
expect(collapsed.childElementCount).toEqual(1);
- expect(collapsed.children[0].getAttribute('aria-label')).toEqual('No Assignee');
+ expect(collapsed.children[0].getAttribute('aria-label')).toEqual('None');
expect(collapsed.children[0].classList.contains('fa')).toEqual(true);
expect(collapsed.children[0].classList.contains('fa-user')).toEqual(true);
});
- it('displays only "No assignee" when no users are assigned and the issue is read-only', () => {
+ it('displays only "None" when no users are assigned and the issue is read-only', () => {
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
@@ -39,11 +39,11 @@ describe('Assignee component', () => {
}).$mount();
const componentTextNoUsers = component.$el.querySelector('.assign-yourself').innerText.trim();
- expect(componentTextNoUsers).toBe('No assignee');
+ expect(componentTextNoUsers).toBe('None');
expect(componentTextNoUsers.indexOf('assign yourself')).toEqual(-1);
});
- it('displays only "No assignee" when no users are assigned and the issue can be edited', () => {
+ it('displays only "None" when no users are assigned and the issue can be edited', () => {
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
@@ -53,7 +53,7 @@ describe('Assignee component', () => {
}).$mount();
const componentTextNoUsers = component.$el.querySelector('.assign-yourself').innerText.trim();
- expect(componentTextNoUsers.indexOf('No assignee')).toEqual(0);
+ expect(componentTextNoUsers.indexOf('None')).toEqual(0);
expect(componentTextNoUsers.indexOf('assign yourself')).toBeGreaterThan(0);
});
@@ -132,9 +132,94 @@ describe('Assignee component', () => {
-1,
);
});
+
+ it('has correct "cannot merge" tooltip when user cannot merge', () => {
+ const user = Object.assign({}, UsersMock.user, { can_merge: false });
+
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000/',
+ users: [user],
+ editable: true,
+ issuableType: 'merge_request',
+ },
+ }).$mount();
+
+ expect(component.mergeNotAllowedTooltipMessage).toEqual('Cannot merge');
+ });
});
describe('Two or more assignees/users', () => {
+ it('has correct "cannot merge" tooltip when one user can merge', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = true;
+ users[1].can_merge = false;
+ users[2].can_merge = false;
+
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000/',
+ users,
+ editable: true,
+ issuableType: 'merge_request',
+ },
+ }).$mount();
+
+ expect(component.mergeNotAllowedTooltipMessage).toEqual('1/3 can merge');
+ });
+
+ it('has correct "cannot merge" tooltip when no user can merge', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(2);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000/',
+ users,
+ editable: true,
+ issuableType: 'merge_request',
+ },
+ }).$mount();
+
+ expect(component.mergeNotAllowedTooltipMessage).toEqual('No one can merge');
+ });
+
+ it('has correct "cannot merge" tooltip when more than one user can merge', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = true;
+ users[2].can_merge = true;
+
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000/',
+ users,
+ editable: true,
+ issuableType: 'merge_request',
+ },
+ }).$mount();
+
+ expect(component.mergeNotAllowedTooltipMessage).toEqual('2/3 can merge');
+ });
+
+ it('has no "cannot merge" tooltip when every user can merge', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(2);
+ users[0].can_merge = true;
+ users[1].can_merge = true;
+
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000/',
+ users,
+ editable: true,
+ issuableType: 'merge_request',
+ },
+ }).$mount();
+
+ expect(component.mergeNotAllowedTooltipMessage).toEqual(null);
+ });
+
it('displays two assignee icons when collapsed', () => {
const users = UsersMockHelper.createNumberRandomUsers(2);
component = new AssigneeComponent({
@@ -210,6 +295,19 @@ describe('Assignee component', () => {
expect(component.$el.querySelector('.user-list-more')).toBe(null);
});
+ it('sets tooltip container to body', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(2);
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000',
+ users,
+ editable: true,
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body');
+ });
+
it('Shows the "show-less" assignees label', done => {
const users = UsersMockHelper.createNumberRandomUsers(6);
component = new AssigneeComponent({
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index 3f0f67d71ca..016f5e033a5 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -11,12 +11,12 @@ describe('sidebar assignees', () => {
let vm;
let mediator;
let sidebarAssigneesEl;
- preloadFixtures('issues/open-issue.html.raw');
+ preloadFixtures('issues/open-issue.html');
beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
- loadFixtures('issues/open-issue.html.raw');
+ loadFixtures('issues/open-issue.html');
mediator = new SidebarMediator(Mock.mediator);
spyOn(mediator, 'saveAssignees').and.callThrough();
diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js
index 657e88ecb96..f46ea5a0499 100644
--- a/spec/javascripts/sidebar/todo_spec.js
+++ b/spec/javascripts/sidebar/todo_spec.js
@@ -116,7 +116,7 @@ describe('SidebarTodo', () => {
const dataAttributes = {
issuableId: '1',
issuableType: 'epic',
- originalTitle: 'Mark todo as done',
+ originalTitle: '',
placement: 'left',
container: 'body',
boundary: 'viewport',
@@ -130,6 +130,10 @@ describe('SidebarTodo', () => {
});
});
+ it('check button label computed property', () => {
+ expect(vm.buttonLabel).toEqual('Mark todo as done');
+ });
+
it('renders button label element when `collapsed` prop is `false`', () => {
const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner');
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index 52da6a79939..ef5c774736b 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
describe('SigninTabsMemoizer', () => {
- const fixtureTemplate = 'static/signin_tabs.html.raw';
+ const fixtureTemplate = 'static/signin_tabs.html';
const tabSelector = 'ul.new-session-tabs';
const currentTabKey = 'current_signin_tab';
let memo;
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index b2b0a50911d..8c80a425581 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -8,6 +8,7 @@ import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
+import CheckEE from '~/vue_shared/mixins/is_ee';
import jasmineDiff from 'jasmine-diff';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
@@ -43,6 +44,7 @@ Vue.config.errorHandler = function(err) {
Vue.use(VueResource);
Vue.use(Translate);
+Vue.use(CheckEE);
// enable test fixtures
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
@@ -67,6 +69,7 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
+window.gon.ee = process.env.IS_GITLAB_EE;
gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
@@ -108,7 +111,7 @@ let longRunningTestTimeoutHandle;
beforeEach(done => {
longRunningTestTimeoutHandle = setTimeout(() => {
done.fail('Test is running too long!');
- }, 2000);
+ }, 4000);
done();
});
@@ -119,19 +122,26 @@ afterEach(() => {
const axiosDefaultAdapter = getDefaultAdapter();
// render all of our tests
-const testsContext = require.context('.', true, /_spec$/);
-testsContext.keys().forEach(function(path) {
- try {
- testsContext(path);
- } catch (err) {
- console.log(err);
- console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
- describe('Test bundle', function() {
- it(`includes '${path}'`, function() {
- expect(err).toBeNull();
+const testContexts = [require.context('spec', true, /_spec$/)];
+
+if (process.env.IS_GITLAB_EE) {
+ testContexts.push(require.context('ee_spec', true, /_spec$/));
+}
+
+testContexts.forEach(context => {
+ context.keys().forEach(path => {
+ try {
+ context(path);
+ } catch (err) {
+ console.log(err);
+ console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
+ describe('Test bundle', function() {
+ it(`includes '${path}'`, function() {
+ expect(err).toBeNull();
+ });
});
- });
- }
+ }
+ });
});
describe('test errors', () => {
@@ -201,24 +211,35 @@ if (process.env.BABEL_ENV === 'coverage') {
];
describe('Uncovered files', function() {
- const sourceFiles = require.context('~', true, /\.(js|vue)$/);
+ const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
+
+ if (process.env.IS_GITLAB_EE) {
+ sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/));
+ }
+
+ const allTestFiles = testContexts.reduce(
+ (accumulator, context) => accumulator.concat(context.keys()),
+ [],
+ );
$.holdReady(true);
- sourceFiles.keys().forEach(function(path) {
- // ignore if there is a matching spec file
- if (testsContext.keys().indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) {
- return;
- }
-
- it(`includes '${path}'`, function() {
- try {
- sourceFiles(path);
- } catch (err) {
- if (troubleMakers.indexOf(path) === -1) {
- expect(err).toBeNull();
- }
+ sourceFilesContexts.forEach(context => {
+ context.keys().forEach(path => {
+ // ignore if there is a matching spec file
+ if (allTestFiles.indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) {
+ return;
}
+
+ it(`includes '${path}'`, function() {
+ try {
+ context(path);
+ } catch (err) {
+ if (troubleMakers.indexOf(path) === -1) {
+ expect(err).toBeNull();
+ }
+ }
+ });
});
});
});
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
index a820dd2d09c..77c206585fe 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -1,7 +1,9 @@
-export const FIXTURES_PATH = '/base/spec/javascripts/fixtures';
+export const FIXTURES_PATH = `/base/${
+ process.env.IS_GITLAB_EE ? 'ee/' : ''
+}spec/javascripts/fixtures`;
export const TEST_HOST = 'http://test.host';
-export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`;
+export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
-export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/green_box.png`;
-export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/red_box.png`;
+export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
+export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 69e43274250..802f54f6a7e 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
- preloadFixtures('todos/todos.html.raw');
+ preloadFixtures('todos/todos.html');
let todoItem;
beforeEach(() => {
- loadFixtures('todos/todos.html.raw');
+ loadFixtures('todos/todos.html');
todoItem = document.querySelector('.todos-list .todo');
return new Todos();
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index ddb09811dda..8f9cb270729 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -4,10 +4,10 @@ import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', function() {
- preloadFixtures('u2f/authenticate.html.raw');
+ preloadFixtures('u2f/authenticate.html');
beforeEach(() => {
- loadFixtures('u2f/authenticate.html.raw');
+ loadFixtures('u2f/authenticate.html');
this.u2fDevice = new MockU2FDevice();
this.container = $('#js-authenticate-u2f');
this.component = new U2FAuthenticate(
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 261db3d66d7..a75ceca9f4c 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -4,10 +4,10 @@ import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FRegister', function() {
- preloadFixtures('u2f/register.html.raw');
+ preloadFixtures('u2f/register.html');
beforeEach(done => {
- loadFixtures('u2f/register.html.raw');
+ loadFixtures('u2f/register.html');
this.u2fDevice = new MockU2FDevice();
this.container = $('#js-register-u2f');
this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token');
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
index b174a51c1a0..c0d5ee9c446 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/javascripts/user_popovers_spec.js
@@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers';
import UsersCache from '~/lib/utils/users_cache';
describe('User Popovers', () => {
- const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ const fixtureTemplate = 'merge_requests/diff_comment.html';
preloadFixtures(fixtureTemplate);
const selector = '.js-user-link';
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js
new file mode 100644
index 00000000000..8ec17efffb9
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js
@@ -0,0 +1,77 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue';
+import { GlLink } from '@gitlab/ui';
+
+describe('MrWidgetAlertMessage', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ const localVue = createLocalVue();
+
+ wrapper = shallowMount(localVue.extend(MrWidgetAlertMessage), {
+ propsData: {},
+ localVue,
+ sync: false,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when type is not provided', () => {
+ it('should render a red message', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.classes()).toContain('danger_message');
+ expect(wrapper.classes()).not.toContain('warning_message');
+ done();
+ });
+ });
+ });
+
+ describe('when type === "danger"', () => {
+ it('should render a red message', done => {
+ wrapper.setProps({ type: 'danger' });
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.classes()).toContain('danger_message');
+ expect(wrapper.classes()).not.toContain('warning_message');
+ done();
+ });
+ });
+ });
+
+ describe('when type === "warning"', () => {
+ it('should render a red message', done => {
+ wrapper.setProps({ type: 'warning' });
+ wrapper.vm.$nextTick(() => {
+ expect(wrapper.classes()).toContain('warning_message');
+ expect(wrapper.classes()).not.toContain('danger_message');
+ done();
+ });
+ });
+ });
+
+ describe('when helpPath is not provided', () => {
+ it('should not render a help icon/link', done => {
+ wrapper.vm.$nextTick(() => {
+ const link = wrapper.find(GlLink);
+
+ expect(link.exists()).toBe(false);
+ done();
+ });
+ });
+ });
+
+ describe('when helpPath is provided', () => {
+ it('should render a help icon/link', done => {
+ wrapper.setProps({ helpPath: '/path/to/help/docs' });
+ wrapper.vm.$nextTick(() => {
+ const link = wrapper.find(GlLink);
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes().href).toBe('/path/to/help/docs');
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 02c476f2871..cd77b0ab815 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -15,6 +15,16 @@ describe('MRWidgetHeader', () => {
gon.relative_url_root = '';
});
+ const expectDownloadDropdownItems = () => {
+ const downloadEmailPatchesEl = vm.$el.querySelector('.js-download-email-patches');
+ const downloadPlainDiffEl = vm.$el.querySelector('.js-download-plain-diff');
+
+ expect(downloadEmailPatchesEl.textContent.trim()).toEqual('Email patches');
+ expect(downloadEmailPatchesEl.getAttribute('href')).toEqual('/mr/email-patches');
+ expect(downloadPlainDiffEl.textContent.trim()).toEqual('Plain diff');
+ expect(downloadPlainDiffEl.getAttribute('href')).toEqual('/mr/plainDiffPath');
+ };
+
describe('computed', () => {
describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => {
@@ -207,21 +217,7 @@ describe('MRWidgetHeader', () => {
});
it('renders download dropdown with links', () => {
- expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual(
- 'Email patches',
- );
-
- expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual(
- '/mr/email-patches',
- );
-
- expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual(
- 'Plain diff',
- );
-
- expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual(
- '/mr/plainDiffPath',
- );
+ expectDownloadDropdownItems();
});
});
@@ -250,10 +246,8 @@ describe('MRWidgetHeader', () => {
expect(button).toEqual(null);
});
- it('does not render download dropdown with links', () => {
- expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null);
-
- expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null);
+ it('renders download dropdown with links', () => {
+ expectDownloadDropdownItems();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
index e5155573f6f..dfbc68c48b9 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, createLocalVue } from '@vue/test-utils';
import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue';
import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import { mockStore } from '../mock_data';
@@ -9,7 +9,7 @@ describe('MrWidgetPipelineContainer', () => {
const factory = (props = {}) => {
const localVue = createLocalVue();
- wrapper = shallowMount(localVue.extend(MrWidgetPipelineContainer), {
+ wrapper = mount(localVue.extend(MrWidgetPipelineContainer), {
propsData: {
mr: Object.assign({}, mockStore),
...props,
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 d905bbe4040..a2308b0dfdb 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
@@ -1,6 +1,7 @@
import Vue from 'vue';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { trimText } from 'spec/helpers/text_helper';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
@@ -77,6 +78,19 @@ describe('MRWidgetPipeline', () => {
);
});
+ it('should render CI error when no pipeline is provided', () => {
+ vm = mountComponent(Component, {
+ pipeline: {},
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ });
+
+ expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
+ 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
+ );
+ });
+
describe('with a pipeline', () => {
beforeEach(() => {
vm = mountComponent(Component, {
@@ -89,7 +103,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
+ `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
);
});
@@ -123,7 +137,7 @@ describe('MRWidgetPipeline', () => {
describe('without commit path', () => {
beforeEach(() => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit;
vm = mountComponent(Component, {
@@ -136,7 +150,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
+ `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
);
});
@@ -164,7 +178,7 @@ describe('MRWidgetPipeline', () => {
describe('without coverage', () => {
it('should not render a coverage', () => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage;
vm = mountComponent(Component, {
@@ -180,7 +194,7 @@ describe('MRWidgetPipeline', () => {
describe('without a pipeline graph', () => {
it('should not render a pipeline graph', () => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages;
vm = mountComponent(Component, {
@@ -193,5 +207,83 @@ describe('MRWidgetPipeline', () => {
expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
});
});
+
+ describe('without pipeline.merge_request', () => {
+ it('should render info that includes the commit and branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ delete mockCopy.pipeline.merge_request;
+ const { pipeline } = mockCopy;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: mockCopy.source_branch_link,
+ });
+
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`;
+
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+
+ expect(actual).toBe(expected);
+ });
+ });
+
+ describe('with pipeline.merge_request and flags.merge_request_pipeline', () => {
+ it('should render info that includes the commit, MR, source branch, and target branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ const { pipeline } = mockCopy;
+ pipeline.flags.merge_request_pipeline = true;
+ pipeline.flags.detached_merge_request_pipeline = false;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: mockCopy.source_branch_link,
+ });
+
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ } into ${pipeline.merge_request.target_branch}`;
+
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+
+ expect(actual).toBe(expected);
+ });
+ });
+
+ describe('with pipeline.merge_request and flags.detached_merge_request_pipeline', () => {
+ it('should render info that includes the commit, MR, and source branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ const { pipeline } = mockCopy;
+ pipeline.flags.merge_request_pipeline = false;
+ pipeline.flags.detached_merge_request_pipeline = true;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: mockCopy.source_branch_link,
+ });
+
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ }`;
+
+ const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+
+ expect(actual).toBe(expected);
+ });
+ });
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index a0a336ae604..f622f52a7b9 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
});
});
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 eb4fa0df727..d93badf8cd3 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
@@ -38,7 +38,7 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
done();
});
});
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 7da27bb8890..96e512d222a 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
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
});
it('renders information about merging', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
deleted file mode 100644
index 5cf6408cf34..00000000000
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-
-const localVue = createLocalVue();
-
-describe('Commits header component', () => {
- let wrapper;
-
- const createComponent = props => {
- wrapper = shallowMount(localVue.extend(CommitsHeader), {
- localVue,
- sync: false,
- propsData: {
- isSquashEnabled: false,
- targetBranch: 'master',
- commitsCount: 5,
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count');
- const findCommitToggle = () => wrapper.find('.commit-edit-toggle');
- const findIcon = () => wrapper.find(Icon);
- const findCommitsCountMessage = () => wrapper.find('.commits-count-message');
- const findTargetBranchMessage = () => wrapper.find('.label-branch');
- const findModifyButton = () => wrapper.find('.modify-message-button');
-
- describe('when collapsed', () => {
- it('toggle has aria-label equal to Expand', () => {
- createComponent();
-
- expect(findCommitToggle().attributes('aria-label')).toBe('Expand');
- });
-
- it('has a chevron-right icon', () => {
- createComponent();
- wrapper.setData({ expanded: false });
-
- expect(findIcon().props('name')).toBe('chevron-right');
- });
-
- describe('when squash is disabled', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('has commits count message showing correct amount of commits', () => {
- expect(findCommitsCountMessage().text()).toBe('5 commits');
- });
-
- it('has button with modify merge commit message', () => {
- expect(findModifyButton().text()).toBe('Modify merge commit');
- });
- });
-
- describe('when squash is enabled', () => {
- beforeEach(() => {
- createComponent({ isSquashEnabled: true });
- });
-
- it('has commits count message showing one commit when squash is enabled', () => {
- expect(findCommitsCountMessage().text()).toBe('1 commit');
- });
-
- it('has button with modify commit messages text', () => {
- expect(findModifyButton().text()).toBe('Modify commit messages');
- });
- });
-
- it('has correct target branch displayed', () => {
- createComponent();
-
- expect(findTargetBranchMessage().text()).toBe('master');
- });
- });
-
- describe('when expanded', () => {
- beforeEach(() => {
- createComponent();
- wrapper.setData({ expanded: true });
- });
-
- it('toggle has aria-label equal to collapse', done => {
- wrapper.vm.$nextTick(() => {
- expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
- done();
- });
- });
-
- it('has a chevron-down icon', done => {
- wrapper.vm.$nextTick(() => {
- expect(findIcon().props('name')).toBe('chevron-down');
- done();
- });
- });
-
- it('has a collapse text', done => {
- wrapper.vm.$nextTick(() => {
- expect(findHeaderWrapper().text()).toBe('Collapse');
- done();
- });
- });
- });
-});
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 0ddbdf67d8b..39b879612ae 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
@@ -1,7 +1,7 @@
import $ from 'jquery';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
-import { removeBreakLine } from 'spec/helpers/vue_component_helper';
+import { removeBreakLine } from 'spec/helpers/text_helper';
describe('MRWidgetConflicts', () => {
let vm;
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 3229ddd5e27..780bed1bf69 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
@@ -120,7 +120,7 @@ describe('MRWidgetFailedToMerge', () => {
it('renders given error', () => {
expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual(
- 'Merge error happened.',
+ 'Merge error happened',
);
});
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 b9718a78fa4..8e0415b813b 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
@@ -21,7 +21,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
canCancelAutomaticMerge: true,
mergeUserId: 1,
currentUserId: 1,
- setToMWPSBy: {},
+ setToAutoMergeBy: {},
sha,
targetBranchPath,
targetBranch,
@@ -106,7 +106,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.merge).toHaveBeenCalledWith({
sha,
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true,
});
done();
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 477041fa383..1d2f3e41509 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
@@ -1,7 +1,7 @@
import Vue from 'vue';
import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { removeBreakLine } from 'spec/helpers/vue_component_helper';
+import { removeBreakLine } from 'spec/helpers/text_helper';
describe('MRWidgetPipelineBlocked', () => {
let vm;
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 f7523a01963..3e4ce2c3696 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
@@ -1,6 +1,6 @@
import Vue from 'vue';
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
-import { removeBreakLine } from 'spec/helpers/vue_component_helper';
+import { removeBreakLine } from 'spec/helpers/text_helper';
describe('PipelineFailed', () => {
describe('template', () => {
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 631da202d1d..3ae773b6ccb 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
@@ -18,6 +18,7 @@ const createTestMr = customConfig => {
isPipelinePassing: false,
isMergeAllowed: true,
onlyAllowMergeIfPipelineSucceeds: false,
+ ffOnlyEnabled: false,
hasCI: false,
ciStatus: null,
sha: '12345678',
@@ -79,7 +80,7 @@ describe('ReadyToMerge', () => {
it('should have default data', () => {
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.showCommitMessageEditor).toBeFalsy();
expect(vm.isMakingRequest).toBeFalsy();
expect(vm.isMergingImmediately).toBeFalsy();
@@ -90,17 +91,17 @@ describe('ReadyToMerge', () => {
});
describe('computed', () => {
- describe('shouldShowMergeWhenPipelineSucceedsText', () => {
+ describe('shouldShowAutoMergeText', () => {
it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy();
+ expect(vm.shouldShowAutoMergeText).toBeTruthy();
});
it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy();
+ expect(vm.shouldShowAutoMergeText).toBeFalsy();
});
});
@@ -324,16 +325,20 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(true);
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeTruthy();
+ expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds');
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
const params = vm.service.merge.calls.argsFor(0)[0];
- expect(params.sha).toEqual(vm.mr.sha);
- expect(params.commit_message).toEqual(vm.mr.commitMessage);
- expect(params.should_remove_source_branch).toBeFalsy();
- expect(params.merge_when_pipeline_succeeds).toBeTruthy();
+ expect(params).toEqual(
+ jasmine.objectContaining({
+ sha: vm.mr.sha,
+ commit_message: vm.mr.commitMessage,
+ should_remove_source_branch: false,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
+ }),
+ );
done();
}, 333);
});
@@ -344,7 +349,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(false, true);
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
@@ -362,7 +367,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick();
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(vm.initiateMergePolling).toHaveBeenCalled();
@@ -376,11 +381,29 @@ describe('ReadyToMerge', () => {
});
describe('initiateMergePolling', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
it('should call simplePoll', () => {
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateMergePolling();
- expect(simplePoll).toHaveBeenCalled();
+ expect(simplePoll).toHaveBeenCalledWith(jasmine.any(Function), { timeout: 0 });
+ });
+
+ it('should call handleMergePolling', () => {
+ spyOn(vm, 'handleMergePolling');
+
+ vm.initiateMergePolling();
+
+ jasmine.clock().tick(2000);
+
+ expect(vm.handleMergePolling).toHaveBeenCalled();
});
});
@@ -396,7 +419,7 @@ describe('ReadyToMerge', () => {
});
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ loadFixtures('merge_requests/merge_request_of_current_user.html');
});
it('should call start and stop polling when MR merged', done => {
@@ -624,6 +647,10 @@ describe('ReadyToMerge', () => {
const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
const findCommitEditElements = () => wrapper.findAll(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
+ const findFirstCommitEditLabel = () =>
+ findCommitEditElements()
+ .at(0)
+ .props('label');
describe('squash checkbox', () => {
it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
@@ -648,32 +675,130 @@ describe('ReadyToMerge', () => {
});
describe('commits count collapsible header', () => {
- it('should be rendered if fast-forward is disabled', () => {
+ it('should be rendered when fast-forward is disabled', () => {
createLocalComponent();
expect(findCommitsHeaderElement().exists()).toBeTruthy();
});
- it('should not be rendered if fast-forward is enabled', () => {
- createLocalComponent({ mr: { ffOnlyEnabled: true } });
+ describe('when fast-forward is enabled', () => {
+ it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ enableSquashBeforeMerge: true,
+ squash: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered if squash before merge is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ enableSquashBeforeMerge: false,
+ squash: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: false,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered if commits count is 1', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 1,
+ },
+ });
- expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
});
});
describe('commits edit components', () => {
+ describe('when fast-forward merge is enabled', () => {
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: false,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should not be rendered if squash before merge is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: false,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should not be rendered if there is only one commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 1,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should have one edit component if squash is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(1);
+ expect(findFirstCommitEditLabel()).toBe('Squash commit message');
+ });
+ });
+
it('should have one edit component when squash is disabled', () => {
createLocalComponent();
expect(findCommitEditElements().length).toBe(1);
});
- const findFirstCommitEditLabel = () =>
- findCommitEditElements()
- .at(0)
- .props('label');
-
- it('should have two edit components when squash is enabled', () => {
+ it('should have two edit components when squash is enabled and there is more than 1 commit', () => {
createLocalComponent({
mr: {
commitsCount: 2,
@@ -685,6 +810,18 @@ describe('ReadyToMerge', () => {
expect(findCommitEditElements().length).toBe(2);
});
+ it('should have one edit components when squash is enabled and there is 1 commit only', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 1,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(1);
+ });
+
it('should have correct edit merge commit label', () => {
createLocalComponent();
@@ -711,8 +848,10 @@ describe('ReadyToMerge', () => {
expect(findCommitDropdownElement().exists()).toBeFalsy();
});
- it('should be rendered if squash is enabled', () => {
- createLocalComponent({ mr: { squash: true } });
+ it('should be rendered if squash is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 },
+ });
expect(findCommitDropdownElement().exists()).toBeTruthy();
});
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 36f8c7a9683..9324c83bf4b 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
@@ -1,7 +1,7 @@
import Vue from 'vue';
import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { removeBreakLine } from 'spec/helpers/vue_component_helper';
+import { removeBreakLine } from 'spec/helpers/text_helper';
describe('ShaMismatch', () => {
let vm;
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 6ef07f81705..edbd0d54151 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -62,6 +62,7 @@ export default {
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
pipeline: {
id: 172,
+ iid: 32,
user: {
name: 'Administrator',
username: 'root',
@@ -134,6 +135,8 @@ export default {
yaml_errors: false,
retryable: true,
cancelable: false,
+ merge_request_pipeline: false,
+ detached_merge_request_pipeline: true,
},
ref: {
name: 'daaaa',
@@ -141,6 +144,15 @@ export default {
tag: false,
branch: true,
},
+ merge_request: {
+ iid: 1,
+ path: '/root/detached-merge-request-pipelines/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
commit: {
id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
short_id: '104096c5',
@@ -222,12 +234,50 @@ export default {
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
+ merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
squash: true,
+ visual_review_app_available: true,
};
export const mockStore = {
- pipeline: { id: 0 },
- mergePipeline: { id: 1 },
+ pipeline: {
+ id: 0,
+ iid: 0,
+ path: '/root/acets-app/pipelines/0',
+ details: {
+ status: {
+ details_path: '/root/review-app-tester/pipelines/66',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png',
+ group: 'success-with-warnings',
+ has_details: true,
+ icon: 'status_warning',
+ illustration: null,
+ label: 'passed with warnings',
+ text: 'passed',
+ tooltip: 'passed',
+ },
+ },
+ },
+ mergePipeline: {
+ id: 1,
+ iid: 1,
+ path: '/root/acets-app/pipelines/0',
+ details: {
+ status: {
+ details_path: '/root/review-app-tester/pipelines/66',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png',
+ group: 'success-with-warnings',
+ has_details: true,
+ icon: 'status_warning',
+ illustration: null,
+ label: 'passed with warnings',
+ text: 'passed',
+ tooltip: 'passed',
+ },
+ },
+ },
targetBranch: 'target-branch',
sourceBranch: 'source-branch',
sourceBranchLink: 'source-branch-link',
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 3e8f73646c8..918717c4547 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -21,7 +21,6 @@ describe('mrWidgetOptions', () => {
const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';
beforeEach(() => {
- gon.features = { approvalRules: false };
// Prevent component mounting
delete mrWidgetOptions.el;
@@ -32,7 +31,6 @@ describe('mrWidgetOptions', () => {
});
afterEach(() => {
- gon.features = null;
vm.$destroy();
});
@@ -183,6 +181,101 @@ describe('mrWidgetOptions', () => {
});
});
});
+
+ describe('showMergePipelineForkWarning', () => {
+ describe('when the source project and target project are the same', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 1);
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(false);
+ });
+ });
+
+ describe('when merge pipelines are not enabled', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', false);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 2);
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(false);
+ });
+ });
+
+ describe('when merge pipelines are enabled _and_ the source project and target project are different', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 2);
+ vm.$nextTick(done);
+ });
+
+ it('should be true', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(true);
+ });
+ });
+ });
+
+ describe('showTargetBranchAdvancedError', () => {
+ describe(`when the pipeline's target_sha property doesn't exist`, () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'isOpen', true);
+ Vue.set(vm.mr.pipeline, 'target_sha', undefined);
+ Vue.set(vm.mr, 'targetBranchSha', 'abcd');
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showTargetBranchAdvancedError).toEqual(false);
+ });
+ });
+
+ describe(`when the pipeline's target_sha matches the target branch's sha`, () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'isOpen', true);
+ Vue.set(vm.mr.pipeline, 'target_sha', 'abcd');
+ Vue.set(vm.mr, 'targetBranchSha', 'abcd');
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showTargetBranchAdvancedError).toEqual(false);
+ });
+ });
+
+ describe(`when the merge request is not open`, () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'isOpen', false);
+ Vue.set(vm.mr.pipeline, 'target_sha', 'abcd');
+ Vue.set(vm.mr, 'targetBranchSha', 'bcde');
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showTargetBranchAdvancedError).toEqual(false);
+ });
+ });
+
+ describe(`when the pipeline's target_sha does not match the target branch's sha`, () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'isOpen', true);
+ Vue.set(vm.mr.pipeline, 'target_sha', 'abcd');
+ Vue.set(vm.mr, 'targetBranchSha', 'bcde');
+ vm.$nextTick(done);
+ });
+
+ it('should be true', () => {
+ expect(vm.showTargetBranchAdvancedError).toEqual(true);
+ });
+ });
+ });
});
describe('methods', () => {
@@ -505,6 +598,7 @@ describe('mrWidgetOptions', () => {
];
const deploymentMockData = {
id: 15,
+ iid: 7,
name: 'review/diplo',
url: '/root/acets-review-apps/environments/15',
stop_url: '/root/acets-review-apps/environments/15/stop',
@@ -551,6 +645,7 @@ describe('mrWidgetOptions', () => {
vm.mr.state = 'merged';
vm.mr.mergePipeline = {
id: 127,
+ iid: 35,
user: {
id: 1,
name: 'Administrator',
diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
deleted file mode 100644
index b356ea85cad..00000000000
--- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import getStateKey from '~/vue_merge_request_widget/stores/get_state_key';
-
-describe('getStateKey', () => {
- it('should return proper state name', () => {
- const context = {
- mergeStatus: 'checked',
- mergeWhenPipelineSucceeds: false,
- canMerge: true,
- onlyAllowMergeIfPipelineSucceeds: false,
- isPipelineFailed: false,
- hasMergeableDiscussionsState: false,
- isPipelineBlocked: false,
- canBeMerged: false,
- };
- const data = {
- project_archived: false,
- branch_missing: false,
- commits_count: 2,
- has_conflicts: false,
- work_in_progress: false,
- };
- const bound = getStateKey.bind(context, data);
-
- expect(bound()).toEqual(null);
-
- context.canBeMerged = true;
-
- expect(bound()).toEqual('readyToMerge');
-
- context.canMerge = false;
-
- expect(bound()).toEqual('notAllowedToMerge');
-
- context.mergeWhenPipelineSucceeds = true;
-
- expect(bound()).toEqual('mergeWhenPipelineSucceeds');
-
- context.isSHAMismatch = true;
-
- expect(bound()).toEqual('shaMismatch');
-
- context.isPipelineBlocked = true;
-
- expect(bound()).toEqual('pipelineBlocked');
-
- context.hasMergeableDiscussionsState = true;
-
- expect(bound()).toEqual('unresolvedDiscussions');
-
- context.onlyAllowMergeIfPipelineSucceeds = true;
- context.isPipelineFailed = true;
-
- expect(bound()).toEqual('pipelineFailed');
-
- data.work_in_progress = true;
-
- expect(bound()).toEqual('workInProgress');
-
- data.has_conflicts = true;
-
- expect(bound()).toEqual('conflicts');
-
- context.mergeStatus = 'unchecked';
-
- expect(bound()).toEqual('checking');
-
- data.commits_count = 0;
-
- expect(bound()).toEqual('nothingToMerge');
-
- data.branch_missing = true;
-
- expect(bound()).toEqual('missingBranch');
-
- data.project_archived = true;
-
- expect(bound()).toEqual('archived');
- });
-
- it('returns rebased state key', () => {
- const context = {
- mergeStatus: 'checked',
- mergeWhenPipelineSucceeds: false,
- canMerge: true,
- onlyAllowMergeIfPipelineSucceeds: true,
- isPipelineFailed: true,
- hasMergeableDiscussionsState: false,
- isPipelineBlocked: false,
- canBeMerged: false,
- shouldBeRebased: true,
- };
- const data = {
- project_archived: false,
- branch_missing: false,
- commits_count: 2,
- has_conflicts: false,
- work_in_progress: false,
- };
- const bound = getStateKey.bind(context, data);
-
- expect(bound()).toEqual('rebase');
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
index c226704694c..e2cd0f084fd 100644
--- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -36,8 +36,8 @@ describe('MergeRequestStore', () => {
expect(store.isPipelinePassing).toBe(true);
});
- it('is true when the CI status is `success_with_warnings`', () => {
- store.setData({ ...mockData, ci_status: 'success_with_warnings' });
+ it('is true when the CI status is `success-with-warnings`', () => {
+ store.setData({ ...mockData, ci_status: 'success-with-warnings' });
expect(store.isPipelinePassing).toBe(true);
});
diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
index 4b0b7ba66e5..42481f8c334 100644
--- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
@@ -59,7 +59,7 @@ describe('CI Badge Link Component', () => {
success_warining: {
text: 'passed',
label: 'passed',
- group: 'success_with_warnings',
+ group: 'success-with-warnings',
icon: 'status_warning',
details_path: 'status/warning',
},
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 18fcdf7ede1..f2e20f626b5 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -61,7 +61,7 @@ describe('Commit component', () => {
});
it('should render a tag icon if it represents a tag', () => {
- expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag');
+ expect(component.$el.querySelector('.icon-container svg.ic-tag')).not.toBeNull();
});
it('should render a link to the ref url', () => {
@@ -143,4 +143,92 @@ describe('Commit component', () => {
);
});
});
+
+ describe('When commit ref is provided, but merge ref is not', () => {
+ it('should render the commit ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('master');
+
+ expect(refEl.href).toBe(props.commitRef.ref_url);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.commitRef.name);
+
+ expect(component.$el.querySelector('.icon-container .ic-branch')).not.toBeNull();
+ });
+ });
+
+ describe('When both commit and merge ref are provided', () => {
+ it('should render the merge ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: 'https://example.com/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('1234');
+
+ expect(refEl.href).toBe(props.mergeRequestRef.path);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.mergeRequestRef.title);
+
+ expect(component.$el.querySelector('.icon-container .ic-git-merge')).not.toBeNull();
+ });
+ });
+
+ describe('When showRefInfo === false', () => {
+ it('should not render any ref info', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: '/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ showRefInfo: false,
+ };
+
+ component = mountComponent(CommitComponent, props);
+
+ expect(component.$el.querySelector('.ref-name')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
index 4da8c6196b1..bdf802052b9 100644
--- a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import contentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL } from 'spec/test_constants';
+import '~/behaviors/markdown/render_gfm';
describe('ContentViewer', () => {
let vm;
@@ -29,6 +30,7 @@ describe('ContentViewer', () => {
path: 'test.md',
content: '* Test',
projectPath: 'testproject',
+ type: 'markdown',
});
const previewContainer = vm.$el.querySelector('.md-previewer');
@@ -44,6 +46,7 @@ describe('ContentViewer', () => {
createComponent({
path: GREEN_BOX_IMAGE_URL,
fileSize: 1024,
+ type: 'image',
});
setTimeout(() => {
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
index 7f2e246d656..97c870f27d9 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -138,22 +138,6 @@ describe('ImageDiffViewer', () => {
done();
});
});
-
- it('drag handler is working', done => {
- vm.$el.querySelector('.view-modes-menu li:nth-child(2)').click();
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.swipe-bar').style.left).toBe('1px');
- expect(vm.$el.querySelector('.top-handle')).not.toBeNull();
-
- dragSlider(vm.$el.querySelector('.swipe-bar'), 40);
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.swipe-bar').style.left).toBe('-20px');
- done();
- });
- });
- });
});
describe('onionSkin', () => {
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 34c9b35e02a..5bea8c43da3 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -70,12 +70,9 @@ describe('File Icon component', () => {
loading: true,
});
- const { classList } = vm.$el.querySelector('i');
+ const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('fa')).toEqual(true);
- expect(classList.contains('fa-spin')).toEqual(true);
- expect(classList.contains('fa-spinner')).toEqual(true);
- expect(classList.contains('fa-1x')).toEqual(true);
+ expect(classList.contains('spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index d1fd899c1a8..7da69e3fa84 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import FileRow from '~/vue_shared/components/file_row.vue';
+import FileRowExtra from '~/ide/components/file_row_extra.vue';
import { file } from 'spec/ide/helpers';
import mountComponent from '../../helpers/vue_mount_component_helper';
@@ -16,6 +17,10 @@ describe('File row component', () => {
vm.$destroy();
});
+ const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown');
+ const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button');
+ const findFileRow = () => vm.$el.querySelector('.file-row');
+
it('renders name', () => {
createComponent({
file: file('t4'),
@@ -84,4 +89,59 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
+
+ describe('new dropdown', () => {
+ beforeEach(() => {
+ createComponent({
+ file: file('t5'),
+ level: 1,
+ extraComponent: FileRowExtra,
+ });
+ });
+
+ it('renders in extra component', () => {
+ expect(findNewDropdown()).not.toBe(null);
+ });
+
+ it('is hidden at start', () => {
+ expect(findNewDropdown()).not.toHaveClass('show');
+ });
+
+ it('is opened when button is clicked', done => {
+ expect(vm.dropdownOpen).toBe(false);
+ findNewDropdownButton().dispatchEvent(new Event('click'));
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.dropdownOpen).toBe(true);
+ expect(findNewDropdown()).toHaveClass('show');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('when opened', () => {
+ beforeEach(() => {
+ vm.dropdownOpen = true;
+ });
+
+ it('stays open when button triggers mouseout', () => {
+ findNewDropdownButton().dispatchEvent(new Event('mouseout'));
+
+ expect(vm.dropdownOpen).toBe(true);
+ });
+
+ it('stays open when button triggers mouseleave', () => {
+ findNewDropdownButton().dispatchEvent(new Event('mouseleave'));
+
+ expect(vm.dropdownOpen).toBe(true);
+ });
+
+ it('closes when row triggers mouseleave', () => {
+ findFileRow().dispatchEvent(new Event('mouseleave'));
+
+ expect(vm.dropdownOpen).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index 7a741bdc067..a9c1a67b39b 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js b/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
deleted file mode 100644
index 9eac75fac96..00000000000
--- a/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import Vue from 'vue';
-
-import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { mockAssigneesList } from 'spec/boards/mock_data';
-
-const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
- const Component = Vue.extend(IssueAssignees);
-
- return mountComponent(Component, {
- assignees,
- cssClass,
- });
-};
-
-describe('IssueAssigneesComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('data', () => {
- it('returns default data props', () => {
- expect(vm.maxVisibleAssignees).toBe(2);
- expect(vm.maxAssigneeAvatars).toBe(3);
- expect(vm.maxAssignees).toBe(99);
- });
- });
-
- describe('computed', () => {
- describe('countOverLimit', () => {
- it('should return difference between assignees count and maxVisibleAssignees', () => {
- expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees);
- });
- });
-
- describe('assigneesToShow', () => {
- it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => {
- expect(vm.assigneesToShow.length).toBe(2);
- });
-
- it('should return all assignees as it is when count less than maxAssigneeAvatars', () => {
- vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
-
- expect(vm.assigneesToShow.length).toBe(3);
- });
- });
-
- describe('assigneesCounterTooltip', () => {
- it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => {
- expect(vm.assigneesCounterTooltip).toBe('3 more assignees');
- });
- });
-
- describe('shouldRenderAssigneesCounter', () => {
- it('should return `false` when assignees count less than maxAssigneeAvatars', () => {
- vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
-
- expect(vm.shouldRenderAssigneesCounter).toBe(false);
- });
-
- it('should return `true` when assignees count more than maxAssigneeAvatars', () => {
- expect(vm.shouldRenderAssigneesCounter).toBe(true);
- });
- });
-
- describe('assigneeCounterLabel', () => {
- it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => {
- expect(vm.assigneeCounterLabel).toBe('+3');
- });
- });
- });
-
- describe('methods', () => {
- describe('avatarUrlTitle', () => {
- it('returns string containing alt text for assignee avatar', () => {
- expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham');
- });
- });
- });
-
- describe('template', () => {
- it('renders component root element with class `issue-assignees`', () => {
- expect(vm.$el.classList.contains('issue-assignees')).toBe(true);
- });
-
- it('renders assignee avatars', () => {
- expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2);
- });
-
- it('renders assignee tooltips', () => {
- const tooltipText = vm.$el
- .querySelectorAll('.user-avatar-link')[0]
- .querySelector('.js-assignee-tooltip').innerText;
-
- expect(tooltipText).toContain('Assignee');
- expect(tooltipText).toContain('Terrell Graham');
- expect(tooltipText).toContain('@monserrate.gleichner');
- });
-
- it('renders additional assignees count', () => {
- const avatarCounterEl = vm.$el.querySelector('.avatar-counter');
-
- expect(avatarCounterEl.innerText.trim()).toBe('+3');
- expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js b/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
deleted file mode 100644
index 8fca2637326..00000000000
--- a/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
+++ /dev/null
@@ -1,234 +0,0 @@
-import Vue from 'vue';
-
-import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { mockMilestone } from 'spec/boards/mock_data';
-
-const createComponent = (milestone = mockMilestone) => {
- const Component = Vue.extend(IssueMilestone);
-
- return mountComponent(Component, {
- milestone,
- });
-};
-
-describe('IssueMilestoneComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('isMilestoneStarted', () => {
- it('should return `false` when milestoneStart prop is not defined', done => {
- const vmStartUndefined = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmStartUndefined.isMilestoneStarted).toBe(false);
- })
- .then(done)
- .catch(done.fail);
-
- vmStartUndefined.$destroy();
- });
-
- it('should return `true` when milestone start date is past current date', done => {
- const vmStarted = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: '1990-07-22',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmStarted.isMilestoneStarted).toBe(true);
- })
- .then(done)
- .catch(done.fail);
-
- vmStarted.$destroy();
- });
- });
-
- describe('isMilestonePastDue', () => {
- it('should return `false` when milestoneDue prop is not defined', done => {
- const vmDueUndefined = createComponent(
- Object.assign({}, mockMilestone, {
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmDueUndefined.isMilestonePastDue).toBe(false);
- })
- .then(done)
- .catch(done.fail);
-
- vmDueUndefined.$destroy();
- });
-
- it('should return `true` when milestone due is past current date', done => {
- const vmPastDue = createComponent(
- Object.assign({}, mockMilestone, {
- due_date: '1990-07-22',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmPastDue.isMilestonePastDue).toBe(true);
- })
- .then(done)
- .catch(done.fail);
-
- vmPastDue.$destroy();
- });
- });
-
- describe('milestoneDatesAbsolute', () => {
- it('returns string containing absolute milestone due date', () => {
- expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
- });
-
- it('returns string containing absolute milestone start date when due date is not present', done => {
- const vmDueUndefined = createComponent(
- Object.assign({}, mockMilestone, {
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmDueUndefined.milestoneDatesAbsolute).toBe('(January 1, 2018)');
- })
- .then(done)
- .catch(done.fail);
-
- vmDueUndefined.$destroy();
- });
-
- it('returns empty string when both milestone start and due dates are not present', done => {
- const vmDatesUndefined = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: '',
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmDatesUndefined.milestoneDatesAbsolute).toBe('');
- })
- .then(done)
- .catch(done.fail);
-
- vmDatesUndefined.$destroy();
- });
- });
-
- describe('milestoneDatesHuman', () => {
- it('returns string containing milestone due date when date is yet to be due', done => {
- const vmFuture = createComponent(
- Object.assign({}, mockMilestone, {
- due_date: `${new Date().getFullYear() + 10}-01-01`,
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmFuture.milestoneDatesHuman).toContain('years remaining');
- })
- .then(done)
- .catch(done.fail);
-
- vmFuture.$destroy();
- });
-
- it('returns string containing milestone start date when date has already started and due date is not present', done => {
- const vmStarted = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: '1990-07-22',
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmStarted.milestoneDatesHuman).toContain('Started');
- })
- .then(done)
- .catch(done.fail);
-
- vmStarted.$destroy();
- });
-
- it('returns string containing milestone start date when date is yet to start and due date is not present', done => {
- const vmStarts = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: `${new Date().getFullYear() + 10}-01-01`,
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmStarts.milestoneDatesHuman).toContain('Starts');
- })
- .then(done)
- .catch(done.fail);
-
- vmStarts.$destroy();
- });
-
- it('returns empty string when milestone start and due dates are not present', done => {
- const vmDatesUndefined = createComponent(
- Object.assign({}, mockMilestone, {
- start_date: '',
- due_date: '',
- }),
- );
-
- Vue.nextTick()
- .then(() => {
- expect(vmDatesUndefined.milestoneDatesHuman).toBe('');
- })
- .then(done)
- .catch(done.fail);
-
- vmDatesUndefined.$destroy();
- });
- });
- });
-
- describe('template', () => {
- it('renders component root element with class `issue-milestone-details`', () => {
- expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
- });
-
- it('renders milestone icon', () => {
- expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
- });
-
- it('renders milestone title', () => {
- expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
- });
-
- it('renders milestone tooltip', () => {
- expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
- mockMilestone.title,
- );
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js
deleted file mode 100644
index aa7d6ea2e34..00000000000
--- a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import Vue from 'vue';
-import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const IssueWarning = Vue.extend(issueWarning);
-
-function formatWarning(string) {
- // Replace newlines with a space then replace multiple spaces with one space
- return string
- .trim()
- .replace(/\n/g, ' ')
- .replace(/\s\s+/g, ' ');
-}
-
-describe('Issue Warning Component', () => {
- describe('isLocked', () => {
- it('should render locked issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
- isLocked: true,
- });
-
- expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/lock$/);
- expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This issue is locked. Only project members can comment.',
- );
- });
- });
-
- describe('isConfidential', () => {
- it('should render confidential issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
- isConfidential: true,
- });
-
- expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/eye-slash$/);
- expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This is a confidential issue. Your comment will not be visible to the public.',
- );
- });
- });
-
- describe('isLocked and isConfidential', () => {
- it('should render locked and confidential issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
- isLocked: true,
- isConfidential: true,
- });
-
- expect(vm.$el.querySelector('.icon')).toBeFalsy();
- expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- "This issue is confidential and locked. People without permission will never get a notification and won't be able to comment.",
- );
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js
new file mode 100644
index 00000000000..26bfdd7551e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js
@@ -0,0 +1,111 @@
+export const defaultProps = {
+ endpoint: '/foo/bar/issues/1/related_issues',
+ currentNamespacePath: 'foo',
+ currentProjectPath: 'bar',
+};
+
+export const issuable1 = {
+ id: 200,
+ epic_issue_id: 1,
+ confidential: false,
+ reference: 'foo/bar#123',
+ displayReference: '#123',
+ title: 'some title',
+ path: '/foo/bar/issues/123',
+ state: 'opened',
+};
+
+export const issuable2 = {
+ id: 201,
+ epic_issue_id: 2,
+ confidential: false,
+ reference: 'foo/bar#124',
+ displayReference: '#124',
+ title: 'some other thing',
+ path: '/foo/bar/issues/124',
+ state: 'opened',
+};
+
+export const issuable3 = {
+ id: 202,
+ epic_issue_id: 3,
+ confidential: false,
+ reference: 'foo/bar#125',
+ displayReference: '#125',
+ title: 'some other other thing',
+ path: '/foo/bar/issues/125',
+ state: 'opened',
+};
+
+export const issuable4 = {
+ id: 203,
+ epic_issue_id: 4,
+ confidential: false,
+ reference: 'foo/bar#126',
+ displayReference: '#126',
+ title: 'some other other other thing',
+ path: '/foo/bar/issues/126',
+ state: 'opened',
+};
+
+export const issuable5 = {
+ id: 204,
+ epic_issue_id: 5,
+ confidential: false,
+ reference: 'foo/bar#127',
+ displayReference: '#127',
+ title: 'some other other other thing',
+ path: '/foo/bar/issues/127',
+ state: 'opened',
+};
+
+export const defaultMilestone = {
+ id: 1,
+ state: 'active',
+ title: 'Milestone title',
+ start_date: '2018-01-01',
+ due_date: '2019-12-31',
+};
+
+export const defaultAssignees = [
+ {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/root`,
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ {
+ id: 13,
+ name: 'Brooks Beatty',
+ username: 'brynn_champlin',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/brynn_champlin`,
+ status_tooltip_html: null,
+ path: '/brynn_champlin',
+ },
+ {
+ id: 6,
+ name: 'Bryce Turcotte',
+ username: 'melynda',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/melynda`,
+ status_tooltip_html: null,
+ path: '/melynda',
+ },
+ {
+ id: 20,
+ name: 'Conchita Eichmann',
+ username: 'juliana_gulgowski',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/juliana_gulgowski`,
+ status_tooltip_html: null,
+ path: '/juliana_gulgowski',
+ },
+];
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 79e0e756a7a..02d73e1849a 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -5,7 +5,7 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue';
function assertMarkdownTabs(isWrite, writeLink, previewLink, vm) {
expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite);
expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite);
- expect(vm.$el.querySelector('.md-preview').style.display).toEqual(isWrite ? 'none' : '');
+ expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : '');
}
describe('Markdown field component', () => {
@@ -80,7 +80,9 @@ describe('Markdown field component', () => {
previewLink.click();
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…');
+ expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain(
+ 'Loading…',
+ );
done();
});
@@ -90,7 +92,7 @@ describe('Markdown field component', () => {
previewLink.click();
setTimeout(() => {
- expect(vm.$el.querySelector('.md-preview').innerHTML).toContain(
+ expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain(
'<p>markdown preview</p>',
);
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
index e733a95288e..d4be2451f0b 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -98,7 +98,7 @@ describe('Markdown field header component', () => {
it('renders suggestion template', () => {
vm.lineContent = 'Some content';
- expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```');
+ expect(vm.mdSuggestion).toEqual('```suggestion:-0+0\n{text}\n```');
});
it('does not render suggestion button if `canSuggest` is set to false', () => {
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
deleted file mode 100644
index 12ee804f668..00000000000
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import Vue from 'vue';
-import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
-
-const MOCK_DATA = {
- canApply: true,
- isApplied: false,
- helpPagePath: 'path_to_docs',
-};
-
-describe('Suggestion Diff component', () => {
- let vm;
-
- function createComponent(propsData) {
- const Component = Vue.extend(SuggestionDiffHeaderComponent);
-
- return new Component({
- propsData,
- }).$mount();
- }
-
- beforeEach(done => {
- vm = createComponent(MOCK_DATA);
- Vue.nextTick(done);
- });
-
- describe('init', () => {
- it('renders a suggestion header', () => {
- const header = vm.$el.querySelector('.qa-suggestion-diff-header');
-
- expect(header).not.toBeNull();
- expect(header.innerHTML.includes('Suggested change')).toBe(true);
- });
-
- it('renders a help button', () => {
- const helpBtn = vm.$el.querySelector('.js-help-btn');
-
- expect(helpBtn).not.toBeNull();
- });
-
- it('renders an apply button', () => {
- const applyBtn = vm.$el.querySelector('.qa-apply-btn');
-
- expect(applyBtn).not.toBeNull();
- expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true);
- });
-
- it('does not render an apply button if `canApply` is set to false', () => {
- const props = Object.assign(MOCK_DATA, { canApply: false });
-
- vm = createComponent(props);
-
- expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull();
- });
- });
-
- describe('applySuggestion', () => {
- it('emits when the apply button is clicked', () => {
- const props = Object.assign(MOCK_DATA, { canApply: true });
-
- vm = createComponent(props);
- spyOn(vm, '$emit');
- vm.applySuggestion();
-
- expect(vm.$emit).toHaveBeenCalled();
- });
-
- it('does not emit when the canApply is set to false', () => {
- spyOn(vm, '$emit');
- vm.canApply = false;
- vm.applySuggestion();
-
- expect(vm.$emit).not.toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index f87c2a92f47..ea74cb9eb21 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -1,21 +1,50 @@
import Vue from 'vue';
import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue';
+import { selectDiffLines } from '~/vue_shared/components/lib/utils/diff_utils';
const MOCK_DATA = {
canApply: true,
- newLines: [
- { content: 'Line 1\n', lineNumber: 1 },
- { content: 'Line 2\n', lineNumber: 2 },
- { content: 'Line 3\n', lineNumber: 3 },
- ],
- fromLine: 1,
- fromContent: 'Old content',
suggestion: {
id: 1,
+ diff_lines: [
+ {
+ can_receive_suggestion: false,
+ line_code: null,
+ meta_data: null,
+ new_line: null,
+ old_line: 5,
+ rich_text: '-test',
+ text: '-test',
+ type: 'old',
+ },
+ {
+ can_receive_suggestion: true,
+ line_code: null,
+ meta_data: null,
+ new_line: 5,
+ old_line: null,
+ rich_text: '+new test',
+ text: '+new test',
+ type: 'new',
+ },
+ {
+ can_receive_suggestion: true,
+ line_code: null,
+ meta_data: null,
+ new_line: 5,
+ old_line: null,
+ rich_text: '+new test2',
+ text: '+new test2',
+ type: 'new',
+ },
+ ],
},
helpPagePath: 'path_to_docs',
};
+const lines = selectDiffLines(MOCK_DATA.suggestion.diff_lines);
+const newLines = lines.filter(line => line.type === 'new');
+
describe('Suggestion Diff component', () => {
let vm;
@@ -39,30 +68,23 @@ describe('Suggestion Diff component', () => {
});
it('renders the oldLineNumber', () => {
- const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML;
+ const fromLine = vm.$el.querySelector('.old_line').innerHTML;
- expect(parseInt(fromLine, 10)).toBe(vm.fromLine);
+ expect(parseInt(fromLine, 10)).toBe(lines[0].old_line);
});
it('renders the oldLineContent', () => {
const fromContent = vm.$el.querySelector('.line_content.old').innerHTML;
- expect(fromContent.includes(vm.fromContent)).toBe(true);
- });
-
- it('renders the contents of newLines', () => {
- const newLines = vm.$el.querySelectorAll('.line_holder.new');
-
- newLines.forEach((line, i) => {
- expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true);
- });
+ expect(fromContent.includes(lines[0].text)).toBe(true);
});
- it('renders a line number for each line', () => {
- const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number');
+ it('renders new lines', () => {
+ const newLinesElements = vm.$el.querySelectorAll('.line_holder.new');
- newLineNumbers.forEach((line, i) => {
- expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true);
+ newLinesElements.forEach((line, i) => {
+ expect(newLinesElements[i].innerHTML.includes(newLines[i].new_line)).toBe(true);
+ expect(newLinesElements[i].innerHTML.includes(newLines[i].text)).toBe(true);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
index 33be63a3a1e..b7de40b4831 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
@@ -2,46 +2,52 @@ import Vue from 'vue';
import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue';
const MOCK_DATA = {
- fromLine: 1,
- fromContent: 'Old content',
- suggestions: [],
+ suggestions: [
+ {
+ id: 1,
+ appliable: true,
+ applied: false,
+ current_user: {
+ can_apply: true,
+ },
+ diff_lines: [
+ {
+ can_receive_suggestion: false,
+ line_code: null,
+ meta_data: null,
+ new_line: null,
+ old_line: 5,
+ rich_text: '-test',
+ text: '-test',
+ type: 'old',
+ },
+ {
+ can_receive_suggestion: true,
+ line_code: null,
+ meta_data: null,
+ new_line: 5,
+ old_line: null,
+ rich_text: '+new test',
+ text: '+new test',
+ type: 'new',
+ },
+ ],
+ },
+ ],
noteHtml: `
+ <div class="suggestion">
+ <div class="line">-oldtest</div>
+ </div>
<div class="suggestion">
- <div class="line">Suggestion 1</div>
+ <div class="line">+newtest</div>
</div>
-
- <div class="suggestion">
- <div class="line">Suggestion 2</div>
- </div>
`,
isApplied: false,
helpPagePath: 'path_to_docs',
};
-const generateLine = content => {
- const line = document.createElement('div');
- line.className = 'line';
- line.innerHTML = content;
-
- return line;
-};
-
-const generateMockLines = () => {
- const line1 = generateLine('Line 1');
- const line2 = generateLine('Line 2');
- const line3 = generateLine('- Line 3');
- const container = document.createElement('div');
-
- container.appendChild(line1);
- container.appendChild(line2);
- container.appendChild(line3);
-
- return container;
-};
-
describe('Suggestion component', () => {
let vm;
- let extractedLines;
let diffTable;
beforeEach(done => {
@@ -51,8 +57,7 @@ describe('Suggestion component', () => {
propsData: MOCK_DATA,
}).$mount();
- extractedLines = vm.extractNewLines(generateMockLines());
- diffTable = vm.generateDiff(extractedLines).$mount().$el;
+ diffTable = vm.generateDiff(0).$mount().$el;
spyOn(vm, 'renderSuggestions');
vm.renderSuggestions();
@@ -70,32 +75,8 @@ describe('Suggestion component', () => {
it('renders suggestions', () => {
expect(vm.renderSuggestions).toHaveBeenCalled();
- expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true);
- expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true);
- });
- });
-
- describe('extractNewLines', () => {
- it('extracts suggested lines', () => {
- const expectedReturn = [
- { content: 'Line 1\n', lineNumber: 1 },
- { content: 'Line 2\n', lineNumber: 2 },
- { content: '- Line 3\n', lineNumber: 3 },
- ];
-
- expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn);
- });
-
- it('increments line number for each extracted line', () => {
- expect(extractedLines[0].lineNumber).toEqual(1);
- expect(extractedLines[1].lineNumber).toEqual(2);
- expect(extractedLines[2].lineNumber).toEqual(3);
- });
-
- it('returns empty array if no lines are found', () => {
- const el = document.createElement('div');
-
- expect(vm.extractNewLines(el)).toEqual([]);
+ expect(vm.$el.innerHTML.includes('oldtest')).toBe(true);
+ expect(vm.$el.innerHTML.includes('newtest')).toBe(true);
});
});
@@ -109,17 +90,17 @@ describe('Suggestion component', () => {
});
it('generates a diff table that contains contents the suggested lines', () => {
- extractedLines.forEach((line, i) => {
- expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true);
+ MOCK_DATA.suggestions[0].diff_lines.forEach(line => {
+ const text = line.text.substring(1);
+
+ expect(diffTable.innerHTML.includes(text)).toBe(true);
});
});
it('generates a diff table with the correct line number for each suggested line', () => {
- const lines = diffTable.getElementsByClassName('qa-new-diff-line-number');
+ const lines = diffTable.querySelectorAll('.old_line');
- expect([...lines][0].innerHTML).toBe('1');
- expect([...lines][1].innerHTML).toBe('2');
- expect([...lines][2].innerHTML).toBe('3');
+ expect(parseInt([...lines][0].innerHTML, 10)).toBe(5);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js
deleted file mode 100644
index 45f131194ca..00000000000
--- a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import Vue from 'vue';
-import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
-import createStore from '~/notes/stores';
-import { userDataMock } from '../../../notes/mock_data';
-
-describe('issue placeholder system note component', () => {
- let store;
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(issuePlaceholderNote);
- store = createStore();
- store.dispatch('setUserData', userDataMock);
- vm = new Component({
- store,
- propsData: { note: { body: 'Foo' } },
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('user information', () => {
- it('should render user avatar with link', () => {
- expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual(
- userDataMock.path,
- );
-
- expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
- `${userDataMock.avatar_url}?width=40`,
- );
- });
- });
-
- describe('note content', () => {
- it('should render note header information', () => {
- expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual(
- userDataMock.path,
- );
-
- expect(
- vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(),
- ).toEqual(`@${userDataMock.username}`);
- });
-
- it('should render note body', () => {
- expect(vm.$el.querySelector('.note-text p').textContent.trim()).toEqual('Foo');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js
deleted file mode 100644
index 6013e85811a..00000000000
--- a/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Vue from 'vue';
-import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('placeholder system note component', () => {
- let PlaceholderSystemNote;
- let vm;
-
- beforeEach(() => {
- PlaceholderSystemNote = Vue.extend(placeholderSystemNote);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should render system note placeholder with plain text', () => {
- vm = mountComponent(PlaceholderSystemNote, {
- note: { body: 'This is a placeholder' },
- });
-
- expect(vm.$el.tagName).toEqual('LI');
- expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual(
- 'This is a placeholder',
- );
- });
-});
diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js
deleted file mode 100644
index adcb1c858aa..00000000000
--- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import Vue from 'vue';
-import issueSystemNote from '~/vue_shared/components/notes/system_note.vue';
-import createStore from '~/notes/stores';
-
-describe('system note component', () => {
- let vm;
- let props;
-
- beforeEach(() => {
- props = {
- note: {
- id: '1424',
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: 'path',
- path: '/root',
- },
- note_html: '<p dir="auto">closed</p>',
- system_note_icon_name: 'status_closed',
- created_at: '2017-08-02T10:51:58.559Z',
- },
- };
-
- const store = createStore();
- store.dispatch('setTargetNoteHash', `note_${props.note.id}`);
-
- const Component = Vue.extend(issueSystemNote);
- vm = new Component({
- store,
- propsData: props,
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should render a list item with correct id', () => {
- expect(vm.$el.getAttribute('id')).toEqual(`note_${props.note.id}`);
- });
-
- it('should render target class is note is target note', () => {
- expect(vm.$el.classList).toContain('target');
- });
-
- it('should render svg icon', () => {
- expect(vm.$el.querySelector('.timeline-icon svg')).toBeDefined();
- });
-
- // Redcarpet Markdown renderer wraps text in `<p>` tags
- // we need to strip them because they break layout of commit lists in system notes:
- // https://gitlab.com/gitlab-org/gitlab-ce/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png
- it('removes wrapping paragraph from note HTML', () => {
- expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('<span>closed</span>');
- });
-});
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js
new file mode 100644
index 00000000000..47964a1702a
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js
@@ -0,0 +1,110 @@
+import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/text_helper';
+
+const localVue = createLocalVue();
+
+describe('ProjectListItem component', () => {
+ const Component = localVue.extend(ProjectListItem);
+ let wrapper;
+ let vm;
+ let options;
+ loadJSONFixtures('static/projects.json');
+ const project = getJSONFixture('static/projects.json')[0];
+
+ beforeEach(() => {
+ options = {
+ propsData: {
+ project,
+ selected: false,
+ },
+ sync: false,
+ localVue,
+ };
+ });
+
+ afterEach(() => {
+ wrapper.vm.$destroy();
+ });
+
+ it('does not render a check mark icon if selected === false', () => {
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true);
+ });
+
+ it('renders a check mark icon if selected === true', () => {
+ options.propsData.selected = true;
+
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-selected-icon.js-selected')).toBe(true);
+ });
+
+ it(`emits a "clicked" event when clicked`, () => {
+ wrapper = shallowMount(Component, options);
+ ({ vm } = wrapper);
+
+ spyOn(vm, '$emit');
+ wrapper.vm.onClick();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('click');
+ });
+
+ it(`renders the project avatar`, () => {
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-project-avatar')).toBe(true);
+ });
+
+ it(`renders a simple namespace name with a trailing slash`, () => {
+ options.propsData.project.name_with_namespace = 'a / b';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('a /');
+ });
+
+ it(`renders a properly truncated namespace with a trailing slash`, () => {
+ options.propsData.project.name_with_namespace = 'a / b / c / d / e / f';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('a / ... / e /');
+ });
+
+ it(`renders the project name`, () => {
+ options.propsData.project.name = 'my-test-project';
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').text());
+
+ expect(renderedName).toBe('my-test-project');
+ });
+
+ it(`renders the project name with highlighting in the case of a search query match`, () => {
+ options.propsData.project.name = 'my-test-project';
+ options.propsData.matcher = 'pro';
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').html());
+ const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject';
+
+ expect(renderedName).toContain(expected);
+ });
+
+ it('prevents search query and project name XSS', () => {
+ const alertSpy = spyOn(window, 'alert');
+ options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject";
+ options.propsData.matcher = "pro<script>alert('XSS');</script>";
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').html());
+ const expected = 'my-xss-project';
+
+ expect(renderedName).toContain(expected);
+ expect(alertSpy).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
new file mode 100644
index 00000000000..7f5f1a778d7
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
@@ -0,0 +1,132 @@
+import Vue from 'vue';
+import _ from 'underscore';
+import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue';
+import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
+import { shallowMount } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/text_helper';
+
+describe('ProjectSelector component', () => {
+ let wrapper;
+ let vm;
+ loadJSONFixtures('static/projects.json');
+ const allProjects = getJSONFixture('static/projects.json');
+ const searchResults = allProjects.slice(0, 5);
+ let selected = [];
+ selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8));
+
+ beforeEach(() => {
+ jasmine.clock().install();
+
+ wrapper = shallowMount(Vue.extend(ProjectSelector), {
+ propsData: {
+ projectSearchResults: searchResults,
+ selectedProjects: selected,
+ showNoResultsMessage: false,
+ showMinimumSearchQueryMessage: false,
+ showLoadingIndicator: false,
+ showSearchErrorMessage: false,
+ },
+ attachToDocument: true,
+ });
+
+ ({ vm } = wrapper);
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ vm.$destroy();
+ });
+
+ it('renders the search results', () => {
+ expect(wrapper.findAll('.js-project-list-item').length).toBe(5);
+ });
+
+ it(`triggers a (debounced) search when the search input value changes`, () => {
+ spyOn(vm, '$emit');
+ const query = 'my test query!';
+ const searchInput = wrapper.find('.js-project-selector-input');
+ searchInput.setValue(query);
+ searchInput.trigger('input');
+
+ expect(vm.$emit).not.toHaveBeenCalledWith();
+ jasmine.clock().tick(501);
+
+ expect(vm.$emit).toHaveBeenCalledWith('searched', query);
+ });
+
+ it(`debounces the search input`, () => {
+ spyOn(vm, '$emit');
+ const searchInput = wrapper.find('.js-project-selector-input');
+
+ const updateSearchQuery = (count = 0) => {
+ if (count === 10) {
+ jasmine.clock().tick(101);
+
+ expect(vm.$emit).toHaveBeenCalledTimes(1);
+ expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`);
+ } else {
+ searchInput.setValue(`search query #${count}`);
+ searchInput.trigger('input');
+
+ jasmine.clock().tick(400);
+ updateSearchQuery(count + 1);
+ }
+ };
+
+ updateSearchQuery();
+ });
+
+ it(`includes a placeholder in the search box`, () => {
+ expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe(
+ 'Search your projects',
+ );
+ });
+
+ it(`triggers a "projectClicked" event when a project is clicked`, () => {
+ spyOn(vm, '$emit');
+ wrapper.find(ProjectListItem).vm.$emit('click', _.first(searchResults));
+
+ expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults));
+ });
+
+ it(`shows a "no results" message if showNoResultsMessage === true`, () => {
+ wrapper.setProps({ showNoResultsMessage: true });
+
+ expect(wrapper.contains('.js-no-results-message')).toBe(true);
+
+ const noResultsEl = wrapper.find('.js-no-results-message');
+
+ expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
+ });
+
+ it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, () => {
+ wrapper.setProps({ showMinimumSearchQueryMessage: true });
+
+ expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true);
+
+ const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
+
+ expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
+ });
+
+ it(`shows a error message if showSearchErrorMessage === true`, () => {
+ wrapper.setProps({ showSearchErrorMessage: true });
+
+ expect(wrapper.contains('.js-search-error-message')).toBe(true);
+
+ const errorMessageEl = wrapper.find('.js-search-error-message');
+
+ expect(trimText(errorMessageEl.text())).toEqual(
+ 'Something went wrong, unable to search projects',
+ );
+ });
+
+ it(`focuses the input element when the focusSearchInput() method is called`, () => {
+ const input = wrapper.find('.js-project-selector-input');
+
+ expect(document.activeElement).not.toBe(input.element);
+ vm.focusSearchInput();
+
+ expect(document.activeElement).toBe(input.element);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
deleted file mode 100644
index 6bff1521695..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import Vue from 'vue';
-import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('collapsedCalendarIcon', () => {
- let vm;
- beforeEach(() => {
- const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon);
- vm = mountComponent(CollapsedCalendarIcon, {
- containerClass: 'test-class',
- text: 'text',
- showIcon: false,
- });
- });
-
- it('should add class to container', () => {
- expect(vm.$el.classList.contains('test-class')).toEqual(true);
- });
-
- it('should hide calendar icon if showIcon', () => {
- expect(vm.$el.querySelector('.fa-calendar')).toBeNull();
- });
-
- it('should render text', () => {
- expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text');
- });
-
- it('should emit click event when container is clicked', () => {
- const click = jasmine.createSpy();
- vm.$on('click', click);
-
- vm.$el.click();
-
- expect(click).toHaveBeenCalled();
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
deleted file mode 100644
index c507a97d37e..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import Vue from 'vue';
-import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('collapsedGroupedDatePicker', () => {
- let vm;
- beforeEach(() => {
- const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker);
- vm = mountComponent(CollapsedGroupedDatePicker, {
- showToggleSidebar: true,
- });
- });
-
- describe('toggleCollapse events', () => {
- beforeEach(done => {
- spyOn(vm, 'toggleSidebar');
- vm.minDate = new Date('07/17/2016');
- Vue.nextTick(done);
- });
-
- it('should emit when collapsed-calendar-icon is clicked', () => {
- vm.$el.querySelector('.sidebar-collapsed-icon').click();
-
- expect(vm.toggleSidebar).toHaveBeenCalled();
- });
- });
-
- describe('minDate and maxDate', () => {
- beforeEach(done => {
- vm.minDate = new Date('07/17/2016');
- vm.maxDate = new Date('07/17/2017');
- Vue.nextTick(done);
- });
-
- it('should render both collapsed-calendar-icon', () => {
- const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
-
- expect(icons.length).toEqual(2);
- expect(icons[0].innerText.trim()).toEqual('Jul 17 2016');
- expect(icons[1].innerText.trim()).toEqual('Jul 17 2017');
- });
- });
-
- describe('minDate', () => {
- beforeEach(done => {
- vm.minDate = new Date('07/17/2016');
- Vue.nextTick(done);
- });
-
- it('should render minDate in collapsed-calendar-icon', () => {
- const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
-
- expect(icons.length).toEqual(1);
- expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016');
- });
- });
-
- describe('maxDate', () => {
- beforeEach(done => {
- vm.maxDate = new Date('07/17/2017');
- Vue.nextTick(done);
- });
-
- it('should render maxDate in collapsed-calendar-icon', () => {
- const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
-
- expect(icons.length).toEqual(1);
- expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017');
- });
- });
-
- describe('no dates', () => {
- it('should render None', () => {
- const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
-
- expect(icons.length).toEqual(1);
- expect(icons[0].innerText.trim()).toEqual('None');
- });
-
- it('should have tooltip as `Start and due date`', () => {
- const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
-
- expect(icons[0].dataset.originalTitle).toBe('Start and due date');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
deleted file mode 100644
index 805ba7b9947..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import Vue from 'vue';
-import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('sidebarDatePicker', () => {
- let vm;
- beforeEach(() => {
- const SidebarDatePicker = Vue.extend(sidebarDatePicker);
- vm = mountComponent(SidebarDatePicker, {
- label: 'label',
- isLoading: true,
- });
- });
-
- it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
- const toggleCollapse = jasmine.createSpy();
- vm.$on('toggleCollapse', toggleCollapse);
-
- vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click();
-
- expect(toggleCollapse).toHaveBeenCalled();
- });
-
- it('should render collapsed-calendar-icon', () => {
- expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined();
- });
-
- it('should render label', () => {
- expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label');
- });
-
- it('should render loading-icon when isLoading', () => {
- expect(vm.$el.querySelector('.fa-spin')).toBeDefined();
- });
-
- it('should render value when not editing', () => {
- expect(vm.$el.querySelector('.value-content')).toBeDefined();
- });
-
- it('should render None if there is no selectedDate', () => {
- expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None');
- });
-
- it('should render date-picker when editing', done => {
- vm.editing = true;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.pika-label')).toBeDefined();
- done();
- });
- });
-
- describe('editable', () => {
- beforeEach(done => {
- vm.editable = true;
- Vue.nextTick(done);
- });
-
- it('should render edit button', () => {
- expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit');
- });
-
- it('should enable editing when edit button is clicked', done => {
- vm.isLoading = false;
- Vue.nextTick(() => {
- vm.$el.querySelector('.title .btn-blank').click();
-
- expect(vm.editing).toEqual(true);
- done();
- });
- });
- });
-
- it('should render date if selectedDate', done => {
- vm.selectedDate = new Date('07/07/2017');
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017');
- done();
- });
- });
-
- describe('selectedDate and editable', () => {
- beforeEach(done => {
- vm.selectedDate = new Date('07/07/2017');
- vm.editable = true;
- Vue.nextTick(done);
- });
-
- it('should render remove button if selectedDate and editable', () => {
- expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove');
- });
-
- it('should emit saveDate when remove button is clicked', () => {
- const saveDate = jasmine.createSpy();
- vm.$on('saveDate', saveDate);
-
- vm.$el.querySelector('.value-content .btn-blank').click();
-
- expect(saveDate).toHaveBeenCalled();
- });
- });
-
- describe('showToggleSidebar', () => {
- beforeEach(done => {
- vm.showToggleSidebar = true;
- Vue.nextTick(done);
- });
-
- it('should render toggle-sidebar when showToggleSidebar', () => {
- expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined();
- });
-
- it('should emit toggleCollapse when toggle sidebar is clicked', () => {
- const toggleCollapse = jasmine.createSpy();
- vm.$on('toggleCollapse', toggleCollapse);
-
- vm.$el.querySelector('.title .gutter-toggle').click();
-
- expect(toggleCollapse).toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
deleted file mode 100644
index c44b04009ca..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
+++ /dev/null
@@ -1,130 +0,0 @@
-import Vue from 'vue';
-
-import LabelsSelect from '~/labels_select';
-import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockConfig, mockLabels } from './mock_data';
-
-const createComponent = (config = mockConfig) => {
- const Component = Vue.extend(baseComponent);
-
- return mountComponent(Component, config);
-};
-
-describe('BaseComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('hiddenInputName', () => {
- it('returns correct string when showCreate prop is `true`', () => {
- expect(vm.hiddenInputName).toBe('issue[label_names][]');
- });
-
- it('returns correct string when showCreate prop is `false`', () => {
- const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false });
- const vmNonEditable = createComponent(mockConfigNonEditable);
-
- expect(vmNonEditable.hiddenInputName).toBe('label_id[]');
- vmNonEditable.$destroy();
- });
- });
-
- describe('createLabelTitle', () => {
- it('returns `Create project label` when `isProject` prop is true', () => {
- expect(vm.createLabelTitle).toBe('Create project label');
- });
-
- it('return `Create group label` when `isProject` prop is false', () => {
- const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
- const vmGroup = createComponent(mockConfigGroup);
-
- expect(vmGroup.createLabelTitle).toBe('Create group label');
- vmGroup.$destroy();
- });
- });
-
- describe('manageLabelsTitle', () => {
- it('returns `Manage project labels` when `isProject` prop is true', () => {
- expect(vm.manageLabelsTitle).toBe('Manage project labels');
- });
-
- it('return `Manage group labels` when `isProject` prop is false', () => {
- const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
- const vmGroup = createComponent(mockConfigGroup);
-
- expect(vmGroup.manageLabelsTitle).toBe('Manage group labels');
- vmGroup.$destroy();
- });
- });
- });
-
- describe('methods', () => {
- describe('handleClick', () => {
- it('emits onLabelClick event with label and list of labels as params', () => {
- spyOn(vm, '$emit');
- vm.handleClick(mockLabels[0]);
-
- expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]);
- });
- });
-
- describe('handleCollapsedValueClick', () => {
- it('emits toggleCollapse event on component', () => {
- spyOn(vm, '$emit');
- vm.handleCollapsedValueClick();
-
- expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse');
- });
- });
-
- describe('handleDropdownHidden', () => {
- it('emits onDropdownClose event on component', () => {
- spyOn(vm, '$emit');
- vm.handleDropdownHidden();
-
- expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose');
- });
- });
- });
-
- describe('mounted', () => {
- it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => {
- expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true);
- });
- });
-
- describe('template', () => {
- it('renders component container element with classes `block labels`', () => {
- expect(vm.$el.classList.contains('block')).toBe(true);
- expect(vm.$el.classList.contains('labels')).toBe(true);
- });
-
- it('renders `.selectbox` element', () => {
- expect(vm.$el.querySelector('.selectbox')).not.toBeNull();
- expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;');
- });
-
- it('renders `.dropdown` element', () => {
- expect(vm.$el.querySelector('.dropdown')).not.toBeNull();
- });
-
- it('renders `.dropdown-menu` element', () => {
- const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu');
-
- expect(dropdownMenuEl).not.toBeNull();
- expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull();
- expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull();
- expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
deleted file mode 100644
index 5cf6afebd7e..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import Vue from 'vue';
-
-import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockConfig, mockLabels } from './mock_data';
-
-const componentConfig = Object.assign({}, mockConfig, {
- fieldName: 'label_id[]',
- labels: mockLabels,
- showExtraOptions: false,
-});
-
-const createComponent = (config = componentConfig) => {
- const Component = Vue.extend(dropdownButtonComponent);
-
- return mountComponent(Component, config);
-};
-
-describe('DropdownButtonComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('dropdownToggleText', () => {
- it('returns text as `Label` when `labels` prop is empty array', () => {
- const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] });
- const vmEmptyLabels = createComponent(mockEmptyLabels);
-
- expect(vmEmptyLabels.dropdownToggleText).toBe('Label');
- vmEmptyLabels.$destroy();
- });
-
- it('returns first label name with remaining label count when `labels` prop has more than one item', () => {
- const mockMoreLabels = Object.assign({}, componentConfig, {
- labels: mockLabels.concat(mockLabels),
- });
- const vmMoreLabels = createComponent(mockMoreLabels);
-
- expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more');
- vmMoreLabels.$destroy();
- });
-
- it('returns first label name when `labels` prop has only one item present', () => {
- expect(vm.dropdownToggleText).toBe('Foo Label');
- });
- });
- });
-
- describe('template', () => {
- it('renders component container element of type `button`', () => {
- expect(vm.$el.nodeName).toBe('BUTTON');
- });
-
- it('renders component container element with required data attributes', () => {
- expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
- expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
- expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
- expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
- expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
- expect(vm.$el.dataset.showAny).not.toBeDefined();
- });
-
- it('renders dropdown toggle text element', () => {
- const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
-
- expect(dropdownToggleTextEl).not.toBeNull();
- expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label');
- });
-
- it('renders dropdown button icon', () => {
- const dropdownIconEl = vm.$el.querySelector('i.fa');
-
- expect(dropdownIconEl).not.toBeNull();
- expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
deleted file mode 100644
index b8f32f96332..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import Vue from 'vue';
-
-import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockSuggestedColors } from './mock_data';
-
-const createComponent = headerTitle => {
- const Component = Vue.extend(dropdownCreateLabelComponent);
-
- return mountComponent(Component, {
- headerTitle,
- });
-};
-
-describe('DropdownCreateLabelComponent', () => {
- let vm;
-
- beforeEach(() => {
- gon.suggested_label_colors = mockSuggestedColors;
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('created', () => {
- it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => {
- expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length);
- });
- });
-
- describe('template', () => {
- it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => {
- expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true);
- });
-
- it('renders `Go back` button on component header', () => {
- const backButtonEl = vm.$el.querySelector(
- '.dropdown-title button.dropdown-title-button.dropdown-menu-back',
- );
-
- expect(backButtonEl).not.toBe(null);
- expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null);
- });
-
- it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => {
- const headerEl = vm.$el.querySelector('.dropdown-title');
-
- expect(headerEl.innerText.trim()).toContain('Create new label');
- });
-
- it('renders component header element with value of `headerTitle` prop', () => {
- const headerTitle = 'Create project label';
- const vmWithHeaderTitle = createComponent(headerTitle);
- const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title');
-
- expect(headerEl.innerText.trim()).toContain(headerTitle);
- vmWithHeaderTitle.$destroy();
- });
-
- it('renders `Close` button on component header', () => {
- const closeButtonEl = vm.$el.querySelector(
- '.dropdown-title button.dropdown-title-button.dropdown-menu-close',
- );
-
- expect(closeButtonEl).not.toBe(null);
- expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null);
- });
-
- it('renders `Name new label` input element', () => {
- expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null);
- expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null);
- });
-
- it('renders suggested colors list elements', () => {
- const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown');
-
- expect(colorsListContainerEl).not.toBe(null);
- expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length);
-
- const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
-
- expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]);
- expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);');
- });
-
- it('renders color input element', () => {
- expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null);
- expect(
- vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview'),
- ).not.toBe(null);
-
- expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null);
- });
-
- it('renders component action buttons', () => {
- const createBtnEl = vm.$el.querySelector('button.js-new-label-btn');
- const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn');
-
- expect(createBtnEl).not.toBe(null);
- expect(createBtnEl.innerText.trim()).toBe('Create');
- expect(cancelBtnEl.innerText.trim()).toBe('Cancel');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
deleted file mode 100644
index 3711e9dac8c..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import Vue from 'vue';
-
-import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockConfig } from './mock_data';
-
-const createComponent = (
- labelsWebUrl = mockConfig.labelsWebUrl,
- createLabelTitle,
- manageLabelsTitle,
-) => {
- const Component = Vue.extend(dropdownFooterComponent);
-
- return mountComponent(Component, {
- labelsWebUrl,
- createLabelTitle,
- manageLabelsTitle,
- });
-};
-
-describe('DropdownFooterComponent', () => {
- const createLabelTitle = 'Create project label';
- const manageLabelsTitle = 'Manage project labels';
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('template', () => {
- it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => {
- const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
-
- expect(createLabelEl).not.toBeNull();
- expect(createLabelEl.innerText.trim()).toBe('Create new label');
- });
-
- it('renders link element with value of `createLabelTitle` prop', () => {
- const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle);
- const createLabelEl = vmWithCreateLabelTitle.$el.querySelector(
- '.dropdown-footer-list .dropdown-toggle-page',
- );
-
- expect(createLabelEl.innerText.trim()).toBe(createLabelTitle);
- vmWithCreateLabelTitle.$destroy();
- });
-
- it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => {
- const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
-
- expect(manageLabelsEl).not.toBeNull();
- expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
- expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
- });
-
- it('renders link element with value of `manageLabelsTitle` prop', () => {
- const vmWithManageLabelsTitle = createComponent(
- mockConfig.labelsWebUrl,
- createLabelTitle,
- manageLabelsTitle,
- );
- const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector(
- '.dropdown-footer-list .dropdown-external-link',
- );
-
- expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle);
- vmWithManageLabelsTitle.$destroy();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
deleted file mode 100644
index 115e21e4f9f..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import Vue from 'vue';
-
-import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = () => {
- const Component = Vue.extend(dropdownHeaderComponent);
-
- return mountComponent(Component);
-};
-
-describe('DropdownHeaderComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('template', () => {
- it('renders header text element', () => {
- const headerEl = vm.$el.querySelector('.dropdown-title span');
-
- expect(headerEl.innerText.trim()).toBe('Assign labels');
- });
-
- it('renders `Close` button element', () => {
- const closeBtnEl = vm.$el.querySelector(
- '.dropdown-title button.dropdown-title-button.dropdown-menu-close',
- );
-
- expect(closeBtnEl).not.toBeNull();
- expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
deleted file mode 100644
index c30e619e76b..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Vue from 'vue';
-
-import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = () => {
- const Component = Vue.extend(dropdownSearchInputComponent);
-
- return mountComponent(Component);
-};
-
-describe('DropdownSearchInputComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('template', () => {
- it('renders input element with type `search`', () => {
- const inputEl = vm.$el.querySelector('input.dropdown-input-field');
-
- expect(inputEl).not.toBeNull();
- expect(inputEl.getAttribute('type')).toBe('search');
- });
-
- it('renders search icon element', () => {
- expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
- });
-
- it('renders clear search icon element', () => {
- expect(
- vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
- ).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
deleted file mode 100644
index 6c84d2e167c..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import Vue from 'vue';
-
-import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = (canEdit = true) => {
- const Component = Vue.extend(dropdownTitleComponent);
-
- return mountComponent(Component, {
- canEdit,
- });
-};
-
-describe('DropdownTitleComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('template', () => {
- it('renders title text', () => {
- expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true);
- expect(vm.$el.innerText.trim()).toContain('Labels');
- });
-
- it('renders spinner icon element', () => {
- expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull();
- });
-
- it('renders `Edit` button element', () => {
- const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle');
-
- expect(editBtnEl).not.toBeNull();
- expect(editBtnEl.innerText.trim()).toBe('Edit');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
deleted file mode 100644
index 804b33422bd..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import Vue from 'vue';
-
-import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockLabels } from './mock_data';
-
-const createComponent = (labels = mockLabels) => {
- const Component = Vue.extend(dropdownValueCollapsedComponent);
-
- return mountComponent(Component, {
- labels,
- });
-};
-
-describe('DropdownValueCollapsedComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('labelsList', () => {
- it('returns empty text when `labels` prop is empty array', () => {
- const vmEmptyLabels = createComponent([]);
-
- expect(vmEmptyLabels.labelsList).toBe('');
- vmEmptyLabels.$destroy();
- });
-
- it('returns labels names separated by coma when `labels` prop has more than one item', () => {
- const vmMoreLabels = createComponent(mockLabels.concat(mockLabels));
-
- expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label');
- vmMoreLabels.$destroy();
- });
-
- it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
- const mockMoreLabels = Object.assign([], mockLabels);
- for (let i = 0; i < 6; i += 1) {
- mockMoreLabels.unshift(mockLabels[0]);
- }
-
- const vmMoreLabels = createComponent(mockMoreLabels);
-
- expect(vmMoreLabels.labelsList).toBe(
- 'Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more',
- );
- vmMoreLabels.$destroy();
- });
-
- it('returns first label name when `labels` prop has only one item present', () => {
- expect(vm.labelsList).toBe('Foo Label');
- });
- });
- });
-
- describe('methods', () => {
- describe('handleClick', () => {
- it('emits onValueClick event on component', () => {
- spyOn(vm, '$emit');
- vm.handleClick();
-
- expect(vm.$emit).toHaveBeenCalledWith('onValueClick');
- });
- });
- });
-
- describe('template', () => {
- it('renders component container element with tooltip`', () => {
- expect(vm.$el.dataset.placement).toBe('left');
- expect(vm.$el.dataset.container).toBe('body');
- expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList);
- });
-
- it('renders tags icon element', () => {
- expect(vm.$el.querySelector('.fa-tags')).not.toBeNull();
- });
-
- it('renders labels count', () => {
- expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
deleted file mode 100644
index 3fff781594f..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import Vue from 'vue';
-
-import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockConfig, mockLabels } from './mock_data';
-
-const createComponent = (
- labels = mockLabels,
- labelFilterBasePath = mockConfig.labelFilterBasePath,
-) => {
- const Component = Vue.extend(dropdownValueComponent);
-
- return mountComponent(Component, {
- labels,
- labelFilterBasePath,
- });
-};
-
-describe('DropdownValueComponent', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('isEmpty', () => {
- it('returns true if `labels` prop is empty', () => {
- const vmEmptyLabels = createComponent([]);
-
- expect(vmEmptyLabels.isEmpty).toBe(true);
- vmEmptyLabels.$destroy();
- });
-
- it('returns false if `labels` prop is empty', () => {
- expect(vm.isEmpty).toBe(false);
- });
- });
- });
-
- describe('methods', () => {
- describe('labelFilterUrl', () => {
- it('returns URL string starting with labelFilterBasePath and encoded label.title', () => {
- expect(
- vm.labelFilterUrl({
- title: 'Foo bar',
- }),
- ).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar');
- });
- });
-
- describe('labelStyle', () => {
- it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => {
- const label = {
- textColor: '#FFFFFF',
- color: '#BADA55',
- };
- const styleObj = vm.labelStyle(label);
-
- expect(styleObj.color).toBe(label.textColor);
- expect(styleObj.backgroundColor).toBe(label.color);
- });
- });
- });
-
- describe('template', () => {
- it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => {
- expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe(
- true,
- );
- });
-
- it('render slot content inside component when `labels` prop is empty', () => {
- const vmEmptyLabels = createComponent([]);
-
- expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe(
- mockConfig.emptyValueText,
- );
- vmEmptyLabels.$destroy();
- });
-
- it('renders label element with filter URL', () => {
- expect(vm.$el.querySelector('a').getAttribute('href')).toBe(
- '/gitlab-org/my-project/issues?label_name[]=Foo%20Label',
- );
- });
-
- it('renders label element with tooltip and styles based on label details', () => {
- const labelEl = vm.$el.querySelector('a span.badge.color-label');
-
- expect(labelEl).not.toBeNull();
- expect(labelEl.dataset.placement).toBe('bottom');
- expect(labelEl.dataset.container).toBe('body');
- expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description);
- expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);');
- expect(labelEl.innerText.trim()).toBe(mockLabels[0].title);
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
index 3fcb91b6f5e..70025f041a7 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -6,6 +6,13 @@ export const mockLabels = [
color: '#BADA55',
text_color: '#FFFFFF',
},
+ {
+ id: 27,
+ title: 'Foo::Bar',
+ description: 'Foobar',
+ color: '#0033CC',
+ text_color: '#FFFFFF',
+ },
];
export const mockSuggestedColors = [
diff --git a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
deleted file mode 100644
index c911a129173..00000000000
--- a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import Vue from 'vue';
-import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('toggleSidebar', () => {
- let vm;
- beforeEach(() => {
- const ToggleSidebar = Vue.extend(toggleSidebar);
- vm = mountComponent(ToggleSidebar, {
- collapsed: true,
- });
- });
-
- it('should render << when collapsed', () => {
- expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true);
- });
-
- it('should render >> when collapsed', () => {
- vm.collapsed = false;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true);
- });
- });
-
- it('should emit toggle event when button clicked', () => {
- const toggle = jasmine.createSpy();
- vm.$on('toggle', toggle);
- vm.$el.click();
-
- expect(toggle).toHaveBeenCalled();
- });
-});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 0dcb712e720..42cd41381dc 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -22,10 +22,10 @@ describe('Pagination component', () => {
it('should not render anything', () => {
component = mountComponent({
pageInfo: {
- nextPage: 1,
+ nextPage: NaN,
page: 1,
perPage: 20,
- previousPage: null,
+ previousPage: NaN,
total: 15,
totalPages: 1,
},
@@ -53,7 +53,29 @@ describe('Pagination component', () => {
component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
).toEqual(true);
- component.$el.querySelector('.js-previous-button a').click();
+ component.$el.querySelector('.js-previous-button .page-link').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be disabled and non clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 2,
+ page: 1,
+ perPage: 20,
+ previousPage: NaN,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
+ ).toEqual(true);
+
+ component.$el.querySelector('.js-previous-button .page-link').click();
expect(spy).not.toHaveBeenCalled();
});
@@ -71,7 +93,25 @@ describe('Pagination component', () => {
change: spy,
});
- component.$el.querySelector('.js-previous-button a').click();
+ component.$el.querySelector('.js-previous-button .page-link').click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+
+ it('should be enabled and clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-previous-button .page-link').click();
expect(spy).toHaveBeenCalledWith(1);
});
@@ -91,7 +131,29 @@ describe('Pagination component', () => {
change: spy,
});
- const button = component.$el.querySelector('.js-first-button a');
+ const button = component.$el.querySelector('.js-first-button .page-link');
+
+ expect(button.textContent.trim()).toEqual('« First');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+
+ it('should call the change callback with the first page when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-first-button .page-link');
expect(button.textContent.trim()).toEqual('« First');
@@ -115,7 +177,7 @@ describe('Pagination component', () => {
change: spy,
});
- const button = component.$el.querySelector('.js-last-button a');
+ const button = component.$el.querySelector('.js-last-button .page-link');
expect(button.textContent.trim()).toEqual('Last »');
@@ -123,16 +185,32 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(5);
});
+
+ it('should not render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.js-last-button .page-link')).toBeNull();
+ });
});
describe('next button', () => {
it('should be disabled and non clickable', () => {
component = mountComponent({
pageInfo: {
- nextPage: 5,
+ nextPage: NaN,
page: 5,
perPage: 20,
- previousPage: 1,
+ previousPage: 4,
total: 84,
totalPages: 5,
},
@@ -141,7 +219,27 @@ describe('Pagination component', () => {
expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
- component.$el.querySelector('.js-next-button a').click();
+ component.$el.querySelector('.js-next-button .page-link').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be disabled and non clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: NaN,
+ page: 5,
+ perPage: 20,
+ previousPage: 4,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+
+ component.$el.querySelector('.js-next-button .page-link').click();
expect(spy).not.toHaveBeenCalled();
});
@@ -159,7 +257,25 @@ describe('Pagination component', () => {
change: spy,
});
- component.$el.querySelector('.js-next-button a').click();
+ component.$el.querySelector('.js-next-button .page-link').click();
+
+ expect(spy).toHaveBeenCalledWith(4);
+ });
+
+ it('should be enabled and clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-next-button .page-link').click();
expect(spy).toHaveBeenCalledWith(4);
});
@@ -181,22 +297,56 @@ describe('Pagination component', () => {
expect(component.$el.querySelectorAll('.page').length).toEqual(5);
});
+
+ it('should not render any page', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelectorAll('.page').length).toEqual(0);
+ });
});
- it('should render the spread operator', () => {
- component = mountComponent({
- pageInfo: {
- nextPage: 4,
- page: 3,
- perPage: 20,
- previousPage: 2,
- total: 84,
- totalPages: 10,
- },
- change: spy,
+ describe('spread operator', () => {
+ it('should render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 10,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
});
- expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
+ it('should not render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.separator')).toBeNull();
+ });
});
});
});
diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
index e8b41e8eeff..c7e0d806d80 100644
--- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
@@ -17,7 +17,7 @@ const DEFAULT_PROPS = {
const UserPopover = Vue.extend(userPopover);
describe('User Popover Component', () => {
- const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ const fixtureTemplate = 'merge_requests/diff_comment.html';
preloadFixtures(fixtureTemplate);
let vm;
@@ -61,6 +61,12 @@ describe('User Popover Component', () => {
expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.username);
expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.location);
});
+
+ it('shows icon for location', () => {
+ const iconEl = vm.$el.querySelector('.js-location svg');
+
+ expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('location');
+ });
});
describe('job data', () => {
@@ -117,6 +123,18 @@ describe('User Popover Component', () => {
'Me & my <funky> Company',
);
});
+
+ it('shows icon for bio', () => {
+ const iconEl = vm.$el.querySelector('.js-bio svg');
+
+ expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('profile');
+ });
+
+ it('shows icon for organization', () => {
+ const iconEl = vm.$el.querySelector('.js-organization svg');
+
+ expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('work');
+ });
});
describe('status data', () => {
diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js
index adb5ff682f0..0aaa4050cba 100644
--- a/spec/javascripts/vue_shared/translate_spec.js
+++ b/spec/javascripts/vue_shared/translate_spec.js
@@ -3,7 +3,7 @@ import Jed from 'jed';
import locale from '~/locale';
import Translate from '~/vue_shared/translate';
-import { trimText } from 'spec/helpers/vue_component_helper';
+import { trimText } from 'spec/helpers/text_helper';
describe('Vue translate filter', () => {
let el;
diff --git a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
deleted file mode 100644
index d07f8ba1e65..00000000000
--- a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import mutations from '~/vuex_shared/modules/modal/mutations';
-import * as types from '~/vuex_shared/modules/modal/mutation_types';
-
-describe('Vuex ModalModule mutations', () => {
- describe(types.SHOW, () => {
- it('sets isVisible to true', () => {
- const state = {
- isVisible: false,
- };
-
- mutations[types.SHOW](state);
-
- expect(state).toEqual({
- isVisible: true,
- });
- });
- });
-
- describe(types.HIDE, () => {
- it('sets isVisible to false', () => {
- const state = {
- isVisible: true,
- };
-
- mutations[types.HIDE](state);
-
- expect(state).toEqual({
- isVisible: false,
- });
- });
- });
-
- describe(types.OPEN, () => {
- it('sets data and sets isVisible to true', () => {
- const data = { id: 7 };
- const state = {
- isVisible: false,
- data: null,
- };
-
- mutations[types.OPEN](state, data);
-
- expect(state).toEqual({
- isVisible: true,
- data,
- });
- });
- });
-});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index e5f1e6ae937..8f662c71c7a 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode';
describe('ZenMode', () => {
let zen;
let dropzoneForElementSpy;
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
preloadFixtures(fixtureName);
diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/job_request/image_spec.rb
new file mode 100644
index 00000000000..092c181ae9c
--- /dev/null
+++ b/spec/lib/api/entities/job_request/image_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Entities::JobRequest::Image do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
+ let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)}
+ let(:entity) { described_class.new(image) }
+
+ subject { entity.as_json }
+
+ it 'returns the image name' do
+ expect(subject[:name]).to eq 'image_name'
+ end
+
+ it 'returns the entrypoint' do
+ expect(subject[:entrypoint]).to eq ['foo']
+ end
+
+ it 'returns the ports' do
+ expect(subject[:ports]).to eq ports
+ end
+
+ context 'when the ports param is nil' do
+ let(:ports) { nil }
+
+ it 'does not return the ports' do
+ expect(subject[:ports]).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/job_request/port_spec.rb
new file mode 100644
index 00000000000..40ab4cd6231
--- /dev/null
+++ b/spec/lib/api/entities/job_request/port_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::API::Entities::JobRequest::Port do
+ let(:port) { double(number: 80, protocol: 'http', name: 'name')}
+ let(:entity) { described_class.new(port) }
+
+ subject { entity.as_json }
+
+ it 'returns the port number' do
+ expect(subject[:number]).to eq 80
+ end
+
+ it 'returns if the port protocol' do
+ expect(subject[:protocol]).to eq 'http'
+ end
+
+ it 'returns the port name' do
+ expect(subject[:name]).to eq 'name'
+ end
+end
diff --git a/spec/lib/api/helpers/custom_validators_spec.rb b/spec/lib/api/helpers/custom_validators_spec.rb
index 41e6fb47b11..aed86b21cb7 100644
--- a/spec/lib/api/helpers/custom_validators_spec.rb
+++ b/spec/lib/api/helpers/custom_validators_spec.rb
@@ -21,7 +21,7 @@ describe API::Helpers::CustomValidators do
end
context 'invalid parameters' do
- it 'should raise a validation error' do
+ it 'raises a validation error' do
expect_validation_error({ 'test' => 'some_value' })
end
end
@@ -44,7 +44,30 @@ describe API::Helpers::CustomValidators do
end
context 'invalid parameters' do
- it 'should raise a validation error' do
+ it 'raises a validation error' do
+ expect_validation_error({ 'test' => 'some_other_string' })
+ end
+ end
+ end
+
+ describe API::Helpers::CustomValidators::ArrayNoneAny do
+ subject do
+ described_class.new(['test'], {}, false, scope.new)
+ end
+
+ context 'valid parameters' do
+ it 'does not raise a validation error' do
+ expect_no_validation_error({ 'test' => [] })
+ expect_no_validation_error({ 'test' => [1, 2, 3] })
+ expect_no_validation_error({ 'test' => 'None' })
+ expect_no_validation_error({ 'test' => 'Any' })
+ expect_no_validation_error({ 'test' => 'none' })
+ expect_no_validation_error({ 'test' => 'any' })
+ end
+ end
+
+ context 'invalid parameters' do
+ it 'raises a validation error' do
expect_validation_error({ 'test' => 'some_other_string' })
end
end
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 6e215ea1561..c788da55cd2 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,8 +2,12 @@ require 'spec_helper'
describe API::Helpers::Pagination do
let(:resource) { Project.all }
- let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" }
- let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" }
+ let(:custom_port) { 8080 }
+ let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
+
+ before do
+ stub_config_setting(port: custom_port)
+ end
subject do
Class.new.include(described_class).new
@@ -48,7 +52,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -71,7 +75,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -171,7 +175,7 @@ describe API::Helpers::Pagination do
it 'returns the right link to the next page' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
end
@@ -224,9 +228,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="prev"')
end
@@ -290,8 +294,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="last"')
expect(val).not_to include('rel="prev"')
end
@@ -318,9 +322,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '1')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
expect(val).not_to include('rel="next"')
end
@@ -367,8 +371,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
expect(val).not_to include('rel="prev"')
expect(val).not_to include('rel="next"')
expect(val).not_to include('page=0')
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
index 66af7f81535..99fe8795d91 100644
--- a/spec/lib/api/helpers/related_resources_helpers_spec.rb
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -5,6 +5,40 @@ describe API::Helpers::RelatedResourcesHelpers do
Class.new.include(described_class).new
end
+ describe '#expose_path' do
+ let(:path) { '/api/v4/awesome_endpoint' }
+
+ context 'empty relative URL root' do
+ before do
+ stub_config_setting(relative_url_root: '')
+ end
+
+ it 'returns the existing path' do
+ expect(helpers.expose_path(path)).to eq(path)
+ end
+ end
+
+ context 'slash relative URL root' do
+ before do
+ stub_config_setting(relative_url_root: '/')
+ end
+
+ it 'returns the existing path' do
+ expect(helpers.expose_path(path)).to eq(path)
+ end
+ end
+
+ context 'with relative URL root' do
+ before do
+ stub_config_setting(relative_url_root: '/gitlab/root')
+ end
+
+ it 'returns the existing path' do
+ expect(helpers.expose_path(path)).to eq("/gitlab/root" + path)
+ end
+ end
+ end
+
describe '#expose_url' do
let(:path) { '/api/v4/awesome_endpoint' }
subject(:url) { helpers.expose_url(path) }
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 08165f147bb..00916f80784 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -137,18 +137,6 @@ describe API::Helpers do
it_behaves_like 'user namespace finder'
end
- describe '#user_namespace' do
- let(:namespace_finder) do
- subject.user_namespace
- end
-
- before do
- allow(subject).to receive(:params).and_return({ id: namespace.id })
- end
-
- it_behaves_like 'user namespace finder'
- end
-
describe '#send_git_blob' do
let(:repository) { double }
let(:blob) { double(name: 'foobar') }
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
new file mode 100644
index 00000000000..544d3754c0f
--- /dev/null
+++ b/spec/lib/backup/uploads_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Backup::Uploads do
+ let(:progress) { StringIO.new }
+ subject(:backup) { described_class.new(progress) }
+
+ describe '#initialize' do
+ it 'uses the correct upload dir' do
+ Dir.mktmpdir do |tmpdir|
+ FileUtils.mkdir_p("#{tmpdir}/uploads")
+
+ allow(Gitlab.config.uploads).to receive(:storage_path) { tmpdir }
+
+ expect(backup.app_files_dir).to eq("#{tmpdir}/uploads")
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index 1f53657c59c..316dbf052c3 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Banzai::CommitRenderer do
- describe '.render' do
+ describe '.render', :clean_gitlab_redis_cache do
it 'renders a commit description and title' do
user = build(:user)
project = create(:project, :repository)
@@ -13,7 +13,7 @@ describe Banzai::CommitRenderer do
described_class::ATTRIBUTES.each do |attr|
expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
- expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, {})
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, { skip_project_check: false }).and_call_original
end
described_class.render([project.commit], project, user)
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index b645e49bd43..5b3f679084e 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -13,6 +13,6 @@ describe Banzai::Filter::BlockquoteFenceFilter do
end
it 'allows trailing whitespace on blockquote fence lines' do
- expect(filter(">>> \ntest\n>>> ")).to eq("> test")
+ expect(filter(">>> \ntest\n>>> ")).to eq("\n> test\n")
end
end
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a0270d93d50..7c94cf37e32 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -121,6 +121,49 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
end
end
+ context "youtrack project" do
+ let(:project) { create(:youtrack_project) }
+
+ before do
+ project.update!(issues_enabled: false)
+ end
+
+ context "with right markdown" do
+ let(:issue) { ExternalIssue.new("YT-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with underscores in the prefix" do
+ let(:issue) { ExternalIssue.new("PRJ_1-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with lowercase letters in the prefix" do
+ let(:issue) { ExternalIssue.new("YTkPrj-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with a single-letter prefix" do
+ let(:issue) { ExternalIssue.new("T-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with a lowercase prefix" do
+ let(:issue) { ExternalIssue.new("gl-030", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+ end
+
context "jira project" do
let(:project) { create(:jira_project) }
let(:reference) { issue.to_reference }
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 55c41e55437..72dfd6ff9ea 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -30,6 +30,23 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
end
end
+ describe 'all references' do
+ let(:doc) { reference_filter(merge.to_reference) }
+ let(:tag_el) { doc.css('a').first }
+
+ it 'adds merge request iid' do
+ expect(tag_el["data-iid"]).to eq(merge.iid.to_s)
+ end
+
+ it 'adds project data attribute with project id' do
+ expect(tag_el["data-project-path"]).to eq(project.full_path)
+ end
+
+ it 'does not add `has-tooltip` class' do
+ expect(tag_el["class"]).not_to include('has-tooltip')
+ end
+ end
+
context 'internal reference' do
let(:reference) { merge.to_reference }
@@ -57,9 +74,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
expect(reference_filter(act).to_html).to eq exp
end
- it 'includes a title attribute' do
+ it 'has no title' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('title')).to eq merge.title
+ expect(doc.css('a').first.attr('title')).to eq ""
end
it 'escapes the title attribute' do
@@ -69,9 +86,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
expect(doc.text).to eq "Merge #{reference}"
end
- it 'includes default classes' do
+ it 'includes default classes, without tooltip' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 4c94e4fdae0..f0a5dc8d0d7 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -295,6 +295,25 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
+ shared_examples 'references with HTML entities' do
+ before do
+ milestone.update!(title: '&lt;html&gt;')
+ end
+
+ it 'links to a valid reference' do
+ doc = reference_filter('See %"&lt;html&gt;"')
+
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
+ expect(doc.text).to eq 'See %<html>'
+ end
+
+ it 'ignores invalid milestone names and escapes entities' do
+ act = %(Milestone %"&lt;non valid&gt;")
+
+ expect(reference_filter(act).to_html).to eq act
+ end
+ end
+
shared_context 'project milestones' do
let(:reference) { milestone.to_reference(format: :iid) }
@@ -307,6 +326,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
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'
+ it_behaves_like 'references with HTML entities'
end
shared_context 'group milestones' do
@@ -317,6 +337,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
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 'references with HTML entities'
it 'does not support references by IID' do
doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}")
diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb
new file mode 100644
index 00000000000..5ffe591c9a4
--- /dev/null
+++ b/spec/lib/banzai/filter/output_safety_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::OutputSafety do
+ subject do
+ Class.new do
+ include Banzai::Filter::OutputSafety
+ end.new
+ end
+
+ let(:content) { '<pre><code>foo</code></pre>' }
+
+ context 'when given HTML is safe' do
+ let(:html) { content.html_safe }
+
+ it 'returns safe HTML' do
+ expect(subject.escape_once(html)).to eq(html)
+ end
+ end
+
+ context 'when given HTML is not safe' do
+ let(:html) { content }
+
+ it 'returns escaped HTML' do
+ expect(subject.escape_once(html)).to eq(ERB::Util.html_escape_once(html))
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index 8235c411eb7..6f7acfe7072 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper
- it 'should replace plantuml pre tag with img tag' do
+ it 'replaces plantuml pre tag with img tag' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
@@ -12,7 +12,7 @@ describe Banzai::Filter::PlantumlFilter do
expect(doc.to_s).to eq output
end
- it 'should not replace plantuml pre tag with img tag if disabled' do
+ it 'does not replace plantuml pre tag with img tag if disabled' do
stub_application_setting(plantuml_enabled: false)
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<pre><code lang="plantuml">Bob -&gt; Sara : Hello</code></pre>'
@@ -21,7 +21,7 @@ describe Banzai::Filter::PlantumlFilter do
expect(doc.to_s).to eq output
end
- it 'should not replace plantuml pre tag with img tag if url is invalid' do
+ it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index b13c90b54bd..9c4650b73de 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
- let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
let(:default_context) do
{ suggestions_filter_enabled: true }
end
@@ -23,4 +23,16 @@ describe Banzai::Filter::SuggestionFilter do
expect(result[:class]).to be_nil
end
+
+ context 'multi-line suggestions' do
+ let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
+
+ it 'element has correct data-lang-params' do
+ doc = filter(input, default_context)
+ pre = doc.css('pre').first
+
+ expect(pre[data_attr]).to eq('-3+2')
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index ef52c572898..80ca7a63435 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -45,7 +45,10 @@ describe Banzai::Filter::SyntaxHighlightFilter do
end
context "languages that should be passed through" do
- %w(math mermaid plantuml).each do |lang|
+ let(:delimiter) { described_class::PARAMS_DELIMITER }
+ let(:data_attr) { described_class::LANG_PARAMS_ATTR }
+
+ %w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
@@ -55,6 +58,33 @@ describe Banzai::Filter::SyntaxHighlightFilter do
include_examples "XSS prevention", lang
end
+
+ context "when #{lang} has extra params" do
+ let(:lang_params) { 'foo-bar-kux' }
+
+ it "includes data-lang-params tag with extra information" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
+
+ include_examples "XSS prevention", lang
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}&lt;script&gt;alert(1)&lt;/script&gt;"
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>"
+ end
+ end
+
+ context 'when multiple param delimiters are used' do
+ let(:lang) { 'suggestion' }
+ let(:lang_params) { '-1+10' }
+
+ it "delimits on the first appearance" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
end
end
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 7213cd58ea7..4a9880ac85a 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -58,6 +58,11 @@ describe Banzai::Filter::TableOfContentsFilter do
expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-filled-with-punctuation'
end
+ it 'removes any leading or trailing spaces' do
+ doc = filter(header(1, " \r\n\tTitle with spaces\r\n\t "))
+ expect(doc.css('h1 a').first.attr('href')).to eq '#title-with-spaces'
+ end
+
it 'appends a unique number to duplicates' do
doc = filter(header(1, 'One') + header(2, 'One'))
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index b9059b85fdc..cce1cd0b284 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -70,5 +70,47 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
+
+ context "when the slug is deemed unsafe or invalid" do
+ let(:link) { "alert(1);" }
+
+ invalid_slugs = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ "javascript&#x3A;",
+ "javascript&#x003A;",
+ "java\0script:",
+ " &#14; javascript:"
+ ]
+
+ invalid_slugs.each do |slug|
+ context "with the slug #{slug}" do
+ it "doesn't rewrite a (.) relative link" do
+ filtered_link = filter(
+ "<a href='.#{link}'>Link</a>",
+ project_wiki: wiki,
+ page_slug: slug).children[0]
+
+ expect(filtered_link.attribute('href').value).not_to include(slug)
+ end
+
+ it "doesn't rewrite a (..) relative link" do
+ filtered_link = filter(
+ "<a href='..#{link}'>Link</a>",
+ project_wiki: wiki,
+ page_slug: slug).children[0]
+
+ expect(filtered_link.attribute('href').value).not_to include(slug)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 3b52f6666d0..7b855251a74 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do
)
end
- let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) }
+ let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) }
describe '#render' do
context 'with cache' do
@@ -60,24 +60,38 @@ describe Banzai::ObjectRenderer do
end
context 'without cache' do
- let(:commit) { project.commit }
+ let(:cacheless_class) do
+ Class.new do
+ attr_accessor :title, :redacted_title_html, :project
+
+ def banzai_render_context(field)
+ { project: project, pipeline: :single_line }
+ end
+ end
+ end
+ let(:cacheless_thing) do
+ cacheless_class.new.tap do |thing|
+ thing.title = "Merge branch 'branch-merged' into 'master'"
+ thing.project = project
+ end
+ end
it 'renders and redacts an Array of objects' do
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
- expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
+ expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
it 'calls Banzai::Redactor to perform redaction' do
expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
end
it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do
- expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title, {}).and_call_original
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(cacheless_thing, :title, {}).and_call_original
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
end
end
end
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index aaeec953e4b..718649e0e10 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -13,10 +13,10 @@ describe Banzai::Redactor do
it 'redacts an array of documents' do
doc1 = Nokogiri::HTML
- .fragment('<a class="gfm" data-reference-type="issue">foo</a>')
+ .fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">foo</a>')
doc2 = Nokogiri::HTML
- .fragment('<a class="gfm" data-reference-type="issue">bar</a>')
+ .fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">bar</a>')
redacted_data = redactor.redact([doc1, doc2])
@@ -27,7 +27,7 @@ describe Banzai::Redactor do
end
it 'replaces redacted reference with inner HTML' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue'>foo</a>")
redactor.redact([doc])
expect(doc.to_html).to eq('foo')
end
@@ -35,20 +35,24 @@ describe Banzai::Redactor do
context 'when data-original attribute provided' do
let(:original_content) { '<code>foo</code>' }
it 'replaces redacted reference with original content' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
redactor.redact([doc])
expect(doc.to_html).to eq(original_content)
end
- end
-
- it 'returns <a> tag with original href if it is originally a link reference' do
- href = 'http://localhost:3000'
- doc = Nokogiri::HTML
- .fragment("<a class='gfm' data-reference-type='issue' data-original=#{href} data-link-reference='true'>#{href}</a>")
- redactor.redact([doc])
+ it 'does not replace redacted reference with original content if href is given' do
+ html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Marge'>Marge</a>"
+ doc = Nokogiri::HTML.fragment(html)
+ redactor.redact([doc])
+ expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Marge</a>')
+ end
- expect(doc.to_html).to eq('<a href="http://localhost:3000">http://localhost:3000</a>')
+ it 'uses the original content as the link content if given' do
+ html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Homer'>Marge</a>"
+ doc = Nokogiri::HTML.fragment(html)
+ redactor.redact([doc])
+ expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Homer</a>')
+ end
end
end
@@ -61,7 +65,7 @@ describe Banzai::Redactor do
end
it 'redacts an issue attached' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
redactor.redact([doc])
@@ -69,7 +73,7 @@ describe Banzai::Redactor do
end
it 'redacts an external issue' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
redactor.redact([doc])
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index 650cecfc778..aa828e2f0e9 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -11,16 +11,24 @@ describe Banzai::Renderer do
object
end
+ def fake_cacheless_object
+ object = double('cacheless object')
+
+ allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(false)
+
+ object
+ end
+
describe '#render_field' do
let(:renderer) { described_class }
context 'without cache' do
- let(:commit) { create(:project, :repository).commit }
+ let(:commit) { fake_cacheless_object }
it 'returns cacheless render field' do
- expect(renderer).to receive(:cacheless_render_field).with(commit, :title, {})
+ expect(renderer).to receive(:cacheless_render_field).with(commit, :field, {})
- renderer.render_field(commit, :title)
+ renderer.render_field(commit, :field)
end
end
diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb
deleted file mode 100644
index 79658d710ce..00000000000
--- a/spec/lib/banzai/suggestions_parser_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Banzai::SuggestionsParser do
- describe '.parse' do
- it 'returns a list of suggestion contents' do
- markdown = <<-MARKDOWN.strip_heredoc
- ```suggestion
- foo
- bar
- ```
-
- ```
- nothing
- ```
-
- ```suggestion
- xpto
- baz
- ```
-
- ```thing
- this is not a suggestion, it's a thing
- ```
- MARKDOWN
-
- expect(described_class.parse(markdown)).to eq([" foo\n bar",
- " xpto\n baz"])
- end
- end
-end
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index c96e7ab8495..3496b01ebcc 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -16,6 +16,10 @@ describe Constraints::ProjectUrlConstrainer do
let(:request) { build_request('foo', 'bar') }
it { expect(subject.matches?(request)).to be_falsey }
+
+ context 'existence_check is false' do
+ it { expect(subject.matches?(request, existence_check: false)).to be_truthy }
+ end
end
context "project id ending with .git" do
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index 30016da6828..6648e141b7a 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -26,10 +26,10 @@ describe EventFilter do
set(:push_event) { create(:push_event, project: public_project) }
set(:merged_event) { create(:event, :merged, project: public_project, target: public_project) }
- set(:created_event) { create(:event, :created, project: public_project, target: public_project) }
- set(:updated_event) { create(:event, :updated, project: public_project, target: public_project) }
- set(:closed_event) { create(:event, :closed, project: public_project, target: public_project) }
- set(:reopened_event) { create(:event, :reopened, project: public_project, target: public_project) }
+ set(:created_event) { create(:event, :created, project: public_project, target: create(:issue, project: public_project)) }
+ set(:updated_event) { create(:event, :updated, project: public_project, target: create(:issue, project: public_project)) }
+ set(:closed_event) { create(:event, :closed, project: public_project, target: create(:issue, project: public_project)) }
+ set(:reopened_event) { create(:event, :reopened, project: public_project, target: create(:issue, project: public_project)) }
set(:comments_event) { create(:event, :commented, project: public_project, target: public_project) }
set(:joined_event) { create(:event, :joined, project: public_project, target: public_project) }
set(:left_event) { create(:event, :left, project: public_project, target: public_project) }
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index e0691aba600..21ba72953fb 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -44,6 +44,36 @@ describe ExtractsPath do
end
end
+ context 'ref contains trailing space' do
+ let(:ref) { 'master ' }
+
+ it 'strips surrounding space' do
+ assign_ref_vars
+
+ expect(@ref).to eq('master')
+ end
+ end
+
+ context 'ref contains leading space' do
+ let(:ref) { ' master ' }
+
+ it 'strips surrounding space' do
+ assign_ref_vars
+
+ expect(@ref).to eq('master')
+ end
+ end
+
+ context 'ref contains space in the middle' do
+ let(:ref) { 'master plan ' }
+
+ it 'returns 404' do
+ expect(self).to receive(:render_404)
+
+ assign_ref_vars
+ end
+ end
+
context 'path contains space' do
let(:params) { { path: 'with space', ref: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } }
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index 494c0561975..b9ffe895bf0 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -5,7 +5,7 @@ describe Forever do
subject { described_class.date }
context 'when using PostgreSQL' do
- it 'should return Postgresql future date' do
+ it 'returns Postgresql future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
expect(subject).to eq(described_class::POSTGRESQL_DATE)
@@ -13,7 +13,7 @@ describe Forever do
end
context 'when using MySQL' do
- it 'should return MySQL future date' do
+ it 'returns MySQL future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
expect(subject).to eq(described_class::MYSQL_DATE)
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index d3ab599d5a0..b91a09e3137 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -5,6 +5,65 @@ describe Gitlab::Auth::LDAP::Config do
let(:config) { described_class.new('ldapmain') }
+ def raw_cert
+ <<-EOS
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk4CCQDX+u/9fICksDANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJV
+UzEMMAoGA1UECAwDRm9vMQwwCgYDVQQHDANCYXIxDDAKBgNVBAoMA0JhejEMMAoG
+A1UECwwDUXV4MQ0wCwYDVQQDDARsZGFwMR8wHQYJKoZIhvcNAQkBFhBsZGFwQGV4
+YW1wbGUuY29tMB4XDTE5MDIyNzE1NTUxNFoXDTE5MDMyOTE1NTUxNFowdTELMAkG
+A1UEBhMCVVMxDDAKBgNVBAgMA0ZvbzEMMAoGA1UEBwwDQmFyMQwwCgYDVQQKDANC
+YXoxDDAKBgNVBAsMA1F1eDENMAsGA1UEAwwEbGRhcDEfMB0GCSqGSIb3DQEJARYQ
+bGRhcEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+APuDB/4/AUmTEmhYzN13no4Kt8hkRbLQuENRHlOeQw05/MVdoB1AWLOPzIXn4kex
+GD9tHkoJl8S0QPmAAcPHn5O97e+gd0ze5dRQZl/cSd2/j5zeaMvZ1mCrPN/dOluM
+94Oj+wQU4bEcOlrqIMSh0ezJw10R3IHXCQFeGtIZU57WmKcrryQX4kP7KTOgRw/t
+CYp+NivQHtLbBEj1MU0l10qMS2+w8Qpqov4MdW4gx4wTgId2j1ZZ56+n6Jsc9qoI
+wBWBNL4XU5a3kwhYZDOJoOvI9po33KLdT1dXS81uOFXClp3LGmKDgLTwQ1w+RmQG
++JG4EvTfDIShdcTDXEaOfCECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJM9Btu5g
+k8qDiz5TilvpyoGuI4viCwusARFAFmOB/my/cHlVvkuq4bbfV1KJoWWGJg8GcklL
+cnIdxc35uYM5icr6xXQyrW0GqAO+LEXyUxVQqYETxrQ/LJ03xhBnuF7hvZJIBiky
+GwUy0clJxGfaCeEM8zXwePawLgGjuUawDDQOwigysoWqoMu3VFW8zl8UPa84bow9
+Kn2QmPAkLw4EcqYSCNSSvnyzu5SM64jwLWRXFsmlqD7773oT29vTkqM1EQANFEfT
+7gQomLyPqoPBoFph5oSNn6Rf31QX1Sie92EAKVnZ1XmD68hKzjv6ChCtzTv4jABg
+XrDwnLkORIAF/Q==
+-----END CERTIFICATE-----
+ EOS
+ end
+
+ def raw_key
+ <<-EOS
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD7gwf+PwFJkxJo
+WMzdd56OCrfIZEWy0LhDUR5TnkMNOfzFXaAdQFizj8yF5+JHsRg/bR5KCZfEtED5
+gAHDx5+Tve3voHdM3uXUUGZf3Endv4+c3mjL2dZgqzzf3TpbjPeDo/sEFOGxHDpa
+6iDEodHsycNdEdyB1wkBXhrSGVOe1pinK68kF+JD+ykzoEcP7QmKfjYr0B7S2wRI
+9TFNJddKjEtvsPEKaqL+DHVuIMeME4CHdo9WWeevp+ibHPaqCMAVgTS+F1OWt5MI
+WGQziaDryPaaN9yi3U9XV0vNbjhVwpadyxpig4C08ENcPkZkBviRuBL03wyEoXXE
+w1xGjnwhAgMBAAECggEAbw82GVui6uUpjLAhjm3CssAi1TcJ2+L0aq1IMe5Bd3ay
+mkg0apY+VNPboQl6zuNxbJh3doPz42UhB8sxfE0Ktwd4KIb4Bxap7+2stwmkCGoN
+NVy0c8d2NWuHzuZ2XXTK2vMu5Wd/HWD0l66o14sJEoEpZlB7yU216UevmjSayxjh
+aBTSaYyyrf24haTaCuqwph/V73ZlMpFdSALGny0uiP/5inxciMCkMpHfX6BflSb4
+EGKsIYt9BJ0kY4GNG5bCP7971UCxp2eEJhU2fV8HuFGCOD12IqSpUqPxHxjsWpfx
+T7FZ3V2kM/58Ca+5LB2y3atcPIdY0/g7/43V4VD+7QKBgQD/PO4/0cmZuuLU1LPT
+C/C596kPK0JLlvvRqhbz4byRAkW/n7uQFG7TMtFNle3UmT7rk7pjtbHnByqzEd+9
+jMhBysjHOMg0+DWm7fEtSg/tJ3qLVO3nbdA4qmXYobLcLoG+PCYRLskEHHqTG/Bv
+QZLbavOU6rrTqckNr1TMpNBmXwKBgQD8Q0C2YTOpwgjRUe8i6Chnc3o4x8a1i98y
+9la6c7y7acWHSbEczMkNfEBrbM73rTb+bBA0Zqw+Z1gkv8bGpvGxX8kbSfJJ2YKW
+9koxpLNTVNVapqBa9ImiaozV285dz9Ukx8bnMOJlTELpOl7RRV7iF0smYjfHIl3D
+Yxyda/MtfwKBgHb9l/Dmw77IkqE4PFFimqqIHCe3OiP1UpavXh36midcUNoCBLYp
+4HTTlyI9iG/5tYysBVQgy7xx6eUrqww6Ss3pVOsTvLp9EL4u5aYAhiZApm+4e2TO
+HCmevvZcg/8EK3Zdoj2Wex5QjJBykQe9IVLrrH07ZTfySon3uGfjWkivAoGAGvqS
+VC8HGHOw/7n0ilYr5Ax8mM/813OzFj80PVKdb6m7P2HJOFxKcE/Gj/aeF+0FgaZL
+AV+tsirZSWzdNGesV5z35Bw/dlh11/FVNAP6TcI34y8I3VFj2uPsVf7hDjVpBTr8
+ccNPoyfJzCm69ESoBiQZnGxKrNhnELtr1wYxhr8CgYApWwf4hVrTWV1zs+pEJenh
+AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
+0Ff8afd2Q/OfBeUdq9KA4JO9fNqzEwOWvv8Ryn4ZSYcAuLP7IVJKjjI6R7rYaO/G
+3OWJdizbykGOi0BFDu+3dw==
+-----END PRIVATE KEY-----
+ EOS
+ end
+
describe '.servers' do
it 'returns empty array if no server information is available' do
allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false)
@@ -89,6 +148,42 @@ describe Gitlab::Auth::LDAP::Config do
expect(config.adapter_options[:encryption]).to include({ method: :start_tls })
end
+ it 'transforms SSL cert and key to OpenSSL objects' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'start_tls',
+ 'tls_options' => {
+ 'cert' => raw_cert,
+ 'key' => raw_key
+ }
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate)
+ expect(config.adapter_options[:encryption][:tls_options][:key]).to be_a(OpenSSL::PKey::RSA)
+ end
+
+ it 'logs an error when an invalid key or cert are configured' do
+ allow(Rails.logger).to receive(:error)
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'start_tls',
+ 'tls_options' => {
+ 'cert' => 'invalid cert',
+ 'key' => 'invalid_key'
+ }
+ }
+ )
+
+ config.adapter_options
+
+ expect(Rails.logger).to have_received(:error).with(/LDAP TLS Options/).twice
+ end
+
context 'when verify_certificates is enabled' do
it 'sets tls_options to OpenSSL defaults' do
stub_ldap_config(
@@ -130,7 +225,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
- 'ca_file' => '/etc/ca.pem'
+ 'tls_options' => {
+ 'ca_file' => '/etc/ca.pem'
+ }
}
)
@@ -145,7 +242,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
- 'ca_file' => ' '
+ 'tls_options' => {
+ 'ca_file' => ' '
+ }
}
)
@@ -160,7 +259,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
- 'ssl_version' => 'TLSv1_2'
+ 'tls_options' => {
+ 'ssl_version' => 'TLSv1_2'
+ }
}
)
@@ -175,7 +276,9 @@ describe Gitlab::Auth::LDAP::Config do
'host' => 'ldap.example.com',
'port' => 686,
'encryption' => 'simple_tls',
- 'ssl_version' => ' '
+ 'tls_options' => {
+ 'ssl_version' => ' '
+ }
}
)
@@ -223,6 +326,23 @@ describe Gitlab::Auth::LDAP::Config do
)
end
+ it 'transforms SSL cert and key to OpenSSL objects' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'start_tls',
+ 'tls_options' => {
+ 'cert' => raw_cert,
+ 'key' => raw_key
+ }
+ }
+ )
+
+ expect(config.omniauth_options[:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate)
+ expect(config.omniauth_options[:tls_options][:key]).to be_a(OpenSSL::PKey::RSA)
+ end
+
context 'when verify_certificates is enabled' do
it 'specifies disable_verify_certificates as false' do
stub_ldap_config(
@@ -261,11 +381,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
- 'ca_file' => '/etc/ca.pem'
+ 'tls_options' => {
+ 'ca_file' => '/etc/ca.pem'
+ }
}
)
- expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' })
+ expect(config.omniauth_options[:tls_options]).to include({ ca_file: '/etc/ca.pem' })
end
end
@@ -277,11 +399,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
- 'ca_file' => ' '
+ 'tls_options' => {
+ 'ca_file' => ' '
+ }
}
)
- expect(config.omniauth_options).not_to have_key(:ca_file)
+ expect(config.omniauth_options[:tls_options]).not_to have_key(:ca_file)
end
end
@@ -293,11 +417,13 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
- 'ssl_version' => 'TLSv1_2'
+ 'tls_options' => {
+ 'ssl_version' => 'TLSv1_2'
+ }
}
)
- expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' })
+ expect(config.omniauth_options[:tls_options]).to include({ ssl_version: 'TLSv1_2' })
end
end
@@ -309,11 +435,14 @@ describe Gitlab::Auth::LDAP::Config do
'port' => 686,
'encryption' => 'simple_tls',
'verify_certificates' => true,
- 'ssl_version' => ' '
+ 'tls_options' => {
+ 'ssl_version' => ' '
+ }
}
)
- expect(config.omniauth_options).not_to have_key(:ssl_version)
+ # OpenSSL default params includes `ssl_version` so we just check that it's not blank
+ expect(config.omniauth_options[:tls_options]).not_to include({ ssl_version: ' ' })
end
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index dcbd12fe190..b765c265e69 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -207,6 +207,7 @@ describe Gitlab::Auth::OAuth::User do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:name) { 'John Doe' }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
allow(ldap_user).to receive(:dn) { dn }
end
@@ -221,6 +222,7 @@ describe Gitlab::Auth::OAuth::User do
it "creates a user with dual LDAP and omniauth identities" do
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
+ expect(gl_user.name).to eql 'John Doe'
expect(gl_user.email).to eql 'johndoe@example.com'
expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
@@ -232,11 +234,13 @@ describe Gitlab::Auth::OAuth::User do
)
end
- it "has email set as synced" do
+ it "has name and email set as synced" do
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
- it "has email set as read-only" do
+ it "has name and email set as read-only" do
+ expect(gl_user.read_only_attribute?(:name)).to be_truthy
expect(gl_user.read_only_attribute?(:email)).to be_truthy
end
@@ -246,7 +250,7 @@ describe Gitlab::Auth::OAuth::User do
end
context "and LDAP user has an account already" do
- let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
+ let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
@@ -254,6 +258,7 @@ describe Gitlab::Auth::OAuth::User do
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
+ expect(gl_user.name).to eql 'John Doe'
expect(gl_user.email).to eql 'john@example.com'
expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index a4a6338961e..3b5ca7c950c 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -5,7 +5,15 @@ describe Gitlab::Auth do
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
- expect(subject::API_SCOPES).to eq %i[api read_user sudo read_repository]
+ expect(subject::API_SCOPES).to eq %i[api read_user]
+ end
+
+ it 'ADMIN_SCOPES contains all scopes for ADMIN access' do
+ expect(subject::ADMIN_SCOPES).to eq %i[sudo]
+ end
+
+ it 'REPOSITORY_SCOPES contains all scopes for REPOSITORY access' do
+ expect(subject::REPOSITORY_SCOPES).to eq %i[read_repository write_repository]
end
it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
@@ -19,7 +27,29 @@ describe Gitlab::Auth do
it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true)
- expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid profile email]
+ expect(subject.optional_scopes).to eq %i[read_user read_repository write_repository read_registry sudo openid profile email]
+ end
+ end
+
+ context 'available_scopes' do
+ it 'contains all non-default scopes' do
+ stub_container_registry_config(enabled: true)
+
+ expect(subject.all_available_scopes).to eq %i[api read_user read_repository write_repository read_registry sudo]
+ end
+
+ it 'contains for non-admin user all non-default scopes without ADMIN access' do
+ stub_container_registry_config(enabled: true)
+ user = create(:user, admin: false)
+
+ expect(subject.available_scopes_for(user)).to eq %i[api read_user read_repository write_repository read_registry]
+ end
+
+ it 'contains for admin user all non-default scopes with ADMIN access' do
+ stub_container_registry_config(enabled: true)
+ user = create(:user, admin: true)
+
+ expect(subject.available_scopes_for(user)).to eq %i[api read_user read_repository write_repository read_registry sudo]
end
context 'registry_scopes' do
@@ -122,7 +152,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities))
end
it 'does not try password auth before oauth' do
@@ -150,7 +180,7 @@ describe Gitlab::Auth do
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_only_authentication_abilities))
end
end
@@ -182,8 +212,19 @@ describe Gitlab::Auth do
it 'succeeds for personal access tokens with the `api` scope' do
personal_access_token = create(:personal_access_token, scopes: ['api'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, full_authentication_abilities))
+ expect_results_with_abilities(personal_access_token, full_authentication_abilities)
+ end
+
+ it 'succeeds for personal access tokens with the `read_repository` scope' do
+ personal_access_token = create(:personal_access_token, scopes: ['read_repository'])
+
+ expect_results_with_abilities(personal_access_token, [:download_code])
+ end
+
+ it 'succeeds for personal access tokens with the `write_repository` scope' do
+ personal_access_token = create(:personal_access_token, scopes: ['write_repository'])
+
+ expect_results_with_abilities(personal_access_token, [:download_code, :push_code])
end
context 'when registry is enabled' do
@@ -194,28 +235,24 @@ describe Gitlab::Auth do
it 'succeeds for personal access tokens with the `read_registry` scope' do
personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, [:read_container_image]))
+ expect_results_with_abilities(personal_access_token, [:read_container_image])
end
end
it 'succeeds if it is an impersonation token' do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_access_token, full_authentication_abilities))
+ expect_results_with_abilities(impersonation_token, full_authentication_abilities)
end
it 'limits abilities based on scope' do
personal_access_token = create(:personal_access_token, scopes: %w[read_user sudo])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, []))
+ expect_results_with_abilities(personal_access_token, [])
end
it 'fails if password is nil' do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
- expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
+ expect_results_with_abilities(nil, nil, false)
end
end
@@ -479,7 +516,7 @@ describe Gitlab::Auth do
]
end
- def read_authentication_abilities
+ def read_only_authentication_abilities
[
:read_project,
:download_code,
@@ -488,7 +525,7 @@ describe Gitlab::Auth do
end
def read_write_authentication_abilities
- read_authentication_abilities + [
+ read_only_authentication_abilities + [
:push_code,
:create_container_image
]
@@ -499,4 +536,10 @@ describe Gitlab::Auth do
:admin_container_image
]
end
+
+ def expect_results_with_abilities(personal_access_token, abilities, success = true)
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: success, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(personal_access_token&.user, nil, personal_access_token.nil? ? nil : :personal_access_token, abilities))
+ end
end
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
new file mode 100644
index 00000000000..42bc509eeef
--- /dev/null
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::AuthorizedKeys do
+ let(:logger) { double('logger').as_null_object }
+
+ subject { described_class.new(logger) }
+
+ describe '#add_key' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "adds a line at the end of the file and strips trailing garbage" do
+ auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
+
+ expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E')
+ expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage'))
+ .to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n")
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'creates the file' do
+ expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+ end
+
+ describe '#batch_add_keys' do
+ let(:keys) do
+ [
+ double(shell_id: 'key-12', key: 'ssh-dsa ASDFASGADG trailing garbage'),
+ double(shell_id: 'key-123', key: 'ssh-rsa GFDGDFSGSDFG')
+ ]
+ end
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "adds lines at the end of the file" do
+ auth_line1 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG"
+ auth_line2 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG"
+
+ expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG')
+ expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG')
+ expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n")
+ end
+
+ context "invalid key" do
+ let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
+
+ it "doesn't add keys" do
+ expect(subject.batch_add_keys(keys)).to be_falsey
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n")
+ end
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'creates the file' do
+ expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+ end
+
+ describe '#rm_key' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "removes the right line" do
+ other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E"
+ delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E"
+ erased_line = delete_line.gsub(/./, '#')
+ File.open(tmp_authorized_keys_path, 'a') do |auth_file|
+ auth_file.puts delete_line
+ auth_file.puts other_line
+ end
+
+ expect(logger).to receive(:info).with('Removing key (key-741)')
+ expect(subject.rm_key('key-741')).to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n")
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'returns false' do
+ expect(subject.rm_key('key-741')).to be_falsey
+ end
+ end
+ end
+
+ describe '#clear' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "returns true" do
+ expect(subject.clear).to be_truthy
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it "still returns true" do
+ expect(subject.clear).to be_truthy
+ end
+ end
+ end
+
+ describe '#list_key_ids' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture(
+ existing_content:
+ "key-1\tssh-dsa AAA\nkey-2\tssh-rsa BBB\nkey-3\tssh-rsa CCC\nkey-9000\tssh-rsa DDD\n"
+ )
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it 'returns array of key IDs' do
+ expect(subject.list_key_ids).to eq([1, 2, 3, 9000])
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'returns an empty array' do
+ expect(subject.list_key_ids).to be_empty
+ end
+ end
+ end
+
+ def create_authorized_keys_fixture(existing_content: 'existing content')
+ FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path))
+ File.open(tmp_authorized_keys_path, 'w') { |file| file.puts(existing_content) }
+ end
+
+ def delete_authorized_keys_file
+ File.delete(tmp_authorized_keys_path) if File.exist?(tmp_authorized_keys_path)
+ end
+
+ def tmp_authorized_keys_path
+ Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
index 27281333348..0a5b99d27e7 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -3,6 +3,12 @@ require 'spec_helper'
# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ end
+
context 'when diff files can be deleted' do
let(:merge_request) { create(:merge_request, :merged) }
let!(:merge_request_diff) do
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
index bc71a90605a..d3f7f1ded16 100644
--- 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
@@ -172,14 +172,12 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
let(:exception) { ActiveRecord::RecordNotFound }
let(:perform_ignoring_exceptions) do
- begin
- subject.perform(start_id, stop_id)
- rescue described_class::Error
- end
+ subject.perform(start_id, stop_id)
+ rescue described_class::Error
end
before do
- allow_any_instance_of(described_class::MergeRequestDiff::ActiveRecord_Relation)
+ allow_any_instance_of(ActiveRecord::Relation)
.to receive(:update_all).and_raise(exception)
end
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index 7c7e58d6bb7..582396275ed 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -51,7 +51,7 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201
statuses[:pending]]
end
- it 'recovers from unique constraint violation only twice' do
+ it 'recovers from unique constraint violation only twice', :quarantine do
allow(described_class::Migratable::Stage)
.to receive(:find_by).and_return(nil)
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb
new file mode 100644
index 00000000000..16b9de6a84e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb
@@ -0,0 +1,253 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateLegacyUploads, :migration, schema: 20190103140724 do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+
+ # rubocop: disable RSpec/FactoriesInMigrationSpecs
+ let(:namespace) { create(:namespace) }
+ let(:project) { create(:project, :legacy_storage, namespace: namespace) }
+ let(:issue) { create(:issue, project: project) }
+
+ let(:note1) { create(:note, note: 'some note text awesome', project: project, noteable: issue) }
+ let(:note2) { create(:note, note: 'some note', project: project, noteable: issue) }
+
+ let(:hashed_project) { create(:project, namespace: namespace) }
+ let(:issue_hashed_project) { create(:issue, project: hashed_project) }
+ let(:note_hashed_project) { create(:note, note: 'some note', project: hashed_project, attachment: 'text.pdf', noteable: issue_hashed_project) }
+
+ let(:standard_upload) do
+ create(:upload,
+ path: "secretabcde/image.png",
+ model_id: create(:project).id, model_type: 'Project', uploader: 'FileUploader')
+ end
+
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+
+ namespace
+ project
+ issue
+ note1
+ note2
+ hashed_project
+ issue_hashed_project
+ note_hashed_project
+ standard_upload
+ end
+
+ def create_remote_upload(model, filename)
+ create(:upload, :attachment_upload,
+ path: "note/attachment/#{model.id}/#{filename}", secret: nil,
+ store: ObjectStorage::Store::REMOTE, model: model)
+ end
+
+ def create_upload(model, filename, with_file = true)
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ upload = if with_file
+ create(:upload, :with_file, :attachment_upload, params)
+ else
+ create(:upload, :attachment_upload, params)
+ end
+
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ end
+
+ let(:start_id) { 1 }
+ let(:end_id) { 10000 }
+
+ def new_upload_legacy
+ Upload.find_by(model_id: project.id, model_type: 'Project')
+ end
+
+ def new_upload_hashed
+ Upload.find_by(model_id: hashed_project.id, model_type: 'Project')
+ end
+
+ shared_examples 'migrates files correctly' do
+ before do
+ described_class.new.perform(start_id, end_id)
+ end
+
+ it 'removes all the legacy upload records' do
+ expect(Upload.where(uploader: 'AttachmentUploader')).to be_empty
+
+ expect(standard_upload.reload).to eq(standard_upload)
+ end
+
+ it 'creates new upload records correctly' do
+ expect(new_upload_legacy.secret).not_to be_nil
+ expect(new_upload_legacy.path).to end_with("#{new_upload_legacy.secret}/image.png")
+ expect(new_upload_legacy.model_id).to eq(project.id)
+ expect(new_upload_legacy.model_type).to eq('Project')
+ expect(new_upload_legacy.uploader).to eq('FileUploader')
+
+ expect(new_upload_hashed.secret).not_to be_nil
+ expect(new_upload_hashed.path).to end_with("#{new_upload_hashed.secret}/text.pdf")
+ expect(new_upload_hashed.model_id).to eq(hashed_project.id)
+ expect(new_upload_hashed.model_type).to eq('Project')
+ expect(new_upload_hashed.uploader).to eq('FileUploader')
+ end
+
+ it 'updates the legacy upload notes so that they include the file references in the markdown' do
+ expected_path = File.join('/uploads', new_upload_legacy.secret, 'image.png')
+ expected_markdown = "some note text awesome \n ![image](#{expected_path})"
+ expect(note1.reload.note).to eq(expected_markdown)
+
+ expected_path = File.join('/uploads', new_upload_hashed.secret, 'text.pdf')
+ expected_markdown = "some note \n [text.pdf](#{expected_path})"
+ expect(note_hashed_project.reload.note).to eq(expected_markdown)
+ end
+
+ it 'removed the attachments from the note model' do
+ expect(note1.reload.attachment.file).to be_nil
+ expect(note2.reload.attachment.file).to be_nil
+ expect(note_hashed_project.reload.attachment.file).to be_nil
+ end
+ end
+
+ context 'when legacy uploads are stored in local storage' do
+ let!(:legacy_upload1) { create_upload(note1, 'image.png') }
+ let!(:legacy_upload_not_found) { create_upload(note2, 'image.png', false) }
+ let!(:legacy_upload_hashed) { create_upload(note_hashed_project, 'text.pdf', with_file: true) }
+
+ shared_examples 'removes legacy local files' do
+ it 'removes all the legacy upload records' do
+ expect(File.exist?(legacy_upload1.absolute_path)).to be_truthy
+ expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_truthy
+
+ described_class.new.perform(start_id, end_id)
+
+ expect(File.exist?(legacy_upload1.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_falsey
+ end
+ end
+
+ context 'when object storage is disabled for FileUploader' do
+ it_behaves_like 'migrates files correctly'
+ it_behaves_like 'removes legacy local files'
+
+ it 'moves legacy uploads to the correct location' do
+ described_class.new.perform(start_id, end_id)
+
+ expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png')
+ expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf')
+
+ expect(File.exist?(expected_path1)).to be_truthy
+ expect(File.exist?(expected_path2)).to be_truthy
+ end
+
+ context 'when the upload move fails' do
+ it 'does not remove old uploads' do
+ expect(FileUploader).to receive(:copy_to).twice.and_raise('failed')
+
+ described_class.new.perform(start_id, end_id)
+
+ expect(legacy_upload1.reload).to eq(legacy_upload1)
+ expect(legacy_upload_hashed.reload).to eq(legacy_upload_hashed)
+ expect(standard_upload.reload).to eq(standard_upload)
+ end
+ end
+ end
+
+ context 'when object storage is enabled for FileUploader' do
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ it_behaves_like 'migrates files correctly'
+ it_behaves_like 'removes legacy local files'
+
+ # The process of migrating to object storage is a manual one,
+ # so it would go against expectations to automatically migrate these files
+ # to object storage during this migration.
+ # After this migration, these files should be able to successfully migrate to object storage.
+ it 'stores files locally' do
+ described_class.new.perform(start_id, end_id)
+
+ expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png')
+ expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf')
+
+ expect(File.exist?(expected_path1)).to be_truthy
+ expect(File.exist?(expected_path2)).to be_truthy
+ end
+ end
+
+ context 'with legacy_diff_note upload' do
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:legacy_diff_note) { create(:legacy_diff_note_on_merge_request, note: 'some note', project: project, noteable: merge_request) }
+ let!(:legacy_upload_diff_note) do
+ create(:upload, :with_file, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{legacy_diff_note.id}/some_legacy.pdf", model: legacy_diff_note)
+ end
+
+ before do
+ described_class.new.perform(start_id, end_id)
+ end
+
+ it 'does not remove legacy diff note file' do
+ expect(File.exist?(legacy_upload_diff_note.absolute_path)).to be_truthy
+ end
+
+ it 'removes all the legacy upload records except for the one with legacy_diff_note' do
+ expect(Upload.where(uploader: 'AttachmentUploader')).to eq([legacy_upload_diff_note])
+ end
+
+ it 'adds link to the troubleshooting documentation to the note' do
+ help_doc_link = 'https://docs.gitlab.com/ee/administration/troubleshooting/migrations.html#legacy-upload-migration'
+
+ expect(legacy_diff_note.reload.note).to include(help_doc_link)
+ end
+ end
+ end
+
+ context 'when legacy uploads are stored in object storage' do
+ let!(:legacy_upload1) { create_remote_upload(note1, 'image.png') }
+ let!(:legacy_upload_not_found) { create_remote_upload(note2, 'non-existing.pdf') }
+ let!(:legacy_upload_hashed) { create_remote_upload(note_hashed_project, 'text.pdf') }
+ let(:remote_files) do
+ [
+ { key: "#{legacy_upload1.path}" },
+ { key: "#{legacy_upload_hashed.path}" }
+ ]
+ end
+ let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:bucket) { connection.directories.create(key: 'uploads') }
+
+ def create_remote_files
+ remote_files.each { |file| bucket.files.create(file) }
+ end
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ create_remote_files
+ end
+
+ it_behaves_like 'migrates files correctly'
+
+ it 'moves legacy uploads to the correct remote location' do
+ described_class.new.perform(start_id, end_id)
+
+ connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
+ expect(connection.get_object('uploads', new_upload_legacy.path)[:status]).to eq(200)
+ expect(connection.get_object('uploads', new_upload_hashed.path)[:status]).to eq(200)
+ end
+
+ it 'removes all the legacy upload records' do
+ described_class.new.perform(start_id, end_id)
+
+ remote_files.each do |remote_file|
+ expect(bucket.files.get(remote_file[:key])).to be_nil
+ end
+ end
+ end
+ # rubocop: enable RSpec/FactoriesInMigrationSpecs
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index f8107dd40b9..4db829b1e7b 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -30,6 +30,6 @@ describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 201
described_class.new.perform(100, 101)
- expect(stages.all.pluck(:position)).to eq [2, 3]
+ expect(stages.all.order(:id).pluck(:position)).to eq [2, 3]
end
end
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 812e0cc6947..128e118ac17 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
end
shared_examples 'consistent kubernetes namespace attributes' do
- it 'should populate namespace and service account information' do
+ it 'populates namespace and service account information' do
migration.perform
clusters_with_namespace.each do |cluster|
@@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do
let(:cluster_projects) { cluster_projects_table.all }
- it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do
+ it 'creates a Clusters::KubernetesNamespace per Clusters::Project' do
expect do
migration.perform
end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects_table.count)
@@ -57,7 +57,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
create_kubernetes_namespace(clusters_table.all)
end
- it 'should not create any Clusters::KubernetesNamespace' do
+ it 'does not create any Clusters::KubernetesNamespace' do
expect do
migration.perform
end.not_to change(Clusters::KubernetesNamespace, :count)
@@ -78,7 +78,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count)
end
- it 'should not modify clusters with Clusters::KubernetesNamespace' do
+ it 'does not modify clusters with Clusters::KubernetesNamespace' do
migration.perform
with_kubernetes_namespace.each do |cluster|
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
index 3e009fed0f1..c6bc3db88a3 100644
--- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -9,6 +9,9 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
before do
# This migration was created before we introduced metadata configs
stub_feature_flags(ci_build_metadata_config: false)
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
end
let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
index 8582af96199..0e73c8c59c9 100644
--- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
@@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch
migration.perform(1, 3)
end
- it 'it creates the fork network' do
+ it 'creates the fork network' do
expect(fork_network1).not_to be_nil
expect(fork_network2).not_to be_nil
end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
new file mode 100644
index 00000000000..4a81a37d341
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+
+ let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') }
+ let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') }
+ let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') }
+
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:merge_request_assignees) { table(:merge_request_assignees) }
+
+ def create_merge_request(id, params = {})
+ params.merge!(id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}")
+
+ merge_requests.create(params)
+ end
+
+ describe '#perform' do
+ it 'creates merge_request_assignees rows according to merge_requests' do
+ create_merge_request(2, assignee_id: user.id)
+ create_merge_request(3, assignee_id: user_2.id)
+ create_merge_request(4, assignee_id: user_3.id)
+ # Test filtering already migrated row
+ merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id)
+
+ subject.perform(1, 4)
+
+ rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') }
+ existing_rows = [
+ { 'merge_request_id' => 2, 'user_id' => user_3.id }
+ ]
+ created_rows = [
+ { 'merge_request_id' => 3, 'user_id' => user_2.id },
+ { 'merge_request_id' => 4, 'user_id' => user_3.id }
+ ]
+ expected_rows = existing_rows + created_rows
+
+ expect(rows.size).to eq(expected_rows.size)
+ expected_rows.each do |expected_row|
+ expect(rows).to include(expected_row)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
new file mode 100644
index 00000000000..740781f1aa5
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, extra_params = {})
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }.merge(extra_params)
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly updates opened mergeable MRs to unchecked' do
+ create_merge_request(1, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(2, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(3, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(4, state: 'merged', merge_status: 'can_be_merged')
+ create_merge_request(5, state: 'opened', merge_status: 'cannot_be_merged')
+
+ subject.perform(1, 5)
+
+ expected_rows = [
+ { id: 1, state: 'opened', merge_status: 'unchecked' },
+ { id: 2, state: 'opened', merge_status: 'unchecked' },
+ { id: 3, state: 'opened', merge_status: 'unchecked' },
+ { id: 4, state: 'merged', merge_status: 'can_be_merged' },
+ { id: 5, state: 'opened', merge_status: 'cannot_be_merged' }
+ ]
+
+ rows = merge_requests.order(:id).map do |row|
+ row.attributes.slice('id', 'state', 'merge_status').symbolize_keys
+ end
+
+ expect(rows).to eq(expected_rows)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
new file mode 100644
index 00000000000..d494ce68c5b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
+
+describe ScheduleCalculateWikiSizes, :migration, :sidekiq do
+ let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:project_statistics) { table(:project_statistics) }
+
+ context 'when missing wiki sizes exist' do
+ before do
+ namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
+ projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
+ projects.create!(id: 2, name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: 1)
+ projects.create!(id: 3, name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: 1)
+ project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
+ project_statistics.create!(id: 2, project_id: 2, namespace_id: 1, wiki_size: nil)
+ project_statistics.create!(id: 3, project_id: 3, namespace_id: 1, wiki_size: nil)
+ end
+
+ it 'schedules a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 2, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+ end
+
+ it 'calculates missing wiki sizes' do
+ expect(project_statistics.find_by(id: 2).wiki_size).to be_nil
+ expect(project_statistics.find_by(id: 3).wiki_size).to be_nil
+
+ migrate!
+
+ expect(project_statistics.find_by(id: 2).wiki_size).not_to be_nil
+ expect(project_statistics.find_by(id: 3).wiki_size).not_to be_nil
+ end
+ end
+
+ context 'when missing wiki sizes do not exist' do
+ before do
+ namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
+ projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
+ project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
+ end
+
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 7d3d8a949ef..1d0ffb5e9df 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -195,4 +195,44 @@ describe Gitlab::BackgroundMigration do
end
end
end
+
+ describe '.dead_jobs?' do
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue)]
+ end
+
+ context 'when there are dead jobs present' do
+ before do
+ allow(Sidekiq::DeadSet).to receive(:new).and_return(queue)
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.dead_jobs?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.dead_jobs?('Bar')).to eq(false)
+ end
+ end
+ end
+
+ describe '.retrying_jobs?' do
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue)]
+ end
+
+ context 'when there are dead jobs present' do
+ before do
+ allow(Sidekiq::RetrySet).to receive(:new).and_return(queue)
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.retrying_jobs?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.retrying_jobs?('Bar')).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index 20fa4f879c3..bcef0b7e120 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -59,6 +59,16 @@ describe Gitlab::Badge::Pipeline::Template do
end
end
+ context 'when status is preparing' do
+ before do
+ allow(badge).to receive(:status).and_return('preparing')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#dfb317'
+ end
+ end
+
context 'when status is unknown' do
before do
allow(badge).to receive(:status).and_return('unknown')
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index c432cc223b9..2e90f6c7f71 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -5,6 +5,7 @@ describe Gitlab::BitbucketImport::Importer do
before do
stub_omniauth_provider('bitbucket')
+ stub_feature_flags(stricter_mr_branch_name: false)
end
let(:statuses) do
@@ -95,6 +96,9 @@ describe Gitlab::BitbucketImport::Importer do
subject { described_class.new(project) }
describe '#import_pull_requests' do
+ let(:source_branch_sha) { sample.commits.last }
+ let(:target_branch_sha) { sample.commits.first }
+
before do
allow(subject).to receive(:import_wiki)
allow(subject).to receive(:import_issues)
@@ -102,9 +106,9 @@ describe Gitlab::BitbucketImport::Importer do
pull_request = instance_double(
Bitbucket::Representation::PullRequest,
iid: 10,
- source_branch_sha: sample.commits.last,
+ source_branch_sha: source_branch_sha,
source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch,
- target_branch_sha: sample.commits.first,
+ target_branch_sha: target_branch_sha,
target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch,
title: 'This is a title',
description: 'This is a test pull request',
@@ -162,6 +166,19 @@ describe Gitlab::BitbucketImport::Importer do
expect(reply_note).to be_a(DiffNote)
expect(reply_note.note).to eq(@reply.note)
end
+
+ context "when branches' sha is not found in the repository" do
+ let(:source_branch_sha) { 'a' * Commit::MIN_SHA_LENGTH }
+ let(:target_branch_sha) { 'b' * Commit::MIN_SHA_LENGTH }
+
+ it 'uses the pull request sha references' do
+ expect { subject.execute }.to change { MergeRequest.count }.by(1)
+
+ merge_request_diff = MergeRequest.first.merge_request_diff
+ expect(merge_request_diff.head_commit_sha).to eq source_branch_sha
+ expect(merge_request_diff.start_commit_sha).to eq target_branch_sha
+ end
+ end
end
context 'issues statuses' do
@@ -206,6 +223,46 @@ describe Gitlab::BitbucketImport::Importer do
body: {}.to_json)
end
+ context 'creating labels on project' do
+ before do
+ allow(importer).to receive(:import_wiki)
+ end
+
+ it 'creates labels as expected' do
+ expect { importer.execute }.to change { Label.count }.from(0).to(Gitlab::BitbucketImport::Importer::LABELS.size)
+ end
+
+ it 'does not fail if label is already existing' do
+ label = Gitlab::BitbucketImport::Importer::LABELS.first
+ ::Labels::CreateService.new(label).execute(project: project)
+
+ expect { importer.execute }.not_to raise_error
+ end
+
+ it 'does not create new labels' do
+ Gitlab::BitbucketImport::Importer::LABELS.each do |label|
+ create(:label, project: project, title: label[:title])
+ end
+
+ expect { importer.execute }.not_to change { Label.count }
+ end
+
+ it 'does not update existing ones' do
+ label_title = Gitlab::BitbucketImport::Importer::LABELS.first[:title]
+ existing_label = create(:label, project: project, title: label_title)
+ # Reload label from database so we avoid timestamp comparison issues related to time precision when comparing
+ # attributes later.
+ existing_label.reload
+
+ Timecop.freeze(Time.now + 1.minute) do
+ importer.execute
+
+ label_after_import = project.labels.find(existing_label.id)
+ expect(label_after_import.attributes).to eq(existing_label.attributes)
+ end
+ end
+ end
+
it 'maps statuses to open or closed' do
allow(importer).to receive(:import_wiki)
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index 1e90a2ef27f..cc09804fd53 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -105,6 +105,7 @@ describe Gitlab::BitbucketServerImport::Importer do
expect(merge_request.metrics.merged_by).to eq(project.owner)
expect(merge_request.metrics.merged_at).to eq(@merge_event.merge_timestamp)
expect(merge_request.merge_commit_sha).to eq('12345678')
+ expect(merge_request.state_id).to eq(3)
end
it 'imports comments' do
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index 77366e91dca..71b64a3b9df 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -48,10 +48,118 @@ describe Gitlab::Checks::BranchCheck do
context 'when project repository is empty' do
let(:project) { create(:project) }
- it 'raises an error if the user is not allowed to push to protected branches' do
- expect(user_access).to receive(:can_push_to_branch?).and_return(false)
+ context 'user is not allowed to push to protected branches' do
+ before do
+ allow(user_access)
+ .to receive(:can_push_to_branch?)
+ .and_return(false)
+ end
+
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/)
+ end
+ end
+
+ context 'user is allowed to push to protected branches' do
+ before do
+ allow(user_access)
+ .to receive(:can_push_to_branch?)
+ .and_return(true)
+ end
+
+ it 'allows branch creation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+ end
+
+ context 'branch creation' do
+ let(:oldrev) { '0000000000000000000000000000000000000000' }
+ let(:ref) { 'refs/heads/feature' }
+
+ context 'user can push to branch' do
+ before do
+ allow(user_access)
+ .to receive(:can_push_to_branch?)
+ .with('feature')
+ .and_return(true)
+ end
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/)
+ it 'does not raise an error' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ context 'user cannot push to branch' do
+ before do
+ allow(user_access)
+ .to receive(:can_push_to_branch?)
+ .with('feature')
+ .and_return(false)
+ end
+
+ context 'user cannot merge to branch' do
+ before do
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
+ .and_return(false)
+ end
+
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
+ end
+ end
+
+ context 'user can merge to branch' do
+ before do
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
+ .and_return(true)
+
+ allow(project.repository)
+ .to receive(:branch_names_contains_sha)
+ .with(newrev)
+ .and_return(['branch'])
+ end
+
+ context "newrev isn't in any protected branches" do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(false)
+ end
+
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
+ end
+ end
+
+ context 'newrev is included in a protected branch' do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(true)
+ end
+
+ context 'via web interface' do
+ let(:protocol) { 'web' }
+
+ it 'allows branch creation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ context 'via SSH' do
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/checks/lfs_check_spec.rb b/spec/lib/gitlab/checks/lfs_check_spec.rb
index 35f8069c8a4..dad14e100a7 100644
--- a/spec/lib/gitlab/checks/lfs_check_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_check_spec.rb
@@ -27,6 +27,18 @@ describe Gitlab::Checks::LfsCheck do
allow(project).to receive(:lfs_enabled?).and_return(true)
end
+ context 'with lfs_check feature disabled' do
+ before do
+ stub_feature_flags(lfs_check: false)
+ end
+
+ it 'skips integrity check' do
+ expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
+
+ subject.validate!
+ end
+ end
+
context 'deletion' do
let(:changes) { { oldrev: oldrev, ref: ref } }
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 773a52cdfbc..6e20e0ef5c3 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -18,11 +18,16 @@ describe Gitlab::Ci::Build::Image do
it 'populates fabricated object with the proper name attribute' do
expect(subject.name).to eq(image_name)
end
+
+ it 'does not populate the ports' do
+ expect(subject.ports).to be_empty
+ end
end
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
- let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) }
+
+ let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint, ports: [80] } } ) }
it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
@@ -32,6 +37,13 @@ describe Gitlab::Ci::Build::Image do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
end
+
+ it 'populates the ports' do
+ port = subject.ports.first
+ expect(port.number).to eq 80
+ expect(port.protocol).to eq 'http'
+ expect(port.name).to eq 'default_port'
+ end
end
context 'when image name is empty' do
@@ -67,6 +79,10 @@ describe Gitlab::Ci::Build::Image do
expect(subject.first).to be_kind_of(described_class)
expect(subject.first.name).to eq(service_image_name)
end
+
+ it 'does not populate the ports' do
+ expect(subject.first.ports).to be_empty
+ end
end
context 'when service is defined as hash' do
@@ -75,7 +91,7 @@ describe Gitlab::Ci::Build::Image do
let(:service_command) { 'sleep 30' }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
- alias: service_alias, command: service_command }] })
+ alias: service_alias, command: service_command, ports: [80] }] })
end
it 'fabricates an non-empty array of objects' do
@@ -89,6 +105,11 @@ describe Gitlab::Ci::Build::Image do
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
+
+ port = subject.first.ports.first
+ expect(port.number).to eq 80
+ expect(port.protocol).to eq 'http'
+ expect(port.name).to eq 'default_port'
end
end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index dc3329061d1..92cf0376c02 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -133,7 +133,7 @@ describe Gitlab::Ci::Build::Policy::Changes do
let(:seed) { double('build seed', to_resource: ci_build) }
context 'when source is merge request' do
- let(:source) { :merge_request }
+ let(:source) { :merge_request_event }
let(:merge_request) do
create(:merge_request,
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index 553fc0fb9bf..22ca681cfd3 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -68,6 +68,20 @@ describe Gitlab::Ci::Build::Policy::Refs do
expect(described_class.new(%w[triggers]))
.not_to be_satisfied_by(pipeline)
end
+
+ context 'when source is merge_request_event' do
+ let(:pipeline) { build_stubbed(:ci_pipeline, source: :merge_request_event) }
+
+ it 'is satisfied with only: merge_request' do
+ expect(described_class.new(%w[merge_requests]))
+ .to be_satisfied_by(pipeline)
+ end
+
+ it 'is not satisfied with only: merge_request_event' do
+ expect(described_class.new(%w[merge_request_events]))
+ .not_to be_satisfied_by(pipeline)
+ end
+ end
end
context 'when matching a ref by a regular expression' do
@@ -78,10 +92,49 @@ describe Gitlab::Ci::Build::Policy::Refs do
.to be_satisfied_by(pipeline)
end
+ it 'is satisfied when case-insensitive regexp matches pipeline ref' do
+ expect(described_class.new(['/DOCS-.*/i']))
+ .to be_satisfied_by(pipeline)
+ end
+
it 'is not satisfied when regexp does not match pipeline ref' do
expect(described_class.new(['/fix-.*/']))
.not_to be_satisfied_by(pipeline)
end
+
+ context 'when unsafe regexp is used' do
+ let(:subject) { described_class.new(['/^(?!master).+/']) }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: false)
+ end
+
+ it 'ignores invalid regexp' do
+ expect(subject)
+ .not_to be_satisfied_by(pipeline)
+ end
+ end
+
+ context 'when allow_unsafe_ruby_regexp is enabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: true)
+ end
+
+ it 'is satisfied by regexp' do
+ expect(subject)
+ .to be_satisfied_by(pipeline)
+ end
+ end
+ end
+ end
+
+ context 'malicious regexp' do
+ let(:pipeline) { build_stubbed(:ci_pipeline, ref: malicious_text) }
+
+ subject { described_class.new([malicious_regexp_ruby]) }
+
+ include_examples 'malicious regexp'
end
end
end
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index c2c0742efc3..9b016901a20 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -15,6 +15,7 @@ describe Gitlab::Ci::Build::Policy::Variables do
before do
pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '')
+ pipeline.variables.build(key: 'MY_VARIABLE', value: 'my-var')
end
describe '#satisfied_by?' do
@@ -24,6 +25,12 @@ describe Gitlab::Ci::Build::Policy::Variables do
expect(policy).to be_satisfied_by(pipeline, seed)
end
+ it 'is satisfied by a matching pipeline variable' do
+ policy = described_class.new(['$MY_VARIABLE'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
it 'is not satisfied by an overridden empty variable' do
policy = described_class.new(['$CI_PROJECT_NAME'])
@@ -68,5 +75,19 @@ describe Gitlab::Ci::Build::Policy::Variables do
expect(pipeline).not_to be_persisted
expect(seed.to_resource).not_to be_persisted
end
+
+ context 'when a bridge job is used' do
+ let(:bridge) do
+ build(:ci_bridge, pipeline: pipeline, project: project, ref: 'master')
+ end
+
+ let(:seed) { double('bridge seed', to_resource: bridge) }
+
+ it 'is satisfied by a matching expression for a bridge job' do
+ policy = described_class.new(['$MY_VARIABLE'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb
new file mode 100644
index 00000000000..1413780dfa6
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/port_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Port do
+ subject { described_class.new(port) }
+
+ context 'when port is defined as an integer' do
+ let(:port) { 80 }
+
+ it 'populates the object' do
+ expect(subject.number).to eq 80
+ expect(subject.protocol).to eq described_class::DEFAULT_PORT_PROTOCOL
+ expect(subject.name).to eq described_class::DEFAULT_PORT_NAME
+ end
+ end
+
+ context 'when port is defined as hash' do
+ let(:port) { { number: 80, protocol: 'https', name: 'port_name' } }
+
+ it 'populates the object' do
+ expect(subject.number).to eq 80
+ expect(subject.protocol).to eq 'https'
+ expect(subject.name).to eq 'port_name'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
new file mode 100644
index 00000000000..5187f99a441
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::Factory do
+ let(:build) { create(:ci_build) }
+
+ describe '.for_build' do
+ let(:kubernetes_namespace) do
+ instance_double(
+ Gitlab::Ci::Build::Prerequisite::KubernetesNamespace,
+ unmet?: unmet)
+ end
+
+ subject { described_class.new(build).unmet }
+
+ before do
+ expect(Gitlab::Ci::Build::Prerequisite::KubernetesNamespace)
+ .to receive(:new).with(build).and_return(kubernetes_namespace)
+ end
+
+ context 'prerequisite is unmet' do
+ let(:unmet) { true }
+
+ it { is_expected.to eq [kubernetes_namespace] }
+ end
+
+ context 'prerequisite is met' do
+ let(:unmet) { false }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
new file mode 100644
index 00000000000..5387863bd07
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
+ let(:build) { create(:ci_build) }
+
+ describe '#unmet?' do
+ subject { described_class.new(build).unmet? }
+
+ context 'build has no deployment' do
+ before do
+ expect(build.deployment).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'build has a deployment' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ context 'and a cluster to deploy to' do
+ let(:cluster) { create(:cluster, :group) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'and the cluster is not managed' do
+ let(:cluster) { create(:cluster, :not_managed, projects: [build.project]) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and a namespace is already created for this project' do
+ let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and cluster is project type' do
+ let(:cluster) { create(:cluster, :project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'and no cluster to deploy to' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe '#complete!' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+ let(:service) { double(execute: true) }
+
+ subject { described_class.new(build).complete! }
+
+ context 'completion is required' do
+ let(:cluster) { create(:cluster, :group) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it 'creates a kubernetes namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace))
+ .and_return(service)
+
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'completion is not required' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it 'does not create a namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 3c0007f4d57..0bc9e8bd3cd 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -100,6 +100,26 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
+ context 'when wrong action type is used' do
+ let(:config) do
+ { name: 'production',
+ action: ['stop'] }
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'contains error about wrong action type' do
+ expect(entry.errors)
+ .to include 'environment action should be a string'
+ end
+ end
+ end
+
context 'when invalid action is used' do
let(:config) do
{ name: 'production',
@@ -151,6 +171,26 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
+ context 'when wrong url type is used' do
+ let(:config) do
+ { name: 'production',
+ url: ['https://meow.meow'] }
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'contains error about wrong url type' do
+ expect(entry.errors)
+ .to include 'environment url should be a string'
+ end
+ end
+ end
+
context 'when variables are used for environment' do
let(:config) do
{ name: 'review/$CI_COMMIT_REF_NAME',
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 7651f594a4c..e23efff18d5 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Config::Entry::Global do
expect(described_class.nodes.keys)
.to match_array(%i[before_script image services
after_script variables stages
- types cache])
+ types cache include])
end
end
end
@@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end
it 'creates node object for each entry' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'creates node object using valid class' do
@@ -189,7 +189,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do
it 'instantizes all nodes' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'contains unspecified nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 1a4d9ed5517..1ebdda398b9 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -35,6 +35,12 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry.entrypoint).to be_nil
end
end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
@@ -69,6 +75,38 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run), ports: ports } }
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("image config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to eq ports
+ end
+ end
+ end
+ end
end
context 'when entry value is not correct' do
@@ -76,8 +114,8 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'image config should be a hash or a string'
+ expect(entry.errors.first)
+ .to match /config should be a hash or a string/
end
end
@@ -93,8 +131,8 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'image config contains unknown keys: non_existing'
+ expect(entry.errors.first)
+ .to match /config contains unknown keys: non_existing/
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 0560eb42e4d..e0552ae8c57 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -94,7 +94,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'returns error about wrong value type' do
expect(entry).not_to be_valid
- expect(entry.errors).to include "job extends should be a string"
+ expect(entry.errors).to include "job extends should be an array of strings or a string"
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 1c987e13a9a..fba5671594d 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,4 +1,5 @@
require 'fast_spec_helper'
+require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
describe Gitlab::Ci::Config::Entry::Policy do
@@ -33,6 +34,44 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when config is an empty regexp' do
+ let(:config) { ['//'] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when using unsafe regexp' do
+ include StubFeatureFlags
+
+ let(:config) { ['/^(?!master).+/'] }
+
+ subject { described_class.new([regexp]) }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: false)
+ end
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ context 'when allow_unsafe_ruby_regexp is enabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: true)
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
context 'when config is a special keyword' do
let(:config) { %w[tags triggers branches] }
@@ -67,6 +106,34 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when using unsafe regexp' do
+ include StubFeatureFlags
+
+ let(:config) { { refs: ['/^(?!master).+/'] } }
+
+ subject { described_class.new([regexp]) }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: false)
+ end
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ context 'when allow_unsafe_ruby_regexp is enabled' do
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: true)
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
context 'when specifying kubernetes policy' do
let(:config) { { kubernetes: 'active' } }
diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb
new file mode 100644
index 00000000000..5f8f294334e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Port do
+ let(:entry) { described_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ context 'when configuration is a string' do
+ let(:config) { 80 }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq(number: 80)
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "is nil" do
+ expect(entry.protocol).to be_nil
+ end
+ end
+
+ describe '#name' do
+ it "is nil" do
+ expect(entry.name).to be_nil
+ end
+ end
+ end
+
+ context 'when configuration is a hash' do
+ context 'with the complete hash' do
+ let(:config) do
+ { number: 80,
+ protocol: 'http',
+ name: 'foobar' }
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "returns port protocol" do
+ expect(entry.protocol).to eq 'http'
+ end
+ end
+
+ describe '#name' do
+ it "returns port name" do
+ expect(entry.name).to eq 'foobar'
+ end
+ end
+ end
+
+ context 'with only the port number' do
+ let(:config) { { number: 80 } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq(number: 80)
+ end
+ end
+
+ describe '#number' do
+ it "returns port number" do
+ expect(entry.number).to eq 80
+ end
+ end
+
+ describe '#protocol' do
+ it "is nil" do
+ expect(entry.protocol).to be_nil
+ end
+ end
+
+ describe '#name' do
+ it "is nil" do
+ expect(entry.name).to be_nil
+ end
+ end
+ end
+
+ context 'without the number' do
+ let(:config) { { protocol: 'http' } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+ end
+
+ context 'when configuration is invalid' do
+ let(:config) { '80' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when protocol' do
+ let(:config) { { number: 80, protocol: protocol, name: 'foobar' } }
+
+ context 'is http' do
+ let(:protocol) { 'http' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'is https' do
+ let(:protocol) { 'https' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'is neither http nor https' do
+ let(:protocol) { 'foo' }
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors).to include("port protocol should be http or https")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/ports_spec.rb b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
new file mode 100644
index 00000000000..2063bd1d86c
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Ports do
+ let(:entry) { described_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ context 'when configuration is valid' do
+ let(:config) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid array' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ context 'when configuration is invalid' do
+ let(:config) { 'postgresql:9.5' }
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ context 'when any of the ports' do
+ before do
+ expect(entry).not_to be_valid
+ expect(entry.errors.count).to eq 1
+ end
+
+ context 'have the same name' do
+ let(:config) do
+ [{ number: 80, protocol: 'http', name: 'foobar' },
+ { number: 81, protocol: 'http', name: 'foobar' }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors.first).to match /each port name must be different/
+ end
+ end
+ end
+
+ context 'have the same port' do
+ let(:config) do
+ [{ number: 80, protocol: 'http', name: 'foobar' },
+ { number: 80, protocol: 'http', name: 'foobar1' }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry.errors.first).to match /each port number can only be referenced once/
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 9ebf947a751..d31866a1987 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -39,6 +39,12 @@ describe Gitlab::Ci::Config::Entry::Service do
expect(entry.command).to be_nil
end
end
+
+ describe '#ports' do
+ it "returns service's ports" do
+ expect(entry.ports).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
@@ -81,6 +87,50 @@ describe Gitlab::Ci::Config::Entry::Service do
expect(entry.entrypoint).to eq %w(/bin/sh run)
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) do
+ { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ end
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ context 'when unknown port keys detected' do
+ let(:ports) { [{ number: 80, invalid_key: 'foo' }] }
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match /port config contains unknown keys: invalid_key/
+ end
+ end
+ end
+
+ describe '#ports' do
+ it "returns image's ports" do
+ expect(entry.ports).to eq ports
+ end
+ end
+ end
+ end
end
context 'when entry value is not correct' do
@@ -88,8 +138,8 @@ describe Gitlab::Ci::Config::Entry::Service do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'service config should be a hash or a string'
+ expect(entry.errors.first)
+ .to match /config should be a hash or a string/
end
end
@@ -105,8 +155,8 @@ describe Gitlab::Ci::Config::Entry::Service do
describe '#errors' do
it 'saves errors' do
- expect(entry.errors)
- .to include 'service config contains unknown keys: non_existing'
+ expect(entry.errors.first)
+ .to match /config contains unknown keys: non_existing/
end
end
@@ -116,4 +166,26 @@ describe Gitlab::Ci::Config::Entry::Service do
end
end
end
+
+ context 'when service has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) do
+ { name: 'postgresql:9.5', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
+ end
+
+ it 'alias field is mandatory' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service alias can't be blank")
+ end
+ end
+
+ context 'when service does not have ports' do
+ let(:config) do
+ { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) }
+ end
+
+ it 'alias field is optional' do
+ expect(entry).to be_valid
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index 7c4319aee63..d5a1316f665 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -32,4 +32,91 @@ describe Gitlab::Ci::Config::Entry::Services do
end
end
end
+
+ context 'when configuration has ports' do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
+ let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }] }
+ let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:image_ports) { false }
+
+ context 'when with_image_ports metadata is not enabled' do
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("service config contains disallowed keys: ports")
+ end
+ end
+ end
+
+ context 'when with_image_ports metadata is enabled' do
+ let(:image_ports) { true }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid array' do
+ expect(entry.value).to eq([{ name: 'postgresql:9.5' }, { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }])
+ end
+ end
+
+ describe 'services alias' do
+ context 'when they are not unique' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'postgres_old', ports: [81] }]
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("services config alias must be unique in services with ports")
+ end
+ end
+ end
+
+ context 'when they are unique' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'ruby', ports: [81] }]
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when one of the duplicated alias is in a service without ports' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old', ports: [80] },
+ { name: 'ruby', alias: 'postgres_old' }]
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when there are not any ports' do
+ let(:config) do
+ ['postgresql:9.5',
+ { name: 'postgresql:9.1', alias: 'postgres_old' },
+ { name: 'ruby', alias: 'postgres_old' }]
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index 0a148375d11..d63612053b6 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -44,12 +44,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
- describe '#extends_key' do
+ describe '#extends_keys' do
context 'when entry is extensible' do
it 'returns symbolized extends key value' do
entry = described_class.new(:test, test: { extends: 'something' })
- expect(entry.extends_key).to eq :something
+ expect(entry.extends_keys).to eq [:something]
end
end
@@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do
it 'returns nil' do
entry = described_class.new(:test, test: 'something')
- expect(entry.extends_key).to be_nil
+ expect(entry.extends_keys).to be_nil
end
end
end
@@ -76,7 +76,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
- describe '#base_hash!' do
+ describe '#base_hashes!' do
subject { described_class.new(:test, hash) }
context 'when base hash is not extensible' do
@@ -87,8 +87,8 @@ describe Gitlab::Ci::Config::Extendable::Entry do
}
end
- it 'returns unchanged base hash' do
- expect(subject.base_hash!).to eq(script: 'rspec')
+ it 'returns unchanged base hashes' do
+ expect(subject.base_hashes!).to eq([{ script: 'rspec' }])
end
end
@@ -101,12 +101,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do
}
end
- it 'extends the base hash first' do
- expect(subject.base_hash!).to eq(extends: 'first', script: 'rspec')
+ it 'extends the base hashes first' do
+ expect(subject.base_hashes!).to eq([{ extends: 'first', script: 'rspec' }])
end
it 'mutates original context' do
- subject.base_hash!
+ subject.base_hashes!
expect(hash.fetch(:second)).to eq(extends: 'first', script: 'rspec')
end
@@ -171,6 +171,34 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
+ context 'when extending multiple hashes correctly' do
+ let(:hash) do
+ {
+ first: { script: 'my value', image: 'ubuntu' },
+ second: { image: 'alpine' },
+ test: { extends: %w(first second) }
+ }
+ end
+
+ let(:result) do
+ {
+ first: { script: 'my value', image: 'ubuntu' },
+ second: { image: 'alpine' },
+ test: { extends: %w(first second), script: 'my value', image: 'alpine' }
+ }
+ end
+
+ it 'returns extended part of the hash' do
+ expect(subject.extend!).to eq result[:test]
+ end
+
+ it 'mutates original context' do
+ subject.extend!
+
+ expect(hash).to eq result
+ end
+ end
+
context 'when hash is not extensible' do
let(:hash) do
{
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index 1a6b3587599..dd536a241bd 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
- let(:context) { described_class::Context.new(nil, 'HEAD', nil) }
+ let(:context) { described_class::Context.new(nil, 'HEAD', nil, Set.new) }
let(:test_class) do
Class.new(described_class) do
@@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::External::File::Base do
context 'when a location is present' do
let(:location) { 'some-location' }
- it 'should return true' do
+ it 'returns true' do
expect(subject).to be_matching
end
end
@@ -34,7 +34,7 @@ describe Gitlab::Ci::Config::External::File::Base do
context 'with a location is missing' do
let(:location) { nil }
- it 'should return false' do
+ it 'returns false' do
expect(subject).not_to be_matching
end
end
@@ -79,4 +79,20 @@ describe Gitlab::Ci::Config::External::File::Base do
end
end
end
+
+ describe '#to_hash' do
+ context 'with includes' do
+ let(:location) { 'some/file/config.yml' }
+ let(:content) { 'include: { template: Bash.gitlab-ci.yml }'}
+
+ before do
+ allow_any_instance_of(test_class)
+ .to receive(:content).and_return(content)
+ end
+
+ it 'does expand hash to include the template' do
+ expect(subject.to_hash).to include(:before_script)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index ff67a765da0..9451db9522a 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -4,8 +4,10 @@ require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Local do
set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
- let(:context) { described_class::Context.new(project, '12345', nil) }
+ let(:sha) { '12345' }
+ let(:context) { described_class::Context.new(project, sha, user, Set.new) }
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
@@ -13,7 +15,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'when a local is specified' do
let(:params) { { local: 'file' } }
- it 'should return true' do
+ it 'returns true' do
expect(local_file).to be_matching
end
end
@@ -21,7 +23,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'with a missing local' do
let(:params) { { local: nil } }
- it 'should return false' do
+ it 'returns false' do
expect(local_file).not_to be_matching
end
end
@@ -29,7 +31,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'with a missing local key' do
let(:params) { {} }
- it 'should return false' do
+ it 'returns false' do
expect(local_file).not_to be_matching
end
end
@@ -43,7 +45,7 @@ describe Gitlab::Ci::Config::External::File::Local do
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'")
end
- it 'should return true' do
+ it 'returns true' do
expect(local_file.valid?).to be_truthy
end
end
@@ -51,7 +53,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'when is not a valid local path' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
- it 'should return false' do
+ it 'returns false' do
expect(local_file.valid?).to be_falsy
end
end
@@ -59,7 +61,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'when is not a yaml file' do
let(:location) { '/config/application.rb' }
- it 'should return false' do
+ it 'returns false' do
expect(local_file.valid?).to be_falsy
end
end
@@ -82,7 +84,7 @@ describe Gitlab::Ci::Config::External::File::Local do
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content)
end
- it 'should return the content of the file' do
+ it 'returns the content of the file' do
expect(local_file.content).to eq(local_file_content)
end
end
@@ -90,7 +92,7 @@ describe Gitlab::Ci::Config::External::File::Local do
context 'with an invalid file' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
- it 'should be nil' do
+ it 'is nil' do
expect(local_file.content).to be_nil
end
end
@@ -99,8 +101,40 @@ describe Gitlab::Ci::Config::External::File::Local do
describe '#error_message' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
- it 'should return an error message' do
+ it 'returns an error message' do
expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
end
end
+
+ describe '#expand_context' do
+ let(:location) { 'location.yml' }
+
+ subject { local_file.send(:expand_context) }
+
+ it 'inherits project, user and sha' do
+ is_expected.to include(user: user, project: project, sha: sha)
+ end
+ end
+
+ describe '#to_hash' do
+ context 'properly includes another local file in the same repository' do
+ let(:location) { 'some/file/config.yml' }
+ let(:content) { 'include: { local: another-config.yml }'}
+
+ let(:another_location) { 'another-config.yml' }
+ let(:another_content) { 'rspec: JOB' }
+
+ before do
+ allow(project.repository).to receive(:blob_data_at).with(sha, location)
+ .and_return(content)
+
+ allow(project.repository).to receive(:blob_data_at).with(sha, another_location)
+ .and_return(another_content)
+ end
+
+ it 'does expand hash to include the template' do
+ expect(local_file.to_hash).to include(:rspec)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 11809adcaf6..4acb4f324d3 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -3,12 +3,13 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Project do
+ set(:context_project) { create(:project) }
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:context_user) { user }
- let(:context) { described_class::Context.new(nil, '12345', context_user) }
- let(:subject) { described_class.new(params, context) }
+ let(:context) { described_class::Context.new(context_project, '12345', context_user, Set.new) }
+ let(:project_file) { described_class.new(params, context) }
before do
project.add_developer(user)
@@ -18,32 +19,32 @@ describe Gitlab::Ci::Config::External::File::Project do
context 'when a file and project is specified' do
let(:params) { { file: 'file.yml', project: 'project' } }
- it 'should return true' do
- expect(subject).to be_matching
+ it 'returns true' do
+ expect(project_file).to be_matching
end
end
context 'with only file is specified' do
let(:params) { { file: 'file.yml' } }
- it 'should return false' do
- expect(subject).not_to be_matching
+ it 'returns false' do
+ expect(project_file).not_to be_matching
end
end
context 'with only project is specified' do
let(:params) { { project: 'project' } }
- it 'should return false' do
- expect(subject).not_to be_matching
+ it 'returns false' do
+ expect(project_file).not_to be_matching
end
end
context 'with a missing local key' do
let(:params) { {} }
- it 'should return false' do
- expect(subject).not_to be_matching
+ it 'returns false' do
+ expect(project_file).not_to be_matching
end
end
end
@@ -60,16 +61,16 @@ describe Gitlab::Ci::Config::External::File::Project do
stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' }
end
- it 'should return true' do
- expect(subject).to be_valid
+ it 'returns true' do
+ expect(project_file).to be_valid
end
context 'when user does not have permission to access file' do
let(:context_user) { create(:user) }
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include("Project `#{project.full_path}` not found or access denied!")
+ it 'returns false' do
+ expect(project_file).not_to be_valid
+ expect(project_file.error_message).to include("Project `#{project.full_path}` not found or access denied!")
end
end
end
@@ -85,8 +86,8 @@ describe Gitlab::Ci::Config::External::File::Project do
stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' }
end
- it 'should return true' do
- expect(subject).to be_valid
+ it 'returns true' do
+ expect(project_file).to be_valid
end
end
@@ -101,9 +102,9 @@ describe Gitlab::Ci::Config::External::File::Project do
stub_project_blob(root_ref_sha, '/file.yml') { '' }
end
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!")
+ it 'returns false' do
+ expect(project_file).not_to be_valid
+ expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!")
end
end
@@ -112,9 +113,9 @@ describe Gitlab::Ci::Config::External::File::Project do
{ project: project.full_path, ref: 'I-Do-Not-Exist', file: '/file.yml' }
end
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!")
+ it 'returns false' do
+ expect(project_file).not_to be_valid
+ expect(project_file.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!")
end
end
@@ -123,9 +124,9 @@ describe Gitlab::Ci::Config::External::File::Project do
{ project: project.full_path, file: '/invalid-file.yml' }
end
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!")
+ it 'returns false' do
+ expect(project_file).not_to be_valid
+ expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!")
end
end
@@ -134,13 +135,23 @@ describe Gitlab::Ci::Config::External::File::Project do
{ project: project.full_path, file: '/invalid-file' }
end
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include('Included file `/invalid-file` does not have YAML extension!')
+ it 'returns false' do
+ expect(project_file).not_to be_valid
+ expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!')
end
end
end
+ describe '#expand_context' do
+ let(:params) { { file: 'file.yml', project: project.full_path, ref: 'master' } }
+
+ subject { project_file.send(:expand_context) }
+
+ it 'inherits user, and target project and sha' do
+ is_expected.to include(user: user, project: project, sha: project.commit('master').id)
+ end
+ end
+
private
def stub_project_blob(ref, path)
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index 3e0fda9c308..46d68097fff 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
- let(:context) { described_class::Context.new(nil, '12345', nil) }
+ include StubRequests
+
+ let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
@@ -21,7 +23,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when a remote is specified' do
let(:params) { { remote: 'http://remote' } }
- it 'should return true' do
+ it 'returns true' do
expect(remote_file).to be_matching
end
end
@@ -29,7 +31,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'with a missing remote' do
let(:params) { { remote: nil } }
- it 'should return false' do
+ it 'returns false' do
expect(remote_file).not_to be_matching
end
end
@@ -37,7 +39,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'with a missing remote key' do
let(:params) { {} }
- it 'should return false' do
+ it 'returns false' do
expect(remote_file).not_to be_matching
end
end
@@ -46,10 +48,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#valid?" do
context 'when is a valid remote url' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ stub_full_request(location).to_return(body: remote_file_content)
end
- it 'should return true' do
+ it 'returns true' do
expect(remote_file.valid?).to be_truthy
end
end
@@ -57,7 +59,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'with an irregular url' do
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
- it 'should return false' do
+ it 'returns false' do
expect(remote_file.valid?).to be_falsy
end
end
@@ -67,7 +69,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
end
- it 'should be falsy' do
+ it 'is falsy' do
expect(remote_file.valid?).to be_falsy
end
end
@@ -75,7 +77,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when is not a yaml file' do
let(:location) { 'https://asdasdasdaj48ggerexample.com' }
- it 'should be falsy' do
+ it 'is falsy' do
expect(remote_file.valid?).to be_falsy
end
end
@@ -83,7 +85,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'with an internal url' do
let(:location) { 'http://localhost:8080' }
- it 'should be falsy' do
+ it 'is falsy' do
expect(remote_file.valid?).to be_falsy
end
end
@@ -92,10 +94,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#content" do
context 'with a valid remote file' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ stub_full_request(location).to_return(body: remote_file_content)
end
- it 'should return the content of the file' do
+ it 'returns the content of the file' do
expect(remote_file.content).to eql(remote_file_content)
end
end
@@ -105,7 +107,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
end
- it 'should be falsy' do
+ it 'is falsy' do
expect(remote_file.content).to be_falsy
end
end
@@ -114,10 +116,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
let(:location) { 'https://asdasdasdaj48ggerexample.com' }
before do
- WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error'))
+ stub_full_request(location).to_raise(SocketError.new('Some HTTP error'))
end
- it 'should be nil' do
+ it 'is nil' do
expect(remote_file.content).to be_nil
end
end
@@ -125,7 +127,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'with an internal url' do
let(:location) { 'http://localhost:8080' }
- it 'should be nil' do
+ it 'is nil' do
expect(remote_file.content).to be_nil
end
end
@@ -144,30 +146,30 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when timeout error has been raised' do
before do
- WebMock.stub_request(:get, location).to_timeout
+ stub_full_request(location).to_timeout
end
- it 'should returns error message about a timeout' do
+ it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of a timeout error!/
end
end
context 'when HTTP error has been raised' do
before do
- WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
+ stub_full_request(location).to_raise(Gitlab::HTTP::Error)
end
- it 'should returns error message about a HTTP error' do
+ it 'returns error message about a HTTP error' do
expect(subject).to match /could not be fetched because of HTTP error!/
end
end
context 'when response has 404 status' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
+ stub_full_request(location).to_return(body: remote_file_content, status: 404)
end
- it 'should returns error message about a timeout' do
+ it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of HTTP code `404` error!/
end
end
@@ -175,10 +177,20 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when the URL is blocked' do
let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
- it 'should include details about blocked URL' do
+ it 'includes details about blocked URL' do
expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \
'is blocked: Requests to localhost are not allowed!'
end
end
end
+
+ describe '#expand_context' do
+ let(:params) { { remote: 'http://remote' } }
+
+ subject { remote_file.send(:expand_context) }
+
+ it 'drops all parameters' do
+ is_expected.to include(user: nil, project: nil, sha: nil)
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 1fb5655309a..1609b8fd66b 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -3,34 +3,37 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Template do
- let(:context) { described_class::Context.new(nil, '12345') }
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+
+ let(:context) { described_class::Context.new(project, '12345', user, Set.new) }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
let(:params) { { template: template } }
- subject { described_class.new(params, context) }
+ let(:template_file) { described_class.new(params, context) }
describe '#matching?' do
context 'when a template is specified' do
let(:params) { { template: 'some-template' } }
- it 'should return true' do
- expect(subject).to be_matching
+ it 'returns true' do
+ expect(template_file).to be_matching
end
end
context 'with a missing template' do
let(:params) { { template: nil } }
- it 'should return false' do
- expect(subject).not_to be_matching
+ it 'returns false' do
+ expect(template_file).not_to be_matching
end
end
context 'with a missing template key' do
let(:params) { {} }
- it 'should return false' do
- expect(subject).not_to be_matching
+ it 'returns false' do
+ expect(template_file).not_to be_matching
end
end
end
@@ -39,32 +42,32 @@ describe Gitlab::Ci::Config::External::File::Template do
context 'when is a valid template name' do
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
- it 'should return true' do
- expect(subject).to be_valid
+ it 'returns true' do
+ expect(template_file).to be_valid
end
end
context 'with invalid template name' do
let(:template) { 'Template.yml' }
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include('Template file `Template.yml` is not a valid location!')
+ it 'returns false' do
+ expect(template_file).not_to be_valid
+ expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!')
end
end
context 'with a non-existing template' do
let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' }
- it 'should return false' do
- expect(subject).not_to be_valid
- expect(subject.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!')
+ it 'returns false' do
+ expect(template_file).not_to be_valid
+ expect(template_file.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!')
end
end
end
describe '#template_name' do
- let(:template_name) { subject.send(:template_name) }
+ let(:template_name) { template_file.send(:template_name) }
context 'when template does end with .gitlab-ci.yml' do
let(:template) { 'my-template.gitlab-ci.yml' }
@@ -90,4 +93,14 @@ describe Gitlab::Ci::Config::External::File::Template do
end
end
end
+
+ describe '#expand_context' do
+ let(:location) { 'location.yml' }
+
+ subject { template_file.send(:expand_context) }
+
+ it 'drops all parameters' do
+ is_expected.to include(user: nil, project: nil, sha: nil)
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 4cab4961b0f..e068b786b02 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -3,12 +3,15 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
+ include StubRequests
+
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
+ let(:expandset) { Set.new }
let(:file_content) do
<<~HEREDOC
@@ -17,11 +20,11 @@ describe Gitlab::Ci::Config::External::Mapper do
end
before do
- WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ stub_full_request(remote_url).to_return(body: file_content)
end
describe '#process' do
- subject { described_class.new(values, project: project, sha: '123456', user: user).process }
+ subject { described_class.new(values, project: project, sha: '123456', user: user, expandset: expandset).process }
context "when single 'include' keyword is defined" do
context 'when the string is a local file' do
@@ -141,5 +144,37 @@ describe Gitlab::Ci::Config::External::Mapper do
expect(subject).to be_empty
end
end
+
+ context "when duplicate 'include' is defined" do
+ let(:values) do
+ { include: [
+ { 'local' => local_file },
+ { 'local' => local_file }
+ ],
+ image: 'ruby:2.2' }
+ end
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error(described_class::DuplicateIncludesError)
+ end
+ end
+
+ context "when too many 'includes' are defined" do
+ let(:values) do
+ { include: [
+ { 'local' => local_file },
+ { 'remote' => remote_url }
+ ],
+ image: 'ruby:2.2' }
+ end
+
+ before do
+ stub_const("#{described_class}::MAX_INCLUDES", 1)
+ end
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error(described_class::TooManyIncludesError)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 1ac58139b25..856187371e1 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -3,20 +3,27 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
+ include StubRequests
+
set(:project) { create(:project, :repository) }
+ set(:another_project) { create(:project, :repository) }
set(:user) { create(:user) }
- let(:processor) { described_class.new(values, project: project, sha: '12345', user: user) }
+ let(:expandset) { Set.new }
+ let(:sha) { '12345' }
+ let(:processor) { described_class.new(values, project: project, sha: '12345', user: user, expandset: expandset) }
before do
project.add_developer(user)
end
describe "#perform" do
+ subject { processor.perform }
+
context 'when no external files defined' do
let(:values) { { image: 'ruby:2.2' } }
- it 'should return the same values' do
+ it 'returns the same values' do
expect(processor.perform).to eq(values)
end
end
@@ -24,7 +31,7 @@ describe Gitlab::Ci::Config::External::Processor do
context 'when an invalid local file is defined' do
let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.2' } }
- it 'should raise an error' do
+ it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
"Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
@@ -37,10 +44,10 @@ describe Gitlab::Ci::Config::External::Processor do
let(:values) { { include: remote_file, image: 'ruby:2.2' } }
before do
- WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error'))
+ stub_full_request(remote_file).and_raise(SocketError.new('Some HTTP error'))
end
- it 'should raise an error' do
+ it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
"Remote file `#{remote_file}` could not be fetched because of a socket error!"
@@ -70,15 +77,15 @@ describe Gitlab::Ci::Config::External::Processor do
end
before do
- WebMock.stub_request(:get, remote_file).to_return(body: external_file_content)
+ stub_full_request(remote_file).to_return(body: external_file_content)
end
- it 'should append the file to the values' do
+ it 'appends the file to the values' do
output = processor.perform
expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop])
end
- it "should remove the 'include' keyword" do
+ it "removes the 'include' keyword" do
expect(processor.perform[:include]).to be_nil
end
end
@@ -100,12 +107,12 @@ describe Gitlab::Ci::Config::External::Processor do
.to receive(:fetch_local_content).and_return(local_file_content)
end
- it 'should append the file to the values' do
+ it 'appends the file to the values' do
output = processor.perform
expect(output.keys).to match_array([:image, :before_script])
end
- it "should remove the 'include' keyword" do
+ it "removes the 'include' keyword" do
expect(processor.perform[:include]).to be_nil
end
end
@@ -140,14 +147,14 @@ describe Gitlab::Ci::Config::External::Processor do
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
- WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ stub_full_request(remote_file).to_return(body: remote_file_content)
end
- it 'should append the files to the values' do
+ it 'appends the files to the values' do
expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec])
end
- it "should remove the 'include' keyword" do
+ it "removes the 'include' keyword" do
expect(processor.perform[:include]).to be_nil
end
end
@@ -162,7 +169,7 @@ describe Gitlab::Ci::Config::External::Processor do
.to receive(:fetch_local_content).and_return(local_file_content)
end
- it 'should raise an error' do
+ it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
"Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
@@ -185,10 +192,111 @@ describe Gitlab::Ci::Config::External::Processor do
HEREDOC
end
- it 'should take precedence' do
- WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ it 'takes precedence' do
+ stub_full_request(remote_file).to_return(body: remote_file_content)
+
expect(processor.perform[:image]).to eq('ruby:2.2')
end
end
+
+ context "when a nested includes are defined" do
+ let(:values) do
+ {
+ include: [
+ { local: '/local/file.yml' }
+ ],
+ image: 'ruby:2.2'
+ }
+ end
+
+ before do
+ allow(project.repository).to receive(:blob_data_at).with('12345', '/local/file.yml') do
+ <<~HEREDOC
+ include:
+ - template: Ruby.gitlab-ci.yml
+ - remote: http://my.domain.com/config.yml
+ - project: #{another_project.full_path}
+ file: /templates/my-workflow.yml
+ HEREDOC
+ end
+
+ allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-workflow.yml') do
+ <<~HEREDOC
+ include:
+ - local: /templates/my-build.yml
+ HEREDOC
+ end
+
+ allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-build.yml') do
+ <<~HEREDOC
+ my_build:
+ script: echo Hello World
+ HEREDOC
+ end
+
+ stub_full_request('http://my.domain.com/config.yml')
+ .to_return(body: 'remote_build: { script: echo Hello World }')
+ end
+
+ context 'when project is public' do
+ before do
+ another_project.update!(visibility: 'public')
+ end
+
+ it 'properly expands all includes' do
+ is_expected.to include(:my_build, :remote_build, :rspec)
+ end
+ end
+
+ context 'when user is reporter of another project' do
+ before do
+ another_project.add_reporter(user)
+ end
+
+ it 'properly expands all includes' do
+ is_expected.to include(:my_build, :remote_build, :rspec)
+ end
+ end
+
+ context 'when user is not allowed' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /not found or access denied/)
+ end
+ end
+
+ context 'when too many includes is included' do
+ before do
+ stub_const('Gitlab::Ci::Config::External::Mapper::MAX_INCLUDES', 1)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /Maximum of 1 nested/)
+ end
+ end
+ end
+
+ context 'when config includes an external configuration file via SSL web request' do
+ before do
+ stub_full_request('https://sha256.badssl.com/fake.yml', ip_address: '8.8.8.8')
+ .to_return(body: 'image: ruby:2.6', status: 200)
+
+ stub_full_request('https://self-signed.badssl.com/fake.yml', ip_address: '8.8.8.9')
+ .to_raise(OpenSSL::SSL::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate)'))
+ end
+
+ context 'with an acceptable certificate' do
+ let(:values) { { include: 'https://sha256.badssl.com/fake.yml' } }
+
+ it { is_expected.to include(image: 'ruby:2.6') }
+ end
+
+ context 'with a self-signed certificate' do
+ let(:values) { { include: 'https://self-signed.badssl.com/fake.yml' } }
+
+ it 'returns a reportable configuration error' do
+ expect { subject }.to raise_error(described_class::IncludeError, /certificate verify failed/)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 18f255c1ab7..7f336ee853e 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config do
+ include StubRequests
+
set(:user) { create(:user) }
let(:config) do
@@ -123,6 +125,63 @@ describe Gitlab::Ci::Config do
)
end
end
+
+ context 'when ports have been set' do
+ context 'in the main image' do
+ let(:yml) do
+ <<-EOS
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("image config contains disallowed keys: ports")
+ end
+ end
+
+ context 'in the job image' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("jobs:test:image config contains disallowed keys: ports")
+ end
+ end
+
+ context 'in the services' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image: ruby:2.2
+ services:
+ - name: test
+ alias: test
+ ports:
+ - 80
+ EOS
+ end
+
+ it 'raises an error' do
+ expect(config.errors).to include("jobs:test:services:service config contains disallowed keys: ports")
+ end
+ end
+ end
end
context "when using 'include' directive" do
@@ -133,7 +192,6 @@ describe Gitlab::Ci::Config do
let(:remote_file_content) do
<<~HEREDOC
variables:
- AUTO_DEVOPS_DOMAIN: domain.example.com
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
@@ -160,22 +218,20 @@ describe Gitlab::Ci::Config do
end
before do
- WebMock.stub_request(:get, remote_location)
- .to_return(body: remote_file_content)
+ stub_full_request(remote_location).to_return(body: remote_file_content)
allow(project.repository)
.to receive(:blob_data_at).and_return(local_file_content)
end
context "when gitlab_ci_yml has valid 'include' defined" do
- it 'should return a composed hash' do
+ it 'returns a composed hash' do
before_script_values = [
"apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v",
"which ruby",
"bundle install --jobs $(nproc) \"${FLAGS[@]}\""
]
variables = {
- AUTO_DEVOPS_DOMAIN: "domain.example.com",
POSTGRES_USER: "user",
POSTGRES_PASSWORD: "testing-password",
POSTGRES_ENABLED: "true",
@@ -259,7 +315,7 @@ describe Gitlab::Ci::Config do
HEREDOC
end
- it 'should take precedence' do
+ it 'takes precedence' do
expect(config.to_hash).to eq({ image: 'ruby:2.2' })
end
end
@@ -284,7 +340,7 @@ describe Gitlab::Ci::Config do
HEREDOC
end
- it 'should merge the variables dictionaries' do
+ it 'merges the variables dictionaries' do
expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } })
end
end
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 2a3f7807fdb..491e3fba9d9 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -174,6 +174,13 @@ describe Gitlab::Ci::CronParser do
it { expect(subject).to be_nil }
end
+
+ context 'when cron is scheduled to a non existent day' do
+ let(:cron) { '0 12 31 2 *' }
+ let(:cron_timezone) { 'UTC' }
+
+ it { expect(subject).to be_nil }
+ end
end
describe '#cron_valid?' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index fab071405df..3debd42ac65 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -96,11 +96,13 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
context 'when pipeline is running for a merge request' do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- source: :merge_request,
+ source: :merge_request_event,
origin_ref: 'feature',
checkout_sha: project.commit.id,
after_sha: nil,
before_sha: nil,
+ source_sha: merge_request.diff_head_sha,
+ target_sha: merge_request.target_branch_sha,
trigger_request: nil,
schedule: nil,
merge_request: merge_request,
@@ -115,8 +117,13 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
end
it 'correctly indicated that this is a merge request pipeline' do
- expect(pipeline).to be_merge_request
+ expect(pipeline).to be_merge_request_event
expect(pipeline.merge_request).to eq(merge_request)
end
+
+ it 'correctly sets souce sha and target sha to pipeline' do
+ expect(pipeline.source_sha).to eq(merge_request.diff_head_sha)
+ expect(pipeline.target_sha).to eq(merge_request.target_branch_sha)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 6aa802ce6fd..5181e9c1583 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -48,6 +48,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#merge_request_ref_exists?' do
+ subject { command.merge_request_ref_exists? }
+
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ context 'for existing merge request ref' do
+ let(:origin_ref) { merge_request.ref_path }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'for branch ref' do
+ let(:origin_ref) { merge_request.source_branch }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
describe '#ref' do
subject { command.ref }
@@ -161,6 +179,54 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#source_sha' do
+ subject { command.source_sha }
+
+ let(:command) do
+ described_class.new(project: project,
+ source_sha: source_sha,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project)
+ end
+
+ let(:source_sha) { nil }
+
+ context 'when source_sha is specified' do
+ let(:source_sha) { 'abc' }
+
+ it 'returns the specified value' do
+ is_expected.to eq('abc')
+ end
+ end
+ end
+
+ describe '#target_sha' do
+ subject { command.target_sha }
+
+ let(:command) do
+ described_class.new(project: project,
+ target_sha: target_sha,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project)
+ end
+
+ let(:target_sha) { nil }
+
+ context 'when target_sha is specified' do
+ let(:target_sha) { 'abc' }
+
+ it 'returns the specified value' do
+ is_expected.to eq('abc')
+ end
+ end
+ end
+
describe '#protected_ref?' do
let(:command) { described_class.new(project: project, origin_ref: 'my-branch') }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index dc13cae961c..c7f4fc98ca3 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do
step.perform!
end
- it 'should break the chain' do
+ it 'breaks the chain' do
expect(step.break?).to be true
end
@@ -37,11 +37,11 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do
step.perform!
end
- it 'should not break the chain' do
+ it 'does not break the chain' do
expect(step.break?).to be false
end
- it 'should not skip a pipeline chain' do
+ it 'does not skip a pipeline chain' do
expect(pipeline.reload).not_to be_skipped
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 8ba56d73838..7d750877d09 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -10,12 +10,33 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project, current_user: user, origin_ref: ref)
+ project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request)
end
let(:step) { described_class.new(pipeline, command) }
let(:ref) { 'master' }
+ let(:origin_ref) { ref }
+ let(:merge_request) { nil }
+
+ shared_context 'detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref,
+ target_project: project,
+ target_branch: 'feature')
+ end
+
+ let(:pipeline) do
+ build(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: project)
+ end
+
+ let(:origin_ref) { merge_request.ref_path }
+ end
context 'when users has no ability to run a pipeline' do
before do
@@ -58,6 +79,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
it { is_expected.to be_truthy }
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
+
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
@@ -65,6 +92,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
it { is_expected.to be_falsey }
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_falsey }
+ end
+
context 'when developers are allowed to merge' do
let!(:protected_branch) do
create(:protected_branch,
@@ -74,6 +107,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
it { is_expected.to be_truthy }
+
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
end
end
@@ -112,6 +151,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
it { is_expected.to be_truthy }
+
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
end
context 'when the tag is protected' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 053bc421649..e6c6a82b463 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -115,7 +115,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
let(:merge_request_pipeline) do
- build(:ci_pipeline, source: :merge_request, project: project)
+ build(:ci_pipeline, source: :merge_request_event, project: project)
end
let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index a7cad423d09..2e8c9d70098 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -42,6 +42,25 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
end
end
+ context 'when origin ref is a merge request ref' do
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project, current_user: user, origin_ref: origin_ref, checkout_sha: checkout_sha)
+ end
+
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:origin_ref) { merge_request.ref_path }
+ let(:checkout_sha) { project.repository.commit(merge_request.ref_path).id }
+
+ it 'does not break the chain' do
+ expect(step.break?).to be false
+ end
+
+ it 'does not append pipeline errors' do
+ expect(pipeline.errors).to be_empty
+ end
+ end
+
context 'when ref is ambiguous' do
let(:project) do
create(:project, :repository).tap do |proj|
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
new file mode 100644
index 00000000000..006ce4d8078
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('&&', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('&&', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+
+ context 'when left or right is falsey' do
+ where(:left_value, :right_value) do
+ [true, false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index 019a2ed184d..fcbd2863289 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('==', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('==', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('==', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -17,23 +29,40 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right are not equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(2)
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate(VARIABLE: 3)).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right are equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(1)
+ context 'when left and right are equal' do
+ where(:left_value, :right_value) do
+ [%w(string string)]
+ end
+
+ with_them do
+ it { is_expected.to eq(true) }
+ end
+ end
- operator = described_class.new(left, right)
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string'].permutation(2).to_a
+ end
- expect(operator.evaluate(VARIABLE: 3)).to eq true
+ with_them do
+ it { is_expected.to eq(false) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 49e5af52f4d..97da66d2bcc 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('=~', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('=~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('/my-string/')
+
+ expect(described_class.build('=~', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right do not match' do
- allow(left).to receive(:evaluate).and_return('my-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('something'))
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right match' do
- allow(left).to receive(:evaluate).and_return('my-awesome-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
- operator = described_class.new(left, right)
-
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports matching against a nil value' do
- allow(left).to receive(:evaluate).and_return(nil)
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('pattern'))
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(3) }
+ end
- operator = described_class.new(left, right)
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(nil) }
end
- it 'supports multiline strings' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My awesome contents
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(24) }
+ end
- My-text-string!
- TEXT
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('text-string'))
+ My-terrible-string!
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports regexp flags' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My AWESOME content
- TEXT
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(3) }
+ end
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
new file mode 100644
index 00000000000..38d30c9035a
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ describe '.build' do
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!=', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!=', left, right))
+ .to be_a(described_class)
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are equal' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:left_value, :right_value) do
+ 'string' | 'string'
+ 1 | 1
+ '' | ''
+ nil | nil
+ end
+
+ with_them do
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string', 1, 2, '', nil, false, true].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
new file mode 100644
index 00000000000..99110ff8d88
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -0,0 +1,122 @@
+require 'fast_spec_helper'
+require_dependency 're2'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ describe '.build' do
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!~', left, right))
+ .to be_a(described_class)
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-terrible-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
new file mode 100644
index 00000000000..d542eebc613
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('||', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('||', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+
+ context 'when left or right is truthy' do
+ where(:left_value, :right_value) do
+ [true, false, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 3ebc2e94727..30ea3f3e28e 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,4 +1,4 @@
-require 'fast_spec_helper'
+require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
describe '.build' do
@@ -30,16 +30,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('pattern')
end
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
it 'does not allow to use an empty pattern' do
scanner = StringScanner.new(%(//))
@@ -68,12 +58,90 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('(?im)pattern')
end
- it 'does not support arbitrary flags' do
+ it 'ignores unsupported flags' do
scanner = StringScanner.new('/pattern/x')
token = described_class.scan(scanner)
- expect(token).to be_nil
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('pattern')
+ end
+
+ it 'is a eager scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* ')
+ end
+
+ it 'does not match on escaped regexp boundaries' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'recognizes \ as an escape character for /' do
+ scanner = StringScanner.new('/some numeric \/$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric /$ pattern')
+ end
+
+ it 'does not recognize \ as an escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'is a greedy scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'does not recognize the \ escape character for /' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
+ end
+
+ it 'does not recognize the \ escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
end
end
@@ -85,7 +153,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
end
it 'raises error if evaluated regexp is not valid' do
- allow(Gitlab::UntrustedRegexp).to receive(:valid?).and_return(true)
+ allow(Gitlab::UntrustedRegexp::RubySyntax).to receive(:valid?).and_return(true)
regexp = described_class.new('/invalid ( .*/')
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 3f11b3f7673..d8db9c262a1 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -58,6 +58,56 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
expect { lexer.tokens }
.to raise_error described_class::SyntaxError
end
+
+ context 'with complex expressions' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(expression).tokens.map(&:value) }
+
+ where(:expression, :tokens) do
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '&&', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '&&', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE || $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '||', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | ['$PRESENT_VARIABLE', '&&', 'null', '||', '$EMPTY_VARIABLE', '==', '""']
+ end
+
+ with_them do
+ it { is_expected.to eq(tokens) }
+ end
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag turned off' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'incorrectly tokenizes conjunctive match statements as one match statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'incorrectly tokenizes disjunctive match statements as one statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'raises an error about && operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+
+ it 'raises an error about || operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+ end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index 2b78b1dd4a7..e88ec5561b6 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -2,25 +2,67 @@ require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
- context 'when using operators' do
+ context 'when using two operators' do
+ it 'returns a reverse descent parse tree' do
+ expect(described_class.seed('$VAR1 == "123"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
+ end
+ end
+
+ context 'when using three operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
end
- context 'when using a single token' do
+ context 'when using a single variable token' do
it 'returns a single token instance' do
expect(described_class.seed('$VAR').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
+ context 'when using a single string token' do
+ it 'returns a single token instance' do
+ expect(described_class.seed('"some value"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String
+ end
+ end
+
context 'when expression is empty' do
it 'returns a null token' do
- expect(described_class.seed('').tree)
+ expect { described_class.seed('').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when expression is null' do
+ it 'returns a null token' do
+ expect(described_class.seed('null').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
end
end
+
+ context 'when two value tokens have no operator' do
+ it 'raises a parsing error' do
+ expect { described_class.seed('$VAR "text"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when an operator has no left side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('== "123"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'when an operator has no right side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('$VAR ==').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 11e73294f18..057e2f3fbe8 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,4 +1,6 @@
-require 'fast_spec_helper'
+# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
+# require 'fast_spec_helper'
+require 'spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -7,8 +9,12 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
let(:variables) do
- { 'PRESENT_VARIABLE' => 'my variable',
- EMPTY_VARIABLE: '' }
+ {
+ 'PRESENT_VARIABLE' => 'my variable',
+ 'PATH_VARIABLE' => 'a/path/variable/value',
+ 'FULL_PATH_VARIABLE' => '/a/full/path/variable/value',
+ 'EMPTY_VARIABLE' => ''
+ }
end
describe '.new' do
@@ -21,93 +27,158 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
end
- describe '#parse_tree' do
- context 'when expression is empty' do
- let(:text) { '' }
-
- it 'raises an error' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
- end
+ describe '#evaluate' do
+ using RSpec::Parameterized::TableSyntax
- context 'when expression grammar is incorrect' do
- table = [
- '$VAR "text"', # missing operator
- '== "123"', # invalid left side
- '"some string"', # only string provided
- '$VAR ==', # invalid right side
- 'null', # missing lexemes
- '' # empty statement
- ]
-
- table.each do |syntax|
- context "when expression grammar is #{syntax.inspect}" do
- let(:text) { syntax }
-
- it 'raises a statement error exception' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
-
- it 'is an invalid statement' do
- expect(subject).not_to be_valid
- end
- end
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil
+ '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ "$PRESENT_VARIABLE =~ /^var.*/" | nil
+ "$EMPTY_VARIABLE =~ /var.*/" | nil
+ "$UNDEFINED_VARIABLE =~ /var.*/" | nil
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | 3
+ '$PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0
+ '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ '$PRESENT_VARIABLE !~ /^v\ar.*/' | true
+ '$PRESENT_VARIABLE !~ /^v\/ar.*/' | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+
+ '$PRESENT_VARIABLE && "string"' | 'string'
+ '$PRESENT_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && null' | nil
+ '"string" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ 'null && $PRESENT_VARIABLE' | nil
+ '$EMPTY_VARIABLE && "string"' | 'string'
+ '$EMPTY_VARIABLE && $EMPTY_VARIABLE' | ''
+ '"string" && $EMPTY_VARIABLE' | ''
+ '"string" && null' | nil
+ 'null && "string"' | nil
+ '"string" && "string"' | 'string'
+ 'null && null' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && $UNDEFINED_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true
+
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable'
end
- context 'when expression grammar is correct' do
- context 'when using an operator' do
- let(:text) { '$VAR == "value"' }
-
- it 'returns a reverse descent parse tree' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
- end
+ with_them do
+ let(:text) { expression }
- it 'is a valid statement' do
- expect(subject).to be_valid
- end
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq(value)
end
- context 'when using a single token' do
- let(:text) { '$PRESENT_VARIABLE' }
+ # This test is used to ensure that our parser
+ # returns exactly the same results as if we
+ # were evaluating using ruby's `eval`
+ context 'when using Ruby eval' do
+ let(:expression_ruby) do
+ expression
+ .gsub(/null/, 'nil')
+ .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" }
+ end
- it 'returns a single token instance' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
+ it 'behaves exactly the same' do
+ expect(instance_eval(expression_ruby)).to eq(subject.evaluate)
end
end
end
- end
- describe '#evaluate' do
- using RSpec::Parameterized::TableSyntax
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | true
+ "$PRESENT_VARIABLE =~ /^var.*/" | false
+ "$EMPTY_VARIABLE =~ /var.*/" | false
+ "$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | true
+ '$PATH_VARIABLE =~ /path/variable/' | true
+ '$PATH_VARIABLE =~ /path\/variable/' | true
+ '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+ end
- with_them do
- let(:text) { expression }
+ with_them do
+ let(:text) { expression }
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq value
+ end
end
end
end
@@ -125,6 +196,8 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
'$INVALID = 1' | false
"$PRESENT_VARIABLE =~ /var.*/" | true
"$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE !~ /var.*/" | false
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
end
with_them do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index b379b08ad62..b6231510b91 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.action_path).to include 'retry'
end
end
+
+ context 'when build has unmet prerequisites' do
+ let(:build) { create(:ci_build, :prerequisite_failure) }
+
+ it 'matches correct core status' do
+ expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
+ end
+
+ it 'matches correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Build::Retryable,
+ Gitlab::Ci::Status::Build::FailedUnmetPrerequisites]
+ end
+
+ it 'fabricates a failed with unmet prerequisites build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::FailedUnmetPrerequisites
+ end
+
+ it 'fabricates status with correct details' do
+ expect(status.text).to eq 'failed'
+ expect(status.icon).to eq 'status_failed'
+ expect(status.favicon).to eq 'favicon_status_failed'
+ expect(status.label).to eq 'failed'
+ expect(status).to have_details
+ expect(status).to have_action
+ expect(status.action_title).to include 'Retry'
+ expect(status.action_path).to include 'retry'
+ end
+ end
end
context 'when build is a canceled' do
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index af03d5a1308..2a5915d75d0 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -31,7 +31,7 @@ describe Gitlab::Ci::Status::Build::FailedAllowed do
describe '#group' do
it 'returns status failed with warnings status group' do
- expect(subject.group).to eq 'failed_with_warnings'
+ expect(subject.group).to eq 'failed-with-warnings'
end
end
diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
new file mode 100644
index 00000000000..a4854bdc6b9
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do
+ describe '#illustration' do
+ subject { described_class.new(double).illustration }
+
+ it { is_expected.to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { described_class.matches?(build, double) }
+
+ context 'when build has not failed' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when build has failed' do
+ before do
+ build.drop!(failure_reason)
+ end
+
+ context 'with unmet prerequisites' do
+ let(:failure_reason) { :unmet_prerequisites }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with a different error' do
+ let(:failure_reason) { :runner_system_failure }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
new file mode 100644
index 00000000000..4d8945845ba
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Preparing do
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, nil) }
+
+ context 'when build is preparing' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not preparing' do
+ let(:build) { create(:ci_build, :success) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
new file mode 100644
index 00000000000..7211c0e506d
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Preparing do
+ subject do
+ described_class.new(double('subject'), nil)
+ end
+
+ describe '#text' do
+ it { expect(subject.text).to eq 'preparing' }
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'preparing' }
+ end
+
+ describe '#icon' do
+ it { expect(subject.icon).to eq 'status_created' }
+ end
+
+ describe '#favicon' do
+ it { expect(subject.favicon).to eq 'favicon_status_created' }
+ end
+
+ describe '#group' do
+ it { expect(subject.group).to eq 'preparing' }
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index dee4f4efd1b..4b299170c87 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
end
context 'when stage has a core status' do
- HasStatus::AVAILABLE_STATUSES.each do |core_status|
+ (HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
context "when core status is #{core_status}" do
before do
create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
@@ -64,4 +64,20 @@ describe Gitlab::Ci::Status::Stage::Factory do
expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}"
end
end
+
+ context 'when stage has manual builds' do
+ (HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
+ context "when status is #{core_status}" do
+ before do
+ create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
+ create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
+ create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual)
+ end
+
+ it 'fabricates a play manual status' do
+ expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
new file mode 100644
index 00000000000..b0113b00ef0
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Stage::PlayManual do
+ let(:stage) { double('stage') }
+ let(:play_manual) { described_class.new(stage) }
+
+ describe '#action_icon' do
+ subject { play_manual.action_icon }
+
+ it { is_expected.to eq('play') }
+ end
+
+ describe '#action_button_title' do
+ subject { play_manual.action_button_title }
+
+ it { is_expected.to eq('Play all manual') }
+ end
+
+ describe '#action_title' do
+ subject { play_manual.action_title }
+
+ it { is_expected.to eq('Play all manual') }
+ end
+
+ describe '#action_path' do
+ let(:stage) { create(:ci_stage_entity, status: 'manual') }
+ let(:pipeline) { stage.pipeline }
+ let(:play_manual) { stage.detailed_status(create(:user)) }
+
+ subject { play_manual.action_path }
+
+ it { is_expected.to eq("/#{pipeline.project.full_path}/pipelines/#{pipeline.id}/stages/#{stage.name}/play_manual") }
+ end
+
+ describe '#action_method' do
+ subject { play_manual.action_method }
+
+ it { is_expected.to eq(:post) }
+ end
+
+ describe '.matches?' do
+ let(:user) { double('user') }
+
+ subject { described_class.matches?(stage, user) }
+
+ context 'when stage is skipped' do
+ let(:stage) { create(:ci_stage_entity, status: :skipped) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when stage is manual' do
+ let(:stage) { create(:ci_stage_entity, status: :manual) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when stage is scheduled' do
+ let(:stage) { create(:ci_stage_entity, status: :scheduled) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when stage is success' do
+ let(:stage) { create(:ci_stage_entity, status: :success) }
+
+ context 'and does not have manual builds' do
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 6d05545d1d8..9493b1d89f2 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Ci::Status::SuccessWarning do
end
describe '#group' do
- it { expect(subject.group).to eq 'success_with_warnings' }
+ it { expect(subject.group).to eq 'success-with-warnings' }
end
describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index 0dd74399a47..b52064b3036 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -3,9 +3,32 @@
require 'spec_helper'
describe "CI YML Templates" do
- Gitlab::Template::GitlabCiYmlTemplate.all.each do |template|
- it "#{template.name} should be valid" do
- expect { Gitlab::Ci::YamlProcessor.new(template.content) }.not_to raise_error
+ using RSpec::Parameterized::TableSyntax
+
+ subject { Gitlab::Ci::YamlProcessor.new(content) }
+
+ where(:template_name) do
+ Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name)
+ end
+
+ with_them do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
+
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
+
+ it 'is valid' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'require default stages to be included' do
+ expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default)
end
end
end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 38626f728d7..e45ea1c2528 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -414,7 +414,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
context 'malicious regexp' do
let(:data) { malicious_text }
- let(:regex) { malicious_regexp }
+ let(:regex) { malicious_regexp_re2 }
include_examples 'malicious regexp'
end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 8bf44acb228..613814df23f 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
let(:expected_value) { variable_value }
let(:variable) do
- { key: variable_key, value: variable_value, public: true }
+ { key: variable_key, value: variable_value, public: true, masked: false }
end
describe '.new' do
@@ -88,7 +88,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
resource = described_class.fabricate(variable)
expect(resource).to be_a(described_class)
- expect(resource).to eq(key: 'CI_VAR', value: '123', public: false)
+ expect(resource).to eq(key: 'CI_VAR', value: '123', public: false, masked: false)
end
it 'supports using another collection item' do
@@ -134,7 +134,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
.to_runner_variable
expect(runner_variable)
- .to eq(key: 'VAR', value: 'value', public: true, file: true)
+ .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false)
end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 5c91816a586..8e732d44d5d 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Ci::Variables::Collection do
describe '.new' do
it 'can be initialized with an array' do
- variable = { key: 'VAR', value: 'value', public: true }
+ variable = { key: 'VAR', value: 'value', public: true, masked: false }
collection = described_class.new([variable])
@@ -66,6 +66,14 @@ describe Gitlab::Ci::Variables::Collection do
expect(collection).to include(key: 'VAR_3', value: '3', public: true)
end
+ it 'does not concatenate resource if it undefined' do
+ collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+
+ collection.concat(nil)
+
+ expect(collection).to be_one
+ end
+
it 'returns self' do
expect(subject.concat([key: 'VAR', value: 'test']))
.to eq subject
@@ -93,7 +101,7 @@ describe Gitlab::Ci::Variables::Collection do
collection = described_class.new([{ key: 'TEST', value: '1' }])
expect(collection.to_runner_variables)
- .to eq [{ key: 'TEST', value: '1', public: true }]
+ .to eq [{ key: 'TEST', value: '1', public: true, masked: false }]
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 91139d421f5..635b4e556e8 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
module Gitlab
module Ci
describe YamlProcessor do
+ include StubRequests
+
subject { described_class.new(config, user: nil) }
describe '#build_attributes' do
@@ -602,6 +604,100 @@ module Gitlab
end
end
+ describe "Include" do
+ let(:opts) { {} }
+
+ let(:config) do
+ {
+ include: include_content,
+ rspec: { script: "test" }
+ }
+ end
+
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) }
+
+ context "when validating a ci config file with no project context" do
+ context "when a single string is provided" do
+ let(:include_content) { "/local.gitlab-ci.yml" }
+
+ it "returns a validation error" do
+ expect { subject }.to raise_error /does not have project/
+ end
+ end
+
+ context "when an array is provided" do
+ let(:include_content) { ["/local.gitlab-ci.yml"] }
+
+ it "returns a validation error" do
+ expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /does not have project/)
+ end
+ end
+
+ context "when an array of wrong keyed object is provided" do
+ let(:include_content) { [{ yolo: "/local.gitlab-ci.yml" }] }
+
+ it "returns a validation error" do
+ expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError)
+ end
+ end
+
+ context "when an array of mixed typed objects is provided" do
+ let(:include_content) do
+ [
+ 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml',
+ { template: 'Auto-DevOps.gitlab-ci.yml' }
+ ]
+ end
+
+ before do
+ stub_full_request('https://gitlab.com/awesome-project/raw/master/.before-script-template.yml')
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: 'prepare: { script: ls -al }')
+ end
+
+ it "does not return any error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when the include type is incorrect" do
+ let(:include_content) { { name: "/local.gitlab-ci.yml" } }
+
+ it "returns an invalid configuration error" do
+ expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError)
+ end
+ end
+ end
+
+ context "when validating a ci config file within a project" do
+ let(:include_content) { "/local.gitlab-ci.yml" }
+ let(:project) { create(:project, :repository) }
+ let(:opts) { { project: project, sha: project.commit.sha } }
+
+ context "when the included internal file is present" do
+ before do
+ expect(project.repository).to receive(:blob_data_at)
+ .and_return(YAML.dump({ job1: { script: 'hello' } }))
+ end
+
+ it "does not return an error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when the included internal file is not present" do
+ it "returns an error with missing file details" do
+ expect { subject }.to raise_error(
+ Gitlab::Ci::YamlProcessor::ValidationError,
+ "Local file `#{include_content}` does not exist!"
+ )
+ end
+ end
+ end
+ end
+
describe "When" do
%w(on_success on_failure always).each do |when_state|
it "returns #{when_state} when defined" do
@@ -1154,7 +1250,7 @@ module Gitlab
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services:service config should be a hash or a string")
end
it "returns errors if job services parameter is not an array" do
@@ -1168,7 +1264,7 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services:service config should be a hash or a string")
end
it "returns error if job configuration is invalid" do
@@ -1374,7 +1470,7 @@ module Gitlab
expect { Gitlab::Ci::YamlProcessor.new(config) }
.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
- 'rspec: unknown key in `extends`')
+ 'rspec: unknown keys in `extends` (something)')
end
end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index b7924302014..51e5bdc6307 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -150,13 +150,13 @@ describe Gitlab::ContributionsCalendar do
end
describe '#starting_year' do
- it "should be the start of last year" do
+ it "is the start of last year" do
expect(calendar.starting_year).to eq(last_year.year)
end
end
describe '#starting_month' do
- it "should be the start of this month" do
+ it "is the start of this month" do
expect(calendar.starting_month).to eq(today.month)
end
end
diff --git a/spec/lib/gitlab/correlation_id_spec.rb b/spec/lib/gitlab/correlation_id_spec.rb
deleted file mode 100644
index 584d1f48386..00000000000
--- a/spec/lib/gitlab/correlation_id_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::CorrelationId do
- describe '.use_id' do
- it 'yields when executed' do
- expect { |blk| described_class.use_id('id', &blk) }.to yield_control
- end
-
- it 'stacks correlation ids' do
- described_class.use_id('id1') do
- described_class.use_id('id2') do |current_id|
- expect(current_id).to eq('id2')
- end
- end
- end
-
- it 'for missing correlation id it generates random one' do
- described_class.use_id('id1') do
- described_class.use_id(nil) do |current_id|
- expect(current_id).not_to be_empty
- expect(current_id).not_to eq('id1')
- end
- end
- end
- end
-
- describe '.current_id' do
- subject { described_class.current_id }
-
- it 'returns last correlation id' do
- described_class.use_id('id1') do
- described_class.use_id('id2') do
- is_expected.to eq('id2')
- end
- end
- end
- end
-
- describe '.current_or_new_id' do
- subject { described_class.current_or_new_id }
-
- context 'when correlation id is set' do
- it 'returns last correlation id' do
- described_class.use_id('id1') do
- is_expected.to eq('id1')
- end
- end
- end
-
- context 'when correlation id is missing' do
- it 'returns a new correlation id' do
- expect(described_class).to receive(:new_id)
- .and_call_original
-
- is_expected.not_to be_empty
- end
- end
- end
-
- describe '.ids' do
- subject { described_class.send(:ids) }
-
- it 'returns empty list if not correlation is used' do
- is_expected.to be_empty
- end
-
- it 'returns list if correlation ids are used' do
- described_class.use_id('id1') do
- described_class.use_id('id2') do
- is_expected.to eq(%w(id1 id2))
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index caf9fc5442c..909dbffa38f 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -115,9 +115,8 @@ describe Gitlab::CurrentSettings do
shared_examples 'a non-persisted ApplicationSetting object' do
let(:current_settings) { described_class.current_application_settings }
- it 'returns a non-persisted ApplicationSetting object' do
- expect(current_settings).to be_a(ApplicationSetting)
- expect(current_settings).not_to be_persisted
+ it 'returns a FakeApplicationSettings object' do
+ expect(current_settings).to be_a(Gitlab::FakeApplicationSettings)
end
it 'uses the default value from ApplicationSetting.defaults' do
@@ -143,9 +142,19 @@ describe Gitlab::CurrentSettings do
it_behaves_like 'a non-persisted ApplicationSetting object'
- it 'uses the value from the DB attribute if present and not overriden by an accessor' do
+ it 'uses the value from the DB attribute if present and not overridden by an accessor' do
expect(current_settings.home_page_url).to eq(db_settings.home_page_url)
end
+
+ context 'when a new column is used before being migrated' do
+ before do
+ allow(ApplicationSetting).to receive(:defaults).and_return({ foo: 'bar' })
+ end
+
+ it 'uses the default value if present' do
+ expect(current_settings.foo).to eq('bar')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 00cb1e6446a..f7642182a17 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -2,7 +2,6 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-require 'webmock/rspec'
require 'gitlab/danger/helper'
@@ -19,39 +18,6 @@ describe Gitlab::Danger::Helper do
end
end
- let(:teammate_json) do
- <<~JSON
- [
- {
- "username": "in-gitlab-ce",
- "name": "CE maintainer",
- "projects":{ "gitlab-ce": "maintainer backend" }
- },
- {
- "username": "in-gitlab-ee",
- "name": "EE reviewer",
- "projects":{ "gitlab-ee": "reviewer frontend" }
- }
- ]
- JSON
- end
-
- let(:ce_teammate_matcher) do
- satisfy do |teammate|
- teammate.username == 'in-gitlab-ce' &&
- teammate.name == 'CE maintainer' &&
- teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
- end
- end
-
- let(:ee_teammate_matcher) do
- satisfy do |teammate|
- teammate.username == 'in-gitlab-ee' &&
- teammate.name == 'EE reviewer' &&
- teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
- end
- end
-
let(:fake_git) { double('fake-git') }
subject(:helper) { FakeDanger.new(git: fake_git) }
@@ -119,69 +85,6 @@ describe Gitlab::Danger::Helper do
end
end
- describe '#team' do
- subject(:team) { helper.team }
-
- context 'HTTP failure' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(status: 404)
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to read/)
- end
- end
-
- context 'JSON failure' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: 'INVALID JSON')
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to parse/)
- end
- end
-
- context 'success' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: teammate_json)
- end
-
- it 'returns an array of teammates' do
- is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
- end
-
- it 'memoizes the result' do
- expect(team.object_id).to eq(helper.team.object_id)
- end
- end
- end
-
- describe '#project_team' do
- subject { helper.project_team }
-
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: teammate_json)
- end
-
- it 'filters team by project_name' do
- expect(helper)
- .to receive(:project_name)
- .at_least(:once)
- .and_return('gitlab-ce')
-
- is_expected.to contain_exactly(ce_teammate_matcher)
- end
- end
-
describe '#changes_by_category' do
it 'categorizes changed files' do
expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo ee/changelogs/foo.yml] }
@@ -191,9 +94,8 @@ describe Gitlab::Danger::Helper do
expect(helper.changes_by_category).to eq(
backend: %w[foo.rb],
database: %w[db/foo],
- docs: %w[foo.md],
frontend: %w[foo.js],
- none: %w[ee/changelogs/foo.yml],
+ none: %w[ee/changelogs/foo.yml foo.md],
qa: %w[qa/foo],
unknown: %w[foo]
)
@@ -202,13 +104,13 @@ describe Gitlab::Danger::Helper do
describe '#category_for_file' do
where(:path, :expected_category) do
- 'doc/foo' | :docs
- 'CONTRIBUTING.md' | :docs
- 'LICENSE' | :docs
- 'MAINTENANCE.md' | :docs
- 'PHILOSOPHY.md' | :docs
- 'PROCESS.md' | :docs
- 'README.md' | :docs
+ 'doc/foo' | :none
+ 'CONTRIBUTING.md' | :none
+ 'LICENSE' | :none
+ 'MAINTENANCE.md' | :none
+ 'PHILOSOPHY.md' | :none
+ 'PROCESS.md' | :none
+ 'README.md' | :none
'ee/doc/foo' | :unknown
'ee/README' | :unknown
@@ -265,14 +167,15 @@ describe Gitlab::Danger::Helper do
'changelogs/foo' | :none
'ee/changelogs/foo' | :none
+ 'locale/gitlab.pot' | :none
'FOO' | :unknown
'foo' | :unknown
'foo/bar.rb' | :backend
'foo/bar.js' | :frontend
- 'foo/bar.txt' | :docs
- 'foo/bar.md' | :docs
+ 'foo/bar.txt' | :none
+ 'foo/bar.md' | :none
end
with_them do
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
new file mode 100644
index 00000000000..40dce0c5378
--- /dev/null
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'webmock/rspec'
+
+require 'gitlab/danger/roulette'
+
+describe Gitlab::Danger::Roulette do
+ let(:teammate_json) do
+ <<~JSON
+ [
+ {
+ "username": "in-gitlab-ce",
+ "name": "CE maintainer",
+ "projects":{ "gitlab-ce": "maintainer backend" }
+ },
+ {
+ "username": "in-gitlab-ee",
+ "name": "EE reviewer",
+ "projects":{ "gitlab-ee": "reviewer frontend" }
+ }
+ ]
+ JSON
+ end
+
+ let(:ce_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ce' &&
+ teammate.name == 'CE maintainer' &&
+ teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
+ end
+ end
+
+ let(:ee_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ee' &&
+ teammate.name == 'EE reviewer' &&
+ teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
+ end
+ end
+
+ subject(:roulette) { Object.new.extend(described_class) }
+
+ describe '#team' do
+ subject(:team) { roulette.team }
+
+ context 'HTTP failure' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(status: 404)
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to read/)
+ end
+ end
+
+ context 'JSON failure' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: 'INVALID JSON')
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to parse/)
+ end
+ end
+
+ context 'success' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: teammate_json)
+ end
+
+ it 'returns an array of teammates' do
+ is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
+ end
+
+ it 'memoizes the result' do
+ expect(team.object_id).to eq(roulette.team.object_id)
+ end
+ end
+ end
+
+ describe '#project_team' do
+ subject { roulette.project_team('gitlab-ce') }
+
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: teammate_json)
+ end
+
+ it 'filters team by project_name' do
+ is_expected.to contain_exactly(ce_teammate_matcher)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
new file mode 100644
index 00000000000..6a6cf1429c8
--- /dev/null
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'gitlab/danger/teammate'
+
+describe Gitlab::Danger::Teammate do
+ subject { described_class.new(options) }
+ let(:options) { { 'projects' => projects, 'role' => role } }
+ let(:projects) { { project => capabilities } }
+ let(:role) { 'Engineer, Manage' }
+ let(:labels) { [] }
+ let(:project) { double }
+
+ context 'when having multiple capabilities' do
+ let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] }
+
+ it '#reviewer? supports multiple roles per project' do
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
+ end
+
+ it '#traintainer? supports multiple roles per project' do
+ expect(subject.traintainer?(project, :qa, labels)).to be_truthy
+ end
+
+ it '#maintainer? supports multiple roles per project' do
+ expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
+ end
+
+ context 'when labels contain Create and the category is test' do
+ let(:labels) { ['Create'] }
+
+ context 'when role is Test Automation Engineer, Create' do
+ let(:role) { 'Test Automation Engineer, Create' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :test, labels)).to be_truthy
+ end
+
+ it '#maintainer? returns false' do
+ expect(subject.maintainer?(project, :test, labels)).to be_falsey
+ end
+ end
+
+ context 'when role is Test Automation Engineer, Manage' do
+ let(:role) { 'Test Automation Engineer, Manage' }
+
+ it '#reviewer? returns false' do
+ expect(subject.reviewer?(project, :test, labels)).to be_falsey
+ end
+ end
+ end
+ end
+
+ context 'when having single capability' do
+ let(:capabilities) { 'reviewer backend' }
+
+ it '#reviewer? supports one role per project' do
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
+ end
+
+ it '#traintainer? supports one role per project' do
+ expect(subject.traintainer?(project, :database, labels)).to be_falsey
+ end
+
+ it '#maintainer? supports one role per project' do
+ expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
new file mode 100644
index 00000000000..0a6e2302b09
--- /dev/null
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DataBuilder::Deployment do
+ describe '.build' do
+ it 'returns the object kind for a deployment' do
+ deployment = build(:deployment)
+
+ data = described_class.build(deployment)
+
+ expect(data[:object_kind]).to eq('deployment')
+ end
+
+ it 'returns data for the given build' do
+ environment = create(:environment, name: "somewhere")
+ project = create(:project, :repository, name: 'myproj')
+ commit = project.commit('HEAD')
+ deployment = create(:deployment, status: :failed, environment: environment, sha: commit.sha, project: project)
+ deployable = deployment.deployable
+ expected_deployable_url = Gitlab::Routing.url_helpers.project_job_url(deployable.project, deployable)
+ expected_user_url = Gitlab::Routing.url_helpers.user_url(deployment.user)
+ expected_commit_url = Gitlab::UrlBuilder.build(commit)
+
+ data = described_class.build(deployment)
+
+ expect(data[:status]).to eq('failed')
+ expect(data[:deployable_id]).to eq(deployable.id)
+ expect(data[:deployable_url]).to eq(expected_deployable_url)
+ expect(data[:environment]).to eq("somewhere")
+ expect(data[:project]).to eq(project.hook_attrs)
+ expect(data[:short_sha]).to eq(deployment.short_sha)
+ expect(data[:user]).to eq(deployment.user.hook_attrs)
+ expect(data[:user_url]).to eq(expected_user_url)
+ expect(data[:commit_url]).to eq(expected_commit_url)
+ expect(data[:commit_title]).to eq(commit.title)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 9ef987a0826..1f36fd5c6ef 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -50,5 +50,14 @@ describe Gitlab::DataBuilder::Pipeline do
it { expect(attributes[:variables]).to be_a(Array) }
it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
end
+
+ context 'when pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.first }
+
+ it 'returns a source ref' do
+ expect(attributes[:ref]).to eq(merge_request.source_branch)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 0c4decc6518..46ad674a1eb 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -23,9 +23,12 @@ describe Gitlab::DataBuilder::Push do
describe '.build' do
let(:data) do
- described_class.build(project, user, Gitlab::Git::BLANK_SHA,
- '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
- 'refs/tags/v1.1.0')
+ described_class.build(
+ project: project,
+ user: user,
+ oldrev: Gitlab::Git::BLANK_SHA,
+ newrev: '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+ ref: 'refs/tags/v1.1.0')
end
it { expect(data).to be_a(Hash) }
@@ -47,7 +50,7 @@ describe Gitlab::DataBuilder::Push do
include_examples 'deprecated repository hook data'
it 'does not raise an error when given nil commits' do
- expect { described_class.build(spy, spy, spy, spy, 'refs/tags/v1.1.0', nil) }
+ expect { described_class.build(project: spy, user: spy, ref: 'refs/tags/v1.1.0', commits: nil) }
.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index b44e8c5a110..bd3c66d0548 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -6,10 +6,11 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
create(:identity)
end
- let(:models) { [Project, Identity] }
subject { described_class.new(models).count }
describe '#count', :postgresql do
+ let(:models) { [Project, Identity] }
+
context 'when reltuples is up to date' do
before do
ActiveRecord::Base.connection.execute('ANALYZE projects')
@@ -23,6 +24,22 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
end
end
+ context 'when models using single-type inheritance are used' do
+ let(:models) { [Group, CiService, Namespace] }
+
+ before do
+ models.each do |model|
+ ActiveRecord::Base.connection.execute("ANALYZE #{model.table_name}")
+ end
+ end
+
+ it 'returns nil counts for inherited tables' do
+ models.each { |model| expect(model).not_to receive(:count) }
+
+ expect(subject).to eq({ Namespace => 3 })
+ end
+ end
+
context 'insufficient permissions' do
it 'returns an empty hash' do
allow(ActiveRecord::Base).to receive(:transaction).and_raise(PG::InsufficientPrivilege)
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 203f9344a41..40d810b195b 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -4,15 +4,23 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
before do
create_list(:project, 3)
create(:identity)
+ create(:group)
end
- let(:models) { [Project, Identity] }
+ let(:models) { [Project, Identity, Group, Namespace] }
let(:strategy) { described_class.new(models) }
subject { strategy.count }
describe '#count', :postgresql do
- let(:estimates) { { Project => threshold + 1, Identity => threshold - 1 } }
+ let(:estimates) do
+ {
+ Project => threshold + 1,
+ Identity => threshold - 1,
+ Group => threshold + 1,
+ Namespace => threshold + 1
+ }
+ end
let(:threshold) { Gitlab::Database::Count::TablesampleCountStrategy::EXACT_COUNT_THRESHOLD }
before do
@@ -30,9 +38,13 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
context 'for tables with an estimated large size' do
it 'performs a tablesample count' do
expect(Project).not_to receive(:count)
+ expect(Group).not_to receive(:count)
+ expect(Namespace).not_to receive(:count)
result = subject
expect(result[Project]).to eq(3)
+ expect(result[Group]).to eq(1)
+ expect(result[Namespace]).to eq(4)
end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 248cca25a2c..81419e51635 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete
Project.find(project.id)
end
- describe "#remove_last_ocurrence" do
+ describe "#remove_last_occurrence" do
it "removes only the last occurrence of a string" do
input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace"
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 1d31f96159c..ddd54a669a3 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
describe '#rename_wildcard_paths' do
it_behaves_like 'renames child namespaces'
- it 'should rename projects' do
+ it 'renames projects' do
rename_projects = double
expect(described_class::RenameProjects)
.to receive(:new).with(['the-path'], subject)
@@ -40,7 +40,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
end
describe '#rename_root_paths' do
- it 'should rename namespaces' do
+ it 'renames namespaces' do
rename_namespaces = double
expect(described_class::RenameNamespaces)
.to receive(:new).with(['the-path'], subject)
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 60106ee3c0b..5f57cd6b825 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -17,6 +17,20 @@ describe Gitlab::Database do
end
end
+ describe '.human_adapter_name' do
+ it 'returns PostgreSQL when using PostgreSQL' do
+ allow(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(described_class.human_adapter_name).to eq('PostgreSQL')
+ end
+
+ it 'returns MySQL when using MySQL' do
+ allow(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(described_class.human_adapter_name).to eq('MySQL')
+ end
+ end
+
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
describe '.mysql?' do
@@ -87,6 +101,38 @@ describe Gitlab::Database do
end
end
+ describe '.postgresql_minimum_supported_version?' do
+ it 'returns false when not using PostgreSQL' do
+ allow(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(false)
+ end
+
+ context 'when using PostgreSQL' do
+ before do
+ allow(described_class).to receive(:postgresql?).and_return(true)
+ end
+
+ it 'returns false when using PostgreSQL 9.5' do
+ allow(described_class).to receive(:version).and_return('9.5')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(false)
+ end
+
+ it 'returns true when using PostgreSQL 9.6' do
+ allow(described_class).to receive(:version).and_return('9.6')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
+
+ it 'returns true when using PostgreSQL 10 or newer' do
+ allow(described_class).to receive(:version).and_return('10')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
+ end
+ end
+
describe '.join_lateral_supported?' do
it 'returns false when using MySQL' do
allow(described_class).to receive(:postgresql?).and_return(false)
@@ -195,6 +241,12 @@ describe Gitlab::Database do
end
end
+ describe '.pg_last_xact_replay_timestamp' do
+ it 'returns pg_last_xact_replay_timestamp' do
+ expect(described_class.pg_last_xact_replay_timestamp).to eq('pg_last_xact_replay_timestamp')
+ end
+ end
+
describe '.nulls_last_order' do
context 'when using PostgreSQL' do
before do
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index 4d222564fd0..0ebd8994636 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -50,8 +50,8 @@ describe Gitlab::DependencyLinker::ComposerJsonLinker do
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
- it 'links the module name' do
- expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
+ it 'does not link the module name' do
+ expect(subject).not_to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
end
it 'links the homepage' do
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index a97803b119e..f00f6b47b94 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -41,13 +41,16 @@ describe Gitlab::DependencyLinker::GemfileLinker do
end
it 'links dependencies' do
- expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails'))
expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
- expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders'))
- expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets'))
expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
end
+ it 'links to external dependencies' do
+ expect(subject).to include(link('rails', 'https://github.com/rails/rails'))
+ expect(subject).to include(link('responders', 'https://github.com/rails/responders'))
+ expect(subject).to include(link('sprockets', 'https://gitlab.example.com/gems/sprockets'))
+ end
+
it 'links GitHub repos' do
expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index 24ad7d12f4c..6c6a5d70576 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -43,8 +43,8 @@ describe Gitlab::DependencyLinker::GemspecLinker do
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
- it 'links the gem name' do
- expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
+ it 'does not link the gem name' do
+ expect(subject).not_to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
end
it 'links the license' do
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 1e8b72afb7b..9050127af7f 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -33,7 +33,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
"express": "4.2.x",
"bigpipe": "bigpipe/pagelet",
"plates": "https://github.com/flatiron/plates/tarball/master",
- "karma": "^1.4.1"
+ "karma": "^1.4.1",
+ "random": "git+https://EdOverflow@github.com/example/example.git"
},
"devDependencies": {
"vows": "^0.7.0",
@@ -51,8 +52,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
- it 'links the module name' do
- expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
+ it 'does not link the module name' do
+ expect(subject).not_to include(link('module-name', 'https://npmjs.com/package/module-name'))
end
it 'links the homepage' do
@@ -71,14 +72,21 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
- expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
- expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
end
+ it 'links dependencies to URL detected on value' do
+ expect(subject).to include(link('bigpipe', 'https://github.com/bigpipe/pagelet'))
+ expect(subject).to include(link('plates', 'https://github.com/flatiron/plates/tarball/master'))
+ end
+
+ it 'does not link to NPM when invalid git URL' do
+ expect(subject).not_to include(link('random', 'https://npmjs.com/package/random'))
+ end
+
it 'links GitHub repos' do
expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
end
diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
new file mode 100644
index 00000000000..9bfb1b13a2b
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::Parser::Gemfile do
+ describe '#parse' do
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ source 'https://rubygems.org'
+
+ gem "rails", '4.2.6', github: "rails/rails"
+ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+ gem 'responders', '~> 2.0', :github => 'rails/responders'
+ gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets'
+ gem 'default_value_for', '~> 3.0.0'
+ CONTENT
+ end
+
+ subject { described_class.new(file_content).parse(keyword: 'gem') }
+
+ def fetch_package(name)
+ subject.find { |package| package.name == name }
+ end
+
+ it 'returns parsed packages' do
+ expect(subject.size).to eq(5)
+ expect(subject).to all(be_a(Gitlab::DependencyLinker::Package))
+ end
+
+ it 'packages respond to name and external_ref accordingly' do
+ expect(fetch_package('rails')).to have_attributes(name: 'rails',
+ github_ref: 'rails/rails',
+ git_ref: nil)
+
+ expect(fetch_package('sprockets')).to have_attributes(name: 'sprockets',
+ github_ref: nil,
+ git_ref: 'https://gitlab.example.com/gems/sprockets')
+
+ expect(fetch_package('default_value_for')).to have_attributes(name: 'default_value_for',
+ github_ref: nil,
+ git_ref: nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index cdfd7ad9826..8f1b523653e 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -43,7 +43,10 @@ describe Gitlab::DependencyLinker::PodfileLinker do
it 'links packages' do
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
- expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
+ end
+
+ it 'links external packages' do
+ expect(subject).to include(link('Interstellar/Core', 'https://github.com/ashfurrow/Interstellar.git'))
end
it 'links Git repos' do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index ed60ab45955..bacec830103 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -42,8 +42,8 @@ describe Gitlab::DependencyLinker::PodspecLinker do
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
- it 'links the gem name' do
- expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
+ it 'does not link the pod name' do
+ expect(subject).not_to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
end
it 'links the license' do
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 256166dbad3..0697594c725 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
let(:diffable) { merge_request.merge_request_diff }
end
- it 'it uses a different cache key if diff line keys change' do
+ it 'uses a different cache key if diff line keys change' do
mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil)
key = mr_diff.cache_key
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 862590268ca..cc36060f864 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -8,6 +8,47 @@ describe Gitlab::Diff::File do
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
+ def create_file(file_name, content)
+ Files::CreateService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name,
+ file_content: content
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
+ def update_file(file_name, content)
+ Files::UpdateService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name,
+ file_content: content
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
+ def delete_file(file_name)
+ Files::DeleteService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
describe '#diff_lines' do
let(:diff_lines) { diff_file.diff_lines }
@@ -31,6 +72,13 @@ describe Gitlab::Diff::File do
expect(diff_file.diff_lines_for_serializer.last.type).to eq('match')
end
+ context 'when called multiple times' do
+ it 'only adds bottom match line once' do
+ expect(diff_file.diff_lines_for_serializer.size).to eq(31)
+ expect(diff_file.diff_lines_for_serializer.size).to eq(31)
+ end
+ end
+
context 'when deleted' do
let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') }
let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') }
@@ -675,47 +723,6 @@ describe Gitlab::Diff::File do
end
let(:branch_name) { 'master' }
- def create_file(file_name, content)
- Files::CreateService.new(
- project,
- project.owner,
- commit_message: 'Update',
- start_branch: branch_name,
- branch_name: branch_name,
- file_path: file_name,
- file_content: content
- ).execute
-
- project.commit(branch_name).diffs.diff_files.first
- end
-
- def update_file(file_name, content)
- Files::UpdateService.new(
- project,
- project.owner,
- commit_message: 'Update',
- start_branch: branch_name,
- branch_name: branch_name,
- file_path: file_name,
- file_content: content
- ).execute
-
- project.commit(branch_name).diffs.diff_files.first
- end
-
- def delete_file(file_name)
- Files::DeleteService.new(
- project,
- project.owner,
- commit_message: 'Update',
- start_branch: branch_name,
- branch_name: branch_name,
- file_path: file_name
- ).execute
-
- project.commit(branch_name).diffs.diff_files.first
- end
-
context 'when empty file is created' do
it 'returns true' do
diff_file = create_file('empty.md', '')
@@ -751,4 +758,123 @@ describe Gitlab::Diff::File do
end
end
end
+
+ describe '#fully_expanded?' do
+ let(:project) do
+ create(:project, :custom_repo, files: {})
+ end
+ let(:branch_name) { 'master' }
+
+ context 'when empty file is created' do
+ it 'returns true' do
+ diff_file = create_file('empty.md', '')
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when empty file is deleted' do
+ it 'returns true' do
+ create_file('empty.md', '')
+ diff_file = delete_file('empty.md')
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when short file with last line removed' do
+ it 'returns true' do
+ create_file('with-content.md', (1..3).to_a.join("\n"))
+ diff_file = update_file('with-content.md', (1..2).to_a.join("\n"))
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when a single line is added to empty file' do
+ it 'returns true' do
+ create_file('empty.md', '')
+ diff_file = update_file('empty.md', 'new content')
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when single line file is changed' do
+ it 'returns true' do
+ create_file('file.md', 'foo')
+ diff_file = update_file('file.md', 'bar')
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when long file is changed' do
+ before do
+ create_file('file.md', (1..999).to_a.join("\n"))
+ end
+
+ context 'when first line is removed' do
+ it 'returns true' do
+ diff_file = update_file('file.md', (2..999).to_a.join("\n"))
+
+ expect(diff_file.fully_expanded?).to be_falsey
+ end
+ end
+
+ context 'when last line is removed' do
+ it 'returns true' do
+ diff_file = update_file('file.md', (1..998).to_a.join("\n"))
+
+ expect(diff_file.fully_expanded?).to be_falsey
+ end
+ end
+
+ context 'when first and last lines are removed' do
+ it 'returns false' do
+ diff_file = update_file('file.md', (2..998).to_a.join("\n"))
+
+ expect(diff_file.fully_expanded?).to be_falsey
+ end
+ end
+
+ context 'when first and last lines are changed' do
+ it 'returns false' do
+ content = (2..998).to_a
+ content.prepend('a')
+ content.append('z')
+ content = content.join("\n")
+
+ diff_file = update_file('file.md', content)
+
+ expect(diff_file.fully_expanded?).to be_falsey
+ end
+ end
+
+ context 'when every line are changed' do
+ it 'returns true' do
+ diff_file = update_file('file.md', "hi\n" * 999)
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when all contents are cleared' do
+ it 'returns true' do
+ diff_file = update_file('file.md', "")
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+
+ context 'when file is binary' do
+ it 'returns true' do
+ diff_file = update_file('file.md', (1..998).to_a.join("\n"))
+ allow(diff_file).to receive(:binary?).and_return(true)
+
+ expect(diff_file.fully_expanded?).to be_truthy
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/suggestion_diff_spec.rb b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
new file mode 100644
index 00000000000..5a32c2bea37
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::SuggestionDiff do
+ describe '#diff_lines' do
+ let(:from_content) do
+ <<-BLOB.strip_heredoc
+ "tags": ["devel", "development", "nightly"],
+ "desktop-file-name-prefix": "(Development) ",
+ "finish-args": "foo",
+ BLOB
+ end
+
+ let(:to_content) do
+ <<-BLOB.strip_heredoc
+ "buildsystem": "meson",
+ "builddir": true,
+ "name": "nautilus",
+ "bar": "bar",
+ BLOB
+ end
+
+ let(:suggestion) do
+ instance_double(Suggestion, from_line: 12,
+ from_content: from_content,
+ to_content: to_content)
+ end
+
+ subject { described_class.new(suggestion).diff_lines }
+
+ let(:expected_diff_lines) do
+ [
+ { old_pos: 12, new_pos: 12, type: "match", text: "@@ -12 +12" },
+ { old_pos: 12, new_pos: 12, type: "old", text: "-\"tags\": [\"devel\", \"development\", \"nightly\"]," },
+ { old_pos: 13, new_pos: 12, type: "old", text: "-\"desktop-file-name-prefix\": \"(Development) \"," },
+ { old_pos: 14, new_pos: 12, type: "old", text: "-\"finish-args\": \"foo\"," },
+ { old_pos: 15, new_pos: 12, type: "new", text: "+\"buildsystem\": \"meson\"," },
+ { old_pos: 15, new_pos: 13, type: "new", text: "+\"builddir\": true," },
+ { old_pos: 15, new_pos: 14, type: "new", text: "+\"name\": \"nautilus\"," },
+ { old_pos: 15, new_pos: 15, type: "new", text: "+\"bar\": \"bar\"," }
+ ]
+ end
+
+ it 'returns diff lines with correct line numbers' do
+ diff_lines = subject
+
+ expect(diff_lines).to all(be_a(Gitlab::Diff::Line))
+
+ expected_diff_lines.each_with_index do |expected_line, index|
+ expect(diff_lines[index].to_hash).to include(expected_line)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb
new file mode 100644
index 00000000000..d7ca0e0a522
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestion_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::Suggestion do
+ shared_examples 'correct suggestion raw content' do
+ it 'returns correct raw data' do
+ expect(suggestion.to_hash).to include(from_content: expected_lines.join,
+ to_content: "#{text}\n",
+ lines_above: above,
+ lines_below: below)
+ end
+
+ it 'returns diff lines with correct line numbers' do
+ diff_lines = suggestion.diff_lines
+
+ expect(diff_lines).to all(be_a(Gitlab::Diff::Line))
+
+ expected_diff_lines.each_with_index do |expected_line, index|
+ expect(diff_lines[index].to_hash).to include(expected_line)
+ end
+ end
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+ let(:text) { "# parsed suggestion content\n# with comments" }
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line)
+ end
+
+ def blob_data
+ blob = diff_file.new_blob
+ blob.load_all_data!
+ blob.data
+ end
+
+ let(:suggestion) do
+ described_class.new(text, line: line, above: above, below: below, diff_file: diff_file)
+ end
+
+ describe '#to_hash' do
+ context 'when changing content surpasses the top limit' do
+ let(:line) { 4 }
+ let(:above) { 5 }
+ let(:below) { 2 }
+ let(:expected_above) { line - 1 }
+ let(:expected_below) { below }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+ let(:expected_diff_lines) do
+ [
+ { old_pos: 1, new_pos: 1, type: 'old', text: "-require 'fileutils'" },
+ { old_pos: 2, new_pos: 1, type: 'old', text: "-require 'open3'" },
+ { old_pos: 3, new_pos: 1, type: 'old', text: "-" },
+ { old_pos: 4, new_pos: 1, type: 'old', text: "-module Popen" },
+ { old_pos: 5, new_pos: 1, type: 'old', text: "- extend self" },
+ { old_pos: 6, new_pos: 1, type: 'old', text: "-" },
+ { old_pos: 7, new_pos: 1, type: 'new', text: "+# parsed suggestion content" },
+ { old_pos: 7, new_pos: 2, type: 'new', text: "+# with comments" }
+ ]
+ end
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when changing content surpasses the amount of lines in the blob (bottom)' do
+ let(:line) { 5 }
+ let(:above) { 1 }
+ let(:below) { blob_data.lines.size + 10 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+ let(:expected_diff_lines) do
+ [
+ { old_pos: 4, new_pos: 4, type: "match", text: "@@ -4 +4" },
+ { old_pos: 4, new_pos: 4, type: "old", text: "-module Popen" },
+ { old_pos: 5, new_pos: 4, type: "old", text: "- extend self" },
+ { old_pos: 6, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 7, new_pos: 4, type: "old", text: "- def popen(cmd, path=nil)" },
+ { old_pos: 8, new_pos: 4, type: "old", text: "- unless cmd.is_a?(Array)" },
+ { old_pos: 9, new_pos: 4, type: "old", text: "- raise RuntimeError, \"System commands must be given as an array of strings\"" },
+ { old_pos: 10, new_pos: 4, type: "old", text: "- end" },
+ { old_pos: 11, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 12, new_pos: 4, type: "old", text: "- path ||= Dir.pwd" },
+ { old_pos: 13, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 14, new_pos: 4, type: "old", text: "- vars = {" },
+ { old_pos: 15, new_pos: 4, type: "old", text: "- \"PWD\" => path" },
+ { old_pos: 16, new_pos: 4, type: "old", text: "- }" },
+ { old_pos: 17, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 18, new_pos: 4, type: "old", text: "- options = {" },
+ { old_pos: 19, new_pos: 4, type: "old", text: "- chdir: path" },
+ { old_pos: 20, new_pos: 4, type: "old", text: "- }" },
+ { old_pos: 21, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 22, new_pos: 4, type: "old", text: "- unless File.directory?(path)" },
+ { old_pos: 23, new_pos: 4, type: "old", text: "- FileUtils.mkdir_p(path)" },
+ { old_pos: 24, new_pos: 4, type: "old", text: "- end" },
+ { old_pos: 25, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 26, new_pos: 4, type: "old", text: "- @cmd_output = \"\"" },
+ { old_pos: 27, new_pos: 4, type: "old", text: "- @cmd_status = 0" },
+ { old_pos: 28, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 29, new_pos: 4, type: "old", text: "- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|" },
+ { old_pos: 30, new_pos: 4, type: "old", text: "- @cmd_output << stdout.read" },
+ { old_pos: 31, new_pos: 4, type: "old", text: "- @cmd_output << stderr.read" },
+ { old_pos: 32, new_pos: 4, type: "old", text: "- @cmd_status = wait_thr.value.exitstatus" },
+ { old_pos: 33, new_pos: 4, type: "old", text: "- end" },
+ { old_pos: 34, new_pos: 4, type: "old", text: "-" },
+ { old_pos: 35, new_pos: 4, type: "old", text: "- return @cmd_output, @cmd_status" },
+ { old_pos: 36, new_pos: 4, type: "old", text: "- end" },
+ { old_pos: 37, new_pos: 4, type: "old", text: "-end" },
+ { old_pos: 38, new_pos: 4, type: "new", text: "+# parsed suggestion content" },
+ { old_pos: 38, new_pos: 5, type: "new", text: "+# with comments" }
+ ]
+ end
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when lines are within blob lines boundary' do
+ let(:line) { 5 }
+ let(:above) { 2 }
+ let(:below) { 3 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+ let(:expected_diff_lines) do
+ [
+ { old_pos: 3, new_pos: 3, type: "match", text: "@@ -3 +3" },
+ { old_pos: 3, new_pos: 3, type: "old", text: "-" },
+ { old_pos: 4, new_pos: 3, type: "old", text: "-module Popen" },
+ { old_pos: 5, new_pos: 3, type: "old", text: "- extend self" },
+ { old_pos: 6, new_pos: 3, type: "old", text: "-" },
+ { old_pos: 7, new_pos: 3, type: "old", text: "- def popen(cmd, path=nil)" },
+ { old_pos: 8, new_pos: 3, type: "old", text: "- unless cmd.is_a?(Array)" },
+ { old_pos: 9, new_pos: 3, type: "new", text: "+# parsed suggestion content" },
+ { old_pos: 9, new_pos: 4, type: "new", text: "+# with comments" }
+ ]
+ end
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
new file mode 100644
index 00000000000..1f2af42f6e7
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::SuggestionsParser do
+ describe '.parse' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+
+ subject do
+ described_class.parse(markdown, project: merge_request.project,
+ position: position)
+ end
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ context 'single-line suggestions' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ bar
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion
+ xpto
+ baz
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+ end
+
+ it 'returns a list of Gitlab::Diff::Suggestion' do
+ expect(subject).to all(be_a(Gitlab::Diff::Suggestion))
+ expect(subject.size).to eq(2)
+ end
+
+ it 'parsed suggestion has correct data' do
+ from_line, to_line = position.new_line, position.new_line
+
+ expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " foo\n bar\n",
+ lines_above: 0,
+ lines_below: 0)
+
+ expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " xpto\n baz\n",
+ lines_above: 0,
+ lines_below: 0)
+ end
+ end
+
+ context 'multi-line suggestions' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion:-2+1
+ # above and below
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion:-3
+ # only above
+ ```
+
+ ```suggestion:+3
+ # only below
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+ end
+
+ it 'returns a list of Gitlab::Diff::Suggestion' do
+ expect(subject).to all(be_a(Gitlab::Diff::Suggestion))
+ expect(subject.size).to eq(3)
+ end
+
+ it 'suggestion with above and below param has correct data' do
+ from_line = position.new_line - 2
+ to_line = position.new_line + 1
+
+ expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " # above and below\n",
+ lines_above: 2,
+ lines_below: 1)
+ end
+
+ it 'suggestion with above param has correct data' do
+ from_line = position.new_line - 3
+ to_line = position.new_line
+
+ expect(subject.second.to_hash).to eq(from_content: blob_lines_data(from_line, to_line),
+ to_content: " # only above\n",
+ lines_above: 3,
+ lines_below: 0)
+ end
+
+ it 'suggestion with below param has correct data' do
+ from_line = position.new_line
+ to_line = position.new_line + 3
+
+ expect(subject.third.to_hash).to eq(from_content: blob_lines_data(from_line, to_line),
+ to_content: " # only below\n",
+ lines_above: 0,
+ lines_below: 3)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
index fe26ebb8796..15ee8c40b55 100644
--- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -3,31 +3,32 @@
require 'spec_helper'
describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
+ def fake_file(offset)
+ {
+ text: 'foo',
+ type: 'new',
+ index: 2 + offset,
+ old_pos: 10 + offset,
+ new_pos: 11 + offset,
+ line_code: 'xpto',
+ rich_text: '<blips>blops</blips>'
+ }
+ end
+
+ let(:mapping) do
+ {
+ 3 => [
+ fake_file(0),
+ fake_file(1)
+ ],
+ 4 => [
+ fake_file(2)
+ ]
+ }
+ end
+
describe '#write_multiple' do
it 'sets multiple keys serializing content as JSON' do
- mapping = {
- 3 => [
- {
- text: 'foo',
- type: 'new',
- index: 2,
- old_pos: 10,
- new_pos: 11,
- line_code: 'xpto',
- rich_text: '<blips>blops</blips>'
- },
- {
- text: 'foo',
- type: 'new',
- index: 3,
- old_pos: 11,
- new_pos: 12,
- line_code: 'xpto',
- rich_text: '<blops>blips</blops>'
- }
- ]
- }
-
described_class.write_multiple(mapping)
mapping.each do |key, value|
@@ -41,53 +42,16 @@ describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
describe '#read_multiple' do
it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
- mapping = {
- 3 => [
- {
- text: 'foo',
- type: 'new',
- index: 2,
- old_pos: 11,
- new_pos: 12,
- line_code: 'xpto',
- rich_text: '<blips>blops</blips>'
- },
- {
- text: 'foo',
- type: 'new',
- index: 3,
- old_pos: 10,
- new_pos: 11,
- line_code: 'xpto',
- rich_text: '<blips>blops</blips>'
- }
- ]
- }
-
described_class.write_multiple(mapping)
found = described_class.read_multiple(mapping.keys)
- expect(found.size).to eq(1)
+ expect(found.size).to eq(2)
expect(found.first.size).to eq(2)
expect(found.first).to all(be_a(Gitlab::Diff::Line))
end
it 'returns nil when cached key is not found' do
- mapping = {
- 3 => [
- {
- text: 'foo',
- type: 'new',
- index: 2,
- old_pos: 11,
- new_pos: 12,
- line_code: 'xpto',
- rich_text: '<blips>blops</blips>'
- }
- ]
- }
-
described_class.write_multiple(mapping)
found = described_class.read_multiple([2, 3])
@@ -95,8 +59,30 @@ describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
expect(found.size).to eq(2)
expect(found.first).to eq(nil)
- expect(found.second.size).to eq(1)
+ expect(found.second.size).to eq(2)
expect(found.second).to all(be_a(Gitlab::Diff::Line))
end
end
+
+ describe '#clear_multiple' do
+ it 'removes all named keys' do
+ described_class.write_multiple(mapping)
+
+ described_class.clear_multiple(mapping.keys)
+
+ expect(described_class.read_multiple(mapping.keys)).to all(be_nil)
+ end
+
+ it 'only removed named keys' do
+ to_clear, to_leave = mapping.keys
+
+ described_class.write_multiple(mapping)
+ described_class.clear_multiple([to_clear])
+
+ cleared, left = described_class.read_multiple([to_clear, to_leave])
+
+ expect(cleared).to be_nil
+ expect(left).to all(be_a(Gitlab::Diff::Line))
+ end
+ end
end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 429816efec3..88ea98eb1e1 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -189,14 +189,23 @@ describe Gitlab::EncodingHelper do
end
end
- describe '#binary_stringio' do
+ describe '#binary_io' do
it 'does not mutate the original string encoding' do
test = 'my-test'
- io_stream = ext_class.binary_stringio(test)
+ io_stream = ext_class.binary_io(test)
expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
expect(test.encoding.name).to eq('UTF-8')
end
+
+ it 'returns a copy of the IO with the correct encoding' do
+ test = fixture_file_upload('spec/fixtures/doc_sample.txt').to_io
+
+ io_stream = ext_class.binary_io(test)
+
+ expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
+ expect(test).not_to eq(io_stream)
+ end
end
end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index f69cb502ca6..a7cb0bb2a87 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -19,6 +19,24 @@ describe Gitlab::EtagCaching::Router do
expect(result.name).to eq 'issue_title'
end
+ it 'matches with a project name that includes a suffix of create' do
+ result = described_class.match(
+ '/group/test-create/issues/123/realtime_changes'
+ )
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_title'
+ end
+
+ it 'matches with a project name that includes a prefix of create' do
+ result = described_class.match(
+ '/group/create-test/issues/123/realtime_changes'
+ )
+
+ expect(result).to be_present
+ expect(result.name).to eq 'issue_title'
+ end
+
it 'matches project pipelines endpoint' do
result = described_class.match(
'/my-group/my-project/pipelines.json'
diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb
new file mode 100644
index 00000000000..5dc2521b310
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization/access_spec.rb
@@ -0,0 +1,142 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
+ subject(:access) { described_class.new(build(:user), 'dummy_label') }
+
+ describe '#loaded?' do
+ it 'is `true` when it was loaded recently' do
+ Timecop.freeze do
+ allow(access).to receive(:loaded_at).and_return(5.minutes.ago)
+
+ expect(access).to be_loaded
+ end
+ end
+
+ it 'is `false` when there is no loading time' do
+ expect(access).not_to be_loaded
+ end
+
+ it 'is `false` when there the result was loaded a long time ago' do
+ Timecop.freeze do
+ allow(access).to receive(:loaded_at).and_return(2.weeks.ago)
+
+ expect(access).not_to be_loaded
+ end
+ end
+ end
+
+ describe 'load!' do
+ let(:fake_client) { double('ExternalAuthorization::Client') }
+ let(:fake_response) do
+ double(
+ 'Response',
+ 'successful?' => true,
+ 'valid?' => true,
+ 'reason' => nil
+ )
+ end
+
+ before do
+ allow(access).to receive(:load_from_cache)
+ allow(fake_client).to receive(:request_access).and_return(fake_response)
+ allow(Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client }
+ end
+
+ context 'when loading from the webservice' do
+ it 'loads from the webservice it the cache was empty' do
+ expect(access).to receive(:load_from_cache)
+ expect(access).to receive(:load_from_service).and_call_original
+
+ access.load!
+
+ expect(access).to be_loaded
+ end
+
+ it 'assigns the accessibility, reason and loaded_at' do
+ allow(fake_response).to receive(:successful?).and_return(false)
+ allow(fake_response).to receive(:reason).and_return('Inaccessible label')
+
+ access.load!
+
+ expect(access.reason).to eq('Inaccessible label')
+ expect(access).not_to have_access
+ expect(access.loaded_at).not_to be_nil
+ end
+
+ it 'returns itself' do
+ expect(access.load!).to eq(access)
+ end
+
+ it 'stores the result in redis' do
+ Timecop.freeze do
+ fake_cache = double
+ expect(fake_cache).to receive(:store).with(true, nil, Time.now)
+ expect(access).to receive(:cache).and_return(fake_cache)
+
+ access.load!
+ end
+ end
+
+ context 'when the request fails' do
+ before do
+ allow(fake_client).to receive(:request_access) do
+ raise ::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable')
+ end
+ end
+
+ it 'is loaded' do
+ access.load!
+
+ expect(access).to be_loaded
+ end
+
+ it 'assigns the correct accessibility, reason and loaded_at' do
+ access.load!
+
+ expect(access.reason).to eq('Service unavailable')
+ expect(access).not_to have_access
+ expect(access.loaded_at).not_to be_nil
+ end
+
+ it 'does not store the result in redis' do
+ fake_cache = double
+ expect(fake_cache).not_to receive(:store)
+ allow(access).to receive(:cache).and_return(fake_cache)
+
+ access.load!
+ end
+ end
+ end
+
+ context 'When loading from cache' do
+ let(:fake_cache) { double('ExternalAuthorization::Cache') }
+
+ before do
+ allow(access).to receive(:cache).and_return(fake_cache)
+ end
+
+ it 'does not load from the webservice' do
+ Timecop.freeze do
+ expect(fake_cache).to receive(:load).and_return([true, nil, Time.now])
+
+ expect(access).to receive(:load_from_cache).and_call_original
+ expect(access).not_to receive(:load_from_service)
+
+ access.load!
+ end
+ end
+
+ it 'loads from the webservice when the cached result was too old' do
+ Timecop.freeze do
+ expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago])
+
+ expect(access).to receive(:load_from_cache).and_call_original
+ expect(access).to receive(:load_from_service).and_call_original
+ allow(fake_cache).to receive(:store)
+
+ access.load!
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb
new file mode 100644
index 00000000000..58e7d626707
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization/cache_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
+ let(:user) { build_stubbed(:user) }
+ let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" }
+
+ subject(:cache) { described_class.new(user, 'dummy_label') }
+
+ def read_from_redis(key)
+ Gitlab::Redis::Cache.with do |redis|
+ redis.hget(cache_key, key)
+ end
+ end
+
+ def set_in_redis(key, value)
+ Gitlab::Redis::Cache.with do |redis|
+ redis.hmset(cache_key, key, value)
+ end
+ end
+
+ describe '#load' do
+ it 'reads stored info from redis' do
+ Timecop.freeze do
+ set_in_redis(:access, false)
+ set_in_redis(:reason, 'Access denied for now')
+ set_in_redis(:refreshed_at, Time.now)
+
+ access, reason, refreshed_at = cache.load
+
+ expect(access).to eq(false)
+ expect(reason).to eq('Access denied for now')
+ expect(refreshed_at).to be_within(1.second).of(Time.now)
+ end
+ end
+ end
+
+ describe '#store' do
+ it 'sets the values in redis' do
+ Timecop.freeze do
+ cache.store(true, 'the reason', Time.now)
+
+ expect(read_from_redis(:access)).to eq('true')
+ expect(read_from_redis(:reason)).to eq('the reason')
+ expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb
new file mode 100644
index 00000000000..fa18c1e56e8
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization/client_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization::Client do
+ let(:user) { build(:user, email: 'dummy_user@example.com') }
+ let(:dummy_url) { 'https://dummy.net/' }
+ subject(:client) { described_class.new(user, 'dummy_label') }
+
+ before do
+ stub_application_setting(external_authorization_service_url: dummy_url)
+ end
+
+ describe '#request_access' do
+ it 'performs requests to the configured endpoint' do
+ expect(Excon).to receive(:post).with(dummy_url, any_args)
+
+ client.request_access
+ end
+
+ it 'adds the correct params for the user to the body of the request' do
+ expected_body = {
+ user_identifier: 'dummy_user@example.com',
+ project_classification_label: 'dummy_label'
+ }.to_json
+ expect(Excon).to receive(:post)
+ .with(dummy_url, hash_including(body: expected_body))
+
+ client.request_access
+ end
+
+ it 'respects the the timeout' do
+ stub_application_setting(
+ external_authorization_service_timeout: 3
+ )
+
+ expect(Excon).to receive(:post).with(dummy_url,
+ hash_including(
+ connect_timeout: 3,
+ read_timeout: 3,
+ write_timeout: 3
+ ))
+
+ client.request_access
+ end
+
+ it 'adds the mutual tls params when they are present' do
+ stub_application_setting(
+ external_auth_client_cert: 'the certificate data',
+ external_auth_client_key: 'the key data',
+ external_auth_client_key_pass: 'open sesame'
+ )
+ expected_params = {
+ client_cert_data: 'the certificate data',
+ client_key_data: 'the key data',
+ client_key_pass: 'open sesame'
+ }
+
+ expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params))
+
+ client.request_access
+ end
+
+ it 'returns an expected response' do
+ expect(Excon).to receive(:post)
+
+ expect(client.request_access)
+ .to be_kind_of(::Gitlab::ExternalAuthorization::Response)
+ end
+
+ it 'wraps exceptions if the request fails' do
+ expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') }
+
+ expect { client.request_access }
+ .to raise_error(::Gitlab::ExternalAuthorization::RequestFailed)
+ end
+
+ describe 'for ldap users' do
+ let(:user) do
+ create(:omniauth_user,
+ email: 'dummy_user@example.com',
+ extern_uid: 'external id',
+ provider: 'ldapprovider')
+ end
+
+ it 'includes the ldap dn for ldap users' do
+ expected_body = {
+ user_identifier: 'dummy_user@example.com',
+ project_classification_label: 'dummy_label',
+ user_ldap_dn: 'external id'
+ }.to_json
+ expect(Excon).to receive(:post)
+ .with(dummy_url, hash_including(body: expected_body))
+
+ client.request_access
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb
new file mode 100644
index 00000000000..81f1b2390e6
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization/logger_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization::Logger do
+ let(:request_time) { Time.parse('2018-03-26 20:22:15') }
+
+ def fake_access(has_access, user, load_type = :request)
+ access = double('access')
+ allow(access).to receive_messages(user: user,
+ has_access?: has_access,
+ loaded_at: request_time,
+ label: 'dummy_label',
+ load_type: load_type)
+
+ access
+ end
+
+ describe '.log_access' do
+ it 'logs a nice message for an access request' do
+ expected_message = "GRANTED admin@example.com access to 'dummy_label' (the/project/path)"
+ fake_access = fake_access(true, build(:user, email: 'admin@example.com'))
+
+ expect(described_class).to receive(:info).with(expected_message)
+
+ described_class.log_access(fake_access, 'the/project/path')
+ end
+
+ it 'does not trip without a project path' do
+ expected_message = "DENIED admin@example.com access to 'dummy_label'"
+ fake_access = fake_access(false, build(:user, email: 'admin@example.com'))
+
+ expect(described_class).to receive(:info).with(expected_message)
+
+ described_class.log_access(fake_access, nil)
+ end
+
+ it 'adds the load time for cached accesses' do
+ expected_message = "DENIED admin@example.com access to 'dummy_label' - cache #{request_time}"
+ fake_access = fake_access(false, build(:user, email: 'admin@example.com'), :cache)
+
+ expect(described_class).to receive(:info).with(expected_message)
+
+ described_class.log_access(fake_access, nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb
new file mode 100644
index 00000000000..43211043eca
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization/response_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization::Response do
+ let(:excon_response) { double }
+ subject(:response) { described_class.new(excon_response) }
+
+ describe '#valid?' do
+ it 'is valid for 200, 401, and 403 responses' do
+ [200, 401, 403].each do |status|
+ allow(excon_response).to receive(:status).and_return(status)
+
+ expect(response).to be_valid
+ end
+ end
+
+ it "is invalid for other statuses" do
+ expect(excon_response).to receive(:status).and_return(500)
+
+ expect(response).not_to be_valid
+ end
+ end
+
+ describe '#reason' do
+ it 'returns a reason if it was included in the response body' do
+ expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json)
+
+ expect(response.reason).to eq('Not authorized')
+ end
+
+ it 'returns nil when there was no body' do
+ expect(excon_response).to receive(:body).and_return('')
+
+ expect(response.reason).to eq(nil)
+ end
+ end
+
+ describe '#successful?' do
+ it 'is `true` if the status is 200' do
+ allow(excon_response).to receive(:status).and_return(200)
+
+ expect(response).to be_successful
+ end
+
+ it 'is `false` if the status is 401 or 403' do
+ [401, 403].each do |status|
+ allow(excon_response).to receive(:status).and_return(status)
+
+ expect(response).not_to be_successful
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb
new file mode 100644
index 00000000000..7394fbfe0ce
--- /dev/null
+++ b/spec/lib/gitlab/external_authorization_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::ExternalAuthorization, :request_store do
+ include ExternalAuthorizationServiceHelpers
+
+ let(:user) { build(:user) }
+ let(:label) { 'dummy_label' }
+
+ describe '#access_allowed?' do
+ it 'is always true when the feature is disabled' do
+ # Not using `stub_application_setting` because the method is prepended in
+ # `EE::ApplicationSetting` which breaks when using `any_instance`
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/33587
+ expect(::Gitlab::CurrentSettings.current_application_settings)
+ .to receive(:external_authorization_service_enabled) { false }
+
+ expect(described_class).not_to receive(:access_for_user_to_label)
+
+ expect(described_class.access_allowed?(user, label)).to be_truthy
+ end
+ end
+
+ describe '#rejection_reason' do
+ it 'is always nil when the feature is disabled' do
+ expect(::Gitlab::CurrentSettings.current_application_settings)
+ .to receive(:external_authorization_service_enabled) { false }
+
+ expect(described_class).not_to receive(:access_for_user_to_label)
+
+ expect(described_class.rejection_reason(user, label)).to be_nil
+ end
+ end
+
+ describe '#access_for_user_to_label' do
+ it 'only loads the access once per request' do
+ enable_external_authorization_service_check
+
+ expect(::Gitlab::ExternalAuthorization::Access)
+ .to receive(:new).with(user, label).once.and_call_original
+
+ 2.times { described_class.access_for_user_to_label(user, label, nil) }
+ end
+
+ it 'logs the access request once per request' do
+ expect(::Gitlab::ExternalAuthorization::Logger)
+ .to receive(:log_access)
+ .with(an_instance_of(::Gitlab::ExternalAuthorization::Access),
+ 'the/project/path')
+ .once
+
+ 2.times { described_class.access_for_user_to_label(user, label, 'the/project/path') }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
index af12e13d36d..c81cb83d9f4 100644
--- a/spec/lib/gitlab/fake_application_settings_spec.rb
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -1,32 +1,33 @@
require 'spec_helper'
describe Gitlab::FakeApplicationSettings do
- let(:defaults) { { password_authentication_enabled_for_web: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } }
+ let(:defaults) do
+ described_class.defaults.merge(
+ foobar: 'asdf',
+ 'test?' => 123
+ )
+ end
- subject { described_class.new(defaults) }
+ let(:setting) { described_class.new(defaults) }
it 'wraps OpenStruct variables properly' do
- expect(subject.password_authentication_enabled_for_web).to be_falsey
- expect(subject.signup_enabled).to be_truthy
- expect(subject.foobar).to eq('asdf')
+ expect(setting.password_authentication_enabled_for_web).to be_truthy
+ expect(setting.signup_enabled).to be_truthy
+ expect(setting.foobar).to eq('asdf')
end
it 'defines predicate methods' do
- expect(subject.password_authentication_enabled_for_web?).to be_falsey
- expect(subject.signup_enabled?).to be_truthy
- end
-
- it 'predicate method changes when value is updated' do
- subject.password_authentication_enabled_for_web = true
-
- expect(subject.password_authentication_enabled_for_web?).to be_truthy
+ expect(setting.password_authentication_enabled_for_web?).to be_truthy
+ expect(setting.signup_enabled?).to be_truthy
end
it 'does not define a predicate method' do
- expect(subject.foobar?).to be_nil
+ expect(setting.foobar?).to be_nil
end
it 'does not override an existing predicate method' do
- expect(subject.test?).to eq(123)
+ expect(setting.test?).to eq(123)
end
+
+ it_behaves_like 'application settings examples'
end
diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb
index 49a423191bb..dce56bbd2c4 100644
--- a/spec/lib/gitlab/favicon_spec.rb
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Favicon, :request_store do
expect(described_class.main).to match_asset_path '/assets/favicon.png'
end
- it 'has blue favicon for development' do
+ it 'has blue favicon for development', unless: Gitlab.ee? do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
expect(described_class.main).to match_asset_path '/assets/favicon-blue.png'
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index a1b5cea88c0..1c24244c3a6 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Git::Blob, :seed_helper do
end
end
- describe '.find' do
+ shared_examples '.find' do
context 'nil path' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) }
@@ -128,6 +128,20 @@ describe Gitlab::Git::Blob, :seed_helper do
end
end
+ describe '.find with Gitaly enabled' do
+ it_behaves_like '.find'
+ end
+
+ describe '.find with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original
+
+ described_class.find(repository, SeedRepo::Commit::ID, 'files/images/6049019_460s.jpg')
+ end
+
+ it_behaves_like '.find'
+ end
+
describe '.raw' do
let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
let(:bad_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::BigCommit::ID) }
@@ -327,7 +341,7 @@ describe Gitlab::Git::Blob, :seed_helper do
it { expect(blob.mode).to eq("100755") }
end
- context 'file with Chinese text' do
+ context 'file with Japanese text' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/テスト.txt") }
it { expect(blob.name).to eq("テスト.txt") }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 2611ebed25b..25052a79916 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -112,14 +112,14 @@ describe Gitlab::Git::Commit, :seed_helper do
end
context 'Class methods' do
- describe '.find' do
- it "should return first head commit if without params" do
+ shared_examples '.find' do
+ it "returns first head commit if without params" do
expect(described_class.last(repository).id).to eq(
rugged_repo.head.target.oid
)
end
- it "should return valid commit" do
+ it "returns valid commit" do
expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit
end
@@ -127,21 +127,21 @@ describe Gitlab::Git::Commit, :seed_helper do
expect(described_class.find(repository, SeedRepo::Commit::ID).parent_ids).to be_an(Array)
end
- it "should return valid commit for tag" do
+ it "returns valid commit for tag" do
expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
end
- it "should return nil for non-commit ids" do
+ it "returns nil for non-commit ids" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
expect(described_class.find(repository, blob.id)).to be_nil
end
- it "should return nil for parent of non-commit object" do
+ it "returns nil for parent of non-commit object" do
blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb")
expect(described_class.find(repository, "#{blob.id}^")).to be_nil
end
- it "should return nil for nonexisting ids" do
+ it "returns nil for nonexisting ids" do
expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil
end
@@ -154,6 +154,20 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ describe '.find with Gitaly enabled' do
+ it_should_behave_like '.find'
+ end
+
+ describe '.find with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original
+
+ described_class.find(repository, SeedRepo::Commit::ID)
+ end
+
+ it_should_behave_like '.find'
+ end
+
describe '.last_for_path' do
context 'no path' do
subject { described_class.last_for_path(repository, 'master') }
@@ -314,7 +328,7 @@ describe Gitlab::Git::Commit, :seed_helper do
end
describe '.find_all' do
- it 'should return a return a collection of commits' do
+ it 'returns a return a collection of commits' do
commits = described_class.find_all(repository)
expect(commits).to all( be_a_kind_of(described_class) )
@@ -366,7 +380,32 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
- describe '#batch_by_oid' do
+ shared_examples '.batch_by_oid' do
+ context 'with multiple OIDs' do
+ let(:oids) { [SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID] }
+
+ it 'returns multiple commits' do
+ commits = described_class.batch_by_oid(repository, oids)
+
+ expect(commits.count).to eq(2)
+ expect(commits).to all( be_a(Gitlab::Git::Commit) )
+ expect(commits.first.sha).to eq(SeedRepo::Commit::ID)
+ expect(commits.second.sha).to eq(SeedRepo::FirstCommit::ID)
+ end
+ end
+
+ context 'when oids is empty' do
+ it 'returns empty commits' do
+ commits = described_class.batch_by_oid(repository, [])
+
+ expect(commits.count).to eq(0)
+ end
+ end
+ end
+
+ describe '.batch_by_oid with Gitaly enabled' do
+ it_should_behave_like '.batch_by_oid'
+
context 'when oids is empty' do
it 'makes no Gitaly request' do
expect(Gitlab::GitalyClient).not_to receive(:call)
@@ -376,6 +415,16 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ describe '.batch_by_oid with Rugged enabled', :enable_rugged do
+ it_should_behave_like '.batch_by_oid'
+
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original
+
+ described_class.batch_by_oid(repository, [SeedRepo::Commit::ID])
+ end
+ end
+
shared_examples 'extracting commit signature' do
context 'when the commit is signed' do
let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
@@ -523,6 +572,18 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ describe '#gitaly_commit?' do
+ context 'when the commit data comes from gitaly' do
+ it { expect(commit.gitaly_commit?).to eq(true) }
+ end
+
+ context 'when the commit data comes from a Hash' do
+ let(:commit) { described_class.new(repository, sample_commit_hash) }
+
+ it { expect(commit.gitaly_commit?).to eq(false) }
+ end
+ end
+
describe '#has_zero_stats?' do
it { expect(commit.has_zero_stats?).to eq(false) }
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 1d22329b670..9ab669ad488 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -182,7 +182,7 @@ EOT
context "without default options" do
let(:filtered_options) { described_class.filter_diff_options(options) }
- it "should filter invalid options" do
+ it "filters invalid options" do
expect(filtered_options).not_to have_key(:invalid_opt)
end
end
@@ -193,16 +193,16 @@ EOT
described_class.filter_diff_options(options, default_options)
end
- it "should filter invalid options" do
+ it "filters invalid options" do
expect(filtered_options).not_to have_key(:invalid_opt)
expect(filtered_options).not_to have_key(:bad_opt)
end
- it "should merge with default options" do
+ it "merges with default options" do
expect(filtered_options).to have_key(:ignore_whitespace_change)
end
- it "should override default options" do
+ it "overrides default options" do
expect(filtered_options).to have_key(:max_files)
expect(filtered_options[:max_files]).to eq(100)
end
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
index 6fd2b33486b..de81dcd227d 100644
--- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Git::GitmodulesParser do
- it 'should parse a .gitmodules file correctly' do
+ it 'parses a .gitmodules file correctly' do
data = <<~GITMODULES
[submodule "vendor/libgit2"]
path = vendor/libgit2
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
index 0d5069568e1..ebeb7b7b633 100644
--- a/spec/lib/gitlab/git/object_pool_spec.rb
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Git::ObjectPool do
+ include RepoHelpers
+
let(:pool_repository) { create(:pool_repository) }
let(:source_repository) { pool_repository.source_project.repository }
@@ -76,4 +78,43 @@ describe Gitlab::Git::ObjectPool do
end
end
end
+
+ describe '#fetch' do
+ let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) }
+ let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) }
+ let(:commit_count) { source_repository.commit_count }
+
+ context "when the object's pool repository exists" do
+ it 'does not raise an error' do
+ expect { subject.fetch }.not_to raise_error
+ end
+ end
+
+ context "when the object's pool repository does not exist" do
+ before do
+ subject.delete
+ end
+
+ it "re-creates the object pool's repository" do
+ subject.fetch
+
+ expect(subject.repository.exists?).to be true
+ end
+
+ it 'does not raise an error' do
+ expect { subject.fetch }.not_to raise_error
+ end
+
+ it 'fetches objects from the source repository' do
+ new_commit_id = new_commit_edit_old_file(source_repository_rugged).oid
+
+ expect(subject.repository.exists?).to be false
+
+ subject.fetch
+
+ expect(subject.repository.commit_count('refs/remotes/origin/master')).to eq(commit_count)
+ expect(subject.repository.commit(new_commit_id).id).to eq(new_commit_id)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb
index 1b8be62dec6..cb030e38032 100644
--- a/spec/lib/gitlab/git/pre_receive_error_spec.rb
+++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb
@@ -1,9 +1,19 @@
require 'spec_helper'
describe Gitlab::Git::PreReceiveError do
- it 'makes its message HTML-friendly' do
- ex = described_class.new("hello\nworld\n")
+ Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix|
+ context "error messages prefixed with #{prefix}" do
+ it 'accepts only errors lines with the prefix' do
+ ex = described_class.new("#{prefix} Hello,\nworld!")
- expect(ex.message).to eq('hello<br>world<br>')
+ expect(ex.message).to eq('Hello,')
+ end
+
+ it 'makes its message HTML-friendly' do
+ ex = described_class.new("#{prefix} Hello,\n#{prefix} world!\n")
+
+ expect(ex.message).to eq('Hello,<br>world!')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb
index 7f9cc2bc9ec..7bba0107e58 100644
--- a/spec/lib/gitlab/git/repository_cleaner_spec.rb
+++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb
@@ -6,57 +6,62 @@ describe Gitlab::Git::RepositoryCleaner do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:head_sha) { repository.head_commit.id }
- let(:object_map_data) { "#{head_sha} #{'0' * 40}" }
+ let(:object_map_data) { "#{head_sha} #{Gitlab::Git::BLANK_SHA}" }
- subject(:cleaner) { described_class.new(repository.raw) }
+ let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] }
+ let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] }
- describe '#apply_bfg_object_map' do
- let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] }
- let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] }
+ subject(:cleaner) { described_class.new(repository.raw) }
+ shared_examples_for '#apply_bfg_object_map_stream' do
before do
(clean_refs + keep_refs).each { |ref| repository.create_ref(head_sha, ref) }
end
- context 'from StringIO' do
- let(:object_map) { StringIO.new(object_map_data) }
+ it 'removes internal references' do
+ entries = []
- it 'removes internal references' do
- cleaner.apply_bfg_object_map(object_map)
+ cleaner.apply_bfg_object_map_stream(object_map) do |rsp|
+ entries.concat(rsp.entries)
+ end
- aggregate_failures do
- clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy }
- keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy }
- end
+ aggregate_failures do
+ clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be(false) }
+ keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be(true) }
+
+ expect(entries).to contain_exactly(
+ Gitaly::ApplyBfgObjectMapStreamResponse::Entry.new(
+ type: :COMMIT,
+ old_oid: head_sha,
+ new_oid: Gitlab::Git::BLANK_SHA
+ )
+ )
end
end
+ end
- context 'from Gitlab::HttpIO' do
- let(:url) { 'http://example.com/bfg_object_map.txt' }
- let(:tempfile) { Tempfile.new }
- let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) }
+ describe '#apply_bfg_object_map_stream (from StringIO)' do
+ let(:object_map) { StringIO.new(object_map_data) }
- around do |example|
- begin
- tempfile.write(object_map_data)
- tempfile.close
+ include_examples '#apply_bfg_object_map_stream'
+ end
- example.run
- ensure
- tempfile.unlink
- end
- end
+ describe '#apply_bfg_object_map_stream (from Gitlab::HttpIO)' do
+ let(:url) { 'http://example.com/bfg_object_map.txt' }
+ let(:tempfile) { Tempfile.new }
+ let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) }
- it 'removes internal references' do
- stub_remote_url_200(url, tempfile.path)
+ around do |example|
+ tempfile.write(object_map_data)
+ tempfile.close
- cleaner.apply_bfg_object_map(object_map)
+ stub_remote_url_200(url, tempfile.path)
- aggregate_failures do
- clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy }
- keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy }
- end
- end
+ example.run
+ ensure
+ tempfile.unlink
end
+
+ include_examples '#apply_bfg_object_map_stream'
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8a9e78ba3c3..e72fb9c6fbc 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -3,6 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Repository, :seed_helper do
include Gitlab::EncodingHelper
+ include RepoHelpers
using RSpec::Parameterized::TableSyntax
shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
@@ -28,51 +29,6 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:storage_path) { TestEnv.repos_path }
let(:user) { build(:user) }
- describe '.create_hooks' do
- let(:repo_path) { File.join(storage_path, 'hook-test.git') }
- let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:target_hooks_dir) { Gitlab.config.gitlab_shell.hooks_path }
- let(:existing_target) { File.join(repo_path, 'foobar') }
-
- before do
- FileUtils.rm_rf(repo_path)
- FileUtils.mkdir_p(repo_path)
- end
-
- context 'hooks is a directory' do
- let(:existing_file) { File.join(hooks_dir, 'my-file') }
-
- before do
- FileUtils.mkdir_p(hooks_dir)
- FileUtils.touch(existing_file)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) }
- end
-
- context 'hooks is a valid symlink' do
- before do
- FileUtils.mkdir_p existing_target
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
-
- context 'hooks is a broken symlink' do
- before do
- FileUtils.rm_f(existing_target)
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
- end
-
describe "Respond to" do
subject { repository }
@@ -95,6 +51,12 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#create_repository' do
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :create_repository do
+ subject { repository.create_repository }
+ end
+ end
+
describe '#branch_names' do
subject { repository.branch_names }
@@ -152,13 +114,14 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:append_sha) { true }
let(:ref) { 'master' }
let(:format) { nil }
+ let(:path) { nil }
let(:expected_extension) { 'tar.gz' }
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
let(:expected_path) { File.join(storage_path, cache_key, expected_filename) }
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" }
- subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) }
+ subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) }
it 'sets CommitId to the commit SHA' do
expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID)
@@ -176,6 +139,14 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(metadata['ArchivePath']).to eq(expected_path)
end
+ context 'path is set' do
+ let(:path) { 'foo/bar' }
+
+ it 'appends the path to the prefix' do
+ expect(metadata['ArchivePrefix']).to eq("#{expected_prefix}-foo-bar")
+ end
+ end
+
context 'append_sha varies archive path and filename' do
where(:append_sha, :ref, :expected_prefix) do
sha = SeedRepo::LastCommit::ID
@@ -215,6 +186,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 }
end
+ describe '#object_directory_size' do
+ before do
+ allow(repository.gitaly_repository_client)
+ .to receive(:get_object_directory_size)
+ .and_return(2)
+ end
+
+ subject { repository.object_directory_size }
+
+ it { is_expected.to eq 2048 }
+ end
+
describe '#empty?' do
it { expect(repository).not_to be_empty }
end
@@ -441,20 +424,20 @@ describe Gitlab::Git::Repository, :seed_helper do
ensure_seeds
end
- it "should create a new branch" do
+ it "creates a new branch" do
expect(repository.create_branch('new_branch', 'master')).not_to be_nil
end
- it "should create a new branch with the right name" do
+ it "creates a new branch with the right name" do
expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
end
- it "should fail if we create an existing branch" do
+ it "fails if we create an existing branch" do
repository.create_branch('duplicated_branch', 'master')
expect {repository.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
+ it "fails if we create a branch from a non existing ref" do
expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
end
end
@@ -513,7 +496,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe "#refs_hash" do
subject { repository.refs_hash }
- it "should have as many entries as branches and tags" do
+ it "has as many entries as branches and tags" do
expected_refs = SeedRepo::Repo::BRANCHES + SeedRepo::Repo::TAGS
# We flatten in case a commit is pointed at by more than one branch and/or tag
expect(subject.values.flatten.size).to eq(expected_refs.size)
@@ -522,6 +505,13 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'has valid commit ids as keys' do
expect(subject.keys).to all( match(Commit::COMMIT_SHA_PATTERN) )
end
+
+ it 'does not error when dereferenced_target is nil' do
+ blob_id = repository.blob_at('master', 'README.md').id
+ repository_rugged.tags.create("refs/tags/blob-tag", blob_id)
+
+ expect { subject }.not_to raise_error
+ end
end
describe '#fetch_repository_as_mirror' do
@@ -604,11 +594,11 @@ describe Gitlab::Git::Repository, :seed_helper do
end
shared_examples 'search files by content' do
- it 'should have 2 items' do
+ it 'has 2 items' do
expect(search_results.size).to eq(2)
end
- it 'should have the correct matching line' do
+ it 'has the correct matching line' do
expect(search_results).to contain_exactly("search-files-by-content-branch:encoding/CHANGELOG\u00001\u0000search-files-by-content change\n",
"search-files-by-content-branch:anotherfile\u00001\u0000search-files-by-content change\n")
end
@@ -619,16 +609,6 @@ describe Gitlab::Git::Repository, :seed_helper do
repository.search_files_by_content('search-files-by-content', 'search-files-by-content-branch')
end
end
-
- it_should_behave_like 'search files by content' do
- let(:search_results) do
- repository.gitaly_repository_client.search_files_by_content(
- 'search-files-by-content-branch',
- 'search-files-by-content',
- chunked_response: false
- )
- end
- end
end
describe '#find_remote_root_ref' do
@@ -851,7 +831,7 @@ describe Gitlab::Git::Repository, :seed_helper do
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "should returns commits on or after that timestamp" do
+ it "returns commits on or after that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
@@ -864,7 +844,7 @@ describe Gitlab::Git::Repository, :seed_helper do
context "where provides 'before' timestamp" do
options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "should returns commits on or before that timestamp" do
+ it "returns commits on or before that timestamp" do
commits = repository.log(options)
expect(commits.size).to be > 0
@@ -1065,14 +1045,14 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#find_branch' do
- it 'should return a Branch for master' do
+ it 'returns a Branch for master' do
branch = repository.find_branch('master')
expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
expect(branch.name).to eq('master')
end
- it 'should handle non-existent branch' do
+ it 'handles non-existent branch' do
branch = repository.find_branch('this-is-garbage')
expect(branch).to eq(nil)
@@ -1698,12 +1678,48 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
+ # Workaround for https://github.com/libgit2/rugged/issues/785: If
+ # Gitaly changes .gitconfig while Rugged has the file loaded
+ # Rugged::Repository#each_key will report stale values unless a
+ # lookup is done first.
+ expect(repository_rugged.config['test.foo1']).to be_nil
config_keys = repository_rugged.config.each_key.to_a
expect(config_keys).not_to include('test.foo1')
expect(config_keys).not_to include('test.foo2')
end
end
+ describe '#merge_to_ref' do
+ let(:repository) { mutable_repository }
+ let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
+ let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:right_branch) { 'test-master' }
+ let(:target_ref) { 'refs/merge-requests/999/merge' }
+
+ before do
+ repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch)
+ end
+
+ def merge_to_ref
+ repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message')
+ end
+
+ it 'generates a commit in the target_ref' do
+ expect(repository.ref_exists?(target_ref)).to be(false)
+
+ commit_sha = merge_to_ref
+ ref_head = repository.commit(target_ref)
+
+ expect(commit_sha).to be_present
+ expect(repository.ref_exists?(target_ref)).to be(true)
+ expect(ref_head.id).to eq(commit_sha)
+ end
+
+ it 'does not change the right branch HEAD' do
+ expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target }
+ end
+ end
+
describe '#merge' do
let(:repository) { mutable_repository }
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
@@ -1910,13 +1926,6 @@ describe Gitlab::Git::Repository, :seed_helper do
expect { imported_repo.fsck }.not_to raise_exception
end
- it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(valid_bundle_path)
- hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
-
- expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
- end
-
it 'raises an error if the bundle is an attempted malicious payload' do
expect do
imported_repo.create_from_bundle(malicious_bundle_path)
@@ -1924,6 +1933,70 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#compare_source_branch' do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') }
+
+ context 'within same repository' do
+ it 'does not create a temp ref' do
+ expect(repository).not_to receive(:fetch_source_branch!)
+ expect(repository).not_to receive(:delete_refs)
+
+ compare = repository.compare_source_branch('master', repository, 'feature', straight: false)
+ expect(compare).to be_a(Gitlab::Git::Compare)
+ expect(compare.commits.count).to be > 0
+ end
+
+ it 'returns empty commits when source ref does not exist' do
+ compare = repository.compare_source_branch('master', repository, 'non-existent-branch', straight: false)
+
+ expect(compare.commits).to be_empty
+ end
+ end
+
+ context 'with different repositories' do
+ context 'when ref is known by source repo, but not by target' do
+ before do
+ mutable_repository.write_ref('another-branch', 'feature')
+ end
+
+ it 'creates temp ref' do
+ expect(repository).not_to receive(:fetch_source_branch!)
+ expect(repository).not_to receive(:delete_refs)
+
+ compare = repository.compare_source_branch('master', mutable_repository, 'another-branch', straight: false)
+ expect(compare).to be_a(Gitlab::Git::Compare)
+ expect(compare.commits.count).to be > 0
+ end
+ end
+
+ context 'when ref is known by source and target repos' do
+ before do
+ mutable_repository.write_ref('another-branch', 'feature')
+ repository.write_ref('another-branch', 'feature')
+ end
+
+ it 'does not create a temp ref' do
+ expect(repository).not_to receive(:fetch_source_branch!)
+ expect(repository).not_to receive(:delete_refs)
+
+ compare = repository.compare_source_branch('master', mutable_repository, 'another-branch', straight: false)
+ expect(compare).to be_a(Gitlab::Git::Compare)
+ expect(compare.commits.count).to be > 0
+ end
+ end
+
+ context 'when ref is unknown by source repo' do
+ it 'returns nil when source ref does not exist' do
+ expect(repository).to receive(:fetch_source_branch!).and_call_original
+ expect(repository).to receive(:delete_refs).and_call_original
+
+ compare = repository.compare_source_branch('master', mutable_repository, 'non-existent-branch', straight: false)
+ expect(compare).to be_nil
+ end
+ end
+ end
+ end
+
describe '#checksum' do
it 'calculates the checksum for non-empty repo' do
expect(repository.checksum).to eq '51d0a9662681f93e1fee547a6b7ba2bcaf716059'
@@ -2097,86 +2170,48 @@ describe Gitlab::Git::Repository, :seed_helper do
repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
end
- # Build the options hash that's passed to Rugged::Commit#create
- def commit_options(repo, index, target, ref, message)
- options = {}
- options[:tree] = index.write_tree(repo)
- options[:author] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:committer] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:message] ||= message
- options[:parents] = repo.empty? ? [] : [target].compact
- options[:update_ref] = ref
-
- options
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of CHANGELOG with a single new line of text.
- def new_commit_edit_old_file(repo)
- oid = repo.write("I replaced the changelog with this text", :blob)
- index = repo.index
- index.read_tree(repo.head.target.tree)
- index.add(path: "CHANGELOG", oid: oid, mode: 0100644)
-
- options = commit_options(
- repo,
- index,
- repo.head.target,
- "HEAD",
- "Edit CHANGELOG in its original location"
- )
-
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of the specified file_path with new text.
- def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head)
- oid = repo.write(text, :blob)
- index = repo.index
- index.read_tree(branch.target.tree)
- index.add(path: file_path, oid: oid, mode: 0100644)
- options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message)
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of encoding/CHANGELOG with new text.
- def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text)
- branch = repo.branches[branch_name]
- new_commit_edit_new_file(repo, file_path, commit_message, text, branch)
- end
-
- # Writes a new commit to the repo and returns a Rugged::Commit. Moves the
- # CHANGELOG file to the encoding/ directory.
- def new_commit_move_file(repo)
- blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid]
- file_content = repo.lookup(blob_oid).content
- oid = repo.write(file_content, :blob)
- index = repo.index
- index.read_tree(repo.head.target.tree)
- index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
- index.remove("CHANGELOG")
-
- options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/")
-
- sha = Rugged::Commit.create(repo, options)
- repo.lookup(sha)
- end
-
def refs(dir)
IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line|
line.split("\t").last
end
end
+
+ describe '#disconnect_alternates' do
+ let(:project) { create(:project, :repository) }
+ let(:pool_repository) { create(:pool_repository) }
+ let(:repository) { project.repository }
+ let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
+ let(:object_pool) { pool_repository.object_pool }
+ let(:object_pool_path) { File.join(TestEnv.repos_path, object_pool.repository.relative_path) }
+ let(:object_pool_rugged) { Rugged::Repository.new(object_pool_path) }
+
+ before do
+ object_pool.create
+ end
+
+ it 'does not raise an error when disconnecting a non-linked repository' do
+ expect { repository.disconnect_alternates }.not_to raise_error
+ end
+
+ it 'removes the alternates file' do
+ object_pool.link(repository)
+
+ alternates_file = File.join(repository_path, "objects", "info", "alternates")
+ expect(File.exist?(alternates_file)).to be_truthy
+
+ repository.disconnect_alternates
+
+ expect(File.exist?(alternates_file)).to be_falsey
+ end
+
+ it 'can still access objects in the object pool' do
+ object_pool.link(repository)
+ new_commit = new_commit_edit_old_file(object_pool_rugged)
+ expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid)
+
+ repository.disconnect_alternates
+
+ expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid)
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 4a4d69490a3..7e169cfe270 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Tree, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
- context :repo do
+ shared_examples :repo do
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
it { expect(tree).to be_kind_of Array }
@@ -12,6 +12,19 @@ describe Gitlab::Git::Tree, :seed_helper do
it { expect(tree.select(&:file?).size).to eq(10) }
it { expect(tree.select(&:submodule?).size).to eq(2) }
+ it 'returns an empty array when called with an invalid ref' do
+ expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
+ end
+
+ it 'returns a list of tree objects' do
+ entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true)
+
+ expect(entries.map(&:path)).to include('files/html',
+ 'files/markdown/ruby-style-guide.md')
+ expect(entries.count).to be >= 10
+ expect(entries).to all(be_a(Gitlab::Git::Tree))
+ end
+
describe '#dir?' do
let(:dir) { tree.select(&:dir?).first }
@@ -20,8 +33,8 @@ describe Gitlab::Git::Tree, :seed_helper do
it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(dir.name).to eq('encoding') }
it { expect(dir.path).to eq('encoding') }
- it { expect(dir.flat_path).to eq('encoding') }
it { expect(dir.mode).to eq('40000') }
+ it { expect(dir.flat_path).to eq('encoding') }
context :subdir do
let(:subdir) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files').first }
@@ -44,6 +57,51 @@ describe Gitlab::Git::Tree, :seed_helper do
it { expect(subdir_file.path).to eq('files/ruby/popen.rb') }
it { expect(subdir_file.flat_path).to eq('files/ruby/popen.rb') }
end
+
+ context :flat_path do
+ let(:filename) { 'files/flat/path/correct/content.txt' }
+ let(:oid) { create_file(filename) }
+ let(:subdir_file) { Gitlab::Git::Tree.where(repository, oid, 'files/flat').first }
+ let(:repository_rugged) { Rugged::Repository.new(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH)) }
+
+ it { expect(subdir_file.flat_path).to eq('files/flat/path/correct') }
+ end
+
+ def create_file(path)
+ oid = repository_rugged.write('test', :blob)
+ index = repository_rugged.index
+ index.add(path: filename, oid: oid, mode: 0100644)
+
+ options = commit_options(
+ repository_rugged,
+ index,
+ repository_rugged.head.target,
+ 'HEAD',
+ 'Add new file')
+
+ Rugged::Commit.create(repository_rugged, options)
+ end
+
+ # Build the options hash that's passed to Rugged::Commit#create
+ def commit_options(repo, index, target, ref, message)
+ options = {}
+ options[:tree] = index.write_tree(repo)
+ options[:author] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:committer] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:message] ||= message
+ options[:parents] = repo.empty? ? [] : [target].compact
+ options[:update_ref] = ref
+
+ options
+ end
end
describe '#file?' do
@@ -79,9 +137,17 @@ describe Gitlab::Git::Tree, :seed_helper do
end
end
- describe '#where' do
- it 'returns an empty array when called with an invalid ref' do
- expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
+ describe '.where with Gitaly enabled' do
+ it_behaves_like :repo
+ end
+
+ describe '.where with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged).to receive(:lookup).with(SeedRepo::Commit::ID)
+
+ described_class.where(repository, SeedRepo::Commit::ID, 'files', false)
end
+
+ it_behaves_like :repo
end
end
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
index ded5d7576df..1e577392949 100644
--- a/spec/lib/gitlab/git/wiki_spec.rb
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -21,13 +21,13 @@ describe Gitlab::Git::Wiki do
end
it 'returns all the pages' do
- expect(subject.pages.count).to eq(2)
- expect(subject.pages.first.title).to eq 'page1'
- expect(subject.pages.last.title).to eq 'page2'
+ expect(subject.list_pages.count).to eq(2)
+ expect(subject.list_pages.first.title).to eq 'page1'
+ expect(subject.list_pages.last.title).to eq 'page2'
end
it 'returns only one page' do
- pages = subject.pages(limit: 1)
+ pages = subject.list_pages(limit: 1)
expect(pages.count).to eq(1)
expect(pages.first.title).to eq 'page1'
@@ -62,8 +62,8 @@ describe Gitlab::Git::Wiki do
subject.delete_page('*', commit_details('whatever'))
- expect(subject.pages.count).to eq 1
- expect(subject.pages.first.title).to eq 'page1'
+ expect(subject.list_pages.count).to eq 1
+ expect(subject.list_pages.first.title).to eq 'page1'
end
end
@@ -87,7 +87,7 @@ describe Gitlab::Git::Wiki do
with_them do
subject { wiki.preview_slug(title, format) }
- let(:gitaly_slug) { wiki.pages.first }
+ let(:gitaly_slug) { wiki.list_pages.first }
it { is_expected.to eq(expected_slug) }
@@ -96,7 +96,7 @@ describe Gitlab::Git::Wiki do
create_page(title, 'content', format: format)
- gitaly_slug = wiki.pages.first.url_path
+ gitaly_slug = wiki.list_pages.first.url_path
is_expected.to eq(gitaly_slug)
end
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index 3ab04a1c46d..b63389af29f 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -1,31 +1,69 @@
require 'spec_helper'
describe Gitlab::GitRefValidator do
- it { expect(described_class.validate('feature/new')).to be_truthy }
- it { expect(described_class.validate('implement_@all')).to be_truthy }
- it { expect(described_class.validate('my_new_feature')).to be_truthy }
- it { expect(described_class.validate('my-branch')).to be_truthy }
- it { expect(described_class.validate('#1')).to be_truthy }
- it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy }
- it { expect(described_class.validate('feature/~new/')).to be_falsey }
- it { expect(described_class.validate('feature/^new/')).to be_falsey }
- it { expect(described_class.validate('feature/:new/')).to be_falsey }
- it { expect(described_class.validate('feature/?new/')).to be_falsey }
- it { expect(described_class.validate('feature/*new/')).to be_falsey }
- it { expect(described_class.validate('feature/[new/')).to be_falsey }
- it { expect(described_class.validate('feature/new/')).to be_falsey }
- it { expect(described_class.validate('feature/new.')).to be_falsey }
- it { expect(described_class.validate('feature\@{')).to be_falsey }
- it { expect(described_class.validate('feature\new')).to be_falsey }
- it { expect(described_class.validate('feature//new')).to be_falsey }
- it { expect(described_class.validate('feature new')).to be_falsey }
- it { expect(described_class.validate('refs/heads/')).to be_falsey }
- it { expect(described_class.validate('refs/remotes/')).to be_falsey }
- it { expect(described_class.validate('refs/heads/feature')).to be_falsey }
- it { expect(described_class.validate('refs/remotes/origin')).to be_falsey }
- it { expect(described_class.validate('-')).to be_falsey }
- it { expect(described_class.validate('-branch')).to be_falsey }
- it { expect(described_class.validate('.tag')).to be_falsey }
- it { expect(described_class.validate('my branch')).to be_falsey }
- it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey }
+ using RSpec::Parameterized::TableSyntax
+
+ context '.validate' do
+ it { expect(described_class.validate('feature/new')).to be true }
+ it { expect(described_class.validate('implement_@all')).to be true }
+ it { expect(described_class.validate('my_new_feature')).to be true }
+ it { expect(described_class.validate('my-branch')).to be true }
+ it { expect(described_class.validate('#1')).to be true }
+ it { expect(described_class.validate('feature/refs/heads/foo')).to be true }
+ it { expect(described_class.validate('feature/~new/')).to be false }
+ it { expect(described_class.validate('feature/^new/')).to be false }
+ it { expect(described_class.validate('feature/:new/')).to be false }
+ it { expect(described_class.validate('feature/?new/')).to be false }
+ it { expect(described_class.validate('feature/*new/')).to be false }
+ it { expect(described_class.validate('feature/[new/')).to be false }
+ it { expect(described_class.validate('feature/new/')).to be false }
+ it { expect(described_class.validate('feature/new.')).to be false }
+ it { expect(described_class.validate('feature\@{')).to be false }
+ it { expect(described_class.validate('feature\new')).to be false }
+ it { expect(described_class.validate('feature//new')).to be false }
+ it { expect(described_class.validate('feature new')).to be false }
+ it { expect(described_class.validate('refs/heads/')).to be false }
+ it { expect(described_class.validate('refs/remotes/')).to be false }
+ it { expect(described_class.validate('refs/heads/feature')).to be false }
+ it { expect(described_class.validate('refs/remotes/origin')).to be false }
+ it { expect(described_class.validate('-')).to be false }
+ it { expect(described_class.validate('-branch')).to be false }
+ it { expect(described_class.validate('+foo:bar')).to be false }
+ it { expect(described_class.validate('foo:bar')).to be false }
+ it { expect(described_class.validate('.tag')).to be false }
+ it { expect(described_class.validate('my branch')).to be false }
+ it { expect(described_class.validate("\xA0\u0000\xB0")).to be false }
+ end
+
+ context '.validate_merge_request_branch' do
+ it { expect(described_class.validate_merge_request_branch('HEAD')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/new')).to be true }
+ it { expect(described_class.validate_merge_request_branch('implement_@all')).to be true }
+ it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be true }
+ it { expect(described_class.validate_merge_request_branch('my-branch')).to be true }
+ it { expect(described_class.validate_merge_request_branch('#1')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/new.')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature\@{')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature\new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature//new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be true }
+ it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('-')).to be false }
+ it { expect(described_class.validate_merge_request_branch('-branch')).to be false }
+ it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be false }
+ it { expect(described_class.validate_merge_request_branch('foo:bar')).to be false }
+ it { expect(described_class.validate_merge_request_branch('.tag')).to be false }
+ it { expect(described_class.validate_merge_request_branch('my branch')).to be false }
+ it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be false }
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
index 369deff732a..c42332dc27b 100644
--- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
@@ -6,14 +6,14 @@ describe Gitlab::GitalyClient::CleanupService do
let(:relative_path) { project.disk_path + '.git' }
let(:client) { described_class.new(project.repository) }
- describe '#apply_bfg_object_map' do
- it 'sends an apply_bfg_object_map message' do
+ describe '#apply_bfg_object_map_stream' do
+ it 'sends an apply_bfg_object_map_stream message' do
expect_any_instance_of(Gitaly::CleanupService::Stub)
- .to receive(:apply_bfg_object_map)
+ .to receive(:apply_bfg_object_map_stream)
.with(kind_of(Enumerator), kind_of(Hash))
- .and_return(double)
+ .and_return([])
- client.apply_bfg_object_map(StringIO.new)
+ client.apply_bfg_object_map_stream(StringIO.new)
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 d7bd757149d..6d6107ca3e7 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -221,6 +221,21 @@ describe Gitlab::GitalyClient::CommitService do
expect(commit).to eq(commit_dbl)
end
end
+
+ context 'when caching of the ref name is enabled' do
+ it 'returns a cached commit' do
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl))
+
+ commit = nil
+ 2.times do
+ ::Gitlab::GitalyClient.allow_ref_name_caching do
+ commit = described_class.new(repository).find_commit('master')
+ end
+ end
+
+ expect(commit).to eq(commit_dbl)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
index 149b7ec5bb0..0e0c3d329b5 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -43,4 +43,24 @@ describe Gitlab::GitalyClient::ObjectPoolService do
end
end
end
+
+ describe '#fetch' do
+ before do
+ subject.delete
+ end
+
+ it 'restores the pool repository objects' do
+ subject.fetch(project.repository)
+
+ expect(object_pool.repository.exists?).to be(true)
+ end
+
+ context 'when called twice' do
+ it "doesn't raise an error" do
+ subject.delete
+
+ expect { subject.fetch(project.repository) }.not_to raise_error
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index b37fe2686b6..7579a6577b9 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -39,7 +39,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserCreateBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserCreateBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -80,7 +80,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -117,7 +117,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -175,7 +175,7 @@ describe Gitlab::GitalyClient::OperationService do
shared_examples 'cherry pick and revert errors' do
context 'when a pre_receive_error is present' do
- let(:response) { response_class.new(pre_receive_error: "something failed") }
+ let(:response) { response_class.new(pre_receive_error: "GitLab: something failed") }
it 'raises a PreReceiveError' do
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
@@ -313,7 +313,7 @@ describe Gitlab::GitalyClient::OperationService do
end
context 'when a pre_receive_error is present' do
- let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "something failed") }
+ let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "GitLab: something failed") }
it 'raises a PreReceiveError' do
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 400d426c949..0bb6e582159 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -89,6 +89,16 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#list_new_blobs' do
+ it 'raises DeadlineExceeded when timeout is too small' do
+ newrev = '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51'
+
+ expect do
+ client.list_new_blobs(newrev, dynamic_timeout: 0.001)
+ end.to raise_error(GRPC::DeadlineExceeded)
+ end
+ end
+
describe '#local_branches' do
it 'sends a find_local_branches message' do
expect_any_instance_of(Gitaly::RefService::Stub)
@@ -155,4 +165,15 @@ describe Gitlab::GitalyClient::RefService do
client.delete_refs(except_with_prefixes: prefixes)
end
end
+
+ describe '#pack_refs' do
+ it 'sends a pack_refs message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:pack_refs)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(:pack_refs_response))
+
+ client.pack_refs
+ 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
index 46ca2340389..a3808adb376 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -73,6 +73,17 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#get_object_directory_size' do
+ it 'sends a get_object_directory_size message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:get_object_directory_size)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(size: 0)
+
+ client.get_object_directory_size
+ end
+ end
+
describe '#apply_gitattributes' do
let(:revision) { 'master' }
@@ -231,4 +242,34 @@ describe Gitlab::GitalyClient::RepositoryService do
client.raw_changes_between('deadbeef', 'deadpork')
end
end
+
+ describe '#disconnect_alternates' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
+ let(:pool_repository) { create(:pool_repository) }
+ let(:object_pool) { pool_repository.object_pool }
+ let(:object_pool_service) { Gitlab::GitalyClient::ObjectPoolService.new(object_pool) }
+
+ before do
+ object_pool_service.create(repository)
+ object_pool_service.link_repository(repository)
+ end
+
+ it 'deletes the alternates file' do
+ repository.disconnect_alternates
+
+ alternates_file = File.join(repository_path, "objects", "info", "alternates")
+
+ expect(File.exist?(alternates_file)).to be_falsey
+ end
+
+ context 'when called twice' do
+ it "doesn't raise an error" do
+ repository.disconnect_alternates
+
+ expect { repository.disconnect_alternates }.not_to raise_error
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
index c89913ec8e9..bb10be2a4dc 100644
--- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
@@ -26,4 +26,14 @@ describe Gitlab::GitalyClient::StorageSettings do
end
end
end
+
+ describe '.disk_access_denied?' do
+ it 'return false when Rugged is enabled', :enable_rugged do
+ expect(described_class.disk_access_denied?).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(described_class.disk_access_denied?).to be_truthy
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
index d82c9c28da0..4fa8e97aca0 100644
--- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
@@ -46,7 +46,7 @@ describe Gitlab::GitalyClient::WikiService do
end
end
- describe '#get_all_pages' do
+ describe '#load_all_pages' do
let(:page_2_info) { { title: 'My Page 2', raw_data: 'c', version: page_version } }
let(:response) do
[
@@ -63,7 +63,7 @@ describe Gitlab::GitalyClient::WikiService do
let(:wiki_page_2) { subject[1].first }
let(:wiki_page_2_version) { subject[1].last }
- subject { client.get_all_pages }
+ subject { client.load_all_pages }
it 'sends a wiki_get_all_pages message' do
expect_any_instance_of(Gitaly::WikiService::Stub)
@@ -99,7 +99,7 @@ describe Gitlab::GitalyClient::WikiService do
end
context 'with limits' do
- subject { client.get_all_pages(limit: 1) }
+ subject { client.load_all_pages(limit: 1) }
it 'sends a request with the limit' do
expect_any_instance_of(Gitaly::WikiService::Stub)
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index cf12baf1a93..da1eb0c2618 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -142,6 +142,48 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.request_kwargs' do
+ context 'when catfile-cache feature is enabled' do
+ before do
+ stub_feature_flags('gitaly_catfile-cache': true)
+ end
+
+ it 'sets the gitaly-session-id in the metadata' do
+ results = described_class.request_kwargs('default', nil)
+ expect(results[:metadata]).to include('gitaly-session-id')
+ end
+
+ context 'when RequestStore is not enabled' do
+ it 'sets a different gitaly-session-id per request' do
+ gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']
+
+ expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).not_to eq(gitaly_session_id)
+ end
+ end
+
+ context 'when RequestStore is enabled', :request_store do
+ it 'sets the same gitaly-session-id on every outgoing request metadata' do
+ gitaly_session_id = described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']
+
+ 3.times do
+ expect(described_class.request_kwargs('default', nil)[:metadata]['gitaly-session-id']).to eq(gitaly_session_id)
+ end
+ end
+ end
+ end
+
+ context 'when catfile-cache feature is disabled' do
+ before do
+ stub_feature_flags({ 'gitaly_catfile-cache': false })
+ end
+
+ it 'does not set the gitaly-session-id in the metadata' do
+ results = described_class.request_kwargs('default', nil)
+ expect(results[:metadata]).not_to include('gitaly-session-id')
+ end
+ end
+ end
+
describe 'enforce_gitaly_request_limits?' do
def call_gitaly(count = 1)
(1..count).each do
@@ -149,11 +191,21 @@ describe Gitlab::GitalyClient do
end
end
- context 'when RequestStore is enabled', :request_store do
+ context 'when RequestStore is enabled and the maximum number of calls is not enforced by a feature flag', :request_store do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: false)
+ end
+
it 'allows up the maximum number of allowed calls' do
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
end
+ it 'allows the maximum number of calls to be exceeded if GITALY_DISABLE_REQUEST_LIMITS is set' do
+ stub_env('GITALY_DISABLE_REQUEST_LIMITS', 'true')
+
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
+ end
+
context 'when the maximum number of calls has been reached' do
before do
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
@@ -189,6 +241,32 @@ describe Gitlab::GitalyClient do
end
end
+ context 'in production and when RequestStore is enabled', :request_store do
+ before do
+ allow(Rails.env).to receive(:production?).and_return(true)
+ end
+
+ context 'when the maximum number of calls is enforced by a feature flag' do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: true)
+ end
+
+ it 'does not allow the maximum number of calls to be exceeded' do
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
+ end
+ end
+
+ context 'when the maximum number of calls is not enforced by a feature flag' do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: false)
+ end
+
+ it 'allows the maximum number of calls to be exceeded' do
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
+ end
+ end
+ end
+
context 'when RequestStore is not active' do
it 'does not raise errors when the maximum number of allowed calls is exceeded' do
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 2) }.not_to raise_error
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 7901ae005d9..dab5767ece1 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -98,6 +98,7 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach
description: 'This is my issue',
milestone_id: milestone.id,
state: :opened,
+ state_id: 1,
created_at: created_at,
updated_at: updated_at
},
@@ -127,6 +128,7 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach
description: "*Created by: alice*\n\nThis is my issue",
milestone_id: milestone.id,
state: :opened,
+ state_id: 1,
created_at: created_at,
updated_at: updated_at
},
diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
index b1cac3b6e46..120a07ff2b3 100644
--- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
+ let(:due_on) { Time.new(2017, 2, 1, 12, 00) }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
@@ -14,6 +15,20 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis
title: '1.0',
description: 'The first release',
state: 'open',
+ due_on: due_on,
+ created_at: created_at,
+ updated_at: updated_at
+ )
+ end
+
+ let(:milestone2) do
+ double(
+ :milestone,
+ number: 1,
+ title: '1.0',
+ description: 'The first release',
+ state: 'open',
+ due_on: nil,
created_at: created_at,
updated_at: updated_at
)
@@ -72,6 +87,7 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis
describe '#build' do
let(:milestone_hash) { importer.build(milestone) }
+ let(:milestone_hash2) { importer.build(milestone2) }
it 'returns the attributes of the milestone as a Hash' do
expect(milestone_hash).to be_an_instance_of(Hash)
@@ -98,6 +114,14 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis
expect(milestone_hash[:state]).to eq(:active)
end
+ it 'includes the due date' do
+ expect(milestone_hash[:due_date]).to eq(due_on.to_date)
+ end
+
+ it 'responds correctly to no due date value' do
+ expect(milestone_hash2[:due_date]).to be nil
+ end
+
it 'includes the created timestamp' do
expect(milestone_hash[:created_at]).to eq(created_at)
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 15e59718dce..6d614c6527a 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
let(:source_commit) { project.repository.commit('feature') }
let(:target_commit) { project.repository.commit('master') }
let(:milestone) { create(:milestone, project: project) }
+ let(:state) { :closed }
let(:pull_request) do
alice = Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice')
@@ -26,13 +27,13 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
source_repository_id: 400,
target_repository_id: 200,
source_repository_owner: 'alice',
- state: :closed,
+ state: state,
milestone_number: milestone.iid,
author: alice,
assignee: alice,
created_at: created_at,
updated_at: updated_at,
- merged_at: merged_at
+ merged_at: state == :closed && merged_at
)
end
@@ -92,6 +93,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
source_branch: 'github/fork/alice/feature',
target_branch: 'master',
state: :merged,
+ state_id: 3,
milestone_id: milestone.id,
author_id: user.id,
assignee_id: user.id,
@@ -137,6 +139,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
source_branch: 'github/fork/alice/feature',
target_branch: 'master',
state: :merged,
+ state_id: 3,
milestone_id: milestone.id,
author_id: project.creator_id,
assignee_id: user.id,
@@ -183,6 +186,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
source_branch: 'master-42',
target_branch: 'master',
state: :merged,
+ state_id: 3,
milestone_id: milestone.id,
author_id: user.id,
assignee_id: user.id,
@@ -260,53 +264,63 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
end
it 'does not create the source branch if merge request is merged' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
end
- it 'creates the source branch if merge request is open' do
- mr, exists = importer.create_merge_request
- mr.state = 'opened'
- mr.save
+ context 'when merge request is open' do
+ let(:state) { :opened }
- importer.insert_git_data(mr, exists)
+ it 'creates the source branch' do
+ # Ensure the project creator is creating the branches because the
+ # merge request author may not have access to push to this
+ # repository. The project owner may also be a group.
+ allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
- expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
- expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
- end
+ mr = insert_git_data
- it 'ignores Git errors when creating a branch' do
- mr, exists = importer.create_merge_request
- mr.state = 'opened'
- mr.save
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
- expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError)
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+ it 'is able to retry on pre-receive errors' do
+ expect(importer).to receive(:insert_or_replace_git_data).twice.and_call_original
+ expect(project.repository).to receive(:add_branch).and_raise('exception')
- importer.insert_git_data(mr, exists)
+ expect { insert_git_data }.to raise_error('exception')
- expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
- expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ expect(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
+
+ mr = insert_git_data
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ expect(mr.merge_request_diffs).to be_one
+ end
+
+ it 'ignores Git command errors when creating a branch' do
+ expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError)
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ mr = insert_git_data
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
end
it 'creates the merge request diffs' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
expect(mr.merge_request_diffs.exists?).to eq(true)
end
it 'creates the merge request diff commits' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
- diff = mr.merge_request_diffs.take
+ diff = mr.merge_request_diffs.reload.first
expect(diff.merge_request_diff_commits.exists?).to eq(true)
end
@@ -322,5 +336,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
expect(mr.merge_request_diffs.exists?).to eq(true)
end
end
+
+ def insert_git_data
+ mr, exists = importer.create_merge_request
+ importer.insert_git_data(mr, exists)
+ mr
+ end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 47233ea6ee2..705df1f4fe7 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -179,6 +179,17 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
describe '#import_repository' do
it 'imports the repository' do
+ repo = double(:repo, default_branch: 'develop')
+
+ expect(client)
+ .to receive(:repository)
+ .with('foo/bar')
+ .and_return(repo)
+
+ expect(project)
+ .to receive(:change_head)
+ .with('develop')
+
expect(project)
.to receive(:ensure_repository)
@@ -186,6 +197,11 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
.to receive(:fetch_as_mirror)
.with(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true, remote_name: 'github')
+ service = double
+ expect(Projects::HousekeepingService)
+ .to receive(:new).with(project, :gc).and_return(service)
+ expect(service).to receive(:execute)
+
expect(importer.import_repository).to eq(true)
end
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index f5df38c9aaf..ecab64a372a 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -25,18 +25,9 @@ describe Gitlab::GithubImport::ParallelImporter do
end
it 'sets the JID in Redis' do
- expect(Gitlab::SidekiqStatus)
- .to receive(:set)
- .with("github-importer/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
- .and_call_original
+ expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project).and_call_original
importer.execute
end
-
- it 'updates the import JID of the project' do
- importer.execute
-
- expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}")
- end
end
end
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
new file mode 100644
index 00000000000..f06a2448ff7
--- /dev/null
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::GlRepository::RepoType do
+ set(:project) { create(:project) }
+
+ shared_examples 'a repo type' do
+ describe "#identifier_for_subject" do
+ subject { described_class.identifier_for_subject(project) }
+
+ it { is_expected.to eq(expected_identifier) }
+ end
+
+ describe "#fetch_id" do
+ it "finds an id match in the identifier" do
+ expect(described_class.fetch_id(expected_identifier)).to eq(expected_id)
+ end
+
+ it 'does not break on other identifiers' do
+ expect(described_class.fetch_id("wiki-noid")).to eq(nil)
+ end
+ end
+
+ describe "#path_suffix" do
+ subject { described_class.path_suffix }
+
+ it { is_expected.to eq(expected_suffix) }
+ end
+
+ describe "#repository_for" do
+ it "finds the repository for the repo type" do
+ expect(described_class.repository_for(project)).to eq(expected_repository)
+ end
+ end
+ end
+
+ describe Gitlab::GlRepository::PROJECT do
+ it_behaves_like 'a repo type' do
+ let(:expected_identifier) { "project-#{project.id}" }
+ let(:expected_id) { project.id.to_s }
+ let(:expected_suffix) { "" }
+ let(:expected_repository) { project.repository }
+ end
+
+ it "knows its type" do
+ expect(described_class).not_to be_wiki
+ expect(described_class).to be_project
+ end
+ end
+
+ describe Gitlab::GlRepository::WIKI do
+ it_behaves_like 'a repo type' do
+ let(:expected_identifier) { "wiki-#{project.id}" }
+ let(:expected_id) { project.id.to_s }
+ let(:expected_suffix) { ".wiki" }
+ let(:expected_repository) { project.wiki.repository }
+ end
+
+ it "knows its type" do
+ expect(described_class).to be_wiki
+ expect(described_class).not_to be_project
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index 4e09020471b..d4b6c629659 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -5,11 +5,11 @@ describe ::Gitlab::GlRepository do
set(:project) { create(:project, :repository) }
it 'parses a project gl_repository' do
- expect(described_class.parse("project-#{project.id}")).to eq([project, false])
+ expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT])
end
it 'parses a wiki gl_repository' do
- expect(described_class.parse("wiki-#{project.id}")).to eq([project, true])
+ expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI])
end
it 'throws an argument error on an invalid gl_repository' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
new file mode 100644
index 00000000000..aec9c4baf0a
--- /dev/null
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Also see spec/graphql/features/authorization_spec.rb for
+# integration tests of AuthorizeFieldService
+describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
+ def type(type_authorizations = [])
+ Class.new(Types::BaseObject) do
+ graphql_name "TestType"
+
+ authorize type_authorizations
+ end
+ end
+
+ def type_with_field(field_type, field_authorizations = [], resolved_value = "Resolved value")
+ Class.new(Types::BaseObject) do
+ graphql_name "TestTypeWithField"
+ field :test_field, field_type, null: true, authorize: field_authorizations, resolve: -> (_, _, _) { resolved_value}
+ end
+ end
+
+ let(:current_user) { double(:current_user) }
+ subject(:service) { described_class.new(field) }
+
+ describe "#authorized_resolve" do
+ let(:presented_object) { double("presented object") }
+ let(:presented_type) { double("parent type", object: presented_object) }
+ subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }
+
+ context "scalar types" do
+ shared_examples "checking permissions on the presented object" do
+ it "checks the abilities on the object being presented and returns the value" do
+ expected_permissions.each do |permission|
+ spy_ability_check_for(permission, presented_object, passed: true)
+ end
+
+ expect(resolved).to eq("Resolved value")
+ end
+
+ it "returns nil if the value wasn't authorized" do
+ allow(Ability).to receive(:allowed?).and_return false
+
+ expect(resolved).to be_nil
+ end
+ end
+
+ context "when the field is a built-in scalar type" do
+ let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql }
+ let(:expected_permissions) { [:read_field] }
+
+ it_behaves_like "checking permissions on the presented object"
+ end
+
+ context "when the field is a list of scalar types" do
+ let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields["testField"].to_graphql }
+ let(:expected_permissions) { [:read_field] }
+
+ it_behaves_like "checking permissions on the presented object"
+ end
+
+ context "when the field is sub-classed scalar type" do
+ let(:field) { type_with_field(Types::TimeType, :read_field).fields["testField"].to_graphql }
+ let(:expected_permissions) { [:read_field] }
+
+ it_behaves_like "checking permissions on the presented object"
+ end
+
+ context "when the field is a list of sub-classed scalar types" do
+ let(:field) { type_with_field([Types::TimeType], :read_field).fields["testField"].to_graphql }
+ let(:expected_permissions) { [:read_field] }
+
+ it_behaves_like "checking permissions on the presented object"
+ end
+ end
+
+ context "when the field is a specific type" do
+ let(:custom_type) { type(:read_type) }
+ let(:object_in_field) { double("presented in field") }
+ let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields["testField"].to_graphql }
+
+ it "checks both field & type permissions" do
+ spy_ability_check_for(:read_field, object_in_field, passed: true)
+ spy_ability_check_for(:read_type, object_in_field, passed: true)
+
+ expect(resolved).to eq(object_in_field)
+ end
+
+ it "returns nil if viewing was not allowed" do
+ spy_ability_check_for(:read_field, object_in_field, passed: false)
+ spy_ability_check_for(:read_type, object_in_field, passed: true)
+
+ expect(resolved).to be_nil
+ end
+
+ context "when the field is a list" do
+ let(:object_1) { double("presented in field 1") }
+ let(:object_2) { double("presented in field 2") }
+ let(:presented_types) { [double(object: object_1), double(object: object_2)] }
+ let(:field) { type_with_field([custom_type], :read_field, presented_types).fields["testField"].to_graphql }
+
+ it "checks all permissions" do
+ allow(Ability).to receive(:allowed?) { true }
+
+ spy_ability_check_for(:read_field, object_1, passed: true)
+ spy_ability_check_for(:read_type, object_1, passed: true)
+ spy_ability_check_for(:read_field, object_2, passed: true)
+ spy_ability_check_for(:read_type, object_2, passed: true)
+
+ expect(resolved).to eq(presented_types)
+ end
+
+ it "filters out objects that the user cannot see" do
+ allow(Ability).to receive(:allowed?) { true }
+
+ spy_ability_check_for(:read_type, object_1, passed: false)
+
+ expect(resolved.map(&:object)).to contain_exactly(object_2)
+ end
+ end
+ end
+ end
+
+ private
+
+ def spy_ability_check_for(ability, object, passed: true)
+ expect(Ability)
+ .to receive(:allowed?)
+ .with(current_user, ability, object)
+ .and_return(passed)
+ end
+end
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 95bf7685ade..13cf52fd795 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -100,4 +100,22 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/)
end
end
+
+ describe '#authorize' do
+ it 'adds permissions from subclasses to those of superclasses when used on classes' do
+ base_class = Class.new do
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorize :base_authorization
+ end
+
+ sub_class = Class.new(base_class) do
+ authorize :sub_authorization
+ end
+
+ expect(base_class.required_permissions).to contain_exactly(:base_authorization)
+ expect(sub_class.required_permissions)
+ .to contain_exactly(:base_authorization, :sub_authorization)
+ end
+ end
end
diff --git a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb b/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb
deleted file mode 100644
index cf3a8bcc8b4..00000000000
--- a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Graphql::Authorize::Instrumentation do
- describe '#build_checker' do
- let(:current_user) { double(:current_user) }
- let(:abilities) { [double(:first_ability), double(:last_ability)] }
-
- let(:checker) do
- described_class.new.__send__(:build_checker, current_user, abilities)
- end
-
- it 'returns a checker which checks for a single object' do
- object = double(:object)
-
- abilities.each do |ability|
- spy_ability_check_for(ability, object, passed: true)
- end
-
- expect(checker.call(object)).to eq(object)
- end
-
- it 'returns a checker which checks for all objects' do
- objects = [double(:first), double(:last)]
-
- abilities.each do |ability|
- objects.each do |object|
- spy_ability_check_for(ability, object, passed: true)
- end
- end
-
- expect(checker.call(objects)).to eq(objects)
- end
-
- context 'when some objects would not pass the check' do
- it 'returns nil when it is single object' do
- disallowed = double(:object)
-
- spy_ability_check_for(abilities.first, disallowed, passed: false)
-
- expect(checker.call(disallowed)).to be_nil
- end
-
- it 'returns only objects which passed when there are more than one' do
- allowed = double(:allowed)
- disallowed = double(:disallowed)
-
- spy_ability_check_for(abilities.first, disallowed, passed: false)
-
- abilities.each do |ability|
- spy_ability_check_for(ability, allowed, passed: true)
- end
-
- expect(checker.call([disallowed, allowed]))
- .to contain_exactly(allowed)
- end
- end
-
- def spy_ability_check_for(ability, object, passed: true)
- expect(Ability)
- .to receive(:allowed?)
- .with(current_user, ability, object)
- .and_return(passed)
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/authorize_spec.rb b/spec/lib/gitlab/graphql/authorize_spec.rb
deleted file mode 100644
index 9c17a3b0e4b..00000000000
--- a/spec/lib/gitlab/graphql/authorize_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Graphql::Authorize do
- describe '#authorize' do
- it 'adds permissions from subclasses to those of superclasses when used on classes' do
- base_class = Class.new do
- extend Gitlab::Graphql::Authorize
-
- authorize :base_authorization
- end
- sub_class = Class.new(base_class) do
- authorize :sub_authorization
- end
-
- expect(base_class.required_permissions).to contain_exactly(:base_authorization)
- expect(sub_class.required_permissions)
- .to contain_exactly(:base_authorization, :sub_authorization)
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
index 9bcc1e78a78..fefa2881b18 100644
--- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
@@ -85,6 +85,11 @@ describe Gitlab::Graphql::Connections::KeysetConnection do
expect(subject.paged_nodes.size).to eq(3)
end
+ it 'is a loaded memoized array' do
+ expect(subject.paged_nodes).to be_an(Array)
+ expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id)
+ end
+
context 'when `first` is passed' do
let(:arguments) { { first: 2 } }
diff --git a/spec/lib/gitlab/graphql/generic_tracing_spec.rb b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
new file mode 100644
index 00000000000..ae92dcc40af
--- /dev/null
+++ b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::GenericTracing do
+ let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') }
+
+ it 'updates graphql histogram with expected labels' do
+ query = 'query { users { id } }'
+ tracer = described_class.new
+
+ allow(tracer)
+ .to receive(:graphql_duration_seconds)
+ .and_return(graphql_duration_seconds_histogram)
+
+ expect_metric('graphql.lex', 'lex')
+ expect_metric('graphql.parse', 'parse')
+ expect_metric('graphql.validate', 'validate')
+ expect_metric('graphql.analyze', 'analyze_multiplex')
+ expect_metric('graphql.execute', 'execute_query_lazy')
+ expect_metric('graphql.execute', 'execute_multiplex')
+
+ GitlabSchema.execute(query, context: { tracers: [tracer] })
+ end
+
+ context "when labkit tracing is enabled" do
+ before do
+ expect(Labkit::Tracing).to receive(:enabled?).and_return(true)
+ end
+
+ it 'yields with labkit tracing' do
+ expected_tags = {
+ 'component' => 'web',
+ 'span.kind' => 'server',
+ 'platform_key' => 'pkey',
+ 'key' => 'key'
+ }
+
+ expect(Labkit::Tracing)
+ .to receive(:with_tracing)
+ .with(operation_name: "pkey.key", tags: expected_tags)
+ .and_yield
+
+ expect { |b| described_class.new.platform_trace('pkey', 'key', nil, &b) }.to yield_control
+ end
+ end
+
+ context "when labkit tracing is disabled" do
+ before do
+ expect(Labkit::Tracing).to receive(:enabled?).and_return(false)
+ end
+
+ it 'yields without measurement' do
+ expect(Labkit::Tracing).not_to receive(:with_tracing)
+
+ expect { |b| described_class.new.platform_trace('pkey', 'key', nil, &b) }.to yield_control
+ end
+ end
+
+ private
+
+ def expect_metric(platform_key, key)
+ expect(graphql_duration_seconds_histogram)
+ .to receive(:observe)
+ .with({ platform_key: platform_key, key: key }, be > 0.0)
+ end
+end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
new file mode 100644
index 00000000000..ec2fcad31e5
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
+ describe '#find' do
+ it 'only queries once for project statistics' do
+ stats = create_list(:project_statistics, 2)
+ project1 = stats.first.project
+ project2 = stats.last.project
+
+ expect do
+ described_class.new(project1.id).find
+ described_class.new(project2.id).find
+ end.not_to exceed_query_limit(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
new file mode 100644
index 00000000000..66033736e01
--- /dev/null
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
+ subject { described_class.new }
+
+ describe '#analyze?' do
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(graphql_logging: false)
+ end
+
+ it 'disables the analyzer' do
+ expect(subject.analyze?(anything)).to be_falsey
+ end
+ end
+
+ context 'feature flag enabled by default' do
+ it 'enables the analyzer' do
+ expect(subject.analyze?(anything)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
new file mode 100644
index 00000000000..d45e690160c
--- /dev/null
+++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Representation::TreeEntry do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '.decorate' do
+ it 'returns NilClass when given nil' do
+ expect(described_class.decorate(nil, repository)).to be_nil
+ end
+
+ it 'returns array of TreeEntry' do
+ entries = described_class.decorate(repository.tree.blobs, repository)
+
+ expect(entries.first).to be_a(described_class)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb
new file mode 100644
index 00000000000..4977f98b83e
--- /dev/null
+++ b/spec/lib/gitlab/graphql_logger_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GraphqlLogger do
+ subject { described_class.new('/dev/null') }
+
+ let(:now) { Time.now }
+
+ it 'builds a logger once' do
+ expect(::Logger).to receive(:new).and_call_original
+
+ subject.info('hello world')
+ subject.error('hello again')
+ end
+
+ context 'logging a GraphQL query' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs a query from JSON' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {},
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ output = subject.format_message('INFO', now, 'test', analyzer_memo)
+
+ data = JSON.parse(output)
+ expect(data['severity']).to eq('INFO')
+ expect(data['time']).to eq(now.utc.iso8601(3))
+ expect(data['complexity']).to eq(181)
+ expect(data['variables']).to eq({})
+ expect(data['depth']).to eq(0)
+ expect(data['duration']).to eq(7)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
new file mode 100644
index 00000000000..2734fcef0a0
--- /dev/null
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::GroupSearchResults do
+ let(:user) { create(:user) }
+
+ describe 'user search' do
+ let(:group) { create(:group) }
+
+ it 'returns the users belonging to the group matching the search query' do
+ user1 = create(:user, username: 'gob_bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ user2 = create(:user, username: 'michael_bluth')
+ create(:group_member, :developer, user: user2, group: group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the subgroup matching the search query', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ subgroup = create(:group, parent: group)
+ create(:group_member, :developer, user: user1, group: subgroup)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the parent group matching the search query', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ parent_group = create(:group, children: [group])
+ create(:group_member, :developer, user: user1, group: parent_group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'does not return the user belonging to the private subgroup', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ subgroup = create(:group, :private, parent: group)
+ create(:group_member, :developer, user: user1, group: subgroup)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq []
+ end
+
+ it 'does not return the user belonging to an unrelated group' do
+ user = create(:user, username: 'gob_bluth')
+ unrelated_group = create(:group)
+ create(:group_member, :developer, user: user, group: unrelated_group)
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq []
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index 3942f168ceb..8e253b51597 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -1,21 +1,31 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::HashedStorage::Migrator do
- describe '#bulk_schedule' do
- it 'schedules job to StorageMigratorWorker' do
+describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do
+ describe '#bulk_schedule_migration' do
+ it 'schedules job to HashedStorage::MigratorWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_schedule_migration(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1)
+ end
+ end
+ end
+
+ describe '#bulk_schedule_rollback' do
+ it 'schedules job to HashedStorage::RollbackerWorker' do
Sidekiq::Testing.fake! do
- expect { subject.bulk_schedule(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1)
+ expect { subject.bulk_schedule_rollback(start: 1, finish: 5) }.to change(HashedStorage::RollbackerWorker.jobs, :size).by(1)
end
end
end
describe '#bulk_migrate' do
- let(:projects) { create_list(:project, 2, :legacy_storage) }
+ let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
let(:ids) { projects.map(&:id) }
- it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do
+ it 'enqueue jobs to HashedStorage::ProjectMigrateWorker' do
Sidekiq::Testing.fake! do
- expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(2)
+ expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(2)
end
end
@@ -32,13 +42,53 @@ describe Gitlab::HashedStorage::Migrator do
subject.bulk_migrate(start: ids.min, finish: ids.max)
end
- it 'has migrated projects set as writable' do
+ it 'has all projects migrated and set as writable' do
perform_enqueued_jobs do
subject.bulk_migrate(start: ids.min, finish: ids.max)
end
projects.each do |project|
- expect(project.reload.repository_read_only?).to be_falsey
+ project.reload
+
+ expect(project.hashed_storage?(:repository)).to be_truthy
+ expect(project.repository_read_only?).to be_falsey
+ end
+ end
+ end
+
+ describe '#bulk_rollback' do
+ let(:projects) { create_list(:project, 2, :empty_repo) }
+ let(:ids) { projects.map(&:id) }
+
+ it 'enqueue jobs to HashedStorage::ProjectRollbackWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(2)
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow_any_instance_of(Project).to receive(:rollback_to_legacy_storage!).and_raise(StandardError)
+ expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.not_to raise_error
+ end
+
+ it 'delegates each project in specified range to #rollback' do
+ projects.each do |project|
+ expect(subject).to receive(:rollback).with(project)
+ end
+
+ subject.bulk_rollback(start: ids.min, finish: ids.max)
+ end
+
+ it 'has all projects rolledback and set as writable' do
+ perform_enqueued_jobs do
+ subject.bulk_rollback(start: ids.min, finish: ids.max)
+ end
+
+ projects.each do |project|
+ project.reload
+
+ expect(project.legacy_storage?).to be_truthy
+ expect(project.repository_read_only?).to be_falsey
end
end
end
@@ -48,7 +98,7 @@ describe Gitlab::HashedStorage::Migrator do
it 'enqueues project migration job' do
Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1)
+ expect { subject.migrate(project) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
end
end
@@ -79,7 +129,7 @@ describe Gitlab::HashedStorage::Migrator do
it 'doesnt enqueue any migration job' do
Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.not_to change(ProjectMigrateHashedStorageWorker.jobs, :size)
+ expect { subject.migrate(project) }.not_to change(HashedStorage::ProjectMigrateWorker.jobs, :size)
end
end
@@ -88,4 +138,98 @@ describe Gitlab::HashedStorage::Migrator do
end
end
end
+
+ describe '#rollback' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'enqueues project rollback job' do
+ Sidekiq::Testing.fake! do
+ expect { subject.rollback(project) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow(project).to receive(:rollback_to_hashed_storage!).and_raise(StandardError)
+
+ expect { subject.rollback(project) }.not_to raise_error
+ end
+
+ it 'rolls-back project storage' do
+ perform_enqueued_jobs do
+ subject.rollback(project)
+ end
+
+ expect(project.reload.legacy_storage?).to be_truthy
+ end
+
+ it 'has rolled-back project set as writable' do
+ perform_enqueued_jobs do
+ subject.rollback(project)
+ end
+
+ expect(project.reload.repository_read_only?).to be_falsey
+ end
+
+ context 'when project is already on legacy storage' do
+ let(:project) { create(:project, :legacy_storage, :empty_repo) }
+
+ it 'doesnt enqueue any rollback job' do
+ Sidekiq::Testing.fake! do
+ expect { subject.rollback(project) }.not_to change(HashedStorage::ProjectRollbackWorker.jobs, :size)
+ end
+ end
+
+ it 'returns false' do
+ expect(subject.rollback(project)).to be_falsey
+ end
+ end
+ end
+
+ describe 'migration_pending?' do
+ set(:project) { create(:project, :empty_repo) }
+
+ it 'returns true when there are MigratorWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::MigratorWorker.perform_async(1, 5)
+
+ expect(subject.migration_pending?).to be_truthy
+ end
+ end
+
+ it 'returns true when there are ProjectMigrateWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::ProjectMigrateWorker.perform_async(1)
+
+ expect(subject.migration_pending?).to be_truthy
+ end
+ end
+
+ it 'returns false when queues are empty' do
+ expect(subject.migration_pending?).to be_falsey
+ end
+ end
+
+ describe 'rollback_pending?' do
+ set(:project) { create(:project, :empty_repo) }
+
+ it 'returns true when there are RollbackerWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::RollbackerWorker.perform_async(1, 5)
+
+ expect(subject.rollback_pending?).to be_truthy
+ end
+ end
+
+ it 'returns true when there are jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::ProjectRollbackWorker.perform_async(1)
+
+ expect(subject.rollback_pending?).to be_truthy
+ end
+ end
+
+ it 'returns false when queues are empty' do
+ expect(subject.rollback_pending?).to be_falsey
+ end
+ end
end
diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
index 26529c4759d..569d5dcc757 100644
--- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
@@ -97,13 +97,13 @@ describe Gitlab::HookData::IssuableBuilder do
end
context 'merge_request is assigned' do
- let(:merge_request) { create(:merge_request, assignee: user) }
+ let(:merge_request) { create(:merge_request, assignees: [user]) }
let(:data) { described_class.new(merge_request).build(user: user) }
it 'returns correct hook data' do
expect(data[:object_attributes]['assignee_id']).to eq(user.id)
- expect(data[:assignee]).to eq(user.hook_attrs)
- expect(data).not_to have_key(:assignees)
+ expect(data[:assignees].first).to eq(user.hook_attrs)
+ expect(data).not_to have_key(:assignee)
end
end
end
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 9ce697adbba..39f80f92fa6 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -10,6 +10,7 @@ describe Gitlab::HookData::MergeRequestBuilder do
it 'includes safe attribute' do
%w[
assignee_id
+ assignee_ids
author_id
created_at
description
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
new file mode 100644
index 00000000000..930d1f62272
--- /dev/null
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::HTTPConnectionAdapter do
+ describe '#connection' do
+ context 'when local requests are not allowed' do
+ it 'sets up the connection' do
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+
+ it 'raises error when it is a request to local address' do
+ uri = URI('http://172.16.0.0/12')
+
+ expect { described_class.new(uri).connection }
+ .to raise_error(Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ uri = URI('http://127.0.0.1')
+
+ expect { described_class.new(uri).connection }
+ .to raise_error(Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ end
+
+ context 'when port different from URL scheme is used' do
+ it 'sets up the addr_port accordingly' do
+ uri = URI('https://example.org:8080')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org:8080')
+ expect(connection.port).to eq(8080)
+ end
+ end
+ end
+
+ context 'when DNS rebinding protection is disabled' do
+ it 'sets up the connection' do
+ stub_application_setting(dns_rebinding_protection_enabled: false)
+
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('example.org')
+ expect(connection.hostname_override).to eq(nil)
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+ end
+
+ context 'when http(s) environment variable is set' do
+ it 'sets up the connection' do
+ stub_env('https_proxy' => 'https://my.proxy')
+
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('example.org')
+ expect(connection.hostname_override).to eq(nil)
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+ end
+
+ context 'when local requests are allowed' do
+ it 'sets up the connection' do
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+
+ it 'sets up the connection when it is a local network' do
+ uri = URI('http://172.16.0.0/12')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('172.16.0.0')
+ expect(connection.hostname_override).to be(nil)
+ expect(connection.addr_port).to eq('172.16.0.0')
+ expect(connection.port).to eq(80)
+ end
+
+ it 'sets up the connection when it is localhost' do
+ uri = URI('http://127.0.0.1')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('127.0.0.1')
+ expect(connection.hostname_override).to be(nil)
+ expect(connection.addr_port).to eq('127.0.0.1')
+ expect(connection.port).to eq(80)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 6c37c157f5d..158f77cab2c 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -1,6 +1,28 @@
require 'spec_helper'
describe Gitlab::HTTP do
+ include StubRequests
+
+ context 'when allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
+
+ described_class.get('https://example.org:8080', allow_local_requests: false)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once
+ end
+ end
+
+ context 'when not allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080')
+
+ described_class.get('https://example.org:8080', allow_local_requests: true)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once
+ end
+ end
+
describe 'allow_local_requests_from_hooks_and_services is' do
before do
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
@@ -21,6 +43,8 @@ describe Gitlab::HTTP do
context 'if allow_local_requests set to true' do
it 'override the global value and allow requests to localhost or private network' do
+ stub_full_request('http://localhost:3003')
+
expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
end
end
@@ -32,6 +56,8 @@ describe Gitlab::HTTP do
end
it 'allow requests to localhost' do
+ stub_full_request('http://localhost:3003')
+
expect { described_class.get('http://localhost:3003') }.not_to raise_error
end
@@ -49,7 +75,7 @@ describe Gitlab::HTTP do
describe 'handle redirect loops' do
before do
- WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
+ stub_full_request("http://example.org", method: :any).to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
end
it 'handles GET requests' do
diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
new file mode 100644
index 00000000000..cc0f2baf905
--- /dev/null
+++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Import::MergeRequestHelpers, type: :helper do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ describe '.create_merge_request_without_hooks' do
+ let(:iid) { 42 }
+
+ let(:attributes) do
+ {
+ iid: iid,
+ title: 'My Pull Request',
+ description: 'This is my pull request',
+ source_project_id: project.id,
+ target_project_id: project.id,
+ source_branch: 'master-42',
+ target_branch: 'master',
+ state: :merged,
+ author_id: user.id,
+ assignee_id: user.id
+ }
+ end
+
+ subject { helper.create_merge_request_without_hooks(project, attributes, iid) }
+
+ context 'when merge request does not exist' do
+ it 'returns a new object' do
+ expect(subject.first).not_to be_nil
+ expect(subject.second).to eq(false)
+ end
+
+ it 'does load all existing objects' do
+ 5.times do |iid|
+ MergeRequest.create!(
+ attributes.merge(iid: iid, source_branch: iid.to_s))
+ end
+
+ # does ensure that we only load object twice
+ # 1. by #insert_and_return_id
+ # 2. by project.merge_requests.find
+ expect_any_instance_of(MergeRequest).to receive(:attributes)
+ .twice.times.and_call_original
+
+ expect(subject.first).not_to be_nil
+ expect(subject.second).to eq(false)
+ end
+ end
+
+ context 'when merge request does exist' do
+ before do
+ MergeRequest.create!(attributes)
+ end
+
+ it 'returns an existing object' do
+ expect(subject.first).not_to be_nil
+ expect(subject.second).to eq(true)
+ end
+ end
+
+ context 'when project is deleted' do
+ before do
+ project.delete
+ end
+
+ it 'returns an existing object' do
+ expect(subject.first).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb
new file mode 100644
index 00000000000..51397280138
--- /dev/null
+++ b/spec/lib/gitlab/import/set_async_jid_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Import::SetAsyncJid do
+ describe '.set_jid', :clean_gitlab_redis_shared_state do
+ let(:project) { create(:project, :import_scheduled) }
+
+ it 'sets the JID in Redis' do
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:set)
+ .with("async-import/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ .and_call_original
+
+ described_class.set_jid(project)
+ end
+
+ it 'updates the import JID of the project' do
+ described_class.set_jid(project)
+
+ expect(project.import_state.reload.jid).to eq("async-import/#{project.id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index ec17ad8541f..21a227335cd 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+ include StubRequests
+
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
let!(:project) { create(:project, :with_export) }
@@ -32,5 +34,17 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
strategy.execute(user, project)
end
+
+ context 'when upload fails' do
+ it 'stores the export error' do
+ stub_full_request(example_url, method: :post).to_return(status: [404, 'Page not found'])
+
+ strategy.execute(user, project)
+
+ errors = project.import_export_shared.errors
+ expect(errors).not_to be_empty
+ expect(errors.first).to eq "Error uploading the project. Code 404: Page not found"
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index f2eccec4635..2242543daad 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -99,7 +99,10 @@ merge_requests:
- timelogs
- head_pipeline
- latest_merge_request_diff
-- merge_request_pipelines
+- pipelines_for_merge_request
+- merge_request_assignees
+- suggestions
+- assignees
merge_request_diff:
- merge_request
- merge_request_diff_commits
@@ -127,7 +130,7 @@ ci_pipelines:
- scheduled_actions
- artifacts
- pipeline_schedule
-- merge_requests
+- merge_requests_as_head_pipeline
- merge_request
- deployments
- environments
@@ -220,6 +223,7 @@ project:
- packagist_service
- pivotaltracker_service
- prometheus_service
+- hipchat_service
- flowdock_service
- assembla_service
- asana_service
@@ -233,6 +237,7 @@ project:
- pushover_service
- jira_service
- redmine_service
+- youtrack_service
- custom_issue_tracker_service
- bugzilla_service
- gitlab_issue_tracker_service
@@ -317,6 +322,7 @@ project:
- pool_repository
- kubernetes_namespaces
- error_tracking_setting
+- metrics_setting
award_emoji:
- awardable
- user
@@ -333,6 +339,9 @@ push_event_payload:
issue_assignees:
- issue
- assignee
+merge_request_assignees:
+- merge_request
+- assignee
lfs_file_locks:
- user
project_badges:
@@ -350,3 +359,7 @@ resource_label_events:
- label
error_tracking_setting:
- project
+suggestions:
+- note
+metrics_setting:
+- project
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 536cc359d39..99669285d5b 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -18,7 +18,11 @@ describe Gitlab::ImportExport::AttributeCleaner do
'notid' => 99,
'import_source' => 'whatever',
'import_type' => 'whatever',
- 'non_existent_attr' => 'whatever'
+ 'non_existent_attr' => 'whatever',
+ 'some_html' => '<p>dodgy html</p>',
+ 'legit_html' => '<p>legit html</p>',
+ '_html' => '<p>perfectly ordinary html</p>',
+ 'cached_markdown_version' => 12345
}
end
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index 87ab81d8169..ddfbb020a55 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -29,7 +29,7 @@ describe 'Import/Export attribute configuration' do
it 'has no new columns' do
relation_names.each do |relation_name|
relation_class = relation_class_for_name(relation_name)
- relation_attributes = relation_class.new.attributes.keys
+ relation_attributes = relation_class.new.attributes.keys - relation_class.encrypted_attributes.keys.map(&:to_s)
current_attributes = parsed_attributes(relation_name, relation_attributes)
safe_attributes = safe_model_attributes[relation_class.to_s].dup || []
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 67e4c289906..b95b5dfe791 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -12,7 +12,6 @@ describe Gitlab::ImportExport::MembersMapper do
"access_level" => 40,
"source_id" => 14,
"source_type" => "Project",
- "user_id" => 19,
"notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z",
@@ -25,7 +24,8 @@ describe Gitlab::ImportExport::MembersMapper do
"id" => exported_user_id,
"email" => user2.email,
"username" => 'test'
- }
+ },
+ "user_id" => 19
},
{
"id" => 3,
@@ -73,6 +73,22 @@ describe Gitlab::ImportExport::MembersMapper do
expect(user2.authorized_project?(project)).to be true
end
+ it 'maps an owner as a maintainer' do
+ exported_members.first['access_level'] = ProjectMember::OWNER
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
+ end
+
+ it 'removes old user_id from member_hash to avoid conflict with user key' do
+ expect(ProjectMember).to receive(:create)
+ .twice
+ .with(hash_excluding('user_id'))
+ .and_call_original
+
+ members_mapper.map
+ end
+
context 'user is not an admin' do
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index 68eaa70e6b6..4b234411a44 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -41,4 +41,20 @@ describe Gitlab::ImportExport::MergeRequestParser do
expect(parsed_merge_request).to eq(merge_request)
end
+
+ context 'when the merge request has diffs' do
+ let(:merge_request) do
+ build(:merge_request, source_project: forked_project, target_project: project)
+ end
+
+ context 'when the diff is invalid' do
+ let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') }
+
+ it 'sets the diff to nil' do
+ expect(merge_request_diff).to be_invalid
+ expect(merge_request_diff.merge_request).to eq merge_request
+ expect(parsed_merge_request.merge_request_diff).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 1327f414498..fb7bddb386c 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -158,6 +158,8 @@
{
"id": 351,
"note": "Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi.",
+ "note_html": "<p>something else entirely</p>",
+ "cached_markdown_version": 917504,
"noteable_type": "Issue",
"author_id": 26,
"created_at": "2016-06-14T15:02:47.770Z",
@@ -2363,6 +2365,8 @@
{
"id": 671,
"note": "Sit voluptatibus eveniet architecto quidem.",
+ "note_html": "<p>something else entirely</p>",
+ "cached_markdown_version": 917504,
"noteable_type": "MergeRequest",
"author_id": 26,
"created_at": "2016-06-14T15:02:56.632Z",
@@ -6630,6 +6634,26 @@
"deploy_keys": [],
"services": [
{
+ "id": 101,
+ "title": "YouTrack",
+ "project_id": 5,
+ "created_at": "2016-06-14T15:01:51.327Z",
+ "updated_at": "2016-06-14T15:01:51.327Z",
+ "active": false,
+ "properties": {},
+ "template": false,
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "job_events": true,
+ "type": "YoutrackService",
+ "category": "issue_tracker",
+ "default": false,
+ "wiki_page_events": true
+ },
+ {
"id": 100,
"title": "JetBrains TeamCity CI",
"project_id": 5,
@@ -6775,6 +6799,28 @@
"wiki_page_events": true
},
{
+ "id": 93,
+ "title": "HipChat",
+ "project_id": 5,
+ "created_at": "2016-06-14T15:01:51.219Z",
+ "updated_at": "2016-06-14T15:01:51.219Z",
+ "active": false,
+ "properties": {
+ "notify_only_broken_pipelines": true
+ },
+ "template": false,
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "pipeline_events": true,
+ "type": "HipchatService",
+ "category": "common",
+ "default": false,
+ "wiki_page_events": true
+ },
+ {
"id": 91,
"title": "Flowdock",
"project_id": 5,
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 6084dc96410..ca46006ea58 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -58,6 +58,26 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
+ context 'when importing a project with cached_markdown_version and note_html' do
+ context 'for an Issue' do
+ it 'does not import note_html' do
+ note_content = 'Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi'
+ issue_note = Issue.find_by(description: 'Aliquam enim illo et possimus.').notes.select { |n| n.note.match(/#{note_content}/)}.first
+
+ expect(issue_note.note_html).to match(/#{note_content}/)
+ end
+ end
+
+ context 'for a Merge Request' do
+ it 'does not import note_html' do
+ note_content = 'Sit voluptatibus eveniet architecto quidem'
+ merge_request_note = MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{note_content}/)}.first
+
+ expect(merge_request_note.note_html).to match(/#{note_content}/)
+ end
+ end
+ end
+
it 'creates a valid pipeline note' do
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
end
@@ -328,6 +348,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'when the project has overridden params in import data' do
+ it 'handles string versions of visibility_level' do
+ # Project needs to be in a group for visibility level comparison
+ # to happen
+ group = create(:group)
+ project.group = group
+
+ project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } })
+
+ restored_project_json
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+
it 'overwrites the params stored in the JSON' do
project.create_import_data(data: { override_params: { description: "Overridden" } })
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index cfc3e0ce926..bc4f867e891 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -91,7 +91,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
it 'has issue comments' do
- expect(saved_project_json['issues'].first['notes']).not_to be_empty
+ notes = saved_project_json['issues'].first['notes']
+
+ expect(notes).not_to be_empty
+ expect(notes.first['type']).to eq('DiscussionNote')
end
it 'has issue assignees' do
@@ -299,7 +302,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:commit_status, project: project, pipeline: ci_build.pipeline)
create(:milestone, project: project)
- create(:note, noteable: issue, project: project)
+ create(:discussion_note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
create(:note, noteable: snippet, project: project)
create(:note_on_commit,
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 8a699eb1461..e2ffb2adb9b 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,11 +34,5 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'restores the repo successfully' do
expect(restorer.restore).to be_truthy
end
-
- it 'has the webhooks' do
- restorer.restore
-
- expect(project_hook_exists?(project)).to be true
- end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index baca8f6d542..9093d21647a 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -11,6 +11,7 @@ Issue:
- branch_name
- description
- state
+- state_id
- iid
- updated_by_id
- confidential
@@ -158,6 +159,7 @@ MergeRequest:
- created_at
- updated_at
- state
+- state_id
- merge_status
- target_project_id
- iid
@@ -235,6 +237,8 @@ Ci::Pipeline:
- ref
- sha
- before_sha
+- source_sha
+- target_sha
- push_data
- created_at
- updated_at
@@ -419,6 +423,7 @@ Service:
- wiki_page_events
- confidential_issues_events
- confidential_note_events
+- deployment_events
ProjectHook:
- id
- url
@@ -492,6 +497,7 @@ Project:
- merge_requests_ff_only_enabled
- merge_requests_rebase_enabled
- jobs_cache_index
+- external_authorization_classification_label
- pages_https_only
Author:
- name
@@ -601,8 +607,27 @@ ResourceLabelEvent:
- user_id
- created_at
ErrorTracking::ProjectErrorTrackingSetting:
-- id
- api_url
- project_id
- project_name
- organization_name
+Suggestion:
+- id
+- note_id
+- relative_order
+- applied
+- commit_id
+- from_content
+- to_content
+- outdated
+- lines_above
+- lines_below
+MergeRequestAssignee:
+- id
+- user_id
+- merge_request_id
+ProjectMetricsSetting:
+- project_id
+- external_dashboard_url
+- created_at
+- updated_at
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 94abf9679c4..8060b5d4448 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -14,7 +14,8 @@ describe Gitlab::ImportSources do
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
'Gitea' => 'gitea',
- 'Manifest file' => 'manifest'
+ 'Manifest file' => 'manifest',
+ 'Phabricator' => 'phabricator'
}
expect(described_class.options).to eq(expected)
@@ -35,6 +36,7 @@ describe Gitlab::ImportSources do
gitlab_project
gitea
manifest
+ phabricator
)
expect(described_class.values).to eq(expected)
@@ -53,6 +55,7 @@ describe Gitlab::ImportSources do
fogbugz
gitlab_project
gitea
+ phabricator
)
expect(described_class.importer_names).to eq(expected)
@@ -70,7 +73,8 @@ describe Gitlab::ImportSources do
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
'gitea' => Gitlab::LegacyGithubImport::Importer,
- 'manifest' => nil
+ 'manifest' => nil,
+ 'phabricator' => Gitlab::PhabricatorImport::Importer
}
import_sources.each do |name, klass|
@@ -91,7 +95,8 @@ describe Gitlab::ImportSources do
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
'gitea' => 'Gitea',
- 'manifest' => 'Manifest file'
+ 'manifest' => 'Manifest file',
+ 'phabricator' => 'Phabricator'
}
import_sources.each do |name, title|
@@ -102,7 +107,7 @@ describe Gitlab::ImportSources do
end
describe 'imports_repository? checker' do
- let(:allowed_importers) { %w[github gitlab_project bitbucket_server] }
+ let(:allowed_importers) { %w[github gitlab_project bitbucket_server phabricator] }
it 'fails if any importer other than the allowed ones implements this method' do
current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) }
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
index 42635a68ee1..916f3876a8e 100644
--- a/spec/lib/gitlab/issuable_metadata_spec.rb
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::IssuableMetadata do
let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) }
let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) }
let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) }
- let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") }
let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) }
it 'aggregates stats on issues' do
@@ -28,18 +28,18 @@ describe Gitlab::IssuableMetadata do
expect(data.count).to eq(2)
expect(data[issue.id].upvotes).to eq(1)
expect(data[issue.id].downvotes).to eq(0)
- expect(data[issue.id].notes_count).to eq(0)
+ expect(data[issue.id].user_notes_count).to eq(0)
expect(data[issue.id].merge_requests_count).to eq(1)
expect(data[closed_issue.id].upvotes).to eq(0)
expect(data[closed_issue.id].downvotes).to eq(1)
- expect(data[closed_issue.id].notes_count).to eq(0)
+ expect(data[closed_issue.id].user_notes_count).to eq(0)
expect(data[closed_issue.id].merge_requests_count).to eq(0)
end
end
context 'merge requests' do
- let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") }
let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") }
let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
@@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do
expect(data.count).to eq(2)
expect(data[merge_request.id].upvotes).to eq(1)
expect(data[merge_request.id].downvotes).to eq(1)
- expect(data[merge_request.id].notes_count).to eq(1)
+ expect(data[merge_request.id].user_notes_count).to eq(1)
expect(data[merge_request.id].merge_requests_count).to eq(0)
expect(data[merge_request_closed.id].upvotes).to eq(0)
expect(data[merge_request_closed.id].downvotes).to eq(0)
- expect(data[merge_request_closed.id].notes_count).to eq(0)
+ expect(data[merge_request_closed.id].user_notes_count).to eq(0)
expect(data[merge_request_closed.id].merge_requests_count).to eq(0)
end
end
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index b52078e8556..b82c09af306 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::JsonCache do
let(:namespace) { 'geo' }
let(:key) { 'foo' }
let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" }
- let(:broadcast_message) { create(:broadcast_message) }
+ set(:broadcast_message) { create(:broadcast_message) }
subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
@@ -146,6 +146,18 @@ describe Gitlab::JsonCache do
expect(cache.read(key, BroadcastMessage)).to be_nil
end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.read(key, BroadcastMessage)
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
end
context 'when the cached value is an array' do
@@ -297,13 +309,79 @@ describe Gitlab::JsonCache do
expect(result).to eq(broadcast_message)
end
+ context 'when the cached value is an instance of ActiveRecord::Base' do
+ it 'returns a persisted record when id is set' do
+ backend.write(expanded_key, broadcast_message.to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_persisted
+ end
+
+ it 'returns a new record when id is nil' do
+ backend.write(expanded_key, build(:broadcast_message).to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+
+ it 'returns a new record when id is missing' do
+ backend.write(expanded_key, build(:broadcast_message).attributes.except('id').to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{')
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{}')
+
+ expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ it 'gracefully handles unknown attributes' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
+ end
+
it "returns the result of the block when 'as' option is nil" do
result = cache.fetch(key, as: nil) { 'block result' }
expect(result).to eq('block result')
end
- it "returns the result of the block when 'as' option is not informed" do
+ it "returns the result of the block when 'as' option is missing" do
result = cache.fetch(key) { 'block result' }
expect(result).to eq('block result')
diff --git a/spec/lib/gitlab/json_logger_spec.rb b/spec/lib/gitlab/json_logger_spec.rb
index cff7dd58c8c..d3d9fe9948a 100644
--- a/spec/lib/gitlab/json_logger_spec.rb
+++ b/spec/lib/gitlab/json_logger_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::JsonLogger do
describe '#format_message' do
before do
- allow(Gitlab::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
end
it 'formats strings' do
diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
index 4a669408025..e1106f7496a 100644
--- a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ClusterRoleBinding do
subject { cluster_role_binding.generate }
- it 'should build a Kubeclient Resource' do
+ it 'builds a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb
index fe65d03875f..911d6024804 100644
--- a/spec/lib/gitlab/kubernetes/config_map_spec.rb
+++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Kubernetes::ConfigMap do
let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: application.files) }
subject { config_map.generate }
- it 'should build a Kubeclient Resource' do
+ it 'builds a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 8433d40b2ea..0de809833e6 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -33,6 +33,52 @@ describe Gitlab::Kubernetes::Helm::Api do
end
end
+ describe '#uninstall' do
+ before do
+ allow(client).to receive(:create_pod).and_return(nil)
+ allow(client).to receive(:get_config_map).and_return(nil)
+ allow(client).to receive(:create_config_map).and_return(nil)
+ allow(client).to receive(:delete_pod).and_return(nil)
+ allow(namespace).to receive(:ensure_exists!).once
+ end
+
+ it 'ensures the namespace exists before creating the POD' do
+ expect(namespace).to receive(:ensure_exists!).once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.uninstall(command)
+ end
+
+ it 'removes an existing pod before installing' do
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.uninstall(command)
+ end
+
+ context 'with a ConfigMap' do
+ let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
+
+ it 'creates a ConfigMap on kubeclient' do
+ expect(client).to receive(:create_config_map).with(resource).once
+
+ subject.install(command)
+ end
+
+ context 'config map already exists' do
+ before do
+ expect(client).to receive(:get_config_map).with("values-content-configuration-#{application_name}", gitlab_namespace).and_return(resource)
+ end
+
+ it 'updates the config map' do
+ expect(client).to receive(:update_config_map).with(resource).once
+
+ subject.install(command)
+ end
+ end
+ end
+ end
+
describe '#install' do
before do
allow(client).to receive(:create_pod).and_return(nil)
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
index aacae78be43..78a4eb44e38 100644
--- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -41,7 +41,7 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do
describe '#pod_resource' do
subject { base_command.pod_resource }
- it 'should returns a kubeclient resoure with pod content for application' do
+ it 'returns a kubeclient resoure with pod content for application' do
is_expected.to be_an_instance_of ::Kubeclient::Resource
end
diff --git a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
index 167bee22fc3..04649353976 100644
--- a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Kubernetes::Helm::Certificate do
describe '.generate_root' do
subject { described_class.generate_root }
- it 'should generate a root CA that expires a long way in the future' do
+ it 'generates a root CA that expires a long way in the future' do
expect(subject.cert.not_after).to be > 999.years.from_now
end
end
@@ -13,14 +13,14 @@ describe Gitlab::Kubernetes::Helm::Certificate do
describe '#issue' do
subject { described_class.generate_root.issue }
- it 'should generate a cert that expires soon' do
+ it 'generates a cert that expires soon' do
expect(subject.cert.not_after).to be < 60.minutes.from_now
end
context 'passing in INFINITE_EXPIRY' do
subject { described_class.generate_root.issue(expires_in: described_class::INFINITE_EXPIRY) }
- it 'should generate a cert that expires a long way in the future' do
+ it 'generates a cert that expires a long way in the future' do
expect(subject.cert.not_after).to be > 999.years.from_now
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
new file mode 100644
index 00000000000..cae92305b19
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Helm::DeleteCommand do
+ let(:app_name) { 'app-name' }
+ let(:rbac) { true }
+ let(:files) { {} }
+ let(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files) }
+
+ subject { delete_command }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --upgrade
+ for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ helm delete --purge app-name
+ EOS
+ end
+ end
+
+ context 'when there is a ca.pem file' do
+ let(:files) { { 'ca.pem': 'some file content' } }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --upgrade
+ for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ #{helm_delete_command}
+ EOS
+ end
+
+ let(:helm_delete_command) do
+ <<~EOS.squish
+ helm delete --purge app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ EOS
+ end
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { delete_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#pod_name' do
+ subject { delete_command.pod_name }
+
+ it { is_expected.to eq('uninstall-app-name') }
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 95b6b3fd953..06c8d127951 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -10,11 +10,11 @@ describe Gitlab::Kubernetes::Helm::Pod do
subject { described_class.new(command, namespace, service_account_name: service_account_name) }
context 'with a command' do
- it 'should generate a Kubeclient::Resource' do
+ it 'generates a Kubeclient::Resource' do
expect(subject.generate).to be_a_kind_of(Kubeclient::Resource)
end
- it 'should generate the appropriate metadata' do
+ it 'generates the appropriate metadata' do
metadata = subject.generate.metadata
expect(metadata.name).to eq("install-#{app.name}")
expect(metadata.namespace).to eq('gitlab-managed-apps')
@@ -22,12 +22,12 @@ describe Gitlab::Kubernetes::Helm::Pod do
expect(metadata.labels['gitlab.org/application']).to eq(app.name)
end
- it 'should generate a container spec' do
+ it 'generates a container spec' do
spec = subject.generate.spec
expect(spec.containers.count).to eq(1)
end
- it 'should generate the appropriate specifications for the container' do
+ it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7')
@@ -37,30 +37,30 @@ describe Gitlab::Kubernetes::Helm::Pod do
expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"])
end
- it 'should include a never restart policy' do
+ it 'includes a never restart policy' do
spec = subject.generate.spec
expect(spec.restartPolicy).to eq('Never')
end
- it 'should include volumes for the container' do
+ it 'includes volumes for the container' do
container = subject.generate.spec.containers.first
expect(container.volumeMounts.first['name']).to eq('configuration-volume')
expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config")
end
- it 'should include a volume inside the specification' do
+ it 'includes a volume inside the specification' do
spec = subject.generate.spec
expect(spec.volumes.first['name']).to eq('configuration-volume')
end
- it 'should mount configMap specification in the volume' do
+ it 'mounts configMap specification in the volume' do
volume = subject.generate.spec.volumes.first
expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(volume.configMap['items'].first['key']).to eq(:'values.yaml')
expect(volume.configMap['items'].first['path']).to eq(:'values.yaml')
end
- it 'should have no serviceAccountName' do
+ it 'has no serviceAccountName' do
spec = subject.generate.spec
expect(spec.serviceAccountName).to be_nil
end
@@ -68,7 +68,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
context 'with a service_account_name' do
let(:service_account_name) { 'sa' }
- it 'should use the serviceAccountName provided' do
+ it 'uses the serviceAccountName provided' do
spec = subject.generate.spec
expect(spec.serviceAccountName).to eq(service_account_name)
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 02364e92149..978e64c4407 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -50,6 +50,36 @@ describe Gitlab::Kubernetes::KubeClient do
end
end
+ describe '#initialize' do
+ shared_examples 'local address' do
+ it 'blocks local addresses' do
+ expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_hooks_and_services: true)
+ end
+
+ it 'allows local addresses' do
+ expect { client }.not_to raise_error
+ end
+ end
+ end
+
+ context 'localhost address' do
+ let(:api_url) { 'http://localhost:22' }
+
+ it_behaves_like 'local address'
+ end
+
+ context 'private network address' do
+ let(:api_url) { 'http://192.168.1.2:3003' }
+
+ it_behaves_like 'local address'
+ end
+ end
+
describe '#core_client' do
subject { client.core_client }
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index e1c35c355f4..e91a755aa03 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -62,5 +62,32 @@ describe Gitlab::Kubernetes::Namespace do
subject.ensure_exists!
end
+
+ context 'when client errors' do
+ let(:exception) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
+ before do
+ allow(client).to receive(:get_namespace).with(name).once.and_raise(exception)
+ end
+
+ it 'raises the exception' do
+ expect { subject.ensure_exists! }.to raise_error(exception)
+ end
+
+ it 'logs the error' do
+ expect(subject.send(:logger)).to receive(:error).with(
+ hash_including(
+ exception: 'Kubeclient::HttpError',
+ status_code: 500,
+ namespace: 'a_namespace',
+ class_name: 'Gitlab::Kubernetes::Namespace',
+ event: :failed_to_create_namespace,
+ message: 'system failure'
+ )
+ )
+
+ expect { subject.ensure_exists! }.to raise_error(exception)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index a1a59533bfb..50acee254cb 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
).generate
end
- it 'should build a Kubeclient Resource' do
+ it 'builds a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb
index 8da9e932dc3..0d525966d18 100644
--- a/spec/lib/gitlab/kubernetes/service_account_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb
@@ -17,7 +17,7 @@ describe Gitlab::Kubernetes::ServiceAccount do
subject { service_account.generate }
- it 'should build a Kubeclient Resource' do
+ it 'builds a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
index 0773d3d9aec..0d334bed45f 100644
--- a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ServiceAccountToken do
subject { service_account_token.generate }
- it 'should build a Kubeclient Resource' do
+ it 'builds a Kubeclient Resource' do
is_expected.to eq(resource)
end
end
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index f326d57e9c6..57b570a9166 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -40,10 +40,40 @@ describe Gitlab::Kubernetes do
describe '#filter_by_label' do
it 'returns matching labels' do
- matching_items = [kube_pod(app: 'foo')]
+ matching_items = [kube_pod(track: 'foo'), kube_deployment(track: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_label(items, 'track' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_annotation' do
+ it 'returns matching labels' do
+ matching_items = [kube_pod(environment_slug: 'foo'), kube_deployment(environment_slug: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_annotation(items, 'app.gitlab.com/env' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_project_environment' do
+ let(:matching_pod) { kube_pod(environment_slug: 'production', project_slug: 'my-cool-app') }
+
+ it 'returns matching legacy env label' do
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/app')
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/env')
+ matching_pod['metadata']['labels']['app'] = 'production'
+ matching_items = [matching_pod]
+ items = matching_items + [kube_pod]
+
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
+ end
+
+ it 'returns matching env label' do
+ matching_items = [matching_pod]
items = matching_items + [kube_pod]
- expect(filter_by_label(items, app: 'foo')).to eq(matching_items)
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
end
end
diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
index 3d4240fa4ba..8675d8691c8 100644
--- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
@@ -47,12 +47,22 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do
end
context 'when GitHub project is public' do
- it 'sets project visibility to public' do
+ it 'sets project visibility to namespace visibility level' do
repo.private = false
-
project = service.execute
- expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ expect(project.visibility_level).to eq(namespace.visibility_level)
+ end
+
+ context 'when importing into a user namespace' do
+ subject(:service) { described_class.new(repo, repo.name, user.namespace, user, github_access_token: 'asdffg') }
+
+ it 'sets project visibility to user namespace visibility level' do
+ repo.private = false
+ project = service.execute
+
+ expect(project.visibility_level).to eq(user.namespace.visibility_level)
+ end
end
end
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index 082e3b36dd0..c57b96fb00d 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -25,6 +25,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
expected = {
project: project,
tag: 'v1.0.0',
+ name: 'First release',
description: 'Release v1.0.0',
created_at: created_at,
updated_at: created_at
diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
new file mode 100644
index 00000000000..fcd92586362
--- /dev/null
+++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::LetsEncrypt::Challenge do
+ include LetsEncryptHelpers
+
+ let(:challenge) { described_class.new(acme_challenge_double) }
+
+ LetsEncryptHelpers::ACME_CHALLENGE_METHODS.each do |method, value|
+ describe "##{method}" do
+ it 'delegates to Acme::Client::Resources::Challenge' do
+ expect(challenge.public_send(method)).to eq(value)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
new file mode 100644
index 00000000000..5454d9c1af4
--- /dev/null
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::LetsEncrypt::Client do
+ include LetsEncryptHelpers
+
+ let(:client) { described_class.new }
+
+ before do
+ stub_application_setting(
+ lets_encrypt_notification_email: 'myemail@test.example.com',
+ lets_encrypt_terms_of_service_accepted: true
+ )
+ end
+
+ let!(:stub_client) { stub_lets_encrypt_client }
+
+ shared_examples 'ensures account registration' do
+ it 'ensures account registration' do
+ subject
+
+ expect(stub_client).to have_received(:new_account).with(
+ contact: 'mailto:myemail@test.example.com',
+ terms_of_service_agreed: true
+ )
+ end
+
+ it 'generates and stores private key and initialize acme client with it' do
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ subject
+
+ saved_private_key = Gitlab::CurrentSettings.lets_encrypt_private_key
+
+ expect(saved_private_key).to be
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ end
+
+ context 'when private key is saved in settings' do
+ let!(:saved_private_key) do
+ key = OpenSSL::PKey::RSA.new(4096).to_pem
+ Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key)
+ key
+ end
+
+ it 'uses current value of private key' do
+ subject
+
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(saved_private_key)
+ end
+ end
+
+ context 'when acme integration is disabled' do
+ before do
+ stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
+ end
+
+ it 'raises error' do
+ expect do
+ subject
+ end.to raise_error('Acme integration is disabled')
+ end
+ end
+ end
+
+ describe '#new_order' do
+ subject(:new_order) { client.new_order('example.com') }
+
+ before do
+ order_double = instance_double('Acme::Order')
+ allow(stub_client).to receive(:new_order).and_return(order_double)
+ end
+
+ include_examples 'ensures account registration'
+
+ it 'returns order' do
+ is_expected.to be_a(::Gitlab::LetsEncrypt::Order)
+ end
+ end
+
+ describe '#load_order' do
+ let(:url) { 'https://example.com/order' }
+ subject { client.load_order(url) }
+
+ before do
+ acme_order = instance_double('Acme::Client::Resources::Order')
+ allow(stub_client).to receive(:order).with(url: url).and_return(acme_order)
+ end
+
+ include_examples 'ensures account registration'
+
+ it 'loads order' do
+ is_expected.to be_a(::Gitlab::LetsEncrypt::Order)
+ end
+ end
+
+ describe '#load_challenge' do
+ let(:url) { 'https://example.com/challenge' }
+ subject { client.load_challenge(url) }
+
+ before do
+ acme_challenge = instance_double('Acme::Client::Resources::Challenge')
+ allow(stub_client).to receive(:challenge).with(url: url).and_return(acme_challenge)
+ end
+
+ include_examples 'ensures account registration'
+
+ it 'loads challenge' do
+ is_expected.to be_a(::Gitlab::LetsEncrypt::Challenge)
+ end
+ end
+
+ describe '#enabled?' do
+ subject { client.enabled? }
+
+ context 'when terms of service are accepted' do
+ it { is_expected.to eq(true) }
+
+ context "when private_key isn't present and database is read only" do
+ before do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'returns false' do
+ expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ is_expected.to eq(false)
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ context 'when terms of service are not accepted' do
+ before do
+ stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#terms_of_service_url' do
+ subject { client.terms_of_service_url }
+
+ it 'returns valid url' do
+ is_expected.to eq("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb
new file mode 100644
index 00000000000..1a759103c44
--- /dev/null
+++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::LetsEncrypt::Order do
+ include LetsEncryptHelpers
+
+ let(:acme_order) { acme_order_double }
+
+ let(:order) { described_class.new(acme_order) }
+
+ LetsEncryptHelpers::ACME_ORDER_METHODS.each do |method, value|
+ describe "##{method}" do
+ it 'delegates to Acme::Client::Resources::Order' do
+ expect(order.public_send(method)).to eq(value)
+ end
+ end
+ end
+
+ describe '#new_challenge' do
+ it 'returns challenge' do
+ expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge)
+ end
+ end
+
+ describe '#request_certificate' do
+ let(:private_key) do
+ OpenSSL::PKey::RSA.new(4096).to_pem
+ end
+
+ it 'generates csr and finalizes order' do
+ expect(acme_order).to receive(:finalize) do |csr:|
+ expect do
+ csr.csr # it's being evaluated lazily
+ end.not_to raise_error
+ end
+
+ order.request_certificate(domain: 'example.com', private_key: private_key)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 8961ecc4be0..701ed1f3a1b 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -77,96 +77,42 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let(:actor) { create(:user, username: 'test_user_lfs_1') }
let(:lfs_token) { described_class.new(actor) }
- context 'for an HMAC token' do
- before do
- # We're not interested in testing LegacyRedisDeviseToken here
- allow(Gitlab::LfsToken::LegacyRedisDeviseToken).to receive_message_chain(:new, :token_valid?).and_return(false)
- end
-
- context 'where the token is invalid' do
- context "because it's junk" do
- it 'returns false' do
- expect(lfs_token.token_valid?('junk')).to be_falsey
- end
- end
-
- context "because it's been fiddled with" do
- it 'returns false' do
- fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' }
- expect(lfs_token.token_valid?(fiddled_token)).to be_falsey
- end
- end
-
- context "because it was generated with a different secret" do
- it 'returns false' do
- different_actor = create(:user, username: 'test_user_lfs_2')
- different_secret_token = described_class.new(different_actor).token
- expect(lfs_token.token_valid?(different_secret_token)).to be_falsey
- end
- end
-
- context "because it's expired" do
- it 'returns false' do
- expired_token = lfs_token.token
- # Needs to be at least 1860 seconds, because the default expiry is
- # 1800 seconds with an additional 60 second leeway.
- Timecop.freeze(Time.now + 1865) do
- expect(lfs_token.token_valid?(expired_token)).to be_falsey
- end
- end
+ context 'where the token is invalid' do
+ context "because it's junk" do
+ it 'returns false' do
+ expect(lfs_token.token_valid?('junk')).to be_falsey
end
end
- context 'where the token is valid' do
- it 'returns true' do
- expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
+ context "because it's been fiddled with" do
+ it 'returns false' do
+ fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' }
+ expect(lfs_token.token_valid?(fiddled_token)).to be_falsey
end
end
- end
-
- context 'for a LegacyRedisDevise token' do
- before do
- # We're not interested in testing HMACToken here
- allow_any_instance_of(Gitlab::LfsToken::HMACToken).to receive(:token_valid?).and_return(false)
- end
-
- context 'where the token is invalid' do
- context "because it's junk" do
- it 'returns false' do
- expect(lfs_token.token_valid?('junk')).to be_falsey
- end
- end
- context "because it's been fiddled with" do
- it 'returns false' do
- generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token
- fiddled_token = generated_token.tap { |token| token[0] = 'E' }
- expect(lfs_token.token_valid?(fiddled_token)).to be_falsey
- end
- end
-
- context "because it was generated with a different secret" do
- it 'returns false' do
- different_actor = create(:user, username: 'test_user_lfs_2')
- different_secret_token = described_class.new(different_actor).token
- expect(lfs_token.token_valid?(different_secret_token)).to be_falsey
- end
+ context "because it was generated with a different secret" do
+ it 'returns false' do
+ different_actor = create(:user, username: 'test_user_lfs_2')
+ different_secret_token = described_class.new(different_actor).token
+ expect(lfs_token.token_valid?(different_secret_token)).to be_falsey
end
+ end
- context "because it's expired" do
- it 'returns false' do
- generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token(1)
- # We need a real sleep here because we need to wait for redis to expire the key.
- sleep(0.01)
- expect(lfs_token.token_valid?(generated_token)).to be_falsey
+ context "because it's expired" do
+ it 'returns false' do
+ expired_token = lfs_token.token
+ # Needs to be at least 1860 seconds, because the default expiry is
+ # 1800 seconds with an additional 60 second leeway.
+ Timecop.freeze(Time.now + 1865) do
+ expect(lfs_token.token_valid?(expired_token)).to be_falsey
end
end
end
context 'where the token is valid' do
it 'returns true' do
- generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token
- expect(lfs_token.token_valid?(generated_token)).to be_truthy
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
end
end
end
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
new file mode 100644
index 00000000000..18052b1991c
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::ActiveRecord::Extension do
+ let(:klass) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'issues'
+ include CacheMarkdownField
+ cache_markdown_field :title, whitelisted: true
+ cache_markdown_field :description, pipeline: :single_line
+
+ attr_accessor :author, :project
+ end
+ end
+
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ let(:markdown) { '`Foo`' }
+ let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
+
+ let(:updated_markdown) { '`Bar`' }
+ let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' }
+
+ context 'an unchanged markdown field' do
+ let(:thing) { klass.new(title: markdown) }
+
+ before do
+ thing.title = thing.title
+ thing.save
+ end
+
+ it { expect(thing.title).to eq(markdown) }
+ it { expect(thing.title_html).to eq(html) }
+ it { expect(thing.title_html_changed?).not_to be_truthy }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'a changed markdown field' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ before do
+ thing.title = updated_markdown
+ thing.save
+ end
+
+ it { expect(thing.title_html).to eq(updated_html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'when a markdown field is set repeatedly to an empty string' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.title = ''
+ thing.save
+ thing.title = ''
+ thing.save
+ end
+ end
+
+ context 'when a markdown field is set repeatedly to a string which renders as empty html' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.title = '[//]: # (This is also a comment.)'
+ thing.save
+ thing.title = '[//]: # (This is also a comment.)'
+ thing.save
+ end
+ end
+
+ context 'a non-markdown field changed' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ before do
+ thing.state = 'closed'
+ thing.save
+ end
+
+ it { expect(thing.state).to eq('closed') }
+ it { expect(thing.title).to eq(markdown) }
+ it { expect(thing.title_html).to eq(html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'version is out of date' do
+ let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+
+ before do
+ thing.save
+ end
+
+ it { expect(thing.title_html).to eq(updated_html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'when an invalidating field is changed' do
+ it 'invalidates the cache when project changes' do
+ thing.project = :new_project
+ allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+
+ thing.save
+
+ expect(thing.title_html).to eq(updated_html)
+ expect(thing.description_html).to eq(updated_html)
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+
+ it 'invalidates the cache when author changes' do
+ thing.author = :new_author
+ allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+
+ thing.save
+
+ expect(thing.title_html).to eq(updated_html)
+ expect(thing.description_html).to eq(updated_html)
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+
+ describe '.attributes' do
+ it 'excludes cache attributes that is blacklisted by default' do
+ expect(thing.attributes.keys.sort).not_to include(%w[description_html])
+ end
+ end
+
+ describe '#cached_html_up_to_date?' do
+ let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+ subject { thing.cached_html_up_to_date?(:title) }
+
+ it 'returns false if markdown has been changed but html has not' do
+ thing.title = "changed!"
+
+ is_expected.to be_falsy
+ end
+
+ it 'returns true if markdown has not been changed but html has' do
+ thing.title_html = updated_html
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if markdown and html have both been changed' do
+ thing.title = updated_markdown
+ thing.title_html = updated_html
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns false if the markdown field is set but the html is not' do
+ thing.title_html = nil
+
+ is_expected.to be_falsy
+ end
+ end
+
+ describe '#refresh_markdown_cache!' do
+ before do
+ thing.title = updated_markdown
+ end
+
+ it 'skips saving if not persisted' do
+ expect(thing).to receive(:persisted?).and_return(false)
+ expect(thing).not_to receive(:update_columns)
+
+ thing.refresh_markdown_cache!
+ end
+
+ it 'saves the changes' do
+ expect(thing).to receive(:persisted?).and_return(true)
+
+ expect(thing).to receive(:update_columns)
+ .with("title_html" => updated_html,
+ "description_html" => "",
+ "cached_markdown_version" => cache_version)
+
+ thing.refresh_markdown_cache!
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
new file mode 100644
index 00000000000..393bf85aa43
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::MarkdownCache::FieldData do
+ subject(:field_data) { described_class.new }
+
+ before do
+ field_data[:description] = { project: double('project in context') }
+ end
+
+ it 'translates a markdown field name into a html field name' do
+ expect(field_data.html_field(:description)).to eq("description_html")
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
new file mode 100644
index 00000000000..b6a781de426
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
+ let(:klass) do
+ Class.new do
+ include CacheMarkdownField
+
+ def initialize(title: nil, description: nil)
+ @title, @description = title, description
+ end
+
+ attr_reader :title, :description
+
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
+
+ def cache_key
+ "cache-key"
+ end
+ end
+ end
+
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
+ let(:thing) { klass.new(title: "`Hello`", description: "`World`") }
+ let(:expected_cache_key) { "markdown_cache:cache-key" }
+
+ it 'defines the html attributes' do
+ expect(thing).to respond_to(:title_html, :description_html, :cached_markdown_version)
+ end
+
+ it 'loads the markdown from the cache only once' do
+ expect(thing).to receive(:load_cached_markdown).once.and_call_original
+
+ thing.title_html
+ thing.description_html
+ end
+
+ it 'correctly loads the markdown if it was stored in redis' do
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(expected_cache_key,
+ title_html: 'hello',
+ description_html: 'world',
+ cached_markdown_version: cache_version)
+ end
+
+ expect(thing.title_html).to eq('hello')
+ expect(thing.description_html).to eq('world')
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+
+ describe "#refresh_markdown_cache!" do
+ it "stores the value in redis" do
+ expected_results = { "title_html" => "`Hello`",
+ "description_html" => "<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>",
+ "cached_markdown_version" => cache_version.to_s }
+
+ thing.refresh_markdown_cache!
+
+ results = Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(expected_cache_key,
+ "title_html", "description_html", "cached_markdown_version")
+ end
+
+ expect(results).to eq(expected_results)
+ end
+
+ it "assigns the values" do
+ thing.refresh_markdown_cache!
+
+ expect(thing.title_html).to eq('`Hello`')
+ expect(thing.description_html).to eq("<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>")
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
new file mode 100644
index 00000000000..95c68e7d491
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
+ let(:storable_class) do
+ Class.new do
+ cattr_reader :cached_markdown_fields do
+ Gitlab::MarkdownCache::FieldData.new.tap do |field_data|
+ field_data[:field_1] = {}
+ field_data[:field_2] = {}
+ end
+ end
+
+ attr_accessor :field_1, :field_2, :field_1_html, :field_2_html, :cached_markdown_version
+
+ def cache_key
+ "cache-key"
+ end
+ end
+ end
+ let(:storable) { storable_class.new }
+ let(:cache_key) { "markdown_cache:#{storable.cache_key}" }
+
+ subject(:store) { described_class.new(storable) }
+
+ def read_values
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(cache_key,
+ :field_1_html, :field_2_html, :cached_markdown_version)
+ end
+ end
+
+ def store_values(values)
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(cache_key,
+ values)
+ end
+ end
+
+ describe '#save' do
+ it 'stores updates to html fields and version' do
+ values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
+
+ store.save(values_to_store)
+
+ expect(read_values)
+ .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" })
+ end
+ end
+
+ describe '#read' do
+ it 'reads the html fields and version from redis if they were stored' do
+ stored_values = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
+
+ store_values(stored_values)
+
+ expect(store.read.symbolize_keys).to eq(stored_values)
+ end
+
+ it 'is mared loaded after reading' do
+ expect(store).not_to be_loaded
+
+ store.read
+
+ expect(store).to be_loaded
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
new file mode 100644
index 00000000000..e88eb140b35
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:environment) { build(:environment, project: project) }
+ let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
+
+ describe '.find' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:service_call) { described_class.find(project, nil, environment, dashboard_path) }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+
+ context 'when the dashboard exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid dashboard service response'
+ end
+
+ context 'when the dashboard is configured incorrectly' do
+ let(:project) { project_with_dashboard(dashboard_path, {}) }
+
+ it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
+ end
+
+ context 'when the system dashboard is specified' do
+ let(:dashboard_path) { system_dashboard_path }
+
+ it_behaves_like 'valid dashboard service response'
+ end
+
+ context 'when no dashboard is specified' do
+ let(:service_call) { described_class.find(project, nil, environment) }
+
+ it_behaves_like 'valid dashboard service response'
+ end
+ end
+
+ describe '.find_all_paths' do
+ let(:all_dashboard_paths) { described_class.find_all_paths(project) }
+ let(:system_dashboard) { { path: system_dashboard_path, default: true } }
+
+ it 'includes only the system dashboard by default' do
+ expect(all_dashboard_paths).to eq([system_dashboard])
+ end
+
+ context 'when the project contains dashboards' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it 'includes system and project dashboards' do
+ project_dashboard = { path: dashboard_path, default: false }
+
+ expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
new file mode 100644
index 00000000000..be3c1095bd7
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Processor do
+ let(:project) { build(:project) }
+ let(:environment) { build(:environment, project: project) }
+ let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
+
+ describe 'process' do
+ let(:process_params) { [project, environment, dashboard_yml] }
+ let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: true) }
+
+ context 'when dashboard config corresponds to common metrics' do
+ let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
+
+ it 'inserts metric ids into the config' do
+ target_metric = all_metrics.find { |metric| metric[:id] == 'metric_a1' }
+
+ expect(target_metric).to include(:metric_id)
+ expect(target_metric[:metric_id]).to eq(common_metric.id)
+ end
+ end
+
+ context 'when the project has associated metrics' do
+ let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
+ let!(:project_system_metric) { create(:prometheus_metric, project: project, group: :system) }
+ let!(:project_business_metric) { create(:prometheus_metric, project: project, group: :business) }
+
+ it 'includes project-specific metrics' do
+ expect(all_metrics).to include get_metric_details(project_system_metric)
+ expect(all_metrics).to include get_metric_details(project_response_metric)
+ expect(all_metrics).to include get_metric_details(project_business_metric)
+ end
+
+ it 'orders groups by priority and panels by weight' do
+ expected_metrics_order = [
+ 'metric_b', # group priority 10, panel weight 1
+ 'metric_a2', # group priority 1, panel weight 2
+ 'metric_a1', # group priority 1, panel weight 1
+ project_business_metric.id, # group priority 0, panel weight nil (0)
+ project_response_metric.id, # group priority -5, panel weight nil (0)
+ project_system_metric.id, # group priority -10, panel weight nil (0)
+ ]
+ actual_metrics_order = all_metrics.map { |m| m[:id] || m[:metric_id] }
+
+ expect(actual_metrics_order).to eq expected_metrics_order
+ end
+
+ context 'when the dashboard should not include project metrics' do
+ let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: false) }
+
+ it 'includes only dashboard metrics' do
+ metrics = all_metrics.map { |m| m[:id] }
+
+ expect(metrics.length).to be(3)
+ expect(metrics).to eq %w(metric_b metric_a2 metric_a1)
+ end
+ end
+ end
+
+ shared_examples_for 'errors with message' do |expected_message|
+ it 'raises a DashboardLayoutError' do
+ error_class = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError
+
+ expect { dashboard }.to raise_error(error_class, expected_message)
+ end
+ end
+
+ context 'when the dashboard is missing panel_groups' do
+ let(:dashboard_yml) { {} }
+
+ it_behaves_like 'errors with message', 'Top-level key :panel_groups must be an array'
+ end
+
+ context 'when the dashboard contains a panel_group which is missing panels' do
+ let(:dashboard_yml) { { panel_groups: [{}] } }
+
+ it_behaves_like 'errors with message', 'Each "panel_group" must define an array :panels'
+ end
+
+ context 'when the dashboard contains a panel which is missing metrics' do
+ let(:dashboard_yml) { { panel_groups: [{ panels: [{}] }] } }
+
+ it_behaves_like 'errors with message', 'Each "panel" must define an array :metrics'
+ end
+ end
+
+ private
+
+ def all_metrics
+ dashboard[:panel_groups].map do |group|
+ group[:panels].map { |panel| panel[:metrics] }
+ end.flatten
+ end
+
+ def get_metric_details(metric)
+ {
+ query_range: metric.query,
+ unit: metric.unit,
+ label: metric.legend,
+ metric_id: metric.id
+ }
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
new file mode 100644
index 00000000000..162beb0268a
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:user) { build(:user) }
+ set(:project) { build(:project) }
+ set(:environment) { build(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'get_dashboard' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ context 'when the dashboard does not exist' do
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the dashboard exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid dashboard service response'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect_any_instance_of(described_class)
+ .to receive(:get_raw_dashboard)
+ .once
+ .and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'and the dashboard is then deleted' do
+ it 'does not return the previously cached dashboard' do
+ described_class.new(*service_params).get_dashboard
+
+ delete_project_dashboard(project, user, dashboard_path)
+
+ expect_any_instance_of(described_class)
+ .to receive(:get_raw_dashboard)
+ .once
+ .and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ end
+ end
+ end
+
+ context 'when the dashboard is configured incorrectly' do
+ let(:project) { project_with_dashboard(dashboard_path, {}) }
+
+ it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
new file mode 100644
index 00000000000..e71ce2481a3
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:environment) { build(:environment, project: project) }
+
+ describe 'get_dashboard' do
+ let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH }
+ let(:service_params) { [project, nil, { environment: environment, dashboard_path: dashboard_path }] }
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ it_behaves_like 'valid dashboard service response'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect(YAML).to receive(:safe_load).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'when called with a non-system dashboard' do
+ let(:dashboard_path) { 'garbage/dashboard/path' }
+
+ # We want to alwaus return the system dashboard.
+ it_behaves_like 'valid dashboard service response'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
new file mode 100644
index 00000000000..f4a6e1fc7d9
--- /dev/null
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Samplers::PumaSampler do
+ subject { described_class.new(5) }
+ let(:null_metric) { double('null_metric', set: nil, observe: nil) }
+
+ before do
+ allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
+ end
+
+ describe '#sample' do
+ before do
+ expect(subject).to receive(:puma_stats).and_return(puma_stats)
+ end
+
+ context 'in cluster mode' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "workers": 2,
+ "phase": 2,
+ "booted_workers": 2,
+ "old_workers": 0,
+ "worker_status": [{
+ "pid": 32534,
+ "index": 0,
+ "phase": 1,
+ "booted": true,
+ "last_checkin": "2019-05-15T07:57:55Z",
+ "last_status": {
+ "backlog":0,
+ "running":1,
+ "pool_capacity":4,
+ "max_threads": 4
+ }
+ }]
+ }
+ EOS
+ end
+
+ it 'samples master statistics' do
+ labels = { worker: 'master' }
+
+ expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2)
+ expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2)
+ expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2)
+ expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1)
+
+ subject.sample
+ end
+
+ it 'samples worker statistics' do
+ labels = { worker: 'worker_0' }
+
+ expect_worker_stats(labels)
+
+ subject.sample
+ end
+ end
+
+ context 'with empty worker stats' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "workers": 2,
+ "phase": 2,
+ "booted_workers": 2,
+ "old_workers": 0,
+ "worker_status": [{
+ "pid": 32534,
+ "index": 0,
+ "phase": 1,
+ "booted": true,
+ "last_checkin": "2019-05-15T07:57:55Z",
+ "last_status": {}
+ }]
+ }
+ EOS
+ end
+
+ it 'does not log worker stats' do
+ expect(subject).not_to receive(:set_worker_metrics)
+
+ subject.sample
+ end
+ end
+
+ context 'in single mode' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "backlog":0,
+ "running":1,
+ "pool_capacity":4,
+ "max_threads": 4
+ }
+ EOS
+ end
+
+ it 'samples worker statistics' do
+ labels = {}
+
+ expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 1)
+ expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 1)
+ expect_worker_stats(labels)
+
+ subject.sample
+ end
+ end
+ end
+
+ def expect_worker_stats(labels)
+ expect(subject.metrics[:puma_queued_connections]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_active_connections]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_running]).to receive(:set).with(labels, 1)
+ expect(subject.metrics[:puma_pool_capacity]).to receive(:set).with(labels, 4)
+ expect(subject.metrics[:puma_max_threads]).to receive(:set).with(labels, 4)
+ expect(subject.metrics[:puma_idle_threads]).to receive(:set).with(labels, 1)
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 7972ff253fe..aaf8c9fa2a0 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -10,17 +10,20 @@ describe Gitlab::Metrics::Samplers::RubySampler do
describe '#sample' do
it 'samples various statistics' do
- expect(Gitlab::Metrics::System).to receive(:memory_usage)
+ expect(Gitlab::Metrics::System).to receive(:cpu_time)
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
+ expect(Gitlab::Metrics::System).to receive(:memory_usage)
+ expect(Gitlab::Metrics::System).to receive(:process_start_time)
+ expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors)
expect(sampler).to receive(:sample_gc)
sampler.sample
end
- it 'adds a metric containing the memory usage' do
+ it 'adds a metric containing the process resident memory bytes' do
expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
- expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000)
+ expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
sampler.sample
end
@@ -34,6 +37,27 @@ describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
+ it 'adds a metric containing the process total cpu time' do
+ expect(Gitlab::Metrics::System).to receive(:cpu_time).and_return(0.51)
+ expect(sampler.metrics[:process_cpu_seconds_total]).to receive(:set).with({}, 0.51)
+
+ sampler.sample
+ end
+
+ it 'adds a metric containing the process start time' do
+ expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345)
+ expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345)
+
+ sampler.sample
+ end
+
+ it 'adds a metric containing the process max file descriptors' do
+ expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024)
+ expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024)
+
+ sampler.sample
+ end
+
it 'clears any GC profiles' do
expect(GC::Profiler).to receive(:clear)
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 4b03f3c2532..090e456644f 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -39,8 +39,8 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'unix', socket_address: socket_address }
- expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
- expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+ expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
+ expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
@@ -50,7 +50,6 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
-
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
@@ -71,13 +70,29 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'tcp', socket_address: tcp_socket_address }
- expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
- expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+ expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
+ expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
end
end
+
+ context 'additional metrics' do
+ let(:unicorn_workers) { 2 }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return([""])
+ allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14)
+ allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers)
+ end
+
+ it "sets additional metrics" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers)
+
+ subject.sample
+ end
+ end
end
describe '#start' do
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 14afcdf5daa..b0603d96eb2 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -13,6 +13,18 @@ describe Gitlab::Metrics::System do
expect(described_class.file_descriptor_count).to be > 0
end
end
+
+ describe '.max_open_file_descriptors' do
+ it 'returns the max allowed open file descriptors' do
+ expect(described_class.max_open_file_descriptors).to be > 0
+ end
+ end
+
+ describe '.process_start_time' do
+ it 'returns the process start time' do
+ expect(described_class.process_start_time).to be > 0
+ end
+ end
else
describe '.memory_usage' do
it 'returns 0.0' do
@@ -25,6 +37,18 @@ describe Gitlab::Metrics::System do
expect(described_class.file_descriptor_count).to eq(0)
end
end
+
+ describe '.max_open_file_descriptors' do
+ it 'returns 0' do
+ expect(described_class.max_open_file_descriptors).to eq(0)
+ end
+ end
+
+ describe 'process_start_time' do
+ it 'returns 0' do
+ expect(described_class.process_start_time).to eq(0)
+ end
+ end
end
describe '.cpu_time' do
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
new file mode 100644
index 00000000000..e70fde09edd
--- /dev/null
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Transaction do
+ let(:transaction) { described_class.new }
+ let(:metric) { transaction.metrics[0] }
+
+ let(:sensitive_tags) do
+ {
+ path: 'private',
+ branch: 'sensitive'
+ }
+ end
+
+ shared_examples 'tag filter' do |sane_tags|
+ it 'filters potentially sensitive tags' do
+ expect(metric.tags).to eq(sane_tags)
+ end
+ end
+
+ describe '#duration' do
+ it 'returns the duration of a transaction in seconds' do
+ transaction.run { }
+
+ expect(transaction.duration).to be > 0
+ end
+ end
+
+ describe '#allocated_memory' do
+ it 'returns the allocated memory in bytes' do
+ transaction.run { 'a' * 32 }
+
+ expect(transaction.allocated_memory).to be_a_kind_of(Numeric)
+ end
+ end
+
+ describe '#run' do
+ it 'yields the supplied block' do
+ expect { |b| transaction.run(&b) }.to yield_control
+ end
+
+ it 'stores the transaction in the current thread' do
+ transaction.run do
+ expect(described_class.current).to eq(transaction)
+ end
+ end
+
+ it 'removes the transaction from the current thread upon completion' do
+ transaction.run { }
+
+ expect(described_class.current).to be_nil
+ end
+ end
+
+ describe '#add_metric' do
+ it 'adds a metric to the transaction' do
+ transaction.add_metric('foo', value: 1)
+
+ expect(metric.series).to eq('rails_foo')
+ expect(metric.tags).to eq({})
+ expect(metric.values).to eq(value: 1)
+ end
+
+ context 'with sensitive tags' do
+ before do
+ transaction
+ .add_metric('foo', { value: 1 }, **sensitive_tags.merge(sane: 'yes'))
+ end
+
+ it_behaves_like 'tag filter', sane: 'yes'
+ end
+ end
+
+ describe '#method_call_for' do
+ it 'returns a MethodCall' do
+ method = transaction.method_call_for('Foo#bar', :Foo, '#bar')
+
+ expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
+ end
+ end
+
+ describe '#increment' do
+ it 'increments a counter' do
+ transaction.increment(:time, 1)
+ transaction.increment(:time, 2)
+
+ values = metric_values(time: 3)
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#set' do
+ it 'sets a value' do
+ transaction.set(:number, 10)
+
+ values = metric_values(number: 10)
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#finish' do
+ it 'tracks the transaction details and submits them to Sidekiq' do
+ expect(transaction).to receive(:track_self)
+ expect(transaction).to receive(:submit)
+
+ transaction.finish
+ end
+ end
+
+ describe '#track_self' do
+ it 'adds a metric for the transaction itself' do
+ values = metric_values
+
+ expect(transaction).to receive(:add_metric)
+ .with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#submit' do
+ it 'submits the metrics to Sidekiq' do
+ transaction.track_self
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([an_instance_of(Hash)])
+
+ transaction.submit
+ end
+
+ it 'adds the action as a tag for every metric' do
+ allow(transaction)
+ .to receive(:labels)
+ .and_return(controller: 'Foo', action: 'bar')
+
+ transaction.track_self
+
+ hash = {
+ series: 'rails_transactions',
+ tags: { action: 'Foo#bar' },
+ values: metric_values,
+ timestamp: a_kind_of(Integer)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([hash])
+
+ transaction.submit
+ end
+
+ it 'does not add an action tag for events' do
+ allow(transaction)
+ .to receive(:labels)
+ .and_return(controller: 'Foo', action: 'bar')
+
+ transaction.add_event(:meow)
+
+ hash = {
+ series: 'events',
+ tags: { event: :meow },
+ values: { count: 1 },
+ timestamp: a_kind_of(Integer)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics)
+ .with([hash])
+
+ transaction.submit
+ end
+ end
+
+ describe '#add_event' do
+ it 'adds a metric' do
+ transaction.add_event(:meow)
+
+ expect(metric).to be_an_instance_of(Gitlab::Metrics::Metric)
+ end
+
+ it "does not prefix the metric's series name" do
+ transaction.add_event(:meow)
+
+ expect(metric.series).to eq(described_class::EVENT_SERIES)
+ end
+
+ it 'tracks a counter for every event' do
+ transaction.add_event(:meow)
+
+ expect(metric.values).to eq(count: 1)
+ end
+
+ it 'tracks the event name' do
+ transaction.add_event(:meow)
+
+ expect(metric.tags).to eq(event: :meow)
+ end
+
+ it 'allows tracking of custom tags' do
+ transaction.add_event(:meow, animal: 'cat')
+
+ expect(metric.tags).to eq(event: :meow, animal: 'cat')
+ end
+
+ context 'with sensitive tags' do
+ before do
+ transaction.add_event(:meow, **sensitive_tags.merge(sane: 'yes'))
+ end
+
+ it_behaves_like 'tag filter', event: :meow, sane: 'yes'
+ end
+ end
+
+ private
+
+ def metric_values(opts = {})
+ {
+ duration: 0.0,
+ allocated_memory: a_kind_of(Numeric)
+ }.merge(opts)
+ end
+end
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 187d903a5e1..86bdc479b66 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -28,6 +28,35 @@ describe Gitlab::Middleware::BasicHealthCheck do
end
end
+ context 'with X-Forwarded-For headers' do
+ let(:load_balancer_ip) { '1.2.3.4' }
+
+ before do
+ env['HTTP_X_FORWARDED_FOR'] = "#{load_balancer_ip}, 127.0.0.1"
+ env['REMOTE_ADDR'] = '127.0.0.1'
+ env['PATH_INFO'] = described_class::HEALTH_PATH
+ end
+
+ it 'returns 200 response when endpoint is allowed' do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([load_balancer_ip])
+ expect(app).not_to receive(:call)
+
+ response = middleware.call(env)
+
+ expect(response[0]).to eq(200)
+ expect(response[1]).to eq({ 'Content-Type' => 'text/plain' })
+ expect(response[2]).to eq(['GitLab OK'])
+ end
+
+ it 'returns 404 when whitelist is not configured' do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([])
+
+ response = middleware.call(env)
+
+ expect(response[0]).to eq(404)
+ end
+ end
+
context 'whitelisted IP' do
before do
env['REMOTE_ADDR'] = '127.0.0.1'
diff --git a/spec/lib/gitlab/namespaced_session_store_spec.rb b/spec/lib/gitlab/namespaced_session_store_spec.rb
new file mode 100644
index 00000000000..c0af2ede32a
--- /dev/null
+++ b/spec/lib/gitlab/namespaced_session_store_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::NamespacedSessionStore do
+ let(:key) { :some_key }
+ subject { described_class.new(key) }
+
+ it 'stores data under the specified key' do
+ Gitlab::Session.with_session({}) do
+ subject[:new_data] = 123
+
+ expect(Thread.current[:session_storage][key]).to eq(new_data: 123)
+ end
+ end
+
+ it 'retrieves data from the given key' do
+ Thread.current[:session_storage] = { key => { existing_data: 123 } }
+
+ expect(subject[:existing_data]).to eq 123
+ end
+end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index 4700a7ad2e1..e6e9ae3223e 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -81,6 +81,24 @@ describe Gitlab::ObjectHierarchy, :postgresql do
expect { relation.update_all(share_with_group_lock: false) }
.to raise_error(ActiveRecord::ReadOnlyRecord)
end
+
+ context 'when with_depth is true' do
+ let(:relation) do
+ described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true)
+ end
+
+ it 'includes depth in the results' do
+ object_depths = {
+ parent.id => 1,
+ child1.id => 2,
+ child2.id => 3
+ }
+
+ relation.each do |object|
+ expect(object.depth).to eq(object_depths[object.id])
+ end
+ end
+ end
end
describe '#descendants' do
@@ -91,6 +109,28 @@ describe Gitlab::ObjectHierarchy, :postgresql do
end
end
+ describe '#max_descendants_depth' do
+ subject { described_class.new(base_relation).max_descendants_depth }
+
+ context 'when base relation is empty' do
+ let(:base_relation) { Group.where(id: nil) }
+
+ it { expect(subject).to be_nil }
+ end
+
+ context 'when base has no children' do
+ let(:base_relation) { Group.where(id: child2) }
+
+ it { expect(subject).to eq(1) }
+ end
+
+ context 'when base has grandchildren' do
+ let(:base_relation) { Group.where(id: parent) }
+
+ it { expect(subject).to eq(3) }
+ end
+ end
+
describe '#ancestors' do
it 'includes only the ancestors' do
relation = described_class.new(Group.where(id: child2)).ancestors
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index d808b4d49e0..32296caf819 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -38,6 +38,28 @@ describe Gitlab::OmniauthInitializer do
subject.execute([hash_config])
end
+ it 'normalizes a String strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::OAuth2Generic' } }
+
+ expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic)
+
+ subject.execute([hash_config])
+ end
+
+ it 'allows a class to be specified in strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: OmniAuth::Strategies::OAuth2Generic } }
+
+ expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic)
+
+ subject.execute([hash_config])
+ end
+
+ it 'throws an error for an invalid strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::Bogus' } }
+
+ expect { subject.execute([hash_config]) }.to raise_error(NameError)
+ end
+
it 'configures fail_with_empty_uid for shibboleth' do
shibboleth_config = { 'name' => 'shibboleth', 'args' => {} }
@@ -61,5 +83,13 @@ describe Gitlab::OmniauthInitializer do
subject.execute([cas3_config])
end
+
+ it 'configures name for openid_connect' do
+ openid_connect_config = { 'name' => 'openid_connect', 'args' => {} }
+
+ expect(devise_config).to receive(:omniauth).with(:openid_connect, name: 'openid_connect')
+
+ subject.execute([openid_connect_config])
+ end
end
end
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 81f81d4f963..6fdf61ee0a7 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::OptimisticLocking do
describe '#retry_lock' do
it 'does not reload object if state changes' do
- expect(pipeline).not_to receive(:reload)
+ expect(pipeline).not_to receive(:reset)
expect(pipeline).to receive(:succeed).and_call_original
described_class.retry_lock(pipeline) do |subject|
@@ -17,7 +17,7 @@ describe Gitlab::OptimisticLocking do
it 'retries action if exception is raised' do
pipeline.succeed
- expect(pipeline2).to receive(:reload).and_call_original
+ expect(pipeline2).to receive(:reset).and_call_original
expect(pipeline2).to receive(:drop).twice.and_call_original
described_class.retry_lock(pipeline2) do |subject|
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index e90e0aba0a4..84b2e2dc823 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -100,7 +100,7 @@ describe Gitlab::PathRegex do
end
let(:ee_top_level_words) do
- ['unsubscribes']
+ %w(unsubscribes v2)
end
let(:files_in_public) do
@@ -120,10 +120,10 @@ describe Gitlab::PathRegex do
# - Followed by one or more path-parts not starting with `:` or `*`
# - Followed by a path-part that includes a wildcard parameter `*`
# At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
- STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
- NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
- ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
- WILDCARD_SEGMENT = /\*/
+ STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}.freeze
+ NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}.freeze
+ ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}.freeze
+ WILDCARD_SEGMENT = /\*/.freeze
let(:namespaced_wildcard_routes) do
routes_without_format.select do |p|
p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
@@ -144,7 +144,7 @@ describe Gitlab::PathRegex do
end.uniq
end
- STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
+ STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}.freeze
let(:group_routes) do
routes_without_format.select do |path|
path =~ STARTING_WITH_GROUP
diff --git a/spec/lib/gitlab/phabricator_import/base_worker_spec.rb b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
new file mode 100644
index 00000000000..d46d908a3e3
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::BaseWorker do
+ let(:subclass) do
+ # Creating an anonymous class for a worker is complicated, as we generate the
+ # queue name from the class name.
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ end
+
+ describe '.schedule' do
+ let(:arguments) { %w[project_id the_next_page] }
+
+ it 'schedules the job' do
+ expect(subclass).to receive(:perform_async).with(*arguments)
+
+ subclass.schedule(*arguments)
+ end
+
+ it 'counts the scheduled job', :clean_gitlab_redis_shared_state do
+ state = Gitlab::PhabricatorImport::WorkerState.new('project_id')
+
+ allow(subclass).to receive(:remove_job) # otherwise the job is removed before we saw it
+
+ expect { subclass.schedule(*arguments) }.to change { state.running_count }.by(1)
+ end
+ end
+
+ describe '#perform' do
+ let(:project) { create(:project, :import_started, import_url: "https://a.phab.instance") }
+ let(:worker) { subclass.new }
+ let(:state) { Gitlab::PhabricatorImport::WorkerState.new(project.id) }
+
+ before do
+ allow(worker).to receive(:import)
+ end
+
+ it 'does not break for a non-existing project' do
+ expect { worker.perform('not a thing') }.not_to raise_error
+ end
+
+ it 'does not do anything when the import is not in progress' do
+ project = create(:project, :import_failed)
+
+ expect(worker).not_to receive(:import)
+
+ worker.perform(project.id)
+ end
+
+ it 'calls import for the project' do
+ expect(worker).to receive(:import).with(project, 'other_arg')
+
+ worker.perform(project.id, 'other_arg')
+ end
+
+ it 'marks the project as imported if there was only one job running' do
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_finished
+ end
+
+ it 'does not mark the job as finished when there are more scheduled jobs' do
+ 2.times { state.add_job }
+
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_in_progress
+ end
+
+ it 'decrements the job counter' do
+ expect { worker.perform(project.id) }.to change { state.running_count }.by(-1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
new file mode 100644
index 00000000000..52c7a02219f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
+ set(:project) { create(:project) }
+ let(:redis) { Gitlab::Redis::Cache }
+ subject(:map) { described_class.new(project) }
+
+ describe '#get_gitlab_model' do
+ it 'returns nil if there was nothing cached for the phabricator id' do
+ expect(map.get_gitlab_model('does not exist')).to be_nil
+ end
+
+ it 'returns the object if it was set in redis' do
+ issue = create(:issue, project: project)
+ set_in_redis('exists', issue)
+
+ expect(map.get_gitlab_model('exists')).to eq(issue)
+ end
+
+ it 'extends the TTL for the cache key' do
+ set_in_redis('extend', create(:issue, project: project)) do |redis|
+ redis.expire(cache_key('extend'), 10.seconds.to_i)
+ end
+
+ map.get_gitlab_model('extend')
+
+ ttl = redis.with { |redis| redis.ttl(cache_key('extend')) }
+
+ expect(ttl).to be > 10.seconds
+ end
+ end
+
+ describe '#set_gitlab_model' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'sets the class and id in redis with a ttl' do
+ issue = create(:issue, project: project)
+
+ map.set_gitlab_model(issue, 'it is set')
+
+ set_data, ttl = redis.with do |redis|
+ redis.pipelined do |p|
+ p.mapped_hmget(cache_key('it is set'), :classname, :database_id)
+ p.ttl(cache_key('it is set'))
+ end
+ end
+
+ expect(set_data).to eq({ classname: 'Issue', database_id: issue.id.to_s })
+ expect(ttl).to be_within(1.second).of(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ end
+ end
+
+ def set_in_redis(key, object)
+ redis.with do |redis|
+ redis.mapped_hmset(cache_key(key),
+ { classname: object.class, database_id: object.id })
+ yield(redis) if block_given?
+ end
+ end
+
+ def cache_key(phabricator_id)
+ subject.__send__(:cache_key_for_phabricator_id, phabricator_id)
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
new file mode 100644
index 00000000000..542b3cd060f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Client do
+ let(:client) do
+ described_class.new('https://see-ya-later.phabricator', 'api-token')
+ end
+
+ describe '#get' do
+ it 'performs and parses a request' do
+ params = { some: 'extra', values: %w[are passed] }
+ stub_valid_request(params)
+
+ response = client.get('test', params: params)
+
+ expect(response).to be_a(Gitlab::PhabricatorImport::Conduit::Response)
+ expect(response).to be_success
+ end
+
+ it 'wraps request errors in an `ApiError`' do
+ stub_timeout
+
+ expect { client.get('test') }.to raise_error(Gitlab::PhabricatorImport::Conduit::ApiError)
+ end
+
+ it 'raises response error' do
+ stub_error_response
+
+ expect { client.get('test') }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /has the wrong length/)
+ end
+ end
+
+ def stub_valid_request(params = {})
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).with(
+ body: CGI.unescape(params.reverse_merge('api.token' => 'api-token').to_query)
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json')
+ )
+ end
+
+ def stub_timeout
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).to_timeout
+ end
+
+ def stub_error_response
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json')
+ )
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
new file mode 100644
index 00000000000..0d7714649b9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Maniphest do
+ let(:maniphest) do
+ described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
+ end
+
+ describe '#tasks' do
+ let(:fake_client) { double('Phabricator client') }
+
+ before do
+ allow(maniphest).to receive(:client).and_return(fake_client)
+ end
+
+ it 'calls the api with the correct params' do
+ expected_params = {
+ after: '123',
+ attachments: {
+ projects: 1, subscribers: 1, columns: 1
+ }
+ }
+
+ expect(fake_client).to receive(:get).with('maniphest.search',
+ params: expected_params)
+
+ maniphest.tasks(after: '123')
+ end
+
+ it 'returns a parsed response' do
+ response = Gitlab::PhabricatorImport::Conduit::Response
+ .new(fixture_file('phabricator_responses/maniphest.search.json'))
+
+ allow(fake_client).to receive(:get).and_return(response)
+
+ expect(maniphest.tasks).to be_a(Gitlab::PhabricatorImport::Conduit::TasksResponse)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
new file mode 100644
index 00000000000..a8596968f14
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Response do
+ let(:response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
+ let(:error_response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
+
+ describe '.parse!' do
+ it 'raises a ResponseError if the http response was not successfull' do
+ fake_response = double(:http_response, success?: false, status: 401)
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /responded with 401/)
+ end
+
+ it 'raises a ResponseError if the response contained a Phabricator error' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json'))
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /ERR-INVALID-AUTH: API token/)
+ end
+
+ it 'raises a ResponseError if JSON parsing failed' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: 'This is no JSON')
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/)
+ end
+
+ it 'returns a parsed response for valid input' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json'))
+
+ expect(described_class.parse!(fake_response)).to be_a(described_class)
+ end
+ end
+
+ describe '#success?' do
+ it { expect(response).to be_success }
+ it { expect(error_response).not_to be_success }
+ end
+
+ describe '#error_code' do
+ it { expect(error_response.error_code).to eq('ERR-INVALID-AUTH') }
+ it { expect(response.error_code).to be_nil }
+ end
+
+ describe '#error_info' do
+ it 'returns the correct error info' do
+ expected_message = 'API token "api-token" has the wrong length. API tokens should be 32 characters long.'
+
+ expect(error_response.error_info).to eq(expected_message)
+ end
+
+ it { expect(response.error_info).to be_nil }
+ end
+
+ describe '#data' do
+ it { expect(error_response.data).to be_nil }
+ it { expect(response.data).to be_an(Array) }
+ end
+
+ describe '#pagination' do
+ it { expect(error_response.pagination).to be_nil }
+
+ it 'builds the pagination correctly' do
+ expect(response.pagination).to be_a(Gitlab::PhabricatorImport::Conduit::Pagination)
+ expect(response.pagination.next_page).to eq('284')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
new file mode 100644
index 00000000000..4b4c2a6276e
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
+ let(:conduit_response) do
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ end
+
+ subject(:response) { described_class.new(conduit_response) }
+
+ describe '#pagination' do
+ it 'delegates to the conduit reponse' do
+ expect(response.pagination).to eq(conduit_response.pagination)
+ end
+ end
+
+ describe '#tasks' do
+ it 'builds the correct tasks representation' do
+ tasks = response.tasks
+
+ titles = tasks.map(&:issue_attributes).map { |attrs| attrs[:title] }
+
+ expect(titles).to contain_exactly('Things are slow', 'Things are broken')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
new file mode 100644
index 00000000000..1e38ef8aaa5
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ImportTasksWorker do
+ describe '#perform' do
+ it 'calls the correct importer' do
+ project = create(:project, :import_started, import_url: "https://the.phab.ulr")
+ fake_importer = instance_double(Gitlab::PhabricatorImport::Issues::Importer)
+
+ expect(Gitlab::PhabricatorImport::Issues::Importer).to receive(:new).with(project).and_return(fake_importer)
+ expect(fake_importer).to receive(:execute)
+
+ described_class.new.perform(project.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
new file mode 100644
index 00000000000..bf14010a187
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Importer do
+ it { expect(described_class).to be_async }
+
+ it "acts like it's importing repositories" do
+ expect(described_class).to be_imports_repository
+ end
+
+ describe '#execute' do
+ let(:project) { create(:project, :import_scheduled) }
+ subject(:importer) { described_class.new(project) }
+
+ it 'sets a custom jid that will be kept up to date' do
+ expect { importer.execute }.to change { project.import_state.reload.jid }
+ end
+
+ it 'starts importing tasks' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker).to receive(:schedule).with(project.id)
+
+ importer.execute
+ end
+
+ it 'marks the import as failed when something goes wrong' do
+ allow(importer).to receive(:schedule_first_tasks_page).and_raise('Stuff is broken')
+
+ importer.execute
+
+ expect(project.import_state).to be_failed
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
new file mode 100644
index 00000000000..2412cf76f79
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::Importer do
+ set(:project) { create(:project) }
+
+ let(:response) do
+ Gitlab::PhabricatorImport::Conduit::TasksResponse.new(
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ )
+ end
+
+ subject(:importer) { described_class.new(project, nil) }
+
+ before do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest)
+
+ allow(client).to receive(:tasks).and_return(response)
+ allow(importer).to receive(:client).and_return(client)
+ end
+
+ describe '#execute' do
+ it 'imports each task in the response' do
+ response.tasks.each do |task|
+ task_importer = instance_double(Gitlab::PhabricatorImport::Issues::TaskImporter)
+
+ expect(task_importer).to receive(:execute)
+ expect(Gitlab::PhabricatorImport::Issues::TaskImporter)
+ .to receive(:new).with(project, task)
+ .and_return(task_importer)
+ end
+
+ importer.execute
+ end
+
+ it 'schedules the next batch if there is one' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .to receive(:schedule).with(project.id, response.pagination.next_page)
+
+ importer.execute
+ end
+
+ it 'does not reschedule when there is no next page' do
+ allow(response.pagination).to receive(:has_next_page?).and_return(false)
+
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .not_to receive(:schedule)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
new file mode 100644
index 00000000000..1625604e754
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::TaskImporter do
+ set(:project) { create(:project) }
+ let(:task) do
+ Gitlab::PhabricatorImport::Representation::Task.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title',
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#execute' do
+ it 'creates the issue with the expected attributes' do
+ issue = described_class.new(project, task).execute
+
+ expect(issue.project).to eq(project)
+ expect(issue).to be_persisted
+ expect(issue.author).to eq(User.ghost)
+ expect(issue.title).to eq('Title')
+ expect(issue.description).to eq('# This is markdown\n it can contain more text.')
+ expect(issue).to be_closed
+ expect(issue.created_at).to eq(Time.at(1518688921))
+ expect(issue.closed_at).to eq(Time.at(1518789995))
+ end
+
+ it 'does not recreate the issue when called multiple times' do
+ expect { described_class.new(project, task).execute }
+ .to change { project.issues.reload.size }.from(0).to(1)
+ expect { described_class.new(project, task).execute }
+ .not_to change { project.issues.reload.size }
+ end
+
+ it 'does not trigger a save when the object did not change' do
+ existing_issue = create(:issue,
+ task.issue_attributes.merge(author: User.ghost))
+ importer = described_class.new(project, task)
+ allow(importer).to receive(:issue).and_return(existing_issue)
+
+ expect(existing_issue).not_to receive(:save!)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
new file mode 100644
index 00000000000..e9455b866ac
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ProjectCreator do
+ let(:user) { create(:user) }
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+ subject(:creator) { described_class.new(user, params) }
+
+ describe '#execute' do
+ it 'creates a project correctly and schedule an import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ project = creator.execute
+
+ expect(project).to be_persisted
+ expect(project).to be_import
+ expect(project.import_type).to eq('phabricator')
+ expect(project.import_data.credentials).to match(a_hash_including(api_token: 'the-token'))
+ expect(project.import_data.data).to match(a_hash_including('phabricator_url' => 'http://phab.example.com'))
+ expect(project.import_url).to eq(Project::UNKNOWN_IMPORT_URL)
+ expect(project.namespace).to eq(user.namespace)
+ end
+
+ context 'when import params are missing' do
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: '' }
+ end
+
+ it 'returns nil' do
+ expect(creator.execute).to be_nil
+ end
+ end
+
+ context 'when import params are invalid' do
+ let(:params) do
+ { path: 'new-phab-import',
+ namespace_id: '-1',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+
+ it 'returns an unpersisted project' do
+ project = creator.execute
+
+ expect(project).not_to be_persisted
+ expect(project).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
new file mode 100644
index 00000000000..dfbd8c546eb
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Representation::Task do
+ subject(:task) do
+ described_class.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#issue_attributes' do
+ it 'contains the expected values' do
+ expected_attributes = {
+ title: 'Title'.ljust(255, '.'),
+ description: '# This is markdown\n it can contain more text.',
+ state: :closed,
+ created_at: Time.at(1518688921),
+ closed_at: Time.at(1518789995)
+ }
+
+ expect(task.issue_attributes).to eq(expected_attributes)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
new file mode 100644
index 00000000000..a44947445c9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
+ subject(:state) { described_class.new('weird-project-id') }
+ let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
+
+ describe '#add_job' do
+ it 'increments the counter for jobs' do
+ set_value(3)
+
+ expect { state.add_job }.to change { get_value }.from('3').to('4')
+ end
+ end
+
+ describe '#remove_job' do
+ it 'decrements the counter for jobs' do
+ set_value(3)
+
+ expect { state.remove_job }.to change { get_value }.from('3').to('2')
+ end
+ end
+
+ describe '#running_count' do
+ it 'reads the value' do
+ set_value(9)
+
+ expect(state.running_count).to eq(9)
+ end
+
+ it 'returns 0 when nothing was set' do
+ expect(state.running_count).to eq(0)
+ end
+ end
+
+ def set_value(value)
+ redis.with { |r| r.set(key, value) }
+ end
+
+ def get_value
+ redis.with { |r| r.get(key) }
+ end
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+end
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 8bb0c1a0b8a..5af52db7a1f 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe Gitlab::Profiler do
- RSpec::Matchers.define_negated_matcher :not_change, :change
-
let(:null_logger) { Logger.new('/dev/null') }
let(:private_token) { 'private' }
@@ -29,13 +27,13 @@ describe Gitlab::Profiler do
it 'sends a POST request when data is passed' do
post_data = '{"a":1}'
- expect(app).to receive(:post).with(anything, post_data, anything)
+ expect(app).to receive(:post).with(anything, params: post_data, headers: anything)
described_class.profile('/', post_data: post_data)
end
it 'uses the private_token for auth if given' do
- expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token })
expect(app).to receive(:get).with('/api/v4/users')
described_class.profile('/', private_token: private_token)
@@ -53,7 +51,7 @@ describe Gitlab::Profiler do
user = double(:user)
expect(described_class).to receive(:with_user).with(nil).and_call_original
- expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token })
expect(app).to receive(:get).with('/api/v4/users')
described_class.profile('/', user: user, private_token: private_token)
@@ -187,7 +185,7 @@ describe Gitlab::Profiler do
end
it 'does not modify the standard Rails loggers' do
- expect { described_class.with_custom_logger(nil) { } }
+ expect { described_class.with_custom_logger(nil) {} }
.to not_change { ActiveRecord::Base.logger }
.and not_change { ActionController::Base.logger }
.and not_change { ActiveSupport::LogSubscriber.colorize_logging }
@@ -204,7 +202,7 @@ describe Gitlab::Profiler do
end
it 'cleans up ApplicationController afterwards' do
- expect { described_class.with_user(user) { } }
+ expect { described_class.with_user(user) {} }
.to not_change { ActionController.instance_methods(false) }
end
end
@@ -213,7 +211,7 @@ describe Gitlab::Profiler do
it 'does not define methods on ApplicationController' do
expect(ApplicationController).not_to receive(:define_method)
- described_class.with_user(nil) { }
+ described_class.with_user(nil) {}
end
end
end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 6831274d37c..4a41d5cf51e 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -412,4 +412,36 @@ describe Gitlab::ProjectSearchResults do
end
end
end
+
+ describe 'user search' do
+ it 'returns the user belonging to the project matching the search query' do
+ project = create(:project)
+
+ user1 = create(:user, username: 'gob_bluth')
+ create(:project_member, :developer, user: user1, project: project)
+
+ user2 = create(:user, username: 'michael_bluth')
+ create(:project_member, :developer, user: user2, project: project)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, project, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the group matching the search query' do
+ group = create(:group)
+ project = create(:project, namespace: group)
+
+ user1 = create(:user, username: 'gob_bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, project, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+ end
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 0cee100b64e..8c2fc048a54 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -7,7 +7,10 @@ describe Gitlab::ProjectTemplate do
described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'),
described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'),
described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'),
- described_class.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'),
+ described_class.new('iosswift', 'iOS (Swift)', 'A ready-to-go template for use with iOS Swift apps.', 'https://gitlab.com/gitlab-org/project-templates/iosswift'),
+ described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'),
+ described_class.new('android', 'Android', 'A ready-to-go template for use with Android apps.', 'https://gitlab.com/gitlab-org/project-templates/android'),
+ described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'),
described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'),
described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'),
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index 5a88b23aa82..a6589f0c0a3 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -9,9 +9,35 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
let(:query_params) { [environment.id] }
it 'queries using specific time' do
- expect(client).to receive(:query_range).with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
-
+ expect(client).to receive(:query_range)
+ .with(anything, start: 8.hours.ago.to_f, stop: Time.now.to_f)
expect(query_result).not_to be_nil
end
+
+ context 'when start and end time parameters are provided' do
+ let(:query_params) { [environment.id, start_time, end_time] }
+
+ context 'as unix timestamps' do
+ let(:start_time) { 4.hours.ago.to_f }
+ let(:end_time) { 2.hours.ago.to_f }
+
+ it 'queries using the provided times' do
+ expect(client).to receive(:query_range)
+ .with(anything, start: start_time, stop: end_time)
+ expect(query_result).not_to be_nil
+ end
+ end
+
+ context 'as Date/Time objects' do
+ let(:start_time) { 4.hours.ago }
+ let(:end_time) { 2.hours.ago }
+
+ it 'queries using the provided times converted to unix' do
+ expect(client).to receive(:query_range)
+ .with(anything, start: start_time.to_f, stop: end_time.to_f)
+ expect(query_result).not_to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
new file mode 100644
index 00000000000..7f6283715f2
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do
+ include PrometheusHelpers
+
+ let(:project) { create(:project) }
+ let(:serverless_func) { Serverless::Function.new(project, 'test-name', 'test-ns') }
+
+ let(:client) { double('prometheus_client') }
+ subject { described_class.new(client) }
+
+ context 'verify queries' do
+ before do
+ allow(PrometheusMetric).to receive(:find_by_identifier).and_return(create(:prometheus_metric, query: prometheus_istio_query('test-name', 'test-ns')))
+ allow(client).to receive(:query_range)
+ end
+
+ it 'has the query, but no data' do
+ results = subject.query(serverless_func.id)
+
+ expect(results.queries[0][:query_range]).to eql('floor(sum(rate(istio_revision_request_count{destination_configuration="test-name", destination_namespace="test-ns"}[1m])*30))')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 420218a695a..936447b8474 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
[{ '__name__' => 'metric_a' },
{ '__name__' => 'metric_b' }]
end
- let(:partialy_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
+ let(:partially_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
let(:empty_series_info) { [] }
let(:client) { double('prometheus_client') }
@@ -60,7 +60,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
context 'one of the series info was not found' do
before do
- allow(client).to receive(:series).and_return(partialy_empty_series_info)
+ allow(client).to receive(:series).and_return(partially_empty_series_info)
end
it 'responds with one active and one missing metric' do
expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 1 }])
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 78c74266c61..6dc99ef26ec 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Prometheus::QueryVariables do
describe '.call' do
+ let(:project) { environment.project }
let(:environment) { create(:environment) }
let(:slug) { environment.slug }
@@ -21,13 +22,32 @@ describe Gitlab::Prometheus::QueryVariables do
end
context 'with deployment platform' do
- let(:kube_namespace) { environment.deployment_platform.actual_namespace }
+ context 'with project cluster' do
+ let(:kube_namespace) { environment.deployment_platform.cluster.kubernetes_namespace_for(project) }
- before do
- create(:cluster, :provided_by_user, projects: [environment.project])
+ before do
+ create(:cluster, :project, :provided_by_user, projects: [project])
+ end
+
+ it { is_expected.to include(kube_namespace: kube_namespace) }
end
- it { is_expected.to include(kube_namespace: kube_namespace) }
+ context 'with group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_user, groups: [group]) }
+ let(:group) { create(:group) }
+ let(:project2) { create(:project) }
+ let(:kube_namespace) { k8s_ns.namespace }
+
+ let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project) }
+ let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2) }
+
+ before do
+ group.projects << project
+ group.projects << project2
+ end
+
+ it { is_expected.to include(kube_namespace: kube_namespace) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 4c3b8deefb9..f15ae83a02c 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -60,15 +60,13 @@ describe Gitlab::PrometheusClient do
end
describe 'failure to reach a provided prometheus url' do
- let(:prometheus_url) {"https://prometheus.invalid.example.com"}
+ let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"}
- subject { described_class.new(RestClient::Resource.new(prometheus_url)) }
-
- context 'exceptions are raised' do
+ shared_examples 'exceptions are raised' do
it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError)
- expect { subject.send(:get, '/', {}) }
+ expect { subject }
.to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}")
expect(req_stub).to have_been_requested
end
@@ -76,7 +74,7 @@ describe Gitlab::PrometheusClient do
it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError)
- expect { subject.send(:get, '/', {}) }
+ expect { subject }
.to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data")
expect(req_stub).to have_been_requested
end
@@ -84,11 +82,23 @@ describe Gitlab::PrometheusClient do
it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception)
- expect { subject.send(:get, '/', {}) }
+ expect { subject }
.to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
expect(req_stub).to have_been_requested
end
end
+
+ context 'ping' do
+ subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping }
+
+ it_behaves_like 'exceptions are raised'
+ end
+
+ context 'proxy' do
+ subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) }
+
+ it_behaves_like 'exceptions are raised'
+ end
end
describe '#query' do
@@ -230,4 +240,87 @@ describe Gitlab::PrometheusClient do
let(:execute_query) { subject.query_range(prometheus_query) }
end
end
+
+ describe '.compute_step' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:now) { Time.now.utc }
+
+ subject { described_class.compute_step(start, stop) }
+
+ where(:time_interval_in_seconds, :step) do
+ 0 | 60
+ 10.hours | 60
+ 10.hours + 1 | 61
+ # frontend options
+ 30.minutes | 60
+ 3.hours | 60
+ 8.hours | 60
+ 1.day | 144
+ 3.days | 432
+ 1.week | 1008
+ end
+
+ with_them do
+ let(:start) { now - time_interval_in_seconds }
+ let(:stop) { now }
+
+ it { is_expected.to eq(step) }
+ end
+ end
+
+ describe 'proxy' do
+ context 'get API' do
+ let(:prometheus_query) { prometheus_cpu_query('env-slug') }
+ let(:query_url) { prometheus_query_url(prometheus_query) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'when response status code is 200' do
+ it 'returns response object' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector'))
+
+ response = subject.proxy('query', { query: prometheus_query })
+ json_response = JSON.parse(response.body)
+
+ expect(response.code).to eq(200)
+ expect(json_response).to eq({
+ 'status' => 'success',
+ 'data' => {
+ 'resultType' => 'vector',
+ 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }]
+ }
+ })
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when response status code is not 200' do
+ it 'returns response object' do
+ req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'error' })
+
+ response = subject.proxy('query', { query: prometheus_query })
+ json_response = JSON.parse(response.body)
+
+ expect(req_stub).to have_been_requested
+ expect(response.code).to eq(400)
+ expect(json_response).to eq('error' => 'error')
+ end
+ end
+
+ context 'when RestClient::Exception is raised' do
+ before do
+ stub_prometheus_request_with_exception(query_url, RestClient::Exception)
+ end
+
+ it 'raises PrometheusClient::Error' do
+ expect { subject.proxy('query', { query: prometheus_query }) }.to(
+ raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
+ )
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb
new file mode 100644
index 00000000000..fc9e421bea6
--- /dev/null
+++ b/spec/lib/gitlab/push_options_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::PushOptions do
+ describe 'namespace and key validation' do
+ it 'ignores unrecognised namespaces' do
+ options = described_class.new(['invalid.key=value'])
+
+ expect(options.get(:invalid)).to eq(nil)
+ end
+
+ it 'ignores unrecognised keys' do
+ options = described_class.new(['merge_request.key=value'])
+
+ expect(options.get(:merge_request)).to eq(nil)
+ end
+
+ it 'ignores blank keys' do
+ options = described_class.new(['merge_request'])
+
+ expect(options.get(:merge_request)).to eq(nil)
+ end
+
+ it 'parses recognised namespace and key pairs' do
+ options = described_class.new(['merge_request.target=value'])
+
+ expect(options.get(:merge_request)).to include({
+ target: 'value'
+ })
+ end
+ end
+
+ describe '#get' do
+ it 'can emulate Hash#dig' do
+ options = described_class.new(['merge_request.target=value'])
+
+ expect(options.get(:merge_request, :target)).to eq('value')
+ end
+ end
+
+ describe '#as_json' do
+ it 'returns all options' do
+ options = described_class.new(['merge_request.target=value'])
+
+ expect(options.as_json).to include(
+ merge_request: {
+ target: 'value'
+ }
+ )
+ end
+ end
+
+ it 'can parse multiple push options' do
+ options = described_class.new([
+ 'merge_request.create',
+ 'merge_request.target=value'
+ ])
+
+ expect(options.get(:merge_request)).to include({
+ create: true,
+ target: 'value'
+ })
+ expect(options.get(:merge_request, :create)).to eq(true)
+ expect(options.get(:merge_request, :target)).to eq('value')
+ end
+
+ it 'stores options internally as a HashWithIndifferentAccess' do
+ options = described_class.new([
+ 'merge_request.create'
+ ])
+
+ expect(options.get('merge_request', 'create')).to eq(true)
+ expect(options.get(:merge_request, :create)).to eq(true)
+ end
+
+ it 'selects the last option when options contain duplicate namespace and key pairs' do
+ options = described_class.new([
+ 'merge_request.target=value1',
+ 'merge_request.target=value2'
+ ])
+
+ expect(options.get(:merge_request, :target)).to eq('value2')
+ end
+
+ it 'defaults values to true' do
+ options = described_class.new(['merge_request.create'])
+
+ expect(options.get(:merge_request, :create)).to eq(true)
+ end
+
+ it 'expands aliases' do
+ options = described_class.new(['mr.target=value'])
+
+ expect(options.get(:merge_request, :target)).to eq('value')
+ end
+
+ it 'forgives broken push options' do
+ options = described_class.new(['merge_request . target = value'])
+
+ expect(options.get(:merge_request, :target)).to eq('value')
+ end
+end
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 5dae82a63b4..b6e0adbc1c2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -69,10 +69,40 @@ describe Gitlab::QuickActions::CommandDefinition do
expect(subject.available?(opts)).to be true
end
end
+
+ context "when the command has types" do
+ before do
+ subject.types = [Issue, Commit]
+ end
+
+ context "when the command target type is allowed" do
+ it "returns true" do
+ opts[:quick_action_target] = Issue.new
+ expect(subject.available?(opts)).to be true
+ end
+ end
+
+ context "when the command target type is not allowed" do
+ it "returns true" do
+ opts[:quick_action_target] = MergeRequest.new
+ expect(subject.available?(opts)).to be false
+ end
+ end
+ end
+
+ context "when the command has no types" do
+ it "any target type is allowed" do
+ opts[:quick_action_target] = Issue.new
+ expect(subject.available?(opts)).to be true
+
+ opts[:quick_action_target] = MergeRequest.new
+ expect(subject.available?(opts)).to be true
+ end
+ end
end
describe "#execute" do
- let(:context) { OpenStruct.new(run: false) }
+ let(:context) { OpenStruct.new(run: false, commands_executed_count: nil) }
context "when the command is a noop" do
it "doesn't execute the command" do
@@ -80,6 +110,7 @@ describe Gitlab::QuickActions::CommandDefinition do
subject.execute(context, nil)
+ expect(context.commands_executed_count).to be_nil
expect(context.run).to be false
end
end
@@ -97,6 +128,7 @@ describe Gitlab::QuickActions::CommandDefinition do
it "doesn't execute the command" do
subject.execute(context, nil)
+ expect(context.commands_executed_count).to be_nil
expect(context.run).to be false
end
end
@@ -112,6 +144,7 @@ describe Gitlab::QuickActions::CommandDefinition do
subject.execute(context, true)
expect(context.run).to be true
+ expect(context.commands_executed_count).to eq(1)
end
end
@@ -120,6 +153,7 @@ describe Gitlab::QuickActions::CommandDefinition do
subject.execute(context, nil)
expect(context.run).to be true
+ expect(context.commands_executed_count).to eq(1)
end
end
end
@@ -134,6 +168,7 @@ describe Gitlab::QuickActions::CommandDefinition do
subject.execute(context, true)
expect(context.run).to be true
+ expect(context.commands_executed_count).to eq(1)
end
end
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index fd4df8694ba..185adab1ff6 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do
substitution :something do |text|
"#{text} Some complicated thing you want in here"
end
+
+ desc 'A command with types'
+ types Issue, Commit
+ command :has_types do
+ "Has Issue and Commit types"
+ end
end
end
describe '.command_definitions' do
it 'returns an array with commands definitions' do
no_args_def, explanation_with_aliases_def, dynamic_description_def,
- cc_def, cond_action_def, with_params_parsing_def, substitution_def =
+ cc_def, cond_action_def, with_params_parsing_def, substitution_def, has_types =
DummyClass.command_definitions
expect(no_args_def.name).to eq(:no_args)
@@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.explanation).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
+ expect(no_args_def.types).to eq([])
expect(no_args_def.action_block).to be_a_kind_of(Proc)
expect(no_args_def.parse_params_block).to be_nil
expect(no_args_def.warning).to eq('')
@@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
+ expect(explanation_with_aliases_def.types).to eq([])
expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc)
expect(explanation_with_aliases_def.parse_params_block).to be_nil
expect(explanation_with_aliases_def.warning).to eq('Possible problem!')
@@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.explanation).to eq('')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
+ expect(dynamic_description_def.types).to eq([])
expect(dynamic_description_def.action_block).to be_a_kind_of(Proc)
expect(dynamic_description_def.parse_params_block).to be_nil
expect(dynamic_description_def.warning).to eq('')
@@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.explanation).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
+ expect(cc_def.types).to eq([])
expect(cc_def.action_block).to be_nil
expect(cc_def.parse_params_block).to be_nil
expect(cc_def.warning).to eq('')
@@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
+ expect(cond_action_def.types).to eq([])
expect(cond_action_def.action_block).to be_a_kind_of(Proc)
expect(cond_action_def.parse_params_block).to be_nil
expect(cond_action_def.warning).to eq('')
@@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.explanation).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
+ expect(with_params_parsing_def.types).to eq([])
expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.warning).to eq('')
@@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.explanation).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
+ expect(substitution_def.types).to eq([])
expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here')
expect(substitution_def.parse_params_block).to be_nil
expect(substitution_def.warning).to eq('')
+
+ expect(has_types.name).to eq(:has_types)
+ expect(has_types.aliases).to eq([])
+ expect(has_types.description).to eq('A command with types')
+ expect(has_types.explanation).to eq('')
+ expect(has_types.params).to eq([])
+ expect(has_types.condition_block).to be_nil
+ expect(has_types.types).to eq([Issue, Commit])
+ expect(has_types.action_block).to be_a_kind_of(Proc)
+ expect(has_types.parse_params_block).to be_nil
+ expect(has_types.warning).to eq('')
end
end
end
diff --git a/spec/lib/gitlab/rack_timeout_observer_spec.rb b/spec/lib/gitlab/rack_timeout_observer_spec.rb
new file mode 100644
index 00000000000..3dc1a8b68fb
--- /dev/null
+++ b/spec/lib/gitlab/rack_timeout_observer_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RackTimeoutObserver do
+ let(:counter) { Gitlab::Metrics::NullMetric.instance }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(any_args)
+ .and_return(counter)
+ end
+
+ describe '#callback' do
+ context 'when request times out' do
+ let(:env) do
+ {
+ ::Rack::Timeout::ENV_INFO_KEY => double(state: :timed_out),
+ 'action_dispatch.request.parameters' => {
+ 'controller' => 'foo',
+ 'action' => 'bar'
+ }
+ }
+ end
+
+ subject { described_class.new }
+
+ it 'increments timeout counter' do
+ expect(counter)
+ .to receive(:increment)
+ .with({ controller: 'foo', action: 'bar', route: nil, state: :timed_out })
+
+ subject.callback.call(env)
+ end
+ end
+
+ context 'when request expires' do
+ let(:endpoint) { double }
+ let(:env) do
+ {
+ ::Rack::Timeout::ENV_INFO_KEY => double(state: :expired),
+ Grape::Env::API_ENDPOINT => endpoint
+ }
+ end
+
+ subject { described_class.new }
+
+ it 'increments timeout counter' do
+ allow(endpoint).to receive_message_chain('route.pattern.origin') { 'foobar' }
+ expect(counter)
+ .to receive(:increment)
+ .with({ controller: nil, action: nil, route: 'foobar', state: :expired })
+
+ subject.callback.call(env)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 4139d1c650c..d982053d92e 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ReferenceExtractor do
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 13940713dfc..8fbda929064 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -6,43 +6,49 @@ describe ::Gitlab::RepoPath do
context 'a repository storage path' do
it 'parses a full repository path' do
- expect(described_class.parse(project.repository.full_path)).to eq([project, false, nil])
+ expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
it 'parses a full wiki path' do
- expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, true, nil])
+ expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil])
end
end
context 'a relative path' do
it 'parses a relative repository path' do
- expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil])
+ expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil])
+ expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil])
+ expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
context 'of a redirected project' do
let(:redirect) { project.route.create_redirect('foo/bar') }
it 'parses a relative repository path' do
- expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki'])
+ expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki'])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
end
end
end
+
+ it "returns the default type for non existent paths" do
+ _project, type, _redirected = described_class.parse("path/non-existent.git")
+
+ expect(type).to eq(Gitlab::GlRepository.default_type)
+ end
end
describe '.find_project' do
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index fd443cc1f71..23e45aff1c5 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -6,6 +6,31 @@ describe Gitlab::RequestContext do
let(:app) { -> (env) {} }
let(:env) { Hash.new }
+ context 'with X-Forwarded-For headers', :request_store do
+ let(:load_balancer_ip) { '1.2.3.4' }
+ let(:headers) do
+ {
+ 'HTTP_X_FORWARDED_FOR' => "#{load_balancer_ip}, 127.0.0.1",
+ 'REMOTE_ADDR' => '127.0.0.1'
+ }
+ end
+
+ let(:env) { Rack::MockRequest.env_for("/").merge(headers) }
+
+ it 'returns the load balancer IP' do
+ client_ip = nil
+
+ endpoint = proc do
+ client_ip = Gitlab::SafeRequestStore[:client_ip]
+ [200, {}, ["Hello"]]
+ end
+
+ described_class.new(endpoint).call(env)
+
+ expect(client_ip).to eq(load_balancer_ip)
+ end
+ end
+
context 'when RequestStore::Middleware is used' do
around do |example|
RequestStore::Middleware.new(-> (env) { example.run }).call({})
@@ -15,7 +40,7 @@ describe Gitlab::RequestContext do
let(:ip) { '192.168.1.11' }
before do
- allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
described_class.new(app).call(env)
end
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index d672f7b5675..a39c774429e 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -60,7 +60,7 @@ describe Gitlab::RouteMap do
subject do
map = described_class.new(<<-"MAP".strip_heredoc)
- - source: '#{malicious_regexp}'
+ - source: '#{malicious_regexp_re2}'
public: '/'
MAP
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
new file mode 100644
index 00000000000..bd5f330c7a1
--- /dev/null
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe Gitlab::Sanitizers::Exif do
+ let(:sanitizer) { described_class.new }
+
+ describe '#batch_clean' do
+ context 'with image uploads' do
+ let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) }
+
+ it 'processes all uploads if range ID is not set' do
+ expect(sanitizer).to receive(:clean).exactly(3).times
+
+ sanitizer.batch_clean
+ end
+
+ it 'processes only uploads in the selected range' do
+ expect(sanitizer).to receive(:clean).once
+
+ sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id)
+ end
+
+ it 'pauses if sleep_time is set' do
+ expect(sanitizer).to receive(:sleep).exactly(3).times.with(1.second)
+ expect(sanitizer).to receive(:clean).exactly(3).times
+
+ sanitizer.batch_clean(sleep_time: 1)
+ end
+ end
+
+ it 'filters only jpg/tiff images' do
+ create(:upload, path: 'filename.jpg')
+ create(:upload, path: 'filename.jpeg')
+ create(:upload, path: 'filename.JPG')
+ create(:upload, path: 'filename.tiff')
+ create(:upload, path: 'filename.TIFF')
+ create(:upload, path: 'filename.png')
+ create(:upload, path: 'filename.txt')
+
+ expect(sanitizer).to receive(:clean).exactly(5).times
+ sanitizer.batch_clean
+ end
+ end
+
+ describe '#clean' do
+ let(:uploader) { create(:upload, :with_file, :issuable_upload).build_uploader }
+
+ context "no dry run" do
+ it "removes exif from the image" do
+ uploader.store!(fixture_file_upload('spec/fixtures/rails_sample.jpg'))
+
+ original_upload = uploader.upload
+ expected_args = ["exiftool", "-all=", "-tagsFromFile", "@", *Gitlab::Sanitizers::Exif::EXCLUDE_PARAMS, "--IPTC:all", "--XMP-iptcExt:all", kind_of(String)]
+
+ expect(sanitizer).to receive(:extra_tags).and_return(["", 0])
+ expect(sanitizer).to receive(:exec_remove_exif!).once.and_call_original
+ expect(uploader).to receive(:store!).and_call_original
+ expect(Gitlab::Popen).to receive(:popen).with(expected_args) do |args|
+ File.write("#{args.last}_original", "foo") if args.last.start_with?(Dir.tmpdir)
+
+ [expected_args, 0]
+ end
+
+ sanitizer.clean(uploader, dry_run: false)
+
+ expect(uploader.upload.id).not_to eq(original_upload.id)
+ expect(uploader.upload.path).to eq(original_upload.path)
+ end
+
+ it "ignores image without exif" do
+ expected_args = ["exiftool", "-all", "-j", "-sort", "--IPTC:all", "--XMP-iptcExt:all", kind_of(String)]
+
+ expect(Gitlab::Popen).to receive(:popen).with(expected_args).and_return(["[{}]", 0])
+ expect(sanitizer).not_to receive(:exec_remove_exif!)
+ expect(uploader).not_to receive(:store!)
+
+ sanitizer.clean(uploader, dry_run: false)
+ end
+
+ it "raises an error if the exiftool fails with an error" do
+ expect(Gitlab::Popen).to receive(:popen).and_return(["error", 1])
+
+ expect { sanitizer.clean(uploader, dry_run: false) }.to raise_exception(RuntimeError, "failed to get exif tags: error")
+ end
+ end
+
+ context "dry run" do
+ it "doesn't change the image" do
+ expect(sanitizer).to receive(:extra_tags).and_return({ 'foo' => 'bar' })
+ expect(sanitizer).not_to receive(:exec_remove_exif!)
+ expect(uploader).not_to receive(:store!)
+
+ sanitizer.clean(uploader, dry_run: true)
+ end
+ end
+ end
+
+ describe "#extra_tags" do
+ it "returns a list of keys for exif file" do
+ tags = '[{
+ "DigitalSourceType": "some source",
+ "ImageHeight": 654
+ }]'
+
+ expect(Gitlab::Popen).to receive(:popen).and_return([tags, 0])
+
+ expect(sanitizer.extra_tags('filename')).not_to be_empty
+ end
+
+ it "returns an empty list for file with only whitelisted and ignored tags" do
+ tags = '[{
+ "ImageHeight": 654,
+ "Megapixels": 0.641
+ }]'
+
+ expect(Gitlab::Popen).to receive(:popen).and_return([tags, 0])
+
+ expect(sanitizer.extra_tags('some file')).to be_empty
+ end
+ end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 87288baedb0..3d27156b356 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -97,7 +97,7 @@ describe Gitlab::SearchResults do
results.objects('merge_requests')
end
- it 'it skips project filter if default project context is used' do
+ it 'skips project filter if default project context is used' do
allow(results).to receive(:default_project_filter).and_return(true)
expect(results).not_to receive(:project_ids_relation)
@@ -113,7 +113,7 @@ describe Gitlab::SearchResults do
results.objects('issues')
end
- it 'it skips project filter if default project context is used' do
+ it 'skips project filter if default project context is used' do
allow(results).to receive(:default_project_filter).and_return(true)
expect(results).not_to receive(:project_ids_relation)
@@ -121,6 +121,22 @@ describe Gitlab::SearchResults do
results.objects('issues')
end
end
+
+ describe '#users' do
+ it 'does not call the UsersFinder when the current_user is not allowed to read users list' do
+ allow(Ability).to receive(:allowed?).and_return(false)
+
+ expect(UsersFinder).not_to receive(:new).with(user, search: 'foo').and_call_original
+
+ results.objects('users')
+ end
+
+ it 'calls the UsersFinder' do
+ expect(UsersFinder).to receive(:new).with(user, search: 'foo').and_call_original
+
+ results.objects('users')
+ end
+ end
end
it 'does not list issues on private projects' do
@@ -240,4 +256,28 @@ describe Gitlab::SearchResults do
expect(results.objects('merge_requests')).not_to include merge_request
end
+
+ context 'milestones' do
+ it 'returns correct set of milestones' do
+ private_project_1 = create(:project, :private)
+ private_project_2 = create(:project, :private)
+ internal_project = create(:project, :internal)
+ public_project_1 = create(:project, :public)
+ public_project_2 = create(:project, :public, :issues_disabled, :merge_requests_disabled)
+ private_project_1.add_developer(user)
+ # milestones that should not be visible
+ create(:milestone, project: private_project_2, title: 'Private project without access milestone')
+ create(:milestone, project: public_project_2, title: 'Public project with milestones disabled milestone')
+ # milestones that should be visible
+ milestone_1 = create(:milestone, project: private_project_1, title: 'Private project with access milestone', state: 'closed')
+ milestone_2 = create(:milestone, project: internal_project, title: 'Internal project milestone')
+ milestone_3 = create(:milestone, project: public_project_1, title: 'Public project with milestones enabled milestone')
+ # Global search scope takes user authorized projects, internal projects and public projects.
+ limit_projects = ProjectsFinder.new(current_user: user).execute
+
+ milestones = described_class.new(user, limit_projects, 'milestone').objects('milestones')
+
+ expect(milestones).to match_array([milestone_1, milestone_2, milestone_3])
+ end
+ end
end
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index 1128eaf8560..af8b059b984 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -2,12 +2,15 @@ require 'spec_helper'
describe Gitlab::Sentry do
describe '.context' do
- it 'adds the locale to the tags' do
+ it 'adds the expected tags' do
expect(described_class).to receive(:enabled?).and_return(true)
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
described_class.context(nil)
expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s)
+ expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s)
+ .to eq('cid')
end
end
@@ -27,7 +30,7 @@ describe Gitlab::Sentry do
context 'when exceptions should not be raised' do
before do
allow(described_class).to receive(:should_raise_for_dev?).and_return(false)
- allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
end
it 'logs the exception with all attributes passed' do
@@ -65,7 +68,7 @@ describe Gitlab::Sentry do
before do
allow(described_class).to receive(:enabled?).and_return(true)
- allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid')
end
it 'calls Raven.capture_exception' do
diff --git a/spec/lib/gitlab/session_spec.rb b/spec/lib/gitlab/session_spec.rb
new file mode 100644
index 00000000000..8db73f0ec7b
--- /dev/null
+++ b/spec/lib/gitlab/session_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Session do
+ it 'uses the current thread as a data store' do
+ Thread.current[:session_storage] = { a: :b }
+
+ expect(described_class.current).to eq(a: :b)
+ ensure
+ Thread.current[:session_storage] = nil
+ end
+
+ describe '#with_session' do
+ it 'sets session hash' do
+ described_class.with_session(one: 1) do
+ expect(described_class.current).to eq(one: 1)
+ end
+ end
+
+ it 'restores current store after' do
+ described_class.with_session(two: 2) { }
+
+ expect(described_class.current).to eq nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 033e1bf81a1..bce2e754176 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -8,6 +8,7 @@ describe Gitlab::Shell do
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
+ let(:gitlab_authorized_keys) { double }
before do
allow(Project).to receive(:find).and_return(project)
@@ -49,13 +50,38 @@ describe Gitlab::Shell do
describe '#add_key' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ it 'calls #gitlab_shell_fast_execute with add-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'add-key',
+ 'key-123',
+ 'ssh-rsa foobar'
+ ])
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
+ end
end
end
@@ -64,10 +90,24 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
end
end
@@ -76,24 +116,89 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with add-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'add-key',
+ 'key-123',
+ 'ssh-rsa foobar'
+ ])
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
+ end
end
end
end
describe '#batch_add_keys' do
+ let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] }
+
context 'when authorized_keys_enabled is true' do
- it 'instantiates KeyAdder' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+ context 'authorized_keys_file not set' do
+ let(:io) { double }
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ context 'valid keys' do
+ before do
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls gitlab-keys with batch-add-keys command' do
+ expect(IO)
+ .to receive(:popen)
+ .with("gitlab_shell_keys_path batch-add-keys", 'w')
+ .and_yield(io)
+
+ expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
+ expect(gitlab_shell.batch_add_keys(keys)).to be_truthy
+ end
+ end
+
+ context 'invalid keys' do
+ let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
+
+ it 'catches failure and returns false' do
+ expect(gitlab_shell.batch_add_keys(keys)).to be_falsey
+ end
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
+
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -103,11 +208,23 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(IO).not_to receive(:popen)
+
+ gitlab_shell.batch_add_keys(keys)
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -117,11 +234,37 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'instantiates KeyAdder' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+ context 'authorized_keys_file not set' do
+ let(:io) { double }
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls gitlab-keys with batch-add-keys command' do
+ expect(IO)
+ .to receive(:popen)
+ .with("gitlab_shell_keys_path batch-add-keys", 'w')
+ .and_yield(io)
+
+ expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
+
+ gitlab_shell.batch_add_keys(keys)
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
+
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -129,13 +272,34 @@ describe Gitlab::Shell do
describe '#remove_key' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with rm-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'rm-key',
+ 'key-123'
+ ])
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+
+ context 'authorized_keys_file not set' do
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
+
+ gitlab_shell.remove_key('key-123')
+ end
end
end
@@ -144,10 +308,24 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.remove_key('key-123')
+ end
end
end
@@ -156,232 +334,256 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with rm-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'rm-key',
+ 'key-123'
+ ])
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.remove_key('key-123')
+ end
end
- end
- context 'when key content is not given' do
- it 'calls rm-key with only one argument' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123']
- )
+ context 'authorized_keys_file not set' do
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- gitlab_shell.remove_key('key-123')
+ gitlab_shell.remove_key('key-123')
+ end
end
end
end
describe '#remove_all_keys' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear'])
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- gitlab_shell.remove_all_keys
- end
- end
+ it 'calls #gitlab_shell_fast_execute with clear command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([:gitlab_shell_keys_path, 'clear'])
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
+ gitlab_shell.remove_all_keys
+ end
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- gitlab_shell.remove_all_keys
+ gitlab_shell.remove_all_keys
+ end
end
end
- context 'when authorized_keys_enabled is nil' do
+ context 'when authorized_keys_enabled is false' do
before do
- stub_application_setting(authorized_keys_enabled: nil)
+ stub_application_setting(authorized_keys_enabled: false)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'clear']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
- gitlab_shell.remove_all_keys
- end
- end
- end
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
- describe '#remove_keys_not_found_in_db' do
- context 'when keys are in the file that are not in the DB' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
+ gitlab_shell.remove_all_keys
+ end
end
- it 'removes the keys' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
- expect(find_in_authorized_keys_file(9876)).to be_truthy
- expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
- expect(find_in_authorized_keys_file(9876)).to be_falsey
- expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+
+ gitlab_shell.remove_all_keys
+ end
end
end
- context 'when keys there are duplicate keys in the file that are not in the DB' do
+ context 'when authorized_keys_enabled is nil' do
before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes the keys' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
- end
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- it 'does not run remove more than once per key (in a batch)' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234').once
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
+ it 'calls #gitlab_shell_fast_execute with clear command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([:gitlab_shell_keys_path, 'clear'])
- context 'when keys there are duplicate keys in the file that ARE in the DB' do
- before do
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
+ gitlab_shell.remove_all_keys
+ end
end
- it 'does not remove the key' do
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(@key.id)).to be_truthy
- end
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- it 'does not need to run a SELECT query for that batch, on account of that key' do
- expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck)
- gitlab_shell.remove_keys_not_found_in_db
+ gitlab_shell.remove_all_keys
+ end
end
end
+ end
- unless ENV['CI'] # Skip in CI, it takes 1 minute
- context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ describe '#remove_keys_not_found_in_db' do
+ context 'when keys are in the file that are not in the DB' do
+ context 'authorized_keys_file not set' do
before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- it 'removes the keys not in the DB' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
+
gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
end
end
- end
- end
- describe '#batch_read_key_ids' do
- context 'when there are keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
- (1..4).each do |i|
- gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- end
- it 'iterates over the key IDs in the file, in batches' do
- loop_count = 0
- first_batch = [1, 2]
- second_batch = [3, 4]
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
- gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch|
- expected = (loop_count == 0 ? first_batch : second_batch)
- expect(batch).to eq(expected)
- loop_count += 1
+ gitlab_shell.remove_keys_not_found_in_db
end
end
end
- end
- describe '#list_key_ids' do
- context 'when there are keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
- (1..4).each do |i|
- gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ context 'when keys there are duplicate keys in the file that are not in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+
+ gitlab_shell.remove_keys_not_found_in_db
end
end
- it 'outputs the key IDs in the file, separated by newlines' do
- ids = []
- gitlab_shell.list_key_ids do |io|
- io.each do |line|
- ids << line
- end
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- expect(ids).to eq(%W{1\n 2\n 3\n 4\n})
- end
- end
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- context 'when there are no keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
+ end
- it 'outputs nothing, not even an empty string' do
- ids = []
- gitlab_shell.list_key_ids do |io|
- io.each do |line|
- ids << line
- end
+ context 'when keys there are duplicate keys in the file that ARE in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
end
- expect(ids).to eq([])
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
+
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
- end
- end
- describe Gitlab::Shell::KeyAdder do
- describe '#add_key' do
- it 'removes trailing garbage' do
- io = spy(:io)
- adder = described_class.new(io)
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
+ end
- adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
- expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
+ end
- it 'handles multiple spaces in the key' do
- io = spy(:io)
- adder = described_class.new(io)
+ unless ENV['CI'] # Skip in CI, it takes 1 minute
+ context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
- adder.add_key('key-42', "ssh-rsa foo")
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
- end
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
- it 'raises an exception if the key contains a tab' do
- expect do
- described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
- end.to raise_error(Gitlab::Shell::Error)
- end
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- it 'raises an exception if the key contains a newline' do
- expect do
- described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
- end.to raise_error(Gitlab::Shell::Error)
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
end
end
end
@@ -393,7 +595,6 @@ describe Gitlab::Shell do
before do
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
- allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
@@ -411,16 +612,6 @@ describe Gitlab::Shell do
FileUtils.rm_rf(created_path)
end
- it 'creates a repository' do
- expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy
-
- expect(File.stat(created_path).mode & 0o777).to eq(0o770)
-
- hooks_path = File.join(created_path, 'hooks')
- expect(File.lstat(hooks_path)).to be_symlink
- expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path)
- end
-
it 'returns false when the command fails' do
FileUtils.mkdir_p(File.dirname(created_path))
# This file will block the creation of the repo's .git directory. That
@@ -567,12 +758,4 @@ describe Gitlab::Shell do
end
end
end
-
- def find_in_authorized_keys_file(key_id)
- gitlab_shell.batch_read_key_ids do |ids|
- return true if ids.include?(key_id) # rubocop:disable Cop/AvoidReturnFromBlocks
- end
-
- false
- end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb
index a138ad7c910..0ff694d409b 100644
--- a/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::SidekiqMiddleware::CorrelationInjector do
it 'injects into payload the correlation id' do
expect_any_instance_of(described_class).to receive(:call).and_call_original
- Gitlab::CorrelationId.use_id('new-correlation-id') do
+ Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do
TestWorker.perform_async(1234)
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb
index 94ae4ffa184..8410467ef1f 100644
--- a/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::SidekiqMiddleware::CorrelationLogger do
expect_any_instance_of(described_class).to receive(:call).and_call_original
expect_any_instance_of(TestWorker).to receive(:perform).with(1234) do
- expect(Gitlab::CorrelationId.current_id).to eq('new-correlation-id')
+ expect(Labkit::Correlation::CorrelationId.current_id).to eq('new-correlation-id')
end
Sidekiq::Client.push(
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
new file mode 100644
index 00000000000..1a5a38b5d99
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::MemoryKiller do
+ subject { described_class.new }
+ let(:pid) { 999 }
+
+ let(:worker) { double(:worker, class: 'TestWorker') }
+ let(:job) { { 'jid' => 123 } }
+ let(:queue) { 'test_queue' }
+
+ def run
+ thread = subject.call(worker, job, queue) { nil }
+ thread&.join
+ end
+
+ before do
+ allow(subject).to receive(:get_rss).and_return(10.kilobytes)
+ allow(subject).to receive(:pid).and_return(pid)
+ end
+
+ context 'when MAX_RSS is set to 0' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 0)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
+ end
+
+ it 'sends the TSTP, TERM and KILL signals at expected times' do
+ expect(subject).to receive(:sleep).with(15 * 60).ordered
+ expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
+
+ expect(subject).to receive(:sleep).with(30).ordered
+ expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
+
+ expect(subject).to receive(:sleep).with(10).ordered
+ expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+
+ run
+ end
+
+ it 'sends TSTP and TERM to the pid, but KILL to the pgroup, when running as process leader' do
+ allow(Process).to receive(:getpgrp) { pid }
+ allow(subject).to receive(:sleep)
+
+ expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
+ expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
+ expect(Process).to receive(:kill).with('SIGKILL', 0).ordered
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is not exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb b/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb
deleted file mode 100644
index 0001795c3f0..00000000000
--- a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::SidekiqMiddleware::Shutdown do
- subject { described_class.new }
-
- let(:pid) { Process.pid }
- let(:worker) { double(:worker, class: 'TestWorker') }
- let(:job) { { 'jid' => 123 } }
- let(:queue) { 'test_queue' }
- let(:block) { proc { nil } }
-
- def run
- subject.call(worker, job, queue) { block.call }
- described_class.shutdown_thread&.join
- end
-
- def pop_trace
- subject.trace.pop(true)
- end
-
- before do
- allow(subject).to receive(:get_rss).and_return(10.kilobytes)
- described_class.clear_shutdown_thread
- end
-
- context 'when MAX_RSS is set to 0' do
- before do
- stub_const("#{described_class}::MAX_RSS", 0)
- end
-
- it 'does nothing' do
- expect(subject).not_to receive(:sleep)
-
- run
- end
- end
-
- def expect_shutdown_sequence
- expect(pop_trace).to eq([:sleep, 15 * 60])
- expect(pop_trace).to eq([:kill, 'SIGTSTP', pid])
-
- expect(pop_trace).to eq([:sleep, 30])
- expect(pop_trace).to eq([:kill, 'SIGTERM', pid])
-
- expect(pop_trace).to eq([:sleep, 10])
- expect(pop_trace).to eq([:kill, 'SIGKILL', pid])
- end
-
- context 'when MAX_RSS is exceeded' do
- before do
- stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
- end
-
- it 'sends the TSTP, TERM and KILL signals at expected times' do
- run
-
- expect_shutdown_sequence
- end
- end
-
- context 'when MAX_RSS is not exceeded' do
- before do
- stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
- end
-
- it 'does nothing' do
- expect(subject).not_to receive(:sleep)
-
- run
- end
- end
-
- context 'when WantShutdown is raised' do
- let(:block) { proc { raise described_class::WantShutdown } }
-
- it 'starts the shutdown sequence and re-raises the exception' do
- expect { run }.to raise_exception(described_class::WantShutdown)
-
- # We can't expect 'run' to have joined on the shutdown thread, because
- # it hit an exception.
- shutdown_thread = described_class.shutdown_thread
- expect(shutdown_thread).not_to be_nil
- shutdown_thread.join
-
- expect_shutdown_sequence
- end
- end
-end
diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb
new file mode 100644
index 00000000000..77ecd1840d2
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_signals_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqSignals do
+ describe '.install' do
+ let(:result) { Hash.new { |h, k| h[k] = 0 } }
+ let(:int_handler) { -> (_) { result['INT'] += 1 } }
+ let(:term_handler) { -> (_) { result['TERM'] += 1 } }
+ let(:other_handler) { -> (_) { result['OTHER'] += 1 } }
+ let(:signals) { { 'INT' => int_handler, 'TERM' => term_handler, 'OTHER' => other_handler } }
+
+ context 'not a process group leader' do
+ before do
+ allow(Process).to receive(:getpgrp) { Process.pid * 2 }
+ end
+
+ it 'does nothing' do
+ expect { described_class.install!(signals) }
+ .not_to change { signals }
+ end
+ end
+
+ context 'as a process group leader' do
+ before do
+ allow(Process).to receive(:getpgrp) { Process.pid }
+ end
+
+ it 'installs its own signal handlers for TERM and INT only' do
+ described_class.install!(signals)
+
+ expect(signals['INT']).not_to eq(int_handler)
+ expect(signals['TERM']).not_to eq(term_handler)
+ expect(signals['OTHER']).to eq(other_handler)
+ end
+
+ %w[INT TERM].each do |signal|
+ it "installs a forwarding signal handler for #{signal}" do
+ described_class.install!(signals)
+
+ expect(described_class)
+ .to receive(:trap)
+ .with(signal, 'IGNORE')
+ .and_return(:original_trap)
+ .ordered
+
+ expect(Process)
+ .to receive(:kill)
+ .with(signal, 0)
+ .ordered
+
+ expect(described_class)
+ .to receive(:trap)
+ .with(signal, :original_trap)
+ .ordered
+
+ signals[signal].call(:cli)
+
+ expect(result[signal]).to eq(1)
+ end
+
+ it "raises if sidekiq no longer traps SIG#{signal}" do
+ signals.delete(signal)
+
+ expect { described_class.install!(signals) }
+ .to raise_error(/Sidekiq should have registered/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index fe46c67a920..5f0a7e925ca 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -15,6 +15,13 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
expect(all).to include('Docker')
expect(all).to include('Ruby')
end
+
+ it 'ensure that the template name is used exactly once' do
+ all = subject.all.group_by(&:name)
+ duplicates = all.select { |_, templates| templates.length > 1 }
+
+ expect(duplicates).to be_empty
+ end
end
describe '.find' do
diff --git a/spec/lib/gitlab/tracing/factory_spec.rb b/spec/lib/gitlab/tracing/factory_spec.rb
deleted file mode 100644
index 945490f0988..00000000000
--- a/spec/lib/gitlab/tracing/factory_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::Tracing::Factory do
- describe '.create_tracer' do
- let(:service_name) { 'rspec' }
-
- context "when tracing is not configured" do
- it 'ignores null connection strings' do
- expect(described_class.create_tracer(service_name, nil)).to be_nil
- end
-
- it 'ignores empty connection strings' do
- expect(described_class.create_tracer(service_name, '')).to be_nil
- end
-
- it 'ignores unknown implementations' do
- expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil
- end
-
- it 'ignores invalid connection strings' do
- expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil
- end
- end
-
- context "when tracing is configured with jaeger" do
- let(:mock_tracer) { double('tracer') }
-
- it 'processes default connections' do
- expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer)
-
- expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer)
- end
-
- it 'processes connections with parameters' do
- expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer)
-
- expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb b/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
deleted file mode 100644
index 7f5aecb7baa..00000000000
--- a/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::Tracing::GRPCInterceptor do
- subject { described_class.instance }
-
- shared_examples_for "a grpc interceptor method" do
- let(:custom_error) { Class.new(StandardError) }
-
- it 'yields' do
- expect { |b| method.call(kwargs, &b) }.to yield_control
- end
-
- it 'propagates exceptions' do
- expect { method.call(kwargs) { raise custom_error } }.to raise_error(custom_error)
- end
- end
-
- describe '#request_response' do
- let(:method) { subject.method(:request_response) }
- let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
-
- it_behaves_like 'a grpc interceptor method'
- end
-
- describe '#client_streamer' do
- let(:method) { subject.method(:client_streamer) }
- let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
-
- it_behaves_like 'a grpc interceptor method'
- end
-
- describe '#server_streamer' do
- let(:method) { subject.method(:server_streamer) }
- let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
-
- it_behaves_like 'a grpc interceptor method'
- end
-
- describe '#bidi_streamer' do
- let(:method) { subject.method(:bidi_streamer) }
- let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
-
- it_behaves_like 'a grpc interceptor method'
- end
-end
diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
deleted file mode 100644
index 3d6a007cfd9..00000000000
--- a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::Tracing::JaegerFactory do
- describe '.create_tracer' do
- let(:service_name) { 'rspec' }
-
- shared_examples_for 'a jaeger tracer' do
- it 'responds to active_span methods' do
- expect(tracer).to respond_to(:active_span)
- end
-
- it 'yields control' do
- expect { |b| tracer.start_active_span('operation_name', &b) }.to yield_control
- end
- end
-
- context 'processes default connections' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, {}) }
- end
- end
-
- context 'handles debug options' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { debug: "1" }) }
- end
- end
-
- context 'handles const sampler' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" }) }
- end
- end
-
- context 'handles probabilistic sampler' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" }) }
- end
- end
-
- context 'handles http_endpoint configurations' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" }) }
- end
- end
-
- context 'handles udp_endpoint configurations' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" }) }
- end
- end
-
- context 'ignores invalid parameters' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { invalid: "true" }) }
- end
- end
-
- context 'accepts the debug parameter when strict_parser is set' do
- it_behaves_like 'a jaeger tracer' do
- let(:tracer) { described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" }) }
- end
- end
-
- it 'rejects invalid parameters when strict_parser is set' do
- expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError)
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/rack_middleware_spec.rb b/spec/lib/gitlab/tracing/rack_middleware_spec.rb
deleted file mode 100644
index 13d4d8a89f7..00000000000
--- a/spec/lib/gitlab/tracing/rack_middleware_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Tracing::RackMiddleware do
- using RSpec::Parameterized::TableSyntax
-
- describe '#call' do
- context 'for normal middleware flow' do
- let(:fake_app) { -> (env) { fake_app_response } }
- subject { described_class.new(fake_app) }
- let(:request) { }
-
- context 'for 200 responses' do
- let(:fake_app_response) { [200, { 'Content-Type': 'text/plain' }, ['OK']] }
-
- it 'delegates correctly' do
- expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
- end
- end
-
- context 'for 500 responses' do
- let(:fake_app_response) { [500, { 'Content-Type': 'text/plain' }, ['Error']] }
-
- it 'delegates correctly' do
- expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
- end
- end
- end
-
- context 'when an application is raising an exception' do
- let(:custom_error) { Class.new(StandardError) }
- let(:fake_app) { ->(env) { raise custom_error } }
-
- subject { described_class.new(fake_app) }
-
- it 'delegates propagates exceptions correctly' do
- expect { subject.call(Rack::MockRequest.env_for("/")) }.to raise_error(custom_error)
- end
- end
- end
-
- describe '.build_sanitized_url_from_env' do
- def env_for_url(url)
- env = Rack::MockRequest.env_for(input_url)
- env['action_dispatch.parameter_filter'] = [/token/]
-
- env
- end
-
- where(:input_url, :output_url) do
- '/gitlab-org/gitlab-ce' | 'http://example.org/gitlab-org/gitlab-ce'
- '/gitlab-org/gitlab-ce?safe=1' | 'http://example.org/gitlab-org/gitlab-ce?safe=1'
- '/gitlab-org/gitlab-ce?private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?private_token=%5BFILTERED%5D'
- '/gitlab-org/gitlab-ce?mixed=1&private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?mixed=1&private_token=%5BFILTERED%5D'
- end
-
- with_them do
- it { expect(described_class.build_sanitized_url_from_env(env_for_url(input_url))).to eq(output_url) }
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb
deleted file mode 100644
index c9d1a06b3e6..00000000000
--- a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb
+++ /dev/null
@@ -1,147 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-
-describe Gitlab::Tracing::Rails::ActionViewSubscriber do
- using RSpec::Parameterized::TableSyntax
-
- shared_examples 'an actionview notification' do
- it 'should notify the tracer when the hash contains null values' do
- expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception)
-
- subject.public_send(notify_method, start, finish, payload)
- end
-
- it 'should notify the tracer when the payload is missing values' do
- expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception)
-
- subject.public_send(notify_method, start, finish, payload.compact)
- end
-
- it 'should not throw exceptions when with the default tracer' do
- expect { subject.public_send(notify_method, start, finish, payload) }.not_to raise_error
- end
- end
-
- describe '.instrument' do
- it 'is unsubscribeable' do
- unsubscribe = described_class.instrument
-
- expect(unsubscribe).not_to be_nil
- expect { unsubscribe.call }.not_to raise_error
- end
- end
-
- describe '#notify_render_template' do
- subject { described_class.new }
- let(:start) { Time.now }
- let(:finish) { Time.now }
- let(:notification_name) { 'render_template' }
- let(:notify_method) { :notify_render_template }
-
- where(:identifier, :layout, :exception) do
- nil | nil | nil
- "" | nil | nil
- "show.haml" | nil | nil
- nil | "" | nil
- nil | "layout.haml" | nil
- nil | nil | StandardError.new
- end
-
- with_them do
- let(:payload) do
- {
- exception: exception,
- identifier: identifier,
- layout: layout
- }
- end
-
- let(:expected_tags) do
- {
- 'component' => 'ActionView',
- 'template.id' => identifier,
- 'template.layout' => layout
- }
- end
-
- it_behaves_like 'an actionview notification'
- end
- end
-
- describe '#notify_render_collection' do
- subject { described_class.new }
- let(:start) { Time.now }
- let(:finish) { Time.now }
- let(:notification_name) { 'render_collection' }
- let(:notify_method) { :notify_render_collection }
-
- where(
- :identifier, :count, :expected_count, :cache_hits, :expected_cache_hits, :exception) do
- nil | nil | 0 | nil | 0 | nil
- "" | nil | 0 | nil | 0 | nil
- "show.haml" | nil | 0 | nil | 0 | nil
- nil | 0 | 0 | nil | 0 | nil
- nil | 1 | 1 | nil | 0 | nil
- nil | nil | 0 | 0 | 0 | nil
- nil | nil | 0 | 1 | 1 | nil
- nil | nil | 0 | nil | 0 | StandardError.new
- end
-
- with_them do
- let(:payload) do
- {
- exception: exception,
- identifier: identifier,
- count: count,
- cache_hits: cache_hits
- }
- end
-
- let(:expected_tags) do
- {
- 'component' => 'ActionView',
- 'template.id' => identifier,
- 'template.count' => expected_count,
- 'template.cache.hits' => expected_cache_hits
- }
- end
-
- it_behaves_like 'an actionview notification'
- end
- end
-
- describe '#notify_render_partial' do
- subject { described_class.new }
- let(:start) { Time.now }
- let(:finish) { Time.now }
- let(:notification_name) { 'render_partial' }
- let(:notify_method) { :notify_render_partial }
-
- where(:identifier, :exception) do
- nil | nil
- "" | nil
- "show.haml" | nil
- nil | StandardError.new
- end
-
- with_them do
- let(:payload) do
- {
- exception: exception,
- identifier: identifier
- }
- end
-
- let(:expected_tags) do
- {
- 'component' => 'ActionView',
- 'template.id' => identifier
- }
- end
-
- it_behaves_like 'an actionview notification'
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb
deleted file mode 100644
index 3d066843148..00000000000
--- a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-
-describe Gitlab::Tracing::Rails::ActiveRecordSubscriber do
- using RSpec::Parameterized::TableSyntax
-
- describe '.instrument' do
- it 'is unsubscribeable' do
- unsubscribe = described_class.instrument
-
- expect(unsubscribe).not_to be_nil
- expect { unsubscribe.call }.not_to raise_error
- end
- end
-
- describe '#notify' do
- subject { described_class.new }
- let(:start) { Time.now }
- let(:finish) { Time.now }
-
- where(:name, :operation_name, :exception, :connection_id, :cached, :cached_response, :sql) do
- nil | "active_record:sqlquery" | nil | nil | nil | false | nil
- "" | "active_record:sqlquery" | nil | nil | nil | false | nil
- "User Load" | "active_record:User Load" | nil | nil | nil | false | nil
- "Repo Load" | "active_record:Repo Load" | StandardError.new | nil | nil | false | nil
- nil | "active_record:sqlquery" | nil | 123 | nil | false | nil
- nil | "active_record:sqlquery" | nil | nil | false | false | nil
- nil | "active_record:sqlquery" | nil | nil | true | true | nil
- nil | "active_record:sqlquery" | nil | nil | true | true | "SELECT * FROM users"
- end
-
- with_them do
- def payload
- {
- name: name,
- exception: exception,
- connection_id: connection_id,
- cached: cached,
- sql: sql
- }
- end
-
- def expected_tags
- {
- "component" => "ActiveRecord",
- "span.kind" => "client",
- "db.type" => "sql",
- "db.connection_id" => connection_id,
- "db.cached" => cached_response,
- "db.statement" => sql
- }
- end
-
- it 'should notify the tracer when the hash contains null values' do
- expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
-
- subject.notify(start, finish, payload)
- end
-
- it 'should notify the tracer when the payload is missing values' do
- expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
-
- subject.notify(start, finish, payload.compact)
- end
-
- it 'should not throw exceptions when with the default tracer' do
- expect { subject.notify(start, finish, payload) }.not_to raise_error
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
deleted file mode 100644
index 3755860b5ba..00000000000
--- a/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::Tracing::Sidekiq::ClientMiddleware do
- describe '#call' do
- let(:worker_class) { 'test_worker_class' }
- let(:job) do
- {
- 'class' => "jobclass",
- 'queue' => "jobqueue",
- 'retry' => 0,
- 'args' => %w{1 2 3}
- }
- end
- let(:queue) { 'test_queue' }
- let(:redis_pool) { double("redis_pool") }
- let(:custom_error) { Class.new(StandardError) }
- let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
-
- subject { described_class.new }
-
- it 'yields' do
- expect(subject).to receive(:in_tracing_span).with(
- operation_name: "sidekiq:jobclass",
- tags: {
- "component" => "sidekiq",
- "span.kind" => "client",
- "sidekiq.queue" => "jobqueue",
- "sidekiq.jid" => nil,
- "sidekiq.retry" => "0",
- "sidekiq.args" => "1, 2, 3"
- }
- ).and_yield(span)
-
- expect { |b| subject.call(worker_class, job, queue, redis_pool, &b) }.to yield_control
- end
-
- it 'propagates exceptions' do
- expect { subject.call(worker_class, job, queue, redis_pool) { raise custom_error } }.to raise_error(custom_error)
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
deleted file mode 100644
index c3087de785a..00000000000
--- a/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-describe Gitlab::Tracing::Sidekiq::ServerMiddleware do
- describe '#call' do
- let(:worker_class) { 'test_worker_class' }
- let(:job) do
- {
- 'class' => "jobclass",
- 'queue' => "jobqueue",
- 'retry' => 0,
- 'args' => %w{1 2 3}
- }
- end
- let(:queue) { 'test_queue' }
- let(:custom_error) { Class.new(StandardError) }
- let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
- subject { described_class.new }
-
- it 'yields' do
- expect(subject).to receive(:in_tracing_span).with(
- hash_including(
- operation_name: "sidekiq:jobclass",
- tags: {
- "component" => "sidekiq",
- "span.kind" => "server",
- "sidekiq.queue" => "jobqueue",
- "sidekiq.jid" => nil,
- "sidekiq.retry" => "0",
- "sidekiq.args" => "1, 2, 3"
- }
- )
- ).and_yield(span)
-
- expect { |b| subject.call(worker_class, job, queue, &b) }.to yield_control
- end
-
- it 'propagates exceptions' do
- expect { subject.call(worker_class, job, queue) { raise custom_error } }.to raise_error(custom_error)
- end
- end
-end
diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb
index 566b5050e47..db75ce2a998 100644
--- a/spec/lib/gitlab/tracing_spec.rb
+++ b/spec/lib/gitlab/tracing_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Tracing do
end
with_them do
- it 'should return the correct state for .enabled?' do
+ it 'returns the correct state for .enabled?' do
expect(described_class).to receive(:connection_string).and_return(connection_string)
expect(described_class.enabled?).to eq(enabled_state)
@@ -33,7 +33,7 @@ describe Gitlab::Tracing do
end
with_them do
- it 'should return the correct state for .tracing_url_enabled?' do
+ it 'returns the correct state for .tracing_url_enabled?' do
expect(described_class).to receive(:enabled?).and_return(enabled?)
allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template)
@@ -56,7 +56,7 @@ describe Gitlab::Tracing do
end
with_them do
- it 'should return the correct state for .tracing_url' do
+ it 'returns the correct state for .tracing_url' do
expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?)
allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template)
allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id)
diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
new file mode 100644
index 00000000000..f1882e03581
--- /dev/null
+++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
@@ -0,0 +1,118 @@
+require 'fast_spec_helper'
+require 'support/shared_examples/malicious_regexp_shared_examples'
+require 'support/helpers/stub_feature_flags'
+
+describe Gitlab::UntrustedRegexp::RubySyntax do
+ describe '.matches_syntax?' do
+ it 'returns true if regexp is valid' do
+ expect(described_class.matches_syntax?('/some .* thing/'))
+ .to be true
+ end
+
+ it 'returns true if regexp is invalid, but resembles regexp' do
+ expect(described_class.matches_syntax?('/some ( thing/'))
+ .to be true
+ end
+ end
+
+ describe '.valid?' do
+ it 'returns true if regexp is valid' do
+ expect(described_class.valid?('/some .* thing/'))
+ .to be true
+ end
+
+ it 'returns false if regexp is invalid' do
+ expect(described_class.valid?('/some ( thing/'))
+ .to be false
+ end
+ end
+
+ describe '.fabricate' do
+ context 'when regexp is valid' do
+ it 'fabricates regexp without flags' do
+ expect(described_class.fabricate('/some .* thing/')).not_to be_nil
+ end
+ end
+
+ context 'when regexp is empty' do
+ it 'fabricates regexp correctly' do
+ expect(described_class.fabricate('//')).not_to be_nil
+ end
+ end
+
+ context 'when regexp is a raw pattern' do
+ it 'returns error' do
+ expect(described_class.fabricate('some .* thing')).to be_nil
+ end
+ end
+ end
+
+ describe '.fabricate!' do
+ context 'safe regexp is used' do
+ context 'when regexp is using /regexp/ scheme with flags' do
+ it 'fabricates regexp with a single flag' do
+ regexp = described_class.fabricate!('/something/i')
+
+ expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?i)something')
+ expect(regexp.scan('SOMETHING')).to be_one
+ end
+
+ it 'fabricates regexp with multiple flags' do
+ regexp = described_class.fabricate!('/something/im')
+
+ expect(regexp).to eq Gitlab::UntrustedRegexp.new('(?im)something')
+ end
+
+ it 'fabricates regexp without flags' do
+ regexp = described_class.fabricate!('/something/')
+
+ expect(regexp).to eq Gitlab::UntrustedRegexp.new('something')
+ end
+ end
+ end
+
+ context 'when unsafe regexp is used' do
+ include StubFeatureFlags
+
+ before do
+ stub_feature_flags(allow_unsafe_ruby_regexp: true)
+
+ allow(Gitlab::UntrustedRegexp).to receive(:new).and_raise(RegexpError)
+ end
+
+ context 'when no fallback is enabled' do
+ it 'raises an exception' do
+ expect { described_class.fabricate!('/something/') }
+ .to raise_error(RegexpError)
+ end
+ end
+
+ context 'when fallback is used' do
+ it 'fabricates regexp with a single flag' do
+ regexp = described_class.fabricate!('/something/i', fallback: true)
+
+ expect(regexp).to eq Regexp.new('something', Regexp::IGNORECASE)
+ end
+
+ it 'fabricates regexp with multiple flags' do
+ regexp = described_class.fabricate!('/something/im', fallback: true)
+
+ expect(regexp).to eq Regexp.new('something', Regexp::IGNORECASE | Regexp::MULTILINE)
+ end
+
+ it 'fabricates regexp without flags' do
+ regexp = described_class.fabricate!('/something/', fallback: true)
+
+ expect(regexp).to eq Regexp.new('something')
+ end
+ end
+ end
+
+ context 'when regexp is a raw pattern' do
+ it 'raises an error' do
+ expect { described_class.fabricate!('some .* thing') }
+ .to raise_error(RegexpError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 0a6ac0aa294..9d483f13a5e 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -2,48 +2,6 @@ require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples'
describe Gitlab::UntrustedRegexp do
- describe '.valid?' do
- it 'returns true if regexp is valid' do
- expect(described_class.valid?('/some ( thing/'))
- .to be false
- end
-
- it 'returns true if regexp is invalid' do
- expect(described_class.valid?('/some .* thing/'))
- .to be true
- end
- end
-
- describe '.fabricate' do
- context 'when regexp is using /regexp/ scheme with flags' do
- it 'fabricates regexp with a single flag' do
- regexp = described_class.fabricate('/something/i')
-
- expect(regexp).to eq described_class.new('(?i)something')
- expect(regexp.scan('SOMETHING')).to be_one
- end
-
- it 'fabricates regexp with multiple flags' do
- regexp = described_class.fabricate('/something/im')
-
- expect(regexp).to eq described_class.new('(?im)something')
- end
-
- it 'fabricates regexp without flags' do
- regexp = described_class.fabricate('/something/')
-
- expect(regexp).to eq described_class.new('something')
- end
- end
-
- context 'when regexp is a raw pattern' do
- it 'raises an error' do
- expect { described_class.fabricate('some .* thing') }
- .to raise_error(RegexpError)
- end
- end
- end
-
describe '#initialize' do
subject { described_class.new(pattern) }
@@ -92,11 +50,41 @@ describe Gitlab::UntrustedRegexp do
end
end
+ describe '#match?' do
+ subject { described_class.new(regexp).match?(text) }
+
+ context 'malicious regexp' do
+ let(:text) { malicious_text }
+ let(:regexp) { malicious_regexp_re2 }
+
+ include_examples 'malicious regexp'
+ end
+
+ context 'matching regexp' do
+ let(:regexp) { 'foo' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches' do
+ is_expected.to eq(true)
+ end
+ end
+
+ context 'non-matching regexp' do
+ let(:regexp) { 'boo' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches' do
+ is_expected.to eq(false)
+ end
+ end
+ end
+
describe '#scan' do
subject { described_class.new(regexp).scan(text) }
+
context 'malicious regexp' do
let(:text) { malicious_text }
- let(:regexp) { malicious_regexp }
+ let(:regexp) { malicious_regexp_re2 }
include_examples 'malicious regexp'
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 62970bd8cb6..253366e0789 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -2,6 +2,87 @@
require 'spec_helper'
describe Gitlab::UrlBlocker do
+ describe '#validate!' do
+ context 'when URI is nil' do
+ let(:import_url) { nil }
+
+ it 'returns no URI and hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to be(nil)
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'when URI is internal' do
+ let(:import_url) { 'http://localhost' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('http://[::1]'))
+ expect(hostname).to eq('localhost')
+ end
+ end
+
+ context 'when the URL hostname is a domain' do
+ let(:import_url) { 'https://example.org' }
+
+ it 'returns URI and hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to eq('example.org')
+ end
+ end
+
+ context 'when the URL hostname is an IP address' do
+ let(:import_url) { 'https://93.184.216.34' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'disabled DNS rebinding protection' do
+ context 'when URI is internal' do
+ let(:import_url) { 'http://localhost' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('http://localhost'))
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'when the URL hostname is a domain' do
+ let(:import_url) { 'https://example.org' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('https://example.org'))
+ expect(hostname).to eq(nil)
+ end
+ end
+
+ context 'when the URL hostname is an IP address' do
+ let(:import_url) { 'https://93.184.216.34' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to be(nil)
+ end
+ end
+ end
+ end
+
describe '#blocked_url?' do
let(:ports) { Project::VALID_IMPORT_PORTS }
@@ -23,10 +104,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', ports: ports)).to be true
end
- it 'returns true for bad protocol' do
- expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['https'])).to be false
+ it 'returns true for bad scheme' do
+ expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['https'])).to be false
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
- expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
+ expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['http'])).to be true
end
it 'returns true for bad protocol on configured web/SSH host and ports' do
@@ -208,7 +289,7 @@ describe Gitlab::UrlBlocker do
end
def stub_domain_resolv(domain, ip)
- address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
+ address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 9f495a5d50b..bbcb92608d8 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::UrlBuilder do
url = described_class.build(milestone)
- expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/-/milestones/#{milestone.iid}"
end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 6e98a999766..7242255d535 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -115,6 +115,40 @@ describe Gitlab::UrlSanitizer do
end
end
+ describe '#user' do
+ context 'credentials in hash' do
+ it 'overrides URL-provided user' do
+ sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' })
+
+ expect(sanitizer.user).to eq('c')
+ end
+ end
+
+ context 'credentials in URL' do
+ where(:url, :user) do
+ 'http://foo:bar@example.com' | 'foo'
+ 'http://foo:bar:baz@example.com' | 'foo'
+ 'http://:bar@example.com' | nil
+ 'http://foo:@example.com' | 'foo'
+ 'http://foo@example.com' | 'foo'
+ 'http://:@example.com' | nil
+ 'http://@example.com' | nil
+ 'http://example.com' | nil
+
+ # Other invalid URLs
+ nil | nil
+ '' | nil
+ 'no' | nil
+ end
+
+ with_them do
+ subject { described_class.new(url).user }
+
+ it { is_expected.to eq(user) }
+ end
+ end
+ end
+
describe '#full_url' do
context 'credentials in hash' do
where(:credentials, :userinfo) do
@@ -161,7 +195,7 @@ describe Gitlab::UrlSanitizer do
end
context 'when credentials contains special chars' do
- it 'should parse the URL without errors' do
+ it 'parses the URL without errors' do
url_sanitizer = described_class.new("https://foo:b?r@github.com/me/project.git")
expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git")
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index d3eae80cc56..e44463dd767 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -13,6 +13,8 @@ describe Gitlab::UsageData do
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
+ create(:project_error_tracking_setting, project: projects[0])
+ create(:project_error_tracking_setting, project: projects[1], enabled: false)
gcp_cluster = create(:cluster, :provided_by_gcp)
create(:cluster, :provided_by_user)
@@ -33,7 +35,7 @@ describe Gitlab::UsageData do
subject { described_class.data }
it "gathers usage data" do
- expect(subject.keys).to match_array(%i(
+ expect(subject.keys).to include(*%i(
active_user_count
counts
recorded_at
@@ -55,16 +57,13 @@ describe Gitlab::UsageData do
database
avg_cycle_analytics
web_ide_commits
+ influxdb_metrics_enabled
+ prometheus_metrics_enabled
))
end
it "gathers usage counts" do
- count_data = subject[:counts]
-
- expect(count_data[:boards]).to eq(1)
- expect(count_data[:projects]).to eq(3)
-
- expect(count_data.keys).to match_array(%i(
+ expected_keys = %i(
assignee_lists
boards
ci_builds
@@ -79,6 +78,8 @@ describe Gitlab::UsageData do
auto_devops_disabled
deploy_keys
deployments
+ successful_deployments
+ failed_deployments
environments
clusters
clusters_enabled
@@ -106,6 +107,7 @@ describe Gitlab::UsageData do
milestone_lists
milestones
notes
+ pool_repositories
projects
projects_imported_from_github
projects_jira_active
@@ -115,6 +117,7 @@ describe Gitlab::UsageData do
projects_slack_slash_active
projects_prometheus_active
projects_with_repositories_enabled
+ projects_with_error_tracking_enabled
pages_domains
protected_branches
releases
@@ -125,7 +128,14 @@ describe Gitlab::UsageData do
uploads
web_hooks
user_preferences
- ))
+ )
+
+ count_data = subject[:counts]
+
+ expect(count_data[:boards]).to eq(1)
+ expect(count_data[:projects]).to eq(3)
+ expect(count_data.keys).to include(*expected_keys)
+ expect(expected_keys - count_data.keys).to be_empty
end
it 'does not gather user preferences usage data when the feature is disabled' do
@@ -144,6 +154,7 @@ describe Gitlab::UsageData do
expect(count_data[:projects_slack_notifications_active]).to eq(2)
expect(count_data[:projects_slack_slash_active]).to eq(1)
expect(count_data[:projects_with_repositories_enabled]).to eq(2)
+ expect(count_data[:projects_with_error_tracking_enabled]).to eq(1)
expect(count_data[:clusters_enabled]).to eq(7)
expect(count_data[:project_clusters_enabled]).to eq(6)
@@ -203,7 +214,7 @@ describe Gitlab::UsageData do
it "gathers license data" do
expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
- expect(subject[:installation_type]).to eq(Gitlab::INSTALLATION_TYPE)
+ expect(subject[:installation_type]).to eq('gitlab-development-kit')
expect(subject[:active_user_count]).to eq(User.active.count)
expect(subject[:recorded_at]).to be_a(Time)
end
diff --git a/spec/lib/gitlab/user_extractor_spec.rb b/spec/lib/gitlab/user_extractor_spec.rb
index fcc05ab3a0c..b86ec5445b8 100644
--- a/spec/lib/gitlab/user_extractor_spec.rb
+++ b/spec/lib/gitlab/user_extractor_spec.rb
@@ -38,6 +38,18 @@ describe Gitlab::UserExtractor do
expect(extractor.users).to include(user)
end
+
+ context 'input as array of strings' do
+ it 'is treated as one string' do
+ extractor = described_class.new(text.lines)
+
+ user_1 = create(:user, username: "USER-1")
+ user_4 = create(:user, username: "USER-4")
+ user_email = create(:user, email: 'user@gitlab.org')
+
+ expect(extractor.users).to contain_exactly(user_1, user_4, user_email)
+ end
+ end
end
describe '#matches' do
@@ -48,6 +60,14 @@ describe Gitlab::UserExtractor do
it 'includes all mentioned usernames' do
expect(extractor.matches[:usernames]).to contain_exactly('user-1', 'user-2', 'user-4')
end
+
+ context 'input has no matching e-mail or usernames' do
+ it 'returns an empty list of users' do
+ extractor = described_class.new('My test')
+
+ expect(extractor.users).to be_empty
+ end
+ end
end
describe '#references' do
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 8f5029b3565..4645339f439 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -213,4 +213,22 @@ describe Gitlab::Utils do
expect(subject[:variables].first[:key]).to eq('VAR1')
end
end
+
+ describe '.try_megabytes_to_bytes' do
+ context 'when the size can be converted to megabytes' do
+ it 'returns the size in megabytes' do
+ size = described_class.try_megabytes_to_bytes(1)
+
+ expect(size).to eq(1.megabytes)
+ end
+ end
+
+ context 'when the size can not be converted to megabytes' do
+ it 'returns the input size' do
+ size = described_class.try_megabytes_to_bytes('foo')
+
+ expect(size).to eq('foo')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 2c1146ceff5..0a170a157fe 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -85,4 +85,12 @@ describe Gitlab::VisibilityLevel do
.to eq(described_class::PRIVATE)
end
end
+
+ describe '.valid_level?' do
+ it 'returns true when visibility is valid' do
+ expect(described_class.valid_level?(described_class::PRIVATE)).to be_truthy
+ expect(described_class.valid_level?(described_class::INTERNAL)).to be_truthy
+ expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy
+ end
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 7213eee5675..f8332757fcd 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -16,40 +16,80 @@ describe Gitlab::Workhorse do
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
- let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
- let(:gitaly_params) do
- base_params.merge(
- 'GitalyServer' => {
- 'address' => Gitlab::GitalyClient.address(project.repository_storage),
- 'token' => Gitlab::GitalyClient.token(project.repository_storage)
- },
- 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
- )
- end
+ let(:path) { 'some/path' if Feature.enabled?(:git_archive_path, default_enabled: true) }
+ let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) }
let(:cache_disabled) { false }
subject do
- described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
+ described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path)
end
before do
allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled)
end
- it 'sets the header correctly' do
- key, command, params = decode_workhorse_header(subject)
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(git_archive_path: false)
+ end
- expect(key).to eq('Gitlab-Workhorse-Send-Data')
- expect(command).to eq('git-archive')
- expect(params).to include(gitaly_params)
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expected_params = metadata.merge(
+ 'GitalyRepository' => repository.gitaly_repository.to_h,
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ }
+ )
+
+ expect(key).to eq('Gitlab-Workhorse-Send-Data')
+ expect(command).to eq('git-archive')
+ expect(params).to eq(expected_params.deep_stringify_keys)
+ end
+
+ context 'when archive caching is disabled' do
+ let(:cache_disabled) { true }
+
+ it 'tells workhorse not to use the cache' do
+ _, _, params = decode_workhorse_header(subject)
+ expect(params).to include({ 'DisableCache' => true })
+ end
+ end
end
- context 'when archive caching is disabled' do
- let(:cache_disabled) { true }
+ context 'feature flag enabled' do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq('Gitlab-Workhorse-Send-Data')
+ expect(command).to eq('git-archive')
+ expect(params).to eq({
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'ArchivePath' => metadata['ArchivePath'],
+ 'GetArchiveRequest' => Base64.encode64(
+ Gitaly::GetArchiveRequest.new(
+ repository: repository.gitaly_repository,
+ commit_id: metadata['CommitId'],
+ prefix: metadata['ArchivePrefix'],
+ format: Gitaly::GetArchiveRequest::Format::ZIP,
+ path: path
+ ).to_proto
+ )
+ }.deep_stringify_keys)
+ end
- it 'tells workhorse not to use the cache' do
- _, _, params = decode_workhorse_header(subject)
- expect(params).to include({ 'DisableCache' => true })
+ context 'when archive caching is disabled' do
+ let(:cache_disabled) { true }
+
+ it 'tells workhorse not to use the cache' do
+ _, _, params = decode_workhorse_header(subject)
+ expect(params).to include({ 'DisableCache' => true })
+ end
end
end
@@ -87,7 +127,7 @@ describe Gitlab::Workhorse do
end
end
- describe '.terminal_websocket' do
+ describe '.channel_websocket' do
def terminal(ca_pem: nil)
out = {
subprotocols: ['foo'],
@@ -101,25 +141,25 @@ describe Gitlab::Workhorse do
def workhorse(ca_pem: nil)
out = {
- 'Terminal' => {
+ 'Channel' => {
'Subprotocols' => ['foo'],
'Url' => 'wss://example.com/terminal.ws',
'Header' => { 'Authorization' => ['Token x'] },
'MaxSessionTime' => 600
}
}
- out['Terminal']['CAPem'] = ca_pem if ca_pem
+ out['Channel']['CAPem'] = ca_pem if ca_pem
out
end
context 'without ca_pem' do
- subject { described_class.terminal_websocket(terminal) }
+ subject { described_class.channel_websocket(terminal) }
it { is_expected.to eq(workhorse) }
end
context 'with ca_pem' do
- subject { described_class.terminal_websocket(terminal(ca_pem: "foo")) }
+ subject { described_class.channel_websocket(terminal(ca_pem: "foo")) }
it { is_expected.to eq(workhorse(ca_pem: "foo")) }
end
@@ -250,11 +290,11 @@ describe Gitlab::Workhorse do
}
end
- subject { described_class.git_http_ok(repository, false, user, action) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) }
it { expect(subject).to include(params) }
- context 'when is_wiki' do
+ context 'when the repo_type is a wiki' do
let(:params) do
{
GL_ID: "user-#{user.id}",
@@ -264,7 +304,7 @@ describe Gitlab::Workhorse do
}
end
- subject { described_class.git_http_ok(repository, true, user, action) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::WIKI, user, action) }
it { expect(subject).to include(params) }
end
@@ -304,7 +344,7 @@ describe Gitlab::Workhorse do
end
context 'show_all_refs enabled' do
- subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
it { is_expected.to include(ShowAllRefs: true) }
end
@@ -322,7 +362,7 @@ describe Gitlab::Workhorse do
it { expect(subject).to include(gitaly_params) }
context 'show_all_refs enabled' do
- subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
it { is_expected.to include(ShowAllRefs: true) }
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 5f7a0cca351..e075904b0cc 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab do
end
describe '.revision' do
- let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] }
+ let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1] }
around do |example|
described_class.instance_variable_set(:@_revision, nil)
@@ -95,4 +95,48 @@ describe Gitlab do
expect(described_class.com?).to eq false
end
end
+
+ describe '.ee?' do
+ it 'returns true when using Enterprise Edition' do
+ stub_const('License', Class.new)
+
+ expect(described_class.ee?).to eq(true)
+ end
+
+ it 'returns false when using Community Edition' do
+ hide_const('License')
+
+ expect(described_class.ee?).to eq(false)
+ end
+ end
+
+ describe '.http_proxy_env?' do
+ it 'returns true when lower case https' do
+ stub_env('https_proxy', 'https://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when upper case https' do
+ stub_env('HTTPS_PROXY', 'https://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when lower case http' do
+ stub_env('http_proxy', 'http://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when upper case http' do
+ stub_env('HTTP_PROXY', 'http://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns false when not set' do
+ expect(described_class.http_proxy_env?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index e2134dc279c..1fefc947636 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -97,6 +97,12 @@ describe GoogleApi::CloudPlatform::Client do
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": "admin",
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": true
}
@@ -122,6 +128,12 @@ describe GoogleApi::CloudPlatform::Client do
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": "admin",
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": false
}
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 77fea5b2d24..346455067a7 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Mattermost::Session, type: :request do
include ExclusiveLeaseHelpers
+ include StubRequests
let(:user) { create(:user) }
@@ -24,7 +25,7 @@ describe Mattermost::Session, type: :request do
let(:location) { 'http://location.tld' }
let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'}
let!(:stub) do
- WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login")
+ stub_full_request("#{mattermost_url}/oauth/gitlab/login")
.to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302)
end
@@ -63,7 +64,7 @@ describe Mattermost::Session, type: :request do
end
before do
- WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete")
+ stub_full_request("#{mattermost_url}/signup/gitlab/complete")
.with(query: hash_including({ 'state' => state }))
.to_return do |request|
post "/oauth/token",
@@ -80,7 +81,7 @@ describe Mattermost::Session, type: :request do
end
end
- WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout")
+ stub_full_request("#{mattermost_url}/api/v4/users/logout", method: :post)
.to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
end
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index 1024e1a25ea..8ccbd90ddb8 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -121,7 +121,7 @@ describe ObjectStorage::DirectUpload do
expect(subject[:MultipartUpload][:PartURLs].length).to eq(2)
end
- it 'part size is mimimum, 5MB' do
+ it 'part size is minimum, 5MB' do
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
end
end
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb
index f35d9464d48..4e77dcc97e6 100644
--- a/spec/lib/quality/kubernetes_client_spec.rb
+++ b/spec/lib/quality/kubernetes_client_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Quality::KubernetesClient do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(kubectl --namespace "#{namespace}" delete ) \
'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \
- "--now -l release=\"#{release_name}\""])
+ "--now --ignore-not-found --include-uninitialized -l release=\"#{release_name}\""])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
@@ -23,7 +23,7 @@ RSpec.describe Quality::KubernetesClient do
expect(Gitlab::Popen).to receive(:popen_with_detail)
.with([%(kubectl --namespace "#{namespace}" delete ) \
'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \
- "--now -l release=\"#{release_name}\""])
+ "--now --ignore-not-found --include-uninitialized -l release=\"#{release_name}\""])
.and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
# We're not verifying the output here, just silencing it
diff --git a/spec/lib/quality/seeders/issues_spec.rb b/spec/lib/quality/seeders/issues_spec.rb
new file mode 100644
index 00000000000..e17414a541a
--- /dev/null
+++ b/spec/lib/quality/seeders/issues_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Quality::Seeders::Issues do
+ let(:project) { create(:project) }
+
+ subject { described_class.new(project: project) }
+
+ describe '#seed' do
+ it 'seeds issues' do
+ issues_created = subject.seed(backfill_weeks: 1, average_issues_per_week: 1)
+
+ expect(issues_created).to be_between(0, 2)
+ expect(project.issues.count).to eq(issues_created)
+ end
+ end
+end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
new file mode 100644
index 00000000000..3465c3a050b
--- /dev/null
+++ b/spec/lib/quality/test_level_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Quality::TestLevel do
+ describe '#pattern' do
+ context 'when level is unit' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:unit))
+ .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'when level is integration' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:integration))
+ .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'when level is system' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:system))
+ .to eq("spec/{features}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'with a prefix' do
+ it 'returns a pattern' do
+ expect(described_class.new('ee/').pattern(:system))
+ .to eq("ee/spec/{features}{,/**/}*_spec.rb")
+ end
+ end
+
+ describe 'performance' do
+ it 'memoizes the pattern for a given level' do
+ expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id)
+ end
+
+ it 'freezes the pattern for a given level' do
+ expect(subject.pattern(:system)).to be_frozen
+ end
+ end
+ end
+
+ describe '#regexp' do
+ context 'when level is unit' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:unit))
+ .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
+ end
+ end
+
+ context 'when level is integration' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:integration))
+ .to eq(%r{spec/(controllers|mailers|requests)})
+ end
+ end
+
+ context 'when level is system' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:system))
+ .to eq(%r{spec/(features)})
+ end
+ end
+
+ context 'with a prefix' do
+ it 'returns a regexp' do
+ expect(described_class.new('ee/').regexp(:system))
+ .to eq(%r{ee/spec/(features)})
+ end
+ end
+
+ describe 'performance' do
+ it 'memoizes the regexp for a given level' do
+ expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id)
+ end
+
+ it 'freezes the regexp for a given level' do
+ expect(subject.regexp(:system)).to be_frozen
+ end
+ end
+ end
+
+ describe '#level_for' do
+ it 'returns the correct level for a unit test' do
+ expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit)
+ end
+
+ it 'returns the correct level for an integration test' do
+ expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration)
+ end
+
+ it 'returns the correct level for a system test' do
+ expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system)
+ end
+
+ it 'raises an error for an unknown level' do
+ expect { subject.level_for('spec/unknown/foo_spec.rb') }
+ .to raise_error(described_class::UnknownTestLevelError,
+ %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.})
+ end
+ end
+end
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index 88e7e2e5ebb..cb14204b99a 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -61,11 +61,37 @@ describe Sentry::Client do
end
end
+ shared_examples 'maps exceptions' do
+ exceptions = {
+ HTTParty::Error => 'Error when connecting to Sentry',
+ Net::OpenTimeout => 'Connection to Sentry timed out',
+ SocketError => 'Received SocketError when trying to connect to Sentry',
+ OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data',
+ Errno::ECONNREFUSED => 'Connection refused',
+ StandardError => 'Sentry request failed due to StandardError'
+ }
+
+ exceptions.each do |exception, message|
+ context "#{exception}" do
+ before do
+ stub_request(:get, sentry_request_url).to_raise(exception)
+ end
+
+ it do
+ expect { subject }
+ .to raise_exception(Sentry::Client::Error, message)
+ end
+ end
+ end
+ end
+
describe '#list_issues' do
let(:issue_status) { 'unresolved' }
let(:limit) { 20 }
+ let(:sentry_api_response) { issues_sample_response }
+ let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' }
- let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: issues_sample_response) }
+ let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
subject { client.list_issues(issue_status: issue_status, limit: limit) }
@@ -74,6 +100,14 @@ describe Sentry::Client do
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
it_behaves_like 'has correct length', 1
+ shared_examples 'has correct external_url' do
+ context 'external_url' do
+ it 'is constructed correctly' do
+ expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
+ end
+ end
+ end
+
context 'error object created from sentry response' do
using RSpec::Parameterized::TableSyntax
@@ -96,14 +130,10 @@ describe Sentry::Client do
end
with_them do
- it { expect(subject[0].public_send(error_object)).to eq(issues_sample_response[0].dig(*sentry_response)) }
+ it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) }
end
- context 'external_url' do
- it 'is constructed correctly' do
- expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
- end
- end
+ it_behaves_like 'has correct external_url'
end
context 'redirects' do
@@ -115,16 +145,14 @@ describe Sentry::Client do
# Sentry API returns 404 if there are extra slashes in the URL!
context 'extra slashes in URL' do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' }
- let(:client) { described_class.new(sentry_url, token) }
- let!(:valid_req_stub) do
- stub_sentry_request(
- 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \
+ let(:sentry_request_url) do
+ 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \
'issues/?limit=20&query=is:unresolved'
- )
end
it 'removes extra slashes in api url' do
+ expect(client.url).to eq(sentry_url)
expect(Gitlab::HTTP).to receive(:get).with(
URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'),
anything
@@ -132,15 +160,47 @@ describe Sentry::Client do
subject
- expect(valid_req_stub).to have_been_requested
+ expect(sentry_api_request).to have_been_requested
end
end
+
+ context 'Older sentry versions where keys are not present' do
+ let(:sentry_api_response) do
+ issues_sample_response[0...1].map do |issue|
+ issue[:project].delete(:id)
+ issue
+ end
+ end
+
+ it_behaves_like 'calls sentry api'
+
+ it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
+ it_behaves_like 'has correct length', 1
+
+ it_behaves_like 'has correct external_url'
+ end
+
+ context 'essential keys missing in API response' do
+ let(:sentry_api_response) do
+ issues_sample_response[0...1].map do |issue|
+ issue.except(:id)
+ end
+ end
+
+ it 'raises exception' do
+ expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"')
+ end
+ end
+
+ it_behaves_like 'maps exceptions'
end
describe '#list_projects' do
let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' }
- let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) }
+ let(:sentry_api_response) { projects_sample_response }
+
+ let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) }
subject { client.list_projects }
@@ -149,14 +209,31 @@ describe Sentry::Client do
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project
it_behaves_like 'has correct length', 2
- context 'keys missing in API response' do
- it 'raises exception' do
- projects_sample_response[0].delete(:slug)
+ context 'essential keys missing in API response' do
+ let(:sentry_api_response) do
+ projects_sample_response[0...1].map do |project|
+ project.except(:slug)
+ end
+ end
- stub_sentry_request(sentry_list_projects_url, body: projects_sample_response)
+ it 'raises exception' do
+ expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"')
+ end
+ end
- expect { subject }.to raise_error(Sentry::Client::SentryError, 'Sentry API response is missing keys. key not found: "slug"')
+ context 'optional keys missing in sentry response' do
+ let(:sentry_api_response) do
+ projects_sample_response[0...1].map do |project|
+ project[:organization].delete(:id)
+ project.delete(:id)
+ project.except(:status)
+ end
end
+
+ it_behaves_like 'calls sentry api'
+
+ it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project
+ it_behaves_like 'has correct length', 1
end
context 'error object created from sentry response' do
@@ -173,7 +250,11 @@ describe Sentry::Client do
end
with_them do
- it { expect(subject[0].public_send(sentry_project_object)).to eq(projects_sample_response[0].dig(*sentry_response)) }
+ it do
+ expect(subject[0].public_send(sentry_project_object)).to(
+ eq(sentry_api_response[0].dig(*sentry_response))
+ )
+ end
end
end
@@ -203,12 +284,18 @@ describe Sentry::Client do
expect(valid_req_stub).to have_been_requested
end
end
+
+ context 'when exception is raised' do
+ let(:sentry_request_url) { sentry_list_projects_url }
+
+ it_behaves_like 'maps exceptions'
+ end
end
private
def stub_sentry_request(url, body: {}, status: 200, headers: {})
- WebMock.stub_request(:get, url)
+ stub_request(:get, url)
.to_return(
status: status,
headers: { 'Content-Type' => 'application/json' }.merge(headers),
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index bda892083b3..f96870cc112 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -4,25 +4,24 @@ describe AbuseReportMailer do
include EmailSpec::Matchers
describe '.notify' do
- context 'with admin_notification_email set' do
- before do
- stub_application_setting(admin_notification_email: 'admin@example.com')
- end
+ before do
+ stub_application_setting(admin_notification_email: 'admin@example.com')
+ end
- it 'sends to the admin_notification_email' do
- report = create(:abuse_report)
+ let(:report) { create(:abuse_report) }
+
+ subject { described_class.notify(report.id) }
- mail = described_class.notify(report.id)
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
- expect(mail).to deliver_to 'admin@example.com'
+ context 'with admin_notification_email set' do
+ it 'sends to the admin_notification_email' do
+ is_expected.to deliver_to 'admin@example.com'
end
it 'includes the user in the subject' do
- report = create(:abuse_report)
-
- mail = described_class.notify(report.id)
-
- expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
+ is_expected.to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
end
end
diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb
new file mode 100644
index 00000000000..bbe0a50ae8e
--- /dev/null
+++ b/spec/mailers/email_rejection_mailer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe EmailRejectionMailer do
+ include EmailSpec::Matchers
+
+ describe '#rejection' do
+ let(:raw_email) { 'From: someone@example.com\nraw email here' }
+
+ subject { described_class.rejection('some rejection reason', raw_email) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
+end
diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb
index 839caf3f50e..dd7c12c3143 100644
--- a/spec/mailers/emails/auto_devops_spec.rb
+++ b/spec/mailers/emails/auto_devops_spec.rb
@@ -13,6 +13,9 @@ describe Emails::AutoDevops do
subject { Notify.autodevops_disabled_email(pipeline, owner.email) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'sents email with correct subject' do
is_expected.to have_subject("#{project.name} | Auto DevOps pipeline was disabled for #{project.name}")
end
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
index 09253cf8003..5b5bd6f4308 100644
--- a/spec/mailers/emails/issues_spec.rb
+++ b/spec/mailers/emails/issues_spec.rb
@@ -29,5 +29,14 @@ describe Emails::Issues do
expect(subject).to have_body_text "23, 34, 58"
end
+
+ context 'with header and footer' do
+ let(:results) { { success: 165, error_lines: [], parse_error: false } }
+
+ subject { Notify.import_issues_csv_email(user.id, project.id, results) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
end
end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index c74fd66ad22..2f594dbf9d1 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -5,11 +5,13 @@ describe Emails::PagesDomains do
include EmailSpec::Matchers
include_context 'gitlab email notification'
- set(:project) { create(:project) }
set(:domain) { create(:pages_domain, project: project) }
- set(:user) { project.owner }
+ set(:user) { project.creator }
shared_examples 'a pages domain email' do
+ let(:test_recipient) { user }
+
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -26,6 +28,26 @@ describe Emails::PagesDomains do
end
end
+ shared_examples 'notification about upcoming domain removal' do
+ context 'when domain is not scheduled for removal' do
+ it 'asks user to remove it' do
+ is_expected.to have_body_text 'please remove it'
+ end
+ end
+
+ context 'when domain is scheduled for removal' do
+ before do
+ domain.update!(remove_at: 1.week.from_now)
+ end
+ it 'notifies user that domain will be removed automatically' do
+ aggregate_failures do
+ is_expected.to have_body_text domain.remove_at.strftime('%F %T')
+ is_expected.to have_body_text "it will be removed from your GitLab project"
+ end
+ end
+ end
+ end
+
describe '#pages_domain_enabled_email' do
let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been enabled" }
@@ -43,6 +65,8 @@ describe Emails::PagesDomains do
it_behaves_like 'a pages domain email'
+ it_behaves_like 'notification about upcoming domain removal'
+
it { is_expected.to have_body_text 'has been disabled' }
end
@@ -63,6 +87,8 @@ describe Emails::PagesDomains do
it_behaves_like 'a pages domain email'
+ it_behaves_like 'notification about upcoming domain removal'
+
it 'says verification has failed and when the domain is enabled until' do
is_expected.to have_body_text 'Verification has failed'
is_expected.to have_body_text domain.enabled_until.strftime('%F %T')
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 4f578c48d5b..cbbb22ad78c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -19,7 +19,7 @@ describe Notify do
create(:merge_request, source_project: project,
target_project: project,
author: current_user,
- assignee: assignee,
+ assignees: [assignee],
description: 'Awesome description')
end
@@ -30,8 +30,25 @@ describe Notify do
description: 'My awesome description!')
end
+ describe 'with HTML-encoded entities' do
+ before do
+ described_class.test_email('test@test.com', 'Subject', 'Some body with &mdash;').deliver
+ end
+
+ subject { ActionMailer::Base.deliveries.last }
+
+ it 'retains 7bit encoding' do
+ expect(subject.body.ascii_only?).to eq(true)
+ expect(subject.body.encoding).to eq('7bit')
+ end
+ end
+
context 'for a project' do
shared_examples 'an assignee email' do
+ let(:test_recipient) { assignee }
+
+ it_behaves_like 'an email sent to a user'
+
it 'is sent to the assignee as the author' do
sender = subject.header[:from].addrs.first
@@ -53,6 +70,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -72,6 +91,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -99,6 +121,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -118,6 +142,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -134,6 +161,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -173,6 +202,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -194,23 +225,53 @@ describe Notify do
let(:new_issue) { create(:issue) }
subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) }
- it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
- let(:model) { issue }
- end
- it_behaves_like 'it should show Gmail Actions View Issue link'
- it_behaves_like 'an unsubscribeable thread'
+ context 'when a user has permissions to access the new issue' do
+ before do
+ new_issue.project.add_developer(recipient)
+ end
- it 'contains description about action taken' do
- is_expected.to have_body_text 'Issue was moved to another project'
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'contains description about action taken' do
+ is_expected.to have_body_text 'Issue was moved to another project'
+ end
+
+ it 'has the correct subject and body' do
+ new_issue_url = project_issue_path(new_issue.project, new_issue)
+
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_body_text(new_issue_url)
+ is_expected.to have_body_text(project_issue_path(project, issue))
+ end
+ end
+
+ it 'contains the issue title' do
+ is_expected.to have_body_text new_issue.title
+ end
end
- it 'has the correct subject and body' do
- new_issue_url = project_issue_path(new_issue.project, new_issue)
+ context 'when a user does not permissions to access the new issue' do
+ it 'has the correct subject and body' do
+ new_issue_url = project_issue_path(new_issue.project, new_issue)
- aggregate_failures do
- is_expected.to have_referable_subject(issue, reply: true)
- is_expected.to have_body_text(new_issue_url)
- is_expected.to have_body_text(project_issue_path(project, issue))
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.not_to have_body_text(new_issue_url)
+ is_expected.to have_body_text(project_issue_path(project, issue))
+ end
+ end
+
+ it 'does not contain the issue title' do
+ is_expected.not_to have_body_text new_issue.title
+ end
+
+ it 'contains information about missing permissions' do
+ is_expected.to have_body_text "You don't have access to the project."
end
end
end
@@ -218,7 +279,7 @@ describe Notify do
context 'for merge requests' do
describe 'that are new' do
- subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
+ subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id) }
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread with reply-by-email enabled' do
@@ -226,6 +287,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -241,7 +304,10 @@ describe Notify do
end
context 'when sent with a reason' do
- subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) }
+ subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id, NotificationReason::ASSIGNED) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
@@ -262,7 +328,7 @@ describe Notify do
describe 'that are reassigned' do
let(:previous_assignee) { create(:user, name: 'Previous Assignee') }
- subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) }
+ subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id) }
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
@@ -270,6 +336,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -287,7 +355,10 @@ describe Notify do
end
context 'when sent with a reason' do
- subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) }
+ subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
@@ -297,11 +368,11 @@ describe Notify do
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED)
is_expected.to have_body_text(text)
- new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::MENTIONED)
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::MENTIONED)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED)
expect(new_subject).to have_body_text(text)
- new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, nil)
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, nil)
text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil)
expect(new_subject).to have_body_text(text)
end
@@ -309,10 +380,12 @@ describe Notify do
end
describe 'that are new with a description' do
- subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
+ subject { described_class.new_merge_request_email(merge_request.assignee_ids.first, merge_request.id) }
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains the description' do
is_expected.to have_body_text(merge_request.description)
@@ -329,6 +402,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -352,6 +427,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -379,6 +456,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -401,7 +480,7 @@ describe Notify do
source_project: project,
target_project: project,
author: current_user,
- assignee: assignee,
+ assignees: [assignee],
description: 'Awesome description')
end
@@ -413,6 +492,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the merge request author' do
sender = subject.header[:from].addrs[0]
@@ -442,6 +523,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the push user' do
sender = subject.header[:from].addrs[0]
@@ -482,6 +565,9 @@ describe Notify do
subject { described_class.note_issue_email(recipient.id, third_note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'has In-Reply-To header pointing to previous note in discussion' do
expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"])
end
@@ -502,6 +588,9 @@ describe Notify do
subject { described_class.note_issue_email(recipient.id, note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'has In-Reply-To header pointing to the issue' do
expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"])
end
@@ -518,6 +607,9 @@ describe Notify do
subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { project_snippet }
end
@@ -530,11 +622,15 @@ describe Notify do
end
describe 'project was moved' do
+ let(:test_recipient) { user }
subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
is_expected.to have_subject("#{project.name} | Project was moved")
@@ -559,6 +655,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
to_emails = subject.header[:to].addrs.map(&:address)
@@ -582,6 +680,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was denied"
@@ -599,12 +699,16 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was granted"
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.human_access
+ is_expected.to have_body_text 'leave the project'
+ is_expected.to have_body_text project_url(project, leave: 1)
end
end
@@ -629,6 +733,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
@@ -653,6 +759,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
@@ -671,11 +779,13 @@ describe Notify do
invitee
end
- subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
@@ -708,6 +818,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -732,6 +844,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -756,6 +870,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -819,6 +935,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
@@ -845,6 +963,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_referable_subject(merge_request, reply: true)
@@ -871,6 +991,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_referable_subject(issue, reply: true)
@@ -948,6 +1070,8 @@ describe Notify do
it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_commit
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
end
describe 'on a merge request' do
@@ -958,13 +1082,13 @@ describe Notify do
it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_merge_request
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
end
end
end
context 'for a group' do
- set(:group) { create(:group) }
-
describe 'group access requested' do
let(:group) { create(:group, :public, :access_requestable) }
let(:group_member) do
@@ -976,6 +1100,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
to_emails = subject.header[:to].addrs.map(&:address)
@@ -998,6 +1124,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was denied"
@@ -1014,12 +1142,16 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was granted"
is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.human_access
+ is_expected.to have_body_text 'leave the group'
+ is_expected.to have_body_text group_url(group, leave: 1)
end
end
@@ -1044,6 +1176,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
@@ -1068,6 +1202,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
@@ -1091,6 +1227,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
@@ -1140,6 +1278,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1165,6 +1305,8 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1189,6 +1331,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1210,6 +1354,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1237,6 +1383,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1328,6 +1476,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1348,6 +1498,11 @@ describe Notify do
describe 'HTML emails setting' do
let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+ subject { multipart_mail }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
context 'when disabled' do
it 'only sends the text template' do
stub_application_setting(html_emails_enabled: false)
@@ -1386,6 +1541,8 @@ describe Notify do
subject { described_class.note_personal_snippet_email(personal_snippet_note.author_id, personal_snippet_note.id) }
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
is_expected.to have_referable_subject(personal_snippet, reply: true)
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 00613c7b671..384660f7221 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -17,5 +17,12 @@ describe RepositoryCheckMailer do
expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check'
end
+
+ context 'with footer and header' do
+ subject { described_class.notify(1) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
end
end
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index efd87173b9c..2500e2f8333 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -36,7 +36,7 @@ describe AddForeignKeysToTodos, :migration do
end
context 'add foreign key on note_id' do
- let(:note) { create(:note) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:note) { table(:notes).create! }
let!(:todo_with_note) { create_todo(note_id: note.id) }
let!(:todo_with_invalid_note) { create_todo(note_id: 4711) }
let!(:todo_without_note) { create_todo(note_id: nil) }
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
index d8dd7a2fb83..13dc62595b5 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -1,23 +1,21 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
-describe AddHeadPipelineForEachMergeRequest, :delete do
- include ProjectForksHelper
-
+describe AddHeadPipelineForEachMergeRequest, :migration do
let(:migration) { described_class.new }
- let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:other_project) { fork_project(project) }
+ let!(:project) { table(:projects).create! }
+ let!(:other_project) { table(:projects).create! }
- let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline_1) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_1") }
+ let!(:pipeline_2) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
+ let!(:pipeline_3) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
+ let!(:pipeline_4) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_2") }
- let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:mr_1) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_1") }
+ let!(:mr_2) { table(:merge_requests).create!(source_project_id: other_project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_2") }
+ let!(:mr_3) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_2", target_branch: "master") }
+ let!(:mr_4) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_3", target_branch: "master") }
context "#up" do
context "when source_project and source_branch of pipeline are the same of merge request" do
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index 19f06810e54..09c78d02890 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -3,12 +3,30 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
-describe CalculateConvDevIndexPercentages, :delete do
+describe CalculateConvDevIndexPercentages, :migration do
let(:migration) { described_class.new }
let!(:conv_dev_index) do
- create(:conversational_development_index_metric, # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ table(:conversational_development_index_metrics).create!(
+ leader_issues: 9.256,
leader_notes: 0,
+ leader_milestones: 16.2456,
+ leader_boards: 5.2123,
+ leader_merge_requests: 1.2,
+ leader_ci_pipelines: 12.1234,
+ leader_environments: 3.3333,
+ leader_deployments: 1.200,
+ leader_projects_prometheus_active: 0.111,
+ leader_service_desk_issues: 15.891,
+ instance_issues: 1.234,
+ instance_notes: 28.123,
instance_milestones: 0,
+ instance_boards: 3.254,
+ instance_merge_requests: 0.6,
+ instance_ci_pipelines: 2.344,
+ instance_environments: 2.2222,
+ instance_deployments: 0.771,
+ instance_projects_prometheus_active: 0.109,
+ instance_service_desk_issues: 13.345,
percentage_issues: 0,
percentage_notes: 0,
percentage_milestones: 0,
diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
new file mode 100644
index 00000000000..93e7e9304b1
--- /dev/null
+++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb')
+
+describe ChangePackagesSizeDefaultsInProjectStatistics, :migration do
+ let(:project_statistics) { table(:project_statistics) }
+ let(:projects) { table(:projects) }
+
+ it 'removes null packages_size' do
+ stats_to_migrate = 10
+
+ stats_to_migrate.times do |i|
+ p = projects.create!(name: "project #{i}", namespace_id: 1)
+ project_statistics.create!(project_id: p.id, namespace_id: p.namespace_id)
+ end
+
+ expect { migrate! }
+ .to change { ProjectStatistics.where(packages_size: nil).count }
+ .from(stats_to_migrate)
+ .to(0)
+ end
+
+ it 'defaults packages_size to 0' do
+ project = projects.create!(name: 'a new project', namespace_id: 2)
+ stat = project_statistics.create!(project_id: project.id, namespace_id: project.namespace_id)
+
+ expect(stat.packages_size).to be_nil
+
+ migrate!
+
+ stat.reload
+ expect(stat.packages_size).to eq(0)
+ end
+end
diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
new file mode 100644
index 00000000000..572b7dfd0c8
--- /dev/null
+++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190313092516_clean_up_noteable_id_for_notes_on_commits.rb')
+
+describe CleanUpNoteableIdForNotesOnCommits, :migration do
+ let(:notes) { table(:notes) }
+
+ before do
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+
+ notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test')
+ notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test')
+ notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test')
+ end
+
+ it 'clears noteable_id for notes on commits' do
+ expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0)
+ end
+
+ it 'does not clear noteable_id for other notes' do
+ expect { migrate! }.not_to change { other_notes.count }
+ end
+
+ def dirty_notes_on_commits
+ notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL')
+ end
+
+ def other_notes
+ notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL")
+ end
+end
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
index 8f40ac3e38b..0e6bded29b4 100644
--- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
@@ -1,20 +1,17 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb')
-describe CleanupNonexistingNamespacePendingDeleteProjects do
- before do
- # Stub after_save callbacks that will fail when Project has invalid namespace
- allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil)
- allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
- end
+describe CleanupNonexistingNamespacePendingDeleteProjects, :migration do
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
describe '#up' do
- set(:some_project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:some_project) { projects.create! }
+ let(:namespace) { namespaces.create!(name: 'test', path: 'test') }
it 'only cleans up when namespace does not exist' do
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project.save(validate: false)
+ projects.create!(pending_delete: true, namespace_id: namespace.id)
+ project = projects.create!(pending_delete: true, namespace_id: 0)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -22,7 +19,7 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project, pending_delete: true, namespace: create(:namespace)) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(pending_delete: true, namespace_id: namespace.id)
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
index e2ce69a7bb1..58b8b4a16f0 100644
--- a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
+++ b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
@@ -1,25 +1,36 @@
# frozen_string_literal: true
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
+
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20180723130817_delete_inconsistent_internal_id_records.rb')
describe DeleteInconsistentInternalIdRecords, :migration do
- let!(:project1) { create(:project) }
- let!(:project2) { create(:project) }
- let!(:project3) { create(:project) }
+ let!(:namespace) { table(:namespaces).create!(name: 'test', path: 'test') }
+ let!(:project1) { table(:projects).create!(namespace_id: namespace.id) }
+ let!(:project2) { table(:projects).create!(namespace_id: namespace.id) }
+ let!(:project3) { table(:projects).create!(namespace_id: namespace.id) }
- let(:internal_id_query) { ->(project) { InternalId.where(usage: InternalId.usages[scope.to_s.tableize], project: project) } }
+ let(:internal_ids) { table(:internal_ids) }
+ let(:internal_id_query) { ->(project) { InternalId.where(usage: InternalId.usages[scope.to_s.tableize], project_id: project.id) } }
let(:create_models) do
- 3.times { create(scope, project: project1) }
- 3.times { create(scope, project: project2) }
- 3.times { create(scope, project: project3) }
+ [project1, project2, project3].each do |project|
+ 3.times do |i|
+ attributes = required_attributes.merge(project_id: project.id,
+ iid: i.succ)
+
+ table(scope.to_s.pluralize).create!(attributes)
+ end
+ end
end
shared_examples_for 'deleting inconsistent internal_id records' do
before do
create_models
+ [project1, project2, project3].each do |project|
+ internal_ids.create!(project_id: project.id, usage: InternalId.usages[scope.to_s.tableize], last_value: 3)
+ end
+
internal_id_query.call(project1).first.tap do |iid|
iid.last_value = iid.last_value - 2
# This is an inconsistent record
@@ -33,11 +44,11 @@ describe DeleteInconsistentInternalIdRecords, :migration do
end
end
- it "deletes inconsistent issues" do
+ it "deletes inconsistent records" do
expect { migrate! }.to change { internal_id_query.call(project1).size }.from(1).to(0)
end
- it "retains consistent issues" do
+ it "retains consistent records" do
expect { migrate! }.not_to change { internal_id_query.call(project2).size }
end
@@ -48,6 +59,8 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for issues' do
let(:scope) { :issue }
+ let(:required_attributes) { {} }
+
it_behaves_like 'deleting inconsistent internal_id records'
end
@@ -55,9 +68,17 @@ describe DeleteInconsistentInternalIdRecords, :migration do
let(:scope) { :merge_request }
let(:create_models) do
- 3.times { |i| create(scope, target_project: project1, source_project: project1, source_branch: i.to_s) }
- 3.times { |i| create(scope, target_project: project2, source_project: project2, source_branch: i.to_s) }
- 3.times { |i| create(scope, target_project: project3, source_project: project3, source_branch: i.to_s) }
+ [project1, project2, project3].each do |project|
+ 3.times do |i|
+ table(:merge_requests).create!(
+ target_project_id: project.id,
+ source_project_id: project.id,
+ target_branch: 'master',
+ source_branch: j.to_s,
+ iid: i.succ
+ )
+ end
+ end
end
it_behaves_like 'deleting inconsistent internal_id records'
@@ -66,13 +87,6 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for deployments' do
let(:scope) { :deployment }
let(:deployments) { table(:deployments) }
- let(:internal_ids) { table(:internal_ids) }
-
- before do
- internal_ids.create!(project_id: project1.id, usage: 2, last_value: 2)
- internal_ids.create!(project_id: project2.id, usage: 2, last_value: 2)
- internal_ids.create!(project_id: project3.id, usage: 2, last_value: 2)
- end
let(:create_models) do
3.times { |i| deployments.create!(project_id: project1.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) }
@@ -85,17 +99,14 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for milestones (by project)' do
let(:scope) { :milestone }
+ let(:required_attributes) { { title: 'test' } }
+
it_behaves_like 'deleting inconsistent internal_id records'
end
context 'for ci_pipelines' do
let(:scope) { :ci_pipeline }
-
- let(:create_models) do
- create_list(:ci_empty_pipeline, 3, project: project1)
- create_list(:ci_empty_pipeline, 3, project: project2)
- create_list(:ci_empty_pipeline, 3, project: project3)
- end
+ let(:required_attributes) { { ref: 'test' } }
it_behaves_like 'deleting inconsistent internal_id records'
end
@@ -107,12 +118,20 @@ describe DeleteInconsistentInternalIdRecords, :migration do
let(:group2) { groups.create(name: 'Group 2', type: 'Group', path: 'group_2') }
let(:group3) { groups.create(name: 'Group 2', type: 'Group', path: 'group_3') }
- let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace: group) } }
+ let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace_id: group.id) } }
before do
- 3.times { create(:milestone, group_id: group1.id) }
- 3.times { create(:milestone, group_id: group2.id) }
- 3.times { create(:milestone, group_id: group3.id) }
+ [group1, group2, group3].each do |group|
+ 3.times do |i|
+ table(:milestones).create!(
+ group_id: group.id,
+ title: 'test',
+ iid: i.succ
+ )
+ end
+
+ internal_ids.create!(namespace_id: group.id, usage: InternalId.usages['milestones'], last_value: 3)
+ end
internal_id_query.call(group1).first.tap do |iid|
iid.last_value = iid.last_value - 2
@@ -127,11 +146,11 @@ describe DeleteInconsistentInternalIdRecords, :migration do
end
end
- it "deletes inconsistent issues" do
+ it "deletes inconsistent records" do
expect { migrate! }.to change { internal_id_query.call(group1).size }.from(1).to(0)
end
- it "retains consistent issues" do
+ it "retains consistent records" do
expect { migrate! }.not_to change { internal_id_query.call(group2).size }
end
diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb
new file mode 100644
index 00000000000..0d5e33bfd46
--- /dev/null
+++ b/spec/migrations/enqueue_reset_merge_status_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merge_status.rb')
+
+describe EnqueueResetMergeStatus, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, extra_params = {})
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }.merge(extra_params)
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(2, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(3, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(4, state: 'merged', merge_status: 'can_be_merged')
+ create_merge_request(5, state: 'opened', merge_status: 'unchecked')
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(5.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(10.minutes, 3, 3)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
index afcaefa0591..abf39317188 100644
--- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
+++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
@@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do
end
end
+ let(:domains_table) { table(:pages_domains) }
+
describe '#up' do
it 'enqueues a verification worker for every domain' do
- domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") }
+ domains = Array.new(3) do |i|
+ domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}")
+ end
expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3)
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
new file mode 100644
index 00000000000..773bf5222f0
--- /dev/null
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb')
+
+describe GenerateLetsEncryptPrivateKey, :migration do
+ describe '#up' do
+ it 'does not fail' do
+ expect do
+ described_class.new.up
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/migrations/generate_missing_routes_spec.rb b/spec/migrations/generate_missing_routes_spec.rb
index 32515d353b0..30ad135d4df 100644
--- a/spec/migrations/generate_missing_routes_spec.rb
+++ b/spec/migrations/generate_missing_routes_spec.rb
@@ -8,7 +8,7 @@ describe GenerateMissingRoutes, :migration do
let(:routes) { table(:routes) }
it 'creates routes for projects without a route' do
- namespace = namespaces.create!(name: 'GitLab', path: 'gitlab')
+ namespace = namespaces.create!(name: 'GitLab', path: 'gitlab', type: 'Group')
routes.create!(
path: 'gitlab',
diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
index 495e86ee888..71a4e71ac8a 100644
--- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
+++ b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
@@ -1,20 +1,19 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_foreign_key.rb')
-# The schema version has to be far enough in advance to have the
-# only_mirror_protected_branches column in the projects table to create a
-# project via FactoryBot.
-describe IssuesMovedToIdForeignKey, :migration, schema: 20171114150259 do
- let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:issue_third) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+describe IssuesMovedToIdForeignKey, :migration do
+ let(:issues) { table(:issues) }
+
+ let!(:issue_third) { issues.create! }
+ let!(:issue_second) { issues.create!(moved_to_id: issue_third.id) }
+ let!(:issue_first) { issues.create!(moved_to_id: issue_second.id) }
subject { described_class.new }
it 'removes the orphaned moved_to_id' do
subject.down
- issue_third.update(moved_to_id: 100000)
+ issue_third.update!(moved_to_id: 0)
subject.up
diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
index b1ff3cfd355..349cffea70e 100644
--- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
+++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
@@ -25,7 +25,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do
context 'with ProjectAutoDevOps with no domain' do
let(:domain) { nil }
- it 'should not update cluster project' do
+ it 'does not update cluster project' do
migrate!
expect(clusters_without_domain.count).to eq(clusters_table.count)
@@ -35,7 +35,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do
context 'with ProjectAutoDevOps with domain' do
let(:domain) { 'example-domain.com' }
- it 'should update all cluster projects' do
+ it 'updates all cluster projects' do
migrate!
expect(clusters_with_domain.count).to eq(clusters_table.count)
@@ -49,7 +49,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do
setup_cluster_projects_with_domain(quantity: 25, domain: nil)
end
- it 'should only update specific cluster projects' do
+ it 'only updates specific cluster projects' do
migrate!
expect(clusters_with_domain.count).to eq(20)
diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
index b2d8f476bb2..a1f243651b5 100644
--- a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_co
describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queue' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
described_class.new.up
@@ -19,12 +20,12 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1])
described_class.new.up
@@ -39,7 +40,7 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
described_class.new.down
@@ -58,11 +59,4 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
index c18ae3b76d3..66555118a43 100644
--- a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_tra
describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
described_class.new.up
@@ -19,11 +20,11 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.up
@@ -37,7 +38,7 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
described_class.new.down
@@ -56,11 +57,4 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
index 1ee6c440cf4..6ce04805e5d 100644
--- a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180603190921_migrate_object_sto
describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queue' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'object_storage_upload').perform_async('Something', [1])
- stubbed_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1])
+ stub_worker(queue: 'object_storage_upload').perform_async('Something', [1])
+ stub_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1])
described_class.new.up
@@ -23,11 +24,4 @@ describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.up }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index af77d64fdbf..bc826d91471 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb')
-describe MigrateOldArtifacts do
+# Adding the ci_job_artifacts table (from the 20170918072948 schema)
+# makes the use of model code below easier.
+describe MigrateOldArtifacts, :migration, schema: 20170918072948 do
let(:migration) { described_class.new }
let!(:directory) { Dir.mktmpdir }
@@ -16,18 +18,22 @@ describe MigrateOldArtifacts do
end
context 'with migratable data' do
- set(:project1) { create(:project, ci_id: 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:project2) { create(:project, ci_id: 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:project3) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:projects) { table(:projects) }
+ let(:ci_pipelines) { table(:ci_pipelines) }
+ let(:ci_builds) { table(:ci_builds) }
- set(:pipeline1) { create(:ci_empty_pipeline, project: project1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:pipeline2) { create(:ci_empty_pipeline, project: project2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:pipeline3) { create(:ci_empty_pipeline, project: project3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:project1) { projects.create!(ci_id: 2) }
+ let!(:project2) { projects.create!(ci_id: 3) }
+ let!(:project3) { projects.create! }
- let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build2) { create(:ci_build, pipeline: pipeline2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build3) { create(:ci_build, pipeline: pipeline3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline1) { ci_pipelines.create!(project_id: project1.id) }
+ let!(:pipeline2) { ci_pipelines.create!(project_id: project2.id) }
+ let!(:pipeline3) { ci_pipelines.create!(project_id: project3.id) }
+
+ let!(:build_with_legacy_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build_without_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build2) { ci_builds.create!(commit_id: pipeline2.id, project_id: project2.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build3) { ci_builds.create!(commit_id: pipeline3.id, project_id: project3.id, type: 'Ci::Build').becomes(Ci::Build) }
before do
setup_builds(build2, build3)
@@ -39,10 +45,6 @@ describe MigrateOldArtifacts do
expect(build_with_legacy_artifacts.artifacts?).to be_falsey
end
- it "legacy artifacts are set" do
- expect(build_with_legacy_artifacts.legacy_artifacts_file_identifier).not_to be_nil
- end
-
describe '#min_id' do
subject { migration.send(:min_id) }
diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
index e02bcd2f4da..e38044ccceb 100644
--- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
+++ b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_s
describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :pipeline).perform_async('Something', [1])
- stubbed_worker(queue: :build).perform_async('Something', [1])
+ stub_worker(queue: :pipeline).perform_async('Something', [1])
+ stub_worker(queue: :build).perform_async('Something', [1])
described_class.new.up
@@ -20,10 +21,10 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :pipeline_default).perform_async('Class', [1])
- stubbed_worker(queue: :pipeline_processing).perform_async('Class', [2])
- stubbed_worker(queue: :pipeline_hooks).perform_async('Class', [3])
- stubbed_worker(queue: :pipeline_cache).perform_async('Class', [4])
+ stub_worker(queue: :pipeline_default).perform_async('Class', [1])
+ stub_worker(queue: :pipeline_processing).perform_async('Class', [2])
+ stub_worker(queue: :pipeline_hooks).perform_async('Class', [3])
+ stub_worker(queue: :pipeline_cache).perform_async('Class', [4])
described_class.new.down
@@ -45,11 +46,4 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
index f8cf76cb339..94de208e53e 100644
--- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
@@ -3,11 +3,12 @@ require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_mi
describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :storage_migrator).perform_async(1, 5)
+ stub_worker(queue: :storage_migrator).perform_async(1, 5)
described_class.new.up
@@ -18,7 +19,7 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5)
+ stub_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5)
described_class.new.down
@@ -37,11 +38,4 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
index 5e3b20ab4a8..976f3ce07d7 100644
--- a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_hea
describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.up
@@ -19,10 +20,10 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
described_class.new.up
@@ -35,7 +36,7 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.down
@@ -54,11 +55,4 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index 99173708190..88aef3b70b4 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
-describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
+describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :migration do
let(:migration) { described_class.new }
- let!(:user_active_1) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:user_active_2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:user_active_1) { table(:users).create!(email: 'test1', username: 'test1') }
+ let!(:user_active_2) { table(:users).create!(email: 'test2', username: 'test2') }
def record_activity(user, time)
Gitlab::Redis::SharedState.with do |redis|
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 80468b9d01e..a0179ab3ceb 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -3,15 +3,15 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
-describe MigrateUserProjectView, :delete do
+describe MigrateUserProjectView, :migration do
let(:migration) { described_class.new }
- let!(:user) { create(:user, project_view: 'readme') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:user) { table(:users).create!(project_view: User.project_views['readme']) }
describe '#up' do
it 'updates project view setting with new value' do
migration.up
- expect(user.reload.project_view).to eq('files')
+ expect(user.reload.project_view).to eq(User.project_views['files'])
end
end
end
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
index 1f39ad98fb8..d94ae1e52f5 100644
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -1,12 +1,19 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb')
-describe MovePersonalSnippetsFiles do
+describe MovePersonalSnippetsFiles, :migration 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(:notes) { table(:notes) }
+ let(:snippets) { table(:snippets) }
+ let(:uploads) { table(:uploads) }
+
+ let(:user) { table(:users).create!(email: 'user@example.com', projects_limit: 10) }
+ let(:project) { table(:projects).create!(name: 'gitlab', namespace_id: 1) }
+
before do
allow(CarrierWave).to receive(:root).and_return(test_dir)
allow(migration).to receive(:base_directory).and_return(test_dir)
@@ -16,14 +23,14 @@ describe MovePersonalSnippetsFiles do
describe "#up" do
let(:snippet) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id, project_id: project.id)
create_upload('picture.jpg', snippet, create_file: false)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
@@ -62,7 +69,10 @@ describe MovePersonalSnippetsFiles do
secret = "secret#{snippet.id}"
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}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ note = notes.create!(noteable_id: snippet.id,
+ noteable_type: Snippet,
+ note: "with #{markdown}",
+ author_id: user.id)
migration.up
@@ -73,14 +83,14 @@ describe MovePersonalSnippetsFiles do
describe "#down" do
let(:snippet) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
@@ -119,7 +129,10 @@ describe MovePersonalSnippetsFiles do
markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
secret = "secret#{snippet.id}"
file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ note = notes.create!(noteable_id: snippet.id,
+ noteable_type: Snippet,
+ note: "with #{markdown}",
+ author_id: user.id)
migration.down
@@ -135,7 +148,7 @@ describe MovePersonalSnippetsFiles do
secret = '123456789'
filename = 'hello.jpg'
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
@@ -161,7 +174,11 @@ describe MovePersonalSnippetsFiles do
FileUtils.touch(absolute_path)
end
- create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ uploads.create!(model_id: snippet.id,
+ model_type: snippet.class,
+ path: "#{secret}/#{filename}",
+ uploader: PersonalFileUploader,
+ size: 100.kilobytes)
end
def markdown_linking_file(filename, snippet, in_new_path: false)
diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb
index 13b8919343e..e8c44c141c3 100644
--- a/spec/migrations/remove_orphaned_label_links_spec.rb
+++ b/spec/migrations/remove_orphaned_label_links_spec.rb
@@ -10,6 +10,12 @@ describe RemoveOrphanedLabelLinks, :migration do
let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:label) { create_label }
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ end
+
context 'add foreign key on label_id' do
let!(:label_link_with_label) { create_label_link(label_id: label.id) }
let!(:label_link_without_label) { create_label_link(label_id: nil) }
diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
new file mode 100644
index 00000000000..54f3e264df0
--- /dev/null
+++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb')
+
+describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:domains_table) { table(:pages_domains) }
+
+ let(:certificate) do
+ File.read('spec/fixtures/passphrase_x509_certificate.crt')
+ end
+
+ before do
+ domains_table.create!(domain: "domain1.example.com", verification_code: "123")
+ domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '')
+ domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate)
+ domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ first_id = domains_table.find_by_domain("domain3.example.com").id
+ last_id = domains_table.find_by_domain("domain4.example.com").id
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ it 'sets certificate valid_not_before/not_after' do
+ perform_enqueued_jobs do
+ migrate!
+
+ domain = domains_table.find_by_domain("domain3.example.com")
+ expect(domain.certificate_valid_not_before)
+ .to eq(Time.parse("2018-03-23 14:02:08 UTC"))
+ expect(domain.certificate_valid_not_after)
+ .to eq(Time.parse("2019-03-23 14:02:08 UTC"))
+ end
+ end
+end
diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
new file mode 100644
index 00000000000..e397fbb7138
--- /dev/null
+++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb')
+
+describe SchedulePopulateMergeRequestAssigneesTable, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id)
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1)
+ create_merge_request(2)
+ create_merge_request(3)
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(8.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(16.minutes, 3, 3)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_runners_token_encryption_spec.rb b/spec/migrations/schedule_runners_token_encryption_spec.rb
index 376d2795277..97ff6c128f3 100644
--- a/spec/migrations/schedule_runners_token_encryption_spec.rb
+++ b/spec/migrations/schedule_runners_token_encryption_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20181121111200_schedule_runners_token_encryption')
-describe ScheduleRunnersTokenEncryption, :migration do
+describe ScheduleRunnersTokenEncryption, :migration, :sidekiq do
let(:settings) { table(:application_settings) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
new file mode 100644
index 00000000000..bc94f8820bd
--- /dev/null
+++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190214112022_schedule_sync_issuables_state_id.rb')
+
+describe ScheduleSyncIssuablesStateId, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:issues) { table(:issues) }
+ let(:migration) { described_class.new }
+ let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') }
+ let(:project) { projects.create!(namespace_id: group.id) }
+
+ shared_examples 'scheduling migrations' do
+ before do
+ Sidekiq::Worker.clear_all
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'correctly schedules issuable sync background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration).to be_scheduled_delayed_migration(120.seconds, resource_1.id, resource_2.id)
+ expect(migration).to be_scheduled_delayed_migration(240.seconds, resource_3.id, resource_4.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+
+ describe '#up' do
+ context 'issues' do
+ it 'migrates state column to integer' do
+ opened_issue = issues.create!(description: 'first', state: 'opened')
+ closed_issue = issues.create!(description: 'second', state: 'closed')
+ invalid_state_issue = issues.create!(description: 'fourth', state: 'not valid')
+
+ migrate!
+
+ expect(opened_issue.reload.state_id).to eq(Issue.available_states[:opened])
+ expect(closed_issue.reload.state_id).to eq(Issue.available_states[:closed])
+ expect(invalid_state_issue.reload.state_id).to be_nil
+ end
+
+ it_behaves_like 'scheduling migrations' do
+ let(:migration) { described_class::ISSUES_MIGRATION }
+ let!(:resource_1) { issues.create!(description: 'first', state: 'opened') }
+ let!(:resource_2) { issues.create!(description: 'second', state: 'closed') }
+ let!(:resource_3) { issues.create!(description: 'third', state: 'closed') }
+ let!(:resource_4) { issues.create!(description: 'fourth', state: 'closed') }
+ end
+ end
+
+ context 'merge requests' do
+ it 'migrates state column to integer' do
+ opened_merge_request = merge_requests.create!(state: 'opened', target_project_id: project.id, target_branch: 'feature1', source_branch: 'master')
+ closed_merge_request = merge_requests.create!(state: 'closed', target_project_id: project.id, target_branch: 'feature2', source_branch: 'master')
+ merged_merge_request = merge_requests.create!(state: 'merged', target_project_id: project.id, target_branch: 'feature3', source_branch: 'master')
+ locked_merge_request = merge_requests.create!(state: 'locked', target_project_id: project.id, target_branch: 'feature4', source_branch: 'master')
+
+ migrate!
+
+ expect(opened_merge_request.reload.state_id).to eq(MergeRequest.available_states[:opened])
+ expect(closed_merge_request.reload.state_id).to eq(MergeRequest.available_states[:closed])
+ expect(merged_merge_request.reload.state_id).to eq(MergeRequest.available_states[:merged])
+ expect(locked_merge_request.reload.state_id).to eq(MergeRequest.available_states[:locked])
+ end
+
+ it_behaves_like 'scheduling migrations' do
+ let(:migration) { described_class::MERGE_REQUESTS_MIGRATION }
+ let!(:resource_1) { merge_requests.create!(state: 'opened', target_project_id: project.id, target_branch: 'feature1', source_branch: 'master') }
+ let!(:resource_2) { merge_requests.create!(state: 'closed', target_project_id: project.id, target_branch: 'feature2', source_branch: 'master') }
+ let!(:resource_3) { merge_requests.create!(state: 'merged', target_project_id: project.id, target_branch: 'feature3', source_branch: 'master') }
+ let!(:resource_4) { merge_requests.create!(state: 'locked', target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') }
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
new file mode 100644
index 00000000000..105c05bb7ca
--- /dev/null
+++ b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190506135400_schedule_sync_issuables_state_id_where_nil')
+
+describe ScheduleSyncIssuablesStateIdWhereNil, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:issues) { table(:issues) }
+ let(:migration) { described_class.new }
+ let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') }
+ let(:project) { projects.create!(namespace_id: group.id) }
+
+ shared_examples 'scheduling migrations' do
+ before do
+ Sidekiq::Worker.clear_all
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ end
+
+ it 'correctly schedules issuable sync background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration).to be_scheduled_delayed_migration(120.seconds, resource_1.id, resource_3.id)
+ expect(migration).to be_scheduled_delayed_migration(240.seconds, resource_5.id, resource_5.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+
+ describe '#up' do
+ context 'issues' do
+ it_behaves_like 'scheduling migrations' do
+ let(:migration) { described_class::ISSUES_MIGRATION }
+ let!(:resource_1) { issues.create!(description: 'first', state: 'opened', state_id: nil) }
+ let!(:resource_2) { issues.create!(description: 'second', state: 'closed', state_id: 2) }
+ let!(:resource_3) { issues.create!(description: 'third', state: 'closed', state_id: nil) }
+ let!(:resource_4) { issues.create!(description: 'fourth', state: 'closed', state_id: 2) }
+ let!(:resource_5) { issues.create!(description: 'fifth', state: 'closed', state_id: nil) }
+ end
+ end
+
+ context 'merge requests' do
+ it_behaves_like 'scheduling migrations' do
+ let(:migration) { described_class::MERGE_REQUESTS_MIGRATION }
+ let!(:resource_1) { merge_requests.create!(state: 'opened', state_id: nil, target_project_id: project.id, target_branch: 'feature1', source_branch: 'master') }
+ let!(:resource_2) { merge_requests.create!(state: 'closed', state_id: 2, target_project_id: project.id, target_branch: 'feature2', source_branch: 'master') }
+ let!(:resource_3) { merge_requests.create!(state: 'merged', state_id: nil, target_project_id: project.id, target_branch: 'feature3', source_branch: 'master') }
+ let!(:resource_4) { merge_requests.create!(state: 'locked', state_id: 3, target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') }
+ let!(:resource_5) { merge_requests.create!(state: 'locked', state_id: nil, target_project_id: project.id, target_branch: 'feature4', source_branch: 'master') }
+ end
+ end
+ end
+end
diff --git a/spec/migrations/truncate_user_fullname_spec.rb b/spec/migrations/truncate_user_fullname_spec.rb
new file mode 100644
index 00000000000..17fd4d9f688
--- /dev/null
+++ b/spec/migrations/truncate_user_fullname_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190325080727_truncate_user_fullname.rb')
+
+describe TruncateUserFullname, :migration do
+ let(:users) { table(:users) }
+
+ let(:user_short) { create_user(name: 'abc', email: 'test_short@example.com') }
+ let(:user_long) { create_user(name: 'a' * 200 + 'z', email: 'test_long@example.com') }
+
+ def create_user(params)
+ users.create!(params.merge(projects_limit: 0))
+ end
+
+ it 'truncates user full name to the first 128 characters' do
+ expect { migrate! }.to change { user_long.reload.name }.to('a' * 128)
+ end
+
+ it 'does not truncate short names' do
+ expect { migrate! }.not_to change { user_short.reload.name.length }
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index eee80e9bad7..d9d60e02a97 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ability do
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index f49a61062c1..a5f8e999d5d 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe AbuseReport do
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 129b2f92683..2762eaeccd3 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
@@ -7,7 +9,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
- let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') }
+ let(:session) do
+ double(:session, { id: '6919a6f1bb119dd7396fadc38fd18d0d',
+ '[]': {} })
+ end
let(:request) do
double(:request, {
@@ -83,6 +88,52 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
+ describe '.list_sessions' do
+ it 'uses the ActiveSession lookup to return original sessions' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' }))
+
+ redis.sadd(
+ "session:lookup:user:gitlab:#{user.id}",
+ %w[
+ 6919a6f1bb119dd7396fadc38fd18d0d
+ 59822c7d9fcdfa03725eff41782ad97d
+ ]
+ )
+ end
+
+ expect(ActiveSession.list_sessions(user)).to eq [{ _csrf_token: 'abcd' }]
+ end
+ end
+
+ describe '.session_ids_for_user' do
+ it 'uses the user lookup table to return session ids' do
+ session_ids = ['59822c7d9fcdfa03725eff41782ad97d']
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids)
+ end
+
+ expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids)
+ end
+ end
+
+ describe '.sessions_from_ids' do
+ it 'uses the ActiveSession lookup to return original sessions' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' }))
+ end
+
+ expect(ActiveSession.sessions_from_ids(['6919a6f1bb119dd7396fadc38fd18d0d'])).to eq [{ _csrf_token: 'abcd' }]
+ end
+
+ it 'avoids a redis lookup for an empty array' do
+ expect(Gitlab::Redis::SharedState).not_to receive(:with)
+
+ expect(ActiveSession.sessions_from_ids([])).to eq([])
+ end
+ end
+
describe '.set' do
it 'sets a new redis entry for the user session and a lookup entry' do
ActiveSession.set(user, request)
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 28d482adebf..209d138f956 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Appearance do
@@ -78,4 +80,22 @@ describe Appearance do
it { is_expected.to allow_value(hex).for(:message_font_color) }
it { is_expected.not_to allow_value('000').for(:message_font_color) }
end
+
+ describe 'email_header_and_footer_enabled' do
+ context 'default email_header_and_footer_enabled flag value' do
+ it 'returns email_header_and_footer_enabled as true' do
+ appearance = build(:appearance)
+
+ expect(appearance.email_header_and_footer_enabled?).to eq(false)
+ end
+ end
+
+ context 'when setting email_header_and_footer_enabled flag value' do
+ it 'returns email_header_and_footer_enabled as true' do
+ appearance = build(:appearance, email_header_and_footer_enabled: true)
+
+ expect(appearance.email_header_and_footer_enabled?).to eq(true)
+ end
+ end
+ end
end
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index fd25132ed3a..74573d0941c 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -11,6 +11,25 @@ describe ApplicationRecord do
end
end
+ describe '.safe_ensure_unique' do
+ let(:model) { build(:suggestion) }
+ let(:klass) { model.class }
+
+ before do
+ allow(model).to receive(:save).and_raise(ActiveRecord::RecordNotUnique)
+ end
+
+ it 'returns false when ActiveRecord::RecordNotUnique is raised' do
+ expect(model).to receive(:save).once
+ expect(klass.safe_ensure_unique { model.save }).to be_falsey
+ end
+
+ it 'retries based on retry count specified' do
+ expect(model).to receive(:save).exactly(3).times
+ expect(klass.safe_ensure_unique(retries: 2) { model.save }).to be_falsey
+ end
+ end
+
describe '.safe_find_or_create_by' do
it 'creates the user avoiding race conditions' do
expect(Suggestion).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique)
@@ -33,4 +52,10 @@ describe ApplicationRecord do
expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid)
end
end
+
+ describe '.underscore' do
+ it 'returns the underscored value of the class as a string' do
+ expect(MergeRequest.underscore).to eq('merge_request')
+ end
+ end
end
diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb
index aa49594f4d1..dd263335b81 100644
--- a/spec/models/application_setting/term_spec.rb
+++ b/spec/models/application_setting/term_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationSetting::Term do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 789e14e8a20..f8dc1541dd3 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationSetting do
- let(:setting) { described_class.create_from_defaults }
+ subject(:setting) { described_class.create_from_defaults }
it { include(CacheableAttributes) }
+ it { include(ApplicationSettingImplementation) }
it { expect(described_class.current_without_cache).to eq(described_class.last) }
it { expect(setting).to be_valid }
@@ -28,6 +31,20 @@ describe ApplicationSetting do
it { is_expected.to allow_value("dev.gitlab.com").for(:commit_email_hostname) }
it { is_expected.not_to allow_value("@dev.gitlab").for(:commit_email_hostname) }
+ it { is_expected.to allow_value("myemail@gitlab.com").for(:lets_encrypt_notification_email) }
+ it { is_expected.to allow_value(nil).for(:lets_encrypt_notification_email) }
+ it { is_expected.not_to allow_value("notanemail").for(:lets_encrypt_notification_email) }
+ it { is_expected.not_to allow_value("myemail@example.com").for(:lets_encrypt_notification_email) }
+ it { is_expected.to allow_value("myemail@test.example.com").for(:lets_encrypt_notification_email) }
+
+ context "when user accepted let's encrypt terms of service" do
+ before do
+ setting.update(lets_encrypt_terms_of_service_accepted: true)
+ end
+
+ it { is_expected.not_to allow_value(nil).for(:lets_encrypt_notification_email) }
+ end
+
describe 'default_artifacts_expire_in' do
it 'sets an error if it cannot parse' do
setting.update(default_artifacts_expire_in: 'a')
@@ -117,14 +134,6 @@ describe ApplicationSetting do
it { expect(setting.repository_storages).to eq(['default']) }
end
- context '#commit_email_hostname' do
- it 'returns configured gitlab hostname if commit_email_hostname is not defined' do
- setting.update(commit_email_hostname: nil)
-
- expect(setting.commit_email_hostname).to eq("users.noreply.#{Gitlab.config.gitlab.host}")
- end
- end
-
context 'auto_devops_domain setting' do
context 'when auto_devops_enabled? is true' do
before do
@@ -182,15 +191,6 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value("").for(:repository_storages) }
it { is_expected.not_to allow_value(nil).for(:repository_storages) }
end
-
- describe '.pick_repository_storage' do
- it 'uses Array#sample to pick a random storage' do
- array = double('array', sample: 'random')
- expect(setting).to receive(:repository_storages).and_return(array)
-
- expect(setting.pick_repository_storage).to eq('random')
- end
- end
end
context 'housekeeping settings' do
@@ -298,15 +298,59 @@ describe ApplicationSetting do
expect(subject).to be_valid
end
end
+
+ describe 'when external authorization service is enabled' do
+ before do
+ setting.external_authorization_service_enabled = true
+ end
+
+ it { is_expected.not_to allow_value('not a URL').for(:external_authorization_service_url) }
+ it { is_expected.to allow_value('https://example.com').for(:external_authorization_service_url) }
+ it { is_expected.to allow_value('').for(:external_authorization_service_url) }
+ it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) }
+ it { is_expected.not_to allow_value(11).for(:external_authorization_service_timeout) }
+ it { is_expected.not_to allow_value(0).for(:external_authorization_service_timeout) }
+ it { is_expected.not_to allow_value('not a certificate').for(:external_auth_client_cert) }
+ it { is_expected.to allow_value('').for(:external_auth_client_cert) }
+ it { is_expected.to allow_value('').for(:external_auth_client_key) }
+
+ context 'when setting a valid client certificate for external authorization' do
+ let(:certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') }
+
+ before do
+ setting.external_auth_client_cert = certificate_data
+ end
+
+ it 'requires a valid client key when a certificate is set' do
+ expect(setting).not_to allow_value('fefefe').for(:external_auth_client_key)
+ end
+
+ it 'requires a matching certificate' do
+ other_private_key = File.read('spec/fixtures/x509_certificate_pk.key')
+
+ expect(setting).not_to allow_value(other_private_key).for(:external_auth_client_key)
+ end
+
+ it 'the credentials are valid when the private key can be read and matches the certificate' do
+ tls_attributes = [:external_auth_client_key_pass,
+ :external_auth_client_key,
+ :external_auth_client_cert]
+ setting.external_auth_client_key = File.read('spec/fixtures/passphrase_x509_certificate_pk.key')
+ setting.external_auth_client_key_pass = '5iveL!fe'
+
+ setting.validate
+
+ expect(setting.errors).not_to include(*tls_attributes)
+ end
+ end
+ end
end
context 'restrict creating duplicates' do
- before do
- described_class.create_from_defaults
- end
+ let!(:current_settings) { described_class.create_from_defaults }
- it 'raises an record creation violation if already created' do
- expect { described_class.create_from_defaults }.to raise_error(ActiveRecord::RecordNotUnique)
+ it 'returns the current settings' do
+ expect(described_class.create_from_defaults).to eq(current_settings)
end
end
@@ -367,65 +411,6 @@ describe ApplicationSetting do
end
end
- context 'restricted signup domains' do
- it 'sets single domain' do
- setting.domain_whitelist_raw = 'example.com'
- expect(setting.domain_whitelist).to eq(['example.com'])
- end
-
- it 'sets multiple domains with spaces' do
- setting.domain_whitelist_raw = 'example.com *.example.com'
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
-
- it 'sets multiple domains with newlines and a space' do
- setting.domain_whitelist_raw = "example.com\n *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
-
- it 'sets multiple domains with commas' do
- setting.domain_whitelist_raw = "example.com, *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
- end
-
- context 'blacklisted signup domains' do
- it 'sets single domain' do
- setting.domain_blacklist_raw = 'example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com')
- end
-
- it 'sets multiple domains with spaces' do
- setting.domain_blacklist_raw = 'example.com *.example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with newlines and a space' do
- setting.domain_blacklist_raw = "example.com\n *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with commas' do
- setting.domain_blacklist_raw = "example.com, *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with semicolon' do
- setting.domain_blacklist_raw = "example.com; *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with mixture of everything' do
- setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
- end
-
- it 'sets multiple domain with file' do
- setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
- expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
- end
- end
-
describe 'performance bar settings' do
describe 'performance_bar_allowed_group' do
context 'with no performance_bar_allowed_group_id saved' do
@@ -462,142 +447,6 @@ describe ApplicationSetting do
end
end
- describe 'usage ping settings' do
- context 'when the usage ping is disabled in gitlab.yml' do
- before do
- allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(false)
- end
-
- it 'does not allow the usage ping to be configured' do
- expect(setting.usage_ping_can_be_configured?).to be_falsey
- end
-
- context 'when the usage ping is disabled in the DB' do
- before do
- setting.usage_ping_enabled = false
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
-
- context 'when the usage ping is enabled in the DB' do
- before do
- setting.usage_ping_enabled = true
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
- end
-
- context 'when the usage ping is enabled in gitlab.yml' do
- before do
- allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(true)
- end
-
- it 'allows the usage ping to be configured' do
- expect(setting.usage_ping_can_be_configured?).to be_truthy
- end
-
- context 'when the usage ping is disabled in the DB' do
- before do
- setting.usage_ping_enabled = false
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
-
- context 'when the usage ping is enabled in the DB' do
- before do
- setting.usage_ping_enabled = true
- end
-
- it 'returns true for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_truthy
- end
- end
- end
- end
-
- describe '#allowed_key_types' do
- it 'includes all key types by default' do
- expect(setting.allowed_key_types).to contain_exactly(*described_class::SUPPORTED_KEY_TYPES)
- end
-
- it 'excludes disabled key types' do
- expect(setting.allowed_key_types).to include(:ed25519)
-
- setting.ed25519_key_restriction = described_class::FORBIDDEN_KEY_VALUE
-
- expect(setting.allowed_key_types).not_to include(:ed25519)
- end
- end
-
- describe '#key_restriction_for' do
- it 'returns the restriction value for recognised types' do
- setting.rsa_key_restriction = 1024
-
- expect(setting.key_restriction_for(:rsa)).to eq(1024)
- end
-
- it 'allows types to be passed as a string' do
- setting.rsa_key_restriction = 1024
-
- expect(setting.key_restriction_for('rsa')).to eq(1024)
- end
-
- it 'returns forbidden for unrecognised type' do
- expect(setting.key_restriction_for(:foo)).to eq(described_class::FORBIDDEN_KEY_VALUE)
- end
- end
-
- describe '#allow_signup?' do
- it 'returns true' do
- expect(setting.allow_signup?).to be_truthy
- end
-
- it 'returns false if signup is disabled' do
- allow(setting).to receive(:signup_enabled?).and_return(false)
-
- expect(setting.allow_signup?).to be_falsey
- end
-
- it 'returns false if password authentication is disabled for the web interface' do
- allow(setting).to receive(:password_authentication_enabled_for_web?).and_return(false)
-
- expect(setting.allow_signup?).to be_falsey
- end
- end
-
- describe '#user_default_internal_regex_enabled?' do
- using RSpec::Parameterized::TableSyntax
-
- where(:user_default_external, :user_default_internal_regex, :result) do
- false | nil | false
- false | '' | false
- false | '^(?:(?!\.ext@).)*$\r?\n?' | false
- true | '' | false
- true | nil | false
- true | '^(?:(?!\.ext@).)*$\r?\n?' | true
- end
-
- with_them do
- before do
- setting.update(user_default_external: user_default_external)
- setting.update(user_default_internal_regex: user_default_internal_regex)
- end
-
- subject { setting.user_default_internal_regex_enabled? }
-
- it { is_expected.to eq(result) }
- end
- end
-
context 'diff limit settings' do
describe '#diff_max_patch_bytes' do
context 'validations' do
@@ -613,23 +462,5 @@ describe ApplicationSetting do
end
end
- describe '#archive_builds_older_than' do
- subject { setting.archive_builds_older_than }
-
- context 'when the archive_builds_in_seconds is set' do
- before do
- setting.archive_builds_in_seconds = 3600
- end
-
- it { is_expected.to be_within(1.minute).of(1.hour.ago) }
- end
-
- context 'when the archive_builds_in_seconds is set' do
- before do
- setting.archive_builds_in_seconds = nil
- end
-
- it { is_expected.to be_nil }
- end
- end
+ it_behaves_like 'application settings examples'
end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 3f52091698c..8452ac69734 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AwardEmoji do
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
index 33dc19e3432..c661f5384ea 100644
--- a/spec/models/badge_spec.rb
+++ b/spec/models/badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Badge do
@@ -59,7 +61,7 @@ describe Badge do
end
shared_examples 'rendered_links' do
- it 'should use the project information to populate the url placeholders' do
+ it 'uses the project information to populate the url placeholders' do
stub_project_commit_info(project)
expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
index ed7f83d0489..c297bc957ea 100644
--- a/spec/models/badges/group_badge_spec.rb
+++ b/spec/models/badges/group_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupBadge do
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
index 0e1a8159cb6..d41c5cf2ca1 100644
--- a/spec/models/badges/project_badge_spec.rb
+++ b/spec/models/badges/project_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectBadge do
@@ -12,7 +14,7 @@ describe ProjectBadge do
end
shared_examples 'rendered_links' do
- it 'should use the badge project information to populate the url placeholders' do
+ it 'uses the badge project information to populate the url placeholders' do
stub_project_commit_info(project)
expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 05cf242e84d..8364293b908 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -1,4 +1,6 @@
# encoding: utf-8
+# frozen_string_literal: true
+
require 'rails_helper'
describe Blob do
@@ -41,6 +43,21 @@ describe Blob do
changelog.id
contributing.id
end
+
+ it 'does not include blobs from previous requests in later requests' do
+ changelog = described_class.lazy(project, commit_id, 'CHANGELOG')
+ contributing = described_class.lazy(same_project, commit_id, 'CONTRIBUTING.md')
+
+ # Access property so the values are loaded
+ changelog.id
+ contributing.id
+
+ readme = described_class.lazy(project, commit_id, 'README.md')
+
+ expect(project.repository).to receive(:blobs_at).with([[commit_id, 'README.md']]).once.and_call_original
+
+ readme.id
+ end
end
describe '#data' do
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 7ba28f72215..39c7a34f052 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Base do
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
index db41eca0fc8..0fcc94182af 100644
--- a/spec/models/blob_viewer/changelog_spec.rb
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Changelog do
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index 85b0d9668a0..eda34779679 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::ComposerJson do
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index d8c4490637f..b6cc82c03ba 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Gemspec do
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index 16bf947b493..db405ceb4f1 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::GitlabCiYml do
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
index 222ed166ee0..e02bfae3829 100644
--- a/spec/models/blob_viewer/license_spec.rb
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::License do
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index fbaa8d47a71..b317278f3c8 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::PackageJson do
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index 9a23877b23f..7f1fb8666fd 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::PodspecJson do
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 02d06ea24d6..527ae79d766 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Podspec do
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
index 8d11d58cfca..958927bddb4 100644
--- a/spec/models/blob_viewer/readme_spec.rb
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Readme do
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index c13662427b0..f7ce873c9d1 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::RouteMap do
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
index 63790486200..f95305abe78 100644
--- a/spec/models/blob_viewer/server_side_spec.rb
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::ServerSide do
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
index 59ad4e5417e..558be61824f 100644
--- a/spec/models/board_group_recent_visit_spec.rb
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -50,15 +50,25 @@ describe BoardGroupRecentVisit do
end
describe '#latest' do
- it 'returns the most recent visited' do
- board2 = create(:board, group: group)
- board3 = create(:board, group: group)
+ def create_visit(time)
+ create :board_group_recent_visit, group: group, user: user, updated_at: time
+ end
- create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago
- create :board_group_recent_visit, group: board2.group, board: board2, user: user, updated_at: 5.days.ago
- recent = create :board_group_recent_visit, group: board3.group, board: board3, user: user, updated_at: 1.day.ago
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
expect(described_class.latest(user, group)).to eq recent
end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, group, count: 3)).to eq([visit2, visit1, visit3])
+ end
end
end
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
index 275581945fa..e404fb3bbdb 100644
--- a/spec/models/board_project_recent_visit_spec.rb
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -50,15 +50,25 @@ describe BoardProjectRecentVisit do
end
describe '#latest' do
- it 'returns the most recent visited' do
- board2 = create(:board, project: project)
- board3 = create(:board, project: project)
+ def create_visit(time)
+ create :board_project_recent_visit, project: project, user: user, updated_at: time
+ end
- create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago
- create :board_project_recent_visit, project: board2.project, board: board2, user: user, updated_at: 5.days.ago
- recent = create :board_project_recent_visit, project: board3.project, board: board3, user: user, updated_at: 1.day.ago
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
expect(described_class.latest(user, project)).to eq recent
end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, project, count: 3)).to eq([visit2, visit1, visit3])
+ end
end
end
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 12d29540137..54452faa0e1 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Board do
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 89839709131..3ab013ddc0e 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BroadcastMessage do
@@ -95,6 +97,12 @@ describe BroadcastMessage do
end
end
+ describe '#attributes' do
+ it 'includes message_html field' do
+ expect(subject.attributes.keys).to include("cached_markdown_version", "message_html")
+ end
+ end
+
describe '#active?' do
it 'is truthy when started and not ended' do
message = build(:broadcast_message)
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 504bc710b25..82991937644 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatName do
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 70a9a206faa..76beb3d506b 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatTeam do
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 0014bbcf9f5..f63816fd92a 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ArtifactBlob do
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 741cdfef1a5..eb32198265b 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Bridge do
@@ -8,6 +10,8 @@ describe Ci::Bridge do
create(:ci_bridge, pipeline: pipeline)
end
+ it { is_expected.to include_module(Ci::PipelineDelegator) }
+
describe '#tags' do
it 'only has a bridge tag' do
expect(bridge.tags).to eq [:bridge]
@@ -22,4 +26,19 @@ describe Ci::Bridge do
expect(status).to be_a Gitlab::Ci::Status::Success
end
end
+
+ describe '#scoped_variables_hash' do
+ it 'returns a hash representing variables' do
+ variables = %w[
+ CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA
+ CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG
+ CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
+ CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID
+ CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
+ CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
+ ]
+
+ expect(bridge.scoped_variables_hash.keys).to include(*variables)
+ end
+ end
end
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 016a5899eef..917a65ddf21 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildMetadata do
diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb
index 35622366829..e51fd009f50 100644
--- a/spec/models/ci/build_runner_session_spec.rb
+++ b/spec/models/ci/build_runner_session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildRunnerSession, model: true do
@@ -11,25 +13,33 @@ describe Ci::BuildRunnerSession, model: true do
it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') }
describe '#terminal_specification' do
- let(:terminal_specification) { subject.terminal_specification }
+ let(:specification) { subject.terminal_specification }
+
+ it 'returns terminal.gitlab.com protocol' do
+ expect(specification[:subprotocols]).to eq ['terminal.gitlab.com']
+ end
+
+ it 'returns a wss url' do
+ expect(specification[:url]).to start_with('wss://')
+ end
it 'returns empty hash if no url' do
subject.url = ''
- expect(terminal_specification).to be_empty
+ expect(specification).to be_empty
end
context 'when url is present' do
it 'returns ca_pem nil if empty certificate' do
subject.certificate = ''
- expect(terminal_specification[:ca_pem]).to be_nil
+ expect(specification[:ca_pem]).to be_nil
end
it 'adds Authorization header if authorization is present' do
subject.authorization = 'whatever'
- expect(terminal_specification[:headers]).to include(Authorization: ['whatever'])
+ expect(specification[:headers]).to include(Authorization: ['whatever'])
end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 17540443688..d98db024f73 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Build do
@@ -23,9 +25,10 @@ describe Ci::Build do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
- it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
-
- it { is_expected.to be_a(ArtifactMigratable) }
+ it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) }
+ it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
+ it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
+ it { is_expected.to include_module(Ci::PipelineDelegator) }
describe 'associations' do
it 'has a bidirectional relationship with projects' do
@@ -107,14 +110,6 @@ describe Ci::Build do
end
end
- context 'when job has a legacy archive' do
- let!(:job) { create(:ci_build, :legacy_artifacts) }
-
- it 'returns the job' do
- is_expected.to include(job)
- end
- end
-
context 'when job has a job artifact archive' do
let!(:job) { create(:ci_build, :artifacts) }
@@ -152,8 +147,8 @@ describe Ci::Build do
end
end
- describe '.with_test_reports' do
- subject { described_class.with_test_reports }
+ describe '.with_reports' do
+ subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
context 'when build has a test report' do
let!(:build) { create(:ci_build, :success, :test_reports) }
@@ -186,6 +181,37 @@ describe Ci::Build do
end
end
+ describe '#enqueue' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.enqueue }
+
+ before do
+ allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites)
+ allow(Ci::PrepareBuildService).to receive(:perform_async)
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:has_prerequisites) { true }
+
+ it 'transitions to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:has_prerequisites) { false }
+
+ it 'transitions to pending' do
+ subject
+
+ expect(build).to be_pending
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -344,6 +370,18 @@ describe Ci::Build do
expect(build).to be_pending
end
+
+ context 'build has unmet prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'transits to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
end
end
@@ -402,43 +440,11 @@ describe Ci::Build do
end
end
end
-
- context 'when legacy artifacts are used' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- subject { build.artifacts? }
-
- context 'is expired' do
- let(:build) { create(:ci_build, :legacy_artifacts, :expired) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive does not exist' do
- let(:build) { create(:ci_build) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive exists' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_truthy }
- end
- end
end
describe '#browsable_artifacts?' do
subject { build.browsable_artifacts? }
- context 'artifacts metadata does not exist' do
- before do
- build.update(legacy_artifacts_metadata: nil)
- end
-
- it { is_expected.to be_falsy }
- end
-
context 'artifacts metadata does exists' do
let(:build) { create(:ci_build, :artifacts) }
@@ -694,12 +700,6 @@ describe Ci::Build do
it { is_expected.to be_truthy }
end
-
- context 'when build does not have job artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_falsy }
- end
end
describe '#has_old_trace?' do
@@ -787,6 +787,10 @@ describe Ci::Build do
let(:deployment) { build.deployment }
let(:environment) { deployment.environment }
+ before do
+ allow(Deployments::FinishedWorker).to receive(:perform_async)
+ end
+
it 'has deployments record with created status' do
expect(deployment).to be_created
expect(environment.name).to eq('review/master')
@@ -1022,11 +1026,11 @@ describe Ci::Build do
describe 'erasable build' do
shared_examples 'erasable' do
it 'removes artifact file' do
- expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
end
it 'removes artifact metadata file' do
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
end
it 'removes all job_artifacts' do
@@ -1118,7 +1122,7 @@ describe Ci::Build do
let!(:build) { create(:ci_build, :success, :artifacts) }
before do
- build.remove_artifacts_metadata!
+ build.erase_erasable_artifacts!
end
describe '#erase' do
@@ -1129,76 +1133,6 @@ describe Ci::Build do
end
end
end
-
- context 'old artifacts' do
- context 'build is erasable' do
- context 'new artifacts' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
-
- describe '#erase' do
- before do
- build.erase(erased_by: erased_by)
- end
-
- context 'erased by user' do
- let!(:erased_by) { create(:user, username: 'eraser') }
-
- include_examples 'erasable'
-
- it 'records user who erased a build' do
- expect(build.erased_by).to eq erased_by
- end
- end
-
- context 'erased by system' do
- let(:erased_by) { nil }
-
- include_examples 'erasable'
-
- it 'does not set user who erased a build' do
- expect(build.erased_by).to be_nil
- end
- end
- end
-
- describe '#erasable?' do
- subject { build.erasable? }
- it { is_expected.to be_truthy }
- end
-
- describe '#erased?' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
- subject { build.erased? }
-
- context 'job has not been erased' do
- it { is_expected.to be_falsey }
- end
-
- context 'job has been erased' do
- before do
- build.erase
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'metadata and build trace are not available' do
- let!(:build) { create(:ci_build, :success, :legacy_artifacts) }
-
- before do
- build.remove_artifacts_metadata!
- end
-
- describe '#erase' do
- it 'does not raise error' do
- expect { build.erase }.not_to raise_error
- end
- end
- end
- end
- end
- end
end
describe '#erase_erasable_artifacts!' do
@@ -2054,54 +1988,6 @@ describe Ci::Build do
end
end
- context 'when updating the build' do
- let(:build) { create(:ci_build, artifacts_size: 23) }
-
- it 'updates project statistics' do
- build.artifacts_size = 42
-
- expect(build).to receive(:update_project_statistics_after_save).and_call_original
-
- expect { build.save! }
- .to change { build.project.statistics.reload.build_artifacts_size }
- .by(19)
- end
-
- context 'when the artifact size stays the same' do
- it 'does not update project statistics' do
- build.name = 'changed'
-
- expect(build).not_to receive(:update_project_statistics_after_save)
-
- build.save!
- end
- end
- end
-
- context 'when destroying the build' do
- let!(:build) { create(:ci_build, artifacts_size: 23) }
-
- it 'updates project statistics' do
- expect(ProjectStatistics)
- .to receive(:increment_statistic)
- .and_call_original
-
- expect { build.destroy! }
- .to change { build.project.statistics.reload.build_artifacts_size }
- .by(-23)
- end
-
- context 'when the build is destroyed due to the project being destroyed' do
- it 'does not update the project statistics' do
- expect(ProjectStatistics)
- .not_to receive(:increment_statistic)
-
- build.project.update(pending_delete: true)
- build.project.destroy!
- end
- end
- end
-
describe '#variables' do
let(:container_registry_enabled) { false }
@@ -2114,55 +2000,56 @@ describe Ci::Build do
context 'returns variables' do
let(:predefined_variables) do
[
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
- { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true },
- { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
- { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true },
- { key: 'CI_JOB_TOKEN', value: 'my-token', public: false },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
- { key: 'CI', value: 'true', public: true },
- { key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true },
- { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true },
- { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true },
- { key: 'CI_JOB_NAME', value: 'test', public: true },
- { key: 'CI_JOB_STAGE', value: 'test', public: true },
- { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
- { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true },
- { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
- { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
- { key: 'CI_NODE_TOTAL', value: '1', public: true },
- { key: 'CI_BUILD_REF', value: build.sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true },
- { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true },
- { key: 'CI_BUILD_NAME', value: 'test', public: true },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true },
- { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
- { key: 'CI_PROJECT_NAME', value: project.path, public: true },
- { key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
- { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
- { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
- { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true },
- { key: 'CI_PAGES_URL', value: project.pages_url, public: true },
- { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true },
- { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true },
- { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
- { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true },
- { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true },
- { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true },
- { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true }
+ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false },
+ { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true, masked: false },
+ { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false },
+ { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false },
+ { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: true },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false },
+ { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: true },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false },
+ { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false },
+ { key: 'CI', value: 'true', public: true, masked: false },
+ { key: 'GITLAB_CI', value: 'true', public: true, masked: false },
+ { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
+ { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
+ { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false },
+ { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
+ { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
+ { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false },
+ { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false },
+ { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
+ { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false },
+ { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false },
+ { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
+ { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
+ { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
+ { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
+ { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false },
+ { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false },
+ { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false },
+ { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false },
+ { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false },
+ { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
+ { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
+ { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
+ { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
+ { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
+ { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
+ { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
+ { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false },
+ { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
+ { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
+ { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
+ { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false },
+ { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false }
]
end
@@ -2175,10 +2062,10 @@ describe Ci::Build do
describe 'variables ordering' do
context 'when variables hierarchy is stubbed' do
- let(:build_pre_var) { { key: 'build', value: 'value', public: true } }
- let(:project_pre_var) { { key: 'project', value: 'value', public: true } }
- let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } }
- let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } }
+ let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } }
+ let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } }
before do
allow(build).to receive(:predefined_variables) { [build_pre_var] }
@@ -2200,7 +2087,7 @@ describe Ci::Build do
project_pre_var,
pipeline_pre_var,
build_yaml_var,
- { key: 'secret', value: 'value', public: false }])
+ { key: 'secret', value: 'value', public: false, masked: false }])
end
end
@@ -2233,10 +2120,10 @@ describe Ci::Build do
context 'when build has user' do
let(:user_variables) do
[
- { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
- { key: 'GITLAB_USER_EMAIL', value: user.email, public: true },
- { key: 'GITLAB_USER_LOGIN', value: user.username, public: true },
- { key: 'GITLAB_USER_NAME', value: user.name, public: true }
+ { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false },
+ { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false },
+ { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false },
+ { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false }
]
end
@@ -2247,11 +2134,24 @@ describe Ci::Build do
it { user_variables.each { |v| is_expected.to include(v) } }
end
+ context 'when build belongs to a pipeline for merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_branch: 'improve/awesome') }
+ let(:pipeline) { merge_request.all_pipelines.first }
+ let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+
+ it 'returns values based on source ref' do
+ is_expected.to include(
+ { key: 'CI_COMMIT_REF_NAME', value: 'improve/awesome', public: true, masked: false },
+ { key: 'CI_COMMIT_REF_SLUG', value: 'improve-awesome', public: true, masked: false }
+ )
+ end
+ end
+
context 'when build has an environment' do
let(:environment_variables) do
[
- { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
- { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
+ { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false },
+ { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }
]
end
@@ -2286,7 +2186,7 @@ describe Ci::Build do
before do
environment_variables <<
- { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
+ { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false }
end
context 'when the URL was set from the job' do
@@ -2323,7 +2223,7 @@ describe Ci::Build do
end
let(:manual_variable) do
- { key: 'CI_JOB_MANUAL', value: 'true', public: true }
+ { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false }
end
it { is_expected.to include(manual_variable) }
@@ -2331,7 +2231,7 @@ describe Ci::Build do
context 'when build is for tag' do
let(:tag_variable) do
- { key: 'CI_COMMIT_TAG', value: 'master', public: true }
+ { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false }
end
before do
@@ -2343,7 +2243,7 @@ describe Ci::Build do
context 'when CI variable is defined' do
let(:ci_variable) do
- { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
end
before do
@@ -2358,7 +2258,7 @@ describe Ci::Build do
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
let(:protected_variable) do
- { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
end
before do
@@ -2390,7 +2290,7 @@ describe Ci::Build do
context 'when group CI variable is defined' do
let(:ci_variable) do
- { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
end
before do
@@ -2405,7 +2305,7 @@ describe Ci::Build do
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
let(:protected_variable) do
- { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
end
before do
@@ -2444,11 +2344,11 @@ describe Ci::Build do
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
- { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false }
+ { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false }
end
let(:predefined_trigger_variable) do
- { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
+ { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false }
end
before do
@@ -2480,7 +2380,7 @@ describe Ci::Build do
context 'when pipeline has a variable' do
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
- it { is_expected.to include(pipeline_variable.to_runner_variable) }
+ it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) }
end
context 'when a job was triggered by a pipeline schedule' do
@@ -2497,16 +2397,16 @@ describe Ci::Build do
pipeline_schedule.reload
end
- it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) }
+ it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) }
end
context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
- { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
+ { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false }
end
let(:ci_registry_image) do
- { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true }
+ { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false }
end
context 'and is disabled for project' do
@@ -2535,13 +2435,13 @@ describe Ci::Build do
build.update(runner: runner)
end
- it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) }
end
context 'when build is for a deployment' do
- let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
+ let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } }
before do
build.environment = 'production'
@@ -2555,7 +2455,7 @@ describe Ci::Build do
end
context 'when project has custom CI config path' do
- let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
before do
project.update(ci_config_path: 'custom')
@@ -2564,30 +2464,6 @@ describe Ci::Build do
it { is_expected.to include(ci_config_path) }
end
- context 'when using auto devops' do
- context 'and is enabled' do
- before do
- project.create_auto_devops!(enabled: true, domain: 'example.com')
- end
-
- it "includes AUTO_DEVOPS_DOMAIN" do
- is_expected.to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
- end
- end
-
- context 'and is disabled' do
- before do
- project.create_auto_devops!(enabled: false, domain: 'example.com')
- end
-
- it "includes AUTO_DEVOPS_DOMAIN" do
- is_expected.not_to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
- end
- end
- end
-
context 'when pipeline variable overrides build variable' do
before do
build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
@@ -2598,9 +2474,9 @@ describe Ci::Build do
variables = subject.reverse.uniq { |variable| variable[:key] }.reverse
expect(variables)
- .not_to include(key: 'MYVAR', value: 'myvar', public: true)
+ .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false)
expect(variables)
- .to include(key: 'MYVAR', value: 'pipeline value', public: false)
+ .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false)
end
end
@@ -2616,13 +2492,13 @@ describe Ci::Build do
it 'includes CI_NODE_INDEX' do
is_expected.to include(
- { key: 'CI_NODE_INDEX', value: index.to_s, public: true }
+ { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false }
)
end
it 'includes correct CI_NODE_TOTAL' do
is_expected.to include(
- { key: 'CI_NODE_TOTAL', value: total.to_s, public: true }
+ { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false }
)
end
end
@@ -2638,10 +2514,12 @@ describe Ci::Build do
)
end
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+
it 'returns static predefined variables' do
expect(build.variables.size).to be >= 28
expect(build.variables)
- .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
expect(build).not_to be_persisted
end
end
@@ -2651,8 +2529,8 @@ describe Ci::Build do
let(:deploy_token_variables) do
[
- { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true },
- { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false }
+ { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false },
+ { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: true }
]
end
@@ -2661,13 +2539,13 @@ describe Ci::Build do
project.deploy_tokens << deploy_token
end
- it 'should include deploy token variables' do
+ it 'includes deploy token variables' do
is_expected.to include(*deploy_token_variables)
end
end
context 'when gitlab-deploy-token does not exist' do
- it 'should not include deploy token variables' do
+ it 'does not include deploy token variables' do
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
end
@@ -2687,6 +2565,8 @@ describe Ci::Build do
)
end
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+
it 'does not persist the build' do
expect(build).to be_valid
expect(build).not_to be_persisted
@@ -2711,7 +2591,7 @@ describe Ci::Build do
end
expect(variables)
- .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
end
it 'does not return prohibited variables' do
@@ -2734,6 +2614,122 @@ describe Ci::Build do
end
end
+ describe '#secret_group_variables' do
+ subject { build.secret_group_variables }
+
+ let!(:variable) { create(:ci_group_variable, protected: true, group: group) }
+
+ context 'when ref is branch' do
+ let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: 'master', project: project)
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_tag, project: project, name: 'v*')
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+ let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
+ end
+
+ it 'does not return protected variables as it is not supported for merge request pipelines' do
+ is_expected.not_to include(variable)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+ end
+
+ describe '#secret_project_variables' do
+ subject { build.secret_project_variables }
+
+ let!(:variable) { create(:ci_variable, protected: true, project: project) }
+
+ context 'when ref is branch' do
+ let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: 'master', project: project)
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_tag, project: project, name: 'v*')
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+ let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
+ end
+
+ it 'does not return protected variables as it is not supported for merge request pipelines' do
+ is_expected.not_to include(variable)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+ end
+
describe '#scoped_variables_hash' do
context 'when overriding CI variables' do
before do
@@ -2760,6 +2756,28 @@ describe Ci::Build do
end
end
+ describe '#any_unmet_prerequisites?' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.any_unmet_prerequisites? }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return(prerequisites)
+ end
+
+ context 'build has prerequisites' do
+ let(:prerequisites) { [double] }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'build does not have prerequisites' do
+ let(:prerequisites) { [] }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#yaml_variables' do
let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) }
@@ -2812,6 +2830,20 @@ describe Ci::Build do
end
end
+ describe 'state transition: any => [:preparing]' do
+ let(:build) { create(:ci_build, :created) }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'queues BuildPrepareWorker' do
+ expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id)
+
+ build.enqueue
+ end
+ end
+
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
@@ -2991,7 +3023,7 @@ describe Ci::Build do
it 'does not try to create a todo' do
project.add_developer(user)
- expect(service).not_to receive(:commit_status_merge_requests)
+ expect(service).not_to receive(:pipeline_merge_requests)
subject.drop!
end
@@ -3027,7 +3059,23 @@ describe Ci::Build do
end
context 'when build is not configured to be retried' do
- subject { create(:ci_build, :running, project: project, user: user) }
+ subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let(:merge_request) do
+ create(:merge_request, :opened,
+ source_branch: 'feature',
+ source_project: project,
+ target_branch: 'master',
+ target_project: project)
+ end
it 'does not retry build' do
expect(described_class).not_to receive(:retry)
@@ -3046,7 +3094,10 @@ describe Ci::Build do
it 'creates a todo' do
project.add_developer(user)
- expect(service).to receive(:commit_status_merge_requests)
+ expect_next_instance_of(TodoService) do |todo_service|
+ expect(todo_service)
+ .to receive(:merge_request_build_failed).with(merge_request)
+ end
subject.drop!
end
@@ -3299,6 +3350,18 @@ describe Ci::Build do
end
end
+ describe '#report_artifacts' do
+ subject { build.report_artifacts }
+
+ context 'when the build has reports' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+
+ it 'returns the artifacts with reports' do
+ expect(subject).to contain_exactly(report)
+ end
+ end
+ end
+
describe '#artifacts_metadata_entry' do
set(:build) { create(:ci_build, project: project) }
let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
@@ -3423,6 +3486,24 @@ describe Ci::Build do
it { is_expected.to be_falsey }
end
end
+
+ context 'when refspecs feature is required by build' do
+ before do
+ allow(build).to receive(:merge_request_ref?) { true }
+ end
+
+ context 'when runner provides given feature' do
+ let(:runner_features) { { refspecs: true } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when runner does not provide given feature' do
+ let(:runner_features) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
describe '#deployment_status' do
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index d214fdf369a..59db347582b 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -171,7 +171,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do
- context 'when new data fullfilled chunk size' do
+ context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE }
it 'schedules trace chunk flush worker' do
@@ -193,7 +193,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
shared_examples_for 'Scheduling no sidekiq worker' do
- context 'when new data fullfilled chunk size' do
+ context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE }
it 'does not schedule trace chunk flush worker' do
diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb
index d8fc9d57e95..eb94d7dae38 100644
--- a/spec/models/ci/build_trace_chunks/database_spec.rb
+++ b/spec/models/ci/build_trace_chunks/database_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Database do
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index 8f49190af13..b8d78bcd069 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Fog do
diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb
index 9da1e6a95ee..6cff33d24fa 100644
--- a/spec/models/ci/build_trace_chunks/redis_spec.rb
+++ b/spec/models/ci/build_trace_chunks/redis_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb
index 386ee6880cb..11e2d27ff79 100644
--- a/spec/models/ci/build_trace_section_name_spec.rb
+++ b/spec/models/ci/build_trace_section_name_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceSectionName, model: true do
diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb
index 541a9a36fb8..5bd3a953ec0 100644
--- a/spec/models/ci/build_trace_section_spec.rb
+++ b/spec/models/ci/build_trace_section_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceSection, model: true do
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 838fa63cb1f..36c65d92840 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Group do
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index 1b10501701c..406a69f3bbc 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -1,10 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::GroupVariable do
subject { build(:ci_group_variable) }
- it { is_expected.to include_module(HasVariable) }
+ it_behaves_like "CI variable"
+
it { is_expected.to include_module(Presentable) }
+ it { is_expected.to include_module(Maskable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) }
describe '.unprotected' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index c68ba02b8de..1ba66565e03 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::JobArtifact do
@@ -17,6 +19,25 @@ describe Ci::JobArtifact do
it_behaves_like 'having unique enum values'
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:ci_job_artifact, :archive, size: 106365) }
+ end
+
+ describe '.with_reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
+
+ subject { described_class.with_reports }
+
+ it { is_expected.to be_empty }
+
+ context 'when there are reports' do
+ let!(:metrics_report) { create(:ci_job_artifact, :junit) }
+ let!(:codequality_report) { create(:ci_job_artifact, :codequality) }
+
+ it { is_expected.to eq([metrics_report, codequality_report]) }
+ end
+ end
+
describe '.test_reports' do
subject { described_class.test_reports }
@@ -100,12 +121,6 @@ describe Ci::JobArtifact do
it 'sets the size from the file size' do
expect(artifact.size).to eq(106365)
end
-
- it 'updates the project statistics' do
- expect { artifact }
- .to change { project.statistics.reload.build_artifacts_size }
- .by(106365)
- end
end
context 'updating the artifact file' do
@@ -113,12 +128,6 @@ describe Ci::JobArtifact do
artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png'))
expect(artifact.size).to eq(1062)
end
-
- it 'updates the project statistics' do
- expect { artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png')) }
- .to change { artifact.project.statistics.reload.build_artifacts_size }
- .by(1062 - 106365)
- end
end
describe 'validates file format' do
@@ -257,34 +266,6 @@ describe Ci::JobArtifact do
end
end
- context 'when destroying the artifact' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
-
- it 'updates the project statistics' do
- artifact = build.job_artifacts.first
-
- expect(ProjectStatistics)
- .to receive(:increment_statistic)
- .and_call_original
-
- expect { artifact.destroy }
- .to change { project.statistics.reload.build_artifacts_size }
- .by(-106365)
- end
-
- context 'when it is destroyed from the project level' do
- it 'does not update the project statistics' do
- expect(ProjectStatistics)
- .not_to receive(:increment_statistic)
-
- project.update(pending_delete: true)
- project.destroy!
- end
- end
- end
-
describe 'file is being stored' do
subject { create(:ci_job_artifact, :archive) }
diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index 0c33c1466b7..bb812cc0533 100644
--- a/spec/models/ci/legacy_stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::LegacyStage do
@@ -270,4 +272,6 @@ describe Ci::LegacyStage do
def create_job(type, status: 'success', stage: stage_name, **opts)
create(type, pipeline: pipeline, stage: stage, status: status, **opts)
end
+
+ it_behaves_like 'manual playable stage', :ci_stage
end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 8ee15f0e734..227870eb27f 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineSchedule do
+ subject { build(:ci_pipeline_schedule) }
+
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:owner) }
@@ -33,34 +37,127 @@ describe Ci::PipelineSchedule do
expect(pipeline_schedule).not_to be_valid
end
end
+
+ context 'when cron contains trailing whitespaces' do
+ it 'strips the attribute' do
+ pipeline_schedule = build(:ci_pipeline_schedule, cron: ' 0 0 * * * ')
+
+ expect(pipeline_schedule).to be_valid
+ expect(pipeline_schedule.cron).to eq('0 0 * * *')
+ end
+ end
+ end
+
+ describe '.runnable_schedules' do
+ subject { described_class.runnable_schedules }
+
+ let!(:pipeline_schedule) do
+ Timecop.freeze(1.day.ago) do
+ create(:ci_pipeline_schedule, :hourly)
+ end
+ end
+
+ it 'returns the runnable schedule' do
+ is_expected.to eq([pipeline_schedule])
+ end
+
+ context 'when there are no runnable schedules' do
+ let!(:pipeline_schedule) { }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.preloaded' do
+ subject { described_class.preloaded }
+
+ before do
+ create_list(:ci_pipeline_schedule, 3)
+ end
+
+ it 'preloads the associations' do
+ subject
+
+ query = ActiveRecord::QueryRecorder.new { subject.each(&:project) }
+
+ expect(query.count).to eq(2)
+ end
end
describe '#set_next_run_at' do
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
+ let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
+
+ let(:expected_next_run_at) do
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(ideal_next_run_at)
+ end
+
+ let(:cron_worker_next_run_at) do
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(Time.zone.now)
+ end
context 'when creates new pipeline schedule' do
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
- .next_time_from(Time.now)
+ it 'updates next_run_at automatically' do
+ expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
+ end
+ end
+
+ context 'when PipelineScheduleWorker runs at a specific interval' do
+ before do
+ allow(Settings).to receive(:cron_jobs) do
+ {
+ 'pipeline_schedule_worker' => {
+ 'cron' => '0 1 2 3 *'
+ }
+ }
+ end
end
- it 'updates next_run_at automatically' do
- expect(described_class.last.next_run_at).to eq(expected_next_run_at)
+ it "updates next_run_at to the sidekiq worker's execution time" do
+ expect(pipeline_schedule.next_run_at.min).to eq(0)
+ expect(pipeline_schedule.next_run_at.hour).to eq(1)
+ expect(pipeline_schedule.next_run_at.day).to eq(2)
+ expect(pipeline_schedule.next_run_at.month).to eq(3)
end
end
- context 'when updates cron of exsisted pipeline schedule' do
- let(:new_cron) { '0 0 1 1 *' }
+ context 'when pipeline schedule runs every minute' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) }
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(new_cron, pipeline_schedule.cron_timezone)
- .next_time_from(Time.now)
+ it "updates next_run_at to the sidekiq worker's execution time", :quarantine do
+ expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at)
end
+ end
+
+ context 'when there are two different pipeline schedules in different time zones' do
+ let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'Eastern Time (US & Canada)') }
+ let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+
+ it 'sets different next_run_at' do
+ expect(pipeline_schedule_1.next_run_at).not_to eq(pipeline_schedule_2.next_run_at)
+ end
+ end
+
+ context 'when there are two different pipeline schedules in the same time zones' do
+ let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+ let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+
+ it 'sets the sames next_run_at' do
+ expect(pipeline_schedule_1.next_run_at).to eq(pipeline_schedule_2.next_run_at)
+ end
+ end
+
+ context 'when updates cron of exsisted pipeline schedule' do
+ let(:new_cron) { '0 0 1 1 *' }
it 'updates next_run_at automatically' do
pipeline_schedule.update!(cron: new_cron)
- expect(described_class.last.next_run_at).to eq(expected_next_run_at)
+ expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
end
end
end
@@ -70,10 +167,11 @@ describe Ci::PipelineSchedule do
context 'when reschedules after 10 days from now' do
let(:future_time) { 10.days.from_now }
+ let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
- .next_time_from(future_time)
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(ideal_next_run_at)
end
it 'points to proper next_run_at' do
@@ -86,38 +184,6 @@ describe Ci::PipelineSchedule do
end
end
- describe '#real_next_run' do
- subject do
- described_class.last.real_next_run(worker_cron: worker_cron,
- worker_time_zone: worker_time_zone)
- end
-
- context 'when GitLab time_zone is UTC' do
- before do
- allow(Time).to receive(:zone)
- .and_return(ActiveSupport::TimeZone[worker_time_zone])
- end
-
- let(:worker_time_zone) { 'UTC' }
-
- context 'when cron_timezone is Eastern Time (US & Canada)' do
- before do
- create(:ci_pipeline_schedule, :nightly,
- cron_timezone: 'Eastern Time (US & Canada)')
- end
-
- let(:worker_cron) { '0 1 2 3 *' }
-
- it 'returns the next time worker executes' do
- expect(subject.min).to eq(0)
- expect(subject.hour).to eq(1)
- expect(subject.day).to eq(2)
- expect(subject.month).to eq(3)
- end
- end
- end
- end
-
describe '#job_variables' do
let!(:pipeline_schedule) { create(:ci_pipeline_schedule) }
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
index dc8427f28bc..c96a24d5042 100644
--- a/spec/models/ci/pipeline_schedule_variable_spec.rb
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineScheduleVariable do
subject { build(:ci_pipeline_schedule_variable) }
- it { is_expected.to include_module(HasVariable) }
+ it_behaves_like "CI variable"
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index b9567ab4d65..a8701f0efa4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Pipeline, :mailer do
+ include ProjectForksHelper
+
let(:user) { create(:user) }
set(:project) { create(:project) }
@@ -78,11 +82,11 @@ describe Ci::Pipeline, :mailer do
context 'when merge request pipelines exist' do
let!(:merge_request_pipeline_1) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let!(:merge_request_pipeline_2) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -104,11 +108,11 @@ describe Ci::Pipeline, :mailer do
let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
let!(:merge_request_pipeline_1) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let!(:merge_request_pipeline_2) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -128,11 +132,373 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '.merge_request' do
- subject { described_class.merge_request }
+ describe '.for_sha' do
+ subject { described_class.for_sha(sha) }
+
+ let(:sha) { 'abc' }
+ let!(:pipeline) { create(:ci_pipeline, sha: 'abc') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'when argument is array' do
+ let(:sha) { %w[abc def] }
+ let!(:pipeline_2) { create(:ci_pipeline, sha: 'def') }
+
+ it 'returns the pipelines' do
+ is_expected.to contain_exactly(pipeline, pipeline_2)
+ end
+ end
+
+ context 'when sha is empty' do
+ let(:sha) { nil }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.for_source_sha' do
+ subject { described_class.for_source_sha(source_sha) }
+
+ let(:source_sha) { 'abc' }
+ let!(:pipeline) { create(:ci_pipeline, source_sha: 'abc') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'when argument is array' do
+ let(:source_sha) { %w[abc def] }
+ let!(:pipeline_2) { create(:ci_pipeline, source_sha: 'def') }
+
+ it 'returns the pipelines' do
+ is_expected.to contain_exactly(pipeline, pipeline_2)
+ end
+ end
+
+ context 'when source_sha is empty' do
+ let(:source_sha) { nil }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.for_sha_or_source_sha' do
+ subject { described_class.for_sha_or_source_sha(sha) }
+
+ let(:sha) { 'abc' }
+
+ context 'when sha is matched' do
+ let!(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when source sha is matched' do
+ let!(:pipeline) { create(:ci_pipeline, source_sha: sha) }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when both sha and source sha are not matched' do
+ let!(:pipeline) { create(:ci_pipeline, sha: 'bcd', source_sha: 'bcd') }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.detached_merge_request_pipelines' do
+ subject { described_class.detached_merge_request_pipelines(merge_request, sha) }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:sha) { merge_request.diff_head_sha }
+
+ it 'returns detached merge request pipelines' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when sha does not exist' do
+ let(:sha) { 'abc' }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha)
+ end
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#detached_merge_request_pipeline?' do
+ subject { pipeline.detached_merge_request_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { nil }
+
+ it { is_expected.to be_truthy }
+
+ context 'when target sha exists' do
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '.merge_request_pipelines' do
+ subject { described_class.merge_request_pipelines(merge_request, source_sha) }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:source_sha) { merge_request.diff_head_sha }
+
+ it 'returns merge pipelines' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when source sha is empty' do
+ let(:source_sha) { nil }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha)
+ end
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#merge_request_pipeline?' do
+ subject { pipeline.merge_request_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it { is_expected.to be_truthy }
+
+ context 'when target sha is empty' do
+ let(:target_sha) { nil }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#merge_request_ref?' do
+ subject { pipeline.merge_request_ref? }
+
+ it 'calls MergeRequest#merge_request_ref?' do
+ expect(MergeRequest).to receive(:merge_request_ref?).with(pipeline.ref)
+
+ subject
+ end
+ end
+
+ describe '#legacy_detached_merge_request_pipeline?' do
+ subject { pipeline.legacy_detached_merge_request_pipeline? }
+
+ set(:merge_request) { create(:merge_request) }
+ let(:ref) { 'feature' }
+ let(:target_sha) { nil }
+
+ let(:pipeline) do
+ build(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: target_sha)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when pipeline ref is a merge request ref' do
+ let(:ref) { 'refs/merge-requests/1/head' }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when target sha is set' do
+ let(:target_sha) { 'target-sha' }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#matches_sha_or_source_sha?' do
+ subject { pipeline.matches_sha_or_source_sha?(sample_sha) }
+
+ let(:sample_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
+
+ context 'when sha matches' do
+ let(:pipeline) { build(:ci_pipeline, sha: sample_sha) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when source_sha matches' do
+ let(:pipeline) { build(:ci_pipeline, source_sha: sample_sha) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when both sha and source_sha do not matche' do
+ let(:pipeline) { build(:ci_pipeline, sha: 'test', source_sha: 'test') }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#source_ref' do
+ subject { pipeline.source_ref }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'feature') }
+
+ it 'returns source ref' do
+ is_expected.to eq('feature')
+ end
+
+ context 'when the pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path)
+ end
+
+ it 'returns source ref' do
+ is_expected.to eq(merge_request.source_branch)
+ end
+ end
+ end
+
+ describe '#source_ref_slug' do
+ subject { pipeline.source_ref_slug }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'feature') }
+
+ it 'slugifies with the source ref' do
+ expect(Gitlab::Utils).to receive(:slugify).with('feature')
+
+ subject
+ end
+
+ context 'when the pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path)
+ end
+
+ it 'slugifies with the source ref of the merge request' do
+ expect(Gitlab::Utils).to receive(:slugify).with(merge_request.source_branch)
+
+ subject
+ end
+ end
+ end
+
+ describe '.triggered_for_branch' do
+ subject { described_class.triggered_for_branch(ref) }
+
+ let(:project) { create(:project, :repository) }
+ let(:ref) { 'feature' }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref) }
+
+ it 'returns the pipeline' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when sha is not specified' do
+ it 'returns the pipeline' do
+ expect(described_class.triggered_for_branch(ref)).to eq([pipeline])
+ end
+ end
+
+ context 'when pipeline is triggered for tag' do
+ let(:ref) { 'v1.1.0' }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref, tag: true) }
+
+ it 'does not return the pipeline' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is triggered for merge_request' do
+ let!(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: project,
+ source_branch: ref,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+
+ it 'does not return the pipeline' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.with_reports' do
+ subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
+
+ context 'when pipeline has a test report' do
+ let!(:pipeline_with_report) { create(:ci_pipeline, :with_test_reports) }
+
+ it 'selects the pipeline' do
+ is_expected.to eq([pipeline_with_report])
+ end
+ end
+
+ context 'when pipeline does not have metrics reports' do
+ let!(:pipeline_without_report) { create(:ci_empty_pipeline) }
+
+ it 'does not select the pipeline' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.merge_request_event' do
+ subject { described_class.merge_request_event }
context 'when there is a merge request pipeline' do
- let!(:pipeline) { create(:ci_pipeline, source: :merge_request, merge_request: merge_request) }
+ let!(:pipeline) { create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) }
let(:merge_request) { create(:merge_request) }
it 'returns merge request pipeline first' do
@@ -153,7 +519,7 @@ describe Ci::Pipeline, :mailer do
let(:pipeline) { build(:ci_pipeline, source: source, merge_request: merge_request) }
context 'when source is merge request' do
- let(:source) { :merge_request }
+ let(:source) { :merge_request_event }
context 'when merge request is specified' do
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
@@ -372,12 +738,13 @@ describe Ci::Pipeline, :mailer do
CI_PIPELINE_SOURCE
CI_COMMIT_MESSAGE
CI_COMMIT_TITLE
- CI_COMMIT_DESCRIPTION]
+ CI_COMMIT_DESCRIPTION
+ CI_COMMIT_REF_PROTECTED]
end
context 'when source is merge request' do
let(:pipeline) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -385,9 +752,16 @@ describe Ci::Pipeline, :mailer do
source_project: project,
source_branch: 'feature',
target_project: project,
- target_branch: 'master')
+ target_branch: 'master',
+ assignees: assignees,
+ milestone: milestone,
+ labels: labels)
end
+ let(:assignees) { create_list(:user, 2) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:labels) { create_list(:label, 2) }
+
it 'exposes merge request pipeline variables' do
expect(subject.to_hash)
.to include(
@@ -398,10 +772,16 @@ describe Ci::Pipeline, :mailer do
'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s,
'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s)
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s,
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','))
end
context 'when source project does not exist' do
@@ -417,6 +797,30 @@ describe Ci::Pipeline, :mailer do
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME])
end
end
+
+ context 'without assignee' do
+ let(:assignees) { [] }
+
+ it 'does not expose assignee variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ end
+ end
+
+ context 'without milestone' do
+ let(:milestone) { nil }
+
+ it 'does not expose milestone variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ end
+ end
+
+ context 'without labels' do
+ let(:labels) { [] }
+
+ it 'does not expose labels variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ end
+ end
end
end
@@ -886,16 +1290,28 @@ describe Ci::Pipeline, :mailer do
end
describe '#started_at' do
- it 'updates on transitioning to running' do
- build.run
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'updates on transitioning to running' do
+ pipeline.run
- expect(pipeline.reload.started_at).not_to be_nil
+ expect(pipeline.started_at).not_to be_nil
+ end
+ end
end
- it 'does not update on transitioning to success' do
- build.success
+ context 'from created' do
+ let(:from_status) { :created }
+
+ it 'does not update on transitioning to success' do
+ pipeline.succeed
- expect(pipeline.reload.started_at).to be_nil
+ expect(pipeline.started_at).to be_nil
+ end
end
end
@@ -914,27 +1330,49 @@ describe Ci::Pipeline, :mailer do
end
describe 'merge request metrics' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
before do
expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
end
context 'when transitioning to running' do
- it 'schedules metrics workers' do
- pipeline.run
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules metrics workers' do
+ pipeline.run
+ end
+ end
end
end
context 'when transitioning to success' do
+ let(:from_status) { 'created' }
+
it 'schedules metrics workers' do
pipeline.succeed
end
end
end
+ describe 'merge on success' do
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending running].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules pipeline success worker' do
+ expect(PipelineSuccessWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.succeed
+ end
+ end
+ end
+ end
+
describe 'pipeline caching' do
it 'performs ExpirePipelinesCacheWorker' do
expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
@@ -943,6 +1381,40 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe 'auto merge' do
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, :running, project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ before do
+ merge_request.update_head_pipeline
+ end
+
+ %w[succeed! drop! cancel! skip!].each do |action|
+ context "when the pipeline recieved #{action} event" do
+ it 'performs AutoMergeProcessWorker' do
+ expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id)
+
+ pipeline.public_send(action)
+ end
+ end
+ end
+
+ context 'when auto merge is not enabled in the merge request' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'performs AutoMergeProcessWorker' do
+ expect(AutoMergeProcessWorker).not_to receive(:perform_async)
+
+ pipeline.succeed!
+ end
+ end
+ end
+
def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
create(:ci_build, *traits,
name: name,
@@ -967,7 +1439,7 @@ describe Ci::Pipeline, :mailer do
context 'when source is merge request' do
let(:pipeline) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -1017,7 +1489,7 @@ describe Ci::Pipeline, :mailer do
context 'when ref is merge request' do
let(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
merge_request: merge_request)
end
@@ -1087,6 +1559,14 @@ describe Ci::Pipeline, :mailer do
end
end
+ context 'with a branch name as the ref' do
+ it 'looks up commit with the full ref name' do
+ expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original
+
+ expect(pipeline).to be_latest
+ end
+ end
+
context 'with not latest sha' do
before do
pipeline.update(
@@ -1180,7 +1660,7 @@ describe Ci::Pipeline, :mailer do
context 'when source is merge request' do
let(:pipeline) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -1453,6 +1933,18 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.reload.status }
+ context 'on prepare' do
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(build).to receive(:prerequisites).and_return([double])
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+
+ build.enqueue
+ end
+
+ it { is_expected.to eq('preparing') }
+ end
+
context 'on queuing' do
before do
build.enqueue
@@ -2087,7 +2579,7 @@ describe Ci::Pipeline, :mailer do
end
end
- describe "#merge_requests" do
+ describe "#merge_requests_as_head_pipeline" do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
@@ -2095,85 +2587,100 @@ describe Ci::Pipeline, :mailer do
allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
- expect(pipeline.merge_requests).to eq([merge_request])
+ expect(pipeline.merge_requests_as_head_pipeline).to eq([merge_request])
end
it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do
create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')
- expect(pipeline.merge_requests).to be_empty
+ expect(pipeline.merge_requests_as_head_pipeline).to be_empty
end
it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do
create(:merge_request, source_project: project, source_branch: pipeline.ref)
allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' }
- expect(pipeline.merge_requests).to be_empty
+ expect(pipeline.merge_requests_as_head_pipeline).to be_empty
end
end
describe "#all_merge_requests" do
let(:project) { create(:project) }
- let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') }
- it "returns all merge requests having the same source branch" do
- merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
+ shared_examples 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') }
- expect(pipeline.all_merge_requests).to eq([merge_request])
- end
-
- it "doesn't return merge requests having a different source branch" do
- create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')
-
- expect(pipeline.all_merge_requests).to be_empty
- end
+ it "returns all merge requests having the same source branch" do
+ merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref)
- context 'when there is a merge request pipeline' do
- let(:source_branch) { 'feature' }
- let(:target_branch) { 'master' }
-
- let!(:pipeline) do
- create(:ci_pipeline,
- source: :merge_request,
- project: project,
- ref: source_branch,
- merge_request: merge_request)
+ expect(pipeline.all_merge_requests).to eq([merge_request])
end
- let(:merge_request) do
- create(:merge_request,
- source_project: project,
- source_branch: source_branch,
- target_project: project,
- target_branch: target_branch)
- end
+ it "doesn't return merge requests having a different source branch" do
+ create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master')
- it 'returns an associated merge request' do
- expect(pipeline.all_merge_requests).to eq([merge_request])
+ expect(pipeline.all_merge_requests).to be_empty
end
- context 'when there is another merge request pipeline that targets a different branch' do
- let(:target_branch_2) { 'merge-test' }
+ context 'when there is a merge request pipeline' do
+ let(:source_branch) { 'feature' }
+ let(:target_branch) { 'master' }
- let!(:pipeline_2) do
+ let!(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
- project: project,
+ source: :merge_request_event,
+ project: pipeline_project,
ref: source_branch,
- merge_request: merge_request_2)
+ merge_request: merge_request)
end
- let(:merge_request_2) do
+ let(:merge_request) do
create(:merge_request,
- source_project: project,
+ source_project: pipeline_project,
source_branch: source_branch,
target_project: project,
- target_branch: target_branch_2)
+ target_branch: target_branch)
end
- it 'does not return an associated merge request' do
- expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+ it 'returns an associated merge request' do
+ expect(pipeline.all_merge_requests).to eq([merge_request])
end
+
+ context 'when there is another merge request pipeline that targets a different branch' do
+ let(:target_branch_2) { 'merge-test' }
+
+ let!(:pipeline_2) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: pipeline_project,
+ ref: source_branch,
+ merge_request: merge_request_2)
+ end
+
+ let(:merge_request_2) do
+ create(:merge_request,
+ source_project: pipeline_project,
+ source_branch: source_branch,
+ target_project: project,
+ target_branch: target_branch_2)
+ end
+
+ it 'does not return an associated merge request' do
+ expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline_project) { project }
+ end
+
+ context 'for a fork' do
+ let(:fork) { fork_project(project) }
+
+ it_behaves_like 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline_project) { fork }
end
end
end
@@ -2301,18 +2808,19 @@ describe Ci::Pipeline, :mailer do
end
describe '#latest_builds_with_artifacts' do
- let!(:pipeline) { create(:ci_pipeline, :success) }
-
- let!(:build) do
- create(:ci_build, :success, :artifacts, pipeline: pipeline)
- end
+ let!(:fresh_build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+ let!(:stale_build) { create(:ci_build, :success, :expired, :artifacts, pipeline: pipeline) }
it 'returns an Array' do
expect(pipeline.latest_builds_with_artifacts).to be_an_instance_of(Array)
end
- it 'returns the latest builds' do
- expect(pipeline.latest_builds_with_artifacts).to eq([build])
+ it 'returns the latest builds with non-expired artifacts' do
+ expect(pipeline.latest_builds_with_artifacts).to contain_exactly(fresh_build)
+ end
+
+ it 'does not return builds with expired artifacts' do
+ expect(pipeline.latest_builds_with_artifacts).not_to include(stale_build)
end
it 'memoizes the returned relation' do
@@ -2324,8 +2832,8 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '#has_test_reports?' do
- subject { pipeline.has_test_reports? }
+ describe '#has_reports?' do
+ subject { pipeline.has_reports?(Ci::JobArtifact.test_reports) }
context 'when pipeline has builds with test reports' do
before do
@@ -2468,4 +2976,36 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ describe '#find_stage_by_name' do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:stage_name) { 'test' }
+
+ let(:stage) do
+ create(:ci_stage_entity,
+ pipeline: pipeline,
+ project: pipeline.project,
+ name: 'test')
+ end
+
+ before do
+ create_list(:ci_build, 2, pipeline: pipeline, stage: stage.name)
+ end
+
+ subject { pipeline.find_stage_by_name!(stage_name) }
+
+ context 'when stage exists' do
+ it { is_expected.to eq(stage) }
+ end
+
+ context 'when stage does not exist' do
+ let(:stage_name) { 'build' }
+
+ it 'raises an ActiveRecord exception' do
+ expect do
+ subject
+ end.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index 03d09cb31d6..e8c7ce088e2 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineVariable do
subject { build(:ci_pipeline_variable) }
- it { is_expected.to include_module(HasVariable) }
+ it_behaves_like "CI variable"
+
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) }
describe '#hook_attrs' do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index eb2daed7f32..f735a89f69f 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Runner do
@@ -70,10 +72,9 @@ describe Ci::Runner do
expect(instance_runner.errors.full_messages).to include('Runner cannot have projects assigned')
end
- it 'should fail to save a group assigned to a project runner even if the runner is already saved' do
- group_runner
-
- expect { create(:group, runners: [project_runner]) }
+ it 'fails to save a group assigned to a project runner even if the runner is already saved' do
+ group.runners << project_runner
+ expect { group.save! }
.to raise_error(ActiveRecord::RecordInvalid)
end
end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 3228c400155..85cd32fb03a 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Stage, :models do
@@ -283,4 +285,6 @@ describe Ci::Stage, :models do
end
end
end
+
+ it_behaves_like 'manual playable stage', :ci_stage_entity
end
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
index 7dcf3528f73..d04349bec92 100644
--- a/spec/models/ci/trigger_request_spec.rb
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::TriggerRequest do
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index d4b72205203..fde8375f2a5 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Trigger do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 875e8b2b682..a231c7eaed8 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,11 +1,15 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Variable do
subject { build(:ci_variable) }
+ it_behaves_like "CI variable"
+
describe 'validations' do
- it { is_expected.to include_module(HasVariable) }
it { is_expected.to include_module(Presentable) }
+ it { is_expected.to include_module(Maskable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index cf5cbf8ec5c..8d853a04e33 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::CertManager do
@@ -8,13 +10,39 @@ describe Clusters::Applications::CertManager do
include_examples 'cluster application version specs', :clusters_applications_cert_managers
include_examples 'cluster application initial status specs'
+ describe '#can_uninstall?' do
+ subject { cert_manager.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#install_command' do
- let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } }
+ let(:cert_email) { 'admin@example.com' }
+
+ let(:cluster_issuer_file) do
+ file_contents = <<~EOF
+ ---
+ apiVersion: certmanager.k8s.io/v1alpha1
+ kind: ClusterIssuer
+ metadata:
+ name: letsencrypt-prod
+ spec:
+ acme:
+ server: https://acme-v02.api.letsencrypt.org/directory
+ email: #{cert_email}
+ privateKeySecretRef:
+ name: letsencrypt-prod
+ http01: {}
+ EOF
+
+ { "cluster_issuer.yaml": file_contents }
+ end
+
subject { cert_manager.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
- it 'should be initialized with cert_manager arguments' do
+ it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
expect(subject.chart).to eq('stable/cert-manager')
expect(subject.version).to eq('v0.5.2')
@@ -24,12 +52,13 @@ describe Clusters::Applications::CertManager do
end
context 'for a specific user' do
+ let(:cert_email) { 'abc@xyz.com' }
+
before do
- cert_manager.email = 'abc@xyz.com'
- cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com'
+ cert_manager.email = cert_email
end
- it 'should use his/her email to register issuer with certificate provider' do
+ it 'uses his/her email to register issuer with certificate provider' do
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
end
end
@@ -45,7 +74,7 @@ describe Clusters::Applications::CertManager do
context 'application failed to install previously' do
let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') }
- it 'should be initialized with the locked version' do
+ it 'is initialized with the locked version' do
expect(subject.version).to eq('v0.5.2')
end
end
@@ -57,7 +86,7 @@ describe Clusters::Applications::CertManager do
subject { application.files }
- it 'should include cert_manager specific keys in the values.yaml file' do
+ it 'includes cert_manager specific keys in the values.yaml file' do
expect(values).to include('ingressShim')
end
end
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index f16eff92167..6ea6c110d62 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Helm do
@@ -16,6 +18,14 @@ describe Clusters::Applications::Helm do
it { is_expected.to contain_exactly(installed_cluster, updated_cluster) }
end
+ describe '#can_uninstall?' do
+ let(:helm) { create(:clusters_applications_helm) }
+
+ subject { helm.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#issue_client_cert' do
let(:application) { create(:clusters_applications_helm) }
subject { application.issue_client_cert }
@@ -34,11 +44,11 @@ describe Clusters::Applications::Helm do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) }
- it 'should be initialized with 1 arguments' do
+ it 'is initialized with 1 arguments' do
expect(subject.name).to eq('helm')
end
- it 'should have cert files' do
+ it 'has cert files' do
expect(subject.files[:'ca.pem']).to be_present
expect(subject.files[:'ca.pem']).to eq(helm.ca_cert)
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index d5fd42509a3..292ddabd2d8 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Ingress do
@@ -16,6 +18,12 @@ describe Clusters::Applications::Ingress do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
end
+ describe '#can_uninstall?' do
+ subject { ingress.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#make_installed!' do
before do
application.make_installed!
@@ -56,6 +64,14 @@ describe Clusters::Applications::Ingress do
expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
end
end
+
+ context 'when there is already an external_hostname' do
+ let(:application) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
+ end
+ end
end
describe '#install_command' do
@@ -63,7 +79,7 @@ describe Clusters::Applications::Ingress do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
- it 'should be initialized with ingress arguments' do
+ it 'is initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
expect(subject.chart).to eq('stable/nginx-ingress')
expect(subject.version).to eq('1.1.2')
@@ -82,7 +98,7 @@ describe Clusters::Applications::Ingress do
context 'application failed to install previously' do
let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') }
- it 'should be initialized with the locked version' do
+ it 'is initialized with the locked version' do
expect(subject.version).to eq('1.1.2')
end
end
@@ -94,7 +110,7 @@ describe Clusters::Applications::Ingress do
subject { application.files }
- it 'should include ingress valid keys in values' do
+ it 'includes ingress valid keys in values' do
expect(values).to include('image')
expect(values).to include('repository')
expect(values).to include('stats')
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 2c22c24c498..43fa1010b2b 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Jupyter do
@@ -8,6 +10,15 @@ describe Clusters::Applications::Jupyter do
it { is_expected.to belong_to(:oauth_application) }
+ describe '#can_uninstall?' do
+ let(:ingress) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') }
+ let(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) }
+
+ subject { jupyter.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#set_initial_status' do
before do
jupyter.set_initial_status
@@ -26,6 +37,13 @@ describe Clusters::Applications::Jupyter do
it { expect(jupyter).to be_installable }
end
+
+ context 'when ingress is installed and external_hostname is assigned' do
+ let(:ingress) { create(:clusters_applications_ingress, :installed, external_hostname: 'localhost.localdomain') }
+ let(:jupyter) { create(:clusters_applications_jupyter, cluster: ingress.cluster) }
+
+ it { expect(jupyter).to be_installable }
+ end
end
describe '#install_command' do
@@ -36,10 +54,10 @@ describe Clusters::Applications::Jupyter do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
- it 'should be initialized with 4 arguments' do
+ it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub')
- expect(subject.version).to eq('v0.6')
+ expect(subject.version).to eq('0.9-174bbd5')
expect(subject).to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files)
@@ -56,8 +74,8 @@ describe Clusters::Applications::Jupyter do
context 'application failed to install previously' do
let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') }
- it 'should be initialized with the locked version' do
- expect(subject.version).to eq('v0.6')
+ it 'is initialized with the locked version' do
+ expect(subject.version).to eq('0.9-174bbd5')
end
end
end
@@ -68,7 +86,7 @@ describe Clusters::Applications::Jupyter do
subject { application.files }
- it 'should include valid values' do
+ it 'includes valid values' do
expect(values).to include('ingress')
expect(values).to include('hub')
expect(values).to include('rbac')
@@ -77,6 +95,9 @@ describe Clusters::Applications::Jupyter do
expect(values).to include('singleuser')
expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
+ expect(values).to include("gitlabProjectIdWhitelist:\n - #{application.cluster.project.id}")
+ expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']")
+ expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/)
end
context 'when cluster belongs to a project' do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 006b922ab27..b38cf96de7e 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -1,9 +1,8 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Knative do
- include KubernetesHelpers
- include ReactiveCachingHelpers
-
let(:knative) { create(:clusters_applications_knative) }
include_examples 'cluster application core specs', :clusters_applications_knative
@@ -37,6 +36,12 @@ describe Clusters::Applications::Knative do
end
end
+ describe '#can_uninstall?' do
+ subject { knative.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#schedule_status_update with external_ip' do
let(:application) { create(:clusters_applications_knative, :installed) }
@@ -64,23 +69,28 @@ describe Clusters::Applications::Knative do
expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
end
end
- end
- describe '#install_command' do
- subject { knative.install_command }
+ context 'when there is already an external_hostname' do
+ let(:application) { create(:clusters_applications_knative, :installed, external_hostname: 'localhost.localdomain') }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
+ end
+ end
+ end
- it 'should be an instance of Helm::InstallCommand' do
+ shared_examples 'a command' do
+ it 'is an instance of Helm::InstallCommand' do
expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand)
end
- it 'should be initialized with knative arguments' do
+ it 'is initialized with knative arguments' do
expect(subject.name).to eq('knative')
expect(subject.chart).to eq('knative/knative')
- expect(subject.version).to eq('0.2.2')
expect(subject.files).to eq(knative.files)
end
- it 'should not install metrics for prometheus' do
+ it 'does not install metrics for prometheus' do
expect(subject.postinstall).to be_nil
end
@@ -90,7 +100,7 @@ describe Clusters::Applications::Knative do
subject { knative.install_command }
- it 'should install metrics' do
+ it 'installs metrics' do
expect(subject.postinstall).not_to be_nil
expect(subject.postinstall.length).to be(1)
expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
@@ -98,91 +108,39 @@ describe Clusters::Applications::Knative do
end
end
- describe '#files' do
- let(:application) { knative }
- let(:values) { subject[:'values.yaml'] }
-
- subject { application.files }
+ describe '#install_command' do
+ subject { knative.install_command }
- it 'should include knative specific keys in the values.yaml file' do
- expect(values).to include('domain')
+ it 'is initialized with latest version' do
+ expect(subject.version).to eq('0.5.0')
end
- end
- describe 'validations' do
- it { is_expected.to validate_presence_of(:hostname) }
+ it_behaves_like 'a command'
end
- describe '#service_pod_details' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, cluster: cluster) }
-
- let(:namespace) do
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
+ describe '#update_command' do
+ let!(:current_installed_version) { knative.version = '0.1.0' }
+ subject { knative.update_command }
- before do
- stub_kubeclient_discover(service.api_url)
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
- stub_reactive_cache(knative,
- {
- services: kube_response(kube_knative_services_body),
- pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace))
- })
- synchronous_reactive_cache(knative)
+ it 'is initialized with current version' do
+ expect(subject.version).to eq(current_installed_version)
end
- it 'should be able k8s core for pod details' do
- expect(knative.service_pod_details(namespace.namespace, cluster.cluster_project.project.name)).not_to be_nil
- end
+ it_behaves_like 'a command'
end
- describe '#services' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, cluster: cluster) }
-
- let(:namespace) do
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
-
- subject { knative.services }
+ describe '#files' do
+ let(:application) { knative }
+ let(:values) { subject[:'values.yaml'] }
- before do
- stub_kubeclient_discover(service.api_url)
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
- end
+ subject { application.files }
- it 'should have an unintialized cache' do
- is_expected.to be_nil
+ it 'includes knative specific keys in the values.yaml file' do
+ expect(values).to include('domain')
end
+ end
- context 'when using synchronous reactive cache' do
- before do
- stub_reactive_cache(knative,
- {
- services: kube_response(kube_knative_services_body),
- pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace))
- })
- synchronous_reactive_cache(knative)
- end
-
- it 'should have cached services' do
- is_expected.not_to be_nil
- end
-
- it 'should match our namespace' do
- expect(knative.services_for(ns: namespace)).not_to be_nil
- end
- end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:hostname) }
end
end
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 81708b0c2ed..26267c64112 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Prometheus do
@@ -9,6 +11,21 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application helm specs', :clusters_applications_prometheus
include_examples 'cluster application initial status specs'
+ describe 'after_destroy' do
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
+ let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+ let!(:prometheus_service) { project.create_prometheus_service(active: true) }
+
+ it 'deactivates prometheus_service after destroy' do
+ expect do
+ application.destroy!
+
+ prometheus_service.reload
+ end.to change(prometheus_service, :active).from(true).to(false)
+ end
+ end
+
describe 'transition to installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
@@ -21,12 +38,20 @@ describe Clusters::Applications::Prometheus do
end
it 'ensures Prometheus service is activated' do
- expect(prometheus_service).to receive(:update).with(active: true)
+ expect(prometheus_service).to receive(:update!).with(active: true)
subject.make_installed
end
end
+ describe '#can_uninstall?' do
+ let(:prometheus) { create(:clusters_applications_prometheus) }
+
+ subject { prometheus.can_uninstall? }
+
+ it { is_expected.to be_truthy }
+ end
+
describe '#prometheus_client' do
context 'cluster is nil' do
it 'returns nil' do
@@ -92,7 +117,7 @@ describe Clusters::Applications::Prometheus do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
- it 'should be initialized with 3 arguments' do
+ it 'is initialized with 3 arguments' do
expect(subject.name).to eq('prometheus')
expect(subject.chart).to eq('stable/prometheus')
expect(subject.version).to eq('6.7.3')
@@ -111,12 +136,12 @@ describe Clusters::Applications::Prometheus do
context 'application failed to install previously' do
let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') }
- it 'should be initialized with the locked version' do
+ it 'is initialized with the locked version' do
expect(subject.version).to eq('6.7.3')
end
end
- it 'should not install knative metrics' do
+ it 'does not install knative metrics' do
expect(subject.postinstall).to be_nil
end
@@ -126,12 +151,40 @@ describe Clusters::Applications::Prometheus do
subject { prometheus.install_command }
- it 'should install knative metrics' do
+ it 'installs knative metrics' do
expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
end
end
end
+ describe '#uninstall_command' do
+ let(:prometheus) { create(:clusters_applications_prometheus) }
+
+ subject { prometheus.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it 'has the application name' do
+ expect(subject.name).to eq('prometheus')
+ end
+
+ it 'has files' do
+ expect(subject.files).to eq(prometheus.files)
+ end
+
+ it 'is rbac' do
+ expect(subject).to be_rbac
+ end
+
+ context 'on a non rbac enabled cluster' do
+ before do
+ prometheus.cluster.platform_kubernetes.abac!
+ end
+
+ it { is_expected.not_to be_rbac }
+ end
+ end
+
describe '#upgrade_command' do
let(:prometheus) { build(:clusters_applications_prometheus) }
let(:values) { prometheus.values }
@@ -140,7 +193,7 @@ describe Clusters::Applications::Prometheus do
expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::InstallCommand)
end
- it 'should be initialized with 3 arguments' do
+ it 'is initialized with 3 arguments' do
command = prometheus.upgrade_command(values)
expect(command.name).to eq('prometheus')
@@ -178,7 +231,7 @@ describe Clusters::Applications::Prometheus do
subject { application.files }
- it 'should include prometheus valid values' do
+ it 'includes prometheus valid values' do
expect(values).to include('alertmanager')
expect(values).to include('kubeStateMetrics')
expect(values).to include('nodeExporter')
@@ -202,7 +255,7 @@ describe Clusters::Applications::Prometheus do
expect(subject[:'values.yaml']).to eq({ hello: :world })
end
- it 'should include cert files' do
+ it 'includes cert files' do
expect(subject[:'ca.pem']).to be_present
expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
@@ -218,7 +271,7 @@ describe Clusters::Applications::Prometheus do
application.cluster.application_helm.ca_cert = nil
end
- it 'should not include cert files' do
+ it 'does not include cert files' do
expect(subject[:'ca.pem']).not_to be_present
expect(subject[:'cert.pem']).not_to be_present
expect(subject[:'key.pem']).not_to be_present
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 6972fc03415..4f0cd0efe9c 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Runner do
@@ -11,6 +13,14 @@ describe Clusters::Applications::Runner do
it { is_expected.to belong_to(:runner) }
+ describe '#can_uninstall?' do
+ let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
+
+ subject { gitlab_runner.can_uninstall? }
+
+ it { is_expected.to be_falsey }
+ end
+
describe '#install_command' do
let(:kubeclient) { double('kubernetes client') }
let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
@@ -19,10 +29,10 @@ describe Clusters::Applications::Runner do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
- it 'should be initialized with 4 arguments' do
+ it 'is initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.2.0')
+ expect(subject.version).to eq(Clusters::Applications::Runner::VERSION)
expect(subject).to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -39,8 +49,8 @@ describe Clusters::Applications::Runner do
context 'application failed to install previously' do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
- it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.2.0')
+ it 'is initialized with the locked version' do
+ expect(subject.version).to eq(Clusters::Applications::Runner::VERSION)
end
end
end
@@ -51,7 +61,7 @@ describe Clusters::Applications::Runner do
subject { application.files }
- it 'should include runner valid values' do
+ it 'includes runner valid values' do
expect(values).to include('concurrent')
expect(values).to include('checkInterval')
expect(values).to include('rbac')
@@ -59,29 +69,62 @@ describe Clusters::Applications::Runner do
expect(values).to include('privileged: true')
expect(values).to include('image: ubuntu:16.04')
expect(values).to include('resources')
- expect(values).to match(/runnerToken: '?#{ci_runner.token}/)
- expect(values).to match(/gitlabUrl: '?#{Gitlab::Routing.url_helpers.root_url}/)
+ expect(values).to match(/runnerToken: '?#{Regexp.escape(ci_runner.token)}/)
+ expect(values).to match(/gitlabUrl: '?#{Regexp.escape(Gitlab::Routing.url_helpers.root_url)}/)
end
context 'without a runner' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) }
+ let(:runner) { application.runner }
+
+ shared_examples 'runner creation' do
+ it 'creates a runner' do
+ expect { subject }.to change { Ci::Runner.count }.by(1)
+ end
+
+ it 'uses the new runner token' do
+ expect(values).to match(/runnerToken: '?#{Regexp.escape(runner.token)}/)
+ end
+ end
+
+ context 'project cluster' do
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
- it 'creates a runner' do
- expect do
+ include_examples 'runner creation'
+
+ it 'creates a project runner' do
subject
- end.to change { Ci::Runner.count }.by(1)
+
+ expect(runner).to be_project_type
+ expect(runner.projects).to eq [project]
+ end
end
- it 'uses the new runner token' do
- expect(values).to match(/runnerToken: '?#{application.reload.runner.token}/)
+ context 'group cluster' do
+ let(:group) { create(:group) }
+ let(:cluster) { create(:cluster, :with_installed_helm, cluster_type: :group_type, groups: [group]) }
+
+ include_examples 'runner creation'
+
+ it 'creates a group runner' do
+ subject
+
+ expect(runner).to be_group_type
+ expect(runner.groups).to eq [group]
+ end
end
- it 'assigns the new runner to runner' do
- subject
+ context 'instance cluster' do
+ let(:cluster) { create(:cluster, :with_installed_helm, :instance) }
+
+ include_examples 'runner creation'
+
+ it 'creates an instance runner' do
+ subject
- expect(application.reload.runner).to be_project_type
+ expect(runner).to be_instance_type
+ end
end
end
@@ -108,7 +151,7 @@ describe Clusters::Applications::Runner do
allow(application).to receive(:chart_values).and_return(stub_values)
end
- it 'should overwrite values.yaml' do
+ it 'overwrites values.yaml' do
expect(values).to match(/privileged: '?#{application.privileged}/)
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 3feed4e9718..f206bb41f45 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -2,9 +2,14 @@
require 'spec_helper'
-describe Clusters::Cluster do
+describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
+ include ReactiveCachingHelpers
+ include KubernetesHelpers
+
it_behaves_like 'having unique enum values'
+ subject { build(:cluster) }
+
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:cluster_projects) }
it { is_expected.to have_many(:projects) }
@@ -17,12 +22,10 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to have_one(:application_runner) }
it { is_expected.to have_many(:kubernetes_namespaces) }
- it { is_expected.to have_one(:kubernetes_namespace) }
it { is_expected.to have_one(:cluster_project) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
- it { is_expected.to delegate_method(:status_name).to(:provider) }
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
@@ -31,9 +34,15 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
it { is_expected.to delegate_method(:external_ip).to(:application_ingress).with_prefix }
+ it { is_expected.to delegate_method(:external_hostname).to(:application_ingress).with_prefix }
it { is_expected.to respond_to :project }
+ it do
+ expect(subject.knative_services_finder(subject.project))
+ .to be_instance_of(Clusters::KnativeServicesFinder)
+ end
+
describe '.enabled' do
subject { described_class.enabled }
@@ -94,6 +103,24 @@ describe Clusters::Cluster do
it { is_expected.to contain_exactly(cluster) }
end
+ describe '.managed' do
+ subject do
+ described_class.managed
+ end
+
+ context 'cluster is not managed' do
+ let!(:cluster) { create(:cluster, :not_managed) }
+
+ it { is_expected.not_to include(cluster) }
+ end
+
+ context 'cluster is managed' do
+ let!(:cluster) { create(:cluster) }
+
+ it { is_expected.to include(cluster) }
+ end
+ end
+
describe '.missing_kubernetes_namespace' do
let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
let(:project) { cluster.project }
@@ -268,7 +295,7 @@ describe Clusters::Cluster do
context 'when cluster is not a valid hostname' do
let(:cluster) { build(:cluster, domain: 'http://not.a.valid.hostname') }
- it 'should add an error on domain' do
+ it 'adds an error on domain' do
expect(subject).not_to be_valid
expect(subject.errors[:domain].first).to eq('contains invalid characters (valid characters: [a-z0-9\\-])')
end
@@ -306,6 +333,15 @@ describe Clusters::Cluster do
end
end
+ context 'when group and instance have configured kubernetes clusters' do
+ let(:project) { create(:project, group: group) }
+ let!(:instance_cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ it 'returns clusters in order, descending the hierachy' do
+ is_expected.to eq([group_cluster, instance_cluster])
+ end
+ end
+
context 'when sub-group has configured kubernetes cluster', :nested_groups do
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:sub_group) { sub_group_cluster.group }
@@ -472,28 +508,6 @@ describe Clusters::Cluster do
end
end
- describe '#created?' do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
-
- subject { cluster.created? }
-
- context 'when status_name is :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when status_name is not :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#allow_user_defined_namespace?' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
@@ -528,62 +542,15 @@ describe Clusters::Cluster do
end
context 'with no domain on cluster' do
- context 'with a project cluster' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.project }
-
- context 'with domain set at instance level' do
- before do
- stub_application_setting(auto_devops_domain: 'global_domain.com')
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
- it { is_expected.to eq('global_domain.com') }
- end
- end
-
- context 'with domain set on ProjectAutoDevops' do
- before do
- auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com')
- auto_devops.save
- end
-
- it { is_expected.to eq('legacy-ado-domain.com') }
- end
-
- context 'with domain set as environment variable on project' do
- before do
- variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('project-ado-domain.com') }
+ context 'with domain set at instance level' do
+ before do
+ stub_application_setting(auto_devops_domain: 'global_domain.com')
end
- context 'with domain set as environment variable on the group project' do
- let(:group) { create(:group) }
-
- before do
- project.update(parent_id: group.id)
- variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('group-ado-domain.com') }
- end
- end
-
- context 'with a group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
-
- context 'with domain set as environment variable for the group' do
- let(:group) { cluster.group }
-
- before do
- variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('group-ado-domain.com') }
- end
+ it { is_expected.to eq('global_domain.com') }
end
end
end
@@ -598,7 +565,7 @@ describe Clusters::Cluster do
stub_application_setting(auto_devops_domain: 'global_domain.com')
end
- it 'should include KUBE_INGRESS_BASE_DOMAIN' do
+ it 'includes KUBE_INGRESS_BASE_DOMAIN' do
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com')
end
end
@@ -606,7 +573,7 @@ describe Clusters::Cluster do
context 'with a cluster domain' do
let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') }
- it 'should include KUBE_INGRESS_BASE_DOMAIN' do
+ it 'includes KUBE_INGRESS_BASE_DOMAIN' do
expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com')
end
end
@@ -614,9 +581,160 @@ describe Clusters::Cluster do
context 'with no domain' do
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
- it 'should return an empty array' do
+ it 'returns an empty array' do
expect(subject.to_hash).to be_empty
end
end
end
+
+ describe '#provided_by_user?' do
+ subject { cluster.provided_by_user? }
+
+ context 'with a GCP provider' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with an user provider' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#status_name' do
+ subject { cluster.status_name }
+
+ context 'the cluster has a provider' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ cluster.provider.make_errored!
+ end
+
+ it { is_expected.to eq :errored }
+ end
+
+ context 'there is a cached connection status' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(:connected)
+ end
+
+ it { is_expected.to eq :connected }
+ end
+
+ context 'there is no connection status in the cache' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(nil)
+ end
+
+ it { is_expected.to eq :created }
+ end
+ end
+
+ describe '#connection_status' do
+ let(:cluster) { create(:cluster) }
+ let(:status) { :connected }
+
+ subject { cluster.connection_status }
+
+ it { is_expected.to be_nil }
+
+ context 'with a cached status' do
+ before do
+ stub_reactive_cache(cluster, connection_status: status)
+ end
+
+ it { is_expected.to eq(status) }
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ subject { cluster.calculate_reactive_cache }
+
+ context 'cluster is disabled' do
+ let(:cluster) { create(:cluster, :disabled) }
+
+ it 'does not populate the cache' do
+ expect(cluster).not_to receive(:retrieve_connection_status)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'cluster is enabled' do
+ let(:cluster) { create(:cluster, :provided_by_user, :group) }
+
+ context 'connection to the cluster is successful' do
+ before do
+ stub_kubeclient_discover(cluster.platform.api_url)
+ end
+
+ it { is_expected.to eq(connection_status: :connected) }
+ end
+
+ context 'cluster cannot be reached' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(SocketError)
+ end
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'cluster cannot be authenticated to' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+ end
+
+ describe 'Kubeclient::HttpError' do
+ let(:error_code) { 403 }
+ let(:error_message) { "Forbidden" }
+
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+
+ context 'generic timeout' do
+ let(:error_message) { 'Timed out connecting to server'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'gateway timeout' do
+ let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+ end
+
+ context 'an uncategorised error is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(StandardError)
+ end
+
+ it { is_expected.to eq(connection_status: :unknown_failure) }
+
+ it 'notifies Sentry' do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
+ .with(instance_of(StandardError), hash_including(extra: { cluster_id: cluster.id }))
+
+ subject
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index b865909c7fd..b5cba80b806 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
context 'when platform has a namespace assigned' do
let(:namespace) { 'platform-namespace' }
- it 'should copy the namespace' do
+ it 'copies the namespace' do
subject
expect(kubernetes_namespace.namespace).to eq('platform-namespace')
@@ -72,7 +72,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
let(:namespace) { nil }
let(:project_slug) { "#{project.path}-#{project.id}" }
- it 'should fallback to project namespace' do
+ it 'fallbacks to project namespace' do
subject
expect(kubernetes_namespace.namespace).to eq(project_slug)
@@ -83,7 +83,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
describe '#service_account_name' do
let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
- it 'should set a service account name based on namespace' do
+ it 'sets a service account name based on namespace' do
subject
expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
@@ -115,7 +115,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
expect(kubernetes_namespace.predefined_variables).to include(
{ key: 'KUBE_SERVICE_ACCOUNT', value: kubernetes_namespace.service_account_name, public: true },
{ key: 'KUBE_NAMESPACE', value: kubernetes_namespace.namespace, public: true },
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false },
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }
)
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 4068d98d8f7..c485850c16e 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do
@@ -13,10 +15,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to validate_presence_of(:api_url) }
it { is_expected.to validate_presence_of(:token) }
- it { is_expected.to delegate_method(:project).to(:cluster) }
it { is_expected.to delegate_method(:enabled?).to(:cluster) }
- it { is_expected.to delegate_method(:managed?).to(:cluster) }
- it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
+ it { is_expected.to delegate_method(:provided_by_user?).to(:cluster) }
it_behaves_like 'having unique enum values'
@@ -98,6 +98,22 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { expect(kubernetes.save).to be_truthy }
end
+
+ context 'when api_url is localhost' do
+ let(:api_url) { 'http://localhost:22' }
+
+ it { expect(kubernetes.save).to be_falsey }
+
+ context 'Application settings allows local requests' do
+ before do
+ allow(ApplicationSetting)
+ .to receive(:current)
+ .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
+ end
+
+ it { expect(kubernetes.save).to be_truthy }
+ end
+ end
end
context 'when validates token' do
@@ -191,7 +207,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_truthy }
end
- describe '#actual_namespace' do
+ describe '#kubernetes_namespace_for' do
let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
@@ -201,7 +217,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
namespace: namespace)
end
- subject { platform.actual_namespace }
+ subject { platform.kubernetes_namespace_for(project) }
context 'with a namespace assigned' do
let(:namespace) { 'namespace-123' }
@@ -253,7 +269,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it 'sets KUBE_TOKEN' do
expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
)
end
end
@@ -265,7 +281,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it 'sets KUBE_TOKEN' do
expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
)
end
end
@@ -281,19 +297,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it 'sets KUBE_TOKEN' do
expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
)
end
end
context 'no namespace provided' do
- let(:namespace) { kubernetes.actual_namespace }
-
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
)
end
end
@@ -313,6 +327,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
{ key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
)
end
+
+ context 'the cluster is not managed' do
+ let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) }
+
+ it_behaves_like 'setting variables'
+
+ it 'sets KUBE_TOKEN' do
+ expect(subject).to include(
+ { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
+ )
+ end
+ end
end
context 'kubernetes namespace exists for the project' do
@@ -322,7 +348,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it 'sets KUBE_TOKEN' do
expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
)
end
end
@@ -359,14 +385,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
- let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
+ let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
@@ -389,6 +415,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
let(:enabled) { true }
+ let(:namespace) { cluster.kubernetes_namespace_for(cluster.project) }
context 'when cluster is disabled' do
let(:enabled) { false }
@@ -398,8 +425,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with valid pods and deployments' do
before do
- stub_kubeclient_pods
- stub_kubeclient_deployments
+ stub_kubeclient_pods(namespace)
+ stub_kubeclient_deployments(namespace)
end
it { is_expected.to include(pods: [kube_pod]) }
@@ -407,8 +434,8 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with 500s' do
before do
- stub_kubeclient_pods(status: 500)
- stub_kubeclient_deployments(status: 500)
+ stub_kubeclient_pods(namespace, status: 500)
+ stub_kubeclient_deployments(namespace, status: 500)
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
@@ -416,12 +443,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with 404s' do
before do
- stub_kubeclient_pods(status: 404)
- stub_kubeclient_deployments(status: 404)
+ stub_kubeclient_pods(namespace, status: 404)
+ stub_kubeclient_deployments(namespace, status: 404)
end
it { is_expected.to include(pods: []) }
end
+
+ context 'when the cluster is not project level' do
+ let(:cluster) { create(:cluster, :group, platform_kubernetes: service) }
+
+ it { is_expected.to include(pods: []) }
+ end
end
describe '#update_kubernetes_namespace' do
@@ -429,7 +462,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
let(:platform) { cluster.platform }
context 'when namespace is updated' do
- it 'should call ConfigureWorker' do
+ it 'calls ConfigureWorker' do
expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once
platform.namespace = 'new-namespace'
@@ -438,7 +471,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
context 'when namespace is not updated' do
- it 'should not call ConfigureWorker' do
+ it 'does not call ConfigureWorker' do
expect(ClusterConfigureWorker).not_to receive(:perform_async)
platform.username = "new-username"
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 82ef5a23c18..671af085d10 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -1,8 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:kubernetes_namespaces) }
- it { is_expected.to have_one(:kubernetes_namespace) }
end
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index 5012e6f15c6..785db4febe0 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Providers::Gcp do
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index 0f5d03ff458..0bdf83fa90f 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitCollection do
@@ -12,37 +14,117 @@ describe CommitCollection do
end
end
- describe '.authors' do
+ describe '.committers' do
it 'returns a relation of users when users are found' do
- user = create(:user, email: commit.author_email.upcase)
+ user = create(:user, email: commit.committer_email.upcase)
collection = described_class.new(project, [commit])
- expect(collection.authors).to contain_exactly(user)
+ expect(collection.committers).to contain_exactly(user)
end
- it 'returns empty array when authors cannot be found' do
+ it 'returns empty array when committers cannot be found' do
collection = described_class.new(project, [commit])
- expect(collection.authors).to be_empty
+ expect(collection.committers).to be_empty
end
it 'excludes authors of merge commits' do
commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
- create(:user, email: commit.author_email.upcase)
+ create(:user, email: commit.committer_email.upcase)
collection = described_class.new(project, [commit])
- expect(collection.authors).to be_empty
+ expect(collection.committers).to be_empty
end
end
describe '#without_merge_commits' do
it 'returns all commits except merge commits' do
+ merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
+ expect(merge_commit).to receive(:merge_commit?).and_return(true)
+
collection = described_class.new(project, [
- build(:commit),
- build(:commit, :merge_commit)
+ commit,
+ merge_commit
])
- expect(collection.without_merge_commits.size).to eq(1)
+ expect(collection.without_merge_commits).to contain_exactly(commit)
+ end
+ end
+
+ describe 'enrichment methods' do
+ let(:gitaly_commit) { commit }
+ let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) }
+
+ describe '#unenriched' do
+ it 'returns all commits that are not backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, hash_commit])
+
+ expect(collection.unenriched).to contain_exactly(hash_commit)
+ end
+ end
+
+ describe '#fully_enriched?' do
+ it 'returns true when all commits are backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, gitaly_commit])
+
+ expect(collection.fully_enriched?).to eq(true)
+ end
+
+ it 'returns false when any commits are not backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, hash_commit])
+
+ expect(collection.fully_enriched?).to eq(false)
+ end
+
+ it 'returns true when the collection is empty' do
+ collection = described_class.new(project, [])
+
+ expect(collection.fully_enriched?).to eq(true)
+ end
+ end
+
+ describe '#enrich!' do
+ it 'replaces commits in the collection with those backed by gitaly data' do
+ collection = described_class.new(project, [hash_commit])
+
+ collection.enrich!
+
+ new_commit = collection.commits.first
+ expect(new_commit.id).to eq(hash_commit.id)
+ expect(hash_commit.gitaly_commit?).to eq(false)
+ expect(new_commit.gitaly_commit?).to eq(true)
+ end
+
+ it 'maintains the original order of the commits' do
+ gitaly_commits = [gitaly_commit] * 3
+ hash_commits = [hash_commit] * 3
+ # Interleave the gitaly and hash commits together
+ original_commits = gitaly_commits.zip(hash_commits).flatten
+ collection = described_class.new(project, original_commits)
+
+ collection.enrich!
+
+ original_commits.each_with_index do |original_commit, i|
+ new_commit = collection.commits[i]
+ expect(original_commit.id).to eq(new_commit.id)
+ end
+ end
+
+ it 'fetches data if there are unenriched commits' do
+ collection = described_class.new(project, [hash_commit])
+
+ expect(Commit).to receive(:lazy).exactly(:once)
+
+ collection.enrich!
+ end
+
+ it 'does not fetch data if all commits are enriched' do
+ collection = described_class.new(project, [gitaly_commit])
+
+ expect(Commit).not_to receive(:lazy)
+
+ collection.enrich!
+ end
end
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index f2efcd9d0e9..b96ca89c893 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitRange do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index baad8352185..14f4b4d692f 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Commit do
@@ -542,7 +544,7 @@ eos
end
end
- describe '#uri_type' do
+ shared_examples '#uri_type' do
it 'returns the URI type at the given path' do
expect(commit.uri_type('files/html')).to be(:tree)
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
@@ -561,6 +563,20 @@ eos
end
end
+ describe '#uri_type with Gitaly enabled' do
+ it_behaves_like "#uri_type"
+ end
+
+ describe '#uri_type with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged::Tree).to receive(:path).with('files/html').and_call_original
+
+ commit.uri_type('files/html')
+ end
+
+ it_behaves_like '#uri_type'
+ end
+
describe '.from_hash' do
let(:new_commit) { described_class.from_hash(commit.to_hash, project) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8b7c88805c1..017cca0541e 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitStatus do
@@ -49,6 +51,16 @@ describe CommitStatus do
commit_status.success!
end
+
+ describe 'transitioning to running' do
+ let(:commit_status) { create(:commit_status, :pending, started_at: nil) }
+
+ it 'records the started at time' do
+ commit_status.run!
+
+ expect(commit_status.started_at).to be_present
+ end
+ end
end
describe '#started?' do
@@ -437,7 +449,7 @@ describe CommitStatus do
end
it "lock" do
- is_expected.to be true
+ is_expected.to be_truthy
end
it "raise exception when trying to update" do
@@ -451,7 +463,7 @@ describe CommitStatus do
end
it "do not lock" do
- is_expected.to be false
+ is_expected.to be_falsey
end
it "save correctly" do
@@ -479,6 +491,12 @@ describe CommitStatus do
it { is_expected.to be_script_failure }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ let(:reason) { :unmet_prerequisites }
+
+ it { is_expected.to be_unmet_prerequisites }
+ end
end
describe 'ensure stage assignment' do
@@ -555,6 +573,7 @@ describe CommitStatus do
before do
allow(Time).to receive(:now).and_return(current_time)
+ expect(commit_status.any_unmet_prerequisites?).to eq false
end
shared_examples 'commit status enqueued' do
@@ -569,6 +588,12 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
+ context 'when initial state is :preparing' do
+ let(:commit_status) { create(:commit_status, :preparing) }
+
+ it_behaves_like 'commit status enqueued'
+ end
+
context 'when initial state is :skipped' do
let(:commit_status) { create(:commit_status, :skipped) }
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 0bc3ee014e6..43c3580bed2 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Compare do
diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb
index 04d6cfa2c02..de2bc3a387b 100644
--- a/spec/models/concerns/access_requestable_spec.rb
+++ b/spec/models/concerns/access_requestable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AccessRequestable do
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index 1ea7f2b9985..c750be6b75c 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Avatarable do
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 5713106418d..9e7106281ee 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Awardable do
diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
index e5392fe0462..1fe90d3cc9a 100644
--- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
+++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BatchDestroyDependentAssociations do
diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb
index 5906b588d0e..e8657c45a93 100644
--- a/spec/models/concerns/blocks_json_serialization_spec.rb
+++ b/spec/models/concerns/blocks_json_serialization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe BlocksJsonSerialization do
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 447279f19a8..0e5fb2b5153 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -1,376 +1,214 @@
-require 'spec_helper'
-
-describe CacheMarkdownField do
- # The minimum necessary ActiveModel to test this concern
- class ThingWithMarkdownFields
- include ActiveModel::Model
- include ActiveModel::Dirty
+# frozen_string_literal: true
- include ActiveModel::Serialization
-
- class_attribute :attribute_names
- self.attribute_names = []
+require 'spec_helper'
- def attributes
- attribute_names.each_with_object({}) do |name, hsh|
- hsh[name.to_s] = send(name)
- end
+describe CacheMarkdownField, :clean_gitlab_redis_cache do
+ let(:ar_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'issues'
+ include CacheMarkdownField
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
end
+ end
- extend ActiveModel::Callbacks
- define_model_callbacks :create, :update
-
- include CacheMarkdownField
- cache_markdown_field :foo
- cache_markdown_field :baz, pipeline: :single_line
+ let(:other_class) do
+ Class.new do
+ include CacheMarkdownField
- def self.add_attr(name)
- self.attribute_names += [name]
- define_attribute_methods(name)
- attr_reader(name)
- define_method("#{name}=") do |value|
- write_attribute(name, value)
+ def initialize(args = {})
+ @title, @description, @cached_markdown_version = args[:title], args[:description], args[:cached_markdown_version]
+ @title_html, @description_html = args[:title_html], args[:description_html]
+ @author, @project = args[:author], args[:project]
end
- end
- add_attr :cached_markdown_version
+ attr_accessor :title, :description, :cached_markdown_version
- [:foo, :foo_html, :bar, :baz, :baz_html].each do |name|
- add_attr(name)
- end
-
- def initialize(*)
- super
-
- # Pretend new is load
- clear_changes_information
- end
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
- def read_attribute(name)
- instance_variable_get("@#{name}")
- end
-
- def write_attribute(name, value)
- send("#{name}_will_change!") unless value == read_attribute(name)
- instance_variable_set("@#{name}", value)
- end
-
- def save
- run_callbacks :update do
- changes_applied
+ def cache_key
+ "cache-key"
end
end
-
- def has_attribute?(attr_name)
- attribute_names.include?(attr_name)
- end
- end
-
- def thing_subclass(new_attr)
- Class.new(ThingWithMarkdownFields) { add_attr(new_attr) }
end
let(:markdown) { '`Foo`' }
- let(:html) { '<p dir="auto"><code>Foo</code></p>' }
+ let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
let(:updated_markdown) { '`Bar`' }
- let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' }
-
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
- let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
-
- before do
- stub_commonmark_sourcepos_disabled
- end
-
- describe '.attributes' do
- it 'excludes cache attributes' do
- expect(thing.attributes.keys.sort).to eq(%w[bar baz foo])
- end
- end
-
- context 'an unchanged markdown field' do
- before do
- thing.foo = thing.foo
- thing.save
- end
-
- it { expect(thing.foo).to eq(markdown) }
- it { expect(thing.foo_html).to eq(html) }
- it { expect(thing.foo_html_changed?).not_to be_truthy }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
-
- context 'a changed markdown field' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
-
- before do
- thing.foo = updated_markdown
- thing.save
- end
+ let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' }
- it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- context 'when a markdown field is set repeatedly to an empty string' do
- it do
- expect(thing).to receive(:refresh_markdown_cache).once
- thing.foo = ''
- thing.save
- thing.foo = ''
- thing.save
- end
+ def thing_subclass(klass, extra_attribute)
+ Class.new(klass) { attr_accessor(extra_attribute) }
end
- context 'when a markdown field is set repeatedly to a string which renders as empty html' do
- it do
- expect(thing).to receive(:refresh_markdown_cache).once
- thing.foo = '[//]: # (This is also a comment.)'
- thing.save
- thing.foo = '[//]: # (This is also a comment.)'
- thing.save
- end
- end
+ shared_examples 'a class with cached markdown fields' do
+ describe '#cached_html_up_to_date?' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
- context 'when a markdown field and html field are both changed' do
- it do
- expect(thing).not_to receive(:refresh_markdown_cache)
- thing.foo = '_look over there!_'
- thing.foo_html = '<em>look over there!</em>'
- thing.save
- end
- end
-
- context 'a non-markdown field changed' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
-
- before do
- thing.bar = 'OK'
- thing.save
- end
-
- it { expect(thing.bar).to eq('OK') }
- it { expect(thing.foo).to eq(markdown) }
- it { expect(thing.foo_html).to eq(html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
+ subject { thing.cached_html_up_to_date?(:title) }
- context 'version is out of date' do
- let(:thing) { ThingWithMarkdownFields.new(foo: updated_markdown, foo_html: html, cached_markdown_version: nil) }
+ it 'returns false when the version is absent' do
+ thing.cached_markdown_version = nil
- before do
- thing.save
- end
-
- it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
-
- describe '#cached_html_up_to_date?' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
-
- subject { thing.cached_html_up_to_date?(:foo) }
-
- it 'returns false when the version is absent' do
- thing.cached_markdown_version = nil
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the cached version is too old' do
- thing.cached_markdown_version = cache_version - 1
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the cached version is in future' do
- thing.cached_markdown_version = cache_version + 1
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the local version was bumped' do
- allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
- thing.cached_markdown_version = cache_version
-
- is_expected.to be_falsy
- end
-
- it 'returns true when the local version is default' do
- thing.cached_markdown_version = cache_version
-
- is_expected.to be_truthy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true when the cached version is just right' do
- allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
- thing.cached_markdown_version = cache_version + 2
+ it 'returns false when the version is too early' do
+ thing.cached_markdown_version -= 1
- is_expected.to be_truthy
- end
+ is_expected.to be_falsy
+ end
- it 'returns false if markdown has been changed but html has not' do
- thing.foo = updated_html
+ it 'returns false when the version is too late' do
+ thing.cached_markdown_version += 1
- is_expected.to be_falsy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true if markdown has not been changed but html has' do
- thing.foo_html = updated_html
+ it 'returns false when the local version was bumped' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
+ thing.cached_markdown_version = cache_version
- is_expected.to be_truthy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true if markdown and html have both been changed' do
- thing.foo = updated_markdown
- thing.foo_html = updated_html
+ it 'returns true when the local version is default' do
+ thing.cached_markdown_version = cache_version
- is_expected.to be_truthy
- end
+ is_expected.to be_truthy
+ end
- it 'returns false if the markdown field is set but the html is not' do
- thing.foo_html = nil
+ it 'returns true when the cached version is just right' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
+ thing.cached_markdown_version = cache_version + 2
- is_expected.to be_falsy
+ is_expected.to be_truthy
+ end
end
- end
- describe '#latest_cached_markdown_version' do
- subject { thing.latest_cached_markdown_version }
+ describe '#latest_cached_markdown_version' do
+ let(:thing) { klass.new }
+ subject { thing.latest_cached_markdown_version }
- it 'returns default version' do
- thing.cached_markdown_version = nil
- is_expected.to eq(cache_version)
- end
- end
-
- describe '#refresh_markdown_cache' do
- before do
- thing.foo = updated_markdown
+ it 'returns default version' do
+ thing.cached_markdown_version = nil
+ is_expected.to eq(cache_version)
+ end
end
- it 'fills all html fields' do
- thing.refresh_markdown_cache
+ describe '#refresh_markdown_cache' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.foo_html_changed?).to be_truthy
- expect(thing.baz_html_changed?).to be_truthy
- end
+ before do
+ thing.description = updated_markdown
+ end
- it 'does not save the result' do
- expect(thing).not_to receive(:update_columns)
+ it 'fills all html fields' do
+ thing.refresh_markdown_cache
- thing.refresh_markdown_cache
- end
+ expect(thing.description_html).to eq(updated_html)
+ end
- it 'updates the markdown cache version' do
- thing.cached_markdown_version = nil
- thing.refresh_markdown_cache
+ it 'does not save the result' do
+ expect(thing).not_to receive(:save_markdown)
- expect(thing.cached_markdown_version).to eq(cache_version)
- end
- end
+ thing.refresh_markdown_cache
+ end
- describe '#refresh_markdown_cache!' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+ it 'updates the markdown cache version' do
+ thing.cached_markdown_version = nil
+ thing.refresh_markdown_cache
- before do
- thing.foo = updated_markdown
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
end
- it 'fills all html fields' do
- thing.refresh_markdown_cache!
+ describe '#refresh_markdown_cache!' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.foo_html_changed?).to be_truthy
- expect(thing.baz_html_changed?).to be_truthy
- end
+ before do
+ thing.description = updated_markdown
+ end
- it 'skips saving if not persisted' do
- expect(thing).to receive(:persisted?).and_return(false)
- expect(thing).not_to receive(:update_columns)
+ it 'fills all html fields' do
+ thing.refresh_markdown_cache!
- thing.refresh_markdown_cache!
- end
+ expect(thing.description_html).to eq(updated_html)
+ end
- it 'saves the changes using #update_columns' do
- expect(thing).to receive(:persisted?).and_return(true)
- expect(thing).to receive(:update_columns)
- .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version)
+ it 'saves the changes' do
+ expect(thing)
+ .to receive(:save_markdown)
+ .with("description_html" => updated_html, "title_html" => "", "cached_markdown_version" => cache_version)
- thing.refresh_markdown_cache!
+ thing.refresh_markdown_cache!
+ end
end
- end
- describe '#banzai_render_context' do
- subject(:context) { thing.banzai_render_context(:foo) }
+ describe '#banzai_render_context' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ subject(:context) { thing.banzai_render_context(:title) }
- it 'sets project to nil if the object lacks a project' do
- is_expected.to have_key(:project)
- expect(context[:project]).to be_nil
- end
+ it 'sets project to nil if the object lacks a project' do
+ is_expected.to have_key(:project)
+ expect(context[:project]).to be_nil
+ end
- it 'excludes author if the object lacks an author' do
- is_expected.not_to have_key(:author)
- end
+ it 'excludes author if the object lacks an author' do
+ is_expected.not_to have_key(:author)
+ end
- it 'raises if the context for an unrecognised field is requested' do
- expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError)
- end
+ it 'raises if the context for an unrecognised field is requested' do
+ expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError)
+ end
- it 'includes the pipeline' do
- baz = thing.banzai_render_context(:baz)
+ it 'includes the pipeline' do
+ title_context = thing.banzai_render_context(:title)
- expect(baz[:pipeline]).to eq(:single_line)
- end
+ expect(title_context[:pipeline]).to eq(:single_line)
+ end
- it 'returns copies of the context template' do
- template = thing.cached_markdown_fields[:baz]
- copy = thing.banzai_render_context(:baz)
+ it 'returns copies of the context template' do
+ template = thing.cached_markdown_fields[:description]
+ copy = thing.banzai_render_context(:description)
- expect(copy).not_to be(template)
- end
+ expect(copy).not_to be(template)
+ end
- context 'with a project' do
- let(:project) { create(:project, group: create(:group)) }
- let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) }
+ context 'with a project' do
+ let(:project) { build(:project, group: create(:group)) }
+ let(:thing) { thing_subclass(klass, :project).new(title: markdown, title_html: html, project: project) }
- it 'sets the project in the context' do
- is_expected.to have_key(:project)
- expect(context[:project]).to eq(project)
+ it 'sets the project in the context' do
+ is_expected.to have_key(:project)
+ expect(context[:project]).to eq(project)
+ end
end
- it 'invalidates the cache when project changes' do
- thing.project = :new_project
- allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
-
- thing.save
+ context 'with an author' do
+ let(:thing) { thing_subclass(klass, :author).new(title: markdown, title_html: html, author: :author_value) }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(cache_version)
+ it 'sets the author in the context' do
+ is_expected.to have_key(:author)
+ expect(context[:author]).to eq(:author_value)
+ end
end
end
+ end
- context 'with an author' do
- let(:thing) { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author_value) }
-
- it 'sets the author in the context' do
- is_expected.to have_key(:author)
- expect(context[:author]).to eq(:author_value)
- end
+ context 'for Active record classes' do
+ let(:klass) { ar_class }
- it 'invalidates the cache when author changes' do
- thing.author = :new_author
- allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+ it_behaves_like 'a class with cached markdown fields'
+ end
- thing.save
+ context 'for other classes' do
+ let(:klass) { other_class }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(cache_version)
- end
- end
+ it_behaves_like 'a class with cached markdown fields'
end
end
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 43a544cfe26..394fac52aa7 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CacheableAttributes do
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 1bf6c9b3404..d6d41a25eac 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CaseSensitivity do
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
index 51221e07ca3..e41d75568f7 100644
--- a/spec/models/concerns/chronic_duration_attribute_spec.rb
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'ChronicDurationAttribute reader' do
diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb
index 6951be903fe..42bed9434f5 100644
--- a/spec/models/concerns/deployable_spec.rb
+++ b/spec/models/concerns/deployable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Deployable do
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 19ab4382b53..0e34d8fccf3 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe DeploymentPlatform do
diff --git a/spec/models/concerns/deprecated_assignee_spec.rb b/spec/models/concerns/deprecated_assignee_spec.rb
new file mode 100644
index 00000000000..e394de0aa34
--- /dev/null
+++ b/spec/models/concerns/deprecated_assignee_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeprecatedAssignee do
+ let(:user) { create(:user) }
+
+ describe '#assignee_id=' do
+ it 'creates the merge_request_assignees relation' do
+ merge_request = create(:merge_request, assignee_id: user.id)
+
+ merge_request.reload
+
+ expect(merge_request.merge_request_assignees.count).to eq(1)
+ end
+
+ it 'nullifies the assignee_id column' do
+ merge_request = create(:merge_request, assignee_id: user.id)
+
+ merge_request.reload
+
+ expect(merge_request.read_attribute(:assignee_id)).to be_nil
+ end
+
+ context 'when relation already exists' do
+ it 'overwrites existing assignees' do
+ other_user = create(:user)
+ merge_request = create(:merge_request, assignee_id: nil)
+ merge_request.merge_request_assignees.create!(user_id: user.id)
+ merge_request.merge_request_assignees.create!(user_id: other_user.id)
+
+ expect { merge_request.update!(assignee_id: other_user.id) }
+ .to change { merge_request.reload.merge_request_assignees.count }
+ .from(2).to(1)
+ end
+ end
+ end
+
+ describe '#assignee=' do
+ it 'creates the merge_request_assignees relation' do
+ merge_request = create(:merge_request, assignee: user)
+
+ merge_request.reload
+
+ expect(merge_request.merge_request_assignees.count).to eq(1)
+ end
+
+ it 'nullifies the assignee_id column' do
+ merge_request = create(:merge_request, assignee: user)
+
+ merge_request.reload
+
+ expect(merge_request.read_attribute(:assignee_id)).to be_nil
+ end
+
+ context 'when relation already exists' do
+ it 'overwrites existing assignees' do
+ other_user = create(:user)
+ merge_request = create(:merge_request, assignee: nil)
+ merge_request.merge_request_assignees.create!(user_id: user.id)
+ merge_request.merge_request_assignees.create!(user_id: other_user.id)
+
+ expect { merge_request.update!(assignee: other_user) }
+ .to change { merge_request.reload.merge_request_assignees.count }
+ .from(2).to(1)
+ end
+ end
+ end
+
+ describe '#assignee_id' do
+ it 'returns the first assignee ID' do
+ other_user = create(:user)
+ merge_request = create(:merge_request, assignees: [user, other_user])
+
+ merge_request.reload
+
+ expect(merge_request.assignee_id).to eq(merge_request.assignee_ids.first)
+ end
+ end
+
+ describe '#assignees' do
+ context 'when assignee_id exists and there is no relation' do
+ it 'creates the relation' do
+ merge_request = create(:merge_request, assignee_id: nil)
+ merge_request.update_column(:assignee_id, user.id)
+
+ expect { merge_request.assignees }.to change { merge_request.merge_request_assignees.count }.from(0).to(1)
+ end
+
+ it 'nullifies the assignee_id' do
+ merge_request = create(:merge_request, assignee_id: nil)
+ merge_request.update_column(:assignee_id, user.id)
+
+ expect { merge_request.assignees }
+ .to change { merge_request.read_attribute(:assignee_id) }
+ .from(user.id).to(nil)
+ end
+ end
+
+ context 'when DB is read-only' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+ end
+
+ it 'returns a users relation' do
+ merge_request = create(:merge_request, assignee_id: user.id)
+
+ expect(merge_request.assignees).to be_a(ActiveRecord::Relation)
+ expect(merge_request.assignees).to eq([user])
+ end
+
+ it 'returns an empty relation if no assignee_id is set' do
+ merge_request = create(:merge_request, assignee_id: nil)
+
+ expect(merge_request.assignees).to be_a(ActiveRecord::Relation)
+ expect(merge_request.assignees).to eq([])
+ end
+ end
+ end
+
+ describe '#assignee_ids' do
+ context 'when assignee_id exists and there is no relation' do
+ it 'creates the relation' do
+ merge_request = create(:merge_request, assignee_id: nil)
+ merge_request.update_column(:assignee_id, user.id)
+
+ expect { merge_request.assignee_ids }.to change { merge_request.merge_request_assignees.count }.from(0).to(1)
+ end
+
+ it 'nullifies the assignee_id' do
+ merge_request = create(:merge_request, assignee_id: nil)
+ merge_request.update_column(:assignee_id, user.id)
+
+ expect { merge_request.assignee_ids }
+ .to change { merge_request.read_attribute(:assignee_id) }
+ .from(user.id).to(nil)
+ end
+ end
+
+ context 'when DB is read-only' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+ end
+
+ it 'returns a list of user IDs' do
+ merge_request = create(:merge_request, assignee_id: user.id)
+
+ expect(merge_request.assignee_ids).to be_a(Array)
+ expect(merge_request.assignee_ids).to eq([user.id])
+ end
+
+ it 'returns an empty relation if no assignee_id is set' do
+ merge_request = create(:merge_request, assignee_id: nil)
+
+ expect(merge_request.assignee_ids).to be_a(Array)
+ expect(merge_request.assignee_ids).to eq([])
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 64bf04071e8..baddca47dfa 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiscussionOnDiff do
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
index 17224c09693..c4cf8e80f7a 100644
--- a/spec/models/concerns/each_batch_spec.rb
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EachBatch do
diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb
index 49a9a8ebcbc..4a4a3ca5687 100644
--- a/spec/models/concerns/editable_spec.rb
+++ b/spec/models/concerns/editable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Editable do
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index f7b436f32e6..f4f5eab5b86 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Expirable do
diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb
index 8d3f94267fa..7830acbae3d 100644
--- a/spec/models/concerns/faster_cache_keys_spec.rb
+++ b/spec/models/concerns/faster_cache_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FasterCacheKeys do
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
index 3f601243245..276d3d9e1d5 100644
--- a/spec/models/concerns/feature_gate_spec.rb
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FeatureGate do
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 28352d8c961..194caac3fce 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupDescendant, :nested_groups do
diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb
index 8aed72d77a4..66b25c77430 100644
--- a/spec/models/concerns/has_ref_spec.rb
+++ b/spec/models/concerns/has_ref_spec.rb
@@ -16,6 +16,16 @@ describe HasRef do
it 'return true when tag is set to false' do
is_expected.to be_truthy
end
+
+ context 'when it was triggered by merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
end
context 'is not a tag' do
@@ -55,5 +65,15 @@ describe HasRef do
is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX)
end
end
+
+ context 'when it is triggered by a merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+ let(:build) { create(:ci_build, tag: false, pipeline: pipeline) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
end
end
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 6b1038cb8fd..a217dc42537 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HasStatus do
@@ -34,6 +36,22 @@ describe HasStatus do
it { is_expected.to eq 'running' }
end
+ context 'all preparing' do
+ let!(:statuses) do
+ [create(type, status: :preparing), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
+ context 'at least one preparing' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
context 'success and failed but allowed to fail' do
let!(:statuses) do
[create(type, status: :success),
@@ -188,7 +206,7 @@ describe HasStatus do
end
end
- %i[created running pending success
+ %i[created preparing running pending success
failed canceled skipped].each do |status|
it_behaves_like 'having a job', status
end
@@ -234,7 +252,7 @@ describe HasStatus do
describe '.alive' do
subject { CommitStatus.alive }
- %i[running pending created].each do |status|
+ %i[running pending preparing created].each do |status|
it_behaves_like 'containing the job', status
end
@@ -270,7 +288,7 @@ describe HasStatus do
describe '.cancelable' do
subject { CommitStatus.cancelable }
- %i[running pending created scheduled].each do |status|
+ %i[running pending preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb
index 3fbe86c5b56..2bb21d7934e 100644
--- a/spec/models/concerns/has_variable_spec.rb
+++ b/spec/models/concerns/has_variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HasVariable do
@@ -57,7 +59,7 @@ describe HasVariable do
describe '#to_runner_variable' do
it 'returns a hash for the runner' do
expect(subject.to_runner_variable)
- .to eq(key: subject.key, value: subject.value, public: false)
+ .to include(key: subject.key, value: subject.value, public: false)
end
end
end
diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb
index b70f2331a0e..6b82825d2cc 100644
--- a/spec/models/concerns/ignorable_column_spec.rb
+++ b/spec/models/concerns/ignorable_column_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IgnorableColumn do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 41159348e04..64f02978d79 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issuable do
@@ -32,17 +34,56 @@ describe Issuable do
end
describe "Validation" do
- subject { build(:issue) }
+ context 'general validations' do
+ subject { build(:issue) }
- before do
- allow(InternalId).to receive(:generate_next).and_return(nil)
+ before do
+ allow(InternalId).to receive(:generate_next).and_return(nil)
+ end
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:iid) }
+ it { is_expected.to validate_presence_of(:author) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_length_of(:title).is_at_most(255) }
end
- it { is_expected.to validate_presence_of(:project) }
- it { is_expected.to validate_presence_of(:iid) }
- it { is_expected.to validate_presence_of(:author) }
- it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ describe 'milestone' do
+ let(:project) { create(:project) }
+ let(:milestone_id) { create(:milestone, project: project).id }
+ let(:params) do
+ {
+ title: 'something',
+ project: project,
+ author: build(:user),
+ milestone_id: milestone_id
+ }
+ end
+
+ subject { issuable_class.new(params) }
+
+ context 'with correct params' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'with empty string milestone' do
+ let(:milestone_id) { '' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'with nil milestone id' do
+ let(:milestone_id) { nil }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'with a milestone id from another project' do
+ let(:milestone_id) { create(:milestone).id }
+
+ it { is_expected.to be_invalid }
+ end
+ end
end
describe "Scope" do
@@ -66,6 +107,48 @@ describe Issuable do
end
end
+ describe '#milestone_available?' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:issue) { create(:issue, project: project) }
+
+ def build_issuable(milestone_id)
+ issuable_class.new(project: project, milestone_id: milestone_id)
+ end
+
+ it 'returns true with a milestone from the issue project' do
+ milestone = create(:milestone, project: project)
+
+ expect(build_issuable(milestone.id).milestone_available?).to be_truthy
+ end
+
+ it 'returns true with a milestone from the issue project group' do
+ milestone = create(:milestone, group: group)
+
+ expect(build_issuable(milestone.id).milestone_available?).to be_truthy
+ end
+
+ it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
+ parent = create(:group)
+ group.update(parent: parent)
+ milestone = create(:milestone, group: parent)
+
+ expect(build_issuable(milestone.id).milestone_available?).to be_truthy
+ end
+
+ it 'returns false with a milestone from another project' do
+ milestone = create(:milestone)
+
+ expect(build_issuable(milestone.id).milestone_available?).to be_falsey
+ end
+
+ it 'returns false with a milestone from another group' do
+ milestone = create(:milestone, group: create(:group))
+
+ expect(build_issuable(milestone.id).milestone_available?).to be_falsey
+ end
+ end
+
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
let!(:searchable_issue2) { create(:issue, title: 'Aw') }
@@ -419,8 +502,8 @@ describe Issuable do
let(:user2) { create(:user) }
before do
- merge_request.update(assignee: user)
- merge_request.update(assignee: user2)
+ merge_request.update(assignees: [user])
+ merge_request.update(assignees: [user, user2])
expect(Gitlab::HookData::IssuableBuilder)
.to receive(:new).with(merge_request).and_return(builder)
end
@@ -429,8 +512,7 @@ describe Issuable do
expect(builder).to receive(:build).with(
user: user,
changes: hash_including(
- 'assignee_id' => [user.id, user2.id],
- 'assignee' => [user.hook_attrs, user2.hook_attrs]
+ 'assignees' => [[user.hook_attrs], [user.hook_attrs, user2.hook_attrs]]
))
merge_request.to_hook_data(user, old_associations: { assignees: [user] })
@@ -576,7 +658,7 @@ describe Issuable do
end
context 'adding time' do
- it 'should update the total time spent' do
+ it 'updates the total time spent' do
spend_time(1800)
expect(issue.total_time_spent).to eq(1800)
@@ -596,7 +678,7 @@ describe Issuable do
spend_time(1800)
end
- it 'should update the total time spent' do
+ it 'updates the total time spent' do
spend_time(-900)
expect(issue.total_time_spent).to eq(900)
diff --git a/spec/models/concerns/issuable_states_spec.rb b/spec/models/concerns/issuable_states_spec.rb
new file mode 100644
index 00000000000..70450159cc0
--- /dev/null
+++ b/spec/models/concerns/issuable_states_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# This spec checks if state_id column of issues and merge requests
+# are being synced on every save.
+# It can be removed in the next release. Check https://gitlab.com/gitlab-org/gitlab-ce/issues/51789 for more information.
+describe IssuableStates do
+ [Issue, MergeRequest].each do |klass|
+ it "syncs state_id column when #{klass.model_name.human} gets created" do
+ klass.available_states.each do |state, state_id|
+ issuable = build(klass.model_name.param_key, state: state.to_s)
+
+ issuable.save!
+
+ expect(issuable.state_id).to eq(state_id)
+ end
+ end
+
+ it "syncs state_id column when #{klass.model_name.human} gets updated" do
+ klass.available_states.each do |state, state_id|
+ issuable = create(klass.model_name.param_key, state: state.to_s)
+
+ issuable.update(state: state)
+
+ expect(issuable.state_id).to eq(state_id)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb
index 7a279547a3a..7c97b580779 100644
--- a/spec/models/concerns/loaded_in_group_list_spec.rb
+++ b/spec/models/concerns/loaded_in_group_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LoadedInGroupList do
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index ff4a04ea573..ee32e3b165b 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ManualInverseAssociation do
diff --git a/spec/models/concerns/maskable_spec.rb b/spec/models/concerns/maskable_spec.rb
new file mode 100644
index 00000000000..aeba7ad862f
--- /dev/null
+++ b/spec/models/concerns/maskable_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Maskable do
+ let(:variable) { build(:ci_variable) }
+
+ describe 'masked value validations' do
+ subject { variable }
+
+ context 'when variable is masked' do
+ before do
+ subject.masked = true
+ end
+
+ it { is_expected.not_to allow_value('hello').for(:value) }
+ it { is_expected.not_to allow_value('hello world').for(:value) }
+ it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.not_to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+
+ context 'when variable is not masked' do
+ before do
+ subject.masked = false
+ end
+
+ it { is_expected.to allow_value('hello').for(:value) }
+ it { is_expected.to allow_value('hello world').for(:value) }
+ it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+ end
+
+ describe 'REGEX' do
+ subject { Maskable::REGEX }
+
+ it 'does not match strings shorter than 8 letters' do
+ expect(subject.match?('hello')).to eq(false)
+ end
+
+ it 'does not match strings with spaces' do
+ expect(subject.match?('hello world')).to eq(false)
+ end
+
+ it 'does not match strings with shell variables' do
+ expect(subject.match?('hello$VARIABLEworld')).to eq(false)
+ end
+
+ it 'does not match strings with escape characters' do
+ expect(subject.match?('hello\rworld')).to eq(false)
+ end
+
+ it 'does not match strings that span more than one line' do
+ string = <<~EOS
+ hello
+ world
+ EOS
+
+ expect(subject.match?(string)).to eq(false)
+ end
+
+ it 'matches valid strings' do
+ expect(subject.match?('helloworld')).to eq(true)
+ end
+ end
+
+ describe '#to_runner_variable' do
+ subject { variable.to_runner_variable }
+
+ it 'exposes the masked attribute' do
+ expect(subject).to include(:masked)
+ end
+ end
+end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index a9b237fa9ea..f31e3e8821d 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mentionable do
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 87bf731340f..7e9a8306612 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestone, 'Milestoneish' do
@@ -9,8 +11,10 @@ describe Milestone, 'Milestoneish' do
let(:admin) { create(:admin) }
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
- let!(:issue) { create(:issue, project: project, milestone: milestone) }
- let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
+ let(:label1) { create(:label, project: project) }
+ let(:label2) { create(:label, project: project) }
+ let!(:issue) { create(:issue, project: project, milestone: milestone, assignees: [member], labels: [label1]) }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone, labels: [label2]) }
let!(:security_issue_2) { create(:issue, :confidential, project: project, assignees: [assignee], milestone: milestone) }
let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
@@ -42,69 +46,151 @@ describe Milestone, 'Milestoneish' do
end
end
- describe '#sorted_merge_requests' do
- it 'sorts merge requests by label priority' do
- merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone)
- merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone)
- merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone)
-
- merge_requests = milestone.sorted_merge_requests
-
- expect(merge_requests.first).to eq(merge_request_2)
- expect(merge_requests.second).to eq(merge_request_1)
- expect(merge_requests.third).to eq(merge_request_3)
+ context 'attributes visibility' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:users) do
+ {
+ anonymous: nil,
+ non_member: non_member,
+ guest: guest,
+ member: member,
+ assignee: assignee
+ }
end
- end
- describe '#closed_items_count' do
- it 'does not count confidential issues for non project members' do
- expect(milestone.closed_items_count(non_member)).to eq 2
+ let(:project_visibility_levels) do
+ {
+ public: Gitlab::VisibilityLevel::PUBLIC,
+ internal: Gitlab::VisibilityLevel::INTERNAL,
+ private: Gitlab::VisibilityLevel::PRIVATE
+ }
end
- it 'does not count confidential issues for project members with guest role' do
- expect(milestone.closed_items_count(guest)).to eq 2
+ describe '#issue_participants_visible_by_user' do
+ where(:visibility, :user_role, :result) do
+ :public | nil | [:member]
+ :public | :non_member | [:member]
+ :public | :guest | [:member]
+ :public | :member | [:member, :assignee]
+ :internal | nil | []
+ :internal | :non_member | [:member]
+ :internal | :guest | [:member]
+ :internal | :member | [:member, :assignee]
+ :private | nil | []
+ :private | :non_member | []
+ :private | :guest | [:member]
+ :private | :member | [:member, :assignee]
+ end
+
+ with_them do
+ before do
+ project.update(visibility_level: project_visibility_levels[visibility])
+ end
+
+ it 'returns the proper participants' do
+ user = users[user_role]
+ participants = result.map { |role| users[role] }
+
+ expect(milestone.issue_participants_visible_by_user(user)).to match_array(participants)
+ end
+ end
end
- it 'counts confidential issues for author' do
- expect(milestone.closed_items_count(author)).to eq 4
+ describe '#issue_labels_visible_by_user' do
+ let(:labels) do
+ {
+ label1: label1,
+ label2: label2
+ }
+ end
+
+ where(:visibility, :user_role, :result) do
+ :public | nil | [:label1]
+ :public | :non_member | [:label1]
+ :public | :guest | [:label1]
+ :public | :member | [:label1, :label2]
+ :internal | nil | []
+ :internal | :non_member | [:label1]
+ :internal | :guest | [:label1]
+ :internal | :member | [:label1, :label2]
+ :private | nil | []
+ :private | :non_member | []
+ :private | :guest | [:label1]
+ :private | :member | [:label1, :label2]
+ end
+
+ with_them do
+ before do
+ project.update(visibility_level: project_visibility_levels[visibility])
+ end
+
+ it 'returns the proper participants' do
+ user = users[user_role]
+ expected_labels = result.map { |label| labels[label] }
+
+ expect(milestone.issue_labels_visible_by_user(user)).to match_array(expected_labels)
+ end
+ end
end
+ end
- it 'counts confidential issues for assignee' do
- expect(milestone.closed_items_count(assignee)).to eq 4
- end
+ describe '#sorted_merge_requests' do
+ it 'sorts merge requests by label priority' do
+ merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone)
+ merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone)
+ merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone)
- it 'counts confidential issues for project members' do
- expect(milestone.closed_items_count(member)).to eq 6
- end
+ merge_requests = milestone.sorted_merge_requests(member)
- it 'counts all issues for admin' do
- expect(milestone.closed_items_count(admin)).to eq 6
+ expect(merge_requests.first).to eq(merge_request_2)
+ expect(merge_requests.second).to eq(merge_request_1)
+ expect(merge_requests.third).to eq(merge_request_3)
end
end
- describe '#total_items_count' do
- it 'does not count confidential issues for non project members' do
- expect(milestone.total_items_count(non_member)).to eq 4
- end
+ describe '#merge_requests_visible_to_user' do
+ let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) }
- it 'does not count confidential issues for project members with guest role' do
- expect(milestone.total_items_count(guest)).to eq 4
- end
+ context 'when project is private' do
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
- it 'counts confidential issues for author' do
- expect(milestone.total_items_count(author)).to eq 7
- end
-
- it 'counts confidential issues for assignee' do
- expect(milestone.total_items_count(assignee)).to eq 7
- end
+ it 'does not return any merge request for a non member' do
+ merge_requests = milestone.merge_requests_visible_to_user(non_member)
+ expect(merge_requests).to be_empty
+ end
- it 'counts confidential issues for project members' do
- expect(milestone.total_items_count(member)).to eq 10
+ it 'returns milestone merge requests for a member' do
+ merge_requests = milestone.merge_requests_visible_to_user(member)
+ expect(merge_requests).to contain_exactly(merge_request)
+ end
end
- it 'counts all issues for admin' do
- expect(milestone.total_items_count(admin)).to eq 10
+ context 'when project is public' do
+ context 'when merge requests are available to anyone' do
+ it 'returns milestone merge requests for a non member' do
+ merge_requests = milestone.merge_requests_visible_to_user(non_member)
+ expect(merge_requests).to contain_exactly(merge_request)
+ end
+ end
+
+ context 'when merge requests are available to project members' do
+ before do
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'does not return any merge request for a non member' do
+ merge_requests = milestone.merge_requests_visible_to_user(non_member)
+ expect(merge_requests).to be_empty
+ end
+
+ it 'returns milestone merge requests for a member' do
+ merge_requests = milestone.merge_requests_visible_to_user(member)
+ expect(merge_requests).to contain_exactly(merge_request)
+ end
+ end
end
end
@@ -122,28 +208,42 @@ describe Milestone, 'Milestoneish' do
end
describe '#percent_complete' do
+ context 'division by zero' do
+ let(:new_milestone) { build_stubbed(:milestone) }
+
+ it { expect(new_milestone.percent_complete(admin)).to eq(0) }
+ end
+ end
+
+ describe '#count_issues_by_state' do
it 'does not count confidential issues for non project members' do
- expect(milestone.percent_complete(non_member)).to eq 50
+ expect(milestone.closed_issues_count(non_member)).to eq 2
+ expect(milestone.total_issues_count(non_member)).to eq 3
end
it 'does not count confidential issues for project members with guest role' do
- expect(milestone.percent_complete(guest)).to eq 50
+ expect(milestone.closed_issues_count(guest)).to eq 2
+ expect(milestone.total_issues_count(guest)).to eq 3
end
it 'counts confidential issues for author' do
- expect(milestone.percent_complete(author)).to eq 57
+ expect(milestone.closed_issues_count(author)).to eq 4
+ expect(milestone.total_issues_count(author)).to eq 6
end
it 'counts confidential issues for assignee' do
- expect(milestone.percent_complete(assignee)).to eq 57
+ expect(milestone.closed_issues_count(assignee)).to eq 4
+ expect(milestone.total_issues_count(assignee)).to eq 6
end
it 'counts confidential issues for project members' do
- expect(milestone.percent_complete(member)).to eq 60
+ expect(milestone.closed_issues_count(member)).to eq 6
+ expect(milestone.total_issues_count(member)).to eq 9
end
it 'counts confidential issues for admin' do
- expect(milestone.percent_complete(admin)).to eq 60
+ expect(milestone.closed_issues_count(admin)).to eq 6
+ expect(milestone.total_issues_count(admin)).to eq 9
end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 485a6e165a1..e17b98536fa 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Noteable do
@@ -258,4 +260,16 @@ describe Noteable do
end
end
end
+
+ describe '.replyable_types' do
+ it 'exposes the replyable types' do
+ expect(described_class.replyable_types).to include('Issue', 'MergeRequest')
+ end
+ end
+
+ describe '.resolvable_types' do
+ it 'exposes the replyable types' do
+ expect(described_class.resolvable_types).to include('MergeRequest')
+ end
+ end
end
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 431f1482615..3d5937c4fc6 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Participable do
diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb
index 941647a79fb..9db868dd348 100644
--- a/spec/models/concerns/presentable_spec.rb
+++ b/spec/models/concerns/presentable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Presentable do
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 9041690023f..5aa43b58217 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectFeaturesCompatibility do
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index f4b9c57e71a..25a2d290f76 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -1,17 +1,20 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
- class TestClass
- include PrometheusAdapter
- end
-
let(:project) { create(:prometheus_project) }
let(:service) { project.prometheus_service }
- let(:described_class) { TestClass }
+ let(:described_class) do
+ Class.new do
+ include PrometheusAdapter
+ end
+ end
+
let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
describe '#query' do
@@ -74,6 +77,28 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe 'additional_metrics' do
+ let(:additional_metrics_environment_query) { Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery }
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ let(:time_window) { [1552642245.067, 1552642095.831] }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'with valid data' do
+ subject { service.query(:additional_metrics_environment, environment, *time_window) }
+
+ before do
+ stub_reactive_cache(service, prometheus_data, additional_metrics_environment_query, environment.id, *time_window)
+ end
+
+ it 'returns reactive data' do
+ expect(subject).to eq(prometheus_data)
+ end
+ end
+ end
end
describe '#calculate_reactive_cache' do
@@ -118,4 +143,24 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe '#build_query_args' do
+ subject { service.build_query_args(*args) }
+
+ context 'when active record models are included' do
+ let(:args) { [double(:environment, id: 12)] }
+
+ it 'serializes by id' do
+ is_expected.to eq [12]
+ end
+ end
+
+ context 'when args are safe for serialization' do
+ let(:args) { ['stringy arg', 5, 6.0, :symbolic_arg] }
+
+ it 'does nothing' do
+ is_expected.to eq args
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb
index ce602337647..f63ad958ed3 100644
--- a/spec/models/concerns/protected_ref_access_spec.rb
+++ b/spec/models/concerns/protected_ref_access_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedRefAccess do
+ include ExternalAuthorizationServiceHelpers
+
subject(:protected_ref_access) do
create(:protected_branch, :maintainers_can_push).push_access_levels.first
end
@@ -27,5 +31,15 @@ describe ProtectedRefAccess do
expect(protected_ref_access.check_access(developer)).to be_falsy
end
+
+ context 'external authorization' do
+ it 'is false if external authorization denies access' do
+ maintainer = create(:user)
+ project.add_maintainer(maintainer)
+ external_service_deny_access(maintainer, project)
+
+ expect(protected_ref_access.check_access(maintainer)).to be_falsey
+ end
+ end
end
end
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 03ae45e6b17..53df9e0bc05 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ReactiveCaching, :use_clean_rails_memory_store_caching do
@@ -14,6 +16,10 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
attr_reader :id
+ def self.primary_key
+ :id
+ end
+
def initialize(id, &blk)
@id = id
@calculator = blk
@@ -104,6 +110,46 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
end
+ describe '.reactive_cache_worker_finder' do
+ context 'with default reactive_cache_worker_finder' do
+ let(:args) { %w(other args) }
+
+ before do
+ allow(instance.class).to receive(:find_by).with(id: instance.id)
+ .and_return(instance)
+ end
+
+ it 'calls the activerecord find_by method' do
+ result = instance.class.reactive_cache_worker_finder.call(instance.id, *args)
+
+ expect(result).to eq(instance)
+ expect(instance.class).to have_received(:find_by).with(id: instance.id)
+ end
+ end
+
+ context 'with custom reactive_cache_worker_finder' do
+ let(:args) { %w(arg1 arg2) }
+ let(:instance) { CustomFinderCacheTest.new(666, &calculation) }
+
+ class CustomFinderCacheTest < CacheTest
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+
+ def self.from_cache(*args); end
+ end
+
+ before do
+ allow(instance.class).to receive(:from_cache).with(*args).and_return(instance)
+ end
+
+ it 'overrides the default reactive_cache_worker_finder' do
+ result = instance.class.reactive_cache_worker_finder.call(instance.id, *args)
+
+ expect(result).to eq(instance)
+ expect(instance.class).to have_received(:from_cache).with(*args)
+ end
+ end
+ end
+
describe '#clear_reactive_cache!' do
before do
stub_reactive_cache(instance, 4)
diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb
index 7feeaa54069..57c7d2cb767 100644
--- a/spec/models/concerns/redactable_spec.rb
+++ b/spec/models/concerns/redactable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Redactable do
diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb
index 23c6c6233e9..a9dca27f258 100644
--- a/spec/models/concerns/redis_cacheable_spec.rb
+++ b/spec/models/concerns/redis_cacheable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RedisCacheable do
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index ac8da30b6c9..d0ae45f7871 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RelativePositioning do
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 97b046b0f21..9ea01ca9002 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussion, ResolvableDiscussion do
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index fcb5250278e..4f46252a044 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Note, ResolvableNote do
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 565266321d3..1fb0dd5030c 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Group, 'Routable' do
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 0d3beb6a6e3..a4a81ae126d 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ShaAttribute do
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index 39c16ae60af..184f7986a6f 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Sortable do
@@ -99,7 +101,7 @@ describe Sortable do
expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa))
end
- it 'sorts groups by name via case-insentitive comparision' do
+ it 'sorts groups by name via case-insensitive comparision' do
expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb))
expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa))
end
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index e698207166c..67353475251 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Spammable do
@@ -10,7 +12,7 @@ describe Spammable do
end
describe 'ClassMethods' do
- it 'should return correct attr_spammable' do
+ it 'returns correct attr_spammable' do
expect(issue.spammable_text).to eq("#{issue.title}\n#{issue.description}")
end
end
@@ -18,7 +20,7 @@ describe Spammable do
describe 'InstanceMethods' do
let(:issue) { build(:issue, spam: true) }
- it 'should be invalid if spam' do
+ it 'is invalid if spam' do
expect(issue.valid?).to be_falsey
end
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
index 8c945686b66..5c0d1042e06 100644
--- a/spec/models/concerns/strip_attribute_spec.rb
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StripAttribute do
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index 45dfb136aea..2f88adf08dd 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Subscribable, 'Subscribable' do
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 40cb4eef60a..51e28974ae0 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'TokenAuthenticatable' do
diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
index 6605f1f5a5f..7332da309d5 100644
--- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TokenAuthenticatableStrategies::Base do
@@ -15,7 +17,7 @@ describe TokenAuthenticatableStrategies::Base do
context 'when encrypted strategy is specified' do
it 'fabricates encrypted strategy object' do
- strategy = described_class.fabricate(instance, field, encrypted: true)
+ strategy = described_class.fabricate(instance, field, encrypted: :required)
expect(strategy).to be_a TokenAuthenticatableStrategies::Encrypted
end
@@ -23,7 +25,7 @@ describe TokenAuthenticatableStrategies::Base do
context 'when no strategy is specified' do
it 'fabricates insecure strategy object' do
- strategy = described_class.fabricate(instance, field, something: true)
+ strategy = described_class.fabricate(instance, field, something: :required)
expect(strategy).to be_a TokenAuthenticatableStrategies::Insecure
end
@@ -31,35 +33,9 @@ describe TokenAuthenticatableStrategies::Base do
context 'when incompatible options are provided' do
it 'raises an error' do
- expect { described_class.fabricate(instance, field, digest: true, encrypted: true) }
+ expect { described_class.fabricate(instance, field, digest: true, encrypted: :required) }
.to raise_error ArgumentError
end
end
end
-
- describe '#fallback?' do
- context 'when fallback is set' do
- it 'recognizes fallback setting' do
- strategy = described_class.new(instance, field, fallback: true)
-
- expect(strategy.fallback?).to be true
- end
- end
-
- context 'when fallback is not a valid value' do
- it 'raises an error' do
- strategy = described_class.new(instance, field, fallback: 'something')
-
- expect { strategy.fallback? }.to raise_error ArgumentError
- end
- end
-
- context 'when fallback is not set' do
- it 'raises an error' do
- strategy = described_class.new(instance, field, {})
-
- expect(strategy.fallback?).to eq false
- end
- end
- end
end
diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
index 93cab80cb1f..70f41981b3b 100644
--- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TokenAuthenticatableStrategies::Encrypted do
@@ -12,19 +14,9 @@ describe TokenAuthenticatableStrategies::Encrypted do
described_class.new(model, 'some_field', options)
end
- describe '.new' do
- context 'when fallback and migration strategies are set' do
- let(:options) { { fallback: true, migrating: true } }
-
- it 'raises an error' do
- expect { subject }.to raise_error ArgumentError, /not compatible/
- end
- end
- end
-
describe '#find_token_authenticatable' do
- context 'when using fallback strategy' do
- let(:options) { { fallback: true } }
+ context 'when using optional strategy' do
+ let(:options) { { encrypted: :optional } }
it 'finds the encrypted resource by cleartext' do
allow(model).to receive(:find_by)
@@ -50,7 +42,7 @@ describe TokenAuthenticatableStrategies::Encrypted do
end
context 'when using migration strategy' do
- let(:options) { { migrating: true } }
+ let(:options) { { encrypted: :migrating } }
it 'finds the cleartext resource by cleartext' do
allow(model).to receive(:find_by)
@@ -73,8 +65,8 @@ describe TokenAuthenticatableStrategies::Encrypted do
end
describe '#get_token' do
- context 'when using fallback strategy' do
- let(:options) { { fallback: true } }
+ context 'when using optional strategy' do
+ let(:options) { { encrypted: :optional } }
it 'returns decrypted token when an encrypted token is present' do
allow(instance).to receive(:read_attribute)
@@ -98,7 +90,7 @@ describe TokenAuthenticatableStrategies::Encrypted do
end
context 'when using migration strategy' do
- let(:options) { { migrating: true } }
+ let(:options) { { encrypted: :migrating } }
it 'returns cleartext token when an encrypted token is present' do
allow(instance).to receive(:read_attribute)
@@ -127,8 +119,8 @@ describe TokenAuthenticatableStrategies::Encrypted do
end
describe '#set_token' do
- context 'when using fallback strategy' do
- let(:options) { { fallback: true } }
+ context 'when using optional strategy' do
+ let(:options) { { encrypted: :optional } }
it 'writes encrypted token and removes plaintext token and returns it' do
expect(instance).to receive(:[]=)
@@ -141,7 +133,7 @@ describe TokenAuthenticatableStrategies::Encrypted do
end
context 'when using migration strategy' do
- let(:options) { { migrating: true } }
+ let(:options) { { encrypted: :migrating } }
it 'writes encrypted token and writes plaintext token' do
expect(instance).to receive(:[]=)
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
index 265abd6bd72..f28e5f56411 100644
--- a/spec/models/concerns/triggerable_hooks_spec.rb
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe TriggerableHooks do
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
index 6cd2de6dcce..9ba35702ba6 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Uniquify do
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index e46945e301e..013112d1d51 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRepository do
diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb
index b3193619503..60b1a860dfd 100644
--- a/spec/models/conversational_development_index/metric_spec.rb
+++ b/spec/models/conversational_development_index/metric_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ConversationalDevelopmentIndex::Metric do
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 6a6b58fb52b..b22a0340015 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#code' do
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 45f1b4fe8a3..07d60be091a 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#issue' do
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index d366e2b723a..3d22a284264 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#plan' do
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 156eb96cfce..383727cd8f7 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#production' do
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 0aedfb49cb5..1af5f9cc1f4 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#review' do
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 0cbda50c688..8375944f03c 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#staging' do
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index e58b8fdff58..b78258df564 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#test' do
diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics_spec.rb
index 0fe24870f02..5d8b5b573cf 100644
--- a/spec/models/cycle_analytics_spec.rb
+++ b/spec/models/cycle_analytics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CycleAnalytics do
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 41440c6d288..ec6cfb6b826 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployKey, :mailer do
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index fca3090ff4a..c137444763b 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployKeysProject do
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 3435f93c999..2fe82eaa778 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployToken do
@@ -7,7 +9,7 @@ describe DeployToken do
it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
describe '#ensure_token' do
- it 'should ensure a token' do
+ it 'ensures a token' do
deploy_token.token = nil
deploy_token.save
@@ -17,13 +19,13 @@ describe DeployToken do
describe '#ensure_at_least_one_scope' do
context 'with at least one scope' do
- it 'should be valid' do
+ it 'is valid' do
is_expected.to be_valid
end
end
context 'with no scopes' do
- it 'should be invalid' do
+ it 'is invalid' do
deploy_token = build(:deploy_token, read_repository: false, read_registry: false)
expect(deploy_token).not_to be_valid
@@ -34,13 +36,13 @@ describe DeployToken do
describe '#scopes' do
context 'with all the scopes' do
- it 'should return scopes assigned to DeployToken' do
+ it 'returns scopes assigned to DeployToken' do
expect(deploy_token.scopes).to eq([:read_repository, :read_registry])
end
end
context 'with only one scope' do
- it 'should return scopes assigned to DeployToken' do
+ it 'returns scopes assigned to DeployToken' do
deploy_token = create(:deploy_token, read_registry: false)
expect(deploy_token.scopes).to eq([:read_repository])
end
@@ -48,7 +50,7 @@ describe DeployToken do
end
describe '#revoke!' do
- it 'should update revoke attribute' do
+ it 'updates revoke attribute' do
deploy_token.revoke!
expect(deploy_token.revoked?).to be_truthy
end
@@ -56,20 +58,20 @@ describe DeployToken do
describe "#active?" do
context "when it has been revoked" do
- it 'should return false' do
+ it 'returns false' do
deploy_token.revoke!
expect(deploy_token.active?).to be_falsy
end
end
context "when it hasn't been revoked and is not expired" do
- it 'should return true' do
+ it 'returns true' do
expect(deploy_token.active?).to be_truthy
end
end
context "when it hasn't been revoked and is expired" do
- it 'should return true' do
+ it 'returns true' do
deploy_token.update_attribute(:expires_at, Date.today - 5.days)
expect(deploy_token.active?).to be_falsy
end
@@ -78,7 +80,7 @@ describe DeployToken do
context "when it hasn't been revoked and has no expiry" do
let(:deploy_token) { create(:deploy_token, expires_at: nil) }
- it 'should return true' do
+ it 'returns true' do
expect(deploy_token.active?).to be_truthy
end
end
@@ -124,7 +126,7 @@ describe DeployToken do
context 'when using Forever.date' do
let(:deploy_token) { create(:deploy_token, expires_at: nil) }
- it 'should return nil' do
+ it 'returns nil' do
expect(deploy_token.expires_at).to be_nil
end
end
@@ -133,7 +135,7 @@ describe DeployToken do
let(:expires_at) { Date.today + 5.months }
let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
- it 'should return the personalized date' do
+ it 'returns the personalized date' do
expect(deploy_token.expires_at).to eq(expires_at)
end
end
@@ -143,7 +145,7 @@ describe DeployToken do
context 'when passing nil' do
let(:deploy_token) { create(:deploy_token, expires_at: nil) }
- it 'should assign Forever.date' do
+ it 'assigns Forever.date' do
expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date)
end
end
@@ -152,7 +154,7 @@ describe DeployToken do
let(:expires_at) { Date.today + 5.months }
let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
- it 'should respect the value' do
+ it 'respects the value' do
expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at)
end
end
@@ -164,14 +166,14 @@ describe DeployToken do
subject { project.deploy_tokens.gitlab_deploy_token }
context 'with a gitlab deploy token associated' do
- it 'should return the gitlab deploy token' do
+ it 'returns the gitlab deploy token' do
deploy_token = create(:deploy_token, :gitlab_deploy_token, projects: [project])
is_expected.to eq(deploy_token)
end
end
context 'with no gitlab deploy token associated' do
- it 'should return nil' do
+ it 'returns nil' do
is_expected.to be_nil
end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a8d53cfcd7d..1dceef3fc00 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Deployment do
subject { build(:deployment) }
- it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:environment) }
+ it { is_expected.to belong_to(:project).required }
+ it { is_expected.to belong_to(:environment).required }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:deployable) }
@@ -100,6 +102,13 @@ describe Deployment do
deployment.succeed!
end
+
+ it 'executes Deployments::FinishedWorker asynchronously' do
+ expect(Deployments::FinishedWorker)
+ .to receive(:perform_async).with(deployment.id)
+
+ deployment.succeed!
+ end
end
context 'when deployment failed' do
@@ -113,6 +122,13 @@ describe Deployment do
expect(deployment.finished_at).to be_like_time(Time.now)
end
end
+
+ it 'executes Deployments::FinishedWorker asynchronously' do
+ expect(Deployments::FinishedWorker)
+ .to receive(:perform_async).with(deployment.id)
+
+ deployment.drop!
+ end
end
context 'when deployment was canceled' do
@@ -126,6 +142,13 @@ describe Deployment do
expect(deployment.finished_at).to be_like_time(Time.now)
end
end
+
+ it 'executes Deployments::FinishedWorker asynchronously' do
+ expect(Deployments::FinishedWorker)
+ .to receive(:perform_async).with(deployment.id)
+
+ deployment.cancel!
+ end
end
end
@@ -356,4 +379,38 @@ describe Deployment do
end
end
end
+
+ describe '#cluster' do
+ let(:deployment) { create(:deployment) }
+ let(:project) { deployment.project }
+ let(:environment) { deployment.environment }
+
+ subject { deployment.cluster }
+
+ before do
+ expect(project).to receive(:deployment_platform)
+ .with(environment: environment.name).and_call_original
+ end
+
+ context 'project has no deployment platform' do
+ before do
+ expect(project.clusters).to be_empty
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'project uses the kubernetes service for deployments' do
+ let!(:service) { create(:kubernetes_service, project: project) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'project has a deployment platform' do
+ let!(:cluster) { create(:cluster, projects: [project]) }
+ let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) }
+
+ it { is_expected.to eq cluster }
+ end
+ end
end
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 50b19000799..cfeb4382927 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffDiscussion do
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index fda00a693f0..d9e1fe4b165 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffNote do
@@ -319,6 +321,14 @@ describe DiffNote do
end
describe '#supports_suggestion?' do
+ context 'when noteable does not exist' do
+ it 'returns false' do
+ allow(subject).to receive(:noteable) { nil }
+
+ expect(subject.supports_suggestion?).to be(false)
+ end
+ end
+
context 'when noteable does not support suggestions' do
it 'returns false' do
allow(subject.noteable).to receive(:supports_suggestion?) { false }
@@ -336,6 +346,16 @@ describe DiffNote do
end
end
+ describe '#banzai_render_context' do
+ let(:note) { create(:diff_note_on_merge_request) }
+
+ it 'includes expected context' do
+ context = note.banzai_render_context(:note)
+
+ expect(context).to include(suggestions_filter_enabled: true, noteable: note.noteable, project: note.project)
+ end
+ end
+
describe "image diff notes" do
subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) }
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index f4efe5a7b3a..b8bdeb781dc 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffViewer::Base do
diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb
index 86b14b6ebf3..27de0584b8a 100644
--- a/spec/models/diff_viewer/server_side_spec.rb
+++ b/spec/models/diff_viewer/server_side_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffViewer::ServerSide do
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index a46f7ed6507..0d02165787a 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussion do
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index 47eb0717c0c..cae88f39660 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Email do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 2d554326f05..7233d2454c6 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Environment do
let(:project) { create(:project, :stubbed_repository) }
subject(:environment) { create(:environment, project: project) }
- it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:project).required }
it { is_expected.to have_many(:deployments) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
@@ -164,6 +166,28 @@ describe Environment do
end
end
+ describe '#name_without_type' do
+ context 'when it is inside a folder' do
+ subject(:environment) do
+ create(:environment, name: 'staging/review-1')
+ end
+
+ it 'returns name without folder' do
+ expect(environment.name_without_type).to eq 'review-1'
+ end
+ end
+
+ context 'when the environment if a top-level item itself' do
+ subject(:environment) do
+ create(:environment, name: 'production')
+ end
+
+ it 'returns full name' do
+ expect(environment.name_without_type).to eq 'production'
+ end
+ end
+ end
+
describe '#nullify_external_url' do
it 'replaces a blank url with nil' do
env = build(:environment, external_url: "")
@@ -568,7 +592,7 @@ describe Environment do
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
it 'returns the terminals from the deployment service' do
- expect(project.deployment_platform)
+ expect(environment.deployment_platform)
.to receive(:terminals).with(environment)
.and_return(:fake_terminals)
@@ -661,7 +685,8 @@ describe Environment do
describe '#additional_metrics' do
let(:project) { create(:prometheus_project) }
- subject { environment.additional_metrics }
+ let(:metric_params) { [] }
+ subject { environment.additional_metrics(*metric_params) }
context 'when the environment has additional metrics' do
before do
@@ -669,12 +694,26 @@ describe Environment do
end
it 'returns the additional metrics from the deployment service' do
- expect(environment.prometheus_adapter).to receive(:query)
- .with(:additional_metrics_environment, environment)
- .and_return(:fake_metrics)
+ expect(environment.prometheus_adapter)
+ .to receive(:query)
+ .with(:additional_metrics_environment, environment)
+ .and_return(:fake_metrics)
is_expected.to eq(:fake_metrics)
end
+
+ context 'when time window arguments are provided' do
+ let(:metric_params) { [1552642245.067, Time.now] }
+
+ it 'queries with the expected parameters' do
+ expect(environment.prometheus_adapter)
+ .to receive(:query)
+ .with(:additional_metrics_environment, environment, *metric_params.map(&:to_f))
+ .and_return(:fake_metrics)
+
+ is_expected.to eq(:fake_metrics)
+ end
+ end
end
context 'when the environment does not have metrics' do
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 9da16dea929..c503c35305f 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EnvironmentStatus do
@@ -64,8 +66,8 @@ describe EnvironmentStatus do
end
describe '.for_merge_request' do
- let(:admin) { create(:admin) }
- let(:pipeline) { create(:ci_pipeline, sha: sha) }
+ let(:admin) { create(:admin) }
+ let!(:pipeline) { create(:ci_pipeline, sha: sha, merge_requests_as_head_pipeline: [merge_request]) }
it 'is based on merge_request.diff_head_sha' do
expect(merge_request).to receive(:diff_head_sha)
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index 076ccc96041..21e381d9fb7 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -62,11 +62,32 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
context 'URL path' do
- it 'fails validation with wrong path' do
+ it 'fails validation without api/0/projects' do
subject.api_url = 'http://gitlab.com/project1/something'
expect(subject).not_to be_valid
- expect(subject.errors.messages[:api_url]).to include('path needs to start with /api/0/projects')
+ expect(subject.errors.messages[:api_url]).to include('is invalid')
+ end
+
+ it 'fails validation without org and project slugs' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:project]).to include('is a required field')
+ end
+
+ it 'fails validation when api_url has extra parts' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/org/proj/something'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include("is invalid")
+ end
+
+ it 'fails validation when api_url has less parts' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/org'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include("is invalid")
end
it 'passes validation with correct path' do
@@ -146,7 +167,7 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
end
- context 'when sentry client raises exception' do
+ context 'when sentry client raises Sentry::Client::Error' do
let(:sentry_client) { spy(:sentry_client) }
before do
@@ -158,7 +179,31 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
it 'returns error' do
- expect(result).to eq(error: 'error message')
+ expect(result).to eq(
+ error: 'error message',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE
+ )
+ expect(subject).to have_received(:sentry_client)
+ expect(sentry_client).to have_received(:list_issues)
+ end
+ end
+
+ context 'when sentry client raises Sentry::Client::MissingKeysError' do
+ let(:sentry_client) { spy(:sentry_client) }
+
+ before do
+ synchronous_reactive_cache(subject)
+
+ allow(subject).to receive(:sentry_client).and_return(sentry_client)
+ allow(sentry_client).to receive(:list_issues).with(opts)
+ .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"')
+ end
+
+ it 'returns error' do
+ expect(result).to eq(
+ error: 'Sentry API response is missing keys. key not found: "id"',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ )
expect(subject).to have_received(:sentry_client)
expect(sentry_client).to have_received(:list_issues)
end
@@ -275,6 +320,16 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
expect(api_url).to eq(':::')
end
+
+ it 'returns nil when api_host is blank' do
+ api_url = described_class.build_api_url_from(
+ api_host: '',
+ organization_slug: 'org-slug',
+ project_slug: 'proj-slug'
+ )
+
+ expect(api_url).to be_nil
+ end
end
describe '#api_host' do
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 6078f429bdc..efe511042c3 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventCollection do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index ce4f8ee4705..62663c247d1 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Event do
@@ -86,7 +88,7 @@ describe Event do
let(:event) { create_push_event(project, user) }
it do
- expect(event.push?).to be_truthy
+ expect(event.push_action?).to be_truthy
expect(event.visible_to_user?(user)).to be_truthy
expect(event.visible_to_user?(nil)).to be_falsey
expect(event.tag?).to be_falsey
@@ -261,7 +263,7 @@ describe Event do
context 'merge request diff note event' do
let(:project) { create(:project, :public) }
- let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) }
+ let(:merge_request) { create(:merge_request, source_project: project, author: author, assignees: [assignee]) }
let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) }
let(:target) { note_on_merge_request }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 83ba22caa03..9d064d458f0 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExternalIssue do
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index 60d04562e6c..eab758248de 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ForkNetworkMember do
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index a43baf1820a..5ec0f8d6b02 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ForkNetwork do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index a3e68d2e646..c851810ffb3 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GenericCommitStatus do
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index f93904065c7..9d901d01a52 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GlobalMilestone do
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 58a1d2e4ea2..479b39cd139 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe GpgKey do
diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb
index 3c86837f47f..51d2f9cb9ac 100644
--- a/spec/models/gpg_key_subkey_spec.rb
+++ b/spec/models/gpg_key_subkey_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe GpgKeySubkey do
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
index e90319c39b1..47c343edf0e 100644
--- a/spec/models/gpg_signature_spec.rb
+++ b/spec/models/gpg_signature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe GpgSignature do
diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb
index 7ecb2022567..7d60c74b62b 100644
--- a/spec/models/group_custom_attribute_spec.rb
+++ b/spec/models/group_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupCustomAttribute do
diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb
index d0fc1eaa3ec..a3a5c631c3d 100644
--- a/spec/models/group_label_spec.rb
+++ b/spec/models/group_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupLabel do
diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb
index fcc33cd95fe..01856870fe0 100644
--- a/spec/models/group_milestone_spec.rb
+++ b/spec/models/group_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupMilestone do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 9dc32a815d8..e6e7298a043 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Group do
@@ -362,6 +364,32 @@ describe Group do
it { expect(group.has_maintainer?(nil)).to be_falsey }
end
+ describe '#last_owner?' do
+ before do
+ @members = setup_group_members(group)
+ end
+
+ it { expect(group.last_owner?(@members[:owner])).to be_truthy }
+
+ context 'with two owners' do
+ before do
+ create(:group_member, :owner, group: group)
+ end
+
+ it { expect(group.last_owner?(@members[:owner])).to be_falsy }
+ end
+
+ context 'with owners from a parent', :postgresql do
+ before do
+ parent_group = create(:group)
+ create(:group_member, :owner, group: parent_group)
+ group.update(parent: parent_group)
+ end
+
+ it { expect(group.last_owner?(@members[:owner])).to be_falsy }
+ end
+ end
+
describe '#lfs_enabled?' do
context 'LFS enabled globally' do
before do
@@ -760,14 +788,14 @@ describe Group do
describe '#has_parent?' do
context 'when the group has a parent' do
- it 'should be truthy' do
+ it 'is truthy' do
group = create(:group, :nested)
expect(group.has_parent?).to be_truthy
end
end
context 'when the group has no parent' do
- it 'should be falsy' do
+ it 'is falsy' do
group = create(:group, parent: nil)
expect(group.has_parent?).to be_falsy
end
@@ -810,4 +838,133 @@ describe Group do
it { is_expected.to be_truthy }
end
end
+
+ describe '#first_auto_devops_config' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:group) { create(:group) }
+
+ subject { group.first_auto_devops_config }
+
+ where(:instance_value, :group_value, :config) do
+ # Instance level enabled
+ true | nil | { status: true, scope: :instance }
+ true | true | { status: true, scope: :group }
+ true | false | { status: false, scope: :group }
+
+ # Instance level disabled
+ false | nil | { status: false, scope: :instance }
+ false | true | { status: true, scope: :group }
+ false | false | { status: false, scope: :group }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(auto_devops_enabled: instance_value)
+
+ group.update_attribute(:auto_devops_enabled, group_value)
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with parent groups', :nested_groups do
+ where(:instance_value, :parent_value, :group_value, :config) do
+ # Instance level enabled
+ true | nil | nil | { status: true, scope: :instance }
+ true | nil | true | { status: true, scope: :group }
+ true | nil | false | { status: false, scope: :group }
+
+ true | true | nil | { status: true, scope: :group }
+ true | true | true | { status: true, scope: :group }
+ true | true | false | { status: false, scope: :group }
+
+ true | false | nil | { status: false, scope: :group }
+ true | false | true | { status: true, scope: :group }
+ true | false | false | { status: false, scope: :group }
+
+ # Instance level disable
+ false | nil | nil | { status: false, scope: :instance }
+ false | nil | true | { status: true, scope: :group }
+ false | nil | false | { status: false, scope: :group }
+
+ false | true | nil | { status: true, scope: :group }
+ false | true | true | { status: true, scope: :group }
+ false | true | false | { status: false, scope: :group }
+
+ false | false | nil | { status: false, scope: :group }
+ false | false | true | { status: true, scope: :group }
+ false | false | false | { status: false, scope: :group }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(auto_devops_enabled: instance_value)
+ parent = create(:group, auto_devops_enabled: parent_value)
+
+ group.update!(
+ auto_devops_enabled: group_value,
+ parent: parent
+ )
+ end
+
+ it { is_expected.to eq(config) }
+ end
+ end
+ end
+
+ describe '#auto_devops_enabled?' do
+ subject { group.auto_devops_enabled? }
+
+ context 'when auto devops is explicitly enabled on group' do
+ let(:group) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops is explicitly disabled on group' do
+ let(:group) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops is implicitly enabled or disabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+
+ group.update!(parent: parent_group)
+ end
+
+ context 'when auto devops is enabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_enabled) }
+ let(:subgroup) { create(:group, parent: root_group) }
+ let(:parent_group) { create(:group, parent: subgroup) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops is disabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_disabled) }
+ let(:subgroup) { create(:group, parent: root_group) }
+ let(:parent_group) { create(:group, parent: subgroup) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops is disabled on parent group and enabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_enabled) }
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: root_group) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
+
+ describe 'project_creation_level' do
+ it 'outputs the default one if it is nil' do
+ group = create(:group, project_creation_level: nil)
+
+ expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation)
+ end
+ end
end
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index fc30f3056e5..93862e98172 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Guest do
diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb
index df7edda2213..1249c793f7f 100644
--- a/spec/models/hooks/active_hook_filter_spec.rb
+++ b/spec/models/hooks/active_hook_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ActiveHookFilter do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 5dd31b1b5de..a945f0d1516 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectHook do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index e32eaafc13f..936c2fbad27 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ServiceHook do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index edd1cb455af..e0d4d2e4858 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe SystemHook do
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index 744a6ccae8b..f812149c9be 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe WebHookLog do
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index a308ac6e33a..fe08dc4f5e6 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WebHook do
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index e1a7a59dfd1..74ddc2d6284 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Identity do
diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb
index 58af84b8a08..18a714f4d98 100644
--- a/spec/models/import_export_upload_spec.rb
+++ b/spec/models/import_export_upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ImportExportUpload do
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index cb3d6c7cda2..43954511858 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InstanceConfiguration do
@@ -30,8 +32,8 @@ describe InstanceConfiguration do
end
def stub_pub_file(exist: true)
- path = 'spec/fixtures/ssh_host_example_key.pub'
- path << 'random' unless exist
+ path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random'
+
allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path))
end
end
@@ -80,6 +82,13 @@ describe InstanceConfiguration do
it 'returns the key artifacts_max_size' do
expect(gitlab_ci.keys).to include(:artifacts_max_size)
end
+
+ it 'returns the key artifacts_max_size with values' do
+ stub_application_setting(max_artifacts_size: 200)
+
+ expect(gitlab_ci[:artifacts_max_size][:default]).to eq(100.megabytes)
+ expect(gitlab_ci[:artifacts_max_size][:value]).to eq(200.megabytes)
+ end
end
end
end
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index d32f163f05b..806b4f61bd8 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InternalId do
@@ -91,7 +93,7 @@ describe InternalId do
before do
described_class.reset_column_information
# Project factory will also call the current_version
- expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
+ expect(ActiveRecord::Migrator).to receive(:current_version).at_least(:once).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
end
let(:init) { double('block') }
@@ -102,6 +104,66 @@ describe InternalId do
expect(init).to receive(:call).with(issue).and_return(val)
expect(subject).to eq(val + 1)
end
+
+ it 'always attempts to generate internal IDs in production mode' do
+ allow(Rails.env).to receive(:test?).and_return(false)
+ val = rand(1..100)
+ generator = double(generate: val)
+ expect(InternalId::InternalIdGenerator).to receive(:new).and_return(generator)
+
+ expect(subject).to eq(val)
+ end
+ end
+ end
+
+ describe '.reset' do
+ subject { described_class.reset(issue, scope, usage, value) }
+
+ context 'in the absence of a record' do
+ let(:value) { 2 }
+
+ it 'does not revert back the value' do
+ expect { subject }.not_to change { described_class.count }
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when valid iid is used to reset' do
+ let!(:value) { generate_next }
+
+ context 'and iid is a latest one' do
+ it 'does rewind and next generated value is the same' do
+ expect(subject).to be_truthy
+ expect(generate_next).to eq(value)
+ end
+ end
+
+ context 'and iid is not a latest one' do
+ it 'does not rewind' do
+ generate_next
+
+ expect(subject).to be_falsey
+ expect(generate_next).to be > value
+ end
+ end
+
+ def generate_next
+ described_class.generate_next(issue, scope, usage, init)
+ end
+ end
+
+ context 'with an insufficient schema version' do
+ let(:value) { 2 }
+
+ before do
+ described_class.reset_column_information
+ # Project factory will also call the current_version
+ expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
+ end
+
+ it 'does not reset any of the iids' do
+ expect(subject).to be_falsey
+ end
end
end
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index 1bf0ecb98ad..07858fe8a70 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issue::Metrics do
@@ -9,7 +11,7 @@ describe Issue::Metrics do
context "milestones" do
it "records the first time an issue is associated with a milestone" do
time = Time.now
- Timecop.freeze(time) { subject.update(milestone: create(:milestone)) }
+ Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) }
metrics = subject.metrics
expect(metrics).to be_present
@@ -18,9 +20,9 @@ describe Issue::Metrics do
it "does not record the second time an issue is associated with a milestone" do
time = Time.now
- Timecop.freeze(time) { subject.update(milestone: create(:milestone)) }
+ Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) }
Timecop.freeze(time + 2.hours) { subject.update(milestone: nil) }
- Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone)) }
+ Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone, project: project)) }
metrics = subject.metrics
expect(metrics).to be_present
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index 580a98193af..7fc635f100f 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueCollection do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6101df2e099..a5c7e9db2a1 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issue do
+ include ExternalAuthorizationServiceHelpers
+
describe "Associations" do
it { is_expected.to belong_to(:milestone) }
it { is_expected.to have_many(:assignees) }
@@ -51,6 +55,29 @@ describe Issue do
end
end
+ describe 'locking' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:lock_version) do
+ [
+ [0],
+ ["0"]
+ ]
+ end
+
+ with_them do
+ it 'works when an issue has a NULL lock_version' do
+ issue = create(:issue)
+
+ described_class.where(id: issue.id).update_all('lock_version = NULL')
+
+ issue.update!(lock_version: lock_version, title: 'locking test')
+
+ expect(issue.reload.title).to eq('locking test')
+ end
+ end
+ end
+
describe '#order_by_position_and_priority' do
let(:project) { create :project }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
@@ -66,6 +93,21 @@ describe Issue do
end
end
+ describe '#sort' do
+ let(:project) { create(:project) }
+
+ context "by relative_position" do
+ let!(:issue) { create(:issue, project: project) }
+ let!(:issue2) { create(:issue, project: project, relative_position: 2) }
+ let!(:issue3) { create(:issue, project: project, relative_position: 1) }
+
+ it "sorts asc with nulls at the end" do
+ issues = project.issues.sort_by_attribute('relative_position')
+ expect(issues).to eq([issue3, issue2, issue])
+ end
+ end
+ end
+
describe '#card_attributes' do
it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
@@ -777,4 +819,47 @@ describe Issue do
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end
+
+ context 'when an external authentication service' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ describe '#visible_to_user?' do
+ it 'is `false` when an external authorization service is enabled' do
+ issue = build(:issue, project: build(:project, :public))
+
+ expect(issue).not_to be_visible_to_user
+ end
+
+ it 'checks the external service to determine if an issue is readable by a user' do
+ project = build(:project, :public,
+ external_authorization_classification_label: 'a-label')
+ issue = build(:issue, project: project)
+ user = build(:user)
+
+ expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false }
+ expect(issue.visible_to_user?(user)).to be_falsy
+ end
+
+ it 'does not check the external service if a user does not have access to the project' do
+ project = build(:project, :private,
+ external_authorization_classification_label: 'a-label')
+ issue = build(:issue, project: project)
+ user = build(:user)
+
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+ expect(issue.visible_to_user?(user)).to be_falsy
+ end
+
+ it 'does not check the external webservice for admins' do
+ issue = build(:issue)
+ user = build(:admin)
+
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ issue.visible_to_user?(user)
+ end
+ end
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 06d26ef89f1..a0b6eff88d5 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Key, :mailer do
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index e2b49bc2de7..b160e72e759 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LabelLink do
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index 9dcb0f06b20..1a93468290f 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LabelPriority do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 3fc6c06b7fa..5174c590a10 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Label do
diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb
index dae97b69c84..49ea319fbd1 100644
--- a/spec/models/legacy_diff_discussion_spec.rb
+++ b/spec/models/legacy_diff_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LegacyDiffDiscussion do
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index 88838b127d2..effd8b08124 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe LfsDownloadObject do
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index 41ca1578b94..aa64d66944b 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe LfsFileLock do
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 3f929710862..3d4d4b7d795 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LfsObject do
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index 0a3180f43e8..3e86ee38566 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LfsObjectsProject do
diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb
index dd912eefac1..7037277e580 100644
--- a/spec/models/license_template_spec.rb
+++ b/spec/models/license_template_spec.rb
@@ -1,16 +1,19 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
- lazy = build_template(-> { 'bar' })
- content = lazy.content
+ content_proc = -> { 'bar' }
+ expect(content_proc).to receive(:call).once.and_call_original
+
+ lazy = build_template(content_proc)
- expect(content).to eq('bar')
- expect(content.object_id).to eq(lazy.content.object_id)
+ expect(lazy.content).to eq('bar')
- content.replace('foo')
- expect(lazy.content).to eq('foo')
+ # Subsequent calls should not call proc again
+ expect(lazy.content).to eq('bar')
end
it 'returns a string if provided' do
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index a51580f8292..18d4549977c 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe List do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 188beac1582..782a84f922b 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Member do
@@ -68,6 +70,16 @@ describe Member do
expect(child_member).not_to be_valid
end
+ # Membership in a subgroup confers certain access rights, such as being
+ # able to merge or push code to protected branches.
+ it "is valid with an equal level" do
+ child_member.access_level = GroupMember::DEVELOPER
+
+ child_member.validate
+
+ expect(child_member).to be_valid
+ end
+
it "is valid with a higher level" do
child_member.access_level = GroupMember::MAINTAINER
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index a3451c67bd8..f227abd3dae 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -1,6 +1,24 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupMember do
+ describe '.count_users_by_group_id' do
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
+ end
+ end
+
describe '.access_level_roles' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner)
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 36bfff2c339..497764b6825 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectMember do
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 02ff7839739..49573af0fed 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequest::Metrics do
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 10487190a44..ab2aadf7d88 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe MergeRequestDiffCommit do
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index faa47660a74..66957c24fdc 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe MergeRequestDiffFile do
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 1849d3bac12..a53add67066 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1,8 +1,24 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestDiff do
+ include RepoHelpers
+
let(:diff_with_commits) { create(:merge_request).merge_request_diff }
+ describe 'validations' do
+ subject { diff_with_commits }
+
+ it 'checks sha format of base_commit_sha, head_commit_sha and start_commit_sha' do
+ subject.base_commit_sha = subject.head_commit_sha = subject.start_commit_sha = 'foobar'
+
+ expect(subject.valid?).to be false
+ expect(subject.errors.count).to eq 3
+ expect(subject.errors).to all(include('is not a valid SHA'))
+ end
+ end
+
describe 'create new record' do
subject { diff_with_commits }
@@ -37,7 +53,104 @@ describe MergeRequestDiff do
end
end
- describe '#latest' do
+ describe '.ids_for_external_storage_migration' do
+ set(:merge_request) { create(:merge_request) }
+ set(:outdated) { merge_request.merge_request_diff }
+ set(:latest) { merge_request.create_merge_request_diff }
+
+ set(:closed_mr) { create(:merge_request, :closed_last_month) }
+ let(:closed) { closed_mr.merge_request_diff }
+
+ set(:merged_mr) { create(:merge_request, :merged_last_month) }
+ let(:merged) { merged_mr.merge_request_diff }
+
+ set(:recently_closed_mr) { create(:merge_request, :closed) }
+ let(:closed_recently) { recently_closed_mr.merge_request_diff }
+
+ set(:recently_merged_mr) { create(:merge_request, :merged) }
+ let(:merged_recently) { recently_merged_mr.merge_request_diff }
+
+ before do
+ merge_request.update!(latest_merge_request_diff: latest)
+ end
+
+ subject { described_class.ids_for_external_storage_migration(limit: 1000) }
+
+ context 'external diffs are disabled' do
+ before do
+ stub_external_diffs_setting(enabled: false)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'external diffs are misconfigured' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'every second tuesday')
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'external diffs are enabled unconditionally' do
+ before do
+ stub_external_diffs_setting(enabled: true)
+ end
+
+ it { is_expected.to contain_exactly(outdated.id, latest.id, closed.id, merged.id, closed_recently.id, merged_recently.id) }
+ end
+
+ context 'external diffs are enabled for outdated diffs' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'outdated')
+ end
+
+ it 'returns records for outdated merge request versions' do
+ is_expected.to contain_exactly(outdated.id, closed.id, merged.id)
+ end
+ end
+
+ context 'with limit' do
+ it 'respects the limit' do
+ stub_external_diffs_setting(enabled: true)
+
+ expect(described_class.ids_for_external_storage_migration(limit: 3).count).to eq(3)
+ end
+ end
+ end
+
+ describe '#migrate_files_to_external_storage!' do
+ let(:diff) { create(:merge_request).merge_request_diff }
+
+ it 'converts from in-database to external storage' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+ expect(diff).to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+
+ expect(diff).to be_stored_externally
+ end
+
+ it 'does nothing with an external diff' do
+ stub_external_diffs_setting(enabled: true)
+
+ expect(diff).to be_stored_externally
+ expect(diff).not_to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+ end
+
+ it 'does nothing if external diffs are disabled' do
+ expect(diff).not_to be_stored_externally
+ expect(diff).not_to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+ end
+ end
+
+ describe '#latest?' do
let!(:mr) { create(:merge_request, :with_diffs) }
let!(:first_diff) { mr.merge_request_diff }
let!(:last_diff) { mr.create_merge_request_diff }
@@ -78,7 +191,7 @@ describe MergeRequestDiff do
it 'returns persisted diffs if cannot compare with diff refs' do
expect(diff).to receive(:load_diffs).and_call_original
- diff.update!(head_commit_sha: 'invalid-sha')
+ diff.update!(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex))
diff.diffs.diff_files
end
@@ -182,6 +295,25 @@ describe MergeRequestDiff do
expect(diff_file).to be_binary
expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [path]).to_a.first.diff)
end
+
+ context 'with diffs that contain a null byte' do
+ let(:filename) { 'test-null.txt' }
+ let(:content) { "a" * 10000 + "\x00" }
+ let(:project) { create(:project, :repository) }
+ let(:branch) { 'null-data' }
+ let(:target_branch) { 'master' }
+
+ it 'saves diffs correctly' do
+ create_file_in_repo(project, target_branch, branch, filename, content)
+
+ mr_diff = create(:merge_request, target_project: project, source_project: project, source_branch: branch, target_branch: target_branch).merge_request_diff
+ diff_file = mr_diff.merge_request_diff_files.find_by(new_path: filename)
+
+ expect(diff_file).to be_binary
+ expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [filename]).to_a.first.diff)
+ expect(diff_file.diff).to include(content)
+ end
+ end
end
end
@@ -189,12 +321,56 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
- describe 'external diffs configured' do
+ describe 'external diffs always enabled' do
before do
- stub_external_diffs_setting(enabled: true)
+ stub_external_diffs_setting(enabled: true, when: 'always')
+ end
+
+ include_examples 'merge request diffs'
+ end
+
+ describe 'exernal diffs enabled for outdated diffs' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'outdated')
end
include_examples 'merge request diffs'
+
+ it 'stores up-to-date diffs in the database' do
+ expect(diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for recently closed MRs in the database' do
+ mr = create(:merge_request, :closed)
+
+ expect(mr.merge_request_diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for recently merged MRs in the database' do
+ mr = create(:merge_request, :merged)
+
+ expect(mr.merge_request_diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for old MR versions in external storage' do
+ old_diff = diff
+ merge_request.create_merge_request_diff
+ old_diff.migrate_files_to_external_storage!
+
+ expect(old_diff).to be_stored_externally
+ end
+
+ it 'stores diffs for old closed MRs in external storage' do
+ mr = create(:merge_request, :closed_last_month)
+
+ expect(mr.merge_request_diff).to be_stored_externally
+ end
+
+ it 'stores diffs for old merged MRs in external storage' do
+ mr = create(:merge_request, :merged_last_month)
+
+ expect(mr.merge_request_diff).to be_stored_externally
+ end
end
describe '#commit_shas' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 82a853a23b9..fc28c216b21 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequest do
@@ -11,7 +13,7 @@ describe MergeRequest do
it { is_expected.to belong_to(:target_project).class_name('Project') }
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
- it { is_expected.to belong_to(:assignee) }
+ it { is_expected.to have_many(:assignees).through(:merge_request_assignees) }
it { is_expected.to have_many(:merge_request_diffs) }
context 'for forks' do
@@ -29,6 +31,29 @@ describe MergeRequest do
end
end
+ describe 'locking' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:lock_version) do
+ [
+ [0],
+ ["0"]
+ ]
+ end
+
+ with_them do
+ it 'works when a merge request has a NULL lock_version' do
+ merge_request = create(:merge_request)
+
+ described_class.where(id: merge_request.id).update_all('lock_version = NULL')
+
+ merge_request.update!(lock_version: lock_version, title: 'locking test')
+
+ expect(merge_request.reload.title).to eq('locking test')
+ end
+ end
+ end
+
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
@@ -84,32 +109,27 @@ describe MergeRequest do
describe '#default_squash_commit_message' do
let(:project) { subject.project }
-
- def commit_collection(commit_hashes)
- raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) }
-
- CommitCollection.new(project, raw_commits)
- end
+ let(:is_multiline) { -> (c) { c.description.present? } }
+ let(:multiline_commits) { subject.commits.select(&is_multiline) }
+ let(:singleline_commits) { subject.commits.reject(&is_multiline) }
it 'returns the oldest multiline commit message' do
- commits = commit_collection([
- { message: 'Singleline', parent_ids: [] },
- { message: "Second multiline\nCommit message", parent_ids: [] },
- { message: "First multiline\nCommit message", parent_ids: [] }
- ])
-
- expect(subject).to receive(:commits).and_return(commits)
-
- expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message")
+ expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message)
end
it 'returns the merge request title if there are no multiline commits' do
- commits = commit_collection([
- { message: 'Singleline', parent_ids: [] }
- ])
+ expect(subject).to receive(:commits).and_return(
+ CommitCollection.new(project, singleline_commits)
+ )
- expect(subject).to receive(:commits).and_return(commits)
+ expect(subject.default_squash_commit_message).to eq(subject.title)
+ end
+ it 'does not return commit messages from multiline merge commits' do
+ collection = CommitCollection.new(project, multiline_commits).enrich!
+
+ expect(collection.commits).to all( receive(:merge_commit?).and_return(true) )
+ expect(subject).to receive(:commits).and_return(collection)
expect(subject.default_squash_commit_message).to eq(subject.title)
end
end
@@ -153,6 +173,42 @@ describe MergeRequest do
end
end
+ context 'for branch' do
+ before do
+ stub_feature_flags(stricter_mr_branch_name: false)
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:branch_name, :valid) do
+ 'foo' | true
+ 'foo:bar' | false
+ '+foo:bar' | false
+ 'foo bar' | false
+ '-foo' | false
+ 'HEAD' | true
+ 'refs/heads/master' | true
+ end
+
+ with_them do
+ it "validates source_branch" do
+ subject = build(:merge_request, source_branch: branch_name, target_branch: 'master')
+
+ subject.valid?
+
+ expect(subject.errors.added?(:source_branch)).to eq(!valid)
+ end
+
+ it "validates target_branch" do
+ subject = build(:merge_request, source_branch: 'master', target_branch: branch_name)
+
+ subject.valid?
+
+ expect(subject.errors.added?(:target_branch)).to eq(!valid)
+ end
+ end
+ end
+
context 'for forks' do
let(:project) { create(:project) }
let(:fork1) { fork_project(project) }
@@ -270,6 +326,25 @@ describe MergeRequest do
end
end
+ describe '.recent_target_branches' do
+ let(:project) { create(:project) }
+ let!(:merge_request1) { create(:merge_request, :opened, source_project: project, target_branch: 'feature') }
+ let!(:merge_request2) { create(:merge_request, :closed, source_project: project, target_branch: 'merge-test') }
+ let!(:merge_request3) { create(:merge_request, :opened, source_project: project, target_branch: 'fix') }
+ let!(:merge_request4) { create(:merge_request, :closed, source_project: project, target_branch: 'feature') }
+
+ before do
+ merge_request1.update_columns(updated_at: 1.day.since)
+ merge_request2.update_columns(updated_at: 2.days.since)
+ merge_request3.update_columns(updated_at: 3.days.since)
+ merge_request4.update_columns(updated_at: 4.days.since)
+ end
+
+ it 'returns target branches sort by updated at desc' do
+ expect(described_class.recent_target_branches).to match_array(['feature', 'merge-test', 'fix'])
+ end
+ end
+
describe '#target_branch_sha' do
let(:project) { create(:project, :repository) }
@@ -296,34 +371,18 @@ describe MergeRequest do
describe '#card_attributes' do
it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
- allow(subject).to receive(:assignee).and_return(nil)
+ allow(subject).to receive(:assignees).and_return([])
expect(subject.card_attributes)
- .to eq({ 'Author' => 'Robert', 'Assignee' => nil })
+ .to eq({ 'Author' => 'Robert', 'Assignee' => "" })
end
- it 'includes the assignee name' do
+ it 'includes the assignees name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
- allow(subject).to receive(:assignee).and_return(double(name: 'Douwe'))
+ allow(subject).to receive(:assignees).and_return([double(name: 'Douwe'), double(name: 'Robert')])
expect(subject.card_attributes)
- .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
- end
- end
-
- describe '#assignee_ids' do
- it 'returns an array of the assigned user id' do
- subject.assignee_id = 123
-
- expect(subject.assignee_ids).to eq([123])
- end
- end
-
- describe '#assignee_ids=' do
- it 'sets assignee_id to the last id in the array' do
- subject.assignee_ids = [123, 456]
-
- expect(subject.assignee_id).to eq(456)
+ .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe and Robert' })
end
end
@@ -331,7 +390,7 @@ describe MergeRequest do
let(:user) { create(:user) }
it 'returns true for a user that is assigned to a merge request' do
- subject.assignee = user
+ subject.assignees = [user]
expect(subject.assignee_or_author?(user)).to eq(true)
end
@@ -435,7 +494,6 @@ describe MergeRequest do
it 'does not cache issues from external trackers' do
issue = ExternalIssue.new('JIRA-123', subject.project)
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
-
allow(subject).to receive(:commits).and_return([commit])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error
@@ -765,6 +823,14 @@ describe MergeRequest do
expect(merge_request.commits).not_to be_empty
expect(merge_request.related_notes.count).to eq(3)
end
+
+ it "excludes system notes for commits" do
+ system_note = create(:note_on_commit, :system, commit_id: merge_request.commits.first.id,
+ project: merge_request.project)
+
+ expect(merge_request.related_notes.count).to eq(2)
+ expect(merge_request.related_notes).not_to include(system_note)
+ end
end
describe '#for_fork?' do
@@ -1008,47 +1074,31 @@ describe MergeRequest do
end
end
- describe "#reset_merge_when_pipeline_succeeds" do
- let(:merge_if_green) do
- create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user),
- merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
- end
+ describe "#auto_merge_strategy" do
+ subject { merge_request.auto_merge_strategy }
- it "sets the item to false" do
- merge_if_green.reset_merge_when_pipeline_succeeds
- merge_if_green.reload
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
- expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey
- expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
- expect(merge_if_green.merge_params["commit_message"]).to be_nil
- end
- end
+ it { is_expected.to eq('merge_when_pipeline_succeeds') }
- describe '#commit_authors' do
- it 'returns all the authors of every commit in the merge request' do
- users = subject.commits.map(&:author_email).uniq.map do |email|
- create(:user, email: email)
- end
+ context 'when auto merge is disabled' do
+ let(:merge_request) { create(:merge_request) }
- expect(subject.commit_authors).to match_array(users)
- end
-
- it 'returns an empty array if no author is associated with a user' do
- expect(subject.commit_authors).to be_empty
+ it { is_expected.to be_nil }
end
end
- describe '#authors' do
- it 'returns a list with all the commit authors in the merge request and author' do
- users = subject.commits.map(&:author_email).uniq.map do |email|
+ describe '#committers' do
+ it 'returns all the committers of every commit in the merge request' do
+ users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email|
create(:user, email: email)
end
- expect(subject.authors).to match_array([subject.author, *users])
+ expect(subject.committers).to match_array(users)
end
- it 'returns only the author if no committer is associated with a user' do
- expect(subject.authors).to contain_exactly(subject.author)
+ it 'returns an empty array if no committer is associated with a user' do
+ expect(subject.committers).to be_empty
end
end
@@ -1168,8 +1218,10 @@ describe MergeRequest do
end
context 'head pipeline' do
+ let(:diff_head_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
+
before do
- allow(subject).to receive(:diff_head_sha).and_return('lastsha')
+ allow(subject).to receive(:diff_head_sha).and_return(diff_head_sha)
end
describe '#head_pipeline' do
@@ -1197,7 +1249,15 @@ describe MergeRequest do
end
it 'returns the pipeline for MR with recent pipeline' do
- pipeline = create(:ci_empty_pipeline, sha: 'lastsha')
+ pipeline = create(:ci_empty_pipeline, sha: diff_head_sha)
+ subject.update_attribute(:head_pipeline_id, pipeline.id)
+
+ expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
+ expect(subject.actual_head_pipeline).to eq(pipeline)
+ end
+
+ it 'returns the pipeline for MR with recent merge request pipeline' do
+ pipeline = create(:ci_empty_pipeline, sha: 'merge-sha', source_sha: diff_head_sha)
subject.update_attribute(:head_pipeline_id, pipeline.id)
expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
@@ -1331,9 +1391,9 @@ describe MergeRequest do
sha: shas.second)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.second,
@@ -1357,7 +1417,7 @@ describe MergeRequest do
it 'returns merge request pipeline first' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline,
branch_pipeline])
end
@@ -1370,9 +1430,9 @@ describe MergeRequest do
sha: shas.first)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.first,
@@ -1381,8 +1441,8 @@ describe MergeRequest do
it 'returns merge request pipelines first' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline_2,
- merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline_2,
+ detached_merge_request_pipeline,
branch_pipeline_2,
branch_pipeline])
end
@@ -1397,9 +1457,9 @@ describe MergeRequest do
sha: shas.first)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.first,
@@ -1420,16 +1480,35 @@ describe MergeRequest do
it 'returns only related merge request pipelines' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline,
branch_pipeline_2,
branch_pipeline])
expect(merge_request_2.all_pipelines)
- .to eq([merge_request_pipeline_2,
+ .to eq([detached_merge_request_pipeline_2,
branch_pipeline_2,
branch_pipeline])
end
end
+
+ context 'when detached merge request pipeline is run on head ref of the merge request' do
+ let!(:detached_merge_request_pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: project,
+ ref: merge_request.ref_path,
+ sha: shas.second,
+ merge_request: merge_request)
+ end
+
+ it 'sets the head ref of the merge request to the pipeline ref' do
+ expect(detached_merge_request_pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
+ end
+
+ it 'includes the detached merge request pipeline even though the ref is custom path' do
+ expect(merge_request.all_pipelines).to include(detached_merge_request_pipeline)
+ end
+ end
end
end
@@ -1470,6 +1549,37 @@ describe MergeRequest do
end
end
+ context 'when detached merge request pipeline is run on head ref of the merge request' do
+ let!(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: merge_request.source_project,
+ ref: merge_request.ref_path,
+ sha: sha,
+ merge_request: merge_request)
+ end
+
+ let(:sha) { merge_request.diff_head_sha }
+
+ it 'sets the head ref of the merge request to the pipeline ref' do
+ expect(pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
+ end
+
+ it 'updates correctly even though the target branch name of the merge request is different from the pipeline ref' do
+ expect { subject }
+ .to change { merge_request.reload.head_pipeline }
+ .from(nil).to(pipeline)
+ end
+
+ context 'when sha is not HEAD of the source branch' do
+ let(:sha) { merge_request.diff_base_sha }
+
+ it 'does not update head pipeline' do
+ expect { subject }.not_to change { merge_request.reload.head_pipeline }
+ end
+ end
+ end
+
context 'when there are no pipelines with the diff head sha' do
it 'does not update the head pipeline' do
expect { subject }
@@ -1855,15 +1965,14 @@ describe MergeRequest do
it 'updates when assignees change' do
user1 = create(:user)
user2 = create(:user)
- mr = create(:merge_request, assignee: user1)
+ mr = create(:merge_request, assignees: [user1])
mr.project.add_developer(user1)
mr.project.add_developer(user2)
expect(user1.assigned_open_merge_requests_count).to eq(1)
expect(user2.assigned_open_merge_requests_count).to eq(0)
- mr.assignee = user2
- mr.save
+ mr.assignees = [user2]
expect(user1.assigned_open_merge_requests_count).to eq(0)
expect(user2.assigned_open_merge_requests_count).to eq(1)
@@ -1887,57 +1996,6 @@ describe MergeRequest do
end
end
- describe '#check_if_can_be_merged' do
- let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
-
- shared_examples 'checking if can be merged' do
- context 'when it is not broken and has no conflicts' do
- before do
- allow(subject).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?).and_return(true)
- end
-
- it 'is marked as mergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
- end
- end
-
- context 'when broken' do
- before do
- allow(subject).to receive(:broken?) { true }
- allow(project.repository).to receive(:can_be_merged?).and_return(false)
- end
-
- it 'becomes unmergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
- end
- end
-
- context 'when it has conflicts' do
- before do
- allow(subject).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?).and_return(false)
- end
-
- it 'becomes unmergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
- end
- end
- end
-
- context 'when merge_status is unchecked' do
- subject { create(:merge_request, source_project: project, merge_status: :unchecked) }
-
- it_behaves_like 'checking if can be merged'
- end
-
- context 'when merge_status is unchecked' do
- subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) }
-
- it_behaves_like 'checking if can be merged'
- end
- end
-
describe '#mergeable?' do
let(:project) { create(:project) }
@@ -1951,7 +2009,7 @@ describe MergeRequest do
it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
allow(subject).to receive(:mergeable_state?) { true }
- expect(subject).to receive(:check_if_can_be_merged)
+ expect(subject).to receive(:check_mergeability)
expect(subject).to receive(:can_be_merged?) { true }
expect(subject.mergeable?).to be_truthy
@@ -1965,7 +2023,7 @@ describe MergeRequest do
it 'checks if merge request can be merged' do
allow(subject).to receive(:mergeable_ci_state?) { true }
- expect(subject).to receive(:check_if_can_be_merged)
+ expect(subject).to receive(:check_mergeability)
subject.mergeable?
end
@@ -2071,7 +2129,7 @@ describe MergeRequest do
end
context 'when merges are not restricted to green builds' do
- subject { build(:merge_request, target_project: build(:project, only_allow_merge_if_pipeline_succeeds: false)) }
+ subject { build(:merge_request, target_project: create(:project, only_allow_merge_if_pipeline_succeeds: false)) }
context 'and a failed pipeline is associated' do
before do
@@ -2210,6 +2268,50 @@ describe MergeRequest do
end
end
+ describe "#environments" do
+ subject { merge_request.environments }
+
+ let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
+ let(:project) { merge_request.project }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request, project: project,
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let!(:job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) }
+
+ it 'returns environments' do
+ is_expected.to eq(pipeline.environments)
+ expect(subject.count).to be(1)
+ end
+
+ context 'when pipeline is not associated with environments' do
+ let!(:job) { create(:ci_build, pipeline: pipeline, project: project) }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is not a pipeline for merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ it 'returns empty relation' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe "#reload_diff" do
it 'calls MergeRequests::ReloadDiffsService#execute with correct params' do
user = create(:user)
@@ -2613,14 +2715,21 @@ describe MergeRequest do
end
describe '#has_commits?' do
- before do
+ it 'returns true when merge request diff has commits' do
allow(subject.merge_request_diff).to receive(:commits_count)
.and_return(2)
- end
- it 'returns true when merge request diff has commits' do
expect(subject.has_commits?).to be_truthy
end
+
+ context 'when commits_count is nil' do
+ it 'returns false' do
+ allow(subject.merge_request_diff).to receive(:commits_count)
+ .and_return(nil)
+
+ expect(subject.has_commits?).to be_falsey
+ end
+ end
end
describe '#has_no_commits?' do
@@ -3016,4 +3125,32 @@ describe MergeRequest do
end
end
end
+
+ describe '.merge_request_ref?' do
+ subject { described_class.merge_request_ref?(ref) }
+
+ context 'when ref is ref name of a branch' do
+ let(:ref) { 'feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a branch' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/head' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when ref is merge ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/merge' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index af7e3d3a6c9..3704a2d468d 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestone do
@@ -29,12 +31,28 @@ describe Milestone do
end
describe 'start_date' do
- it 'adds an error when start_date is greated then due_date' do
+ it 'adds an error when start_date is greater then due_date' do
milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)
expect(milestone).not_to be_valid
expect(milestone.errors[:due_date]).to include("must be greater than start date")
end
+
+ it 'adds an error when start_date is greater than 9999-12-31' do
+ milestone = build(:milestone, start_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:start_date]).to include("date must not be after 9999-12-31")
+ end
+ end
+
+ describe 'due_date' do
+ it 'adds an error when due_date is greater than 9999-12-31' do
+ milestone = build(:milestone, due_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31")
+ end
end
end
@@ -164,38 +182,16 @@ describe Milestone do
end
end
- describe '#percent_complete' do
- before do
- allow(milestone).to receive_messages(
- closed_items_count: 3,
- total_items_count: 4
- )
- end
-
- it { expect(milestone.percent_complete(user)).to eq(75) }
- end
-
describe '#can_be_closed?' do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe '#total_items_count' do
- before do
- create :closed_issue, milestone: milestone, project: project
- create :merge_request, milestone: milestone
- end
-
- it 'returns total count of issues and merge requests assigned to milestone' do
- expect(milestone.total_items_count(user)).to eq 2
- end
- end
-
describe '#can_be_closed?' do
before do
- milestone = create :milestone
- create :closed_issue, milestone: milestone
+ milestone = create :milestone, project: project
+ create :closed_issue, milestone: milestone, project: project
- create :issue
+ create :issue, project: project
end
it 'returns true if milestone active and all nested issues closed' do
@@ -502,4 +498,20 @@ describe Milestone do
end
end
end
+
+ describe '.reference_pattern' do
+ subject { described_class.reference_pattern }
+
+ it { is_expected.to match('gitlab-org/gitlab-ce%123') }
+ it { is_expected.to match('gitlab-org/gitlab-ce%"my-milestone"') }
+ end
+
+ describe '.link_reference_pattern' do
+ subject { described_class.link_reference_pattern }
+
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/milestones/123") }
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/-/milestones/123") }
+ it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/issues/123") }
+ it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") }
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 475fbe56e4d..d80183af33e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Namespace do
@@ -61,6 +63,11 @@ describe Namespace do
end
end
+ describe 'delegate' do
+ it { is_expected.to delegate_method(:name).to(:owner).with_prefix.with_arguments(allow_nil: true) }
+ it { is_expected.to delegate_method(:avatar_url).to(:owner).with_arguments(allow_nil: true) }
+ end
+
describe "Respond to" do
it { is_expected.to respond_to(:human_name) }
it { is_expected.to respond_to(:to_param) }
@@ -139,20 +146,22 @@ describe Namespace do
create(:project,
namespace: namespace,
statistics: build(:project_statistics,
- storage_size: 606,
repository_size: 101,
+ wiki_size: 505,
lfs_objects_size: 202,
- build_artifacts_size: 303))
+ build_artifacts_size: 303,
+ packages_size: 404))
end
let(:project2) do
create(:project,
namespace: namespace,
statistics: build(:project_statistics,
- storage_size: 60,
repository_size: 10,
+ wiki_size: 50,
lfs_objects_size: 20,
- build_artifacts_size: 30))
+ build_artifacts_size: 30,
+ packages_size: 40))
end
it "sums all project storage counters in the namespace" do
@@ -160,10 +169,12 @@ describe Namespace do
project2
statistics = described_class.with_statistics.find(namespace.id)
- expect(statistics.storage_size).to eq 666
+ expect(statistics.storage_size).to eq 1665
expect(statistics.repository_size).to eq 111
+ expect(statistics.wiki_size).to eq 555
expect(statistics.lfs_objects_size).to eq 222
expect(statistics.build_artifacts_size).to eq 333
+ expect(statistics.packages_size).to eq 444
end
it "correctly handles namespaces without projects" do
@@ -171,8 +182,10 @@ describe Namespace do
expect(statistics.storage_size).to eq 0
expect(statistics.repository_size).to eq 0
+ expect(statistics.wiki_size).to eq 0
expect(statistics.lfs_objects_size).to eq 0
expect(statistics.build_artifacts_size).to eq 0
+ expect(statistics.packages_size).to eq 0
end
end
@@ -736,43 +749,90 @@ describe Namespace do
end
end
- describe '#full_path_was' do
+ describe '#full_path_before_last_save' do
context 'when the group has no parent' do
- it 'should return the path was' do
- group = create(:group, parent: nil)
- expect(group.full_path_was).to eq(group.path_was)
+ it 'returns the path before last save' do
+ group = create(:group)
+
+ group.update(parent: nil)
+
+ expect(group.full_path_before_last_save).to eq(group.path_before_last_save)
end
end
context 'when a parent is assigned to a group with no previous parent' do
- it 'should return the path was' do
+ it 'returns the path before last save' do
group = create(:group, parent: nil)
-
parent = create(:group)
- group.parent = parent
- expect(group.full_path_was).to eq("#{group.path_was}")
+ group.update(parent: parent)
+
+ expect(group.full_path_before_last_save).to eq("#{group.path_before_last_save}")
end
end
context 'when a parent is removed from the group' do
- it 'should return the parent full path' do
+ it 'returns the parent full path' do
parent = create(:group)
group = create(:group, parent: parent)
- group.parent = nil
- expect(group.full_path_was).to eq("#{parent.full_path}/#{group.path}")
+ group.update(parent: nil)
+
+ expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}")
end
end
context 'when changing parents' do
- it 'should return the previous parent full path' do
+ it 'returns the previous parent full path' do
parent = create(:group)
group = create(:group, parent: parent)
new_parent = create(:group)
- group.parent = new_parent
- expect(group.full_path_was).to eq("#{parent.full_path}/#{group.path}")
+
+ group.update(parent: new_parent)
+
+ expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}")
end
end
end
+
+ describe '#auto_devops_enabled' do
+ context 'with users' do
+ let(:user) { create(:user) }
+
+ subject { user.namespace.auto_devops_enabled? }
+
+ before do
+ user.namespace.update!(auto_devops_enabled: auto_devops_enabled)
+ end
+
+ context 'when auto devops is explicitly enabled' do
+ let(:auto_devops_enabled) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when auto devops is explicitly disabled' do
+ let(:auto_devops_enabled) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
+
+ describe '#user?' do
+ subject { namespace.user? }
+
+ context 'when type is a user' do
+ let(:user) { create(:user) }
+ let(:namespace) { user.namespace }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when type is a group' do
+ let(:namespace) { create(:group) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
end
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index c364dd6643b..232172fde76 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Network::Graph do
@@ -20,7 +22,7 @@ describe Network::Graph do
expect(commits).to all( be_kind_of(Network::Commit) )
end
- it 'it the commits by commit date (descending)' do
+ it 'sorts commits by commit date (descending)' do
# Remove duplicate timestamps because they make it harder to
# assert that the commits are sorted as expected.
commits = graph.commits.uniq(&:date)
diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb
index 591c1a89748..b15bedd257e 100644
--- a/spec/models/note_diff_file_spec.rb
+++ b/spec/models/note_diff_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe NoteDiffFile do
@@ -8,4 +10,31 @@ describe NoteDiffFile do
describe 'validations' do
it { is_expected.to validate_presence_of(:diff_note) }
end
+
+ describe '.referencing_sha' do
+ let!(:diff_note) { create(:diff_note_on_commit) }
+
+ let(:note_diff_file) { diff_note.note_diff_file }
+ let(:project) { diff_note.project }
+
+ it 'finds note diff files by project and sha' do
+ found = described_class.referencing_sha(diff_note.commit_id, project_id: project.id)
+
+ expect(found).to contain_exactly(note_diff_file)
+ end
+
+ it 'excludes note diff files with the wrong project' do
+ other_project = create(:project)
+
+ found = described_class.referencing_sha(diff_note.commit_id, project_id: other_project.id)
+
+ expect(found).to be_empty
+ end
+
+ it 'excludes note diff files with the wrong sha' do
+ found = described_class.referencing_sha(Gitlab::Git::BLANK_SHA, project_id: project.id)
+
+ expect(found).to be_empty
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 385b8a7959f..7a1ab20186a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Note do
@@ -208,6 +210,24 @@ describe Note do
end
end
+ describe "edited?" do
+ let(:note) { build(:note, updated_by_id: nil, created_at: Time.now, updated_at: Time.now + 5.hours) }
+
+ context "with updated_by" do
+ it "returns true" do
+ note.updated_by = build(:user)
+
+ expect(note.edited?).to be_truthy
+ end
+ end
+
+ context "without updated_by" do
+ it "returns false" do
+ expect(note.edited?).to be_falsy
+ end
+ end
+ end
+
describe "confidential?" do
it "delegates to noteable" do
issue_note = build(:note, :on_issue)
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 13fe47799ed..1b1ede6b14c 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotificationRecipient do
@@ -7,11 +9,43 @@ describe NotificationRecipient do
subject(:recipient) { described_class.new(user, :watch, target: target, project: project) }
- it 'denies access to a target when cross project access is denied' do
- allow(Ability).to receive(:allowed?).and_call_original
- expect(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ describe '#has_access?' do
+ before do
+ allow(user).to receive(:can?).and_call_original
+ end
+
+ context 'user cannot read cross project' do
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_cross_project).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
+
+ context 'user cannot read build' do
+ let(:target) { build(:ci_pipeline) }
+
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_build, target).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
+
+ context 'user cannot read commit' do
+ let(:target) { build(:commit) }
+
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_commit, target).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
+
+ context 'target has no policy' do
+ let(:target) { double.as_null_object }
- expect(recipient.has_access?).to be_falsy
+ it 'returns true' do
+ expect(recipient.has_access?).to eq true
+ end
+ end
end
context '#notification_setting' do
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index c8ab564e3bc..85128456918 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe NotificationSetting do
diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb
new file mode 100644
index 00000000000..4ffb4fc7389
--- /dev/null
+++ b/spec/models/pages_domain_acme_order_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainAcmeOrder do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.expired' do
+ let!(:not_expired_order) { create(:pages_domain_acme_order) }
+ let!(:expired_order) { create(:pages_domain_acme_order, :expired) }
+
+ it 'returns only expired orders' do
+ expect(described_class.count).to eq(2)
+ expect(described_class.expired).to eq([expired_order])
+ end
+ end
+
+ describe '.find_by_domain_and_token' do
+ let!(:domain) { create(:pages_domain, domain: 'test.com') }
+ let!(:acme_order) { create(:pages_domain_acme_order, challenge_token: 'righttoken', pages_domain: domain) }
+
+ where(:domain_name, :challenge_token, :present) do
+ 'test.com' | 'righttoken' | true
+ 'test.com' | 'wrongtoken' | false
+ 'test.org' | 'righttoken' | false
+ end
+
+ with_them do
+ subject { described_class.find_by_domain_and_token(domain_name, challenge_token).present? }
+
+ it { is_expected.to eq(present) }
+ end
+ end
+
+ subject { create(:pages_domain_acme_order) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:pages_domain) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:pages_domain) }
+ it { is_expected.to validate_presence_of(:expires_at) }
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:challenge_token) }
+ it { is_expected.to validate_presence_of(:challenge_file_content) }
+ it { is_expected.to validate_presence_of(:private_key) }
+ end
+end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 4b85c5e8720..fdc81359d34 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomain do
@@ -79,6 +81,17 @@ describe PagesDomain do
end
end
+ describe 'when certificate is specified' do
+ let(:domain) { build(:pages_domain) }
+
+ it 'saves validity time' do
+ domain.save
+
+ expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC"))
+ expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC"))
+ end
+ end
+
describe 'validate certificate' do
subject { domain }
@@ -342,4 +355,32 @@ describe PagesDomain do
end
end
end
+
+ describe '.for_removal' do
+ subject { described_class.for_removal }
+
+ context 'when domain is not schedule for removal' do
+ let!(:domain) { create :pages_domain }
+
+ it 'does not return domain' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when domain is scheduled for removal yesterday' do
+ let!(:domain) { create :pages_domain, remove_at: 1.day.ago }
+
+ it 'returns domain' do
+ is_expected.to eq([domain])
+ end
+ end
+
+ context 'when domain is scheduled for removal tomorrow' do
+ let!(:domain) { create :pages_domain, remove_at: 1.day.from_now }
+
+ it 'does not return domain' do
+ is_expected.to be_empty
+ end
+ end
+ end
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index c82ab9c9e62..e0e1101ffc6 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PersonalAccessToken do
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
index 112d4ab56fc..ae00f9df89e 100644
--- a/spec/models/pool_repository_spec.rb
+++ b/spec/models/pool_repository_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe PoolRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
- it { is_expected.to have_one(:source_project) }
+ it { is_expected.to belong_to(:source_project) }
it { is_expected.to have_many(:member_projects) }
end
@@ -24,14 +24,14 @@ describe PoolRepository do
end
end
- describe '#unlink_repository' do
+ describe '#mark_obsolete_if_last' do
let(:pool) { create(:pool_repository, :ready) }
context 'when the last member leaves' do
it 'schedules pool removal' do
expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original
- pool.unlink_repository(pool.source_project.repository)
+ pool.mark_obsolete_if_last(pool.source_project.repository)
end
end
@@ -40,7 +40,7 @@ describe PoolRepository do
create(:project, :repository, pool_repository: pool)
expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id)
- pool.unlink_repository(pool.source_project.repository)
+ pool.mark_obsolete_if_last(pool.source_project.repository)
end
end
end
diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb
index 99cd358f863..b327d360461 100644
--- a/spec/models/programming_language_spec.rb
+++ b/spec/models/programming_language_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProgrammingLanguage do
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index c289ee0859a..6f06fe4e55a 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectAuthorization do
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 7ff64c76e37..7bdd2367a68 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectAutoDevops do
@@ -12,65 +14,9 @@ describe ProjectAutoDevops do
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
- describe '#has_domain?' do
- context 'when domain is defined' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: 'domain.com') }
-
- it { expect(auto_devops).to have_domain }
- end
-
- context 'when domain is empty' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: '') }
-
- context 'when there is an instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
- end
-
- it { expect(auto_devops).to have_domain }
- end
-
- context 'when there is no instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
- end
-
- it { expect(auto_devops).not_to have_domain }
- end
- end
- end
-
describe '#predefined_variables' do
let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
- context 'when domain is defined' do
- let(:domain) { 'example.com' }
-
- it 'returns AUTO_DEVOPS_DOMAIN' do
- expect(auto_devops.predefined_variables).to include(domain_variable)
- end
- end
-
- context 'when domain is not defined' do
- let(:domain) { nil }
-
- context 'when there is an instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
- end
-
- it { expect(auto_devops.predefined_variables).to include(domain_variable) }
- end
-
- context 'when there is no instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
- end
-
- it { expect(auto_devops.predefined_variables).not_to include(domain_variable) }
- end
- end
-
context 'when deploy_strategy is manual' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) }
let(:expected_variables) do
@@ -103,10 +49,6 @@ describe ProjectAutoDevops do
.not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
end
end
-
- def domain_variable
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
- end
end
describe '#create_gitlab_deploy_token' do
@@ -115,7 +57,7 @@ describe ProjectAutoDevops do
context 'when the project is public' do
let(:project) { create(:project, :repository, :public) }
- it 'should not create a gitlab deploy token' do
+ it 'does not create a gitlab deploy token' do
expect do
auto_devops.save
end.not_to change { DeployToken.count }
@@ -125,7 +67,7 @@ describe ProjectAutoDevops do
context 'when the project is internal' do
let(:project) { create(:project, :repository, :internal) }
- it 'should create a gitlab deploy token' do
+ it 'creates a gitlab deploy token' do
expect do
auto_devops.save
end.to change { DeployToken.count }.by(1)
@@ -135,7 +77,7 @@ describe ProjectAutoDevops do
context 'when the project is private' do
let(:project) { create(:project, :repository, :private) }
- it 'should create a gitlab deploy token' do
+ it 'creates a gitlab deploy token' do
expect do
auto_devops.save
end.to change { DeployToken.count }.by(1)
@@ -146,7 +88,7 @@ describe ProjectAutoDevops do
let(:project) { create(:project, :repository, :internal) }
let(:auto_devops) { build(:project_auto_devops, project: project) }
- it 'should create a deploy token' do
+ it 'creates a deploy token' do
expect do
auto_devops.save
end.to change { DeployToken.count }.by(1)
@@ -157,7 +99,7 @@ describe ProjectAutoDevops do
let(:project) { create(:project, :repository, :internal) }
let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) }
- it 'should create a deploy token' do
+ it 'creates a deploy token' do
allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true)
expect do
@@ -170,7 +112,7 @@ describe ProjectAutoDevops do
let(:project) { create(:project, :repository, :internal) }
let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) }
- it 'should not create a deploy token' do
+ it 'does not create a deploy token' do
expect do
auto_devops.save
end.not_to change { DeployToken.count }
@@ -182,7 +124,7 @@ describe ProjectAutoDevops do
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
let(:auto_devops) { build(:project_auto_devops, project: project) }
- it 'should not create a deploy token' do
+ it 'does not create a deploy token' do
expect do
auto_devops.save
end.not_to change { DeployToken.count }
@@ -194,7 +136,7 @@ describe ProjectAutoDevops do
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) }
let(:auto_devops) { build(:project_auto_devops, project: project) }
- it 'should not create a deploy token' do
+ it 'does not create a deploy token' do
expect do
auto_devops.save
end.not_to change { DeployToken.count }
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index 4aa62028169..f596cee81dc 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -21,4 +21,32 @@ describe ProjectCiCdSetting do
2.times { described_class.available? }
end
end
+
+ describe 'validations' do
+ it 'validates default_git_depth is between 0 and 1000 or nil' do
+ expect(subject).to validate_numericality_of(:default_git_depth)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ .is_less_than_or_equal_to(1000)
+ .allow_nil
+ end
+ end
+
+ describe '#default_git_depth' do
+ let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
+
+ it 'sets default value for new records' do
+ project = create(:project)
+
+ expect(project.ci_cd_settings.default_git_depth).to eq(default_value)
+ end
+
+ it 'does not set default value if present' do
+ project = build(:project)
+ project.build_ci_cd_settings(default_git_depth: 0)
+ project.save!
+
+ expect(project.reload.ci_cd_settings.default_git_depth).to eq(0)
+ end
+ end
end
diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb
index 669de5506bc..80638676b49 100644
--- a/spec/models/project_custom_attribute_spec.rb
+++ b/spec/models/project_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectCustomAttribute do
diff --git a/spec/models/project_daily_statistic_spec.rb b/spec/models/project_daily_statistic_spec.rb
new file mode 100644
index 00000000000..86210af15d8
--- /dev/null
+++ b/spec/models/project_daily_statistic_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectDailyStatistic do
+ it { is_expected.to belong_to(:project) }
+end
diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb
index 9e2e40c2e8f..2a5fefc1ab0 100644
--- a/spec/models/project_deploy_token_spec.rb
+++ b/spec/models/project_deploy_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe ProjectDeployToken, type: :model do
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index fee7d65c217..50c9d5968ac 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectFeature do
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 5bea21427d4..dad5506900b 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectGroupLink do
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index e3b2d971419..472bf8f9713 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ProjectImportState, type: :model do
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 689d4e505e5..330aab9f856 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectLabel do
diff --git a/spec/models/project_metrics_setting_spec.rb b/spec/models/project_metrics_setting_spec.rb
new file mode 100644
index 00000000000..7df01625ba1
--- /dev/null
+++ b/spec/models/project_metrics_setting_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectMetricsSetting do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'Validations' do
+ context 'when external_dashboard_url is over 255 chars' do
+ before do
+ subject.external_dashboard_url = 'https://' + 'a' * 250
+ end
+
+ it 'fails validation' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:external_dashboard_url])
+ .to include('is too long (maximum is 255 characters)')
+ end
+ end
+
+ context 'with unsafe url' do
+ before do
+ subject.external_dashboard_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
+ end
+
+ it { is_expected.to be_invalid }
+ end
+
+ context 'non ascii chars in external_dashboard_url' do
+ before do
+ subject.external_dashboard_url = 'http://gitlab.com/api/0/projects/project1/something€'
+ end
+
+ it { is_expected.to be_invalid }
+ end
+
+ context 'internal url in external_dashboard_url' do
+ before do
+ subject.external_dashboard_url = 'http://192.168.1.1'
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'external_dashboard_url is blank' do
+ before do
+ subject.external_dashboard_url = ''
+ end
+
+ it { is_expected.to be_invalid }
+ end
+ end
+end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index e66109fd98f..18e839bcc64 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AsanaService do
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 5cb6d63659e..2c86c0ec7be 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AssemblaService do
+ include StubRequests
+
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -21,12 +25,12 @@ describe AssemblaService do
)
@sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
@api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret'
- WebMock.stub_request(:post, @api_url)
+ stub_full_request(@api_url, method: :post)
end
it "calls Assembla API" do
@assembla_service.execute(@sample_data)
- expect(WebMock).to have_requested(:post, @api_url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(@api_url)).with(
body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
).once
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index b880d90d28f..65d227a17f9 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
@@ -255,7 +258,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end
def stub_bamboo_request(url, status, body)
- WebMock.stub_request(:get, url).to_return(
+ stub_full_request(url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index 43f7bcb1a19..6818db48fee 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BugzillaService do
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 1615a93a4ca..ca196069055 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:project) { create(:project) }
@@ -108,10 +111,9 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do
body ||= %q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
- WebMock.stub_request(:get, buildkite_full_url).to_return(
- status: status,
- headers: { 'Content-Type' => 'application/json' },
- body: body
- )
+ stub_full_request(buildkite_full_url)
+ .to_return(status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body)
end
end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index ed8347edffd..0d3dd89e93b 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CampfireService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -47,39 +51,37 @@ describe CampfireService do
it "calls Campfire API to get a list of rooms and speak in a room" do
# make sure a valid list of rooms is returned
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
- WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
+
+ stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
+
# stub the speak request with the room id found in the previous request's response
speak_url = 'https://project-name.campfirenow.com/room/123/speak.json'
- WebMock.stub_request(:post, speak_url).with(basic_auth: @auth)
+ stub_full_request(speak_url, method: :post).with(basic_auth: @auth)
@campfire_service.execute(@sample_data)
- expect(WebMock).to have_requested(:get, @rooms_url).once
- expect(WebMock).to have_requested(:post, speak_url).with(
- body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/
- ).once
+ expect(WebMock).to have_requested(:get, stubbed_hostname(@rooms_url)).once
+ expect(WebMock).to have_requested(:post, stubbed_hostname(speak_url))
+ .with(body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/).once
end
it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
# return a list of rooms that do not contain a room named 'test-room'
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
- WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
+ stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
- # we want to make sure no request is sent to the /speak endpoint, here is a basic
- # regexp that matches this endpoint
- speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/.*/speak.json'
@campfire_service.execute(@sample_data)
- expect(WebMock).to have_requested(:get, @rooms_url).once
- expect(WebMock).not_to have_requested(:post, /#{speak_url}/)
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.9/rooms.json').once
+ expect(WebMock).not_to have_requested(:post, '*/room/.*/speak.json')
end
end
end
diff --git a/spec/models/project_services/chat_message/deployment_message_spec.rb b/spec/models/project_services/chat_message/deployment_message_spec.rb
new file mode 100644
index 00000000000..42c1689db3d
--- /dev/null
+++ b/spec/models/project_services/chat_message/deployment_message_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ChatMessage::DeploymentMessage do
+ describe '#pretext' do
+ it 'returns a message with the data returned by the deployment data builder' do
+ environment = create(:environment, name: "myenvironment")
+ project = create(:project, :repository)
+ commit = project.commit('HEAD')
+ deployment = create(:deployment, status: :success, environment: environment, project: project, sha: commit.sha)
+ data = Gitlab::DataBuilder::Deployment.build(deployment)
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq("Deploy to myenvironment succeeded")
+ end
+
+ it 'returns a message for a successful deployment' do
+ data = {
+ status: 'success',
+ environment: 'production'
+ }
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq('Deploy to production succeeded')
+ end
+
+ it 'returns a message for a failed deployment' do
+ data = {
+ status: 'failed',
+ environment: 'production'
+ }
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq('Deploy to production failed')
+ end
+
+ it 'returns a message for a canceled deployment' do
+ data = {
+ status: 'canceled',
+ environment: 'production'
+ }
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq('Deploy to production canceled')
+ end
+
+ it 'returns a message for a deployment to another environment' do
+ data = {
+ status: 'success',
+ environment: 'staging'
+ }
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq('Deploy to staging succeeded')
+ end
+
+ it 'returns a message for a deployment with any other status' do
+ data = {
+ status: 'unknown',
+ environment: 'staging'
+ }
+
+ message = described_class.new(data)
+
+ expect(message.pretext).to eq('Deploy to staging unknown')
+ end
+ end
+
+ describe '#attachments' do
+ def deployment_data(params)
+ {
+ object_kind: "deployment",
+ status: "success",
+ deployable_id: 3,
+ deployable_url: "deployable_url",
+ environment: "sandbox",
+ project: {
+ name: "greatproject",
+ web_url: "project_web_url",
+ path_with_namespace: "project_path_with_namespace"
+ },
+ user: {
+ name: "Jane Person",
+ username: "jane"
+ },
+ user_url: "user_url",
+ short_sha: "12345678",
+ commit_url: "commit_url",
+ commit_title: "commit title text"
+ }.merge(params)
+ end
+
+ it 'returns attachments with the data returned by the deployment data builder' do
+ user = create(:user, name: "John Smith", username: "smith")
+ namespace = create(:namespace, name: "myspace")
+ project = create(:project, :repository, namespace: namespace, name: "myproject")
+ commit = project.commit('HEAD')
+ environment = create(:environment, name: "myenvironment", project: project)
+ ci_build = create(:ci_build, project: project)
+ deployment = create(:deployment, :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha)
+ job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build)
+ commit_url = Gitlab::UrlBuilder.build(deployment.commit)
+ user_url = Gitlab::Routing.url_helpers.user_url(user)
+ data = Gitlab::DataBuilder::Deployment.build(deployment)
+
+ message = described_class.new(data)
+
+ expect(message.attachments).to eq([{
+ text: "[myspace/myproject](#{project.web_url}) with job [##{ci_build.id}](#{job_url}) by [John Smith (smith)](#{user_url})\n[#{deployment.short_sha}](#{commit_url}): #{commit.title}",
+ color: "good"
+ }])
+ end
+
+ it 'returns attachments for a failed deployment' do
+ data = deployment_data(status: 'failed')
+
+ message = described_class.new(data)
+
+ expect(message.attachments).to eq([{
+ text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text",
+ color: "danger"
+ }])
+ end
+
+ it 'returns attachments for a canceled deployment' do
+ data = deployment_data(status: 'canceled')
+
+ message = described_class.new(data)
+
+ expect(message.attachments).to eq([{
+ text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text",
+ color: "warning"
+ }])
+ end
+
+ it 'uses a neutral color for a deployment with any other status' do
+ data = deployment_data(status: 'some-new-status-we-make-in-the-future')
+
+ message = described_class.new(data)
+
+ expect(message.attachments).to eq([{
+ text: "[project_path_with_namespace](project_web_url) with job [#3](deployable_url) by [Jane Person (jane)](user_url)\n[12345678](commit_url): commit title text",
+ color: "#334455"
+ }])
+ end
+ end
+end
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index f7a35fdc88a..d3adc62c38e 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::IssueMessage do
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 7997b5bb6b9..b56eb19dd55 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::MergeMessage do
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 5abbd7bec18..5e7987dc0f6 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::NoteMessage do
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 0ff20400999..8f9fa310ad4 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::PipelineMessage do
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index 973d6bdb2a0..a89645a3ea8 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::PushMessage do
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 7efcba9bcfd..c3db516f253 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::WikiPageMessage do
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 46713df77da..6f4ddd223f6 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatNotificationService do
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index 7e1b1a4f2af..f0e7551693d 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CustomIssueTrackerService do
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 26597d9b83c..22df19d943f 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DroneCiService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index d9b7010e5e5..0a58eb367e3 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EmailsOnPushService do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 62fd97b038b..bdd8605436f 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExternalWikiService do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index fabcb142858..c1ebe69ee66 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FlowdockService do
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 3237b660a16..11f96c03d46 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabIssueTrackerService do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
new file mode 100644
index 00000000000..a04b984c1f6
--- /dev/null
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -0,0 +1,409 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HipchatService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
+ describe "Execute" do
+ let(:hipchat) { described_class.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
+ let(:project_name) { project.full_name.gsub(/\s/, '') }
+ let(:token) { 'verySecret' }
+ let(:server_url) { 'https://hipchat.example.com'}
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(hipchat).to receive_messages(
+ project_id: project.id,
+ project: project,
+ room: 123456,
+ server: server_url,
+ token: token
+ )
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it 'tests and return errors' do
+ allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room')
+ result = hipchat.test(push_sample_data)
+
+ expect(result[:success]).to be_falsey
+ expect(result[:result].to_s).to eq('no such room')
+ end
+
+ it 'uses v1 if version is provided' do
+ allow(hipchat).to receive(:api_version).and_return('v1')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v1',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ it 'uses v2 as the version when nothing is provided' do
+ allow(hipchat).to receive(:api_version).and_return('')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v2',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ context 'push events' do
+ it "calls Hipchat API for push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ branch = push_sample_data[:ref].gsub('refs/heads/', '')
+ expect(message).to include("#{user.name} pushed to branch " \
+ "<a href=\"#{project.web_url}/commits/#{branch}\">#{branch}</a> of " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>")
+ end
+ end
+
+ context 'tag_push events' do
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build(
+ project: project,
+ user: user,
+ oldrev: Gitlab::Git::BLANK_SHA,
+ newrev: '1' * 40,
+ ref: 'refs/tags/test')
+ end
+
+ it "calls Hipchat API for tag push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a tag push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} pushed new tag " \
+ "<a href=\"#{project.web_url}/commits/test\">test</a> to " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>\n")
+ end
+ end
+
+ context 'issue events' do
+ let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') }
+ let(:issue_service) { Issues::CreateService.new(project, user) }
+ let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
+
+ it "calls Hipchat API for issue events" do
+ hipchat.execute(issues_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates an issue message" do
+ message = hipchat.send(:create_issue_message, issues_sample_data)
+
+ obj_attr = issues_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome issue</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context 'merge request events' do
+ let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) }
+ let(:merge_service) { MergeRequests::CreateService.new(project, user) }
+ let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
+
+ it "calls Hipchat API for merge requests events" do
+ hipchat.execute(merge_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a merge request message" do
+ message = hipchat.send(:create_merge_request_message,
+ merge_sample_data)
+
+ obj_attr = merge_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome merge request</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context "Note events" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, creator: user) }
+
+ context 'when commit comment event triggered' do
+ let(:commit_note) do
+ create(:note_on_commit, author: user, project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit')
+ end
+
+ it "calls Hipchat API for commit comment events" do
+ data = Gitlab::DataBuilder::Note.build(commit_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ commit_id = Commit.truncate_sha(data[:commit][:id])
+ title = hipchat.send(:format_title, data[:commit][:message])
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "#{title}" \
+ "<pre>a comment on a commit</pre>")
+ end
+ end
+
+ context 'when merge request comment event triggered' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:merge_request_note) do
+ create(:note_on_merge_request, noteable: merge_request,
+ project: project,
+ note: "merge request **note**")
+ end
+
+ it "calls Hipchat API for merge request comment events" do
+ data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ merge_id = data[:merge_request]['iid']
+ title = data[:merge_request]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>merge request <strong>note</strong></pre>")
+ end
+ end
+
+ context 'when issue comment event triggered' do
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_note) do
+ create(:note_on_issue, noteable: issue, project: project,
+ note: "issue **note**")
+ end
+
+ it "calls Hipchat API for issue comment events" do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ issue_id = data[:issue]['iid']
+ title = data[:issue]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>issue <strong>note</strong></pre>")
+ end
+
+ context 'with confidential issue' do
+ before do
+ issue.update!(confidential: true)
+ end
+
+ it 'calls Hipchat API with issue comment' do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ expect(message).to include("<pre>issue <strong>note</strong></pre>")
+ end
+ end
+ end
+
+ context 'when snippet comment event triggered' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:snippet_note) do
+ create(:note_on_project_snippet, noteable: snippet,
+ project: project,
+ note: "snippet note")
+ end
+
+ it "calls Hipchat API for snippet comment events" do
+ data = Gitlab::DataBuilder::Note.build(snippet_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ snippet_id = data[:snippet]['id']
+ title = data[:snippet]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>snippet note</pre>")
+ end
+ end
+ end
+
+ context 'pipeline events' do
+ let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) }
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ context 'for failed' do
+ before do
+ pipeline.drop
+ end
+
+ it "calls Hipchat API" do
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a build message" do
+ message = hipchat.__send__(:create_pipeline_message, data)
+
+ project_url = project.web_url
+ project_name = project.full_name.gsub(/\s/, '')
+ pipeline_attributes = data[:object_attributes]
+ ref = pipeline_attributes[:ref]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ duration = pipeline_attributes[:duration]
+ user_name = data[:user][:name]
+
+ expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
+ "Pipeline <a href=\"#{project_url}/pipelines/#{pipeline.id}\">##{pipeline.id}</a> " \
+ "of <a href=\"#{project_url}/commits/#{ref}\">#{ref}</a> #{ref_type} " \
+ "by #{user_name} failed in #{duration} second(s)")
+ end
+ end
+
+ context 'for succeeded' do
+ before do
+ pipeline.succeed
+ end
+
+ it "calls Hipchat API" do
+ hipchat.notify_only_broken_pipelines = false
+ hipchat.execute(data)
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "notifies only broken" do
+ hipchat.notify_only_broken_pipelines = true
+ hipchat.execute(data)
+ expect(WebMock).not_to have_requested(:post, api_url).once
+ end
+ end
+ end
+
+ context "#message_options" do
+ it "is set to the defaults" do
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
+ end
+
+ it "sets notify to true" do
+ allow(hipchat).to receive(:notify).and_return('1')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
+ end
+
+ it "sets the color" do
+ allow(hipchat).to receive(:color).and_return('red')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+ end
+
+ context 'with a successful build' do
+ it 'uses the green color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'success' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'green' })
+ end
+ end
+
+ context 'with a failed build' do
+ it 'uses the red color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'failed' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'red' })
+ end
+ end
+ end
+ end
+
+ context 'with UrlBlocker' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:hipchat) { create(:hipchat_service, project: project, properties: { room: 'test' }) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ describe '#execute' do
+ before do
+ hipchat.server = 'http://localhost:9123'
+ end
+
+ it 'raises UrlBlocker for localhost' do
+ expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original
+ expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index cb9ca76fc3f..2e1f6964692 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'socket'
require 'json'
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index e6a1752576b..2fc4d69c2db 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueTrackerService do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 788b3179b01..04ae9390436 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe JiraService do
@@ -164,6 +166,13 @@ describe JiraService do
).once
end
+ it 'does not fail if remote_link.all on issue returns nil' do
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil)
+
+ expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) }
+ .not_to raise_error(NoMethodError)
+ end
+
# Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
# for more information
it 'creates Remote Link reference in JIRA for comment' do
@@ -177,9 +186,10 @@ describe JiraService do
expect(WebMock).to have_requested(:post, @remote_link_url).with(
body: hash_including(
GlobalID: 'GitLab',
+ relationship: 'mentioned on',
object: {
url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{commit_id}",
- title: "GitLab: Solved by commit #{commit_id}.",
+ title: "Solved by commit #{commit_id}.",
icon: { title: 'GitLab', url16x16: favicon_path },
status: { resolved: true }
}
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 9c27357ffaf..2fce120381b 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe KubernetesService, :use_clean_rails_memory_store_caching do
@@ -68,11 +70,11 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
kubernetes_service.properties['namespace'] = "foo"
end
- it 'should not update attributes' do
+ it 'does not update attributes' do
expect(kubernetes_service.save).to be_falsy
end
- it 'should include an error with a deprecation message' do
+ it 'includes an error with a deprecation message' do
kubernetes_service.valid?
expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been deprecated/)
end
@@ -81,7 +83,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'with a non-deprecated service' do
let(:kubernetes_service) { create(:kubernetes_service) }
- it 'should update attributes' do
+ it 'updates attributes' do
kubernetes_service.properties['namespace'] = 'foo'
expect(kubernetes_service.save).to be_truthy
end
@@ -96,15 +98,15 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
kubernetes_service.save
end
- it 'should deactive the service' do
+ it 'deactivates the service' do
expect(kubernetes_service.active?).to be_falsy
end
- it 'should not include a deprecation message as error' do
+ it 'does not include a deprecation message as error' do
expect(kubernetes_service.errors.messages.count).to eq(0)
end
- it 'should update attributes' do
+ it 'updates attributes' do
expect(kubernetes_service.properties['namespace']).to eq("foo")
end
end
@@ -116,7 +118,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
kubernetes_service.properties['namespace'] = 'foo'
end
- it 'should update attributes' do
+ it 'updates attributes' do
expect(kubernetes_service.save).to be_truthy
expect(kubernetes_service.properties['namespace']).to eq('foo')
end
@@ -159,8 +161,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
end
- describe '#actual_namespace' do
- subject { service.actual_namespace }
+ describe '#kubernetes_namespace_for' do
+ subject { service.kubernetes_namespace_for(project) }
shared_examples 'a correctly formatted namespace' do
it 'returns a valid Kubernetes namespace name' do
@@ -276,7 +278,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
it 'sets the variables' do
expect(subject.predefined_variables(project: project)).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
- { key: 'KUBE_TOKEN', value: 'token', public: false },
+ { key: 'KUBE_TOKEN', value: 'token', public: false, masked: true },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
@@ -296,7 +298,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
context 'no namespace provided' do
- let(:namespace) { subject.actual_namespace }
+ let(:namespace) { subject.kubernetes_namespace_for(project) }
it_behaves_like 'setting variables'
@@ -323,13 +325,14 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
+ let(:pod) { kube_pod(environment_slug: environment.slug, namespace: service.kubernetes_namespace_for(project), project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
@@ -349,6 +352,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
describe '#calculate_reactive_cache' do
subject { service.calculate_reactive_cache }
+ let(:namespace) { service.kubernetes_namespace_for(project) }
+
context 'when service is inactive' do
before do
service.active = false
@@ -359,15 +364,17 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with valid pods' do
before do
- stub_kubeclient_pods
+ stub_kubeclient_pods(namespace)
+ stub_kubeclient_deployments(namespace) # Used by EE
end
- it { is_expected.to eq(pods: [kube_pod]) }
+ it { is_expected.to include(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
- stub_kubeclient_pods(status: 500)
+ stub_kubeclient_pods(namespace, status: 500)
+ stub_kubeclient_deployments(namespace, status: 500) # Used by EE
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
@@ -375,10 +382,11 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with 404s' do
before do
- stub_kubeclient_pods(status: 404)
+ stub_kubeclient_pods(namespace, status: 404)
+ stub_kubeclient_deployments(namespace, status: 404) # Used by EE
end
- it { is_expected.to eq(pods: []) }
+ it { is_expected.to include(pods: []) }
end
end
@@ -386,13 +394,13 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
let(:kubernetes_service) { create(:kubernetes_service) }
context 'with an active kubernetes service' do
- it 'should return false' do
+ it 'returns false' do
expect(kubernetes_service.deprecated?).to be_falsy
end
end
context 'with a inactive kubernetes service' do
- it 'should return true' do
+ it 'returns true' do
kubernetes_service.update_attribute(:active, false)
expect(kubernetes_service.deprecated?).to be_truthy
end
@@ -402,18 +410,18 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
describe "#deprecation_message" do
let(:kubernetes_service) { create(:kubernetes_service) }
- it 'should indicate the service is deprecated' do
+ it 'indicates the service is deprecated' do
expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/)
end
context 'if the services is active' do
- it 'should return a message' do
+ it 'returns a message' do
expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/)
end
end
context 'if the service is not active' do
- it 'should return a message' do
+ it 'returns a message' do
kubernetes_service.update_attribute(:active, false)
expect(kubernetes_service.deprecation_message).to match(/Fields on this page are now uneditable/)
end
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
index 10c62ca55a7..6261c70f266 100644
--- a/spec/models/project_services/mattermost_service_spec.rb
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MattermostService do
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 1983e0cc967..87e482059f2 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MattermostSlashCommandsService do
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 3351c6280b4..c025d7c882e 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeamsService do
@@ -28,6 +30,12 @@ describe MicrosoftTeamsService do
end
end
+ describe '.supported_events' do
+ it 'does not support deployment_events' do
+ expect(described_class.supported_events).not_to include('deployment')
+ end
+ end
+
describe "#execute" do
let(:user) { create(:user) }
set(:project) { create(:project, :repository, :wiki_repo) }
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index 6acee311700..53f18a1bdd9 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PackagistService do
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index 75ae2207910..b85565e0c25 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -1,8 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelinesEmailService, :mailer do
let(:pipeline) do
- create(:ci_pipeline, project: project, sha: project.commit('master').sha)
+ create(:ci_pipeline, :failed,
+ project: project,
+ sha: project.commit('master').sha,
+ ref: project.default_branch
+ )
end
let(:project) { create(:project, :repository) }
@@ -82,12 +88,7 @@ describe PipelinesEmailService, :mailer do
subject.test(data)
end
- context 'when pipeline is failed' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
+ context 'when pipeline is failed and on default branch' do
it_behaves_like 'sending email'
end
@@ -99,6 +100,25 @@ describe PipelinesEmailService, :mailer do
it_behaves_like 'sending email'
end
+
+ context 'when pipeline is failed and on a non-default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ context 'with notify_only_default branch on' do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ it_behaves_like 'sending email'
+ end
+
+ context 'with notify_only_default_branch off' do
+ it_behaves_like 'sending email'
+ end
+ end
end
describe '#execute' do
@@ -108,11 +128,6 @@ describe PipelinesEmailService, :mailer do
context 'with recipients' do
context 'with failed pipeline' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
it_behaves_like 'sending email'
end
@@ -131,11 +146,6 @@ describe PipelinesEmailService, :mailer do
end
context 'with failed pipeline' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
it_behaves_like 'sending email'
end
@@ -148,6 +158,40 @@ describe PipelinesEmailService, :mailer do
it_behaves_like 'not sending email'
end
end
+
+ context 'with notify_only_default_branch off' do
+ context 'with default branch' do
+ it_behaves_like 'sending email'
+ end
+
+ context 'with non default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ it_behaves_like 'sending email'
+ end
+ end
+
+ context 'with notify_only_default_branch on' do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ context 'with default branch' do
+ it_behaves_like 'sending email'
+ end
+
+ context 'with non default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ it_behaves_like 'not sending email'
+ end
+ end
end
context 'with empty recipients list' do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index f7d2372eca2..dde46c82df6 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PivotaltrackerService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -51,12 +55,12 @@ describe PivotaltrackerService do
end
before do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
end
- it 'should post correct message' do
+ it 'posts correct message' do
service.execute(push_data)
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
body: {
'source_commit' => {
'commit_id' => '21c12ea',
@@ -79,18 +83,18 @@ describe PivotaltrackerService do
end
end
- it 'should post message if branch is in the list' do
+ it 'posts message if branch is in the list' do
service.execute(push_data(branch: 'master'))
service.execute(push_data(branch: 'v10'))
- expect(WebMock).to have_requested(:post, url).twice
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice
end
- it 'should not post message if branch is not in the list' do
+ it 'does not post message if branch is not in the list' do
service.execute(push_data(branch: 'mas'))
service.execute(push_data(branch: 'v11'))
- expect(WebMock).not_to have_requested(:post, url)
+ expect(WebMock).not_to have_requested(:post, stubbed_hostname(url))
end
end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index b6cf4c72450..e9c7c94ad70 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -33,18 +33,38 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
describe 'Validations' do
context 'when manual_configuration is enabled' do
before do
- subject.manual_configuration = true
+ service.manual_configuration = true
end
- it { is_expected.to validate_presence_of(:api_url) }
+ it 'validates presence of api_url' do
+ expect(service).to validate_presence_of(:api_url)
+ end
end
context 'when manual configuration is disabled' do
before do
- subject.manual_configuration = false
+ service.manual_configuration = false
end
- it { is_expected.not_to validate_presence_of(:api_url) }
+ it 'does not validate presence of api_url' do
+ expect(service).not_to validate_presence_of(:api_url)
+ end
+ end
+
+ context 'when the api_url domain points to localhost or local network' do
+ let(:domain) { Addressable::URI.parse(service.api_url).hostname }
+
+ it 'cannot query' do
+ expect(service.can_query?).to be true
+
+ aggregate_failures do
+ ['127.0.0.1', '192.168.2.3'].each do |url|
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)])
+
+ expect(service.can_query?).to be false
+ end
+ end
+ end
end
end
@@ -74,30 +94,35 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
describe '#prometheus_client' do
+ let(:api_url) { 'http://some_url' }
+
+ before do
+ service.active = true
+ service.api_url = api_url
+ service.manual_configuration = manual_configuration
+ end
+
context 'manual configuration is enabled' do
- let(:api_url) { 'http://some_url' }
+ let(:manual_configuration) { true }
- before do
- subject.active = true
- subject.manual_configuration = true
- subject.api_url = api_url
+ it 'returns rest client from api_url' do
+ expect(service.prometheus_client.url).to eq(api_url)
end
- it 'returns rest client from api_url' do
- expect(subject.prometheus_client.url).to eq(api_url)
+ it 'calls valid?' do
+ allow(service).to receive(:valid?).and_call_original
+
+ expect(service.prometheus_client).not_to be_nil
+
+ expect(service).to have_received(:valid?)
end
end
context 'manual configuration is disabled' do
- let(:api_url) { 'http://some_url' }
-
- before do
- subject.manual_configuration = false
- subject.api_url = api_url
- end
+ let(:manual_configuration) { false }
it 'no client provided' do
- expect(subject.prometheus_client).to be_nil
+ expect(service.prometheus_client).to be_nil
end
end
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 54b8c658ff6..380f02739bc 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushoverService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -55,13 +59,13 @@ describe PushoverService do
sound: sound
)
- WebMock.stub_request(:post, api_url)
+ stub_full_request(api_url, method: :post, ip_address: '8.8.8.8')
end
it 'calls Pushover API' do
pushover.execute(sample_data)
- expect(WebMock).to have_requested(:post, api_url).once
+ expect(WebMock).to have_requested(:post, 'https://8.8.8.8/1/messages.json').once
end
end
end
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 2ac14eab5e1..ac570ac27e1 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RedmineService do
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 13cf4d1915e..01f580c5d01 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SlackService do
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 5c4bce90ace..8c57907d064 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SlackSlashCommandsService do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 64b4efca43a..1c434b25205 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
@@ -210,7 +213,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
- WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return(
+ stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
new file mode 100644
index 00000000000..bf9d892f66c
--- /dev/null
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe YoutrackService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
+
+ describe '.reference_pattern' do
+ it_behaves_like 'allows project key on reference pattern'
+
+ it 'does allow project prefix on the reference' do
+ expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123')
+ end
+ end
+end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 1b439bcfad1..e87b4f41f4d 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectSnippet do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index bcbe687f4a2..aad08b9d4aa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Project do
include ProjectForksHelper
include GitHelpers
+ include ExternalAuthorizationServiceHelpers
it_behaves_like 'having unique enum values'
@@ -41,6 +44,7 @@ describe Project do
it { is_expected.to have_one(:pipelines_email_service) }
it { is_expected.to have_one(:irker_service) }
it { is_expected.to have_one(:pivotaltracker_service) }
+ it { is_expected.to have_one(:hipchat_service) }
it { is_expected.to have_one(:flowdock_service) }
it { is_expected.to have_one(:assembla_service) }
it { is_expected.to have_one(:slack_slash_commands_service) }
@@ -50,6 +54,7 @@ describe Project do
it { is_expected.to have_one(:teamcity_service) }
it { is_expected.to have_one(:jira_service) }
it { is_expected.to have_one(:redmine_service) }
+ it { is_expected.to have_one(:youtrack_service) }
it { is_expected.to have_one(:custom_issue_tracker_service) }
it { is_expected.to have_one(:bugzilla_service) }
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
@@ -135,15 +140,6 @@ describe Project do
end
end
- describe '#boards' do
- it 'raises an error when attempting to add more than one board to the project' do
- subject.boards.build
-
- expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
- expect(subject.boards.size).to eq 1
- end
- end
-
describe 'ci_pipelines association' do
it 'returns only pipelines from ci_sources' do
expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
@@ -218,6 +214,13 @@ describe Project do
expect(project2).not_to be_valid
end
+ it 'validates the visibility' do
+ expect_any_instance_of(described_class).to receive(:visibility_level_allowed_as_fork).and_call_original
+ expect_any_instance_of(described_class).to receive(:visibility_level_allowed_by_group).and_call_original
+
+ create(:project)
+ end
+
describe 'wiki path conflict' do
context "when the new path has been used by the wiki of other Project" do
it 'has an error on the name attribute' do
@@ -421,7 +424,7 @@ describe Project do
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
end
- it 'should return .external pipelines' do
+ it 'returns .external pipelines' do
expect(project.all_pipelines).to all(have_attributes(source: 'external'))
expect(project.all_pipelines.size).to eq(1)
end
@@ -445,7 +448,7 @@ describe Project do
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
end
- it 'should return .external pipelines' do
+ it 'returns .external pipelines' do
expect(project.ci_pipelines).to all(have_attributes(source: 'external'))
expect(project.ci_pipelines.size).to eq(1)
end
@@ -1144,7 +1147,7 @@ describe Project do
allow(project).to receive(:avatar_in_git) { true }
end
- let(:avatar_path) { "/#{project.full_path}/avatar" }
+ let(:avatar_path) { "/#{project.full_path}/-/avatar" }
it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
end
@@ -1916,7 +1919,7 @@ describe Project do
tags: %w[latest rc1])
end
- it 'should have image tags' do
+ it 'has image tags' do
expect(project).to have_container_registry_tags
end
end
@@ -1927,7 +1930,7 @@ describe Project do
tags: %w[latest rc1 pre1])
end
- it 'should have image tags' do
+ it 'has image tags' do
expect(project).to have_container_registry_tags
end
end
@@ -1937,7 +1940,7 @@ describe Project do
stub_container_registry_tags(repository: :any, tags: [])
end
- it 'should not have image tags' do
+ it 'does not have image tags' do
expect(project).not_to have_container_registry_tags
end
end
@@ -1948,16 +1951,16 @@ describe Project do
stub_container_registry_config(enabled: false)
end
- it 'should not have image tags' do
+ it 'does not have image tags' do
expect(project).not_to have_container_registry_tags
end
- it 'should not check root repository tags' do
+ it 'does not check root repository tags' do
expect(project).not_to receive(:full_path)
expect(project).not_to have_container_registry_tags
end
- it 'should iterate through container repositories' do
+ it 'iterates through container repositories' do
expect(project).to receive(:container_repositories)
expect(project).not_to have_container_registry_tags
end
@@ -2149,6 +2152,15 @@ describe Project do
expect(project.add_import_job).to eq(import_jid)
end
+
+ context 'without repository' do
+ it 'schedules RepositoryImportWorker' do
+ project = create(:project, import_url: generate(:url))
+
+ expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
+ expect(project.add_import_job).to eq(import_jid)
+ end
+ end
end
context 'not forked' do
@@ -2360,6 +2372,18 @@ describe Project do
end
end
+ describe '#daily_statistics_enabled?' do
+ it { is_expected.to be_daily_statistics_enabled }
+
+ context 'when :project_daily_statistics is disabled for the project' do
+ before do
+ stub_feature_flags(project_daily_statistics: { thing: subject, enabled: false })
+ end
+
+ it { is_expected.not_to be_daily_statistics_enabled }
+ end
+ end
+
describe '#change_head' do
let(:project) { create(:project, :repository) }
@@ -2375,6 +2399,12 @@ describe Project do
project.change_head(project.default_branch)
end
+ it 'updates commit count' do
+ expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:commit_count])
+
+ project.change_head(project.default_branch)
+ end
+
it 'copies the gitattributes' do
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
project.change_head(project.default_branch)
@@ -2511,6 +2541,16 @@ describe Project do
end
end
+ describe '#set_repository_writable!' do
+ it 'sets repository_read_only to false' do
+ project = create(:project, :read_only)
+
+ expect { project.set_repository_writable! }
+ .to change(project, :repository_read_only)
+ .from(true).to(false)
+ end
+ end
+
describe '#pushes_since_gc' do
let(:project) { create(:project) }
@@ -2584,7 +2624,7 @@ describe Project do
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
it 'returns variables from this service' do
expect(project.deployment_variables).to include(
- { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false }
+ { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true }
)
end
end
@@ -2607,9 +2647,9 @@ describe Project do
let!(:cluster) { kubernetes_namespace.cluster }
let(:project) { kubernetes_namespace.project }
- it 'should return token from kubernetes namespace' do
+ it 'returns token from kubernetes namespace' do
expect(project.deployment_variables).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
)
end
end
@@ -3130,62 +3170,123 @@ describe Project do
end
end
- describe '.with_feature_available_for_user' do
+ describe '.ids_with_milestone_available_for' do
let!(:user) { create(:user) }
- let!(:feature) { MergeRequest }
- let!(:project) { create(:project, :public, :merge_requests_enabled) }
+
+ it 'returns project ids with milestones available for user' do
+ project_1 = create(:project, :public, :merge_requests_disabled, :issues_disabled)
+ project_2 = create(:project, :public, :merge_requests_disabled)
+ project_3 = create(:project, :public, :issues_disabled)
+ project_4 = create(:project, :public)
+ project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE )
+
+ project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id)
+
+ expect(project_ids).to include(project_2.id, project_3.id)
+ expect(project_ids).not_to include(project_1.id, project_4.id)
+ end
+ end
+
+ describe '.with_feature_available_for_user' do
+ let(:user) { create(:user) }
+ let(:feature) { MergeRequest }
subject { described_class.with_feature_available_for_user(feature, user) }
- context 'when user has access to project' do
- subject { described_class.with_feature_available_for_user(feature, user) }
+ shared_examples 'feature disabled' do
+ let(:project) { create(:project, :public, :merge_requests_disabled) }
+
+ it 'does not return projects with the project feature disabled' do
+ is_expected.not_to include(project)
+ end
+ end
+
+ shared_examples 'feature public' do
+ let(:project) { create(:project, :public, :merge_requests_public) }
+
+ it 'returns projects with the project feature public' do
+ is_expected.to include(project)
+ end
+ end
+
+ shared_examples 'feature enabled' do
+ let(:project) { create(:project, :public, :merge_requests_enabled) }
+ it 'returns projects with the project feature enabled' do
+ is_expected.to include(project)
+ end
+ end
+
+ shared_examples 'feature access level is nil' do
+ let(:project) { create(:project, :public) }
+
+ it 'returns projects with the project feature access level nil' do
+ project.project_feature.update(merge_requests_access_level: nil)
+
+ is_expected.to include(project)
+ end
+ end
+
+ context 'with user' do
before do
project.add_guest(user)
end
- context 'when public project' do
- context 'when feature is public' do
- it 'returns project' do
- is_expected.to include(project)
+ it_behaves_like 'feature disabled'
+ it_behaves_like 'feature public'
+ it_behaves_like 'feature enabled'
+ it_behaves_like 'feature access level is nil'
+
+ context 'when feature is private' do
+ let(:project) { create(:project, :public, :merge_requests_private) }
+
+ context 'when user does not has access to the feature' do
+ it 'does not return projects with the project feature private' do
+ is_expected.not_to include(project)
end
end
- context 'when feature is private' do
- let!(:project) { create(:project, :public, :merge_requests_private) }
-
- it 'returns project when user has access to the feature' do
- project.add_maintainer(user)
+ context 'when user has access to the feature' do
+ it 'returns projects with the project feature private' do
+ project.add_reporter(user)
is_expected.to include(project)
end
-
- it 'does not return project when user does not have the minimum access level required' do
- is_expected.not_to include(project)
- end
end
end
+ end
- context 'when private project' do
- let!(:project) { create(:project) }
+ context 'user is an admin' do
+ let(:user) { create(:user, :admin) }
- it 'returns project when user has access to the feature' do
- project.add_maintainer(user)
+ it_behaves_like 'feature disabled'
+ it_behaves_like 'feature public'
+ it_behaves_like 'feature enabled'
+ it_behaves_like 'feature access level is nil'
- is_expected.to include(project)
- end
+ context 'when feature is private' do
+ let(:project) { create(:project, :public, :merge_requests_private) }
- it 'does not return project when user does not have the minimum access level required' do
- is_expected.not_to include(project)
+ it 'returns projects with the project feature private' do
+ is_expected.to include(project)
end
end
end
- context 'when user does not have access to project' do
- let!(:project) { create(:project) }
+ context 'without user' do
+ let(:user) { nil }
- it 'does not return project when user cant access project' do
- is_expected.not_to include(project)
+ it_behaves_like 'feature disabled'
+ it_behaves_like 'feature public'
+ it_behaves_like 'feature enabled'
+ it_behaves_like 'feature access level is nil'
+
+ context 'when feature is private' do
+ let(:project) { create(:project, :public, :merge_requests_private) }
+
+ it 'does not return projects with the project feature private' do
+ is_expected.not_to include(project)
+ end
end
end
end
@@ -3407,28 +3508,42 @@ describe Project do
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do
- Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase
+ it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do
+ Gitlab::ReferenceCounter.new(Gitlab::GlRepository::PROJECT.identifier_for_subject(project)).increase
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do
- Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase
+ it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do
+ Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_subject(project)).increase
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker' do
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id)
+ it 'schedules HashedStorage::ProjectMigrateWorker' do
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_async).with(project.id)
project.migrate_to_hashed_storage!
end
end
+
+ describe '#rollback_to_legacy_storage!' do
+ let(:project) { create(:project, :empty_repo, :legacy_storage) }
+
+ it 'returns nil' do
+ expect(project.rollback_to_legacy_storage!).to be_nil
+ end
+
+ it 'does not run validations' do
+ expect(project).not_to receive(:valid?)
+
+ project.rollback_to_legacy_storage!
+ end
+ end
end
context 'hashed storage' do
@@ -3504,20 +3619,34 @@ describe Project do
project = create(:project, storage_version: 1, skip_disk_validation: true)
Sidekiq::Testing.fake! do
- expect { project.migrate_to_hashed_storage! }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1)
+ expect { project.migrate_to_hashed_storage! }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
end
end
end
end
- end
- describe '#gl_repository' do
- let(:project) { create(:project) }
+ describe '#rollback_to_legacy_storage!' do
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+
+ it 'returns true' do
+ expect(project.rollback_to_legacy_storage!).to be_truthy
+ end
+
+ it 'does not run validations' do
+ expect(project).not_to receive(:valid?)
+
+ project.rollback_to_legacy_storage!
+ end
- it 'delegates to Gitlab::GlRepository.gl_repository' do
- expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true)
+ it 'does not flag as read-only' do
+ expect { project.rollback_to_legacy_storage! }.not_to change { project.repository_read_only }
+ end
- project.gl_repository(is_wiki: true)
+ it 'enqueues a job' do
+ Sidekiq::Testing.fake! do
+ expect { project.rollback_to_legacy_storage! }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
+ end
+ end
end
end
@@ -3570,12 +3699,36 @@ describe Project do
subject { project.auto_devops_enabled? }
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when explicitly disabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
context 'when enabled in settings' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it { is_expected.to be_truthy }
+ end
+
+ context 'when disabled in settings' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
context 'when explicitly enabled' do
before do
@@ -3587,34 +3740,91 @@ describe Project do
context 'when explicitly disabled' do
before do
- create(:project_auto_devops, project: project, enabled: false)
+ create(:project_auto_devops, :disabled, project: project)
end
it { is_expected.to be_falsey }
end
end
- context 'when disabled in settings' do
+ context 'when force_autodevops_on_by_default is enabled for the project' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with group parents' do
+ let(:instance_enabled) { true }
+
before do
- stub_application_setting(auto_devops_enabled: false)
+ stub_application_setting(auto_devops_enabled: instance_enabled)
+ project.update!(namespace: parent_group)
end
- it { is_expected.to be_falsey }
+ context 'when enabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_enabled) }
- context 'when explicitly enabled' do
- before do
- create(:project_auto_devops, project: project)
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_truthy }
end
- it { is_expected.to be_truthy }
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_truthy }
+ end
end
- context 'when force_autodevops_on_by_default is enabled for the project' do
- before do
- Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
+ context 'when disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_falsy }
end
- it { is_expected.to be_truthy }
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when enabled on root parent', :nested_groups do
+ let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when explicitly disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when disabled on root parent', :nested_groups do
+ let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when explicitly disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }
+
+ it { is_expected.to be_falsy }
+ end
end
end
end
@@ -3661,15 +3871,52 @@ describe Project do
end
end
end
+
+ context 'when enabled on group' do
+ it 'has auto devops implicitly enabled' do
+ project.update(namespace: create(:group, :auto_devops_enabled))
+
+ expect(project).to have_auto_devops_implicitly_enabled
+ end
+ end
+
+ context 'when enabled on parent group' do
+ it 'has auto devops implicitly enabled' do
+ subgroup = create(:group, parent: create(:group, :auto_devops_enabled))
+ project.update(namespace: subgroup)
+
+ expect(project).to have_auto_devops_implicitly_enabled
+ end
+ end
end
describe '#has_auto_devops_implicitly_disabled?' do
+ set(:project) { create(:project) }
+
before do
allow(Feature).to receive(:enabled?).and_call_original
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
end
- set(:project) { create(:project) }
+ context 'when explicitly disabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: false)
+ end
+
+ it 'does not have auto devops implicitly disabled' do
+ expect(project).not_to have_auto_devops_implicitly_disabled
+ end
+ end
+
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: true)
+ end
+
+ it 'does not have auto devops implicitly disabled' do
+ expect(project).not_to have_auto_devops_implicitly_disabled
+ end
+ end
context 'when enabled in settings' do
before do
@@ -3692,6 +3939,8 @@ describe Project do
context 'when force_autodevops_on_by_default is enabled for the project' do
before do
+ create(:project_auto_devops, project: project, enabled: false)
+
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
end
@@ -3700,23 +3949,20 @@ describe Project do
end
end
- context 'when explicitly disabled' do
- before do
- create(:project_auto_devops, project: project, enabled: false)
- end
+ context 'when disabled on group' do
+ it 'has auto devops implicitly disabled' do
+ project.update!(namespace: create(:group, :auto_devops_disabled))
- it 'does not have auto devops implicitly disabled' do
- expect(project).not_to have_auto_devops_implicitly_disabled
+ expect(project).to have_auto_devops_implicitly_disabled
end
end
- context 'when explicitly enabled' do
- before do
- create(:project_auto_devops, project: project, enabled: true)
- end
+ context 'when disabled on parent group' do
+ it 'has auto devops implicitly disabled' do
+ subgroup = create(:group, parent: create(:group, :auto_devops_disabled))
+ project.update!(namespace: subgroup)
- it 'does not have auto devops implicitly disabled' do
- expect(project).not_to have_auto_devops_implicitly_disabled
+ expect(project).to have_auto_devops_implicitly_disabled
end
end
end
@@ -3746,64 +3992,6 @@ describe Project do
end
end
- describe '#auto_devops_variables' do
- set(:project) { create(:project) }
-
- subject { project.auto_devops_variables }
-
- context 'when enabled in instance settings' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- end
-
- context 'when domain is empty' do
- before do
- stub_application_setting(auto_devops_domain: nil)
- end
-
- it 'variables does not include AUTO_DEVOPS_DOMAIN' do
- is_expected.not_to include(domain_variable)
- end
- end
-
- context 'when domain is configured' do
- before do
- stub_application_setting(auto_devops_domain: 'example.com')
- end
-
- it 'variables includes AUTO_DEVOPS_DOMAIN' do
- is_expected.to include(domain_variable)
- end
- end
- end
-
- context 'when explicitly enabled' do
- context 'when domain is empty' do
- before do
- create(:project_auto_devops, project: project, domain: nil)
- end
-
- it 'variables does not include AUTO_DEVOPS_DOMAIN' do
- is_expected.not_to include(domain_variable)
- end
- end
-
- context 'when domain is configured' do
- before do
- create(:project_auto_devops, project: project, domain: 'example.com')
- end
-
- it 'variables includes AUTO_DEVOPS_DOMAIN' do
- is_expected.to include(domain_variable)
- end
- end
- end
-
- def domain_variable
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
- end
- end
-
describe '#latest_successful_builds_for' do
let(:project) { build(:project) }
@@ -4194,6 +4382,25 @@ describe Project do
end
end
+ describe '#external_authorization_classification_label' do
+ it 'falls back to the default when none is configured' do
+ enable_external_authorization_service_check
+
+ expect(build(:project).external_authorization_classification_label)
+ .to eq('default_label')
+ end
+
+ it 'returns the classification label if it was configured on the project' do
+ enable_external_authorization_service_check
+
+ project = build(:project,
+ external_authorization_classification_label: 'hello')
+
+ expect(project.external_authorization_classification_label)
+ .to eq('hello')
+ end
+ end
+
describe "#pages_https_only?" do
subject { build(:project) }
@@ -4505,6 +4712,8 @@ describe Project do
it 'returns that pool repository' do
expect(subject).not_to be_empty
expect(subject[:pool_repository]).to be_persisted
+
+ expect(project.reload.pool_repository).to eq(subject[:pool_repository])
end
end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 64c39f09e33..358873f9a2f 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ProjectStatistics do
@@ -9,21 +11,37 @@ describe ProjectStatistics do
it { is_expected.to belong_to(:namespace) }
end
+ describe 'scopes' do
+ describe '.for_project_ids' do
+ it 'returns only requested projects' do
+ stats = create_list(:project_statistics, 3)
+ project_ids = stats[0..1].map { |s| s.project_id }
+ expected_ids = stats[0..1].map { |s| s.id }
+
+ requested_stats = described_class.for_project_ids(project_ids).pluck(:id)
+
+ expect(requested_stats).to eq(expected_ids)
+ end
+ end
+ end
+
describe 'statistics columns' do
it "support values up to 8 exabytes" do
statistics.update!(
commit_count: 8.exabytes - 1,
repository_size: 2.exabytes,
+ wiki_size: 1.exabytes,
lfs_objects_size: 2.exabytes,
- build_artifacts_size: 4.exabytes - 1
+ build_artifacts_size: 3.exabytes - 1
)
statistics.reload
expect(statistics.commit_count).to eq(8.exabytes - 1)
expect(statistics.repository_size).to eq(2.exabytes)
+ expect(statistics.wiki_size).to eq(1.exabytes)
expect(statistics.lfs_objects_size).to eq(2.exabytes)
- expect(statistics.build_artifacts_size).to eq(4.exabytes - 1)
+ expect(statistics.build_artifacts_size).to eq(3.exabytes - 1)
expect(statistics.storage_size).to eq(8.exabytes - 1)
end
end
@@ -31,6 +49,7 @@ describe ProjectStatistics do
describe '#total_repository_size' do
it "sums repository and LFS object size" do
statistics.repository_size = 2
+ statistics.wiki_size = 6
statistics.lfs_objects_size = 3
statistics.build_artifacts_size = 4
@@ -38,10 +57,17 @@ describe ProjectStatistics do
end
end
+ describe '#wiki_size' do
+ it "is initialized with not null value" do
+ expect(statistics.wiki_size).to eq 0
+ end
+ end
+
describe '#refresh!' do
before do
allow(statistics).to receive(:update_commit_count)
allow(statistics).to receive(:update_repository_size)
+ allow(statistics).to receive(:update_wiki_size)
allow(statistics).to receive(:update_lfs_objects_size)
allow(statistics).to receive(:update_storage_size)
end
@@ -54,6 +80,7 @@ describe ProjectStatistics do
it "sums all counters" do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_lfs_objects_size)
end
end
@@ -67,6 +94,45 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_lfs_objects_size)
expect(statistics).not_to have_received(:update_commit_count)
expect(statistics).not_to have_received(:update_repository_size)
+ expect(statistics).not_to have_received(:update_wiki_size)
+ end
+ end
+
+ context 'without repositories' do
+ it 'does not crash' do
+ expect(project.repository.exists?).to be_falsey
+ expect(project.wiki.repository.exists?).to be_falsey
+
+ statistics.refresh!
+
+ expect(statistics).to have_received(:update_commit_count)
+ expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics.repository_size).to eq(0)
+ expect(statistics.commit_count).to eq(0)
+ expect(statistics.wiki_size).to eq(0)
+ end
+ end
+
+ context 'with deleted repositories' do
+ let(:project) { create(:project, :repository, :wiki_repo) }
+
+ before do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(project.repository.path)
+ FileUtils.rm_rf(project.wiki.repository.path)
+ end
+ end
+
+ it 'does not crash' do
+ statistics.refresh!
+
+ expect(statistics).to have_received(:update_commit_count)
+ expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics.repository_size).to eq(0)
+ expect(statistics.commit_count).to eq(0)
+ expect(statistics.wiki_size).to eq(0)
end
end
end
@@ -93,6 +159,17 @@ describe ProjectStatistics do
end
end
+ describe '#update_wiki_size' do
+ before do
+ allow(project.wiki.repository).to receive(:size).and_return(34)
+ statistics.update_wiki_size
+ end
+
+ it "stores the size of the wiki" do
+ expect(statistics.wiki_size).to eq 34.megabytes
+ end
+ end
+
describe '#update_lfs_objects_size' do
let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) }
let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) }
@@ -112,26 +189,41 @@ describe ProjectStatistics do
it "sums all storage counters" do
statistics.update!(
repository_size: 2,
+ wiki_size: 4,
lfs_objects_size: 3
)
statistics.reload
- expect(statistics.storage_size).to eq 5
+ expect(statistics.storage_size).to eq 9
end
end
describe '.increment_statistic' do
- it 'increases the statistic by that amount' do
- expect { described_class.increment_statistic(project.id, :build_artifacts_size, 13) }
- .to change { statistics.reload.build_artifacts_size }
- .by(13)
+ shared_examples 'a statistic that increases storage_size' do
+ it 'increases the statistic by that amount' do
+ expect { described_class.increment_statistic(project.id, stat, 13) }
+ .to change { statistics.reload.send(stat) || 0 }
+ .by(13)
+ end
+
+ it 'increases also storage size by that amount' do
+ expect { described_class.increment_statistic(project.id, stat, 20) }
+ .to change { statistics.reload.storage_size }
+ .by(20)
+ end
end
- it 'increases also storage size by that amount' do
- expect { described_class.increment_statistic(project.id, :build_artifacts_size, 20) }
- .to change { statistics.reload.storage_size }
- .by(20)
+ context 'when adjusting :build_artifacts_size' do
+ let(:stat) { :build_artifacts_size }
+
+ it_behaves_like 'a statistic that increases storage_size'
+ end
+
+ context 'when adjusting :packages_size' do
+ let(:stat) { :packages_size }
+
+ it_behaves_like 'a statistic that increases storage_size'
end
context 'when the amount is 0' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 3537dead5d1..77c88a04cde 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe ProjectTeam do
@@ -193,6 +195,30 @@ describe ProjectTeam do
end
end
+ describe '#add_users' do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:project) { create(:project) }
+
+ it 'add the given users to the team' do
+ project.team.add_users([user1, user2], :reporter)
+
+ expect(project.team.reporter?(user1)).to be(true)
+ expect(project.team.reporter?(user2)).to be(true)
+ end
+ end
+
+ describe '#add_user' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ it 'add the given user to the team' do
+ project.team.add_user(user, :reporter)
+
+ expect(project.team.reporter?(user)).to be(true)
+ end
+ end
+
describe "#human_max_access" do
it 'returns Maintainer role' do
user = create(:user)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 3ccc706edf2..d12dd97bb9e 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,4 +1,5 @@
-# coding: utf-8
+# frozen_string_literal: true
+
require "spec_helper"
describe ProjectWiki do
@@ -71,6 +72,14 @@ describe ProjectWiki do
expect(project_wiki.create_page("index", "test content")).to be_truthy
end
+ it "creates a new wiki repo with a default commit message" do
+ expect(project_wiki.create_page("index", "test content", :markdown, "")).to be_truthy
+
+ page = project_wiki.find_page('index')
+
+ expect(page.last_version.message).to eq("#{user.username} created page: index")
+ end
+
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
@@ -100,8 +109,7 @@ describe ProjectWiki do
subject { super().empty? }
it { is_expected.to be_falsey }
- # Re-enable this when https://gitlab.com/gitlab-org/gitaly/issues/1204 is fixed
- xit 'only instantiates a Wiki page once' do
+ it 'only instantiates a Wiki page once' do
expect(WikiPage).to receive(:new).once.and_call_original
subject
@@ -110,22 +118,65 @@ describe ProjectWiki do
end
end
- describe "#pages" do
+ describe "#list_pages" do
+ let(:wiki_pages) { subject.list_pages }
+
before do
- create_page("index", "This is an awesome new Gollum Wiki")
- @pages = subject.pages
+ create_page("index", "This is an index")
+ create_page("index2", "This is an index2")
+ create_page("an index3", "This is an index3")
end
after do
- destroy_page(@pages.first.page)
+ wiki_pages.each do |wiki_page|
+ destroy_page(wiki_page.page)
+ end
end
it "returns an array of WikiPage instances" do
- expect(@pages.first).to be_a WikiPage
+ expect(wiki_pages.first).to be_a WikiPage
+ end
+
+ it 'does not load WikiPage content by default' do
+ wiki_pages.each do |page|
+ expect(page.content).to be_empty
+ end
+ end
+
+ it 'returns all pages by default' do
+ expect(wiki_pages.count).to eq(3)
end
- it "returns the correct number of pages" do
- expect(@pages.count).to eq(1)
+ context "with limit option" do
+ it 'returns limited set of pages' do
+ expect(subject.list_pages(limit: 1).count).to eq(1)
+ end
+ end
+
+ context "with sorting options" do
+ it 'returns pages sorted by title by default' do
+ pages = ['an index3', 'index', 'index2']
+
+ expect(subject.list_pages.map(&:title)).to eq(pages)
+ expect(subject.list_pages(direction: "desc").map(&:title)).to eq(pages.reverse)
+ end
+
+ it 'returns pages sorted by created_at' do
+ pages = ['index', 'index2', 'an index3']
+
+ expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
+ expect(subject.list_pages(sort: 'created_at', direction: "desc").map(&:title)).to eq(pages.reverse)
+ end
+ end
+
+ context "with load_content option" do
+ let(:pages) { subject.list_pages(load_content: true) }
+
+ it 'loads WikiPage content' do
+ expect(pages.first.content).to eq("This is an index3")
+ expect(pages.second.content).to eq("This is an index")
+ expect(pages.third.content).to eq("This is an index2")
+ end
end
end
@@ -135,7 +186,7 @@ describe ProjectWiki do
end
after do
- subject.pages.each { |page| destroy_page(page.page) }
+ subject.list_pages.each { |page| destroy_page(page.page) }
end
it "returns the latest version of the page if it exists" do
@@ -186,7 +237,7 @@ describe ProjectWiki do
end
after do
- subject.pages.each { |page| destroy_page(page.page) }
+ subject.list_pages.each { |page| destroy_page(page.page) }
end
it 'finds the page defined as _sidebar' do
@@ -233,12 +284,12 @@ describe ProjectWiki do
describe "#create_page" do
after do
- destroy_page(subject.pages.first.page)
+ destroy_page(subject.list_pages.first.page)
end
it "creates a new wiki page" do
expect(subject.create_page("test page", "this is content")).not_to eq(false)
- expect(subject.pages.count).to eq(1)
+ expect(subject.list_pages.count).to eq(1)
end
it "returns false when a duplicate page exists" do
@@ -253,7 +304,7 @@ describe ProjectWiki do
it "sets the correct commit message" do
subject.create_page("test page", "some content", :markdown, "commit message")
- expect(subject.pages.first.page.version.message).to eq("commit message")
+ expect(subject.list_pages.first.page.version.message).to eq("commit message")
end
it 'sets the correct commit email' do
@@ -284,7 +335,7 @@ describe ProjectWiki do
format: :markdown,
message: "updated page"
)
- @page = subject.pages.first.page
+ @page = subject.list_pages(load_content: true).first.page
end
after do
@@ -328,7 +379,7 @@ describe ProjectWiki do
it "deletes the page" do
subject.delete_page(@page)
- expect(subject.pages.count).to eq(0)
+ expect(subject.list_pages.count).to eq(0)
end
it 'sets the correct commit email' do
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index d4433a88a15..aca3df9fdde 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectableDropdown do
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 612e4a0e332..39dd586b157 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch::MergeAccessLevel do
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 9ccdc22fd41..628c8d29ecd 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch::PushAccessLevel do
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 4c677200ae2..267434a4148 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch do
@@ -190,4 +192,32 @@ describe ProtectedBranch do
end
end
end
+
+ describe '#any_protected?' do
+ context 'existing project' do
+ let(:project) { create(:project, :repository) }
+
+ it 'returns true when any of the branch names match a protected branch via direct match' do
+ create(:protected_branch, project: project, name: 'foo')
+
+ expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true)
+ end
+
+ it 'returns true when any of the branch matches a protected branch via wildcard match' do
+ create(:protected_branch, project: project, name: 'production/*')
+
+ expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true)
+ end
+
+ it 'returns false when none of branches does not match a protected branch via direct match' do
+ expect(described_class.any_protected?(project, ['foo'])).to eq(false)
+ end
+
+ it 'returns false when none of the branches does not match a protected branch via wildcard match' do
+ create(:protected_branch, project: project, name: 'production/*')
+
+ expect(described_class.any_protected?(project, ['staging/some-branch'])).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb
index e5a0f6ec23f..79120d17d39 100644
--- a/spec/models/protected_tag_spec.rb
+++ b/spec/models/protected_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedTag do
diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb
index 69a4922b6fd..6b59ee5ee57 100644
--- a/spec/models/push_event_payload_spec.rb
+++ b/spec/models/push_event_payload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushEventPayload do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index bfe7a30b96a..5509ed87308 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushEvent do
@@ -121,9 +123,9 @@ describe PushEvent do
end
end
- describe '#push?' do
+ describe '#push_action?' do
it 'returns true' do
- expect(event).to be_push
+ expect(event).to be_push_action
end
end
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 106ae59af29..6ecb5c31c7e 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RedirectRoute do
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 157c96c1f65..7c106ce6b85 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Release do
@@ -16,6 +18,22 @@ RSpec.describe Release do
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:description) }
+ it { is_expected.to validate_presence_of(:name) }
+
+ context 'when a release exists in the database without a name' do
+ it 'does not require name' do
+ existing_release_without_name = build(:release, project: project, author: user, name: nil)
+ existing_release_without_name.save(validate: false)
+
+ existing_release_without_name.description = "change"
+ existing_release_without_name.save
+ existing_release_without_name.reload
+
+ expect(existing_release_without_name).to be_valid
+ expect(existing_release_without_name.description).to eq("change")
+ expect(existing_release_without_name.name).to be_nil
+ end
+ end
end
describe '#assets_count' do
@@ -31,6 +49,11 @@ RSpec.describe Release do
it 'counts the link as an asset' do
is_expected.to eq(1 + Releases::Source::FORMATS.count)
end
+
+ it "excludes sources count when asked" do
+ assets_count = release.assets_count(except: [:sources])
+ expect(assets_count).to eq(1)
+ end
end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index c06e9a08ab4..e14b19db915 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RemoteMirror, :mailer do
@@ -5,14 +7,14 @@ describe RemoteMirror, :mailer do
describe 'URL validation' do
context 'with a valid URL' do
- it 'should be valid' do
+ it 'is valid' do
remote_mirror = build(:remote_mirror)
expect(remote_mirror).to be_valid
end
end
context 'with an invalid URL' do
- it 'should not be valid' do
+ it 'is not valid' do
remote_mirror = build(:remote_mirror, url: 'ftp://invalid.invalid')
expect(remote_mirror).not_to be_valid
@@ -371,6 +373,22 @@ describe RemoteMirror, :mailer do
end
end
+ describe '#disabled?' do
+ subject { remote_mirror.disabled? }
+
+ context 'when disabled' do
+ let(:remote_mirror) { build(:remote_mirror, enabled: false) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when enabled' do
+ let(:remote_mirror) { build(:remote_mirror, enabled: true) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
def create_mirror(params)
project = FactoryBot.create(:project, :repository)
project.remote_mirrors.create!(params)
diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb
index e2e4beb512f..13a4cd1e7cf 100644
--- a/spec/models/repository_language_spec.rb
+++ b/spec/models/repository_language_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryLanguage do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f78760bf567..c5ab7e57272 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Repository do
@@ -48,7 +50,7 @@ describe Repository do
it { is_expected.not_to include('fix') }
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error do
broken_repository.branch_names_contains(sample_commit.id)
end
@@ -215,6 +217,25 @@ describe Repository do
expect(result.size).to eq(0)
end
+
+ context 'with a commit with invalid UTF-8 path' do
+ def create_commit_with_invalid_utf8_path
+ rugged = rugged_repo(repository)
+ blob_id = Rugged::Blob.from_buffer(rugged, "some contents")
+ tree_builder = Rugged::Tree::Builder.new(rugged)
+ tree_builder.insert({ oid: blob_id, name: "hello\x80world", filemode: 0100644 })
+ tree_id = tree_builder.write
+ user = { email: "jcai@gitlab.com", time: Time.now, name: "John Cai" }
+
+ Rugged::Commit.create(rugged, message: 'some commit message', parents: [rugged.head.target.oid], tree: tree_id, committer: user, author: user)
+ end
+
+ it 'does not raise an error' do
+ commit = create_commit_with_invalid_utf8_path
+
+ expect { repository.list_last_commits_for_tree(commit, '.', offset: 0) }.not_to raise_error
+ end
+ end
end
describe '#last_commit_for_path' do
@@ -223,7 +244,7 @@ describe Repository do
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error do
broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore')
end
@@ -247,7 +268,7 @@ describe Repository do
end
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error do
broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id
end
@@ -388,7 +409,7 @@ describe Repository do
end
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') }
end
end
@@ -724,7 +745,7 @@ describe Repository do
end
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error do
broken_repository.search_files_by_content('feature', 'master')
end
@@ -773,7 +794,7 @@ describe Repository do
end
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') }
end
end
@@ -815,7 +836,7 @@ describe Repository do
let(:broken_repository) { create(:project, :broken_storage).repository }
describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
+ it 'raises a storage error' do
expect_to_raise_storage_error do
broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2')
end
@@ -1016,7 +1037,7 @@ describe Repository do
repository.add_branch(project.creator, ref, 'master')
end
- it 'should be true' do
+ it 'is true' do
is_expected.to eq(true)
end
end
@@ -1026,7 +1047,7 @@ describe Repository do
repository.add_tag(project.creator, ref, 'master')
end
- it 'should be false' do
+ it 'is false' do
is_expected.to eq(false)
end
end
@@ -1095,65 +1116,69 @@ describe Repository do
end
end
- describe '#exists?' do
- it 'returns true when a repository exists' do
- expect(repository.exists?).to be(true)
- end
-
- it 'returns false if no full path can be constructed' do
- allow(repository).to receive(:full_path).and_return(nil)
-
- expect(repository.exists?).to be(false)
- end
-
- context 'with broken storage', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error { broken_repository.exists? }
- end
- end
-
+ shared_examples 'asymmetric cached method' do |method|
context 'asymmetric caching', :use_clean_rails_memory_store_caching, :request_store do
let(:cache) { repository.send(:cache) }
let(:request_store_cache) { repository.send(:request_store_cache) }
context 'when it returns true' do
before do
- expect(repository.raw_repository).to receive(:exists?).once.and_return(true)
+ expect(repository.raw_repository).to receive(method).once.and_return(true)
end
it 'caches the output in RequestStore' do
expect do
- repository.exists?
- end.to change { request_store_cache.read(:exists?) }.from(nil).to(true)
+ repository.send(method)
+ end.to change { request_store_cache.read(method) }.from(nil).to(true)
end
it 'caches the output in RepositoryCache' do
expect do
- repository.exists?
- end.to change { cache.read(:exists?) }.from(nil).to(true)
+ repository.send(method)
+ end.to change { cache.read(method) }.from(nil).to(true)
end
end
context 'when it returns false' do
before do
- expect(repository.raw_repository).to receive(:exists?).once.and_return(false)
+ expect(repository.raw_repository).to receive(method).once.and_return(false)
end
it 'caches the output in RequestStore' do
expect do
- repository.exists?
- end.to change { request_store_cache.read(:exists?) }.from(nil).to(false)
+ repository.send(method)
+ end.to change { request_store_cache.read(method) }.from(nil).to(false)
end
it 'does NOT cache the output in RepositoryCache' do
expect do
- repository.exists?
- end.not_to change { cache.read(:exists?) }.from(nil)
+ repository.send(method)
+ end.not_to change { cache.read(method) }.from(nil)
end
end
end
end
+ describe '#exists?' do
+ it 'returns true when a repository exists' do
+ expect(repository.exists?).to be(true)
+ end
+
+ it 'returns false if no full path can be constructed' do
+ allow(repository).to receive(:full_path).and_return(nil)
+
+ expect(repository.exists?).to be(false)
+ end
+
+ context 'with broken storage', :broken_storage do
+ it 'raises a storage error' do
+ expect_to_raise_storage_error { broken_repository.exists? }
+ end
+ end
+
+ it_behaves_like 'asymmetric cached method', :exists?
+ end
+
describe '#has_visible_content?' do
before do
# If raw_repository.has_visible_content? gets called more than once then
@@ -1271,6 +1296,8 @@ describe Repository do
repository.root_ref
repository.root_ref
end
+
+ it_behaves_like 'asymmetric cached method', :root_ref
end
describe '#expire_root_ref_cache' do
@@ -1373,6 +1400,29 @@ describe Repository do
end
end
+ describe '#merge_to_ref' do
+ let(:merge_request) do
+ create(:merge_request, source_branch: 'feature',
+ target_branch: 'master',
+ source_project: project)
+ end
+
+ it 'writes merge of source and target to MR merge_ref_path' do
+ merge_commit_id = repository.merge_to_ref(user,
+ merge_request.diff_head_sha,
+ merge_request,
+ merge_request.merge_ref_path,
+ 'Custom message')
+
+ merge_commit = repository.commit(merge_commit_id)
+
+ expect(merge_commit.message).to eq('Custom message')
+ expect(merge_commit.author_name).to eq(user.name)
+ expect(merge_commit.author_email).to eq(user.commit_email)
+ expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
+ end
+ end
+
describe '#ff_merge' do
before do
repository.add_branch(user, 'ff-target', 'feature~5')
@@ -1401,6 +1451,91 @@ describe Repository do
end
end
+ describe '#rebase' do
+ let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) }
+
+ shared_examples_for 'a method that can rebase successfully' do
+ it 'returns the rebase commit sha' do
+ rebase_commit_sha = repository.rebase(user, merge_request)
+ head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
+
+ expect(rebase_commit_sha).to eq(head_sha)
+ end
+
+ it 'sets the `rebase_commit_sha` for the given merge request' do
+ rebase_commit_sha = repository.rebase(user, merge_request)
+
+ expect(rebase_commit_sha).not_to be_nil
+ expect(merge_request.rebase_commit_sha).to eq(rebase_commit_sha)
+ end
+ end
+
+ context 'when two_step_rebase feature is enabled' do
+ before do
+ stub_feature_flags(two_step_rebase: true)
+ end
+
+ it_behaves_like 'a method that can rebase successfully'
+
+ it 'executes the new Gitaly RPC' do
+ expect_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rebase)
+ expect_any_instance_of(Gitlab::GitalyClient::OperationService).not_to receive(:user_rebase)
+
+ repository.rebase(user, merge_request)
+ end
+
+ describe 'rolling back the `rebase_commit_sha`' do
+ let(:new_sha) { Digest::SHA1.hexdigest('foo') }
+
+ it 'does not rollback when there are no errors' do
+ second_response = double(pre_receive_error: nil, git_error: nil)
+ mock_gitaly(second_response)
+
+ repository.rebase(user, merge_request)
+
+ expect(merge_request.reload.rebase_commit_sha).to eq(new_sha)
+ end
+
+ it 'does rollback when an error is encountered in the second step' do
+ second_response = double(pre_receive_error: 'my_error', git_error: nil)
+ mock_gitaly(second_response)
+
+ expect do
+ repository.rebase(user, merge_request)
+ end.to raise_error(Gitlab::Git::PreReceiveError)
+
+ expect(merge_request.reload.rebase_commit_sha).to be_nil
+ end
+
+ def mock_gitaly(second_response)
+ responses = [
+ double(rebase_sha: new_sha).as_null_object,
+ second_response
+ ]
+
+ expect_any_instance_of(
+ Gitaly::OperationService::Stub
+ ).to receive(:user_rebase_confirmable).and_return(responses.each)
+ end
+ end
+ end
+
+ context 'when two_step_rebase feature is disabled' do
+ before do
+ stub_feature_flags(two_step_rebase: false)
+ end
+
+ it_behaves_like 'a method that can rebase successfully'
+
+ it 'executes the deprecated Gitaly RPC' do
+ expect_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:user_rebase)
+ expect_any_instance_of(Gitlab::GitalyClient::OperationService).not_to receive(:rebase)
+
+ repository.rebase(user, merge_request)
+ end
+ end
+ end
+
describe '#revert' do
let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
@@ -1587,6 +1722,7 @@ describe Repository do
:has_visible_content?,
:issue_template_names,
:merge_request_template_names,
+ :metrics_dashboard_paths,
:xcode_project?
])
@@ -2150,12 +2286,45 @@ describe Repository do
end
describe '#diverging_commit_counts' do
+ let(:diverged_branch) { repository.find_branch('fix') }
+ let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id }
+ let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha }
+
it 'returns the commit counts behind and ahead of default branch' do
- result = repository.diverging_commit_counts(
- repository.find_branch('fix'))
+ result = repository.diverging_commit_counts(diverged_branch)
expect(result).to eq(behind: 29, ahead: 2)
end
+
+ context 'when gitaly_count_diverging_commits_no_max is enabled' do
+ before do
+ stub_feature_flags(gitaly_count_diverging_commits_no_max: true)
+ end
+
+ it 'calls diverging_commit_count without max count' do
+ expect(repository.raw_repository)
+ .to receive(:diverging_commit_count)
+ .with(root_ref_sha, diverged_branch_sha)
+ .and_return([29, 2])
+
+ repository.diverging_commit_counts(diverged_branch)
+ end
+ end
+
+ context 'when gitaly_count_diverging_commits_no_max is disabled' do
+ before do
+ stub_feature_flags(gitaly_count_diverging_commits_no_max: false)
+ end
+
+ it 'calls diverging_commit_count with max count' do
+ expect(repository.raw_repository)
+ .to receive(:diverging_commit_count)
+ .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT)
+ .and_return([29, 2])
+
+ repository.diverging_commit_counts(diverged_branch)
+ end
+ end
end
describe '#refresh_method_caches' do
@@ -2214,15 +2383,15 @@ describe Repository do
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
- describe '#ancestor?' do
+ shared_examples '#ancestor?' do
let(:commit) { repository.commit }
let(:ancestor) { commit.parents.first }
- it 'it is an ancestor' do
+ it 'is an ancestor' do
expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
end
- it 'it is not an ancestor' do
+ it 'is not an ancestor' do
expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
end
@@ -2238,6 +2407,20 @@ describe Repository do
end
end
+ describe '#ancestor? with Gitaly enabled' do
+ it_behaves_like "#ancestor?"
+ end
+
+ describe '#ancestor? with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged).to receive(:merge_base).with(repository.commit.id, Gitlab::Git::BLANK_SHA).and_call_original
+
+ repository.ancestor?(repository.commit.id, Gitlab::Git::BLANK_SHA)
+ end
+
+ it_behaves_like '#ancestor?'
+ end
+
describe '#archive_metadata' do
let(:ref) { 'master' }
let(:storage_path) { '/tmp' }
@@ -2423,4 +2606,69 @@ describe Repository do
repository.merge_base('master', 'fix')
end
end
+
+ describe '#create_if_not_exists' do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ it 'creates the repository if it did not exist' do
+ expect { repository.create_if_not_exists }.to change { repository.exists? }.from(false).to(true)
+ end
+
+ it 'calls out to the repository client to create a repo' do
+ expect(repository.raw.gitaly_repository_client).to receive(:create_repository)
+
+ repository.create_if_not_exists
+ end
+
+ context 'it does nothing if the repository already existed' do
+ let(:project) { create(:project, :repository) }
+
+ it 'does nothing if the repository already existed' do
+ expect(repository.raw.gitaly_repository_client).not_to receive(:create_repository)
+
+ repository.create_if_not_exists
+ end
+ end
+
+ context 'when the repository exists but the cache is not up to date' do
+ let(:project) { create(:project, :repository) }
+
+ it 'does not raise errors' do
+ allow(repository).to receive(:exists?).and_return(false)
+ expect(repository.raw).to receive(:create_repository).and_call_original
+
+ expect { repository.create_if_not_exists }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#blobs_metadata" do
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ def expect_metadata_blob(thing)
+ expect(thing).to be_a(Blob)
+ expect(thing.data).to be_empty
+ end
+
+ it "returns blob metadata in batch for HEAD" do
+ result = repository.blobs_metadata(["bar/branch-test.txt", "README.md", "does/not/exist"])
+
+ expect_metadata_blob(result.first)
+ expect_metadata_blob(result.second)
+ expect(result.size).to eq(2)
+ end
+
+ it "returns blob metadata for a specified ref" do
+ result = repository.blobs_metadata(["files/ruby/feature.rb"], "feature")
+
+ expect_metadata_blob(result.first)
+ end
+
+ it "performs a single gitaly call", :request_store do
+ expect { repository.blobs_metadata(["bar/branch-test.txt", "readme.txt", "does/not/exist"]) }
+ .to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
end
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index 7eeb2fae57d..cb52f154299 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -82,13 +82,13 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
it 'returns true if markdown is outdated' do
- subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 }
+ subject.attributes = { cached_markdown_version: ((Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 }
expect(subject.outdated_markdown?).to be true
end
it 'returns false if label and reference are set' do
- subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
+ subject.attributes = { reference: 'whatever', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
expect(subject.outdated_markdown?).to be false
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 48799781b87..20289afbeb5 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Route do
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 6c35ed8f649..09be90b82ed 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SentNotification do
diff --git a/spec/models/serverless/function_spec.rb b/spec/models/serverless/function_spec.rb
new file mode 100644
index 00000000000..1854d5f9415
--- /dev/null
+++ b/spec/models/serverless/function_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Serverless::Function do
+ let(:project) { create(:project) }
+ let(:func) { described_class.new(project, 'test', 'test-ns') }
+
+ it 'has a proper id' do
+ expect(func.id).to eql("#{project.id}/test/test-ns")
+ expect(func.name).to eql("test")
+ expect(func.namespace).to eql("test-ns")
+ end
+
+ it 'can decode an identifier' do
+ f = described_class.find_by_id("#{project.id}/testfunc/dummy-ns")
+
+ expect(f.name).to eql("testfunc")
+ expect(f.namespace).to eql("dummy-ns")
+ end
+end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 25eecb3f909..64db32781fe 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Service do
@@ -289,7 +291,7 @@ describe Service do
describe "#deprecated?" do
let(:project) { create(:project, :repository) }
- it 'should return false by default' do
+ it 'returns false by default' do
service = create(:service, project: project)
expect(service.deprecated?).to be_falsy
end
@@ -298,7 +300,7 @@ describe Service do
describe "#deprecation_message" do
let(:project) { create(:project, :repository) }
- it 'should be empty by default' do
+ it 'is empty by default' do
service = create(:service, project: project)
expect(service.deprecation_message).to be_nil
end
diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb
index 7c71c458fcc..88441e39d45 100644
--- a/spec/models/snippet_blob_spec.rb
+++ b/spec/models/snippet_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SnippetBlob do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 664dc3fa145..3524cdae3b8 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Snippet do
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index 90a2caaeb88..e9ea234f75d 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SpamLog do
diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb
index 4c677569561..a17cd8ba345 100644
--- a/spec/models/ssh_host_key_spec.rb
+++ b/spec/models/ssh_host_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SshHostKey do
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index 9e4c2620d82..41bd48810b2 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Subscription do
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index cafc725dddb..8d4e9070b19 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -21,6 +21,22 @@ describe Suggestion do
end
end
+ describe '#diff_lines' do
+ let(:suggestion) { create(:suggestion, :content_from_repo) }
+
+ it 'returns parsed diff lines' do
+ expected_diff_lines = Gitlab::Diff::SuggestionDiff.new(suggestion).diff_lines
+ diff_lines = suggestion.diff_lines
+
+ expect(diff_lines.size).to eq(expected_diff_lines.size)
+ expect(diff_lines).to all(be_a(Gitlab::Diff::Line))
+
+ expected_diff_lines.each_with_index do |expected_line, index|
+ expect(diff_lines[index].to_hash).to eq(expected_line.to_hash)
+ end
+ end
+ end
+
describe '#appliable?' do
context 'when note does not support suggestions' do
it 'returns false' do
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index 1e3f587e460..bcd3c03f947 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemNoteMetadata do
diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb
index 950dfa09a6a..42a48048b67 100644
--- a/spec/models/term_agreement_spec.rb
+++ b/spec/models/term_agreement_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TermAgreement do
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index a0c93c531ea..9d69a0ab148 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Timelog do
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 3682e21ca40..b5bf294790a 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todo do
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 6bdb62a0864..c2d5dfdf9c4 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Tree do
diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb
index 3b5e7ca0d39..619fc8e7d38 100644
--- a/spec/models/trending_project_spec.rb
+++ b/spec/models/trending_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TrendingProject do
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 5a0df9fbbb0..02702cb2497 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Upload do
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index b4669f8c1c2..f191d245045 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UserAgentDetail do
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
index d54355afe12..b87f6f03d6f 100644
--- a/spec/models/user_callout_spec.rb
+++ b/spec/models/user_callout_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UserCallout do
diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb
index 37fc3cb64f0..d0981b2d771 100644
--- a/spec/models/user_custom_attribute_spec.rb
+++ b/spec/models/user_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserCustomAttribute do
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
index cb4bb3372d4..47d919c1d12 100644
--- a/spec/models/user_interacted_project_spec.rb
+++ b/spec/models/user_interacted_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserInteractedProject do
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index b2ef17a81d4..e09c91e874a 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -73,4 +73,10 @@ describe UserPreference do
it_behaves_like 'a sort_by preference'
end
end
+
+ describe '#timezone' do
+ it 'returns server time as default' do
+ expect(user_preference.timezone).to eq(Time.zone.tzinfo.name)
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 85b157a9435..d1338e34bb8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe User do
@@ -96,6 +98,11 @@ describe User do
end
describe 'validations' do
+ describe 'name' do
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_length_of(:name).is_at_most(128) }
+ end
+
describe 'username' do
it 'validates presence' do
expect(subject).to validate_presence_of(:username)
@@ -660,6 +667,68 @@ describe User do
end
end
+ describe '#highest_role' do
+ let(:user) { create(:user) }
+
+ let(:group) { create(:group) }
+
+ it 'returns NO_ACCESS if none has been set' do
+ expect(user.highest_role).to eq(Gitlab::Access::NO_ACCESS)
+ end
+
+ it 'returns MAINTAINER if user is maintainer of a project' do
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple projects' do
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ create(:project, group: group) do |project|
+ project.add_developer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns MAINTAINER if user is maintainer of a group' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::MAINTAINER)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple groups' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::MAINTAINER)
+ end
+
+ create(:group) do |group|
+ group.add_user(user, GroupMember::DEVELOPER)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple groups and projects' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::DEVELOPER)
+ end
+
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+
describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do
let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
let(:user) { create(:user) }
@@ -2747,9 +2816,9 @@ describe User do
project = create(:project, :public)
archived_project = create(:project, :public, :archived)
- create(:merge_request, source_project: project, author: user, assignee: user)
- create(:merge_request, :closed, source_project: project, author: user, assignee: user)
- create(:merge_request, source_project: archived_project, author: user, assignee: user)
+ create(:merge_request, source_project: project, author: user, assignees: [user])
+ create(:merge_request, :closed, source_project: project, author: user, assignees: [user])
+ create(:merge_request, source_project: archived_project, author: user, assignees: [user])
expect(user.assigned_open_merge_requests_count(force: true)).to eq 1
end
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
index fb8575cfe2b..5fbcccf897e 100644
--- a/spec/models/wiki_directory_spec.rb
+++ b/spec/models/wiki_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe WikiDirectory do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index cba22b2cc4e..520a06e138e 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe WikiPage do
@@ -18,12 +20,18 @@ describe WikiPage do
context 'when there are pages' do
before do
create_page('dir_1/dir_1_1/page_3', 'content')
+ create_page('page_1', 'content')
create_page('dir_1/page_2', 'content')
+ create_page('dir_2', 'page with dir name')
create_page('dir_2/page_5', 'content')
+ create_page('page_6', 'content')
create_page('dir_2/page_4', 'content')
- create_page('page_1', 'content')
end
+
let(:page_1) { wiki.find_page('page_1') }
+ let(:page_6) { wiki.find_page('page_6') }
+ let(:page_dir_2) { wiki.find_page('dir_2') }
+
let(:dir_1) do
WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
end
@@ -36,34 +44,49 @@ describe WikiPage do
WikiDirectory.new('dir_2', pages)
end
- it 'returns an array with pages and directories' do
- expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2]
-
- grouped_entries = described_class.group_by_directory(wiki.pages)
+ context "#list_pages" do
+ context 'sort by title' do
+ let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) }
+ let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] }
- grouped_entries.each_with_index do |page_or_dir, i|
- expected_page_or_dir = expected_grouped_entries[i]
- expected_slugs = get_slugs(expected_page_or_dir)
- slugs = get_slugs(page_or_dir)
+ it 'returns an array with pages and directories' do
+ grouped_entries.each_with_index do |page_or_dir, i|
+ expected_page_or_dir = expected_grouped_entries[i]
+ expected_slugs = get_slugs(expected_page_or_dir)
+ slugs = get_slugs(page_or_dir)
- expect(slugs).to match_array(expected_slugs)
+ expect(slugs).to match_array(expected_slugs)
+ end
+ end
end
- end
- it 'returns an array sorted by alphabetical position' do
- # Directories and pages within directories are sorted alphabetically.
- # Pages at root come before everything.
- expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
- 'dir_2/page_4', 'dir_2/page_5']
+ context 'sort by created_at' do
+ let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages(sort: 'created_at')) }
+ let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] }
- grouped_entries = described_class.group_by_directory(wiki.pages)
+ it 'returns an array with pages and directories' do
+ grouped_entries.each_with_index do |page_or_dir, i|
+ expected_page_or_dir = expected_grouped_entries[i]
+ expected_slugs = get_slugs(expected_page_or_dir)
+ slugs = get_slugs(page_or_dir)
- actual_order =
- grouped_entries.map do |page_or_dir|
- get_slugs(page_or_dir)
+ expect(slugs).to match_array(expected_slugs)
+ end
end
- .flatten
- expect(actual_order).to eq(expected_order)
+ end
+
+ it 'returns an array with retained order with directories at the top' do
+ expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']
+
+ grouped_entries = described_class.group_by_directory(wiki.list_pages)
+
+ actual_order =
+ grouped_entries.map do |page_or_dir|
+ get_slugs(page_or_dir)
+ end
+ .flatten
+ expect(actual_order).to eq(expected_order)
+ end
end
end
end
@@ -365,7 +388,7 @@ describe WikiPage do
it "deletes the page" do
@page.delete
- expect(wiki.pages).to be_empty
+ expect(wiki.list_pages).to be_empty
end
it "returns true" do
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index c03d95b34db..09be831dcd5 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe BasePolicy do
+ include ExternalAuthorizationServiceHelpers
+
describe '.class_for' do
it 'detects policy class based on the subject ancestors' do
expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
@@ -16,4 +18,25 @@ describe BasePolicy do
expect(DeclarativePolicy.class_for(:global)).to eq(GlobalPolicy)
end
end
+
+ describe 'read cross project' do
+ let(:current_user) { create(:user) }
+ let(:user) { create(:user) }
+
+ subject { described_class.new(current_user, [user]) }
+
+ it { is_expected.to be_allowed(:read_cross_project) }
+
+ context 'when an external authorization service is enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it { is_expected.not_to be_allowed(:read_cross_project) }
+
+ it 'allows admins' do
+ expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project)
+ end
+ end
+ end
end
diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
index 4b76d65ef69..52c23951e37 100644
--- a/spec/policies/board_policy_spec.rb
+++ b/spec/policies/board_policy_spec.rb
@@ -17,14 +17,6 @@ describe BoardPolicy do
]
end
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
-
context 'group board' do
subject { described_class.new(user, group_board) }
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index 844d96017de..126d44d1860 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -100,5 +100,51 @@ describe Ci::PipelinePolicy, :models do
end
end
end
+
+ describe 'read_pipeline_variable' do
+ let(:project) { create(:project, :public) }
+
+ context 'when user has owner access' do
+ let(:user) { project.owner }
+
+ it 'is enabled' do
+ expect(policy).to be_allowed :read_pipeline_variable
+ end
+ end
+
+ context 'when user is developer and the creator of the pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: user) }
+
+ before do
+ project.add_developer(user)
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'is enabled' do
+ expect(policy).to be_allowed :read_pipeline_variable
+ end
+ end
+
+ context 'when user is developer and it is not the creator of the pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.owner) }
+
+ before do
+ project.add_developer(user)
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'is disabled' do
+ expect(policy).to be_disallowed :read_pipeline_variable
+ end
+ end
+
+ context 'when user is not owner nor developer' do
+ it 'is disabled' do
+ expect(policy).not_to be_allowed :read_pipeline_variable
+ end
+ end
+ end
end
end
diff --git a/spec/policies/clusters/cluster_policy_spec.rb b/spec/policies/clusters/cluster_policy_spec.rb
index b2f0ca1bc30..cc3dde154dc 100644
--- a/spec/policies/clusters/cluster_policy_spec.rb
+++ b/spec/policies/clusters/cluster_policy_spec.rb
@@ -66,5 +66,21 @@ describe Clusters::ClusterPolicy, :models do
it { expect(policy).to be_disallowed :admin_cluster }
end
end
+
+ context 'instance cluster' do
+ let(:cluster) { create(:cluster, :instance) }
+
+ context 'when user' do
+ it { expect(policy).to be_disallowed :update_cluster }
+ it { expect(policy).to be_disallowed :admin_cluster }
+ end
+
+ context 'when admin' do
+ let(:user) { create(:admin) }
+
+ it { expect(policy).to be_allowed :update_cluster }
+ it { expect(policy).to be_allowed :admin_cluster }
+ end
+ end
end
end
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
new file mode 100644
index 00000000000..9d755c6d29d
--- /dev/null
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::InstancePolicy do
+ let(:user) { create(:user) }
+ let(:policy) { described_class.new(user, Clusters::Instance.new) }
+
+ describe 'rules' do
+ context 'when user' do
+ it { expect(policy).to be_disallowed :read_cluster }
+ it { expect(policy).to be_disallowed :update_cluster }
+ it { expect(policy).to be_disallowed :admin_cluster }
+ end
+
+ context 'when admin' do
+ let(:user) { create(:admin) }
+
+ context 'with instance_level_clusters enabled' do
+ it { expect(policy).to be_allowed :read_cluster }
+ it { expect(policy).to be_allowed :update_cluster }
+ it { expect(policy).to be_allowed :admin_cluster }
+ end
+
+ context 'with instance_level_clusters disabled' do
+ before do
+ stub_feature_flags(instance_clusters: false)
+ end
+
+ it { expect(policy).to be_disallowed :read_cluster }
+ it { expect(policy).to be_disallowed :update_cluster }
+ it { expect(policy).to be_disallowed :admin_cluster }
+ end
+ end
+ end
+end
diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb
new file mode 100644
index 00000000000..41f6fb08426
--- /dev/null
+++ b/spec/policies/commit_policy_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CommitPolicy do
+ describe '#rules' do
+ let(:user) { create(:user) }
+ let(:commit) { project.repository.head_commit }
+ let(:policy) { described_class.new(user, commit) }
+
+ context 'when project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it 'can read commit and create a note' do
+ expect(policy).to be_allowed(:read_commit)
+ end
+
+ context 'when repository access level is private' do
+ let(:project) { create(:project, :public, :repository, :repository_private) }
+
+ it 'can not read commit and create a note' do
+ expect(policy).to be_disallowed(:read_commit)
+ end
+
+ context 'when the user is a project member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can read commit and create a note' do
+ expect(policy).to be_allowed(:read_commit)
+ end
+ end
+ end
+ end
+
+ context 'when project is private' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it 'can not read commit and create a note' do
+ expect(policy).to be_disallowed(:read_commit)
+ end
+
+ context 'when the user is a project member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can read commit and create a note' do
+ expect(policy).to be_allowed(:read_commit)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index 30d68e7dc9d..12be3927e18 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -181,6 +181,18 @@ describe GlobalPolicy do
end
end
+ describe 'read instance metadata' do
+ context 'regular user' do
+ it { is_expected.to be_allowed(:read_instance_metadata) }
+ end
+
+ context 'anonymous' do
+ let(:current_user) { nil }
+
+ it { is_expected.not_to be_allowed(:read_instance_metadata) }
+ end
+ end
+
describe 'read instance statistics' do
context 'regular user' do
it { is_expected.to be_allowed(:read_instance_statistics) }
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
new file mode 100644
index 00000000000..7bd7184cffe
--- /dev/null
+++ b/spec/policies/group_member_policy_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GroupMemberPolicy do
+ let(:guest) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:group) { create(:group, :private) }
+
+ before do
+ group.add_guest(guest)
+ group.add_owner(owner)
+ end
+
+ let(:member_related_permissions) do
+ [:update_group_member, :destroy_group_member]
+ end
+
+ let(:membership) { current_user.members.first }
+
+ subject { described_class.new(current_user, membership) }
+
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
+
+ context 'with guest user' do
+ let(:current_user) { guest }
+
+ it do
+ expect_disallowed(:member_related_permissions)
+ end
+ end
+
+ context 'with one owner' do
+ let(:current_user) { owner }
+
+ it do
+ expect_disallowed(:destroy_group_member)
+ expect_disallowed(:update_group_member)
+ end
+ end
+
+ context 'with more than one owner' do
+ let(:current_user) { owner }
+
+ before do
+ group.add_owner(create(:user))
+ end
+
+ it do
+ expect_allowed(:destroy_group_member)
+ expect_allowed(:update_group_member)
+ end
+ end
+
+ context 'with the group parent', :postgresql do
+ let(:current_user) { create :user }
+ let(:subgroup) { create(:group, :private, parent: group)}
+
+ before do
+ group.add_owner(owner)
+ subgroup.add_owner(current_user)
+ end
+
+ it do
+ expect_allowed(:destroy_group_member)
+ expect_allowed(:update_group_member)
+ end
+ end
+
+ context 'without group parent' do
+ let(:current_user) { create :user }
+ let(:subgroup) { create(:group, :private)}
+
+ before do
+ subgroup.add_owner(current_user)
+ end
+
+ it do
+ expect_disallowed(:destroy_group_member)
+ expect_disallowed(:update_group_member)
+ end
+ end
+
+ context 'without group parent with two owners' do
+ let(:current_user) { create :user }
+ let(:other_user) { create :user }
+ let(:subgroup) { create(:group, :private)}
+
+ before do
+ subgroup.add_owner(current_user)
+ subgroup.add_owner(other_user)
+ end
+
+ it do
+ expect_allowed(:destroy_group_member)
+ expect_allowed(:update_group_member)
+ end
+ end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index be1804c5ce0..59f3a961d50 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1,63 +1,7 @@
require 'spec_helper'
describe GroupPolicy do
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:maintainer) { create(:user) }
- let(:owner) { create(:user) }
- let(:admin) { create(:admin) }
- let(:group) { create(:group, :private) }
-
- let(:guest_permissions) do
- [:read_label, :read_group, :upload_file, :read_namespace, :read_group_activity,
- :read_group_issues, :read_group_boards, :read_group_labels, :read_group_milestones,
- :read_group_merge_requests]
- end
-
- let(:reporter_permissions) { [:admin_label] }
-
- let(:developer_permissions) { [:admin_milestone] }
-
- let(:maintainer_permissions) do
- [
- :create_projects,
- :read_cluster,
- :create_cluster,
- :update_cluster,
- :admin_cluster,
- :add_cluster
- ]
- end
-
- let(:owner_permissions) do
- [
- :admin_group,
- :admin_namespace,
- :admin_group_member,
- :change_visibility_level,
- :set_note_created_at,
- (Gitlab::Database.postgresql? ? :create_subgroup : nil)
- ].compact
- end
-
- before do
- group.add_guest(guest)
- group.add_reporter(reporter)
- group.add_developer(developer)
- group.add_maintainer(maintainer)
- group.add_owner(owner)
- end
-
- subject { described_class.new(current_user, group) }
-
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
+ include_context 'GroupPolicy context'
context 'with no user' do
let(:group) { create(:group, :public) }
@@ -74,6 +18,29 @@ describe GroupPolicy do
end
end
+ context 'with no user and public project' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { expect_disallowed(:read_group) }
+ end
+
+ context 'with foreign user and public project' do
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ let(:current_user) { create(:user) }
+
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { expect_disallowed(:read_group) }
+ end
+
context 'has projects' do
let(:current_user) { create(:user) }
let(:project) { create(:project, namespace: group) }
@@ -82,17 +49,13 @@ describe GroupPolicy do
project.add_developer(current_user)
end
- it do
- expect_allowed(:read_group, :read_label)
- end
+ it { expect_allowed(:read_label, :read_list) }
context 'in subgroups', :nested_groups do
let(:subgroup) { create(:group, :private, parent: group) }
let(:project) { create(:project, namespace: subgroup) }
- it do
- expect_allowed(:read_group, :read_label)
- end
+ it { expect_allowed(:read_label, :read_list) }
end
end
@@ -384,6 +347,120 @@ describe GroupPolicy do
end
end
+ context "create_projects" do
+ context 'when group has no project creation level set' do
+ let(:group) { create(:group, project_creation_level: nil) }
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+ end
+
+ context 'when group has project creation level set to no one' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS) }
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+ end
+
+ context 'when group has project creation level set to maintainer only' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) }
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+ end
+
+ context 'when group has project creation level set to developers + maintainer' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_projects) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_projects) }
+ end
+ end
+ end
+
it_behaves_like 'clusterable policies' do
let(:clusterable) { create(:group) }
let(:cluster) do
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
new file mode 100644
index 00000000000..2520469d4e7
--- /dev/null
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IdentityProviderPolicy do
+ subject(:policy) { described_class.new(user, provider) }
+ let(:user) { User.new }
+ let(:provider) { :a_provider }
+
+ describe '#rules' do
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.to be_allowed(:unlink) }
+
+ context 'when user is anonymous' do
+ let(:user) { nil }
+
+ it { is_expected.not_to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+
+ %w[saml cas3].each do |provider_name|
+ context "when provider is #{provider_name}" do
+ let(:provider) { provider_name }
+
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+ end
+ end
+end
diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb
index db3df760472..6d34b0a8b4b 100644
--- a/spec/policies/issuable_policy_spec.rb
+++ b/spec/policies/issuable_policy_spec.rb
@@ -13,7 +13,7 @@ describe IssuablePolicy, models: true do
context 'when user is able to read project' do
it 'enables user to read and update issuables' do
- expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
@@ -24,12 +24,12 @@ describe IssuablePolicy, models: true do
it 'enables user to read and update issuables' do
project.add_maintainer(user)
- expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
it 'disallows user from reading and updating issuables from that project' do
- expect(policies).to be_disallowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_disallowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 008d118b557..b149dbcf871 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe IssuePolicy do
+ include ExternalAuthorizationServiceHelpers
+
let(:guest) { create(:user) }
let(:author) { create(:user) }
let(:assignee) { create(:user) }
@@ -204,4 +206,21 @@ describe IssuePolicy do
end
end
end
+
+ context 'with external authorization enabled' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+ let(:policies) { described_class.new(user, issue) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'can read the issue iid without accessing the external service' do
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ expect(policies).to be_allowed(:read_issue_iid)
+ end
+ end
end
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
new file mode 100644
index 00000000000..81279225d61
--- /dev/null
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe MergeRequestPolicy do
+ include ExternalAuthorizationServiceHelpers
+
+ let(:guest) { create(:user) }
+ let(:author) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ def permissions(user, merge_request)
+ described_class.new(user, merge_request)
+ end
+
+ before do
+ project.add_guest(guest)
+ project.add_guest(author)
+ project.add_developer(developer)
+ end
+
+ context 'when merge request is unlocked' do
+ let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
+
+ it 'allows author to reopen merge request' do
+ expect(permissions(author, merge_request)).to be_allowed(:reopen_merge_request)
+ end
+
+ it 'allows developer to reopen merge request' do
+ expect(permissions(developer, merge_request)).to be_allowed(:reopen_merge_request)
+ end
+
+ it 'prevents guest from reopening merge request' do
+ expect(permissions(guest, merge_request)).to be_disallowed(:reopen_merge_request)
+ end
+ end
+
+ context 'when merge request is locked' do
+ let(:merge_request_locked) { create(:merge_request, :closed, discussion_locked: true, source_project: project, target_project: project, author: author) }
+
+ it 'prevents author from reopening merge request' do
+ expect(permissions(author, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+
+ it 'prevents developer from reopening merge request' do
+ expect(permissions(developer, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+
+ it 'prevents guests from reopening merge request' do
+ expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+ end
+
+ context 'with external authorization enabled' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:policies) { described_class.new(user, merge_request) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'can read the issue iid without accessing the external service' do
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ expect(policies).to be_allowed(:read_merge_request_iid)
+ end
+ end
+end
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 1fdf95ad716..99fa8b1fe44 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -30,7 +30,7 @@ describe NamespacePolicy do
context 'user who has exceeded project limit' do
let(:owner) { create(:user, projects_limit: 0) }
- it { is_expected.not_to be_allowed(:create_projects) }
+ it { is_expected.to be_disallowed(:create_projects) }
end
end
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index 0e848c74659..4be7a0266d1 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -1,28 +1,15 @@
require 'spec_helper'
-describe NotePolicy, mdoels: true do
+describe NotePolicy do
describe '#rules' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
-
- def policies(noteable = nil)
- return @policies if @policies
-
- noteable ||= issue
- note = if noteable.is_a?(Commit)
- create(:note_on_commit, commit_id: noteable.id, author: user, project: project)
- else
- create(:note, noteable: noteable, author: user, project: project)
- end
-
- @policies = described_class.new(user, note)
- end
+ let(:noteable) { issue }
+ let(:policy) { described_class.new(user, note) }
+ let(:note) { create(:note, noteable: noteable, author: user, project: project) }
shared_examples_for 'a discussion with a private noteable' do
- let(:noteable) { issue }
- let(:policy) { policies(noteable) }
-
context 'when the note author can no longer see the noteable' do
it 'can not edit nor read the note' do
expect(policy).to be_disallowed(:admin_note)
@@ -46,12 +33,21 @@ describe NotePolicy, mdoels: true do
end
end
- context 'when the project is private' do
- let(:project) { create(:project, :private, :repository) }
+ context 'when the noteable is a commit' do
+ let(:commit) { project.repository.head_commit }
+ let(:note) { create(:note_on_commit, commit_id: commit.id, author: user, project: project) }
+
+ context 'when the project is private' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it_behaves_like 'a discussion with a private noteable'
+ end
- context 'when the noteable is a commit' do
- it_behaves_like 'a discussion with a private noteable' do
- let(:noteable) { project.repository.head_commit }
+ context 'when the project is public' do
+ context 'when repository access level is private' do
+ let(:project) { create(:project, :public, :repository, :repository_private) }
+
+ it_behaves_like 'a discussion with a private noteable'
end
end
end
@@ -59,44 +55,44 @@ describe NotePolicy, mdoels: true do
context 'when the project is public' do
context 'when the note author is not a project member' do
it 'can edit a note' do
- expect(policies).to be_allowed(:admin_note)
- expect(policies).to be_allowed(:resolve_note)
- expect(policies).to be_allowed(:read_note)
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
end
end
context 'when the noteable is a project snippet' do
- it 'can edit note' do
- policies = policies(create(:project_snippet, :public, project: project))
+ let(:noteable) { create(:project_snippet, :public, project: project) }
- expect(policies).to be_allowed(:admin_note)
- expect(policies).to be_allowed(:resolve_note)
- expect(policies).to be_allowed(:read_note)
+ it 'can edit note' do
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
end
context 'when it is private' do
- it_behaves_like 'a discussion with a private noteable' do
- let(:noteable) { create(:project_snippet, :private, project: project) }
- end
+ let(:noteable) { create(:project_snippet, :private, project: project) }
+
+ it_behaves_like 'a discussion with a private noteable'
end
end
context 'when the noteable is a personal snippet' do
- it 'can edit note' do
- policies = policies(create(:personal_snippet, :public))
+ let(:noteable) { create(:personal_snippet, :public) }
- expect(policies).to be_allowed(:admin_note)
- expect(policies).to be_allowed(:resolve_note)
- expect(policies).to be_allowed(:read_note)
+ it 'can edit note' do
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
end
context 'when it is private' do
- it 'can not edit nor read the note' do
- policies = policies(create(:personal_snippet, :private))
+ let(:noteable) { create(:personal_snippet, :private) }
- expect(policies).to be_disallowed(:admin_note)
- expect(policies).to be_disallowed(:resolve_note)
- expect(policies).to be_disallowed(:read_note)
+ it 'can not edit nor read the note' do
+ expect(policy).to be_disallowed(:admin_note)
+ expect(policy).to be_disallowed(:resolve_note)
+ expect(policy).to be_disallowed(:read_note)
end
end
end
@@ -120,20 +116,20 @@ describe NotePolicy, mdoels: true do
end
it 'can edit a note' do
- expect(policies).to be_allowed(:admin_note)
- expect(policies).to be_allowed(:resolve_note)
- expect(policies).to be_allowed(:read_note)
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
end
end
context 'when the note author is not a project member' do
it 'can not edit a note' do
- expect(policies).to be_disallowed(:admin_note)
- expect(policies).to be_disallowed(:resolve_note)
+ expect(policy).to be_disallowed(:admin_note)
+ expect(policy).to be_disallowed(:resolve_note)
end
it 'can read a note' do
- expect(policies).to be_allowed(:read_note)
+ expect(policy).to be_allowed(:read_note)
end
end
end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index a38e0dbd797..097000ceb6a 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -14,13 +14,6 @@ describe PersonalSnippetPolicy do
]
end
- let(:comment_permissions) do
- [
- :comment_personal_snippet,
- :create_note
- ]
- end
-
def permissions(user)
described_class.new(user, snippet)
end
@@ -33,7 +26,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -44,7 +37,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_allowed(*comment_permissions)
+ is_expected.to be_allowed(:create_note)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -55,7 +48,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_allowed(*comment_permissions)
+ is_expected.to be_allowed(:create_note)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
@@ -70,7 +63,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -81,7 +74,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_allowed(*comment_permissions)
+ is_expected.to be_allowed(:create_note)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -92,7 +85,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -103,7 +96,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_allowed(*comment_permissions)
+ is_expected.to be_allowed(:create_note)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
@@ -118,7 +111,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -129,7 +122,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -140,7 +133,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -151,7 +144,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
- is_expected.to be_disallowed(*comment_permissions)
+ is_expected.to be_disallowed(:create_note)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
@@ -162,7 +155,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
- is_expected.to be_allowed(*comment_permissions)
+ is_expected.to be_allowed(:create_note)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 93a468f585b..8075fcade5f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe ProjectPolicy do
+ include ExternalAuthorizationServiceHelpers
+ include_context 'ProjectPolicy context'
set(:guest) { create(:user) }
set(:reporter) { create(:user) }
set(:developer) { create(:user) }
@@ -45,10 +47,10 @@ describe ProjectPolicy do
let(:base_maintainer_permissions) do
%i[
push_to_delete_protected_branch update_project_snippet update_environment
- update_deployment admin_project_snippet
- admin_project_member admin_note admin_wiki admin_project
+ update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
+ daily_statistics
]
end
@@ -130,22 +132,26 @@ describe ProjectPolicy do
subject { described_class.new(owner, project) }
context 'when the feature is disabled' do
- it 'does not include the issues permissions' do
+ before do
project.issues_enabled = false
project.save!
+ end
+ it 'does not include the issues permissions' do
expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue
end
- end
- context 'when the feature is disabled and external tracker configured' do
- it 'does not include the issues permissions' do
- create(:jira_service, project: project)
+ it 'disables boards and lists permissions' do
+ expect_disallowed :read_board, :create_board, :update_board
+ expect_disallowed :read_list, :create_list, :update_list, :admin_list
+ end
- project.issues_enabled = false
- project.save!
+ context 'when external tracker configured' do
+ it 'does not include the issues permissions' do
+ create(:jira_service, project: project)
- expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue
+ expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue
+ end
end
end
end
@@ -233,237 +239,6 @@ describe ProjectPolicy do
end
end
- shared_examples 'archived project policies' do
- let(:feature_write_abilities) do
- described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
- described_class.create_update_admin_destroy(feature)
- end
- end
-
- let(:other_write_abilities) do
- %i[
- create_merge_request_in
- create_merge_request_from
- push_to_delete_protected_branch
- push_code
- request_access
- upload_file
- resolve_note
- award_emoji
- ]
- end
-
- context 'when the project is archived' do
- before do
- project.archived = true
- end
-
- it 'disables write actions on all relevant project features' do
- expect_disallowed(*feature_write_abilities)
- end
-
- it 'disables some other important write actions' do
- expect_disallowed(*other_write_abilities)
- end
-
- it 'does not disable other abilities' do
- expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
- end
- end
- end
-
- shared_examples 'project policies as anonymous' do
- context 'abilities for public projects' do
- context 'when a project has pending invites' do
- let(:group) { create(:group, :public) }
- let(:project) { create(:project, :public, namespace: group) }
- let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
- let(:anonymous_permissions) { guest_permissions - user_permissions }
-
- subject { described_class.new(nil, project) }
-
- before do
- create(:group_member, :invited, group: group)
- end
-
- it 'does not grant owner access' do
- expect_allowed(*anonymous_permissions)
- expect_disallowed(*user_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { anonymous_permissions }
- end
- end
- end
-
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(nil, project) }
-
- it { is_expected.to be_banned }
- end
- end
-
- shared_examples 'project policies as guest' do
- subject { described_class.new(guest, project) }
-
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
- let(:reporter_public_build_permissions) do
- reporter_permissions - [:read_build, :read_pipeline]
- end
-
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(*reporter_public_build_permissions)
- expect_disallowed(*team_member_reporter_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { guest_permissions }
- end
-
- context 'public builds enabled' do
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(:read_build, :read_pipeline)
- end
- end
-
- context 'when public builds disabled' do
- before do
- project.update(public_builds: false)
- end
-
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(:read_build, :read_pipeline)
- end
- end
-
- context 'when builds are disabled' do
- before do
- project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
- end
-
- it do
- expect_disallowed(:read_build)
- expect_allowed(:read_pipeline)
- end
- end
- end
- end
-
- shared_examples 'project policies as reporter' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(reporter, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { reporter_permissions }
- end
- end
- end
-
- shared_examples 'project policies as developer' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(developer, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { developer_permissions }
- end
- end
- end
-
- shared_examples 'project policies as maintainer' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(maintainer, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { maintainer_permissions }
- end
- end
- end
-
- shared_examples 'project policies as owner' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(owner, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_allowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { owner_permissions }
- end
- end
- end
-
- shared_examples 'project policies as admin' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(admin, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_disallowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_allowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { owner_permissions }
- end
- end
- end
-
it_behaves_like 'project policies as anonymous'
it_behaves_like 'project policies as guest'
it_behaves_like 'project policies as reporter'
@@ -518,4 +293,56 @@ describe ProjectPolicy do
projects: [clusterable])
end
end
+
+ context 'reading a project' do
+ it 'allows access when a user has read access to the repo' do
+ expect(described_class.new(owner, project)).to be_allowed(:read_project)
+ expect(described_class.new(developer, project)).to be_allowed(:read_project)
+ expect(described_class.new(admin, project)).to be_allowed(:read_project)
+ end
+
+ it 'never checks the external service' do
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ expect(described_class.new(owner, project)).to be_allowed(:read_project)
+ end
+
+ context 'with an external authorization service' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'allows access when the external service allows it' do
+ external_service_allow_access(owner, project)
+ external_service_allow_access(developer, project)
+
+ expect(described_class.new(owner, project)).to be_allowed(:read_project)
+ expect(described_class.new(developer, project)).to be_allowed(:read_project)
+ end
+
+ it 'does not check the external service for admins and allows access' do
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ expect(described_class.new(admin, project)).to be_allowed(:read_project)
+ end
+
+ it 'prevents all but seeing a public project in a list when access is denied' do
+ [developer, owner, build(:user), nil].each do |user|
+ external_service_deny_access(user, project)
+ policy = described_class.new(user, project)
+
+ expect(policy).not_to be_allowed(:read_project)
+ expect(policy).not_to be_allowed(:owner_access)
+ expect(policy).not_to be_allowed(:change_namespace)
+ end
+ end
+
+ it 'passes the full path to external authorization for logging purposes' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(owner, 'default_label', project.full_path).and_call_original
+
+ described_class.new(owner, project).allowed?(:read_project)
+ end
+ end
+ end
end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index d6329e84579..2e9ef1e89fd 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -5,7 +5,7 @@ describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:project) { create(:project, :public) }
-
+ let(:snippet) { create(:project_snippet, snippet_visibility, project: project) }
let(:author_permissions) do
[
:update_project_snippet,
@@ -13,23 +13,13 @@ describe ProjectSnippetPolicy do
]
end
- def abilities(user, snippet_visibility)
- snippet = create(:project_snippet, snippet_visibility, project: project)
-
- described_class.new(user, snippet)
- end
-
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
+ subject { described_class.new(current_user, snippet) }
context 'public snippet' do
+ let(:snippet_visibility) { :public }
+
context 'no user' do
- subject { abilities(nil, :public) }
+ let(:current_user) { nil }
it do
expect_allowed(:read_project_snippet)
@@ -38,7 +28,7 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :public) }
+ let(:current_user) { regular_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -47,7 +37,7 @@ describe ProjectSnippetPolicy do
end
context 'external user' do
- subject { abilities(external_user, :public) }
+ let(:current_user) { external_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -57,8 +47,10 @@ describe ProjectSnippetPolicy do
end
context 'internal snippet' do
+ let(:snippet_visibility) { :internal }
+
context 'no user' do
- subject { abilities(nil, :internal) }
+ let(:current_user) { nil }
it do
expect_disallowed(:read_project_snippet)
@@ -67,7 +59,7 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :internal) }
+ let(:current_user) { regular_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -76,31 +68,31 @@ describe ProjectSnippetPolicy do
end
context 'external user' do
- subject { abilities(external_user, :internal) }
+ let(:current_user) { external_user }
it do
expect_disallowed(:read_project_snippet, :create_note)
expect_disallowed(*author_permissions)
end
- end
- context 'project team member external user' do
- subject { abilities(external_user, :internal) }
-
- before do
- project.add_developer(external_user)
- end
+ context 'project team member' do
+ before do
+ project.add_developer(external_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
end
context 'private snippet' do
+ let(:snippet_visibility) { :private }
+
context 'no user' do
- subject { abilities(nil, :private) }
+ let(:current_user) { nil }
it do
expect_disallowed(:read_project_snippet)
@@ -109,53 +101,52 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :private) }
+ let(:current_user) { regular_user }
it do
expect_disallowed(:read_project_snippet, :create_note)
expect_disallowed(*author_permissions)
end
- end
-
- context 'snippet author' do
- let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
- subject { described_class.new(regular_user, snippet) }
+ context 'snippet author' do
+ let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_allowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_allowed(*author_permissions)
+ end
end
- end
- context 'project team member normal user' do
- subject { abilities(regular_user, :private) }
-
- before do
- project.add_developer(regular_user)
- end
+ context 'project team member normal user' do
+ before do
+ project.add_developer(regular_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
- context 'project team member external user' do
- subject { abilities(external_user, :private) }
+ context 'external user' do
+ context 'project team member' do
+ let(:current_user) { external_user }
- before do
- project.add_developer(external_user)
- end
+ before do
+ project.add_developer(external_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
context 'admin user' do
- subject { abilities(create(:admin), :private) }
+ let(:snippet_visibility) { :private }
+ let(:current_user) { create(:admin) }
it do
expect_allowed(:read_project_snippet, :create_note)
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index bb1db9a3d51..eacf383be7d 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -14,6 +14,16 @@ describe BlobPresenter, :seed_helper do
end
let(:blob) { Blob.new(git_blob) }
+ describe '.web_url' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) }
+
+ subject { described_class.new(blob) }
+
+ it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/blob/#{blob.commit_id}/#{blob.path}") }
+ end
+
describe '#highlight' do
subject { described_class.new(blob) }
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
new file mode 100644
index 00000000000..7ece5f623ce
--- /dev/null
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Blobs::UnfoldPresenter do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'foo', data: "1\n2\n3") }
+ let(:subject) { described_class.new(blob, params) }
+
+ describe '#initialize' do
+ context 'when full is false' do
+ let(:params) { { full: false, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
+
+ it 'sets attributes' do
+ result = subject
+
+ expect(result.full?).to eq(false)
+ expect(result.since).to eq(2)
+ expect(result.to).to eq(3)
+ expect(result.bottom).to eq(false)
+ expect(result.offset).to eq(1)
+ expect(result.indent).to eq(1)
+ end
+ end
+
+ context 'when full is true' do
+ let(:params) { { full: true, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
+
+ it 'sets other attributes' do
+ result = subject
+
+ expect(result.full?).to eq(true)
+ expect(result.since).to eq(1)
+ expect(result.to).to eq(blob.lines.size)
+ expect(result.bottom).to eq(false)
+ expect(result.offset).to eq(0)
+ expect(result.indent).to eq(0)
+ end
+ end
+ end
+
+ describe '#diff_lines' do
+ let(:total_lines) { 50 }
+ let(:blob) { fake_blob(path: 'foo', data: (1..total_lines).to_a.join("\n")) }
+
+ context 'when "full" is true' do
+ let(:params) { { full: true } }
+
+ it 'returns all lines' do
+ lines = subject.diff_lines
+
+ expect(lines.size).to eq(total_lines)
+
+ lines.each.with_index do |line, index|
+ expect(line.text).to include("LC#{index + 1}")
+ expect(line.text).to eq(line.rich_text)
+ expect(line.type).to be_nil
+ end
+ end
+
+ context 'when last line is empty' do
+ let(:blob) { fake_blob(path: 'foo', data: "1\n2\n") }
+
+ it 'disregards last line' do
+ lines = subject.diff_lines
+
+ expect(lines.size).to eq(2)
+ end
+ end
+ end
+
+ context 'when "since" is equal to 1' do
+ let(:params) { { since: 1, to: 10, offset: 10 } }
+
+ it 'does not add top match line' do
+ line = subject.diff_lines.first
+
+ expect(line.type).to be_nil
+ end
+ end
+
+ context 'when since is greater than 1' do
+ let(:params) { { since: 5, to: 10, offset: 10 } }
+
+ it 'adds top match line' do
+ line = subject.diff_lines.first
+
+ expect(line.type).to eq('match')
+ expect(line.old_pos).to eq(5)
+ expect(line.new_pos).to eq(5)
+ end
+ end
+
+ context 'when "to" is less than blob size' do
+ let(:params) { { since: 1, to: 5, offset: 10, bottom: true } }
+
+ it 'adds bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to eq('match')
+ expect(line.old_pos).to eq(-5)
+ expect(line.new_pos).to eq(5)
+ end
+ end
+
+ context 'when "to" is equal to blob size' do
+ let(:params) { { since: 1, to: total_lines, offset: 10, bottom: true } }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+ end
+ end
+
+ describe '#lines' do
+ context 'when scope is specified' do
+ let(:params) { { since: 2, to: 3 } }
+
+ it 'returns lines cropped by params' do
+ expect(subject.lines.size).to eq(2)
+ expect(subject.lines[0]).to include('LC2')
+ expect(subject.lines[1]).to include('LC3')
+ end
+ end
+
+ context 'when full is true' do
+ let(:params) { { full: true } }
+
+ it 'returns all lines' do
+ expect(subject.lines.size).to eq(3)
+ expect(subject.lines[0]).to include('LC1')
+ expect(subject.lines[1]).to include('LC2')
+ expect(subject.lines[2]).to include('LC3')
+ end
+ end
+ end
+
+ describe '#match_line_text' do
+ context 'when bottom is true' do
+ let(:params) { { since: 2, to: 3, bottom: true } }
+
+ it 'returns empty string' do
+ expect(subject.match_line_text).to eq('')
+ end
+ end
+
+ context 'when bottom is false' do
+ let(:params) { { since: 2, to: 3, bottom: false } }
+
+ it 'returns match line string' do
+ expect(subject.match_line_text).to eq("@@ -2,1+2,1 @@")
+ end
+ end
+ end
+end
diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb
new file mode 100644
index 00000000000..986818a7b9e
--- /dev/null
+++ b/spec/presenters/ci/bridge_presenter_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Ci::BridgePresenter do
+ set(:project) { create(:project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+ set(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) }
+
+ subject(:presenter) do
+ described_class.new(bridge)
+ end
+
+ it 'presents information about recoverable state' do
+ expect(presenter).to be_recoverable
+ end
+end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 676835b3880..e202f7a9b5f 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -180,7 +180,7 @@ describe Ci::BuildPresenter do
context 'When build has failed and retried' do
let(:build) { create(:ci_build, :script_failure, :retried, pipeline: pipeline) }
- it 'should include the reason of failure and the retried title' do
+ it 'includes the reason of failure and the retried title' do
tooltip = subject.tooltip_message
expect(tooltip).to eq("#{build.name} - failed - (script failure) (retried)")
@@ -190,7 +190,7 @@ describe Ci::BuildPresenter do
context 'When build has failed and is allowed to' do
let(:build) { create(:ci_build, :script_failure, :allowed_to_fail, pipeline: pipeline) }
- it 'should include the reason of failure' do
+ it 'includes the reason of failure' do
tooltip = subject.tooltip_message
expect(tooltip).to eq("#{build.name} - failed - (script failure) (allowed to fail)")
@@ -200,7 +200,7 @@ describe Ci::BuildPresenter do
context 'For any other build (no retried)' do
let(:build) { create(:ci_build, :success, pipeline: pipeline) }
- it 'should include build name and status' do
+ it 'includes build name and status' do
tooltip = subject.tooltip_message
expect(tooltip).to eq("#{build.name} - passed")
@@ -210,7 +210,7 @@ describe Ci::BuildPresenter do
context 'For any other build (retried)' do
let(:build) { create(:ci_build, :success, :retried, pipeline: pipeline) }
- it 'should include build name and status' do
+ it 'includes build name and status' do
tooltip = subject.tooltip_message
expect(tooltip).to eq("#{build.name} - passed (retried)")
@@ -269,7 +269,7 @@ describe Ci::BuildPresenter do
context 'when is a script or missing dependency failure' do
let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure) }
- it 'should return false' do
+ it 'returns false' do
failure_reasons.each do |failure_reason|
build.update_attribute(:failure_reason, failure_reason)
expect(presenter.recoverable?).to be_falsy
@@ -280,7 +280,7 @@ describe Ci::BuildPresenter do
context 'when is any other failure type' do
let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) }
- it 'should return true' do
+ it 'returns true' do
failure_reasons.each do |failure_reason|
build.update_attribute(:failure_reason, failure_reason)
expect(presenter.recoverable?).to be_truthy
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index f50bcf54b46..9ed8e3a4e0a 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -119,23 +119,23 @@ describe Ci::BuildRunnerPresenter do
end
describe '#git_depth' do
- subject { presenter.git_depth }
-
let(:build) { create(:ci_build) }
- it 'returns the correct git depth' do
- is_expected.to eq(0)
- end
+ subject(:git_depth) { presenter.git_depth }
context 'when GIT_DEPTH variable is specified' do
before do
create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
end
- it 'returns the correct git depth' do
- is_expected.to eq(1)
+ it 'returns its value' do
+ expect(git_depth).to eq(1)
end
end
+
+ it 'defaults to git depth setting for the project' do
+ expect(git_depth).to eq(build.project.default_git_depth)
+ end
end
describe '#refspecs' do
@@ -144,24 +144,56 @@ describe Ci::BuildRunnerPresenter do
let(:build) { create(:ci_build) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
- context 'when GIT_DEPTH variable is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, :tag) }
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
end
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.first }
+ let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
+ is_expected
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head')
+ end
+
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct refspecs' do
+ is_expected
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head',
+ '+refs/heads/*:refs/remotes/origin/*',
+ '+refs/tags/*:refs/tags/*')
+ end
end
- context 'when ref is tag' do
- let(:build) { create(:ci_build, :tag) }
+ context 'when pipeline is legacy detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
end
end
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index f7ceaf844be..cda07a0ae09 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
describe Ci::PipelinePresenter do
+ include Gitlab::Routing
+
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -8,6 +11,11 @@ describe Ci::PipelinePresenter do
described_class.new(pipeline)
end
+ before do
+ project.add_developer(user)
+ allow(presenter).to receive(:current_user) { user }
+ end
+
it 'inherits from Gitlab::View::Presenter::Delegated' do
expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
end
@@ -68,4 +76,130 @@ describe Ci::PipelinePresenter do
end
end
end
+
+ describe '#ref_text' do
+ subject { presenter.ref_text }
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+ "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a>")
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+ "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a> " \
+ "into <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.target_project, merge_request.target_branch)}\">#{merge_request.target_branch}</a>")
+ end
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when ref exists in the repository' do
+ before do
+ allow(pipeline).to receive(:ref_exists?) { true }
+ end
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"ref-name\" href=\"#{project_commits_path(pipeline.project, pipeline.ref)}\">#{pipeline.ref}</a>")
+ end
+
+ context 'when ref contains malicious script' do
+ let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+ it 'does not include the malicious script' do
+ is_expected.not_to include("<script>alter('1')</script>")
+ end
+ end
+ end
+
+ context 'when ref exists in the repository' do
+ before do
+ allow(pipeline).to receive(:ref_exists?) { false }
+ end
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <span class=\"ref-name\">#{pipeline.ref}</span>")
+ end
+
+ context 'when ref contains malicious script' do
+ let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+ it 'does not include the malicious script' do
+ is_expected.not_to include("<script>alter('1')</script>")
+ end
+ end
+ end
+ end
+ end
+
+ describe '#link_to_merge_request' do
+ subject { presenter.link_to_merge_request }
+
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_merge_request_path(merge_request.project, merge_request))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#link_to_merge_request_source_branch' do
+ subject { presenter.link_to_merge_request_source_branch }
+
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_commits_path(merge_request.source_project,
+ merge_request.source_branch))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#link_to_merge_request_target_branch' do
+ subject { presenter.link_to_merge_request_target_branch }
+
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 754ba0a594c..7054a70e2ed 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -158,46 +158,6 @@ describe Clusters::ClusterPresenter do
it { is_expected.to include(cluster.name) }
end
- describe '#can_toggle_cluster' do
- let(:user) { create(:user) }
-
- before do
- allow(cluster).to receive(:current_user).and_return(user)
- end
-
- subject { described_class.new(cluster).can_toggle_cluster? }
-
- context 'when user can update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(true)
- end
-
- context 'when cluster is created' do
- before do
- allow(cluster).to receive(:created?).and_return(true)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when cluster is not created' do
- before do
- allow(cluster).to receive(:created?).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- context 'when user can not update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#cluster_type_description' do
subject { described_class.new(cluster).cluster_type_description }
@@ -210,6 +170,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq('Group cluster') }
end
+
+ context 'instance_type cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ it { is_expected.to eq('Instance cluster') }
+ end
end
describe '#show_path' do
@@ -227,5 +193,27 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
+
+ context 'instance_type cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
+
+ it { is_expected.to eq(admin_cluster_path(cluster)) }
+ end
+ end
+
+ describe '#read_only_kubernetes_platform_fields?' do
+ subject { described_class.new(cluster).read_only_kubernetes_platform_fields? }
+
+ context 'with a user-provided cluster' do
+ let(:cluster) { build_stubbed(:cluster, :provided_by_user) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a GCP-provided cluster' do
+ let(:cluster) { build_stubbed(:cluster, :provided_by_gcp) }
+
+ it { is_expected.to be_truthy }
+ end
end
end
diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb
index 205160742bf..fa77273f6aa 100644
--- a/spec/presenters/group_clusterable_presenter_spec.rb
+++ b/spec/presenters/group_clusterable_presenter_spec.rb
@@ -69,6 +69,14 @@ describe GroupClusterablePresenter do
it { is_expected.to eq(install_applications_group_cluster_path(group, cluster, application)) }
end
+ describe '#update_applications_cluster_path' do
+ let(:application) { :helm }
+
+ subject { presenter.update_applications_cluster_path(cluster, application) }
+
+ it { is_expected.to eq(update_applications_group_cluster_path(group, cluster, application)) }
+ end
+
describe '#cluster_path' do
subject { presenter.cluster_path(cluster) }
diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb
new file mode 100644
index 00000000000..8e24559341b
--- /dev/null
+++ b/spec/presenters/issue_presenter_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IssuePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:issue) { create(:issue, project: project) }
+ let(:presenter) { described_class.new(issue, current_user: user) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#web_url' do
+ it 'returns correct path' do
+ expect(presenter.web_url).to eq "http://localhost/#{group.name}/#{project.name}/issues/#{issue.iid}"
+ end
+ end
+
+ describe '#issue_path' do
+ it 'returns correct path' do
+ expect(presenter.issue_path).to eq "/#{group.name}/#{project.name}/issues/#{issue.iid}"
+ end
+ end
+end
diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb
new file mode 100644
index 00000000000..d566da7c872
--- /dev/null
+++ b/spec/presenters/label_presenter_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe LabelPresenter do
+ include Gitlab::Routing.url_helpers
+
+ set(:group) { create(:group) }
+ set(:project) { create(:project, group: group) }
+ let(:label) { build_stubbed(:label, project: project).present(issuable_subject: project) }
+ let(:group_label) { build_stubbed(:group_label, group: group).present(issuable_subject: project) }
+
+ describe '#edit_path' do
+ context 'with group label' do
+ subject { group_label.edit_path }
+
+ it { is_expected.to eq(edit_group_label_path(group, group_label)) }
+ end
+
+ context 'with project label' do
+ subject { label.edit_path }
+
+ it { is_expected.to eq(edit_project_label_path(project, label)) }
+ end
+ end
+
+ describe '#destroy_path' do
+ context 'with group label' do
+ subject { group_label.destroy_path }
+
+ it { is_expected.to eq(group_label_path(group, group_label)) }
+ end
+
+ context 'with project label' do
+ subject { label.destroy_path }
+
+ it { is_expected.to eq(project_label_path(project, label)) }
+ end
+ end
+
+ describe '#filter_path' do
+ context 'with group as context subject' do
+ let(:label_in_group) { build_stubbed(:label, project: project).present(issuable_subject: group) }
+ subject { label_in_group.filter_path }
+
+ it { is_expected.to eq(issues_group_path(group, label_name: [label_in_group.title])) }
+ end
+
+ context 'with project as context subject' do
+ subject { label.filter_path }
+
+ it { is_expected.to eq(namespace_project_issues_path(group, project, label_name: [label.title])) }
+ end
+ end
+
+ describe '#can_subscribe_to_label_in_different_levels?' do
+ it 'returns true for group labels in project context' do
+ expect(group_label.can_subscribe_to_label_in_different_levels?).to be_truthy
+ end
+
+ it 'returns false for project labels in project context' do
+ expect(label.can_subscribe_to_label_in_different_levels?).to be_falsey
+ end
+ end
+
+ describe '#project_label?' do
+ context 'with group label' do
+ subject { group_label.project_label? }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'with project label' do
+ subject { label.project_label? }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#subject_name' do
+ context 'with group label' do
+ subject { group_label.subject_name }
+
+ it { is_expected.to eq(group_label.group.name) }
+ end
+
+ context 'with project label' do
+ subject { label.subject_name }
+
+ it { is_expected.to eq(label.project.name) }
+ end
+ end
+end
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index bafcddebbb7..6408b0bd748 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe MergeRequestPresenter do
- let(:resource) { create :merge_request, source_project: project }
- let(:project) { create :project }
+ let(:resource) { create(:merge_request, source_project: project) }
+ let(:project) { create(:project) }
let(:user) { create(:user) }
describe '#ci_status' do
@@ -40,8 +40,8 @@ describe MergeRequestPresenter do
allow(pipeline).to receive(:has_warnings?) { true }
end
- it 'returns "success_with_warnings"' do
- is_expected.to eq('success_with_warnings')
+ it 'returns "success-with-warnings"' do
+ is_expected.to eq('success-with-warnings')
end
end
@@ -207,25 +207,25 @@ describe MergeRequestPresenter do
end
end
- describe '#cancel_merge_when_pipeline_succeeds_path' do
+ describe '#cancel_auto_merge_path' do
subject do
described_class.new(resource, current_user: user)
- .cancel_merge_when_pipeline_succeeds_path
+ .cancel_auto_merge_path
end
context 'when can cancel mwps' do
it 'returns path' do
- allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?)
+ allow(resource).to receive(:can_cancel_auto_merge?)
.with(user)
.and_return(true)
- is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_merge_when_pipeline_succeeds")
+ is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_auto_merge")
end
end
context 'when cannot cancel mwps' do
it 'returns nil' do
- allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?)
+ allow(resource).to receive(:can_cancel_auto_merge?)
.with(user)
.and_return(false)
@@ -345,6 +345,30 @@ describe MergeRequestPresenter do
end
end
+ describe '#source_branch_commits_path' do
+ subject do
+ described_class.new(resource, current_user: user)
+ .source_branch_commits_path
+ end
+
+ context 'when source branch exists' do
+ it 'returns path' do
+ allow(resource).to receive(:source_branch_exists?) { true }
+
+ is_expected
+ .to eq("/#{resource.source_project.full_path}/commits/#{resource.source_branch}")
+ end
+ end
+
+ context 'when source branch does not exist' do
+ it 'returns nil' do
+ allow(resource).to receive(:source_branch_exists?) { false }
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
describe '#target_branch_tree_path' do
subject do
described_class.new(resource, current_user: user)
@@ -379,7 +403,7 @@ describe MergeRequestPresenter do
allow(resource).to receive(:source_branch_exists?) { true }
is_expected
- .to eq("/#{resource.source_project.full_path}/branches/#{resource.source_branch}")
+ .to eq("/#{resource.source_project.full_path}/-/branches/#{resource.source_branch}")
end
end
@@ -392,6 +416,75 @@ describe MergeRequestPresenter do
end
end
+ describe '#target_branch_path' do
+ subject do
+ described_class.new(resource, current_user: user).target_branch_path
+ end
+
+ context 'when target branch exists' do
+ it 'returns path' do
+ allow(resource).to receive(:target_branch_exists?) { true }
+
+ is_expected
+ .to eq("/#{resource.source_project.full_path}/-/branches/#{resource.target_branch}")
+ end
+ end
+
+ context 'when target branch does not exist' do
+ it 'returns nil' do
+ allow(resource).to receive(:target_branch_exists?) { false }
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#source_branch_link' do
+ subject { presenter.source_branch_link }
+
+ let(:presenter) { described_class.new(resource, current_user: user) }
+
+ context 'when source branch exists' do
+ it 'returns link' do
+ allow(resource).to receive(:source_branch_exists?) { true }
+
+ is_expected
+ .to eq("<a class=\"ref-name\" href=\"#{presenter.source_branch_commits_path}\">#{presenter.source_branch}</a>")
+ end
+ end
+
+ context 'when source branch does not exist' do
+ it 'returns text' do
+ allow(resource).to receive(:source_branch_exists?) { false }
+
+ is_expected.to eq("<span class=\"ref-name\">#{presenter.source_branch}</span>")
+ end
+ end
+ end
+
+ describe '#target_branch_link' do
+ subject { presenter.target_branch_link }
+
+ let(:presenter) { described_class.new(resource, current_user: user) }
+
+ context 'when target branch exists' do
+ it 'returns link' do
+ allow(resource).to receive(:target_branch_exists?) { true }
+
+ is_expected
+ .to eq("<a class=\"ref-name\" href=\"#{presenter.target_branch_commits_path}\">#{presenter.target_branch}</a>")
+ end
+ end
+
+ context 'when target branch does not exist' do
+ it 'returns text' do
+ allow(resource).to receive(:target_branch_exists?) { false }
+
+ is_expected.to eq("<span class=\"ref-name\">#{presenter.target_branch}</span>")
+ end
+ end
+ end
+
describe '#source_branch_with_namespace_link' do
subject do
described_class.new(resource, current_user: user).source_branch_with_namespace_link
@@ -476,4 +569,46 @@ describe MergeRequestPresenter do
end
end
end
+
+ describe '#can_push_to_source_branch' do
+ before do
+ allow(resource).to receive(:source_branch_exists?) { source_branch_exists }
+
+ allow_any_instance_of(Gitlab::UserAccess::RequestCacheExtension)
+ .to receive(:can_push_to_branch?)
+ .with(resource.source_branch)
+ .and_return(can_push_to_branch)
+ end
+
+ subject do
+ described_class.new(resource, current_user: user).can_push_to_source_branch?
+ end
+
+ context 'when source branch exists AND user can push to source branch' do
+ let(:source_branch_exists) { true }
+ let(:can_push_to_branch) { true }
+
+ it 'returns true' do
+ is_expected.to eq(true)
+ end
+ end
+
+ context 'when source branch does not exists' do
+ let(:source_branch_exists) { false }
+ let(:can_push_to_branch) { true }
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+
+ context 'when user cannot push to source branch' do
+ let(:source_branch_exists) { true }
+ let(:can_push_to_branch) { false }
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
index c50d90ae1e8..6786a84243f 100644
--- a/spec/presenters/project_clusterable_presenter_spec.rb
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -69,6 +69,14 @@ describe ProjectClusterablePresenter do
it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) }
end
+ describe '#update_applications_cluster_path' do
+ let(:application) { :helm }
+
+ subject { presenter.update_applications_cluster_path(cluster, application) }
+
+ it { is_expected.to eq(update_applications_project_cluster_path(project, cluster, application)) }
+ end
+
describe '#cluster_path' do
subject { presenter.cluster_path(cluster) }
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 456de5f1b9a..5bf80f6e318 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -411,4 +411,23 @@ describe ProjectPresenter do
end
end
end
+
+ describe '#statistics_buttons' do
+ let(:project) { build(:project) }
+ let(:presenter) { described_class.new(project, current_user: user) }
+
+ it 'orders the items correctly' do
+ allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
+ allow(project.repository).to receive(:changelog).and_return(nil)
+ allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo'))
+ allow(presenter).to receive(:filename_path).and_return('fake/path')
+ allow(presenter).to receive(:contribution_guide_path).and_return('fake_path')
+
+ buttons = presenter.statistics_buttons(show_auto_devops_callout: false)
+ expect(buttons.map(&:label)).to start_with(
+ a_string_including('README'),
+ a_string_including('CONTRIBUTING')
+ )
+ end
+ end
end
diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb
new file mode 100644
index 00000000000..d74ee5dc28f
--- /dev/null
+++ b/spec/presenters/tree_entry_presenter_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe TreeEntryPresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:tree) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.trees.first, repository) }
+ let(:presenter) { described_class.new(tree) }
+
+ describe '.web_url' do
+ it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/tree/#{tree.commit_id}/#{tree.path}") }
+ end
+end
diff --git a/spec/rack_servers/configs/puma.rb b/spec/rack_servers/configs/puma.rb
deleted file mode 100644
index d6b6d83d648..00000000000
--- a/spec/rack_servers/configs/puma.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-# Note: this file is used for testing puma in `spec/rack_servers/puma_spec.rb` only
-# Note: as per the convention in `config/puma.example.development.rb`,
-# this file will replace `/home/git` with the actual working directory
-
-directory '/home/git'
-threads 1, 10
-queue_requests false
-pidfile '/home/git/gitlab/tmp/pids/puma.pid'
-bind 'unix:///home/git/gitlab/tmp/tests/puma.socket'
-workers 1
-preload_app!
-worker_timeout 60
-
-require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
-require_relative "/home/git/gitlab/lib/gitlab/cluster/puma_worker_killer_initializer"
-
-before_fork do
- Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options
- Gitlab::Cluster::LifecycleEvents.do_before_fork
-end
-
-Gitlab::Cluster::LifecycleEvents.set_puma_options @config.options
-on_worker_boot do
- Gitlab::Cluster::LifecycleEvents.do_worker_start
- File.write('/home/git/gitlab/tmp/tests/puma-worker-ready', Process.pid)
-end
-
-on_restart do
- Gitlab::Cluster::LifecycleEvents.do_master_restart
-end
diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb
index 431fab87857..8290473821c 100644
--- a/spec/rack_servers/puma_spec.rb
+++ b/spec/rack_servers/puma_spec.rb
@@ -1,20 +1,20 @@
# frozen_string_literal: true
-require 'fileutils'
+require 'spec_helper'
+require 'fileutils'
require 'excon'
-require 'spec_helper'
-
describe 'Puma' do
before(:all) do
- project_root = File.expand_path('../..', __dir__)
-
- config_lines = File.read('spec/rack_servers/configs/puma.rb')
- .gsub('/home/git/gitlab', project_root)
- .gsub('/home/git', project_root)
-
- config_path = File.join(project_root, "tmp/tests/puma.rb")
+ project_root = Rails.root.to_s
+ config_lines = File.read(Rails.root.join('config/puma.example.development.rb'))
+ .gsub('config.ru', File.join(__dir__, 'configs/config.ru'))
+ .gsub('workers 2', 'workers 1')
+ .gsub('/home/git/gitlab.socket', File.join(project_root, 'tmp/tests/puma.socket'))
+ .gsub('on_worker_boot do', "on_worker_boot do\nFile.write('#{File.join(project_root, 'tmp/tests/puma-worker-ready')}', Process.pid)")
+ .gsub(%r{/home/git(/gitlab)?}, project_root)
+ config_path = File.join(project_root, 'tmp/tests/puma.rb')
@socket_path = File.join(project_root, 'tmp/tests/puma.socket')
File.write(config_path, config_lines)
@@ -44,11 +44,9 @@ describe 'Puma' do
end
after(:all) do
- begin
- WebMock.disable_net_connect!(allow_localhost: true)
- Process.kill('TERM', @puma_master_pid)
- rescue Errno::ESRCH
- end
+ WebMock.disable_net_connect!(allow_localhost: true)
+ Process.kill('TERM', @puma_master_pid)
+ rescue Errno::ESRCH
end
def wait_puma_boot!(master_pid, ready_file)
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index 1271324a2ba..1dd0cb4817c 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -73,7 +73,7 @@ describe API::Badges do
let(:badge) { source.badges.first }
context "as a #{type}" do
- it 'returns 200' do
+ it 'returns 200', :quarantine do
user = public_send(type)
get api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user)
@@ -193,7 +193,7 @@ describe API::Badges do
end
context 'when authenticated as a maintainer/owner' do
- it 'updates the member' do
+ it 'updates the member', :quarantine do
put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer),
params: { link_url: example_url, image_url: example_url2 }
@@ -239,7 +239,7 @@ describe API::Badges do
end
end
- context 'when authenticated as a maintainer/owner' do
+ context 'when authenticated as a maintainer/owner', :quarantine do
it 'deletes the badge' do
expect do
delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index b38cd66986f..8b503777443 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -20,9 +20,9 @@ describe API::Branches do
let(:route) { "/projects/#{project_id}/repository/branches" }
shared_examples_for 'repository branches' do
- RSpec::Matchers.define :has_merged_branch_names_count do |expected|
+ RSpec::Matchers.define :has_up_to_merged_branch_names_count do |expected|
match do |actual|
- actual[:merged_branch_names].count == expected
+ expected >= actual[:merged_branch_names].count
end
end
@@ -36,10 +36,30 @@ describe API::Branches do
expect(branch_names).to match_array(project.repository.branch_names)
end
+ def check_merge_status(json_response)
+ merged, unmerged = json_response.partition { |branch| branch['merged'] }
+ merged_branches = merged.map { |branch| branch['name'] }
+ unmerged_branches = unmerged.map { |branch| branch['name'] }
+ expect(Set.new(merged_branches)).to eq(project.repository.merged_branch_names(merged_branches + unmerged_branches))
+ expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
+ end
+
it 'determines only a limited number of merged branch names' do
- expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(2))
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ check_merge_status(json_response)
+ end
+
+ it 'merge status matches reality on paginated input' do
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ check_merge_status(json_response)
end
context 'when repository is disabled' do
diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb
deleted file mode 100644
index 6c7cb151c74..00000000000
--- a/spec/requests/api/circuit_breakers_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-describe API::CircuitBreakers do
- set(:user) { create(:user) }
- set(: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_gitlab_http_status(401)
- end
-
- it 'returns a 403 for users' do
- get api('/circuit_breakers/repository_storage', user)
-
- expect(response).to have_gitlab_http_status(403)
- end
-
- it 'returns an Array of storages' do
- get api('/circuit_breakers/repository_storage', admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_kind_of(Array)
- expect(json_response).to be_empty
- end
-
- describe 'GET circuit_breakers/repository_storage/failing' do
- it 'returns an array of failing storages' do
- get api('/circuit_breakers/repository_storage/failing', admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_kind_of(Array)
- expect(json_response).to be_empty
- end
- end
- end
-
- describe 'DELETE circuit_breakers/repository_storage' do
- it 'clears all circuit_breakers' do
- delete api('/circuit_breakers/repository_storage', admin)
-
- expect(response).to have_gitlab_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 9388343c392..b5e45f99109 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -306,7 +306,22 @@ describe API::CommitStatuses do
it 'responds with bad request status and validation errors' do
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['target_url'])
- .to include 'is blocked: Only allowed protocols are http, https'
+ .to include 'is blocked: Only allowed schemes are http, https'
+ end
+ end
+
+ context 'when target URL is an unsupported scheme' do
+ before do
+ post api(post_url, developer), params: {
+ state: 'pending',
+ target_url: 'git://example.com'
+ }
+ end
+
+ it 'responds with bad request status and validation errors' do
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['target_url'])
+ .to include 'is blocked: Only allowed schemes are http, https'
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 066f1d6862a..f104da6ebba 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require 'mime/types'
describe API::Commits do
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
@@ -317,6 +319,96 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(201)
end
end
+
+ context 'when the API user is a guest' do
+ def last_commit_id(project, branch_name)
+ project.repository.find_branch(branch_name)&.dereferenced_target&.id
+ end
+
+ let(:public_project) { create(:project, :public, :repository) }
+ let!(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ end
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+ end
+
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'when posting to a forked project the user does not have write access' do
+ let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
end
describe 'delete' do
@@ -1430,8 +1522,8 @@ describe API::Commits do
end
describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do
- let!(:project) { create(:project, :repository, :private) }
- let!(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
+ let(:project) { create(:project, :repository, :private) }
+ let(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
let(:commit) { merged_mr.merge_request_diff.commits.last }
it 'returns the correct merge request' do
@@ -1456,6 +1548,17 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(404)
end
+
+ context 'public project' do
+ let(:project) { create(:project, :repository, :public, :merge_requests_private) }
+ let(:non_member) { create(:user) }
+
+ it 'responds 403 when only members are allowed to read merge requests' do
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", non_member)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
end
describe 'GET /projects/:id/repository/commits/:sha/signature' do
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index 35c448d187d..ca1ffe3c524 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -13,7 +13,7 @@ describe API::Discussions do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) }
- it_behaves_like 'discussions API', 'projects', 'issues', 'iid' do
+ it_behaves_like 'discussions API', 'projects', 'issues', 'iid', can_reply_to_individual_notes: true do
let(:parent) { project }
let(:noteable) { issue }
let(:note) { issue_note }
@@ -37,7 +37,7 @@ describe API::Discussions do
let!(:diff_note) { create(:diff_note_on_merge_request, noteable: noteable, project: project, author: user) }
let(:parent) { project }
- it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid'
+ it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid', can_reply_to_individual_notes: true
it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid'
it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid'
end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 493d3642255..8fc7fdc8632 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -32,6 +32,7 @@ describe API::Environments do
expect(json_response.first['name']).to eq(environment.name)
expect(json_response.first['external_url']).to eq(environment.external_url)
expect(json_response.first['project'].keys).to contain_exactly(*project_data_keys)
+ expect(json_response.first).not_to have_key("last_deployment")
end
end
@@ -188,4 +189,25 @@ describe API::Environments do
end
end
end
+
+ describe 'GET /projects/:id/environments/:environment_id' do
+ context 'as member of the project' do
+ it 'returns project environments' do
+ create(:deployment, :success, project: project, environment: environment)
+
+ get api("/projects/#{project.id}/environments/#{environment.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/environment')
+ end
+ end
+
+ context 'as non member' do
+ it 'returns a 404 status code' do
+ get api("/projects/#{project.id}/environments/#{environment.id}", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 0ac23505de7..018691e8099 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -164,139 +164,4 @@ describe API::Events do
expect(json_response['message']).to eq('404 User Not Found')
end
end
-
- describe 'GET /projects/:id/events' do
- context 'when unauthenticated ' do
- it 'returns 404 for private project' do
- get api("/projects/#{private_project.id}/events")
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 200 status for a public project' do
- public_project = create(:project, :public)
-
- get api("/projects/#{public_project.id}/events")
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context 'with inaccessible events' do
- let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
- let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
- let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
- let(:public_issue) { create(:closed_issue, project: public_project, author: user) }
- let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) }
-
- it 'returns only accessible events' do
- get api("/projects/#{public_project.id}/events", non_member)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.size).to eq(1)
- end
-
- it 'returns all events when the user has access' do
- get api("/projects/#{public_project.id}/events", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.size).to eq(2)
- end
- end
-
- context 'pagination' do
- let(:public_project) { create(:project, :public) }
-
- before do
- create(:event,
- project: public_project,
- target: create(:issue, project: public_project, title: 'Issue 1'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-10'))
- create(:event,
- project: public_project,
- target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-11'))
- create(:event,
- project: public_project,
- target: create(:issue, project: public_project, title: 'Issue 2'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-12'))
- end
-
- it 'correctly returns the second page without inaccessible events' do
- get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 }
-
- titles = json_response.map { |event| event['target_title'] }
-
- expect(titles.first).to eq('Issue 1')
- expect(titles).not_to include('Confidential event')
- end
-
- it 'correctly returns the first page without inaccessible events' do
- get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 }
-
- titles = json_response.map { |event| event['target_title'] }
-
- expect(titles.first).to eq('Issue 2')
- expect(titles).not_to include('Confidential event')
- end
- end
-
- context 'when not permitted to read' do
- it 'returns 404' do
- get api("/projects/#{private_project.id}/events", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- context 'when authenticated' do
- it 'returns project events' do
- get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
-
- 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(1)
- end
-
- it 'returns 404 if project does not exist' do
- get api("/projects/1234/events", user)
-
- expect(response).to have_gitlab_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(skip_cached: false) do
- get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
- end.count
-
- create_event(merge_request2)
-
- expect do
- get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
- end.not_to exceed_all_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/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
new file mode 100644
index 00000000000..28676bb02f4
--- /dev/null
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -0,0 +1,150 @@
+require 'spec_helper'
+
+describe 'GitlabSchema configurations' do
+ include GraphqlHelpers
+
+ set(:project) { create(:project) }
+
+ shared_examples 'imposing query limits' do
+ describe '#max_complexity' do
+ context 'when complexity is too high' do
+ it 'shows an error' do
+ allow(GitlabSchema).to receive(:max_query_complexity).and_return 1
+
+ subject
+
+ expect(graphql_errors.flatten.first['message']).to include('which exceeds max complexity of 1')
+ end
+ end
+ end
+
+ describe '#max_depth' do
+ context 'when query depth is too high' do
+ it 'shows error' do
+ errors = { "message" => "Query has depth of 2, which exceeds max depth of 1" }
+ allow(GitlabSchema).to receive(:max_query_depth).and_return 1
+
+ subject
+
+ expect(graphql_errors.flatten).to include(errors)
+ end
+ end
+
+ context 'when query depth is within range' do
+ it 'has no error' do
+ allow(GitlabSchema).to receive(:max_query_depth).and_return 5
+
+ subject
+
+ expect(Array.wrap(graphql_errors).compact).to be_empty
+ end
+ end
+ end
+ end
+
+ context 'regular queries' do
+ subject do
+ query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description))
+ post_graphql(query)
+ end
+
+ it_behaves_like 'imposing query limits'
+ end
+
+ context 'multiplexed queries' do
+ let(:current_user) { nil }
+
+ subject do
+ queries = [
+ { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w(id name description)) },
+ { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } },
+ { query: graphql_query_for('project', { 'fullPath' => project.full_path }, "userPermissions { createIssue }") }
+ ]
+
+ post_multiplex(queries, current_user: current_user)
+ end
+
+ it 'does not authenticate all queries' do
+ subject
+
+ expect(json_response.last['data']['project']).to be_nil
+ end
+
+ it_behaves_like 'imposing query limits' do
+ it "fails all queries when only one of the queries is too complex" do
+ # The `project` query above has a complexity of 5
+ allow(GitlabSchema).to receive(:max_query_complexity).and_return 4
+
+ subject
+
+ # Expect a response for each query, even though it will be empty
+ expect(json_response.size).to eq(3)
+ json_response.each do |single_query_response|
+ expect(single_query_response).not_to have_key('data')
+ end
+
+ # Expect errors for each query
+ expect(graphql_errors.size).to eq(3)
+ graphql_errors.each do |single_query_errors|
+ expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4')
+ end
+ end
+ end
+
+ context 'authentication' do
+ let(:current_user) { project.owner }
+
+ it 'authenticates all queries' do
+ subject
+
+ expect(json_response.last['data']['project']['userPermissions']['createIssue']).to be(true)
+ end
+ end
+ end
+
+ context 'when IntrospectionQuery' do
+ it 'is not too complex' do
+ query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql'))
+
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_errors).to be_nil
+ end
+ end
+
+ context 'logging' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs the query complexity and depth' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {}.to_s,
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7)
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo)
+
+ post_graphql(query, current_user: nil)
+ end
+
+ it 'logs using `format_message`' do
+ expect_any_instance_of(Gitlab::GraphqlLogger).to receive(:format_message)
+
+ post_graphql(query, current_user: nil)
+ end
+ end
+
+ context "global id's" do
+ it 'uses GlobalID to expose ids' do
+ post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id)),
+ current_user: project.owner)
+
+ parsed_id = GlobalID.parse(graphql_data['project']['id'])
+
+ expect(parsed_id).to eq(project.to_global_id)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
new file mode 100644
index 00000000000..e0f1e4dbe9e
--- /dev/null
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Based on spec/requests/api/groups_spec.rb
+# Should follow closely in order to ensure all situations are covered
+describe 'getting group information' do
+ include GraphqlHelpers
+ include UploadHelpers
+
+ let(:user1) { create(:user, can_create_group: false) }
+ let(:user2) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:public_group) { create(:group, :public) }
+ let(:private_group) { create(:group, :private) }
+
+ # similar to the API "GET /groups/:id"
+ describe "Query group(fullPath)" do
+ def group_query(group)
+ graphql_query_for('group', 'fullPath' => group.full_path)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(group_query(public_group))
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns nil for a private group' do
+ post_graphql(group_query(private_group))
+
+ expect(graphql_data['group']).to be_nil
+ end
+
+ it 'returns a public group' do
+ post_graphql(group_query(public_group))
+
+ expect(graphql_data['group']).not_to be_nil
+ end
+ end
+
+ context "when authenticated as user" do
+ let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
+ let!(:group2) { create(:group, :private) }
+
+ before do
+ group1.add_owner(user1)
+ group2.add_owner(user2)
+ end
+
+ it "returns one of user1's groups" do
+ project = create(:project, namespace: group2, path: 'Foo')
+ create(:project_group_link, project: project, group: group1)
+
+ post_graphql(group_query(group1), current_user: user1)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(graphql_data['group']['id']).to eq(group1.to_global_id.to_s)
+ expect(graphql_data['group']['name']).to eq(group1.name)
+ expect(graphql_data['group']['path']).to eq(group1.path)
+ expect(graphql_data['group']['description']).to eq(group1.description)
+ expect(graphql_data['group']['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level))
+ expect(graphql_data['group']['avatarUrl']).to eq(group1.avatar_url(only_path: false))
+ expect(graphql_data['group']['webUrl']).to eq(group1.web_url)
+ expect(graphql_data['group']['requestAccessEnabled']).to eq(group1.request_access_enabled)
+ expect(graphql_data['group']['fullName']).to eq(group1.full_name)
+ expect(graphql_data['group']['fullPath']).to eq(group1.full_path)
+ expect(graphql_data['group']['parentId']).to eq(group1.parent_id)
+ end
+
+ it "does not return a non existing group" do
+ query = graphql_query_for('group', 'fullPath' => '1328')
+
+ post_graphql(query, current_user: user1)
+
+ expect(graphql_data['group']).to be_nil
+ end
+
+ it "does not return a group not attached to user1" do
+ private_group.add_owner(user2)
+
+ post_graphql(group_query(private_group), current_user: user1)
+
+ expect(graphql_data['group']).to be_nil
+ end
+
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ post_graphql(group_query(group1), current_user: admin)
+ end.count
+
+ queries = [{ query: group_query(group1) },
+ { query: group_query(group2) }]
+
+ expect do
+ post_multiplex(queries, current_user: admin)
+ end.not_to exceed_query_limit(control_count)
+
+ expect(graphql_errors).to contain_exactly(nil, nil)
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "returns any existing group" do
+ post_graphql(group_query(private_group), current_user: admin)
+
+ expect(graphql_data['group']['name']).to eq(private_group.name)
+ end
+
+ it "does not return a non existing group" do
+ query = graphql_query_for('group', 'fullPath' => '1328')
+ post_graphql(query, current_user: admin)
+
+ expect(graphql_data['group']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb
new file mode 100644
index 00000000000..4c56c559cf9
--- /dev/null
+++ b/spec/requests/api/graphql/metadata_query_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'getting project information' do
+ include GraphqlHelpers
+
+ let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) }
+
+ context 'logged in' do
+ it 'returns version and revision' do
+ post_graphql(query, current_user: create(:user))
+
+ expect(graphql_errors).to be_nil
+ expect(graphql_data).to eq(
+ 'metadata' => {
+ 'version' => Gitlab::VERSION,
+ 'revision' => Gitlab.revision
+ }
+ )
+ end
+ end
+
+ context 'anonymous user' do
+ it 'returns nothing' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_errors).to be_nil
+ expect(graphql_data).to eq('metadata' => nil)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
new file mode 100644
index 00000000000..844fd979285
--- /dev/null
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'Multiplexed queries' do
+ include GraphqlHelpers
+
+ it 'returns responses for multiple queries' do
+ queries = [
+ { query: 'query($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'Hello' } },
+ { query: 'query($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'World' } }
+ ]
+
+ post_multiplex(queries)
+
+ first_response = json_response.first['data']['echo']
+ second_response = json_response.last['data']['echo']
+
+ expect(first_response).to eq('nil says: Hello')
+ expect(second_response).to eq('nil says: World')
+ end
+
+ it 'returns error and data combinations' do
+ queries = [
+ { query: 'query($text: String) { broken query }' },
+ { query: 'query working($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'World' } }
+ ]
+
+ post_multiplex(queries)
+
+ first_response = json_response.first['errors']
+ second_response = json_response.last['data']['echo']
+
+ expect(first_response).not_to be_empty
+ expect(second_response).to eq('nil says: World')
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index 8f427d71a32..d75f0df9fd3 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -11,7 +11,7 @@ describe 'Setting WIP status of a merge request' do
let(:mutation) do
variables = {
project_path: project.full_path,
- iid: merge_request.iid
+ iid: merge_request.iid.to_s
}
graphql_mutation(:merge_request_set_wip, variables.merge(input))
end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
new file mode 100644
index 00000000000..de1cd9586b6
--- /dev/null
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'getting projects', :nested_groups do
+ include GraphqlHelpers
+
+ let(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: subject) }
+ let(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:project, group: nested_group) }
+ let!(:public_project) { create(:project, :public, namespace: subject) }
+ let(:user) { create(:user) }
+ let(:include_subgroups) { true }
+
+ subject { group }
+
+ let(:query) do
+ graphql_query_for(
+ 'namespace',
+ { 'fullPath' => subject.full_path },
+ <<~QUERY
+ projects(includeSubgroups: #{include_subgroups}) {
+ edges {
+ node {
+ #{all_graphql_fields_for('Project')}
+ }
+ }
+ }
+ QUERY
+ )
+ end
+
+ before do
+ group.add_owner(user)
+ end
+
+ shared_examples 'a graphql namespace' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it "includes the packages size if the user can read the statistics" do
+ post_graphql(query, current_user: user)
+
+ count = if include_subgroups
+ subject.all_projects.count
+ else
+ subject.projects.count
+ end
+
+ expect(graphql_data['namespace']['projects']['edges'].size).to eq(count)
+ end
+
+ context 'with no user' do
+ it 'finds only public projects' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['namespace']['projects']['edges'].size).to eq(1)
+ project = graphql_data['namespace']['projects']['edges'][0]['node']
+ expect(project['id']).to eq(public_project.to_global_id.to_s)
+ end
+ end
+ end
+
+ it_behaves_like 'a graphql namespace'
+
+ context 'when the namespace is a user' do
+ subject { user.namespace }
+ let(:include_subgroups) { false }
+
+ it_behaves_like 'a graphql namespace'
+ end
+
+ context 'when not including subgroups' do
+ let(:include_subgroups) { false }
+
+ it_behaves_like 'a graphql namespace'
+ end
+end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index c2934430821..4f9f916f22e 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -7,8 +7,8 @@ describe 'getting an issue list for a project' do
let(:current_user) { create(:user) }
let(:issues_data) { graphql_data['project']['issues']['edges'] }
let!(:issues) do
- create(:issue, project: project, discussion_locked: true)
- create(:issue, project: project)
+ [create(:issue, project: project, discussion_locked: true),
+ create(:issue, project: project)]
end
let(:fields) do
<<~QUERY
@@ -47,6 +47,30 @@ describe 'getting an issue list for a project' do
expect(issues_data[1]['node']['discussionLocked']).to eq true
end
+ context 'when limiting the number of results' do
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ "issues(first: 1) { #{fields} }"
+ )
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it "is expected to check permissions on the first issue only" do
+ allow(Ability).to receive(:allowed?).and_call_original
+ # Newest first, we only want to see the newest checked
+ expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first)
+
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
context 'when the user does not have access to the issue' do
it 'returns nil' do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index deb6abbc026..74820d39102 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -70,13 +70,13 @@ describe 'getting merge request information nested in a project' do
context 'when there are pipelines' do
before do
- pipeline = create(
+ create(
:ci_pipeline,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha
)
- merge_request.update!(head_pipeline: pipeline)
+ merge_request.update_head_pipeline
end
it 'has a head pipeline' do
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
new file mode 100644
index 00000000000..8683fa1f390
--- /dev/null
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'rendering namespace statistics' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project) }
+ let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.megabytes) }
+ let(:user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('project',
+ { 'fullPath' => project.full_path },
+ "statistics { #{all_graphql_fields_for('ProjectStatistics')} }")
+ end
+
+ before do
+ project.add_reporter(user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it "includes the packages size if the user can read the statistics" do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.megabytes)
+ end
+
+ context 'when the project is public' do
+ let(:project) { create(:project, :public) }
+
+ it 'includes the statistics regardless of the user' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['project']['statistics']).to be_present
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb
new file mode 100644
index 00000000000..67af612a4a0
--- /dev/null
+++ b/spec/requests/api/graphql/project/repository_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'getting a repository in a project' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
+ let(:fields) do
+ <<~QUERY
+ #{all_graphql_fields_for('repository'.classify)}
+ QUERY
+ end
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('repository', {}, fields)
+ )
+ end
+
+ it 'returns repository' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']).to be_present
+ end
+
+ context 'as a non-authorized user' do
+ let(:current_user) { create(:user) }
+
+ it 'returns nil' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']).to be(nil)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
new file mode 100644
index 00000000000..b07aa1e12d3
--- /dev/null
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'getting a tree in a project' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
+ let(:path) { "" }
+ let(:ref) { "master" }
+ let(:fields) do
+ <<~QUERY
+ tree(path:"#{path}", ref:"#{ref}") {
+ #{all_graphql_fields_for('tree'.classify)}
+ }
+ QUERY
+ end
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('repository', {}, fields)
+ )
+ end
+
+ context 'when path does not exist' do
+ let(:path) { "testing123" }
+
+ it 'returns empty tree' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['tree']['trees']['edges']).to eq([])
+ expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
+ expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
+ end
+ end
+
+ context 'when ref does not exist' do
+ let(:ref) { "testing123" }
+
+ it 'returns empty tree' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['tree']['trees']['edges']).to eq([])
+ expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
+ expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
+ end
+ end
+
+ context 'when ref and path exist' do
+ it 'returns tree' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['tree']).to be_present
+ end
+
+ it 'returns blobs, subtrees and submodules inside tree' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['tree']['trees']['edges'].size).to be > 0
+ expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0
+ expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0
+ end
+ end
+
+ context 'when current user is nil' do
+ it 'returns empty project' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['project']).to be(nil)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
new file mode 100644
index 00000000000..656d6f8b50b
--- /dev/null
+++ b/spec/requests/api/graphql_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'GraphQL' do
+ include GraphqlHelpers
+
+ let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
+
+ context 'graphql is disabled by feature flag' do
+ before do
+ stub_feature_flags(graphql: false)
+ end
+
+ it 'does not generate a route for GraphQL' do
+ expect { post_graphql(query) }.to raise_error(ActionController::RoutingError)
+ end
+ end
+
+ context 'logging' do
+ shared_examples 'logging a graphql query' do
+ let(:expected_params) do
+ { query_string: query, variables: variables.to_s, duration: anything, depth: 1, complexity: 1 }
+ end
+
+ it 'logs a query with the expected params' do
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once
+
+ post_graphql(query, variables: variables)
+ end
+
+ it 'does not instantiate any query analyzers' do # they are static and re-used
+ expect(GraphQL::Analysis::QueryComplexity).not_to receive(:new)
+ expect(GraphQL::Analysis::QueryDepth).not_to receive(:new)
+
+ 2.times { post_graphql(query, variables: variables) }
+ end
+ end
+
+ context 'with no variables' do
+ let(:variables) { {} }
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'with variables' do
+ let(:variables) do
+ { "foo" => "bar" }
+ end
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'when there is an error in the logger' do
+ before do
+ allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:process_variables).and_raise(StandardError.new("oh noes!"))
+ end
+
+ it 'logs the exception in Sentry and continues with the request' do
+ expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times
+ expect(Gitlab::GraphqlLogger).to receive(:info)
+
+ post_graphql(query, variables: {})
+ end
+ end
+ end
+
+ context 'invalid variables' do
+ it 'returns an error' do
+ post_graphql(query, variables: "This is not JSON")
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response['errors'].first['message']).not_to be_nil
+ end
+ end
+
+ context 'authentication', :allow_forgery_protection do
+ let(:user) { create(:user) }
+
+ it 'allows access to public data without authentication' do
+ post_graphql(query)
+
+ expect(graphql_data['echo']).to eq('nil says: Hello world')
+ end
+
+ it 'does not authenticate a user with an invalid CSRF' do
+ login_as(user)
+
+ post_graphql(query, headers: { 'X-CSRF-Token' => 'invalid' })
+
+ expect(graphql_data['echo']).to eq('nil says: Hello world')
+ end
+
+ it 'authenticates a user with a valid session token' do
+ # Create a session to get a CSRF token from
+ login_as(user)
+ get('/')
+
+ post '/api/graphql', params: { query: query }, headers: { 'X-CSRF-Token' => response.session['_csrf_token'] }
+
+ expect(graphql_data['echo']).to eq("\"#{user.username}\" says: Hello world")
+ end
+
+ context 'token authentication' do
+ let(:token) { create(:personal_access_token) }
+
+ before do
+ stub_authentication_activity_metrics(debug: false)
+ end
+
+ it 'Authenticates users with a PAT' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
+
+ expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
+ end
+
+ context 'when the personal access token has no api scope' do
+ it 'does not log the user in' do
+ token.update(scopes: [:read_user])
+
+ post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(graphql_data['echo']).to eq('nil says: Hello world')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index e52f4c70407..d50bae3dc47 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -51,6 +51,7 @@ describe API::GroupVariables do
expect(response).to have_gitlab_http_status(200)
expect(json_response['value']).to eq(variable.value)
expect(json_response['protected']).to eq(variable.protected?)
+ expect(json_response['variable_type']).to eq(variable.variable_type)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
@@ -87,24 +88,26 @@ describe API::GroupVariables do
it 'creates variable' do
expect do
- post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
+ post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true }
end.to change {group.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
- expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['value']).to eq('PROTECTED_VALUE_2')
expect(json_response['protected']).to be_truthy
+ expect(json_response['variable_type']).to eq('env_var')
end
it 'creates variable with optional attributes' do
expect do
- post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
+ post api("/groups/#{group.id}/variables", user), params: { variable_type: 'file', key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
end.to change {group.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
expect(json_response['protected']).to be_falsey
+ expect(json_response['variable_type']).to eq('file')
end
it 'does not allow to duplicate variable key' do
@@ -145,7 +148,7 @@ describe API::GroupVariables do
initial_variable = group.variables.reload.first
value_before = initial_variable.value
- put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true }
+ put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { variable_type: 'file', value: 'VALUE_1_UP', protected: true }
updated_variable = group.variables.reload.first
@@ -153,6 +156,7 @@ describe API::GroupVariables do
expect(value_before).to eq(variable.value)
expect(updated_variable.value).to eq('VALUE_1_UP')
expect(updated_variable).to be_protected
+ expect(json_response['variable_type']).to eq('file')
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 7176bc23e34..c41408fba65 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -90,8 +90,9 @@ describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 702,
+ storage_size: 1158,
repository_size: 123,
+ wiki_size: 456,
lfs_objects_size: 234,
build_artifacts_size: 345
}.stringify_keys
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index a0c64d295c0..ed907841bd8 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -247,11 +247,10 @@ describe API::Helpers do
exception = RuntimeError.new('test error')
allow(exception).to receive(:backtrace).and_return(caller)
- expect(Raven).to receive(:capture_exception).with(exception, tags: {
- correlation_id: 'new-correlation-id'
- }, extra: {})
+ expect(Raven).to receive(:capture_exception).with(exception, tags:
+ a_hash_including(correlation_id: 'new-correlation-id'), extra: {})
- Gitlab::CorrelationId.use_id('new-correlation-id') do
+ Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do
handle_api_exception(exception)
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index cd85151ec1b..fcbff19bd61 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -26,6 +26,21 @@ describe API::Internal do
expect(json_response['redis']).to be(false)
end
+
+ context 'authenticating' do
+ it 'authenticates using a header' do
+ get api("/internal/check"),
+ headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns 401 when no credentials provided' do
+ get(api("/internal/check"))
+
+ expect(response).to have_gitlab_http_status(401)
+ end
+ end
end
describe 'GET /internal/broadcast_message' do
@@ -237,6 +252,14 @@ describe API::Internal do
expect(json_response['name']).to eq(user.name)
end
+
+ it 'responds successfully when a user is not found' do
+ get(api("/internal/discover"), params: { username: 'noone', secret_token: secret_token })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(response.body).to eq('null')
+ end
end
describe "GET /internal/authorized_keys" do
@@ -298,7 +321,7 @@ describe API::Internal do
end
context 'with env passed as a JSON' do
- let(:gl_repository) { project.gl_repository(is_wiki: true) }
+ let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_subject(project) }
it 'sets env in RequestStore' do
obj_dir_relative = './objects'
@@ -324,7 +347,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to be_nil
@@ -337,7 +359,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to eql(Date.today)
@@ -350,7 +371,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
@@ -370,7 +390,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
@@ -479,6 +498,40 @@ describe API::Internal do
end
end
+ context "console message" do
+ before do
+ project.add_developer(user)
+ end
+
+ context "git pull" do
+ context "with no console message" do
+ it "has the correct payload" do
+ pull(key, project)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['gl_console_messages']).to eq([])
+ end
+ end
+
+ context "with a console message" do
+ let(:console_messages) { ['message for the console'] }
+
+ it "has the correct payload" do
+ expect_next_instance_of(Gitlab::GitAccess) do |access|
+ expect(access).to receive(:check_for_console_messages)
+ .with('git-upload-pack')
+ .and_return(console_messages)
+ end
+
+ pull(key, project)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['gl_console_messages']).to eq(console_messages)
+ end
+ end
+ end
+ end
+
context "blocked user" do
let(:personal_project) { create(:project, namespace: user.namespace) }
@@ -591,6 +644,22 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(404)
expect(json_response["status"]).to be_falsey
end
+
+ it 'returns a 200 response when using a project path that does not exist' do
+ post(
+ api("/internal/allowed"),
+ params: {
+ key_id: key.id,
+ project: 'project/does-not-exist.git',
+ action: 'git-upload-pack',
+ secret_token: secret_token,
+ protocol: 'ssh'
+ }
+ )
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response["status"]).to be_falsey
+ end
end
context 'user does not exist' do
@@ -819,8 +888,10 @@ describe API::Internal do
}
end
+ let(:branch_name) { 'feature' }
+
let(:changes) do
- "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch"
+ "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{branch_name}"
end
let(:push_options) do
@@ -836,9 +907,9 @@ describe API::Internal do
it 'enqueues a PostReceive worker job' do
expect(PostReceive).to receive(:perform_async)
- .with(gl_repository, identifier, changes, push_options)
+ .with(gl_repository, identifier, changes, { ci: { skip: true } })
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
end
it 'decreases the reference counter and returns the result' do
@@ -846,17 +917,17 @@ describe API::Internal do
.and_return(reference_counter)
expect(reference_counter).to receive(:decrease).and_return(true)
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(json_response['reference_counter_decreased']).to be(true)
end
it 'returns link to create new merge request' do
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(json_response['merge_request_urls']).to match [{
- "branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "branch_name" => branch_name,
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
"new_merge_request" => true
}]
end
@@ -864,16 +935,75 @@ describe API::Internal do
it 'returns empty array if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(json_response['merge_request_urls']).to eq([])
end
+ it 'does not invoke MergeRequests::PushOptionsHandlerService' do
+ expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new)
+
+ post api('/internal/post_receive'), params: valid_params
+ end
+
+ context 'when there are merge_request push options' do
+ before do
+ valid_params[:push_options] = ['merge_request.create']
+ end
+
+ it 'invokes MergeRequests::PushOptionsHandlerService' do
+ expect(MergeRequests::PushOptionsHandlerService).to receive(:new)
+
+ post api('/internal/post_receive'), params: valid_params
+ end
+
+ it 'creates a new merge request' do
+ expect do
+ Sidekiq::Testing.fake! do
+ post api('/internal/post_receive'), params: valid_params
+ end
+ end.to change { MergeRequest.count }.by(1)
+ end
+
+ it 'links to the newly created merge request' do
+ post api('/internal/post_receive'), params: valid_params
+
+ expect(json_response['merge_request_urls']).to match [{
+ 'branch_name' => branch_name,
+ 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1",
+ 'new_merge_request' => false
+ }]
+ end
+
+ it 'adds errors on the service instance to warnings' do
+ expect_any_instance_of(
+ MergeRequests::PushOptionsHandlerService
+ ).to receive(:errors).at_least(:once).and_return(['my error'])
+
+ post api('/internal/post_receive'), params: valid_params
+
+ expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ end
+
+ it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do
+ invalid_merge_request = MergeRequest.new
+ invalid_merge_request.errors.add(:base, 'my error')
+
+ expect_any_instance_of(
+ MergeRequests::CreateService
+ ).to receive(:execute).and_return(invalid_merge_request)
+
+ post api('/internal/post_receive'), params: valid_params
+
+ expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ end
+ end
+
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
it 'returns one broadcast message' do
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['broadcast_message']).to eq(broadcast_message.message)
@@ -882,7 +1012,7 @@ describe API::Internal do
context 'broadcast message does not exist' do
it 'returns empty string' do
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
@@ -893,7 +1023,7 @@ describe API::Internal do
it 'returns empty string' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
@@ -905,7 +1035,7 @@ describe API::Internal do
project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz')
project_moved.add_message
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response["redirected_message"]).to be_present
@@ -918,7 +1048,7 @@ describe API::Internal do
project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http')
project_created.add_message
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response["project_created_message"]).to be_present
@@ -930,7 +1060,7 @@ describe API::Internal do
it 'does not try to notify that project moved' do
allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(nil)
- post api("/internal/post_receive"), params: valid_params
+ post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
end
@@ -956,9 +1086,9 @@ describe API::Internal do
def gl_repository_for(project_or_wiki)
case project_or_wiki
when ProjectWiki
- project_or_wiki.project.gl_repository(is_wiki: true)
+ Gitlab::GlRepository::WIKI.identifier_for_subject(project_or_wiki.project)
when Project
- project_or_wiki.gl_repository(is_wiki: false)
+ Gitlab::GlRepository::PROJECT.identifier_for_subject(project_or_wiki)
else
nil
end
diff --git a/spec/requests/api/issuable_bulk_update_spec.rb b/spec/requests/api/issuable_bulk_update_spec.rb
deleted file mode 100644
index 6463f3f5d35..00000000000
--- a/spec/requests/api/issuable_bulk_update_spec.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe API::IssuableBulkUpdate do
- set(:project) { create(:project) }
- set(:user) { project.creator }
-
- shared_examples "PUT /projects/:id/:issuable/bulk_update" do |issuable|
- def bulk_update(issuable, issuables, params, update_user = user)
- put api("/projects/#{project.id}/#{issuable.pluralize}/bulk_update", update_user),
- params: { issuable_ids: Array(issuables).map(&:id) }.merge(params)
- end
-
- context 'with not enough permissions' do
- it 'returns 403 for guest users' do
- guest = create(:user)
- project.add_guest(guest)
-
- bulk_update(issuable, issuables, { state_event: 'close' }, guest)
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- context 'when modifying the state' do
- it "closes #{issuable}" do
- bulk_update(issuable, issuables, { state_event: 'close' })
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['message']).to eq("#{issuables.count} #{issuable.pluralize(issuables.count)} updated")
- expect(project.public_send(issuable.pluralize).opened).to be_empty
- expect(project.public_send(issuable.pluralize).closed).not_to be_empty
- end
-
- it "opens #{issuable}" do
- closed_issuables = create_list("closed_#{issuable}".to_sym, 2)
-
- bulk_update(issuable, closed_issuables, { state_event: 'reopen' })
-
- expect(response).to have_gitlab_http_status(200)
- expect(project.public_send(issuable.pluralize).closed).to be_empty
- end
- end
-
- context 'when modifying the milestone' do
- let(:milestone) { create(:milestone, project: project) }
-
- it "adds a milestone #{issuable}" do
- bulk_update(issuable, issuables, { milestone_id: milestone.id })
-
- expect(response).to have_gitlab_http_status(200)
- issuables.each do |issuable|
- expect(issuable.reload.milestone).to eq(milestone)
- end
- end
-
- it 'removes a milestone' do
- issuables.first.milestone = milestone
- milestone_issuable = issuables.first
-
- bulk_update(issuable, [milestone_issuable], { milestone_id: 0 })
-
- expect(response).to have_gitlab_http_status(200)
- expect(milestone_issuable.reload.milestone).to eq(nil)
- end
- end
-
- context 'when modifying the subscription state' do
- it "subscribes to #{issuable}" do
- bulk_update(issuable, issuables, { subscription_event: 'subscribe' })
-
- expect(response).to have_gitlab_http_status(200)
- expect(issuables).to all(be_subscribed(user, project))
- end
-
- it 'unsubscribes from issues' do
- issuables.each do |issuable|
- issuable.subscriptions.create(user: user, project: project, subscribed: true)
- end
-
- bulk_update(issuable, issuables, { subscription_event: 'unsubscribe' })
-
- expect(response).to have_gitlab_http_status(200)
- issuables.each do |issuable|
- expect(issuable).not_to be_subscribed(user, project)
- end
- end
- end
-
- context 'when modifying the assignee' do
- it 'adds assignee to issues' do
- params = issuable == 'issue' ? { assignee_ids: [user.id] } : { assignee_id: user.id }
-
- bulk_update(issuable, issuables, params)
-
- expect(response).to have_gitlab_http_status(200)
- issuables.each do |issuable|
- expect(issuable.reload.assignees).to eq([user])
- end
- end
-
- it 'removes assignee' do
- assigned_issuable = issuables.first
-
- if issuable == 'issue'
- params = { assignee_ids: 0 }
- assigned_issuable.assignees << user
- else
- params = { assignee_id: 0 }
- assigned_issuable.update_attribute(:assignee, user)
- end
-
- bulk_update(issuable, [assigned_issuable], params)
- expect(assigned_issuable.reload.assignees).to eq([])
- end
- end
-
- context 'when modifying labels' do
- let(:bug) { create(:label, project: project) }
- let(:regression) { create(:label, project: project) }
- let(:feature) { create(:label, project: project) }
-
- it 'adds new labels' do
- bulk_update(issuable, issuables, { add_label_ids: [bug.id, regression.id, feature.id] })
-
- issuables.each do |issusable|
- expect(issusable.reload.label_ids).to contain_exactly(bug.id, regression.id, feature.id)
- end
- end
-
- it 'removes labels' do
- labled_issuable = issuables.first
- labled_issuable.labels << bug
- labled_issuable.labels << regression
- labled_issuable.labels << feature
-
- bulk_update(issuable, [labled_issuable], { remove_label_ids: [bug.id, regression.id] })
-
- expect(labled_issuable.reload.label_ids).to contain_exactly(feature.id)
- end
- end
- end
-
- it_behaves_like 'PUT /projects/:id/:issuable/bulk_update', 'issue' do
- let(:issuables) { create_list(:issue, 2, project: project) }
- end
-
- it_behaves_like 'PUT /projects/:id/:issuable/bulk_update', 'merge_request' do
- let(:merge_request_1) { create(:merge_request, source_project: project) }
- let(:merge_request_2) { create(:merge_request, :simple, source_project: project) }
- let(:issuables) { [merge_request_1, merge_request_2] }
- end
-end
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
new file mode 100644
index 00000000000..8b02cf56e9f
--- /dev/null
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -0,0 +1,652 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Issues do
+ set(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:non_member) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:author) { create(:author) }
+ set(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
+
+ let(:no_milestone_title) { 'None' }
+ let(:any_milestone_title) { 'Any' }
+
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ end
+
+ describe 'GET /groups/:id/issues' do
+ let!(:group) { create(:group) }
+ let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) }
+ let!(:group_closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: group_project,
+ state: :closed,
+ milestone: group_milestone,
+ updated_at: 3.hours.ago,
+ created_at: 1.day.ago
+ end
+ let!(:group_confidential_issue) do
+ create :issue,
+ :confidential,
+ project: group_project,
+ author: author,
+ assignees: [assignee],
+ updated_at: 2.hours.ago,
+ created_at: 2.days.ago
+ end
+ let!(:group_issue) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: group_project,
+ milestone: group_milestone,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description,
+ created_at: 5.days.ago
+ end
+ let!(:group_label) do
+ create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
+ end
+ let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
+ let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
+ let!(:group_empty_milestone) do
+ create(:milestone, title: '4.0.0', project: group_project)
+ end
+ let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
+
+ let(:base_url) { "/groups/#{group.id}/issues" }
+
+ shared_examples 'group issues statistics' do
+ it 'returns issues statistics' do
+ get api("/groups/#{group.id}/issues_statistics", user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['statistics']).not_to be_nil
+ expect(json_response['statistics']['counts']['all']).to eq counts[:all]
+ expect(json_response['statistics']['counts']['closed']).to eq counts[:closed]
+ expect(json_response['statistics']['counts']['opened']).to eq counts[:opened]
+ end
+ end
+
+ context 'when group has subgroups', :nested_groups do
+ let(:subgroup_1) { create(:group, parent: group) }
+ let(:subgroup_2) { create(:group, parent: subgroup_1) }
+
+ let(:subgroup_1_project) { create(:project, :public, namespace: subgroup_1) }
+ let(:subgroup_2_project) { create(:project, namespace: subgroup_2) }
+
+ let!(:issue_1) { create(:issue, project: subgroup_1_project) }
+ let!(:issue_2) { create(:issue, project: subgroup_2_project) }
+
+ context 'when user is unauthenticated' do
+ it 'also returns subgroups public projects issues' do
+ get api(base_url)
+
+ expect_paginated_array_response([issue_1.id, group_closed_issue.id, group_issue.id])
+ end
+
+ it 'also returns subgroups public projects issues filtered by milestone' do
+ get api(base_url), params: { milestone: group_milestone.title }
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: group_milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: group_milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+ end
+ end
+
+ context 'when user is a group member' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'also returns subgroups projects issues' do
+ get api(base_url, user)
+
+ expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'also returns subgroups public projects issues filtered by milestone' do
+ get api(base_url, user), params: { milestone: group_milestone.title }
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 5, closed: 1, opened: 4 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 5, closed: 1, opened: 4 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 5, closed: 1, opened: 4 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 5, closed: 1, opened: 4 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: group_milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: group_milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+ end
+ end
+ end
+
+ context 'when user is unauthenticated' do
+ it 'lists all issues in public projects' do
+ get api(base_url)
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
+ it 'also returns subgroups public projects issues filtered by milestone' do
+ get api(base_url), params: { milestone: group_milestone.title }
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: group_milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: group_milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+ end
+ end
+
+ context 'when user is a group member' do
+ before do
+ group_project.add_reporter(user)
+ end
+
+ it 'returns all group issues (including opened and closed)' do
+ get api(base_url, admin)
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'returns group issues without confidential issues for non project members' do
+ get api(base_url, non_member), params: { state: :opened }
+
+ expect_paginated_array_response(group_issue.id)
+ end
+
+ it 'returns group confidential issues for author' do
+ get api(base_url, author), params: { state: :opened }
+
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'returns group confidential issues for assignee' do
+ get api(base_url, assignee), params: { state: :opened }
+
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'returns group issues with confidential issues for project members' do
+ get api(base_url, user), params: { state: :opened }
+
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'returns group confidential issues for admin' do
+ get api(base_url, admin), params: { state: :opened }
+
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
+ end
+
+ it 'returns only confidential issues' do
+ get api(base_url, user), params: { confidential: true }
+
+ expect_paginated_array_response(group_confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api(base_url, user), params: { confidential: false }
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
+ it 'returns an array of labeled group issues' do
+ get api(base_url, user), params: { labels: group_label.title }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['labels']).to eq([group_label.title])
+ end
+
+ it 'returns an array of labeled group issues with labels param as array' do
+ get api(base_url, user), params: { labels: [group_label.title] }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['labels']).to eq([group_label.title])
+ end
+
+ it 'returns an array of labeled group issues where all labels match' do
+ get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of labeled group issues where all labels match with labels param as array' do
+ get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns issues matching given search string for title' do
+ get api(base_url, user), params: { search: group_issue.title }
+
+ expect_paginated_array_response(group_issue.id)
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api(base_url, user), params: { search: group_issue.description }
+
+ expect_paginated_array_response(group_issue.id)
+ end
+
+ context 'with labeled issues' do
+ let(:label_b) { create(:label, title: 'foo', project: group_project) }
+ let(:label_c) { create(:label, title: 'bar', project: group_project) }
+
+ before do
+ create(:label_link, label: label_b, target: group_issue)
+ create(:label_link, label: label_c, target: group_issue)
+
+ get api(base_url, user), params: params
+ end
+
+ let(:issue) { group_issue }
+ let(:label) { group_label }
+
+ it_behaves_like 'labeled issues with labels and label_name params'
+ end
+
+ it 'returns an array of issues found by iids' do
+ get api(base_url, user), params: { iids: [group_issue.iid] }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['id']).to eq(group_issue.id)
+ end
+
+ it 'returns an empty array if iid does not exist' do
+ get api(base_url, user), params: { iids: [0] }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if no group issue matches labels' do
+ get api(base_url, user), params: { labels: 'foo,bar' }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of group issues with any label' do
+ get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['id']).to eq(group_issue.id)
+ end
+
+ it 'returns an array of group issues with any label with labels param as array' do
+ get api(base_url, user), params: { labels: [IssuesFinder::FILTER_ANY] }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['id']).to eq(group_issue.id)
+ end
+
+ it 'returns an array of group issues with no label' do
+ get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
+ end
+
+ it 'returns an array of group issues with no label with labels param as array' do
+ get api(base_url, user), params: { labels: [IssuesFinder::FILTER_NONE] }
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api(base_url, user), params: { milestone: group_empty_milestone.title }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api(base_url, user), params: { milestone: 'foo' }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of issues in given milestone' do
+ get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
+
+ expect_paginated_array_response(group_issue.id)
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
+
+ expect_paginated_array_response(group_closed_issue.id)
+ end
+
+ it 'returns an array of issues with no milestone' do
+ get api(base_url, user), params: { milestone: no_milestone_title }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect_paginated_array_response(group_confidential_issue.id)
+ end
+
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api(base_url, user)
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:group_issue2) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: group_project,
+ milestone: group_milestone,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description,
+ created_at: group_issue.created_at
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{base_url}?per_page=3", user)
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{base_url}?per_page=3&page=2", user)
+
+ expect_paginated_array_response([group_issue.id])
+ end
+ end
+ end
+
+ it 'sorts ascending when requested' do
+ get api("#{base_url}?sort=asc", user)
+
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
+ end
+
+ it 'sorts by updated_at descending when requested' do
+ get api("#{base_url}?order_by=updated_at", user)
+
+ group_issue.touch(:updated_at)
+
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
+ end
+
+ it 'sorts by updated_at ascending when requested' do
+ get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: group_milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: group_milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: group_milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+
+ context 'sort does not affect statistics ' do
+ let(:params) { { state: :opened, order_by: 'updated_at' } }
+ let(:counts) { { all: 3, closed: 1, opened: 2 } }
+
+ it_behaves_like 'group issues statistics'
+ end
+ end
+
+ context 'filtering by assignee_username' do
+ let(:another_assignee) { create(:assignee) }
+ let!(:issue1) { create(:issue, author: user2, project: group_project, created_at: 3.days.ago) }
+ let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) }
+ let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) }
+
+ it 'returns issues with by assignee_username' do
+ get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([issue3.id, group_confidential_issue.id])
+ end
+
+ it 'returns issues by assignee_username as string' do
+ get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([issue3.id, group_confidential_issue.id])
+ end
+
+ it 'returns error when multiple assignees are passed' do
+ get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("allows one value, but found 2")
+ end
+
+ it 'returns error when assignee_username and assignee_id are passed together' do
+ get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("mutually exclusive")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
new file mode 100644
index 00000000000..0b0f754ab57
--- /dev/null
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -0,0 +1,807 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Issues do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ let(:user2) { create(:user) }
+ let(:non_member) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:author) { create(:author) }
+ set(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
+ let!(:closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ state: :closed,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 3.hours.ago,
+ closed_at: 1.hour.ago
+ end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignees: [assignee],
+ created_at: generate(:past_time),
+ updated_at: 2.hours.ago
+ end
+ let!(:issue) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+ set(:label) do
+ create(:label, title: 'label', color: '#FFAABB', project: project)
+ end
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ set(:empty_milestone) do
+ create(:milestone, title: '2.0.0', project: project)
+ end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+
+ let(:no_milestone_title) { 'None' }
+ let(:any_milestone_title) { 'Any' }
+
+ before(:all) do
+ project.add_reporter(user)
+ project.add_guest(guest)
+ end
+
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ end
+
+ shared_examples 'project issues statistics' do
+ it 'returns project issues statistics' do
+ get api("/issues_statistics", user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['statistics']).not_to be_nil
+ expect(json_response['statistics']['counts']['all']).to eq counts[:all]
+ expect(json_response['statistics']['counts']['closed']).to eq counts[:closed]
+ expect(json_response['statistics']['counts']['opened']).to eq counts[:opened]
+ end
+ end
+
+ describe "GET /projects/:id/issues" do
+ let(:base_url) { "/projects/#{project.id}" }
+
+ context 'when unauthenticated' do
+ it 'returns public project issues' do
+ get api("/projects/#{project.id}/issues")
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'sort does not affect statistics ' do
+ let(:params) { { state: :opened, order_by: 'updated_at' } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+ end
+ end
+
+ it 'avoids N+1 queries' do
+ get api("/projects/#{project.id}/issues", user)
+
+ create_list(:issue, 3, project: project)
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ get api("/projects/#{project.id}/issues", user)
+ end.count
+
+ expect do
+ get api("/projects/#{project.id}/issues", user)
+ end.not_to exceed_all_query_limit(control_count)
+ end
+
+ it 'returns 404 when project does not exist' do
+ max_project_id = Project.maximum(:id).to_i
+
+ get api("/projects/#{max_project_id + 1}/issues", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 on private projects for other users' do
+ private_project = create(:project, :private)
+ create(:issue, project: private_project)
+
+ get api("/projects/#{private_project.id}/issues", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns no issues when user has access to project but not issues' do
+ restricted_project = create(:project, :public, :issues_private)
+ create(:issue, project: restricted_project)
+
+ get api("/projects/#{restricted_project.id}/issues", non_member)
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns project issues without confidential issues for non project members' do
+ get api("#{base_url}/issues", non_member)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns project issues without confidential issues for project members with guest role' do
+ get api("#{base_url}/issues", guest)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns project confidential issues for author' do
+ get api("#{base_url}/issues", author)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns only confidential issues' do
+ get api("#{base_url}/issues", author), params: { confidential: true }
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api("#{base_url}/issues", author), params: { confidential: false }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns project confidential issues for assignee' do
+ get api("#{base_url}/issues", assignee)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns project issues with confidential issues for project members' do
+ get api("#{base_url}/issues", user)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns project confidential issues for admin' do
+ get api("#{base_url}/issues", admin)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns an array of labeled project issues' do
+ get api("#{base_url}/issues", user), params: { labels: label.title }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of labeled project issues with labels param as array' do
+ get api("#{base_url}/issues", user), params: { labels: [label.title] }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ context 'with labeled issues' do
+ let(:label_b) { create(:label, title: 'foo', project: project) }
+ let(:label_c) { create(:label, title: 'bar', project: project) }
+
+ before do
+ create(:label_link, label: label_b, target: issue)
+ create(:label_link, label: label_c, target: issue)
+
+ get api('/issues', user), params: params
+ end
+
+ it_behaves_like 'labeled issues with labels and label_name params'
+ end
+
+ it 'returns issues matching given search string for title' do
+ get api("#{base_url}/issues?search=#{issue.title}", user)
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api("#{base_url}/issues?search=#{issue.description}", user)
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of issues found by iids' do
+ get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an empty array if iid does not exist' do
+ get api("#{base_url}/issues", user), params: { iids: [0] }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if not all labels matches' do
+ get api("#{base_url}/issues?labels=#{label.title},foo", user)
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of project issues with any label' do
+ get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of project issues with any label with labels param as array' do
+ get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_ANY] }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of project issues with no label' do
+ get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
+
+ expect_paginated_array_response([confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns an array of project issues with no label with labels param as array' do
+ get api("#{base_url}/issues", user), params: { labels: [IssuesFinder::FILTER_NONE] }
+
+ expect_paginated_array_response([confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns an empty array if no project issue matches labels' do
+ get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api("#{base_url}/issues", user), params: { milestone: :foo }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of issues in given milestone' do
+ get api("#{base_url}/issues", user), params: { milestone: milestone.title }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no milestone' do
+ get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns an array of issues with any milestone' do
+ get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api("#{base_url}/issues", user)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:closed_issue2) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: closed_issue.created_at,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{base_url}/issues?per_page=3", user)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{base_url}/issues?per_page=3&page=2", user)
+
+ expect_paginated_array_response([closed_issue.id])
+ end
+ end
+ end
+
+ it 'sorts ascending when requested' do
+ get api("#{base_url}/issues", user), params: { sort: :asc }
+
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
+ end
+
+ it 'sorts by updated_at descending when requested' do
+ get api("#{base_url}/issues", user), params: { order_by: :updated_at }
+
+ issue.touch(:updated_at)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'sorts by updated_at ascending when requested' do
+ get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
+
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+
+ context 'sort does not affect statistics ' do
+ let(:params) { { state: :opened, order_by: 'updated_at' } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'project issues statistics'
+ end
+ end
+
+ context 'filtering by assignee_username' do
+ let(:another_assignee) { create(:assignee) }
+ let!(:issue1) { create(:issue, author: user2, project: project, created_at: 3.days.ago) }
+ let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
+ let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
+
+ it 'returns issues by assignee_username' do
+ get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([confidential_issue.id, issue3.id])
+ end
+
+ it 'returns issues by assignee_username as string' do
+ get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([confidential_issue.id, issue3.id])
+ end
+
+ it 'returns error when multiple assignees are passed' do
+ get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("allows one value, but found 2")
+ end
+
+ it 'returns error when assignee_username and assignee_id are passed together' do
+ get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("mutually exclusive")
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/issues/:issue_iid' do
+ context 'when unauthenticated' do
+ it 'returns public issues' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}")
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(issue.id)
+ expect(json_response['iid']).to eq(issue.iid)
+ expect(json_response['project_id']).to eq(issue.project.id)
+ expect(json_response['title']).to eq(issue.title)
+ expect(json_response['description']).to eq(issue.description)
+ expect(json_response['state']).to eq(issue.state)
+ expect(json_response['closed_at']).to be_falsy
+ expect(json_response['created_at']).to be_present
+ expect(json_response['updated_at']).to be_present
+ expect(json_response['labels']).to eq(issue.label_names)
+ expect(json_response['milestone']).to be_a Hash
+ expect(json_response['assignees']).to be_a Array
+ expect(json_response['assignee']).to be_a Hash
+ expect(json_response['author']).to be_a Hash
+ expect(json_response['confidential']).to be_falsy
+ end
+
+ it 'exposes the closed_at attribute' do
+ get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['closed_at']).to be_present
+ end
+
+ context 'links exposure' do
+ it 'exposes related resources full URIs' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
+
+ links = json_response['_links']
+
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}")
+ expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes")
+ expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji")
+ expect(links['project']).to end_with("/api/v4/projects/#{project.id}")
+ end
+ end
+
+ it 'returns a project issue by internal id' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq(issue.title)
+ expect(json_response['iid']).to eq(issue.iid)
+ end
+
+ it 'returns 404 if issue id not found' do
+ get api("/projects/#{project.id}/issues/54321", user)
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if the issue ID is used' do
+ get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'confidential issues' do
+ it 'returns 404 for non project members' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 for project members with guest role' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns confidential issue for project members' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it 'returns confidential issue for author' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it 'returns confidential issue for assignee' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it 'returns confidential issue for admin' do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+ end
+ end
+
+ describe 'GET :id/issues/:issue_iid/closed_by' do
+ let(:merge_request) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "closes #{issue.to_reference}")
+ end
+
+ before do
+ create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
+ end
+
+ context 'when unauthenticated' do
+ it 'return public project issues' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
+
+ expect_paginated_array_response(merge_request.id)
+ end
+ end
+
+ it 'returns merge requests that will close issue on merge' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
+
+ expect_paginated_array_response(merge_request.id)
+ end
+
+ context 'when no merge requests will close issue' do
+ it 'returns empty array' do
+ get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user)
+
+ expect_paginated_array_response([])
+ end
+ end
+
+ it "returns 404 when issue doesn't exists" do
+ get api("/projects/#{project.id}/issues/0/closed_by", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe 'GET :id/issues/:issue_iid/related_merge_requests' do
+ def get_related_merge_requests(project_id, issue_iid, user = nil)
+ get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
+ end
+
+ def create_referencing_mr(user, project, issue)
+ attributes = {
+ author: user,
+ source_project: project,
+ target_project: project,
+ source_branch: 'master',
+ target_branch: 'test',
+ description: "See #{issue.to_reference}"
+ }
+ create(:merge_request, attributes).tap do |merge_request|
+ create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
+ end
+ end
+
+ let!(:related_mr) { create_referencing_mr(user, project, issue) }
+
+ context 'when unauthenticated' do
+ it 'return list of referenced merge requests from issue' do
+ get_related_merge_requests(project.id, issue.iid)
+
+ expect_paginated_array_response(related_mr.id)
+ end
+
+ it 'renders 404 if project is not visible' do
+ private_project = create(:project, :private)
+ private_issue = create(:issue, project: private_project)
+ create_referencing_mr(user, private_project, private_issue)
+
+ get_related_merge_requests(private_project.id, private_issue.iid)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ it 'returns merge requests that mentioned a issue' do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: 'Some description')
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect_paginated_array_response(related_mr.id)
+ end
+
+ it 'returns merge requests cross-project wide' do
+ project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ merge_request = create_referencing_mr(user, project2, issue)
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect_paginated_array_response([related_mr.id, merge_request.id])
+ end
+
+ it 'does not generate references to projects with no access' do
+ private_project = create(:project, :private)
+ create_referencing_mr(private_project.creator, private_project, issue)
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect_paginated_array_response(related_mr.id)
+ end
+
+ context 'no merge request mentioned a issue' do
+ it 'returns empty array' do
+ get_related_merge_requests(project.id, closed_issue.iid, user)
+
+ expect_paginated_array_response([])
+ end
+ end
+
+ it "returns 404 when issue doesn't exists" do
+ get_related_merge_requests(project.id, 0, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe 'GET /projects/:id/issues/:issue_iid/user_agent_detail' do
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
+
+ context 'when unauthenticated' do
+ it 'returns unauthorized' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail")
+
+ expect(response).to have_gitlab_http_status(401)
+ end
+ end
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it 'returns unauthorized for non-admin users' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ describe 'GET projects/:id/issues/:issue_iid/participants' do
+ it_behaves_like 'issuable participants endpoint' do
+ let(:entity) { issue }
+ end
+
+ it 'returns 404 if the issue is confidential' do
+ post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
new file mode 100644
index 00000000000..9b9cc778fb3
--- /dev/null
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -0,0 +1,796 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Issues do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ let(:user2) { create(:user) }
+ let(:non_member) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:author) { create(:author) }
+ set(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
+ let!(:closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ state: :closed,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 3.hours.ago,
+ closed_at: 1.hour.ago
+ end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignees: [assignee],
+ created_at: generate(:past_time),
+ updated_at: 2.hours.ago
+ end
+ let!(:issue) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+ set(:label) do
+ create(:label, title: 'label', color: '#FFAABB', project: project)
+ end
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ set(:empty_milestone) do
+ create(:milestone, title: '2.0.0', project: project)
+ end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+
+ let(:no_milestone_title) { 'None' }
+ let(:any_milestone_title) { 'Any' }
+
+ before(:all) do
+ project.add_reporter(user)
+ project.add_guest(guest)
+ end
+
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ end
+
+ shared_examples 'issues statistics' do
+ it 'returns issues statistics' do
+ get api("/issues_statistics", user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['statistics']).not_to be_nil
+ expect(json_response['statistics']['counts']['all']).to eq counts[:all]
+ expect(json_response['statistics']['counts']['closed']).to eq counts[:closed]
+ expect(json_response['statistics']['counts']['opened']).to eq counts[:opened]
+ end
+ end
+
+ describe 'GET /issues' do
+ context 'when unauthenticated' do
+ it 'returns an array of all issues' do
+ get api('/issues'), params: { scope: 'all' }
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+
+ it 'returns authentication error without any scope' do
+ get api('/issues')
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns authentication error when scope is assigned-to-me' do
+ get api('/issues'), params: { scope: 'assigned-to-me' }
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns authentication error when scope is created-by-me' do
+ get api('/issues'), params: { scope: 'created-by-me' }
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api('/issues'), params: { milestone: 'foo', scope: 'all' }
+
+ expect(response).to have_http_status(200)
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api('/issues'), params: { milestone: milestone.title, scope: 'all' }
+
+ expect(response).to have_http_status(200)
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ context 'issues_statistics' do
+ it 'returns authentication error without any scope' do
+ get api('/issues_statistics')
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns authentication error when scope is assigned_to_me' do
+ get api('/issues_statistics'), params: { scope: 'assigned_to_me' }
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns authentication error when scope is created_by_me' do
+ get api('/issues_statistics'), params: { scope: 'created_by_me' }
+
+ expect(response).to have_http_status(401)
+ end
+
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'sort does not affect statistics ' do
+ let(:params) { { state: :opened, order_by: 'updated_at' } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns an array of issues' do
+ get api('/issues', user)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ expect(json_response.first['title']).to eq(issue.title)
+ expect(json_response.last).to have_key('web_url')
+ end
+
+ it 'returns an array of closed issues' do
+ get api('/issues', user), params: { state: :closed }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of opened issues' do
+ get api('/issues', user), params: { state: :opened }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of all issues' do
+ get api('/issues', user), params: { state: :all }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns issues assigned to me' do
+ issue2 = create(:issue, assignees: [user2], project: project)
+
+ get api('/issues', user2), params: { scope: 'assigned_to_me' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues assigned to me (kebab-case)' do
+ issue2 = create(:issue, assignees: [user2], project: project)
+
+ get api('/issues', user2), params: { scope: 'assigned-to-me' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues authored by the given author id' do
+ issue2 = create(:issue, author: user2, project: project)
+
+ get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues assigned to the given assignee id' do
+ issue2 = create(:issue, assignees: [user2], project: project)
+
+ get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues authored by the given author id and assigned to the given assignee id' do
+ issue2 = create(:issue, author: user2, assignees: [user2], project: project)
+
+ get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues with no assignee' do
+ issue2 = create(:issue, author: user2, project: project)
+
+ get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues with no assignee' do
+ issue2 = create(:issue, author: user2, project: project)
+
+ get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues with any assignee' do
+ # This issue without assignee should not be returned
+ create(:issue, author: user2, project: project)
+
+ get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ it 'returns only confidential issues' do
+ get api('/issues', user), params: { confidential: true, scope: 'all' }
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api('/issues', user), params: { confidential: false }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns issues reacted by the authenticated user' do
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
+ create(:award_emoji, awardable: issue2, user: user2, name: 'star')
+ create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
+
+ get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
+
+ expect_paginated_array_response([issue2.id, issue.id])
+ end
+
+ it 'returns issues not reacted by the authenticated user' do
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
+ create(:award_emoji, awardable: issue2, user: user2, name: 'star')
+
+ get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns issues matching given search string for title' do
+ get api('/issues', user), params: { search: issue.title }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns issues matching given search string for title and scoped in title' do
+ get api('/issues', user), params: { search: issue.title, in: 'title' }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an empty array if no issue matches given search string for title and scoped in description' do
+ get api('/issues', user), params: { search: issue.title, in: 'description' }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns issues matching given search string for description' do
+ get api('/issues', user), params: { search: issue.description }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ context 'filtering before a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
+
+ it 'returns issues created before a specific date' do
+ get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues updated before a specific date' do
+ get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
+
+ expect_paginated_array_response(issue2.id)
+ end
+ end
+
+ context 'filtering after a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+
+ it 'returns issues created after a specific date' do
+ get api("/issues?created_after=#{issue2.created_at}", user)
+
+ expect_paginated_array_response(issue2.id)
+ end
+
+ it 'returns issues updated after a specific date' do
+ get api("/issues?updated_after=#{issue2.updated_at}", user)
+
+ expect_paginated_array_response(issue2.id)
+ end
+ end
+
+ context 'filter by labels or label_name param' do
+ context 'N+1' do
+ let(:label_b) { create(:label, title: 'foo', project: project) }
+ let(:label_c) { create(:label, title: 'bar', project: project) }
+
+ before do
+ create(:label_link, label: label_b, target: issue)
+ create(:label_link, label: label_c, target: issue)
+ end
+ it 'tests N+1' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] }
+ end
+
+ label_d = create(:label, title: 'dar', project: project)
+ label_e = create(:label, title: 'ear', project: project)
+ create(:label_link, label: label_d, target: issue)
+ create(:label_link, label: label_e, target: issue)
+
+ expect do
+ get api('/issues', user), params: { labels: [label.title, label_b.title, label_c.title] }
+ end.not_to exceed_query_limit(control)
+ expect(issue.labels.count).to eq(5)
+ end
+ end
+
+ it 'returns an array of labeled issues' do
+ get api('/issues', user), params: { labels: label.title }
+
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
+ end
+
+ it 'returns an array of labeled issues with labels param as array' do
+ get api('/issues', user), params: { labels: [label.title] }
+
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
+ end
+
+ context 'with labeled issues' do
+ let(:label_b) { create(:label, title: 'foo', project: project) }
+ let(:label_c) { create(:label, title: 'bar', project: project) }
+
+ before do
+ create(:label_link, label: label_b, target: issue)
+ create(:label_link, label: label_c, target: issue)
+
+ get api('/issues', user), params: params
+ end
+
+ it_behaves_like 'labeled issues with labels and label_name params'
+ end
+
+ it 'returns an empty array if no issue matches labels' do
+ get api('/issues', user), params: { labels: 'foo,bar' }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if no issue matches labels with labels param as array' do
+ get api('/issues', user), params: { labels: %w(foo bar) }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of labeled issues matching given state' do
+ get api('/issues', user), params: { labels: label.title, state: :opened }
+
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['state']).to eq('opened')
+ end
+
+ it 'returns an array of labeled issues matching given state with labels param as array' do
+ get api('/issues', user), params: { labels: [label.title], state: :opened }
+
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['state']).to eq('opened')
+ end
+
+ it 'returns an empty array if no issue matches labels and state filters' do
+ get api('/issues', user), params: { labels: label.title, state: :closed }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of issues with any label' do
+ get api('/issues', user), params: { labels: IssuesFinder::FILTER_ANY }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of issues with any label with labels param as array' do
+ get api('/issues', user), params: { labels: [IssuesFinder::FILTER_ANY] }
+
+ expect_paginated_array_response(issue.id)
+ end
+
+ it 'returns an array of issues with no label' do
+ get api('/issues', user), params: { labels: IssuesFinder::FILTER_NONE }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no label with labels param as array' do
+ get api('/issues', user), params: { labels: [IssuesFinder::FILTER_NONE] }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no label when using the legacy No+Label filter' do
+ get api('/issues', user), params: { labels: 'No Label' }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do
+ get api('/issues', user), params: { labels: ['No Label'] }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api("/issues?milestone=#{empty_milestone.title}", user)
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api('/issues?milestone=foo', user)
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of issues in given milestone' do
+ get api("/issues?milestone=#{milestone.title}", user)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns an array of issues in given milestone_title param' do
+ get api("/issues?milestone_title=#{milestone.title}", user)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api("/issues?milestone=#{milestone.title}&state=closed", user)
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no milestone' do
+ get api("/issues?milestone=#{no_milestone_title}", author)
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns an array of issues with no milestone using milestone_title param' do
+ get api("/issues?milestone_title=#{no_milestone_title}", author)
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns an array of issues found by iids' do
+ get api('/issues', user), params: { iids: [closed_issue.iid] }
+
+ expect_paginated_array_response(closed_issue.id)
+ end
+
+ it 'returns an empty array if iid does not exist' do
+ get api('/issues', user), params: { iids: [0] }
+
+ expect_paginated_array_response([])
+ end
+
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api('/issues', user)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:closed_issue2) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: closed_issue.created_at,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+
+ it 'page breaks first page correctly' do
+ get api('/issues?per_page=2', user)
+
+ expect_paginated_array_response([issue.id, closed_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api('/issues?per_page=2&page=2', user)
+
+ expect_paginated_array_response([closed_issue.id])
+ end
+ end
+ end
+
+ it 'sorts ascending when requested' do
+ get api('/issues?sort=asc', user)
+
+ expect_paginated_array_response([closed_issue.id, issue.id])
+ end
+
+ it 'sorts by updated_at descending when requested' do
+ get api('/issues?order_by=updated_at', user)
+
+ issue.touch(:updated_at)
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ it 'sorts by updated_at ascending when requested' do
+ get api('/issues?order_by=updated_at&sort=asc', user)
+
+ issue.touch(:updated_at)
+
+ expect_paginated_array_response([closed_issue.id, issue.id])
+ end
+
+ it 'matches V4 response schema' do
+ get api('/issues', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ end
+
+ it 'returns a related merge request count of 0 if there are no related merge requests' do
+ get api('/issues', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ expect(json_response.first).to include('merge_requests_count' => 0)
+ end
+
+ it 'returns a related merge request count > 0 if there are related merge requests' do
+ create(:merge_requests_closing_issues, issue: issue)
+
+ get api('/issues', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ expect(json_response.first).to include('merge_requests_count' => 1)
+ end
+
+ context 'issues_statistics' do
+ context 'no state is treated as all state' do
+ let(:params) { {} }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'statistics when all state is passed' do
+ let(:params) { { state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'closed state is treated as all state' do
+ let(:params) { { state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'opened state is treated as all state' do
+ let(:params) { { state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and no state treated as all state' do
+ let(:params) { { milestone: milestone.title } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and all state' do
+ let(:params) { { milestone: milestone.title, state: :all } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and closed state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :closed } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'when filtering by milestone and opened state treated as all state' do
+ let(:params) { { milestone: milestone.title, state: :opened } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'sort does not affect statistics ' do
+ let(:params) { { state: :opened, order_by: 'updated_at' } }
+ let(:counts) { { all: 2, closed: 1, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+ end
+ end
+
+ context 'filtering by assignee_username' do
+ let(:another_assignee) { create(:assignee) }
+ let!(:issue1) { create(:issue, author: user2, project: project, created_at: 3.days.ago) }
+ let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
+ let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
+
+ it 'returns issues with by assignee_username' do
+ get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([confidential_issue.id, issue3.id])
+ end
+
+ it 'returns issues by assignee_username as string' do
+ get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
+
+ expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
+ expect_paginated_array_response([confidential_issue.id, issue3.id])
+ end
+
+ it 'returns error when multiple assignees are passed' do
+ get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("allows one value, but found 2")
+ end
+
+ it 'returns error when assignee_username and assignee_id are passed together' do
+ get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to include("mutually exclusive")
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/issues/:issue_iid' do
+ it 'rejects a non member from deleting an issue' do
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'rejects a developer from deleting an issue' do
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ context 'when the user is project owner' do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ it 'deletes the issue if an admin requests it' do
+ delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) }
+ end
+ end
+
+ context 'when issue does not exist' do
+ it 'returns 404 when trying to move an issue' do
+ delete api("/projects/#{project.id}/issues/123", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ it 'returns 404 when using the issue ID instead of IID' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe 'time tracking endpoints' do
+ let(:issuable) { issue }
+
+ include_examples 'time tracking endpoints', 'issue'
+ end
+end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
new file mode 100644
index 00000000000..b74e8867310
--- /dev/null
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -0,0 +1,549 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Issues do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ let(:user2) { create(:user) }
+ let(:non_member) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:author) { create(:author) }
+ set(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
+ let!(:closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ state: :closed,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 3.hours.ago,
+ closed_at: 1.hour.ago
+ end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignees: [assignee],
+ created_at: generate(:past_time),
+ updated_at: 2.hours.ago
+ end
+ let!(:issue) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+ set(:label) do
+ create(:label, title: 'label', color: '#FFAABB', project: project)
+ end
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ set(:empty_milestone) do
+ create(:milestone, title: '2.0.0', project: project)
+ end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+
+ let(:no_milestone_title) { 'None' }
+ let(:any_milestone_title) { 'Any' }
+
+ before(:all) do
+ project.add_reporter(user)
+ project.add_guest(guest)
+ end
+
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ end
+
+ describe 'POST /projects/:id/issues' do
+ context 'support for deprecated assignee_id' do
+ it 'creates a new project issue' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', assignee_id: user2.id }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['assignee']['name']).to eq(user2.name)
+ expect(json_response['assignees'].first['name']).to eq(user2.name)
+ end
+
+ it 'creates a new project issue when assignee_id is empty' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', assignee_id: '' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['assignee']).to be_nil
+ end
+ end
+
+ context 'single assignee restrictions' do
+ it 'creates a new project issue with no more than one assignee' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', assignee_ids: [user2.id, guest.id] }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['assignees'].count).to eq(1)
+ end
+ end
+
+ context 'user does not have permissions to create issue' do
+ let(:not_member) { create(:user) }
+
+ before do
+ project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'renders 403' do
+ post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' }
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'an internal ID is provided' do
+ context 'by an admin' do
+ it 'sets the internal ID on the new issue' do
+ post api("/projects/#{project.id}/issues", admin),
+ params: { title: 'new issue', iid: 9001 }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['iid']).to eq 9001
+ end
+ end
+
+ context 'by an owner' do
+ it 'sets the internal ID on the new issue' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', iid: 9001 }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['iid']).to eq 9001
+ end
+ end
+
+ context 'by a group owner' do
+ let(:group) { create(:group) }
+ let(:group_project) { create(:project, :public, namespace: group) }
+
+ it 'sets the internal ID on the new issue' do
+ group.add_owner(user2)
+ post api("/projects/#{group_project.id}/issues", user2),
+ params: { title: 'new issue', iid: 9001 }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['iid']).to eq 9001
+ end
+ end
+
+ context 'by another user' do
+ it 'ignores the given internal ID' do
+ post api("/projects/#{project.id}/issues", user2),
+ params: { title: 'new issue', iid: 9001 }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['iid']).not_to eq 9001
+ end
+ end
+ end
+
+ it 'creates a new project issue' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['description']).to be_nil
+ expect(json_response['labels']).to eq(%w(label label2))
+ expect(json_response['confidential']).to be_falsy
+ expect(json_response['assignee']['name']).to eq(user2.name)
+ expect(json_response['assignees'].first['name']).to eq(user2.name)
+ end
+
+ it 'creates a new project issue with labels param as array' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['description']).to be_nil
+ expect(json_response['labels']).to eq(%w(label label2))
+ expect(json_response['confidential']).to be_falsy
+ expect(json_response['assignee']['name']).to eq(user2.name)
+ expect(json_response['assignees'].first['name']).to eq(user2.name)
+ end
+
+ it 'creates a new confidential project issue' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', confidential: true }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['confidential']).to be_truthy
+ end
+
+ it 'creates a new confidential project issue with a different param' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', confidential: 'y' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['confidential']).to be_truthy
+ end
+
+ it 'creates a public issue when confidential param is false' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', confidential: false }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['confidential']).to be_falsy
+ end
+
+ it 'creates a public issue when confidential param is invalid' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', confidential: 'foo' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('confidential is invalid')
+ end
+
+ it 'returns a 400 bad request if title not given' do
+ post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' }
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'allows special label names' do
+ post api("/projects/#{project.id}/issues", user),
+ params: {
+ title: 'new issue',
+ labels: 'label, label?, label&foo, ?, &'
+ }
+ expect(response.status).to eq(201)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'allows special label names with labels param as array' do
+ post api("/projects/#{project.id}/issues", user),
+ params: {
+ title: 'new issue',
+ labels: ['label', 'label?', 'label&foo, ?, &']
+ }
+ expect(response.status).to eq(201)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'returns 400 if title is too long' do
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'g' * 256 }
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['title']).to eq([
+ 'is too long (maximum is 255 characters)'
+ ])
+ end
+
+ context 'resolving discussions' do
+ let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
+ let(:merge_request) { discussion.noteable }
+ let(:project) { merge_request.source_project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'resolving all discussions in a merge request' do
+ before do
+ post api("/projects/#{project.id}/issues", user),
+ params: {
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ }
+ end
+
+ it_behaves_like 'creating an issue resolving discussions through the API'
+ end
+
+ context 'resolving a single discussion' do
+ before do
+ post api("/projects/#{project.id}/issues", user),
+ params: {
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id
+ }
+ end
+
+ it_behaves_like 'creating an issue resolving discussions through the API'
+ end
+ end
+
+ context 'with due date' do
+ it 'creates a new project issue' do
+ due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
+
+ post api("/projects/#{project.id}/issues", user),
+ params: { title: 'new issue', due_date: due_date }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['description']).to be_nil
+ expect(json_response['due_date']).to eq(due_date)
+ end
+ end
+
+ context 'setting created_at' do
+ let(:creation_time) { 2.weeks.ago }
+ let(:params) { { title: 'new issue', labels: 'label, label2', created_at: creation_time } }
+
+ context 'by an admin' do
+ it 'sets the creation time on the new issue' do
+ post api("/projects/#{project.id}/issues", admin), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
+ end
+ end
+
+ context 'by a project owner' do
+ it 'sets the creation time on the new issue' do
+ post api("/projects/#{project.id}/issues", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
+ end
+ end
+
+ context 'by a group owner' do
+ it 'sets the creation time on the new issue' do
+ group = create(:group)
+ group_project = create(:project, :public, namespace: group)
+ group.add_owner(user2)
+ post api("/projects/#{group_project.id}/issues", user2), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
+ end
+ end
+
+ context 'by another user' do
+ it 'ignores the given creation time' do
+ post api("/projects/#{project.id}/issues", user2), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time)
+ end
+ end
+ end
+
+ context 'the user can only read the issue' do
+ it 'cannot create new labels' do
+ expect do
+ post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' }
+ end.not_to change { project.labels.count }
+ end
+
+ it 'cannot create new labels with labels param as array' do
+ expect do
+ post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w(label label2) }
+ end.not_to change { project.labels.count }
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
+ end
+
+ let(:params) do
+ {
+ title: 'new issue',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it 'does not create a new project issue' do
+ expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq({ 'error' => 'Spam detected' })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('new issue')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ end
+ end
+
+ describe '/projects/:id/issues/:issue_iid/move' do
+ let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) }
+ let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) }
+
+ it 'moves an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
+ params: { to_project_id: target_project.id }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['project_id']).to eq(target_project.id)
+ end
+
+ context 'when source and target projects are the same' do
+ it 'returns 400 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
+ params: { to_project_id: project.id }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Cannot move issue to project it originates from!')
+ end
+ end
+
+ context 'when the user does not have the permission to move issues' do
+ it 'returns 400 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
+ params: { to_project_id: target_project2.id }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!')
+ end
+ end
+
+ it 'moves the issue to another namespace if I am admin' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
+ params: { to_project_id: target_project2.id }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['project_id']).to eq(target_project2.id)
+ end
+
+ context 'when using the issue ID instead of iid' do
+ it 'returns 404 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
+ params: { to_project_id: target_project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Issue Not Found')
+ end
+ end
+
+ context 'when issue does not exist' do
+ it 'returns 404 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/123/move", user),
+ params: { to_project_id: target_project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Issue Not Found')
+ end
+ end
+
+ context 'when source project does not exist' do
+ it 'returns 404 when trying to move an issue' do
+ post api("/projects/0/issues/#{issue.iid}/move", user),
+ params: { to_project_id: target_project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+ end
+
+ context 'when target project does not exist' do
+ it 'returns 404 when trying to move an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
+ params: { to_project_id: 0 }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST :id/issues/:issue_iid/subscribe' do
+ it 'subscribes to an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
+
+ 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}/issues/#{issue.iid}/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ post api("/projects/#{project.id}/issues/123/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if the issue ID is used instead of the iid' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if the issue is confidential' do
+ post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe 'POST :id/issues/:issue_id/unsubscribe' do
+ it 'unsubscribes from an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
+
+ 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}/issues/#{issue.iid}/unsubscribe", user2)
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ post api("/projects/#{project.id}/issues/123/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if using the issue ID instead of iid' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 if the issue is confidential' do
+ post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
new file mode 100644
index 00000000000..267cba93713
--- /dev/null
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -0,0 +1,392 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Issues do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ let(:user2) { create(:user) }
+ let(:non_member) { create(:user) }
+ set(:guest) { create(:user) }
+ set(:author) { create(:author) }
+ set(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let(:issue_title) { 'foo' }
+ let(:issue_description) { 'closed' }
+ let!(:closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ state: :closed,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 3.hours.ago,
+ closed_at: 1.hour.ago
+ end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignees: [assignee],
+ created_at: generate(:past_time),
+ updated_at: 2.hours.ago
+ end
+ let!(:issue) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+ set(:label) do
+ create(:label, title: 'label', color: '#FFAABB', project: project)
+ end
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ set(:empty_milestone) do
+ create(:milestone, title: '2.0.0', project: project)
+ end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+
+ let(:no_milestone_title) { 'None' }
+ let(:any_milestone_title) { 'Any' }
+
+ before(:all) do
+ project.add_reporter(user)
+ project.add_guest(guest)
+ end
+
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid to update only title' do
+ it 'updates a project issue' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it 'returns 404 error if issue iid not found' do
+ put api("/projects/#{project.id}/issues/44444", user),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 error if issue id is used instead of the iid' do
+ put api("/projects/#{project.id}/issues/#{issue.id}", user),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'allows special label names' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: {
+ title: 'updated title',
+ labels: 'label, label?, label&foo, ?, &'
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'allows special label names with labels param as array' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: {
+ title: 'updated title',
+ labels: ['label', 'label?', 'label&foo, ?, &']
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ context 'confidential issues' do
+ it 'returns 403 for non project members' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'returns 403 for project members with guest role' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'updates a confidential issue for project members' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it 'updates a confidential issue for author' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it 'updates a confidential issue for admin' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it 'sets an issue to confidential' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { confidential: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['confidential']).to be_truthy
+ end
+
+ it 'makes a confidential issue public' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
+ params: { confidential: false }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['confidential']).to be_falsy
+ end
+
+ it 'does not update a confidential issue with wrong confidential flag' do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
+ params: { confidential: 'foo' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('confidential is invalid')
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
+ let(:params) do
+ {
+ title: 'updated title',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it 'does not create a new project issue' do
+ allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
+ allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
+
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq({ 'error' => 'Spam detected' })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('updated title')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ end
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do
+ context 'support for deprecated assignee_id' do
+ it 'removes assignee' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { assignee_id: 0 }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['assignee']).to be_nil
+ end
+
+ it 'updates an issue with new assignee' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { assignee_id: user2.id }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['assignee']['name']).to eq(user2.name)
+ end
+ end
+
+ it 'removes assignee' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { assignee_ids: [0] }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['assignees']).to be_empty
+ end
+
+ it 'updates an issue with new assignee' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { assignee_ids: [user2.id] }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['assignees'].first['name']).to eq(user2.name)
+ end
+
+ context 'single assignee restrictions' do
+ it 'updates an issue with several assignees but only one has been applied' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { assignee_ids: [user2.id, guest.id] }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['assignees'].size).to eq(1)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
+ let!(:label) { create(:label, title: 'dummy', project: project) }
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+
+ it 'does not update labels if not present' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { title: 'updated title' }
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to eq([label.title])
+ end
+
+ it 'removes all labels and touches the record' do
+ Timecop.travel(1.minute.from_now) do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' }
+ end
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to eq([])
+ expect(json_response['updated_at']).to be > Time.now
+ end
+
+ it 'removes all labels and touches the record with labels param as array' do
+ Timecop.travel(1.minute.from_now) do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: [''] }
+ end
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to eq([])
+ expect(json_response['updated_at']).to be > Time.now
+ end
+
+ it 'updates labels and touches the record' do
+ Timecop.travel(1.minute.from_now) do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: 'foo,bar' }
+ end
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to include 'foo'
+ expect(json_response['labels']).to include 'bar'
+ expect(json_response['updated_at']).to be > Time.now
+ end
+
+ it 'updates labels and touches the record with labels param as array' do
+ Timecop.travel(1.minute.from_now) do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: %w(foo bar) }
+ end
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to include 'foo'
+ expect(json_response['labels']).to include 'bar'
+ expect(json_response['updated_at']).to be > Time.now
+ end
+
+ it 'allows special label names' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' }
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label:foo'
+ expect(json_response['labels']).to include 'label-bar'
+ expect(json_response['labels']).to include 'label_bar'
+ expect(json_response['labels']).to include 'label/bar'
+ expect(json_response['labels']).to include 'label?bar'
+ expect(json_response['labels']).to include 'label&bar'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'allows special label names with labels param as array' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] }
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label:foo'
+ expect(json_response['labels']).to include 'label-bar'
+ expect(json_response['labels']).to include 'label_bar'
+ expect(json_response['labels']).to include 'label/bar'
+ expect(json_response['labels']).to include 'label?bar'
+ expect(json_response['labels']).to include 'label&bar'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'returns 400 if title is too long' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { title: 'g' * 256 }
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['title']).to eq([
+ 'is too long (maximum is 255 characters)'
+ ])
+ end
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do
+ it 'updates a project issue' do
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: 'label2', state_event: 'close' }
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['labels']).to include 'label2'
+ expect(json_response['state']).to eq 'closed'
+ end
+
+ it 'reopens a project isssue' do
+ put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['state']).to eq 'opened'
+ end
+
+ context 'when an admin or owner makes the request' do
+ it 'accepts the update date to be set' do
+ update_time = 2.weeks.ago
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user),
+ params: { labels: 'label3', state_event: 'close', updated_at: update_time }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['labels']).to include 'label3'
+ expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
+ it 'creates a new project issue' do
+ due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
+
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['due_date']).to eq(due_date)
+ end
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
deleted file mode 100644
index 1a4be2bd30f..00000000000
--- a/spec/requests/api/issues_spec.rb
+++ /dev/null
@@ -1,2041 +0,0 @@
-require 'spec_helper'
-
-describe API::Issues do
- set(:user) { create(:user) }
- set(:project) do
- create(:project, :public, creator_id: user.id, namespace: user.namespace)
- end
-
- let(:user2) { create(:user) }
- let(:non_member) { create(:user) }
- set(:guest) { create(:user) }
- set(:author) { create(:author) }
- set(:assignee) { create(:assignee) }
- let(:admin) { create(:user, :admin) }
- let(:issue_title) { 'foo' }
- let(:issue_description) { 'closed' }
- let!(:closed_issue) do
- create :closed_issue,
- author: user,
- assignees: [user],
- project: project,
- state: :closed,
- milestone: milestone,
- created_at: generate(:past_time),
- updated_at: 3.hours.ago,
- closed_at: 1.hour.ago
- end
- let!(:confidential_issue) do
- create :issue,
- :confidential,
- project: project,
- author: author,
- assignees: [assignee],
- created_at: generate(:past_time),
- updated_at: 2.hours.ago
- end
- let!(:issue) do
- create :issue,
- author: user,
- assignees: [user],
- project: project,
- milestone: milestone,
- created_at: generate(:past_time),
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
- end
- set(:label) do
- create(:label, title: 'label', color: '#FFAABB', project: project)
- end
- let!(:label_link) { create(:label_link, label: label, target: issue) }
- set(:milestone) { create(:milestone, title: '1.0.0', project: project) }
- set(:empty_milestone) do
- create(:milestone, title: '2.0.0', project: project)
- end
- let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
-
- let(:no_milestone_title) { "None" }
- let(:any_milestone_title) { "Any" }
-
- before(:all) do
- project.add_reporter(user)
- project.add_guest(guest)
- end
-
- describe "GET /issues" do
- context "when unauthenticated" do
- it "returns an array of all issues" do
- get api("/issues"), params: { scope: 'all' }
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- end
-
- it "returns authentication error without any scope" do
- get api("/issues")
-
- expect(response).to have_http_status(401)
- end
-
- it "returns authentication error when scope is assigned-to-me" do
- get api("/issues"), params: { scope: 'assigned-to-me' }
-
- expect(response).to have_http_status(401)
- end
-
- it "returns authentication error when scope is created-by-me" do
- get api("/issues"), params: { scope: 'created-by-me' }
-
- expect(response).to have_http_status(401)
- end
- end
-
- context "when authenticated" do
- it "returns an array of issues" do
- get api("/issues", user)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- expect(json_response.first['title']).to eq(issue.title)
- expect(json_response.last).to have_key('web_url')
- end
-
- it 'returns an array of closed issues' do
- get api('/issues', user), params: { state: :closed }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an array of opened issues' do
- get api('/issues', user), params: { state: :opened }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an array of all issues' do
- get api('/issues', user), params: { state: :all }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns issues assigned to me' do
- issue2 = create(:issue, assignees: [user2], project: project)
-
- get api('/issues', user2), params: { scope: 'assigned_to_me' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues assigned to me (kebab-case)' do
- issue2 = create(:issue, assignees: [user2], project: project)
-
- get api('/issues', user2), params: { scope: 'assigned-to-me' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues authored by the given author id' do
- issue2 = create(:issue, author: user2, project: project)
-
- get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues assigned to the given assignee id' do
- issue2 = create(:issue, assignees: [user2], project: project)
-
- get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues authored by the given author id and assigned to the given assignee id' do
- issue2 = create(:issue, author: user2, assignees: [user2], project: project)
-
- get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues with no assignee' do
- issue2 = create(:issue, author: user2, project: project)
-
- get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues with no assignee' do
- issue2 = create(:issue, author: user2, project: project)
-
- get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues with any assignee' do
- # This issue without assignee should not be returned
- create(:issue, author: user2, project: project)
-
- get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'returns only confidential issues' do
- get api('/issues', user), params: { confidential: true, scope: 'all' }
-
- expect_paginated_array_response(confidential_issue.id)
- end
-
- it 'returns only public issues' do
- get api('/issues', user), params: { confidential: false }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns issues reacted by the authenticated user' do
- issue2 = create(:issue, project: project, author: user, assignees: [user])
- create(:award_emoji, awardable: issue2, user: user2, name: 'star')
- create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
-
- get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
-
- expect_paginated_array_response([issue2.id, issue.id])
- end
-
- it 'returns issues not reacted by the authenticated user' do
- issue2 = create(:issue, project: project, author: user, assignees: [user])
- create(:award_emoji, awardable: issue2, user: user2, name: 'star')
-
- get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns issues matching given search string for title' do
- get api("/issues", user), params: { search: issue.title }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns issues matching given search string for title and scoped in title' do
- get api("/issues", user), params: { search: issue.title, in: 'title' }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an empty array if no issue matches given search string for title and scoped in description' do
- get api("/issues", user), params: { search: issue.title, in: 'description' }
-
- expect_paginated_array_response([])
- end
-
- it 'returns issues matching given search string for description' do
- get api("/issues", user), params: { search: issue.description }
-
- expect_paginated_array_response(issue.id)
- end
-
- context 'filtering before a specific date' do
- let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
-
- it 'returns issues created before a specific date' do
- get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues updated before a specific date' do
- get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
-
- expect_paginated_array_response(issue2.id)
- end
- end
-
- context 'filtering after a specific date' do
- let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
-
- it 'returns issues created after a specific date' do
- get api("/issues?created_after=#{issue2.created_at}", user)
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues updated after a specific date' do
- get api("/issues?updated_after=#{issue2.updated_at}", user)
-
- expect_paginated_array_response(issue2.id)
- end
- end
-
- it 'returns an array of labeled issues' do
- get api("/issues", user), params: { labels: label.title }
-
- expect_paginated_array_response(issue.id)
- expect(json_response.first['labels']).to eq([label.title])
- end
-
- it 'returns an array of labeled issues when all labels matches' do
- label_b = create(:label, title: 'foo', project: project)
- label_c = create(:label, title: 'bar', project: project)
-
- create(:label_link, label: label_b, target: issue)
- create(:label_link, label: label_c, target: issue)
-
- get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
-
- expect_paginated_array_response(issue.id)
- expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
- end
-
- it 'returns an empty array if no issue matches labels' do
- get api('/issues', user), params: { labels: 'foo,bar' }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of labeled issues matching given state' do
- get api("/issues", user), params: { labels: label.title, state: :opened }
-
- expect_paginated_array_response(issue.id)
- expect(json_response.first['labels']).to eq([label.title])
- expect(json_response.first['state']).to eq('opened')
- end
-
- it 'returns an empty array if no issue matches labels and state filters' do
- get api("/issues", user), params: { labels: label.title, state: :closed }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of issues with any label' do
- get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an array of issues with no label' do
- get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an array of issues with no label when using the legacy No+Label filter' do
- get api("/issues", user), params: { labels: "No Label" }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an empty array if no issue matches milestone' do
- get api("/issues?milestone=#{empty_milestone.title}", user)
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if milestone does not exist' do
- get api("/issues?milestone=foo", user)
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of issues in given milestone' do
- get api("/issues?milestone=#{milestone.title}", user)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns an array of issues matching state in milestone' do
- get api("/issues?milestone=#{milestone.title}"\
- '&state=closed', user)
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an array of issues with no milestone' do
- get api("/issues?milestone=#{no_milestone_title}", author)
-
- expect_paginated_array_response(confidential_issue.id)
- end
-
- it 'returns an array of issues found by iids' do
- get api('/issues', user), params: { iids: [closed_issue.iid] }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an empty array if iid does not exist' do
- get api("/issues", user), params: { iids: [99999] }
-
- expect_paginated_array_response([])
- end
-
- context 'without sort params' do
- it 'sorts by created_at descending by default' do
- get api('/issues', user)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- context 'with 2 issues with same created_at' do
- let!(:closed_issue2) do
- create :closed_issue,
- author: user,
- assignees: [user],
- project: project,
- milestone: milestone,
- created_at: closed_issue.created_at,
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
- end
-
- it 'page breaks first page correctly' do
- get api('/issues?per_page=2', user)
-
- expect_paginated_array_response([issue.id, closed_issue2.id])
- end
-
- it 'page breaks second page correctly' do
- get api('/issues?per_page=2&page=2', user)
-
- expect_paginated_array_response([closed_issue.id])
- end
- end
- end
-
- it 'sorts ascending when requested' do
- get api('/issues?sort=asc', user)
-
- expect_paginated_array_response([closed_issue.id, issue.id])
- end
-
- it 'sorts by updated_at descending when requested' do
- get api('/issues?order_by=updated_at', user)
-
- issue.touch(:updated_at)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'sorts by updated_at ascending when requested' do
- get api('/issues?order_by=updated_at&sort=asc', user)
-
- issue.touch(:updated_at)
-
- expect_paginated_array_response([closed_issue.id, issue.id])
- end
-
- it 'matches V4 response schema' do
- get api('/issues', user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/issues')
- end
-
- it 'returns a related merge request count of 0 if there are no related merge requests' do
- get api('/issues', user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/issues')
- expect(json_response.first).to include('merge_requests_count' => 0)
- end
-
- it 'returns a related merge request count > 0 if there are related merge requests' do
- create(:merge_requests_closing_issues, issue: issue)
-
- get api('/issues', user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/issues')
- expect(json_response.first).to include('merge_requests_count' => 1)
- end
- end
- end
-
- describe "GET /groups/:id/issues" do
- let!(:group) { create(:group) }
- let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) }
- let!(:group_closed_issue) do
- create :closed_issue,
- author: user,
- assignees: [user],
- project: group_project,
- state: :closed,
- milestone: group_milestone,
- updated_at: 3.hours.ago,
- created_at: 1.day.ago
- end
- let!(:group_confidential_issue) do
- create :issue,
- :confidential,
- project: group_project,
- author: author,
- assignees: [assignee],
- updated_at: 2.hours.ago,
- created_at: 2.days.ago
- end
- let!(:group_issue) do
- create :issue,
- author: user,
- assignees: [user],
- project: group_project,
- milestone: group_milestone,
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description,
- created_at: 5.days.ago
- end
- let!(:group_label) do
- create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
- end
- let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
- let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
- let!(:group_empty_milestone) do
- create(:milestone, title: '4.0.0', project: group_project)
- end
- let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
-
- let(:base_url) { "/groups/#{group.id}/issues" }
-
- context 'when group has subgroups', :nested_groups do
- let(:subgroup_1) { create(:group, parent: group) }
- let(:subgroup_2) { create(:group, parent: subgroup_1) }
-
- let(:subgroup_1_project) { create(:project, namespace: subgroup_1) }
- let(:subgroup_2_project) { create(:project, namespace: subgroup_2) }
-
- let!(:issue_1) { create(:issue, project: subgroup_1_project) }
- let!(:issue_2) { create(:issue, project: subgroup_2_project) }
-
- before do
- group.add_developer(user)
- end
-
- it 'also returns subgroups projects issues' do
- get api(base_url, user)
-
- expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
- end
- end
-
- context 'when user is unauthenticated' do
- it 'lists all issues in public projects' do
- get api(base_url)
-
- expect_paginated_array_response([group_closed_issue.id, group_issue.id])
- end
- end
-
- context 'when user is a group member' do
- before do
- group_project.add_reporter(user)
- end
-
- it 'returns all group issues (including opened and closed)' do
- get api(base_url, admin)
-
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
- end
-
- it 'returns group issues without confidential issues for non project members' do
- get api(base_url, non_member), params: { state: :opened }
-
- expect_paginated_array_response(group_issue.id)
- end
-
- it 'returns group confidential issues for author' do
- get api(base_url, author), params: { state: :opened }
-
- expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
- end
-
- it 'returns group confidential issues for assignee' do
- get api(base_url, assignee), params: { state: :opened }
-
- expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
- end
-
- it 'returns group issues with confidential issues for project members' do
- get api(base_url, user), params: { state: :opened }
-
- expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
- end
-
- it 'returns group confidential issues for admin' do
- get api(base_url, admin), params: { state: :opened }
-
- expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
- end
-
- it 'returns only confidential issues' do
- get api(base_url, user), params: { confidential: true }
-
- expect_paginated_array_response(group_confidential_issue.id)
- end
-
- it 'returns only public issues' do
- get api(base_url, user), params: { confidential: false }
-
- expect_paginated_array_response([group_closed_issue.id, group_issue.id])
- end
-
- it 'returns an array of labeled group issues' do
- get api(base_url, user), params: { labels: group_label.title }
-
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['labels']).to eq([group_label.title])
- end
-
- it 'returns an array of labeled group issues where all labels match' do
- get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
-
- expect_paginated_array_response([])
- end
-
- it 'returns issues matching given search string for title' do
- get api(base_url, user), params: { search: group_issue.title }
-
- expect_paginated_array_response(group_issue.id)
- end
-
- it 'returns issues matching given search string for description' do
- get api(base_url, user), params: { search: group_issue.description }
-
- expect_paginated_array_response(group_issue.id)
- end
-
- it 'returns an array of labeled issues when all labels matches' do
- label_b = create(:label, title: 'foo', project: group_project)
- label_c = create(:label, title: 'bar', project: group_project)
-
- create(:label_link, label: label_b, target: group_issue)
- create(:label_link, label: label_c, target: group_issue)
-
- get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" }
-
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
- end
-
- it 'returns an array of issues found by iids' do
- get api(base_url, user), params: { iids: [group_issue.iid] }
-
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['id']).to eq(group_issue.id)
- end
-
- it 'returns an empty array if iid does not exist' do
- get api(base_url, user), params: { iids: [99999] }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if no group issue matches labels' do
- get api(base_url, user), params: { labels: 'foo,bar' }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of group issues with any label' do
- get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
-
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['id']).to eq(group_issue.id)
- end
-
- it 'returns an array of group issues with no label' do
- get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
-
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
- end
-
- it 'returns an empty array if no issue matches milestone' do
- get api(base_url, user), params: { milestone: group_empty_milestone.title }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if milestone does not exist' do
- get api(base_url, user), params: { milestone: 'foo' }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of issues in given milestone' do
- get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
-
- expect_paginated_array_response(group_issue.id)
- end
-
- it 'returns an array of issues matching state in milestone' do
- get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
-
- expect_paginated_array_response(group_closed_issue.id)
- end
-
- it 'returns an array of issues with no milestone' do
- get api(base_url, user), params: { milestone: no_milestone_title }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect_paginated_array_response(group_confidential_issue.id)
- end
-
- context 'without sort params' do
- it 'sorts by created_at descending by default' do
- get api(base_url, user)
-
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
- end
-
- context 'with 2 issues with same created_at' do
- let!(:group_issue2) do
- create :issue,
- author: user,
- assignees: [user],
- project: group_project,
- milestone: group_milestone,
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description,
- created_at: group_issue.created_at
- end
-
- it 'page breaks first page correctly' do
- get api("#{base_url}?per_page=3", user)
-
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id])
- end
-
- it 'page breaks second page correctly' do
- get api("#{base_url}?per_page=3&page=2", user)
-
- expect_paginated_array_response([group_issue.id])
- end
- end
- end
-
- it 'sorts ascending when requested' do
- get api("#{base_url}?sort=asc", user)
-
- expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
- end
-
- it 'sorts by updated_at descending when requested' do
- get api("#{base_url}?order_by=updated_at", user)
-
- group_issue.touch(:updated_at)
-
- expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
- end
-
- it 'sorts by updated_at ascending when requested' do
- get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
-
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
- end
- end
- end
-
- describe "GET /projects/:id/issues" do
- let(:base_url) { "/projects/#{project.id}" }
-
- context 'when unauthenticated' do
- it 'returns public project issues' do
- get api("/projects/#{project.id}/issues")
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
- end
-
- it 'avoids N+1 queries' do
- get api("/projects/#{project.id}/issues", user)
-
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/projects/#{project.id}/issues", user)
- end.count
-
- create_list(:issue, 3, project: project)
-
- expect do
- get api("/projects/#{project.id}/issues", user)
- end.not_to exceed_all_query_limit(control_count)
- end
-
- it 'returns 404 when project does not exist' do
- get api('/projects/1000/issues', non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 on private projects for other users" do
- private_project = create(:project, :private)
- create(:issue, project: private_project)
-
- get api("/projects/#{private_project.id}/issues", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns no issues when user has access to project but not issues' do
- restricted_project = create(:project, :public, :issues_private)
- create(:issue, project: restricted_project)
-
- get api("/projects/#{restricted_project.id}/issues", non_member)
-
- expect_paginated_array_response([])
- end
-
- it 'returns project issues without confidential issues for non project members' do
- get api("#{base_url}/issues", non_member)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns project issues without confidential issues for project members with guest role' do
- get api("#{base_url}/issues", guest)
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns project confidential issues for author' do
- get api("#{base_url}/issues", author)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'returns only confidential issues' do
- get api("#{base_url}/issues", author), params: { confidential: true }
-
- expect_paginated_array_response(confidential_issue.id)
- end
-
- it 'returns only public issues' do
- get api("#{base_url}/issues", author), params: { confidential: false }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns project confidential issues for assignee' do
- get api("#{base_url}/issues", assignee)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'returns project issues with confidential issues for project members' do
- get api("#{base_url}/issues", user)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'returns project confidential issues for admin' do
- get api("#{base_url}/issues", admin)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'returns an array of labeled project issues' do
- get api("#{base_url}/issues", user), params: { labels: label.title }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an array of labeled issues when all labels matches' do
- label_b = create(:label, title: 'foo', project: project)
- label_c = create(:label, title: 'bar', project: project)
-
- create(:label_link, label: label_b, target: issue)
- create(:label_link, label: label_c, target: issue)
-
- get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns issues matching given search string for title' do
- get api("#{base_url}/issues?search=#{issue.title}", user)
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns issues matching given search string for description' do
- get api("#{base_url}/issues?search=#{issue.description}", user)
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an array of issues found by iids' do
- get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an empty array if iid does not exist' do
- get api("#{base_url}/issues", user), params: { iids: [99999] }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if not all labels matches' do
- get api("#{base_url}/issues?labels=#{label.title},foo", user)
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of project issues with any label' do
- get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
-
- expect_paginated_array_response(issue.id)
- end
-
- it 'returns an array of project issues with no label' do
- get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
-
- expect_paginated_array_response([confidential_issue.id, closed_issue.id])
- end
-
- it 'returns an empty array if no project issue matches labels' do
- get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if no issue matches milestone' do
- get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an empty array if milestone does not exist' do
- get api("#{base_url}/issues", user), params: { milestone: :foo }
-
- expect_paginated_array_response([])
- end
-
- it 'returns an array of issues in given milestone' do
- get api("#{base_url}/issues", user), params: { milestone: milestone.title }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an array of issues with no milestone' do
- get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
-
- expect_paginated_array_response(confidential_issue.id)
- end
-
- it 'returns an array of issues with any milestone' do
- get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
-
- expect_paginated_array_response([issue.id, closed_issue.id])
- end
-
- context 'without sort params' do
- it 'sorts by created_at descending by default' do
- get api("#{base_url}/issues", user)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- context 'with 2 issues with same created_at' do
- let!(:closed_issue2) do
- create :closed_issue,
- author: user,
- assignees: [user],
- project: project,
- milestone: milestone,
- created_at: closed_issue.created_at,
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
- end
-
- it 'page breaks first page correctly' do
- get api("#{base_url}/issues?per_page=3", user)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id])
- end
-
- it 'page breaks second page correctly' do
- get api("#{base_url}/issues?per_page=3&page=2", user)
-
- expect_paginated_array_response([closed_issue.id])
- end
- end
- end
-
- it 'sorts ascending when requested' do
- get api("#{base_url}/issues", user), params: { sort: :asc }
-
- expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
- end
-
- it 'sorts by updated_at descending when requested' do
- get api("#{base_url}/issues", user), params: { order_by: :updated_at }
-
- issue.touch(:updated_at)
-
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
- end
-
- it 'sorts by updated_at ascending when requested' do
- get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
-
- expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
- end
- end
-
- describe "GET /projects/:id/issues/:issue_iid" do
- context 'when unauthenticated' do
- it 'returns public issues' do
- get api("/projects/#{project.id}/issues/#{issue.iid}")
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- it 'exposes known attributes' do
- get api("/projects/#{project.id}/issues/#{issue.iid}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['id']).to eq(issue.id)
- expect(json_response['iid']).to eq(issue.iid)
- expect(json_response['project_id']).to eq(issue.project.id)
- expect(json_response['title']).to eq(issue.title)
- expect(json_response['description']).to eq(issue.description)
- expect(json_response['state']).to eq(issue.state)
- expect(json_response['closed_at']).to be_falsy
- expect(json_response['created_at']).to be_present
- expect(json_response['updated_at']).to be_present
- expect(json_response['labels']).to eq(issue.label_names)
- expect(json_response['milestone']).to be_a Hash
- expect(json_response['assignees']).to be_a Array
- expect(json_response['assignee']).to be_a Hash
- expect(json_response['author']).to be_a Hash
- expect(json_response['confidential']).to be_falsy
- end
-
- it "exposes the 'closed_at' attribute" do
- get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['closed_at']).to be_present
- end
-
- context 'links exposure' do
- it 'exposes related resources full URIs' do
- get api("/projects/#{project.id}/issues/#{issue.iid}", user)
-
- links = json_response['_links']
-
- expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}")
- expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes")
- expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji")
- expect(links['project']).to end_with("/api/v4/projects/#{project.id}")
- end
- end
-
- it "returns a project issue by internal id" do
- get api("/projects/#{project.id}/issues/#{issue.iid}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(issue.title)
- expect(json_response['iid']).to eq(issue.iid)
- end
-
- it "returns 404 if issue id not found" do
- get api("/projects/#{project.id}/issues/54321", user)
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 if the issue ID is used" do
- get api("/projects/#{project.id}/issues/#{issue.id}", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- context 'confidential issues' do
- it "returns 404 for non project members" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 for project members with guest role" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns confidential issue for project members" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(confidential_issue.title)
- expect(json_response['iid']).to eq(confidential_issue.iid)
- end
-
- it "returns confidential issue for author" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(confidential_issue.title)
- expect(json_response['iid']).to eq(confidential_issue.iid)
- end
-
- it "returns confidential issue for assignee" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(confidential_issue.title)
- expect(json_response['iid']).to eq(confidential_issue.iid)
- end
-
- it "returns confidential issue for admin" do
- get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(confidential_issue.title)
- expect(json_response['iid']).to eq(confidential_issue.iid)
- end
- end
- end
-
- describe "POST /projects/:id/issues" do
- context 'support for deprecated assignee_id' do
- it 'creates a new project issue' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', assignee_id: user2.id }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['assignee']['name']).to eq(user2.name)
- expect(json_response['assignees'].first['name']).to eq(user2.name)
- end
-
- it 'creates a new project issue when assignee_id is empty' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', assignee_id: '' }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['assignee']).to be_nil
- end
- end
-
- context 'single assignee restrictions' do
- it 'creates a new project issue with no more than one assignee' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', assignee_ids: [user2.id, guest.id] }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['assignees'].count).to eq(1)
- end
- end
-
- context 'user does not have permissions to create issue' do
- let(:not_member) { create(:user) }
-
- before do
- project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
- end
-
- it 'renders 403' do
- post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' }
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- context 'an internal ID is provided' do
- context 'by an admin' do
- it 'sets the internal ID on the new issue' do
- post api("/projects/#{project.id}/issues", admin),
- params: { title: 'new issue', iid: 9001 }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['iid']).to eq 9001
- end
- end
-
- context 'by an owner' do
- it 'sets the internal ID on the new issue' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', iid: 9001 }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['iid']).to eq 9001
- end
- end
-
- context 'by a group owner' do
- let(:group) { create(:group) }
- let(:group_project) { create(:project, :public, namespace: group) }
-
- it 'sets the internal ID on the new issue' do
- group.add_owner(user2)
- post api("/projects/#{group_project.id}/issues", user2),
- params: { title: 'new issue', iid: 9001 }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['iid']).to eq 9001
- end
- end
-
- context 'by another user' do
- it 'ignores the given internal ID' do
- post api("/projects/#{project.id}/issues", user2),
- params: { title: 'new issue', iid: 9001 }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['iid']).not_to eq 9001
- end
- end
- end
-
- it 'creates a new project issue' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['description']).to be_nil
- expect(json_response['labels']).to eq(%w(label label2))
- expect(json_response['confidential']).to be_falsy
- expect(json_response['assignee']['name']).to eq(user2.name)
- expect(json_response['assignees'].first['name']).to eq(user2.name)
- end
-
- it 'creates a new confidential project issue' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', confidential: true }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['confidential']).to be_truthy
- end
-
- it 'creates a new confidential project issue with a different param' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', confidential: 'y' }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['confidential']).to be_truthy
- end
-
- it 'creates a public issue when confidential param is false' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', confidential: false }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['confidential']).to be_falsy
- end
-
- it 'creates a public issue when confidential param is invalid' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', confidential: 'foo' }
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('confidential is invalid')
- end
-
- it "returns a 400 bad request if title not given" do
- post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' }
- expect(response).to have_gitlab_http_status(400)
- end
-
- it 'allows special label names' do
- post api("/projects/#{project.id}/issues", user),
- params: {
- title: 'new issue',
- labels: 'label, label?, label&foo, ?, &'
- }
- expect(response.status).to eq(201)
- expect(json_response['labels']).to include 'label'
- expect(json_response['labels']).to include 'label?'
- expect(json_response['labels']).to include 'label&foo'
- expect(json_response['labels']).to include '?'
- expect(json_response['labels']).to include '&'
- end
-
- it 'returns 400 if title is too long' do
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'g' * 256 }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['title']).to eq([
- 'is too long (maximum is 255 characters)'
- ])
- end
-
- context 'resolving discussions' do
- let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
- let(:merge_request) { discussion.noteable }
- let(:project) { merge_request.source_project }
-
- before do
- project.add_maintainer(user)
- end
-
- context 'resolving all discussions in a merge request' do
- before do
- post api("/projects/#{project.id}/issues", user),
- params: {
- title: 'New Issue',
- merge_request_to_resolve_discussions_of: merge_request.iid
- }
- end
-
- it_behaves_like 'creating an issue resolving discussions through the API'
- end
-
- context 'resolving a single discussion' do
- before do
- post api("/projects/#{project.id}/issues", user),
- params: {
- title: 'New Issue',
- merge_request_to_resolve_discussions_of: merge_request.iid,
- discussion_to_resolve: discussion.id
- }
- end
-
- it_behaves_like 'creating an issue resolving discussions through the API'
- end
- end
-
- context 'with due date' do
- it 'creates a new project issue' do
- due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
-
- post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', due_date: due_date }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq('new issue')
- expect(json_response['description']).to be_nil
- expect(json_response['due_date']).to eq(due_date)
- end
- end
-
- context 'setting created_at' do
- let(:creation_time) { 2.weeks.ago }
- let(:params) { { title: 'new issue', labels: 'label, label2', created_at: creation_time } }
-
- context 'by an admin' do
- it 'sets the creation time on the new issue' do
- post api("/projects/#{project.id}/issues", admin), params: params
-
- expect(response).to have_gitlab_http_status(201)
- expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
- end
- end
-
- context 'by a project owner' do
- it 'sets the creation time on the new issue' do
- post api("/projects/#{project.id}/issues", user), params: params
-
- expect(response).to have_gitlab_http_status(201)
- expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
- end
- end
-
- context 'by a group owner' do
- it 'sets the creation time on the new issue' do
- group = create(:group)
- group_project = create(:project, :public, namespace: group)
- group.add_owner(user2)
- post api("/projects/#{group_project.id}/issues", user2), params: params
-
- expect(response).to have_gitlab_http_status(201)
- expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
- end
- end
-
- context 'by another user' do
- it 'ignores the given creation time' do
- post api("/projects/#{project.id}/issues", user2), params: params
-
- expect(response).to have_gitlab_http_status(201)
- expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time)
- end
- end
- end
-
- context 'the user can only read the issue' do
- it 'cannot create new labels' do
- expect do
- post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' }
- end.not_to change { project.labels.count }
- end
- end
- end
-
- describe 'POST /projects/:id/issues with spam filtering' do
- before do
- allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
- allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
- end
-
- let(:params) do
- {
- title: 'new issue',
- description: 'content here',
- labels: 'label, label2'
- }
- end
-
- it "does not create a new project issue" do
- expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count)
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
-
- spam_logs = SpamLog.all
- expect(spam_logs.count).to eq(1)
- expect(spam_logs[0].title).to eq('new issue')
- expect(spam_logs[0].description).to eq('content here')
- expect(spam_logs[0].user).to eq(user)
- expect(spam_logs[0].noteable_type).to eq('Issue')
- end
- end
-
- describe "PUT /projects/:id/issues/:issue_iid to update only title" do
- it "updates a project issue" do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['title']).to eq('updated title')
- end
-
- it "returns 404 error if issue iid not found" do
- put api("/projects/#{project.id}/issues/44444", user),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 error if issue id is used instead of the iid" do
- put api("/projects/#{project.id}/issues/#{issue.id}", user),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'allows special label names' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: {
- title: 'updated title',
- labels: 'label, label?, label&foo, ?, &'
- }
-
- expect(response.status).to eq(200)
- expect(json_response['labels']).to include 'label'
- expect(json_response['labels']).to include 'label?'
- expect(json_response['labels']).to include 'label&foo'
- expect(json_response['labels']).to include '?'
- expect(json_response['labels']).to include '&'
- end
-
- context 'confidential issues' do
- it "returns 403 for non project members" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(403)
- end
-
- it "returns 403 for project members with guest role" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(403)
- end
-
- it "updates a confidential issue for project members" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq('updated title')
- end
-
- it "updates a confidential issue for author" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq('updated title')
- end
-
- it "updates a confidential issue for admin" do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq('updated title')
- end
-
- it 'sets an issue to confidential' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { confidential: true }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['confidential']).to be_truthy
- end
-
- it 'makes a confidential issue public' do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- params: { confidential: false }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['confidential']).to be_falsy
- end
-
- it 'does not update a confidential issue with wrong confidential flag' do
- put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- params: { confidential: 'foo' }
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('confidential is invalid')
- end
- end
- end
-
- describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
- let(:params) do
- {
- title: 'updated title',
- description: 'content here',
- labels: 'label, label2'
- }
- end
-
- it "does not create a new project issue" do
- allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
- allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
-
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
-
- spam_logs = SpamLog.all
- expect(spam_logs.count).to eq(1)
- expect(spam_logs[0].title).to eq('updated title')
- expect(spam_logs[0].description).to eq('content here')
- expect(spam_logs[0].user).to eq(user)
- expect(spam_logs[0].noteable_type).to eq('Issue')
- end
- end
-
- describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do
- context 'support for deprecated assignee_id' do
- it 'removes assignee' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { assignee_id: 0 }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['assignee']).to be_nil
- end
-
- it 'updates an issue with new assignee' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { assignee_id: user2.id }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['assignee']['name']).to eq(user2.name)
- end
- end
-
- it 'removes assignee' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { assignee_ids: [0] }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['assignees']).to be_empty
- end
-
- it 'updates an issue with new assignee' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { assignee_ids: [user2.id] }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['assignees'].first['name']).to eq(user2.name)
- end
-
- context 'single assignee restrictions' do
- it 'updates an issue with several assignees but only one has been applied' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { assignee_ids: [user2.id, guest.id] }
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['assignees'].size).to eq(1)
- end
- end
- end
-
- describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
- let!(:label) { create(:label, title: 'dummy', project: project) }
- let!(:label_link) { create(:label_link, label: label, target: issue) }
-
- it 'does not update labels if not present' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { title: 'updated title' }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['labels']).to eq([label.title])
- end
-
- it 'removes all labels and touches the record' do
- Timecop.travel(1.minute.from_now) do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' }
- end
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['labels']).to eq([])
- expect(json_response['updated_at']).to be > Time.now
- end
-
- it 'updates labels and touches the record' do
- Timecop.travel(1.minute.from_now) do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { labels: 'foo,bar' }
- end
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['labels']).to include 'foo'
- expect(json_response['labels']).to include 'bar'
- expect(json_response['updated_at']).to be > Time.now
- end
-
- it 'allows special label names' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' }
- expect(response.status).to eq(200)
- expect(json_response['labels']).to include 'label:foo'
- expect(json_response['labels']).to include 'label-bar'
- expect(json_response['labels']).to include 'label_bar'
- expect(json_response['labels']).to include 'label/bar'
- expect(json_response['labels']).to include 'label?bar'
- expect(json_response['labels']).to include 'label&bar'
- expect(json_response['labels']).to include '?'
- expect(json_response['labels']).to include '&'
- end
-
- it 'returns 400 if title is too long' do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { title: 'g' * 256 }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['title']).to eq([
- 'is too long (maximum is 255 characters)'
- ])
- end
- end
-
- describe "PUT /projects/:id/issues/:issue_iid to update state and label" do
- it "updates a project issue" do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { labels: 'label2', state_event: "close" }
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response['labels']).to include 'label2'
- expect(json_response['state']).to eq "closed"
- end
-
- it 'reopens a project isssue' do
- put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['state']).to eq 'opened'
- end
-
- context 'when an admin or owner makes the request' do
- it 'accepts the update date to be set' do
- update_time = 2.weeks.ago
- put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- params: { labels: 'label3', state_event: 'close', updated_at: update_time }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['labels']).to include 'label3'
- expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
- end
- end
- end
-
- describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
- it 'creates a new project issue' do
- due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
-
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['due_date']).to eq(due_date)
- end
- end
-
- describe "DELETE /projects/:id/issues/:issue_iid" do
- it "rejects a non member from deleting an issue" do
- delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
- expect(response).to have_gitlab_http_status(403)
- end
-
- it "rejects a developer from deleting an issue" do
- delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
- expect(response).to have_gitlab_http_status(403)
- end
-
- context "when the user is project owner" do
- let(:owner) { create(:user) }
- let(:project) { create(:project, namespace: owner.namespace) }
-
- it "deletes the issue if an admin requests it" do
- delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
-
- expect(response).to have_gitlab_http_status(204)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) }
- end
- end
-
- context 'when issue does not exist' do
- it 'returns 404 when trying to move an issue' do
- delete api("/projects/#{project.id}/issues/123", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- it 'returns 404 when using the issue ID instead of IID' do
- delete api("/projects/#{project.id}/issues/#{issue.id}", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe '/projects/:id/issues/:issue_iid/move' do
- let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace ) }
- let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) }
-
- it 'moves an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- params: { to_project_id: target_project.id }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['project_id']).to eq(target_project.id)
- end
-
- context 'when source and target projects are the same' do
- it 'returns 400 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- params: { to_project_id: project.id }
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']).to eq('Cannot move issue to project it originates from!')
- end
- end
-
- context 'when the user does not have the permission to move issues' do
- it 'returns 400 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- params: { to_project_id: target_project2.id }
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!')
- end
- end
-
- it 'moves the issue to another namespace if I am admin' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
- params: { to_project_id: target_project2.id }
-
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['project_id']).to eq(target_project2.id)
- end
-
- context 'when using the issue ID instead of iid' do
- it 'returns 404 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
- params: { to_project_id: target_project.id }
-
- expect(response).to have_gitlab_http_status(404)
- expect(json_response['message']).to eq('404 Issue Not Found')
- end
- end
-
- context 'when issue does not exist' do
- it 'returns 404 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/123/move", user),
- params: { to_project_id: target_project.id }
-
- expect(response).to have_gitlab_http_status(404)
- expect(json_response['message']).to eq('404 Issue Not Found')
- end
- end
-
- context 'when source project does not exist' do
- it 'returns 404 when trying to move an issue' do
- post api("/projects/0/issues/#{issue.iid}/move", user),
- params: { to_project_id: target_project.id }
-
- expect(response).to have_gitlab_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
- end
-
- context 'when target project does not exist' do
- it 'returns 404 when trying to move an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- params: { to_project_id: 0 }
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
- describe 'POST :id/issues/:issue_iid/subscribe' do
- it 'subscribes to an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
-
- 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}/issues/#{issue.iid}/subscribe", user)
-
- expect(response).to have_gitlab_http_status(304)
- end
-
- it 'returns 404 if the issue is not found' do
- post api("/projects/#{project.id}/issues/123/subscribe", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 404 if the issue ID is used instead of the iid' do
- post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe 'POST :id/issues/:issue_id/unsubscribe' do
- it 'unsubscribes from an issue' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
-
- 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}/issues/#{issue.iid}/unsubscribe", user2)
-
- expect(response).to have_gitlab_http_status(304)
- end
-
- it 'returns 404 if the issue is not found' do
- post api("/projects/#{project.id}/issues/123/unsubscribe", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 404 if using the issue ID instead of iid' do
- post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe 'time tracking endpoints' do
- let(:issuable) { issue }
-
- include_examples 'time tracking endpoints', 'issue'
- end
-
- describe 'GET :id/issues/:issue_iid/closed_by' do
- let(:merge_request) do
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: "closes #{issue.to_reference}")
- end
-
- before do
- create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
- end
-
- context 'when unauthenticated' do
- it 'return public project issues' do
- get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
-
- expect_paginated_array_response(merge_request.id)
- end
- end
-
- it 'returns merge requests that will close issue on merge' do
- get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
-
- expect_paginated_array_response(merge_request.id)
- end
-
- context 'when no merge requests will close issue' do
- it 'returns empty array' do
- get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user)
-
- expect_paginated_array_response([])
- end
- end
-
- it "returns 404 when issue doesn't exists" do
- get api("/projects/#{project.id}/issues/9999/closed_by", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe 'GET :id/issues/:issue_iid/related_merge_requests' do
- def get_related_merge_requests(project_id, issue_iid, user = nil)
- get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
- end
-
- def create_referencing_mr(user, project, issue)
- attributes = {
- author: user,
- source_project: project,
- target_project: project,
- source_branch: "master",
- target_branch: "test",
- description: "See #{issue.to_reference}"
- }
- create(:merge_request, attributes).tap do |merge_request|
- create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
- end
- end
-
- let!(:related_mr) { create_referencing_mr(user, project, issue) }
-
- context 'when unauthenticated' do
- it 'return list of referenced merge requests from issue' do
- get_related_merge_requests(project.id, issue.iid)
-
- expect_paginated_array_response(related_mr.id)
- end
-
- it 'renders 404 if project is not visible' do
- private_project = create(:project, :private)
- private_issue = create(:issue, project: private_project)
- create_referencing_mr(user, private_project, private_issue)
-
- get_related_merge_requests(private_project.id, private_issue.iid)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- it 'returns merge requests that mentioned a issue' do
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: "Some description")
-
- get_related_merge_requests(project.id, issue.iid, user)
-
- expect_paginated_array_response(related_mr.id)
- end
-
- it 'returns merge requests cross-project wide' do
- project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
- merge_request = create_referencing_mr(user, project2, issue)
-
- get_related_merge_requests(project.id, issue.iid, user)
-
- expect_paginated_array_response([related_mr.id, merge_request.id])
- end
-
- it 'does not generate references to projects with no access' do
- private_project = create(:project, :private)
- create_referencing_mr(private_project.creator, private_project, issue)
-
- get_related_merge_requests(project.id, issue.iid, user)
-
- expect_paginated_array_response(related_mr.id)
- end
-
- context 'no merge request mentioned a issue' do
- it 'returns empty array' do
- get_related_merge_requests(project.id, closed_issue.iid, user)
-
- expect_paginated_array_response([])
- end
- end
-
- it "returns 404 when issue doesn't exists" do
- get_related_merge_requests(project.id, 999999, user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do
- let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
-
- context 'when unauthenticated' do
- it "returns unauthorized" do
- get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail")
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
-
- it 'exposes known attributes' do
- get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
- expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
- expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
- end
-
- it "returns unauthorized for non-admin users" do
- get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user)
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- describe 'GET projects/:id/issues/:issue_iid/participants' do
- it_behaves_like 'issuable participants endpoint' do
- let(:entity) { issue }
- end
-
- it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 3defe8bbf51..89ee6f896f9 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -286,6 +286,7 @@ describe API::Jobs do
expect(json_response['ref']).to eq(job.ref)
expect(json_response['tag']).to eq(job.tag)
expect(json_response['coverage']).to eq(job.coverage)
+ expect(json_response['allow_failure']).to eq(job.allow_failure)
expect(Time.parse(json_response['created_at'])).to be_like_time(job.created_at)
expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at)
expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at)
@@ -321,6 +322,49 @@ describe API::Jobs do
end
end
+ describe 'DELETE /projects/:id/jobs/:job_id/artifacts' do
+ let!(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
+
+ before do
+ delete api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ it 'does not delete artifacts' do
+ expect(job.job_artifacts.size).to eq 2
+ end
+
+ it 'returns status 401 (unauthorized)' do
+ expect(response).to have_http_status :unauthorized
+ end
+ end
+
+ context 'with developer' do
+ it 'does not delete artifacts' do
+ expect(job.job_artifacts.size).to eq 2
+ end
+
+ it 'returns status 403 (forbidden)' do
+ expect(response).to have_http_status :forbidden
+ end
+ end
+
+ context 'with authorized user' do
+ let(:maintainer) { create(:project_member, :maintainer, project: project).user }
+ let!(:api_user) { maintainer }
+
+ it 'deletes artifacts' do
+ expect(job.job_artifacts.size).to eq 0
+ end
+
+ it 'returns status 204 (no content)' do
+ expect(response).to have_http_status :no_content
+ end
+ end
+ end
+
describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do
context 'when job has artifacts' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
@@ -869,8 +913,8 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(201)
expect(job.job_artifacts.count).to eq(0)
expect(job.trace.exist?).to be_falsy
- expect(job.artifacts_file.exists?).to be_falsy
- expect(job.artifacts_metadata.exists?).to be_falsy
+ expect(job.artifacts_file.present?).to be_falsy
+ expect(job.artifacts_metadata.present?).to be_falsy
expect(job.has_job_artifacts?).to be_falsy
end
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 3c4719964b6..f37d84fddef 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -16,7 +16,7 @@ describe API::Keys do
context 'when authenticated' do
it 'returns 404 for non-existing key' do
- get api('/keys/999999', admin)
+ get api('/keys/0', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 79edbb301f2..55f38079b1f 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -132,6 +132,19 @@ describe API::Members do
expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id, project_user.id, linked_group_user.id]
end
+ it 'returns only one member for each user without returning duplicated members' do
+ linked_group.add_developer(developer)
+
+ get api("/projects/#{project.id}/members/all", developer)
+
+ 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 { |u| u['id'] }).to eq [developer.id, maintainer.id, nested_user.id, project_user.id, linked_group_user.id]
+ expect(json_response.map { |u| u['access_level'] }).to eq [Gitlab::Access::DEVELOPER, Gitlab::Access::OWNER, Gitlab::Access::DEVELOPER,
+ Gitlab::Access::DEVELOPER, Gitlab::Access::DEVELOPER]
+ end
+
it 'finds all group members including inherited members' do
get api("/groups/#{nested_group.id}/members/all", developer)
@@ -236,7 +249,7 @@ describe API::Members do
params: { user_id: stranger.id, access_level: Member::REPORTER }
expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['access_level']).to eq(["should be higher than Developer inherited membership from group #{parent.name}"])
+ expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"])
end
it 'creates the member if group level is lower', :nested_groups do
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index 6530dc956cb..8a67d98fc4c 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -30,7 +30,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/versions", user)
+ get api("/projects/#{project.id}/merge_requests/0/versions", user)
expect(response).to have_gitlab_http_status(404)
end
end
@@ -53,7 +53,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
end
it 'returns a 404 when merge_request version_id is not found' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/0", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index b4cd3130dc5..4cb4fcc890d 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -4,30 +4,408 @@ describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
- let(:user) { create(:user) }
- let(:admin) { create(:user, :admin) }
- let(:non_member) { create(:user) }
- let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+ set(:admin) { create(:user, :admin) }
+ let(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
- let(:pipeline) { create(:ci_empty_pipeline) }
- let(:milestone1) { create(:milestone, title: '0.9', project: project) }
- let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
- let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
- let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
- let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) }
+ let(:milestone1) { create(:milestone, title: '0.9', project: project) }
+ let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Test", created_at: base_time) }
+ let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
+ let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignees: [user], source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
+ let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
- let!(:label) do
- create(:label, title: 'label', color: '#FFAABB', project: project)
- end
- let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
- let!(:label_link) { create(:label_link, label: label, target: merge_request) }
- let!(:label_link2) { create(:label_link, label: label2, target: merge_request) }
- let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
- let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
+ let(:label) { create(:label, title: 'label', color: '#FFAABB', project: project) }
+ let(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
before do
project.add_reporter(user)
+ project.add_reporter(user2)
+
+ stub_licensed_features(multiple_merge_request_assignees: false)
+ end
+
+ shared_context 'with labels' do
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request)
+ end
+ end
+
+ shared_examples 'merge requests list' do
+ context 'when unauthenticated' do
+ it 'returns merge requests for public projects' do
+ get api(endpoint_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'when authenticated' do
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api(endpoint_path, user)
+ end
+
+ create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ merge_request = create(:merge_request, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ merge_request.metrics.update!(merged_by: user,
+ latest_closed_by: user,
+ latest_closed_at: 1.hour.ago,
+ merged_at: 2.hours.ago)
+
+ expect do
+ get api(endpoint_path, user)
+ end.not_to exceed_query_limit(control)
+ end
+
+ context 'with labels' do
+ include_context 'with labels'
+
+ it 'returns an array of all merge_requests' do
+ get api(endpoint_path, user)
+
+ 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(4)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
+ expect(json_response.last['merge_commit_sha']).to be_nil
+ expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.last['downvotes']).to eq(0)
+ expect(json_response.last['upvotes']).to eq(0)
+ expect(json_response.last['labels']).to eq([label2.title, label.title])
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
+ expect(json_response.first['merge_commit_sha']).not_to be_nil
+ expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
+ end
+ end
+
+ it 'returns an array of all merge_requests using simple mode' do
+ path = endpoint_path + '?view=simple'
+
+ get api(path, user)
+
+ 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
+ expect(json_response.length).to eq(4)
+ expect(json_response.last['iid']).to eq(merge_request.iid)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.first['iid']).to eq(merge_request_merged.iid)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first).to have_key('web_url')
+ end
+
+ it 'returns an array of all merge_requests' do
+ path = endpoint_path + '?state'
+
+ get api(path, user)
+
+ 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(4)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of open merge_requests' do
+ path = endpoint_path + '?state=opened'
+
+ get api(path, user)
+
+ 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)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of closed merge_requests' do
+ path = endpoint_path + '?state=closed'
+
+ get api(path, user)
+
+ 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)
+ expect(json_response.first['title']).to eq(merge_request_closed.title)
+ end
+
+ it 'returns an array of merged merge_requests' do
+ path = endpoint_path + '?state=merged'
+
+ get api(path, user)
+
+ 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)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ end
+
+ it 'matches V4 response schema' do
+ get api(endpoint_path, user)
+
+ 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(endpoint_path, user), params: { milestone: '1.0.0' }
+
+ 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 empty array if milestone does not exist' do
+ get api(endpoint_path, user), params: { milestone: 'foo' }
+
+ 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 merge requests in given milestone' do
+ get api(endpoint_path, user), params: { milestone: '0.9' }
+
+ closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id }
+ expect(closed_issues.length).to eq(1)
+ expect(closed_issues.first['title']).to eq merge_request_closed.title
+ end
+
+ it 'returns an array of merge requests matching state in milestone' do
+ get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
+
+ 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)
+ end
+
+ context 'with labels' do
+ include_context 'with labels'
+
+ it 'returns an array of labeled merge requests' do
+ path = endpoint_path + "?labels=#{label.title}"
+
+ get api(path, 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['labels']).to eq([label2.title, label.title])
+ end
+
+ it 'returns an array of labeled merge requests where all labels match' do
+ path = endpoint_path + "?labels=#{label.title},foo,bar"
+
+ get api(path, user)
+
+ 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 empty array if no merge request matches labels' do
+ path = endpoint_path + '?labels=foo,bar'
+
+ get api(path, user)
+
+ 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 where all labels match' do
+ path = endpoint_path + "?labels[]=#{label.title}&labels[]=#{label2.title}"
+
+ get api(path, 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['labels']).to eq([label2.title, label.title])
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] }
+
+ expect_paginated_array_response
+ 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])
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] }
+
+ expect_paginated_array_response
+ 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])
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
+
+ expect_paginated_array_response
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests without a label when filtering by no label' do
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect_paginated_array_response
+ expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
+ end
+ 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)
+
+ path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged"
+
+ get api(path, 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
+ @mr_earlier = mr_with_earlier_created_and_updated_at_time
+ end
+
+ it 'returns an array of merge_requests in ascending order' do
+ path = endpoint_path + '?sort=asc'
+
+ get api(path, user)
+
+ 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(4)
+ 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
+ path = endpoint_path + '?sort=desc'
+
+ get api(path, user)
+
+ 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(4)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ context '2 merge requests with equal created_at' do
+ let!(:closed_mr2) do
+ create :merge_request,
+ state: 'closed',
+ milestone: milestone1,
+ author: user,
+ assignees: [user],
+ source_project: project,
+ target_project: project,
+ title: "Test",
+ created_at: @mr_earlier.created_at
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).to include(closed_mr2.id)
+ expect(response_ids).not_to include(@mr_earlier.id)
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4&page=2", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).not_to include(closed_mr2.id)
+ expect(response_ids).to include(@mr_earlier.id)
+ end
+ end
+
+ it 'returns an array of merge_requests ordered by updated_at' do
+ path = endpoint_path + '?order_by=updated_at'
+
+ get api(path, user)
+
+ 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(4)
+ 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
+ path = endpoint_path + '?order_by=created_at&sort=asc'
+
+ get api(path, user)
+
+ 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(4)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
+ end
+ end
+
+ context 'source_branch param' do
+ it 'returns merge requests with the given source branch' do
+ get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ end
+ end
+
+ context 'target_branch param' do
+ it 'returns merge requests with the given target branch' do
+ get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ end
+ end
+ end
end
describe 'route shadowing' do
@@ -77,7 +455,7 @@ describe API::MergeRequests do
context 'when authenticated' do
let!(:project2) { create(:project, :public, namespace: user.namespace) }
- let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) }
+ let!(:merge_request2) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project2, target_project: project2) }
let(:user2) { create(:user) }
it 'returns an array of all merge requests except unauthorized ones' do
@@ -120,7 +498,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests created by current user if no scope is given' do
- merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user2)
@@ -128,7 +506,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests authored by the given user' do
- merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user), params: { author_id: user2.id, scope: :all }
@@ -136,7 +514,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests assigned to the given user' do
- merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all }
@@ -161,7 +539,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests assigned to me' do
- merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user2), params: { scope: 'assigned_to_me' }
@@ -169,7 +547,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests assigned to me (kebab-case)' do
- merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user2), params: { scope: 'assigned-to-me' }
@@ -177,7 +555,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests created by me' do
- merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user2), params: { scope: 'created_by_me' }
@@ -185,7 +563,7 @@ describe API::MergeRequests do
end
it 'returns an array of merge requests created by me (kebab-case)' do
- merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user2, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch')
get api('/merge_requests', user2), params: { scope: 'created-by-me' }
@@ -193,7 +571,7 @@ describe API::MergeRequests do
end
it 'returns merge requests reacted by the authenticated user by the given emoji' do
- merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
+ merge_request3 = create(:merge_request, :simple, author: user, assignees: [user], source_project: project2, target_project: project2, source_branch: 'other-branch')
award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star')
get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' }
@@ -320,6 +698,18 @@ describe API::MergeRequests do
expect(json_response.first['title']).to eq merge_request_closed.title
expect(json_response.first['id']).to eq merge_request_closed.id
end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.count
+
+ create(:merge_request, author: user, assignees: [user], source_project: project, target_project: project, created_at: base_time)
+
+ expect do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.not_to exceed_query_limit(control)
+ end
end
describe "GET /groups/:id/merge_requests" do
@@ -343,7 +733,18 @@ describe API::MergeRequests do
end
describe "GET /projects/:id/merge_requests/:merge_request_iid" do
+ it 'matches json schema' do
+ merge_request = create(:merge_request, :with_test_reports, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Test", created_at: base_time)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/merge_request')
+ end
+
it 'exposes known attributes' do
+ create(:award_emoji, :downvote, awardable: merge_request)
+ create(:award_emoji, :upvote, awardable: merge_request)
+
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_gitlab_http_status(200)
@@ -393,6 +794,8 @@ describe API::MergeRequests do
end
context 'merge_request_metrics' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
before do
merge_request.metrics.update!(merged_by: user,
latest_closed_by: user,
@@ -441,7 +844,7 @@ describe API::MergeRequests do
end
it "returns a 404 error if merge_request_iid not found" do
- get api("/projects/#{project.id}/merge_requests/999", user)
+ get api("/projects/#{project.id}/merge_requests/0", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -452,7 +855,7 @@ describe API::MergeRequests do
end
context 'Work in Progress' do
- let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }
+ let!(:merge_request_wip) { create(:merge_request, author: user, assignees: [user], source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }
it "returns merge request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
@@ -468,7 +871,7 @@ describe API::MergeRequests do
merge_request_overflow = create(:merge_request, :simple,
author: user,
- assignee: user,
+ assignees: [user],
source_project: project,
source_branch: 'expand-collapse-files',
target_project: project,
@@ -531,7 +934,7 @@ describe API::MergeRequests do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/0/commits", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -551,7 +954,7 @@ describe API::MergeRequests do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/0/changes", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -605,26 +1008,180 @@ describe API::MergeRequests do
end
end
- describe "POST /projects/:id/merge_requests" do
- context 'between branches projects' do
- it "returns merge_request" do
- post api("/projects/#{project.id}/merge_requests", user),
- params: {
- title: 'Test merge_request',
- source_branch: 'feature_conflict',
- target_branch: 'master',
- author: user,
- labels: 'label, label2',
- milestone_id: milestone.id,
- squash: true
- }
+ describe 'POST /projects/:id/merge_requests' do
+ context 'support for deprecated assignee_id' do
+ let(:params) do
+ {
+ title: 'Test merge request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author_id: user.id,
+ assignee_id: user2.id
+ }
+ end
+
+ it 'creates a new merge request' do
+ post api("/projects/#{project.id}/merge_requests", user), params: params
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)
- expect(json_response['squash']).to be_truthy
- expect(json_response['force_remove_source_branch']).to be_falsy
+ expect(json_response['title']).to eq('Test merge request')
+ expect(json_response['assignee']['name']).to eq(user2.name)
+ expect(json_response['assignees'].first['name']).to eq(user2.name)
+ end
+
+ it 'creates a new merge request when assignee_id is empty' do
+ params[:assignee_id] = ''
+
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('Test merge request')
+ expect(json_response['assignee']).to be_nil
+ end
+
+ it 'filters assignee_id of unauthorized user' do
+ private_project = create(:project, :private, :repository)
+ another_user = create(:user)
+ private_project.add_maintainer(user)
+ params[:assignee_id] = another_user.id
+
+ post api("/projects/#{private_project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['assignee']).to be_nil
+ end
+ end
+
+ context 'single assignee restrictions' do
+ let(:params) do
+ {
+ title: 'Test merge request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author_id: user.id,
+ assignee_ids: [user.id, user2.id]
+ }
+ end
+
+ it 'creates a new project merge request with no more than one assignee' do
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('Test merge request')
+ expect(json_response['assignees'].count).to eq(1)
+ expect(json_response['assignees'].first['name']).to eq(user.name)
+ expect(json_response.dig('assignee', 'name')).to eq(user.name)
+ end
+ end
+
+ context 'between branches projects' do
+ context 'different labels' do
+ let(:params) do
+ {
+ title: 'Test merge_request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author_id: user.id,
+ milestone_id: milestone.id,
+ squash: true
+ }
+ end
+
+ shared_examples_for 'creates merge request with labels' do
+ it 'returns merge_request' do
+ params[:labels] = labels
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ 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)
+ expect(json_response['squash']).to be_truthy
+ expect(json_response['force_remove_source_branch']).to be_falsy
+ end
+ end
+
+ it_behaves_like 'creates merge request with labels' do
+ let(:labels) { 'label, label2' }
+ end
+
+ it_behaves_like 'creates merge request with labels' do
+ let(:labels) { %w(label label2) }
+ end
+
+ it_behaves_like 'creates merge request with labels' do
+ let(:labels) { %w(label label2) }
+ end
+
+ it 'creates merge request with special label names' do
+ params[:labels] = 'label, label?, label&foo, ?, &'
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ 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'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'creates merge request with special label names as array' do
+ params[:labels] = ['label', 'label?', 'label&foo, ?, &', '1, 2', 3, 4]
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ 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'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ expect(json_response['labels']).to include '1'
+ expect(json_response['labels']).to include '2'
+ expect(json_response['labels']).to include '3'
+ expect(json_response['labels']).to include '4'
+ end
+
+ it 'empty label param does not add any labels' do
+ params[:labels] = ''
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['labels']).to eq([])
+ end
+
+ it 'empty label param as array does not add any labels, but only explicitly as json' do
+ params[:labels] = []
+ post api("/projects/#{project.id}/merge_requests", user),
+ params: params.to_json,
+ headers: { 'Content-Type': 'application/json' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['labels']).to eq([])
+ end
+
+ xit 'empty label param as array, does not add any labels' do
+ params[:labels] = []
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['labels']).to eq([])
+ end
+
+ it 'array with one empty string element does not add labels' do
+ params[:labels] = ['']
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['labels']).to eq([])
+ end
+
+ it 'array with multiple empty string elements, does not add labels' do
+ params[:labels] = ['', '', '']
+ post api("/projects/#{project.id}/merge_requests", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['labels']).to eq([])
+ end
end
it "returns 422 when source_branch equals target_branch" do
@@ -651,23 +1208,6 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- it 'allows special label names' do
- post api("/projects/#{project.id}/merge_requests", user),
- params: {
- title: 'Test merge_request',
- source_branch: 'markdown',
- target_branch: 'master',
- author: user,
- labels: 'label, label?, label&foo, ?, &'
- }
- 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'
- expect(json_response['labels']).to include '?'
- expect(json_response['labels']).to include '&'
- end
-
context 'with existing MR' do
before do
post api("/projects/#{project.id}/merge_requests", user),
@@ -890,7 +1430,12 @@ describe API::MergeRequests do
end
it 'returns 405 if the build failed for a merge request that requires success' do
- allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false)
+ project.update!(only_allow_merge_if_pipeline_succeeds: true)
+
+ create(:ci_pipeline,
+ :failed,
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
@@ -928,7 +1473,7 @@ describe API::MergeRequests do
end
it "enables merge when pipeline succeeds if the pipeline is active" do
- allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
+ allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true)
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true }
@@ -939,7 +1484,7 @@ describe API::MergeRequests do
end
it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do
- allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
+ allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true)
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
@@ -1001,7 +1546,95 @@ describe API::MergeRequests do
end
end
+ describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do
+ before do
+ merge_request.mark_as_unchecked!
+ end
+
+ let(:merge_request_iid) { merge_request.iid }
+
+ let(:url) do
+ "/projects/#{project.id}/merge_requests/#{merge_request_iid}/merge_ref"
+ end
+
+ it 'returns the generated ID from the merge service in case of success' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha)
+ end
+
+ it "returns 400 if branch can't be merged" do
+ merge_request.update!(merge_status: 'cannot_be_merged')
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Merge request is not mergeable')
+ end
+
+ context 'when user has no access to the MR' do
+ let(:project) { create(:project, :private) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ it 'returns 404' do
+ project.add_guest(user)
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
+ context 'when invalid merge request IID' do
+ let(:merge_request_iid) { '12345' }
+
+ it 'returns 404' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when merge request ID is used instead IID' do
+ let(:merge_request_iid) { merge_request.id }
+
+ it 'returns 404' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
+ context 'updates force_remove_source_branch properly' do
+ it 'sets to false' do
+ merge_request.update(merge_params: { 'force_remove_source_branch' => true } )
+
+ expect(merge_request.force_remove_source_branch?).to be_truthy
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: "close", remove_source_branch: false }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['state']).to eq('closed')
+ expect(json_response['force_remove_source_branch']).to be_falsey
+ end
+
+ it 'sets to true' do
+ merge_request.update(merge_params: { 'force_remove_source_branch' => false } )
+
+ expect(merge_request.force_remove_source_branch?).to be_falsey
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: "close", remove_source_branch: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['state']).to eq('closed')
+ expect(json_response['force_remove_source_branch']).to be_truthy
+ end
+ end
+
context "to close a MR" do
it "returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: "close" }
@@ -1049,19 +1682,110 @@ describe API::MergeRequests do
expect(json_response['force_remove_source_branch']).to be_truthy
end
- it 'allows special label names' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
- params: {
- title: 'new issue',
- labels: 'label, label?, label&foo, ?, &'
- }
+ it 'filters assignee_id of unauthorized user' do
+ private_project = create(:project, :private, :repository)
+ mr = create(:merge_request, source_project: private_project, target_project: private_project)
+ another_user = create(:user)
+ private_project.add_maintainer(user)
+ params = { assignee_id: another_user.id }
+
+ put api("/projects/#{private_project.id}/merge_requests/#{mr.iid}", user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['assignee']).to be_nil
+ end
+
+ context 'when updating labels' do
+ it 'allows special label names' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: 'label, label?, label&foo, ?, &'
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ end
+
+ it 'also accepts labels as an array' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: ['label', 'label?', 'label&foo, ?, &', '1, 2', 3, 4]
+ }
- expect(response.status).to eq(200)
- expect(json_response['labels']).to include 'label'
- expect(json_response['labels']).to include 'label?'
- expect(json_response['labels']).to include 'label&foo'
- expect(json_response['labels']).to include '?'
- expect(json_response['labels']).to include '&'
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label'
+ expect(json_response['labels']).to include 'label?'
+ expect(json_response['labels']).to include 'label&foo'
+ expect(json_response['labels']).to include '?'
+ expect(json_response['labels']).to include '&'
+ expect(json_response['labels']).to include '1'
+ expect(json_response['labels']).to include '2'
+ expect(json_response['labels']).to include '3'
+ expect(json_response['labels']).to include '4'
+ end
+
+ it 'empty label param removes labels' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: ''
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq []
+ end
+
+ it 'label param as empty array, but only explicitly as json, removes labels' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: []
+ }.to_json,
+ headers: { 'Content-Type' => 'application/json' }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq []
+ end
+
+ xit 'empty label as array, removes labels' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: []
+ }
+
+ expect(response.status).to eq(200)
+ # fails, as grape ommits for some reason empty array as optional param value, so nothing it passed along
+ expect(json_response['labels']).to eq []
+ end
+
+ it 'array with one empty string element removes labels' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: ['']
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq []
+ end
+
+ it 'array with multiple empty string elements, removes labels' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
+ params: {
+ title: 'new issue',
+ labels: ['', '', '']
+ }
+
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq []
+ end
end
it 'does not update state when title is empty' do
@@ -1125,7 +1849,7 @@ describe API::MergeRequests do
issue = create(:issue, project: jira_project)
description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}"
merge_request = create(:merge_request,
- :simple, author: user, assignee: user, source_project: jira_project, description: description)
+ :simple, author: user, assignees: [user], source_project: jira_project, description: description)
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
@@ -1239,7 +1963,7 @@ describe API::MergeRequests do
describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
before do
- ::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request)
+ ::AutoMergeService.new(merge_request.target_project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
it 'removes the merge_when_pipeline_succeeds status' do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 145356c4df5..2e376109b42 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -149,7 +149,7 @@ describe API::Namespaces do
context "when namespace doesn't exist" do
it 'returns not-found' do
- get api('/namespaces/9999', request_actor)
+ get api('/namespaces/0', request_actor)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 870ef34437f..072bd02f2ac 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -91,6 +91,7 @@ describe API::PipelineSchedules do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
before do
+ pipeline_schedule.variables << build(:ci_pipeline_schedule_variable)
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
@@ -331,13 +332,14 @@ describe API::PipelineSchedules do
it 'creates pipeline_schedule_variable' do
expect do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
- params: params
+ params: params.merge(variable_type: 'file')
end.to change { pipeline_schedule.variables.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('pipeline_schedule_variable')
expect(json_response['key']).to eq(params[:key])
expect(json_response['value']).to eq(params[:value])
+ expect(json_response['variable_type']).to eq('file')
end
end
@@ -389,11 +391,12 @@ describe API::PipelineSchedules do
context 'authenticated user with valid permissions' do
it 'updates pipeline_schedule_variable' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
- params: { value: 'updated_value' }
+ params: { value: 'updated_value', variable_type: 'file' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule_variable')
expect(json_response['value']).to eq('updated_value')
+ expect(json_response['variable_type']).to eq('file')
end
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 52599db9a9e..35b3dd219f7 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Pipelines do
@@ -292,6 +294,7 @@ describe API::Pipelines do
expect(variable.key).to eq(expected_variable['key'])
expect(variable.value).to eq(expected_variable['value'])
+ expect(variable.variable_type).to eq(expected_variable['variable_type'])
end
end
@@ -312,7 +315,7 @@ describe API::Pipelines do
end
context 'variables given' do
- let(:variables) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
+ let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
it 'creates and returns a new pipeline using the given variables' do
expect do
@@ -328,7 +331,7 @@ describe API::Pipelines do
end
describe 'using variables conditions' do
- let(:variables) { [{ 'key' => 'STAGING', 'value' => 'true' }] }
+ let(:variables) { [{ 'variable_type' => 'env_var', 'key' => 'STAGING', 'value' => 'true' }] }
before do
config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } })
@@ -399,6 +402,13 @@ describe API::Pipelines do
describe 'GET /projects/:id/pipelines/:pipeline_id' do
context 'authorized user' do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/pipeline/detail')
+ end
+
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
@@ -428,7 +438,7 @@ describe API::Pipelines do
end
context 'unauthorized user' do
- it 'should not return a project pipeline' do
+ it 'does not return a project pipeline' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
expect(response).to have_gitlab_http_status(404)
@@ -438,6 +448,72 @@ describe API::Pipelines do
end
end
+ describe 'GET /projects/:id/pipelines/:pipeline_id/variables' do
+ subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", api_user) }
+
+ let(:api_user) { user }
+
+ context 'user is a mantainer' do
+ it 'returns pipeline variables empty' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_empty
+ end
+
+ context 'with variables' do
+ let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
+
+ it 'returns pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
+ end
+ end
+ end
+
+ context 'user is a developer' do
+ let(:pipeline_owner_user) { create(:user) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: pipeline_owner_user) }
+
+ before do
+ project.add_developer(api_user)
+ end
+
+ context 'pipeline created by the developer user' do
+ let(:api_user) { pipeline_owner_user }
+ let!(:variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'foo', value: 'bar') }
+
+ it 'returns pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to contain_exactly({ "variable_type" => "env_var", "key" => "foo", "value" => "bar" })
+ end
+ end
+
+ context 'pipeline created is not created by the developer user' do
+ let(:api_user) { create(:user) }
+
+ it 'does not return pipeline variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'user is not a project member' do
+ it 'does not return pipeline variables' do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/variables", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq '404 Project Not Found'
+ end
+ end
+ end
+
describe 'DELETE /projects/:id/pipelines/:pipeline_id' do
context 'authorized user' do
let(:owner) { project.owner }
@@ -474,7 +550,7 @@ describe API::Pipelines do
context 'unauthorized user' do
context 'when user is not member' do
- it 'should return a 404' do
+ it 'returns a 404' do
delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member)
expect(response).to have_gitlab_http_status(404)
@@ -489,7 +565,7 @@ describe API::Pipelines do
project.add_developer(developer)
end
- it 'should return a 403' do
+ it 'returns a 403' do
delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer)
expect(response).to have_gitlab_http_status(403)
@@ -519,7 +595,7 @@ describe API::Pipelines do
end
context 'unauthorized user' do
- it 'should not return a project pipeline' do
+ it 'does not return a project pipeline' do
post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member)
expect(response).to have_gitlab_http_status(404)
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index 9bab1f95150..fc0381159dd 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -22,7 +22,7 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'should respond with 404' do
+ it 'responds with 404' do
get api("/projects/#{project.id}/clusters", non_member)
expect(response).to have_gitlab_http_status(404)
@@ -34,15 +34,15 @@ describe API::ProjectClusters do
get api("/projects/#{project.id}/clusters", current_user)
end
- it 'should respond with 200' do
+ it 'responds with 200' do
expect(response).to have_gitlab_http_status(200)
end
- it 'should include pagination headers' do
+ it 'includes pagination headers' do
expect(response).to include_pagination_headers
end
- it 'should only include authorized clusters' do
+ it 'onlies include authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(cluster_ids).to match_array(clusters.pluck(:id))
@@ -60,14 +60,14 @@ describe API::ProjectClusters do
end
let(:cluster) do
- create(:cluster, :project, :provided_by_gcp,
+ create(:cluster, :project, :provided_by_gcp, :with_domain,
platform_kubernetes: platform_kubernetes,
user: current_user,
projects: [project])
end
context 'non-authorized user' do
- it 'should respond with 404' do
+ it 'responds with 404' do
get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member)
expect(response).to have_gitlab_http_status(404)
@@ -88,6 +88,7 @@ describe API::ProjectClusters do
expect(json_response['platform_type']).to eq('kubernetes')
expect(json_response['environment_scope']).to eq('*')
expect(json_response['cluster_type']).to eq('project_type')
+ expect(json_response['domain']).to eq('example.com')
end
it 'returns project information' do
@@ -131,7 +132,7 @@ describe API::ProjectClusters do
projects: [project])
end
- it 'should not include GCP provider info' do
+ it 'does not include GCP provider info' do
expect(json_response['provider_gcp']).not_to be_present
end
end
@@ -187,12 +188,14 @@ describe API::ProjectClusters do
let(:cluster_params) do
{
name: 'test-cluster',
+ domain: 'domain.example.com',
+ managed: false,
platform_kubernetes_attributes: platform_kubernetes_attributes
}
end
context 'non-authorized user' do
- it 'should respond with 404' do
+ it 'responds with 404' do
post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params
expect(response).to have_gitlab_http_status(404)
@@ -205,11 +208,11 @@ describe API::ProjectClusters do
end
context 'with valid params' do
- it 'should respond with 201' do
+ it 'responds with 201' do
expect(response).to have_gitlab_http_status(201)
end
- it 'should create a new Cluster::Cluster' do
+ it 'creates a new Cluster::Cluster' do
cluster_result = Clusters::Cluster.find(json_response["id"])
platform_kubernetes = cluster_result.platform
@@ -217,6 +220,8 @@ describe API::ProjectClusters do
expect(cluster_result).to be_kubernetes
expect(cluster_result.project).to eq(project)
expect(cluster_result.name).to eq('test-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.managed).to be_falsy
expect(platform_kubernetes.rbac?).to be_truthy
expect(platform_kubernetes.api_url).to eq(api_url)
expect(platform_kubernetes.namespace).to eq(namespace)
@@ -243,7 +248,7 @@ describe API::ProjectClusters do
context 'when user sets authorization type as ABAC' do
let(:authorization_type) { 'abac' }
- it 'should create an ABAC cluster' do
+ it 'creates an ABAC cluster' do
cluster_result = Clusters::Cluster.find(json_response['id'])
expect(cluster_result.platform.abac?).to be_truthy
@@ -253,15 +258,15 @@ describe API::ProjectClusters do
context 'with invalid params' do
let(:namespace) { 'invalid_namespace' }
- it 'should respond with 400' do
+ it 'responds with 400' do
expect(response).to have_gitlab_http_status(400)
end
- it 'should not create a new Clusters::Cluster' do
+ it 'does not create a new Clusters::Cluster' do
expect(project.reload.clusters).to be_empty
end
- it 'should return validation errors' do
+ it 'returns validation errors' do
expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present
end
end
@@ -275,11 +280,11 @@ describe API::ProjectClusters do
post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
- it 'should respond with 403' do
+ it 'responds with 403' do
expect(response).to have_gitlab_http_status(403)
end
- it 'should return an appropriate message' do
+ it 'returns an appropriate message' do
expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
end
end
@@ -294,6 +299,7 @@ describe API::ProjectClusters do
let(:update_params) do
{
+ domain: 'new-domain.com',
platform_kubernetes_attributes: platform_kubernetes_attributes
}
end
@@ -310,7 +316,7 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'should respond with 404' do
+ it 'responds with 404' do
put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params
expect(response).to have_gitlab_http_status(404)
@@ -325,29 +331,30 @@ describe API::ProjectClusters do
end
context 'with valid params' do
- it 'should respond with 200' do
+ it 'responds with 200' do
expect(response).to have_gitlab_http_status(200)
end
- it 'should update cluster attributes' do
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
- expect(cluster.kubernetes_namespace.namespace).to eq('new-namespace')
end
end
context 'with invalid params' do
let(:namespace) { 'invalid_namespace' }
- it 'should respond with 400' do
+ it 'responds with 400' do
expect(response).to have_gitlab_http_status(400)
end
- it 'should not update cluster attributes' do
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).not_to eq('new_domain.com')
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
- expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace')
+ expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace')
end
- it 'should return validation errors' do
+ it 'returns validation errors' do
expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters')
end
end
@@ -361,11 +368,11 @@ describe API::ProjectClusters do
}
end
- it 'should respond with 400' do
+ it 'responds with 400' do
expect(response).to have_gitlab_http_status(400)
end
- it 'should return validation error' do
+ it 'returns validation error' do
expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster')
end
end
@@ -373,7 +380,7 @@ describe API::ProjectClusters do
context 'when user tries to change namespace' do
let(:namespace) { 'new-namespace' }
- it 'should respond with 200' do
+ it 'responds with 200' do
expect(response).to have_gitlab_http_status(200)
end
end
@@ -402,11 +409,11 @@ describe API::ProjectClusters do
}
end
- it 'should respond with 200' do
+ it 'responds with 200' do
expect(response).to have_gitlab_http_status(200)
end
- it 'should update platform kubernetes attributes' do
+ it 'updates platform kubernetes attributes' do
platform_kubernetes = cluster.platform_kubernetes
expect(cluster.name).to eq('new-name')
@@ -419,7 +426,7 @@ describe API::ProjectClusters do
context 'with a cluster that does not belong to user' do
let(:cluster) { create(:cluster, :project, :provided_by_user) }
- it 'should respond with 404' do
+ it 'responds with 404' do
expect(response).to have_gitlab_http_status(404)
end
end
@@ -435,7 +442,7 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'should respond with 404' do
+ it 'responds with 404' do
delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params
expect(response).to have_gitlab_http_status(404)
@@ -447,18 +454,18 @@ describe API::ProjectClusters do
delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params
end
- it 'should respond with 204' do
+ it 'responds with 204' do
expect(response).to have_gitlab_http_status(204)
end
- it 'should delete the cluster' do
+ it 'deletes the cluster' do
expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
end
context 'with a cluster that does not belong to user' do
let(:cluster) { create(:cluster, :project, :provided_by_user) }
- it 'should respond with 404' do
+ it 'responds with 404' do
expect(response).to have_gitlab_http_status(404)
end
end
diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb
new file mode 100644
index 00000000000..43df9993eb9
--- /dev/null
+++ b/spec/requests/api/project_events_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe API::ProjectEvents do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
+ let(:closed_issue) { create(:closed_issue, project: private_project, author: user) }
+ let!(:closed_issue_event) { create(:event, project: private_project, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) }
+
+ describe 'GET /projects/:id/events' do
+ context 'when unauthenticated ' do
+ it 'returns 404 for private project' do
+ get api("/projects/#{private_project.id}/events")
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 200 status for a public project' do
+ public_project = create(:project, :public)
+
+ get api("/projects/#{public_project.id}/events")
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'with inaccessible events' do
+ let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
+ let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
+ let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
+ let(:public_issue) { create(:closed_issue, project: public_project, author: user) }
+ let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) }
+
+ it 'returns only accessible events' do
+ get api("/projects/#{public_project.id}/events", non_member)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns all events when the user has access' do
+ get api("/projects/#{public_project.id}/events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(2)
+ end
+ end
+
+ context 'pagination' do
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 1'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-10'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-11'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 2'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-12'))
+ end
+
+ it 'correctly returns the second page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 1')
+ expect(titles).not_to include('Confidential event')
+ end
+
+ it 'correctly returns the first page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 2')
+ expect(titles).not_to include('Confidential event')
+ end
+ end
+
+ context 'when not permitted to read' do
+ it 'returns 404' do
+ get api("/projects/#{private_project.id}/events", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns project events' do
+ get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
+
+ 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(1)
+ end
+
+ it 'returns 404 if project does not exist' do
+ get api("/projects/1234/events", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the requesting token does not have "api" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "403" response' do
+ get api("/projects/#{private_project.id}/events", personal_access_token: token)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'when exists some events' do
+ let(:merge_request1) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') }
+ let(:merge_request2) { create(:merge_request, :closed, author: user, assignees: [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(skip_cached: false) do
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
+ end.count
+
+ create_event(merge_request2)
+
+ expect do
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
+ end.not_to exceed_all_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/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 49b5dfb0b33..895f05a98e8 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -23,13 +23,13 @@ describe API::ProjectMilestones do
end
it 'returns 404 response when the project does not exists' do
- delete api("/projects/999/milestones/#{milestone.id}", user)
+ delete api("/projects/0/milestones/#{milestone.id}", user)
expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 response when the milestone does not exists' do
- delete api("/projects/#{project.id}/milestones/999", user)
+ delete api("/projects/#{project.id}/milestones/0", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -49,4 +49,74 @@ describe API::ProjectMilestones do
params: { state_event: 'close' }
end
end
+
+ describe 'POST /projects/:id/milestones/:milestone_id/promote' do
+ let(:group) { create(:group) }
+
+ before do
+ project.update(namespace: group)
+ end
+
+ context 'when user does not have permission to promote milestone' do
+ before do
+ group.add_guest(user)
+ end
+
+ it 'returns 403' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'when user has permission' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns 200' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(group.milestones.first.title).to eq(milestone.title)
+ end
+
+ it 'returns 200 for closed milestone' do
+ post api("/projects/#{project.id}/milestones/#{closed_milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(group.milestones.first.title).to eq(closed_milestone.title)
+ end
+ end
+
+ context 'when no such resources' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns 404 response when the project does not exist' do
+ post api("/projects/0/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 response when the milestone does not exist' do
+ post api("/projects/#{project.id}/milestones/0/promote", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when project does not belong to group' do
+ before do
+ project.update(namespace: user.namespace)
+ end
+
+ it 'returns 403' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb
new file mode 100644
index 00000000000..184d0a72c37
--- /dev/null
+++ b/spec/requests/api/project_statistics_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::ProjectStatistics do
+ let(:maintainer) { create(:user) }
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ public_project.add_maintainer(maintainer)
+ end
+
+ describe 'GET /projects/:id/statistics' do
+ let!(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
+ let!(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
+ let!(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
+ let!(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
+ let!(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
+ let!(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
+
+ it 'returns the fetch statistics of the last 30 days' do
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(200)
+ fetches = json_response['fetches']
+ expect(fetches['total']).to eq(40)
+ expect(fetches['days'].length).to eq(5)
+ expect(fetches['days'].first).to eq({ 'count' => fetch_statistics5.fetch_count, 'date' => fetch_statistics5.date.to_s })
+ expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
+ end
+
+ it 'excludes the fetch statistics older than 30 days' do
+ create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago)
+
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(200)
+ fetches = json_response['fetches']
+ expect(fetches['total']).to eq(40)
+ expect(fetches['days'].length).to eq(5)
+ expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
+ end
+
+ it 'responds with 403 when the user is not a maintainer of the repository' do
+ developer = create(:user)
+ public_project.add_developer(developer)
+
+ get api("/projects/#{public_project.id}/statistics", developer)
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'responds with 404 when daily_statistics_enabled? is false' do
+ stub_feature_flags(project_daily_statistics: { thing: public_project, enabled: false })
+
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 792abdb2972..799e84e83c1 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -13,12 +13,18 @@ shared_examples 'languages and percentages JSON response' do
)
end
- it 'returns expected language values' do
- get api("/projects/#{project.id}/languages", user)
+ context "when the languages haven't been detected yet" do
+ it 'returns expected language values' do
+ get api("/projects/#{project.id}/languages", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({})
+
+ get api("/projects/#{project.id}/languages", user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq(expected_languages)
- expect(json_response.count).to be > 1
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(JSON.parse(response.body)).to eq(expected_languages)
+ end
end
context 'when the languages were detected before' do
@@ -40,6 +46,8 @@ shared_examples 'languages and percentages JSON response' do
end
describe API::Projects do
+ include ExternalAuthorizationServiceHelpers
+
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
@@ -136,6 +144,7 @@ describe API::Projects do
end
let!(:public_project) { create(:project, :public, name: 'public_project') }
+
before do
project
project2
@@ -495,8 +504,9 @@ describe API::Projects do
project4.add_reporter(user2)
end
- it 'returns an array of groups the user has at least developer access' do
+ it 'returns an array of projects the user has at least developer access' do
get api('/projects', user2), params: { min_access_level: 30 }
+
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -778,7 +788,7 @@ describe API::Projects do
let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
it 'returns error when user not found' do
- get api('/users/9999/projects/')
+ get api('/users/0/projects/')
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -968,8 +978,16 @@ describe API::Projects do
describe 'GET /projects/:id' do
context 'when unauthenticated' do
- it 'returns the public projects' do
- public_project = create(:project, :public)
+ it 'does not return private projects' do
+ private_project = create(:project, :private)
+
+ get api("/projects/#{private_project.id}")
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns public projects' do
+ public_project = create(:project, :repository, :public)
get api("/projects/#{public_project.id}")
@@ -977,11 +995,84 @@ describe API::Projects do
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['ci_config_path']).to eq(public_project.ci_config_path)
expect(json_response.keys).not_to include('permissions')
end
+
+ context 'and the project has a private repository' do
+ let(:project) { create(:project, :repository, :public, :repository_private) }
+ let(:protected_attributes) { %w(default_branch ci_config_path) }
+
+ it 'hides protected attributes of private repositories if user is not a member' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ protected_attributes.each do |attribute|
+ expect(json_response.keys).not_to include(attribute)
+ end
+ end
+
+ it 'exposes protected attributes of private repositories if user is a member' do
+ project.add_developer(user)
+
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ protected_attributes.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+ end
end
- context 'when authenticated' do
+ context 'when authenticated as an admin' do
+ it 'returns a project by id' do
+ project
+ project_member
+ group = create(:group)
+ link = create(:project_group_link, project: project, group: group)
+
+ get api("/projects/#{project.id}", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(project.id)
+ expect(json_response['description']).to eq(project.description)
+ expect(json_response['default_branch']).to eq(project.default_branch)
+ expect(json_response['tag_list']).to be_an Array
+ expect(json_response['archived']).to be_falsey
+ expect(json_response['visibility']).to be_present
+ expect(json_response['ssh_url_to_repo']).to be_present
+ expect(json_response['http_url_to_repo']).to be_present
+ expect(json_response['web_url']).to be_present
+ expect(json_response['owner']).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to be_present
+ expect(json_response['issues_enabled']).to be_present
+ expect(json_response['merge_requests_enabled']).to be_present
+ expect(json_response['wiki_enabled']).to be_present
+ expect(json_response['jobs_enabled']).to be_present
+ expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['container_registry_enabled']).to be_present
+ expect(json_response['created_at']).to be_present
+ expect(json_response['last_activity_at']).to be_present
+ expect(json_response['shared_runners_enabled']).to be_present
+ expect(json_response['creator_id']).to be_present
+ expect(json_response['namespace']).to be_present
+ expect(json_response['avatar_url']).to be_nil
+ expect(json_response['star_count']).to be_present
+ expect(json_response['forks_count']).to be_present
+ expect(json_response['public_jobs']).to be_present
+ expect(json_response['shared_with_groups']).to be_an Array
+ expect(json_response['shared_with_groups'].length).to eq(1)
+ expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
+ expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
+ expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+ end
+ end
+
+ context 'when authenticated as a regular user' do
before do
project
project_member
@@ -1004,7 +1095,6 @@ describe API::Projects do
expect(json_response['http_url_to_repo']).to be_present
expect(json_response['web_url']).to be_present
expect(json_response['owner']).to be_a Hash
- expect(json_response['owner']).to be_a Hash
expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to be_present
expect(json_response['issues_enabled']).to be_present
@@ -1092,7 +1182,9 @@ describe API::Projects do
'path' => user.namespace.path,
'kind' => user.namespace.kind,
'full_path' => user.namespace.full_path,
- 'parent_id' => nil
+ 'parent_id' => nil,
+ 'avatar_url' => user.avatar_url,
+ 'web_url' => Gitlab::Routing.url_helpers.user_url(user)
})
end
@@ -1130,6 +1222,36 @@ describe API::Projects do
expect(json_response).to include 'statistics'
end
+ context "and the project has a private repository" do
+ let(:project) { create(:project, :public, :repository, :repository_private) }
+
+ it "does not include statistics if user is not a member" do
+ get api("/projects/#{project.id}", user), params: { statistics: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'statistics'
+ end
+
+ it "includes statistics if user is a member" do
+ project.add_developer(user)
+
+ get api("/projects/#{project.id}", user), params: { statistics: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'statistics'
+ end
+
+ it "includes statistics also when repository is disabled" do
+ project.add_developer(user)
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+
+ get api("/projects/#{project.id}", user), params: { statistics: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'statistics'
+ end
+ end
+
it "includes import_error if user can admin project" do
get api("/projects/#{project.id}", user)
@@ -1264,6 +1386,70 @@ describe API::Projects do
end
end
end
+
+ context 'when project belongs to a group namespace' do
+ let(:group) { create(:group, :with_avatar) }
+ let(:project) { create(:project, namespace: group) }
+ let!(:project_member) { create(:project_member, :developer, user: user, project: project) }
+
+ it 'returns group web_url and avatar_url' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+
+ group_data = json_response['namespace']
+ expect(group_data['web_url']).to eq(group.web_url)
+ expect(group_data['avatar_url']).to eq(group.avatar_url)
+ end
+ end
+
+ context 'when project belongs to a user namespace' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ it 'returns user web_url and avatar_url' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+
+ user_data = json_response['namespace']
+ expect(user_data['web_url']).to eq("http://localhost/#{user.username}")
+ expect(user_data['avatar_url']).to eq(user.avatar_url)
+ end
+ end
+ end
+
+ context 'with external authorization' do
+ let(:project) do
+ create(:project,
+ namespace: user.namespace,
+ external_authorization_classification_label: 'the-label')
+ end
+
+ context 'when the user has access to the project' do
+ before do
+ external_service_allow_access(user, project)
+ end
+
+ it 'includes the label in the response' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['external_authorization_classification_label']).to eq('the-label')
+ end
+ end
+
+ context 'when the external service denies access' do
+ before do
+ external_service_deny_access(user, project)
+ end
+
+ it 'returns a 404' do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
end
@@ -1385,7 +1571,7 @@ describe API::Projects do
end
it 'fails if forked_from project which does not exist' do
- post api("/projects/#{project_fork_target.id}/fork/9999", admin)
+ post api("/projects/#{project_fork_target.id}/fork/0", admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -1510,6 +1696,9 @@ describe API::Projects do
describe "POST /projects/:id/share" do
let(:group) { create(:group) }
+ before do
+ group.add_developer(user)
+ end
it "shares project with group" do
expires_at = 10.days.from_now.to_date
@@ -1560,6 +1749,15 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq 'group_access does not have a valid value'
end
+
+ it "returns a 409 error when link is not saved" do
+ allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute)
+ .and_return({ status: :error, http_status: 409, message: 'error' })
+
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
+
+ expect(response).to have_gitlab_http_status(409)
+ end
end
describe 'DELETE /projects/:id/share/:group_id' do
@@ -1807,6 +2005,20 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(403)
end
end
+
+ context 'when updating external classification' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'updates the classification label' do
+ put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(project.reload.external_authorization_classification_label).to eq('new label')
+ end
+ end
end
describe 'POST /projects/:id/archive' do
@@ -1936,7 +2148,7 @@ describe API::Projects do
end
it 'returns not_found(404) for not existing project' do
- get api("/projects/9999999999/languages", user)
+ get api("/projects/0/languages", user)
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index ba948e37e2f..3a59052bb29 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -73,6 +73,22 @@ describe API::Release::Links do
expect(response).to have_gitlab_http_status(:ok)
end
end
+
+ context 'when project is public and the repository is private' do
+ let(:project) { create(:project, :repository, :public, :repository_private) }
+
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) }
+ end
+
+ context 'when the release does not exists' do
+ let!(:release) { }
+
+ it_behaves_like '403 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) }
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 1f317971a66..8603fa2a73d 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -4,12 +4,14 @@ describe API::Releases do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
let(:non_project_member) { create(:user) }
let(:commit) { create(:commit, project: project) }
before do
project.add_maintainer(maintainer)
project.add_reporter(reporter)
+ project.add_guest(guest)
project.repository.add_tag(maintainer, 'v0.1', commit.id)
project.repository.add_tag(maintainer, 'v0.2', commit.id)
@@ -50,7 +52,7 @@ describe API::Releases do
it 'matches response schema' do
get api("/projects/#{project.id}/releases", maintainer)
- expect(response).to match_response_schema('releases')
+ expect(response).to match_response_schema('public_api/v4/releases')
end
end
@@ -66,6 +68,46 @@ describe API::Releases do
end
end
+ context 'when user is a guest' do
+ let!(:release) do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ author: maintainer,
+ created_at: 2.days.ago)
+ end
+
+ it 'responds 200 OK' do
+ get api("/projects/#{project.id}/releases", guest)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it "does not expose tag, commit and source code" do
+ get api("/projects/#{project.id}/releases", guest)
+
+ expect(response).to match_response_schema('public_api/v4/release/releases_for_guest')
+ expect(json_response[0]['assets']['count']).to eq(release.links.count)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'responds 200 OK' do
+ get api("/projects/#{project.id}/releases", guest)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it "exposes tag, commit and source code" do
+ get api("/projects/#{project.id}/releases", guest)
+
+ expect(response).to match_response_schema('public_api/v4/releases')
+ expect(json_response[0]['assets']['count']).to eq(release.links.count + release.sources.count)
+ end
+ end
+ end
+
context 'when user is not a project member' do
it 'cannot find the project' do
get api("/projects/#{project.id}/releases", non_project_member)
@@ -115,7 +157,7 @@ describe API::Releases do
it 'matches response schema' do
get api("/projects/#{project.id}/releases/v0.1", maintainer)
- expect(response).to match_response_schema('release')
+ expect(response).to match_response_schema('public_api/v4/release')
end
it 'contains source information as assets' do
@@ -189,6 +231,35 @@ describe API::Releases do
end
end
end
+
+ context 'when user is a guest' do
+ it 'responds 403 Forbidden' do
+ get api("/projects/#{project.id}/releases/v0.1", guest)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'responds 200 OK' do
+ get api("/projects/#{project.id}/releases/v0.1", guest)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it "exposes tag and commit" do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ author: maintainer,
+ created_at: 2.days.ago)
+ get api("/projects/#{project.id}/releases/v0.1", guest)
+
+ expect(response).to match_response_schema('public_api/v4/release')
+ end
+ end
+ end
end
context 'when specified tag is not found in the project' do
@@ -268,7 +339,7 @@ describe API::Releases do
it 'matches response schema' do
post api("/projects/#{project.id}/releases", maintainer), params: params
- expect(response).to match_response_schema('release')
+ expect(response).to match_response_schema('public_api/v4/release')
end
it 'does not create a new tag' do
@@ -340,7 +411,7 @@ describe API::Releases do
it 'matches response schema' do
post api("/projects/#{project.id}/releases", maintainer), params: params
- expect(response).to match_response_schema('release')
+ expect(response).to match_response_schema('public_api/v4/release')
end
end
@@ -494,7 +565,7 @@ describe API::Releases do
it 'matches response schema' do
put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params
- expect(response).to match_response_schema('release')
+ expect(response).to match_response_schema('public_api/v4/release')
end
context 'when user tries to update sha' do
@@ -586,7 +657,7 @@ describe API::Releases do
it 'matches response schema' do
delete api("/projects/#{project.id}/releases/v0.1", maintainer)
- expect(response).to match_response_schema('release')
+ expect(response).to match_response_schema('public_api/v4/release')
end
context 'when there are no corresponding releases' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index e6c235ca26e..038c958b5cc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -68,7 +68,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
post api('/runners'), params: { token: group.runners_token }
expect(response).to have_http_status 201
- expect(group.runners.size).to eq(1)
+ expect(group.runners.reload.size).to eq(1)
runner = Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
@@ -168,6 +168,32 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when access_level is provided for Runner' do
+ context 'when access_level is set to ref_protected' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ access_level: 'ref_protected'
+ }
+
+ expect(response).to have_gitlab_http_status 201
+ expect(Ci::Runner.first.ref_protected?).to be true
+ end
+ end
+
+ context 'when access_level is set to not_protected' do
+ it 'creates runner' do
+ post api('/runners'), params: {
+ token: registration_token,
+ access_level: 'not_protected'
+ }
+
+ expect(response).to have_gitlab_http_status 201
+ expect(Ci::Runner.first.ref_protected?).to be false
+ end
+ end
+ end
+
context 'when maximum job timeout is specified' do
it 'creates runner' do
post api('/runners'), params: {
@@ -418,8 +444,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
'sha' => job.sha,
'before_sha' => job.before_sha,
'ref_type' => 'branch',
- 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*],
- 'depth' => 0 }
+ 'refspecs' => ["+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
+ 'depth' => project.default_git_depth }
end
let(:expected_steps) do
@@ -436,9 +462,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }]
end
let(:expected_artifacts) do
@@ -470,11 +496,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['token']).to eq(job.token)
expect(json_response['job_info']).to eq(expected_job_info)
expect(json_response['git_info']).to eq(expected_git_info)
- expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
+ expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh', 'ports' => [] })
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
- 'alias' => nil, 'command' => nil },
+ 'alias' => nil, 'command' => nil, 'ports' => [] },
{ 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
- 'alias' => 'docker', 'command' => 'sleep 30' }])
+ 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }])
expect(json_response['steps']).to eq(expected_steps)
expect(json_response['artifacts']).to eq(expected_artifacts)
expect(json_response['cache']).to eq(expected_cache)
@@ -505,7 +531,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
@@ -516,6 +546,30 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when job filtered by job_age' do
+ let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago) }
+
+ context 'job is queued less than job_age parameter' do
+ let(:job_age) { 120 }
+
+ it 'gives 204' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
+ context 'job is queued more than job_age parameter' do
+ let(:job_age) { 30 }
+
+ it 'picks a job' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+ end
+
context 'when job is made for branch' do
it 'sets tag as ref_type' do
request_job
@@ -537,7 +591,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
@@ -549,7 +607,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
context 'when job is made for merge request' do
- let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request, project: project, ref: 'feature', merge_request: merge_request) }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) }
let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) }
let(:merge_request) { create(:merge_request) }
@@ -740,12 +798,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when triggered job is available' do
let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
- { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
- { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false },
+ { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false },
+ { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }]
end
let(:trigger) { create(:ci_trigger, project: project) }
@@ -853,6 +911,56 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ describe 'port support' do
+ let(:job) { create(:ci_build, pipeline: pipeline, options: options) }
+
+ context 'when job image has ports' do
+ let(:options) do
+ {
+ image: {
+ name: 'ruby',
+ ports: [80]
+ },
+ services: ['mysql']
+ }
+ end
+
+ it 'returns the image ports' do
+ request_job
+
+ expect(response).to have_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby', 'ports' => [{ 'number' => 80, 'protocol' => 'http', 'name' => 'default_port' }]),
+ 'services' => all(a_hash_including('name' => 'mysql')))
+ end
+ end
+
+ context 'when job services settings has ports' do
+ let(:options) do
+ {
+ image: 'ruby',
+ services: [
+ {
+ name: 'tomcat',
+ ports: [{ number: 8081, protocol: 'http', name: 'custom_port' }]
+ }
+ ]
+ }
+ end
+
+ it 'returns the service ports' do
+ request_job
+
+ expect(response).to have_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => a_hash_including('name' => 'ruby'),
+ 'services' => all(a_hash_including('name' => 'tomcat', 'ports' => [{ 'number' => 8081, 'protocol' => 'http', 'name' => 'custom_port' }])))
+ end
+ end
+ end
+
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
@@ -918,6 +1026,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it { expect(job).to be_job_execution_timeout }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ before do
+ update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
+ job.reload
+ end
+
+ it { expect(job).to be_unmet_prerequisites }
+ end
end
context 'when trace is given' do
@@ -1523,8 +1640,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:metadata) { file_upload2 }
let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
- let(:stored_artifacts_file) { job.reload.artifacts_file.file }
- let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
+ let(:stored_artifacts_file) { job.reload.artifacts_file }
+ let(:stored_metadata_file) { job.reload.artifacts_metadata }
let(:stored_artifacts_size) { job.reload.artifacts_size }
let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
@@ -1545,9 +1662,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'stores artifacts and artifacts metadata' do
expect(response).to have_gitlab_http_status(201)
- expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
- expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
- expect(stored_artifacts_size).to eq(72821)
+ expect(stored_artifacts_file.filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.filename).to eq(metadata.original_filename)
+ expect(stored_artifacts_size).to eq(artifacts.size)
expect(stored_artifacts_sha256).to eq(artifacts_sha256)
expect(stored_metadata_sha256).to eq(metadata_sha256)
end
@@ -1675,7 +1792,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
it 'download artifacts' do
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response.headers.to_h).to include download_headers
end
end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 7f11c8c9fe8..5548e3fd01a 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -90,6 +90,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners?tag_list=tag1,tag2', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'unauthorized user' do
@@ -181,6 +192,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners/all?tag_list=tag1,tag2', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'without admin privileges' do
@@ -241,7 +263,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- get api('/runners/9999', admin)
+ get api('/runners/0', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -394,7 +416,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- update_runner(9999, admin, description: 'test')
+ update_runner(0, admin, description: 'test')
expect(response).to have_gitlab_http_status(404)
end
@@ -468,7 +490,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- delete api('/runners/9999', admin)
+ delete api('/runners/0', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -573,7 +595,7 @@ describe API::Runners do
context "when runner doesn't exist" do
it 'returns 404' do
- get api('/runners/9999/jobs', admin)
+ get api('/runners/0/jobs', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -626,7 +648,7 @@ describe API::Runners do
context "when runner doesn't exist" do
it 'returns 404' do
- get api('/runners/9999/jobs', user)
+ get api('/runners/0/jobs', user)
expect(response).to have_gitlab_http_status(404)
end
@@ -716,6 +738,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api("/projects/#{project.id}/runners?tag_list=tag1,tag2", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'authorized user without maintainer privileges' do
@@ -857,7 +890,7 @@ describe API::Runners do
end
it 'returns 404 is runner is not found' do
- delete api("/projects/#{project.id}/runners/9999", user)
+ delete api("/projects/#{project.id}/runners/0", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 831f47debeb..3e0b478abb3 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -70,11 +70,52 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
+ end
+
+ context 'when user can read project milestones' do
+ before do
+ get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+ end
- get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+ it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
- it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
+ context 'when user cannot read project milestones' do
+ before do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns empty array' do
+ get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones).to be_empty
+ end
+ end
+ end
+
+ context 'for users scope' do
+ before do
+ create(:user, name: 'billy')
+
+ get api('/search', user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api('/search', user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
end
context 'for snippet_titles scope' do
@@ -126,7 +167,7 @@ describe API::Search do
context 'when group does not exist' do
it 'returns 404 error' do
- get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' }
+ get api('/groups/0/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -192,6 +233,40 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
+
+ context 'for users scope' do
+ before do
+ user = create(:user, name: 'billy')
+ create(:group_member, :developer, user: user, group: group)
+
+ get api("/groups/#{group.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api("/groups/#{group.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
+ context 'for users scope with group path as id' do
+ before do
+ user1 = create(:user, name: 'billy')
+ create(:group_member, :developer, user: user1, group: group)
+
+ get api("/groups/#{CGI.escape(group.full_path)}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+ end
end
end
@@ -222,7 +297,7 @@ describe API::Search do
context 'when project does not exist' do
it 'returns 404 error' do
- get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' }
+ get api('/projects/0/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -262,11 +337,53 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
+ end
- get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+ context 'when user can read milestones' do
+ before do
+ get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
- it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
+ context 'when user cannot read project milestones' do
+ before do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns empty array' do
+ get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones).to be_empty
+ end
+ end
+ end
+
+ context 'for users scope' do
+ before do
+ user1 = create(:user, name: 'billy')
+ create(:project_member, :developer, user: user1, project: project)
+
+ get api("/projects/#{project.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api("/projects/#{project.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
end
context 'for notes scope' do
@@ -335,6 +452,13 @@ describe API::Search do
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(11)
end
+
+ it 'by ref' do
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'This file is used in tests for ci_environments_status', ref: 'pages-deploy' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ end
end
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index f33eb5b9e02..8a60980fe80 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -13,6 +13,7 @@ describe API::Settings, 'Settings' do
expect(json_response['default_projects_limit']).to eq(42)
expect(json_response['password_authentication_enabled_for_web']).to be_truthy
expect(json_response['repository_storages']).to eq(['default'])
+ expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['plantuml_enabled']).to be_falsey
expect(json_response['plantuml_url']).to be_nil
expect(json_response['default_project_visibility']).to be_a String
@@ -44,6 +45,7 @@ describe API::Settings, 'Settings' do
put api("/application/settings", admin),
params: {
default_projects_limit: 3,
+ default_project_creation: 2,
password_authentication_enabled_for_web: false,
repository_storages: ['custom'],
plantuml_enabled: true,
@@ -64,12 +66,13 @@ describe API::Settings, 'Settings' do
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000,
- default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE,
+ default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE,
local_markdown_version: 3
}
expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
+ expect(json_response['default_project_creation']).to eq(::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
expect(json_response['password_authentication_enabled_for_web']).to be_falsey
expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['plantuml_enabled']).to be_truthy
@@ -114,6 +117,39 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to be_nil
end
+ context 'external policy classification settings' do
+ let(:settings) do
+ {
+ external_authorization_service_enabled: true,
+ external_authorization_service_url: 'https://custom.service/',
+ external_authorization_service_default_label: 'default',
+ external_authorization_service_timeout: 9.99,
+ external_auth_client_cert: File.read('spec/fixtures/passphrase_x509_certificate.crt'),
+ external_auth_client_key: File.read('spec/fixtures/passphrase_x509_certificate_pk.key'),
+ external_auth_client_key_pass: "5iveL!fe"
+ }
+ end
+ let(:attribute_names) { settings.keys.map(&:to_s) }
+
+ it 'includes the attributes in the API' do
+ get api("/application/settings", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ attribute_names.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+
+ it 'allows updating the settings' do
+ put api("/application/settings", admin), params: settings
+
+ expect(response).to have_gitlab_http_status(200)
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+ end
+
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { plantuml_enabled: true }
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 7c8512f7589..d600076e9fb 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -84,10 +84,17 @@ describe API::Snippets do
end
describe 'GET /snippets/:id/raw' do
- let(:snippet) { create(:personal_snippet, author: user) }
+ set(:author) { create(:user) }
+ set(:snippet) { create(:personal_snippet, :private, author: author) }
+
+ it 'requires authentication' do
+ get api("/snippets/#{snippet.id}", nil)
+
+ expect(response).to have_gitlab_http_status(401)
+ end
it 'returns raw text' do
- get api("/snippets/#{snippet.id}/raw", user)
+ get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq 'text/plain'
@@ -95,38 +102,83 @@ describe API::Snippets do
end
it 'forces attachment content disposition' do
- get api("/snippets/#{snippet.id}/raw", user)
+ get api("/snippets/#{snippet.id}/raw", author)
expect(headers['Content-Disposition']).to match(/^attachment/)
end
it 'returns 404 for invalid snippet id' do
- get api("/snippets/1234/raw", user)
+ snippet.destroy
+
+ get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
end
+
+ it 'hides private snippets from ordinary users' do
+ get api("/snippets/#{snippet.id}/raw", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'shows internal snippets to ordinary users' do
+ internal_snippet = create(:personal_snippet, :internal, author: author)
+
+ get api("/snippets/#{internal_snippet.id}/raw", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
describe 'GET /snippets/:id' do
- let(:snippet) { create(:personal_snippet, author: user) }
+ set(:admin) { create(:user, :admin) }
+ set(:author) { create(:user) }
+ set(:private_snippet) { create(:personal_snippet, :private, author: author) }
+ set(:internal_snippet) { create(:personal_snippet, :internal, author: author) }
+
+ it 'requires authentication' do
+ get api("/snippets/#{private_snippet.id}", nil)
+
+ expect(response).to have_gitlab_http_status(401)
+ end
it 'returns snippet json' do
- get api("/snippets/#{snippet.id}", user)
+ get api("/snippets/#{private_snippet.id}", author)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['title']).to eq(snippet.title)
- expect(json_response['description']).to eq(snippet.description)
- expect(json_response['file_name']).to eq(snippet.file_name)
- expect(json_response['visibility']).to eq(snippet.visibility)
+ expect(json_response['title']).to eq(private_snippet.title)
+ expect(json_response['description']).to eq(private_snippet.description)
+ expect(json_response['file_name']).to eq(private_snippet.file_name)
+ expect(json_response['visibility']).to eq(private_snippet.visibility)
+ end
+
+ it 'shows private snippets to an admin' do
+ get api("/snippets/#{private_snippet.id}", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'hides private snippets from an ordinary user' do
+ get api("/snippets/#{private_snippet.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'shows internal snippets to an ordinary user' do
+ get api("/snippets/#{internal_snippet.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
end
it 'returns 404 for invalid snippet id' do
- get api("/snippets/1234", user)
+ private_snippet.destroy
+
+ get api("/snippets/#{private_snippet.id}", admin)
expect(response).to have_gitlab_http_status(404)
- expect(json_response['message']).to eq('404 Not found')
+ expect(json_response['message']).to eq('404 Snippet Not Found')
end
end
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index 3c2842e5725..5b07e598b8d 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -42,8 +42,7 @@ describe API::Suggestions do
expect(response).to have_gitlab_http_status(200)
expect(json_response)
- .to include('id', 'from_original_line', 'to_original_line',
- 'from_line', 'to_line', 'appliable', 'applied',
+ .to include('id', 'from_line', 'to_line', 'appliable', 'applied',
'from_content', 'to_content')
end
end
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index b6e8d74c2e9..0e2f3face71 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -1,12 +1,14 @@
require 'spec_helper'
describe API::SystemHooks do
+ include StubRequests
+
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let!(:hook) { create(:system_hook, url: "http://example.com") }
before do
- stub_request(:post, hook.url)
+ stub_full_request(hook.url, method: :post)
end
describe "GET /hooks" do
@@ -68,6 +70,8 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
+ stub_full_request('http://mep.mep', method: :post)
+
post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(201)
@@ -78,6 +82,8 @@ describe API::SystemHooks do
end
it 'sets explicit values for events' do
+ stub_full_request('http://mep.mep', method: :post)
+
post api('/hooks', admin),
params: {
url: 'http://mep.mep',
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index fffe878ddbd..d898319e709 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -378,7 +378,7 @@ describe API::Tags do
post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(201)
- expect(response).to match_response_schema('public_api/v4/release')
+ expect(response).to match_response_schema('public_api/v4/release/tag_release')
expect(json_response['tag_name']).to eq(tag_name)
expect(json_response['description']).to eq(description)
end
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
new file mode 100644
index 00000000000..ee2531197b1
--- /dev/null
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'task completion status response' do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ shared_examples 'taskable completion status provider' do |path|
+ samples = [
+ {
+ description: '',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: 'Lorem ipsum',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [ ] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 1
+ },
+ {
+ description: %{- [ ] task 1
+ - [ ] task 2 },
+ expected_count: 2,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 2
+ },
+ {
+ description: %{- [ ] task 1},
+ expected_count: 1,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1},
+ expected_count: 1,
+ expected_completed_count: 1
+ }
+ ]
+ samples.each do |sample_data|
+ context "with a description of #{sample_data[:description].inspect}" do
+ before do
+ taskable.update!(description: sample_data[:description])
+
+ get api("#{path}?iids[]=#{taskable.iid}", user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
+
+ it 'returns the expected results' do
+ expect(json_response).to be_an Array
+ expect(json_response).not_to be_empty
+
+ task_completion_status = json_response.first['task_completion_status']
+ expect(task_completion_status['count']).to eq(sample_data[:expected_count])
+ expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count])
+ end
+ end
+ end
+ end
+
+ context 'task list completion status for issues' do
+ it_behaves_like 'taskable completion status provider', '/issues' do
+ let(:taskable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'task list completion status for merge_requests' do
+ it_behaves_like 'taskable completion status provider', '/merge_requests' do
+ let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index f121a1d3b78..9f0d5ad5d12 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -8,10 +8,14 @@ describe API::Todos do
let(:author_2) { create(:user) }
let(:john_doe) { create(:user, username: 'john_doe') }
let(:merge_request) { create(:merge_request, source_project: project_1) }
+ let!(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) }
let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
let!(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) }
let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
+ let!(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') }
+ let!(:award_emoji_2) { create(:award_emoji, awardable: pending_1.target, user: author_1, name: 'thumbsup') }
+ let!(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') }
before do
project_1.add_developer(john_doe)
@@ -34,7 +38,7 @@ describe API::Todos do
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect(json_response.length).to eq(4)
expect(json_response[0]['id']).to eq(pending_3.id)
expect(json_response[0]['project']).to be_a Hash
expect(json_response[0]['author']).to be_a Hash
@@ -45,6 +49,23 @@ describe API::Todos do
expect(json_response[0]['state']).to eq('pending')
expect(json_response[0]['action_name']).to eq('assigned')
expect(json_response[0]['created_at']).to be_present
+ expect(json_response[0]['target_type']).to eq('Commit')
+
+ expect(json_response[1]['target_type']).to eq('Issue')
+ expect(json_response[1]['target']['upvotes']).to eq(0)
+ expect(json_response[1]['target']['downvotes']).to eq(1)
+ expect(json_response[1]['target']['merge_requests_count']).to eq(0)
+
+ expect(json_response[2]['target_type']).to eq('Issue')
+ expect(json_response[2]['target']['upvotes']).to eq(1)
+ expect(json_response[2]['target']['downvotes']).to eq(0)
+ expect(json_response[2]['target']['merge_requests_count']).to eq(0)
+
+ expect(json_response[3]['target_type']).to eq('MergeRequest')
+ # Only issues get a merge request count at the moment
+ expect(json_response[3]['target']['merge_requests_count']).to be_nil
+ expect(json_response[3]['target']['upvotes']).to eq(1)
+ expect(json_response[3]['target']['downvotes']).to eq(0)
end
context 'and using the author filter' do
@@ -54,7 +75,7 @@ describe API::Todos do
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
end
end
@@ -67,7 +88,7 @@ describe API::Todos do
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
end
end
@@ -100,7 +121,7 @@ describe API::Todos do
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
end
end
@@ -115,6 +136,27 @@ describe API::Todos do
end
end
end
+
+ it 'avoids N+1 queries', :request_store do
+ create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
+
+ get api('/todos', john_doe)
+
+ control = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
+
+ merge_request_2 = create(:merge_request, source_project: project_2)
+ create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2)
+
+ project_3 = create(:project, :repository)
+ project_3.add_developer(john_doe)
+ merge_request_3 = create(:merge_request, source_project: project_3)
+ create(:todo, project: project_3, author: author_2, user: john_doe, target: merge_request_3)
+ create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe)
+ create(:on_commit_todo, project: project_3, author: author_1, user: john_doe)
+
+ expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control)
+ expect(response.status).to eq(200)
+ end
end
describe 'POST /todos/:id/mark_as_done' do
@@ -230,7 +272,7 @@ describe API::Todos do
context 'for a merge request' do
it_behaves_like 'an issuable', 'merge_requests' do
- let(:issuable) { merge_request }
+ let(:issuable) { create(:merge_request, :simple, source_project: project_1) }
end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b381431306d..bab1520b960 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -68,6 +68,13 @@ describe API::Users do
expect(json_response.size).to eq(0)
end
+ it "does not return the highest role" do
+ get api("/users"), params: { username: user.username }
+
+ expect(response).to match_response_schema('public_api/v4/user/basics')
+ expect(json_response.first.keys).not_to include 'highest_role'
+ end
+
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@@ -269,6 +276,18 @@ describe API::Users do
expect(response).to have_gitlab_http_status(400)
end
end
+
+ context "when authenticated and ldap is enabled" do
+ it "returns non-ldap user" do
+ create :omniauth_user, provider: "ldapserver1"
+
+ get api("/users", user), params: { skip_ldap: "true" }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["username"]).to eq user.username
+ end
+ end
end
describe "GET /users/:id" do
@@ -286,6 +305,13 @@ describe API::Users do
expect(json_response.keys).not_to include 'is_admin'
end
+ it "does not return the user's `highest_role`" do
+ get api("/users/#{user.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).not_to include 'highest_role'
+ end
+
context 'when authenticated as admin' do
it 'includes the `is_admin` field' do
get api("/users/#{user.id}", admin)
@@ -300,6 +326,12 @@ describe API::Users do
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response.keys).to include 'created_at'
end
+ it 'includes the `highest_role` field' do
+ get api("/users/#{user.id}", admin)
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(json_response['highest_role']).to be(0)
+ end
end
context 'for an anonymous user' do
@@ -335,7 +367,7 @@ describe API::Users do
end
it "returns a 404 error if user id not found" do
- get api("/users/9999", user)
+ get api("/users/0", user)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -732,7 +764,7 @@ describe API::Users do
end
it "returns 404 for non-existing user" do
- put api("/users/999999", admin), params: { bio: 'update should fail' }
+ put api("/users/0", admin), params: { bio: 'update should fail' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -836,7 +868,7 @@ describe API::Users do
end
it "returns 400 for invalid ID" do
- post api("/users/999999/keys", admin)
+ post api("/users/0/keys", admin)
expect(response).to have_gitlab_http_status(400)
end
end
@@ -895,7 +927,7 @@ describe API::Users do
it 'returns 404 error if user not found' do
user.keys << key
user.save
- delete api("/users/999999/keys/#{key.id}", admin)
+ delete api("/users/0/keys/#{key.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -930,7 +962,7 @@ describe API::Users do
end
it 'returns 400 for invalid ID' do
- post api('/users/999999/gpg_keys', admin)
+ post api('/users/0/gpg_keys', admin)
expect(response).to have_gitlab_http_status(400)
end
@@ -951,7 +983,7 @@ describe API::Users do
context 'when authenticated' do
it 'returns 404 for non-existing user' do
- get api('/users/999999/gpg_keys', admin)
+ get api('/users/0/gpg_keys', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1007,7 +1039,7 @@ describe API::Users do
user.keys << key
user.save
- delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin)
+ delete api("/users/0/gpg_keys/#{gpg_key.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1051,7 +1083,7 @@ describe API::Users do
user.gpg_keys << gpg_key
user.save
- post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin)
+ post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1089,7 +1121,7 @@ describe API::Users do
end
it "returns a 400 for invalid ID" do
- post api("/users/999999/emails", admin)
+ post api("/users/0/emails", admin)
expect(response).to have_gitlab_http_status(400)
end
@@ -1121,7 +1153,7 @@ describe API::Users do
context 'when authenticated' do
it 'returns 404 for non-existing user' do
- get api('/users/999999/emails', admin)
+ get api('/users/0/emails', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1177,7 +1209,7 @@ describe API::Users do
it 'returns 404 error if user not found' do
user.emails << email
user.save
- delete api("/users/999999/emails/#{email.id}", admin)
+ delete api("/users/0/emails/#{email.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1227,7 +1259,7 @@ describe API::Users do
end
it "returns 404 for non-existing user" do
- perform_enqueued_jobs { delete api("/users/999999", admin) }
+ perform_enqueued_jobs { delete api("/users/0", admin) }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1778,7 +1810,7 @@ describe API::Users do
end
it 'returns a 404 error if user id not found' do
- post api('/users/9999/block', admin)
+ post api('/users/0/block', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1816,7 +1848,7 @@ describe API::Users do
end
it 'returns a 404 error if user id not found' do
- post api('/users/9999/block', admin)
+ post api('/users/0/block', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index cdac5b2f400..55b1419a004 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -43,6 +43,8 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(200)
expect(json_response['value']).to eq(variable.value)
expect(json_response['protected']).to eq(variable.protected?)
+ expect(json_response['masked']).to eq(variable.masked?)
+ expect(json_response['variable_type']).to eq('env_var')
end
it 'responds with 404 Not Found if requesting non-existing variable' do
@@ -73,24 +75,28 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true, masked: true }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
- expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['value']).to eq('PROTECTED_VALUE_2')
expect(json_response['protected']).to be_truthy
+ expect(json_response['masked']).to be_truthy
+ expect(json_response['variable_type']).to eq('env_var')
end
it 'creates variable with optional attributes' do
expect do
- post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
+ post api("/projects/#{project.id}/variables", user), params: { variable_type: 'file', key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
expect(json_response['protected']).to be_falsey
+ expect(json_response['masked']).to be_falsey
+ expect(json_response['variable_type']).to eq('file')
end
it 'does not allow to duplicate variable key' do
@@ -125,7 +131,7 @@ describe API::Variables do
initial_variable = project.variables.reload.first
value_before = initial_variable.value
- put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true }
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { variable_type: 'file', value: 'VALUE_1_UP', protected: true }
updated_variable = project.variables.reload.first
@@ -133,6 +139,7 @@ describe API::Variables do
expect(value_before).to eq(variable.value)
expect(updated_variable.value).to eq('VALUE_1_UP')
expect(updated_variable).to be_protected
+ expect(updated_variable.variable_type).to eq('file')
end
it 'responds with 404 Not Found if requesting non-existing variable' do
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 38b618191fb..e06f8bbc095 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe API::Version do
- describe 'GET /version' do
+ shared_examples_for 'GET /version' do
context 'when unauthenticated' do
it 'returns authentication error' do
get api('/version')
@@ -22,4 +22,20 @@ describe API::Version do
end
end
end
+
+ context 'with graphql enabled' do
+ before do
+ stub_feature_flags(graphql: true)
+ end
+
+ include_examples 'GET /version'
+ end
+
+ context 'with graphql disabled' do
+ before do
+ stub_feature_flags(graphql: false)
+ end
+
+ include_examples 'GET /version'
+ end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 5b625fd47be..5c9a5b73ee5 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -104,6 +104,70 @@ describe 'Git HTTP requests' do
end
end
+ shared_examples_for 'project path without .git suffix' do
+ context "GET info/refs" do
+ let(:path) { "/#{project_path}/info/refs" }
+
+ context "when no params are added" do
+ before do
+ get path
+ end
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project_path}.git/info/refs")
+ end
+ end
+
+ context "when the upload-pack service is requested" do
+ let(:params) { { service: 'git-upload-pack' } }
+
+ before do
+ get path, params: params
+ end
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}")
+ end
+ end
+
+ context "when the receive-pack service is requested" do
+ let(:params) { { service: 'git-receive-pack' } }
+
+ before do
+ get path, params: params
+ end
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}")
+ end
+ end
+
+ context "when the params are anything else" do
+ let(:params) { { service: 'git-implode-pack' } }
+
+ before do
+ get path, params: params
+ end
+
+ it "redirects to the sign-in page" do
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context "POST git-upload-pack" do
+ it "fails to find a route" do
+ expect { clone_post(project_path) }.to raise_error(ActionController::RoutingError)
+ end
+ end
+
+ context "POST git-receive-pack" do
+ it "fails to find a route" do
+ expect { push_post(project_path) }.to raise_error(ActionController::RoutingError)
+ end
+ end
+ end
+
describe "User with no identities" do
let(:user) { create(:user) }
@@ -143,6 +207,10 @@ describe 'Git HTTP requests' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
+
+ it_behaves_like 'project path without .git suffix' do
+ let(:project_path) { "#{user.namespace.path}/project.git-project" }
+ end
end
end
@@ -481,14 +549,14 @@ describe 'Git HTTP requests' do
it 'rejects pulls with personal access token error message' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
it 'rejects the push attempt with personal access token error message' do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
end
@@ -498,6 +566,47 @@ describe 'Git HTTP requests' do
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
+
+ it 'rejects the push attempt for read_repository scope' do
+ read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
+
+ upload(path, user: user.username, password: read_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response.body).to include('You are not allowed to upload code')
+ end
+ end
+
+ it 'accepts the push attempt for write_repository scope' do
+ write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
+
+ upload(path, user: user.username, password: write_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'accepts the pull attempt for read_repository scope' do
+ read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
+
+ download(path, user: user.username, password: read_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'accepts the pull attempt for api scope' do
+ read_access_token = create(:personal_access_token, user: user, scopes: [:api])
+
+ download(path, user: user.username, password: read_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'accepts the push attempt for api scope' do
+ write_access_token = create(:personal_access_token, user: user, scopes: [:api])
+
+ upload(path, user: user.username, password: write_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
end
@@ -509,14 +618,14 @@ describe 'Git HTTP requests' do
it 'rejects pulls with personal access token error message' do
download(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
it 'rejects pushes with personal access token error message' do
upload(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
@@ -530,7 +639,7 @@ describe 'Git HTTP requests' do
it 'does not display the personal access token error message' do
upload(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP')
+ expect(response.body).not_to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
end
@@ -706,70 +815,8 @@ describe 'Git HTTP requests' do
end
end
- context "when the project path doesn't end in .git" do
- let(:project) { create(:project, :repository, :public, path: 'project.git-project') }
-
- context "GET info/refs" do
- let(:path) { "/#{project.full_path}/info/refs" }
-
- context "when no params are added" do
- before do
- get path
- end
-
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.full_path}.git/info/refs")
- end
- end
-
- context "when the upload-pack service is requested" do
- let(:params) { { service: 'git-upload-pack' } }
-
- before do
- get path, params: params
- end
-
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}")
- end
- end
-
- context "when the receive-pack service is requested" do
- let(:params) { { service: 'git-receive-pack' } }
-
- before do
- get path, params: params
- end
-
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}")
- end
- end
-
- context "when the params are anything else" do
- let(:params) { { service: 'git-implode-pack' } }
-
- before do
- get path, params: params
- end
-
- it "redirects to the sign-in page" do
- expect(response).to redirect_to(new_user_session_path)
- end
- end
- end
-
- context "POST git-upload-pack" do
- it "fails to find a route" do
- expect { clone_post(project.full_path) }.to raise_error(ActionController::RoutingError)
- end
- end
-
- context "POST git-receive-pack" do
- it "fails to find a route" do
- expect { push_post(project.full_path) }.to raise_error(ActionController::RoutingError)
- end
- end
+ it_behaves_like 'project path without .git suffix' do
+ let(:project_path) { create(:project, :repository, :public, path: 'project.git-project').full_path }
end
context "retrieving an info/refs file" do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 4bb3b848e17..bba473f1c20 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -142,7 +142,7 @@ describe JwtController do
end
it 'allows read access' do
- expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_authentication_abilities)
+ expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_only_authentication_abilities)
get '/jwt/auth', params: parameters
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 2a455523e2c..86e41cbdf00 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -187,7 +187,7 @@ describe 'OpenID Connect requests' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['issuer']).to eq('http://localhost')
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
- expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid profile email])
+ expect(json_response['scopes_supported']).to eq(%w[api read_user read_repository write_repository sudo openid profile email])
end
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 49021f5d1b7..89adbc77a7f 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -182,6 +182,17 @@ describe 'Rack Attack global throttles' do
end
end
end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect(Gitlab::AuthLogger).to receive(:error).once
+
+ get url_that_does_not_require_authentication
+ end
end
context 'when the throttle is disabled' do
@@ -251,8 +262,8 @@ describe 'Rack Attack global throttles' do
let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
context 'with the token in the query string' do
- let(:get_args) { [rss_url(user), nil] }
- let(:other_user_get_args) { [rss_url(other_user), nil] }
+ let(:get_args) { [rss_url(user), params: nil] }
+ let(:other_user_get_args) { [rss_url(other_user), params: nil] }
it_behaves_like 'rate-limited token-authenticated requests'
end
@@ -327,6 +338,17 @@ describe 'Rack Attack global throttles' do
expect_rejection { get url_that_requires_authentication }
end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect(Gitlab::AuthLogger).to receive(:error).once
+
+ get url_that_requires_authentication
+ end
end
context 'when the throttle is disabled' do
diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
index 5fde4bd885b..3c48ead4ff2 100644
--- a/spec/routing/api_routing_spec.rb
+++ b/spec/routing/api_routing_spec.rb
@@ -7,25 +7,17 @@ describe 'api', 'routing' do
end
it 'does not route to the GraphqlController' do
- expect(get('/api/graphql')).not_to route_to('graphql#execute')
- end
-
- it 'does not expose graphiql' do
- expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ expect(post('/api/graphql')).not_to route_to('graphql#execute')
end
end
- context 'when graphql is disabled' do
+ context 'when graphql is enabled' do
before do
stub_feature_flags(graphql: true)
end
it 'routes to the GraphqlController' do
- expect(get('/api/graphql')).not_to route_to('graphql#execute')
- end
-
- it 'exposes graphiql' do
- expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ expect(post('/api/graphql')).to route_to('graphql#execute')
end
end
end
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index 71788028cbf..00ca394a50b 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -17,6 +17,10 @@ describe "Groups", "routing" do
expect(get("/#{group_path}")).to route_to('groups#show', id: group_path)
end
+ it "to #details" do
+ expect(get("/groups/#{group_path}/-/details")).to route_to('groups#details', id: group_path)
+ end
+
it "to #activity" do
expect(get("/groups/#{group_path}/-/activity")).to route_to('groups#activity', id: group_path)
end
@@ -129,5 +133,19 @@ describe "Groups", "routing" do
let(:resource) { create(:group, parent: parent, path: 'activity') }
end
end
+
+ describe 'subgroup "boards"' do
+ it 'shows group show page' do
+ allow(Group).to receive(:find_by_full_path).with('gitlabhq/boards', any_args).and_return(true)
+
+ expect(get('/groups/gitlabhq/boards')).to route_to('groups#show', id: 'gitlabhq/boards')
+ end
+
+ it 'shows boards index page' do
+ allow(Group).to receive(:find_by_full_path).with('gitlabhq', any_args).and_return(true)
+
+ expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq')
+ end
+ end
end
end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 106f92082e4..3fdede7914d 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -174,3 +174,15 @@ describe Import::GitlabProjectsController, 'routing' do
expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new')
end
end
+
+# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new
+# import_phabricator POST /import/phabricator(.:format) import/phabricator#create
+describe Import::PhabricatorController, 'routing' do
+ it 'to #create' do
+ expect(post("/import/phabricator")).to route_to("import/phabricator#create")
+ end
+
+ it 'to #new' do
+ expect(get("/import/phabricator/new")).to route_to("import/phabricator#new")
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index a0d01fc8263..83775b1040e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -138,9 +138,11 @@ describe 'project routing' do
describe Projects::AutocompleteSourcesController, 'routing' do
[:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action|
it "to ##{action}" do
- expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get("/gitlab/gitlabhq/-/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/autocomplete_sources/labels", "/gitlab/gitlabhq/-/autocomplete_sources/labels"
end
# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
@@ -204,25 +206,27 @@ describe 'project routing' do
describe Projects::BranchesController, 'routing' do
it 'to #branches' do
- expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
- expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
- expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
- expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
- expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
- expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
- expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/-/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/-/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/branches", "/gitlab/gitlabhq/-/branches"
end
describe Projects::TagsController, 'routing' do
it 'to #tags' do
- expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
- expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
- expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
- expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
- expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
- expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
- expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/-/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/-/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
end
end
@@ -237,7 +241,10 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :new, :create, :edit, :update] }
let(:controller) { 'deploy_keys' }
+ let(:controller_path) { '/-/deploy_keys' }
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/deploy_keys", "/gitlab/gitlabhq/-/deploy_keys"
end
# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index
@@ -247,6 +254,7 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'protected_branches' }
+ let(:controller_path) { '/-/protected_branches' }
end
end
@@ -444,7 +452,10 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :update, :destroy] }
let(:controller) { 'project_members' }
+ let(:controller_path) { '/-/project_members' }
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/project_members", "/gitlab/gitlabhq/-/project_members"
end
# project_milestones GET /:project_id/milestones(.:format) milestones#index
@@ -459,18 +470,23 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:controller) { 'milestones' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
+ let(:controller_path) { '/-/milestones' }
end
it 'to #promote' do
- expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
+ expect(post('/gitlab/gitlabhq/-/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/milestones", "/gitlab/gitlabhq/-/milestones"
end
# project_labels GET /:project_id/labels(.:format) labels#index
describe Projects::LabelsController, 'routing' do
it 'to #index' do
- expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/labels", "/gitlab/gitlabhq/-/labels"
end
# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort
@@ -592,36 +608,44 @@ describe 'project routing' do
describe Projects::NetworkController, 'routing' do
it 'to #show' do
- expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
- expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
- expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ expect(get('/gitlab/gitlabhq/-/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ expect(get('/gitlab/gitlabhq/-/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+ expect(get('/gitlab/gitlabhq/-/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/network/master", "/gitlab/gitlabhq/-/network/master"
end
describe Projects::GraphsController, 'routing' do
it 'to #show' do
- expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
- expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
- expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ expect(get('/gitlab/gitlabhq/-/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ expect(get('/gitlab/gitlabhq/-/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+ expect(get('/gitlab/gitlabhq/-/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/graphs/master", "/gitlab/gitlabhq/-/graphs/master"
end
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/-/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/forks", "/gitlab/gitlabhq/-/forks"
end
# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy
describe Projects::AvatarsController, 'routing' do
it 'to #destroy' do
- expect(delete('/gitlab/gitlabhq/avatar')).to route_to(
+ expect(delete('/gitlab/gitlabhq/-/avatar')).to route_to(
'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/avatar", "/gitlab/gitlabhq/-/avatar"
end
describe Projects::PagesDomainsController, 'routing' do
@@ -661,4 +685,12 @@ describe 'project routing' do
end
end
end
+
+ describe Projects::Settings::RepositoryController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/-/settings/repository')).to route_to('projects/settings/repository#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/settings/repository", "/gitlab/gitlabhq/-/settings/repository"
+ end
end
diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb
new file mode 100644
index 00000000000..6a041ffdd6c
--- /dev/null
+++ b/spec/routing/uploads_routing_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Uploads', 'routing' do
+ it 'allows creating uploads for personal snippets' do
+ expect(post('/uploads/personal_snippet?id=1')).to route_to(
+ controller: 'uploads',
+ action: 'create',
+ model: 'personal_snippet',
+ id: '1'
+ )
+ end
+
+ it 'does not allow creating uploads for other models' do
+ UploadsController::MODEL_CLASSES.keys.compact.each do |model|
+ next if model == 'personal_snippet'
+
+ expect(post("/uploads/#{model}?id=1")).not_to be_routable
+ end
+ end
+end
diff --git a/spec/rubocop/cop/active_record_association_reload_spec.rb b/spec/rubocop/cop/active_record_association_reload_spec.rb
new file mode 100644
index 00000000000..69eb16a54d2
--- /dev/null
+++ b/spec/rubocop/cop/active_record_association_reload_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require_relative '../../../rubocop/cop/active_record_association_reload'
+
+describe RuboCop::Cop::ActiveRecordAssociationReload do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when using ActiveRecord::Base' do
+ it 'registers an offense on reload usage' do
+ expect_offense(<<~PATTERN.strip_indent)
+ users = User.all
+ users.reload
+ ^^^^^^ Use reset instead of reload. For more details check the https://gitlab.com/gitlab-org/gitlab-ce/issues/60218.
+ PATTERN
+ end
+
+ it 'does not register an offense on reset usage' do
+ expect_no_offenses(<<~PATTERN.strip_indent)
+ users = User.all
+ users.reset
+ PATTERN
+ end
+ end
+
+ context 'when using ActiveRecord::Relation' do
+ it 'registers an offense on reload usage' do
+ expect_offense(<<~PATTERN.strip_indent)
+ user = User.new
+ user.reload
+ ^^^^^^ Use reset instead of reload. For more details check the https://gitlab.com/gitlab-org/gitlab-ce/issues/60218.
+ PATTERN
+ end
+
+ it 'does not register an offense on reset usage' do
+ expect_no_offenses(<<~PATTERN.strip_indent)
+ user = User.new
+ user.reset
+ PATTERN
+ end
+ end
+
+ context 'when using on self' do
+ it 'registers an offense on reload usage' do
+ expect_offense(<<~PATTERN.strip_indent)
+ reload
+ ^^^^^^ Use reset instead of reload. For more details check the https://gitlab.com/gitlab-org/gitlab-ce/issues/60218.
+ PATTERN
+ end
+
+ it 'does not register an offense on reset usage' do
+ expect_no_offenses(<<~PATTERN.strip_indent)
+ reset
+ PATTERN
+ end
+ end
+end
diff --git a/spec/rubocop/cop/code_reuse/active_record_spec.rb b/spec/rubocop/cop/code_reuse/active_record_spec.rb
index a30fc52d26f..8f3a3690d88 100644
--- a/spec/rubocop/cop/code_reuse/active_record_spec.rb
+++ b/spec/rubocop/cop/code_reuse/active_record_spec.rb
@@ -14,7 +14,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
expect_offense(<<~SOURCE)
def foo
User.where
- ^^^^^ This method can only be used inside an ActiveRecord model
+ ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653
end
SOURCE
end
@@ -23,7 +23,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
expect_offense(<<~SOURCE)
def foo
User.where(id: 10)
- ^^^^^ This method can only be used inside an ActiveRecord model
+ ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653
end
SOURCE
end
@@ -40,7 +40,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
expect_offense(<<~SOURCE)
def foo
project.group(:name)
- ^^^^^ This method can only be used inside an ActiveRecord model
+ ^^^^^ This method can only be used inside an ActiveRecord model: https://gitlab.com/gitlab-org/gitlab-ce/issues/49653
end
SOURCE
end
diff --git a/spec/rubocop/cop/include_action_view_context_spec.rb b/spec/rubocop/cop/include_action_view_context_spec.rb
new file mode 100644
index 00000000000..c888555b54f
--- /dev/null
+++ b/spec/rubocop/cop/include_action_view_context_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/include_action_view_context'
+
+describe RuboCop::Cop::IncludeActionViewContext do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when `ActionView::Context` is included' do
+ let(:source) { 'include ActionView::Context' }
+ let(:correct_source) { 'include ::Gitlab::ActionViewOutput::Context' }
+
+ it 'registers an offense' do
+ inspect_source(source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['ActionView::Context'])
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(source)
+
+ expect(autocorrected).to eq(correct_source)
+ end
+ end
+
+ context 'when `ActionView::Context` is not included' do
+ it 'registers no offense' do
+ inspect_source('include Context')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 1c8ab0ad5d2..cba01400d85 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -93,4 +93,22 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'
end
+
+ context 'EE migrations' do
+ let(:spec_filepath) { tmp_rails_root.join('ee', 'spec', 'migrations', 'my_super_migration_spec.rb') }
+
+ context 'in a migration' do
+ let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+
+ context 'in a post migration' do
+ let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+ end
end
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index c5beb40f9fd..ef20d9a1f26 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rubocop'
@@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter, 'search_field_tag :filter'
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
element :groups_filter_placeholder, /Search by name/
- ^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
+ ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
end
RUBY
end
@@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter_placeholder
end
RUBY
+
+ expect_no_offenses(<<-RUBY)
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter, required: true
+ element :groups_filter_placeholder, required: false
+ end
+ RUBY
end
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index be6aa7c65c3..5b05c2f2ef3 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -14,11 +14,41 @@ describe AnalyticsStageSerializer do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
- it 'it generates payload for single object' do
+ it 'generates payload for single object' do
expect(subject).to be_kind_of Hash
end
it 'contains important elements of AnalyticsStage' do
expect(subject).to include(:title, :description, :value)
end
+
+ context 'when median is equal 0' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0)
+ end
+
+ it 'sets the value to nil' do
+ expect(subject.fetch(:value)).to be_nil
+ end
+ end
+
+ context 'when median is below 1' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12)
+ end
+
+ it 'sets the value to equal to median' do
+ expect(subject.fetch(:value)).to eq('less than a minute')
+ end
+ end
+
+ context 'when median is above 1' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12)
+ end
+
+ it 'sets the value to equal to median' do
+ expect(subject.fetch(:value)).to eq('1 minute')
+ end
+ end
end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
index 236c244b402..8fa0574bfd6 100644
--- a/spec/serializers/analytics_summary_serializer_spec.rb
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -18,7 +18,7 @@ describe AnalyticsSummarySerializer do
.to receive(:value).and_return(1.12)
end
- it 'it generates payload for single object' do
+ it 'generates payload for single object' do
expect(subject).to be_kind_of Hash
end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index f6bd6e9ede4..d922e8246c7 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -112,5 +112,48 @@ describe BuildDetailsEntity do
expect(subject['merge_request_path']).to be_nil
end
end
+
+ context 'when the build has failed' do
+ let(:build) { create(:ci_build, :created) }
+
+ before do
+ build.drop!(:unmet_prerequisites)
+ end
+
+ it { is_expected.to include(failure_reason: 'unmet_prerequisites') }
+ end
+
+ context 'when a build has environment with latest deployment' do
+ let(:build) do
+ create(:ci_build, :running, environment: environment.name, pipeline: pipeline)
+ end
+
+ let(:environment) do
+ create(:environment, project: project, name: 'staging', state: :available)
+ end
+
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ it 'does not serialize latest deployment commit and associated builds' do
+ response = subject.with_indifferent_access
+
+ response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment|
+ expect(deployment).not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+ end
+ end
+
+ context 'when the build has reports' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+
+ it 'exposes the report artifacts' do
+ expect(subject[:reports].count).to eq(1)
+ expect(subject[:reports].first[:file_type]).to eq('codequality')
+ end
+ end
end
end
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index 7e151c3744e..f38a18fcf59 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -21,6 +21,10 @@ describe ClusterApplicationEntity do
expect(subject[:status_reason]).to be_nil
end
+ it 'has can_uninstall' do
+ expect(subject[:can_uninstall]).to be_falsey
+ end
+
context 'non-helm application' do
let(:application) { build(:clusters_applications_runner, version: '0.0.0') }
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 894fd7a0a12..76ad2aee5c5 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -10,6 +10,7 @@ describe DeploymentEntity do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:entity) { described_class.new(deployment, request: request) }
+
subject { entity.as_json }
before do
@@ -47,6 +48,16 @@ describe DeploymentEntity do
expect(subject[:manual_actions]).not_to be_present
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize manual actions details' do
+ expect(subject.with_indifferent_access).not_to include(:manual_actions)
+ end
+ end
end
describe 'scheduled_actions' do
@@ -69,5 +80,35 @@ describe DeploymentEntity do
expect(subject[:scheduled_actions]).to be_empty
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize scheduled actions details' do
+ expect(subject.with_indifferent_access).not_to include(:scheduled_actions)
+ end
+ end
+ end
+
+ context 'when deployment details serialization was disabled' do
+ include Gitlab::Routing
+
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize deployment details' do
+ expect(subject.with_indifferent_access)
+ .not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+
+ it 'only exposes deployable name and path' do
+ project_job_path(project, deployment.deployable).tap do |path|
+ expect(subject.fetch(:deployable))
+ .to eq(name: 'test', build_path: path)
+ end
+ end
end
end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index 791b64dc356..c2312734042 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -54,7 +54,7 @@ describe EnvironmentEntity do
projects: [project])
end
- it 'should include cluster_type' do
+ it 'includes cluster_type' do
expect(subject).to include(:cluster_type)
expect(subject[:cluster_type]).to eq('project_type')
end
@@ -65,7 +65,7 @@ describe EnvironmentEntity do
create(:kubernetes_service, project: project)
end
- it 'should not include cluster_type' do
+ it 'does not include cluster_type' do
expect(subject).not_to include(:cluster_type)
end
end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index d02b4c554b1..b58d95ccb43 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe GroupChildEntity do
+ include ExternalAuthorizationServiceHelpers
include Gitlab::Routing.url_helpers
let(:user) { create(:user) }
@@ -109,4 +110,22 @@ describe GroupChildEntity do
it_behaves_like 'group child json'
end
+
+ describe 'for a project with external authorization enabled' do
+ let(:object) do
+ create(:project, :with_avatar,
+ description: 'Awesomeness')
+ end
+
+ before do
+ enable_external_authorization_service_check
+ object.add_maintainer(user)
+ end
+
+ it 'does not hit the external authorization service' do
+ expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
+
+ expect(json[:can_edit]).to eq(false)
+ end
+ end
end
diff --git a/spec/serializers/job_artifact_report_entity_spec.rb b/spec/serializers/job_artifact_report_entity_spec.rb
new file mode 100644
index 00000000000..eef5c16d0fb
--- /dev/null
+++ b/spec/serializers/job_artifact_report_entity_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe JobArtifactReportEntity do
+ let(:report) { create(:ci_job_artifact, :codequality) }
+ let(:entity) { described_class.new(report, request: double) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'exposes file_type' do
+ expect(subject[:file_type]).to eq(report.file_type)
+ end
+
+ it 'exposes file_format' do
+ expect(subject[:file_format]).to eq(report.file_format)
+ end
+
+ it 'exposes size' do
+ expect(subject[:size]).to eq(report.size)
+ end
+
+ it 'exposes download path' do
+ expect(subject[:download_path]).to include("jobs/#{report.job.id}/artifacts/download")
+ end
+ end
+end
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 851b41a7f7e..8de61d4d466 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -154,15 +154,15 @@ describe JobEntity do
expect(subject[:status][:label]).to eq('failed')
end
- it 'should indicate the failure reason on tooltip' do
+ it 'indicates the failure reason on tooltip' do
expect(subject[:status][:tooltip]).to eq('failed - (API failure)')
end
- it 'should include a callout message with a verbose output' do
+ it 'includes a callout message with a verbose output' do
expect(subject[:callout_message]).to eq('There has been an API failure, please try again')
end
- it 'should state that it is not recoverable' do
+ it 'states that it is not recoverable' do
expect(subject[:recoverable]).to be_truthy
end
end
@@ -178,15 +178,15 @@ describe JobEntity do
expect(subject[:status][:label]).to eq('failed (allowed to fail)')
end
- it 'should indicate the failure reason on tooltip' do
+ it 'indicates the failure reason on tooltip' do
expect(subject[:status][:tooltip]).to eq('failed - (API failure) (allowed to fail)')
end
- it 'should include a callout message with a verbose output' do
+ it 'includes a callout message with a verbose output' do
expect(subject[:callout_message]).to eq('There has been an API failure, please try again')
end
- it 'should state that it is not recoverable' do
+ it 'states that it is not recoverable' do
expect(subject[:recoverable]).to be_truthy
end
end
@@ -194,7 +194,7 @@ describe JobEntity do
context 'when the job failed with a script failure' do
let(:job) { create(:ci_build, :failed, :script_failure) }
- it 'should not include callout message or recoverable keys' do
+ it 'does not include callout message or recoverable keys' do
expect(subject).not_to include('callout_message')
expect(subject).not_to include('recoverable')
end
@@ -203,7 +203,7 @@ describe JobEntity do
context 'when job failed and is recoverable' do
let(:job) { create(:ci_build, :api_failure) }
- it 'should state it is recoverable' do
+ it 'states it is recoverable' do
expect(subject[:recoverable]).to be_truthy
end
end
@@ -211,7 +211,7 @@ describe JobEntity do
context 'when job passed' do
let(:job) { create(:ci_build, :success) }
- it 'should not include callout message or recoverable keys' do
+ it 'does not include callout message or recoverable keys' do
expect(subject).not_to include('callout_message')
expect(subject).not_to include('recoverable')
end
diff --git a/spec/serializers/merge_request_for_pipeline_entity_spec.rb b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
new file mode 100644
index 00000000000..e49b45bc7d7
--- /dev/null
+++ b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe MergeRequestForPipelineEntity do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:request) { EntityRequest.new(project: project) }
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
+ let(:presenter) { MergeRequestPresenter.new(merge_request, current_user: user) }
+
+ let(:entity) do
+ described_class.new(presenter, request: request)
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'as json' do
+ subject { entity.as_json }
+
+ it 'exposes needed attributes' do
+ expect(subject).to include(
+ :iid, :path, :title,
+ :source_branch, :source_branch_path,
+ :target_branch, :target_branch_path
+ )
+ end
+ end
+end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 4dbd79f2fc0..a27c22191f4 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -13,6 +13,10 @@ describe MergeRequestWidgetEntity do
described_class.new(resource, request: request).as_json
end
+ it 'has the latest sha of the target branch' do
+ is_expected.to include(:target_branch_sha)
+ end
+
describe 'source_project_full_path' do
it 'includes the full path of the source project' do
expect(subject[:source_project_full_path]).to be_present
@@ -279,13 +283,64 @@ describe MergeRequestWidgetEntity do
end
describe 'commits_without_merge_commits' do
- it 'should not include merge commits' do
- # Mock all but the first 5 commits to be merge commits
- resource.commits.each_with_index do |commit, i|
- expect(commit).to receive(:merge_commit?).at_least(:once).and_return(i > 4)
+ def find_matching_commit(short_id)
+ resource.commits.find { |c| c.short_id == short_id }
+ end
+
+ it 'does not include merge commits' do
+ commits_in_widget = subject[:commits_without_merge_commits]
+
+ expect(commits_in_widget.length).to be < resource.commits.length
+ expect(commits_in_widget.length).to eq(resource.commits.without_merge_commits.length)
+ commits_in_widget.each do |c|
+ expect(find_matching_commit(c[:short_id]).merge_commit?).to eq(false)
end
+ end
+ end
- expect(subject[:commits_without_merge_commits].size).to eq(5)
+ describe 'auto merge' do
+ context 'when auto merge is enabled' do
+ let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'returns auto merge related information' do
+ expect(subject[:auto_merge_enabled]).to be_truthy
+ expect(subject[:auto_merge_strategy]).to eq('merge_when_pipeline_succeeds')
+ end
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:resource) { create(:merge_request) }
+
+ it 'returns auto merge related information' do
+ expect(subject[:auto_merge_enabled]).to be_falsy
+ expect(subject[:auto_merge_strategy]).to be_nil
+ end
+ end
+
+ context 'when head pipeline is running' do
+ before do
+ create(:ci_pipeline, :running, project: project,
+ ref: resource.source_branch,
+ sha: resource.diff_head_sha)
+ resource.update_head_pipeline
+ end
+
+ it 'returns available auto merge strategies' do
+ expect(subject[:available_auto_merge_strategies]).to eq(%w[merge_when_pipeline_succeeds])
+ end
+ end
+
+ context 'when head pipeline is finished' do
+ before do
+ create(:ci_pipeline, :success, project: project,
+ ref: resource.source_branch,
+ sha: resource.diff_head_sha)
+ resource.update_head_pipeline
+ end
+
+ it 'returns available auto merge strategies' do
+ expect(subject[:available_auto_merge_strategies]).to be_empty
+ end
end
end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 774486dcb6d..6be612ec226 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -1,13 +1,18 @@
require 'spec_helper'
describe PipelineEntity do
+ include Gitlab::Routing
+
+ set(:project) { create(:project) }
set(:user) { create(:user) }
+ set(:project) { create(:project) }
let(:request) { double('request') }
before do
stub_not_protect_default_branch
allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
end
let(:entity) do
@@ -43,8 +48,8 @@ describe PipelineEntity do
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
- .to include :latest, :stuck, :auto_devops,
- :yaml_errors, :retryable, :cancelable, :merge_request
+ .to include :stuck, :auto_devops, :yaml_errors,
+ :retryable, :cancelable, :merge_request
end
end
@@ -59,6 +64,12 @@ describe PipelineEntity do
create(:ci_build, :failed, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to retry pipeline' do
before do
project.add_developer(user)
@@ -87,6 +98,12 @@ describe PipelineEntity do
create(:ci_build, :pending, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to cancel pipeline' do
before do
project.add_developer(user)
@@ -128,5 +145,72 @@ describe PipelineEntity do
.to eq 'CI/CD YAML configuration error!'
end
end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:project) { merge_request.target_project }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+
+ it 'makes detached flag true' do
+ expect(subject[:flags][:detached_merge_request_pipeline]).to be_truthy
+ end
+
+ it 'does not expose source sha and target sha' do
+ expect(subject[:source_sha]).to be_nil
+ expect(subject[:target_sha]).to be_nil
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'has merge request information' do
+ expect(subject[:merge_request][:iid]).to eq(merge_request.iid)
+
+ expect(project_merge_request_path(project, merge_request))
+ .to include(subject[:merge_request][:path])
+
+ expect(subject[:merge_request][:title]).to eq(merge_request.title)
+
+ expect(subject[:merge_request][:source_branch])
+ .to eq(merge_request.source_branch)
+
+ expect(project_commits_path(project, merge_request.source_branch))
+ .to include(subject[:merge_request][:source_branch_path])
+
+ expect(subject[:merge_request][:target_branch])
+ .to eq(merge_request.target_branch)
+
+ expect(project_commits_path(project, merge_request.target_branch))
+ .to include(subject[:merge_request][:target_branch_path])
+ end
+ end
+
+ context 'when user is an external user' do
+ it 'has no merge request information' do
+ expect(subject[:merge_request]).to be_nil
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline, merge_sha: 'abc') }
+ let(:project) { merge_request.target_project }
+ let(:pipeline) { merge_request.pipelines_for_merge_request.first }
+
+ it 'makes detached flag false' do
+ expect(subject[:flags][:detached_merge_request_pipeline]).to be_falsy
+ end
+
+ it 'makes atached flag true' do
+ expect(subject[:flags][:merge_request_pipeline]).to be_truthy
+ end
+
+ it 'exposes source sha and target sha' do
+ expect(subject[:source_sha]).to be_present
+ expect(subject[:target_sha]).to be_present
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 2bdcb2a45f6..54e6abc2d3a 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -5,7 +5,7 @@ describe PipelineSerializer do
set(:user) { create(:user) }
let(:serializer) do
- described_class.new(current_user: user)
+ described_class.new(current_user: user, project: project)
end
before do
@@ -97,6 +97,44 @@ describe PipelineSerializer do
end
end
+ context 'when there are pipelines for merge requests' do
+ let(:resource) { Ci::Pipeline.all }
+
+ let!(:merge_request_1) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ target_project: project,
+ target_branch: 'master',
+ source_project: project,
+ source_branch: 'feature')
+ end
+
+ let!(:merge_request_2) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ target_project: project,
+ target_branch: 'master',
+ source_project: project,
+ source_branch: '2-mb-file')
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'includes merge requests information' do
+ expect(subject.all? { |entry| entry[:merge_request].present? }).to be_truthy
+ end
+
+ it 'preloads related merge requests', :postgresql do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+
+ expect(recorded.log)
+ .to include("SELECT \"merge_requests\".* FROM \"merge_requests\" " \
+ "WHERE \"merge_requests\".\"id\" IN (#{merge_request_1.id}, #{merge_request_2.id})")
+ end
+ end
+
describe 'number of queries when preloaded' do
subject { serializer.represent(resource, preload: true) }
let(:resource) { Ci::Pipeline.all }
@@ -118,8 +156,9 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
+ expected_queries = Gitlab.ee? ? 38 : 31
- expect(recorded.count).to be_within(2).of(31)
+ expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
end
end
@@ -138,7 +177,8 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-ce/issues/46368
- expect(recorded.count).to be_within(2).of(38)
+ expected_queries = Gitlab.ee? ? 44 : 38
+ expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
end
end
diff --git a/spec/serializers/provider_repo_entity_spec.rb b/spec/serializers/provider_repo_entity_spec.rb
index b67115bab10..9a1160d16d5 100644
--- a/spec/serializers/provider_repo_entity_spec.rb
+++ b/spec/serializers/provider_repo_entity_spec.rb
@@ -13,7 +13,7 @@ describe ProviderRepoEntity do
describe '#as_json' do
subject { entity.as_json }
- it 'includes requried fields' do
+ it 'includes required fields' do
expect(subject[:id]).to eq(provider_repo[:id])
expect(subject[:full_name]).to eq(provider_repo[:full_name])
expect(subject[:owner_name]).to eq(provider_repo[:owner][:login])
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
index 2034c7891ef..6b1185d1283 100644
--- a/spec/serializers/stage_entity_spec.rb
+++ b/spec/serializers/stage_entity_spec.rb
@@ -48,6 +48,10 @@ describe StageEntity do
expect(subject[:title]).to eq 'test: passed'
end
+ it 'does not contain play_details info' do
+ expect(subject[:status][:action]).not_to be_present
+ end
+
context 'when the jobs should be grouped' do
let(:entity) { described_class.new(stage, request: request, grouped: true) }
@@ -66,5 +70,29 @@ describe StageEntity do
end
end
end
+
+ context 'with a skipped stage ' do
+ let(:stage) { create(:ci_stage_entity, status: 'skipped') }
+
+ it 'contains play_all_manual' do
+ expect(subject[:status][:action]).to be_present
+ end
+ end
+
+ context 'with a scheduled stage ' do
+ let(:stage) { create(:ci_stage_entity, status: 'scheduled') }
+
+ it 'contains play_all_manual' do
+ expect(subject[:status][:action]).to be_present
+ end
+ end
+
+ context 'with a manual stage ' do
+ let(:stage) { create(:ci_stage_entity, status: 'manual') }
+
+ it 'contains play_all_manual' do
+ expect(subject[:status][:action]).to be_present
+ end
+ end
end
end
diff --git a/spec/serializers/stage_serializer_spec.rb b/spec/serializers/stage_serializer_spec.rb
new file mode 100644
index 00000000000..aae17cfbcb9
--- /dev/null
+++ b/spec/serializers/stage_serializer_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe StageSerializer do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:resource) { create(:ci_stage_entity) }
+
+ let(:serializer) do
+ described_class.new(current_user: user, project: project)
+ end
+
+ subject { serializer.represent(resource) }
+
+ describe '#represent' do
+ context 'with a single entity' do
+ it 'serializes the stage object' do
+ expect(subject[:name]).to eq(resource.name)
+ end
+ end
+
+ context 'with an array of entities' do
+ let(:resource) { create_list(:ci_stage_entity, 2) }
+
+ it 'serializes the array of pipelines' do
+ expect(subject).not_to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
index 047571f161c..d282a7f9c7a 100644
--- a/spec/serializers/suggestion_entity_spec.rb
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -13,8 +13,7 @@ describe SuggestionEntity do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line,
- :to_line, :appliable, :applied, :from_content, :to_content)
+ expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user])
end
it 'exposes current user abilities' do
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index a55910f98bb..986c9feb07b 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -14,6 +14,7 @@ describe TestCaseEntity do
it 'contains correct test case details' do
expect(subject[:status]).to eq('success')
expect(subject[:name]).to eq('Test#sum when a is 1 and b is 3 returns summary')
+ expect(subject[:classname]).to eq('spec.test_spec')
expect(subject[:execution_time]).to eq(1.11)
end
end
@@ -24,6 +25,7 @@ describe TestCaseEntity do
it 'contains correct test case details' do
expect(subject[:status]).to eq('failed')
expect(subject[:name]).to eq('Test#sum when a is 2 and b is 2 returns summary')
+ expect(subject[:classname]).to eq('spec.test_spec')
expect(subject[:execution_time]).to eq(2.22)
end
end
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index 38a3f522504..b2a8da6c4c6 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AccessTokenValidationService do
diff --git a/spec/services/after_branch_delete_service_spec.rb b/spec/services/after_branch_delete_service_spec.rb
deleted file mode 100644
index bc9747d1413..00000000000
--- a/spec/services/after_branch_delete_service_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe AfterBranchDeleteService do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user) }
- let(:service) { described_class.new(project, user) }
-
- describe '#execute' do
- it 'stops environments attached to branch' do
- expect(service).to receive(:stop_environments)
-
- service.execute('feature')
- end
- end
-end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index a4a733eff77..a641828faa5 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationSettings::UpdateService do
+ include ExternalAuthorizationServiceHelpers
+
let(:application_settings) { create(:application_setting) }
let(:admin) { create(:user, :admin) }
let(:params) { {} }
@@ -143,4 +147,37 @@ describe ApplicationSettings::UpdateService do
end
end
end
+
+ context 'when external authorization is enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'does not save the settings with an error if the service denies access' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(admin, 'new-label') { false }
+
+ described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute
+
+ expect(application_settings.errors[:external_authorization_service_default_label]).to be_present
+ end
+
+ it 'saves the setting when the user has access to the label' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(admin, 'new-label') { true }
+
+ described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute
+
+ # Read the attribute directly to avoid the stub from
+ # `enable_external_authorization_service_check`
+ expect(application_settings[:external_authorization_service_default_label]).to eq('new-label')
+ end
+
+ it 'does not validate the label if it was not passed' do
+ expect(::Gitlab::ExternalAuthorization)
+ .not_to receive(:access_allowed?)
+
+ described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute
+ end
+ 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 8021bd338e0..4f4776bbb27 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Auth::ContainerRegistryAuthenticationService do
@@ -88,6 +90,12 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ shared_examples 'a deletable since registry 2.7' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['delete'] }
+ end
+ end
+
shared_examples 'a pullable' do
it_behaves_like 'an accessible' do
let(:actions) { ['pull'] }
@@ -184,6 +192,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow developer to delete images since registry 2.7' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ 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.add_reporter(current_user)
@@ -212,6 +233,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow reporter to delete images since registry 2.7' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ 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.add_reporter(current_user)
@@ -250,6 +284,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 since registry 2.7' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for public project' do
@@ -282,6 +329,15 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ 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
{ scopes: ['repository:invalid:push'] }
@@ -322,6 +378,15 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for external user' do
@@ -344,6 +409,16 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
end
end
@@ -371,6 +446,16 @@ describe Auth::ContainerRegistryAuthenticationService do
let(:project) { current_project }
end
end
+
+ context 'allow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'a deletable since registry 2.7' do
+ let(:project) { current_project }
+ end
+ end
end
context 'build authorized as user' do
@@ -419,6 +504,16 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ context 'disallow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ 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
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
new file mode 100644
index 00000000000..197fa16961d
--- /dev/null
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMerge::BaseService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:merge_request) { create(:merge_request) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ it 'sets properies to the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).to be_auto_merge_enabled
+ expect(merge_request.merge_user).to eq(user)
+ expect(merge_request.auto_merge_strategy).to eq('base')
+ end
+
+ it 'yields block' do
+ expect { |b| service.execute(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(:base)
+ end
+
+ context 'when merge parameters are given' do
+ let(:params) do
+ {
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18",
+ 'should_remove_source_branch' => false,
+ 'squash' => false,
+ 'squash_commit_message' => "Update README.md"
+ }
+ end
+
+ it 'sets merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
+ expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
+ expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
+ expect(merge_request.merge_params['squash']).to eq(false)
+ expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
+ end
+ end
+
+ context 'when strategy is merge when pipeline succeeds' do
+ let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) }
+
+ it 'sets the auto merge strategy' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS.to_sym)
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'removes properies from the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).not_to be_auto_merge_enabled
+ expect(merge_request.merge_user).to be_nil
+ expect(merge_request.auto_merge_strategy).to be_nil
+ end
+
+ it 'yields block' do
+ expect { |b| service.cancel(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns success status' do
+ expect(subject[:status]).to eq(:success)
+ end
+
+ context 'when merge params are set' do
+ before do
+ merge_request.update!(merge_params:
+ {
+ 'should_remove_source_branch' => false,
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'squash_commit_message' => "Update README.md",
+ 'auto_merge_strategy' => 'merge_when_pipeline_succeeds'
+ })
+ end
+
+ it 'removes merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['should_remove_source_branch']).to be_nil
+ expect(merge_request.merge_params['commit_message']).to be_nil
+ expect(merge_request.merge_params['squash_commit_message']).to be_nil
+ expect(merge_request.merge_params['auto_merge_strategy']).to be_nil
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns error status' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq("Can't cancel the automatic merge")
+ end
+ end
+ end
+end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
new file mode 100644
index 00000000000..a20bf8e17e4
--- /dev/null
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMerge::MergeWhenPipelineSucceedsService do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ let(:mr_merge_if_green_enabled) do
+ create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user,
+ source_branch: "master", target_branch: 'feature',
+ source_project: project, target_project: project, state: "opened")
+ end
+
+ let(:pipeline) do
+ create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch,
+ project: project)
+ end
+
+ let(:service) do
+ described_class.new(project, user, commit_message: 'Awesome message')
+ end
+
+ describe "#available_for?" do
+ subject { service.available_for?(mr_merge_if_green_enabled) }
+
+ let(:pipeline_status) { :running }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: mr_merge_if_green_enabled.source_branch,
+ sha: mr_merge_if_green_enabled.diff_head_sha,
+ project: mr_merge_if_green_enabled.source_project)
+ mr_merge_if_green_enabled.update_head_pipeline
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe "#execute" do
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project,
+ source_branch: "feature", target_branch: 'master')
+ end
+
+ context 'first time enabling' do
+ before do
+ allow(merge_request)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+
+ service.execute(merge_request)
+ end
+
+ it 'sets the params, merge_user, and flag' do
+ expect(merge_request).to be_valid
+ expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
+ expect(merge_request.merge_params).to include commit_message: 'Awesome message'
+ expect(merge_request.merge_user).to be user
+ end
+
+ it 'creates a system note' do
+ note = merge_request.notes.last
+ expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}}
+ end
+ end
+
+ context 'already approved' do
+ let(:service) { described_class.new(project, user, new_key: true) }
+ let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
+
+ before do
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+
+ allow(mr_merge_if_green_enabled).to receive(:mergeable?)
+ .and_return(true)
+
+ allow(pipeline).to receive(:success?).and_return(true)
+ end
+
+ it 'updates the merge params' do
+ expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
+
+ service.execute(mr_merge_if_green_enabled)
+ expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key)
+ end
+ end
+ end
+
+ describe "#process" do
+ let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch }
+ let(:merge_request_head) do
+ project.commit(mr_merge_if_green_enabled.source_branch).id
+ end
+
+ context 'when triggered by pipeline with valid ref and sha' do
+ let(:triggering_pipeline) do
+ create(:ci_pipeline, project: project, ref: merge_request_ref,
+ sha: merge_request_head, status: 'success',
+ head_pipeline_of: mr_merge_if_green_enabled)
+ end
+
+ it "merges all merge requests with merge when the pipeline succeeds enabled" do
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: triggering_pipeline, actual_head_pipeline: triggering_pipeline)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.process(mr_merge_if_green_enabled)
+ end
+ end
+
+ context 'when triggered by an old pipeline' do
+ let(:old_pipeline) do
+ create(:ci_pipeline, project: project, ref: merge_request_ref,
+ sha: '1234abcdef', status: 'success')
+ end
+
+ it 'does not merge request' do
+ expect(MergeWorker).not_to receive(:perform_async)
+ service.process(mr_merge_if_green_enabled)
+ end
+ end
+
+ context 'when triggered by pipeline from a different branch' do
+ let(:unrelated_pipeline) do
+ create(:ci_pipeline, project: project, ref: 'feature',
+ sha: merge_request_head, status: 'success')
+ end
+
+ it 'does not merge request' do
+ expect(MergeWorker).not_to receive(:perform_async)
+ service.process(mr_merge_if_green_enabled)
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:pipeline) do
+ create(:ci_pipeline, :success,
+ source: :merge_request_event,
+ ref: mr_merge_if_green_enabled.merge_ref_path,
+ merge_request: mr_merge_if_green_enabled,
+ merge_requests_as_head_pipeline: [mr_merge_if_green_enabled])
+ end
+
+ it 'merges the associated merge request' do
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.process(mr_merge_if_green_enabled)
+ end
+ end
+ end
+
+ describe "#cancel" do
+ before do
+ service.cancel(mr_merge_if_green_enabled)
+ end
+
+ it "resets all the pipeline succeeds params" do
+ expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey
+ expect(mr_merge_if_green_enabled.merge_params).to eq({})
+ expect(mr_merge_if_green_enabled.merge_user).to be nil
+ end
+
+ it 'Posts a system note' do
+ note = mr_merge_if_green_enabled.notes.last
+ expect(note.note).to include 'canceled the automatic merge'
+ end
+ end
+
+ describe 'pipeline integration' do
+ context 'when there are multiple stages in the pipeline' do
+ let(:ref) { mr_merge_if_green_enabled.source_branch }
+ let(:sha) { project.commit(ref).id }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: ref, sha: sha, project: project)
+ end
+
+ let!(:build) do
+ create(:ci_build, :created, pipeline: pipeline, ref: ref,
+ name: 'build', stage: 'build')
+ end
+
+ let!(:test) do
+ create(:ci_build, :created, pipeline: pipeline, ref: ref,
+ name: 'test', stage: 'test')
+ end
+
+ before do
+ # This behavior of MergeRequest: we instantiate a new object
+ #
+ allow_any_instance_of(MergeRequest)
+ .to receive(:head_pipeline)
+ .and_wrap_original do
+ Ci::Pipeline.find(pipeline.id)
+ end
+ end
+
+ it "doesn't merge if any of stages failed" do
+ expect(MergeWorker).not_to receive(:perform_async)
+
+ build.success
+ test.reload
+ test.drop
+ end
+
+ it 'merges when all stages succeeded' do
+ expect(MergeWorker).to receive(:perform_async)
+
+ build.success
+ test.reload
+ test.success
+ end
+ end
+ end
+end
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
new file mode 100644
index 00000000000..d0eefed3150
--- /dev/null
+++ b/spec/services/auto_merge_service_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMergeService do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '.all_strategies' do
+ subject { described_class.all_strategies }
+
+ it 'returns all strategies' do
+ is_expected.to eq(AutoMergeService::STRATEGIES)
+ end
+ end
+
+ describe '#available_strategies' do
+ subject { service.available_strategies(merge_request) }
+
+ let(:merge_request) { create(:merge_request) }
+ let(:pipeline_status) { :running }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ project: merge_request.source_project)
+
+ merge_request.update_head_pipeline
+ end
+
+ it 'returns available strategies' do
+ is_expected.to include('merge_when_pipeline_succeeds')
+ end
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it 'returns available strategies' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.get_service_class' do
+ subject { described_class.get_service_class(strategy) }
+
+ let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
+
+ it 'returns service instance' do
+ is_expected.to eq(AutoMerge::MergeWhenPipelineSucceedsService)
+ end
+
+ context 'when strategy is not present' do
+ let(:strategy) { }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute(merge_request, strategy) }
+
+ let(:merge_request) { create(:merge_request) }
+ let(:pipeline_status) { :running }
+ let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ project: merge_request.source_project)
+
+ merge_request.update_head_pipeline
+ end
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:execute).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#process' do
+ subject { service.process(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:process).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:cancel).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns error' do
+ expect(subject[:message]).to eq("Can't cancel the automatic merge")
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:http_status]).to eq(406)
+ end
+ end
+ end
+end
diff --git a/spec/services/base_count_service_spec.rb b/spec/services/base_count_service_spec.rb
index 090b2dcdd43..275bec9982d 100644
--- a/spec/services/base_count_service_spec.rb
+++ b/spec/services/base_count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BaseCountService, :use_clean_rails_memory_store_caching do
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index a715261cd6c..7d4fb04c6c0 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::CreateService do
diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb
index f0179e35652..33637419f83 100644
--- a/spec/services/boards/issues/create_service_spec.rb
+++ b/spec/services/boards/issues/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Issues::CreateService do
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index aaad29536af..40878e24cb4 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Issues::ListService do
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 6020f0771e5..16e2a2fba6b 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Issues::MoveService do
diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb
index 7518e9e9b75..c9d372ea166 100644
--- a/spec/services/boards/list_service_spec.rb
+++ b/spec/services/boards/list_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::ListService do
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 7d3f5f86deb..295ec2c8156 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Lists::CreateService do
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index 3c4eb6b3fc5..b936ef3837f 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Lists::DestroyService do
diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb
index 82dbd1ee744..77b42392470 100644
--- a/spec/services/boards/lists/generate_service_spec.rb
+++ b/spec/services/boards/lists/generate_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Lists::GenerateService do
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 24e04eed642..2ebfd295fa2 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Lists::ListService do
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index 16dfb2ae6af..f8fc70ef2d6 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Boards::Lists::MoveService do
diff --git a/spec/services/boards/visits/latest_service_spec.rb b/spec/services/boards/visits/latest_service_spec.rb
index e55d599e2cc..c8a0a5e4243 100644
--- a/spec/services/boards/visits/latest_service_spec.rb
+++ b/spec/services/boards/visits/latest_service_spec.rb
@@ -23,6 +23,12 @@ describe Boards::Visits::LatestService do
service.execute
end
+
+ it 'queries for last N visits' do
+ expect(BoardProjectRecentVisit).to receive(:latest).with(user, project, count: 5).once
+
+ described_class.new(project_board.parent, user, count: 5).execute
+ end
end
context 'when a group board' do
@@ -42,6 +48,12 @@ describe Boards::Visits::LatestService do
service.execute
end
+
+ it 'queries for last N visits' do
+ expect(BoardGroupRecentVisit).to receive(:latest).with(user, group, count: 5).once
+
+ described_class.new(group_board.parent, user, count: 5).execute
+ end
end
end
end
diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb
index d88b2504133..41cbac4e8e9 100644
--- a/spec/services/chat_names/authorize_user_service_spec.rb
+++ b/spec/services/chat_names/authorize_user_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatNames::AuthorizeUserService do
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index 5734b10109a..9d26f98cd56 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
index 8e9cb65f3bc..44a77c29086 100644
--- a/spec/services/ci/archive_trace_service_spec.rb
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ArchiveTraceService, '#execute' do
diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb
index a26c970a8f0..f5edd3a552d 100644
--- a/spec/services/ci/compare_test_reports_service_spec.rb
+++ b/spec/services/ci/compare_test_reports_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::CompareTestReportsService do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 8497e90bd8b..867692d4d64 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::CreatePipelineService do
@@ -12,6 +14,7 @@ describe Ci::CreatePipelineService do
end
describe '#execute' do
+ # rubocop:disable Metrics/ParameterLists
def execute_service(
source: :push,
after: project.commit.id,
@@ -20,17 +23,23 @@ describe Ci::CreatePipelineService do
trigger_request: nil,
variables_attributes: nil,
merge_request: nil,
- push_options: nil)
+ push_options: nil,
+ source_sha: nil,
+ target_sha: nil,
+ save_on_errors: true)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }],
variables_attributes: variables_attributes,
- push_options: push_options }
+ push_options: push_options,
+ source_sha: source_sha,
+ target_sha: target_sha }
described_class.new(project, user, params).execute(
- source, trigger_request: trigger_request, merge_request: merge_request)
+ source, save_on_errors: save_on_errors, trigger_request: trigger_request, merge_request: merge_request)
end
+ # rubocop:enable Metrics/ParameterLists
context 'valid params' do
let(:pipeline) { execute_service }
@@ -49,6 +58,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).to eq(project.ci_pipelines.last)
expect(pipeline).to have_attributes(user: user)
expect(pipeline).to have_attributes(status: 'pending')
+ expect(pipeline.iid).not_to be_nil
expect(pipeline.repository_source?).to be true
expect(pipeline.builds.first).to be_kind_of(Ci::Build)
end
@@ -300,6 +310,56 @@ describe Ci::CreatePipelineService do
it_behaves_like 'a failed pipeline'
end
+
+ context 'when config has ports' do
+ context 'in the main image' do
+ let(:ci_yaml) do
+ <<-EOS
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+
+ context 'in the job image' do
+ let(:ci_yaml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image:
+ name: ruby:2.2
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+
+ context 'in the service' do
+ let(:ci_yaml) do
+ <<-EOS
+ image: ruby:2.2
+
+ test:
+ script: rspec
+ image: ruby:2.2
+ services:
+ - name: test
+ ports:
+ - 80
+ EOS
+ end
+
+ it_behaves_like 'a failed pipeline'
+ end
+ end
end
context 'when commit contains a [ci skip] directive' do
@@ -362,8 +422,7 @@ describe Ci::CreatePipelineService do
context 'when push options contain ci.skip' do
let(:push_options) do
- ['ci.skip',
- 'another push option']
+ { 'ci' => { 'skip' => true } }
end
it 'creates a pipline in the skipped state' do
@@ -389,6 +448,43 @@ describe Ci::CreatePipelineService do
expect(Ci::Build.all).to be_empty
expect(Ci::Pipeline.count).to eq(0)
end
+
+ describe '#iid' do
+ let(:internal_id) do
+ InternalId.find_by(project_id: project.id, usage: :ci_pipelines)
+ end
+
+ before do
+ expect_any_instance_of(Ci::Pipeline).to receive(:ensure_project_iid!)
+ .and_call_original
+ end
+
+ context 'when ci_pipeline_rewind_iid is enabled' do
+ before do
+ stub_feature_flags(ci_pipeline_rewind_iid: true)
+ end
+
+ it 'rewinds iid' do
+ result = execute_service
+
+ expect(result).not_to be_persisted
+ expect(internal_id.last_value).to eq(0)
+ end
+ end
+
+ context 'when ci_pipeline_rewind_iid is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_rewind_iid: false)
+ end
+
+ it 'does not rewind iid' do
+ result = execute_service
+
+ expect(result).not_to be_persisted
+ expect(internal_id.last_value).to eq(1)
+ end
+ end
+ end
end
context 'with manual actions' do
@@ -677,9 +773,13 @@ describe Ci::CreatePipelineService do
end
end
- describe 'Merge request pipelines' do
+ describe 'Pipelines for merge requests' do
let(:pipeline) do
- execute_service(source: source, merge_request: merge_request, ref: ref_name)
+ execute_service(source: source,
+ merge_request: merge_request,
+ ref: ref_name,
+ source_sha: source_sha,
+ target_sha: target_sha)
end
before do
@@ -687,9 +787,11 @@ describe Ci::CreatePipelineService do
end
let(:ref_name) { 'refs/heads/feature' }
+ let(:source_sha) { project.commit(ref_name).id }
+ let(:target_sha) { nil }
context 'when source is merge request' do
- let(:source) { :merge_request }
+ let(:source) { :merge_request_event }
context "when config has merge_requests keywords" do
let(:config) do
@@ -715,18 +817,43 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: Gitlab::Git.ref_name(ref_name),
+ source_branch: 'feature',
target_project: project,
target_branch: 'master')
end
- it 'creates a merge request pipeline' do
+ let(:ref_name) { merge_request.ref_path }
+
+ it 'creates a detached merge request pipeline' do
expect(pipeline).to be_persisted
- expect(pipeline).to be_merge_request
+ expect(pipeline).to be_merge_request_event
expect(pipeline.merge_request).to eq(merge_request)
expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test])
end
+ it 'persists the specified source sha' do
+ expect(pipeline.source_sha).to eq(source_sha)
+ end
+
+ it 'does not persist target sha for detached merge request pipeline' do
+ expect(pipeline.target_sha).to be_nil
+ end
+
+ it 'schedules update for the head pipeline of the merge request' do
+ expect(UpdateHeadPipelineForMergeRequestWorker)
+ .to receive(:perform_async).with(merge_request.id)
+
+ pipeline
+ end
+
+ context 'when target sha is specified' do
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it 'persists the target sha' do
+ expect(pipeline.target_sha).to eq(target_sha)
+ end
+ end
+
context 'when ref is tag' do
let(:ref_name) { 'refs/tags/v1.1.0' }
@@ -740,15 +867,16 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: Gitlab::Git.ref_name(ref_name),
+ source_branch: 'feature',
target_project: target_project,
target_branch: 'master')
end
+ let(:ref_name) { 'refs/heads/feature' }
let!(:project) { fork_project(target_project, nil, repository: true) }
let!(:target_project) { create(:project, :repository) }
- it 'creates a merge request pipeline in the forked project' do
+ it 'creates a legacy detached merge request pipeline in the forked project' do
expect(pipeline).to be_persisted
expect(project.ci_pipelines).to eq([pipeline])
expect(target_project.ci_pipelines).to be_empty
@@ -766,7 +894,7 @@ describe Ci::CreatePipelineService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."])
end
@@ -776,7 +904,7 @@ describe Ci::CreatePipelineService do
context 'when merge request is not specified' do
let(:merge_request) { nil }
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:merge_request]).to eq(["can't be blank"])
end
@@ -810,7 +938,7 @@ describe Ci::CreatePipelineService do
target_branch: 'master')
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base])
@@ -821,7 +949,7 @@ describe Ci::CreatePipelineService do
context 'when merge request is not specified' do
let(:merge_request) { nil }
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base])
@@ -845,12 +973,12 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base])
@@ -876,12 +1004,12 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base])
@@ -905,12 +1033,12 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(pipeline).not_to be_persisted
expect(pipeline.errors[:base])
diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
index 80d82ba3ac9..fc5450ab33d 100644
--- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
+++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index d896f990470..bff2b3179fb 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe ::Ci::DestroyPipelineService do
- let(:project) { create(:project) }
- let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:project) { create(:project, :repository) }
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }
subject { described_class.new(project, user).execute(pipeline) }
@@ -17,6 +17,17 @@ describe ::Ci::DestroyPipelineService do
expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
+ it 'clears the cache', :use_clean_rails_memory_store_caching do
+ create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref)
+
+ expect(project.pipeline_status.has_status?).to be_truthy
+
+ subject
+
+ # Need to use find to avoid memoization
+ expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey
+ end
+
it 'does not log an audit event' do
expect { subject }.not_to change { SecurityEvent.count }
end
diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb
index d17e30763d7..43bbd2130a4 100644
--- a/spec/services/ci/ensure_stage_service_spec.rb
+++ b/spec/services/ci/ensure_stage_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::EnsureStageService, '#execute' do
diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb
new file mode 100644
index 00000000000..ff2d286465a
--- /dev/null
+++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::ExpirePipelineCacheService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { described_class.new }
+
+ describe '#execute' do
+ it 'invalidates Etag caching for project pipelines path' do
+ pipelines_path = "/#{project.full_path}/pipelines.json"
+ new_mr_pipelines_path = "/#{project.full_path}/merge_requests/new.json"
+ pipeline_path = "/#{project.full_path}/pipelines/#{pipeline.id}.json"
+
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipelines_path)
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(new_mr_pipelines_path)
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipeline_path)
+
+ subject.execute(pipeline)
+ end
+
+ it 'invalidates Etag caching for merge request pipelines if pipeline runs on any commit of that source branch' do
+ pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master')
+ merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
+ merge_request_pipelines_path = "/#{project.full_path}/merge_requests/#{merge_request.iid}/pipelines.json"
+
+ allow_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch)
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(merge_request_pipelines_path)
+
+ subject.execute(pipeline)
+ end
+
+ it 'updates the cached status for a project' do
+ expect(Gitlab::Cache::Ci::ProjectPipelineStatus).to receive(:update_for_pipeline)
+ .with(pipeline)
+
+ subject.execute(pipeline)
+ end
+
+ context 'destroyed pipeline' do
+ let(:project_with_repo) { create(:project, :repository) }
+ let!(:pipeline_with_commit) { create(:ci_pipeline, :success, project: project_with_repo, sha: project_with_repo.commit.id) }
+
+ it 'clears the cache', :use_clean_rails_memory_store_caching do
+ create(:commit_status, :success, pipeline: pipeline_with_commit, ref: pipeline_with_commit.ref)
+
+ # Sanity check
+ expect(project_with_repo.pipeline_status.has_status?).to be_truthy
+
+ subject.execute(pipeline_with_commit, delete: true)
+
+ pipeline_with_commit.destroy!
+
+ # Need to use find to avoid memoization
+ expect(Project.find(project_with_repo.id).pipeline_status.has_status?).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
index 28f2fa7903a..03c67c611fe 100644
--- a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
+++ b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do
diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb
new file mode 100644
index 00000000000..f2ac53cb25a
--- /dev/null
+++ b/spec/services/ci/pipeline_schedule_service_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PipelineScheduleService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ subject { service.execute(schedule) }
+
+ let(:schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+
+ it 'schedules next run' do
+ expect(schedule).to receive(:schedule_next_run!)
+
+ subject
+ end
+
+ it 'runs RunPipelineScheduleWorker' do
+ expect(RunPipelineScheduleWorker)
+ .to receive(:perform_async).with(schedule.id, schedule.owner.id)
+
+ subject
+ end
+ end
+end
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index f4ff818c479..76251b5b557 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineTriggerService do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index 330ec81e87d..634f865e2d8 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PlayBuildService, '#execute' do
diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb
new file mode 100644
index 00000000000..5d812745c7f
--- /dev/null
+++ b/spec/services/ci/play_manual_stage_service_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PlayManualStageService, '#execute' do
+ let(:current_user) { create(:user) }
+ let(:pipeline) { create(:ci_pipeline, user: current_user) }
+ let(:project) { pipeline.project }
+ let(:service) { described_class.new(project, current_user, pipeline: pipeline) }
+ let(:stage_status) { 'manual' }
+
+ let(:stage) do
+ create(:ci_stage_entity,
+ pipeline: pipeline,
+ project: project,
+ name: 'test')
+ end
+
+ before do
+ project.add_maintainer(current_user)
+ create_builds_for_stage(status: stage_status)
+ end
+
+ context 'when pipeline has manual builds' do
+ before do
+ service.execute(stage)
+ end
+
+ it 'starts manual builds from pipeline' do
+ expect(pipeline.builds.manual.count).to eq(0)
+ end
+
+ it 'updates manual builds' do
+ pipeline.builds.each do |build|
+ expect(build.user).to eq(current_user)
+ end
+ end
+ end
+
+ context 'when pipeline has no manual builds' do
+ let(:stage_status) { 'failed' }
+
+ before do
+ service.execute(stage)
+ end
+
+ it 'does not update the builds' do
+ expect(pipeline.builds.failed.count).to eq(3)
+ end
+ end
+
+ context 'when user does not have permission on a specific build' do
+ before do
+ allow_any_instance_of(Ci::Build).to receive(:play)
+ .and_raise(Gitlab::Access::AccessDeniedError)
+
+ service.execute(stage)
+ end
+
+ it 'logs the error' do
+ expect(Gitlab::AppLogger).to receive(:error)
+ .exactly(stage.builds.manual.count)
+
+ service.execute(stage)
+ end
+ end
+
+ def create_builds_for_stage(options)
+ options.merge!({
+ when: 'manual',
+ pipeline: pipeline,
+ stage: stage.name,
+ stage_id: stage.id,
+ user: pipeline.user
+ })
+
+ create_list(:ci_build, 3, options)
+ end
+end
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
new file mode 100644
index 00000000000..2d027f13e52
--- /dev/null
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PrepareBuildService do
+ describe '#execute' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ subject { described_class.new(build).execute }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return(prerequisites)
+ end
+
+ shared_examples 'build enqueueing' do
+ it 'enqueues the build' do
+ expect(build).to receive(:enqueue).once
+
+ subject
+ end
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:prerequisite) { double(complete!: true) }
+ let(:prerequisites) { [prerequisite] }
+
+ it 'completes each prerequisite' do
+ expect(prerequisites).to all(receive(:complete!))
+
+ subject
+ end
+
+ include_examples 'build enqueueing'
+
+ context 'prerequisites fail to complete' do
+ before do
+ allow(build).to receive(:enqueue).and_return(false)
+ end
+
+ it 'drops the build' do
+ expect(build).to receive(:drop).with(:unmet_prerequisites).once
+
+ subject
+ end
+ end
+
+ context 'prerequisites raise an error' do
+ before do
+ allow(prerequisite).to receive(:complete!).and_raise Kubeclient::HttpError.new(401, 'unauthorized', nil)
+ end
+
+ it 'drops the build and notifies Sentry' do
+ expect(build).to receive(:drop).with(:unmet_prerequisites).once
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
+ .with(instance_of(Kubeclient::HttpError), hash_including(extra: { build_id: build.id }))
+
+ subject
+ end
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:prerequisites) { [] }
+
+ include_examples 'build enqueueing'
+ end
+ end
+end
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 6674d89518e..cadb519ccee 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ProcessPipelineService, '#execute' do
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 20181387612..874945c8585 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
module Ci
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 87185891470..e9a26400723 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::RetryBuildService do
@@ -21,22 +23,23 @@ describe Ci::RetryBuildService do
REJECT_ACCESSORS =
%i[id status user token token_encrypted coverage trace runner
- artifacts_expire_at artifacts_file artifacts_metadata artifacts_size
+ artifacts_expire_at
created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_performance
- job_artifacts_codequality scheduled_at].freeze
+ job_artifacts_codequality job_artifacts_metrics scheduled_at].freeze
IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections
commit_id deployment erased_by_id project_id
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason
- artifacts_file_store artifacts_metadata_store
- metadata runner_session trace_chunks].freeze
+ sourced_pipelines artifacts_file_store artifacts_metadata_store
+ metadata runner_session trace_chunks
+ artifacts_file artifacts_metadata artifacts_size].freeze
shared_examples 'build duplication' do
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
@@ -95,7 +98,8 @@ describe Ci::RetryBuildService do
end
it 'has correct number of known attributes' do
- known_accessors = CLONE_ACCESSORS + REJECT_ACCESSORS + IGNORE_ACCESSORS
+ processed_accessors = CLONE_ACCESSORS + REJECT_ACCESSORS
+ known_accessors = processed_accessors + IGNORE_ACCESSORS
# :tag_list is a special case, this accessor does not exist
# in reflected associations, comes from `act_as_taggable` and
@@ -108,7 +112,8 @@ describe Ci::RetryBuildService do
current_accessors.uniq!
- expect(known_accessors).to contain_exactly(*current_accessors)
+ expect(current_accessors).to include(*processed_accessors)
+ expect(known_accessors).to include(*current_accessors)
end
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 75042b29bea..e42de3cd48f 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::RetryPipelineService, '#execute' do
diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb
index be2aad33ef4..ab8d9f4ba2e 100644
--- a/spec/services/ci/run_scheduled_build_service_spec.rb
+++ b/spec/services/ci/run_scheduled_build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::RunScheduledBuildService do
diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb
index cdd3d851f61..890fa5bc009 100644
--- a/spec/services/ci/stop_environments_service_spec.rb
+++ b/spec/services/ci/stop_environments_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::StopEnvironmentsService do
@@ -103,6 +105,82 @@ describe Ci::StopEnvironmentsService do
end
end
+ describe '#execute_for_merge_request' do
+ subject { service.execute_for_merge_request(merge_request) }
+
+ let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
+ let(:project) { merge_request.project }
+ let(:user) { create(:user) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let!(:review_job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) }
+ let!(:stop_review_job) { create(:ci_build, :stop_review_app, :manual, pipeline: pipeline, project: project) }
+
+ before do
+ review_job.deployment.success!
+ end
+
+ it 'has active environment at first' do
+ expect(pipeline.environments.first).to be_available
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'stops the active environment' do
+ subject
+
+ expect(pipeline.environments.first).to be_stopped
+ end
+ end
+
+ context 'when user is a reporter' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'does not stop the active environment' do
+ subject
+
+ expect(pipeline.environments.first).to be_available
+ end
+ end
+
+ context 'when pipeline is not associated with environments' do
+ let!(:job) { create(:ci_build, pipeline: pipeline, project: project) }
+
+ it 'does not raise exception' do
+ expect { subject }.not_to raise_exception
+ end
+ end
+
+ context 'when pipeline is not a pipeline for merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ it 'does not stop the active environment' do
+ subject
+
+ expect(pipeline.environments.first).to be_available
+ end
+ end
+ end
+
def expect_environment_stopped_on(branch)
expect_any_instance_of(Environment)
.to receive(:stop!)
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index ca0c6be5da6..4b869385128 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::UpdateBuildQueueService do
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
index 7cc04c92d27..2b07dad7248 100644
--- a/spec/services/ci/update_runner_service_spec.rb
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::UpdateRunnerService do
diff --git a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
index f3036fbcb0e..84bca76e69b 100644
--- a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
+++ b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Applications::CheckIngressIpAddressService do
@@ -6,9 +8,17 @@ describe Clusters::Applications::CheckIngressIpAddressService do
let(:application) { create(:clusters_applications_ingress, :installed) }
let(:service) { described_class.new(application) }
let(:kubeclient) { double(::Kubeclient::Client, get_service: kube_service) }
- let(:ingress) { [{ ip: '111.222.111.222' }] }
let(:lease_key) { "check_ingress_ip_address_service:#{application.id}" }
+ let(:ingress) do
+ [
+ {
+ ip: '111.222.111.222',
+ hostname: 'localhost.localdomain'
+ }
+ ]
+ end
+
let(:kube_service) do
::Kubeclient::Resource.new(
{
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 19446ce1cf8..a54bd85a11a 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
@@ -16,7 +18,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context "when phase is #{a_phase}" do
- context 'when not timeouted' do
+ context 'when not timed_out' do
it 'reschedule a new check' do
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
expect(service).not_to receive(:remove_installation_pod)
@@ -33,14 +35,22 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
end
- shared_examples 'error logging' do
+ shared_examples 'error handling' do
context 'when installation raises a Kubeclient::HttpError' do
let(:cluster) { create(:cluster, :provided_by_user, :project) }
+ let(:logger) { service.send(:logger) }
+ let(:error) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) }
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ expect(service).to receive(:installation_phase).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'Unauthorized' }
+ let(:error_code) { 401 }
end
it 'shows the response code from the error' do
@@ -49,12 +59,6 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
expect(application).to be_errored.or(be_update_errored)
expect(application.status_reason).to eq('Kubernetes error: 401')
end
-
- it 'should log error' do
- expect(service.send(:logger)).to receive(:error)
-
- service.execute
- end
end
end
@@ -66,7 +70,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when application is updating' do
let(:application) { create(:clusters_applications_helm, :updating) }
- include_examples 'error logging'
+ include_examples 'error handling'
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
@@ -109,7 +113,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context 'when timed out' do
- let(:application) { create(:clusters_applications_helm, :timeouted, :updating) }
+ let(:application) { create(:clusters_applications_helm, :timed_out, :updating) }
before do
expect(service).to receive(:installation_phase).once.and_return(phase)
@@ -127,7 +131,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context 'when application is installing' do
- include_examples 'error logging'
+ include_examples 'error handling'
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
@@ -170,7 +174,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
context 'when timed out' do
- let(:application) { create(:clusters_applications_helm, :timeouted) }
+ let(:application) { create(:clusters_applications_helm, :timed_out) }
before do
expect(service).to receive(:installation_phase).once.and_return(phase)
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
new file mode 100644
index 00000000000..9ab83d913f5
--- /dev/null
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::CheckUninstallProgressService do
+ RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
+
+ let(:application) { create(:clusters_applications_prometheus, :uninstalling) }
+ let(:service) { described_class.new(application) }
+ let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN }
+ let(:errors) { nil }
+ let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker }
+
+ before do
+ allow(service).to receive(:installation_errors).and_return(errors)
+ allow(service).to receive(:remove_installation_pod)
+ end
+
+ shared_examples 'a not yet terminated installation' do |a_phase|
+ let(:phase) { a_phase }
+
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ context "when phase is #{a_phase}" do
+ context 'when not timed_out' do
+ it 'reschedule a new check' do
+ expect(worker_class).to receive(:perform_in).once
+ expect(service).not_to receive(:remove_installation_pod)
+
+ expect do
+ service.execute
+
+ application.reload
+ end.not_to change(application, :status)
+
+ expect(application.status_reason).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when application is installing' do
+ RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
+
+ context 'when installation POD succeeded' do
+ let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ it 'removes the installation POD' do
+ expect(service).to receive(:remove_installation_pod).once
+
+ service.execute
+ end
+
+ it 'destroys the application' do
+ expect(worker_class).not_to receive(:perform_in)
+
+ service.execute
+
+ expect(application).to be_destroyed
+ end
+
+ context 'an error occurs while destroying' do
+ before do
+ expect(application).to receive(:destroy!).once.and_raise("destroy failed")
+ end
+
+ it 'still removes the installation POD' do
+ expect(service).to receive(:remove_installation_pod).once
+
+ service.execute
+ end
+
+ it 'makes the application uninstall_errored' do
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to eq('Application uninstalled but failed to destroy: destroy failed')
+ end
+ end
+ end
+
+ context 'when installation POD failed' do
+ let(:phase) { Gitlab::Kubernetes::Pod::FAILED }
+ let(:errors) { 'test installation failed' }
+
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to eq('Operation failed. Check pod logs for uninstall-prometheus for more details.')
+ end
+ end
+
+ context 'when timed out' do
+ let(:application) { create(:clusters_applications_prometheus, :timed_out, :uninstalling) }
+
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ it 'make the application errored' do
+ expect(worker_class).not_to receive(:perform_in)
+
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to eq('Operation timed out. Check pod logs for uninstall-prometheus for more details.')
+ end
+ end
+
+ context 'when installation raises a Kubeclient::HttpError' do
+ let(:cluster) { create(:cluster, :provided_by_user, :project) }
+ let(:logger) { service.send(:logger) }
+ let(:error) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) }
+
+ before do
+ application.update!(cluster: cluster)
+
+ expect(service).to receive(:installation_phase).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'Unauthorized' }
+ let(:error_code) { 401 }
+ end
+
+ it 'shows the response code from the error' do
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to eq('Kubernetes error: 401')
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 3f621ed5944..bb86a742f0e 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -26,12 +26,6 @@ describe Clusters::Applications::CreateService do
end.to change(cluster, :application_helm)
end
- it 'schedules an install via worker' do
- expect(ClusterInstallAppWorker).to receive(:perform_async).with('helm', anything).once
-
- subject
- end
-
context 'application already installed' do
let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) }
@@ -42,88 +36,101 @@ describe Clusters::Applications::CreateService do
end
it 'schedules an upgrade for the application' do
- expect(Clusters::Applications::ScheduleInstallationService).to receive(:new).with(application).and_call_original
+ expect(ClusterUpgradeAppWorker).to receive(:perform_async)
subject
end
end
- context 'cert manager application' do
- let(:params) do
- {
- application: 'cert_manager',
- email: 'test@example.com'
- }
- end
-
+ context 'known applications' do
before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ create(:clusters_applications_helm, :installed, cluster: cluster)
end
- it 'creates the application' do
- expect do
- subject
+ context 'cert manager application' do
+ let(:params) do
+ {
+ application: 'cert_manager',
+ email: 'test@example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_cert_manager)
- end
+ before do
+ expect_any_instance_of(Clusters::Applications::CertManager)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the email' do
- expect(subject.email).to eq('test@example.com')
- end
- end
+ it 'creates the application' do
+ expect do
+ subject
- context 'jupyter application' do
- let(:params) do
- {
- application: 'jupyter',
- hostname: 'example.com'
- }
- end
+ cluster.reload
+ end.to change(cluster, :application_cert_manager)
+ end
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ it 'sets the email' do
+ expect(subject.email).to eq('test@example.com')
+ end
end
- it 'creates the application' do
- expect do
- subject
+ context 'jupyter application' do
+ let(:params) do
+ {
+ application: 'jupyter',
+ hostname: 'example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_jupyter)
- end
+ before do
+ create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster)
+ expect_any_instance_of(Clusters::Applications::Jupyter)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the hostname' do
- expect(subject.hostname).to eq('example.com')
- end
+ it 'creates the application' do
+ expect do
+ subject
- it 'sets the oauth_application' do
- expect(subject.oauth_application).to be_present
- end
- end
+ cluster.reload
+ end.to change(cluster, :application_jupyter)
+ end
- context 'knative application' do
- let(:params) do
- {
- application: 'knative',
- hostname: 'example.com'
- }
- end
+ it 'sets the hostname' do
+ expect(subject.hostname).to eq('example.com')
+ end
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ it 'sets the oauth_application' do
+ expect(subject.oauth_application).to be_present
+ end
end
- it 'creates the application' do
- expect do
- subject
+ context 'knative application' do
+ let(:params) do
+ {
+ application: 'knative',
+ hostname: 'example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_knative)
- end
+ before do
+ expect_any_instance_of(Clusters::Applications::Knative)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the hostname' do
- expect(subject.hostname).to eq('example.com')
+ it 'creates the application' do
+ expect do
+ subject
+
+ cluster.reload
+ end.to change(cluster, :application_knative)
+ end
+
+ it 'sets the hostname' do
+ expect(subject.hostname).to eq('example.com')
+ end
end
end
@@ -140,19 +147,21 @@ describe Clusters::Applications::CreateService do
using RSpec::Parameterized::TableSyntax
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
- end
-
- where(:application, :association, :allowed) do
- 'helm' | :application_helm | true
- 'ingress' | :application_ingress | true
- 'runner' | :application_runner | false
- 'jupyter' | :application_jupyter | false
- 'prometheus' | :application_prometheus | false
+ where(:application, :association, :allowed, :pre_create_helm) do
+ 'helm' | :application_helm | true | false
+ 'ingress' | :application_ingress | true | true
+ 'runner' | :application_runner | true | true
+ 'prometheus' | :application_prometheus | true | true
+ 'jupyter' | :application_jupyter | false | true
end
with_them do
+ before do
+ klass = "Clusters::Applications::#{application.titleize}"
+ allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original
+ create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm
+ end
+
let(:params) { { application: application } }
it 'executes for each application' do
@@ -168,5 +177,68 @@ describe Clusters::Applications::CreateService do
end
end
end
+
+ context 'when application is installable' do
+ shared_examples 'installable applications' do
+ it 'makes the application scheduled' do
+ expect do
+ subject
+ end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1)
+ end
+
+ it 'schedules an install via worker' do
+ expect(ClusterInstallAppWorker)
+ .to receive(:perform_async)
+ .with(*worker_arguments)
+ .once
+
+ subject
+ end
+ end
+
+ context 'when application is associated with a cluster' do
+ let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) }
+ let(:worker_arguments) { [application.name, application.id] }
+
+ it_behaves_like 'installable applications'
+ end
+
+ context 'when application is not associated with a cluster' do
+ let(:worker_arguments) { [params[:application], kind_of(Numeric)] }
+
+ it_behaves_like 'installable applications'
+ end
+ end
+
+ context 'when installation is already in progress' do
+ let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) }
+
+ it 'raises an exception' do
+ expect { subject }
+ .to raise_exception(StateMachines::InvalidTransition)
+ .and not_change(application.class.with_status(:scheduled), :count)
+ end
+
+ it 'does not schedule a cluster worker' do
+ expect(ClusterInstallAppWorker).not_to receive(:perform_async)
+ end
+ end
+
+ context 'when application is installed' do
+ %i(installed updated).each do |status|
+ let(:application) { create(:clusters_applications_helm, status, cluster: cluster) }
+
+ it 'schedules an upgrade via worker' do
+ expect(ClusterUpgradeAppWorker)
+ .to receive(:perform_async)
+ .with(application.name, application.id)
+ .once
+
+ subject
+
+ expect(application.reload).to be_scheduled
+ end
+ end
+ end
end
end
diff --git a/spec/services/clusters/applications/destroy_service_spec.rb b/spec/services/clusters/applications/destroy_service_spec.rb
new file mode 100644
index 00000000000..8d9dc6a0f11
--- /dev/null
+++ b/spec/services/clusters/applications/destroy_service_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::DestroyService, '#execute' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:user) { create(:user) }
+ let(:params) { { application: 'prometheus' } }
+ let(:service) { described_class.new(cluster, user, params) }
+ let(:test_request) { double }
+ let(:worker_class) { Clusters::Applications::UninstallWorker }
+
+ subject { service.execute(test_request) }
+
+ before do
+ allow(worker_class).to receive(:perform_async)
+ end
+
+ context 'application is not installed' do
+ it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do
+ expect(worker_class).not_to receive(:perform_async)
+
+ expect { subject }
+ .to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError }
+ .and not_change { Clusters::Applications::Prometheus.count }
+ .and not_change { Clusters::Applications::Prometheus.with_status(:scheduled).count }
+ end
+ end
+
+ context 'application is installed' do
+ context 'application is schedulable' do
+ let!(:application) do
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ end
+
+ it 'makes application scheduled!' do
+ subject
+
+ expect(application.reload).to be_scheduled
+ end
+
+ it 'schedules UninstallWorker' do
+ expect(worker_class).to receive(:perform_async).with(application.name, application.id)
+
+ subject
+ end
+ end
+
+ context 'application is not schedulable' do
+ let!(:application) do
+ create(:clusters_applications_prometheus, :updating, cluster: cluster)
+ end
+
+ it 'raises StateMachines::InvalidTransition' do
+ expect(worker_class).not_to receive(:perform_async)
+
+ expect { subject }
+ .to raise_exception { StateMachines::InvalidTransition }
+ .and not_change { Clusters::Applications::Prometheus.with_status(:scheduled).count }
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 018d9822d3e..9e1ae5e8742 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Applications::InstallService do
@@ -39,87 +41,39 @@ describe Clusters::Applications::InstallService do
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
it 'make the application errored' do
service.execute
expect(application).to be_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- service.execute
- end
end
context 'a non kubernetes error happens' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
- let(:error) { StandardError.new("something bad happened") }
+ let(:error) { StandardError.new('something bad happened') }
before do
- expect(application).to receive(:make_installing!).once.and_raise(error)
+ expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
- it 'make the application errored' do
- expect(helm_client).not_to receive(:install)
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+ it 'make the application errored' do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq("Can't start installation process.")
- end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::InstallService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- service.execute
+ expect(application.status_reason).to eq('Failed to install.')
end
end
end
diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb
new file mode 100644
index 00000000000..3ebe0540837
--- /dev/null
+++ b/spec/services/clusters/applications/patch_service_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::PatchService do
+ describe '#execute' do
+ let(:application) { create(:clusters_applications_knative, :scheduled) }
+ let!(:update_command) { application.update_command }
+ let(:service) { described_class.new(application) }
+ let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
+
+ before do
+ allow(service).to receive(:update_command).and_return(update_command)
+ allow(service).to receive(:helm_api).and_return(helm_client)
+ end
+
+ context 'when there are no errors' do
+ before do
+ expect(helm_client).to receive(:update).with(update_command)
+ allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil)
+ end
+
+ it 'make the application updating' do
+ expect(application.cluster).not_to be_nil
+ service.execute
+
+ expect(application).to be_updating
+ end
+
+ it 'schedule async installation status check' do
+ expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
+
+ service.execute
+ end
+ end
+
+ context 'when kubernetes cluster communication fails' do
+ let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
+ before do
+ expect(helm_client).to receive(:update).with(update_command).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to match('Kubernetes error: 500')
+ end
+ end
+
+ context 'a non kubernetes error happens' do
+ let(:application) { create(:clusters_applications_knative, :scheduled) }
+ let(:error) { StandardError.new('something bad happened') }
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+
+ before do
+ expect(helm_client).to receive(:update).with(update_command).and_raise(error)
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to eq('Failed to update.')
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb
deleted file mode 100644
index 8380932dfaa..00000000000
--- a/spec/services/clusters/applications/schedule_installation_service_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-describe Clusters::Applications::ScheduleInstallationService do
- def count_scheduled
- application&.class&.with_status(:scheduled)&.count || 0
- end
-
- shared_examples 'a failing service' do
- it 'raise an exception' do
- expect(ClusterInstallAppWorker).not_to receive(:perform_async)
- count_before = count_scheduled
-
- expect { service.execute }.to raise_error(StandardError)
- expect(count_scheduled).to eq(count_before)
- end
- end
-
- describe '#execute' do
- let(:service) { described_class.new(application) }
-
- context 'when application is installable' do
- let(:application) { create(:clusters_applications_helm, :installable) }
-
- it 'make the application scheduled' do
- expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once
-
- expect { service.execute }.to change { application.class.with_status(:scheduled).count }.by(1)
- end
- end
-
- context 'when installation is already in progress' do
- let(:application) { create(:clusters_applications_helm, :installing) }
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application is nil' do
- let(:application) { nil }
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application cannot be persisted' do
- let(:application) { create(:clusters_applications_helm) }
-
- before do
- expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid)
- end
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application is installed' do
- let(:application) { create(:clusters_applications_helm, :installed) }
-
- it 'schedules an upgrade via worker' do
- expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
-
- service.execute
-
- expect(application).to be_scheduled
- end
- end
-
- context 'when application is updated' do
- let(:application) { create(:clusters_applications_helm, :updated) }
-
- it 'schedules an upgrade via worker' do
- expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
-
- service.execute
-
- expect(application).to be_scheduled
- end
- end
- end
-end
diff --git a/spec/services/clusters/applications/uninstall_service_spec.rb b/spec/services/clusters/applications/uninstall_service_spec.rb
new file mode 100644
index 00000000000..16497d752b2
--- /dev/null
+++ b/spec/services/clusters/applications/uninstall_service_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::UninstallService, '#execute' do
+ let(:application) { create(:clusters_applications_prometheus, :scheduled) }
+ let(:service) { described_class.new(application) }
+ let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
+ let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker }
+
+ before do
+ allow(service).to receive(:helm_api).and_return(helm_client)
+ end
+
+ context 'when there are no errors' do
+ before do
+ expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand))
+ allow(worker_class).to receive(:perform_in).and_return(nil)
+ end
+
+ it 'make the application to be uninstalling' do
+ expect(application.cluster).not_to be_nil
+ service.execute
+
+ expect(application).to be_uninstalling
+ end
+
+ it 'schedule async installation status check' do
+ expect(worker_class).to receive(:perform_in).once
+
+ service.execute
+ end
+ end
+
+ context 'when k8s cluster communication fails' do
+ let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
+ before do
+ expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to match('Kubernetes error: 500')
+ end
+ end
+
+ context 'a non kubernetes error happens' do
+ let(:application) { create(:clusters_applications_prometheus, :scheduled) }
+ let(:error) { StandardError.new('something bad happened') }
+
+ before do
+ expect(helm_client).to receive(:uninstall).with(kind_of(Gitlab::Kubernetes::Helm::DeleteCommand)).and_raise(error)
+ end
+
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_uninstall_errored
+ expect(application.status_reason).to eq('Failed to uninstall.')
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb
new file mode 100644
index 00000000000..2d299882af0
--- /dev/null
+++ b/spec/services/clusters/applications/update_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::UpdateService do
+ include TestRequestHelpers
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:user) { create(:user) }
+ let(:params) { { application: 'knative', hostname: 'udpate.example.com' } }
+ let(:service) { described_class.new(cluster, user, params) }
+
+ subject { service.execute(test_request) }
+
+ describe '#execute' do
+ before do
+ allow(ClusterPatchAppWorker).to receive(:perform_async)
+ end
+
+ context 'application is not installed' do
+ it 'raises Clusters::Applications::BaseService::InvalidApplicationError' do
+ expect(ClusterPatchAppWorker).not_to receive(:perform_async)
+
+ expect { subject }
+ .to raise_exception { Clusters::Applications::BaseService::InvalidApplicationError }
+ .and not_change { Clusters::Applications::Knative.count }
+ .and not_change { Clusters::Applications::Knative.with_status(:scheduled).count }
+ end
+ end
+
+ context 'application is installed' do
+ context 'application is schedulable' do
+ let!(:application) do
+ create(:clusters_applications_knative, status: 3, cluster: cluster)
+ end
+
+ it 'updates the application data' do
+ expect do
+ subject
+ end.to change { application.reload.hostname }.to(params[:hostname])
+ end
+
+ it 'makes application scheduled!' do
+ subject
+
+ expect(application.reload).to be_scheduled
+ end
+
+ it 'schedules ClusterPatchAppWorker' do
+ expect(ClusterPatchAppWorker).to receive(:perform_async)
+
+ subject
+ end
+ end
+
+ context 'application is not schedulable' do
+ let!(:application) do
+ create(:clusters_applications_knative, status: 4, cluster: cluster)
+ end
+
+ it 'raises StateMachines::InvalidTransition' do
+ expect(ClusterPatchAppWorker).not_to receive(:perform_async)
+
+ expect { subject }
+ .to raise_exception { StateMachines::InvalidTransition }
+ .and not_change { application.reload.hostname }
+ .and not_change { Clusters::Applications::Knative.with_status(:scheduled).count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
index 1822fc38dbd..a80b1d9127c 100644
--- a/spec/services/clusters/applications/upgrade_service_spec.rb
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -41,41 +41,18 @@ describe Clusters::Applications::UpgradeService do
expect(helm_client).to receive(:update).with(install_command).and_raise(error)
end
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'Kubeclient::HttpError' }
+ let(:error_message) { 'system failure' }
+ let(:error_code) { 500 }
+ end
+
it 'make the application errored' do
service.execute
expect(application).to be_update_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'Kubeclient::HttpError',
- message: 'system failure',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.project_ids,
- group_ids: [],
- error_code: 500
- }
- )
-
- service.execute
- end
end
context 'a non kubernetes error happens' do
@@ -83,45 +60,20 @@ describe Clusters::Applications::UpgradeService do
let(:error) { StandardError.new('something bad happened') }
before do
- expect(application).to receive(:make_updating!).once.and_raise(error)
+ expect(helm_client).to receive(:update).with(install_command).and_raise(error)
end
- it 'make the application errored' do
- expect(helm_client).not_to receive(:update)
+ include_examples 'logs kubernetes errors' do
+ let(:error_name) { 'StandardError' }
+ let(:error_message) { 'something bad happened' }
+ let(:error_code) { nil }
+ end
+ it 'make the application errored' do
service.execute
expect(application).to be_update_errored
- expect(application.status_reason).to eq("Can't start upgrade process.")
- end
-
- it 'logs errors' do
- expect(service.send(:logger)).to receive(:error).with(
- {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
- error,
- extra: {
- exception: 'StandardError',
- error_code: nil,
- message: 'something bad happened',
- service: 'Clusters::Applications::UpgradeService',
- app_id: application.id,
- project_ids: application.cluster.projects.pluck(:id),
- group_ids: []
- }
- )
-
- service.execute
+ expect(application.status_reason).to eq('Failed to upgrade.')
end
end
end
diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb
index da0cb42b3a1..f3e852726f4 100644
--- a/spec/services/clusters/build_service_spec.rb
+++ b/spec/services/clusters/build_service_spec.rb
@@ -21,5 +21,13 @@ describe Clusters::BuildService do
is_expected.to be_group_type
end
end
+
+ describe 'when cluster subject is an instance' do
+ let(:cluster_subject) { Clusters::Instance.new }
+
+ it 'sets the cluster_type to instance_type' do
+ is_expected.to be_instance_type
+ end
+ end
end
end
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 274880f2c49..ecf0a9c9dce 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::CreateService do
diff --git a/spec/services/clusters/gcp/fetch_operation_service_spec.rb b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
index 55f123ee786..23da8004a7d 100644
--- a/spec/services/clusters/gcp/fetch_operation_service_spec.rb
+++ b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Gcp::FetchOperationService do
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
index 18f218fc236..be052a07da7 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
@@ -113,7 +113,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
it 'does not create any Clusters::KubernetesNamespace' do
subject
- expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
+ expect(cluster.kubernetes_namespaces).to eq([kubernetes_namespace])
end
it 'creates project service account' do
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
index 11a65d0c300..382b9043566 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
@@ -89,7 +89,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
it_behaves_like 'creates service account and token'
- it 'should create a cluster role binding with cluster-admin access' do
+ it 'creates a cluster role binding with cluster-admin access' do
subject
expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with(
diff --git a/spec/services/clusters/gcp/provision_service_spec.rb b/spec/services/clusters/gcp/provision_service_spec.rb
index c0bdac40938..dfd15690a1f 100644
--- a/spec/services/clusters/gcp/provision_service_spec.rb
+++ b/spec/services/clusters/gcp/provision_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Gcp::ProvisionService do
diff --git a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
index 2ee2fa51f63..9611b2080ba 100644
--- a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
+++ b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Gcp::VerifyProvisionStatusService do
diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb
index 58ab3c3cf73..5bc8a709941 100644
--- a/spec/services/clusters/refresh_service_spec.rb
+++ b/spec/services/clusters/refresh_service_spec.rb
@@ -93,7 +93,7 @@ describe Clusters::RefreshService do
let(:group) { cluster.group }
let(:project) { create(:project, group: group) }
- include_examples 'creates a kubernetes namespace'
+ include_examples 'does not create a kubernetes namespace'
context 'when project already has kubernetes namespace' do
before do
@@ -103,5 +103,11 @@ describe Clusters::RefreshService do
include_examples 'does not create a kubernetes namespace'
end
end
+
+ context 'cluster is not managed' do
+ let!(:cluster) { create(:cluster, :project, :not_managed, projects: [project]) }
+
+ include_examples 'does not create a kubernetes namespace'
+ end
end
end
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index b2e6ebecd4a..21b37f88fd8 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::UpdateService do
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
index 77595d7ba2d..2c012f080dd 100644
--- a/spec/services/cohorts_service_spec.rb
+++ b/spec/services/cohorts_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CohortsService do
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index 9e15eae8c13..fadd43635a6 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CompareService do
@@ -17,5 +19,18 @@ describe CompareService do
it { expect(subject.diffs.size).to eq(3) }
end
+
+ context 'compare with target branch that does not exist' do
+ subject { service.execute(project, 'non-existent-ref') }
+
+ it { expect(subject).to be_nil }
+ end
+
+ context 'compare with source branch that does not exist' do
+ let(:service) { described_class.new(project, 'non-existent-branch') }
+ subject { service.execute(project, 'non-existent-ref') }
+
+ it { expect(subject).to be_nil }
+ end
end
end
diff --git a/spec/services/create_branch_service_spec.rb b/spec/services/create_branch_service_spec.rb
index 38096a080a7..0d34c7f9a82 100644
--- a/spec/services/create_branch_service_spec.rb
+++ b/spec/services/create_branch_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateBranchService do
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index b6ab6b8271c..f6b6989b955 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateSnippetService do
diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/delete_branch_service_spec.rb
index 9c9fba030e7..b8064c2cbc1 100644
--- a/spec/services/delete_branch_service_spec.rb
+++ b/spec/services/delete_branch_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteBranchService do
@@ -6,20 +8,24 @@ describe DeleteBranchService do
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
+ shared_examples 'a deleted branch' do |branch_name|
+ it 'removes the branch' do
+ expect(branch_exists?(branch_name)).to be true
+
+ result = service.execute(branch_name)
+
+ expect(result.status).to eq :success
+ expect(branch_exists?(branch_name)).to be false
+ end
+ end
+
describe '#execute' do
context 'when user has access to push to repository' do
before do
project.add_developer(user)
end
- it 'removes the branch' do
- expect(branch_exists?('feature')).to be true
-
- result = service.execute('feature')
-
- expect(result[:status]).to eq :success
- expect(branch_exists?('feature')).to be false
- end
+ it_behaves_like 'a deleted branch', 'feature'
end
context 'when user does not have access to push to repository' do
@@ -28,8 +34,8 @@ describe DeleteBranchService do
result = service.execute('feature')
- expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'You dont have push access to repo'
+ expect(result.status).to eq :error
+ expect(result.message).to eq 'You dont have push access to repo'
expect(branch_exists?('feature')).to be true
end
end
diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb
index 0de02576203..dffc2bd93ee 100644
--- a/spec/services/delete_merged_branches_service_spec.rb
+++ b/spec/services/delete_merged_branches_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteMergedBranchesService do
diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb
index 7a604c0cadd..a55f1561194 100644
--- a/spec/services/deploy_keys/create_service_spec.rb
+++ b/spec/services/deploy_keys/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployKeys::CreateService do
diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb
index 3a2bbf1ecd1..886ffd4593d 100644
--- a/spec/services/deploy_tokens/create_service_spec.rb
+++ b/spec/services/deploy_tokens/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployTokens::CreateService do
@@ -9,11 +11,11 @@ describe DeployTokens::CreateService do
subject { described_class.new(project, user, deploy_token_params).execute }
context 'when the deploy token is valid' do
- it 'should create a new DeployToken' do
+ it 'creates a new DeployToken' do
expect { subject }.to change { DeployToken.count }.by(1)
end
- it 'should create a new ProjectDeployToken' do
+ it 'creates a new ProjectDeployToken' do
expect { subject }.to change { ProjectDeployToken.count }.by(1)
end
@@ -25,7 +27,7 @@ describe DeployTokens::CreateService do
context 'when expires at date is not passed' do
let(:deploy_token_params) { attributes_for(:deploy_token, expires_at: '') }
- it 'should set Forever.date' do
+ it 'sets Forever.date' do
expect(subject.read_attribute(:expires_at)).to eq(Forever.date)
end
end
@@ -33,11 +35,11 @@ describe DeployTokens::CreateService do
context 'when the deploy token is invalid' do
let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) }
- it 'should not create a new DeployToken' do
+ it 'does not create a new DeployToken' do
expect { subject }.not_to change { DeployToken.count }
end
- it 'should not create a new ProjectDeployToken' do
+ it 'does not create a new ProjectDeployToken' do
expect { subject }.not_to change { ProjectDeployToken.count }
end
end
diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb
index 4e0d4749239..5b99430cb75 100644
--- a/spec/services/discussions/resolve_service_spec.rb
+++ b/spec/services/discussions/resolve_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussions::ResolveService do
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index 2b84206318f..60ec83e9062 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussions::UpdateDiffPositionService do
diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb
index 2b2c31e2521..6a274ca9dfe 100644
--- a/spec/services/emails/confirm_service_spec.rb
+++ b/spec/services/emails/confirm_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Emails::ConfirmService do
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index 54692c88623..87f93ec97c9 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Emails::CreateService do
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index c3204fac3df..5abe8da2529 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Emails::DestroyService do
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index 9d4fc62f923..3a8f3069911 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -53,7 +53,10 @@ describe ErrorTracking::ListIssuesService do
before do
allow(error_tracking_setting)
.to receive(:list_sentry_issues)
- .and_return(error: 'Sentry response status code: 401')
+ .and_return(
+ error: 'Sentry response status code: 401',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE
+ )
end
it 'returns the error' do
@@ -64,6 +67,25 @@ describe ErrorTracking::ListIssuesService do
)
end
end
+
+ context 'when list_sentry_issues returns error with http_status' do
+ before do
+ allow(error_tracking_setting)
+ .to receive(:list_sentry_issues)
+ .and_return(
+ error: 'Sentry API response is missing keys. key not found: "id"',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ )
+ end
+
+ it 'returns the error with correct http_status' do
+ expect(result).to eq(
+ status: :error,
+ http_status: :internal_server_error,
+ message: 'Sentry API response is missing keys. key not found: "id"'
+ )
+ end
+ end
end
context 'with unauthorized user' do
diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb
index 9f25a633deb..730fccc599e 100644
--- a/spec/services/error_tracking/list_projects_service_spec.rb
+++ b/spec/services/error_tracking/list_projects_service_spec.rb
@@ -32,7 +32,7 @@ describe ErrorTracking::ListProjectsService do
end
context 'set model attributes to new values' do
- let(:new_api_url) { new_api_host + 'api/0/projects/' }
+ let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' }
before do
expect(error_tracking_setting).to receive(:list_sentry_projects)
@@ -51,14 +51,28 @@ describe ErrorTracking::ListProjectsService do
end
context 'sentry client raises exception' do
- before do
- expect(error_tracking_setting).to receive(:list_sentry_projects)
- .and_raise(Sentry::Client::Error, 'Sentry response status code: 500')
+ context 'Sentry::Client::Error' do
+ before do
+ expect(error_tracking_setting).to receive(:list_sentry_projects)
+ .and_raise(Sentry::Client::Error, 'Sentry response status code: 500')
+ end
+
+ it 'returns error response' do
+ expect(result[:message]).to eq('Sentry response status code: 500')
+ expect(result[:http_status]).to eq(:bad_request)
+ end
end
- it 'returns error response' do
- expect(result[:message]).to eq('Sentry response status code: 500')
- expect(result[:http_status]).to eq(:bad_request)
+ context 'Sentry::Client::MissingKeysError' do
+ before do
+ expect(error_tracking_setting).to receive(:list_sentry_projects)
+ .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"')
+ end
+
+ it 'returns error response' do
+ expect(result[:message]).to eq('Sentry API response is missing keys. key not found: "id"')
+ expect(result[:http_status]).to eq(:internal_server_error)
+ end
end
end
@@ -121,7 +135,7 @@ describe ErrorTracking::ListProjectsService do
context 'error_tracking_setting is nil' do
let(:error_tracking_setting) { build(:project_error_tracking_setting) }
- let(:new_api_url) { new_api_host + 'api/0/projects/' }
+ let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' }
before do
expect(project).to receive(:build_error_tracking_setting).once
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 443665c9959..9f2c3fec62c 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventCreateService do
diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb
index 075cb45e46c..a623a05a56d 100644
--- a/spec/services/events/render_service_spec.rb
+++ b/spec/services/events/render_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Events::RenderService do
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 751b7160276..195f56a2909 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Files::CreateService do
diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb
index 309802ce733..b849def06fc 100644
--- a/spec/services/files/delete_service_spec.rb
+++ b/spec/services/files/delete_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Files::DeleteService do
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index 84c48d63c64..0f51c72019e 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Files::MultiService do
@@ -235,6 +237,22 @@ describe Files::MultiService do
expect(blob).to be_present
end
end
+
+ context 'when force is set to true and branch already exists' do
+ let(:commit_params) do
+ {
+ commit_message: commit_message,
+ branch_name: 'feature',
+ start_branch: 'master',
+ actions: actions,
+ force: true
+ }
+ end
+
+ it 'is still a success' do
+ expect(subject.execute[:status]).to eq(:success)
+ end
+ end
end
def update_file(path)
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 23db35c2418..37869b176ef 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Files::UpdateService do
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
new file mode 100644
index 00000000000..4a2ec769116
--- /dev/null
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Git::BaseHooksService do
+ include RepoHelpers
+ include GitHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
+
+ describe 'with remote mirrors' do
+ class TestService < described_class
+ def commits
+ []
+ end
+ end
+
+ let(:project) { create(:project, :repository, :remote_mirror) }
+
+ subject { TestService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ before do
+ expect(subject).to receive(:execute_project_hooks)
+ end
+
+ context 'when remote mirror feature is enabled' do
+ it 'fails stuck remote mirrors' do
+ allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors)
+ expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!)
+
+ subject.execute
+ end
+
+ it 'updates remote mirrors' do
+ expect(project).to receive(:update_remote_mirrors)
+
+ subject.execute
+ end
+ end
+
+ context 'when remote mirror feature is disabled' do
+ before do
+ stub_application_setting(mirror_available: false)
+ end
+
+ context 'with remote mirrors global setting overridden' do
+ before do
+ project.remote_mirror_available_overridden = true
+ end
+
+ it 'fails stuck remote mirrors' do
+ allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors)
+ expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!)
+
+ subject.execute
+ end
+
+ it 'updates remote mirrors' do
+ expect(project).to receive(:update_remote_mirrors)
+
+ subject.execute
+ end
+ end
+
+ context 'without remote mirrors global setting overridden' do
+ before do
+ project.remote_mirror_available_overridden = false
+ end
+
+ it 'does not fails stuck remote mirrors' do
+ expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!)
+
+ subject.execute
+ end
+
+ it 'does not updates remote mirrors' do
+ expect(project).not_to receive(:update_remote_mirrors)
+
+ subject.execute
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
new file mode 100644
index 00000000000..22faa996015
--- /dev/null
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -0,0 +1,347 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Git::BranchHooksService do
+ include RepoHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
+
+ let(:branch) { project.default_branch }
+ let(:ref) { "refs/heads/#{branch}" }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:oldrev) { commit.parent_id }
+ let(:newrev) { commit.id }
+
+ let(:service) do
+ described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+
+ it 'update remote mirrors' do
+ expect(service).to receive(:update_remote_mirrors).and_call_original
+
+ service.execute
+ end
+
+ describe "Git Push Data" do
+ subject(:push_data) { service.execute }
+
+ it 'has expected push data attributes' do
+ is_expected.to match a_hash_including(
+ object_kind: 'push',
+ before: oldrev,
+ after: newrev,
+ ref: ref,
+ user_id: user.id,
+ user_name: user.name,
+ project_id: project.id
+ )
+ end
+
+ context "with repository data" do
+ subject { push_data[:repository] }
+
+ it 'has expected attributes' do
+ is_expected.to match a_hash_including(
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url
+ )
+ end
+ end
+
+ context "with commits" do
+ subject { push_data[:commits] }
+
+ it { is_expected.to be_an(Array) }
+
+ it 'has 1 element' do
+ expect(subject.size).to eq(1)
+ end
+
+ context "the commit" do
+ subject { push_data[:commits].first }
+
+ it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) }
+
+ it 'includes expected commit data' do
+ is_expected.to match a_hash_including(
+ id: commit.id,
+ message: commit.safe_message,
+ url: [
+ Gitlab.config.gitlab.url,
+ project.namespace.to_param,
+ project.to_param,
+ 'commit',
+ commit.id
+ ].join('/')
+ )
+ end
+
+ context "with a author" do
+ subject { push_data[:commits].first[:author] }
+
+ it 'includes expected author data' do
+ is_expected.to match a_hash_including(
+ name: commit.author_name,
+ email: commit.author_email
+ )
+ end
+ end
+ end
+ end
+ end
+
+ describe 'Push Event' do
+ let(:event) { Event.find_by_action(Event::PUSHED) }
+
+ before do
+ service.execute
+ end
+
+ context "with an existing branch" do
+ it 'generates a push event with one commit' do
+ expect(event).to be_an_instance_of(PushEvent)
+ expect(event.project).to eq(project)
+ expect(event.action).to eq(Event::PUSHED)
+ expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
+ expect(event.push_event_payload.commit_from).to eq(oldrev)
+ expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.ref).to eq('master')
+ expect(event.push_event_payload.commit_count).to eq(1)
+ end
+ end
+
+ context "with a new branch" do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'generates a push event with more than one commit' do
+ expect(event).to be_an_instance_of(PushEvent)
+ expect(event.project).to eq(project)
+ expect(event.action).to eq(Event::PUSHED)
+ expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
+ expect(event.push_event_payload.commit_from).to be_nil
+ expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.ref).to eq('master')
+ expect(event.push_event_payload.commit_count).to be > 1
+ end
+ end
+
+ context 'removing a branch' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'generates a push event with no commits' do
+ expect(event).to be_an_instance_of(PushEvent)
+ expect(event.project).to eq(project)
+ expect(event.action).to eq(Event::PUSHED)
+ expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
+ expect(event.push_event_payload.commit_from).to eq(oldrev)
+ expect(event.push_event_payload.commit_to).to be_nil
+ expect(event.push_event_payload.ref).to eq('master')
+ expect(event.push_event_payload.commit_count).to eq(0)
+ end
+ end
+ end
+
+ describe 'Invalidating project cache' do
+ let(:commit_id) do
+ project.repository.update_file(
+ user, 'README.md', '', message: 'Update', branch_name: branch
+ )
+ end
+
+ let(:commit) { project.repository.commit(commit_id) }
+ let(:blank_sha) { Gitlab::Git::BLANK_SHA }
+
+ def clears_cache(extended: [])
+ expect(ProjectCacheWorker)
+ .to receive(:perform_async)
+ .with(project.id, extended, %i[commit_count repository_size])
+
+ service.execute
+ end
+
+ def clears_extended_cache
+ clears_cache(extended: %i[readme])
+ end
+
+ context 'on default branch' do
+ context 'create' do
+ # FIXME: When creating the default branch,the cache worker runs twice
+ before do
+ allow(ProjectCacheWorker).to receive(:perform_async)
+ end
+
+ let(:oldrev) { blank_sha }
+
+ it { clears_cache }
+ end
+
+ context 'update' do
+ it { clears_extended_cache }
+ end
+
+ context 'remove' do
+ let(:newrev) { blank_sha }
+
+ # TODO: this case should pass, but we only take account of added files
+ it { clears_cache }
+ end
+ end
+
+ context 'on ordinary branch' do
+ let(:branch) { 'fix' }
+
+ context 'create' do
+ let(:oldrev) { blank_sha }
+
+ it { clears_cache }
+ end
+
+ context 'update' do
+ it { clears_cache }
+ end
+
+ context 'remove' do
+ let(:newrev) { blank_sha }
+
+ it { clears_cache }
+ end
+ end
+ end
+
+ describe 'GPG signatures' do
+ context 'when the commit has a signature' do
+ context 'when the signature is already cached' do
+ before do
+ create(:gpg_signature, commit_sha: commit.id)
+ end
+
+ it 'does not queue a CreateGpgSignatureWorker' do
+ expect(CreateGpgSignatureWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'when the signature is not yet cached' do
+ it 'queues a CreateGpgSignatureWorker' do
+ expect(CreateGpgSignatureWorker).to receive(:perform_async).with([commit.id], project.id)
+
+ service.execute
+ end
+
+ it 'can queue several commits to create the gpg signature' do
+ allow(Gitlab::Git::Commit)
+ .to receive(:shas_with_signatures)
+ .and_return([sample_commit.id, another_sample_commit.id])
+
+ expect(CreateGpgSignatureWorker)
+ .to receive(:perform_async)
+ .with([sample_commit.id, another_sample_commit.id], project.id)
+
+ service.execute
+ end
+ end
+ end
+
+ context 'when the commit does not have a signature' do
+ before do
+ allow(Gitlab::Git::Commit)
+ .to receive(:shas_with_signatures)
+ .with(project.repository, [sample_commit.id])
+ .and_return([])
+ end
+
+ it 'does not queue a CreateGpgSignatureWorker' do
+ expect(CreateGpgSignatureWorker)
+ .not_to receive(:perform_async)
+ .with(sample_commit.id, project.id)
+
+ service.execute
+ end
+ end
+ end
+
+ describe 'Processing commit messages' do
+ # Create 4 commits, 2 of which have references. Limiting to 2 commits, we
+ # expect to see one commit message processor enqueued.
+ let(:commit_ids) do
+ Array.new(4) do |i|
+ message = "Issue #{'#' if i.even?}#{i}"
+ project.repository.update_file(
+ user, 'README.md', '', message: message, branch_name: branch
+ )
+ end
+ end
+
+ let(:oldrev) { commit_ids.first }
+ let(:newrev) { commit_ids.last }
+
+ before do
+ stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 2)
+ end
+
+ context 'creating the default branch' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'updating the default branch' do
+ it 'processes a limited number of commit messages' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ service.execute
+ end
+ end
+
+ context 'removing the default branch' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'creating a normal branch' do
+ let(:branch) { 'fix' }
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'processes a limited number of commit messages' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ service.execute
+ end
+ end
+
+ context 'updating a normal branch' do
+ let(:branch) { 'fix' }
+
+ it 'processes a limited number of commit messages' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ service.execute
+ end
+ end
+
+ context 'removing a normal branch' do
+ let(:branch) { 'fix' }
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
new file mode 100644
index 00000000000..6e39fa6b3c0
--- /dev/null
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -0,0 +1,605 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Git::BranchPushService, services: true do
+ include RepoHelpers
+
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :repository) }
+ let(:blankrev) { Gitlab::Git::BLANK_SHA }
+ let(:oldrev) { sample_commit.parent_id }
+ let(:newrev) { sample_commit.id }
+ let(:branch) { 'master' }
+ let(:ref) { "refs/heads/#{branch}" }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'Push branches' do
+ subject do
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ context 'new branch' do
+ let(:oldrev) { blankrev }
+
+ it { is_expected.to be_truthy }
+
+ it 'calls the after_push_commit hook' do
+ expect(project.repository).to receive(:after_push_commit).with('master')
+
+ subject
+ end
+
+ it 'calls the after_create_branch hook' do
+ expect(project.repository).to receive(:after_create_branch)
+
+ subject
+ end
+ end
+
+ context 'existing branch' do
+ it { is_expected.to be_truthy }
+
+ it 'calls the after_push_commit hook' do
+ expect(project.repository).to receive(:after_push_commit).with('master')
+
+ subject
+ end
+ end
+
+ context 'rm branch' do
+ let(:newrev) { blankrev }
+
+ it { is_expected.to be_truthy }
+
+ it 'calls the after_push_commit hook' do
+ expect(project.repository).to receive(:after_push_commit).with('master')
+
+ subject
+ end
+
+ it 'calls the after_remove_branch hook' do
+ expect(project.repository).to receive(:after_remove_branch)
+
+ subject
+ end
+ end
+ end
+
+ describe "Pipelines" do
+ subject { execute_service(project, user, oldrev, newrev, ref) }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ it "creates a new pipeline" do
+ expect { subject }.to change { Ci::Pipeline.count }
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
+ describe "Updates merge requests" do
+ it "when pushing a new branch for the first time" do
+ expect(UpdateMergeRequestsWorker)
+ .to receive(:perform_async)
+ .with(project.id, user.id, blankrev, 'newrev', ref)
+
+ execute_service(project, user, blankrev, 'newrev', ref )
+ end
+ end
+
+ describe "Updates git attributes" do
+ context "for default branch" do
+ it "calls the copy attributes method for the first push to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with('master')
+
+ execute_service(project, user, blankrev, 'newrev', ref)
+ end
+
+ it "calls the copy attributes method for changes to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with(ref)
+
+ execute_service(project, user, 'oldrev', 'newrev', ref)
+ end
+ end
+
+ context "for non-default branch" do
+ before do
+ # Make sure the "default" branch is different
+ allow(project).to receive(:default_branch).and_return('not-master')
+ end
+
+ it "does not call copy attributes method" do
+ expect(project.repository).not_to receive(:copy_gitattributes)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+ end
+
+ describe "Webhooks" do
+ context "execute webhooks" do
+ it "when pushing a branch for the first time" do
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ execute_service(project, user, blankrev, 'newrev', ref)
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
+ expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
+ end
+
+ it "when pushing a branch for the first time with default branch protection disabled" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ execute_service(project, user, blankrev, 'newrev', ref)
+ expect(project.protected_branches).to be_empty
+ end
+
+ it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+
+ execute_service(project, user, blankrev, 'newrev', ref)
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+ expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
+ end
+
+ it "when pushing a branch for the first time with an existing branch permission configured" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master')
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute)
+
+ execute_service(project, user, blankrev, 'newrev', ref)
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS])
+ expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+ end
+
+ it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ execute_service(project, user, blankrev, 'newrev', ref)
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
+ expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
+ end
+
+ it "when pushing new commits to existing branch" do
+ expect(project).to receive(:execute_hooks)
+ execute_service(project, user, 'oldrev', 'newrev', ref)
+ end
+ end
+ end
+
+ describe "cross-reference notes" do
+ let(:issue) { create :issue, project: project }
+ let(:commit_author) { create :user }
+ let(:commit) { project.commit }
+
+ before do
+ project.add_developer(commit_author)
+ project.add_developer(user)
+
+ allow(commit).to receive_messages(
+ safe_message: "this commit \n mentions #{issue.to_reference}",
+ references: [issue],
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ )
+
+ allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+ .and_return(commit)
+
+ allow(project.repository).to receive(:commits_between).and_return([commit])
+ end
+
+ it "creates a note if a pushed commit mentions an issue" do
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ it "only creates a cross-reference note if one doesn't already exist" do
+ SystemNoteService.cross_reference(issue, commit, user)
+
+ expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ it "defaults to the pushing user if the commit's author is not known" do
+ allow(commit).to receive_messages(
+ author_name: 'unknown name',
+ author_email: 'unknown@email.com'
+ )
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ it "finds references in the first push to a non-default branch" do
+ allow(project.repository).to receive(:commits_between).with(blankrev, newrev).and_return([])
+ allow(project.repository).to receive(:commits_between).with("master", newrev).and_return([commit])
+
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
+
+ execute_service(project, user, blankrev, newrev, 'refs/heads/other')
+ end
+ end
+
+ describe "issue metrics" do
+ let(:issue) { create :issue, project: project }
+ let(:commit_author) { create :user }
+ let(:commit) { project.commit }
+ let(:commit_time) { Time.now }
+
+ before do
+ project.add_developer(commit_author)
+ project.add_developer(user)
+
+ allow(commit).to receive_messages(
+ safe_message: "this commit \n mentions #{issue.to_reference}",
+ references: [issue],
+ author_name: commit_author.name,
+ author_email: commit_author.email,
+ committed_date: commit_time
+ )
+
+ allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+ .and_return(commit)
+
+ allow(project.repository).to receive(:commits_between).and_return([commit])
+ end
+
+ context "while saving the 'first_mentioned_in_commit_at' metric for an issue" do
+ it 'sets the metric for referenced issues' do
+ execute_service(project, user, oldrev, newrev, ref)
+
+ expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time)
+ end
+
+ it 'does not set the metric for non-referenced issues' do
+ non_referenced_issue = create(:issue, project: project)
+ execute_service(project, user, oldrev, newrev, ref)
+
+ expect(non_referenced_issue.reload.metrics.first_mentioned_in_commit_at).to be_nil
+ end
+ end
+ end
+
+ describe "closing issues from pushed commits containing a closing reference" do
+ let(:issue) { create :issue, project: project }
+ let(:other_issue) { create :issue, project: project }
+ let(:commit_author) { create :user }
+ let(:closing_commit) { project.commit }
+
+ before do
+ allow(closing_commit).to receive_messages(
+ issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/,
+ safe_message: "this is some work.\n\ncloses ##{issue.iid}",
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ )
+
+ allow(project.repository).to receive(:commits_between)
+ .and_return([closing_commit])
+
+ allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
+ .and_return(closing_commit)
+
+ project.add_maintainer(commit_author)
+ end
+
+ context "to default branches" do
+ it "closes issues" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+ expect(Issue.find(issue.id)).to be_closed
+ end
+
+ it "adds a note indicating that the issue is now closed" do
+ expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
+ execute_service(project, commit_author, oldrev, newrev, ref)
+ end
+
+ it "doesn't create additional cross-reference notes" do
+ expect(SystemNoteService).not_to receive(:cross_reference)
+ execute_service(project, commit_author, oldrev, newrev, ref)
+ end
+ end
+
+ context "to non-default branches" do
+ before do
+ # Make sure the "default" branch is different
+ allow(project).to receive(:default_branch).and_return('not-master')
+ end
+
+ it "creates cross-reference notes" do
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author)
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ it "doesn't close issues" do
+ execute_service(project, user, oldrev, newrev, ref)
+ expect(Issue.find(issue.id)).to be_opened
+ end
+ end
+
+ context "for jira issue tracker" do
+ include JiraServiceHelper
+
+ let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
+
+ before do
+ # project.create_jira_service doesn't seem to invalidate the cache here
+ project.has_external_issue_tracker = true
+ jira_service_settings
+ stub_jira_urls("JIRA-1")
+
+ allow(closing_commit).to receive_messages({
+ issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern),
+ safe_message: message,
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ })
+
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
+
+ allow(project.repository).to receive_messages(commits_between: [closing_commit])
+ end
+
+ after do
+ jira_tracker.destroy!
+ end
+
+ context "mentioning an issue" do
+ let(:message) { "this is some work.\n\nrelated to JIRA-1" }
+
+ it "initiates one api call to jira server to mention the issue" do
+ execute_service(project, user, oldrev, newrev, ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: /mentioned this issue in/
+ ).once
+ end
+ end
+
+ context "closing an issue" do
+ let(:message) { "this is some work.\n\ncloses JIRA-1" }
+ let(:comment_body) do
+ {
+ body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]."
+ }.to_json
+ end
+
+ before do
+ open_issue = JIRA::Resource::Issue.new(jira_tracker.client, attrs: { "id" => "JIRA-1" })
+ closed_issue = open_issue.dup
+ allow(open_issue).to receive(:resolution).and_return(false)
+ allow(closed_issue).to receive(:resolution).and_return(true)
+ allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue)
+
+ allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-1")
+ end
+
+ context "using right markdown" do
+ it "initiates one api call to jira server to close the issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
+ end
+
+ it "initiates one api call to jira server to comment on the issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+ end
+
+ context "using internal issue reference" do
+ context 'when internal issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+ let(:message) { "this is some work.\n\ncloses #1" }
+
+ it "does not initiates one api call to jira server to close the issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
+ end
+
+ it "does not initiates one api call to jira server to comment on the issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+ end
+
+ context 'when internal issues are enabled' do
+ let(:issue) { create(:issue, project: project) }
+ let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" }
+
+ it "initiates one api call to jira server to close the jira issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
+ end
+
+ it "initiates one api call to jira server to comment on the jira issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+
+ it "closes the internal issue" do
+ execute_service(project, commit_author, oldrev, newrev, ref)
+ expect(issue.reload).to be_closed
+ end
+
+ it "adds a note indicating that the issue is now closed" do
+ expect(SystemNoteService).to receive(:change_status)
+ .with(issue, project, commit_author, "closed", closing_commit)
+ execute_service(project, commit_author, oldrev, newrev, ref)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe "empty project" do
+ let(:project) { create(:project_empty_repo) }
+ let(:new_ref) { 'refs/heads/feature' }
+
+ before do
+ allow(project).to receive(:default_branch).and_return('feature')
+ expect(project).to receive(:change_head) { 'feature'}
+ end
+
+ it 'push to first branch updates HEAD' do
+ execute_service(project, user, blankrev, newrev, new_ref)
+ end
+ end
+
+ describe "housekeeping" do
+ let(:housekeeping) { Projects::HousekeepingService.new(project) }
+
+ before do
+ # Flush any raw key-value data stored by the housekeeping code.
+ Gitlab::Redis::Cache.with { |conn| conn.flushall }
+ Gitlab::Redis::Queues.with { |conn| conn.flushall }
+ Gitlab::Redis::SharedState.with { |conn| conn.flushall }
+
+ allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
+ end
+
+ after do
+ Gitlab::Redis::Cache.with { |conn| conn.flushall }
+ Gitlab::Redis::Queues.with { |conn| conn.flushall }
+ Gitlab::Redis::SharedState.with { |conn| conn.flushall }
+ end
+
+ it 'does not perform housekeeping when not needed' do
+ expect(housekeeping).not_to receive(:execute)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ context 'when housekeeping is needed' do
+ before do
+ allow(housekeeping).to receive(:needed?).and_return(true)
+ end
+
+ it 'performs housekeeping' do
+ expect(housekeeping).to receive(:execute)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+
+ it 'does not raise an exception' do
+ allow(housekeeping).to receive(:try_obtain_lease).and_return(false)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+
+ it 'increments the push counter' do
+ expect(housekeeping).to receive(:increment!)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+
+ describe "CI environments" do
+ context 'create branch' do
+ let(:oldrev) { blankrev }
+
+ it 'does nothing' do
+ expect(::Ci::StopEnvironmentsService).not_to receive(:new)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+
+ context 'update branch' do
+ it 'does nothing' do
+ expect(::Ci::StopEnvironmentsService).not_to receive(:new)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+
+ context 'delete branch' do
+ let(:newrev) { blankrev }
+
+ it 'stops environments' do
+ expect_next_instance_of(::Ci::StopEnvironmentsService) do |stop_service|
+ expect(stop_service.project).to eq(project)
+ expect(stop_service.current_user).to eq(user)
+ expect(stop_service).to receive(:execute).with(branch)
+ end
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+ end
+
+ describe 'Hooks' do
+ context 'run on a branch' do
+ it 'delegates to Git::BranchHooksService' do
+ expect_next_instance_of(::Git::BranchHooksService) do |hooks_service|
+ expect(hooks_service.project).to eq(project)
+ expect(hooks_service.current_user).to eq(user)
+ expect(hooks_service.params).to include(
+ oldrev: oldrev,
+ newrev: newrev,
+ ref: ref
+ )
+
+ expect(hooks_service).to receive(:execute)
+ end
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+
+ context 'run on a tag' do
+ let(:ref) { 'refs/tags/v1.1.0' }
+
+ it 'does nothing' do
+ expect(::Git::BranchHooksService).not_to receive(:new)
+
+ execute_service(project, user, oldrev, newrev, ref)
+ end
+ end
+ end
+
+ def execute_service(project, user, oldrev, newrev, ref)
+ service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ service.execute
+ service
+ end
+end
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
new file mode 100644
index 00000000000..f5938a5c708
--- /dev/null
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Git::TagHooksService, :service do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { "refs/tags/#{tag_name}" }
+ let(:tag_name) { 'v1.1.0' }
+
+ let(:tag) { project.repository.find_tag(tag_name) }
+ let(:commit) { tag.dereferenced_target }
+
+ let(:service) do
+ described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+
+ it 'update remote mirrors' do
+ expect(service).to receive(:update_remote_mirrors).and_call_original
+
+ service.execute
+ end
+
+ describe 'System hooks' do
+ it 'Executes system hooks' do
+ push_data = service.execute
+
+ expect_next_instance_of(SystemHooksService) do |system_hooks_service|
+ expect(system_hooks_service)
+ .to receive(:execute_hooks)
+ .with(push_data, :tag_push_hooks)
+ end
+
+ service.execute
+ end
+ end
+
+ describe "Webhooks" do
+ it "executes hooks on the project" do
+ expect(project).to receive(:execute_hooks)
+
+ service.execute
+ end
+ end
+
+ describe "Pipelines" do
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ project.add_developer(user)
+ end
+
+ it "creates a new pipeline" do
+ expect { service.execute }.to change { Ci::Pipeline.count }
+
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
+ describe 'Push data' do
+ shared_examples_for 'tag push data expectations' do
+ subject(:push_data) { service.execute }
+ it 'has expected push data attributes' do
+ is_expected.to match a_hash_including(
+ object_kind: 'tag_push',
+ ref: ref,
+ before: oldrev,
+ after: newrev,
+ message: tag.message,
+ user_id: user.id,
+ user_name: user.name,
+ project_id: project.id
+ )
+ end
+
+ context "with repository data" do
+ subject { push_data[:repository] }
+
+ it 'has expected repository attributes' do
+ is_expected.to match a_hash_including(
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url
+ )
+ end
+ end
+
+ context "with commits" do
+ subject { push_data[:commits] }
+
+ it { is_expected.to be_an(Array) }
+
+ it 'has 1 element' do
+ expect(subject.size).to eq(1)
+ end
+
+ context "the commit" do
+ subject { push_data[:commits].first }
+
+ it { is_expected.to include(timestamp: commit.date.xmlschema) }
+
+ it 'has expected commit attributes' do
+ is_expected.to match a_hash_including(
+ id: commit.id,
+ message: commit.safe_message,
+ url: [
+ Gitlab.config.gitlab.url,
+ project.namespace.to_param,
+ project.to_param,
+ 'commit',
+ commit.id
+ ].join('/')
+ )
+ end
+
+ context "with an author" do
+ subject { push_data[:commits].first[:author] }
+
+ it 'has expected author attributes' do
+ is_expected.to match a_hash_including(
+ name: commit.author_name,
+ email: commit.author_email
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context 'annotated tag' do
+ include_examples 'tag push data expectations'
+ end
+
+ context 'lightweight tag' do
+ let(:tag_name) { 'light-tag' }
+ let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
+
+ before do
+ # Create the lightweight tag
+ rugged_repo(project.repository).tags.create(tag_name, newrev)
+
+ # Clear tag list cache
+ project.repository.expire_tags_cache
+ end
+
+ include_examples 'tag push data expectations'
+ end
+ end
+end
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
new file mode 100644
index 00000000000..418952b52da
--- /dev/null
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Git::TagPushService do
+ include RepoHelpers
+ include GitHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
+
+ describe "Push tags" do
+ subject do
+ service.execute
+ service
+ end
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:before_push_tag)
+
+ subject
+ end
+
+ it 'flushes the tags cache' do
+ expect(project.repository).to receive(:expire_tags_cache)
+
+ subject
+ end
+ end
+
+ describe 'Hooks' do
+ context 'run on a tag' do
+ it 'delegates to Git::TagHooksService' do
+ expect_next_instance_of(::Git::TagHooksService) do |hooks_service|
+ expect(hooks_service.project).to eq(service.project)
+ expect(hooks_service.current_user).to eq(service.current_user)
+ expect(hooks_service.params).to eq(service.params)
+
+ expect(hooks_service).to receive(:execute)
+ end
+
+ service.execute
+ end
+ end
+
+ context 'run on a branch' do
+ let(:ref) { 'refs/heads/master' }
+
+ it 'does nothing' do
+ expect(::Git::BranchHooksService).not_to receive(:new)
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
deleted file mode 100644
index e8fce951155..00000000000
--- a/spec/services/git_push_service_spec.rb
+++ /dev/null
@@ -1,837 +0,0 @@
-require 'spec_helper'
-
-describe GitPushService, services: true do
- include RepoHelpers
-
- set(:user) { create(:user) }
- set(:project) { create(:project, :repository) }
- let(:blankrev) { Gitlab::Git::BLANK_SHA }
- let(:oldrev) { sample_commit.parent_id }
- let(:newrev) { sample_commit.id }
- let(:ref) { 'refs/heads/master' }
-
- before do
- project.add_maintainer(user)
- end
-
- describe 'with remote mirrors' do
- let(:project) { create(:project, :repository, :remote_mirror) }
-
- subject do
- described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
- end
-
- context 'when remote mirror feature is enabled' do
- it 'fails stuck remote mirrors' do
- allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors)
- expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!)
-
- subject.execute
- end
-
- it 'updates remote mirrors' do
- expect(project).to receive(:update_remote_mirrors)
-
- subject.execute
- end
- end
-
- context 'when remote mirror feature is disabled' do
- before do
- stub_application_setting(mirror_available: false)
- end
-
- context 'with remote mirrors global setting overridden' do
- before do
- project.remote_mirror_available_overridden = true
- end
-
- it 'fails stuck remote mirrors' do
- allow(project).to receive(:update_remote_mirrors).and_return(project.remote_mirrors)
- expect(project).to receive(:mark_stuck_remote_mirrors_as_failed!)
-
- subject.execute
- end
-
- it 'updates remote mirrors' do
- expect(project).to receive(:update_remote_mirrors)
-
- subject.execute
- end
- end
-
- context 'without remote mirrors global setting overridden' do
- before do
- project.remote_mirror_available_overridden = false
- end
-
- it 'does not fails stuck remote mirrors' do
- expect(project).not_to receive(:mark_stuck_remote_mirrors_as_failed!)
-
- subject.execute
- end
-
- it 'does not updates remote mirrors' do
- expect(project).not_to receive(:update_remote_mirrors)
-
- subject.execute
- end
- end
- end
- end
-
- describe 'Push branches' do
- subject do
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- context 'new branch' do
- let(:oldrev) { blankrev }
-
- it { is_expected.to be_truthy }
-
- it 'calls the after_push_commit hook' do
- expect(project.repository).to receive(:after_push_commit).with('master')
-
- subject
- end
-
- it 'calls the after_create_branch hook' do
- expect(project.repository).to receive(:after_create_branch)
-
- subject
- end
- end
-
- context 'existing branch' do
- it { is_expected.to be_truthy }
-
- it 'calls the after_push_commit hook' do
- expect(project.repository).to receive(:after_push_commit).with('master')
-
- subject
- end
- end
-
- context 'rm branch' do
- let(:newrev) { blankrev }
-
- it { is_expected.to be_truthy }
-
- it 'calls the after_push_commit hook' do
- expect(project.repository).to receive(:after_push_commit).with('master')
-
- subject
- end
-
- it 'calls the after_remove_branch hook' do
- expect(project.repository).to receive(:after_remove_branch)
-
- subject
- end
- end
- end
-
- describe "Git Push Data" do
- let(:commit) { project.commit(newrev) }
-
- subject { push_data_from_service(project, user, oldrev, newrev, ref) }
-
- it { is_expected.to include(object_kind: 'push') }
- it { is_expected.to include(before: oldrev) }
- it { is_expected.to include(after: newrev) }
- it { is_expected.to include(ref: ref) }
- it { is_expected.to include(user_id: user.id) }
- it { is_expected.to include(user_name: user.name) }
- it { is_expected.to include(project_id: project.id) }
-
- context "with repository data" do
- subject { push_data_from_service(project, user, oldrev, newrev, ref)[:repository] }
-
- it { is_expected.to include(name: project.name) }
- it { is_expected.to include(url: project.url_to_repo) }
- it { is_expected.to include(description: project.description) }
- it { is_expected.to include(homepage: project.web_url) }
- end
-
- context "with commits" do
- subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits] }
-
- it { is_expected.to be_an(Array) }
- it 'has 1 element' do
- expect(subject.size).to eq(1)
- end
-
- context "the commit" do
- subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first }
-
- it { is_expected.to include(id: commit.id) }
- it { is_expected.to include(message: commit.safe_message) }
- it { expect(subject[:timestamp].in_time_zone).to eq(commit.date.in_time_zone) }
- it do
- is_expected.to include(
- url: [
- Gitlab.config.gitlab.url,
- project.namespace.to_param,
- project.to_param,
- 'commit',
- commit.id
- ].join('/')
- )
- end
-
- context "with a author" do
- subject { push_data_from_service(project, user, oldrev, newrev, ref)[:commits].first[:author] }
-
- it { is_expected.to include(name: commit.author_name) }
- it { is_expected.to include(email: commit.author_email) }
- end
- end
- end
- end
-
- describe "Pipelines" do
- subject { execute_service(project, user, oldrev, newrev, ref) }
-
- before do
- stub_ci_pipeline_to_return_yaml_file
- end
-
- it "creates a new pipeline" do
- expect { subject }.to change { Ci::Pipeline.count }
- expect(Ci::Pipeline.last).to be_push
- end
- end
-
- describe "Push Event" do
- context "with an existing branch" do
- let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) }
- let(:event) { Event.find_by_action(Event::PUSHED) }
-
- it 'generates a push event with one commit' do
- expect(event).to be_an_instance_of(PushEvent)
- expect(event.project).to eq(project)
- expect(event.action).to eq(Event::PUSHED)
- expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
- expect(event.push_event_payload.commit_from).to eq(oldrev)
- expect(event.push_event_payload.commit_to).to eq(newrev)
- expect(event.push_event_payload.ref).to eq('master')
- expect(event.push_event_payload.commit_count).to eq(1)
- end
- end
-
- context "with a new branch" do
- let!(:new_branch_data) { push_data_from_service(project, user, Gitlab::Git::BLANK_SHA, newrev, ref) }
- let(:event) { Event.find_by_action(Event::PUSHED) }
-
- it 'generates a push event with more than one commit' do
- expect(event).to be_an_instance_of(PushEvent)
- expect(event.project).to eq(project)
- expect(event.action).to eq(Event::PUSHED)
- expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
- expect(event.push_event_payload.commit_from).to be_nil
- expect(event.push_event_payload.commit_to).to eq(newrev)
- expect(event.push_event_payload.ref).to eq('master')
- expect(event.push_event_payload.commit_count).to be > 1
- end
- end
-
- context "Updates merge requests" do
- it "when pushing a new branch for the first time" do
- expect(UpdateMergeRequestsWorker).to receive(:perform_async)
- .with(project.id, user.id, blankrev, 'newrev', ref)
- execute_service(project, user, blankrev, 'newrev', ref )
- end
- end
-
- describe 'system hooks' do
- let!(:push_data) { push_data_from_service(project, user, oldrev, newrev, ref) }
- let!(:system_hooks_service) { SystemHooksService.new }
-
- it "sends a system hook after pushing a branch" do
- allow(SystemHooksService).to receive(:new).and_return(system_hooks_service)
- allow(system_hooks_service).to receive(:execute_hooks)
-
- execute_service(project, user, oldrev, newrev, ref)
-
- expect(system_hooks_service).to have_received(:execute_hooks).with(push_data, :push_hooks)
- end
- end
- end
-
- describe "Updates git attributes" do
- context "for default branch" do
- it "calls the copy attributes method for the first push to the default branch" do
- expect(project.repository).to receive(:copy_gitattributes).with('master')
-
- execute_service(project, user, blankrev, 'newrev', ref)
- end
-
- it "calls the copy attributes method for changes to the default branch" do
- expect(project.repository).to receive(:copy_gitattributes).with(ref)
-
- execute_service(project, user, 'oldrev', 'newrev', ref)
- end
- end
-
- context "for non-default branch" do
- before do
- # Make sure the "default" branch is different
- allow(project).to receive(:default_branch).and_return('not-master')
- end
-
- it "does not call copy attributes method" do
- expect(project.repository).not_to receive(:copy_gitattributes)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
- end
-
- describe "Webhooks" do
- context "execute webhooks" do
- it "when pushing a branch for the first time" do
- expect(project).to receive(:execute_hooks)
- expect(project.default_branch).to eq("master")
- execute_service(project, user, blankrev, 'newrev', ref)
- expect(project.protected_branches).not_to be_empty
- expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
- expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
- end
-
- it "when pushing a branch for the first time with default branch protection disabled" do
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
-
- expect(project).to receive(:execute_hooks)
- expect(project.default_branch).to eq("master")
- execute_service(project, user, blankrev, 'newrev', ref)
- expect(project.protected_branches).to be_empty
- end
-
- it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
-
- expect(project).to receive(:execute_hooks)
- expect(project.default_branch).to eq("master")
-
- execute_service(project, user, blankrev, 'newrev', ref)
-
- expect(project.protected_branches).not_to be_empty
- expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
- expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
- end
-
- it "when pushing a branch for the first time with an existing branch permission configured" do
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
-
- create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master')
- expect(project).to receive(:execute_hooks)
- expect(project.default_branch).to eq("master")
- expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute)
-
- execute_service(project, user, blankrev, 'newrev', ref)
-
- expect(project.protected_branches).not_to be_empty
- expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS])
- expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
- end
-
- it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
-
- expect(project).to receive(:execute_hooks)
- expect(project.default_branch).to eq("master")
- execute_service(project, user, blankrev, 'newrev', ref)
- expect(project.protected_branches).not_to be_empty
- expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
- expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
- end
-
- it "when pushing new commits to existing branch" do
- expect(project).to receive(:execute_hooks)
- execute_service(project, user, 'oldrev', 'newrev', ref)
- end
- end
- end
-
- describe "cross-reference notes" do
- let(:issue) { create :issue, project: project }
- let(:commit_author) { create :user }
- let(:commit) { project.commit }
-
- before do
- project.add_developer(commit_author)
- project.add_developer(user)
-
- allow(commit).to receive_messages(
- safe_message: "this commit \n mentions #{issue.to_reference}",
- references: [issue],
- author_name: commit_author.name,
- author_email: commit_author.email
- )
-
- allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
- .and_return(commit)
-
- allow(project.repository).to receive(:commits_between).and_return([commit])
- end
-
- it "creates a note if a pushed commit mentions an issue" do
- expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it "only creates a cross-reference note if one doesn't already exist" do
- SystemNoteService.cross_reference(issue, commit, user)
-
- expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it "defaults to the pushing user if the commit's author is not known" do
- allow(commit).to receive_messages(
- author_name: 'unknown name',
- author_email: 'unknown@email.com'
- )
- expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it "finds references in the first push to a non-default branch" do
- allow(project.repository).to receive(:commits_between).with(blankrev, newrev).and_return([])
- allow(project.repository).to receive(:commits_between).with("master", newrev).and_return([commit])
-
- expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
-
- execute_service(project, user, blankrev, newrev, 'refs/heads/other')
- end
- end
-
- describe "issue metrics" do
- let(:issue) { create :issue, project: project }
- let(:commit_author) { create :user }
- let(:commit) { project.commit }
- let(:commit_time) { Time.now }
-
- before do
- project.add_developer(commit_author)
- project.add_developer(user)
-
- allow(commit).to receive_messages(
- safe_message: "this commit \n mentions #{issue.to_reference}",
- references: [issue],
- author_name: commit_author.name,
- author_email: commit_author.email,
- committed_date: commit_time
- )
-
- allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
- .and_return(commit)
-
- allow(project.repository).to receive(:commits_between).and_return([commit])
- end
-
- context "while saving the 'first_mentioned_in_commit_at' metric for an issue" do
- it 'sets the metric for referenced issues' do
- execute_service(project, user, oldrev, newrev, ref)
-
- expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time)
- end
-
- it 'does not set the metric for non-referenced issues' do
- non_referenced_issue = create(:issue, project: project)
- execute_service(project, user, oldrev, newrev, ref)
-
- expect(non_referenced_issue.reload.metrics.first_mentioned_in_commit_at).to be_nil
- end
- end
- end
-
- describe "closing issues from pushed commits containing a closing reference" do
- let(:issue) { create :issue, project: project }
- let(:other_issue) { create :issue, project: project }
- let(:commit_author) { create :user }
- let(:closing_commit) { project.commit }
-
- before do
- allow(closing_commit).to receive_messages(
- issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/,
- safe_message: "this is some work.\n\ncloses ##{issue.iid}",
- author_name: commit_author.name,
- author_email: commit_author.email
- )
-
- allow(project.repository).to receive(:commits_between)
- .and_return([closing_commit])
-
- allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit)
- .and_return(closing_commit)
-
- project.add_maintainer(commit_author)
- end
-
- context "to default branches" do
- it "closes issues" do
- execute_service(project, commit_author, oldrev, newrev, ref)
- expect(Issue.find(issue.id)).to be_closed
- end
-
- it "adds a note indicating that the issue is now closed" do
- expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
- execute_service(project, commit_author, oldrev, newrev, ref)
- end
-
- it "doesn't create additional cross-reference notes" do
- expect(SystemNoteService).not_to receive(:cross_reference)
- execute_service(project, commit_author, oldrev, newrev, ref)
- end
- end
-
- context "to non-default branches" do
- before do
- # Make sure the "default" branch is different
- allow(project).to receive(:default_branch).and_return('not-master')
- end
-
- it "creates cross-reference notes" do
- expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author)
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it "doesn't close issues" do
- execute_service(project, user, oldrev, newrev, ref)
- expect(Issue.find(issue.id)).to be_opened
- end
- end
-
- context "for jira issue tracker" do
- include JiraServiceHelper
-
- let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
-
- before do
- # project.create_jira_service doesn't seem to invalidate the cache here
- project.has_external_issue_tracker = true
- jira_service_settings
- stub_jira_urls("JIRA-1")
-
- allow(closing_commit).to receive_messages({
- issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern),
- safe_message: message,
- author_name: commit_author.name,
- author_email: commit_author.email
- })
-
- allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
-
- allow(project.repository).to receive_messages(commits_between: [closing_commit])
- end
-
- after do
- jira_tracker.destroy!
- end
-
- context "mentioning an issue" do
- let(:message) { "this is some work.\n\nrelated to JIRA-1" }
-
- it "initiates one api call to jira server to mention the issue" do
- execute_service(project, user, oldrev, newrev, ref)
-
- expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: /mentioned this issue in/
- ).once
- end
- end
-
- context "closing an issue" do
- let(:message) { "this is some work.\n\ncloses JIRA-1" }
- let(:comment_body) do
- {
- body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]."
- }.to_json
- end
-
- before do
- open_issue = JIRA::Resource::Issue.new(jira_tracker.client, attrs: { "id" => "JIRA-1" })
- closed_issue = open_issue.dup
- allow(open_issue).to receive(:resolution).and_return(false)
- allow(closed_issue).to receive(:resolution).and_return(true)
- allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue)
-
- allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-1")
- end
-
- context "using right markdown" do
- it "initiates one api call to jira server to close the issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
- end
-
- it "initiates one api call to jira server to comment on the issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: comment_body
- ).once
- end
- end
-
- context "using internal issue reference" do
- context 'when internal issues are disabled' do
- before do
- project.issues_enabled = false
- project.save!
- end
- let(:message) { "this is some work.\n\ncloses #1" }
-
- it "does not initiates one api call to jira server to close the issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
- end
-
- it "does not initiates one api call to jira server to comment on the issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: comment_body
- ).once
- end
- end
-
- context 'when internal issues are enabled' do
- let(:issue) { create(:issue, project: project) }
- let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" }
-
- it "initiates one api call to jira server to close the jira issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
- end
-
- it "initiates one api call to jira server to comment on the jira issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
-
- expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: comment_body
- ).once
- end
-
- it "closes the internal issue" do
- execute_service(project, commit_author, oldrev, newrev, ref)
- expect(issue.reload).to be_closed
- end
-
- it "adds a note indicating that the issue is now closed" do
- expect(SystemNoteService).to receive(:change_status)
- .with(issue, project, commit_author, "closed", closing_commit)
- execute_service(project, commit_author, oldrev, newrev, ref)
- end
- end
- end
- end
- end
- end
-
- describe "empty project" do
- let(:project) { create(:project_empty_repo) }
- let(:new_ref) { 'refs/heads/feature' }
-
- before do
- allow(project).to receive(:default_branch).and_return('feature')
- expect(project).to receive(:change_head) { 'feature'}
- end
-
- it 'push to first branch updates HEAD' do
- execute_service(project, user, blankrev, newrev, new_ref)
- end
- end
-
- describe "housekeeping" do
- let(:housekeeping) { Projects::HousekeepingService.new(project) }
-
- before do
- # Flush any raw key-value data stored by the housekeeping code.
- Gitlab::Redis::Cache.with { |conn| conn.flushall }
- Gitlab::Redis::Queues.with { |conn| conn.flushall }
- Gitlab::Redis::SharedState.with { |conn| conn.flushall }
-
- allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
- end
-
- after do
- Gitlab::Redis::Cache.with { |conn| conn.flushall }
- Gitlab::Redis::Queues.with { |conn| conn.flushall }
- Gitlab::Redis::SharedState.with { |conn| conn.flushall }
- end
-
- it 'does not perform housekeeping when not needed' do
- expect(housekeeping).not_to receive(:execute)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- context 'when housekeeping is needed' do
- before do
- allow(housekeeping).to receive(:needed?).and_return(true)
- end
-
- it 'performs housekeeping' do
- expect(housekeeping).to receive(:execute)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it 'does not raise an exception' do
- allow(housekeeping).to receive(:try_obtain_lease).and_return(false)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
-
- it 'increments the push counter' do
- expect(housekeeping).to receive(:increment!)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
-
- describe '#update_caches' do
- let(:service) do
- described_class.new(project,
- user,
- oldrev: oldrev,
- newrev: newrev,
- ref: ref)
- end
-
- context 'on the default branch' do
- before do
- allow(service).to receive(:default_branch?).and_return(true)
- end
-
- it 'flushes the caches of any special files that have been changed' do
- commit = double(:commit)
- diff = double(:diff, new_path: 'README.md')
-
- expect(commit).to receive(:raw_deltas)
- .and_return([diff])
-
- service.push_commits = [commit]
-
- expect(ProjectCacheWorker).to receive(:perform_async)
- .with(project.id, %i(readme), %i(commit_count repository_size))
-
- service.update_caches
- end
- end
-
- context 'on a non-default branch' do
- before do
- allow(service).to receive(:default_branch?).and_return(false)
- end
-
- it 'does not flush any conditional caches' do
- expect(ProjectCacheWorker).to receive(:perform_async)
- .with(project.id, [], %i(commit_count repository_size))
- .and_call_original
-
- service.update_caches
- end
- end
- end
-
- describe '#process_commit_messages' do
- let(:service) do
- described_class.new(project,
- user,
- oldrev: oldrev,
- newrev: newrev,
- ref: ref)
- end
-
- it 'only schedules a limited number of commits' do
- service.push_commits = Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true))
-
- expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times
-
- service.process_commit_messages
- end
-
- it "skips commits which don't include cross-references" do
- service.push_commits = [double(:commit, to_hash: {}, matches_cross_reference_regex?: false)]
-
- expect(ProcessCommitWorker).not_to receive(:perform_async)
-
- service.process_commit_messages
- end
- end
-
- describe '#update_signatures' do
- let(:service) do
- described_class.new(
- project,
- user,
- oldrev: oldrev,
- newrev: newrev,
- ref: 'refs/heads/master'
- )
- end
-
- context 'when the commit has a signature' do
- context 'when the signature is already cached' do
- before do
- create(:gpg_signature, commit_sha: sample_commit.id)
- end
-
- it 'does not queue a CreateGpgSignatureWorker' do
- expect(CreateGpgSignatureWorker).not_to receive(:perform_async)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
-
- context 'when the signature is not yet cached' do
- it 'queues a CreateGpgSignatureWorker' do
- expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id], project.id)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
-
- it 'can queue several commits to create the gpg signature' do
- allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).and_return([sample_commit.id, another_sample_commit.id])
-
- expect(CreateGpgSignatureWorker).to receive(:perform_async).with([sample_commit.id, another_sample_commit.id], project.id)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
- end
-
- context 'when the commit does not have a signature' do
- before do
- allow(Gitlab::Git::Commit).to receive(:shas_with_signatures).with(project.repository, [sample_commit.id]).and_return([])
- end
-
- it 'does not queue a CreateGpgSignatureWorker' do
- expect(CreateGpgSignatureWorker).not_to receive(:perform_async).with(sample_commit.id, project.id)
-
- execute_service(project, user, oldrev, newrev, ref)
- end
- end
- end
-
- def execute_service(project, user, oldrev, newrev, ref)
- service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
- service.execute
- service
- end
-
- def push_data_from_service(project, user, oldrev, newrev, ref)
- execute_service(project, user, oldrev, newrev, ref).push_data
- end
-end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
deleted file mode 100644
index 2699f6e7bcd..00000000000
--- a/spec/services/git_tag_push_service_spec.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-require 'spec_helper'
-
-describe GitTagPushService do
- include RepoHelpers
- include GitHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
-
- let(:oldrev) { Gitlab::Git::BLANK_SHA }
- let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
- let(:ref) { 'refs/tags/v1.1.0' }
-
- describe "Push tags" do
- subject do
- service.execute
- service
- end
-
- it 'flushes general cached data' do
- expect(project.repository).to receive(:before_push_tag)
-
- subject
- end
-
- it 'flushes the tags cache' do
- expect(project.repository).to receive(:expire_tags_cache)
-
- subject
- end
- end
-
- describe "Pipelines" do
- subject { service.execute }
-
- before do
- stub_ci_pipeline_to_return_yaml_file
- project.add_developer(user)
- end
-
- it "creates a new pipeline" do
- expect { subject }.to change { Ci::Pipeline.count }
- expect(Ci::Pipeline.last).to be_push
- end
- end
-
- describe "Git Tag Push Data" do
- subject { @push_data }
- let(:tag) { project.repository.find_tag(tag_name) }
- let(:commit) { tag.dereferenced_target }
-
- context 'annotated tag' do
- let(:tag_name) { Gitlab::Git.ref_name(ref) }
-
- before do
- service.execute
- @push_data = service.push_data
- end
-
- it { is_expected.to include(object_kind: 'tag_push') }
- it { is_expected.to include(ref: ref) }
- it { is_expected.to include(before: oldrev) }
- it { is_expected.to include(after: newrev) }
- it { is_expected.to include(message: tag.message) }
- it { is_expected.to include(user_id: user.id) }
- it { is_expected.to include(user_name: user.name) }
- it { is_expected.to include(project_id: project.id) }
-
- context "with repository data" do
- subject { @push_data[:repository] }
-
- it { is_expected.to include(name: project.name) }
- it { is_expected.to include(url: project.url_to_repo) }
- it { is_expected.to include(description: project.description) }
- it { is_expected.to include(homepage: project.web_url) }
- end
-
- context "with commits" do
- subject { @push_data[:commits] }
-
- it { is_expected.to be_an(Array) }
- it 'has 1 element' do
- expect(subject.size).to eq(1)
- end
-
- context "the commit" do
- subject { @push_data[:commits].first }
-
- it { is_expected.to include(id: commit.id) }
- it { is_expected.to include(message: commit.safe_message) }
- it { is_expected.to include(timestamp: commit.date.xmlschema) }
- it do
- is_expected.to include(
- url: [
- Gitlab.config.gitlab.url,
- project.namespace.to_param,
- project.to_param,
- 'commit',
- commit.id
- ].join('/')
- )
- end
-
- context "with a author" do
- subject { @push_data[:commits].first[:author] }
-
- it { is_expected.to include(name: commit.author_name) }
- it { is_expected.to include(email: commit.author_email) }
- end
- end
- end
- end
-
- context 'lightweight tag' do
- let(:tag_name) { 'light-tag' }
- let(:newrev) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
- let(:ref) { "refs/tags/light-tag" }
-
- before do
- # Create the lightweight tag
- rugged_repo(project.repository).tags.create(tag_name, newrev)
-
- # Clear tag list cache
- project.repository.expire_tags_cache
-
- service.execute
- @push_data = service.push_data
- end
-
- it { is_expected.to include(object_kind: 'tag_push') }
- it { is_expected.to include(ref: ref) }
- it { is_expected.to include(before: oldrev) }
- it { is_expected.to include(after: newrev) }
- it { is_expected.to include(message: tag.message) }
- it { is_expected.to include(user_id: user.id) }
- it { is_expected.to include(user_name: user.name) }
- it { is_expected.to include(project_id: project.id) }
-
- context "with repository data" do
- subject { @push_data[:repository] }
-
- it { is_expected.to include(name: project.name) }
- it { is_expected.to include(url: project.url_to_repo) }
- it { is_expected.to include(description: project.description) }
- it { is_expected.to include(homepage: project.web_url) }
- end
-
- context "with commits" do
- subject { @push_data[:commits] }
-
- it { is_expected.to be_an(Array) }
- it 'has 1 element' do
- expect(subject.size).to eq(1)
- end
-
- context "the commit" do
- subject { @push_data[:commits].first }
-
- it { is_expected.to include(id: commit.id) }
- it { is_expected.to include(message: commit.safe_message) }
- it { is_expected.to include(timestamp: commit.date.xmlschema) }
- it do
- is_expected.to include(
- url: [
- Gitlab.config.gitlab.url,
- project.namespace.to_param,
- project.to_param,
- 'commit',
- commit.id
- ].join('/')
- )
- end
-
- context "with a author" do
- subject { @push_data[:commits].first[:author] }
-
- it { is_expected.to include(name: commit.author_name) }
- it { is_expected.to include(email: commit.author_email) }
- end
- end
- end
- end
- end
-
- describe "Webhooks" do
- context "execute webhooks" do
- let(:service) { described_class.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
-
- it "when pushing tags" do
- expect(project).to receive(:execute_hooks)
- service.execute
- end
- end
- end
-end
diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb
index 1cd2625531e..8dfc9f19439 100644
--- a/spec/services/gpg_keys/create_service_spec.rb
+++ b/spec/services/gpg_keys/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GpgKeys::CreateService do
diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
index d2cc53fe0ee..9ce1df0f76f 100644
--- a/spec/services/gravatar_service_spec.rb
+++ b/spec/services/gravatar_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GravatarService do
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
new file mode 100644
index 00000000000..7f8ab517cef
--- /dev/null
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Groups::AutoDevopsService, '#execute' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ let(:group_params) { { auto_devops_enabled: '0' } }
+ let(:service) { described_class.new(group, user, group_params) }
+
+ context 'when user does not have enough privileges' do
+ it 'raises exception' do
+ group.add_developer(user)
+
+ expect do
+ service.execute
+ end.to raise_exception(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ context 'when user has enough privileges' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'updates group auto devops enabled accordingly' do
+ service.execute
+
+ expect(group.auto_devops_enabled).to eq(false)
+ end
+
+ context 'when group has projects' do
+ it 'reflects changes on projects' do
+ project_1 = create(:project, namespace: group)
+
+ service.execute
+
+ expect(project_1).not_to have_auto_devops_implicitly_enabled
+ end
+ end
+
+ context 'when group has subgroups' do
+ it 'reflects changes on subgroups' do
+ subgroup_1 = create(:group, parent: group)
+
+ service.execute
+
+ expect(subgroup_1.auto_devops_enabled?).to eq(false)
+ end
+
+ context 'when subgroups have projects', :nested_groups do
+ it 'reflects changes on projects' do
+ subgroup_1 = create(:group, parent: group)
+ project_1 = create(:project, namespace: subgroup_1)
+
+ service.execute
+
+ expect(project_1).not_to have_auto_devops_implicitly_enabled
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index fe6a8691ae0..c5ff6cdbacd 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::CreateService, '#execute' do
@@ -88,6 +90,17 @@ describe Groups::CreateService, '#execute' do
end
end
+ describe "when visibility level is passed as a string" do
+ let(:service) { described_class.new(user, group_params) }
+ let(:group_params) { { path: 'group_path', visibility: 'public' } }
+
+ it "assigns the correct visibility level" do
+ group = service.execute
+
+ expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
describe 'creating a mattermost team' do
let!(:params) { group_params.merge(create_chat_team: "true") }
let!(:service) { described_class.new(user, params) }
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index d80d0f5a8a8..1ab2e994b7e 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::DestroyService do
@@ -82,44 +84,6 @@ describe Groups::DestroyService do
expect(Group.unscoped.count).to eq(2)
end
end
-
- context 'potential race conditions' do
- context "when the `GroupDestroyWorker` task runs immediately" do
- it "deletes the group" do
- # Commit the contents of this spec's transaction so far
- # so subsequent db connections can see it.
- #
- # DO NOT REMOVE THIS LINE, even if you see a WARNING with "No
- # transaction is currently in progress". Without this, this
- # spec will always be green, since the group created in setup
- # cannot be seen by any other connections / threads in this spec.
- Group.connection.commit_db_transaction
-
- group_record = run_with_new_database_connection do |conn|
- conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
- end
-
- expect(group_record).not_to be_nil
-
- # Execute the contents of `GroupDestroyWorker` in a separate thread, to
- # simulate data manipulation by the Sidekiq worker (different database
- # connection / transaction).
- expect(GroupDestroyWorker).to receive(:perform_async).and_wrap_original do |m, group_id, user_id|
- Thread.new { m[group_id, user_id] }.join(5)
- end
-
- # Kick off the initial group destroy in a new thread, so that
- # it doesn't share this spec's database transaction.
- Thread.new { described_class.new(group, user).async_execute }.join(5)
-
- group_record = run_with_new_database_connection do |conn|
- conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
- end
-
- expect(group_record).to be_nil
- end
- end
- end
end
describe 'synchronous delete' do
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 75d6ddb0a2c..13acf9e055b 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::NestedCreateService do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 6b48c993c57..b5708ebba76 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Groups::TransferService, :postgresql do
@@ -12,11 +14,11 @@ describe Groups::TransferService, :postgresql do
allow(Group).to receive(:supports_nested_objects?).and_return(false)
end
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq('Transfer failed: Database is not supported.')
end
@@ -30,11 +32,11 @@ describe Groups::TransferService, :postgresql do
create(:group_member, :owner, group: new_parent_group, user: user)
end
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq('Transfer failed: namespace directory cannot be moved')
end
@@ -50,7 +52,7 @@ describe Groups::TransferService, :postgresql do
context 'when the group is already a root group' do
let(:group) { create(:group, :public) }
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(nil)
expect(transfer_service.error).to eq('Transfer failed: Group is already a root group.')
end
@@ -59,11 +61,11 @@ describe Groups::TransferService, :postgresql do
context 'when the user does not have the right policies' do
let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
- it "should return false" do
+ it "returns false" do
expect(transfer_service.execute(nil)).to be_falsy
end
- it "should add an error on group" do
+ it "adds an error on group" do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.")
end
@@ -76,11 +78,11 @@ describe Groups::TransferService, :postgresql do
create(:group, path: 'not-unique')
end
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(nil)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(nil)
expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.')
end
@@ -96,17 +98,17 @@ describe Groups::TransferService, :postgresql do
group.reload
end
- it 'should update group attributes' do
+ it 'updates group attributes' do
expect(group.parent).to be_nil
end
- it 'should update group children path' do
+ it 'updates group children path' do
group.children.each do |subgroup|
expect(subgroup.full_path).to eq("#{group.path}/#{subgroup.path}")
end
end
- it 'should update group projects path' do
+ it 'updates group projects path' do
group.projects.each do |project|
expect(project.full_path).to eq("#{group.path}/#{project.path}")
end
@@ -122,11 +124,11 @@ describe Groups::TransferService, :postgresql do
context 'when the new parent group is the same as the previous parent group' do
let(:group) { create(:group, :public, :nested, parent: new_parent_group) }
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq('Transfer failed: Group is already associated to the parent group.')
end
@@ -135,11 +137,11 @@ describe Groups::TransferService, :postgresql do
context 'when the user does not have the right policies' do
let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
- it "should return false" do
+ it "returns false" do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it "should add an error on group" do
+ it "adds an error on group" do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.")
end
@@ -152,11 +154,11 @@ describe Groups::TransferService, :postgresql do
create(:group, path: "not-unique", parent: new_parent_group)
end
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.')
end
@@ -171,11 +173,11 @@ describe Groups::TransferService, :postgresql do
group.update_attribute(:path, 'foo')
end
- it 'should return false' do
+ it 'returns false' do
expect(transfer_service.execute(new_parent_group)).to be_falsy
end
- it 'should add an error on group' do
+ it 'adds an error on group' do
transfer_service.execute(new_parent_group)
expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken')
end
@@ -191,7 +193,7 @@ describe Groups::TransferService, :postgresql do
let(:new_parent_group) { create(:group, :public) }
let(:group) { create(:group, :private, :nested) }
- it 'should not update the visibility for the group' do
+ it 'does not update the visibility for the group' do
group.reload
expect(group.private?).to be_truthy
expect(group.visibility_level).not_to eq(new_parent_group.visibility_level)
@@ -202,27 +204,27 @@ describe Groups::TransferService, :postgresql do
let(:new_parent_group) { create(:group, :private) }
let(:group) { create(:group, :public, :nested) }
- it 'should update visibility level based on the parent group' do
+ it 'updates visibility level based on the parent group' do
group.reload
expect(group.private?).to be_truthy
expect(group.visibility_level).to eq(new_parent_group.visibility_level)
end
end
- it 'should update visibility for the group based on the parent group' do
+ it 'updates visibility for the group based on the parent group' do
expect(group.visibility_level).to eq(new_parent_group.visibility_level)
end
- it 'should update parent group to the new parent ' do
+ it 'updates parent group to the new parent' do
expect(group.parent).to eq(new_parent_group)
end
- it 'should return the group as children of the new parent' do
+ it 'returns the group as children of the new parent' do
expect(new_parent_group.children.count).to eq(1)
expect(new_parent_group.children.first).to eq(group)
end
- it 'should create a redirect for the group' do
+ it 'creates a redirect for the group' do
expect(group.redirect_routes.count).to eq(1)
end
end
@@ -236,21 +238,21 @@ describe Groups::TransferService, :postgresql do
transfer_service.execute(new_parent_group)
end
- it 'should update subgroups path' do
+ it 'updates subgroups path' do
new_parent_path = new_parent_group.path
group.children.each do |subgroup|
expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}")
end
end
- it 'should create redirects for the subgroups' do
+ it 'creates redirects for the subgroups' do
expect(group.redirect_routes.count).to eq(1)
expect(subgroup1.redirect_routes.count).to eq(1)
expect(subgroup2.redirect_routes.count).to eq(1)
end
context 'when the new parent has a higher visibility than the children' do
- it 'should not update the children visibility' do
+ it 'does not update the children visibility' do
expect(subgroup1.private?).to be_truthy
expect(subgroup2.internal?).to be_truthy
end
@@ -261,7 +263,7 @@ describe Groups::TransferService, :postgresql do
let!(:subgroup2) { create(:group, :public, parent: group) }
let(:new_parent_group) { create(:group, :private) }
- it 'should update children visibility to match the new parent' do
+ it 'updates children visibility to match the new parent' do
group.children.each do |subgroup|
expect(subgroup.private?).to be_truthy
end
@@ -279,21 +281,21 @@ describe Groups::TransferService, :postgresql do
transfer_service.execute(new_parent_group)
end
- it 'should update projects path' do
+ it 'updates projects path' do
new_parent_path = new_parent_group.path
group.projects.each do |project|
expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}")
end
end
- it 'should create permanent redirects for the projects' do
+ it 'creates permanent redirects for the projects' do
expect(group.redirect_routes.count).to eq(1)
expect(project1.redirect_routes.count).to eq(1)
expect(project2.redirect_routes.count).to eq(1)
end
context 'when the new parent has a higher visibility than the projects' do
- it 'should not update projects visibility' do
+ it 'does not update projects visibility' do
expect(project1.private?).to be_truthy
expect(project2.internal?).to be_truthy
end
@@ -304,7 +306,7 @@ describe Groups::TransferService, :postgresql do
let!(:project2) { create(:project, :repository, :public, namespace: group) }
let(:new_parent_group) { create(:group, :private) }
- it 'should update projects visibility to match the new parent' do
+ it 'updates projects visibility to match the new parent' do
group.projects.each do |project|
expect(project.private?).to be_truthy
end
@@ -324,21 +326,21 @@ describe Groups::TransferService, :postgresql do
transfer_service.execute(new_parent_group)
end
- it 'should update subgroups path' do
+ it 'updates subgroups path' do
new_parent_path = new_parent_group.path
group.children.each do |subgroup|
expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}")
end
end
- it 'should update projects path' do
+ it 'updates projects path' do
new_parent_path = new_parent_group.path
group.projects.each do |project|
expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}")
end
end
- it 'should create redirect for the subgroups and projects' do
+ it 'creates redirect for the subgroups and projects' do
expect(group.redirect_routes.count).to eq(1)
expect(subgroup1.redirect_routes.count).to eq(1)
expect(subgroup2.redirect_routes.count).to eq(1)
@@ -360,7 +362,7 @@ describe Groups::TransferService, :postgresql do
transfer_service.execute(new_parent_group)
end
- it 'should update subgroups path' do
+ it 'updates subgroups path' do
new_base_path = "#{new_parent_group.path}/#{group.path}"
group.children.each do |children|
expect(children.full_path).to eq("#{new_base_path}/#{children.path}")
@@ -372,7 +374,7 @@ describe Groups::TransferService, :postgresql do
end
end
- it 'should update projects path' do
+ it 'updates projects path' do
new_parent_path = "#{new_parent_group.path}/#{group.path}"
subgroup1.projects.each do |project|
project_full_path = "#{new_parent_path}/#{project.namespace.path}/#{project.name}"
@@ -380,7 +382,7 @@ describe Groups::TransferService, :postgresql do
end
end
- it 'should create redirect for the subgroups and projects' do
+ it 'creates redirect for the subgroups and projects' do
expect(group.redirect_routes.count).to eq(1)
expect(project1.redirect_routes.count).to eq(1)
expect(subgroup1.redirect_routes.count).to eq(1)
@@ -402,7 +404,7 @@ describe Groups::TransferService, :postgresql do
transfer_service.execute(new_parent_group)
end
- it 'should restore group and projects visibility' do
+ it 'restores group and projects visibility' do
subgroup1.reload
project1.reload
expect(subgroup1.public?).to be_truthy
@@ -410,5 +412,34 @@ describe Groups::TransferService, :postgresql do
end
end
end
+
+ context 'when transferring a subgroup into root group' do
+ let(:group) { create(:group, :public, :nested) }
+ let(:subgroup) { create(:group, :public, parent: group) }
+ let(:transfer_service) { described_class.new(subgroup, user) }
+
+ it 'ensures there is still an owner for the transferred group' do
+ expect(subgroup.owners).to be_empty
+
+ transfer_service.execute(nil)
+ subgroup.reload
+
+ expect(subgroup.owners).to match_array(user)
+ end
+
+ context 'when group has explicit owner' do
+ let(:another_owner) { create(:user) }
+ let!(:another_member) { create(:group_member, :owner, group: subgroup, user: another_owner) }
+
+ it 'does not add additional owner' do
+ expect(subgroup.owners).to match_array(another_owner)
+
+ transfer_service.execute(nil)
+ subgroup.reload
+
+ expect(subgroup.owners).to match_array(another_owner)
+ end
+ end
+ end
end
end
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index d87a7dd234d..d081c20f669 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Groups::UpdateService do
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
index d5fcef1246f..51720e786dc 100644
--- a/spec/services/import_export_clean_up_service_spec.rb
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ImportExportCleanUpService do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index ca366cdf1df..aebc5ba2874 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issuable::BulkUpdateService do
@@ -76,14 +78,14 @@ describe Issuable::BulkUpdateService do
end
describe 'updating merge request assignee' do
- let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignee: user) }
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
context 'when the new assignee ID is a valid user' do
it 'succeeds' do
new_assignee = create(:user)
project.add_developer(new_assignee)
- result = bulk_update(merge_request, assignee_id: new_assignee.id)
+ result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
@@ -93,22 +95,22 @@ describe Issuable::BulkUpdateService do
assignee = create(:user)
project.add_developer(assignee)
- expect { bulk_update(merge_request, assignee_id: assignee.id) }
- .to change { merge_request.reload.assignee }.from(user).to(assignee)
+ expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
+ .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
it 'unassigns the issues' do
- expect { bulk_update(merge_request, assignee_id: IssuableFinder::NONE) }
- .to change { merge_request.reload.assignee }.to(nil)
+ expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
+ .to change { merge_request.reload.assignee_ids }.to([])
end
end
context 'when the new assignee ID is not present' do
it 'does not unassign' do
- expect { bulk_update(merge_request, assignee_id: nil) }
- .not_to change { merge_request.reload.assignee }
+ expect { bulk_update(merge_request, assignee_ids: []) }
+ .not_to change { merge_request.reload.assignee_ids }
end
end
end
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 4d3cb0bd254..230e1123280 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -149,5 +149,21 @@ describe Issuable::Clone::ContentRewriter do
expect(new_note.author).to eq(note.author)
end
end
+
+ context 'notes with upload' do
+ let(:uploader) { build(:file_uploader, project: project1) }
+ let(:text) { "Simple text with image: #{uploader.markdown_link} "}
+ let!(:note) { create(:note, noteable: original_issue, note: text, project: project1) }
+
+ it 'rewrites note content correctly' do
+ subject.execute
+ new_note = new_issue.notes.first
+
+ expect(note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(new_note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(note.note).not_to eq(new_note.note)
+ expect(note.note_html).not_to eq(new_note.note_html)
+ end
+ end
end
end
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index fa5d5ebac5c..7e40ac9ff4d 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issuable::CommonSystemNotesService do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:issuable) { create(:issue) }
+ let(:issuable) { create(:issue, project: project) }
context 'on issuable update' do
it_behaves_like 'system note creation', { title: 'New title' }, 'changed title'
@@ -70,7 +72,7 @@ describe Issuable::CommonSystemNotesService do
end
context 'on issuable create' do
- let(:issuable) { build(:issue) }
+ let(:issuable) { build(:issue, project: project) }
subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) }
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index 8ccbba7fa58..dd6a966c145 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issuable::DestroyService do
@@ -34,7 +36,7 @@ describe Issuable::DestroyService do
end
context 'when issuable is a merge request' do
- let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) }
+ let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignees: [user]) }
it 'destroys the merge request' do
expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1)
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 248e7d5a389..140b78f9b7a 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper.rb'
describe Issues::BuildService do
@@ -8,29 +10,29 @@ describe Issues::BuildService do
project.add_developer(user)
end
+ def build_issue(issue_params = {})
+ described_class.new(project, user, issue_params).execute
+ end
+
context 'for a single discussion' do
describe '#execute' do
let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
let(:discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done").to_discussion }
- let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) }
- it 'references the noteable title in the issue title' do
- issue = service.execute
+ subject { build_issue(merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) }
- expect(issue.title).to include('Hello world')
+ it 'references the noteable title in the issue title' do
+ expect(subject.title).to include('Hello world')
end
it 'adds the note content to the description' do
- issue = service.execute
-
- expect(issue.description).to include('Almost done')
+ expect(subject.description).to include('Almost done')
end
end
end
context 'for discussions in a merge request' do
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
- let(:issue) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute }
describe '#items_for_discussions' do
it 'has an item for each discussion' do
@@ -58,36 +60,40 @@ describe Issues::BuildService do
"> That has a quote\n"\
">>>\n"
note_result = " > This is a string\n"\
+ " > \n"\
" > > with a blockquote\n"\
- " > > > That has a quote\n"
+ " > > > That has a quote\n"\
+ " > \n"
discussion = create(:diff_note_on_merge_request, note: note_text).to_discussion
expect(service.item_for_discussion(discussion)).to include(note_result)
end
end
describe '#execute' do
- it 'has the merge request reference in the title' do
- expect(issue.title).to include(merge_request.title)
- end
+ let(:base_params) { { merge_request_to_resolve_discussions_of: merge_request.iid } }
+
+ context 'without additional params' do
+ subject { build_issue(base_params) }
- it 'has the reference of the merge request in the description' do
- expect(issue.description).to include(merge_request.to_reference)
+ it 'has the merge request reference in the title' do
+ expect(subject.title).to include(merge_request.title)
+ end
+
+ it 'has the reference of the merge request in the description' do
+ expect(subject.description).to include(merge_request.to_reference)
+ end
end
- it 'does not assign title when a title was given' do
- issue = described_class.new(project, user,
- merge_request_to_resolve_discussions_of: merge_request,
- title: 'What an issue').execute
+ it 'uses provided title if title param given' do
+ issue = build_issue(base_params.merge(title: 'What an issue'))
expect(issue.title).to eq('What an issue')
end
- it 'does not assign description when a description was given' do
- issue = described_class.new(project, user,
- merge_request_to_resolve_discussions_of: merge_request,
- description: 'Fix at your earliest conveignance').execute
+ it 'uses provided description if description param given' do
+ issue = build_issue(base_params.merge(description: 'Fix at your earliest convenience'))
- expect(issue.description).to eq('Fix at your earliest conveignance')
+ expect(issue.description).to eq('Fix at your earliest convenience')
end
describe 'with multiple discussions' do
@@ -96,20 +102,20 @@ describe Issues::BuildService do
it 'mentions all the authors in the description' do
authors = merge_request.resolvable_discussions.map(&:author)
- expect(issue.description).to include(*authors.map(&:to_reference))
+ expect(build_issue(base_params).description).to include(*authors.map(&:to_reference))
end
it 'has a link for each unresolved discussion in the description' do
notes = merge_request.resolvable_discussions.map(&:first_note)
links = notes.map { |note| Gitlab::UrlBuilder.build(note) }
- expect(issue.description).to include(*links)
+ expect(build_issue(base_params).description).to include(*links)
end
it 'mentions additional notes' do
create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, in_reply_to: diff_note)
- expect(issue.description).to include('(+2 comments)')
+ expect(build_issue(base_params).description).to include('(+2 comments)')
end
end
end
@@ -120,7 +126,7 @@ describe Issues::BuildService do
describe '#execute' do
it 'mentions the merge request in the description' do
- issue = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute
+ issue = build_issue(merge_request_to_resolve_discussions_of: merge_request.iid)
expect(issue.description).to include("Review the conversation in #{merge_request.to_reference}")
end
@@ -128,20 +134,18 @@ describe Issues::BuildService do
end
describe '#execute' do
- let(:milestone) { create(:milestone, project: project) }
-
it 'builds a new issues with given params' do
- issue = described_class.new(
- project,
- user,
- title: 'Issue #1',
- description: 'Issue description',
- milestone_id: milestone.id
- ).execute
-
- expect(issue.title).to eq('Issue #1')
- expect(issue.description).to eq('Issue description')
+ milestone = create(:milestone, project: project)
+ issue = build_issue(milestone_id: milestone.id)
+
expect(issue.milestone).to eq(milestone)
end
+
+ it 'sets milestone to nil if it is not available for the project' do
+ milestone = create(:milestone, project: create(:project))
+ issue = build_issue(milestone_id: milestone.id)
+
+ expect(issue.milestone).to be_nil
+ end
end
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 5e38d0aeb6a..6874a8a0929 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -1,11 +1,15 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::CloseService do
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, email: "user@example.com") }
+ let(:user2) { create(:user, email: "user2@example.com") }
let(:guest) { create(:user) }
- let(:issue) { create(:issue, assignees: [user2], author: create(:user)) }
- let(:project) { issue.project }
+ let(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: create(:user)) }
+ let(:closing_merge_request) { create(:merge_request, source_project: project) }
+ let(:closing_commit) { create(:commit, project: project) }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
before do
@@ -37,7 +41,7 @@ describe Issues::CloseService do
.and_return(true)
expect(service).to receive(:close_issue)
- .with(issue, commit: nil, notifications: true, system_note: true)
+ .with(issue, closed_via: nil, notifications: true, system_note: true)
service.execute(issue)
end
@@ -55,6 +59,38 @@ describe Issues::CloseService do
end
describe '#close_issue' do
+ context "closed by a merge request" do
+ before do
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
+ end
+ end
+
+ it 'mentions closure via a merge request' do
+ email = ActionMailer::Base.deliveries.last
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference))
+ end
+ end
+
+ context "closed by a commit" do
+ before do
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
+ end
+ end
+
+ it 'mentions closure via a commit' do
+ email = ActionMailer::Base.deliveries.last
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(email.body.parts.map(&:body)).to all(include(closing_commit.id))
+ end
+ end
+
context "valid params" do
before do
perform_enqueued_jobs do
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 715b1168bfb..b7bedc2f97e 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::CreateService do
@@ -138,6 +140,20 @@ describe Issues::CreateService do
end
end
+ context 'when duplicate label titles are given' do
+ let(:label) { create(:label, project: project) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ labels: [label.title, label.title] }
+ end
+
+ it 'assigns the label once' do
+ expect(issue.labels).to contain_exactly(label)
+ end
+ end
+
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
index 089e77cc88b..2c9581b993c 100644
--- a/spec/services/issues/duplicate_service_spec.rb
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::DuplicateService do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 1e088bc7d9b..7483688d631 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::MoveService do
diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb
index c2e1eba6a63..eae35f12560 100644
--- a/spec/services/issues/related_branches_service_spec.rb
+++ b/spec/services/issues/related_branches_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::RelatedBranchesService do
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 2a56075419b..f04029e64aa 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::ReopenService do
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index b6cfc09da65..f12a3820b8d 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper.rb'
describe Issues::ResolveDiscussions do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 931e47d3a77..22f5607cb9c 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issues::UpdateService, :mailer do
@@ -356,7 +358,7 @@ describe Issues::UpdateService, :mailer do
it_behaves_like 'system notes for milestones'
it 'sends notifications for subscribers of changed milestone' do
- issue.milestone = create(:milestone)
+ issue.milestone = create(:milestone, project: project)
issue.save
@@ -380,7 +382,7 @@ describe Issues::UpdateService, :mailer do
end
it 'marks todos as done' do
- update_issue(milestone: create(:milestone))
+ update_issue(milestone: create(:milestone, project: project))
expect(todo.reload.done?).to eq true
end
@@ -389,7 +391,7 @@ describe Issues::UpdateService, :mailer do
it 'sends notifications for subscribers of changed milestone' do
perform_enqueued_jobs do
- update_issue(milestone: create(:milestone))
+ update_issue(milestone: create(:milestone, project: project))
end
should_email(subscriber)
@@ -592,6 +594,16 @@ describe Issues::UpdateService, :mailer do
expect(result.label_ids).not_to include(label.id)
end
end
+
+ context 'when duplicate label titles are given' do
+ let(:params) do
+ { labels: [label3.title, label3.title] }
+ end
+
+ it 'assigns the label once' do
+ expect(result.labels).to contain_exactly(label3)
+ end
+ end
end
context 'updating asssignee_id' do
diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb
index bcb436c1e46..1f8b402cf08 100644
--- a/spec/services/keys/create_service_spec.rb
+++ b/spec/services/keys/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Keys::CreateService do
diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb
index 28ac72ddd42..ca4bbd50c03 100644
--- a/spec/services/keys/destroy_service_spec.rb
+++ b/spec/services/keys/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Keys::DestroyService do
diff --git a/spec/services/keys/last_used_service_spec.rb b/spec/services/keys/last_used_service_spec.rb
index 8e553c2f1fa..c675df39f4d 100644
--- a/spec/services/keys/last_used_service_spec.rb
+++ b/spec/services/keys/last_used_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Keys::LastUsedService do
diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb
new file mode 100644
index 00000000000..4d5c87ecc53
--- /dev/null
+++ b/spec/services/labels/available_labels_service_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Labels::AvailableLabelsService do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, group: group) }
+ let(:group) { create(:group) }
+
+ let(:project_label) { create(:label, project: project) }
+ let(:other_project_label) { create(:label) }
+ let(:group_label) { create(:group_label, group: group) }
+ let(:other_group_label) { create(:group_label) }
+ let(:labels) { [project_label, other_project_label, group_label, other_group_label] }
+
+ context '#find_or_create_by_titles' do
+ let(:label_titles) { labels.map(&:title).push('non existing title') }
+
+ context 'when parent is a project' do
+ context 'when a user is not a project member' do
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, project, labels: label_titles).find_or_create_by_titles
+
+ expect(result).to match_array([project_label, group_label])
+ end
+ end
+
+ context 'when a user is a project member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates new labels for not found titles' do
+ result = described_class.new(user, project, labels: label_titles).find_or_create_by_titles
+
+ expect(result.count).to eq(5)
+ expect(result).to include(project_label, group_label)
+ expect(result).not_to include(other_project_label, other_group_label)
+ end
+ end
+ end
+
+ context 'when parent is a group' do
+ context 'when a user is not a group member' do
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, group, labels: label_titles).find_or_create_by_titles
+
+ expect(result).to match_array([group_label])
+ end
+ end
+
+ context 'when a user is a group member' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'creates new labels for not found titles' do
+ result = described_class.new(user, group, labels: label_titles).find_or_create_by_titles
+
+ expect(result.count).to eq(5)
+ expect(result).to include(group_label)
+ expect(result).not_to include(project_label, other_project_label, other_group_label)
+ end
+ end
+ end
+ end
+
+ context '#filter_labels_ids_in_param' do
+ let(:label_ids) { labels.map(&:id).push(99999) }
+
+ context 'when parent is a project' do
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, project, ids: label_ids).filter_labels_ids_in_param(:ids)
+
+ expect(result).to match_array([project_label.id, group_label.id])
+ end
+ end
+
+ context 'when parent is a group' do
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, group, ids: label_ids).filter_labels_ids_in_param(:ids)
+
+ expect(result).to match_array([group_label.id])
+ end
+ end
+ end
+end
diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb
index 438e6dbc628..f057c4401e7 100644
--- a/spec/services/labels/create_service_spec.rb
+++ b/spec/services/labels/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Labels::CreateService do
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index 7af514a5bea..438d895392b 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Labels::FindOrCreateService do
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index c4c7f33e36a..d86281b751c 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Labels::PromoteService do
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index 80bac590a11..e29c6aeef5b 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Labels::TransferService do
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index c3fe33045fa..045e8af1135 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Labels::UpdateService do
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
index e8938338cb7..888eea6e91e 100644
--- a/spec/services/lfs/file_transformer_spec.rb
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Lfs::FileTransformer do
@@ -62,6 +64,25 @@ describe Lfs::FileTransformer do
expect(result.encoding).to eq('text')
end
+ context 'when an actual file is passed' do
+ let(:file) { Tempfile.new(file_path) }
+
+ before do
+ file.write(file_content)
+ file.rewind
+ end
+
+ after do
+ file.unlink
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.new_file(file_path, file)
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+ end
+
context "when doesn't use LFS" do
let(:file_path) { 'other.filetype' }
diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb
index 3e58eea2501..15dbc3231a3 100644
--- a/spec/services/lfs/lock_file_service_spec.rb
+++ b/spec/services/lfs/lock_file_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Lfs::LockFileService do
diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb
index e409b77babf..0fc2c593d94 100644
--- a/spec/services/lfs/locks_finder_service_spec.rb
+++ b/spec/services/lfs/locks_finder_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Lfs::LocksFinderService do
diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb
index fe42ca41633..8e36617c0d6 100644
--- a/spec/services/lfs/unlock_file_service_spec.rb
+++ b/spec/services/lfs/unlock_file_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Lfs::UnlockFileService do
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index 5c30f5b6a61..f56c31e51f6 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Members::ApproveAccessRequestService do
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 3bc05182932..674fe0f666e 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Members::CreateService do
@@ -42,7 +44,18 @@ describe Members::CreateService do
result = described_class.new(user, params).execute(project)
expect(result[:status]).to eq(:error)
- expect(result[:message]).to include(project_user.username)
+ expect(result[:message]).to include("#{project_user.username}: Access level is not included in the list")
expect(project.users).not_to include project_user
end
+
+ it 'does not add a member with an existing invite' do
+ invited_member = create(:project_member, :invited, project: project)
+
+ params = { user_ids: invited_member.invite_email,
+ access_level: Gitlab::Access::GUEST }
+ result = described_class.new(user, params).execute(project)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Invite email has already been taken')
+ end
end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index d37ca13ebd2..52f9a305d8f 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Members::DestroyService do
@@ -43,9 +45,9 @@ describe Members::DestroyService do
shared_examples 'a service destroying a member with access' do
it_behaves_like 'a service destroying a member'
- it 'invalidates cached counts for todos and assigned issues and merge requests', :aggregate_failures do
+ it 'invalidates cached counts for assigned issues and merge requests', :aggregate_failures do
create(:issue, project: group_project, assignees: [member_user])
- create(:merge_request, source_project: group_project, assignee: member_user)
+ create(:merge_request, source_project: group_project, assignees: [member_user])
create(:todo, :pending, project: group_project, user: member_user)
create(:todo, :done, project: group_project, user: member_user)
diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb
index e93ba5a85c0..2e5275eb3f2 100644
--- a/spec/services/members/request_access_service_spec.rb
+++ b/spec/services/members/request_access_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Members::RequestAccessService do
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index 599ed39ca37..a8b28127df2 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Members::UpdateService do
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index af0a214c00f..f26b67f902d 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::AddTodoWhenBuildFailsService do
@@ -77,6 +79,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
service.execute(commit_status)
end
end
+
+ context 'when build belongs to a merge request pipeline' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event,
+ ref: merge_request.merge_ref_path,
+ merge_request: merge_request,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) }
+
+ it 'notifies the todo service' do
+ expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
+ service.execute(commit_status)
+ end
+ end
end
describe '#close' do
@@ -106,6 +124,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
service.close(commit_status)
end
end
+
+ context 'when build belongs to a merge request pipeline' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event,
+ ref: merge_request.merge_ref_path,
+ merge_request: merge_request,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) }
+
+ it 'notifies the todo service' do
+ expect(todo_service).to receive(:merge_request_build_retried).with(merge_request)
+ service.close(commit_status)
+ end
+ end
end
describe '#close_all' do
diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb
index bda6383a346..c0b57b9092d 100644
--- a/spec/services/merge_requests/assign_issues_service_spec.rb
+++ b/spec/services/merge_requests/assign_issues_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::AssignIssuesService do
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 536d0d345a4..5c3b209086c 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::BuildService do
@@ -229,6 +231,15 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'when a milestone is from another project' do
+ let(:milestone) { create(:milestone, project: create(:project)) }
+ let(:milestone_id) { milestone.id }
+
+ it 'sets milestone to nil' do
+ expect(merge_request.milestone).to be_nil
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 433ffbd97f0..29b7e0f17e2 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user2, author: create(:user)) }
+ let(:merge_request) { create(:merge_request, assignees: [user2], author: create(:user)) }
let(:project) { merge_request.project }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
@@ -50,6 +52,14 @@ describe MergeRequests::CloseService do
it 'marks todos as done' do
expect(todo.reload).to be_done
end
+
+ context 'when auto merge is enabled' do
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'cancels the auto merge' do
+ expect(@merge_request).not_to be_auto_merge_enabled
+ end
+ end
end
it 'updates metrics' do
@@ -72,6 +82,14 @@ describe MergeRequests::CloseService do
.to change { project.open_merge_requests_count }.from(1).to(0)
end
+ it 'clean up environments for the merge request' do
+ expect_next_instance_of(Ci::StopEnvironmentsService) do |service|
+ expect(service).to receive(:execute_for_merge_request).with(merge_request)
+ end
+
+ described_class.new(project, user).execute(merge_request)
+ end
+
context 'current user is not authorized to close merge request' do
before do
perform_enqueued_jobs do
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index c81fa95e4b7..68a9c0a8b86 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::Conflicts::ListService do
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index 7edf8a96c94..74f20094081 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::Conflicts::ResolveService do
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 393299cce00..a0ac7dba89d 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::CreateFromIssueService do
@@ -118,7 +120,7 @@ describe MergeRequests::CreateFromIssueService do
result = service.execute
- expect(result[:merge_request].assignee).to eq(user)
+ expect(result[:merge_request].assignees).to eq([user])
end
context 'when ref branch is set' do
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
new file mode 100644
index 00000000000..9479439bde8
--- /dev/null
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::CreatePipelineService do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:params) { {} }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:config) do
+ { rspec: { script: 'echo', only: ['merge_requests'] } }
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_branch: 'feature',
+ source_project: project,
+ target_branch: 'master',
+ target_project: project)
+ end
+
+ it 'creates a detached merge request pipeline' do
+ expect { subject }.to change { Ci::Pipeline.count }.by(1)
+
+ expect(subject).to be_persisted
+ expect(subject).to be_detached_merge_request_pipeline
+ end
+
+ context 'when service is called multiple times' do
+ it 'creates a pipeline once' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(1)
+ end
+
+ context 'when allow_duplicate option is true' do
+ let(:params) { { allow_duplicate: true } }
+
+ it 'creates pipelines multiple times' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(2)
+ end
+ end
+ end
+
+ context 'when .gitlab-ci.yml does not have only: [merge_requests] keyword' do
+ let(:config) do
+ { rspec: { script: 'echo' } }
+ end
+
+ it 'does not create a pipeline' do
+ expect { subject }.not_to change { Ci::Pipeline.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index b46aa65818d..ed48f4b1e44 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::CreateService do
@@ -32,7 +34,7 @@ describe MergeRequests::CreateService do
expect(merge_request).to be_valid
expect(merge_request.work_in_progress?).to be(false)
expect(merge_request.title).to eq('Awesome merge_request')
- expect(merge_request.assignee).to be_nil
+ expect(merge_request.assignees).to be_empty
expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
end
@@ -73,7 +75,7 @@ describe MergeRequests::CreateService do
description: "well this is not done yet\n/wip",
source_branch: 'feature',
target_branch: 'master',
- assignee: assignee
+ assignees: [assignee]
}
end
@@ -89,7 +91,7 @@ describe MergeRequests::CreateService do
description: "well this is not done yet\n/wip",
source_branch: 'feature',
target_branch: 'master',
- assignee: assignee
+ assignees: [assignee]
}
end
@@ -106,11 +108,11 @@ describe MergeRequests::CreateService do
description: 'please fix',
source_branch: 'feature',
target_branch: 'master',
- assignee: assignee
+ assignees: [assignee]
}
end
- it { expect(merge_request.assignee).to eq assignee }
+ it { expect(merge_request.assignees).to eq([assignee]) }
it 'creates a todo for new assignee' do
attributes = {
@@ -173,7 +175,7 @@ describe MergeRequests::CreateService do
end
end
- describe 'Merge request pipelines' do
+ describe 'Pipelines for merge requests' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
@@ -189,12 +191,46 @@ describe MergeRequests::CreateService do
}
end
- it 'creates a merge request pipeline and sets it as a head pipeline' do
+ it 'creates a detached merge request pipeline and sets it as a head pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
- expect(merge_request.merge_request_pipelines.count).to eq(1)
- expect(merge_request.actual_head_pipeline).to be_merge_request
+ expect(merge_request.pipelines_for_merge_request.count).to eq(1)
+ expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline
+ end
+
+ context 'when merge request is submitted from forked project' do
+ let(:target_project) { fork_project(project, nil, repository: true) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ target_project_id: target_project.id
+ }
+ end
+
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_maintainer(user)
+ end
+
+ it 'create legacy detached merge request pipeline for fork merge request' do
+ expect(merge_request.actual_head_pipeline)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+ end
+
+ context 'when ci_use_merge_request_ref feature flag is false' do
+ before do
+ stub_feature_flags(ci_use_merge_request_ref: false)
+ end
+
+ it 'create legacy detached merge request pipeline for non-fork merge request' do
+ expect(merge_request.actual_head_pipeline)
+ .to be_legacy_detached_merge_request_pipeline
+ end
end
context 'when there are no commits between source branch and target branch' do
@@ -207,11 +243,11 @@ describe MergeRequests::CreateService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
- expect(merge_request.merge_request_pipelines.count).to eq(0)
+ expect(merge_request.pipelines_for_merge_request.count).to eq(0)
end
end
@@ -225,21 +261,8 @@ describe MergeRequests::CreateService do
merge_request
end
- it 'sets the latest merge request pipeline as the head pipeline' do
- expect(merge_request.actual_head_pipeline).to be_merge_request
- end
- end
-
- context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
- before do
- stub_feature_flags(ci_merge_request_pipeline: false)
- end
-
- it 'does not create a merge request pipeline' do
- expect(merge_request).to be_persisted
-
- merge_request.reload
- expect(merge_request.merge_request_pipelines.count).to eq(0)
+ it 'sets the latest detached merge request pipeline as the head pipeline' do
+ expect(merge_request.actual_head_pipeline).to be_merge_request_event
end
end
end
@@ -254,11 +277,11 @@ describe MergeRequests::CreateService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
- expect(merge_request.merge_request_pipelines.count).to eq(0)
+ expect(merge_request.pipelines_for_merge_request.count).to eq(0)
end
end
end
@@ -280,7 +303,7 @@ describe MergeRequests::CreateService do
let(:opts) do
{
- assignee_id: create(:user).id,
+ assignee_ids: create(:user).id,
milestone_id: 1,
title: 'Title',
description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}"),
@@ -296,7 +319,7 @@ describe MergeRequests::CreateService do
it 'assigns and sets milestone to issuable from command' do
expect(merge_request).to be_persisted
- expect(merge_request.assignee).to eq(assignee)
+ expect(merge_request.assignees).to eq([assignee])
expect(merge_request.milestone).to eq(milestone)
end
end
@@ -311,28 +334,28 @@ describe MergeRequests::CreateService do
end
it 'removes assignee_id when user id is invalid' do
- opts = { title: 'Title', description: 'Description', assignee_id: -1 }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [-1] }
merge_request = described_class.new(project, user, opts).execute
- expect(merge_request.assignee_id).to be_nil
+ expect(merge_request.assignee_ids).to be_empty
end
it 'removes assignee_id when user id is 0' do
- opts = { title: 'Title', description: 'Description', assignee_id: 0 }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [0] }
merge_request = described_class.new(project, user, opts).execute
- expect(merge_request.assignee_id).to be_nil
+ expect(merge_request.assignee_ids).to be_empty
end
it 'saves assignee when user id is valid' do
project.add_maintainer(assignee)
- opts = { title: 'Title', description: 'Description', assignee_id: assignee.id }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
merge_request = described_class.new(project, user, opts).execute
- expect(merge_request.assignee).to eq(assignee)
+ expect(merge_request.assignees).to eq([assignee])
end
context 'when assignee is set' do
@@ -340,7 +363,7 @@ describe MergeRequests::CreateService do
{
title: 'Title',
description: 'Description',
- assignee_id: assignee.id,
+ assignee_ids: [assignee.id],
source_branch: 'feature',
target_branch: 'master'
}
@@ -366,7 +389,7 @@ describe MergeRequests::CreateService do
levels.each do |level|
it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
project.update(visibility_level: level)
- opts = { title: 'Title', description: 'Description', assignee_id: assignee.id }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
merge_request = described_class.new(project, user, opts).execute
diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
index 6268c149fc6..a1c86467f34 100644
--- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
+++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index fe673de46aa..3b1096c51cb 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::FfMergeService do
@@ -7,7 +9,7 @@ describe MergeRequests::FfMergeService do
create(:merge_request,
source_branch: 'flatten-dir',
target_branch: 'improve/awesome',
- assignee: user2,
+ assignees: [user2],
author: create(:user))
end
let(:project) { merge_request.project }
@@ -72,7 +74,7 @@ describe MergeRequests::FfMergeService do
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 274624aa8bb..0933c6d4336 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe MergeRequests::GetUrlsService do
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 04a62aa454d..2fbe5468b21 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::MergeService do
set(:user) { create(:user) }
set(:user2) { create(:user) }
- let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
+ let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
let(:project) { merge_request.project }
before do
@@ -111,7 +113,7 @@ describe MergeRequests::MergeService do
end
context 'closes related todos' do
- let(:merge_request) { create(:merge_request, assignee: user, author: user) }
+ let(:merge_request) { create(:merge_request, assignees: [user], author: user) }
let(:project) { merge_request.project }
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
let!(:todo) do
@@ -224,10 +226,22 @@ describe MergeRequests::MergeService do
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
+ it 'logs and saves error if user is not authorized' do
+ unauthorized_user = create(:user)
+ project.add_reporter(unauthorized_user)
+
+ service = described_class.new(project, unauthorized_user)
+
+ service.execute(merge_request)
+
+ expect(merge_request.merge_error)
+ .to eq('You are not allowed to merge this merge request')
+ end
+
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
new file mode 100644
index 00000000000..5d492e4b013
--- /dev/null
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MergeToRefService do
+ shared_examples_for 'MergeService for target ref' do
+ it 'target_ref has the same state of target branch' do
+ repo = merge_request.target_project.repository
+
+ process_merge_to_ref
+ merge_service.execute(merge_request)
+
+ ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3)
+ target_branch_commits = repo.commits(merge_request.target_branch, limit: 3)
+
+ ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit|
+ expect(ref_commit.parents).to eq(target_branch_commit.parents)
+ end
+ end
+ end
+
+ shared_examples_for 'successfully merges to ref with merge method' do
+ it 'writes commit to merge ref' do
+ repository = project.repository
+ target_ref = merge_request.merge_ref_path
+
+ expect(repository.ref_exists?(target_ref)).to be(false)
+
+ result = service.execute(merge_request)
+
+ ref_head = repository.commit(target_ref)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:commit_id]).to be_present
+ expect(repository.ref_exists?(target_ref)).to be(true)
+ expect(ref_head.sha).to eq(result[:commit_id])
+ end
+ end
+
+ shared_examples_for 'successfully evaluates pre-condition checks' do
+ it 'returns an error when the failing to process the merge' do
+ allow(project.repository).to receive(:merge_to_ref).and_return(nil)
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Conflicts detected during merge')
+ end
+
+ it 'does not send any mail' do
+ expect { process_merge_to_ref }.not_to change { ActionMailer::Base.deliveries.count }
+ end
+
+ it 'does not change the MR state' do
+ expect { process_merge_to_ref }.not_to change { merge_request.state }
+ end
+
+ it 'does not create notes' do
+ expect { process_merge_to_ref }.not_to change { merge_request.notes.count }
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+
+ process_merge_to_ref
+ end
+ end
+
+ set(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, :simple) }
+ let(:project) { merge_request.project }
+
+ describe '#execute' do
+ let(:service) do
+ described_class.new(project, user, commit_message: 'Awesome message',
+ should_remove_source_branch: true)
+ end
+
+ def process_merge_to_ref
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ end
+ end
+
+ it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully evaluates pre-condition checks'
+
+ context 'commit history comparison with regular MergeService' do
+ before do
+ # The merge service needs an authorized user while merge-to-ref
+ # doesn't.
+ project.add_maintainer(user)
+ end
+
+ let(:merge_ref_service) do
+ described_class.new(project, user, {})
+ end
+
+ let(:merge_service) do
+ MergeRequests::MergeService.new(project, user, {})
+ end
+
+ context 'when merge commit' do
+ it_behaves_like 'MergeService for target ref'
+ end
+
+ context 'when merge commit with squash', :quarantine do
+ before do
+ merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature')
+ end
+
+ it_behaves_like 'MergeService for target ref'
+ end
+ end
+
+ context 'merge pre-condition checks' do
+ before do
+ merge_request.project.update!(merge_method: merge_method)
+ end
+
+ context 'when semi-linear merge method' do
+ let(:merge_method) { :rebase_merge }
+
+ it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully evaluates pre-condition checks'
+ end
+
+ context 'when fast-forward merge method' do
+ let(:merge_method) { :ff }
+
+ it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully evaluates pre-condition checks'
+ end
+
+ context 'when MR is not mergeable to ref' do
+ let(:merge_method) { :merge }
+
+ it 'returns error' do
+ allow(project).to receive_message_chain(:repository, :merge_to_ref) { nil }
+
+ error_message = 'Conflicts detected during merge'
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+ end
+
+ context 'does not close related todos' do
+ let(:merge_request) { create(:merge_request, assignees: [user], author: user) }
+ let(:project) { merge_request.project }
+ let!(:todo) do
+ create(:todo, :assigned,
+ project: project,
+ author: user,
+ user: user,
+ target: merge_request)
+ end
+
+ before do
+ allow(service).to receive(:execute_hooks)
+
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ todo.reload
+ end
+ end
+
+ it { expect(todo).not_to be_done }
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
deleted file mode 100644
index 52bbd4e794d..00000000000
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-require 'spec_helper'
-
-describe MergeRequests::MergeWhenPipelineSucceedsService do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- let(:mr_merge_if_green_enabled) do
- create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user,
- source_branch: "master", target_branch: 'feature',
- source_project: project, target_project: project, state: "opened")
- end
-
- let(:pipeline) do
- create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch,
- project: project)
- end
-
- let(:service) do
- described_class.new(project, user, commit_message: 'Awesome message')
- end
-
- describe "#execute" do
- let(:merge_request) do
- create(:merge_request, target_project: project, source_project: project,
- source_branch: "feature", target_branch: 'master')
- end
-
- context 'first time enabling' do
- before do
- allow(merge_request)
- .to receive(:head_pipeline)
- .and_return(pipeline)
-
- service.execute(merge_request)
- end
-
- it 'sets the params, merge_user, and flag' do
- expect(merge_request).to be_valid
- expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
- expect(merge_request.merge_params).to eq commit_message: 'Awesome message'
- expect(merge_request.merge_user).to be user
- end
-
- it 'creates a system note' do
- note = merge_request.notes.last
- expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}}
- end
- end
-
- context 'already approved' do
- let(:service) { described_class.new(project, user, new_key: true) }
- let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
-
- before do
- allow(mr_merge_if_green_enabled).to receive(:head_pipeline)
- .and_return(pipeline)
-
- allow(mr_merge_if_green_enabled).to receive(:mergeable?)
- .and_return(true)
-
- allow(pipeline).to receive(:success?).and_return(true)
- end
-
- it 'updates the merge params' do
- expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
-
- service.execute(mr_merge_if_green_enabled)
- expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key)
- end
- end
- end
-
- describe "#trigger" do
- let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch }
- let(:merge_request_head) do
- project.commit(mr_merge_if_green_enabled.source_branch).id
- end
-
- context 'when triggered by pipeline with valid ref and sha' do
- let(:triggering_pipeline) do
- create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: merge_request_head, status: 'success',
- head_pipeline_of: mr_merge_if_green_enabled)
- end
-
- it "merges all merge requests with merge when the pipeline succeeds enabled" do
- expect(MergeWorker).to receive(:perform_async)
- service.trigger(triggering_pipeline)
- end
- end
-
- context 'when triggered by an old pipeline' do
- let(:old_pipeline) do
- create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: '1234abcdef', status: 'success')
- end
-
- it 'it does not merge request' do
- expect(MergeWorker).not_to receive(:perform_async)
- service.trigger(old_pipeline)
- end
- end
-
- context 'when triggered by pipeline from a different branch' do
- let(:unrelated_pipeline) do
- create(:ci_pipeline, project: project, ref: 'feature',
- sha: merge_request_head, status: 'success')
- end
-
- it 'does not merge request' do
- expect(MergeWorker).not_to receive(:perform_async)
- service.trigger(unrelated_pipeline)
- end
- end
- end
-
- describe "#cancel" do
- before do
- service.cancel(mr_merge_if_green_enabled)
- end
-
- it "resets all the pipeline succeeds params" do
- expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey
- expect(mr_merge_if_green_enabled.merge_params).to eq({})
- expect(mr_merge_if_green_enabled.merge_user).to be nil
- end
-
- it 'Posts a system note' do
- note = mr_merge_if_green_enabled.notes.last
- expect(note.note).to include 'canceled the automatic merge'
- end
- end
-
- describe 'pipeline integration' do
- context 'when there are multiple stages in the pipeline' do
- let(:ref) { mr_merge_if_green_enabled.source_branch }
- let(:sha) { project.commit(ref).id }
-
- let(:pipeline) do
- create(:ci_empty_pipeline, ref: ref, sha: sha, project: project)
- end
-
- let!(:build) do
- create(:ci_build, :created, pipeline: pipeline, ref: ref,
- name: 'build', stage: 'build')
- end
-
- let!(:test) do
- create(:ci_build, :created, pipeline: pipeline, ref: ref,
- name: 'test', stage: 'test')
- end
-
- before do
- # This behavior of MergeRequest: we instantiate a new object
- #
- allow_any_instance_of(MergeRequest)
- .to receive(:head_pipeline)
- .and_wrap_original do
- Ci::Pipeline.find(pipeline.id)
- end
- end
-
- it "doesn't merge if any of stages failed" do
- expect(MergeWorker).not_to receive(:perform_async)
-
- build.success
- test.reload
- test.drop
- end
-
- it 'merges when all stages succeeded' do
- expect(MergeWorker).to receive(:perform_async)
-
- build.success
- test.reload
- test.success
- end
- end
- end
-end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
new file mode 100644
index 00000000000..aa0485467ed
--- /dev/null
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -0,0 +1,187 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MergeabilityCheckService do
+ shared_examples_for 'unmergeable merge request' do
+ it 'updates or keeps merge status as cannot_be_merged' do
+ subject
+
+ expect(merge_request.merge_status).to eq('cannot_be_merged')
+ end
+
+ it 'does not change the merge ref HEAD' do
+ expect { subject }.not_to change(merge_request, :merge_ref_head)
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result).to be_error
+ end
+ end
+
+ shared_examples_for 'mergeable merge request' do
+ it 'updates or keeps merge status as can_be_merged' do
+ subject
+
+ expect(merge_request.merge_status).to eq('can_be_merged')
+ end
+
+ it 'updates the merge ref' do
+ expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result).to be_success
+ end
+
+ it 'ServiceResponse has merge_ref_head payload' do
+ result = subject
+
+ expect(result.payload.keys).to contain_exactly(:merge_ref_head)
+ expect(result.payload[:merge_ref_head].keys)
+ .to contain_exactly(:commit_id, :target_id, :source_id)
+ end
+ end
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, merge_status: :unchecked, source_project: project, target_project: project) }
+ let(:repo) { project.repository }
+
+ subject { described_class.new(merge_request).execute }
+
+ before do
+ project.add_developer(merge_request.author)
+ end
+
+ it_behaves_like 'mergeable merge request'
+
+ context 'when multiple calls to the service' do
+ it 'returns success' do
+ subject
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.success?).to be(true)
+ end
+
+ it 'second call does not change the merge-ref' do
+ expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
+ expect { subject }.not_to change(merge_request, :merge_ref_head)
+ end
+ end
+
+ context 'when broken' do
+ before do
+ allow(merge_request).to receive(:broken?) { true }
+ allow(project.repository).to receive(:can_be_merged?) { false }
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when it has conflicts' do
+ before do
+ allow(merge_request).to receive(:broken?) { false }
+ allow(project.repository).to receive(:can_be_merged?) { false }
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when MR cannot be merged and has no merge ref' do
+ before do
+ merge_request.mark_as_unmergeable!
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when MR cannot be merged and has outdated merge ref' do
+ before do
+ MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ merge_request.mark_as_unmergeable!
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when merge request is not given' do
+ subject { described_class.new(nil).execute }
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.message).to eq('Invalid argument')
+ end
+ end
+
+ context 'when read only DB' do
+ it 'returns ServiceResponse.error' do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.message).to eq('Unsupported operation')
+ end
+ end
+
+ context 'when MR is mergeable but merge-ref does not exists' do
+ before do
+ merge_request.mark_as_mergeable!
+ end
+
+ it 'keeps merge status as can_be_merged' do
+ expect { subject }.not_to change(merge_request, :merge_status).from('can_be_merged')
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge ref was not found')
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
new file mode 100644
index 00000000000..40ac747e66f
--- /dev/null
+++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MigrateExternalDiffsService do
+ let(:merge_request) { create(:merge_request) }
+ let(:diff) { merge_request.merge_request_diff }
+
+ describe '.enqueue!', :sidekiq do
+ around do |example|
+ Sidekiq::Testing.fake! { example.run }
+ end
+
+ it 'enqueues nothing if external diffs are disabled' do
+ expect(diff).not_to be_stored_externally
+
+ expect { described_class.enqueue! }
+ .not_to change { MigrateExternalDiffsWorker.jobs.count }
+ end
+
+ it 'enqueues eligible in-database diffs if external diffs are enabled' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+
+ expect { described_class.enqueue! }
+ .to change { MigrateExternalDiffsWorker.jobs.count }
+ .by(1)
+ end
+ end
+
+ describe '#execute' do
+ it 'migrates an in-database diff to the external store' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+
+ described_class.new(diff).execute
+
+ expect(diff).to be_stored_externally
+ end
+ end
+end
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index 5ad6f5528f9..ffc86f68469 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::PostMergeService do
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user) }
+ let(:merge_request) { create(:merge_request, assignees: [user]) }
let(:project) { merge_request.project }
before do
@@ -60,5 +62,13 @@ describe MergeRequests::PostMergeService do
expect(merge_request.reload).to be_merged
end
+
+ it 'clean up environments for the merge request' do
+ expect_next_instance_of(Ci::StopEnvironmentsService) do |service|
+ expect(service).to receive(:execute_for_merge_request).with(merge_request)
+ end
+
+ described_class.new(project, user).execute(merge_request)
+ end
end
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
new file mode 100644
index 00000000000..54b9c6dae38
--- /dev/null
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -0,0 +1,405 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::PushOptionsHandlerService do
+ include ProjectForksHelper
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:forked_project) { fork_project(project, user, repository: true) }
+ let(:service) { described_class.new(project, user, changes, push_options) }
+ let(:source_branch) { 'fix' }
+ let(:target_branch) { 'feature' }
+ let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
+ let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
+ let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
+ let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{project.default_branch}" }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples_for 'a service that can create a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'creates a merge request' do
+ expect { service.execute }.to change { MergeRequest.count }.by(1)
+ end
+
+ it 'sets the correct target branch' do
+ branch = push_options[:target] || project.default_branch
+
+ service.execute
+
+ expect(last_mr.target_branch).to eq(branch)
+ end
+
+ it 'assigns the MR to the user' do
+ service.execute
+
+ expect(last_mr.assignees).to contain_exactly(user)
+ end
+
+ context 'when project has been forked' do
+ let(:forked_project) { fork_project(project, user, repository: true) }
+ let(:service) { described_class.new(forked_project, user, changes, push_options) }
+
+ before do
+ allow(forked_project).to receive(:empty_repo?).and_return(false)
+ end
+
+ it 'sets the correct source project' do
+ service.execute
+
+ expect(last_mr.source_project).to eq(forked_project)
+ end
+
+ it 'sets the correct target project' do
+ service.execute
+
+ expect(last_mr.target_project).to eq(project)
+ end
+ end
+ end
+
+ shared_examples_for 'a service that can set the target of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the target_branch' do
+ service.execute
+
+ expect(last_mr.target_branch).to eq(target_branch)
+ end
+ end
+
+ shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets auto_merge_enabled' do
+ service.execute
+
+ expect(last_mr.auto_merge_enabled).to eq(true)
+ expect(last_mr.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ end
+
+ it 'sets merge_user to the user' do
+ service.execute
+
+ expect(last_mr.merge_user).to eq(user)
+ end
+ end
+
+ shared_examples_for 'a service that does not create a merge request' do
+ it do
+ expect { service.execute }.not_to change { MergeRequest.count }
+ end
+ end
+
+ shared_examples_for 'a service that does not update a merge request' do
+ it do
+ expect { service.execute }.not_to change { MergeRequest.maximum(:updated_at) }
+ end
+ end
+
+ shared_examples_for 'a service that does nothing' do
+ include_examples 'a service that does not create a merge request'
+ include_examples 'a service that does not update a merge request'
+ end
+
+ describe '`create` push option' do
+ let(:push_options) { { create: true } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that can create a merge request'
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that can create a merge request'
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`merge_when_pipeline_succeeds` push option' do
+ let(:push_options) { { merge_when_pipeline_succeeds: true } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`target` push option' do
+ let(:push_options) { { target: target_branch } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, target: target_branch } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the target of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, target: target_branch } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the target of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the target of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe 'multiple pushed branches' do
+ let(:push_options) { { create: true } }
+ let(:changes) do
+ [
+ new_branch_changes,
+ "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/feature_conflict"
+ ]
+ end
+
+ it 'creates a merge request per branch' do
+ expect { service.execute }.to change { MergeRequest.count }.by(2)
+ end
+
+ context 'when there are too many pushed branches' do
+ let(:limit) { MergeRequests::PushOptionsHandlerService::LIMIT }
+ let(:changes) do
+ TestEnv::BRANCH_SHA.to_a[0..limit].map do |x|
+ "#{Gitlab::Git::BLANK_SHA} #{x.first} refs/heads/#{x.last}"
+ end
+ end
+
+ it 'records an error' do
+ service.execute
+
+ expect(service.errors).to eq(["Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})"])
+ end
+ end
+ end
+
+ describe 'no push options' do
+ let(:push_options) { {} }
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ describe 'no user' do
+ let(:user) { nil }
+ let(:push_options) { { create: true } }
+ let(:changes) { new_branch_changes }
+
+ it 'records an error' do
+ service.execute
+
+ expect(service.errors).to eq(['User is required'])
+ end
+ end
+
+ describe 'unauthorized user' do
+ let(:push_options) { { create: true } }
+ let(:changes) { new_branch_changes }
+
+ it 'records an error' do
+ Members::DestroyService.new(user).execute(ProjectMember.find_by!(user_id: user.id))
+
+ service.execute
+
+ expect(service.errors).to eq(['User access was denied'])
+ end
+ end
+
+ describe 'handling unexpected exceptions' do
+ let(:push_options) { { create: true } }
+ let(:changes) { new_branch_changes }
+ let(:exception) { StandardError.new('My standard error') }
+
+ def run_service_with_exception
+ allow_any_instance_of(
+ MergeRequests::BuildService
+ ).to receive(:execute).and_raise(exception)
+
+ service.execute
+ end
+
+ it 'records an error' do
+ run_service_with_exception
+
+ expect(service.errors).to eq(['An unknown error occurred'])
+ end
+
+ it 'writes to Gitlab::AppLogger' do
+ expect(Gitlab::AppLogger).to receive(:error).with(exception)
+
+ run_service_with_exception
+ end
+ end
+
+ describe 'when target is not a valid branch name' do
+ let(:push_options) { { create: true, target: 'my-branch' } }
+ let(:changes) { new_branch_changes }
+
+ it 'records an error' do
+ service.execute
+
+ expect(service.errors).to eq(['Branch my-branch does not exist'])
+ end
+ end
+
+ describe 'when MRs are not enabled' do
+ let(:push_options) { { create: true } }
+ let(:changes) { new_branch_changes }
+
+ it 'records an error' do
+ expect(project).to receive(:merge_requests_enabled?).and_return(false)
+
+ service.execute
+
+ expect(service.errors).to eq(["Merge requests are not enabled for project #{project.full_path}"])
+ end
+ end
+
+ describe 'when MR has ActiveRecord errors' do
+ let(:push_options) { { create: true } }
+ let(:changes) { new_branch_changes }
+
+ it 'adds the error to its errors property' do
+ invalid_merge_request = MergeRequest.new
+ invalid_merge_request.errors.add(:base, 'my error')
+
+ expect_any_instance_of(
+ MergeRequests::CreateService
+ ).to receive(:execute).and_return(invalid_merge_request)
+
+ service.execute
+
+ expect(service.errors).to eq(['my error'])
+ end
+ end
+end
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 427a2d63a88..7e2f03d1097 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::RebaseService do
@@ -36,6 +38,32 @@ describe MergeRequests::RebaseService do
end
end
+ shared_examples 'sequence of failure and success' do
+ it 'properly clears the error message' do
+ allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong')
+
+ service.execute(merge_request)
+
+ expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
+
+ allow(repository).to receive(:gitaly_operation_client).and_call_original
+
+ service.execute(merge_request)
+
+ expect(merge_request.reload.merge_error).to eq nil
+ end
+ end
+
+ it_behaves_like 'sequence of failure and success'
+
+ context 'with deprecated step rebase feature' do
+ before do
+ stub_feature_flags(two_step_rebase: false)
+ end
+
+ it_behaves_like 'sequence of failure and success'
+ end
+
context 'when unexpected error occurs' do
before do
allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong')
@@ -71,7 +99,7 @@ describe MergeRequests::RebaseService do
end
context 'valid params' do
- describe 'successful rebase' do
+ shared_examples_for 'a service that can execute a successful rebase' do
before do
service.execute(merge_request)
end
@@ -97,6 +125,22 @@ describe MergeRequests::RebaseService do
end
end
+ context 'when the two_step_rebase feature is enabled' do
+ before do
+ stub_feature_flags(two_step_rebase: true)
+ end
+
+ it_behaves_like 'a service that can execute a successful rebase'
+ end
+
+ context 'when the two_step_rebase feature is disabled' do
+ before do
+ stub_feature_flags(two_step_rebase: false)
+ end
+
+ it_behaves_like 'a service that can execute a successful rebase'
+ end
+
context 'fork' do
describe 'successful fork rebase' do
let(:forked_project) do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 9e9dc5a576c..6ba67c7165c 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::RefreshService do
@@ -21,7 +23,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master',
target_branch: 'feature',
target_project: @project,
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user)
@another_merge_request = create(:merge_request,
@@ -29,7 +32,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master',
target_branch: 'test',
target_project: @project,
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user)
@fork_merge_request = create(:merge_request,
@@ -81,7 +85,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open
- expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
+ expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
@@ -97,6 +101,15 @@ describe MergeRequests::RefreshService do
}
end
+ it 'outdates MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@merge_request).and_call_original
+ expect(service).to receive(:execute).with(@another_merge_request).and_call_original
+ end
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ end
+
context 'when source branch ref does not exists' do
before do
DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
@@ -132,12 +145,15 @@ describe MergeRequests::RefreshService do
end
end
- describe 'Merge request pipelines' do
+ describe 'Pipelines for merge requests' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
- subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') }
+ subject { service.new(project, @user).execute(@oldrev, @newrev, ref) }
+
+ let(:ref) { 'refs/heads/master' }
+ let(:project) { @project }
context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do
@@ -150,18 +166,62 @@ describe MergeRequests::RefreshService do
}
end
- it 'create merge request pipeline with commits' do
+ it 'create detached merge request pipeline with commits' do
expect { subject }
- .to change { @merge_request.merge_request_pipelines.count }.by(1)
- .and change { @fork_merge_request.merge_request_pipelines.count }.by(1)
- .and change { @another_merge_request.merge_request_pipelines.count }.by(0)
+ .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
+ .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0)
expect(@merge_request.has_commits?).to be_truthy
- expect(@fork_merge_request.has_commits?).to be_truthy
expect(@another_merge_request.has_commits?).to be_falsy
end
- context "when branch pipeline was created before a merge request pipline has been created" do
+ it 'does not create detached merge request pipeline for forked project' do
+ expect { subject }
+ .not_to change { @fork_merge_request.pipelines_for_merge_request.count }
+ end
+
+ it 'create detached merge request pipeline for non-fork merge request' do
+ subject
+
+ expect(@merge_request.pipelines_for_merge_request.first)
+ .to be_detached_merge_request_pipeline
+ end
+
+ context 'when service is hooked by target branch' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it 'does not create detached merge request pipeline' do
+ expect { subject }
+ .not_to change { @merge_request.pipelines_for_merge_request.count }
+ end
+ end
+
+ context 'when service runs on forked project' do
+ let(:project) { @fork_project }
+
+ it 'creates legacy detached merge request pipeline for fork merge request' do
+ expect { subject }
+ .to change { @fork_merge_request.pipelines_for_merge_request.count }.by(1)
+
+ expect(@fork_merge_request.pipelines_for_merge_request.first)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+ end
+
+ context 'when ci_use_merge_request_ref feature flag is false' do
+ before do
+ stub_feature_flags(ci_use_merge_request_ref: false)
+ end
+
+ it 'create legacy detached merge request pipeline for non-fork merge request' do
+ subject
+
+ expect(@merge_request.pipelines_for_merge_request.first)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+ end
+
+ context "when branch pipeline was created before a detaced merge request pipeline has been created" do
before do
create(:ci_pipeline, project: @merge_request.source_project,
sha: @merge_request.diff_head_sha,
@@ -171,38 +231,27 @@ describe MergeRequests::RefreshService do
subject
end
- it 'sets the latest merge request pipeline as a head pipeline' do
+ it 'sets the latest detached merge request pipeline as a head pipeline' do
@merge_request.reload
- expect(@merge_request.actual_head_pipeline).to be_merge_request
+ expect(@merge_request.actual_head_pipeline).to be_merge_request_event
end
it 'returns pipelines in correct order' do
@merge_request.reload
- expect(@merge_request.all_pipelines.first).to be_merge_request
+ expect(@merge_request.all_pipelines.first).to be_merge_request_event
expect(@merge_request.all_pipelines.second).to be_push
end
end
context "when MergeRequestUpdateWorker is retried by an exception" do
- it 'does not re-create a duplicate merge request pipeline' do
+ it 'does not re-create a duplicate detached merge request pipeline' do
expect do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
- end.to change { @merge_request.merge_request_pipelines.count }.by(1)
+ end.to change { @merge_request.pipelines_for_merge_request.count }.by(1)
expect do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
- end.not_to change { @merge_request.merge_request_pipelines.count }
- end
- end
-
- context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
- before do
- stub_feature_flags(ci_merge_request_pipeline: false)
- end
-
- it 'does not create a merge request pipeline' do
- expect { subject }
- .not_to change { @merge_request.merge_request_pipelines.count }
+ end.not_to change { @merge_request.pipelines_for_merge_request.count }
end
end
end
@@ -217,20 +266,18 @@ describe MergeRequests::RefreshService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect { subject }
- .not_to change { @merge_request.merge_request_pipelines.count }
+ .not_to change { @merge_request.pipelines_for_merge_request.count }
end
end
end
- context 'push to origin repo source branch when an MR was reopened' do
+ context 'push to origin repo source branch' do
let(:refresh_service) { service.new(@project, @user) }
let(:notification_service) { spy('notification_service') }
before do
- @merge_request.update(state: :reopened)
-
allow(refresh_service).to receive(:execute_hooks)
allow(NotificationService).to receive(:new) { notification_service }
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
@@ -247,7 +294,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open
- expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
+ expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
@@ -329,14 +376,16 @@ describe MergeRequests::RefreshService do
context 'push to fork repo source branch' do
let(:refresh_service) { service.new(@fork_project, @user) }
- context 'open fork merge request' do
- before do
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
- end
+ def refresh
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+ context 'open fork merge request' do
it 'executes hooks with update action' do
+ refresh
+
expect(refresh_service).to have_received(:execute_hooks)
.with(@fork_merge_request, 'update', old_rev: @oldrev)
@@ -347,21 +396,30 @@ describe MergeRequests::RefreshService do
expect(@build_failed_todo).to be_pending
expect(@fork_build_failed_todo).to be_pending
end
+
+ it 'outdates opened forked MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@fork_merge_request).and_call_original
+ end
+
+ refresh
+ end
end
context 'closed fork merge request' do
before do
@fork_merge_request.close!
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
end
it 'do not execute hooks with update action' do
+ refresh
+
expect(refresh_service).not_to have_received(:execute_hooks)
end
it 'updates merge request to closed state' do
+ refresh
+
expect(@merge_request.notes).to be_empty
expect(@merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
@@ -418,35 +476,35 @@ describe MergeRequests::RefreshService do
end
let(:force_push_commit) { @project.commit('feature').id }
- it 'should reload a new diff for a push to the forked project' do
+ it 'reloads a new diff for a push to the forked project' do
expect do
service.new(@fork_project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
- it 'should reload a new diff for a force push to the source branch' do
+ it 'reloads a new diff for a force push to the source branch' do
expect do
service.new(@fork_project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
- it 'should reload a new diff for a force push to the target branch' do
+ it 'reloads a new diff for a force push to the target branch' do
expect do
service.new(@project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
- it 'should reload a new diff for a push to the target project that contains a commit in the MR' do
+ it 'reloads a new diff for a push to the target project that contains a commit in the MR' do
expect do
service.new(@project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
- it 'should not increase the diff count for a new push to target branch' do
+ it 'does not increase the diff count for a new push to target branch' do
new_commit = @project.repository.create_file(@user, 'new-file.txt', 'A new file',
message: 'This is a test',
branch_name: 'master')
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index 5acd01828cb..cc21348ab11 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 21e71509ed6..7a98437f724 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, :closed, assignee: user2, author: create(:user)) }
+ let(:merge_request) { create(:merge_request, :closed, assignees: [user2], author: create(:user)) }
let(:project) { merge_request.project }
before do
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
index e3fd906fe7b..0a10a9ee13b 100644
--- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
+++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::ResolvedDiscussionNotificationService do
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index 2713652873e..cb278eec692 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::SquashService do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 20580bf14b9..fbfcd95e204 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequests::UpdateService, :mailer do
@@ -13,7 +15,7 @@ describe MergeRequests::UpdateService, :mailer do
let(:merge_request) do
create(:merge_request, :simple, title: 'Old title',
description: "FYI #{user2.to_reference}",
- assignee_id: user3.id,
+ assignee_ids: [user3.id],
source_project: project,
author: create(:user))
end
@@ -48,7 +50,7 @@ describe MergeRequests::UpdateService, :mailer do
{
title: 'New title',
description: 'Also please fix',
- assignee_id: user2.id,
+ assignee_ids: [user.id],
state_event: 'close',
label_ids: [label.id],
target_branch: 'target',
@@ -71,7 +73,7 @@ describe MergeRequests::UpdateService, :mailer do
it 'matches base expectations' do
expect(@merge_request).to be_valid
expect(@merge_request.title).to eq('New title')
- expect(@merge_request.assignee).to eq(user2)
+ expect(@merge_request.assignees).to match_array([user])
expect(@merge_request).to be_closed
expect(@merge_request.labels.count).to eq(1)
expect(@merge_request.labels.first.title).to eq(label.name)
@@ -106,7 +108,7 @@ describe MergeRequests::UpdateService, :mailer do
note = find_note('assigned to')
expect(note).not_to be_nil
- expect(note.note).to include "assigned to #{user2.to_reference}"
+ expect(note.note).to include "assigned to #{user.to_reference} and unassigned #{user3.to_reference}"
end
it 'creates a resource label event' do
@@ -215,8 +217,9 @@ describe MergeRequests::UpdateService, :mailer do
head_pipeline_of: merge_request
)
- expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user)
+ expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user, {})
.and_return(service_mock)
+ allow(service_mock).to receive(:available_for?) { true }
expect(service_mock).to receive(:execute).with(merge_request)
end
@@ -293,7 +296,7 @@ describe MergeRequests::UpdateService, :mailer do
context 'when is reassigned' do
before do
- update_merge_request({ assignee: user2 })
+ update_merge_request({ assignee_ids: [user2.id] })
end
it 'marks previous assignee pending todos as done' do
@@ -328,7 +331,7 @@ describe MergeRequests::UpdateService, :mailer do
it_behaves_like 'system notes for milestones'
it 'sends notifications for subscribers of changed milestone' do
- merge_request.milestone = create(:milestone)
+ merge_request.milestone = create(:milestone, project: project)
merge_request.save
@@ -352,7 +355,7 @@ describe MergeRequests::UpdateService, :mailer do
end
it 'marks pending todos as done' do
- update_merge_request({ milestone: create(:milestone) })
+ update_merge_request({ milestone: create(:milestone, project: project) })
expect(pending_todo.reload).to be_done
end
@@ -361,7 +364,7 @@ describe MergeRequests::UpdateService, :mailer do
it 'sends notifications for subscribers of changed milestone' do
perform_enqueued_jobs do
- update_merge_request(milestone: create(:milestone))
+ update_merge_request(milestone: create(:milestone, project: project))
end
should_email(subscriber)
@@ -387,7 +390,7 @@ describe MergeRequests::UpdateService, :mailer do
context 'when the assignee changes' do
it 'updates open merge request counter for assignees when merge request is reassigned' do
- update_merge_request(assignee_id: user2.id)
+ update_merge_request(assignee_ids: [user2.id])
expect(user3.assigned_open_merge_requests_count).to eq 0
expect(user2.assigned_open_merge_requests_count).to eq 1
@@ -405,7 +408,7 @@ describe MergeRequests::UpdateService, :mailer do
end
end
- context 'when the issue is relabeled' do
+ context 'when the merge request is relabeled' do
let!(:non_subscriber) { create(:user) }
let!(:subscriber) { create(:user) { |u| label.toggle_subscription(u, project) } }
@@ -541,36 +544,36 @@ describe MergeRequests::UpdateService, :mailer do
end
end
- context 'updating asssignee_id' do
+ context 'updating asssignee_ids' do
it 'does not update assignee when assignee_id is invalid' do
- merge_request.update(assignee_id: user.id)
+ merge_request.update(assignee_ids: [user.id])
- update_merge_request(assignee_id: -1)
+ update_merge_request(assignee_ids: [-1])
- expect(merge_request.reload.assignee).to eq(user)
+ expect(merge_request.reload.assignees).to eq([user])
end
it 'unassigns assignee when user id is 0' do
- merge_request.update(assignee_id: user.id)
+ merge_request.update(assignee_ids: [user.id])
- update_merge_request(assignee_id: 0)
+ update_merge_request(assignee_ids: [0])
- expect(merge_request.assignee_id).to be_nil
+ expect(merge_request.assignee_ids).to be_empty
end
it 'saves assignee when user id is valid' do
- update_merge_request(assignee_id: user.id)
+ update_merge_request(assignee_ids: [user.id])
- expect(merge_request.assignee_id).to eq(user.id)
+ expect(merge_request.assignee_ids).to eq([user.id])
end
it 'does not update assignee_id when user cannot read issue' do
- non_member = create(:user)
- original_assignee = merge_request.assignee
+ non_member = create(:user)
+ original_assignees = merge_request.assignees
- update_merge_request(assignee_id: non_member.id)
+ update_merge_request(assignee_ids: [non_member.id])
- expect(merge_request.assignee_id).to eq(original_assignee.id)
+ expect(merge_request.reload.assignees).to eq(original_assignees)
end
context "when issuable feature is private" do
@@ -583,7 +586,7 @@ describe MergeRequests::UpdateService, :mailer 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_ids: [assignee]) }.not_to change { merge_request.reload.assignees }
end
end
end
@@ -619,7 +622,7 @@ describe MergeRequests::UpdateService, :mailer do
end
it 'is allowed by a user that can push to the source and can update the merge request' do
- merge_request.update!(assignee: user)
+ merge_request.update!(assignees: [user])
source_project.add_developer(user)
update_merge_request(allow_collaboration: true, title: 'Updated title')
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index 3f7a544ea0a..55e705063b2 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestones::CloseService do
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 0c91112026f..97f6e947539 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestones::CreateService do
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
index 9d2be30c636..3a22e4d4f92 100644
--- a/spec/services/milestones/destroy_service_spec.rb
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestones::DestroyService do
diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb
index df212d912e9..22c7e9dde30 100644
--- a/spec/services/milestones/promote_service_spec.rb
+++ b/spec/services/milestones/promote_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestones::PromoteService do
diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb
index a6cc2251e48..e59731207a5 100644
--- a/spec/services/note_summary_spec.rb
+++ b/spec/services/note_summary_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NoteSummary do
@@ -21,16 +23,20 @@ describe NoteSummary do
describe '#note' do
it 'returns note hash' do
- expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note')
+ Timecop.freeze do
+ expect(create_note_summary.note).to eq(noteable: noteable, project: project, author: user, note: 'note',
+ created_at: Time.now)
+ end
end
context 'when noteable is a commit' do
- let(:noteable) { build(:commit) }
+ let(:noteable) { build(:commit, system_note_timestamp: Time.at(43)) }
it 'returns note hash specific to commit' do
expect(create_note_summary.note).to eq(
noteable: nil, project: project, author: user, note: 'note',
- noteable_type: 'Commit', commit_id: noteable.id
+ noteable_type: 'Commit', commit_id: noteable.id,
+ created_at: Time.at(43)
)
end
end
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index af4daff336b..984658cbd19 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::BuildService do
@@ -128,37 +130,19 @@ describe Notes::BuildService do
subject { described_class.new(project, author, note: 'Test', in_reply_to_discussion_id: note.discussion_id).execute }
- shared_examples 'an individual note reply' do
- it 'builds another individual note' do
- expect(subject).to be_valid
- expect(subject).to be_a(Note)
- expect(subject.discussion_id).not_to eq(note.discussion_id)
- end
- end
-
- context 'when reply_to_individual_notes is disabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: false)
- end
-
- it_behaves_like 'an individual note reply'
+ it 'sets the note up to be in reply to that note' do
+ expect(subject).to be_valid
+ expect(subject).to be_a(DiscussionNote)
+ expect(subject.discussion_id).to eq(note.discussion_id)
end
- context 'when reply_to_individual_notes is enabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: true)
- end
+ context 'when noteable does not support replies' do
+ let(:note) { create(:note_on_commit) }
- it 'sets the note up to be in reply to that note' do
+ it 'builds another individual note' do
expect(subject).to be_valid
- expect(subject).to be_a(DiscussionNote)
- expect(subject.discussion_id).to eq(note.discussion_id)
- end
-
- context 'when noteable does not support replies' do
- let(:note) { create(:note_on_commit) }
-
- it_behaves_like 'an individual note reply'
+ expect(subject).to be_a(Note)
+ expect(subject.discussion_id).not_to eq(note.discussion_id)
end
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 1645b67c329..494ca95f66d 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::CreateService do
@@ -220,6 +222,19 @@ describe Notes::CreateService do
expect(note.note).to eq "HELLO\nWORLD"
end
end
+
+ context 'when note only have commands' do
+ it 'adds commands applied message to note errors' do
+ note_text = %(/close)
+ service = double(:service)
+ allow(Issues::UpdateService).to receive(:new).and_return(service)
+ expect(service).to receive(:execute)
+
+ note = described_class.new(project, user, opts.merge(note: note_text)).execute
+
+ expect(note.errors[:commands_only]).to be_present
+ end
+ end
end
context 'as a user who cannot update the target' do
@@ -285,41 +300,20 @@ describe Notes::CreateService do
subject { described_class.new(project, user, reply_opts).execute }
- context 'when reply_to_individual_notes is disabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: false)
- end
-
- it 'creates an individual note' do
- expect(subject.type).to eq(nil)
- expect(subject.discussion_id).not_to eq(existing_note.discussion_id)
- end
-
- it 'does not convert existing note' do
- expect { subject }.not_to change { existing_note.reload.type }
- end
+ it 'creates a DiscussionNote in reply to existing note' do
+ expect(subject).to be_a(DiscussionNote)
+ expect(subject.discussion_id).to eq(existing_note.discussion_id)
end
- context 'when reply_to_individual_notes is enabled' do
- before do
- stub_feature_flags(reply_to_individual_notes: true)
- end
-
- it 'creates a DiscussionNote in reply to existing note' do
- expect(subject).to be_a(DiscussionNote)
- expect(subject.discussion_id).to eq(existing_note.discussion_id)
- end
+ it 'converts existing note to DiscussionNote' do
+ expect do
+ existing_note
- it 'converts existing note to DiscussionNote' do
- expect do
- existing_note
+ Timecop.freeze(Time.now + 1.minute) { subject }
- Timecop.freeze(Time.now + 1.minute) { subject }
-
- existing_note.reload
- end.to change { existing_note.type }.from(nil).to('DiscussionNote')
- .and change { existing_note.updated_at }
- end
+ existing_note.reload
+ end.to change { existing_note.type }.from(nil).to('DiscussionNote')
+ .and change { existing_note.updated_at }
end
end
end
diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index b1f4e87e8ea..9faf1299ef2 100644
--- a/spec/services/notes/destroy_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::DestroyService do
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index 5aae0d711c3..99db7897664 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::PostProcessService do
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 14d62763a5b..7cb0bd41f13 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::QuickActionsService do
@@ -28,8 +30,8 @@ describe Notes::QuickActionsService do
end
it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do
- content, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ content, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(content).to eq ''
expect(note.noteable).to be_closed
@@ -47,8 +49,8 @@ describe Notes::QuickActionsService do
let(:note_text) { '/reopen' }
it 'opens the noteable, and leave no note' do
- content, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ content, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(content).to eq ''
expect(note.noteable).to be_open
@@ -59,8 +61,8 @@ describe Notes::QuickActionsService do
let(:note_text) { '/spend 1h' }
it 'updates the spent time on the noteable' do
- content, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ content, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(content).to eq ''
expect(note.noteable.time_spent).to eq(3600)
@@ -75,8 +77,8 @@ describe Notes::QuickActionsService do
end
it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do
- content, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ content, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(content).to eq "HELLO\nWORLD"
expect(note.noteable).to be_closed
@@ -94,8 +96,8 @@ describe Notes::QuickActionsService do
let(:note_text) { "HELLO\n/reopen\nWORLD" }
it 'opens the noteable' do
- content, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ content, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(content).to eq "HELLO\nWORLD"
expect(note.noteable).to be_open
@@ -185,13 +187,14 @@ describe Notes::QuickActionsService do
end
before do
+ stub_licensed_features(multiple_issue_assignees: false)
project.add_maintainer(maintainer)
project.add_maintainer(assignee)
end
it 'adds only one assignee from the list' do
- _, command_params = service.extract_commands(note)
- service.execute(command_params, note)
+ _, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
expect(note.noteable.assignees.count).to eq(1)
end
diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb
index f771620bc0d..ad69721d876 100644
--- a/spec/services/notes/render_service_spec.rb
+++ b/spec/services/notes/render_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::RenderService do
diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb
index b54d40a7a5c..3f82e1dbdc0 100644
--- a/spec/services/notes/resolve_service_spec.rb
+++ b/spec/services/notes/resolve_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::ResolveService do
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
index fd9bff46a06..717eb97fa5a 100644
--- a/spec/services/notes/update_service_spec.rb
+++ b/spec/services/notes/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Notes::UpdateService do
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
index cea5ea125b9..9c2283f555b 100644
--- a/spec/services/notification_recipient_service_spec.rb
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotificationRecipientService do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 6a5a6989607..4b40c86574f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotificationService, :mailer do
include EmailSpec::Matchers
+ include ExternalAuthorizationServiceHelpers
include NotificationHelpers
let(:notification) { described_class.new }
@@ -125,11 +128,7 @@ describe NotificationService, :mailer do
shared_examples 'participating by assignee notification' do
it 'emails the participant' do
- if issuable.is_a?(Issue)
- issuable.assignees << participant
- else
- issuable.update_attribute(:assignee, participant)
- end
+ issuable.assignees << participant
notification_trigger
@@ -177,7 +176,7 @@ describe NotificationService, :mailer do
end
end
- context 'when recieving a non-existent method' do
+ context 'when receiving a non-existent method' do
it 'raises NoMethodError' do
expect { async.foo(key) }.to raise_error(NoMethodError)
end
@@ -620,13 +619,13 @@ describe NotificationService, :mailer do
context "merge request diff note" do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, assignee: user, author: create(:user)) }
+ let(:merge_request) { create(:merge_request, source_project: project, assignees: [user], author: create(:user)) }
let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
before do
build_team(note.project)
project.add_maintainer(merge_request.author)
- project.add_maintainer(merge_request.assignee)
+ merge_request.assignees.each { |assignee| project.add_maintainer(assignee) }
end
describe '#new_note' do
@@ -637,13 +636,71 @@ describe NotificationService, :mailer do
notification.new_note(note)
expect(SentNotification.last(3).map(&:recipient).map(&:id))
- .to contain_exactly(merge_request.assignee.id, merge_request.author.id, @u_watcher.id)
+ .to contain_exactly(*merge_request.assignees.pluck(:id), merge_request.author.id, @u_watcher.id)
expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id)
end
end
end
end
+ describe 'Participating project notification settings have priority over group and global settings if available' do
+ let!(:group) { create(:group) }
+ let!(:maintainer) { group.add_owner(create(:user, username: 'maintainer')).user }
+ let!(:user1) { group.add_developer(create(:user, username: 'user_with_project_and_custom_setting')).user }
+
+ let(:project) { create(:project, :public, namespace: group) }
+ let(:issue) { create :issue, project: project, assignees: [assignee], description: '' }
+
+ before do
+ reset_delivered_emails!
+
+ create_notification_setting(user1, project, :participating)
+ end
+
+ context 'custom on group' do
+ [nil, true].each do |new_issue_value|
+ value_caption = new_issue_value || 'nil'
+ it "does not send an email to user1 when a new issue is created and new_issue is set to #{value_caption}" do
+ update_custom_notification(:new_issue, user1, resource: group, value: new_issue_value)
+
+ notification.new_issue(issue, maintainer)
+ should_not_email(user1)
+ end
+ end
+ end
+
+ context 'watch on group' do
+ it 'does not send an email' do
+ user1.notification_settings_for(group).update!(level: :watch)
+
+ notification.new_issue(issue, maintainer)
+ should_not_email(user1)
+ end
+ end
+
+ context 'custom on global, global on group' do
+ it 'does not send an email' do
+ user1.notification_settings_for(nil).update!(level: :custom)
+
+ user1.notification_settings_for(group).update!(level: :global)
+
+ notification.new_issue(issue, maintainer)
+ should_not_email(user1)
+ end
+ end
+
+ context 'watch on global, global on group' do
+ it 'does not send an email' do
+ user1.notification_settings_for(nil).update!(level: :watch)
+
+ user1.notification_settings_for(group).update!(level: :global)
+
+ notification.new_issue(issue, maintainer)
+ should_not_email(user1)
+ end
+ end
+ end
+
describe 'Issues' do
let(:group) { create(:group) }
let(:project) { create(:project, :public, namespace: group) }
@@ -661,7 +718,7 @@ describe NotificationService, :mailer do
end
describe '#new_issue' do
- it do
+ it 'notifies the expected users' do
notification.new_issue(issue, @u_disabled)
should_email(assignee)
@@ -1223,11 +1280,12 @@ describe NotificationService, :mailer do
let(:group) { create(:group) }
let(:project) { create(:project, :public, :repository, namespace: group) }
let(:another_project) { create(:project, :public, namespace: group) }
- let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user), description: 'cc @participant' }
+ let(:assignee) { create(:user) }
+ let(:merge_request) { create :merge_request, source_project: project, assignees: [assignee], description: 'cc @participant' }
before do
project.add_maintainer(merge_request.author)
- project.add_maintainer(merge_request.assignee)
+ merge_request.assignees.each { |assignee| project.add_maintainer(assignee) }
build_team(merge_request.target_project)
add_users_with_subscription(merge_request.target_project, merge_request)
update_custom_notification(:new_merge_request, @u_guest_custom, resource: project)
@@ -1239,7 +1297,7 @@ describe NotificationService, :mailer do
it do
notification.new_merge_request(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_watcher)
should_email(@watcher_and_subscriber)
should_email(@u_participant_mentioned)
@@ -1254,9 +1312,11 @@ describe NotificationService, :mailer do
it 'adds "assigned" reason for assignee, if any' do
notification.new_merge_request(merge_request, @u_disabled)
- email = find_email_for(merge_request.assignee)
+ merge_request.assignees.each do |assignee|
+ email = find_email_for(assignee)
- expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
end
it "emails any mentioned users with the mention level" do
@@ -1347,9 +1407,9 @@ describe NotificationService, :mailer do
end
it do
- notification.reassigned_merge_request(merge_request, current_user, merge_request.author)
+ notification.reassigned_merge_request(merge_request, current_user, [assignee])
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(merge_request.author)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
@@ -1365,17 +1425,19 @@ describe NotificationService, :mailer do
end
it 'adds "assigned" reason for new assignee' do
- notification.reassigned_merge_request(merge_request, current_user, merge_request.author)
+ notification.reassigned_merge_request(merge_request, current_user, [assignee])
- email = find_email_for(merge_request.assignee)
+ merge_request.assignees.each do |assignee|
+ email = find_email_for(assignee)
- expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
end
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
- let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, merge_request.author) }
+ let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) }
end
end
@@ -1388,7 +1450,7 @@ describe NotificationService, :mailer do
it do
notification.push_to_merge_request(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_guest_custom)
should_email(@u_custom_global)
should_email(@u_participant_mentioned)
@@ -1430,7 +1492,7 @@ describe NotificationService, :mailer do
should_email(subscriber_1_to_group_label_2)
should_email(subscriber_2_to_group_label_2)
should_email(subscriber_to_label_2)
- should_not_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_not_email(assignee) }
should_not_email(merge_request.author)
should_not_email(@u_watcher)
should_not_email(@u_participant_mentioned)
@@ -1499,7 +1561,7 @@ describe NotificationService, :mailer do
it do
notification.close_mr(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_watcher)
should_email(@u_guest_watcher)
should_email(@u_guest_custom)
@@ -1529,7 +1591,7 @@ describe NotificationService, :mailer do
it do
notification.merge_mr(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_watcher)
should_email(@u_guest_watcher)
should_email(@u_guest_custom)
@@ -1581,7 +1643,7 @@ describe NotificationService, :mailer do
it do
notification.reopen_mr(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
@@ -1606,7 +1668,7 @@ describe NotificationService, :mailer do
it do
notification.resolve_all_discussions(merge_request, @u_disabled)
- should_email(merge_request.assignee)
+ merge_request.assignees.each { |assignee| should_email(assignee) }
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
@@ -1635,7 +1697,7 @@ describe NotificationService, :mailer do
end
describe '#project_was_moved' do
- it do
+ it 'notifies the expected users' do
notification.project_was_moved(project, "gitlab/gitlab")
should_email(@u_watcher)
@@ -1850,8 +1912,8 @@ describe NotificationService, :mailer do
let(:guest) { create(:user) }
let(:developer) { create(:user) }
let(:assignee) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) }
- let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") }
+ let(:merge_request) { create(:merge_request, source_project: private_project, assignees: [assignee]) }
+ let(:merge_request1) { create(:merge_request, source_project: private_project, assignees: [assignee], description: "cc @#{guest.username}") }
let(:note) { create(:note, noteable: merge_request, project: private_project) }
before do
@@ -2217,6 +2279,46 @@ describe NotificationService, :mailer do
end
end
+ context 'with external authorization service' do
+ let(:issue) { create(:issue) }
+ let(:project) { issue.project }
+ let(:note) { create(:note, noteable: issue, project: project) }
+ let(:member) { create(:user) }
+
+ subject { NotificationService.new }
+
+ before do
+ project.add_maintainer(member)
+ member.global_notification_setting.update!(level: :watch)
+ end
+
+ it 'sends email when the service is not enabled' do
+ expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original
+
+ subject.new_issue(issue, member)
+ end
+
+ context 'when the service is enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'does not send an email' do
+ expect(Notify).not_to receive(:new_issue_email)
+
+ subject.new_issue(issue, member)
+ end
+
+ it 'still delivers email to admins' do
+ member.update!(admin: true)
+
+ expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original
+
+ subject.new_issue(issue, member)
+ end
+ end
+ end
+
def build_team(project)
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create_global_setting_for(create(:user), :participating)
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
new file mode 100644
index 00000000000..d59aa9b979e
--- /dev/null
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::CreateAcmeOrderService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain) }
+
+ let(:challenge) { ::Gitlab::LetsEncrypt::Challenge.new(acme_challenge_double) }
+
+ let(:order_double) do
+ Gitlab::LetsEncrypt::Order.new(acme_order_double).tap do |order|
+ allow(order).to receive(:new_challenge).and_return(challenge)
+ end
+ end
+
+ let(:lets_encrypt_client) do
+ instance_double('Gitlab::LetsEncrypt::Client').tap do |client|
+ allow(client).to receive(:new_order).with(pages_domain.domain)
+ .and_return(order_double)
+ end
+ end
+
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ allow(::Gitlab::LetsEncrypt::Client).to receive(:new).and_return(lets_encrypt_client)
+ end
+
+ it 'saves order to database before requesting validation' do
+ allow(pages_domain.acme_orders).to receive(:create!).and_call_original
+ allow(challenge).to receive(:request_validation).and_call_original
+
+ service.execute
+
+ expect(pages_domain.acme_orders).to have_received(:create!).ordered
+ expect(challenge).to have_received(:request_validation).ordered
+ end
+
+ it 'generates and saves private key' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error
+ end
+
+ it 'properly saves order attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.url).to eq(order_double.url)
+ expect(saved_order.expires_at).to be_like_time(order_double.expires)
+ end
+
+ it 'properly saves challenge attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.challenge_token).to eq(challenge.token)
+ expect(saved_order.challenge_file_content).to eq(challenge.file_content)
+ end
+end
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
new file mode 100644
index 00000000000..6d7be27939c
--- /dev/null
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::ObtainLetsEncryptCertificateService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) }
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ def expect_to_create_acme_challenge
+ expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain)
+ .and_wrap_original do |m, *args|
+ create_service = m.call(*args)
+
+ expect(create_service).to receive(:execute)
+
+ create_service
+ end
+ end
+
+ def stub_lets_encrypt_order(url, status)
+ order = ::Gitlab::LetsEncrypt::Order.new(acme_order_double(status: status))
+
+ allow_any_instance_of(::Gitlab::LetsEncrypt::Client).to(
+ receive(:load_order).with(url).and_return(order)
+ )
+
+ order
+ end
+
+ context 'when there is no acme order' do
+ it 'creates acme order' do
+ expect_to_create_acme_challenge
+
+ service.execute
+ end
+ end
+
+ context 'when there is expired acme order' do
+ let!(:existing_order) do
+ create(:pages_domain_acme_order, :expired, pages_domain: pages_domain)
+ end
+
+ it 'removes acme order and creates new one' do
+ expect_to_create_acme_challenge
+
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+
+ %w(pending processing).each do |status|
+ context "there is an order in '#{status}' status" do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ before do
+ stub_lets_encrypt_order(existing_order.url, status)
+ end
+
+ it 'does not raise errors' do
+ expect do
+ service.execute
+ end.not_to raise_error
+ end
+ end
+ end
+
+ context 'when order is ready' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'ready')
+ end
+
+ it 'request certificate' do
+ expect(api_order).to receive(:request_certificate).and_call_original
+
+ service.execute
+ end
+ end
+
+ context 'when order is valid' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'valid')
+ end
+
+ let(:certificate) do
+ key = OpenSSL::PKey.read(existing_order.private_key)
+
+ subject = "/C=BE/O=Test/OU=Test/CN=#{pages_domain.domain}"
+
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
+ cert.not_before = Time.now
+ cert.not_after = 1.year.from_now
+ cert.public_key = key.public_key
+ cert.serial = 0x0
+ cert.version = 2
+
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = cert
+ ef.issuer_certificate = cert
+ cert.extensions = [
+ ef.create_extension("basicConstraints", "CA:TRUE", true),
+ ef.create_extension("subjectKeyIdentifier", "hash")
+ ]
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+
+ cert.sign key, OpenSSL::Digest::SHA1.new
+
+ cert.to_pem
+ end
+
+ before do
+ expect(api_order).to receive(:certificate) { certificate }
+ end
+
+ it 'saves private_key and certificate for domain' do
+ service.execute
+
+ expect(pages_domain.key).to be_present
+ expect(pages_domain.certificate).to eq(certificate)
+ end
+
+ it 'removes order from database' do
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+end
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index 85515d548a7..d25e9958831 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PreviewMarkdownService do
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
before do
project.add_developer(user)
@@ -20,23 +22,74 @@ describe PreviewMarkdownService do
end
describe 'suggestions' do
- let(:params) { { text: "```suggestion\nfoo\n```", preview_suggestions: preview_suggestions } }
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project)
+ end
+ let(:text) { "```suggestion\nfoo\n```" }
+ let(:params) do
+ suggestion_params.merge(text: text,
+ target_type: 'MergeRequest',
+ target_id: merge_request.iid)
+ end
let(:service) { described_class.new(project, user, params) }
context 'when preview markdown param is present' do
- let(:preview_suggestions) { true }
+ let(:path) { "files/ruby/popen.rb" }
+ let(:line) { 10 }
+ let(:diff_refs) { merge_request.diff_refs }
+
+ let(:suggestion_params) do
+ {
+ preview_suggestions: true,
+ file_path: path,
+ line: line,
+ base_sha: diff_refs.base_sha,
+ start_sha: diff_refs.start_sha,
+ head_sha: diff_refs.head_sha
+ }
+ end
+
+ it 'returns suggestions referenced in text' do
+ position = Gitlab::Diff::Position.new(new_path: path,
+ new_line: line,
+ diff_refs: diff_refs)
+
+ expect(Gitlab::Diff::SuggestionsParser)
+ .to receive(:parse)
+ .with(text, position: position,
+ project: merge_request.project,
+ supports_suggestion: true)
+ .and_call_original
- it 'returns users referenced in text' do
result = service.execute
- expect(result[:suggestions]).to eq(['foo'])
+ expect(result[:suggestions]).to all(be_a(Gitlab::Diff::Suggestion))
+ end
+
+ context 'when user is not authorized' do
+ let(:another_user) { create(:user) }
+ let(:service) { described_class.new(project, another_user, params) }
+
+ before do
+ project.add_guest(another_user)
+ end
+
+ it 'returns no suggestions' do
+ result = service.execute
+
+ expect(result[:suggestions]).to be_empty
+ end
end
end
context 'when preview markdown param is not present' do
- let(:preview_suggestions) { false }
+ let(:suggestion_params) do
+ {
+ preview_suggestions: false
+ }
+ end
- it 'returns users referenced in text' do
+ it 'returns suggestions referenced in text' do
result = service.execute
expect(result[:suggestions]).to eq([])
@@ -49,8 +102,8 @@ describe PreviewMarkdownService do
let(:params) do
{
text: "Please do it\n/assign #{user.to_reference}",
- quick_actions_target_type: 'Issue',
- quick_actions_target_id: issue.id
+ target_type: 'Issue',
+ target_id: issue.id
}
end
let(:service) { described_class.new(project, user, params) }
@@ -72,7 +125,7 @@ describe PreviewMarkdownService do
let(:params) do
{
text: "My work\n/estimate 2y",
- quick_actions_target_type: 'MergeRequest'
+ target_type: 'MergeRequest'
}
end
let(:service) { described_class.new(project, user, params) }
@@ -96,8 +149,8 @@ describe PreviewMarkdownService do
let(:params) do
{
text: "My work\n/tag v1.2.3 Stable release",
- quick_actions_target_type: 'Commit',
- quick_actions_target_id: commit.id
+ target_type: 'Commit',
+ target_id: commit.id
}
end
let(:service) { described_class.new(project, user, params) }
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index 4dd6c6dab86..51d3fd18881 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::AfterImportService do
diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb
index 76977d7a1a7..fb1ab3f9949 100644
--- a/spec/services/projects/auto_devops/disable_service_spec.rb
+++ b/spec/services/projects/auto_devops/disable_service_spec.rb
@@ -46,7 +46,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do
create(:ci_pipeline, :failed, :auto_devops_source, project: project)
end
- it 'should disable Auto DevOps for project' do
+ it 'disables Auto DevOps for project' do
subject
expect(auto_devops.enabled).to eq(false)
@@ -58,7 +58,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do
create_list(:ci_pipeline, 2, :failed, :auto_devops_source, project: project)
end
- it 'should explicitly disable Auto DevOps for project' do
+ it 'explicitly disables Auto DevOps for project' do
subject
expect(auto_devops.reload.enabled).to eq(false)
@@ -70,7 +70,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do
create(:ci_pipeline, :success, :auto_devops_source, project: project)
end
- it 'should not disable Auto DevOps for project' do
+ it 'does not disable Auto DevOps for project' do
subject
expect(auto_devops.reload.enabled).to be_nil
@@ -85,14 +85,14 @@ describe Projects::AutoDevops::DisableService, '#execute' do
create(:ci_pipeline, :failed, :auto_devops_source, project: project)
end
- it 'should disable Auto DevOps for project' do
+ it 'disables Auto DevOps for project' do
subject
auto_devops = project.reload.auto_devops
expect(auto_devops.enabled).to eq(false)
end
- it 'should create a ProjectAutoDevops record' do
+ it 'creates a ProjectAutoDevops record' do
expect { subject }.to change { ProjectAutoDevops.count }.from(0).to(1)
end
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 373fe7cb7dd..2f70c8ea94d 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::AutocompleteService do
diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb
index 599aaf62080..e978334d68b 100644
--- a/spec/services/projects/batch_open_issues_count_service_spec.rb
+++ b/spec/services/projects/batch_open_issues_count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::BatchOpenIssuesCountService do
diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb
index 3d4587ce2a1..5c246854eb7 100644
--- a/spec/services/projects/cleanup_service_spec.rb
+++ b/spec/services/projects/cleanup_service_spec.rb
@@ -1,16 +1,18 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CleanupService do
let(:project) { create(:project, :repository, bfg_object_map: fixture_file_upload('spec/fixtures/bfg_object_map.txt')) }
let(:object_map) { project.bfg_object_map }
+ let(:cleaner) { service.__send__(:repository_cleaner) }
+
subject(:service) { described_class.new(project) }
describe '#execute' do
- it 'runs the apply_bfg_object_map gitaly RPC' do
- expect_next_instance_of(Gitlab::Git::RepositoryCleaner) do |cleaner|
- expect(cleaner).to receive(:apply_bfg_object_map).with(kind_of(IO))
- end
+ it 'runs the apply_bfg_object_map_stream gitaly RPC' do
+ expect(cleaner).to receive(:apply_bfg_object_map_stream).with(kind_of(IO))
service.execute
end
@@ -35,10 +37,91 @@ describe Projects::CleanupService do
expect(object_map.exists?).to be_falsy
end
+ context 'with a tainted merge request diff' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:diff) { merge_request.merge_request_diff }
+ let(:entry) { build_entry(diff.commits.first.id) }
+
+ before do
+ allow(cleaner)
+ .to receive(:apply_bfg_object_map_stream)
+ .and_yield(Gitaly::ApplyBfgObjectMapStreamResponse.new(entries: [entry]))
+ end
+
+ it 'removes the tainted commit from the database' do
+ service.execute
+
+ expect(MergeRequestDiff.exists?(diff.id)).to be_falsy
+ end
+
+ it 'ignores non-commit responses from Gitaly' do
+ entry.type = :UNKNOWN
+
+ service.execute
+
+ expect(MergeRequestDiff.exists?(diff.id)).to be_truthy
+ end
+ end
+
+ context 'with a tainted diff note' do
+ let(:diff_note) { create(:diff_note_on_commit, project: project) }
+ let(:note_diff_file) { diff_note.note_diff_file }
+ let(:entry) { build_entry(diff_note.commit_id) }
+
+ let(:highlight_cache) { Gitlab::DiscussionsDiff::HighlightCache }
+ let(:cache_id) { note_diff_file.id }
+
+ before do
+ allow(cleaner)
+ .to receive(:apply_bfg_object_map_stream)
+ .and_yield(Gitaly::ApplyBfgObjectMapStreamResponse.new(entries: [entry]))
+ end
+
+ it 'removes the tainted commit from the database' do
+ service.execute
+
+ expect(NoteDiffFile.exists?(note_diff_file.id)).to be_falsy
+ end
+
+ it 'removes the highlight cache from redis' do
+ write_cache(highlight_cache, cache_id, [{}])
+
+ expect(read_cache(highlight_cache, cache_id)).not_to be_nil
+
+ service.execute
+
+ expect(read_cache(highlight_cache, cache_id)).to be_nil
+ end
+
+ it 'ignores non-commit responses from Gitaly' do
+ entry.type = :UNKNOWN
+
+ service.execute
+
+ expect(NoteDiffFile.exists?(note_diff_file.id)).to be_truthy
+ end
+ end
+
it 'raises an error if no object map can be found' do
object_map.remove!
expect { service.execute }.to raise_error(described_class::NoUploadError)
end
end
+
+ def build_entry(old_oid)
+ Gitaly::ApplyBfgObjectMapStreamResponse::Entry.new(
+ type: :COMMIT,
+ old_oid: old_oid,
+ new_oid: Gitlab::Git::BLANK_SHA
+ )
+ end
+
+ def read_cache(cache, key)
+ cache.read_multiple([key]).first
+ end
+
+ def write_cache(cache, key, value)
+ cache.write_multiple(key => value)
+ end
end
diff --git a/spec/services/projects/count_service_spec.rb b/spec/services/projects/count_service_spec.rb
index 183f6128c7b..e345b508f53 100644
--- a/spec/services/projects/count_service_spec.rb
+++ b/spec/services/projects/count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CountService do
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index da078dd36c6..6c244d23877 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CreateFromTemplateService do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d1b110b9806..f54f9200661 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::CreateService, '#execute' do
+ include ExternalAuthorizationServiceHelpers
include GitHelpers
let(:gitlab_shell) { Gitlab::Shell.new }
@@ -265,32 +268,6 @@ describe Projects::CreateService, '#execute' do
end
end
- context 'when group has kubernetes cluster' do
- let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { group_cluster.group }
-
- let(:token) { 'aaaa' }
- let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) }
- let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
-
- before do
- group.add_owner(user)
-
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
- end
-
- it 'creates kubernetes namespace for the project' do
- project = create_project(user, opts.merge!(namespace_id: group.id))
-
- expect(project).to be_valid
-
- kubernetes_namespace = group_cluster.kubernetes_namespaces.first
- expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.project).to eq(project)
- end
- end
-
context 'when there is an active service template' do
before do
create(:service, project: nil, template: true, active: true)
@@ -343,6 +320,42 @@ describe Projects::CreateService, '#execute' do
expect(rugged.config['gitlab.fullpath']).to eq project.full_path
end
+ context 'with external authorization enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'does not save the project with an error if the service denies access' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'new-label', any_args) { false }
+
+ project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' }))
+
+ expect(project.errors[:external_authorization_classification_label]).to be_present
+ expect(project).not_to be_persisted
+ end
+
+ it 'saves the project when the user has access to the label' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'new-label', any_args) { true }
+
+ project = create_project(user, opts.merge({ external_authorization_classification_label: 'new-label' }))
+
+ expect(project).to be_persisted
+ expect(project.external_authorization_classification_label).to eq('new-label')
+ end
+
+ it 'does not save the project when the user has no access to the default label and no label is provided' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'default_label', any_args) { false }
+
+ project = create_project(user, opts)
+
+ expect(project.errors[:external_authorization_classification_label]).to be_present
+ expect(project).not_to be_persisted
+ end
+ end
+
def create_project(user, opts)
Projects::CreateService.new(user, opts).execute
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index dfbdfa2ab69..3af7ee3ad50 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DestroyService do
@@ -128,10 +130,8 @@ describe Projects::DestroyService do
it 'keeps project team intact upon an error' do
perform_enqueued_jobs do
- begin
- destroy_project(project, user, {})
- rescue ::Redis::CannotConnectError
- end
+ destroy_project(project, user, {})
+ rescue ::Redis::CannotConnectError
end
expect(project.team.members.count).to eq 2
diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb
index deea1189cdf..df5eed18ac0 100644
--- a/spec/services/projects/detect_repository_languages_service_spec.rb
+++ b/spec/services/projects/detect_repository_languages_service_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do
set(:project) { create(:project, :repository) }
- subject { described_class.new(project, project.owner) }
+ subject { described_class.new(project) }
describe '#execute' do
context 'without previous detection' do
@@ -19,6 +21,10 @@ describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_
expect(names).to eq(%w[Ruby JavaScript HTML CoffeeScript])
end
+
+ it 'updates detected_repository_languages flag' do
+ expect { subject.execute }.to change(project, :detected_repository_languages).to(true)
+ end
end
context 'with a previous detection' do
@@ -36,6 +42,12 @@ describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_
expect(repository_languages).to eq(%w[Ruby D])
end
+
+ it "doesn't touch detected_repository_languages flag" do
+ expect(project).not_to receive(:update_column).with(:detected_repository_languages, true)
+
+ subject.execute
+ end
end
context 'when no repository exists' do
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index da236052ebf..f25233ceeb1 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::DownloadService do
diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb
index 835dae68fcd..64de373d7f6 100644
--- a/spec/services/projects/enable_deploy_key_service_spec.rb
+++ b/spec/services/projects/enable_deploy_key_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::EnableDeployKeyService do
diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb
new file mode 100644
index 00000000000..fcfb138aad6
--- /dev/null
+++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+module Projects
+ describe FetchStatisticsIncrementService do
+ let(:project) { create(:project) }
+
+ describe '#execute' do
+ subject { described_class.new(project).execute }
+
+ it 'creates a new record for today with count == 1' do
+ expect { subject }.to change { ProjectDailyStatistic.count }.by(1)
+ created_stat = ProjectDailyStatistic.last
+
+ expect(created_stat.fetch_count).to eq(1)
+ expect(created_stat.project).to eq(project)
+ expect(created_stat.date).to eq(Date.today)
+ end
+
+ it "doesn't increment previous days statistics" do
+ yesterday_stat = create(:project_daily_statistic, fetch_count: 5, project: project, date: 1.day.ago)
+
+ expect { subject }.not_to change { yesterday_stat.reload.fetch_count }
+ end
+
+ context 'when the record already exists for today' do
+ let!(:project_daily_stat) { create(:project_daily_statistic, fetch_count: 5, project: project, date: Date.today) }
+
+ it 'increments the today record count by 1' do
+ expect { subject }.to change { project_daily_stat.reload.fetch_count }.to(6)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 23ec29cce7b..3211a6e1310 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ForkService do
@@ -143,6 +145,30 @@ describe Projects::ForkService do
end
end
+ context "CI/CD settings" do
+ let(:to_project) { fork_project(@from_project, @to_user) }
+
+ context "when origin has git depth specified" do
+ before do
+ @from_project.update(default_git_depth: 42)
+ end
+
+ it "inherits default_git_depth from the origin project" do
+ expect(to_project.default_git_depth).to eq(42)
+ end
+ end
+
+ context "when origin does not define git depth" do
+ before do
+ @from_project.update!(default_git_depth: nil)
+ end
+
+ it "the fork has git depth set to 0" do
+ expect(to_project.default_git_depth).to eq(0)
+ end
+ end
+ end
+
context "when project has restricted visibility level" do
context "and only one visibility level is restricted" do
before do
diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
index 9f8e7ee18a8..7e35648e9ff 100644
--- a/spec/services/projects/forks_count_service_spec.rb
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ForksCountService do
diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb
new file mode 100644
index 00000000000..3acbc46b473
--- /dev/null
+++ b/spec/services/projects/git_deduplication_service_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::GitDeduplicationService do
+ include ExclusiveLeaseHelpers
+
+ let(:pool) { create(:pool_repository, :ready) }
+ let(:project) { create(:project, :repository) }
+ let(:lease_key) { "git_deduplication:#{project.id}" }
+ let(:lease_timeout) { Projects::GitDeduplicationService::LEASE_TIMEOUT }
+
+ subject(:service) { described_class.new(project) }
+
+ describe '#execute' do
+ context 'when there is not already a lease' do
+ context 'when the project does not have a pool repository' do
+ it 'calls disconnect_git_alternates' do
+ stub_exclusive_lease(lease_key, timeout: lease_timeout)
+
+ expect(project.repository).to receive(:disconnect_alternates)
+
+ service.execute
+ end
+ end
+
+ context 'when the project has a pool repository' do
+ let(:project) { create(:project, :repository, pool_repository: pool) }
+
+ context 'when the project is a source project' do
+ let(:lease_key) { "git_deduplication:#{pool.source_project.id}" }
+
+ subject(:service) { described_class.new(pool.source_project) }
+
+ it 'calls fetch' do
+ stub_exclusive_lease(lease_key, timeout: lease_timeout)
+ allow(pool.source_project).to receive(:git_objects_poolable?).and_return(true)
+
+ expect(pool.object_pool).to receive(:fetch)
+
+ service.execute
+ end
+
+ it 'does not call fetch if git objects are not poolable' do
+ stub_exclusive_lease(lease_key, timeout: lease_timeout)
+ allow(pool.source_project).to receive(:git_objects_poolable?).and_return(false)
+
+ expect(pool.object_pool).not_to receive(:fetch)
+
+ service.execute
+ end
+
+ it 'does not call fetch if pool and project are not on the same storage' do
+ stub_exclusive_lease(lease_key, timeout: lease_timeout)
+ allow(pool.source_project.repository).to receive(:storage).and_return('special_storage_001')
+
+ expect(pool.object_pool).not_to receive(:fetch)
+
+ service.execute
+ end
+ end
+
+ it 'links the repository to the object pool' do
+ expect(project).to receive(:link_pool_repository)
+
+ service.execute
+ end
+
+ it 'does not link the repository to the object pool if they are not on the same storage' do
+ allow(project.repository).to receive(:storage).and_return('special_storage_001')
+ expect(project).not_to receive(:link_pool_repository)
+
+ service.execute
+ end
+ end
+
+ context 'when a lease is already out' do
+ before do
+ stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
+ end
+
+ it 'fails when a lease is already out' do
+ expect(service).to receive(:log_error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+
+ service.execute
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index b5f2c826c97..78580bfa604 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::GitlabProjectsImportService do
diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb
index ffb270d277e..6ded57f961c 100644
--- a/spec/services/projects/group_links/create_service_spec.rb
+++ b/spec/services/projects/group_links/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::GroupLinks::CreateService, '#execute' do
@@ -12,6 +14,10 @@ describe Projects::GroupLinks::CreateService, '#execute' do
end
let(:subject) { described_class.new(project, user, opts) }
+ before do
+ group.add_developer(user)
+ end
+
it 'adds group to project' do
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
end
@@ -19,4 +25,8 @@ describe Projects::GroupLinks::CreateService, '#execute' do
it 'returns false if group is blank' do
expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
end
+
+ it 'returns error if user is not allowed to share with a group' do
+ expect { subject.execute(create :group) }.not_to change { project.project_group_links.count }
+ end
end
diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb
index 336ee01ae50..d78ab78c3d8 100644
--- a/spec/services/projects/group_links/destroy_service_spec.rb
+++ b/spec/services/projects/group_links/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::GroupLinks::DestroyService, '#execute' do
diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
index 61dbb57ec08..32ebec318f2 100644
--- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::HashedStorage::MigrateAttachmentsService do
@@ -70,12 +72,18 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
FileUtils.mkdir_p(base_path(hashed_storage))
end
- it 'raises AttachmentMigrationError' do
+ it 'raises AttachmentCannotMoveError' do
expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage))
- expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentMigrationError)
+ expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError)
end
end
+
+ it 'works even when project validation fails' do
+ allow(project).to receive(:valid?) { false }
+
+ expect { service.execute }.to change { project.hashed_storage?(:attachments) }.to(true)
+ end
end
context '#old_disk_path' do
@@ -86,6 +94,8 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
context '#new_disk_path' do
it 'returns new disk_path for project' do
+ service.execute
+
expect(service.new_disk_path).to eq(project.disk_path)
end
end
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index 0772dc4b85b..5b778f16b5a 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::HashedStorage::MigrateRepositoryService do
@@ -28,7 +30,17 @@ describe Projects::HashedStorage::MigrateRepositoryService do
it 'fails when a git operation is in progress' do
allow(project).to receive(:repo_reference_count) { 1 }
- expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryMigrationError)
+ expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
+ end
+ end
+
+ context 'when repository doesnt exist on disk' do
+ let(:project) { create(:project, :legacy_storage) }
+
+ it 'skips the disk change but increase the version' do
+ service.execute
+
+ expect(project.hashed_storage?(:repository)).to be_truthy
end
end
@@ -92,6 +104,12 @@ describe Projects::HashedStorage::MigrateRepositoryService do
end
end
+ it 'works even when project validation fails' do
+ allow(project).to receive(:valid?) { false }
+
+ expect { service.execute }.to change { project.hashed_storage?(:repository) }.to(true)
+ end
+
def expect_move_repository(from_name, to_name)
expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original
end
diff --git a/spec/services/projects/hashed_storage/migration_service_spec.rb b/spec/services/projects/hashed_storage/migration_service_spec.rb
index b4647586363..e3191cd7ebc 100644
--- a/spec/services/projects/hashed_storage/migration_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migration_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::HashedStorage::MigrationService do
diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
new file mode 100644
index 00000000000..815c85e0866
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackAttachmentsService do
+ subject(:service) { described_class.new(project, logger: nil) }
+
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+
+ let!(:upload) { Upload.find_by(path: file_uploader.upload_path) }
+ let(:file_uploader) { build(:file_uploader, project: project) }
+ let(:old_disk_path) { File.join(base_path(hashed_storage), upload.path) }
+ let(:new_disk_path) { File.join(base_path(legacy_storage), upload.path) }
+
+ context '#execute' do
+ context 'when succeeds' do
+ it 'moves attachments to legacy storage layout' do
+ expect(File.file?(old_disk_path)).to be_truthy
+ expect(File.file?(new_disk_path)).to be_falsey
+ expect(File.exist?(base_path(hashed_storage))).to be_truthy
+ expect(File.exist?(base_path(legacy_storage))).to be_falsey
+ expect(FileUtils).to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage)).and_call_original
+
+ service.execute
+
+ expect(File.exist?(base_path(legacy_storage))).to be_truthy
+ expect(File.exist?(base_path(hashed_storage))).to be_falsey
+ expect(File.file?(old_disk_path)).to be_falsey
+ expect(File.file?(new_disk_path)).to be_truthy
+ end
+
+ it 'returns true' do
+ expect(service.execute).to be_truthy
+ end
+
+ it 'sets skipped to false' do
+ service.execute
+
+ expect(service.skipped?).to be_falsey
+ end
+ end
+
+ context 'when original folder does not exist anymore' do
+ before do
+ FileUtils.rm_rf(base_path(hashed_storage))
+ end
+
+ it 'skips moving folders and go to next' do
+ expect(FileUtils).not_to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage))
+
+ service.execute
+
+ expect(File.exist?(base_path(legacy_storage))).to be_falsey
+ expect(File.file?(new_disk_path)).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(service.execute).to be_truthy
+ end
+
+ it 'sets skipped to true' do
+ service.execute
+
+ expect(service.skipped?).to be_truthy
+ end
+ end
+
+ context 'when target folder already exists' do
+ before do
+ FileUtils.mkdir_p(base_path(legacy_storage))
+ end
+
+ it 'raises AttachmentCannotMoveError' do
+ expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage))
+
+ expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError)
+ end
+ end
+
+ it 'works even when project validation fails' do
+ allow(project).to receive(:valid?) { false }
+
+ expect { service.execute }.to change { project.hashed_storage?(:attachments) }.to(false)
+ end
+ end
+
+ context '#old_disk_path' do
+ it 'returns old disk_path for project' do
+ expect(service.old_disk_path).to eq(project.disk_path)
+ end
+ end
+
+ context '#new_disk_path' do
+ it 'returns new disk_path for project' do
+ service.execute
+
+ expect(service.new_disk_path).to eq(project.full_path)
+ end
+ end
+
+ def base_path(storage)
+ File.join(FileUploader.root, storage.disk_path)
+ end
+end
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
new file mode 100644
index 00000000000..bd4354a7df3
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
+ include GitHelpers
+
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project) { create(:project, :repository, :wiki_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+
+ subject(:service) { described_class.new(project, project.disk_path) }
+
+ describe '#execute' do
+ let(:old_disk_path) { hashed_storage.disk_path }
+ let(:new_disk_path) { legacy_storage.disk_path }
+
+ before do
+ allow(service).to receive(:gitlab_shell) { gitlab_shell }
+ end
+
+ context 'repository lock' do
+ it 'tries to lock the repository' do
+ expect(service).to receive(:try_to_set_repository_read_only!)
+
+ service.execute
+ end
+
+ it 'fails when a git operation is in progress' do
+ allow(project).to receive(:repo_reference_count) { 1 }
+
+ expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
+ end
+ end
+
+ context 'when repository doesnt exist on disk' do
+ let(:project) { create(:project) }
+
+ it 'skips the disk change but decrease the version' do
+ service.execute
+
+ expect(project.legacy_storage?).to be_truthy
+ end
+ end
+
+ context 'when succeeds' do
+ it 'renames project and wiki repositories' do
+ service.execute
+
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_truthy
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_truthy
+ end
+
+ it 'updates project to be legacy and not read-only' do
+ service.execute
+
+ expect(project.legacy_storage?).to be_truthy
+ expect(project.repository_read_only).to be_falsey
+ end
+
+ it 'move operation is called for both repositories' do
+ expect_move_repository(old_disk_path, new_disk_path)
+ expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki")
+
+ service.execute
+ end
+
+ it 'writes project full path to .git/config' do
+ service.execute
+
+ rugged_config = rugged_repo(project.repository).config['gitlab.fullpath']
+
+ expect(rugged_config).to eq project.full_path
+ end
+ end
+
+ context 'when one move fails' do
+ it 'rolls repositories back to original name' do
+ allow(service).to receive(:move_repository).and_call_original
+ allow(service).to receive(:move_repository).with(old_disk_path, new_disk_path).once { false } # will disable first move only
+
+ expect(service).to receive(:rollback_folder_move).and_call_original
+
+ service.execute
+
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_falsey
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_falsey
+ expect(project.repository_read_only?).to be_falsey
+ end
+
+ context 'when rollback fails' do
+ before do
+ legacy_storage.ensure_storage_path_exists
+ gitlab_shell.mv_repository(project.repository_storage, old_disk_path, new_disk_path)
+ end
+
+ it 'does not try to move nil repository over existing' do
+ expect(gitlab_shell).not_to receive(:mv_repository).with(project.repository_storage, old_disk_path, new_disk_path)
+ expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki")
+
+ service.execute
+ end
+ end
+ end
+
+ it 'works even when project validation fails' do
+ allow(project).to receive(:valid?) { false }
+
+ expect { service.execute }.to change { project.legacy_storage? }.to(true)
+ end
+
+ def expect_move_repository(from_name, to_name)
+ expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original
+ end
+ end
+end
diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb
new file mode 100644
index 00000000000..427d1535559
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackService do
+ let(:project) { create(:project, :empty_repo, :wiki_repo) }
+ let(:logger) { double }
+
+ subject(:service) { described_class.new(project, project.full_path, logger: logger) }
+
+ describe '#execute' do
+ context 'attachments rollback' do
+ let(:attachments_service_class) { Projects::HashedStorage::RollbackAttachmentsService }
+ let(:attachments_service) { attachments_service_class.new(project, logger: logger) }
+
+ it 'delegates rollback to Projects::HashedStorage::RollbackAttachmentsService' do
+ expect(attachments_service_class).to receive(:new)
+ .with(project, logger: logger)
+ .and_return(attachments_service)
+ expect(attachments_service).to receive(:execute)
+
+ service.execute
+ end
+
+ it 'does not delegate rollback if repository is in legacy storage already' do
+ project.storage_version = nil
+ expect(attachments_service_class).not_to receive(:new)
+
+ service.execute
+ end
+ end
+
+ context 'repository rollback' do
+ let(:repository_service_class) { Projects::HashedStorage::RollbackRepositoryService }
+ let(:repository_service) { repository_service_class.new(project, project.full_path, logger: logger) }
+
+ it 'delegates rollback to RollbackRepositoryService' do
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+
+ expect(repository_service_class).to receive(:new)
+ .with(project, project.full_path, logger: logger)
+ .and_return(repository_service)
+ expect(repository_service).to receive(:execute)
+
+ service.execute
+ end
+
+ it 'does not delegate rollback if repository is in legacy storage already' do
+ project.storage_version = nil
+
+ expect(repository_service_class).not_to receive(:new)
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 18ecef1c0a1..f651db70cbd 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::HousekeepingService do
@@ -79,6 +81,9 @@ describe Projects::HousekeepingService do
# At push 10, 20, ... (except those above)
expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid)
.exactly(16).times
+ # At push 6, 12, 18, ... (except those above)
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :pack_refs, :the_lease_key, :the_uuid)
+ .exactly(27).times
201.times do
subject.increment!
@@ -88,6 +93,19 @@ describe Projects::HousekeepingService do
expect(project.pushes_since_gc).to eq(1)
end
end
+
+ it 'runs the task specifically requested' do
+ housekeeping = described_class.new(project, :gc)
+
+ allow(housekeeping).to receive(:try_obtain_lease).and_return(:gc_uuid)
+ allow(housekeeping).to receive(:lease_key).and_return(:gc_lease_key)
+
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :gc_lease_key, :gc_uuid).twice
+
+ 2.times do
+ housekeeping.execute
+ end
+ end
end
describe '#needed?' do
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index f9e5530bc9d..404bb55629a 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ImportExport::ExportService do
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 7faf0fc2868..d9f9ede8ecd 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -1,17 +1,15 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ImportService do
let!(:project) { create(:project) }
let(:user) { project.creator }
- let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
- let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } }
subject { described_class.new(project, user) }
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
- allow_any_instance_of(Projects::LfsPointers::LfsDownloadService).to receive(:execute)
- allow_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
end
describe '#async?' do
@@ -75,7 +73,6 @@ describe Projects::ImportService do
context 'when repository creation succeeds' do
it 'does not download lfs files' do
expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
subject.execute
end
@@ -112,7 +109,6 @@ describe Projects::ImportService do
context 'when repository import scheduled' do
it 'does not download lfs objects' do
expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
subject.execute
end
@@ -128,7 +124,7 @@ describe Projects::ImportService do
it 'succeeds if repository import is successful' do
expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true)
- expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return({})
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :success)
result = subject.execute
@@ -144,6 +140,19 @@ describe Projects::ImportService do
expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
end
+ context 'when lfs import fails' do
+ it 'logs the error' do
+ error_message = 'error message'
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
+ expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true)
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :error, message: error_message)
+ expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}")
+
+ subject.execute
+ end
+ end
+
context 'when repository import scheduled' do
before do
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
@@ -153,10 +162,7 @@ describe Projects::ImportService do
it 'downloads lfs objects if lfs_enabled is enabled for project' do
allow(project).to receive(:lfs_enabled?).and_return(true)
- service = double
- expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
- expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice
- expect(service).to receive(:execute).twice
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute)
subject.execute
end
@@ -164,7 +170,6 @@ describe Projects::ImportService do
it 'does not download lfs objects if lfs_enabled is not enabled for project' do
allow(project).to receive(:lfs_enabled?).and_return(false)
expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
subject.execute
end
@@ -206,7 +211,6 @@ describe Projects::ImportService do
allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(true)
expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
subject.execute
end
@@ -214,13 +218,22 @@ describe Projects::ImportService do
it 'does not have a custom repository importer downloads lfs objects' do
allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false)
- service = double
- expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
- expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice
- expect(service).to receive(:execute).twice
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute)
subject.execute
end
+
+ context 'when lfs import fails' do
+ it 'logs the error' do
+ error_message = 'error message'
+
+ allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false)
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(status: :error, message: error_message)
+ expect(Gitlab::AppLogger).to receive(:error).with("The Lfs import process failed. #{error_message}")
+
+ subject.execute
+ end
+ end
end
end
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
index f222c52199f..80debcd3a7a 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require 'spec_helper'
describe Projects::LfsPointers::LfsDownloadLinkListService do
@@ -32,7 +33,10 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
- allow(Gitlab::HTTP).to receive(:post).and_return(objects_response)
+ response = instance_double(HTTParty::Response)
+ allow(response).to receive(:body).and_return(objects_response.to_json)
+ allow(response).to receive(:success?).and_return(true)
+ allow(Gitlab::HTTP).to receive(:post).and_return(response)
end
describe '#execute' do
@@ -83,11 +87,26 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
end
describe '#get_download_links' do
- it 'raise errorif request fails' do
+ it 'raise error if request fails' do
allow(Gitlab::HTTP).to receive(:post).and_return(Struct.new(:success?, :message).new(false, 'Failed request'))
expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError)
end
+
+ shared_examples 'JSON parse errors' do |body|
+ it 'raises error' do
+ response = instance_double(HTTParty::Response)
+ allow(response).to receive(:body).and_return(body)
+ allow(response).to receive(:success?).and_return(true)
+ allow(Gitlab::HTTP).to receive(:post).and_return(response)
+
+ expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError)
+ end
+ end
+
+ it_behaves_like 'JSON parse errors', '{'
+ it_behaves_like 'JSON parse errors', '{}'
+ it_behaves_like 'JSON parse errors', '{ foo: 123 }'
end
describe '#parse_response_links' do
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 876beb39801..75d534c59bf 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
require 'spec_helper'
describe Projects::LfsPointers::LfsDownloadService do
+ include StubRequests
+
let(:project) { create(:project) }
let(:lfs_content) { SecureRandom.random_bytes(10) }
let(:oid) { Digest::SHA256.hexdigest(lfs_content) }
@@ -61,7 +64,7 @@ describe Projects::LfsPointers::LfsDownloadService do
describe '#execute' do
context 'when file download succeeds' do
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
@@ -103,7 +106,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:size) { 1 }
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'no lfs object is created'
@@ -117,7 +120,7 @@ describe Projects::LfsPointers::LfsDownloadService do
context 'when downloaded lfs file has a different oid' do
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
allow_any_instance_of(Digest::SHA256).to receive(:hexdigest).and_return('foobar')
end
@@ -135,7 +138,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:lfs_object) { LfsDownloadObject.new(oid: oid, size: size, link: download_link_with_credentials) }
before do
- WebMock.stub_request(:get, download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
+ stub_full_request(download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
end
it 'the request adds authorization headers' do
@@ -148,7 +151,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:local_request_setting) { true }
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link, ip_address: '192.168.2.120').to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
@@ -172,7 +175,8 @@ describe Projects::LfsPointers::LfsDownloadService do
with_them do
before do
- WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
+ stub_full_request(download_link, ip_address: '192.168.2.120')
+ .to_return(status: 301, headers: { 'Location' => redirect_link })
end
it_behaves_like 'no lfs object is created'
@@ -183,8 +187,8 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:redirect_link) { "http://example.com/"}
before do
- WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
- WebMock.stub_request(:get, redirect_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
+ stub_full_request(redirect_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
index 5a75fb38dec..7ca20a6d751 100644
--- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
@@ -1,146 +1,63 @@
+# frozen_string_literal: true
require 'spec_helper'
describe Projects::LfsPointers::LfsImportService do
+ let(:project) { create(:project) }
+ let(:user) { project.creator }
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
- let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"}
- let(:group) { create(:group, lfs_enabled: true)}
- let!(:project) { create(:project, namespace: group, import_url: import_url, lfs_enabled: true) }
- let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
- let!(:existing_lfs_objects) { LfsObject.pluck(:oid, :size).to_h }
- let(:oids) { { 'oid1' => 123, 'oid2' => 125 } }
let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } }
- let(:all_oids) { existing_lfs_objects.merge(oids) }
- let(:remote_uri) { URI.parse(lfs_endpoint) }
- subject { described_class.new(project) }
+ subject { described_class.new(project, user) }
- before do
- allow(project.repository).to receive(:lfsconfig_for).and_return(nil)
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
- allow_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute).and_return(all_oids)
- end
-
- describe '#execute' do
- context 'when no lfs pointer is linked' do
- before do
- allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([])
- allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
- expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original
- end
-
- it 'retrieves all lfs pointers in the project repository' do
- expect_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute)
-
- subject.execute
- end
-
- it 'links existent lfs objects to the project' do
- expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute)
-
- subject.execute
- end
-
- it 'retrieves the download links of non existent objects' do
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids)
-
- subject.execute
- end
+ context 'when lfs is enabled for the project' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
end
- context 'when some lfs objects are linked' do
- before do
- allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys)
- allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
- end
+ it 'downloads lfs objects' do
+ service = double
+ expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_return(oid_download_links)
+ expect(Projects::LfsPointers::LfsDownloadService).to receive(:new).and_return(service).twice
+ expect(service).to receive(:execute).twice
- it 'retrieves the download links of non existent objects' do
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids)
+ result = subject.execute
- subject.execute
- end
+ expect(result[:status]).to eq :success
end
- context 'when all lfs objects are linked' do
- before do
- allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys)
- allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute)
- end
+ context 'when no downloadable lfs object links' do
+ it 'does not call LfsDownloadService' do
+ expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_return({})
+ expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new)
- it 'retrieves no download links' do
- expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original
+ result = subject.execute
- expect(subject.execute).to be_empty
+ expect(result[:status]).to eq :success
end
end
- context 'when lfsconfig file exists' do
- before do
- allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n")
- end
+ context 'when an exception is raised' do
+ it 'returns error' do
+ error_message = "error message"
+ expect_any_instance_of(Projects::LfsPointers::LfsObjectDownloadListService).to receive(:execute).and_raise(StandardError, error_message)
- context 'when url points to the same import url host' do
- let(:lfs_endpoint) { "#{import_url}/different_endpoint" }
- let(:service) { double }
+ result = subject.execute
- before do
- allow(service).to receive(:execute)
- end
- it 'downloads lfs object using the new endpoint' do
- expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: remote_uri).and_return(service)
-
- subject.execute
- end
-
- context 'when import url has credentials' do
- let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git'}
-
- it 'adds the credentials to the new endpoint' do
- expect(Projects::LfsPointers::LfsDownloadLinkListService)
- .to receive(:new).with(project, remote_uri: URI.parse("http://user:password@www.gitlab.com/demo/repo.git/different_endpoint"))
- .and_return(service)
-
- subject.execute
- end
-
- context 'when url has its own credentials' do
- let(:lfs_endpoint) { "http://user1:password1@www.gitlab.com/demo/repo.git/different_endpoint" }
-
- it 'does not add the import url credentials' do
- expect(Projects::LfsPointers::LfsDownloadLinkListService)
- .to receive(:new).with(project, remote_uri: remote_uri)
- .and_return(service)
-
- subject.execute
- end
- end
- end
- end
-
- context 'when url points to a third party service' do
- let(:lfs_endpoint) { 'http://third_party_service.com/info/lfs/objects/' }
-
- it 'disables lfs from the project' do
- expect(project.lfs_enabled?).to be_truthy
-
- subject.execute
-
- expect(project.lfs_enabled?).to be_falsey
- end
-
- it 'does not download anything' do
- expect_any_instance_of(Projects::LfsPointers::LfsListService).not_to receive(:execute)
-
- subject.execute
- end
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq error_message
end
end
end
- describe '#default_endpoint_uri' do
- let(:import_url) { 'http://www.gitlab.com/demo/repo' }
+ context 'when lfs is not enabled for the project' do
+ it 'does not download lfs objects' do
+ allow(project).to receive(:lfs_enabled?).and_return(false)
+ expect(Projects::LfsPointers::LfsObjectDownloadListService).not_to receive(:new)
+ expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new)
+
+ result = subject.execute
- it 'adds suffix .git if the url does not have it' do
- expect(subject.send(:default_endpoint_uri).path).to match(/repo.git/)
+ expect(result[:status]).to eq :success
end
end
end
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
index b7b153655db..849601c4a63 100644
--- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require 'spec_helper'
describe Projects::LfsPointers::LfsLinkService do
diff --git a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
new file mode 100644
index 00000000000..9dac29765a2
--- /dev/null
+++ b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Projects::LfsPointers::LfsObjectDownloadListService do
+ let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
+ let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"}
+ let(:group) { create(:group, lfs_enabled: true)}
+ let!(:project) { create(:project, namespace: group, import_url: import_url, lfs_enabled: true) }
+ let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
+ let!(:existing_lfs_objects) { LfsObject.pluck(:oid, :size).to_h }
+ let(:oids) { { 'oid1' => 123, 'oid2' => 125 } }
+ let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } }
+ let(:all_oids) { existing_lfs_objects.merge(oids) }
+ let(:remote_uri) { URI.parse(lfs_endpoint) }
+
+ subject { described_class.new(project) }
+
+ before do
+ allow(project.repository).to receive(:lfsconfig_for).and_return(nil)
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ allow_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute).and_return(all_oids)
+ end
+
+ describe '#execute' do
+ context 'when no lfs pointer is linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([])
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
+ expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original
+ end
+
+ it 'retrieves all lfs pointers in the project repository' do
+ expect_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'links existent lfs objects to the project' do
+ expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'retrieves the download links of non existent objects' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids)
+
+ subject.execute
+ end
+ end
+
+ context 'when some lfs objects are linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys)
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
+ end
+
+ it 'retrieves the download links of non existent objects' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids)
+
+ subject.execute
+ end
+ end
+
+ context 'when all lfs objects are linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys)
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute)
+ end
+
+ it 'retrieves no download links' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original
+
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when lfsconfig file exists' do
+ before do
+ allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n")
+ end
+
+ context 'when url points to the same import url host' do
+ let(:lfs_endpoint) { "#{import_url}/different_endpoint" }
+ let(:service) { double }
+
+ before do
+ allow(service).to receive(:execute)
+ end
+
+ it 'downloads lfs object using the new endpoint' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: remote_uri).and_return(service)
+
+ subject.execute
+ end
+
+ context 'when import url has credentials' do
+ let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git'}
+
+ it 'adds the credentials to the new endpoint' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService)
+ .to receive(:new).with(project, remote_uri: URI.parse("http://user:password@www.gitlab.com/demo/repo.git/different_endpoint"))
+ .and_return(service)
+
+ subject.execute
+ end
+
+ context 'when url has its own credentials' do
+ let(:lfs_endpoint) { "http://user1:password1@www.gitlab.com/demo/repo.git/different_endpoint" }
+
+ it 'does not add the import url credentials' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService)
+ .to receive(:new).with(project, remote_uri: remote_uri)
+ .and_return(service)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
+ context 'when url points to a third party service' do
+ let(:lfs_endpoint) { 'http://third_party_service.com/info/lfs/objects/' }
+
+ it 'disables lfs from the project' do
+ expect(project.lfs_enabled?).to be_truthy
+
+ subject.execute
+
+ expect(project.lfs_enabled?).to be_falsey
+ end
+
+ it 'does not download anything' do
+ expect_any_instance_of(Projects::LfsPointers::LfsListService).not_to receive(:execute)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
+ describe '#default_endpoint_uri' do
+ let(:import_url) { 'http://www.gitlab.com/demo/repo' }
+
+ it 'adds suffix .git if the url does not have it' do
+ expect(subject.send(:default_endpoint_uri).path).to match(/repo.git/)
+ end
+ end
+end
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
index 88d9d93c33b..efa34c84522 100644
--- a/spec/services/projects/move_access_service_spec.rb
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveAccessService do
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
index c548edf39a8..a5d28fb0fbf 100644
--- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveDeployKeysProjectsService do
diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb
index f4a5a7f9fc2..8f9f048d5ff 100644
--- a/spec/services/projects/move_forks_service_spec.rb
+++ b/spec/services/projects/move_forks_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveForksService do
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
index 517a24a982a..114509229c5 100644
--- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveLfsObjectsProjectsService do
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
index 24d69eef86a..54d85404bf6 100644
--- a/spec/services/projects/move_notification_settings_service_spec.rb
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveNotificationSettingsService do
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
index b4408393624..fe3ba31c881 100644
--- a/spec/services/projects/move_project_authorizations_service_spec.rb
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveProjectAuthorizationsService do
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
index 7ca8cf304fe..6140d679929 100644
--- a/spec/services/projects/move_project_group_links_service_spec.rb
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveProjectGroupLinksService do
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
index c8c0eac1f13..bdd5cd6a87a 100644
--- a/spec/services/projects/move_project_members_service_spec.rb
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveProjectMembersService do
diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb
index e0545c5a21b..cde188f9f5f 100644
--- a/spec/services/projects/move_users_star_projects_service_spec.rb
+++ b/spec/services/projects/move_users_star_projects_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::MoveUsersStarProjectsService do
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index 562c14a8df8..8efa34765d0 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::OpenIssuesCountService do
diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb
index 9f49b9ec6a2..0d8227f7db5 100644
--- a/spec/services/projects/open_merge_requests_count_service_spec.rb
+++ b/spec/services/projects/open_merge_requests_count_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::OpenMergeRequestsCountService do
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index 6afae3da80c..7e765659b9d 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -11,14 +11,70 @@ describe Projects::Operations::UpdateService do
subject { described_class.new(project, user, params) }
describe '#execute' do
+ context 'metrics dashboard setting' do
+ let(:params) do
+ {
+ metrics_setting_attributes: {
+ external_dashboard_url: 'http://gitlab.com'
+ }
+ }
+ end
+
+ context 'without existing metrics dashboard setting' do
+ it 'creates a setting' do
+ expect(result[:status]).to eq(:success)
+
+ expect(project.reload.metrics_setting.external_dashboard_url).to eq(
+ 'http://gitlab.com'
+ )
+ end
+ end
+
+ context 'with existing metrics dashboard setting' do
+ before do
+ create(:project_metrics_setting, project: project)
+ end
+
+ it 'updates the settings' do
+ expect(result[:status]).to eq(:success)
+
+ expect(project.reload.metrics_setting.external_dashboard_url).to eq(
+ 'http://gitlab.com'
+ )
+ end
+
+ context 'with blank external_dashboard_url in params' do
+ let(:params) do
+ {
+ metrics_setting_attributes: {
+ external_dashboard_url: ''
+ }
+ }
+ end
+
+ it 'destroys the metrics_setting entry in DB' do
+ expect(result[:status]).to eq(:success)
+
+ expect(project.reload.metrics_setting).to be_nil
+ end
+ end
+ end
+ end
+
context 'error tracking' do
context 'with existing error tracking setting' do
let(:params) do
{
error_tracking_setting_attributes: {
enabled: false,
- api_url: 'http://gitlab.com/api/0/projects/org/project',
- token: 'token'
+ api_host: 'http://gitlab.com/',
+ token: 'token',
+ project: {
+ slug: 'project',
+ name: 'Project',
+ organization_slug: 'org',
+ organization_name: 'Org'
+ }
}
}
end
@@ -32,8 +88,30 @@ describe Projects::Operations::UpdateService do
project.reload
expect(project.error_tracking_setting).not_to be_enabled
- expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.api_url).to eq(
+ 'http://gitlab.com/api/0/projects/org/project/'
+ )
expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
+ end
+
+ context 'disable error tracking' do
+ before do
+ params[:error_tracking_setting_attributes][:api_host] = ''
+ params[:error_tracking_setting_attributes][:enabled] = false
+ end
+
+ it 'can set api_url to nil' do
+ expect(result[:status]).to eq(:success)
+
+ project.reload
+ expect(project.error_tracking_setting).not_to be_enabled
+ expect(project.error_tracking_setting.api_url).to be_nil
+ expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
+ end
end
end
@@ -42,8 +120,14 @@ describe Projects::Operations::UpdateService do
{
error_tracking_setting_attributes: {
enabled: true,
- api_url: 'http://gitlab.com/api/0/projects/org/project',
- token: 'token'
+ api_host: 'http://gitlab.com/',
+ token: 'token',
+ project: {
+ slug: 'project',
+ name: 'Project',
+ organization_slug: 'org',
+ organization_name: 'Org'
+ }
}
}
end
@@ -52,8 +136,12 @@ describe Projects::Operations::UpdateService do
expect(result[:status]).to eq(:success)
expect(project.error_tracking_setting).to be_enabled
- expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.api_url).to eq(
+ 'http://gitlab.com/api/0/projects/org/project/'
+ )
expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
end
end
diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb
index c7900629f5f..def39ad3789 100644
--- a/spec/services/projects/overwrite_project_service_spec.rb
+++ b/spec/services/projects/overwrite_project_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::OverwriteProjectService do
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 6040f9100f8..4def83513a4 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -1,30 +1,59 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ParticipantsService do
describe '#groups' do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ let(:service) { described_class.new(project, user) }
+
+ it 'avoids N+1 queries' do
+ group_1 = create(:group)
+ group_1.add_owner(user)
+
+ service.groups # Run general application warmup queries
+ control_count = ActiveRecord::QueryRecorder.new { service.groups }.count
+
+ group_2 = create(:group)
+ group_2.add_owner(user)
+
+ expect { service.groups }.not_to exceed_query_limit(control_count)
+ end
+
+ it 'returns correct user counts for groups' do
+ group_1 = create(:group)
+ group_1.add_owner(user)
+ group_1.add_owner(create(:user))
+
+ group_2 = create(:group)
+ group_2.add_owner(user)
+ create(:group_member, :access_request, group: group_2, user: create(:user))
+
+ expect(service.groups).to contain_exactly(
+ a_hash_including(name: group_1.full_name, count: 2),
+ a_hash_including(name: group_2.full_name, count: 1)
+ )
+ end
+
describe 'avatar_url' do
- let(:project) { create(:project, :public) }
let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
- let(:user) { create(:user) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- it 'should return an url for the avatar' do
- participants = described_class.new(project, user)
- groups = participants.groups
+ before do
+ group.add_owner(user)
+ end
- expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ it 'returns an url for the avatar' do
+ expect(service.groups.size).to eq 1
+ expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
- it 'should return an url for the avatar with relative url' do
+ it 'returns an url for the avatar with relative url' do
stub_config_setting(relative_url_root: '/gitlab')
stub_config_setting(url: Settings.send(:build_gitlab_url))
- participants = described_class.new(project, user)
- groups = participants.groups
-
- expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ expect(service.groups.size).to eq 1
+ expect(service.groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
end
end
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index f4c59735c43..f93e5aae82a 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::PropagateServiceTemplate do
diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb
new file mode 100644
index 00000000000..46c5095327d
--- /dev/null
+++ b/spec/services/projects/repository_languages_service_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::RepositoryLanguagesService do
+ let(:service) { described_class.new(project, project.owner) }
+
+ context 'when detected_repository_languages flag is set' do
+ let(:project) { create(:project) }
+
+ context 'when a project is without detected programming languages' do
+ it 'schedules a worker and returns the empty result' do
+ expect(::DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
+ expect(service.execute).to eq([])
+ end
+ end
+
+ context 'when a project is with detected programming languages' do
+ let!(:repository_language) { create(:repository_language, project: project) }
+
+ it 'does not schedule a worker and returns the detected languages' do
+ expect(::DetectRepositoryLanguagesWorker).not_to receive(:perform_async).with(project.id)
+
+ languages = service.execute
+
+ expect(languages.size).to eq(1)
+ expect(languages.last.attributes.values).to eq(
+ [project.id, repository_language.programming_language_id, repository_language.share]
+ )
+ end
+
+ it 'sets detected_repository_languages flag' do
+ expect { service.execute }.to change(project, :detected_repository_languages).from(nil).to(true)
+ end
+ end
+ end
+
+ context 'when detected_repository_languages flag is not set' do
+ let!(:repository_language) { create(:repository_language, project: project) }
+ let(:project) { create(:project, detected_repository_languages: true) }
+ let(:languages) { service.execute }
+
+ it 'returns repository languages' do
+ expect(languages.size).to eq(1)
+ expect(languages.last.attributes.values).to eq(
+ [project.id, repository_language.programming_language_id, repository_language.share]
+ )
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index aae50d5307f..a47c10d991a 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::TransferService do
@@ -71,32 +73,6 @@ describe Projects::TransferService do
shard_name: project.repository_storage
)
end
-
- context 'new group has a kubernetes cluster' do
- let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { group_cluster.group }
-
- let(:token) { 'aaaa' }
- let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) }
- let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
-
- subject { transfer_project(project, user, group) }
-
- before do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
- end
-
- it 'creates kubernetes namespace for the project' do
- subject
-
- expect(project.kubernetes_namespaces.count).to eq(1)
-
- kubernetes_namespace = group_cluster.kubernetes_namespaces.first
- expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.project).to eq(project)
- end
- end
end
context 'when transfer fails' do
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 014aab44281..a1175bf7123 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::UnlinkForkService do
diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb
index 7f5ef3129d7..363d3df0f84 100644
--- a/spec/services/projects/update_pages_configuration_service_spec.rb
+++ b/spec/services/projects/update_pages_configuration_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::UpdatePagesConfigurationService do
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 8b70845befe..b597717c347 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Projects::UpdatePagesService do
@@ -25,59 +27,6 @@ describe Projects::UpdatePagesService do
it { is_expected.not_to match(Gitlab::PathRegex.namespace_format_regex) }
end
- context 'legacy artifacts' do
- before do
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metadata)
- end
-
- describe 'pages artifacts' do
- it "doesn't delete artifacts after deploying" do
- expect(execute).to eq(:success)
-
- expect(build.reload.artifacts?).to eq(true)
- end
- end
-
- it 'succeeds' do
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
-
- # Check that all expected files are extracted
- %w[index.html zero .hidden/file].each do |filename|
- expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
- end
- end
-
- it 'limits pages size' do
- stub_application_setting(max_pages_size: 1)
- expect(execute).not_to eq(:success)
- end
-
- it 'removes pages after destroy' do
- expect(PagesWorker).to receive(:perform_in)
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
- project.destroy
- expect(project.pages_deployed?).to be_falsey
- end
-
- it 'fails if sha on branch is not latest' do
- build.update(ref: 'feature')
-
- expect(execute).not_to eq(:success)
- end
-
- it 'fails for empty file fails' do
- build.update(legacy_artifacts_file: empty_file)
-
- expect { execute }
- .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
- end
- end
-
context 'for new artifacts' do
context "for a valid job" do
before do
@@ -205,7 +154,7 @@ describe Projects::UpdatePagesService do
end
it 'fails for invalid archive' do
- build.update(legacy_artifacts_file: invalid_file)
+ create(:ci_job_artifact, :archive, file: invalid_file, job: build)
expect(execute).not_to eq(:success)
end
@@ -216,8 +165,8 @@ describe Projects::UpdatePagesService do
file = fixture_file_upload('spec/fixtures/pages.zip')
metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metafile)
+ create(:ci_job_artifact, :archive, file: file, job: build)
+ create(:ci_job_artifact, :metadata, file: metafile, job: build)
allow(build).to receive(:artifacts_metadata_entry)
.and_return(metadata)
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index c1e5f788146..be2811ab1e7 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 90eaea9c872..1dcfb739eb6 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::UpdateService do
+ include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
let(:user) { create(:user) }
@@ -42,6 +45,7 @@ describe Projects::UpdateService do
it 'updates the project to private' do
expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id)
+ expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, nil, project.id)
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
@@ -361,6 +365,46 @@ describe Projects::UpdateService do
call_service
end
end
+
+ context 'with external authorization enabled' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'does not save the project with an error if the service denies access' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'new-label') { false }
+
+ result = update_project(project, user, { external_authorization_classification_label: 'new-label' })
+
+ expect(result[:message]).to be_present
+ expect(result[:status]).to eq(:error)
+ end
+
+ it 'saves the new label if the service allows access' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'new-label') { true }
+
+ result = update_project(project, user, { external_authorization_classification_label: 'new-label' })
+
+ expect(result[:status]).to eq(:success)
+ expect(project.reload.external_authorization_classification_label).to eq('new-label')
+ end
+
+ it 'checks the default label when the classification label was cleared' do
+ expect(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'default_label') { true }
+
+ update_project(project, user, { external_authorization_classification_label: '' })
+ end
+
+ it 'does not check the label when it does not change' do
+ expect(::Gitlab::ExternalAuthorization)
+ .not_to receive(:access_allowed?)
+
+ update_project(project, user, { name: 'New name' })
+ end
+ end
end
describe '#run_auto_devops_pipeline?' do
@@ -397,6 +441,8 @@ describe Projects::UpdateService do
context 'when auto devops is set to instance setting' do
before do
project.create_auto_devops!(enabled: nil)
+ project.reload
+
allow(project.auto_devops).to receive(:previous_changes).and_return('enabled' => true)
end
diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb
new file mode 100644
index 00000000000..8534853fbc7
--- /dev/null
+++ b/spec/services/projects/update_statistics_service_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::UpdateStatisticsService do
+ let(:service) { described_class.new(project, nil, statistics: statistics)}
+ let(:statistics) { %w(repository_size) }
+
+ describe '#execute' do
+ context 'with a non-existing project' do
+ let(:project) { nil }
+
+ it 'does nothing' do
+ expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!)
+
+ service.execute
+ end
+ end
+
+ context 'with an existing project' do
+ let(:project) { create(:project) }
+
+ it 'refreshes the project statistics' do
+ expect_any_instance_of(ProjectStatistics).to receive(:refresh!)
+ .with(only: statistics.map(&:to_sym))
+ .and_call_original
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb
index 505e2935e93..5e972a966eb 100644
--- a/spec/services/prometheus/adapter_service_spec.rb
+++ b/spec/services/prometheus/adapter_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Prometheus::AdapterService do
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
new file mode 100644
index 00000000000..4bdb20de4c9
--- /dev/null
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -0,0 +1,195 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Prometheus::ProxyService do
+ include ReactiveCachingHelpers
+
+ set(:project) { create(:project) }
+ set(:environment) { create(:environment, project: project) }
+
+ describe '#initialize' do
+ let(:params) { ActionController::Parameters.new(query: '1').permit! }
+
+ it 'initializes attributes' do
+ result = described_class.new(environment, 'GET', 'query', params)
+
+ expect(result.proxyable).to eq(environment)
+ expect(result.method).to eq('GET')
+ expect(result.path).to eq('query')
+ expect(result.params).to eq('query' => '1')
+ end
+
+ it 'converts ActionController::Parameters into hash' do
+ result = described_class.new(environment, 'GET', 'query', params)
+
+ expect(result.params).to be_an_instance_of(Hash)
+ end
+
+ context 'with unknown params' do
+ let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val').permit! }
+
+ it 'filters unknown params' do
+ result = described_class.new(environment, 'GET', 'query', params)
+
+ expect(result.params).to eq('query' => '1')
+ end
+ end
+ end
+
+ describe '#execute' do
+ let(:prometheus_adapter) { instance_double(PrometheusService) }
+ let(:params) { ActionController::Parameters.new(query: '1').permit! }
+
+ subject { described_class.new(environment, 'GET', 'query', params) }
+
+ context 'when prometheus_adapter is nil' do
+ before do
+ allow(environment).to receive(:prometheus_adapter).and_return(nil)
+ end
+
+ it 'returns error' do
+ expect(subject.execute).to eq(
+ status: :error,
+ message: 'No prometheus server found',
+ http_status: :service_unavailable
+ )
+ end
+ end
+
+ context 'when prometheus_adapter cannot query' do
+ before do
+ allow(environment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ allow(prometheus_adapter).to receive(:can_query?).and_return(false)
+ end
+
+ it 'returns error' do
+ expect(subject.execute).to eq(
+ status: :error,
+ message: 'No prometheus server found',
+ http_status: :service_unavailable
+ )
+ end
+ end
+
+ context 'cannot proxy' do
+ subject { described_class.new(environment, 'POST', 'garbage', params) }
+
+ it 'returns error' do
+ expect(subject.execute).to eq(
+ message: 'Proxy support for this API is not available currently',
+ status: :error
+ )
+ end
+ end
+
+ context 'with caching', :use_clean_rails_memory_store_caching do
+ let(:return_value) { { 'http_status' => 200, 'body' => 'body' } }
+
+ let(:opts) do
+ [environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' }]
+ end
+
+ before do
+ allow(environment).to receive(:prometheus_adapter)
+ .and_return(prometheus_adapter)
+ allow(prometheus_adapter).to receive(:can_query?).and_return(true)
+ end
+
+ context 'when value present in cache' do
+ before do
+ stub_reactive_cache(subject, return_value, opts)
+ end
+
+ it 'returns cached value' do
+ result = subject.execute
+
+ expect(result[:http_status]).to eq(return_value[:http_status])
+ expect(result[:body]).to eq(return_value[:body])
+ end
+ end
+
+ context 'when value not present in cache' do
+ it 'returns nil' do
+ expect(ReactiveCachingWorker)
+ .to receive(:perform_async)
+ .with(subject.class, subject.id, *opts)
+
+ result = subject.execute
+
+ expect(result).to eq(nil)
+ end
+ end
+ end
+
+ context 'call prometheus api' do
+ let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) }
+
+ before do
+ synchronous_reactive_cache(subject)
+
+ allow(environment).to receive(:prometheus_adapter)
+ .and_return(prometheus_adapter)
+ allow(prometheus_adapter).to receive(:can_query?).and_return(true)
+ allow(prometheus_adapter).to receive(:prometheus_client_wrapper)
+ .and_return(prometheus_client)
+ end
+
+ context 'connection to prometheus server succeeds' do
+ let(:rest_client_response) { instance_double(RestClient::Response) }
+ let(:prometheus_http_status_code) { 400 }
+
+ let(:response_body) do
+ '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}'
+ end
+
+ before do
+ allow(prometheus_client).to receive(:proxy).and_return(rest_client_response)
+
+ allow(rest_client_response).to receive(:code)
+ .and_return(prometheus_http_status_code)
+ allow(rest_client_response).to receive(:body).and_return(response_body)
+ end
+
+ it 'returns the http status code and body from prometheus' do
+ expect(subject.execute).to eq(
+ http_status: prometheus_http_status_code,
+ body: response_body,
+ status: :success
+ )
+ end
+ end
+
+ context 'connection to prometheus server fails' do
+ context 'prometheus client raises Gitlab::PrometheusClient::Error' do
+ before do
+ allow(prometheus_client).to receive(:proxy)
+ .and_raise(Gitlab::PrometheusClient::Error, 'Network connection error')
+ end
+
+ it 'returns error' do
+ expect(subject.execute).to eq(
+ status: :error,
+ message: 'Network connection error',
+ http_status: :service_unavailable
+ )
+ end
+ end
+ end
+ end
+ end
+
+ describe '.from_cache' do
+ it 'initializes an instance of ProxyService class' do
+ result = described_class.from_cache(
+ environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' }
+ )
+
+ expect(result).to be_an_instance_of(described_class)
+ expect(result.proxyable).to eq(environment)
+ expect(result.method).to eq('GET')
+ expect(result.path).to eq('query')
+ expect(result.params).to eq('query' => '1')
+ end
+ end
+end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 79b744142c6..82d24ec43f6 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranches::CreateService do
diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb
index 4a391b6c25c..3287eb9a59b 100644
--- a/spec/services/protected_branches/destroy_service_spec.rb
+++ b/spec/services/protected_branches/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranches::DestroyService do
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 3f6f8e09565..7967ff81075 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranches::UpdateService do
diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb
index b16acf1d36c..e58a539eb6f 100644
--- a/spec/services/protected_tags/create_service_spec.rb
+++ b/spec/services/protected_tags/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedTags::CreateService do
diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb
index e12f53a2221..52d1d1caa34 100644
--- a/spec/services/protected_tags/destroy_service_spec.rb
+++ b/spec/services/protected_tags/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedTags::DestroyService do
diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb
index 2ece4e3b07b..ca5109aca9c 100644
--- a/spec/services/protected_tags/update_service_spec.rb
+++ b/spec/services/protected_tags/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedTags::UpdateService do
diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb
index 81956200bff..855b10c0259 100644
--- a/spec/services/push_event_payload_service_spec.rb
+++ b/spec/services/push_event_payload_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushEventPayloadService do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 938764f40b0..95a131e8c86 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -10,11 +10,15 @@ describe QuickActions::InterpretService do
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:commit) { create(:commit, project: project) }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
+ let(:helmchart) { create(:label, project: project, title: 'Helm Chart Registry') }
let(:bug) { create(:label, project: project, title: 'Bug') }
let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
let(:service) { described_class.new(project, developer) }
before do
+ stub_licensed_features(multiple_issue_assignees: false,
+ multiple_merge_request_assignees: false)
+
project.add_developer(developer)
end
@@ -93,6 +97,26 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'multiword label name starting without ~' do
+ it 'fetches label ids and populates add_label_ids if content contains /label' do
+ helmchart # populate the label
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(add_label_ids: [helmchart.id])
+ end
+ end
+
+ shared_examples 'label name is included in the middle of another label name' do
+ it 'ignores the sublabel when the content contains the includer label name' do
+ helmchart # populate the label
+ create(:label, project: project, title: 'Chart')
+
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(add_label_ids: [helmchart.id])
+ end
+ end
+
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
@@ -505,7 +529,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'assign command' do
+ it_behaves_like 'assign command', :quarantine do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
let(:issuable) { merge_request }
end
@@ -623,6 +647,26 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
+ it_behaves_like 'multiword label name starting without ~' do
+ let(:content) { %(/label "#{helmchart.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'multiword label name starting without ~' do
+ let(:content) { %(/label "#{helmchart.title}") }
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label name is included in the middle of another label name' do
+ let(:content) { %(/label ~"#{helmchart.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'label name is included in the middle of another label name' do
+ let(:content) { %(/label ~"#{helmchart.title}") }
+ let(:issuable) { merge_request }
+ end
+
it_behaves_like 'unlabel command' do
let(:content) { %(/unlabel ~"#{inprogress.title}") }
let(:issuable) { issue }
@@ -1526,5 +1570,15 @@ describe QuickActions::InterpretService do
end
end
end
+
+ context "#commands_executed_count" do
+ it 'counts commands executed' do
+ content = "/close and \n/assign me and \n/title new title"
+
+ service.execute(content, issue)
+
+ expect(service.commands_executed_count).to eq(3)
+ end
+ end
end
end
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index 612e9f152e7..e26676cdd55 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Releases::CreateService do
@@ -19,6 +21,8 @@ describe Releases::CreateService do
shared_examples 'a successful release creation' do
it 'creates a new release' do
result = service.execute
+
+ expect(project.releases.count).to eq(1)
expect(result[:status]).to eq(:success)
expect(result[:tag]).not_to be_nil
expect(result[:release]).not_to be_nil
@@ -69,4 +73,12 @@ describe Releases::CreateService do
end
end
end
+
+ describe '#find_or_build_release' do
+ it 'does not save the built release' do
+ service.find_or_build_release
+
+ expect(project.releases.count).to eq(0)
+ end
+ end
end
diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb
index dd5b8708f36..f4c901e6585 100644
--- a/spec/services/releases/destroy_service_spec.rb
+++ b/spec/services/releases/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Releases::DestroyService do
@@ -28,13 +30,11 @@ describe Releases::DestroyService do
end
end
- context 'when tag is not found' do
+ context 'when tag does not exist in the repository' do
let(:tag) { 'v1.1.1' }
- it 'returns an error' do
- is_expected.to include(status: :error,
- message: 'Tag does not exist',
- http_status: 404)
+ it 'removes the orphaned release' do
+ expect { subject }.to change { project.releases.count }.by(-1)
end
end
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index 6c68f364739..14e6a5f13c8 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Releases::UpdateService do
diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb
index bf79cfe74b7..9918bb8e054 100644
--- a/spec/services/repair_ldap_blocked_user_service_spec.rb
+++ b/spec/services/repair_ldap_blocked_user_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepairLdapBlockedUserService do
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
index ab1c638fc39..60a14d7a107 100644
--- a/spec/services/repository_archive_clean_up_service_spec.rb
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryArchiveCleanUpService do
diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb
index 1490ad5fe3b..a4db4481c36 100644
--- a/spec/services/reset_project_cache_service_spec.rb
+++ b/spec/services/reset_project_cache_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ResetProjectCacheService do
diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb
index 980545b8083..62a70ccd3da 100644
--- a/spec/services/search/global_service_spec.rb
+++ b/spec/services/search/global_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Search::GlobalService do
diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb
index cbc553a60cf..aac2f3fe4cb 100644
--- a/spec/services/search/group_service_spec.rb
+++ b/spec/services/search/group_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Search::GroupService do
diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb
index 8ad162ad66e..430c71880a3 100644
--- a/spec/services/search/snippet_service_spec.rb
+++ b/spec/services/search/snippet_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Search::SnippetService do
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index e5e036c7d44..48065bf596a 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SearchService do
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
new file mode 100644
index 00000000000..e790d272e61
--- /dev/null
+++ b/spec/services/service_response_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+ActiveSupport::Dependencies.autoload_paths << 'app/services'
+
+describe ServiceResponse do
+ describe '.success' do
+ it 'creates a successful response without a message' do
+ expect(described_class.success).to be_success
+ end
+
+ it 'creates a successful response with a message' do
+ response = described_class.success(message: 'Good orange')
+
+ expect(response).to be_success
+ expect(response.message).to eq('Good orange')
+ end
+
+ it 'creates a successful response with payload' do
+ response = described_class.success(payload: { good: 'orange' })
+
+ expect(response).to be_success
+ expect(response.payload).to eq(good: 'orange')
+ end
+ end
+
+ describe '.error' do
+ it 'creates a failed response without HTTP status' do
+ response = described_class.error(message: 'Bad apple')
+
+ expect(response).to be_error
+ expect(response.message).to eq('Bad apple')
+ end
+
+ it 'creates a failed response with HTTP status' do
+ response = described_class.error(message: 'Bad apple', http_status: 400)
+
+ expect(response).to be_error
+ expect(response.message).to eq('Bad apple')
+ expect(response.http_status).to eq(400)
+ end
+
+ it 'creates a failed response with payload' do
+ response = described_class.error(message: 'Bad apple',
+ payload: { bad: 'apple' })
+
+ expect(response).to be_error
+ expect(response.message).to eq('Bad apple')
+ expect(response.payload).to eq(bad: 'apple')
+ end
+ end
+
+ describe '#success?' do
+ it 'returns true for a successful response' do
+ expect(described_class.success.success?).to eq(true)
+ end
+
+ it 'returns false for a failed response' do
+ expect(described_class.error(message: 'Bad apple').success?).to eq(false)
+ end
+ end
+
+ describe '#error?' do
+ it 'returns false for a successful response' do
+ expect(described_class.success.error?).to eq(false)
+ end
+
+ it 'returns true for a failed response' do
+ expect(described_class.error(message: 'Bad apple').error?).to eq(true)
+ end
+ end
+end
diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb
index 61312d55b84..b9e5e844c1f 100644
--- a/spec/services/spam_service_spec.rb
+++ b/spec/services/spam_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SpamService do
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index c8a6fc1a99b..653f17a4324 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SubmitUsagePingService do
+ include StubRequests
+
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
@@ -97,7 +101,7 @@ describe SubmitUsagePingService do
end
def stub_response(body)
- stub_request(:post, 'https://version.gitlab.com/usage_data')
+ stub_full_request('https://version.gitlab.com/usage_data', method: :post)
.to_return(
headers: { 'Content-Type' => 'application/json' },
body: body.to_json
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 8e77d582eb4..bdbcb0fdb07 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -5,21 +5,63 @@ require 'spec_helper'
describe Suggestions::ApplyService do
include ProjectForksHelper
+ def build_position(args = {})
+ default_args = { old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs }
+
+ Gitlab::Diff::Position.new(default_args.merge(args))
+ end
+
+ shared_examples 'successfully creates commit and updates suggestion' do
+ def apply(suggestion)
+ result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'updates the file with the new contents' do
+ apply(suggestion)
+
+ blob = project.repository.blob_at_branch(merge_request.source_branch,
+ position.new_path)
+
+ expect(blob.data).to eq(expected_content)
+ end
+
+ it 'updates suggestion applied and commit_id columns' do
+ expect { apply(suggestion) }
+ .to change(suggestion, :applied)
+ .from(false).to(true)
+ .and change(suggestion, :commit_id)
+ .from(nil)
+ end
+
+ it 'created commit has users email and name' do
+ apply(suggestion)
+
+ commit = project.repository.commit
+
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(user.name)
+ end
+ end
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :commit_email) }
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 9,
- diff_refs: merge_request.diff_refs)
+ let(:position) { build_position }
+
+ let(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project)
end
let(:suggestion) do
- create(:suggestion, note: diff_note,
- from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
- to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
+ create(:suggestion, :content_from_repo, note: diff_note,
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
end
subject { described_class.new(user) }
@@ -74,49 +116,11 @@ describe Suggestions::ApplyService do
target_project: project)
end
- let!(:diff_note) do
- create(:diff_note_on_merge_request, noteable: merge_request,
- position: position,
- project: project)
- end
-
before do
project.add_maintainer(user)
end
- it 'updates the file with the new contents' do
- subject.execute(suggestion)
-
- blob = project.repository.blob_at_branch(merge_request.source_branch,
- position.new_path)
-
- expect(blob.data).to eq(expected_content)
- end
-
- it 'returns success status' do
- result = subject.execute(suggestion)
-
- expect(result[:status]).to eq(:success)
- end
-
- it 'updates suggestion applied and commit_id columns' do
- expect { subject.execute(suggestion) }
- .to change(suggestion, :applied)
- .from(false).to(true)
- .and change(suggestion, :commit_id)
- .from(nil)
- end
-
- it 'created commit has users email and name' do
- subject.execute(suggestion)
-
- commit = project.repository.commit
-
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- expect(commit.author_name).to eq(user.name)
- end
+ it_behaves_like 'successfully creates commit and updates suggestion'
context 'when it fails to apply because the file was changed' do
it 'returns error message' do
@@ -190,11 +194,6 @@ describe Suggestions::ApplyService do
CONTENT
end
- let(:merge_request) do
- create(:merge_request, source_project: project,
- target_project: project)
- end
-
def create_suggestion(diff, old_line: nil, new_line: nil, from_content:, to_content:, path:)
position = Gitlab::Diff::Position.new(old_path: path,
new_path: path,
@@ -212,11 +211,13 @@ describe Suggestions::ApplyService do
end
def apply_suggestion(suggestion)
- suggestion.note.reload
+ suggestion.reload
merge_request.reload
merge_request.clear_memoized_shas
result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+
refresh = MergeRequests::RefreshService.new(project, user)
refresh.execute(merge_request.diff_head_sha,
suggestion.commit_id,
@@ -241,7 +242,7 @@ describe Suggestions::ApplyService do
suggestion_2_changes = { old_line: 24,
new_line: 31,
- from_content: " @cmd_output << stderr.read\n",
+ from_content: " @cmd_output << stderr.read\n",
to_content: "# v2 change\n",
path: path }
@@ -287,6 +288,105 @@ describe Suggestions::ApplyService do
expect(suggestion_2_diff.strip).to eq(expected_suggestion_2_diff.strip)
end
end
+
+ context 'multi-line suggestion' do
+ let(:expected_content) do
+ <<~CONTENT
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ # multi
+ # line
+
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ let(:suggestion) do
+ create(:suggestion, :content_from_repo, note: diff_note,
+ lines_above: 2,
+ lines_below: 3,
+ to_content: "# multi\n# line\n")
+ end
+
+ it_behaves_like 'successfully creates commit and updates suggestion'
+ end
+
+ context 'remove an empty line suggestion' do
+ let(:expected_content) do
+ <<~CONTENT
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ let(:position) { build_position(new_line: 13) }
+ let(:suggestion) do
+ create(:suggestion, :content_from_repo, note: diff_note, to_content: "")
+ end
+
+ it_behaves_like 'successfully creates commit and updates suggestion'
+ end
end
context 'fork-project' do
@@ -362,6 +462,28 @@ describe Suggestions::ApplyService do
project.add_maintainer(user)
end
+ context 'diff file was not found' do
+ it 'returns error message' do
+ expect(suggestion.note).to receive(:latest_diff_file) { nil }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+
+ context 'suggestion is eligible to be outdated' do
+ it 'returns error message' do
+ expect(suggestion).to receive(:outdated?) { true }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+
context 'suggestion was already applied' do
it 'returns success status' do
result = subject.execute(suggestion)
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index f1142c88a69..d95f9e3349b 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -9,14 +9,18 @@ describe Suggestions::CreateService do
target_project: project_with_repo)
end
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs)
+ def build_position(args = {})
+ default_args = { old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs }
+
+ Gitlab::Diff::Position.new(default_args.merge(args))
end
+ let(:position) { build_position }
+
let(:markdown) do
<<-MARKDOWN.strip_heredoc
```suggestion
@@ -36,6 +40,14 @@ describe Suggestions::CreateService do
```thing
this is not a suggestion, it's a thing
```
+
+ ```suggestion:-3+2
+ # multi-line suggestion 1
+ ```
+
+ ```suggestion:-5
+ # multi-line suggestion 1
+ ```
MARKDOWN
end
@@ -50,7 +62,7 @@ describe Suggestions::CreateService do
end
it 'does not try to parse suggestions' do
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
@@ -67,13 +79,30 @@ describe Suggestions::CreateService do
it 'does not try to parse suggestions' do
allow(note).to receive(:on_text?) { false }
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
end
end
+ context 'should not create suggestions' do
+ let(:note) do
+ create(:diff_note_on_merge_request, project: project_with_repo,
+ noteable: merge_request,
+ position: position,
+ note: markdown)
+ end
+
+ it 'creates no suggestion when diff file is not found' do
+ expect_next_instance_of(DiffNote) do |diff_note|
+ expect(diff_note).to receive(:latest_diff_file).once { nil }
+ end
+
+ expect { subject.execute }.not_to change(Suggestion, :count)
+ end
+ end
+
context 'should create suggestions' do
let(:note) do
create(:diff_note_on_merge_request, project: project_with_repo,
@@ -82,27 +111,64 @@ describe Suggestions::CreateService do
note: markdown)
end
- context 'single line suggestions' do
- it 'persists suggestion records' do
- expect { subject.execute }
- .to change { note.suggestions.count }
- .from(0)
- .to(2)
+ let(:expected_suggestions) do
+ Gitlab::Diff::SuggestionsParser.parse(markdown,
+ project: note.project,
+ position: note.position)
+ end
+
+ it 'persists suggestion records' do
+ expect { subject.execute }.to change { note.suggestions.count }
+ .from(0).to(expected_suggestions.size)
+ end
+
+ it 'persists suggestions data correctly' do
+ subject.execute
+
+ suggestions = note.suggestions.order(:relative_order)
+
+ suggestions.zip(expected_suggestions) do |suggestion, expected_suggestion|
+ expected_data = expected_suggestion.to_hash
+
+ expect(suggestion.from_content).to eq(expected_data[:from_content])
+ expect(suggestion.to_content).to eq(expected_data[:to_content])
+ expect(suggestion.lines_above).to eq(expected_data[:lines_above])
+ expect(suggestion.lines_below).to eq(expected_data[:lines_below])
end
+ end
+
+ context 'outdated position note' do
+ let!(:outdated_diff) { merge_request.merge_request_diff }
+ let!(:latest_diff) { merge_request.create_merge_request_diff }
+ let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
+ let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
+
+ it 'uses the correct position when creating the suggestion' do
+ expect(Gitlab::Diff::SuggestionsParser).to receive(:parse)
+ .with(note.note, project: note.project, position: note.position)
+ .and_call_original
- it 'persists original from_content lines and suggested lines' do
subject.execute
+ end
+ end
- suggestions = note.suggestions.order(:relative_order)
+ context 'when a patch removes an empty line' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ ```
+ MARKDOWN
+ end
+ let(:position) { build_position(new_line: 13) }
- suggestion_1 = suggestions.first
- suggestion_2 = suggestions.last
+ it 'creates an appliable suggestion' do
+ subject.execute
- expect(suggestion_1).to have_attributes(from_content: " vars = {\n",
- to_content: " foo\n bar\n")
+ suggestion = note.suggestions.last
- expect(suggestion_2).to have_attributes(from_content: " vars = {\n",
- to_content: " xpto\n baz\n")
+ expect(suggestion).to be_appliable
+ expect(suggestion.from_content).to eq("\n")
+ expect(suggestion.to_content).to eq("")
end
end
end
diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb
new file mode 100644
index 00000000000..bcc627013d8
--- /dev/null
+++ b/spec/services/suggestions/outdate_service_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestions::OutdateService do
+ describe '#execute' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.target_project }
+ let(:user) { merge_request.author }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:branch_name) { project.default_branch }
+ let(:diff_file) { suggestion.diff_file }
+ let(:position) { build_position(file_path, comment_line) }
+ let(:note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ def build_position(path, line)
+ Gitlab::Diff::Position.new(old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: line,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ def commit_changes(file_path, new_content)
+ params = {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: new_content,
+ start_project: project,
+ start_branch: project.default_branch,
+ branch_name: branch_name
+ }
+
+ Files::UpdateService.new(project, user, params).execute
+ end
+
+ def update_file_line(diff_file, change_line, content)
+ new_lines = diff_file.new_blob.data.lines
+ new_lines[change_line..change_line] = content
+ result = commit_changes(diff_file.file_path, new_lines.join)
+ newrev = result[:result]
+
+ expect(result[:status]).to eq(:success)
+ expect(newrev).to be_present
+
+ # Ensure all memoized data is cleared in order
+ # to generate the new merge_request_diff.
+ MergeRequest.find(merge_request.id).reload_diff(user)
+
+ note.reload
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ context 'when there is a change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 8 } # suggesting to change lines 1..9
+ let(:change_line) { 2 } # line 2 is within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ it 'updates the outdatable suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.to change { suggestion.reload.outdated }
+ .from(false).to(true)
+ end
+ end
+
+ context 'when there is no change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 3 } # suggesting to change lines 6..9
+ let(:change_line) { 2 } # line 2 is not within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ it 'does not outdates suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.not_to change { suggestion.reload.outdated }.from(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 81b2c17fdb5..f5c6e972953 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemHooksService do
@@ -84,20 +86,20 @@ describe SystemHooksService do
context 'group_rename' do
it 'contains old and new path' do
- allow(group).to receive(:path_was).and_return('old-path')
+ allow(group).to receive(:path_before_last_save).and_return('old-path')
data = event_data(group, :rename)
expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path)
expect(data[:path]).to eq(group.path)
expect(data[:full_path]).to eq(group.path)
- expect(data[:old_path]).to eq(group.path_was)
- expect(data[:old_full_path]).to eq(group.path_was)
+ expect(data[:old_path]).to eq(group.path_before_last_save)
+ expect(data[:old_full_path]).to eq(group.path_before_last_save)
end
it 'contains old and new full_path for subgroup' do
subgroup = create(:group, parent: group)
- allow(subgroup).to receive(:path_was).and_return('old-path')
+ allow(subgroup).to receive(:path_before_last_save).and_return('old-path')
data = event_data(subgroup, :rename)
@@ -108,13 +110,13 @@ describe SystemHooksService do
context 'user_rename' do
it 'contains old and new username' do
- allow(user).to receive(:username_was).and_return('old-username')
+ allow(user).to receive(:username_before_last_save).and_return('old-username')
data = event_data(user, :rename)
expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username)
expect(data[:username]).to eq(user.username)
- expect(data[:old_username]).to eq(user.username_was)
+ expect(data[:old_username]).to eq(user.username_before_last_save)
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 82544ab0413..2420817e1f7 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemNoteService do
+ include ProjectForksHelper
include Gitlab::Routing
include RepoHelpers
include AssetsHelpers
@@ -11,6 +14,14 @@ describe SystemNoteService do
let(:noteable) { create(:issue, project: project) }
let(:issue) { noteable }
+ shared_examples_for 'a note with overridable created_at' do
+ let(:noteable) { create(:issue, project: project, system_note_timestamp: Time.at(42)) }
+
+ it 'the note has the correct time' do
+ expect(subject.created_at).to eq Time.at(42)
+ end
+ end
+
shared_examples_for 'a system note' do
let(:expected_noteable) { noteable }
let(:commit_count) { nil }
@@ -121,7 +132,7 @@ describe SystemNoteService do
end
it 'sets the note text' do
- link = "http://localhost/#{project.full_path}/tags/#{tag_name}"
+ link = "/#{project.full_path}/-/tags/#{tag_name}"
expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
end
@@ -137,6 +148,8 @@ describe SystemNoteService do
end
context 'when assignee added' do
+ it_behaves_like 'a note with overridable created_at'
+
it 'sets the note text' do
expect(subject.note).to eq "assigned to @#{assignee.username}"
end
@@ -145,14 +158,16 @@ describe SystemNoteService do
context 'when assignee removed' do
let(:assignee) { nil }
+ it_behaves_like 'a note with overridable created_at'
+
it 'sets the note text' do
expect(subject.note).to eq 'removed assignee'
end
end
end
- describe '.change_issue_assignees' do
- subject { described_class.change_issue_assignees(noteable, project, author, [assignee]) }
+ describe '.change_issuable_assignees' do
+ subject { described_class.change_issuable_assignees(noteable, project, author, [assignee]) }
let(:assignee) { create(:user) }
let(:assignee1) { create(:user) }
@@ -165,9 +180,11 @@ describe SystemNoteService do
def build_note(old_assignees, new_assignees)
issue.assignees = new_assignees
- described_class.change_issue_assignees(issue, project, author, old_assignees).note
+ described_class.change_issuable_assignees(issue, project, author, old_assignees).note
end
+ it_behaves_like 'a note with overridable created_at'
+
it 'builds a correct phrase when an assignee is added to a non-assigned issue' do
expect(build_note([], [assignee1])).to eq "assigned to @#{assignee1.username}"
end
@@ -213,6 +230,8 @@ describe SystemNoteService do
expect(subject.note).to eq "changed milestone to #{reference}"
end
+
+ it_behaves_like 'a note with overridable created_at'
end
context 'when milestone removed' do
@@ -221,6 +240,8 @@ describe SystemNoteService do
it 'sets the note text' do
expect(subject.note).to eq 'removed milestone'
end
+
+ it_behaves_like 'a note with overridable created_at'
end
end
@@ -237,6 +258,8 @@ describe SystemNoteService 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
+
+ it_behaves_like 'a note with overridable created_at'
end
context 'when milestone removed' do
@@ -245,6 +268,8 @@ describe SystemNoteService do
it 'sets the note text' do
expect(subject.note).to eq 'removed milestone'
end
+
+ it_behaves_like 'a note with overridable created_at'
end
end
end
@@ -254,6 +279,8 @@ describe SystemNoteService do
let(:due_date) { Date.today }
+ it_behaves_like 'a note with overridable created_at'
+
it_behaves_like 'a system note' do
let(:action) { 'due_date' }
end
@@ -280,6 +307,8 @@ describe SystemNoteService do
let(:status) { 'reopened' }
let(:source) { nil }
+ it_behaves_like 'a note with overridable created_at'
+
it_behaves_like 'a system note' do
let(:action) { 'opened' }
end
@@ -289,6 +318,8 @@ describe SystemNoteService do
let(:status) { 'opened' }
let(:source) { double('commit', gfm_reference: 'commit 123456') }
+ it_behaves_like 'a note with overridable created_at'
+
it 'sets the note text' do
expect(subject.note).to eq "#{status} via commit 123456"
end
@@ -338,6 +369,8 @@ describe SystemNoteService do
let(:action) { 'title' }
end
+ it_behaves_like 'a note with overridable created_at'
+
it 'sets the note text' do
expect(subject.note)
.to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**"
@@ -353,6 +386,8 @@ describe SystemNoteService do
let(:action) { 'description' }
end
+ it_behaves_like 'a note with overridable created_at'
+
it 'sets the note text' do
expect(subject.note).to eq('changed the description')
end
@@ -478,6 +513,8 @@ describe SystemNoteService do
let(:action) { 'cross_reference' }
end
+ it_behaves_like 'a note with overridable created_at'
+
describe 'note_body' do
context 'cross-project' do
let(:project2) { create(:project, :repository) }
@@ -619,7 +656,7 @@ describe SystemNoteService do
context 'commit with cross-reference from fork' do
let(:author2) { create(:project_member, :reporter, user: create(:user), project: project).user }
- let(:forked_project) { Projects::ForkService.new(project, author2).execute }
+ let(:forked_project) { fork_project(project, author2, repository: true) }
let(:commit2) { forked_project.commit }
before do
@@ -807,9 +844,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_commit_url(project, commit),
- title: "GitLab: Mentioned on commit - #{commit.title}",
+ title: "Commit - #{commit.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
@@ -833,9 +871,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_issue_url(project, issue),
- title: "GitLab: Mentioned on issue - #{issue.title}",
+ title: "Issue - #{issue.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
@@ -859,9 +898,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_snippet_url(project, snippet),
- title: "GitLab: Mentioned on snippet - #{snippet.title}",
+ title: "Snippet - #{snippet.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
@@ -893,6 +933,28 @@ describe SystemNoteService do
end
end
+ describe '.change_time_estimate' do
+ subject { described_class.change_time_estimate(noteable, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'time_tracking' }
+ end
+
+ context 'with a time estimate' do
+ it 'sets the note text' do
+ noteable.update_attribute(:time_estimate, 277200)
+
+ expect(subject.note).to eq "changed time estimate to 1w 4d 5h"
+ end
+ end
+
+ context 'without a time estimate' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "removed time estimate"
+ end
+ end
+ end
+
describe '.discussion_continued_in_issue' do
let(:discussion) { create(:diff_note_on_merge_request, project: project).to_discussion }
let(:merge_request) { discussion.noteable }
@@ -919,28 +981,6 @@ describe SystemNoteService do
end
end
- describe '.change_time_estimate' do
- subject { described_class.change_time_estimate(noteable, project, author) }
-
- it_behaves_like 'a system note' do
- let(:action) { 'time_tracking' }
- end
-
- context 'with a time estimate' do
- it 'sets the note text' do
- noteable.update_attribute(:time_estimate, 277200)
-
- expect(subject.note).to eq "changed time estimate to 1w 4d 5h"
- end
- end
-
- context 'without a time estimate' do
- it 'sets the note text' do
- expect(subject.note).to eq "removed time estimate"
- end
- end
- end
-
describe '.change_time_spent' do
# We need a custom noteable in order to the shared examples to be green.
let(:noteable) do
@@ -1099,7 +1139,7 @@ describe SystemNoteService do
diff_id = merge_request.merge_request_diff.id
line_code = change_position.line_code(project.repository)
- expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
+ expect(subject.note).to include(diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code))
end
end
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index 0cbe57352be..ee558f90d6f 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Tags::CreateService do
@@ -41,7 +43,7 @@ describe Tags::CreateService do
it 'returns an error' do
expect(repository).to receive(:add_tag)
.with(user, 'v1.1.0', 'master', 'Foo')
- .and_raise(Gitlab::Git::PreReceiveError, 'something went wrong')
+ .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: something went wrong')
response = service.execute('v1.1.0', 'master', 'Foo')
diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index 7c8c1dd0d3a..b46bd77eafe 100644
--- a/spec/services/tags/destroy_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Tags::DestroyService do
@@ -7,11 +9,27 @@ describe Tags::DestroyService do
let(:service) { described_class.new(project, user) }
describe '#execute' do
+ subject { service.execute(tag_name) }
+
it 'removes the tag' do
expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success)
service.execute('v1.1.0')
end
+
+ context 'when there is an associated release on the tag' do
+ let(:tag) { repository.tags.first }
+ let(:tag_name) { tag.name }
+
+ before do
+ project.add_maintainer(user)
+ create(:release, tag: tag_name, project: project)
+ end
+
+ it 'destroys the release' do
+ expect { subject }.to change { project.releases.count }.by(-1)
+ end
+ end
end
end
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index b1260cf740a..9adaee6481b 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -113,4 +113,25 @@ describe TaskListToggleService do
expect(toggler.execute).to be_falsey
end
+
+ it 'properly handles a GitLab blockquote' do
+ markdown =
+ <<-EOT.strip_heredoc
+ >>>
+ gitlab blockquote
+ >>>
+
+ * [ ] Task 1
+ * [x] Task 2
+ EOT
+
+ markdown_html = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)[:output].to_html
+ toggler = described_class.new(markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '* [ ] Task 1', line_number: 5)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[4]).to eq "* [x] Task 1\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> Task 1')
+ end
end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index 19e1c5ff3b2..8d30f5018dd 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TestHooks::ProjectService do
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index 74d7715e50f..799b57eb04e 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TestHooks::SystemService do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 8631f3f9a33..9ee23f3eb48 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodoService do
@@ -272,28 +274,6 @@ describe TodoService do
end
end
- describe '#reassigned_issue' do
- it 'creates a pending todo for new assignee' do
- unassigned_issue.assignees << john_doe
- service.reassigned_issue(unassigned_issue, author)
-
- should_create_todo(user: john_doe, target: unassigned_issue, action: Todo::ASSIGNED)
- end
-
- it 'does not create a todo if unassigned' do
- issue.assignees.destroy_all # rubocop: disable DestroyAll
-
- should_not_create_any_todo { service.reassigned_issue(issue, author) }
- end
-
- it 'creates a todo if new assignee is the current user' do
- unassigned_issue.assignees << john_doe
- service.reassigned_issue(unassigned_issue, john_doe)
-
- should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
- end
- end
-
describe '#mark_pending_todos_as_done' do
it 'marks related pending todos to the target for the user as done' do
first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
@@ -504,10 +484,60 @@ describe TodoService do
end
end
+ describe '#reassigned_issuable' do
+ shared_examples 'reassigned issuable' do
+ it 'creates a pending todo for new assignee' do
+ issuable_unassigned.assignees = [john_doe]
+ service.reassigned_issuable(issuable_unassigned, author)
+
+ should_create_todo(user: john_doe, target: issuable_unassigned, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ issuable_assigned.assignees = []
+
+ should_not_create_any_todo { service.reassigned_issuable(issuable_assigned, author) }
+ end
+
+ it 'creates a todo if new assignee is the current user' do
+ issuable_assigned.assignees = [john_doe]
+ service.reassigned_issuable(issuable_assigned, john_doe)
+
+ should_create_todo(user: john_doe, target: issuable_assigned, author: john_doe, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo for guests' do
+ service.reassigned_issuable(issuable_assigned, author)
+ should_not_create_todo(user: guest, target: issuable_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'does not create a directly addressed todo for guests' do
+ service.reassigned_issuable(addressed_issuable_assigned, author)
+ should_not_create_todo(user: guest, target: addressed_issuable_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
+ end
+
+ context 'issuable is a merge request' do
+ it_behaves_like 'reassigned issuable' do
+ let(:issuable_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_issuable_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
+ let(:issuable_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) }
+ end
+ end
+
+ context 'issuable is an issue' do
+ it_behaves_like 'reassigned issuable' do
+ let(:issuable_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_issuable_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
+ let(:issuable_unassigned) { create(:issue, project: project, author: author, assignees: []) }
+ end
+ end
+ end
+
describe 'Merge Requests' do
- let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
- let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
- let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
+ let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
+ let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) }
describe '#new_merge_request' do
it 'creates a pending todo if assigned' do
@@ -659,38 +689,6 @@ describe TodoService do
end
end
- describe '#reassigned_merge_request' do
- it 'creates a pending todo for new assignee' do
- mr_unassigned.update_attribute(:assignee, john_doe)
- service.reassigned_merge_request(mr_unassigned, author)
-
- should_create_todo(user: john_doe, target: mr_unassigned, action: Todo::ASSIGNED)
- end
-
- it 'does not create a todo if unassigned' do
- mr_assigned.update_attribute(:assignee, nil)
-
- should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) }
- end
-
- it 'creates a todo if new assignee is the current user' do
- mr_assigned.update_attribute(:assignee, john_doe)
- service.reassigned_merge_request(mr_assigned, john_doe)
-
- should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED)
- end
-
- it 'does not create a todo for guests' do
- service.reassigned_merge_request(mr_assigned, author)
- should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
- end
-
- it 'does not create a directly addressed todo for guests' do
- service.reassigned_merge_request(addressed_mr_assigned, author)
- should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
- end
- end
-
describe '#merge_merge_request' do
it 'marks related pending todos to the target for the user as done' do
first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb
index 3294f7509aa..9f7e656f7d3 100644
--- a/spec/services/todos/destroy/confidential_issue_service_spec.rb
+++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todos::Destroy::ConfidentialIssueService do
@@ -7,36 +9,60 @@ describe Todos::Destroy::ConfidentialIssueService do
let(:assignee) { create(:user) }
let(:guest) { create(:user) }
let(:project_member) { create(:user) }
- let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
-
- let!(:todo_issue_non_member) { create(:todo, user: user, target: issue, project: project) }
- let!(:todo_issue_member) { create(:todo, user: project_member, target: issue, project: project) }
- let!(:todo_issue_author) { create(:todo, user: author, target: issue, project: project) }
- let!(:todo_issue_asignee) { create(:todo, user: assignee, target: issue, project: project) }
- let!(:todo_issue_guest) { create(:todo, user: guest, target: issue, project: project) }
- let!(:todo_another_non_member) { create(:todo, user: user, project: project) }
+ let(:issue_1) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
describe '#execute' do
before do
project.add_developer(project_member)
project.add_guest(guest)
+
+ # todos not to be deleted
+ create(:todo, user: project_member, target: issue_1, project: project)
+ create(:todo, user: author, target: issue_1, project: project)
+ create(:todo, user: assignee, target: issue_1, project: project)
+ create(:todo, user: user, project: project)
+ # Todos to be deleted
+ create(:todo, user: guest, target: issue_1, project: project)
+ create(:todo, user: user, target: issue_1, project: project)
end
- subject { described_class.new(issue.id).execute }
+ subject { described_class.new(issue_id: issue_1.id).execute }
- context 'when provided issue is confidential' do
- before do
- issue.update!(confidential: true)
+ context 'when issue_id parameter is present' do
+ context 'when provided issue is confidential' do
+ it 'removes issue todos for users who can not access the confidential issue' do
+ expect { subject }.to change { Todo.count }.from(6).to(4)
+ end
end
- it 'removes issue todos for users who can not access the confidential issue' do
- expect { subject }.to change { Todo.count }.from(6).to(4)
+ context 'when provided issue is not confidential' do
+ it 'does not remove any todos' do
+ issue_1.update(confidential: false)
+
+ expect { subject }.not_to change { Todo.count }
+ end
end
end
- context 'when provided issue is not confidential' do
- it 'does not remove any todos' do
- expect { subject }.not_to change { Todo.count }
+ context 'when project_id parameter is present' do
+ subject { described_class.new(issue_id: nil, project_id: project.id).execute }
+
+ it 'removes issues todos for users that cannot access confidential issues' do
+ issue_2 = create(:issue, :confidential, project: project)
+ issue_3 = create(:issue, :confidential, project: project, author: author, assignees: [assignee])
+ issue_4 = create(:issue, project: project)
+ # Todos not to be deleted
+ create(:todo, user: guest, target: issue_1, project: project)
+ create(:todo, user: assignee, target: issue_1, project: project)
+ create(:todo, user: project_member, target: issue_2, project: project)
+ create(:todo, user: author, target: issue_3, project: project)
+ create(:todo, user: user, target: issue_4, project: project)
+ create(:todo, user: user, project: project)
+ # Todos to be deleted
+ create(:todo, user: user, target: issue_1, project: project)
+ create(:todo, user: guest, target: issue_2, project: project)
+
+ expect { subject }.to change { Todo.count }.from(14).to(10)
end
end
end
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 4b238280848..2a553e18807 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todos::Destroy::EntityLeaveService do
@@ -73,6 +75,13 @@ describe Todos::Destroy::EntityLeaveService do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
+ it 'enqueues the PrivateFeaturesWorker' do
+ expect(TodosDestroyer::PrivateFeaturesWorker)
+ .to receive(:perform_async).with(project.id, user.id)
+
+ subject
+ end
+
context 'confidential issues' do
context 'when a user is not an author of confidential issue' do
it 'removes only confidential issues todos' do
@@ -244,6 +253,13 @@ describe Todos::Destroy::EntityLeaveService do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
+ it 'enqueues the PrivateFeaturesWorker' do
+ expect(TodosDestroyer::PrivateFeaturesWorker)
+ .to receive(:perform_async).with(project.id, user.id)
+
+ subject
+ end
+
context 'when user is not member' do
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index 5cefbdd35ab..a1798686d7c 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todos::Destroy::GroupPrivateService do
diff --git a/spec/services/todos/destroy/private_features_service_spec.rb b/spec/services/todos/destroy/private_features_service_spec.rb
index be8b5bb3979..7831e3a47e0 100644
--- a/spec/services/todos/destroy/private_features_service_spec.rb
+++ b/spec/services/todos/destroy/private_features_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todos::Destroy::PrivateFeaturesService do
diff --git a/spec/services/todos/destroy/project_private_service_spec.rb b/spec/services/todos/destroy/project_private_service_spec.rb
index 128d3487514..7c0c76b6c29 100644
--- a/spec/services/todos/destroy/project_private_service_spec.rb
+++ b/spec/services/todos/destroy/project_private_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todos::Destroy::ProjectPrivateService do
diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb
index 3c55dd9659a..7dc52f6816a 100644
--- a/spec/services/update_deployment_service_spec.rb
+++ b/spec/services/update_deployment_service_spec.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateDeploymentService do
let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
let(:options) { { name: 'production' } }
let(:job) do
@@ -13,24 +16,23 @@ describe UpdateDeploymentService do
project: project)
end
- let(:project) { create(:project, :repository) }
- let(:environment) { deployment.environment }
let(:deployment) { job.deployment }
- let(:service) { described_class.new(deployment) }
+ let(:environment) { deployment.environment }
+
+ subject(:service) { described_class.new(deployment) }
before do
+ allow(Deployments::FinishedWorker).to receive(:perform_async)
job.success! # Create/Succeed deployment
end
describe '#execute' do
- subject { service.execute }
-
let(:store) { Gitlab::EtagCaching::Store.new }
it 'invalidates the environment etag cache' do
old_value = store.get(environment.etag_cache_key)
- subject
+ service.execute
expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
end
@@ -40,14 +42,30 @@ describe UpdateDeploymentService do
.to receive(:create_ref)
.with(deployment.ref, deployment.send(:ref_path))
- subject
+ service.execute
end
it 'updates merge request metrics' do
expect_any_instance_of(Deployment)
.to receive(:update_merge_request_metrics!)
- subject
+ service.execute
+ end
+
+ it 'returns the deployment' do
+ expect(subject.execute).to eq(deployment)
+ end
+
+ it 'returns the deployment when could not save the environment' do
+ allow(environment).to receive(:save).and_return(false)
+
+ expect(subject.execute).to eq(deployment)
+ end
+
+ it 'returns the deployment when environment is stopped' do
+ allow(environment).to receive(:stopped?).and_return(true)
+
+ expect(subject.execute).to eq(deployment)
end
context 'when start action is defined' do
@@ -59,7 +77,7 @@ describe UpdateDeploymentService do
end
it 'makes environment available' do
- subject
+ service.execute
expect(environment.reload).to be_available
end
@@ -78,11 +96,11 @@ describe UpdateDeploymentService do
end
it 'does not create a new environment' do
- expect { subject }.not_to change { Environment.count }
+ expect { subject.execute }.not_to change { Environment.count }
end
it 'updates external url' do
- subject
+ subject.execute
expect(subject.environment.name).to eq('review-apps/master')
expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com')
diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb
index 812dd42934d..12a2b287c72 100644
--- a/spec/services/update_merge_request_metrics_service_spec.rb
+++ b/spec/services/update_merge_request_metrics_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe MergeRequestMetricsService do
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index ef535c5cf1f..23ea4e003f8 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateSnippetService do
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 4a809d5bf18..504e61f9903 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UploadService do
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index 87a90378e2b..902ed723e09 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserProjectAccessChangedService do
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 3c0a4ac8e18..d8d2be87fd3 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::ActivityService do
@@ -76,7 +78,7 @@ describe Users::ActivityService do
let(:last_activity_on) { nil }
it 'does not update last_activity_on' do
- stub_exclusive_lease_taken("acitvity_service:#{user.id}", timeout: 1.minute.to_i)
+ stub_exclusive_lease_taken("activity_service:#{user.id}", timeout: 1.minute.to_i)
expect { subject.execute }.not_to change(user, :last_activity_on)
end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index b7b9817efdb..aed5d2598ef 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::BuildService do
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index 24dac569678..a139dc01314 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::CreateService do
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 83f1495a1c6..4a5f4509a7b 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::DestroyService do
@@ -78,7 +80,7 @@ describe Users::DestroyService do
end
context "for an merge request the user was assigned to" do
- let!(:merge_request) { create(:merge_request, source_project: project, assignee: user) }
+ let!(:merge_request) { create(:merge_request, source_project: project, assignees: [user]) }
before do
service.execute(user)
@@ -91,7 +93,7 @@ describe Users::DestroyService do
it 'migrates the merge request so that it is "Unassigned"' do
migrated_merge_request = MergeRequest.find_by_id(merge_request.id)
- expect(migrated_merge_request.assignee).to be_nil
+ expect(migrated_merge_request.assignees).to be_empty
end
end
end
@@ -208,6 +210,8 @@ describe Users::DestroyService do
describe "calls the before/after callbacks" do
it 'of project_members' do
+ expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:find).once
+ expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:initialize).once
expect_any_instance_of(ProjectMember).to receive(:run_callbacks).with(:destroy).once
service.execute(user)
@@ -217,6 +221,8 @@ describe Users::DestroyService do
group_member = create(:group_member)
group_member.group.group_members.create(user: user, access_level: 40)
+ expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:find).once
+ expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:initialize).once
expect_any_instance_of(GroupMember).to receive(:run_callbacks).with(:destroy).once
service.execute(user)
diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb
index 2b6c0267a0f..424e9e2f8ef 100644
--- a/spec/services/users/last_push_event_service_spec.rb
+++ b/spec/services/users/last_push_event_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::LastPushEventService do
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index 68b0f79c6d1..40206775aed 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::MigrateToGhostUserService do
@@ -78,7 +80,7 @@ describe Users::MigrateToGhostUserService do
context "when record migration fails with a rollback exception" do
before do
- expect_any_instance_of(MergeRequest::ActiveRecord_Associations_CollectionProxy)
+ expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
.to receive(:update_all).and_raise(ActiveRecord::Rollback)
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 122b96ef216..0287a24808d 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::RefreshAuthorizedProjectsService do
diff --git a/spec/services/users/respond_to_terms_service_spec.rb b/spec/services/users/respond_to_terms_service_spec.rb
index fb08dd10b87..d840706e8a5 100644
--- a/spec/services/users/respond_to_terms_service_spec.rb
+++ b/spec/services/users/respond_to_terms_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::RespondToTermsService do
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index 529c8485202..9384287f98a 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Users::UpdateService do
@@ -11,6 +13,15 @@ describe Users::UpdateService do
expect(user.name).to eq('New Name')
end
+ it 'updates time preferences' do
+ result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true, time_format_in_24h: false)
+
+ expect(result).to eq(status: :success)
+ expect(user.reload.timezone).to eq('Europe/Warsaw')
+ expect(user.time_display_relative).to eq(true)
+ expect(user.time_format_in_24h).to eq(false)
+ end
+
it 'returns an error result when record cannot be updated' do
result = {}
expect do
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index d974cc0226f..f2b3b44d223 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe VerifyPagesDomainService do
@@ -9,88 +11,177 @@ describe VerifyPagesDomainService do
subject(:service) { described_class.new(domain) }
describe '#execute' do
- context 'verification code recognition (verified domain)' do
- where(:domain_sym, :code_sym) do
- :domain | :verification_code
- :domain | :keyed_verification_code
-
- :verification_domain | :verification_code
- :verification_domain | :keyed_verification_code
- end
+ where(:domain_sym, :code_sym) do
+ :domain | :verification_code
+ :domain | :keyed_verification_code
- with_them do
- set(:domain) { create(:pages_domain) }
+ :verification_domain | :verification_code
+ :verification_domain | :keyed_verification_code
+ end
- let(:domain_name) { domain.send(domain_sym) }
- let(:verification_code) { domain.send(code_sym) }
+ with_them do
+ let(:domain_name) { domain.send(domain_sym) }
+ let(:verification_code) { domain.send(code_sym) }
+ shared_examples 'verifies and enables the domain' do
it 'verifies and enables the domain' do
- stub_resolver(domain_name => ['something else', verification_code])
-
expect(service.execute).to eq(status: :success)
+
expect(domain).to be_verified
expect(domain).to be_enabled
+ expect(domain.remove_at).to be_nil
end
+ end
- it 'verifies and enables when the code is contained partway through a TXT record' do
- stub_resolver(domain_name => "something #{verification_code} else")
+ shared_examples 'successful enablement and verification' do
+ context 'when txt record contains verification code' do
+ before do
+ stub_resolver(domain_name => ['something else', verification_code])
+ end
- expect(service.execute).to eq(status: :success)
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ include_examples 'verifies and enables the domain'
end
- it 'does not verify when the code is not present' do
- stub_resolver(domain_name => 'something else')
+ context 'when txt record contains verification code with other text' do
+ before do
+ stub_resolver(domain_name => "something #{verification_code} else")
+ end
- expect(service.execute).to eq(error_status)
+ include_examples 'verifies and enables the domain'
+ end
+ end
+ shared_examples 'unverifies and disables domain' do
+ it 'unverifies domain' do
+ expect(service.execute).to eq(error_status)
expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ end
+
+ it 'disables domain and shedules it for removal in 1 week' do
+ service.execute
+
+ expect(domain).not_to be_enabled
+
+ expect(domain.remove_at).to be_like_time(7.days.from_now)
end
end
- context 'verified domain' do
- set(:domain) { create(:pages_domain) }
+ context 'when domain is disabled(or new)' do
+ let(:domain) { create(:pages_domain, :disabled) }
- it 'unverifies (but does not disable) when the right code is not present' do
- stub_resolver(domain.domain => 'something else')
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
+
+ include_examples 'unverifies and disables domain'
end
- it 'unverifies (but does not disable) when no records are present' do
- stub_resolver
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ include_examples 'unverifies and disables domain'
+ end
+
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
+
+ include_examples 'unverifies and disables domain'
end
end
- context 'expired domain' do
- set(:domain) { create(:pages_domain, :expired) }
+ context 'when domain is verified' do
+ let(:domain) { create(:pages_domain) }
- it 'verifies and enables when the right code is present' do
- stub_resolver(domain.domain => domain.keyed_verification_code)
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(status: :success)
+ shared_examples 'unverifing domain' do
+ it 'unverifies but does not disable domain' do
+ expect(service.execute).to eq(error_status)
+ expect(domain).not_to be_verified
+ expect(domain).to be_enabled
+ end
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ it 'does not schedule domain for removal' do
+ service.execute
+ expect(domain.remove_at).to be_nil
+ end
end
- it 'disables when the right code is not present' do
- error_status[:message] += '. It is now disabled.'
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- stub_resolver
+ include_examples 'unverifing domain'
+ end
- expect(service.execute).to eq(error_status)
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
- expect(domain).not_to be_verified
- expect(domain).not_to be_enabled
+ include_examples 'unverifing domain'
+ end
+ end
+
+ context 'when domain is expired' do
+ let(:domain) { create(:pages_domain, :expired) }
+
+ context 'when the right code is present' do
+ before do
+ stub_resolver(domain_name => domain.keyed_verification_code)
+ end
+
+ include_examples 'verifies and enables the domain'
+ end
+
+ context 'when the right code is not present' do
+ before do
+ stub_resolver
+ end
+
+ let(:error_status) { { status: :error, message: "Couldn't verify #{domain.domain}. It is now disabled." } }
+
+ include_examples 'unverifies and disables domain'
+ end
+ end
+
+ context 'when domain is disabled and scheduled for removal' do
+ let(:domain) { create(:pages_domain, :disabled, :scheduled_for_removal) }
+
+ context 'when the right code is present' do
+ before do
+ stub_resolver(domain.domain => domain.keyed_verification_code)
+ end
+
+ it 'verifies and enables domain' do
+ expect(service.execute).to eq(status: :success)
+
+ expect(domain).to be_verified
+ expect(domain).to be_enabled
+ end
+
+ it 'prevent domain from being removed' do
+ expect { service.execute }.to change { domain.remove_at }.to(nil)
+ end
+ end
+
+ context 'when the right code is not present' do
+ before do
+ stub_resolver
+ end
+
+ it 'keeps domain scheduled for removal but does not change removal time' do
+ expect { service.execute }.not_to change { domain.remove_at }
+ expect(domain.remove_at).to be_present
+ end
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 747e04fb18c..37bafc0c002 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WebHookService do
+ include StubRequests
+
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook) }
let(:headers) do
@@ -65,11 +69,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
it 'uses the credentials' do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
service_instance.execute
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v')
).once
end
@@ -80,11 +84,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
it 'uses the credentials anyways' do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
service_instance.execute
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzo=')
).once
end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 259f445247e..84510dcf700 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WikiPages::CreateService do
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index 2938126914b..c74eac4dad6 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WikiPages::DestroyService do
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 2399db7d3d4..19866bd3bfc 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WikiPages::UpdateService do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 97e7a019222..390a869d93f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -44,6 +44,8 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
+quality_level = Quality::TestLevel.new
+
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
@@ -53,10 +55,12 @@ RSpec.configure do |config|
config.display_try_failure_messages = true
config.infer_spec_type_from_file_location!
+ config.full_backtrace = !!ENV['CI']
- config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
+ config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location]
+ metadata[:level] = quality_level.level_for(location)
metadata[:api] = true if location =~ %r{/spec/requests/api/}
# do not overwrite type if it's already set
@@ -66,6 +70,7 @@ RSpec.configure do |config|
metadata[:type] = match[1].singularize.to_sym if match
end
+ config.include LicenseHelpers
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
config.include CycleAnalyticsHelpers
@@ -82,6 +87,7 @@ RSpec.configure do |config|
config.include Devise::Test::IntegrationHelpers, type: :feature
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
+ config.include WaitHelpers, type: :feature
config.include EmailHelpers, :mailer, type: :mailer
config.include Warden::Test::Helpers, type: :request
config.include Gitlab::Routing, type: :routing
@@ -96,6 +102,7 @@ RSpec.configure do |config|
config.include MigrationsHelpers, :migration
config.include RedisHelpers
config.include Rails.application.routes.url_helpers, type: :routing
+ config.include PolicyHelpers, type: :policy
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
@@ -115,10 +122,17 @@ RSpec.configure do |config|
TestEnv.clean_test_path
end
- config.before do
+ config.before do |example|
# Enable all features by default for testing
allow(Feature).to receive(:enabled?) { true }
+ enabled = example.metadata[:enable_rugged].present?
+
+ # Disable Rugged features by default
+ Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
+ allow(Feature).to receive(:enabled?).with(flag).and_return(enabled)
+ end
+
# The following can be removed when we remove the staged rollout strategy
# and we can just enable it using instance wide settings
# (ie. ApplicationSetting#auto_devops_enabled)
@@ -127,7 +141,7 @@ RSpec.configure do |config|
.and_return(false)
end
- config.before(:example, :quarantine) do
+ config.around(:example, :quarantine) do
# Skip tests in quarantine unless we explicitly focus on them.
skip('In quarantine') unless config.inclusion_filter[:quarantine]
end
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 5f709831ce1..63b719be03e 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -72,6 +72,15 @@ shared_examples_for 'group and project milestones' do |route_definition|
expect(json_response.first['id']).to eq closed_milestone.id
end
+ it 'returns a milestone by title' do
+ get api(route, user), params: { title: 'version2' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['title']).to eq milestone.title
+ expect(json_response.first['id']).to eq milestone.id
+ end
+
it 'returns a milestone by searching for title' do
get api(route, user), params: { search: 'version2' }
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index 6591d56e473..4cf34d43117 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,10 +1,16 @@
module SchemaPath
- def self.expand(schema, dir = '')
- Rails.root.join('spec', dir, "fixtures/api/schemas/#{schema}.json").to_s
+ def self.expand(schema, dir = nil)
+ if Gitlab.ee? && dir.nil?
+ ee_path = expand(schema, 'ee')
+
+ return ee_path if File.exist?(ee_path)
+ end
+
+ Rails.root.join(dir.to_s, 'spec', "fixtures/api/schemas/#{schema}.json").to_s
end
end
-RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options|
+RSpec::Matchers.define :match_response_schema do |schema, dir: nil, **options|
match do |response|
@errors = JSON::Validator.fully_validate(
SchemaPath.expand(schema, dir), response.body, options)
@@ -18,8 +24,16 @@ RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options|
end
end
-RSpec::Matchers.define :match_schema do |schema, dir: '', **options|
+RSpec::Matchers.define :match_schema do |schema, dir: nil, **options|
match do |data|
- JSON::Validator.validate!(SchemaPath.expand(schema, dir), data, options)
+ @errors = JSON::Validator.fully_validate(
+ SchemaPath.expand(schema, dir), data, options)
+
+ @errors.empty?
+ end
+
+ failure_message do |response|
+ "didn't match the schema defined by #{SchemaPath.expand(schema, dir)}" \
+ " The validation errors were:\n#{@errors.join("\n")}"
end
end
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index e883d33f671..15037222630 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -3,6 +3,8 @@ shared_examples 'an unauthorized API user' do
end
shared_examples 'time tracking endpoints' do |issuable_name|
+ let(:non_member) { create(:user) }
+
issuable_collection_name = issuable_name.pluralize
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 18a7a392c12..875a9a76e12 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -17,6 +17,8 @@ JS_CONSOLE_FILTER = Regexp.union([
"Download the Vue Devtools extension"
])
+CAPYBARA_WINDOW_SIZE = [1366, 768].freeze
+
Capybara.register_driver :chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
# This enables access to logs with `page.driver.manage.get_log(:browser)`
@@ -29,7 +31,7 @@ Capybara.register_driver :chrome do |app|
)
options = Selenium::WebDriver::Chrome::Options.new
- options.add_argument("window-size=1240,1400")
+ options.add_argument("window-size=#{CAPYBARA_WINDOW_SIZE.join(',')}")
# Chrome won't work properly in a Docker container in sandbox mode
options.add_argument("no-sandbox")
@@ -78,8 +80,11 @@ RSpec.configure do |config|
protocol: 'http')
# reset window size between tests
- unless session.current_window.size == [1240, 1400]
- session.current_window.resize_to(1240, 1400) rescue nil
+ unless session.current_window.size == CAPYBARA_WINDOW_SIZE
+ begin
+ session.current_window.resize_to(*CAPYBARA_WINDOW_SIZE)
+ rescue # ?
+ end
end
end
diff --git a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
index 72912ffb89d..a0c77eecb61 100644
--- a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
+++ b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
@@ -25,9 +25,13 @@ shared_context 'Ldap::OmniauthCallbacksController' do
described_class.define_providers!
Rails.application.reload_routes!
- mock_auth_hash(provider.to_s, uid, user.email)
+ @original_env_config_omniauth_auth = mock_auth_hash(provider.to_s, uid, user.email)
stub_omniauth_provider(provider, context: request)
allow(Gitlab::Auth::LDAP::Access).to receive(:allowed?).and_return(valid_login?)
end
+
+ after do
+ Rails.application.env_config['omniauth.auth'] = @original_env_config_omniauth_auth
+ end
end
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
new file mode 100644
index 00000000000..edd7de94203
--- /dev/null
+++ b/spec/support/database_cleaner.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'database_cleaner/active_record/deletion'
+require_relative 'db_cleaner'
+
+module FakeInformationSchema
+ # Work around a bug in DatabaseCleaner when using the deletion strategy:
+ # https://github.com/DatabaseCleaner/database_cleaner/issues/347
+ #
+ # On MySQL, if the information schema is said to exist, we use an inaccurate
+ # row count leading to some tables not being cleaned when they should
+ def information_schema_exists?(_connection)
+ false
+ end
+end
+
+DatabaseCleaner::ActiveRecord::Deletion.prepend(FakeInformationSchema)
+
+RSpec.configure do |config|
+ include DbCleaner
+
+ # Ensure all sequences are reset at the start of the suite run
+ config.before(:suite) do
+ setup_database_cleaner
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
+ config.append_after(:context) do
+ DatabaseCleaner.clean_with(:deletion, cache_tables: false)
+ end
+
+ config.before do
+ setup_database_cleaner
+ DatabaseCleaner.strategy = :transaction
+ end
+
+ config.before(:each, :js) do
+ DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
+ end
+
+ config.before(:each, :delete) do
+ DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
+ end
+
+ config.before(:each, :migration) do
+ DatabaseCleaner.strategy = :deletion, { cache_tables: false }
+ end
+
+ config.before do
+ DatabaseCleaner.start
+ end
+
+ config.append_after do
+ DatabaseCleaner.clean
+ end
+end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 34b9efaaecd..c69fa322073 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,49 +1,9 @@
-require 'database_cleaner/active_record/deletion'
-
-module FakeInformationSchema
- # Work around a bug in DatabaseCleaner when using the deletion strategy:
- # https://github.com/DatabaseCleaner/database_cleaner/issues/347
- #
- # On MySQL, if the information schema is said to exist, we use an inaccurate
- # row count leading to some tables not being cleaned when they should
- def information_schema_exists?(_connection)
- false
- end
-end
-
-DatabaseCleaner::ActiveRecord::Deletion.prepend(FakeInformationSchema)
-
-RSpec.configure do |config|
- # Ensure all sequences are reset at the start of the suite run
- config.before(:suite) do
- DatabaseCleaner.clean_with(:truncation)
- end
-
- config.append_after(:context) do
- DatabaseCleaner.clean_with(:deletion, cache_tables: false)
- end
-
- config.before do
- DatabaseCleaner.strategy = :transaction
- end
-
- config.before(:each, :js) do
- DatabaseCleaner.strategy = :deletion, { cache_tables: false }
- end
-
- config.before(:each, :delete) do
- DatabaseCleaner.strategy = :deletion, { cache_tables: false }
- end
-
- config.before(:each, :migration) do
- DatabaseCleaner.strategy = :deletion, { cache_tables: false }
- end
-
- config.before do
- DatabaseCleaner.start
+module DbCleaner
+ def deletion_except_tables
+ []
end
- config.append_after do
- DatabaseCleaner.clean
+ def setup_database_cleaner
+ DatabaseCleaner[:active_record, { connection: ActiveRecord::Base }]
end
end
diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb
new file mode 100644
index 00000000000..79dd9a3d58e
--- /dev/null
+++ b/spec/support/external_authorization_service_helpers.rb
@@ -0,0 +1,33 @@
+module ExternalAuthorizationServiceHelpers
+ def enable_external_authorization_service_check
+ stub_application_setting(external_authorization_service_enabled: true)
+
+ stub_application_setting(external_authorization_service_url: 'https://authorize.me')
+ stub_application_setting(external_authorization_service_default_label: 'default_label')
+ stub_request(:post, "https://authorize.me").to_return(status: 200)
+ end
+
+ def external_service_set_access(allowed, user, project)
+ enable_external_authorization_service_check
+ classification_label = ::Gitlab::CurrentSettings.current_application_settings
+ .external_authorization_service_default_label
+
+ # Reload the project so cached licensed features are reloaded
+ if project
+ classification_label = Project.find(project.id).external_authorization_classification_label
+ end
+
+ allow(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?)
+ .with(user, classification_label, any_args)
+ .and_return(allowed)
+ end
+
+ def external_service_allow_access(user, project = nil)
+ external_service_set_access(true, user, project)
+ end
+
+ def external_service_deny_access(user, project = nil)
+ external_service_set_access(false, user, project)
+ end
+end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 42a086d58d2..542f533d590 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -7,7 +7,7 @@ shared_examples 'discussion comments' do |resource_name|
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
let(:comments_selector) { '.timeline > .note.timeline-entry' }
- it 'clicking "Comment" will post a comment' do
+ it 'clicking "Comment" will post a comment', :quarantine do
expect(page).to have_selector toggle_selector
find("#{form_selector} .note-textarea").send_keys('a')
@@ -224,7 +224,7 @@ shared_examples 'discussion comments' do |resource_name|
find(toggle_selector).click
end
- it 'should have "Start discussion" selected' do
+ it 'has "Start discussion" selected' do
find("#{menu_selector} li", match: :first)
items = all("#{menu_selector} li")
@@ -267,7 +267,7 @@ shared_examples 'discussion comments' do |resource_name|
end
end
- it 'should have "Comment" selected when opening the menu' do
+ it 'has "Comment" selected when opening the menu' do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
diff --git a/spec/support/features/issuable_quick_actions_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb
deleted file mode 100644
index 2a883ce1074..00000000000
--- a/spec/support/features/issuable_quick_actions_shared_examples.rb
+++ /dev/null
@@ -1,389 +0,0 @@
-# Specifications for behavior common to all objects with executable attributes.
-# It takes a `issuable_type`, and expect an `issuable`.
-
-shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
- include Spec::Support::Helpers::Features::NotesHelpers
-
- let(:maintainer) { create(:user) }
- let(:project) do
- case issuable_type
- when :merge_request
- create(:project, :public, :repository)
- when :issue
- create(:project, :public)
- end
- end
- let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
- let!(:label_bug) { create(:label, project: project, title: 'bug') }
- let!(:label_feature) { create(:label, project: project, title: 'feature') }
- let(:new_url_opts) { {} }
-
- before do
- project.add_maintainer(maintainer)
-
- gitlab_sign_in(maintainer)
- end
-
- after do
- # Ensure all outstanding Ajax requests are complete to avoid database deadlocks
- wait_for_requests
- end
-
- describe "new #{issuable_type}", :js do
- context 'with commands in the description' do
- it "creates the #{issuable_type} and interpret commands accordingly" do
- case issuable_type
- when :merge_request
- visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
- when :issue
- visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
- end
- fill_in "#{issuable_type}_title", with: 'bug 345'
- fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
- click_button "Submit #{issuable_type}".humanize
-
- issuable = project.public_send(issuable_type.to_s.pluralize).first
-
- expect(issuable.description).to eq "bug description"
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- expect(page).to have_content 'bug 345'
- expect(page).to have_content 'bug description'
- end
- end
- end
-
- describe "note on #{issuable_type}", :js do
- before do
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- context 'with a note containing commands' do
- it 'creates a note without the commands and interpret the commands accordingly' do
- assignee = create(:user, username: 'bob')
- add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/assign @bob'
- expect(page).not_to have_content '/label ~bug'
- expect(page).not_to have_content '/milestone %"ASAP"'
-
- wait_for_requests
- issuable.reload
- note = issuable.notes.user.first
-
- expect(note.note).to eq "Awesome!"
- expect(issuable.assignees).to eq [assignee]
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- end
-
- it 'removes the quick action from note and explains it in the preview' do
- preview_note("Awesome!\n\n/close")
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/close'
- issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request'
- expect(page).to have_content "Closes this #{issuable_name}."
- end
- end
-
- context 'with a note containing only commands' do
- it 'does not create a note but interpret the commands accordingly' do
- assignee = create(:user, username: 'bob')
- add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
-
- expect(page).not_to have_content '/assign @bob'
- expect(page).not_to have_content '/label ~bug'
- expect(page).not_to have_content '/milestone %"ASAP"'
- expect(page).to have_content 'Commands applied'
-
- issuable.reload
-
- expect(issuable.notes.user).to be_empty
- expect(issuable.assignees).to eq [assignee]
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- end
- end
-
- context "with a note closing the #{issuable_type}" do
- before do
- expect(issuable).to be_open
- end
-
- context "when current user can close #{issuable_type}" do
- it "closes the #{issuable_type}" do
- add_note("/close")
-
- expect(page).not_to have_content '/close'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_closed
- end
- end
-
- context "when current user cannot close #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not close the #{issuable_type}" do
- add_note("/close")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_open
- end
- end
- end
-
- context "with a note reopening the #{issuable_type}" do
- before do
- issuable.close
- expect(issuable).to be_closed
- end
-
- context "when current user can reopen #{issuable_type}" do
- it "reopens the #{issuable_type}" do
- add_note("/reopen")
-
- expect(page).not_to have_content '/reopen'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_open
- end
- end
-
- context "when current user cannot reopen #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not reopen the #{issuable_type}" do
- add_note("/reopen")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_closed
- end
- end
- end
-
- context "with a note changing the #{issuable_type}'s title" do
- context "when current user can change title of #{issuable_type}" do
- it "reopens the #{issuable_type}" do
- add_note("/title Awesome new title")
-
- expect(page).not_to have_content '/title'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload.title).to eq 'Awesome new title'
- end
- end
-
- context "when current user cannot change title of #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not change the #{issuable_type} title" do
- add_note("/title Awesome new title")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable.reload.title).not_to eq 'Awesome new title'
- end
- end
- end
-
- context "with a note marking the #{issuable_type} as todo" do
- it "creates a new todo for the #{issuable_type}" do
- add_note("/todo")
-
- expect(page).not_to have_content '/todo'
- expect(page).to have_content 'Commands applied'
-
- todos = TodosFinder.new(maintainer).execute
- todo = todos.first
-
- expect(todos.size).to eq 1
- expect(todo).to be_pending
- expect(todo.target).to eq issuable
- expect(todo.author).to eq maintainer
- expect(todo.user).to eq maintainer
- end
- end
-
- context "with a note marking the #{issuable_type} as done" do
- before do
- TodoService.new.mark_todo(issuable, maintainer)
- end
-
- it "creates a new todo for the #{issuable_type}" do
- todos = TodosFinder.new(maintainer).execute
- todo = todos.first
-
- expect(todos.size).to eq 1
- expect(todos.first).to be_pending
- expect(todo.target).to eq issuable
- expect(todo.author).to eq maintainer
- expect(todo.user).to eq maintainer
-
- add_note("/done")
-
- expect(page).not_to have_content '/done'
- expect(page).to have_content 'Commands applied'
-
- expect(todo.reload).to be_done
- end
- end
-
- context "with a note subscribing to the #{issuable_type}" do
- it "creates a new todo for the #{issuable_type}" do
- expect(issuable.subscribed?(maintainer, project)).to be_falsy
-
- add_note("/subscribe")
-
- expect(page).not_to have_content '/subscribe'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.subscribed?(maintainer, project)).to be_truthy
- end
- end
-
- context "with a note unsubscribing to the #{issuable_type} as done" do
- before do
- issuable.subscribe(maintainer, project)
- end
-
- it "creates a new todo for the #{issuable_type}" do
- expect(issuable.subscribed?(maintainer, project)).to be_truthy
-
- add_note("/unsubscribe")
-
- expect(page).not_to have_content '/unsubscribe'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.subscribed?(maintainer, 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
- add_note("/assign me")
-
- expect(page).not_to have_content '/assign me'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload.assignees).to eq [maintainer]
- end
- end
-
- context "with a note locking the #{issuable_type} discussion" do
- before do
- issuable.update(discussion_locked: false)
- expect(issuable).not_to be_discussion_locked
- end
-
- context "when current user can lock #{issuable_type} discussion" do
- it "locks the #{issuable_type} discussion" do
- add_note("/lock")
-
- expect(page).not_to have_content '/lock'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_discussion_locked
- end
- end
-
- context "when current user cannot lock #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not lock the #{issuable_type} discussion" do
- add_note("/lock")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).not_to be_discussion_locked
- end
- end
- end
-
- context "with a note unlocking the #{issuable_type} discussion" do
- before do
- issuable.update(discussion_locked: true)
- expect(issuable).to be_discussion_locked
- end
-
- context "when current user can unlock #{issuable_type} discussion" do
- it "unlocks the #{issuable_type} discussion" do
- add_note("/unlock")
-
- expect(page).not_to have_content '/unlock'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).not_to be_discussion_locked
- end
- end
-
- context "when current user cannot unlock #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not unlock the #{issuable_type} discussion" do
- add_note("/unlock")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_discussion_locked
- end
- end
- end
- end
-
- describe "preview of note on #{issuable_type}", :js do
- it 'removes quick actions from note and explains them' do
- create(:user, username: 'bob')
-
- visit public_send("project_#{issuable_type}_path", project, issuable)
-
- page.within('.js-main-target-form') do
- fill_in 'note[note]', with: "Awesome!\n/assign @bob "
- click_on 'Preview'
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/assign @bob'
- expect(page).to have_content 'Assigns @bob.'
- end
- end
- end
-end
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 8cfce49da8a..5d5a0a7b5d2 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -20,7 +20,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- expect(dropdown).to have_link('Report abuse to GitLab', href: abuse_report_path)
+ expect(dropdown).to have_link('Report abuse to admin', href: abuse_report_path)
if type == 'issue' || type == 'merge_request'
expect(dropdown).to have_button('Delete comment')
@@ -33,7 +33,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- dropdown.click_link('Report abuse to GitLab')
+ dropdown.click_link('Report abuse to admin')
expect(find('#user_name')['value']).to match(note.author.username)
expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
@@ -41,7 +41,7 @@ shared_examples 'reportable note' do |type|
def open_dropdown(dropdown)
# make window wide enough that tooltip doesn't trigger horizontal scrollbar
- resize_window(1200, 800)
+ restore_window_size
dropdown.find('.more-actions-toggle').click
dropdown.find('.dropdown-menu li', match: :first)
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 0a464d77cb7..01531864c1f 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -8,7 +8,7 @@ shared_examples 'variable list' do
it 'adds new CI variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key value')
+ find('.js-ci-variable-input-value').set('key_value')
end
click_button('Save variables')
@@ -17,16 +17,19 @@ shared_examples 'variable list' do
visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
end
end
- it 'adds empty variable' do
+ it 'adds a new protected variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('')
+ find('.js-ci-variable-input-value').set('key_value')
+ find('.ci-variable-protected-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
end
click_button('Save variables')
@@ -35,19 +38,19 @@ shared_examples 'variable list' do
visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
+ expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
end
end
- it 'adds new protected variable' do
+ it 'defaults to unmasked' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key value')
- find('.ci-variable-protected-item .js-project-feature-toggle').click
+ find('.js-ci-variable-input-value').set('key_value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
end
click_button('Save variables')
@@ -56,10 +59,10 @@ shared_examples 'variable list' do
visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
- expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
end
end
@@ -113,19 +116,19 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section') do
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
- expect(page).to have_content('*' * 20)
+ expect(page).to have_content('*' * 17)
click_button('Reveal value')
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value').value).to eq(variable.value)
- expect(page).not_to have_content('*' * 20)
+ expect(page).not_to have_content('*' * 17)
click_button('Hide value')
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
- expect(page).to have_content('*' * 20)
+ expect(page).to have_content('*' * 17)
end
end
@@ -146,7 +149,7 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section') do
click_button('Reveal value')
- page.within('.js-row:nth-child(1)') do
+ page.within('.js-row:nth-child(2)') do
find('.js-ci-variable-input-key').set('new_key')
find('.js-ci-variable-input-value').set('new_value')
end
@@ -156,34 +159,13 @@ shared_examples 'variable list' do
visit page_path
- page.within('.js-row:nth-child(1)') do
+ page.within('.js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('new_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value')
end
end
end
- it 'edits variable with empty value' do
- page.within('.js-ci-variable-list-section') do
- click_button('Reveal value')
-
- page.within('.js-row:nth-child(1)') do
- find('.js-ci-variable-input-key').set('new_key')
- find('.js-ci-variable-input-value').set('')
- end
-
- click_button('Save variables')
- wait_for_requests
-
- visit page_path
-
- page.within('.js-row:nth-child(1)') do
- expect(find('.js-ci-variable-input-key').value).to eq('new_key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('')
- end
- end
- end
-
it 'edits variable to be protected' do
# Create the unprotected variable
page.within('.js-ci-variable-list-section .js-row:last-child') do
@@ -199,7 +181,7 @@ shared_examples 'variable list' do
visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
find('.ci-variable-protected-item .js-project-feature-toggle').click
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
@@ -211,7 +193,7 @@ shared_examples 'variable list' do
visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
@@ -233,7 +215,7 @@ shared_examples 'variable list' do
visit page_path
- page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
find('.ci-variable-protected-item .js-project-feature-toggle').click
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
@@ -244,13 +226,68 @@ shared_examples 'variable list' do
visit page_path
- page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('protected_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
end
end
+ it 'edits variable to be unmasked' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('unmasked_key')
+ find('.js-ci-variable-input-value').set('unmasked_value')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+ end
+
+ it 'edits variable to be masked' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('masked_key')
+ find('.js-ci-variable-input-value').set('masked_value')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ end
+ end
+
it 'handles multiple edits and deletion in the middle' do
page.within('.js-ci-variable-list-section') do
# Create 2 variables
@@ -269,7 +306,7 @@ shared_examples 'variable list' do
expect(page).to have_selector('.js-row', count: 4)
# Remove the `akey` variable
- page.within('.js-row:nth-child(2)') do
+ page.within('.js-row:nth-child(3)') do
first('.js-row-remove-button').click
end
@@ -297,11 +334,11 @@ shared_examples 'variable list' do
it 'shows validation error box about duplicate keys' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value1')
+ find('.js-ci-variable-input-value').set('value123')
end
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('samekey')
- find('.js-ci-variable-input-value').set('value2')
+ find('.js-ci-variable-input-value').set('value456')
end
click_button('Save variables')
@@ -314,4 +351,36 @@ shared_examples 'variable list' do
expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/)
end
end
+
+ it 'shows validation error box about masking empty values' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('empty_value')
+ find('.js-ci-variable-input-value').set('')
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ page.within('.js-ci-variable-list-section') do
+ expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
+ expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
+ end
+ end
+
+ it 'shows validation error box about unmaskable values' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('unmaskable_value')
+ find('.js-ci-variable-input-value').set('???')
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ page.within('.js-ci-variable-list-section') do
+ expect(all('.js-ci-variable-error-box ul li').count).to eq(1)
+ expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables value is invalid/)
+ end
+ end
end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index ecefdc23811..33648292037 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -23,7 +23,7 @@ module CycleAnalyticsHelpers
return if skip_push_handler
- GitPushService.new(project,
+ Git::BranchPushService.new(project,
user,
oldrev: oldrev,
newrev: commit_shas.last,
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index ad6e1064499..ed049daba80 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -16,7 +16,9 @@ module EmailHelpers
end
def should_email(user, times: 1, recipients: email_recipients)
- expect(sent_to_user(user, recipients: recipients)).to eq(times)
+ amount = sent_to_user(user, recipients: recipients)
+ failed_message = lambda { "User #{user.username} (#{user.id}): email test failed (expected #{times}, got #{amount})" }
+ expect(amount).to eq(times), failed_message
end
def should_not_email(user, recipients: email_recipients)
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 89517fde6e2..8a139fafac2 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -23,8 +23,16 @@ module Spec
def preview_note(text)
page.within('.js-main-target-form') do
- fill_in('note[note]', with: text)
+ filled_text = fill_in('note[note]', with: text)
+
+ # Wait for quick action prompt to load and then dismiss it with ESC
+ # because it may block the Preview button
+ wait_for_requests
+ filled_text.send_keys(:escape)
+
click_on('Preview')
+
+ yield if block_given?
end
end
end
diff --git a/spec/support/helpers/file_mover_helpers.rb b/spec/support/helpers/file_mover_helpers.rb
new file mode 100644
index 00000000000..1ba7cc03354
--- /dev/null
+++ b/spec/support/helpers/file_mover_helpers.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module FileMoverHelpers
+ def stub_file_mover(file_path, stub_real_path: nil)
+ file_name = File.basename(file_path)
+ allow(Pathname).to receive(:new).and_call_original
+
+ expect_next_instance_of(Pathname, a_string_including(file_name)) do |pathname|
+ allow(pathname).to receive(:realpath) { stub_real_path || pathname.cleanpath }
+ end
+ end
+end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 6569feec39b..34ef185ea27 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -78,20 +78,17 @@ module FilteredSearchHelpers
# .tokens-container to make sure the correct names and values are rendered
def expect_tokens(tokens)
page.within '.filtered-search-box .tokens-container' do
- page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index|
- token_name = tokens[index][:name]
- token_value = tokens[index][:value]
- token_emoji = tokens[index][:emoji_name]
+ token_elements = page.all(:css, 'li.filtered-search-token')
- expect(el.find('.name')).to have_content(token_name)
+ tokens.each_with_index do |token, index|
+ el = token_elements[index]
- if token_value
- expect(el.find('.value')).to have_content(token_value)
- end
+ expect(el.find('.name')).to have_content(token[:name])
+ expect(el.find('.value')).to have_content(token[:value]) if token[:value].present?
# gl-emoji content is blank when the emoji unicode is not supported
- if token_emoji
- selector = %(gl-emoji[data-name="#{token_emoji}"])
+ if token[:emoji_name].present?
+ selector = %(gl-emoji[data-name="#{token[:emoji_name]}"])
expect(el.find('.value')).to have_css(selector)
end
end
@@ -149,4 +146,10 @@ module FilteredSearchHelpers
loop until find('.filtered-search').value.strip == text
end
end
+
+ def close_dropdown_menu_if_visible
+ find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
+ toggle.click if toggle.visible?
+ end
+ end
end
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
index 99a7c39852e..99c5871ba54 100644
--- a/spec/support/helpers/git_helpers.rb
+++ b/spec/support/helpers/git_helpers.rb
@@ -6,12 +6,4 @@ module GitHelpers
Rugged::Repository.new(path)
end
-
- def project_hook_exists?(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project_path = project.repository.raw_repository.path
-
- File.exist?(File.join(project_path, 'hooks', 'post-receive'))
- end
- end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index e468ee4676d..e95c7f2a6d6 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -18,12 +18,10 @@ module GraphqlHelpers
# Runs a block inside a BatchLoader::Executor wrapper
def batch(max_queries: nil, &blk)
wrapper = proc do
- begin
- BatchLoader::Executor.ensure_current
- yield
- ensure
- BatchLoader::Executor.clear_current
- end
+ BatchLoader::Executor.ensure_current
+ yield
+ ensure
+ BatchLoader::Executor.clear_current
end
if max_queries
@@ -63,7 +61,14 @@ module GraphqlHelpers
def variables_for_mutation(name, input)
graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h
- { input_variable_name_for_mutation(name) => graphql_input }.to_json
+ result = { input_variable_name_for_mutation(name) => graphql_input }
+
+ # Avoid trying to serialize multipart data into JSON
+ if graphql_input.values.none? { |value| io_value?(value) }
+ result.to_json
+ else
+ result
+ end
end
def input_variable_name_for_mutation(mutation_name)
@@ -77,14 +82,28 @@ module GraphqlHelpers
def query_graphql_field(name, attributes = {}, fields = nil)
fields ||= all_graphql_fields_for(name.classify)
attributes = attributes_to_graphql(attributes)
+ attributes = "(#{attributes})" if attributes.present?
<<~QUERY
- #{name}(#{attributes}) {
- #{fields}
- }
+ #{name}#{attributes}
+ #{wrap_fields(fields)}
QUERY
end
+ def wrap_fields(fields)
+ fields = Array.wrap(fields).join("\n")
+ return unless fields.present?
+
+ <<~FIELDS
+ {
+ #{fields}
+ }
+ FIELDS
+ end
+
def all_graphql_fields_for(class_name, parent_types = Set.new)
+ allow_unlimited_graphql_complexity
+ allow_unlimited_graphql_depth
+
type = GitlabSchema.types[class_name.to_s]
return "" unless type
@@ -115,8 +134,12 @@ module GraphqlHelpers
end.join(", ")
end
- def post_graphql(query, current_user: nil, variables: nil)
- post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }
+ def post_multiplex(queries, current_user: nil, headers: {})
+ post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers
+ end
+
+ def post_graphql(query, current_user: nil, variables: nil, headers: {})
+ post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }, headers: headers
end
def post_graphql_mutation(mutation, current_user: nil)
@@ -128,7 +151,14 @@ module GraphqlHelpers
end
def graphql_errors
- json_response['errors']
+ case json_response
+ when Hash # regular query
+ json_response['errors']
+ when Array # multiplexed queries
+ json_response.map { |response| response['errors'] }
+ else
+ raise "Unkown GraphQL response type #{json_response.class}"
+ end
end
def graphql_mutation_response(mutation_name)
@@ -151,6 +181,10 @@ module GraphqlHelpers
field.arguments.values.any? { |argument| argument.type.non_null? }
end
+ def io_value?(value)
+ Array.wrap(value).any? { |v| v.respond_to?(:to_io) }
+ end
+
def field_type(field)
field_type = field.type
@@ -162,4 +196,19 @@ module GraphqlHelpers
field_type
end
+
+ # for most tests, we want to allow unlimited complexity
+ def allow_unlimited_graphql_complexity
+ allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil
+ allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil
+ end
+
+ def allow_unlimited_graphql_depth
+ allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil
+ allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil
+ end
end
+
+# This warms our schema, doing this as part of loading the helpers to avoid
+# duplicate loading error when Rails tries autoload the types.
+GitlabSchema.graphql_definition
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 89c5ec7a718..cdd7724cc13 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -2,47 +2,56 @@ require 'action_dispatch/testing/test_request'
require 'fileutils'
module JavaScriptFixturesHelpers
+ extend ActiveSupport::Concern
include Gitlab::Popen
- FIXTURE_PATH = 'spec/javascripts/fixtures'.freeze
+ extend self
- def self.included(base)
+ included do |base|
base.around do |example|
# pick an arbitrary date from the past, so tests are not time dependent
Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run }
+
+ raise NoMethodError.new('You need to set `response` for the fixture generator! This will automatically happen with `type: :controller` or `type: :request`.', 'response') unless respond_to?(:response)
+
+ store_frontend_fixture(response, example.description)
end
end
+ def fixture_root_path
+ (Gitlab.ee? ? 'ee/' : '') + 'spec/javascripts/fixtures'
+ end
+
# Public: Removes all fixture files from given directory
#
- # directory_name - directory of the fixtures (relative to FIXTURE_PATH)
+ # directory_name - directory of the fixtures (relative to .fixture_root_path)
#
def clean_frontend_fixtures(directory_name)
- directory_name = File.expand_path(directory_name, FIXTURE_PATH)
- Dir[File.expand_path('*.html.raw', directory_name)].each do |file_name|
+ full_directory_name = File.expand_path(directory_name, fixture_root_path)
+ Dir[File.expand_path('*.html', full_directory_name)].each do |file_name|
FileUtils.rm(file_name)
end
end
- # Public: Store a response object as fixture file
+ def remove_repository(project)
+ Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
+ end
+
+ private
+
+ # Private: Store a response object as fixture file
#
# response - string or response object to store
- # fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATH)
+ # fixture_file_name - file name to store the fixture in (relative to .fixture_root_path)
#
def store_frontend_fixture(response, fixture_file_name)
- fixture_file_name = File.expand_path(fixture_file_name, FIXTURE_PATH)
+ full_fixture_path = File.expand_path(fixture_file_name, fixture_root_path)
fixture = response.respond_to?(:body) ? parse_response(response) : response
- FileUtils.mkdir_p(File.dirname(fixture_file_name))
- File.write(fixture_file_name, fixture)
- end
-
- def remove_repository(project)
- Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
+ FileUtils.mkdir_p(File.dirname(full_fixture_path))
+ File.write(full_fixture_path, fixture)
end
- private
-
# Private: Prepare a response object for use as a frontend fixture
#
# response - response object to prepare
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 9dc89b483b2..011c4df0fe5 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -9,47 +9,86 @@ module KubernetesHelpers
kube_response(kube_pods_body)
end
+ def kube_logs_response
+ kube_response(kube_logs_body)
+ end
+
def kube_deployments_response
kube_response(kube_deployments_body)
end
- def stub_kubeclient_discover(api_url)
+ def stub_kubeclient_discover_base(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1').to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1').to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
+ WebMock
+ .stub_request(:get, api_url + '/apis/extensions/v1beta1')
+ .to_return(kube_response(kube_v1beta1_discovery_body))
+ WebMock
+ .stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1')
+ .to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
+ end
+
+ def stub_kubeclient_discover(api_url)
+ stub_kubeclient_discover_base(api_url)
+
+ WebMock
+ .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
+ .to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
+ end
+
+ def stub_kubeclient_discover_knative_not_found(api_url)
+ stub_kubeclient_discover_base(api_url)
+
+ WebMock
+ .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
+ .to_return(status: [404, "Resource Not Found"])
end
- def stub_kubeclient_service_pods(response = nil)
+ def stub_kubeclient_service_pods(response = nil, options = {})
stub_kubeclient_discover(service.api_url)
- pods_url = service.api_url + "/api/v1/pods"
+
+ namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
+
+ pods_url = service.api_url + "/api/v1/#{namespace_path}pods"
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
- def stub_kubeclient_pods(response = nil)
+ def stub_kubeclient_pods(namespace, status: nil)
stub_kubeclient_discover(service.api_url)
- pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods"
+ pods_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods"
+ response = { status: status } if status
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
- def stub_kubeclient_deployments(response = nil)
+ def stub_kubeclient_logs(pod_name, namespace, status: nil)
stub_kubeclient_discover(service.api_url)
- deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{service.actual_namespace}/deployments"
+ logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
+ response = { status: status } if status
+
+ WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response)
+ end
+
+ def stub_kubeclient_deployments(namespace, status: nil)
+ stub_kubeclient_discover(service.api_url)
+ deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/deployments"
+ response = { status: status } if status
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_knative_services(**options)
+ def stub_kubeclient_knative_services(options = {})
+ namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
+
options[:name] ||= "kubetest"
- options[:namespace] ||= "default"
options[:domain] ||= "example.com"
+ options[:response] ||= kube_response(kube_knative_services_body(options))
stub_kubeclient_discover(service.api_url)
- knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/services"
- WebMock.stub_request(:get, knative_url).to_return(kube_response(kube_knative_services_body(options)))
+ knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/#{namespace_path}services"
+
+ WebMock.stub_request(:get, knative_url).to_return(options[:response])
end
def stub_kubeclient_get_secret(api_url, **options)
@@ -212,6 +251,10 @@ module KubernetesHelpers
}
end
+ def kube_logs_body
+ "Log 1\nLog 2\nLog 3"
+ end
+
def kube_deployments_body
{
"kind" => "DeploymentList",
@@ -235,16 +278,20 @@ module KubernetesHelpers
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
- def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
+ def kube_pod(name: "kube-pod", environment_slug: "production", namespace: "project-namespace", project_slug: "project-path-slug", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
+ "namespace" => namespace,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
- }
+ }.compact
},
"spec" => {
"containers" => [
@@ -278,13 +325,16 @@ module KubernetesHelpers
}
end
- def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
+ def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
}.compact
},
@@ -348,12 +398,13 @@ module KubernetesHelpers
def kube_terminals(service, pod)
pod_name = pod['metadata']['name']
+ pod_namespace = pod['metadata']['namespace']
containers = pod['spec']['containers']
containers.map do |container|
terminal = {
selectors: { pod: pod_name, container: container['name'] },
- url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']),
+ url: container_exec_url(service.api_url, pod_namespace, pod_name, container['name']),
subprotocols: ['channel.k8s.io'],
headers: { 'Authorization' => ["Bearer #{service.token}"] },
created_at: DateTime.parse(pod['metadata']['creationTimestamp']),
diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb
new file mode 100644
index 00000000000..2857416ad95
--- /dev/null
+++ b/spec/support/helpers/lets_encrypt_helpers.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module LetsEncryptHelpers
+ ACME_ORDER_METHODS = {
+ url: 'https://example.com/',
+ status: 'valid',
+ expires: 2.days.from_now
+ }.freeze
+
+ ACME_CHALLENGE_METHODS = {
+ status: 'pending',
+ token: 'tokenvalue',
+ file_content: 'hereisfilecontent',
+ request_validation: true
+ }.freeze
+
+ def stub_lets_encrypt_settings
+ stub_application_setting(
+ lets_encrypt_notification_email: 'myemail@test.example.com',
+ lets_encrypt_terms_of_service_accepted: true
+ )
+ end
+
+ def stub_lets_encrypt_client
+ client = instance_double('Acme::Client')
+
+ allow(client).to receive(:new_account)
+ allow(client).to receive(:terms_of_service).and_return(
+ "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
+ )
+
+ allow(Acme::Client).to receive(:new).with(
+ private_key: kind_of(OpenSSL::PKey::RSA),
+ directory: ::Gitlab::LetsEncrypt::Client::STAGING_DIRECTORY_URL
+ ).and_return(client)
+
+ client
+ end
+
+ def acme_challenge_double
+ challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
+ allow(challenge).to receive_messages(ACME_CHALLENGE_METHODS)
+ challenge
+ end
+
+ def acme_authorization_double
+ authorization = instance_double('Acme::Client::Resources::Authorization')
+ allow(authorization).to receive(:http).and_return(acme_challenge_double)
+ authorization
+ end
+
+ def acme_order_double(attributes = {})
+ acme_order = instance_double('Acme::Client::Resources::Order')
+ allow(acme_order).to receive_messages(ACME_ORDER_METHODS.merge(attributes))
+ allow(acme_order).to receive(:authorizations).and_return([acme_authorization_double])
+ allow(acme_order).to receive(:finalize)
+ acme_order
+ end
+end
diff --git a/spec/support/helpers/license_helper.rb b/spec/support/helpers/license_helper.rb
new file mode 100644
index 00000000000..4aaad55a8ef
--- /dev/null
+++ b/spec/support/helpers/license_helper.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Placeholder module for EE implementation needed for CE specs to be run in EE codebase
+module LicenseHelpers
+ def stub_licensed_features(features)
+ # do nothing
+ end
+end
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 3fee6872498..0bb2d2510c2 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -47,7 +47,7 @@ module LoginHelpers
end
def gitlab_sign_in_via(provider, user, uid, saml_response = nil)
- mock_auth_hash(provider, uid, user.email, saml_response)
+ mock_auth_hash_with_saml_xml(provider, uid, user.email, saml_response)
visit new_user_session_path
click_link provider
end
@@ -87,7 +87,12 @@ module LoginHelpers
click_link "oauth-login-#{provider}"
end
- def mock_auth_hash(provider, uid, email, saml_response = nil)
+ def mock_auth_hash_with_saml_xml(provider, uid, email, saml_response)
+ response_object = { document: saml_xml(saml_response) }
+ mock_auth_hash(provider, uid, email, response_object: response_object)
+ end
+
+ def mock_auth_hash(provider, uid, email, response_object: nil)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({
@@ -110,12 +115,13 @@ module LoginHelpers
image: 'mock_user_thumbnail_url'
}
},
- response_object: {
- document: saml_xml(saml_response)
- }
+ response_object: response_object
}
})
+ original_env_config_omniauth_auth = Rails.application.env_config['omniauth.auth']
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
+
+ original_env_config_omniauth_auth
end
def saml_xml(raw_saml_response)
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
new file mode 100644
index 00000000000..1f36b0e217c
--- /dev/null
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module MetricsDashboardHelpers
+ def project_with_dashboard(dashboard_path, dashboard_yml = nil)
+ dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
+
+ create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
+ end
+
+ def delete_project_dashboard(project, user, dashboard_path)
+ project.repository.delete_file(
+ user,
+ dashboard_path,
+ branch_name: 'master',
+ message: 'Delete dashboard'
+ )
+
+ project.repository.refresh_method_caches([:metrics_dashboard])
+ end
+
+ shared_examples_for 'misconfigured dashboard service response' do |status_code|
+ it 'returns an appropriate message and status code' do
+ result = service_call
+
+ expect(result.keys).to contain_exactly(:message, :http_status, :status)
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(status_code)
+ end
+ end
+
+ shared_examples_for 'valid dashboard service response' do
+ let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) }
+
+ it 'returns a json representation of the dashboard' do
+ result = service_call
+
+ expect(result.keys).to contain_exactly(:dashboard, :status)
+ expect(result[:status]).to eq(:success)
+
+ expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty
+ end
+ end
+end
diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb
index 9dc1f1de436..4230d315d9b 100644
--- a/spec/support/helpers/mobile_helpers.rb
+++ b/spec/support/helpers/mobile_helpers.rb
@@ -8,7 +8,7 @@ module MobileHelpers
end
def restore_window_size
- resize_window(1366, 768)
+ resize_window(*CAPYBARA_WINDOW_SIZE)
end
def resize_window(width, height)
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index 8d84510fb73..44c2051598c 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -17,11 +17,15 @@ module NotificationHelpers
def create_user_with_notification(level, username, resource = project)
user = create(:user, username: username)
+ create_notification_setting(user, resource, level)
+
+ user
+ end
+
+ def create_notification_setting(user, resource, level)
setting = user.notification_settings_for(resource)
setting.level = level
setting.save
-
- user
end
# Create custom notifications
diff --git a/spec/support/helpers/policy_helpers.rb b/spec/support/helpers/policy_helpers.rb
new file mode 100644
index 00000000000..3d780eb5fb1
--- /dev/null
+++ b/spec/support/helpers/policy_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module PolicyHelpers
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
+end
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index 9a86560da2a..bcb11a09b36 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -1,5 +1,11 @@
module ProjectForksHelper
def fork_project(project, user = nil, params = {})
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ fork_project_direct(project, user, params)
+ end
+ end
+
+ def fork_project_direct(project, user = nil, params = {})
# Load the `fork_network` for the project to fork as there might be one that
# wasn't loaded yet.
project.reload unless project.fork_network
@@ -44,11 +50,16 @@ module ProjectForksHelper
end
def fork_project_with_submodules(project, user = nil, params = {})
- forked_project = fork_project(project, user, params)
- TestEnv.copy_repo(forked_project,
- bare_repo: TestEnv.forked_repo_path_bare,
- refs: TestEnv::FORKED_BRANCH_SHA)
- forked_project.repository.after_import
- forked_project
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ forked_project = fork_project_direct(project, user, params)
+ TestEnv.copy_repo(
+ forked_project,
+ bare_repo: TestEnv.forked_repo_path_bare,
+ refs: TestEnv::FORKED_BRANCH_SHA
+ )
+ forked_project.repository.after_import
+
+ forked_project
+ end
end
end
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index ce1f9fce10d..87f825152cf 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -7,6 +7,10 @@ module PrometheusHelpers
%{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100}
end
+ def prometheus_istio_query(function_name, kube_namespace)
+ %{floor(sum(rate(istio_revision_request_count{destination_configuration=\"#{function_name}\", destination_namespace=\"#{kube_namespace}\"}[1m])*30))}
+ end
+
def prometheus_ping_url(prometheus_query)
query = { query: prometheus_query }.to_query
@@ -25,12 +29,16 @@ module PrometheusHelpers
"https://prometheus.example.com/api/v1/query?#{query}"
end
- def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now.to_f)
+ def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now, step: nil)
+ start = start.to_f
+ stop = stop.to_f
+ step ||= Gitlab::PrometheusClient.compute_step(start, stop)
+
query = {
query: prometheus_query,
- start: start.to_f,
+ start: start,
end: stop,
- step: 1.minute.to_i
+ step: step
}.to_query
"https://prometheus.example.com/api/v1/query_range?#{query}"
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index 7ce63375d34..c4ae62b25e4 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def callback(name, start, finish, message_id, values)
show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG']
- if values[:name]&.include?("CACHE") && skip_cached
+ if values[:cached] && skip_cached
@cached << values[:sql]
elsif !values[:name]&.include?("SCHEMA")
@log << values[:sql]
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 3c6956cf5e0..44d95a029af 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -11,6 +11,8 @@ module RepoHelpers
# blob.path # => 'files/js/commit.js.coffee'
# blob.data # => 'class Commit...'
#
+ # Build the options hash that's passed to Rugged::Commit#create
+
def sample_blob
OpenStruct.new(
oid: '5f53439ca4b009096571d3c8bc3d09d30e7431b3',
@@ -115,4 +117,94 @@ eos
commits: commits
)
end
+
+ def create_file_in_repo(
+ project, start_branch, branch_name, filename, content,
+ commit_message: 'Add new content')
+ Files::CreateService.new(
+ project,
+ project.owner,
+ commit_message: commit_message,
+ start_branch: start_branch,
+ branch_name: branch_name,
+ file_path: filename,
+ file_content: content
+ ).execute
+ end
+
+ def commit_options(repo, index, target, ref, message)
+ options = {}
+ options[:tree] = index.write_tree(repo)
+ options[:author] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:committer] = {
+ email: "test@example.com",
+ name: "Test Author",
+ time: Time.gm(2014, "mar", 3, 20, 15, 1)
+ }
+ options[:message] ||= message
+ options[:parents] = repo.empty? ? [] : [target].compact
+ options[:update_ref] = ref
+
+ options
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
+ # contents of CHANGELOG with a single new line of text.
+ def new_commit_edit_old_file(repo)
+ oid = repo.write("I replaced the changelog with this text", :blob)
+ index = repo.index
+ index.read_tree(repo.head.target.tree)
+ index.add(path: "CHANGELOG", oid: oid, mode: 0100644)
+
+ options = commit_options(
+ repo,
+ index,
+ repo.head.target,
+ "HEAD",
+ "Edit CHANGELOG in its original location"
+ )
+
+ sha = Rugged::Commit.create(repo, options)
+ repo.lookup(sha)
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
+ # contents of the specified file_path with new text.
+ def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head)
+ oid = repo.write(text, :blob)
+ index = repo.index
+ index.read_tree(branch.target.tree)
+ index.add(path: file_path, oid: oid, mode: 0100644)
+ options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message)
+ sha = Rugged::Commit.create(repo, options)
+ repo.lookup(sha)
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
+ # contents of encoding/CHANGELOG with new text.
+ def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text)
+ branch = repo.branches[branch_name]
+ new_commit_edit_new_file(repo, file_path, commit_message, text, branch)
+ end
+
+ # Writes a new commit to the repo and returns a Rugged::Commit. Moves the
+ # CHANGELOG file to the encoding/ directory.
+ def new_commit_move_file(repo)
+ blob_oid = repo.head.target.tree.detect { |i| i[:name] == "CHANGELOG" }[:oid]
+ file_content = repo.lookup(blob_oid).content
+ oid = repo.write(file_content, :blob)
+ index = repo.index
+ index.read_tree(repo.head.target.tree)
+ index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
+ index.remove("CHANGELOG")
+
+ options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/")
+
+ sha = Rugged::Commit.create(repo, options)
+ repo.lookup(sha)
+ end
end
diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb
index f4f0415985c..87672c8896d 100644
--- a/spec/support/helpers/select2_helper.rb
+++ b/spec/support/helpers/select2_helper.rb
@@ -35,6 +35,10 @@ module Select2Helper
execute_script("$('#{selector}').select2('open');")
end
+ def close_select2(selector)
+ execute_script("$('#{selector}').select2('close');")
+ end
+
def scroll_select2_to_bottom(selector)
evaluate_script "$('#{selector}').scrollTop($('#{selector}')[0].scrollHeight); $('#{selector}');"
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index ff21bbe28ca..f6c613ad5aa 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -2,7 +2,8 @@ require 'active_support/core_ext/hash/transform_values'
require 'active_support/hash_with_indifferent_access'
require 'active_support/dependencies'
-require_dependency 'gitlab'
+# check gets rid of already initialized constant warnings when using spring
+require_dependency 'gitlab' unless defined?(Gitlab)
module StubConfiguration
def stub_application_setting(messages)
@@ -84,6 +85,10 @@ module StubConfiguration
allow(Gitlab.config.kerberos).to receive_messages(to_settings(messages))
end
+ def stub_gitlab_shell_setting(messages)
+ allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
@@ -116,3 +121,6 @@ module StubConfiguration
end
end
end
+
+require_relative '../../../ee/spec/support/helpers/ee/stub_configuration' if
+ Dir.exist?("#{__dir__}/../../../ee")
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index e0c50e533a6..d31f9908714 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -23,15 +23,13 @@ module StubObjectStorage
Fog.mock!
::Fog::Storage.new(connection_params).tap do |connection|
- begin
- connection.directories.create(key: remote_directory)
+ connection.directories.create(key: remote_directory)
- # Cleanup remaining files
- connection.directories.each do |directory|
- directory.files.map(&:destroy)
- end
- rescue Excon::Error::Conflict
+ # Cleanup remaining files
+ connection.directories.each do |directory|
+ directory.files.map(&:destroy)
end
+ rescue Excon::Error::Conflict
end
end
@@ -75,3 +73,6 @@ module StubObjectStorage
EOS
end
end
+
+require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage' if
+ Dir.exist?("#{__dir__}/../../../ee")
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
new file mode 100644
index 00000000000..5cad35282c0
--- /dev/null
+++ b/spec/support/helpers/stub_requests.rb
@@ -0,0 +1,40 @@
+module StubRequests
+ IP_ADDRESS_STUB = '8.8.8.9'.freeze
+
+ # Fully stubs a request using WebMock class. This class also
+ # stubs the IP address the URL is translated to (DNS lookup).
+ #
+ # It expects the final request to go to the `ip_address` instead the given url.
+ # That's primarily a DNS rebind attack prevention of Gitlab::HTTP
+ # (see: Gitlab::UrlBlocker).
+ #
+ def stub_full_request(url, ip_address: IP_ADDRESS_STUB, port: 80, method: :get)
+ stub_dns(url, ip_address: ip_address, port: port)
+
+ url = stubbed_hostname(url, hostname: ip_address)
+ WebMock.stub_request(method, url)
+ end
+
+ def stub_dns(url, ip_address:, port: 80)
+ url = parse_url(url)
+ socket = Socket.sockaddr_in(port, ip_address)
+ addr = Addrinfo.new(socket)
+
+ # See Gitlab::UrlBlocker
+ allow(Addrinfo).to receive(:getaddrinfo)
+ .with(url.hostname, url.port, nil, :STREAM)
+ .and_return([addr])
+ end
+
+ def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
+ url = parse_url(url)
+ url.hostname = hostname
+ url.to_s
+ end
+
+ private
+
+ def parse_url(url)
+ url.is_a?(URI) ? url : URI(url)
+ end
+end
diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb
new file mode 100644
index 00000000000..58b7ee93dff
--- /dev/null
+++ b/spec/support/helpers/stub_worker.rb
@@ -0,0 +1,9 @@
+# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
+module StubWorker
+ def stub_worker(queue:)
+ Class.new do
+ include Sidekiq::Worker
+ sidekiq_options queue: queue
+ end
+ end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 06bcf4f8013..06b5ecdf150 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -135,7 +135,7 @@ module TestEnv
def clean_gitlab_test_path
Dir[TMP_TEST_PATH].each do |entry|
- if File.basename(entry) =~ /\A(gitlab-(test|test_bare|test-fork|test-fork_bare))\z/
+ unless test_dirs.include?(File.basename(entry))
FileUtils.rm_rf(entry)
end
end
@@ -147,12 +147,15 @@ module TestEnv
version: Gitlab::Shell.version_required,
task: 'gitlab:shell:install')
- create_fake_git_hooks
+ # gitlab-shell hooks don't work in our test environment because they try to make internal API calls
+ sabotage_gitlab_shell_hooks
end
- def create_fake_git_hooks
- # gitlab-shell hooks don't work in our test environment because they try to make internal API calls
- hooks_dir = File.join(Gitlab.config.gitlab_shell.path, 'hooks')
+ def sabotage_gitlab_shell_hooks
+ create_fake_git_hooks(Gitlab::Shell.new.hooks_path)
+ end
+
+ def create_fake_git_hooks(hooks_dir)
%w[pre-receive post-receive update].each do |hook|
File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' }
end
@@ -169,6 +172,7 @@ module TestEnv
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
+ create_fake_git_hooks(File.join(gitaly_dir, 'ruby/git-hooks'))
start_gitaly(gitaly_dir)
end
end
@@ -198,12 +202,10 @@ module TestEnv
socket = Gitlab::GitalyClient.address('default').sub('unix:', '')
Integer(sleep_time / sleep_interval).times do
- begin
- Socket.unix(socket)
- return
- rescue
- sleep sleep_interval
- end
+ Socket.unix(socket)
+ return
+ rescue
+ sleep sleep_interval
end
raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds"
@@ -310,6 +312,18 @@ module TestEnv
private
+ # These are directories that should be preserved at cleanup time
+ def test_dirs
+ @test_dirs ||= %w[
+ gitaly
+ gitlab-shell
+ gitlab-test
+ gitlab-test_bare
+ gitlab-test-fork
+ gitlab-test-fork_bare
+ ]
+ end
+
def factory_repo_path
@factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
end
diff --git a/spec/support/helpers/test_request_helpers.rb b/spec/support/helpers/test_request_helpers.rb
index 5a84d67bdfc..39e5dafb059 100644
--- a/spec/support/helpers/test_request_helpers.rb
+++ b/spec/support/helpers/test_request_helpers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module TestRequestHelpers
- def test_request(remote_ip: '127.0.0.1')
- ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new)
+ def test_request(remote_ip: '127.0.0.1', controller: nil)
+ ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new, controller)
end
end
diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb
index c7f878b7371..45b9faa0fea 100644
--- a/spec/support/helpers/wait_for_requests.rb
+++ b/spec/support/helpers/wait_for_requests.rb
@@ -50,20 +50,6 @@ module WaitForRequests
finished_all_vue_resource_requests?
end
- # Waits until the passed block returns true
- def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01)
- wait_until = Time.now + max_wait_time.seconds
- loop do
- break if yield
-
- if Time.now > wait_until
- raise "Condition not met: #{condition_name}"
- else
- sleep(polling_interval)
- end
- end
- end
-
def finished_all_vue_resource_requests?
Capybara.page.evaluate_script('window.activeVueResources || 0').zero?
end
diff --git a/spec/support/helpers/wait_helpers.rb b/spec/support/helpers/wait_helpers.rb
new file mode 100644
index 00000000000..7e8e25798e8
--- /dev/null
+++ b/spec/support/helpers/wait_helpers.rb
@@ -0,0 +1,20 @@
+module WaitHelpers
+ extend self
+
+ # Waits until the passed block returns true
+ def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01, reload: false)
+ wait_until = Time.now + max_wait_time.seconds
+ loop do
+ result = yield
+ break if result
+
+ page.refresh if reload
+
+ if Time.now > wait_until
+ raise "Condition not met: #{condition_name}"
+ else
+ sleep(polling_interval)
+ end
+ end
+ end
+end
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index ac320934f5a..388b88f0331 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -91,7 +91,7 @@ module ExportFileHelper
loop do
object_with_parent = deep_find_with_parent(sensitive_word, project_hash)
- return nil unless object_with_parent && object_with_parent.object
+ return unless object_with_parent && object_with_parent.object
if is_safe_hash?(object_with_parent.parent, sensitive_word)
# It's in the safe list, remove hash and keep looking
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index 3e4ca8b7ab0..e6899e2d23c 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -7,29 +7,28 @@ module AccessMatchers
extend RSpec::Matchers::DSL
include Warden::Test::Helpers
- def emulate_user(user, membership = nil)
- case user
- when :user
- login_as(create(:user))
+ def emulate_user(user_type_or_trait, membership = nil)
+ case user_type_or_trait
+ when :user, :admin
+ login_as(create(user_type_or_trait))
+ when :external, :auditor
+ login_as(create(:user, user_type_or_trait))
when :visitor
logout
- when :admin
- login_as(create(:admin))
- when :external
- login_as(create(:user, external: true))
when User
- login_as(user)
+ login_as(user_type_or_trait)
when *Gitlab::Access.sym_options_with_owner.keys
- raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership
-
- role = user
+ raise ArgumentError, "cannot emulate #{user_type_or_trait} without membership parent" unless membership
- if role == :owner && membership.owner
- user = membership.owner
- else
- user = create(:user)
- membership.public_send(:"add_#{role}", user)
- end
+ role = user_type_or_trait
+ user =
+ if role == :owner && membership.owner
+ membership.owner
+ else
+ create(:user).tap do |new_user|
+ membership.public_send(:"add_#{role}", new_user)
+ end
+ end
login_as(user)
else
diff --git a/spec/support/matchers/eq_pem.rb b/spec/support/matchers/eq_pem.rb
new file mode 100644
index 00000000000..158281e4a19
--- /dev/null
+++ b/spec/support/matchers/eq_pem.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :eq_pem do |expected_pem_string|
+ match do |actual|
+ actual.to_pem == expected_pem_string
+ end
+
+ description do
+ "contain pem #{expected_pem_string}"
+ end
+end
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7be84838e00..7894484f590 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -1,8 +1,6 @@
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
- field_definition = field.metadata[:type_class]
- expect(field_definition).to respond_to(:required_permissions)
- expect(field_definition.required_permissions).to contain_exactly(*expected)
+ expect(field.metadata[:authorize]).to eq(*expected)
end
end
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
index f5d9a97051a..62f510b0fbd 100644
--- a/spec/support/matchers/issuable_matchers.rb
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -1,4 +1,4 @@
-RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".wiki"|
+RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}")
diff --git a/spec/support/matchers/not_changed_matcher.rb b/spec/support/matchers/not_changed_matcher.rb
new file mode 100644
index 00000000000..8ef4694982d
--- /dev/null
+++ b/spec/support/matchers/not_changed_matcher.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define_negated_matcher :not_change, :change
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
index 0fd67531c3b..8044b061ca5 100644
--- a/spec/support/prometheus/additional_metrics_shared_examples.rb
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -46,7 +46,7 @@ RSpec.shared_examples 'additional metrics query' do
describe 'project has Kubernetes service' do
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
- let(:kube_namespace) { project.deployment_platform.actual_namespace }
+ let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) }
it_behaves_like 'query context containing environment slug and filter'
diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb
new file mode 100644
index 00000000000..ede16d1c1e2
--- /dev/null
+++ b/spec/support/protected_branch_helpers.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module ProtectedBranchHelpers
+ def set_allowed_to(operation, option = 'Maintainers', form: '.js-new-protected-branch')
+ within form do
+ select_elem = find(".js-allowed-to-#{operation}")
+ select_elem.click
+
+ wait_for_requests
+
+ within('.dropdown-content') do
+ Array(option).each { |opt| click_on(opt) }
+ end
+
+ # Enhanced select is used in EE, therefore an extra click is needed.
+ select_elem.click if select_elem['aria-expanded'] == 'true'
+ end
+ end
+
+ def set_protected_branch_name(branch_name)
+ find('.js-protected-branch-select').click
+ find('.dropdown-input-field').set(branch_name)
+ click_on("Create wildcard #{branch_name}")
+ end
+
+ def set_defaults
+ set_allowed_to('merge')
+ set_allowed_to('push')
+ end
+end
diff --git a/spec/support/protected_tag_helpers.rb b/spec/support/protected_tag_helpers.rb
new file mode 100644
index 00000000000..fe9be856286
--- /dev/null
+++ b/spec/support/protected_tag_helpers.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require_relative 'protected_branch_helpers'
+
+module ProtectedTagHelpers
+ include ::ProtectedBranchHelpers
+
+ def set_allowed_to(operation, option = 'Maintainers', form: '.new-protected-tag')
+ super
+ end
+
+ def set_protected_tag_name(tag_name)
+ find('.js-protected-tag-select').click
+ find('.dropdown-input-field').set(tag_name)
+ click_on("Create wildcard #{tag_name}")
+ find('.protected-tags-dropdown .dropdown-menu', visible: false)
+ end
+end
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index a8b00004fe7..6aa59960092 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -90,7 +90,7 @@ RSpec.shared_examples "redis_shared_examples" do
subject { described_class._raw_config }
let(:config_file_name) { '/var/empty/doesnotexist' }
- it 'should be frozen' do
+ it 'is frozen' do
expect(subject).to be_frozen
end
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 62ae95df8c0..1284415da1f 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -45,7 +45,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user
context "race conditions" do
context "when #{record_class_name} migration fails and is rolled back" do
before do
- expect_any_instance_of(record_class::ActiveRecord_Associations_CollectionProxy)
+ expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
.to receive(:update_all).and_raise(ActiveRecord::Rollback)
end
@@ -66,7 +66,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user
context "when #{record_class_name} migration fails with a non-rollback exception" do
before do
- expect_any_instance_of(record_class::ActiveRecord_Associations_CollectionProxy)
+ expect_any_instance_of(ActiveRecord::Associations::CollectionProxy)
.to receive(:update_all).and_raise(ArgumentError)
end
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
new file mode 100644
index 00000000000..a0d994c4d8d
--- /dev/null
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+RSpec.shared_context 'GroupProjectsFinder context' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:current_user) { create(:user) }
+ let(:options) { {} }
+
+ let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
+
+ let!(:public_project) { create(:project, :public, group: group, path: '1') }
+ let!(:private_project) { create(:project, :private, group: group, path: '2') }
+ let!(:shared_project_1) { create(:project, :public, path: '3') }
+ let!(:shared_project_2) { create(:project, :private, path: '4') }
+ let!(:shared_project_3) { create(:project, :internal, path: '5') }
+ let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
+ let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
+
+ before do
+ shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ end
+end
diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
new file mode 100644
index 00000000000..b8a9554f55f
--- /dev/null
+++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+RSpec.shared_context 'IssuesFinder context' do
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) { create(:project, group: group) }
+ set(:project2) { create(:project) }
+ set(:project3) { create(:project, group: subgroup) }
+ set(:milestone) { create(:milestone, project: project1) }
+ set(:label) { create(:label, project: project2) }
+ set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
+ set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+ set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
+ set(:issue4) { create(:issue, project: project3) }
+ set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
+ set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
+ set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
+end
+
+RSpec.shared_context 'IssuesFinder#execute context' do
+ let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
+ let!(:label_link) { create(:label_link, label: label, target: issue2) }
+ let(:search_user) { user }
+ let(:params) { {} }
+ let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
+
+ before(:context) do
+ project1.add_maintainer(user)
+ project2.add_developer(user)
+ project2.add_developer(user2)
+ project3.add_developer(user)
+
+ issue1
+ issue2
+ issue3
+ issue4
+
+ award_emoji1
+ award_emoji2
+ award_emoji3
+ end
+end
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
new file mode 100644
index 00000000000..ab6687f1d07
--- /dev/null
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do
+ include ProjectForksHelper
+
+ # We need to explicitly permit Gitaly N+1s because of the specs that use
+ # :request_store. Gitaly N+1 detection is only enabled when :request_store is,
+ # but we don't care about potential N+1s when we're just creating several
+ # projects in the setup phase.
+ def allow_gitaly_n_plus_1
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ yield
+ end
+ end
+
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) do
+ allow_gitaly_n_plus_1 { create(:project, :public, group: group) }
+ end
+ # We cannot use `set` here otherwise we get:
+ # Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
+ # The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
+ let(:project2) do
+ allow_gitaly_n_plus_1 do
+ fork_project(project1, user)
+ end
+ end
+ let(:project3) do
+ allow_gitaly_n_plus_1 do
+ fork_project(project1, user).tap do |project|
+ project.update!(archived: true)
+ end
+ end
+ end
+ set(:project4) do
+ allow_gitaly_n_plus_1 { create(:project, :repository, group: subgroup) }
+ end
+ set(:project5) do
+ allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
+ end
+ set(:project6) do
+ allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
+ end
+
+ let!(:merge_request1) { create(:merge_request, assignees: [user], author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') }
+ let!(:merge_request2) { create(:merge_request, :conflict, assignees: [user], author: user, source_project: project2, target_project: project1, state: 'closed') }
+ let!(:merge_request3) { create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
+ let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
+ let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
+
+ before do
+ project1.add_maintainer(user)
+ project2.add_developer(user)
+ project3.add_developer(user)
+ project4.add_developer(user)
+ project5.add_developer(user)
+ project6.add_developer(user)
+
+ project2.add_developer(user2)
+ end
+end
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
new file mode 100644
index 00000000000..9e1f89ee0ed
--- /dev/null
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+
+RSpec.shared_context 'UsersFinder#execute filter by project context' do
+ set(:normal_user) { create(:user, username: 'johndoe') }
+ set(:blocked_user) { create(:user, :blocked, username: 'notsorandom') }
+ set(:external_user) { create(:user, :external) }
+ set(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+end
diff --git a/spec/support/shared_contexts/merge_request_create.rb b/spec/support/shared_contexts/merge_request_create.rb
new file mode 100644
index 00000000000..529f481c2b6
--- /dev/null
+++ b/spec/support/shared_contexts/merge_request_create.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+shared_context 'merge request create context' do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+
+ before do
+ source_project.add_maintainer(user)
+ target_project.add_maintainer(user)
+ target_project.add_maintainer(user2)
+
+ sign_in(user)
+ visit project_new_merge_request_path(target_project,
+ merge_request: {
+ source_project_id: source_project.id,
+ target_project_id: target_project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ })
+ end
+end
diff --git a/spec/support/shared_contexts/merge_request_edit.rb b/spec/support/shared_contexts/merge_request_edit.rb
new file mode 100644
index 00000000000..c84510ff47d
--- /dev/null
+++ b/spec/support/shared_contexts/merge_request_edit.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+shared_context 'merge request edit context' do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project,
+ target_project: target_project,
+ source_branch: 'fix',
+ target_branch: 'master')
+ end
+
+ before do
+ source_project.add_maintainer(user)
+ target_project.add_maintainer(user)
+ target_project.add_maintainer(user2)
+
+ sign_in(user)
+ visit edit_project_merge_request_path(target_project, merge_request)
+ end
+end
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
new file mode 100644
index 00000000000..b4808ac0068
--- /dev/null
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'GroupPolicy context' do
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:maintainer) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:group) { create(:group, :private) }
+
+ let(:guest_permissions) do
+ %i[
+ read_label read_group upload_file read_namespace read_group_activity
+ read_group_issues read_group_boards read_group_labels read_group_milestones
+ read_group_merge_requests
+ ]
+ end
+ let(:reporter_permissions) { [:admin_label] }
+ let(:developer_permissions) { [:admin_milestone] }
+ let(:maintainer_permissions) do
+ %i[
+ create_projects
+ read_cluster create_cluster update_cluster admin_cluster add_cluster
+ ]
+ end
+ let(:owner_permissions) do
+ [
+ :admin_group,
+ :admin_namespace,
+ :admin_group_member,
+ :change_visibility_level,
+ :set_note_created_at,
+ (Gitlab::Database.postgresql? ? :create_subgroup : nil)
+ ].compact
+ end
+
+ before do
+ group.add_guest(guest)
+ group.add_reporter(reporter)
+ group.add_developer(developer)
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ subject { described_class.new(current_user, group) }
+end
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
new file mode 100644
index 00000000000..54d9f5b15f2
--- /dev/null
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'ProjectPolicy context' do
+ set(:guest) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:maintainer) { create(:user) }
+ set(:owner) { create(:user) }
+ set(:admin) { create(:admin) }
+ let(:project) { create(:project, :public, namespace: owner.namespace) }
+
+ let(:base_guest_permissions) do
+ %i[
+ read_project read_board read_list read_wiki read_issue
+ read_project_for_iids read_issue_iid read_label
+ read_milestone read_project_snippet read_project_member read_note
+ create_project create_issue create_note upload_file create_merge_request_in
+ award_emoji
+ ]
+ end
+
+ let(:base_reporter_permissions) do
+ %i[
+ download_code fork_project create_project_snippet update_issue
+ admin_issue admin_label admin_list read_commit_status read_build
+ read_container_image read_pipeline read_environment read_deployment
+ read_merge_request download_wiki_code read_sentry_issue read_prometheus
+ ]
+ end
+
+ let(:team_member_reporter_permissions) do
+ %i[build_download_code build_read_container_image]
+ end
+
+ let(:developer_permissions) do
+ %i[
+ admin_milestone admin_merge_request update_merge_request create_commit_status
+ update_commit_status create_build update_build create_pipeline
+ update_pipeline create_merge_request_from create_wiki push_code
+ resolve_note create_container_image update_container_image
+ create_environment create_deployment create_release update_release
+ ]
+ end
+
+ let(:base_maintainer_permissions) do
+ %i[
+ push_to_delete_protected_branch update_project_snippet update_environment
+ update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project
+ admin_commit_status admin_build admin_container_image
+ admin_pipeline admin_environment admin_deployment destroy_release add_cluster
+ daily_statistics
+ ]
+ end
+
+ let(:public_permissions) do
+ %i[
+ download_code fork_project read_commit_status read_pipeline
+ read_container_image build_download_code build_read_container_image
+ download_wiki_code read_release
+ ]
+ end
+
+ let(:base_owner_permissions) do
+ %i[
+ change_namespace change_visibility_level rename_project remove_project
+ archive_project remove_fork_project destroy_merge_request destroy_issue
+ set_issue_iid set_issue_created_at set_note_created_at
+ ]
+ end
+
+ # Used in EE specs
+ let(:additional_guest_permissions) { [] }
+ let(:additional_reporter_permissions) { [] }
+ let(:additional_maintainer_permissions) { [] }
+ let(:additional_owner_permissions) { [] }
+
+ let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
+ let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }
+ let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions }
+ let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }
+
+ before do
+ project.add_guest(guest)
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ end
+end
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index d92e8318fa0..089f1798cd2 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -26,6 +26,14 @@ Service.available_services_names.each do |service|
end
end
+ before do
+ if service == 'github' && respond_to?(:stub_licensed_features)
+ stub_licensed_features(github_project_service_integration: true)
+ project.clear_memoization(:disabled_services)
+ project.clear_memoization(:licensed_feature_available)
+ end
+ end
+
def initialize_service(service)
service_item = project.find_or_initialize_service(service)
service_item.properties = service_attrs
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb
new file mode 100644
index 00000000000..421303c97be
--- /dev/null
+++ b/spec/support/shared_examples/application_setting_examples.rb
@@ -0,0 +1,291 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'application settings examples' do
+ context 'restricted signup domains' do
+ it 'sets single domain' do
+ setting.domain_whitelist_raw = 'example.com'
+ expect(setting.domain_whitelist).to eq(['example.com'])
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.domain_whitelist_raw = 'example.com *.example.com'
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.domain_whitelist_raw = "example.com\n *.example.com"
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.domain_whitelist_raw = "example.com, *.example.com"
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+ end
+
+ context 'blacklisted signup domains' do
+ it 'sets single domain' do
+ setting.domain_blacklist_raw = 'example.com'
+ expect(setting.domain_blacklist).to contain_exactly('example.com')
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.domain_blacklist_raw = 'example.com *.example.com'
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.domain_blacklist_raw = "example.com\n *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.domain_blacklist_raw = "example.com, *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with semicolon' do
+ setting.domain_blacklist_raw = "example.com; *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with mixture of everything' do
+ setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ end
+
+ it 'sets multiple domain with file' do
+ setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
+ expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
+ end
+ end
+
+ describe 'usage ping settings' do
+ context 'when the usage ping is disabled in gitlab.yml' do
+ before do
+ allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(false)
+ end
+
+ it 'does not allow the usage ping to be configured' do
+ expect(setting.usage_ping_can_be_configured?).to be_falsey
+ end
+
+ context 'when the usage ping is disabled in the DB' do
+ before do
+ setting.usage_ping_enabled = false
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+
+ context 'when the usage ping is enabled in the DB' do
+ before do
+ setting.usage_ping_enabled = true
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+ end
+
+ context 'when the usage ping is enabled in gitlab.yml' do
+ before do
+ allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(true)
+ end
+
+ it 'allows the usage ping to be configured' do
+ expect(setting.usage_ping_can_be_configured?).to be_truthy
+ end
+
+ context 'when the usage ping is disabled in the DB' do
+ before do
+ setting.usage_ping_enabled = false
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+
+ context 'when the usage ping is enabled in the DB' do
+ before do
+ setting.usage_ping_enabled = true
+ end
+
+ it 'returns true for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe '#allowed_key_types' do
+ it 'includes all key types by default' do
+ expect(setting.allowed_key_types).to contain_exactly(*described_class::SUPPORTED_KEY_TYPES)
+ end
+
+ it 'excludes disabled key types' do
+ expect(setting.allowed_key_types).to include(:ed25519)
+
+ setting.ed25519_key_restriction = described_class::FORBIDDEN_KEY_VALUE
+
+ expect(setting.allowed_key_types).not_to include(:ed25519)
+ end
+ end
+
+ describe '#key_restriction_for' do
+ it 'returns the restriction value for recognised types' do
+ setting.rsa_key_restriction = 1024
+
+ expect(setting.key_restriction_for(:rsa)).to eq(1024)
+ end
+
+ it 'allows types to be passed as a string' do
+ setting.rsa_key_restriction = 1024
+
+ expect(setting.key_restriction_for('rsa')).to eq(1024)
+ end
+
+ it 'returns forbidden for unrecognised type' do
+ expect(setting.key_restriction_for(:foo)).to eq(described_class::FORBIDDEN_KEY_VALUE)
+ end
+ end
+
+ describe '#allow_signup?' do
+ it 'returns true' do
+ expect(setting.allow_signup?).to be_truthy
+ end
+
+ it 'returns false if signup is disabled' do
+ allow(setting).to receive(:signup_enabled?).and_return(false)
+
+ expect(setting.allow_signup?).to be_falsey
+ end
+
+ it 'returns false if password authentication is disabled for the web interface' do
+ allow(setting).to receive(:password_authentication_enabled_for_web?).and_return(false)
+
+ expect(setting.allow_signup?).to be_falsey
+ end
+ end
+
+ describe '#pick_repository_storage' do
+ it 'uses Array#sample to pick a random storage' do
+ array = double('array', sample: 'random')
+ expect(setting).to receive(:repository_storages).and_return(array)
+
+ expect(setting.pick_repository_storage).to eq('random')
+ end
+ end
+
+ describe '#user_default_internal_regex_enabled?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | false
+ false | '' | false
+ false | '^(?:(?!\.ext@).)*$\r?\n?' | false
+ true | '' | false
+ true | nil | false
+ true | '^(?:(?!\.ext@).)*$\r?\n?' | true
+ end
+
+ with_them do
+ before do
+ setting.user_default_external = user_default_external
+ setting.user_default_internal_regex = user_default_internal_regex
+ end
+
+ subject { setting.user_default_internal_regex_enabled? }
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ describe '#archive_builds_older_than' do
+ subject { setting.archive_builds_older_than }
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = 3600
+ end
+
+ it { is_expected.to be_within(1.minute).of(1.hour.ago) }
+ end
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = nil
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#commit_email_hostname' do
+ context 'when the value is provided' do
+ before do
+ setting.commit_email_hostname = 'localhost'
+ end
+
+ it 'returns the provided value' do
+ expect(setting.commit_email_hostname).to eq('localhost')
+ end
+ end
+
+ context 'when the value is not provided' do
+ it 'returns the default from the class' do
+ expect(setting.commit_email_hostname)
+ .to eq(described_class.default_commit_email_hostname)
+ end
+ end
+ end
+
+ it 'predicate method changes when value is updated' do
+ setting.password_authentication_enabled_for_web = false
+
+ expect(setting.password_authentication_enabled_for_web?).to be_falsey
+ end
+
+ describe 'sentry settings' do
+ context 'when the sentry settings are not set in gitlab.yml' do
+ it 'fallbacks to the settings in the database' do
+ setting.sentry_enabled = true
+ setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40'
+ setting.clientside_sentry_enabled = true
+ setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41'
+
+ allow(Gitlab.config.sentry).to receive(:enabled).and_return(false)
+ allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil)
+ allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(nil)
+
+ expect(setting.sentry_enabled).to eq true
+ expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40'
+ expect(setting.clientside_sentry_enabled).to eq true
+ expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41'
+ end
+ end
+
+ context 'when the sentry settings are set in gitlab.yml' do
+ it 'does not fallback to the settings in the database' do
+ setting.sentry_enabled = false
+ setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40'
+ setting.clientside_sentry_enabled = false
+ setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41'
+
+ allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
+ allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42')
+ allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43')
+
+ expect(setting).not_to receive(:read_attribute)
+ expect(setting.sentry_enabled).to eq true
+ expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42'
+ expect(setting.clientside_sentry_enabled).to eq true
+ expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/ci/stage_shared_examples.rb b/spec/support/shared_examples/ci/stage_shared_examples.rb
new file mode 100644
index 00000000000..925974ed11e
--- /dev/null
+++ b/spec/support/shared_examples/ci/stage_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+shared_examples 'manual playable stage' do |stage_type|
+ let(:stage) { build(stage_type, status: status) }
+
+ describe '#manual_playable?' do
+ subject { stage.manual_playable? }
+
+ context 'when is manual' do
+ let(:status) { 'manual' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when is scheduled' do
+ let(:status) { 'scheduled' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when is skipped' do
+ let(:status) { 'skipped' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index c603421d748..db935bcb388 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -329,14 +329,6 @@ shared_examples_for 'trace with disabled live trace feature' do
it_behaves_like 'read successfully with IO'
end
- context 'when current_path (with project_ci_id) exists' do
- before do
- expect(trace).to receive(:deprecated_path) { expand_fixture_path('trace/sample_trace') }
- end
-
- it_behaves_like 'read successfully with IO'
- end
-
context 'when db trace exists' do
before do
build.send(:write_attribute, :trace, "data")
@@ -396,37 +388,6 @@ shared_examples_for 'trace with disabled live trace feature' do
end
end
- context 'deprecated path' do
- let(:path) { trace.send(:deprecated_path) }
-
- context 'with valid ci_id' do
- before do
- build.project.update(ci_id: 1000)
-
- FileUtils.mkdir_p(File.dirname(path))
-
- File.open(path, "w") do |file|
- file.write("data")
- end
- end
-
- it "trace exist" do
- expect(trace.exist?).to be(true)
- end
-
- it "can be erased" do
- trace.erase!
- expect(trace.exist?).to be(false)
- end
- end
-
- context 'without valid ci_id' do
- it "does not return deprecated path" do
- expect(path).to be_nil
- end
- end
- end
-
context 'stored in database' do
before do
build.send(:write_attribute, :trace, "data")
diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
new file mode 100644
index 00000000000..8dd78fd0a25
--- /dev/null
+++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+shared_examples 'disabled when using an external authorization service' do
+ include ExternalAuthorizationServiceHelpers
+
+ it 'works when the feature is not enabled' do
+ subject
+
+ expect(response).to be_success
+ end
+
+ it 'renders a 404 with a message when the feature is enabled' do
+ enable_external_authorization_service_check
+
+ subject
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+end
+
+shared_examples 'unauthorized when external service denies access' do
+ include ExternalAuthorizationServiceHelpers
+
+ it 'allows access when the authorization service allows it' do
+ external_service_allow_access(user, project)
+
+ subject
+
+ # Account for redirects after updates
+ expect(response.status).to be_between(200, 302)
+ end
+
+ it 'allows access when the authorization service denies it' do
+ external_service_deny_access(user, project)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
index 982e0317f7f..b7080c68270 100644
--- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
@@ -9,87 +9,87 @@
# - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project }
-shared_examples 'repository lfs file load' do
- context 'when file is stored in lfs' do
- let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
- let(:lfs_size) { '1575078' }
- let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+shared_examples 'a controller that can serve LFS files' do
+ let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
+ let(:lfs_size) { '1575078' }
+ let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+
+ context 'when lfs is enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ end
- context 'when lfs is enabled' do
+ context 'when project has access' do
before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ project.lfs_objects << lfs_object
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
end
- context 'when project has access' do
- before do
- project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
- end
+ it 'serves the file' do
+ lfs_uploader = LfsObjectUploader.new(lfs_object)
- it 'serves the file' do
- # Notice the filename= is omitted from the disposition; this is because
- # Rails 5 will append this header in send_file
- expect(controller).to receive(:send_file)
- .with(
- "#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897",
- filename: filename,
- disposition: %Q(attachment; filename*=UTF-8''#{filename}))
+ # Notice the filename= is omitted from the disposition; this is because
+ # Rails 5 will append this header in send_file
+ expect(controller).to receive(:send_file)
+ .with(
+ File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename),
+ filename: filename,
+ disposition: %Q(attachment; filename*=UTF-8''#{filename}))
- subject
+ subject
- expect(response).to have_gitlab_http_status(200)
- end
+ expect(response).to have_gitlab_http_status(200)
+ end
- context 'and lfs uses object storage' do
- let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
+ context 'and lfs uses object storage' do
+ let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
- before do
- stub_lfs_object_storage
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
+ before do
+ stub_lfs_object_storage
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
- it 'responds with redirect to file' do
- subject
+ it 'responds with redirect to file' do
+ subject
- expect(response).to have_gitlab_http_status(302)
- expect(response.location).to include(lfs_object.reload.file.path)
- end
+ expect(response).to have_gitlab_http_status(302)
+ expect(response.location).to include(lfs_object.reload.file.path)
+ end
- it 'sets content disposition' do
- subject
+ it 'sets content disposition' do
+ subject
- file_uri = URI.parse(response.location)
- params = CGI.parse(file_uri.query)
+ file_uri = URI.parse(response.location)
+ params = CGI.parse(file_uri.query)
- expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso))
- end
+ expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename}))
end
end
+ end
- context 'when project does not have access' do
- it 'does not serve the file' do
- subject
+ context 'when project does not have access' do
+ it 'does not serve the file' do
+ subject
- expect(response).to have_gitlab_http_status(404)
- end
+ expect(response).to have_gitlab_http_status(404)
end
end
+ end
- context 'when lfs is not enabled' do
- before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
- end
+ context 'when lfs is not enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ end
- it 'delivers ASCII file' do
- subject
+ it 'delivers ASCII file' do
+ subject
- expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(response.header['Content-Disposition'])
- .to eq('inline')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header['Content-Disposition'])
+ .to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
index 98ab04c5636..eb051166a69 100644
--- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
@@ -4,7 +4,7 @@ shared_examples 'set sort order from user preference' do
# however any other field present in user_preferences table can be used for testing.
context 'when database is in read-only mode' do
- it 'it does not update user preference' do
+ it 'does not update user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb
index b615a8f54cf..e80722857ec 100644
--- a/spec/support/shared_examples/controllers/variables_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb
@@ -120,4 +120,16 @@ shared_examples 'PATCH #update updates variables' do
expect(response).to match_response_schema('variables')
end
end
+
+ context 'for variables of type file' do
+ let(:variables_attributes) do
+ [
+ new_variable_attributes.merge(variable_type: 'file')
+ ]
+ end
+
+ it 'creates new variable of type file' do
+ expect { subject }.to change { owner.variables.file.count }.by(1)
+ end
+ end
end
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 52a2ee49495..4e45e2921e7 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,18 +1,17 @@
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
- def expect_disabled_state(form, submit, is_disabled = true)
+ def expect_disabled_state(form, submit_selector, is_disabled = true)
disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])'
- form.find(".js-dirty-submit#{disabled_selector}", match: :first)
-
- expect(submit.disabled?).to be is_disabled
+ form.find("#{submit_selector}#{disabled_selector}")
end
selectors.each do |selector|
it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do
form = find(selector[:form])
- submit = form.first('.js-dirty-submit')
+ submit_selector = selector[:submit] || 'input[type="submit"]'
+ submit = form.first(submit_selector)
input = form.first(selector[:input])
is_radio = input[:type] == 'radio'
is_checkbox = input[:type] == 'checkbox'
@@ -22,15 +21,14 @@ shared_examples 'dirty submit form' do |selector_args|
original_checkable = input if is_checkbox
expect(submit.disabled?).to be true
- expect(input.checked?).to be false
is_checkable ? input.click : input.set("#{original_value} changes")
- expect_disabled_state(form, submit, false)
+ expect_disabled_state(form, submit_selector, false)
is_checkable ? original_checkable.click : input.set(original_value)
- expect_disabled_state(form, submit)
+ expect_disabled_state(form, submit_selector)
end
end
end
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index 7038a366144..ec1b1754cf0 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -1,42 +1,17 @@
RSpec.shared_examples 'a creatable merge request' do
include WaitForRequests
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let(:target_project) { create(:project, :public, :repository) }
- let(:source_project) { target_project }
- let!(:milestone) { create(:milestone, project: target_project) }
- let!(:label) { create(:label, project: target_project) }
- let!(:label2) { create(:label, project: target_project) }
-
- before do
- source_project.add_maintainer(user)
- target_project.add_maintainer(user)
- target_project.add_maintainer(user2)
-
- sign_in(user)
- visit project_new_merge_request_path(
- target_project,
- merge_request: {
- source_project_id: source_project.id,
- target_project_id: target_project.id,
- source_branch: 'fix',
- target_branch: 'master'
- })
- end
-
it 'creates new merge request', :js do
- click_button 'Assignee'
+ find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
click_link user2.name
end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+ expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user2.name
end
-
click_link 'Assign to me'
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
end
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index eef0327c9a6..a6121fcc50a 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -1,34 +1,10 @@
RSpec.shared_examples 'an editable merge request' do
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:milestone) { create(:milestone, project: target_project) }
- let!(:label) { create(:label, project: target_project) }
- let!(:label2) { create(:label, project: target_project) }
- let(:target_project) { create(:project, :public, :repository) }
- let(:source_project) { target_project }
- let(:merge_request) do
- create(:merge_request,
- source_project: source_project,
- target_project: target_project,
- source_branch: 'fix',
- target_branch: 'master')
- end
-
- before do
- source_project.add_maintainer(user)
- target_project.add_maintainer(user)
- target_project.add_maintainer(user2)
-
- sign_in(user)
- visit edit_project_merge_request_path(target_project, merge_request)
- end
-
it 'updates merge request', :js do
- click_button 'Assignee'
+ find('.js-assignee-search').click
page.within '.dropdown-menu-user' do
click_link user.name
end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
end
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 75ad948e42c..d87e5fcaa88 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -19,7 +19,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
- accept_confirm { click_on 'Grant access' }
+ click_on 'Grant access'
expect_no_visible_access_request(entity, user)
@@ -40,13 +40,11 @@ RSpec.shared_examples 'Maintainer manages access requests' do
end
def expect_visible_access_request(entity, user)
- expect(entity.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content "Users requesting access to #{entity.name} 1"
expect(page).to have_content user.name
end
def expect_no_visible_access_request(entity, user)
- expect(entity.requesters.exists?(user_id: user)).to be_falsy
expect(page).not_to have_content "Users requesting access to #{entity.name}"
end
end
diff --git a/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
new file mode 100644
index 00000000000..bab7963f06f
--- /dev/null
+++ b/spec/support/shared_examples/features/multiple_assignees_mr_shared_examples.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+shared_examples 'multiple assignees merge request' do |action, save_button_title|
+ it "#{action} a MR with multiple assignees", :js do
+ find('.js-assignee-search').click
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ click_link user2.name
+ end
+
+ # Extra click needed in order to toggle the dropdown
+ find('.js-assignee-search').click
+
+ expect(all('input[name="merge_request[assignee_ids][]"]', visible: false).map(&:value))
+ .to match_array([user.id.to_s, user2.id.to_s])
+
+ page.within '.js-assignee-search' do
+ expect(page).to have_content "#{user2.name} + 1 more"
+ end
+
+ click_button save_button_title
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content '2 Assignees'
+
+ click_link 'Edit'
+
+ expect(page).to have_content user.name
+ expect(page).to have_content user2.name
+ end
+ end
+
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ # Closing dropdown to persist
+ click_link 'Edit'
+
+ expect(page).to have_content user2.name
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb
new file mode 100644
index 00000000000..782a2d97746
--- /dev/null
+++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+shared_examples 'assignee ID filter' do
+ it 'returns issuables assigned to that user' do
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+end
+
+shared_examples 'assignee username filter' do
+ it 'returns issuables assigned to those users' do
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+end
+
+shared_examples 'no assignee filter' do
+ let(:params) { { assignee_id: 'None' } }
+
+ it 'returns issuables not assigned to any assignee' do
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+
+ it 'returns issuables not assigned to any assignee' do
+ params[:assignee_id] = 0
+
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+
+ it 'returns issuables not assigned to any assignee' do
+ params[:assignee_id] = 'none'
+
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+end
+
+shared_examples 'any assignee filter' do
+ context '' do
+ let(:params) { { assignee_id: 'Any' } }
+
+ it 'returns issuables assigned to any assignee' do
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+
+ it 'returns issuables assigned to any assignee' do
+ params[:assignee_id] = 'any'
+
+ expect(issuables).to contain_exactly(*expected_issuables)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
new file mode 100644
index 00000000000..d7e17cc0b70
--- /dev/null
+++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+shared_examples 'a finder with external authorization service' do
+ include ExternalAuthorizationServiceHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'finds the subject' do
+ expect(described_class.new(user).execute).to include(subject)
+ end
+
+ context 'with an external authorization service' do
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'does not include the subject when no project was given' do
+ expect(described_class.new(user).execute).not_to include(subject)
+ end
+
+ it 'includes the subject when a project id was given' do
+ expect(described_class.new(user, project_params).execute).to include(subject)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb
index ba9b7d3bdcf..01bee603274 100644
--- a/spec/support/shared_examples/helm_generated_script.rb
+++ b/spec/support/shared_examples/helm_generated_script.rb
@@ -6,7 +6,7 @@ shared_examples 'helm commands' do
EOS
end
- it 'should return appropriate command' do
+ it 'returns appropriate command' do
expect(subject.generate_script.strip).to eq((helm_setup + commands).strip)
end
end
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index c3d40c5b231..d97b21f71cd 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -31,7 +31,7 @@ shared_examples 'system notes for milestones' do
context 'project milestones' do
it 'creates a system note' do
expect do
- update_issuable(milestone: create(:milestone))
+ update_issuable(milestone: create(:milestone, project: project))
end.to change { Note.system.count }.by(1)
end
end
diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
index 90d67fd00fc..244f4766a84 100644
--- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
@@ -1,11 +1,11 @@
shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
include ProjectForksHelper
- def get_action(action, project)
+ def get_action(action, project, extra_params = {})
if action
- get action, params: { author_id: project.creator.id }
+ get action, params: { author_id: project.creator.id }.merge(extra_params)
else
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ get :index, params: { namespace_id: project.namespace, project_id: project }.merge(extra_params)
end
end
@@ -17,23 +17,44 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
end
end
- before do
- @issuable_ids = %w[fix improve/awesome].map do |source_branch|
- create_issuable(issuable_type, project, source_branch: source_branch).id
+ let!(:issuables) do
+ %w[fix improve/awesome].map do |source_branch|
+ create_issuable(issuable_type, project, source_branch: source_branch)
end
end
+ let(:issuable_ids) { issuables.map(&:id) }
+
it "creates indexed meta-data object for issuable notes and votes count" do
get_action(action, project)
meta_data = assigns(:issuable_meta_data)
aggregate_failures do
- expect(meta_data.keys).to match_array(@issuable_ids)
+ expect(meta_data.keys).to match_array(issuables.map(&:id))
expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta))
end
end
+ context 'searching' do
+ let(:result_issuable) { issuables.first }
+ let(:search) { result_issuable.title }
+
+ before do
+ stub_feature_flags(attempt_project_search_optimizations: true)
+ end
+
+ # .simple_sorts is the same across all Sortable classes
+ sorts = ::Issue.simple_sorts.keys + %w[popularity priority label_priority]
+ sorts.each do |sort|
+ it "works when sorting by #{sort}" do
+ get_action(action, project, search: search, sort: sort)
+
+ expect(assigns(:issuable_meta_data).keys).to include(result_issuable.id)
+ end
+ end
+ end
+
it "avoids N+1 queries" do
control = ActiveRecord::QueryRecorder.new { get_action(action, project) }
issuable = create_issuable(issuable_type, project, source_branch: 'csv')
diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
index f300bdd48b1..f326e502092 100644
--- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
+++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
@@ -11,3 +11,11 @@ shared_examples 'redirecting a legacy path' do |source, target|
expect(get(source)).not_to redirect_to(target)
end
end
+
+shared_examples 'redirecting a legacy project path' do |source, target|
+ include RSpec::Rails::RequestExampleGroup
+
+ it "redirects #{source} to #{target}" do
+ expect(get(source)).to redirect_to(target)
+ end
+end
diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
index db69b75c0c8..a86050e2cf2 100644
--- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb
+++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
@@ -2,7 +2,8 @@ require 'timeout'
shared_examples 'malicious regexp' do
let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' }
- let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' }
+ let(:malicious_regexp_re2) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' }
+ let(:malicious_regexp_ruby) { '/^(([a-z])+.)+[A-Z]([a-z])+$/i' }
it 'takes under a second' do
expect { Timeout.timeout(1) { subject } }.not_to raise_error
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
new file mode 100644
index 00000000000..a248f60d23e
--- /dev/null
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -0,0 +1,113 @@
+require 'spec_helper'
+
+shared_examples_for 'AtomicInternalId' do |validate_presence: true|
+ describe '.has_internal_id' do
+ describe 'Module inclusion' do
+ subject { described_class }
+
+ it { is_expected.to include_module(AtomicInternalId) }
+ end
+
+ describe 'Validation' do
+ before do
+ allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
+
+ instance.valid?
+ end
+
+ context 'when presence validation is required' do
+ before do
+ skip unless validate_presence
+ end
+
+ it 'validates presence' do
+ expect(instance.errors[internal_id_attribute]).to include("can't be blank")
+ end
+ end
+
+ context 'when presence validation is not required' do
+ before do
+ skip if validate_presence
+ end
+
+ it 'does not validate presence' do
+ expect(instance.errors[internal_id_attribute]).to be_empty
+ end
+ end
+ end
+
+ describe 'Creating an instance' do
+ subject { instance.save! }
+
+ it 'saves a new instance properly' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ describe 'internal id generation' do
+ subject { instance.save! }
+
+ it 'calls InternalId.generate_next and sets internal id attribute' do
+ iid = rand(1..1000)
+
+ expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
+ subject
+ expect(read_internal_id).to eq(iid)
+ end
+
+ it 'does not overwrite an existing internal id' do
+ write_internal_id(4711)
+
+ expect { subject }.not_to change { read_internal_id }
+ end
+
+ context 'when the instance has an internal ID set' do
+ let(:internal_id) { 9001 }
+
+ it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do
+ write_internal_id(internal_id)
+
+ expect(InternalId)
+ .to receive(:track_greatest)
+ .with(instance, scope_attrs, usage, internal_id, any_args)
+ .and_return(internal_id)
+ subject
+ end
+ end
+ end
+
+ describe "#reset_scope_internal_id_attribute" do
+ it 'rewinds the allocated IID' do
+ expect { ensure_scope_attribute! }.not_to raise_error
+ expect(read_internal_id).not_to be_nil
+
+ expect(reset_scope_attribute).to be_nil
+ expect(read_internal_id).to be_nil
+ end
+
+ it 'allocates the same IID' do
+ internal_id = ensure_scope_attribute!
+ reset_scope_attribute
+ expect(read_internal_id).to be_nil
+
+ expect(ensure_scope_attribute!).to eq(internal_id)
+ end
+ end
+
+ def ensure_scope_attribute!
+ instance.public_send(:"ensure_#{scope}_#{internal_id_attribute}!")
+ end
+
+ def reset_scope_attribute
+ instance.public_send(:"reset_#{scope}_#{internal_id_attribute}")
+ end
+
+ def read_internal_id
+ instance.public_send(internal_id_attribute)
+ end
+
+ def write_internal_id(value)
+ instance.public_send(:"#{internal_id_attribute}=", value)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb
deleted file mode 100644
index c659be8f13a..00000000000
--- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-
-shared_examples_for 'AtomicInternalId' do |validate_presence: true|
- describe '.has_internal_id' do
- describe 'Module inclusion' do
- subject { described_class }
-
- it { is_expected.to include_module(AtomicInternalId) }
- end
-
- describe 'Validation' do
- before do
- allow_any_instance_of(described_class).to receive(:"ensure_#{scope}_#{internal_id_attribute}!")
-
- instance.valid?
- end
-
- context 'when presence validation is required' do
- before do
- skip unless validate_presence
- end
-
- it 'validates presence' do
- expect(instance.errors[internal_id_attribute]).to include("can't be blank")
- end
- end
-
- context 'when presence validation is not required' do
- before do
- skip if validate_presence
- end
-
- it 'does not validate presence' do
- expect(instance.errors[internal_id_attribute]).to be_empty
- end
- end
- end
-
- describe 'Creating an instance' do
- subject { instance.save! }
-
- it 'saves a new instance properly' do
- expect { subject }.not_to raise_error
- end
- end
-
- describe 'internal id generation' do
- subject { instance.save! }
-
- it 'calls InternalId.generate_next and sets internal id attribute' do
- iid = rand(1..1000)
-
- expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
- subject
- expect(instance.public_send(internal_id_attribute)).to eq(iid)
- end
-
- it 'does not overwrite an existing internal id' do
- instance.public_send("#{internal_id_attribute}=", 4711)
-
- expect { subject }.not_to change { instance.public_send(internal_id_attribute) }
- end
-
- context 'when the instance has an internal ID set' do
- let(:internal_id) { 9001 }
-
- it 'calls InternalId.update_last_value and sets the `last_value` to that of the instance' do
- instance.send("#{internal_id_attribute}=", internal_id)
-
- expect(InternalId)
- .to receive(:track_greatest)
- .with(instance, scope_attrs, usage, internal_id, any_args)
- .and_return(internal_id)
- subject
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
new file mode 100644
index 00000000000..0a302e7d030
--- /dev/null
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -0,0 +1,248 @@
+require "spec_helper"
+
+shared_examples_for "chat service" do |service_name|
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Validations" do
+ context "when service is active" do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:webhook) }
+ it_behaves_like "issue tracker service URL attribute", :webhook
+ end
+
+ context "when service is inactive" do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:webhook) }
+ end
+ end
+
+ describe '.supported_events' do
+ it 'does not support deployment_events' do
+ expect(described_class.supported_events).not_to include('deployment')
+ end
+ end
+
+ describe "#execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:webhook_url) { "https://example.gitlab.com/" }
+
+ before do
+ allow(subject).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ shared_examples "#{service_name} service" do
+ it "calls #{service_name} API" do
+ subject.execute(sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once
+ end
+ end
+
+ context "with push events" do
+ let(:sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ it_behaves_like "#{service_name} service"
+
+ it "specifies the webhook when it is configured" do
+ expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object)
+
+ subject.execute(sample_data)
+ end
+
+ context "with not default branch" do
+ let(:sample_data) do
+ Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch")
+ end
+
+ context "when notify_only_default_branch enabled" do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ it "does not call the Discord Webhooks API" do
+ result = subject.execute(sample_data)
+
+ expect(result).to be_falsy
+ end
+ end
+
+ context "when notify_only_default_branch disabled" do
+ before do
+ subject.notify_only_default_branch = false
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+ end
+ end
+
+ context "with issue events" do
+ let(:opts) { { title: "Awesome issue", description: "please fix" } }
+ let(:sample_data) do
+ service = Issues::CreateService.new(project, user, opts)
+ issue = service.execute
+ service.hook_data(issue, "open")
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with merge events" do
+ let(:opts) do
+ {
+ title: "Awesome merge_request",
+ description: "please fix",
+ source_branch: "feature",
+ target_branch: "master"
+ }
+ end
+
+ let(:sample_data) do
+ service = MergeRequests::CreateService.new(project, user, opts)
+ merge_request = service.execute
+ service.hook_data(merge_request, "open")
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with wiki page events" do
+ let(:opts) do
+ {
+ title: "Awesome wiki_page",
+ content: "Some text describing some thing or another",
+ format: "md",
+ message: "user created page: Awesome wiki_page"
+ }
+ end
+ let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) }
+ let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") }
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with note events" do
+ let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) }
+
+ context "with commit comment" do
+ let(:note) do
+ create(:note_on_commit,
+ author: user,
+ project: project,
+ commit_id: project.repository.commit.id,
+ note: "a comment on a commit")
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with merge request comment" do
+ let(:note) do
+ create(:note_on_merge_request, project: project, note: "merge request note")
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with issue comment" do
+ let(:note) do
+ create(:note_on_issue, project: project, note: "issue note")
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with snippet comment" do
+ let(:note) do
+ create(:note_on_project_snippet, project: project, note: "snippet note")
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+ end
+
+ context "with pipeline events" do
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project, status: status,
+ sha: project.commit.sha, ref: project.default_branch)
+ end
+ let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ context "with failed pipeline" do
+ let(:status) { "failed" }
+
+ it_behaves_like "#{service_name} service"
+ end
+
+ context "with succeeded pipeline" do
+ let(:status) { "success" }
+
+ context "with default notify_only_broken_pipelines" do
+ it "does not call Discord Webhooks API" do
+ result = subject.execute(sample_data)
+
+ expect(result).to be_falsy
+ end
+ end
+
+ context "when notify_only_broken_pipelines is false" do
+ before do
+ subject.notify_only_broken_pipelines = false
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+ end
+
+ context "with not default branch" do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch")
+ end
+
+ context "when notify_only_default_branch enabled" do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ it "does not call the Discord Webhooks API" do
+ result = subject.execute(sample_data)
+
+ expect(result).to be_falsy
+ end
+ end
+
+ context "when notify_only_default_branch disabled" do
+ before do
+ subject.notify_only_default_branch = false
+ end
+
+ it_behaves_like "#{service_name} service"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb
deleted file mode 100644
index cf1d52a9616..00000000000
--- a/spec/support/shared_examples/models/chat_service_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-require "spec_helper"
-
-shared_examples_for "chat service" do |service_name|
- describe "Associations" do
- it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
- end
-
- describe "Validations" do
- context "when service is active" do
- before do
- subject.active = true
- end
-
- it { is_expected.to validate_presence_of(:webhook) }
- it_behaves_like "issue tracker service URL attribute", :webhook
- end
-
- context "when service is inactive" do
- before do
- subject.active = false
- end
-
- it { is_expected.not_to validate_presence_of(:webhook) }
- end
- end
-
- describe "#execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:webhook_url) { "https://example.gitlab.com/" }
-
- before do
- allow(subject).to receive_messages(
- project: project,
- project_id: project.id,
- service_hook: true,
- webhook: webhook_url
- )
-
- WebMock.stub_request(:post, webhook_url)
- end
-
- shared_examples "#{service_name} service" do
- it "calls #{service_name} API" do
- subject.execute(sample_data)
-
- expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once
- end
- end
-
- context "with push events" do
- let(:sample_data) do
- Gitlab::DataBuilder::Push.build_sample(project, user)
- end
-
- it_behaves_like "#{service_name} service"
-
- it "specifies the webhook when it is configured" do
- expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object)
-
- subject.execute(sample_data)
- end
-
- context "with not default branch" do
- let(:sample_data) do
- Gitlab::DataBuilder::Push.build(project, user, nil, nil, "not-the-default-branch")
- end
-
- context "when notify_only_default_branch enabled" do
- before do
- subject.notify_only_default_branch = true
- end
-
- it "does not call the Discord Webhooks API" do
- result = subject.execute(sample_data)
-
- expect(result).to be_falsy
- end
- end
-
- context "when notify_only_default_branch disabled" do
- before do
- subject.notify_only_default_branch = false
- end
-
- it_behaves_like "#{service_name} service"
- end
- end
- end
-
- context "with issue events" do
- let(:opts) { { title: "Awesome issue", description: "please fix" } }
- let(:sample_data) do
- service = Issues::CreateService.new(project, user, opts)
- issue = service.execute
- service.hook_data(issue, "open")
- end
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with merge events" do
- let(:opts) do
- {
- title: "Awesome merge_request",
- description: "please fix",
- source_branch: "feature",
- target_branch: "master"
- }
- end
-
- let(:sample_data) do
- service = MergeRequests::CreateService.new(project, user, opts)
- merge_request = service.execute
- service.hook_data(merge_request, "open")
- end
-
- before do
- project.add_developer(user)
- end
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with wiki page events" do
- let(:opts) do
- {
- title: "Awesome wiki_page",
- content: "Some text describing some thing or another",
- format: "md",
- message: "user created page: Awesome wiki_page"
- }
- end
- let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) }
- let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") }
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with note events" do
- let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) }
-
- context "with commit comment" do
- let(:note) do
- create(:note_on_commit,
- author: user,
- project: project,
- commit_id: project.repository.commit.id,
- note: "a comment on a commit")
- end
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with merge request comment" do
- let(:note) do
- create(:note_on_merge_request, project: project, note: "merge request note")
- end
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with issue comment" do
- let(:note) do
- create(:note_on_issue, project: project, note: "issue note")
- end
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with snippet comment" do
- let(:note) do
- create(:note_on_project_snippet, project: project, note: "snippet note")
- end
-
- it_behaves_like "#{service_name} service"
- end
- end
-
- context "with pipeline events" do
- let(:pipeline) do
- create(:ci_pipeline,
- project: project, status: status,
- sha: project.commit.sha, ref: project.default_branch)
- end
- let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
-
- context "with failed pipeline" do
- let(:status) { "failed" }
-
- it_behaves_like "#{service_name} service"
- end
-
- context "with succeeded pipeline" do
- let(:status) { "success" }
-
- context "with default notify_only_broken_pipelines" do
- it "does not call Discord Webhooks API" do
- result = subject.execute(sample_data)
-
- expect(result).to be_falsy
- end
- end
-
- context "when notify_only_broken_pipelines is false" do
- before do
- subject.notify_only_broken_pipelines = false
- end
-
- it_behaves_like "#{service_name} service"
- end
- end
-
- context "with not default branch" do
- let(:pipeline) do
- create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch")
- end
-
- context "when notify_only_default_branch enabled" do
- before do
- subject.notify_only_default_branch = true
- end
-
- it "does not call the Discord Webhooks API" do
- result = subject.execute(sample_data)
-
- expect(result).to be_falsy
- end
- end
-
- context "when notify_only_default_branch disabled" do
- before do
- subject.notify_only_default_branch = false
- end
-
- it_behaves_like "#{service_name} service"
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/ci_variable_shared_examples.rb b/spec/support/shared_examples/models/ci_variable_shared_examples.rb
new file mode 100644
index 00000000000..f93de8b6ff1
--- /dev/null
+++ b/spec/support/shared_examples/models/ci_variable_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+shared_examples_for 'CI variable' do
+ it { is_expected.to include_module(HasVariable) }
+
+ describe "variable type" do
+ it 'defines variable types' do
+ expect(described_class.variable_types).to eq({ "env_var" => 1, "file" => 2 })
+ end
+
+ it "defaults variable type to env_var" do
+ expect(subject.variable_type).to eq("env_var")
+ end
+
+ it "supports variable type file" do
+ variable = described_class.new(variable_type: :file)
+ expect(variable).to be_file
+ end
+ end
+
+ it 'strips whitespaces when assigning key' do
+ subject.key = " SECRET "
+ expect(subject.key).to eq("SECRET")
+ end
+
+ it 'can convert to runner variable' do
+ expect(subject.to_runner_variable.keys).to include(:key, :value, :public, :file)
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
index 1f76b981292..d6490a808ce 100644
--- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -2,6 +2,14 @@ shared_examples 'cluster application core specs' do |application_name|
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
+ describe '#can_uninstall?' do
+ it 'calls allowed_to_uninstall?' do
+ expect(subject).to receive(:allowed_to_uninstall?).and_return(true)
+
+ expect(subject.can_uninstall?).to be_truthy
+ end
+ end
+
describe '#name' do
it 'is .application_name' do
expect(subject.name).to eq(described_class.application_name)
diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
index d87b3181e80..bd3661471f8 100644
--- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
@@ -1,6 +1,32 @@
shared_examples 'cluster application helm specs' do |application_name|
let(:application) { create(application_name) }
+ describe '#uninstall_command' do
+ subject { application.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it 'has the application name' do
+ expect(subject.name).to eq(application.name)
+ end
+
+ it 'has files' do
+ expect(subject.files).to eq(application.files)
+ end
+
+ it 'is rbac' do
+ expect(subject).to be_rbac
+ end
+
+ context 'on a non rbac enabled cluster' do
+ before do
+ application.cluster.platform_kubernetes.abac!
+ end
+
+ it { is_expected.not_to be_rbac }
+ end
+ end
+
describe '#files' do
subject { application.files }
@@ -9,12 +35,12 @@ shared_examples 'cluster application helm specs' do |application_name|
application.cluster.application_helm.ca_cert = nil
end
- it 'should not include cert files when there is no ca_cert entry' do
+ it 'does not include cert files when there is no ca_cert entry' do
expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem')
end
end
- it 'should include cert files when there is a ca_cert entry' do
+ it 'includes cert files when there is a ca_cert entry' do
expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem')
expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index b8c19cab0c4..4525c03837f 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -114,6 +114,17 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject.status_reason).to eq(reason)
end
end
+
+ context 'application is uninstalling' do
+ subject { create(application_name, :uninstalling) }
+
+ it 'is uninstall_errored' do
+ subject.make_errored(reason)
+
+ expect(subject).to be_uninstall_errored
+ expect(subject.status_reason).to eq(reason)
+ end
+ end
end
describe '#make_scheduled' do
@@ -125,6 +136,16 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject).to be_scheduled
end
+ describe 'when installed' do
+ subject { create(application_name, :installed) }
+
+ it 'is scheduled' do
+ subject.make_scheduled
+
+ expect(subject).to be_scheduled
+ end
+ end
+
describe 'when was errored' do
subject { create(application_name, :errored) }
@@ -148,6 +169,28 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject.status_reason).to be_nil
end
end
+
+ describe 'when was uninstall_errored' do
+ subject { create(application_name, :uninstall_errored) }
+
+ it 'clears #status_reason' do
+ expect(subject.status_reason).not_to be_nil
+
+ subject.make_scheduled!
+
+ expect(subject.status_reason).to be_nil
+ end
+ end
+ end
+
+ describe '#make_uninstalling' do
+ subject { create(application_name, :scheduled) }
+
+ it 'is uninstalling' do
+ subject.make_uninstalling!
+
+ expect(subject).to be_uninstalling
+ end
end
end
@@ -155,16 +198,18 @@ shared_examples 'cluster application status specs' do |application_name|
using RSpec::Parameterized::TableSyntax
where(:trait, :available) do
- :not_installable | false
- :installable | false
- :scheduled | false
- :installing | false
- :installed | true
- :updating | false
- :updated | true
- :errored | false
- :update_errored | false
- :timeouted | false
+ :not_installable | false
+ :installable | false
+ :scheduled | false
+ :installing | false
+ :installed | true
+ :updating | false
+ :updated | true
+ :errored | false
+ :update_errored | false
+ :uninstalling | false
+ :uninstall_errored | false
+ :timed_out | false
end
with_them do
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 77376496854..e5375bc8280 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -41,7 +41,7 @@ shared_examples_for 'inherited access level as a member of entity' do
member.update(access_level: Gitlab::Access::REPORTER)
- expect(member.errors.full_messages).to eq(["Access level should be higher than Developer inherited membership from group #{parent_entity.name}"])
+ expect(member.errors.full_messages).to eq(["Access level should be greater than or equal to Developer inherited membership from group #{parent_entity.name}"])
end
it 'allows changing the level from a non existing member' do
diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
new file mode 100644
index 00000000000..1b09c3dd636
--- /dev/null
+++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+shared_examples_for 'UpdateProjectStatistics' do
+ let(:project) { subject.project }
+ let(:project_statistics_name) { described_class.project_statistics_name }
+ let(:statistic_attribute) { described_class.statistic_attribute }
+
+ def reload_stat
+ project.statistics.reload.send(project_statistics_name).to_i
+ end
+
+ def read_attribute
+ subject.read_attribute(statistic_attribute).to_i
+ end
+
+ it { is_expected.to be_new_record }
+
+ context 'when creating' do
+ it 'updates the project statistics' do
+ delta = read_attribute
+
+ expect { subject.save! }
+ .to change { reload_stat }
+ .by(delta)
+ end
+ end
+
+ context 'when updating' do
+ before do
+ subject.save!
+ end
+
+ it 'updates project statistics' do
+ delta = 42
+
+ expect(ProjectStatistics)
+ .to receive(:increment_statistic)
+ .and_call_original
+
+ subject.write_attribute(statistic_attribute, read_attribute + delta)
+
+ expect { subject.save! }
+ .to change { reload_stat }
+ .by(delta)
+ end
+ end
+
+ context 'when destroying' do
+ before do
+ subject.save!
+ end
+
+ it 'updates the project statistics' do
+ delta = -read_attribute
+
+ expect(ProjectStatistics)
+ .to receive(:increment_statistic)
+ .and_call_original
+
+ expect { subject.destroy }
+ .to change { reload_stat }
+ .by(delta)
+ end
+
+ context 'when it is destroyed from the project level' do
+ it 'does not update the project statistics' do
+ expect(ProjectStatistics)
+ .not_to receive(:increment_statistic)
+
+ project.update(pending_delete: true)
+ project.destroy!
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index a38354060cf..897c9106d77 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,5 +1,7 @@
shared_context 'gitlab email notification' do
- set(:project) { create(:project, :repository, name: 'a-known-name') }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project) { create(:project, :repository, name: 'a-known-name', group: group) }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
@@ -39,6 +41,47 @@ shared_examples 'an email sent from GitLab' do
end
end
+shared_examples 'an email sent to a user' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ it 'is sent to user\'s global notification email address' do
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+
+ context 'that is part of a project\'s group' do
+ it 'is sent to user\'s group notification email address when set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email)
+ expect(subject).to deliver_to(group_notification_email)
+ end
+
+ it 'is sent to user\'s global notification email address when no group email set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+
+ context 'when project is in a sub-group', :nested_groups do
+ before do
+ project.update!(group: subgroup)
+ end
+
+ it 'is sent to user\'s subgroup notification email address when set' do
+ # Set top-level group notification email address to make sure it doesn't get selected
+ create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email)
+
+ subgroup_notification_email = 'user+subgroup@example.com'
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email)
+
+ expect(subject).to deliver_to(subgroup_notification_email)
+ end
+
+ it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+end
+
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
@@ -252,3 +295,31 @@ shared_examples 'a note email' do
end
end
end
+
+shared_examples 'appearance header and footer enabled' do
+ it "contains header and footer" do
+ create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(subject.html_part).to have_body_text("<div class=\"header-message\" style=\"\"><p>Foo</p></div>")
+ expect(subject.html_part).to have_body_text("<div class=\"footer-message\" style=\"\"><p>Bar</p></div>")
+
+ expect(subject.text_part).to have_body_text(/^Foo/)
+ expect(subject.text_part).to have_body_text(/Bar$/)
+ end
+ end
+end
+
+shared_examples 'appearance header and footer not enabled' do
+ it "does not contain header and footer" do
+ create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: false
+
+ aggregate_failures do
+ expect(subject.html_part).not_to have_body_text("<div class=\"header-message\" style=\"\"><p>Foo</p></div>")
+ expect(subject.html_part).not_to have_body_text("<div class=\"footer-message\" style=\"\"><p>Bar</p></div>")
+
+ expect(subject.text_part).not_to have_body_text(/^Foo/)
+ expect(subject.text_part).not_to have_body_text(/Bar$/)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
new file mode 100644
index 00000000000..7a71e2ee370
--- /dev/null
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'archived project policies' do
+ let(:feature_write_abilities) do
+ described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
+ described_class.create_update_admin_destroy(feature)
+ end + additional_reporter_permissions + additional_maintainer_permissions
+ end
+
+ let(:other_write_abilities) do
+ %i[
+ create_merge_request_in
+ create_merge_request_from
+ push_to_delete_protected_branch
+ push_code
+ request_access
+ upload_file
+ resolve_note
+ award_emoji
+ ]
+ end
+
+ context 'when the project is archived' do
+ before do
+ project.archived = true
+ end
+
+ it 'disables write actions on all relevant project features' do
+ expect_disallowed(*feature_write_abilities)
+ end
+
+ it 'disables some other important write actions' do
+ expect_disallowed(*other_write_abilities)
+ end
+
+ it 'does not disable other abilities' do
+ expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as anonymous' do
+ context 'abilities for public projects' do
+ context 'when a project has pending invites' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, namespace: group) }
+ let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
+
+ subject { described_class.new(nil, project) }
+
+ before do
+ create(:group_member, :invited, group: group)
+ end
+
+ it 'does not grant owner access' do
+ expect_allowed(*anonymous_permissions)
+ expect_disallowed(*user_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { anonymous_permissions }
+ end
+ end
+ end
+
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(nil, project) }
+
+ it { is_expected.to be_banned }
+ end
+end
+
+RSpec.shared_examples 'project policies as guest' do
+ subject { described_class.new(guest, project) }
+
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+ let(:reporter_public_build_permissions) do
+ reporter_permissions - [:read_build, :read_pipeline]
+ end
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(*reporter_public_build_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { guest_permissions }
+ end
+
+ context 'public builds enabled' do
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(:read_build, :read_pipeline)
+ end
+ end
+
+ context 'when public builds disabled' do
+ before do
+ project.update(public_builds: false)
+ end
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(:read_build, :read_pipeline)
+ end
+ end
+
+ context 'when builds are disabled' do
+ before do
+ project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
+ end
+
+ it do
+ expect_disallowed(:read_build)
+ expect_allowed(:read_pipeline)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as reporter' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(reporter, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { reporter_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as developer' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+ subject { described_class.new(developer, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { developer_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as maintainer' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(maintainer, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { maintainer_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as owner' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(owner, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_allowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as admin' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(admin, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_allowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..b337a1c18d8
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+shared_examples 'tag quick action' do
+ context "post note to existing commit" do
+ it 'tags this commit' do
+ add_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "tagged commit #{truncated_commit_sha}"
+ expect(page).to have_content tag_name
+
+ visit project_tag_path(project, tag_name)
+ expect(page).to have_content tag_name
+ expect(page).to have_content tag_message
+ expect(page).to have_content truncated_commit_sha
+ end
+ end
+
+ context 'preview', :js do
+ it 'removes quick action from note and explains it' do
+ preview_note("/tag #{tag_name} #{tag_message}")
+
+ expect(page).not_to have_content '/tag'
+ expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"}
+ expect(page).to have_content tag_name
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..d97da6be192
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+shared_examples 'assign quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets assign quick action accordingly" do
+ assignee = create(:user, username: 'bob')
+
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq [assignee]
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+
+ it "creates the #{issuable_type} and interprets assign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/assign me"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq [maintainer]
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the assign quick action accordingly' do
+ assignee = create(:user, username: 'bob')
+ add_note("Awesome!\n\n/assign @bob")
+
+ expect(page).to have_content 'Awesome!'
+ expect(page).not_to have_content '/assign @bob'
+
+ wait_for_requests
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'Awesome!'
+ expect(issuable.assignees).to eq [assignee]
+ end
+
+ it "assigns the #{issuable_type} to the current user" do
+ add_note("/assign me")
+
+ expect(page).not_to have_content '/assign me'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload.assignees).to eq [maintainer]
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains assign quick action to bob' do
+ create(:user, username: 'bob')
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/assign @bob "
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/assign @bob'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content 'Assigns @bob.'
+ end
+ end
+
+ it 'explains assign quick action to me' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/assign me"
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/assign me'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content "Assigns @#{maintainer.username}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..74cbfa3f4b4
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+shared_examples 'award quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets award quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/award :100:"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.award_emoji).to eq []
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.award_emoji).to eq []
+ end
+
+ it 'creates the note and interprets the award quick action accordingly' do
+ add_note("/award :100:")
+
+ wait_for_requests
+ expect(page).not_to have_content '/award'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.award_emoji.last.name).to eq('100')
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains label quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/award :100:')
+
+ expect(page).not_to have_content '/award'
+ expect(page).to have_selector "gl-emoji[data-name='100']"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..a79a61bc708
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+shared_examples 'close quick action' do |issuable_type|
+ include Spec::Support::Helpers::Features::NotesHelpers
+
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets close quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/close"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ expect(issuable).to be_opened
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the close quick action accordingly' do
+ add_note("this is done, close\n\n/close")
+
+ wait_for_requests
+ expect(page).not_to have_content '/close'
+ expect(page).to have_content 'this is done, close'
+
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'this is done, close'
+ expect(issuable).to be_closed
+ end
+
+ context "when current user cannot close #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it "does not close the #{issuable_type}" do
+ add_note('/close')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable).to be_open
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains close quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("this is done, close\n/close") do
+ expect(page).not_to have_content '/close'
+ expect(page).to have_content 'this is done, close'
+ expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..1e1e3c7bc95
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+shared_examples 'copy_metadata quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).last
+
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ issuable.reload
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.milestone).to eq milestone
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets copy_metadata quick action accordingly' do
+ add_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ wait_for_requests
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).to have_content 'Commands applied'
+ issuable.reload
+ expect(issuable.milestone).to eq milestone
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+
+ context "when current user cannot copy_metadata" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not copy_metadata' do
+ add_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ wait_for_requests
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).not_to have_content 'Commands applied'
+ issuable.reload
+ expect(issuable.milestone).not_to eq milestone
+ expect(issuable.labels).to eq []
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains copy_metadata quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..8a72bbc13bf
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+shared_examples 'done quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets done quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/done"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ TodoService.new.mark_todo(issuable, maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the done quick action accordingly' do
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+ expect(todo.reload).to be_pending
+
+ expect(todos.size).to eq 1
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+
+ add_note('/done')
+
+ wait_for_requests
+ expect(page).not_to have_content '/done'
+ expect(page).to have_content 'Commands applied'
+ expect(todo.reload).to be_done
+ end
+
+ context "when current user cannot mark #{issuable_type} todo as done" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not set the #{issuable_type} todo as done" do
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+ expect(todo.reload).to be_pending
+
+ expect(todos.size).to eq 1
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+
+ add_note('/done')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(todo.reload).to be_pending
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains done quick action' do
+ TodoService.new.mark_todo(issuable, maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/done')
+
+ expect(page).not_to have_content '/done'
+ expect(page).to have_content "Marks todo as done."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..648755d7e55
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+shared_examples 'estimate quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.time_estimate).to eq 36180
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the estimate quick action accordingly' do
+ add_note("/estimate 1d 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/estimate'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.time_estimate).to eq 36180
+ end
+
+ context "when current user cannot set estimate to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set estimate' do
+ add_note("/estimate ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/estimate'
+ expect(issuable.reload.time_estimate).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains estimate quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/estimate 1d 2h 3m')
+
+ expect(page).not_to have_content '/estimate'
+ expect(page).to have_content 'Sets time estimate to 1d 2h 3m.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..9066e382b70
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+shared_examples 'label quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets label quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.labels).to eq []
+ end
+
+ it 'creates the note and interprets the label quick action accordingly' do
+ add_note("/label ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/label'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_bug, label_feature])
+ end
+
+ context "when current user cannot set label to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set label' do
+ add_note("/label ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/label'
+ expect(issuable.labels).to eq []
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains label quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/label ~bug ~feature')
+
+ expect(page).not_to have_content '/label'
+ expect(page).to have_content 'Adds bug feature labels.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..d3197f2a459
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+shared_examples 'lock quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets lock quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/lock"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update(discussion_locked: false)
+ expect(issuable).not_to be_discussion_locked
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the lock quick action accordingly' do
+ add_note('/lock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/lock'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload).to be_discussion_locked
+ end
+
+ context "when current user cannot lock to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not lock the #{issuable_type}" do
+ add_note('/lock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/lock'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains lock quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/lock')
+
+ expect(page).not_to have_content '/lock'
+ expect(page).to have_content "Locks the discussion"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..7f16ce93b6a
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+shared_examples 'milestone quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets milestone quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\""
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.milestone).to eq milestone
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.milestone).to be_nil
+ end
+
+ it 'creates the note and interprets the milestone quick action accordingly' do
+ add_note("/milestone %\"ASAP\"")
+
+ wait_for_requests
+ expect(page).not_to have_content '/milestone'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.milestone).to eq milestone
+ end
+
+ context "when current user cannot set milestone to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set milestone' do
+ add_note('/milestone')
+
+ wait_for_requests
+ expect(page).not_to have_content '/milestone'
+ expect(issuable.reload.milestone).to be_nil
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains milestone quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/milestone %\"ASAP\"")
+
+ expect(page).not_to have_content '/milestone'
+ expect(page).to have_content 'Sets the milestone to %ASAP'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..643ae77516a
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+shared_examples 'relabel quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets relabel quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to eq [label_bug, label_feature]
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(labels: [label_bug])
+ end
+
+ it 'creates the note and interprets the relabel quick action accordingly' do
+ add_note('/relabel ~feature')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_feature])
+ end
+
+ it 'creates the note and interprets the relabel quick action with empty param' do
+ add_note('/relabel')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_bug])
+ end
+
+ context "when current user cannot relabel to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not relabel' do
+ add_note('/relabel ~feature')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(issuable.labels).to match_array([label_bug])
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ before do
+ issuable.update(labels: [label_bug])
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it 'explains relabel all quick action' do
+ preview_note('/relabel ~feature')
+
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Replaces all labels with feature label.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..24f6f8d5bf4
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_estimate quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.time_estimate).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update_attribute(:time_estimate, 36180)
+ end
+
+ it 'creates the note and interprets the remove_estimate quick action accordingly' do
+ add_note("/remove_estimate")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_estimate'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.time_estimate).to eq 0
+ end
+
+ context "when current user cannot remove_estimate" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not remove_estimate' do
+ add_note('/remove_estimate')
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_estimate'
+ expect(issuable.reload.time_estimate).to eq 36180
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_estimate quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/remove_estimate')
+
+ expect(page).not_to have_content '/remove_estimate'
+ expect(page).to have_content 'Removes time estimate.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..edd92d5cdbc
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_milestone quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.milestone).to be_nil
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(milestone: milestone)
+ expect(issuable.milestone).to eq(milestone)
+ end
+
+ it 'creates the note and interprets the remove_milestone quick action accordingly' do
+ add_note("/remove_milestone")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_milestone'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.milestone).to be_nil
+ end
+
+ context "when current user cannot remove milestone to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not remove milestone' do
+ add_note('/remove_milestone')
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_milestone'
+ expect(issuable.reload.milestone).to eq(milestone)
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_milestone quick action' do
+ issuable.update(milestone: milestone)
+ expect(issuable.milestone).to eq(milestone)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/remove_milestone")
+
+ expect(page).not_to have_content '/remove_milestone'
+ expect(page).to have_content 'Removes %ASAP milestone.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..6d5894b2318
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_time_spent quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.total_time_spent).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id })
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the remove_time_spent quick action accordingly' do
+ add_note("/remove_time_spent")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.total_time_spent).to eq 0
+ end
+
+ context "when current user cannot set remove_time_spent time" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set remove_time_spent time' do
+ add_note("/remove_time_spent")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(issuable.reload.total_time_spent).to eq 36180
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_time_spent quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/remove_time_spent')
+
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(page).to have_content 'Removes spent time.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..af173e93bb5
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'reopen quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets reopen quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/reopen"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.close
+ expect(issuable).to be_closed
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the reopen quick action accordingly' do
+ add_note('/reopen')
+
+ wait_for_requests
+ expect(page).not_to have_content '/reopen'
+ expect(page).to have_content 'Commands applied'
+
+ issuable.reload
+ expect(issuable).to be_opened
+ end
+
+ context "when current user cannot reopen #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not reopen the #{issuable_type}" do
+ add_note('/reopen')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable).to be_closed
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains reopen quick action' do
+ issuable.close
+ expect(issuable).to be_closed
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/reopen')
+
+ expect(page).not_to have_content '/reopen'
+ expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..0a526808585
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+shared_examples 'shrug quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets shrug quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯"
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content "bug description\noops ¯\\_(ツ)_/¯"
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets shrug quick action accordingly' do
+ add_note("/shrug oops")
+
+ wait_for_requests
+ expect(page).not_to have_content '/shrug oops'
+ expect(page).to have_content "oops ¯\\_(ツ)_/¯"
+ expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯"
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains shrug quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/shrug oops')
+
+ expect(page).not_to have_content '/shrug'
+ expect(page).to have_content "oops ¯\\_(ツ)_/¯"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..97b4885eba0
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+shared_examples 'spend quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets spend quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.total_time_spent).to eq 36180
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the spend quick action accordingly' do
+ add_note("/spend 1d 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/spend'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.total_time_spent).to eq 36180
+ end
+
+ context "when current user cannot set spend time" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set spend time' do
+ add_note("/spend 1s 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/spend'
+ expect(issuable.reload.total_time_spent).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains spend quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/spend 1d 2h 3m')
+
+ expect(page).not_to have_content '/spend'
+ expect(page).to have_content 'Adds 1d 2h 3m spent time.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..15aefd511a5
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+shared_examples 'subscribe quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/subscribe"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.subscribed?(maintainer, project)).to be_falsy
+ end
+
+ it 'creates the note and interprets the subscribe quick action accordingly' do
+ add_note('/subscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+
+ context "when current user cannot subscribe to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not subscribe to the #{issuable_type}" do
+ add_note('/subscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_falsy
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains subscribe quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/subscribe')
+
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ef831e39872
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+shared_examples 'tableflip quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ â”»â”â”»"
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content "bug description\noops (╯°□°)╯︵ â”»â”â”»"
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets tableflip quick action accordingly' do
+ add_note("/tableflip oops")
+
+ wait_for_requests
+ expect(page).not_to have_content '/tableflip oops'
+ expect(page).to have_content "oops (╯°□°)╯︵ â”»â”â”»"
+ expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ â”»â”â”»"
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains tableflip quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/tableflip oops')
+
+ expect(page).not_to have_content '/tableflip'
+ expect(page).to have_content "oops (╯°□°)╯︵ â”»â”â”»"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ed904c8d539
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+shared_examples 'issuable time tracker' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ after do
+ wait_for_requests
+ end
+
+ it 'renders the sidebar component empty state' do
+ page.within '.time-tracking-no-tracking-pane' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is added' do
+ submit_time('/estimate 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-estimate-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when spent is added' do
+ submit_time('/spend 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-spend-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'shows the comparison when estimate and spent are added' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/spend 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-comparison-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is removed' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/remove_estimate')
+
+ page.within '.time-tracking-component-wrap' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when spent is removed' do
+ submit_time('/spend 3w 1d 1h')
+ submit_time('/remove_time_spent')
+
+ page.within '.time-tracking-component-wrap' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'shows the help state when icon is clicked' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+ expect(page).to have_content 'Track time with quick actions'
+ expect(page).to have_content 'Learn more'
+ end
+ end
+
+ it 'hides the help state when close icon is clicked' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+ find('.close-help-button').click
+
+ expect(page).not_to have_content 'Track time with quick actions'
+ expect(page).not_to have_content 'Learn more'
+ end
+ end
+
+ it 'displays the correct help url' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+
+ expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
+ end
+ end
+end
+
+def submit_time(quick_action)
+ fill_in 'note[note]', with: quick_action
+ find('.js-comment-submit-button').click
+ wait_for_requests
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..93a69093dde
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+shared_examples 'title quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets title quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/title new title"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(issuable.title).to eq 'bug 345'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the title quick action accordingly' do
+ add_note('/title New title')
+
+ wait_for_requests
+ expect(page).not_to have_content '/title new title'
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'New title'
+
+ issuable.reload
+ expect(issuable.title).to eq 'New title'
+ end
+
+ context "when current user cannot set title #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not set title to the #{issuable_type}" do
+ add_note('/title New title')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable.title).not_to eq 'New title'
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains title quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/title New title')
+ wait_for_requests
+
+ expect(page).not_to have_content '/title New title'
+ expect(page).to have_content 'Changes the title to "New title".'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..cccc28127ce
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+shared_examples 'todo quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets todo quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/todo"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the todo quick action accordingly' do
+ add_note('/todo')
+
+ wait_for_requests
+ expect(page).not_to have_content '/todo'
+ expect(page).to have_content 'Commands applied'
+
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+
+ expect(todos.size).to eq 1
+ expect(todo).to be_pending
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+ end
+
+ context "when current user cannot add todo #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not add todo the #{issuable_type}" do
+ add_note('/todo')
+
+ expect(page).not_to have_content 'Commands applied'
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains todo quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/todo')
+
+ expect(page).not_to have_content '/todo'
+ expect(page).to have_content "Adds a todo."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..0b1a52bc860
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+shared_examples 'unassign quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq []
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+
+ it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unassign me"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq []
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the unassign quick action accordingly' do
+ assignee = create(:user, username: 'bob')
+ issuable.update(assignee_ids: [assignee.id])
+ expect(issuable.assignees).to eq [assignee]
+
+ add_note("Awesome!\n\n/unassign @bob")
+
+ expect(page).to have_content 'Awesome!'
+ expect(page).not_to have_content '/unassign @bob'
+
+ wait_for_requests
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'Awesome!'
+ expect(issuable.assignees).to eq []
+ end
+
+ it "unassigns the #{issuable_type} from current user" do
+ issuable.update(assignee_ids: [maintainer.id])
+ expect(issuable.reload.assignees).to eq [maintainer]
+ expect(issuable.assignees).to eq [maintainer]
+
+ add_note("/unassign me")
+
+ expect(page).not_to have_content '/unassign me'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload.assignees).to eq []
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unassign quick action: from bob' do
+ assignee = create(:user, username: 'bob')
+ issuable.update(assignee_ids: [assignee.id])
+ expect(issuable.assignees).to eq [assignee]
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/unassign @bob "
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/unassign @bob'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content 'Removes assignee @bob.'
+ end
+ end
+
+ it 'explains unassign quick action: from me' do
+ issuable.update(assignee_ids: [maintainer.id])
+ expect(issuable.assignees).to eq [maintainer]
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/unassign me"
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/unassign me'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content "Removes assignee @#{maintainer.username}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..1a1ee05841f
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+shared_examples 'unlabel quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to eq [label_bug]
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(labels: [label_bug, label_feature])
+ end
+
+ it 'creates the note and interprets the unlabel all quick action accordingly' do
+ add_note("/unlabel")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to eq []
+ end
+
+ it 'creates the note and interprets the unlabel some quick action accordingly' do
+ add_note("/unlabel ~bug")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_feature])
+ end
+
+ context "when current user cannot unlabel to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not unlabel' do
+ add_note("/unlabel")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ before do
+ issuable.update(labels: [label_bug, label_feature])
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it 'explains unlabel all quick action' do
+ preview_note('/unlabel')
+
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Removes all labels.'
+ end
+
+ it 'explains unlabel some quick action' do
+ preview_note('/unlabel ~bug')
+
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Removes bug label.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..998ff99b32e
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+shared_examples 'unlock quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unlock quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unlock"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update(discussion_locked: true)
+ expect(issuable).to be_discussion_locked
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the unlock quick action accordingly' do
+ add_note('/unlock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlock'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload).not_to be_discussion_locked
+ end
+
+ context "when current user cannot unlock to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not lock the #{issuable_type}" do
+ add_note('/unlock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlock'
+ expect(issuable).to be_discussion_locked
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unlock quick action' do
+ issuable.update(discussion_locked: true)
+ expect(issuable).to be_discussion_locked
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/unlock')
+
+ expect(page).not_to have_content '/unlock'
+ expect(page).to have_content 'Unlocks the discussion'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..bd92f133889
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'unsubscribe quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.subscribe(maintainer, project)
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+
+ it 'creates the note and interprets the unsubscribe quick action accordingly' do
+ add_note('/unsubscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unsubscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_falsey
+ end
+
+ context "when current user cannot unsubscribe to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not unsubscribe to the #{issuable_type}" do
+ add_note('/unsubscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unsubscribe'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unsubscribe quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ issuable.subscribe(maintainer, project)
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+
+ preview_note('/unsubscribe')
+
+ expect(page).not_to have_content '/unsubscribe'
+ expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..6edd20bb024
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'board_move quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..336500487fe
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+shared_examples 'confidential quick action' do
+ context 'when the current user can update issues' do
+ it 'does not create a note, and marks the issue as confidential' do
+ add_note('/confidential')
+
+ expect(page).not_to have_content '/confidential'
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'made the issue confidential'
+
+ expect(issue.reload).to be_confidential
+ end
+ end
+
+ context 'when the current user cannot update the issue' do
+ let(:guest) { create(:user) }
+
+ before do
+ project.add_guest(guest)
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not create a note, and does not mark the issue as confidential' do
+ add_note('/confidential')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content 'made the issue confidential'
+
+ expect(issue.reload).not_to be_confidential
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..34dba5dbc31
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+shared_examples 'create_merge_request quick action' do
+ context 'create a merge request starting from an issue' do
+ def expect_mr_quickaction(success)
+ expect(page).to have_content 'Commands applied'
+
+ if success
+ expect(page).to have_content 'created merge request'
+ else
+ expect(page).not_to have_content 'created merge request'
+ end
+ end
+
+ it "doesn't create a merge request when the branch name is invalid" do
+ add_note("/create_merge_request invalid branch name")
+
+ wait_for_requests
+
+ expect_mr_quickaction(false)
+ end
+
+ it "doesn't create a merge request when a branch with that name already exists" do
+ add_note("/create_merge_request feature")
+
+ wait_for_requests
+
+ expect_mr_quickaction(false)
+ end
+
+ it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
+ add_note("/create_merge_request")
+
+ wait_for_requests
+
+ expect_mr_quickaction(true)
+
+ created_mr = project.merge_requests.last
+ expect(created_mr.source_branch).to eq(issue.to_branch_name)
+
+ visit project_merge_request_path(project, created_mr)
+ expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ end
+
+ it 'creates a merge request using the given branch name' do
+ branch_name = '1-feature'
+ add_note("/create_merge_request #{branch_name}")
+
+ expect_mr_quickaction(true)
+
+ created_mr = project.merge_requests.last
+ expect(created_mr.source_branch).to eq(branch_name)
+
+ visit project_merge_request_path(project, created_mr)
+ expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ae78cd86cd5
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+shared_examples 'due quick action' do
+ context 'due quick action available and date can be added' do
+ it 'sets the due date accordingly' do
+ add_note('/due 2016-08-28')
+
+ expect(page).not_to have_content '/due 2016-08-28'
+ expect(page).to have_content 'Commands applied'
+
+ visit project_issue_path(project, issue)
+
+ page.within '.due_date' do
+ expect(page).to have_content 'Aug 28, 2016'
+ end
+ end
+ end
+
+ context 'due quick action not available' do
+ let(:guest) { create(:user) }
+ before do
+ project.add_guest(guest)
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not set the due date' do
+ add_note('/due 2016-08-28')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content '/due 2016-08-28'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..633c7135fbc
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+shared_examples 'duplicate quick action' do
+ context 'mark issue as duplicate' do
+ let(:original_issue) { create(:issue, project: project) }
+
+ context 'when the current user can update issues' do
+ it 'does not create a note, and marks the issue as a duplicate' do
+ add_note("/duplicate ##{original_issue.to_reference}")
+
+ expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
+
+ expect(issue.reload).to be_closed
+ end
+ end
+
+ context 'when the current user cannot update the issue' do
+ let(:guest) { create(:user) }
+ before do
+ project.add_guest(guest)
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not create a note, and does not mark the issue as a duplicate' do
+ add_note("/duplicate ##{original_issue.to_reference}")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
+
+ expect(issue.reload).to be_open
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..a0b0d888769
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+shared_examples 'move quick action' do
+ context 'move the issue to another project' do
+ let(:target_project) { create(:project, :public) }
+
+ context 'when the project is valid' do
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ it 'moves the issue' do
+ add_note("/move #{target_project.full_path}")
+
+ expect(page).to have_content 'Commands applied'
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).to have_content 'Issues 1'
+ end
+ end
+
+ context 'when the project is valid but the user not authorized' do
+ let(:project_unauthorized) { create(:project, :public) }
+
+ it 'does not move the issue' do
+ add_note("/move #{project_unauthorized.full_path}")
+
+ wait_for_requests
+
+ expect(page).to have_content 'Commands applied'
+ expect(issue.reload).to be_open
+ end
+ end
+
+ context 'when the project is invalid' do
+ it 'does not move the issue' do
+ add_note("/move not/valid")
+
+ wait_for_requests
+
+ expect(page).to have_content 'Commands applied'
+ expect(issue.reload).to be_open
+ end
+ end
+
+ context 'when the user issues multiple commands' do
+ let(:milestone) { create(:milestone, title: '1.0', project: project) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:wontfix) { create(:label, project: project, title: 'wontfix') }
+
+ before do
+ target_project.add_maintainer(user)
+ end
+
+ shared_examples 'applies the commands to issues in both projects, target and source' do
+ it "applies quick actions" do
+ expect(page).to have_content 'Commands applied'
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'wontfix'
+ expect(page).to have_content '1.0'
+
+ visit project_issue_path(project, issue)
+ expect(page).to have_content 'Closed'
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'wontfix'
+ expect(page).to have_content '1.0'
+ end
+ end
+
+ context 'applies multiple commands with move command in the end' do
+ before do
+ add_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}")
+ end
+
+ it_behaves_like 'applies the commands to issues in both projects, target and source'
+ end
+
+ context 'applies multiple commands with move command in the begining' do
+ before do
+ add_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
+ end
+
+ it_behaves_like 'applies the commands to issues in both projects, target and source'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..657c2a60d24
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_due_date quick action' do
+ context 'remove_due_date action available and due date can be removed' do
+ it 'removes the due date accordingly' do
+ add_note('/remove_due_date')
+
+ expect(page).not_to have_content '/remove_due_date'
+ expect(page).to have_content 'Commands applied'
+
+ visit project_issue_path(project, issue)
+
+ page.within '.due_date' do
+ expect(page).to have_content 'None'
+ end
+ end
+ end
+
+ context 'remove_due_date action not available' do
+ let(:guest) { create(:user) }
+ before do
+ project.add_guest(guest)
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not remove the due date' do
+ add_note("/remove_due_date")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content '/remove_due_date'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..c454ddc4bba
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+shared_examples 'merge quick action' do
+ context 'when the current user can merge the MR' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'merges the MR' do
+ add_note("/merge")
+
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload).to be_merged
+ end
+ end
+
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+
+ context 'when the current user cannot merge the MR' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..cf2bdb1dd68
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+shared_examples 'target_branch quick action' do
+ describe '/target_branch command in merge request' do
+ let(:another_project) { create(:project, :public, :repository) }
+ let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
+
+ before do
+ another_project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'changes target_branch in new merge_request' do
+ visit project_new_merge_request_path(another_project, new_url_opts)
+
+ fill_in "merge_request_title", with: 'My brand new feature'
+ fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:"
+ click_button "Submit merge request"
+
+ merge_request = another_project.merge_requests.first
+ expect(merge_request.description).to eq "le feature \nFeature description:"
+ expect(merge_request.target_branch).to eq 'fix'
+ end
+
+ it 'does not change target branch when merge request is edited' do
+ new_merge_request = create(:merge_request, source_project: another_project)
+
+ visit edit_project_merge_request_path(another_project, new_merge_request)
+ fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n"
+ click_button "Save changes"
+
+ new_merge_request = another_project.merge_requests.first
+ expect(new_merge_request.description).to include('/target_branch')
+ expect(new_merge_request.target_branch).not_to eq('fix')
+ end
+ end
+
+ describe '/target_branch command from note' do
+ context 'when the current user can change target branch' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'changes target branch from a note' do
+ add_note("message start \n/target_branch merge-test\n message end.")
+
+ wait_for_requests
+ expect(page).not_to have_content('/target_branch')
+ expect(page).to have_content('message start')
+ expect(page).to have_content('message end.')
+
+ expect(merge_request.reload.target_branch).to eq 'merge-test'
+ end
+
+ it 'does not fail when target branch does not exists' do
+ add_note('/target_branch totally_not_existing_branch')
+
+ expect(page).not_to have_content('/target_branch')
+
+ expect(merge_request.target_branch).to eq 'feature'
+ end
+ end
+
+ context 'when current user can not change target branch' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not change target branch' do
+ add_note('/target_branch merge-test')
+
+ expect(page).not_to have_content '/target_branch merge-test'
+
+ expect(merge_request.target_branch).to eq 'feature'
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..60d6e53e74f
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+shared_examples 'wip quick action' do
+ context 'when the current user can toggle the WIP prefix' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+ end
+
+ it 'adds the WIP: prefix to the title' do
+ add_note('/wip')
+
+ expect(page).not_to have_content '/wip'
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload.work_in_progress?).to eq true
+ end
+
+ it 'removes the WIP: prefix from the title' do
+ merge_request.update!(title: merge_request.wip_title)
+ add_note('/wip')
+
+ expect(page).not_to have_content '/wip'
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload.work_in_progress?).to eq false
+ end
+ end
+
+ context 'when the current user cannot toggle the WIP prefix' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not change the WIP prefix' do
+ add_note('/wip')
+
+ expect(page).not_to have_content '/wip'
+ expect(page).not_to have_content 'Commands applied'
+
+ expect(merge_request.reload.work_in_progress?).to eq false
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index e44da4faa5a..c3132c41f5b 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,4 +1,4 @@
-shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
+shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "returns an array of discussions" do
get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user)
@@ -86,6 +86,37 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(404)
end
end
+
+ context 'when a project is public with private repo access' do
+ let!(:parent) { create(:project, :public, :repository, :repository_private, :snippets_private) }
+ let!(:user_without_access) { create(:user) }
+
+ context 'when user is not a team member of private repo' do
+ before do
+ project.team.truncate
+ end
+
+ context "creating a new note" do
+ before do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user_without_access), params: { body: 'hi!' }
+ end
+
+ it 'raises 404 error' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context "fetching a discussion" do
+ before do
+ get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user_without_access)
+ end
+
+ it 'raises 404 error' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
end
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do
@@ -105,13 +136,25 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(400)
end
- it "returns a 400 bad request error if discussion is individual note" do
- note.update_attribute(:type, nil)
+ context 'when the discussion is an individual note' do
+ before do
+ note.update!(type: nil)
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' }
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
+ "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' }
+ end
- expect(response).to have_gitlab_http_status(400)
+ if can_reply_to_individual_notes
+ it 'creates a new discussion' do
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['type']).to eq('DiscussionNote')
+ end
+ else
+ it 'returns 400 bad request' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/requests/api/issues_shared_examples.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
new file mode 100644
index 00000000000..1133e95e44e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+shared_examples 'labeled issues with labels and label_name params' do
+ shared_examples 'returns label names' do
+ it 'returns label names' do
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
+ end
+ end
+
+ shared_examples 'returns basic label entity' do
+ it 'returns basic label entity' do
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels'].pluck('name')).to eq([label_c.title, label_b.title, label.title])
+ expect(json_response.first['labels'].first).to match_schema('/public_api/v4/label_basic')
+ end
+ end
+
+ context 'array of labeled issues when all labels match' do
+ let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}" } }
+
+ it_behaves_like 'returns label names'
+ end
+
+ context 'array of labeled issues when all labels match with labels param as array' do
+ let(:params) { { labels: [label.title, label_b.title, label_c.title] } }
+
+ it_behaves_like 'returns label names'
+ end
+
+ context 'when with_labels_details provided' do
+ context 'array of labeled issues when all labels match' do
+ let(:params) { { labels: "#{label.title},#{label_b.title},#{label_c.title}", with_labels_details: true } }
+
+ it_behaves_like 'returns basic label entity'
+ end
+
+ context 'array of labeled issues when all labels match with labels param as array' do
+ let(:params) { { labels: [label.title, label_b.title, label_c.title], with_labels_details: true } }
+
+ it_behaves_like 'returns basic label entity'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
deleted file mode 100644
index 6713ec47ace..00000000000
--- a/spec/support/shared_examples/requests/api/merge_requests_list.rb
+++ /dev/null
@@ -1,335 +0,0 @@
-shared_examples 'merge requests list' do
- context 'when unauthenticated' do
- it 'returns merge requests for public projects' do
- get api(endpoint_path)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- end
- end
-
- context 'when authenticated' do
- it 'avoids N+1 queries' do
- control = ActiveRecord::QueryRecorder.new do
- get api(endpoint_path, user)
- end
-
- create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
-
- merge_request = create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
-
- merge_request.metrics.update!(merged_by: user,
- latest_closed_by: user,
- latest_closed_at: 1.hour.ago,
- merged_at: 2.hours.ago)
-
- expect do
- get api(endpoint_path, user)
- end.not_to exceed_query_limit(control)
- end
-
- it 'returns an array of all merge_requests' do
- get api(endpoint_path, user)
-
- 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(4)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
- expect(json_response.last['merge_commit_sha']).to be_nil
- expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
- expect(json_response.last['downvotes']).to eq(1)
- expect(json_response.last['upvotes']).to eq(1)
- expect(json_response.last['labels']).to eq([label2.title, label.title])
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
- expect(json_response.first['merge_commit_sha']).not_to be_nil
- expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
- end
-
- it 'returns an array of all merge_requests using simple mode' do
- path = endpoint_path + '?view=simple'
-
- get api(path, user)
-
- 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
- expect(json_response.length).to eq(4)
- expect(json_response.last['iid']).to eq(merge_request.iid)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.first['iid']).to eq(merge_request_merged.iid)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first).to have_key('web_url')
- end
-
- it 'returns an array of all merge_requests' do
- path = endpoint_path + '?state'
-
- get api(path, user)
-
- 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(4)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
-
- it 'returns an array of open merge_requests' do
- path = endpoint_path + '?state=opened'
-
- get api(path, user)
-
- 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)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
-
- it 'returns an array of closed merge_requests' do
- path = endpoint_path + '?state=closed'
-
- get api(path, user)
-
- 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)
- expect(json_response.first['title']).to eq(merge_request_closed.title)
- end
-
- it 'returns an array of merged merge_requests' do
- path = endpoint_path + '?state=merged'
-
- get api(path, user)
-
- 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)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- end
-
- it 'matches V4 response schema' do
- get api(endpoint_path, user)
-
- 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(endpoint_path, user), params: { milestone: '1.0.0' }
-
- 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 empty array if milestone does not exist' do
- get api(endpoint_path, user), params: { milestone: 'foo' }
-
- 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 merge requests in given milestone' do
- get api(endpoint_path, user), params: { milestone: '0.9' }
-
- closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id }
- expect(closed_issues.length).to eq(1)
- expect(closed_issues.first['title']).to eq merge_request_closed.title
- end
-
- it 'returns an array of merge requests matching state in milestone' do
- get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
-
- 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)
- end
-
- it 'returns an array of labeled merge requests' do
- path = endpoint_path + "?labels=#{label.title}"
-
- get api(path, 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['labels']).to eq([label2.title, label.title])
- end
-
- it 'returns an array of labeled merge requests where all labels match' do
- path = endpoint_path + "?labels=#{label.title},foo,bar"
-
- get api(path, user)
-
- 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 empty array if no merge request matches labels' do
- path = endpoint_path + '?labels=foo,bar'
-
- get api(path, user)
-
- 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 merge requests with any label when filtering by any label' do
- get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
-
- expect_paginated_array_response
- expect(json_response.length).to eq(1)
- expect(json_response.first['id']).to eq(merge_request.id)
- end
-
- it 'returns an array of merge requests without a label when filtering by no label' do
- get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect_paginated_array_response
- expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
- 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)
-
- path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged"
-
- get api(path, 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
- @mr_earlier = mr_with_earlier_created_and_updated_at_time
- end
-
- it 'returns an array of merge_requests in ascending order' do
- path = endpoint_path + '?sort=asc'
-
- get api(path, user)
-
- 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(4)
- 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
- path = endpoint_path + '?sort=desc'
-
- get api(path, user)
-
- 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(4)
- response_dates = json_response.map { |merge_request| merge_request['created_at'] }
- expect(response_dates).to eq(response_dates.sort.reverse)
- end
-
- context '2 merge requests with equal created_at' do
- let!(:closed_mr2) do
- create :merge_request,
- state: 'closed',
- milestone: milestone1,
- author: user,
- assignee: user,
- source_project: project,
- target_project: project,
- title: "Test",
- created_at: @mr_earlier.created_at
- end
-
- it 'page breaks first page correctly' do
- get api("#{endpoint_path}?sort=desc&per_page=4", user)
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect(response_ids).to include(closed_mr2.id)
- expect(response_ids).not_to include(@mr_earlier.id)
- end
-
- it 'page breaks second page correctly' do
- get api("#{endpoint_path}?sort=desc&per_page=4&page=2", user)
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect(response_ids).not_to include(closed_mr2.id)
- expect(response_ids).to include(@mr_earlier.id)
- end
- end
-
- it 'returns an array of merge_requests ordered by updated_at' do
- path = endpoint_path + '?order_by=updated_at'
-
- get api(path, user)
-
- 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(4)
- 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
- path = endpoint_path + '?order_by=created_at&sort=asc'
-
- get api(path, user)
-
- 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(4)
- response_dates = json_response.map { |merge_request| merge_request['created_at'] }
- expect(response_dates).to eq(response_dates.sort)
- end
- end
-
- context 'source_branch param' do
- it 'returns merge requests with the given source branch' do
- get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
-
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
- end
- end
-
- context 'target_branch param' do
- it 'returns merge requests with the given target branch' do
- get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
-
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/services/base_helm_service_shared_examples.rb b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
new file mode 100644
index 00000000000..fa76b95f768
--- /dev/null
+++ b/spec/support/shared_examples/services/base_helm_service_shared_examples.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+shared_examples 'logs kubernetes errors' do
+ let(:error_hash) do
+ {
+ service: service.class.name,
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: error_code
+ }
+ end
+
+ let(:logger_hash) do
+ error_hash.merge(
+ exception: error_name,
+ message: error_message,
+ backtrace: instance_of(Array)
+ )
+ end
+
+ it 'logs into kubernetes.log and Sentry' do
+ expect(service.send(:logger)).to receive(:error).with(hash_including(logger_hash))
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: hash_including(error_hash)
+ )
+
+ service.execute
+ end
+end
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
index 14638a574a5..02de47a96dd 100644
--- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -12,6 +12,14 @@ shared_examples 'check ingress ip executions' do |app_name|
end
end
+ context 'when the ingress external hostname is available' do
+ it 'updates the external_hostname for the app' do
+ subject
+
+ expect(application.external_hostname).to eq('localhost.localdomain')
+ end
+ end
+
context 'when the ingress ip address is not available' do
let(:ingress) { nil }
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 940c24c8d67..36c486dbdd6 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -106,6 +106,14 @@ RSpec.shared_examples 'slack or mattermost notifications' do
expect(WebMock).to have_requested(:post, webhook_url).once
end
+ it "calls Slack/Mattermost API for deployment events" do
+ deployment_event_data = { object_kind: 'deployment' }
+
+ chat_service.execute(deployment_event_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
it 'uses the username as an option for slack when configured' do
allow(chat_service).to receive(:username).and_return(username)
@@ -267,7 +275,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'does not notify push events if they are not for the default branch' do
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
@@ -284,7 +292,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'still notifies about pushed tags' do
ref = "#{Gitlab::Git::TAG_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
@@ -299,7 +307,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'notifies about all push events' do
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
diff --git a/spec/support/shared_examples/snippet_visibility.rb b/spec/support/shared_examples/snippet_visibility.rb
deleted file mode 100644
index 3a7c69b7877..00000000000
--- a/spec/support/shared_examples/snippet_visibility.rb
+++ /dev/null
@@ -1,322 +0,0 @@
-RSpec.shared_examples 'snippet visibility' do
- let!(:author) { create(:user) }
- let!(:member) { create(:user) }
- let!(:external) { create(:user, :external) }
-
- let!(:snippet_type_visibilities) do
- {
- public: Snippet::PUBLIC,
- internal: Snippet::INTERNAL,
- private: Snippet::PRIVATE
- }
- end
-
- context "For project snippets" do
- let!(:users) do
- {
- unauthenticated: nil,
- external: external,
- non_member: create(:user),
- member: member,
- author: author
- }
- end
-
- let!(:project_type_visibilities) do
- {
- public: Gitlab::VisibilityLevel::PUBLIC,
- internal: Gitlab::VisibilityLevel::INTERNAL,
- private: Gitlab::VisibilityLevel::PRIVATE
- }
- end
-
- let(:project_feature_visibilities) do
- {
- enabled: ProjectFeature::ENABLED,
- private: ProjectFeature::PRIVATE,
- disabled: ProjectFeature::DISABLED
- }
- end
-
- where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
- [
- # Public projects
- [:public, :enabled, :unauthenticated, :public, true],
- [:public, :enabled, :unauthenticated, :internal, false],
- [:public, :enabled, :unauthenticated, :private, false],
-
- [:public, :enabled, :external, :public, true],
- [:public, :enabled, :external, :internal, false],
- [:public, :enabled, :external, :private, false],
-
- [:public, :enabled, :non_member, :public, true],
- [:public, :enabled, :non_member, :internal, true],
- [:public, :enabled, :non_member, :private, false],
-
- [:public, :enabled, :member, :public, true],
- [:public, :enabled, :member, :internal, true],
- [:public, :enabled, :member, :private, true],
-
- [:public, :enabled, :author, :public, true],
- [:public, :enabled, :author, :internal, true],
- [:public, :enabled, :author, :private, true],
-
- [:public, :private, :unauthenticated, :public, false],
- [:public, :private, :unauthenticated, :internal, false],
- [:public, :private, :unauthenticated, :private, false],
-
- [:public, :private, :external, :public, false],
- [:public, :private, :external, :internal, false],
- [:public, :private, :external, :private, false],
-
- [:public, :private, :non_member, :public, false],
- [:public, :private, :non_member, :internal, false],
- [:public, :private, :non_member, :private, false],
-
- [:public, :private, :member, :public, true],
- [:public, :private, :member, :internal, true],
- [:public, :private, :member, :private, true],
-
- [:public, :private, :author, :public, true],
- [:public, :private, :author, :internal, true],
- [:public, :private, :author, :private, true],
-
- [:public, :disabled, :unauthenticated, :public, false],
- [:public, :disabled, :unauthenticated, :internal, false],
- [:public, :disabled, :unauthenticated, :private, false],
-
- [:public, :disabled, :external, :public, false],
- [:public, :disabled, :external, :internal, false],
- [:public, :disabled, :external, :private, false],
-
- [:public, :disabled, :non_member, :public, false],
- [:public, :disabled, :non_member, :internal, false],
- [:public, :disabled, :non_member, :private, false],
-
- [:public, :disabled, :member, :public, false],
- [:public, :disabled, :member, :internal, false],
- [:public, :disabled, :member, :private, false],
-
- [:public, :disabled, :author, :public, false],
- [:public, :disabled, :author, :internal, false],
- [:public, :disabled, :author, :private, false],
-
- # Internal projects
- [:internal, :enabled, :unauthenticated, :public, false],
- [:internal, :enabled, :unauthenticated, :internal, false],
- [:internal, :enabled, :unauthenticated, :private, false],
-
- [:internal, :enabled, :external, :public, false],
- [:internal, :enabled, :external, :internal, false],
- [:internal, :enabled, :external, :private, false],
-
- [:internal, :enabled, :non_member, :public, true],
- [:internal, :enabled, :non_member, :internal, true],
- [:internal, :enabled, :non_member, :private, false],
-
- [:internal, :enabled, :member, :public, true],
- [:internal, :enabled, :member, :internal, true],
- [:internal, :enabled, :member, :private, true],
-
- [:internal, :enabled, :author, :public, true],
- [:internal, :enabled, :author, :internal, true],
- [:internal, :enabled, :author, :private, true],
-
- [:internal, :private, :unauthenticated, :public, false],
- [:internal, :private, :unauthenticated, :internal, false],
- [:internal, :private, :unauthenticated, :private, false],
-
- [:internal, :private, :external, :public, false],
- [:internal, :private, :external, :internal, false],
- [:internal, :private, :external, :private, false],
-
- [:internal, :private, :non_member, :public, false],
- [:internal, :private, :non_member, :internal, false],
- [:internal, :private, :non_member, :private, false],
-
- [:internal, :private, :member, :public, true],
- [:internal, :private, :member, :internal, true],
- [:internal, :private, :member, :private, true],
-
- [:internal, :private, :author, :public, true],
- [:internal, :private, :author, :internal, true],
- [:internal, :private, :author, :private, true],
-
- [:internal, :disabled, :unauthenticated, :public, false],
- [:internal, :disabled, :unauthenticated, :internal, false],
- [:internal, :disabled, :unauthenticated, :private, false],
-
- [:internal, :disabled, :external, :public, false],
- [:internal, :disabled, :external, :internal, false],
- [:internal, :disabled, :external, :private, false],
-
- [:internal, :disabled, :non_member, :public, false],
- [:internal, :disabled, :non_member, :internal, false],
- [:internal, :disabled, :non_member, :private, false],
-
- [:internal, :disabled, :member, :public, false],
- [:internal, :disabled, :member, :internal, false],
- [:internal, :disabled, :member, :private, false],
-
- [:internal, :disabled, :author, :public, false],
- [:internal, :disabled, :author, :internal, false],
- [:internal, :disabled, :author, :private, false],
-
- # Private projects
- [:private, :enabled, :unauthenticated, :public, false],
- [:private, :enabled, :unauthenticated, :internal, false],
- [:private, :enabled, :unauthenticated, :private, false],
-
- [:private, :enabled, :external, :public, true],
- [:private, :enabled, :external, :internal, true],
- [:private, :enabled, :external, :private, true],
-
- [:private, :enabled, :non_member, :public, false],
- [:private, :enabled, :non_member, :internal, false],
- [:private, :enabled, :non_member, :private, false],
-
- [:private, :enabled, :member, :public, true],
- [:private, :enabled, :member, :internal, true],
- [:private, :enabled, :member, :private, true],
-
- [:private, :enabled, :author, :public, true],
- [:private, :enabled, :author, :internal, true],
- [:private, :enabled, :author, :private, true],
-
- [:private, :private, :unauthenticated, :public, false],
- [:private, :private, :unauthenticated, :internal, false],
- [:private, :private, :unauthenticated, :private, false],
-
- [:private, :private, :external, :public, true],
- [:private, :private, :external, :internal, true],
- [:private, :private, :external, :private, true],
-
- [:private, :private, :non_member, :public, false],
- [:private, :private, :non_member, :internal, false],
- [:private, :private, :non_member, :private, false],
-
- [:private, :private, :member, :public, true],
- [:private, :private, :member, :internal, true],
- [:private, :private, :member, :private, true],
-
- [:private, :private, :author, :public, true],
- [:private, :private, :author, :internal, true],
- [:private, :private, :author, :private, true],
-
- [:private, :disabled, :unauthenticated, :public, false],
- [:private, :disabled, :unauthenticated, :internal, false],
- [:private, :disabled, :unauthenticated, :private, false],
-
- [:private, :disabled, :external, :public, false],
- [:private, :disabled, :external, :internal, false],
- [:private, :disabled, :external, :private, false],
-
- [:private, :disabled, :non_member, :public, false],
- [:private, :disabled, :non_member, :internal, false],
- [:private, :disabled, :non_member, :private, false],
-
- [:private, :disabled, :member, :public, false],
- [:private, :disabled, :member, :internal, false],
- [:private, :disabled, :member, :private, false],
-
- [:private, :disabled, :author, :public, false],
- [:private, :disabled, :author, :internal, false],
- [:private, :disabled, :author, :private, false]
- ]
- end
-
- with_them do
- let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) }
- let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) }
- let!(:user) { users[user_type] }
- let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) }
- let!(:members) do
- project.add_developer(author)
- project.add_developer(member)
- project.add_developer(external) if project.private?
- end
-
- context "For #{params[:project_type]} project and #{params[:user_type]} users" do
- it 'should agree with the read_project_snippet policy' do
- expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
- end
-
- it 'should return proper outcome' do
- results = described_class.new(user, project: project).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
- end
-
- context "Without a given project and #{params[:user_type]} users" do
- it 'should return proper outcome' do
- results = described_class.new(user).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
-
- it 'returns no snippets when the user cannot read cross project' do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
-
- snippets = described_class.new(user).execute
-
- expect(snippets).to be_empty
- end
- end
- end
- end
-
- context 'For personal snippets' do
- let!(:users) do
- {
- unauthenticated: nil,
- external: external,
- non_member: create(:user),
- author: author
- }
- end
-
- where(:snippet_visibility, :user_type, :outcome) do
- [
- [:public, :unauthenticated, true],
- [:public, :external, true],
- [:public, :non_member, true],
- [:public, :author, true],
-
- [:internal, :unauthenticated, false],
- [:internal, :external, false],
- [:internal, :non_member, true],
- [:internal, :author, true],
-
- [:private, :unauthenticated, false],
- [:private, :external, false],
- [:private, :non_member, false],
- [:private, :author, true]
- ]
- end
-
- with_them do
- let!(:user) { users[user_type] }
- let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) }
-
- context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
- it 'should agree with read_personal_snippet policy' do
- expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
- end
-
- it 'should return proper outcome' do
- results = described_class.new(user).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
-
- it 'should return personal snippets when the user cannot read cross project' do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
-
- results = described_class.new(user).execute
-
- expect(results.include?(snippet)).to eq(outcome)
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
new file mode 100644
index 00000000000..833c31a57cb
--- /dev/null
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -0,0 +1,306 @@
+RSpec.shared_examples 'snippet visibility' do
+ using RSpec::Parameterized::TableSyntax
+
+ # Make sure no snippets exist prior to running the test matrix
+ before(:context) do
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
+ set(:author) { create(:user) }
+ set(:member) { create(:user) }
+ set(:external) { create(:user, :external) }
+
+ context "For project snippets" do
+ let!(:users) do
+ {
+ unauthenticated: nil,
+ external: external,
+ non_member: create(:user),
+ member: member,
+ author: author
+ }
+ end
+
+ where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
+ [
+ # Public projects
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false],
+
+ # Internal projects
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false],
+
+ # Private projects
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false]
+ ]
+ end
+
+ with_them do
+ let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel.level_value(project_type.to_s)) }
+ let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, feature_visibility) }
+ let!(:user) { users[user_type] }
+ let!(:snippet) { create(:project_snippet, visibility_level: snippet_type, project: project, author: author) }
+ let!(:members) do
+ project.add_developer(author)
+ project.add_developer(member)
+ project.add_developer(external) if project.private?
+ end
+
+ context "For #{params[:project_type]} project and #{params[:user_type]} users" do
+ it 'agrees with the read_project_snippet policy' do
+ expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
+ end
+
+ it 'returns proper outcome' do
+ results = described_class.new(user, project: project).execute
+
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+ end
+
+ context "Without a given project and #{params[:user_type]} users" do
+ it 'returns proper outcome' do
+ results = described_class.new(user).execute
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+
+ it 'returns no snippets when the user cannot read cross project' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+
+ snippets = described_class.new(user).execute
+
+ expect(snippets).to be_empty
+ end
+ end
+ end
+ end
+
+ context 'For personal snippets' do
+ let!(:users) do
+ {
+ unauthenticated: nil,
+ external: external,
+ non_member: create(:user),
+ author: author
+ }
+ end
+
+ where(:snippet_visibility, :user_type, :outcome) do
+ [
+ [Snippet::PUBLIC, :unauthenticated, true],
+ [Snippet::PUBLIC, :external, true],
+ [Snippet::PUBLIC, :non_member, true],
+ [Snippet::PUBLIC, :author, true],
+
+ [Snippet::INTERNAL, :unauthenticated, false],
+ [Snippet::INTERNAL, :external, false],
+ [Snippet::INTERNAL, :non_member, true],
+ [Snippet::INTERNAL, :author, true],
+
+ [Snippet::PRIVATE, :unauthenticated, false],
+ [Snippet::PRIVATE, :external, false],
+ [Snippet::PRIVATE, :non_member, false],
+ [Snippet::PRIVATE, :author, true]
+ ]
+ end
+
+ with_them do
+ let!(:user) { users[user_type] }
+ let!(:snippet) { create(:personal_snippet, visibility_level: snippet_visibility, author: author) }
+
+ context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
+ it 'agrees with read_personal_snippet policy' do
+ expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
+ end
+
+ it 'returns proper outcome' do
+ results = described_class.new(user).execute
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+
+ it 'returns personal snippets when the user cannot read cross project' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+
+ results = described_class.new(user).execute
+
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/time_tracking_shared_examples.rb b/spec/support/shared_examples/time_tracking_shared_examples.rb
deleted file mode 100644
index 909d4e2ee8d..00000000000
--- a/spec/support/shared_examples/time_tracking_shared_examples.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-shared_examples 'issuable time tracker' do
- it 'renders the sidebar component empty state' do
- page.within '.time-tracking-no-tracking-pane' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'updates the sidebar component when estimate is added' do
- submit_time('/estimate 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-estimate-only-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'updates the sidebar component when spent is added' do
- submit_time('/spend 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-spend-only-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'shows the comparison when estimate and spent are added' do
- submit_time('/estimate 3w 1d 1h')
- submit_time('/spend 3w 1d 1h')
-
- wait_for_requests
- page.within '.time-tracking-comparison-pane' do
- expect(page).to have_content '3w 1d 1h'
- end
- end
-
- it 'updates the sidebar component when estimate is removed' do
- submit_time('/estimate 3w 1d 1h')
- submit_time('/remove_estimate')
-
- page.within '.time-tracking-component-wrap' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'updates the sidebar component when spent is removed' do
- submit_time('/spend 3w 1d 1h')
- submit_time('/remove_time_spent')
-
- page.within '.time-tracking-component-wrap' do
- expect(page).to have_content 'No estimate or time spent'
- end
- end
-
- it 'shows the help state when icon is clicked' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
- expect(page).to have_content 'Track time with quick actions'
- expect(page).to have_content 'Learn more'
- end
- end
-
- it 'hides the help state when close icon is clicked' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
- find('.close-help-button').click
-
- expect(page).not_to have_content 'Track time with quick actions'
- expect(page).not_to have_content 'Learn more'
- end
- end
-
- it 'displays the correct help url' do
- page.within '.time-tracking-component-wrap' do
- find('.help-button').click
-
- expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
- end
- end
-end
-
-def submit_time(quick_action)
- fill_in 'note[note]', with: quick_action
- find('.js-comment-submit-button').click
- wait_for_requests
-end
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index 1f7e2f7ff79..25277ccd9aa 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,15 +1,15 @@
-RSpec.shared_examples 'url validator examples' do |protocols|
+RSpec.shared_examples 'url validator examples' do |schemes|
let(:validator) { described_class.new(attributes: [:link_url], **options) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
+ subject { validator.validate(badge) }
- describe '#validates_each' do
+ describe '#validate' do
context 'with no options' do
let(:options) { {} }
- it "allows #{protocols.join(',')} protocols by default" do
- expect(validator.send(:default_options)[:protocols]).to eq protocols
+ it "allows #{schemes.join(',')} schemes by default" do
+ expect(validator.options[:schemes]).to eq schemes
end
it 'checks that the url structure is valid' do
@@ -17,25 +17,25 @@ RSpec.shared_examples 'url validator examples' do |protocols|
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
- context 'with protocols' do
- let(:options) { { protocols: %w[http] } }
+ context 'with schemes' do
+ let(:options) { { schemes: %w(http) } }
- it 'allows urls with the defined protocols' do
+ it 'allows urls with the defined schemes' do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
- it 'add error if the url protocol does not match the selected ones' do
+ it 'add error if the url scheme does not match the selected ones' do
badge.link_url = 'https://www.example.com'
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
end
diff --git a/spec/support/shared_examples/views/nav_sidebar.rb b/spec/support/shared_examples/views/nav_sidebar.rb
new file mode 100644
index 00000000000..6ac5abe275d
--- /dev/null
+++ b/spec/support/shared_examples/views/nav_sidebar.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+shared_examples 'has nav sidebar' do
+ it 'has collapsed nav sidebar on mobile' do
+ render
+
+ expect(rendered).to have_selector('.nav-sidebar')
+ expect(rendered).not_to have_selector('.sidebar-collapsed-desktop')
+ expect(rendered).not_to have_selector('.sidebar-expanded-mobile')
+ end
+end
diff --git a/spec/support/shared_examples/wiki_file_attachments_examples.rb b/spec/support/shared_examples/wiki_file_attachments_examples.rb
index b6fb2a66b0e..22fbfb48928 100644
--- a/spec/support/shared_examples/wiki_file_attachments_examples.rb
+++ b/spec/support/shared_examples/wiki_file_attachments_examples.rb
@@ -42,7 +42,7 @@ shared_examples 'wiki file attachments' do
end
end
- context 'uploading is complete' do
+ context 'uploading is complete', :quarantine do
it 'shows "Attach a file" button on uploading complete' do
attach_with_dropzone
wait_for_requests
diff --git a/spec/support/shoulda/matchers/rails_shim.rb b/spec/support/shoulda/matchers/rails_shim.rb
deleted file mode 100644
index 8d70598beb5..00000000000
--- a/spec/support/shoulda/matchers/rails_shim.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# monkey patch which fixes serialization matcher in Rails 5
-# https://github.com/thoughtbot/shoulda-matchers/issues/913
-# This can be removed when a new version of shoulda-matchers
-# is released
-module Shoulda
- module Matchers
- class RailsShim
- def self.serialized_attributes_for(model)
- if defined?(::ActiveRecord::Type::Serialized)
- # Rails 5+
- serialized_columns = model.columns.select do |column|
- model.type_for_attribute(column.name).is_a?(
- ::ActiveRecord::Type::Serialized
- )
- end
-
- serialized_columns.inject({}) do |hash, column| # rubocop:disable Style/EachWithObject
- hash[column.name.to_s] = model.type_for_attribute(column.name).coder
- hash
- end
- else
- model.serialized_attributes
- end
- end
- end
- end
-end
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index af2906b7568..9ac7e7fc515 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -1,4 +1,12 @@
require 'webmock'
require 'webmock/rspec'
-WebMock.disable_net_connect!(allow_localhost: true)
+def webmock_allowed_hosts
+ %w[elasticsearch registry.gitlab.com-gitlab-org-test-elastic-image].tap do |hosts|
+ if ENV.key?('ELASTIC_URL')
+ hosts << URI.parse(ENV['ELASTIC_URL']).host
+ end
+ end.uniq
+end
+
+WebMock.disable_net_connect!(allow_localhost: true, allow: webmock_allowed_hosts)
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
index 8544fb62b5a..be69c10d7c8 100644
--- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -13,61 +13,6 @@ describe 'gitlab:artifacts namespace rake task' do
subject { run_rake_task('gitlab:artifacts:migrate') }
- context 'legacy artifacts' do
- describe 'migrate' do
- let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) }
-
- context 'when local storage is used' do
- let(:store) { ObjectStorage::Store::LOCAL }
-
- context 'and job does not have file store defined' do
- let(:object_storage_enabled) { true }
- let(:store) { nil }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is defined' do
- let(:object_storage_enabled) { true }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is not defined' do
- it "fails to migrate to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL)
- end
- end
- end
-
- context 'when remote storage is used' do
- let(:object_storage_enabled) { true }
-
- let(:store) { ObjectStorage::Store::REMOTE }
-
- it "file stays on remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
- end
- end
-
context 'job artifacts' do
let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) }
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index a8fae4a88a3..bdbd39475b9 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -21,9 +21,6 @@ describe 'gitlab:app namespace rake task' do
# empty task as env is already loaded
Rake::Task.define_task :environment
-
- # We need this directory to run `gitlab:backup:create` task
- FileUtils.mkdir_p('public/uploads')
end
before do
@@ -38,6 +35,7 @@ describe 'gitlab:app namespace rake task' do
end
def run_rake_task(task_name)
+ FileUtils.mkdir_p('tmp/tests/public/uploads')
Rake::Task[task_name].reenable
Rake.application.invoke_task task_name
end
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 0ed5d3e27b9..c3e912b02c5 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -7,14 +7,8 @@ describe 'gitlab:shell rake tasks' do
stub_warn_user_is_not_gitlab
end
- after do
- TestEnv.create_fake_git_hooks
- end
-
describe 'install task' do
- it 'invokes create_hooks task' do
- expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
-
+ it 'installs and compiles gitlab-shell' do
storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
end
@@ -24,14 +18,4 @@ describe 'gitlab:shell rake tasks' do
run_rake_task('gitlab:shell:install')
end
end
-
- 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",
- *Gitlab::TaskHelpers.repository_storage_paths_args)
-
- run_rake_task('gitlab:shell:create_hooks')
- end
- end
end
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 6b50670c3c0..4b04d9cec39 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -1,6 +1,6 @@
require 'rake_helper'
-describe 'rake gitlab:storage:*' do
+describe 'rake gitlab:storage:*', :sidekiq do
before do
Rake.application.rake_require 'tasks/gitlab/storage'
@@ -43,9 +43,7 @@ describe 'rake gitlab:storage:*' do
end
end
- describe 'gitlab:storage:migrate_to_hashed' do
- let(:task) { 'gitlab:storage:migrate_to_hashed' }
-
+ shared_examples "make sure database is writable" do
context 'read-only database' do
it 'does nothing' do
expect(Gitlab::Database).to receive(:read_only?).and_return(true)
@@ -55,48 +53,68 @@ describe 'rake gitlab:storage:*' do
expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr
end
end
+ end
- context '0 legacy projects' do
- it 'does nothing' do
- expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
+ shared_examples "handles custom BATCH env var" do |worker_klass|
+ context 'in batches of 1' do
+ before do
+ stub_env('BATCH' => 1)
+ end
+
+ it "enqueues one #{worker_klass} per project" do
+ projects.each do |project|
+ expect(worker_klass).to receive(:perform_async).with(project.id, project.id)
+ end
run_rake_task(task)
end
end
- context '3 legacy projects' do
- let(:projects) { create_list(:project, 3, :legacy_storage) }
+ context 'in batches of 2' do
+ before do
+ stub_env('BATCH' => 2)
+ end
- context 'in batches of 1' do
- before do
- stub_env('BATCH' => 1)
+ it "enqueues one #{worker_klass} per 2 projects" do
+ projects.map(&:id).sort.each_slice(2) do |first, last|
+ last ||= first
+ expect(worker_klass).to receive(:perform_async).with(first, last)
end
- it 'enqueues one HashedStorage::MigratorWorker per project' do
- projects.each do |project|
- expect(::HashedStorage::MigratorWorker).to receive(:perform_async).with(project.id, project.id)
- end
-
- run_rake_task(task)
- end
+ run_rake_task(task)
end
+ end
+ end
- context 'in batches of 2' do
- before do
- stub_env('BATCH' => 2)
- end
+ describe 'gitlab:storage:migrate_to_hashed' do
+ let(:task) { 'gitlab:storage:migrate_to_hashed' }
- it 'enqueues one HashedStorage::MigratorWorker per 2 projects' do
- projects.map(&:id).sort.each_slice(2) do |first, last|
- last ||= first
- expect(::HashedStorage::MigratorWorker).to receive(:perform_async).with(first, last)
- end
+ context 'with rollback already scheduled', :redis do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::RollbackerWorker.perform_async(1, 5)
+
+ expect(Project).not_to receive(:with_unmigrated_storage)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to output(/There is already a rollback operation in progress/).to_stderr
end
end
end
+ context 'with 0 legacy projects' do
+ it 'does nothing' do
+ expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
+
+ run_rake_task(task)
+ end
+ end
+
+ context 'with 3 legacy projects' do
+ let(:projects) { create_list(:project, 3, :legacy_storage) }
+
+ it_behaves_like "handles custom BATCH env var", ::HashedStorage::MigratorWorker
+ end
+
context 'with same id in range' do
it 'displays message when project cant be found' do
stub_env('ID_FROM', 99999)
@@ -123,6 +141,38 @@ describe 'rake gitlab:storage:*' do
end
end
+ describe 'gitlab:storage:rollback_to_legacy' do
+ let(:task) { 'gitlab:storage:rollback_to_legacy' }
+
+ it_behaves_like 'make sure database is writable'
+
+ context 'with migration already scheduled', :redis do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::MigratorWorker.perform_async(1, 5)
+
+ expect(Project).not_to receive(:with_unmigrated_storage)
+
+ expect { run_rake_task(task) }.to output(/There is already a migration operation in progress/).to_stderr
+ end
+ end
+ end
+
+ context 'with 0 hashed projects' do
+ it 'does nothing' do
+ expect(::HashedStorage::RollbackerWorker).not_to receive(:perform_async)
+
+ run_rake_task(task)
+ end
+ end
+
+ context 'with 3 hashed projects' do
+ let(:projects) { create_list(:project, 3) }
+
+ it_behaves_like "handles custom BATCH env var", ::HashedStorage::RollbackerWorker
+ end
+ end
+
describe 'gitlab:storage:legacy_projects' do
it_behaves_like 'rake entities summary', 'projects', 'Legacy' do
let(:task) { 'gitlab:storage:legacy_projects' }
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 555a58e9aa1..4188e7caccb 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -8,13 +8,13 @@ describe 'tokens rake tasks' do
end
describe 'reset_all_email task' do
- it 'invokes create_hooks task' do
+ it 'changes the incoming email token' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end
end
describe 'reset_all_feed task' do
- it 'invokes create_hooks task' do
+ it 'changes the feed token for the user' do
expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end
end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index de29d0c943f..e474a714b10 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe FileMover do
+ include FileMoverHelpers
+
let(:filename) { 'banana_sample.gif' }
- let(:file) { fixture_file_upload(File.join('spec', 'fixtures', filename)) }
let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) }
let(:temp_description) do
@@ -12,7 +13,7 @@ describe FileMover do
let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) }
let(:snippet) { create(:personal_snippet, description: temp_description) }
- subject { described_class.new(file_path, snippet).execute }
+ subject { described_class.new(temp_file_path, snippet).execute }
describe '#execute' do
before do
@@ -20,6 +21,8 @@ describe FileMover do
expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true)
allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10)
+
+ stub_file_mover(temp_file_path)
end
context 'when move and field update successful' do
@@ -66,4 +69,30 @@ describe FileMover do
end
end
end
+
+ context 'security' do
+ context 'when relative path is involved' do
+ let(:temp_file_path) { File.join('uploads/-/system/temp', '..', 'another_subdir_of_temp') }
+
+ it 'does not trigger move if path is outside designated directory' do
+ stub_file_mover('uploads/-/system/another_subdir_of_temp')
+ expect(FileUtils).not_to receive(:move)
+
+ subject
+
+ expect(snippet.reload.description).to eq(temp_description)
+ end
+ end
+
+ context 'when symlink is involved' do
+ it 'does not trigger move if path is outside designated directory' do
+ stub_file_mover(temp_file_path, stub_real_path: Pathname('/etc'))
+ expect(FileUtils).not_to receive(:move)
+
+ subject
+
+ expect(snippet.reload.description).to eq(temp_description)
+ end
+ end
+ end
end
diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb
index 825c1cabc14..2dea48e3a88 100644
--- a/spec/uploaders/import_export_uploader_spec.rb
+++ b/spec/uploaders/import_export_uploader_spec.rb
@@ -3,9 +3,18 @@ require 'spec_helper'
describe ImportExportUploader do
let(:model) { build_stubbed(:import_export_upload) }
let(:upload) { create(:upload, model: model) }
+ let(:import_export_upload) { ImportExportUpload.new }
subject { described_class.new(model, :import_file) }
+ context 'local store' do
+ describe '#move_to_store' do
+ it 'returns true' do
+ expect(subject.move_to_store).to be true
+ end
+ end
+ end
+
context "object_store is REMOTE" do
before do
stub_uploads_object_storage
@@ -16,5 +25,28 @@ describe ImportExportUploader do
it_behaves_like 'builds correct paths',
store_dir: %r[import_export_upload/import_file/],
upload_path: %r[import_export_upload/import_file/]
+
+ describe '#move_to_store' do
+ it 'returns false' do
+ expect(subject.move_to_store).to be false
+ end
+ end
+
+ describe 'with an export file directly uploaded' do
+ let(:tempfile) { Tempfile.new(['test', '.gz']) }
+
+ before do
+ stub_uploads_object_storage(described_class, direct_upload: true)
+ import_export_upload.export_file = tempfile
+ end
+
+ it 'cleans up cached file' do
+ cache_dir = File.join(import_export_upload.export_file.cache_path(nil), '*')
+
+ import_export_upload.save!
+
+ expect(Dir[cache_dir]).to be_empty
+ end
+ end
end
end
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
deleted file mode 100644
index 0589563b502..00000000000
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'rails_helper'
-
-describe LegacyArtifactUploader do
- let(:store) { described_class::Store::LOCAL }
- let(:job) { create(:ci_build, artifacts_file_store: store) }
- let(:uploader) { described_class.new(job, :legacy_artifacts_file) }
- let(:local_path) { described_class.root }
-
- subject { uploader }
-
- # TODO: move to Workhorse::UploadPath
- describe '.workhorse_upload_path' do
- subject { described_class.workhorse_upload_path }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('tmp/uploads') }
- end
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z],
- cache_dir: %r[artifacts/tmp/cache],
- work_dir: %r[artifacts/tmp/work]
-
- context 'object store is remote' do
- before do
- stub_artifacts_object_storage
- end
-
- include_context 'with storage', described_class::Store::REMOTE
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z]
- end
-
- describe '#filename' do
- # we need to use uploader, as this makes to use mounter
- # which initialises uploader.file object
- let(:uploader) { job.artifacts_file }
-
- subject { uploader.filename }
-
- it { is_expected.to be_nil }
- end
-
- context 'file is stored in valid path' do
- let(:file) do
- fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip')
- end
-
- before do
- uploader.store!(file)
- end
-
- subject { uploader.file.path }
-
- it { is_expected.to start_with("#{uploader.root}") }
- it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") }
- it { is_expected.to include("/#{job.project_id}/") }
- it { is_expected.to end_with("ci_build_artifacts.zip") }
- end
-end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 533e9d87ea6..6bad5d49b1c 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -12,7 +12,7 @@ class Implementation < GitlabUploader
# user/:id
def dynamic_segment
- File.join(model.class.to_s.underscore, model.id.to_s)
+ File.join(model.class.underscore, model.id.to_s)
end
end
@@ -375,7 +375,7 @@ describe ObjectStorage do
describe '#fog_public' do
subject { uploader.fog_public }
- it { is_expected.to eq(false) }
+ it { is_expected.to eq(nil) }
end
describe '.workhorse_authorize' do
@@ -771,6 +771,14 @@ describe ObjectStorage do
expect { avatars }.not_to exceed_query_limit(1)
end
+ it 'does not attempt to replace methods' do
+ models.each do |model|
+ expect(model.avatar.upload).to receive(:method_missing).and_call_original
+
+ model.avatar.upload.path
+ end
+ end
+
it 'fetches a unique upload for each model' do
expect(avatars.map(&:url).uniq).to eq(avatars.map(&:url))
expect(avatars.map(&:upload).uniq).to eq(avatars.map(&:upload))
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index 97758f0243e..d9f0e2f3cb7 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -7,33 +7,19 @@ describe PersonalFileUploader do
subject { uploader }
- it_behaves_like 'builds correct paths',
- store_dir: %r[uploads/-/system/personal_snippet/\d+],
- upload_path: %r[\h+/\S+],
- absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet\/\d+\/\h+\/\S+$]
-
- context "object_store is REMOTE" do
+ shared_examples '#base_dir' do
before do
- stub_uploads_object_storage
+ subject.instance_variable_set(:@secret, 'secret')
end
- include_context 'with storage', described_class::Store::REMOTE
-
- it_behaves_like 'builds correct paths',
- store_dir: %r[\d+/\h+],
- upload_path: %r[^personal_snippet\/\d+\/\h+\/<filename>]
- end
-
- describe '#upload_paths' do
- it 'builds correct paths for both local and remote storage' do
- paths = uploader.upload_paths('test.jpg')
+ it 'is prefixed with uploads/-/system' do
+ allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name'))
- expect(paths.first).to match(%r[\h+\/test.jpg])
- expect(paths.second).to match(%r[^personal_snippet\/\d+\/\h+\/test.jpg])
+ expect(described_class.base_dir(model)).to eq("uploads/-/system/personal_snippet/#{model.id}")
end
end
- describe '#to_h' do
+ shared_examples '#to_h' do
before do
subject.instance_variable_set(:@secret, 'secret')
end
@@ -50,6 +36,40 @@ describe PersonalFileUploader do
end
end
+ describe '#upload_paths' do
+ it 'builds correct paths for both local and remote storage' do
+ paths = uploader.upload_paths('test.jpg')
+
+ expect(paths.first).to match(%r[\h+\/test.jpg])
+ expect(paths.second).to match(%r[^personal_snippet\/\d+\/\h+\/test.jpg])
+ end
+ end
+
+ context 'object_store is LOCAL' do
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/personal_snippet/\d+/\h+],
+ upload_path: %r[\h+/\S+],
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet\/\d+\/\h+\/\S+$]
+
+ it_behaves_like '#base_dir'
+ it_behaves_like '#to_h'
+ end
+
+ context "object_store is REMOTE" do
+ before do
+ stub_uploads_object_storage
+ end
+
+ include_context 'with storage', described_class::Store::REMOTE
+
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[\d+/\h+],
+ upload_path: %r[^personal_snippet\/\d+\/\h+\/<filename>]
+
+ it_behaves_like '#base_dir'
+ it_behaves_like '#to_h'
+ end
+
describe "#migrate!" do
before do
uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 3592a11360d..42352f9b9f8 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -71,7 +71,7 @@ describe RecordsUploads do
expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.not_to change { Upload.count }
end
- it 'it destroys Upload records at the same path before recording' do
+ it 'destroys Upload records at the same path before recording' do
existing = Upload.create!(
path: File.join('uploads', 'rails_sample.jpg'),
size: 512.kilobytes,
@@ -88,10 +88,19 @@ describe RecordsUploads do
end
describe '#destroy_upload callback' do
- it 'it destroys Upload records at the same path after removal' do
+ it 'destroys Upload records at the same path after removal' do
uploader.store!(upload_fixture('rails_sample.jpg'))
expect { uploader.remove! }.to change { Upload.count }.from(1).to(0)
end
end
+
+ describe '#filename' do
+ it 'gets the filename from the path recorded in the database, not CarrierWave' do
+ uploader.store!(upload_fixture('rails_sample.jpg'))
+ expect_any_instance_of(GitlabUploader).not_to receive(:filename)
+
+ expect(uploader.filename).to eq('rails_sample.jpg')
+ end
+ end
end
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index 95813d15e52..cc8970d2ba0 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -48,40 +48,6 @@ describe ObjectStorage::BackgroundMoveWorker do
end
end
- context 'for legacy artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
- let(:uploader_class) { LegacyArtifactUploader }
- let(:subject_class) { Ci::Build }
- let(:file_field) { :artifacts_file }
- let(:subject_id) { build.id }
-
- context 'when local storage is used' do
- let(:store) { local }
-
- context 'and remote storage is defined' do
- before do
- stub_artifacts_object_storage(background_upload: true)
- end
-
- it "migrates file to remote storage" do
- perform
-
- expect(build.reload.artifacts_file_store).to eq(remote)
- end
-
- context 'for artifacts_metadata' do
- let(:file_field) { :artifacts_metadata }
-
- it 'migrates metadata to remote storage' do
- perform
-
- expect(build.reload.artifacts_metadata_store).to eq(remote)
- end
- end
- end
- end
- end
-
context 'for job artifacts' do
let(:artifact) { create(:ci_job_artifact, :archive) }
let(:uploader_class) { JobArtifactUploader }
diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
new file mode 100644
index 00000000000..387e84b2d04
--- /dev/null
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -0,0 +1,315 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AddressableUrlValidator do
+ let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
+ subject { validator.validate(badge) }
+
+ include_examples 'url validator examples', described_class::DEFAULT_OPTIONS[:schemes]
+
+ describe 'validations' do
+ include_context 'invalid urls'
+
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+
+ it 'returns error when url is nil' do
+ expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
+ expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
+ end
+
+ it 'returns error when url is empty' do
+ expect(validator.validate_each(badge, :link_url, '')).to be_falsey
+ expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
+ end
+
+ it 'does not allow urls with CR or LF characters' do
+ aggregate_failures do
+ urls_with_CRLF.each do |url|
+ expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid'
+ end
+ end
+ end
+
+ it 'provides all arguments to UrlBlock validate' do
+ expect(Gitlab::UrlBlocker)
+ .to receive(:validate!)
+ .with(badge.link_url, described_class::BLOCKER_VALIDATE_OPTIONS)
+ .and_return(true)
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'by default' do
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+
+ it 'does not block urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+
+ it 'does not block urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+
+ it 'does block nil urls' do
+ badge.link_url = nil
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'does block blank urls' do
+ badge.link_url = '\n\r \n'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'strips urls' do
+ badge.link_url = "\n\r\n\nhttps://127.0.0.1\r\n\r\n\n\n\n"
+
+ # It's unusual for a validator to modify its arguments. Some extensions,
+ # such as attr_encrypted, freeze the string to signal that modifications
+ # will not be persisted, so freeze this string to ensure the scheme is
+ # compatible with them.
+ badge.link_url.freeze
+
+ subject
+
+ expect(badge.errors).to be_empty
+ expect(badge.link_url).to eq('https://127.0.0.1')
+ end
+ end
+
+ context 'when message is set' do
+ let(:message) { 'is blocked: test message' }
+ let(:validator) { described_class.new(attributes: [:link_url], allow_nil: false, message: message) }
+
+ it 'does block nil url with provided error message' do
+ expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
+ expect(badge.errors.first[1]).to eq message
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_nil: true) }
+
+ it 'does not block nil urls' do
+ badge.link_url = nil
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_blank: true) }
+
+ it 'does not block blank urls' do
+ badge.link_url = "\n\r \n"
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'when allow_localhost is set to false' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) }
+
+ it 'blocks urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ context 'when allow_setting_local_requests is set to true' do
+ it 'does not block urls pointing to localhost' do
+ expect(described_class)
+ .to receive(:allow_setting_local_requests?)
+ .and_return(true)
+
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when allow_local_network is set to false' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_local_network: false) }
+
+ it 'blocks urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ context 'when allow_setting_local_requests is set to true' do
+ it 'does not block urls pointing to local network' do
+ expect(described_class)
+ .to receive(:allow_setting_local_requests?)
+ .and_return(true)
+
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when ports is' do
+ let(:validator) { described_class.new(attributes: [:link_url], ports: ports) }
+
+ context 'empty' do
+ let(:ports) { [] }
+
+ it 'does not block any port' do
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'set' do
+ let(:ports) { [443] }
+
+ it 'blocks urls with a different port' do
+ subject
+
+ expect(badge.errors).to be_present
+ end
+ end
+ end
+
+ context 'when enforce_user is' do
+ let(:url) { 'http://$user@example.com'}
+ let(:validator) { described_class.new(attributes: [:link_url], enforce_user: enforce_user) }
+
+ context 'true' do
+ let(:enforce_user) { true }
+
+ it 'checks user format' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+ end
+
+ context 'false (default)' do
+ let(:enforce_user) { false }
+
+ it 'does not check user format' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when ascii_only is' do
+ let(:url) { 'https://ð•˜itⅼαƄ.com/foo/foo.bar'}
+ let(:validator) { described_class.new(attributes: [:link_url], ascii_only: ascii_only) }
+
+ context 'true' do
+ let(:ascii_only) { true }
+
+ it 'prevents unicode characters' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+ end
+
+ context 'false (default)' do
+ let(:ascii_only) { false }
+
+ it 'does not prevent unicode characters' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when enforce_sanitization is' do
+ let(:validator) { described_class.new(attributes: [:link_url], enforce_sanitization: enforce_sanitization) }
+ let(:unsafe_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
+ let(:safe_url) { 'https://replaceme.com/path/to/somewhere' }
+
+ let(:unsafe_internal_url) do
+ Gitlab.config.gitlab.protocol + '://' + Gitlab.config.gitlab.host +
+ "/'><script>alert(document.cookie)</script>"
+ end
+
+ context 'true' do
+ let(:enforce_sanitization) { true }
+
+ it 'prevents unsafe urls' do
+ badge.link_url = unsafe_url
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'prevents unsafe internal urls' do
+ badge.link_url = unsafe_internal_url
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'allows safe urls' do
+ badge.link_url = safe_url
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'false' do
+ let(:enforce_sanitization) { false }
+
+ it 'allows unsafe urls' do
+ badge.link_url = unsafe_url
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb
new file mode 100644
index 00000000000..7860b659bd3
--- /dev/null
+++ b/spec/validators/devise_email_validator_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeviseEmailValidator do
+ let!(:user) { build(:user, public_email: 'test@example.com') }
+ subject { validator.validate(user) }
+
+ describe 'validations' do
+ context 'by default' do
+ let(:validator) { described_class.new(attributes: [:public_email]) }
+
+ it 'allows when email is valid' do
+ subject
+
+ expect(user.errors).to be_empty
+ end
+
+ it 'returns error when email is invalid' do
+ user.public_email = 'invalid'
+
+ subject
+
+ expect(user.errors).to be_present
+ expect(user.errors.first[1]).to eq 'is invalid'
+ end
+
+ it 'returns error when email is nil' do
+ user.public_email = nil
+
+ subject
+
+ expect(user.errors).to be_present
+ end
+
+ it 'returns error when email is blank' do
+ user.public_email = ''
+
+ subject
+
+ expect(user.errors).to be_present
+ expect(user.errors.first[1]).to eq 'is invalid'
+ end
+ end
+ end
+
+ context 'when regexp is set as Regexp' do
+ let(:validator) { described_class.new(attributes: [:public_email], regexp: /[0-9]/) }
+
+ it 'allows when value match' do
+ user.public_email = '1'
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+
+ it 'returns error when value does not match' do
+ subject
+
+ expect(user.errors).to be_present
+ end
+ end
+
+ context 'when regexp is set as String' do
+ it 'raise argument error' do
+ expect { described_class.new( { regexp: 'something' } ) }.to raise_error ArgumentError
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:public_email], allow_nil: true) }
+
+ it 'allows when email is nil' do
+ user.public_email = nil
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:public_email], allow_blank: true) }
+
+ it 'allows when email is blank' do
+ user.public_email = ''
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+ end
+end
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index 710dd3dc38e..f6364fb1dd5 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
describe PublicUrlValidator do
- include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
+ include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
+ subject { validator.validate(badge) }
it 'blocks urls pointing to localhost' do
badge.link_url = 'https://127.0.0.1'
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
it 'blocks urls pointing to the local network' do
@@ -22,7 +22,7 @@ describe PublicUrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
end
diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb
new file mode 100644
index 00000000000..0a76570f65e
--- /dev/null
+++ b/spec/validators/sha_validator_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ShaValidator do
+ let(:validator) { described_class.new(attributes: [:base_commit_sha]) }
+ let!(:merge_diff) { build(:merge_request_diff) }
+
+ subject { validator.validate_each(merge_diff, :base_commit_sha, value) }
+
+ context 'with empty value' do
+ let(:value) { nil }
+
+ it 'does not add any error if value is empty' do
+ expect(Commit).not_to receive(:valid_hash?)
+
+ subject
+
+ expect(merge_diff.errors).to be_empty
+ end
+ end
+
+ context 'with valid sha' do
+ let(:value) { Digest::SHA1.hexdigest(SecureRandom.hex) }
+
+ it 'does not add any error' do
+ expect(Commit).to receive(:valid_hash?).and_call_original
+
+ subject
+
+ expect(merge_diff.errors).to be_empty
+ end
+ end
+
+ context 'with invalid sha' do
+ let(:value) { 'foo' }
+
+ it 'adds error to the record' do
+ expect(Commit).to receive(:valid_hash?).and_call_original
+ expect(merge_diff.errors).to be_empty
+
+ subject
+
+ expect(merge_diff.errors).not_to be_empty
+ end
+ end
+end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
deleted file mode 100644
index 1bb42382e8a..00000000000
--- a/spec/validators/url_validator_spec.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe UrlValidator do
- let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
-
- include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
-
- describe 'validations' do
- include_context 'invalid urls'
-
- let(:validator) { described_class.new(attributes: [:link_url]) }
-
- it 'returns error when url is nil' do
- expect(validator.validate_each(badge, :link_url, nil)).to be_nil
- expect(badge.errors.first[1]).to eq 'must be a valid URL'
- end
-
- it 'returns error when url is empty' do
- expect(validator.validate_each(badge, :link_url, '')).to be_nil
- expect(badge.errors.first[1]).to eq 'must be a valid URL'
- end
-
- it 'does not allow urls with CR or LF characters' do
- aggregate_failures do
- urls_with_CRLF.each do |url|
- expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid'
- end
- end
- end
- end
-
- context 'by default' do
- let(:validator) { described_class.new(attributes: [:link_url]) }
-
- it 'does not block urls pointing to localhost' do
- badge.link_url = 'https://127.0.0.1'
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
-
- it 'does not block urls pointing to the local network' do
- badge.link_url = 'https://192.168.1.1'
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
-
- it 'strips urls' do
- badge.link_url = "\n\r\n\nhttps://127.0.0.1\r\n\r\n\n\n\n"
-
- # It's unusual for a validator to modify its arguments. Some extensions,
- # such as attr_encrypted, freeze the string to signal that modifications
- # will not be persisted, so freeze this string to ensure the scheme is
- # compatible with them.
- badge.link_url.freeze
-
- subject
-
- expect(badge.errors).to be_empty
- expect(badge.link_url).to eq('https://127.0.0.1')
- end
- end
-
- context 'when allow_localhost is set to false' do
- let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) }
-
- it 'blocks urls pointing to localhost' do
- badge.link_url = 'https://127.0.0.1'
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
- end
-
- context 'when allow_local_network is set to false' do
- let(:validator) { described_class.new(attributes: [:link_url], allow_local_network: false) }
-
- it 'blocks urls pointing to the local network' do
- badge.link_url = 'https://192.168.1.1'
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
- end
-
- context 'when ports is' do
- let(:validator) { described_class.new(attributes: [:link_url], ports: ports) }
-
- context 'empty' do
- let(:ports) { [] }
-
- it 'does not block any port' do
- subject
-
- expect(badge.errors.empty?).to be true
- end
- end
-
- context 'set' do
- let(:ports) { [443] }
-
- it 'blocks urls with a different port' do
- subject
-
- expect(badge.errors.empty?).to be false
- end
- end
- end
-
- context 'when enforce_user is' do
- let(:url) { 'http://$user@example.com'}
- let(:validator) { described_class.new(attributes: [:link_url], enforce_user: enforce_user) }
-
- context 'true' do
- let(:enforce_user) { true }
-
- it 'checks user format' do
- badge.link_url = url
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
- end
-
- context 'false (default)' do
- let(:enforce_user) { false }
-
- it 'does not check user format' do
- badge.link_url = url
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
- end
- end
-
- context 'when ascii_only is' do
- let(:url) { 'https://ð•˜itⅼαƄ.com/foo/foo.bar'}
- let(:validator) { described_class.new(attributes: [:link_url], ascii_only: ascii_only) }
-
- context 'true' do
- let(:ascii_only) { true }
-
- it 'prevents unicode characters' do
- badge.link_url = url
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
- end
-
- context 'false (default)' do
- let(:ascii_only) { false }
-
- it 'does not prevent unicode characters' do
- badge.link_url = url
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
- end
- end
-
- context 'when enforce_sanitization is' do
- let(:validator) { described_class.new(attributes: [:link_url], enforce_sanitization: enforce_sanitization) }
- let(:unsafe_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
- let(:safe_url) { 'https://replaceme.com/path/to/somewhere' }
-
- let(:unsafe_internal_url) do
- Gitlab.config.gitlab.protocol + '://' + Gitlab.config.gitlab.host +
- "/'><script>alert(document.cookie)</script>"
- end
-
- context 'true' do
- let(:enforce_sanitization) { true }
-
- it 'prevents unsafe urls' do
- badge.link_url = unsafe_url
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
-
- it 'prevents unsafe internal urls' do
- badge.link_url = unsafe_internal_url
-
- subject
-
- expect(badge.errors.empty?).to be false
- end
-
- it 'allows safe urls' do
- badge.link_url = safe_url
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
- end
-
- context 'false' do
- let(:enforce_sanitization) { false }
-
- it 'allows unsafe urls' do
- badge.link_url = unsafe_url
-
- subject
-
- expect(badge.errors.empty?).to be true
- end
- end
- end
-end
diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb
new file mode 100644
index 00000000000..24ef68c1fab
--- /dev/null
+++ b/spec/validators/x509_certificate_credentials_validator_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe X509CertificateCredentialsValidator do
+ let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') }
+ let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') }
+
+ let(:validatable) do
+ Class.new do
+ include ActiveModel::Validations
+
+ attr_accessor :certificate, :private_key, :passphrase
+
+ def initialize(certificate, private_key, passphrase = nil)
+ @certificate, @private_key, @passphrase = certificate, private_key, passphrase
+ end
+ end
+ end
+
+ subject(:validator) do
+ described_class.new(certificate: :certificate, pkey: :private_key)
+ end
+
+ it 'is not valid when the certificate is not valid' do
+ record = validatable.new('not a certificate', nil)
+
+ validator.validate(record)
+
+ expect(record.errors[:certificate]).to include('is not a valid X509 certificate.')
+ end
+
+ it 'is not valid without a certificate' do
+ record = validatable.new(nil, nil)
+
+ validator.validate(record)
+
+ expect(record.errors[:certificate]).not_to be_empty
+ end
+
+ context 'when a valid certificate is passed' do
+ let(:record) { validatable.new(certificate_data, nil) }
+
+ it 'does not track an error for the certificate' do
+ validator.validate(record)
+
+ expect(record.errors[:certificate]).to be_empty
+ end
+
+ it 'adds an error when not passing a correct private key' do
+ validator.validate(record)
+
+ expect(record.errors[:private_key]).to include('could not read private key, is the passphrase correct?')
+ end
+
+ it 'has no error when the private key is correct' do
+ record.private_key = pkey_data
+
+ validator.validate(record)
+
+ expect(record.errors).to be_empty
+ end
+ end
+
+ context 'when using a passphrase' do
+ let(:passphrase_certificate_data) { File.read('spec/fixtures/passphrase_x509_certificate.crt') }
+ let(:passphrase_pkey_data) { File.read('spec/fixtures/passphrase_x509_certificate_pk.key') }
+
+ let(:record) { validatable.new(passphrase_certificate_data, passphrase_pkey_data, '5iveL!fe') }
+
+ subject(:validator) do
+ described_class.new(certificate: :certificate, pkey: :private_key, pass: :passphrase)
+ end
+
+ it 'is valid with the correct data' do
+ validator.validate(record)
+
+ expect(record.errors).to be_empty
+ end
+
+ it 'adds an error when the passphrase is wrong' do
+ record.passphrase = 'wrong'
+
+ validator.validate(record)
+
+ expect(record.errors[:private_key]).not_to be_empty
+ end
+ end
+end
diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb
new file mode 100644
index 00000000000..626159fc512
--- /dev/null
+++ b/spec/views/ci/status/_icon.html.haml_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'ci/status/_icon' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :private) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when rendering status for build' do
+ let(:build) do
+ create(:ci_build, :success, pipeline: pipeline)
+ end
+
+ context 'when user has ability to see details' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'has link to build details page' do
+ details_path = project_job_path(project, build)
+
+ render_status(build)
+
+ expect(rendered).to have_link(href: details_path)
+ end
+ end
+
+ context 'when user do not have ability to see build details' do
+ before do
+ render_status(build)
+ end
+
+ it 'contains build status text' do
+ expect(rendered).to have_css('.ci-status-icon.ci-status-icon-success')
+ end
+
+ it 'does not contain links' do
+ expect(rendered).not_to have_link
+ end
+ end
+ end
+
+ context 'when rendering status for external job' do
+ context 'when user has ability to see commit status details' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'status has external target url' do
+ before do
+ external_job = create(:generic_commit_status,
+ status: :running,
+ pipeline: pipeline,
+ target_url: 'http://gitlab.com')
+
+ render_status(external_job)
+ end
+
+ it 'contains valid commit status text' do
+ expect(rendered).to have_css('.ci-status-icon.ci-status-icon-running')
+ end
+
+ it 'has link to external status page' do
+ expect(rendered).to have_link(href: 'http://gitlab.com')
+ end
+ end
+
+ context 'status do not have external target url' do
+ before do
+ external_job = create(:generic_commit_status, status: :canceled)
+
+ render_status(external_job)
+ end
+
+ it 'contains valid commit status text' do
+ expect(rendered).to have_css('.ci-status-icon.ci-status-icon-canceled')
+ end
+
+ it 'has link to external status page' do
+ expect(rendered).not_to have_link
+ end
+ end
+ end
+ end
+
+ def render_status(resource)
+ render 'ci/status/icon', status: resource.detailed_status(user)
+ end
+end
diff --git a/spec/views/groups/_home_panel.html.haml_spec.rb b/spec/views/groups/_home_panel.html.haml_spec.rb
new file mode 100644
index 00000000000..91c5ca261b9
--- /dev/null
+++ b/spec/views/groups/_home_panel.html.haml_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'groups/_home_panel' do
+ let(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ it 'renders the group ID' do
+ render
+
+ expect(rendered).to have_content("Group ID: #{group.id}")
+ end
+end
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 38cfb84f0d5..29e15960fb8 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -12,7 +12,7 @@ describe 'groups/edit.html.haml' do
end
shared_examples_for '"Share with group lock" setting' do |checkbox_options|
- it 'should have the correct label, help text, and checkbox options' do
+ it 'has the correct label, help text, and checkbox options' do
assign(:group, test_group)
allow(view).to receive(:can?).with(test_user, :admin_group, test_group).and_return(true)
allow(view).to receive(:can_change_group_visibility_level?).and_return(false)
diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb
index 34e93d929a7..257991549a9 100644
--- a/spec/views/help/index.html.haml_spec.rb
+++ b/spec/views/help/index.html.haml_spec.rb
@@ -31,7 +31,7 @@ describe 'help/index' do
render
expect(rendered).to match '8.0.2'
- expect(rendered).to have_link('8.0.2', href: %r{https://gitlab.com/gitlab-org/gitlab-(ce|ee)/tags/v8.0.2})
+ expect(rendered).to have_link('8.0.2', href: %r{https://gitlab.com/gitlab-org/gitlab-(ce|ee)/-/tags/v8.0.2})
end
it 'shows a link to the commit for pre-releases' do
diff --git a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
index 05c2f61a606..bf63021a7fa 100644
--- a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
@@ -26,6 +26,8 @@ describe 'layouts/nav/sidebar/_admin' do
it_behaves_like 'page has active tab', 'Overview'
end
+ it_behaves_like 'has nav sidebar'
+
context 'on projects' do
before do
allow(controller).to receive(:controller_name).and_return('projects')
diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
new file mode 100644
index 00000000000..24b66a0e767
--- /dev/null
+++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'layouts/nav/sidebar/_group' do
+ let(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ it_behaves_like 'has nav sidebar'
+end
diff --git a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
new file mode 100644
index 00000000000..7f7f5637035
--- /dev/null
+++ b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'layouts/nav/sidebar/_instance_statistics' do
+ it_behaves_like 'has nav sidebar'
+end
diff --git a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
new file mode 100644
index 00000000000..6b820ab0b4c
--- /dev/null
+++ b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'layouts/nav/sidebar/_profile' do
+ let(:user) { create(:user) }
+
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ it_behaves_like 'has nav sidebar'
+end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index d9f05e5f94f..c6c10001bc5 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -11,6 +11,8 @@ describe 'layouts/nav/sidebar/_project' do
allow(view).to receive(:can?).and_return(true)
end
+ it_behaves_like 'has nav sidebar'
+
describe 'issue boards' do
it 'has board tab' do
render
@@ -111,4 +113,56 @@ describe 'layouts/nav/sidebar/_project' do
end
end
end
+
+ describe 'ci/cd settings tab' do
+ before do
+ project.update!(archived: project_archived)
+ end
+
+ context 'when project is archived' do
+ let(:project_archived) { true }
+
+ it 'does not show the ci/cd settings tab' do
+ render
+
+ expect(rendered).not_to have_link('CI / CD', href: project_settings_ci_cd_path(project))
+ end
+ end
+
+ context 'when project is active' do
+ let(:project_archived) { false }
+
+ it 'shows the ci/cd settings tab' do
+ render
+
+ expect(rendered).to have_link('CI / CD', href: project_settings_ci_cd_path(project))
+ end
+ end
+ end
+
+ describe 'operations settings tab' do
+ before do
+ project.update!(archived: project_archived)
+ end
+
+ context 'when project is archived' do
+ let(:project_archived) { true }
+
+ it 'does not show the operations settings tab' do
+ render
+
+ expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project))
+ end
+ end
+
+ context 'when project is active' do
+ let(:project_archived) { false }
+
+ it 'shows the operations settings tab' do
+ render
+
+ expect(rendered).to have_link('Operations', href: project_settings_operations_path(project))
+ end
+ end
+ end
end
diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
new file mode 100644
index 00000000000..a7d3dc09fd4
--- /dev/null
+++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'notify/pipeline_failed_email.text.erb' do
+ include Devise::Test::ControllerHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :failed,
+ project: project,
+ user: user,
+ ref: project.default_branch,
+ sha: project.commit.sha)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:pipeline, pipeline)
+ assign(:merge_request, merge_request)
+ end
+
+ it 'renders the email correctly' do
+ job = create(:ci_build, :failed, pipeline: pipeline, project: pipeline.project)
+
+ render
+
+ expect(rendered).to have_content('Your pipeline has failed')
+ expect(rendered).to have_content(pipeline.project.name)
+ expect(rendered).to have_content(pipeline.git_commit_message.truncate(50))
+ expect(rendered).to have_content(pipeline.commit.author_name)
+ expect(rendered).to have_content("##{pipeline.id}")
+ expect(rendered).to have_content(pipeline.user.name)
+ expect(rendered).to have_content("/-/jobs/#{job.id}/raw")
+ end
+end
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 908ecb898e4..12925a5ab07 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -45,7 +45,7 @@ describe 'projects/_home_panel' do
context 'badges' do
shared_examples 'show badges' do
- it 'should render the all badges' do
+ it 'renders the all badges' do
render
expect(rendered).to have_selector('.project-badges a')
@@ -70,7 +70,7 @@ describe 'projects/_home_panel' do
context 'has no badges' do
let(:project) { create(:project) }
- it 'should not render any badge' do
+ it 'does not render any badge' do
render
expect(rendered).not_to have_selector('.project-badges')
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 1086546c10d..457dd2e940f 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -27,7 +27,7 @@ describe 'projects/commit/_commit_box.html.haml' do
render
- expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed")
+ expect(rendered).to have_text("Pipeline ##{third_pipeline.id} (##{third_pipeline.iid}) failed")
end
end
@@ -40,7 +40,7 @@ describe 'projects/commit/_commit_box.html.haml' do
it 'shows correct pipeline description' do
render
- expect(rendered).to have_text "Pipeline ##{pipeline.id} " \
+ expect(rendered).to have_text "Pipeline ##{pipeline.id} (##{pipeline.iid}) " \
'waiting for manual action'
end
end
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb
new file mode 100644
index 00000000000..54ec4f32856
--- /dev/null
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'projects/deployments/_confirm_rollback_modal' do
+ let(:environment) { create(:environment, :with_review_app) }
+ let(:deployments) { environment.deployments }
+ let(:project) { environment.project }
+
+ before do
+ assign(:environment, environment)
+ assign(:deployments, deployments)
+ assign(:project, project)
+ end
+
+ context 'when re-deploying last deployment' do
+ let(:deployment) { deployments.first }
+
+ before do
+ allow(view).to receive(:deployment).and_return(deployment)
+ end
+
+ it 'shows "re-deploy"' do
+ render
+
+ expect(rendered).to have_selector('h4', text: "Re-deploy environment #{environment.name}?")
+ expect(rendered).to have_selector('p', text: "This action will relaunch the job for commit #{deployment.short_sha}, putting the environment in a previous version. Are you sure you want to continue?")
+ expect(rendered).to have_selector('a.btn-danger', text: 'Re-deploy')
+ end
+
+ it 'links to re-deploying the environment' do
+ expected_link = retry_project_job_path(environment.project, deployment.deployable)
+
+ render
+
+ expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Re-deploy')
+ end
+ end
+
+ context 'when rolling back to previous deployment' do
+ let(:deployment) { create(:deployment, environment: environment) }
+
+ before do
+ allow(view).to receive(:deployment).and_return(deployment)
+ end
+
+ it 'shows "rollback"' do
+ render
+
+ expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?")
+ expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
+ expect(rendered).to have_selector('a.btn-danger', text: 'Rollback')
+ end
+
+ it 'links to re-deploying the environment' do
+ expected_link = retry_project_job_path(environment.project, deployment.deployable)
+
+ render
+
+ expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Rollback')
+ end
+ end
+end
diff --git a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb b/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb
deleted file mode 100644
index 02c225292ce..00000000000
--- a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-describe 'projects/issues/_merge_requests_status.html.haml' do
- it 'shows date of status change in tooltip' do
- merge_request = create(:merge_request, created_at: 1.month.ago)
-
- render partial: 'projects/issues/merge_requests_status',
- locals: { merge_request: merge_request, css_class: '' }
-
- expect(rendered).to match("Opened.*about 1 month ago")
- end
-
- it 'shows only status in tooltip if date is not set' do
- merge_request = create(:merge_request, state: :closed)
-
- render partial: 'projects/issues/merge_requests_status',
- locals: { merge_request: merge_request, css_class: '' }
-
- expect(rendered).to match("Closed")
- end
-end
diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb
index 1d9c6d36ad7..1ca9eaf8fdb 100644
--- a/spec/views/projects/issues/show.html.haml_spec.rb
+++ b/spec/views/projects/issues/show.html.haml_spec.rb
@@ -19,6 +19,7 @@ describe 'projects/issues/show' do
context 'when the issue is closed' do
before do
allow(issue).to receive(:closed?).and_return(true)
+ allow(view).to receive(:current_user).and_return(user)
end
context 'when the issue was moved' do
@@ -28,16 +29,30 @@ describe 'projects/issues/show' do
issue.moved_to = new_issue
end
- it 'shows "Closed (moved)" if an issue has been moved' do
- render
+ context 'when user can see the moved issue' do
+ before do
+ project.add_developer(user)
+ end
- expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ it 'shows "Closed (moved)" if an issue has been moved' do
+ render
+
+ expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ end
+
+ it 'links "moved" to the new issue the original issue was moved to' do
+ render
+
+ expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ end
end
- it 'links "moved" to the new issue the original issue was moved to' do
- render
+ context 'when user cannot see moved issue' do
+ it 'does not show moved issue link' do
+ render
- expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ expect(rendered).not_to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ end
end
end
diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 1d58891036e..97b25a6976f 100644
--- a/spec/views/projects/jobs/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
- let(:pipeline) { create(:ci_empty_pipeline, id: 1337, project: project, sha: project.commit.id) }
+ let(:pipeline) { create(:ci_empty_pipeline, id: 1337, iid: 57, project: project, sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'rspec 0:2', status: :pending) }
before do
@@ -15,14 +15,14 @@ describe 'projects/ci/jobs/_build' do
it 'won\'t include a column with a link to its pipeline by default' do
render partial: 'projects/ci/builds/build', locals: { build: build }
- expect(rendered).not_to have_link('#1337')
- expect(rendered).not_to have_text('#1337 by API')
+ expect(rendered).not_to have_link('#1337 (#57)')
+ expect(rendered).not_to have_text('#1337 (#57) by API')
end
it 'can include a column with a link to its pipeline' do
render partial: 'projects/ci/builds/build', locals: { build: build, pipeline_link: true }
- expect(rendered).to have_link('#1337')
- expect(rendered).to have_text('#1337 by API')
+ expect(rendered).to have_link('#1337 (#57)')
+ expect(rendered).to have_text('#1337 (#57) by API')
end
end
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index c13eab30054..529afa03f9c 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -17,7 +17,7 @@ describe 'projects/merge_requests/edit.html.haml' do
source_project: forked_project,
target_project: project,
author: user,
- assignee: user,
+ assignees: [user],
milestone: milestone)
end
@@ -40,7 +40,7 @@ describe 'projects/merge_requests/edit.html.haml' do
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
- expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
+ expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
end
@@ -52,7 +52,7 @@ describe 'projects/merge_requests/edit.html.haml' do
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
- expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
+ expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
end
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index d9bda1a3414..23cb319a202 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -53,19 +53,6 @@ describe 'projects/merge_requests/show.html.haml' do
expect(rendered).not_to have_css('.cannot-be-merged')
end
end
-
- context 'when assignee is not allowed to merge' do
- it 'shows a warning icon' do
- reporter = create(:user)
- project.add_reporter(reporter)
- closed_merge_request.update(assignee_id: reporter.id)
- assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
-
- render
-
- expect(rendered).to have_css('.cannot-be-merged')
- end
- end
end
context 'when the merge request is closed' do
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 8a9ab02eaca..ae47f364296 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
@@ -12,10 +12,10 @@ describe 'projects/notes/_more_actions_dropdown' do
assign(:project, project)
end
- it 'shows Report abuse to GitLab button if not editable and not current users comment' do
+ it 'shows Report abuse to admin button if not editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note
- expect(rendered).to have_link('Report abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
end
it 'does not show the More actions button if not editable and current users comment' do
@@ -24,10 +24,10 @@ describe 'projects/notes/_more_actions_dropdown' do
expect(rendered).not_to have_selector('.dropdown.more-actions')
end
- it 'shows Report abuse to GitLab and Delete buttons if editable and not current users comment' do
+ it 'shows Report abuse to admin 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 abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
expect(rendered).to have_link('Delete comment')
end
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index 2a2539c80b5..ff2d491539b 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -5,6 +5,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
before do
assign :project, project
+ allow(view).to receive(:auto_devops_enabled) { true }
end
it 'shows a warning message about Kubernetes cluster' do
@@ -12,4 +13,14 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
expect(rendered).to have_text('You must add a Kubernetes cluster integration to this project with a domain in order for your deployment strategy to work correctly.')
end
+
+ context 'when the project has an available kubernetes cluster' do
+ let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) }
+
+ it 'does not show a warning message' do
+ render
+
+ expect(rendered).not_to have_text('You must add a Kubernetes cluster')
+ end
+ end
end
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 8e34521c7c8..6762fe3759b 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -18,6 +18,7 @@ describe 'projects/settings/operations/show' do
allow(view).to receive(:error_tracking_setting)
.and_return(error_tracking_setting)
allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:incident_management_available?) { false }
end
let!(:error_tracking_setting) do
@@ -30,7 +31,6 @@ describe 'projects/settings/operations/show' do
expect(rendered).to have_content _('Error Tracking')
expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token')
- expect(rendered).to have_content _('Active')
end
end
end
diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb
index cb97d17988c..34355e27544 100644
--- a/spec/views/projects/tags/index.html.haml_spec.rb
+++ b/spec/views/projects/tags/index.html.haml_spec.rb
@@ -1,20 +1,53 @@
require 'spec_helper'
describe 'projects/tags/index' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository) }
+ let(:tags) { TagsFinder.new(project.repository, {}).execute }
+ let(:git_tag) { project.repository.tags.last }
+ let(:release) { create(:release, project: project, sha: git_tag.target_commit.sha) }
+ let(:pipeline) { create(:ci_pipeline, :success, project: project, ref: git_tag.name, sha: release.sha) }
before do
assign(:project, project)
assign(:repository, project.repository)
- assign(:tags, [])
+ assign(:releases, project.releases)
+ assign(:tags, Kaminari.paginate_array(tags).page(0))
+ assign(:tags_pipelines, { git_tag.name => pipeline })
allow(view).to receive(:current_ref).and_return('master')
- allow(view).to receive(:can?).and_return(false)
+ allow(view).to receive(:current_user).and_return(project.namespace.owner)
end
it 'defaults sort dropdown toggle to last updated' do
render
-
expect(rendered).to have_button('Last updated')
end
+
+ context 'when the most recent build for a tag has artifacts' do
+ let!(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+ it 'renders the Artifacts section in the download list' do
+ render
+ expect(rendered).to have_selector('li', text: 'Artifacts')
+ end
+
+ it 'renders artifact download links' do
+ render
+ expect(rendered).to have_link(href: latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: 'test'))
+ end
+ end
+
+ context 'when the most recent build for a tag has expired artifacts' do
+ let!(:build) { create(:ci_build, :success, :expired, :artifacts, pipeline: pipeline) }
+
+ it 'does not render the Artifacts section in the download list' do
+ render
+ expect(rendered).not_to have_selector('li', text: 'Artifacts')
+ end
+
+ it 'does not render artifact download links' do
+ render
+ expect(rendered).not_to have_link(href: latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: 'test'))
+ end
+ end
end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 3b098320ad7..5bb0173ab89 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -7,6 +7,8 @@ describe 'projects/tree/show' do
let(:repository) { project.repository }
before do
+ stub_feature_flags(vue_file_list: false)
+
assign(:project, project)
assign(:repository, repository)
assign(:lfs_blob_ids, [])
diff --git a/spec/views/shared/_label_row.html.haml.rb b/spec/views/shared/_label_row.html.haml.rb
new file mode 100644
index 00000000000..a58d5efc1e3
--- /dev/null
+++ b/spec/views/shared/_label_row.html.haml.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'shared/_label_row.html.haml' do
+ label_types = {
+ 'project label': :label,
+ 'group label': :group_label
+ }
+
+ label_types.each do |label_type, label_factory|
+ let!(:label) { create(label_factory) }
+
+ context "for a #{label_type}" do
+ it 'has a non-linked label title' do
+ render 'shared/label_row', label: label
+
+ expect(rendered).not_to have_css('a', text: label.title)
+ end
+
+ it "has Issues link for #{label_type}" do
+ render 'shared/label_row', label: label
+
+ expect(rendered).to have_css('a', text: 'Issues')
+ end
+
+ it "has Merge request link for #{label_type}" do
+ render 'shared/label_row', label: label
+
+ expect(rendered).to have_css('a', text: 'Merge requests')
+ end
+ end
+ end
+end
diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml.rb
index 4769d569548..cbbb984935f 100644
--- a/spec/views/shared/milestones/_issuables.html.haml.rb
+++ b/spec/views/shared/milestones/_issuables.html.haml.rb
@@ -11,12 +11,12 @@ describe 'shared/milestones/_issuables.html.haml' do
stub_template 'shared/milestones/_issuable.html.haml' => ''
end
- it 'should show the issuables count if show_counter is true' do
+ it 'shows the issuables count if show_counter is true' do
render 'shared/milestones/issuables', show_counter: true
expect(rendered).to have_content('100')
end
- it 'should not show the issuables count if show_counter is false' do
+ it 'does not show the issuables count if show_counter is false' do
render 'shared/milestones/issuables', show_counter: false
expect(rendered).not_to have_content('100')
end
@@ -24,7 +24,7 @@ describe 'shared/milestones/_issuables.html.haml' do
describe 'a high issuables count' do
let(:issuables_size) { 1000 }
- it 'should show a delimited number if show_counter is true' do
+ it 'shows a delimited number if show_counter is true' do
render 'shared/milestones/issuables', show_counter: true
expect(rendered).to have_content('1,000')
end
diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb
index 3b14045e61f..dc223861037 100644
--- a/spec/views/shared/projects/_project.html.haml_spec.rb
+++ b/spec/views/shared/projects/_project.html.haml_spec.rb
@@ -8,13 +8,13 @@ describe 'shared/projects/_project.html.haml' do
allow(view).to receive(:can?) { true }
end
- it 'should render creator avatar if project has a creator' do
+ it 'renders creator avatar if project has a creator' do
render 'shared/projects/project', use_creator_avatar: true, project: project
expect(rendered).to have_selector('img.avatar')
end
- it 'should render a generic avatar if project does not have a creator' do
+ it 'renders a generic avatar if project does not have a creator' do
project.creator = nil
render 'shared/projects/project', use_creator_avatar: true, project: project
diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb
index 27687f069ea..f72b932423f 100644
--- a/spec/workers/admin_email_worker_spec.rb
+++ b/spec/workers/admin_email_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AdminEmailWorker do
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 7244ad4f199..368ed3f3db1 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ArchiveTraceWorker do
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index d095138f6b7..4c02278de64 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AuthorizedProjectsWorker do
diff --git a/spec/workers/auto_merge_process_worker_spec.rb b/spec/workers/auto_merge_process_worker_spec.rb
new file mode 100644
index 00000000000..616727ce5ca
--- /dev/null
+++ b/spec/workers/auto_merge_process_worker_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMergeProcessWorker do
+ describe '#perform' do
+ subject { described_class.new.perform(merge_request&.id) }
+
+ context 'when merge request is found' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'executes AutoMergeService' do
+ expect_next_instance_of(AutoMergeService) do |auto_merge|
+ expect(auto_merge).to receive(:process)
+ end
+
+ subject
+ end
+ end
+
+ context 'when merge request is not found' do
+ let(:merge_request) { nil }
+
+ it 'does not execute AutoMergeService' do
+ expect(AutoMergeService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 3bd072e7125..746c858609f 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state do
diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb
index ba20488f663..25686ae68ca 100644
--- a/spec/workers/build_coverage_worker_spec.rb
+++ b/spec/workers/build_coverage_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildCoverageWorker do
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index ccb26849e67..4adb795b1d6 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildFinishedWorker do
@@ -15,6 +17,7 @@ describe BuildFinishedWorker do
expect_any_instance_of(BuildCoverageWorker).to receive(:perform)
expect(BuildHooksWorker).to receive(:perform_async)
expect(ArchiveTraceWorker).to receive(:perform_async)
+ expect(ExpirePipelineCacheWorker).to receive(:perform_async)
described_class.new.perform(build.id)
end
diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb
index 97654a93f5c..59b252a8be3 100644
--- a/spec/workers/build_hooks_worker_spec.rb
+++ b/spec/workers/build_hooks_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildHooksWorker do
diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb
index 5eb9709ded9..ffe8796ded9 100644
--- a/spec/workers/build_success_worker_spec.rb
+++ b/spec/workers/build_success_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildSuccessWorker do
@@ -13,6 +15,7 @@ describe BuildSuccessWorker do
let!(:build) { create(:ci_build, :deploy_to_production) }
before do
+ allow(Deployments::FinishedWorker).to receive(:perform_async)
Deployment.delete_all
build.reload
end
diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb
index 45243f45547..97fc0a2da0c 100644
--- a/spec/workers/build_trace_sections_worker_spec.rb
+++ b/spec/workers/build_trace_sections_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildTraceSectionsWorker do
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 478fb7d2c0f..eca6cf5235f 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ArchiveTracesCronWorker do
diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb
new file mode 100644
index 00000000000..9f76696ee66
--- /dev/null
+++ b/spec/workers/ci/build_prepare_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildPrepareWorker do
+ subject { described_class.new.perform(build_id) }
+
+ context 'build exists' do
+ let(:build) { create(:ci_build) }
+ let(:build_id) { build.id }
+ let(:service) { double(execute: true) }
+
+ it 'calls the prepare build service' do
+ expect(Ci::PrepareBuildService).to receive(:new).with(build).and_return(service)
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'build does not exist' do
+ let(:build_id) { -1 }
+
+ it 'does not attempt to prepare the build' do
+ expect(Ci::PrepareBuildService).not_to receive(:new)
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb
index 4a3fe84d7f7..647f9763fed 100644
--- a/spec/workers/ci/build_schedule_worker_spec.rb
+++ b/spec/workers/ci/build_schedule_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildScheduleWorker do
diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
index 6918ee3d7d8..975088f3ee6 100644
--- a/spec/workers/cluster_configure_worker_spec.rb
+++ b/spec/workers/cluster_configure_worker_spec.rb
@@ -5,55 +5,57 @@ require 'spec_helper'
describe ClusterConfigureWorker, '#perform' do
let(:worker) { described_class.new }
- context 'when group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { cluster.group }
+ shared_examples 'configured cluster' do
+ it 'creates a namespace' do
+ expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_cluster).with(cluster).once
+
+ worker.perform(cluster.id)
+ end
+ end
- context 'when group has no projects' do
- it 'does not create a namespace' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
+ shared_examples 'unconfigured cluster' do
+ it 'does not create a namespace' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
- worker.perform(cluster.id)
- end
+ worker.perform(cluster.id)
end
+ end
+
+ context 'group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+ let(:group) { cluster.group }
context 'when group has a project' do
let!(:project) { create(:project, group: group) }
- it 'creates a namespace for the project' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once
-
- worker.perform(cluster.id)
- end
+ it_behaves_like 'unconfigured cluster'
end
context 'when group has project in a sub-group' do
let!(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, group: subgroup) }
- it 'creates a namespace for the project' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once
-
- worker.perform(cluster.id)
- end
+ it_behaves_like 'unconfigured cluster'
end
end
context 'when provider type is gcp' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- it 'configures kubernetes platform' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
-
- described_class.new.perform(cluster.id)
- end
+ it_behaves_like 'configured cluster'
end
context 'when provider type is user' do
- let(:cluster) { create(:cluster, :project, :provided_by_user) }
+ let!(:cluster) { create(:cluster, :project, :provided_by_user) }
+
+ it_behaves_like 'configured cluster'
+ end
+
+ context 'when cluster is not managed' do
+ let(:cluster) { create(:cluster, :not_managed) }
- it 'configures kubernetes platform' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ it 'does not configure the cluster' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
described_class.new.perform(cluster.id)
end
diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb
new file mode 100644
index 00000000000..2ac9d0f61b4
--- /dev/null
+++ b/spec/workers/cluster_project_configure_worker_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterProjectConfigureWorker, '#perform' do
+ let(:worker) { described_class.new }
+ let(:cluster) { create(:cluster, :project) }
+
+ it 'configures the cluster' do
+ expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_project)
+
+ described_class.new.perform(cluster.projects.first.id)
+ end
+end
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index da32f29fec0..9cc2ad12bfc 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterProvisionWorker do
diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
index 2e2e9afd25a..a9ffdfb085e 100644
--- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterWaitForIngressIpAddressWorker do
diff --git a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
new file mode 100644
index 00000000000..aaf5c9defc4
--- /dev/null
+++ b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do
+ let(:app) { create(:clusters_applications_helm) }
+ let(:app_name) { app.name }
+ let(:app_id) { app.id }
+
+ subject { described_class.new.perform(app_name, app_id) }
+
+ context 'app exists' do
+ let(:service) { instance_double(Clusters::Applications::CheckUninstallProgressService) }
+
+ it 'calls the check service' do
+ expect(Clusters::Applications::CheckUninstallProgressService).to receive(:new).with(app).and_return(service)
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'app does not exist' do
+ let(:app_id) { 0 }
+
+ it 'does not call the check service' do
+ expect(Clusters::Applications::CheckUninstallProgressService).not_to receive(:new)
+
+ expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+end
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index 901d77178bc..ae5244e2f62 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationWorker do
diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb
index 4118b9aa194..732d55dfbde 100644
--- a/spec/workers/concerns/cluster_queue_spec.rb
+++ b/spec/workers/concerns/cluster_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterQueue do
diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb
index c042a52f41f..cf4d47b7500 100644
--- a/spec/workers/concerns/cronjob_queue_spec.rb
+++ b/spec/workers/concerns/cronjob_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CronjobQueue do
diff --git a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
index 4b9aa9a7ef8..200cdffd560 100644
--- a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::NotifyUponDeath do
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 9c187bead0a..51b685b5792 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ObjectImporter do
diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
index a96f583aff7..d262bc2e05c 100644
--- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Queue do
diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
index 8de4059c4ae..294eacf09ab 100644
--- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ReschedulingMethods do
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index d85a87f2cb0..f9081a875b5 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::StageMethods do
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
index 24c0a3c6a20..78ceafb359f 100644
--- a/spec/workers/concerns/pipeline_background_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineBackgroundQueue do
diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb
index a312b307fce..eedfceb8bf0 100644
--- a/spec/workers/concerns/pipeline_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineQueue do
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index 3699fd83a9a..c5fbcfb5fb0 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectImportOptions do
diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb
index d2eeecfc9a8..55ed71f124c 100644
--- a/spec/workers/concerns/repository_check_queue_spec.rb
+++ b/spec/workers/concerns/repository_check_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheckQueue do
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index ce38cde9208..37fadd6ac02 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WaitableWorker do
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
index f5479e57260..ae09b4b77f1 100644
--- a/spec/workers/create_gpg_signature_worker_spec.rb
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateGpgSignatureWorker do
diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb
index 0ac946a1232..e35aaa7d593 100644
--- a/spec/workers/create_note_diff_file_worker_spec.rb
+++ b/spec/workers/create_note_diff_file_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateNoteDiffFileWorker do
diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb
index 02cb0f46cb4..62a17da80c0 100644
--- a/spec/workers/create_pipeline_worker_spec.rb
+++ b/spec/workers/create_pipeline_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreatePipelineWorker do
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
index e0edd313922..9f8b20df48e 100644
--- a/spec/workers/delete_diff_files_worker_spec.rb
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteDiffFilesWorker do
diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb
index 39009d9e4b2..a218ca921d9 100644
--- a/spec/workers/delete_merged_branches_worker_spec.rb
+++ b/spec/workers/delete_merged_branches_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteMergedBranchesWorker do
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 06d9e125105..c963b886e62 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteUserWorker do
diff --git a/spec/workers/deployments/finished_worker_spec.rb b/spec/workers/deployments/finished_worker_spec.rb
new file mode 100644
index 00000000000..df62821e2cd
--- /dev/null
+++ b/spec/workers/deployments/finished_worker_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Deployments::FinishedWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ before do
+ allow(ProjectServiceWorker).to receive(:perform_async)
+ end
+
+ it 'executes project services for deployment_hooks' do
+ deployment = create(:deployment)
+ project = deployment.project
+ service = create(:service, type: 'SlackService', project: project, deployment_events: true, active: true)
+
+ worker.perform(deployment.id)
+
+ expect(ProjectServiceWorker).to have_received(:perform_async).with(service.id, an_instance_of(Hash))
+ end
+
+ it 'does not execute an inactive service' do
+ deployment = create(:deployment)
+ project = deployment.project
+ create(:service, type: 'SlackService', project: project, deployment_events: true, active: false)
+
+ worker.perform(deployment.id)
+
+ expect(ProjectServiceWorker).not_to have_received(:perform_async)
+ end
+
+ it 'does nothing if a deployment with the given id does not exist' do
+ worker.perform(0)
+
+ expect(ProjectServiceWorker).not_to have_received(:perform_async)
+ end
+ end
+end
diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb
index ba7d45eca01..1c68922b03d 100644
--- a/spec/workers/deployments/success_worker_spec.rb
+++ b/spec/workers/deployments/success_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Deployments::SuccessWorker do
diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb
index ff3878fbc8e..755eb8dbf6b 100644
--- a/spec/workers/detect_repository_languages_worker_spec.rb
+++ b/spec/workers/detect_repository_languages_worker_spec.rb
@@ -1,8 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DetectRepositoryLanguagesWorker do
set(:project) { create(:project) }
- let(:user) { project.owner }
subject { described_class.new }
@@ -12,19 +13,13 @@ describe DetectRepositoryLanguagesWorker do
allow(::Projects::DetectRepositoryLanguagesService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
- subject.perform(project.id, user.id)
+ subject.perform(project.id)
end
context 'when invalid ids are used' do
it 'does not raise when the project could not be found' do
expect do
- subject.perform(-1, user.id)
- end.not_to raise_error
- end
-
- it 'does not raise when the user could not be found' do
- expect do
- subject.perform(project.id, -1)
+ subject.perform(-1)
end.not_to raise_error
end
end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index 045135255d6..f8a31fcdee6 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe EmailReceiverWorker, :mailer do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 05b4fb49ea3..0f87df89c29 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EmailsOnPushWorker, :mailer do
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index ebe02373275..8fddd8540ef 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Every Sidekiq worker' do
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index 27995cf1611..74d6b5605d1 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireBuildArtifactsWorker do
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index e1a56c72162..39f676f1057 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireBuildInstanceArtifactsWorker do
@@ -19,7 +21,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does remove files' do
- expect(build.reload.artifacts_file.exists?).to be_falsey
+ expect(build.reload.artifacts_file.present?).to be_falsey
end
it 'does remove the job artifact record' do
@@ -38,7 +40,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do
@@ -54,7 +56,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do
diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb
index 1b614342a18..6ac285ca944 100644
--- a/spec/workers/expire_job_cache_worker_spec.rb
+++ b/spec/workers/expire_job_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireJobCacheWorker do
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index 54c9a69d329..5652f5e8685 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpirePipelineCacheWorker do
@@ -7,40 +9,17 @@ describe ExpirePipelineCacheWorker do
subject { described_class.new }
describe '#perform' do
- it 'invalidates Etag caching for project pipelines path' do
- pipelines_path = "/#{project.full_path}/pipelines.json"
- new_mr_pipelines_path = "/#{project.full_path}/merge_requests/new.json"
- pipeline_path = "/#{project.full_path}/pipelines/#{pipeline.id}.json"
-
- expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipelines_path)
- expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(new_mr_pipelines_path)
- expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipeline_path)
-
- subject.perform(pipeline.id)
- end
-
- it 'invalidates Etag caching for merge request pipelines if pipeline runs on any commit of that source branch' do
- pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master')
- merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
- merge_request_pipelines_path = "/#{project.full_path}/merge_requests/#{merge_request.iid}/pipelines.json"
-
- allow_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch)
- expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(merge_request_pipelines_path)
+ it 'executes the service' do
+ expect_any_instance_of(Ci::ExpirePipelineCacheService).to receive(:execute).with(pipeline).and_call_original
subject.perform(pipeline.id)
end
it "doesn't do anything if the pipeline not exist" do
+ expect_any_instance_of(Ci::ExpirePipelineCacheService).not_to receive(:execute)
expect_any_instance_of(Gitlab::EtagCaching::Store).not_to receive(:touch)
subject.perform(617748)
end
-
- it 'updates the cached status for a project' do
- expect(Gitlab::Cache::Ci::ProjectPipelineStatus).to receive(:update_for_pipeline)
- .with(pipeline)
-
- subject.perform(pipeline.id)
- end
end
end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 4895a968d6e..cc1c23bb9e7 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
require 'spec_helper'
@@ -113,6 +115,19 @@ describe GitGarbageCollectWorker do
end
end
+ context "pack_refs" do
+ before do
+ expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
+ end
+
+ it "calls Gitaly" do
+ expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:pack_refs)
+ .and_return(nil)
+
+ subject.perform(project.id, :pack_refs, lease_key, lease_uuid)
+ end
+ end
+
context "repack_incremental" do
before do
expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
index fc7aafbc0c9..b1647d8c7df 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 5b1c6b6010a..42d69ff6166 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportDiffNoteWorker do
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index ab070d6d081..06a573e16b7 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportIssueWorker do
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 3a30f06bb2d..5110c3ff11b 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportNoteWorker do
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index 3cccd7cab21..d46e381fc51 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportPullRequestWorker do
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index 7ff133f1049..fa4ded8e42f 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::RefreshImportJidWorker do
diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
index 91e0cddb5d8..35a856802c2 100644
--- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::FinishImportWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
index ad6154cc4a4..0c7fc2a164e 100644
--- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
index ab347f5b75b..5d96f562c30 100644
--- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
index b19884d7991..e7c9dabb292 100644
--- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
index 94cff9e4e80..90590a45900 100644
--- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportNotesWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 1fbb073a34a..15d485f1018 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
index adab535ac05..6d47d73b92e 100644
--- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
index 6b222af454d..0758cfc4ee2 100644
--- a/spec/workers/gitlab_shell_worker_spec.rb
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabShellWorker do
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index 49b4e04dc7c..aff5d112cdd 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabUsagePingWorker do
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
index a170c84ab12..90a4150a31a 100644
--- a/spec/workers/group_destroy_worker_spec.rb
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupDestroyWorker do
diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb
index a85f820a3eb..a318cdd003e 100644
--- a/spec/workers/hashed_storage/migrator_worker_spec.rb
+++ b/spec/workers/hashed_storage/migrator_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HashedStorage::MigratorWorker do
diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
new file mode 100644
index 00000000000..f266c7dbe8c
--- /dev/null
+++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ describe '#perform' do
+ let(:project) { create(:project, :empty_repo, :legacy_storage) }
+ let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" }
+ let(:lease_timeout) { described_class::LEASE_TIMEOUT }
+ let(:migration_service) { ::Projects::HashedStorage::MigrationService }
+
+ it 'skips when project no longer exists' do
+ expect(migration_service).not_to receive(:new)
+
+ subject.perform(-1)
+ end
+
+ it 'skips when project is pending delete' do
+ pending_delete_project = create(:project, :empty_repo, pending_delete: true)
+
+ expect(migration_service).not_to receive(:new)
+
+ subject.perform(pending_delete_project.id)
+ end
+
+ it 'delegates migration to service class when we have exclusive lease' do
+ stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout)
+
+ service_spy = spy
+
+ allow(migration_service)
+ .to receive(:new).with(project, project.full_path, logger: subject.logger)
+ .and_return(service_spy)
+
+ subject.perform(project.id)
+
+ expect(service_spy).to have_received(:execute)
+ end
+
+ it 'skips when it cant acquire the exclusive lease' do
+ stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
+
+ expect(migration_service).not_to receive(:new)
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/hashed_storage/project_rollback_worker_spec.rb b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
new file mode 100644
index 00000000000..d833553c0ec
--- /dev/null
+++ b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ describe '#perform' do
+ let(:project) { create(:project, :empty_repo) }
+ let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" }
+ let(:lease_timeout) { described_class::LEASE_TIMEOUT }
+ let(:rollback_service) { ::Projects::HashedStorage::RollbackService }
+
+ it 'skips when project no longer exists' do
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(-1)
+ end
+
+ it 'skips when project is pending delete' do
+ pending_delete_project = create(:project, :empty_repo, pending_delete: true)
+
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(pending_delete_project.id)
+ end
+
+ it 'delegates rollback to service class when have exclusive lease' do
+ stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout)
+
+ service_spy = spy
+
+ allow(rollback_service)
+ .to receive(:new).with(project, project.disk_path, logger: subject.logger)
+ .and_return(service_spy)
+
+ subject.perform(project.id)
+
+ expect(service_spy).to have_received(:execute)
+ end
+
+ it 'skips when it cant acquire the exclusive lease' do
+ stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
+
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
new file mode 100644
index 00000000000..4055f380978
--- /dev/null
+++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HashedStorage::RollbackerWorker do
+ subject(:worker) { described_class.new }
+ let(:projects) { create_list(:project, 2, :empty_repo) }
+ let(:ids) { projects.map(&:id) }
+
+ describe '#perform' do
+ it 'delegates to MigratorService' do
+ expect_any_instance_of(Gitlab::HashedStorage::Migrator).to receive(:bulk_rollback).with(start: 5, finish: 10)
+
+ worker.perform(5, 10)
+ end
+
+ it 'rollsback projects in the specified range' do
+ perform_enqueued_jobs do
+ worker.perform(ids.min, ids.max)
+ end
+
+ projects.each do |project|
+ expect(project.reload.legacy_storage?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
index 5972696515b..4f727469ea8 100644
--- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb
+++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InvalidGpgSignatureUpdateWorker do
diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb
index 2710267d384..61ad8330840 100644
--- a/spec/workers/issue_due_scheduler_worker_spec.rb
+++ b/spec/workers/issue_due_scheduler_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueDueSchedulerWorker do
diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
index 1026ae5b4bf..fa17775e9f2 100644
--- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb
+++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MailScheduler::IssueDueWorker do
diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
index 5cfba01850c..0729c5f9ffb 100644
--- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb
+++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MailScheduler::NotificationServiceWorker do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index b57c275c770..138a99abde6 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeWorker do
diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb
new file mode 100644
index 00000000000..88d48cad14b
--- /dev/null
+++ b/spec/workers/migrate_external_diffs_worker_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MigrateExternalDiffsWorker do
+ let(:worker) { described_class.new }
+ let(:diff) { create(:merge_request).merge_request_diff }
+
+ describe '#perform' do
+ it 'migrates the listed diff' do
+ expect_next_instance_of(MergeRequests::MigrateExternalDiffsService) do |instance|
+ expect(instance.diff).to eq(diff)
+ expect(instance).to receive(:execute)
+ end
+
+ worker.perform(diff.id)
+ end
+
+ it 'does nothing if the diff is missing' do
+ diff.destroy
+
+ worker.perform(diff.id)
+ end
+ end
+end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index 2f21a1321e1..4fbda37e268 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NamespacelessProjectDestroyWorker do
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index baa8ddb59e5..88a75ce5b70 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NewIssueWorker do
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index c3f29a40d58..d078ddd07d9 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NewMergeRequestWorker do
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 575361c93d4..2966a201a62 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe NewNoteWorker do
diff --git a/spec/workers/pages_domain_removal_cron_worker_spec.rb b/spec/workers/pages_domain_removal_cron_worker_spec.rb
new file mode 100644
index 00000000000..2408ad54189
--- /dev/null
+++ b/spec/workers/pages_domain_removal_cron_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainRemovalCronWorker do
+ subject(:worker) { described_class.new }
+
+ describe '#perform' do
+ context 'when there is domain which should be removed' do
+ let!(:domain_for_removal) { create(:pages_domain, :should_be_removed) }
+
+ it 'removes domain' do
+ expect { worker.perform }.to change { PagesDomain.count }.by(-1)
+ expect(PagesDomain.exists?).to eq(false)
+ end
+ end
+
+ context 'where there is a domain which scheduled for removal in the future' do
+ let!(:domain_for_removal) { create(:pages_domain, :scheduled_for_removal) }
+
+ it 'does not remove pages domain' do
+ expect { worker.perform }.not_to change { PagesDomain.count }
+ expect(PagesDomain.find_by(domain: domain_for_removal.domain)).to be_present
+ end
+ end
+ end
+end
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 8f780428c82..3fb86adee11 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -1,14 +1,23 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomainVerificationCronWorker do
subject(:worker) { described_class.new }
describe '#perform' do
- it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do
- verified = create(:pages_domain)
- reverify = create(:pages_domain, :reverify)
- disabled = create(:pages_domain, :disabled)
+ let!(:verified) { create(:pages_domain) }
+ let!(:reverify) { create(:pages_domain, :reverify) }
+ let!(:disabled) { create(:pages_domain, :disabled) }
+ it 'does nothing if the database is read-only' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(reverify.id)
+
+ worker.perform
+ end
+
+ it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do
[reverify, disabled].each do |domain|
expect(PagesDomainVerificationWorker).to receive(:perform_async).with(domain.id)
end
diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb
index 372fc95ab4a..f51ac1f4323 100644
--- a/spec/workers/pages_domain_verification_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomainVerificationWorker do
@@ -6,6 +8,13 @@ describe PagesDomainVerificationWorker do
let(:domain) { create(:pages_domain) }
describe '#perform' do
+ it 'does nothing if the database is read-only' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ expect(PagesDomain).not_to receive(:find_by).with(id: domain.id)
+
+ worker.perform(domain.id)
+ end
+
it 'does nothing for a non-existent domain' do
domain.destroy
diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb
index 035e329839f..60df08f40da 100644
--- a/spec/workers/pipeline_hooks_worker_spec.rb
+++ b/spec/workers/pipeline_hooks_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineHooksWorker do
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 896f9e6e7f2..6beecbcd114 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineMetricsWorker do
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index eb539ffd893..98b0f139fe2 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineNotificationWorker, :mailer do
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index 86e9d7f6684..d33cf72e51e 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineProcessWorker do
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index ff408427926..9326db34209 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineScheduleWorker do
+ include ExclusiveLeaseHelpers
+
subject { described_class.new.perform }
set(:project) { create(:project, :repository) }
@@ -63,47 +67,19 @@ describe PipelineScheduleWorker do
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { variables: 'rspec' } ))
end
- it 'creates a failed pipeline with the reason' do
- expect { subject }.to change { project.ci_pipelines.count }.by(1)
- expect(Ci::Pipeline.last).to be_config_error
- expect(Ci::Pipeline.last.yaml_errors).not_to be_nil
+ it 'does not creates a new pipeline' do
+ expect { subject }.not_to change { project.ci_pipelines.count }
end
end
end
context 'when the schedule is not runnable by the user' do
- before do
- expect(Gitlab::Sentry)
- .to receive(:track_exception)
- .with(Ci::CreatePipelineService::CreateError,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: pipeline_schedule.id } ).once
- end
-
it 'does not deactivate the schedule' do
subject
expect(pipeline_schedule.reload.active).to be_truthy
end
- it 'increments Prometheus counter' do
- expect(Gitlab::Metrics)
- .to receive(:counter)
- .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation")
- .and_call_original
-
- subject
- end
-
- it 'logging a pipeline error' do
- expect(Rails.logger)
- .to receive(:error)
- .with(a_string_matching("Insufficient permissions to create a new pipeline"))
- .and_call_original
-
- subject
- end
-
it 'does not create a pipeline' do
expect { subject }.not_to change { project.ci_pipelines.count }
end
@@ -117,21 +93,6 @@ describe PipelineScheduleWorker do
before do
stub_ci_pipeline_yaml_file(nil)
project.add_maintainer(user)
-
- expect(Gitlab::Sentry)
- .to receive(:track_exception)
- .with(Ci::CreatePipelineService::CreateError,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: pipeline_schedule.id } ).once
- end
-
- it 'logging a pipeline error' do
- expect(Rails.logger)
- .to receive(:error)
- .with(a_string_matching("Missing .gitlab-ci.yml file"))
- .and_call_original
-
- subject
end
it 'does not create a pipeline' do
diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb
deleted file mode 100644
index d1c84adda6f..00000000000
--- a/spec/workers/pipeline_success_worker_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe PipelineSuccessWorker do
- describe '#perform' do
- context 'when pipeline exists' do
- let(:pipeline) { create(:ci_pipeline, status: 'success') }
-
- it 'performs "merge when pipeline succeeds"' do
- expect_any_instance_of(
- MergeRequests::MergeWhenPipelineSucceedsService
- ).to receive(:trigger)
-
- described_class.new.perform(pipeline.id)
- end
- end
-
- context 'when pipeline does not exist' do
- it 'does not raise exception' do
- expect { described_class.new.perform(123) }
- .not_to raise_error
- end
- end
- end
-end
diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb
index 0b456cfd0da..0225e4a9601 100644
--- a/spec/workers/pipeline_update_worker_spec.rb
+++ b/spec/workers/pipeline_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineUpdateWorker do
diff --git a/spec/workers/plugin_worker_spec.rb b/spec/workers/plugin_worker_spec.rb
index 9238a8199bc..ca6c9986131 100644
--- a/spec/workers/plugin_worker_spec.rb
+++ b/spec/workers/plugin_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PluginWorker do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index caae46a3175..39f1beb4efa 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PostReceive do
@@ -33,8 +35,8 @@ describe PostReceive do
describe "#process_project_changes" do
context 'empty changes' do
it "does not call any PushService but runs after project hooks" do
- expect(GitPushService).not_to receive(:new)
- expect(GitTagPushService).not_to receive(:new)
+ expect(Git::BranchPushService).not_to receive(:new)
+ expect(Git::TagPushService).not_to receive(:new)
expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
described_class.new.perform(gl_repository, key_id, "")
@@ -45,8 +47,8 @@ describe PostReceive do
let!(:key_id) { "" }
it 'returns false' do
- expect(GitPushService).not_to receive(:new)
- expect(GitTagPushService).not_to receive(:new)
+ expect(Git::BranchPushService).not_to receive(:new)
+ expect(Git::TagPushService).not_to receive(:new)
expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false
end
@@ -60,9 +62,13 @@ describe PostReceive do
context "branches" do
let(:changes) { "123456 789012 refs/heads/tést" }
- it "calls GitPushService" do
- expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ it "calls Git::BranchPushService" do
+ expect_next_instance_of(Git::BranchPushService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
+ expect(Git::TagPushService).not_to receive(:new)
+
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -70,9 +76,13 @@ describe PostReceive do
context "tags" do
let(:changes) { "123456 789012 refs/tags/tag" }
- it "calls GitTagPushService" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
+ it "calls Git::TagPushService" do
+ expect(Git::BranchPushService).not_to receive(:execute)
+
+ expect_next_instance_of(Git::TagPushService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -81,18 +91,29 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/merge-requests/123" }
it "does not call any of the services" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ expect(Git::BranchPushService).not_to receive(:new)
+ expect(Git::TagPushService).not_to receive(:new)
+
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
context "gitlab-ci.yml" do
- let(:changes) { "123456 789012 refs/heads/feature\n654321 210987 refs/tags/tag" }
+ let(:changes) do
+ <<-EOF.strip_heredoc
+ 123456 789012 refs/heads/feature
+ 654321 210987 refs/tags/tag
+ 123456 789012 refs/heads/feature2
+ 123458 789013 refs/heads/feature3
+ 123459 789015 refs/heads/feature4
+ EOF
+ end
+
+ let(:changes_count) { changes.lines.count }
subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
- context "creates a Ci::Pipeline for every change" do
+ context "with valid .gitlab-ci.yml" do
before do
stub_ci_pipeline_to_return_yaml_file
@@ -105,7 +126,33 @@ describe PostReceive do
.and_return(true)
end
- it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
+ context 'when git_push_create_all_pipelines is disabled' do
+ before do
+ stub_feature_flags(git_push_create_all_pipelines: false)
+ end
+
+ it "creates pipeline for branches and tags" do
+ subject
+
+ expect(Ci::Pipeline.pluck(:ref)).to contain_exactly("feature", "tag", "feature2", "feature3")
+ end
+
+ it "creates exactly #{described_class::PIPELINE_PROCESS_LIMIT} pipelines" do
+ expect(changes_count).to be > described_class::PIPELINE_PROCESS_LIMIT
+
+ expect { subject }.to change { Ci::Pipeline.count }.by(described_class::PIPELINE_PROCESS_LIMIT)
+ end
+ end
+
+ context 'when git_push_create_all_pipelines is enabled' do
+ before do
+ stub_feature_flags(git_push_create_all_pipelines: true)
+ end
+
+ it "creates all pipelines" do
+ expect { subject }.to change { Ci::Pipeline.count }.by(changes_count)
+ end
+ end
end
context "does not create a Ci::Pipeline" do
@@ -125,7 +172,9 @@ describe PostReceive do
allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
# silence hooks so we can isolate
allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
- allow_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+ expect_next_instance_of(Git::BranchPushService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
end
it 'calls SystemHooksService' do
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 2d071c181c2..47bac63511e 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProcessCommitWorker do
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index b9b5445562f..51afb076da1 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectCacheWorker do
@@ -5,9 +7,9 @@ describe ProjectCacheWorker do
let(:worker) { described_class.new }
let(:project) { create(:project, :repository) }
- let(:statistics) { project.statistics }
- let(:lease_key) { "project_cache_worker:#{project.id}:update_statistics" }
+ let(:lease_key) { ["project_cache_worker", project.id, *statistics.sort].join(":") }
let(:lease_timeout) { ProjectCacheWorker::LEASE_TIMEOUT }
+ let(:statistics) { [] }
describe '#perform' do
before do
@@ -23,24 +25,17 @@ describe ProjectCacheWorker do
end
context 'with an existing project without a repository' do
- it 'does nothing' do
+ it 'updates statistics but does not refresh the method cashes' do
allow_any_instance_of(Repository).to receive(:exists?).and_return(false)
- expect(worker).not_to receive(:update_statistics)
+ expect(worker).to receive(:update_statistics)
+ expect_any_instance_of(Repository).not_to receive(:refresh_method_caches)
worker.perform(project.id)
end
end
context 'with an existing project' do
- it 'updates the project statistics' do
- expect(worker).to receive(:update_statistics)
- .with(kind_of(Project), %i(repository_size))
- .and_call_original
-
- worker.perform(project.id, [], %w(repository_size))
- end
-
it 'refreshes the method caches' do
expect_any_instance_of(Repository).to receive(:refresh_method_caches)
.with(%i(readme))
@@ -49,6 +44,18 @@ describe ProjectCacheWorker do
worker.perform(project.id, %w(readme))
end
+ context 'with statistics' do
+ let(:statistics) { %w(repository_size) }
+
+ it 'updates the project statistics' do
+ expect(worker).to receive(:update_statistics)
+ .with(kind_of(Project), statistics)
+ .and_call_original
+
+ worker.perform(project.id, [], statistics)
+ end
+ end
+
context 'with plain readme' do
it 'refreshes the method caches' do
allow(MarkupHelper).to receive(:gitlab_markdown?).and_return(false)
@@ -64,25 +71,34 @@ describe ProjectCacheWorker do
end
describe '#update_statistics' do
+ let(:statistics) { %w(repository_size) }
+
context 'when a lease could not be obtained' do
- it 'does not update the repository size' do
+ it 'does not update the project statistics' do
stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
- expect(statistics).not_to receive(:refresh!)
+ expect(Projects::UpdateStatisticsService).not_to receive(:new)
+
+ expect(UpdateProjectStatisticsWorker).not_to receive(:perform_in)
- worker.update_statistics(project)
+ worker.update_statistics(project, statistics)
end
end
context 'when a lease could be obtained' do
- it 'updates the project statistics' do
+ it 'updates the project statistics twice' do
stub_exclusive_lease(lease_key, timeout: lease_timeout)
- expect(statistics).to receive(:refresh!)
- .with(only: %i(repository_size))
+ expect(Projects::UpdateStatisticsService).to receive(:new)
+ .with(project, nil, statistics: statistics)
+ .and_call_original
+ .twice
+
+ expect(UpdateProjectStatisticsWorker).to receive(:perform_in)
+ .with(lease_timeout, project.id, statistics)
.and_call_original
- worker.update_statistics(project, %i(repository_size))
+ worker.update_statistics(project, statistics)
end
end
end
diff --git a/spec/workers/project_daily_statistics_worker_spec.rb b/spec/workers/project_daily_statistics_worker_spec.rb
new file mode 100644
index 00000000000..8640add99e5
--- /dev/null
+++ b/spec/workers/project_daily_statistics_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe ProjectDailyStatisticsWorker, '#perform' do
+ let(:worker) { described_class.new }
+ let(:project) { create(:project) }
+
+ describe '#perform' do
+ context 'with a non-existing project' do
+ it 'does nothing' do
+ expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
+
+ worker.perform(-1)
+ end
+ end
+
+ context 'with an existing project without a repository' do
+ it 'does nothing' do
+ expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
+
+ worker.perform(project.id)
+ end
+ end
+
+ it 'calls daily_statistics_service with the given project' do
+ project = create(:project, :repository)
+
+ expect_next_instance_of(Projects::FetchStatisticsIncrementService, project) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ worker.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 6132f145f8d..ec40900a5b7 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectDestroyWorker do
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index 8899969c178..8065087796c 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectExportWorker do
diff --git a/spec/workers/project_migrate_hashed_storage_worker_spec.rb b/spec/workers/project_migrate_hashed_storage_worker_spec.rb
deleted file mode 100644
index 333eb6a0569..00000000000
--- a/spec/workers/project_migrate_hashed_storage_worker_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe ProjectMigrateHashedStorageWorker, :clean_gitlab_redis_shared_state do
- include ExclusiveLeaseHelpers
-
- describe '#perform' do
- let(:project) { create(:project, :empty_repo, :legacy_storage) }
- let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" }
- let(:lease_timeout) { described_class::LEASE_TIMEOUT }
- let(:migration_service) { ::Projects::HashedStorage::MigrationService }
-
- it 'skips when project no longer exists' do
- expect(migration_service).not_to receive(:new)
-
- subject.perform(-1)
- end
-
- it 'skips when project is pending delete' do
- pending_delete_project = create(:project, :empty_repo, pending_delete: true)
-
- expect(migration_service).not_to receive(:new)
-
- subject.perform(pending_delete_project.id)
- end
-
- it 'delegates migration to service class when we have exclusive lease' do
- stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout)
-
- service_spy = spy
-
- allow(migration_service)
- .to receive(:new).with(project, project.full_path, logger: subject.logger)
- .and_return(service_spy)
-
- subject.perform(project.id)
-
- expect(service_spy).to have_received(:execute)
- end
-
- it 'skips when it cant acquire the exclusive lease' do
- stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
-
- expect(migration_service).not_to receive(:new)
-
- subject.perform(project.id)
- end
- end
-end
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
index af1fb80a51d..fb4ced77832 100644
--- a/spec/workers/propagate_service_template_worker_spec.rb
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PropagateServiceTemplateWorker do
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index ea2b6ae229e..f1eef1923bf 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PruneOldEventsWorker do
diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb
index b3ec71d4a00..e57334967fd 100644
--- a/spec/workers/prune_web_hook_logs_worker_spec.rb
+++ b/spec/workers/prune_web_hook_logs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PruneWebHookLogsWorker do
diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb
index 3da851de067..2395e6ec947 100644
--- a/spec/workers/reactive_caching_worker_spec.rb
+++ b/spec/workers/reactive_caching_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ReactiveCachingWorker do
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 900332ed6b3..578b8cf7451 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RebaseWorker, '#perform' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
index e3db10ed645..5182f67b4af 100644
--- a/spec/workers/remote_mirror_notification_worker_spec.rb
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoteMirrorNotificationWorker, :mailer do
diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb
index 689bc3d27b4..10d9aa37dee 100644
--- a/spec/workers/remove_expired_group_links_worker_spec.rb
+++ b/spec/workers/remove_expired_group_links_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveExpiredGroupLinksWorker do
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index 058fdf4c009..69a5725bb35 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveExpiredMembersWorker do
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 57f83c1dbe9..0e21933a9a5 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveUnreferencedLfsObjectsWorker do
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index 50b93fce2dc..051c6a5d141 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::BatchWorker do
diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb
index 1c49415d46c..7ad9e287204 100644
--- a/spec/workers/repository_check/clear_worker_spec.rb
+++ b/spec/workers/repository_check/clear_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::ClearWorker do
diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb
index 7877429aa8f..03efb6a0a80 100644
--- a/spec/workers/repository_check/dispatch_worker_spec.rb
+++ b/spec/workers/repository_check/dispatch_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::DispatchWorker do
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index f11875cffd1..65e1c5e9d5d 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'fileutils'
diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb
index 3adae0b6cfa..e58664cf22a 100644
--- a/spec/workers/repository_cleanup_worker_spec.rb
+++ b/spec/workers/repository_cleanup_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCleanupWorker do
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 31bfe88d0bd..26fd67adfaa 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryForkWorker do
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 87ac4bc05c1..b8767af8eee 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryImportWorker do
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index 6ddb653d142..6eba5c50960 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RepositoryRemoveRemoteWorker do
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index b582a3650b6..4de51ecb3e9 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RepositoryUpdateRemoteMirrorWorker do
diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb
index 481a84837f9..7414470f8e7 100644
--- a/spec/workers/run_pipeline_schedule_worker_spec.rb
+++ b/spec/workers/run_pipeline_schedule_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RunPipelineScheduleWorker do
@@ -30,7 +32,37 @@ describe RunPipelineScheduleWorker do
it 'calls the Service' do
expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service)
- expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule)
+ expect(create_pipeline_service).to receive(:execute!).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule)
+
+ worker.perform(pipeline_schedule.id, user.id)
+ end
+ end
+
+ context 'when database statement timeout happens' do
+ before do
+ allow(Ci::CreatePipelineService).to receive(:new) { raise ActiveRecord::StatementInvalid }
+
+ expect(Gitlab::Sentry)
+ .to receive(:track_exception)
+ .with(ActiveRecord::StatementInvalid,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
+ extra: { schedule_id: pipeline_schedule.id } ).once
+ end
+
+ it 'increments Prometheus counter' do
+ expect(Gitlab::Metrics)
+ .to receive(:counter)
+ .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation")
+ .and_call_original
+
+ worker.perform(pipeline_schedule.id, user.id)
+ end
+
+ it 'logging a pipeline error' do
+ expect(Rails.logger)
+ .to receive(:error)
+ .with(a_string_matching('ActiveRecord::StatementInvalid'))
+ .and_call_original
worker.perform(pipeline_schedule.id, user.id)
end
diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
new file mode 100644
index 00000000000..9d6fecc9f4e
--- /dev/null
+++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ScheduleMigrateExternalDiffsWorker do
+ include ExclusiveLeaseHelpers
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'triggers a scan for diffs to migrate' do
+ expect(MergeRequests::MigrateExternalDiffsService).to receive(:enqueue!)
+
+ worker.perform
+ end
+
+ it 'will not run if the lease is already taken' do
+ stub_exclusive_lease_taken('schedule_migrate_external_diffs_worker', timeout: 2.hours)
+
+ expect(MergeRequests::MigrateExternalDiffsService).not_to receive(:enqueue!)
+
+ worker.perform
+ end
+ end
+end
diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb
index 7bc76c79464..429d42bac29 100644
--- a/spec/workers/stage_update_worker_spec.rb
+++ b/spec/workers/stage_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StageUpdateWorker do
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index e09b8e5b964..72de62f1188 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckCiJobsWorker do
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
index e94d2be9850..dcb8e59ed28 100644
--- a/spec/workers/stuck_import_jobs_worker_spec.rb
+++ b/spec/workers/stuck_import_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckImportJobsWorker do
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index 5aaff27a6b2..09efed6d2cf 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckMergeJobsWorker do
diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb
index b1d446ed25f..890a622d11a 100644
--- a/spec/workers/system_hook_push_worker_spec.rb
+++ b/spec/workers/system_hook_push_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemHookPushWorker do
diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
index 9d7c0b8f560..0907e2768ba 100644
--- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
+++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
@@ -1,12 +1,21 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::ConfidentialIssueWorker do
- it "calls the Todos::Destroy::ConfidentialIssueService with the params it was given" do
- service = double
+ let(:service) { double }
- expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(100).and_return(service)
+ it "calls the Todos::Destroy::ConfidentialIssueService with issue_id parameter" do
+ expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: 100, project_id: nil).and_return(service)
expect(service).to receive(:execute)
described_class.new.perform(100)
end
+
+ it "calls the Todos::Destroy::ConfidentialIssueService with project_id parameter" do
+ expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: nil, project_id: 100).and_return(service)
+ expect(service).to receive(:execute)
+
+ described_class.new.perform(nil, 100)
+ end
end
diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
index 955447906aa..cb14fac0910 100644
--- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
+++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::EntityLeaveWorker do
diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb
index fcc38989ced..d9a240136d5 100644
--- a/spec/workers/todos_destroyer/group_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::GroupPrivateWorker do
diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb
index 9599f5ee071..abd04acc3bd 100644
--- a/spec/workers/todos_destroyer/private_features_worker_spec.rb
+++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::PrivateFeaturesWorker do
diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb
index 15d926fa9d5..c1bb0438ec3 100644
--- a/spec/workers/todos_destroyer/project_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::ProjectPrivateWorker do
diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb
index c3c6fdcf2d5..6e524085662 100644
--- a/spec/workers/trending_projects_worker_spec.rb
+++ b/spec/workers/trending_projects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TrendingProjectsWorker do
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 963237ceadf..c4af829a5e2 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateHeadPipelineForMergeRequestWorker do
@@ -18,7 +20,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do
context 'when merge request sha does not equal pipeline sha' do
before do
- merge_request.merge_request_diff.update(head_commit_sha: 'different_sha')
+ merge_request.merge_request_diff.update(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex))
end
it 'does not update head pipeline' do
@@ -39,7 +41,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do
let!(:merge_request_pipeline) do
create(:ci_pipeline,
project: project,
- source: :merge_request,
+ source: :merge_request_event,
sha: latest_sha,
merge_request: merge_request)
end
diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb
index 0b553db0ca4..486dade454a 100644
--- a/spec/workers/update_merge_requests_worker_spec.rb
+++ b/spec/workers/update_merge_requests_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateMergeRequestsWorker do
diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb
new file mode 100644
index 00000000000..a268fd2e4ba
--- /dev/null
+++ b/spec/workers/update_project_statistics_worker_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe UpdateProjectStatisticsWorker do
+ let(:worker) { described_class.new }
+ let(:project) { create(:project, :repository) }
+ let(:statistics) { %w(repository_size) }
+
+ describe '#perform' do
+ it 'updates the project statistics' do
+ expect(Projects::UpdateStatisticsService).to receive(:new)
+ .with(project, nil, statistics: statistics)
+ .and_call_original
+
+ worker.perform(project.id, statistics)
+ end
+ end
+end
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 9e50ce15871..7202c8001b4 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UploadChecksumWorker do
diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb
index 0e92b298178..850eba263a7 100644
--- a/spec/workers/wait_for_cluster_creation_worker_spec.rb
+++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WaitForClusterCreationWorker do
diff --git a/vendor/assets/javascripts/jquery.atwho.js b/vendor/assets/javascripts/jquery.atwho.js
deleted file mode 100644
index e058e13303a..00000000000
--- a/vendor/assets/javascripts/jquery.atwho.js
+++ /dev/null
@@ -1,1202 +0,0 @@
-/**
- * at.js - 1.5.1
- * Copyright (c) 2016 chord.luo <chord.luo@gmail.com>;
- * Homepage: http://ichord.github.com/At.js
- * License: MIT
- */
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module unless amdModuleId is set
- define(["jquery"], function (a0) {
- return (factory(a0));
- });
- } else if (typeof exports === 'object') {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like environments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"));
- } else {
- factory(jQuery);
- }
-}(this, function ($) {
-var DEFAULT_CALLBACKS, KEY_CODE;
-
-KEY_CODE = {
- DOWN: 40,
- UP: 38,
- ESC: 27,
- TAB: 9,
- ENTER: 13,
- CTRL: 17,
- A: 65,
- P: 80,
- N: 78,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- BACKSPACE: 8,
- SPACE: 32
-};
-
-DEFAULT_CALLBACKS = {
- beforeSave: function(data) {
- return Controller.arrayToDefaultHash(data);
- },
- matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
- var _a, _y, match, regexp, space;
- flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
- if (should_startWithSpace) {
- flag = '(?:^|\\s)' + flag;
- }
- _a = decodeURI("%C3%80");
- _y = decodeURI("%C3%BF");
- space = acceptSpaceBar ? "\ " : "";
- regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
- match = regexp.exec(subtext);
- if (match) {
- return match[2] || match[1];
- } else {
- return null;
- }
- },
- filter: function(query, data, searchKey) {
- var _results, i, item, len;
- _results = [];
- for (i = 0, len = data.length; i < len; i++) {
- item = data[i];
- if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
- _results.push(item);
- }
- }
- return _results;
- },
- remoteFilter: null,
- sorter: function(query, items, searchKey) {
- var _results, i, item, len;
- if (!query) {
- return items;
- }
- _results = [];
- for (i = 0, len = items.length; i < len; i++) {
- item = items[i];
- item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
- if (item.atwho_order > -1) {
- _results.push(item);
- }
- }
- return _results.sort(function(a, b) {
- return a.atwho_order - b.atwho_order;
- });
- },
- tplEval: function(tpl, map) {
- var error, error1, template;
- template = tpl;
- try {
- if (typeof tpl !== 'string') {
- template = tpl(map);
- }
- return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
- return map[key];
- });
- } catch (error1) {
- error = error1;
- return "";
- }
- },
- highlighter: function(li, query) {
- var regexp;
- if (!query) {
- return li;
- }
- regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
- return li.replace(regexp, function(str, $1, $2, $3) {
- return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
- });
- },
- beforeInsert: function(value, $li, e) {
- return value;
- },
- beforeReposition: function(offset) {
- return offset;
- },
- afterMatchFailed: function(at, el) {}
-};
-
-var App;
-
-App = (function() {
- function App(inputor) {
- this.currentFlag = null;
- this.controllers = {};
- this.aliasMaps = {};
- this.$inputor = $(inputor);
- this.setupRootElement();
- this.listen();
- }
-
- App.prototype.createContainer = function(doc) {
- var ref;
- if ((ref = this.$el) != null) {
- ref.remove();
- }
- return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
- };
-
- App.prototype.setupRootElement = function(iframe, asRoot) {
- var error, error1;
- if (asRoot == null) {
- asRoot = false;
- }
- if (iframe) {
- this.window = iframe.contentWindow;
- this.document = iframe.contentDocument || this.window.document;
- this.iframe = iframe;
- } else {
- this.document = this.$inputor[0].ownerDocument;
- this.window = this.document.defaultView || this.document.parentWindow;
- try {
- this.iframe = this.window.frameElement;
- } catch (error1) {
- error = error1;
- this.iframe = null;
- if ($.fn.atwho.debug) {
- throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
- }
- }
- }
- return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
- };
-
- App.prototype.controller = function(at) {
- var c, current, currentFlag, ref;
- if (this.aliasMaps[at]) {
- current = this.controllers[this.aliasMaps[at]];
- } else {
- ref = this.controllers;
- for (currentFlag in ref) {
- c = ref[currentFlag];
- if (currentFlag === at) {
- current = c;
- break;
- }
- }
- }
- if (current) {
- return current;
- } else {
- return this.controllers[this.currentFlag];
- }
- };
-
- App.prototype.setContextFor = function(at) {
- this.currentFlag = at;
- return this;
- };
-
- App.prototype.reg = function(flag, setting) {
- var base, controller;
- controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
- if (setting.alias) {
- this.aliasMaps[setting.alias] = flag;
- }
- controller.init(setting);
- return this;
- };
-
- App.prototype.listen = function() {
- return this.$inputor.on('compositionstart', (function(_this) {
- return function(e) {
- var ref;
- if ((ref = _this.controller()) != null) {
- ref.view.hide();
- }
- _this.isComposing = true;
- return null;
- };
- })(this)).on('compositionend', (function(_this) {
- return function(e) {
- _this.isComposing = false;
- setTimeout(function(e) {
- return _this.dispatch(e);
- });
- return null;
- };
- })(this)).on('keyup.atwhoInner', (function(_this) {
- return function(e) {
- return _this.onKeyup(e);
- };
- })(this)).on('keydown.atwhoInner', (function(_this) {
- return function(e) {
- return _this.onKeydown(e);
- };
- })(this)).on('blur.atwhoInner', (function(_this) {
- return function(e) {
- var c;
- if (c = _this.controller()) {
- c.expectedQueryCBId = null;
- return c.view.hide(e, c.getOpt("displayTimeout"));
- }
- };
- })(this)).on('click.atwhoInner', (function(_this) {
- return function(e) {
- return _this.dispatch(e);
- };
- })(this)).on('scroll.atwhoInner', (function(_this) {
- return function() {
- var lastScrollTop;
- lastScrollTop = _this.$inputor.scrollTop();
- return function(e) {
- var currentScrollTop, ref;
- currentScrollTop = e.target.scrollTop;
- if (lastScrollTop !== currentScrollTop) {
- if ((ref = _this.controller()) != null) {
- ref.view.hide(e);
- }
- }
- lastScrollTop = currentScrollTop;
- return true;
- };
- };
- })(this)());
- };
-
- App.prototype.shutdown = function() {
- var _, c, ref;
- ref = this.controllers;
- for (_ in ref) {
- c = ref[_];
- c.destroy();
- delete this.controllers[_];
- }
- this.$inputor.off('.atwhoInner');
- return this.$el.remove();
- };
-
- App.prototype.dispatch = function(e) {
- var _, c, ref, results;
- ref = this.controllers;
- results = [];
- for (_ in ref) {
- c = ref[_];
- results.push(c.lookUp(e));
- }
- return results;
- };
-
- App.prototype.onKeyup = function(e) {
- var ref;
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- if ((ref = this.controller()) != null) {
- ref.view.hide();
- }
- break;
- case KEY_CODE.DOWN:
- case KEY_CODE.UP:
- case KEY_CODE.CTRL:
- case KEY_CODE.ENTER:
- $.noop();
- break;
- case KEY_CODE.P:
- case KEY_CODE.N:
- if (!e.ctrlKey) {
- this.dispatch(e);
- }
- break;
- default:
- this.dispatch(e);
- }
- };
-
- App.prototype.onKeydown = function(e) {
- var ref, view;
- view = (ref = this.controller()) != null ? ref.view : void 0;
- if (!(view && view.visible())) {
- return;
- }
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- view.hide(e);
- break;
- case KEY_CODE.UP:
- e.preventDefault();
- view.prev();
- break;
- case KEY_CODE.DOWN:
- e.preventDefault();
- view.next();
- break;
- case KEY_CODE.P:
- if (!e.ctrlKey) {
- return;
- }
- e.preventDefault();
- view.prev();
- break;
- case KEY_CODE.N:
- if (!e.ctrlKey) {
- return;
- }
- e.preventDefault();
- view.next();
- break;
- case KEY_CODE.TAB:
- case KEY_CODE.ENTER:
- case KEY_CODE.SPACE:
- if (!view.visible()) {
- return;
- }
- if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
- return;
- }
- if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
- return;
- }
- if (view.highlighted()) {
- e.preventDefault();
- view.choose(e);
- } else {
- view.hide(e);
- }
- break;
- default:
- $.noop();
- }
- };
-
- return App;
-
-})();
-
-var Controller,
- slice = [].slice;
-
-Controller = (function() {
- Controller.prototype.uid = function() {
- return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
- };
-
- function Controller(app, at1) {
- this.app = app;
- this.at = at1;
- this.$inputor = this.app.$inputor;
- this.id = this.$inputor[0].id || this.uid();
- this.expectedQueryCBId = null;
- this.setting = null;
- this.query = null;
- this.pos = 0;
- this.range = null;
- if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
- this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
- }
- this.model = new Model(this);
- this.view = new View(this);
- }
-
- Controller.prototype.init = function(setting) {
- this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
- this.view.init();
- return this.model.reload(this.setting.data);
- };
-
- Controller.prototype.destroy = function() {
- this.trigger('beforeDestroy');
- this.model.destroy();
- this.view.destroy();
- return this.$el.remove();
- };
-
- Controller.prototype.callDefault = function() {
- var args, error, error1, funcName;
- funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
- try {
- return DEFAULT_CALLBACKS[funcName].apply(this, args);
- } catch (error1) {
- error = error1;
- return $.error(error + " Or maybe At.js doesn't have function " + funcName);
- }
- };
-
- Controller.prototype.trigger = function(name, data) {
- var alias, eventName;
- if (data == null) {
- data = [];
- }
- data.push(this);
- alias = this.getOpt('alias');
- eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
- return this.$inputor.trigger(eventName, data);
- };
-
- Controller.prototype.callbacks = function(funcName) {
- return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
- };
-
- Controller.prototype.getOpt = function(at, default_value) {
- var e, error1;
- try {
- return this.setting[at];
- } catch (error1) {
- e = error1;
- return null;
- }
- };
-
- Controller.prototype.insertContentFor = function($li) {
- var data, tpl;
- tpl = this.getOpt('insertTpl');
- data = $.extend({}, $li.data('itemData'), {
- 'atwho-at': this.at
- });
- return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
- };
-
- Controller.prototype.renderView = function(data) {
- var searchKey;
- searchKey = this.getOpt("searchKey");
- data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
- return this.view.render(data.slice(0, this.getOpt('limit')));
- };
-
- Controller.arrayToDefaultHash = function(data) {
- var i, item, len, results;
- if (!$.isArray(data)) {
- return data;
- }
- results = [];
- for (i = 0, len = data.length; i < len; i++) {
- item = data[i];
- if ($.isPlainObject(item)) {
- results.push(item);
- } else {
- results.push({
- name: item
- });
- }
- }
- return results;
- };
-
- Controller.prototype.lookUp = function(e) {
- var query, wait;
- if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
- return;
- }
- if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
- return;
- }
- query = this.catchQuery(e);
- if (!query) {
- this.expectedQueryCBId = null;
- return query;
- }
- this.app.setContextFor(this.at);
- if (wait = this.getOpt('delay')) {
- this._delayLookUp(query, wait);
- } else {
- this._lookUp(query);
- }
- return query;
- };
-
- Controller.prototype._delayLookUp = function(query, wait) {
- var now, remaining;
- now = Date.now ? Date.now() : new Date().getTime();
- this.previousCallTime || (this.previousCallTime = now);
- remaining = wait - (now - this.previousCallTime);
- if ((0 < remaining && remaining < wait)) {
- this.previousCallTime = now;
- this._stopDelayedCall();
- return this.delayedCallTimeout = setTimeout((function(_this) {
- return function() {
- _this.previousCallTime = 0;
- _this.delayedCallTimeout = null;
- return _this._lookUp(query);
- };
- })(this), wait);
- } else {
- this._stopDelayedCall();
- if (this.previousCallTime !== now) {
- this.previousCallTime = 0;
- }
- return this._lookUp(query);
- }
- };
-
- Controller.prototype._stopDelayedCall = function() {
- if (this.delayedCallTimeout) {
- clearTimeout(this.delayedCallTimeout);
- return this.delayedCallTimeout = null;
- }
- };
-
- Controller.prototype._generateQueryCBId = function() {
- return {};
- };
-
- Controller.prototype._lookUp = function(query) {
- var _callback;
- _callback = function(queryCBId, data) {
- if (queryCBId !== this.expectedQueryCBId) {
- return;
- }
- if (data && data.length > 0) {
- return this.renderView(this.constructor.arrayToDefaultHash(data));
- } else {
- return this.view.hide();
- }
- };
- this.expectedQueryCBId = this._generateQueryCBId();
- return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
- };
-
- return Controller;
-
-})();
-
-var TextareaController,
- 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; },
- hasProp = {}.hasOwnProperty;
-
-TextareaController = (function(superClass) {
- extend(TextareaController, superClass);
-
- function TextareaController() {
- return TextareaController.__super__.constructor.apply(this, arguments);
- }
-
- TextareaController.prototype.catchQuery = function() {
- var caretPos, content, end, isString, query, start, subtext;
- content = this.$inputor.val();
- caretPos = this.$inputor.caret('pos', {
- iframe: this.app.iframe
- });
- subtext = content.slice(0, caretPos);
- query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
- isString = typeof query === 'string';
- if (isString && query.length < this.getOpt('minLen', 0)) {
- return;
- }
- if (isString && query.length <= this.getOpt('maxLen', 20)) {
- start = caretPos - query.length;
- end = start + query.length;
- this.pos = start;
- query = {
- 'text': query,
- 'headPos': start,
- 'endPos': end
- };
- this.trigger("matched", [this.at, query.text]);
- } else {
- query = null;
- this.view.hide();
- }
- return this.query = query;
- };
-
- TextareaController.prototype.rect = function() {
- var c, iframeOffset, scaleBottom;
- if (!(c = this.$inputor.caret('offset', this.pos - 1, {
- iframe: this.app.iframe
- }))) {
- return;
- }
- if (this.app.iframe && !this.app.iframeAsRoot) {
- iframeOffset = $(this.app.iframe).offset();
- c.left += iframeOffset.left;
- c.top += iframeOffset.top;
- }
- scaleBottom = this.app.document.selection ? 0 : 2;
- return {
- left: c.left,
- top: c.top,
- bottom: c.top + c.height + scaleBottom
- };
- };
-
- TextareaController.prototype.insert = function(content, $li) {
- var $inputor, source, startStr, suffix, text;
- $inputor = this.$inputor;
- source = $inputor.val();
- startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
- suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
- content += suffix;
- text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
- $inputor.val(text);
- $inputor.caret('pos', startStr.length + content.length, {
- iframe: this.app.iframe
- });
- if (!$inputor.is(':focus')) {
- $inputor.focus();
- }
- return $inputor.change();
- };
-
- return TextareaController;
-
-})(Controller);
-
-var EditableController,
- 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; },
- hasProp = {}.hasOwnProperty;
-
-EditableController = (function(superClass) {
- extend(EditableController, superClass);
-
- function EditableController() {
- return EditableController.__super__.constructor.apply(this, arguments);
- }
-
- EditableController.prototype._getRange = function() {
- var sel;
- sel = this.app.window.getSelection();
- if (sel.rangeCount > 0) {
- return sel.getRangeAt(0);
- }
- };
-
- EditableController.prototype._setRange = function(position, node, range) {
- if (range == null) {
- range = this._getRange();
- }
- if (!range) {
- return;
- }
- node = $(node)[0];
- if (position === 'after') {
- range.setEndAfter(node);
- range.setStartAfter(node);
- } else {
- range.setEndBefore(node);
- range.setStartBefore(node);
- }
- range.collapse(false);
- return this._clearRange(range);
- };
-
- EditableController.prototype._clearRange = function(range) {
- var sel;
- if (range == null) {
- range = this._getRange();
- }
- sel = this.app.window.getSelection();
- if (this.ctrl_a_pressed == null) {
- sel.removeAllRanges();
- return sel.addRange(range);
- }
- };
-
- EditableController.prototype._movingEvent = function(e) {
- var ref;
- return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
- };
-
- EditableController.prototype._unwrap = function(node) {
- var next;
- node = $(node).unwrap().get(0);
- if ((next = node.nextSibling) && next.nodeValue) {
- node.nodeValue += next.nodeValue;
- $(next).remove();
- }
- return node;
- };
-
- EditableController.prototype.catchQuery = function(e) {
- var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
- if (!(range = this._getRange())) {
- return;
- }
- if (!range.collapsed) {
- return;
- }
- if (e.which === KEY_CODE.ENTER) {
- ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
- if ($query.is(':empty')) {
- $query.remove();
- }
- ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
- this._clearRange();
- return;
- }
- if (/firefox/i.test(navigator.userAgent)) {
- if ($(range.startContainer).is(this.$inputor)) {
- this._clearRange();
- return;
- }
- if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
- _range = range.cloneRange();
- _range.setStart(range.startContainer, offset);
- if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
- inserted = $(range.startContainer).contents().get(offset);
- this._setRange('after', $(inserted).contents().last());
- }
- } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
- $inserted = $(range.startContainer.previousSibling);
- if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
- this._setRange('after', $inserted.contents().last());
- }
- }
- }
- $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
- if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
- $query.remove();
- }
- if (!this._movingEvent(e)) {
- $query.removeClass('atwho-inserted');
- }
- if ($query.length > 0) {
- switch (e.which) {
- case KEY_CODE.LEFT:
- this._setRange('before', $query.get(0), range);
- $query.removeClass('atwho-query');
- return;
- case KEY_CODE.RIGHT:
- this._setRange('after', $query.get(0).nextSibling, range);
- $query.removeClass('atwho-query');
- return;
- }
- }
- if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
- $query.empty().html(query_content).attr('data-atwho-at-query', null);
- this._setRange('after', $query.get(0), range);
- }
- _range = range.cloneRange();
- _range.setStart(range.startContainer, 0);
- matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
- isString = typeof matched === 'string';
- if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
- range.setStart(range.startContainer, index);
- $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
- range.surroundContents($query.get(0));
- lastNode = $query.contents().last().get(0);
- if (/firefox/i.test(navigator.userAgent)) {
- range.setStart(lastNode, lastNode.length);
- range.setEnd(lastNode, lastNode.length);
- this._clearRange(range);
- } else {
- this._setRange('after', lastNode, range);
- }
- }
- if (isString && matched.length < this.getOpt('minLen', 0)) {
- return;
- }
- if (isString && matched.length <= this.getOpt('maxLen', 20)) {
- query = {
- text: matched,
- el: $query
- };
- this.trigger("matched", [this.at, query.text]);
- return this.query = query;
- } else {
- this.view.hide();
- this.query = {
- el: $query
- };
- if ($query.text().indexOf(this.at) >= 0) {
- if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
- $query.removeClass('atwho-query');
- } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
- this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
- }
- }
- return null;
- }
- };
-
- EditableController.prototype.rect = function() {
- var $iframe, iframeOffset, rect;
- rect = this.query.el.offset();
- if (this.app.iframe && !this.app.iframeAsRoot) {
- iframeOffset = ($iframe = $(this.app.iframe)).offset();
- rect.left += iframeOffset.left - this.$inputor.scrollLeft();
- rect.top += iframeOffset.top - this.$inputor.scrollTop();
- }
- rect.bottom = rect.top + this.query.el.height();
- return rect;
- };
-
- EditableController.prototype.insert = function(content, $li) {
- var data, range, suffix, suffixNode;
- if (!this.$inputor.is(':focus')) {
- this.$inputor.focus();
- }
- suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
- data = $li.data('itemData');
- this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text);
- if (range = this._getRange()) {
- range.setEndAfter(this.query.el[0]);
- range.collapse(false);
- range.insertNode(suffixNode = this.app.document.createTextNode("\u200D" + suffix));
- this._setRange('after', suffixNode, range);
- }
- if (!this.$inputor.is(':focus')) {
- this.$inputor.focus();
- }
- return this.$inputor.change();
- };
-
- return EditableController;
-
-})(Controller);
-
-var Model;
-
-Model = (function() {
- function Model(context) {
- this.context = context;
- this.at = this.context.at;
- this.storage = this.context.$inputor;
- }
-
- Model.prototype.destroy = function() {
- return this.storage.data(this.at, null);
- };
-
- Model.prototype.saved = function() {
- return this.fetch() > 0;
- };
-
- Model.prototype.query = function(query, callback) {
- var _remoteFilter, data, searchKey;
- data = this.fetch();
- searchKey = this.context.getOpt("searchKey");
- data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
- _remoteFilter = this.context.callbacks('remoteFilter');
- if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
- return callback(data);
- } else {
- return _remoteFilter.call(this.context, query, callback);
- }
- };
-
- Model.prototype.fetch = function() {
- return this.storage.data(this.at) || [];
- };
-
- Model.prototype.save = function(data) {
- return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
- };
-
- Model.prototype.load = function(data) {
- if (!(this.saved() || !data)) {
- return this._load(data);
- }
- };
-
- Model.prototype.reload = function(data) {
- return this._load(data);
- };
-
- Model.prototype._load = function(data) {
- if (typeof data === "string") {
- return $.ajax(data, {
- dataType: "json"
- }).done((function(_this) {
- return function(data) {
- return _this.save(data);
- };
- })(this));
- } else {
- return this.save(data);
- }
- };
-
- return Model;
-
-})();
-
-var View;
-
-View = (function() {
- function View(context) {
- this.context = context;
- this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
- this.$elUl = this.$el.children();
- this.timeoutID = null;
- this.context.$el.append(this.$el);
- this.bindEvent();
- }
-
- View.prototype.init = function() {
- var header_tpl, id;
- id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
- header_tpl = this.context.getOpt("headerTpl");
- if (header_tpl && this.$el.children().length === 1) {
- this.$el.prepend(header_tpl);
- }
- return this.$el.attr({
- 'id': "at-view-" + id
- });
- };
-
- View.prototype.destroy = function() {
- return this.$el.remove();
- };
-
- View.prototype.bindEvent = function() {
- var $menu, lastCoordX, lastCoordY;
- $menu = this.$el.find('ul');
- lastCoordX = 0;
- lastCoordY = 0;
- return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
- return function(e) {
- var $cur;
- if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
- return;
- }
- lastCoordX = e.clientX;
- lastCoordY = e.clientY;
- $cur = $(e.currentTarget);
- if ($cur.hasClass('cur')) {
- return;
- }
- $menu.find('.cur').removeClass('cur');
- return $cur.addClass('cur');
- };
- })(this)).on('click.atwho-view', 'li', (function(_this) {
- return function(e) {
- $menu.find('.cur').removeClass('cur');
- $(e.currentTarget).addClass('cur');
- _this.choose(e);
- return e.preventDefault();
- };
- })(this));
- };
-
- View.prototype.visible = function() {
- return this.$el.is(":visible");
- };
-
- View.prototype.highlighted = function() {
- return this.$el.find(".cur").length > 0;
- };
-
- View.prototype.choose = function(e) {
- var $li, content;
- if (($li = this.$el.find(".cur")).length) {
- content = this.context.insertContentFor($li);
- this.context._stopDelayedCall();
- this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li);
- this.context.trigger("inserted", [$li, e]);
- this.hide(e);
- }
- if (this.context.getOpt("hideWithoutSuffix")) {
- return this.stopShowing = true;
- }
- };
-
- View.prototype.reposition = function(rect) {
- var _window, offset, overflowOffset, ref;
- _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
- if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
- rect.bottom = rect.top - this.$el.height();
- }
- if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
- rect.left = overflowOffset;
- }
- offset = {
- left: rect.left,
- top: rect.bottom
- };
- if ((ref = this.context.callbacks("beforeReposition")) != null) {
- ref.call(this.context, offset);
- }
- this.$el.offset(offset);
- return this.context.trigger("reposition", [offset]);
- };
-
- View.prototype.next = function() {
- var cur, next, nextEl, offset;
- cur = this.$el.find('.cur').removeClass('cur');
- next = cur.next();
- if (!next.length) {
- next = this.$el.find('li:first');
- }
- next.addClass('cur');
- nextEl = next[0];
- offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
- return this.scrollTop(Math.max(0, offset - this.$el.height()));
- };
-
- View.prototype.prev = function() {
- var cur, offset, prev, prevEl;
- cur = this.$el.find('.cur').removeClass('cur');
- prev = cur.prev();
- if (!prev.length) {
- prev = this.$el.find('li:last');
- }
- prev.addClass('cur');
- prevEl = prev[0];
- offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
- return this.scrollTop(Math.max(0, offset - this.$el.height()));
- };
-
- View.prototype.scrollTop = function(scrollTop) {
- var scrollDuration;
- scrollDuration = this.context.getOpt('scrollDuration');
- if (scrollDuration) {
- return this.$elUl.animate({
- scrollTop: scrollTop
- }, scrollDuration);
- } else {
- return this.$elUl.scrollTop(scrollTop);
- }
- };
-
- View.prototype.show = function() {
- var rect;
- if (this.stopShowing) {
- this.stopShowing = false;
- return;
- }
- if (!this.visible()) {
- this.$el.show();
- this.$el.scrollTop(0);
- this.context.trigger('shown');
- }
- if (rect = this.context.rect()) {
- return this.reposition(rect);
- }
- };
-
- View.prototype.hide = function(e, time) {
- var callback;
- if (!this.visible()) {
- return;
- }
- if (isNaN(time)) {
- this.$el.hide();
- return this.context.trigger('hidden', [e]);
- } else {
- callback = (function(_this) {
- return function() {
- return _this.hide();
- };
- })(this);
- clearTimeout(this.timeoutID);
- return this.timeoutID = setTimeout(callback, time);
- }
- };
-
- View.prototype.render = function(list) {
- var $li, $ul, i, item, len, li, tpl;
- if (!($.isArray(list) && list.length > 0)) {
- this.hide();
- return;
- }
- this.$el.find('ul').empty();
- $ul = this.$el.find('ul');
- tpl = this.context.getOpt('displayTpl');
- for (i = 0, len = list.length; i < len; i++) {
- item = list[i];
- item = $.extend({}, item, {
- 'atwho-at': this.context.at
- });
- li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
- $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
- $li.data("item-data", item);
- $ul.append($li);
- }
- this.show();
- if (this.context.getOpt('highlightFirst')) {
- return $ul.find("li:first").addClass("cur");
- }
- };
-
- return View;
-
-})();
-
-var Api;
-
-Api = {
- load: function(at, data) {
- var c;
- if (c = this.controller(at)) {
- return c.model.load(data);
- }
- },
- isSelecting: function() {
- var ref;
- return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
- },
- hide: function() {
- var ref;
- return (ref = this.controller()) != null ? ref.view.hide() : void 0;
- },
- reposition: function() {
- var c;
- if (c = this.controller()) {
- return c.view.reposition(c.rect());
- }
- },
- setIframe: function(iframe, asRoot) {
- this.setupRootElement(iframe, asRoot);
- return null;
- },
- run: function() {
- return this.dispatch();
- },
- destroy: function() {
- this.shutdown();
- return this.$inputor.data('atwho', null);
- }
-};
-
-$.fn.atwho = function(method) {
- var _args, result;
- _args = arguments;
- result = null;
- this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
- var $this, app;
- if (!(app = ($this = $(this)).data("atwho"))) {
- $this.data('atwho', (app = new App(this)));
- }
- if (typeof method === 'object' || !method) {
- return app.reg(method.at, method);
- } else if (Api[method] && app) {
- return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
- } else {
- return $.error("Method " + method + " does not exist on jQuery.atwho");
- }
- });
- if (result != null) {
- return result;
- } else {
- return this;
- }
-};
-
-$.fn.atwho["default"] = {
- at: void 0,
- alias: void 0,
- data: null,
- displayTpl: "<li>${name}</li>",
- insertTpl: "${atwho-at}${name}",
- headerTpl: null,
- callbacks: DEFAULT_CALLBACKS,
- searchKey: "name",
- suffix: void 0,
- hideWithoutSuffix: false,
- startWithSpace: true,
- acceptSpaceBar: false,
- highlightFirst: true,
- limit: 5,
- maxLen: 20,
- minLen: 0,
- displayTimeout: 300,
- delay: null,
- spaceSelectsMatch: false,
- tabSelectsMatch: true,
- editableAtwhoQueryAttrs: {},
- scrollDuration: 150,
- suspendOnComposing: true,
- lookUpOnClick: true
-};
-
-$.fn.atwho.debug = false;
-
-}));
diff --git a/vendor/assets/javascripts/jquery.caret.js b/vendor/assets/javascripts/jquery.caret.js
deleted file mode 100644
index 811ec63ee47..00000000000
--- a/vendor/assets/javascripts/jquery.caret.js
+++ /dev/null
@@ -1,436 +0,0 @@
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(["jquery"], function ($) {
- return (root.returnExportsGlobal = factory($));
- });
- } else if (typeof exports === 'object') {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like enviroments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"));
- } else {
- factory(jQuery);
- }
-}(this, function ($) {
-
-/*
- Implement Github like autocomplete mentions
- http://ichord.github.com/At.js
-
- Copyright (c) 2013 chord.luo@gmail.com
- Licensed under the MIT license.
-*/
-
-/*
-本æ’件æ“作 textarea 或者 input 内的æ’入符
-åªå®žçŽ°äº†èŽ·å¾—æ’入符在文本框中的ä½ç½®ï¼Œæˆ‘设置
-æ’入符的ä½ç½®.
-*/
-
-"use strict";
-var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
-
-pluginName = 'caret';
-
-EditableCaret = (function() {
- function EditableCaret($inputor) {
- this.$inputor = $inputor;
- this.domInputor = this.$inputor[0];
- }
-
- EditableCaret.prototype.setPos = function(pos) {
- var fn, found, offset, sel;
- if (sel = oWindow.getSelection()) {
- offset = 0;
- found = false;
- (fn = function(pos, parent) {
- var node, range, _i, _len, _ref, _results;
- _ref = parent.childNodes;
- _results = [];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- node = _ref[_i];
- if (found) {
- break;
- }
- if (node.nodeType === 3) {
- if (offset + node.length >= pos) {
- found = true;
- range = oDocument.createRange();
- range.setStart(node, pos - offset);
- sel.removeAllRanges();
- sel.addRange(range);
- break;
- } else {
- _results.push(offset += node.length);
- }
- } else {
- _results.push(fn(pos, node));
- }
- }
- return _results;
- })(pos, this.domInputor);
- }
- return this.domInputor;
- };
-
- EditableCaret.prototype.getIEPosition = function() {
- return this.getPosition();
- };
-
- EditableCaret.prototype.getPosition = function() {
- var inputor_offset, offset;
- offset = this.getOffset();
- inputor_offset = this.$inputor.offset();
- offset.left -= inputor_offset.left;
- offset.top -= inputor_offset.top;
- return offset;
- };
-
- EditableCaret.prototype.getOldIEPos = function() {
- var preCaretTextRange, textRange;
- textRange = oDocument.selection.createRange();
- preCaretTextRange = oDocument.body.createTextRange();
- preCaretTextRange.moveToElementText(this.domInputor);
- preCaretTextRange.setEndPoint("EndToEnd", textRange);
- return preCaretTextRange.text.length;
- };
-
- EditableCaret.prototype.getPos = function() {
- var clonedRange, pos, range;
- if (range = this.range()) {
- clonedRange = range.cloneRange();
- clonedRange.selectNodeContents(this.domInputor);
- clonedRange.setEnd(range.endContainer, range.endOffset);
- pos = clonedRange.toString().length;
- clonedRange.detach();
- return pos;
- } else if (oDocument.selection) {
- return this.getOldIEPos();
- }
- };
-
- EditableCaret.prototype.getOldIEOffset = function() {
- var range, rect;
- range = oDocument.selection.createRange().duplicate();
- range.moveStart("character", -1);
- rect = range.getBoundingClientRect();
- return {
- height: rect.bottom - rect.top,
- left: rect.left,
- top: rect.top
- };
- };
-
- EditableCaret.prototype.getOffset = function(pos) {
- var clonedRange, offset, range, rect, shadowCaret;
- if (oWindow.getSelection && (range = this.range())) {
- if (range.endOffset - 1 > 0 && range.endContainer !== this.domInputor) {
- clonedRange = range.cloneRange();
- clonedRange.setStart(range.endContainer, range.endOffset - 1);
- clonedRange.setEnd(range.endContainer, range.endOffset);
- rect = clonedRange.getBoundingClientRect();
- offset = {
- height: rect.height,
- left: rect.left + rect.width,
- top: rect.top
- };
- clonedRange.detach();
- }
- if (!offset || (offset != null ? offset.height : void 0) === 0) {
- clonedRange = range.cloneRange();
- shadowCaret = $(oDocument.createTextNode("|"));
- clonedRange.insertNode(shadowCaret[0]);
- clonedRange.selectNode(shadowCaret[0]);
- rect = clonedRange.getBoundingClientRect();
- offset = {
- height: rect.height,
- left: rect.left,
- top: rect.top
- };
- shadowCaret.remove();
- clonedRange.detach();
- }
- } else if (oDocument.selection) {
- offset = this.getOldIEOffset();
- }
- if (offset) {
- offset.top += $(oWindow).scrollTop();
- offset.left += $(oWindow).scrollLeft();
- }
- return offset;
- };
-
- EditableCaret.prototype.range = function() {
- var sel;
- if (!oWindow.getSelection) {
- return;
- }
- sel = oWindow.getSelection();
- if (sel.rangeCount > 0) {
- return sel.getRangeAt(0);
- } else {
- return null;
- }
- };
-
- return EditableCaret;
-
-})();
-
-InputCaret = (function() {
- function InputCaret($inputor) {
- this.$inputor = $inputor;
- this.domInputor = this.$inputor[0];
- }
-
- InputCaret.prototype.getIEPos = function() {
- var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
- inputor = this.domInputor;
- range = oDocument.selection.createRange();
- pos = 0;
- if (range && range.parentElement() === inputor) {
- normalizedValue = inputor.value.replace(/\r\n/g, "\n");
- len = normalizedValue.length;
- textInputRange = inputor.createTextRange();
- textInputRange.moveToBookmark(range.getBookmark());
- endRange = inputor.createTextRange();
- endRange.collapse(false);
- if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
- pos = len;
- } else {
- pos = -textInputRange.moveStart("character", -len);
- }
- }
- return pos;
- };
-
- InputCaret.prototype.getPos = function() {
- if (oDocument.selection) {
- return this.getIEPos();
- } else {
- return this.domInputor.selectionStart;
- }
- };
-
- InputCaret.prototype.setPos = function(pos) {
- var inputor, range;
- inputor = this.domInputor;
- if (oDocument.selection) {
- range = inputor.createTextRange();
- range.move("character", pos);
- range.select();
- } else if (inputor.setSelectionRange) {
- inputor.setSelectionRange(pos, pos);
- }
- return inputor;
- };
-
- InputCaret.prototype.getIEOffset = function(pos) {
- var h, textRange, x, y;
- textRange = this.domInputor.createTextRange();
- pos || (pos = this.getPos());
- textRange.move('character', pos);
- x = textRange.boundingLeft;
- y = textRange.boundingTop;
- h = textRange.boundingHeight;
- return {
- left: x,
- top: y,
- height: h
- };
- };
-
- InputCaret.prototype.getOffset = function(pos) {
- var $inputor, offset, position;
- $inputor = this.$inputor;
- if (oDocument.selection) {
- offset = this.getIEOffset(pos);
- offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
- offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
- return offset;
- } else {
- offset = $inputor.offset();
- position = this.getPosition(pos);
- return offset = {
- left: offset.left + position.left - $inputor.scrollLeft(),
- top: offset.top + position.top - $inputor.scrollTop(),
- height: position.height
- };
- }
- };
-
- InputCaret.prototype.getPosition = function(pos) {
- var $inputor, at_rect, end_range, format, html, mirror, start_range;
- $inputor = this.$inputor;
- format = function(value) {
- value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
- if (/firefox/i.test(navigator.userAgent)) {
- value = value.replace(/\s/g, '&nbsp;');
- }
- return value;
- };
- if (pos === void 0) {
- pos = this.getPos();
- }
- start_range = $inputor.val().slice(0, pos);
- end_range = $inputor.val().slice(pos);
- html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
- html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
- html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
- mirror = new Mirror($inputor);
- return at_rect = mirror.create(html).rect();
- };
-
- InputCaret.prototype.getIEPosition = function(pos) {
- var h, inputorOffset, offset, x, y;
- offset = this.getIEOffset(pos);
- inputorOffset = this.$inputor.offset();
- x = offset.left - inputorOffset.left;
- y = offset.top - inputorOffset.top;
- h = offset.height;
- return {
- left: x,
- top: y,
- height: h
- };
- };
-
- return InputCaret;
-
-})();
-
-Mirror = (function() {
- Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"];
-
- function Mirror($inputor) {
- this.$inputor = $inputor;
- }
-
- Mirror.prototype.mirrorCss = function() {
- var css,
- _this = this;
- css = {
- position: 'absolute',
- left: -9999,
- top: 0,
- zIndex: -20000
- };
- if (this.$inputor.prop('tagName') === 'TEXTAREA') {
- this.css_attr.push('width');
- }
- $.each(this.css_attr, function(i, p) {
- return css[p] = _this.$inputor.css(p);
- });
- return css;
- };
-
- Mirror.prototype.create = function(html) {
- this.$mirror = $('<div></div>');
- this.$mirror.css(this.mirrorCss());
- this.$mirror.html(html);
- this.$inputor.after(this.$mirror);
- return this;
- };
-
- Mirror.prototype.rect = function() {
- var $flag, pos, rect;
- $flag = this.$mirror.find("#caret");
- pos = $flag.position();
- rect = {
- left: pos.left,
- top: pos.top,
- height: $flag.height()
- };
- this.$mirror.remove();
- return rect;
- };
-
- return Mirror;
-
-})();
-
-Utils = {
- contentEditable: function($inputor) {
- return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
- }
-};
-
-methods = {
- pos: function(pos) {
- if (pos || pos === 0) {
- return this.setPos(pos);
- } else {
- return this.getPos();
- }
- },
- position: function(pos) {
- if (oDocument.selection) {
- return this.getIEPosition(pos);
- } else {
- return this.getPosition(pos);
- }
- },
- offset: function(pos) {
- var offset;
- offset = this.getOffset(pos);
- return offset;
- }
-};
-
-oDocument = null;
-
-oWindow = null;
-
-oFrame = null;
-
-setContextBy = function(settings) {
- var iframe;
- if (iframe = settings != null ? settings.iframe : void 0) {
- oFrame = iframe;
- oWindow = iframe.contentWindow;
- return oDocument = iframe.contentDocument || oWindow.document;
- } else {
- oFrame = void 0;
- oWindow = window;
- return oDocument = document;
- }
-};
-
-discoveryIframeOf = function($dom) {
- var error;
- oDocument = $dom[0].ownerDocument;
- oWindow = oDocument.defaultView || oDocument.parentWindow;
- try {
- return oFrame = oWindow.frameElement;
- } catch (_error) {
- error = _error;
- }
-};
-
-$.fn.caret = function(method, value, settings) {
- var caret;
- if (methods[method]) {
- if ($.isPlainObject(value)) {
- setContextBy(value);
- value = void 0;
- } else {
- setContextBy(settings);
- }
- caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
- return methods[method].apply(caret, [value]);
- } else {
- return $.error("Method " + method + " does not exist on jQuery.caret");
- }
-};
-
-$.fn.caret.EditableCaret = EditableCaret;
-
-$.fn.caret.InputCaret = InputCaret;
-
-$.fn.caret.Utils = Utils;
-
-$.fn.caret.apis = methods;
-
-
-}));
diff --git a/vendor/assets/javascripts/pdf.js b/vendor/assets/javascripts/pdf.js
deleted file mode 100755
index 91c43b12716..00000000000
--- a/vendor/assets/javascripts/pdf.js
+++ /dev/null
@@ -1,9365 +0,0 @@
-/* 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.
- * 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.
- */
-
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define("pdfjs-dist/build/pdf", [], factory);
- else if(typeof exports === 'object')
- exports["pdfjs-dist/build/pdf"] = factory();
- else
- root["pdfjs-dist/build/pdf"] = root.pdfjsDistBuildPdf = factory();
-})(this, function() {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __w_pdfjs_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, __w_pdfjs_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __w_pdfjs_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __w_pdfjs_require__.c = installedModules;
-/******/
-/******/ // identity function for calling harmony imports with the correct context
-/******/ __w_pdfjs_require__.i = function(value) { return value; };
-/******/
-/******/ // define getter function for harmony exports
-/******/ __w_pdfjs_require__.d = function(exports, name, getter) {
-/******/ if(!__w_pdfjs_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, {
-/******/ configurable: false,
-/******/ enumerable: true,
-/******/ get: getter
-/******/ });
-/******/ }
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __w_pdfjs_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __w_pdfjs_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __w_pdfjs_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __w_pdfjs_require__.p = "";
-/******/
-/******/ // Load entry module and return exports
-/******/ return __w_pdfjs_require__(__w_pdfjs_require__.s = 13);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-var compatibility = __w_pdfjs_require__(14);
-var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
-var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
-var TextRenderingMode = {
- 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
-};
-var ImageKind = {
- GRAYSCALE_1BPP: 1,
- RGB_24BPP: 2,
- RGBA_32BPP: 3
-};
-var AnnotationType = {
- 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
-};
-var AnnotationFlag = {
- INVISIBLE: 0x01,
- HIDDEN: 0x02,
- PRINT: 0x04,
- NOZOOM: 0x08,
- NOROTATE: 0x10,
- NOVIEW: 0x20,
- READONLY: 0x40,
- LOCKED: 0x80,
- TOGGLENOVIEW: 0x100,
- LOCKEDCONTENTS: 0x200
-};
-var AnnotationFieldFlag = {
- READONLY: 0x0000001,
- REQUIRED: 0x0000002,
- NOEXPORT: 0x0000004,
- MULTILINE: 0x0001000,
- PASSWORD: 0x0002000,
- NOTOGGLETOOFF: 0x0004000,
- RADIO: 0x0008000,
- PUSHBUTTON: 0x0010000,
- COMBO: 0x0020000,
- EDIT: 0x0040000,
- SORT: 0x0080000,
- FILESELECT: 0x0100000,
- MULTISELECT: 0x0200000,
- DONOTSPELLCHECK: 0x0400000,
- DONOTSCROLL: 0x0800000,
- COMB: 0x1000000,
- RICHTEXT: 0x2000000,
- RADIOSINUNISON: 0x2000000,
- COMMITONSELCHANGE: 0x4000000
-};
-var AnnotationBorderStyleType = {
- SOLID: 1,
- DASHED: 2,
- BEVELED: 3,
- INSET: 4,
- UNDERLINE: 5
-};
-var StreamType = {
- UNKNOWN: 0,
- FLATE: 1,
- LZW: 2,
- DCT: 3,
- JPX: 4,
- JBIG: 5,
- A85: 6,
- AHX: 7,
- CCF: 8,
- RL: 9
-};
-var FontType = {
- UNKNOWN: 0,
- TYPE1: 1,
- TYPE1C: 2,
- CIDFONTTYPE0: 3,
- CIDFONTTYPE0C: 4,
- TRUETYPE: 5,
- CIDFONTTYPE2: 6,
- TYPE3: 7,
- OPENTYPE: 8,
- TYPE0: 9,
- MMTYPE1: 10
-};
-var VERBOSITY_LEVELS = {
- errors: 0,
- warnings: 1,
- infos: 5
-};
-var CMapCompressionType = {
- NONE: 0,
- BINARY: 1,
- STREAM: 2
-};
-var OPS = {
- 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
-};
-var verbosity = VERBOSITY_LEVELS.warnings;
-function setVerbosityLevel(level) {
- verbosity = level;
-}
-function getVerbosityLevel() {
- return verbosity;
-}
-function info(msg) {
- if (verbosity >= VERBOSITY_LEVELS.infos) {
- console.log('Info: ' + msg);
- }
-}
-function warn(msg) {
- if (verbosity >= VERBOSITY_LEVELS.warnings) {
- console.log('Warning: ' + msg);
- }
-}
-function deprecated(details) {
- console.log('Deprecated API usage: ' + details);
-}
-function error(msg) {
- if (verbosity >= VERBOSITY_LEVELS.errors) {
- console.log('Error: ' + msg);
- console.log(backtrace());
- }
- throw new Error(msg);
-}
-function backtrace() {
- try {
- throw new Error();
- } catch (e) {
- return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
- }
-}
-function assert(cond, msg) {
- if (!cond) {
- error(msg);
- }
-}
-var UNSUPPORTED_FEATURES = {
- unknown: 'unknown',
- forms: 'forms',
- javaScript: 'javaScript',
- smask: 'smask',
- shadingPattern: 'shadingPattern',
- font: 'font'
-};
-function isSameOrigin(baseUrl, otherUrl) {
- try {
- var base = new URL(baseUrl);
- if (!base.origin || base.origin === 'null') {
- return false;
- }
- } catch (e) {
- return false;
- }
- var other = new URL(otherUrl, base);
- return base.origin === other.origin;
-}
-function isValidProtocol(url) {
- if (!url) {
- return false;
- }
- switch (url.protocol) {
- case 'http:':
- case 'https:':
- case 'ftp:':
- case 'mailto:':
- case 'tel:':
- return true;
- default:
- return false;
- }
-}
-function createValidAbsoluteUrl(url, baseUrl) {
- if (!url) {
- return null;
- }
- try {
- var absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
- if (isValidProtocol(absoluteUrl)) {
- return absoluteUrl;
- }
- } catch (ex) {}
- return null;
-}
-function shadow(obj, prop, value) {
- Object.defineProperty(obj, prop, {
- value: value,
- enumerable: true,
- configurable: true,
- writable: false
- });
- return value;
-}
-function getLookupTableFactory(initializer) {
- var lookup;
- return function () {
- if (initializer) {
- lookup = Object.create(null);
- initializer(lookup);
- initializer = null;
- }
- return lookup;
- };
-}
-var PasswordResponses = {
- NEED_PASSWORD: 1,
- INCORRECT_PASSWORD: 2
-};
-var PasswordException = function PasswordExceptionClosure() {
- function PasswordException(msg, code) {
- this.name = 'PasswordException';
- this.message = msg;
- this.code = code;
- }
- PasswordException.prototype = new Error();
- PasswordException.constructor = PasswordException;
- return PasswordException;
-}();
-var UnknownErrorException = function UnknownErrorExceptionClosure() {
- function UnknownErrorException(msg, details) {
- this.name = 'UnknownErrorException';
- this.message = msg;
- this.details = details;
- }
- UnknownErrorException.prototype = new Error();
- UnknownErrorException.constructor = UnknownErrorException;
- return UnknownErrorException;
-}();
-var InvalidPDFException = function InvalidPDFExceptionClosure() {
- function InvalidPDFException(msg) {
- this.name = 'InvalidPDFException';
- this.message = msg;
- }
- InvalidPDFException.prototype = new Error();
- InvalidPDFException.constructor = InvalidPDFException;
- return InvalidPDFException;
-}();
-var MissingPDFException = function MissingPDFExceptionClosure() {
- function MissingPDFException(msg) {
- this.name = 'MissingPDFException';
- this.message = msg;
- }
- MissingPDFException.prototype = new Error();
- MissingPDFException.constructor = MissingPDFException;
- return MissingPDFException;
-}();
-var UnexpectedResponseException = function UnexpectedResponseExceptionClosure() {
- function UnexpectedResponseException(msg, status) {
- this.name = 'UnexpectedResponseException';
- this.message = msg;
- this.status = status;
- }
- UnexpectedResponseException.prototype = new Error();
- UnexpectedResponseException.constructor = UnexpectedResponseException;
- return UnexpectedResponseException;
-}();
-var NotImplementedException = function NotImplementedExceptionClosure() {
- function NotImplementedException(msg) {
- this.message = msg;
- }
- NotImplementedException.prototype = new Error();
- NotImplementedException.prototype.name = 'NotImplementedException';
- NotImplementedException.constructor = NotImplementedException;
- return NotImplementedException;
-}();
-var MissingDataException = function MissingDataExceptionClosure() {
- function MissingDataException(begin, end) {
- this.begin = begin;
- this.end = end;
- this.message = 'Missing data [' + begin + ', ' + end + ')';
- }
- MissingDataException.prototype = new Error();
- MissingDataException.prototype.name = 'MissingDataException';
- MissingDataException.constructor = MissingDataException;
- return MissingDataException;
-}();
-var XRefParseException = function XRefParseExceptionClosure() {
- function XRefParseException(msg) {
- this.message = msg;
- }
- XRefParseException.prototype = new Error();
- XRefParseException.prototype.name = 'XRefParseException';
- XRefParseException.constructor = XRefParseException;
- return XRefParseException;
-}();
-var NullCharactersRegExp = /\x00/g;
-function removeNullCharacters(str) {
- if (typeof str !== 'string') {
- warn('The argument for removeNullCharacters must be a string.');
- return str;
- }
- return str.replace(NullCharactersRegExp, '');
-}
-function bytesToString(bytes) {
- assert(bytes !== null && typeof bytes === 'object' && bytes.length !== undefined, 'Invalid argument for bytesToString');
- var length = bytes.length;
- var MAX_ARGUMENT_COUNT = 8192;
- if (length < MAX_ARGUMENT_COUNT) {
- return String.fromCharCode.apply(null, bytes);
- }
- var strBuf = [];
- for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
- var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
- var chunk = bytes.subarray(i, chunkEnd);
- strBuf.push(String.fromCharCode.apply(null, chunk));
- }
- return strBuf.join('');
-}
-function stringToBytes(str) {
- assert(typeof str === 'string', 'Invalid argument for stringToBytes');
- var length = str.length;
- var bytes = new Uint8Array(length);
- for (var i = 0; i < length; ++i) {
- bytes[i] = str.charCodeAt(i) & 0xFF;
- }
- return bytes;
-}
-function arrayByteLength(arr) {
- if (arr.length !== undefined) {
- return arr.length;
- }
- assert(arr.byteLength !== undefined);
- return arr.byteLength;
-}
-function arraysToBytes(arr) {
- if (arr.length === 1 && arr[0] instanceof Uint8Array) {
- return arr[0];
- }
- var resultLength = 0;
- var i,
- ii = arr.length;
- var item, itemLength;
- for (i = 0; i < ii; i++) {
- item = arr[i];
- itemLength = arrayByteLength(item);
- resultLength += itemLength;
- }
- var pos = 0;
- var data = new Uint8Array(resultLength);
- for (i = 0; i < ii; i++) {
- item = arr[i];
- if (!(item instanceof Uint8Array)) {
- if (typeof item === 'string') {
- item = stringToBytes(item);
- } else {
- item = new Uint8Array(item);
- }
- }
- itemLength = item.byteLength;
- data.set(item, pos);
- pos += itemLength;
- }
- return data;
-}
-function string32(value) {
- return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
-}
-function log2(x) {
- var n = 1,
- i = 0;
- while (x > n) {
- n <<= 1;
- i++;
- }
- return i;
-}
-function readInt8(data, start) {
- return data[start] << 24 >> 24;
-}
-function readUint16(data, offset) {
- return data[offset] << 8 | data[offset + 1];
-}
-function readUint32(data, offset) {
- return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
-}
-function isLittleEndian() {
- var buffer8 = new Uint8Array(2);
- buffer8[0] = 1;
- var buffer16 = new Uint16Array(buffer8.buffer);
- return buffer16[0] === 1;
-}
-function isEvalSupported() {
- try {
- new Function('');
- return true;
- } catch (e) {
- return false;
- }
-}
-var Uint32ArrayView = function Uint32ArrayViewClosure() {
- function Uint32ArrayView(buffer, length) {
- this.buffer = buffer;
- this.byteLength = buffer.length;
- this.length = length === undefined ? this.byteLength >> 2 : length;
- ensureUint32ArrayViewProps(this.length);
- }
- Uint32ArrayView.prototype = Object.create(null);
- var uint32ArrayViewSetters = 0;
- function createUint32ArrayProp(index) {
- return {
- get: function () {
- var buffer = this.buffer,
- offset = index << 2;
- return (buffer[offset] | buffer[offset + 1] << 8 | buffer[offset + 2] << 16 | buffer[offset + 3] << 24) >>> 0;
- },
- set: function (value) {
- var buffer = this.buffer,
- offset = index << 2;
- buffer[offset] = value & 255;
- buffer[offset + 1] = value >> 8 & 255;
- buffer[offset + 2] = value >> 16 & 255;
- buffer[offset + 3] = value >>> 24 & 255;
- }
- };
- }
- function ensureUint32ArrayViewProps(length) {
- while (uint32ArrayViewSetters < length) {
- Object.defineProperty(Uint32ArrayView.prototype, uint32ArrayViewSetters, createUint32ArrayProp(uint32ArrayViewSetters));
- uint32ArrayViewSetters++;
- }
- }
- return Uint32ArrayView;
-}();
-exports.Uint32ArrayView = Uint32ArrayView;
-var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
-var Util = function UtilClosure() {
- function Util() {}
- var rgbBuf = ['rgb(', 0, ',', 0, ',', 0, ')'];
- Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
- rgbBuf[1] = r;
- rgbBuf[3] = g;
- rgbBuf[5] = b;
- return rgbBuf.join('');
- };
- Util.transform = function Util_transform(m1, m2) {
- return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
- };
- Util.applyTransform = function Util_applyTransform(p, m) {
- var xt = p[0] * m[0] + p[1] * m[2] + m[4];
- var yt = p[0] * m[1] + p[1] * m[3] + m[5];
- return [xt, yt];
- };
- Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
- var d = m[0] * m[3] - m[1] * m[2];
- var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
- var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
- return [xt, yt];
- };
- Util.getAxialAlignedBoundingBox = function Util_getAxialAlignedBoundingBox(r, m) {
- var p1 = Util.applyTransform(r, m);
- var p2 = Util.applyTransform(r.slice(2, 4), m);
- var p3 = Util.applyTransform([r[0], r[3]], m);
- var p4 = Util.applyTransform([r[2], r[1]], m);
- return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
- };
- Util.inverseTransform = function Util_inverseTransform(m) {
- var d = m[0] * m[3] - m[1] * m[2];
- return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
- };
- Util.apply3dTransform = function Util_apply3dTransform(m, v) {
- return [m[0] * v[0] + m[1] * v[1] + m[2] * v[2], m[3] * v[0] + m[4] * v[1] + m[5] * v[2], m[6] * v[0] + m[7] * v[1] + m[8] * v[2]];
- };
- Util.singularValueDecompose2dScale = function Util_singularValueDecompose2dScale(m) {
- var transpose = [m[0], m[2], m[1], m[3]];
- var a = m[0] * transpose[0] + m[1] * transpose[2];
- var b = m[0] * transpose[1] + m[1] * transpose[3];
- var c = m[2] * transpose[0] + m[3] * transpose[2];
- var d = m[2] * transpose[1] + m[3] * transpose[3];
- var first = (a + d) / 2;
- var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
- var sx = first + second || 1;
- var sy = first - second || 1;
- return [Math.sqrt(sx), Math.sqrt(sy)];
- };
- Util.normalizeRect = function Util_normalizeRect(rect) {
- var r = rect.slice(0);
- if (rect[0] > rect[2]) {
- r[0] = rect[2];
- r[2] = rect[0];
- }
- if (rect[1] > rect[3]) {
- r[1] = rect[3];
- r[3] = rect[1];
- }
- return r;
- };
- Util.intersect = function Util_intersect(rect1, rect2) {
- function compare(a, b) {
- return a - b;
- }
- var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
- orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
- result = [];
- rect1 = Util.normalizeRect(rect1);
- rect2 = Util.normalizeRect(rect2);
- if (orderedX[0] === rect1[0] && orderedX[1] === rect2[0] || orderedX[0] === rect2[0] && orderedX[1] === rect1[0]) {
- result[0] = orderedX[1];
- result[2] = orderedX[2];
- } else {
- return false;
- }
- if (orderedY[0] === rect1[1] && orderedY[1] === rect2[1] || orderedY[0] === rect2[1] && orderedY[1] === rect1[1]) {
- result[1] = orderedY[1];
- result[3] = orderedY[2];
- } else {
- return false;
- }
- return result;
- };
- Util.sign = function Util_sign(num) {
- return num < 0 ? -1 : 1;
- };
- var ROMAN_NUMBER_MAP = ['', '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'];
- Util.toRoman = function Util_toRoman(number, lowerCase) {
- assert(isInt(number) && number > 0, 'The number should be a positive integer.');
- var pos,
- romanBuf = [];
- while (number >= 1000) {
- number -= 1000;
- romanBuf.push('M');
- }
- pos = number / 100 | 0;
- number %= 100;
- romanBuf.push(ROMAN_NUMBER_MAP[pos]);
- pos = number / 10 | 0;
- number %= 10;
- romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
- romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
- var romanStr = romanBuf.join('');
- return lowerCase ? romanStr.toLowerCase() : romanStr;
- };
- Util.appendToArray = function Util_appendToArray(arr1, arr2) {
- Array.prototype.push.apply(arr1, arr2);
- };
- Util.prependToArray = function Util_prependToArray(arr1, arr2) {
- Array.prototype.unshift.apply(arr1, arr2);
- };
- Util.extendObj = function extendObj(obj1, obj2) {
- for (var key in obj2) {
- obj1[key] = obj2[key];
- }
- };
- Util.getInheritableProperty = function Util_getInheritableProperty(dict, name, getArray) {
- while (dict && !dict.has(name)) {
- dict = dict.get('Parent');
- }
- if (!dict) {
- return null;
- }
- return getArray ? dict.getArray(name) : dict.get(name);
- };
- Util.inherit = function Util_inherit(sub, base, prototype) {
- sub.prototype = Object.create(base.prototype);
- sub.prototype.constructor = sub;
- for (var prop in prototype) {
- sub.prototype[prop] = prototype[prop];
- }
- };
- Util.loadScript = function Util_loadScript(src, callback) {
- var script = document.createElement('script');
- var loaded = false;
- script.setAttribute('src', src);
- if (callback) {
- script.onload = function () {
- if (!loaded) {
- callback();
- }
- loaded = true;
- };
- }
- document.getElementsByTagName('head')[0].appendChild(script);
- };
- return Util;
-}();
-var PageViewport = function PageViewportClosure() {
- function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
- this.viewBox = viewBox;
- this.scale = scale;
- this.rotation = rotation;
- this.offsetX = offsetX;
- this.offsetY = offsetY;
- var centerX = (viewBox[2] + viewBox[0]) / 2;
- var centerY = (viewBox[3] + viewBox[1]) / 2;
- var rotateA, rotateB, rotateC, rotateD;
- rotation = rotation % 360;
- rotation = rotation < 0 ? rotation + 360 : rotation;
- switch (rotation) {
- case 180:
- rotateA = -1;
- rotateB = 0;
- rotateC = 0;
- rotateD = 1;
- break;
- case 90:
- rotateA = 0;
- rotateB = 1;
- rotateC = 1;
- rotateD = 0;
- break;
- case 270:
- rotateA = 0;
- rotateB = -1;
- rotateC = -1;
- rotateD = 0;
- break;
- default:
- rotateA = 1;
- rotateB = 0;
- rotateC = 0;
- rotateD = -1;
- break;
- }
- if (dontFlip) {
- rotateC = -rotateC;
- rotateD = -rotateD;
- }
- var offsetCanvasX, offsetCanvasY;
- var width, height;
- if (rotateA === 0) {
- offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
- offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
- width = Math.abs(viewBox[3] - viewBox[1]) * scale;
- height = Math.abs(viewBox[2] - viewBox[0]) * scale;
- } else {
- offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
- offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
- width = Math.abs(viewBox[2] - viewBox[0]) * scale;
- height = Math.abs(viewBox[3] - viewBox[1]) * scale;
- }
- this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
- this.width = width;
- this.height = height;
- this.fontScale = scale;
- }
- PageViewport.prototype = {
- clone: function PageViewPort_clone(args) {
- args = args || {};
- var scale = 'scale' in args ? args.scale : this.scale;
- var rotation = 'rotation' in args ? args.rotation : this.rotation;
- return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip);
- },
- convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
- return Util.applyTransform([x, y], this.transform);
- },
- convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) {
- var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
- var br = Util.applyTransform([rect[2], rect[3]], this.transform);
- return [tl[0], tl[1], br[0], br[1]];
- },
- convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
- return Util.applyInverseTransform([x, y], this.transform);
- }
- };
- return PageViewport;
-}();
-var PDFStringTranslateTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC];
-function stringToPDFString(str) {
- var i,
- n = str.length,
- strBuf = [];
- if (str[0] === '\xFE' && str[1] === '\xFF') {
- for (i = 2; i < n; i += 2) {
- strBuf.push(String.fromCharCode(str.charCodeAt(i) << 8 | str.charCodeAt(i + 1)));
- }
- } else {
- for (i = 0; i < n; ++i) {
- var code = PDFStringTranslateTable[str.charCodeAt(i)];
- strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
- }
- }
- return strBuf.join('');
-}
-function stringToUTF8String(str) {
- return decodeURIComponent(escape(str));
-}
-function utf8StringToString(str) {
- return unescape(encodeURIComponent(str));
-}
-function isEmptyObj(obj) {
- for (var key in obj) {
- return false;
- }
- return true;
-}
-function isBool(v) {
- return typeof v === 'boolean';
-}
-function isInt(v) {
- return typeof v === 'number' && (v | 0) === v;
-}
-function isNum(v) {
- return typeof v === 'number';
-}
-function isString(v) {
- return typeof v === 'string';
-}
-function isArray(v) {
- return v instanceof Array;
-}
-function isArrayBuffer(v) {
- return typeof v === 'object' && v !== null && v.byteLength !== undefined;
-}
-function isSpace(ch) {
- return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
-}
-function isNodeJS() {
- if (typeof __pdfjsdev_webpack__ === 'undefined') {
- return typeof process === 'object' && process + '' === '[object process]';
- }
- return false;
-}
-function createPromiseCapability() {
- var capability = {};
- capability.promise = new Promise(function (resolve, reject) {
- capability.resolve = resolve;
- capability.reject = reject;
- });
- return capability;
-}
-var StatTimer = function StatTimerClosure() {
- function rpad(str, pad, length) {
- while (str.length < length) {
- str += pad;
- }
- return str;
- }
- function StatTimer() {
- this.started = Object.create(null);
- this.times = [];
- this.enabled = true;
- }
- StatTimer.prototype = {
- time: function StatTimer_time(name) {
- if (!this.enabled) {
- return;
- }
- if (name in this.started) {
- warn('Timer is already running for ' + name);
- }
- this.started[name] = Date.now();
- },
- timeEnd: function StatTimer_timeEnd(name) {
- if (!this.enabled) {
- return;
- }
- if (!(name in this.started)) {
- warn('Timer has not been started for ' + name);
- }
- this.times.push({
- 'name': name,
- 'start': this.started[name],
- 'end': Date.now()
- });
- delete this.started[name];
- },
- toString: function StatTimer_toString() {
- var i, ii;
- var times = this.times;
- var out = '';
- var longest = 0;
- for (i = 0, ii = times.length; i < ii; ++i) {
- var name = times[i]['name'];
- if (name.length > longest) {
- longest = name.length;
- }
- }
- for (i = 0, ii = times.length; i < ii; ++i) {
- var span = times[i];
- var duration = span.end - span.start;
- out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
- }
- return out;
- }
- };
- return StatTimer;
-}();
-var createBlob = function createBlob(data, contentType) {
- if (typeof Blob !== 'undefined') {
- return new Blob([data], { type: contentType });
- }
- warn('The "Blob" constructor is not supported.');
-};
-var createObjectURL = function createObjectURLClosure() {
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- return function createObjectURL(data, contentType, forceDataSchema) {
- if (!forceDataSchema && typeof URL !== 'undefined' && URL.createObjectURL) {
- var blob = createBlob(data, contentType);
- return URL.createObjectURL(blob);
- }
- var buffer = 'data:' + contentType + ';base64,';
- for (var i = 0, ii = data.length; i < ii; i += 3) {
- var b1 = data[i] & 0xFF;
- var b2 = data[i + 1] & 0xFF;
- var b3 = data[i + 2] & 0xFF;
- var d1 = b1 >> 2,
- d2 = (b1 & 3) << 4 | b2 >> 4;
- var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
- var d4 = i + 2 < ii ? b3 & 0x3F : 64;
- buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
- }
- return buffer;
- };
-}();
-function MessageHandler(sourceName, targetName, comObj) {
- this.sourceName = sourceName;
- this.targetName = targetName;
- this.comObj = comObj;
- this.callbackIndex = 1;
- this.postMessageTransfers = true;
- var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
- var ah = this.actionHandler = Object.create(null);
- this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
- var data = event.data;
- if (data.targetName !== this.sourceName) {
- return;
- }
- if (data.isReply) {
- var callbackId = data.callbackId;
- if (data.callbackId in callbacksCapabilities) {
- var callback = callbacksCapabilities[callbackId];
- delete callbacksCapabilities[callbackId];
- if ('error' in data) {
- callback.reject(data.error);
- } else {
- callback.resolve(data.data);
- }
- } else {
- error('Cannot resolve callback ' + callbackId);
- }
- } else if (data.action in ah) {
- var action = ah[data.action];
- if (data.callbackId) {
- var sourceName = this.sourceName;
- var targetName = data.sourceName;
- Promise.resolve().then(function () {
- return action[0].call(action[1], data.data);
- }).then(function (result) {
- comObj.postMessage({
- sourceName: sourceName,
- targetName: targetName,
- isReply: true,
- callbackId: data.callbackId,
- data: result
- });
- }, function (reason) {
- if (reason instanceof Error) {
- reason = reason + '';
- }
- comObj.postMessage({
- sourceName: sourceName,
- targetName: targetName,
- isReply: true,
- callbackId: data.callbackId,
- error: reason
- });
- });
- } else {
- action[0].call(action[1], data.data);
- }
- } else {
- error('Unknown action from worker: ' + data.action);
- }
- }.bind(this);
- comObj.addEventListener('message', this._onComObjOnMessage);
-}
-MessageHandler.prototype = {
- on: function messageHandlerOn(actionName, handler, scope) {
- var ah = this.actionHandler;
- if (ah[actionName]) {
- error('There is already an actionName called "' + actionName + '"');
- }
- ah[actionName] = [handler, scope];
- },
- send: function messageHandlerSend(actionName, data, transfers) {
- var message = {
- sourceName: this.sourceName,
- targetName: this.targetName,
- action: actionName,
- data: data
- };
- this.postMessage(message, transfers);
- },
- sendWithPromise: function messageHandlerSendWithPromise(actionName, data, transfers) {
- var callbackId = this.callbackIndex++;
- var message = {
- sourceName: this.sourceName,
- targetName: this.targetName,
- action: actionName,
- data: data,
- callbackId: callbackId
- };
- var capability = createPromiseCapability();
- this.callbacksCapabilities[callbackId] = capability;
- try {
- this.postMessage(message, transfers);
- } catch (e) {
- capability.reject(e);
- }
- return capability.promise;
- },
- postMessage: function (message, transfers) {
- if (transfers && this.postMessageTransfers) {
- this.comObj.postMessage(message, transfers);
- } else {
- this.comObj.postMessage(message);
- }
- },
- destroy: function () {
- this.comObj.removeEventListener('message', this._onComObjOnMessage);
- }
-};
-function loadJpegStream(id, imageUrl, objs) {
- var img = new Image();
- img.onload = function loadJpegStream_onloadClosure() {
- objs.resolve(id, img);
- };
- img.onerror = function loadJpegStream_onerrorClosure() {
- objs.resolve(id, null);
- warn('Error during JPEG image loading');
- };
- img.src = imageUrl;
-}
-exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
-exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
-exports.OPS = OPS;
-exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS;
-exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
-exports.AnnotationBorderStyleType = AnnotationBorderStyleType;
-exports.AnnotationFieldFlag = AnnotationFieldFlag;
-exports.AnnotationFlag = AnnotationFlag;
-exports.AnnotationType = AnnotationType;
-exports.FontType = FontType;
-exports.ImageKind = ImageKind;
-exports.CMapCompressionType = CMapCompressionType;
-exports.InvalidPDFException = InvalidPDFException;
-exports.MessageHandler = MessageHandler;
-exports.MissingDataException = MissingDataException;
-exports.MissingPDFException = MissingPDFException;
-exports.NotImplementedException = NotImplementedException;
-exports.PageViewport = PageViewport;
-exports.PasswordException = PasswordException;
-exports.PasswordResponses = PasswordResponses;
-exports.StatTimer = StatTimer;
-exports.StreamType = StreamType;
-exports.TextRenderingMode = TextRenderingMode;
-exports.UnexpectedResponseException = UnexpectedResponseException;
-exports.UnknownErrorException = UnknownErrorException;
-exports.Util = Util;
-exports.XRefParseException = XRefParseException;
-exports.arrayByteLength = arrayByteLength;
-exports.arraysToBytes = arraysToBytes;
-exports.assert = assert;
-exports.bytesToString = bytesToString;
-exports.createBlob = createBlob;
-exports.createPromiseCapability = createPromiseCapability;
-exports.createObjectURL = createObjectURL;
-exports.deprecated = deprecated;
-exports.error = error;
-exports.getLookupTableFactory = getLookupTableFactory;
-exports.getVerbosityLevel = getVerbosityLevel;
-exports.globalScope = globalScope;
-exports.info = info;
-exports.isArray = isArray;
-exports.isArrayBuffer = isArrayBuffer;
-exports.isBool = isBool;
-exports.isEmptyObj = isEmptyObj;
-exports.isInt = isInt;
-exports.isNum = isNum;
-exports.isString = isString;
-exports.isSpace = isSpace;
-exports.isNodeJS = isNodeJS;
-exports.isSameOrigin = isSameOrigin;
-exports.createValidAbsoluteUrl = createValidAbsoluteUrl;
-exports.isLittleEndian = isLittleEndian;
-exports.isEvalSupported = isEvalSupported;
-exports.loadJpegStream = loadJpegStream;
-exports.log2 = log2;
-exports.readInt8 = readInt8;
-exports.readUint16 = readUint16;
-exports.readUint32 = readUint32;
-exports.removeNullCharacters = removeNullCharacters;
-exports.setVerbosityLevel = setVerbosityLevel;
-exports.shadow = shadow;
-exports.string32 = string32;
-exports.stringToBytes = stringToBytes;
-exports.stringToPDFString = stringToPDFString;
-exports.stringToUTF8String = stringToUTF8String;
-exports.utf8StringToString = utf8StringToString;
-exports.warn = warn;
-/* WEBPACK VAR INJECTION */}.call(exports, __w_pdfjs_require__(6)))
-
-/***/ }),
-/* 1 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var assert = sharedUtil.assert;
-var removeNullCharacters = sharedUtil.removeNullCharacters;
-var warn = sharedUtil.warn;
-var deprecated = sharedUtil.deprecated;
-var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
-var stringToBytes = sharedUtil.stringToBytes;
-var CMapCompressionType = sharedUtil.CMapCompressionType;
-var DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
-function DOMCanvasFactory() {}
-DOMCanvasFactory.prototype = {
- create: function DOMCanvasFactory_create(width, height) {
- assert(width > 0 && height > 0, 'invalid canvas size');
- var canvas = document.createElement('canvas');
- var context = canvas.getContext('2d');
- canvas.width = width;
- canvas.height = height;
- return {
- canvas: canvas,
- context: context
- };
- },
- reset: function DOMCanvasFactory_reset(canvasAndContextPair, width, height) {
- assert(canvasAndContextPair.canvas, 'canvas is not specified');
- assert(width > 0 && height > 0, 'invalid canvas size');
- canvasAndContextPair.canvas.width = width;
- canvasAndContextPair.canvas.height = height;
- },
- destroy: function DOMCanvasFactory_destroy(canvasAndContextPair) {
- assert(canvasAndContextPair.canvas, 'canvas is not specified');
- canvasAndContextPair.canvas.width = 0;
- canvasAndContextPair.canvas.height = 0;
- canvasAndContextPair.canvas = null;
- canvasAndContextPair.context = null;
- }
-};
-var DOMCMapReaderFactory = function DOMCMapReaderFactoryClosure() {
- function DOMCMapReaderFactory(params) {
- this.baseUrl = params.baseUrl || null;
- this.isCompressed = params.isCompressed || false;
- }
- DOMCMapReaderFactory.prototype = {
- fetch: function (params) {
- var name = params.name;
- if (!name) {
- return Promise.reject(new Error('CMap name must be specified.'));
- }
- return new Promise(function (resolve, reject) {
- var url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
- var request = new XMLHttpRequest();
- request.open('GET', url, true);
- if (this.isCompressed) {
- request.responseType = 'arraybuffer';
- }
- request.onreadystatechange = function () {
- if (request.readyState !== XMLHttpRequest.DONE) {
- return;
- }
- if (request.status === 200 || request.status === 0) {
- var data;
- if (this.isCompressed && request.response) {
- data = new Uint8Array(request.response);
- } else if (!this.isCompressed && request.responseText) {
- data = stringToBytes(request.responseText);
- }
- if (data) {
- resolve({
- cMapData: data,
- compressionType: this.isCompressed ? CMapCompressionType.BINARY : CMapCompressionType.NONE
- });
- return;
- }
- }
- reject(new Error('Unable to load ' + (this.isCompressed ? 'binary ' : '') + 'CMap at: ' + url));
- }.bind(this);
- request.send(null);
- }.bind(this));
- }
- };
- return DOMCMapReaderFactory;
-}();
-var CustomStyle = function CustomStyleClosure() {
- var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
- var _cache = Object.create(null);
- function CustomStyle() {}
- CustomStyle.getProp = function get(propName, element) {
- if (arguments.length === 1 && typeof _cache[propName] === 'string') {
- return _cache[propName];
- }
- element = element || document.documentElement;
- var style = element.style,
- prefixed,
- uPropName;
- if (typeof style[propName] === 'string') {
- return _cache[propName] = propName;
- }
- uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
- for (var i = 0, l = prefixes.length; i < l; i++) {
- prefixed = prefixes[i] + uPropName;
- if (typeof style[prefixed] === 'string') {
- return _cache[propName] = prefixed;
- }
- }
- return _cache[propName] = 'undefined';
- };
- CustomStyle.setProp = function set(propName, element, str) {
- var prop = this.getProp(propName);
- if (prop !== 'undefined') {
- element.style[prop] = str;
- }
- };
- return CustomStyle;
-}();
-var RenderingCancelledException = function RenderingCancelledException() {
- function RenderingCancelledException(msg, type) {
- this.message = msg;
- this.type = type;
- }
- RenderingCancelledException.prototype = new Error();
- RenderingCancelledException.prototype.name = 'RenderingCancelledException';
- RenderingCancelledException.constructor = RenderingCancelledException;
- return RenderingCancelledException;
-}();
-var hasCanvasTypedArrays;
-hasCanvasTypedArrays = function hasCanvasTypedArrays() {
- var canvas = document.createElement('canvas');
- canvas.width = canvas.height = 1;
- var ctx = canvas.getContext('2d');
- var imageData = ctx.createImageData(1, 1);
- return typeof imageData.data.buffer !== 'undefined';
-};
-var LinkTarget = {
- NONE: 0,
- SELF: 1,
- BLANK: 2,
- PARENT: 3,
- TOP: 4
-};
-var LinkTargetStringMap = ['', '_self', '_blank', '_parent', '_top'];
-function addLinkAttributes(link, params) {
- var url = params && params.url;
- link.href = link.title = url ? removeNullCharacters(url) : '';
- if (url) {
- var target = params.target;
- if (typeof target === 'undefined') {
- target = getDefaultSetting('externalLinkTarget');
- }
- link.target = LinkTargetStringMap[target];
- var rel = params.rel;
- if (typeof rel === 'undefined') {
- rel = getDefaultSetting('externalLinkRel');
- }
- link.rel = rel;
- }
-}
-function getFilenameFromUrl(url) {
- var anchor = url.indexOf('#');
- var query = url.indexOf('?');
- var end = Math.min(anchor > 0 ? anchor : url.length, query > 0 ? query : url.length);
- return url.substring(url.lastIndexOf('/', end) + 1, end);
-}
-function getDefaultSetting(id) {
- var globalSettings = sharedUtil.globalScope.PDFJS;
- switch (id) {
- case 'pdfBug':
- return globalSettings ? globalSettings.pdfBug : false;
- case 'disableAutoFetch':
- return globalSettings ? globalSettings.disableAutoFetch : false;
- case 'disableStream':
- return globalSettings ? globalSettings.disableStream : false;
- case 'disableRange':
- return globalSettings ? globalSettings.disableRange : false;
- case 'disableFontFace':
- return globalSettings ? globalSettings.disableFontFace : false;
- case 'disableCreateObjectURL':
- return globalSettings ? globalSettings.disableCreateObjectURL : false;
- case 'disableWebGL':
- return globalSettings ? globalSettings.disableWebGL : true;
- case 'cMapUrl':
- return globalSettings ? globalSettings.cMapUrl : null;
- case 'cMapPacked':
- return globalSettings ? globalSettings.cMapPacked : false;
- case 'postMessageTransfers':
- return globalSettings ? globalSettings.postMessageTransfers : true;
- case 'workerPort':
- return globalSettings ? globalSettings.workerPort : null;
- case 'workerSrc':
- return globalSettings ? globalSettings.workerSrc : null;
- case 'disableWorker':
- return globalSettings ? globalSettings.disableWorker : false;
- case 'maxImageSize':
- return globalSettings ? globalSettings.maxImageSize : -1;
- case 'imageResourcesPath':
- return globalSettings ? globalSettings.imageResourcesPath : '';
- case 'isEvalSupported':
- return globalSettings ? globalSettings.isEvalSupported : true;
- case 'externalLinkTarget':
- if (!globalSettings) {
- return LinkTarget.NONE;
- }
- switch (globalSettings.externalLinkTarget) {
- case LinkTarget.NONE:
- case LinkTarget.SELF:
- case LinkTarget.BLANK:
- case LinkTarget.PARENT:
- case LinkTarget.TOP:
- return globalSettings.externalLinkTarget;
- }
- warn('PDFJS.externalLinkTarget is invalid: ' + globalSettings.externalLinkTarget);
- globalSettings.externalLinkTarget = LinkTarget.NONE;
- return LinkTarget.NONE;
- case 'externalLinkRel':
- return globalSettings ? globalSettings.externalLinkRel : DEFAULT_LINK_REL;
- case 'enableStats':
- return !!(globalSettings && globalSettings.enableStats);
- case 'pdfjsNext':
- return !!(globalSettings && globalSettings.pdfjsNext);
- default:
- throw new Error('Unknown default setting: ' + id);
- }
-}
-function isExternalLinkTargetSet() {
- var externalLinkTarget = getDefaultSetting('externalLinkTarget');
- switch (externalLinkTarget) {
- case LinkTarget.NONE:
- return false;
- case LinkTarget.SELF:
- case LinkTarget.BLANK:
- case LinkTarget.PARENT:
- case LinkTarget.TOP:
- return true;
- }
-}
-function isValidUrl(url, allowRelative) {
- deprecated('isValidUrl(), please use createValidAbsoluteUrl() instead.');
- var baseUrl = allowRelative ? 'http://example.com' : null;
- return createValidAbsoluteUrl(url, baseUrl) !== null;
-}
-exports.CustomStyle = CustomStyle;
-exports.addLinkAttributes = addLinkAttributes;
-exports.isExternalLinkTargetSet = isExternalLinkTargetSet;
-exports.isValidUrl = isValidUrl;
-exports.getFilenameFromUrl = getFilenameFromUrl;
-exports.LinkTarget = LinkTarget;
-exports.RenderingCancelledException = RenderingCancelledException;
-exports.hasCanvasTypedArrays = hasCanvasTypedArrays;
-exports.getDefaultSetting = getDefaultSetting;
-exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL;
-exports.DOMCanvasFactory = DOMCanvasFactory;
-exports.DOMCMapReaderFactory = DOMCMapReaderFactory;
-
-/***/ }),
-/* 2 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
-var AnnotationType = sharedUtil.AnnotationType;
-var stringToPDFString = sharedUtil.stringToPDFString;
-var Util = sharedUtil.Util;
-var addLinkAttributes = displayDOMUtils.addLinkAttributes;
-var LinkTarget = displayDOMUtils.LinkTarget;
-var getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
-var warn = sharedUtil.warn;
-var CustomStyle = displayDOMUtils.CustomStyle;
-var getDefaultSetting = displayDOMUtils.getDefaultSetting;
-function AnnotationElementFactory() {}
-AnnotationElementFactory.prototype = {
- create: function AnnotationElementFactory_create(parameters) {
- var subtype = parameters.data.annotationType;
- switch (subtype) {
- case AnnotationType.LINK:
- return new LinkAnnotationElement(parameters);
- case AnnotationType.TEXT:
- return new TextAnnotationElement(parameters);
- case AnnotationType.WIDGET:
- var fieldType = parameters.data.fieldType;
- switch (fieldType) {
- case 'Tx':
- return new TextWidgetAnnotationElement(parameters);
- case 'Btn':
- if (parameters.data.radioButton) {
- return new RadioButtonWidgetAnnotationElement(parameters);
- } else if (parameters.data.checkBox) {
- return new CheckboxWidgetAnnotationElement(parameters);
- }
- warn('Unimplemented button widget annotation: pushbutton');
- break;
- case 'Ch':
- return new ChoiceWidgetAnnotationElement(parameters);
- }
- return new WidgetAnnotationElement(parameters);
- case AnnotationType.POPUP:
- return new PopupAnnotationElement(parameters);
- case AnnotationType.HIGHLIGHT:
- return new HighlightAnnotationElement(parameters);
- case AnnotationType.UNDERLINE:
- return new UnderlineAnnotationElement(parameters);
- case AnnotationType.SQUIGGLY:
- return new SquigglyAnnotationElement(parameters);
- case AnnotationType.STRIKEOUT:
- return new StrikeOutAnnotationElement(parameters);
- case AnnotationType.FILEATTACHMENT:
- return new FileAttachmentAnnotationElement(parameters);
- default:
- return new AnnotationElement(parameters);
- }
- }
-};
-var AnnotationElement = function AnnotationElementClosure() {
- function AnnotationElement(parameters, isRenderable) {
- this.isRenderable = isRenderable || false;
- this.data = parameters.data;
- this.layer = parameters.layer;
- this.page = parameters.page;
- this.viewport = parameters.viewport;
- this.linkService = parameters.linkService;
- this.downloadManager = parameters.downloadManager;
- this.imageResourcesPath = parameters.imageResourcesPath;
- this.renderInteractiveForms = parameters.renderInteractiveForms;
- if (isRenderable) {
- this.container = this._createContainer();
- }
- }
- AnnotationElement.prototype = {
- _createContainer: function AnnotationElement_createContainer() {
- var data = this.data,
- page = this.page,
- viewport = this.viewport;
- var container = document.createElement('section');
- var width = data.rect[2] - data.rect[0];
- var height = data.rect[3] - data.rect[1];
- container.setAttribute('data-annotation-id', data.id);
- var rect = Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
- CustomStyle.setProp('transform', container, 'matrix(' + viewport.transform.join(',') + ')');
- CustomStyle.setProp('transformOrigin', container, -rect[0] + 'px ' + -rect[1] + 'px');
- if (data.borderStyle.width > 0) {
- container.style.borderWidth = data.borderStyle.width + 'px';
- if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) {
- width = width - 2 * data.borderStyle.width;
- height = height - 2 * data.borderStyle.width;
- }
- var horizontalRadius = data.borderStyle.horizontalCornerRadius;
- var verticalRadius = data.borderStyle.verticalCornerRadius;
- if (horizontalRadius > 0 || verticalRadius > 0) {
- var radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
- CustomStyle.setProp('borderRadius', container, radius);
- }
- switch (data.borderStyle.style) {
- case AnnotationBorderStyleType.SOLID:
- container.style.borderStyle = 'solid';
- break;
- case AnnotationBorderStyleType.DASHED:
- container.style.borderStyle = 'dashed';
- break;
- case AnnotationBorderStyleType.BEVELED:
- warn('Unimplemented border style: beveled');
- break;
- case AnnotationBorderStyleType.INSET:
- warn('Unimplemented border style: inset');
- break;
- case AnnotationBorderStyleType.UNDERLINE:
- container.style.borderBottomStyle = 'solid';
- break;
- default:
- break;
- }
- if (data.color) {
- container.style.borderColor = Util.makeCssRgb(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
- } else {
- container.style.borderWidth = 0;
- }
- }
- container.style.left = rect[0] + 'px';
- container.style.top = rect[1] + 'px';
- container.style.width = width + 'px';
- container.style.height = height + 'px';
- return container;
- },
- _createPopup: function AnnotationElement_createPopup(container, trigger, data) {
- if (!trigger) {
- trigger = document.createElement('div');
- trigger.style.height = container.style.height;
- trigger.style.width = container.style.width;
- container.appendChild(trigger);
- }
- var popupElement = new PopupElement({
- container: container,
- trigger: trigger,
- color: data.color,
- title: data.title,
- contents: data.contents,
- hideWrapper: true
- });
- var popup = popupElement.render();
- popup.style.left = container.style.width;
- container.appendChild(popup);
- },
- render: function AnnotationElement_render() {
- throw new Error('Abstract method AnnotationElement.render called');
- }
- };
- return AnnotationElement;
-}();
-var LinkAnnotationElement = function LinkAnnotationElementClosure() {
- function LinkAnnotationElement(parameters) {
- AnnotationElement.call(this, parameters, true);
- }
- Util.inherit(LinkAnnotationElement, AnnotationElement, {
- render: function LinkAnnotationElement_render() {
- this.container.className = 'linkAnnotation';
- var link = document.createElement('a');
- addLinkAttributes(link, {
- url: this.data.url,
- target: this.data.newWindow ? LinkTarget.BLANK : undefined
- });
- if (!this.data.url) {
- if (this.data.action) {
- this._bindNamedAction(link, this.data.action);
- } else {
- this._bindLink(link, this.data.dest);
- }
- }
- this.container.appendChild(link);
- return this.container;
- },
- _bindLink: function LinkAnnotationElement_bindLink(link, destination) {
- var self = this;
- link.href = this.linkService.getDestinationHash(destination);
- link.onclick = function () {
- if (destination) {
- self.linkService.navigateTo(destination);
- }
- return false;
- };
- if (destination) {
- link.className = 'internalLink';
- }
- },
- _bindNamedAction: function LinkAnnotationElement_bindNamedAction(link, action) {
- var self = this;
- link.href = this.linkService.getAnchorUrl('');
- link.onclick = function () {
- self.linkService.executeNamedAction(action);
- return false;
- };
- link.className = 'internalLink';
- }
- });
- return LinkAnnotationElement;
-}();
-var TextAnnotationElement = function TextAnnotationElementClosure() {
- function TextAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(TextAnnotationElement, AnnotationElement, {
- render: function TextAnnotationElement_render() {
- this.container.className = 'textAnnotation';
- var image = document.createElement('img');
- image.style.height = this.container.style.height;
- image.style.width = this.container.style.width;
- image.src = this.imageResourcesPath + 'annotation-' + this.data.name.toLowerCase() + '.svg';
- image.alt = '[{{type}} Annotation]';
- image.dataset.l10nId = 'text_annotation_type';
- image.dataset.l10nArgs = JSON.stringify({ type: this.data.name });
- if (!this.data.hasPopup) {
- this._createPopup(this.container, image, this.data);
- }
- this.container.appendChild(image);
- return this.container;
- }
- });
- return TextAnnotationElement;
-}();
-var WidgetAnnotationElement = function WidgetAnnotationElementClosure() {
- function WidgetAnnotationElement(parameters, isRenderable) {
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(WidgetAnnotationElement, AnnotationElement, {
- render: function WidgetAnnotationElement_render() {
- return this.container;
- }
- });
- return WidgetAnnotationElement;
-}();
-var TextWidgetAnnotationElement = function TextWidgetAnnotationElementClosure() {
- var TEXT_ALIGNMENT = ['left', 'center', 'right'];
- function TextWidgetAnnotationElement(parameters) {
- var isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
- WidgetAnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
- render: function TextWidgetAnnotationElement_render() {
- this.container.className = 'textWidgetAnnotation';
- var element = null;
- if (this.renderInteractiveForms) {
- if (this.data.multiLine) {
- element = document.createElement('textarea');
- element.textContent = this.data.fieldValue;
- } else {
- element = document.createElement('input');
- element.type = 'text';
- element.setAttribute('value', this.data.fieldValue);
- }
- element.disabled = this.data.readOnly;
- if (this.data.maxLen !== null) {
- element.maxLength = this.data.maxLen;
- }
- if (this.data.comb) {
- var fieldWidth = this.data.rect[2] - this.data.rect[0];
- var combWidth = fieldWidth / this.data.maxLen;
- element.classList.add('comb');
- element.style.letterSpacing = 'calc(' + combWidth + 'px - 1ch)';
- }
- } else {
- element = document.createElement('div');
- element.textContent = this.data.fieldValue;
- element.style.verticalAlign = 'middle';
- element.style.display = 'table-cell';
- var font = null;
- if (this.data.fontRefName) {
- font = this.page.commonObjs.getData(this.data.fontRefName);
- }
- this._setTextStyle(element, font);
- }
- if (this.data.textAlignment !== null) {
- element.style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
- }
- this.container.appendChild(element);
- return this.container;
- },
- _setTextStyle: function TextWidgetAnnotationElement_setTextStyle(element, font) {
- var style = element.style;
- style.fontSize = this.data.fontSize + 'px';
- style.direction = this.data.fontDirection < 0 ? 'rtl' : 'ltr';
- if (!font) {
- return;
- }
- style.fontWeight = font.black ? font.bold ? '900' : 'bold' : font.bold ? 'bold' : 'normal';
- style.fontStyle = font.italic ? 'italic' : 'normal';
- var fontFamily = font.loadedName ? '"' + font.loadedName + '", ' : '';
- var fallbackName = font.fallbackName || 'Helvetica, sans-serif';
- style.fontFamily = fontFamily + fallbackName;
- }
- });
- return TextWidgetAnnotationElement;
-}();
-var CheckboxWidgetAnnotationElement = function CheckboxWidgetAnnotationElementClosure() {
- function CheckboxWidgetAnnotationElement(parameters) {
- WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
- }
- Util.inherit(CheckboxWidgetAnnotationElement, WidgetAnnotationElement, {
- render: function CheckboxWidgetAnnotationElement_render() {
- this.container.className = 'buttonWidgetAnnotation checkBox';
- var element = document.createElement('input');
- element.disabled = this.data.readOnly;
- element.type = 'checkbox';
- if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
- element.setAttribute('checked', true);
- }
- this.container.appendChild(element);
- return this.container;
- }
- });
- return CheckboxWidgetAnnotationElement;
-}();
-var RadioButtonWidgetAnnotationElement = function RadioButtonWidgetAnnotationElementClosure() {
- function RadioButtonWidgetAnnotationElement(parameters) {
- WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
- }
- Util.inherit(RadioButtonWidgetAnnotationElement, WidgetAnnotationElement, {
- render: function RadioButtonWidgetAnnotationElement_render() {
- this.container.className = 'buttonWidgetAnnotation radioButton';
- var element = document.createElement('input');
- element.disabled = this.data.readOnly;
- element.type = 'radio';
- element.name = this.data.fieldName;
- if (this.data.fieldValue === this.data.buttonValue) {
- element.setAttribute('checked', true);
- }
- this.container.appendChild(element);
- return this.container;
- }
- });
- return RadioButtonWidgetAnnotationElement;
-}();
-var ChoiceWidgetAnnotationElement = function ChoiceWidgetAnnotationElementClosure() {
- function ChoiceWidgetAnnotationElement(parameters) {
- WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
- }
- Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
- render: function ChoiceWidgetAnnotationElement_render() {
- this.container.className = 'choiceWidgetAnnotation';
- var selectElement = document.createElement('select');
- selectElement.disabled = this.data.readOnly;
- if (!this.data.combo) {
- selectElement.size = this.data.options.length;
- if (this.data.multiSelect) {
- selectElement.multiple = true;
- }
- }
- for (var i = 0, ii = this.data.options.length; i < ii; i++) {
- var option = this.data.options[i];
- var optionElement = document.createElement('option');
- optionElement.textContent = option.displayValue;
- optionElement.value = option.exportValue;
- if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
- optionElement.setAttribute('selected', true);
- }
- selectElement.appendChild(optionElement);
- }
- this.container.appendChild(selectElement);
- return this.container;
- }
- });
- return ChoiceWidgetAnnotationElement;
-}();
-var PopupAnnotationElement = function PopupAnnotationElementClosure() {
- function PopupAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(PopupAnnotationElement, AnnotationElement, {
- render: function PopupAnnotationElement_render() {
- this.container.className = 'popupAnnotation';
- var selector = '[data-annotation-id="' + this.data.parentId + '"]';
- var parentElement = this.layer.querySelector(selector);
- if (!parentElement) {
- return this.container;
- }
- var popup = new PopupElement({
- container: this.container,
- trigger: parentElement,
- color: this.data.color,
- title: this.data.title,
- contents: this.data.contents
- });
- var parentLeft = parseFloat(parentElement.style.left);
- var parentWidth = parseFloat(parentElement.style.width);
- CustomStyle.setProp('transformOrigin', this.container, -(parentLeft + parentWidth) + 'px -' + parentElement.style.top);
- this.container.style.left = parentLeft + parentWidth + 'px';
- this.container.appendChild(popup.render());
- return this.container;
- }
- });
- return PopupAnnotationElement;
-}();
-var PopupElement = function PopupElementClosure() {
- var BACKGROUND_ENLIGHT = 0.7;
- function PopupElement(parameters) {
- this.container = parameters.container;
- this.trigger = parameters.trigger;
- this.color = parameters.color;
- this.title = parameters.title;
- this.contents = parameters.contents;
- this.hideWrapper = parameters.hideWrapper || false;
- this.pinned = false;
- }
- PopupElement.prototype = {
- render: function PopupElement_render() {
- var wrapper = document.createElement('div');
- wrapper.className = 'popupWrapper';
- this.hideElement = this.hideWrapper ? wrapper : this.container;
- this.hideElement.setAttribute('hidden', true);
- var popup = document.createElement('div');
- popup.className = 'popup';
- var color = this.color;
- if (color) {
- var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
- var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
- var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
- popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
- }
- var contents = this._formatContents(this.contents);
- var title = document.createElement('h1');
- title.textContent = this.title;
- this.trigger.addEventListener('click', this._toggle.bind(this));
- this.trigger.addEventListener('mouseover', this._show.bind(this, false));
- this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
- popup.addEventListener('click', this._hide.bind(this, true));
- popup.appendChild(title);
- popup.appendChild(contents);
- wrapper.appendChild(popup);
- return wrapper;
- },
- _formatContents: function PopupElement_formatContents(contents) {
- var p = document.createElement('p');
- var lines = contents.split(/(?:\r\n?|\n)/);
- for (var i = 0, ii = lines.length; i < ii; ++i) {
- var line = lines[i];
- p.appendChild(document.createTextNode(line));
- if (i < ii - 1) {
- p.appendChild(document.createElement('br'));
- }
- }
- return p;
- },
- _toggle: function PopupElement_toggle() {
- if (this.pinned) {
- this._hide(true);
- } else {
- this._show(true);
- }
- },
- _show: function PopupElement_show(pin) {
- if (pin) {
- this.pinned = true;
- }
- if (this.hideElement.hasAttribute('hidden')) {
- this.hideElement.removeAttribute('hidden');
- this.container.style.zIndex += 1;
- }
- },
- _hide: function PopupElement_hide(unpin) {
- if (unpin) {
- this.pinned = false;
- }
- if (!this.hideElement.hasAttribute('hidden') && !this.pinned) {
- this.hideElement.setAttribute('hidden', true);
- this.container.style.zIndex -= 1;
- }
- }
- };
- return PopupElement;
-}();
-var HighlightAnnotationElement = function HighlightAnnotationElementClosure() {
- function HighlightAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(HighlightAnnotationElement, AnnotationElement, {
- render: function HighlightAnnotationElement_render() {
- this.container.className = 'highlightAnnotation';
- if (!this.data.hasPopup) {
- this._createPopup(this.container, null, this.data);
- }
- return this.container;
- }
- });
- return HighlightAnnotationElement;
-}();
-var UnderlineAnnotationElement = function UnderlineAnnotationElementClosure() {
- function UnderlineAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(UnderlineAnnotationElement, AnnotationElement, {
- render: function UnderlineAnnotationElement_render() {
- this.container.className = 'underlineAnnotation';
- if (!this.data.hasPopup) {
- this._createPopup(this.container, null, this.data);
- }
- return this.container;
- }
- });
- return UnderlineAnnotationElement;
-}();
-var SquigglyAnnotationElement = function SquigglyAnnotationElementClosure() {
- function SquigglyAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(SquigglyAnnotationElement, AnnotationElement, {
- render: function SquigglyAnnotationElement_render() {
- this.container.className = 'squigglyAnnotation';
- if (!this.data.hasPopup) {
- this._createPopup(this.container, null, this.data);
- }
- return this.container;
- }
- });
- return SquigglyAnnotationElement;
-}();
-var StrikeOutAnnotationElement = function StrikeOutAnnotationElementClosure() {
- function StrikeOutAnnotationElement(parameters) {
- var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
- AnnotationElement.call(this, parameters, isRenderable);
- }
- Util.inherit(StrikeOutAnnotationElement, AnnotationElement, {
- render: function StrikeOutAnnotationElement_render() {
- this.container.className = 'strikeoutAnnotation';
- if (!this.data.hasPopup) {
- this._createPopup(this.container, null, this.data);
- }
- return this.container;
- }
- });
- return StrikeOutAnnotationElement;
-}();
-var FileAttachmentAnnotationElement = function FileAttachmentAnnotationElementClosure() {
- function FileAttachmentAnnotationElement(parameters) {
- AnnotationElement.call(this, parameters, true);
- var file = this.data.file;
- this.filename = getFilenameFromUrl(file.filename);
- this.content = file.content;
- this.linkService.onFileAttachmentAnnotation({
- id: stringToPDFString(file.filename),
- filename: file.filename,
- content: file.content
- });
- }
- Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, {
- render: function FileAttachmentAnnotationElement_render() {
- this.container.className = 'fileAttachmentAnnotation';
- var trigger = document.createElement('div');
- trigger.style.height = this.container.style.height;
- trigger.style.width = this.container.style.width;
- trigger.addEventListener('dblclick', this._download.bind(this));
- if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
- this._createPopup(this.container, trigger, this.data);
- }
- this.container.appendChild(trigger);
- return this.container;
- },
- _download: function FileAttachmentAnnotationElement_download() {
- if (!this.downloadManager) {
- warn('Download cannot be started due to unavailable download manager');
- return;
- }
- this.downloadManager.downloadData(this.content, this.filename, '');
- }
- });
- return FileAttachmentAnnotationElement;
-}();
-var AnnotationLayer = function AnnotationLayerClosure() {
- return {
- render: function AnnotationLayer_render(parameters) {
- var annotationElementFactory = new AnnotationElementFactory();
- for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
- var data = parameters.annotations[i];
- if (!data) {
- continue;
- }
- var element = annotationElementFactory.create({
- data: data,
- layer: parameters.div,
- page: parameters.page,
- viewport: parameters.viewport,
- linkService: parameters.linkService,
- downloadManager: parameters.downloadManager,
- imageResourcesPath: parameters.imageResourcesPath || getDefaultSetting('imageResourcesPath'),
- renderInteractiveForms: parameters.renderInteractiveForms || false
- });
- if (element.isRenderable) {
- parameters.div.appendChild(element.render());
- }
- }
- },
- update: function AnnotationLayer_update(parameters) {
- for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
- var data = parameters.annotations[i];
- var element = parameters.div.querySelector('[data-annotation-id="' + data.id + '"]');
- if (element) {
- CustomStyle.setProp('transform', element, 'matrix(' + parameters.viewport.transform.join(',') + ')');
- }
- }
- parameters.div.removeAttribute('hidden');
- }
- };
-}();
-exports.AnnotationLayer = AnnotationLayer;
-
-/***/ }),
-/* 3 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayFontLoader = __w_pdfjs_require__(11);
-var displayCanvas = __w_pdfjs_require__(10);
-var displayMetadata = __w_pdfjs_require__(7);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var amdRequire;
-var InvalidPDFException = sharedUtil.InvalidPDFException;
-var MessageHandler = sharedUtil.MessageHandler;
-var MissingPDFException = sharedUtil.MissingPDFException;
-var PageViewport = sharedUtil.PageViewport;
-var PasswordException = sharedUtil.PasswordException;
-var StatTimer = sharedUtil.StatTimer;
-var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
-var UnknownErrorException = sharedUtil.UnknownErrorException;
-var Util = sharedUtil.Util;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var error = sharedUtil.error;
-var deprecated = sharedUtil.deprecated;
-var getVerbosityLevel = sharedUtil.getVerbosityLevel;
-var info = sharedUtil.info;
-var isInt = sharedUtil.isInt;
-var isArray = sharedUtil.isArray;
-var isArrayBuffer = sharedUtil.isArrayBuffer;
-var isSameOrigin = sharedUtil.isSameOrigin;
-var loadJpegStream = sharedUtil.loadJpegStream;
-var stringToBytes = sharedUtil.stringToBytes;
-var globalScope = sharedUtil.globalScope;
-var warn = sharedUtil.warn;
-var FontFaceObject = displayFontLoader.FontFaceObject;
-var FontLoader = displayFontLoader.FontLoader;
-var CanvasGraphics = displayCanvas.CanvasGraphics;
-var Metadata = displayMetadata.Metadata;
-var RenderingCancelledException = displayDOMUtils.RenderingCancelledException;
-var getDefaultSetting = displayDOMUtils.getDefaultSetting;
-var DOMCanvasFactory = displayDOMUtils.DOMCanvasFactory;
-var DOMCMapReaderFactory = displayDOMUtils.DOMCMapReaderFactory;
-var DEFAULT_RANGE_CHUNK_SIZE = 65536;
-var isWorkerDisabled = false;
-var workerSrc;
-var isPostMessageTransfersDisabled = false;
-var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
-var fakeWorkerFilesLoader = null;
-var useRequireEnsure = false;
-if (typeof __pdfjsdev_webpack__ === 'undefined') {
- if (typeof window === 'undefined') {
- isWorkerDisabled = true;
- if (typeof require.ensure === 'undefined') {
- require.ensure = require('node-ensure');
- }
- useRequireEnsure = true;
- } else if (typeof require !== 'undefined' && typeof require.ensure === 'function') {
- useRequireEnsure = true;
- }
- if (typeof requirejs !== 'undefined' && requirejs.toUrl) {
- workerSrc = requirejs.toUrl('pdfjs-dist/build/pdf.worker.js');
- }
- var dynamicLoaderSupported = typeof requirejs !== 'undefined' && requirejs.load;
- fakeWorkerFilesLoader = useRequireEnsure ? function (callback) {
- require.ensure([], function () {
- var worker = require('./pdf.worker.js');
- callback(worker.WorkerMessageHandler);
- });
- } : dynamicLoaderSupported ? function (callback) {
- requirejs(['pdfjs-dist/build/pdf.worker'], function (worker) {
- callback(worker.WorkerMessageHandler);
- });
- } : null;
-}
-function getDocument(src, pdfDataRangeTransport, passwordCallback, progressCallback) {
- var task = new PDFDocumentLoadingTask();
- if (arguments.length > 1) {
- deprecated('getDocument is called with pdfDataRangeTransport, ' + 'passwordCallback or progressCallback argument');
- }
- if (pdfDataRangeTransport) {
- if (!(pdfDataRangeTransport instanceof PDFDataRangeTransport)) {
- pdfDataRangeTransport = Object.create(pdfDataRangeTransport);
- pdfDataRangeTransport.length = src.length;
- pdfDataRangeTransport.initialData = src.initialData;
- if (!pdfDataRangeTransport.abort) {
- pdfDataRangeTransport.abort = function () {};
- }
- }
- src = Object.create(src);
- src.range = pdfDataRangeTransport;
- }
- task.onPassword = passwordCallback || null;
- task.onProgress = progressCallback || null;
- var source;
- if (typeof src === 'string') {
- source = { url: src };
- } else if (isArrayBuffer(src)) {
- source = { data: src };
- } else if (src instanceof PDFDataRangeTransport) {
- source = { range: src };
- } else {
- if (typeof src !== 'object') {
- error('Invalid parameter in getDocument, need either Uint8Array, ' + 'string or a parameter object');
- }
- if (!src.url && !src.data && !src.range) {
- error('Invalid parameter object: need either .data, .range or .url');
- }
- source = src;
- }
- var params = {};
- var rangeTransport = null;
- var worker = null;
- for (var key in source) {
- if (key === 'url' && typeof window !== 'undefined') {
- params[key] = new URL(source[key], window.location).href;
- continue;
- } else if (key === 'range') {
- rangeTransport = source[key];
- continue;
- } else if (key === 'worker') {
- worker = source[key];
- continue;
- } else if (key === 'data' && !(source[key] instanceof Uint8Array)) {
- var pdfBytes = source[key];
- if (typeof pdfBytes === 'string') {
- params[key] = stringToBytes(pdfBytes);
- } else if (typeof pdfBytes === 'object' && pdfBytes !== null && !isNaN(pdfBytes.length)) {
- params[key] = new Uint8Array(pdfBytes);
- } else if (isArrayBuffer(pdfBytes)) {
- params[key] = new Uint8Array(pdfBytes);
- } else {
- error('Invalid PDF binary data: either typed array, string or ' + 'array-like object is expected in the data property.');
- }
- continue;
- }
- params[key] = source[key];
- }
- params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
- params.disableNativeImageDecoder = params.disableNativeImageDecoder === true;
- var CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory;
- if (!worker) {
- var workerPort = getDefaultSetting('workerPort');
- worker = workerPort ? new PDFWorker(null, workerPort) : new PDFWorker();
- task._worker = worker;
- }
- var docId = task.docId;
- worker.promise.then(function () {
- if (task.destroyed) {
- throw new Error('Loading aborted');
- }
- return _fetchDocument(worker, params, rangeTransport, docId).then(function (workerId) {
- if (task.destroyed) {
- throw new Error('Loading aborted');
- }
- var messageHandler = new MessageHandler(docId, workerId, worker.port);
- var transport = new WorkerTransport(messageHandler, task, rangeTransport, CMapReaderFactory);
- task._transport = transport;
- messageHandler.send('Ready', null);
- });
- }).catch(task._capability.reject);
- return task;
-}
-function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
- if (worker.destroyed) {
- return Promise.reject(new Error('Worker was destroyed'));
- }
- source.disableAutoFetch = getDefaultSetting('disableAutoFetch');
- source.disableStream = getDefaultSetting('disableStream');
- source.chunkedViewerLoading = !!pdfDataRangeTransport;
- if (pdfDataRangeTransport) {
- source.length = pdfDataRangeTransport.length;
- source.initialData = pdfDataRangeTransport.initialData;
- }
- return worker.messageHandler.sendWithPromise('GetDocRequest', {
- docId: docId,
- source: source,
- disableRange: getDefaultSetting('disableRange'),
- maxImageSize: getDefaultSetting('maxImageSize'),
- disableFontFace: getDefaultSetting('disableFontFace'),
- disableCreateObjectURL: getDefaultSetting('disableCreateObjectURL'),
- postMessageTransfers: getDefaultSetting('postMessageTransfers') && !isPostMessageTransfersDisabled,
- docBaseUrl: source.docBaseUrl,
- disableNativeImageDecoder: source.disableNativeImageDecoder
- }).then(function (workerId) {
- if (worker.destroyed) {
- throw new Error('Worker was destroyed');
- }
- return workerId;
- });
-}
-var PDFDocumentLoadingTask = function PDFDocumentLoadingTaskClosure() {
- var nextDocumentId = 0;
- function PDFDocumentLoadingTask() {
- this._capability = createPromiseCapability();
- this._transport = null;
- this._worker = null;
- this.docId = 'd' + nextDocumentId++;
- this.destroyed = false;
- this.onPassword = null;
- this.onProgress = null;
- this.onUnsupportedFeature = null;
- }
- PDFDocumentLoadingTask.prototype = {
- get promise() {
- return this._capability.promise;
- },
- destroy: function () {
- this.destroyed = true;
- var transportDestroyed = !this._transport ? Promise.resolve() : this._transport.destroy();
- return transportDestroyed.then(function () {
- this._transport = null;
- if (this._worker) {
- this._worker.destroy();
- this._worker = null;
- }
- }.bind(this));
- },
- then: function PDFDocumentLoadingTask_then(onFulfilled, onRejected) {
- return this.promise.then.apply(this.promise, arguments);
- }
- };
- return PDFDocumentLoadingTask;
-}();
-var PDFDataRangeTransport = function pdfDataRangeTransportClosure() {
- function PDFDataRangeTransport(length, initialData) {
- this.length = length;
- this.initialData = initialData;
- this._rangeListeners = [];
- this._progressListeners = [];
- this._progressiveReadListeners = [];
- this._readyCapability = createPromiseCapability();
- }
- PDFDataRangeTransport.prototype = {
- addRangeListener: function PDFDataRangeTransport_addRangeListener(listener) {
- this._rangeListeners.push(listener);
- },
- addProgressListener: function PDFDataRangeTransport_addProgressListener(listener) {
- this._progressListeners.push(listener);
- },
- addProgressiveReadListener: function PDFDataRangeTransport_addProgressiveReadListener(listener) {
- this._progressiveReadListeners.push(listener);
- },
- onDataRange: function PDFDataRangeTransport_onDataRange(begin, chunk) {
- var listeners = this._rangeListeners;
- for (var i = 0, n = listeners.length; i < n; ++i) {
- listeners[i](begin, chunk);
- }
- },
- onDataProgress: function PDFDataRangeTransport_onDataProgress(loaded) {
- this._readyCapability.promise.then(function () {
- var listeners = this._progressListeners;
- for (var i = 0, n = listeners.length; i < n; ++i) {
- listeners[i](loaded);
- }
- }.bind(this));
- },
- onDataProgressiveRead: function PDFDataRangeTransport_onDataProgress(chunk) {
- this._readyCapability.promise.then(function () {
- var listeners = this._progressiveReadListeners;
- for (var i = 0, n = listeners.length; i < n; ++i) {
- listeners[i](chunk);
- }
- }.bind(this));
- },
- transportReady: function PDFDataRangeTransport_transportReady() {
- this._readyCapability.resolve();
- },
- requestDataRange: function PDFDataRangeTransport_requestDataRange(begin, end) {
- throw new Error('Abstract method PDFDataRangeTransport.requestDataRange');
- },
- abort: function PDFDataRangeTransport_abort() {}
- };
- return PDFDataRangeTransport;
-}();
-var PDFDocumentProxy = function PDFDocumentProxyClosure() {
- function PDFDocumentProxy(pdfInfo, transport, loadingTask) {
- this.pdfInfo = pdfInfo;
- this.transport = transport;
- this.loadingTask = loadingTask;
- }
- PDFDocumentProxy.prototype = {
- get numPages() {
- return this.pdfInfo.numPages;
- },
- get fingerprint() {
- return this.pdfInfo.fingerprint;
- },
- getPage: function PDFDocumentProxy_getPage(pageNumber) {
- return this.transport.getPage(pageNumber);
- },
- getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
- return this.transport.getPageIndex(ref);
- },
- getDestinations: function PDFDocumentProxy_getDestinations() {
- return this.transport.getDestinations();
- },
- getDestination: function PDFDocumentProxy_getDestination(id) {
- return this.transport.getDestination(id);
- },
- getPageLabels: function PDFDocumentProxy_getPageLabels() {
- return this.transport.getPageLabels();
- },
- getAttachments: function PDFDocumentProxy_getAttachments() {
- return this.transport.getAttachments();
- },
- getJavaScript: function PDFDocumentProxy_getJavaScript() {
- return this.transport.getJavaScript();
- },
- getOutline: function PDFDocumentProxy_getOutline() {
- return this.transport.getOutline();
- },
- getMetadata: function PDFDocumentProxy_getMetadata() {
- return this.transport.getMetadata();
- },
- getData: function PDFDocumentProxy_getData() {
- return this.transport.getData();
- },
- getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
- return this.transport.downloadInfoCapability.promise;
- },
- getStats: function PDFDocumentProxy_getStats() {
- return this.transport.getStats();
- },
- cleanup: function PDFDocumentProxy_cleanup() {
- this.transport.startCleanup();
- },
- destroy: function PDFDocumentProxy_destroy() {
- return this.loadingTask.destroy();
- }
- };
- return PDFDocumentProxy;
-}();
-var PDFPageProxy = function PDFPageProxyClosure() {
- function PDFPageProxy(pageIndex, pageInfo, transport) {
- this.pageIndex = pageIndex;
- this.pageInfo = pageInfo;
- this.transport = transport;
- this.stats = new StatTimer();
- this.stats.enabled = getDefaultSetting('enableStats');
- this.commonObjs = transport.commonObjs;
- this.objs = new PDFObjects();
- this.cleanupAfterRender = false;
- this.pendingCleanup = false;
- this.intentStates = Object.create(null);
- this.destroyed = false;
- }
- PDFPageProxy.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 PDFPageProxy_getViewport(scale, rotate) {
- if (arguments.length < 2) {
- rotate = this.rotate;
- }
- return new PageViewport(this.view, scale, rotate, 0, 0);
- },
- getAnnotations: function PDFPageProxy_getAnnotations(params) {
- var intent = params && params.intent || null;
- if (!this.annotationsPromise || this.annotationsIntent !== intent) {
- this.annotationsPromise = this.transport.getAnnotations(this.pageIndex, intent);
- this.annotationsIntent = intent;
- }
- return this.annotationsPromise;
- },
- render: function PDFPageProxy_render(params) {
- var stats = this.stats;
- stats.time('Overall');
- this.pendingCleanup = false;
- var renderingIntent = params.intent === 'print' ? 'print' : 'display';
- var renderInteractiveForms = params.renderInteractiveForms === true ? true : false;
- var canvasFactory = params.canvasFactory || new DOMCanvasFactory();
- if (!this.intentStates[renderingIntent]) {
- this.intentStates[renderingIntent] = Object.create(null);
- }
- var intentState = this.intentStates[renderingIntent];
- if (!intentState.displayReadyCapability) {
- intentState.receivingOperatorList = true;
- intentState.displayReadyCapability = createPromiseCapability();
- intentState.operatorList = {
- fnArray: [],
- argsArray: [],
- lastChunk: false
- };
- this.stats.time('Page Request');
- this.transport.messageHandler.send('RenderPageRequest', {
- pageIndex: this.pageNumber - 1,
- intent: renderingIntent,
- renderInteractiveForms: renderInteractiveForms
- });
- }
- var internalRenderTask = new InternalRenderTask(complete, params, this.objs, this.commonObjs, intentState.operatorList, this.pageNumber, canvasFactory);
- internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print';
- if (!intentState.renderTasks) {
- intentState.renderTasks = [];
- }
- intentState.renderTasks.push(internalRenderTask);
- var renderTask = internalRenderTask.task;
- if (params.continueCallback) {
- deprecated('render is used with continueCallback parameter');
- renderTask.onContinue = params.continueCallback;
- }
- var self = this;
- intentState.displayReadyCapability.promise.then(function pageDisplayReadyPromise(transparency) {
- if (self.pendingCleanup) {
- complete();
- return;
- }
- stats.time('Rendering');
- internalRenderTask.initializeGraphics(transparency);
- internalRenderTask.operatorListChanged();
- }, function pageDisplayReadPromiseError(reason) {
- complete(reason);
- });
- function complete(error) {
- var i = intentState.renderTasks.indexOf(internalRenderTask);
- if (i >= 0) {
- intentState.renderTasks.splice(i, 1);
- }
- if (self.cleanupAfterRender) {
- self.pendingCleanup = true;
- }
- self._tryCleanup();
- if (error) {
- internalRenderTask.capability.reject(error);
- } else {
- internalRenderTask.capability.resolve();
- }
- stats.timeEnd('Rendering');
- stats.timeEnd('Overall');
- }
- return renderTask;
- },
- getOperatorList: function PDFPageProxy_getOperatorList() {
- function operatorListChanged() {
- if (intentState.operatorList.lastChunk) {
- intentState.opListReadCapability.resolve(intentState.operatorList);
- var i = intentState.renderTasks.indexOf(opListTask);
- if (i >= 0) {
- intentState.renderTasks.splice(i, 1);
- }
- }
- }
- var renderingIntent = 'oplist';
- if (!this.intentStates[renderingIntent]) {
- this.intentStates[renderingIntent] = Object.create(null);
- }
- var intentState = this.intentStates[renderingIntent];
- var opListTask;
- if (!intentState.opListReadCapability) {
- opListTask = {};
- opListTask.operatorListChanged = operatorListChanged;
- intentState.receivingOperatorList = true;
- intentState.opListReadCapability = createPromiseCapability();
- intentState.renderTasks = [];
- intentState.renderTasks.push(opListTask);
- intentState.operatorList = {
- fnArray: [],
- argsArray: [],
- lastChunk: false
- };
- this.transport.messageHandler.send('RenderPageRequest', {
- pageIndex: this.pageIndex,
- intent: renderingIntent
- });
- }
- return intentState.opListReadCapability.promise;
- },
- getTextContent: function PDFPageProxy_getTextContent(params) {
- return this.transport.messageHandler.sendWithPromise('GetTextContent', {
- pageIndex: this.pageNumber - 1,
- normalizeWhitespace: params && params.normalizeWhitespace === true ? true : false,
- combineTextItems: params && params.disableCombineTextItems === true ? false : true
- });
- },
- _destroy: function PDFPageProxy_destroy() {
- this.destroyed = true;
- this.transport.pageCache[this.pageIndex] = null;
- var waitOn = [];
- Object.keys(this.intentStates).forEach(function (intent) {
- if (intent === 'oplist') {
- return;
- }
- var intentState = this.intentStates[intent];
- intentState.renderTasks.forEach(function (renderTask) {
- var renderCompleted = renderTask.capability.promise.catch(function () {});
- waitOn.push(renderCompleted);
- renderTask.cancel();
- });
- }, this);
- this.objs.clear();
- this.annotationsPromise = null;
- this.pendingCleanup = false;
- return Promise.all(waitOn);
- },
- destroy: function () {
- deprecated('page destroy method, use cleanup() instead');
- this.cleanup();
- },
- cleanup: function PDFPageProxy_cleanup() {
- this.pendingCleanup = true;
- this._tryCleanup();
- },
- _tryCleanup: function PDFPageProxy_tryCleanup() {
- if (!this.pendingCleanup || Object.keys(this.intentStates).some(function (intent) {
- var intentState = this.intentStates[intent];
- return intentState.renderTasks.length !== 0 || intentState.receivingOperatorList;
- }, this)) {
- return;
- }
- Object.keys(this.intentStates).forEach(function (intent) {
- delete this.intentStates[intent];
- }, this);
- this.objs.clear();
- this.annotationsPromise = null;
- this.pendingCleanup = false;
- },
- _startRenderPage: function PDFPageProxy_startRenderPage(transparency, intent) {
- var intentState = this.intentStates[intent];
- if (intentState.displayReadyCapability) {
- intentState.displayReadyCapability.resolve(transparency);
- }
- },
- _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, intent) {
- var intentState = this.intentStates[intent];
- var i, ii;
- for (i = 0, ii = operatorListChunk.length; i < ii; i++) {
- intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
- intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
- }
- intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
- for (i = 0; i < intentState.renderTasks.length; i++) {
- intentState.renderTasks[i].operatorListChanged();
- }
- if (operatorListChunk.lastChunk) {
- intentState.receivingOperatorList = false;
- this._tryCleanup();
- }
- }
- };
- return PDFPageProxy;
-}();
-var PDFWorker = function PDFWorkerClosure() {
- var nextFakeWorkerId = 0;
- function getWorkerSrc() {
- if (typeof workerSrc !== 'undefined') {
- return workerSrc;
- }
- if (getDefaultSetting('workerSrc')) {
- return getDefaultSetting('workerSrc');
- }
- if (pdfjsFilePath) {
- return pdfjsFilePath.replace(/\.js$/i, '.worker.js');
- }
- error('No PDFJS.workerSrc specified');
- }
- var fakeWorkerFilesLoadedCapability;
- function setupFakeWorkerGlobal() {
- var WorkerMessageHandler;
- if (fakeWorkerFilesLoadedCapability) {
- return fakeWorkerFilesLoadedCapability.promise;
- }
- fakeWorkerFilesLoadedCapability = createPromiseCapability();
- var loader = fakeWorkerFilesLoader || function (callback) {
- Util.loadScript(getWorkerSrc(), function () {
- callback(window.pdfjsDistBuildPdfWorker.WorkerMessageHandler);
- });
- };
- loader(fakeWorkerFilesLoadedCapability.resolve);
- return fakeWorkerFilesLoadedCapability.promise;
- }
- function FakeWorkerPort(defer) {
- this._listeners = [];
- this._defer = defer;
- this._deferred = Promise.resolve(undefined);
- }
- FakeWorkerPort.prototype = {
- postMessage: function (obj, transfers) {
- function cloneValue(value) {
- if (typeof value !== 'object' || value === null) {
- return value;
- }
- if (cloned.has(value)) {
- return cloned.get(value);
- }
- var result;
- var buffer;
- if ((buffer = value.buffer) && isArrayBuffer(buffer)) {
- var transferable = transfers && transfers.indexOf(buffer) >= 0;
- if (value === buffer) {
- result = value;
- } else if (transferable) {
- result = new value.constructor(buffer, value.byteOffset, value.byteLength);
- } else {
- result = new value.constructor(value);
- }
- cloned.set(value, result);
- return result;
- }
- result = isArray(value) ? [] : {};
- cloned.set(value, result);
- for (var i in value) {
- var desc,
- p = value;
- while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
- p = Object.getPrototypeOf(p);
- }
- if (typeof desc.value === 'undefined' || typeof desc.value === 'function') {
- continue;
- }
- result[i] = cloneValue(desc.value);
- }
- return result;
- }
- if (!this._defer) {
- this._listeners.forEach(function (listener) {
- listener.call(this, { data: obj });
- }, this);
- return;
- }
- var cloned = new WeakMap();
- var e = { data: cloneValue(obj) };
- this._deferred.then(function () {
- this._listeners.forEach(function (listener) {
- listener.call(this, e);
- }, this);
- }.bind(this));
- },
- addEventListener: function (name, listener) {
- this._listeners.push(listener);
- },
- removeEventListener: function (name, listener) {
- var i = this._listeners.indexOf(listener);
- this._listeners.splice(i, 1);
- },
- terminate: function () {
- this._listeners = [];
- }
- };
- function createCDNWrapper(url) {
- var wrapper = 'importScripts(\'' + url + '\');';
- return URL.createObjectURL(new Blob([wrapper]));
- }
- function PDFWorker(name, port) {
- this.name = name;
- this.destroyed = false;
- this._readyCapability = createPromiseCapability();
- this._port = null;
- this._webWorker = null;
- this._messageHandler = null;
- if (port) {
- this._initializeFromPort(port);
- return;
- }
- this._initialize();
- }
- PDFWorker.prototype = {
- get promise() {
- return this._readyCapability.promise;
- },
- get port() {
- return this._port;
- },
- get messageHandler() {
- return this._messageHandler;
- },
- _initializeFromPort: function PDFWorker_initializeFromPort(port) {
- this._port = port;
- this._messageHandler = new MessageHandler('main', 'worker', port);
- this._messageHandler.on('ready', function () {});
- this._readyCapability.resolve();
- },
- _initialize: function PDFWorker_initialize() {
- if (!isWorkerDisabled && !getDefaultSetting('disableWorker') && typeof Worker !== 'undefined') {
- var workerSrc = getWorkerSrc();
- try {
- if (!isSameOrigin(window.location.href, workerSrc)) {
- workerSrc = createCDNWrapper(new URL(workerSrc, window.location).href);
- }
- var worker = new Worker(workerSrc);
- var messageHandler = new MessageHandler('main', 'worker', worker);
- var terminateEarly = function () {
- worker.removeEventListener('error', onWorkerError);
- messageHandler.destroy();
- worker.terminate();
- if (this.destroyed) {
- this._readyCapability.reject(new Error('Worker was destroyed'));
- } else {
- this._setupFakeWorker();
- }
- }.bind(this);
- var onWorkerError = function (event) {
- if (!this._webWorker) {
- terminateEarly();
- }
- }.bind(this);
- worker.addEventListener('error', onWorkerError);
- messageHandler.on('test', function PDFWorker_test(data) {
- worker.removeEventListener('error', onWorkerError);
- if (this.destroyed) {
- terminateEarly();
- return;
- }
- var supportTypedArray = data && data.supportTypedArray;
- if (supportTypedArray) {
- this._messageHandler = messageHandler;
- this._port = worker;
- this._webWorker = worker;
- if (!data.supportTransfers) {
- isPostMessageTransfersDisabled = true;
- }
- this._readyCapability.resolve();
- messageHandler.send('configure', { verbosity: getVerbosityLevel() });
- } else {
- this._setupFakeWorker();
- messageHandler.destroy();
- worker.terminate();
- }
- }.bind(this));
- messageHandler.on('console_log', function (data) {
- console.log.apply(console, data);
- });
- messageHandler.on('console_error', function (data) {
- console.error.apply(console, data);
- });
- messageHandler.on('ready', function (data) {
- worker.removeEventListener('error', onWorkerError);
- if (this.destroyed) {
- terminateEarly();
- return;
- }
- try {
- sendTest();
- } catch (e) {
- this._setupFakeWorker();
- }
- }.bind(this));
- var sendTest = function () {
- var postMessageTransfers = getDefaultSetting('postMessageTransfers') && !isPostMessageTransfersDisabled;
- var testObj = new Uint8Array([postMessageTransfers ? 255 : 0]);
- try {
- messageHandler.send('test', testObj, [testObj.buffer]);
- } catch (ex) {
- info('Cannot use postMessage transfers');
- testObj[0] = 0;
- messageHandler.send('test', testObj);
- }
- };
- sendTest();
- return;
- } catch (e) {
- info('The worker has been disabled.');
- }
- }
- this._setupFakeWorker();
- },
- _setupFakeWorker: function PDFWorker_setupFakeWorker() {
- if (!isWorkerDisabled && !getDefaultSetting('disableWorker')) {
- warn('Setting up fake worker.');
- isWorkerDisabled = true;
- }
- setupFakeWorkerGlobal().then(function (WorkerMessageHandler) {
- if (this.destroyed) {
- this._readyCapability.reject(new Error('Worker was destroyed'));
- return;
- }
- var isTypedArraysPresent = Uint8Array !== Float32Array;
- var port = new FakeWorkerPort(isTypedArraysPresent);
- this._port = port;
- var id = 'fake' + nextFakeWorkerId++;
- var workerHandler = new MessageHandler(id + '_worker', id, port);
- WorkerMessageHandler.setup(workerHandler, port);
- var messageHandler = new MessageHandler(id, id + '_worker', port);
- this._messageHandler = messageHandler;
- this._readyCapability.resolve();
- }.bind(this));
- },
- destroy: function PDFWorker_destroy() {
- this.destroyed = true;
- if (this._webWorker) {
- this._webWorker.terminate();
- this._webWorker = null;
- }
- this._port = null;
- if (this._messageHandler) {
- this._messageHandler.destroy();
- this._messageHandler = null;
- }
- }
- };
- return PDFWorker;
-}();
-var WorkerTransport = function WorkerTransportClosure() {
- function WorkerTransport(messageHandler, loadingTask, pdfDataRangeTransport, CMapReaderFactory) {
- this.messageHandler = messageHandler;
- this.loadingTask = loadingTask;
- this.pdfDataRangeTransport = pdfDataRangeTransport;
- this.commonObjs = new PDFObjects();
- this.fontLoader = new FontLoader(loadingTask.docId);
- this.CMapReaderFactory = new CMapReaderFactory({
- baseUrl: getDefaultSetting('cMapUrl'),
- isCompressed: getDefaultSetting('cMapPacked')
- });
- this.destroyed = false;
- this.destroyCapability = null;
- this._passwordCapability = null;
- this.pageCache = [];
- this.pagePromises = [];
- this.downloadInfoCapability = createPromiseCapability();
- this.setupMessageHandler();
- }
- WorkerTransport.prototype = {
- destroy: function WorkerTransport_destroy() {
- if (this.destroyCapability) {
- return this.destroyCapability.promise;
- }
- this.destroyed = true;
- this.destroyCapability = createPromiseCapability();
- if (this._passwordCapability) {
- this._passwordCapability.reject(new Error('Worker was destroyed during onPassword callback'));
- }
- var waitOn = [];
- this.pageCache.forEach(function (page) {
- if (page) {
- waitOn.push(page._destroy());
- }
- });
- this.pageCache = [];
- this.pagePromises = [];
- var self = this;
- var terminated = this.messageHandler.sendWithPromise('Terminate', null);
- waitOn.push(terminated);
- Promise.all(waitOn).then(function () {
- self.fontLoader.clear();
- if (self.pdfDataRangeTransport) {
- self.pdfDataRangeTransport.abort();
- self.pdfDataRangeTransport = null;
- }
- if (self.messageHandler) {
- self.messageHandler.destroy();
- self.messageHandler = null;
- }
- self.destroyCapability.resolve();
- }, this.destroyCapability.reject);
- return this.destroyCapability.promise;
- },
- setupMessageHandler: function WorkerTransport_setupMessageHandler() {
- var messageHandler = this.messageHandler;
- var loadingTask = this.loadingTask;
- var pdfDataRangeTransport = this.pdfDataRangeTransport;
- if (pdfDataRangeTransport) {
- pdfDataRangeTransport.addRangeListener(function (begin, chunk) {
- messageHandler.send('OnDataRange', {
- begin: begin,
- chunk: chunk
- });
- });
- pdfDataRangeTransport.addProgressListener(function (loaded) {
- messageHandler.send('OnDataProgress', { loaded: loaded });
- });
- pdfDataRangeTransport.addProgressiveReadListener(function (chunk) {
- messageHandler.send('OnDataRange', { chunk: chunk });
- });
- messageHandler.on('RequestDataRange', function transportDataRange(data) {
- pdfDataRangeTransport.requestDataRange(data.begin, data.end);
- }, this);
- }
- messageHandler.on('GetDoc', function transportDoc(data) {
- var pdfInfo = data.pdfInfo;
- this.numPages = data.pdfInfo.numPages;
- var loadingTask = this.loadingTask;
- var pdfDocument = new PDFDocumentProxy(pdfInfo, this, loadingTask);
- this.pdfDocument = pdfDocument;
- loadingTask._capability.resolve(pdfDocument);
- }, this);
- messageHandler.on('PasswordRequest', function transportPasswordRequest(exception) {
- this._passwordCapability = createPromiseCapability();
- if (loadingTask.onPassword) {
- var updatePassword = function (password) {
- this._passwordCapability.resolve({ password: password });
- }.bind(this);
- loadingTask.onPassword(updatePassword, exception.code);
- } else {
- this._passwordCapability.reject(new PasswordException(exception.message, exception.code));
- }
- return this._passwordCapability.promise;
- }, this);
- messageHandler.on('PasswordException', function transportPasswordException(exception) {
- loadingTask._capability.reject(new PasswordException(exception.message, exception.code));
- }, this);
- messageHandler.on('InvalidPDF', function transportInvalidPDF(exception) {
- this.loadingTask._capability.reject(new InvalidPDFException(exception.message));
- }, this);
- messageHandler.on('MissingPDF', function transportMissingPDF(exception) {
- this.loadingTask._capability.reject(new MissingPDFException(exception.message));
- }, this);
- messageHandler.on('UnexpectedResponse', function transportUnexpectedResponse(exception) {
- this.loadingTask._capability.reject(new UnexpectedResponseException(exception.message, exception.status));
- }, this);
- messageHandler.on('UnknownError', function transportUnknownError(exception) {
- this.loadingTask._capability.reject(new UnknownErrorException(exception.message, exception.details));
- }, this);
- messageHandler.on('DataLoaded', function transportPage(data) {
- this.downloadInfoCapability.resolve(data);
- }, this);
- messageHandler.on('PDFManagerReady', function transportPage(data) {
- if (this.pdfDataRangeTransport) {
- this.pdfDataRangeTransport.transportReady();
- }
- }, this);
- messageHandler.on('StartRenderPage', function transportRender(data) {
- if (this.destroyed) {
- return;
- }
- var page = this.pageCache[data.pageIndex];
- page.stats.timeEnd('Page Request');
- page._startRenderPage(data.transparency, data.intent);
- }, this);
- messageHandler.on('RenderPageChunk', function transportRender(data) {
- if (this.destroyed) {
- return;
- }
- var page = this.pageCache[data.pageIndex];
- page._renderPageChunk(data.operatorList, data.intent);
- }, this);
- messageHandler.on('commonobj', function transportObj(data) {
- if (this.destroyed) {
- return;
- }
- var id = data[0];
- var type = data[1];
- if (this.commonObjs.hasData(id)) {
- return;
- }
- switch (type) {
- case 'Font':
- var exportedData = data[2];
- if ('error' in exportedData) {
- var exportedError = exportedData.error;
- warn('Error during font loading: ' + exportedError);
- this.commonObjs.resolve(id, exportedError);
- break;
- }
- var fontRegistry = null;
- if (getDefaultSetting('pdfBug') && globalScope.FontInspector && globalScope['FontInspector'].enabled) {
- fontRegistry = {
- registerFont: function (font, url) {
- globalScope['FontInspector'].fontAdded(font, url);
- }
- };
- }
- var font = new FontFaceObject(exportedData, {
- isEvalSuported: getDefaultSetting('isEvalSupported'),
- disableFontFace: getDefaultSetting('disableFontFace'),
- fontRegistry: fontRegistry
- });
- this.fontLoader.bind([font], function fontReady(fontObjs) {
- this.commonObjs.resolve(id, font);
- }.bind(this));
- break;
- case 'FontPath':
- this.commonObjs.resolve(id, data[2]);
- break;
- default:
- error('Got unknown common object type ' + type);
- }
- }, this);
- messageHandler.on('obj', function transportObj(data) {
- if (this.destroyed) {
- return;
- }
- var id = data[0];
- var pageIndex = data[1];
- var type = data[2];
- var pageProxy = this.pageCache[pageIndex];
- var imageData;
- if (pageProxy.objs.hasData(id)) {
- return;
- }
- switch (type) {
- case 'JpegStream':
- imageData = data[3];
- loadJpegStream(id, imageData, pageProxy.objs);
- break;
- case 'Image':
- imageData = data[3];
- pageProxy.objs.resolve(id, imageData);
- var MAX_IMAGE_SIZE_TO_STORE = 8000000;
- if (imageData && 'data' in imageData && imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
- pageProxy.cleanupAfterRender = true;
- }
- break;
- default:
- error('Got unknown object type ' + type);
- }
- }, this);
- messageHandler.on('DocProgress', function transportDocProgress(data) {
- if (this.destroyed) {
- return;
- }
- var loadingTask = this.loadingTask;
- if (loadingTask.onProgress) {
- loadingTask.onProgress({
- loaded: data.loaded,
- total: data.total
- });
- }
- }, this);
- messageHandler.on('PageError', function transportError(data) {
- if (this.destroyed) {
- return;
- }
- var page = this.pageCache[data.pageNum - 1];
- var intentState = page.intentStates[data.intent];
- if (intentState.displayReadyCapability) {
- intentState.displayReadyCapability.reject(data.error);
- } else {
- error(data.error);
- }
- if (intentState.operatorList) {
- intentState.operatorList.lastChunk = true;
- for (var i = 0; i < intentState.renderTasks.length; i++) {
- intentState.renderTasks[i].operatorListChanged();
- }
- }
- }, this);
- messageHandler.on('UnsupportedFeature', function transportUnsupportedFeature(data) {
- if (this.destroyed) {
- return;
- }
- var featureId = data.featureId;
- var loadingTask = this.loadingTask;
- if (loadingTask.onUnsupportedFeature) {
- loadingTask.onUnsupportedFeature(featureId);
- }
- _UnsupportedManager.notify(featureId);
- }, this);
- messageHandler.on('JpegDecode', function (data) {
- if (this.destroyed) {
- return Promise.reject(new Error('Worker was destroyed'));
- }
- if (typeof document === 'undefined') {
- return Promise.reject(new Error('"document" is not defined.'));
- }
- var imageUrl = data[0];
- var components = data[1];
- if (components !== 3 && components !== 1) {
- return Promise.reject(new Error('Only 3 components or 1 component can be returned'));
- }
- return new Promise(function (resolve, reject) {
- var img = new Image();
- img.onload = function () {
- var width = img.width;
- var height = img.height;
- var size = width * height;
- var rgbaLength = size * 4;
- var buf = new Uint8Array(size * components);
- var tmpCanvas = document.createElement('canvas');
- tmpCanvas.width = width;
- tmpCanvas.height = height;
- var tmpCtx = tmpCanvas.getContext('2d');
- tmpCtx.drawImage(img, 0, 0);
- var data = tmpCtx.getImageData(0, 0, width, height).data;
- var i, j;
- if (components === 3) {
- for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
- buf[j] = data[i];
- buf[j + 1] = data[i + 1];
- buf[j + 2] = data[i + 2];
- }
- } else if (components === 1) {
- for (i = 0, j = 0; i < rgbaLength; i += 4, j++) {
- buf[j] = data[i];
- }
- }
- resolve({
- data: buf,
- width: width,
- height: height
- });
- };
- img.onerror = function () {
- reject(new Error('JpegDecode failed to load image'));
- };
- img.src = imageUrl;
- });
- }, this);
- messageHandler.on('FetchBuiltInCMap', function (data) {
- if (this.destroyed) {
- return Promise.reject(new Error('Worker was destroyed'));
- }
- return this.CMapReaderFactory.fetch({ name: data.name });
- }, this);
- },
- getData: function WorkerTransport_getData() {
- return this.messageHandler.sendWithPromise('GetData', null);
- },
- getPage: function WorkerTransport_getPage(pageNumber, capability) {
- if (!isInt(pageNumber) || pageNumber <= 0 || pageNumber > this.numPages) {
- return Promise.reject(new Error('Invalid page request'));
- }
- var pageIndex = pageNumber - 1;
- if (pageIndex in this.pagePromises) {
- return this.pagePromises[pageIndex];
- }
- var promise = this.messageHandler.sendWithPromise('GetPage', { pageIndex: pageIndex }).then(function (pageInfo) {
- if (this.destroyed) {
- throw new Error('Transport destroyed');
- }
- var page = new PDFPageProxy(pageIndex, pageInfo, this);
- this.pageCache[pageIndex] = page;
- return page;
- }.bind(this));
- this.pagePromises[pageIndex] = promise;
- return promise;
- },
- getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
- return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref }).catch(function (reason) {
- return Promise.reject(new Error(reason));
- });
- },
- getAnnotations: function WorkerTransport_getAnnotations(pageIndex, intent) {
- return this.messageHandler.sendWithPromise('GetAnnotations', {
- pageIndex: pageIndex,
- intent: intent
- });
- },
- getDestinations: function WorkerTransport_getDestinations() {
- return this.messageHandler.sendWithPromise('GetDestinations', null);
- },
- getDestination: function WorkerTransport_getDestination(id) {
- return this.messageHandler.sendWithPromise('GetDestination', { id: id });
- },
- getPageLabels: function WorkerTransport_getPageLabels() {
- return this.messageHandler.sendWithPromise('GetPageLabels', null);
- },
- getAttachments: function WorkerTransport_getAttachments() {
- return this.messageHandler.sendWithPromise('GetAttachments', null);
- },
- getJavaScript: function WorkerTransport_getJavaScript() {
- return this.messageHandler.sendWithPromise('GetJavaScript', null);
- },
- getOutline: function WorkerTransport_getOutline() {
- return this.messageHandler.sendWithPromise('GetOutline', null);
- },
- getMetadata: function WorkerTransport_getMetadata() {
- return this.messageHandler.sendWithPromise('GetMetadata', null).then(function transportMetadata(results) {
- return {
- info: results[0],
- metadata: results[1] ? new Metadata(results[1]) : null
- };
- });
- },
- getStats: function WorkerTransport_getStats() {
- return this.messageHandler.sendWithPromise('GetStats', null);
- },
- startCleanup: function WorkerTransport_startCleanup() {
- this.messageHandler.sendWithPromise('Cleanup', null).then(function endCleanup() {
- for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
- var page = this.pageCache[i];
- if (page) {
- page.cleanup();
- }
- }
- this.commonObjs.clear();
- this.fontLoader.clear();
- }.bind(this));
- }
- };
- return WorkerTransport;
-}();
-var PDFObjects = function PDFObjectsClosure() {
- function PDFObjects() {
- this.objs = Object.create(null);
- }
- PDFObjects.prototype = {
- ensureObj: function PDFObjects_ensureObj(objId) {
- if (this.objs[objId]) {
- return this.objs[objId];
- }
- var obj = {
- capability: createPromiseCapability(),
- data: null,
- resolved: false
- };
- this.objs[objId] = obj;
- return obj;
- },
- get: function PDFObjects_get(objId, callback) {
- if (callback) {
- this.ensureObj(objId).capability.promise.then(callback);
- return null;
- }
- var obj = this.objs[objId];
- if (!obj || !obj.resolved) {
- error('Requesting object that isn\'t resolved yet ' + objId);
- }
- return obj.data;
- },
- resolve: function PDFObjects_resolve(objId, data) {
- var obj = this.ensureObj(objId);
- obj.resolved = true;
- obj.data = data;
- obj.capability.resolve(data);
- },
- isResolved: function PDFObjects_isResolved(objId) {
- var objs = this.objs;
- if (!objs[objId]) {
- return false;
- }
- return objs[objId].resolved;
- },
- hasData: function PDFObjects_hasData(objId) {
- return this.isResolved(objId);
- },
- getData: function PDFObjects_getData(objId) {
- var objs = this.objs;
- if (!objs[objId] || !objs[objId].resolved) {
- return null;
- }
- return objs[objId].data;
- },
- clear: function PDFObjects_clear() {
- this.objs = Object.create(null);
- }
- };
- return PDFObjects;
-}();
-var RenderTask = function RenderTaskClosure() {
- function RenderTask(internalRenderTask) {
- this._internalRenderTask = internalRenderTask;
- this.onContinue = null;
- }
- RenderTask.prototype = {
- get promise() {
- return this._internalRenderTask.capability.promise;
- },
- cancel: function RenderTask_cancel() {
- this._internalRenderTask.cancel();
- },
- then: function RenderTask_then(onFulfilled, onRejected) {
- return this.promise.then.apply(this.promise, arguments);
- }
- };
- return RenderTask;
-}();
-var InternalRenderTask = function InternalRenderTaskClosure() {
- function InternalRenderTask(callback, params, objs, commonObjs, operatorList, pageNumber, canvasFactory) {
- this.callback = callback;
- this.params = params;
- this.objs = objs;
- this.commonObjs = commonObjs;
- this.operatorListIdx = null;
- this.operatorList = operatorList;
- this.pageNumber = pageNumber;
- this.canvasFactory = canvasFactory;
- this.running = false;
- this.graphicsReadyCallback = null;
- this.graphicsReady = false;
- this.useRequestAnimationFrame = false;
- this.cancelled = false;
- this.capability = createPromiseCapability();
- this.task = new RenderTask(this);
- this._continueBound = this._continue.bind(this);
- this._scheduleNextBound = this._scheduleNext.bind(this);
- this._nextBound = this._next.bind(this);
- }
- InternalRenderTask.prototype = {
- initializeGraphics: function InternalRenderTask_initializeGraphics(transparency) {
- if (this.cancelled) {
- return;
- }
- if (getDefaultSetting('pdfBug') && globalScope.StepperManager && globalScope.StepperManager.enabled) {
- this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
- this.stepper.init(this.operatorList);
- this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
- }
- var params = this.params;
- this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, this.objs, this.canvasFactory, params.imageLayer);
- this.gfx.beginDrawing(params.transform, params.viewport, transparency);
- this.operatorListIdx = 0;
- this.graphicsReady = true;
- if (this.graphicsReadyCallback) {
- this.graphicsReadyCallback();
- }
- },
- cancel: function InternalRenderTask_cancel() {
- this.running = false;
- this.cancelled = true;
- if (getDefaultSetting('pdfjsNext')) {
- this.callback(new RenderingCancelledException('Rendering cancelled, page ' + this.pageNumber, 'canvas'));
- } else {
- this.callback('cancelled');
- }
- },
- operatorListChanged: function InternalRenderTask_operatorListChanged() {
- if (!this.graphicsReady) {
- if (!this.graphicsReadyCallback) {
- this.graphicsReadyCallback = this._continueBound;
- }
- return;
- }
- if (this.stepper) {
- this.stepper.updateOperatorList(this.operatorList);
- }
- if (this.running) {
- return;
- }
- this._continue();
- },
- _continue: function InternalRenderTask__continue() {
- this.running = true;
- if (this.cancelled) {
- return;
- }
- if (this.task.onContinue) {
- this.task.onContinue(this._scheduleNextBound);
- } else {
- this._scheduleNext();
- }
- },
- _scheduleNext: function InternalRenderTask__scheduleNext() {
- if (this.useRequestAnimationFrame && typeof window !== 'undefined') {
- window.requestAnimationFrame(this._nextBound);
- } else {
- Promise.resolve(undefined).then(this._nextBound);
- }
- },
- _next: function InternalRenderTask__next() {
- if (this.cancelled) {
- return;
- }
- this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper);
- if (this.operatorListIdx === this.operatorList.argsArray.length) {
- this.running = false;
- if (this.operatorList.lastChunk) {
- this.gfx.endDrawing();
- this.callback();
- }
- }
- }
- };
- return InternalRenderTask;
-}();
-var _UnsupportedManager = function UnsupportedManagerClosure() {
- var listeners = [];
- return {
- listen: function (cb) {
- deprecated('Global UnsupportedManager.listen is used: ' + ' use PDFDocumentLoadingTask.onUnsupportedFeature instead');
- listeners.push(cb);
- },
- notify: function (featureId) {
- for (var i = 0, ii = listeners.length; i < ii; i++) {
- listeners[i](featureId);
- }
- }
- };
-}();
-exports.version = '1.8.172';
-exports.build = '8ff1fbe7';
-exports.getDocument = getDocument;
-exports.PDFDataRangeTransport = PDFDataRangeTransport;
-exports.PDFWorker = PDFWorker;
-exports.PDFDocumentProxy = PDFDocumentProxy;
-exports.PDFPageProxy = PDFPageProxy;
-exports._UnsupportedManager = _UnsupportedManager;
-
-/***/ }),
-/* 4 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
-var ImageKind = sharedUtil.ImageKind;
-var OPS = sharedUtil.OPS;
-var Util = sharedUtil.Util;
-var isNum = sharedUtil.isNum;
-var isArray = sharedUtil.isArray;
-var warn = sharedUtil.warn;
-var createObjectURL = sharedUtil.createObjectURL;
-var SVG_DEFAULTS = {
- fontStyle: 'normal',
- fontWeight: 'normal',
- fillColor: '#000000'
-};
-var convertImgDataToPng = function convertImgDataToPngClosure() {
- var PNG_HEADER = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
- var CHUNK_WRAPPER_SIZE = 12;
- var crcTable = new Int32Array(256);
- for (var i = 0; i < 256; i++) {
- var c = i;
- for (var h = 0; h < 8; h++) {
- if (c & 1) {
- c = 0xedB88320 ^ c >> 1 & 0x7fffffff;
- } else {
- c = c >> 1 & 0x7fffffff;
- }
- }
- crcTable[i] = c;
- }
- function crc32(data, start, end) {
- var crc = -1;
- for (var i = start; i < end; i++) {
- var a = (crc ^ data[i]) & 0xff;
- var b = crcTable[a];
- crc = crc >>> 8 ^ b;
- }
- return crc ^ -1;
- }
- function writePngChunk(type, body, data, offset) {
- var p = offset;
- var len = body.length;
- data[p] = len >> 24 & 0xff;
- data[p + 1] = len >> 16 & 0xff;
- data[p + 2] = len >> 8 & 0xff;
- data[p + 3] = len & 0xff;
- p += 4;
- data[p] = type.charCodeAt(0) & 0xff;
- data[p + 1] = type.charCodeAt(1) & 0xff;
- data[p + 2] = type.charCodeAt(2) & 0xff;
- data[p + 3] = type.charCodeAt(3) & 0xff;
- p += 4;
- data.set(body, p);
- p += body.length;
- var crc = crc32(data, offset + 4, p);
- data[p] = crc >> 24 & 0xff;
- data[p + 1] = crc >> 16 & 0xff;
- data[p + 2] = crc >> 8 & 0xff;
- data[p + 3] = crc & 0xff;
- }
- function adler32(data, start, end) {
- var a = 1;
- var b = 0;
- for (var i = start; i < end; ++i) {
- a = (a + (data[i] & 0xff)) % 65521;
- b = (b + a) % 65521;
- }
- return b << 16 | a;
- }
- function encode(imgData, kind, forceDataSchema) {
- var width = imgData.width;
- var height = imgData.height;
- var bitDepth, colorType, lineSize;
- var bytes = imgData.data;
- switch (kind) {
- case ImageKind.GRAYSCALE_1BPP:
- colorType = 0;
- bitDepth = 1;
- lineSize = width + 7 >> 3;
- break;
- case ImageKind.RGB_24BPP:
- colorType = 2;
- bitDepth = 8;
- lineSize = width * 3;
- break;
- case ImageKind.RGBA_32BPP:
- colorType = 6;
- bitDepth = 8;
- lineSize = width * 4;
- break;
- default:
- throw new Error('invalid format');
- }
- var literals = new Uint8Array((1 + lineSize) * height);
- var offsetLiterals = 0,
- offsetBytes = 0;
- var y, i;
- for (y = 0; y < height; ++y) {
- literals[offsetLiterals++] = 0;
- literals.set(bytes.subarray(offsetBytes, offsetBytes + lineSize), offsetLiterals);
- offsetBytes += lineSize;
- offsetLiterals += lineSize;
- }
- if (kind === ImageKind.GRAYSCALE_1BPP) {
- offsetLiterals = 0;
- for (y = 0; y < height; y++) {
- offsetLiterals++;
- for (i = 0; i < lineSize; i++) {
- literals[offsetLiterals++] ^= 0xFF;
- }
- }
- }
- var ihdr = new Uint8Array([width >> 24 & 0xff, width >> 16 & 0xff, width >> 8 & 0xff, width & 0xff, height >> 24 & 0xff, height >> 16 & 0xff, height >> 8 & 0xff, height & 0xff, bitDepth, colorType, 0x00, 0x00, 0x00]);
- var len = literals.length;
- var maxBlockLength = 0xFFFF;
- var deflateBlocks = Math.ceil(len / maxBlockLength);
- var idat = new Uint8Array(2 + len + deflateBlocks * 5 + 4);
- var pi = 0;
- idat[pi++] = 0x78;
- idat[pi++] = 0x9c;
- var pos = 0;
- while (len > maxBlockLength) {
- idat[pi++] = 0x00;
- idat[pi++] = 0xff;
- idat[pi++] = 0xff;
- idat[pi++] = 0x00;
- idat[pi++] = 0x00;
- idat.set(literals.subarray(pos, pos + maxBlockLength), pi);
- pi += maxBlockLength;
- pos += maxBlockLength;
- len -= maxBlockLength;
- }
- idat[pi++] = 0x01;
- idat[pi++] = len & 0xff;
- idat[pi++] = len >> 8 & 0xff;
- idat[pi++] = ~len & 0xffff & 0xff;
- idat[pi++] = (~len & 0xffff) >> 8 & 0xff;
- idat.set(literals.subarray(pos), pi);
- pi += literals.length - pos;
- var adler = adler32(literals, 0, literals.length);
- idat[pi++] = adler >> 24 & 0xff;
- idat[pi++] = adler >> 16 & 0xff;
- idat[pi++] = adler >> 8 & 0xff;
- idat[pi++] = adler & 0xff;
- var pngLength = PNG_HEADER.length + CHUNK_WRAPPER_SIZE * 3 + ihdr.length + idat.length;
- var data = new Uint8Array(pngLength);
- var offset = 0;
- data.set(PNG_HEADER, offset);
- offset += PNG_HEADER.length;
- writePngChunk('IHDR', ihdr, data, offset);
- offset += CHUNK_WRAPPER_SIZE + ihdr.length;
- writePngChunk('IDATA', idat, data, offset);
- offset += CHUNK_WRAPPER_SIZE + idat.length;
- writePngChunk('IEND', new Uint8Array(0), data, offset);
- return createObjectURL(data, 'image/png', forceDataSchema);
- }
- return function convertImgDataToPng(imgData, forceDataSchema) {
- var kind = imgData.kind === undefined ? ImageKind.GRAYSCALE_1BPP : imgData.kind;
- return encode(imgData, kind, forceDataSchema);
- };
-}();
-var SVGExtraState = function SVGExtraStateClosure() {
- function SVGExtraState() {
- this.fontSizeScale = 1;
- this.fontWeight = SVG_DEFAULTS.fontWeight;
- this.fontSize = 0;
- this.textMatrix = IDENTITY_MATRIX;
- this.fontMatrix = FONT_IDENTITY_MATRIX;
- 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 = SVG_DEFAULTS.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 = '';
- }
- SVGExtraState.prototype = {
- clone: function SVGExtraState_clone() {
- return Object.create(this);
- },
- setCurrentPoint: function SVGExtraState_setCurrentPoint(x, y) {
- this.x = x;
- this.y = y;
- }
- };
- return SVGExtraState;
-}();
-var SVGGraphics = function SVGGraphicsClosure() {
- function opListToTree(opList) {
- var opTree = [];
- var tmp = [];
- var opListLen = opList.length;
- for (var x = 0; x < opListLen; x++) {
- if (opList[x].fn === 'save') {
- opTree.push({
- 'fnId': 92,
- 'fn': 'group',
- 'items': []
- });
- tmp.push(opTree);
- opTree = opTree[opTree.length - 1].items;
- continue;
- }
- if (opList[x].fn === 'restore') {
- opTree = tmp.pop();
- } else {
- opTree.push(opList[x]);
- }
- }
- return opTree;
- }
- function pf(value) {
- if (value === (value | 0)) {
- return value.toString();
- }
- var s = value.toFixed(10);
- var i = s.length - 1;
- if (s[i] !== '0') {
- return s;
- }
- do {
- i--;
- } while (s[i] === '0');
- return s.substr(0, s[i] === '.' ? i : i + 1);
- }
- function pm(m) {
- if (m[4] === 0 && m[5] === 0) {
- if (m[1] === 0 && m[2] === 0) {
- if (m[0] === 1 && m[3] === 1) {
- return '';
- }
- return 'scale(' + pf(m[0]) + ' ' + pf(m[3]) + ')';
- }
- if (m[0] === m[3] && m[1] === -m[2]) {
- var a = Math.acos(m[0]) * 180 / Math.PI;
- return 'rotate(' + pf(a) + ')';
- }
- } else {
- if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1) {
- return 'translate(' + pf(m[4]) + ' ' + pf(m[5]) + ')';
- }
- }
- return 'matrix(' + pf(m[0]) + ' ' + pf(m[1]) + ' ' + pf(m[2]) + ' ' + pf(m[3]) + ' ' + pf(m[4]) + ' ' + pf(m[5]) + ')';
- }
- function SVGGraphics(commonObjs, objs, forceDataSchema) {
- this.current = new SVGExtraState();
- this.transformMatrix = IDENTITY_MATRIX;
- this.transformStack = [];
- this.extraStack = [];
- this.commonObjs = commonObjs;
- this.objs = objs;
- this.pendingEOFill = false;
- this.embedFonts = false;
- this.embeddedFonts = Object.create(null);
- this.cssStyle = null;
- this.forceDataSchema = !!forceDataSchema;
- }
- var NS = 'http://www.w3.org/2000/svg';
- var XML_NS = 'http://www.w3.org/XML/1998/namespace';
- var XLINK_NS = 'http://www.w3.org/1999/xlink';
- var LINE_CAP_STYLES = ['butt', 'round', 'square'];
- var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
- var clipCount = 0;
- var maskCount = 0;
- SVGGraphics.prototype = {
- save: function SVGGraphics_save() {
- this.transformStack.push(this.transformMatrix);
- var old = this.current;
- this.extraStack.push(old);
- this.current = old.clone();
- },
- restore: function SVGGraphics_restore() {
- this.transformMatrix = this.transformStack.pop();
- this.current = this.extraStack.pop();
- this.tgrp = null;
- },
- group: function SVGGraphics_group(items) {
- this.save();
- this.executeOpTree(items);
- this.restore();
- },
- loadDependencies: function SVGGraphics_loadDependencies(operatorList) {
- var fnArray = operatorList.fnArray;
- var fnArrayLen = fnArray.length;
- var argsArray = operatorList.argsArray;
- var self = this;
- for (var i = 0; i < fnArrayLen; i++) {
- if (OPS.dependency === fnArray[i]) {
- var deps = argsArray[i];
- for (var n = 0, nn = deps.length; n < nn; n++) {
- var obj = deps[n];
- var common = obj.substring(0, 2) === 'g_';
- var promise;
- if (common) {
- promise = new Promise(function (resolve) {
- self.commonObjs.get(obj, resolve);
- });
- } else {
- promise = new Promise(function (resolve) {
- self.objs.get(obj, resolve);
- });
- }
- this.current.dependencies.push(promise);
- }
- }
- }
- return Promise.all(this.current.dependencies);
- },
- transform: function SVGGraphics_transform(a, b, c, d, e, f) {
- var transformMatrix = [a, b, c, d, e, f];
- this.transformMatrix = Util.transform(this.transformMatrix, transformMatrix);
- this.tgrp = null;
- },
- getSVG: function SVGGraphics_getSVG(operatorList, viewport) {
- this.viewport = viewport;
- var svgElement = this._initialize(viewport);
- return this.loadDependencies(operatorList).then(function () {
- this.transformMatrix = IDENTITY_MATRIX;
- var opTree = this.convertOpList(operatorList);
- this.executeOpTree(opTree);
- return svgElement;
- }.bind(this));
- },
- convertOpList: function SVGGraphics_convertOpList(operatorList) {
- var argsArray = operatorList.argsArray;
- var fnArray = operatorList.fnArray;
- var fnArrayLen = fnArray.length;
- var REVOPS = [];
- var opList = [];
- for (var op in OPS) {
- REVOPS[OPS[op]] = op;
- }
- for (var x = 0; x < fnArrayLen; x++) {
- var fnId = fnArray[x];
- opList.push({
- 'fnId': fnId,
- 'fn': REVOPS[fnId],
- 'args': argsArray[x]
- });
- }
- return opListToTree(opList);
- },
- executeOpTree: function SVGGraphics_executeOpTree(opTree) {
- var opTreeLen = opTree.length;
- for (var x = 0; x < opTreeLen; x++) {
- var fn = opTree[x].fn;
- var fnId = opTree[x].fnId;
- var args = opTree[x].args;
- switch (fnId | 0) {
- case OPS.beginText:
- this.beginText();
- break;
- case OPS.setLeading:
- this.setLeading(args);
- break;
- case OPS.setLeadingMoveText:
- this.setLeadingMoveText(args[0], args[1]);
- break;
- case OPS.setFont:
- this.setFont(args);
- break;
- case OPS.showText:
- this.showText(args[0]);
- break;
- case OPS.showSpacedText:
- this.showText(args[0]);
- break;
- case OPS.endText:
- this.endText();
- break;
- case OPS.moveText:
- this.moveText(args[0], args[1]);
- break;
- case OPS.setCharSpacing:
- this.setCharSpacing(args[0]);
- break;
- case OPS.setWordSpacing:
- this.setWordSpacing(args[0]);
- break;
- case OPS.setHScale:
- this.setHScale(args[0]);
- break;
- case OPS.setTextMatrix:
- this.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
- break;
- case OPS.setLineWidth:
- this.setLineWidth(args[0]);
- break;
- case OPS.setLineJoin:
- this.setLineJoin(args[0]);
- break;
- case OPS.setLineCap:
- this.setLineCap(args[0]);
- break;
- case OPS.setMiterLimit:
- this.setMiterLimit(args[0]);
- break;
- case OPS.setFillRGBColor:
- this.setFillRGBColor(args[0], args[1], args[2]);
- break;
- case OPS.setStrokeRGBColor:
- this.setStrokeRGBColor(args[0], args[1], args[2]);
- break;
- case OPS.setDash:
- this.setDash(args[0], args[1]);
- break;
- case OPS.setGState:
- this.setGState(args[0]);
- break;
- case OPS.fill:
- this.fill();
- break;
- case OPS.eoFill:
- this.eoFill();
- break;
- case OPS.stroke:
- this.stroke();
- break;
- case OPS.fillStroke:
- this.fillStroke();
- break;
- case OPS.eoFillStroke:
- this.eoFillStroke();
- break;
- case OPS.clip:
- this.clip('nonzero');
- break;
- case OPS.eoClip:
- this.clip('evenodd');
- break;
- case OPS.paintSolidColorImageMask:
- this.paintSolidColorImageMask();
- break;
- case OPS.paintJpegXObject:
- this.paintJpegXObject(args[0], args[1], args[2]);
- break;
- case OPS.paintImageXObject:
- this.paintImageXObject(args[0]);
- break;
- case OPS.paintInlineImageXObject:
- this.paintInlineImageXObject(args[0]);
- break;
- case OPS.paintImageMaskXObject:
- this.paintImageMaskXObject(args[0]);
- break;
- case OPS.paintFormXObjectBegin:
- this.paintFormXObjectBegin(args[0], args[1]);
- break;
- case OPS.paintFormXObjectEnd:
- this.paintFormXObjectEnd();
- break;
- case OPS.closePath:
- this.closePath();
- break;
- case OPS.closeStroke:
- this.closeStroke();
- break;
- case OPS.closeFillStroke:
- this.closeFillStroke();
- break;
- case OPS.nextLine:
- this.nextLine();
- break;
- case OPS.transform:
- this.transform(args[0], args[1], args[2], args[3], args[4], args[5]);
- break;
- case OPS.constructPath:
- this.constructPath(args[0], args[1]);
- break;
- case OPS.endPath:
- this.endPath();
- break;
- case 92:
- this.group(opTree[x].items);
- break;
- default:
- warn('Unimplemented operator ' + fn);
- break;
- }
- }
- },
- setWordSpacing: function SVGGraphics_setWordSpacing(wordSpacing) {
- this.current.wordSpacing = wordSpacing;
- },
- setCharSpacing: function SVGGraphics_setCharSpacing(charSpacing) {
- this.current.charSpacing = charSpacing;
- },
- nextLine: function SVGGraphics_nextLine() {
- this.moveText(0, this.current.leading);
- },
- setTextMatrix: function SVGGraphics_setTextMatrix(a, b, c, d, e, f) {
- var current = this.current;
- this.current.textMatrix = this.current.lineMatrix = [a, b, c, d, e, f];
- this.current.x = this.current.lineX = 0;
- this.current.y = this.current.lineY = 0;
- current.xcoords = [];
- current.tspan = document.createElementNS(NS, 'svg:tspan');
- current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
- current.tspan.setAttributeNS(null, 'font-size', pf(current.fontSize) + 'px');
- current.tspan.setAttributeNS(null, 'y', pf(-current.y));
- current.txtElement = document.createElementNS(NS, 'svg:text');
- current.txtElement.appendChild(current.tspan);
- },
- beginText: function SVGGraphics_beginText() {
- this.current.x = this.current.lineX = 0;
- this.current.y = this.current.lineY = 0;
- this.current.textMatrix = IDENTITY_MATRIX;
- this.current.lineMatrix = IDENTITY_MATRIX;
- this.current.tspan = document.createElementNS(NS, 'svg:tspan');
- this.current.txtElement = document.createElementNS(NS, 'svg:text');
- this.current.txtgrp = document.createElementNS(NS, 'svg:g');
- this.current.xcoords = [];
- },
- moveText: function SVGGraphics_moveText(x, y) {
- var current = this.current;
- this.current.x = this.current.lineX += x;
- this.current.y = this.current.lineY += y;
- current.xcoords = [];
- current.tspan = document.createElementNS(NS, 'svg:tspan');
- current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
- current.tspan.setAttributeNS(null, 'font-size', pf(current.fontSize) + 'px');
- current.tspan.setAttributeNS(null, 'y', pf(-current.y));
- },
- showText: function SVGGraphics_showText(glyphs) {
- var current = this.current;
- var font = current.font;
- var fontSize = current.fontSize;
- if (fontSize === 0) {
- return;
- }
- var charSpacing = current.charSpacing;
- var wordSpacing = current.wordSpacing;
- var fontDirection = current.fontDirection;
- var textHScale = current.textHScale * fontDirection;
- var glyphsLength = glyphs.length;
- var vertical = font.vertical;
- var widthAdvanceScale = fontSize * current.fontMatrix[0];
- var x = 0,
- i;
- for (i = 0; i < glyphsLength; ++i) {
- var glyph = glyphs[i];
- if (glyph === null) {
- x += fontDirection * wordSpacing;
- continue;
- } else if (isNum(glyph)) {
- x += -glyph * fontSize * 0.001;
- continue;
- }
- current.xcoords.push(current.x + x * textHScale);
- var width = glyph.width;
- var character = glyph.fontChar;
- var charWidth = width * widthAdvanceScale + charSpacing * fontDirection;
- x += charWidth;
- current.tspan.textContent += character;
- }
- if (vertical) {
- current.y -= x * textHScale;
- } else {
- current.x += x * textHScale;
- }
- current.tspan.setAttributeNS(null, 'x', current.xcoords.map(pf).join(' '));
- current.tspan.setAttributeNS(null, 'y', pf(-current.y));
- current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
- current.tspan.setAttributeNS(null, 'font-size', pf(current.fontSize) + 'px');
- if (current.fontStyle !== SVG_DEFAULTS.fontStyle) {
- current.tspan.setAttributeNS(null, 'font-style', current.fontStyle);
- }
- if (current.fontWeight !== SVG_DEFAULTS.fontWeight) {
- current.tspan.setAttributeNS(null, 'font-weight', current.fontWeight);
- }
- if (current.fillColor !== SVG_DEFAULTS.fillColor) {
- current.tspan.setAttributeNS(null, 'fill', current.fillColor);
- }
- current.txtElement.setAttributeNS(null, 'transform', pm(current.textMatrix) + ' scale(1, -1)');
- current.txtElement.setAttributeNS(XML_NS, 'xml:space', 'preserve');
- current.txtElement.appendChild(current.tspan);
- current.txtgrp.appendChild(current.txtElement);
- this._ensureTransformGroup().appendChild(current.txtElement);
- },
- setLeadingMoveText: function SVGGraphics_setLeadingMoveText(x, y) {
- this.setLeading(-y);
- this.moveText(x, y);
- },
- addFontStyle: function SVGGraphics_addFontStyle(fontObj) {
- if (!this.cssStyle) {
- this.cssStyle = document.createElementNS(NS, 'svg:style');
- this.cssStyle.setAttributeNS(null, 'type', 'text/css');
- this.defs.appendChild(this.cssStyle);
- }
- var url = createObjectURL(fontObj.data, fontObj.mimetype, this.forceDataSchema);
- this.cssStyle.textContent += '@font-face { font-family: "' + fontObj.loadedName + '";' + ' src: url(' + url + '); }\n';
- },
- setFont: function SVGGraphics_setFont(details) {
- var current = this.current;
- var fontObj = this.commonObjs.get(details[0]);
- var size = details[1];
- this.current.font = fontObj;
- if (this.embedFonts && fontObj.data && !this.embeddedFonts[fontObj.loadedName]) {
- this.addFontStyle(fontObj);
- this.embeddedFonts[fontObj.loadedName] = fontObj;
- }
- current.fontMatrix = fontObj.fontMatrix ? fontObj.fontMatrix : FONT_IDENTITY_MATRIX;
- var bold = fontObj.black ? fontObj.bold ? 'bolder' : 'bold' : fontObj.bold ? 'bold' : 'normal';
- var italic = fontObj.italic ? 'italic' : 'normal';
- if (size < 0) {
- size = -size;
- current.fontDirection = -1;
- } else {
- current.fontDirection = 1;
- }
- current.fontSize = size;
- current.fontFamily = fontObj.loadedName;
- current.fontWeight = bold;
- current.fontStyle = italic;
- current.tspan = document.createElementNS(NS, 'svg:tspan');
- current.tspan.setAttributeNS(null, 'y', pf(-current.y));
- current.xcoords = [];
- },
- endText: function SVGGraphics_endText() {},
- setLineWidth: function SVGGraphics_setLineWidth(width) {
- this.current.lineWidth = width;
- },
- setLineCap: function SVGGraphics_setLineCap(style) {
- this.current.lineCap = LINE_CAP_STYLES[style];
- },
- setLineJoin: function SVGGraphics_setLineJoin(style) {
- this.current.lineJoin = LINE_JOIN_STYLES[style];
- },
- setMiterLimit: function SVGGraphics_setMiterLimit(limit) {
- this.current.miterLimit = limit;
- },
- setStrokeRGBColor: function SVGGraphics_setStrokeRGBColor(r, g, b) {
- var color = Util.makeCssRgb(r, g, b);
- this.current.strokeColor = color;
- },
- setFillRGBColor: function SVGGraphics_setFillRGBColor(r, g, b) {
- var color = Util.makeCssRgb(r, g, b);
- this.current.fillColor = color;
- this.current.tspan = document.createElementNS(NS, 'svg:tspan');
- this.current.xcoords = [];
- },
- setDash: function SVGGraphics_setDash(dashArray, dashPhase) {
- this.current.dashArray = dashArray;
- this.current.dashPhase = dashPhase;
- },
- constructPath: function SVGGraphics_constructPath(ops, args) {
- var current = this.current;
- var x = current.x,
- y = current.y;
- current.path = document.createElementNS(NS, 'svg:path');
- var d = [];
- var opLength = ops.length;
- for (var i = 0, j = 0; i < opLength; i++) {
- switch (ops[i] | 0) {
- case OPS.rectangle:
- x = args[j++];
- y = args[j++];
- var width = args[j++];
- var height = args[j++];
- var xw = x + width;
- var yh = y + height;
- d.push('M', pf(x), pf(y), 'L', pf(xw), pf(y), 'L', pf(xw), pf(yh), 'L', pf(x), pf(yh), 'Z');
- break;
- case OPS.moveTo:
- x = args[j++];
- y = args[j++];
- d.push('M', pf(x), pf(y));
- break;
- case OPS.lineTo:
- x = args[j++];
- y = args[j++];
- d.push('L', pf(x), pf(y));
- break;
- case OPS.curveTo:
- x = args[j + 4];
- y = args[j + 5];
- d.push('C', pf(args[j]), pf(args[j + 1]), pf(args[j + 2]), pf(args[j + 3]), pf(x), pf(y));
- j += 6;
- break;
- case OPS.curveTo2:
- x = args[j + 2];
- y = args[j + 3];
- d.push('C', pf(x), pf(y), pf(args[j]), pf(args[j + 1]), pf(args[j + 2]), pf(args[j + 3]));
- j += 4;
- break;
- case OPS.curveTo3:
- x = args[j + 2];
- y = args[j + 3];
- d.push('C', pf(args[j]), pf(args[j + 1]), pf(x), pf(y), pf(x), pf(y));
- j += 4;
- break;
- case OPS.closePath:
- d.push('Z');
- break;
- }
- }
- current.path.setAttributeNS(null, 'd', d.join(' '));
- current.path.setAttributeNS(null, 'stroke-miterlimit', pf(current.miterLimit));
- current.path.setAttributeNS(null, 'stroke-linecap', current.lineCap);
- current.path.setAttributeNS(null, 'stroke-linejoin', current.lineJoin);
- current.path.setAttributeNS(null, 'stroke-width', pf(current.lineWidth) + 'px');
- current.path.setAttributeNS(null, 'stroke-dasharray', current.dashArray.map(pf).join(' '));
- current.path.setAttributeNS(null, 'stroke-dashoffset', pf(current.dashPhase) + 'px');
- current.path.setAttributeNS(null, 'fill', 'none');
- this._ensureTransformGroup().appendChild(current.path);
- current.element = current.path;
- current.setCurrentPoint(x, y);
- },
- endPath: function SVGGraphics_endPath() {},
- clip: function SVGGraphics_clip(type) {
- var current = this.current;
- var clipId = 'clippath' + clipCount;
- clipCount++;
- var clipPath = document.createElementNS(NS, 'svg:clipPath');
- clipPath.setAttributeNS(null, 'id', clipId);
- clipPath.setAttributeNS(null, 'transform', pm(this.transformMatrix));
- var clipElement = current.element.cloneNode();
- if (type === 'evenodd') {
- clipElement.setAttributeNS(null, 'clip-rule', 'evenodd');
- } else {
- clipElement.setAttributeNS(null, 'clip-rule', 'nonzero');
- }
- clipPath.appendChild(clipElement);
- this.defs.appendChild(clipPath);
- if (current.activeClipUrl) {
- current.clipGroup = null;
- this.extraStack.forEach(function (prev) {
- prev.clipGroup = null;
- });
- }
- current.activeClipUrl = 'url(#' + clipId + ')';
- this.tgrp = null;
- },
- closePath: function SVGGraphics_closePath() {
- var current = this.current;
- var d = current.path.getAttributeNS(null, 'd');
- d += 'Z';
- current.path.setAttributeNS(null, 'd', d);
- },
- setLeading: function SVGGraphics_setLeading(leading) {
- this.current.leading = -leading;
- },
- setTextRise: function SVGGraphics_setTextRise(textRise) {
- this.current.textRise = textRise;
- },
- setHScale: function SVGGraphics_setHScale(scale) {
- this.current.textHScale = scale / 100;
- },
- setGState: function SVGGraphics_setGState(states) {
- for (var i = 0, ii = states.length; i < ii; i++) {
- var state = states[i];
- var key = state[0];
- var value = state[1];
- switch (key) {
- case 'LW':
- this.setLineWidth(value);
- break;
- case 'LC':
- this.setLineCap(value);
- break;
- case 'LJ':
- this.setLineJoin(value);
- break;
- case 'ML':
- this.setMiterLimit(value);
- break;
- case 'D':
- this.setDash(value[0], value[1]);
- break;
- case 'Font':
- this.setFont(value);
- break;
- default:
- warn('Unimplemented graphic state ' + key);
- break;
- }
- }
- },
- fill: function SVGGraphics_fill() {
- var current = this.current;
- current.element.setAttributeNS(null, 'fill', current.fillColor);
- },
- stroke: function SVGGraphics_stroke() {
- var current = this.current;
- current.element.setAttributeNS(null, 'stroke', current.strokeColor);
- current.element.setAttributeNS(null, 'fill', 'none');
- },
- eoFill: function SVGGraphics_eoFill() {
- var current = this.current;
- current.element.setAttributeNS(null, 'fill', current.fillColor);
- current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
- },
- fillStroke: function SVGGraphics_fillStroke() {
- this.stroke();
- this.fill();
- },
- eoFillStroke: function SVGGraphics_eoFillStroke() {
- this.current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
- this.fillStroke();
- },
- closeStroke: function SVGGraphics_closeStroke() {
- this.closePath();
- this.stroke();
- },
- closeFillStroke: function SVGGraphics_closeFillStroke() {
- this.closePath();
- this.fillStroke();
- },
- paintSolidColorImageMask: function SVGGraphics_paintSolidColorImageMask() {
- var current = this.current;
- var rect = document.createElementNS(NS, 'svg:rect');
- rect.setAttributeNS(null, 'x', '0');
- rect.setAttributeNS(null, 'y', '0');
- rect.setAttributeNS(null, 'width', '1px');
- rect.setAttributeNS(null, 'height', '1px');
- rect.setAttributeNS(null, 'fill', current.fillColor);
- this._ensureTransformGroup().appendChild(rect);
- },
- paintJpegXObject: function SVGGraphics_paintJpegXObject(objId, w, h) {
- var imgObj = this.objs.get(objId);
- var imgEl = document.createElementNS(NS, 'svg:image');
- imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgObj.src);
- imgEl.setAttributeNS(null, 'width', imgObj.width + 'px');
- imgEl.setAttributeNS(null, 'height', imgObj.height + 'px');
- imgEl.setAttributeNS(null, 'x', '0');
- imgEl.setAttributeNS(null, 'y', pf(-h));
- imgEl.setAttributeNS(null, 'transform', 'scale(' + pf(1 / w) + ' ' + pf(-1 / h) + ')');
- this._ensureTransformGroup().appendChild(imgEl);
- },
- paintImageXObject: function SVGGraphics_paintImageXObject(objId) {
- var imgData = this.objs.get(objId);
- if (!imgData) {
- warn('Dependent image isn\'t ready yet');
- return;
- }
- this.paintInlineImageXObject(imgData);
- },
- paintInlineImageXObject: function SVGGraphics_paintInlineImageXObject(imgData, mask) {
- var width = imgData.width;
- var height = imgData.height;
- var imgSrc = convertImgDataToPng(imgData, this.forceDataSchema);
- var cliprect = document.createElementNS(NS, 'svg:rect');
- cliprect.setAttributeNS(null, 'x', '0');
- cliprect.setAttributeNS(null, 'y', '0');
- cliprect.setAttributeNS(null, 'width', pf(width));
- cliprect.setAttributeNS(null, 'height', pf(height));
- this.current.element = cliprect;
- this.clip('nonzero');
- var imgEl = document.createElementNS(NS, 'svg:image');
- imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgSrc);
- imgEl.setAttributeNS(null, 'x', '0');
- imgEl.setAttributeNS(null, 'y', pf(-height));
- imgEl.setAttributeNS(null, 'width', pf(width) + 'px');
- imgEl.setAttributeNS(null, 'height', pf(height) + 'px');
- imgEl.setAttributeNS(null, 'transform', 'scale(' + pf(1 / width) + ' ' + pf(-1 / height) + ')');
- if (mask) {
- mask.appendChild(imgEl);
- } else {
- this._ensureTransformGroup().appendChild(imgEl);
- }
- },
- paintImageMaskXObject: function SVGGraphics_paintImageMaskXObject(imgData) {
- var current = this.current;
- var width = imgData.width;
- var height = imgData.height;
- var fillColor = current.fillColor;
- current.maskId = 'mask' + maskCount++;
- var mask = document.createElementNS(NS, 'svg:mask');
- mask.setAttributeNS(null, 'id', current.maskId);
- var rect = document.createElementNS(NS, 'svg:rect');
- rect.setAttributeNS(null, 'x', '0');
- rect.setAttributeNS(null, 'y', '0');
- rect.setAttributeNS(null, 'width', pf(width));
- rect.setAttributeNS(null, 'height', pf(height));
- rect.setAttributeNS(null, 'fill', fillColor);
- rect.setAttributeNS(null, 'mask', 'url(#' + current.maskId + ')');
- this.defs.appendChild(mask);
- this._ensureTransformGroup().appendChild(rect);
- this.paintInlineImageXObject(imgData, mask);
- },
- paintFormXObjectBegin: function SVGGraphics_paintFormXObjectBegin(matrix, bbox) {
- if (isArray(matrix) && matrix.length === 6) {
- this.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
- }
- if (isArray(bbox) && bbox.length === 4) {
- var width = bbox[2] - bbox[0];
- var height = bbox[3] - bbox[1];
- var cliprect = document.createElementNS(NS, 'svg:rect');
- cliprect.setAttributeNS(null, 'x', bbox[0]);
- cliprect.setAttributeNS(null, 'y', bbox[1]);
- cliprect.setAttributeNS(null, 'width', pf(width));
- cliprect.setAttributeNS(null, 'height', pf(height));
- this.current.element = cliprect;
- this.clip('nonzero');
- this.endPath();
- }
- },
- paintFormXObjectEnd: function SVGGraphics_paintFormXObjectEnd() {},
- _initialize: function SVGGraphics_initialize(viewport) {
- var svg = document.createElementNS(NS, 'svg:svg');
- svg.setAttributeNS(null, 'version', '1.1');
- svg.setAttributeNS(null, 'width', viewport.width + 'px');
- svg.setAttributeNS(null, 'height', viewport.height + 'px');
- svg.setAttributeNS(null, 'preserveAspectRatio', 'none');
- svg.setAttributeNS(null, 'viewBox', '0 0 ' + viewport.width + ' ' + viewport.height);
- var definitions = document.createElementNS(NS, 'svg:defs');
- svg.appendChild(definitions);
- this.defs = definitions;
- var rootGroup = document.createElementNS(NS, 'svg:g');
- rootGroup.setAttributeNS(null, 'transform', pm(viewport.transform));
- svg.appendChild(rootGroup);
- this.svg = rootGroup;
- return svg;
- },
- _ensureClipGroup: function SVGGraphics_ensureClipGroup() {
- if (!this.current.clipGroup) {
- var clipGroup = document.createElementNS(NS, 'svg:g');
- clipGroup.setAttributeNS(null, 'clip-path', this.current.activeClipUrl);
- this.svg.appendChild(clipGroup);
- this.current.clipGroup = clipGroup;
- }
- return this.current.clipGroup;
- },
- _ensureTransformGroup: function SVGGraphics_ensureTransformGroup() {
- if (!this.tgrp) {
- this.tgrp = document.createElementNS(NS, 'svg:g');
- this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix));
- if (this.current.activeClipUrl) {
- this._ensureClipGroup().appendChild(this.tgrp);
- } else {
- this.svg.appendChild(this.tgrp);
- }
- }
- return this.tgrp;
- }
- };
- return SVGGraphics;
-}();
-exports.SVGGraphics = SVGGraphics;
-
-/***/ }),
-/* 5 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var Util = sharedUtil.Util;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var CustomStyle = displayDOMUtils.CustomStyle;
-var getDefaultSetting = displayDOMUtils.getDefaultSetting;
-var renderTextLayer = function renderTextLayerClosure() {
- var MAX_TEXT_DIVS_TO_RENDER = 100000;
- var NonWhitespaceRegexp = /\S/;
- function isAllWhitespace(str) {
- return !NonWhitespaceRegexp.test(str);
- }
- var styleBuf = ['left: ', 0, 'px; top: ', 0, 'px; font-size: ', 0, 'px; font-family: ', '', ';'];
- function appendText(task, geom, styles) {
- var textDiv = document.createElement('div');
- var textDivProperties = {
- style: null,
- angle: 0,
- canvasWidth: 0,
- isWhitespace: false,
- originalTransform: null,
- paddingBottom: 0,
- paddingLeft: 0,
- paddingRight: 0,
- paddingTop: 0,
- scale: 1
- };
- task._textDivs.push(textDiv);
- if (isAllWhitespace(geom.str)) {
- textDivProperties.isWhitespace = true;
- task._textDivProperties.set(textDiv, textDivProperties);
- return;
- }
- var tx = Util.transform(task._viewport.transform, geom.transform);
- var angle = Math.atan2(tx[1], tx[0]);
- var style = styles[geom.fontName];
- if (style.vertical) {
- angle += Math.PI / 2;
- }
- var fontHeight = Math.sqrt(tx[2] * tx[2] + tx[3] * tx[3]);
- var fontAscent = fontHeight;
- if (style.ascent) {
- fontAscent = style.ascent * fontAscent;
- } else if (style.descent) {
- fontAscent = (1 + style.descent) * fontAscent;
- }
- var left;
- var top;
- if (angle === 0) {
- left = tx[4];
- top = tx[5] - fontAscent;
- } else {
- left = tx[4] + fontAscent * Math.sin(angle);
- top = tx[5] - fontAscent * Math.cos(angle);
- }
- styleBuf[1] = left;
- styleBuf[3] = top;
- styleBuf[5] = fontHeight;
- styleBuf[7] = style.fontFamily;
- textDivProperties.style = styleBuf.join('');
- textDiv.setAttribute('style', textDivProperties.style);
- textDiv.textContent = geom.str;
- if (getDefaultSetting('pdfBug')) {
- textDiv.dataset.fontName = geom.fontName;
- }
- if (angle !== 0) {
- textDivProperties.angle = angle * (180 / Math.PI);
- }
- if (geom.str.length > 1) {
- if (style.vertical) {
- textDivProperties.canvasWidth = geom.height * task._viewport.scale;
- } else {
- textDivProperties.canvasWidth = geom.width * task._viewport.scale;
- }
- }
- task._textDivProperties.set(textDiv, textDivProperties);
- if (task._enhanceTextSelection) {
- var angleCos = 1,
- angleSin = 0;
- if (angle !== 0) {
- angleCos = Math.cos(angle);
- angleSin = Math.sin(angle);
- }
- var divWidth = (style.vertical ? geom.height : geom.width) * task._viewport.scale;
- var divHeight = fontHeight;
- var m, b;
- if (angle !== 0) {
- m = [angleCos, angleSin, -angleSin, angleCos, left, top];
- b = Util.getAxialAlignedBoundingBox([0, 0, divWidth, divHeight], m);
- } else {
- b = [left, top, left + divWidth, top + divHeight];
- }
- task._bounds.push({
- left: b[0],
- top: b[1],
- right: b[2],
- bottom: b[3],
- div: textDiv,
- size: [divWidth, divHeight],
- m: m
- });
- }
- }
- function render(task) {
- if (task._canceled) {
- return;
- }
- var textLayerFrag = task._container;
- var textDivs = task._textDivs;
- var capability = task._capability;
- var textDivsLength = textDivs.length;
- if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
- task._renderingDone = true;
- capability.resolve();
- return;
- }
- var canvas = document.createElement('canvas');
- canvas.mozOpaque = true;
- var ctx = canvas.getContext('2d', { alpha: false });
- var lastFontSize;
- var lastFontFamily;
- for (var i = 0; i < textDivsLength; i++) {
- var textDiv = textDivs[i];
- var textDivProperties = task._textDivProperties.get(textDiv);
- if (textDivProperties.isWhitespace) {
- continue;
- }
- var fontSize = textDiv.style.fontSize;
- var fontFamily = textDiv.style.fontFamily;
- if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
- ctx.font = fontSize + ' ' + fontFamily;
- lastFontSize = fontSize;
- lastFontFamily = fontFamily;
- }
- var width = ctx.measureText(textDiv.textContent).width;
- textLayerFrag.appendChild(textDiv);
- var transform = '';
- if (textDivProperties.canvasWidth !== 0 && width > 0) {
- textDivProperties.scale = textDivProperties.canvasWidth / width;
- transform = 'scaleX(' + textDivProperties.scale + ')';
- }
- if (textDivProperties.angle !== 0) {
- transform = 'rotate(' + textDivProperties.angle + 'deg) ' + transform;
- }
- if (transform !== '') {
- textDivProperties.originalTransform = transform;
- CustomStyle.setProp('transform', textDiv, transform);
- }
- task._textDivProperties.set(textDiv, textDivProperties);
- }
- task._renderingDone = true;
- capability.resolve();
- }
- function expand(task) {
- var bounds = task._bounds;
- var viewport = task._viewport;
- var expanded = expandBounds(viewport.width, viewport.height, bounds);
- for (var i = 0; i < expanded.length; i++) {
- var div = bounds[i].div;
- var divProperties = task._textDivProperties.get(div);
- if (divProperties.angle === 0) {
- divProperties.paddingLeft = bounds[i].left - expanded[i].left;
- divProperties.paddingTop = bounds[i].top - expanded[i].top;
- divProperties.paddingRight = expanded[i].right - bounds[i].right;
- divProperties.paddingBottom = expanded[i].bottom - bounds[i].bottom;
- task._textDivProperties.set(div, divProperties);
- continue;
- }
- var e = expanded[i],
- b = bounds[i];
- var m = b.m,
- c = m[0],
- s = m[1];
- var points = [[0, 0], [0, b.size[1]], [b.size[0], 0], b.size];
- var ts = new Float64Array(64);
- points.forEach(function (p, i) {
- var t = Util.applyTransform(p, m);
- ts[i + 0] = c && (e.left - t[0]) / c;
- ts[i + 4] = s && (e.top - t[1]) / s;
- ts[i + 8] = c && (e.right - t[0]) / c;
- ts[i + 12] = s && (e.bottom - t[1]) / s;
- ts[i + 16] = s && (e.left - t[0]) / -s;
- ts[i + 20] = c && (e.top - t[1]) / c;
- ts[i + 24] = s && (e.right - t[0]) / -s;
- ts[i + 28] = c && (e.bottom - t[1]) / c;
- ts[i + 32] = c && (e.left - t[0]) / -c;
- ts[i + 36] = s && (e.top - t[1]) / -s;
- ts[i + 40] = c && (e.right - t[0]) / -c;
- ts[i + 44] = s && (e.bottom - t[1]) / -s;
- ts[i + 48] = s && (e.left - t[0]) / s;
- ts[i + 52] = c && (e.top - t[1]) / -c;
- ts[i + 56] = s && (e.right - t[0]) / s;
- ts[i + 60] = c && (e.bottom - t[1]) / -c;
- });
- var findPositiveMin = function (ts, offset, count) {
- var result = 0;
- for (var i = 0; i < count; i++) {
- var t = ts[offset++];
- if (t > 0) {
- result = result ? Math.min(t, result) : t;
- }
- }
- return result;
- };
- var boxScale = 1 + Math.min(Math.abs(c), Math.abs(s));
- divProperties.paddingLeft = findPositiveMin(ts, 32, 16) / boxScale;
- divProperties.paddingTop = findPositiveMin(ts, 48, 16) / boxScale;
- divProperties.paddingRight = findPositiveMin(ts, 0, 16) / boxScale;
- divProperties.paddingBottom = findPositiveMin(ts, 16, 16) / boxScale;
- task._textDivProperties.set(div, divProperties);
- }
- }
- function expandBounds(width, height, boxes) {
- var bounds = boxes.map(function (box, i) {
- return {
- x1: box.left,
- y1: box.top,
- x2: box.right,
- y2: box.bottom,
- index: i,
- x1New: undefined,
- x2New: undefined
- };
- });
- expandBoundsLTR(width, bounds);
- var expanded = new Array(boxes.length);
- bounds.forEach(function (b) {
- var i = b.index;
- expanded[i] = {
- left: b.x1New,
- top: 0,
- right: b.x2New,
- bottom: 0
- };
- });
- boxes.map(function (box, i) {
- var e = expanded[i],
- b = bounds[i];
- b.x1 = box.top;
- b.y1 = width - e.right;
- b.x2 = box.bottom;
- b.y2 = width - e.left;
- b.index = i;
- b.x1New = undefined;
- b.x2New = undefined;
- });
- expandBoundsLTR(height, bounds);
- bounds.forEach(function (b) {
- var i = b.index;
- expanded[i].top = b.x1New;
- expanded[i].bottom = b.x2New;
- });
- return expanded;
- }
- function expandBoundsLTR(width, bounds) {
- bounds.sort(function (a, b) {
- return a.x1 - b.x1 || a.index - b.index;
- });
- var fakeBoundary = {
- x1: -Infinity,
- y1: -Infinity,
- x2: 0,
- y2: Infinity,
- index: -1,
- x1New: 0,
- x2New: 0
- };
- var horizon = [{
- start: -Infinity,
- end: Infinity,
- boundary: fakeBoundary
- }];
- bounds.forEach(function (boundary) {
- var i = 0;
- while (i < horizon.length && horizon[i].end <= boundary.y1) {
- i++;
- }
- var j = horizon.length - 1;
- while (j >= 0 && horizon[j].start >= boundary.y2) {
- j--;
- }
- var horizonPart, affectedBoundary;
- var q,
- k,
- maxXNew = -Infinity;
- for (q = i; q <= j; q++) {
- horizonPart = horizon[q];
- affectedBoundary = horizonPart.boundary;
- var xNew;
- if (affectedBoundary.x2 > boundary.x1) {
- xNew = affectedBoundary.index > boundary.index ? affectedBoundary.x1New : boundary.x1;
- } else if (affectedBoundary.x2New === undefined) {
- xNew = (affectedBoundary.x2 + boundary.x1) / 2;
- } else {
- xNew = affectedBoundary.x2New;
- }
- if (xNew > maxXNew) {
- maxXNew = xNew;
- }
- }
- boundary.x1New = maxXNew;
- for (q = i; q <= j; q++) {
- horizonPart = horizon[q];
- affectedBoundary = horizonPart.boundary;
- if (affectedBoundary.x2New === undefined) {
- if (affectedBoundary.x2 > boundary.x1) {
- if (affectedBoundary.index > boundary.index) {
- affectedBoundary.x2New = affectedBoundary.x2;
- }
- } else {
- affectedBoundary.x2New = maxXNew;
- }
- } else if (affectedBoundary.x2New > maxXNew) {
- affectedBoundary.x2New = Math.max(maxXNew, affectedBoundary.x2);
- }
- }
- var changedHorizon = [],
- lastBoundary = null;
- for (q = i; q <= j; q++) {
- horizonPart = horizon[q];
- affectedBoundary = horizonPart.boundary;
- var useBoundary = affectedBoundary.x2 > boundary.x2 ? affectedBoundary : boundary;
- if (lastBoundary === useBoundary) {
- changedHorizon[changedHorizon.length - 1].end = horizonPart.end;
- } else {
- changedHorizon.push({
- start: horizonPart.start,
- end: horizonPart.end,
- boundary: useBoundary
- });
- lastBoundary = useBoundary;
- }
- }
- if (horizon[i].start < boundary.y1) {
- changedHorizon[0].start = boundary.y1;
- changedHorizon.unshift({
- start: horizon[i].start,
- end: boundary.y1,
- boundary: horizon[i].boundary
- });
- }
- if (boundary.y2 < horizon[j].end) {
- changedHorizon[changedHorizon.length - 1].end = boundary.y2;
- changedHorizon.push({
- start: boundary.y2,
- end: horizon[j].end,
- boundary: horizon[j].boundary
- });
- }
- for (q = i; q <= j; q++) {
- horizonPart = horizon[q];
- affectedBoundary = horizonPart.boundary;
- if (affectedBoundary.x2New !== undefined) {
- continue;
- }
- var used = false;
- for (k = i - 1; !used && k >= 0 && horizon[k].start >= affectedBoundary.y1; k--) {
- used = horizon[k].boundary === affectedBoundary;
- }
- for (k = j + 1; !used && k < horizon.length && horizon[k].end <= affectedBoundary.y2; k++) {
- used = horizon[k].boundary === affectedBoundary;
- }
- for (k = 0; !used && k < changedHorizon.length; k++) {
- used = changedHorizon[k].boundary === affectedBoundary;
- }
- if (!used) {
- affectedBoundary.x2New = maxXNew;
- }
- }
- Array.prototype.splice.apply(horizon, [i, j - i + 1].concat(changedHorizon));
- });
- horizon.forEach(function (horizonPart) {
- var affectedBoundary = horizonPart.boundary;
- if (affectedBoundary.x2New === undefined) {
- affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
- }
- });
- }
- function TextLayerRenderTask(textContent, container, viewport, textDivs, enhanceTextSelection) {
- this._textContent = textContent;
- this._container = container;
- this._viewport = viewport;
- this._textDivs = textDivs || [];
- this._textDivProperties = new WeakMap();
- this._renderingDone = false;
- this._canceled = false;
- this._capability = createPromiseCapability();
- this._renderTimer = null;
- this._bounds = [];
- this._enhanceTextSelection = !!enhanceTextSelection;
- }
- TextLayerRenderTask.prototype = {
- get promise() {
- return this._capability.promise;
- },
- cancel: function TextLayer_cancel() {
- this._canceled = true;
- if (this._renderTimer !== null) {
- clearTimeout(this._renderTimer);
- this._renderTimer = null;
- }
- this._capability.reject('canceled');
- },
- _render: function TextLayer_render(timeout) {
- var textItems = this._textContent.items;
- var textStyles = this._textContent.styles;
- for (var i = 0, len = textItems.length; i < len; i++) {
- appendText(this, textItems[i], textStyles);
- }
- if (!timeout) {
- render(this);
- } else {
- var self = this;
- this._renderTimer = setTimeout(function () {
- render(self);
- self._renderTimer = null;
- }, timeout);
- }
- },
- expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
- if (!this._enhanceTextSelection || !this._renderingDone) {
- return;
- }
- if (this._bounds !== null) {
- expand(this);
- this._bounds = null;
- }
- for (var i = 0, ii = this._textDivs.length; i < ii; i++) {
- var div = this._textDivs[i];
- var divProperties = this._textDivProperties.get(div);
- if (divProperties.isWhitespace) {
- continue;
- }
- if (expandDivs) {
- var transform = '',
- padding = '';
- if (divProperties.scale !== 1) {
- transform = 'scaleX(' + divProperties.scale + ')';
- }
- if (divProperties.angle !== 0) {
- transform = 'rotate(' + divProperties.angle + 'deg) ' + transform;
- }
- if (divProperties.paddingLeft !== 0) {
- padding += ' padding-left: ' + divProperties.paddingLeft / divProperties.scale + 'px;';
- transform += ' translateX(' + -divProperties.paddingLeft / divProperties.scale + 'px)';
- }
- if (divProperties.paddingTop !== 0) {
- padding += ' padding-top: ' + divProperties.paddingTop + 'px;';
- transform += ' translateY(' + -divProperties.paddingTop + 'px)';
- }
- if (divProperties.paddingRight !== 0) {
- padding += ' padding-right: ' + divProperties.paddingRight / divProperties.scale + 'px;';
- }
- if (divProperties.paddingBottom !== 0) {
- padding += ' padding-bottom: ' + divProperties.paddingBottom + 'px;';
- }
- if (padding !== '') {
- div.setAttribute('style', divProperties.style + padding);
- }
- if (transform !== '') {
- CustomStyle.setProp('transform', div, transform);
- }
- } else {
- div.style.padding = 0;
- CustomStyle.setProp('transform', div, divProperties.originalTransform || '');
- }
- }
- }
- };
- function renderTextLayer(renderParameters) {
- var task = new TextLayerRenderTask(renderParameters.textContent, renderParameters.container, renderParameters.viewport, renderParameters.textDivs, renderParameters.enhanceTextSelection);
- task._render(renderParameters.timeout);
- return task;
- }
- return renderTextLayer;
-}();
-exports.renderTextLayer = renderTextLayer;
-
-/***/ }),
-/* 6 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var g;
-g = function () {
- return this;
-}();
-try {
- g = g || Function("return this")() || (1, eval)("this");
-} catch (e) {
- if (typeof window === "object") g = window;
-}
-module.exports = g;
-
-/***/ }),
-/* 7 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var error = sharedUtil.error;
-function fixMetadata(meta) {
- return meta.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
- var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
- return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
- });
- var chars = '';
- for (var i = 0; i < bytes.length; i += 2) {
- var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
- chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
- }
- return '>' + chars;
- });
-}
-function Metadata(meta) {
- if (typeof meta === 'string') {
- meta = fixMetadata(meta);
- var parser = new DOMParser();
- meta = parser.parseFromString(meta, 'application/xml');
- } else if (!(meta instanceof Document)) {
- error('Metadata: Invalid metadata object');
- }
- this.metaDocument = meta;
- this.metadata = Object.create(null);
- this.parse();
-}
-Metadata.prototype = {
- parse: function Metadata_parse() {
- var doc = this.metaDocument;
- var rdf = doc.documentElement;
- if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
- rdf = rdf.firstChild;
- while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
- rdf = rdf.nextSibling;
- }
- }
- var nodeName = rdf ? rdf.nodeName.toLowerCase() : null;
- if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
- return;
- }
- var children = rdf.childNodes,
- desc,
- entry,
- name,
- i,
- ii,
- length,
- iLength;
- for (i = 0, length = children.length; i < length; i++) {
- desc = children[i];
- if (desc.nodeName.toLowerCase() !== 'rdf:description') {
- continue;
- }
- for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
- if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
- entry = desc.childNodes[ii];
- name = entry.nodeName.toLowerCase();
- this.metadata[name] = entry.textContent.trim();
- }
- }
- }
- },
- get: function Metadata_get(name) {
- return this.metadata[name] || null;
- },
- has: function Metadata_has(name) {
- return typeof this.metadata[name] !== 'undefined';
- }
-};
-exports.Metadata = Metadata;
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var shadow = sharedUtil.shadow;
-var getDefaultSetting = displayDOMUtils.getDefaultSetting;
-var WebGLUtils = function WebGLUtilsClosure() {
- function loadShader(gl, code, shaderType) {
- var shader = gl.createShader(shaderType);
- gl.shaderSource(shader, code);
- gl.compileShader(shader);
- var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
- if (!compiled) {
- var errorMsg = gl.getShaderInfoLog(shader);
- throw new Error('Error during shader compilation: ' + errorMsg);
- }
- return shader;
- }
- function createVertexShader(gl, code) {
- return loadShader(gl, code, gl.VERTEX_SHADER);
- }
- function createFragmentShader(gl, code) {
- return loadShader(gl, code, gl.FRAGMENT_SHADER);
- }
- function createProgram(gl, shaders) {
- var program = gl.createProgram();
- for (var i = 0, ii = shaders.length; i < ii; ++i) {
- gl.attachShader(program, shaders[i]);
- }
- gl.linkProgram(program);
- var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
- if (!linked) {
- var errorMsg = gl.getProgramInfoLog(program);
- throw new Error('Error during program linking: ' + errorMsg);
- }
- return program;
- }
- function createTexture(gl, image, textureId) {
- gl.activeTexture(textureId);
- var texture = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
- return texture;
- }
- var currentGL, currentCanvas;
- function generateGL() {
- if (currentGL) {
- return;
- }
- currentCanvas = document.createElement('canvas');
- currentGL = currentCanvas.getContext('webgl', { premultipliedalpha: false });
- }
- var smaskVertexShaderCode = '\
- 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; \
- } ';
- var smaskFragmentShaderCode = '\
- 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; \
- } ';
- var smaskCache = null;
- function initSmaskGL() {
- var canvas, gl;
- generateGL();
- canvas = currentCanvas;
- currentCanvas = null;
- gl = currentGL;
- currentGL = null;
- var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
- var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
- var program = createProgram(gl, [vertexShader, fragmentShader]);
- gl.useProgram(program);
- var cache = {};
- cache.gl = gl;
- cache.canvas = canvas;
- cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
- cache.positionLocation = gl.getAttribLocation(program, 'a_position');
- cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop');
- cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype');
- var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
- var texLayerLocation = gl.getUniformLocation(program, 'u_image');
- var texMaskLocation = gl.getUniformLocation(program, 'u_mask');
- var texCoordBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW);
- gl.enableVertexAttribArray(texCoordLocation);
- gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
- gl.uniform1i(texLayerLocation, 0);
- gl.uniform1i(texMaskLocation, 1);
- smaskCache = cache;
- }
- function composeSMask(layer, mask, properties) {
- var width = layer.width,
- height = layer.height;
- if (!smaskCache) {
- initSmaskGL();
- }
- var cache = smaskCache,
- canvas = cache.canvas,
- gl = cache.gl;
- canvas.width = width;
- canvas.height = height;
- gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
- gl.uniform2f(cache.resolutionLocation, width, height);
- if (properties.backdrop) {
- gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], properties.backdrop[1], properties.backdrop[2], 1);
- } else {
- gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
- }
- gl.uniform1i(cache.subtypeLocation, properties.subtype === 'Luminosity' ? 1 : 0);
- var texture = createTexture(gl, layer, gl.TEXTURE0);
- var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
- var buffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, width, 0, 0, height, 0, height, width, 0, width, height]), gl.STATIC_DRAW);
- gl.enableVertexAttribArray(cache.positionLocation);
- gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
- gl.clearColor(0, 0, 0, 0);
- gl.enable(gl.BLEND);
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
- gl.clear(gl.COLOR_BUFFER_BIT);
- gl.drawArrays(gl.TRIANGLES, 0, 6);
- gl.flush();
- gl.deleteTexture(texture);
- gl.deleteTexture(maskTexture);
- gl.deleteBuffer(buffer);
- return canvas;
- }
- var figuresVertexShaderCode = '\
- 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); \
- } ';
- var figuresFragmentShaderCode = '\
- precision mediump float; \
- \
- varying vec4 v_color; \
- \
- void main() { \
- gl_FragColor = v_color; \
- } ';
- var figuresCache = null;
- function initFiguresGL() {
- var canvas, gl;
- generateGL();
- canvas = currentCanvas;
- currentCanvas = null;
- gl = currentGL;
- currentGL = null;
- var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
- var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
- var program = createProgram(gl, [vertexShader, fragmentShader]);
- gl.useProgram(program);
- var cache = {};
- cache.gl = gl;
- cache.canvas = canvas;
- cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
- cache.scaleLocation = gl.getUniformLocation(program, 'u_scale');
- cache.offsetLocation = gl.getUniformLocation(program, 'u_offset');
- cache.positionLocation = gl.getAttribLocation(program, 'a_position');
- cache.colorLocation = gl.getAttribLocation(program, 'a_color');
- figuresCache = cache;
- }
- function drawFigures(width, height, backgroundColor, figures, context) {
- if (!figuresCache) {
- initFiguresGL();
- }
- var cache = figuresCache,
- canvas = cache.canvas,
- gl = cache.gl;
- canvas.width = width;
- canvas.height = height;
- gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
- gl.uniform2f(cache.resolutionLocation, width, height);
- var count = 0;
- var i, ii, rows;
- for (i = 0, ii = figures.length; i < ii; i++) {
- switch (figures[i].type) {
- case 'lattice':
- rows = figures[i].coords.length / figures[i].verticesPerRow | 0;
- count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
- break;
- case 'triangles':
- count += figures[i].coords.length;
- break;
- }
- }
- var coords = new Float32Array(count * 2);
- var colors = new Uint8Array(count * 3);
- var coordsMap = context.coords,
- colorsMap = context.colors;
- var pIndex = 0,
- cIndex = 0;
- for (i = 0, ii = figures.length; i < ii; i++) {
- var figure = figures[i],
- ps = figure.coords,
- cs = figure.colors;
- switch (figure.type) {
- case 'lattice':
- var cols = figure.verticesPerRow;
- rows = ps.length / cols | 0;
- for (var row = 1; row < rows; row++) {
- var offset = row * cols + 1;
- for (var col = 1; col < cols; col++, offset++) {
- coords[pIndex] = coordsMap[ps[offset - cols - 1]];
- coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
- coords[pIndex + 2] = coordsMap[ps[offset - cols]];
- coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
- coords[pIndex + 4] = coordsMap[ps[offset - 1]];
- coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
- colors[cIndex] = colorsMap[cs[offset - cols - 1]];
- colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
- colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
- colors[cIndex + 3] = colorsMap[cs[offset - cols]];
- colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
- colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
- colors[cIndex + 6] = colorsMap[cs[offset - 1]];
- colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
- colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
- coords[pIndex + 6] = coords[pIndex + 2];
- coords[pIndex + 7] = coords[pIndex + 3];
- coords[pIndex + 8] = coords[pIndex + 4];
- coords[pIndex + 9] = coords[pIndex + 5];
- coords[pIndex + 10] = coordsMap[ps[offset]];
- coords[pIndex + 11] = coordsMap[ps[offset] + 1];
- colors[cIndex + 9] = colors[cIndex + 3];
- colors[cIndex + 10] = colors[cIndex + 4];
- colors[cIndex + 11] = colors[cIndex + 5];
- colors[cIndex + 12] = colors[cIndex + 6];
- colors[cIndex + 13] = colors[cIndex + 7];
- colors[cIndex + 14] = colors[cIndex + 8];
- colors[cIndex + 15] = colorsMap[cs[offset]];
- colors[cIndex + 16] = colorsMap[cs[offset] + 1];
- colors[cIndex + 17] = colorsMap[cs[offset] + 2];
- pIndex += 12;
- cIndex += 18;
- }
- }
- break;
- case 'triangles':
- for (var j = 0, jj = ps.length; j < jj; j++) {
- coords[pIndex] = coordsMap[ps[j]];
- coords[pIndex + 1] = coordsMap[ps[j] + 1];
- colors[cIndex] = colorsMap[cs[j]];
- colors[cIndex + 1] = colorsMap[cs[j] + 1];
- colors[cIndex + 2] = colorsMap[cs[j] + 2];
- pIndex += 2;
- cIndex += 3;
- }
- break;
- }
- }
- if (backgroundColor) {
- gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, backgroundColor[2] / 255, 1.0);
- } else {
- gl.clearColor(0, 0, 0, 0);
- }
- gl.clear(gl.COLOR_BUFFER_BIT);
- var coordsBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
- gl.enableVertexAttribArray(cache.positionLocation);
- gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
- var colorsBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
- gl.enableVertexAttribArray(cache.colorLocation);
- gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, 0, 0);
- gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
- gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
- gl.drawArrays(gl.TRIANGLES, 0, count);
- gl.flush();
- gl.deleteBuffer(coordsBuffer);
- gl.deleteBuffer(colorsBuffer);
- return canvas;
- }
- function cleanup() {
- if (smaskCache && smaskCache.canvas) {
- smaskCache.canvas.width = 0;
- smaskCache.canvas.height = 0;
- }
- if (figuresCache && figuresCache.canvas) {
- figuresCache.canvas.width = 0;
- figuresCache.canvas.height = 0;
- }
- smaskCache = null;
- figuresCache = null;
- }
- return {
- get isEnabled() {
- if (getDefaultSetting('disableWebGL')) {
- return false;
- }
- var enabled = false;
- try {
- generateGL();
- enabled = !!currentGL;
- } catch (e) {}
- return shadow(this, 'isEnabled', enabled);
- },
- composeSMask: composeSMask,
- drawFigures: drawFigures,
- clear: cleanup
- };
-}();
-exports.WebGLUtils = WebGLUtils;
-
-/***/ }),
-/* 9 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var displayAPI = __w_pdfjs_require__(3);
-var displayAnnotationLayer = __w_pdfjs_require__(2);
-var displayTextLayer = __w_pdfjs_require__(5);
-var displayMetadata = __w_pdfjs_require__(7);
-var displaySVG = __w_pdfjs_require__(4);
-var globalScope = sharedUtil.globalScope;
-var deprecated = sharedUtil.deprecated;
-var warn = sharedUtil.warn;
-var LinkTarget = displayDOMUtils.LinkTarget;
-var DEFAULT_LINK_REL = displayDOMUtils.DEFAULT_LINK_REL;
-var isWorker = typeof window === 'undefined';
-if (!globalScope.PDFJS) {
- globalScope.PDFJS = {};
-}
-var PDFJS = globalScope.PDFJS;
-PDFJS.version = '1.8.172';
-PDFJS.build = '8ff1fbe7';
-PDFJS.pdfBug = false;
-if (PDFJS.verbosity !== undefined) {
- sharedUtil.setVerbosityLevel(PDFJS.verbosity);
-}
-delete PDFJS.verbosity;
-Object.defineProperty(PDFJS, 'verbosity', {
- get: function () {
- return sharedUtil.getVerbosityLevel();
- },
- set: function (level) {
- sharedUtil.setVerbosityLevel(level);
- },
- enumerable: true,
- configurable: true
-});
-PDFJS.VERBOSITY_LEVELS = sharedUtil.VERBOSITY_LEVELS;
-PDFJS.OPS = sharedUtil.OPS;
-PDFJS.UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
-PDFJS.isValidUrl = displayDOMUtils.isValidUrl;
-PDFJS.shadow = sharedUtil.shadow;
-PDFJS.createBlob = sharedUtil.createBlob;
-PDFJS.createObjectURL = function PDFJS_createObjectURL(data, contentType) {
- return sharedUtil.createObjectURL(data, contentType, PDFJS.disableCreateObjectURL);
-};
-Object.defineProperty(PDFJS, 'isLittleEndian', {
- configurable: true,
- get: function PDFJS_isLittleEndian() {
- var value = sharedUtil.isLittleEndian();
- return sharedUtil.shadow(PDFJS, 'isLittleEndian', value);
- }
-});
-PDFJS.removeNullCharacters = sharedUtil.removeNullCharacters;
-PDFJS.PasswordResponses = sharedUtil.PasswordResponses;
-PDFJS.PasswordException = sharedUtil.PasswordException;
-PDFJS.UnknownErrorException = sharedUtil.UnknownErrorException;
-PDFJS.InvalidPDFException = sharedUtil.InvalidPDFException;
-PDFJS.MissingPDFException = sharedUtil.MissingPDFException;
-PDFJS.UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
-PDFJS.Util = sharedUtil.Util;
-PDFJS.PageViewport = sharedUtil.PageViewport;
-PDFJS.createPromiseCapability = sharedUtil.createPromiseCapability;
-PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize;
-PDFJS.cMapUrl = PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl;
-PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
-PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ? false : PDFJS.disableFontFace;
-PDFJS.imageResourcesPath = PDFJS.imageResourcesPath === undefined ? '' : PDFJS.imageResourcesPath;
-PDFJS.disableWorker = PDFJS.disableWorker === undefined ? false : PDFJS.disableWorker;
-PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
-PDFJS.workerPort = PDFJS.workerPort === undefined ? null : PDFJS.workerPort;
-PDFJS.disableRange = PDFJS.disableRange === undefined ? false : PDFJS.disableRange;
-PDFJS.disableStream = PDFJS.disableStream === undefined ? false : PDFJS.disableStream;
-PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ? false : PDFJS.disableAutoFetch;
-PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
-PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ? true : PDFJS.postMessageTransfers;
-PDFJS.disableCreateObjectURL = PDFJS.disableCreateObjectURL === undefined ? false : PDFJS.disableCreateObjectURL;
-PDFJS.disableWebGL = PDFJS.disableWebGL === undefined ? true : PDFJS.disableWebGL;
-PDFJS.externalLinkTarget = PDFJS.externalLinkTarget === undefined ? LinkTarget.NONE : PDFJS.externalLinkTarget;
-PDFJS.externalLinkRel = PDFJS.externalLinkRel === undefined ? DEFAULT_LINK_REL : PDFJS.externalLinkRel;
-PDFJS.isEvalSupported = PDFJS.isEvalSupported === undefined ? true : PDFJS.isEvalSupported;
-PDFJS.pdfjsNext = PDFJS.pdfjsNext === undefined ? false : PDFJS.pdfjsNext;
-var savedOpenExternalLinksInNewWindow = PDFJS.openExternalLinksInNewWindow;
-delete PDFJS.openExternalLinksInNewWindow;
-Object.defineProperty(PDFJS, 'openExternalLinksInNewWindow', {
- get: function () {
- return PDFJS.externalLinkTarget === LinkTarget.BLANK;
- },
- set: function (value) {
- if (value) {
- deprecated('PDFJS.openExternalLinksInNewWindow, please use ' + '"PDFJS.externalLinkTarget = PDFJS.LinkTarget.BLANK" instead.');
- }
- if (PDFJS.externalLinkTarget !== LinkTarget.NONE) {
- warn('PDFJS.externalLinkTarget is already initialized');
- return;
- }
- PDFJS.externalLinkTarget = value ? LinkTarget.BLANK : LinkTarget.NONE;
- },
- enumerable: true,
- configurable: true
-});
-if (savedOpenExternalLinksInNewWindow) {
- PDFJS.openExternalLinksInNewWindow = savedOpenExternalLinksInNewWindow;
-}
-PDFJS.getDocument = displayAPI.getDocument;
-PDFJS.PDFDataRangeTransport = displayAPI.PDFDataRangeTransport;
-PDFJS.PDFWorker = displayAPI.PDFWorker;
-Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', {
- configurable: true,
- get: function PDFJS_hasCanvasTypedArrays() {
- var value = displayDOMUtils.hasCanvasTypedArrays();
- return sharedUtil.shadow(PDFJS, 'hasCanvasTypedArrays', value);
- }
-});
-PDFJS.CustomStyle = displayDOMUtils.CustomStyle;
-PDFJS.LinkTarget = LinkTarget;
-PDFJS.addLinkAttributes = displayDOMUtils.addLinkAttributes;
-PDFJS.getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
-PDFJS.isExternalLinkTargetSet = displayDOMUtils.isExternalLinkTargetSet;
-PDFJS.AnnotationLayer = displayAnnotationLayer.AnnotationLayer;
-PDFJS.renderTextLayer = displayTextLayer.renderTextLayer;
-PDFJS.Metadata = displayMetadata.Metadata;
-PDFJS.SVGGraphics = displaySVG.SVGGraphics;
-PDFJS.UnsupportedManager = displayAPI._UnsupportedManager;
-exports.globalScope = globalScope;
-exports.isWorker = isWorker;
-exports.PDFJS = globalScope.PDFJS;
-
-/***/ }),
-/* 10 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayDOMUtils = __w_pdfjs_require__(1);
-var displayPatternHelper = __w_pdfjs_require__(12);
-var displayWebGL = __w_pdfjs_require__(8);
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
-var ImageKind = sharedUtil.ImageKind;
-var OPS = sharedUtil.OPS;
-var TextRenderingMode = sharedUtil.TextRenderingMode;
-var Uint32ArrayView = sharedUtil.Uint32ArrayView;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var info = sharedUtil.info;
-var isNum = sharedUtil.isNum;
-var isArray = sharedUtil.isArray;
-var isLittleEndian = sharedUtil.isLittleEndian;
-var error = sharedUtil.error;
-var shadow = sharedUtil.shadow;
-var warn = sharedUtil.warn;
-var TilingPattern = displayPatternHelper.TilingPattern;
-var getShadingPatternFromIR = displayPatternHelper.getShadingPatternFromIR;
-var WebGLUtils = displayWebGL.WebGLUtils;
-var hasCanvasTypedArrays = displayDOMUtils.hasCanvasTypedArrays;
-var MIN_FONT_SIZE = 16;
-var MAX_FONT_SIZE = 100;
-var MAX_GROUP_SIZE = 4096;
-var MIN_WIDTH_FACTOR = 0.65;
-var COMPILE_TYPE3_GLYPHS = true;
-var MAX_SIZE_TO_COMPILE = 1000;
-var FULL_CHUNK_HEIGHT = 16;
-var HasCanvasTypedArraysCached = {
- get value() {
- return shadow(HasCanvasTypedArraysCached, 'value', hasCanvasTypedArrays());
- }
-};
-var IsLittleEndianCached = {
- get value() {
- return shadow(IsLittleEndianCached, 'value', isLittleEndian());
- }
-};
-function addContextCurrentTransform(ctx) {
- if (!ctx.mozCurrentTransform) {
- ctx._originalSave = ctx.save;
- ctx._originalRestore = ctx.restore;
- ctx._originalRotate = ctx.rotate;
- ctx._originalScale = ctx.scale;
- ctx._originalTranslate = ctx.translate;
- ctx._originalTransform = ctx.transform;
- ctx._originalSetTransform = ctx.setTransform;
- ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
- ctx._transformStack = [];
- Object.defineProperty(ctx, 'mozCurrentTransform', {
- get: function getCurrentTransform() {
- return this._transformMatrix;
- }
- });
- Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
- get: function getCurrentTransformInverse() {
- var m = this._transformMatrix;
- var a = m[0],
- b = m[1],
- c = m[2],
- d = m[3],
- e = m[4],
- f = m[5];
- var ad_bc = a * d - b * c;
- var bc_ad = b * c - a * d;
- return [d / ad_bc, b / bc_ad, c / bc_ad, a / ad_bc, (d * e - c * f) / bc_ad, (b * e - a * f) / ad_bc];
- }
- });
- ctx.save = function ctxSave() {
- var old = this._transformMatrix;
- this._transformStack.push(old);
- this._transformMatrix = old.slice(0, 6);
- this._originalSave();
- };
- ctx.restore = function ctxRestore() {
- var prev = this._transformStack.pop();
- if (prev) {
- this._transformMatrix = prev;
- this._originalRestore();
- }
- };
- ctx.translate = function ctxTranslate(x, y) {
- var m = this._transformMatrix;
- m[4] = m[0] * x + m[2] * y + m[4];
- m[5] = m[1] * x + m[3] * y + m[5];
- this._originalTranslate(x, y);
- };
- ctx.scale = function ctxScale(x, y) {
- var m = this._transformMatrix;
- m[0] = m[0] * x;
- m[1] = m[1] * x;
- m[2] = m[2] * y;
- m[3] = m[3] * y;
- this._originalScale(x, y);
- };
- ctx.transform = function ctxTransform(a, b, c, d, e, f) {
- var m = this._transformMatrix;
- this._transformMatrix = [m[0] * a + m[2] * b, m[1] * a + m[3] * b, m[0] * c + m[2] * d, m[1] * c + m[3] * d, m[0] * e + m[2] * f + m[4], m[1] * e + m[3] * f + m[5]];
- ctx._originalTransform(a, b, c, d, e, f);
- };
- ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
- this._transformMatrix = [a, b, c, d, e, f];
- ctx._originalSetTransform(a, b, c, d, e, f);
- };
- ctx.rotate = function ctxRotate(angle) {
- var cosValue = Math.cos(angle);
- var sinValue = Math.sin(angle);
- var m = this._transformMatrix;
- this._transformMatrix = [m[0] * cosValue + m[2] * sinValue, m[1] * cosValue + m[3] * sinValue, m[0] * -sinValue + m[2] * cosValue, m[1] * -sinValue + m[3] * cosValue, m[4], m[5]];
- this._originalRotate(angle);
- };
- }
-}
-var CachedCanvases = function CachedCanvasesClosure() {
- function CachedCanvases(canvasFactory) {
- this.canvasFactory = canvasFactory;
- this.cache = Object.create(null);
- }
- CachedCanvases.prototype = {
- getCanvas: function CachedCanvases_getCanvas(id, width, height, trackTransform) {
- var canvasEntry;
- if (this.cache[id] !== undefined) {
- canvasEntry = this.cache[id];
- this.canvasFactory.reset(canvasEntry, width, height);
- canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
- } else {
- canvasEntry = this.canvasFactory.create(width, height);
- this.cache[id] = canvasEntry;
- }
- if (trackTransform) {
- addContextCurrentTransform(canvasEntry.context);
- }
- return canvasEntry;
- },
- clear: function () {
- for (var id in this.cache) {
- var canvasEntry = this.cache[id];
- this.canvasFactory.destroy(canvasEntry);
- delete this.cache[id];
- }
- }
- };
- return CachedCanvases;
-}();
-function compileType3Glyph(imgData) {
- var POINT_TO_PROCESS_LIMIT = 1000;
- var width = imgData.width,
- height = imgData.height;
- var i,
- j,
- j0,
- width1 = width + 1;
- var points = new Uint8Array(width1 * (height + 1));
- var POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
- var lineSize = width + 7 & ~7,
- data0 = imgData.data;
- var data = new Uint8Array(lineSize * height),
- pos = 0,
- ii;
- for (i = 0, ii = data0.length; i < ii; i++) {
- var mask = 128,
- elem = data0[i];
- while (mask > 0) {
- data[pos++] = elem & mask ? 0 : 255;
- mask >>= 1;
- }
- }
- var count = 0;
- pos = 0;
- if (data[pos] !== 0) {
- points[0] = 1;
- ++count;
- }
- for (j = 1; j < width; j++) {
- if (data[pos] !== data[pos + 1]) {
- points[j] = data[pos] ? 2 : 1;
- ++count;
- }
- pos++;
- }
- if (data[pos] !== 0) {
- points[j] = 2;
- ++count;
- }
- for (i = 1; i < height; i++) {
- pos = i * lineSize;
- j0 = i * width1;
- if (data[pos - lineSize] !== data[pos]) {
- points[j0] = data[pos] ? 1 : 8;
- ++count;
- }
- var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
- for (j = 1; j < width; j++) {
- sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0);
- if (POINT_TYPES[sum]) {
- points[j0 + j] = POINT_TYPES[sum];
- ++count;
- }
- pos++;
- }
- if (data[pos - lineSize] !== data[pos]) {
- points[j0 + j] = data[pos] ? 2 : 4;
- ++count;
- }
- if (count > POINT_TO_PROCESS_LIMIT) {
- return null;
- }
- }
- pos = lineSize * (height - 1);
- j0 = i * width1;
- if (data[pos] !== 0) {
- points[j0] = 8;
- ++count;
- }
- for (j = 1; j < width; j++) {
- if (data[pos] !== data[pos + 1]) {
- points[j0 + j] = data[pos] ? 4 : 8;
- ++count;
- }
- pos++;
- }
- if (data[pos] !== 0) {
- points[j0 + j] = 4;
- ++count;
- }
- if (count > POINT_TO_PROCESS_LIMIT) {
- return null;
- }
- var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
- var outlines = [];
- for (i = 0; count && i <= height; i++) {
- var p = i * width1;
- var end = p + width;
- while (p < end && !points[p]) {
- p++;
- }
- if (p === end) {
- continue;
- }
- var coords = [p % width1, i];
- var type = points[p],
- p0 = p,
- pp;
- do {
- var step = steps[type];
- do {
- p += step;
- } while (!points[p]);
- pp = points[p];
- if (pp !== 5 && pp !== 10) {
- type = pp;
- points[p] = 0;
- } else {
- type = pp & 0x33 * type >> 4;
- points[p] &= type >> 2 | type << 2;
- }
- coords.push(p % width1);
- coords.push(p / width1 | 0);
- --count;
- } while (p0 !== p);
- outlines.push(coords);
- --i;
- }
- var drawOutline = function (c) {
- c.save();
- c.scale(1 / width, -1 / height);
- c.translate(0, -height);
- c.beginPath();
- for (var i = 0, ii = outlines.length; i < ii; i++) {
- var o = outlines[i];
- c.moveTo(o[0], o[1]);
- for (var j = 2, jj = o.length; j < jj; j += 2) {
- c.lineTo(o[j], o[j + 1]);
- }
- }
- c.fill();
- c.beginPath();
- c.restore();
- };
- return drawOutline;
-}
-var CanvasExtraState = function CanvasExtraStateClosure() {
- function CanvasExtraState(old) {
- this.alphaIsShape = false;
- this.fontSize = 0;
- this.fontSizeScale = 1;
- this.textMatrix = IDENTITY_MATRIX;
- this.textMatrixScale = 1;
- this.fontMatrix = FONT_IDENTITY_MATRIX;
- 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 = TextRenderingMode.FILL;
- this.textRise = 0;
- this.fillColor = '#000000';
- this.strokeColor = '#000000';
- this.patternFill = false;
- this.fillAlpha = 1;
- this.strokeAlpha = 1;
- this.lineWidth = 1;
- this.activeSMask = null;
- this.resumeSMaskCtx = null;
- this.old = old;
- }
- CanvasExtraState.prototype = {
- clone: function CanvasExtraState_clone() {
- return Object.create(this);
- },
- setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
- this.x = x;
- this.y = y;
- }
- };
- return CanvasExtraState;
-}();
-var CanvasGraphics = function CanvasGraphicsClosure() {
- var EXECUTION_TIME = 15;
- var EXECUTION_STEPS = 10;
- function CanvasGraphics(canvasCtx, commonObjs, objs, canvasFactory, imageLayer) {
- this.ctx = canvasCtx;
- this.current = new CanvasExtraState();
- this.stateStack = [];
- this.pendingClip = null;
- this.pendingEOFill = false;
- this.res = null;
- this.xobjs = null;
- this.commonObjs = commonObjs;
- this.objs = objs;
- this.canvasFactory = canvasFactory;
- this.imageLayer = imageLayer;
- this.groupStack = [];
- this.processingType3 = null;
- this.baseTransform = null;
- this.baseTransformStack = [];
- this.groupLevel = 0;
- this.smaskStack = [];
- this.smaskCounter = 0;
- this.tempSMask = null;
- this.cachedCanvases = new CachedCanvases(this.canvasFactory);
- if (canvasCtx) {
- addContextCurrentTransform(canvasCtx);
- }
- this.cachedGetSinglePixelWidth = null;
- }
- function putBinaryImageData(ctx, imgData) {
- if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
- ctx.putImageData(imgData, 0, 0);
- return;
- }
- var height = imgData.height,
- width = imgData.width;
- var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
- var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
- var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
- var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
- var srcPos = 0,
- destPos;
- var src = imgData.data;
- var dest = chunkImgData.data;
- var i, j, thisChunkHeight, elemsInThisChunk;
- if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
- var srcLength = src.byteLength;
- var dest32 = HasCanvasTypedArraysCached.value ? new Uint32Array(dest.buffer) : new Uint32ArrayView(dest);
- var dest32DataLength = dest32.length;
- var fullSrcDiff = width + 7 >> 3;
- var white = 0xFFFFFFFF;
- var black = IsLittleEndianCached.value || !HasCanvasTypedArraysCached.value ? 0xFF000000 : 0x000000FF;
- for (i = 0; i < totalChunks; i++) {
- thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
- destPos = 0;
- for (j = 0; j < thisChunkHeight; j++) {
- var srcDiff = srcLength - srcPos;
- var k = 0;
- var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
- var kEndUnrolled = kEnd & ~7;
- var mask = 0;
- var srcByte = 0;
- for (; k < kEndUnrolled; k += 8) {
- srcByte = src[srcPos++];
- dest32[destPos++] = srcByte & 128 ? white : black;
- dest32[destPos++] = srcByte & 64 ? white : black;
- dest32[destPos++] = srcByte & 32 ? white : black;
- dest32[destPos++] = srcByte & 16 ? white : black;
- dest32[destPos++] = srcByte & 8 ? white : black;
- dest32[destPos++] = srcByte & 4 ? white : black;
- dest32[destPos++] = srcByte & 2 ? white : black;
- dest32[destPos++] = srcByte & 1 ? white : black;
- }
- for (; k < kEnd; k++) {
- if (mask === 0) {
- srcByte = src[srcPos++];
- mask = 128;
- }
- dest32[destPos++] = srcByte & mask ? white : black;
- mask >>= 1;
- }
- }
- while (destPos < dest32DataLength) {
- dest32[destPos++] = 0;
- }
- ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
- }
- } else if (imgData.kind === ImageKind.RGBA_32BPP) {
- j = 0;
- elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
- for (i = 0; i < fullChunks; i++) {
- dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
- srcPos += elemsInThisChunk;
- ctx.putImageData(chunkImgData, 0, j);
- j += FULL_CHUNK_HEIGHT;
- }
- if (i < totalChunks) {
- elemsInThisChunk = width * partialChunkHeight * 4;
- dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
- ctx.putImageData(chunkImgData, 0, j);
- }
- } else if (imgData.kind === ImageKind.RGB_24BPP) {
- thisChunkHeight = FULL_CHUNK_HEIGHT;
- elemsInThisChunk = width * thisChunkHeight;
- for (i = 0; i < totalChunks; i++) {
- if (i >= fullChunks) {
- thisChunkHeight = partialChunkHeight;
- elemsInThisChunk = width * thisChunkHeight;
- }
- destPos = 0;
- for (j = elemsInThisChunk; j--;) {
- dest[destPos++] = src[srcPos++];
- dest[destPos++] = src[srcPos++];
- dest[destPos++] = src[srcPos++];
- dest[destPos++] = 255;
- }
- ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
- }
- } else {
- error('bad image kind: ' + imgData.kind);
- }
- }
- function putBinaryImageMask(ctx, imgData) {
- var height = imgData.height,
- width = imgData.width;
- var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
- var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
- var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
- var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
- var srcPos = 0;
- var src = imgData.data;
- var dest = chunkImgData.data;
- for (var i = 0; i < totalChunks; i++) {
- var thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
- var destPos = 3;
- for (var j = 0; j < thisChunkHeight; j++) {
- var mask = 0;
- for (var k = 0; k < width; k++) {
- if (!mask) {
- var elem = src[srcPos++];
- mask = 128;
- }
- dest[destPos] = elem & mask ? 0 : 255;
- destPos += 4;
- mask >>= 1;
- }
- }
- ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
- }
- }
- function copyCtxState(sourceCtx, destCtx) {
- var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha', 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', 'globalCompositeOperation', 'font'];
- for (var i = 0, ii = properties.length; i < ii; i++) {
- var property = properties[i];
- if (sourceCtx[property] !== undefined) {
- destCtx[property] = sourceCtx[property];
- }
- }
- if (sourceCtx.setLineDash !== undefined) {
- destCtx.setLineDash(sourceCtx.getLineDash());
- destCtx.lineDashOffset = sourceCtx.lineDashOffset;
- }
- }
- function composeSMaskBackdrop(bytes, r0, g0, b0) {
- var length = bytes.length;
- for (var i = 3; i < length; i += 4) {
- var alpha = bytes[i];
- if (alpha === 0) {
- bytes[i - 3] = r0;
- bytes[i - 2] = g0;
- bytes[i - 1] = b0;
- } else if (alpha < 255) {
- var alpha_ = 255 - alpha;
- bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8;
- bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8;
- bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8;
- }
- }
- }
- function composeSMaskAlpha(maskData, layerData, transferMap) {
- var length = maskData.length;
- var scale = 1 / 255;
- for (var i = 3; i < length; i += 4) {
- var alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
- layerData[i] = layerData[i] * alpha * scale | 0;
- }
- }
- function composeSMaskLuminosity(maskData, layerData, transferMap) {
- var length = maskData.length;
- for (var i = 3; i < length; i += 4) {
- var y = maskData[i - 3] * 77 + maskData[i - 2] * 152 + maskData[i - 1] * 28;
- layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16;
- }
- }
- function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap) {
- var hasBackdrop = !!backdrop;
- var r0 = hasBackdrop ? backdrop[0] : 0;
- var g0 = hasBackdrop ? backdrop[1] : 0;
- var b0 = hasBackdrop ? backdrop[2] : 0;
- var composeFn;
- if (subtype === 'Luminosity') {
- composeFn = composeSMaskLuminosity;
- } else {
- composeFn = composeSMaskAlpha;
- }
- var PIXELS_TO_PROCESS = 1048576;
- var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
- for (var row = 0; row < height; row += chunkSize) {
- var chunkHeight = Math.min(chunkSize, height - row);
- var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
- var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
- if (hasBackdrop) {
- composeSMaskBackdrop(maskData.data, r0, g0, b0);
- }
- composeFn(maskData.data, layerData.data, transferMap);
- maskCtx.putImageData(layerData, 0, row);
- }
- }
- function composeSMask(ctx, smask, layerCtx) {
- var mask = smask.canvas;
- var maskCtx = smask.context;
- ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, smask.offsetX, smask.offsetY);
- var backdrop = smask.backdrop || null;
- if (!smask.transferMap && WebGLUtils.isEnabled) {
- var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, {
- subtype: smask.subtype,
- backdrop: backdrop
- });
- ctx.setTransform(1, 0, 0, 1, 0, 0);
- ctx.drawImage(composed, smask.offsetX, smask.offsetY);
- return;
- }
- genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, smask.subtype, backdrop, smask.transferMap);
- ctx.drawImage(mask, 0, 0);
- }
- var LINE_CAP_STYLES = ['butt', 'round', 'square'];
- var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
- var NORMAL_CLIP = {};
- var EO_CLIP = {};
- CanvasGraphics.prototype = {
- beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport, transparency) {
- var width = this.ctx.canvas.width;
- var height = this.ctx.canvas.height;
- this.ctx.save();
- this.ctx.fillStyle = 'rgb(255, 255, 255)';
- this.ctx.fillRect(0, 0, width, height);
- this.ctx.restore();
- if (transparency) {
- var transparentCanvas = this.cachedCanvases.getCanvas('transparent', width, height, true);
- this.compositeCtx = this.ctx;
- this.transparentCanvas = transparentCanvas.canvas;
- this.ctx = transparentCanvas.context;
- this.ctx.save();
- this.ctx.transform.apply(this.ctx, this.compositeCtx.mozCurrentTransform);
- }
- this.ctx.save();
- if (transform) {
- this.ctx.transform.apply(this.ctx, transform);
- }
- this.ctx.transform.apply(this.ctx, viewport.transform);
- this.baseTransform = this.ctx.mozCurrentTransform.slice();
- if (this.imageLayer) {
- this.imageLayer.beginLayout();
- }
- },
- executeOperatorList: function CanvasGraphics_executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
- var argsArray = operatorList.argsArray;
- var fnArray = operatorList.fnArray;
- var i = executionStartIdx || 0;
- var argsArrayLen = argsArray.length;
- if (argsArrayLen === i) {
- return i;
- }
- var chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === 'function';
- var endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
- var steps = 0;
- var commonObjs = this.commonObjs;
- var objs = this.objs;
- var fnId;
- while (true) {
- if (stepper !== undefined && i === stepper.nextBreakPoint) {
- stepper.breakIt(i, continueCallback);
- return i;
- }
- fnId = fnArray[i];
- if (fnId !== OPS.dependency) {
- this[fnId].apply(this, argsArray[i]);
- } else {
- var deps = argsArray[i];
- for (var n = 0, nn = deps.length; n < nn; n++) {
- var depObjId = deps[n];
- var common = depObjId[0] === 'g' && depObjId[1] === '_';
- var objsPool = common ? commonObjs : objs;
- if (!objsPool.isResolved(depObjId)) {
- objsPool.get(depObjId, continueCallback);
- return i;
- }
- }
- }
- i++;
- if (i === argsArrayLen) {
- return i;
- }
- if (chunkOperations && ++steps > EXECUTION_STEPS) {
- if (Date.now() > endTime) {
- continueCallback();
- return i;
- }
- steps = 0;
- }
- }
- },
- endDrawing: function CanvasGraphics_endDrawing() {
- if (this.current.activeSMask !== null) {
- this.endSMaskGroup();
- }
- this.ctx.restore();
- if (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();
- WebGLUtils.clear();
- if (this.imageLayer) {
- this.imageLayer.endLayout();
- }
- },
- setLineWidth: function CanvasGraphics_setLineWidth(width) {
- this.current.lineWidth = width;
- this.ctx.lineWidth = width;
- },
- setLineCap: function CanvasGraphics_setLineCap(style) {
- this.ctx.lineCap = LINE_CAP_STYLES[style];
- },
- setLineJoin: function CanvasGraphics_setLineJoin(style) {
- this.ctx.lineJoin = LINE_JOIN_STYLES[style];
- },
- setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
- this.ctx.miterLimit = limit;
- },
- setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
- var ctx = this.ctx;
- if (ctx.setLineDash !== undefined) {
- ctx.setLineDash(dashArray);
- ctx.lineDashOffset = dashPhase;
- }
- },
- setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {},
- setFlatness: function CanvasGraphics_setFlatness(flatness) {},
- setGState: function CanvasGraphics_setGState(states) {
- for (var i = 0, ii = states.length; i < ii; i++) {
- var state = states[i];
- var key = state[0];
- var value = state[1];
- switch (key) {
- case 'LW':
- this.setLineWidth(value);
- break;
- case 'LC':
- this.setLineCap(value);
- break;
- case 'LJ':
- this.setLineJoin(value);
- break;
- case 'ML':
- this.setMiterLimit(value);
- break;
- case 'D':
- this.setDash(value[0], value[1]);
- break;
- case 'RI':
- this.setRenderingIntent(value);
- break;
- case 'FL':
- this.setFlatness(value);
- break;
- case 'Font':
- this.setFont(value[0], value[1]);
- break;
- case 'CA':
- this.current.strokeAlpha = state[1];
- break;
- case 'ca':
- this.current.fillAlpha = state[1];
- this.ctx.globalAlpha = state[1];
- break;
- case 'BM':
- if (value && value.name && value.name !== 'Normal') {
- var mode = value.name.replace(/([A-Z])/g, function (c) {
- return '-' + c.toLowerCase();
- }).substring(1);
- this.ctx.globalCompositeOperation = mode;
- if (this.ctx.globalCompositeOperation !== mode) {
- warn('globalCompositeOperation "' + mode + '" is not supported');
- }
- } else {
- this.ctx.globalCompositeOperation = 'source-over';
- }
- break;
- case 'SMask':
- if (this.current.activeSMask) {
- if (this.stateStack.length > 0 && this.stateStack[this.stateStack.length - 1].activeSMask === this.current.activeSMask) {
- this.suspendSMaskGroup();
- } else {
- this.endSMaskGroup();
- }
- }
- this.current.activeSMask = value ? this.tempSMask : null;
- if (this.current.activeSMask) {
- this.beginSMaskGroup();
- }
- this.tempSMask = null;
- break;
- }
- }
- },
- beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
- var activeSMask = this.current.activeSMask;
- var drawnWidth = activeSMask.canvas.width;
- var drawnHeight = activeSMask.canvas.height;
- var cacheId = 'smaskGroupAt' + this.groupLevel;
- var scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
- var currentCtx = this.ctx;
- var currentTransform = currentCtx.mozCurrentTransform;
- this.ctx.save();
- var groupCtx = scratchCanvas.context;
- groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
- groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
- groupCtx.transform.apply(groupCtx, currentTransform);
- activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
- copyCtxState(currentCtx, groupCtx);
- this.ctx = groupCtx;
- this.setGState([['BM', 'Normal'], ['ca', 1], ['CA', 1]]);
- this.groupStack.push(currentCtx);
- this.groupLevel++;
- },
- suspendSMaskGroup: function CanvasGraphics_endSMaskGroup() {
- var groupCtx = this.ctx;
- this.groupLevel--;
- this.ctx = this.groupStack.pop();
- composeSMask(this.ctx, this.current.activeSMask, groupCtx);
- this.ctx.restore();
- this.ctx.save();
- copyCtxState(groupCtx, this.ctx);
- this.current.resumeSMaskCtx = groupCtx;
- var deltaTransform = Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
- this.ctx.transform.apply(this.ctx, deltaTransform);
- groupCtx.save();
- groupCtx.setTransform(1, 0, 0, 1, 0, 0);
- groupCtx.clearRect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
- groupCtx.restore();
- },
- resumeSMaskGroup: function CanvasGraphics_endSMaskGroup() {
- var groupCtx = this.current.resumeSMaskCtx;
- var currentCtx = this.ctx;
- this.ctx = groupCtx;
- this.groupStack.push(currentCtx);
- this.groupLevel++;
- },
- endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
- var groupCtx = this.ctx;
- this.groupLevel--;
- this.ctx = this.groupStack.pop();
- composeSMask(this.ctx, this.current.activeSMask, groupCtx);
- this.ctx.restore();
- copyCtxState(groupCtx, this.ctx);
- var deltaTransform = Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
- this.ctx.transform.apply(this.ctx, deltaTransform);
- },
- save: function CanvasGraphics_save() {
- this.ctx.save();
- var old = this.current;
- this.stateStack.push(old);
- this.current = old.clone();
- this.current.resumeSMaskCtx = null;
- },
- restore: function CanvasGraphics_restore() {
- if (this.current.resumeSMaskCtx) {
- this.resumeSMaskGroup();
- }
- if (this.current.activeSMask !== null && (this.stateStack.length === 0 || this.stateStack[this.stateStack.length - 1].activeSMask !== this.current.activeSMask)) {
- this.endSMaskGroup();
- }
- if (this.stateStack.length !== 0) {
- this.current = this.stateStack.pop();
- this.ctx.restore();
- this.pendingClip = null;
- this.cachedGetSinglePixelWidth = null;
- }
- },
- transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
- this.ctx.transform(a, b, c, d, e, f);
- this.cachedGetSinglePixelWidth = null;
- },
- constructPath: function CanvasGraphics_constructPath(ops, args) {
- var ctx = this.ctx;
- var current = this.current;
- var x = current.x,
- y = current.y;
- for (var i = 0, j = 0, ii = ops.length; i < ii; i++) {
- switch (ops[i] | 0) {
- case OPS.rectangle:
- x = args[j++];
- y = args[j++];
- var width = args[j++];
- var height = args[j++];
- if (width === 0) {
- width = this.getSinglePixelWidth();
- }
- if (height === 0) {
- height = this.getSinglePixelWidth();
- }
- var xw = x + width;
- var yh = y + height;
- this.ctx.moveTo(x, y);
- this.ctx.lineTo(xw, y);
- this.ctx.lineTo(xw, yh);
- this.ctx.lineTo(x, yh);
- this.ctx.lineTo(x, y);
- this.ctx.closePath();
- break;
- case OPS.moveTo:
- x = args[j++];
- y = args[j++];
- ctx.moveTo(x, y);
- break;
- case OPS.lineTo:
- x = args[j++];
- y = args[j++];
- ctx.lineTo(x, y);
- break;
- case OPS.curveTo:
- x = args[j + 4];
- y = args[j + 5];
- ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
- j += 6;
- break;
- case OPS.curveTo2:
- ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
- x = args[j + 2];
- y = args[j + 3];
- j += 4;
- break;
- case OPS.curveTo3:
- x = args[j + 2];
- y = args[j + 3];
- ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
- j += 4;
- break;
- case OPS.closePath:
- ctx.closePath();
- break;
- }
- }
- current.setCurrentPoint(x, y);
- },
- closePath: function CanvasGraphics_closePath() {
- this.ctx.closePath();
- },
- stroke: function CanvasGraphics_stroke(consumePath) {
- consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
- var ctx = this.ctx;
- var strokeColor = this.current.strokeColor;
- ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR, this.current.lineWidth);
- ctx.globalAlpha = this.current.strokeAlpha;
- if (strokeColor && strokeColor.hasOwnProperty('type') && strokeColor.type === 'Pattern') {
- ctx.save();
- ctx.strokeStyle = strokeColor.getPattern(ctx, this);
- ctx.stroke();
- ctx.restore();
- } else {
- ctx.stroke();
- }
- if (consumePath) {
- this.consumePath();
- }
- ctx.globalAlpha = this.current.fillAlpha;
- },
- closeStroke: function CanvasGraphics_closeStroke() {
- this.closePath();
- this.stroke();
- },
- fill: function CanvasGraphics_fill(consumePath) {
- consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
- var ctx = this.ctx;
- var fillColor = this.current.fillColor;
- var isPatternFill = this.current.patternFill;
- var needRestore = false;
- if (isPatternFill) {
- ctx.save();
- if (this.baseTransform) {
- ctx.setTransform.apply(ctx, this.baseTransform);
- }
- ctx.fillStyle = fillColor.getPattern(ctx, this);
- needRestore = true;
- }
- if (this.pendingEOFill) {
- ctx.fill('evenodd');
- this.pendingEOFill = false;
- } else {
- ctx.fill();
- }
- if (needRestore) {
- ctx.restore();
- }
- if (consumePath) {
- this.consumePath();
- }
- },
- eoFill: function CanvasGraphics_eoFill() {
- this.pendingEOFill = true;
- this.fill();
- },
- fillStroke: function CanvasGraphics_fillStroke() {
- this.fill(false);
- this.stroke(false);
- this.consumePath();
- },
- eoFillStroke: function CanvasGraphics_eoFillStroke() {
- this.pendingEOFill = true;
- this.fillStroke();
- },
- closeFillStroke: function CanvasGraphics_closeFillStroke() {
- this.closePath();
- this.fillStroke();
- },
- closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
- this.pendingEOFill = true;
- this.closePath();
- this.fillStroke();
- },
- endPath: function CanvasGraphics_endPath() {
- this.consumePath();
- },
- clip: function CanvasGraphics_clip() {
- this.pendingClip = NORMAL_CLIP;
- },
- eoClip: function CanvasGraphics_eoClip() {
- this.pendingClip = EO_CLIP;
- },
- beginText: function CanvasGraphics_beginText() {
- this.current.textMatrix = IDENTITY_MATRIX;
- this.current.textMatrixScale = 1;
- this.current.x = this.current.lineX = 0;
- this.current.y = this.current.lineY = 0;
- },
- endText: function CanvasGraphics_endText() {
- var paths = this.pendingTextPaths;
- var ctx = this.ctx;
- if (paths === undefined) {
- ctx.beginPath();
- return;
- }
- ctx.save();
- ctx.beginPath();
- for (var i = 0; i < paths.length; i++) {
- var path = paths[i];
- ctx.setTransform.apply(ctx, path.transform);
- ctx.translate(path.x, path.y);
- path.addToPath(ctx, path.fontSize);
- }
- ctx.restore();
- ctx.clip();
- ctx.beginPath();
- delete this.pendingTextPaths;
- },
- setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
- this.current.charSpacing = spacing;
- },
- setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
- this.current.wordSpacing = spacing;
- },
- setHScale: function CanvasGraphics_setHScale(scale) {
- this.current.textHScale = scale / 100;
- },
- setLeading: function CanvasGraphics_setLeading(leading) {
- this.current.leading = -leading;
- },
- setFont: function CanvasGraphics_setFont(fontRefName, size) {
- var fontObj = this.commonObjs.get(fontRefName);
- var current = this.current;
- if (!fontObj) {
- error('Can\'t find font for ' + fontRefName);
- }
- current.fontMatrix = fontObj.fontMatrix ? fontObj.fontMatrix : FONT_IDENTITY_MATRIX;
- if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
- warn('Invalid font matrix for font ' + fontRefName);
- }
- if (size < 0) {
- size = -size;
- current.fontDirection = -1;
- } else {
- current.fontDirection = 1;
- }
- this.current.font = fontObj;
- this.current.fontSize = size;
- if (fontObj.isType3Font) {
- return;
- }
- var name = fontObj.loadedName || 'sans-serif';
- var bold = fontObj.black ? '900' : fontObj.bold ? 'bold' : 'normal';
- var italic = fontObj.italic ? 'italic' : 'normal';
- var typeface = '"' + name + '", ' + fontObj.fallbackName;
- var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE : size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size;
- this.current.fontSizeScale = size / browserFontSize;
- var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
- this.ctx.font = rule;
- },
- setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
- this.current.textRenderingMode = mode;
- },
- setTextRise: function CanvasGraphics_setTextRise(rise) {
- this.current.textRise = rise;
- },
- moveText: function CanvasGraphics_moveText(x, y) {
- this.current.x = this.current.lineX += x;
- this.current.y = this.current.lineY += y;
- },
- setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
- this.setLeading(-y);
- this.moveText(x, y);
- },
- setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
- this.current.textMatrix = [a, b, c, d, e, f];
- this.current.textMatrixScale = Math.sqrt(a * a + b * b);
- this.current.x = this.current.lineX = 0;
- this.current.y = this.current.lineY = 0;
- },
- nextLine: function CanvasGraphics_nextLine() {
- this.moveText(0, this.current.leading);
- },
- paintChar: function CanvasGraphics_paintChar(character, x, y) {
- var ctx = this.ctx;
- var current = this.current;
- var font = current.font;
- var textRenderingMode = current.textRenderingMode;
- var fontSize = current.fontSize / current.fontSizeScale;
- var fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
- var isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
- var addToPath;
- if (font.disableFontFace || isAddToPathSet) {
- addToPath = font.getPathGenerator(this.commonObjs, character);
- }
- if (font.disableFontFace) {
- ctx.save();
- ctx.translate(x, y);
- ctx.beginPath();
- addToPath(ctx, fontSize);
- if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
- ctx.fill();
- }
- if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
- ctx.stroke();
- }
- ctx.restore();
- } else {
- if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
- ctx.fillText(character, x, y);
- }
- if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
- ctx.strokeText(character, x, y);
- }
- }
- if (isAddToPathSet) {
- var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
- paths.push({
- transform: ctx.mozCurrentTransform,
- x: x,
- y: y,
- fontSize: fontSize,
- addToPath: addToPath
- });
- }
- },
- get isFontSubpixelAAEnabled() {
- var ctx = this.canvasFactory.create(10, 10).context;
- ctx.scale(1.5, 1);
- ctx.fillText('I', 0, 10);
- var data = ctx.getImageData(0, 0, 10, 10).data;
- var enabled = false;
- for (var i = 3; i < data.length; i += 4) {
- if (data[i] > 0 && data[i] < 255) {
- enabled = true;
- break;
- }
- }
- return shadow(this, 'isFontSubpixelAAEnabled', enabled);
- },
- showText: function CanvasGraphics_showText(glyphs) {
- var current = this.current;
- var font = current.font;
- if (font.isType3Font) {
- return this.showType3Text(glyphs);
- }
- var fontSize = current.fontSize;
- if (fontSize === 0) {
- return;
- }
- var ctx = this.ctx;
- var fontSizeScale = current.fontSizeScale;
- var charSpacing = current.charSpacing;
- var wordSpacing = current.wordSpacing;
- var fontDirection = current.fontDirection;
- var textHScale = current.textHScale * fontDirection;
- var glyphsLength = glyphs.length;
- var vertical = font.vertical;
- var spacingDir = vertical ? 1 : -1;
- var defaultVMetrics = font.defaultVMetrics;
- var widthAdvanceScale = fontSize * current.fontMatrix[0];
- var simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace;
- ctx.save();
- ctx.transform.apply(ctx, current.textMatrix);
- ctx.translate(current.x, current.y + current.textRise);
- if (current.patternFill) {
- ctx.fillStyle = current.fillColor.getPattern(ctx, this);
- }
- if (fontDirection > 0) {
- ctx.scale(textHScale, -1);
- } else {
- ctx.scale(textHScale, 1);
- }
- var lineWidth = current.lineWidth;
- var scale = current.textMatrixScale;
- if (scale === 0 || lineWidth === 0) {
- var fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
- if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
- this.cachedGetSinglePixelWidth = null;
- lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
- }
- } else {
- lineWidth /= scale;
- }
- if (fontSizeScale !== 1.0) {
- ctx.scale(fontSizeScale, fontSizeScale);
- lineWidth /= fontSizeScale;
- }
- ctx.lineWidth = lineWidth;
- var x = 0,
- i;
- for (i = 0; i < glyphsLength; ++i) {
- var glyph = glyphs[i];
- if (isNum(glyph)) {
- x += spacingDir * glyph * fontSize / 1000;
- continue;
- }
- var restoreNeeded = false;
- var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
- var character = glyph.fontChar;
- var accent = glyph.accent;
- var scaledX, scaledY, scaledAccentX, scaledAccentY;
- var width = glyph.width;
- if (vertical) {
- var vmetric, vx, vy;
- vmetric = glyph.vmetric || defaultVMetrics;
- vx = glyph.vmetric ? vmetric[1] : width * 0.5;
- vx = -vx * widthAdvanceScale;
- vy = vmetric[2] * widthAdvanceScale;
- width = vmetric ? -vmetric[0] : width;
- scaledX = vx / fontSizeScale;
- scaledY = (x + vy) / fontSizeScale;
- } else {
- scaledX = x / fontSizeScale;
- scaledY = 0;
- }
- if (font.remeasure && width > 0) {
- var measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
- if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
- var characterScaleX = width / measuredWidth;
- restoreNeeded = true;
- ctx.save();
- ctx.scale(characterScaleX, 1);
- scaledX /= characterScaleX;
- } else if (width !== measuredWidth) {
- scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
- }
- }
- if (glyph.isInFont || font.missingFile) {
- if (simpleFillText && !accent) {
- ctx.fillText(character, scaledX, scaledY);
- } else {
- this.paintChar(character, scaledX, scaledY);
- if (accent) {
- scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
- scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
- this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
- }
- }
- }
- var charWidth = width * widthAdvanceScale + spacing * fontDirection;
- x += charWidth;
- if (restoreNeeded) {
- ctx.restore();
- }
- }
- if (vertical) {
- current.y -= x * textHScale;
- } else {
- current.x += x * textHScale;
- }
- ctx.restore();
- },
- showType3Text: function CanvasGraphics_showType3Text(glyphs) {
- var ctx = this.ctx;
- var current = this.current;
- var font = current.font;
- var fontSize = current.fontSize;
- var fontDirection = current.fontDirection;
- var spacingDir = font.vertical ? 1 : -1;
- var charSpacing = current.charSpacing;
- var wordSpacing = current.wordSpacing;
- var textHScale = current.textHScale * fontDirection;
- var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
- var glyphsLength = glyphs.length;
- var isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
- var i, glyph, width, spacingLength;
- if (isTextInvisible || fontSize === 0) {
- return;
- }
- this.cachedGetSinglePixelWidth = null;
- ctx.save();
- ctx.transform.apply(ctx, current.textMatrix);
- ctx.translate(current.x, current.y);
- ctx.scale(textHScale, fontDirection);
- for (i = 0; i < glyphsLength; ++i) {
- glyph = glyphs[i];
- if (isNum(glyph)) {
- spacingLength = spacingDir * glyph * fontSize / 1000;
- this.ctx.translate(spacingLength, 0);
- current.x += spacingLength * textHScale;
- continue;
- }
- var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
- var operatorList = font.charProcOperatorList[glyph.operatorListId];
- if (!operatorList) {
- warn('Type3 character \"' + glyph.operatorListId + '\" is not available');
- continue;
- }
- this.processingType3 = glyph;
- this.save();
- ctx.scale(fontSize, fontSize);
- ctx.transform.apply(ctx, fontMatrix);
- this.executeOperatorList(operatorList);
- this.restore();
- var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
- width = transformed[0] * fontSize + spacing;
- ctx.translate(width, 0);
- current.x += width * textHScale;
- }
- ctx.restore();
- this.processingType3 = null;
- },
- setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {},
- setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
- this.ctx.rect(llx, lly, urx - llx, ury - lly);
- this.clip();
- this.endPath();
- },
- getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
- var pattern;
- if (IR[0] === 'TilingPattern') {
- var color = IR[1];
- var baseTransform = this.baseTransform || this.ctx.mozCurrentTransform.slice();
- var self = this;
- var canvasGraphicsFactory = {
- createCanvasGraphics: function (ctx) {
- return new CanvasGraphics(ctx, self.commonObjs, self.objs, self.canvasFactory);
- }
- };
- pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
- } else {
- pattern = getShadingPatternFromIR(IR);
- }
- return pattern;
- },
- setStrokeColorN: function CanvasGraphics_setStrokeColorN() {
- this.current.strokeColor = this.getColorN_Pattern(arguments);
- },
- setFillColorN: function CanvasGraphics_setFillColorN() {
- this.current.fillColor = this.getColorN_Pattern(arguments);
- this.current.patternFill = true;
- },
- setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
- var color = Util.makeCssRgb(r, g, b);
- this.ctx.strokeStyle = color;
- this.current.strokeColor = color;
- },
- setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
- var color = Util.makeCssRgb(r, g, b);
- this.ctx.fillStyle = color;
- this.current.fillColor = color;
- this.current.patternFill = false;
- },
- shadingFill: function CanvasGraphics_shadingFill(patternIR) {
- var ctx = this.ctx;
- this.save();
- var pattern = getShadingPatternFromIR(patternIR);
- ctx.fillStyle = pattern.getPattern(ctx, this, true);
- var inv = ctx.mozCurrentTransformInverse;
- if (inv) {
- var canvas = ctx.canvas;
- var width = canvas.width;
- var height = canvas.height;
- var bl = Util.applyTransform([0, 0], inv);
- var br = Util.applyTransform([0, height], inv);
- var ul = Util.applyTransform([width, 0], inv);
- var ur = Util.applyTransform([width, height], inv);
- var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
- var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
- var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
- var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
- this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
- } else {
- this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
- }
- this.restore();
- },
- beginInlineImage: function CanvasGraphics_beginInlineImage() {
- error('Should not call beginInlineImage');
- },
- beginImageData: function CanvasGraphics_beginImageData() {
- error('Should not call beginImageData');
- },
- paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix, bbox) {
- this.save();
- this.baseTransformStack.push(this.baseTransform);
- if (isArray(matrix) && matrix.length === 6) {
- this.transform.apply(this, matrix);
- }
- this.baseTransform = this.ctx.mozCurrentTransform;
- if (isArray(bbox) && bbox.length === 4) {
- var width = bbox[2] - bbox[0];
- var height = bbox[3] - bbox[1];
- this.ctx.rect(bbox[0], bbox[1], width, height);
- this.clip();
- this.endPath();
- }
- },
- paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
- this.restore();
- this.baseTransform = this.baseTransformStack.pop();
- },
- beginGroup: function CanvasGraphics_beginGroup(group) {
- this.save();
- var currentCtx = this.ctx;
- if (!group.isolated) {
- info('TODO: Support non-isolated groups.');
- }
- if (group.knockout) {
- warn('Knockout groups not supported.');
- }
- var currentTransform = currentCtx.mozCurrentTransform;
- if (group.matrix) {
- currentCtx.transform.apply(currentCtx, group.matrix);
- }
- assert(group.bbox, 'Bounding box is required.');
- var bounds = Util.getAxialAlignedBoundingBox(group.bbox, currentCtx.mozCurrentTransform);
- var canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
- bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
- var offsetX = Math.floor(bounds[0]);
- var offsetY = Math.floor(bounds[1]);
- var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
- var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
- var scaleX = 1,
- scaleY = 1;
- if (drawnWidth > MAX_GROUP_SIZE) {
- scaleX = drawnWidth / MAX_GROUP_SIZE;
- drawnWidth = MAX_GROUP_SIZE;
- }
- if (drawnHeight > MAX_GROUP_SIZE) {
- scaleY = drawnHeight / MAX_GROUP_SIZE;
- drawnHeight = MAX_GROUP_SIZE;
- }
- var cacheId = 'groupAt' + this.groupLevel;
- if (group.smask) {
- cacheId += '_smask_' + this.smaskCounter++ % 2;
- }
- var scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
- var groupCtx = scratchCanvas.context;
- groupCtx.scale(1 / scaleX, 1 / scaleY);
- groupCtx.translate(-offsetX, -offsetY);
- groupCtx.transform.apply(groupCtx, currentTransform);
- if (group.smask) {
- this.smaskStack.push({
- canvas: scratchCanvas.canvas,
- context: groupCtx,
- offsetX: offsetX,
- offsetY: offsetY,
- scaleX: scaleX,
- scaleY: scaleY,
- subtype: group.smask.subtype,
- backdrop: group.smask.backdrop,
- transferMap: group.smask.transferMap || null,
- startTransformInverse: null
- });
- } else {
- currentCtx.setTransform(1, 0, 0, 1, 0, 0);
- currentCtx.translate(offsetX, offsetY);
- currentCtx.scale(scaleX, scaleY);
- }
- copyCtxState(currentCtx, groupCtx);
- this.ctx = groupCtx;
- this.setGState([['BM', 'Normal'], ['ca', 1], ['CA', 1]]);
- this.groupStack.push(currentCtx);
- this.groupLevel++;
- this.current.activeSMask = null;
- },
- endGroup: function CanvasGraphics_endGroup(group) {
- this.groupLevel--;
- var groupCtx = this.ctx;
- this.ctx = this.groupStack.pop();
- if (this.ctx.imageSmoothingEnabled !== undefined) {
- this.ctx.imageSmoothingEnabled = false;
- } else {
- this.ctx.mozImageSmoothingEnabled = false;
- }
- if (group.smask) {
- this.tempSMask = this.smaskStack.pop();
- } else {
- this.ctx.drawImage(groupCtx.canvas, 0, 0);
- }
- this.restore();
- },
- beginAnnotations: function CanvasGraphics_beginAnnotations() {
- this.save();
- this.current = new CanvasExtraState();
- if (this.baseTransform) {
- this.ctx.setTransform.apply(this.ctx, this.baseTransform);
- }
- },
- endAnnotations: function CanvasGraphics_endAnnotations() {
- this.restore();
- },
- beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, matrix) {
- this.save();
- if (isArray(rect) && rect.length === 4) {
- var width = rect[2] - rect[0];
- var height = rect[3] - rect[1];
- this.ctx.rect(rect[0], rect[1], width, height);
- this.clip();
- this.endPath();
- }
- this.transform.apply(this, transform);
- this.transform.apply(this, matrix);
- },
- endAnnotation: function CanvasGraphics_endAnnotation() {
- this.restore();
- },
- paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
- var domImage = this.objs.get(objId);
- if (!domImage) {
- warn('Dependent image isn\'t ready yet');
- return;
- }
- this.save();
- var ctx = this.ctx;
- ctx.scale(1 / w, -1 / h);
- ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h);
- if (this.imageLayer) {
- var currentTransform = ctx.mozCurrentTransformInverse;
- var position = this.getCanvasPosition(0, 0);
- this.imageLayer.appendImage({
- objId: objId,
- left: position[0],
- top: position[1],
- width: w / currentTransform[0],
- height: h / currentTransform[3]
- });
- }
- this.restore();
- },
- paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
- var ctx = this.ctx;
- var width = img.width,
- height = img.height;
- var fillColor = this.current.fillColor;
- var isPatternFill = this.current.patternFill;
- var glyph = this.processingType3;
- if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
- if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
- glyph.compiled = compileType3Glyph({
- data: img.data,
- width: width,
- height: height
- });
- } else {
- glyph.compiled = null;
- }
- }
- if (glyph && glyph.compiled) {
- glyph.compiled(ctx);
- return;
- }
- var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
- var maskCtx = maskCanvas.context;
- maskCtx.save();
- putBinaryImageMask(maskCtx, img);
- maskCtx.globalCompositeOperation = 'source-in';
- maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
- maskCtx.fillRect(0, 0, width, height);
- maskCtx.restore();
- this.paintInlineImageXObject(maskCanvas.canvas);
- },
- paintImageMaskXObjectRepeat: function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX, scaleY, positions) {
- var width = imgData.width;
- var height = imgData.height;
- var fillColor = this.current.fillColor;
- var isPatternFill = this.current.patternFill;
- var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
- var maskCtx = maskCanvas.context;
- maskCtx.save();
- putBinaryImageMask(maskCtx, imgData);
- maskCtx.globalCompositeOperation = 'source-in';
- maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
- maskCtx.fillRect(0, 0, width, height);
- maskCtx.restore();
- var ctx = this.ctx;
- for (var i = 0, ii = positions.length; i < ii; i += 2) {
- ctx.save();
- ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
- ctx.scale(1, -1);
- ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
- ctx.restore();
- }
- },
- paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup(images) {
- var ctx = this.ctx;
- var fillColor = this.current.fillColor;
- var isPatternFill = this.current.patternFill;
- for (var i = 0, ii = images.length; i < ii; i++) {
- var image = images[i];
- var width = image.width,
- height = image.height;
- var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
- var maskCtx = maskCanvas.context;
- maskCtx.save();
- putBinaryImageMask(maskCtx, image);
- maskCtx.globalCompositeOperation = 'source-in';
- maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
- maskCtx.fillRect(0, 0, width, height);
- maskCtx.restore();
- ctx.save();
- ctx.transform.apply(ctx, image.transform);
- ctx.scale(1, -1);
- ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
- ctx.restore();
- }
- },
- paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
- var imgData = this.objs.get(objId);
- if (!imgData) {
- warn('Dependent image isn\'t ready yet');
- return;
- }
- this.paintInlineImageXObject(imgData);
- },
- paintImageXObjectRepeat: function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
- var imgData = this.objs.get(objId);
- if (!imgData) {
- warn('Dependent image isn\'t ready yet');
- return;
- }
- var width = imgData.width;
- var height = imgData.height;
- var map = [];
- for (var i = 0, ii = positions.length; i < ii; i += 2) {
- map.push({
- transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
- x: 0,
- y: 0,
- w: width,
- h: height
- });
- }
- this.paintInlineImageXObjectGroup(imgData, map);
- },
- paintInlineImageXObject: function CanvasGraphics_paintInlineImageXObject(imgData) {
- var width = imgData.width;
- var height = imgData.height;
- var ctx = this.ctx;
- this.save();
- ctx.scale(1 / width, -1 / height);
- var currentTransform = ctx.mozCurrentTransformInverse;
- var a = currentTransform[0],
- b = currentTransform[1];
- var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
- var c = currentTransform[2],
- d = currentTransform[3];
- var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
- var imgToPaint, tmpCanvas;
- if (imgData instanceof HTMLElement || !imgData.data) {
- imgToPaint = imgData;
- } else {
- tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', width, height);
- var tmpCtx = tmpCanvas.context;
- putBinaryImageData(tmpCtx, imgData);
- imgToPaint = tmpCanvas.canvas;
- }
- var paintWidth = width,
- paintHeight = height;
- var tmpCanvasId = 'prescale1';
- while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
- var newWidth = paintWidth,
- newHeight = paintHeight;
- if (widthScale > 2 && paintWidth > 1) {
- newWidth = Math.ceil(paintWidth / 2);
- widthScale /= paintWidth / newWidth;
- }
- if (heightScale > 2 && paintHeight > 1) {
- newHeight = Math.ceil(paintHeight / 2);
- heightScale /= paintHeight / newHeight;
- }
- tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
- tmpCtx = tmpCanvas.context;
- tmpCtx.clearRect(0, 0, newWidth, newHeight);
- tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
- imgToPaint = tmpCanvas.canvas;
- paintWidth = newWidth;
- paintHeight = newHeight;
- tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
- }
- ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, 0, -height, width, height);
- if (this.imageLayer) {
- var position = this.getCanvasPosition(0, -height);
- this.imageLayer.appendImage({
- imgData: imgData,
- left: position[0],
- top: position[1],
- width: width / currentTransform[0],
- height: height / currentTransform[3]
- });
- }
- this.restore();
- },
- paintInlineImageXObjectGroup: function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
- var ctx = this.ctx;
- var w = imgData.width;
- var h = imgData.height;
- var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h);
- var tmpCtx = tmpCanvas.context;
- putBinaryImageData(tmpCtx, imgData);
- for (var i = 0, ii = map.length; i < ii; i++) {
- var entry = map[i];
- ctx.save();
- ctx.transform.apply(ctx, entry.transform);
- ctx.scale(1, -1);
- ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
- if (this.imageLayer) {
- var position = this.getCanvasPosition(entry.x, entry.y);
- this.imageLayer.appendImage({
- imgData: imgData,
- left: position[0],
- top: position[1],
- width: w,
- height: h
- });
- }
- ctx.restore();
- }
- },
- paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() {
- this.ctx.fillRect(0, 0, 1, 1);
- },
- paintXObject: function CanvasGraphics_paintXObject() {
- warn('Unsupported \'paintXObject\' command.');
- },
- markPoint: function CanvasGraphics_markPoint(tag) {},
- markPointProps: function CanvasGraphics_markPointProps(tag, properties) {},
- beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {},
- beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(tag, properties) {},
- endMarkedContent: function CanvasGraphics_endMarkedContent() {},
- beginCompat: function CanvasGraphics_beginCompat() {},
- endCompat: function CanvasGraphics_endCompat() {},
- consumePath: function CanvasGraphics_consumePath() {
- var ctx = this.ctx;
- if (this.pendingClip) {
- if (this.pendingClip === EO_CLIP) {
- ctx.clip('evenodd');
- } else {
- ctx.clip();
- }
- this.pendingClip = null;
- }
- ctx.beginPath();
- },
- getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
- if (this.cachedGetSinglePixelWidth === null) {
- this.ctx.save();
- var inverse = this.ctx.mozCurrentTransformInverse;
- this.ctx.restore();
- this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(inverse[0] * inverse[0] + inverse[1] * inverse[1], inverse[2] * inverse[2] + inverse[3] * inverse[3]));
- }
- return this.cachedGetSinglePixelWidth;
- },
- getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
- var transform = this.ctx.mozCurrentTransform;
- return [transform[0] * x + transform[2] * y + transform[4], transform[1] * x + transform[3] * y + transform[5]];
- }
- };
- for (var op in OPS) {
- CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
- }
- return CanvasGraphics;
-}();
-exports.CanvasGraphics = CanvasGraphics;
-
-/***/ }),
-/* 11 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var assert = sharedUtil.assert;
-var bytesToString = sharedUtil.bytesToString;
-var string32 = sharedUtil.string32;
-var shadow = sharedUtil.shadow;
-var warn = sharedUtil.warn;
-function FontLoader(docId) {
- this.docId = docId;
- this.styleElement = null;
- this.nativeFontFaces = [];
- this.loadTestFontId = 0;
- this.loadingContext = {
- requests: [],
- nextRequestId: 0
- };
-}
-FontLoader.prototype = {
- insertRule: function fontLoaderInsertRule(rule) {
- var styleElement = this.styleElement;
- if (!styleElement) {
- styleElement = this.styleElement = document.createElement('style');
- styleElement.id = 'PDFJS_FONT_STYLE_TAG_' + this.docId;
- document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);
- }
- var styleSheet = styleElement.sheet;
- styleSheet.insertRule(rule, styleSheet.cssRules.length);
- },
- clear: function fontLoaderClear() {
- if (this.styleElement) {
- this.styleElement.remove();
- this.styleElement = null;
- }
- this.nativeFontFaces.forEach(function (nativeFontFace) {
- document.fonts.delete(nativeFontFace);
- });
- this.nativeFontFaces.length = 0;
- }
-};
-var getLoadTestFont = function () {
- return atob('T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQ' + 'AABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwA' + 'AAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbm' + 'FtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAA' + 'AADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6A' + 'ABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAA' + 'MQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAA' + 'AAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAA' + 'AAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQ' + 'AAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMA' + 'AQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAA' + 'EAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAA' + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAA' + 'AAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgc' + 'A/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWF' + 'hYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQA' + 'AAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAg' + 'ABAAAAAAAAAAAD6AAAAAAAAA==');
-};
-Object.defineProperty(FontLoader.prototype, 'loadTestFont', {
- get: function () {
- return shadow(this, 'loadTestFont', getLoadTestFont());
- },
- configurable: true
-});
-FontLoader.prototype.addNativeFontFace = function fontLoader_addNativeFontFace(nativeFontFace) {
- this.nativeFontFaces.push(nativeFontFace);
- document.fonts.add(nativeFontFace);
-};
-FontLoader.prototype.bind = function fontLoaderBind(fonts, callback) {
- var rules = [];
- var fontsToLoad = [];
- var fontLoadPromises = [];
- var getNativeFontPromise = function (nativeFontFace) {
- return nativeFontFace.loaded.catch(function (e) {
- warn('Failed to load font "' + nativeFontFace.family + '": ' + e);
- });
- };
- var isFontLoadingAPISupported = FontLoader.isFontLoadingAPISupported && !FontLoader.isSyncFontLoadingSupported;
- for (var i = 0, ii = fonts.length; i < ii; i++) {
- var font = fonts[i];
- if (font.attached || font.loading === false) {
- continue;
- }
- font.attached = true;
- if (isFontLoadingAPISupported) {
- var nativeFontFace = font.createNativeFontFace();
- if (nativeFontFace) {
- this.addNativeFontFace(nativeFontFace);
- fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
- }
- } else {
- var rule = font.createFontFaceRule();
- if (rule) {
- this.insertRule(rule);
- rules.push(rule);
- fontsToLoad.push(font);
- }
- }
- }
- var request = this.queueLoadingCallback(callback);
- if (isFontLoadingAPISupported) {
- Promise.all(fontLoadPromises).then(function () {
- request.complete();
- });
- } else if (rules.length > 0 && !FontLoader.isSyncFontLoadingSupported) {
- this.prepareFontLoadEvent(rules, fontsToLoad, request);
- } else {
- request.complete();
- }
-};
-FontLoader.prototype.queueLoadingCallback = function FontLoader_queueLoadingCallback(callback) {
- function LoadLoader_completeRequest() {
- assert(!request.end, 'completeRequest() cannot be called twice');
- request.end = Date.now();
- while (context.requests.length > 0 && context.requests[0].end) {
- var otherRequest = context.requests.shift();
- setTimeout(otherRequest.callback, 0);
- }
- }
- var context = this.loadingContext;
- var requestId = 'pdfjs-font-loading-' + context.nextRequestId++;
- var request = {
- id: requestId,
- complete: LoadLoader_completeRequest,
- callback: callback,
- started: Date.now()
- };
- context.requests.push(request);
- return request;
-};
-FontLoader.prototype.prepareFontLoadEvent = function fontLoaderPrepareFontLoadEvent(rules, fonts, request) {
- function int32(data, offset) {
- return data.charCodeAt(offset) << 24 | data.charCodeAt(offset + 1) << 16 | data.charCodeAt(offset + 2) << 8 | data.charCodeAt(offset + 3) & 0xff;
- }
- function spliceString(s, offset, remove, insert) {
- var chunk1 = s.substr(0, offset);
- var chunk2 = s.substr(offset + remove);
- return chunk1 + insert + chunk2;
- }
- var i, ii;
- var canvas = document.createElement('canvas');
- canvas.width = 1;
- canvas.height = 1;
- var ctx = canvas.getContext('2d');
- var called = 0;
- function isFontReady(name, callback) {
- called++;
- if (called > 30) {
- warn('Load test font never loaded.');
- callback();
- return;
- }
- ctx.font = '30px ' + name;
- ctx.fillText('.', 0, 20);
- var imageData = ctx.getImageData(0, 0, 1, 1);
- if (imageData.data[3] > 0) {
- callback();
- return;
- }
- setTimeout(isFontReady.bind(null, name, callback));
- }
- var loadTestFontId = 'lt' + Date.now() + this.loadTestFontId++;
- var data = this.loadTestFont;
- var COMMENT_OFFSET = 976;
- data = spliceString(data, COMMENT_OFFSET, loadTestFontId.length, loadTestFontId);
- var CFF_CHECKSUM_OFFSET = 16;
- var XXXX_VALUE = 0x58585858;
- var checksum = int32(data, CFF_CHECKSUM_OFFSET);
- for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) {
- checksum = checksum - XXXX_VALUE + int32(loadTestFontId, i) | 0;
- }
- if (i < loadTestFontId.length) {
- checksum = checksum - XXXX_VALUE + int32(loadTestFontId + 'XXX', i) | 0;
- }
- data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum));
- var url = 'url(data:font/opentype;base64,' + btoa(data) + ');';
- var rule = '@font-face { font-family:"' + loadTestFontId + '";src:' + url + '}';
- this.insertRule(rule);
- var names = [];
- for (i = 0, ii = fonts.length; i < ii; i++) {
- names.push(fonts[i].loadedName);
- }
- names.push(loadTestFontId);
- var div = document.createElement('div');
- div.setAttribute('style', 'visibility: hidden;' + 'width: 10px; height: 10px;' + 'position: absolute; top: 0px; left: 0px;');
- for (i = 0, ii = names.length; i < ii; ++i) {
- var span = document.createElement('span');
- span.textContent = 'Hi';
- span.style.fontFamily = names[i];
- div.appendChild(span);
- }
- document.body.appendChild(div);
- isFontReady(loadTestFontId, function () {
- document.body.removeChild(div);
- request.complete();
- });
-};
-FontLoader.isFontLoadingAPISupported = typeof document !== 'undefined' && !!document.fonts;
-var isSyncFontLoadingSupported = function isSyncFontLoadingSupported() {
- if (typeof navigator === 'undefined') {
- return true;
- }
- var supported = false;
- var m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(navigator.userAgent);
- if (m && m[1] >= 14) {
- supported = true;
- }
- return supported;
-};
-Object.defineProperty(FontLoader, 'isSyncFontLoadingSupported', {
- get: function () {
- return shadow(FontLoader, 'isSyncFontLoadingSupported', isSyncFontLoadingSupported());
- },
- enumerable: true,
- configurable: true
-});
-var IsEvalSupportedCached = {
- get value() {
- return shadow(this, 'value', sharedUtil.isEvalSupported());
- }
-};
-var FontFaceObject = function FontFaceObjectClosure() {
- function FontFaceObject(translatedData, options) {
- this.compiledGlyphs = Object.create(null);
- for (var i in translatedData) {
- this[i] = translatedData[i];
- }
- this.options = options;
- }
- FontFaceObject.prototype = {
- createNativeFontFace: function FontFaceObject_createNativeFontFace() {
- if (!this.data) {
- return null;
- }
- if (this.options.disableFontFace) {
- this.disableFontFace = true;
- return null;
- }
- var nativeFontFace = new FontFace(this.loadedName, this.data, {});
- if (this.options.fontRegistry) {
- this.options.fontRegistry.registerFont(this);
- }
- return nativeFontFace;
- },
- createFontFaceRule: function FontFaceObject_createFontFaceRule() {
- if (!this.data) {
- return null;
- }
- if (this.options.disableFontFace) {
- this.disableFontFace = true;
- return null;
- }
- var data = bytesToString(new Uint8Array(this.data));
- var fontName = this.loadedName;
- var url = 'url(data:' + this.mimetype + ';base64,' + btoa(data) + ');';
- var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
- if (this.options.fontRegistry) {
- this.options.fontRegistry.registerFont(this, url);
- }
- return rule;
- },
- getPathGenerator: function FontFaceObject_getPathGenerator(objs, character) {
- if (!(character in this.compiledGlyphs)) {
- var cmds = objs.get(this.loadedName + '_path_' + character);
- var current, i, len;
- if (this.options.isEvalSupported && IsEvalSupportedCached.value) {
- var args,
- js = '';
- for (i = 0, len = cmds.length; i < len; i++) {
- current = cmds[i];
- if (current.args !== undefined) {
- args = current.args.join(',');
- } else {
- args = '';
- }
- js += 'c.' + current.cmd + '(' + args + ');\n';
- }
- this.compiledGlyphs[character] = new Function('c', 'size', js);
- } else {
- this.compiledGlyphs[character] = function (c, size) {
- for (i = 0, len = cmds.length; i < len; i++) {
- current = cmds[i];
- if (current.cmd === 'scale') {
- current.args = [size, -size];
- }
- c[current.cmd].apply(c, current.args);
- }
- };
- }
- }
- return this.compiledGlyphs[character];
- }
- };
- return FontFaceObject;
-}();
-exports.FontFaceObject = FontFaceObject;
-exports.FontLoader = FontLoader;
-
-/***/ }),
-/* 12 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var displayWebGL = __w_pdfjs_require__(8);
-var Util = sharedUtil.Util;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var error = sharedUtil.error;
-var WebGLUtils = displayWebGL.WebGLUtils;
-var ShadingIRs = {};
-ShadingIRs.RadialAxial = {
- fromIR: function RadialAxial_fromIR(raw) {
- var type = raw[1];
- var colorStops = raw[2];
- var p0 = raw[3];
- var p1 = raw[4];
- var r0 = raw[5];
- var r1 = raw[6];
- return {
- type: 'Pattern',
- getPattern: function RadialAxial_getPattern(ctx) {
- var grad;
- if (type === 'axial') {
- grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
- } else if (type === 'radial') {
- grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
- }
- for (var i = 0, ii = colorStops.length; i < ii; ++i) {
- var c = colorStops[i];
- grad.addColorStop(c[0], c[1]);
- }
- return grad;
- }
- };
- }
-};
-var createMeshCanvas = function createMeshCanvasClosure() {
- function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
- var coords = context.coords,
- colors = context.colors;
- var bytes = data.data,
- rowSize = data.width * 4;
- var tmp;
- if (coords[p1 + 1] > coords[p2 + 1]) {
- tmp = p1;
- p1 = p2;
- p2 = tmp;
- tmp = c1;
- c1 = c2;
- c2 = tmp;
- }
- if (coords[p2 + 1] > coords[p3 + 1]) {
- tmp = p2;
- p2 = p3;
- p3 = tmp;
- tmp = c2;
- c2 = c3;
- c3 = tmp;
- }
- if (coords[p1 + 1] > coords[p2 + 1]) {
- tmp = p1;
- p1 = p2;
- p2 = tmp;
- tmp = c1;
- c1 = c2;
- c2 = tmp;
- }
- var x1 = (coords[p1] + context.offsetX) * context.scaleX;
- var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
- var x2 = (coords[p2] + context.offsetX) * context.scaleX;
- var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
- var x3 = (coords[p3] + context.offsetX) * context.scaleX;
- var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
- if (y1 >= y3) {
- return;
- }
- var c1r = colors[c1],
- c1g = colors[c1 + 1],
- c1b = colors[c1 + 2];
- var c2r = colors[c2],
- c2g = colors[c2 + 1],
- c2b = colors[c2 + 2];
- var c3r = colors[c3],
- c3g = colors[c3 + 1],
- c3b = colors[c3 + 2];
- var minY = Math.round(y1),
- maxY = Math.round(y3);
- var xa, car, cag, cab;
- var xb, cbr, cbg, cbb;
- var k;
- for (var y = minY; y <= maxY; y++) {
- if (y < y2) {
- k = y < y1 ? 0 : y1 === y2 ? 1 : (y1 - y) / (y1 - y2);
- xa = x1 - (x1 - x2) * k;
- car = c1r - (c1r - c2r) * k;
- cag = c1g - (c1g - c2g) * k;
- cab = c1b - (c1b - c2b) * k;
- } else {
- k = y > y3 ? 1 : y2 === y3 ? 0 : (y2 - y) / (y2 - y3);
- xa = x2 - (x2 - x3) * k;
- car = c2r - (c2r - c3r) * k;
- cag = c2g - (c2g - c3g) * k;
- cab = c2b - (c2b - c3b) * k;
- }
- k = y < y1 ? 0 : y > y3 ? 1 : (y1 - y) / (y1 - y3);
- xb = x1 - (x1 - x3) * k;
- cbr = c1r - (c1r - c3r) * k;
- cbg = c1g - (c1g - c3g) * k;
- cbb = c1b - (c1b - c3b) * k;
- var x1_ = Math.round(Math.min(xa, xb));
- var x2_ = Math.round(Math.max(xa, xb));
- var j = rowSize * y + x1_ * 4;
- for (var x = x1_; x <= x2_; x++) {
- k = (xa - x) / (xa - xb);
- k = k < 0 ? 0 : k > 1 ? 1 : k;
- bytes[j++] = car - (car - cbr) * k | 0;
- bytes[j++] = cag - (cag - cbg) * k | 0;
- bytes[j++] = cab - (cab - cbb) * k | 0;
- bytes[j++] = 255;
- }
- }
- }
- function drawFigure(data, figure, context) {
- var ps = figure.coords;
- var cs = figure.colors;
- var i, ii;
- switch (figure.type) {
- case 'lattice':
- var verticesPerRow = figure.verticesPerRow;
- var rows = Math.floor(ps.length / verticesPerRow) - 1;
- var cols = verticesPerRow - 1;
- for (i = 0; i < rows; i++) {
- var q = i * verticesPerRow;
- for (var j = 0; j < cols; j++, q++) {
- drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]);
- drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
- }
- }
- break;
- case 'triangles':
- for (i = 0, ii = ps.length; i < ii; i += 3) {
- drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]);
- }
- break;
- default:
- error('illigal figure');
- break;
- }
- }
- function createMeshCanvas(bounds, combinesScale, coords, colors, figures, backgroundColor, cachedCanvases) {
- var EXPECTED_SCALE = 1.1;
- var MAX_PATTERN_SIZE = 3000;
- var BORDER_SIZE = 2;
- var offsetX = Math.floor(bounds[0]);
- var offsetY = Math.floor(bounds[1]);
- var boundsWidth = Math.ceil(bounds[2]) - offsetX;
- var boundsHeight = Math.ceil(bounds[3]) - offsetY;
- var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
- var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
- var scaleX = boundsWidth / width;
- var scaleY = boundsHeight / height;
- var context = {
- coords: coords,
- colors: colors,
- offsetX: -offsetX,
- offsetY: -offsetY,
- scaleX: 1 / scaleX,
- scaleY: 1 / scaleY
- };
- var paddedWidth = width + BORDER_SIZE * 2;
- var paddedHeight = height + BORDER_SIZE * 2;
- var canvas, tmpCanvas, i, ii;
- if (WebGLUtils.isEnabled) {
- canvas = WebGLUtils.drawFigures(width, height, backgroundColor, figures, context);
- tmpCanvas = cachedCanvases.getCanvas('mesh', paddedWidth, paddedHeight, false);
- tmpCanvas.context.drawImage(canvas, BORDER_SIZE, BORDER_SIZE);
- canvas = tmpCanvas.canvas;
- } else {
- tmpCanvas = cachedCanvases.getCanvas('mesh', paddedWidth, paddedHeight, false);
- var tmpCtx = tmpCanvas.context;
- var data = tmpCtx.createImageData(width, height);
- if (backgroundColor) {
- var bytes = data.data;
- for (i = 0, ii = bytes.length; i < ii; i += 4) {
- bytes[i] = backgroundColor[0];
- bytes[i + 1] = backgroundColor[1];
- bytes[i + 2] = backgroundColor[2];
- bytes[i + 3] = 255;
- }
- }
- for (i = 0; i < figures.length; i++) {
- drawFigure(data, figures[i], context);
- }
- tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
- canvas = tmpCanvas.canvas;
- }
- return {
- canvas: canvas,
- offsetX: offsetX - BORDER_SIZE * scaleX,
- offsetY: offsetY - BORDER_SIZE * scaleY,
- scaleX: scaleX,
- scaleY: scaleY
- };
- }
- return createMeshCanvas;
-}();
-ShadingIRs.Mesh = {
- fromIR: function Mesh_fromIR(raw) {
- var coords = raw[2];
- var colors = raw[3];
- var figures = raw[4];
- var bounds = raw[5];
- var matrix = raw[6];
- var background = raw[8];
- return {
- type: 'Pattern',
- getPattern: function Mesh_getPattern(ctx, owner, shadingFill) {
- var scale;
- if (shadingFill) {
- scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
- } else {
- scale = Util.singularValueDecompose2dScale(owner.baseTransform);
- if (matrix) {
- var matrixScale = Util.singularValueDecompose2dScale(matrix);
- scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
- }
- }
- var temporaryPatternCanvas = createMeshCanvas(bounds, scale, coords, colors, figures, shadingFill ? null : background, owner.cachedCanvases);
- if (!shadingFill) {
- ctx.setTransform.apply(ctx, owner.baseTransform);
- if (matrix) {
- ctx.transform.apply(ctx, matrix);
- }
- }
- ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
- ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
- return ctx.createPattern(temporaryPatternCanvas.canvas, 'no-repeat');
- }
- };
- }
-};
-ShadingIRs.Dummy = {
- fromIR: function Dummy_fromIR() {
- return {
- type: 'Pattern',
- getPattern: function Dummy_fromIR_getPattern() {
- return 'hotpink';
- }
- };
- }
-};
-function getShadingPatternFromIR(raw) {
- var shadingIR = ShadingIRs[raw[0]];
- if (!shadingIR) {
- error('Unknown IR type: ' + raw[0]);
- }
- return shadingIR.fromIR(raw);
-}
-var TilingPattern = function TilingPatternClosure() {
- var PaintType = {
- COLORED: 1,
- UNCOLORED: 2
- };
- var MAX_PATTERN_SIZE = 3000;
- function TilingPattern(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
- this.operatorList = IR[2];
- this.matrix = IR[3] || [1, 0, 0, 1, 0, 0];
- this.bbox = Util.normalizeRect(IR[4]);
- this.xstep = IR[5];
- this.ystep = IR[6];
- this.paintType = IR[7];
- this.tilingType = IR[8];
- this.color = color;
- this.canvasGraphicsFactory = canvasGraphicsFactory;
- this.baseTransform = baseTransform;
- this.type = 'Pattern';
- this.ctx = ctx;
- }
- TilingPattern.prototype = {
- createPatternCanvas: function TilinPattern_createPatternCanvas(owner) {
- var operatorList = this.operatorList;
- var bbox = this.bbox;
- var xstep = this.xstep;
- var ystep = this.ystep;
- var paintType = this.paintType;
- var tilingType = this.tilingType;
- var color = this.color;
- var canvasGraphicsFactory = this.canvasGraphicsFactory;
- info('TilingType: ' + tilingType);
- var x0 = bbox[0],
- y0 = bbox[1],
- x1 = bbox[2],
- y1 = bbox[3];
- var topLeft = [x0, y0];
- var botRight = [x0 + xstep, y0 + ystep];
- var width = botRight[0] - topLeft[0];
- var height = botRight[1] - topLeft[1];
- var matrixScale = Util.singularValueDecompose2dScale(this.matrix);
- var curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform);
- var combinedScale = [matrixScale[0] * curMatrixScale[0], matrixScale[1] * curMatrixScale[1]];
- width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), MAX_PATTERN_SIZE);
- height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), MAX_PATTERN_SIZE);
- var tmpCanvas = owner.cachedCanvases.getCanvas('pattern', width, height, true);
- var tmpCtx = tmpCanvas.context;
- var graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
- graphics.groupLevel = owner.groupLevel;
- this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
- this.setScale(width, height, xstep, ystep);
- this.transformToScale(graphics);
- var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
- graphics.transform.apply(graphics, tmpTranslate);
- this.clipBbox(graphics, bbox, x0, y0, x1, y1);
- graphics.executeOperatorList(operatorList);
- return tmpCanvas.canvas;
- },
- setScale: function TilingPattern_setScale(width, height, xstep, ystep) {
- this.scale = [width / xstep, height / ystep];
- },
- transformToScale: function TilingPattern_transformToScale(graphics) {
- var scale = this.scale;
- var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
- graphics.transform.apply(graphics, tmpScale);
- },
- scaleToContext: function TilingPattern_scaleToContext() {
- var scale = this.scale;
- this.ctx.scale(1 / scale[0], 1 / scale[1]);
- },
- clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) {
- if (isArray(bbox) && bbox.length === 4) {
- var bboxWidth = x1 - x0;
- var bboxHeight = y1 - y0;
- graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
- graphics.clip();
- graphics.endPath();
- }
- },
- setFillAndStrokeStyleToContext: function setFillAndStrokeStyleToContext(context, paintType, color) {
- switch (paintType) {
- case PaintType.COLORED:
- var ctx = this.ctx;
- context.fillStyle = ctx.fillStyle;
- context.strokeStyle = ctx.strokeStyle;
- break;
- case PaintType.UNCOLORED:
- var cssColor = Util.makeCssRgb(color[0], color[1], color[2]);
- context.fillStyle = cssColor;
- context.strokeStyle = cssColor;
- break;
- default:
- error('Unsupported paint type: ' + paintType);
- }
- },
- getPattern: function TilingPattern_getPattern(ctx, owner) {
- var temporaryPatternCanvas = this.createPatternCanvas(owner);
- ctx = this.ctx;
- ctx.setTransform.apply(ctx, this.baseTransform);
- ctx.transform.apply(ctx, this.matrix);
- this.scaleToContext();
- return ctx.createPattern(temporaryPatternCanvas, 'repeat');
- }
- };
- return TilingPattern;
-}();
-exports.getShadingPatternFromIR = getShadingPatternFromIR;
-exports.TilingPattern = TilingPattern;
-
-/***/ }),
-/* 13 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var pdfjsVersion = '1.8.172';
-var pdfjsBuild = '8ff1fbe7';
-var pdfjsSharedUtil = __w_pdfjs_require__(0);
-var pdfjsDisplayGlobal = __w_pdfjs_require__(9);
-var pdfjsDisplayAPI = __w_pdfjs_require__(3);
-var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
-var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
-var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
-var pdfjsDisplaySVG = __w_pdfjs_require__(4);
-exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
-exports.build = pdfjsDisplayAPI.build;
-exports.version = pdfjsDisplayAPI.version;
-exports.getDocument = pdfjsDisplayAPI.getDocument;
-exports.PDFDataRangeTransport = pdfjsDisplayAPI.PDFDataRangeTransport;
-exports.PDFWorker = pdfjsDisplayAPI.PDFWorker;
-exports.renderTextLayer = pdfjsDisplayTextLayer.renderTextLayer;
-exports.AnnotationLayer = pdfjsDisplayAnnotationLayer.AnnotationLayer;
-exports.CustomStyle = pdfjsDisplayDOMUtils.CustomStyle;
-exports.createPromiseCapability = pdfjsSharedUtil.createPromiseCapability;
-exports.PasswordResponses = pdfjsSharedUtil.PasswordResponses;
-exports.InvalidPDFException = pdfjsSharedUtil.InvalidPDFException;
-exports.MissingPDFException = pdfjsSharedUtil.MissingPDFException;
-exports.SVGGraphics = pdfjsDisplaySVG.SVGGraphics;
-exports.UnexpectedResponseException = pdfjsSharedUtil.UnexpectedResponseException;
-exports.OPS = pdfjsSharedUtil.OPS;
-exports.UNSUPPORTED_FEATURES = pdfjsSharedUtil.UNSUPPORTED_FEATURES;
-exports.isValidUrl = pdfjsDisplayDOMUtils.isValidUrl;
-exports.createValidAbsoluteUrl = pdfjsSharedUtil.createValidAbsoluteUrl;
-exports.createObjectURL = pdfjsSharedUtil.createObjectURL;
-exports.removeNullCharacters = pdfjsSharedUtil.removeNullCharacters;
-exports.shadow = pdfjsSharedUtil.shadow;
-exports.createBlob = pdfjsSharedUtil.createBlob;
-exports.RenderingCancelledException = pdfjsDisplayDOMUtils.RenderingCancelledException;
-exports.getFilenameFromUrl = pdfjsDisplayDOMUtils.getFilenameFromUrl;
-exports.addLinkAttributes = pdfjsDisplayDOMUtils.addLinkAttributes;
-
-/***/ }),
-/* 14 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
- var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
- var userAgent = typeof navigator !== 'undefined' && navigator.userAgent || '';
- var isAndroid = /Android/.test(userAgent);
- var isAndroidPre3 = /Android\s[0-2][^\d]/.test(userAgent);
- var isAndroidPre5 = /Android\s[0-4][^\d]/.test(userAgent);
- var isChrome = userAgent.indexOf('Chrom') >= 0;
- var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(userAgent);
- var isIOSChrome = userAgent.indexOf('CriOS') >= 0;
- var isIE = userAgent.indexOf('Trident') >= 0;
- var isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
- var isOpera = userAgent.indexOf('Opera') >= 0;
- var isSafari = /Safari\//.test(userAgent) && !/(Chrome\/|Android\s)/.test(userAgent);
- var hasDOM = typeof window === 'object' && typeof document === 'object';
- if (typeof PDFJS === 'undefined') {
- globalScope.PDFJS = {};
- }
- PDFJS.compatibilityChecked = true;
- (function checkTypedArrayCompatibility() {
- if (typeof Uint8Array !== 'undefined') {
- if (typeof Uint8Array.prototype.subarray === 'undefined') {
- Uint8Array.prototype.subarray = function subarray(start, end) {
- return new Uint8Array(this.slice(start, end));
- };
- Float32Array.prototype.subarray = function subarray(start, end) {
- return new Float32Array(this.slice(start, end));
- };
- }
- if (typeof Float64Array === 'undefined') {
- globalScope.Float64Array = Float32Array;
- }
- return;
- }
- function subarray(start, end) {
- return new TypedArray(this.slice(start, end));
- }
- function setArrayOffset(array, offset) {
- if (arguments.length < 2) {
- offset = 0;
- }
- for (var i = 0, n = array.length; i < n; ++i, ++offset) {
- this[offset] = array[i] & 0xFF;
- }
- }
- function TypedArray(arg1) {
- var result, i, n;
- if (typeof arg1 === 'number') {
- result = [];
- for (i = 0; i < arg1; ++i) {
- result[i] = 0;
- }
- } else if ('slice' in arg1) {
- result = arg1.slice(0);
- } else {
- result = [];
- for (i = 0, n = arg1.length; i < n; ++i) {
- result[i] = arg1[i];
- }
- }
- result.subarray = subarray;
- result.buffer = result;
- result.byteLength = result.length;
- result.set = setArrayOffset;
- if (typeof arg1 === 'object' && arg1.buffer) {
- result.buffer = arg1.buffer;
- }
- return result;
- }
- globalScope.Uint8Array = TypedArray;
- globalScope.Int8Array = TypedArray;
- globalScope.Uint32Array = TypedArray;
- globalScope.Int32Array = TypedArray;
- globalScope.Uint16Array = TypedArray;
- globalScope.Float32Array = TypedArray;
- globalScope.Float64Array = TypedArray;
- })();
- (function normalizeURLObject() {
- if (!globalScope.URL) {
- globalScope.URL = globalScope.webkitURL;
- }
- })();
- (function checkObjectDefinePropertyCompatibility() {
- if (typeof Object.defineProperty !== 'undefined') {
- var definePropertyPossible = true;
- try {
- if (hasDOM) {
- Object.defineProperty(new Image(), 'id', { value: 'test' });
- }
- var Test = function Test() {};
- Test.prototype = {
- get id() {}
- };
- Object.defineProperty(new Test(), 'id', {
- value: '',
- configurable: true,
- enumerable: true,
- writable: false
- });
- } catch (e) {
- definePropertyPossible = false;
- }
- if (definePropertyPossible) {
- return;
- }
- }
- Object.defineProperty = function objectDefineProperty(obj, name, def) {
- delete obj[name];
- if ('get' in def) {
- obj.__defineGetter__(name, def['get']);
- }
- if ('set' in def) {
- obj.__defineSetter__(name, def['set']);
- }
- if ('value' in def) {
- obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
- this.__defineGetter__(name, function objectDefinePropertyGetter() {
- return value;
- });
- return value;
- });
- obj[name] = def.value;
- }
- };
- })();
- (function checkXMLHttpRequestResponseCompatibility() {
- if (typeof XMLHttpRequest === 'undefined') {
- return;
- }
- var xhrPrototype = XMLHttpRequest.prototype;
- var xhr = new XMLHttpRequest();
- if (!('overrideMimeType' in xhr)) {
- Object.defineProperty(xhrPrototype, 'overrideMimeType', {
- value: function xmlHttpRequestOverrideMimeType(mimeType) {}
- });
- }
- if ('responseType' in xhr) {
- return;
- }
- Object.defineProperty(xhrPrototype, 'responseType', {
- get: function xmlHttpRequestGetResponseType() {
- return this._responseType || 'text';
- },
- set: function xmlHttpRequestSetResponseType(value) {
- if (value === 'text' || value === 'arraybuffer') {
- this._responseType = value;
- if (value === 'arraybuffer' && typeof this.overrideMimeType === 'function') {
- this.overrideMimeType('text/plain; charset=x-user-defined');
- }
- }
- }
- });
- if (typeof VBArray !== 'undefined') {
- Object.defineProperty(xhrPrototype, 'response', {
- get: function xmlHttpRequestResponseGet() {
- if (this.responseType === 'arraybuffer') {
- return new Uint8Array(new VBArray(this.responseBody).toArray());
- }
- return this.responseText;
- }
- });
- return;
- }
- Object.defineProperty(xhrPrototype, 'response', {
- get: function xmlHttpRequestResponseGet() {
- if (this.responseType !== 'arraybuffer') {
- return this.responseText;
- }
- var text = this.responseText;
- var i,
- n = text.length;
- var result = new Uint8Array(n);
- for (i = 0; i < n; ++i) {
- result[i] = text.charCodeAt(i) & 0xFF;
- }
- return result.buffer;
- }
- });
- })();
- (function checkWindowBtoaCompatibility() {
- if ('btoa' in globalScope) {
- return;
- }
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- globalScope.btoa = function (chars) {
- var buffer = '';
- var i, n;
- for (i = 0, n = chars.length; i < n; i += 3) {
- var b1 = chars.charCodeAt(i) & 0xFF;
- var b2 = chars.charCodeAt(i + 1) & 0xFF;
- var b3 = chars.charCodeAt(i + 2) & 0xFF;
- var d1 = b1 >> 2,
- d2 = (b1 & 3) << 4 | b2 >> 4;
- var d3 = i + 1 < n ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
- var d4 = i + 2 < n ? b3 & 0x3F : 64;
- buffer += digits.charAt(d1) + digits.charAt(d2) + digits.charAt(d3) + digits.charAt(d4);
- }
- return buffer;
- };
- })();
- (function checkWindowAtobCompatibility() {
- if ('atob' in globalScope) {
- return;
- }
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- globalScope.atob = function (input) {
- input = input.replace(/=+$/, '');
- if (input.length % 4 === 1) {
- throw new Error('bad atob input');
- }
- for (var bc = 0, bs, buffer, idx = 0, output = ''; buffer = input.charAt(idx++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
- buffer = digits.indexOf(buffer);
- }
- return output;
- };
- })();
- (function checkFunctionPrototypeBindCompatibility() {
- if (typeof Function.prototype.bind !== 'undefined') {
- return;
- }
- Function.prototype.bind = function functionPrototypeBind(obj) {
- var fn = this,
- headArgs = Array.prototype.slice.call(arguments, 1);
- var bound = function functionPrototypeBindBound() {
- var args = headArgs.concat(Array.prototype.slice.call(arguments));
- return fn.apply(obj, args);
- };
- return bound;
- };
- })();
- (function checkDatasetProperty() {
- if (!hasDOM) {
- return;
- }
- var div = document.createElement('div');
- if ('dataset' in div) {
- return;
- }
- Object.defineProperty(HTMLElement.prototype, 'dataset', {
- get: function () {
- if (this._dataset) {
- return this._dataset;
- }
- var dataset = {};
- for (var j = 0, jj = this.attributes.length; j < jj; j++) {
- var attribute = this.attributes[j];
- if (attribute.name.substring(0, 5) !== 'data-') {
- continue;
- }
- var key = attribute.name.substring(5).replace(/\-([a-z])/g, function (all, ch) {
- return ch.toUpperCase();
- });
- dataset[key] = attribute.value;
- }
- Object.defineProperty(this, '_dataset', {
- value: dataset,
- writable: false,
- enumerable: false
- });
- return dataset;
- },
- enumerable: true
- });
- })();
- (function checkClassListProperty() {
- function changeList(element, itemName, add, remove) {
- var s = element.className || '';
- var list = s.split(/\s+/g);
- if (list[0] === '') {
- list.shift();
- }
- var index = list.indexOf(itemName);
- if (index < 0 && add) {
- list.push(itemName);
- }
- if (index >= 0 && remove) {
- list.splice(index, 1);
- }
- element.className = list.join(' ');
- return index >= 0;
- }
- if (!hasDOM) {
- return;
- }
- var div = document.createElement('div');
- if ('classList' in div) {
- return;
- }
- var classListPrototype = {
- add: function (name) {
- changeList(this.element, name, true, false);
- },
- contains: function (name) {
- return changeList(this.element, name, false, false);
- },
- remove: function (name) {
- changeList(this.element, name, false, true);
- },
- toggle: function (name) {
- changeList(this.element, name, true, true);
- }
- };
- Object.defineProperty(HTMLElement.prototype, 'classList', {
- get: function () {
- if (this._classList) {
- return this._classList;
- }
- var classList = Object.create(classListPrototype, {
- element: {
- value: this,
- writable: false,
- enumerable: true
- }
- });
- Object.defineProperty(this, '_classList', {
- value: classList,
- writable: false,
- enumerable: false
- });
- return classList;
- },
- enumerable: true
- });
- })();
- (function checkWorkerConsoleCompatibility() {
- if (typeof importScripts === 'undefined' || 'console' in globalScope) {
- return;
- }
- var consoleTimer = {};
- var workerConsole = {
- log: function log() {
- var args = Array.prototype.slice.call(arguments);
- globalScope.postMessage({
- targetName: 'main',
- action: 'console_log',
- data: args
- });
- },
- error: function error() {
- var args = Array.prototype.slice.call(arguments);
- globalScope.postMessage({
- targetName: 'main',
- action: 'console_error',
- data: args
- });
- },
- time: function time(name) {
- consoleTimer[name] = Date.now();
- },
- timeEnd: function timeEnd(name) {
- var time = consoleTimer[name];
- if (!time) {
- throw new Error('Unknown timer name ' + name);
- }
- this.log('Timer:', name, Date.now() - time);
- }
- };
- globalScope.console = workerConsole;
- })();
- (function checkConsoleCompatibility() {
- if (!hasDOM) {
- return;
- }
- if (!('console' in window)) {
- window.console = {
- log: function () {},
- error: function () {},
- warn: function () {}
- };
- return;
- }
- if (!('bind' in console.log)) {
- console.log = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.log);
- console.error = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.error);
- console.warn = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.warn);
- return;
- }
- })();
- (function checkOnClickCompatibility() {
- function ignoreIfTargetDisabled(event) {
- if (isDisabled(event.target)) {
- event.stopPropagation();
- }
- }
- function isDisabled(node) {
- return node.disabled || node.parentNode && isDisabled(node.parentNode);
- }
- if (isOpera) {
- document.addEventListener('click', ignoreIfTargetDisabled, true);
- }
- })();
- (function checkOnBlobSupport() {
- if (isIE || isIOSChrome) {
- PDFJS.disableCreateObjectURL = true;
- }
- })();
- (function checkNavigatorLanguage() {
- if (typeof navigator === 'undefined') {
- return;
- }
- if ('language' in navigator) {
- return;
- }
- PDFJS.locale = navigator.userLanguage || 'en-US';
- })();
- (function checkRangeRequests() {
- if (isSafari || isAndroidPre3 || isChromeWithRangeBug || isIOS) {
- PDFJS.disableRange = true;
- PDFJS.disableStream = true;
- }
- })();
- (function checkHistoryManipulation() {
- if (!hasDOM) {
- return;
- }
- if (!history.pushState || isAndroidPre3) {
- PDFJS.disableHistory = true;
- }
- })();
- (function checkSetPresenceInImageData() {
- if (!hasDOM) {
- return;
- }
- if (window.CanvasPixelArray) {
- if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
- window.CanvasPixelArray.prototype.set = function (arr) {
- for (var i = 0, ii = this.length; i < ii; i++) {
- this[i] = arr[i];
- }
- };
- }
- } else {
- var polyfill = false,
- versionMatch;
- if (isChrome) {
- versionMatch = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
- polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
- } else if (isAndroid) {
- polyfill = isAndroidPre5;
- } else if (isSafari) {
- versionMatch = userAgent.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
- polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
- }
- if (polyfill) {
- var contextPrototype = window.CanvasRenderingContext2D.prototype;
- var createImageData = contextPrototype.createImageData;
- contextPrototype.createImageData = function (w, h) {
- var imageData = createImageData.call(this, w, h);
- imageData.data.set = function (arr) {
- for (var i = 0, ii = this.length; i < ii; i++) {
- this[i] = arr[i];
- }
- };
- return imageData;
- };
- contextPrototype = null;
- }
- }
- })();
- (function checkRequestAnimationFrame() {
- function installFakeAnimationFrameFunctions() {
- window.requestAnimationFrame = function (callback) {
- return window.setTimeout(callback, 20);
- };
- window.cancelAnimationFrame = function (timeoutID) {
- window.clearTimeout(timeoutID);
- };
- }
- if (!hasDOM) {
- return;
- }
- if (isIOS) {
- installFakeAnimationFrameFunctions();
- return;
- }
- if ('requestAnimationFrame' in window) {
- return;
- }
- window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
- if (!('requestAnimationFrame' in window)) {
- installFakeAnimationFrameFunctions();
- }
- })();
- (function checkCanvasSizeLimitation() {
- if (isIOS || isAndroid) {
- PDFJS.maxCanvasPixels = 5242880;
- }
- })();
- (function checkFullscreenSupport() {
- if (!hasDOM) {
- return;
- }
- if (isIE && window.parent !== window) {
- PDFJS.disableFullscreen = true;
- }
- })();
- (function checkCurrentScript() {
- if (!hasDOM) {
- return;
- }
- if ('currentScript' in document) {
- return;
- }
- Object.defineProperty(document, 'currentScript', {
- get: function () {
- var scripts = document.getElementsByTagName('script');
- return scripts[scripts.length - 1];
- },
- enumerable: true,
- configurable: true
- });
- })();
- (function checkInputTypeNumberAssign() {
- if (!hasDOM) {
- return;
- }
- var el = document.createElement('input');
- try {
- el.type = 'number';
- } catch (ex) {
- var inputProto = el.constructor.prototype;
- var typeProperty = Object.getOwnPropertyDescriptor(inputProto, 'type');
- Object.defineProperty(inputProto, 'type', {
- get: function () {
- return typeProperty.get.call(this);
- },
- set: function (value) {
- typeProperty.set.call(this, value === 'number' ? 'text' : value);
- },
- enumerable: true,
- configurable: true
- });
- }
- })();
- (function checkDocumentReadyState() {
- if (!hasDOM) {
- return;
- }
- if (!document.attachEvent) {
- return;
- }
- var documentProto = document.constructor.prototype;
- var readyStateProto = Object.getOwnPropertyDescriptor(documentProto, 'readyState');
- Object.defineProperty(documentProto, 'readyState', {
- get: function () {
- var value = readyStateProto.get.call(this);
- return value === 'interactive' ? 'loading' : value;
- },
- set: function (value) {
- readyStateProto.set.call(this, value);
- },
- enumerable: true,
- configurable: true
- });
- })();
- (function checkChildNodeRemove() {
- if (!hasDOM) {
- return;
- }
- if (typeof Element.prototype.remove !== 'undefined') {
- return;
- }
- Element.prototype.remove = function () {
- if (this.parentNode) {
- this.parentNode.removeChild(this);
- }
- };
- })();
- (function checkPromise() {
- if (globalScope.Promise) {
- if (typeof globalScope.Promise.all !== 'function') {
- globalScope.Promise.all = function (iterable) {
- var count = 0,
- results = [],
- resolve,
- reject;
- var promise = new globalScope.Promise(function (resolve_, reject_) {
- resolve = resolve_;
- reject = reject_;
- });
- iterable.forEach(function (p, i) {
- count++;
- p.then(function (result) {
- results[i] = result;
- count--;
- if (count === 0) {
- resolve(results);
- }
- }, reject);
- });
- if (count === 0) {
- resolve(results);
- }
- return promise;
- };
- }
- if (typeof globalScope.Promise.resolve !== 'function') {
- globalScope.Promise.resolve = function (value) {
- return new globalScope.Promise(function (resolve) {
- resolve(value);
- });
- };
- }
- if (typeof globalScope.Promise.reject !== 'function') {
- globalScope.Promise.reject = function (reason) {
- return new globalScope.Promise(function (resolve, reject) {
- reject(reason);
- });
- };
- }
- if (typeof globalScope.Promise.prototype.catch !== 'function') {
- globalScope.Promise.prototype.catch = function (onReject) {
- return globalScope.Promise.prototype.then(undefined, onReject);
- };
- }
- return;
- }
- var STATUS_PENDING = 0;
- var STATUS_RESOLVED = 1;
- var STATUS_REJECTED = 2;
- var REJECTION_TIMEOUT = 500;
- var HandlerManager = {
- handlers: [],
- running: false,
- unhandledRejections: [],
- pendingRejectionCheck: false,
- scheduleHandlers: function scheduleHandlers(promise) {
- if (promise._status === STATUS_PENDING) {
- return;
- }
- this.handlers = this.handlers.concat(promise._handlers);
- promise._handlers = [];
- if (this.running) {
- return;
- }
- this.running = true;
- setTimeout(this.runHandlers.bind(this), 0);
- },
- runHandlers: function runHandlers() {
- var RUN_TIMEOUT = 1;
- var timeoutAt = Date.now() + RUN_TIMEOUT;
- while (this.handlers.length > 0) {
- var handler = this.handlers.shift();
- var nextStatus = handler.thisPromise._status;
- var nextValue = handler.thisPromise._value;
- try {
- if (nextStatus === STATUS_RESOLVED) {
- if (typeof handler.onResolve === 'function') {
- nextValue = handler.onResolve(nextValue);
- }
- } else if (typeof handler.onReject === 'function') {
- nextValue = handler.onReject(nextValue);
- nextStatus = STATUS_RESOLVED;
- if (handler.thisPromise._unhandledRejection) {
- this.removeUnhandeledRejection(handler.thisPromise);
- }
- }
- } catch (ex) {
- nextStatus = STATUS_REJECTED;
- nextValue = ex;
- }
- handler.nextPromise._updateStatus(nextStatus, nextValue);
- if (Date.now() >= timeoutAt) {
- break;
- }
- }
- if (this.handlers.length > 0) {
- setTimeout(this.runHandlers.bind(this), 0);
- return;
- }
- this.running = false;
- },
- addUnhandledRejection: function addUnhandledRejection(promise) {
- this.unhandledRejections.push({
- promise: promise,
- time: Date.now()
- });
- this.scheduleRejectionCheck();
- },
- removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
- promise._unhandledRejection = false;
- for (var i = 0; i < this.unhandledRejections.length; i++) {
- if (this.unhandledRejections[i].promise === promise) {
- this.unhandledRejections.splice(i);
- i--;
- }
- }
- },
- scheduleRejectionCheck: function scheduleRejectionCheck() {
- if (this.pendingRejectionCheck) {
- return;
- }
- this.pendingRejectionCheck = true;
- setTimeout(function rejectionCheck() {
- this.pendingRejectionCheck = false;
- var now = Date.now();
- for (var i = 0; i < this.unhandledRejections.length; i++) {
- if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
- var unhandled = this.unhandledRejections[i].promise._value;
- var msg = 'Unhandled rejection: ' + unhandled;
- if (unhandled.stack) {
- msg += '\n' + unhandled.stack;
- }
- try {
- throw new Error(msg);
- } catch (_) {
- console.warn(msg);
- }
- this.unhandledRejections.splice(i);
- i--;
- }
- }
- if (this.unhandledRejections.length) {
- this.scheduleRejectionCheck();
- }
- }.bind(this), REJECTION_TIMEOUT);
- }
- };
- var Promise = function Promise(resolver) {
- this._status = STATUS_PENDING;
- this._handlers = [];
- try {
- resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
- } catch (e) {
- this._reject(e);
- }
- };
- Promise.all = function Promise_all(promises) {
- var resolveAll, rejectAll;
- var deferred = new Promise(function (resolve, reject) {
- resolveAll = resolve;
- rejectAll = reject;
- });
- var unresolved = promises.length;
- var results = [];
- if (unresolved === 0) {
- resolveAll(results);
- return deferred;
- }
- function reject(reason) {
- if (deferred._status === STATUS_REJECTED) {
- return;
- }
- results = [];
- rejectAll(reason);
- }
- for (var i = 0, ii = promises.length; i < ii; ++i) {
- var promise = promises[i];
- var resolve = function (i) {
- return function (value) {
- if (deferred._status === STATUS_REJECTED) {
- return;
- }
- results[i] = value;
- unresolved--;
- if (unresolved === 0) {
- resolveAll(results);
- }
- };
- }(i);
- if (Promise.isPromise(promise)) {
- promise.then(resolve, reject);
- } else {
- resolve(promise);
- }
- }
- return deferred;
- };
- Promise.isPromise = function Promise_isPromise(value) {
- return value && typeof value.then === 'function';
- };
- Promise.resolve = function Promise_resolve(value) {
- return new Promise(function (resolve) {
- resolve(value);
- });
- };
- Promise.reject = function Promise_reject(reason) {
- return new Promise(function (resolve, reject) {
- reject(reason);
- });
- };
- Promise.prototype = {
- _status: null,
- _value: null,
- _handlers: null,
- _unhandledRejection: null,
- _updateStatus: function Promise__updateStatus(status, value) {
- if (this._status === STATUS_RESOLVED || this._status === STATUS_REJECTED) {
- return;
- }
- if (status === STATUS_RESOLVED && Promise.isPromise(value)) {
- value.then(this._updateStatus.bind(this, STATUS_RESOLVED), this._updateStatus.bind(this, STATUS_REJECTED));
- return;
- }
- this._status = status;
- this._value = value;
- if (status === STATUS_REJECTED && this._handlers.length === 0) {
- this._unhandledRejection = true;
- HandlerManager.addUnhandledRejection(this);
- }
- HandlerManager.scheduleHandlers(this);
- },
- _resolve: function Promise_resolve(value) {
- this._updateStatus(STATUS_RESOLVED, value);
- },
- _reject: function Promise_reject(reason) {
- this._updateStatus(STATUS_REJECTED, reason);
- },
- then: function Promise_then(onResolve, onReject) {
- var nextPromise = new Promise(function (resolve, reject) {
- this.resolve = resolve;
- this.reject = reject;
- });
- this._handlers.push({
- thisPromise: this,
- onResolve: onResolve,
- onReject: onReject,
- nextPromise: nextPromise
- });
- HandlerManager.scheduleHandlers(this);
- return nextPromise;
- },
- catch: function Promise_catch(onReject) {
- return this.then(undefined, onReject);
- }
- };
- globalScope.Promise = Promise;
- })();
- (function checkWeakMap() {
- if (globalScope.WeakMap) {
- return;
- }
- var id = 0;
- function WeakMap() {
- this.id = '$weakmap' + id++;
- }
- WeakMap.prototype = {
- has: function (obj) {
- return !!Object.getOwnPropertyDescriptor(obj, this.id);
- },
- get: function (obj, defaultValue) {
- return this.has(obj) ? obj[this.id] : defaultValue;
- },
- set: function (obj, value) {
- Object.defineProperty(obj, this.id, {
- value: value,
- enumerable: false,
- configurable: true
- });
- },
- delete: function (obj) {
- delete obj[this.id];
- }
- };
- globalScope.WeakMap = WeakMap;
- })();
- (function checkURLConstructor() {
- var hasWorkingUrl = false;
- try {
- if (typeof URL === 'function' && typeof URL.prototype === 'object' && 'origin' in URL.prototype) {
- var u = new URL('b', 'http://a');
- u.pathname = 'c%20d';
- hasWorkingUrl = u.href === 'http://a/c%20d';
- }
- } catch (e) {}
- if (hasWorkingUrl) {
- return;
- }
- var relative = Object.create(null);
- relative['ftp'] = 21;
- relative['file'] = 0;
- relative['gopher'] = 70;
- relative['http'] = 80;
- relative['https'] = 443;
- relative['ws'] = 80;
- relative['wss'] = 443;
- var relativePathDotMapping = Object.create(null);
- relativePathDotMapping['%2e'] = '.';
- relativePathDotMapping['.%2e'] = '..';
- relativePathDotMapping['%2e.'] = '..';
- relativePathDotMapping['%2e%2e'] = '..';
- function isRelativeScheme(scheme) {
- return relative[scheme] !== undefined;
- }
- function invalid() {
- clear.call(this);
- this._isInvalid = true;
- }
- function IDNAToASCII(h) {
- if (h === '') {
- invalid.call(this);
- }
- return h.toLowerCase();
- }
- function percentEscape(c) {
- var unicode = c.charCodeAt(0);
- if (unicode > 0x20 && unicode < 0x7F && [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) === -1) {
- return c;
- }
- return encodeURIComponent(c);
- }
- function percentEscapeQuery(c) {
- var unicode = c.charCodeAt(0);
- if (unicode > 0x20 && unicode < 0x7F && [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) === -1) {
- return c;
- }
- return encodeURIComponent(c);
- }
- var EOF,
- ALPHA = /[a-zA-Z]/,
- ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
- function parse(input, stateOverride, base) {
- function err(message) {
- errors.push(message);
- }
- var state = stateOverride || 'scheme start',
- cursor = 0,
- buffer = '',
- seenAt = false,
- seenBracket = false,
- errors = [];
- loop: while ((input[cursor - 1] !== EOF || cursor === 0) && !this._isInvalid) {
- var c = input[cursor];
- switch (state) {
- case 'scheme start':
- if (c && ALPHA.test(c)) {
- buffer += c.toLowerCase();
- state = 'scheme';
- } else if (!stateOverride) {
- buffer = '';
- state = 'no scheme';
- continue;
- } else {
- err('Invalid scheme.');
- break loop;
- }
- break;
- case 'scheme':
- if (c && ALPHANUMERIC.test(c)) {
- buffer += c.toLowerCase();
- } else if (c === ':') {
- this._scheme = buffer;
- buffer = '';
- if (stateOverride) {
- break loop;
- }
- if (isRelativeScheme(this._scheme)) {
- this._isRelative = true;
- }
- if (this._scheme === 'file') {
- state = 'relative';
- } else if (this._isRelative && base && base._scheme === this._scheme) {
- state = 'relative or authority';
- } else if (this._isRelative) {
- state = 'authority first slash';
- } else {
- state = 'scheme data';
- }
- } else if (!stateOverride) {
- buffer = '';
- cursor = 0;
- state = 'no scheme';
- continue;
- } else if (c === EOF) {
- break loop;
- } else {
- err('Code point not allowed in scheme: ' + c);
- break loop;
- }
- break;
- case 'scheme data':
- if (c === '?') {
- this._query = '?';
- state = 'query';
- } else if (c === '#') {
- this._fragment = '#';
- state = 'fragment';
- } else {
- if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._schemeData += percentEscape(c);
- }
- }
- break;
- case 'no scheme':
- if (!base || !isRelativeScheme(base._scheme)) {
- err('Missing scheme.');
- invalid.call(this);
- } else {
- state = 'relative';
- continue;
- }
- break;
- case 'relative or authority':
- if (c === '/' && input[cursor + 1] === '/') {
- state = 'authority ignore slashes';
- } else {
- err('Expected /, got: ' + c);
- state = 'relative';
- continue;
- }
- break;
- case 'relative':
- this._isRelative = true;
- if (this._scheme !== 'file') {
- this._scheme = base._scheme;
- }
- if (c === EOF) {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = base._query;
- this._username = base._username;
- this._password = base._password;
- break loop;
- } else if (c === '/' || c === '\\') {
- if (c === '\\') {
- err('\\ is an invalid code point.');
- }
- state = 'relative slash';
- } else if (c === '?') {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = '?';
- this._username = base._username;
- this._password = base._password;
- state = 'query';
- } else if (c === '#') {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = base._query;
- this._fragment = '#';
- this._username = base._username;
- this._password = base._password;
- state = 'fragment';
- } else {
- var nextC = input[cursor + 1];
- var nextNextC = input[cursor + 2];
- if (this._scheme !== 'file' || !ALPHA.test(c) || nextC !== ':' && nextC !== '|' || nextNextC !== EOF && nextNextC !== '/' && nextNextC !== '\\' && nextNextC !== '?' && nextNextC !== '#') {
- this._host = base._host;
- this._port = base._port;
- this._username = base._username;
- this._password = base._password;
- this._path = base._path.slice();
- this._path.pop();
- }
- state = 'relative path';
- continue;
- }
- break;
- case 'relative slash':
- if (c === '/' || c === '\\') {
- if (c === '\\') {
- err('\\ is an invalid code point.');
- }
- if (this._scheme === 'file') {
- state = 'file host';
- } else {
- state = 'authority ignore slashes';
- }
- } else {
- if (this._scheme !== 'file') {
- this._host = base._host;
- this._port = base._port;
- this._username = base._username;
- this._password = base._password;
- }
- state = 'relative path';
- continue;
- }
- break;
- case 'authority first slash':
- if (c === '/') {
- state = 'authority second slash';
- } else {
- err('Expected \'/\', got: ' + c);
- state = 'authority ignore slashes';
- continue;
- }
- break;
- case 'authority second slash':
- state = 'authority ignore slashes';
- if (c !== '/') {
- err('Expected \'/\', got: ' + c);
- continue;
- }
- break;
- case 'authority ignore slashes':
- if (c !== '/' && c !== '\\') {
- state = 'authority';
- continue;
- } else {
- err('Expected authority, got: ' + c);
- }
- break;
- case 'authority':
- if (c === '@') {
- if (seenAt) {
- err('@ already seen.');
- buffer += '%40';
- }
- seenAt = true;
- for (var i = 0; i < buffer.length; i++) {
- var cp = buffer[i];
- if (cp === '\t' || cp === '\n' || cp === '\r') {
- err('Invalid whitespace in authority.');
- continue;
- }
- if (cp === ':' && this._password === null) {
- this._password = '';
- continue;
- }
- var tempC = percentEscape(cp);
- if (this._password !== null) {
- this._password += tempC;
- } else {
- this._username += tempC;
- }
- }
- buffer = '';
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- cursor -= buffer.length;
- buffer = '';
- state = 'host';
- continue;
- } else {
- buffer += c;
- }
- break;
- case 'file host':
- if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- if (buffer.length === 2 && ALPHA.test(buffer[0]) && (buffer[1] === ':' || buffer[1] === '|')) {
- state = 'relative path';
- } else if (buffer.length === 0) {
- state = 'relative path start';
- } else {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'relative path start';
- }
- continue;
- } else if (c === '\t' || c === '\n' || c === '\r') {
- err('Invalid whitespace in file host.');
- } else {
- buffer += c;
- }
- break;
- case 'host':
- case 'hostname':
- if (c === ':' && !seenBracket) {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'port';
- if (stateOverride === 'hostname') {
- break loop;
- }
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'relative path start';
- if (stateOverride) {
- break loop;
- }
- continue;
- } else if (c !== '\t' && c !== '\n' && c !== '\r') {
- if (c === '[') {
- seenBracket = true;
- } else if (c === ']') {
- seenBracket = false;
- }
- buffer += c;
- } else {
- err('Invalid code point in host/hostname: ' + c);
- }
- break;
- case 'port':
- if (/[0-9]/.test(c)) {
- buffer += c;
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#' || stateOverride) {
- if (buffer !== '') {
- var temp = parseInt(buffer, 10);
- if (temp !== relative[this._scheme]) {
- this._port = temp + '';
- }
- buffer = '';
- }
- if (stateOverride) {
- break loop;
- }
- state = 'relative path start';
- continue;
- } else if (c === '\t' || c === '\n' || c === '\r') {
- err('Invalid code point in port: ' + c);
- } else {
- invalid.call(this);
- }
- break;
- case 'relative path start':
- if (c === '\\') {
- err('\'\\\' not allowed in path.');
- }
- state = 'relative path';
- if (c !== '/' && c !== '\\') {
- continue;
- }
- break;
- case 'relative path':
- if (c === EOF || c === '/' || c === '\\' || !stateOverride && (c === '?' || c === '#')) {
- if (c === '\\') {
- err('\\ not allowed in relative path.');
- }
- var tmp;
- if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
- buffer = tmp;
- }
- if (buffer === '..') {
- this._path.pop();
- if (c !== '/' && c !== '\\') {
- this._path.push('');
- }
- } else if (buffer === '.' && c !== '/' && c !== '\\') {
- this._path.push('');
- } else if (buffer !== '.') {
- if (this._scheme === 'file' && this._path.length === 0 && buffer.length === 2 && ALPHA.test(buffer[0]) && buffer[1] === '|') {
- buffer = buffer[0] + ':';
- }
- this._path.push(buffer);
- }
- buffer = '';
- if (c === '?') {
- this._query = '?';
- state = 'query';
- } else if (c === '#') {
- this._fragment = '#';
- state = 'fragment';
- }
- } else if (c !== '\t' && c !== '\n' && c !== '\r') {
- buffer += percentEscape(c);
- }
- break;
- case 'query':
- if (!stateOverride && c === '#') {
- this._fragment = '#';
- state = 'fragment';
- } else if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._query += percentEscapeQuery(c);
- }
- break;
- case 'fragment':
- if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._fragment += c;
- }
- break;
- }
- cursor++;
- }
- }
- function clear() {
- this._scheme = '';
- this._schemeData = '';
- this._username = '';
- this._password = null;
- this._host = '';
- this._port = '';
- this._path = [];
- this._query = '';
- this._fragment = '';
- this._isInvalid = false;
- this._isRelative = false;
- }
- function JURL(url, base) {
- if (base !== undefined && !(base instanceof JURL)) {
- base = new JURL(String(base));
- }
- this._url = url;
- clear.call(this);
- var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
- parse.call(this, input, null, base);
- }
- JURL.prototype = {
- toString: function () {
- return this.href;
- },
- get href() {
- if (this._isInvalid) {
- return this._url;
- }
- var authority = '';
- if (this._username !== '' || this._password !== null) {
- authority = this._username + (this._password !== null ? ':' + this._password : '') + '@';
- }
- return this.protocol + (this._isRelative ? '//' + authority + this.host : '') + this.pathname + this._query + this._fragment;
- },
- set href(href) {
- clear.call(this);
- parse.call(this, href);
- },
- get protocol() {
- return this._scheme + ':';
- },
- set protocol(protocol) {
- if (this._isInvalid) {
- return;
- }
- parse.call(this, protocol + ':', 'scheme start');
- },
- get host() {
- return this._isInvalid ? '' : this._port ? this._host + ':' + this._port : this._host;
- },
- set host(host) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, host, 'host');
- },
- get hostname() {
- return this._host;
- },
- set hostname(hostname) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, hostname, 'hostname');
- },
- get port() {
- return this._port;
- },
- set port(port) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, port, 'port');
- },
- get pathname() {
- return this._isInvalid ? '' : this._isRelative ? '/' + this._path.join('/') : this._schemeData;
- },
- set pathname(pathname) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- this._path = [];
- parse.call(this, pathname, 'relative path start');
- },
- get search() {
- return this._isInvalid || !this._query || this._query === '?' ? '' : this._query;
- },
- set search(search) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- this._query = '?';
- if (search[0] === '?') {
- search = search.slice(1);
- }
- parse.call(this, search, 'query');
- },
- get hash() {
- return this._isInvalid || !this._fragment || this._fragment === '#' ? '' : this._fragment;
- },
- set hash(hash) {
- if (this._isInvalid) {
- return;
- }
- this._fragment = '#';
- if (hash[0] === '#') {
- hash = hash.slice(1);
- }
- parse.call(this, hash, 'fragment');
- },
- get origin() {
- var host;
- if (this._isInvalid || !this._scheme) {
- return '';
- }
- switch (this._scheme) {
- case 'data':
- case 'file':
- case 'javascript':
- case 'mailto':
- return 'null';
- }
- host = this.host;
- if (!host) {
- return '';
- }
- return this._scheme + '://' + host;
- }
- };
- var OriginalURL = globalScope.URL;
- if (OriginalURL) {
- JURL.createObjectURL = function (blob) {
- return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
- };
- JURL.revokeObjectURL = function (url) {
- OriginalURL.revokeObjectURL(url);
- };
- }
- globalScope.URL = JURL;
- })();
-}
-/* WEBPACK VAR INJECTION */}.call(exports, __w_pdfjs_require__(6)))
-
-/***/ })
-/******/ ]);
-}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdf.min.js b/vendor/assets/javascripts/pdf.min.js
deleted file mode 100755
index 271a471fd49..00000000000
--- a/vendor/assets/javascripts/pdf.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-!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
deleted file mode 100755
index 6caaf016ebc..00000000000
--- a/vendor/assets/javascripts/pdf.worker.js
+++ /dev/null
@@ -1,38338 +0,0 @@
-/* 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.
- * 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.
- */
-
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define("pdfjs-dist/build/pdf.worker", [], factory);
- else if(typeof exports === 'object')
- exports["pdfjs-dist/build/pdf.worker"] = factory();
- else
- root["pdfjs-dist/build/pdf.worker"] = root.pdfjsDistBuildPdfWorker = factory();
-})(this, function() {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __w_pdfjs_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, __w_pdfjs_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __w_pdfjs_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __w_pdfjs_require__.c = installedModules;
-/******/
-/******/ // identity function for calling harmony imports with the correct context
-/******/ __w_pdfjs_require__.i = function(value) { return value; };
-/******/
-/******/ // define getter function for harmony exports
-/******/ __w_pdfjs_require__.d = function(exports, name, getter) {
-/******/ if(!__w_pdfjs_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, {
-/******/ configurable: false,
-/******/ enumerable: true,
-/******/ get: getter
-/******/ });
-/******/ }
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __w_pdfjs_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __w_pdfjs_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __w_pdfjs_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __w_pdfjs_require__.p = "";
-/******/
-/******/ // Load entry module and return exports
-/******/ return __w_pdfjs_require__(__w_pdfjs_require__.s = 36);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-var compatibility = __w_pdfjs_require__(37);
-var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
-var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
-var TextRenderingMode = {
- 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
-};
-var ImageKind = {
- GRAYSCALE_1BPP: 1,
- RGB_24BPP: 2,
- RGBA_32BPP: 3
-};
-var AnnotationType = {
- 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
-};
-var AnnotationFlag = {
- INVISIBLE: 0x01,
- HIDDEN: 0x02,
- PRINT: 0x04,
- NOZOOM: 0x08,
- NOROTATE: 0x10,
- NOVIEW: 0x20,
- READONLY: 0x40,
- LOCKED: 0x80,
- TOGGLENOVIEW: 0x100,
- LOCKEDCONTENTS: 0x200
-};
-var AnnotationFieldFlag = {
- READONLY: 0x0000001,
- REQUIRED: 0x0000002,
- NOEXPORT: 0x0000004,
- MULTILINE: 0x0001000,
- PASSWORD: 0x0002000,
- NOTOGGLETOOFF: 0x0004000,
- RADIO: 0x0008000,
- PUSHBUTTON: 0x0010000,
- COMBO: 0x0020000,
- EDIT: 0x0040000,
- SORT: 0x0080000,
- FILESELECT: 0x0100000,
- MULTISELECT: 0x0200000,
- DONOTSPELLCHECK: 0x0400000,
- DONOTSCROLL: 0x0800000,
- COMB: 0x1000000,
- RICHTEXT: 0x2000000,
- RADIOSINUNISON: 0x2000000,
- COMMITONSELCHANGE: 0x4000000
-};
-var AnnotationBorderStyleType = {
- SOLID: 1,
- DASHED: 2,
- BEVELED: 3,
- INSET: 4,
- UNDERLINE: 5
-};
-var StreamType = {
- UNKNOWN: 0,
- FLATE: 1,
- LZW: 2,
- DCT: 3,
- JPX: 4,
- JBIG: 5,
- A85: 6,
- AHX: 7,
- CCF: 8,
- RL: 9
-};
-var FontType = {
- UNKNOWN: 0,
- TYPE1: 1,
- TYPE1C: 2,
- CIDFONTTYPE0: 3,
- CIDFONTTYPE0C: 4,
- TRUETYPE: 5,
- CIDFONTTYPE2: 6,
- TYPE3: 7,
- OPENTYPE: 8,
- TYPE0: 9,
- MMTYPE1: 10
-};
-var VERBOSITY_LEVELS = {
- errors: 0,
- warnings: 1,
- infos: 5
-};
-var CMapCompressionType = {
- NONE: 0,
- BINARY: 1,
- STREAM: 2
-};
-var OPS = {
- 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
-};
-var verbosity = VERBOSITY_LEVELS.warnings;
-function setVerbosityLevel(level) {
- verbosity = level;
-}
-function getVerbosityLevel() {
- return verbosity;
-}
-function info(msg) {
- if (verbosity >= VERBOSITY_LEVELS.infos) {
- console.log('Info: ' + msg);
- }
-}
-function warn(msg) {
- if (verbosity >= VERBOSITY_LEVELS.warnings) {
- console.log('Warning: ' + msg);
- }
-}
-function deprecated(details) {
- console.log('Deprecated API usage: ' + details);
-}
-function error(msg) {
- if (verbosity >= VERBOSITY_LEVELS.errors) {
- console.log('Error: ' + msg);
- console.log(backtrace());
- }
- throw new Error(msg);
-}
-function backtrace() {
- try {
- throw new Error();
- } catch (e) {
- return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
- }
-}
-function assert(cond, msg) {
- if (!cond) {
- error(msg);
- }
-}
-var UNSUPPORTED_FEATURES = {
- unknown: 'unknown',
- forms: 'forms',
- javaScript: 'javaScript',
- smask: 'smask',
- shadingPattern: 'shadingPattern',
- font: 'font'
-};
-function isSameOrigin(baseUrl, otherUrl) {
- try {
- var base = new URL(baseUrl);
- if (!base.origin || base.origin === 'null') {
- return false;
- }
- } catch (e) {
- return false;
- }
- var other = new URL(otherUrl, base);
- return base.origin === other.origin;
-}
-function isValidProtocol(url) {
- if (!url) {
- return false;
- }
- switch (url.protocol) {
- case 'http:':
- case 'https:':
- case 'ftp:':
- case 'mailto:':
- case 'tel:':
- return true;
- default:
- return false;
- }
-}
-function createValidAbsoluteUrl(url, baseUrl) {
- if (!url) {
- return null;
- }
- try {
- var absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
- if (isValidProtocol(absoluteUrl)) {
- return absoluteUrl;
- }
- } catch (ex) {}
- return null;
-}
-function shadow(obj, prop, value) {
- Object.defineProperty(obj, prop, {
- value: value,
- enumerable: true,
- configurable: true,
- writable: false
- });
- return value;
-}
-function getLookupTableFactory(initializer) {
- var lookup;
- return function () {
- if (initializer) {
- lookup = Object.create(null);
- initializer(lookup);
- initializer = null;
- }
- return lookup;
- };
-}
-var PasswordResponses = {
- NEED_PASSWORD: 1,
- INCORRECT_PASSWORD: 2
-};
-var PasswordException = function PasswordExceptionClosure() {
- function PasswordException(msg, code) {
- this.name = 'PasswordException';
- this.message = msg;
- this.code = code;
- }
- PasswordException.prototype = new Error();
- PasswordException.constructor = PasswordException;
- return PasswordException;
-}();
-var UnknownErrorException = function UnknownErrorExceptionClosure() {
- function UnknownErrorException(msg, details) {
- this.name = 'UnknownErrorException';
- this.message = msg;
- this.details = details;
- }
- UnknownErrorException.prototype = new Error();
- UnknownErrorException.constructor = UnknownErrorException;
- return UnknownErrorException;
-}();
-var InvalidPDFException = function InvalidPDFExceptionClosure() {
- function InvalidPDFException(msg) {
- this.name = 'InvalidPDFException';
- this.message = msg;
- }
- InvalidPDFException.prototype = new Error();
- InvalidPDFException.constructor = InvalidPDFException;
- return InvalidPDFException;
-}();
-var MissingPDFException = function MissingPDFExceptionClosure() {
- function MissingPDFException(msg) {
- this.name = 'MissingPDFException';
- this.message = msg;
- }
- MissingPDFException.prototype = new Error();
- MissingPDFException.constructor = MissingPDFException;
- return MissingPDFException;
-}();
-var UnexpectedResponseException = function UnexpectedResponseExceptionClosure() {
- function UnexpectedResponseException(msg, status) {
- this.name = 'UnexpectedResponseException';
- this.message = msg;
- this.status = status;
- }
- UnexpectedResponseException.prototype = new Error();
- UnexpectedResponseException.constructor = UnexpectedResponseException;
- return UnexpectedResponseException;
-}();
-var NotImplementedException = function NotImplementedExceptionClosure() {
- function NotImplementedException(msg) {
- this.message = msg;
- }
- NotImplementedException.prototype = new Error();
- NotImplementedException.prototype.name = 'NotImplementedException';
- NotImplementedException.constructor = NotImplementedException;
- return NotImplementedException;
-}();
-var MissingDataException = function MissingDataExceptionClosure() {
- function MissingDataException(begin, end) {
- this.begin = begin;
- this.end = end;
- this.message = 'Missing data [' + begin + ', ' + end + ')';
- }
- MissingDataException.prototype = new Error();
- MissingDataException.prototype.name = 'MissingDataException';
- MissingDataException.constructor = MissingDataException;
- return MissingDataException;
-}();
-var XRefParseException = function XRefParseExceptionClosure() {
- function XRefParseException(msg) {
- this.message = msg;
- }
- XRefParseException.prototype = new Error();
- XRefParseException.prototype.name = 'XRefParseException';
- XRefParseException.constructor = XRefParseException;
- return XRefParseException;
-}();
-var NullCharactersRegExp = /\x00/g;
-function removeNullCharacters(str) {
- if (typeof str !== 'string') {
- warn('The argument for removeNullCharacters must be a string.');
- return str;
- }
- return str.replace(NullCharactersRegExp, '');
-}
-function bytesToString(bytes) {
- assert(bytes !== null && typeof bytes === 'object' && bytes.length !== undefined, 'Invalid argument for bytesToString');
- var length = bytes.length;
- var MAX_ARGUMENT_COUNT = 8192;
- if (length < MAX_ARGUMENT_COUNT) {
- return String.fromCharCode.apply(null, bytes);
- }
- var strBuf = [];
- for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
- var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
- var chunk = bytes.subarray(i, chunkEnd);
- strBuf.push(String.fromCharCode.apply(null, chunk));
- }
- return strBuf.join('');
-}
-function stringToBytes(str) {
- assert(typeof str === 'string', 'Invalid argument for stringToBytes');
- var length = str.length;
- var bytes = new Uint8Array(length);
- for (var i = 0; i < length; ++i) {
- bytes[i] = str.charCodeAt(i) & 0xFF;
- }
- return bytes;
-}
-function arrayByteLength(arr) {
- if (arr.length !== undefined) {
- return arr.length;
- }
- assert(arr.byteLength !== undefined);
- return arr.byteLength;
-}
-function arraysToBytes(arr) {
- if (arr.length === 1 && arr[0] instanceof Uint8Array) {
- return arr[0];
- }
- var resultLength = 0;
- var i,
- ii = arr.length;
- var item, itemLength;
- for (i = 0; i < ii; i++) {
- item = arr[i];
- itemLength = arrayByteLength(item);
- resultLength += itemLength;
- }
- var pos = 0;
- var data = new Uint8Array(resultLength);
- for (i = 0; i < ii; i++) {
- item = arr[i];
- if (!(item instanceof Uint8Array)) {
- if (typeof item === 'string') {
- item = stringToBytes(item);
- } else {
- item = new Uint8Array(item);
- }
- }
- itemLength = item.byteLength;
- data.set(item, pos);
- pos += itemLength;
- }
- return data;
-}
-function string32(value) {
- return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
-}
-function log2(x) {
- var n = 1,
- i = 0;
- while (x > n) {
- n <<= 1;
- i++;
- }
- return i;
-}
-function readInt8(data, start) {
- return data[start] << 24 >> 24;
-}
-function readUint16(data, offset) {
- return data[offset] << 8 | data[offset + 1];
-}
-function readUint32(data, offset) {
- return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
-}
-function isLittleEndian() {
- var buffer8 = new Uint8Array(2);
- buffer8[0] = 1;
- var buffer16 = new Uint16Array(buffer8.buffer);
- return buffer16[0] === 1;
-}
-function isEvalSupported() {
- try {
- new Function('');
- return true;
- } catch (e) {
- return false;
- }
-}
-var Uint32ArrayView = function Uint32ArrayViewClosure() {
- function Uint32ArrayView(buffer, length) {
- this.buffer = buffer;
- this.byteLength = buffer.length;
- this.length = length === undefined ? this.byteLength >> 2 : length;
- ensureUint32ArrayViewProps(this.length);
- }
- Uint32ArrayView.prototype = Object.create(null);
- var uint32ArrayViewSetters = 0;
- function createUint32ArrayProp(index) {
- return {
- get: function () {
- var buffer = this.buffer,
- offset = index << 2;
- return (buffer[offset] | buffer[offset + 1] << 8 | buffer[offset + 2] << 16 | buffer[offset + 3] << 24) >>> 0;
- },
- set: function (value) {
- var buffer = this.buffer,
- offset = index << 2;
- buffer[offset] = value & 255;
- buffer[offset + 1] = value >> 8 & 255;
- buffer[offset + 2] = value >> 16 & 255;
- buffer[offset + 3] = value >>> 24 & 255;
- }
- };
- }
- function ensureUint32ArrayViewProps(length) {
- while (uint32ArrayViewSetters < length) {
- Object.defineProperty(Uint32ArrayView.prototype, uint32ArrayViewSetters, createUint32ArrayProp(uint32ArrayViewSetters));
- uint32ArrayViewSetters++;
- }
- }
- return Uint32ArrayView;
-}();
-exports.Uint32ArrayView = Uint32ArrayView;
-var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
-var Util = function UtilClosure() {
- function Util() {}
- var rgbBuf = ['rgb(', 0, ',', 0, ',', 0, ')'];
- Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
- rgbBuf[1] = r;
- rgbBuf[3] = g;
- rgbBuf[5] = b;
- return rgbBuf.join('');
- };
- Util.transform = function Util_transform(m1, m2) {
- return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
- };
- Util.applyTransform = function Util_applyTransform(p, m) {
- var xt = p[0] * m[0] + p[1] * m[2] + m[4];
- var yt = p[0] * m[1] + p[1] * m[3] + m[5];
- return [xt, yt];
- };
- Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
- var d = m[0] * m[3] - m[1] * m[2];
- var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
- var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
- return [xt, yt];
- };
- Util.getAxialAlignedBoundingBox = function Util_getAxialAlignedBoundingBox(r, m) {
- var p1 = Util.applyTransform(r, m);
- var p2 = Util.applyTransform(r.slice(2, 4), m);
- var p3 = Util.applyTransform([r[0], r[3]], m);
- var p4 = Util.applyTransform([r[2], r[1]], m);
- return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
- };
- Util.inverseTransform = function Util_inverseTransform(m) {
- var d = m[0] * m[3] - m[1] * m[2];
- return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
- };
- Util.apply3dTransform = function Util_apply3dTransform(m, v) {
- return [m[0] * v[0] + m[1] * v[1] + m[2] * v[2], m[3] * v[0] + m[4] * v[1] + m[5] * v[2], m[6] * v[0] + m[7] * v[1] + m[8] * v[2]];
- };
- Util.singularValueDecompose2dScale = function Util_singularValueDecompose2dScale(m) {
- var transpose = [m[0], m[2], m[1], m[3]];
- var a = m[0] * transpose[0] + m[1] * transpose[2];
- var b = m[0] * transpose[1] + m[1] * transpose[3];
- var c = m[2] * transpose[0] + m[3] * transpose[2];
- var d = m[2] * transpose[1] + m[3] * transpose[3];
- var first = (a + d) / 2;
- var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
- var sx = first + second || 1;
- var sy = first - second || 1;
- return [Math.sqrt(sx), Math.sqrt(sy)];
- };
- Util.normalizeRect = function Util_normalizeRect(rect) {
- var r = rect.slice(0);
- if (rect[0] > rect[2]) {
- r[0] = rect[2];
- r[2] = rect[0];
- }
- if (rect[1] > rect[3]) {
- r[1] = rect[3];
- r[3] = rect[1];
- }
- return r;
- };
- Util.intersect = function Util_intersect(rect1, rect2) {
- function compare(a, b) {
- return a - b;
- }
- var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
- orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
- result = [];
- rect1 = Util.normalizeRect(rect1);
- rect2 = Util.normalizeRect(rect2);
- if (orderedX[0] === rect1[0] && orderedX[1] === rect2[0] || orderedX[0] === rect2[0] && orderedX[1] === rect1[0]) {
- result[0] = orderedX[1];
- result[2] = orderedX[2];
- } else {
- return false;
- }
- if (orderedY[0] === rect1[1] && orderedY[1] === rect2[1] || orderedY[0] === rect2[1] && orderedY[1] === rect1[1]) {
- result[1] = orderedY[1];
- result[3] = orderedY[2];
- } else {
- return false;
- }
- return result;
- };
- Util.sign = function Util_sign(num) {
- return num < 0 ? -1 : 1;
- };
- var ROMAN_NUMBER_MAP = ['', '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'];
- Util.toRoman = function Util_toRoman(number, lowerCase) {
- assert(isInt(number) && number > 0, 'The number should be a positive integer.');
- var pos,
- romanBuf = [];
- while (number >= 1000) {
- number -= 1000;
- romanBuf.push('M');
- }
- pos = number / 100 | 0;
- number %= 100;
- romanBuf.push(ROMAN_NUMBER_MAP[pos]);
- pos = number / 10 | 0;
- number %= 10;
- romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
- romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
- var romanStr = romanBuf.join('');
- return lowerCase ? romanStr.toLowerCase() : romanStr;
- };
- Util.appendToArray = function Util_appendToArray(arr1, arr2) {
- Array.prototype.push.apply(arr1, arr2);
- };
- Util.prependToArray = function Util_prependToArray(arr1, arr2) {
- Array.prototype.unshift.apply(arr1, arr2);
- };
- Util.extendObj = function extendObj(obj1, obj2) {
- for (var key in obj2) {
- obj1[key] = obj2[key];
- }
- };
- Util.getInheritableProperty = function Util_getInheritableProperty(dict, name, getArray) {
- while (dict && !dict.has(name)) {
- dict = dict.get('Parent');
- }
- if (!dict) {
- return null;
- }
- return getArray ? dict.getArray(name) : dict.get(name);
- };
- Util.inherit = function Util_inherit(sub, base, prototype) {
- sub.prototype = Object.create(base.prototype);
- sub.prototype.constructor = sub;
- for (var prop in prototype) {
- sub.prototype[prop] = prototype[prop];
- }
- };
- Util.loadScript = function Util_loadScript(src, callback) {
- var script = document.createElement('script');
- var loaded = false;
- script.setAttribute('src', src);
- if (callback) {
- script.onload = function () {
- if (!loaded) {
- callback();
- }
- loaded = true;
- };
- }
- document.getElementsByTagName('head')[0].appendChild(script);
- };
- return Util;
-}();
-var PageViewport = function PageViewportClosure() {
- function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
- this.viewBox = viewBox;
- this.scale = scale;
- this.rotation = rotation;
- this.offsetX = offsetX;
- this.offsetY = offsetY;
- var centerX = (viewBox[2] + viewBox[0]) / 2;
- var centerY = (viewBox[3] + viewBox[1]) / 2;
- var rotateA, rotateB, rotateC, rotateD;
- rotation = rotation % 360;
- rotation = rotation < 0 ? rotation + 360 : rotation;
- switch (rotation) {
- case 180:
- rotateA = -1;
- rotateB = 0;
- rotateC = 0;
- rotateD = 1;
- break;
- case 90:
- rotateA = 0;
- rotateB = 1;
- rotateC = 1;
- rotateD = 0;
- break;
- case 270:
- rotateA = 0;
- rotateB = -1;
- rotateC = -1;
- rotateD = 0;
- break;
- default:
- rotateA = 1;
- rotateB = 0;
- rotateC = 0;
- rotateD = -1;
- break;
- }
- if (dontFlip) {
- rotateC = -rotateC;
- rotateD = -rotateD;
- }
- var offsetCanvasX, offsetCanvasY;
- var width, height;
- if (rotateA === 0) {
- offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
- offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
- width = Math.abs(viewBox[3] - viewBox[1]) * scale;
- height = Math.abs(viewBox[2] - viewBox[0]) * scale;
- } else {
- offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
- offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
- width = Math.abs(viewBox[2] - viewBox[0]) * scale;
- height = Math.abs(viewBox[3] - viewBox[1]) * scale;
- }
- this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
- this.width = width;
- this.height = height;
- this.fontScale = scale;
- }
- PageViewport.prototype = {
- clone: function PageViewPort_clone(args) {
- args = args || {};
- var scale = 'scale' in args ? args.scale : this.scale;
- var rotation = 'rotation' in args ? args.rotation : this.rotation;
- return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip);
- },
- convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
- return Util.applyTransform([x, y], this.transform);
- },
- convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) {
- var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
- var br = Util.applyTransform([rect[2], rect[3]], this.transform);
- return [tl[0], tl[1], br[0], br[1]];
- },
- convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
- return Util.applyInverseTransform([x, y], this.transform);
- }
- };
- return PageViewport;
-}();
-var PDFStringTranslateTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC];
-function stringToPDFString(str) {
- var i,
- n = str.length,
- strBuf = [];
- if (str[0] === '\xFE' && str[1] === '\xFF') {
- for (i = 2; i < n; i += 2) {
- strBuf.push(String.fromCharCode(str.charCodeAt(i) << 8 | str.charCodeAt(i + 1)));
- }
- } else {
- for (i = 0; i < n; ++i) {
- var code = PDFStringTranslateTable[str.charCodeAt(i)];
- strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
- }
- }
- return strBuf.join('');
-}
-function stringToUTF8String(str) {
- return decodeURIComponent(escape(str));
-}
-function utf8StringToString(str) {
- return unescape(encodeURIComponent(str));
-}
-function isEmptyObj(obj) {
- for (var key in obj) {
- return false;
- }
- return true;
-}
-function isBool(v) {
- return typeof v === 'boolean';
-}
-function isInt(v) {
- return typeof v === 'number' && (v | 0) === v;
-}
-function isNum(v) {
- return typeof v === 'number';
-}
-function isString(v) {
- return typeof v === 'string';
-}
-function isArray(v) {
- return v instanceof Array;
-}
-function isArrayBuffer(v) {
- return typeof v === 'object' && v !== null && v.byteLength !== undefined;
-}
-function isSpace(ch) {
- return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
-}
-function isNodeJS() {
- if (typeof __pdfjsdev_webpack__ === 'undefined') {
- return typeof process === 'object' && process + '' === '[object process]';
- }
- return false;
-}
-function createPromiseCapability() {
- var capability = {};
- capability.promise = new Promise(function (resolve, reject) {
- capability.resolve = resolve;
- capability.reject = reject;
- });
- return capability;
-}
-var StatTimer = function StatTimerClosure() {
- function rpad(str, pad, length) {
- while (str.length < length) {
- str += pad;
- }
- return str;
- }
- function StatTimer() {
- this.started = Object.create(null);
- this.times = [];
- this.enabled = true;
- }
- StatTimer.prototype = {
- time: function StatTimer_time(name) {
- if (!this.enabled) {
- return;
- }
- if (name in this.started) {
- warn('Timer is already running for ' + name);
- }
- this.started[name] = Date.now();
- },
- timeEnd: function StatTimer_timeEnd(name) {
- if (!this.enabled) {
- return;
- }
- if (!(name in this.started)) {
- warn('Timer has not been started for ' + name);
- }
- this.times.push({
- 'name': name,
- 'start': this.started[name],
- 'end': Date.now()
- });
- delete this.started[name];
- },
- toString: function StatTimer_toString() {
- var i, ii;
- var times = this.times;
- var out = '';
- var longest = 0;
- for (i = 0, ii = times.length; i < ii; ++i) {
- var name = times[i]['name'];
- if (name.length > longest) {
- longest = name.length;
- }
- }
- for (i = 0, ii = times.length; i < ii; ++i) {
- var span = times[i];
- var duration = span.end - span.start;
- out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
- }
- return out;
- }
- };
- return StatTimer;
-}();
-var createBlob = function createBlob(data, contentType) {
- if (typeof Blob !== 'undefined') {
- return new Blob([data], { type: contentType });
- }
- warn('The "Blob" constructor is not supported.');
-};
-var createObjectURL = function createObjectURLClosure() {
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- return function createObjectURL(data, contentType, forceDataSchema) {
- if (!forceDataSchema && typeof URL !== 'undefined' && URL.createObjectURL) {
- var blob = createBlob(data, contentType);
- return URL.createObjectURL(blob);
- }
- var buffer = 'data:' + contentType + ';base64,';
- for (var i = 0, ii = data.length; i < ii; i += 3) {
- var b1 = data[i] & 0xFF;
- var b2 = data[i + 1] & 0xFF;
- var b3 = data[i + 2] & 0xFF;
- var d1 = b1 >> 2,
- d2 = (b1 & 3) << 4 | b2 >> 4;
- var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
- var d4 = i + 2 < ii ? b3 & 0x3F : 64;
- buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
- }
- return buffer;
- };
-}();
-function MessageHandler(sourceName, targetName, comObj) {
- this.sourceName = sourceName;
- this.targetName = targetName;
- this.comObj = comObj;
- this.callbackIndex = 1;
- this.postMessageTransfers = true;
- var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
- var ah = this.actionHandler = Object.create(null);
- this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
- var data = event.data;
- if (data.targetName !== this.sourceName) {
- return;
- }
- if (data.isReply) {
- var callbackId = data.callbackId;
- if (data.callbackId in callbacksCapabilities) {
- var callback = callbacksCapabilities[callbackId];
- delete callbacksCapabilities[callbackId];
- if ('error' in data) {
- callback.reject(data.error);
- } else {
- callback.resolve(data.data);
- }
- } else {
- error('Cannot resolve callback ' + callbackId);
- }
- } else if (data.action in ah) {
- var action = ah[data.action];
- if (data.callbackId) {
- var sourceName = this.sourceName;
- var targetName = data.sourceName;
- Promise.resolve().then(function () {
- return action[0].call(action[1], data.data);
- }).then(function (result) {
- comObj.postMessage({
- sourceName: sourceName,
- targetName: targetName,
- isReply: true,
- callbackId: data.callbackId,
- data: result
- });
- }, function (reason) {
- if (reason instanceof Error) {
- reason = reason + '';
- }
- comObj.postMessage({
- sourceName: sourceName,
- targetName: targetName,
- isReply: true,
- callbackId: data.callbackId,
- error: reason
- });
- });
- } else {
- action[0].call(action[1], data.data);
- }
- } else {
- error('Unknown action from worker: ' + data.action);
- }
- }.bind(this);
- comObj.addEventListener('message', this._onComObjOnMessage);
-}
-MessageHandler.prototype = {
- on: function messageHandlerOn(actionName, handler, scope) {
- var ah = this.actionHandler;
- if (ah[actionName]) {
- error('There is already an actionName called "' + actionName + '"');
- }
- ah[actionName] = [handler, scope];
- },
- send: function messageHandlerSend(actionName, data, transfers) {
- var message = {
- sourceName: this.sourceName,
- targetName: this.targetName,
- action: actionName,
- data: data
- };
- this.postMessage(message, transfers);
- },
- sendWithPromise: function messageHandlerSendWithPromise(actionName, data, transfers) {
- var callbackId = this.callbackIndex++;
- var message = {
- sourceName: this.sourceName,
- targetName: this.targetName,
- action: actionName,
- data: data,
- callbackId: callbackId
- };
- var capability = createPromiseCapability();
- this.callbacksCapabilities[callbackId] = capability;
- try {
- this.postMessage(message, transfers);
- } catch (e) {
- capability.reject(e);
- }
- return capability.promise;
- },
- postMessage: function (message, transfers) {
- if (transfers && this.postMessageTransfers) {
- this.comObj.postMessage(message, transfers);
- } else {
- this.comObj.postMessage(message);
- }
- },
- destroy: function () {
- this.comObj.removeEventListener('message', this._onComObjOnMessage);
- }
-};
-function loadJpegStream(id, imageUrl, objs) {
- var img = new Image();
- img.onload = function loadJpegStream_onloadClosure() {
- objs.resolve(id, img);
- };
- img.onerror = function loadJpegStream_onerrorClosure() {
- objs.resolve(id, null);
- warn('Error during JPEG image loading');
- };
- img.src = imageUrl;
-}
-exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
-exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
-exports.OPS = OPS;
-exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS;
-exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
-exports.AnnotationBorderStyleType = AnnotationBorderStyleType;
-exports.AnnotationFieldFlag = AnnotationFieldFlag;
-exports.AnnotationFlag = AnnotationFlag;
-exports.AnnotationType = AnnotationType;
-exports.FontType = FontType;
-exports.ImageKind = ImageKind;
-exports.CMapCompressionType = CMapCompressionType;
-exports.InvalidPDFException = InvalidPDFException;
-exports.MessageHandler = MessageHandler;
-exports.MissingDataException = MissingDataException;
-exports.MissingPDFException = MissingPDFException;
-exports.NotImplementedException = NotImplementedException;
-exports.PageViewport = PageViewport;
-exports.PasswordException = PasswordException;
-exports.PasswordResponses = PasswordResponses;
-exports.StatTimer = StatTimer;
-exports.StreamType = StreamType;
-exports.TextRenderingMode = TextRenderingMode;
-exports.UnexpectedResponseException = UnexpectedResponseException;
-exports.UnknownErrorException = UnknownErrorException;
-exports.Util = Util;
-exports.XRefParseException = XRefParseException;
-exports.arrayByteLength = arrayByteLength;
-exports.arraysToBytes = arraysToBytes;
-exports.assert = assert;
-exports.bytesToString = bytesToString;
-exports.createBlob = createBlob;
-exports.createPromiseCapability = createPromiseCapability;
-exports.createObjectURL = createObjectURL;
-exports.deprecated = deprecated;
-exports.error = error;
-exports.getLookupTableFactory = getLookupTableFactory;
-exports.getVerbosityLevel = getVerbosityLevel;
-exports.globalScope = globalScope;
-exports.info = info;
-exports.isArray = isArray;
-exports.isArrayBuffer = isArrayBuffer;
-exports.isBool = isBool;
-exports.isEmptyObj = isEmptyObj;
-exports.isInt = isInt;
-exports.isNum = isNum;
-exports.isString = isString;
-exports.isSpace = isSpace;
-exports.isNodeJS = isNodeJS;
-exports.isSameOrigin = isSameOrigin;
-exports.createValidAbsoluteUrl = createValidAbsoluteUrl;
-exports.isLittleEndian = isLittleEndian;
-exports.isEvalSupported = isEvalSupported;
-exports.loadJpegStream = loadJpegStream;
-exports.log2 = log2;
-exports.readInt8 = readInt8;
-exports.readUint16 = readUint16;
-exports.readUint32 = readUint32;
-exports.removeNullCharacters = removeNullCharacters;
-exports.setVerbosityLevel = setVerbosityLevel;
-exports.shadow = shadow;
-exports.string32 = string32;
-exports.stringToBytes = stringToBytes;
-exports.stringToPDFString = stringToPDFString;
-exports.stringToUTF8String = stringToUTF8String;
-exports.utf8StringToString = utf8StringToString;
-exports.warn = warn;
-/* WEBPACK VAR INJECTION */}.call(exports, __w_pdfjs_require__(9)))
-
-/***/ }),
-/* 1 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var isArray = sharedUtil.isArray;
-var EOF = {};
-var Name = function NameClosure() {
- function Name(name) {
- this.name = name;
- }
- Name.prototype = {};
- var nameCache = Object.create(null);
- Name.get = function Name_get(name) {
- var nameValue = nameCache[name];
- return nameValue ? nameValue : nameCache[name] = new Name(name);
- };
- return Name;
-}();
-var Cmd = function CmdClosure() {
- function Cmd(cmd) {
- this.cmd = cmd;
- }
- Cmd.prototype = {};
- var cmdCache = Object.create(null);
- Cmd.get = function Cmd_get(cmd) {
- var cmdValue = cmdCache[cmd];
- return cmdValue ? cmdValue : cmdCache[cmd] = new Cmd(cmd);
- };
- return Cmd;
-}();
-var Dict = function DictClosure() {
- var nonSerializable = function nonSerializableClosure() {
- return nonSerializable;
- };
- function Dict(xref) {
- this.map = Object.create(null);
- this.xref = xref;
- this.objId = null;
- this.suppressEncryption = false;
- this.__nonSerializable__ = nonSerializable;
- }
- Dict.prototype = {
- assignXref: function Dict_assignXref(newXref) {
- this.xref = newXref;
- },
- get: function Dict_get(key1, key2, key3) {
- var value;
- var xref = this.xref,
- suppressEncryption = this.suppressEncryption;
- if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') {
- return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
- }
- if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') {
- return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
- }
- value = this.map[key3] || null;
- return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
- },
- getAsync: function Dict_getAsync(key1, key2, key3) {
- var value;
- var xref = this.xref,
- suppressEncryption = this.suppressEncryption;
- if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') {
- if (xref) {
- return xref.fetchIfRefAsync(value, suppressEncryption);
- }
- return Promise.resolve(value);
- }
- if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') {
- if (xref) {
- return xref.fetchIfRefAsync(value, suppressEncryption);
- }
- return Promise.resolve(value);
- }
- value = this.map[key3] || null;
- if (xref) {
- return xref.fetchIfRefAsync(value, suppressEncryption);
- }
- return Promise.resolve(value);
- },
- getArray: function Dict_getArray(key1, key2, key3) {
- var value = this.get(key1, key2, key3);
- var xref = this.xref,
- suppressEncryption = this.suppressEncryption;
- if (!isArray(value) || !xref) {
- return value;
- }
- value = value.slice();
- for (var i = 0, ii = value.length; i < ii; i++) {
- if (!isRef(value[i])) {
- continue;
- }
- value[i] = xref.fetch(value[i], suppressEncryption);
- }
- return value;
- },
- getRaw: function Dict_getRaw(key) {
- return this.map[key];
- },
- getKeys: function Dict_getKeys() {
- return Object.keys(this.map);
- },
- set: function Dict_set(key, value) {
- this.map[key] = value;
- },
- has: function Dict_has(key) {
- return key in this.map;
- },
- forEach: function Dict_forEach(callback) {
- for (var key in this.map) {
- callback(key, this.get(key));
- }
- }
- };
- Dict.empty = new Dict(null);
- Dict.merge = function Dict_merge(xref, dictArray) {
- var mergedDict = new Dict(xref);
- for (var i = 0, ii = dictArray.length; i < ii; i++) {
- var dict = dictArray[i];
- if (!isDict(dict)) {
- continue;
- }
- for (var keyName in dict.map) {
- if (mergedDict.map[keyName]) {
- continue;
- }
- mergedDict.map[keyName] = dict.map[keyName];
- }
- }
- return mergedDict;
- };
- return Dict;
-}();
-var Ref = function RefClosure() {
- function Ref(num, gen) {
- this.num = num;
- this.gen = gen;
- }
- Ref.prototype = {
- toString: function Ref_toString() {
- var str = this.num + 'R';
- if (this.gen !== 0) {
- str += this.gen;
- }
- return str;
- }
- };
- return Ref;
-}();
-var RefSet = function RefSetClosure() {
- function RefSet() {
- this.dict = Object.create(null);
- }
- RefSet.prototype = {
- has: function RefSet_has(ref) {
- return ref.toString() in this.dict;
- },
- put: function RefSet_put(ref) {
- this.dict[ref.toString()] = true;
- },
- remove: function RefSet_remove(ref) {
- delete this.dict[ref.toString()];
- }
- };
- return RefSet;
-}();
-var RefSetCache = function RefSetCacheClosure() {
- function RefSetCache() {
- this.dict = Object.create(null);
- }
- RefSetCache.prototype = {
- get: function RefSetCache_get(ref) {
- return this.dict[ref.toString()];
- },
- has: function RefSetCache_has(ref) {
- return ref.toString() in this.dict;
- },
- put: function RefSetCache_put(ref, obj) {
- this.dict[ref.toString()] = obj;
- },
- putAlias: function RefSetCache_putAlias(ref, aliasRef) {
- this.dict[ref.toString()] = this.get(aliasRef);
- },
- forEach: function RefSetCache_forEach(fn, thisArg) {
- for (var i in this.dict) {
- fn.call(thisArg, this.dict[i]);
- }
- },
- clear: function RefSetCache_clear() {
- this.dict = Object.create(null);
- }
- };
- return RefSetCache;
-}();
-function isEOF(v) {
- return v === EOF;
-}
-function isName(v, name) {
- return v instanceof Name && (name === undefined || v.name === name);
-}
-function isCmd(v, cmd) {
- return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
-}
-function isDict(v, type) {
- return v instanceof Dict && (type === undefined || isName(v.get('Type'), type));
-}
-function isRef(v) {
- return v instanceof Ref;
-}
-function isRefsEqual(v1, v2) {
- return v1.num === v2.num && v1.gen === v2.gen;
-}
-function isStream(v) {
- return typeof v === 'object' && v !== null && v.getBytes !== undefined;
-}
-exports.EOF = EOF;
-exports.Cmd = Cmd;
-exports.Dict = Dict;
-exports.Name = Name;
-exports.Ref = Ref;
-exports.RefSet = RefSet;
-exports.RefSetCache = RefSetCache;
-exports.isEOF = isEOF;
-exports.isCmd = isCmd;
-exports.isDict = isDict;
-exports.isName = isName;
-exports.isRef = isRef;
-exports.isRefsEqual = isRefsEqual;
-exports.isStream = isStream;
-
-/***/ }),
-/* 2 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreJbig2 = __w_pdfjs_require__(28);
-var coreJpg = __w_pdfjs_require__(29);
-var coreJpx = __w_pdfjs_require__(15);
-var Util = sharedUtil.Util;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isInt = sharedUtil.isInt;
-var isArray = sharedUtil.isArray;
-var createObjectURL = sharedUtil.createObjectURL;
-var shadow = sharedUtil.shadow;
-var isSpace = sharedUtil.isSpace;
-var Dict = corePrimitives.Dict;
-var isDict = corePrimitives.isDict;
-var isStream = corePrimitives.isStream;
-var Jbig2Image = coreJbig2.Jbig2Image;
-var JpegImage = coreJpg.JpegImage;
-var JpxImage = coreJpx.JpxImage;
-var Stream = function StreamClosure() {
- function Stream(arrayBuffer, start, length, dict) {
- this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);
- this.start = start || 0;
- this.pos = this.start;
- this.end = start + length || this.bytes.length;
- this.dict = dict;
- }
- Stream.prototype = {
- get length() {
- return this.end - this.start;
- },
- get isEmpty() {
- return this.length === 0;
- },
- getByte: function Stream_getByte() {
- if (this.pos >= this.end) {
- return -1;
- }
- return this.bytes[this.pos++];
- },
- getUint16: function Stream_getUint16() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- if (b0 === -1 || b1 === -1) {
- return -1;
- }
- return (b0 << 8) + b1;
- },
- getInt32: function Stream_getInt32() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- var b2 = this.getByte();
- var b3 = this.getByte();
- return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
- },
- getBytes: function Stream_getBytes(length) {
- var bytes = this.bytes;
- var pos = this.pos;
- var strEnd = this.end;
- if (!length) {
- return bytes.subarray(pos, strEnd);
- }
- var end = pos + length;
- if (end > strEnd) {
- end = strEnd;
- }
- this.pos = end;
- return bytes.subarray(pos, end);
- },
- peekByte: function Stream_peekByte() {
- var peekedByte = this.getByte();
- this.pos--;
- return peekedByte;
- },
- peekBytes: function Stream_peekBytes(length) {
- var bytes = this.getBytes(length);
- this.pos -= bytes.length;
- return bytes;
- },
- skip: function Stream_skip(n) {
- if (!n) {
- n = 1;
- }
- this.pos += n;
- },
- reset: function Stream_reset() {
- this.pos = this.start;
- },
- moveStart: function Stream_moveStart() {
- this.start = this.pos;
- },
- makeSubStream: function Stream_makeSubStream(start, length, dict) {
- return new Stream(this.bytes.buffer, start, length, dict);
- }
- };
- return Stream;
-}();
-var StringStream = function StringStreamClosure() {
- function StringStream(str) {
- var length = str.length;
- var bytes = new Uint8Array(length);
- for (var n = 0; n < length; ++n) {
- bytes[n] = str.charCodeAt(n);
- }
- Stream.call(this, bytes);
- }
- StringStream.prototype = Stream.prototype;
- return StringStream;
-}();
-var DecodeStream = function DecodeStreamClosure() {
- var emptyBuffer = new Uint8Array(0);
- function DecodeStream(maybeMinBufferLength) {
- this.pos = 0;
- this.bufferLength = 0;
- this.eof = false;
- this.buffer = emptyBuffer;
- this.minBufferLength = 512;
- if (maybeMinBufferLength) {
- while (this.minBufferLength < maybeMinBufferLength) {
- this.minBufferLength *= 2;
- }
- }
- }
- DecodeStream.prototype = {
- get isEmpty() {
- while (!this.eof && this.bufferLength === 0) {
- this.readBlock();
- }
- return this.bufferLength === 0;
- },
- ensureBuffer: function DecodeStream_ensureBuffer(requested) {
- var buffer = this.buffer;
- if (requested <= buffer.byteLength) {
- return buffer;
- }
- var size = this.minBufferLength;
- while (size < requested) {
- size *= 2;
- }
- var buffer2 = new Uint8Array(size);
- buffer2.set(buffer);
- return this.buffer = buffer2;
- },
- getByte: function DecodeStream_getByte() {
- var pos = this.pos;
- while (this.bufferLength <= pos) {
- if (this.eof) {
- return -1;
- }
- this.readBlock();
- }
- return this.buffer[this.pos++];
- },
- getUint16: function DecodeStream_getUint16() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- if (b0 === -1 || b1 === -1) {
- return -1;
- }
- return (b0 << 8) + b1;
- },
- getInt32: function DecodeStream_getInt32() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- var b2 = this.getByte();
- var b3 = this.getByte();
- return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
- },
- getBytes: function DecodeStream_getBytes(length) {
- var end,
- pos = this.pos;
- if (length) {
- this.ensureBuffer(pos + length);
- end = pos + length;
- while (!this.eof && this.bufferLength < end) {
- this.readBlock();
- }
- var bufEnd = this.bufferLength;
- if (end > bufEnd) {
- end = bufEnd;
- }
- } else {
- while (!this.eof) {
- this.readBlock();
- }
- end = this.bufferLength;
- }
- this.pos = end;
- return this.buffer.subarray(pos, end);
- },
- peekByte: function DecodeStream_peekByte() {
- var peekedByte = this.getByte();
- this.pos--;
- return peekedByte;
- },
- peekBytes: function DecodeStream_peekBytes(length) {
- var bytes = this.getBytes(length);
- this.pos -= bytes.length;
- return bytes;
- },
- makeSubStream: function DecodeStream_makeSubStream(start, length, dict) {
- var end = start + length;
- while (this.bufferLength <= end && !this.eof) {
- this.readBlock();
- }
- return new Stream(this.buffer, start, length, dict);
- },
- skip: function DecodeStream_skip(n) {
- if (!n) {
- n = 1;
- }
- this.pos += n;
- },
- reset: function DecodeStream_reset() {
- this.pos = 0;
- },
- getBaseStreams: function DecodeStream_getBaseStreams() {
- if (this.str && this.str.getBaseStreams) {
- return this.str.getBaseStreams();
- }
- return [];
- }
- };
- return DecodeStream;
-}();
-var StreamsSequenceStream = function StreamsSequenceStreamClosure() {
- function StreamsSequenceStream(streams) {
- this.streams = streams;
- DecodeStream.call(this, null);
- }
- StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype);
- StreamsSequenceStream.prototype.readBlock = function streamSequenceStreamReadBlock() {
- var streams = this.streams;
- if (streams.length === 0) {
- this.eof = true;
- return;
- }
- var stream = streams.shift();
- var chunk = stream.getBytes();
- var bufferLength = this.bufferLength;
- var newLength = bufferLength + chunk.length;
- var buffer = this.ensureBuffer(newLength);
- buffer.set(chunk, bufferLength);
- this.bufferLength = newLength;
- };
- StreamsSequenceStream.prototype.getBaseStreams = function StreamsSequenceStream_getBaseStreams() {
- var baseStreams = [];
- for (var i = 0, ii = this.streams.length; i < ii; i++) {
- var stream = this.streams[i];
- if (stream.getBaseStreams) {
- Util.appendToArray(baseStreams, stream.getBaseStreams());
- }
- }
- return baseStreams;
- };
- return StreamsSequenceStream;
-}();
-var FlateStream = function FlateStreamClosure() {
- var codeLenCodeMap = new Int32Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
- var lengthDecode = new Int32Array([0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102]);
- var distDecode = new Int32Array([0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001]);
- var fixedLitCodeTab = [new Int32Array([0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff]), 9];
- var fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000]), 5];
- function FlateStream(str, maybeLength) {
- this.str = str;
- this.dict = str.dict;
- var cmf = str.getByte();
- var flg = str.getByte();
- if (cmf === -1 || flg === -1) {
- error('Invalid header in flate stream: ' + cmf + ', ' + flg);
- }
- if ((cmf & 0x0f) !== 0x08) {
- error('Unknown compression method in flate stream: ' + cmf + ', ' + flg);
- }
- if (((cmf << 8) + flg) % 31 !== 0) {
- error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg);
- }
- if (flg & 0x20) {
- error('FDICT bit set in flate stream: ' + cmf + ', ' + flg);
- }
- this.codeSize = 0;
- this.codeBuf = 0;
- DecodeStream.call(this, maybeLength);
- }
- FlateStream.prototype = Object.create(DecodeStream.prototype);
- FlateStream.prototype.getBits = function FlateStream_getBits(bits) {
- var str = this.str;
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var b;
- while (codeSize < bits) {
- if ((b = str.getByte()) === -1) {
- error('Bad encoding in flate stream');
- }
- codeBuf |= b << codeSize;
- codeSize += 8;
- }
- b = codeBuf & (1 << bits) - 1;
- this.codeBuf = codeBuf >> bits;
- this.codeSize = codeSize -= bits;
- return b;
- };
- FlateStream.prototype.getCode = function FlateStream_getCode(table) {
- var str = this.str;
- var codes = table[0];
- var maxLen = table[1];
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var b;
- while (codeSize < maxLen) {
- if ((b = str.getByte()) === -1) {
- break;
- }
- codeBuf |= b << codeSize;
- codeSize += 8;
- }
- var code = codes[codeBuf & (1 << maxLen) - 1];
- var codeLen = code >> 16;
- var codeVal = code & 0xffff;
- if (codeLen < 1 || codeSize < codeLen) {
- error('Bad encoding in flate stream');
- }
- this.codeBuf = codeBuf >> codeLen;
- this.codeSize = codeSize - codeLen;
- return codeVal;
- };
- FlateStream.prototype.generateHuffmanTable = function flateStreamGenerateHuffmanTable(lengths) {
- var n = lengths.length;
- var maxLen = 0;
- var i;
- for (i = 0; i < n; ++i) {
- if (lengths[i] > maxLen) {
- maxLen = lengths[i];
- }
- }
- var size = 1 << maxLen;
- var codes = new Int32Array(size);
- for (var len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) {
- for (var val = 0; val < n; ++val) {
- if (lengths[val] === len) {
- var code2 = 0;
- var t = code;
- for (i = 0; i < len; ++i) {
- code2 = code2 << 1 | t & 1;
- t >>= 1;
- }
- for (i = code2; i < size; i += skip) {
- codes[i] = len << 16 | val;
- }
- ++code;
- }
- }
- }
- return [codes, maxLen];
- };
- FlateStream.prototype.readBlock = function FlateStream_readBlock() {
- var buffer, len;
- var str = this.str;
- var hdr = this.getBits(3);
- if (hdr & 1) {
- this.eof = true;
- }
- hdr >>= 1;
- if (hdr === 0) {
- var b;
- if ((b = str.getByte()) === -1) {
- error('Bad block header in flate stream');
- }
- var blockLen = b;
- if ((b = str.getByte()) === -1) {
- error('Bad block header in flate stream');
- }
- blockLen |= b << 8;
- if ((b = str.getByte()) === -1) {
- error('Bad block header in flate stream');
- }
- var check = b;
- if ((b = str.getByte()) === -1) {
- error('Bad block header in flate stream');
- }
- check |= b << 8;
- if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) {
- error('Bad uncompressed block length in flate stream');
- }
- this.codeBuf = 0;
- this.codeSize = 0;
- var bufferLength = this.bufferLength;
- buffer = this.ensureBuffer(bufferLength + blockLen);
- var end = bufferLength + blockLen;
- this.bufferLength = end;
- if (blockLen === 0) {
- if (str.peekByte() === -1) {
- this.eof = true;
- }
- } else {
- for (var n = bufferLength; n < end; ++n) {
- if ((b = str.getByte()) === -1) {
- this.eof = true;
- break;
- }
- buffer[n] = b;
- }
- }
- return;
- }
- var litCodeTable;
- var distCodeTable;
- if (hdr === 1) {
- litCodeTable = fixedLitCodeTab;
- distCodeTable = fixedDistCodeTab;
- } else if (hdr === 2) {
- var numLitCodes = this.getBits(5) + 257;
- var numDistCodes = this.getBits(5) + 1;
- var numCodeLenCodes = this.getBits(4) + 4;
- var codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length);
- var i;
- for (i = 0; i < numCodeLenCodes; ++i) {
- codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3);
- }
- var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
- len = 0;
- i = 0;
- var codes = numLitCodes + numDistCodes;
- var codeLengths = new Uint8Array(codes);
- var bitsLength, bitsOffset, what;
- while (i < codes) {
- var code = this.getCode(codeLenCodeTab);
- if (code === 16) {
- bitsLength = 2;
- bitsOffset = 3;
- what = len;
- } else if (code === 17) {
- bitsLength = 3;
- bitsOffset = 3;
- what = len = 0;
- } else if (code === 18) {
- bitsLength = 7;
- bitsOffset = 11;
- what = len = 0;
- } else {
- codeLengths[i++] = len = code;
- continue;
- }
- var repeatLength = this.getBits(bitsLength) + bitsOffset;
- while (repeatLength-- > 0) {
- codeLengths[i++] = what;
- }
- }
- litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
- distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
- } else {
- error('Unknown block type in flate stream');
- }
- buffer = this.buffer;
- var limit = buffer ? buffer.length : 0;
- var pos = this.bufferLength;
- while (true) {
- var code1 = this.getCode(litCodeTable);
- if (code1 < 256) {
- if (pos + 1 >= limit) {
- buffer = this.ensureBuffer(pos + 1);
- limit = buffer.length;
- }
- buffer[pos++] = code1;
- continue;
- }
- if (code1 === 256) {
- this.bufferLength = pos;
- return;
- }
- code1 -= 257;
- code1 = lengthDecode[code1];
- var code2 = code1 >> 16;
- if (code2 > 0) {
- code2 = this.getBits(code2);
- }
- len = (code1 & 0xffff) + code2;
- code1 = this.getCode(distCodeTable);
- code1 = distDecode[code1];
- code2 = code1 >> 16;
- if (code2 > 0) {
- code2 = this.getBits(code2);
- }
- var dist = (code1 & 0xffff) + code2;
- if (pos + len >= limit) {
- buffer = this.ensureBuffer(pos + len);
- limit = buffer.length;
- }
- for (var k = 0; k < len; ++k, ++pos) {
- buffer[pos] = buffer[pos - dist];
- }
- }
- };
- return FlateStream;
-}();
-var PredictorStream = function PredictorStreamClosure() {
- function PredictorStream(str, maybeLength, params) {
- if (!isDict(params)) {
- return str;
- }
- var predictor = this.predictor = params.get('Predictor') || 1;
- if (predictor <= 1) {
- return str;
- }
- if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
- error('Unsupported predictor: ' + predictor);
- }
- if (predictor === 2) {
- this.readBlock = this.readBlockTiff;
- } else {
- this.readBlock = this.readBlockPng;
- }
- this.str = str;
- this.dict = str.dict;
- var colors = this.colors = params.get('Colors') || 1;
- var bits = this.bits = params.get('BitsPerComponent') || 8;
- var columns = this.columns = params.get('Columns') || 1;
- this.pixBytes = colors * bits + 7 >> 3;
- this.rowBytes = columns * colors * bits + 7 >> 3;
- DecodeStream.call(this, maybeLength);
- return this;
- }
- PredictorStream.prototype = Object.create(DecodeStream.prototype);
- PredictorStream.prototype.readBlockTiff = function predictorStreamReadBlockTiff() {
- var rowBytes = this.rowBytes;
- var bufferLength = this.bufferLength;
- var buffer = this.ensureBuffer(bufferLength + rowBytes);
- var bits = this.bits;
- var colors = this.colors;
- var rawBytes = this.str.getBytes(rowBytes);
- this.eof = !rawBytes.length;
- if (this.eof) {
- return;
- }
- var inbuf = 0,
- outbuf = 0;
- var inbits = 0,
- outbits = 0;
- var pos = bufferLength;
- var i;
- if (bits === 1 && colors === 1) {
- for (i = 0; i < rowBytes; ++i) {
- var c = rawBytes[i] ^ inbuf;
- c ^= c >> 1;
- c ^= c >> 2;
- c ^= c >> 4;
- inbuf = (c & 1) << 7;
- buffer[pos++] = c;
- }
- } else if (bits === 8) {
- for (i = 0; i < colors; ++i) {
- buffer[pos++] = rawBytes[i];
- }
- for (; i < rowBytes; ++i) {
- buffer[pos] = buffer[pos - colors] + rawBytes[i];
- pos++;
- }
- } else {
- var compArray = new Uint8Array(colors + 1);
- var bitMask = (1 << bits) - 1;
- var j = 0,
- k = bufferLength;
- var columns = this.columns;
- for (i = 0; i < columns; ++i) {
- for (var kk = 0; kk < colors; ++kk) {
- if (inbits < bits) {
- inbuf = inbuf << 8 | rawBytes[j++] & 0xFF;
- inbits += 8;
- }
- compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask;
- inbits -= bits;
- outbuf = outbuf << bits | compArray[kk];
- outbits += bits;
- if (outbits >= 8) {
- buffer[k++] = outbuf >> outbits - 8 & 0xFF;
- outbits -= 8;
- }
- }
- }
- if (outbits > 0) {
- buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1);
- }
- }
- this.bufferLength += rowBytes;
- };
- PredictorStream.prototype.readBlockPng = function predictorStreamReadBlockPng() {
- var rowBytes = this.rowBytes;
- var pixBytes = this.pixBytes;
- var predictor = this.str.getByte();
- var rawBytes = this.str.getBytes(rowBytes);
- this.eof = !rawBytes.length;
- if (this.eof) {
- return;
- }
- var bufferLength = this.bufferLength;
- var buffer = this.ensureBuffer(bufferLength + rowBytes);
- var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
- if (prevRow.length === 0) {
- prevRow = new Uint8Array(rowBytes);
- }
- var i,
- j = bufferLength,
- up,
- c;
- switch (predictor) {
- case 0:
- for (i = 0; i < rowBytes; ++i) {
- buffer[j++] = rawBytes[i];
- }
- break;
- case 1:
- for (i = 0; i < pixBytes; ++i) {
- buffer[j++] = rawBytes[i];
- }
- for (; i < rowBytes; ++i) {
- buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xFF;
- j++;
- }
- break;
- case 2:
- for (i = 0; i < rowBytes; ++i) {
- buffer[j++] = prevRow[i] + rawBytes[i] & 0xFF;
- }
- break;
- case 3:
- for (i = 0; i < pixBytes; ++i) {
- buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
- }
- for (; i < rowBytes; ++i) {
- buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xFF;
- j++;
- }
- break;
- case 4:
- for (i = 0; i < pixBytes; ++i) {
- up = prevRow[i];
- c = rawBytes[i];
- buffer[j++] = up + c;
- }
- for (; i < rowBytes; ++i) {
- up = prevRow[i];
- var upLeft = prevRow[i - pixBytes];
- var left = buffer[j - pixBytes];
- var p = left + up - upLeft;
- var pa = p - left;
- if (pa < 0) {
- pa = -pa;
- }
- var pb = p - up;
- if (pb < 0) {
- pb = -pb;
- }
- var pc = p - upLeft;
- if (pc < 0) {
- pc = -pc;
- }
- c = rawBytes[i];
- if (pa <= pb && pa <= pc) {
- buffer[j++] = left + c;
- } else if (pb <= pc) {
- buffer[j++] = up + c;
- } else {
- buffer[j++] = upLeft + c;
- }
- }
- break;
- default:
- error('Unsupported predictor: ' + predictor);
- }
- this.bufferLength += rowBytes;
- };
- return PredictorStream;
-}();
-var JpegStream = function JpegStreamClosure() {
- function JpegStream(stream, maybeLength, dict, params) {
- var ch;
- while ((ch = stream.getByte()) !== -1) {
- if (ch === 0xFF) {
- stream.skip(-1);
- break;
- }
- }
- this.stream = stream;
- this.maybeLength = maybeLength;
- this.dict = dict;
- this.params = params;
- DecodeStream.call(this, maybeLength);
- }
- JpegStream.prototype = Object.create(DecodeStream.prototype);
- Object.defineProperty(JpegStream.prototype, 'bytes', {
- get: function JpegStream_bytes() {
- return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
- },
- configurable: true
- });
- JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
- if (this.bufferLength) {
- return;
- }
- var jpegImage = new JpegImage();
- var decodeArr = this.dict.getArray('Decode', 'D');
- if (this.forceRGB && isArray(decodeArr)) {
- var bitsPerComponent = this.dict.get('BitsPerComponent') || 8;
- var decodeArrLength = decodeArr.length;
- var transform = new Int32Array(decodeArrLength);
- var transformNeeded = false;
- var maxValue = (1 << bitsPerComponent) - 1;
- for (var i = 0; i < decodeArrLength; i += 2) {
- transform[i] = (decodeArr[i + 1] - decodeArr[i]) * 256 | 0;
- transform[i + 1] = decodeArr[i] * maxValue | 0;
- if (transform[i] !== 256 || transform[i + 1] !== 0) {
- transformNeeded = true;
- }
- }
- if (transformNeeded) {
- jpegImage.decodeTransform = transform;
- }
- }
- if (isDict(this.params)) {
- var colorTransform = this.params.get('ColorTransform');
- if (isInt(colorTransform)) {
- jpegImage.colorTransform = colorTransform;
- }
- }
- jpegImage.parse(this.bytes);
- var data = jpegImage.getData(this.drawWidth, this.drawHeight, this.forceRGB);
- this.buffer = data;
- this.bufferLength = data.length;
- this.eof = true;
- };
- JpegStream.prototype.getBytes = function JpegStream_getBytes(length) {
- this.ensureBuffer();
- return this.buffer;
- };
- JpegStream.prototype.getIR = function JpegStream_getIR(forceDataSchema) {
- return createObjectURL(this.bytes, 'image/jpeg', forceDataSchema);
- };
- return JpegStream;
-}();
-var JpxStream = function JpxStreamClosure() {
- function JpxStream(stream, maybeLength, dict, params) {
- this.stream = stream;
- this.maybeLength = maybeLength;
- this.dict = dict;
- this.params = params;
- DecodeStream.call(this, maybeLength);
- }
- JpxStream.prototype = Object.create(DecodeStream.prototype);
- Object.defineProperty(JpxStream.prototype, 'bytes', {
- get: function JpxStream_bytes() {
- return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
- },
- configurable: true
- });
- JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) {
- if (this.bufferLength) {
- return;
- }
- var jpxImage = new JpxImage();
- jpxImage.parse(this.bytes);
- var width = jpxImage.width;
- var height = jpxImage.height;
- var componentsCount = jpxImage.componentsCount;
- var tileCount = jpxImage.tiles.length;
- if (tileCount === 1) {
- this.buffer = jpxImage.tiles[0].items;
- } else {
- var data = new Uint8Array(width * height * componentsCount);
- for (var k = 0; k < tileCount; k++) {
- var tileComponents = jpxImage.tiles[k];
- var tileWidth = tileComponents.width;
- var tileHeight = tileComponents.height;
- var tileLeft = tileComponents.left;
- var tileTop = tileComponents.top;
- var src = tileComponents.items;
- var srcPosition = 0;
- var dataPosition = (width * tileTop + tileLeft) * componentsCount;
- var imgRowSize = width * componentsCount;
- var tileRowSize = tileWidth * componentsCount;
- for (var j = 0; j < tileHeight; j++) {
- var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize);
- data.set(rowBytes, dataPosition);
- srcPosition += tileRowSize;
- dataPosition += imgRowSize;
- }
- }
- this.buffer = data;
- }
- this.bufferLength = this.buffer.length;
- this.eof = true;
- };
- return JpxStream;
-}();
-var Jbig2Stream = function Jbig2StreamClosure() {
- function Jbig2Stream(stream, maybeLength, dict, params) {
- this.stream = stream;
- this.maybeLength = maybeLength;
- this.dict = dict;
- this.params = params;
- DecodeStream.call(this, maybeLength);
- }
- Jbig2Stream.prototype = Object.create(DecodeStream.prototype);
- Object.defineProperty(Jbig2Stream.prototype, 'bytes', {
- get: function Jbig2Stream_bytes() {
- return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
- },
- configurable: true
- });
- Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) {
- if (this.bufferLength) {
- return;
- }
- var jbig2Image = new Jbig2Image();
- var chunks = [];
- if (isDict(this.params)) {
- var globalsStream = this.params.get('JBIG2Globals');
- if (isStream(globalsStream)) {
- var globals = globalsStream.getBytes();
- chunks.push({
- data: globals,
- start: 0,
- end: globals.length
- });
- }
- }
- chunks.push({
- data: this.bytes,
- start: 0,
- end: this.bytes.length
- });
- var data = jbig2Image.parseChunks(chunks);
- var dataLength = data.length;
- for (var i = 0; i < dataLength; i++) {
- data[i] ^= 0xFF;
- }
- this.buffer = data;
- this.bufferLength = dataLength;
- this.eof = true;
- };
- return Jbig2Stream;
-}();
-var DecryptStream = function DecryptStreamClosure() {
- function DecryptStream(str, maybeLength, decrypt) {
- this.str = str;
- this.dict = str.dict;
- this.decrypt = decrypt;
- this.nextChunk = null;
- this.initialized = false;
- DecodeStream.call(this, maybeLength);
- }
- var chunkSize = 512;
- DecryptStream.prototype = Object.create(DecodeStream.prototype);
- DecryptStream.prototype.readBlock = function DecryptStream_readBlock() {
- var chunk;
- if (this.initialized) {
- chunk = this.nextChunk;
- } else {
- chunk = this.str.getBytes(chunkSize);
- this.initialized = true;
- }
- if (!chunk || chunk.length === 0) {
- this.eof = true;
- return;
- }
- this.nextChunk = this.str.getBytes(chunkSize);
- var hasMoreData = this.nextChunk && this.nextChunk.length > 0;
- var decrypt = this.decrypt;
- chunk = decrypt(chunk, !hasMoreData);
- var bufferLength = this.bufferLength;
- var i,
- n = chunk.length;
- var buffer = this.ensureBuffer(bufferLength + n);
- for (i = 0; i < n; i++) {
- buffer[bufferLength++] = chunk[i];
- }
- this.bufferLength = bufferLength;
- };
- return DecryptStream;
-}();
-var Ascii85Stream = function Ascii85StreamClosure() {
- function Ascii85Stream(str, maybeLength) {
- this.str = str;
- this.dict = str.dict;
- this.input = new Uint8Array(5);
- if (maybeLength) {
- maybeLength = 0.8 * maybeLength;
- }
- DecodeStream.call(this, maybeLength);
- }
- Ascii85Stream.prototype = Object.create(DecodeStream.prototype);
- Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() {
- var TILDA_CHAR = 0x7E;
- var Z_LOWER_CHAR = 0x7A;
- var EOF = -1;
- var str = this.str;
- var c = str.getByte();
- while (isSpace(c)) {
- c = str.getByte();
- }
- if (c === EOF || c === TILDA_CHAR) {
- this.eof = true;
- return;
- }
- var bufferLength = this.bufferLength,
- buffer;
- var i;
- if (c === Z_LOWER_CHAR) {
- buffer = this.ensureBuffer(bufferLength + 4);
- for (i = 0; i < 4; ++i) {
- buffer[bufferLength + i] = 0;
- }
- this.bufferLength += 4;
- } else {
- var input = this.input;
- input[0] = c;
- for (i = 1; i < 5; ++i) {
- c = str.getByte();
- while (isSpace(c)) {
- c = str.getByte();
- }
- input[i] = c;
- if (c === EOF || c === TILDA_CHAR) {
- break;
- }
- }
- buffer = this.ensureBuffer(bufferLength + i - 1);
- this.bufferLength += i - 1;
- if (i < 5) {
- for (; i < 5; ++i) {
- input[i] = 0x21 + 84;
- }
- this.eof = true;
- }
- var t = 0;
- for (i = 0; i < 5; ++i) {
- t = t * 85 + (input[i] - 0x21);
- }
- for (i = 3; i >= 0; --i) {
- buffer[bufferLength + i] = t & 0xFF;
- t >>= 8;
- }
- }
- };
- return Ascii85Stream;
-}();
-var AsciiHexStream = function AsciiHexStreamClosure() {
- function AsciiHexStream(str, maybeLength) {
- this.str = str;
- this.dict = str.dict;
- this.firstDigit = -1;
- if (maybeLength) {
- maybeLength = 0.5 * maybeLength;
- }
- DecodeStream.call(this, maybeLength);
- }
- AsciiHexStream.prototype = Object.create(DecodeStream.prototype);
- AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() {
- var UPSTREAM_BLOCK_SIZE = 8000;
- var bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE);
- if (!bytes.length) {
- this.eof = true;
- return;
- }
- var maxDecodeLength = bytes.length + 1 >> 1;
- var buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
- var bufferLength = this.bufferLength;
- var firstDigit = this.firstDigit;
- for (var i = 0, ii = bytes.length; i < ii; i++) {
- var ch = bytes[i],
- digit;
- if (ch >= 0x30 && ch <= 0x39) {
- digit = ch & 0x0F;
- } else if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
- digit = (ch & 0x0F) + 9;
- } else if (ch === 0x3E) {
- this.eof = true;
- break;
- } else {
- continue;
- }
- if (firstDigit < 0) {
- firstDigit = digit;
- } else {
- buffer[bufferLength++] = firstDigit << 4 | digit;
- firstDigit = -1;
- }
- }
- if (firstDigit >= 0 && this.eof) {
- buffer[bufferLength++] = firstDigit << 4;
- firstDigit = -1;
- }
- this.firstDigit = firstDigit;
- this.bufferLength = bufferLength;
- };
- return AsciiHexStream;
-}();
-var RunLengthStream = function RunLengthStreamClosure() {
- function RunLengthStream(str, maybeLength) {
- this.str = str;
- this.dict = str.dict;
- DecodeStream.call(this, maybeLength);
- }
- RunLengthStream.prototype = Object.create(DecodeStream.prototype);
- RunLengthStream.prototype.readBlock = function RunLengthStream_readBlock() {
- var repeatHeader = this.str.getBytes(2);
- if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) {
- this.eof = true;
- return;
- }
- var buffer;
- var bufferLength = this.bufferLength;
- var n = repeatHeader[0];
- if (n < 128) {
- buffer = this.ensureBuffer(bufferLength + n + 1);
- buffer[bufferLength++] = repeatHeader[1];
- if (n > 0) {
- var source = this.str.getBytes(n);
- buffer.set(source, bufferLength);
- bufferLength += n;
- }
- } else {
- n = 257 - n;
- var b = repeatHeader[1];
- buffer = this.ensureBuffer(bufferLength + n + 1);
- for (var i = 0; i < n; i++) {
- buffer[bufferLength++] = b;
- }
- }
- this.bufferLength = bufferLength;
- };
- return RunLengthStream;
-}();
-var CCITTFaxStream = function CCITTFaxStreamClosure() {
- var ccittEOL = -2;
- var ccittEOF = -1;
- var twoDimPass = 0;
- var twoDimHoriz = 1;
- var twoDimVert0 = 2;
- var twoDimVertR1 = 3;
- var twoDimVertL1 = 4;
- var twoDimVertR2 = 5;
- var twoDimVertL2 = 6;
- var twoDimVertR3 = 7;
- var twoDimVertL3 = 8;
- var twoDimTable = [[-1, -1], [-1, -1], [7, twoDimVertL3], [7, twoDimVertR3], [6, twoDimVertL2], [6, twoDimVertL2], [6, twoDimVertR2], [6, twoDimVertR2], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0]];
- var whiteTable1 = [[-1, -1], [12, ccittEOL], [-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]];
- var whiteTable2 = [[-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]];
- var blackTable1 = [[-1, -1], [-1, -1], [12, ccittEOL], [12, ccittEOL], [-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]];
- var blackTable2 = [[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]];
- var blackTable3 = [[-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]];
- function CCITTFaxStream(str, maybeLength, params) {
- this.str = str;
- this.dict = str.dict;
- params = params || Dict.empty;
- this.encoding = params.get('K') || 0;
- this.eoline = params.get('EndOfLine') || false;
- this.byteAlign = params.get('EncodedByteAlign') || false;
- this.columns = params.get('Columns') || 1728;
- this.rows = params.get('Rows') || 0;
- var eoblock = params.get('EndOfBlock');
- if (eoblock === null || eoblock === undefined) {
- eoblock = true;
- }
- this.eoblock = eoblock;
- this.black = params.get('BlackIs1') || false;
- 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;
- var code1;
- while ((code1 = this.lookBits(12)) === 0) {
- this.eatBits(1);
- }
- if (code1 === 1) {
- this.eatBits(12);
- }
- if (this.encoding > 0) {
- this.nextLine2D = !this.lookBits(1);
- this.eatBits(1);
- }
- DecodeStream.call(this, maybeLength);
- }
- CCITTFaxStream.prototype = Object.create(DecodeStream.prototype);
- CCITTFaxStream.prototype.readBlock = function CCITTFaxStream_readBlock() {
- while (!this.eof) {
- var c = this.lookChar();
- this.ensureBuffer(this.bufferLength + 1);
- this.buffer[this.bufferLength++] = c;
- }
- };
- CCITTFaxStream.prototype.addPixels = function ccittFaxStreamAddPixels(a1, blackPixels) {
- var codingLine = this.codingLine;
- var codingPos = this.codingPos;
- if (a1 > codingLine[codingPos]) {
- if (a1 > this.columns) {
- info('row is wrong length');
- this.err = true;
- a1 = this.columns;
- }
- if (codingPos & 1 ^ blackPixels) {
- ++codingPos;
- }
- codingLine[codingPos] = a1;
- }
- this.codingPos = codingPos;
- };
- CCITTFaxStream.prototype.addPixelsNeg = function ccittFaxStreamAddPixelsNeg(a1, blackPixels) {
- var codingLine = this.codingLine;
- var codingPos = this.codingPos;
- if (a1 > codingLine[codingPos]) {
- if (a1 > this.columns) {
- info('row is wrong length');
- this.err = true;
- a1 = this.columns;
- }
- if (codingPos & 1 ^ blackPixels) {
- ++codingPos;
- }
- codingLine[codingPos] = a1;
- } else if (a1 < codingLine[codingPos]) {
- if (a1 < 0) {
- info('invalid code');
- this.err = true;
- a1 = 0;
- }
- while (codingPos > 0 && a1 < codingLine[codingPos - 1]) {
- --codingPos;
- }
- codingLine[codingPos] = a1;
- }
- this.codingPos = codingPos;
- };
- CCITTFaxStream.prototype.lookChar = function CCITTFaxStream_lookChar() {
- var refLine = this.refLine;
- var codingLine = this.codingLine;
- var columns = this.columns;
- var refPos, blackPixels, bits, i;
- if (this.outputBits === 0) {
- if (this.eof) {
- return null;
- }
- this.err = false;
- var code1, code2, code3;
- if (this.nextLine2D) {
- for (i = 0; codingLine[i] < columns; ++i) {
- refLine[i] = codingLine[i];
- }
- refLine[i++] = columns;
- refLine[i] = columns;
- codingLine[0] = 0;
- this.codingPos = 0;
- refPos = 0;
- blackPixels = 0;
- while (codingLine[this.codingPos] < columns) {
- code1 = this.getTwoDimCode();
- switch (code1) {
- case twoDimPass:
- this.addPixels(refLine[refPos + 1], blackPixels);
- if (refLine[refPos + 1] < columns) {
- refPos += 2;
- }
- break;
- case twoDimHoriz:
- code1 = code2 = 0;
- if (blackPixels) {
- do {
- code1 += code3 = this.getBlackCode();
- } while (code3 >= 64);
- do {
- code2 += code3 = this.getWhiteCode();
- } while (code3 >= 64);
- } else {
- do {
- code1 += code3 = this.getWhiteCode();
- } while (code3 >= 64);
- do {
- code2 += code3 = this.getBlackCode();
- } while (code3 >= 64);
- }
- this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
- if (codingLine[this.codingPos] < columns) {
- this.addPixels(codingLine[this.codingPos] + code2, blackPixels ^ 1);
- }
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- break;
- case twoDimVertR3:
- this.addPixels(refLine[refPos] + 3, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- ++refPos;
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVertR2:
- this.addPixels(refLine[refPos] + 2, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- ++refPos;
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVertR1:
- this.addPixels(refLine[refPos] + 1, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- ++refPos;
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVert0:
- this.addPixels(refLine[refPos], blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- ++refPos;
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVertL3:
- this.addPixelsNeg(refLine[refPos] - 3, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- if (refPos > 0) {
- --refPos;
- } else {
- ++refPos;
- }
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVertL2:
- this.addPixelsNeg(refLine[refPos] - 2, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- if (refPos > 0) {
- --refPos;
- } else {
- ++refPos;
- }
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case twoDimVertL1:
- this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
- blackPixels ^= 1;
- if (codingLine[this.codingPos] < columns) {
- if (refPos > 0) {
- --refPos;
- } else {
- ++refPos;
- }
- while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
- refPos += 2;
- }
- }
- break;
- case ccittEOF:
- this.addPixels(columns, 0);
- this.eof = true;
- break;
- default:
- info('bad 2d code');
- this.addPixels(columns, 0);
- this.err = true;
- }
- }
- } else {
- codingLine[0] = 0;
- this.codingPos = 0;
- blackPixels = 0;
- while (codingLine[this.codingPos] < columns) {
- code1 = 0;
- if (blackPixels) {
- do {
- code1 += code3 = this.getBlackCode();
- } while (code3 >= 64);
- } else {
- do {
- code1 += code3 = this.getWhiteCode();
- } while (code3 >= 64);
- }
- this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
- blackPixels ^= 1;
- }
- }
- var gotEOL = false;
- if (this.byteAlign) {
- this.inputBits &= ~7;
- }
- if (!this.eoblock && this.row === this.rows - 1) {
- this.eof = true;
- } else {
- code1 = this.lookBits(12);
- if (this.eoline) {
- while (code1 !== ccittEOF && code1 !== 1) {
- this.eatBits(1);
- code1 = this.lookBits(12);
- }
- } else {
- while (code1 === 0) {
- this.eatBits(1);
- code1 = this.lookBits(12);
- }
- }
- if (code1 === 1) {
- this.eatBits(12);
- gotEOL = true;
- } else if (code1 === ccittEOF) {
- this.eof = true;
- }
- }
- if (!this.eof && this.encoding > 0) {
- this.nextLine2D = !this.lookBits(1);
- this.eatBits(1);
- }
- if (this.eoblock && gotEOL && this.byteAlign) {
- code1 = this.lookBits(12);
- if (code1 === 1) {
- this.eatBits(12);
- if (this.encoding > 0) {
- this.lookBits(1);
- this.eatBits(1);
- }
- if (this.encoding >= 0) {
- for (i = 0; i < 4; ++i) {
- code1 = this.lookBits(12);
- if (code1 !== 1) {
- info('bad rtc code: ' + code1);
- }
- this.eatBits(12);
- if (this.encoding > 0) {
- this.lookBits(1);
- this.eatBits(1);
- }
- }
- }
- this.eof = true;
- }
- } else if (this.err && this.eoline) {
- while (true) {
- code1 = this.lookBits(13);
- if (code1 === ccittEOF) {
- this.eof = true;
- return null;
- }
- if (code1 >> 1 === 1) {
- break;
- }
- this.eatBits(1);
- }
- this.eatBits(12);
- if (this.encoding > 0) {
- this.eatBits(1);
- this.nextLine2D = !(code1 & 1);
- }
- }
- if (codingLine[0] > 0) {
- this.outputBits = codingLine[this.codingPos = 0];
- } else {
- this.outputBits = codingLine[this.codingPos = 1];
- }
- this.row++;
- }
- var c;
- if (this.outputBits >= 8) {
- c = this.codingPos & 1 ? 0 : 0xFF;
- this.outputBits -= 8;
- if (this.outputBits === 0 && codingLine[this.codingPos] < columns) {
- this.codingPos++;
- this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
- }
- } else {
- bits = 8;
- c = 0;
- do {
- if (this.outputBits > bits) {
- c <<= bits;
- if (!(this.codingPos & 1)) {
- c |= 0xFF >> 8 - bits;
- }
- this.outputBits -= bits;
- bits = 0;
- } else {
- c <<= this.outputBits;
- if (!(this.codingPos & 1)) {
- c |= 0xFF >> 8 - this.outputBits;
- }
- bits -= this.outputBits;
- this.outputBits = 0;
- if (codingLine[this.codingPos] < columns) {
- this.codingPos++;
- this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
- } else if (bits > 0) {
- c <<= bits;
- bits = 0;
- }
- }
- } while (bits);
- }
- if (this.black) {
- c ^= 0xFF;
- }
- return c;
- };
- CCITTFaxStream.prototype.findTableCode = function ccittFaxStreamFindTableCode(start, end, table, limit) {
- var limitValue = limit || 0;
- for (var i = start; i <= end; ++i) {
- var code = this.lookBits(i);
- if (code === ccittEOF) {
- return [true, 1, false];
- }
- if (i < end) {
- code <<= end - i;
- }
- if (!limitValue || code >= limitValue) {
- var p = table[code - limitValue];
- if (p[0] === i) {
- this.eatBits(i);
- return [true, p[1], true];
- }
- }
- }
- return [false, 0, false];
- };
- CCITTFaxStream.prototype.getTwoDimCode = function ccittFaxStreamGetTwoDimCode() {
- var code = 0;
- var p;
- if (this.eoblock) {
- code = this.lookBits(7);
- p = twoDimTable[code];
- if (p && p[0] > 0) {
- this.eatBits(p[0]);
- return p[1];
- }
- } else {
- var result = this.findTableCode(1, 7, twoDimTable);
- if (result[0] && result[2]) {
- return result[1];
- }
- }
- info('Bad two dim code');
- return ccittEOF;
- };
- CCITTFaxStream.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() {
- var code = 0;
- var p;
- if (this.eoblock) {
- code = this.lookBits(12);
- if (code === ccittEOF) {
- return 1;
- }
- if (code >> 5 === 0) {
- p = whiteTable1[code];
- } else {
- p = whiteTable2[code >> 3];
- }
- if (p[0] > 0) {
- this.eatBits(p[0]);
- return p[1];
- }
- } else {
- var result = this.findTableCode(1, 9, whiteTable2);
- if (result[0]) {
- return result[1];
- }
- result = this.findTableCode(11, 12, whiteTable1);
- if (result[0]) {
- return result[1];
- }
- }
- info('bad white code');
- this.eatBits(1);
- return 1;
- };
- CCITTFaxStream.prototype.getBlackCode = function ccittFaxStreamGetBlackCode() {
- var code, p;
- if (this.eoblock) {
- code = this.lookBits(13);
- if (code === ccittEOF) {
- return 1;
- }
- if (code >> 7 === 0) {
- p = blackTable1[code];
- } else if (code >> 9 === 0 && code >> 7 !== 0) {
- p = blackTable2[(code >> 1) - 64];
- } else {
- p = blackTable3[code >> 7];
- }
- if (p[0] > 0) {
- this.eatBits(p[0]);
- return p[1];
- }
- } else {
- var result = this.findTableCode(2, 6, blackTable3);
- if (result[0]) {
- return result[1];
- }
- result = this.findTableCode(7, 12, blackTable2, 64);
- if (result[0]) {
- return result[1];
- }
- result = this.findTableCode(10, 13, blackTable1);
- if (result[0]) {
- return result[1];
- }
- }
- info('bad black code');
- this.eatBits(1);
- return 1;
- };
- CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) {
- var c;
- while (this.inputBits < n) {
- if ((c = this.str.getByte()) === -1) {
- if (this.inputBits === 0) {
- return ccittEOF;
- }
- return this.inputBuf << n - this.inputBits & 0xFFFF >> 16 - n;
- }
- this.inputBuf = this.inputBuf << 8 | c;
- this.inputBits += 8;
- }
- return this.inputBuf >> this.inputBits - n & 0xFFFF >> 16 - n;
- };
- CCITTFaxStream.prototype.eatBits = function CCITTFaxStream_eatBits(n) {
- if ((this.inputBits -= n) < 0) {
- this.inputBits = 0;
- }
- };
- return CCITTFaxStream;
-}();
-var LZWStream = function LZWStreamClosure() {
- function LZWStream(str, maybeLength, earlyChange) {
- this.str = str;
- this.dict = str.dict;
- this.cachedData = 0;
- this.bitsCached = 0;
- var maxLzwDictionarySize = 4096;
- var lzwState = {
- earlyChange: earlyChange,
- codeLength: 9,
- nextCode: 258,
- dictionaryValues: new Uint8Array(maxLzwDictionarySize),
- dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
- dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
- currentSequence: new Uint8Array(maxLzwDictionarySize),
- currentSequenceLength: 0
- };
- for (var i = 0; i < 256; ++i) {
- lzwState.dictionaryValues[i] = i;
- lzwState.dictionaryLengths[i] = 1;
- }
- this.lzwState = lzwState;
- DecodeStream.call(this, maybeLength);
- }
- LZWStream.prototype = Object.create(DecodeStream.prototype);
- LZWStream.prototype.readBits = function LZWStream_readBits(n) {
- var bitsCached = this.bitsCached;
- var cachedData = this.cachedData;
- while (bitsCached < n) {
- var c = this.str.getByte();
- if (c === -1) {
- this.eof = true;
- return null;
- }
- cachedData = cachedData << 8 | c;
- bitsCached += 8;
- }
- this.bitsCached = bitsCached -= n;
- this.cachedData = cachedData;
- this.lastCode = null;
- return cachedData >>> bitsCached & (1 << n) - 1;
- };
- LZWStream.prototype.readBlock = function LZWStream_readBlock() {
- var blockSize = 512;
- var estimatedDecodedSize = blockSize * 2,
- decodedSizeDelta = blockSize;
- var i, j, q;
- var lzwState = this.lzwState;
- if (!lzwState) {
- return;
- }
- var earlyChange = lzwState.earlyChange;
- var nextCode = lzwState.nextCode;
- var dictionaryValues = lzwState.dictionaryValues;
- var dictionaryLengths = lzwState.dictionaryLengths;
- var dictionaryPrevCodes = lzwState.dictionaryPrevCodes;
- var codeLength = lzwState.codeLength;
- var prevCode = lzwState.prevCode;
- var currentSequence = lzwState.currentSequence;
- var currentSequenceLength = lzwState.currentSequenceLength;
- var decodedLength = 0;
- var currentBufferLength = this.bufferLength;
- var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
- for (i = 0; i < blockSize; i++) {
- var code = this.readBits(codeLength);
- var hasPrev = currentSequenceLength > 0;
- if (code < 256) {
- currentSequence[0] = code;
- currentSequenceLength = 1;
- } else if (code >= 258) {
- if (code < nextCode) {
- currentSequenceLength = dictionaryLengths[code];
- for (j = currentSequenceLength - 1, q = code; j >= 0; j--) {
- currentSequence[j] = dictionaryValues[q];
- q = dictionaryPrevCodes[q];
- }
- } else {
- currentSequence[currentSequenceLength++] = currentSequence[0];
- }
- } else if (code === 256) {
- codeLength = 9;
- nextCode = 258;
- currentSequenceLength = 0;
- continue;
- } else {
- this.eof = true;
- delete this.lzwState;
- break;
- }
- if (hasPrev) {
- dictionaryPrevCodes[nextCode] = prevCode;
- dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
- dictionaryValues[nextCode] = currentSequence[0];
- nextCode++;
- codeLength = nextCode + earlyChange & nextCode + earlyChange - 1 ? codeLength : Math.min(Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, 12) | 0;
- }
- prevCode = code;
- decodedLength += currentSequenceLength;
- if (estimatedDecodedSize < decodedLength) {
- do {
- estimatedDecodedSize += decodedSizeDelta;
- } while (estimatedDecodedSize < decodedLength);
- buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
- }
- for (j = 0; j < currentSequenceLength; j++) {
- buffer[currentBufferLength++] = currentSequence[j];
- }
- }
- lzwState.nextCode = nextCode;
- lzwState.codeLength = codeLength;
- lzwState.prevCode = prevCode;
- lzwState.currentSequenceLength = currentSequenceLength;
- this.bufferLength = currentBufferLength;
- };
- return LZWStream;
-}();
-var NullStream = function NullStreamClosure() {
- function NullStream() {
- Stream.call(this, new Uint8Array(0));
- }
- NullStream.prototype = Stream.prototype;
- return NullStream;
-}();
-exports.Ascii85Stream = Ascii85Stream;
-exports.AsciiHexStream = AsciiHexStream;
-exports.CCITTFaxStream = CCITTFaxStream;
-exports.DecryptStream = DecryptStream;
-exports.DecodeStream = DecodeStream;
-exports.FlateStream = FlateStream;
-exports.Jbig2Stream = Jbig2Stream;
-exports.JpegStream = JpegStream;
-exports.JpxStream = JpxStream;
-exports.NullStream = NullStream;
-exports.PredictorStream = PredictorStream;
-exports.RunLengthStream = RunLengthStream;
-exports.Stream = Stream;
-exports.StreamsSequenceStream = StreamsSequenceStream;
-exports.StringStream = StringStream;
-exports.LZWStream = LZWStream;
-
-/***/ }),
-/* 3 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreFunction = __w_pdfjs_require__(6);
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isString = sharedUtil.isString;
-var shadow = sharedUtil.shadow;
-var warn = sharedUtil.warn;
-var isDict = corePrimitives.isDict;
-var isName = corePrimitives.isName;
-var isStream = corePrimitives.isStream;
-var PDFFunction = coreFunction.PDFFunction;
-var ColorSpace = function ColorSpaceClosure() {
- function resizeRgbImage(src, bpc, w1, h1, w2, h2, alpha01, dest) {
- var COMPONENTS = 3;
- alpha01 = alpha01 !== 1 ? 0 : alpha01;
- var xRatio = w1 / w2;
- var yRatio = h1 / h2;
- var i,
- j,
- py,
- newIndex = 0,
- oldIndex;
- var xScaled = new Uint16Array(w2);
- var w1Scanline = w1 * COMPONENTS;
- for (i = 0; i < w2; i++) {
- xScaled[i] = Math.floor(i * xRatio) * COMPONENTS;
- }
- for (i = 0; i < h2; i++) {
- py = Math.floor(i * yRatio) * w1Scanline;
- for (j = 0; j < w2; j++) {
- oldIndex = py + xScaled[j];
- dest[newIndex++] = src[oldIndex++];
- dest[newIndex++] = src[oldIndex++];
- dest[newIndex++] = src[oldIndex++];
- newIndex += alpha01;
- }
- }
- }
- function ColorSpace() {
- error('should not call ColorSpace constructor');
- }
- ColorSpace.prototype = {
- getRgb: function ColorSpace_getRgb(src, srcOffset) {
- var rgb = new Uint8Array(3);
- this.getRgbItem(src, srcOffset, rgb, 0);
- return rgb;
- },
- getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, dest, destOffset) {
- error('Should not call ColorSpace.getRgbItem');
- },
- getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- error('Should not call ColorSpace.getRgbBuffer');
- },
- getOutputLength: function ColorSpace_getOutputLength(inputLength, alpha01) {
- error('Should not call ColorSpace.getOutputLength');
- },
- isPassthrough: function ColorSpace_isPassthrough(bits) {
- return false;
- },
- fillRgb: function ColorSpace_fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) {
- var count = originalWidth * originalHeight;
- var rgbBuf = null;
- var numComponentColors = 1 << bpc;
- var needsResizing = originalHeight !== height || originalWidth !== width;
- var i, ii;
- if (this.isPassthrough(bpc)) {
- rgbBuf = comps;
- } else if (this.numComps === 1 && count > numComponentColors && this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
- var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : new Uint16Array(numComponentColors);
- var key;
- for (i = 0; i < numComponentColors; i++) {
- allColors[i] = i;
- }
- var colorMap = new Uint8Array(numComponentColors * 3);
- this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, 0);
- var destPos, rgbPos;
- if (!needsResizing) {
- destPos = 0;
- for (i = 0; i < count; ++i) {
- key = comps[i] * 3;
- dest[destPos++] = colorMap[key];
- dest[destPos++] = colorMap[key + 1];
- dest[destPos++] = colorMap[key + 2];
- destPos += alpha01;
- }
- } else {
- rgbBuf = new Uint8Array(count * 3);
- rgbPos = 0;
- for (i = 0; i < count; ++i) {
- key = comps[i] * 3;
- rgbBuf[rgbPos++] = colorMap[key];
- rgbBuf[rgbPos++] = colorMap[key + 1];
- rgbBuf[rgbPos++] = colorMap[key + 2];
- }
- }
- } else {
- if (!needsResizing) {
- this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01);
- } else {
- rgbBuf = new Uint8Array(count * 3);
- this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, 0);
- }
- }
- if (rgbBuf) {
- if (needsResizing) {
- resizeRgbImage(rgbBuf, bpc, originalWidth, originalHeight, width, height, alpha01, dest);
- } else {
- rgbPos = 0;
- destPos = 0;
- for (i = 0, ii = width * actualHeight; i < ii; i++) {
- dest[destPos++] = rgbBuf[rgbPos++];
- dest[destPos++] = rgbBuf[rgbPos++];
- dest[destPos++] = rgbBuf[rgbPos++];
- destPos += alpha01;
- }
- }
- }
- },
- usesZeroToOneRange: true
- };
- ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
- var IR = ColorSpace.parseToIR(cs, xref, res);
- if (IR instanceof AlternateCS) {
- return IR;
- }
- return ColorSpace.fromIR(IR);
- };
- ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
- var name = isArray(IR) ? IR[0] : IR;
- var whitePoint, blackPoint, gamma;
- switch (name) {
- case 'DeviceGrayCS':
- return this.singletons.gray;
- case 'DeviceRgbCS':
- return this.singletons.rgb;
- case 'DeviceCmykCS':
- return this.singletons.cmyk;
- case 'CalGrayCS':
- whitePoint = IR[1];
- blackPoint = IR[2];
- gamma = IR[3];
- return new CalGrayCS(whitePoint, blackPoint, gamma);
- case 'CalRGBCS':
- whitePoint = IR[1];
- blackPoint = IR[2];
- gamma = IR[3];
- var matrix = IR[4];
- return new CalRGBCS(whitePoint, blackPoint, gamma, matrix);
- case 'PatternCS':
- var basePatternCS = IR[1];
- if (basePatternCS) {
- basePatternCS = ColorSpace.fromIR(basePatternCS);
- }
- return new PatternCS(basePatternCS);
- case 'IndexedCS':
- var baseIndexedCS = IR[1];
- var hiVal = IR[2];
- var lookup = IR[3];
- return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
- case 'AlternateCS':
- var numComps = IR[1];
- var alt = IR[2];
- var tintFnIR = IR[3];
- return new AlternateCS(numComps, ColorSpace.fromIR(alt), PDFFunction.fromIR(tintFnIR));
- case 'LabCS':
- whitePoint = IR[1];
- blackPoint = IR[2];
- var range = IR[3];
- return new LabCS(whitePoint, blackPoint, range);
- default:
- error('Unknown name ' + name);
- }
- return null;
- };
- ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
- if (isName(cs)) {
- var colorSpaces = res.get('ColorSpace');
- if (isDict(colorSpaces)) {
- var refcs = colorSpaces.get(cs.name);
- if (refcs) {
- cs = refcs;
- }
- }
- }
- cs = xref.fetchIfRef(cs);
- if (isName(cs)) {
- switch (cs.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:
- error('unrecognized colorspace ' + cs.name);
- }
- } else if (isArray(cs)) {
- var mode = xref.fetchIfRef(cs[0]).name;
- var numComps, params, alt, whitePoint, blackPoint, gamma;
- switch (mode) {
- case 'DeviceGray':
- case 'G':
- return 'DeviceGrayCS';
- case 'DeviceRGB':
- case 'RGB':
- return 'DeviceRgbCS';
- case 'DeviceCMYK':
- case 'CMYK':
- return 'DeviceCmykCS';
- case 'CalGray':
- params = xref.fetchIfRef(cs[1]);
- whitePoint = params.getArray('WhitePoint');
- blackPoint = params.getArray('BlackPoint');
- gamma = params.get('Gamma');
- return ['CalGrayCS', whitePoint, blackPoint, gamma];
- case 'CalRGB':
- params = xref.fetchIfRef(cs[1]);
- whitePoint = params.getArray('WhitePoint');
- blackPoint = params.getArray('BlackPoint');
- gamma = params.getArray('Gamma');
- var matrix = params.getArray('Matrix');
- return ['CalRGBCS', whitePoint, blackPoint, gamma, matrix];
- case 'ICCBased':
- var stream = xref.fetchIfRef(cs[1]);
- var dict = stream.dict;
- numComps = dict.get('N');
- alt = dict.get('Alternate');
- if (alt) {
- var altIR = ColorSpace.parseToIR(alt, xref, res);
- var altCS = ColorSpace.fromIR(altIR);
- if (altCS.numComps === numComps) {
- return altIR;
- }
- warn('ICCBased color space: Ignoring incorrect /Alternate entry.');
- }
- if (numComps === 1) {
- return 'DeviceGrayCS';
- } else if (numComps === 3) {
- return 'DeviceRgbCS';
- } else if (numComps === 4) {
- return 'DeviceCmykCS';
- }
- break;
- case 'Pattern':
- var basePatternCS = cs[1] || null;
- if (basePatternCS) {
- basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
- }
- return ['PatternCS', basePatternCS];
- case 'Indexed':
- case 'I':
- var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
- var hiVal = xref.fetchIfRef(cs[2]) + 1;
- var lookup = xref.fetchIfRef(cs[3]);
- if (isStream(lookup)) {
- lookup = lookup.getBytes();
- }
- return ['IndexedCS', baseIndexedCS, hiVal, lookup];
- case 'Separation':
- case 'DeviceN':
- var name = xref.fetchIfRef(cs[1]);
- numComps = isArray(name) ? name.length : 1;
- alt = ColorSpace.parseToIR(cs[2], xref, res);
- var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
- return ['AlternateCS', numComps, alt, tintFnIR];
- case 'Lab':
- params = xref.fetchIfRef(cs[1]);
- whitePoint = params.getArray('WhitePoint');
- blackPoint = params.getArray('BlackPoint');
- var range = params.getArray('Range');
- return ['LabCS', whitePoint, blackPoint, range];
- default:
- error('unimplemented color space object "' + mode + '"');
- }
- } else {
- error('unrecognized color space object: "' + cs + '"');
- }
- return null;
- };
- ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
- if (!isArray(decode)) {
- return true;
- }
- if (n * 2 !== decode.length) {
- warn('The decode map is not the correct length');
- return true;
- }
- for (var i = 0, ii = decode.length; i < ii; i += 2) {
- if (decode[i] !== 0 || decode[i + 1] !== 1) {
- return false;
- }
- }
- return true;
- };
- ColorSpace.singletons = {
- get gray() {
- return shadow(this, 'gray', new DeviceGrayCS());
- },
- get rgb() {
- return shadow(this, 'rgb', new DeviceRgbCS());
- },
- get cmyk() {
- return shadow(this, 'cmyk', new DeviceCmykCS());
- }
- };
- return ColorSpace;
-}();
-var AlternateCS = function AlternateCSClosure() {
- function AlternateCS(numComps, base, tintFn) {
- this.name = 'Alternate';
- this.numComps = numComps;
- this.defaultColor = new Float32Array(numComps);
- for (var i = 0; i < numComps; ++i) {
- this.defaultColor[i] = 1;
- }
- this.base = base;
- this.tintFn = tintFn;
- this.tmpBuf = new Float32Array(base.numComps);
- }
- AlternateCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, dest, destOffset) {
- var tmpBuf = this.tmpBuf;
- this.tintFn(src, srcOffset, tmpBuf, 0);
- this.base.getRgbItem(tmpBuf, 0, dest, destOffset);
- },
- getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var tintFn = this.tintFn;
- var base = this.base;
- var scale = 1 / ((1 << bits) - 1);
- var baseNumComps = base.numComps;
- var usesZeroToOneRange = base.usesZeroToOneRange;
- var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
- var pos = isPassthrough ? destOffset : 0;
- var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
- var numComps = this.numComps;
- var scaled = new Float32Array(numComps);
- var tinted = new Float32Array(baseNumComps);
- var i, j;
- for (i = 0; i < count; i++) {
- for (j = 0; j < numComps; j++) {
- scaled[j] = src[srcOffset++] * scale;
- }
- tintFn(scaled, 0, tinted, 0);
- if (usesZeroToOneRange) {
- for (j = 0; j < baseNumComps; j++) {
- baseBuf[pos++] = tinted[j] * 255;
- }
- } else {
- base.getRgbItem(tinted, 0, baseBuf, pos);
- pos += baseNumComps;
- }
- }
- if (!isPassthrough) {
- base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
- }
- },
- getOutputLength: function AlternateCS_getOutputLength(inputLength, alpha01) {
- return this.base.getOutputLength(inputLength * this.base.numComps / this.numComps, alpha01);
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return AlternateCS;
-}();
-var PatternCS = function PatternCSClosure() {
- function PatternCS(baseCS) {
- this.name = 'Pattern';
- this.base = baseCS;
- }
- PatternCS.prototype = {};
- return PatternCS;
-}();
-var IndexedCS = function IndexedCSClosure() {
- function IndexedCS(base, highVal, lookup) {
- this.name = 'Indexed';
- this.numComps = 1;
- this.defaultColor = new Uint8Array(this.numComps);
- this.base = base;
- this.highVal = highVal;
- var baseNumComps = base.numComps;
- var length = baseNumComps * highVal;
- if (isStream(lookup)) {
- this.lookup = new Uint8Array(length);
- var bytes = lookup.getBytes(length);
- this.lookup.set(bytes);
- } else if (isString(lookup)) {
- this.lookup = new Uint8Array(length);
- for (var i = 0; i < length; ++i) {
- this.lookup[i] = lookup.charCodeAt(i);
- }
- } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
- this.lookup = lookup;
- } else {
- error('Unrecognized lookup table: ' + lookup);
- }
- }
- IndexedCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, dest, destOffset) {
- var numComps = this.base.numComps;
- var start = src[srcOffset] * numComps;
- this.base.getRgbItem(this.lookup, start, dest, destOffset);
- },
- getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var base = this.base;
- var numComps = base.numComps;
- var outputDelta = base.getOutputLength(numComps, alpha01);
- var lookup = this.lookup;
- for (var i = 0; i < count; ++i) {
- var lookupPos = src[srcOffset++] * numComps;
- base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
- destOffset += outputDelta;
- }
- },
- getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) {
- return this.base.getOutputLength(inputLength * this.base.numComps, alpha01);
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
- return true;
- },
- usesZeroToOneRange: true
- };
- return IndexedCS;
-}();
-var DeviceGrayCS = function DeviceGrayCSClosure() {
- function DeviceGrayCS() {
- this.name = 'DeviceGray';
- this.numComps = 1;
- this.defaultColor = new Float32Array(this.numComps);
- }
- DeviceGrayCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
- var c = src[srcOffset] * 255 | 0;
- c = c < 0 ? 0 : c > 255 ? 255 : c;
- dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
- },
- getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var scale = 255 / ((1 << bits) - 1);
- var j = srcOffset,
- q = destOffset;
- for (var i = 0; i < count; ++i) {
- var c = scale * src[j++] | 0;
- dest[q++] = c;
- dest[q++] = c;
- dest[q++] = c;
- q += alpha01;
- }
- },
- getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, alpha01) {
- return inputLength * (3 + alpha01);
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return DeviceGrayCS;
-}();
-var DeviceRgbCS = function DeviceRgbCSClosure() {
- function DeviceRgbCS() {
- this.name = 'DeviceRGB';
- this.numComps = 3;
- this.defaultColor = new Float32Array(this.numComps);
- }
- DeviceRgbCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, dest, destOffset) {
- var r = src[srcOffset] * 255 | 0;
- var g = src[srcOffset + 1] * 255 | 0;
- var b = src[srcOffset + 2] * 255 | 0;
- dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
- dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g;
- dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b;
- },
- getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- if (bits === 8 && alpha01 === 0) {
- dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
- return;
- }
- var scale = 255 / ((1 << bits) - 1);
- var j = srcOffset,
- q = destOffset;
- for (var i = 0; i < count; ++i) {
- dest[q++] = scale * src[j++] | 0;
- dest[q++] = scale * src[j++] | 0;
- dest[q++] = scale * src[j++] | 0;
- q += alpha01;
- }
- },
- getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, alpha01) {
- return inputLength * (3 + alpha01) / 3 | 0;
- },
- isPassthrough: function DeviceRgbCS_isPassthrough(bits) {
- return bits === 8;
- },
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return DeviceRgbCS;
-}();
-var DeviceCmykCS = function DeviceCmykCSClosure() {
- function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
- var c = src[srcOffset + 0] * srcScale;
- var m = src[srcOffset + 1] * srcScale;
- var y = src[srcOffset + 2] * srcScale;
- var k = src[srcOffset + 3] * srcScale;
- var r = c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k + -285.2331026137004) + m * (1.7149763477362134 * m - 5.6096736904047315 * y + -17.873870861415444 * k - 5.497006427196366) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + k * (-21.86122147463605 * k - 189.48180835922747) + 255 | 0;
- var g = c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k + -79.2970844816548) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + k * (-20.737325471181034 * k - 187.80453709719578) + 255 | 0;
- var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367) + 255 | 0;
- dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
- dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
- dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
- }
- function DeviceCmykCS() {
- this.name = 'DeviceCMYK';
- this.numComps = 4;
- this.defaultColor = new Float32Array(this.numComps);
- this.defaultColor[3] = 1;
- }
- DeviceCmykCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, dest, destOffset) {
- convertToRgb(src, srcOffset, 1, dest, destOffset);
- },
- getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var scale = 1 / ((1 << bits) - 1);
- for (var i = 0; i < count; i++) {
- convertToRgb(src, srcOffset, scale, dest, destOffset);
- srcOffset += 4;
- destOffset += 3 + alpha01;
- }
- },
- getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, alpha01) {
- return inputLength / 4 * (3 + alpha01) | 0;
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return DeviceCmykCS;
-}();
-var CalGrayCS = function CalGrayCSClosure() {
- function CalGrayCS(whitePoint, blackPoint, gamma) {
- this.name = 'CalGray';
- this.numComps = 1;
- this.defaultColor = new Float32Array(this.numComps);
- if (!whitePoint) {
- error('WhitePoint missing - required for color space CalGray');
- }
- blackPoint = blackPoint || [0, 0, 0];
- gamma = gamma || 1;
- this.XW = whitePoint[0];
- this.YW = whitePoint[1];
- this.ZW = whitePoint[2];
- this.XB = blackPoint[0];
- this.YB = blackPoint[1];
- this.ZB = blackPoint[2];
- this.G = gamma;
- if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
- error('Invalid WhitePoint components for ' + this.name + ', no fallback available');
- }
- if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
- info('Invalid BlackPoint for ' + this.name + ', falling back to default');
- this.XB = this.YB = this.ZB = 0;
- }
- if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
- warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + ', ZB: ' + this.ZB + ', only default values are supported.');
- }
- if (this.G < 1) {
- info('Invalid Gamma: ' + this.G + ' for ' + this.name + ', falling back to default');
- this.G = 1;
- }
- }
- function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
- var A = src[srcOffset] * scale;
- var AG = Math.pow(A, cs.G);
- var L = cs.YW * AG;
- var val = Math.max(295.8 * Math.pow(L, 0.333333333333333333) - 40.8, 0) | 0;
- dest[destOffset] = val;
- dest[destOffset + 1] = val;
- dest[destOffset + 2] = val;
- }
- CalGrayCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
- convertToRgb(this, src, srcOffset, dest, destOffset, 1);
- },
- getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var scale = 1 / ((1 << bits) - 1);
- for (var i = 0; i < count; ++i) {
- convertToRgb(this, src, srcOffset, dest, destOffset, scale);
- srcOffset += 1;
- destOffset += 3 + alpha01;
- }
- },
- getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) {
- return inputLength * (3 + alpha01);
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return CalGrayCS;
-}();
-var CalRGBCS = function CalRGBCSClosure() {
- var BRADFORD_SCALE_MATRIX = new Float32Array([0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296]);
- var BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428, 0.9684867]);
- var SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252]);
- var FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]);
- var tempNormalizeMatrix = new Float32Array(3);
- var tempConvertMatrix1 = new Float32Array(3);
- var tempConvertMatrix2 = new Float32Array(3);
- var DECODE_L_CONSTANT = Math.pow((8 + 16) / 116, 3) / 8.0;
- function CalRGBCS(whitePoint, blackPoint, gamma, matrix) {
- this.name = 'CalRGB';
- this.numComps = 3;
- this.defaultColor = new Float32Array(this.numComps);
- if (!whitePoint) {
- error('WhitePoint missing - required for color space CalRGB');
- }
- blackPoint = blackPoint || new Float32Array(3);
- gamma = gamma || new Float32Array([1, 1, 1]);
- matrix = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
- var XW = whitePoint[0];
- var YW = whitePoint[1];
- var ZW = whitePoint[2];
- this.whitePoint = whitePoint;
- var XB = blackPoint[0];
- var YB = blackPoint[1];
- var ZB = blackPoint[2];
- this.blackPoint = blackPoint;
- this.GR = gamma[0];
- this.GG = gamma[1];
- this.GB = gamma[2];
- this.MXA = matrix[0];
- this.MYA = matrix[1];
- this.MZA = matrix[2];
- this.MXB = matrix[3];
- this.MYB = matrix[4];
- this.MZB = matrix[5];
- this.MXC = matrix[6];
- this.MYC = matrix[7];
- this.MZC = matrix[8];
- if (XW < 0 || ZW < 0 || YW !== 1) {
- error('Invalid WhitePoint components for ' + this.name + ', no fallback available');
- }
- if (XB < 0 || YB < 0 || ZB < 0) {
- info('Invalid BlackPoint for ' + this.name + ' [' + XB + ', ' + YB + ', ' + ZB + '], falling back to default');
- this.blackPoint = new Float32Array(3);
- }
- if (this.GR < 0 || this.GG < 0 || this.GB < 0) {
- info('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) {
- info('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 matrixProduct(a, b, result) {
- result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
- result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2];
- result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2];
- }
- function convertToFlat(sourceWhitePoint, LMS, result) {
- result[0] = LMS[0] * 1 / sourceWhitePoint[0];
- result[1] = LMS[1] * 1 / sourceWhitePoint[1];
- result[2] = LMS[2] * 1 / sourceWhitePoint[2];
- }
- function convertToD65(sourceWhitePoint, LMS, result) {
- var D65X = 0.95047;
- var D65Y = 1;
- var D65Z = 1.08883;
- result[0] = LMS[0] * D65X / sourceWhitePoint[0];
- result[1] = LMS[1] * D65Y / sourceWhitePoint[1];
- result[2] = LMS[2] * D65Z / sourceWhitePoint[2];
- }
- function sRGBTransferFunction(color) {
- if (color <= 0.0031308) {
- return adjustToRange(0, 1, 12.92 * color);
- }
- return adjustToRange(0, 1, (1 + 0.055) * Math.pow(color, 1 / 2.4) - 0.055);
- }
- function adjustToRange(min, max, value) {
- return Math.max(min, Math.min(max, value));
- }
- function decodeL(L) {
- if (L < 0) {
- return -decodeL(-L);
- }
- if (L > 8.0) {
- return Math.pow((L + 16) / 116, 3);
- }
- return L * DECODE_L_CONSTANT;
- }
- function compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) {
- if (sourceBlackPoint[0] === 0 && sourceBlackPoint[1] === 0 && sourceBlackPoint[2] === 0) {
- result[0] = XYZ_Flat[0];
- result[1] = XYZ_Flat[1];
- result[2] = XYZ_Flat[2];
- return;
- }
- var zeroDecodeL = decodeL(0);
- var X_DST = zeroDecodeL;
- var X_SRC = decodeL(sourceBlackPoint[0]);
- var Y_DST = zeroDecodeL;
- var Y_SRC = decodeL(sourceBlackPoint[1]);
- var Z_DST = zeroDecodeL;
- var Z_SRC = decodeL(sourceBlackPoint[2]);
- var X_Scale = (1 - X_DST) / (1 - X_SRC);
- var X_Offset = 1 - X_Scale;
- var Y_Scale = (1 - Y_DST) / (1 - Y_SRC);
- var Y_Offset = 1 - Y_Scale;
- var Z_Scale = (1 - Z_DST) / (1 - Z_SRC);
- var Z_Offset = 1 - Z_Scale;
- result[0] = XYZ_Flat[0] * X_Scale + X_Offset;
- result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset;
- result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset;
- }
- function normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) {
- if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) {
- result[0] = XYZ_In[0];
- result[1] = XYZ_In[1];
- result[2] = XYZ_In[2];
- return;
- }
- var LMS = result;
- matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
- var LMS_Flat = tempNormalizeMatrix;
- convertToFlat(sourceWhitePoint, LMS, LMS_Flat);
- matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result);
- }
- function normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) {
- var LMS = result;
- matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
- var LMS_D65 = tempNormalizeMatrix;
- convertToD65(sourceWhitePoint, LMS, LMS_D65);
- matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result);
- }
- function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
- var A = adjustToRange(0, 1, src[srcOffset] * scale);
- var B = adjustToRange(0, 1, src[srcOffset + 1] * scale);
- var C = adjustToRange(0, 1, src[srcOffset + 2] * scale);
- var AGR = Math.pow(A, cs.GR);
- var BGG = Math.pow(B, cs.GG);
- var CGB = Math.pow(C, cs.GB);
- var X = cs.MXA * AGR + cs.MXB * BGG + cs.MXC * CGB;
- var Y = cs.MYA * AGR + cs.MYB * BGG + cs.MYC * CGB;
- var Z = cs.MZA * AGR + cs.MZB * BGG + cs.MZC * CGB;
- var XYZ = tempConvertMatrix1;
- XYZ[0] = X;
- XYZ[1] = Y;
- XYZ[2] = Z;
- var XYZ_Flat = tempConvertMatrix2;
- normalizeWhitePointToFlat(cs.whitePoint, XYZ, XYZ_Flat);
- var XYZ_Black = tempConvertMatrix1;
- compensateBlackPoint(cs.blackPoint, XYZ_Flat, XYZ_Black);
- var XYZ_D65 = tempConvertMatrix2;
- normalizeWhitePointToD65(FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65);
- var SRGB = tempConvertMatrix1;
- matrixProduct(SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB);
- var sR = sRGBTransferFunction(SRGB[0]);
- var sG = sRGBTransferFunction(SRGB[1]);
- var sB = sRGBTransferFunction(SRGB[2]);
- dest[destOffset] = Math.round(sR * 255);
- dest[destOffset + 1] = Math.round(sG * 255);
- dest[destOffset + 2] = Math.round(sB * 255);
- }
- CalRGBCS.prototype = {
- getRgb: function CalRGBCS_getRgb(src, srcOffset) {
- var rgb = new Uint8Array(3);
- this.getRgbItem(src, srcOffset, rgb, 0);
- return rgb;
- },
- getRgbItem: function CalRGBCS_getRgbItem(src, srcOffset, dest, destOffset) {
- convertToRgb(this, src, srcOffset, dest, destOffset, 1);
- },
- getRgbBuffer: function CalRGBCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var scale = 1 / ((1 << bits) - 1);
- for (var i = 0; i < count; ++i) {
- convertToRgb(this, src, srcOffset, dest, destOffset, scale);
- srcOffset += 3;
- destOffset += 3 + alpha01;
- }
- },
- getOutputLength: function CalRGBCS_getOutputLength(inputLength, alpha01) {
- return inputLength * (3 + alpha01) / 3 | 0;
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function CalRGBCS_isDefaultDecode(decodeMap) {
- return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
- },
- usesZeroToOneRange: true
- };
- return CalRGBCS;
-}();
-var LabCS = function LabCSClosure() {
- function LabCS(whitePoint, blackPoint, range) {
- this.name = 'Lab';
- this.numComps = 3;
- this.defaultColor = new Float32Array(this.numComps);
- if (!whitePoint) {
- error('WhitePoint missing - required for color space Lab');
- }
- blackPoint = blackPoint || [0, 0, 0];
- range = range || [-100, 100, -100, 100];
- this.XW = whitePoint[0];
- this.YW = whitePoint[1];
- this.ZW = whitePoint[2];
- this.amin = range[0];
- this.amax = range[1];
- this.bmin = range[2];
- this.bmax = range[3];
- this.XB = blackPoint[0];
- this.YB = blackPoint[1];
- this.ZB = blackPoint[2];
- if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
- error('Invalid WhitePoint components, no fallback available');
- }
- if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
- info('Invalid BlackPoint, falling back to default');
- this.XB = this.YB = this.ZB = 0;
- }
- if (this.amin > this.amax || this.bmin > this.bmax) {
- info('Invalid Range, falling back to defaults');
- this.amin = -100;
- this.amax = 100;
- this.bmin = -100;
- this.bmax = 100;
- }
- }
- function fn_g(x) {
- var result;
- if (x >= 6 / 29) {
- result = x * x * x;
- } else {
- result = 108 / 841 * (x - 4 / 29);
- }
- return result;
- }
- function decode(value, high1, low2, high2) {
- return low2 + value * (high2 - low2) / high1;
- }
- function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
- var Ls = src[srcOffset];
- var as = src[srcOffset + 1];
- var bs = src[srcOffset + 2];
- if (maxVal !== false) {
- Ls = decode(Ls, maxVal, 0, 100);
- as = decode(as, maxVal, cs.amin, cs.amax);
- bs = decode(bs, maxVal, cs.bmin, cs.bmax);
- }
- as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as;
- bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs;
- var M = (Ls + 16) / 116;
- var L = M + as / 500;
- var N = M - bs / 200;
- var X = cs.XW * fn_g(L);
- var Y = cs.YW * fn_g(M);
- var Z = cs.ZW * fn_g(N);
- var r, g, b;
- if (cs.ZW < 1) {
- r = X * 3.1339 + Y * -1.6170 + Z * -0.4906;
- g = X * -0.9785 + Y * 1.9160 + Z * 0.0333;
- b = X * 0.0720 + Y * -0.2290 + Z * 1.4057;
- } else {
- r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
- g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
- b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
- }
- dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0;
- dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0;
- dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0;
- }
- LabCS.prototype = {
- getRgb: ColorSpace.prototype.getRgb,
- getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) {
- convertToRgb(this, src, srcOffset, false, dest, destOffset);
- },
- getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
- var maxVal = (1 << bits) - 1;
- for (var i = 0; i < count; i++) {
- convertToRgb(this, src, srcOffset, maxVal, dest, destOffset);
- srcOffset += 3;
- destOffset += 3 + alpha01;
- }
- },
- getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) {
- return inputLength * (3 + alpha01) / 3 | 0;
- },
- isPassthrough: ColorSpace.prototype.isPassthrough,
- fillRgb: ColorSpace.prototype.fillRgb,
- isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
- return true;
- },
- usesZeroToOneRange: false
- };
- return LabCS;
-}();
-exports.ColorSpace = ColorSpace;
-
-/***/ }),
-/* 4 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var ExpertEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var MacExpertEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var MacRomanEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var StandardEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var WinAnsiEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var SymbolSetEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-var ZapfDingbatsEncoding = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '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'];
-function getEncoding(encodingName) {
- switch (encodingName) {
- case 'WinAnsiEncoding':
- return WinAnsiEncoding;
- case 'StandardEncoding':
- return StandardEncoding;
- case 'MacRomanEncoding':
- return MacRomanEncoding;
- case 'SymbolSetEncoding':
- return SymbolSetEncoding;
- case 'ZapfDingbatsEncoding':
- return ZapfDingbatsEncoding;
- case 'ExpertEncoding':
- return ExpertEncoding;
- case 'MacExpertEncoding':
- return MacExpertEncoding;
- default:
- return null;
- }
-}
-exports.WinAnsiEncoding = WinAnsiEncoding;
-exports.StandardEncoding = StandardEncoding;
-exports.MacRomanEncoding = MacRomanEncoding;
-exports.SymbolSetEncoding = SymbolSetEncoding;
-exports.ZapfDingbatsEncoding = ZapfDingbatsEncoding;
-exports.ExpertEncoding = ExpertEncoding;
-exports.getEncoding = getEncoding;
-
-/***/ }),
-/* 5 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var MissingDataException = sharedUtil.MissingDataException;
-var StreamType = sharedUtil.StreamType;
-var assert = sharedUtil.assert;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isInt = sharedUtil.isInt;
-var isNum = sharedUtil.isNum;
-var isString = sharedUtil.isString;
-var warn = sharedUtil.warn;
-var EOF = corePrimitives.EOF;
-var Cmd = corePrimitives.Cmd;
-var Dict = corePrimitives.Dict;
-var Name = corePrimitives.Name;
-var Ref = corePrimitives.Ref;
-var isEOF = corePrimitives.isEOF;
-var isCmd = corePrimitives.isCmd;
-var isDict = corePrimitives.isDict;
-var isName = corePrimitives.isName;
-var Ascii85Stream = coreStream.Ascii85Stream;
-var AsciiHexStream = coreStream.AsciiHexStream;
-var CCITTFaxStream = coreStream.CCITTFaxStream;
-var FlateStream = coreStream.FlateStream;
-var Jbig2Stream = coreStream.Jbig2Stream;
-var JpegStream = coreStream.JpegStream;
-var JpxStream = coreStream.JpxStream;
-var LZWStream = coreStream.LZWStream;
-var NullStream = coreStream.NullStream;
-var PredictorStream = coreStream.PredictorStream;
-var RunLengthStream = coreStream.RunLengthStream;
-var MAX_LENGTH_TO_CACHE = 1000;
-var Parser = function ParserClosure() {
- function Parser(lexer, allowStreams, xref, recoveryMode) {
- this.lexer = lexer;
- this.allowStreams = allowStreams;
- this.xref = xref;
- this.recoveryMode = recoveryMode || false;
- this.imageCache = Object.create(null);
- this.refill();
- }
- Parser.prototype = {
- refill: function Parser_refill() {
- this.buf1 = this.lexer.getObj();
- this.buf2 = this.lexer.getObj();
- },
- shift: function Parser_shift() {
- if (isCmd(this.buf2, 'ID')) {
- this.buf1 = this.buf2;
- this.buf2 = null;
- } else {
- this.buf1 = this.buf2;
- this.buf2 = this.lexer.getObj();
- }
- },
- tryShift: function Parser_tryShift() {
- try {
- this.shift();
- return true;
- } catch (e) {
- if (e instanceof MissingDataException) {
- throw e;
- }
- return false;
- }
- },
- getObj: function Parser_getObj(cipherTransform) {
- var buf1 = this.buf1;
- this.shift();
- if (buf1 instanceof Cmd) {
- switch (buf1.cmd) {
- case 'BI':
- return this.makeInlineImage(cipherTransform);
- case '[':
- var array = [];
- while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) {
- array.push(this.getObj(cipherTransform));
- }
- if (isEOF(this.buf1)) {
- if (!this.recoveryMode) {
- error('End of file inside array');
- }
- return array;
- }
- this.shift();
- return array;
- case '<<':
- var dict = new Dict(this.xref);
- while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
- if (!isName(this.buf1)) {
- info('Malformed dictionary: key must be a name object');
- this.shift();
- continue;
- }
- var key = this.buf1.name;
- this.shift();
- if (isEOF(this.buf1)) {
- break;
- }
- dict.set(key, this.getObj(cipherTransform));
- }
- if (isEOF(this.buf1)) {
- if (!this.recoveryMode) {
- error('End of file inside dictionary');
- }
- return dict;
- }
- if (isCmd(this.buf2, 'stream')) {
- return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
- }
- this.shift();
- return dict;
- default:
- return buf1;
- }
- }
- if (isInt(buf1)) {
- var num = buf1;
- if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
- var ref = new Ref(num, this.buf1);
- this.shift();
- this.shift();
- return ref;
- }
- return num;
- }
- if (isString(buf1)) {
- var str = buf1;
- if (cipherTransform) {
- str = cipherTransform.decryptString(str);
- }
- return str;
- }
- return buf1;
- },
- findDefaultInlineStreamEnd: function Parser_findDefaultInlineStreamEnd(stream) {
- var E = 0x45,
- I = 0x49,
- SPACE = 0x20,
- LF = 0xA,
- CR = 0xD;
- var startPos = stream.pos,
- state = 0,
- ch,
- i,
- n,
- followingBytes;
- while ((ch = stream.getByte()) !== -1) {
- if (state === 0) {
- state = ch === E ? 1 : 0;
- } else if (state === 1) {
- state = ch === I ? 2 : 0;
- } else {
- assert(state === 2);
- if (ch === SPACE || ch === LF || ch === CR) {
- n = 5;
- followingBytes = stream.peekBytes(n);
- for (i = 0; i < n; i++) {
- ch = followingBytes[i];
- if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) {
- state = 0;
- break;
- }
- }
- if (state === 2) {
- break;
- }
- } else {
- state = 0;
- }
- }
- }
- return stream.pos - 4 - startPos;
- },
- findDCTDecodeInlineStreamEnd: function Parser_findDCTDecodeInlineStreamEnd(stream) {
- var startPos = stream.pos,
- foundEOI = false,
- b,
- markerLength,
- length;
- while ((b = stream.getByte()) !== -1) {
- if (b !== 0xFF) {
- continue;
- }
- switch (stream.getByte()) {
- case 0x00:
- break;
- case 0xFF:
- stream.skip(-1);
- break;
- case 0xD9:
- foundEOI = true;
- break;
- case 0xC0:
- case 0xC1:
- case 0xC2:
- case 0xC3:
- case 0xC5:
- case 0xC6:
- case 0xC7:
- case 0xC9:
- case 0xCA:
- case 0xCB:
- case 0xCD:
- case 0xCE:
- case 0xCF:
- case 0xC4:
- case 0xCC:
- case 0xDA:
- case 0xDB:
- case 0xDC:
- case 0xDD:
- case 0xDE:
- case 0xDF:
- case 0xE0:
- case 0xE1:
- case 0xE2:
- case 0xE3:
- case 0xE4:
- case 0xE5:
- case 0xE6:
- case 0xE7:
- case 0xE8:
- case 0xE9:
- case 0xEA:
- case 0xEB:
- case 0xEC:
- case 0xED:
- case 0xEE:
- case 0xEF:
- case 0xFE:
- markerLength = stream.getUint16();
- if (markerLength > 2) {
- stream.skip(markerLength - 2);
- } else {
- stream.skip(-2);
- }
- break;
- }
- if (foundEOI) {
- break;
- }
- }
- length = stream.pos - startPos;
- if (b === -1) {
- warn('Inline DCTDecode image stream: ' + 'EOI marker not found, searching for /EI/ instead.');
- stream.skip(-length);
- return this.findDefaultInlineStreamEnd(stream);
- }
- this.inlineStreamSkipEI(stream);
- return length;
- },
- findASCII85DecodeInlineStreamEnd: function Parser_findASCII85DecodeInlineStreamEnd(stream) {
- var TILDE = 0x7E,
- GT = 0x3E;
- var startPos = stream.pos,
- ch,
- length;
- while ((ch = stream.getByte()) !== -1) {
- if (ch === TILDE && stream.peekByte() === GT) {
- stream.skip();
- break;
- }
- }
- length = stream.pos - startPos;
- if (ch === -1) {
- warn('Inline ASCII85Decode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
- stream.skip(-length);
- return this.findDefaultInlineStreamEnd(stream);
- }
- this.inlineStreamSkipEI(stream);
- return length;
- },
- findASCIIHexDecodeInlineStreamEnd: function Parser_findASCIIHexDecodeInlineStreamEnd(stream) {
- var GT = 0x3E;
- var startPos = stream.pos,
- ch,
- length;
- while ((ch = stream.getByte()) !== -1) {
- if (ch === GT) {
- break;
- }
- }
- length = stream.pos - startPos;
- if (ch === -1) {
- warn('Inline ASCIIHexDecode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
- stream.skip(-length);
- return this.findDefaultInlineStreamEnd(stream);
- }
- this.inlineStreamSkipEI(stream);
- return length;
- },
- inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) {
- var E = 0x45,
- I = 0x49;
- var state = 0,
- ch;
- while ((ch = stream.getByte()) !== -1) {
- if (state === 0) {
- state = ch === E ? 1 : 0;
- } else if (state === 1) {
- state = ch === I ? 2 : 0;
- } else if (state === 2) {
- break;
- }
- }
- },
- makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
- var lexer = this.lexer;
- var stream = lexer.stream;
- var dict = new Dict(this.xref);
- while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
- if (!isName(this.buf1)) {
- error('Dictionary key must be a name object');
- }
- var key = this.buf1.name;
- this.shift();
- if (isEOF(this.buf1)) {
- break;
- }
- dict.set(key, this.getObj(cipherTransform));
- }
- var filter = dict.get('Filter', 'F'),
- filterName;
- if (isName(filter)) {
- filterName = filter.name;
- } else if (isArray(filter)) {
- var filterZero = this.xref.fetchIfRef(filter[0]);
- if (isName(filterZero)) {
- filterName = filterZero.name;
- }
- }
- var startPos = stream.pos,
- length,
- i,
- ii;
- if (filterName === 'DCTDecode' || filterName === 'DCT') {
- length = this.findDCTDecodeInlineStreamEnd(stream);
- } else if (filterName === 'ASCII85Decide' || filterName === 'A85') {
- length = this.findASCII85DecodeInlineStreamEnd(stream);
- } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') {
- length = this.findASCIIHexDecodeInlineStreamEnd(stream);
- } else {
- length = this.findDefaultInlineStreamEnd(stream);
- }
- var imageStream = stream.makeSubStream(startPos, length, dict);
- var adler32;
- if (length < MAX_LENGTH_TO_CACHE) {
- var imageBytes = imageStream.getBytes();
- imageStream.reset();
- var a = 1;
- var b = 0;
- for (i = 0, ii = imageBytes.length; i < ii; ++i) {
- a += imageBytes[i] & 0xff;
- b += a;
- }
- adler32 = b % 65521 << 16 | a % 65521;
- if (this.imageCache.adler32 === adler32) {
- this.buf2 = Cmd.get('EI');
- this.shift();
- this.imageCache[adler32].reset();
- return this.imageCache[adler32];
- }
- }
- if (cipherTransform) {
- imageStream = cipherTransform.createStream(imageStream, length);
- }
- imageStream = this.filter(imageStream, dict, length);
- imageStream.dict = dict;
- if (adler32 !== undefined) {
- imageStream.cacheKey = 'inline_' + length + '_' + adler32;
- this.imageCache[adler32] = imageStream;
- }
- this.buf2 = Cmd.get('EI');
- this.shift();
- return imageStream;
- },
- makeStream: function Parser_makeStream(dict, cipherTransform) {
- var lexer = this.lexer;
- var stream = lexer.stream;
- lexer.skipToNextLine();
- var pos = stream.pos - 1;
- var length = dict.get('Length');
- if (!isInt(length)) {
- info('Bad ' + length + ' attribute in stream');
- length = 0;
- }
- stream.pos = pos + length;
- lexer.nextChar();
- if (this.tryShift() && isCmd(this.buf2, 'endstream')) {
- this.shift();
- } else {
- stream.pos = pos;
- var SCAN_BLOCK_SIZE = 2048;
- var ENDSTREAM_SIGNATURE_LENGTH = 9;
- var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D];
- var skipped = 0,
- found = false,
- i,
- j;
- while (stream.pos < stream.end) {
- var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
- var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
- if (scanLength <= 0) {
- break;
- }
- found = false;
- i = 0;
- while (i < scanLength) {
- j = 0;
- while (j < ENDSTREAM_SIGNATURE_LENGTH && scanBytes[i + j] === ENDSTREAM_SIGNATURE[j]) {
- j++;
- }
- if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
- found = true;
- break;
- }
- i++;
- }
- if (found) {
- skipped += i;
- stream.pos += i;
- break;
- }
- skipped += scanLength;
- stream.pos += scanLength;
- }
- if (!found) {
- error('Missing endstream');
- }
- length = skipped;
- lexer.nextChar();
- this.shift();
- this.shift();
- }
- this.shift();
- stream = stream.makeSubStream(pos, length, dict);
- if (cipherTransform) {
- stream = cipherTransform.createStream(stream, length);
- }
- stream = this.filter(stream, dict, length);
- stream.dict = dict;
- return stream;
- },
- filter: function Parser_filter(stream, dict, length) {
- var filter = dict.get('Filter', 'F');
- var params = dict.get('DecodeParms', 'DP');
- if (isName(filter)) {
- if (isArray(params)) {
- params = this.xref.fetchIfRef(params[0]);
- }
- return this.makeFilter(stream, filter.name, length, params);
- }
- var maybeLength = length;
- if (isArray(filter)) {
- var filterArray = filter;
- var paramsArray = params;
- for (var i = 0, ii = filterArray.length; i < ii; ++i) {
- filter = this.xref.fetchIfRef(filterArray[i]);
- if (!isName(filter)) {
- error('Bad filter name: ' + filter);
- }
- params = null;
- if (isArray(paramsArray) && i in paramsArray) {
- params = this.xref.fetchIfRef(paramsArray[i]);
- }
- stream = this.makeFilter(stream, filter.name, maybeLength, params);
- maybeLength = null;
- }
- }
- return stream;
- },
- makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
- if (maybeLength === 0) {
- warn('Empty "' + name + '" stream.');
- return new NullStream(stream);
- }
- try {
- var xrefStreamStats = this.xref.stats.streamTypes;
- if (name === 'FlateDecode' || name === 'Fl') {
- xrefStreamStats[StreamType.FLATE] = true;
- if (params) {
- return new PredictorStream(new FlateStream(stream, maybeLength), maybeLength, params);
- }
- return new FlateStream(stream, maybeLength);
- }
- if (name === 'LZWDecode' || name === 'LZW') {
- xrefStreamStats[StreamType.LZW] = true;
- var earlyChange = 1;
- if (params) {
- if (params.has('EarlyChange')) {
- earlyChange = params.get('EarlyChange');
- }
- return new PredictorStream(new LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
- }
- return new LZWStream(stream, maybeLength, earlyChange);
- }
- if (name === 'DCTDecode' || name === 'DCT') {
- xrefStreamStats[StreamType.DCT] = true;
- return new JpegStream(stream, maybeLength, stream.dict, params);
- }
- if (name === 'JPXDecode' || name === 'JPX') {
- xrefStreamStats[StreamType.JPX] = true;
- return new JpxStream(stream, maybeLength, stream.dict, params);
- }
- if (name === 'ASCII85Decode' || name === 'A85') {
- xrefStreamStats[StreamType.A85] = true;
- return new Ascii85Stream(stream, maybeLength);
- }
- if (name === 'ASCIIHexDecode' || name === 'AHx') {
- xrefStreamStats[StreamType.AHX] = true;
- return new AsciiHexStream(stream, maybeLength);
- }
- if (name === 'CCITTFaxDecode' || name === 'CCF') {
- xrefStreamStats[StreamType.CCF] = true;
- return new CCITTFaxStream(stream, maybeLength, params);
- }
- if (name === 'RunLengthDecode' || name === 'RL') {
- xrefStreamStats[StreamType.RL] = true;
- return new RunLengthStream(stream, maybeLength);
- }
- if (name === 'JBIG2Decode') {
- xrefStreamStats[StreamType.JBIG] = true;
- return new Jbig2Stream(stream, maybeLength, stream.dict, params);
- }
- warn('filter "' + name + '" not supported yet');
- return stream;
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- warn('Invalid stream: \"' + ex + '\"');
- return new NullStream(stream);
- }
- }
- };
- return Parser;
-}();
-var Lexer = function LexerClosure() {
- function Lexer(stream, knownCommands) {
- this.stream = stream;
- this.nextChar();
- this.strBuf = [];
- this.knownCommands = knownCommands;
- }
- var specialChars = [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];
- function toHexDigit(ch) {
- if (ch >= 0x30 && ch <= 0x39) {
- return ch & 0x0F;
- }
- if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
- return (ch & 0x0F) + 9;
- }
- return -1;
- }
- Lexer.prototype = {
- nextChar: function Lexer_nextChar() {
- return this.currentChar = this.stream.getByte();
- },
- peekChar: function Lexer_peekChar() {
- return this.stream.peekByte();
- },
- getNumber: function Lexer_getNumber() {
- var ch = this.currentChar;
- var eNotation = false;
- var divideBy = 0;
- var sign = 1;
- if (ch === 0x2D) {
- sign = -1;
- ch = this.nextChar();
- if (ch === 0x2D) {
- ch = this.nextChar();
- }
- } else if (ch === 0x2B) {
- ch = this.nextChar();
- }
- if (ch === 0x2E) {
- divideBy = 10;
- ch = this.nextChar();
- }
- if (ch < 0x30 || ch > 0x39) {
- error('Invalid number: ' + String.fromCharCode(ch));
- return 0;
- }
- var baseValue = ch - 0x30;
- var powerValue = 0;
- var powerValueSign = 1;
- while ((ch = this.nextChar()) >= 0) {
- if (0x30 <= ch && ch <= 0x39) {
- var currentDigit = ch - 0x30;
- if (eNotation) {
- powerValue = powerValue * 10 + currentDigit;
- } else {
- if (divideBy !== 0) {
- divideBy *= 10;
- }
- baseValue = baseValue * 10 + currentDigit;
- }
- } else if (ch === 0x2E) {
- if (divideBy === 0) {
- divideBy = 1;
- } else {
- break;
- }
- } else if (ch === 0x2D) {
- warn('Badly formatted number');
- } else if (ch === 0x45 || ch === 0x65) {
- ch = this.peekChar();
- if (ch === 0x2B || ch === 0x2D) {
- powerValueSign = ch === 0x2D ? -1 : 1;
- this.nextChar();
- } else if (ch < 0x30 || ch > 0x39) {
- break;
- }
- eNotation = true;
- } else {
- break;
- }
- }
- if (divideBy !== 0) {
- baseValue /= divideBy;
- }
- if (eNotation) {
- baseValue *= Math.pow(10, powerValueSign * powerValue);
- }
- return sign * baseValue;
- },
- getString: function Lexer_getString() {
- var numParen = 1;
- var done = false;
- var strBuf = this.strBuf;
- strBuf.length = 0;
- var ch = this.nextChar();
- while (true) {
- var charBuffered = false;
- switch (ch | 0) {
- case -1:
- warn('Unterminated string');
- done = true;
- break;
- case 0x28:
- ++numParen;
- strBuf.push('(');
- break;
- case 0x29:
- if (--numParen === 0) {
- this.nextChar();
- done = true;
- } else {
- strBuf.push(')');
- }
- break;
- case 0x5C:
- ch = this.nextChar();
- switch (ch) {
- case -1:
- warn('Unterminated string');
- done = true;
- break;
- case 0x6E:
- strBuf.push('\n');
- break;
- case 0x72:
- strBuf.push('\r');
- break;
- case 0x74:
- strBuf.push('\t');
- break;
- case 0x62:
- strBuf.push('\b');
- break;
- case 0x66:
- strBuf.push('\f');
- break;
- case 0x5C:
- case 0x28:
- case 0x29:
- strBuf.push(String.fromCharCode(ch));
- break;
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- var x = ch & 0x0F;
- ch = this.nextChar();
- charBuffered = true;
- if (ch >= 0x30 && ch <= 0x37) {
- x = (x << 3) + (ch & 0x0F);
- ch = this.nextChar();
- if (ch >= 0x30 && ch <= 0x37) {
- charBuffered = false;
- x = (x << 3) + (ch & 0x0F);
- }
- }
- strBuf.push(String.fromCharCode(x));
- break;
- case 0x0D:
- if (this.peekChar() === 0x0A) {
- this.nextChar();
- }
- break;
- case 0x0A:
- break;
- default:
- strBuf.push(String.fromCharCode(ch));
- break;
- }
- break;
- default:
- strBuf.push(String.fromCharCode(ch));
- break;
- }
- if (done) {
- break;
- }
- if (!charBuffered) {
- ch = this.nextChar();
- }
- }
- return strBuf.join('');
- },
- getName: function Lexer_getName() {
- var ch, previousCh;
- var strBuf = this.strBuf;
- strBuf.length = 0;
- while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
- if (ch === 0x23) {
- ch = this.nextChar();
- if (specialChars[ch]) {
- warn('Lexer_getName: ' + 'NUMBER SIGN (#) should be followed by a hexadecimal number.');
- strBuf.push('#');
- break;
- }
- var x = toHexDigit(ch);
- if (x !== -1) {
- previousCh = ch;
- ch = this.nextChar();
- var x2 = toHexDigit(ch);
- if (x2 === -1) {
- warn('Lexer_getName: Illegal digit (' + String.fromCharCode(ch) + ') in hexadecimal number.');
- strBuf.push('#', String.fromCharCode(previousCh));
- if (specialChars[ch]) {
- break;
- }
- strBuf.push(String.fromCharCode(ch));
- continue;
- }
- strBuf.push(String.fromCharCode(x << 4 | x2));
- } else {
- strBuf.push('#', String.fromCharCode(ch));
- }
- } else {
- strBuf.push(String.fromCharCode(ch));
- }
- }
- if (strBuf.length > 127) {
- warn('name token is longer than allowed by the spec: ' + strBuf.length);
- }
- return Name.get(strBuf.join(''));
- },
- getHexString: function Lexer_getHexString() {
- var strBuf = this.strBuf;
- strBuf.length = 0;
- var ch = this.currentChar;
- var isFirstHex = true;
- var firstDigit;
- var secondDigit;
- while (true) {
- if (ch < 0) {
- warn('Unterminated hex string');
- break;
- } else if (ch === 0x3E) {
- this.nextChar();
- break;
- } else if (specialChars[ch] === 1) {
- ch = this.nextChar();
- continue;
- } else {
- if (isFirstHex) {
- firstDigit = toHexDigit(ch);
- if (firstDigit === -1) {
- warn('Ignoring invalid character "' + ch + '" in hex string');
- ch = this.nextChar();
- continue;
- }
- } else {
- secondDigit = toHexDigit(ch);
- if (secondDigit === -1) {
- warn('Ignoring invalid character "' + ch + '" in hex string');
- ch = this.nextChar();
- continue;
- }
- strBuf.push(String.fromCharCode(firstDigit << 4 | secondDigit));
- }
- isFirstHex = !isFirstHex;
- ch = this.nextChar();
- }
- }
- return strBuf.join('');
- },
- getObj: function Lexer_getObj() {
- var comment = false;
- var ch = this.currentChar;
- while (true) {
- if (ch < 0) {
- return EOF;
- }
- if (comment) {
- if (ch === 0x0A || ch === 0x0D) {
- comment = false;
- }
- } else if (ch === 0x25) {
- comment = true;
- } else if (specialChars[ch] !== 1) {
- break;
- }
- ch = this.nextChar();
- }
- switch (ch | 0) {
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- case 0x38:
- case 0x39:
- case 0x2B:
- case 0x2D:
- case 0x2E:
- return this.getNumber();
- case 0x28:
- return this.getString();
- case 0x2F:
- return this.getName();
- case 0x5B:
- this.nextChar();
- return Cmd.get('[');
- case 0x5D:
- this.nextChar();
- return Cmd.get(']');
- case 0x3C:
- ch = this.nextChar();
- if (ch === 0x3C) {
- this.nextChar();
- return Cmd.get('<<');
- }
- return this.getHexString();
- case 0x3E:
- ch = this.nextChar();
- if (ch === 0x3E) {
- this.nextChar();
- return Cmd.get('>>');
- }
- return Cmd.get('>');
- case 0x7B:
- this.nextChar();
- return Cmd.get('{');
- case 0x7D:
- this.nextChar();
- return Cmd.get('}');
- case 0x29:
- this.nextChar();
- error('Illegal character: ' + ch);
- break;
- }
- var str = String.fromCharCode(ch);
- var knownCommands = this.knownCommands;
- var knownCommandFound = knownCommands && knownCommands[str] !== undefined;
- while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
- var possibleCommand = str + String.fromCharCode(ch);
- if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
- break;
- }
- if (str.length === 128) {
- error('Command token too long: ' + str.length);
- }
- str = possibleCommand;
- knownCommandFound = knownCommands && knownCommands[str] !== undefined;
- }
- if (str === 'true') {
- return true;
- }
- if (str === 'false') {
- return false;
- }
- if (str === 'null') {
- return null;
- }
- return Cmd.get(str);
- },
- skipToNextLine: function Lexer_skipToNextLine() {
- var ch = this.currentChar;
- while (ch >= 0) {
- if (ch === 0x0D) {
- ch = this.nextChar();
- if (ch === 0x0A) {
- this.nextChar();
- }
- break;
- } else if (ch === 0x0A) {
- this.nextChar();
- break;
- }
- ch = this.nextChar();
- }
- }
- };
- return Lexer;
-}();
-var Linearization = {
- create: function LinearizationCreate(stream) {
- function getInt(name, allowZeroValue) {
- var obj = linDict.get(name);
- if (isInt(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) {
- return obj;
- }
- throw new Error('The "' + name + '" parameter in the linearization ' + 'dictionary is invalid.');
- }
- function getHints() {
- var hints = linDict.get('H'),
- hintsLength,
- item;
- if (isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) {
- for (var index = 0; index < hintsLength; index++) {
- if (!(isInt(item = hints[index]) && item > 0)) {
- throw new Error('Hint (' + index + ') in the linearization dictionary is invalid.');
- }
- }
- return hints;
- }
- throw new Error('Hint array in the linearization dictionary is invalid.');
- }
- var parser = new Parser(new Lexer(stream), false, null);
- var obj1 = parser.getObj();
- var obj2 = parser.getObj();
- var obj3 = parser.getObj();
- var linDict = parser.getObj();
- var obj, length;
- if (!(isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') && isDict(linDict) && isNum(obj = linDict.get('Linearized')) && obj > 0)) {
- return null;
- } else if ((length = getInt('L')) !== stream.length) {
- throw new Error('The "L" parameter in the linearization dictionary ' + 'does not equal the stream length.');
- }
- return {
- length: length,
- hints: getHints(),
- objectNumberFirst: getInt('O'),
- endFirst: getInt('E'),
- numPages: getInt('N'),
- mainXRefEntriesOffset: getInt('T'),
- pageFirst: linDict.has('P') ? getInt('P', true) : 0
- };
- }
-};
-exports.Lexer = Lexer;
-exports.Linearization = Linearization;
-exports.Parser = Parser;
-
-/***/ }),
-/* 6 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var corePsParser = __w_pdfjs_require__(34);
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isBool = sharedUtil.isBool;
-var isDict = corePrimitives.isDict;
-var isStream = corePrimitives.isStream;
-var PostScriptLexer = corePsParser.PostScriptLexer;
-var PostScriptParser = corePsParser.PostScriptParser;
-var PDFFunction = function PDFFunctionClosure() {
- var CONSTRUCT_SAMPLED = 0;
- var CONSTRUCT_INTERPOLATED = 2;
- var CONSTRUCT_STICHED = 3;
- var CONSTRUCT_POSTSCRIPT = 4;
- return {
- getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) {
- var i, ii;
- var length = 1;
- for (i = 0, ii = size.length; i < ii; i++) {
- length *= size[i];
- }
- length *= outputSize;
- var array = new Array(length);
- var codeSize = 0;
- var codeBuf = 0;
- var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
- var strBytes = str.getBytes((length * bps + 7) / 8);
- var strIdx = 0;
- for (i = 0; i < length; i++) {
- while (codeSize < bps) {
- codeBuf <<= 8;
- codeBuf |= strBytes[strIdx++];
- codeSize += 8;
- }
- codeSize -= bps;
- array[i] = (codeBuf >> codeSize) * sampleMul;
- codeBuf &= (1 << codeSize) - 1;
- }
- return array;
- },
- getIR: function PDFFunction_getIR(xref, fn) {
- var dict = fn.dict;
- if (!dict) {
- dict = fn;
- }
- var types = [this.constructSampled, null, this.constructInterpolated, this.constructStiched, this.constructPostScript];
- var typeNum = dict.get('FunctionType');
- var typeFn = types[typeNum];
- if (!typeFn) {
- error('Unknown type of function');
- }
- return typeFn.call(this, fn, dict, xref);
- },
- fromIR: function PDFFunction_fromIR(IR) {
- var type = IR[0];
- switch (type) {
- case CONSTRUCT_SAMPLED:
- return this.constructSampledFromIR(IR);
- case CONSTRUCT_INTERPOLATED:
- return this.constructInterpolatedFromIR(IR);
- case CONSTRUCT_STICHED:
- return this.constructStichedFromIR(IR);
- default:
- return this.constructPostScriptFromIR(IR);
- }
- },
- parse: function PDFFunction_parse(xref, fn) {
- var IR = this.getIR(xref, fn);
- return this.fromIR(IR);
- },
- parseArray: function PDFFunction_parseArray(xref, fnObj) {
- if (!isArray(fnObj)) {
- return this.parse(xref, fnObj);
- }
- var fnArray = [];
- for (var j = 0, jj = fnObj.length; j < jj; j++) {
- var obj = xref.fetchIfRef(fnObj[j]);
- fnArray.push(PDFFunction.parse(xref, obj));
- }
- return function (src, srcOffset, dest, destOffset) {
- for (var i = 0, ii = fnArray.length; i < ii; i++) {
- fnArray[i](src, srcOffset, dest, destOffset + i);
- }
- };
- },
- constructSampled: function PDFFunction_constructSampled(str, dict) {
- function toMultiArray(arr) {
- var inputLength = arr.length;
- var out = [];
- var index = 0;
- for (var i = 0; i < inputLength; i += 2) {
- out[index] = [arr[i], arr[i + 1]];
- ++index;
- }
- return out;
- }
- var domain = dict.getArray('Domain');
- var range = dict.getArray('Range');
- if (!domain || !range) {
- error('No domain or range');
- }
- var inputSize = domain.length / 2;
- var outputSize = range.length / 2;
- domain = toMultiArray(domain);
- range = toMultiArray(range);
- var size = dict.get('Size');
- var bps = dict.get('BitsPerSample');
- var order = dict.get('Order') || 1;
- if (order !== 1) {
- info('No support for cubic spline interpolation: ' + order);
- }
- var encode = dict.getArray('Encode');
- if (!encode) {
- encode = [];
- for (var i = 0; i < inputSize; ++i) {
- encode.push(0);
- encode.push(size[i] - 1);
- }
- }
- encode = toMultiArray(encode);
- var decode = dict.getArray('Decode');
- if (!decode) {
- decode = range;
- } else {
- decode = toMultiArray(decode);
- }
- var samples = this.getSampleArray(size, outputSize, bps, str);
- return [CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, outputSize, Math.pow(2, bps) - 1, range];
- },
- constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) {
- function interpolate(x, xmin, xmax, ymin, ymax) {
- return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin));
- }
- return function constructSampledFromIRResult(src, srcOffset, dest, destOffset) {
- var m = IR[1];
- var domain = IR[2];
- var encode = IR[3];
- var decode = IR[4];
- var samples = IR[5];
- var size = IR[6];
- var n = IR[7];
- var range = IR[9];
- var cubeVertices = 1 << m;
- var cubeN = new Float64Array(cubeVertices);
- var cubeVertex = new Uint32Array(cubeVertices);
- var i, j;
- for (j = 0; j < cubeVertices; j++) {
- cubeN[j] = 1;
- }
- var k = n,
- pos = 1;
- for (i = 0; i < m; ++i) {
- var domain_2i = domain[i][0];
- var domain_2i_1 = domain[i][1];
- var xi = Math.min(Math.max(src[srcOffset + i], domain_2i), domain_2i_1);
- var e = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]);
- var size_i = size[i];
- e = Math.min(Math.max(e, 0), size_i - 1);
- var e0 = e < size_i - 1 ? Math.floor(e) : e - 1;
- var n0 = e0 + 1 - e;
- var n1 = e - e0;
- var offset0 = e0 * k;
- var offset1 = offset0 + k;
- for (j = 0; j < cubeVertices; j++) {
- if (j & pos) {
- cubeN[j] *= n1;
- cubeVertex[j] += offset1;
- } else {
- cubeN[j] *= n0;
- cubeVertex[j] += offset0;
- }
- }
- k *= size_i;
- pos <<= 1;
- }
- for (j = 0; j < n; ++j) {
- var rj = 0;
- for (i = 0; i < cubeVertices; i++) {
- rj += samples[cubeVertex[i] + j] * cubeN[i];
- }
- rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
- dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
- }
- };
- },
- constructInterpolated: function PDFFunction_constructInterpolated(str, dict) {
- var c0 = dict.getArray('C0') || [0];
- var c1 = dict.getArray('C1') || [1];
- var n = dict.get('N');
- if (!isArray(c0) || !isArray(c1)) {
- error('Illegal dictionary for interpolated function');
- }
- var length = c0.length;
- var diff = [];
- for (var i = 0; i < length; ++i) {
- diff.push(c1[i] - c0[i]);
- }
- return [CONSTRUCT_INTERPOLATED, c0, diff, n];
- },
- constructInterpolatedFromIR: function PDFFunction_constructInterpolatedFromIR(IR) {
- var c0 = IR[1];
- var diff = IR[2];
- var n = IR[3];
- var length = diff.length;
- return function constructInterpolatedFromIRResult(src, srcOffset, dest, destOffset) {
- var x = n === 1 ? src[srcOffset] : Math.pow(src[srcOffset], n);
- for (var j = 0; j < length; ++j) {
- dest[destOffset + j] = c0[j] + x * diff[j];
- }
- };
- },
- constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
- var domain = dict.getArray('Domain');
- if (!domain) {
- error('No domain');
- }
- var inputSize = domain.length / 2;
- if (inputSize !== 1) {
- error('Bad domain for stiched function');
- }
- var fnRefs = dict.get('Functions');
- var fns = [];
- for (var i = 0, ii = fnRefs.length; i < ii; ++i) {
- fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
- }
- var bounds = dict.getArray('Bounds');
- var encode = dict.getArray('Encode');
- return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
- },
- constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
- var domain = IR[1];
- var bounds = IR[2];
- var encode = IR[3];
- var fnsIR = IR[4];
- var fns = [];
- var tmpBuf = new Float32Array(1);
- for (var i = 0, ii = fnsIR.length; i < ii; i++) {
- fns.push(PDFFunction.fromIR(fnsIR[i]));
- }
- return function constructStichedFromIRResult(src, srcOffset, dest, destOffset) {
- var clip = function constructStichedFromIRClip(v, min, max) {
- if (v > max) {
- v = max;
- } else if (v < min) {
- v = min;
- }
- return v;
- };
- var v = clip(src[srcOffset], domain[0], domain[1]);
- for (var i = 0, ii = bounds.length; i < ii; ++i) {
- if (v < bounds[i]) {
- break;
- }
- }
- var dmin = domain[0];
- if (i > 0) {
- dmin = bounds[i - 1];
- }
- var dmax = domain[1];
- if (i < bounds.length) {
- dmax = bounds[i];
- }
- var rmin = encode[2 * i];
- var rmax = encode[2 * i + 1];
- tmpBuf[0] = dmin === dmax ? rmin : rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
- fns[i](tmpBuf, 0, dest, destOffset);
- };
- },
- constructPostScript: function PDFFunction_constructPostScript(fn, dict, xref) {
- var domain = dict.getArray('Domain');
- var range = dict.getArray('Range');
- if (!domain) {
- error('No domain.');
- }
- if (!range) {
- error('No range.');
- }
- var lexer = new PostScriptLexer(fn);
- var parser = new PostScriptParser(lexer);
- var code = parser.parse();
- return [CONSTRUCT_POSTSCRIPT, domain, range, code];
- },
- constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR(IR) {
- var domain = IR[1];
- var range = IR[2];
- var code = IR[3];
- var compiled = new PostScriptCompiler().compile(code, domain, range);
- if (compiled) {
- return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
- }
- info('Unable to compile PS function');
- var numOutputs = range.length >> 1;
- var numInputs = domain.length >> 1;
- var evaluator = new PostScriptEvaluator(code);
- var cache = Object.create(null);
- var MAX_CACHE_SIZE = 2048 * 4;
- var cache_available = MAX_CACHE_SIZE;
- var tmpBuf = new Float32Array(numInputs);
- return function constructPostScriptFromIRResult(src, srcOffset, dest, destOffset) {
- var i, value;
- var key = '';
- var input = tmpBuf;
- for (i = 0; i < numInputs; i++) {
- value = src[srcOffset + i];
- input[i] = value;
- key += value + '_';
- }
- var cachedValue = cache[key];
- if (cachedValue !== undefined) {
- dest.set(cachedValue, destOffset);
- return;
- }
- var output = new Float32Array(numOutputs);
- var stack = evaluator.execute(input);
- var stackIndex = stack.length - numOutputs;
- for (i = 0; i < numOutputs; i++) {
- value = stack[stackIndex + i];
- var bound = range[i * 2];
- if (value < bound) {
- value = bound;
- } else {
- bound = range[i * 2 + 1];
- if (value > bound) {
- value = bound;
- }
- }
- output[i] = value;
- }
- if (cache_available > 0) {
- cache_available--;
- cache[key] = output;
- }
- dest.set(output, destOffset);
- };
- }
- };
-}();
-function isPDFFunction(v) {
- var fnDict;
- if (typeof v !== 'object') {
- return false;
- } else if (isDict(v)) {
- fnDict = v;
- } else if (isStream(v)) {
- fnDict = v.dict;
- } else {
- return false;
- }
- return fnDict.has('FunctionType');
-}
-var PostScriptStack = function PostScriptStackClosure() {
- var MAX_STACK_SIZE = 100;
- function PostScriptStack(initialStack) {
- this.stack = !initialStack ? [] : Array.prototype.slice.call(initialStack, 0);
- }
- PostScriptStack.prototype = {
- push: function PostScriptStack_push(value) {
- if (this.stack.length >= MAX_STACK_SIZE) {
- error('PostScript function stack overflow.');
- }
- this.stack.push(value);
- },
- pop: function PostScriptStack_pop() {
- if (this.stack.length <= 0) {
- error('PostScript function stack underflow.');
- }
- return this.stack.pop();
- },
- copy: function PostScriptStack_copy(n) {
- if (this.stack.length + n >= MAX_STACK_SIZE) {
- error('PostScript function stack overflow.');
- }
- var stack = this.stack;
- for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
- stack.push(stack[i]);
- }
- },
- index: function PostScriptStack_index(n) {
- this.push(this.stack[this.stack.length - n - 1]);
- },
- roll: function PostScriptStack_roll(n, p) {
- var stack = this.stack;
- var l = stack.length - n;
- var r = stack.length - 1,
- c = l + (p - Math.floor(p / n) * n),
- i,
- j,
- t;
- for (i = l, j = r; i < j; i++, j--) {
- t = stack[i];
- stack[i] = stack[j];
- stack[j] = t;
- }
- for (i = l, j = c - 1; i < j; i++, j--) {
- t = stack[i];
- stack[i] = stack[j];
- stack[j] = t;
- }
- for (i = c, j = r; i < j; i++, j--) {
- t = stack[i];
- stack[i] = stack[j];
- stack[j] = t;
- }
- }
- };
- return PostScriptStack;
-}();
-var PostScriptEvaluator = function PostScriptEvaluatorClosure() {
- function PostScriptEvaluator(operators) {
- this.operators = operators;
- }
- PostScriptEvaluator.prototype = {
- execute: function PostScriptEvaluator_execute(initialStack) {
- var stack = new PostScriptStack(initialStack);
- var counter = 0;
- var operators = this.operators;
- var length = operators.length;
- var operator, a, b;
- while (counter < length) {
- operator = operators[counter++];
- if (typeof operator === 'number') {
- stack.push(operator);
- continue;
- }
- switch (operator) {
- case 'jz':
- b = stack.pop();
- a = stack.pop();
- if (!a) {
- counter = b;
- }
- break;
- case 'j':
- a = stack.pop();
- counter = a;
- break;
- case 'abs':
- a = stack.pop();
- stack.push(Math.abs(a));
- break;
- case 'add':
- b = stack.pop();
- a = stack.pop();
- stack.push(a + b);
- break;
- case 'and':
- b = stack.pop();
- a = stack.pop();
- if (isBool(a) && isBool(b)) {
- stack.push(a && b);
- } else {
- stack.push(a & b);
- }
- break;
- case 'atan':
- a = stack.pop();
- stack.push(Math.atan(a));
- break;
- case 'bitshift':
- b = stack.pop();
- a = stack.pop();
- if (a > 0) {
- stack.push(a << b);
- } else {
- stack.push(a >> b);
- }
- break;
- case 'ceiling':
- a = stack.pop();
- stack.push(Math.ceil(a));
- break;
- case 'copy':
- a = stack.pop();
- stack.copy(a);
- break;
- case 'cos':
- a = stack.pop();
- stack.push(Math.cos(a));
- break;
- case 'cvi':
- a = stack.pop() | 0;
- stack.push(a);
- break;
- case 'cvr':
- break;
- case 'div':
- b = stack.pop();
- a = stack.pop();
- stack.push(a / b);
- break;
- case 'dup':
- stack.copy(1);
- break;
- case 'eq':
- b = stack.pop();
- a = stack.pop();
- stack.push(a === b);
- break;
- case 'exch':
- stack.roll(2, 1);
- break;
- case 'exp':
- b = stack.pop();
- a = stack.pop();
- stack.push(Math.pow(a, b));
- break;
- case 'false':
- stack.push(false);
- break;
- case 'floor':
- a = stack.pop();
- stack.push(Math.floor(a));
- break;
- case 'ge':
- b = stack.pop();
- a = stack.pop();
- stack.push(a >= b);
- break;
- case 'gt':
- b = stack.pop();
- a = stack.pop();
- stack.push(a > b);
- break;
- case 'idiv':
- b = stack.pop();
- a = stack.pop();
- stack.push(a / b | 0);
- break;
- case 'index':
- a = stack.pop();
- stack.index(a);
- break;
- case 'le':
- b = stack.pop();
- a = stack.pop();
- stack.push(a <= b);
- break;
- case 'ln':
- a = stack.pop();
- stack.push(Math.log(a));
- break;
- case 'log':
- a = stack.pop();
- stack.push(Math.log(a) / Math.LN10);
- break;
- case 'lt':
- b = stack.pop();
- a = stack.pop();
- stack.push(a < b);
- break;
- case 'mod':
- b = stack.pop();
- a = stack.pop();
- stack.push(a % b);
- break;
- case 'mul':
- b = stack.pop();
- a = stack.pop();
- stack.push(a * b);
- break;
- case 'ne':
- b = stack.pop();
- a = stack.pop();
- stack.push(a !== b);
- break;
- case 'neg':
- a = stack.pop();
- stack.push(-a);
- break;
- case 'not':
- a = stack.pop();
- if (isBool(a)) {
- stack.push(!a);
- } else {
- stack.push(~a);
- }
- break;
- case 'or':
- b = stack.pop();
- a = stack.pop();
- if (isBool(a) && isBool(b)) {
- stack.push(a || b);
- } else {
- stack.push(a | b);
- }
- break;
- case 'pop':
- stack.pop();
- break;
- case 'roll':
- b = stack.pop();
- a = stack.pop();
- stack.roll(a, b);
- break;
- case 'round':
- a = stack.pop();
- stack.push(Math.round(a));
- break;
- case 'sin':
- a = stack.pop();
- stack.push(Math.sin(a));
- break;
- case 'sqrt':
- a = stack.pop();
- stack.push(Math.sqrt(a));
- break;
- case 'sub':
- b = stack.pop();
- a = stack.pop();
- stack.push(a - b);
- break;
- case 'true':
- stack.push(true);
- break;
- case 'truncate':
- a = stack.pop();
- a = a < 0 ? Math.ceil(a) : Math.floor(a);
- stack.push(a);
- break;
- case 'xor':
- b = stack.pop();
- a = stack.pop();
- if (isBool(a) && isBool(b)) {
- stack.push(a !== b);
- } else {
- stack.push(a ^ b);
- }
- break;
- default:
- error('Unknown operator ' + operator);
- break;
- }
- }
- return stack.stack;
- }
- };
- return PostScriptEvaluator;
-}();
-var PostScriptCompiler = function PostScriptCompilerClosure() {
- function AstNode(type) {
- this.type = type;
- }
- AstNode.prototype.visit = function (visitor) {
- throw new Error('abstract method');
- };
- function AstArgument(index, min, max) {
- AstNode.call(this, 'args');
- this.index = index;
- this.min = min;
- this.max = max;
- }
- AstArgument.prototype = Object.create(AstNode.prototype);
- AstArgument.prototype.visit = function (visitor) {
- visitor.visitArgument(this);
- };
- function AstLiteral(number) {
- AstNode.call(this, 'literal');
- this.number = number;
- this.min = number;
- this.max = number;
- }
- AstLiteral.prototype = Object.create(AstNode.prototype);
- AstLiteral.prototype.visit = function (visitor) {
- visitor.visitLiteral(this);
- };
- function AstBinaryOperation(op, arg1, arg2, min, max) {
- AstNode.call(this, 'binary');
- this.op = op;
- this.arg1 = arg1;
- this.arg2 = arg2;
- this.min = min;
- this.max = max;
- }
- AstBinaryOperation.prototype = Object.create(AstNode.prototype);
- AstBinaryOperation.prototype.visit = function (visitor) {
- visitor.visitBinaryOperation(this);
- };
- function AstMin(arg, max) {
- AstNode.call(this, 'max');
- this.arg = arg;
- this.min = arg.min;
- this.max = max;
- }
- AstMin.prototype = Object.create(AstNode.prototype);
- AstMin.prototype.visit = function (visitor) {
- visitor.visitMin(this);
- };
- function AstVariable(index, min, max) {
- AstNode.call(this, 'var');
- this.index = index;
- this.min = min;
- this.max = max;
- }
- AstVariable.prototype = Object.create(AstNode.prototype);
- AstVariable.prototype.visit = function (visitor) {
- visitor.visitVariable(this);
- };
- function AstVariableDefinition(variable, arg) {
- AstNode.call(this, 'definition');
- this.variable = variable;
- this.arg = arg;
- }
- AstVariableDefinition.prototype = Object.create(AstNode.prototype);
- AstVariableDefinition.prototype.visit = function (visitor) {
- visitor.visitVariableDefinition(this);
- };
- function ExpressionBuilderVisitor() {
- this.parts = [];
- }
- ExpressionBuilderVisitor.prototype = {
- visitArgument: function (arg) {
- this.parts.push('Math.max(', arg.min, ', Math.min(', arg.max, ', src[srcOffset + ', arg.index, ']))');
- },
- visitVariable: function (variable) {
- this.parts.push('v', variable.index);
- },
- visitLiteral: function (literal) {
- this.parts.push(literal.number);
- },
- visitBinaryOperation: function (operation) {
- this.parts.push('(');
- operation.arg1.visit(this);
- this.parts.push(' ', operation.op, ' ');
- operation.arg2.visit(this);
- this.parts.push(')');
- },
- visitVariableDefinition: function (definition) {
- this.parts.push('var ');
- definition.variable.visit(this);
- this.parts.push(' = ');
- definition.arg.visit(this);
- this.parts.push(';');
- },
- visitMin: function (max) {
- this.parts.push('Math.min(');
- max.arg.visit(this);
- this.parts.push(', ', max.max, ')');
- },
- toString: function () {
- return this.parts.join('');
- }
- };
- function buildAddOperation(num1, num2) {
- if (num2.type === 'literal' && num2.number === 0) {
- return num1;
- }
- if (num1.type === 'literal' && num1.number === 0) {
- return num2;
- }
- if (num2.type === 'literal' && num1.type === 'literal') {
- return new AstLiteral(num1.number + num2.number);
- }
- return new AstBinaryOperation('+', num1, num2, num1.min + num2.min, num1.max + num2.max);
- }
- function buildMulOperation(num1, num2) {
- if (num2.type === 'literal') {
- if (num2.number === 0) {
- return new AstLiteral(0);
- } else if (num2.number === 1) {
- return num1;
- } else if (num1.type === 'literal') {
- return new AstLiteral(num1.number * num2.number);
- }
- }
- if (num1.type === 'literal') {
- if (num1.number === 0) {
- return new AstLiteral(0);
- } else if (num1.number === 1) {
- return num2;
- }
- }
- var min = Math.min(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
- var max = Math.max(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
- return new AstBinaryOperation('*', num1, num2, min, max);
- }
- function buildSubOperation(num1, num2) {
- if (num2.type === 'literal') {
- if (num2.number === 0) {
- return num1;
- } else if (num1.type === 'literal') {
- return new AstLiteral(num1.number - num2.number);
- }
- }
- if (num2.type === 'binary' && num2.op === '-' && num1.type === 'literal' && num1.number === 1 && num2.arg1.type === 'literal' && num2.arg1.number === 1) {
- return num2.arg2;
- }
- return new AstBinaryOperation('-', num1, num2, num1.min - num2.max, num1.max - num2.min);
- }
- function buildMinOperation(num1, max) {
- if (num1.min >= max) {
- return new AstLiteral(max);
- } else if (num1.max <= max) {
- return num1;
- }
- return new AstMin(num1, max);
- }
- function PostScriptCompiler() {}
- PostScriptCompiler.prototype = {
- compile: function PostScriptCompiler_compile(code, domain, range) {
- var stack = [];
- var i, ii;
- var instructions = [];
- var inputSize = domain.length >> 1,
- outputSize = range.length >> 1;
- var lastRegister = 0;
- var n, j;
- var num1, num2, ast1, ast2, tmpVar, item;
- for (i = 0; i < inputSize; i++) {
- stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1]));
- }
- for (i = 0, ii = code.length; i < ii; i++) {
- item = code[i];
- if (typeof item === 'number') {
- stack.push(new AstLiteral(item));
- continue;
- }
- switch (item) {
- case 'add':
- if (stack.length < 2) {
- return null;
- }
- num2 = stack.pop();
- num1 = stack.pop();
- stack.push(buildAddOperation(num1, num2));
- break;
- case 'cvr':
- if (stack.length < 1) {
- return null;
- }
- break;
- case 'mul':
- if (stack.length < 2) {
- return null;
- }
- num2 = stack.pop();
- num1 = stack.pop();
- stack.push(buildMulOperation(num1, num2));
- break;
- case 'sub':
- if (stack.length < 2) {
- return null;
- }
- num2 = stack.pop();
- num1 = stack.pop();
- stack.push(buildSubOperation(num1, num2));
- break;
- case 'exch':
- if (stack.length < 2) {
- return null;
- }
- ast1 = stack.pop();
- ast2 = stack.pop();
- stack.push(ast1, ast2);
- break;
- case 'pop':
- if (stack.length < 1) {
- return null;
- }
- stack.pop();
- break;
- case 'index':
- if (stack.length < 1) {
- return null;
- }
- num1 = stack.pop();
- if (num1.type !== 'literal') {
- return null;
- }
- n = num1.number;
- if (n < 0 || (n | 0) !== n || stack.length < n) {
- return null;
- }
- ast1 = stack[stack.length - n - 1];
- if (ast1.type === 'literal' || ast1.type === 'var') {
- stack.push(ast1);
- break;
- }
- tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
- stack[stack.length - n - 1] = tmpVar;
- stack.push(tmpVar);
- instructions.push(new AstVariableDefinition(tmpVar, ast1));
- break;
- case 'dup':
- if (stack.length < 1) {
- return null;
- }
- if (typeof code[i + 1] === 'number' && code[i + 2] === 'gt' && code[i + 3] === i + 7 && code[i + 4] === 'jz' && code[i + 5] === 'pop' && code[i + 6] === code[i + 1]) {
- num1 = stack.pop();
- stack.push(buildMinOperation(num1, code[i + 1]));
- i += 6;
- break;
- }
- ast1 = stack[stack.length - 1];
- if (ast1.type === 'literal' || ast1.type === 'var') {
- stack.push(ast1);
- break;
- }
- tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
- stack[stack.length - 1] = tmpVar;
- stack.push(tmpVar);
- instructions.push(new AstVariableDefinition(tmpVar, ast1));
- break;
- case 'roll':
- if (stack.length < 2) {
- return null;
- }
- num2 = stack.pop();
- num1 = stack.pop();
- if (num2.type !== 'literal' || num1.type !== 'literal') {
- return null;
- }
- j = num2.number;
- n = num1.number;
- if (n <= 0 || (n | 0) !== n || (j | 0) !== j || stack.length < n) {
- return null;
- }
- j = (j % n + n) % n;
- if (j === 0) {
- break;
- }
- Array.prototype.push.apply(stack, stack.splice(stack.length - n, n - j));
- break;
- default:
- return null;
- }
- }
- if (stack.length !== outputSize) {
- return null;
- }
- var result = [];
- instructions.forEach(function (instruction) {
- var statementBuilder = new ExpressionBuilderVisitor();
- instruction.visit(statementBuilder);
- result.push(statementBuilder.toString());
- });
- stack.forEach(function (expr, i) {
- var statementBuilder = new ExpressionBuilderVisitor();
- expr.visit(statementBuilder);
- var min = range[i * 2],
- max = range[i * 2 + 1];
- var out = [statementBuilder.toString()];
- if (min > expr.min) {
- out.unshift('Math.max(', min, ', ');
- out.push(')');
- }
- if (max < expr.max) {
- out.unshift('Math.min(', max, ', ');
- out.push(')');
- }
- out.unshift('dest[destOffset + ', i, '] = ');
- out.push(';');
- result.push(out.join(''));
- });
- return result.join('\n');
- }
- };
- return PostScriptCompiler;
-}();
-exports.isPDFFunction = isPDFFunction;
-exports.PDFFunction = PDFFunction;
-exports.PostScriptEvaluator = PostScriptEvaluator;
-exports.PostScriptCompiler = PostScriptCompiler;
-
-/***/ }),
-/* 7 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var getLookupTableFactory = sharedUtil.getLookupTableFactory;
-var getGlyphsUnicode = getLookupTableFactory(function (t) {
- t['A'] = 0x0041;
- t['AE'] = 0x00C6;
- t['AEacute'] = 0x01FC;
- t['AEmacron'] = 0x01E2;
- t['AEsmall'] = 0xF7E6;
- t['Aacute'] = 0x00C1;
- t['Aacutesmall'] = 0xF7E1;
- t['Abreve'] = 0x0102;
- t['Abreveacute'] = 0x1EAE;
- t['Abrevecyrillic'] = 0x04D0;
- t['Abrevedotbelow'] = 0x1EB6;
- t['Abrevegrave'] = 0x1EB0;
- t['Abrevehookabove'] = 0x1EB2;
- t['Abrevetilde'] = 0x1EB4;
- t['Acaron'] = 0x01CD;
- t['Acircle'] = 0x24B6;
- t['Acircumflex'] = 0x00C2;
- t['Acircumflexacute'] = 0x1EA4;
- t['Acircumflexdotbelow'] = 0x1EAC;
- t['Acircumflexgrave'] = 0x1EA6;
- t['Acircumflexhookabove'] = 0x1EA8;
- t['Acircumflexsmall'] = 0xF7E2;
- t['Acircumflextilde'] = 0x1EAA;
- t['Acute'] = 0xF6C9;
- t['Acutesmall'] = 0xF7B4;
- t['Acyrillic'] = 0x0410;
- t['Adblgrave'] = 0x0200;
- t['Adieresis'] = 0x00C4;
- t['Adieresiscyrillic'] = 0x04D2;
- t['Adieresismacron'] = 0x01DE;
- t['Adieresissmall'] = 0xF7E4;
- t['Adotbelow'] = 0x1EA0;
- t['Adotmacron'] = 0x01E0;
- t['Agrave'] = 0x00C0;
- t['Agravesmall'] = 0xF7E0;
- t['Ahookabove'] = 0x1EA2;
- t['Aiecyrillic'] = 0x04D4;
- t['Ainvertedbreve'] = 0x0202;
- t['Alpha'] = 0x0391;
- t['Alphatonos'] = 0x0386;
- t['Amacron'] = 0x0100;
- t['Amonospace'] = 0xFF21;
- t['Aogonek'] = 0x0104;
- t['Aring'] = 0x00C5;
- t['Aringacute'] = 0x01FA;
- t['Aringbelow'] = 0x1E00;
- t['Aringsmall'] = 0xF7E5;
- t['Asmall'] = 0xF761;
- t['Atilde'] = 0x00C3;
- t['Atildesmall'] = 0xF7E3;
- t['Aybarmenian'] = 0x0531;
- t['B'] = 0x0042;
- t['Bcircle'] = 0x24B7;
- t['Bdotaccent'] = 0x1E02;
- t['Bdotbelow'] = 0x1E04;
- t['Becyrillic'] = 0x0411;
- t['Benarmenian'] = 0x0532;
- t['Beta'] = 0x0392;
- t['Bhook'] = 0x0181;
- t['Blinebelow'] = 0x1E06;
- t['Bmonospace'] = 0xFF22;
- t['Brevesmall'] = 0xF6F4;
- t['Bsmall'] = 0xF762;
- t['Btopbar'] = 0x0182;
- t['C'] = 0x0043;
- t['Caarmenian'] = 0x053E;
- t['Cacute'] = 0x0106;
- t['Caron'] = 0xF6CA;
- t['Caronsmall'] = 0xF6F5;
- t['Ccaron'] = 0x010C;
- t['Ccedilla'] = 0x00C7;
- t['Ccedillaacute'] = 0x1E08;
- t['Ccedillasmall'] = 0xF7E7;
- t['Ccircle'] = 0x24B8;
- t['Ccircumflex'] = 0x0108;
- t['Cdot'] = 0x010A;
- t['Cdotaccent'] = 0x010A;
- t['Cedillasmall'] = 0xF7B8;
- t['Chaarmenian'] = 0x0549;
- t['Cheabkhasiancyrillic'] = 0x04BC;
- t['Checyrillic'] = 0x0427;
- t['Chedescenderabkhasiancyrillic'] = 0x04BE;
- t['Chedescendercyrillic'] = 0x04B6;
- t['Chedieresiscyrillic'] = 0x04F4;
- t['Cheharmenian'] = 0x0543;
- t['Chekhakassiancyrillic'] = 0x04CB;
- t['Cheverticalstrokecyrillic'] = 0x04B8;
- t['Chi'] = 0x03A7;
- t['Chook'] = 0x0187;
- t['Circumflexsmall'] = 0xF6F6;
- t['Cmonospace'] = 0xFF23;
- t['Coarmenian'] = 0x0551;
- t['Csmall'] = 0xF763;
- t['D'] = 0x0044;
- t['DZ'] = 0x01F1;
- t['DZcaron'] = 0x01C4;
- t['Daarmenian'] = 0x0534;
- t['Dafrican'] = 0x0189;
- t['Dcaron'] = 0x010E;
- t['Dcedilla'] = 0x1E10;
- t['Dcircle'] = 0x24B9;
- t['Dcircumflexbelow'] = 0x1E12;
- t['Dcroat'] = 0x0110;
- t['Ddotaccent'] = 0x1E0A;
- t['Ddotbelow'] = 0x1E0C;
- t['Decyrillic'] = 0x0414;
- t['Deicoptic'] = 0x03EE;
- t['Delta'] = 0x2206;
- t['Deltagreek'] = 0x0394;
- t['Dhook'] = 0x018A;
- t['Dieresis'] = 0xF6CB;
- t['DieresisAcute'] = 0xF6CC;
- t['DieresisGrave'] = 0xF6CD;
- t['Dieresissmall'] = 0xF7A8;
- t['Digammagreek'] = 0x03DC;
- t['Djecyrillic'] = 0x0402;
- t['Dlinebelow'] = 0x1E0E;
- t['Dmonospace'] = 0xFF24;
- t['Dotaccentsmall'] = 0xF6F7;
- t['Dslash'] = 0x0110;
- t['Dsmall'] = 0xF764;
- t['Dtopbar'] = 0x018B;
- t['Dz'] = 0x01F2;
- t['Dzcaron'] = 0x01C5;
- t['Dzeabkhasiancyrillic'] = 0x04E0;
- t['Dzecyrillic'] = 0x0405;
- t['Dzhecyrillic'] = 0x040F;
- t['E'] = 0x0045;
- t['Eacute'] = 0x00C9;
- t['Eacutesmall'] = 0xF7E9;
- t['Ebreve'] = 0x0114;
- t['Ecaron'] = 0x011A;
- t['Ecedillabreve'] = 0x1E1C;
- t['Echarmenian'] = 0x0535;
- t['Ecircle'] = 0x24BA;
- t['Ecircumflex'] = 0x00CA;
- t['Ecircumflexacute'] = 0x1EBE;
- t['Ecircumflexbelow'] = 0x1E18;
- t['Ecircumflexdotbelow'] = 0x1EC6;
- t['Ecircumflexgrave'] = 0x1EC0;
- t['Ecircumflexhookabove'] = 0x1EC2;
- t['Ecircumflexsmall'] = 0xF7EA;
- t['Ecircumflextilde'] = 0x1EC4;
- t['Ecyrillic'] = 0x0404;
- t['Edblgrave'] = 0x0204;
- t['Edieresis'] = 0x00CB;
- t['Edieresissmall'] = 0xF7EB;
- t['Edot'] = 0x0116;
- t['Edotaccent'] = 0x0116;
- t['Edotbelow'] = 0x1EB8;
- t['Efcyrillic'] = 0x0424;
- t['Egrave'] = 0x00C8;
- t['Egravesmall'] = 0xF7E8;
- t['Eharmenian'] = 0x0537;
- t['Ehookabove'] = 0x1EBA;
- t['Eightroman'] = 0x2167;
- t['Einvertedbreve'] = 0x0206;
- t['Eiotifiedcyrillic'] = 0x0464;
- t['Elcyrillic'] = 0x041B;
- t['Elevenroman'] = 0x216A;
- t['Emacron'] = 0x0112;
- t['Emacronacute'] = 0x1E16;
- t['Emacrongrave'] = 0x1E14;
- t['Emcyrillic'] = 0x041C;
- t['Emonospace'] = 0xFF25;
- t['Encyrillic'] = 0x041D;
- t['Endescendercyrillic'] = 0x04A2;
- t['Eng'] = 0x014A;
- t['Enghecyrillic'] = 0x04A4;
- t['Enhookcyrillic'] = 0x04C7;
- t['Eogonek'] = 0x0118;
- t['Eopen'] = 0x0190;
- t['Epsilon'] = 0x0395;
- t['Epsilontonos'] = 0x0388;
- t['Ercyrillic'] = 0x0420;
- t['Ereversed'] = 0x018E;
- t['Ereversedcyrillic'] = 0x042D;
- t['Escyrillic'] = 0x0421;
- t['Esdescendercyrillic'] = 0x04AA;
- t['Esh'] = 0x01A9;
- t['Esmall'] = 0xF765;
- t['Eta'] = 0x0397;
- t['Etarmenian'] = 0x0538;
- t['Etatonos'] = 0x0389;
- t['Eth'] = 0x00D0;
- t['Ethsmall'] = 0xF7F0;
- t['Etilde'] = 0x1EBC;
- t['Etildebelow'] = 0x1E1A;
- t['Euro'] = 0x20AC;
- t['Ezh'] = 0x01B7;
- t['Ezhcaron'] = 0x01EE;
- t['Ezhreversed'] = 0x01B8;
- t['F'] = 0x0046;
- t['Fcircle'] = 0x24BB;
- t['Fdotaccent'] = 0x1E1E;
- t['Feharmenian'] = 0x0556;
- t['Feicoptic'] = 0x03E4;
- t['Fhook'] = 0x0191;
- t['Fitacyrillic'] = 0x0472;
- t['Fiveroman'] = 0x2164;
- t['Fmonospace'] = 0xFF26;
- t['Fourroman'] = 0x2163;
- t['Fsmall'] = 0xF766;
- t['G'] = 0x0047;
- t['GBsquare'] = 0x3387;
- t['Gacute'] = 0x01F4;
- t['Gamma'] = 0x0393;
- t['Gammaafrican'] = 0x0194;
- t['Gangiacoptic'] = 0x03EA;
- t['Gbreve'] = 0x011E;
- t['Gcaron'] = 0x01E6;
- t['Gcedilla'] = 0x0122;
- t['Gcircle'] = 0x24BC;
- t['Gcircumflex'] = 0x011C;
- t['Gcommaaccent'] = 0x0122;
- t['Gdot'] = 0x0120;
- t['Gdotaccent'] = 0x0120;
- t['Gecyrillic'] = 0x0413;
- t['Ghadarmenian'] = 0x0542;
- t['Ghemiddlehookcyrillic'] = 0x0494;
- t['Ghestrokecyrillic'] = 0x0492;
- t['Gheupturncyrillic'] = 0x0490;
- t['Ghook'] = 0x0193;
- t['Gimarmenian'] = 0x0533;
- t['Gjecyrillic'] = 0x0403;
- t['Gmacron'] = 0x1E20;
- t['Gmonospace'] = 0xFF27;
- t['Grave'] = 0xF6CE;
- t['Gravesmall'] = 0xF760;
- t['Gsmall'] = 0xF767;
- t['Gsmallhook'] = 0x029B;
- t['Gstroke'] = 0x01E4;
- t['H'] = 0x0048;
- t['H18533'] = 0x25CF;
- t['H18543'] = 0x25AA;
- t['H18551'] = 0x25AB;
- t['H22073'] = 0x25A1;
- t['HPsquare'] = 0x33CB;
- t['Haabkhasiancyrillic'] = 0x04A8;
- t['Hadescendercyrillic'] = 0x04B2;
- t['Hardsigncyrillic'] = 0x042A;
- t['Hbar'] = 0x0126;
- t['Hbrevebelow'] = 0x1E2A;
- t['Hcedilla'] = 0x1E28;
- t['Hcircle'] = 0x24BD;
- t['Hcircumflex'] = 0x0124;
- t['Hdieresis'] = 0x1E26;
- t['Hdotaccent'] = 0x1E22;
- t['Hdotbelow'] = 0x1E24;
- t['Hmonospace'] = 0xFF28;
- t['Hoarmenian'] = 0x0540;
- t['Horicoptic'] = 0x03E8;
- t['Hsmall'] = 0xF768;
- t['Hungarumlaut'] = 0xF6CF;
- t['Hungarumlautsmall'] = 0xF6F8;
- t['Hzsquare'] = 0x3390;
- t['I'] = 0x0049;
- t['IAcyrillic'] = 0x042F;
- t['IJ'] = 0x0132;
- t['IUcyrillic'] = 0x042E;
- t['Iacute'] = 0x00CD;
- t['Iacutesmall'] = 0xF7ED;
- t['Ibreve'] = 0x012C;
- t['Icaron'] = 0x01CF;
- t['Icircle'] = 0x24BE;
- t['Icircumflex'] = 0x00CE;
- t['Icircumflexsmall'] = 0xF7EE;
- t['Icyrillic'] = 0x0406;
- t['Idblgrave'] = 0x0208;
- t['Idieresis'] = 0x00CF;
- t['Idieresisacute'] = 0x1E2E;
- t['Idieresiscyrillic'] = 0x04E4;
- t['Idieresissmall'] = 0xF7EF;
- t['Idot'] = 0x0130;
- t['Idotaccent'] = 0x0130;
- t['Idotbelow'] = 0x1ECA;
- t['Iebrevecyrillic'] = 0x04D6;
- t['Iecyrillic'] = 0x0415;
- t['Ifraktur'] = 0x2111;
- t['Igrave'] = 0x00CC;
- t['Igravesmall'] = 0xF7EC;
- t['Ihookabove'] = 0x1EC8;
- t['Iicyrillic'] = 0x0418;
- t['Iinvertedbreve'] = 0x020A;
- t['Iishortcyrillic'] = 0x0419;
- t['Imacron'] = 0x012A;
- t['Imacroncyrillic'] = 0x04E2;
- t['Imonospace'] = 0xFF29;
- t['Iniarmenian'] = 0x053B;
- t['Iocyrillic'] = 0x0401;
- t['Iogonek'] = 0x012E;
- t['Iota'] = 0x0399;
- t['Iotaafrican'] = 0x0196;
- t['Iotadieresis'] = 0x03AA;
- t['Iotatonos'] = 0x038A;
- t['Ismall'] = 0xF769;
- t['Istroke'] = 0x0197;
- t['Itilde'] = 0x0128;
- t['Itildebelow'] = 0x1E2C;
- t['Izhitsacyrillic'] = 0x0474;
- t['Izhitsadblgravecyrillic'] = 0x0476;
- t['J'] = 0x004A;
- t['Jaarmenian'] = 0x0541;
- t['Jcircle'] = 0x24BF;
- t['Jcircumflex'] = 0x0134;
- t['Jecyrillic'] = 0x0408;
- t['Jheharmenian'] = 0x054B;
- t['Jmonospace'] = 0xFF2A;
- t['Jsmall'] = 0xF76A;
- t['K'] = 0x004B;
- t['KBsquare'] = 0x3385;
- t['KKsquare'] = 0x33CD;
- t['Kabashkircyrillic'] = 0x04A0;
- t['Kacute'] = 0x1E30;
- t['Kacyrillic'] = 0x041A;
- t['Kadescendercyrillic'] = 0x049A;
- t['Kahookcyrillic'] = 0x04C3;
- t['Kappa'] = 0x039A;
- t['Kastrokecyrillic'] = 0x049E;
- t['Kaverticalstrokecyrillic'] = 0x049C;
- t['Kcaron'] = 0x01E8;
- t['Kcedilla'] = 0x0136;
- t['Kcircle'] = 0x24C0;
- t['Kcommaaccent'] = 0x0136;
- t['Kdotbelow'] = 0x1E32;
- t['Keharmenian'] = 0x0554;
- t['Kenarmenian'] = 0x053F;
- t['Khacyrillic'] = 0x0425;
- t['Kheicoptic'] = 0x03E6;
- t['Khook'] = 0x0198;
- t['Kjecyrillic'] = 0x040C;
- t['Klinebelow'] = 0x1E34;
- t['Kmonospace'] = 0xFF2B;
- t['Koppacyrillic'] = 0x0480;
- t['Koppagreek'] = 0x03DE;
- t['Ksicyrillic'] = 0x046E;
- t['Ksmall'] = 0xF76B;
- t['L'] = 0x004C;
- t['LJ'] = 0x01C7;
- t['LL'] = 0xF6BF;
- t['Lacute'] = 0x0139;
- t['Lambda'] = 0x039B;
- t['Lcaron'] = 0x013D;
- t['Lcedilla'] = 0x013B;
- t['Lcircle'] = 0x24C1;
- t['Lcircumflexbelow'] = 0x1E3C;
- t['Lcommaaccent'] = 0x013B;
- t['Ldot'] = 0x013F;
- t['Ldotaccent'] = 0x013F;
- t['Ldotbelow'] = 0x1E36;
- t['Ldotbelowmacron'] = 0x1E38;
- t['Liwnarmenian'] = 0x053C;
- t['Lj'] = 0x01C8;
- t['Ljecyrillic'] = 0x0409;
- t['Llinebelow'] = 0x1E3A;
- t['Lmonospace'] = 0xFF2C;
- t['Lslash'] = 0x0141;
- t['Lslashsmall'] = 0xF6F9;
- t['Lsmall'] = 0xF76C;
- t['M'] = 0x004D;
- t['MBsquare'] = 0x3386;
- t['Macron'] = 0xF6D0;
- t['Macronsmall'] = 0xF7AF;
- t['Macute'] = 0x1E3E;
- t['Mcircle'] = 0x24C2;
- t['Mdotaccent'] = 0x1E40;
- t['Mdotbelow'] = 0x1E42;
- t['Menarmenian'] = 0x0544;
- t['Mmonospace'] = 0xFF2D;
- t['Msmall'] = 0xF76D;
- t['Mturned'] = 0x019C;
- t['Mu'] = 0x039C;
- t['N'] = 0x004E;
- t['NJ'] = 0x01CA;
- t['Nacute'] = 0x0143;
- t['Ncaron'] = 0x0147;
- t['Ncedilla'] = 0x0145;
- t['Ncircle'] = 0x24C3;
- t['Ncircumflexbelow'] = 0x1E4A;
- t['Ncommaaccent'] = 0x0145;
- t['Ndotaccent'] = 0x1E44;
- t['Ndotbelow'] = 0x1E46;
- t['Nhookleft'] = 0x019D;
- t['Nineroman'] = 0x2168;
- t['Nj'] = 0x01CB;
- t['Njecyrillic'] = 0x040A;
- t['Nlinebelow'] = 0x1E48;
- t['Nmonospace'] = 0xFF2E;
- t['Nowarmenian'] = 0x0546;
- t['Nsmall'] = 0xF76E;
- t['Ntilde'] = 0x00D1;
- t['Ntildesmall'] = 0xF7F1;
- t['Nu'] = 0x039D;
- t['O'] = 0x004F;
- t['OE'] = 0x0152;
- t['OEsmall'] = 0xF6FA;
- t['Oacute'] = 0x00D3;
- t['Oacutesmall'] = 0xF7F3;
- t['Obarredcyrillic'] = 0x04E8;
- t['Obarreddieresiscyrillic'] = 0x04EA;
- t['Obreve'] = 0x014E;
- t['Ocaron'] = 0x01D1;
- t['Ocenteredtilde'] = 0x019F;
- t['Ocircle'] = 0x24C4;
- t['Ocircumflex'] = 0x00D4;
- t['Ocircumflexacute'] = 0x1ED0;
- t['Ocircumflexdotbelow'] = 0x1ED8;
- t['Ocircumflexgrave'] = 0x1ED2;
- t['Ocircumflexhookabove'] = 0x1ED4;
- t['Ocircumflexsmall'] = 0xF7F4;
- t['Ocircumflextilde'] = 0x1ED6;
- t['Ocyrillic'] = 0x041E;
- t['Odblacute'] = 0x0150;
- t['Odblgrave'] = 0x020C;
- t['Odieresis'] = 0x00D6;
- t['Odieresiscyrillic'] = 0x04E6;
- t['Odieresissmall'] = 0xF7F6;
- t['Odotbelow'] = 0x1ECC;
- t['Ogoneksmall'] = 0xF6FB;
- t['Ograve'] = 0x00D2;
- t['Ogravesmall'] = 0xF7F2;
- t['Oharmenian'] = 0x0555;
- t['Ohm'] = 0x2126;
- t['Ohookabove'] = 0x1ECE;
- t['Ohorn'] = 0x01A0;
- t['Ohornacute'] = 0x1EDA;
- t['Ohorndotbelow'] = 0x1EE2;
- t['Ohorngrave'] = 0x1EDC;
- t['Ohornhookabove'] = 0x1EDE;
- t['Ohorntilde'] = 0x1EE0;
- t['Ohungarumlaut'] = 0x0150;
- t['Oi'] = 0x01A2;
- t['Oinvertedbreve'] = 0x020E;
- t['Omacron'] = 0x014C;
- t['Omacronacute'] = 0x1E52;
- t['Omacrongrave'] = 0x1E50;
- t['Omega'] = 0x2126;
- t['Omegacyrillic'] = 0x0460;
- t['Omegagreek'] = 0x03A9;
- t['Omegaroundcyrillic'] = 0x047A;
- t['Omegatitlocyrillic'] = 0x047C;
- t['Omegatonos'] = 0x038F;
- t['Omicron'] = 0x039F;
- t['Omicrontonos'] = 0x038C;
- t['Omonospace'] = 0xFF2F;
- t['Oneroman'] = 0x2160;
- t['Oogonek'] = 0x01EA;
- t['Oogonekmacron'] = 0x01EC;
- t['Oopen'] = 0x0186;
- t['Oslash'] = 0x00D8;
- t['Oslashacute'] = 0x01FE;
- t['Oslashsmall'] = 0xF7F8;
- t['Osmall'] = 0xF76F;
- t['Ostrokeacute'] = 0x01FE;
- t['Otcyrillic'] = 0x047E;
- t['Otilde'] = 0x00D5;
- t['Otildeacute'] = 0x1E4C;
- t['Otildedieresis'] = 0x1E4E;
- t['Otildesmall'] = 0xF7F5;
- t['P'] = 0x0050;
- t['Pacute'] = 0x1E54;
- t['Pcircle'] = 0x24C5;
- t['Pdotaccent'] = 0x1E56;
- t['Pecyrillic'] = 0x041F;
- t['Peharmenian'] = 0x054A;
- t['Pemiddlehookcyrillic'] = 0x04A6;
- t['Phi'] = 0x03A6;
- t['Phook'] = 0x01A4;
- t['Pi'] = 0x03A0;
- t['Piwrarmenian'] = 0x0553;
- t['Pmonospace'] = 0xFF30;
- t['Psi'] = 0x03A8;
- t['Psicyrillic'] = 0x0470;
- t['Psmall'] = 0xF770;
- t['Q'] = 0x0051;
- t['Qcircle'] = 0x24C6;
- t['Qmonospace'] = 0xFF31;
- t['Qsmall'] = 0xF771;
- t['R'] = 0x0052;
- t['Raarmenian'] = 0x054C;
- t['Racute'] = 0x0154;
- t['Rcaron'] = 0x0158;
- t['Rcedilla'] = 0x0156;
- t['Rcircle'] = 0x24C7;
- t['Rcommaaccent'] = 0x0156;
- t['Rdblgrave'] = 0x0210;
- t['Rdotaccent'] = 0x1E58;
- t['Rdotbelow'] = 0x1E5A;
- t['Rdotbelowmacron'] = 0x1E5C;
- t['Reharmenian'] = 0x0550;
- t['Rfraktur'] = 0x211C;
- t['Rho'] = 0x03A1;
- t['Ringsmall'] = 0xF6FC;
- t['Rinvertedbreve'] = 0x0212;
- t['Rlinebelow'] = 0x1E5E;
- t['Rmonospace'] = 0xFF32;
- t['Rsmall'] = 0xF772;
- t['Rsmallinverted'] = 0x0281;
- t['Rsmallinvertedsuperior'] = 0x02B6;
- t['S'] = 0x0053;
- t['SF010000'] = 0x250C;
- t['SF020000'] = 0x2514;
- t['SF030000'] = 0x2510;
- t['SF040000'] = 0x2518;
- t['SF050000'] = 0x253C;
- t['SF060000'] = 0x252C;
- t['SF070000'] = 0x2534;
- t['SF080000'] = 0x251C;
- t['SF090000'] = 0x2524;
- t['SF100000'] = 0x2500;
- t['SF110000'] = 0x2502;
- t['SF190000'] = 0x2561;
- t['SF200000'] = 0x2562;
- t['SF210000'] = 0x2556;
- t['SF220000'] = 0x2555;
- t['SF230000'] = 0x2563;
- t['SF240000'] = 0x2551;
- t['SF250000'] = 0x2557;
- t['SF260000'] = 0x255D;
- t['SF270000'] = 0x255C;
- t['SF280000'] = 0x255B;
- t['SF360000'] = 0x255E;
- t['SF370000'] = 0x255F;
- t['SF380000'] = 0x255A;
- t['SF390000'] = 0x2554;
- t['SF400000'] = 0x2569;
- t['SF410000'] = 0x2566;
- t['SF420000'] = 0x2560;
- t['SF430000'] = 0x2550;
- t['SF440000'] = 0x256C;
- t['SF450000'] = 0x2567;
- t['SF460000'] = 0x2568;
- t['SF470000'] = 0x2564;
- t['SF480000'] = 0x2565;
- t['SF490000'] = 0x2559;
- t['SF500000'] = 0x2558;
- t['SF510000'] = 0x2552;
- t['SF520000'] = 0x2553;
- t['SF530000'] = 0x256B;
- t['SF540000'] = 0x256A;
- t['Sacute'] = 0x015A;
- t['Sacutedotaccent'] = 0x1E64;
- t['Sampigreek'] = 0x03E0;
- t['Scaron'] = 0x0160;
- t['Scarondotaccent'] = 0x1E66;
- t['Scaronsmall'] = 0xF6FD;
- t['Scedilla'] = 0x015E;
- t['Schwa'] = 0x018F;
- t['Schwacyrillic'] = 0x04D8;
- t['Schwadieresiscyrillic'] = 0x04DA;
- t['Scircle'] = 0x24C8;
- t['Scircumflex'] = 0x015C;
- t['Scommaaccent'] = 0x0218;
- t['Sdotaccent'] = 0x1E60;
- t['Sdotbelow'] = 0x1E62;
- t['Sdotbelowdotaccent'] = 0x1E68;
- t['Seharmenian'] = 0x054D;
- t['Sevenroman'] = 0x2166;
- t['Shaarmenian'] = 0x0547;
- t['Shacyrillic'] = 0x0428;
- t['Shchacyrillic'] = 0x0429;
- t['Sheicoptic'] = 0x03E2;
- t['Shhacyrillic'] = 0x04BA;
- t['Shimacoptic'] = 0x03EC;
- t['Sigma'] = 0x03A3;
- t['Sixroman'] = 0x2165;
- t['Smonospace'] = 0xFF33;
- t['Softsigncyrillic'] = 0x042C;
- t['Ssmall'] = 0xF773;
- t['Stigmagreek'] = 0x03DA;
- t['T'] = 0x0054;
- t['Tau'] = 0x03A4;
- t['Tbar'] = 0x0166;
- t['Tcaron'] = 0x0164;
- t['Tcedilla'] = 0x0162;
- t['Tcircle'] = 0x24C9;
- t['Tcircumflexbelow'] = 0x1E70;
- t['Tcommaaccent'] = 0x0162;
- t['Tdotaccent'] = 0x1E6A;
- t['Tdotbelow'] = 0x1E6C;
- t['Tecyrillic'] = 0x0422;
- t['Tedescendercyrillic'] = 0x04AC;
- t['Tenroman'] = 0x2169;
- t['Tetsecyrillic'] = 0x04B4;
- t['Theta'] = 0x0398;
- t['Thook'] = 0x01AC;
- t['Thorn'] = 0x00DE;
- t['Thornsmall'] = 0xF7FE;
- t['Threeroman'] = 0x2162;
- t['Tildesmall'] = 0xF6FE;
- t['Tiwnarmenian'] = 0x054F;
- t['Tlinebelow'] = 0x1E6E;
- t['Tmonospace'] = 0xFF34;
- t['Toarmenian'] = 0x0539;
- t['Tonefive'] = 0x01BC;
- t['Tonesix'] = 0x0184;
- t['Tonetwo'] = 0x01A7;
- t['Tretroflexhook'] = 0x01AE;
- t['Tsecyrillic'] = 0x0426;
- t['Tshecyrillic'] = 0x040B;
- t['Tsmall'] = 0xF774;
- t['Twelveroman'] = 0x216B;
- t['Tworoman'] = 0x2161;
- t['U'] = 0x0055;
- t['Uacute'] = 0x00DA;
- t['Uacutesmall'] = 0xF7FA;
- t['Ubreve'] = 0x016C;
- t['Ucaron'] = 0x01D3;
- t['Ucircle'] = 0x24CA;
- t['Ucircumflex'] = 0x00DB;
- t['Ucircumflexbelow'] = 0x1E76;
- t['Ucircumflexsmall'] = 0xF7FB;
- t['Ucyrillic'] = 0x0423;
- t['Udblacute'] = 0x0170;
- t['Udblgrave'] = 0x0214;
- t['Udieresis'] = 0x00DC;
- t['Udieresisacute'] = 0x01D7;
- t['Udieresisbelow'] = 0x1E72;
- t['Udieresiscaron'] = 0x01D9;
- t['Udieresiscyrillic'] = 0x04F0;
- t['Udieresisgrave'] = 0x01DB;
- t['Udieresismacron'] = 0x01D5;
- t['Udieresissmall'] = 0xF7FC;
- t['Udotbelow'] = 0x1EE4;
- t['Ugrave'] = 0x00D9;
- t['Ugravesmall'] = 0xF7F9;
- t['Uhookabove'] = 0x1EE6;
- t['Uhorn'] = 0x01AF;
- t['Uhornacute'] = 0x1EE8;
- t['Uhorndotbelow'] = 0x1EF0;
- t['Uhorngrave'] = 0x1EEA;
- t['Uhornhookabove'] = 0x1EEC;
- t['Uhorntilde'] = 0x1EEE;
- t['Uhungarumlaut'] = 0x0170;
- t['Uhungarumlautcyrillic'] = 0x04F2;
- t['Uinvertedbreve'] = 0x0216;
- t['Ukcyrillic'] = 0x0478;
- t['Umacron'] = 0x016A;
- t['Umacroncyrillic'] = 0x04EE;
- t['Umacrondieresis'] = 0x1E7A;
- t['Umonospace'] = 0xFF35;
- t['Uogonek'] = 0x0172;
- t['Upsilon'] = 0x03A5;
- t['Upsilon1'] = 0x03D2;
- t['Upsilonacutehooksymbolgreek'] = 0x03D3;
- t['Upsilonafrican'] = 0x01B1;
- t['Upsilondieresis'] = 0x03AB;
- t['Upsilondieresishooksymbolgreek'] = 0x03D4;
- t['Upsilonhooksymbol'] = 0x03D2;
- t['Upsilontonos'] = 0x038E;
- t['Uring'] = 0x016E;
- t['Ushortcyrillic'] = 0x040E;
- t['Usmall'] = 0xF775;
- t['Ustraightcyrillic'] = 0x04AE;
- t['Ustraightstrokecyrillic'] = 0x04B0;
- t['Utilde'] = 0x0168;
- t['Utildeacute'] = 0x1E78;
- t['Utildebelow'] = 0x1E74;
- t['V'] = 0x0056;
- t['Vcircle'] = 0x24CB;
- t['Vdotbelow'] = 0x1E7E;
- t['Vecyrillic'] = 0x0412;
- t['Vewarmenian'] = 0x054E;
- t['Vhook'] = 0x01B2;
- t['Vmonospace'] = 0xFF36;
- t['Voarmenian'] = 0x0548;
- t['Vsmall'] = 0xF776;
- t['Vtilde'] = 0x1E7C;
- t['W'] = 0x0057;
- t['Wacute'] = 0x1E82;
- t['Wcircle'] = 0x24CC;
- t['Wcircumflex'] = 0x0174;
- t['Wdieresis'] = 0x1E84;
- t['Wdotaccent'] = 0x1E86;
- t['Wdotbelow'] = 0x1E88;
- t['Wgrave'] = 0x1E80;
- t['Wmonospace'] = 0xFF37;
- t['Wsmall'] = 0xF777;
- t['X'] = 0x0058;
- t['Xcircle'] = 0x24CD;
- t['Xdieresis'] = 0x1E8C;
- t['Xdotaccent'] = 0x1E8A;
- t['Xeharmenian'] = 0x053D;
- t['Xi'] = 0x039E;
- t['Xmonospace'] = 0xFF38;
- t['Xsmall'] = 0xF778;
- t['Y'] = 0x0059;
- t['Yacute'] = 0x00DD;
- t['Yacutesmall'] = 0xF7FD;
- t['Yatcyrillic'] = 0x0462;
- t['Ycircle'] = 0x24CE;
- t['Ycircumflex'] = 0x0176;
- t['Ydieresis'] = 0x0178;
- t['Ydieresissmall'] = 0xF7FF;
- t['Ydotaccent'] = 0x1E8E;
- t['Ydotbelow'] = 0x1EF4;
- t['Yericyrillic'] = 0x042B;
- t['Yerudieresiscyrillic'] = 0x04F8;
- t['Ygrave'] = 0x1EF2;
- t['Yhook'] = 0x01B3;
- t['Yhookabove'] = 0x1EF6;
- t['Yiarmenian'] = 0x0545;
- t['Yicyrillic'] = 0x0407;
- t['Yiwnarmenian'] = 0x0552;
- t['Ymonospace'] = 0xFF39;
- t['Ysmall'] = 0xF779;
- t['Ytilde'] = 0x1EF8;
- t['Yusbigcyrillic'] = 0x046A;
- t['Yusbigiotifiedcyrillic'] = 0x046C;
- t['Yuslittlecyrillic'] = 0x0466;
- t['Yuslittleiotifiedcyrillic'] = 0x0468;
- t['Z'] = 0x005A;
- t['Zaarmenian'] = 0x0536;
- t['Zacute'] = 0x0179;
- t['Zcaron'] = 0x017D;
- t['Zcaronsmall'] = 0xF6FF;
- t['Zcircle'] = 0x24CF;
- t['Zcircumflex'] = 0x1E90;
- t['Zdot'] = 0x017B;
- t['Zdotaccent'] = 0x017B;
- t['Zdotbelow'] = 0x1E92;
- t['Zecyrillic'] = 0x0417;
- t['Zedescendercyrillic'] = 0x0498;
- t['Zedieresiscyrillic'] = 0x04DE;
- t['Zeta'] = 0x0396;
- t['Zhearmenian'] = 0x053A;
- t['Zhebrevecyrillic'] = 0x04C1;
- t['Zhecyrillic'] = 0x0416;
- t['Zhedescendercyrillic'] = 0x0496;
- t['Zhedieresiscyrillic'] = 0x04DC;
- t['Zlinebelow'] = 0x1E94;
- t['Zmonospace'] = 0xFF3A;
- t['Zsmall'] = 0xF77A;
- t['Zstroke'] = 0x01B5;
- t['a'] = 0x0061;
- t['aabengali'] = 0x0986;
- t['aacute'] = 0x00E1;
- t['aadeva'] = 0x0906;
- t['aagujarati'] = 0x0A86;
- t['aagurmukhi'] = 0x0A06;
- t['aamatragurmukhi'] = 0x0A3E;
- t['aarusquare'] = 0x3303;
- t['aavowelsignbengali'] = 0x09BE;
- t['aavowelsigndeva'] = 0x093E;
- t['aavowelsigngujarati'] = 0x0ABE;
- t['abbreviationmarkarmenian'] = 0x055F;
- t['abbreviationsigndeva'] = 0x0970;
- t['abengali'] = 0x0985;
- t['abopomofo'] = 0x311A;
- t['abreve'] = 0x0103;
- t['abreveacute'] = 0x1EAF;
- t['abrevecyrillic'] = 0x04D1;
- t['abrevedotbelow'] = 0x1EB7;
- t['abrevegrave'] = 0x1EB1;
- t['abrevehookabove'] = 0x1EB3;
- t['abrevetilde'] = 0x1EB5;
- t['acaron'] = 0x01CE;
- t['acircle'] = 0x24D0;
- t['acircumflex'] = 0x00E2;
- t['acircumflexacute'] = 0x1EA5;
- t['acircumflexdotbelow'] = 0x1EAD;
- t['acircumflexgrave'] = 0x1EA7;
- t['acircumflexhookabove'] = 0x1EA9;
- t['acircumflextilde'] = 0x1EAB;
- t['acute'] = 0x00B4;
- t['acutebelowcmb'] = 0x0317;
- t['acutecmb'] = 0x0301;
- t['acutecomb'] = 0x0301;
- t['acutedeva'] = 0x0954;
- t['acutelowmod'] = 0x02CF;
- t['acutetonecmb'] = 0x0341;
- t['acyrillic'] = 0x0430;
- t['adblgrave'] = 0x0201;
- t['addakgurmukhi'] = 0x0A71;
- t['adeva'] = 0x0905;
- t['adieresis'] = 0x00E4;
- t['adieresiscyrillic'] = 0x04D3;
- t['adieresismacron'] = 0x01DF;
- t['adotbelow'] = 0x1EA1;
- t['adotmacron'] = 0x01E1;
- t['ae'] = 0x00E6;
- t['aeacute'] = 0x01FD;
- t['aekorean'] = 0x3150;
- t['aemacron'] = 0x01E3;
- t['afii00208'] = 0x2015;
- t['afii08941'] = 0x20A4;
- t['afii10017'] = 0x0410;
- t['afii10018'] = 0x0411;
- t['afii10019'] = 0x0412;
- t['afii10020'] = 0x0413;
- t['afii10021'] = 0x0414;
- t['afii10022'] = 0x0415;
- t['afii10023'] = 0x0401;
- t['afii10024'] = 0x0416;
- t['afii10025'] = 0x0417;
- t['afii10026'] = 0x0418;
- t['afii10027'] = 0x0419;
- t['afii10028'] = 0x041A;
- t['afii10029'] = 0x041B;
- t['afii10030'] = 0x041C;
- t['afii10031'] = 0x041D;
- t['afii10032'] = 0x041E;
- t['afii10033'] = 0x041F;
- t['afii10034'] = 0x0420;
- t['afii10035'] = 0x0421;
- t['afii10036'] = 0x0422;
- t['afii10037'] = 0x0423;
- t['afii10038'] = 0x0424;
- t['afii10039'] = 0x0425;
- t['afii10040'] = 0x0426;
- t['afii10041'] = 0x0427;
- t['afii10042'] = 0x0428;
- t['afii10043'] = 0x0429;
- t['afii10044'] = 0x042A;
- t['afii10045'] = 0x042B;
- t['afii10046'] = 0x042C;
- t['afii10047'] = 0x042D;
- t['afii10048'] = 0x042E;
- t['afii10049'] = 0x042F;
- t['afii10050'] = 0x0490;
- t['afii10051'] = 0x0402;
- t['afii10052'] = 0x0403;
- t['afii10053'] = 0x0404;
- t['afii10054'] = 0x0405;
- t['afii10055'] = 0x0406;
- t['afii10056'] = 0x0407;
- t['afii10057'] = 0x0408;
- t['afii10058'] = 0x0409;
- t['afii10059'] = 0x040A;
- t['afii10060'] = 0x040B;
- t['afii10061'] = 0x040C;
- t['afii10062'] = 0x040E;
- t['afii10063'] = 0xF6C4;
- t['afii10064'] = 0xF6C5;
- t['afii10065'] = 0x0430;
- t['afii10066'] = 0x0431;
- t['afii10067'] = 0x0432;
- t['afii10068'] = 0x0433;
- t['afii10069'] = 0x0434;
- t['afii10070'] = 0x0435;
- t['afii10071'] = 0x0451;
- t['afii10072'] = 0x0436;
- t['afii10073'] = 0x0437;
- t['afii10074'] = 0x0438;
- t['afii10075'] = 0x0439;
- t['afii10076'] = 0x043A;
- t['afii10077'] = 0x043B;
- t['afii10078'] = 0x043C;
- t['afii10079'] = 0x043D;
- t['afii10080'] = 0x043E;
- t['afii10081'] = 0x043F;
- t['afii10082'] = 0x0440;
- t['afii10083'] = 0x0441;
- t['afii10084'] = 0x0442;
- t['afii10085'] = 0x0443;
- t['afii10086'] = 0x0444;
- t['afii10087'] = 0x0445;
- t['afii10088'] = 0x0446;
- t['afii10089'] = 0x0447;
- t['afii10090'] = 0x0448;
- t['afii10091'] = 0x0449;
- t['afii10092'] = 0x044A;
- t['afii10093'] = 0x044B;
- t['afii10094'] = 0x044C;
- t['afii10095'] = 0x044D;
- t['afii10096'] = 0x044E;
- t['afii10097'] = 0x044F;
- t['afii10098'] = 0x0491;
- t['afii10099'] = 0x0452;
- t['afii10100'] = 0x0453;
- t['afii10101'] = 0x0454;
- t['afii10102'] = 0x0455;
- t['afii10103'] = 0x0456;
- t['afii10104'] = 0x0457;
- t['afii10105'] = 0x0458;
- t['afii10106'] = 0x0459;
- t['afii10107'] = 0x045A;
- t['afii10108'] = 0x045B;
- t['afii10109'] = 0x045C;
- t['afii10110'] = 0x045E;
- t['afii10145'] = 0x040F;
- t['afii10146'] = 0x0462;
- t['afii10147'] = 0x0472;
- t['afii10148'] = 0x0474;
- t['afii10192'] = 0xF6C6;
- t['afii10193'] = 0x045F;
- t['afii10194'] = 0x0463;
- t['afii10195'] = 0x0473;
- t['afii10196'] = 0x0475;
- t['afii10831'] = 0xF6C7;
- t['afii10832'] = 0xF6C8;
- t['afii10846'] = 0x04D9;
- t['afii299'] = 0x200E;
- t['afii300'] = 0x200F;
- t['afii301'] = 0x200D;
- t['afii57381'] = 0x066A;
- t['afii57388'] = 0x060C;
- t['afii57392'] = 0x0660;
- t['afii57393'] = 0x0661;
- t['afii57394'] = 0x0662;
- t['afii57395'] = 0x0663;
- t['afii57396'] = 0x0664;
- t['afii57397'] = 0x0665;
- t['afii57398'] = 0x0666;
- t['afii57399'] = 0x0667;
- t['afii57400'] = 0x0668;
- t['afii57401'] = 0x0669;
- t['afii57403'] = 0x061B;
- t['afii57407'] = 0x061F;
- t['afii57409'] = 0x0621;
- t['afii57410'] = 0x0622;
- t['afii57411'] = 0x0623;
- t['afii57412'] = 0x0624;
- t['afii57413'] = 0x0625;
- t['afii57414'] = 0x0626;
- t['afii57415'] = 0x0627;
- t['afii57416'] = 0x0628;
- t['afii57417'] = 0x0629;
- t['afii57418'] = 0x062A;
- t['afii57419'] = 0x062B;
- t['afii57420'] = 0x062C;
- t['afii57421'] = 0x062D;
- t['afii57422'] = 0x062E;
- t['afii57423'] = 0x062F;
- t['afii57424'] = 0x0630;
- t['afii57425'] = 0x0631;
- t['afii57426'] = 0x0632;
- t['afii57427'] = 0x0633;
- t['afii57428'] = 0x0634;
- t['afii57429'] = 0x0635;
- t['afii57430'] = 0x0636;
- t['afii57431'] = 0x0637;
- t['afii57432'] = 0x0638;
- t['afii57433'] = 0x0639;
- t['afii57434'] = 0x063A;
- t['afii57440'] = 0x0640;
- t['afii57441'] = 0x0641;
- t['afii57442'] = 0x0642;
- t['afii57443'] = 0x0643;
- t['afii57444'] = 0x0644;
- t['afii57445'] = 0x0645;
- t['afii57446'] = 0x0646;
- t['afii57448'] = 0x0648;
- t['afii57449'] = 0x0649;
- t['afii57450'] = 0x064A;
- t['afii57451'] = 0x064B;
- t['afii57452'] = 0x064C;
- t['afii57453'] = 0x064D;
- t['afii57454'] = 0x064E;
- t['afii57455'] = 0x064F;
- t['afii57456'] = 0x0650;
- t['afii57457'] = 0x0651;
- t['afii57458'] = 0x0652;
- t['afii57470'] = 0x0647;
- t['afii57505'] = 0x06A4;
- t['afii57506'] = 0x067E;
- t['afii57507'] = 0x0686;
- t['afii57508'] = 0x0698;
- t['afii57509'] = 0x06AF;
- t['afii57511'] = 0x0679;
- t['afii57512'] = 0x0688;
- t['afii57513'] = 0x0691;
- t['afii57514'] = 0x06BA;
- t['afii57519'] = 0x06D2;
- t['afii57534'] = 0x06D5;
- t['afii57636'] = 0x20AA;
- t['afii57645'] = 0x05BE;
- t['afii57658'] = 0x05C3;
- t['afii57664'] = 0x05D0;
- t['afii57665'] = 0x05D1;
- t['afii57666'] = 0x05D2;
- t['afii57667'] = 0x05D3;
- t['afii57668'] = 0x05D4;
- t['afii57669'] = 0x05D5;
- t['afii57670'] = 0x05D6;
- t['afii57671'] = 0x05D7;
- t['afii57672'] = 0x05D8;
- t['afii57673'] = 0x05D9;
- t['afii57674'] = 0x05DA;
- t['afii57675'] = 0x05DB;
- t['afii57676'] = 0x05DC;
- t['afii57677'] = 0x05DD;
- t['afii57678'] = 0x05DE;
- t['afii57679'] = 0x05DF;
- t['afii57680'] = 0x05E0;
- t['afii57681'] = 0x05E1;
- t['afii57682'] = 0x05E2;
- t['afii57683'] = 0x05E3;
- t['afii57684'] = 0x05E4;
- t['afii57685'] = 0x05E5;
- t['afii57686'] = 0x05E6;
- t['afii57687'] = 0x05E7;
- t['afii57688'] = 0x05E8;
- t['afii57689'] = 0x05E9;
- t['afii57690'] = 0x05EA;
- t['afii57694'] = 0xFB2A;
- t['afii57695'] = 0xFB2B;
- t['afii57700'] = 0xFB4B;
- t['afii57705'] = 0xFB1F;
- t['afii57716'] = 0x05F0;
- t['afii57717'] = 0x05F1;
- t['afii57718'] = 0x05F2;
- t['afii57723'] = 0xFB35;
- t['afii57793'] = 0x05B4;
- t['afii57794'] = 0x05B5;
- t['afii57795'] = 0x05B6;
- t['afii57796'] = 0x05BB;
- t['afii57797'] = 0x05B8;
- t['afii57798'] = 0x05B7;
- t['afii57799'] = 0x05B0;
- t['afii57800'] = 0x05B2;
- t['afii57801'] = 0x05B1;
- t['afii57802'] = 0x05B3;
- t['afii57803'] = 0x05C2;
- t['afii57804'] = 0x05C1;
- t['afii57806'] = 0x05B9;
- t['afii57807'] = 0x05BC;
- t['afii57839'] = 0x05BD;
- t['afii57841'] = 0x05BF;
- t['afii57842'] = 0x05C0;
- t['afii57929'] = 0x02BC;
- t['afii61248'] = 0x2105;
- t['afii61289'] = 0x2113;
- t['afii61352'] = 0x2116;
- t['afii61573'] = 0x202C;
- t['afii61574'] = 0x202D;
- t['afii61575'] = 0x202E;
- t['afii61664'] = 0x200C;
- t['afii63167'] = 0x066D;
- t['afii64937'] = 0x02BD;
- t['agrave'] = 0x00E0;
- t['agujarati'] = 0x0A85;
- t['agurmukhi'] = 0x0A05;
- t['ahiragana'] = 0x3042;
- t['ahookabove'] = 0x1EA3;
- t['aibengali'] = 0x0990;
- t['aibopomofo'] = 0x311E;
- t['aideva'] = 0x0910;
- t['aiecyrillic'] = 0x04D5;
- t['aigujarati'] = 0x0A90;
- t['aigurmukhi'] = 0x0A10;
- t['aimatragurmukhi'] = 0x0A48;
- t['ainarabic'] = 0x0639;
- t['ainfinalarabic'] = 0xFECA;
- t['aininitialarabic'] = 0xFECB;
- t['ainmedialarabic'] = 0xFECC;
- t['ainvertedbreve'] = 0x0203;
- t['aivowelsignbengali'] = 0x09C8;
- t['aivowelsigndeva'] = 0x0948;
- t['aivowelsigngujarati'] = 0x0AC8;
- t['akatakana'] = 0x30A2;
- t['akatakanahalfwidth'] = 0xFF71;
- t['akorean'] = 0x314F;
- t['alef'] = 0x05D0;
- t['alefarabic'] = 0x0627;
- t['alefdageshhebrew'] = 0xFB30;
- t['aleffinalarabic'] = 0xFE8E;
- t['alefhamzaabovearabic'] = 0x0623;
- t['alefhamzaabovefinalarabic'] = 0xFE84;
- t['alefhamzabelowarabic'] = 0x0625;
- t['alefhamzabelowfinalarabic'] = 0xFE88;
- t['alefhebrew'] = 0x05D0;
- t['aleflamedhebrew'] = 0xFB4F;
- t['alefmaddaabovearabic'] = 0x0622;
- t['alefmaddaabovefinalarabic'] = 0xFE82;
- t['alefmaksuraarabic'] = 0x0649;
- t['alefmaksurafinalarabic'] = 0xFEF0;
- t['alefmaksurainitialarabic'] = 0xFEF3;
- t['alefmaksuramedialarabic'] = 0xFEF4;
- t['alefpatahhebrew'] = 0xFB2E;
- t['alefqamatshebrew'] = 0xFB2F;
- t['aleph'] = 0x2135;
- t['allequal'] = 0x224C;
- t['alpha'] = 0x03B1;
- t['alphatonos'] = 0x03AC;
- t['amacron'] = 0x0101;
- t['amonospace'] = 0xFF41;
- t['ampersand'] = 0x0026;
- t['ampersandmonospace'] = 0xFF06;
- t['ampersandsmall'] = 0xF726;
- t['amsquare'] = 0x33C2;
- t['anbopomofo'] = 0x3122;
- t['angbopomofo'] = 0x3124;
- t['angbracketleft'] = 0x3008;
- t['angbracketright'] = 0x3009;
- t['angkhankhuthai'] = 0x0E5A;
- t['angle'] = 0x2220;
- t['anglebracketleft'] = 0x3008;
- t['anglebracketleftvertical'] = 0xFE3F;
- t['anglebracketright'] = 0x3009;
- t['anglebracketrightvertical'] = 0xFE40;
- t['angleleft'] = 0x2329;
- t['angleright'] = 0x232A;
- t['angstrom'] = 0x212B;
- t['anoteleia'] = 0x0387;
- t['anudattadeva'] = 0x0952;
- t['anusvarabengali'] = 0x0982;
- t['anusvaradeva'] = 0x0902;
- t['anusvaragujarati'] = 0x0A82;
- t['aogonek'] = 0x0105;
- t['apaatosquare'] = 0x3300;
- t['aparen'] = 0x249C;
- t['apostrophearmenian'] = 0x055A;
- t['apostrophemod'] = 0x02BC;
- t['apple'] = 0xF8FF;
- t['approaches'] = 0x2250;
- t['approxequal'] = 0x2248;
- t['approxequalorimage'] = 0x2252;
- t['approximatelyequal'] = 0x2245;
- t['araeaekorean'] = 0x318E;
- t['araeakorean'] = 0x318D;
- t['arc'] = 0x2312;
- t['arighthalfring'] = 0x1E9A;
- t['aring'] = 0x00E5;
- t['aringacute'] = 0x01FB;
- t['aringbelow'] = 0x1E01;
- t['arrowboth'] = 0x2194;
- t['arrowdashdown'] = 0x21E3;
- t['arrowdashleft'] = 0x21E0;
- t['arrowdashright'] = 0x21E2;
- t['arrowdashup'] = 0x21E1;
- t['arrowdblboth'] = 0x21D4;
- t['arrowdbldown'] = 0x21D3;
- t['arrowdblleft'] = 0x21D0;
- t['arrowdblright'] = 0x21D2;
- t['arrowdblup'] = 0x21D1;
- t['arrowdown'] = 0x2193;
- t['arrowdownleft'] = 0x2199;
- t['arrowdownright'] = 0x2198;
- t['arrowdownwhite'] = 0x21E9;
- t['arrowheaddownmod'] = 0x02C5;
- t['arrowheadleftmod'] = 0x02C2;
- t['arrowheadrightmod'] = 0x02C3;
- t['arrowheadupmod'] = 0x02C4;
- t['arrowhorizex'] = 0xF8E7;
- t['arrowleft'] = 0x2190;
- t['arrowleftdbl'] = 0x21D0;
- t['arrowleftdblstroke'] = 0x21CD;
- t['arrowleftoverright'] = 0x21C6;
- t['arrowleftwhite'] = 0x21E6;
- t['arrowright'] = 0x2192;
- t['arrowrightdblstroke'] = 0x21CF;
- t['arrowrightheavy'] = 0x279E;
- t['arrowrightoverleft'] = 0x21C4;
- t['arrowrightwhite'] = 0x21E8;
- t['arrowtableft'] = 0x21E4;
- t['arrowtabright'] = 0x21E5;
- t['arrowup'] = 0x2191;
- t['arrowupdn'] = 0x2195;
- t['arrowupdnbse'] = 0x21A8;
- t['arrowupdownbase'] = 0x21A8;
- t['arrowupleft'] = 0x2196;
- t['arrowupleftofdown'] = 0x21C5;
- t['arrowupright'] = 0x2197;
- t['arrowupwhite'] = 0x21E7;
- t['arrowvertex'] = 0xF8E6;
- t['asciicircum'] = 0x005E;
- t['asciicircummonospace'] = 0xFF3E;
- t['asciitilde'] = 0x007E;
- t['asciitildemonospace'] = 0xFF5E;
- t['ascript'] = 0x0251;
- t['ascriptturned'] = 0x0252;
- t['asmallhiragana'] = 0x3041;
- t['asmallkatakana'] = 0x30A1;
- t['asmallkatakanahalfwidth'] = 0xFF67;
- t['asterisk'] = 0x002A;
- t['asteriskaltonearabic'] = 0x066D;
- t['asteriskarabic'] = 0x066D;
- t['asteriskmath'] = 0x2217;
- t['asteriskmonospace'] = 0xFF0A;
- t['asterisksmall'] = 0xFE61;
- t['asterism'] = 0x2042;
- t['asuperior'] = 0xF6E9;
- t['asymptoticallyequal'] = 0x2243;
- t['at'] = 0x0040;
- t['atilde'] = 0x00E3;
- t['atmonospace'] = 0xFF20;
- t['atsmall'] = 0xFE6B;
- t['aturned'] = 0x0250;
- t['aubengali'] = 0x0994;
- t['aubopomofo'] = 0x3120;
- t['audeva'] = 0x0914;
- t['augujarati'] = 0x0A94;
- t['augurmukhi'] = 0x0A14;
- t['aulengthmarkbengali'] = 0x09D7;
- t['aumatragurmukhi'] = 0x0A4C;
- t['auvowelsignbengali'] = 0x09CC;
- t['auvowelsigndeva'] = 0x094C;
- t['auvowelsigngujarati'] = 0x0ACC;
- t['avagrahadeva'] = 0x093D;
- t['aybarmenian'] = 0x0561;
- t['ayin'] = 0x05E2;
- t['ayinaltonehebrew'] = 0xFB20;
- t['ayinhebrew'] = 0x05E2;
- t['b'] = 0x0062;
- t['babengali'] = 0x09AC;
- t['backslash'] = 0x005C;
- t['backslashmonospace'] = 0xFF3C;
- t['badeva'] = 0x092C;
- t['bagujarati'] = 0x0AAC;
- t['bagurmukhi'] = 0x0A2C;
- t['bahiragana'] = 0x3070;
- t['bahtthai'] = 0x0E3F;
- t['bakatakana'] = 0x30D0;
- t['bar'] = 0x007C;
- t['barmonospace'] = 0xFF5C;
- t['bbopomofo'] = 0x3105;
- t['bcircle'] = 0x24D1;
- t['bdotaccent'] = 0x1E03;
- t['bdotbelow'] = 0x1E05;
- t['beamedsixteenthnotes'] = 0x266C;
- t['because'] = 0x2235;
- t['becyrillic'] = 0x0431;
- t['beharabic'] = 0x0628;
- t['behfinalarabic'] = 0xFE90;
- t['behinitialarabic'] = 0xFE91;
- t['behiragana'] = 0x3079;
- t['behmedialarabic'] = 0xFE92;
- t['behmeeminitialarabic'] = 0xFC9F;
- t['behmeemisolatedarabic'] = 0xFC08;
- t['behnoonfinalarabic'] = 0xFC6D;
- t['bekatakana'] = 0x30D9;
- t['benarmenian'] = 0x0562;
- t['bet'] = 0x05D1;
- t['beta'] = 0x03B2;
- t['betasymbolgreek'] = 0x03D0;
- t['betdagesh'] = 0xFB31;
- t['betdageshhebrew'] = 0xFB31;
- t['bethebrew'] = 0x05D1;
- t['betrafehebrew'] = 0xFB4C;
- t['bhabengali'] = 0x09AD;
- t['bhadeva'] = 0x092D;
- t['bhagujarati'] = 0x0AAD;
- t['bhagurmukhi'] = 0x0A2D;
- t['bhook'] = 0x0253;
- t['bihiragana'] = 0x3073;
- t['bikatakana'] = 0x30D3;
- t['bilabialclick'] = 0x0298;
- t['bindigurmukhi'] = 0x0A02;
- t['birusquare'] = 0x3331;
- t['blackcircle'] = 0x25CF;
- t['blackdiamond'] = 0x25C6;
- t['blackdownpointingtriangle'] = 0x25BC;
- t['blackleftpointingpointer'] = 0x25C4;
- t['blackleftpointingtriangle'] = 0x25C0;
- t['blacklenticularbracketleft'] = 0x3010;
- t['blacklenticularbracketleftvertical'] = 0xFE3B;
- t['blacklenticularbracketright'] = 0x3011;
- t['blacklenticularbracketrightvertical'] = 0xFE3C;
- t['blacklowerlefttriangle'] = 0x25E3;
- t['blacklowerrighttriangle'] = 0x25E2;
- t['blackrectangle'] = 0x25AC;
- t['blackrightpointingpointer'] = 0x25BA;
- t['blackrightpointingtriangle'] = 0x25B6;
- t['blacksmallsquare'] = 0x25AA;
- t['blacksmilingface'] = 0x263B;
- t['blacksquare'] = 0x25A0;
- t['blackstar'] = 0x2605;
- t['blackupperlefttriangle'] = 0x25E4;
- t['blackupperrighttriangle'] = 0x25E5;
- t['blackuppointingsmalltriangle'] = 0x25B4;
- t['blackuppointingtriangle'] = 0x25B2;
- t['blank'] = 0x2423;
- t['blinebelow'] = 0x1E07;
- t['block'] = 0x2588;
- t['bmonospace'] = 0xFF42;
- t['bobaimaithai'] = 0x0E1A;
- t['bohiragana'] = 0x307C;
- t['bokatakana'] = 0x30DC;
- t['bparen'] = 0x249D;
- t['bqsquare'] = 0x33C3;
- t['braceex'] = 0xF8F4;
- t['braceleft'] = 0x007B;
- t['braceleftbt'] = 0xF8F3;
- t['braceleftmid'] = 0xF8F2;
- t['braceleftmonospace'] = 0xFF5B;
- t['braceleftsmall'] = 0xFE5B;
- t['bracelefttp'] = 0xF8F1;
- t['braceleftvertical'] = 0xFE37;
- t['braceright'] = 0x007D;
- t['bracerightbt'] = 0xF8FE;
- t['bracerightmid'] = 0xF8FD;
- t['bracerightmonospace'] = 0xFF5D;
- t['bracerightsmall'] = 0xFE5C;
- t['bracerighttp'] = 0xF8FC;
- t['bracerightvertical'] = 0xFE38;
- t['bracketleft'] = 0x005B;
- t['bracketleftbt'] = 0xF8F0;
- t['bracketleftex'] = 0xF8EF;
- t['bracketleftmonospace'] = 0xFF3B;
- t['bracketlefttp'] = 0xF8EE;
- t['bracketright'] = 0x005D;
- t['bracketrightbt'] = 0xF8FB;
- t['bracketrightex'] = 0xF8FA;
- t['bracketrightmonospace'] = 0xFF3D;
- t['bracketrighttp'] = 0xF8F9;
- t['breve'] = 0x02D8;
- t['brevebelowcmb'] = 0x032E;
- t['brevecmb'] = 0x0306;
- t['breveinvertedbelowcmb'] = 0x032F;
- t['breveinvertedcmb'] = 0x0311;
- t['breveinverteddoublecmb'] = 0x0361;
- t['bridgebelowcmb'] = 0x032A;
- t['bridgeinvertedbelowcmb'] = 0x033A;
- t['brokenbar'] = 0x00A6;
- t['bstroke'] = 0x0180;
- t['bsuperior'] = 0xF6EA;
- t['btopbar'] = 0x0183;
- t['buhiragana'] = 0x3076;
- t['bukatakana'] = 0x30D6;
- t['bullet'] = 0x2022;
- t['bulletinverse'] = 0x25D8;
- t['bulletoperator'] = 0x2219;
- t['bullseye'] = 0x25CE;
- t['c'] = 0x0063;
- t['caarmenian'] = 0x056E;
- t['cabengali'] = 0x099A;
- t['cacute'] = 0x0107;
- t['cadeva'] = 0x091A;
- t['cagujarati'] = 0x0A9A;
- t['cagurmukhi'] = 0x0A1A;
- t['calsquare'] = 0x3388;
- t['candrabindubengali'] = 0x0981;
- t['candrabinducmb'] = 0x0310;
- t['candrabindudeva'] = 0x0901;
- t['candrabindugujarati'] = 0x0A81;
- t['capslock'] = 0x21EA;
- t['careof'] = 0x2105;
- t['caron'] = 0x02C7;
- t['caronbelowcmb'] = 0x032C;
- t['caroncmb'] = 0x030C;
- t['carriagereturn'] = 0x21B5;
- t['cbopomofo'] = 0x3118;
- t['ccaron'] = 0x010D;
- t['ccedilla'] = 0x00E7;
- t['ccedillaacute'] = 0x1E09;
- t['ccircle'] = 0x24D2;
- t['ccircumflex'] = 0x0109;
- t['ccurl'] = 0x0255;
- t['cdot'] = 0x010B;
- t['cdotaccent'] = 0x010B;
- t['cdsquare'] = 0x33C5;
- t['cedilla'] = 0x00B8;
- t['cedillacmb'] = 0x0327;
- t['cent'] = 0x00A2;
- t['centigrade'] = 0x2103;
- t['centinferior'] = 0xF6DF;
- t['centmonospace'] = 0xFFE0;
- t['centoldstyle'] = 0xF7A2;
- t['centsuperior'] = 0xF6E0;
- t['chaarmenian'] = 0x0579;
- t['chabengali'] = 0x099B;
- t['chadeva'] = 0x091B;
- t['chagujarati'] = 0x0A9B;
- t['chagurmukhi'] = 0x0A1B;
- t['chbopomofo'] = 0x3114;
- t['cheabkhasiancyrillic'] = 0x04BD;
- t['checkmark'] = 0x2713;
- t['checyrillic'] = 0x0447;
- t['chedescenderabkhasiancyrillic'] = 0x04BF;
- t['chedescendercyrillic'] = 0x04B7;
- t['chedieresiscyrillic'] = 0x04F5;
- t['cheharmenian'] = 0x0573;
- t['chekhakassiancyrillic'] = 0x04CC;
- t['cheverticalstrokecyrillic'] = 0x04B9;
- t['chi'] = 0x03C7;
- t['chieuchacirclekorean'] = 0x3277;
- t['chieuchaparenkorean'] = 0x3217;
- t['chieuchcirclekorean'] = 0x3269;
- t['chieuchkorean'] = 0x314A;
- t['chieuchparenkorean'] = 0x3209;
- t['chochangthai'] = 0x0E0A;
- t['chochanthai'] = 0x0E08;
- t['chochingthai'] = 0x0E09;
- t['chochoethai'] = 0x0E0C;
- t['chook'] = 0x0188;
- t['cieucacirclekorean'] = 0x3276;
- t['cieucaparenkorean'] = 0x3216;
- t['cieuccirclekorean'] = 0x3268;
- t['cieuckorean'] = 0x3148;
- t['cieucparenkorean'] = 0x3208;
- t['cieucuparenkorean'] = 0x321C;
- t['circle'] = 0x25CB;
- t['circlecopyrt'] = 0x00A9;
- t['circlemultiply'] = 0x2297;
- t['circleot'] = 0x2299;
- t['circleplus'] = 0x2295;
- t['circlepostalmark'] = 0x3036;
- t['circlewithlefthalfblack'] = 0x25D0;
- t['circlewithrighthalfblack'] = 0x25D1;
- t['circumflex'] = 0x02C6;
- t['circumflexbelowcmb'] = 0x032D;
- t['circumflexcmb'] = 0x0302;
- t['clear'] = 0x2327;
- t['clickalveolar'] = 0x01C2;
- t['clickdental'] = 0x01C0;
- t['clicklateral'] = 0x01C1;
- t['clickretroflex'] = 0x01C3;
- t['club'] = 0x2663;
- t['clubsuitblack'] = 0x2663;
- t['clubsuitwhite'] = 0x2667;
- t['cmcubedsquare'] = 0x33A4;
- t['cmonospace'] = 0xFF43;
- t['cmsquaredsquare'] = 0x33A0;
- t['coarmenian'] = 0x0581;
- t['colon'] = 0x003A;
- t['colonmonetary'] = 0x20A1;
- t['colonmonospace'] = 0xFF1A;
- t['colonsign'] = 0x20A1;
- t['colonsmall'] = 0xFE55;
- t['colontriangularhalfmod'] = 0x02D1;
- t['colontriangularmod'] = 0x02D0;
- t['comma'] = 0x002C;
- t['commaabovecmb'] = 0x0313;
- t['commaaboverightcmb'] = 0x0315;
- t['commaaccent'] = 0xF6C3;
- t['commaarabic'] = 0x060C;
- t['commaarmenian'] = 0x055D;
- t['commainferior'] = 0xF6E1;
- t['commamonospace'] = 0xFF0C;
- t['commareversedabovecmb'] = 0x0314;
- t['commareversedmod'] = 0x02BD;
- t['commasmall'] = 0xFE50;
- t['commasuperior'] = 0xF6E2;
- t['commaturnedabovecmb'] = 0x0312;
- t['commaturnedmod'] = 0x02BB;
- t['compass'] = 0x263C;
- t['congruent'] = 0x2245;
- t['contourintegral'] = 0x222E;
- t['control'] = 0x2303;
- t['controlACK'] = 0x0006;
- t['controlBEL'] = 0x0007;
- t['controlBS'] = 0x0008;
- t['controlCAN'] = 0x0018;
- t['controlCR'] = 0x000D;
- t['controlDC1'] = 0x0011;
- t['controlDC2'] = 0x0012;
- t['controlDC3'] = 0x0013;
- t['controlDC4'] = 0x0014;
- t['controlDEL'] = 0x007F;
- t['controlDLE'] = 0x0010;
- t['controlEM'] = 0x0019;
- t['controlENQ'] = 0x0005;
- t['controlEOT'] = 0x0004;
- t['controlESC'] = 0x001B;
- t['controlETB'] = 0x0017;
- t['controlETX'] = 0x0003;
- t['controlFF'] = 0x000C;
- t['controlFS'] = 0x001C;
- t['controlGS'] = 0x001D;
- t['controlHT'] = 0x0009;
- t['controlLF'] = 0x000A;
- t['controlNAK'] = 0x0015;
- t['controlNULL'] = 0x0000;
- t['controlRS'] = 0x001E;
- t['controlSI'] = 0x000F;
- t['controlSO'] = 0x000E;
- t['controlSOT'] = 0x0002;
- t['controlSTX'] = 0x0001;
- t['controlSUB'] = 0x001A;
- t['controlSYN'] = 0x0016;
- t['controlUS'] = 0x001F;
- t['controlVT'] = 0x000B;
- t['copyright'] = 0x00A9;
- t['copyrightsans'] = 0xF8E9;
- t['copyrightserif'] = 0xF6D9;
- t['cornerbracketleft'] = 0x300C;
- t['cornerbracketlefthalfwidth'] = 0xFF62;
- t['cornerbracketleftvertical'] = 0xFE41;
- t['cornerbracketright'] = 0x300D;
- t['cornerbracketrighthalfwidth'] = 0xFF63;
- t['cornerbracketrightvertical'] = 0xFE42;
- t['corporationsquare'] = 0x337F;
- t['cosquare'] = 0x33C7;
- t['coverkgsquare'] = 0x33C6;
- t['cparen'] = 0x249E;
- t['cruzeiro'] = 0x20A2;
- t['cstretched'] = 0x0297;
- t['curlyand'] = 0x22CF;
- t['curlyor'] = 0x22CE;
- t['currency'] = 0x00A4;
- t['cyrBreve'] = 0xF6D1;
- t['cyrFlex'] = 0xF6D2;
- t['cyrbreve'] = 0xF6D4;
- t['cyrflex'] = 0xF6D5;
- t['d'] = 0x0064;
- t['daarmenian'] = 0x0564;
- t['dabengali'] = 0x09A6;
- t['dadarabic'] = 0x0636;
- t['dadeva'] = 0x0926;
- t['dadfinalarabic'] = 0xFEBE;
- t['dadinitialarabic'] = 0xFEBF;
- t['dadmedialarabic'] = 0xFEC0;
- t['dagesh'] = 0x05BC;
- t['dageshhebrew'] = 0x05BC;
- t['dagger'] = 0x2020;
- t['daggerdbl'] = 0x2021;
- t['dagujarati'] = 0x0AA6;
- t['dagurmukhi'] = 0x0A26;
- t['dahiragana'] = 0x3060;
- t['dakatakana'] = 0x30C0;
- t['dalarabic'] = 0x062F;
- t['dalet'] = 0x05D3;
- t['daletdagesh'] = 0xFB33;
- t['daletdageshhebrew'] = 0xFB33;
- t['dalethebrew'] = 0x05D3;
- t['dalfinalarabic'] = 0xFEAA;
- t['dammaarabic'] = 0x064F;
- t['dammalowarabic'] = 0x064F;
- t['dammatanaltonearabic'] = 0x064C;
- t['dammatanarabic'] = 0x064C;
- t['danda'] = 0x0964;
- t['dargahebrew'] = 0x05A7;
- t['dargalefthebrew'] = 0x05A7;
- t['dasiapneumatacyrilliccmb'] = 0x0485;
- t['dblGrave'] = 0xF6D3;
- t['dblanglebracketleft'] = 0x300A;
- t['dblanglebracketleftvertical'] = 0xFE3D;
- t['dblanglebracketright'] = 0x300B;
- t['dblanglebracketrightvertical'] = 0xFE3E;
- t['dblarchinvertedbelowcmb'] = 0x032B;
- t['dblarrowleft'] = 0x21D4;
- t['dblarrowright'] = 0x21D2;
- t['dbldanda'] = 0x0965;
- t['dblgrave'] = 0xF6D6;
- t['dblgravecmb'] = 0x030F;
- t['dblintegral'] = 0x222C;
- t['dbllowline'] = 0x2017;
- t['dbllowlinecmb'] = 0x0333;
- t['dbloverlinecmb'] = 0x033F;
- t['dblprimemod'] = 0x02BA;
- t['dblverticalbar'] = 0x2016;
- t['dblverticallineabovecmb'] = 0x030E;
- t['dbopomofo'] = 0x3109;
- t['dbsquare'] = 0x33C8;
- t['dcaron'] = 0x010F;
- t['dcedilla'] = 0x1E11;
- t['dcircle'] = 0x24D3;
- t['dcircumflexbelow'] = 0x1E13;
- t['dcroat'] = 0x0111;
- t['ddabengali'] = 0x09A1;
- t['ddadeva'] = 0x0921;
- t['ddagujarati'] = 0x0AA1;
- t['ddagurmukhi'] = 0x0A21;
- t['ddalarabic'] = 0x0688;
- t['ddalfinalarabic'] = 0xFB89;
- t['dddhadeva'] = 0x095C;
- t['ddhabengali'] = 0x09A2;
- t['ddhadeva'] = 0x0922;
- t['ddhagujarati'] = 0x0AA2;
- t['ddhagurmukhi'] = 0x0A22;
- t['ddotaccent'] = 0x1E0B;
- t['ddotbelow'] = 0x1E0D;
- t['decimalseparatorarabic'] = 0x066B;
- t['decimalseparatorpersian'] = 0x066B;
- t['decyrillic'] = 0x0434;
- t['degree'] = 0x00B0;
- t['dehihebrew'] = 0x05AD;
- t['dehiragana'] = 0x3067;
- t['deicoptic'] = 0x03EF;
- t['dekatakana'] = 0x30C7;
- t['deleteleft'] = 0x232B;
- t['deleteright'] = 0x2326;
- t['delta'] = 0x03B4;
- t['deltaturned'] = 0x018D;
- t['denominatorminusonenumeratorbengali'] = 0x09F8;
- t['dezh'] = 0x02A4;
- t['dhabengali'] = 0x09A7;
- t['dhadeva'] = 0x0927;
- t['dhagujarati'] = 0x0AA7;
- t['dhagurmukhi'] = 0x0A27;
- t['dhook'] = 0x0257;
- t['dialytikatonos'] = 0x0385;
- t['dialytikatonoscmb'] = 0x0344;
- t['diamond'] = 0x2666;
- t['diamondsuitwhite'] = 0x2662;
- t['dieresis'] = 0x00A8;
- t['dieresisacute'] = 0xF6D7;
- t['dieresisbelowcmb'] = 0x0324;
- t['dieresiscmb'] = 0x0308;
- t['dieresisgrave'] = 0xF6D8;
- t['dieresistonos'] = 0x0385;
- t['dihiragana'] = 0x3062;
- t['dikatakana'] = 0x30C2;
- t['dittomark'] = 0x3003;
- t['divide'] = 0x00F7;
- t['divides'] = 0x2223;
- t['divisionslash'] = 0x2215;
- t['djecyrillic'] = 0x0452;
- t['dkshade'] = 0x2593;
- t['dlinebelow'] = 0x1E0F;
- t['dlsquare'] = 0x3397;
- t['dmacron'] = 0x0111;
- t['dmonospace'] = 0xFF44;
- t['dnblock'] = 0x2584;
- t['dochadathai'] = 0x0E0E;
- t['dodekthai'] = 0x0E14;
- t['dohiragana'] = 0x3069;
- t['dokatakana'] = 0x30C9;
- t['dollar'] = 0x0024;
- t['dollarinferior'] = 0xF6E3;
- t['dollarmonospace'] = 0xFF04;
- t['dollaroldstyle'] = 0xF724;
- t['dollarsmall'] = 0xFE69;
- t['dollarsuperior'] = 0xF6E4;
- t['dong'] = 0x20AB;
- t['dorusquare'] = 0x3326;
- t['dotaccent'] = 0x02D9;
- t['dotaccentcmb'] = 0x0307;
- t['dotbelowcmb'] = 0x0323;
- t['dotbelowcomb'] = 0x0323;
- t['dotkatakana'] = 0x30FB;
- t['dotlessi'] = 0x0131;
- t['dotlessj'] = 0xF6BE;
- t['dotlessjstrokehook'] = 0x0284;
- t['dotmath'] = 0x22C5;
- t['dottedcircle'] = 0x25CC;
- t['doubleyodpatah'] = 0xFB1F;
- t['doubleyodpatahhebrew'] = 0xFB1F;
- t['downtackbelowcmb'] = 0x031E;
- t['downtackmod'] = 0x02D5;
- t['dparen'] = 0x249F;
- t['dsuperior'] = 0xF6EB;
- t['dtail'] = 0x0256;
- t['dtopbar'] = 0x018C;
- t['duhiragana'] = 0x3065;
- t['dukatakana'] = 0x30C5;
- t['dz'] = 0x01F3;
- t['dzaltone'] = 0x02A3;
- t['dzcaron'] = 0x01C6;
- t['dzcurl'] = 0x02A5;
- t['dzeabkhasiancyrillic'] = 0x04E1;
- t['dzecyrillic'] = 0x0455;
- t['dzhecyrillic'] = 0x045F;
- t['e'] = 0x0065;
- t['eacute'] = 0x00E9;
- t['earth'] = 0x2641;
- t['ebengali'] = 0x098F;
- t['ebopomofo'] = 0x311C;
- t['ebreve'] = 0x0115;
- t['ecandradeva'] = 0x090D;
- t['ecandragujarati'] = 0x0A8D;
- t['ecandravowelsigndeva'] = 0x0945;
- t['ecandravowelsigngujarati'] = 0x0AC5;
- t['ecaron'] = 0x011B;
- t['ecedillabreve'] = 0x1E1D;
- t['echarmenian'] = 0x0565;
- t['echyiwnarmenian'] = 0x0587;
- t['ecircle'] = 0x24D4;
- t['ecircumflex'] = 0x00EA;
- t['ecircumflexacute'] = 0x1EBF;
- t['ecircumflexbelow'] = 0x1E19;
- t['ecircumflexdotbelow'] = 0x1EC7;
- t['ecircumflexgrave'] = 0x1EC1;
- t['ecircumflexhookabove'] = 0x1EC3;
- t['ecircumflextilde'] = 0x1EC5;
- t['ecyrillic'] = 0x0454;
- t['edblgrave'] = 0x0205;
- t['edeva'] = 0x090F;
- t['edieresis'] = 0x00EB;
- t['edot'] = 0x0117;
- t['edotaccent'] = 0x0117;
- t['edotbelow'] = 0x1EB9;
- t['eegurmukhi'] = 0x0A0F;
- t['eematragurmukhi'] = 0x0A47;
- t['efcyrillic'] = 0x0444;
- t['egrave'] = 0x00E8;
- t['egujarati'] = 0x0A8F;
- t['eharmenian'] = 0x0567;
- t['ehbopomofo'] = 0x311D;
- t['ehiragana'] = 0x3048;
- t['ehookabove'] = 0x1EBB;
- t['eibopomofo'] = 0x311F;
- t['eight'] = 0x0038;
- t['eightarabic'] = 0x0668;
- t['eightbengali'] = 0x09EE;
- t['eightcircle'] = 0x2467;
- t['eightcircleinversesansserif'] = 0x2791;
- t['eightdeva'] = 0x096E;
- t['eighteencircle'] = 0x2471;
- t['eighteenparen'] = 0x2485;
- t['eighteenperiod'] = 0x2499;
- t['eightgujarati'] = 0x0AEE;
- t['eightgurmukhi'] = 0x0A6E;
- t['eighthackarabic'] = 0x0668;
- t['eighthangzhou'] = 0x3028;
- t['eighthnotebeamed'] = 0x266B;
- t['eightideographicparen'] = 0x3227;
- t['eightinferior'] = 0x2088;
- t['eightmonospace'] = 0xFF18;
- t['eightoldstyle'] = 0xF738;
- t['eightparen'] = 0x247B;
- t['eightperiod'] = 0x248F;
- t['eightpersian'] = 0x06F8;
- t['eightroman'] = 0x2177;
- t['eightsuperior'] = 0x2078;
- t['eightthai'] = 0x0E58;
- t['einvertedbreve'] = 0x0207;
- t['eiotifiedcyrillic'] = 0x0465;
- t['ekatakana'] = 0x30A8;
- t['ekatakanahalfwidth'] = 0xFF74;
- t['ekonkargurmukhi'] = 0x0A74;
- t['ekorean'] = 0x3154;
- t['elcyrillic'] = 0x043B;
- t['element'] = 0x2208;
- t['elevencircle'] = 0x246A;
- t['elevenparen'] = 0x247E;
- t['elevenperiod'] = 0x2492;
- t['elevenroman'] = 0x217A;
- t['ellipsis'] = 0x2026;
- t['ellipsisvertical'] = 0x22EE;
- t['emacron'] = 0x0113;
- t['emacronacute'] = 0x1E17;
- t['emacrongrave'] = 0x1E15;
- t['emcyrillic'] = 0x043C;
- t['emdash'] = 0x2014;
- t['emdashvertical'] = 0xFE31;
- t['emonospace'] = 0xFF45;
- t['emphasismarkarmenian'] = 0x055B;
- t['emptyset'] = 0x2205;
- t['enbopomofo'] = 0x3123;
- t['encyrillic'] = 0x043D;
- t['endash'] = 0x2013;
- t['endashvertical'] = 0xFE32;
- t['endescendercyrillic'] = 0x04A3;
- t['eng'] = 0x014B;
- t['engbopomofo'] = 0x3125;
- t['enghecyrillic'] = 0x04A5;
- t['enhookcyrillic'] = 0x04C8;
- t['enspace'] = 0x2002;
- t['eogonek'] = 0x0119;
- t['eokorean'] = 0x3153;
- t['eopen'] = 0x025B;
- t['eopenclosed'] = 0x029A;
- t['eopenreversed'] = 0x025C;
- t['eopenreversedclosed'] = 0x025E;
- t['eopenreversedhook'] = 0x025D;
- t['eparen'] = 0x24A0;
- t['epsilon'] = 0x03B5;
- t['epsilontonos'] = 0x03AD;
- t['equal'] = 0x003D;
- t['equalmonospace'] = 0xFF1D;
- t['equalsmall'] = 0xFE66;
- t['equalsuperior'] = 0x207C;
- t['equivalence'] = 0x2261;
- t['erbopomofo'] = 0x3126;
- t['ercyrillic'] = 0x0440;
- t['ereversed'] = 0x0258;
- t['ereversedcyrillic'] = 0x044D;
- t['escyrillic'] = 0x0441;
- t['esdescendercyrillic'] = 0x04AB;
- t['esh'] = 0x0283;
- t['eshcurl'] = 0x0286;
- t['eshortdeva'] = 0x090E;
- t['eshortvowelsigndeva'] = 0x0946;
- t['eshreversedloop'] = 0x01AA;
- t['eshsquatreversed'] = 0x0285;
- t['esmallhiragana'] = 0x3047;
- t['esmallkatakana'] = 0x30A7;
- t['esmallkatakanahalfwidth'] = 0xFF6A;
- t['estimated'] = 0x212E;
- t['esuperior'] = 0xF6EC;
- t['eta'] = 0x03B7;
- t['etarmenian'] = 0x0568;
- t['etatonos'] = 0x03AE;
- t['eth'] = 0x00F0;
- t['etilde'] = 0x1EBD;
- t['etildebelow'] = 0x1E1B;
- t['etnahtafoukhhebrew'] = 0x0591;
- t['etnahtafoukhlefthebrew'] = 0x0591;
- t['etnahtahebrew'] = 0x0591;
- t['etnahtalefthebrew'] = 0x0591;
- t['eturned'] = 0x01DD;
- t['eukorean'] = 0x3161;
- t['euro'] = 0x20AC;
- t['evowelsignbengali'] = 0x09C7;
- t['evowelsigndeva'] = 0x0947;
- t['evowelsigngujarati'] = 0x0AC7;
- t['exclam'] = 0x0021;
- t['exclamarmenian'] = 0x055C;
- t['exclamdbl'] = 0x203C;
- t['exclamdown'] = 0x00A1;
- t['exclamdownsmall'] = 0xF7A1;
- t['exclammonospace'] = 0xFF01;
- t['exclamsmall'] = 0xF721;
- t['existential'] = 0x2203;
- t['ezh'] = 0x0292;
- t['ezhcaron'] = 0x01EF;
- t['ezhcurl'] = 0x0293;
- t['ezhreversed'] = 0x01B9;
- t['ezhtail'] = 0x01BA;
- t['f'] = 0x0066;
- t['fadeva'] = 0x095E;
- t['fagurmukhi'] = 0x0A5E;
- t['fahrenheit'] = 0x2109;
- t['fathaarabic'] = 0x064E;
- t['fathalowarabic'] = 0x064E;
- t['fathatanarabic'] = 0x064B;
- t['fbopomofo'] = 0x3108;
- t['fcircle'] = 0x24D5;
- t['fdotaccent'] = 0x1E1F;
- t['feharabic'] = 0x0641;
- t['feharmenian'] = 0x0586;
- t['fehfinalarabic'] = 0xFED2;
- t['fehinitialarabic'] = 0xFED3;
- t['fehmedialarabic'] = 0xFED4;
- t['feicoptic'] = 0x03E5;
- t['female'] = 0x2640;
- t['ff'] = 0xFB00;
- t['ffi'] = 0xFB03;
- t['ffl'] = 0xFB04;
- t['fi'] = 0xFB01;
- t['fifteencircle'] = 0x246E;
- t['fifteenparen'] = 0x2482;
- t['fifteenperiod'] = 0x2496;
- t['figuredash'] = 0x2012;
- t['filledbox'] = 0x25A0;
- t['filledrect'] = 0x25AC;
- t['finalkaf'] = 0x05DA;
- t['finalkafdagesh'] = 0xFB3A;
- t['finalkafdageshhebrew'] = 0xFB3A;
- t['finalkafhebrew'] = 0x05DA;
- t['finalmem'] = 0x05DD;
- t['finalmemhebrew'] = 0x05DD;
- t['finalnun'] = 0x05DF;
- t['finalnunhebrew'] = 0x05DF;
- t['finalpe'] = 0x05E3;
- t['finalpehebrew'] = 0x05E3;
- t['finaltsadi'] = 0x05E5;
- t['finaltsadihebrew'] = 0x05E5;
- t['firsttonechinese'] = 0x02C9;
- t['fisheye'] = 0x25C9;
- t['fitacyrillic'] = 0x0473;
- t['five'] = 0x0035;
- t['fivearabic'] = 0x0665;
- t['fivebengali'] = 0x09EB;
- t['fivecircle'] = 0x2464;
- t['fivecircleinversesansserif'] = 0x278E;
- t['fivedeva'] = 0x096B;
- t['fiveeighths'] = 0x215D;
- t['fivegujarati'] = 0x0AEB;
- t['fivegurmukhi'] = 0x0A6B;
- t['fivehackarabic'] = 0x0665;
- t['fivehangzhou'] = 0x3025;
- t['fiveideographicparen'] = 0x3224;
- t['fiveinferior'] = 0x2085;
- t['fivemonospace'] = 0xFF15;
- t['fiveoldstyle'] = 0xF735;
- t['fiveparen'] = 0x2478;
- t['fiveperiod'] = 0x248C;
- t['fivepersian'] = 0x06F5;
- t['fiveroman'] = 0x2174;
- t['fivesuperior'] = 0x2075;
- t['fivethai'] = 0x0E55;
- t['fl'] = 0xFB02;
- t['florin'] = 0x0192;
- t['fmonospace'] = 0xFF46;
- t['fmsquare'] = 0x3399;
- t['fofanthai'] = 0x0E1F;
- t['fofathai'] = 0x0E1D;
- t['fongmanthai'] = 0x0E4F;
- t['forall'] = 0x2200;
- t['four'] = 0x0034;
- t['fourarabic'] = 0x0664;
- t['fourbengali'] = 0x09EA;
- t['fourcircle'] = 0x2463;
- t['fourcircleinversesansserif'] = 0x278D;
- t['fourdeva'] = 0x096A;
- t['fourgujarati'] = 0x0AEA;
- t['fourgurmukhi'] = 0x0A6A;
- t['fourhackarabic'] = 0x0664;
- t['fourhangzhou'] = 0x3024;
- t['fourideographicparen'] = 0x3223;
- t['fourinferior'] = 0x2084;
- t['fourmonospace'] = 0xFF14;
- t['fournumeratorbengali'] = 0x09F7;
- t['fouroldstyle'] = 0xF734;
- t['fourparen'] = 0x2477;
- t['fourperiod'] = 0x248B;
- t['fourpersian'] = 0x06F4;
- t['fourroman'] = 0x2173;
- t['foursuperior'] = 0x2074;
- t['fourteencircle'] = 0x246D;
- t['fourteenparen'] = 0x2481;
- t['fourteenperiod'] = 0x2495;
- t['fourthai'] = 0x0E54;
- t['fourthtonechinese'] = 0x02CB;
- t['fparen'] = 0x24A1;
- t['fraction'] = 0x2044;
- t['franc'] = 0x20A3;
- t['g'] = 0x0067;
- t['gabengali'] = 0x0997;
- t['gacute'] = 0x01F5;
- t['gadeva'] = 0x0917;
- t['gafarabic'] = 0x06AF;
- t['gaffinalarabic'] = 0xFB93;
- t['gafinitialarabic'] = 0xFB94;
- t['gafmedialarabic'] = 0xFB95;
- t['gagujarati'] = 0x0A97;
- t['gagurmukhi'] = 0x0A17;
- t['gahiragana'] = 0x304C;
- t['gakatakana'] = 0x30AC;
- t['gamma'] = 0x03B3;
- t['gammalatinsmall'] = 0x0263;
- t['gammasuperior'] = 0x02E0;
- t['gangiacoptic'] = 0x03EB;
- t['gbopomofo'] = 0x310D;
- t['gbreve'] = 0x011F;
- t['gcaron'] = 0x01E7;
- t['gcedilla'] = 0x0123;
- t['gcircle'] = 0x24D6;
- t['gcircumflex'] = 0x011D;
- t['gcommaaccent'] = 0x0123;
- t['gdot'] = 0x0121;
- t['gdotaccent'] = 0x0121;
- t['gecyrillic'] = 0x0433;
- t['gehiragana'] = 0x3052;
- t['gekatakana'] = 0x30B2;
- t['geometricallyequal'] = 0x2251;
- t['gereshaccenthebrew'] = 0x059C;
- t['gereshhebrew'] = 0x05F3;
- t['gereshmuqdamhebrew'] = 0x059D;
- t['germandbls'] = 0x00DF;
- t['gershayimaccenthebrew'] = 0x059E;
- t['gershayimhebrew'] = 0x05F4;
- t['getamark'] = 0x3013;
- t['ghabengali'] = 0x0998;
- t['ghadarmenian'] = 0x0572;
- t['ghadeva'] = 0x0918;
- t['ghagujarati'] = 0x0A98;
- t['ghagurmukhi'] = 0x0A18;
- t['ghainarabic'] = 0x063A;
- t['ghainfinalarabic'] = 0xFECE;
- t['ghaininitialarabic'] = 0xFECF;
- t['ghainmedialarabic'] = 0xFED0;
- t['ghemiddlehookcyrillic'] = 0x0495;
- t['ghestrokecyrillic'] = 0x0493;
- t['gheupturncyrillic'] = 0x0491;
- t['ghhadeva'] = 0x095A;
- t['ghhagurmukhi'] = 0x0A5A;
- t['ghook'] = 0x0260;
- t['ghzsquare'] = 0x3393;
- t['gihiragana'] = 0x304E;
- t['gikatakana'] = 0x30AE;
- t['gimarmenian'] = 0x0563;
- t['gimel'] = 0x05D2;
- t['gimeldagesh'] = 0xFB32;
- t['gimeldageshhebrew'] = 0xFB32;
- t['gimelhebrew'] = 0x05D2;
- t['gjecyrillic'] = 0x0453;
- t['glottalinvertedstroke'] = 0x01BE;
- t['glottalstop'] = 0x0294;
- t['glottalstopinverted'] = 0x0296;
- t['glottalstopmod'] = 0x02C0;
- t['glottalstopreversed'] = 0x0295;
- t['glottalstopreversedmod'] = 0x02C1;
- t['glottalstopreversedsuperior'] = 0x02E4;
- t['glottalstopstroke'] = 0x02A1;
- t['glottalstopstrokereversed'] = 0x02A2;
- t['gmacron'] = 0x1E21;
- t['gmonospace'] = 0xFF47;
- t['gohiragana'] = 0x3054;
- t['gokatakana'] = 0x30B4;
- t['gparen'] = 0x24A2;
- t['gpasquare'] = 0x33AC;
- t['gradient'] = 0x2207;
- t['grave'] = 0x0060;
- t['gravebelowcmb'] = 0x0316;
- t['gravecmb'] = 0x0300;
- t['gravecomb'] = 0x0300;
- t['gravedeva'] = 0x0953;
- t['gravelowmod'] = 0x02CE;
- t['gravemonospace'] = 0xFF40;
- t['gravetonecmb'] = 0x0340;
- t['greater'] = 0x003E;
- t['greaterequal'] = 0x2265;
- t['greaterequalorless'] = 0x22DB;
- t['greatermonospace'] = 0xFF1E;
- t['greaterorequivalent'] = 0x2273;
- t['greaterorless'] = 0x2277;
- t['greateroverequal'] = 0x2267;
- t['greatersmall'] = 0xFE65;
- t['gscript'] = 0x0261;
- t['gstroke'] = 0x01E5;
- t['guhiragana'] = 0x3050;
- t['guillemotleft'] = 0x00AB;
- t['guillemotright'] = 0x00BB;
- t['guilsinglleft'] = 0x2039;
- t['guilsinglright'] = 0x203A;
- t['gukatakana'] = 0x30B0;
- t['guramusquare'] = 0x3318;
- t['gysquare'] = 0x33C9;
- t['h'] = 0x0068;
- t['haabkhasiancyrillic'] = 0x04A9;
- t['haaltonearabic'] = 0x06C1;
- t['habengali'] = 0x09B9;
- t['hadescendercyrillic'] = 0x04B3;
- t['hadeva'] = 0x0939;
- t['hagujarati'] = 0x0AB9;
- t['hagurmukhi'] = 0x0A39;
- t['haharabic'] = 0x062D;
- t['hahfinalarabic'] = 0xFEA2;
- t['hahinitialarabic'] = 0xFEA3;
- t['hahiragana'] = 0x306F;
- t['hahmedialarabic'] = 0xFEA4;
- t['haitusquare'] = 0x332A;
- t['hakatakana'] = 0x30CF;
- t['hakatakanahalfwidth'] = 0xFF8A;
- t['halantgurmukhi'] = 0x0A4D;
- t['hamzaarabic'] = 0x0621;
- t['hamzalowarabic'] = 0x0621;
- t['hangulfiller'] = 0x3164;
- t['hardsigncyrillic'] = 0x044A;
- t['harpoonleftbarbup'] = 0x21BC;
- t['harpoonrightbarbup'] = 0x21C0;
- t['hasquare'] = 0x33CA;
- t['hatafpatah'] = 0x05B2;
- t['hatafpatah16'] = 0x05B2;
- t['hatafpatah23'] = 0x05B2;
- t['hatafpatah2f'] = 0x05B2;
- t['hatafpatahhebrew'] = 0x05B2;
- t['hatafpatahnarrowhebrew'] = 0x05B2;
- t['hatafpatahquarterhebrew'] = 0x05B2;
- t['hatafpatahwidehebrew'] = 0x05B2;
- t['hatafqamats'] = 0x05B3;
- t['hatafqamats1b'] = 0x05B3;
- t['hatafqamats28'] = 0x05B3;
- t['hatafqamats34'] = 0x05B3;
- t['hatafqamatshebrew'] = 0x05B3;
- t['hatafqamatsnarrowhebrew'] = 0x05B3;
- t['hatafqamatsquarterhebrew'] = 0x05B3;
- t['hatafqamatswidehebrew'] = 0x05B3;
- t['hatafsegol'] = 0x05B1;
- t['hatafsegol17'] = 0x05B1;
- t['hatafsegol24'] = 0x05B1;
- t['hatafsegol30'] = 0x05B1;
- t['hatafsegolhebrew'] = 0x05B1;
- t['hatafsegolnarrowhebrew'] = 0x05B1;
- t['hatafsegolquarterhebrew'] = 0x05B1;
- t['hatafsegolwidehebrew'] = 0x05B1;
- t['hbar'] = 0x0127;
- t['hbopomofo'] = 0x310F;
- t['hbrevebelow'] = 0x1E2B;
- t['hcedilla'] = 0x1E29;
- t['hcircle'] = 0x24D7;
- t['hcircumflex'] = 0x0125;
- t['hdieresis'] = 0x1E27;
- t['hdotaccent'] = 0x1E23;
- t['hdotbelow'] = 0x1E25;
- t['he'] = 0x05D4;
- t['heart'] = 0x2665;
- t['heartsuitblack'] = 0x2665;
- t['heartsuitwhite'] = 0x2661;
- t['hedagesh'] = 0xFB34;
- t['hedageshhebrew'] = 0xFB34;
- t['hehaltonearabic'] = 0x06C1;
- t['heharabic'] = 0x0647;
- t['hehebrew'] = 0x05D4;
- t['hehfinalaltonearabic'] = 0xFBA7;
- t['hehfinalalttwoarabic'] = 0xFEEA;
- t['hehfinalarabic'] = 0xFEEA;
- t['hehhamzaabovefinalarabic'] = 0xFBA5;
- t['hehhamzaaboveisolatedarabic'] = 0xFBA4;
- t['hehinitialaltonearabic'] = 0xFBA8;
- t['hehinitialarabic'] = 0xFEEB;
- t['hehiragana'] = 0x3078;
- t['hehmedialaltonearabic'] = 0xFBA9;
- t['hehmedialarabic'] = 0xFEEC;
- t['heiseierasquare'] = 0x337B;
- t['hekatakana'] = 0x30D8;
- t['hekatakanahalfwidth'] = 0xFF8D;
- t['hekutaarusquare'] = 0x3336;
- t['henghook'] = 0x0267;
- t['herutusquare'] = 0x3339;
- t['het'] = 0x05D7;
- t['hethebrew'] = 0x05D7;
- t['hhook'] = 0x0266;
- t['hhooksuperior'] = 0x02B1;
- t['hieuhacirclekorean'] = 0x327B;
- t['hieuhaparenkorean'] = 0x321B;
- t['hieuhcirclekorean'] = 0x326D;
- t['hieuhkorean'] = 0x314E;
- t['hieuhparenkorean'] = 0x320D;
- t['hihiragana'] = 0x3072;
- t['hikatakana'] = 0x30D2;
- t['hikatakanahalfwidth'] = 0xFF8B;
- t['hiriq'] = 0x05B4;
- t['hiriq14'] = 0x05B4;
- t['hiriq21'] = 0x05B4;
- t['hiriq2d'] = 0x05B4;
- t['hiriqhebrew'] = 0x05B4;
- t['hiriqnarrowhebrew'] = 0x05B4;
- t['hiriqquarterhebrew'] = 0x05B4;
- t['hiriqwidehebrew'] = 0x05B4;
- t['hlinebelow'] = 0x1E96;
- t['hmonospace'] = 0xFF48;
- t['hoarmenian'] = 0x0570;
- t['hohipthai'] = 0x0E2B;
- t['hohiragana'] = 0x307B;
- t['hokatakana'] = 0x30DB;
- t['hokatakanahalfwidth'] = 0xFF8E;
- t['holam'] = 0x05B9;
- t['holam19'] = 0x05B9;
- t['holam26'] = 0x05B9;
- t['holam32'] = 0x05B9;
- t['holamhebrew'] = 0x05B9;
- t['holamnarrowhebrew'] = 0x05B9;
- t['holamquarterhebrew'] = 0x05B9;
- t['holamwidehebrew'] = 0x05B9;
- t['honokhukthai'] = 0x0E2E;
- t['hookabovecomb'] = 0x0309;
- t['hookcmb'] = 0x0309;
- t['hookpalatalizedbelowcmb'] = 0x0321;
- t['hookretroflexbelowcmb'] = 0x0322;
- t['hoonsquare'] = 0x3342;
- t['horicoptic'] = 0x03E9;
- t['horizontalbar'] = 0x2015;
- t['horncmb'] = 0x031B;
- t['hotsprings'] = 0x2668;
- t['house'] = 0x2302;
- t['hparen'] = 0x24A3;
- t['hsuperior'] = 0x02B0;
- t['hturned'] = 0x0265;
- t['huhiragana'] = 0x3075;
- t['huiitosquare'] = 0x3333;
- t['hukatakana'] = 0x30D5;
- t['hukatakanahalfwidth'] = 0xFF8C;
- t['hungarumlaut'] = 0x02DD;
- t['hungarumlautcmb'] = 0x030B;
- t['hv'] = 0x0195;
- t['hyphen'] = 0x002D;
- t['hypheninferior'] = 0xF6E5;
- t['hyphenmonospace'] = 0xFF0D;
- t['hyphensmall'] = 0xFE63;
- t['hyphensuperior'] = 0xF6E6;
- t['hyphentwo'] = 0x2010;
- t['i'] = 0x0069;
- t['iacute'] = 0x00ED;
- t['iacyrillic'] = 0x044F;
- t['ibengali'] = 0x0987;
- t['ibopomofo'] = 0x3127;
- t['ibreve'] = 0x012D;
- t['icaron'] = 0x01D0;
- t['icircle'] = 0x24D8;
- t['icircumflex'] = 0x00EE;
- t['icyrillic'] = 0x0456;
- t['idblgrave'] = 0x0209;
- t['ideographearthcircle'] = 0x328F;
- t['ideographfirecircle'] = 0x328B;
- t['ideographicallianceparen'] = 0x323F;
- t['ideographiccallparen'] = 0x323A;
- t['ideographiccentrecircle'] = 0x32A5;
- t['ideographicclose'] = 0x3006;
- t['ideographiccomma'] = 0x3001;
- t['ideographiccommaleft'] = 0xFF64;
- t['ideographiccongratulationparen'] = 0x3237;
- t['ideographiccorrectcircle'] = 0x32A3;
- t['ideographicearthparen'] = 0x322F;
- t['ideographicenterpriseparen'] = 0x323D;
- t['ideographicexcellentcircle'] = 0x329D;
- t['ideographicfestivalparen'] = 0x3240;
- t['ideographicfinancialcircle'] = 0x3296;
- t['ideographicfinancialparen'] = 0x3236;
- t['ideographicfireparen'] = 0x322B;
- t['ideographichaveparen'] = 0x3232;
- t['ideographichighcircle'] = 0x32A4;
- t['ideographiciterationmark'] = 0x3005;
- t['ideographiclaborcircle'] = 0x3298;
- t['ideographiclaborparen'] = 0x3238;
- t['ideographicleftcircle'] = 0x32A7;
- t['ideographiclowcircle'] = 0x32A6;
- t['ideographicmedicinecircle'] = 0x32A9;
- t['ideographicmetalparen'] = 0x322E;
- t['ideographicmoonparen'] = 0x322A;
- t['ideographicnameparen'] = 0x3234;
- t['ideographicperiod'] = 0x3002;
- t['ideographicprintcircle'] = 0x329E;
- t['ideographicreachparen'] = 0x3243;
- t['ideographicrepresentparen'] = 0x3239;
- t['ideographicresourceparen'] = 0x323E;
- t['ideographicrightcircle'] = 0x32A8;
- t['ideographicsecretcircle'] = 0x3299;
- t['ideographicselfparen'] = 0x3242;
- t['ideographicsocietyparen'] = 0x3233;
- t['ideographicspace'] = 0x3000;
- t['ideographicspecialparen'] = 0x3235;
- t['ideographicstockparen'] = 0x3231;
- t['ideographicstudyparen'] = 0x323B;
- t['ideographicsunparen'] = 0x3230;
- t['ideographicsuperviseparen'] = 0x323C;
- t['ideographicwaterparen'] = 0x322C;
- t['ideographicwoodparen'] = 0x322D;
- t['ideographiczero'] = 0x3007;
- t['ideographmetalcircle'] = 0x328E;
- t['ideographmooncircle'] = 0x328A;
- t['ideographnamecircle'] = 0x3294;
- t['ideographsuncircle'] = 0x3290;
- t['ideographwatercircle'] = 0x328C;
- t['ideographwoodcircle'] = 0x328D;
- t['ideva'] = 0x0907;
- t['idieresis'] = 0x00EF;
- t['idieresisacute'] = 0x1E2F;
- t['idieresiscyrillic'] = 0x04E5;
- t['idotbelow'] = 0x1ECB;
- t['iebrevecyrillic'] = 0x04D7;
- t['iecyrillic'] = 0x0435;
- t['ieungacirclekorean'] = 0x3275;
- t['ieungaparenkorean'] = 0x3215;
- t['ieungcirclekorean'] = 0x3267;
- t['ieungkorean'] = 0x3147;
- t['ieungparenkorean'] = 0x3207;
- t['igrave'] = 0x00EC;
- t['igujarati'] = 0x0A87;
- t['igurmukhi'] = 0x0A07;
- t['ihiragana'] = 0x3044;
- t['ihookabove'] = 0x1EC9;
- t['iibengali'] = 0x0988;
- t['iicyrillic'] = 0x0438;
- t['iideva'] = 0x0908;
- t['iigujarati'] = 0x0A88;
- t['iigurmukhi'] = 0x0A08;
- t['iimatragurmukhi'] = 0x0A40;
- t['iinvertedbreve'] = 0x020B;
- t['iishortcyrillic'] = 0x0439;
- t['iivowelsignbengali'] = 0x09C0;
- t['iivowelsigndeva'] = 0x0940;
- t['iivowelsigngujarati'] = 0x0AC0;
- t['ij'] = 0x0133;
- t['ikatakana'] = 0x30A4;
- t['ikatakanahalfwidth'] = 0xFF72;
- t['ikorean'] = 0x3163;
- t['ilde'] = 0x02DC;
- t['iluyhebrew'] = 0x05AC;
- t['imacron'] = 0x012B;
- t['imacroncyrillic'] = 0x04E3;
- t['imageorapproximatelyequal'] = 0x2253;
- t['imatragurmukhi'] = 0x0A3F;
- t['imonospace'] = 0xFF49;
- t['increment'] = 0x2206;
- t['infinity'] = 0x221E;
- t['iniarmenian'] = 0x056B;
- t['integral'] = 0x222B;
- t['integralbottom'] = 0x2321;
- t['integralbt'] = 0x2321;
- t['integralex'] = 0xF8F5;
- t['integraltop'] = 0x2320;
- t['integraltp'] = 0x2320;
- t['intersection'] = 0x2229;
- t['intisquare'] = 0x3305;
- t['invbullet'] = 0x25D8;
- t['invcircle'] = 0x25D9;
- t['invsmileface'] = 0x263B;
- t['iocyrillic'] = 0x0451;
- t['iogonek'] = 0x012F;
- t['iota'] = 0x03B9;
- t['iotadieresis'] = 0x03CA;
- t['iotadieresistonos'] = 0x0390;
- t['iotalatin'] = 0x0269;
- t['iotatonos'] = 0x03AF;
- t['iparen'] = 0x24A4;
- t['irigurmukhi'] = 0x0A72;
- t['ismallhiragana'] = 0x3043;
- t['ismallkatakana'] = 0x30A3;
- t['ismallkatakanahalfwidth'] = 0xFF68;
- t['issharbengali'] = 0x09FA;
- t['istroke'] = 0x0268;
- t['isuperior'] = 0xF6ED;
- t['iterationhiragana'] = 0x309D;
- t['iterationkatakana'] = 0x30FD;
- t['itilde'] = 0x0129;
- t['itildebelow'] = 0x1E2D;
- t['iubopomofo'] = 0x3129;
- t['iucyrillic'] = 0x044E;
- t['ivowelsignbengali'] = 0x09BF;
- t['ivowelsigndeva'] = 0x093F;
- t['ivowelsigngujarati'] = 0x0ABF;
- t['izhitsacyrillic'] = 0x0475;
- t['izhitsadblgravecyrillic'] = 0x0477;
- t['j'] = 0x006A;
- t['jaarmenian'] = 0x0571;
- t['jabengali'] = 0x099C;
- t['jadeva'] = 0x091C;
- t['jagujarati'] = 0x0A9C;
- t['jagurmukhi'] = 0x0A1C;
- t['jbopomofo'] = 0x3110;
- t['jcaron'] = 0x01F0;
- t['jcircle'] = 0x24D9;
- t['jcircumflex'] = 0x0135;
- t['jcrossedtail'] = 0x029D;
- t['jdotlessstroke'] = 0x025F;
- t['jecyrillic'] = 0x0458;
- t['jeemarabic'] = 0x062C;
- t['jeemfinalarabic'] = 0xFE9E;
- t['jeeminitialarabic'] = 0xFE9F;
- t['jeemmedialarabic'] = 0xFEA0;
- t['jeharabic'] = 0x0698;
- t['jehfinalarabic'] = 0xFB8B;
- t['jhabengali'] = 0x099D;
- t['jhadeva'] = 0x091D;
- t['jhagujarati'] = 0x0A9D;
- t['jhagurmukhi'] = 0x0A1D;
- t['jheharmenian'] = 0x057B;
- t['jis'] = 0x3004;
- t['jmonospace'] = 0xFF4A;
- t['jparen'] = 0x24A5;
- t['jsuperior'] = 0x02B2;
- t['k'] = 0x006B;
- t['kabashkircyrillic'] = 0x04A1;
- t['kabengali'] = 0x0995;
- t['kacute'] = 0x1E31;
- t['kacyrillic'] = 0x043A;
- t['kadescendercyrillic'] = 0x049B;
- t['kadeva'] = 0x0915;
- t['kaf'] = 0x05DB;
- t['kafarabic'] = 0x0643;
- t['kafdagesh'] = 0xFB3B;
- t['kafdageshhebrew'] = 0xFB3B;
- t['kaffinalarabic'] = 0xFEDA;
- t['kafhebrew'] = 0x05DB;
- t['kafinitialarabic'] = 0xFEDB;
- t['kafmedialarabic'] = 0xFEDC;
- t['kafrafehebrew'] = 0xFB4D;
- t['kagujarati'] = 0x0A95;
- t['kagurmukhi'] = 0x0A15;
- t['kahiragana'] = 0x304B;
- t['kahookcyrillic'] = 0x04C4;
- t['kakatakana'] = 0x30AB;
- t['kakatakanahalfwidth'] = 0xFF76;
- t['kappa'] = 0x03BA;
- t['kappasymbolgreek'] = 0x03F0;
- t['kapyeounmieumkorean'] = 0x3171;
- t['kapyeounphieuphkorean'] = 0x3184;
- t['kapyeounpieupkorean'] = 0x3178;
- t['kapyeounssangpieupkorean'] = 0x3179;
- t['karoriisquare'] = 0x330D;
- t['kashidaautoarabic'] = 0x0640;
- t['kashidaautonosidebearingarabic'] = 0x0640;
- t['kasmallkatakana'] = 0x30F5;
- t['kasquare'] = 0x3384;
- t['kasraarabic'] = 0x0650;
- t['kasratanarabic'] = 0x064D;
- t['kastrokecyrillic'] = 0x049F;
- t['katahiraprolongmarkhalfwidth'] = 0xFF70;
- t['kaverticalstrokecyrillic'] = 0x049D;
- t['kbopomofo'] = 0x310E;
- t['kcalsquare'] = 0x3389;
- t['kcaron'] = 0x01E9;
- t['kcedilla'] = 0x0137;
- t['kcircle'] = 0x24DA;
- t['kcommaaccent'] = 0x0137;
- t['kdotbelow'] = 0x1E33;
- t['keharmenian'] = 0x0584;
- t['kehiragana'] = 0x3051;
- t['kekatakana'] = 0x30B1;
- t['kekatakanahalfwidth'] = 0xFF79;
- t['kenarmenian'] = 0x056F;
- t['kesmallkatakana'] = 0x30F6;
- t['kgreenlandic'] = 0x0138;
- t['khabengali'] = 0x0996;
- t['khacyrillic'] = 0x0445;
- t['khadeva'] = 0x0916;
- t['khagujarati'] = 0x0A96;
- t['khagurmukhi'] = 0x0A16;
- t['khaharabic'] = 0x062E;
- t['khahfinalarabic'] = 0xFEA6;
- t['khahinitialarabic'] = 0xFEA7;
- t['khahmedialarabic'] = 0xFEA8;
- t['kheicoptic'] = 0x03E7;
- t['khhadeva'] = 0x0959;
- t['khhagurmukhi'] = 0x0A59;
- t['khieukhacirclekorean'] = 0x3278;
- t['khieukhaparenkorean'] = 0x3218;
- t['khieukhcirclekorean'] = 0x326A;
- t['khieukhkorean'] = 0x314B;
- t['khieukhparenkorean'] = 0x320A;
- t['khokhaithai'] = 0x0E02;
- t['khokhonthai'] = 0x0E05;
- t['khokhuatthai'] = 0x0E03;
- t['khokhwaithai'] = 0x0E04;
- t['khomutthai'] = 0x0E5B;
- t['khook'] = 0x0199;
- t['khorakhangthai'] = 0x0E06;
- t['khzsquare'] = 0x3391;
- t['kihiragana'] = 0x304D;
- t['kikatakana'] = 0x30AD;
- t['kikatakanahalfwidth'] = 0xFF77;
- t['kiroguramusquare'] = 0x3315;
- t['kiromeetorusquare'] = 0x3316;
- t['kirosquare'] = 0x3314;
- t['kiyeokacirclekorean'] = 0x326E;
- t['kiyeokaparenkorean'] = 0x320E;
- t['kiyeokcirclekorean'] = 0x3260;
- t['kiyeokkorean'] = 0x3131;
- t['kiyeokparenkorean'] = 0x3200;
- t['kiyeoksioskorean'] = 0x3133;
- t['kjecyrillic'] = 0x045C;
- t['klinebelow'] = 0x1E35;
- t['klsquare'] = 0x3398;
- t['kmcubedsquare'] = 0x33A6;
- t['kmonospace'] = 0xFF4B;
- t['kmsquaredsquare'] = 0x33A2;
- t['kohiragana'] = 0x3053;
- t['kohmsquare'] = 0x33C0;
- t['kokaithai'] = 0x0E01;
- t['kokatakana'] = 0x30B3;
- t['kokatakanahalfwidth'] = 0xFF7A;
- t['kooposquare'] = 0x331E;
- t['koppacyrillic'] = 0x0481;
- t['koreanstandardsymbol'] = 0x327F;
- t['koroniscmb'] = 0x0343;
- t['kparen'] = 0x24A6;
- t['kpasquare'] = 0x33AA;
- t['ksicyrillic'] = 0x046F;
- t['ktsquare'] = 0x33CF;
- t['kturned'] = 0x029E;
- t['kuhiragana'] = 0x304F;
- t['kukatakana'] = 0x30AF;
- t['kukatakanahalfwidth'] = 0xFF78;
- t['kvsquare'] = 0x33B8;
- t['kwsquare'] = 0x33BE;
- t['l'] = 0x006C;
- t['labengali'] = 0x09B2;
- t['lacute'] = 0x013A;
- t['ladeva'] = 0x0932;
- t['lagujarati'] = 0x0AB2;
- t['lagurmukhi'] = 0x0A32;
- t['lakkhangyaothai'] = 0x0E45;
- t['lamaleffinalarabic'] = 0xFEFC;
- t['lamalefhamzaabovefinalarabic'] = 0xFEF8;
- t['lamalefhamzaaboveisolatedarabic'] = 0xFEF7;
- t['lamalefhamzabelowfinalarabic'] = 0xFEFA;
- t['lamalefhamzabelowisolatedarabic'] = 0xFEF9;
- t['lamalefisolatedarabic'] = 0xFEFB;
- t['lamalefmaddaabovefinalarabic'] = 0xFEF6;
- t['lamalefmaddaaboveisolatedarabic'] = 0xFEF5;
- t['lamarabic'] = 0x0644;
- t['lambda'] = 0x03BB;
- t['lambdastroke'] = 0x019B;
- t['lamed'] = 0x05DC;
- t['lameddagesh'] = 0xFB3C;
- t['lameddageshhebrew'] = 0xFB3C;
- t['lamedhebrew'] = 0x05DC;
- t['lamfinalarabic'] = 0xFEDE;
- t['lamhahinitialarabic'] = 0xFCCA;
- t['laminitialarabic'] = 0xFEDF;
- t['lamjeeminitialarabic'] = 0xFCC9;
- t['lamkhahinitialarabic'] = 0xFCCB;
- t['lamlamhehisolatedarabic'] = 0xFDF2;
- t['lammedialarabic'] = 0xFEE0;
- t['lammeemhahinitialarabic'] = 0xFD88;
- t['lammeeminitialarabic'] = 0xFCCC;
- t['largecircle'] = 0x25EF;
- t['lbar'] = 0x019A;
- t['lbelt'] = 0x026C;
- t['lbopomofo'] = 0x310C;
- t['lcaron'] = 0x013E;
- t['lcedilla'] = 0x013C;
- t['lcircle'] = 0x24DB;
- t['lcircumflexbelow'] = 0x1E3D;
- t['lcommaaccent'] = 0x013C;
- t['ldot'] = 0x0140;
- t['ldotaccent'] = 0x0140;
- t['ldotbelow'] = 0x1E37;
- t['ldotbelowmacron'] = 0x1E39;
- t['leftangleabovecmb'] = 0x031A;
- t['lefttackbelowcmb'] = 0x0318;
- t['less'] = 0x003C;
- t['lessequal'] = 0x2264;
- t['lessequalorgreater'] = 0x22DA;
- t['lessmonospace'] = 0xFF1C;
- t['lessorequivalent'] = 0x2272;
- t['lessorgreater'] = 0x2276;
- t['lessoverequal'] = 0x2266;
- t['lesssmall'] = 0xFE64;
- t['lezh'] = 0x026E;
- t['lfblock'] = 0x258C;
- t['lhookretroflex'] = 0x026D;
- t['lira'] = 0x20A4;
- t['liwnarmenian'] = 0x056C;
- t['lj'] = 0x01C9;
- t['ljecyrillic'] = 0x0459;
- t['ll'] = 0xF6C0;
- t['lladeva'] = 0x0933;
- t['llagujarati'] = 0x0AB3;
- t['llinebelow'] = 0x1E3B;
- t['llladeva'] = 0x0934;
- t['llvocalicbengali'] = 0x09E1;
- t['llvocalicdeva'] = 0x0961;
- t['llvocalicvowelsignbengali'] = 0x09E3;
- t['llvocalicvowelsigndeva'] = 0x0963;
- t['lmiddletilde'] = 0x026B;
- t['lmonospace'] = 0xFF4C;
- t['lmsquare'] = 0x33D0;
- t['lochulathai'] = 0x0E2C;
- t['logicaland'] = 0x2227;
- t['logicalnot'] = 0x00AC;
- t['logicalnotreversed'] = 0x2310;
- t['logicalor'] = 0x2228;
- t['lolingthai'] = 0x0E25;
- t['longs'] = 0x017F;
- t['lowlinecenterline'] = 0xFE4E;
- t['lowlinecmb'] = 0x0332;
- t['lowlinedashed'] = 0xFE4D;
- t['lozenge'] = 0x25CA;
- t['lparen'] = 0x24A7;
- t['lslash'] = 0x0142;
- t['lsquare'] = 0x2113;
- t['lsuperior'] = 0xF6EE;
- t['ltshade'] = 0x2591;
- t['luthai'] = 0x0E26;
- t['lvocalicbengali'] = 0x098C;
- t['lvocalicdeva'] = 0x090C;
- t['lvocalicvowelsignbengali'] = 0x09E2;
- t['lvocalicvowelsigndeva'] = 0x0962;
- t['lxsquare'] = 0x33D3;
- t['m'] = 0x006D;
- t['mabengali'] = 0x09AE;
- t['macron'] = 0x00AF;
- t['macronbelowcmb'] = 0x0331;
- t['macroncmb'] = 0x0304;
- t['macronlowmod'] = 0x02CD;
- t['macronmonospace'] = 0xFFE3;
- t['macute'] = 0x1E3F;
- t['madeva'] = 0x092E;
- t['magujarati'] = 0x0AAE;
- t['magurmukhi'] = 0x0A2E;
- t['mahapakhhebrew'] = 0x05A4;
- t['mahapakhlefthebrew'] = 0x05A4;
- t['mahiragana'] = 0x307E;
- t['maichattawalowleftthai'] = 0xF895;
- t['maichattawalowrightthai'] = 0xF894;
- t['maichattawathai'] = 0x0E4B;
- t['maichattawaupperleftthai'] = 0xF893;
- t['maieklowleftthai'] = 0xF88C;
- t['maieklowrightthai'] = 0xF88B;
- t['maiekthai'] = 0x0E48;
- t['maiekupperleftthai'] = 0xF88A;
- t['maihanakatleftthai'] = 0xF884;
- t['maihanakatthai'] = 0x0E31;
- t['maitaikhuleftthai'] = 0xF889;
- t['maitaikhuthai'] = 0x0E47;
- t['maitholowleftthai'] = 0xF88F;
- t['maitholowrightthai'] = 0xF88E;
- t['maithothai'] = 0x0E49;
- t['maithoupperleftthai'] = 0xF88D;
- t['maitrilowleftthai'] = 0xF892;
- t['maitrilowrightthai'] = 0xF891;
- t['maitrithai'] = 0x0E4A;
- t['maitriupperleftthai'] = 0xF890;
- t['maiyamokthai'] = 0x0E46;
- t['makatakana'] = 0x30DE;
- t['makatakanahalfwidth'] = 0xFF8F;
- t['male'] = 0x2642;
- t['mansyonsquare'] = 0x3347;
- t['maqafhebrew'] = 0x05BE;
- t['mars'] = 0x2642;
- t['masoracirclehebrew'] = 0x05AF;
- t['masquare'] = 0x3383;
- t['mbopomofo'] = 0x3107;
- t['mbsquare'] = 0x33D4;
- t['mcircle'] = 0x24DC;
- t['mcubedsquare'] = 0x33A5;
- t['mdotaccent'] = 0x1E41;
- t['mdotbelow'] = 0x1E43;
- t['meemarabic'] = 0x0645;
- t['meemfinalarabic'] = 0xFEE2;
- t['meeminitialarabic'] = 0xFEE3;
- t['meemmedialarabic'] = 0xFEE4;
- t['meemmeeminitialarabic'] = 0xFCD1;
- t['meemmeemisolatedarabic'] = 0xFC48;
- t['meetorusquare'] = 0x334D;
- t['mehiragana'] = 0x3081;
- t['meizierasquare'] = 0x337E;
- t['mekatakana'] = 0x30E1;
- t['mekatakanahalfwidth'] = 0xFF92;
- t['mem'] = 0x05DE;
- t['memdagesh'] = 0xFB3E;
- t['memdageshhebrew'] = 0xFB3E;
- t['memhebrew'] = 0x05DE;
- t['menarmenian'] = 0x0574;
- t['merkhahebrew'] = 0x05A5;
- t['merkhakefulahebrew'] = 0x05A6;
- t['merkhakefulalefthebrew'] = 0x05A6;
- t['merkhalefthebrew'] = 0x05A5;
- t['mhook'] = 0x0271;
- t['mhzsquare'] = 0x3392;
- t['middledotkatakanahalfwidth'] = 0xFF65;
- t['middot'] = 0x00B7;
- t['mieumacirclekorean'] = 0x3272;
- t['mieumaparenkorean'] = 0x3212;
- t['mieumcirclekorean'] = 0x3264;
- t['mieumkorean'] = 0x3141;
- t['mieumpansioskorean'] = 0x3170;
- t['mieumparenkorean'] = 0x3204;
- t['mieumpieupkorean'] = 0x316E;
- t['mieumsioskorean'] = 0x316F;
- t['mihiragana'] = 0x307F;
- t['mikatakana'] = 0x30DF;
- t['mikatakanahalfwidth'] = 0xFF90;
- t['minus'] = 0x2212;
- t['minusbelowcmb'] = 0x0320;
- t['minuscircle'] = 0x2296;
- t['minusmod'] = 0x02D7;
- t['minusplus'] = 0x2213;
- t['minute'] = 0x2032;
- t['miribaarusquare'] = 0x334A;
- t['mirisquare'] = 0x3349;
- t['mlonglegturned'] = 0x0270;
- t['mlsquare'] = 0x3396;
- t['mmcubedsquare'] = 0x33A3;
- t['mmonospace'] = 0xFF4D;
- t['mmsquaredsquare'] = 0x339F;
- t['mohiragana'] = 0x3082;
- t['mohmsquare'] = 0x33C1;
- t['mokatakana'] = 0x30E2;
- t['mokatakanahalfwidth'] = 0xFF93;
- t['molsquare'] = 0x33D6;
- t['momathai'] = 0x0E21;
- t['moverssquare'] = 0x33A7;
- t['moverssquaredsquare'] = 0x33A8;
- t['mparen'] = 0x24A8;
- t['mpasquare'] = 0x33AB;
- t['mssquare'] = 0x33B3;
- t['msuperior'] = 0xF6EF;
- t['mturned'] = 0x026F;
- t['mu'] = 0x00B5;
- t['mu1'] = 0x00B5;
- t['muasquare'] = 0x3382;
- t['muchgreater'] = 0x226B;
- t['muchless'] = 0x226A;
- t['mufsquare'] = 0x338C;
- t['mugreek'] = 0x03BC;
- t['mugsquare'] = 0x338D;
- t['muhiragana'] = 0x3080;
- t['mukatakana'] = 0x30E0;
- t['mukatakanahalfwidth'] = 0xFF91;
- t['mulsquare'] = 0x3395;
- t['multiply'] = 0x00D7;
- t['mumsquare'] = 0x339B;
- t['munahhebrew'] = 0x05A3;
- t['munahlefthebrew'] = 0x05A3;
- t['musicalnote'] = 0x266A;
- t['musicalnotedbl'] = 0x266B;
- t['musicflatsign'] = 0x266D;
- t['musicsharpsign'] = 0x266F;
- t['mussquare'] = 0x33B2;
- t['muvsquare'] = 0x33B6;
- t['muwsquare'] = 0x33BC;
- t['mvmegasquare'] = 0x33B9;
- t['mvsquare'] = 0x33B7;
- t['mwmegasquare'] = 0x33BF;
- t['mwsquare'] = 0x33BD;
- t['n'] = 0x006E;
- t['nabengali'] = 0x09A8;
- t['nabla'] = 0x2207;
- t['nacute'] = 0x0144;
- t['nadeva'] = 0x0928;
- t['nagujarati'] = 0x0AA8;
- t['nagurmukhi'] = 0x0A28;
- t['nahiragana'] = 0x306A;
- t['nakatakana'] = 0x30CA;
- t['nakatakanahalfwidth'] = 0xFF85;
- t['napostrophe'] = 0x0149;
- t['nasquare'] = 0x3381;
- t['nbopomofo'] = 0x310B;
- t['nbspace'] = 0x00A0;
- t['ncaron'] = 0x0148;
- t['ncedilla'] = 0x0146;
- t['ncircle'] = 0x24DD;
- t['ncircumflexbelow'] = 0x1E4B;
- t['ncommaaccent'] = 0x0146;
- t['ndotaccent'] = 0x1E45;
- t['ndotbelow'] = 0x1E47;
- t['nehiragana'] = 0x306D;
- t['nekatakana'] = 0x30CD;
- t['nekatakanahalfwidth'] = 0xFF88;
- t['newsheqelsign'] = 0x20AA;
- t['nfsquare'] = 0x338B;
- t['ngabengali'] = 0x0999;
- t['ngadeva'] = 0x0919;
- t['ngagujarati'] = 0x0A99;
- t['ngagurmukhi'] = 0x0A19;
- t['ngonguthai'] = 0x0E07;
- t['nhiragana'] = 0x3093;
- t['nhookleft'] = 0x0272;
- t['nhookretroflex'] = 0x0273;
- t['nieunacirclekorean'] = 0x326F;
- t['nieunaparenkorean'] = 0x320F;
- t['nieuncieuckorean'] = 0x3135;
- t['nieuncirclekorean'] = 0x3261;
- t['nieunhieuhkorean'] = 0x3136;
- t['nieunkorean'] = 0x3134;
- t['nieunpansioskorean'] = 0x3168;
- t['nieunparenkorean'] = 0x3201;
- t['nieunsioskorean'] = 0x3167;
- t['nieuntikeutkorean'] = 0x3166;
- t['nihiragana'] = 0x306B;
- t['nikatakana'] = 0x30CB;
- t['nikatakanahalfwidth'] = 0xFF86;
- t['nikhahitleftthai'] = 0xF899;
- t['nikhahitthai'] = 0x0E4D;
- t['nine'] = 0x0039;
- t['ninearabic'] = 0x0669;
- t['ninebengali'] = 0x09EF;
- t['ninecircle'] = 0x2468;
- t['ninecircleinversesansserif'] = 0x2792;
- t['ninedeva'] = 0x096F;
- t['ninegujarati'] = 0x0AEF;
- t['ninegurmukhi'] = 0x0A6F;
- t['ninehackarabic'] = 0x0669;
- t['ninehangzhou'] = 0x3029;
- t['nineideographicparen'] = 0x3228;
- t['nineinferior'] = 0x2089;
- t['ninemonospace'] = 0xFF19;
- t['nineoldstyle'] = 0xF739;
- t['nineparen'] = 0x247C;
- t['nineperiod'] = 0x2490;
- t['ninepersian'] = 0x06F9;
- t['nineroman'] = 0x2178;
- t['ninesuperior'] = 0x2079;
- t['nineteencircle'] = 0x2472;
- t['nineteenparen'] = 0x2486;
- t['nineteenperiod'] = 0x249A;
- t['ninethai'] = 0x0E59;
- t['nj'] = 0x01CC;
- t['njecyrillic'] = 0x045A;
- t['nkatakana'] = 0x30F3;
- t['nkatakanahalfwidth'] = 0xFF9D;
- t['nlegrightlong'] = 0x019E;
- t['nlinebelow'] = 0x1E49;
- t['nmonospace'] = 0xFF4E;
- t['nmsquare'] = 0x339A;
- t['nnabengali'] = 0x09A3;
- t['nnadeva'] = 0x0923;
- t['nnagujarati'] = 0x0AA3;
- t['nnagurmukhi'] = 0x0A23;
- t['nnnadeva'] = 0x0929;
- t['nohiragana'] = 0x306E;
- t['nokatakana'] = 0x30CE;
- t['nokatakanahalfwidth'] = 0xFF89;
- t['nonbreakingspace'] = 0x00A0;
- t['nonenthai'] = 0x0E13;
- t['nonuthai'] = 0x0E19;
- t['noonarabic'] = 0x0646;
- t['noonfinalarabic'] = 0xFEE6;
- t['noonghunnaarabic'] = 0x06BA;
- t['noonghunnafinalarabic'] = 0xFB9F;
- t['nooninitialarabic'] = 0xFEE7;
- t['noonjeeminitialarabic'] = 0xFCD2;
- t['noonjeemisolatedarabic'] = 0xFC4B;
- t['noonmedialarabic'] = 0xFEE8;
- t['noonmeeminitialarabic'] = 0xFCD5;
- t['noonmeemisolatedarabic'] = 0xFC4E;
- t['noonnoonfinalarabic'] = 0xFC8D;
- t['notcontains'] = 0x220C;
- t['notelement'] = 0x2209;
- t['notelementof'] = 0x2209;
- t['notequal'] = 0x2260;
- t['notgreater'] = 0x226F;
- t['notgreaternorequal'] = 0x2271;
- t['notgreaternorless'] = 0x2279;
- t['notidentical'] = 0x2262;
- t['notless'] = 0x226E;
- t['notlessnorequal'] = 0x2270;
- t['notparallel'] = 0x2226;
- t['notprecedes'] = 0x2280;
- t['notsubset'] = 0x2284;
- t['notsucceeds'] = 0x2281;
- t['notsuperset'] = 0x2285;
- t['nowarmenian'] = 0x0576;
- t['nparen'] = 0x24A9;
- t['nssquare'] = 0x33B1;
- t['nsuperior'] = 0x207F;
- t['ntilde'] = 0x00F1;
- t['nu'] = 0x03BD;
- t['nuhiragana'] = 0x306C;
- t['nukatakana'] = 0x30CC;
- t['nukatakanahalfwidth'] = 0xFF87;
- t['nuktabengali'] = 0x09BC;
- t['nuktadeva'] = 0x093C;
- t['nuktagujarati'] = 0x0ABC;
- t['nuktagurmukhi'] = 0x0A3C;
- t['numbersign'] = 0x0023;
- t['numbersignmonospace'] = 0xFF03;
- t['numbersignsmall'] = 0xFE5F;
- t['numeralsigngreek'] = 0x0374;
- t['numeralsignlowergreek'] = 0x0375;
- t['numero'] = 0x2116;
- t['nun'] = 0x05E0;
- t['nundagesh'] = 0xFB40;
- t['nundageshhebrew'] = 0xFB40;
- t['nunhebrew'] = 0x05E0;
- t['nvsquare'] = 0x33B5;
- t['nwsquare'] = 0x33BB;
- t['nyabengali'] = 0x099E;
- t['nyadeva'] = 0x091E;
- t['nyagujarati'] = 0x0A9E;
- t['nyagurmukhi'] = 0x0A1E;
- t['o'] = 0x006F;
- t['oacute'] = 0x00F3;
- t['oangthai'] = 0x0E2D;
- t['obarred'] = 0x0275;
- t['obarredcyrillic'] = 0x04E9;
- t['obarreddieresiscyrillic'] = 0x04EB;
- t['obengali'] = 0x0993;
- t['obopomofo'] = 0x311B;
- t['obreve'] = 0x014F;
- t['ocandradeva'] = 0x0911;
- t['ocandragujarati'] = 0x0A91;
- t['ocandravowelsigndeva'] = 0x0949;
- t['ocandravowelsigngujarati'] = 0x0AC9;
- t['ocaron'] = 0x01D2;
- t['ocircle'] = 0x24DE;
- t['ocircumflex'] = 0x00F4;
- t['ocircumflexacute'] = 0x1ED1;
- t['ocircumflexdotbelow'] = 0x1ED9;
- t['ocircumflexgrave'] = 0x1ED3;
- t['ocircumflexhookabove'] = 0x1ED5;
- t['ocircumflextilde'] = 0x1ED7;
- t['ocyrillic'] = 0x043E;
- t['odblacute'] = 0x0151;
- t['odblgrave'] = 0x020D;
- t['odeva'] = 0x0913;
- t['odieresis'] = 0x00F6;
- t['odieresiscyrillic'] = 0x04E7;
- t['odotbelow'] = 0x1ECD;
- t['oe'] = 0x0153;
- t['oekorean'] = 0x315A;
- t['ogonek'] = 0x02DB;
- t['ogonekcmb'] = 0x0328;
- t['ograve'] = 0x00F2;
- t['ogujarati'] = 0x0A93;
- t['oharmenian'] = 0x0585;
- t['ohiragana'] = 0x304A;
- t['ohookabove'] = 0x1ECF;
- t['ohorn'] = 0x01A1;
- t['ohornacute'] = 0x1EDB;
- t['ohorndotbelow'] = 0x1EE3;
- t['ohorngrave'] = 0x1EDD;
- t['ohornhookabove'] = 0x1EDF;
- t['ohorntilde'] = 0x1EE1;
- t['ohungarumlaut'] = 0x0151;
- t['oi'] = 0x01A3;
- t['oinvertedbreve'] = 0x020F;
- t['okatakana'] = 0x30AA;
- t['okatakanahalfwidth'] = 0xFF75;
- t['okorean'] = 0x3157;
- t['olehebrew'] = 0x05AB;
- t['omacron'] = 0x014D;
- t['omacronacute'] = 0x1E53;
- t['omacrongrave'] = 0x1E51;
- t['omdeva'] = 0x0950;
- t['omega'] = 0x03C9;
- t['omega1'] = 0x03D6;
- t['omegacyrillic'] = 0x0461;
- t['omegalatinclosed'] = 0x0277;
- t['omegaroundcyrillic'] = 0x047B;
- t['omegatitlocyrillic'] = 0x047D;
- t['omegatonos'] = 0x03CE;
- t['omgujarati'] = 0x0AD0;
- t['omicron'] = 0x03BF;
- t['omicrontonos'] = 0x03CC;
- t['omonospace'] = 0xFF4F;
- t['one'] = 0x0031;
- t['onearabic'] = 0x0661;
- t['onebengali'] = 0x09E7;
- t['onecircle'] = 0x2460;
- t['onecircleinversesansserif'] = 0x278A;
- t['onedeva'] = 0x0967;
- t['onedotenleader'] = 0x2024;
- t['oneeighth'] = 0x215B;
- t['onefitted'] = 0xF6DC;
- t['onegujarati'] = 0x0AE7;
- t['onegurmukhi'] = 0x0A67;
- t['onehackarabic'] = 0x0661;
- t['onehalf'] = 0x00BD;
- t['onehangzhou'] = 0x3021;
- t['oneideographicparen'] = 0x3220;
- t['oneinferior'] = 0x2081;
- t['onemonospace'] = 0xFF11;
- t['onenumeratorbengali'] = 0x09F4;
- t['oneoldstyle'] = 0xF731;
- t['oneparen'] = 0x2474;
- t['oneperiod'] = 0x2488;
- t['onepersian'] = 0x06F1;
- t['onequarter'] = 0x00BC;
- t['oneroman'] = 0x2170;
- t['onesuperior'] = 0x00B9;
- t['onethai'] = 0x0E51;
- t['onethird'] = 0x2153;
- t['oogonek'] = 0x01EB;
- t['oogonekmacron'] = 0x01ED;
- t['oogurmukhi'] = 0x0A13;
- t['oomatragurmukhi'] = 0x0A4B;
- t['oopen'] = 0x0254;
- t['oparen'] = 0x24AA;
- t['openbullet'] = 0x25E6;
- t['option'] = 0x2325;
- t['ordfeminine'] = 0x00AA;
- t['ordmasculine'] = 0x00BA;
- t['orthogonal'] = 0x221F;
- t['oshortdeva'] = 0x0912;
- t['oshortvowelsigndeva'] = 0x094A;
- t['oslash'] = 0x00F8;
- t['oslashacute'] = 0x01FF;
- t['osmallhiragana'] = 0x3049;
- t['osmallkatakana'] = 0x30A9;
- t['osmallkatakanahalfwidth'] = 0xFF6B;
- t['ostrokeacute'] = 0x01FF;
- t['osuperior'] = 0xF6F0;
- t['otcyrillic'] = 0x047F;
- t['otilde'] = 0x00F5;
- t['otildeacute'] = 0x1E4D;
- t['otildedieresis'] = 0x1E4F;
- t['oubopomofo'] = 0x3121;
- t['overline'] = 0x203E;
- t['overlinecenterline'] = 0xFE4A;
- t['overlinecmb'] = 0x0305;
- t['overlinedashed'] = 0xFE49;
- t['overlinedblwavy'] = 0xFE4C;
- t['overlinewavy'] = 0xFE4B;
- t['overscore'] = 0x00AF;
- t['ovowelsignbengali'] = 0x09CB;
- t['ovowelsigndeva'] = 0x094B;
- t['ovowelsigngujarati'] = 0x0ACB;
- t['p'] = 0x0070;
- t['paampssquare'] = 0x3380;
- t['paasentosquare'] = 0x332B;
- t['pabengali'] = 0x09AA;
- t['pacute'] = 0x1E55;
- t['padeva'] = 0x092A;
- t['pagedown'] = 0x21DF;
- t['pageup'] = 0x21DE;
- t['pagujarati'] = 0x0AAA;
- t['pagurmukhi'] = 0x0A2A;
- t['pahiragana'] = 0x3071;
- t['paiyannoithai'] = 0x0E2F;
- t['pakatakana'] = 0x30D1;
- t['palatalizationcyrilliccmb'] = 0x0484;
- t['palochkacyrillic'] = 0x04C0;
- t['pansioskorean'] = 0x317F;
- t['paragraph'] = 0x00B6;
- t['parallel'] = 0x2225;
- t['parenleft'] = 0x0028;
- t['parenleftaltonearabic'] = 0xFD3E;
- t['parenleftbt'] = 0xF8ED;
- t['parenleftex'] = 0xF8EC;
- t['parenleftinferior'] = 0x208D;
- t['parenleftmonospace'] = 0xFF08;
- t['parenleftsmall'] = 0xFE59;
- t['parenleftsuperior'] = 0x207D;
- t['parenlefttp'] = 0xF8EB;
- t['parenleftvertical'] = 0xFE35;
- t['parenright'] = 0x0029;
- t['parenrightaltonearabic'] = 0xFD3F;
- t['parenrightbt'] = 0xF8F8;
- t['parenrightex'] = 0xF8F7;
- t['parenrightinferior'] = 0x208E;
- t['parenrightmonospace'] = 0xFF09;
- t['parenrightsmall'] = 0xFE5A;
- t['parenrightsuperior'] = 0x207E;
- t['parenrighttp'] = 0xF8F6;
- t['parenrightvertical'] = 0xFE36;
- t['partialdiff'] = 0x2202;
- t['paseqhebrew'] = 0x05C0;
- t['pashtahebrew'] = 0x0599;
- t['pasquare'] = 0x33A9;
- t['patah'] = 0x05B7;
- t['patah11'] = 0x05B7;
- t['patah1d'] = 0x05B7;
- t['patah2a'] = 0x05B7;
- t['patahhebrew'] = 0x05B7;
- t['patahnarrowhebrew'] = 0x05B7;
- t['patahquarterhebrew'] = 0x05B7;
- t['patahwidehebrew'] = 0x05B7;
- t['pazerhebrew'] = 0x05A1;
- t['pbopomofo'] = 0x3106;
- t['pcircle'] = 0x24DF;
- t['pdotaccent'] = 0x1E57;
- t['pe'] = 0x05E4;
- t['pecyrillic'] = 0x043F;
- t['pedagesh'] = 0xFB44;
- t['pedageshhebrew'] = 0xFB44;
- t['peezisquare'] = 0x333B;
- t['pefinaldageshhebrew'] = 0xFB43;
- t['peharabic'] = 0x067E;
- t['peharmenian'] = 0x057A;
- t['pehebrew'] = 0x05E4;
- t['pehfinalarabic'] = 0xFB57;
- t['pehinitialarabic'] = 0xFB58;
- t['pehiragana'] = 0x307A;
- t['pehmedialarabic'] = 0xFB59;
- t['pekatakana'] = 0x30DA;
- t['pemiddlehookcyrillic'] = 0x04A7;
- t['perafehebrew'] = 0xFB4E;
- t['percent'] = 0x0025;
- t['percentarabic'] = 0x066A;
- t['percentmonospace'] = 0xFF05;
- t['percentsmall'] = 0xFE6A;
- t['period'] = 0x002E;
- t['periodarmenian'] = 0x0589;
- t['periodcentered'] = 0x00B7;
- t['periodhalfwidth'] = 0xFF61;
- t['periodinferior'] = 0xF6E7;
- t['periodmonospace'] = 0xFF0E;
- t['periodsmall'] = 0xFE52;
- t['periodsuperior'] = 0xF6E8;
- t['perispomenigreekcmb'] = 0x0342;
- t['perpendicular'] = 0x22A5;
- t['perthousand'] = 0x2030;
- t['peseta'] = 0x20A7;
- t['pfsquare'] = 0x338A;
- t['phabengali'] = 0x09AB;
- t['phadeva'] = 0x092B;
- t['phagujarati'] = 0x0AAB;
- t['phagurmukhi'] = 0x0A2B;
- t['phi'] = 0x03C6;
- t['phi1'] = 0x03D5;
- t['phieuphacirclekorean'] = 0x327A;
- t['phieuphaparenkorean'] = 0x321A;
- t['phieuphcirclekorean'] = 0x326C;
- t['phieuphkorean'] = 0x314D;
- t['phieuphparenkorean'] = 0x320C;
- t['philatin'] = 0x0278;
- t['phinthuthai'] = 0x0E3A;
- t['phisymbolgreek'] = 0x03D5;
- t['phook'] = 0x01A5;
- t['phophanthai'] = 0x0E1E;
- t['phophungthai'] = 0x0E1C;
- t['phosamphaothai'] = 0x0E20;
- t['pi'] = 0x03C0;
- t['pieupacirclekorean'] = 0x3273;
- t['pieupaparenkorean'] = 0x3213;
- t['pieupcieuckorean'] = 0x3176;
- t['pieupcirclekorean'] = 0x3265;
- t['pieupkiyeokkorean'] = 0x3172;
- t['pieupkorean'] = 0x3142;
- t['pieupparenkorean'] = 0x3205;
- t['pieupsioskiyeokkorean'] = 0x3174;
- t['pieupsioskorean'] = 0x3144;
- t['pieupsiostikeutkorean'] = 0x3175;
- t['pieupthieuthkorean'] = 0x3177;
- t['pieuptikeutkorean'] = 0x3173;
- t['pihiragana'] = 0x3074;
- t['pikatakana'] = 0x30D4;
- t['pisymbolgreek'] = 0x03D6;
- t['piwrarmenian'] = 0x0583;
- t['plus'] = 0x002B;
- t['plusbelowcmb'] = 0x031F;
- t['pluscircle'] = 0x2295;
- t['plusminus'] = 0x00B1;
- t['plusmod'] = 0x02D6;
- t['plusmonospace'] = 0xFF0B;
- t['plussmall'] = 0xFE62;
- t['plussuperior'] = 0x207A;
- t['pmonospace'] = 0xFF50;
- t['pmsquare'] = 0x33D8;
- t['pohiragana'] = 0x307D;
- t['pointingindexdownwhite'] = 0x261F;
- t['pointingindexleftwhite'] = 0x261C;
- t['pointingindexrightwhite'] = 0x261E;
- t['pointingindexupwhite'] = 0x261D;
- t['pokatakana'] = 0x30DD;
- t['poplathai'] = 0x0E1B;
- t['postalmark'] = 0x3012;
- t['postalmarkface'] = 0x3020;
- t['pparen'] = 0x24AB;
- t['precedes'] = 0x227A;
- t['prescription'] = 0x211E;
- t['primemod'] = 0x02B9;
- t['primereversed'] = 0x2035;
- t['product'] = 0x220F;
- t['projective'] = 0x2305;
- t['prolongedkana'] = 0x30FC;
- t['propellor'] = 0x2318;
- t['propersubset'] = 0x2282;
- t['propersuperset'] = 0x2283;
- t['proportion'] = 0x2237;
- t['proportional'] = 0x221D;
- t['psi'] = 0x03C8;
- t['psicyrillic'] = 0x0471;
- t['psilipneumatacyrilliccmb'] = 0x0486;
- t['pssquare'] = 0x33B0;
- t['puhiragana'] = 0x3077;
- t['pukatakana'] = 0x30D7;
- t['pvsquare'] = 0x33B4;
- t['pwsquare'] = 0x33BA;
- t['q'] = 0x0071;
- t['qadeva'] = 0x0958;
- t['qadmahebrew'] = 0x05A8;
- t['qafarabic'] = 0x0642;
- t['qaffinalarabic'] = 0xFED6;
- t['qafinitialarabic'] = 0xFED7;
- t['qafmedialarabic'] = 0xFED8;
- t['qamats'] = 0x05B8;
- t['qamats10'] = 0x05B8;
- t['qamats1a'] = 0x05B8;
- t['qamats1c'] = 0x05B8;
- t['qamats27'] = 0x05B8;
- t['qamats29'] = 0x05B8;
- t['qamats33'] = 0x05B8;
- t['qamatsde'] = 0x05B8;
- t['qamatshebrew'] = 0x05B8;
- t['qamatsnarrowhebrew'] = 0x05B8;
- t['qamatsqatanhebrew'] = 0x05B8;
- t['qamatsqatannarrowhebrew'] = 0x05B8;
- t['qamatsqatanquarterhebrew'] = 0x05B8;
- t['qamatsqatanwidehebrew'] = 0x05B8;
- t['qamatsquarterhebrew'] = 0x05B8;
- t['qamatswidehebrew'] = 0x05B8;
- t['qarneyparahebrew'] = 0x059F;
- t['qbopomofo'] = 0x3111;
- t['qcircle'] = 0x24E0;
- t['qhook'] = 0x02A0;
- t['qmonospace'] = 0xFF51;
- t['qof'] = 0x05E7;
- t['qofdagesh'] = 0xFB47;
- t['qofdageshhebrew'] = 0xFB47;
- t['qofhebrew'] = 0x05E7;
- t['qparen'] = 0x24AC;
- t['quarternote'] = 0x2669;
- t['qubuts'] = 0x05BB;
- t['qubuts18'] = 0x05BB;
- t['qubuts25'] = 0x05BB;
- t['qubuts31'] = 0x05BB;
- t['qubutshebrew'] = 0x05BB;
- t['qubutsnarrowhebrew'] = 0x05BB;
- t['qubutsquarterhebrew'] = 0x05BB;
- t['qubutswidehebrew'] = 0x05BB;
- t['question'] = 0x003F;
- t['questionarabic'] = 0x061F;
- t['questionarmenian'] = 0x055E;
- t['questiondown'] = 0x00BF;
- t['questiondownsmall'] = 0xF7BF;
- t['questiongreek'] = 0x037E;
- t['questionmonospace'] = 0xFF1F;
- t['questionsmall'] = 0xF73F;
- t['quotedbl'] = 0x0022;
- t['quotedblbase'] = 0x201E;
- t['quotedblleft'] = 0x201C;
- t['quotedblmonospace'] = 0xFF02;
- t['quotedblprime'] = 0x301E;
- t['quotedblprimereversed'] = 0x301D;
- t['quotedblright'] = 0x201D;
- t['quoteleft'] = 0x2018;
- t['quoteleftreversed'] = 0x201B;
- t['quotereversed'] = 0x201B;
- t['quoteright'] = 0x2019;
- t['quoterightn'] = 0x0149;
- t['quotesinglbase'] = 0x201A;
- t['quotesingle'] = 0x0027;
- t['quotesinglemonospace'] = 0xFF07;
- t['r'] = 0x0072;
- t['raarmenian'] = 0x057C;
- t['rabengali'] = 0x09B0;
- t['racute'] = 0x0155;
- t['radeva'] = 0x0930;
- t['radical'] = 0x221A;
- t['radicalex'] = 0xF8E5;
- t['radoverssquare'] = 0x33AE;
- t['radoverssquaredsquare'] = 0x33AF;
- t['radsquare'] = 0x33AD;
- t['rafe'] = 0x05BF;
- t['rafehebrew'] = 0x05BF;
- t['ragujarati'] = 0x0AB0;
- t['ragurmukhi'] = 0x0A30;
- t['rahiragana'] = 0x3089;
- t['rakatakana'] = 0x30E9;
- t['rakatakanahalfwidth'] = 0xFF97;
- t['ralowerdiagonalbengali'] = 0x09F1;
- t['ramiddlediagonalbengali'] = 0x09F0;
- t['ramshorn'] = 0x0264;
- t['ratio'] = 0x2236;
- t['rbopomofo'] = 0x3116;
- t['rcaron'] = 0x0159;
- t['rcedilla'] = 0x0157;
- t['rcircle'] = 0x24E1;
- t['rcommaaccent'] = 0x0157;
- t['rdblgrave'] = 0x0211;
- t['rdotaccent'] = 0x1E59;
- t['rdotbelow'] = 0x1E5B;
- t['rdotbelowmacron'] = 0x1E5D;
- t['referencemark'] = 0x203B;
- t['reflexsubset'] = 0x2286;
- t['reflexsuperset'] = 0x2287;
- t['registered'] = 0x00AE;
- t['registersans'] = 0xF8E8;
- t['registerserif'] = 0xF6DA;
- t['reharabic'] = 0x0631;
- t['reharmenian'] = 0x0580;
- t['rehfinalarabic'] = 0xFEAE;
- t['rehiragana'] = 0x308C;
- t['rekatakana'] = 0x30EC;
- t['rekatakanahalfwidth'] = 0xFF9A;
- t['resh'] = 0x05E8;
- t['reshdageshhebrew'] = 0xFB48;
- t['reshhebrew'] = 0x05E8;
- t['reversedtilde'] = 0x223D;
- t['reviahebrew'] = 0x0597;
- t['reviamugrashhebrew'] = 0x0597;
- t['revlogicalnot'] = 0x2310;
- t['rfishhook'] = 0x027E;
- t['rfishhookreversed'] = 0x027F;
- t['rhabengali'] = 0x09DD;
- t['rhadeva'] = 0x095D;
- t['rho'] = 0x03C1;
- t['rhook'] = 0x027D;
- t['rhookturned'] = 0x027B;
- t['rhookturnedsuperior'] = 0x02B5;
- t['rhosymbolgreek'] = 0x03F1;
- t['rhotichookmod'] = 0x02DE;
- t['rieulacirclekorean'] = 0x3271;
- t['rieulaparenkorean'] = 0x3211;
- t['rieulcirclekorean'] = 0x3263;
- t['rieulhieuhkorean'] = 0x3140;
- t['rieulkiyeokkorean'] = 0x313A;
- t['rieulkiyeoksioskorean'] = 0x3169;
- t['rieulkorean'] = 0x3139;
- t['rieulmieumkorean'] = 0x313B;
- t['rieulpansioskorean'] = 0x316C;
- t['rieulparenkorean'] = 0x3203;
- t['rieulphieuphkorean'] = 0x313F;
- t['rieulpieupkorean'] = 0x313C;
- t['rieulpieupsioskorean'] = 0x316B;
- t['rieulsioskorean'] = 0x313D;
- t['rieulthieuthkorean'] = 0x313E;
- t['rieultikeutkorean'] = 0x316A;
- t['rieulyeorinhieuhkorean'] = 0x316D;
- t['rightangle'] = 0x221F;
- t['righttackbelowcmb'] = 0x0319;
- t['righttriangle'] = 0x22BF;
- t['rihiragana'] = 0x308A;
- t['rikatakana'] = 0x30EA;
- t['rikatakanahalfwidth'] = 0xFF98;
- t['ring'] = 0x02DA;
- t['ringbelowcmb'] = 0x0325;
- t['ringcmb'] = 0x030A;
- t['ringhalfleft'] = 0x02BF;
- t['ringhalfleftarmenian'] = 0x0559;
- t['ringhalfleftbelowcmb'] = 0x031C;
- t['ringhalfleftcentered'] = 0x02D3;
- t['ringhalfright'] = 0x02BE;
- t['ringhalfrightbelowcmb'] = 0x0339;
- t['ringhalfrightcentered'] = 0x02D2;
- t['rinvertedbreve'] = 0x0213;
- t['rittorusquare'] = 0x3351;
- t['rlinebelow'] = 0x1E5F;
- t['rlongleg'] = 0x027C;
- t['rlonglegturned'] = 0x027A;
- t['rmonospace'] = 0xFF52;
- t['rohiragana'] = 0x308D;
- t['rokatakana'] = 0x30ED;
- t['rokatakanahalfwidth'] = 0xFF9B;
- t['roruathai'] = 0x0E23;
- t['rparen'] = 0x24AD;
- t['rrabengali'] = 0x09DC;
- t['rradeva'] = 0x0931;
- t['rragurmukhi'] = 0x0A5C;
- t['rreharabic'] = 0x0691;
- t['rrehfinalarabic'] = 0xFB8D;
- t['rrvocalicbengali'] = 0x09E0;
- t['rrvocalicdeva'] = 0x0960;
- t['rrvocalicgujarati'] = 0x0AE0;
- t['rrvocalicvowelsignbengali'] = 0x09C4;
- t['rrvocalicvowelsigndeva'] = 0x0944;
- t['rrvocalicvowelsigngujarati'] = 0x0AC4;
- t['rsuperior'] = 0xF6F1;
- t['rtblock'] = 0x2590;
- t['rturned'] = 0x0279;
- t['rturnedsuperior'] = 0x02B4;
- t['ruhiragana'] = 0x308B;
- t['rukatakana'] = 0x30EB;
- t['rukatakanahalfwidth'] = 0xFF99;
- t['rupeemarkbengali'] = 0x09F2;
- t['rupeesignbengali'] = 0x09F3;
- t['rupiah'] = 0xF6DD;
- t['ruthai'] = 0x0E24;
- t['rvocalicbengali'] = 0x098B;
- t['rvocalicdeva'] = 0x090B;
- t['rvocalicgujarati'] = 0x0A8B;
- t['rvocalicvowelsignbengali'] = 0x09C3;
- t['rvocalicvowelsigndeva'] = 0x0943;
- t['rvocalicvowelsigngujarati'] = 0x0AC3;
- t['s'] = 0x0073;
- t['sabengali'] = 0x09B8;
- t['sacute'] = 0x015B;
- t['sacutedotaccent'] = 0x1E65;
- t['sadarabic'] = 0x0635;
- t['sadeva'] = 0x0938;
- t['sadfinalarabic'] = 0xFEBA;
- t['sadinitialarabic'] = 0xFEBB;
- t['sadmedialarabic'] = 0xFEBC;
- t['sagujarati'] = 0x0AB8;
- t['sagurmukhi'] = 0x0A38;
- t['sahiragana'] = 0x3055;
- t['sakatakana'] = 0x30B5;
- t['sakatakanahalfwidth'] = 0xFF7B;
- t['sallallahoualayhewasallamarabic'] = 0xFDFA;
- t['samekh'] = 0x05E1;
- t['samekhdagesh'] = 0xFB41;
- t['samekhdageshhebrew'] = 0xFB41;
- t['samekhhebrew'] = 0x05E1;
- t['saraaathai'] = 0x0E32;
- t['saraaethai'] = 0x0E41;
- t['saraaimaimalaithai'] = 0x0E44;
- t['saraaimaimuanthai'] = 0x0E43;
- t['saraamthai'] = 0x0E33;
- t['saraathai'] = 0x0E30;
- t['saraethai'] = 0x0E40;
- t['saraiileftthai'] = 0xF886;
- t['saraiithai'] = 0x0E35;
- t['saraileftthai'] = 0xF885;
- t['saraithai'] = 0x0E34;
- t['saraothai'] = 0x0E42;
- t['saraueeleftthai'] = 0xF888;
- t['saraueethai'] = 0x0E37;
- t['saraueleftthai'] = 0xF887;
- t['sarauethai'] = 0x0E36;
- t['sarauthai'] = 0x0E38;
- t['sarauuthai'] = 0x0E39;
- t['sbopomofo'] = 0x3119;
- t['scaron'] = 0x0161;
- t['scarondotaccent'] = 0x1E67;
- t['scedilla'] = 0x015F;
- t['schwa'] = 0x0259;
- t['schwacyrillic'] = 0x04D9;
- t['schwadieresiscyrillic'] = 0x04DB;
- t['schwahook'] = 0x025A;
- t['scircle'] = 0x24E2;
- t['scircumflex'] = 0x015D;
- t['scommaaccent'] = 0x0219;
- t['sdotaccent'] = 0x1E61;
- t['sdotbelow'] = 0x1E63;
- t['sdotbelowdotaccent'] = 0x1E69;
- t['seagullbelowcmb'] = 0x033C;
- t['second'] = 0x2033;
- t['secondtonechinese'] = 0x02CA;
- t['section'] = 0x00A7;
- t['seenarabic'] = 0x0633;
- t['seenfinalarabic'] = 0xFEB2;
- t['seeninitialarabic'] = 0xFEB3;
- t['seenmedialarabic'] = 0xFEB4;
- t['segol'] = 0x05B6;
- t['segol13'] = 0x05B6;
- t['segol1f'] = 0x05B6;
- t['segol2c'] = 0x05B6;
- t['segolhebrew'] = 0x05B6;
- t['segolnarrowhebrew'] = 0x05B6;
- t['segolquarterhebrew'] = 0x05B6;
- t['segoltahebrew'] = 0x0592;
- t['segolwidehebrew'] = 0x05B6;
- t['seharmenian'] = 0x057D;
- t['sehiragana'] = 0x305B;
- t['sekatakana'] = 0x30BB;
- t['sekatakanahalfwidth'] = 0xFF7E;
- t['semicolon'] = 0x003B;
- t['semicolonarabic'] = 0x061B;
- t['semicolonmonospace'] = 0xFF1B;
- t['semicolonsmall'] = 0xFE54;
- t['semivoicedmarkkana'] = 0x309C;
- t['semivoicedmarkkanahalfwidth'] = 0xFF9F;
- t['sentisquare'] = 0x3322;
- t['sentosquare'] = 0x3323;
- t['seven'] = 0x0037;
- t['sevenarabic'] = 0x0667;
- t['sevenbengali'] = 0x09ED;
- t['sevencircle'] = 0x2466;
- t['sevencircleinversesansserif'] = 0x2790;
- t['sevendeva'] = 0x096D;
- t['seveneighths'] = 0x215E;
- t['sevengujarati'] = 0x0AED;
- t['sevengurmukhi'] = 0x0A6D;
- t['sevenhackarabic'] = 0x0667;
- t['sevenhangzhou'] = 0x3027;
- t['sevenideographicparen'] = 0x3226;
- t['seveninferior'] = 0x2087;
- t['sevenmonospace'] = 0xFF17;
- t['sevenoldstyle'] = 0xF737;
- t['sevenparen'] = 0x247A;
- t['sevenperiod'] = 0x248E;
- t['sevenpersian'] = 0x06F7;
- t['sevenroman'] = 0x2176;
- t['sevensuperior'] = 0x2077;
- t['seventeencircle'] = 0x2470;
- t['seventeenparen'] = 0x2484;
- t['seventeenperiod'] = 0x2498;
- t['seventhai'] = 0x0E57;
- t['sfthyphen'] = 0x00AD;
- t['shaarmenian'] = 0x0577;
- t['shabengali'] = 0x09B6;
- t['shacyrillic'] = 0x0448;
- t['shaddaarabic'] = 0x0651;
- t['shaddadammaarabic'] = 0xFC61;
- t['shaddadammatanarabic'] = 0xFC5E;
- t['shaddafathaarabic'] = 0xFC60;
- t['shaddakasraarabic'] = 0xFC62;
- t['shaddakasratanarabic'] = 0xFC5F;
- t['shade'] = 0x2592;
- t['shadedark'] = 0x2593;
- t['shadelight'] = 0x2591;
- t['shademedium'] = 0x2592;
- t['shadeva'] = 0x0936;
- t['shagujarati'] = 0x0AB6;
- t['shagurmukhi'] = 0x0A36;
- t['shalshelethebrew'] = 0x0593;
- t['shbopomofo'] = 0x3115;
- t['shchacyrillic'] = 0x0449;
- t['sheenarabic'] = 0x0634;
- t['sheenfinalarabic'] = 0xFEB6;
- t['sheeninitialarabic'] = 0xFEB7;
- t['sheenmedialarabic'] = 0xFEB8;
- t['sheicoptic'] = 0x03E3;
- t['sheqel'] = 0x20AA;
- t['sheqelhebrew'] = 0x20AA;
- t['sheva'] = 0x05B0;
- t['sheva115'] = 0x05B0;
- t['sheva15'] = 0x05B0;
- t['sheva22'] = 0x05B0;
- t['sheva2e'] = 0x05B0;
- t['shevahebrew'] = 0x05B0;
- t['shevanarrowhebrew'] = 0x05B0;
- t['shevaquarterhebrew'] = 0x05B0;
- t['shevawidehebrew'] = 0x05B0;
- t['shhacyrillic'] = 0x04BB;
- t['shimacoptic'] = 0x03ED;
- t['shin'] = 0x05E9;
- t['shindagesh'] = 0xFB49;
- t['shindageshhebrew'] = 0xFB49;
- t['shindageshshindot'] = 0xFB2C;
- t['shindageshshindothebrew'] = 0xFB2C;
- t['shindageshsindot'] = 0xFB2D;
- t['shindageshsindothebrew'] = 0xFB2D;
- t['shindothebrew'] = 0x05C1;
- t['shinhebrew'] = 0x05E9;
- t['shinshindot'] = 0xFB2A;
- t['shinshindothebrew'] = 0xFB2A;
- t['shinsindot'] = 0xFB2B;
- t['shinsindothebrew'] = 0xFB2B;
- t['shook'] = 0x0282;
- t['sigma'] = 0x03C3;
- t['sigma1'] = 0x03C2;
- t['sigmafinal'] = 0x03C2;
- t['sigmalunatesymbolgreek'] = 0x03F2;
- t['sihiragana'] = 0x3057;
- t['sikatakana'] = 0x30B7;
- t['sikatakanahalfwidth'] = 0xFF7C;
- t['siluqhebrew'] = 0x05BD;
- t['siluqlefthebrew'] = 0x05BD;
- t['similar'] = 0x223C;
- t['sindothebrew'] = 0x05C2;
- t['siosacirclekorean'] = 0x3274;
- t['siosaparenkorean'] = 0x3214;
- t['sioscieuckorean'] = 0x317E;
- t['sioscirclekorean'] = 0x3266;
- t['sioskiyeokkorean'] = 0x317A;
- t['sioskorean'] = 0x3145;
- t['siosnieunkorean'] = 0x317B;
- t['siosparenkorean'] = 0x3206;
- t['siospieupkorean'] = 0x317D;
- t['siostikeutkorean'] = 0x317C;
- t['six'] = 0x0036;
- t['sixarabic'] = 0x0666;
- t['sixbengali'] = 0x09EC;
- t['sixcircle'] = 0x2465;
- t['sixcircleinversesansserif'] = 0x278F;
- t['sixdeva'] = 0x096C;
- t['sixgujarati'] = 0x0AEC;
- t['sixgurmukhi'] = 0x0A6C;
- t['sixhackarabic'] = 0x0666;
- t['sixhangzhou'] = 0x3026;
- t['sixideographicparen'] = 0x3225;
- t['sixinferior'] = 0x2086;
- t['sixmonospace'] = 0xFF16;
- t['sixoldstyle'] = 0xF736;
- t['sixparen'] = 0x2479;
- t['sixperiod'] = 0x248D;
- t['sixpersian'] = 0x06F6;
- t['sixroman'] = 0x2175;
- t['sixsuperior'] = 0x2076;
- t['sixteencircle'] = 0x246F;
- t['sixteencurrencydenominatorbengali'] = 0x09F9;
- t['sixteenparen'] = 0x2483;
- t['sixteenperiod'] = 0x2497;
- t['sixthai'] = 0x0E56;
- t['slash'] = 0x002F;
- t['slashmonospace'] = 0xFF0F;
- t['slong'] = 0x017F;
- t['slongdotaccent'] = 0x1E9B;
- t['smileface'] = 0x263A;
- t['smonospace'] = 0xFF53;
- t['sofpasuqhebrew'] = 0x05C3;
- t['softhyphen'] = 0x00AD;
- t['softsigncyrillic'] = 0x044C;
- t['sohiragana'] = 0x305D;
- t['sokatakana'] = 0x30BD;
- t['sokatakanahalfwidth'] = 0xFF7F;
- t['soliduslongoverlaycmb'] = 0x0338;
- t['solidusshortoverlaycmb'] = 0x0337;
- t['sorusithai'] = 0x0E29;
- t['sosalathai'] = 0x0E28;
- t['sosothai'] = 0x0E0B;
- t['sosuathai'] = 0x0E2A;
- t['space'] = 0x0020;
- t['spacehackarabic'] = 0x0020;
- t['spade'] = 0x2660;
- t['spadesuitblack'] = 0x2660;
- t['spadesuitwhite'] = 0x2664;
- t['sparen'] = 0x24AE;
- t['squarebelowcmb'] = 0x033B;
- t['squarecc'] = 0x33C4;
- t['squarecm'] = 0x339D;
- t['squarediagonalcrosshatchfill'] = 0x25A9;
- t['squarehorizontalfill'] = 0x25A4;
- t['squarekg'] = 0x338F;
- t['squarekm'] = 0x339E;
- t['squarekmcapital'] = 0x33CE;
- t['squareln'] = 0x33D1;
- t['squarelog'] = 0x33D2;
- t['squaremg'] = 0x338E;
- t['squaremil'] = 0x33D5;
- t['squaremm'] = 0x339C;
- t['squaremsquared'] = 0x33A1;
- t['squareorthogonalcrosshatchfill'] = 0x25A6;
- t['squareupperlefttolowerrightfill'] = 0x25A7;
- t['squareupperrighttolowerleftfill'] = 0x25A8;
- t['squareverticalfill'] = 0x25A5;
- t['squarewhitewithsmallblack'] = 0x25A3;
- t['srsquare'] = 0x33DB;
- t['ssabengali'] = 0x09B7;
- t['ssadeva'] = 0x0937;
- t['ssagujarati'] = 0x0AB7;
- t['ssangcieuckorean'] = 0x3149;
- t['ssanghieuhkorean'] = 0x3185;
- t['ssangieungkorean'] = 0x3180;
- t['ssangkiyeokkorean'] = 0x3132;
- t['ssangnieunkorean'] = 0x3165;
- t['ssangpieupkorean'] = 0x3143;
- t['ssangsioskorean'] = 0x3146;
- t['ssangtikeutkorean'] = 0x3138;
- t['ssuperior'] = 0xF6F2;
- t['sterling'] = 0x00A3;
- t['sterlingmonospace'] = 0xFFE1;
- t['strokelongoverlaycmb'] = 0x0336;
- t['strokeshortoverlaycmb'] = 0x0335;
- t['subset'] = 0x2282;
- t['subsetnotequal'] = 0x228A;
- t['subsetorequal'] = 0x2286;
- t['succeeds'] = 0x227B;
- t['suchthat'] = 0x220B;
- t['suhiragana'] = 0x3059;
- t['sukatakana'] = 0x30B9;
- t['sukatakanahalfwidth'] = 0xFF7D;
- t['sukunarabic'] = 0x0652;
- t['summation'] = 0x2211;
- t['sun'] = 0x263C;
- t['superset'] = 0x2283;
- t['supersetnotequal'] = 0x228B;
- t['supersetorequal'] = 0x2287;
- t['svsquare'] = 0x33DC;
- t['syouwaerasquare'] = 0x337C;
- t['t'] = 0x0074;
- t['tabengali'] = 0x09A4;
- t['tackdown'] = 0x22A4;
- t['tackleft'] = 0x22A3;
- t['tadeva'] = 0x0924;
- t['tagujarati'] = 0x0AA4;
- t['tagurmukhi'] = 0x0A24;
- t['taharabic'] = 0x0637;
- t['tahfinalarabic'] = 0xFEC2;
- t['tahinitialarabic'] = 0xFEC3;
- t['tahiragana'] = 0x305F;
- t['tahmedialarabic'] = 0xFEC4;
- t['taisyouerasquare'] = 0x337D;
- t['takatakana'] = 0x30BF;
- t['takatakanahalfwidth'] = 0xFF80;
- t['tatweelarabic'] = 0x0640;
- t['tau'] = 0x03C4;
- t['tav'] = 0x05EA;
- t['tavdages'] = 0xFB4A;
- t['tavdagesh'] = 0xFB4A;
- t['tavdageshhebrew'] = 0xFB4A;
- t['tavhebrew'] = 0x05EA;
- t['tbar'] = 0x0167;
- t['tbopomofo'] = 0x310A;
- t['tcaron'] = 0x0165;
- t['tccurl'] = 0x02A8;
- t['tcedilla'] = 0x0163;
- t['tcheharabic'] = 0x0686;
- t['tchehfinalarabic'] = 0xFB7B;
- t['tchehinitialarabic'] = 0xFB7C;
- t['tchehmedialarabic'] = 0xFB7D;
- t['tcircle'] = 0x24E3;
- t['tcircumflexbelow'] = 0x1E71;
- t['tcommaaccent'] = 0x0163;
- t['tdieresis'] = 0x1E97;
- t['tdotaccent'] = 0x1E6B;
- t['tdotbelow'] = 0x1E6D;
- t['tecyrillic'] = 0x0442;
- t['tedescendercyrillic'] = 0x04AD;
- t['teharabic'] = 0x062A;
- t['tehfinalarabic'] = 0xFE96;
- t['tehhahinitialarabic'] = 0xFCA2;
- t['tehhahisolatedarabic'] = 0xFC0C;
- t['tehinitialarabic'] = 0xFE97;
- t['tehiragana'] = 0x3066;
- t['tehjeeminitialarabic'] = 0xFCA1;
- t['tehjeemisolatedarabic'] = 0xFC0B;
- t['tehmarbutaarabic'] = 0x0629;
- t['tehmarbutafinalarabic'] = 0xFE94;
- t['tehmedialarabic'] = 0xFE98;
- t['tehmeeminitialarabic'] = 0xFCA4;
- t['tehmeemisolatedarabic'] = 0xFC0E;
- t['tehnoonfinalarabic'] = 0xFC73;
- t['tekatakana'] = 0x30C6;
- t['tekatakanahalfwidth'] = 0xFF83;
- t['telephone'] = 0x2121;
- t['telephoneblack'] = 0x260E;
- t['telishagedolahebrew'] = 0x05A0;
- t['telishaqetanahebrew'] = 0x05A9;
- t['tencircle'] = 0x2469;
- t['tenideographicparen'] = 0x3229;
- t['tenparen'] = 0x247D;
- t['tenperiod'] = 0x2491;
- t['tenroman'] = 0x2179;
- t['tesh'] = 0x02A7;
- t['tet'] = 0x05D8;
- t['tetdagesh'] = 0xFB38;
- t['tetdageshhebrew'] = 0xFB38;
- t['tethebrew'] = 0x05D8;
- t['tetsecyrillic'] = 0x04B5;
- t['tevirhebrew'] = 0x059B;
- t['tevirlefthebrew'] = 0x059B;
- t['thabengali'] = 0x09A5;
- t['thadeva'] = 0x0925;
- t['thagujarati'] = 0x0AA5;
- t['thagurmukhi'] = 0x0A25;
- t['thalarabic'] = 0x0630;
- t['thalfinalarabic'] = 0xFEAC;
- t['thanthakhatlowleftthai'] = 0xF898;
- t['thanthakhatlowrightthai'] = 0xF897;
- t['thanthakhatthai'] = 0x0E4C;
- t['thanthakhatupperleftthai'] = 0xF896;
- t['theharabic'] = 0x062B;
- t['thehfinalarabic'] = 0xFE9A;
- t['thehinitialarabic'] = 0xFE9B;
- t['thehmedialarabic'] = 0xFE9C;
- t['thereexists'] = 0x2203;
- t['therefore'] = 0x2234;
- t['theta'] = 0x03B8;
- t['theta1'] = 0x03D1;
- t['thetasymbolgreek'] = 0x03D1;
- t['thieuthacirclekorean'] = 0x3279;
- t['thieuthaparenkorean'] = 0x3219;
- t['thieuthcirclekorean'] = 0x326B;
- t['thieuthkorean'] = 0x314C;
- t['thieuthparenkorean'] = 0x320B;
- t['thirteencircle'] = 0x246C;
- t['thirteenparen'] = 0x2480;
- t['thirteenperiod'] = 0x2494;
- t['thonangmonthothai'] = 0x0E11;
- t['thook'] = 0x01AD;
- t['thophuthaothai'] = 0x0E12;
- t['thorn'] = 0x00FE;
- t['thothahanthai'] = 0x0E17;
- t['thothanthai'] = 0x0E10;
- t['thothongthai'] = 0x0E18;
- t['thothungthai'] = 0x0E16;
- t['thousandcyrillic'] = 0x0482;
- t['thousandsseparatorarabic'] = 0x066C;
- t['thousandsseparatorpersian'] = 0x066C;
- t['three'] = 0x0033;
- t['threearabic'] = 0x0663;
- t['threebengali'] = 0x09E9;
- t['threecircle'] = 0x2462;
- t['threecircleinversesansserif'] = 0x278C;
- t['threedeva'] = 0x0969;
- t['threeeighths'] = 0x215C;
- t['threegujarati'] = 0x0AE9;
- t['threegurmukhi'] = 0x0A69;
- t['threehackarabic'] = 0x0663;
- t['threehangzhou'] = 0x3023;
- t['threeideographicparen'] = 0x3222;
- t['threeinferior'] = 0x2083;
- t['threemonospace'] = 0xFF13;
- t['threenumeratorbengali'] = 0x09F6;
- t['threeoldstyle'] = 0xF733;
- t['threeparen'] = 0x2476;
- t['threeperiod'] = 0x248A;
- t['threepersian'] = 0x06F3;
- t['threequarters'] = 0x00BE;
- t['threequartersemdash'] = 0xF6DE;
- t['threeroman'] = 0x2172;
- t['threesuperior'] = 0x00B3;
- t['threethai'] = 0x0E53;
- t['thzsquare'] = 0x3394;
- t['tihiragana'] = 0x3061;
- t['tikatakana'] = 0x30C1;
- t['tikatakanahalfwidth'] = 0xFF81;
- t['tikeutacirclekorean'] = 0x3270;
- t['tikeutaparenkorean'] = 0x3210;
- t['tikeutcirclekorean'] = 0x3262;
- t['tikeutkorean'] = 0x3137;
- t['tikeutparenkorean'] = 0x3202;
- t['tilde'] = 0x02DC;
- t['tildebelowcmb'] = 0x0330;
- t['tildecmb'] = 0x0303;
- t['tildecomb'] = 0x0303;
- t['tildedoublecmb'] = 0x0360;
- t['tildeoperator'] = 0x223C;
- t['tildeoverlaycmb'] = 0x0334;
- t['tildeverticalcmb'] = 0x033E;
- t['timescircle'] = 0x2297;
- t['tipehahebrew'] = 0x0596;
- t['tipehalefthebrew'] = 0x0596;
- t['tippigurmukhi'] = 0x0A70;
- t['titlocyrilliccmb'] = 0x0483;
- t['tiwnarmenian'] = 0x057F;
- t['tlinebelow'] = 0x1E6F;
- t['tmonospace'] = 0xFF54;
- t['toarmenian'] = 0x0569;
- t['tohiragana'] = 0x3068;
- t['tokatakana'] = 0x30C8;
- t['tokatakanahalfwidth'] = 0xFF84;
- t['tonebarextrahighmod'] = 0x02E5;
- t['tonebarextralowmod'] = 0x02E9;
- t['tonebarhighmod'] = 0x02E6;
- t['tonebarlowmod'] = 0x02E8;
- t['tonebarmidmod'] = 0x02E7;
- t['tonefive'] = 0x01BD;
- t['tonesix'] = 0x0185;
- t['tonetwo'] = 0x01A8;
- t['tonos'] = 0x0384;
- t['tonsquare'] = 0x3327;
- t['topatakthai'] = 0x0E0F;
- t['tortoiseshellbracketleft'] = 0x3014;
- t['tortoiseshellbracketleftsmall'] = 0xFE5D;
- t['tortoiseshellbracketleftvertical'] = 0xFE39;
- t['tortoiseshellbracketright'] = 0x3015;
- t['tortoiseshellbracketrightsmall'] = 0xFE5E;
- t['tortoiseshellbracketrightvertical'] = 0xFE3A;
- t['totaothai'] = 0x0E15;
- t['tpalatalhook'] = 0x01AB;
- t['tparen'] = 0x24AF;
- t['trademark'] = 0x2122;
- t['trademarksans'] = 0xF8EA;
- t['trademarkserif'] = 0xF6DB;
- t['tretroflexhook'] = 0x0288;
- t['triagdn'] = 0x25BC;
- t['triaglf'] = 0x25C4;
- t['triagrt'] = 0x25BA;
- t['triagup'] = 0x25B2;
- t['ts'] = 0x02A6;
- t['tsadi'] = 0x05E6;
- t['tsadidagesh'] = 0xFB46;
- t['tsadidageshhebrew'] = 0xFB46;
- t['tsadihebrew'] = 0x05E6;
- t['tsecyrillic'] = 0x0446;
- t['tsere'] = 0x05B5;
- t['tsere12'] = 0x05B5;
- t['tsere1e'] = 0x05B5;
- t['tsere2b'] = 0x05B5;
- t['tserehebrew'] = 0x05B5;
- t['tserenarrowhebrew'] = 0x05B5;
- t['tserequarterhebrew'] = 0x05B5;
- t['tserewidehebrew'] = 0x05B5;
- t['tshecyrillic'] = 0x045B;
- t['tsuperior'] = 0xF6F3;
- t['ttabengali'] = 0x099F;
- t['ttadeva'] = 0x091F;
- t['ttagujarati'] = 0x0A9F;
- t['ttagurmukhi'] = 0x0A1F;
- t['tteharabic'] = 0x0679;
- t['ttehfinalarabic'] = 0xFB67;
- t['ttehinitialarabic'] = 0xFB68;
- t['ttehmedialarabic'] = 0xFB69;
- t['tthabengali'] = 0x09A0;
- t['tthadeva'] = 0x0920;
- t['tthagujarati'] = 0x0AA0;
- t['tthagurmukhi'] = 0x0A20;
- t['tturned'] = 0x0287;
- t['tuhiragana'] = 0x3064;
- t['tukatakana'] = 0x30C4;
- t['tukatakanahalfwidth'] = 0xFF82;
- t['tusmallhiragana'] = 0x3063;
- t['tusmallkatakana'] = 0x30C3;
- t['tusmallkatakanahalfwidth'] = 0xFF6F;
- t['twelvecircle'] = 0x246B;
- t['twelveparen'] = 0x247F;
- t['twelveperiod'] = 0x2493;
- t['twelveroman'] = 0x217B;
- t['twentycircle'] = 0x2473;
- t['twentyhangzhou'] = 0x5344;
- t['twentyparen'] = 0x2487;
- t['twentyperiod'] = 0x249B;
- t['two'] = 0x0032;
- t['twoarabic'] = 0x0662;
- t['twobengali'] = 0x09E8;
- t['twocircle'] = 0x2461;
- t['twocircleinversesansserif'] = 0x278B;
- t['twodeva'] = 0x0968;
- t['twodotenleader'] = 0x2025;
- t['twodotleader'] = 0x2025;
- t['twodotleadervertical'] = 0xFE30;
- t['twogujarati'] = 0x0AE8;
- t['twogurmukhi'] = 0x0A68;
- t['twohackarabic'] = 0x0662;
- t['twohangzhou'] = 0x3022;
- t['twoideographicparen'] = 0x3221;
- t['twoinferior'] = 0x2082;
- t['twomonospace'] = 0xFF12;
- t['twonumeratorbengali'] = 0x09F5;
- t['twooldstyle'] = 0xF732;
- t['twoparen'] = 0x2475;
- t['twoperiod'] = 0x2489;
- t['twopersian'] = 0x06F2;
- t['tworoman'] = 0x2171;
- t['twostroke'] = 0x01BB;
- t['twosuperior'] = 0x00B2;
- t['twothai'] = 0x0E52;
- t['twothirds'] = 0x2154;
- t['u'] = 0x0075;
- t['uacute'] = 0x00FA;
- t['ubar'] = 0x0289;
- t['ubengali'] = 0x0989;
- t['ubopomofo'] = 0x3128;
- t['ubreve'] = 0x016D;
- t['ucaron'] = 0x01D4;
- t['ucircle'] = 0x24E4;
- t['ucircumflex'] = 0x00FB;
- t['ucircumflexbelow'] = 0x1E77;
- t['ucyrillic'] = 0x0443;
- t['udattadeva'] = 0x0951;
- t['udblacute'] = 0x0171;
- t['udblgrave'] = 0x0215;
- t['udeva'] = 0x0909;
- t['udieresis'] = 0x00FC;
- t['udieresisacute'] = 0x01D8;
- t['udieresisbelow'] = 0x1E73;
- t['udieresiscaron'] = 0x01DA;
- t['udieresiscyrillic'] = 0x04F1;
- t['udieresisgrave'] = 0x01DC;
- t['udieresismacron'] = 0x01D6;
- t['udotbelow'] = 0x1EE5;
- t['ugrave'] = 0x00F9;
- t['ugujarati'] = 0x0A89;
- t['ugurmukhi'] = 0x0A09;
- t['uhiragana'] = 0x3046;
- t['uhookabove'] = 0x1EE7;
- t['uhorn'] = 0x01B0;
- t['uhornacute'] = 0x1EE9;
- t['uhorndotbelow'] = 0x1EF1;
- t['uhorngrave'] = 0x1EEB;
- t['uhornhookabove'] = 0x1EED;
- t['uhorntilde'] = 0x1EEF;
- t['uhungarumlaut'] = 0x0171;
- t['uhungarumlautcyrillic'] = 0x04F3;
- t['uinvertedbreve'] = 0x0217;
- t['ukatakana'] = 0x30A6;
- t['ukatakanahalfwidth'] = 0xFF73;
- t['ukcyrillic'] = 0x0479;
- t['ukorean'] = 0x315C;
- t['umacron'] = 0x016B;
- t['umacroncyrillic'] = 0x04EF;
- t['umacrondieresis'] = 0x1E7B;
- t['umatragurmukhi'] = 0x0A41;
- t['umonospace'] = 0xFF55;
- t['underscore'] = 0x005F;
- t['underscoredbl'] = 0x2017;
- t['underscoremonospace'] = 0xFF3F;
- t['underscorevertical'] = 0xFE33;
- t['underscorewavy'] = 0xFE4F;
- t['union'] = 0x222A;
- t['universal'] = 0x2200;
- t['uogonek'] = 0x0173;
- t['uparen'] = 0x24B0;
- t['upblock'] = 0x2580;
- t['upperdothebrew'] = 0x05C4;
- t['upsilon'] = 0x03C5;
- t['upsilondieresis'] = 0x03CB;
- t['upsilondieresistonos'] = 0x03B0;
- t['upsilonlatin'] = 0x028A;
- t['upsilontonos'] = 0x03CD;
- t['uptackbelowcmb'] = 0x031D;
- t['uptackmod'] = 0x02D4;
- t['uragurmukhi'] = 0x0A73;
- t['uring'] = 0x016F;
- t['ushortcyrillic'] = 0x045E;
- t['usmallhiragana'] = 0x3045;
- t['usmallkatakana'] = 0x30A5;
- t['usmallkatakanahalfwidth'] = 0xFF69;
- t['ustraightcyrillic'] = 0x04AF;
- t['ustraightstrokecyrillic'] = 0x04B1;
- t['utilde'] = 0x0169;
- t['utildeacute'] = 0x1E79;
- t['utildebelow'] = 0x1E75;
- t['uubengali'] = 0x098A;
- t['uudeva'] = 0x090A;
- t['uugujarati'] = 0x0A8A;
- t['uugurmukhi'] = 0x0A0A;
- t['uumatragurmukhi'] = 0x0A42;
- t['uuvowelsignbengali'] = 0x09C2;
- t['uuvowelsigndeva'] = 0x0942;
- t['uuvowelsigngujarati'] = 0x0AC2;
- t['uvowelsignbengali'] = 0x09C1;
- t['uvowelsigndeva'] = 0x0941;
- t['uvowelsigngujarati'] = 0x0AC1;
- t['v'] = 0x0076;
- t['vadeva'] = 0x0935;
- t['vagujarati'] = 0x0AB5;
- t['vagurmukhi'] = 0x0A35;
- t['vakatakana'] = 0x30F7;
- t['vav'] = 0x05D5;
- t['vavdagesh'] = 0xFB35;
- t['vavdagesh65'] = 0xFB35;
- t['vavdageshhebrew'] = 0xFB35;
- t['vavhebrew'] = 0x05D5;
- t['vavholam'] = 0xFB4B;
- t['vavholamhebrew'] = 0xFB4B;
- t['vavvavhebrew'] = 0x05F0;
- t['vavyodhebrew'] = 0x05F1;
- t['vcircle'] = 0x24E5;
- t['vdotbelow'] = 0x1E7F;
- t['vecyrillic'] = 0x0432;
- t['veharabic'] = 0x06A4;
- t['vehfinalarabic'] = 0xFB6B;
- t['vehinitialarabic'] = 0xFB6C;
- t['vehmedialarabic'] = 0xFB6D;
- t['vekatakana'] = 0x30F9;
- t['venus'] = 0x2640;
- t['verticalbar'] = 0x007C;
- t['verticallineabovecmb'] = 0x030D;
- t['verticallinebelowcmb'] = 0x0329;
- t['verticallinelowmod'] = 0x02CC;
- t['verticallinemod'] = 0x02C8;
- t['vewarmenian'] = 0x057E;
- t['vhook'] = 0x028B;
- t['vikatakana'] = 0x30F8;
- t['viramabengali'] = 0x09CD;
- t['viramadeva'] = 0x094D;
- t['viramagujarati'] = 0x0ACD;
- t['visargabengali'] = 0x0983;
- t['visargadeva'] = 0x0903;
- t['visargagujarati'] = 0x0A83;
- t['vmonospace'] = 0xFF56;
- t['voarmenian'] = 0x0578;
- t['voicediterationhiragana'] = 0x309E;
- t['voicediterationkatakana'] = 0x30FE;
- t['voicedmarkkana'] = 0x309B;
- t['voicedmarkkanahalfwidth'] = 0xFF9E;
- t['vokatakana'] = 0x30FA;
- t['vparen'] = 0x24B1;
- t['vtilde'] = 0x1E7D;
- t['vturned'] = 0x028C;
- t['vuhiragana'] = 0x3094;
- t['vukatakana'] = 0x30F4;
- t['w'] = 0x0077;
- t['wacute'] = 0x1E83;
- t['waekorean'] = 0x3159;
- t['wahiragana'] = 0x308F;
- t['wakatakana'] = 0x30EF;
- t['wakatakanahalfwidth'] = 0xFF9C;
- t['wakorean'] = 0x3158;
- t['wasmallhiragana'] = 0x308E;
- t['wasmallkatakana'] = 0x30EE;
- t['wattosquare'] = 0x3357;
- t['wavedash'] = 0x301C;
- t['wavyunderscorevertical'] = 0xFE34;
- t['wawarabic'] = 0x0648;
- t['wawfinalarabic'] = 0xFEEE;
- t['wawhamzaabovearabic'] = 0x0624;
- t['wawhamzaabovefinalarabic'] = 0xFE86;
- t['wbsquare'] = 0x33DD;
- t['wcircle'] = 0x24E6;
- t['wcircumflex'] = 0x0175;
- t['wdieresis'] = 0x1E85;
- t['wdotaccent'] = 0x1E87;
- t['wdotbelow'] = 0x1E89;
- t['wehiragana'] = 0x3091;
- t['weierstrass'] = 0x2118;
- t['wekatakana'] = 0x30F1;
- t['wekorean'] = 0x315E;
- t['weokorean'] = 0x315D;
- t['wgrave'] = 0x1E81;
- t['whitebullet'] = 0x25E6;
- t['whitecircle'] = 0x25CB;
- t['whitecircleinverse'] = 0x25D9;
- t['whitecornerbracketleft'] = 0x300E;
- t['whitecornerbracketleftvertical'] = 0xFE43;
- t['whitecornerbracketright'] = 0x300F;
- t['whitecornerbracketrightvertical'] = 0xFE44;
- t['whitediamond'] = 0x25C7;
- t['whitediamondcontainingblacksmalldiamond'] = 0x25C8;
- t['whitedownpointingsmalltriangle'] = 0x25BF;
- t['whitedownpointingtriangle'] = 0x25BD;
- t['whiteleftpointingsmalltriangle'] = 0x25C3;
- t['whiteleftpointingtriangle'] = 0x25C1;
- t['whitelenticularbracketleft'] = 0x3016;
- t['whitelenticularbracketright'] = 0x3017;
- t['whiterightpointingsmalltriangle'] = 0x25B9;
- t['whiterightpointingtriangle'] = 0x25B7;
- t['whitesmallsquare'] = 0x25AB;
- t['whitesmilingface'] = 0x263A;
- t['whitesquare'] = 0x25A1;
- t['whitestar'] = 0x2606;
- t['whitetelephone'] = 0x260F;
- t['whitetortoiseshellbracketleft'] = 0x3018;
- t['whitetortoiseshellbracketright'] = 0x3019;
- t['whiteuppointingsmalltriangle'] = 0x25B5;
- t['whiteuppointingtriangle'] = 0x25B3;
- t['wihiragana'] = 0x3090;
- t['wikatakana'] = 0x30F0;
- t['wikorean'] = 0x315F;
- t['wmonospace'] = 0xFF57;
- t['wohiragana'] = 0x3092;
- t['wokatakana'] = 0x30F2;
- t['wokatakanahalfwidth'] = 0xFF66;
- t['won'] = 0x20A9;
- t['wonmonospace'] = 0xFFE6;
- t['wowaenthai'] = 0x0E27;
- t['wparen'] = 0x24B2;
- t['wring'] = 0x1E98;
- t['wsuperior'] = 0x02B7;
- t['wturned'] = 0x028D;
- t['wynn'] = 0x01BF;
- t['x'] = 0x0078;
- t['xabovecmb'] = 0x033D;
- t['xbopomofo'] = 0x3112;
- t['xcircle'] = 0x24E7;
- t['xdieresis'] = 0x1E8D;
- t['xdotaccent'] = 0x1E8B;
- t['xeharmenian'] = 0x056D;
- t['xi'] = 0x03BE;
- t['xmonospace'] = 0xFF58;
- t['xparen'] = 0x24B3;
- t['xsuperior'] = 0x02E3;
- t['y'] = 0x0079;
- t['yaadosquare'] = 0x334E;
- t['yabengali'] = 0x09AF;
- t['yacute'] = 0x00FD;
- t['yadeva'] = 0x092F;
- t['yaekorean'] = 0x3152;
- t['yagujarati'] = 0x0AAF;
- t['yagurmukhi'] = 0x0A2F;
- t['yahiragana'] = 0x3084;
- t['yakatakana'] = 0x30E4;
- t['yakatakanahalfwidth'] = 0xFF94;
- t['yakorean'] = 0x3151;
- t['yamakkanthai'] = 0x0E4E;
- t['yasmallhiragana'] = 0x3083;
- t['yasmallkatakana'] = 0x30E3;
- t['yasmallkatakanahalfwidth'] = 0xFF6C;
- t['yatcyrillic'] = 0x0463;
- t['ycircle'] = 0x24E8;
- t['ycircumflex'] = 0x0177;
- t['ydieresis'] = 0x00FF;
- t['ydotaccent'] = 0x1E8F;
- t['ydotbelow'] = 0x1EF5;
- t['yeharabic'] = 0x064A;
- t['yehbarreearabic'] = 0x06D2;
- t['yehbarreefinalarabic'] = 0xFBAF;
- t['yehfinalarabic'] = 0xFEF2;
- t['yehhamzaabovearabic'] = 0x0626;
- t['yehhamzaabovefinalarabic'] = 0xFE8A;
- t['yehhamzaaboveinitialarabic'] = 0xFE8B;
- t['yehhamzaabovemedialarabic'] = 0xFE8C;
- t['yehinitialarabic'] = 0xFEF3;
- t['yehmedialarabic'] = 0xFEF4;
- t['yehmeeminitialarabic'] = 0xFCDD;
- t['yehmeemisolatedarabic'] = 0xFC58;
- t['yehnoonfinalarabic'] = 0xFC94;
- t['yehthreedotsbelowarabic'] = 0x06D1;
- t['yekorean'] = 0x3156;
- t['yen'] = 0x00A5;
- t['yenmonospace'] = 0xFFE5;
- t['yeokorean'] = 0x3155;
- t['yeorinhieuhkorean'] = 0x3186;
- t['yerahbenyomohebrew'] = 0x05AA;
- t['yerahbenyomolefthebrew'] = 0x05AA;
- t['yericyrillic'] = 0x044B;
- t['yerudieresiscyrillic'] = 0x04F9;
- t['yesieungkorean'] = 0x3181;
- t['yesieungpansioskorean'] = 0x3183;
- t['yesieungsioskorean'] = 0x3182;
- t['yetivhebrew'] = 0x059A;
- t['ygrave'] = 0x1EF3;
- t['yhook'] = 0x01B4;
- t['yhookabove'] = 0x1EF7;
- t['yiarmenian'] = 0x0575;
- t['yicyrillic'] = 0x0457;
- t['yikorean'] = 0x3162;
- t['yinyang'] = 0x262F;
- t['yiwnarmenian'] = 0x0582;
- t['ymonospace'] = 0xFF59;
- t['yod'] = 0x05D9;
- t['yoddagesh'] = 0xFB39;
- t['yoddageshhebrew'] = 0xFB39;
- t['yodhebrew'] = 0x05D9;
- t['yodyodhebrew'] = 0x05F2;
- t['yodyodpatahhebrew'] = 0xFB1F;
- t['yohiragana'] = 0x3088;
- t['yoikorean'] = 0x3189;
- t['yokatakana'] = 0x30E8;
- t['yokatakanahalfwidth'] = 0xFF96;
- t['yokorean'] = 0x315B;
- t['yosmallhiragana'] = 0x3087;
- t['yosmallkatakana'] = 0x30E7;
- t['yosmallkatakanahalfwidth'] = 0xFF6E;
- t['yotgreek'] = 0x03F3;
- t['yoyaekorean'] = 0x3188;
- t['yoyakorean'] = 0x3187;
- t['yoyakthai'] = 0x0E22;
- t['yoyingthai'] = 0x0E0D;
- t['yparen'] = 0x24B4;
- t['ypogegrammeni'] = 0x037A;
- t['ypogegrammenigreekcmb'] = 0x0345;
- t['yr'] = 0x01A6;
- t['yring'] = 0x1E99;
- t['ysuperior'] = 0x02B8;
- t['ytilde'] = 0x1EF9;
- t['yturned'] = 0x028E;
- t['yuhiragana'] = 0x3086;
- t['yuikorean'] = 0x318C;
- t['yukatakana'] = 0x30E6;
- t['yukatakanahalfwidth'] = 0xFF95;
- t['yukorean'] = 0x3160;
- t['yusbigcyrillic'] = 0x046B;
- t['yusbigiotifiedcyrillic'] = 0x046D;
- t['yuslittlecyrillic'] = 0x0467;
- t['yuslittleiotifiedcyrillic'] = 0x0469;
- t['yusmallhiragana'] = 0x3085;
- t['yusmallkatakana'] = 0x30E5;
- t['yusmallkatakanahalfwidth'] = 0xFF6D;
- t['yuyekorean'] = 0x318B;
- t['yuyeokorean'] = 0x318A;
- t['yyabengali'] = 0x09DF;
- t['yyadeva'] = 0x095F;
- t['z'] = 0x007A;
- t['zaarmenian'] = 0x0566;
- t['zacute'] = 0x017A;
- t['zadeva'] = 0x095B;
- t['zagurmukhi'] = 0x0A5B;
- t['zaharabic'] = 0x0638;
- t['zahfinalarabic'] = 0xFEC6;
- t['zahinitialarabic'] = 0xFEC7;
- t['zahiragana'] = 0x3056;
- t['zahmedialarabic'] = 0xFEC8;
- t['zainarabic'] = 0x0632;
- t['zainfinalarabic'] = 0xFEB0;
- t['zakatakana'] = 0x30B6;
- t['zaqefgadolhebrew'] = 0x0595;
- t['zaqefqatanhebrew'] = 0x0594;
- t['zarqahebrew'] = 0x0598;
- t['zayin'] = 0x05D6;
- t['zayindagesh'] = 0xFB36;
- t['zayindageshhebrew'] = 0xFB36;
- t['zayinhebrew'] = 0x05D6;
- t['zbopomofo'] = 0x3117;
- t['zcaron'] = 0x017E;
- t['zcircle'] = 0x24E9;
- t['zcircumflex'] = 0x1E91;
- t['zcurl'] = 0x0291;
- t['zdot'] = 0x017C;
- t['zdotaccent'] = 0x017C;
- t['zdotbelow'] = 0x1E93;
- t['zecyrillic'] = 0x0437;
- t['zedescendercyrillic'] = 0x0499;
- t['zedieresiscyrillic'] = 0x04DF;
- t['zehiragana'] = 0x305C;
- t['zekatakana'] = 0x30BC;
- t['zero'] = 0x0030;
- t['zeroarabic'] = 0x0660;
- t['zerobengali'] = 0x09E6;
- t['zerodeva'] = 0x0966;
- t['zerogujarati'] = 0x0AE6;
- t['zerogurmukhi'] = 0x0A66;
- t['zerohackarabic'] = 0x0660;
- t['zeroinferior'] = 0x2080;
- t['zeromonospace'] = 0xFF10;
- t['zerooldstyle'] = 0xF730;
- t['zeropersian'] = 0x06F0;
- t['zerosuperior'] = 0x2070;
- t['zerothai'] = 0x0E50;
- t['zerowidthjoiner'] = 0xFEFF;
- t['zerowidthnonjoiner'] = 0x200C;
- t['zerowidthspace'] = 0x200B;
- t['zeta'] = 0x03B6;
- t['zhbopomofo'] = 0x3113;
- t['zhearmenian'] = 0x056A;
- t['zhebrevecyrillic'] = 0x04C2;
- t['zhecyrillic'] = 0x0436;
- t['zhedescendercyrillic'] = 0x0497;
- t['zhedieresiscyrillic'] = 0x04DD;
- t['zihiragana'] = 0x3058;
- t['zikatakana'] = 0x30B8;
- t['zinorhebrew'] = 0x05AE;
- t['zlinebelow'] = 0x1E95;
- t['zmonospace'] = 0xFF5A;
- t['zohiragana'] = 0x305E;
- t['zokatakana'] = 0x30BE;
- t['zparen'] = 0x24B5;
- t['zretroflexhook'] = 0x0290;
- t['zstroke'] = 0x01B6;
- t['zuhiragana'] = 0x305A;
- t['zukatakana'] = 0x30BA;
- t['.notdef'] = 0x0000;
- t['angbracketleftbig'] = 0x2329;
- t['angbracketleftBig'] = 0x2329;
- t['angbracketleftbigg'] = 0x2329;
- t['angbracketleftBigg'] = 0x2329;
- t['angbracketrightBig'] = 0x232A;
- t['angbracketrightbig'] = 0x232A;
- t['angbracketrightBigg'] = 0x232A;
- t['angbracketrightbigg'] = 0x232A;
- t['arrowhookleft'] = 0x21AA;
- t['arrowhookright'] = 0x21A9;
- t['arrowlefttophalf'] = 0x21BC;
- t['arrowleftbothalf'] = 0x21BD;
- t['arrownortheast'] = 0x2197;
- t['arrownorthwest'] = 0x2196;
- t['arrowrighttophalf'] = 0x21C0;
- t['arrowrightbothalf'] = 0x21C1;
- t['arrowsoutheast'] = 0x2198;
- t['arrowsouthwest'] = 0x2199;
- t['backslashbig'] = 0x2216;
- t['backslashBig'] = 0x2216;
- t['backslashBigg'] = 0x2216;
- t['backslashbigg'] = 0x2216;
- t['bardbl'] = 0x2016;
- t['bracehtipdownleft'] = 0xFE37;
- t['bracehtipdownright'] = 0xFE37;
- t['bracehtipupleft'] = 0xFE38;
- t['bracehtipupright'] = 0xFE38;
- t['braceleftBig'] = 0x007B;
- t['braceleftbig'] = 0x007B;
- t['braceleftbigg'] = 0x007B;
- t['braceleftBigg'] = 0x007B;
- t['bracerightBig'] = 0x007D;
- t['bracerightbig'] = 0x007D;
- t['bracerightbigg'] = 0x007D;
- t['bracerightBigg'] = 0x007D;
- t['bracketleftbig'] = 0x005B;
- t['bracketleftBig'] = 0x005B;
- t['bracketleftbigg'] = 0x005B;
- t['bracketleftBigg'] = 0x005B;
- t['bracketrightBig'] = 0x005D;
- t['bracketrightbig'] = 0x005D;
- t['bracketrightbigg'] = 0x005D;
- t['bracketrightBigg'] = 0x005D;
- t['ceilingleftbig'] = 0x2308;
- t['ceilingleftBig'] = 0x2308;
- t['ceilingleftBigg'] = 0x2308;
- t['ceilingleftbigg'] = 0x2308;
- t['ceilingrightbig'] = 0x2309;
- t['ceilingrightBig'] = 0x2309;
- t['ceilingrightbigg'] = 0x2309;
- t['ceilingrightBigg'] = 0x2309;
- t['circledotdisplay'] = 0x2299;
- t['circledottext'] = 0x2299;
- t['circlemultiplydisplay'] = 0x2297;
- t['circlemultiplytext'] = 0x2297;
- t['circleplusdisplay'] = 0x2295;
- t['circleplustext'] = 0x2295;
- t['contintegraldisplay'] = 0x222E;
- t['contintegraltext'] = 0x222E;
- t['coproductdisplay'] = 0x2210;
- t['coproducttext'] = 0x2210;
- t['floorleftBig'] = 0x230A;
- t['floorleftbig'] = 0x230A;
- t['floorleftbigg'] = 0x230A;
- t['floorleftBigg'] = 0x230A;
- t['floorrightbig'] = 0x230B;
- t['floorrightBig'] = 0x230B;
- t['floorrightBigg'] = 0x230B;
- t['floorrightbigg'] = 0x230B;
- t['hatwide'] = 0x0302;
- t['hatwider'] = 0x0302;
- t['hatwidest'] = 0x0302;
- t['intercal'] = 0x1D40;
- t['integraldisplay'] = 0x222B;
- t['integraltext'] = 0x222B;
- t['intersectiondisplay'] = 0x22C2;
- t['intersectiontext'] = 0x22C2;
- t['logicalanddisplay'] = 0x2227;
- t['logicalandtext'] = 0x2227;
- t['logicalordisplay'] = 0x2228;
- t['logicalortext'] = 0x2228;
- t['parenleftBig'] = 0x0028;
- t['parenleftbig'] = 0x0028;
- t['parenleftBigg'] = 0x0028;
- t['parenleftbigg'] = 0x0028;
- t['parenrightBig'] = 0x0029;
- t['parenrightbig'] = 0x0029;
- t['parenrightBigg'] = 0x0029;
- t['parenrightbigg'] = 0x0029;
- t['prime'] = 0x2032;
- t['productdisplay'] = 0x220F;
- t['producttext'] = 0x220F;
- t['radicalbig'] = 0x221A;
- t['radicalBig'] = 0x221A;
- t['radicalBigg'] = 0x221A;
- t['radicalbigg'] = 0x221A;
- t['radicalbt'] = 0x221A;
- t['radicaltp'] = 0x221A;
- t['radicalvertex'] = 0x221A;
- t['slashbig'] = 0x002F;
- t['slashBig'] = 0x002F;
- t['slashBigg'] = 0x002F;
- t['slashbigg'] = 0x002F;
- t['summationdisplay'] = 0x2211;
- t['summationtext'] = 0x2211;
- t['tildewide'] = 0x02DC;
- t['tildewider'] = 0x02DC;
- t['tildewidest'] = 0x02DC;
- t['uniondisplay'] = 0x22C3;
- t['unionmultidisplay'] = 0x228E;
- t['unionmultitext'] = 0x228E;
- t['unionsqdisplay'] = 0x2294;
- t['unionsqtext'] = 0x2294;
- t['uniontext'] = 0x22C3;
- t['vextenddouble'] = 0x2225;
- t['vextendsingle'] = 0x2223;
-});
-var getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) {
- t['space'] = 0x0020;
- t['a1'] = 0x2701;
- t['a2'] = 0x2702;
- t['a202'] = 0x2703;
- t['a3'] = 0x2704;
- t['a4'] = 0x260E;
- t['a5'] = 0x2706;
- t['a119'] = 0x2707;
- t['a118'] = 0x2708;
- t['a117'] = 0x2709;
- t['a11'] = 0x261B;
- t['a12'] = 0x261E;
- t['a13'] = 0x270C;
- t['a14'] = 0x270D;
- t['a15'] = 0x270E;
- t['a16'] = 0x270F;
- t['a105'] = 0x2710;
- t['a17'] = 0x2711;
- t['a18'] = 0x2712;
- t['a19'] = 0x2713;
- t['a20'] = 0x2714;
- t['a21'] = 0x2715;
- t['a22'] = 0x2716;
- t['a23'] = 0x2717;
- t['a24'] = 0x2718;
- t['a25'] = 0x2719;
- t['a26'] = 0x271A;
- t['a27'] = 0x271B;
- t['a28'] = 0x271C;
- t['a6'] = 0x271D;
- t['a7'] = 0x271E;
- t['a8'] = 0x271F;
- t['a9'] = 0x2720;
- t['a10'] = 0x2721;
- t['a29'] = 0x2722;
- t['a30'] = 0x2723;
- t['a31'] = 0x2724;
- t['a32'] = 0x2725;
- t['a33'] = 0x2726;
- t['a34'] = 0x2727;
- t['a35'] = 0x2605;
- t['a36'] = 0x2729;
- t['a37'] = 0x272A;
- t['a38'] = 0x272B;
- t['a39'] = 0x272C;
- t['a40'] = 0x272D;
- t['a41'] = 0x272E;
- t['a42'] = 0x272F;
- t['a43'] = 0x2730;
- t['a44'] = 0x2731;
- t['a45'] = 0x2732;
- t['a46'] = 0x2733;
- t['a47'] = 0x2734;
- t['a48'] = 0x2735;
- t['a49'] = 0x2736;
- t['a50'] = 0x2737;
- t['a51'] = 0x2738;
- t['a52'] = 0x2739;
- t['a53'] = 0x273A;
- t['a54'] = 0x273B;
- t['a55'] = 0x273C;
- t['a56'] = 0x273D;
- t['a57'] = 0x273E;
- t['a58'] = 0x273F;
- t['a59'] = 0x2740;
- t['a60'] = 0x2741;
- t['a61'] = 0x2742;
- t['a62'] = 0x2743;
- t['a63'] = 0x2744;
- t['a64'] = 0x2745;
- t['a65'] = 0x2746;
- t['a66'] = 0x2747;
- t['a67'] = 0x2748;
- t['a68'] = 0x2749;
- t['a69'] = 0x274A;
- t['a70'] = 0x274B;
- t['a71'] = 0x25CF;
- t['a72'] = 0x274D;
- t['a73'] = 0x25A0;
- t['a74'] = 0x274F;
- t['a203'] = 0x2750;
- t['a75'] = 0x2751;
- t['a204'] = 0x2752;
- t['a76'] = 0x25B2;
- t['a77'] = 0x25BC;
- t['a78'] = 0x25C6;
- t['a79'] = 0x2756;
- t['a81'] = 0x25D7;
- t['a82'] = 0x2758;
- t['a83'] = 0x2759;
- t['a84'] = 0x275A;
- t['a97'] = 0x275B;
- t['a98'] = 0x275C;
- t['a99'] = 0x275D;
- t['a100'] = 0x275E;
- t['a101'] = 0x2761;
- t['a102'] = 0x2762;
- t['a103'] = 0x2763;
- t['a104'] = 0x2764;
- t['a106'] = 0x2765;
- t['a107'] = 0x2766;
- t['a108'] = 0x2767;
- t['a112'] = 0x2663;
- t['a111'] = 0x2666;
- t['a110'] = 0x2665;
- t['a109'] = 0x2660;
- t['a120'] = 0x2460;
- t['a121'] = 0x2461;
- t['a122'] = 0x2462;
- t['a123'] = 0x2463;
- t['a124'] = 0x2464;
- t['a125'] = 0x2465;
- t['a126'] = 0x2466;
- t['a127'] = 0x2467;
- t['a128'] = 0x2468;
- t['a129'] = 0x2469;
- t['a130'] = 0x2776;
- t['a131'] = 0x2777;
- t['a132'] = 0x2778;
- t['a133'] = 0x2779;
- t['a134'] = 0x277A;
- t['a135'] = 0x277B;
- t['a136'] = 0x277C;
- t['a137'] = 0x277D;
- t['a138'] = 0x277E;
- t['a139'] = 0x277F;
- t['a140'] = 0x2780;
- t['a141'] = 0x2781;
- t['a142'] = 0x2782;
- t['a143'] = 0x2783;
- t['a144'] = 0x2784;
- t['a145'] = 0x2785;
- t['a146'] = 0x2786;
- t['a147'] = 0x2787;
- t['a148'] = 0x2788;
- t['a149'] = 0x2789;
- t['a150'] = 0x278A;
- t['a151'] = 0x278B;
- t['a152'] = 0x278C;
- t['a153'] = 0x278D;
- t['a154'] = 0x278E;
- t['a155'] = 0x278F;
- t['a156'] = 0x2790;
- t['a157'] = 0x2791;
- t['a158'] = 0x2792;
- t['a159'] = 0x2793;
- t['a160'] = 0x2794;
- t['a161'] = 0x2192;
- t['a163'] = 0x2194;
- t['a164'] = 0x2195;
- t['a196'] = 0x2798;
- t['a165'] = 0x2799;
- t['a192'] = 0x279A;
- t['a166'] = 0x279B;
- t['a167'] = 0x279C;
- t['a168'] = 0x279D;
- t['a169'] = 0x279E;
- t['a170'] = 0x279F;
- t['a171'] = 0x27A0;
- t['a172'] = 0x27A1;
- t['a173'] = 0x27A2;
- t['a162'] = 0x27A3;
- t['a174'] = 0x27A4;
- t['a175'] = 0x27A5;
- t['a176'] = 0x27A6;
- t['a177'] = 0x27A7;
- t['a178'] = 0x27A8;
- t['a179'] = 0x27A9;
- t['a193'] = 0x27AA;
- t['a180'] = 0x27AB;
- t['a199'] = 0x27AC;
- t['a181'] = 0x27AD;
- t['a200'] = 0x27AE;
- t['a182'] = 0x27AF;
- t['a201'] = 0x27B1;
- t['a183'] = 0x27B2;
- t['a184'] = 0x27B3;
- t['a197'] = 0x27B4;
- t['a185'] = 0x27B5;
- t['a194'] = 0x27B6;
- t['a198'] = 0x27B7;
- t['a186'] = 0x27B8;
- t['a195'] = 0x27B9;
- t['a187'] = 0x27BA;
- t['a188'] = 0x27BB;
- t['a189'] = 0x27BC;
- t['a190'] = 0x27BD;
- t['a191'] = 0x27BE;
- t['a89'] = 0x2768;
- t['a90'] = 0x2769;
- t['a93'] = 0x276A;
- t['a94'] = 0x276B;
- t['a91'] = 0x276C;
- t['a92'] = 0x276D;
- t['a205'] = 0x276E;
- t['a85'] = 0x276F;
- t['a206'] = 0x2770;
- t['a86'] = 0x2771;
- t['a87'] = 0x2772;
- t['a88'] = 0x2773;
- t['a95'] = 0x2774;
- t['a96'] = 0x2775;
- t['.notdef'] = 0x0000;
-});
-exports.getGlyphsUnicode = getGlyphsUnicode;
-exports.getDingbatsGlyphsUnicode = getDingbatsGlyphsUnicode;
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var corePdfManager = __w_pdfjs_require__(33);
-var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
-var InvalidPDFException = sharedUtil.InvalidPDFException;
-var MessageHandler = sharedUtil.MessageHandler;
-var MissingPDFException = sharedUtil.MissingPDFException;
-var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
-var PasswordException = sharedUtil.PasswordException;
-var UnknownErrorException = sharedUtil.UnknownErrorException;
-var XRefParseException = sharedUtil.XRefParseException;
-var arrayByteLength = sharedUtil.arrayByteLength;
-var arraysToBytes = sharedUtil.arraysToBytes;
-var assert = sharedUtil.assert;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var info = sharedUtil.info;
-var warn = sharedUtil.warn;
-var setVerbosityLevel = sharedUtil.setVerbosityLevel;
-var isNodeJS = sharedUtil.isNodeJS;
-var Ref = corePrimitives.Ref;
-var LocalPdfManager = corePdfManager.LocalPdfManager;
-var NetworkPdfManager = corePdfManager.NetworkPdfManager;
-var WorkerTask = function WorkerTaskClosure() {
- function WorkerTask(name) {
- this.name = name;
- this.terminated = false;
- this._capability = createPromiseCapability();
- }
- WorkerTask.prototype = {
- get finished() {
- return this._capability.promise;
- },
- finish: function () {
- this._capability.resolve();
- },
- terminate: function () {
- this.terminated = true;
- },
- ensureNotTerminated: function () {
- if (this.terminated) {
- throw new Error('Worker task was terminated');
- }
- }
- };
- return WorkerTask;
-}();
-var PDFWorkerStream = function PDFWorkerStreamClosure() {
- function PDFWorkerStream(params, msgHandler) {
- this._queuedChunks = [];
- var initialData = params.initialData;
- if (initialData && initialData.length > 0) {
- this._queuedChunks.push(initialData);
- }
- this._msgHandler = msgHandler;
- this._isRangeSupported = !params.disableRange;
- this._isStreamingSupported = !params.disableStream;
- this._contentLength = params.length;
- this._fullRequestReader = null;
- this._rangeReaders = [];
- msgHandler.on('OnDataRange', this._onReceiveData.bind(this));
- msgHandler.on('OnDataProgress', this._onProgress.bind(this));
- }
- PDFWorkerStream.prototype = {
- _onReceiveData: function PDFWorkerStream_onReceiveData(args) {
- if (args.begin === undefined) {
- if (this._fullRequestReader) {
- this._fullRequestReader._enqueue(args.chunk);
- } else {
- this._queuedChunks.push(args.chunk);
- }
- } else {
- var found = this._rangeReaders.some(function (rangeReader) {
- if (rangeReader._begin !== args.begin) {
- return false;
- }
- rangeReader._enqueue(args.chunk);
- return true;
- });
- assert(found);
- }
- },
- _onProgress: function PDFWorkerStream_onProgress(evt) {
- if (this._rangeReaders.length > 0) {
- var firstReader = this._rangeReaders[0];
- if (firstReader.onProgress) {
- firstReader.onProgress({ loaded: evt.loaded });
- }
- }
- },
- _removeRangeReader: function PDFWorkerStream_removeRangeReader(reader) {
- var i = this._rangeReaders.indexOf(reader);
- if (i >= 0) {
- this._rangeReaders.splice(i, 1);
- }
- },
- getFullReader: function PDFWorkerStream_getFullReader() {
- assert(!this._fullRequestReader);
- var queuedChunks = this._queuedChunks;
- this._queuedChunks = null;
- return new PDFWorkerStreamReader(this, queuedChunks);
- },
- getRangeReader: function PDFWorkerStream_getRangeReader(begin, end) {
- var reader = new PDFWorkerStreamRangeReader(this, begin, end);
- this._msgHandler.send('RequestDataRange', {
- begin: begin,
- end: end
- });
- this._rangeReaders.push(reader);
- return reader;
- },
- cancelAllRequests: function PDFWorkerStream_cancelAllRequests(reason) {
- if (this._fullRequestReader) {
- this._fullRequestReader.cancel(reason);
- }
- var readers = this._rangeReaders.slice(0);
- readers.forEach(function (rangeReader) {
- rangeReader.cancel(reason);
- });
- }
- };
- function PDFWorkerStreamReader(stream, queuedChunks) {
- this._stream = stream;
- this._done = false;
- this._queuedChunks = queuedChunks || [];
- this._requests = [];
- this._headersReady = Promise.resolve();
- stream._fullRequestReader = this;
- this.onProgress = null;
- }
- PDFWorkerStreamReader.prototype = {
- _enqueue: function PDFWorkerStreamReader_enqueue(chunk) {
- if (this._done) {
- return;
- }
- if (this._requests.length > 0) {
- var requestCapability = this._requests.shift();
- requestCapability.resolve({
- value: chunk,
- done: false
- });
- return;
- }
- this._queuedChunks.push(chunk);
- },
- 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 PDFWorkerStreamReader_read() {
- if (this._queuedChunks.length > 0) {
- var chunk = this._queuedChunks.shift();
- return Promise.resolve({
- value: chunk,
- done: false
- });
- }
- if (this._done) {
- return Promise.resolve({
- value: undefined,
- done: true
- });
- }
- var requestCapability = createPromiseCapability();
- this._requests.push(requestCapability);
- return requestCapability.promise;
- },
- cancel: function PDFWorkerStreamReader_cancel(reason) {
- this._done = true;
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- }
- };
- function PDFWorkerStreamRangeReader(stream, begin, end) {
- this._stream = stream;
- this._begin = begin;
- this._end = end;
- this._queuedChunk = null;
- this._requests = [];
- this._done = false;
- this.onProgress = null;
- }
- PDFWorkerStreamRangeReader.prototype = {
- _enqueue: function PDFWorkerStreamRangeReader_enqueue(chunk) {
- if (this._done) {
- return;
- }
- if (this._requests.length === 0) {
- this._queuedChunk = chunk;
- } else {
- var requestsCapability = this._requests.shift();
- requestsCapability.resolve({
- value: chunk,
- done: false
- });
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- }
- this._done = true;
- this._stream._removeRangeReader(this);
- },
- get isStreamingSupported() {
- return false;
- },
- read: function PDFWorkerStreamRangeReader_read() {
- if (this._queuedChunk) {
- return Promise.resolve({
- value: this._queuedChunk,
- done: false
- });
- }
- if (this._done) {
- return Promise.resolve({
- value: undefined,
- done: true
- });
- }
- var requestCapability = createPromiseCapability();
- this._requests.push(requestCapability);
- return requestCapability.promise;
- },
- cancel: function PDFWorkerStreamRangeReader_cancel(reason) {
- this._done = true;
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- this._stream._removeRangeReader(this);
- }
- };
- return PDFWorkerStream;
-}();
-var PDFNetworkStream;
-function setPDFNetworkStreamClass(cls) {
- PDFNetworkStream = cls;
-}
-var WorkerMessageHandler = {
- setup: function wphSetup(handler, port) {
- var testMessageProcessed = false;
- handler.on('test', function wphSetupTest(data) {
- if (testMessageProcessed) {
- return;
- }
- testMessageProcessed = true;
- if (!(data instanceof Uint8Array)) {
- handler.send('test', 'main', false);
- return;
- }
- var supportTransfers = data[0] === 255;
- handler.postMessageTransfers = supportTransfers;
- var xhr = new XMLHttpRequest();
- var responseExists = 'response' in xhr;
- try {
- xhr.responseType;
- } catch (e) {
- responseExists = false;
- }
- if (!responseExists) {
- handler.send('test', false);
- return;
- }
- handler.send('test', {
- supportTypedArray: true,
- supportTransfers: supportTransfers
- });
- });
- handler.on('configure', function wphConfigure(data) {
- setVerbosityLevel(data.verbosity);
- });
- handler.on('GetDocRequest', function wphSetupDoc(data) {
- return WorkerMessageHandler.createDocumentHandler(data, port);
- });
- },
- createDocumentHandler: function wphCreateDocumentHandler(docParams, port) {
- var pdfManager;
- var terminated = false;
- var cancelXHRs = null;
- var WorkerTasks = [];
- var docId = docParams.docId;
- var docBaseUrl = docParams.docBaseUrl;
- var workerHandlerName = docParams.docId + '_worker';
- var handler = new MessageHandler(workerHandlerName, docId, port);
- handler.postMessageTransfers = docParams.postMessageTransfers;
- function ensureNotTerminated() {
- if (terminated) {
- throw new Error('Worker was terminated');
- }
- }
- function startWorkerTask(task) {
- WorkerTasks.push(task);
- }
- function finishWorkerTask(task) {
- task.finish();
- var i = WorkerTasks.indexOf(task);
- WorkerTasks.splice(i, 1);
- }
- function loadDocument(recoveryMode) {
- var loadDocumentCapability = createPromiseCapability();
- var parseSuccess = function parseSuccess() {
- var numPagesPromise = pdfManager.ensureDoc('numPages');
- var fingerprintPromise = pdfManager.ensureDoc('fingerprint');
- var encryptedPromise = pdfManager.ensureXRef('encrypt');
- Promise.all([numPagesPromise, fingerprintPromise, encryptedPromise]).then(function onDocReady(results) {
- var doc = {
- numPages: results[0],
- fingerprint: results[1],
- encrypted: !!results[2]
- };
- loadDocumentCapability.resolve(doc);
- }, parseFailure);
- };
- var parseFailure = function parseFailure(e) {
- loadDocumentCapability.reject(e);
- };
- pdfManager.ensureDoc('checkHeader', []).then(function () {
- pdfManager.ensureDoc('parseStartXRef', []).then(function () {
- pdfManager.ensureDoc('parse', [recoveryMode]).then(parseSuccess, parseFailure);
- }, parseFailure);
- }, parseFailure);
- return loadDocumentCapability.promise;
- }
- function getPdfManager(data, evaluatorOptions) {
- var pdfManagerCapability = createPromiseCapability();
- var pdfManager;
- var source = data.source;
- if (source.data) {
- try {
- pdfManager = new LocalPdfManager(docId, source.data, source.password, evaluatorOptions, docBaseUrl);
- pdfManagerCapability.resolve(pdfManager);
- } catch (ex) {
- pdfManagerCapability.reject(ex);
- }
- return pdfManagerCapability.promise;
- }
- var pdfStream;
- try {
- if (source.chunkedViewerLoading) {
- pdfStream = new PDFWorkerStream(source, handler);
- } else {
- assert(PDFNetworkStream, 'pdfjs/core/network module is not loaded');
- pdfStream = new PDFNetworkStream(data);
- }
- } catch (ex) {
- pdfManagerCapability.reject(ex);
- return pdfManagerCapability.promise;
- }
- var fullRequest = pdfStream.getFullReader();
- fullRequest.headersReady.then(function () {
- if (!fullRequest.isStreamingSupported || !fullRequest.isRangeSupported) {
- fullRequest.onProgress = function (evt) {
- handler.send('DocProgress', {
- loaded: evt.loaded,
- total: evt.total
- });
- };
- }
- if (!fullRequest.isRangeSupported) {
- return;
- }
- var disableAutoFetch = source.disableAutoFetch || fullRequest.isStreamingSupported;
- pdfManager = new NetworkPdfManager(docId, pdfStream, {
- msgHandler: handler,
- url: source.url,
- password: source.password,
- length: fullRequest.contentLength,
- disableAutoFetch: disableAutoFetch,
- rangeChunkSize: source.rangeChunkSize
- }, evaluatorOptions, docBaseUrl);
- pdfManagerCapability.resolve(pdfManager);
- cancelXHRs = null;
- }).catch(function (reason) {
- pdfManagerCapability.reject(reason);
- cancelXHRs = null;
- });
- var cachedChunks = [],
- loaded = 0;
- var flushChunks = function () {
- var pdfFile = arraysToBytes(cachedChunks);
- if (source.length && pdfFile.length !== source.length) {
- warn('reported HTTP length is different from actual');
- }
- try {
- pdfManager = new LocalPdfManager(docId, pdfFile, source.password, evaluatorOptions, docBaseUrl);
- pdfManagerCapability.resolve(pdfManager);
- } catch (ex) {
- pdfManagerCapability.reject(ex);
- }
- cachedChunks = [];
- };
- var readPromise = new Promise(function (resolve, reject) {
- var readChunk = function (chunk) {
- try {
- ensureNotTerminated();
- if (chunk.done) {
- if (!pdfManager) {
- flushChunks();
- }
- cancelXHRs = null;
- return;
- }
- var data = chunk.value;
- loaded += arrayByteLength(data);
- if (!fullRequest.isStreamingSupported) {
- handler.send('DocProgress', {
- loaded: loaded,
- total: Math.max(loaded, fullRequest.contentLength || 0)
- });
- }
- if (pdfManager) {
- pdfManager.sendProgressiveData(data);
- } else {
- cachedChunks.push(data);
- }
- fullRequest.read().then(readChunk, reject);
- } catch (e) {
- reject(e);
- }
- };
- fullRequest.read().then(readChunk, reject);
- });
- readPromise.catch(function (e) {
- pdfManagerCapability.reject(e);
- cancelXHRs = null;
- });
- cancelXHRs = function () {
- pdfStream.cancelAllRequests('abort');
- };
- return pdfManagerCapability.promise;
- }
- function setupDoc(data) {
- function onSuccess(doc) {
- ensureNotTerminated();
- handler.send('GetDoc', { pdfInfo: doc });
- }
- function onFailure(e) {
- if (e instanceof PasswordException) {
- var task = new WorkerTask('PasswordException: response ' + e.code);
- startWorkerTask(task);
- handler.sendWithPromise('PasswordRequest', e).then(function (data) {
- finishWorkerTask(task);
- pdfManager.updatePassword(data.password);
- pdfManagerReady();
- }).catch(function (ex) {
- finishWorkerTask(task);
- handler.send('PasswordException', ex);
- }.bind(null, e));
- } else if (e instanceof InvalidPDFException) {
- handler.send('InvalidPDF', e);
- } else if (e instanceof MissingPDFException) {
- handler.send('MissingPDF', e);
- } else if (e instanceof UnexpectedResponseException) {
- handler.send('UnexpectedResponse', e);
- } else {
- handler.send('UnknownError', new UnknownErrorException(e.message, e.toString()));
- }
- }
- function pdfManagerReady() {
- ensureNotTerminated();
- loadDocument(false).then(onSuccess, function loadFailure(ex) {
- ensureNotTerminated();
- if (!(ex instanceof XRefParseException)) {
- onFailure(ex);
- return;
- }
- pdfManager.requestLoadedStream();
- pdfManager.onLoadedStream().then(function () {
- ensureNotTerminated();
- loadDocument(true).then(onSuccess, onFailure);
- });
- }, onFailure);
- }
- ensureNotTerminated();
- var evaluatorOptions = {
- forceDataSchema: data.disableCreateObjectURL,
- maxImageSize: data.maxImageSize === undefined ? -1 : data.maxImageSize,
- disableFontFace: data.disableFontFace,
- disableNativeImageDecoder: data.disableNativeImageDecoder
- };
- getPdfManager(data, evaluatorOptions).then(function (newPdfManager) {
- if (terminated) {
- newPdfManager.terminate();
- throw new Error('Worker was terminated');
- }
- pdfManager = newPdfManager;
- handler.send('PDFManagerReady', null);
- pdfManager.onLoadedStream().then(function (stream) {
- handler.send('DataLoaded', { length: stream.bytes.byteLength });
- });
- }).then(pdfManagerReady, onFailure);
- }
- handler.on('GetPage', function wphSetupGetPage(data) {
- return pdfManager.getPage(data.pageIndex).then(function (page) {
- var rotatePromise = pdfManager.ensure(page, 'rotate');
- var refPromise = pdfManager.ensure(page, 'ref');
- var userUnitPromise = pdfManager.ensure(page, 'userUnit');
- var viewPromise = pdfManager.ensure(page, 'view');
- return Promise.all([rotatePromise, refPromise, userUnitPromise, viewPromise]).then(function (results) {
- return {
- rotate: results[0],
- ref: results[1],
- userUnit: results[2],
- view: results[3]
- };
- });
- });
- });
- handler.on('GetPageIndex', function wphSetupGetPageIndex(data) {
- var ref = new Ref(data.ref.num, data.ref.gen);
- var catalog = pdfManager.pdfDocument.catalog;
- return catalog.getPageIndex(ref);
- });
- handler.on('GetDestinations', function wphSetupGetDestinations(data) {
- return pdfManager.ensureCatalog('destinations');
- });
- handler.on('GetDestination', function wphSetupGetDestination(data) {
- return pdfManager.ensureCatalog('getDestination', [data.id]);
- });
- handler.on('GetPageLabels', function wphSetupGetPageLabels(data) {
- return pdfManager.ensureCatalog('pageLabels');
- });
- handler.on('GetAttachments', function wphSetupGetAttachments(data) {
- return pdfManager.ensureCatalog('attachments');
- });
- handler.on('GetJavaScript', function wphSetupGetJavaScript(data) {
- return pdfManager.ensureCatalog('javaScript');
- });
- handler.on('GetOutline', function wphSetupGetOutline(data) {
- return pdfManager.ensureCatalog('documentOutline');
- });
- handler.on('GetMetadata', function wphSetupGetMetadata(data) {
- return Promise.all([pdfManager.ensureDoc('documentInfo'), pdfManager.ensureCatalog('metadata')]);
- });
- handler.on('GetData', function wphSetupGetData(data) {
- pdfManager.requestLoadedStream();
- return pdfManager.onLoadedStream().then(function (stream) {
- return stream.bytes;
- });
- });
- handler.on('GetStats', function wphSetupGetStats(data) {
- return pdfManager.pdfDocument.xref.stats;
- });
- handler.on('GetAnnotations', function wphSetupGetAnnotations(data) {
- return pdfManager.getPage(data.pageIndex).then(function (page) {
- return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]);
- });
- });
- handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
- var pageIndex = data.pageIndex;
- pdfManager.getPage(pageIndex).then(function (page) {
- var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
- startWorkerTask(task);
- var pageNum = pageIndex + 1;
- var start = Date.now();
- page.getOperatorList(handler, task, data.intent, data.renderInteractiveForms).then(function (operatorList) {
- finishWorkerTask(task);
- info('page=' + pageNum + ' - getOperatorList: time=' + (Date.now() - start) + 'ms, len=' + operatorList.totalLength);
- }, function (e) {
- finishWorkerTask(task);
- if (task.terminated) {
- return;
- }
- handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.unknown });
- var minimumStackMessage = 'worker.js: while trying to getPage() and getOperatorList()';
- var wrappedException;
- if (typeof e === 'string') {
- wrappedException = {
- message: e,
- stack: minimumStackMessage
- };
- } else if (typeof e === 'object') {
- wrappedException = {
- message: e.message || e.toString(),
- stack: e.stack || minimumStackMessage
- };
- } else {
- wrappedException = {
- message: 'Unknown exception type: ' + typeof e,
- stack: minimumStackMessage
- };
- }
- handler.send('PageError', {
- pageNum: pageNum,
- error: wrappedException,
- intent: data.intent
- });
- });
- });
- }, this);
- handler.on('GetTextContent', function wphExtractText(data) {
- var pageIndex = data.pageIndex;
- var normalizeWhitespace = data.normalizeWhitespace;
- var combineTextItems = data.combineTextItems;
- return pdfManager.getPage(pageIndex).then(function (page) {
- var task = new WorkerTask('GetTextContent: page ' + pageIndex);
- startWorkerTask(task);
- var pageNum = pageIndex + 1;
- var start = Date.now();
- return page.extractTextContent(handler, task, normalizeWhitespace, combineTextItems).then(function (textContent) {
- finishWorkerTask(task);
- info('text indexing: page=' + pageNum + ' - time=' + (Date.now() - start) + 'ms');
- return textContent;
- }, function (reason) {
- finishWorkerTask(task);
- if (task.terminated) {
- return;
- }
- throw reason;
- });
- });
- });
- handler.on('Cleanup', function wphCleanup(data) {
- return pdfManager.cleanup();
- });
- handler.on('Terminate', function wphTerminate(data) {
- terminated = true;
- if (pdfManager) {
- pdfManager.terminate();
- pdfManager = null;
- }
- if (cancelXHRs) {
- cancelXHRs();
- }
- var waitOn = [];
- WorkerTasks.forEach(function (task) {
- waitOn.push(task.finished);
- task.terminate();
- });
- return Promise.all(waitOn).then(function () {
- handler.destroy();
- handler = null;
- });
- });
- handler.on('Ready', function wphReady(data) {
- setupDoc(docParams);
- docParams = null;
- });
- return workerHandlerName;
- }
-};
-function initializeWorker() {
- var handler = new MessageHandler('worker', 'main', self);
- WorkerMessageHandler.setup(handler, self);
- handler.send('ready', null);
-}
-if (typeof window === 'undefined' && !isNodeJS()) {
- initializeWorker();
-}
-exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
-exports.WorkerTask = WorkerTask;
-exports.WorkerMessageHandler = WorkerMessageHandler;
-
-/***/ }),
-/* 9 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var g;
-g = function () {
- return this;
-}();
-try {
- g = g || Function("return this")() || (1, eval)("this");
-} catch (e) {
- if (typeof window === "object") g = window;
-}
-module.exports = g;
-
-/***/ }),
-/* 10 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var ArithmeticDecoder = function ArithmeticDecoderClosure() {
- var QeTable = [{
- qe: 0x5601,
- nmps: 1,
- nlps: 1,
- switchFlag: 1
- }, {
- qe: 0x3401,
- nmps: 2,
- nlps: 6,
- switchFlag: 0
- }, {
- qe: 0x1801,
- nmps: 3,
- nlps: 9,
- switchFlag: 0
- }, {
- qe: 0x0AC1,
- nmps: 4,
- nlps: 12,
- switchFlag: 0
- }, {
- qe: 0x0521,
- nmps: 5,
- nlps: 29,
- switchFlag: 0
- }, {
- qe: 0x0221,
- nmps: 38,
- nlps: 33,
- switchFlag: 0
- }, {
- qe: 0x5601,
- nmps: 7,
- nlps: 6,
- switchFlag: 1
- }, {
- qe: 0x5401,
- nmps: 8,
- nlps: 14,
- switchFlag: 0
- }, {
- qe: 0x4801,
- nmps: 9,
- nlps: 14,
- switchFlag: 0
- }, {
- qe: 0x3801,
- nmps: 10,
- nlps: 14,
- switchFlag: 0
- }, {
- qe: 0x3001,
- nmps: 11,
- nlps: 17,
- switchFlag: 0
- }, {
- qe: 0x2401,
- nmps: 12,
- nlps: 18,
- switchFlag: 0
- }, {
- qe: 0x1C01,
- nmps: 13,
- nlps: 20,
- switchFlag: 0
- }, {
- qe: 0x1601,
- nmps: 29,
- nlps: 21,
- switchFlag: 0
- }, {
- qe: 0x5601,
- nmps: 15,
- nlps: 14,
- switchFlag: 1
- }, {
- qe: 0x5401,
- nmps: 16,
- nlps: 14,
- switchFlag: 0
- }, {
- qe: 0x5101,
- nmps: 17,
- nlps: 15,
- switchFlag: 0
- }, {
- qe: 0x4801,
- nmps: 18,
- nlps: 16,
- switchFlag: 0
- }, {
- qe: 0x3801,
- nmps: 19,
- nlps: 17,
- switchFlag: 0
- }, {
- qe: 0x3401,
- nmps: 20,
- nlps: 18,
- switchFlag: 0
- }, {
- qe: 0x3001,
- nmps: 21,
- nlps: 19,
- switchFlag: 0
- }, {
- qe: 0x2801,
- nmps: 22,
- nlps: 19,
- switchFlag: 0
- }, {
- qe: 0x2401,
- nmps: 23,
- nlps: 20,
- switchFlag: 0
- }, {
- qe: 0x2201,
- nmps: 24,
- nlps: 21,
- switchFlag: 0
- }, {
- qe: 0x1C01,
- nmps: 25,
- nlps: 22,
- switchFlag: 0
- }, {
- qe: 0x1801,
- nmps: 26,
- nlps: 23,
- switchFlag: 0
- }, {
- qe: 0x1601,
- nmps: 27,
- nlps: 24,
- switchFlag: 0
- }, {
- qe: 0x1401,
- nmps: 28,
- nlps: 25,
- switchFlag: 0
- }, {
- qe: 0x1201,
- nmps: 29,
- nlps: 26,
- switchFlag: 0
- }, {
- qe: 0x1101,
- nmps: 30,
- nlps: 27,
- switchFlag: 0
- }, {
- qe: 0x0AC1,
- nmps: 31,
- nlps: 28,
- switchFlag: 0
- }, {
- qe: 0x09C1,
- nmps: 32,
- nlps: 29,
- switchFlag: 0
- }, {
- qe: 0x08A1,
- nmps: 33,
- nlps: 30,
- switchFlag: 0
- }, {
- qe: 0x0521,
- nmps: 34,
- nlps: 31,
- switchFlag: 0
- }, {
- qe: 0x0441,
- nmps: 35,
- nlps: 32,
- switchFlag: 0
- }, {
- qe: 0x02A1,
- nmps: 36,
- nlps: 33,
- switchFlag: 0
- }, {
- qe: 0x0221,
- nmps: 37,
- nlps: 34,
- switchFlag: 0
- }, {
- qe: 0x0141,
- nmps: 38,
- nlps: 35,
- switchFlag: 0
- }, {
- qe: 0x0111,
- nmps: 39,
- nlps: 36,
- switchFlag: 0
- }, {
- qe: 0x0085,
- nmps: 40,
- nlps: 37,
- switchFlag: 0
- }, {
- qe: 0x0049,
- nmps: 41,
- nlps: 38,
- switchFlag: 0
- }, {
- qe: 0x0025,
- nmps: 42,
- nlps: 39,
- switchFlag: 0
- }, {
- qe: 0x0015,
- nmps: 43,
- nlps: 40,
- switchFlag: 0
- }, {
- qe: 0x0009,
- nmps: 44,
- nlps: 41,
- switchFlag: 0
- }, {
- qe: 0x0005,
- nmps: 45,
- nlps: 42,
- switchFlag: 0
- }, {
- qe: 0x0001,
- nmps: 45,
- nlps: 43,
- switchFlag: 0
- }, {
- qe: 0x5601,
- nmps: 46,
- nlps: 46,
- switchFlag: 0
- }];
- function ArithmeticDecoder(data, start, end) {
- this.data = data;
- this.bp = start;
- this.dataEnd = end;
- this.chigh = data[start];
- this.clow = 0;
- this.byteIn();
- this.chigh = this.chigh << 7 & 0xFFFF | this.clow >> 9 & 0x7F;
- this.clow = this.clow << 7 & 0xFFFF;
- this.ct -= 7;
- this.a = 0x8000;
- }
- ArithmeticDecoder.prototype = {
- byteIn: function ArithmeticDecoder_byteIn() {
- var data = this.data;
- var bp = this.bp;
- if (data[bp] === 0xFF) {
- var b1 = data[bp + 1];
- if (b1 > 0x8F) {
- this.clow += 0xFF00;
- this.ct = 8;
- } else {
- bp++;
- this.clow += data[bp] << 9;
- this.ct = 7;
- this.bp = bp;
- }
- } else {
- bp++;
- this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xFF00;
- this.ct = 8;
- this.bp = bp;
- }
- if (this.clow > 0xFFFF) {
- this.chigh += this.clow >> 16;
- this.clow &= 0xFFFF;
- }
- },
- readBit: function ArithmeticDecoder_readBit(contexts, pos) {
- var cx_index = contexts[pos] >> 1,
- cx_mps = contexts[pos] & 1;
- var qeTableIcx = QeTable[cx_index];
- var qeIcx = qeTableIcx.qe;
- var d;
- var a = this.a - qeIcx;
- if (this.chigh < qeIcx) {
- if (a < qeIcx) {
- a = qeIcx;
- d = cx_mps;
- cx_index = qeTableIcx.nmps;
- } else {
- a = qeIcx;
- d = 1 ^ cx_mps;
- if (qeTableIcx.switchFlag === 1) {
- cx_mps = d;
- }
- cx_index = qeTableIcx.nlps;
- }
- } else {
- this.chigh -= qeIcx;
- if ((a & 0x8000) !== 0) {
- this.a = a;
- return cx_mps;
- }
- if (a < qeIcx) {
- d = 1 ^ cx_mps;
- if (qeTableIcx.switchFlag === 1) {
- cx_mps = d;
- }
- cx_index = qeTableIcx.nlps;
- } else {
- d = cx_mps;
- cx_index = qeTableIcx.nmps;
- }
- }
- do {
- if (this.ct === 0) {
- this.byteIn();
- }
- a <<= 1;
- this.chigh = this.chigh << 1 & 0xFFFF | this.clow >> 15 & 1;
- this.clow = this.clow << 1 & 0xFFFF;
- this.ct--;
- } while ((a & 0x8000) === 0);
- this.a = a;
- contexts[pos] = cx_index << 1 | cx_mps;
- return d;
- }
- };
- return ArithmeticDecoder;
-}();
-exports.ArithmeticDecoder = ArithmeticDecoder;
-
-/***/ }),
-/* 11 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreCharsets = __w_pdfjs_require__(22);
-var coreEncodings = __w_pdfjs_require__(4);
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var bytesToString = sharedUtil.bytesToString;
-var warn = sharedUtil.warn;
-var isArray = sharedUtil.isArray;
-var Util = sharedUtil.Util;
-var stringToBytes = sharedUtil.stringToBytes;
-var assert = sharedUtil.assert;
-var ISOAdobeCharset = coreCharsets.ISOAdobeCharset;
-var ExpertCharset = coreCharsets.ExpertCharset;
-var ExpertSubsetCharset = coreCharsets.ExpertSubsetCharset;
-var StandardEncoding = coreEncodings.StandardEncoding;
-var ExpertEncoding = coreEncodings.ExpertEncoding;
-var MAX_SUBR_NESTING = 10;
-var CFFStandardStrings = ['.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'];
-var CFFParser = function CFFParserClosure() {
- var CharstringValidationData = [null, {
- id: 'hstem',
- min: 2,
- stackClearing: true,
- stem: true
- }, null, {
- id: 'vstem',
- min: 2,
- stackClearing: true,
- stem: true
- }, {
- id: 'vmoveto',
- min: 1,
- stackClearing: true
- }, {
- id: 'rlineto',
- min: 2,
- resetStack: true
- }, {
- id: 'hlineto',
- min: 1,
- resetStack: true
- }, {
- id: 'vlineto',
- min: 1,
- resetStack: true
- }, {
- id: 'rrcurveto',
- min: 6,
- resetStack: true
- }, null, {
- id: 'callsubr',
- min: 1,
- undefStack: true
- }, {
- id: 'return',
- min: 0,
- undefStack: true
- }, null, null, {
- id: 'endchar',
- min: 0,
- stackClearing: true
- }, null, null, null, {
- id: 'hstemhm',
- min: 2,
- stackClearing: true,
- stem: true
- }, {
- id: 'hintmask',
- min: 0,
- stackClearing: true
- }, {
- id: 'cntrmask',
- min: 0,
- stackClearing: true
- }, {
- id: 'rmoveto',
- min: 2,
- stackClearing: true
- }, {
- id: 'hmoveto',
- min: 1,
- stackClearing: true
- }, {
- id: 'vstemhm',
- min: 2,
- stackClearing: true,
- stem: true
- }, {
- id: 'rcurveline',
- min: 8,
- resetStack: true
- }, {
- id: 'rlinecurve',
- min: 8,
- resetStack: true
- }, {
- id: 'vvcurveto',
- min: 4,
- resetStack: true
- }, {
- id: 'hhcurveto',
- min: 4,
- resetStack: true
- }, null, {
- id: 'callgsubr',
- min: 1,
- undefStack: true
- }, {
- id: 'vhcurveto',
- min: 4,
- resetStack: true
- }, {
- id: 'hvcurveto',
- min: 4,
- resetStack: true
- }];
- var CharstringValidationData12 = [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 stack_div(stack, index) {
- stack[index - 2] = stack[index - 2] + stack[index - 1];
- }
- }, {
- id: 'sub',
- min: 2,
- stackDelta: -1,
- stackFn: function stack_div(stack, index) {
- stack[index - 2] = stack[index - 2] - stack[index - 1];
- }
- }, {
- id: 'div',
- min: 2,
- stackDelta: -1,
- stackFn: function stack_div(stack, index) {
- stack[index - 2] = stack[index - 2] / stack[index - 1];
- }
- }, null, {
- id: 'neg',
- min: 1,
- stackDelta: 0,
- stackFn: function stack_div(stack, index) {
- stack[index - 1] = -stack[index - 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 stack_div(stack, index) {
- stack[index - 2] = stack[index - 2] * stack[index - 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: true
- }, {
- id: 'flex',
- min: 13,
- resetStack: true
- }, {
- id: 'hflex1',
- min: 9,
- resetStack: true
- }, {
- id: 'flex1',
- min: 11,
- resetStack: true
- }];
- function CFFParser(file, properties, seacAnalysisEnabled) {
- this.bytes = file.getBytes();
- this.properties = properties;
- this.seacAnalysisEnabled = !!seacAnalysisEnabled;
- }
- CFFParser.prototype = {
- parse: function CFFParser_parse() {
- var properties = this.properties;
- var cff = new CFF();
- this.cff = cff;
- var header = this.parseHeader();
- var nameIndex = this.parseIndex(header.endPos);
- var topDictIndex = this.parseIndex(nameIndex.endPos);
- var stringIndex = this.parseIndex(topDictIndex.endPos);
- var globalSubrIndex = this.parseIndex(stringIndex.endPos);
- var topDictParsed = this.parseDict(topDictIndex.obj.get(0));
- var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
- cff.header = header.obj;
- cff.names = this.parseNameIndex(nameIndex.obj);
- cff.strings = this.parseStringIndex(stringIndex.obj);
- cff.topDict = topDict;
- cff.globalSubrIndex = globalSubrIndex.obj;
- this.parsePrivateDict(cff.topDict);
- cff.isCIDFont = topDict.hasName('ROS');
- var charStringOffset = topDict.getByName('CharStrings');
- var charStringIndex = this.parseIndex(charStringOffset).obj;
- var fontMatrix = topDict.getByName('FontMatrix');
- if (fontMatrix) {
- properties.fontMatrix = fontMatrix;
- }
- var fontBBox = topDict.getByName('FontBBox');
- if (fontBBox) {
- properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
- properties.descent = Math.min(fontBBox[1], fontBBox[3]);
- properties.ascentScaled = true;
- }
- var charset, encoding;
- if (cff.isCIDFont) {
- var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj;
- for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
- var dictRaw = fdArrayIndex.get(i);
- var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), cff.strings);
- this.parsePrivateDict(fontDict);
- cff.fdArray.push(fontDict);
- }
- encoding = null;
- charset = this.parseCharsets(topDict.getByName('charset'), charStringIndex.count, cff.strings, true);
- cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'), charStringIndex.count);
- } else {
- charset = this.parseCharsets(topDict.getByName('charset'), charStringIndex.count, cff.strings, false);
- encoding = this.parseEncoding(topDict.getByName('Encoding'), properties, cff.strings, charset.charset);
- }
- cff.charset = charset;
- cff.encoding = encoding;
- var charStringsAndSeacs = this.parseCharStrings(charStringIndex, topDict.privateDict.subrsIndex, globalSubrIndex.obj, cff.fdSelect, cff.fdArray);
- cff.charStrings = charStringsAndSeacs.charStrings;
- cff.seacs = charStringsAndSeacs.seacs;
- cff.widths = charStringsAndSeacs.widths;
- return cff;
- },
- parseHeader: function CFFParser_parseHeader() {
- var bytes = this.bytes;
- var bytesLength = bytes.length;
- var offset = 0;
- while (offset < bytesLength && bytes[offset] !== 1) {
- ++offset;
- }
- if (offset >= bytesLength) {
- error('Invalid CFF header');
- } else if (offset !== 0) {
- info('cff data is shifted');
- bytes = bytes.subarray(offset);
- this.bytes = bytes;
- }
- var major = bytes[0];
- var minor = bytes[1];
- var hdrSize = bytes[2];
- var offSize = bytes[3];
- var header = new CFFHeader(major, minor, hdrSize, offSize);
- return {
- obj: header,
- endPos: hdrSize
- };
- },
- parseDict: function CFFParser_parseDict(dict) {
- var pos = 0;
- function parseOperand() {
- var value = dict[pos++];
- if (value === 30) {
- return parseFloatOperand();
- } else if (value === 28) {
- value = dict[pos++];
- value = (value << 24 | dict[pos++] << 16) >> 16;
- return value;
- } else if (value === 29) {
- value = dict[pos++];
- value = value << 8 | dict[pos++];
- value = value << 8 | dict[pos++];
- value = value << 8 | dict[pos++];
- return value;
- } else if (value >= 32 && value <= 246) {
- return value - 139;
- } else if (value >= 247 && value <= 250) {
- return (value - 247) * 256 + dict[pos++] + 108;
- } else if (value >= 251 && value <= 254) {
- return -((value - 251) * 256) - dict[pos++] - 108;
- }
- warn('CFFParser_parseDict: "' + value + '" is a reserved command.');
- return NaN;
- }
- function parseFloatOperand() {
- var str = '';
- var eof = 15;
- var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
- var length = dict.length;
- while (pos < length) {
- var b = dict[pos++];
- var b1 = b >> 4;
- var b2 = b & 15;
- if (b1 === eof) {
- break;
- }
- str += lookup[b1];
- if (b2 === eof) {
- break;
- }
- str += lookup[b2];
- }
- return parseFloat(str);
- }
- var operands = [];
- var entries = [];
- pos = 0;
- var end = dict.length;
- while (pos < end) {
- var b = dict[pos];
- if (b <= 21) {
- if (b === 12) {
- b = b << 8 | dict[++pos];
- }
- entries.push([b, operands]);
- operands = [];
- ++pos;
- } else {
- operands.push(parseOperand());
- }
- }
- return entries;
- },
- parseIndex: function CFFParser_parseIndex(pos) {
- var cffIndex = new CFFIndex();
- var bytes = this.bytes;
- var count = bytes[pos++] << 8 | bytes[pos++];
- var offsets = [];
- var end = pos;
- var i, ii;
- if (count !== 0) {
- var offsetSize = bytes[pos++];
- var startPos = pos + (count + 1) * offsetSize - 1;
- for (i = 0, ii = count + 1; i < ii; ++i) {
- var offset = 0;
- for (var j = 0; j < offsetSize; ++j) {
- offset <<= 8;
- offset += bytes[pos++];
- }
- offsets.push(startPos + offset);
- }
- end = offsets[count];
- }
- for (i = 0, ii = offsets.length - 1; i < ii; ++i) {
- var offsetStart = offsets[i];
- var offsetEnd = offsets[i + 1];
- cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
- }
- return {
- obj: cffIndex,
- endPos: end
- };
- },
- parseNameIndex: function CFFParser_parseNameIndex(index) {
- var names = [];
- for (var i = 0, ii = index.count; i < ii; ++i) {
- var name = index.get(i);
- var length = Math.min(name.length, 127);
- var data = [];
- for (var j = 0; j < length; ++j) {
- var c = name[j];
- if (j === 0 && c === 0) {
- data[j] = c;
- continue;
- }
- if (c < 33 || c > 126 || c === 91 || c === 93 || c === 40 || c === 41 || c === 123 || c === 125 || c === 60 || c === 62 || c === 47 || c === 37 || c === 35) {
- data[j] = 95;
- continue;
- }
- data[j] = c;
- }
- names.push(bytesToString(data));
- }
- return names;
- },
- parseStringIndex: function CFFParser_parseStringIndex(index) {
- var strings = new CFFStrings();
- for (var i = 0, ii = index.count; i < ii; ++i) {
- var data = index.get(i);
- strings.add(bytesToString(data));
- }
- return strings;
- },
- createDict: function CFFParser_createDict(Type, dict, strings) {
- var cffDict = new Type(strings);
- for (var i = 0, ii = dict.length; i < ii; ++i) {
- var pair = dict[i];
- var key = pair[0];
- var value = pair[1];
- cffDict.setByKey(key, value);
- }
- return cffDict;
- },
- parseCharString: function CFFParser_parseCharString(state, data, localSubrIndex, globalSubrIndex) {
- if (!data || state.callDepth > MAX_SUBR_NESTING) {
- return false;
- }
- var stackSize = state.stackSize;
- var stack = state.stack;
- var length = data.length;
- for (var j = 0; j < length;) {
- var value = data[j++];
- var validationCommand = null;
- if (value === 12) {
- var q = data[j++];
- if (q === 0) {
- data[j - 2] = 139;
- data[j - 1] = 22;
- stackSize = 0;
- } else {
- validationCommand = CharstringValidationData12[q];
- }
- } else if (value === 28) {
- stack[stackSize] = (data[j] << 24 | data[j + 1] << 16) >> 16;
- j += 2;
- stackSize++;
- } else if (value === 14) {
- if (stackSize >= 4) {
- stackSize -= 4;
- if (this.seacAnalysisEnabled) {
- state.seac = stack.slice(stackSize, stackSize + 4);
- return false;
- }
- }
- validationCommand = CharstringValidationData[value];
- } else if (value >= 32 && value <= 246) {
- stack[stackSize] = value - 139;
- stackSize++;
- } else if (value >= 247 && value <= 254) {
- stack[stackSize] = value < 251 ? (value - 247 << 8) + data[j] + 108 : -(value - 251 << 8) - data[j] - 108;
- j++;
- stackSize++;
- } else if (value === 255) {
- stack[stackSize] = (data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) / 65536;
- j += 4;
- stackSize++;
- } else if (value === 19 || value === 20) {
- state.hints += stackSize >> 1;
- j += state.hints + 7 >> 3;
- stackSize %= 2;
- validationCommand = CharstringValidationData[value];
- } else if (value === 10 || value === 29) {
- var subrsIndex;
- if (value === 10) {
- subrsIndex = localSubrIndex;
- } else {
- subrsIndex = globalSubrIndex;
- }
- if (!subrsIndex) {
- validationCommand = CharstringValidationData[value];
- warn('Missing subrsIndex for ' + validationCommand.id);
- return false;
- }
- var bias = 32768;
- if (subrsIndex.count < 1240) {
- bias = 107;
- } else if (subrsIndex.count < 33900) {
- bias = 1131;
- }
- var subrNumber = stack[--stackSize] + bias;
- if (subrNumber < 0 || subrNumber >= subrsIndex.count || isNaN(subrNumber)) {
- validationCommand = CharstringValidationData[value];
- warn('Out of bounds subrIndex for ' + validationCommand.id);
- return false;
- }
- state.stackSize = stackSize;
- state.callDepth++;
- var valid = this.parseCharString(state, subrsIndex.get(subrNumber), localSubrIndex, globalSubrIndex);
- if (!valid) {
- return false;
- }
- state.callDepth--;
- stackSize = state.stackSize;
- continue;
- } else if (value === 11) {
- state.stackSize = stackSize;
- return true;
- } else {
- validationCommand = CharstringValidationData[value];
- }
- if (validationCommand) {
- if (validationCommand.stem) {
- state.hints += stackSize >> 1;
- }
- if ('min' in validationCommand) {
- if (!state.undefStack && stackSize < validationCommand.min) {
- warn('Not enough parameters for ' + validationCommand.id + '; actual: ' + stackSize + ', expected: ' + validationCommand.min);
- return false;
- }
- }
- if (state.firstStackClearing && validationCommand.stackClearing) {
- state.firstStackClearing = false;
- stackSize -= validationCommand.min;
- if (stackSize >= 2 && validationCommand.stem) {
- stackSize %= 2;
- } else if (stackSize > 1) {
- warn('Found too many parameters for stack-clearing command');
- }
- if (stackSize > 0 && stack[stackSize - 1] >= 0) {
- state.width = stack[stackSize - 1];
- }
- }
- if ('stackDelta' in validationCommand) {
- if ('stackFn' in validationCommand) {
- validationCommand.stackFn(stack, stackSize);
- }
- stackSize += validationCommand.stackDelta;
- } else if (validationCommand.stackClearing) {
- stackSize = 0;
- } else if (validationCommand.resetStack) {
- stackSize = 0;
- state.undefStack = false;
- } else if (validationCommand.undefStack) {
- stackSize = 0;
- state.undefStack = true;
- state.firstStackClearing = false;
- }
- }
- }
- state.stackSize = stackSize;
- return true;
- },
- parseCharStrings: function CFFParser_parseCharStrings(charStrings, localSubrIndex, globalSubrIndex, fdSelect, fdArray) {
- var seacs = [];
- var widths = [];
- var count = charStrings.count;
- for (var i = 0; i < count; i++) {
- var charstring = charStrings.get(i);
- var state = {
- callDepth: 0,
- stackSize: 0,
- stack: [],
- undefStack: true,
- hints: 0,
- firstStackClearing: true,
- seac: null,
- width: null
- };
- var valid = true;
- var localSubrToUse = null;
- if (fdSelect && fdArray.length) {
- var fdIndex = fdSelect.getFDIndex(i);
- if (fdIndex === -1) {
- warn('Glyph index is not in fd select.');
- valid = false;
- }
- if (fdIndex >= fdArray.length) {
- warn('Invalid fd index for glyph index.');
- valid = false;
- }
- if (valid) {
- localSubrToUse = fdArray[fdIndex].privateDict.subrsIndex;
- }
- } else if (localSubrIndex) {
- localSubrToUse = localSubrIndex;
- }
- if (valid) {
- valid = this.parseCharString(state, charstring, localSubrToUse, globalSubrIndex);
- }
- if (state.width !== null) {
- widths[i] = state.width;
- }
- if (state.seac !== null) {
- seacs[i] = state.seac;
- }
- if (!valid) {
- charStrings.set(i, new Uint8Array([14]));
- }
- }
- return {
- charStrings: charStrings,
- seacs: seacs,
- widths: widths
- };
- },
- emptyPrivateDictionary: function CFFParser_emptyPrivateDictionary(parentDict) {
- var privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings);
- parentDict.setByKey(18, [0, 0]);
- parentDict.privateDict = privateDict;
- },
- parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
- if (!parentDict.hasName('Private')) {
- this.emptyPrivateDictionary(parentDict);
- return;
- }
- var privateOffset = parentDict.getByName('Private');
- if (!isArray(privateOffset) || privateOffset.length !== 2) {
- parentDict.removeByName('Private');
- return;
- }
- var size = privateOffset[0];
- var offset = privateOffset[1];
- if (size === 0 || offset >= this.bytes.length) {
- this.emptyPrivateDictionary(parentDict);
- return;
- }
- var privateDictEnd = offset + size;
- var dictData = this.bytes.subarray(offset, privateDictEnd);
- var dict = this.parseDict(dictData);
- var privateDict = this.createDict(CFFPrivateDict, dict, parentDict.strings);
- parentDict.privateDict = privateDict;
- if (!privateDict.getByName('Subrs')) {
- return;
- }
- var subrsOffset = privateDict.getByName('Subrs');
- var relativeOffset = offset + subrsOffset;
- if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
- this.emptyPrivateDictionary(parentDict);
- return;
- }
- var subrsIndex = this.parseIndex(relativeOffset);
- privateDict.subrsIndex = subrsIndex.obj;
- },
- parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) {
- if (pos === 0) {
- return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset);
- } else if (pos === 1) {
- return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset);
- } else if (pos === 2) {
- return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset);
- }
- var bytes = this.bytes;
- var start = pos;
- var format = bytes[pos++];
- var charset = ['.notdef'];
- var id, count, i;
- length -= 1;
- switch (format) {
- case 0:
- for (i = 0; i < length; i++) {
- id = bytes[pos++] << 8 | bytes[pos++];
- charset.push(cid ? id : strings.get(id));
- }
- break;
- case 1:
- while (charset.length <= length) {
- id = bytes[pos++] << 8 | bytes[pos++];
- count = bytes[pos++];
- for (i = 0; i <= count; i++) {
- charset.push(cid ? id++ : strings.get(id++));
- }
- }
- break;
- case 2:
- while (charset.length <= length) {
- id = bytes[pos++] << 8 | bytes[pos++];
- count = bytes[pos++] << 8 | bytes[pos++];
- for (i = 0; i <= count; i++) {
- charset.push(cid ? id++ : strings.get(id++));
- }
- }
- break;
- default:
- error('Unknown charset format');
- }
- var end = pos;
- var raw = bytes.subarray(start, end);
- return new CFFCharset(false, format, charset, raw);
- },
- parseEncoding: function CFFParser_parseEncoding(pos, properties, strings, charset) {
- var encoding = Object.create(null);
- var bytes = this.bytes;
- var predefined = false;
- var format, i, ii;
- var raw = null;
- function readSupplement() {
- var supplementsCount = bytes[pos++];
- for (i = 0; i < supplementsCount; i++) {
- var code = bytes[pos++];
- var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
- encoding[code] = charset.indexOf(strings.get(sid));
- }
- }
- if (pos === 0 || pos === 1) {
- predefined = true;
- format = pos;
- var baseEncoding = pos ? ExpertEncoding : StandardEncoding;
- for (i = 0, ii = charset.length; i < ii; i++) {
- var index = baseEncoding.indexOf(charset[i]);
- if (index !== -1) {
- encoding[index] = i;
- }
- }
- } else {
- var dataStart = pos;
- format = bytes[pos++];
- switch (format & 0x7f) {
- case 0:
- var glyphsCount = bytes[pos++];
- for (i = 1; i <= glyphsCount; i++) {
- encoding[bytes[pos++]] = i;
- }
- break;
- case 1:
- var rangesCount = bytes[pos++];
- var gid = 1;
- for (i = 0; i < rangesCount; i++) {
- var start = bytes[pos++];
- var left = bytes[pos++];
- for (var j = start; j <= start + left; j++) {
- encoding[j] = gid++;
- }
- }
- break;
- default:
- error('Unknown encoding format: ' + format + ' in CFF');
- break;
- }
- var dataEnd = pos;
- if (format & 0x80) {
- bytes[dataStart] &= 0x7f;
- readSupplement();
- }
- raw = bytes.subarray(dataStart, dataEnd);
- }
- format = format & 0x7f;
- return new CFFEncoding(predefined, format, encoding, raw);
- },
- parseFDSelect: function CFFParser_parseFDSelect(pos, length) {
- var start = pos;
- var bytes = this.bytes;
- var format = bytes[pos++];
- var fdSelect = [],
- rawBytes;
- var i,
- invalidFirstGID = false;
- switch (format) {
- case 0:
- for (i = 0; i < length; ++i) {
- var id = bytes[pos++];
- fdSelect.push(id);
- }
- rawBytes = bytes.subarray(start, pos);
- break;
- case 3:
- var rangesCount = bytes[pos++] << 8 | bytes[pos++];
- for (i = 0; i < rangesCount; ++i) {
- var first = bytes[pos++] << 8 | bytes[pos++];
- if (i === 0 && first !== 0) {
- warn('parseFDSelect: The first range must have a first GID of 0' + ' -- trying to recover.');
- invalidFirstGID = true;
- first = 0;
- }
- var fdIndex = bytes[pos++];
- var next = bytes[pos] << 8 | bytes[pos + 1];
- for (var j = first; j < next; ++j) {
- fdSelect.push(fdIndex);
- }
- }
- pos += 2;
- rawBytes = bytes.subarray(start, pos);
- if (invalidFirstGID) {
- rawBytes[3] = rawBytes[4] = 0;
- }
- break;
- default:
- error('parseFDSelect: Unknown format "' + format + '".');
- break;
- }
- assert(fdSelect.length === length, 'parseFDSelect: Invalid font data.');
- return new CFFFDSelect(fdSelect, rawBytes);
- }
- };
- return CFFParser;
-}();
-var CFF = function CFFClosure() {
- function CFF() {
- this.header = null;
- this.names = [];
- this.topDict = null;
- this.strings = new CFFStrings();
- this.globalSubrIndex = null;
- this.encoding = null;
- this.charset = null;
- this.charStrings = null;
- this.fdArray = [];
- this.fdSelect = null;
- this.isCIDFont = false;
- }
- return CFF;
-}();
-var CFFHeader = function CFFHeaderClosure() {
- function CFFHeader(major, minor, hdrSize, offSize) {
- this.major = major;
- this.minor = minor;
- this.hdrSize = hdrSize;
- this.offSize = offSize;
- }
- return CFFHeader;
-}();
-var CFFStrings = function CFFStringsClosure() {
- function CFFStrings() {
- this.strings = [];
- }
- CFFStrings.prototype = {
- get: function CFFStrings_get(index) {
- if (index >= 0 && index <= 390) {
- return CFFStandardStrings[index];
- }
- if (index - 391 <= this.strings.length) {
- return this.strings[index - 391];
- }
- return CFFStandardStrings[0];
- },
- add: function CFFStrings_add(value) {
- this.strings.push(value);
- },
- get count() {
- return this.strings.length;
- }
- };
- return CFFStrings;
-}();
-var CFFIndex = function CFFIndexClosure() {
- function CFFIndex() {
- this.objects = [];
- this.length = 0;
- }
- CFFIndex.prototype = {
- add: function CFFIndex_add(data) {
- this.length += data.length;
- this.objects.push(data);
- },
- set: function CFFIndex_set(index, data) {
- this.length += data.length - this.objects[index].length;
- this.objects[index] = data;
- },
- get: function CFFIndex_get(index) {
- return this.objects[index];
- },
- get count() {
- return this.objects.length;
- }
- };
- return CFFIndex;
-}();
-var CFFDict = function CFFDictClosure() {
- function CFFDict(tables, strings) {
- this.keyToNameMap = tables.keyToNameMap;
- this.nameToKeyMap = tables.nameToKeyMap;
- this.defaults = tables.defaults;
- this.types = tables.types;
- this.opcodes = tables.opcodes;
- this.order = tables.order;
- this.strings = strings;
- this.values = Object.create(null);
- }
- CFFDict.prototype = {
- setByKey: function CFFDict_setByKey(key, value) {
- if (!(key in this.keyToNameMap)) {
- return false;
- }
- var valueLength = value.length;
- if (valueLength === 0) {
- return true;
- }
- for (var i = 0; i < valueLength; i++) {
- if (isNaN(value[i])) {
- warn('Invalid CFFDict value: "' + value + '" for key "' + key + '".');
- return true;
- }
- }
- var type = this.types[key];
- if (type === 'num' || type === 'sid' || type === 'offset') {
- value = value[0];
- }
- this.values[key] = value;
- return true;
- },
- setByName: function CFFDict_setByName(name, value) {
- if (!(name in this.nameToKeyMap)) {
- error('Invalid dictionary name "' + name + '"');
- }
- this.values[this.nameToKeyMap[name]] = value;
- },
- hasName: function CFFDict_hasName(name) {
- return this.nameToKeyMap[name] in this.values;
- },
- getByName: function CFFDict_getByName(name) {
- if (!(name in this.nameToKeyMap)) {
- error('Invalid dictionary name "' + name + '"');
- }
- var key = this.nameToKeyMap[name];
- if (!(key in this.values)) {
- return this.defaults[key];
- }
- return this.values[key];
- },
- removeByName: function CFFDict_removeByName(name) {
- delete this.values[this.nameToKeyMap[name]];
- }
- };
- CFFDict.createTables = function CFFDict_createTables(layout) {
- var tables = {
- keyToNameMap: {},
- nameToKeyMap: {},
- defaults: {},
- types: {},
- opcodes: {},
- order: []
- };
- for (var i = 0, ii = layout.length; i < ii; ++i) {
- var entry = layout[i];
- var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
- tables.keyToNameMap[key] = entry[1];
- tables.nameToKeyMap[entry[1]] = key;
- tables.types[key] = entry[2];
- tables.defaults[key] = entry[3];
- tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]];
- tables.order.push(key);
- }
- return tables;
- };
- return CFFDict;
-}();
-var CFFTopDict = function CFFTopDictClosure() {
- var layout = [[[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'], [0.001, 0, 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]];
- var tables = null;
- function CFFTopDict(strings) {
- if (tables === null) {
- tables = CFFDict.createTables(layout);
- }
- CFFDict.call(this, tables, strings);
- this.privateDict = null;
- }
- CFFTopDict.prototype = Object.create(CFFDict.prototype);
- return CFFTopDict;
-}();
-var CFFPrivateDict = function CFFPrivateDictClosure() {
- var layout = [[6, 'BlueValues', 'delta', null], [7, 'OtherBlues', 'delta', null], [8, 'FamilyBlues', 'delta', null], [9, 'FamilyOtherBlues', 'delta', null], [[12, 9], 'BlueScale', 'num', 0.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', 0.06], [[12, 19], 'initialRandomSeed', 'num', 0], [20, 'defaultWidthX', 'num', 0], [21, 'nominalWidthX', 'num', 0], [19, 'Subrs', 'offset', null]];
- var tables = null;
- function CFFPrivateDict(strings) {
- if (tables === null) {
- tables = CFFDict.createTables(layout);
- }
- CFFDict.call(this, tables, strings);
- this.subrsIndex = null;
- }
- CFFPrivateDict.prototype = Object.create(CFFDict.prototype);
- return CFFPrivateDict;
-}();
-var CFFCharsetPredefinedTypes = {
- ISO_ADOBE: 0,
- EXPERT: 1,
- EXPERT_SUBSET: 2
-};
-var CFFCharset = function CFFCharsetClosure() {
- function CFFCharset(predefined, format, charset, raw) {
- this.predefined = predefined;
- this.format = format;
- this.charset = charset;
- this.raw = raw;
- }
- return CFFCharset;
-}();
-var CFFEncoding = function CFFEncodingClosure() {
- function CFFEncoding(predefined, format, encoding, raw) {
- this.predefined = predefined;
- this.format = format;
- this.encoding = encoding;
- this.raw = raw;
- }
- return CFFEncoding;
-}();
-var CFFFDSelect = function CFFFDSelectClosure() {
- function CFFFDSelect(fdSelect, raw) {
- this.fdSelect = fdSelect;
- this.raw = raw;
- }
- CFFFDSelect.prototype = {
- getFDIndex: function CFFFDSelect_get(glyphIndex) {
- if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
- return -1;
- }
- return this.fdSelect[glyphIndex];
- }
- };
- return CFFFDSelect;
-}();
-var CFFOffsetTracker = function CFFOffsetTrackerClosure() {
- function CFFOffsetTracker() {
- this.offsets = Object.create(null);
- }
- CFFOffsetTracker.prototype = {
- isTracking: function CFFOffsetTracker_isTracking(key) {
- return key in this.offsets;
- },
- track: function CFFOffsetTracker_track(key, location) {
- if (key in this.offsets) {
- error('Already tracking location of ' + key);
- }
- this.offsets[key] = location;
- },
- offset: function CFFOffsetTracker_offset(value) {
- for (var key in this.offsets) {
- this.offsets[key] += value;
- }
- },
- setEntryLocation: function CFFOffsetTracker_setEntryLocation(key, values, output) {
- if (!(key in this.offsets)) {
- error('Not tracking location of ' + key);
- }
- var data = output.data;
- var dataOffset = this.offsets[key];
- var size = 5;
- for (var i = 0, ii = values.length; i < ii; ++i) {
- var offset0 = i * size + dataOffset;
- var offset1 = offset0 + 1;
- var offset2 = offset0 + 2;
- var offset3 = offset0 + 3;
- var offset4 = offset0 + 4;
- if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
- error('writing to an offset that is not empty');
- }
- var value = values[i];
- data[offset0] = 0x1d;
- data[offset1] = value >> 24 & 0xFF;
- data[offset2] = value >> 16 & 0xFF;
- data[offset3] = value >> 8 & 0xFF;
- data[offset4] = value & 0xFF;
- }
- }
- };
- return CFFOffsetTracker;
-}();
-var CFFCompiler = function CFFCompilerClosure() {
- function CFFCompiler(cff) {
- this.cff = cff;
- }
- CFFCompiler.prototype = {
- compile: function CFFCompiler_compile() {
- var cff = this.cff;
- var output = {
- data: [],
- length: 0,
- add: function CFFCompiler_add(data) {
- this.data = this.data.concat(data);
- this.length = this.data.length;
- }
- };
- var header = this.compileHeader(cff.header);
- output.add(header);
- var nameIndex = this.compileNameIndex(cff.names);
- output.add(nameIndex);
- if (cff.isCIDFont) {
- if (cff.topDict.hasName('FontMatrix')) {
- var base = cff.topDict.getByName('FontMatrix');
- cff.topDict.removeByName('FontMatrix');
- for (var i = 0, ii = cff.fdArray.length; i < ii; i++) {
- var subDict = cff.fdArray[i];
- var matrix = base.slice(0);
- if (subDict.hasName('FontMatrix')) {
- matrix = Util.transform(matrix, subDict.getByName('FontMatrix'));
- }
- subDict.setByName('FontMatrix', matrix);
- }
- }
- }
- var compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont);
- output.add(compiled.output);
- var topDictTracker = compiled.trackers[0];
- var stringIndex = this.compileStringIndex(cff.strings.strings);
- output.add(stringIndex);
- var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
- output.add(globalSubrIndex);
- if (cff.encoding && cff.topDict.hasName('Encoding')) {
- if (cff.encoding.predefined) {
- topDictTracker.setEntryLocation('Encoding', [cff.encoding.format], output);
- } else {
- var encoding = this.compileEncoding(cff.encoding);
- topDictTracker.setEntryLocation('Encoding', [output.length], output);
- output.add(encoding);
- }
- }
- if (cff.charset && cff.topDict.hasName('charset')) {
- if (cff.charset.predefined) {
- topDictTracker.setEntryLocation('charset', [cff.charset.format], output);
- } else {
- var charset = this.compileCharset(cff.charset);
- topDictTracker.setEntryLocation('charset', [output.length], output);
- output.add(charset);
- }
- }
- var charStrings = this.compileCharStrings(cff.charStrings);
- topDictTracker.setEntryLocation('CharStrings', [output.length], output);
- output.add(charStrings);
- if (cff.isCIDFont) {
- topDictTracker.setEntryLocation('FDSelect', [output.length], output);
- var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
- output.add(fdSelect);
- compiled = this.compileTopDicts(cff.fdArray, output.length, true);
- topDictTracker.setEntryLocation('FDArray', [output.length], output);
- output.add(compiled.output);
- var fontDictTrackers = compiled.trackers;
- this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
- }
- this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
- output.add([0]);
- return output.data;
- },
- encodeNumber: function CFFCompiler_encodeNumber(value) {
- if (parseFloat(value) === parseInt(value, 10) && !isNaN(value)) {
- return this.encodeInteger(value);
- }
- return this.encodeFloat(value);
- },
- encodeFloat: function CFFCompiler_encodeFloat(num) {
- var value = num.toString();
- var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
- if (m) {
- var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
- value = (Math.round(num * epsilon) / epsilon).toString();
- }
- var nibbles = '';
- var i, ii;
- for (i = 0, ii = value.length; i < ii; ++i) {
- var a = value[i];
- if (a === 'e') {
- nibbles += value[++i] === '-' ? 'c' : 'b';
- } else if (a === '.') {
- nibbles += 'a';
- } else if (a === '-') {
- nibbles += 'e';
- } else {
- nibbles += a;
- }
- }
- nibbles += nibbles.length & 1 ? 'f' : 'ff';
- var out = [30];
- for (i = 0, ii = nibbles.length; i < ii; i += 2) {
- out.push(parseInt(nibbles.substr(i, 2), 16));
- }
- return out;
- },
- encodeInteger: function CFFCompiler_encodeInteger(value) {
- var code;
- if (value >= -107 && value <= 107) {
- code = [value + 139];
- } else if (value >= 108 && value <= 1131) {
- value = value - 108;
- code = [(value >> 8) + 247, value & 0xFF];
- } else if (value >= -1131 && value <= -108) {
- value = -value - 108;
- code = [(value >> 8) + 251, value & 0xFF];
- } else if (value >= -32768 && value <= 32767) {
- code = [0x1c, value >> 8 & 0xFF, value & 0xFF];
- } else {
- code = [0x1d, value >> 24 & 0xFF, value >> 16 & 0xFF, value >> 8 & 0xFF, value & 0xFF];
- }
- return code;
- },
- compileHeader: function CFFCompiler_compileHeader(header) {
- return [header.major, header.minor, header.hdrSize, header.offSize];
- },
- compileNameIndex: function CFFCompiler_compileNameIndex(names) {
- var nameIndex = new CFFIndex();
- for (var i = 0, ii = names.length; i < ii; ++i) {
- nameIndex.add(stringToBytes(names[i]));
- }
- return this.compileIndex(nameIndex);
- },
- compileTopDicts: function CFFCompiler_compileTopDicts(dicts, length, removeCidKeys) {
- var fontDictTrackers = [];
- var fdArrayIndex = new CFFIndex();
- for (var i = 0, ii = dicts.length; i < ii; ++i) {
- var fontDict = dicts[i];
- if (removeCidKeys) {
- fontDict.removeByName('CIDFontVersion');
- fontDict.removeByName('CIDFontRevision');
- fontDict.removeByName('CIDFontType');
- fontDict.removeByName('CIDCount');
- fontDict.removeByName('UIDBase');
- }
- var fontDictTracker = new CFFOffsetTracker();
- var fontDictData = this.compileDict(fontDict, fontDictTracker);
- fontDictTrackers.push(fontDictTracker);
- fdArrayIndex.add(fontDictData);
- fontDictTracker.offset(length);
- }
- fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
- return {
- trackers: fontDictTrackers,
- output: fdArrayIndex
- };
- },
- compilePrivateDicts: function CFFCompiler_compilePrivateDicts(dicts, trackers, output) {
- for (var i = 0, ii = dicts.length; i < ii; ++i) {
- var fontDict = dicts[i];
- assert(fontDict.privateDict && fontDict.hasName('Private'), 'There must be an private dictionary.');
- var privateDict = fontDict.privateDict;
- var privateDictTracker = new CFFOffsetTracker();
- var privateDictData = this.compileDict(privateDict, privateDictTracker);
- var outputLength = output.length;
- privateDictTracker.offset(outputLength);
- if (!privateDictData.length) {
- outputLength = 0;
- }
- trackers[i].setEntryLocation('Private', [privateDictData.length, outputLength], output);
- output.add(privateDictData);
- if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
- var subrs = this.compileIndex(privateDict.subrsIndex);
- privateDictTracker.setEntryLocation('Subrs', [privateDictData.length], output);
- output.add(subrs);
- }
- }
- },
- compileDict: function CFFCompiler_compileDict(dict, offsetTracker) {
- var out = [];
- var order = dict.order;
- for (var i = 0; i < order.length; ++i) {
- var key = order[i];
- if (!(key in dict.values)) {
- continue;
- }
- var values = dict.values[key];
- var types = dict.types[key];
- if (!isArray(types)) {
- types = [types];
- }
- if (!isArray(values)) {
- values = [values];
- }
- if (values.length === 0) {
- continue;
- }
- for (var j = 0, jj = types.length; j < jj; ++j) {
- var type = types[j];
- var value = values[j];
- switch (type) {
- case 'num':
- case 'sid':
- out = out.concat(this.encodeNumber(value));
- break;
- case 'offset':
- var name = dict.keyToNameMap[key];
- if (!offsetTracker.isTracking(name)) {
- offsetTracker.track(name, out.length);
- }
- out = out.concat([0x1d, 0, 0, 0, 0]);
- break;
- case 'array':
- case 'delta':
- out = out.concat(this.encodeNumber(value));
- for (var k = 1, kk = values.length; k < kk; ++k) {
- out = out.concat(this.encodeNumber(values[k]));
- }
- break;
- default:
- error('Unknown data type of ' + type);
- break;
- }
- }
- out = out.concat(dict.opcodes[key]);
- }
- return out;
- },
- compileStringIndex: function CFFCompiler_compileStringIndex(strings) {
- var stringIndex = new CFFIndex();
- for (var i = 0, ii = strings.length; i < ii; ++i) {
- stringIndex.add(stringToBytes(strings[i]));
- }
- return this.compileIndex(stringIndex);
- },
- compileGlobalSubrIndex: function CFFCompiler_compileGlobalSubrIndex() {
- var globalSubrIndex = this.cff.globalSubrIndex;
- this.out.writeByteArray(this.compileIndex(globalSubrIndex));
- },
- compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) {
- return this.compileIndex(charStrings);
- },
- compileCharset: function CFFCompiler_compileCharset(charset) {
- return this.compileTypedArray(charset.raw);
- },
- compileEncoding: function CFFCompiler_compileEncoding(encoding) {
- return this.compileTypedArray(encoding.raw);
- },
- compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) {
- return this.compileTypedArray(fdSelect);
- },
- compileTypedArray: function CFFCompiler_compileTypedArray(data) {
- var out = [];
- for (var i = 0, ii = data.length; i < ii; ++i) {
- out[i] = data[i];
- }
- return out;
- },
- compileIndex: function CFFCompiler_compileIndex(index, trackers) {
- trackers = trackers || [];
- var objects = index.objects;
- var count = objects.length;
- if (count === 0) {
- return [0, 0, 0];
- }
- var data = [count >> 8 & 0xFF, count & 0xff];
- var lastOffset = 1,
- i;
- for (i = 0; i < count; ++i) {
- lastOffset += objects[i].length;
- }
- var offsetSize;
- if (lastOffset < 0x100) {
- offsetSize = 1;
- } else if (lastOffset < 0x10000) {
- offsetSize = 2;
- } else if (lastOffset < 0x1000000) {
- offsetSize = 3;
- } else {
- offsetSize = 4;
- }
- data.push(offsetSize);
- var relativeOffset = 1;
- for (i = 0; i < count + 1; i++) {
- if (offsetSize === 1) {
- data.push(relativeOffset & 0xFF);
- } else if (offsetSize === 2) {
- data.push(relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
- } else if (offsetSize === 3) {
- data.push(relativeOffset >> 16 & 0xFF, relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
- } else {
- data.push(relativeOffset >>> 24 & 0xFF, relativeOffset >> 16 & 0xFF, relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
- }
- if (objects[i]) {
- relativeOffset += objects[i].length;
- }
- }
- for (i = 0; i < count; i++) {
- if (trackers[i]) {
- trackers[i].offset(data.length);
- }
- for (var j = 0, jj = objects[i].length; j < jj; j++) {
- data.push(objects[i][j]);
- }
- }
- return data;
- }
- };
- return CFFCompiler;
-}();
-exports.CFFStandardStrings = CFFStandardStrings;
-exports.CFFParser = CFFParser;
-exports.CFF = CFF;
-exports.CFFHeader = CFFHeader;
-exports.CFFStrings = CFFStrings;
-exports.CFFIndex = CFFIndex;
-exports.CFFCharset = CFFCharset;
-exports.CFFTopDict = CFFTopDict;
-exports.CFFPrivateDict = CFFPrivateDict;
-exports.CFFCompiler = CFFCompiler;
-
-/***/ }),
-/* 12 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var MissingDataException = sharedUtil.MissingDataException;
-var arrayByteLength = sharedUtil.arrayByteLength;
-var arraysToBytes = sharedUtil.arraysToBytes;
-var assert = sharedUtil.assert;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var isInt = sharedUtil.isInt;
-var isEmptyObj = sharedUtil.isEmptyObj;
-var ChunkedStream = function ChunkedStreamClosure() {
- function ChunkedStream(length, chunkSize, manager) {
- this.bytes = new Uint8Array(length);
- this.start = 0;
- this.pos = 0;
- this.end = length;
- this.chunkSize = chunkSize;
- this.loadedChunks = [];
- this.numChunksLoaded = 0;
- this.numChunks = Math.ceil(length / chunkSize);
- this.manager = manager;
- this.progressiveDataLength = 0;
- this.lastSuccessfulEnsureByteChunk = -1;
- }
- ChunkedStream.prototype = {
- getMissingChunks: function ChunkedStream_getMissingChunks() {
- var chunks = [];
- for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
- if (!this.loadedChunks[chunk]) {
- chunks.push(chunk);
- }
- }
- return chunks;
- },
- getBaseStreams: function ChunkedStream_getBaseStreams() {
- return [this];
- },
- allChunksLoaded: function ChunkedStream_allChunksLoaded() {
- return this.numChunksLoaded === this.numChunks;
- },
- onReceiveData: function ChunkedStream_onReceiveData(begin, chunk) {
- var end = begin + chunk.byteLength;
- assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
- var length = this.bytes.length;
- assert(end % this.chunkSize === 0 || end === length, 'Bad end offset: ' + end);
- this.bytes.set(new Uint8Array(chunk), begin);
- var chunkSize = this.chunkSize;
- var beginChunk = Math.floor(begin / chunkSize);
- var endChunk = Math.floor((end - 1) / chunkSize) + 1;
- var curChunk;
- for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
- if (!this.loadedChunks[curChunk]) {
- this.loadedChunks[curChunk] = true;
- ++this.numChunksLoaded;
- }
- }
- },
- onReceiveProgressiveData: function ChunkedStream_onReceiveProgressiveData(data) {
- var position = this.progressiveDataLength;
- var beginChunk = Math.floor(position / this.chunkSize);
- this.bytes.set(new Uint8Array(data), position);
- position += data.byteLength;
- this.progressiveDataLength = position;
- var endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
- var curChunk;
- for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
- if (!this.loadedChunks[curChunk]) {
- this.loadedChunks[curChunk] = true;
- ++this.numChunksLoaded;
- }
- }
- },
- ensureByte: function ChunkedStream_ensureByte(pos) {
- var chunk = Math.floor(pos / this.chunkSize);
- if (chunk === this.lastSuccessfulEnsureByteChunk) {
- return;
- }
- if (!this.loadedChunks[chunk]) {
- throw new MissingDataException(pos, pos + 1);
- }
- this.lastSuccessfulEnsureByteChunk = chunk;
- },
- ensureRange: function ChunkedStream_ensureRange(begin, end) {
- if (begin >= end) {
- return;
- }
- if (end <= this.progressiveDataLength) {
- return;
- }
- var chunkSize = this.chunkSize;
- var beginChunk = Math.floor(begin / chunkSize);
- var endChunk = Math.floor((end - 1) / chunkSize) + 1;
- for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
- if (!this.loadedChunks[chunk]) {
- throw new MissingDataException(begin, end);
- }
- }
- },
- nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
- var chunk,
- numChunks = this.numChunks;
- for (var i = 0; i < numChunks; ++i) {
- chunk = (beginChunk + i) % numChunks;
- if (!this.loadedChunks[chunk]) {
- return chunk;
- }
- }
- return null;
- },
- hasChunk: function ChunkedStream_hasChunk(chunk) {
- return !!this.loadedChunks[chunk];
- },
- get length() {
- return this.end - this.start;
- },
- get isEmpty() {
- return this.length === 0;
- },
- getByte: function ChunkedStream_getByte() {
- var pos = this.pos;
- if (pos >= this.end) {
- return -1;
- }
- this.ensureByte(pos);
- return this.bytes[this.pos++];
- },
- getUint16: function ChunkedStream_getUint16() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- if (b0 === -1 || b1 === -1) {
- return -1;
- }
- return (b0 << 8) + b1;
- },
- getInt32: function ChunkedStream_getInt32() {
- var b0 = this.getByte();
- var b1 = this.getByte();
- var b2 = this.getByte();
- var b3 = this.getByte();
- return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
- },
- getBytes: function ChunkedStream_getBytes(length) {
- var bytes = this.bytes;
- var pos = this.pos;
- var strEnd = this.end;
- if (!length) {
- this.ensureRange(pos, strEnd);
- return bytes.subarray(pos, strEnd);
- }
- var end = pos + length;
- if (end > strEnd) {
- end = strEnd;
- }
- this.ensureRange(pos, end);
- this.pos = end;
- return bytes.subarray(pos, end);
- },
- peekByte: function ChunkedStream_peekByte() {
- var peekedByte = this.getByte();
- this.pos--;
- return peekedByte;
- },
- peekBytes: function ChunkedStream_peekBytes(length) {
- var bytes = this.getBytes(length);
- this.pos -= bytes.length;
- return bytes;
- },
- getByteRange: function ChunkedStream_getBytes(begin, end) {
- this.ensureRange(begin, end);
- return this.bytes.subarray(begin, end);
- },
- skip: function ChunkedStream_skip(n) {
- if (!n) {
- n = 1;
- }
- this.pos += n;
- },
- reset: function ChunkedStream_reset() {
- this.pos = this.start;
- },
- moveStart: function ChunkedStream_moveStart() {
- this.start = this.pos;
- },
- makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
- this.ensureRange(start, start + length);
- function ChunkedStreamSubstream() {}
- ChunkedStreamSubstream.prototype = Object.create(this);
- ChunkedStreamSubstream.prototype.getMissingChunks = function () {
- var chunkSize = this.chunkSize;
- var beginChunk = Math.floor(this.start / chunkSize);
- var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
- var missingChunks = [];
- for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
- if (!this.loadedChunks[chunk]) {
- missingChunks.push(chunk);
- }
- }
- return missingChunks;
- };
- var subStream = new ChunkedStreamSubstream();
- subStream.pos = subStream.start = start;
- subStream.end = start + length || this.end;
- subStream.dict = dict;
- return subStream;
- }
- };
- return ChunkedStream;
-}();
-var ChunkedStreamManager = function ChunkedStreamManagerClosure() {
- function ChunkedStreamManager(pdfNetworkStream, args) {
- var chunkSize = args.rangeChunkSize;
- var length = args.length;
- this.stream = new ChunkedStream(length, chunkSize, this);
- this.length = length;
- this.chunkSize = chunkSize;
- this.pdfNetworkStream = pdfNetworkStream;
- this.url = args.url;
- this.disableAutoFetch = args.disableAutoFetch;
- this.msgHandler = args.msgHandler;
- this.currRequestId = 0;
- this.chunksNeededByRequest = Object.create(null);
- this.requestsByChunk = Object.create(null);
- this.promisesByRequest = Object.create(null);
- this.progressiveDataLength = 0;
- this.aborted = false;
- this._loadedStreamCapability = createPromiseCapability();
- }
- ChunkedStreamManager.prototype = {
- onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
- return this._loadedStreamCapability.promise;
- },
- sendRequest: function ChunkedStreamManager_sendRequest(begin, end) {
- var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
- if (!rangeReader.isStreamingSupported) {
- rangeReader.onProgress = this.onProgress.bind(this);
- }
- var chunks = [],
- loaded = 0;
- var manager = this;
- var promise = new Promise(function (resolve, reject) {
- var readChunk = function (chunk) {
- try {
- if (!chunk.done) {
- var data = chunk.value;
- chunks.push(data);
- loaded += arrayByteLength(data);
- if (rangeReader.isStreamingSupported) {
- manager.onProgress({ loaded: loaded });
- }
- rangeReader.read().then(readChunk, reject);
- return;
- }
- var chunkData = arraysToBytes(chunks);
- chunks = null;
- resolve(chunkData);
- } catch (e) {
- reject(e);
- }
- };
- rangeReader.read().then(readChunk, reject);
- });
- promise.then(function (data) {
- if (this.aborted) {
- return;
- }
- this.onReceiveData({
- chunk: data,
- begin: begin
- });
- }.bind(this));
- },
- requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
- var missingChunks = this.stream.getMissingChunks();
- this._requestChunks(missingChunks);
- return this._loadedStreamCapability.promise;
- },
- _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
- var requestId = this.currRequestId++;
- var i, ii;
- var chunksNeeded = Object.create(null);
- this.chunksNeededByRequest[requestId] = chunksNeeded;
- for (i = 0, ii = chunks.length; i < ii; i++) {
- if (!this.stream.hasChunk(chunks[i])) {
- chunksNeeded[chunks[i]] = true;
- }
- }
- if (isEmptyObj(chunksNeeded)) {
- return Promise.resolve();
- }
- var capability = createPromiseCapability();
- this.promisesByRequest[requestId] = capability;
- var chunksToRequest = [];
- for (var chunk in chunksNeeded) {
- chunk = chunk | 0;
- if (!(chunk in this.requestsByChunk)) {
- this.requestsByChunk[chunk] = [];
- chunksToRequest.push(chunk);
- }
- this.requestsByChunk[chunk].push(requestId);
- }
- if (!chunksToRequest.length) {
- return capability.promise;
- }
- var groupedChunksToRequest = this.groupChunks(chunksToRequest);
- for (i = 0; i < groupedChunksToRequest.length; ++i) {
- var groupedChunk = groupedChunksToRequest[i];
- var begin = groupedChunk.beginChunk * this.chunkSize;
- var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
- this.sendRequest(begin, end);
- }
- return capability.promise;
- },
- getStream: function ChunkedStreamManager_getStream() {
- return this.stream;
- },
- requestRange: function ChunkedStreamManager_requestRange(begin, end) {
- end = Math.min(end, this.length);
- var beginChunk = this.getBeginChunk(begin);
- var endChunk = this.getEndChunk(end);
- var chunks = [];
- for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
- chunks.push(chunk);
- }
- return this._requestChunks(chunks);
- },
- requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
- ranges = ranges || [];
- var chunksToRequest = [];
- for (var i = 0; i < ranges.length; i++) {
- var beginChunk = this.getBeginChunk(ranges[i].begin);
- var endChunk = this.getEndChunk(ranges[i].end);
- for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
- if (chunksToRequest.indexOf(chunk) < 0) {
- chunksToRequest.push(chunk);
- }
- }
- }
- chunksToRequest.sort(function (a, b) {
- return a - b;
- });
- return this._requestChunks(chunksToRequest);
- },
- groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
- var groupedChunks = [];
- var beginChunk = -1;
- var prevChunk = -1;
- for (var i = 0; i < chunks.length; ++i) {
- var chunk = chunks[i];
- if (beginChunk < 0) {
- beginChunk = chunk;
- }
- if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
- groupedChunks.push({
- beginChunk: beginChunk,
- endChunk: prevChunk + 1
- });
- beginChunk = chunk;
- }
- if (i + 1 === chunks.length) {
- groupedChunks.push({
- beginChunk: beginChunk,
- endChunk: chunk + 1
- });
- }
- prevChunk = chunk;
- }
- return groupedChunks;
- },
- onProgress: function ChunkedStreamManager_onProgress(args) {
- var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
- this.msgHandler.send('DocProgress', {
- loaded: bytesLoaded,
- total: this.length
- });
- },
- onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
- var chunk = args.chunk;
- var isProgressive = args.begin === undefined;
- var begin = isProgressive ? this.progressiveDataLength : args.begin;
- var end = begin + chunk.byteLength;
- var beginChunk = Math.floor(begin / this.chunkSize);
- var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
- if (isProgressive) {
- this.stream.onReceiveProgressiveData(chunk);
- this.progressiveDataLength = end;
- } else {
- this.stream.onReceiveData(begin, chunk);
- }
- if (this.stream.allChunksLoaded()) {
- this._loadedStreamCapability.resolve(this.stream);
- }
- var loadedRequests = [];
- var i, requestId;
- for (chunk = beginChunk; chunk < endChunk; ++chunk) {
- var requestIds = this.requestsByChunk[chunk] || [];
- delete this.requestsByChunk[chunk];
- for (i = 0; i < requestIds.length; ++i) {
- requestId = requestIds[i];
- var chunksNeeded = this.chunksNeededByRequest[requestId];
- if (chunk in chunksNeeded) {
- delete chunksNeeded[chunk];
- }
- if (!isEmptyObj(chunksNeeded)) {
- continue;
- }
- loadedRequests.push(requestId);
- }
- }
- if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
- var nextEmptyChunk;
- if (this.stream.numChunksLoaded === 1) {
- var lastChunk = this.stream.numChunks - 1;
- if (!this.stream.hasChunk(lastChunk)) {
- nextEmptyChunk = lastChunk;
- }
- } else {
- nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
- }
- if (isInt(nextEmptyChunk)) {
- this._requestChunks([nextEmptyChunk]);
- }
- }
- for (i = 0; i < loadedRequests.length; ++i) {
- requestId = loadedRequests[i];
- var capability = this.promisesByRequest[requestId];
- delete this.promisesByRequest[requestId];
- capability.resolve();
- }
- this.msgHandler.send('DocProgress', {
- loaded: this.stream.numChunksLoaded * this.chunkSize,
- total: this.length
- });
- },
- onError: function ChunkedStreamManager_onError(err) {
- this._loadedStreamCapability.reject(err);
- },
- getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
- var chunk = Math.floor(begin / this.chunkSize);
- return chunk;
- },
- getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
- var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
- return chunk;
- },
- abort: function ChunkedStreamManager_abort() {
- this.aborted = true;
- if (this.pdfNetworkStream) {
- this.pdfNetworkStream.cancelAllRequests('abort');
- }
- for (var requestId in this.promisesByRequest) {
- var capability = this.promisesByRequest[requestId];
- capability.reject(new Error('Request was aborted'));
- }
- }
- };
- return ChunkedStreamManager;
-}();
-exports.ChunkedStream = ChunkedStream;
-exports.ChunkedStreamManager = ChunkedStreamManager;
-
-/***/ }),
-/* 13 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var PasswordException = sharedUtil.PasswordException;
-var PasswordResponses = sharedUtil.PasswordResponses;
-var bytesToString = sharedUtil.bytesToString;
-var warn = sharedUtil.warn;
-var error = sharedUtil.error;
-var assert = sharedUtil.assert;
-var isInt = sharedUtil.isInt;
-var stringToBytes = sharedUtil.stringToBytes;
-var utf8StringToString = sharedUtil.utf8StringToString;
-var Name = corePrimitives.Name;
-var isName = corePrimitives.isName;
-var isDict = corePrimitives.isDict;
-var DecryptStream = coreStream.DecryptStream;
-var ARCFourCipher = function ARCFourCipherClosure() {
- function ARCFourCipher(key) {
- this.a = 0;
- this.b = 0;
- var s = new Uint8Array(256);
- var i,
- j = 0,
- tmp,
- keyLength = key.length;
- for (i = 0; i < 256; ++i) {
- s[i] = i;
- }
- for (i = 0; i < 256; ++i) {
- tmp = s[i];
- j = j + tmp + key[i % keyLength] & 0xFF;
- s[i] = s[j];
- s[j] = tmp;
- }
- this.s = s;
- }
- ARCFourCipher.prototype = {
- encryptBlock: function ARCFourCipher_encryptBlock(data) {
- var i,
- n = data.length,
- tmp,
- tmp2;
- var a = this.a,
- b = this.b,
- s = this.s;
- var output = new Uint8Array(n);
- for (i = 0; i < n; ++i) {
- a = a + 1 & 0xFF;
- tmp = s[a];
- b = b + tmp & 0xFF;
- tmp2 = s[b];
- s[a] = tmp2;
- s[b] = tmp;
- output[i] = data[i] ^ s[tmp + tmp2 & 0xFF];
- }
- this.a = a;
- this.b = b;
- return output;
- }
- };
- ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
- return ARCFourCipher;
-}();
-var calculateMD5 = function calculateMD5Closure() {
- var r = 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]);
- var k = 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]);
- function hash(data, offset, length) {
- var h0 = 1732584193,
- h1 = -271733879,
- h2 = -1732584194,
- h3 = 271733878;
- var paddedLength = length + 72 & ~63;
- var padded = new Uint8Array(paddedLength);
- var i, j, n;
- for (i = 0; i < length; ++i) {
- padded[i] = data[offset++];
- }
- padded[i++] = 0x80;
- n = paddedLength - 8;
- while (i < n) {
- padded[i++] = 0;
- }
- padded[i++] = length << 3 & 0xFF;
- padded[i++] = length >> 5 & 0xFF;
- padded[i++] = length >> 13 & 0xFF;
- padded[i++] = length >> 21 & 0xFF;
- padded[i++] = length >>> 29 & 0xFF;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- var w = new Int32Array(16);
- for (i = 0; i < paddedLength;) {
- for (j = 0; j < 16; ++j, i += 4) {
- w[j] = padded[i] | padded[i + 1] << 8 | padded[i + 2] << 16 | padded[i + 3] << 24;
- }
- var a = h0,
- b = h1,
- c = h2,
- d = h3,
- f,
- g;
- for (j = 0; j < 64; ++j) {
- if (j < 16) {
- f = b & c | ~b & d;
- g = j;
- } else if (j < 32) {
- f = d & b | ~d & c;
- g = 5 * j + 1 & 15;
- } else if (j < 48) {
- f = b ^ c ^ d;
- g = 3 * j + 5 & 15;
- } else {
- f = c ^ (b | ~d);
- g = 7 * j & 15;
- }
- var tmp = d,
- rotateArg = a + f + k[j] + w[g] | 0,
- rotate = r[j];
- d = c;
- c = b;
- b = b + (rotateArg << rotate | rotateArg >>> 32 - rotate) | 0;
- a = tmp;
- }
- h0 = h0 + a | 0;
- h1 = h1 + b | 0;
- h2 = h2 + c | 0;
- h3 = h3 + d | 0;
- }
- return new Uint8Array([h0 & 0xFF, h0 >> 8 & 0xFF, h0 >> 16 & 0xFF, h0 >>> 24 & 0xFF, h1 & 0xFF, h1 >> 8 & 0xFF, h1 >> 16 & 0xFF, h1 >>> 24 & 0xFF, h2 & 0xFF, h2 >> 8 & 0xFF, h2 >> 16 & 0xFF, h2 >>> 24 & 0xFF, h3 & 0xFF, h3 >> 8 & 0xFF, h3 >> 16 & 0xFF, h3 >>> 24 & 0xFF]);
- }
- return hash;
-}();
-var Word64 = function Word64Closure() {
- function Word64(highInteger, lowInteger) {
- this.high = highInteger | 0;
- this.low = lowInteger | 0;
- }
- Word64.prototype = {
- and: function Word64_and(word) {
- this.high &= word.high;
- this.low &= word.low;
- },
- xor: function Word64_xor(word) {
- this.high ^= word.high;
- this.low ^= word.low;
- },
- or: function Word64_or(word) {
- this.high |= word.high;
- this.low |= word.low;
- },
- shiftRight: function Word64_shiftRight(places) {
- if (places >= 32) {
- this.low = this.high >>> places - 32 | 0;
- this.high = 0;
- } else {
- this.low = this.low >>> places | this.high << 32 - places;
- this.high = this.high >>> places | 0;
- }
- },
- shiftLeft: function Word64_shiftLeft(places) {
- if (places >= 32) {
- this.high = this.low << places - 32;
- this.low = 0;
- } else {
- this.high = this.high << places | this.low >>> 32 - places;
- this.low = this.low << places;
- }
- },
- rotateRight: function Word64_rotateRight(places) {
- var low, high;
- if (places & 32) {
- high = this.low;
- low = this.high;
- } else {
- low = this.low;
- high = this.high;
- }
- places &= 31;
- this.low = low >>> places | high << 32 - places;
- this.high = high >>> places | low << 32 - places;
- },
- not: function Word64_not() {
- this.high = ~this.high;
- this.low = ~this.low;
- },
- add: function Word64_add(word) {
- var lowAdd = (this.low >>> 0) + (word.low >>> 0);
- var highAdd = (this.high >>> 0) + (word.high >>> 0);
- if (lowAdd > 0xFFFFFFFF) {
- highAdd += 1;
- }
- this.low = lowAdd | 0;
- this.high = highAdd | 0;
- },
- copyTo: function Word64_copyTo(bytes, offset) {
- bytes[offset] = this.high >>> 24 & 0xFF;
- bytes[offset + 1] = this.high >> 16 & 0xFF;
- bytes[offset + 2] = this.high >> 8 & 0xFF;
- bytes[offset + 3] = this.high & 0xFF;
- bytes[offset + 4] = this.low >>> 24 & 0xFF;
- bytes[offset + 5] = this.low >> 16 & 0xFF;
- bytes[offset + 6] = this.low >> 8 & 0xFF;
- bytes[offset + 7] = this.low & 0xFF;
- },
- assign: function Word64_assign(word) {
- this.high = word.high;
- this.low = word.low;
- }
- };
- return Word64;
-}();
-var calculateSHA256 = function calculateSHA256Closure() {
- function rotr(x, n) {
- return x >>> n | x << 32 - n;
- }
- function ch(x, y, z) {
- return x & y ^ ~x & z;
- }
- function maj(x, y, z) {
- return x & y ^ x & z ^ y & z;
- }
- function sigma(x) {
- return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
- }
- function sigmaPrime(x) {
- return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
- }
- function littleSigma(x) {
- return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
- }
- function littleSigmaPrime(x) {
- return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
- }
- var k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
- function hash(data, offset, length) {
- var h0 = 0x6a09e667,
- h1 = 0xbb67ae85,
- h2 = 0x3c6ef372,
- h3 = 0xa54ff53a,
- h4 = 0x510e527f,
- h5 = 0x9b05688c,
- h6 = 0x1f83d9ab,
- h7 = 0x5be0cd19;
- var paddedLength = Math.ceil((length + 9) / 64) * 64;
- var padded = new Uint8Array(paddedLength);
- var i, j, n;
- for (i = 0; i < length; ++i) {
- padded[i] = data[offset++];
- }
- padded[i++] = 0x80;
- n = paddedLength - 8;
- while (i < n) {
- padded[i++] = 0;
- }
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = length >>> 29 & 0xFF;
- padded[i++] = length >> 21 & 0xFF;
- padded[i++] = length >> 13 & 0xFF;
- padded[i++] = length >> 5 & 0xFF;
- padded[i++] = length << 3 & 0xFF;
- var w = new Uint32Array(64);
- for (i = 0; i < paddedLength;) {
- for (j = 0; j < 16; ++j) {
- w[j] = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
- i += 4;
- }
- for (j = 16; j < 64; ++j) {
- w[j] = littleSigmaPrime(w[j - 2]) + w[j - 7] + littleSigma(w[j - 15]) + w[j - 16] | 0;
- }
- var a = h0,
- b = h1,
- c = h2,
- d = h3,
- e = h4,
- f = h5,
- g = h6,
- h = h7,
- t1,
- t2;
- for (j = 0; j < 64; ++j) {
- t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j];
- t2 = sigma(a) + maj(a, b, c);
- h = g;
- g = f;
- f = e;
- e = d + t1 | 0;
- d = c;
- c = b;
- b = a;
- a = t1 + t2 | 0;
- }
- h0 = h0 + a | 0;
- h1 = h1 + b | 0;
- h2 = h2 + c | 0;
- h3 = h3 + d | 0;
- h4 = h4 + e | 0;
- h5 = h5 + f | 0;
- h6 = h6 + g | 0;
- h7 = h7 + h | 0;
- }
- return new Uint8Array([h0 >> 24 & 0xFF, h0 >> 16 & 0xFF, h0 >> 8 & 0xFF, h0 & 0xFF, h1 >> 24 & 0xFF, h1 >> 16 & 0xFF, h1 >> 8 & 0xFF, h1 & 0xFF, h2 >> 24 & 0xFF, h2 >> 16 & 0xFF, h2 >> 8 & 0xFF, h2 & 0xFF, h3 >> 24 & 0xFF, h3 >> 16 & 0xFF, h3 >> 8 & 0xFF, h3 & 0xFF, h4 >> 24 & 0xFF, h4 >> 16 & 0xFF, h4 >> 8 & 0xFF, h4 & 0xFF, h5 >> 24 & 0xFF, h5 >> 16 & 0xFF, h5 >> 8 & 0xFF, h5 & 0xFF, h6 >> 24 & 0xFF, h6 >> 16 & 0xFF, h6 >> 8 & 0xFF, h6 & 0xFF, h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF]);
- }
- return hash;
-}();
-var calculateSHA512 = function calculateSHA512Closure() {
- function ch(result, x, y, z, tmp) {
- result.assign(x);
- result.and(y);
- tmp.assign(x);
- tmp.not();
- tmp.and(z);
- result.xor(tmp);
- }
- function maj(result, x, y, z, tmp) {
- result.assign(x);
- result.and(y);
- tmp.assign(x);
- tmp.and(z);
- result.xor(tmp);
- tmp.assign(y);
- tmp.and(z);
- result.xor(tmp);
- }
- function sigma(result, x, tmp) {
- result.assign(x);
- result.rotateRight(28);
- tmp.assign(x);
- tmp.rotateRight(34);
- result.xor(tmp);
- tmp.assign(x);
- tmp.rotateRight(39);
- result.xor(tmp);
- }
- function sigmaPrime(result, x, tmp) {
- result.assign(x);
- result.rotateRight(14);
- tmp.assign(x);
- tmp.rotateRight(18);
- result.xor(tmp);
- tmp.assign(x);
- tmp.rotateRight(41);
- result.xor(tmp);
- }
- function littleSigma(result, x, tmp) {
- result.assign(x);
- result.rotateRight(1);
- tmp.assign(x);
- tmp.rotateRight(8);
- result.xor(tmp);
- tmp.assign(x);
- tmp.shiftRight(7);
- result.xor(tmp);
- }
- function littleSigmaPrime(result, x, tmp) {
- result.assign(x);
- result.rotateRight(19);
- tmp.assign(x);
- tmp.rotateRight(61);
- result.xor(tmp);
- tmp.assign(x);
- tmp.shiftRight(6);
- result.xor(tmp);
- }
- var k = [new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd), new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc), new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019), new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118), new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe), new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2), new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1), new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694), new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3), new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65), new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483), new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5), new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210), new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4), new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725), new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70), new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926), new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df), new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8), new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b), new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001), new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30), new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910), new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8), new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53), new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8), new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb), new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3), new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60), new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec), new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9), new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b), new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207), new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178), new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6), new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b), new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493), new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c), new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)];
- function hash(data, offset, length, mode384) {
- mode384 = !!mode384;
- var h0, h1, h2, h3, h4, h5, h6, h7;
- if (!mode384) {
- h0 = new Word64(0x6a09e667, 0xf3bcc908);
- h1 = new Word64(0xbb67ae85, 0x84caa73b);
- h2 = new Word64(0x3c6ef372, 0xfe94f82b);
- h3 = new Word64(0xa54ff53a, 0x5f1d36f1);
- h4 = new Word64(0x510e527f, 0xade682d1);
- h5 = new Word64(0x9b05688c, 0x2b3e6c1f);
- h6 = new Word64(0x1f83d9ab, 0xfb41bd6b);
- h7 = new Word64(0x5be0cd19, 0x137e2179);
- } else {
- h0 = new Word64(0xcbbb9d5d, 0xc1059ed8);
- h1 = new Word64(0x629a292a, 0x367cd507);
- h2 = new Word64(0x9159015a, 0x3070dd17);
- h3 = new Word64(0x152fecd8, 0xf70e5939);
- h4 = new Word64(0x67332667, 0xffc00b31);
- h5 = new Word64(0x8eb44a87, 0x68581511);
- h6 = new Word64(0xdb0c2e0d, 0x64f98fa7);
- h7 = new Word64(0x47b5481d, 0xbefa4fa4);
- }
- var paddedLength = Math.ceil((length + 17) / 128) * 128;
- var padded = new Uint8Array(paddedLength);
- var i, j, n;
- for (i = 0; i < length; ++i) {
- padded[i] = data[offset++];
- }
- padded[i++] = 0x80;
- n = paddedLength - 16;
- while (i < n) {
- padded[i++] = 0;
- }
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = 0;
- padded[i++] = length >>> 29 & 0xFF;
- padded[i++] = length >> 21 & 0xFF;
- padded[i++] = length >> 13 & 0xFF;
- padded[i++] = length >> 5 & 0xFF;
- padded[i++] = length << 3 & 0xFF;
- var w = new Array(80);
- for (i = 0; i < 80; i++) {
- w[i] = new Word64(0, 0);
- }
- var a = new Word64(0, 0),
- b = new Word64(0, 0),
- c = new Word64(0, 0);
- var d = new Word64(0, 0),
- e = new Word64(0, 0),
- f = new Word64(0, 0);
- var g = new Word64(0, 0),
- h = new Word64(0, 0);
- var t1 = new Word64(0, 0),
- t2 = new Word64(0, 0);
- var tmp1 = new Word64(0, 0),
- tmp2 = new Word64(0, 0),
- tmp3;
- for (i = 0; i < paddedLength;) {
- for (j = 0; j < 16; ++j) {
- w[j].high = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
- w[j].low = padded[i + 4] << 24 | padded[i + 5] << 16 | padded[i + 6] << 8 | padded[i + 7];
- i += 8;
- }
- for (j = 16; j < 80; ++j) {
- tmp3 = w[j];
- littleSigmaPrime(tmp3, w[j - 2], tmp2);
- tmp3.add(w[j - 7]);
- littleSigma(tmp1, w[j - 15], tmp2);
- tmp3.add(tmp1);
- tmp3.add(w[j - 16]);
- }
- a.assign(h0);
- b.assign(h1);
- c.assign(h2);
- d.assign(h3);
- e.assign(h4);
- f.assign(h5);
- g.assign(h6);
- h.assign(h7);
- for (j = 0; j < 80; ++j) {
- t1.assign(h);
- sigmaPrime(tmp1, e, tmp2);
- t1.add(tmp1);
- ch(tmp1, e, f, g, tmp2);
- t1.add(tmp1);
- t1.add(k[j]);
- t1.add(w[j]);
- sigma(t2, a, tmp2);
- maj(tmp1, a, b, c, tmp2);
- t2.add(tmp1);
- tmp3 = h;
- h = g;
- g = f;
- f = e;
- d.add(t1);
- e = d;
- d = c;
- c = b;
- b = a;
- tmp3.assign(t1);
- tmp3.add(t2);
- a = tmp3;
- }
- h0.add(a);
- h1.add(b);
- h2.add(c);
- h3.add(d);
- h4.add(e);
- h5.add(f);
- h6.add(g);
- h7.add(h);
- }
- var result;
- if (!mode384) {
- result = new Uint8Array(64);
- h0.copyTo(result, 0);
- h1.copyTo(result, 8);
- h2.copyTo(result, 16);
- h3.copyTo(result, 24);
- h4.copyTo(result, 32);
- h5.copyTo(result, 40);
- h6.copyTo(result, 48);
- h7.copyTo(result, 56);
- } else {
- result = new Uint8Array(48);
- h0.copyTo(result, 0);
- h1.copyTo(result, 8);
- h2.copyTo(result, 16);
- h3.copyTo(result, 24);
- h4.copyTo(result, 32);
- h5.copyTo(result, 40);
- }
- return result;
- }
- return hash;
-}();
-var calculateSHA384 = function calculateSHA384Closure() {
- function hash(data, offset, length) {
- return calculateSHA512(data, offset, length, true);
- }
- return hash;
-}();
-var NullCipher = function NullCipherClosure() {
- function NullCipher() {}
- NullCipher.prototype = {
- decryptBlock: function NullCipher_decryptBlock(data) {
- return data;
- }
- };
- return NullCipher;
-}();
-var AES128Cipher = function AES128CipherClosure() {
- var rcon = new Uint8Array([0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]);
- var s = new Uint8Array([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]);
- var inv_s = new Uint8Array([0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]);
- var mixCol = new Uint8Array(256);
- for (var i = 0; i < 256; i++) {
- if (i < 128) {
- mixCol[i] = i << 1;
- } else {
- mixCol[i] = i << 1 ^ 0x1b;
- }
- }
- var mix = new Uint32Array([0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
- function expandKey128(cipherKey) {
- var b = 176,
- result = new Uint8Array(b);
- result.set(cipherKey);
- for (var j = 16, i = 1; j < b; ++i) {
- var t1 = result[j - 3],
- t2 = result[j - 2],
- t3 = result[j - 1],
- t4 = result[j - 4];
- t1 = s[t1];
- t2 = s[t2];
- t3 = s[t3];
- t4 = s[t4];
- t1 = t1 ^ rcon[i];
- for (var n = 0; n < 4; ++n) {
- result[j] = t1 ^= result[j - 16];
- j++;
- result[j] = t2 ^= result[j - 16];
- j++;
- result[j] = t3 ^= result[j - 16];
- j++;
- result[j] = t4 ^= result[j - 16];
- j++;
- }
- }
- return result;
- }
- function decrypt128(input, key) {
- var state = new Uint8Array(16);
- state.set(input);
- var i, j, k;
- var t, u, v;
- for (j = 0, k = 160; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- for (i = 9; i >= 1; --i) {
- t = state[13];
- state[13] = state[9];
- state[9] = state[5];
- state[5] = state[1];
- state[1] = t;
- t = state[14];
- u = state[10];
- state[14] = state[6];
- state[10] = state[2];
- state[6] = t;
- state[2] = u;
- t = state[15];
- u = state[11];
- v = state[7];
- state[15] = state[3];
- state[11] = t;
- state[7] = u;
- state[3] = v;
- for (j = 0; j < 16; ++j) {
- state[j] = inv_s[state[j]];
- }
- for (j = 0, k = i * 16; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- for (j = 0; j < 16; j += 4) {
- var s0 = mix[state[j]],
- s1 = mix[state[j + 1]],
- s2 = mix[state[j + 2]],
- s3 = mix[state[j + 3]];
- t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
- state[j] = t >>> 24 & 0xFF;
- state[j + 1] = t >> 16 & 0xFF;
- state[j + 2] = t >> 8 & 0xFF;
- state[j + 3] = t & 0xFF;
- }
- }
- t = state[13];
- state[13] = state[9];
- state[9] = state[5];
- state[5] = state[1];
- state[1] = t;
- t = state[14];
- u = state[10];
- state[14] = state[6];
- state[10] = state[2];
- state[6] = t;
- state[2] = u;
- t = state[15];
- u = state[11];
- v = state[7];
- state[15] = state[3];
- state[11] = t;
- state[7] = u;
- state[3] = v;
- for (j = 0; j < 16; ++j) {
- state[j] = inv_s[state[j]];
- state[j] ^= key[j];
- }
- return state;
- }
- function encrypt128(input, key) {
- var t, u, v, k;
- var state = new Uint8Array(16);
- state.set(input);
- for (j = 0; j < 16; ++j) {
- state[j] ^= key[j];
- }
- for (i = 1; i < 10; i++) {
- for (j = 0; j < 16; ++j) {
- state[j] = s[state[j]];
- }
- v = state[1];
- state[1] = state[5];
- state[5] = state[9];
- state[9] = state[13];
- state[13] = v;
- v = state[2];
- u = state[6];
- state[2] = state[10];
- state[6] = state[14];
- state[10] = v;
- state[14] = u;
- v = state[3];
- u = state[7];
- t = state[11];
- state[3] = state[15];
- state[7] = v;
- state[11] = u;
- state[15] = t;
- for (var j = 0; j < 16; j += 4) {
- var s0 = state[j + 0],
- s1 = state[j + 1];
- var s2 = state[j + 2],
- s3 = state[j + 3];
- t = s0 ^ s1 ^ s2 ^ s3;
- state[j + 0] ^= t ^ mixCol[s0 ^ s1];
- state[j + 1] ^= t ^ mixCol[s1 ^ s2];
- state[j + 2] ^= t ^ mixCol[s2 ^ s3];
- state[j + 3] ^= t ^ mixCol[s3 ^ s0];
- }
- for (j = 0, k = i * 16; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- }
- for (j = 0; j < 16; ++j) {
- state[j] = s[state[j]];
- }
- v = state[1];
- state[1] = state[5];
- state[5] = state[9];
- state[9] = state[13];
- state[13] = v;
- v = state[2];
- u = state[6];
- state[2] = state[10];
- state[6] = state[14];
- state[10] = v;
- state[14] = u;
- v = state[3];
- u = state[7];
- t = state[11];
- state[3] = state[15];
- state[7] = v;
- state[11] = u;
- state[15] = t;
- for (j = 0, k = 160; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- return state;
- }
- function AES128Cipher(key) {
- this.key = expandKey128(key);
- this.buffer = new Uint8Array(16);
- this.bufferPosition = 0;
- }
- function decryptBlock2(data, finalize) {
- var i,
- j,
- ii,
- sourceLength = data.length,
- buffer = this.buffer,
- bufferLength = this.bufferPosition,
- result = [],
- iv = this.iv;
- for (i = 0; i < sourceLength; ++i) {
- buffer[bufferLength] = data[i];
- ++bufferLength;
- if (bufferLength < 16) {
- continue;
- }
- var plain = decrypt128(buffer, this.key);
- for (j = 0; j < 16; ++j) {
- plain[j] ^= iv[j];
- }
- iv = buffer;
- result.push(plain);
- buffer = new Uint8Array(16);
- bufferLength = 0;
- }
- this.buffer = buffer;
- this.bufferLength = bufferLength;
- this.iv = iv;
- if (result.length === 0) {
- return new Uint8Array([]);
- }
- var outputLength = 16 * result.length;
- if (finalize) {
- var lastBlock = result[result.length - 1];
- var psLen = lastBlock[15];
- if (psLen <= 16) {
- for (i = 15, ii = 16 - psLen; i >= ii; --i) {
- if (lastBlock[i] !== psLen) {
- psLen = 0;
- break;
- }
- }
- outputLength -= psLen;
- result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
- }
- }
- var output = new Uint8Array(outputLength);
- for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
- output.set(result[i], j);
- }
- return output;
- }
- AES128Cipher.prototype = {
- decryptBlock: function AES128Cipher_decryptBlock(data, finalize) {
- var i,
- sourceLength = data.length;
- var buffer = this.buffer,
- bufferLength = this.bufferPosition;
- for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
- buffer[bufferLength] = data[i];
- }
- if (bufferLength < 16) {
- this.bufferLength = bufferLength;
- return new Uint8Array([]);
- }
- this.iv = buffer;
- this.buffer = new Uint8Array(16);
- this.bufferLength = 0;
- this.decryptBlock = decryptBlock2;
- return this.decryptBlock(data.subarray(16), finalize);
- },
- encrypt: function AES128Cipher_encrypt(data, iv) {
- var i,
- j,
- ii,
- sourceLength = data.length,
- buffer = this.buffer,
- bufferLength = this.bufferPosition,
- result = [];
- if (!iv) {
- iv = new Uint8Array(16);
- }
- for (i = 0; i < sourceLength; ++i) {
- buffer[bufferLength] = data[i];
- ++bufferLength;
- if (bufferLength < 16) {
- continue;
- }
- for (j = 0; j < 16; ++j) {
- buffer[j] ^= iv[j];
- }
- var cipher = encrypt128(buffer, this.key);
- iv = cipher;
- result.push(cipher);
- buffer = new Uint8Array(16);
- bufferLength = 0;
- }
- this.buffer = buffer;
- this.bufferLength = bufferLength;
- this.iv = iv;
- if (result.length === 0) {
- return new Uint8Array([]);
- }
- var outputLength = 16 * result.length;
- var output = new Uint8Array(outputLength);
- for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
- output.set(result[i], j);
- }
- return output;
- }
- };
- return AES128Cipher;
-}();
-var AES256Cipher = function AES256CipherClosure() {
- var s = new Uint8Array([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]);
- var inv_s = new Uint8Array([0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]);
- var mixCol = new Uint8Array(256);
- for (var i = 0; i < 256; i++) {
- if (i < 128) {
- mixCol[i] = i << 1;
- } else {
- mixCol[i] = i << 1 ^ 0x1b;
- }
- }
- var mix = new Uint32Array([0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
- function expandKey256(cipherKey) {
- var b = 240,
- result = new Uint8Array(b);
- var r = 1;
- result.set(cipherKey);
- for (var j = 32, i = 1; j < b; ++i) {
- if (j % 32 === 16) {
- t1 = s[t1];
- t2 = s[t2];
- t3 = s[t3];
- t4 = s[t4];
- } else if (j % 32 === 0) {
- var t1 = result[j - 3],
- t2 = result[j - 2],
- t3 = result[j - 1],
- t4 = result[j - 4];
- t1 = s[t1];
- t2 = s[t2];
- t3 = s[t3];
- t4 = s[t4];
- t1 = t1 ^ r;
- if ((r <<= 1) >= 256) {
- r = (r ^ 0x1b) & 0xFF;
- }
- }
- for (var n = 0; n < 4; ++n) {
- result[j] = t1 ^= result[j - 32];
- j++;
- result[j] = t2 ^= result[j - 32];
- j++;
- result[j] = t3 ^= result[j - 32];
- j++;
- result[j] = t4 ^= result[j - 32];
- j++;
- }
- }
- return result;
- }
- function decrypt256(input, key) {
- var state = new Uint8Array(16);
- state.set(input);
- var i, j, k;
- var t, u, v;
- for (j = 0, k = 224; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- for (i = 13; i >= 1; --i) {
- t = state[13];
- state[13] = state[9];
- state[9] = state[5];
- state[5] = state[1];
- state[1] = t;
- t = state[14];
- u = state[10];
- state[14] = state[6];
- state[10] = state[2];
- state[6] = t;
- state[2] = u;
- t = state[15];
- u = state[11];
- v = state[7];
- state[15] = state[3];
- state[11] = t;
- state[7] = u;
- state[3] = v;
- for (j = 0; j < 16; ++j) {
- state[j] = inv_s[state[j]];
- }
- for (j = 0, k = i * 16; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- for (j = 0; j < 16; j += 4) {
- var s0 = mix[state[j]],
- s1 = mix[state[j + 1]],
- s2 = mix[state[j + 2]],
- s3 = mix[state[j + 3]];
- t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
- state[j] = t >>> 24 & 0xFF;
- state[j + 1] = t >> 16 & 0xFF;
- state[j + 2] = t >> 8 & 0xFF;
- state[j + 3] = t & 0xFF;
- }
- }
- t = state[13];
- state[13] = state[9];
- state[9] = state[5];
- state[5] = state[1];
- state[1] = t;
- t = state[14];
- u = state[10];
- state[14] = state[6];
- state[10] = state[2];
- state[6] = t;
- state[2] = u;
- t = state[15];
- u = state[11];
- v = state[7];
- state[15] = state[3];
- state[11] = t;
- state[7] = u;
- state[3] = v;
- for (j = 0; j < 16; ++j) {
- state[j] = inv_s[state[j]];
- state[j] ^= key[j];
- }
- return state;
- }
- function encrypt256(input, key) {
- var t, u, v, k;
- var state = new Uint8Array(16);
- state.set(input);
- for (j = 0; j < 16; ++j) {
- state[j] ^= key[j];
- }
- for (i = 1; i < 14; i++) {
- for (j = 0; j < 16; ++j) {
- state[j] = s[state[j]];
- }
- v = state[1];
- state[1] = state[5];
- state[5] = state[9];
- state[9] = state[13];
- state[13] = v;
- v = state[2];
- u = state[6];
- state[2] = state[10];
- state[6] = state[14];
- state[10] = v;
- state[14] = u;
- v = state[3];
- u = state[7];
- t = state[11];
- state[3] = state[15];
- state[7] = v;
- state[11] = u;
- state[15] = t;
- for (var j = 0; j < 16; j += 4) {
- var s0 = state[j + 0],
- s1 = state[j + 1];
- var s2 = state[j + 2],
- s3 = state[j + 3];
- t = s0 ^ s1 ^ s2 ^ s3;
- state[j + 0] ^= t ^ mixCol[s0 ^ s1];
- state[j + 1] ^= t ^ mixCol[s1 ^ s2];
- state[j + 2] ^= t ^ mixCol[s2 ^ s3];
- state[j + 3] ^= t ^ mixCol[s3 ^ s0];
- }
- for (j = 0, k = i * 16; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- }
- for (j = 0; j < 16; ++j) {
- state[j] = s[state[j]];
- }
- v = state[1];
- state[1] = state[5];
- state[5] = state[9];
- state[9] = state[13];
- state[13] = v;
- v = state[2];
- u = state[6];
- state[2] = state[10];
- state[6] = state[14];
- state[10] = v;
- state[14] = u;
- v = state[3];
- u = state[7];
- t = state[11];
- state[3] = state[15];
- state[7] = v;
- state[11] = u;
- state[15] = t;
- for (j = 0, k = 224; j < 16; ++j, ++k) {
- state[j] ^= key[k];
- }
- return state;
- }
- function AES256Cipher(key) {
- this.key = expandKey256(key);
- this.buffer = new Uint8Array(16);
- this.bufferPosition = 0;
- }
- function decryptBlock2(data, finalize) {
- var i,
- j,
- ii,
- sourceLength = data.length,
- buffer = this.buffer,
- bufferLength = this.bufferPosition,
- result = [],
- iv = this.iv;
- for (i = 0; i < sourceLength; ++i) {
- buffer[bufferLength] = data[i];
- ++bufferLength;
- if (bufferLength < 16) {
- continue;
- }
- var plain = decrypt256(buffer, this.key);
- for (j = 0; j < 16; ++j) {
- plain[j] ^= iv[j];
- }
- iv = buffer;
- result.push(plain);
- buffer = new Uint8Array(16);
- bufferLength = 0;
- }
- this.buffer = buffer;
- this.bufferLength = bufferLength;
- this.iv = iv;
- if (result.length === 0) {
- return new Uint8Array([]);
- }
- var outputLength = 16 * result.length;
- if (finalize) {
- var lastBlock = result[result.length - 1];
- var psLen = lastBlock[15];
- if (psLen <= 16) {
- for (i = 15, ii = 16 - psLen; i >= ii; --i) {
- if (lastBlock[i] !== psLen) {
- psLen = 0;
- break;
- }
- }
- outputLength -= psLen;
- result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
- }
- }
- var output = new Uint8Array(outputLength);
- for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
- output.set(result[i], j);
- }
- return output;
- }
- AES256Cipher.prototype = {
- decryptBlock: function AES256Cipher_decryptBlock(data, finalize, iv) {
- var i,
- sourceLength = data.length;
- var buffer = this.buffer,
- bufferLength = this.bufferPosition;
- if (iv) {
- this.iv = iv;
- } else {
- for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
- buffer[bufferLength] = data[i];
- }
- if (bufferLength < 16) {
- this.bufferLength = bufferLength;
- return new Uint8Array([]);
- }
- this.iv = buffer;
- data = data.subarray(16);
- }
- this.buffer = new Uint8Array(16);
- this.bufferLength = 0;
- this.decryptBlock = decryptBlock2;
- return this.decryptBlock(data, finalize);
- },
- encrypt: function AES256Cipher_encrypt(data, iv) {
- var i,
- j,
- ii,
- sourceLength = data.length,
- buffer = this.buffer,
- bufferLength = this.bufferPosition,
- result = [];
- if (!iv) {
- iv = new Uint8Array(16);
- }
- for (i = 0; i < sourceLength; ++i) {
- buffer[bufferLength] = data[i];
- ++bufferLength;
- if (bufferLength < 16) {
- continue;
- }
- for (j = 0; j < 16; ++j) {
- buffer[j] ^= iv[j];
- }
- var cipher = encrypt256(buffer, this.key);
- this.iv = cipher;
- result.push(cipher);
- buffer = new Uint8Array(16);
- bufferLength = 0;
- }
- this.buffer = buffer;
- this.bufferLength = bufferLength;
- this.iv = iv;
- if (result.length === 0) {
- return new Uint8Array([]);
- }
- var outputLength = 16 * result.length;
- var output = new Uint8Array(outputLength);
- for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
- output.set(result[i], j);
- }
- return output;
- }
- };
- return AES256Cipher;
-}();
-var PDF17 = function PDF17Closure() {
- function compareByteArrays(array1, array2) {
- if (array1.length !== array2.length) {
- return false;
- }
- for (var i = 0; i < array1.length; i++) {
- if (array1[i] !== array2[i]) {
- return false;
- }
- }
- return true;
- }
- function PDF17() {}
- PDF17.prototype = {
- checkOwnerPassword: function PDF17_checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
- var hashData = new Uint8Array(password.length + 56);
- hashData.set(password, 0);
- hashData.set(ownerValidationSalt, password.length);
- hashData.set(userBytes, password.length + ownerValidationSalt.length);
- var result = calculateSHA256(hashData, 0, hashData.length);
- return compareByteArrays(result, ownerPassword);
- },
- checkUserPassword: function PDF17_checkUserPassword(password, userValidationSalt, userPassword) {
- var hashData = new Uint8Array(password.length + 8);
- hashData.set(password, 0);
- hashData.set(userValidationSalt, password.length);
- var result = calculateSHA256(hashData, 0, hashData.length);
- return compareByteArrays(result, userPassword);
- },
- getOwnerKey: function PDF17_getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
- var hashData = new Uint8Array(password.length + 56);
- hashData.set(password, 0);
- hashData.set(ownerKeySalt, password.length);
- hashData.set(userBytes, password.length + ownerKeySalt.length);
- var key = calculateSHA256(hashData, 0, hashData.length);
- var cipher = new AES256Cipher(key);
- return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
- },
- getUserKey: function PDF17_getUserKey(password, userKeySalt, userEncryption) {
- var hashData = new Uint8Array(password.length + 8);
- hashData.set(password, 0);
- hashData.set(userKeySalt, password.length);
- var key = calculateSHA256(hashData, 0, hashData.length);
- var cipher = new AES256Cipher(key);
- return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
- }
- };
- return PDF17;
-}();
-var PDF20 = function PDF20Closure() {
- function concatArrays(array1, array2) {
- var t = new Uint8Array(array1.length + array2.length);
- t.set(array1, 0);
- t.set(array2, array1.length);
- return t;
- }
- function calculatePDF20Hash(password, input, userBytes) {
- var k = calculateSHA256(input, 0, input.length).subarray(0, 32);
- var e = [0];
- var i = 0;
- while (i < 64 || e[e.length - 1] > i - 32) {
- var arrayLength = password.length + k.length + userBytes.length;
- var k1 = new Uint8Array(arrayLength * 64);
- var array = concatArrays(password, k);
- array = concatArrays(array, userBytes);
- for (var j = 0, pos = 0; j < 64; j++, pos += arrayLength) {
- k1.set(array, pos);
- }
- var cipher = new AES128Cipher(k.subarray(0, 16));
- e = cipher.encrypt(k1, k.subarray(16, 32));
- var remainder = 0;
- for (var z = 0; z < 16; z++) {
- remainder *= 256 % 3;
- remainder %= 3;
- remainder += (e[z] >>> 0) % 3;
- remainder %= 3;
- }
- if (remainder === 0) {
- k = calculateSHA256(e, 0, e.length);
- } else if (remainder === 1) {
- k = calculateSHA384(e, 0, e.length);
- } else if (remainder === 2) {
- k = calculateSHA512(e, 0, e.length);
- }
- i++;
- }
- return k.subarray(0, 32);
- }
- function PDF20() {}
- function compareByteArrays(array1, array2) {
- if (array1.length !== array2.length) {
- return false;
- }
- for (var i = 0; i < array1.length; i++) {
- if (array1[i] !== array2[i]) {
- return false;
- }
- }
- return true;
- }
- PDF20.prototype = {
- hash: function PDF20_hash(password, concatBytes, userBytes) {
- return calculatePDF20Hash(password, concatBytes, userBytes);
- },
- checkOwnerPassword: function PDF20_checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
- var hashData = new Uint8Array(password.length + 56);
- hashData.set(password, 0);
- hashData.set(ownerValidationSalt, password.length);
- hashData.set(userBytes, password.length + ownerValidationSalt.length);
- var result = calculatePDF20Hash(password, hashData, userBytes);
- return compareByteArrays(result, ownerPassword);
- },
- checkUserPassword: function PDF20_checkUserPassword(password, userValidationSalt, userPassword) {
- var hashData = new Uint8Array(password.length + 8);
- hashData.set(password, 0);
- hashData.set(userValidationSalt, password.length);
- var result = calculatePDF20Hash(password, hashData, []);
- return compareByteArrays(result, userPassword);
- },
- getOwnerKey: function PDF20_getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
- var hashData = new Uint8Array(password.length + 56);
- hashData.set(password, 0);
- hashData.set(ownerKeySalt, password.length);
- hashData.set(userBytes, password.length + ownerKeySalt.length);
- var key = calculatePDF20Hash(password, hashData, userBytes);
- var cipher = new AES256Cipher(key);
- return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
- },
- getUserKey: function PDF20_getUserKey(password, userKeySalt, userEncryption) {
- var hashData = new Uint8Array(password.length + 8);
- hashData.set(password, 0);
- hashData.set(userKeySalt, password.length);
- var key = calculatePDF20Hash(password, hashData, []);
- var cipher = new AES256Cipher(key);
- return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
- }
- };
- return PDF20;
-}();
-var CipherTransform = function CipherTransformClosure() {
- function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
- this.StringCipherConstructor = stringCipherConstructor;
- this.StreamCipherConstructor = streamCipherConstructor;
- }
- CipherTransform.prototype = {
- createStream: function CipherTransform_createStream(stream, length) {
- var cipher = new this.StreamCipherConstructor();
- return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) {
- return cipher.decryptBlock(data, finalize);
- });
- },
- decryptString: function CipherTransform_decryptString(s) {
- var cipher = new this.StringCipherConstructor();
- var data = stringToBytes(s);
- data = cipher.decryptBlock(data, true);
- return bytesToString(data);
- }
- };
- return CipherTransform;
-}();
-var CipherTransformFactory = function CipherTransformFactoryClosure() {
- var defaultPasswordBytes = new Uint8Array([0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
- function createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) {
- if (password) {
- var passwordLength = Math.min(127, password.length);
- password = password.subarray(0, passwordLength);
- } else {
- password = [];
- }
- var pdfAlgorithm;
- if (revision === 6) {
- pdfAlgorithm = new PDF20();
- } else {
- pdfAlgorithm = new PDF17();
- }
- if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) {
- return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
- } else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) {
- return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption);
- }
- return null;
- }
- function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) {
- var hashDataSize = 40 + ownerPassword.length + fileId.length;
- var hashData = new Uint8Array(hashDataSize),
- i = 0,
- j,
- n;
- if (password) {
- n = Math.min(32, password.length);
- for (; i < n; ++i) {
- hashData[i] = password[i];
- }
- }
- j = 0;
- while (i < 32) {
- hashData[i++] = defaultPasswordBytes[j++];
- }
- for (j = 0, n = ownerPassword.length; j < n; ++j) {
- hashData[i++] = ownerPassword[j];
- }
- hashData[i++] = flags & 0xFF;
- hashData[i++] = flags >> 8 & 0xFF;
- hashData[i++] = flags >> 16 & 0xFF;
- hashData[i++] = flags >>> 24 & 0xFF;
- for (j = 0, n = fileId.length; j < n; ++j) {
- hashData[i++] = fileId[j];
- }
- if (revision >= 4 && !encryptMetadata) {
- hashData[i++] = 0xFF;
- hashData[i++] = 0xFF;
- hashData[i++] = 0xFF;
- hashData[i++] = 0xFF;
- }
- var hash = calculateMD5(hashData, 0, i);
- var keyLengthInBytes = keyLength >> 3;
- if (revision >= 3) {
- for (j = 0; j < 50; ++j) {
- hash = calculateMD5(hash, 0, keyLengthInBytes);
- }
- }
- var encryptionKey = hash.subarray(0, keyLengthInBytes);
- var cipher, checkData;
- if (revision >= 3) {
- for (i = 0; i < 32; ++i) {
- hashData[i] = defaultPasswordBytes[i];
- }
- for (j = 0, n = fileId.length; j < n; ++j) {
- hashData[i++] = fileId[j];
- }
- cipher = new ARCFourCipher(encryptionKey);
- checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
- n = encryptionKey.length;
- var derivedKey = new Uint8Array(n),
- k;
- for (j = 1; j <= 19; ++j) {
- for (k = 0; k < n; ++k) {
- derivedKey[k] = encryptionKey[k] ^ j;
- }
- cipher = new ARCFourCipher(derivedKey);
- checkData = cipher.encryptBlock(checkData);
- }
- for (j = 0, n = checkData.length; j < n; ++j) {
- if (userPassword[j] !== checkData[j]) {
- return null;
- }
- }
- } else {
- cipher = new ARCFourCipher(encryptionKey);
- checkData = cipher.encryptBlock(defaultPasswordBytes);
- for (j = 0, n = checkData.length; j < n; ++j) {
- if (userPassword[j] !== checkData[j]) {
- return null;
- }
- }
- }
- return encryptionKey;
- }
- function decodeUserPassword(password, ownerPassword, revision, keyLength) {
- var hashData = new Uint8Array(32),
- i = 0,
- j,
- n;
- n = Math.min(32, password.length);
- for (; i < n; ++i) {
- hashData[i] = password[i];
- }
- j = 0;
- while (i < 32) {
- hashData[i++] = defaultPasswordBytes[j++];
- }
- var hash = calculateMD5(hashData, 0, i);
- var keyLengthInBytes = keyLength >> 3;
- if (revision >= 3) {
- for (j = 0; j < 50; ++j) {
- hash = calculateMD5(hash, 0, hash.length);
- }
- }
- var cipher, userPassword;
- if (revision >= 3) {
- userPassword = ownerPassword;
- var derivedKey = new Uint8Array(keyLengthInBytes),
- k;
- for (j = 19; j >= 0; j--) {
- for (k = 0; k < keyLengthInBytes; ++k) {
- derivedKey[k] = hash[k] ^ j;
- }
- cipher = new ARCFourCipher(derivedKey);
- userPassword = cipher.encryptBlock(userPassword);
- }
- } else {
- cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
- userPassword = cipher.encryptBlock(ownerPassword);
- }
- return userPassword;
- }
- var identityName = Name.get('Identity');
- function CipherTransformFactory(dict, fileId, password) {
- var filter = dict.get('Filter');
- if (!isName(filter, 'Standard')) {
- error('unknown encryption method');
- }
- this.dict = dict;
- var algorithm = dict.get('V');
- if (!isInt(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) {
- error('unsupported encryption algorithm');
- }
- this.algorithm = algorithm;
- var keyLength = dict.get('Length');
- if (!keyLength) {
- if (algorithm <= 3) {
- keyLength = 40;
- } else {
- var cfDict = dict.get('CF');
- var streamCryptoName = dict.get('StmF');
- if (isDict(cfDict) && isName(streamCryptoName)) {
- cfDict.suppressEncryption = true;
- var handlerDict = cfDict.get(streamCryptoName.name);
- keyLength = handlerDict && handlerDict.get('Length') || 128;
- if (keyLength < 40) {
- keyLength <<= 3;
- }
- }
- }
- }
- if (!isInt(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
- error('invalid key length');
- }
- var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32);
- var userPassword = stringToBytes(dict.get('U')).subarray(0, 32);
- var flags = dict.get('P');
- var revision = dict.get('R');
- var encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get('EncryptMetadata') !== false;
- this.encryptMetadata = encryptMetadata;
- var fileIdBytes = stringToBytes(fileId);
- var passwordBytes;
- if (password) {
- if (revision === 6) {
- try {
- password = utf8StringToString(password);
- } catch (ex) {
- warn('CipherTransformFactory: ' + 'Unable to convert UTF8 encoded password.');
- }
- }
- passwordBytes = stringToBytes(password);
- }
- var encryptionKey;
- if (algorithm !== 5) {
- encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
- } else {
- var ownerValidationSalt = stringToBytes(dict.get('O')).subarray(32, 40);
- var ownerKeySalt = stringToBytes(dict.get('O')).subarray(40, 48);
- var uBytes = stringToBytes(dict.get('U')).subarray(0, 48);
- var userValidationSalt = stringToBytes(dict.get('U')).subarray(32, 40);
- var userKeySalt = stringToBytes(dict.get('U')).subarray(40, 48);
- var ownerEncryption = stringToBytes(dict.get('OE'));
- var userEncryption = stringToBytes(dict.get('UE'));
- var perms = stringToBytes(dict.get('Perms'));
- encryptionKey = createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms);
- }
- if (!encryptionKey && !password) {
- throw new PasswordException('No password given', PasswordResponses.NEED_PASSWORD);
- } else if (!encryptionKey && password) {
- var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength);
- encryptionKey = prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
- }
- if (!encryptionKey) {
- throw new PasswordException('Incorrect Password', PasswordResponses.INCORRECT_PASSWORD);
- }
- this.encryptionKey = encryptionKey;
- if (algorithm >= 4) {
- var cf = dict.get('CF');
- if (isDict(cf)) {
- cf.suppressEncryption = true;
- }
- this.cf = cf;
- this.stmf = dict.get('StmF') || identityName;
- this.strf = dict.get('StrF') || identityName;
- this.eff = dict.get('EFF') || this.stmf;
- }
- }
- function buildObjectKey(num, gen, encryptionKey, isAes) {
- var key = new Uint8Array(encryptionKey.length + 9),
- i,
- n;
- for (i = 0, n = encryptionKey.length; i < n; ++i) {
- key[i] = encryptionKey[i];
- }
- key[i++] = num & 0xFF;
- key[i++] = num >> 8 & 0xFF;
- key[i++] = num >> 16 & 0xFF;
- key[i++] = gen & 0xFF;
- key[i++] = gen >> 8 & 0xFF;
- if (isAes) {
- key[i++] = 0x73;
- key[i++] = 0x41;
- key[i++] = 0x6C;
- key[i++] = 0x54;
- }
- var hash = calculateMD5(key, 0, i);
- return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
- }
- function buildCipherConstructor(cf, name, num, gen, key) {
- assert(isName(name), 'Invalid crypt filter name.');
- var cryptFilter = cf.get(name.name);
- var cfm;
- if (cryptFilter !== null && cryptFilter !== undefined) {
- cfm = cryptFilter.get('CFM');
- }
- if (!cfm || cfm.name === 'None') {
- return function cipherTransformFactoryBuildCipherConstructorNone() {
- return new NullCipher();
- };
- }
- if (cfm.name === 'V2') {
- return function cipherTransformFactoryBuildCipherConstructorV2() {
- return new ARCFourCipher(buildObjectKey(num, gen, key, false));
- };
- }
- if (cfm.name === 'AESV2') {
- return function cipherTransformFactoryBuildCipherConstructorAESV2() {
- return new AES128Cipher(buildObjectKey(num, gen, key, true));
- };
- }
- if (cfm.name === 'AESV3') {
- return function cipherTransformFactoryBuildCipherConstructorAESV3() {
- return new AES256Cipher(key);
- };
- }
- error('Unknown crypto method');
- }
- CipherTransformFactory.prototype = {
- createCipherTransform: function CipherTransformFactory_createCipherTransform(num, gen) {
- if (this.algorithm === 4 || this.algorithm === 5) {
- return new CipherTransform(buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey), buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey));
- }
- var key = buildObjectKey(num, gen, this.encryptionKey, false);
- var cipherConstructor = function buildCipherCipherConstructor() {
- return new ARCFourCipher(key);
- };
- return new CipherTransform(cipherConstructor, cipherConstructor);
- }
- };
- return CipherTransformFactory;
-}();
-exports.AES128Cipher = AES128Cipher;
-exports.AES256Cipher = AES256Cipher;
-exports.ARCFourCipher = ARCFourCipher;
-exports.CipherTransformFactory = CipherTransformFactory;
-exports.PDF17 = PDF17;
-exports.PDF20 = PDF20;
-exports.calculateMD5 = calculateMD5;
-exports.calculateSHA256 = calculateSHA256;
-exports.calculateSHA384 = calculateSHA384;
-exports.calculateSHA512 = calculateSHA512;
-
-/***/ }),
-/* 14 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var coreParser = __w_pdfjs_require__(5);
-var coreImage = __w_pdfjs_require__(27);
-var coreColorSpace = __w_pdfjs_require__(3);
-var coreMurmurHash3 = __w_pdfjs_require__(31);
-var coreFonts = __w_pdfjs_require__(26);
-var coreFunction = __w_pdfjs_require__(6);
-var corePattern = __w_pdfjs_require__(32);
-var coreCMap = __w_pdfjs_require__(23);
-var coreMetrics = __w_pdfjs_require__(30);
-var coreBidi = __w_pdfjs_require__(21);
-var coreEncodings = __w_pdfjs_require__(4);
-var coreStandardFonts = __w_pdfjs_require__(17);
-var coreUnicode = __w_pdfjs_require__(18);
-var coreGlyphList = __w_pdfjs_require__(7);
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
-var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
-var ImageKind = sharedUtil.ImageKind;
-var OPS = sharedUtil.OPS;
-var TextRenderingMode = sharedUtil.TextRenderingMode;
-var CMapCompressionType = sharedUtil.CMapCompressionType;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isNum = sharedUtil.isNum;
-var isString = sharedUtil.isString;
-var getLookupTableFactory = sharedUtil.getLookupTableFactory;
-var warn = sharedUtil.warn;
-var Dict = corePrimitives.Dict;
-var Name = corePrimitives.Name;
-var isEOF = corePrimitives.isEOF;
-var isCmd = corePrimitives.isCmd;
-var isDict = corePrimitives.isDict;
-var isName = corePrimitives.isName;
-var isRef = corePrimitives.isRef;
-var isStream = corePrimitives.isStream;
-var DecodeStream = coreStream.DecodeStream;
-var JpegStream = coreStream.JpegStream;
-var Stream = coreStream.Stream;
-var Lexer = coreParser.Lexer;
-var Parser = coreParser.Parser;
-var PDFImage = coreImage.PDFImage;
-var ColorSpace = coreColorSpace.ColorSpace;
-var MurmurHash3_64 = coreMurmurHash3.MurmurHash3_64;
-var ErrorFont = coreFonts.ErrorFont;
-var FontFlags = coreFonts.FontFlags;
-var Font = coreFonts.Font;
-var IdentityToUnicodeMap = coreFonts.IdentityToUnicodeMap;
-var ToUnicodeMap = coreFonts.ToUnicodeMap;
-var getFontType = coreFonts.getFontType;
-var isPDFFunction = coreFunction.isPDFFunction;
-var PDFFunction = coreFunction.PDFFunction;
-var Pattern = corePattern.Pattern;
-var getTilingPatternIR = corePattern.getTilingPatternIR;
-var CMapFactory = coreCMap.CMapFactory;
-var IdentityCMap = coreCMap.IdentityCMap;
-var getMetrics = coreMetrics.getMetrics;
-var bidi = coreBidi.bidi;
-var WinAnsiEncoding = coreEncodings.WinAnsiEncoding;
-var StandardEncoding = coreEncodings.StandardEncoding;
-var MacRomanEncoding = coreEncodings.MacRomanEncoding;
-var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
-var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
-var getEncoding = coreEncodings.getEncoding;
-var getStdFontMap = coreStandardFonts.getStdFontMap;
-var getSerifFonts = coreStandardFonts.getSerifFonts;
-var getSymbolsFonts = coreStandardFonts.getSymbolsFonts;
-var getNormalizedUnicodes = coreUnicode.getNormalizedUnicodes;
-var reverseIfRtl = coreUnicode.reverseIfRtl;
-var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
-var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
-var PartialEvaluator = function PartialEvaluatorClosure() {
- var DefaultPartialEvaluatorOptions = {
- forceDataSchema: false,
- maxImageSize: -1,
- disableFontFace: false,
- disableNativeImageDecoder: false
- };
- function NativeImageDecoder(xref, resources, handler, forceDataSchema) {
- this.xref = xref;
- this.resources = resources;
- this.handler = handler;
- this.forceDataSchema = forceDataSchema;
- }
- NativeImageDecoder.prototype = {
- canDecode: function (image) {
- return image instanceof JpegStream && NativeImageDecoder.isDecodable(image, this.xref, this.resources);
- },
- decode: function (image) {
- var dict = image.dict;
- var colorSpace = dict.get('ColorSpace', 'CS');
- colorSpace = ColorSpace.parse(colorSpace, this.xref, this.resources);
- var numComps = colorSpace.numComps;
- var decodePromise = this.handler.sendWithPromise('JpegDecode', [image.getIR(this.forceDataSchema), numComps]);
- return decodePromise.then(function (message) {
- var data = message.data;
- return new Stream(data, 0, data.length, image.dict);
- });
- }
- };
- NativeImageDecoder.isSupported = function NativeImageDecoder_isSupported(image, xref, res) {
- var dict = image.dict;
- if (dict.has('DecodeParms') || dict.has('DP')) {
- return false;
- }
- var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res);
- return (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB') && cs.isDefaultDecode(dict.getArray('Decode', 'D'));
- };
- NativeImageDecoder.isDecodable = function NativeImageDecoder_isDecodable(image, xref, res) {
- var dict = image.dict;
- if (dict.has('DecodeParms') || dict.has('DP')) {
- return false;
- }
- var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res);
- return (cs.numComps === 1 || cs.numComps === 3) && cs.isDefaultDecode(dict.getArray('Decode', 'D'));
- };
- function PartialEvaluator(pdfManager, xref, handler, pageIndex, idFactory, fontCache, builtInCMapCache, options) {
- this.pdfManager = pdfManager;
- this.xref = xref;
- this.handler = handler;
- this.pageIndex = pageIndex;
- this.idFactory = idFactory;
- this.fontCache = fontCache;
- this.builtInCMapCache = builtInCMapCache;
- this.options = options || DefaultPartialEvaluatorOptions;
- this.fetchBuiltInCMap = function (name) {
- var cachedCMap = builtInCMapCache[name];
- if (cachedCMap) {
- return Promise.resolve(cachedCMap);
- }
- return handler.sendWithPromise('FetchBuiltInCMap', { name: name }).then(function (data) {
- if (data.compressionType !== CMapCompressionType.NONE) {
- builtInCMapCache[name] = data;
- }
- return data;
- });
- };
- }
- var TIME_SLOT_DURATION_MS = 20;
- var CHECK_TIME_EVERY = 100;
- function TimeSlotManager() {
- this.reset();
- }
- TimeSlotManager.prototype = {
- check: function TimeSlotManager_check() {
- if (++this.checked < CHECK_TIME_EVERY) {
- return false;
- }
- this.checked = 0;
- return this.endTime <= Date.now();
- },
- reset: function TimeSlotManager_reset() {
- this.endTime = Date.now() + TIME_SLOT_DURATION_MS;
- this.checked = 0;
- }
- };
- var deferred = Promise.resolve();
- var TILING_PATTERN = 1,
- SHADING_PATTERN = 2;
- PartialEvaluator.prototype = {
- hasBlendModes: function PartialEvaluator_hasBlendModes(resources) {
- if (!isDict(resources)) {
- return false;
- }
- var processed = Object.create(null);
- if (resources.objId) {
- processed[resources.objId] = true;
- }
- var nodes = [resources],
- xref = this.xref;
- while (nodes.length) {
- var key, i, ii;
- var node = nodes.shift();
- var graphicStates = node.get('ExtGState');
- if (isDict(graphicStates)) {
- var graphicStatesKeys = graphicStates.getKeys();
- for (i = 0, ii = graphicStatesKeys.length; i < ii; i++) {
- key = graphicStatesKeys[i];
- var graphicState = graphicStates.get(key);
- var bm = graphicState.get('BM');
- if (isName(bm) && bm.name !== 'Normal') {
- return true;
- }
- }
- }
- var xObjects = node.get('XObject');
- if (!isDict(xObjects)) {
- continue;
- }
- var xObjectsKeys = xObjects.getKeys();
- for (i = 0, ii = xObjectsKeys.length; i < ii; i++) {
- key = xObjectsKeys[i];
- var xObject = xObjects.getRaw(key);
- if (isRef(xObject)) {
- if (processed[xObject.toString()]) {
- continue;
- }
- xObject = xref.fetch(xObject);
- }
- if (!isStream(xObject)) {
- continue;
- }
- if (xObject.dict.objId) {
- if (processed[xObject.dict.objId]) {
- continue;
- }
- processed[xObject.dict.objId] = true;
- }
- var xResources = xObject.dict.get('Resources');
- if (isDict(xResources) && (!xResources.objId || !processed[xResources.objId])) {
- nodes.push(xResources);
- if (xResources.objId) {
- processed[xResources.objId] = true;
- }
- }
- }
- }
- return false;
- },
- buildFormXObject: function PartialEvaluator_buildFormXObject(resources, xobj, smask, operatorList, task, initialState) {
- var matrix = xobj.dict.getArray('Matrix');
- var bbox = xobj.dict.getArray('BBox');
- var group = xobj.dict.get('Group');
- if (group) {
- var groupOptions = {
- matrix: matrix,
- bbox: bbox,
- smask: smask,
- isolated: false,
- knockout: false
- };
- var groupSubtype = group.get('S');
- var colorSpace;
- if (isName(groupSubtype, 'Transparency')) {
- groupOptions.isolated = group.get('I') || false;
- groupOptions.knockout = group.get('K') || false;
- colorSpace = group.has('CS') ? ColorSpace.parse(group.get('CS'), this.xref, resources) : null;
- }
- if (smask && smask.backdrop) {
- colorSpace = colorSpace || ColorSpace.singletons.rgb;
- smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
- }
- operatorList.addOp(OPS.beginGroup, [groupOptions]);
- }
- operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
- return this.getOperatorList(xobj, task, xobj.dict.get('Resources') || resources, operatorList, initialState).then(function () {
- operatorList.addOp(OPS.paintFormXObjectEnd, []);
- if (group) {
- operatorList.addOp(OPS.endGroup, [groupOptions]);
- }
- });
- },
- buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(resources, image, inline, operatorList, cacheKey, imageCache) {
- var self = this;
- var dict = image.dict;
- var w = dict.get('Width', 'W');
- var h = dict.get('Height', 'H');
- if (!(w && isNum(w)) || !(h && isNum(h))) {
- warn('Image dimensions are missing, or not numbers.');
- return;
- }
- var maxImageSize = this.options.maxImageSize;
- if (maxImageSize !== -1 && w * h > maxImageSize) {
- warn('Image exceeded maximum allowed size and was removed.');
- return;
- }
- var imageMask = dict.get('ImageMask', 'IM') || false;
- var imgData, args;
- if (imageMask) {
- var width = dict.get('Width', 'W');
- var height = dict.get('Height', 'H');
- var bitStrideLength = width + 7 >> 3;
- var imgArray = image.getBytes(bitStrideLength * height);
- var decode = dict.getArray('Decode', 'D');
- var inverseDecode = !!decode && decode[0] > 0;
- imgData = PDFImage.createMask(imgArray, width, height, image instanceof DecodeStream, inverseDecode);
- imgData.cached = true;
- args = [imgData];
- operatorList.addOp(OPS.paintImageMaskXObject, args);
- if (cacheKey) {
- imageCache[cacheKey] = {
- fn: OPS.paintImageMaskXObject,
- args: args
- };
- }
- return;
- }
- var softMask = dict.get('SMask', 'SM') || false;
- var mask = dict.get('Mask') || false;
- var SMALL_IMAGE_DIMENSIONS = 200;
- if (inline && !softMask && !mask && !(image instanceof JpegStream) && w + h < SMALL_IMAGE_DIMENSIONS) {
- var imageObj = new PDFImage(this.xref, resources, image, inline, null, null);
- imgData = imageObj.createImageData(true);
- operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
- return;
- }
- var useNativeImageDecoder = !this.options.disableNativeImageDecoder;
- var objId = 'img_' + this.idFactory.createObjId();
- operatorList.addDependency(objId);
- args = [objId, w, h];
- if (useNativeImageDecoder && !softMask && !mask && image instanceof JpegStream && NativeImageDecoder.isSupported(image, this.xref, resources)) {
- operatorList.addOp(OPS.paintJpegXObject, args);
- this.handler.send('obj', [objId, this.pageIndex, 'JpegStream', image.getIR(this.options.forceDataSchema)]);
- return;
- }
- var nativeImageDecoder = null;
- if (useNativeImageDecoder && (image instanceof JpegStream || mask instanceof JpegStream || softMask instanceof JpegStream)) {
- nativeImageDecoder = new NativeImageDecoder(self.xref, resources, self.handler, self.options.forceDataSchema);
- }
- PDFImage.buildImage(self.handler, self.xref, resources, image, inline, nativeImageDecoder).then(function (imageObj) {
- var imgData = imageObj.createImageData(false);
- self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], [imgData.data.buffer]);
- }).then(undefined, function (reason) {
- warn('Unable to decode image: ' + reason);
- self.handler.send('obj', [objId, self.pageIndex, 'Image', null]);
- });
- operatorList.addOp(OPS.paintImageXObject, args);
- if (cacheKey) {
- imageCache[cacheKey] = {
- fn: OPS.paintImageXObject,
- args: args
- };
- }
- },
- handleSMask: function PartialEvaluator_handleSmask(smask, resources, operatorList, task, stateManager) {
- var smaskContent = smask.get('G');
- var smaskOptions = {
- subtype: smask.get('S').name,
- backdrop: smask.get('BC')
- };
- var transferObj = smask.get('TR');
- if (isPDFFunction(transferObj)) {
- var transferFn = PDFFunction.parse(this.xref, transferObj);
- var transferMap = new Uint8Array(256);
- var tmp = new Float32Array(1);
- for (var i = 0; i < 256; i++) {
- tmp[0] = i / 255;
- transferFn(tmp, 0, tmp, 0);
- transferMap[i] = tmp[0] * 255 | 0;
- }
- smaskOptions.transferMap = transferMap;
- }
- return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone());
- },
- handleTilingType: function PartialEvaluator_handleTilingType(fn, args, resources, pattern, patternDict, operatorList, task) {
- var tilingOpList = new OperatorList();
- var resourcesArray = [patternDict.get('Resources'), resources];
- var patternResources = Dict.merge(this.xref, resourcesArray);
- return this.getOperatorList(pattern, task, patternResources, tilingOpList).then(function () {
- operatorList.addDependencies(tilingOpList.dependencies);
- operatorList.addOp(fn, getTilingPatternIR({
- fnArray: tilingOpList.fnArray,
- argsArray: tilingOpList.argsArray
- }, patternDict, args));
- });
- },
- handleSetFont: function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) {
- var fontName;
- if (fontArgs) {
- fontArgs = fontArgs.slice();
- fontName = fontArgs[0].name;
- }
- var self = this;
- return this.loadFont(fontName, fontRef, resources).then(function (translated) {
- if (!translated.font.isType3Font) {
- return translated;
- }
- return translated.loadType3Data(self, resources, operatorList, task).then(function () {
- return translated;
- }, function (reason) {
- self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
- return new TranslatedFont('g_font_error', new ErrorFont('Type3 font load error: ' + reason), translated.font);
- });
- }).then(function (translated) {
- state.font = translated.font;
- translated.send(self.handler);
- return translated.loadedName;
- });
- },
- handleText: function PartialEvaluator_handleText(chars, state) {
- var font = state.font;
- var glyphs = font.charsToGlyphs(chars);
- var isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
- if (font.data && (isAddToPathSet || this.options.disableFontFace)) {
- var buildPath = function (fontChar) {
- if (!font.renderer.hasBuiltPath(fontChar)) {
- var path = font.renderer.getPathJs(fontChar);
- this.handler.send('commonobj', [font.loadedName + '_path_' + fontChar, 'FontPath', path]);
- }
- }.bind(this);
- for (var i = 0, ii = glyphs.length; i < ii; i++) {
- var glyph = glyphs[i];
- buildPath(glyph.fontChar);
- var accent = glyph.accent;
- if (accent && accent.fontChar) {
- buildPath(accent.fontChar);
- }
- }
- }
- return glyphs;
- },
- setGState: function PartialEvaluator_setGState(resources, gState, operatorList, task, stateManager) {
- var gStateObj = [];
- var gStateKeys = gState.getKeys();
- var self = this;
- var promise = Promise.resolve();
- for (var i = 0, ii = gStateKeys.length; i < ii; i++) {
- var key = gStateKeys[i];
- var value = gState.get(key);
- switch (key) {
- case 'Type':
- break;
- case 'LW':
- case 'LC':
- case 'LJ':
- case 'ML':
- case 'D':
- case 'RI':
- case 'FL':
- case 'CA':
- case 'ca':
- gStateObj.push([key, value]);
- break;
- case 'Font':
- promise = promise.then(function () {
- return self.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
- operatorList.addDependency(loadedName);
- gStateObj.push([key, [loadedName, value[1]]]);
- });
- });
- break;
- case 'BM':
- gStateObj.push([key, value]);
- break;
- case 'SMask':
- if (isName(value, 'None')) {
- gStateObj.push([key, false]);
- break;
- }
- if (isDict(value)) {
- promise = promise.then(function (dict) {
- return self.handleSMask(dict, resources, operatorList, task, stateManager);
- }.bind(this, value));
- gStateObj.push([key, true]);
- } else {
- warn('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':
- info('graphic state operator ' + key);
- break;
- default:
- info('Unknown graphic state operator ' + key);
- break;
- }
- }
- return promise.then(function () {
- if (gStateObj.length > 0) {
- operatorList.addOp(OPS.setGState, [gStateObj]);
- }
- });
- },
- loadFont: function PartialEvaluator_loadFont(fontName, font, resources) {
- function errorFont() {
- return Promise.resolve(new TranslatedFont('g_font_error', new ErrorFont('Font ' + fontName + ' is not available'), font));
- }
- var fontRef,
- xref = this.xref;
- if (font) {
- assert(isRef(font));
- fontRef = font;
- } else {
- var fontRes = resources.get('Font');
- if (fontRes) {
- fontRef = fontRes.getRaw(fontName);
- } else {
- warn('fontRes not available');
- return errorFont();
- }
- }
- if (!fontRef) {
- warn('fontRef not available');
- return errorFont();
- }
- if (this.fontCache.has(fontRef)) {
- return this.fontCache.get(fontRef);
- }
- font = xref.fetchIfRef(fontRef);
- if (!isDict(font)) {
- return errorFont();
- }
- if (font.translated) {
- return font.translated;
- }
- var fontCapability = createPromiseCapability();
- var preEvaluatedFont = this.preEvaluateFont(font);
- var descriptor = preEvaluatedFont.descriptor;
- var fontRefIsRef = isRef(fontRef),
- fontID;
- if (fontRefIsRef) {
- fontID = fontRef.toString();
- }
- if (isDict(descriptor)) {
- if (!descriptor.fontAliases) {
- descriptor.fontAliases = Object.create(null);
- }
- var fontAliases = descriptor.fontAliases;
- var hash = preEvaluatedFont.hash;
- if (fontAliases[hash]) {
- var aliasFontRef = fontAliases[hash].aliasRef;
- if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) {
- this.fontCache.putAlias(fontRef, aliasFontRef);
- return this.fontCache.get(fontRef);
- }
- } else {
- fontAliases[hash] = { fontID: Font.getFontID() };
- }
- if (fontRefIsRef) {
- fontAliases[hash].aliasRef = fontRef;
- }
- fontID = fontAliases[hash].fontID;
- }
- if (fontRefIsRef) {
- this.fontCache.put(fontRef, fontCapability.promise);
- } else {
- if (!fontID) {
- fontID = this.idFactory.createObjId();
- }
- this.fontCache.put('id_' + fontID, fontCapability.promise);
- }
- assert(fontID, 'The "fontID" must be defined.');
- font.loadedName = 'g_' + this.pdfManager.docId + '_f' + fontID;
- font.translated = fontCapability.promise;
- var translatedPromise;
- try {
- translatedPromise = this.translateFont(preEvaluatedFont);
- } catch (e) {
- translatedPromise = Promise.reject(e);
- }
- var self = this;
- translatedPromise.then(function (translatedFont) {
- if (translatedFont.fontType !== undefined) {
- var xrefFontStats = xref.stats.fontTypes;
- xrefFontStats[translatedFont.fontType] = true;
- }
- fontCapability.resolve(new TranslatedFont(font.loadedName, translatedFont, font));
- }, function (reason) {
- self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
- try {
- var descriptor = preEvaluatedFont.descriptor;
- var fontFile3 = descriptor && descriptor.get('FontFile3');
- var subtype = fontFile3 && fontFile3.get('Subtype');
- var fontType = getFontType(preEvaluatedFont.type, subtype && subtype.name);
- var xrefFontStats = xref.stats.fontTypes;
- xrefFontStats[fontType] = true;
- } catch (ex) {}
- fontCapability.resolve(new TranslatedFont(font.loadedName, new ErrorFont(reason instanceof Error ? reason.message : reason), font));
- });
- return fontCapability.promise;
- },
- buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) {
- var lastIndex = operatorList.length - 1;
- if (!args) {
- args = [];
- }
- if (lastIndex < 0 || operatorList.fnArray[lastIndex] !== OPS.constructPath) {
- operatorList.addOp(OPS.constructPath, [[fn], args]);
- } else {
- var opArgs = operatorList.argsArray[lastIndex];
- opArgs[0].push(fn);
- Array.prototype.push.apply(opArgs[1], args);
- }
- },
- handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args, cs, patterns, resources, task) {
- var patternName = args[args.length - 1];
- var pattern;
- if (isName(patternName) && (pattern = patterns.get(patternName.name))) {
- var dict = isStream(pattern) ? pattern.dict : pattern;
- var typeNum = dict.get('PatternType');
- if (typeNum === TILING_PATTERN) {
- var color = cs.base ? cs.base.getRgb(args, 0) : null;
- return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task);
- } else if (typeNum === SHADING_PATTERN) {
- var shading = dict.get('Shading');
- var matrix = dict.getArray('Matrix');
- pattern = Pattern.parseShading(shading, matrix, this.xref, resources, this.handler);
- operatorList.addOp(fn, pattern.getIR());
- return Promise.resolve();
- }
- return Promise.reject('Unknown PatternType: ' + typeNum);
- }
- operatorList.addOp(fn, args);
- return Promise.resolve();
- },
- getOperatorList: function PartialEvaluator_getOperatorList(stream, task, resources, operatorList, initialState) {
- var self = this;
- var xref = this.xref;
- var imageCache = Object.create(null);
- assert(operatorList);
- resources = resources || Dict.empty;
- var xobjs = resources.get('XObject') || Dict.empty;
- var patterns = resources.get('Pattern') || Dict.empty;
- var stateManager = new StateManager(initialState || new EvalState());
- var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
- var timeSlotManager = new TimeSlotManager();
- return new Promise(function promiseBody(resolve, reject) {
- var next = function (promise) {
- promise.then(function () {
- try {
- promiseBody(resolve, reject);
- } catch (ex) {
- reject(ex);
- }
- }, reject);
- };
- task.ensureNotTerminated();
- timeSlotManager.reset();
- var stop,
- operation = {},
- i,
- ii,
- cs;
- while (!(stop = timeSlotManager.check())) {
- operation.args = null;
- if (!preprocessor.read(operation)) {
- break;
- }
- var args = operation.args;
- var fn = operation.fn;
- switch (fn | 0) {
- case OPS.paintXObject:
- if (args[0].code) {
- break;
- }
- var name = args[0].name;
- if (!name) {
- warn('XObject must be referred to by name.');
- continue;
- }
- if (imageCache[name] !== undefined) {
- operatorList.addOp(imageCache[name].fn, imageCache[name].args);
- args = null;
- continue;
- }
- var xobj = xobjs.get(name);
- if (xobj) {
- assert(isStream(xobj), 'XObject should be a stream');
- var type = xobj.dict.get('Subtype');
- assert(isName(type), 'XObject should have a Name subtype');
- if (type.name === 'Form') {
- stateManager.save();
- next(self.buildFormXObject(resources, xobj, null, operatorList, task, stateManager.state.clone()).then(function () {
- stateManager.restore();
- }));
- return;
- } else if (type.name === 'Image') {
- self.buildPaintImageXObject(resources, xobj, false, operatorList, name, imageCache);
- args = null;
- continue;
- } else if (type.name === 'PS') {
- info('Ignored XObject subtype PS');
- continue;
- } else {
- error('Unhandled XObject subtype ' + type.name);
- }
- }
- break;
- case OPS.setFont:
- var fontSize = args[1];
- next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state).then(function (loadedName) {
- operatorList.addDependency(loadedName);
- operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
- }));
- return;
- case OPS.endInlineImage:
- var cacheKey = args[0].cacheKey;
- if (cacheKey) {
- var cacheEntry = imageCache[cacheKey];
- if (cacheEntry !== undefined) {
- operatorList.addOp(cacheEntry.fn, cacheEntry.args);
- args = null;
- continue;
- }
- }
- self.buildPaintImageXObject(resources, args[0], true, operatorList, cacheKey, imageCache);
- args = null;
- continue;
- case OPS.showText:
- args[0] = self.handleText(args[0], stateManager.state);
- break;
- case OPS.showSpacedText:
- var arr = args[0];
- var combinedGlyphs = [];
- var arrLength = arr.length;
- var state = stateManager.state;
- for (i = 0; i < arrLength; ++i) {
- var arrItem = arr[i];
- if (isString(arrItem)) {
- Array.prototype.push.apply(combinedGlyphs, self.handleText(arrItem, state));
- } else if (isNum(arrItem)) {
- combinedGlyphs.push(arrItem);
- }
- }
- args[0] = combinedGlyphs;
- fn = OPS.showText;
- break;
- case OPS.nextLineShowText:
- operatorList.addOp(OPS.nextLine);
- args[0] = self.handleText(args[0], stateManager.state);
- fn = OPS.showText;
- break;
- case OPS.nextLineSetSpacingShowText:
- operatorList.addOp(OPS.nextLine);
- operatorList.addOp(OPS.setWordSpacing, [args.shift()]);
- operatorList.addOp(OPS.setCharSpacing, [args.shift()]);
- args[0] = self.handleText(args[0], stateManager.state);
- fn = OPS.showText;
- break;
- case OPS.setTextRenderingMode:
- stateManager.state.textRenderingMode = args[0];
- break;
- case OPS.setFillColorSpace:
- stateManager.state.fillColorSpace = ColorSpace.parse(args[0], xref, resources);
- continue;
- case OPS.setStrokeColorSpace:
- stateManager.state.strokeColorSpace = ColorSpace.parse(args[0], xref, resources);
- continue;
- case OPS.setFillColor:
- cs = stateManager.state.fillColorSpace;
- args = cs.getRgb(args, 0);
- fn = OPS.setFillRGBColor;
- break;
- case OPS.setStrokeColor:
- cs = stateManager.state.strokeColorSpace;
- args = cs.getRgb(args, 0);
- fn = OPS.setStrokeRGBColor;
- break;
- case OPS.setFillGray:
- stateManager.state.fillColorSpace = ColorSpace.singletons.gray;
- args = ColorSpace.singletons.gray.getRgb(args, 0);
- fn = OPS.setFillRGBColor;
- break;
- case OPS.setStrokeGray:
- stateManager.state.strokeColorSpace = ColorSpace.singletons.gray;
- args = ColorSpace.singletons.gray.getRgb(args, 0);
- fn = OPS.setStrokeRGBColor;
- break;
- case OPS.setFillCMYKColor:
- stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk;
- args = ColorSpace.singletons.cmyk.getRgb(args, 0);
- fn = OPS.setFillRGBColor;
- break;
- case OPS.setStrokeCMYKColor:
- stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk;
- args = ColorSpace.singletons.cmyk.getRgb(args, 0);
- fn = OPS.setStrokeRGBColor;
- break;
- case OPS.setFillRGBColor:
- stateManager.state.fillColorSpace = ColorSpace.singletons.rgb;
- args = ColorSpace.singletons.rgb.getRgb(args, 0);
- break;
- case OPS.setStrokeRGBColor:
- stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb;
- args = ColorSpace.singletons.rgb.getRgb(args, 0);
- break;
- case OPS.setFillColorN:
- cs = stateManager.state.fillColorSpace;
- if (cs.name === 'Pattern') {
- next(self.handleColorN(operatorList, OPS.setFillColorN, args, cs, patterns, resources, task));
- return;
- }
- args = cs.getRgb(args, 0);
- fn = OPS.setFillRGBColor;
- break;
- case OPS.setStrokeColorN:
- cs = stateManager.state.strokeColorSpace;
- if (cs.name === 'Pattern') {
- next(self.handleColorN(operatorList, OPS.setStrokeColorN, args, cs, patterns, resources, task));
- return;
- }
- args = cs.getRgb(args, 0);
- fn = OPS.setStrokeRGBColor;
- break;
- case OPS.shadingFill:
- var shadingRes = resources.get('Shading');
- assert(shadingRes, 'No shading resource found');
- var shading = shadingRes.get(args[0].name);
- assert(shading, 'No shading object found');
- var shadingFill = Pattern.parseShading(shading, null, xref, resources, self.handler);
- var patternIR = shadingFill.getIR();
- args = [patternIR];
- fn = OPS.shadingFill;
- break;
- case OPS.setGState:
- var dictName = args[0];
- var extGState = resources.get('ExtGState');
- if (!isDict(extGState) || !extGState.has(dictName.name)) {
- break;
- }
- var gState = extGState.get(dictName.name);
- next(self.setGState(resources, gState, operatorList, task, stateManager));
- return;
- case OPS.moveTo:
- case OPS.lineTo:
- case OPS.curveTo:
- case OPS.curveTo2:
- case OPS.curveTo3:
- case OPS.closePath:
- self.buildPath(operatorList, fn, args);
- continue;
- case OPS.rectangle:
- self.buildPath(operatorList, fn, args);
- continue;
- case OPS.markPoint:
- case OPS.markPointProps:
- case OPS.beginMarkedContent:
- case OPS.beginMarkedContentProps:
- case OPS.endMarkedContent:
- case OPS.beginCompat:
- case OPS.endCompat:
- continue;
- default:
- if (args !== null) {
- for (i = 0, ii = args.length; i < ii; i++) {
- if (args[i] instanceof Dict) {
- break;
- }
- }
- if (i < ii) {
- warn('getOperatorList - ignoring operator: ' + fn);
- continue;
- }
- }
- }
- operatorList.addOp(fn, args);
- }
- if (stop) {
- next(deferred);
- return;
- }
- for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
- operatorList.addOp(OPS.restore, []);
- }
- resolve();
- });
- },
- getTextContent: function PartialEvaluator_getTextContent(stream, task, resources, stateManager, normalizeWhitespace, combineTextItems) {
- stateManager = stateManager || new StateManager(new TextState());
- var WhitespaceRegexp = /\s/g;
- var textContent = {
- items: [],
- styles: Object.create(null)
- };
- var textContentItem = {
- initialized: false,
- str: [],
- width: 0,
- height: 0,
- vertical: false,
- lastAdvanceWidth: 0,
- lastAdvanceHeight: 0,
- textAdvanceScale: 0,
- spaceWidth: 0,
- fakeSpaceMin: Infinity,
- fakeMultiSpaceMin: Infinity,
- fakeMultiSpaceMax: -0,
- textRunBreakAllowed: false,
- transform: null,
- fontName: null
- };
- var SPACE_FACTOR = 0.3;
- var MULTI_SPACE_FACTOR = 1.5;
- var MULTI_SPACE_FACTOR_MAX = 4;
- var self = this;
- var xref = this.xref;
- resources = xref.fetchIfRef(resources) || Dict.empty;
- var xobjs = null;
- var xobjsCache = Object.create(null);
- var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
- var textState;
- function ensureTextContentItem() {
- if (textContentItem.initialized) {
- return textContentItem;
- }
- var font = textState.font;
- if (!(font.loadedName in textContent.styles)) {
- textContent.styles[font.loadedName] = {
- fontFamily: font.fallbackName,
- ascent: font.ascent,
- descent: font.descent,
- vertical: font.vertical
- };
- }
- textContentItem.fontName = font.loadedName;
- var tsm = [textState.fontSize * textState.textHScale, 0, 0, textState.fontSize, 0, textState.textRise];
- if (font.isType3Font && textState.fontMatrix !== FONT_IDENTITY_MATRIX && textState.fontSize === 1) {
- var glyphHeight = font.bbox[3] - font.bbox[1];
- if (glyphHeight > 0) {
- glyphHeight = glyphHeight * textState.fontMatrix[3];
- tsm[3] *= glyphHeight;
- }
- }
- var trm = Util.transform(textState.ctm, Util.transform(textState.textMatrix, tsm));
- textContentItem.transform = trm;
- if (!font.vertical) {
- textContentItem.width = 0;
- textContentItem.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
- textContentItem.vertical = false;
- } else {
- textContentItem.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
- textContentItem.height = 0;
- textContentItem.vertical = true;
- }
- var a = textState.textLineMatrix[0];
- var b = textState.textLineMatrix[1];
- var scaleLineX = Math.sqrt(a * a + b * b);
- a = textState.ctm[0];
- b = textState.ctm[1];
- var scaleCtmX = Math.sqrt(a * a + b * b);
- textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
- textContentItem.lastAdvanceWidth = 0;
- textContentItem.lastAdvanceHeight = 0;
- var spaceWidth = font.spaceWidth / 1000 * textState.fontSize;
- if (spaceWidth) {
- textContentItem.spaceWidth = spaceWidth;
- textContentItem.fakeSpaceMin = spaceWidth * SPACE_FACTOR;
- textContentItem.fakeMultiSpaceMin = spaceWidth * MULTI_SPACE_FACTOR;
- textContentItem.fakeMultiSpaceMax = spaceWidth * MULTI_SPACE_FACTOR_MAX;
- textContentItem.textRunBreakAllowed = !font.isMonospace;
- } else {
- textContentItem.spaceWidth = 0;
- textContentItem.fakeSpaceMin = Infinity;
- textContentItem.fakeMultiSpaceMin = Infinity;
- textContentItem.fakeMultiSpaceMax = 0;
- textContentItem.textRunBreakAllowed = false;
- }
- textContentItem.initialized = true;
- return textContentItem;
- }
- function replaceWhitespace(str) {
- var i = 0,
- ii = str.length,
- code;
- while (i < ii && (code = str.charCodeAt(i)) >= 0x20 && code <= 0x7F) {
- i++;
- }
- return i < ii ? str.replace(WhitespaceRegexp, ' ') : str;
- }
- function runBidiTransform(textChunk) {
- var str = textChunk.str.join('');
- var bidiResult = bidi(str, -1, textChunk.vertical);
- return {
- str: normalizeWhitespace ? replaceWhitespace(bidiResult.str) : bidiResult.str,
- dir: bidiResult.dir,
- width: textChunk.width,
- height: textChunk.height,
- transform: textChunk.transform,
- fontName: textChunk.fontName
- };
- }
- function handleSetFont(fontName, fontRef) {
- return self.loadFont(fontName, fontRef, resources).then(function (translated) {
- textState.font = translated.font;
- textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX;
- });
- }
- function buildTextContentItem(chars) {
- var font = textState.font;
- var textChunk = ensureTextContentItem();
- var width = 0;
- var height = 0;
- var glyphs = font.charsToGlyphs(chars);
- for (var i = 0; i < glyphs.length; i++) {
- var glyph = glyphs[i];
- var glyphWidth = null;
- if (font.vertical && glyph.vmetric) {
- glyphWidth = glyph.vmetric[0];
- } else {
- glyphWidth = glyph.width;
- }
- var glyphUnicode = glyph.unicode;
- var NormalizedUnicodes = getNormalizedUnicodes();
- if (NormalizedUnicodes[glyphUnicode] !== undefined) {
- glyphUnicode = NormalizedUnicodes[glyphUnicode];
- }
- glyphUnicode = reverseIfRtl(glyphUnicode);
- var charSpacing = textState.charSpacing;
- if (glyph.isSpace) {
- var wordSpacing = textState.wordSpacing;
- charSpacing += wordSpacing;
- if (wordSpacing > 0) {
- addFakeSpaces(wordSpacing, textChunk.str);
- }
- }
- var tx = 0;
- var ty = 0;
- if (!font.vertical) {
- var w0 = glyphWidth * textState.fontMatrix[0];
- tx = (w0 * textState.fontSize + charSpacing) * textState.textHScale;
- width += tx;
- } else {
- var w1 = glyphWidth * textState.fontMatrix[0];
- ty = w1 * textState.fontSize + charSpacing;
- height += ty;
- }
- textState.translateTextMatrix(tx, ty);
- textChunk.str.push(glyphUnicode);
- }
- if (!font.vertical) {
- textChunk.lastAdvanceWidth = width;
- textChunk.width += width;
- } else {
- textChunk.lastAdvanceHeight = height;
- textChunk.height += Math.abs(height);
- }
- return textChunk;
- }
- function addFakeSpaces(width, strBuf) {
- if (width < textContentItem.fakeSpaceMin) {
- return;
- }
- if (width < textContentItem.fakeMultiSpaceMin) {
- strBuf.push(' ');
- return;
- }
- var fakeSpaces = Math.round(width / textContentItem.spaceWidth);
- while (fakeSpaces-- > 0) {
- strBuf.push(' ');
- }
- }
- function flushTextContentItem() {
- if (!textContentItem.initialized) {
- return;
- }
- textContentItem.width *= textContentItem.textAdvanceScale;
- textContentItem.height *= textContentItem.textAdvanceScale;
- textContent.items.push(runBidiTransform(textContentItem));
- textContentItem.initialized = false;
- textContentItem.str.length = 0;
- }
- var timeSlotManager = new TimeSlotManager();
- return new Promise(function promiseBody(resolve, reject) {
- var next = function (promise) {
- promise.then(function () {
- try {
- promiseBody(resolve, reject);
- } catch (ex) {
- reject(ex);
- }
- }, reject);
- };
- task.ensureNotTerminated();
- timeSlotManager.reset();
- var stop,
- operation = {},
- args = [];
- while (!(stop = timeSlotManager.check())) {
- args.length = 0;
- operation.args = args;
- if (!preprocessor.read(operation)) {
- break;
- }
- textState = stateManager.state;
- var fn = operation.fn;
- args = operation.args;
- var advance, diff;
- switch (fn | 0) {
- case OPS.setFont:
- var fontNameArg = args[0].name,
- fontSizeArg = args[1];
- if (textState.font && fontNameArg === textState.fontName && fontSizeArg === textState.fontSize) {
- break;
- }
- flushTextContentItem();
- textState.fontName = fontNameArg;
- textState.fontSize = fontSizeArg;
- next(handleSetFont(fontNameArg, null));
- return;
- case OPS.setTextRise:
- flushTextContentItem();
- textState.textRise = args[0];
- break;
- case OPS.setHScale:
- flushTextContentItem();
- textState.textHScale = args[0] / 100;
- break;
- case OPS.setLeading:
- flushTextContentItem();
- textState.leading = args[0];
- break;
- case OPS.moveText:
- var isSameTextLine = !textState.font ? false : (textState.font.vertical ? args[0] : args[1]) === 0;
- advance = args[0] - args[1];
- if (combineTextItems && isSameTextLine && textContentItem.initialized && advance > 0 && advance <= textContentItem.fakeMultiSpaceMax) {
- textState.translateTextLineMatrix(args[0], args[1]);
- textContentItem.width += args[0] - textContentItem.lastAdvanceWidth;
- textContentItem.height += args[1] - textContentItem.lastAdvanceHeight;
- diff = args[0] - textContentItem.lastAdvanceWidth - (args[1] - textContentItem.lastAdvanceHeight);
- addFakeSpaces(diff, textContentItem.str);
- break;
- }
- flushTextContentItem();
- textState.translateTextLineMatrix(args[0], args[1]);
- textState.textMatrix = textState.textLineMatrix.slice();
- break;
- case OPS.setLeadingMoveText:
- flushTextContentItem();
- textState.leading = -args[1];
- textState.translateTextLineMatrix(args[0], args[1]);
- textState.textMatrix = textState.textLineMatrix.slice();
- break;
- case OPS.nextLine:
- flushTextContentItem();
- textState.carriageReturn();
- break;
- case OPS.setTextMatrix:
- advance = textState.calcTextLineMatrixAdvance(args[0], args[1], args[2], args[3], args[4], args[5]);
- if (combineTextItems && advance !== null && textContentItem.initialized && advance.value > 0 && advance.value <= textContentItem.fakeMultiSpaceMax) {
- textState.translateTextLineMatrix(advance.width, advance.height);
- textContentItem.width += advance.width - textContentItem.lastAdvanceWidth;
- textContentItem.height += advance.height - textContentItem.lastAdvanceHeight;
- diff = advance.width - textContentItem.lastAdvanceWidth - (advance.height - textContentItem.lastAdvanceHeight);
- addFakeSpaces(diff, textContentItem.str);
- break;
- }
- flushTextContentItem();
- textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
- textState.setTextLineMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
- break;
- case OPS.setCharSpacing:
- textState.charSpacing = args[0];
- break;
- case OPS.setWordSpacing:
- textState.wordSpacing = args[0];
- break;
- case OPS.beginText:
- flushTextContentItem();
- textState.textMatrix = IDENTITY_MATRIX.slice();
- textState.textLineMatrix = IDENTITY_MATRIX.slice();
- break;
- case OPS.showSpacedText:
- var items = args[0];
- var offset;
- for (var j = 0, jj = items.length; j < jj; j++) {
- if (typeof items[j] === 'string') {
- buildTextContentItem(items[j]);
- } else if (isNum(items[j])) {
- ensureTextContentItem();
- advance = items[j] * textState.fontSize / 1000;
- var breakTextRun = false;
- if (textState.font.vertical) {
- offset = advance;
- textState.translateTextMatrix(0, offset);
- breakTextRun = textContentItem.textRunBreakAllowed && advance > textContentItem.fakeMultiSpaceMax;
- if (!breakTextRun) {
- textContentItem.height += offset;
- }
- } else {
- advance = -advance;
- offset = advance * textState.textHScale;
- textState.translateTextMatrix(offset, 0);
- breakTextRun = textContentItem.textRunBreakAllowed && advance > textContentItem.fakeMultiSpaceMax;
- if (!breakTextRun) {
- textContentItem.width += offset;
- }
- }
- if (breakTextRun) {
- flushTextContentItem();
- } else if (advance > 0) {
- addFakeSpaces(advance, textContentItem.str);
- }
- }
- }
- break;
- case OPS.showText:
- buildTextContentItem(args[0]);
- break;
- case OPS.nextLineShowText:
- flushTextContentItem();
- textState.carriageReturn();
- buildTextContentItem(args[0]);
- break;
- case OPS.nextLineSetSpacingShowText:
- flushTextContentItem();
- textState.wordSpacing = args[0];
- textState.charSpacing = args[1];
- textState.carriageReturn();
- buildTextContentItem(args[2]);
- break;
- case OPS.paintXObject:
- flushTextContentItem();
- if (args[0].code) {
- break;
- }
- if (!xobjs) {
- xobjs = resources.get('XObject') || Dict.empty;
- }
- var name = args[0].name;
- if (xobjsCache.key === name) {
- if (xobjsCache.texts) {
- Util.appendToArray(textContent.items, xobjsCache.texts.items);
- Util.extendObj(textContent.styles, xobjsCache.texts.styles);
- }
- break;
- }
- var xobj = xobjs.get(name);
- if (!xobj) {
- break;
- }
- assert(isStream(xobj), 'XObject should be a stream');
- var type = xobj.dict.get('Subtype');
- assert(isName(type), 'XObject should have a Name subtype');
- if (type.name !== 'Form') {
- xobjsCache.key = name;
- xobjsCache.texts = null;
- break;
- }
- stateManager.save();
- var matrix = xobj.dict.getArray('Matrix');
- if (isArray(matrix) && matrix.length === 6) {
- stateManager.transform(matrix);
- }
- next(self.getTextContent(xobj, task, xobj.dict.get('Resources') || resources, stateManager, normalizeWhitespace, combineTextItems).then(function (formTextContent) {
- Util.appendToArray(textContent.items, formTextContent.items);
- Util.extendObj(textContent.styles, formTextContent.styles);
- stateManager.restore();
- xobjsCache.key = name;
- xobjsCache.texts = formTextContent;
- }));
- return;
- case OPS.setGState:
- flushTextContentItem();
- var dictName = args[0];
- var extGState = resources.get('ExtGState');
- if (!isDict(extGState) || !isName(dictName)) {
- break;
- }
- var gState = extGState.get(dictName.name);
- if (!isDict(gState)) {
- break;
- }
- var gStateFont = gState.get('Font');
- if (gStateFont) {
- textState.fontName = null;
- textState.fontSize = gStateFont[1];
- next(handleSetFont(null, gStateFont[0]));
- return;
- }
- break;
- }
- }
- if (stop) {
- next(deferred);
- return;
- }
- flushTextContentItem();
- resolve(textContent);
- });
- },
- extractDataStructures: function PartialEvaluator_extractDataStructures(dict, baseDict, properties) {
- var xref = this.xref;
- var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
- var toUnicodePromise = toUnicode ? this.readToUnicode(toUnicode) : Promise.resolve(undefined);
- if (properties.composite) {
- var cidSystemInfo = dict.get('CIDSystemInfo');
- if (isDict(cidSystemInfo)) {
- properties.cidSystemInfo = {
- registry: cidSystemInfo.get('Registry'),
- ordering: cidSystemInfo.get('Ordering'),
- supplement: cidSystemInfo.get('Supplement')
- };
- }
- var cidToGidMap = dict.get('CIDToGIDMap');
- if (isStream(cidToGidMap)) {
- properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
- }
- }
- var differences = [];
- var baseEncodingName = null;
- var encoding;
- if (dict.has('Encoding')) {
- encoding = dict.get('Encoding');
- if (isDict(encoding)) {
- baseEncodingName = encoding.get('BaseEncoding');
- baseEncodingName = isName(baseEncodingName) ? baseEncodingName.name : null;
- if (encoding.has('Differences')) {
- var diffEncoding = encoding.get('Differences');
- var index = 0;
- for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
- var data = xref.fetchIfRef(diffEncoding[j]);
- if (isNum(data)) {
- index = data;
- } else if (isName(data)) {
- differences[index++] = data.name;
- } else {
- error('Invalid entry in \'Differences\' array: ' + data);
- }
- }
- }
- } else if (isName(encoding)) {
- baseEncodingName = encoding.name;
- } else {
- error('Encoding is not a Name nor a Dict');
- }
- if (baseEncodingName !== 'MacRomanEncoding' && baseEncodingName !== 'MacExpertEncoding' && baseEncodingName !== 'WinAnsiEncoding') {
- baseEncodingName = null;
- }
- }
- if (baseEncodingName) {
- properties.defaultEncoding = getEncoding(baseEncodingName).slice();
- } else {
- var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
- var isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic);
- encoding = StandardEncoding;
- if (properties.type === 'TrueType' && !isNonsymbolicFont) {
- encoding = WinAnsiEncoding;
- }
- if (isSymbolicFont) {
- encoding = MacRomanEncoding;
- if (!properties.file) {
- if (/Symbol/i.test(properties.name)) {
- encoding = SymbolSetEncoding;
- } else if (/Dingbats/i.test(properties.name)) {
- encoding = ZapfDingbatsEncoding;
- }
- }
- }
- properties.defaultEncoding = encoding;
- }
- properties.differences = differences;
- properties.baseEncodingName = baseEncodingName;
- properties.hasEncoding = !!baseEncodingName || differences.length > 0;
- properties.dict = dict;
- return toUnicodePromise.then(function (toUnicode) {
- properties.toUnicode = toUnicode;
- return this.buildToUnicode(properties);
- }.bind(this)).then(function (toUnicode) {
- properties.toUnicode = toUnicode;
- return properties;
- });
- },
- buildToUnicode: function PartialEvaluator_buildToUnicode(properties) {
- properties.hasIncludedToUnicodeMap = !!properties.toUnicode && properties.toUnicode.length > 0;
- if (properties.hasIncludedToUnicodeMap) {
- return Promise.resolve(properties.toUnicode);
- }
- var toUnicode, charcode, glyphName;
- if (!properties.composite) {
- toUnicode = [];
- var encoding = properties.defaultEncoding.slice();
- var baseEncodingName = properties.baseEncodingName;
- var differences = properties.differences;
- for (charcode in differences) {
- glyphName = differences[charcode];
- if (glyphName === '.notdef') {
- continue;
- }
- encoding[charcode] = glyphName;
- }
- var glyphsUnicodeMap = getGlyphsUnicode();
- for (charcode in encoding) {
- glyphName = encoding[charcode];
- if (glyphName === '') {
- continue;
- } else if (glyphsUnicodeMap[glyphName] === undefined) {
- var code = 0;
- switch (glyphName[0]) {
- case 'G':
- if (glyphName.length === 3) {
- code = parseInt(glyphName.substr(1), 16);
- }
- break;
- case 'g':
- if (glyphName.length === 5) {
- code = parseInt(glyphName.substr(1), 16);
- }
- break;
- case 'C':
- case 'c':
- if (glyphName.length >= 3) {
- code = +glyphName.substr(1);
- }
- break;
- default:
- var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
- if (unicode !== -1) {
- code = unicode;
- }
- }
- if (code) {
- if (baseEncodingName && code === +charcode) {
- var baseEncoding = getEncoding(baseEncodingName);
- if (baseEncoding && (glyphName = baseEncoding[charcode])) {
- toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
- continue;
- }
- }
- toUnicode[charcode] = String.fromCharCode(code);
- }
- continue;
- }
- toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
- }
- return Promise.resolve(new ToUnicodeMap(toUnicode));
- }
- if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo.registry === 'Adobe' && (properties.cidSystemInfo.ordering === 'GB1' || properties.cidSystemInfo.ordering === 'CNS1' || properties.cidSystemInfo.ordering === 'Japan1' || properties.cidSystemInfo.ordering === 'Korea1'))) {
- var registry = properties.cidSystemInfo.registry;
- var ordering = properties.cidSystemInfo.ordering;
- var ucs2CMapName = Name.get(registry + '-' + ordering + '-UCS2');
- return CMapFactory.create({
- encoding: ucs2CMapName,
- fetchBuiltInCMap: this.fetchBuiltInCMap,
- useCMap: null
- }).then(function (ucs2CMap) {
- var cMap = properties.cMap;
- toUnicode = [];
- cMap.forEach(function (charcode, cid) {
- assert(cid <= 0xffff, 'Max size of CID is 65,535');
- var ucs2 = ucs2CMap.lookup(cid);
- if (ucs2) {
- toUnicode[charcode] = String.fromCharCode((ucs2.charCodeAt(0) << 8) + ucs2.charCodeAt(1));
- }
- });
- return new ToUnicodeMap(toUnicode);
- });
- }
- return Promise.resolve(new IdentityToUnicodeMap(properties.firstChar, properties.lastChar));
- },
- readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
- var cmapObj = toUnicode;
- if (isName(cmapObj)) {
- return CMapFactory.create({
- encoding: cmapObj,
- fetchBuiltInCMap: this.fetchBuiltInCMap,
- useCMap: null
- }).then(function (cmap) {
- if (cmap instanceof IdentityCMap) {
- return new IdentityToUnicodeMap(0, 0xFFFF);
- }
- return new ToUnicodeMap(cmap.getMap());
- });
- } else if (isStream(cmapObj)) {
- return CMapFactory.create({
- encoding: cmapObj,
- fetchBuiltInCMap: this.fetchBuiltInCMap,
- useCMap: null
- }).then(function (cmap) {
- if (cmap instanceof IdentityCMap) {
- return new IdentityToUnicodeMap(0, 0xFFFF);
- }
- var map = new Array(cmap.length);
- cmap.forEach(function (charCode, token) {
- var str = [];
- for (var k = 0; k < token.length; k += 2) {
- var w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
- if ((w1 & 0xF800) !== 0xD800) {
- str.push(w1);
- continue;
- }
- k += 2;
- var w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
- str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
- }
- map[charCode] = String.fromCharCode.apply(String, str);
- });
- return new ToUnicodeMap(map);
- });
- }
- return Promise.resolve(null);
- },
- readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
- var glyphsData = cidToGidStream.getBytes();
- var result = [];
- for (var j = 0, jj = glyphsData.length; j < jj; j++) {
- var glyphID = glyphsData[j++] << 8 | glyphsData[j];
- if (glyphID === 0) {
- continue;
- }
- var code = j >> 1;
- result[code] = glyphID;
- }
- return result;
- },
- extractWidths: function PartialEvaluator_extractWidths(dict, descriptor, properties) {
- var xref = this.xref;
- var glyphsWidths = [];
- var defaultWidth = 0;
- var glyphsVMetrics = [];
- var defaultVMetrics;
- var i, ii, j, jj, start, code, widths;
- if (properties.composite) {
- defaultWidth = dict.get('DW') || 1000;
- widths = dict.get('W');
- if (widths) {
- for (i = 0, ii = widths.length; i < ii; i++) {
- start = xref.fetchIfRef(widths[i++]);
- code = xref.fetchIfRef(widths[i]);
- if (isArray(code)) {
- for (j = 0, jj = code.length; j < jj; j++) {
- glyphsWidths[start++] = xref.fetchIfRef(code[j]);
- }
- } else {
- var width = xref.fetchIfRef(widths[++i]);
- for (j = start; j <= code; j++) {
- glyphsWidths[j] = width;
- }
- }
- }
- }
- if (properties.vertical) {
- var vmetrics = dict.getArray('DW2') || [880, -1000];
- defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]];
- vmetrics = dict.get('W2');
- if (vmetrics) {
- for (i = 0, ii = vmetrics.length; i < ii; i++) {
- start = xref.fetchIfRef(vmetrics[i++]);
- code = xref.fetchIfRef(vmetrics[i]);
- if (isArray(code)) {
- for (j = 0, jj = code.length; j < jj; j++) {
- glyphsVMetrics[start++] = [xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j])];
- }
- } else {
- var vmetric = [xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i])];
- for (j = start; j <= code; j++) {
- glyphsVMetrics[j] = vmetric;
- }
- }
- }
- }
- }
- } else {
- var firstChar = properties.firstChar;
- widths = dict.get('Widths');
- if (widths) {
- j = firstChar;
- for (i = 0, ii = widths.length; i < ii; i++) {
- glyphsWidths[j++] = xref.fetchIfRef(widths[i]);
- }
- defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
- } else {
- var baseFontName = dict.get('BaseFont');
- if (isName(baseFontName)) {
- var metrics = this.getBaseFontMetrics(baseFontName.name);
- glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
- defaultWidth = metrics.defaultWidth;
- }
- }
- }
- var isMonospace = true;
- var firstWidth = defaultWidth;
- for (var glyph in glyphsWidths) {
- var glyphWidth = glyphsWidths[glyph];
- if (!glyphWidth) {
- continue;
- }
- if (!firstWidth) {
- firstWidth = glyphWidth;
- continue;
- }
- if (firstWidth !== glyphWidth) {
- isMonospace = false;
- break;
- }
- }
- if (isMonospace) {
- properties.flags |= FontFlags.FixedPitch;
- }
- properties.defaultWidth = defaultWidth;
- properties.widths = glyphsWidths;
- properties.defaultVMetrics = defaultVMetrics;
- properties.vmetrics = glyphsVMetrics;
- },
- isSerifFont: function PartialEvaluator_isSerifFont(baseFontName) {
- var fontNameWoStyle = baseFontName.split('-')[0];
- return fontNameWoStyle in getSerifFonts() || fontNameWoStyle.search(/serif/gi) !== -1;
- },
- getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) {
- var defaultWidth = 0;
- var widths = [];
- var monospace = false;
- var stdFontMap = getStdFontMap();
- var lookupName = stdFontMap[name] || name;
- var Metrics = getMetrics();
- if (!(lookupName in Metrics)) {
- if (this.isSerifFont(name)) {
- lookupName = 'Times-Roman';
- } else {
- lookupName = 'Helvetica';
- }
- }
- var glyphWidths = Metrics[lookupName];
- if (isNum(glyphWidths)) {
- defaultWidth = glyphWidths;
- monospace = true;
- } else {
- widths = glyphWidths();
- }
- return {
- defaultWidth: defaultWidth,
- monospace: monospace,
- widths: widths
- };
- },
- buildCharCodeToWidth: function PartialEvaluator_bulildCharCodeToWidth(widthsByGlyphName, properties) {
- var widths = Object.create(null);
- var differences = properties.differences;
- var encoding = properties.defaultEncoding;
- for (var charCode = 0; charCode < 256; charCode++) {
- if (charCode in differences && widthsByGlyphName[differences[charCode]]) {
- widths[charCode] = widthsByGlyphName[differences[charCode]];
- continue;
- }
- if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
- widths[charCode] = widthsByGlyphName[encoding[charCode]];
- continue;
- }
- }
- return widths;
- },
- preEvaluateFont: function PartialEvaluator_preEvaluateFont(dict) {
- var baseDict = dict;
- var type = dict.get('Subtype');
- assert(isName(type), 'invalid font Subtype');
- var composite = false;
- var uint8array;
- if (type.name === 'Type0') {
- var df = dict.get('DescendantFonts');
- assert(df, 'Descendant fonts are not specified');
- dict = isArray(df) ? this.xref.fetchIfRef(df[0]) : df;
- type = dict.get('Subtype');
- assert(isName(type), 'invalid font Subtype');
- composite = true;
- }
- var descriptor = dict.get('FontDescriptor');
- if (descriptor) {
- var hash = new MurmurHash3_64();
- var encoding = baseDict.getRaw('Encoding');
- if (isName(encoding)) {
- hash.update(encoding.name);
- } else if (isRef(encoding)) {
- hash.update(encoding.toString());
- } else if (isDict(encoding)) {
- var keys = encoding.getKeys();
- for (var i = 0, ii = keys.length; i < ii; i++) {
- var entry = encoding.getRaw(keys[i]);
- if (isName(entry)) {
- hash.update(entry.name);
- } else if (isRef(entry)) {
- hash.update(entry.toString());
- } else if (isArray(entry)) {
- var diffLength = entry.length,
- diffBuf = new Array(diffLength);
- for (var j = 0; j < diffLength; j++) {
- var diffEntry = entry[j];
- if (isName(diffEntry)) {
- diffBuf[j] = diffEntry.name;
- } else if (isNum(diffEntry) || isRef(diffEntry)) {
- diffBuf[j] = diffEntry.toString();
- }
- }
- hash.update(diffBuf.join());
- }
- }
- }
- var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
- if (isStream(toUnicode)) {
- var stream = toUnicode.str || toUnicode;
- uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start);
- hash.update(uint8array);
- } else if (isName(toUnicode)) {
- hash.update(toUnicode.name);
- }
- var widths = dict.get('Widths') || baseDict.get('Widths');
- if (widths) {
- uint8array = new Uint8Array(new Uint32Array(widths).buffer);
- hash.update(uint8array);
- }
- }
- return {
- descriptor: descriptor,
- dict: dict,
- baseDict: baseDict,
- composite: composite,
- type: type.name,
- hash: hash ? hash.hexdigest() : ''
- };
- },
- translateFont: function PartialEvaluator_translateFont(preEvaluatedFont) {
- var baseDict = preEvaluatedFont.baseDict;
- var dict = preEvaluatedFont.dict;
- var composite = preEvaluatedFont.composite;
- var descriptor = preEvaluatedFont.descriptor;
- var type = preEvaluatedFont.type;
- var maxCharIndex = composite ? 0xFFFF : 0xFF;
- var properties;
- if (!descriptor) {
- if (type === 'Type3') {
- descriptor = new Dict(null);
- descriptor.set('FontName', Name.get(type));
- descriptor.set('FontBBox', dict.getArray('FontBBox'));
- } else {
- var baseFontName = dict.get('BaseFont');
- assert(isName(baseFontName), 'Base font is not specified');
- baseFontName = baseFontName.name.replace(/[,_]/g, '-');
- var metrics = this.getBaseFontMetrics(baseFontName);
- var fontNameWoStyle = baseFontName.split('-')[0];
- var flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic);
- properties = {
- type: type,
- name: baseFontName,
- widths: metrics.widths,
- defaultWidth: metrics.defaultWidth,
- flags: flags,
- firstChar: 0,
- lastChar: maxCharIndex
- };
- return this.extractDataStructures(dict, dict, properties).then(function (properties) {
- properties.widths = this.buildCharCodeToWidth(metrics.widths, properties);
- return new Font(baseFontName, null, properties);
- }.bind(this));
- }
- }
- var firstChar = dict.get('FirstChar') || 0;
- var lastChar = dict.get('LastChar') || maxCharIndex;
- var fontName = descriptor.get('FontName');
- var baseFont = dict.get('BaseFont');
- if (isString(fontName)) {
- fontName = Name.get(fontName);
- }
- if (isString(baseFont)) {
- baseFont = Name.get(baseFont);
- }
- if (type !== 'Type3') {
- var fontNameStr = fontName && fontName.name;
- var baseFontStr = baseFont && baseFont.name;
- if (fontNameStr !== baseFontStr) {
- info('The FontDescriptor\'s FontName is "' + fontNameStr + '" but should be the same as the Font\'s BaseFont "' + baseFontStr + '"');
- if (fontNameStr && baseFontStr && baseFontStr.indexOf(fontNameStr) === 0) {
- fontName = baseFont;
- }
- }
- }
- fontName = fontName || baseFont;
- assert(isName(fontName), 'invalid font name');
- var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
- if (fontFile) {
- if (fontFile.dict) {
- var subtype = fontFile.dict.get('Subtype');
- if (subtype) {
- subtype = subtype.name;
- }
- var length1 = fontFile.dict.get('Length1');
- var length2 = fontFile.dict.get('Length2');
- var length3 = fontFile.dict.get('Length3');
- }
- }
- properties = {
- type: type,
- name: fontName.name,
- subtype: subtype,
- file: fontFile,
- length1: length1,
- length2: length2,
- length3: length3,
- loadedName: baseDict.loadedName,
- composite: composite,
- wideChars: composite,
- fixedPitch: false,
- fontMatrix: dict.getArray('FontMatrix') || FONT_IDENTITY_MATRIX,
- firstChar: firstChar || 0,
- lastChar: lastChar || maxCharIndex,
- bbox: descriptor.getArray('FontBBox'),
- ascent: descriptor.get('Ascent'),
- descent: descriptor.get('Descent'),
- xHeight: descriptor.get('XHeight'),
- capHeight: descriptor.get('CapHeight'),
- flags: descriptor.get('Flags'),
- italicAngle: descriptor.get('ItalicAngle'),
- coded: false
- };
- var cMapPromise;
- if (composite) {
- var cidEncoding = baseDict.get('Encoding');
- if (isName(cidEncoding)) {
- properties.cidEncoding = cidEncoding.name;
- }
- cMapPromise = CMapFactory.create({
- encoding: cidEncoding,
- fetchBuiltInCMap: this.fetchBuiltInCMap,
- useCMap: null
- }).then(function (cMap) {
- properties.cMap = cMap;
- properties.vertical = properties.cMap.vertical;
- });
- } else {
- cMapPromise = Promise.resolve(undefined);
- }
- return cMapPromise.then(function () {
- return this.extractDataStructures(dict, baseDict, properties);
- }.bind(this)).then(function (properties) {
- this.extractWidths(dict, descriptor, properties);
- if (type === 'Type3') {
- properties.isType3Font = true;
- }
- return new Font(fontName.name, fontFile, properties);
- }.bind(this));
- }
- };
- return PartialEvaluator;
-}();
-var TranslatedFont = function TranslatedFontClosure() {
- function TranslatedFont(loadedName, font, dict) {
- this.loadedName = loadedName;
- this.font = font;
- this.dict = dict;
- this.type3Loaded = null;
- this.sent = false;
- }
- TranslatedFont.prototype = {
- send: function (handler) {
- if (this.sent) {
- return;
- }
- var fontData = this.font.exportData();
- handler.send('commonobj', [this.loadedName, 'Font', fontData]);
- this.sent = true;
- },
- loadType3Data: function (evaluator, resources, parentOperatorList, task) {
- assert(this.font.isType3Font);
- if (this.type3Loaded) {
- return this.type3Loaded;
- }
- var translatedFont = this.font;
- var loadCharProcsPromise = Promise.resolve();
- var charProcs = this.dict.get('CharProcs');
- var fontResources = this.dict.get('Resources') || resources;
- var charProcKeys = charProcs.getKeys();
- var charProcOperatorList = Object.create(null);
- for (var i = 0, n = charProcKeys.length; i < n; ++i) {
- loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
- var glyphStream = charProcs.get(key);
- var operatorList = new OperatorList();
- return evaluator.getOperatorList(glyphStream, task, fontResources, operatorList).then(function () {
- charProcOperatorList[key] = operatorList.getIR();
- parentOperatorList.addDependencies(operatorList.dependencies);
- }, function (reason) {
- warn('Type3 font resource \"' + key + '\" is not available');
- var operatorList = new OperatorList();
- charProcOperatorList[key] = operatorList.getIR();
- });
- }.bind(this, charProcKeys[i]));
- }
- this.type3Loaded = loadCharProcsPromise.then(function () {
- translatedFont.charProcOperatorList = charProcOperatorList;
- });
- return this.type3Loaded;
- }
- };
- return TranslatedFont;
-}();
-var OperatorList = function OperatorListClosure() {
- var CHUNK_SIZE = 1000;
- var CHUNK_SIZE_ABOUT = CHUNK_SIZE - 5;
- function getTransfers(queue) {
- var transfers = [];
- var fnArray = queue.fnArray,
- argsArray = queue.argsArray;
- for (var i = 0, ii = queue.length; i < ii; i++) {
- switch (fnArray[i]) {
- case OPS.paintInlineImageXObject:
- case OPS.paintInlineImageXObjectGroup:
- case OPS.paintImageMaskXObject:
- var arg = argsArray[i][0];
- if (!arg.cached) {
- transfers.push(arg.data.buffer);
- }
- break;
- }
- }
- return transfers;
- }
- function OperatorList(intent, messageHandler, pageIndex) {
- this.messageHandler = messageHandler;
- this.fnArray = [];
- this.argsArray = [];
- this.dependencies = Object.create(null);
- this._totalLength = 0;
- this.pageIndex = pageIndex;
- this.intent = intent;
- }
- OperatorList.prototype = {
- get length() {
- return this.argsArray.length;
- },
- get totalLength() {
- return this._totalLength + this.length;
- },
- addOp: function (fn, args) {
- this.fnArray.push(fn);
- this.argsArray.push(args);
- if (this.messageHandler) {
- if (this.fnArray.length >= CHUNK_SIZE) {
- this.flush();
- } else if (this.fnArray.length >= CHUNK_SIZE_ABOUT && (fn === OPS.restore || fn === OPS.endText)) {
- this.flush();
- }
- }
- },
- addDependency: function (dependency) {
- if (dependency in this.dependencies) {
- return;
- }
- this.dependencies[dependency] = true;
- this.addOp(OPS.dependency, [dependency]);
- },
- addDependencies: function (dependencies) {
- for (var key in dependencies) {
- this.addDependency(key);
- }
- },
- addOpList: function (opList) {
- Util.extendObj(this.dependencies, opList.dependencies);
- for (var i = 0, ii = opList.length; i < ii; i++) {
- this.addOp(opList.fnArray[i], opList.argsArray[i]);
- }
- },
- getIR: function () {
- return {
- fnArray: this.fnArray,
- argsArray: this.argsArray,
- length: this.length
- };
- },
- flush: function (lastChunk) {
- if (this.intent !== 'oplist') {
- new QueueOptimizer().optimize(this);
- }
- var transfers = getTransfers(this);
- var length = this.length;
- this._totalLength += length;
- this.messageHandler.send('RenderPageChunk', {
- operatorList: {
- fnArray: this.fnArray,
- argsArray: this.argsArray,
- lastChunk: lastChunk,
- length: length
- },
- pageIndex: this.pageIndex,
- intent: this.intent
- }, transfers);
- this.dependencies = Object.create(null);
- this.fnArray.length = 0;
- this.argsArray.length = 0;
- }
- };
- return OperatorList;
-}();
-var StateManager = function StateManagerClosure() {
- function StateManager(initialState) {
- this.state = initialState;
- this.stateStack = [];
- }
- StateManager.prototype = {
- save: function () {
- var old = this.state;
- this.stateStack.push(this.state);
- this.state = old.clone();
- },
- restore: function () {
- var prev = this.stateStack.pop();
- if (prev) {
- this.state = prev;
- }
- },
- transform: function (args) {
- this.state.ctm = Util.transform(this.state.ctm, args);
- }
- };
- return StateManager;
-}();
-var TextState = function TextStateClosure() {
- function TextState() {
- this.ctm = new Float32Array(IDENTITY_MATRIX);
- this.fontName = null;
- this.fontSize = 0;
- this.font = null;
- this.fontMatrix = FONT_IDENTITY_MATRIX;
- this.textMatrix = IDENTITY_MATRIX.slice();
- this.textLineMatrix = IDENTITY_MATRIX.slice();
- this.charSpacing = 0;
- this.wordSpacing = 0;
- this.leading = 0;
- this.textHScale = 1;
- this.textRise = 0;
- }
- TextState.prototype = {
- setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
- var m = this.textMatrix;
- m[0] = a;
- m[1] = b;
- m[2] = c;
- m[3] = d;
- m[4] = e;
- m[5] = f;
- },
- setTextLineMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
- var m = this.textLineMatrix;
- m[0] = a;
- m[1] = b;
- m[2] = c;
- m[3] = d;
- m[4] = e;
- m[5] = f;
- },
- translateTextMatrix: function TextState_translateTextMatrix(x, y) {
- var m = this.textMatrix;
- m[4] = m[0] * x + m[2] * y + m[4];
- m[5] = m[1] * x + m[3] * y + m[5];
- },
- translateTextLineMatrix: function TextState_translateTextMatrix(x, y) {
- var m = this.textLineMatrix;
- m[4] = m[0] * x + m[2] * y + m[4];
- m[5] = m[1] * x + m[3] * y + m[5];
- },
- calcTextLineMatrixAdvance: function TextState_calcTextLineMatrixAdvance(a, b, c, d, e, f) {
- var font = this.font;
- if (!font) {
- return null;
- }
- var m = this.textLineMatrix;
- if (!(a === m[0] && b === m[1] && c === m[2] && d === m[3])) {
- return null;
- }
- var txDiff = e - m[4],
- tyDiff = f - m[5];
- if (font.vertical && txDiff !== 0 || !font.vertical && tyDiff !== 0) {
- return null;
- }
- var tx,
- ty,
- denominator = a * d - b * c;
- if (font.vertical) {
- tx = -tyDiff * c / denominator;
- ty = tyDiff * a / denominator;
- } else {
- tx = txDiff * d / denominator;
- ty = -txDiff * b / denominator;
- }
- return {
- width: tx,
- height: ty,
- value: font.vertical ? ty : tx
- };
- },
- calcRenderMatrix: function TextState_calcRendeMatrix(ctm) {
- var tsm = [this.fontSize * this.textHScale, 0, 0, this.fontSize, 0, this.textRise];
- return Util.transform(ctm, Util.transform(this.textMatrix, tsm));
- },
- carriageReturn: function TextState_carriageReturn() {
- this.translateTextLineMatrix(0, -this.leading);
- this.textMatrix = this.textLineMatrix.slice();
- },
- clone: function TextState_clone() {
- var clone = Object.create(this);
- clone.textMatrix = this.textMatrix.slice();
- clone.textLineMatrix = this.textLineMatrix.slice();
- clone.fontMatrix = this.fontMatrix.slice();
- return clone;
- }
- };
- return TextState;
-}();
-var EvalState = function EvalStateClosure() {
- function EvalState() {
- this.ctm = new Float32Array(IDENTITY_MATRIX);
- this.font = null;
- this.textRenderingMode = TextRenderingMode.FILL;
- this.fillColorSpace = ColorSpace.singletons.gray;
- this.strokeColorSpace = ColorSpace.singletons.gray;
- }
- EvalState.prototype = {
- clone: function CanvasExtraState_clone() {
- return Object.create(this);
- }
- };
- return EvalState;
-}();
-var EvaluatorPreprocessor = function EvaluatorPreprocessorClosure() {
- var getOPMap = getLookupTableFactory(function (t) {
- t['w'] = {
- id: OPS.setLineWidth,
- numArgs: 1,
- variableArgs: false
- };
- t['J'] = {
- id: OPS.setLineCap,
- numArgs: 1,
- variableArgs: false
- };
- t['j'] = {
- id: OPS.setLineJoin,
- numArgs: 1,
- variableArgs: false
- };
- t['M'] = {
- id: OPS.setMiterLimit,
- numArgs: 1,
- variableArgs: false
- };
- t['d'] = {
- id: OPS.setDash,
- numArgs: 2,
- variableArgs: false
- };
- t['ri'] = {
- id: OPS.setRenderingIntent,
- numArgs: 1,
- variableArgs: false
- };
- t['i'] = {
- id: OPS.setFlatness,
- numArgs: 1,
- variableArgs: false
- };
- t['gs'] = {
- id: OPS.setGState,
- numArgs: 1,
- variableArgs: false
- };
- t['q'] = {
- id: OPS.save,
- numArgs: 0,
- variableArgs: false
- };
- t['Q'] = {
- id: OPS.restore,
- numArgs: 0,
- variableArgs: false
- };
- t['cm'] = {
- id: OPS.transform,
- numArgs: 6,
- variableArgs: false
- };
- t['m'] = {
- id: OPS.moveTo,
- numArgs: 2,
- variableArgs: false
- };
- t['l'] = {
- id: OPS.lineTo,
- numArgs: 2,
- variableArgs: false
- };
- t['c'] = {
- id: OPS.curveTo,
- numArgs: 6,
- variableArgs: false
- };
- t['v'] = {
- id: OPS.curveTo2,
- numArgs: 4,
- variableArgs: false
- };
- t['y'] = {
- id: OPS.curveTo3,
- numArgs: 4,
- variableArgs: false
- };
- t['h'] = {
- id: OPS.closePath,
- numArgs: 0,
- variableArgs: false
- };
- t['re'] = {
- id: OPS.rectangle,
- numArgs: 4,
- variableArgs: false
- };
- t['S'] = {
- id: OPS.stroke,
- numArgs: 0,
- variableArgs: false
- };
- t['s'] = {
- id: OPS.closeStroke,
- numArgs: 0,
- variableArgs: false
- };
- t['f'] = {
- id: OPS.fill,
- numArgs: 0,
- variableArgs: false
- };
- t['F'] = {
- id: OPS.fill,
- numArgs: 0,
- variableArgs: false
- };
- t['f*'] = {
- id: OPS.eoFill,
- numArgs: 0,
- variableArgs: false
- };
- t['B'] = {
- id: OPS.fillStroke,
- numArgs: 0,
- variableArgs: false
- };
- t['B*'] = {
- id: OPS.eoFillStroke,
- numArgs: 0,
- variableArgs: false
- };
- t['b'] = {
- id: OPS.closeFillStroke,
- numArgs: 0,
- variableArgs: false
- };
- t['b*'] = {
- id: OPS.closeEOFillStroke,
- numArgs: 0,
- variableArgs: false
- };
- t['n'] = {
- id: OPS.endPath,
- numArgs: 0,
- variableArgs: false
- };
- t['W'] = {
- id: OPS.clip,
- numArgs: 0,
- variableArgs: false
- };
- t['W*'] = {
- id: OPS.eoClip,
- numArgs: 0,
- variableArgs: false
- };
- t['BT'] = {
- id: OPS.beginText,
- numArgs: 0,
- variableArgs: false
- };
- t['ET'] = {
- id: OPS.endText,
- numArgs: 0,
- variableArgs: false
- };
- t['Tc'] = {
- id: OPS.setCharSpacing,
- numArgs: 1,
- variableArgs: false
- };
- t['Tw'] = {
- id: OPS.setWordSpacing,
- numArgs: 1,
- variableArgs: false
- };
- t['Tz'] = {
- id: OPS.setHScale,
- numArgs: 1,
- variableArgs: false
- };
- t['TL'] = {
- id: OPS.setLeading,
- numArgs: 1,
- variableArgs: false
- };
- t['Tf'] = {
- id: OPS.setFont,
- numArgs: 2,
- variableArgs: false
- };
- t['Tr'] = {
- id: OPS.setTextRenderingMode,
- numArgs: 1,
- variableArgs: false
- };
- t['Ts'] = {
- id: OPS.setTextRise,
- numArgs: 1,
- variableArgs: false
- };
- t['Td'] = {
- id: OPS.moveText,
- numArgs: 2,
- variableArgs: false
- };
- t['TD'] = {
- id: OPS.setLeadingMoveText,
- numArgs: 2,
- variableArgs: false
- };
- t['Tm'] = {
- id: OPS.setTextMatrix,
- numArgs: 6,
- variableArgs: false
- };
- t['T*'] = {
- id: OPS.nextLine,
- numArgs: 0,
- variableArgs: false
- };
- t['Tj'] = {
- id: OPS.showText,
- numArgs: 1,
- variableArgs: false
- };
- t['TJ'] = {
- id: OPS.showSpacedText,
- numArgs: 1,
- variableArgs: false
- };
- t['\''] = {
- id: OPS.nextLineShowText,
- numArgs: 1,
- variableArgs: false
- };
- t['"'] = {
- id: OPS.nextLineSetSpacingShowText,
- numArgs: 3,
- variableArgs: false
- };
- t['d0'] = {
- id: OPS.setCharWidth,
- numArgs: 2,
- variableArgs: false
- };
- t['d1'] = {
- id: OPS.setCharWidthAndBounds,
- numArgs: 6,
- variableArgs: false
- };
- t['CS'] = {
- id: OPS.setStrokeColorSpace,
- numArgs: 1,
- variableArgs: false
- };
- t['cs'] = {
- id: OPS.setFillColorSpace,
- numArgs: 1,
- variableArgs: false
- };
- t['SC'] = {
- id: OPS.setStrokeColor,
- numArgs: 4,
- variableArgs: true
- };
- t['SCN'] = {
- id: OPS.setStrokeColorN,
- numArgs: 33,
- variableArgs: true
- };
- t['sc'] = {
- id: OPS.setFillColor,
- numArgs: 4,
- variableArgs: true
- };
- t['scn'] = {
- id: OPS.setFillColorN,
- numArgs: 33,
- variableArgs: true
- };
- t['G'] = {
- id: OPS.setStrokeGray,
- numArgs: 1,
- variableArgs: false
- };
- t['g'] = {
- id: OPS.setFillGray,
- numArgs: 1,
- variableArgs: false
- };
- t['RG'] = {
- id: OPS.setStrokeRGBColor,
- numArgs: 3,
- variableArgs: false
- };
- t['rg'] = {
- id: OPS.setFillRGBColor,
- numArgs: 3,
- variableArgs: false
- };
- t['K'] = {
- id: OPS.setStrokeCMYKColor,
- numArgs: 4,
- variableArgs: false
- };
- t['k'] = {
- id: OPS.setFillCMYKColor,
- numArgs: 4,
- variableArgs: false
- };
- t['sh'] = {
- id: OPS.shadingFill,
- numArgs: 1,
- variableArgs: false
- };
- t['BI'] = {
- id: OPS.beginInlineImage,
- numArgs: 0,
- variableArgs: false
- };
- t['ID'] = {
- id: OPS.beginImageData,
- numArgs: 0,
- variableArgs: false
- };
- t['EI'] = {
- id: OPS.endInlineImage,
- numArgs: 1,
- variableArgs: false
- };
- t['Do'] = {
- id: OPS.paintXObject,
- numArgs: 1,
- variableArgs: false
- };
- t['MP'] = {
- id: OPS.markPoint,
- numArgs: 1,
- variableArgs: false
- };
- t['DP'] = {
- id: OPS.markPointProps,
- numArgs: 2,
- variableArgs: false
- };
- t['BMC'] = {
- id: OPS.beginMarkedContent,
- numArgs: 1,
- variableArgs: false
- };
- t['BDC'] = {
- id: OPS.beginMarkedContentProps,
- numArgs: 2,
- variableArgs: false
- };
- t['EMC'] = {
- id: OPS.endMarkedContent,
- numArgs: 0,
- variableArgs: false
- };
- t['BX'] = {
- id: OPS.beginCompat,
- numArgs: 0,
- variableArgs: false
- };
- t['EX'] = {
- id: OPS.endCompat,
- numArgs: 0,
- variableArgs: false
- };
- t['BM'] = null;
- t['BD'] = null;
- t['true'] = null;
- t['fa'] = null;
- t['fal'] = null;
- t['fals'] = null;
- t['false'] = null;
- t['nu'] = null;
- t['nul'] = null;
- t['null'] = null;
- });
- function EvaluatorPreprocessor(stream, xref, stateManager) {
- this.opMap = getOPMap();
- this.parser = new Parser(new Lexer(stream, this.opMap), false, xref);
- this.stateManager = stateManager;
- this.nonProcessedArgs = [];
- }
- EvaluatorPreprocessor.prototype = {
- get savedStatesDepth() {
- return this.stateManager.stateStack.length;
- },
- read: function EvaluatorPreprocessor_read(operation) {
- var args = operation.args;
- while (true) {
- var obj = this.parser.getObj();
- if (isCmd(obj)) {
- var cmd = obj.cmd;
- var opSpec = this.opMap[cmd];
- if (!opSpec) {
- warn('Unknown command "' + cmd + '"');
- continue;
- }
- var fn = opSpec.id;
- var numArgs = opSpec.numArgs;
- var argsLength = args !== null ? args.length : 0;
- if (!opSpec.variableArgs) {
- if (argsLength !== numArgs) {
- var nonProcessedArgs = this.nonProcessedArgs;
- while (argsLength > numArgs) {
- nonProcessedArgs.push(args.shift());
- argsLength--;
- }
- while (argsLength < numArgs && nonProcessedArgs.length !== 0) {
- if (args === null) {
- args = [];
- }
- args.unshift(nonProcessedArgs.pop());
- argsLength++;
- }
- }
- if (argsLength < numArgs) {
- warn('Skipping command ' + fn + ': expected ' + numArgs + ' args, but received ' + argsLength + ' args.');
- if (args !== null) {
- args.length = 0;
- }
- continue;
- }
- } else if (argsLength > numArgs) {
- info('Command ' + fn + ': expected [0,' + numArgs + '] args, but received ' + argsLength + ' args.');
- }
- this.preprocessCommand(fn, args);
- operation.fn = fn;
- operation.args = args;
- return true;
- }
- if (isEOF(obj)) {
- return false;
- }
- if (obj !== null) {
- if (args === null) {
- args = [];
- }
- args.push(obj);
- assert(args.length <= 33, 'Too many arguments');
- }
- }
- },
- preprocessCommand: function EvaluatorPreprocessor_preprocessCommand(fn, args) {
- switch (fn | 0) {
- case OPS.save:
- this.stateManager.save();
- break;
- case OPS.restore:
- this.stateManager.restore();
- break;
- case OPS.transform:
- this.stateManager.transform(args);
- break;
- }
- }
- };
- return EvaluatorPreprocessor;
-}();
-var QueueOptimizer = function QueueOptimizerClosure() {
- function addState(parentState, pattern, fn) {
- var state = parentState;
- for (var i = 0, ii = pattern.length - 1; i < ii; i++) {
- var item = pattern[i];
- state = state[item] || (state[item] = []);
- }
- state[pattern[pattern.length - 1]] = fn;
- }
- function handlePaintSolidColorImageMask(iFirstSave, count, fnArray, argsArray) {
- var iFirstPIMXO = iFirstSave + 2;
- for (var i = 0; i < count; i++) {
- var arg = argsArray[iFirstPIMXO + 4 * i];
- var imageMask = arg.length === 1 && arg[0];
- if (imageMask && imageMask.width === 1 && imageMask.height === 1 && (!imageMask.data.length || imageMask.data.length === 1 && imageMask.data[0] === 0)) {
- fnArray[iFirstPIMXO + 4 * i] = OPS.paintSolidColorImageMask;
- continue;
- }
- break;
- }
- return count - i;
- }
- var InitialState = [];
- addState(InitialState, [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], function foundInlineImageGroup(context) {
- var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
- var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
- var MAX_WIDTH = 1000;
- var IMAGE_PADDING = 1;
- var fnArray = context.fnArray,
- argsArray = context.argsArray;
- var curr = context.iCurr;
- var iFirstSave = curr - 3;
- var iFirstTransform = curr - 2;
- var iFirstPIIXO = curr - 1;
- var i = iFirstSave + 4;
- var ii = fnArray.length;
- while (i + 3 < ii) {
- if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintInlineImageXObject || fnArray[i + 3] !== OPS.restore) {
- break;
- }
- i += 4;
- }
- var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
- if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
- return i;
- }
- var maxX = 0;
- var map = [],
- maxLineHeight = 0;
- var currentX = IMAGE_PADDING,
- currentY = IMAGE_PADDING;
- var q;
- for (q = 0; q < count; q++) {
- var transform = argsArray[iFirstTransform + (q << 2)];
- var img = argsArray[iFirstPIIXO + (q << 2)][0];
- if (currentX + img.width > MAX_WIDTH) {
- maxX = Math.max(maxX, currentX);
- currentY += maxLineHeight + 2 * IMAGE_PADDING;
- currentX = 0;
- maxLineHeight = 0;
- }
- map.push({
- transform: transform,
- x: currentX,
- y: currentY,
- w: img.width,
- h: img.height
- });
- currentX += img.width + 2 * IMAGE_PADDING;
- maxLineHeight = Math.max(maxLineHeight, img.height);
- }
- var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
- var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
- var imgData = new Uint8Array(imgWidth * imgHeight * 4);
- var imgRowSize = imgWidth << 2;
- for (q = 0; q < count; q++) {
- var data = argsArray[iFirstPIIXO + (q << 2)][0].data;
- var rowSize = map[q].w << 2;
- var dataOffset = 0;
- var offset = map[q].x + map[q].y * imgWidth << 2;
- imgData.set(data.subarray(0, rowSize), offset - imgRowSize);
- for (var k = 0, kk = map[q].h; k < kk; k++) {
- imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset);
- dataOffset += rowSize;
- offset += imgRowSize;
- }
- imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset);
- while (offset >= 0) {
- data[offset - 4] = data[offset];
- data[offset - 3] = data[offset + 1];
- data[offset - 2] = data[offset + 2];
- data[offset - 1] = data[offset + 3];
- data[offset + rowSize] = data[offset + rowSize - 4];
- data[offset + rowSize + 1] = data[offset + rowSize - 3];
- data[offset + rowSize + 2] = data[offset + rowSize - 2];
- data[offset + rowSize + 3] = data[offset + rowSize - 1];
- offset -= imgRowSize;
- }
- }
- fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
- argsArray.splice(iFirstSave, count * 4, [{
- width: imgWidth,
- height: imgHeight,
- kind: ImageKind.RGBA_32BPP,
- data: imgData
- }, map]);
- return iFirstSave + 1;
- });
- addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], function foundImageMaskGroup(context) {
- var MIN_IMAGES_IN_MASKS_BLOCK = 10;
- var MAX_IMAGES_IN_MASKS_BLOCK = 100;
- var MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
- var fnArray = context.fnArray,
- argsArray = context.argsArray;
- var curr = context.iCurr;
- var iFirstSave = curr - 3;
- var iFirstTransform = curr - 2;
- var iFirstPIMXO = curr - 1;
- var i = iFirstSave + 4;
- var ii = fnArray.length;
- while (i + 3 < ii) {
- if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintImageMaskXObject || fnArray[i + 3] !== OPS.restore) {
- break;
- }
- i += 4;
- }
- var count = (i - iFirstSave) / 4;
- count = handlePaintSolidColorImageMask(iFirstSave, count, fnArray, argsArray);
- if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
- return i;
- }
- var q;
- var isSameImage = false;
- var iTransform, transformArgs;
- var firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
- if (argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0) {
- isSameImage = true;
- var firstTransformArg0 = argsArray[iFirstTransform][0];
- var firstTransformArg3 = argsArray[iFirstTransform][3];
- iTransform = iFirstTransform + 4;
- var iPIMXO = iFirstPIMXO + 4;
- for (q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
- transformArgs = argsArray[iTransform];
- if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== 0 || transformArgs[2] !== 0 || transformArgs[3] !== firstTransformArg3) {
- if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
- isSameImage = false;
- } else {
- count = q;
- }
- break;
- }
- }
- }
- if (isSameImage) {
- count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
- var positions = new Float32Array(count * 2);
- iTransform = iFirstTransform;
- for (q = 0; q < count; q++, iTransform += 4) {
- transformArgs = argsArray[iTransform];
- positions[q << 1] = transformArgs[4];
- positions[(q << 1) + 1] = transformArgs[5];
- }
- fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat);
- argsArray.splice(iFirstSave, count * 4, [firstPIMXOArg0, firstTransformArg0, firstTransformArg3, positions]);
- } else {
- count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
- var images = [];
- for (q = 0; q < count; q++) {
- transformArgs = argsArray[iFirstTransform + (q << 2)];
- var maskParams = argsArray[iFirstPIMXO + (q << 2)][0];
- images.push({
- data: maskParams.data,
- width: maskParams.width,
- height: maskParams.height,
- transform: transformArgs
- });
- }
- fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup);
- argsArray.splice(iFirstSave, count * 4, [images]);
- }
- return iFirstSave + 1;
- });
- addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], function (context) {
- var MIN_IMAGES_IN_BLOCK = 3;
- var MAX_IMAGES_IN_BLOCK = 1000;
- var fnArray = context.fnArray,
- argsArray = context.argsArray;
- var curr = context.iCurr;
- var iFirstSave = curr - 3;
- var iFirstTransform = curr - 2;
- var iFirstPIXO = curr - 1;
- var iFirstRestore = curr;
- if (argsArray[iFirstTransform][1] !== 0 || argsArray[iFirstTransform][2] !== 0) {
- return iFirstRestore + 1;
- }
- var firstPIXOArg0 = argsArray[iFirstPIXO][0];
- var firstTransformArg0 = argsArray[iFirstTransform][0];
- var firstTransformArg3 = argsArray[iFirstTransform][3];
- var i = iFirstSave + 4;
- var ii = fnArray.length;
- while (i + 3 < ii) {
- if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintImageXObject || fnArray[i + 3] !== OPS.restore) {
- break;
- }
- if (argsArray[i + 1][0] !== firstTransformArg0 || argsArray[i + 1][1] !== 0 || argsArray[i + 1][2] !== 0 || argsArray[i + 1][3] !== firstTransformArg3) {
- break;
- }
- if (argsArray[i + 2][0] !== firstPIXOArg0) {
- break;
- }
- i += 4;
- }
- var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_BLOCK);
- if (count < MIN_IMAGES_IN_BLOCK) {
- return i;
- }
- var positions = new Float32Array(count * 2);
- var iTransform = iFirstTransform;
- for (var q = 0; q < count; q++, iTransform += 4) {
- var transformArgs = argsArray[iTransform];
- positions[q << 1] = transformArgs[4];
- positions[(q << 1) + 1] = transformArgs[5];
- }
- var args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, positions];
- fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat);
- argsArray.splice(iFirstSave, count * 4, args);
- return iFirstSave + 1;
- });
- addState(InitialState, [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], function (context) {
- var MIN_CHARS_IN_BLOCK = 3;
- var MAX_CHARS_IN_BLOCK = 1000;
- var fnArray = context.fnArray,
- argsArray = context.argsArray;
- var curr = context.iCurr;
- var iFirstBeginText = curr - 4;
- var iFirstSetFont = curr - 3;
- var iFirstSetTextMatrix = curr - 2;
- var iFirstShowText = curr - 1;
- var iFirstEndText = curr;
- var firstSetFontArg0 = argsArray[iFirstSetFont][0];
- var firstSetFontArg1 = argsArray[iFirstSetFont][1];
- var i = iFirstBeginText + 5;
- var ii = fnArray.length;
- while (i + 4 < ii) {
- if (fnArray[i] !== OPS.beginText || fnArray[i + 1] !== OPS.setFont || fnArray[i + 2] !== OPS.setTextMatrix || fnArray[i + 3] !== OPS.showText || fnArray[i + 4] !== OPS.endText) {
- break;
- }
- if (argsArray[i + 1][0] !== firstSetFontArg0 || argsArray[i + 1][1] !== firstSetFontArg1) {
- break;
- }
- i += 5;
- }
- var count = Math.min((i - iFirstBeginText) / 5, MAX_CHARS_IN_BLOCK);
- if (count < MIN_CHARS_IN_BLOCK) {
- return i;
- }
- var iFirst = iFirstBeginText;
- if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) {
- count++;
- iFirst -= 5;
- }
- var iEndText = iFirst + 4;
- for (var q = 1; q < count; q++) {
- fnArray.splice(iEndText, 3);
- argsArray.splice(iEndText, 3);
- iEndText += 2;
- }
- return iEndText + 1;
- });
- function QueueOptimizer() {}
- QueueOptimizer.prototype = {
- optimize: function QueueOptimizer_optimize(queue) {
- var fnArray = queue.fnArray,
- argsArray = queue.argsArray;
- var context = {
- iCurr: 0,
- fnArray: fnArray,
- argsArray: argsArray
- };
- var state;
- var i = 0,
- ii = fnArray.length;
- while (i < ii) {
- state = (state || InitialState)[fnArray[i]];
- if (typeof state === 'function') {
- context.iCurr = i;
- i = state(context);
- state = undefined;
- ii = context.fnArray.length;
- } else {
- i++;
- }
- }
- }
- };
- return QueueOptimizer;
-}();
-exports.OperatorList = OperatorList;
-exports.PartialEvaluator = PartialEvaluator;
-
-/***/ }),
-/* 15 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreArithmeticDecoder = __w_pdfjs_require__(10);
-var info = sharedUtil.info;
-var warn = sharedUtil.warn;
-var error = sharedUtil.error;
-var log2 = sharedUtil.log2;
-var readUint16 = sharedUtil.readUint16;
-var readUint32 = sharedUtil.readUint32;
-var ArithmeticDecoder = coreArithmeticDecoder.ArithmeticDecoder;
-var JpxImage = function JpxImageClosure() {
- var SubbandsGainLog2 = {
- 'LL': 0,
- 'LH': 1,
- 'HL': 1,
- 'HH': 2
- };
- function JpxImage() {
- this.failOnCorruptedImage = false;
- }
- JpxImage.prototype = {
- parse: function JpxImage_parse(data) {
- var head = readUint16(data, 0);
- if (head === 0xFF4F) {
- this.parseCodestream(data, 0, data.length);
- return;
- }
- var position = 0,
- length = data.length;
- while (position < length) {
- var headerSize = 8;
- var lbox = readUint32(data, position);
- var tbox = readUint32(data, position + 4);
- position += headerSize;
- if (lbox === 1) {
- lbox = readUint32(data, position) * 4294967296 + readUint32(data, position + 4);
- position += 8;
- headerSize += 8;
- }
- if (lbox === 0) {
- lbox = length - position + headerSize;
- }
- if (lbox < headerSize) {
- error('JPX Error: Invalid box field size');
- }
- var dataLength = lbox - headerSize;
- var jumpDataLength = true;
- switch (tbox) {
- case 0x6A703268:
- jumpDataLength = false;
- break;
- case 0x636F6C72:
- var method = data[position];
- if (method === 1) {
- var colorspace = readUint32(data, position + 3);
- switch (colorspace) {
- case 16:
- case 17:
- case 18:
- break;
- default:
- warn('Unknown colorspace ' + colorspace);
- break;
- }
- } else if (method === 2) {
- info('ICC profile not supported');
- }
- break;
- case 0x6A703263:
- this.parseCodestream(data, position, position + dataLength);
- break;
- case 0x6A502020:
- if (readUint32(data, position) !== 0x0d0a870a) {
- warn('Invalid JP2 signature');
- }
- break;
- case 0x6A501A1A:
- case 0x66747970:
- case 0x72726571:
- case 0x72657320:
- case 0x69686472:
- break;
- default:
- var headerType = String.fromCharCode(tbox >> 24 & 0xFF, tbox >> 16 & 0xFF, tbox >> 8 & 0xFF, tbox & 0xFF);
- warn('Unsupported header type ' + tbox + ' (' + headerType + ')');
- break;
- }
- if (jumpDataLength) {
- position += dataLength;
- }
- }
- },
- parseImageProperties: function JpxImage_parseImageProperties(stream) {
- var newByte = stream.getByte();
- while (newByte >= 0) {
- var oldByte = newByte;
- newByte = stream.getByte();
- var code = oldByte << 8 | newByte;
- if (code === 0xFF51) {
- stream.skip(4);
- var Xsiz = stream.getInt32() >>> 0;
- var Ysiz = stream.getInt32() >>> 0;
- var XOsiz = stream.getInt32() >>> 0;
- var YOsiz = stream.getInt32() >>> 0;
- stream.skip(16);
- var Csiz = stream.getUint16();
- this.width = Xsiz - XOsiz;
- this.height = Ysiz - YOsiz;
- this.componentsCount = Csiz;
- this.bitsPerComponent = 8;
- return;
- }
- }
- error('JPX Error: No size marker found in JPX stream');
- },
- parseCodestream: function JpxImage_parseCodestream(data, start, end) {
- var context = {};
- var doNotRecover = false;
- try {
- var position = start;
- while (position + 1 < end) {
- var code = readUint16(data, position);
- position += 2;
- var length = 0,
- j,
- sqcd,
- spqcds,
- spqcdSize,
- scalarExpounded,
- tile;
- switch (code) {
- case 0xFF4F:
- context.mainHeader = true;
- break;
- case 0xFFD9:
- break;
- case 0xFF51:
- length = readUint16(data, position);
- var siz = {};
- siz.Xsiz = readUint32(data, position + 4);
- siz.Ysiz = readUint32(data, position + 8);
- siz.XOsiz = readUint32(data, position + 12);
- siz.YOsiz = readUint32(data, position + 16);
- siz.XTsiz = readUint32(data, position + 20);
- siz.YTsiz = readUint32(data, position + 24);
- siz.XTOsiz = readUint32(data, position + 28);
- siz.YTOsiz = readUint32(data, position + 32);
- var componentsCount = readUint16(data, position + 36);
- siz.Csiz = componentsCount;
- var components = [];
- j = position + 38;
- for (var i = 0; i < componentsCount; i++) {
- var component = {
- precision: (data[j] & 0x7F) + 1,
- isSigned: !!(data[j] & 0x80),
- XRsiz: data[j + 1],
- YRsiz: data[j + 1]
- };
- calculateComponentDimensions(component, siz);
- components.push(component);
- }
- context.SIZ = siz;
- context.components = components;
- calculateTileGrids(context, components);
- context.QCC = [];
- context.COC = [];
- break;
- case 0xFF5C:
- length = readUint16(data, position);
- var qcd = {};
- j = position + 2;
- sqcd = data[j++];
- switch (sqcd & 0x1F) {
- case 0:
- spqcdSize = 8;
- scalarExpounded = true;
- break;
- case 1:
- spqcdSize = 16;
- scalarExpounded = false;
- break;
- case 2:
- spqcdSize = 16;
- scalarExpounded = true;
- break;
- default:
- throw new Error('Invalid SQcd value ' + sqcd);
- }
- qcd.noQuantization = spqcdSize === 8;
- qcd.scalarExpounded = scalarExpounded;
- qcd.guardBits = sqcd >> 5;
- spqcds = [];
- while (j < length + position) {
- var spqcd = {};
- if (spqcdSize === 8) {
- spqcd.epsilon = data[j++] >> 3;
- spqcd.mu = 0;
- } else {
- spqcd.epsilon = data[j] >> 3;
- spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1];
- j += 2;
- }
- spqcds.push(spqcd);
- }
- qcd.SPqcds = spqcds;
- if (context.mainHeader) {
- context.QCD = qcd;
- } else {
- context.currentTile.QCD = qcd;
- context.currentTile.QCC = [];
- }
- break;
- case 0xFF5D:
- length = readUint16(data, position);
- var qcc = {};
- j = position + 2;
- var cqcc;
- if (context.SIZ.Csiz < 257) {
- cqcc = data[j++];
- } else {
- cqcc = readUint16(data, j);
- j += 2;
- }
- sqcd = data[j++];
- switch (sqcd & 0x1F) {
- case 0:
- spqcdSize = 8;
- scalarExpounded = true;
- break;
- case 1:
- spqcdSize = 16;
- scalarExpounded = false;
- break;
- case 2:
- spqcdSize = 16;
- scalarExpounded = true;
- break;
- default:
- throw new Error('Invalid SQcd value ' + sqcd);
- }
- qcc.noQuantization = spqcdSize === 8;
- qcc.scalarExpounded = scalarExpounded;
- qcc.guardBits = sqcd >> 5;
- spqcds = [];
- while (j < length + position) {
- spqcd = {};
- if (spqcdSize === 8) {
- spqcd.epsilon = data[j++] >> 3;
- spqcd.mu = 0;
- } else {
- spqcd.epsilon = data[j] >> 3;
- spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1];
- j += 2;
- }
- spqcds.push(spqcd);
- }
- qcc.SPqcds = spqcds;
- if (context.mainHeader) {
- context.QCC[cqcc] = qcc;
- } else {
- context.currentTile.QCC[cqcc] = qcc;
- }
- break;
- case 0xFF52:
- length = readUint16(data, position);
- var cod = {};
- j = position + 2;
- var scod = data[j++];
- cod.entropyCoderWithCustomPrecincts = !!(scod & 1);
- cod.sopMarkerUsed = !!(scod & 2);
- cod.ephMarkerUsed = !!(scod & 4);
- cod.progressionOrder = data[j++];
- cod.layersCount = readUint16(data, j);
- j += 2;
- cod.multipleComponentTransform = data[j++];
- cod.decompositionLevelsCount = data[j++];
- cod.xcb = (data[j++] & 0xF) + 2;
- cod.ycb = (data[j++] & 0xF) + 2;
- var blockStyle = data[j++];
- cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1);
- cod.resetContextProbabilities = !!(blockStyle & 2);
- cod.terminationOnEachCodingPass = !!(blockStyle & 4);
- cod.verticalyStripe = !!(blockStyle & 8);
- cod.predictableTermination = !!(blockStyle & 16);
- cod.segmentationSymbolUsed = !!(blockStyle & 32);
- cod.reversibleTransformation = data[j++];
- if (cod.entropyCoderWithCustomPrecincts) {
- var precinctsSizes = [];
- while (j < length + position) {
- var precinctsSize = data[j++];
- precinctsSizes.push({
- PPx: precinctsSize & 0xF,
- PPy: precinctsSize >> 4
- });
- }
- cod.precinctsSizes = precinctsSizes;
- }
- var unsupported = [];
- if (cod.selectiveArithmeticCodingBypass) {
- unsupported.push('selectiveArithmeticCodingBypass');
- }
- if (cod.resetContextProbabilities) {
- unsupported.push('resetContextProbabilities');
- }
- if (cod.terminationOnEachCodingPass) {
- unsupported.push('terminationOnEachCodingPass');
- }
- if (cod.verticalyStripe) {
- unsupported.push('verticalyStripe');
- }
- if (cod.predictableTermination) {
- unsupported.push('predictableTermination');
- }
- if (unsupported.length > 0) {
- doNotRecover = true;
- throw new Error('Unsupported COD options (' + unsupported.join(', ') + ')');
- }
- if (context.mainHeader) {
- context.COD = cod;
- } else {
- context.currentTile.COD = cod;
- context.currentTile.COC = [];
- }
- break;
- case 0xFF90:
- length = readUint16(data, position);
- tile = {};
- tile.index = readUint16(data, position + 2);
- tile.length = readUint32(data, position + 4);
- tile.dataEnd = tile.length + position - 2;
- tile.partIndex = data[position + 8];
- tile.partsCount = data[position + 9];
- context.mainHeader = false;
- if (tile.partIndex === 0) {
- tile.COD = context.COD;
- tile.COC = context.COC.slice(0);
- tile.QCD = context.QCD;
- tile.QCC = context.QCC.slice(0);
- }
- context.currentTile = tile;
- break;
- case 0xFF93:
- tile = context.currentTile;
- if (tile.partIndex === 0) {
- initializeTile(context, tile.index);
- buildPackets(context);
- }
- length = tile.dataEnd - position;
- parseTilePackets(context, data, position, length);
- break;
- case 0xFF55:
- case 0xFF57:
- case 0xFF58:
- case 0xFF64:
- length = readUint16(data, position);
- break;
- case 0xFF53:
- throw new Error('Codestream code 0xFF53 (COC) is ' + 'not implemented');
- default:
- throw new Error('Unknown codestream code: ' + code.toString(16));
- }
- position += length;
- }
- } catch (e) {
- if (doNotRecover || this.failOnCorruptedImage) {
- error('JPX Error: ' + e.message);
- } else {
- warn('JPX: Trying to recover from: ' + e.message);
- }
- }
- this.tiles = transformComponents(context);
- this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
- this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
- this.componentsCount = context.SIZ.Csiz;
- }
- };
- function calculateComponentDimensions(component, siz) {
- component.x0 = Math.ceil(siz.XOsiz / component.XRsiz);
- component.x1 = Math.ceil(siz.Xsiz / component.XRsiz);
- component.y0 = Math.ceil(siz.YOsiz / component.YRsiz);
- component.y1 = Math.ceil(siz.Ysiz / component.YRsiz);
- component.width = component.x1 - component.x0;
- component.height = component.y1 - component.y0;
- }
- function calculateTileGrids(context, components) {
- var siz = context.SIZ;
- var tile,
- tiles = [];
- var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz);
- var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz);
- for (var q = 0; q < numYtiles; q++) {
- for (var p = 0; p < numXtiles; p++) {
- tile = {};
- tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz);
- tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz);
- tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz);
- tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz);
- tile.width = tile.tx1 - tile.tx0;
- tile.height = tile.ty1 - tile.ty0;
- tile.components = [];
- tiles.push(tile);
- }
- }
- context.tiles = tiles;
- var componentsCount = siz.Csiz;
- for (var i = 0, ii = componentsCount; i < ii; i++) {
- var component = components[i];
- for (var j = 0, jj = tiles.length; j < jj; j++) {
- var tileComponent = {};
- tile = tiles[j];
- tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
- tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
- tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
- tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
- tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
- tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0;
- tile.components[i] = tileComponent;
- }
- }
- }
- function getBlocksDimensions(context, component, r) {
- var codOrCoc = component.codingStyleParameters;
- var result = {};
- if (!codOrCoc.entropyCoderWithCustomPrecincts) {
- result.PPx = 15;
- result.PPy = 15;
- } else {
- result.PPx = codOrCoc.precinctsSizes[r].PPx;
- result.PPy = codOrCoc.precinctsSizes[r].PPy;
- }
- result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) : Math.min(codOrCoc.xcb, result.PPx);
- result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) : Math.min(codOrCoc.ycb, result.PPy);
- return result;
- }
- function buildPrecincts(context, resolution, dimensions) {
- var precinctWidth = 1 << dimensions.PPx;
- var precinctHeight = 1 << dimensions.PPy;
- var isZeroRes = resolution.resLevel === 0;
- var precinctWidthInSubband = 1 << dimensions.PPx + (isZeroRes ? 0 : -1);
- var precinctHeightInSubband = 1 << dimensions.PPy + (isZeroRes ? 0 : -1);
- var numprecinctswide = resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - Math.floor(resolution.trx0 / precinctWidth) : 0;
- var numprecinctshigh = resolution.try1 > resolution.try0 ? Math.ceil(resolution.try1 / precinctHeight) - Math.floor(resolution.try0 / precinctHeight) : 0;
- var numprecincts = numprecinctswide * numprecinctshigh;
- resolution.precinctParameters = {
- precinctWidth: precinctWidth,
- precinctHeight: precinctHeight,
- numprecinctswide: numprecinctswide,
- numprecinctshigh: numprecinctshigh,
- numprecincts: numprecincts,
- precinctWidthInSubband: precinctWidthInSubband,
- precinctHeightInSubband: precinctHeightInSubband
- };
- }
- function buildCodeblocks(context, subband, dimensions) {
- var xcb_ = dimensions.xcb_;
- var ycb_ = dimensions.ycb_;
- var codeblockWidth = 1 << xcb_;
- var codeblockHeight = 1 << ycb_;
- var cbx0 = subband.tbx0 >> xcb_;
- var cby0 = subband.tby0 >> ycb_;
- var cbx1 = subband.tbx1 + codeblockWidth - 1 >> xcb_;
- var cby1 = subband.tby1 + codeblockHeight - 1 >> ycb_;
- var precinctParameters = subband.resolution.precinctParameters;
- var codeblocks = [];
- var precincts = [];
- var i, j, codeblock, precinctNumber;
- for (j = cby0; j < cby1; j++) {
- for (i = cbx0; i < cbx1; i++) {
- codeblock = {
- cbx: i,
- cby: j,
- tbx0: codeblockWidth * i,
- tby0: codeblockHeight * j,
- tbx1: codeblockWidth * (i + 1),
- tby1: codeblockHeight * (j + 1)
- };
- codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
- codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
- codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
- codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
- var pi = Math.floor((codeblock.tbx0_ - subband.tbx0) / precinctParameters.precinctWidthInSubband);
- var pj = Math.floor((codeblock.tby0_ - subband.tby0) / precinctParameters.precinctHeightInSubband);
- precinctNumber = pi + pj * precinctParameters.numprecinctswide;
- codeblock.precinctNumber = precinctNumber;
- codeblock.subbandType = subband.type;
- codeblock.Lblock = 3;
- if (codeblock.tbx1_ <= codeblock.tbx0_ || codeblock.tby1_ <= codeblock.tby0_) {
- continue;
- }
- codeblocks.push(codeblock);
- var precinct = precincts[precinctNumber];
- if (precinct !== undefined) {
- if (i < precinct.cbxMin) {
- precinct.cbxMin = i;
- } else if (i > precinct.cbxMax) {
- precinct.cbxMax = i;
- }
- if (j < precinct.cbyMin) {
- precinct.cbxMin = j;
- } else if (j > precinct.cbyMax) {
- precinct.cbyMax = j;
- }
- } else {
- precincts[precinctNumber] = precinct = {
- cbxMin: i,
- cbyMin: j,
- cbxMax: i,
- cbyMax: j
- };
- }
- codeblock.precinct = precinct;
- }
- }
- subband.codeblockParameters = {
- codeblockWidth: xcb_,
- codeblockHeight: ycb_,
- numcodeblockwide: cbx1 - cbx0 + 1,
- numcodeblockhigh: cby1 - cby0 + 1
- };
- subband.codeblocks = codeblocks;
- subband.precincts = precincts;
- }
- function createPacket(resolution, precinctNumber, layerNumber) {
- var precinctCodeblocks = [];
- var subbands = resolution.subbands;
- for (var i = 0, ii = subbands.length; i < ii; i++) {
- var subband = subbands[i];
- var codeblocks = subband.codeblocks;
- for (var j = 0, jj = codeblocks.length; j < jj; j++) {
- var codeblock = codeblocks[j];
- if (codeblock.precinctNumber !== precinctNumber) {
- continue;
- }
- precinctCodeblocks.push(codeblock);
- }
- }
- return {
- layerNumber: layerNumber,
- codeblocks: precinctCodeblocks
- };
- }
- function LayerResolutionComponentPositionIterator(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var layersCount = tile.codingStyleDefaultParameters.layersCount;
- var componentsCount = siz.Csiz;
- var maxDecompositionLevelsCount = 0;
- for (var q = 0; q < componentsCount; q++) {
- maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount);
- }
- var l = 0,
- r = 0,
- i = 0,
- k = 0;
- this.nextPacket = function JpxImage_nextPacket() {
- for (; l < layersCount; l++) {
- for (; r <= maxDecompositionLevelsCount; r++) {
- for (; i < componentsCount; i++) {
- var component = tile.components[i];
- if (r > component.codingStyleParameters.decompositionLevelsCount) {
- continue;
- }
- var resolution = component.resolutions[r];
- var numprecincts = resolution.precinctParameters.numprecincts;
- for (; k < numprecincts;) {
- var packet = createPacket(resolution, k, l);
- k++;
- return packet;
- }
- k = 0;
- }
- i = 0;
- }
- r = 0;
- }
- error('JPX Error: Out of packets');
- };
- }
- function ResolutionLayerComponentPositionIterator(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var layersCount = tile.codingStyleDefaultParameters.layersCount;
- var componentsCount = siz.Csiz;
- var maxDecompositionLevelsCount = 0;
- for (var q = 0; q < componentsCount; q++) {
- maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount);
- }
- var r = 0,
- l = 0,
- i = 0,
- k = 0;
- this.nextPacket = function JpxImage_nextPacket() {
- for (; r <= maxDecompositionLevelsCount; r++) {
- for (; l < layersCount; l++) {
- for (; i < componentsCount; i++) {
- var component = tile.components[i];
- if (r > component.codingStyleParameters.decompositionLevelsCount) {
- continue;
- }
- var resolution = component.resolutions[r];
- var numprecincts = resolution.precinctParameters.numprecincts;
- for (; k < numprecincts;) {
- var packet = createPacket(resolution, k, l);
- k++;
- return packet;
- }
- k = 0;
- }
- i = 0;
- }
- l = 0;
- }
- error('JPX Error: Out of packets');
- };
- }
- function ResolutionPositionComponentLayerIterator(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var layersCount = tile.codingStyleDefaultParameters.layersCount;
- var componentsCount = siz.Csiz;
- var l, r, c, p;
- var maxDecompositionLevelsCount = 0;
- for (c = 0; c < componentsCount; c++) {
- var component = tile.components[c];
- maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, component.codingStyleParameters.decompositionLevelsCount);
- }
- var maxNumPrecinctsInLevel = new Int32Array(maxDecompositionLevelsCount + 1);
- for (r = 0; r <= maxDecompositionLevelsCount; ++r) {
- var maxNumPrecincts = 0;
- for (c = 0; c < componentsCount; ++c) {
- var resolutions = tile.components[c].resolutions;
- if (r < resolutions.length) {
- maxNumPrecincts = Math.max(maxNumPrecincts, resolutions[r].precinctParameters.numprecincts);
- }
- }
- maxNumPrecinctsInLevel[r] = maxNumPrecincts;
- }
- l = 0;
- r = 0;
- c = 0;
- p = 0;
- this.nextPacket = function JpxImage_nextPacket() {
- for (; r <= maxDecompositionLevelsCount; r++) {
- for (; p < maxNumPrecinctsInLevel[r]; p++) {
- for (; c < componentsCount; c++) {
- var component = tile.components[c];
- if (r > component.codingStyleParameters.decompositionLevelsCount) {
- continue;
- }
- var resolution = component.resolutions[r];
- var numprecincts = resolution.precinctParameters.numprecincts;
- if (p >= numprecincts) {
- continue;
- }
- for (; l < layersCount;) {
- var packet = createPacket(resolution, p, l);
- l++;
- return packet;
- }
- l = 0;
- }
- c = 0;
- }
- p = 0;
- }
- error('JPX Error: Out of packets');
- };
- }
- function PositionComponentResolutionLayerIterator(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var layersCount = tile.codingStyleDefaultParameters.layersCount;
- var componentsCount = siz.Csiz;
- var precinctsSizes = getPrecinctSizesInImageScale(tile);
- var precinctsIterationSizes = precinctsSizes;
- var l = 0,
- r = 0,
- c = 0,
- px = 0,
- py = 0;
- this.nextPacket = function JpxImage_nextPacket() {
- for (; py < precinctsIterationSizes.maxNumHigh; py++) {
- for (; px < precinctsIterationSizes.maxNumWide; px++) {
- for (; c < componentsCount; c++) {
- var component = tile.components[c];
- var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
- for (; r <= decompositionLevelsCount; r++) {
- var resolution = component.resolutions[r];
- var sizeInImageScale = precinctsSizes.components[c].resolutions[r];
- var k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution);
- if (k === null) {
- continue;
- }
- for (; l < layersCount;) {
- var packet = createPacket(resolution, k, l);
- l++;
- return packet;
- }
- l = 0;
- }
- r = 0;
- }
- c = 0;
- }
- px = 0;
- }
- error('JPX Error: Out of packets');
- };
- }
- function ComponentPositionResolutionLayerIterator(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var layersCount = tile.codingStyleDefaultParameters.layersCount;
- var componentsCount = siz.Csiz;
- var precinctsSizes = getPrecinctSizesInImageScale(tile);
- var l = 0,
- r = 0,
- c = 0,
- px = 0,
- py = 0;
- this.nextPacket = function JpxImage_nextPacket() {
- for (; c < componentsCount; ++c) {
- var component = tile.components[c];
- var precinctsIterationSizes = precinctsSizes.components[c];
- var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
- for (; py < precinctsIterationSizes.maxNumHigh; py++) {
- for (; px < precinctsIterationSizes.maxNumWide; px++) {
- for (; r <= decompositionLevelsCount; r++) {
- var resolution = component.resolutions[r];
- var sizeInImageScale = precinctsIterationSizes.resolutions[r];
- var k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution);
- if (k === null) {
- continue;
- }
- for (; l < layersCount;) {
- var packet = createPacket(resolution, k, l);
- l++;
- return packet;
- }
- l = 0;
- }
- r = 0;
- }
- px = 0;
- }
- py = 0;
- }
- error('JPX Error: Out of packets');
- };
- }
- function getPrecinctIndexIfExist(pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) {
- var posX = pxIndex * precinctIterationSizes.minWidth;
- var posY = pyIndex * precinctIterationSizes.minHeight;
- if (posX % sizeInImageScale.width !== 0 || posY % sizeInImageScale.height !== 0) {
- return null;
- }
- var startPrecinctRowIndex = posY / sizeInImageScale.width * resolution.precinctParameters.numprecinctswide;
- return posX / sizeInImageScale.height + startPrecinctRowIndex;
- }
- function getPrecinctSizesInImageScale(tile) {
- var componentsCount = tile.components.length;
- var minWidth = Number.MAX_VALUE;
- var minHeight = Number.MAX_VALUE;
- var maxNumWide = 0;
- var maxNumHigh = 0;
- var sizePerComponent = new Array(componentsCount);
- for (var c = 0; c < componentsCount; c++) {
- var component = tile.components[c];
- var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
- var sizePerResolution = new Array(decompositionLevelsCount + 1);
- var minWidthCurrentComponent = Number.MAX_VALUE;
- var minHeightCurrentComponent = Number.MAX_VALUE;
- var maxNumWideCurrentComponent = 0;
- var maxNumHighCurrentComponent = 0;
- var scale = 1;
- for (var r = decompositionLevelsCount; r >= 0; --r) {
- var resolution = component.resolutions[r];
- var widthCurrentResolution = scale * resolution.precinctParameters.precinctWidth;
- var heightCurrentResolution = scale * resolution.precinctParameters.precinctHeight;
- minWidthCurrentComponent = Math.min(minWidthCurrentComponent, widthCurrentResolution);
- minHeightCurrentComponent = Math.min(minHeightCurrentComponent, heightCurrentResolution);
- maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent, resolution.precinctParameters.numprecinctswide);
- maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent, resolution.precinctParameters.numprecinctshigh);
- sizePerResolution[r] = {
- width: widthCurrentResolution,
- height: heightCurrentResolution
- };
- scale <<= 1;
- }
- minWidth = Math.min(minWidth, minWidthCurrentComponent);
- minHeight = Math.min(minHeight, minHeightCurrentComponent);
- maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent);
- maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent);
- sizePerComponent[c] = {
- resolutions: sizePerResolution,
- minWidth: minWidthCurrentComponent,
- minHeight: minHeightCurrentComponent,
- maxNumWide: maxNumWideCurrentComponent,
- maxNumHigh: maxNumHighCurrentComponent
- };
- }
- return {
- components: sizePerComponent,
- minWidth: minWidth,
- minHeight: minHeight,
- maxNumWide: maxNumWide,
- maxNumHigh: maxNumHigh
- };
- }
- function buildPackets(context) {
- var siz = context.SIZ;
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var componentsCount = siz.Csiz;
- for (var c = 0; c < componentsCount; c++) {
- var component = tile.components[c];
- var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
- var resolutions = [];
- var subbands = [];
- for (var r = 0; r <= decompositionLevelsCount; r++) {
- var blocksDimensions = getBlocksDimensions(context, component, r);
- var resolution = {};
- var scale = 1 << decompositionLevelsCount - r;
- resolution.trx0 = Math.ceil(component.tcx0 / scale);
- resolution.try0 = Math.ceil(component.tcy0 / scale);
- resolution.trx1 = Math.ceil(component.tcx1 / scale);
- resolution.try1 = Math.ceil(component.tcy1 / scale);
- resolution.resLevel = r;
- buildPrecincts(context, resolution, blocksDimensions);
- resolutions.push(resolution);
- var subband;
- if (r === 0) {
- subband = {};
- subband.type = 'LL';
- subband.tbx0 = Math.ceil(component.tcx0 / scale);
- subband.tby0 = Math.ceil(component.tcy0 / scale);
- subband.tbx1 = Math.ceil(component.tcx1 / scale);
- subband.tby1 = Math.ceil(component.tcy1 / scale);
- subband.resolution = resolution;
- buildCodeblocks(context, subband, blocksDimensions);
- subbands.push(subband);
- resolution.subbands = [subband];
- } else {
- var bscale = 1 << decompositionLevelsCount - r + 1;
- var resolutionSubbands = [];
- subband = {};
- subband.type = 'HL';
- subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
- subband.tby0 = Math.ceil(component.tcy0 / bscale);
- subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
- subband.tby1 = Math.ceil(component.tcy1 / bscale);
- subband.resolution = resolution;
- buildCodeblocks(context, subband, blocksDimensions);
- subbands.push(subband);
- resolutionSubbands.push(subband);
- subband = {};
- subband.type = 'LH';
- subband.tbx0 = Math.ceil(component.tcx0 / bscale);
- subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
- subband.tbx1 = Math.ceil(component.tcx1 / bscale);
- subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
- subband.resolution = resolution;
- buildCodeblocks(context, subband, blocksDimensions);
- subbands.push(subband);
- resolutionSubbands.push(subband);
- subband = {};
- subband.type = 'HH';
- subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
- subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
- subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
- subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
- subband.resolution = resolution;
- buildCodeblocks(context, subband, blocksDimensions);
- subbands.push(subband);
- resolutionSubbands.push(subband);
- resolution.subbands = resolutionSubbands;
- }
- }
- component.resolutions = resolutions;
- component.subbands = subbands;
- }
- var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder;
- switch (progressionOrder) {
- case 0:
- tile.packetsIterator = new LayerResolutionComponentPositionIterator(context);
- break;
- case 1:
- tile.packetsIterator = new ResolutionLayerComponentPositionIterator(context);
- break;
- case 2:
- tile.packetsIterator = new ResolutionPositionComponentLayerIterator(context);
- break;
- case 3:
- tile.packetsIterator = new PositionComponentResolutionLayerIterator(context);
- break;
- case 4:
- tile.packetsIterator = new ComponentPositionResolutionLayerIterator(context);
- break;
- default:
- error('JPX Error: Unsupported progression order ' + progressionOrder);
- }
- }
- function parseTilePackets(context, data, offset, dataLength) {
- var position = 0;
- var buffer,
- bufferSize = 0,
- skipNextBit = false;
- function readBits(count) {
- while (bufferSize < count) {
- var b = data[offset + position];
- position++;
- if (skipNextBit) {
- buffer = buffer << 7 | b;
- bufferSize += 7;
- skipNextBit = false;
- } else {
- buffer = buffer << 8 | b;
- bufferSize += 8;
- }
- if (b === 0xFF) {
- skipNextBit = true;
- }
- }
- bufferSize -= count;
- return buffer >>> bufferSize & (1 << count) - 1;
- }
- function skipMarkerIfEqual(value) {
- if (data[offset + position - 1] === 0xFF && data[offset + position] === value) {
- skipBytes(1);
- return true;
- } else if (data[offset + position] === 0xFF && data[offset + position + 1] === value) {
- skipBytes(2);
- return true;
- }
- return false;
- }
- function skipBytes(count) {
- position += count;
- }
- function alignToByte() {
- bufferSize = 0;
- if (skipNextBit) {
- position++;
- skipNextBit = false;
- }
- }
- function readCodingpasses() {
- if (readBits(1) === 0) {
- return 1;
- }
- if (readBits(1) === 0) {
- return 2;
- }
- var value = readBits(2);
- if (value < 3) {
- return value + 3;
- }
- value = readBits(5);
- if (value < 31) {
- return value + 6;
- }
- value = readBits(7);
- return value + 37;
- }
- var tileIndex = context.currentTile.index;
- var tile = context.tiles[tileIndex];
- var sopMarkerUsed = context.COD.sopMarkerUsed;
- var ephMarkerUsed = context.COD.ephMarkerUsed;
- var packetsIterator = tile.packetsIterator;
- while (position < dataLength) {
- alignToByte();
- if (sopMarkerUsed && skipMarkerIfEqual(0x91)) {
- skipBytes(4);
- }
- var packet = packetsIterator.nextPacket();
- if (!readBits(1)) {
- continue;
- }
- var layerNumber = packet.layerNumber;
- var queue = [],
- codeblock;
- for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
- codeblock = packet.codeblocks[i];
- var precinct = codeblock.precinct;
- var codeblockColumn = codeblock.cbx - precinct.cbxMin;
- var codeblockRow = codeblock.cby - precinct.cbyMin;
- var codeblockIncluded = false;
- var firstTimeInclusion = false;
- var valueReady;
- if (codeblock['included'] !== undefined) {
- codeblockIncluded = !!readBits(1);
- } else {
- precinct = codeblock.precinct;
- var inclusionTree, zeroBitPlanesTree;
- if (precinct['inclusionTree'] !== undefined) {
- inclusionTree = precinct.inclusionTree;
- } else {
- var width = precinct.cbxMax - precinct.cbxMin + 1;
- var height = precinct.cbyMax - precinct.cbyMin + 1;
- inclusionTree = new InclusionTree(width, height, layerNumber);
- zeroBitPlanesTree = new TagTree(width, height);
- precinct.inclusionTree = inclusionTree;
- precinct.zeroBitPlanesTree = zeroBitPlanesTree;
- }
- if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
- while (true) {
- if (readBits(1)) {
- valueReady = !inclusionTree.nextLevel();
- if (valueReady) {
- codeblock.included = true;
- codeblockIncluded = firstTimeInclusion = true;
- break;
- }
- } else {
- inclusionTree.incrementValue(layerNumber);
- break;
- }
- }
- }
- }
- if (!codeblockIncluded) {
- continue;
- }
- if (firstTimeInclusion) {
- zeroBitPlanesTree = precinct.zeroBitPlanesTree;
- zeroBitPlanesTree.reset(codeblockColumn, codeblockRow);
- while (true) {
- if (readBits(1)) {
- valueReady = !zeroBitPlanesTree.nextLevel();
- if (valueReady) {
- break;
- }
- } else {
- zeroBitPlanesTree.incrementValue();
- }
- }
- codeblock.zeroBitPlanes = zeroBitPlanesTree.value;
- }
- var codingpasses = readCodingpasses();
- while (readBits(1)) {
- codeblock.Lblock++;
- }
- var codingpassesLog2 = log2(codingpasses);
- var bits = (codingpasses < 1 << codingpassesLog2 ? codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock;
- var codedDataLength = readBits(bits);
- queue.push({
- codeblock: codeblock,
- codingpasses: codingpasses,
- dataLength: codedDataLength
- });
- }
- alignToByte();
- if (ephMarkerUsed) {
- skipMarkerIfEqual(0x92);
- }
- while (queue.length > 0) {
- var packetItem = queue.shift();
- codeblock = packetItem.codeblock;
- if (codeblock['data'] === undefined) {
- codeblock.data = [];
- }
- codeblock.data.push({
- data: data,
- start: offset + position,
- end: offset + position + packetItem.dataLength,
- codingpasses: packetItem.codingpasses
- });
- position += packetItem.dataLength;
- }
- }
- return position;
- }
- function copyCoefficients(coefficients, levelWidth, levelHeight, subband, delta, mb, reversible, segmentationSymbolUsed) {
- var x0 = subband.tbx0;
- var y0 = subband.tby0;
- var width = subband.tbx1 - subband.tbx0;
- var codeblocks = subband.codeblocks;
- var right = subband.type.charAt(0) === 'H' ? 1 : 0;
- var bottom = subband.type.charAt(1) === 'H' ? levelWidth : 0;
- for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
- var codeblock = codeblocks[i];
- var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
- var blockHeight = codeblock.tby1_ - codeblock.tby0_;
- if (blockWidth === 0 || blockHeight === 0) {
- continue;
- }
- if (codeblock['data'] === undefined) {
- continue;
- }
- var bitModel, currentCodingpassType;
- bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, codeblock.zeroBitPlanes, mb);
- currentCodingpassType = 2;
- var data = codeblock.data,
- totalLength = 0,
- codingpasses = 0;
- var j, jj, dataItem;
- for (j = 0, jj = data.length; j < jj; j++) {
- dataItem = data[j];
- totalLength += dataItem.end - dataItem.start;
- codingpasses += dataItem.codingpasses;
- }
- var encodedData = new Uint8Array(totalLength);
- var position = 0;
- for (j = 0, jj = data.length; j < jj; j++) {
- dataItem = data[j];
- var chunk = dataItem.data.subarray(dataItem.start, dataItem.end);
- encodedData.set(chunk, position);
- position += chunk.length;
- }
- var decoder = new ArithmeticDecoder(encodedData, 0, totalLength);
- bitModel.setDecoder(decoder);
- for (j = 0; j < codingpasses; j++) {
- switch (currentCodingpassType) {
- case 0:
- bitModel.runSignificancePropagationPass();
- break;
- case 1:
- bitModel.runMagnitudeRefinementPass();
- break;
- case 2:
- bitModel.runCleanupPass();
- if (segmentationSymbolUsed) {
- bitModel.checkSegmentationSymbol();
- }
- break;
- }
- currentCodingpassType = (currentCodingpassType + 1) % 3;
- }
- var offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width;
- var sign = bitModel.coefficentsSign;
- var magnitude = bitModel.coefficentsMagnitude;
- var bitsDecoded = bitModel.bitsDecoded;
- var magnitudeCorrection = reversible ? 0 : 0.5;
- var k, n, nb;
- position = 0;
- var interleave = subband.type !== 'LL';
- for (j = 0; j < blockHeight; j++) {
- var row = offset / width | 0;
- var levelOffset = 2 * row * (levelWidth - width) + right + bottom;
- for (k = 0; k < blockWidth; k++) {
- n = magnitude[position];
- if (n !== 0) {
- n = (n + magnitudeCorrection) * delta;
- if (sign[position] !== 0) {
- n = -n;
- }
- nb = bitsDecoded[position];
- var pos = interleave ? levelOffset + (offset << 1) : offset;
- if (reversible && nb >= mb) {
- coefficients[pos] = n;
- } else {
- coefficients[pos] = n * (1 << mb - nb);
- }
- }
- offset++;
- position++;
- }
- offset += width - blockWidth;
- }
- }
- }
- function transformTile(context, tile, c) {
- var component = tile.components[c];
- var codingStyleParameters = component.codingStyleParameters;
- var quantizationParameters = component.quantizationParameters;
- var decompositionLevelsCount = codingStyleParameters.decompositionLevelsCount;
- var spqcds = quantizationParameters.SPqcds;
- var scalarExpounded = quantizationParameters.scalarExpounded;
- var guardBits = quantizationParameters.guardBits;
- var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed;
- var precision = context.components[c].precision;
- var reversible = codingStyleParameters.reversibleTransformation;
- var transform = reversible ? new ReversibleTransform() : new IrreversibleTransform();
- var subbandCoefficients = [];
- var b = 0;
- for (var i = 0; i <= decompositionLevelsCount; i++) {
- var resolution = component.resolutions[i];
- var width = resolution.trx1 - resolution.trx0;
- var height = resolution.try1 - resolution.try0;
- var coefficients = new Float32Array(width * height);
- for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
- var mu, epsilon;
- if (!scalarExpounded) {
- mu = spqcds[0].mu;
- epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
- } else {
- mu = spqcds[b].mu;
- epsilon = spqcds[b].epsilon;
- b++;
- }
- var subband = resolution.subbands[j];
- var gainLog2 = SubbandsGainLog2[subband.type];
- var delta = reversible ? 1 : Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048);
- var mb = guardBits + epsilon - 1;
- copyCoefficients(coefficients, width, height, subband, delta, mb, reversible, segmentationSymbolUsed);
- }
- subbandCoefficients.push({
- width: width,
- height: height,
- items: coefficients
- });
- }
- var result = transform.calculate(subbandCoefficients, component.tcx0, component.tcy0);
- return {
- left: component.tcx0,
- top: component.tcy0,
- width: result.width,
- height: result.height,
- items: result.items
- };
- }
- function transformComponents(context) {
- var siz = context.SIZ;
- var components = context.components;
- var componentsCount = siz.Csiz;
- var resultImages = [];
- for (var i = 0, ii = context.tiles.length; i < ii; i++) {
- var tile = context.tiles[i];
- var transformedTiles = [];
- var c;
- for (c = 0; c < componentsCount; c++) {
- transformedTiles[c] = transformTile(context, tile, c);
- }
- var tile0 = transformedTiles[0];
- var out = new Uint8Array(tile0.items.length * componentsCount);
- var result = {
- left: tile0.left,
- top: tile0.top,
- width: tile0.width,
- height: tile0.height,
- items: out
- };
- var shift, offset, max, min, maxK;
- var pos = 0,
- j,
- jj,
- y0,
- y1,
- y2,
- r,
- g,
- b,
- k,
- val;
- if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
- var fourComponents = componentsCount === 4;
- var y0items = transformedTiles[0].items;
- var y1items = transformedTiles[1].items;
- var y2items = transformedTiles[2].items;
- var y3items = fourComponents ? transformedTiles[3].items : null;
- shift = components[0].precision - 8;
- offset = (128 << shift) + 0.5;
- max = 255 * (1 << shift);
- maxK = max * 0.5;
- min = -maxK;
- var component0 = tile.components[0];
- var alpha01 = componentsCount - 3;
- jj = y0items.length;
- if (!component0.codingStyleParameters.reversibleTransformation) {
- for (j = 0; j < jj; j++, pos += alpha01) {
- y0 = y0items[j] + offset;
- y1 = y1items[j];
- y2 = y2items[j];
- r = y0 + 1.402 * y2;
- g = y0 - 0.34413 * y1 - 0.71414 * y2;
- b = y0 + 1.772 * y1;
- out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
- out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
- out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
- }
- } else {
- for (j = 0; j < jj; j++, pos += alpha01) {
- y0 = y0items[j] + offset;
- y1 = y1items[j];
- y2 = y2items[j];
- g = y0 - (y2 + y1 >> 2);
- r = g + y2;
- b = g + y1;
- out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
- out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
- out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
- }
- }
- if (fourComponents) {
- for (j = 0, pos = 3; j < jj; j++, pos += 4) {
- k = y3items[j];
- out[pos] = k <= min ? 0 : k >= maxK ? 255 : k + offset >> shift;
- }
- }
- } else {
- for (c = 0; c < componentsCount; c++) {
- var items = transformedTiles[c].items;
- shift = components[c].precision - 8;
- offset = (128 << shift) + 0.5;
- max = 127.5 * (1 << shift);
- min = -max;
- for (pos = c, j = 0, jj = items.length; j < jj; j++) {
- val = items[j];
- out[pos] = val <= min ? 0 : val >= max ? 255 : val + offset >> shift;
- pos += componentsCount;
- }
- }
- }
- resultImages.push(result);
- }
- return resultImages;
- }
- function initializeTile(context, tileIndex) {
- var siz = context.SIZ;
- var componentsCount = siz.Csiz;
- var tile = context.tiles[tileIndex];
- for (var c = 0; c < componentsCount; c++) {
- var component = tile.components[c];
- var qcdOrQcc = context.currentTile.QCC[c] !== undefined ? context.currentTile.QCC[c] : context.currentTile.QCD;
- component.quantizationParameters = qcdOrQcc;
- var codOrCoc = context.currentTile.COC[c] !== undefined ? context.currentTile.COC[c] : context.currentTile.COD;
- component.codingStyleParameters = codOrCoc;
- }
- tile.codingStyleDefaultParameters = context.currentTile.COD;
- }
- var TagTree = function TagTreeClosure() {
- function TagTree(width, height) {
- var levelsLength = log2(Math.max(width, height)) + 1;
- this.levels = [];
- for (var i = 0; i < levelsLength; i++) {
- var level = {
- width: width,
- height: height,
- items: []
- };
- this.levels.push(level);
- width = Math.ceil(width / 2);
- height = Math.ceil(height / 2);
- }
- }
- TagTree.prototype = {
- reset: function TagTree_reset(i, j) {
- var currentLevel = 0,
- value = 0,
- level;
- while (currentLevel < this.levels.length) {
- level = this.levels[currentLevel];
- var index = i + j * level.width;
- if (level.items[index] !== undefined) {
- value = level.items[index];
- break;
- }
- level.index = index;
- i >>= 1;
- j >>= 1;
- currentLevel++;
- }
- currentLevel--;
- level = this.levels[currentLevel];
- level.items[level.index] = value;
- this.currentLevel = currentLevel;
- delete this.value;
- },
- incrementValue: function TagTree_incrementValue() {
- var level = this.levels[this.currentLevel];
- level.items[level.index]++;
- },
- nextLevel: function TagTree_nextLevel() {
- var currentLevel = this.currentLevel;
- var level = this.levels[currentLevel];
- var value = level.items[level.index];
- currentLevel--;
- if (currentLevel < 0) {
- this.value = value;
- return false;
- }
- this.currentLevel = currentLevel;
- level = this.levels[currentLevel];
- level.items[level.index] = value;
- return true;
- }
- };
- return TagTree;
- }();
- var InclusionTree = function InclusionTreeClosure() {
- function InclusionTree(width, height, defaultValue) {
- var levelsLength = log2(Math.max(width, height)) + 1;
- this.levels = [];
- for (var i = 0; i < levelsLength; i++) {
- var items = new Uint8Array(width * height);
- for (var j = 0, jj = items.length; j < jj; j++) {
- items[j] = defaultValue;
- }
- var level = {
- width: width,
- height: height,
- items: items
- };
- this.levels.push(level);
- width = Math.ceil(width / 2);
- height = Math.ceil(height / 2);
- }
- }
- InclusionTree.prototype = {
- reset: function InclusionTree_reset(i, j, stopValue) {
- var currentLevel = 0;
- while (currentLevel < this.levels.length) {
- var level = this.levels[currentLevel];
- var index = i + j * level.width;
- level.index = index;
- var value = level.items[index];
- if (value === 0xFF) {
- break;
- }
- if (value > stopValue) {
- this.currentLevel = currentLevel;
- this.propagateValues();
- return false;
- }
- i >>= 1;
- j >>= 1;
- currentLevel++;
- }
- this.currentLevel = currentLevel - 1;
- return true;
- },
- incrementValue: function InclusionTree_incrementValue(stopValue) {
- var level = this.levels[this.currentLevel];
- level.items[level.index] = stopValue + 1;
- this.propagateValues();
- },
- propagateValues: function InclusionTree_propagateValues() {
- var levelIndex = this.currentLevel;
- var level = this.levels[levelIndex];
- var currentValue = level.items[level.index];
- while (--levelIndex >= 0) {
- level = this.levels[levelIndex];
- level.items[level.index] = currentValue;
- }
- },
- nextLevel: function InclusionTree_nextLevel() {
- var currentLevel = this.currentLevel;
- var level = this.levels[currentLevel];
- var value = level.items[level.index];
- level.items[level.index] = 0xFF;
- currentLevel--;
- if (currentLevel < 0) {
- return false;
- }
- this.currentLevel = currentLevel;
- level = this.levels[currentLevel];
- level.items[level.index] = value;
- return true;
- }
- };
- return InclusionTree;
- }();
- var BitModel = function BitModelClosure() {
- var UNIFORM_CONTEXT = 17;
- var RUNLENGTH_CONTEXT = 18;
- var LLAndLHContextsLabel = 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]);
- var HLContextLabel = 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]);
- var HHContextLabel = 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]);
- function BitModel(width, height, subband, zeroBitPlanes, mb) {
- this.width = width;
- this.height = height;
- this.contextLabelTable = subband === 'HH' ? HHContextLabel : subband === 'HL' ? HLContextLabel : LLAndLHContextsLabel;
- var coefficientCount = width * height;
- this.neighborsSignificance = new Uint8Array(coefficientCount);
- this.coefficentsSign = new Uint8Array(coefficientCount);
- this.coefficentsMagnitude = mb > 14 ? new Uint32Array(coefficientCount) : mb > 6 ? new Uint16Array(coefficientCount) : new Uint8Array(coefficientCount);
- this.processingFlags = new Uint8Array(coefficientCount);
- var bitsDecoded = new Uint8Array(coefficientCount);
- if (zeroBitPlanes !== 0) {
- for (var i = 0; i < coefficientCount; i++) {
- bitsDecoded[i] = zeroBitPlanes;
- }
- }
- this.bitsDecoded = bitsDecoded;
- this.reset();
- }
- BitModel.prototype = {
- setDecoder: function BitModel_setDecoder(decoder) {
- this.decoder = decoder;
- },
- reset: function BitModel_reset() {
- this.contexts = new Int8Array(19);
- this.contexts[0] = 4 << 1 | 0;
- this.contexts[UNIFORM_CONTEXT] = 46 << 1 | 0;
- this.contexts[RUNLENGTH_CONTEXT] = 3 << 1 | 0;
- },
- setNeighborsSignificance: function BitModel_setNeighborsSignificance(row, column, index) {
- var neighborsSignificance = this.neighborsSignificance;
- var width = this.width,
- height = this.height;
- var left = column > 0;
- var right = column + 1 < width;
- var i;
- if (row > 0) {
- i = index - width;
- if (left) {
- neighborsSignificance[i - 1] += 0x10;
- }
- if (right) {
- neighborsSignificance[i + 1] += 0x10;
- }
- neighborsSignificance[i] += 0x04;
- }
- if (row + 1 < height) {
- i = index + width;
- if (left) {
- neighborsSignificance[i - 1] += 0x10;
- }
- if (right) {
- neighborsSignificance[i + 1] += 0x10;
- }
- neighborsSignificance[i] += 0x04;
- }
- if (left) {
- neighborsSignificance[index - 1] += 0x01;
- }
- if (right) {
- neighborsSignificance[index + 1] += 0x01;
- }
- neighborsSignificance[index] |= 0x80;
- },
- runSignificancePropagationPass: function BitModel_runSignificancePropagationPass() {
- var decoder = this.decoder;
- var width = this.width,
- height = this.height;
- var coefficentsMagnitude = this.coefficentsMagnitude;
- var coefficentsSign = this.coefficentsSign;
- var neighborsSignificance = this.neighborsSignificance;
- var processingFlags = this.processingFlags;
- var contexts = this.contexts;
- var labels = this.contextLabelTable;
- var bitsDecoded = this.bitsDecoded;
- var processedInverseMask = ~1;
- var processedMask = 1;
- var firstMagnitudeBitMask = 2;
- for (var i0 = 0; i0 < height; i0 += 4) {
- for (var j = 0; j < width; j++) {
- var index = i0 * width + j;
- for (var i1 = 0; i1 < 4; i1++, index += width) {
- var i = i0 + i1;
- if (i >= height) {
- break;
- }
- processingFlags[index] &= processedInverseMask;
- if (coefficentsMagnitude[index] || !neighborsSignificance[index]) {
- continue;
- }
- var contextLabel = labels[neighborsSignificance[index]];
- var decision = decoder.readBit(contexts, contextLabel);
- if (decision) {
- var sign = this.decodeSignBit(i, j, index);
- coefficentsSign[index] = sign;
- coefficentsMagnitude[index] = 1;
- this.setNeighborsSignificance(i, j, index);
- processingFlags[index] |= firstMagnitudeBitMask;
- }
- bitsDecoded[index]++;
- processingFlags[index] |= processedMask;
- }
- }
- }
- },
- decodeSignBit: function BitModel_decodeSignBit(row, column, index) {
- var width = this.width,
- height = this.height;
- var coefficentsMagnitude = this.coefficentsMagnitude;
- var coefficentsSign = this.coefficentsSign;
- var contribution, sign0, sign1, significance1;
- var contextLabel, decoded;
- significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0;
- if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) {
- sign1 = coefficentsSign[index + 1];
- if (significance1) {
- sign0 = coefficentsSign[index - 1];
- contribution = 1 - sign1 - sign0;
- } else {
- contribution = 1 - sign1 - sign1;
- }
- } else if (significance1) {
- sign0 = coefficentsSign[index - 1];
- contribution = 1 - sign0 - sign0;
- } else {
- contribution = 0;
- }
- var horizontalContribution = 3 * contribution;
- significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0;
- if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) {
- sign1 = coefficentsSign[index + width];
- if (significance1) {
- sign0 = coefficentsSign[index - width];
- contribution = 1 - sign1 - sign0 + horizontalContribution;
- } else {
- contribution = 1 - sign1 - sign1 + horizontalContribution;
- }
- } else if (significance1) {
- sign0 = coefficentsSign[index - width];
- contribution = 1 - sign0 - sign0 + horizontalContribution;
- } else {
- contribution = horizontalContribution;
- }
- if (contribution >= 0) {
- contextLabel = 9 + contribution;
- decoded = this.decoder.readBit(this.contexts, contextLabel);
- } else {
- contextLabel = 9 - contribution;
- decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1;
- }
- return decoded;
- },
- runMagnitudeRefinementPass: function BitModel_runMagnitudeRefinementPass() {
- var decoder = this.decoder;
- var width = this.width,
- height = this.height;
- var coefficentsMagnitude = this.coefficentsMagnitude;
- var neighborsSignificance = this.neighborsSignificance;
- var contexts = this.contexts;
- var bitsDecoded = this.bitsDecoded;
- var processingFlags = this.processingFlags;
- var processedMask = 1;
- var firstMagnitudeBitMask = 2;
- var length = width * height;
- var width4 = width * 4;
- for (var index0 = 0, indexNext; index0 < length; index0 = indexNext) {
- indexNext = Math.min(length, index0 + width4);
- for (var j = 0; j < width; j++) {
- for (var index = index0 + j; index < indexNext; index += width) {
- if (!coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) {
- continue;
- }
- var contextLabel = 16;
- if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) {
- processingFlags[index] ^= firstMagnitudeBitMask;
- var significance = neighborsSignificance[index] & 127;
- contextLabel = significance === 0 ? 15 : 14;
- }
- var bit = decoder.readBit(contexts, contextLabel);
- coefficentsMagnitude[index] = coefficentsMagnitude[index] << 1 | bit;
- bitsDecoded[index]++;
- processingFlags[index] |= processedMask;
- }
- }
- }
- },
- runCleanupPass: function BitModel_runCleanupPass() {
- var decoder = this.decoder;
- var width = this.width,
- height = this.height;
- var neighborsSignificance = this.neighborsSignificance;
- var coefficentsMagnitude = this.coefficentsMagnitude;
- var coefficentsSign = this.coefficentsSign;
- var contexts = this.contexts;
- var labels = this.contextLabelTable;
- var bitsDecoded = this.bitsDecoded;
- var processingFlags = this.processingFlags;
- var processedMask = 1;
- var firstMagnitudeBitMask = 2;
- var oneRowDown = width;
- var twoRowsDown = width * 2;
- var threeRowsDown = width * 3;
- var iNext;
- for (var i0 = 0; i0 < height; i0 = iNext) {
- iNext = Math.min(i0 + 4, height);
- var indexBase = i0 * width;
- var checkAllEmpty = i0 + 3 < height;
- for (var j = 0; j < width; j++) {
- var index0 = indexBase + j;
- var allEmpty = checkAllEmpty && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && processingFlags[index0 + twoRowsDown] === 0 && processingFlags[index0 + threeRowsDown] === 0 && neighborsSignificance[index0] === 0 && neighborsSignificance[index0 + oneRowDown] === 0 && neighborsSignificance[index0 + twoRowsDown] === 0 && neighborsSignificance[index0 + threeRowsDown] === 0;
- var i1 = 0,
- index = index0;
- var i = i0,
- sign;
- if (allEmpty) {
- var hasSignificantCoefficent = decoder.readBit(contexts, RUNLENGTH_CONTEXT);
- if (!hasSignificantCoefficent) {
- bitsDecoded[index0]++;
- bitsDecoded[index0 + oneRowDown]++;
- bitsDecoded[index0 + twoRowsDown]++;
- bitsDecoded[index0 + threeRowsDown]++;
- continue;
- }
- i1 = decoder.readBit(contexts, UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, UNIFORM_CONTEXT);
- if (i1 !== 0) {
- i = i0 + i1;
- index += i1 * width;
- }
- sign = this.decodeSignBit(i, j, index);
- coefficentsSign[index] = sign;
- coefficentsMagnitude[index] = 1;
- this.setNeighborsSignificance(i, j, index);
- processingFlags[index] |= firstMagnitudeBitMask;
- index = index0;
- for (var i2 = i0; i2 <= i; i2++, index += width) {
- bitsDecoded[index]++;
- }
- i1++;
- }
- for (i = i0 + i1; i < iNext; i++, index += width) {
- if (coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) {
- continue;
- }
- var contextLabel = labels[neighborsSignificance[index]];
- var decision = decoder.readBit(contexts, contextLabel);
- if (decision === 1) {
- sign = this.decodeSignBit(i, j, index);
- coefficentsSign[index] = sign;
- coefficentsMagnitude[index] = 1;
- this.setNeighborsSignificance(i, j, index);
- processingFlags[index] |= firstMagnitudeBitMask;
- }
- bitsDecoded[index]++;
- }
- }
- }
- },
- checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() {
- var decoder = this.decoder;
- var contexts = this.contexts;
- var symbol = decoder.readBit(contexts, UNIFORM_CONTEXT) << 3 | decoder.readBit(contexts, UNIFORM_CONTEXT) << 2 | decoder.readBit(contexts, UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, UNIFORM_CONTEXT);
- if (symbol !== 0xA) {
- error('JPX Error: Invalid segmentation symbol');
- }
- }
- };
- return BitModel;
- }();
- var Transform = function TransformClosure() {
- function Transform() {}
- Transform.prototype.calculate = function transformCalculate(subbands, u0, v0) {
- var ll = subbands[0];
- for (var i = 1, ii = subbands.length; i < ii; i++) {
- ll = this.iterate(ll, subbands[i], u0, v0);
- }
- return ll;
- };
- Transform.prototype.extend = function extend(buffer, offset, size) {
- var i1 = offset - 1,
- j1 = offset + 1;
- var i2 = offset + size - 2,
- j2 = offset + size;
- buffer[i1--] = buffer[j1++];
- buffer[j2++] = buffer[i2--];
- buffer[i1--] = buffer[j1++];
- buffer[j2++] = buffer[i2--];
- buffer[i1--] = buffer[j1++];
- buffer[j2++] = buffer[i2--];
- buffer[i1] = buffer[j1];
- buffer[j2] = buffer[i2];
- };
- Transform.prototype.iterate = function Transform_iterate(ll, hl_lh_hh, u0, v0) {
- var llWidth = ll.width,
- llHeight = ll.height,
- llItems = ll.items;
- var width = hl_lh_hh.width;
- var height = hl_lh_hh.height;
- var items = hl_lh_hh.items;
- var i, j, k, l, u, v;
- for (k = 0, i = 0; i < llHeight; i++) {
- l = i * 2 * width;
- for (j = 0; j < llWidth; j++, k++, l += 2) {
- items[l] = llItems[k];
- }
- }
- llItems = ll.items = null;
- var bufferPadding = 4;
- var rowBuffer = new Float32Array(width + 2 * bufferPadding);
- if (width === 1) {
- if ((u0 & 1) !== 0) {
- for (v = 0, k = 0; v < height; v++, k += width) {
- items[k] *= 0.5;
- }
- }
- } else {
- for (v = 0, k = 0; v < height; v++, k += width) {
- rowBuffer.set(items.subarray(k, k + width), bufferPadding);
- this.extend(rowBuffer, bufferPadding, width);
- this.filter(rowBuffer, bufferPadding, width);
- items.set(rowBuffer.subarray(bufferPadding, bufferPadding + width), k);
- }
- }
- var numBuffers = 16;
- var colBuffers = [];
- for (i = 0; i < numBuffers; i++) {
- colBuffers.push(new Float32Array(height + 2 * bufferPadding));
- }
- var b,
- currentBuffer = 0;
- ll = bufferPadding + height;
- if (height === 1) {
- if ((v0 & 1) !== 0) {
- for (u = 0; u < width; u++) {
- items[u] *= 0.5;
- }
- }
- } else {
- for (u = 0; u < width; u++) {
- if (currentBuffer === 0) {
- numBuffers = Math.min(width - u, numBuffers);
- for (k = u, l = bufferPadding; l < ll; k += width, l++) {
- for (b = 0; b < numBuffers; b++) {
- colBuffers[b][l] = items[k + b];
- }
- }
- currentBuffer = numBuffers;
- }
- currentBuffer--;
- var buffer = colBuffers[currentBuffer];
- this.extend(buffer, bufferPadding, height);
- this.filter(buffer, bufferPadding, height);
- if (currentBuffer === 0) {
- k = u - numBuffers + 1;
- for (l = bufferPadding; l < ll; k += width, l++) {
- for (b = 0; b < numBuffers; b++) {
- items[k + b] = colBuffers[b][l];
- }
- }
- }
- }
- }
- return {
- width: width,
- height: height,
- items: items
- };
- };
- return Transform;
- }();
- var IrreversibleTransform = function IrreversibleTransformClosure() {
- function IrreversibleTransform() {
- Transform.call(this);
- }
- IrreversibleTransform.prototype = Object.create(Transform.prototype);
- IrreversibleTransform.prototype.filter = function irreversibleTransformFilter(x, offset, length) {
- var len = length >> 1;
- offset = offset | 0;
- var j, n, current, next;
- var alpha = -1.586134342059924;
- var beta = -0.052980118572961;
- var gamma = 0.882911075530934;
- var delta = 0.443506852043971;
- var K = 1.230174104914001;
- var K_ = 1 / K;
- j = offset - 3;
- for (n = len + 4; n--; j += 2) {
- x[j] *= K_;
- }
- j = offset - 2;
- current = delta * x[j - 1];
- for (n = len + 3; n--; j += 2) {
- next = delta * x[j + 1];
- x[j] = K * x[j] - current - next;
- if (n--) {
- j += 2;
- current = delta * x[j + 1];
- x[j] = K * x[j] - current - next;
- } else {
- break;
- }
- }
- j = offset - 1;
- current = gamma * x[j - 1];
- for (n = len + 2; n--; j += 2) {
- next = gamma * x[j + 1];
- x[j] -= current + next;
- if (n--) {
- j += 2;
- current = gamma * x[j + 1];
- x[j] -= current + next;
- } else {
- break;
- }
- }
- j = offset;
- current = beta * x[j - 1];
- for (n = len + 1; n--; j += 2) {
- next = beta * x[j + 1];
- x[j] -= current + next;
- if (n--) {
- j += 2;
- current = beta * x[j + 1];
- x[j] -= current + next;
- } else {
- break;
- }
- }
- if (len !== 0) {
- j = offset + 1;
- current = alpha * x[j - 1];
- for (n = len; n--; j += 2) {
- next = alpha * x[j + 1];
- x[j] -= current + next;
- if (n--) {
- j += 2;
- current = alpha * x[j + 1];
- x[j] -= current + next;
- } else {
- break;
- }
- }
- }
- };
- return IrreversibleTransform;
- }();
- var ReversibleTransform = function ReversibleTransformClosure() {
- function ReversibleTransform() {
- Transform.call(this);
- }
- ReversibleTransform.prototype = Object.create(Transform.prototype);
- ReversibleTransform.prototype.filter = function reversibleTransformFilter(x, offset, length) {
- var len = length >> 1;
- offset = offset | 0;
- var j, n;
- for (j = offset, n = len + 1; n--; j += 2) {
- x[j] -= x[j - 1] + x[j + 1] + 2 >> 2;
- }
- for (j = offset + 1, n = len; n--; j += 2) {
- x[j] += x[j - 1] + x[j + 1] >> 1;
- }
- };
- return ReversibleTransform;
- }();
- return JpxImage;
-}();
-exports.JpxImage = JpxImage;
-
-/***/ }),
-/* 16 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreCrypto = __w_pdfjs_require__(13);
-var coreParser = __w_pdfjs_require__(5);
-var coreChunkedStream = __w_pdfjs_require__(12);
-var coreColorSpace = __w_pdfjs_require__(3);
-var InvalidPDFException = sharedUtil.InvalidPDFException;
-var MissingDataException = sharedUtil.MissingDataException;
-var XRefParseException = sharedUtil.XRefParseException;
-var assert = sharedUtil.assert;
-var bytesToString = sharedUtil.bytesToString;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isBool = sharedUtil.isBool;
-var isInt = sharedUtil.isInt;
-var isString = sharedUtil.isString;
-var shadow = sharedUtil.shadow;
-var stringToPDFString = sharedUtil.stringToPDFString;
-var stringToUTF8String = sharedUtil.stringToUTF8String;
-var warn = sharedUtil.warn;
-var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
-var Util = sharedUtil.Util;
-var Dict = corePrimitives.Dict;
-var Ref = corePrimitives.Ref;
-var RefSet = corePrimitives.RefSet;
-var RefSetCache = corePrimitives.RefSetCache;
-var isName = corePrimitives.isName;
-var isCmd = corePrimitives.isCmd;
-var isDict = corePrimitives.isDict;
-var isRef = corePrimitives.isRef;
-var isRefsEqual = corePrimitives.isRefsEqual;
-var isStream = corePrimitives.isStream;
-var CipherTransformFactory = coreCrypto.CipherTransformFactory;
-var Lexer = coreParser.Lexer;
-var Parser = coreParser.Parser;
-var ChunkedStream = coreChunkedStream.ChunkedStream;
-var ColorSpace = coreColorSpace.ColorSpace;
-var Catalog = function CatalogClosure() {
- function Catalog(pdfManager, xref, pageFactory) {
- this.pdfManager = pdfManager;
- this.xref = xref;
- this.catDict = xref.getCatalogObj();
- assert(isDict(this.catDict), 'catalog object is not a dictionary');
- this.fontCache = new RefSetCache();
- this.builtInCMapCache = Object.create(null);
- this.pageKidsCountCache = new RefSetCache();
- this.pageFactory = pageFactory;
- this.pagePromises = [];
- }
- Catalog.prototype = {
- get metadata() {
- var streamRef = this.catDict.getRaw('Metadata');
- if (!isRef(streamRef)) {
- return shadow(this, 'metadata', null);
- }
- var encryptMetadata = !this.xref.encrypt ? false : this.xref.encrypt.encryptMetadata;
- var stream = this.xref.fetch(streamRef, !encryptMetadata);
- var metadata;
- if (stream && isDict(stream.dict)) {
- var type = stream.dict.get('Type');
- var subtype = stream.dict.get('Subtype');
- if (isName(type, 'Metadata') && isName(subtype, 'XML')) {
- try {
- metadata = stringToUTF8String(bytesToString(stream.getBytes()));
- } catch (e) {
- if (e instanceof MissingDataException) {
- throw e;
- }
- info('Skipping invalid metadata.');
- }
- }
- }
- return shadow(this, 'metadata', metadata);
- },
- get toplevelPagesDict() {
- var pagesObj = this.catDict.get('Pages');
- assert(isDict(pagesObj), 'invalid top-level pages dictionary');
- return shadow(this, 'toplevelPagesDict', pagesObj);
- },
- get documentOutline() {
- var obj = null;
- try {
- obj = this.readDocumentOutline();
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- warn('Unable to read document outline');
- }
- return shadow(this, 'documentOutline', obj);
- },
- readDocumentOutline: function Catalog_readDocumentOutline() {
- var obj = this.catDict.get('Outlines');
- if (!isDict(obj)) {
- return null;
- }
- obj = obj.getRaw('First');
- if (!isRef(obj)) {
- return null;
- }
- var root = { items: [] };
- var queue = [{
- obj: obj,
- parent: root
- }];
- var processed = new RefSet();
- processed.put(obj);
- var xref = this.xref,
- blackColor = new Uint8Array(3);
- while (queue.length > 0) {
- var i = queue.shift();
- var outlineDict = xref.fetchIfRef(i.obj);
- if (outlineDict === null) {
- continue;
- }
- assert(outlineDict.has('Title'), 'Invalid outline item');
- var data = {
- url: null,
- dest: null
- };
- Catalog.parseDestDictionary({
- destDict: outlineDict,
- resultObj: data,
- docBaseUrl: this.pdfManager.docBaseUrl
- });
- var title = outlineDict.get('Title');
- var flags = outlineDict.get('F') || 0;
- var color = outlineDict.getArray('C'),
- rgbColor = blackColor;
- if (isArray(color) && color.length === 3 && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
- rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
- }
- var outlineItem = {
- dest: data.dest,
- url: data.url,
- unsafeUrl: data.unsafeUrl,
- newWindow: data.newWindow,
- title: stringToPDFString(title),
- color: rgbColor,
- count: outlineDict.get('Count'),
- bold: !!(flags & 2),
- italic: !!(flags & 1),
- items: []
- };
- i.parent.items.push(outlineItem);
- obj = outlineDict.getRaw('First');
- if (isRef(obj) && !processed.has(obj)) {
- queue.push({
- obj: obj,
- parent: outlineItem
- });
- processed.put(obj);
- }
- obj = outlineDict.getRaw('Next');
- if (isRef(obj) && !processed.has(obj)) {
- queue.push({
- obj: obj,
- parent: i.parent
- });
- processed.put(obj);
- }
- }
- return root.items.length > 0 ? root.items : null;
- },
- get numPages() {
- var obj = this.toplevelPagesDict.get('Count');
- assert(isInt(obj), 'page count in top level pages object is not an integer');
- return shadow(this, 'num', obj);
- },
- get destinations() {
- function fetchDestination(dest) {
- return isDict(dest) ? dest.get('D') : dest;
- }
- var xref = this.xref;
- var dests = {},
- nameTreeRef,
- nameDictionaryRef;
- var obj = this.catDict.get('Names');
- if (obj && obj.has('Dests')) {
- nameTreeRef = obj.getRaw('Dests');
- } else if (this.catDict.has('Dests')) {
- nameDictionaryRef = this.catDict.get('Dests');
- }
- if (nameDictionaryRef) {
- obj = nameDictionaryRef;
- obj.forEach(function catalogForEach(key, value) {
- if (!value) {
- return;
- }
- dests[key] = fetchDestination(value);
- });
- }
- if (nameTreeRef) {
- var nameTree = new NameTree(nameTreeRef, xref);
- var names = nameTree.getAll();
- for (var name in names) {
- dests[name] = fetchDestination(names[name]);
- }
- }
- return shadow(this, 'destinations', dests);
- },
- getDestination: function Catalog_getDestination(destinationId) {
- function fetchDestination(dest) {
- return isDict(dest) ? dest.get('D') : dest;
- }
- var xref = this.xref;
- var dest = null,
- nameTreeRef,
- nameDictionaryRef;
- var obj = this.catDict.get('Names');
- if (obj && obj.has('Dests')) {
- nameTreeRef = obj.getRaw('Dests');
- } else if (this.catDict.has('Dests')) {
- nameDictionaryRef = this.catDict.get('Dests');
- }
- if (nameDictionaryRef) {
- var value = nameDictionaryRef.get(destinationId);
- if (value) {
- dest = fetchDestination(value);
- }
- }
- if (nameTreeRef) {
- var nameTree = new NameTree(nameTreeRef, xref);
- dest = fetchDestination(nameTree.get(destinationId));
- }
- return dest;
- },
- get pageLabels() {
- var obj = null;
- try {
- obj = this.readPageLabels();
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- warn('Unable to read page labels.');
- }
- return shadow(this, 'pageLabels', obj);
- },
- readPageLabels: function Catalog_readPageLabels() {
- var obj = this.catDict.getRaw('PageLabels');
- if (!obj) {
- return null;
- }
- var pageLabels = new Array(this.numPages);
- var style = null;
- var prefix = '';
- var numberTree = new NumberTree(obj, this.xref);
- var nums = numberTree.getAll();
- var currentLabel = '',
- currentIndex = 1;
- for (var i = 0, ii = this.numPages; i < ii; i++) {
- if (i in nums) {
- var labelDict = nums[i];
- assert(isDict(labelDict), 'The PageLabel is not a dictionary.');
- var type = labelDict.get('Type');
- assert(!type || isName(type, 'PageLabel'), 'Invalid type in PageLabel dictionary.');
- var s = labelDict.get('S');
- assert(!s || isName(s), 'Invalid style in PageLabel dictionary.');
- style = s ? s.name : null;
- var p = labelDict.get('P');
- assert(!p || isString(p), 'Invalid prefix in PageLabel dictionary.');
- prefix = p ? stringToPDFString(p) : '';
- var st = labelDict.get('St');
- assert(!st || isInt(st) && st >= 1, 'Invalid start in PageLabel dictionary.');
- currentIndex = st || 1;
- }
- switch (style) {
- case 'D':
- currentLabel = currentIndex;
- break;
- case 'R':
- case 'r':
- currentLabel = Util.toRoman(currentIndex, style === 'r');
- break;
- case 'A':
- case 'a':
- var LIMIT = 26;
- var A_UPPER_CASE = 0x41,
- A_LOWER_CASE = 0x61;
- var baseCharCode = style === 'a' ? A_LOWER_CASE : A_UPPER_CASE;
- var letterIndex = currentIndex - 1;
- var character = String.fromCharCode(baseCharCode + letterIndex % LIMIT);
- var charBuf = [];
- for (var j = 0, jj = letterIndex / LIMIT | 0; j <= jj; j++) {
- charBuf.push(character);
- }
- currentLabel = charBuf.join('');
- break;
- default:
- assert(!style, 'Invalid style "' + style + '" in PageLabel dictionary.');
- }
- pageLabels[i] = prefix + currentLabel;
- currentLabel = '';
- currentIndex++;
- }
- return pageLabels;
- },
- get attachments() {
- var xref = this.xref;
- var attachments = null,
- nameTreeRef;
- var obj = this.catDict.get('Names');
- if (obj) {
- nameTreeRef = obj.getRaw('EmbeddedFiles');
- }
- if (nameTreeRef) {
- var nameTree = new NameTree(nameTreeRef, xref);
- var names = nameTree.getAll();
- for (var name in names) {
- var fs = new FileSpec(names[name], xref);
- if (!attachments) {
- attachments = Object.create(null);
- }
- attachments[stringToPDFString(name)] = fs.serializable;
- }
- }
- return shadow(this, 'attachments', attachments);
- },
- get javaScript() {
- var xref = this.xref;
- var obj = this.catDict.get('Names');
- var javaScript = [];
- function appendIfJavaScriptDict(jsDict) {
- var type = jsDict.get('S');
- if (!isName(type, 'JavaScript')) {
- return;
- }
- var js = jsDict.get('JS');
- if (isStream(js)) {
- js = bytesToString(js.getBytes());
- } else if (!isString(js)) {
- return;
- }
- javaScript.push(stringToPDFString(js));
- }
- if (obj && obj.has('JavaScript')) {
- var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
- var names = nameTree.getAll();
- for (var name in names) {
- var jsDict = names[name];
- if (isDict(jsDict)) {
- appendIfJavaScriptDict(jsDict);
- }
- }
- }
- var openactionDict = this.catDict.get('OpenAction');
- if (isDict(openactionDict, 'Action')) {
- var actionType = openactionDict.get('S');
- if (isName(actionType, 'Named')) {
- var action = openactionDict.get('N');
- if (isName(action, 'Print')) {
- javaScript.push('print({});');
- }
- } else {
- appendIfJavaScriptDict(openactionDict);
- }
- }
- return shadow(this, 'javaScript', javaScript);
- },
- cleanup: function Catalog_cleanup() {
- this.pageKidsCountCache.clear();
- var promises = [];
- this.fontCache.forEach(function (promise) {
- promises.push(promise);
- });
- return Promise.all(promises).then(function (translatedFonts) {
- for (var i = 0, ii = translatedFonts.length; i < ii; i++) {
- var font = translatedFonts[i].dict;
- delete font.translated;
- }
- this.fontCache.clear();
- this.builtInCMapCache = Object.create(null);
- }.bind(this));
- },
- getPage: function Catalog_getPage(pageIndex) {
- if (!(pageIndex in this.pagePromises)) {
- this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(function (a) {
- var dict = a[0];
- var ref = a[1];
- return this.pageFactory.createPage(pageIndex, dict, ref, this.fontCache, this.builtInCMapCache);
- }.bind(this));
- }
- return this.pagePromises[pageIndex];
- },
- getPageDict: function Catalog_getPageDict(pageIndex) {
- var capability = createPromiseCapability();
- var nodesToVisit = [this.catDict.getRaw('Pages')];
- var count,
- currentPageIndex = 0;
- var xref = this.xref,
- pageKidsCountCache = this.pageKidsCountCache;
- function next() {
- while (nodesToVisit.length) {
- var currentNode = nodesToVisit.pop();
- if (isRef(currentNode)) {
- count = pageKidsCountCache.get(currentNode);
- if (count > 0 && currentPageIndex + count < pageIndex) {
- currentPageIndex += count;
- continue;
- }
- xref.fetchAsync(currentNode).then(function (obj) {
- if (isDict(obj, 'Page') || isDict(obj) && !obj.has('Kids')) {
- if (pageIndex === currentPageIndex) {
- if (currentNode && !pageKidsCountCache.has(currentNode)) {
- pageKidsCountCache.put(currentNode, 1);
- }
- capability.resolve([obj, currentNode]);
- } else {
- currentPageIndex++;
- next();
- }
- return;
- }
- nodesToVisit.push(obj);
- next();
- }, capability.reject);
- return;
- }
- assert(isDict(currentNode), 'page dictionary kid reference points to wrong type of object');
- count = currentNode.get('Count');
- var objId = currentNode.objId;
- if (objId && !pageKidsCountCache.has(objId)) {
- pageKidsCountCache.put(objId, count);
- }
- if (currentPageIndex + count <= pageIndex) {
- currentPageIndex += count;
- continue;
- }
- var kids = currentNode.get('Kids');
- assert(isArray(kids), 'page dictionary kids object is not an array');
- for (var last = kids.length - 1; last >= 0; last--) {
- nodesToVisit.push(kids[last]);
- }
- }
- capability.reject('Page index ' + pageIndex + ' not found.');
- }
- next();
- return capability.promise;
- },
- getPageIndex: function Catalog_getPageIndex(pageRef) {
- var xref = this.xref;
- function pagesBeforeRef(kidRef) {
- var total = 0;
- var parentRef;
- return xref.fetchAsync(kidRef).then(function (node) {
- if (isRefsEqual(kidRef, pageRef) && !isDict(node, 'Page') && !(isDict(node) && !node.has('Type') && node.has('Contents'))) {
- throw new Error('The reference does not point to a /Page Dict.');
- }
- if (!node) {
- return null;
- }
- assert(isDict(node), 'node must be a Dict.');
- parentRef = node.getRaw('Parent');
- return node.getAsync('Parent');
- }).then(function (parent) {
- if (!parent) {
- return null;
- }
- assert(isDict(parent), 'parent must be a Dict.');
- return parent.getAsync('Kids');
- }).then(function (kids) {
- if (!kids) {
- return null;
- }
- var kidPromises = [];
- var found = false;
- for (var i = 0; i < kids.length; i++) {
- var kid = kids[i];
- assert(isRef(kid), 'kid must be a Ref.');
- if (kid.num === kidRef.num) {
- found = true;
- break;
- }
- kidPromises.push(xref.fetchAsync(kid).then(function (kid) {
- if (kid.has('Count')) {
- var count = kid.get('Count');
- total += count;
- } else {
- total++;
- }
- }));
- }
- if (!found) {
- error('kid ref not found in parents kids');
- }
- return Promise.all(kidPromises).then(function () {
- return [total, parentRef];
- });
- });
- }
- var total = 0;
- function next(ref) {
- return pagesBeforeRef(ref).then(function (args) {
- if (!args) {
- return total;
- }
- var count = args[0];
- var parentRef = args[1];
- total += count;
- return next(parentRef);
- });
- }
- return next(pageRef);
- }
- };
- Catalog.parseDestDictionary = function Catalog_parseDestDictionary(params) {
- function addDefaultProtocolToUrl(url) {
- if (url.indexOf('www.') === 0) {
- return 'http://' + url;
- }
- return url;
- }
- function tryConvertUrlEncoding(url) {
- try {
- return stringToUTF8String(url);
- } catch (e) {
- return url;
- }
- }
- var destDict = params.destDict;
- if (!isDict(destDict)) {
- warn('Catalog_parseDestDictionary: "destDict" must be a dictionary.');
- return;
- }
- var resultObj = params.resultObj;
- if (typeof resultObj !== 'object') {
- warn('Catalog_parseDestDictionary: "resultObj" must be an object.');
- return;
- }
- var docBaseUrl = params.docBaseUrl || null;
- var action = destDict.get('A'),
- url,
- dest;
- if (isDict(action)) {
- var linkType = action.get('S').name;
- switch (linkType) {
- case 'URI':
- url = action.get('URI');
- if (isName(url)) {
- url = '/' + url.name;
- } else if (isString(url)) {
- url = addDefaultProtocolToUrl(url);
- }
- break;
- case 'GoTo':
- dest = action.get('D');
- break;
- case 'Launch':
- case 'GoToR':
- var urlDict = action.get('F');
- if (isDict(urlDict)) {
- url = urlDict.get('F') || null;
- } else if (isString(urlDict)) {
- url = urlDict;
- }
- var remoteDest = action.get('D');
- if (remoteDest) {
- if (isName(remoteDest)) {
- remoteDest = remoteDest.name;
- }
- if (isString(url)) {
- var baseUrl = url.split('#')[0];
- if (isString(remoteDest)) {
- url = baseUrl + '#' + (/^\d+$/.test(remoteDest) ? 'nameddest=' : '') + remoteDest;
- } else if (isArray(remoteDest)) {
- url = baseUrl + '#' + JSON.stringify(remoteDest);
- }
- }
- }
- var newWindow = action.get('NewWindow');
- if (isBool(newWindow)) {
- resultObj.newWindow = newWindow;
- }
- break;
- case 'Named':
- var namedAction = action.get('N');
- if (isName(namedAction)) {
- resultObj.action = namedAction.name;
- }
- break;
- case 'JavaScript':
- var jsAction = action.get('JS'),
- js;
- if (isStream(jsAction)) {
- js = bytesToString(jsAction.getBytes());
- } else if (isString(jsAction)) {
- js = jsAction;
- }
- if (js) {
- var URL_OPEN_METHODS = ['app.launchURL', 'window.open'];
- var regex = new RegExp('^\\s*(' + URL_OPEN_METHODS.join('|').split('.').join('\\.') + ')\\((?:\'|\")([^\'\"]*)(?:\'|\")(?:,\\s*(\\w+)\\)|\\))', 'i');
- var jsUrl = regex.exec(stringToPDFString(js));
- if (jsUrl && jsUrl[2]) {
- url = jsUrl[2];
- if (jsUrl[3] === 'true' && jsUrl[1] === 'app.launchURL') {
- resultObj.newWindow = true;
- }
- break;
- }
- }
- default:
- warn('Catalog_parseDestDictionary: Unrecognized link type "' + linkType + '".');
- break;
- }
- } else if (destDict.has('Dest')) {
- dest = destDict.get('Dest');
- }
- if (isString(url)) {
- url = tryConvertUrlEncoding(url);
- var absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl);
- if (absoluteUrl) {
- resultObj.url = absoluteUrl.href;
- }
- resultObj.unsafeUrl = url;
- }
- if (dest) {
- if (isName(dest)) {
- dest = dest.name;
- }
- if (isString(dest) || isArray(dest)) {
- resultObj.dest = dest;
- }
- }
- };
- return Catalog;
-}();
-var XRef = function XRefClosure() {
- function XRef(stream, pdfManager) {
- this.stream = stream;
- this.pdfManager = pdfManager;
- this.entries = [];
- this.xrefstms = Object.create(null);
- this.cache = [];
- this.stats = {
- streamTypes: [],
- fontTypes: []
- };
- }
- XRef.prototype = {
- setStartXRef: function XRef_setStartXRef(startXRef) {
- this.startXRefQueue = [startXRef];
- },
- parse: function XRef_parse(recoveryMode) {
- var trailerDict;
- if (!recoveryMode) {
- trailerDict = this.readXRef();
- } else {
- warn('Indexing all PDF objects');
- trailerDict = this.indexObjects();
- }
- trailerDict.assignXref(this);
- this.trailer = trailerDict;
- var encrypt = trailerDict.get('Encrypt');
- if (isDict(encrypt)) {
- var ids = trailerDict.get('ID');
- var fileId = ids && ids.length ? ids[0] : '';
- encrypt.suppressEncryption = true;
- this.encrypt = new CipherTransformFactory(encrypt, fileId, this.pdfManager.password);
- }
- if (!(this.root = trailerDict.get('Root'))) {
- error('Invalid root reference');
- }
- },
- processXRefTable: function XRef_processXRefTable(parser) {
- if (!('tableState' in this)) {
- this.tableState = {
- entryNum: 0,
- streamPos: parser.lexer.stream.pos,
- parserBuf1: parser.buf1,
- parserBuf2: parser.buf2
- };
- }
- var obj = this.readXRefTable(parser);
- if (!isCmd(obj, 'trailer')) {
- error('Invalid XRef table: could not find trailer dictionary');
- }
- var dict = parser.getObj();
- if (!isDict(dict) && dict.dict) {
- dict = dict.dict;
- }
- if (!isDict(dict)) {
- error('Invalid XRef table: could not parse trailer dictionary');
- }
- delete this.tableState;
- return dict;
- },
- readXRefTable: function XRef_readXRefTable(parser) {
- var stream = parser.lexer.stream;
- var tableState = this.tableState;
- stream.pos = tableState.streamPos;
- parser.buf1 = tableState.parserBuf1;
- parser.buf2 = tableState.parserBuf2;
- var obj;
- while (true) {
- if (!('firstEntryNum' in tableState) || !('entryCount' in tableState)) {
- if (isCmd(obj = parser.getObj(), 'trailer')) {
- break;
- }
- tableState.firstEntryNum = obj;
- tableState.entryCount = parser.getObj();
- }
- var first = tableState.firstEntryNum;
- var count = tableState.entryCount;
- if (!isInt(first) || !isInt(count)) {
- error('Invalid XRef table: wrong types in subsection header');
- }
- for (var i = tableState.entryNum; i < count; i++) {
- tableState.streamPos = stream.pos;
- tableState.entryNum = i;
- tableState.parserBuf1 = parser.buf1;
- tableState.parserBuf2 = parser.buf2;
- var entry = {};
- entry.offset = parser.getObj();
- entry.gen = parser.getObj();
- var type = parser.getObj();
- if (isCmd(type, 'f')) {
- entry.free = true;
- } else if (isCmd(type, 'n')) {
- entry.uncompressed = true;
- }
- if (!isInt(entry.offset) || !isInt(entry.gen) || !(entry.free || entry.uncompressed)) {
- error('Invalid entry in XRef subsection: ' + first + ', ' + count);
- }
- if (i === 0 && entry.free && first === 1) {
- first = 0;
- }
- if (!this.entries[i + first]) {
- this.entries[i + first] = entry;
- }
- }
- tableState.entryNum = 0;
- tableState.streamPos = stream.pos;
- tableState.parserBuf1 = parser.buf1;
- tableState.parserBuf2 = parser.buf2;
- delete tableState.firstEntryNum;
- delete tableState.entryCount;
- }
- if (this.entries[0] && !this.entries[0].free) {
- error('Invalid XRef table: unexpected first object');
- }
- return obj;
- },
- processXRefStream: function XRef_processXRefStream(stream) {
- if (!('streamState' in this)) {
- var streamParameters = stream.dict;
- var byteWidths = streamParameters.get('W');
- var range = streamParameters.get('Index');
- if (!range) {
- range = [0, streamParameters.get('Size')];
- }
- this.streamState = {
- entryRanges: range,
- byteWidths: byteWidths,
- entryNum: 0,
- streamPos: stream.pos
- };
- }
- this.readXRefStream(stream);
- delete this.streamState;
- return stream.dict;
- },
- readXRefStream: function XRef_readXRefStream(stream) {
- var i, j;
- var streamState = this.streamState;
- stream.pos = streamState.streamPos;
- var byteWidths = streamState.byteWidths;
- var typeFieldWidth = byteWidths[0];
- var offsetFieldWidth = byteWidths[1];
- var generationFieldWidth = byteWidths[2];
- var entryRanges = streamState.entryRanges;
- while (entryRanges.length > 0) {
- var first = entryRanges[0];
- var n = entryRanges[1];
- if (!isInt(first) || !isInt(n)) {
- error('Invalid XRef range fields: ' + first + ', ' + n);
- }
- if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) || !isInt(generationFieldWidth)) {
- error('Invalid XRef entry fields length: ' + first + ', ' + n);
- }
- for (i = streamState.entryNum; i < n; ++i) {
- streamState.entryNum = i;
- streamState.streamPos = stream.pos;
- var type = 0,
- offset = 0,
- generation = 0;
- for (j = 0; j < typeFieldWidth; ++j) {
- type = type << 8 | stream.getByte();
- }
- if (typeFieldWidth === 0) {
- type = 1;
- }
- for (j = 0; j < offsetFieldWidth; ++j) {
- offset = offset << 8 | stream.getByte();
- }
- for (j = 0; j < generationFieldWidth; ++j) {
- generation = generation << 8 | stream.getByte();
- }
- var entry = {};
- entry.offset = offset;
- entry.gen = generation;
- switch (type) {
- case 0:
- entry.free = true;
- break;
- case 1:
- entry.uncompressed = true;
- break;
- case 2:
- break;
- default:
- error('Invalid XRef entry type: ' + type);
- }
- if (!this.entries[first + i]) {
- this.entries[first + i] = entry;
- }
- }
- streamState.entryNum = 0;
- streamState.streamPos = stream.pos;
- entryRanges.splice(0, 2);
- }
- },
- indexObjects: function XRef_indexObjects() {
- var TAB = 0x9,
- LF = 0xA,
- CR = 0xD,
- SPACE = 0x20;
- var PERCENT = 0x25,
- LT = 0x3C;
- function readToken(data, offset) {
- var token = '',
- ch = data[offset];
- while (ch !== LF && ch !== CR && ch !== LT) {
- if (++offset >= data.length) {
- break;
- }
- token += String.fromCharCode(ch);
- ch = data[offset];
- }
- return token;
- }
- function skipUntil(data, offset, what) {
- var length = what.length,
- dataLength = data.length;
- var skipped = 0;
- while (offset < dataLength) {
- var i = 0;
- while (i < length && data[offset + i] === what[i]) {
- ++i;
- }
- if (i >= length) {
- break;
- }
- offset++;
- skipped++;
- }
- return skipped;
- }
- var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
- var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
- var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]);
- var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
- var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
- this.entries.length = 0;
- var stream = this.stream;
- stream.pos = 0;
- var buffer = stream.getBytes();
- var position = stream.start,
- length = buffer.length;
- var trailers = [],
- xrefStms = [];
- while (position < length) {
- var ch = buffer[position];
- if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
- ++position;
- continue;
- }
- if (ch === PERCENT) {
- do {
- ++position;
- if (position >= length) {
- break;
- }
- ch = buffer[position];
- } while (ch !== LF && ch !== CR);
- continue;
- }
- var token = readToken(buffer, position);
- var m;
- if (token.indexOf('xref') === 0 && (token.length === 4 || /\s/.test(token[4]))) {
- position += skipUntil(buffer, position, trailerBytes);
- trailers.push(position);
- position += skipUntil(buffer, position, startxrefBytes);
- } else if (m = objRegExp.exec(token)) {
- if (typeof this.entries[m[1]] === 'undefined') {
- this.entries[m[1]] = {
- offset: position - stream.start,
- gen: m[2] | 0,
- uncompressed: true
- };
- }
- var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
- var content = buffer.subarray(position, position + contentLength);
- var xrefTagOffset = skipUntil(content, 0, xrefBytes);
- if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) {
- xrefStms.push(position - stream.start);
- this.xrefstms[position - stream.start] = 1;
- }
- position += contentLength;
- } else if (token.indexOf('trailer') === 0 && (token.length === 7 || /\s/.test(token[7]))) {
- trailers.push(position);
- position += skipUntil(buffer, position, startxrefBytes);
- } else {
- position += token.length + 1;
- }
- }
- var i, ii;
- for (i = 0, ii = xrefStms.length; i < ii; ++i) {
- this.startXRefQueue.push(xrefStms[i]);
- this.readXRef(true);
- }
- var dict;
- for (i = 0, ii = trailers.length; i < ii; ++i) {
- stream.pos = trailers[i];
- var parser = new Parser(new Lexer(stream), true, this, true);
- var obj = parser.getObj();
- if (!isCmd(obj, 'trailer')) {
- continue;
- }
- dict = parser.getObj();
- if (!isDict(dict)) {
- continue;
- }
- if (dict.has('ID')) {
- return dict;
- }
- }
- if (dict) {
- return dict;
- }
- throw new InvalidPDFException('Invalid PDF structure');
- },
- readXRef: function XRef_readXRef(recoveryMode) {
- var stream = this.stream;
- try {
- while (this.startXRefQueue.length) {
- var startXRef = this.startXRefQueue[0];
- stream.pos = startXRef + stream.start;
- var parser = new Parser(new Lexer(stream), true, this);
- var obj = parser.getObj();
- var dict;
- if (isCmd(obj, 'xref')) {
- dict = this.processXRefTable(parser);
- if (!this.topDict) {
- this.topDict = dict;
- }
- obj = dict.get('XRefStm');
- if (isInt(obj)) {
- var pos = obj;
- if (!(pos in this.xrefstms)) {
- this.xrefstms[pos] = 1;
- this.startXRefQueue.push(pos);
- }
- }
- } else if (isInt(obj)) {
- if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || !isStream(obj = parser.getObj())) {
- error('Invalid XRef stream');
- }
- dict = this.processXRefStream(obj);
- if (!this.topDict) {
- this.topDict = dict;
- }
- if (!dict) {
- error('Failed to read XRef stream');
- }
- } else {
- error('Invalid XRef stream header');
- }
- obj = dict.get('Prev');
- if (isInt(obj)) {
- this.startXRefQueue.push(obj);
- } else if (isRef(obj)) {
- this.startXRefQueue.push(obj.num);
- }
- this.startXRefQueue.shift();
- }
- return this.topDict;
- } catch (e) {
- if (e instanceof MissingDataException) {
- throw e;
- }
- info('(while reading XRef): ' + e);
- }
- if (recoveryMode) {
- return;
- }
- throw new XRefParseException();
- },
- getEntry: function XRef_getEntry(i) {
- var xrefEntry = this.entries[i];
- if (xrefEntry && !xrefEntry.free && xrefEntry.offset) {
- return xrefEntry;
- }
- return null;
- },
- fetchIfRef: function XRef_fetchIfRef(obj, suppressEncryption) {
- if (!isRef(obj)) {
- return obj;
- }
- return this.fetch(obj, suppressEncryption);
- },
- fetch: function XRef_fetch(ref, suppressEncryption) {
- assert(isRef(ref), 'ref object is not a reference');
- var num = ref.num;
- if (num in this.cache) {
- var cacheEntry = this.cache[num];
- if (cacheEntry instanceof Dict && !cacheEntry.objId) {
- cacheEntry.objId = ref.toString();
- }
- return cacheEntry;
- }
- var xrefEntry = this.getEntry(num);
- if (xrefEntry === null) {
- return this.cache[num] = null;
- }
- if (xrefEntry.uncompressed) {
- xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
- } else {
- xrefEntry = this.fetchCompressed(xrefEntry, suppressEncryption);
- }
- if (isDict(xrefEntry)) {
- xrefEntry.objId = ref.toString();
- } else if (isStream(xrefEntry)) {
- xrefEntry.dict.objId = ref.toString();
- }
- return xrefEntry;
- },
- fetchUncompressed: function XRef_fetchUncompressed(ref, xrefEntry, suppressEncryption) {
- var gen = ref.gen;
- var num = ref.num;
- if (xrefEntry.gen !== gen) {
- error('inconsistent generation in XRef');
- }
- var stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
- var parser = new Parser(new Lexer(stream), true, this);
- var obj1 = parser.getObj();
- var obj2 = parser.getObj();
- var obj3 = parser.getObj();
- if (!isInt(obj1) || parseInt(obj1, 10) !== num || !isInt(obj2) || parseInt(obj2, 10) !== gen || !isCmd(obj3)) {
- error('bad XRef entry');
- }
- if (!isCmd(obj3, 'obj')) {
- if (obj3.cmd.indexOf('obj') === 0) {
- num = parseInt(obj3.cmd.substring(3), 10);
- if (!isNaN(num)) {
- return num;
- }
- }
- error('bad XRef entry');
- }
- if (this.encrypt && !suppressEncryption) {
- xrefEntry = parser.getObj(this.encrypt.createCipherTransform(num, gen));
- } else {
- xrefEntry = parser.getObj();
- }
- if (!isStream(xrefEntry)) {
- this.cache[num] = xrefEntry;
- }
- return xrefEntry;
- },
- fetchCompressed: function XRef_fetchCompressed(xrefEntry, suppressEncryption) {
- var tableOffset = xrefEntry.offset;
- var stream = this.fetch(new Ref(tableOffset, 0));
- if (!isStream(stream)) {
- error('bad ObjStm stream');
- }
- var first = stream.dict.get('First');
- var n = stream.dict.get('N');
- if (!isInt(first) || !isInt(n)) {
- error('invalid first and n parameters for ObjStm stream');
- }
- var parser = new Parser(new Lexer(stream), false, this);
- parser.allowStreams = true;
- var i,
- entries = [],
- num,
- nums = [];
- for (i = 0; i < n; ++i) {
- num = parser.getObj();
- if (!isInt(num)) {
- error('invalid object number in the ObjStm stream: ' + num);
- }
- nums.push(num);
- var offset = parser.getObj();
- if (!isInt(offset)) {
- error('invalid object offset in the ObjStm stream: ' + offset);
- }
- }
- for (i = 0; i < n; ++i) {
- entries.push(parser.getObj());
- if (isCmd(parser.buf1, 'endobj')) {
- parser.shift();
- }
- num = nums[i];
- var entry = this.entries[num];
- if (entry && entry.offset === tableOffset && entry.gen === i) {
- this.cache[num] = entries[i];
- }
- }
- xrefEntry = entries[xrefEntry.gen];
- if (xrefEntry === undefined) {
- error('bad XRef entry for compressed object');
- }
- return xrefEntry;
- },
- fetchIfRefAsync: function XRef_fetchIfRefAsync(obj, suppressEncryption) {
- if (!isRef(obj)) {
- return Promise.resolve(obj);
- }
- return this.fetchAsync(obj, suppressEncryption);
- },
- fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) {
- var streamManager = this.stream.manager;
- var xref = this;
- return new Promise(function tryFetch(resolve, reject) {
- try {
- resolve(xref.fetch(ref, suppressEncryption));
- } catch (e) {
- if (e instanceof MissingDataException) {
- streamManager.requestRange(e.begin, e.end).then(function () {
- tryFetch(resolve, reject);
- }, reject);
- return;
- }
- reject(e);
- }
- });
- },
- getCatalogObj: function XRef_getCatalogObj() {
- return this.root;
- }
- };
- return XRef;
-}();
-var NameOrNumberTree = function NameOrNumberTreeClosure() {
- function NameOrNumberTree(root, xref) {
- throw new Error('Cannot initialize NameOrNumberTree.');
- }
- NameOrNumberTree.prototype = {
- getAll: function NameOrNumberTree_getAll() {
- var dict = Object.create(null);
- if (!this.root) {
- return dict;
- }
- var xref = this.xref;
- var processed = new RefSet();
- processed.put(this.root);
- var queue = [this.root];
- while (queue.length > 0) {
- var i, n;
- var obj = xref.fetchIfRef(queue.shift());
- if (!isDict(obj)) {
- continue;
- }
- if (obj.has('Kids')) {
- var kids = obj.get('Kids');
- for (i = 0, n = kids.length; i < n; i++) {
- var kid = kids[i];
- assert(!processed.has(kid), 'Duplicate entry in "' + this._type + '" tree.');
- queue.push(kid);
- processed.put(kid);
- }
- continue;
- }
- var entries = obj.get(this._type);
- if (isArray(entries)) {
- for (i = 0, n = entries.length; i < n; i += 2) {
- dict[xref.fetchIfRef(entries[i])] = xref.fetchIfRef(entries[i + 1]);
- }
- }
- }
- return dict;
- },
- get: function NameOrNumberTree_get(key) {
- if (!this.root) {
- return null;
- }
- var xref = this.xref;
- var kidsOrEntries = xref.fetchIfRef(this.root);
- var loopCount = 0;
- var MAX_LEVELS = 10;
- var l, r, m;
- while (kidsOrEntries.has('Kids')) {
- if (++loopCount > MAX_LEVELS) {
- warn('Search depth limit reached for "' + this._type + '" tree.');
- return null;
- }
- var kids = kidsOrEntries.get('Kids');
- if (!isArray(kids)) {
- return null;
- }
- l = 0;
- r = kids.length - 1;
- while (l <= r) {
- m = l + r >> 1;
- var kid = xref.fetchIfRef(kids[m]);
- var limits = kid.get('Limits');
- if (key < xref.fetchIfRef(limits[0])) {
- r = m - 1;
- } else if (key > xref.fetchIfRef(limits[1])) {
- l = m + 1;
- } else {
- kidsOrEntries = xref.fetchIfRef(kids[m]);
- break;
- }
- }
- if (l > r) {
- return null;
- }
- }
- var entries = kidsOrEntries.get(this._type);
- if (isArray(entries)) {
- l = 0;
- r = entries.length - 2;
- while (l <= r) {
- m = l + r & ~1;
- var currentKey = xref.fetchIfRef(entries[m]);
- if (key < currentKey) {
- r = m - 2;
- } else if (key > currentKey) {
- l = m + 2;
- } else {
- return xref.fetchIfRef(entries[m + 1]);
- }
- }
- }
- return null;
- }
- };
- return NameOrNumberTree;
-}();
-var NameTree = function NameTreeClosure() {
- function NameTree(root, xref) {
- this.root = root;
- this.xref = xref;
- this._type = 'Names';
- }
- Util.inherit(NameTree, NameOrNumberTree, {});
- return NameTree;
-}();
-var NumberTree = function NumberTreeClosure() {
- function NumberTree(root, xref) {
- this.root = root;
- this.xref = xref;
- this._type = 'Nums';
- }
- Util.inherit(NumberTree, NameOrNumberTree, {});
- return NumberTree;
-}();
-var FileSpec = function FileSpecClosure() {
- function FileSpec(root, xref) {
- if (!root || !isDict(root)) {
- return;
- }
- this.xref = xref;
- this.root = root;
- if (root.has('FS')) {
- this.fs = root.get('FS');
- }
- this.description = root.has('Desc') ? stringToPDFString(root.get('Desc')) : '';
- if (root.has('RF')) {
- warn('Related file specifications are not supported');
- }
- this.contentAvailable = true;
- if (!root.has('EF')) {
- this.contentAvailable = false;
- warn('Non-embedded file specifications are not supported');
- }
- }
- function pickPlatformItem(dict) {
- if (dict.has('UF')) {
- return dict.get('UF');
- } else if (dict.has('F')) {
- return dict.get('F');
- } else if (dict.has('Unix')) {
- return dict.get('Unix');
- } else if (dict.has('Mac')) {
- return dict.get('Mac');
- } else if (dict.has('DOS')) {
- return dict.get('DOS');
- }
- return null;
- }
- FileSpec.prototype = {
- get filename() {
- if (!this._filename && this.root) {
- var filename = pickPlatformItem(this.root) || 'unnamed';
- this._filename = stringToPDFString(filename).replace(/\\\\/g, '\\').replace(/\\\//g, '/').replace(/\\/g, '/');
- }
- return this._filename;
- },
- get content() {
- if (!this.contentAvailable) {
- return null;
- }
- if (!this.contentRef && this.root) {
- this.contentRef = pickPlatformItem(this.root.get('EF'));
- }
- var content = null;
- if (this.contentRef) {
- var xref = this.xref;
- var fileObj = xref.fetchIfRef(this.contentRef);
- if (fileObj && isStream(fileObj)) {
- content = fileObj.getBytes();
- } else {
- warn('Embedded file specification points to non-existing/invalid ' + 'content');
- }
- } else {
- warn('Embedded file specification does not have a content');
- }
- return content;
- },
- get serializable() {
- return {
- filename: this.filename,
- content: this.content
- };
- }
- };
- return FileSpec;
-}();
-var ObjectLoader = function () {
- function mayHaveChildren(value) {
- return isRef(value) || isDict(value) || isArray(value) || isStream(value);
- }
- function addChildren(node, nodesToVisit) {
- var value;
- if (isDict(node) || isStream(node)) {
- var map;
- if (isDict(node)) {
- map = node.map;
- } else {
- map = node.dict.map;
- }
- for (var key in map) {
- value = map[key];
- if (mayHaveChildren(value)) {
- nodesToVisit.push(value);
- }
- }
- } else if (isArray(node)) {
- for (var i = 0, ii = node.length; i < ii; i++) {
- value = node[i];
- if (mayHaveChildren(value)) {
- nodesToVisit.push(value);
- }
- }
- }
- }
- function ObjectLoader(obj, keys, xref) {
- this.obj = obj;
- this.keys = keys;
- this.xref = xref;
- this.refSet = null;
- this.capability = null;
- }
- ObjectLoader.prototype = {
- load: function ObjectLoader_load() {
- var keys = this.keys;
- this.capability = createPromiseCapability();
- if (!(this.xref.stream instanceof ChunkedStream) || this.xref.stream.getMissingChunks().length === 0) {
- this.capability.resolve();
- return this.capability.promise;
- }
- this.refSet = new RefSet();
- var nodesToVisit = [];
- for (var i = 0; i < keys.length; i++) {
- nodesToVisit.push(this.obj[keys[i]]);
- }
- this._walk(nodesToVisit);
- return this.capability.promise;
- },
- _walk: function ObjectLoader_walk(nodesToVisit) {
- var nodesToRevisit = [];
- var pendingRequests = [];
- while (nodesToVisit.length) {
- var currentNode = nodesToVisit.pop();
- if (isRef(currentNode)) {
- if (this.refSet.has(currentNode)) {
- continue;
- }
- try {
- var ref = currentNode;
- this.refSet.put(ref);
- currentNode = this.xref.fetch(currentNode);
- } catch (e) {
- if (!(e instanceof MissingDataException)) {
- throw e;
- }
- nodesToRevisit.push(currentNode);
- pendingRequests.push({
- begin: e.begin,
- end: e.end
- });
- }
- }
- if (currentNode && currentNode.getBaseStreams) {
- var baseStreams = currentNode.getBaseStreams();
- var foundMissingData = false;
- for (var i = 0; i < baseStreams.length; i++) {
- var stream = baseStreams[i];
- if (stream.getMissingChunks && stream.getMissingChunks().length) {
- foundMissingData = true;
- pendingRequests.push({
- begin: stream.start,
- end: stream.end
- });
- }
- }
- if (foundMissingData) {
- nodesToRevisit.push(currentNode);
- }
- }
- addChildren(currentNode, nodesToVisit);
- }
- if (pendingRequests.length) {
- this.xref.stream.manager.requestRanges(pendingRequests).then(function pendingRequestCallback() {
- nodesToVisit = nodesToRevisit;
- for (var i = 0; i < nodesToRevisit.length; i++) {
- var node = nodesToRevisit[i];
- if (isRef(node)) {
- this.refSet.remove(node);
- }
- }
- this._walk(nodesToVisit);
- }.bind(this), this.capability.reject);
- return;
- }
- this.refSet = null;
- this.capability.resolve();
- }
- };
- return ObjectLoader;
-}();
-exports.Catalog = Catalog;
-exports.ObjectLoader = ObjectLoader;
-exports.XRef = XRef;
-exports.FileSpec = FileSpec;
-
-/***/ }),
-/* 17 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var getLookupTableFactory = sharedUtil.getLookupTableFactory;
-var getStdFontMap = getLookupTableFactory(function (t) {
- t['ArialNarrow'] = 'Helvetica';
- t['ArialNarrow-Bold'] = 'Helvetica-Bold';
- t['ArialNarrow-BoldItalic'] = 'Helvetica-BoldOblique';
- t['ArialNarrow-Italic'] = 'Helvetica-Oblique';
- t['ArialBlack'] = 'Helvetica';
- t['ArialBlack-Bold'] = 'Helvetica-Bold';
- t['ArialBlack-BoldItalic'] = 'Helvetica-BoldOblique';
- t['ArialBlack-Italic'] = 'Helvetica-Oblique';
- t['Arial-Black'] = 'Helvetica';
- t['Arial-Black-Bold'] = 'Helvetica-Bold';
- t['Arial-Black-BoldItalic'] = 'Helvetica-BoldOblique';
- t['Arial-Black-Italic'] = 'Helvetica-Oblique';
- t['Arial'] = 'Helvetica';
- t['Arial-Bold'] = 'Helvetica-Bold';
- t['Arial-BoldItalic'] = 'Helvetica-BoldOblique';
- t['Arial-Italic'] = 'Helvetica-Oblique';
- t['Arial-BoldItalicMT'] = 'Helvetica-BoldOblique';
- t['Arial-BoldMT'] = 'Helvetica-Bold';
- t['Arial-ItalicMT'] = 'Helvetica-Oblique';
- t['ArialMT'] = 'Helvetica';
- t['Courier-Bold'] = 'Courier-Bold';
- t['Courier-BoldItalic'] = 'Courier-BoldOblique';
- t['Courier-Italic'] = 'Courier-Oblique';
- t['CourierNew'] = 'Courier';
- t['CourierNew-Bold'] = 'Courier-Bold';
- t['CourierNew-BoldItalic'] = 'Courier-BoldOblique';
- t['CourierNew-Italic'] = 'Courier-Oblique';
- t['CourierNewPS-BoldItalicMT'] = 'Courier-BoldOblique';
- t['CourierNewPS-BoldMT'] = 'Courier-Bold';
- t['CourierNewPS-ItalicMT'] = 'Courier-Oblique';
- t['CourierNewPSMT'] = 'Courier';
- t['Helvetica'] = 'Helvetica';
- t['Helvetica-Bold'] = 'Helvetica-Bold';
- t['Helvetica-BoldItalic'] = 'Helvetica-BoldOblique';
- t['Helvetica-BoldOblique'] = 'Helvetica-BoldOblique';
- t['Helvetica-Italic'] = 'Helvetica-Oblique';
- t['Helvetica-Oblique'] = 'Helvetica-Oblique';
- t['Symbol-Bold'] = 'Symbol';
- t['Symbol-BoldItalic'] = 'Symbol';
- t['Symbol-Italic'] = 'Symbol';
- t['TimesNewRoman'] = 'Times-Roman';
- t['TimesNewRoman-Bold'] = 'Times-Bold';
- t['TimesNewRoman-BoldItalic'] = 'Times-BoldItalic';
- t['TimesNewRoman-Italic'] = 'Times-Italic';
- t['TimesNewRomanPS'] = 'Times-Roman';
- t['TimesNewRomanPS-Bold'] = 'Times-Bold';
- t['TimesNewRomanPS-BoldItalic'] = 'Times-BoldItalic';
- t['TimesNewRomanPS-BoldItalicMT'] = 'Times-BoldItalic';
- t['TimesNewRomanPS-BoldMT'] = 'Times-Bold';
- t['TimesNewRomanPS-Italic'] = 'Times-Italic';
- t['TimesNewRomanPS-ItalicMT'] = 'Times-Italic';
- t['TimesNewRomanPSMT'] = 'Times-Roman';
- t['TimesNewRomanPSMT-Bold'] = 'Times-Bold';
- t['TimesNewRomanPSMT-BoldItalic'] = 'Times-BoldItalic';
- t['TimesNewRomanPSMT-Italic'] = 'Times-Italic';
-});
-var getNonStdFontMap = getLookupTableFactory(function (t) {
- t['CenturyGothic'] = 'Helvetica';
- t['CenturyGothic-Bold'] = 'Helvetica-Bold';
- t['CenturyGothic-BoldItalic'] = 'Helvetica-BoldOblique';
- t['CenturyGothic-Italic'] = 'Helvetica-Oblique';
- t['ComicSansMS'] = 'Comic Sans MS';
- t['ComicSansMS-Bold'] = 'Comic Sans MS-Bold';
- t['ComicSansMS-BoldItalic'] = 'Comic Sans MS-BoldItalic';
- t['ComicSansMS-Italic'] = 'Comic Sans MS-Italic';
- t['LucidaConsole'] = 'Courier';
- t['LucidaConsole-Bold'] = 'Courier-Bold';
- t['LucidaConsole-BoldItalic'] = 'Courier-BoldOblique';
- t['LucidaConsole-Italic'] = 'Courier-Oblique';
- t['MS-Gothic'] = 'MS Gothic';
- t['MS-Gothic-Bold'] = 'MS Gothic-Bold';
- t['MS-Gothic-BoldItalic'] = 'MS Gothic-BoldItalic';
- t['MS-Gothic-Italic'] = 'MS Gothic-Italic';
- t['MS-Mincho'] = 'MS Mincho';
- t['MS-Mincho-Bold'] = 'MS Mincho-Bold';
- t['MS-Mincho-BoldItalic'] = 'MS Mincho-BoldItalic';
- t['MS-Mincho-Italic'] = 'MS Mincho-Italic';
- t['MS-PGothic'] = 'MS PGothic';
- t['MS-PGothic-Bold'] = 'MS PGothic-Bold';
- t['MS-PGothic-BoldItalic'] = 'MS PGothic-BoldItalic';
- t['MS-PGothic-Italic'] = 'MS PGothic-Italic';
- t['MS-PMincho'] = 'MS PMincho';
- t['MS-PMincho-Bold'] = 'MS PMincho-Bold';
- t['MS-PMincho-BoldItalic'] = 'MS PMincho-BoldItalic';
- t['MS-PMincho-Italic'] = 'MS PMincho-Italic';
- t['NuptialScript'] = 'Times-Italic';
- t['Wingdings'] = 'ZapfDingbats';
-});
-var getSerifFonts = getLookupTableFactory(function (t) {
- t['Adobe Jenson'] = true;
- t['Adobe Text'] = true;
- t['Albertus'] = true;
- t['Aldus'] = true;
- t['Alexandria'] = true;
- t['Algerian'] = true;
- t['American Typewriter'] = true;
- t['Antiqua'] = true;
- t['Apex'] = true;
- t['Arno'] = true;
- t['Aster'] = true;
- t['Aurora'] = true;
- t['Baskerville'] = true;
- t['Bell'] = true;
- t['Bembo'] = true;
- t['Bembo Schoolbook'] = true;
- t['Benguiat'] = true;
- t['Berkeley Old Style'] = true;
- t['Bernhard Modern'] = true;
- t['Berthold City'] = true;
- t['Bodoni'] = true;
- t['Bauer Bodoni'] = true;
- t['Book Antiqua'] = true;
- t['Bookman'] = true;
- t['Bordeaux Roman'] = true;
- t['Californian FB'] = true;
- t['Calisto'] = true;
- t['Calvert'] = true;
- t['Capitals'] = true;
- t['Cambria'] = true;
- t['Cartier'] = true;
- t['Caslon'] = true;
- t['Catull'] = true;
- t['Centaur'] = true;
- t['Century Old Style'] = true;
- t['Century Schoolbook'] = true;
- t['Chaparral'] = true;
- t['Charis SIL'] = true;
- t['Cheltenham'] = true;
- t['Cholla Slab'] = true;
- t['Clarendon'] = true;
- t['Clearface'] = true;
- t['Cochin'] = true;
- t['Colonna'] = true;
- t['Computer Modern'] = true;
- t['Concrete Roman'] = true;
- t['Constantia'] = true;
- t['Cooper Black'] = true;
- t['Corona'] = true;
- t['Ecotype'] = true;
- t['Egyptienne'] = true;
- t['Elephant'] = true;
- t['Excelsior'] = true;
- t['Fairfield'] = true;
- t['FF Scala'] = true;
- t['Folkard'] = true;
- t['Footlight'] = true;
- t['FreeSerif'] = true;
- t['Friz Quadrata'] = true;
- t['Garamond'] = true;
- t['Gentium'] = true;
- t['Georgia'] = true;
- t['Gloucester'] = true;
- t['Goudy Old Style'] = true;
- t['Goudy Schoolbook'] = true;
- t['Goudy Pro Font'] = true;
- t['Granjon'] = true;
- t['Guardian Egyptian'] = true;
- t['Heather'] = true;
- t['Hercules'] = true;
- t['High Tower Text'] = true;
- t['Hiroshige'] = true;
- t['Hoefler Text'] = true;
- t['Humana Serif'] = true;
- t['Imprint'] = true;
- t['Ionic No. 5'] = true;
- t['Janson'] = true;
- t['Joanna'] = true;
- t['Korinna'] = true;
- t['Lexicon'] = true;
- t['Liberation Serif'] = true;
- t['Linux Libertine'] = true;
- t['Literaturnaya'] = true;
- t['Lucida'] = true;
- t['Lucida Bright'] = true;
- t['Melior'] = true;
- t['Memphis'] = true;
- t['Miller'] = true;
- t['Minion'] = true;
- t['Modern'] = true;
- t['Mona Lisa'] = true;
- t['Mrs Eaves'] = true;
- t['MS Serif'] = true;
- t['Museo Slab'] = true;
- t['New York'] = true;
- t['Nimbus Roman'] = true;
- t['NPS Rawlinson Roadway'] = true;
- t['NuptialScript'] = true;
- t['Palatino'] = true;
- t['Perpetua'] = true;
- t['Plantin'] = true;
- t['Plantin Schoolbook'] = true;
- t['Playbill'] = true;
- t['Poor Richard'] = true;
- t['Rawlinson Roadway'] = true;
- t['Renault'] = true;
- t['Requiem'] = true;
- t['Rockwell'] = true;
- t['Roman'] = true;
- t['Rotis Serif'] = true;
- t['Sabon'] = true;
- t['Scala'] = true;
- t['Seagull'] = true;
- t['Sistina'] = true;
- t['Souvenir'] = true;
- t['STIX'] = true;
- t['Stone Informal'] = true;
- t['Stone Serif'] = true;
- t['Sylfaen'] = true;
- t['Times'] = true;
- t['Trajan'] = true;
- t['Trinité'] = true;
- t['Trump Mediaeval'] = true;
- t['Utopia'] = true;
- t['Vale Type'] = true;
- t['Bitstream Vera'] = true;
- t['Vera Serif'] = true;
- t['Versailles'] = true;
- t['Wanted'] = true;
- t['Weiss'] = true;
- t['Wide Latin'] = true;
- t['Windsor'] = true;
- t['XITS'] = true;
-});
-var getSymbolsFonts = getLookupTableFactory(function (t) {
- t['Dingbats'] = true;
- t['Symbol'] = true;
- t['ZapfDingbats'] = true;
-});
-var getGlyphMapForStandardFonts = getLookupTableFactory(function (t) {
- t[2] = 10;
- t[3] = 32;
- t[4] = 33;
- t[5] = 34;
- t[6] = 35;
- t[7] = 36;
- t[8] = 37;
- t[9] = 38;
- t[10] = 39;
- t[11] = 40;
- t[12] = 41;
- t[13] = 42;
- t[14] = 43;
- t[15] = 44;
- t[16] = 45;
- t[17] = 46;
- t[18] = 47;
- t[19] = 48;
- t[20] = 49;
- t[21] = 50;
- t[22] = 51;
- t[23] = 52;
- t[24] = 53;
- t[25] = 54;
- t[26] = 55;
- t[27] = 56;
- t[28] = 57;
- t[29] = 58;
- t[30] = 894;
- t[31] = 60;
- t[32] = 61;
- t[33] = 62;
- t[34] = 63;
- t[35] = 64;
- t[36] = 65;
- t[37] = 66;
- t[38] = 67;
- t[39] = 68;
- t[40] = 69;
- t[41] = 70;
- t[42] = 71;
- t[43] = 72;
- t[44] = 73;
- t[45] = 74;
- t[46] = 75;
- t[47] = 76;
- t[48] = 77;
- t[49] = 78;
- t[50] = 79;
- t[51] = 80;
- t[52] = 81;
- t[53] = 82;
- t[54] = 83;
- t[55] = 84;
- t[56] = 85;
- t[57] = 86;
- t[58] = 87;
- t[59] = 88;
- t[60] = 89;
- t[61] = 90;
- t[62] = 91;
- t[63] = 92;
- t[64] = 93;
- t[65] = 94;
- t[66] = 95;
- t[67] = 96;
- t[68] = 97;
- t[69] = 98;
- t[70] = 99;
- t[71] = 100;
- t[72] = 101;
- t[73] = 102;
- t[74] = 103;
- t[75] = 104;
- t[76] = 105;
- t[77] = 106;
- t[78] = 107;
- t[79] = 108;
- t[80] = 109;
- t[81] = 110;
- t[82] = 111;
- t[83] = 112;
- t[84] = 113;
- t[85] = 114;
- t[86] = 115;
- t[87] = 116;
- t[88] = 117;
- t[89] = 118;
- t[90] = 119;
- t[91] = 120;
- t[92] = 121;
- t[93] = 122;
- t[94] = 123;
- t[95] = 124;
- t[96] = 125;
- t[97] = 126;
- t[98] = 196;
- t[99] = 197;
- t[100] = 199;
- t[101] = 201;
- t[102] = 209;
- t[103] = 214;
- t[104] = 220;
- t[105] = 225;
- t[106] = 224;
- t[107] = 226;
- t[108] = 228;
- t[109] = 227;
- t[110] = 229;
- t[111] = 231;
- t[112] = 233;
- t[113] = 232;
- t[114] = 234;
- t[115] = 235;
- t[116] = 237;
- t[117] = 236;
- t[118] = 238;
- t[119] = 239;
- t[120] = 241;
- t[121] = 243;
- t[122] = 242;
- t[123] = 244;
- t[124] = 246;
- t[125] = 245;
- t[126] = 250;
- t[127] = 249;
- t[128] = 251;
- t[129] = 252;
- t[130] = 8224;
- t[131] = 176;
- t[132] = 162;
- t[133] = 163;
- t[134] = 167;
- t[135] = 8226;
- t[136] = 182;
- t[137] = 223;
- t[138] = 174;
- t[139] = 169;
- t[140] = 8482;
- t[141] = 180;
- t[142] = 168;
- t[143] = 8800;
- t[144] = 198;
- t[145] = 216;
- t[146] = 8734;
- t[147] = 177;
- t[148] = 8804;
- t[149] = 8805;
- t[150] = 165;
- t[151] = 181;
- t[152] = 8706;
- t[153] = 8721;
- t[154] = 8719;
- t[156] = 8747;
- t[157] = 170;
- t[158] = 186;
- t[159] = 8486;
- t[160] = 230;
- t[161] = 248;
- t[162] = 191;
- t[163] = 161;
- t[164] = 172;
- t[165] = 8730;
- t[166] = 402;
- t[167] = 8776;
- t[168] = 8710;
- t[169] = 171;
- t[170] = 187;
- t[171] = 8230;
- t[210] = 218;
- t[223] = 711;
- t[224] = 321;
- t[225] = 322;
- t[227] = 353;
- t[229] = 382;
- t[234] = 253;
- t[252] = 263;
- t[253] = 268;
- t[254] = 269;
- t[258] = 258;
- t[260] = 260;
- t[261] = 261;
- t[265] = 280;
- t[266] = 281;
- t[268] = 283;
- t[269] = 313;
- t[275] = 323;
- t[276] = 324;
- t[278] = 328;
- t[284] = 345;
- t[285] = 346;
- t[286] = 347;
- t[292] = 367;
- t[295] = 377;
- t[296] = 378;
- t[298] = 380;
- t[305] = 963;
- t[306] = 964;
- t[307] = 966;
- t[308] = 8215;
- t[309] = 8252;
- t[310] = 8319;
- t[311] = 8359;
- t[312] = 8592;
- t[313] = 8593;
- t[337] = 9552;
- t[493] = 1039;
- t[494] = 1040;
- t[705] = 1524;
- t[706] = 8362;
- t[710] = 64288;
- t[711] = 64298;
- t[759] = 1617;
- t[761] = 1776;
- t[763] = 1778;
- t[775] = 1652;
- t[777] = 1764;
- t[778] = 1780;
- t[779] = 1781;
- t[780] = 1782;
- t[782] = 771;
- t[783] = 64726;
- t[786] = 8363;
- t[788] = 8532;
- t[790] = 768;
- t[791] = 769;
- t[792] = 768;
- t[795] = 803;
- t[797] = 64336;
- t[798] = 64337;
- t[799] = 64342;
- t[800] = 64343;
- t[801] = 64344;
- t[802] = 64345;
- t[803] = 64362;
- t[804] = 64363;
- t[805] = 64364;
- t[2424] = 7821;
- t[2425] = 7822;
- t[2426] = 7823;
- t[2427] = 7824;
- t[2428] = 7825;
- t[2429] = 7826;
- t[2430] = 7827;
- t[2433] = 7682;
- t[2678] = 8045;
- t[2679] = 8046;
- t[2830] = 1552;
- t[2838] = 686;
- t[2840] = 751;
- t[2842] = 753;
- t[2843] = 754;
- t[2844] = 755;
- t[2846] = 757;
- t[2856] = 767;
- t[2857] = 848;
- t[2858] = 849;
- t[2862] = 853;
- t[2863] = 854;
- t[2864] = 855;
- t[2865] = 861;
- t[2866] = 862;
- t[2906] = 7460;
- t[2908] = 7462;
- t[2909] = 7463;
- t[2910] = 7464;
- t[2912] = 7466;
- t[2913] = 7467;
- t[2914] = 7468;
- t[2916] = 7470;
- t[2917] = 7471;
- t[2918] = 7472;
- t[2920] = 7474;
- t[2921] = 7475;
- t[2922] = 7476;
- t[2924] = 7478;
- t[2925] = 7479;
- t[2926] = 7480;
- t[2928] = 7482;
- t[2929] = 7483;
- t[2930] = 7484;
- t[2932] = 7486;
- t[2933] = 7487;
- t[2934] = 7488;
- t[2936] = 7490;
- t[2937] = 7491;
- t[2938] = 7492;
- t[2940] = 7494;
- t[2941] = 7495;
- t[2942] = 7496;
- t[2944] = 7498;
- t[2946] = 7500;
- t[2948] = 7502;
- t[2950] = 7504;
- t[2951] = 7505;
- t[2952] = 7506;
- t[2954] = 7508;
- t[2955] = 7509;
- t[2956] = 7510;
- t[2958] = 7512;
- t[2959] = 7513;
- t[2960] = 7514;
- t[2962] = 7516;
- t[2963] = 7517;
- t[2964] = 7518;
- t[2966] = 7520;
- t[2967] = 7521;
- t[2968] = 7522;
- t[2970] = 7524;
- t[2971] = 7525;
- t[2972] = 7526;
- t[2974] = 7528;
- t[2975] = 7529;
- t[2976] = 7530;
- t[2978] = 1537;
- t[2979] = 1538;
- t[2980] = 1539;
- t[2982] = 1549;
- t[2983] = 1551;
- t[2984] = 1552;
- t[2986] = 1554;
- t[2987] = 1555;
- t[2988] = 1556;
- t[2990] = 1623;
- t[2991] = 1624;
- t[2995] = 1775;
- t[2999] = 1791;
- t[3002] = 64290;
- t[3003] = 64291;
- t[3004] = 64292;
- t[3006] = 64294;
- t[3007] = 64295;
- t[3008] = 64296;
- t[3011] = 1900;
- t[3014] = 8223;
- t[3015] = 8244;
- t[3017] = 7532;
- t[3018] = 7533;
- t[3019] = 7534;
- t[3075] = 7590;
- t[3076] = 7591;
- t[3079] = 7594;
- t[3080] = 7595;
- t[3083] = 7598;
- t[3084] = 7599;
- t[3087] = 7602;
- t[3088] = 7603;
- t[3091] = 7606;
- t[3092] = 7607;
- t[3095] = 7610;
- t[3096] = 7611;
- t[3099] = 7614;
- t[3100] = 7615;
- t[3103] = 7618;
- t[3104] = 7619;
- t[3107] = 8337;
- t[3108] = 8338;
- t[3116] = 1884;
- t[3119] = 1885;
- t[3120] = 1885;
- t[3123] = 1886;
- t[3124] = 1886;
- t[3127] = 1887;
- t[3128] = 1887;
- t[3131] = 1888;
- t[3132] = 1888;
- t[3135] = 1889;
- t[3136] = 1889;
- t[3139] = 1890;
- t[3140] = 1890;
- t[3143] = 1891;
- t[3144] = 1891;
- t[3147] = 1892;
- t[3148] = 1892;
- t[3153] = 580;
- t[3154] = 581;
- t[3157] = 584;
- t[3158] = 585;
- t[3161] = 588;
- t[3162] = 589;
- t[3165] = 891;
- t[3166] = 892;
- t[3169] = 1274;
- t[3170] = 1275;
- t[3173] = 1278;
- t[3174] = 1279;
- t[3181] = 7622;
- t[3182] = 7623;
- t[3282] = 11799;
- t[3316] = 578;
- t[3379] = 42785;
- t[3393] = 1159;
- t[3416] = 8377;
-});
-var getSupplementalGlyphMapForArialBlack = getLookupTableFactory(function (t) {
- t[227] = 322;
- t[264] = 261;
- t[291] = 346;
-});
-exports.getStdFontMap = getStdFontMap;
-exports.getNonStdFontMap = getNonStdFontMap;
-exports.getSerifFonts = getSerifFonts;
-exports.getSymbolsFonts = getSymbolsFonts;
-exports.getGlyphMapForStandardFonts = getGlyphMapForStandardFonts;
-exports.getSupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack;
-
-/***/ }),
-/* 18 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var getLookupTableFactory = sharedUtil.getLookupTableFactory;
-var getSpecialPUASymbols = getLookupTableFactory(function (t) {
- t[63721] = 0x00A9;
- t[63193] = 0x00A9;
- t[63720] = 0x00AE;
- t[63194] = 0x00AE;
- t[63722] = 0x2122;
- t[63195] = 0x2122;
- t[63729] = 0x23A7;
- t[63730] = 0x23A8;
- t[63731] = 0x23A9;
- t[63740] = 0x23AB;
- t[63741] = 0x23AC;
- t[63742] = 0x23AD;
- t[63726] = 0x23A1;
- t[63727] = 0x23A2;
- t[63728] = 0x23A3;
- t[63737] = 0x23A4;
- t[63738] = 0x23A5;
- t[63739] = 0x23A6;
- t[63723] = 0x239B;
- t[63724] = 0x239C;
- t[63725] = 0x239D;
- t[63734] = 0x239E;
- t[63735] = 0x239F;
- t[63736] = 0x23A0;
-});
-function mapSpecialUnicodeValues(code) {
- if (code >= 0xFFF0 && code <= 0xFFFF) {
- return 0;
- } else if (code >= 0xF600 && code <= 0xF8FF) {
- return getSpecialPUASymbols()[code] || code;
- }
- return code;
-}
-function getUnicodeForGlyph(name, glyphsUnicodeMap) {
- var unicode = glyphsUnicodeMap[name];
- if (unicode !== undefined) {
- return unicode;
- }
- if (!name) {
- return -1;
- }
- if (name[0] === 'u') {
- var nameLen = name.length,
- hexStr;
- if (nameLen === 7 && name[1] === 'n' && name[2] === 'i') {
- hexStr = name.substr(3);
- } else if (nameLen >= 5 && nameLen <= 7) {
- hexStr = name.substr(1);
- } else {
- return -1;
- }
- if (hexStr === hexStr.toUpperCase()) {
- unicode = parseInt(hexStr, 16);
- if (unicode >= 0) {
- return unicode;
- }
- }
- }
- return -1;
-}
-var UnicodeRanges = [{
- 'begin': 0x0000,
- 'end': 0x007F
-}, {
- 'begin': 0x0080,
- 'end': 0x00FF
-}, {
- 'begin': 0x0100,
- 'end': 0x017F
-}, {
- 'begin': 0x0180,
- 'end': 0x024F
-}, {
- 'begin': 0x0250,
- 'end': 0x02AF
-}, {
- 'begin': 0x02B0,
- 'end': 0x02FF
-}, {
- 'begin': 0x0300,
- 'end': 0x036F
-}, {
- 'begin': 0x0370,
- 'end': 0x03FF
-}, {
- 'begin': 0x2C80,
- 'end': 0x2CFF
-}, {
- 'begin': 0x0400,
- 'end': 0x04FF
-}, {
- 'begin': 0x0530,
- 'end': 0x058F
-}, {
- 'begin': 0x0590,
- 'end': 0x05FF
-}, {
- 'begin': 0xA500,
- 'end': 0xA63F
-}, {
- 'begin': 0x0600,
- 'end': 0x06FF
-}, {
- 'begin': 0x07C0,
- 'end': 0x07FF
-}, {
- 'begin': 0x0900,
- 'end': 0x097F
-}, {
- 'begin': 0x0980,
- 'end': 0x09FF
-}, {
- 'begin': 0x0A00,
- 'end': 0x0A7F
-}, {
- 'begin': 0x0A80,
- 'end': 0x0AFF
-}, {
- 'begin': 0x0B00,
- 'end': 0x0B7F
-}, {
- 'begin': 0x0B80,
- 'end': 0x0BFF
-}, {
- 'begin': 0x0C00,
- 'end': 0x0C7F
-}, {
- 'begin': 0x0C80,
- 'end': 0x0CFF
-}, {
- 'begin': 0x0D00,
- 'end': 0x0D7F
-}, {
- 'begin': 0x0E00,
- 'end': 0x0E7F
-}, {
- 'begin': 0x0E80,
- 'end': 0x0EFF
-}, {
- 'begin': 0x10A0,
- 'end': 0x10FF
-}, {
- 'begin': 0x1B00,
- 'end': 0x1B7F
-}, {
- 'begin': 0x1100,
- 'end': 0x11FF
-}, {
- 'begin': 0x1E00,
- 'end': 0x1EFF
-}, {
- 'begin': 0x1F00,
- 'end': 0x1FFF
-}, {
- 'begin': 0x2000,
- 'end': 0x206F
-}, {
- 'begin': 0x2070,
- 'end': 0x209F
-}, {
- 'begin': 0x20A0,
- 'end': 0x20CF
-}, {
- 'begin': 0x20D0,
- 'end': 0x20FF
-}, {
- 'begin': 0x2100,
- 'end': 0x214F
-}, {
- 'begin': 0x2150,
- 'end': 0x218F
-}, {
- 'begin': 0x2190,
- 'end': 0x21FF
-}, {
- 'begin': 0x2200,
- 'end': 0x22FF
-}, {
- 'begin': 0x2300,
- 'end': 0x23FF
-}, {
- 'begin': 0x2400,
- 'end': 0x243F
-}, {
- 'begin': 0x2440,
- 'end': 0x245F
-}, {
- 'begin': 0x2460,
- 'end': 0x24FF
-}, {
- 'begin': 0x2500,
- 'end': 0x257F
-}, {
- 'begin': 0x2580,
- 'end': 0x259F
-}, {
- 'begin': 0x25A0,
- 'end': 0x25FF
-}, {
- 'begin': 0x2600,
- 'end': 0x26FF
-}, {
- 'begin': 0x2700,
- 'end': 0x27BF
-}, {
- 'begin': 0x3000,
- 'end': 0x303F
-}, {
- 'begin': 0x3040,
- 'end': 0x309F
-}, {
- 'begin': 0x30A0,
- 'end': 0x30FF
-}, {
- 'begin': 0x3100,
- 'end': 0x312F
-}, {
- 'begin': 0x3130,
- 'end': 0x318F
-}, {
- 'begin': 0xA840,
- 'end': 0xA87F
-}, {
- 'begin': 0x3200,
- 'end': 0x32FF
-}, {
- 'begin': 0x3300,
- 'end': 0x33FF
-}, {
- 'begin': 0xAC00,
- 'end': 0xD7AF
-}, {
- 'begin': 0xD800,
- 'end': 0xDFFF
-}, {
- 'begin': 0x10900,
- 'end': 0x1091F
-}, {
- 'begin': 0x4E00,
- 'end': 0x9FFF
-}, {
- 'begin': 0xE000,
- 'end': 0xF8FF
-}, {
- 'begin': 0x31C0,
- 'end': 0x31EF
-}, {
- 'begin': 0xFB00,
- 'end': 0xFB4F
-}, {
- 'begin': 0xFB50,
- 'end': 0xFDFF
-}, {
- 'begin': 0xFE20,
- 'end': 0xFE2F
-}, {
- 'begin': 0xFE10,
- 'end': 0xFE1F
-}, {
- 'begin': 0xFE50,
- 'end': 0xFE6F
-}, {
- 'begin': 0xFE70,
- 'end': 0xFEFF
-}, {
- 'begin': 0xFF00,
- 'end': 0xFFEF
-}, {
- 'begin': 0xFFF0,
- 'end': 0xFFFF
-}, {
- 'begin': 0x0F00,
- 'end': 0x0FFF
-}, {
- 'begin': 0x0700,
- 'end': 0x074F
-}, {
- 'begin': 0x0780,
- 'end': 0x07BF
-}, {
- 'begin': 0x0D80,
- 'end': 0x0DFF
-}, {
- 'begin': 0x1000,
- 'end': 0x109F
-}, {
- 'begin': 0x1200,
- 'end': 0x137F
-}, {
- 'begin': 0x13A0,
- 'end': 0x13FF
-}, {
- 'begin': 0x1400,
- 'end': 0x167F
-}, {
- 'begin': 0x1680,
- 'end': 0x169F
-}, {
- 'begin': 0x16A0,
- 'end': 0x16FF
-}, {
- 'begin': 0x1780,
- 'end': 0x17FF
-}, {
- 'begin': 0x1800,
- 'end': 0x18AF
-}, {
- 'begin': 0x2800,
- 'end': 0x28FF
-}, {
- 'begin': 0xA000,
- 'end': 0xA48F
-}, {
- 'begin': 0x1700,
- 'end': 0x171F
-}, {
- 'begin': 0x10300,
- 'end': 0x1032F
-}, {
- 'begin': 0x10330,
- 'end': 0x1034F
-}, {
- 'begin': 0x10400,
- 'end': 0x1044F
-}, {
- 'begin': 0x1D000,
- 'end': 0x1D0FF
-}, {
- 'begin': 0x1D400,
- 'end': 0x1D7FF
-}, {
- 'begin': 0xFF000,
- 'end': 0xFFFFD
-}, {
- 'begin': 0xFE00,
- 'end': 0xFE0F
-}, {
- 'begin': 0xE0000,
- 'end': 0xE007F
-}, {
- 'begin': 0x1900,
- 'end': 0x194F
-}, {
- 'begin': 0x1950,
- 'end': 0x197F
-}, {
- 'begin': 0x1980,
- 'end': 0x19DF
-}, {
- 'begin': 0x1A00,
- 'end': 0x1A1F
-}, {
- 'begin': 0x2C00,
- 'end': 0x2C5F
-}, {
- 'begin': 0x2D30,
- 'end': 0x2D7F
-}, {
- 'begin': 0x4DC0,
- 'end': 0x4DFF
-}, {
- 'begin': 0xA800,
- 'end': 0xA82F
-}, {
- 'begin': 0x10000,
- 'end': 0x1007F
-}, {
- 'begin': 0x10140,
- 'end': 0x1018F
-}, {
- 'begin': 0x10380,
- 'end': 0x1039F
-}, {
- 'begin': 0x103A0,
- 'end': 0x103DF
-}, {
- 'begin': 0x10450,
- 'end': 0x1047F
-}, {
- 'begin': 0x10480,
- 'end': 0x104AF
-}, {
- 'begin': 0x10800,
- 'end': 0x1083F
-}, {
- 'begin': 0x10A00,
- 'end': 0x10A5F
-}, {
- 'begin': 0x1D300,
- 'end': 0x1D35F
-}, {
- 'begin': 0x12000,
- 'end': 0x123FF
-}, {
- 'begin': 0x1D360,
- 'end': 0x1D37F
-}, {
- 'begin': 0x1B80,
- 'end': 0x1BBF
-}, {
- 'begin': 0x1C00,
- 'end': 0x1C4F
-}, {
- 'begin': 0x1C50,
- 'end': 0x1C7F
-}, {
- 'begin': 0xA880,
- 'end': 0xA8DF
-}, {
- 'begin': 0xA900,
- 'end': 0xA92F
-}, {
- 'begin': 0xA930,
- 'end': 0xA95F
-}, {
- 'begin': 0xAA00,
- 'end': 0xAA5F
-}, {
- 'begin': 0x10190,
- 'end': 0x101CF
-}, {
- 'begin': 0x101D0,
- 'end': 0x101FF
-}, {
- 'begin': 0x102A0,
- 'end': 0x102DF
-}, {
- 'begin': 0x1F030,
- 'end': 0x1F09F
-}];
-function getUnicodeRangeFor(value) {
- for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) {
- var range = UnicodeRanges[i];
- if (value >= range.begin && value < range.end) {
- return i;
- }
- }
- return -1;
-}
-function isRTLRangeFor(value) {
- var range = UnicodeRanges[13];
- if (value >= range.begin && value < range.end) {
- return true;
- }
- range = UnicodeRanges[11];
- if (value >= range.begin && value < range.end) {
- return true;
- }
- return false;
-}
-var getNormalizedUnicodes = getLookupTableFactory(function (t) {
- t['\u00A8'] = '\u0020\u0308';
- t['\u00AF'] = '\u0020\u0304';
- t['\u00B4'] = '\u0020\u0301';
- t['\u00B5'] = '\u03BC';
- t['\u00B8'] = '\u0020\u0327';
- t['\u0132'] = '\u0049\u004A';
- t['\u0133'] = '\u0069\u006A';
- t['\u013F'] = '\u004C\u00B7';
- t['\u0140'] = '\u006C\u00B7';
- t['\u0149'] = '\u02BC\u006E';
- t['\u017F'] = '\u0073';
- t['\u01C4'] = '\u0044\u017D';
- t['\u01C5'] = '\u0044\u017E';
- t['\u01C6'] = '\u0064\u017E';
- t['\u01C7'] = '\u004C\u004A';
- t['\u01C8'] = '\u004C\u006A';
- t['\u01C9'] = '\u006C\u006A';
- t['\u01CA'] = '\u004E\u004A';
- t['\u01CB'] = '\u004E\u006A';
- t['\u01CC'] = '\u006E\u006A';
- t['\u01F1'] = '\u0044\u005A';
- t['\u01F2'] = '\u0044\u007A';
- t['\u01F3'] = '\u0064\u007A';
- t['\u02D8'] = '\u0020\u0306';
- t['\u02D9'] = '\u0020\u0307';
- t['\u02DA'] = '\u0020\u030A';
- t['\u02DB'] = '\u0020\u0328';
- t['\u02DC'] = '\u0020\u0303';
- t['\u02DD'] = '\u0020\u030B';
- t['\u037A'] = '\u0020\u0345';
- t['\u0384'] = '\u0020\u0301';
- t['\u03D0'] = '\u03B2';
- t['\u03D1'] = '\u03B8';
- t['\u03D2'] = '\u03A5';
- t['\u03D5'] = '\u03C6';
- t['\u03D6'] = '\u03C0';
- t['\u03F0'] = '\u03BA';
- t['\u03F1'] = '\u03C1';
- t['\u03F2'] = '\u03C2';
- t['\u03F4'] = '\u0398';
- t['\u03F5'] = '\u03B5';
- t['\u03F9'] = '\u03A3';
- t['\u0587'] = '\u0565\u0582';
- t['\u0675'] = '\u0627\u0674';
- t['\u0676'] = '\u0648\u0674';
- t['\u0677'] = '\u06C7\u0674';
- t['\u0678'] = '\u064A\u0674';
- t['\u0E33'] = '\u0E4D\u0E32';
- t['\u0EB3'] = '\u0ECD\u0EB2';
- t['\u0EDC'] = '\u0EAB\u0E99';
- t['\u0EDD'] = '\u0EAB\u0EA1';
- t['\u0F77'] = '\u0FB2\u0F81';
- t['\u0F79'] = '\u0FB3\u0F81';
- t['\u1E9A'] = '\u0061\u02BE';
- t['\u1FBD'] = '\u0020\u0313';
- t['\u1FBF'] = '\u0020\u0313';
- t['\u1FC0'] = '\u0020\u0342';
- t['\u1FFE'] = '\u0020\u0314';
- t['\u2002'] = '\u0020';
- t['\u2003'] = '\u0020';
- t['\u2004'] = '\u0020';
- t['\u2005'] = '\u0020';
- t['\u2006'] = '\u0020';
- t['\u2008'] = '\u0020';
- t['\u2009'] = '\u0020';
- t['\u200A'] = '\u0020';
- t['\u2017'] = '\u0020\u0333';
- t['\u2024'] = '\u002E';
- t['\u2025'] = '\u002E\u002E';
- t['\u2026'] = '\u002E\u002E\u002E';
- t['\u2033'] = '\u2032\u2032';
- t['\u2034'] = '\u2032\u2032\u2032';
- t['\u2036'] = '\u2035\u2035';
- t['\u2037'] = '\u2035\u2035\u2035';
- t['\u203C'] = '\u0021\u0021';
- t['\u203E'] = '\u0020\u0305';
- t['\u2047'] = '\u003F\u003F';
- t['\u2048'] = '\u003F\u0021';
- t['\u2049'] = '\u0021\u003F';
- t['\u2057'] = '\u2032\u2032\u2032\u2032';
- t['\u205F'] = '\u0020';
- t['\u20A8'] = '\u0052\u0073';
- t['\u2100'] = '\u0061\u002F\u0063';
- t['\u2101'] = '\u0061\u002F\u0073';
- t['\u2103'] = '\u00B0\u0043';
- t['\u2105'] = '\u0063\u002F\u006F';
- t['\u2106'] = '\u0063\u002F\u0075';
- t['\u2107'] = '\u0190';
- t['\u2109'] = '\u00B0\u0046';
- t['\u2116'] = '\u004E\u006F';
- t['\u2121'] = '\u0054\u0045\u004C';
- t['\u2135'] = '\u05D0';
- t['\u2136'] = '\u05D1';
- t['\u2137'] = '\u05D2';
- t['\u2138'] = '\u05D3';
- t['\u213B'] = '\u0046\u0041\u0058';
- t['\u2160'] = '\u0049';
- t['\u2161'] = '\u0049\u0049';
- t['\u2162'] = '\u0049\u0049\u0049';
- t['\u2163'] = '\u0049\u0056';
- t['\u2164'] = '\u0056';
- t['\u2165'] = '\u0056\u0049';
- t['\u2166'] = '\u0056\u0049\u0049';
- t['\u2167'] = '\u0056\u0049\u0049\u0049';
- t['\u2168'] = '\u0049\u0058';
- t['\u2169'] = '\u0058';
- t['\u216A'] = '\u0058\u0049';
- t['\u216B'] = '\u0058\u0049\u0049';
- t['\u216C'] = '\u004C';
- t['\u216D'] = '\u0043';
- t['\u216E'] = '\u0044';
- t['\u216F'] = '\u004D';
- t['\u2170'] = '\u0069';
- t['\u2171'] = '\u0069\u0069';
- t['\u2172'] = '\u0069\u0069\u0069';
- t['\u2173'] = '\u0069\u0076';
- t['\u2174'] = '\u0076';
- t['\u2175'] = '\u0076\u0069';
- t['\u2176'] = '\u0076\u0069\u0069';
- t['\u2177'] = '\u0076\u0069\u0069\u0069';
- t['\u2178'] = '\u0069\u0078';
- t['\u2179'] = '\u0078';
- t['\u217A'] = '\u0078\u0069';
- t['\u217B'] = '\u0078\u0069\u0069';
- t['\u217C'] = '\u006C';
- t['\u217D'] = '\u0063';
- t['\u217E'] = '\u0064';
- t['\u217F'] = '\u006D';
- t['\u222C'] = '\u222B\u222B';
- t['\u222D'] = '\u222B\u222B\u222B';
- t['\u222F'] = '\u222E\u222E';
- t['\u2230'] = '\u222E\u222E\u222E';
- t['\u2474'] = '\u0028\u0031\u0029';
- t['\u2475'] = '\u0028\u0032\u0029';
- t['\u2476'] = '\u0028\u0033\u0029';
- t['\u2477'] = '\u0028\u0034\u0029';
- t['\u2478'] = '\u0028\u0035\u0029';
- t['\u2479'] = '\u0028\u0036\u0029';
- t['\u247A'] = '\u0028\u0037\u0029';
- t['\u247B'] = '\u0028\u0038\u0029';
- t['\u247C'] = '\u0028\u0039\u0029';
- t['\u247D'] = '\u0028\u0031\u0030\u0029';
- t['\u247E'] = '\u0028\u0031\u0031\u0029';
- t['\u247F'] = '\u0028\u0031\u0032\u0029';
- t['\u2480'] = '\u0028\u0031\u0033\u0029';
- t['\u2481'] = '\u0028\u0031\u0034\u0029';
- t['\u2482'] = '\u0028\u0031\u0035\u0029';
- t['\u2483'] = '\u0028\u0031\u0036\u0029';
- t['\u2484'] = '\u0028\u0031\u0037\u0029';
- t['\u2485'] = '\u0028\u0031\u0038\u0029';
- t['\u2486'] = '\u0028\u0031\u0039\u0029';
- t['\u2487'] = '\u0028\u0032\u0030\u0029';
- t['\u2488'] = '\u0031\u002E';
- t['\u2489'] = '\u0032\u002E';
- t['\u248A'] = '\u0033\u002E';
- t['\u248B'] = '\u0034\u002E';
- t['\u248C'] = '\u0035\u002E';
- t['\u248D'] = '\u0036\u002E';
- t['\u248E'] = '\u0037\u002E';
- t['\u248F'] = '\u0038\u002E';
- t['\u2490'] = '\u0039\u002E';
- t['\u2491'] = '\u0031\u0030\u002E';
- t['\u2492'] = '\u0031\u0031\u002E';
- t['\u2493'] = '\u0031\u0032\u002E';
- t['\u2494'] = '\u0031\u0033\u002E';
- t['\u2495'] = '\u0031\u0034\u002E';
- t['\u2496'] = '\u0031\u0035\u002E';
- t['\u2497'] = '\u0031\u0036\u002E';
- t['\u2498'] = '\u0031\u0037\u002E';
- t['\u2499'] = '\u0031\u0038\u002E';
- t['\u249A'] = '\u0031\u0039\u002E';
- t['\u249B'] = '\u0032\u0030\u002E';
- t['\u249C'] = '\u0028\u0061\u0029';
- t['\u249D'] = '\u0028\u0062\u0029';
- t['\u249E'] = '\u0028\u0063\u0029';
- t['\u249F'] = '\u0028\u0064\u0029';
- t['\u24A0'] = '\u0028\u0065\u0029';
- t['\u24A1'] = '\u0028\u0066\u0029';
- t['\u24A2'] = '\u0028\u0067\u0029';
- t['\u24A3'] = '\u0028\u0068\u0029';
- t['\u24A4'] = '\u0028\u0069\u0029';
- t['\u24A5'] = '\u0028\u006A\u0029';
- t['\u24A6'] = '\u0028\u006B\u0029';
- t['\u24A7'] = '\u0028\u006C\u0029';
- t['\u24A8'] = '\u0028\u006D\u0029';
- t['\u24A9'] = '\u0028\u006E\u0029';
- t['\u24AA'] = '\u0028\u006F\u0029';
- t['\u24AB'] = '\u0028\u0070\u0029';
- t['\u24AC'] = '\u0028\u0071\u0029';
- t['\u24AD'] = '\u0028\u0072\u0029';
- t['\u24AE'] = '\u0028\u0073\u0029';
- t['\u24AF'] = '\u0028\u0074\u0029';
- t['\u24B0'] = '\u0028\u0075\u0029';
- t['\u24B1'] = '\u0028\u0076\u0029';
- t['\u24B2'] = '\u0028\u0077\u0029';
- t['\u24B3'] = '\u0028\u0078\u0029';
- t['\u24B4'] = '\u0028\u0079\u0029';
- t['\u24B5'] = '\u0028\u007A\u0029';
- t['\u2A0C'] = '\u222B\u222B\u222B\u222B';
- t['\u2A74'] = '\u003A\u003A\u003D';
- t['\u2A75'] = '\u003D\u003D';
- t['\u2A76'] = '\u003D\u003D\u003D';
- t['\u2E9F'] = '\u6BCD';
- t['\u2EF3'] = '\u9F9F';
- t['\u2F00'] = '\u4E00';
- t['\u2F01'] = '\u4E28';
- t['\u2F02'] = '\u4E36';
- t['\u2F03'] = '\u4E3F';
- t['\u2F04'] = '\u4E59';
- t['\u2F05'] = '\u4E85';
- t['\u2F06'] = '\u4E8C';
- t['\u2F07'] = '\u4EA0';
- t['\u2F08'] = '\u4EBA';
- t['\u2F09'] = '\u513F';
- t['\u2F0A'] = '\u5165';
- t['\u2F0B'] = '\u516B';
- t['\u2F0C'] = '\u5182';
- t['\u2F0D'] = '\u5196';
- t['\u2F0E'] = '\u51AB';
- t['\u2F0F'] = '\u51E0';
- t['\u2F10'] = '\u51F5';
- t['\u2F11'] = '\u5200';
- t['\u2F12'] = '\u529B';
- t['\u2F13'] = '\u52F9';
- t['\u2F14'] = '\u5315';
- t['\u2F15'] = '\u531A';
- t['\u2F16'] = '\u5338';
- t['\u2F17'] = '\u5341';
- t['\u2F18'] = '\u535C';
- t['\u2F19'] = '\u5369';
- t['\u2F1A'] = '\u5382';
- t['\u2F1B'] = '\u53B6';
- t['\u2F1C'] = '\u53C8';
- t['\u2F1D'] = '\u53E3';
- t['\u2F1E'] = '\u56D7';
- t['\u2F1F'] = '\u571F';
- t['\u2F20'] = '\u58EB';
- t['\u2F21'] = '\u5902';
- t['\u2F22'] = '\u590A';
- t['\u2F23'] = '\u5915';
- t['\u2F24'] = '\u5927';
- t['\u2F25'] = '\u5973';
- t['\u2F26'] = '\u5B50';
- t['\u2F27'] = '\u5B80';
- t['\u2F28'] = '\u5BF8';
- t['\u2F29'] = '\u5C0F';
- t['\u2F2A'] = '\u5C22';
- t['\u2F2B'] = '\u5C38';
- t['\u2F2C'] = '\u5C6E';
- t['\u2F2D'] = '\u5C71';
- t['\u2F2E'] = '\u5DDB';
- t['\u2F2F'] = '\u5DE5';
- t['\u2F30'] = '\u5DF1';
- t['\u2F31'] = '\u5DFE';
- t['\u2F32'] = '\u5E72';
- t['\u2F33'] = '\u5E7A';
- t['\u2F34'] = '\u5E7F';
- t['\u2F35'] = '\u5EF4';
- t['\u2F36'] = '\u5EFE';
- t['\u2F37'] = '\u5F0B';
- t['\u2F38'] = '\u5F13';
- t['\u2F39'] = '\u5F50';
- t['\u2F3A'] = '\u5F61';
- t['\u2F3B'] = '\u5F73';
- t['\u2F3C'] = '\u5FC3';
- t['\u2F3D'] = '\u6208';
- t['\u2F3E'] = '\u6236';
- t['\u2F3F'] = '\u624B';
- t['\u2F40'] = '\u652F';
- t['\u2F41'] = '\u6534';
- t['\u2F42'] = '\u6587';
- t['\u2F43'] = '\u6597';
- t['\u2F44'] = '\u65A4';
- t['\u2F45'] = '\u65B9';
- t['\u2F46'] = '\u65E0';
- t['\u2F47'] = '\u65E5';
- t['\u2F48'] = '\u66F0';
- t['\u2F49'] = '\u6708';
- t['\u2F4A'] = '\u6728';
- t['\u2F4B'] = '\u6B20';
- t['\u2F4C'] = '\u6B62';
- t['\u2F4D'] = '\u6B79';
- t['\u2F4E'] = '\u6BB3';
- t['\u2F4F'] = '\u6BCB';
- t['\u2F50'] = '\u6BD4';
- t['\u2F51'] = '\u6BDB';
- t['\u2F52'] = '\u6C0F';
- t['\u2F53'] = '\u6C14';
- t['\u2F54'] = '\u6C34';
- t['\u2F55'] = '\u706B';
- t['\u2F56'] = '\u722A';
- t['\u2F57'] = '\u7236';
- t['\u2F58'] = '\u723B';
- t['\u2F59'] = '\u723F';
- t['\u2F5A'] = '\u7247';
- t['\u2F5B'] = '\u7259';
- t['\u2F5C'] = '\u725B';
- t['\u2F5D'] = '\u72AC';
- t['\u2F5E'] = '\u7384';
- t['\u2F5F'] = '\u7389';
- t['\u2F60'] = '\u74DC';
- t['\u2F61'] = '\u74E6';
- t['\u2F62'] = '\u7518';
- t['\u2F63'] = '\u751F';
- t['\u2F64'] = '\u7528';
- t['\u2F65'] = '\u7530';
- t['\u2F66'] = '\u758B';
- t['\u2F67'] = '\u7592';
- t['\u2F68'] = '\u7676';
- t['\u2F69'] = '\u767D';
- t['\u2F6A'] = '\u76AE';
- t['\u2F6B'] = '\u76BF';
- t['\u2F6C'] = '\u76EE';
- t['\u2F6D'] = '\u77DB';
- t['\u2F6E'] = '\u77E2';
- t['\u2F6F'] = '\u77F3';
- t['\u2F70'] = '\u793A';
- t['\u2F71'] = '\u79B8';
- t['\u2F72'] = '\u79BE';
- t['\u2F73'] = '\u7A74';
- t['\u2F74'] = '\u7ACB';
- t['\u2F75'] = '\u7AF9';
- t['\u2F76'] = '\u7C73';
- t['\u2F77'] = '\u7CF8';
- t['\u2F78'] = '\u7F36';
- t['\u2F79'] = '\u7F51';
- t['\u2F7A'] = '\u7F8A';
- t['\u2F7B'] = '\u7FBD';
- t['\u2F7C'] = '\u8001';
- t['\u2F7D'] = '\u800C';
- t['\u2F7E'] = '\u8012';
- t['\u2F7F'] = '\u8033';
- t['\u2F80'] = '\u807F';
- t['\u2F81'] = '\u8089';
- t['\u2F82'] = '\u81E3';
- t['\u2F83'] = '\u81EA';
- t['\u2F84'] = '\u81F3';
- t['\u2F85'] = '\u81FC';
- t['\u2F86'] = '\u820C';
- t['\u2F87'] = '\u821B';
- t['\u2F88'] = '\u821F';
- t['\u2F89'] = '\u826E';
- t['\u2F8A'] = '\u8272';
- t['\u2F8B'] = '\u8278';
- t['\u2F8C'] = '\u864D';
- t['\u2F8D'] = '\u866B';
- t['\u2F8E'] = '\u8840';
- t['\u2F8F'] = '\u884C';
- t['\u2F90'] = '\u8863';
- t['\u2F91'] = '\u897E';
- t['\u2F92'] = '\u898B';
- t['\u2F93'] = '\u89D2';
- t['\u2F94'] = '\u8A00';
- t['\u2F95'] = '\u8C37';
- t['\u2F96'] = '\u8C46';
- t['\u2F97'] = '\u8C55';
- t['\u2F98'] = '\u8C78';
- t['\u2F99'] = '\u8C9D';
- t['\u2F9A'] = '\u8D64';
- t['\u2F9B'] = '\u8D70';
- t['\u2F9C'] = '\u8DB3';
- t['\u2F9D'] = '\u8EAB';
- t['\u2F9E'] = '\u8ECA';
- t['\u2F9F'] = '\u8F9B';
- t['\u2FA0'] = '\u8FB0';
- t['\u2FA1'] = '\u8FB5';
- t['\u2FA2'] = '\u9091';
- t['\u2FA3'] = '\u9149';
- t['\u2FA4'] = '\u91C6';
- t['\u2FA5'] = '\u91CC';
- t['\u2FA6'] = '\u91D1';
- t['\u2FA7'] = '\u9577';
- t['\u2FA8'] = '\u9580';
- t['\u2FA9'] = '\u961C';
- t['\u2FAA'] = '\u96B6';
- t['\u2FAB'] = '\u96B9';
- t['\u2FAC'] = '\u96E8';
- t['\u2FAD'] = '\u9751';
- t['\u2FAE'] = '\u975E';
- t['\u2FAF'] = '\u9762';
- t['\u2FB0'] = '\u9769';
- t['\u2FB1'] = '\u97CB';
- t['\u2FB2'] = '\u97ED';
- t['\u2FB3'] = '\u97F3';
- t['\u2FB4'] = '\u9801';
- t['\u2FB5'] = '\u98A8';
- t['\u2FB6'] = '\u98DB';
- t['\u2FB7'] = '\u98DF';
- t['\u2FB8'] = '\u9996';
- t['\u2FB9'] = '\u9999';
- t['\u2FBA'] = '\u99AC';
- t['\u2FBB'] = '\u9AA8';
- t['\u2FBC'] = '\u9AD8';
- t['\u2FBD'] = '\u9ADF';
- t['\u2FBE'] = '\u9B25';
- t['\u2FBF'] = '\u9B2F';
- t['\u2FC0'] = '\u9B32';
- t['\u2FC1'] = '\u9B3C';
- t['\u2FC2'] = '\u9B5A';
- t['\u2FC3'] = '\u9CE5';
- t['\u2FC4'] = '\u9E75';
- t['\u2FC5'] = '\u9E7F';
- t['\u2FC6'] = '\u9EA5';
- t['\u2FC7'] = '\u9EBB';
- t['\u2FC8'] = '\u9EC3';
- t['\u2FC9'] = '\u9ECD';
- t['\u2FCA'] = '\u9ED1';
- t['\u2FCB'] = '\u9EF9';
- t['\u2FCC'] = '\u9EFD';
- t['\u2FCD'] = '\u9F0E';
- t['\u2FCE'] = '\u9F13';
- t['\u2FCF'] = '\u9F20';
- t['\u2FD0'] = '\u9F3B';
- t['\u2FD1'] = '\u9F4A';
- t['\u2FD2'] = '\u9F52';
- t['\u2FD3'] = '\u9F8D';
- t['\u2FD4'] = '\u9F9C';
- t['\u2FD5'] = '\u9FA0';
- t['\u3036'] = '\u3012';
- t['\u3038'] = '\u5341';
- t['\u3039'] = '\u5344';
- t['\u303A'] = '\u5345';
- t['\u309B'] = '\u0020\u3099';
- t['\u309C'] = '\u0020\u309A';
- t['\u3131'] = '\u1100';
- t['\u3132'] = '\u1101';
- t['\u3133'] = '\u11AA';
- t['\u3134'] = '\u1102';
- t['\u3135'] = '\u11AC';
- t['\u3136'] = '\u11AD';
- t['\u3137'] = '\u1103';
- t['\u3138'] = '\u1104';
- t['\u3139'] = '\u1105';
- t['\u313A'] = '\u11B0';
- t['\u313B'] = '\u11B1';
- t['\u313C'] = '\u11B2';
- t['\u313D'] = '\u11B3';
- t['\u313E'] = '\u11B4';
- t['\u313F'] = '\u11B5';
- t['\u3140'] = '\u111A';
- t['\u3141'] = '\u1106';
- t['\u3142'] = '\u1107';
- t['\u3143'] = '\u1108';
- t['\u3144'] = '\u1121';
- t['\u3145'] = '\u1109';
- t['\u3146'] = '\u110A';
- t['\u3147'] = '\u110B';
- t['\u3148'] = '\u110C';
- t['\u3149'] = '\u110D';
- t['\u314A'] = '\u110E';
- t['\u314B'] = '\u110F';
- t['\u314C'] = '\u1110';
- t['\u314D'] = '\u1111';
- t['\u314E'] = '\u1112';
- t['\u314F'] = '\u1161';
- t['\u3150'] = '\u1162';
- t['\u3151'] = '\u1163';
- t['\u3152'] = '\u1164';
- t['\u3153'] = '\u1165';
- t['\u3154'] = '\u1166';
- t['\u3155'] = '\u1167';
- t['\u3156'] = '\u1168';
- t['\u3157'] = '\u1169';
- t['\u3158'] = '\u116A';
- t['\u3159'] = '\u116B';
- t['\u315A'] = '\u116C';
- t['\u315B'] = '\u116D';
- t['\u315C'] = '\u116E';
- t['\u315D'] = '\u116F';
- t['\u315E'] = '\u1170';
- t['\u315F'] = '\u1171';
- t['\u3160'] = '\u1172';
- t['\u3161'] = '\u1173';
- t['\u3162'] = '\u1174';
- t['\u3163'] = '\u1175';
- t['\u3164'] = '\u1160';
- t['\u3165'] = '\u1114';
- t['\u3166'] = '\u1115';
- t['\u3167'] = '\u11C7';
- t['\u3168'] = '\u11C8';
- t['\u3169'] = '\u11CC';
- t['\u316A'] = '\u11CE';
- t['\u316B'] = '\u11D3';
- t['\u316C'] = '\u11D7';
- t['\u316D'] = '\u11D9';
- t['\u316E'] = '\u111C';
- t['\u316F'] = '\u11DD';
- t['\u3170'] = '\u11DF';
- t['\u3171'] = '\u111D';
- t['\u3172'] = '\u111E';
- t['\u3173'] = '\u1120';
- t['\u3174'] = '\u1122';
- t['\u3175'] = '\u1123';
- t['\u3176'] = '\u1127';
- t['\u3177'] = '\u1129';
- t['\u3178'] = '\u112B';
- t['\u3179'] = '\u112C';
- t['\u317A'] = '\u112D';
- t['\u317B'] = '\u112E';
- t['\u317C'] = '\u112F';
- t['\u317D'] = '\u1132';
- t['\u317E'] = '\u1136';
- t['\u317F'] = '\u1140';
- t['\u3180'] = '\u1147';
- t['\u3181'] = '\u114C';
- t['\u3182'] = '\u11F1';
- t['\u3183'] = '\u11F2';
- t['\u3184'] = '\u1157';
- t['\u3185'] = '\u1158';
- t['\u3186'] = '\u1159';
- t['\u3187'] = '\u1184';
- t['\u3188'] = '\u1185';
- t['\u3189'] = '\u1188';
- t['\u318A'] = '\u1191';
- t['\u318B'] = '\u1192';
- t['\u318C'] = '\u1194';
- t['\u318D'] = '\u119E';
- t['\u318E'] = '\u11A1';
- t['\u3200'] = '\u0028\u1100\u0029';
- t['\u3201'] = '\u0028\u1102\u0029';
- t['\u3202'] = '\u0028\u1103\u0029';
- t['\u3203'] = '\u0028\u1105\u0029';
- t['\u3204'] = '\u0028\u1106\u0029';
- t['\u3205'] = '\u0028\u1107\u0029';
- t['\u3206'] = '\u0028\u1109\u0029';
- t['\u3207'] = '\u0028\u110B\u0029';
- t['\u3208'] = '\u0028\u110C\u0029';
- t['\u3209'] = '\u0028\u110E\u0029';
- t['\u320A'] = '\u0028\u110F\u0029';
- t['\u320B'] = '\u0028\u1110\u0029';
- t['\u320C'] = '\u0028\u1111\u0029';
- t['\u320D'] = '\u0028\u1112\u0029';
- t['\u320E'] = '\u0028\u1100\u1161\u0029';
- t['\u320F'] = '\u0028\u1102\u1161\u0029';
- t['\u3210'] = '\u0028\u1103\u1161\u0029';
- t['\u3211'] = '\u0028\u1105\u1161\u0029';
- t['\u3212'] = '\u0028\u1106\u1161\u0029';
- t['\u3213'] = '\u0028\u1107\u1161\u0029';
- t['\u3214'] = '\u0028\u1109\u1161\u0029';
- t['\u3215'] = '\u0028\u110B\u1161\u0029';
- t['\u3216'] = '\u0028\u110C\u1161\u0029';
- t['\u3217'] = '\u0028\u110E\u1161\u0029';
- t['\u3218'] = '\u0028\u110F\u1161\u0029';
- t['\u3219'] = '\u0028\u1110\u1161\u0029';
- t['\u321A'] = '\u0028\u1111\u1161\u0029';
- t['\u321B'] = '\u0028\u1112\u1161\u0029';
- t['\u321C'] = '\u0028\u110C\u116E\u0029';
- t['\u321D'] = '\u0028\u110B\u1169\u110C\u1165\u11AB\u0029';
- t['\u321E'] = '\u0028\u110B\u1169\u1112\u116E\u0029';
- t['\u3220'] = '\u0028\u4E00\u0029';
- t['\u3221'] = '\u0028\u4E8C\u0029';
- t['\u3222'] = '\u0028\u4E09\u0029';
- t['\u3223'] = '\u0028\u56DB\u0029';
- t['\u3224'] = '\u0028\u4E94\u0029';
- t['\u3225'] = '\u0028\u516D\u0029';
- t['\u3226'] = '\u0028\u4E03\u0029';
- t['\u3227'] = '\u0028\u516B\u0029';
- t['\u3228'] = '\u0028\u4E5D\u0029';
- t['\u3229'] = '\u0028\u5341\u0029';
- t['\u322A'] = '\u0028\u6708\u0029';
- t['\u322B'] = '\u0028\u706B\u0029';
- t['\u322C'] = '\u0028\u6C34\u0029';
- t['\u322D'] = '\u0028\u6728\u0029';
- t['\u322E'] = '\u0028\u91D1\u0029';
- t['\u322F'] = '\u0028\u571F\u0029';
- t['\u3230'] = '\u0028\u65E5\u0029';
- t['\u3231'] = '\u0028\u682A\u0029';
- t['\u3232'] = '\u0028\u6709\u0029';
- t['\u3233'] = '\u0028\u793E\u0029';
- t['\u3234'] = '\u0028\u540D\u0029';
- t['\u3235'] = '\u0028\u7279\u0029';
- t['\u3236'] = '\u0028\u8CA1\u0029';
- t['\u3237'] = '\u0028\u795D\u0029';
- t['\u3238'] = '\u0028\u52B4\u0029';
- t['\u3239'] = '\u0028\u4EE3\u0029';
- t['\u323A'] = '\u0028\u547C\u0029';
- t['\u323B'] = '\u0028\u5B66\u0029';
- t['\u323C'] = '\u0028\u76E3\u0029';
- t['\u323D'] = '\u0028\u4F01\u0029';
- t['\u323E'] = '\u0028\u8CC7\u0029';
- t['\u323F'] = '\u0028\u5354\u0029';
- t['\u3240'] = '\u0028\u796D\u0029';
- t['\u3241'] = '\u0028\u4F11\u0029';
- t['\u3242'] = '\u0028\u81EA\u0029';
- t['\u3243'] = '\u0028\u81F3\u0029';
- t['\u32C0'] = '\u0031\u6708';
- t['\u32C1'] = '\u0032\u6708';
- t['\u32C2'] = '\u0033\u6708';
- t['\u32C3'] = '\u0034\u6708';
- t['\u32C4'] = '\u0035\u6708';
- t['\u32C5'] = '\u0036\u6708';
- t['\u32C6'] = '\u0037\u6708';
- t['\u32C7'] = '\u0038\u6708';
- t['\u32C8'] = '\u0039\u6708';
- t['\u32C9'] = '\u0031\u0030\u6708';
- t['\u32CA'] = '\u0031\u0031\u6708';
- t['\u32CB'] = '\u0031\u0032\u6708';
- t['\u3358'] = '\u0030\u70B9';
- t['\u3359'] = '\u0031\u70B9';
- t['\u335A'] = '\u0032\u70B9';
- t['\u335B'] = '\u0033\u70B9';
- t['\u335C'] = '\u0034\u70B9';
- t['\u335D'] = '\u0035\u70B9';
- t['\u335E'] = '\u0036\u70B9';
- t['\u335F'] = '\u0037\u70B9';
- t['\u3360'] = '\u0038\u70B9';
- t['\u3361'] = '\u0039\u70B9';
- t['\u3362'] = '\u0031\u0030\u70B9';
- t['\u3363'] = '\u0031\u0031\u70B9';
- t['\u3364'] = '\u0031\u0032\u70B9';
- t['\u3365'] = '\u0031\u0033\u70B9';
- t['\u3366'] = '\u0031\u0034\u70B9';
- t['\u3367'] = '\u0031\u0035\u70B9';
- t['\u3368'] = '\u0031\u0036\u70B9';
- t['\u3369'] = '\u0031\u0037\u70B9';
- t['\u336A'] = '\u0031\u0038\u70B9';
- t['\u336B'] = '\u0031\u0039\u70B9';
- t['\u336C'] = '\u0032\u0030\u70B9';
- t['\u336D'] = '\u0032\u0031\u70B9';
- t['\u336E'] = '\u0032\u0032\u70B9';
- t['\u336F'] = '\u0032\u0033\u70B9';
- t['\u3370'] = '\u0032\u0034\u70B9';
- t['\u33E0'] = '\u0031\u65E5';
- t['\u33E1'] = '\u0032\u65E5';
- t['\u33E2'] = '\u0033\u65E5';
- t['\u33E3'] = '\u0034\u65E5';
- t['\u33E4'] = '\u0035\u65E5';
- t['\u33E5'] = '\u0036\u65E5';
- t['\u33E6'] = '\u0037\u65E5';
- t['\u33E7'] = '\u0038\u65E5';
- t['\u33E8'] = '\u0039\u65E5';
- t['\u33E9'] = '\u0031\u0030\u65E5';
- t['\u33EA'] = '\u0031\u0031\u65E5';
- t['\u33EB'] = '\u0031\u0032\u65E5';
- t['\u33EC'] = '\u0031\u0033\u65E5';
- t['\u33ED'] = '\u0031\u0034\u65E5';
- t['\u33EE'] = '\u0031\u0035\u65E5';
- t['\u33EF'] = '\u0031\u0036\u65E5';
- t['\u33F0'] = '\u0031\u0037\u65E5';
- t['\u33F1'] = '\u0031\u0038\u65E5';
- t['\u33F2'] = '\u0031\u0039\u65E5';
- t['\u33F3'] = '\u0032\u0030\u65E5';
- t['\u33F4'] = '\u0032\u0031\u65E5';
- t['\u33F5'] = '\u0032\u0032\u65E5';
- t['\u33F6'] = '\u0032\u0033\u65E5';
- t['\u33F7'] = '\u0032\u0034\u65E5';
- t['\u33F8'] = '\u0032\u0035\u65E5';
- t['\u33F9'] = '\u0032\u0036\u65E5';
- t['\u33FA'] = '\u0032\u0037\u65E5';
- t['\u33FB'] = '\u0032\u0038\u65E5';
- t['\u33FC'] = '\u0032\u0039\u65E5';
- t['\u33FD'] = '\u0033\u0030\u65E5';
- t['\u33FE'] = '\u0033\u0031\u65E5';
- t['\uFB00'] = '\u0066\u0066';
- t['\uFB01'] = '\u0066\u0069';
- t['\uFB02'] = '\u0066\u006C';
- t['\uFB03'] = '\u0066\u0066\u0069';
- t['\uFB04'] = '\u0066\u0066\u006C';
- t['\uFB05'] = '\u017F\u0074';
- t['\uFB06'] = '\u0073\u0074';
- t['\uFB13'] = '\u0574\u0576';
- t['\uFB14'] = '\u0574\u0565';
- t['\uFB15'] = '\u0574\u056B';
- t['\uFB16'] = '\u057E\u0576';
- t['\uFB17'] = '\u0574\u056D';
- t['\uFB4F'] = '\u05D0\u05DC';
- t['\uFB50'] = '\u0671';
- t['\uFB51'] = '\u0671';
- t['\uFB52'] = '\u067B';
- t['\uFB53'] = '\u067B';
- t['\uFB54'] = '\u067B';
- t['\uFB55'] = '\u067B';
- t['\uFB56'] = '\u067E';
- t['\uFB57'] = '\u067E';
- t['\uFB58'] = '\u067E';
- t['\uFB59'] = '\u067E';
- t['\uFB5A'] = '\u0680';
- t['\uFB5B'] = '\u0680';
- t['\uFB5C'] = '\u0680';
- t['\uFB5D'] = '\u0680';
- t['\uFB5E'] = '\u067A';
- t['\uFB5F'] = '\u067A';
- t['\uFB60'] = '\u067A';
- t['\uFB61'] = '\u067A';
- t['\uFB62'] = '\u067F';
- t['\uFB63'] = '\u067F';
- t['\uFB64'] = '\u067F';
- t['\uFB65'] = '\u067F';
- t['\uFB66'] = '\u0679';
- t['\uFB67'] = '\u0679';
- t['\uFB68'] = '\u0679';
- t['\uFB69'] = '\u0679';
- t['\uFB6A'] = '\u06A4';
- t['\uFB6B'] = '\u06A4';
- t['\uFB6C'] = '\u06A4';
- t['\uFB6D'] = '\u06A4';
- t['\uFB6E'] = '\u06A6';
- t['\uFB6F'] = '\u06A6';
- t['\uFB70'] = '\u06A6';
- t['\uFB71'] = '\u06A6';
- t['\uFB72'] = '\u0684';
- t['\uFB73'] = '\u0684';
- t['\uFB74'] = '\u0684';
- t['\uFB75'] = '\u0684';
- t['\uFB76'] = '\u0683';
- t['\uFB77'] = '\u0683';
- t['\uFB78'] = '\u0683';
- t['\uFB79'] = '\u0683';
- t['\uFB7A'] = '\u0686';
- t['\uFB7B'] = '\u0686';
- t['\uFB7C'] = '\u0686';
- t['\uFB7D'] = '\u0686';
- t['\uFB7E'] = '\u0687';
- t['\uFB7F'] = '\u0687';
- t['\uFB80'] = '\u0687';
- t['\uFB81'] = '\u0687';
- t['\uFB82'] = '\u068D';
- t['\uFB83'] = '\u068D';
- t['\uFB84'] = '\u068C';
- t['\uFB85'] = '\u068C';
- t['\uFB86'] = '\u068E';
- t['\uFB87'] = '\u068E';
- t['\uFB88'] = '\u0688';
- t['\uFB89'] = '\u0688';
- t['\uFB8A'] = '\u0698';
- t['\uFB8B'] = '\u0698';
- t['\uFB8C'] = '\u0691';
- t['\uFB8D'] = '\u0691';
- t['\uFB8E'] = '\u06A9';
- t['\uFB8F'] = '\u06A9';
- t['\uFB90'] = '\u06A9';
- t['\uFB91'] = '\u06A9';
- t['\uFB92'] = '\u06AF';
- t['\uFB93'] = '\u06AF';
- t['\uFB94'] = '\u06AF';
- t['\uFB95'] = '\u06AF';
- t['\uFB96'] = '\u06B3';
- t['\uFB97'] = '\u06B3';
- t['\uFB98'] = '\u06B3';
- t['\uFB99'] = '\u06B3';
- t['\uFB9A'] = '\u06B1';
- t['\uFB9B'] = '\u06B1';
- t['\uFB9C'] = '\u06B1';
- t['\uFB9D'] = '\u06B1';
- t['\uFB9E'] = '\u06BA';
- t['\uFB9F'] = '\u06BA';
- t['\uFBA0'] = '\u06BB';
- t['\uFBA1'] = '\u06BB';
- t['\uFBA2'] = '\u06BB';
- t['\uFBA3'] = '\u06BB';
- t['\uFBA4'] = '\u06C0';
- t['\uFBA5'] = '\u06C0';
- t['\uFBA6'] = '\u06C1';
- t['\uFBA7'] = '\u06C1';
- t['\uFBA8'] = '\u06C1';
- t['\uFBA9'] = '\u06C1';
- t['\uFBAA'] = '\u06BE';
- t['\uFBAB'] = '\u06BE';
- t['\uFBAC'] = '\u06BE';
- t['\uFBAD'] = '\u06BE';
- t['\uFBAE'] = '\u06D2';
- t['\uFBAF'] = '\u06D2';
- t['\uFBB0'] = '\u06D3';
- t['\uFBB1'] = '\u06D3';
- t['\uFBD3'] = '\u06AD';
- t['\uFBD4'] = '\u06AD';
- t['\uFBD5'] = '\u06AD';
- t['\uFBD6'] = '\u06AD';
- t['\uFBD7'] = '\u06C7';
- t['\uFBD8'] = '\u06C7';
- t['\uFBD9'] = '\u06C6';
- t['\uFBDA'] = '\u06C6';
- t['\uFBDB'] = '\u06C8';
- t['\uFBDC'] = '\u06C8';
- t['\uFBDD'] = '\u0677';
- t['\uFBDE'] = '\u06CB';
- t['\uFBDF'] = '\u06CB';
- t['\uFBE0'] = '\u06C5';
- t['\uFBE1'] = '\u06C5';
- t['\uFBE2'] = '\u06C9';
- t['\uFBE3'] = '\u06C9';
- t['\uFBE4'] = '\u06D0';
- t['\uFBE5'] = '\u06D0';
- t['\uFBE6'] = '\u06D0';
- t['\uFBE7'] = '\u06D0';
- t['\uFBE8'] = '\u0649';
- t['\uFBE9'] = '\u0649';
- t['\uFBEA'] = '\u0626\u0627';
- t['\uFBEB'] = '\u0626\u0627';
- t['\uFBEC'] = '\u0626\u06D5';
- t['\uFBED'] = '\u0626\u06D5';
- t['\uFBEE'] = '\u0626\u0648';
- t['\uFBEF'] = '\u0626\u0648';
- t['\uFBF0'] = '\u0626\u06C7';
- t['\uFBF1'] = '\u0626\u06C7';
- t['\uFBF2'] = '\u0626\u06C6';
- t['\uFBF3'] = '\u0626\u06C6';
- t['\uFBF4'] = '\u0626\u06C8';
- t['\uFBF5'] = '\u0626\u06C8';
- t['\uFBF6'] = '\u0626\u06D0';
- t['\uFBF7'] = '\u0626\u06D0';
- t['\uFBF8'] = '\u0626\u06D0';
- t['\uFBF9'] = '\u0626\u0649';
- t['\uFBFA'] = '\u0626\u0649';
- t['\uFBFB'] = '\u0626\u0649';
- t['\uFBFC'] = '\u06CC';
- t['\uFBFD'] = '\u06CC';
- t['\uFBFE'] = '\u06CC';
- t['\uFBFF'] = '\u06CC';
- t['\uFC00'] = '\u0626\u062C';
- t['\uFC01'] = '\u0626\u062D';
- t['\uFC02'] = '\u0626\u0645';
- t['\uFC03'] = '\u0626\u0649';
- t['\uFC04'] = '\u0626\u064A';
- t['\uFC05'] = '\u0628\u062C';
- t['\uFC06'] = '\u0628\u062D';
- t['\uFC07'] = '\u0628\u062E';
- t['\uFC08'] = '\u0628\u0645';
- t['\uFC09'] = '\u0628\u0649';
- t['\uFC0A'] = '\u0628\u064A';
- t['\uFC0B'] = '\u062A\u062C';
- t['\uFC0C'] = '\u062A\u062D';
- t['\uFC0D'] = '\u062A\u062E';
- t['\uFC0E'] = '\u062A\u0645';
- t['\uFC0F'] = '\u062A\u0649';
- t['\uFC10'] = '\u062A\u064A';
- t['\uFC11'] = '\u062B\u062C';
- t['\uFC12'] = '\u062B\u0645';
- t['\uFC13'] = '\u062B\u0649';
- t['\uFC14'] = '\u062B\u064A';
- t['\uFC15'] = '\u062C\u062D';
- t['\uFC16'] = '\u062C\u0645';
- t['\uFC17'] = '\u062D\u062C';
- t['\uFC18'] = '\u062D\u0645';
- t['\uFC19'] = '\u062E\u062C';
- t['\uFC1A'] = '\u062E\u062D';
- t['\uFC1B'] = '\u062E\u0645';
- t['\uFC1C'] = '\u0633\u062C';
- t['\uFC1D'] = '\u0633\u062D';
- t['\uFC1E'] = '\u0633\u062E';
- t['\uFC1F'] = '\u0633\u0645';
- t['\uFC20'] = '\u0635\u062D';
- t['\uFC21'] = '\u0635\u0645';
- t['\uFC22'] = '\u0636\u062C';
- t['\uFC23'] = '\u0636\u062D';
- t['\uFC24'] = '\u0636\u062E';
- t['\uFC25'] = '\u0636\u0645';
- t['\uFC26'] = '\u0637\u062D';
- t['\uFC27'] = '\u0637\u0645';
- t['\uFC28'] = '\u0638\u0645';
- t['\uFC29'] = '\u0639\u062C';
- t['\uFC2A'] = '\u0639\u0645';
- t['\uFC2B'] = '\u063A\u062C';
- t['\uFC2C'] = '\u063A\u0645';
- t['\uFC2D'] = '\u0641\u062C';
- t['\uFC2E'] = '\u0641\u062D';
- t['\uFC2F'] = '\u0641\u062E';
- t['\uFC30'] = '\u0641\u0645';
- t['\uFC31'] = '\u0641\u0649';
- t['\uFC32'] = '\u0641\u064A';
- t['\uFC33'] = '\u0642\u062D';
- t['\uFC34'] = '\u0642\u0645';
- t['\uFC35'] = '\u0642\u0649';
- t['\uFC36'] = '\u0642\u064A';
- t['\uFC37'] = '\u0643\u0627';
- t['\uFC38'] = '\u0643\u062C';
- t['\uFC39'] = '\u0643\u062D';
- t['\uFC3A'] = '\u0643\u062E';
- t['\uFC3B'] = '\u0643\u0644';
- t['\uFC3C'] = '\u0643\u0645';
- t['\uFC3D'] = '\u0643\u0649';
- t['\uFC3E'] = '\u0643\u064A';
- t['\uFC3F'] = '\u0644\u062C';
- t['\uFC40'] = '\u0644\u062D';
- t['\uFC41'] = '\u0644\u062E';
- t['\uFC42'] = '\u0644\u0645';
- t['\uFC43'] = '\u0644\u0649';
- t['\uFC44'] = '\u0644\u064A';
- t['\uFC45'] = '\u0645\u062C';
- t['\uFC46'] = '\u0645\u062D';
- t['\uFC47'] = '\u0645\u062E';
- t['\uFC48'] = '\u0645\u0645';
- t['\uFC49'] = '\u0645\u0649';
- t['\uFC4A'] = '\u0645\u064A';
- t['\uFC4B'] = '\u0646\u062C';
- t['\uFC4C'] = '\u0646\u062D';
- t['\uFC4D'] = '\u0646\u062E';
- t['\uFC4E'] = '\u0646\u0645';
- t['\uFC4F'] = '\u0646\u0649';
- t['\uFC50'] = '\u0646\u064A';
- t['\uFC51'] = '\u0647\u062C';
- t['\uFC52'] = '\u0647\u0645';
- t['\uFC53'] = '\u0647\u0649';
- t['\uFC54'] = '\u0647\u064A';
- t['\uFC55'] = '\u064A\u062C';
- t['\uFC56'] = '\u064A\u062D';
- t['\uFC57'] = '\u064A\u062E';
- t['\uFC58'] = '\u064A\u0645';
- t['\uFC59'] = '\u064A\u0649';
- t['\uFC5A'] = '\u064A\u064A';
- t['\uFC5B'] = '\u0630\u0670';
- t['\uFC5C'] = '\u0631\u0670';
- t['\uFC5D'] = '\u0649\u0670';
- t['\uFC5E'] = '\u0020\u064C\u0651';
- t['\uFC5F'] = '\u0020\u064D\u0651';
- t['\uFC60'] = '\u0020\u064E\u0651';
- t['\uFC61'] = '\u0020\u064F\u0651';
- t['\uFC62'] = '\u0020\u0650\u0651';
- t['\uFC63'] = '\u0020\u0651\u0670';
- t['\uFC64'] = '\u0626\u0631';
- t['\uFC65'] = '\u0626\u0632';
- t['\uFC66'] = '\u0626\u0645';
- t['\uFC67'] = '\u0626\u0646';
- t['\uFC68'] = '\u0626\u0649';
- t['\uFC69'] = '\u0626\u064A';
- t['\uFC6A'] = '\u0628\u0631';
- t['\uFC6B'] = '\u0628\u0632';
- t['\uFC6C'] = '\u0628\u0645';
- t['\uFC6D'] = '\u0628\u0646';
- t['\uFC6E'] = '\u0628\u0649';
- t['\uFC6F'] = '\u0628\u064A';
- t['\uFC70'] = '\u062A\u0631';
- t['\uFC71'] = '\u062A\u0632';
- t['\uFC72'] = '\u062A\u0645';
- t['\uFC73'] = '\u062A\u0646';
- t['\uFC74'] = '\u062A\u0649';
- t['\uFC75'] = '\u062A\u064A';
- t['\uFC76'] = '\u062B\u0631';
- t['\uFC77'] = '\u062B\u0632';
- t['\uFC78'] = '\u062B\u0645';
- t['\uFC79'] = '\u062B\u0646';
- t['\uFC7A'] = '\u062B\u0649';
- t['\uFC7B'] = '\u062B\u064A';
- t['\uFC7C'] = '\u0641\u0649';
- t['\uFC7D'] = '\u0641\u064A';
- t['\uFC7E'] = '\u0642\u0649';
- t['\uFC7F'] = '\u0642\u064A';
- t['\uFC80'] = '\u0643\u0627';
- t['\uFC81'] = '\u0643\u0644';
- t['\uFC82'] = '\u0643\u0645';
- t['\uFC83'] = '\u0643\u0649';
- t['\uFC84'] = '\u0643\u064A';
- t['\uFC85'] = '\u0644\u0645';
- t['\uFC86'] = '\u0644\u0649';
- t['\uFC87'] = '\u0644\u064A';
- t['\uFC88'] = '\u0645\u0627';
- t['\uFC89'] = '\u0645\u0645';
- t['\uFC8A'] = '\u0646\u0631';
- t['\uFC8B'] = '\u0646\u0632';
- t['\uFC8C'] = '\u0646\u0645';
- t['\uFC8D'] = '\u0646\u0646';
- t['\uFC8E'] = '\u0646\u0649';
- t['\uFC8F'] = '\u0646\u064A';
- t['\uFC90'] = '\u0649\u0670';
- t['\uFC91'] = '\u064A\u0631';
- t['\uFC92'] = '\u064A\u0632';
- t['\uFC93'] = '\u064A\u0645';
- t['\uFC94'] = '\u064A\u0646';
- t['\uFC95'] = '\u064A\u0649';
- t['\uFC96'] = '\u064A\u064A';
- t['\uFC97'] = '\u0626\u062C';
- t['\uFC98'] = '\u0626\u062D';
- t['\uFC99'] = '\u0626\u062E';
- t['\uFC9A'] = '\u0626\u0645';
- t['\uFC9B'] = '\u0626\u0647';
- t['\uFC9C'] = '\u0628\u062C';
- t['\uFC9D'] = '\u0628\u062D';
- t['\uFC9E'] = '\u0628\u062E';
- t['\uFC9F'] = '\u0628\u0645';
- t['\uFCA0'] = '\u0628\u0647';
- t['\uFCA1'] = '\u062A\u062C';
- t['\uFCA2'] = '\u062A\u062D';
- t['\uFCA3'] = '\u062A\u062E';
- t['\uFCA4'] = '\u062A\u0645';
- t['\uFCA5'] = '\u062A\u0647';
- t['\uFCA6'] = '\u062B\u0645';
- t['\uFCA7'] = '\u062C\u062D';
- t['\uFCA8'] = '\u062C\u0645';
- t['\uFCA9'] = '\u062D\u062C';
- t['\uFCAA'] = '\u062D\u0645';
- t['\uFCAB'] = '\u062E\u062C';
- t['\uFCAC'] = '\u062E\u0645';
- t['\uFCAD'] = '\u0633\u062C';
- t['\uFCAE'] = '\u0633\u062D';
- t['\uFCAF'] = '\u0633\u062E';
- t['\uFCB0'] = '\u0633\u0645';
- t['\uFCB1'] = '\u0635\u062D';
- t['\uFCB2'] = '\u0635\u062E';
- t['\uFCB3'] = '\u0635\u0645';
- t['\uFCB4'] = '\u0636\u062C';
- t['\uFCB5'] = '\u0636\u062D';
- t['\uFCB6'] = '\u0636\u062E';
- t['\uFCB7'] = '\u0636\u0645';
- t['\uFCB8'] = '\u0637\u062D';
- t['\uFCB9'] = '\u0638\u0645';
- t['\uFCBA'] = '\u0639\u062C';
- t['\uFCBB'] = '\u0639\u0645';
- t['\uFCBC'] = '\u063A\u062C';
- t['\uFCBD'] = '\u063A\u0645';
- t['\uFCBE'] = '\u0641\u062C';
- t['\uFCBF'] = '\u0641\u062D';
- t['\uFCC0'] = '\u0641\u062E';
- t['\uFCC1'] = '\u0641\u0645';
- t['\uFCC2'] = '\u0642\u062D';
- t['\uFCC3'] = '\u0642\u0645';
- t['\uFCC4'] = '\u0643\u062C';
- t['\uFCC5'] = '\u0643\u062D';
- t['\uFCC6'] = '\u0643\u062E';
- t['\uFCC7'] = '\u0643\u0644';
- t['\uFCC8'] = '\u0643\u0645';
- t['\uFCC9'] = '\u0644\u062C';
- t['\uFCCA'] = '\u0644\u062D';
- t['\uFCCB'] = '\u0644\u062E';
- t['\uFCCC'] = '\u0644\u0645';
- t['\uFCCD'] = '\u0644\u0647';
- t['\uFCCE'] = '\u0645\u062C';
- t['\uFCCF'] = '\u0645\u062D';
- t['\uFCD0'] = '\u0645\u062E';
- t['\uFCD1'] = '\u0645\u0645';
- t['\uFCD2'] = '\u0646\u062C';
- t['\uFCD3'] = '\u0646\u062D';
- t['\uFCD4'] = '\u0646\u062E';
- t['\uFCD5'] = '\u0646\u0645';
- t['\uFCD6'] = '\u0646\u0647';
- t['\uFCD7'] = '\u0647\u062C';
- t['\uFCD8'] = '\u0647\u0645';
- t['\uFCD9'] = '\u0647\u0670';
- t['\uFCDA'] = '\u064A\u062C';
- t['\uFCDB'] = '\u064A\u062D';
- t['\uFCDC'] = '\u064A\u062E';
- t['\uFCDD'] = '\u064A\u0645';
- t['\uFCDE'] = '\u064A\u0647';
- t['\uFCDF'] = '\u0626\u0645';
- t['\uFCE0'] = '\u0626\u0647';
- t['\uFCE1'] = '\u0628\u0645';
- t['\uFCE2'] = '\u0628\u0647';
- t['\uFCE3'] = '\u062A\u0645';
- t['\uFCE4'] = '\u062A\u0647';
- t['\uFCE5'] = '\u062B\u0645';
- t['\uFCE6'] = '\u062B\u0647';
- t['\uFCE7'] = '\u0633\u0645';
- t['\uFCE8'] = '\u0633\u0647';
- t['\uFCE9'] = '\u0634\u0645';
- t['\uFCEA'] = '\u0634\u0647';
- t['\uFCEB'] = '\u0643\u0644';
- t['\uFCEC'] = '\u0643\u0645';
- t['\uFCED'] = '\u0644\u0645';
- t['\uFCEE'] = '\u0646\u0645';
- t['\uFCEF'] = '\u0646\u0647';
- t['\uFCF0'] = '\u064A\u0645';
- t['\uFCF1'] = '\u064A\u0647';
- t['\uFCF2'] = '\u0640\u064E\u0651';
- t['\uFCF3'] = '\u0640\u064F\u0651';
- t['\uFCF4'] = '\u0640\u0650\u0651';
- t['\uFCF5'] = '\u0637\u0649';
- t['\uFCF6'] = '\u0637\u064A';
- t['\uFCF7'] = '\u0639\u0649';
- t['\uFCF8'] = '\u0639\u064A';
- t['\uFCF9'] = '\u063A\u0649';
- t['\uFCFA'] = '\u063A\u064A';
- t['\uFCFB'] = '\u0633\u0649';
- t['\uFCFC'] = '\u0633\u064A';
- t['\uFCFD'] = '\u0634\u0649';
- t['\uFCFE'] = '\u0634\u064A';
- t['\uFCFF'] = '\u062D\u0649';
- t['\uFD00'] = '\u062D\u064A';
- t['\uFD01'] = '\u062C\u0649';
- t['\uFD02'] = '\u062C\u064A';
- t['\uFD03'] = '\u062E\u0649';
- t['\uFD04'] = '\u062E\u064A';
- t['\uFD05'] = '\u0635\u0649';
- t['\uFD06'] = '\u0635\u064A';
- t['\uFD07'] = '\u0636\u0649';
- t['\uFD08'] = '\u0636\u064A';
- t['\uFD09'] = '\u0634\u062C';
- t['\uFD0A'] = '\u0634\u062D';
- t['\uFD0B'] = '\u0634\u062E';
- t['\uFD0C'] = '\u0634\u0645';
- t['\uFD0D'] = '\u0634\u0631';
- t['\uFD0E'] = '\u0633\u0631';
- t['\uFD0F'] = '\u0635\u0631';
- t['\uFD10'] = '\u0636\u0631';
- t['\uFD11'] = '\u0637\u0649';
- t['\uFD12'] = '\u0637\u064A';
- t['\uFD13'] = '\u0639\u0649';
- t['\uFD14'] = '\u0639\u064A';
- t['\uFD15'] = '\u063A\u0649';
- t['\uFD16'] = '\u063A\u064A';
- t['\uFD17'] = '\u0633\u0649';
- t['\uFD18'] = '\u0633\u064A';
- t['\uFD19'] = '\u0634\u0649';
- t['\uFD1A'] = '\u0634\u064A';
- t['\uFD1B'] = '\u062D\u0649';
- t['\uFD1C'] = '\u062D\u064A';
- t['\uFD1D'] = '\u062C\u0649';
- t['\uFD1E'] = '\u062C\u064A';
- t['\uFD1F'] = '\u062E\u0649';
- t['\uFD20'] = '\u062E\u064A';
- t['\uFD21'] = '\u0635\u0649';
- t['\uFD22'] = '\u0635\u064A';
- t['\uFD23'] = '\u0636\u0649';
- t['\uFD24'] = '\u0636\u064A';
- t['\uFD25'] = '\u0634\u062C';
- t['\uFD26'] = '\u0634\u062D';
- t['\uFD27'] = '\u0634\u062E';
- t['\uFD28'] = '\u0634\u0645';
- t['\uFD29'] = '\u0634\u0631';
- t['\uFD2A'] = '\u0633\u0631';
- t['\uFD2B'] = '\u0635\u0631';
- t['\uFD2C'] = '\u0636\u0631';
- t['\uFD2D'] = '\u0634\u062C';
- t['\uFD2E'] = '\u0634\u062D';
- t['\uFD2F'] = '\u0634\u062E';
- t['\uFD30'] = '\u0634\u0645';
- t['\uFD31'] = '\u0633\u0647';
- t['\uFD32'] = '\u0634\u0647';
- t['\uFD33'] = '\u0637\u0645';
- t['\uFD34'] = '\u0633\u062C';
- t['\uFD35'] = '\u0633\u062D';
- t['\uFD36'] = '\u0633\u062E';
- t['\uFD37'] = '\u0634\u062C';
- t['\uFD38'] = '\u0634\u062D';
- t['\uFD39'] = '\u0634\u062E';
- t['\uFD3A'] = '\u0637\u0645';
- t['\uFD3B'] = '\u0638\u0645';
- t['\uFD3C'] = '\u0627\u064B';
- t['\uFD3D'] = '\u0627\u064B';
- t['\uFD50'] = '\u062A\u062C\u0645';
- t['\uFD51'] = '\u062A\u062D\u062C';
- t['\uFD52'] = '\u062A\u062D\u062C';
- t['\uFD53'] = '\u062A\u062D\u0645';
- t['\uFD54'] = '\u062A\u062E\u0645';
- t['\uFD55'] = '\u062A\u0645\u062C';
- t['\uFD56'] = '\u062A\u0645\u062D';
- t['\uFD57'] = '\u062A\u0645\u062E';
- t['\uFD58'] = '\u062C\u0645\u062D';
- t['\uFD59'] = '\u062C\u0645\u062D';
- t['\uFD5A'] = '\u062D\u0645\u064A';
- t['\uFD5B'] = '\u062D\u0645\u0649';
- t['\uFD5C'] = '\u0633\u062D\u062C';
- t['\uFD5D'] = '\u0633\u062C\u062D';
- t['\uFD5E'] = '\u0633\u062C\u0649';
- t['\uFD5F'] = '\u0633\u0645\u062D';
- t['\uFD60'] = '\u0633\u0645\u062D';
- t['\uFD61'] = '\u0633\u0645\u062C';
- t['\uFD62'] = '\u0633\u0645\u0645';
- t['\uFD63'] = '\u0633\u0645\u0645';
- t['\uFD64'] = '\u0635\u062D\u062D';
- t['\uFD65'] = '\u0635\u062D\u062D';
- t['\uFD66'] = '\u0635\u0645\u0645';
- t['\uFD67'] = '\u0634\u062D\u0645';
- t['\uFD68'] = '\u0634\u062D\u0645';
- t['\uFD69'] = '\u0634\u062C\u064A';
- t['\uFD6A'] = '\u0634\u0645\u062E';
- t['\uFD6B'] = '\u0634\u0645\u062E';
- t['\uFD6C'] = '\u0634\u0645\u0645';
- t['\uFD6D'] = '\u0634\u0645\u0645';
- t['\uFD6E'] = '\u0636\u062D\u0649';
- t['\uFD6F'] = '\u0636\u062E\u0645';
- t['\uFD70'] = '\u0636\u062E\u0645';
- t['\uFD71'] = '\u0637\u0645\u062D';
- t['\uFD72'] = '\u0637\u0645\u062D';
- t['\uFD73'] = '\u0637\u0645\u0645';
- t['\uFD74'] = '\u0637\u0645\u064A';
- t['\uFD75'] = '\u0639\u062C\u0645';
- t['\uFD76'] = '\u0639\u0645\u0645';
- t['\uFD77'] = '\u0639\u0645\u0645';
- t['\uFD78'] = '\u0639\u0645\u0649';
- t['\uFD79'] = '\u063A\u0645\u0645';
- t['\uFD7A'] = '\u063A\u0645\u064A';
- t['\uFD7B'] = '\u063A\u0645\u0649';
- t['\uFD7C'] = '\u0641\u062E\u0645';
- t['\uFD7D'] = '\u0641\u062E\u0645';
- t['\uFD7E'] = '\u0642\u0645\u062D';
- t['\uFD7F'] = '\u0642\u0645\u0645';
- t['\uFD80'] = '\u0644\u062D\u0645';
- t['\uFD81'] = '\u0644\u062D\u064A';
- t['\uFD82'] = '\u0644\u062D\u0649';
- t['\uFD83'] = '\u0644\u062C\u062C';
- t['\uFD84'] = '\u0644\u062C\u062C';
- t['\uFD85'] = '\u0644\u062E\u0645';
- t['\uFD86'] = '\u0644\u062E\u0645';
- t['\uFD87'] = '\u0644\u0645\u062D';
- t['\uFD88'] = '\u0644\u0645\u062D';
- t['\uFD89'] = '\u0645\u062D\u062C';
- t['\uFD8A'] = '\u0645\u062D\u0645';
- t['\uFD8B'] = '\u0645\u062D\u064A';
- t['\uFD8C'] = '\u0645\u062C\u062D';
- t['\uFD8D'] = '\u0645\u062C\u0645';
- t['\uFD8E'] = '\u0645\u062E\u062C';
- t['\uFD8F'] = '\u0645\u062E\u0645';
- t['\uFD92'] = '\u0645\u062C\u062E';
- t['\uFD93'] = '\u0647\u0645\u062C';
- t['\uFD94'] = '\u0647\u0645\u0645';
- t['\uFD95'] = '\u0646\u062D\u0645';
- t['\uFD96'] = '\u0646\u062D\u0649';
- t['\uFD97'] = '\u0646\u062C\u0645';
- t['\uFD98'] = '\u0646\u062C\u0645';
- t['\uFD99'] = '\u0646\u062C\u0649';
- t['\uFD9A'] = '\u0646\u0645\u064A';
- t['\uFD9B'] = '\u0646\u0645\u0649';
- t['\uFD9C'] = '\u064A\u0645\u0645';
- t['\uFD9D'] = '\u064A\u0645\u0645';
- t['\uFD9E'] = '\u0628\u062E\u064A';
- t['\uFD9F'] = '\u062A\u062C\u064A';
- t['\uFDA0'] = '\u062A\u062C\u0649';
- t['\uFDA1'] = '\u062A\u062E\u064A';
- t['\uFDA2'] = '\u062A\u062E\u0649';
- t['\uFDA3'] = '\u062A\u0645\u064A';
- t['\uFDA4'] = '\u062A\u0645\u0649';
- t['\uFDA5'] = '\u062C\u0645\u064A';
- t['\uFDA6'] = '\u062C\u062D\u0649';
- t['\uFDA7'] = '\u062C\u0645\u0649';
- t['\uFDA8'] = '\u0633\u062E\u0649';
- t['\uFDA9'] = '\u0635\u062D\u064A';
- t['\uFDAA'] = '\u0634\u062D\u064A';
- t['\uFDAB'] = '\u0636\u062D\u064A';
- t['\uFDAC'] = '\u0644\u062C\u064A';
- t['\uFDAD'] = '\u0644\u0645\u064A';
- t['\uFDAE'] = '\u064A\u062D\u064A';
- t['\uFDAF'] = '\u064A\u062C\u064A';
- t['\uFDB0'] = '\u064A\u0645\u064A';
- t['\uFDB1'] = '\u0645\u0645\u064A';
- t['\uFDB2'] = '\u0642\u0645\u064A';
- t['\uFDB3'] = '\u0646\u062D\u064A';
- t['\uFDB4'] = '\u0642\u0645\u062D';
- t['\uFDB5'] = '\u0644\u062D\u0645';
- t['\uFDB6'] = '\u0639\u0645\u064A';
- t['\uFDB7'] = '\u0643\u0645\u064A';
- t['\uFDB8'] = '\u0646\u062C\u062D';
- t['\uFDB9'] = '\u0645\u062E\u064A';
- t['\uFDBA'] = '\u0644\u062C\u0645';
- t['\uFDBB'] = '\u0643\u0645\u0645';
- t['\uFDBC'] = '\u0644\u062C\u0645';
- t['\uFDBD'] = '\u0646\u062C\u062D';
- t['\uFDBE'] = '\u062C\u062D\u064A';
- t['\uFDBF'] = '\u062D\u062C\u064A';
- t['\uFDC0'] = '\u0645\u062C\u064A';
- t['\uFDC1'] = '\u0641\u0645\u064A';
- t['\uFDC2'] = '\u0628\u062D\u064A';
- t['\uFDC3'] = '\u0643\u0645\u0645';
- t['\uFDC4'] = '\u0639\u062C\u0645';
- t['\uFDC5'] = '\u0635\u0645\u0645';
- t['\uFDC6'] = '\u0633\u062E\u064A';
- t['\uFDC7'] = '\u0646\u062C\u064A';
- t['\uFE49'] = '\u203E';
- t['\uFE4A'] = '\u203E';
- t['\uFE4B'] = '\u203E';
- t['\uFE4C'] = '\u203E';
- t['\uFE4D'] = '\u005F';
- t['\uFE4E'] = '\u005F';
- t['\uFE4F'] = '\u005F';
- t['\uFE80'] = '\u0621';
- t['\uFE81'] = '\u0622';
- t['\uFE82'] = '\u0622';
- t['\uFE83'] = '\u0623';
- t['\uFE84'] = '\u0623';
- t['\uFE85'] = '\u0624';
- t['\uFE86'] = '\u0624';
- t['\uFE87'] = '\u0625';
- t['\uFE88'] = '\u0625';
- t['\uFE89'] = '\u0626';
- t['\uFE8A'] = '\u0626';
- t['\uFE8B'] = '\u0626';
- t['\uFE8C'] = '\u0626';
- t['\uFE8D'] = '\u0627';
- t['\uFE8E'] = '\u0627';
- t['\uFE8F'] = '\u0628';
- t['\uFE90'] = '\u0628';
- t['\uFE91'] = '\u0628';
- t['\uFE92'] = '\u0628';
- t['\uFE93'] = '\u0629';
- t['\uFE94'] = '\u0629';
- t['\uFE95'] = '\u062A';
- t['\uFE96'] = '\u062A';
- t['\uFE97'] = '\u062A';
- t['\uFE98'] = '\u062A';
- t['\uFE99'] = '\u062B';
- t['\uFE9A'] = '\u062B';
- t['\uFE9B'] = '\u062B';
- t['\uFE9C'] = '\u062B';
- t['\uFE9D'] = '\u062C';
- t['\uFE9E'] = '\u062C';
- t['\uFE9F'] = '\u062C';
- t['\uFEA0'] = '\u062C';
- t['\uFEA1'] = '\u062D';
- t['\uFEA2'] = '\u062D';
- t['\uFEA3'] = '\u062D';
- t['\uFEA4'] = '\u062D';
- t['\uFEA5'] = '\u062E';
- t['\uFEA6'] = '\u062E';
- t['\uFEA7'] = '\u062E';
- t['\uFEA8'] = '\u062E';
- t['\uFEA9'] = '\u062F';
- t['\uFEAA'] = '\u062F';
- t['\uFEAB'] = '\u0630';
- t['\uFEAC'] = '\u0630';
- t['\uFEAD'] = '\u0631';
- t['\uFEAE'] = '\u0631';
- t['\uFEAF'] = '\u0632';
- t['\uFEB0'] = '\u0632';
- t['\uFEB1'] = '\u0633';
- t['\uFEB2'] = '\u0633';
- t['\uFEB3'] = '\u0633';
- t['\uFEB4'] = '\u0633';
- t['\uFEB5'] = '\u0634';
- t['\uFEB6'] = '\u0634';
- t['\uFEB7'] = '\u0634';
- t['\uFEB8'] = '\u0634';
- t['\uFEB9'] = '\u0635';
- t['\uFEBA'] = '\u0635';
- t['\uFEBB'] = '\u0635';
- t['\uFEBC'] = '\u0635';
- t['\uFEBD'] = '\u0636';
- t['\uFEBE'] = '\u0636';
- t['\uFEBF'] = '\u0636';
- t['\uFEC0'] = '\u0636';
- t['\uFEC1'] = '\u0637';
- t['\uFEC2'] = '\u0637';
- t['\uFEC3'] = '\u0637';
- t['\uFEC4'] = '\u0637';
- t['\uFEC5'] = '\u0638';
- t['\uFEC6'] = '\u0638';
- t['\uFEC7'] = '\u0638';
- t['\uFEC8'] = '\u0638';
- t['\uFEC9'] = '\u0639';
- t['\uFECA'] = '\u0639';
- t['\uFECB'] = '\u0639';
- t['\uFECC'] = '\u0639';
- t['\uFECD'] = '\u063A';
- t['\uFECE'] = '\u063A';
- t['\uFECF'] = '\u063A';
- t['\uFED0'] = '\u063A';
- t['\uFED1'] = '\u0641';
- t['\uFED2'] = '\u0641';
- t['\uFED3'] = '\u0641';
- t['\uFED4'] = '\u0641';
- t['\uFED5'] = '\u0642';
- t['\uFED6'] = '\u0642';
- t['\uFED7'] = '\u0642';
- t['\uFED8'] = '\u0642';
- t['\uFED9'] = '\u0643';
- t['\uFEDA'] = '\u0643';
- t['\uFEDB'] = '\u0643';
- t['\uFEDC'] = '\u0643';
- t['\uFEDD'] = '\u0644';
- t['\uFEDE'] = '\u0644';
- t['\uFEDF'] = '\u0644';
- t['\uFEE0'] = '\u0644';
- t['\uFEE1'] = '\u0645';
- t['\uFEE2'] = '\u0645';
- t['\uFEE3'] = '\u0645';
- t['\uFEE4'] = '\u0645';
- t['\uFEE5'] = '\u0646';
- t['\uFEE6'] = '\u0646';
- t['\uFEE7'] = '\u0646';
- t['\uFEE8'] = '\u0646';
- t['\uFEE9'] = '\u0647';
- t['\uFEEA'] = '\u0647';
- t['\uFEEB'] = '\u0647';
- t['\uFEEC'] = '\u0647';
- t['\uFEED'] = '\u0648';
- t['\uFEEE'] = '\u0648';
- t['\uFEEF'] = '\u0649';
- t['\uFEF0'] = '\u0649';
- t['\uFEF1'] = '\u064A';
- t['\uFEF2'] = '\u064A';
- t['\uFEF3'] = '\u064A';
- t['\uFEF4'] = '\u064A';
- t['\uFEF5'] = '\u0644\u0622';
- t['\uFEF6'] = '\u0644\u0622';
- t['\uFEF7'] = '\u0644\u0623';
- t['\uFEF8'] = '\u0644\u0623';
- t['\uFEF9'] = '\u0644\u0625';
- t['\uFEFA'] = '\u0644\u0625';
- t['\uFEFB'] = '\u0644\u0627';
- t['\uFEFC'] = '\u0644\u0627';
-});
-function reverseIfRtl(chars) {
- var charsLength = chars.length;
- if (charsLength <= 1 || !isRTLRangeFor(chars.charCodeAt(0))) {
- return chars;
- }
- var s = '';
- for (var ii = charsLength - 1; ii >= 0; ii--) {
- s += chars[ii];
- }
- return s;
-}
-exports.mapSpecialUnicodeValues = mapSpecialUnicodeValues;
-exports.reverseIfRtl = reverseIfRtl;
-exports.getUnicodeRangeFor = getUnicodeRangeFor;
-exports.getNormalizedUnicodes = getNormalizedUnicodes;
-exports.getUnicodeForGlyph = getUnicodeForGlyph;
-
-/***/ }),
-/* 19 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreWorker = __w_pdfjs_require__(8);
-var globalScope = sharedUtil.globalScope;
-var OK_RESPONSE = 200;
-var PARTIAL_CONTENT_RESPONSE = 206;
-function NetworkManager(url, args) {
- this.url = url;
- args = args || {};
- this.isHttp = /^https?:/i.test(url);
- this.httpHeaders = this.isHttp && args.httpHeaders || {};
- this.withCredentials = args.withCredentials || false;
- this.getXhr = args.getXhr || function NetworkManager_getXhr() {
- return new XMLHttpRequest();
- };
- this.currXhrId = 0;
- this.pendingRequests = Object.create(null);
- this.loadedRequests = Object.create(null);
-}
-function getArrayBuffer(xhr) {
- var data = xhr.response;
- if (typeof data !== 'string') {
- return data;
- }
- var length = data.length;
- var array = new Uint8Array(length);
- for (var i = 0; i < length; i++) {
- array[i] = data.charCodeAt(i) & 0xFF;
- }
- return array.buffer;
-}
-var supportsMozChunked = function supportsMozChunkedClosure() {
- try {
- var x = new XMLHttpRequest();
- x.open('GET', globalScope.location.href);
- x.responseType = 'moz-chunked-arraybuffer';
- return x.responseType === 'moz-chunked-arraybuffer';
- } catch (e) {
- return false;
- }
-}();
-NetworkManager.prototype = {
- requestRange: function NetworkManager_requestRange(begin, end, listeners) {
- var args = {
- begin: begin,
- end: end
- };
- for (var prop in listeners) {
- args[prop] = listeners[prop];
- }
- return this.request(args);
- },
- requestFull: function NetworkManager_requestFull(listeners) {
- return this.request(listeners);
- },
- request: function NetworkManager_request(args) {
- var xhr = this.getXhr();
- var xhrId = this.currXhrId++;
- var pendingRequest = this.pendingRequests[xhrId] = { xhr: xhr };
- xhr.open('GET', this.url);
- xhr.withCredentials = this.withCredentials;
- for (var property in this.httpHeaders) {
- var value = this.httpHeaders[property];
- if (typeof value === 'undefined') {
- continue;
- }
- xhr.setRequestHeader(property, value);
- }
- if (this.isHttp && 'begin' in args && 'end' in args) {
- var rangeStr = args.begin + '-' + (args.end - 1);
- xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
- pendingRequest.expectedStatus = 206;
- } else {
- pendingRequest.expectedStatus = 200;
- }
- var useMozChunkedLoading = supportsMozChunked && !!args.onProgressiveData;
- if (useMozChunkedLoading) {
- xhr.responseType = 'moz-chunked-arraybuffer';
- pendingRequest.onProgressiveData = args.onProgressiveData;
- pendingRequest.mozChunked = true;
- } else {
- xhr.responseType = 'arraybuffer';
- }
- if (args.onError) {
- xhr.onerror = function (evt) {
- args.onError(xhr.status);
- };
- }
- xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
- xhr.onprogress = this.onProgress.bind(this, xhrId);
- pendingRequest.onHeadersReceived = args.onHeadersReceived;
- pendingRequest.onDone = args.onDone;
- pendingRequest.onError = args.onError;
- pendingRequest.onProgress = args.onProgress;
- xhr.send(null);
- return xhrId;
- },
- onProgress: function NetworkManager_onProgress(xhrId, evt) {
- var pendingRequest = this.pendingRequests[xhrId];
- if (!pendingRequest) {
- return;
- }
- if (pendingRequest.mozChunked) {
- var chunk = getArrayBuffer(pendingRequest.xhr);
- pendingRequest.onProgressiveData(chunk);
- }
- var onProgress = pendingRequest.onProgress;
- if (onProgress) {
- onProgress(evt);
- }
- },
- onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
- var pendingRequest = this.pendingRequests[xhrId];
- if (!pendingRequest) {
- return;
- }
- var xhr = pendingRequest.xhr;
- if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
- pendingRequest.onHeadersReceived();
- delete pendingRequest.onHeadersReceived;
- }
- if (xhr.readyState !== 4) {
- return;
- }
- if (!(xhrId in this.pendingRequests)) {
- return;
- }
- delete this.pendingRequests[xhrId];
- if (xhr.status === 0 && this.isHttp) {
- if (pendingRequest.onError) {
- pendingRequest.onError(xhr.status);
- }
- return;
- }
- var xhrStatus = xhr.status || OK_RESPONSE;
- var ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
- if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) {
- if (pendingRequest.onError) {
- pendingRequest.onError(xhr.status);
- }
- return;
- }
- this.loadedRequests[xhrId] = true;
- var chunk = getArrayBuffer(xhr);
- if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
- var rangeHeader = xhr.getResponseHeader('Content-Range');
- var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
- var begin = parseInt(matches[1], 10);
- pendingRequest.onDone({
- begin: begin,
- chunk: chunk
- });
- } else if (pendingRequest.onProgressiveData) {
- pendingRequest.onDone(null);
- } else if (chunk) {
- pendingRequest.onDone({
- begin: 0,
- chunk: chunk
- });
- } else if (pendingRequest.onError) {
- pendingRequest.onError(xhr.status);
- }
- },
- hasPendingRequests: function NetworkManager_hasPendingRequests() {
- for (var xhrId in this.pendingRequests) {
- return true;
- }
- return false;
- },
- getRequestXhr: function NetworkManager_getXhr(xhrId) {
- return this.pendingRequests[xhrId].xhr;
- },
- isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
- return !!this.pendingRequests[xhrId].onProgressiveData;
- },
- isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
- return xhrId in this.pendingRequests;
- },
- isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
- return xhrId in this.loadedRequests;
- },
- abortAllRequests: function NetworkManager_abortAllRequests() {
- for (var xhrId in this.pendingRequests) {
- this.abortRequest(xhrId | 0);
- }
- },
- abortRequest: function NetworkManager_abortRequest(xhrId) {
- var xhr = this.pendingRequests[xhrId].xhr;
- delete this.pendingRequests[xhrId];
- xhr.abort();
- }
-};
-var assert = sharedUtil.assert;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var isInt = sharedUtil.isInt;
-var MissingPDFException = sharedUtil.MissingPDFException;
-var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
-function PDFNetworkStream(options) {
- this._options = options;
- var source = options.source;
- this._manager = new NetworkManager(source.url, {
- httpHeaders: source.httpHeaders,
- withCredentials: source.withCredentials
- });
- this._rangeChunkSize = source.rangeChunkSize;
- this._fullRequestReader = null;
- this._rangeRequestReaders = [];
-}
-PDFNetworkStream.prototype = {
- _onRangeRequestReaderClosed: function PDFNetworkStream_onRangeRequestReaderClosed(reader) {
- var i = this._rangeRequestReaders.indexOf(reader);
- if (i >= 0) {
- this._rangeRequestReaders.splice(i, 1);
- }
- },
- getFullReader: function PDFNetworkStream_getFullReader() {
- assert(!this._fullRequestReader);
- this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._options);
- return this._fullRequestReader;
- },
- getRangeReader: function PDFNetworkStream_getRangeReader(begin, end) {
- var reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end);
- reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
- this._rangeRequestReaders.push(reader);
- return reader;
- },
- cancelAllRequests: function PDFNetworkStream_cancelAllRequests(reason) {
- if (this._fullRequestReader) {
- this._fullRequestReader.cancel(reason);
- }
- var readers = this._rangeRequestReaders.slice(0);
- readers.forEach(function (reader) {
- reader.cancel(reason);
- });
- }
-};
-function PDFNetworkStreamFullRequestReader(manager, options) {
- this._manager = manager;
- var source = options.source;
- var args = {
- onHeadersReceived: this._onHeadersReceived.bind(this),
- onProgressiveData: source.disableStream ? null : this._onProgressiveData.bind(this),
- onDone: this._onDone.bind(this),
- onError: this._onError.bind(this),
- onProgress: this._onProgress.bind(this)
- };
- this._url = source.url;
- this._fullRequestId = manager.requestFull(args);
- this._headersReceivedCapability = createPromiseCapability();
- this._disableRange = options.disableRange || false;
- this._contentLength = source.length;
- this._rangeChunkSize = source.rangeChunkSize;
- if (!this._rangeChunkSize && !this._disableRange) {
- this._disableRange = true;
- }
- this._isStreamingSupported = false;
- this._isRangeSupported = false;
- this._cachedChunks = [];
- this._requests = [];
- this._done = false;
- this._storedError = undefined;
- this.onProgress = null;
-}
-PDFNetworkStreamFullRequestReader.prototype = {
- _validateRangeRequestCapabilities: function PDFNetworkStreamFullRequestReader_validateRangeRequestCapabilities() {
- if (this._disableRange) {
- return false;
- }
- var networkManager = this._manager;
- if (!networkManager.isHttp) {
- return false;
- }
- var fullRequestXhrId = this._fullRequestId;
- var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
- if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
- return false;
- }
- var contentEncoding = fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
- if (contentEncoding !== 'identity') {
- return false;
- }
- var length = fullRequestXhr.getResponseHeader('Content-Length');
- length = parseInt(length, 10);
- if (!isInt(length)) {
- return false;
- }
- this._contentLength = length;
- if (length <= 2 * this._rangeChunkSize) {
- return false;
- }
- return true;
- },
- _onHeadersReceived: function PDFNetworkStreamFullRequestReader_onHeadersReceived() {
- if (this._validateRangeRequestCapabilities()) {
- this._isRangeSupported = true;
- }
- var networkManager = this._manager;
- var fullRequestXhrId = this._fullRequestId;
- if (networkManager.isStreamingRequest(fullRequestXhrId)) {
- this._isStreamingSupported = true;
- } else if (this._isRangeSupported) {
- networkManager.abortRequest(fullRequestXhrId);
- }
- this._headersReceivedCapability.resolve();
- },
- _onProgressiveData: function PDFNetworkStreamFullRequestReader_onProgressiveData(chunk) {
- if (this._requests.length > 0) {
- var requestCapability = this._requests.shift();
- requestCapability.resolve({
- value: chunk,
- done: false
- });
- } else {
- this._cachedChunks.push(chunk);
- }
- },
- _onDone: function PDFNetworkStreamFullRequestReader_onDone(args) {
- if (args) {
- this._onProgressiveData(args.chunk);
- }
- this._done = true;
- if (this._cachedChunks.length > 0) {
- return;
- }
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- },
- _onError: function PDFNetworkStreamFullRequestReader_onError(status) {
- var url = this._url;
- var exception;
- if (status === 404 || status === 0 && /^file:/.test(url)) {
- exception = new MissingPDFException('Missing PDF "' + url + '".');
- } else {
- exception = new UnexpectedResponseException('Unexpected server response (' + status + ') while retrieving PDF "' + url + '".', status);
- }
- this._storedError = exception;
- this._headersReceivedCapability.reject(exception);
- this._requests.forEach(function (requestCapability) {
- requestCapability.reject(exception);
- });
- this._requests = [];
- this._cachedChunks = [];
- },
- _onProgress: function PDFNetworkStreamFullRequestReader_onProgress(data) {
- if (this.onProgress) {
- this.onProgress({
- loaded: data.loaded,
- total: data.lengthComputable ? data.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 PDFNetworkStreamFullRequestReader_read() {
- if (this._storedError) {
- return Promise.reject(this._storedError);
- }
- if (this._cachedChunks.length > 0) {
- var chunk = this._cachedChunks.shift();
- return Promise.resolve(chunk);
- }
- if (this._done) {
- return Promise.resolve({
- value: undefined,
- done: true
- });
- }
- var requestCapability = createPromiseCapability();
- this._requests.push(requestCapability);
- return requestCapability.promise;
- },
- cancel: function PDFNetworkStreamFullRequestReader_cancel(reason) {
- this._done = true;
- this._headersReceivedCapability.reject(reason);
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- if (this._manager.isPendingRequest(this._fullRequestId)) {
- this._manager.abortRequest(this._fullRequestId);
- }
- this._fullRequestReader = null;
- }
-};
-function PDFNetworkStreamRangeRequestReader(manager, begin, end) {
- this._manager = manager;
- var args = {
- onDone: this._onDone.bind(this),
- onProgress: this._onProgress.bind(this)
- };
- this._requestId = manager.requestRange(begin, end, args);
- this._requests = [];
- this._queuedChunk = null;
- this._done = false;
- this.onProgress = null;
- this.onClosed = null;
-}
-PDFNetworkStreamRangeRequestReader.prototype = {
- _close: function PDFNetworkStreamRangeRequestReader_close() {
- if (this.onClosed) {
- this.onClosed(this);
- }
- },
- _onDone: function PDFNetworkStreamRangeRequestReader_onDone(data) {
- var chunk = data.chunk;
- if (this._requests.length > 0) {
- var requestCapability = this._requests.shift();
- requestCapability.resolve({
- value: chunk,
- done: false
- });
- } else {
- this._queuedChunk = chunk;
- }
- this._done = true;
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- this._close();
- },
- _onProgress: function PDFNetworkStreamRangeRequestReader_onProgress(evt) {
- if (!this.isStreamingSupported && this.onProgress) {
- this.onProgress({ loaded: evt.loaded });
- }
- },
- get isStreamingSupported() {
- return false;
- },
- read: function PDFNetworkStreamRangeRequestReader_read() {
- if (this._queuedChunk !== null) {
- var chunk = this._queuedChunk;
- this._queuedChunk = null;
- return Promise.resolve({
- value: chunk,
- done: false
- });
- }
- if (this._done) {
- return Promise.resolve({
- value: undefined,
- done: true
- });
- }
- var requestCapability = createPromiseCapability();
- this._requests.push(requestCapability);
- return requestCapability.promise;
- },
- cancel: function PDFNetworkStreamRangeRequestReader_cancel(reason) {
- this._done = true;
- this._requests.forEach(function (requestCapability) {
- requestCapability.resolve({
- value: undefined,
- done: true
- });
- });
- this._requests = [];
- if (this._manager.isPendingRequest(this._requestId)) {
- this._manager.abortRequest(this._requestId);
- }
- this._close();
- }
-};
-coreWorker.setPDFNetworkStreamClass(PDFNetworkStream);
-exports.PDFNetworkStream = PDFNetworkStream;
-exports.NetworkManager = NetworkManager;
-
-/***/ }),
-/* 20 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var coreColorSpace = __w_pdfjs_require__(3);
-var coreObj = __w_pdfjs_require__(16);
-var coreEvaluator = __w_pdfjs_require__(14);
-var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
-var AnnotationFieldFlag = sharedUtil.AnnotationFieldFlag;
-var AnnotationFlag = sharedUtil.AnnotationFlag;
-var AnnotationType = sharedUtil.AnnotationType;
-var OPS = sharedUtil.OPS;
-var Util = sharedUtil.Util;
-var isArray = sharedUtil.isArray;
-var isInt = sharedUtil.isInt;
-var stringToBytes = sharedUtil.stringToBytes;
-var stringToPDFString = sharedUtil.stringToPDFString;
-var warn = sharedUtil.warn;
-var Dict = corePrimitives.Dict;
-var isDict = corePrimitives.isDict;
-var isName = corePrimitives.isName;
-var isRef = corePrimitives.isRef;
-var isStream = corePrimitives.isStream;
-var Stream = coreStream.Stream;
-var ColorSpace = coreColorSpace.ColorSpace;
-var Catalog = coreObj.Catalog;
-var ObjectLoader = coreObj.ObjectLoader;
-var FileSpec = coreObj.FileSpec;
-var OperatorList = coreEvaluator.OperatorList;
-function AnnotationFactory() {}
-AnnotationFactory.prototype = {
- create: function AnnotationFactory_create(xref, ref, pdfManager, idFactory) {
- var dict = xref.fetchIfRef(ref);
- if (!isDict(dict)) {
- return;
- }
- var id = isRef(ref) ? ref.toString() : 'annot_' + idFactory.createObjId();
- var subtype = dict.get('Subtype');
- subtype = isName(subtype) ? subtype.name : null;
- var parameters = {
- xref: xref,
- dict: dict,
- ref: isRef(ref) ? ref : null,
- subtype: subtype,
- id: id,
- pdfManager: pdfManager
- };
- switch (subtype) {
- case 'Link':
- return new LinkAnnotation(parameters);
- case 'Text':
- return new TextAnnotation(parameters);
- case 'Widget':
- var fieldType = Util.getInheritableProperty(dict, 'FT');
- fieldType = isName(fieldType) ? fieldType.name : null;
- switch (fieldType) {
- case 'Tx':
- return new TextWidgetAnnotation(parameters);
- case 'Btn':
- return new ButtonWidgetAnnotation(parameters);
- case 'Ch':
- return new ChoiceWidgetAnnotation(parameters);
- }
- warn('Unimplemented widget field type "' + fieldType + '", ' + 'falling back to base field type.');
- return new WidgetAnnotation(parameters);
- case 'Popup':
- return new PopupAnnotation(parameters);
- case 'Highlight':
- return new HighlightAnnotation(parameters);
- case 'Underline':
- return new UnderlineAnnotation(parameters);
- case 'Squiggly':
- return new SquigglyAnnotation(parameters);
- case 'StrikeOut':
- return new StrikeOutAnnotation(parameters);
- case 'FileAttachment':
- return new FileAttachmentAnnotation(parameters);
- default:
- if (!subtype) {
- warn('Annotation is missing the required /Subtype.');
- } else {
- warn('Unimplemented annotation type "' + subtype + '", ' + 'falling back to base annotation.');
- }
- return new Annotation(parameters);
- }
- }
-};
-var Annotation = function AnnotationClosure() {
- function getTransformMatrix(rect, bbox, matrix) {
- var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
- var minX = bounds[0];
- var minY = bounds[1];
- var maxX = bounds[2];
- var maxY = bounds[3];
- if (minX === maxX || minY === maxY) {
- return [1, 0, 0, 1, rect[0], rect[1]];
- }
- var xRatio = (rect[2] - rect[0]) / (maxX - minX);
- var yRatio = (rect[3] - rect[1]) / (maxY - minY);
- return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
- }
- function Annotation(params) {
- var dict = params.dict;
- this.setFlags(dict.get('F'));
- this.setRectangle(dict.getArray('Rect'));
- this.setColor(dict.getArray('C'));
- this.setBorderStyle(dict);
- this.setAppearance(dict);
- this.data = {};
- this.data.id = params.id;
- this.data.subtype = params.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;
- }
- Annotation.prototype = {
- _hasFlag: function Annotation_hasFlag(flags, flag) {
- return !!(flags & flag);
- },
- _isViewable: function Annotation_isViewable(flags) {
- return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.NOVIEW);
- },
- _isPrintable: function AnnotationFlag_isPrintable(flags) {
- return this._hasFlag(flags, AnnotationFlag.PRINT) && !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.HIDDEN);
- },
- get viewable() {
- if (this.flags === 0) {
- return true;
- }
- return this._isViewable(this.flags);
- },
- get printable() {
- if (this.flags === 0) {
- return false;
- }
- return this._isPrintable(this.flags);
- },
- setFlags: function Annotation_setFlags(flags) {
- this.flags = isInt(flags) && flags > 0 ? flags : 0;
- },
- hasFlag: function Annotation_hasFlag(flag) {
- return this._hasFlag(this.flags, flag);
- },
- setRectangle: function Annotation_setRectangle(rectangle) {
- if (isArray(rectangle) && rectangle.length === 4) {
- this.rectangle = Util.normalizeRect(rectangle);
- } else {
- this.rectangle = [0, 0, 0, 0];
- }
- },
- setColor: function Annotation_setColor(color) {
- var rgbColor = new Uint8Array(3);
- if (!isArray(color)) {
- this.color = rgbColor;
- return;
- }
- switch (color.length) {
- case 0:
- this.color = null;
- break;
- case 1:
- ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
- this.color = rgbColor;
- break;
- case 3:
- ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
- this.color = rgbColor;
- break;
- case 4:
- ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
- this.color = rgbColor;
- break;
- default:
- this.color = rgbColor;
- break;
- }
- },
- setBorderStyle: function Annotation_setBorderStyle(borderStyle) {
- this.borderStyle = new AnnotationBorderStyle();
- if (!isDict(borderStyle)) {
- return;
- }
- if (borderStyle.has('BS')) {
- var dict = borderStyle.get('BS');
- var dictType = dict.get('Type');
- if (!dictType || isName(dictType, 'Border')) {
- this.borderStyle.setWidth(dict.get('W'));
- this.borderStyle.setStyle(dict.get('S'));
- this.borderStyle.setDashArray(dict.getArray('D'));
- }
- } else if (borderStyle.has('Border')) {
- var array = borderStyle.getArray('Border');
- if (isArray(array) && array.length >= 3) {
- this.borderStyle.setHorizontalCornerRadius(array[0]);
- this.borderStyle.setVerticalCornerRadius(array[1]);
- this.borderStyle.setWidth(array[2]);
- if (array.length === 4) {
- this.borderStyle.setDashArray(array[3]);
- }
- }
- } else {
- this.borderStyle.setWidth(0);
- }
- },
- setAppearance: function Annotation_setAppearance(dict) {
- this.appearance = null;
- var appearanceStates = dict.get('AP');
- if (!isDict(appearanceStates)) {
- return;
- }
- var normalAppearanceState = appearanceStates.get('N');
- if (isStream(normalAppearanceState)) {
- this.appearance = normalAppearanceState;
- return;
- }
- if (!isDict(normalAppearanceState)) {
- return;
- }
- var as = dict.get('AS');
- if (!isName(as) || !normalAppearanceState.has(as.name)) {
- return;
- }
- this.appearance = normalAppearanceState.get(as.name);
- },
- _preparePopup: function Annotation_preparePopup(dict) {
- if (!dict.has('C')) {
- this.data.color = null;
- }
- this.data.hasPopup = dict.has('Popup');
- this.data.title = stringToPDFString(dict.get('T') || '');
- this.data.contents = stringToPDFString(dict.get('Contents') || '');
- },
- loadResources: function Annotation_loadResources(keys) {
- return new Promise(function (resolve, reject) {
- this.appearance.dict.getAsync('Resources').then(function (resources) {
- if (!resources) {
- resolve();
- return;
- }
- var objectLoader = new ObjectLoader(resources.map, keys, resources.xref);
- objectLoader.load().then(function () {
- resolve(resources);
- }, reject);
- }, reject);
- }.bind(this));
- },
- getOperatorList: function Annotation_getOperatorList(evaluator, task, renderForms) {
- if (!this.appearance) {
- return Promise.resolve(new OperatorList());
- }
- var data = this.data;
- var appearanceDict = this.appearance.dict;
- var resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
- var bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
- var matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0, 0];
- var transform = getTransformMatrix(data.rect, bbox, matrix);
- var self = this;
- return resourcesPromise.then(function (resources) {
- var opList = new OperatorList();
- opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
- return evaluator.getOperatorList(self.appearance, task, resources, opList).then(function () {
- opList.addOp(OPS.endAnnotation, []);
- self.appearance.reset();
- return opList;
- });
- });
- }
- };
- return Annotation;
-}();
-var AnnotationBorderStyle = function AnnotationBorderStyleClosure() {
- function AnnotationBorderStyle() {
- this.width = 1;
- this.style = AnnotationBorderStyleType.SOLID;
- this.dashArray = [3];
- this.horizontalCornerRadius = 0;
- this.verticalCornerRadius = 0;
- }
- AnnotationBorderStyle.prototype = {
- setWidth: function AnnotationBorderStyle_setWidth(width) {
- if (width === (width | 0)) {
- this.width = width;
- }
- },
- setStyle: function AnnotationBorderStyle_setStyle(style) {
- if (!style) {
- return;
- }
- switch (style.name) {
- case 'S':
- this.style = AnnotationBorderStyleType.SOLID;
- break;
- case 'D':
- this.style = AnnotationBorderStyleType.DASHED;
- break;
- case 'B':
- this.style = AnnotationBorderStyleType.BEVELED;
- break;
- case 'I':
- this.style = AnnotationBorderStyleType.INSET;
- break;
- case 'U':
- this.style = AnnotationBorderStyleType.UNDERLINE;
- break;
- default:
- break;
- }
- },
- setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) {
- if (isArray(dashArray) && dashArray.length > 0) {
- var isValid = true;
- var allZeros = true;
- for (var i = 0, len = dashArray.length; i < len; i++) {
- var element = dashArray[i];
- var validNumber = +element >= 0;
- if (!validNumber) {
- isValid = false;
- break;
- } else if (element > 0) {
- allZeros = false;
- }
- }
- if (isValid && !allZeros) {
- this.dashArray = dashArray;
- } else {
- this.width = 0;
- }
- } else if (dashArray) {
- this.width = 0;
- }
- },
- setHorizontalCornerRadius: function AnnotationBorderStyle_setHorizontalCornerRadius(radius) {
- if (radius === (radius | 0)) {
- this.horizontalCornerRadius = radius;
- }
- },
- setVerticalCornerRadius: function AnnotationBorderStyle_setVerticalCornerRadius(radius) {
- if (radius === (radius | 0)) {
- this.verticalCornerRadius = radius;
- }
- }
- };
- return AnnotationBorderStyle;
-}();
-var WidgetAnnotation = function WidgetAnnotationClosure() {
- function WidgetAnnotation(params) {
- Annotation.call(this, params);
- var dict = params.dict;
- var data = this.data;
- data.annotationType = AnnotationType.WIDGET;
- data.fieldName = this._constructFieldName(dict);
- data.fieldValue = Util.getInheritableProperty(dict, 'V', true);
- data.alternativeText = stringToPDFString(dict.get('TU') || '');
- data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
- var fieldType = Util.getInheritableProperty(dict, 'FT');
- data.fieldType = isName(fieldType) ? fieldType.name : null;
- this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
- data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
- if (!isInt(data.fieldFlags) || data.fieldFlags < 0) {
- data.fieldFlags = 0;
- }
- data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
- if (data.fieldType === 'Sig') {
- this.setFlags(AnnotationFlag.HIDDEN);
- }
- }
- Util.inherit(WidgetAnnotation, Annotation, {
- _constructFieldName: function WidgetAnnotation_constructFieldName(dict) {
- if (!dict.has('T') && !dict.has('Parent')) {
- warn('Unknown field name, falling back to empty field name.');
- return '';
- }
- if (!dict.has('Parent')) {
- return stringToPDFString(dict.get('T'));
- }
- var fieldName = [];
- if (dict.has('T')) {
- fieldName.unshift(stringToPDFString(dict.get('T')));
- }
- var loopDict = dict;
- while (loopDict.has('Parent')) {
- loopDict = loopDict.get('Parent');
- if (!isDict(loopDict)) {
- break;
- }
- if (loopDict.has('T')) {
- fieldName.unshift(stringToPDFString(loopDict.get('T')));
- }
- }
- return fieldName.join('.');
- },
- hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) {
- return !!(this.data.fieldFlags & flag);
- }
- });
- return WidgetAnnotation;
-}();
-var TextWidgetAnnotation = function TextWidgetAnnotationClosure() {
- function TextWidgetAnnotation(params) {
- WidgetAnnotation.call(this, params);
- this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
- var alignment = Util.getInheritableProperty(params.dict, 'Q');
- if (!isInt(alignment) || alignment < 0 || alignment > 2) {
- alignment = null;
- }
- this.data.textAlignment = alignment;
- var maximumLength = Util.getInheritableProperty(params.dict, 'MaxLen');
- if (!isInt(maximumLength) || maximumLength < 0) {
- maximumLength = null;
- }
- this.data.maxLen = maximumLength;
- this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
- this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== null;
- }
- Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
- getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
- var operatorList = new OperatorList();
- if (renderForms) {
- return Promise.resolve(operatorList);
- }
- if (this.appearance) {
- return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
- }
- if (!this.data.defaultAppearance) {
- return Promise.resolve(operatorList);
- }
- var stream = new Stream(stringToBytes(this.data.defaultAppearance));
- return evaluator.getOperatorList(stream, task, this.fieldResources, operatorList).then(function () {
- return operatorList;
- });
- }
- });
- return TextWidgetAnnotation;
-}();
-var ButtonWidgetAnnotation = function ButtonWidgetAnnotationClosure() {
- function ButtonWidgetAnnotation(params) {
- WidgetAnnotation.call(this, params);
- this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
- if (this.data.checkBox) {
- if (!isName(this.data.fieldValue)) {
- return;
- }
- this.data.fieldValue = this.data.fieldValue.name;
- }
- this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
- if (this.data.radioButton) {
- this.data.fieldValue = this.data.buttonValue = null;
- var fieldParent = params.dict.get('Parent');
- if (isDict(fieldParent) && fieldParent.has('V')) {
- var fieldParentValue = fieldParent.get('V');
- if (isName(fieldParentValue)) {
- this.data.fieldValue = fieldParentValue.name;
- }
- }
- var appearanceStates = params.dict.get('AP');
- if (!isDict(appearanceStates)) {
- return;
- }
- var normalAppearanceState = appearanceStates.get('N');
- if (!isDict(normalAppearanceState)) {
- return;
- }
- var keys = normalAppearanceState.getKeys();
- for (var i = 0, ii = keys.length; i < ii; i++) {
- if (keys[i] !== 'Off') {
- this.data.buttonValue = keys[i];
- break;
- }
- }
- }
- }
- Util.inherit(ButtonWidgetAnnotation, WidgetAnnotation, {
- getOperatorList: function ButtonWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
- var operatorList = new OperatorList();
- if (renderForms) {
- return Promise.resolve(operatorList);
- }
- if (this.appearance) {
- return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
- }
- return Promise.resolve(operatorList);
- }
- });
- return ButtonWidgetAnnotation;
-}();
-var ChoiceWidgetAnnotation = function ChoiceWidgetAnnotationClosure() {
- function ChoiceWidgetAnnotation(params) {
- WidgetAnnotation.call(this, params);
- this.data.options = [];
- var options = Util.getInheritableProperty(params.dict, 'Opt');
- if (isArray(options)) {
- var xref = params.xref;
- for (var i = 0, ii = options.length; i < ii; i++) {
- var option = xref.fetchIfRef(options[i]);
- var isOptionArray = isArray(option);
- this.data.options[i] = {
- exportValue: isOptionArray ? xref.fetchIfRef(option[0]) : option,
- displayValue: isOptionArray ? xref.fetchIfRef(option[1]) : option
- };
- }
- }
- if (!isArray(this.data.fieldValue)) {
- this.data.fieldValue = [this.data.fieldValue];
- }
- this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
- this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
- }
- Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
- getOperatorList: function ChoiceWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
- var operatorList = new OperatorList();
- if (renderForms) {
- return Promise.resolve(operatorList);
- }
- return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
- }
- });
- return ChoiceWidgetAnnotation;
-}();
-var TextAnnotation = function TextAnnotationClosure() {
- var DEFAULT_ICON_SIZE = 22;
- function TextAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.TEXT;
- if (this.data.hasAppearance) {
- this.data.name = 'NoIcon';
- } else {
- this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
- this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
- this.data.name = parameters.dict.has('Name') ? parameters.dict.get('Name').name : 'Note';
- }
- this._preparePopup(parameters.dict);
- }
- Util.inherit(TextAnnotation, Annotation, {});
- return TextAnnotation;
-}();
-var LinkAnnotation = function LinkAnnotationClosure() {
- function LinkAnnotation(params) {
- Annotation.call(this, params);
- var data = this.data;
- data.annotationType = AnnotationType.LINK;
- Catalog.parseDestDictionary({
- destDict: params.dict,
- resultObj: data,
- docBaseUrl: params.pdfManager.docBaseUrl
- });
- }
- Util.inherit(LinkAnnotation, Annotation, {});
- return LinkAnnotation;
-}();
-var PopupAnnotation = function PopupAnnotationClosure() {
- function PopupAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.POPUP;
- var dict = parameters.dict;
- var parentItem = dict.get('Parent');
- if (!parentItem) {
- warn('Popup annotation has a missing or invalid parent annotation.');
- return;
- }
- this.data.parentId = dict.getRaw('Parent').toString();
- this.data.title = stringToPDFString(parentItem.get('T') || '');
- this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
- if (!parentItem.has('C')) {
- this.data.color = null;
- } else {
- this.setColor(parentItem.getArray('C'));
- this.data.color = this.color;
- }
- if (!this.viewable) {
- var parentFlags = parentItem.get('F');
- if (this._isViewable(parentFlags)) {
- this.setFlags(parentFlags);
- }
- }
- }
- Util.inherit(PopupAnnotation, Annotation, {});
- return PopupAnnotation;
-}();
-var HighlightAnnotation = function HighlightAnnotationClosure() {
- function HighlightAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.HIGHLIGHT;
- this._preparePopup(parameters.dict);
- this.data.borderStyle.setWidth(0);
- }
- Util.inherit(HighlightAnnotation, Annotation, {});
- return HighlightAnnotation;
-}();
-var UnderlineAnnotation = function UnderlineAnnotationClosure() {
- function UnderlineAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.UNDERLINE;
- this._preparePopup(parameters.dict);
- this.data.borderStyle.setWidth(0);
- }
- Util.inherit(UnderlineAnnotation, Annotation, {});
- return UnderlineAnnotation;
-}();
-var SquigglyAnnotation = function SquigglyAnnotationClosure() {
- function SquigglyAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.SQUIGGLY;
- this._preparePopup(parameters.dict);
- this.data.borderStyle.setWidth(0);
- }
- Util.inherit(SquigglyAnnotation, Annotation, {});
- return SquigglyAnnotation;
-}();
-var StrikeOutAnnotation = function StrikeOutAnnotationClosure() {
- function StrikeOutAnnotation(parameters) {
- Annotation.call(this, parameters);
- this.data.annotationType = AnnotationType.STRIKEOUT;
- this._preparePopup(parameters.dict);
- this.data.borderStyle.setWidth(0);
- }
- Util.inherit(StrikeOutAnnotation, Annotation, {});
- return StrikeOutAnnotation;
-}();
-var FileAttachmentAnnotation = function FileAttachmentAnnotationClosure() {
- function FileAttachmentAnnotation(parameters) {
- Annotation.call(this, parameters);
- var file = new FileSpec(parameters.dict.get('FS'), parameters.xref);
- this.data.annotationType = AnnotationType.FILEATTACHMENT;
- this.data.file = file.serializable;
- this._preparePopup(parameters.dict);
- }
- Util.inherit(FileAttachmentAnnotation, Annotation, {});
- return FileAttachmentAnnotation;
-}();
-exports.Annotation = Annotation;
-exports.AnnotationBorderStyle = AnnotationBorderStyle;
-exports.AnnotationFactory = AnnotationFactory;
-
-/***/ }),
-/* 21 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var warn = sharedUtil.warn;
-var baseTypes = ['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'];
-var arabicTypes = ['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'];
-function isOdd(i) {
- return (i & 1) !== 0;
-}
-function isEven(i) {
- return (i & 1) === 0;
-}
-function findUnequal(arr, start, value) {
- for (var j = start, jj = arr.length; j < jj; ++j) {
- if (arr[j] !== value) {
- return j;
- }
- }
- return j;
-}
-function setValues(arr, start, end, value) {
- for (var j = start; j < end; ++j) {
- arr[j] = value;
- }
-}
-function reverseValues(arr, start, end) {
- for (var i = start, j = end - 1; i < j; ++i, --j) {
- var temp = arr[i];
- arr[i] = arr[j];
- arr[j] = temp;
- }
-}
-function createBidiText(str, isLTR, vertical) {
- return {
- str: str,
- dir: vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl'
- };
-}
-var chars = [];
-var types = [];
-function bidi(str, startLevel, vertical) {
- var isLTR = true;
- var strLength = str.length;
- if (strLength === 0 || vertical) {
- return createBidiText(str, isLTR, vertical);
- }
- chars.length = strLength;
- types.length = strLength;
- var numBidi = 0;
- var i, ii;
- for (i = 0; i < strLength; ++i) {
- chars[i] = str.charAt(i);
- var charCode = str.charCodeAt(i);
- var charType = 'L';
- if (charCode <= 0x00ff) {
- charType = baseTypes[charCode];
- } else if (0x0590 <= charCode && charCode <= 0x05f4) {
- charType = 'R';
- } else if (0x0600 <= charCode && charCode <= 0x06ff) {
- charType = arabicTypes[charCode & 0xff];
- if (!charType) {
- warn('Bidi: invalid Unicode character ' + charCode.toString(16));
- }
- } else if (0x0700 <= charCode && charCode <= 0x08AC) {
- charType = 'AL';
- }
- if (charType === 'R' || charType === 'AL' || charType === 'AN') {
- numBidi++;
- }
- types[i] = charType;
- }
- if (numBidi === 0) {
- isLTR = true;
- return createBidiText(str, isLTR);
- }
- if (startLevel === -1) {
- if (numBidi / strLength < 0.3) {
- isLTR = true;
- startLevel = 0;
- } else {
- isLTR = false;
- startLevel = 1;
- }
- }
- var levels = [];
- for (i = 0; i < strLength; ++i) {
- levels[i] = startLevel;
- }
- var e = isOdd(startLevel) ? 'R' : 'L';
- var sor = e;
- var eor = sor;
- var lastType = sor;
- for (i = 0; i < strLength; ++i) {
- if (types[i] === 'NSM') {
- types[i] = lastType;
- } else {
- lastType = types[i];
- }
- }
- lastType = sor;
- var t;
- for (i = 0; i < strLength; ++i) {
- t = types[i];
- if (t === 'EN') {
- types[i] = lastType === 'AL' ? 'AN' : 'EN';
- } else if (t === 'R' || t === 'L' || t === 'AL') {
- lastType = t;
- }
- }
- for (i = 0; i < strLength; ++i) {
- t = types[i];
- if (t === 'AL') {
- types[i] = 'R';
- }
- }
- for (i = 1; i < strLength - 1; ++i) {
- if (types[i] === 'ES' && types[i - 1] === 'EN' && types[i + 1] === 'EN') {
- types[i] = 'EN';
- }
- if (types[i] === 'CS' && (types[i - 1] === 'EN' || types[i - 1] === 'AN') && types[i + 1] === types[i - 1]) {
- types[i] = types[i - 1];
- }
- }
- for (i = 0; i < strLength; ++i) {
- if (types[i] === 'EN') {
- var j;
- for (j = i - 1; j >= 0; --j) {
- if (types[j] !== 'ET') {
- break;
- }
- types[j] = 'EN';
- }
- for (j = i + 1; j < strLength; ++j) {
- if (types[j] !== 'ET') {
- break;
- }
- types[j] = 'EN';
- }
- }
- }
- for (i = 0; i < strLength; ++i) {
- t = types[i];
- if (t === 'WS' || t === 'ES' || t === 'ET' || t === 'CS') {
- types[i] = 'ON';
- }
- }
- lastType = sor;
- for (i = 0; i < strLength; ++i) {
- t = types[i];
- if (t === 'EN') {
- types[i] = lastType === 'L' ? 'L' : 'EN';
- } else if (t === 'R' || t === 'L') {
- lastType = t;
- }
- }
- for (i = 0; i < strLength; ++i) {
- if (types[i] === 'ON') {
- var end = findUnequal(types, i + 1, 'ON');
- var before = sor;
- if (i > 0) {
- before = types[i - 1];
- }
- var after = eor;
- if (end + 1 < strLength) {
- after = types[end + 1];
- }
- if (before !== 'L') {
- before = 'R';
- }
- if (after !== 'L') {
- after = 'R';
- }
- if (before === after) {
- setValues(types, i, end, before);
- }
- i = end - 1;
- }
- }
- for (i = 0; i < strLength; ++i) {
- if (types[i] === 'ON') {
- types[i] = e;
- }
- }
- for (i = 0; i < strLength; ++i) {
- t = types[i];
- if (isEven(levels[i])) {
- if (t === 'R') {
- levels[i] += 1;
- } else if (t === 'AN' || t === 'EN') {
- levels[i] += 2;
- }
- } else {
- if (t === 'L' || t === 'AN' || t === 'EN') {
- levels[i] += 1;
- }
- }
- }
- var highestLevel = -1;
- var lowestOddLevel = 99;
- var level;
- for (i = 0, ii = levels.length; i < ii; ++i) {
- level = levels[i];
- if (highestLevel < level) {
- highestLevel = level;
- }
- if (lowestOddLevel > level && isOdd(level)) {
- lowestOddLevel = level;
- }
- }
- for (level = highestLevel; level >= lowestOddLevel; --level) {
- var start = -1;
- for (i = 0, ii = levels.length; i < ii; ++i) {
- if (levels[i] < level) {
- if (start >= 0) {
- reverseValues(chars, start, i);
- start = -1;
- }
- } else if (start < 0) {
- start = i;
- }
- }
- if (start >= 0) {
- reverseValues(chars, start, levels.length);
- }
- }
- for (i = 0, ii = chars.length; i < ii; ++i) {
- var ch = chars[i];
- if (ch === '<' || ch === '>') {
- chars[i] = '';
- }
- }
- return createBidiText(chars.join(''), isLTR);
-}
-exports.bidi = bidi;
-
-/***/ }),
-/* 22 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var ISOAdobeCharset = ['.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'];
-var ExpertCharset = ['.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'];
-var ExpertSubsetCharset = ['.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'];
-exports.ISOAdobeCharset = ISOAdobeCharset;
-exports.ExpertCharset = ExpertCharset;
-exports.ExpertSubsetCharset = ExpertSubsetCharset;
-
-/***/ }),
-/* 23 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var coreParser = __w_pdfjs_require__(5);
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var warn = sharedUtil.warn;
-var error = sharedUtil.error;
-var isInt = sharedUtil.isInt;
-var isString = sharedUtil.isString;
-var MissingDataException = sharedUtil.MissingDataException;
-var CMapCompressionType = sharedUtil.CMapCompressionType;
-var isEOF = corePrimitives.isEOF;
-var isName = corePrimitives.isName;
-var isCmd = corePrimitives.isCmd;
-var isStream = corePrimitives.isStream;
-var Stream = coreStream.Stream;
-var Lexer = coreParser.Lexer;
-var BUILT_IN_CMAPS = ['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'];
-var CMap = function CMapClosure() {
- function CMap(builtInCMap) {
- this.codespaceRanges = [[], [], [], []];
- this.numCodespaceRanges = 0;
- this._map = [];
- this.name = '';
- this.vertical = false;
- this.useCMap = null;
- this.builtInCMap = builtInCMap;
- }
- CMap.prototype = {
- addCodespaceRange: function (n, low, high) {
- this.codespaceRanges[n - 1].push(low, high);
- this.numCodespaceRanges++;
- },
- mapCidRange: function (low, high, dstLow) {
- while (low <= high) {
- this._map[low++] = dstLow++;
- }
- },
- mapBfRange: function (low, high, dstLow) {
- var lastByte = dstLow.length - 1;
- while (low <= high) {
- this._map[low++] = dstLow;
- dstLow = dstLow.substr(0, lastByte) + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1);
- }
- },
- mapBfRangeToArray: function (low, high, array) {
- var i = 0,
- ii = array.length;
- while (low <= high && i < ii) {
- this._map[low] = array[i++];
- ++low;
- }
- },
- mapOne: function (src, dst) {
- this._map[src] = dst;
- },
- lookup: function (code) {
- return this._map[code];
- },
- contains: function (code) {
- return this._map[code] !== undefined;
- },
- forEach: function (callback) {
- var map = this._map;
- var length = map.length;
- var i;
- if (length <= 0x10000) {
- for (i = 0; i < length; i++) {
- if (map[i] !== undefined) {
- callback(i, map[i]);
- }
- }
- } else {
- for (i in this._map) {
- callback(i, map[i]);
- }
- }
- },
- charCodeOf: function (value) {
- return this._map.indexOf(value);
- },
- getMap: function () {
- return this._map;
- },
- readCharCode: function (str, offset, out) {
- var c = 0;
- var codespaceRanges = this.codespaceRanges;
- var codespaceRangesLen = this.codespaceRanges.length;
- for (var n = 0; n < codespaceRangesLen; n++) {
- c = (c << 8 | str.charCodeAt(offset + n)) >>> 0;
- var codespaceRange = codespaceRanges[n];
- for (var k = 0, kk = codespaceRange.length; k < kk;) {
- var low = codespaceRange[k++];
- var high = codespaceRange[k++];
- if (c >= low && c <= high) {
- out.charcode = c;
- out.length = n + 1;
- return;
- }
- }
- }
- out.charcode = 0;
- out.length = 1;
- },
- get length() {
- return this._map.length;
- },
- get isIdentityCMap() {
- if (!(this.name === 'Identity-H' || this.name === 'Identity-V')) {
- return false;
- }
- if (this._map.length !== 0x10000) {
- return false;
- }
- for (var i = 0; i < 0x10000; i++) {
- if (this._map[i] !== i) {
- return false;
- }
- }
- return true;
- }
- };
- return CMap;
-}();
-var IdentityCMap = function IdentityCMapClosure() {
- function IdentityCMap(vertical, n) {
- CMap.call(this);
- this.vertical = vertical;
- this.addCodespaceRange(n, 0, 0xffff);
- }
- Util.inherit(IdentityCMap, CMap, {});
- IdentityCMap.prototype = {
- addCodespaceRange: CMap.prototype.addCodespaceRange,
- mapCidRange: function (low, high, dstLow) {
- error('should not call mapCidRange');
- },
- mapBfRange: function (low, high, dstLow) {
- error('should not call mapBfRange');
- },
- mapBfRangeToArray: function (low, high, array) {
- error('should not call mapBfRangeToArray');
- },
- mapOne: function (src, dst) {
- error('should not call mapCidOne');
- },
- lookup: function (code) {
- return isInt(code) && code <= 0xffff ? code : undefined;
- },
- contains: function (code) {
- return isInt(code) && code <= 0xffff;
- },
- forEach: function (callback) {
- for (var i = 0; i <= 0xffff; i++) {
- callback(i, i);
- }
- },
- charCodeOf: function (value) {
- return isInt(value) && value <= 0xffff ? value : -1;
- },
- getMap: function () {
- var map = new Array(0x10000);
- for (var i = 0; i <= 0xffff; i++) {
- map[i] = i;
- }
- return map;
- },
- readCharCode: CMap.prototype.readCharCode,
- get length() {
- return 0x10000;
- },
- get isIdentityCMap() {
- error('should not access .isIdentityCMap');
- }
- };
- return IdentityCMap;
-}();
-var BinaryCMapReader = function BinaryCMapReaderClosure() {
- function hexToInt(a, size) {
- var n = 0;
- for (var i = 0; i <= size; i++) {
- n = n << 8 | a[i];
- }
- return n >>> 0;
- }
- function hexToStr(a, size) {
- if (size === 1) {
- return String.fromCharCode(a[0], a[1]);
- }
- if (size === 3) {
- return String.fromCharCode(a[0], a[1], a[2], a[3]);
- }
- return String.fromCharCode.apply(null, a.subarray(0, size + 1));
- }
- function addHex(a, b, size) {
- var c = 0;
- for (var i = size; i >= 0; i--) {
- c += a[i] + b[i];
- a[i] = c & 255;
- c >>= 8;
- }
- }
- function incHex(a, size) {
- var c = 1;
- for (var i = size; i >= 0 && c > 0; i--) {
- c += a[i];
- a[i] = c & 255;
- c >>= 8;
- }
- }
- var MAX_NUM_SIZE = 16;
- var MAX_ENCODED_NUM_SIZE = 19;
- function BinaryCMapStream(data) {
- this.buffer = data;
- this.pos = 0;
- this.end = data.length;
- this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE);
- }
- BinaryCMapStream.prototype = {
- readByte: function () {
- if (this.pos >= this.end) {
- return -1;
- }
- return this.buffer[this.pos++];
- },
- readNumber: function () {
- var n = 0;
- var last;
- do {
- var b = this.readByte();
- if (b < 0) {
- error('unexpected EOF in bcmap');
- }
- last = !(b & 0x80);
- n = n << 7 | b & 0x7F;
- } while (!last);
- return n;
- },
- readSigned: function () {
- var n = this.readNumber();
- return n & 1 ? ~(n >>> 1) : n >>> 1;
- },
- readHex: function (num, size) {
- num.set(this.buffer.subarray(this.pos, this.pos + size + 1));
- this.pos += size + 1;
- },
- readHexNumber: function (num, size) {
- var last;
- var stack = this.tmpBuf,
- sp = 0;
- do {
- var b = this.readByte();
- if (b < 0) {
- error('unexpected EOF in bcmap');
- }
- last = !(b & 0x80);
- stack[sp++] = b & 0x7F;
- } while (!last);
- var i = size,
- buffer = 0,
- bufferSize = 0;
- while (i >= 0) {
- while (bufferSize < 8 && stack.length > 0) {
- buffer = stack[--sp] << bufferSize | buffer;
- bufferSize += 7;
- }
- num[i] = buffer & 255;
- i--;
- buffer >>= 8;
- bufferSize -= 8;
- }
- },
- readHexSigned: function (num, size) {
- this.readHexNumber(num, size);
- var sign = num[size] & 1 ? 255 : 0;
- var c = 0;
- for (var i = 0; i <= size; i++) {
- c = (c & 1) << 8 | num[i];
- num[i] = c >> 1 ^ sign;
- }
- },
- readString: function () {
- var len = this.readNumber();
- var s = '';
- for (var i = 0; i < len; i++) {
- s += String.fromCharCode(this.readNumber());
- }
- return s;
- }
- };
- function processBinaryCMap(data, cMap, extend) {
- return new Promise(function (resolve, reject) {
- var stream = new BinaryCMapStream(data);
- var header = stream.readByte();
- cMap.vertical = !!(header & 1);
- var useCMap = null;
- var start = new Uint8Array(MAX_NUM_SIZE);
- var end = new Uint8Array(MAX_NUM_SIZE);
- var char = new Uint8Array(MAX_NUM_SIZE);
- var charCode = new Uint8Array(MAX_NUM_SIZE);
- var tmp = new Uint8Array(MAX_NUM_SIZE);
- var code;
- var b;
- while ((b = stream.readByte()) >= 0) {
- var type = b >> 5;
- if (type === 7) {
- switch (b & 0x1F) {
- case 0:
- stream.readString();
- break;
- case 1:
- useCMap = stream.readString();
- break;
- }
- continue;
- }
- var sequence = !!(b & 0x10);
- var dataSize = b & 15;
- assert(dataSize + 1 <= MAX_NUM_SIZE);
- var ucs2DataSize = 1;
- var subitemsCount = stream.readNumber();
- var i;
- switch (type) {
- case 0:
- stream.readHex(start, dataSize);
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
- for (i = 1; i < subitemsCount; i++) {
- incHex(end, dataSize);
- stream.readHexNumber(start, dataSize);
- addHex(start, end, dataSize);
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
- }
- break;
- case 1:
- stream.readHex(start, dataSize);
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- code = stream.readNumber();
- for (i = 1; i < subitemsCount; i++) {
- incHex(end, dataSize);
- stream.readHexNumber(start, dataSize);
- addHex(start, end, dataSize);
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- code = stream.readNumber();
- }
- break;
- case 2:
- stream.readHex(char, dataSize);
- code = stream.readNumber();
- cMap.mapOne(hexToInt(char, dataSize), code);
- for (i = 1; i < subitemsCount; i++) {
- incHex(char, dataSize);
- if (!sequence) {
- stream.readHexNumber(tmp, dataSize);
- addHex(char, tmp, dataSize);
- }
- code = stream.readSigned() + (code + 1);
- cMap.mapOne(hexToInt(char, dataSize), code);
- }
- break;
- case 3:
- stream.readHex(start, dataSize);
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- code = stream.readNumber();
- cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
- for (i = 1; i < subitemsCount; i++) {
- incHex(end, dataSize);
- if (!sequence) {
- stream.readHexNumber(start, dataSize);
- addHex(start, end, dataSize);
- } else {
- start.set(end);
- }
- stream.readHexNumber(end, dataSize);
- addHex(end, start, dataSize);
- code = stream.readNumber();
- cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
- }
- break;
- case 4:
- stream.readHex(char, ucs2DataSize);
- stream.readHex(charCode, dataSize);
- cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
- for (i = 1; i < subitemsCount; i++) {
- incHex(char, ucs2DataSize);
- if (!sequence) {
- stream.readHexNumber(tmp, ucs2DataSize);
- addHex(char, tmp, ucs2DataSize);
- }
- incHex(charCode, dataSize);
- stream.readHexSigned(tmp, dataSize);
- addHex(charCode, tmp, dataSize);
- cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
- }
- break;
- case 5:
- stream.readHex(start, ucs2DataSize);
- stream.readHexNumber(end, ucs2DataSize);
- addHex(end, start, ucs2DataSize);
- stream.readHex(charCode, dataSize);
- cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
- for (i = 1; i < subitemsCount; i++) {
- incHex(end, ucs2DataSize);
- if (!sequence) {
- stream.readHexNumber(start, ucs2DataSize);
- addHex(start, end, ucs2DataSize);
- } else {
- start.set(end);
- }
- stream.readHexNumber(end, ucs2DataSize);
- addHex(end, start, ucs2DataSize);
- stream.readHex(charCode, dataSize);
- cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
- }
- break;
- default:
- reject(new Error('processBinaryCMap: Unknown type: ' + type));
- return;
- }
- }
- if (useCMap) {
- resolve(extend(useCMap));
- return;
- }
- resolve(cMap);
- });
- }
- function BinaryCMapReader() {}
- BinaryCMapReader.prototype = { process: processBinaryCMap };
- return BinaryCMapReader;
-}();
-var CMapFactory = function CMapFactoryClosure() {
- function strToInt(str) {
- var a = 0;
- for (var i = 0; i < str.length; i++) {
- a = a << 8 | str.charCodeAt(i);
- }
- return a >>> 0;
- }
- function expectString(obj) {
- if (!isString(obj)) {
- error('Malformed CMap: expected string.');
- }
- }
- function expectInt(obj) {
- if (!isInt(obj)) {
- error('Malformed CMap: expected int.');
- }
- }
- function parseBfChar(cMap, lexer) {
- while (true) {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- }
- if (isCmd(obj, 'endbfchar')) {
- return;
- }
- expectString(obj);
- var src = strToInt(obj);
- obj = lexer.getObj();
- expectString(obj);
- var dst = obj;
- cMap.mapOne(src, dst);
- }
- }
- function parseBfRange(cMap, lexer) {
- while (true) {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- }
- if (isCmd(obj, 'endbfrange')) {
- return;
- }
- expectString(obj);
- var low = strToInt(obj);
- obj = lexer.getObj();
- expectString(obj);
- var high = strToInt(obj);
- obj = lexer.getObj();
- if (isInt(obj) || isString(obj)) {
- var dstLow = isInt(obj) ? String.fromCharCode(obj) : obj;
- cMap.mapBfRange(low, high, dstLow);
- } else if (isCmd(obj, '[')) {
- obj = lexer.getObj();
- var array = [];
- while (!isCmd(obj, ']') && !isEOF(obj)) {
- array.push(obj);
- obj = lexer.getObj();
- }
- cMap.mapBfRangeToArray(low, high, array);
- } else {
- break;
- }
- }
- error('Invalid bf range.');
- }
- function parseCidChar(cMap, lexer) {
- while (true) {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- }
- if (isCmd(obj, 'endcidchar')) {
- return;
- }
- expectString(obj);
- var src = strToInt(obj);
- obj = lexer.getObj();
- expectInt(obj);
- var dst = obj;
- cMap.mapOne(src, dst);
- }
- }
- function parseCidRange(cMap, lexer) {
- while (true) {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- }
- if (isCmd(obj, 'endcidrange')) {
- return;
- }
- expectString(obj);
- var low = strToInt(obj);
- obj = lexer.getObj();
- expectString(obj);
- var high = strToInt(obj);
- obj = lexer.getObj();
- expectInt(obj);
- var dstLow = obj;
- cMap.mapCidRange(low, high, dstLow);
- }
- }
- function parseCodespaceRange(cMap, lexer) {
- while (true) {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- }
- if (isCmd(obj, 'endcodespacerange')) {
- return;
- }
- if (!isString(obj)) {
- break;
- }
- var low = strToInt(obj);
- obj = lexer.getObj();
- if (!isString(obj)) {
- break;
- }
- var high = strToInt(obj);
- cMap.addCodespaceRange(obj.length, low, high);
- }
- error('Invalid codespace range.');
- }
- function parseWMode(cMap, lexer) {
- var obj = lexer.getObj();
- if (isInt(obj)) {
- cMap.vertical = !!obj;
- }
- }
- function parseCMapName(cMap, lexer) {
- var obj = lexer.getObj();
- if (isName(obj) && isString(obj.name)) {
- cMap.name = obj.name;
- }
- }
- function parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap) {
- var previous;
- var embededUseCMap;
- objLoop: while (true) {
- try {
- var obj = lexer.getObj();
- if (isEOF(obj)) {
- break;
- } else if (isName(obj)) {
- if (obj.name === 'WMode') {
- parseWMode(cMap, lexer);
- } else if (obj.name === 'CMapName') {
- parseCMapName(cMap, lexer);
- }
- previous = obj;
- } else if (isCmd(obj)) {
- switch (obj.cmd) {
- case 'endcmap':
- break objLoop;
- case 'usecmap':
- if (isName(previous)) {
- embededUseCMap = previous.name;
- }
- break;
- case 'begincodespacerange':
- parseCodespaceRange(cMap, lexer);
- break;
- case 'beginbfchar':
- parseBfChar(cMap, lexer);
- break;
- case 'begincidchar':
- parseCidChar(cMap, lexer);
- break;
- case 'beginbfrange':
- parseBfRange(cMap, lexer);
- break;
- case 'begincidrange':
- parseCidRange(cMap, lexer);
- break;
- }
- }
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- warn('Invalid cMap data: ' + ex);
- continue;
- }
- }
- if (!useCMap && embededUseCMap) {
- useCMap = embededUseCMap;
- }
- if (useCMap) {
- return extendCMap(cMap, fetchBuiltInCMap, useCMap);
- }
- return Promise.resolve(cMap);
- }
- function extendCMap(cMap, fetchBuiltInCMap, useCMap) {
- return createBuiltInCMap(useCMap, fetchBuiltInCMap).then(function (newCMap) {
- cMap.useCMap = newCMap;
- if (cMap.numCodespaceRanges === 0) {
- var useCodespaceRanges = cMap.useCMap.codespaceRanges;
- for (var i = 0; i < useCodespaceRanges.length; i++) {
- cMap.codespaceRanges[i] = useCodespaceRanges[i].slice();
- }
- cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges;
- }
- cMap.useCMap.forEach(function (key, value) {
- if (!cMap.contains(key)) {
- cMap.mapOne(key, cMap.useCMap.lookup(key));
- }
- });
- return cMap;
- });
- }
- function createBuiltInCMap(name, fetchBuiltInCMap) {
- if (name === 'Identity-H') {
- return Promise.resolve(new IdentityCMap(false, 2));
- } else if (name === 'Identity-V') {
- return Promise.resolve(new IdentityCMap(true, 2));
- }
- if (BUILT_IN_CMAPS.indexOf(name) === -1) {
- return Promise.reject(new Error('Unknown CMap name: ' + name));
- }
- assert(fetchBuiltInCMap, 'Built-in CMap parameters are not provided.');
- return fetchBuiltInCMap(name).then(function (data) {
- var cMapData = data.cMapData,
- compressionType = data.compressionType;
- var cMap = new CMap(true);
- if (compressionType === CMapCompressionType.BINARY) {
- return new BinaryCMapReader().process(cMapData, cMap, function (useCMap) {
- return extendCMap(cMap, fetchBuiltInCMap, useCMap);
- });
- }
- assert(compressionType === CMapCompressionType.NONE, 'TODO: Only BINARY/NONE CMap compression is currently supported.');
- var lexer = new Lexer(new Stream(cMapData));
- return parseCMap(cMap, lexer, fetchBuiltInCMap, null);
- });
- }
- return {
- create: function (params) {
- var encoding = params.encoding;
- var fetchBuiltInCMap = params.fetchBuiltInCMap;
- var useCMap = params.useCMap;
- if (isName(encoding)) {
- return createBuiltInCMap(encoding.name, fetchBuiltInCMap);
- } else if (isStream(encoding)) {
- var cMap = new CMap();
- var lexer = new Lexer(encoding);
- return parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap).then(function (parsedCMap) {
- if (parsedCMap.isIdentityCMap) {
- return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap);
- }
- return parsedCMap;
- });
- }
- return Promise.reject(new Error('Encoding required.'));
- }
- };
-}();
-exports.CMap = CMap;
-exports.CMapFactory = CMapFactory;
-exports.IdentityCMap = IdentityCMap;
-
-/***/ }),
-/* 24 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var coreObj = __w_pdfjs_require__(16);
-var coreParser = __w_pdfjs_require__(5);
-var coreCrypto = __w_pdfjs_require__(13);
-var coreEvaluator = __w_pdfjs_require__(14);
-var coreAnnotation = __w_pdfjs_require__(20);
-var OPS = sharedUtil.OPS;
-var MissingDataException = sharedUtil.MissingDataException;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isArrayBuffer = sharedUtil.isArrayBuffer;
-var isNum = sharedUtil.isNum;
-var isString = sharedUtil.isString;
-var shadow = sharedUtil.shadow;
-var stringToBytes = sharedUtil.stringToBytes;
-var stringToPDFString = sharedUtil.stringToPDFString;
-var warn = sharedUtil.warn;
-var isSpace = sharedUtil.isSpace;
-var Dict = corePrimitives.Dict;
-var isDict = corePrimitives.isDict;
-var isName = corePrimitives.isName;
-var isStream = corePrimitives.isStream;
-var NullStream = coreStream.NullStream;
-var Stream = coreStream.Stream;
-var StreamsSequenceStream = coreStream.StreamsSequenceStream;
-var Catalog = coreObj.Catalog;
-var ObjectLoader = coreObj.ObjectLoader;
-var XRef = coreObj.XRef;
-var Linearization = coreParser.Linearization;
-var calculateMD5 = coreCrypto.calculateMD5;
-var OperatorList = coreEvaluator.OperatorList;
-var PartialEvaluator = coreEvaluator.PartialEvaluator;
-var AnnotationFactory = coreAnnotation.AnnotationFactory;
-var Page = function PageClosure() {
- var DEFAULT_USER_UNIT = 1.0;
- var LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
- function isAnnotationRenderable(annotation, intent) {
- return intent === 'display' && annotation.viewable || intent === 'print' && annotation.printable;
- }
- function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache, builtInCMapCache) {
- this.pdfManager = pdfManager;
- this.pageIndex = pageIndex;
- this.pageDict = pageDict;
- this.xref = xref;
- this.ref = ref;
- this.fontCache = fontCache;
- this.builtInCMapCache = builtInCMapCache;
- this.evaluatorOptions = pdfManager.evaluatorOptions;
- this.resourcesPromise = null;
- var uniquePrefix = 'p' + this.pageIndex + '_';
- var idCounters = { obj: 0 };
- this.idFactory = {
- createObjId: function () {
- return uniquePrefix + ++idCounters.obj;
- }
- };
- }
- Page.prototype = {
- getPageProp: function Page_getPageProp(key) {
- return this.pageDict.get(key);
- },
- getInheritedPageProp: function Page_getInheritedPageProp(key, getArray) {
- var dict = this.pageDict,
- valueArray = null,
- loopCount = 0;
- var MAX_LOOP_COUNT = 100;
- getArray = getArray || false;
- while (dict) {
- var value = getArray ? dict.getArray(key) : dict.get(key);
- if (value !== undefined) {
- if (!valueArray) {
- valueArray = [];
- }
- valueArray.push(value);
- }
- if (++loopCount > MAX_LOOP_COUNT) {
- warn('getInheritedPageProp: maximum loop count exceeded for ' + key);
- return valueArray ? valueArray[0] : undefined;
- }
- dict = dict.get('Parent');
- }
- if (!valueArray) {
- return undefined;
- }
- if (valueArray.length === 1 || !isDict(valueArray[0])) {
- return valueArray[0];
- }
- return Dict.merge(this.xref, valueArray);
- },
- get content() {
- return this.getPageProp('Contents');
- },
- get resources() {
- return shadow(this, 'resources', this.getInheritedPageProp('Resources') || Dict.empty);
- },
- get mediaBox() {
- var mediaBox = this.getInheritedPageProp('MediaBox', true);
- if (!isArray(mediaBox) || mediaBox.length !== 4) {
- return shadow(this, 'mediaBox', LETTER_SIZE_MEDIABOX);
- }
- return shadow(this, 'mediaBox', mediaBox);
- },
- get cropBox() {
- var cropBox = this.getInheritedPageProp('CropBox', true);
- if (!isArray(cropBox) || cropBox.length !== 4) {
- return shadow(this, 'cropBox', this.mediaBox);
- }
- return shadow(this, 'cropBox', cropBox);
- },
- get userUnit() {
- var obj = this.getPageProp('UserUnit');
- if (!isNum(obj) || obj <= 0) {
- obj = DEFAULT_USER_UNIT;
- }
- return shadow(this, 'userUnit', obj);
- },
- get view() {
- var mediaBox = this.mediaBox,
- cropBox = this.cropBox;
- if (mediaBox === cropBox) {
- return shadow(this, 'view', mediaBox);
- }
- var intersection = Util.intersect(cropBox, mediaBox);
- return shadow(this, 'view', intersection || mediaBox);
- },
- get rotate() {
- var rotate = this.getInheritedPageProp('Rotate') || 0;
- if (rotate % 90 !== 0) {
- rotate = 0;
- } else if (rotate >= 360) {
- rotate = rotate % 360;
- } else if (rotate < 0) {
- rotate = (rotate % 360 + 360) % 360;
- }
- return shadow(this, 'rotate', rotate);
- },
- getContentStream: function Page_getContentStream() {
- var content = this.content;
- var stream;
- if (isArray(content)) {
- var xref = this.xref;
- var i,
- n = content.length;
- var streams = [];
- for (i = 0; i < n; ++i) {
- streams.push(xref.fetchIfRef(content[i]));
- }
- stream = new StreamsSequenceStream(streams);
- } else if (isStream(content)) {
- stream = content;
- } else {
- stream = new NullStream();
- }
- return stream;
- },
- loadResources: function Page_loadResources(keys) {
- if (!this.resourcesPromise) {
- this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
- }
- return this.resourcesPromise.then(function resourceSuccess() {
- var objectLoader = new ObjectLoader(this.resources.map, keys, this.xref);
- return objectLoader.load();
- }.bind(this));
- },
- getOperatorList: function Page_getOperatorList(handler, task, intent, renderInteractiveForms) {
- var self = this;
- var pdfManager = this.pdfManager;
- var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
- var resourcesPromise = this.loadResources(['ExtGState', 'ColorSpace', 'Pattern', 'Shading', 'XObject', 'Font']);
- var partialEvaluator = new PartialEvaluator(pdfManager, this.xref, handler, this.pageIndex, this.idFactory, this.fontCache, this.builtInCMapCache, this.evaluatorOptions);
- var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
- var pageListPromise = dataPromises.then(function (data) {
- var contentStream = data[0];
- var opList = new OperatorList(intent, handler, self.pageIndex);
- handler.send('StartRenderPage', {
- transparency: partialEvaluator.hasBlendModes(self.resources),
- pageIndex: self.pageIndex,
- intent: intent
- });
- return partialEvaluator.getOperatorList(contentStream, task, self.resources, opList).then(function () {
- return opList;
- });
- });
- var annotationsPromise = pdfManager.ensure(this, 'annotations');
- return Promise.all([pageListPromise, annotationsPromise]).then(function (datas) {
- var pageOpList = datas[0];
- var annotations = datas[1];
- if (annotations.length === 0) {
- pageOpList.flush(true);
- return pageOpList;
- }
- var i,
- ii,
- opListPromises = [];
- for (i = 0, ii = annotations.length; i < ii; i++) {
- if (isAnnotationRenderable(annotations[i], intent)) {
- opListPromises.push(annotations[i].getOperatorList(partialEvaluator, task, renderInteractiveForms));
- }
- }
- return Promise.all(opListPromises).then(function (opLists) {
- pageOpList.addOp(OPS.beginAnnotations, []);
- for (i = 0, ii = opLists.length; i < ii; i++) {
- pageOpList.addOpList(opLists[i]);
- }
- pageOpList.addOp(OPS.endAnnotations, []);
- pageOpList.flush(true);
- return pageOpList;
- });
- });
- },
- extractTextContent: function Page_extractTextContent(handler, task, normalizeWhitespace, combineTextItems) {
- var self = this;
- var pdfManager = this.pdfManager;
- var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
- var resourcesPromise = this.loadResources(['ExtGState', 'XObject', 'Font']);
- var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
- return dataPromises.then(function (data) {
- var contentStream = data[0];
- var partialEvaluator = new PartialEvaluator(pdfManager, self.xref, handler, self.pageIndex, self.idFactory, self.fontCache, self.builtInCMapCache, self.evaluatorOptions);
- return partialEvaluator.getTextContent(contentStream, task, self.resources, null, normalizeWhitespace, combineTextItems);
- });
- },
- getAnnotationsData: function Page_getAnnotationsData(intent) {
- var annotations = this.annotations;
- var annotationsData = [];
- for (var i = 0, n = annotations.length; i < n; ++i) {
- if (!intent || isAnnotationRenderable(annotations[i], intent)) {
- annotationsData.push(annotations[i].data);
- }
- }
- return annotationsData;
- },
- get annotations() {
- var annotations = [];
- var annotationRefs = this.getInheritedPageProp('Annots') || [];
- var annotationFactory = new AnnotationFactory();
- for (var i = 0, n = annotationRefs.length; i < n; ++i) {
- var annotationRef = annotationRefs[i];
- var annotation = annotationFactory.create(this.xref, annotationRef, this.pdfManager, this.idFactory);
- if (annotation) {
- annotations.push(annotation);
- }
- }
- return shadow(this, 'annotations', annotations);
- }
- };
- return Page;
-}();
-var PDFDocument = function PDFDocumentClosure() {
- var FINGERPRINT_FIRST_BYTES = 1024;
- var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00\x00';
- function PDFDocument(pdfManager, arg) {
- var stream;
- if (isStream(arg)) {
- stream = arg;
- } else if (isArrayBuffer(arg)) {
- stream = new Stream(arg);
- } else {
- error('PDFDocument: Unknown argument type');
- }
- assert(stream.length > 0, 'stream must have data');
- this.pdfManager = pdfManager;
- this.stream = stream;
- this.xref = new XRef(stream, pdfManager);
- }
- function find(stream, needle, limit, backwards) {
- var pos = stream.pos;
- var end = stream.end;
- var strBuf = [];
- if (pos + limit > end) {
- limit = end - pos;
- }
- for (var n = 0; n < limit; ++n) {
- strBuf.push(String.fromCharCode(stream.getByte()));
- }
- var str = strBuf.join('');
- stream.pos = pos;
- var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
- if (index === -1) {
- return false;
- }
- stream.pos += index;
- return true;
- }
- var DocumentInfoValidators = {
- get entries() {
- return shadow(this, 'entries', {
- Title: isString,
- Author: isString,
- Subject: isString,
- Keywords: isString,
- Creator: isString,
- Producer: isString,
- CreationDate: isString,
- ModDate: isString,
- Trapped: isName
- });
- }
- };
- PDFDocument.prototype = {
- parse: function PDFDocument_parse(recoveryMode) {
- this.setup(recoveryMode);
- var version = this.catalog.catDict.get('Version');
- if (isName(version)) {
- this.pdfFormatVersion = version.name;
- }
- try {
- this.acroForm = this.catalog.catDict.get('AcroForm');
- if (this.acroForm) {
- this.xfa = this.acroForm.get('XFA');
- var fields = this.acroForm.get('Fields');
- if ((!fields || !isArray(fields) || fields.length === 0) && !this.xfa) {
- this.acroForm = null;
- }
- }
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- info('Something wrong with AcroForm entry');
- this.acroForm = null;
- }
- },
- get linearization() {
- var linearization = null;
- if (this.stream.length) {
- try {
- linearization = Linearization.create(this.stream);
- } catch (err) {
- if (err instanceof MissingDataException) {
- throw err;
- }
- info(err);
- }
- }
- return shadow(this, 'linearization', linearization);
- },
- get startXRef() {
- var stream = this.stream;
- var startXRef = 0;
- var linearization = this.linearization;
- if (linearization) {
- stream.reset();
- if (find(stream, 'endobj', 1024)) {
- startXRef = stream.pos + 6;
- }
- } else {
- var step = 1024;
- var found = false,
- pos = stream.end;
- while (!found && pos > 0) {
- pos -= step - 'startxref'.length;
- if (pos < 0) {
- pos = 0;
- }
- stream.pos = pos;
- found = find(stream, 'startxref', step, true);
- }
- if (found) {
- stream.skip(9);
- var ch;
- do {
- ch = stream.getByte();
- } while (isSpace(ch));
- var str = '';
- while (ch >= 0x20 && ch <= 0x39) {
- str += String.fromCharCode(ch);
- ch = stream.getByte();
- }
- startXRef = parseInt(str, 10);
- if (isNaN(startXRef)) {
- startXRef = 0;
- }
- }
- }
- return shadow(this, 'startXRef', startXRef);
- },
- get mainXRefEntriesOffset() {
- var mainXRefEntriesOffset = 0;
- var linearization = this.linearization;
- if (linearization) {
- mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
- }
- return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
- },
- checkHeader: function PDFDocument_checkHeader() {
- var stream = this.stream;
- stream.reset();
- if (find(stream, '%PDF-', 1024)) {
- stream.moveStart();
- var MAX_VERSION_LENGTH = 12;
- var version = '',
- ch;
- while ((ch = stream.getByte()) > 0x20) {
- if (version.length >= MAX_VERSION_LENGTH) {
- break;
- }
- version += String.fromCharCode(ch);
- }
- if (!this.pdfFormatVersion) {
- this.pdfFormatVersion = version.substring(5);
- }
- return;
- }
- },
- parseStartXRef: function PDFDocument_parseStartXRef() {
- var startXRef = this.startXRef;
- this.xref.setStartXRef(startXRef);
- },
- setup: function PDFDocument_setup(recoveryMode) {
- this.xref.parse(recoveryMode);
- var self = this;
- var pageFactory = {
- createPage: function (pageIndex, dict, ref, fontCache, builtInCMapCache) {
- return new Page(self.pdfManager, self.xref, pageIndex, dict, ref, fontCache, builtInCMapCache);
- }
- };
- this.catalog = new Catalog(this.pdfManager, this.xref, pageFactory);
- },
- get numPages() {
- var linearization = this.linearization;
- var num = linearization ? linearization.numPages : this.catalog.numPages;
- return shadow(this, 'numPages', num);
- },
- get documentInfo() {
- var docInfo = {
- PDFFormatVersion: this.pdfFormatVersion,
- IsAcroFormPresent: !!this.acroForm,
- IsXFAPresent: !!this.xfa
- };
- var infoDict;
- try {
- infoDict = this.xref.trailer.get('Info');
- } catch (err) {
- if (err instanceof MissingDataException) {
- throw err;
- }
- info('The document information dictionary is invalid.');
- }
- if (infoDict) {
- var validEntries = DocumentInfoValidators.entries;
- for (var key in validEntries) {
- if (infoDict.has(key)) {
- var value = infoDict.get(key);
- if (validEntries[key](value)) {
- docInfo[key] = typeof value !== 'string' ? value : stringToPDFString(value);
- } else {
- info('Bad value in document info for "' + key + '"');
- }
- }
- }
- }
- return shadow(this, 'documentInfo', docInfo);
- },
- get fingerprint() {
- var xref = this.xref,
- hash,
- fileID = '';
- var idArray = xref.trailer.get('ID');
- if (idArray && isArray(idArray) && idArray[0] && isString(idArray[0]) && idArray[0] !== EMPTY_FINGERPRINT) {
- hash = stringToBytes(idArray[0]);
- } else {
- if (this.stream.ensureRange) {
- this.stream.ensureRange(0, Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end));
- }
- hash = calculateMD5(this.stream.bytes.subarray(0, FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
- }
- for (var i = 0, n = hash.length; i < n; i++) {
- var hex = hash[i].toString(16);
- fileID += hex.length === 1 ? '0' + hex : hex;
- }
- return shadow(this, 'fingerprint', fileID);
- },
- getPage: function PDFDocument_getPage(pageIndex) {
- return this.catalog.getPage(pageIndex);
- },
- cleanup: function PDFDocument_cleanup() {
- return this.catalog.cleanup();
- }
- };
- return PDFDocument;
-}();
-exports.Page = Page;
-exports.PDFDocument = PDFDocument;
-
-/***/ }),
-/* 25 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreStream = __w_pdfjs_require__(2);
-var coreGlyphList = __w_pdfjs_require__(7);
-var coreEncodings = __w_pdfjs_require__(4);
-var coreCFFParser = __w_pdfjs_require__(11);
-var Util = sharedUtil.Util;
-var bytesToString = sharedUtil.bytesToString;
-var error = sharedUtil.error;
-var Stream = coreStream.Stream;
-var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
-var StandardEncoding = coreEncodings.StandardEncoding;
-var CFFParser = coreCFFParser.CFFParser;
-var FontRendererFactory = function FontRendererFactoryClosure() {
- function getLong(data, offset) {
- return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
- }
- function getUshort(data, offset) {
- return data[offset] << 8 | data[offset + 1];
- }
- function parseCmap(data, start, end) {
- var offset = getUshort(data, start + 2) === 1 ? getLong(data, start + 8) : getLong(data, start + 16);
- var format = getUshort(data, start + offset);
- var ranges, p, i;
- if (format === 4) {
- getUshort(data, start + offset + 2);
- var segCount = getUshort(data, start + offset + 6) >> 1;
- p = start + offset + 14;
- ranges = [];
- for (i = 0; i < segCount; i++, p += 2) {
- ranges[i] = { end: getUshort(data, p) };
- }
- p += 2;
- for (i = 0; i < segCount; i++, p += 2) {
- ranges[i].start = getUshort(data, p);
- }
- for (i = 0; i < segCount; i++, p += 2) {
- ranges[i].idDelta = getUshort(data, p);
- }
- for (i = 0; i < segCount; i++, p += 2) {
- var idOffset = getUshort(data, p);
- if (idOffset === 0) {
- continue;
- }
- ranges[i].ids = [];
- for (var j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
- ranges[i].ids[j] = getUshort(data, p + idOffset);
- idOffset += 2;
- }
- }
- return ranges;
- } else if (format === 12) {
- getLong(data, start + offset + 4);
- var groups = getLong(data, start + offset + 12);
- p = start + offset + 16;
- ranges = [];
- for (i = 0; i < groups; i++) {
- ranges.push({
- start: getLong(data, p),
- end: getLong(data, p + 4),
- idDelta: getLong(data, p + 8) - getLong(data, p)
- });
- p += 12;
- }
- return ranges;
- }
- error('not supported cmap: ' + format);
- }
- function parseCff(data, start, end, seacAnalysisEnabled) {
- var properties = {};
- var parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled);
- var cff = parser.parse();
- return {
- glyphs: cff.charStrings.objects,
- subrs: cff.topDict.privateDict && cff.topDict.privateDict.subrsIndex && cff.topDict.privateDict.subrsIndex.objects,
- gsubrs: cff.globalSubrIndex && cff.globalSubrIndex.objects
- };
- }
- function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
- var itemSize, itemDecode;
- if (isGlyphLocationsLong) {
- itemSize = 4;
- itemDecode = function fontItemDecodeLong(data, offset) {
- return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
- };
- } else {
- itemSize = 2;
- itemDecode = function fontItemDecode(data, offset) {
- return data[offset] << 9 | data[offset + 1] << 1;
- };
- }
- var glyphs = [];
- var startOffset = itemDecode(loca, 0);
- for (var j = itemSize; j < loca.length; j += itemSize) {
- var endOffset = itemDecode(loca, j);
- glyphs.push(glyf.subarray(startOffset, endOffset));
- startOffset = endOffset;
- }
- return glyphs;
- }
- function lookupCmap(ranges, unicode) {
- var code = unicode.charCodeAt(0),
- gid = 0;
- var l = 0,
- r = ranges.length - 1;
- while (l < r) {
- var c = l + r + 1 >> 1;
- if (code < ranges[c].start) {
- r = c - 1;
- } else {
- l = c;
- }
- }
- if (ranges[l].start <= code && code <= ranges[l].end) {
- gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xFFFF;
- }
- return {
- charCode: code,
- glyphId: gid
- };
- }
- function compileGlyf(code, cmds, font) {
- function moveTo(x, y) {
- cmds.push({
- cmd: 'moveTo',
- args: [x, y]
- });
- }
- function lineTo(x, y) {
- cmds.push({
- cmd: 'lineTo',
- args: [x, y]
- });
- }
- function quadraticCurveTo(xa, ya, x, y) {
- cmds.push({
- cmd: 'quadraticCurveTo',
- args: [xa, ya, x, y]
- });
- }
- var i = 0;
- var numberOfContours = (code[i] << 24 | code[i + 1] << 16) >> 16;
- var flags;
- var x = 0,
- y = 0;
- i += 10;
- if (numberOfContours < 0) {
- do {
- flags = code[i] << 8 | code[i + 1];
- var glyphIndex = code[i + 2] << 8 | code[i + 3];
- i += 4;
- var arg1, arg2;
- if (flags & 0x01) {
- arg1 = (code[i] << 24 | code[i + 1] << 16) >> 16;
- arg2 = (code[i + 2] << 24 | code[i + 3] << 16) >> 16;
- i += 4;
- } else {
- arg1 = code[i++];
- arg2 = code[i++];
- }
- if (flags & 0x02) {
- x = arg1;
- y = arg2;
- } else {
- x = 0;
- y = 0;
- }
- var scaleX = 1,
- scaleY = 1,
- scale01 = 0,
- scale10 = 0;
- if (flags & 0x08) {
- scaleX = scaleY = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
- i += 2;
- } else if (flags & 0x40) {
- scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
- scaleY = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
- i += 4;
- } else if (flags & 0x80) {
- scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
- scale01 = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
- scale10 = (code[i + 4] << 24 | code[i + 5] << 16) / 1073741824;
- scaleY = (code[i + 6] << 24 | code[i + 7] << 16) / 1073741824;
- i += 8;
- }
- var subglyph = font.glyphs[glyphIndex];
- if (subglyph) {
- cmds.push({ cmd: 'save' });
- cmds.push({
- cmd: 'transform',
- args: [scaleX, scale01, scale10, scaleY, x, y]
- });
- compileGlyf(subglyph, cmds, font);
- cmds.push({ cmd: 'restore' });
- }
- } while (flags & 0x20);
- } else {
- var endPtsOfContours = [];
- var j, jj;
- for (j = 0; j < numberOfContours; j++) {
- endPtsOfContours.push(code[i] << 8 | code[i + 1]);
- i += 2;
- }
- var instructionLength = code[i] << 8 | code[i + 1];
- i += 2 + instructionLength;
- var numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
- var points = [];
- while (points.length < numberOfPoints) {
- flags = code[i++];
- var repeat = 1;
- if (flags & 0x08) {
- repeat += code[i++];
- }
- while (repeat-- > 0) {
- points.push({ flags: flags });
- }
- }
- for (j = 0; j < numberOfPoints; j++) {
- switch (points[j].flags & 0x12) {
- case 0x00:
- x += (code[i] << 24 | code[i + 1] << 16) >> 16;
- i += 2;
- break;
- case 0x02:
- x -= code[i++];
- break;
- case 0x12:
- x += code[i++];
- break;
- }
- points[j].x = x;
- }
- for (j = 0; j < numberOfPoints; j++) {
- switch (points[j].flags & 0x24) {
- case 0x00:
- y += (code[i] << 24 | code[i + 1] << 16) >> 16;
- i += 2;
- break;
- case 0x04:
- y -= code[i++];
- break;
- case 0x24:
- y += code[i++];
- break;
- }
- points[j].y = y;
- }
- var startPoint = 0;
- for (i = 0; i < numberOfContours; i++) {
- var endPoint = endPtsOfContours[i];
- var contour = points.slice(startPoint, endPoint + 1);
- if (contour[0].flags & 1) {
- contour.push(contour[0]);
- } else if (contour[contour.length - 1].flags & 1) {
- contour.unshift(contour[contour.length - 1]);
- } else {
- var p = {
- flags: 1,
- x: (contour[0].x + contour[contour.length - 1].x) / 2,
- y: (contour[0].y + contour[contour.length - 1].y) / 2
- };
- contour.unshift(p);
- contour.push(p);
- }
- moveTo(contour[0].x, contour[0].y);
- for (j = 1, jj = contour.length; j < jj; j++) {
- if (contour[j].flags & 1) {
- lineTo(contour[j].x, contour[j].y);
- } else if (contour[j + 1].flags & 1) {
- quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y);
- j++;
- } else {
- quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2);
- }
- }
- startPoint = endPoint + 1;
- }
- }
- }
- function compileCharString(code, cmds, font) {
- var stack = [];
- var x = 0,
- y = 0;
- var stems = 0;
- function moveTo(x, y) {
- cmds.push({
- cmd: 'moveTo',
- args: [x, y]
- });
- }
- function lineTo(x, y) {
- cmds.push({
- cmd: 'lineTo',
- args: [x, y]
- });
- }
- function bezierCurveTo(x1, y1, x2, y2, x, y) {
- cmds.push({
- cmd: 'bezierCurveTo',
- args: [x1, y1, x2, y2, x, y]
- });
- }
- function parse(code) {
- var i = 0;
- while (i < code.length) {
- var stackClean = false;
- var v = code[i++];
- var xa, xb, ya, yb, y1, y2, y3, n, subrCode;
- switch (v) {
- case 1:
- stems += stack.length >> 1;
- stackClean = true;
- break;
- case 3:
- stems += stack.length >> 1;
- stackClean = true;
- break;
- case 4:
- y += stack.pop();
- moveTo(x, y);
- stackClean = true;
- break;
- case 5:
- while (stack.length > 0) {
- x += stack.shift();
- y += stack.shift();
- lineTo(x, y);
- }
- break;
- case 6:
- while (stack.length > 0) {
- x += stack.shift();
- lineTo(x, y);
- if (stack.length === 0) {
- break;
- }
- y += stack.shift();
- lineTo(x, y);
- }
- break;
- case 7:
- while (stack.length > 0) {
- y += stack.shift();
- lineTo(x, y);
- if (stack.length === 0) {
- break;
- }
- x += stack.shift();
- lineTo(x, y);
- }
- break;
- case 8:
- while (stack.length > 0) {
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- break;
- case 10:
- n = stack.pop() + font.subrsBias;
- subrCode = font.subrs[n];
- if (subrCode) {
- parse(subrCode);
- }
- break;
- case 11:
- return;
- case 12:
- v = code[i++];
- switch (v) {
- case 34:
- xa = x + stack.shift();
- xb = xa + stack.shift();
- y1 = y + stack.shift();
- x = xb + stack.shift();
- bezierCurveTo(xa, y, xb, y1, x, y1);
- xa = x + stack.shift();
- xb = xa + stack.shift();
- x = xb + stack.shift();
- bezierCurveTo(xa, y1, xb, y, x, y);
- break;
- case 35:
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- stack.pop();
- break;
- case 36:
- xa = x + stack.shift();
- y1 = y + stack.shift();
- xb = xa + stack.shift();
- y2 = y1 + stack.shift();
- x = xb + stack.shift();
- bezierCurveTo(xa, y1, xb, y2, x, y2);
- xa = x + stack.shift();
- xb = xa + stack.shift();
- y3 = y2 + stack.shift();
- x = xb + stack.shift();
- bezierCurveTo(xa, y2, xb, y3, x, y);
- break;
- case 37:
- var x0 = x,
- y0 = y;
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb;
- y = yb;
- if (Math.abs(x - x0) > Math.abs(y - y0)) {
- x += stack.shift();
- } else {
- y += stack.shift();
- }
- bezierCurveTo(xa, ya, xb, yb, x, y);
- break;
- default:
- error('unknown operator: 12 ' + v);
- }
- break;
- case 14:
- if (stack.length >= 4) {
- var achar = stack.pop();
- var bchar = stack.pop();
- y = stack.pop();
- x = stack.pop();
- cmds.push({ cmd: 'save' });
- cmds.push({
- cmd: 'translate',
- args: [x, y]
- });
- var cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]));
- compileCharString(font.glyphs[cmap.glyphId], cmds, font);
- cmds.push({ cmd: 'restore' });
- cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]));
- compileCharString(font.glyphs[cmap.glyphId], cmds, font);
- }
- return;
- case 18:
- stems += stack.length >> 1;
- stackClean = true;
- break;
- case 19:
- stems += stack.length >> 1;
- i += stems + 7 >> 3;
- stackClean = true;
- break;
- case 20:
- stems += stack.length >> 1;
- i += stems + 7 >> 3;
- stackClean = true;
- break;
- case 21:
- y += stack.pop();
- x += stack.pop();
- moveTo(x, y);
- stackClean = true;
- break;
- case 22:
- x += stack.pop();
- moveTo(x, y);
- stackClean = true;
- break;
- case 23:
- stems += stack.length >> 1;
- stackClean = true;
- break;
- case 24:
- while (stack.length > 2) {
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- x += stack.shift();
- y += stack.shift();
- lineTo(x, y);
- break;
- case 25:
- while (stack.length > 6) {
- x += stack.shift();
- y += stack.shift();
- lineTo(x, y);
- }
- xa = x + stack.shift();
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- break;
- case 26:
- if (stack.length % 2) {
- x += stack.shift();
- }
- while (stack.length > 0) {
- xa = x;
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb;
- y = yb + stack.shift();
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- break;
- case 27:
- if (stack.length % 2) {
- y += stack.shift();
- }
- while (stack.length > 0) {
- xa = x + stack.shift();
- ya = y;
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb;
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- break;
- case 28:
- stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
- i += 2;
- break;
- case 29:
- n = stack.pop() + font.gsubrsBias;
- subrCode = font.gsubrs[n];
- if (subrCode) {
- parse(subrCode);
- }
- break;
- case 30:
- while (stack.length > 0) {
- xa = x;
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + (stack.length === 1 ? stack.shift() : 0);
- bezierCurveTo(xa, ya, xb, yb, x, y);
- if (stack.length === 0) {
- break;
- }
- xa = x + stack.shift();
- ya = y;
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- y = yb + stack.shift();
- x = xb + (stack.length === 1 ? stack.shift() : 0);
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- break;
- case 31:
- while (stack.length > 0) {
- xa = x + stack.shift();
- ya = y;
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- y = yb + stack.shift();
- x = xb + (stack.length === 1 ? stack.shift() : 0);
- bezierCurveTo(xa, ya, xb, yb, x, y);
- if (stack.length === 0) {
- break;
- }
- xa = x;
- ya = y + stack.shift();
- xb = xa + stack.shift();
- yb = ya + stack.shift();
- x = xb + stack.shift();
- y = yb + (stack.length === 1 ? stack.shift() : 0);
- bezierCurveTo(xa, ya, xb, yb, x, y);
- }
- break;
- default:
- if (v < 32) {
- error('unknown operator: ' + v);
- }
- if (v < 247) {
- stack.push(v - 139);
- } else if (v < 251) {
- stack.push((v - 247) * 256 + code[i++] + 108);
- } else if (v < 255) {
- stack.push(-(v - 251) * 256 - code[i++] - 108);
- } else {
- stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
- i += 4;
- }
- break;
- }
- if (stackClean) {
- stack.length = 0;
- }
- }
- }
- parse(code);
- }
- var noop = '';
- function CompiledFont(fontMatrix) {
- this.compiledGlyphs = Object.create(null);
- this.compiledCharCodeToGlyphId = Object.create(null);
- this.fontMatrix = fontMatrix;
- }
- CompiledFont.prototype = {
- getPathJs: function (unicode) {
- var cmap = lookupCmap(this.cmap, unicode);
- var fn = this.compiledGlyphs[cmap.glyphId];
- if (!fn) {
- fn = this.compileGlyph(this.glyphs[cmap.glyphId]);
- this.compiledGlyphs[cmap.glyphId] = fn;
- }
- if (this.compiledCharCodeToGlyphId[cmap.charCode] === undefined) {
- this.compiledCharCodeToGlyphId[cmap.charCode] = cmap.glyphId;
- }
- return fn;
- },
- compileGlyph: function (code) {
- if (!code || code.length === 0 || code[0] === 14) {
- return noop;
- }
- var cmds = [];
- cmds.push({ cmd: 'save' });
- cmds.push({
- cmd: 'transform',
- args: this.fontMatrix.slice()
- });
- cmds.push({
- cmd: 'scale',
- args: ['size', '-size']
- });
- this.compileGlyphImpl(code, cmds);
- cmds.push({ cmd: 'restore' });
- return cmds;
- },
- compileGlyphImpl: function () {
- error('Children classes should implement this.');
- },
- hasBuiltPath: function (unicode) {
- var cmap = lookupCmap(this.cmap, unicode);
- return this.compiledGlyphs[cmap.glyphId] !== undefined && this.compiledCharCodeToGlyphId[cmap.charCode] !== undefined;
- }
- };
- function TrueTypeCompiled(glyphs, cmap, fontMatrix) {
- fontMatrix = fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0];
- CompiledFont.call(this, fontMatrix);
- this.glyphs = glyphs;
- this.cmap = cmap;
- }
- Util.inherit(TrueTypeCompiled, CompiledFont, {
- compileGlyphImpl: function (code, cmds) {
- compileGlyf(code, cmds, this);
- }
- });
- function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
- fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
- CompiledFont.call(this, fontMatrix);
- this.glyphs = cffInfo.glyphs;
- this.gsubrs = cffInfo.gsubrs || [];
- this.subrs = cffInfo.subrs || [];
- this.cmap = cmap;
- this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
- 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;
- }
- Util.inherit(Type2Compiled, CompiledFont, {
- compileGlyphImpl: function (code, cmds) {
- compileCharString(code, cmds, this);
- }
- });
- return {
- create: function FontRendererFactory_create(font, seacAnalysisEnabled) {
- var data = new Uint8Array(font.data);
- var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
- var numTables = getUshort(data, 4);
- for (var i = 0, p = 12; i < numTables; i++, p += 16) {
- var tag = bytesToString(data.subarray(p, p + 4));
- var offset = getLong(data, p + 8);
- var length = getLong(data, p + 12);
- switch (tag) {
- case 'cmap':
- cmap = parseCmap(data, offset, offset + length);
- break;
- case 'glyf':
- glyf = data.subarray(offset, offset + length);
- break;
- case 'loca':
- loca = data.subarray(offset, offset + length);
- break;
- case 'head':
- unitsPerEm = getUshort(data, offset + 18);
- indexToLocFormat = getUshort(data, offset + 50);
- break;
- case 'CFF ':
- cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
- break;
- }
- }
- if (glyf) {
- var fontMatrix = !unitsPerEm ? font.fontMatrix : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
- return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix);
- }
- return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
- }
- };
-}();
-exports.FontRendererFactory = FontRendererFactory;
-
-/***/ }),
-/* 26 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreStream = __w_pdfjs_require__(2);
-var coreGlyphList = __w_pdfjs_require__(7);
-var coreFontRenderer = __w_pdfjs_require__(25);
-var coreEncodings = __w_pdfjs_require__(4);
-var coreStandardFonts = __w_pdfjs_require__(17);
-var coreUnicode = __w_pdfjs_require__(18);
-var coreType1Parser = __w_pdfjs_require__(35);
-var coreCFFParser = __w_pdfjs_require__(11);
-var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
-var FontType = sharedUtil.FontType;
-var assert = sharedUtil.assert;
-var bytesToString = sharedUtil.bytesToString;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var isInt = sharedUtil.isInt;
-var isNum = sharedUtil.isNum;
-var readUint32 = sharedUtil.readUint32;
-var shadow = sharedUtil.shadow;
-var string32 = sharedUtil.string32;
-var warn = sharedUtil.warn;
-var MissingDataException = sharedUtil.MissingDataException;
-var isSpace = sharedUtil.isSpace;
-var Stream = coreStream.Stream;
-var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
-var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
-var FontRendererFactory = coreFontRenderer.FontRendererFactory;
-var StandardEncoding = coreEncodings.StandardEncoding;
-var MacRomanEncoding = coreEncodings.MacRomanEncoding;
-var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
-var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
-var getEncoding = coreEncodings.getEncoding;
-var getStdFontMap = coreStandardFonts.getStdFontMap;
-var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
-var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
-var getSupplementalGlyphMapForArialBlack = coreStandardFonts.getSupplementalGlyphMapForArialBlack;
-var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
-var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
-var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
-var Type1Parser = coreType1Parser.Type1Parser;
-var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
-var CFFParser = coreCFFParser.CFFParser;
-var CFFCompiler = coreCFFParser.CFFCompiler;
-var CFF = coreCFFParser.CFF;
-var CFFHeader = coreCFFParser.CFFHeader;
-var CFFTopDict = coreCFFParser.CFFTopDict;
-var CFFPrivateDict = coreCFFParser.CFFPrivateDict;
-var CFFStrings = coreCFFParser.CFFStrings;
-var CFFIndex = coreCFFParser.CFFIndex;
-var CFFCharset = coreCFFParser.CFFCharset;
-var PRIVATE_USE_OFFSET_START = 0xE000;
-var PRIVATE_USE_OFFSET_END = 0xF8FF;
-var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
-var PDF_GLYPH_SPACE_UNITS = 1000;
-var SEAC_ANALYSIS_ENABLED = false;
-var FontFlags = {
- FixedPitch: 1,
- Serif: 2,
- Symbolic: 4,
- Script: 8,
- Nonsymbolic: 32,
- Italic: 64,
- AllCap: 65536,
- SmallCap: 131072,
- ForceBold: 262144
-};
-var MacStandardGlyphOrdering = ['.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'];
-function adjustWidths(properties) {
- if (!properties.fontMatrix) {
- return;
- }
- if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
- return;
- }
- var scale = 0.001 / properties.fontMatrix[0];
- var glyphsWidths = properties.widths;
- for (var glyph in glyphsWidths) {
- glyphsWidths[glyph] *= scale;
- }
- properties.defaultWidth *= scale;
-}
-function adjustToUnicode(properties, builtInEncoding) {
- if (properties.hasIncludedToUnicodeMap) {
- return;
- }
- if (properties.hasEncoding) {
- return;
- }
- if (builtInEncoding === properties.defaultEncoding) {
- return;
- }
- if (properties.toUnicode instanceof IdentityToUnicodeMap) {
- return;
- }
- var toUnicode = [],
- glyphsUnicodeMap = getGlyphsUnicode();
- for (var charCode in builtInEncoding) {
- var glyphName = builtInEncoding[charCode];
- var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
- if (unicode !== -1) {
- toUnicode[charCode] = String.fromCharCode(unicode);
- }
- }
- properties.toUnicode.amend(toUnicode);
-}
-function getFontType(type, subtype) {
- switch (type) {
- case 'Type1':
- return subtype === 'Type1C' ? FontType.TYPE1C : FontType.TYPE1;
- case 'CIDFontType0':
- return subtype === 'CIDFontType0C' ? FontType.CIDFONTTYPE0C : FontType.CIDFONTTYPE0;
- case 'OpenType':
- return FontType.OPENTYPE;
- case 'TrueType':
- return FontType.TRUETYPE;
- case 'CIDFontType2':
- return FontType.CIDFONTTYPE2;
- case 'MMType1':
- return FontType.MMTYPE1;
- case 'Type0':
- return FontType.TYPE0;
- default:
- return FontType.UNKNOWN;
- }
-}
-function recoverGlyphName(name, glyphsUnicodeMap) {
- if (glyphsUnicodeMap[name] !== undefined) {
- return name;
- }
- var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
- if (unicode !== -1) {
- for (var key in glyphsUnicodeMap) {
- if (glyphsUnicodeMap[key] === unicode) {
- return key;
- }
- }
- }
- info('Unable to recover a standard glyph name for: ' + name);
- return name;
-}
-var Glyph = function GlyphClosure() {
- function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
- this.fontChar = fontChar;
- this.unicode = unicode;
- this.accent = accent;
- this.width = width;
- this.vmetric = vmetric;
- this.operatorListId = operatorListId;
- this.isSpace = isSpace;
- this.isInFont = isInFont;
- }
- Glyph.prototype.matchesForCache = function (fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
- return this.fontChar === fontChar && this.unicode === unicode && this.accent === accent && this.width === width && this.vmetric === vmetric && this.operatorListId === operatorListId && this.isSpace === isSpace && this.isInFont === isInFont;
- };
- return Glyph;
-}();
-var ToUnicodeMap = function ToUnicodeMapClosure() {
- function ToUnicodeMap(cmap) {
- this._map = cmap;
- }
- ToUnicodeMap.prototype = {
- get length() {
- return this._map.length;
- },
- forEach: function (callback) {
- for (var charCode in this._map) {
- callback(charCode, this._map[charCode].charCodeAt(0));
- }
- },
- has: function (i) {
- return this._map[i] !== undefined;
- },
- get: function (i) {
- return this._map[i];
- },
- charCodeOf: function (v) {
- return this._map.indexOf(v);
- },
- amend: function (map) {
- for (var charCode in map) {
- this._map[charCode] = map[charCode];
- }
- }
- };
- return ToUnicodeMap;
-}();
-var IdentityToUnicodeMap = function IdentityToUnicodeMapClosure() {
- function IdentityToUnicodeMap(firstChar, lastChar) {
- this.firstChar = firstChar;
- this.lastChar = lastChar;
- }
- IdentityToUnicodeMap.prototype = {
- get length() {
- return this.lastChar + 1 - this.firstChar;
- },
- forEach: function (callback) {
- for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
- callback(i, i);
- }
- },
- has: function (i) {
- return this.firstChar <= i && i <= this.lastChar;
- },
- get: function (i) {
- if (this.firstChar <= i && i <= this.lastChar) {
- return String.fromCharCode(i);
- }
- return undefined;
- },
- charCodeOf: function (v) {
- return isInt(v) && v >= this.firstChar && v <= this.lastChar ? v : -1;
- },
- amend: function (map) {
- error('Should not call amend()');
- }
- };
- return IdentityToUnicodeMap;
-}();
-var OpenTypeFileBuilder = function OpenTypeFileBuilderClosure() {
- function writeInt16(dest, offset, num) {
- dest[offset] = num >> 8 & 0xFF;
- dest[offset + 1] = num & 0xFF;
- }
- function writeInt32(dest, offset, num) {
- dest[offset] = num >> 24 & 0xFF;
- dest[offset + 1] = num >> 16 & 0xFF;
- dest[offset + 2] = num >> 8 & 0xFF;
- dest[offset + 3] = num & 0xFF;
- }
- function writeData(dest, offset, data) {
- var i, ii;
- if (data instanceof Uint8Array) {
- dest.set(data, offset);
- } else if (typeof data === 'string') {
- for (i = 0, ii = data.length; i < ii; i++) {
- dest[offset++] = data.charCodeAt(i) & 0xFF;
- }
- } else {
- for (i = 0, ii = data.length; i < ii; i++) {
- dest[offset++] = data[i] & 0xFF;
- }
- }
- }
- function OpenTypeFileBuilder(sfnt) {
- this.sfnt = sfnt;
- this.tables = Object.create(null);
- }
- OpenTypeFileBuilder.getSearchParams = function OpenTypeFileBuilder_getSearchParams(entriesCount, entrySize) {
- var maxPower2 = 1,
- log2 = 0;
- while ((maxPower2 ^ entriesCount) > maxPower2) {
- maxPower2 <<= 1;
- log2++;
- }
- var searchRange = maxPower2 * entrySize;
- return {
- range: searchRange,
- entry: log2,
- rangeShift: entrySize * entriesCount - searchRange
- };
- };
- var OTF_HEADER_SIZE = 12;
- var OTF_TABLE_ENTRY_SIZE = 16;
- OpenTypeFileBuilder.prototype = {
- toArray: function OpenTypeFileBuilder_toArray() {
- var sfnt = this.sfnt;
- var tables = this.tables;
- var tablesNames = Object.keys(tables);
- tablesNames.sort();
- var numTables = tablesNames.length;
- var i, j, jj, table, tableName;
- var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
- var tableOffsets = [offset];
- for (i = 0; i < numTables; i++) {
- table = tables[tablesNames[i]];
- var paddedLength = (table.length + 3 & ~3) >>> 0;
- offset += paddedLength;
- tableOffsets.push(offset);
- }
- var file = new Uint8Array(offset);
- for (i = 0; i < numTables; i++) {
- table = tables[tablesNames[i]];
- writeData(file, tableOffsets[i], table);
- }
- if (sfnt === 'true') {
- sfnt = string32(0x00010000);
- }
- file[0] = sfnt.charCodeAt(0) & 0xFF;
- file[1] = sfnt.charCodeAt(1) & 0xFF;
- file[2] = sfnt.charCodeAt(2) & 0xFF;
- file[3] = sfnt.charCodeAt(3) & 0xFF;
- writeInt16(file, 4, numTables);
- var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
- writeInt16(file, 6, searchParams.range);
- writeInt16(file, 8, searchParams.entry);
- writeInt16(file, 10, searchParams.rangeShift);
- offset = OTF_HEADER_SIZE;
- for (i = 0; i < numTables; i++) {
- tableName = tablesNames[i];
- file[offset] = tableName.charCodeAt(0) & 0xFF;
- file[offset + 1] = tableName.charCodeAt(1) & 0xFF;
- file[offset + 2] = tableName.charCodeAt(2) & 0xFF;
- file[offset + 3] = tableName.charCodeAt(3) & 0xFF;
- var checksum = 0;
- for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
- var quad = readUint32(file, j);
- checksum = checksum + quad >>> 0;
- }
- writeInt32(file, offset + 4, checksum);
- writeInt32(file, offset + 8, tableOffsets[i]);
- writeInt32(file, offset + 12, tables[tableName].length);
- offset += OTF_TABLE_ENTRY_SIZE;
- }
- return file;
- },
- addTable: function OpenTypeFileBuilder_addTable(tag, data) {
- if (tag in this.tables) {
- throw new Error('Table ' + tag + ' already exists');
- }
- this.tables[tag] = data;
- }
- };
- return OpenTypeFileBuilder;
-}();
-var ProblematicCharRanges = new Int32Array([0x0000, 0x0020, 0x007F, 0x00A1, 0x00AD, 0x00AE, 0x0600, 0x0780, 0x08A0, 0x10A0, 0x1780, 0x1800, 0x1C00, 0x1C50, 0x2000, 0x2010, 0x2011, 0x2012, 0x2028, 0x2030, 0x205F, 0x2070, 0x25CC, 0x25CD, 0x3000, 0x3001, 0xAA60, 0xAA80, 0xFFF0, 0x10000]);
-var Font = function FontClosure() {
- function Font(name, file, properties) {
- var charCode, glyphName, unicode;
- this.name = name;
- this.loadedName = properties.loadedName;
- this.isType3Font = properties.isType3Font;
- this.sizes = [];
- this.missingFile = false;
- this.glyphCache = Object.create(null);
- this.isSerifFont = !!(properties.flags & FontFlags.Serif);
- this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
- this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
- var type = properties.type;
- var subtype = properties.subtype;
- this.type = type;
- this.fallbackName = this.isMonospace ? 'monospace' : this.isSerifFont ? 'serif' : 'sans-serif';
- this.differences = properties.differences;
- this.widths = properties.widths;
- this.defaultWidth = properties.defaultWidth;
- this.composite = properties.composite;
- this.wideChars = properties.wideChars;
- this.cMap = properties.cMap;
- this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
- this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
- this.fontMatrix = properties.fontMatrix;
- this.bbox = properties.bbox;
- this.toUnicode = properties.toUnicode;
- this.toFontChar = [];
- if (properties.type === 'Type3') {
- for (charCode = 0; charCode < 256; charCode++) {
- this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode];
- }
- this.fontType = FontType.TYPE3;
- return;
- }
- this.cidEncoding = properties.cidEncoding;
- this.vertical = properties.vertical;
- if (this.vertical) {
- this.vmetrics = properties.vmetrics;
- this.defaultVMetrics = properties.defaultVMetrics;
- }
- var glyphsUnicodeMap;
- if (!file || file.isEmpty) {
- if (file) {
- warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
- }
- this.missingFile = true;
- var fontName = name.replace(/[,_]/g, '-');
- var stdFontMap = getStdFontMap(),
- nonStdFontMap = getNonStdFontMap();
- var isStandardFont = !!stdFontMap[fontName] || !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
- fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
- this.bold = fontName.search(/bold/gi) !== -1;
- this.italic = fontName.search(/oblique/gi) !== -1 || fontName.search(/italic/gi) !== -1;
- this.black = name.search(/Black/g) !== -1;
- this.remeasure = Object.keys(this.widths).length > 0;
- if (isStandardFont && type === 'CIDFontType2' && properties.cidEncoding.indexOf('Identity-') === 0) {
- var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
- var map = [];
- for (charCode in GlyphMapForStandardFonts) {
- map[+charCode] = GlyphMapForStandardFonts[charCode];
- }
- if (/Arial-?Black/i.test(name)) {
- var SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack();
- for (charCode in SupplementalGlyphMapForArialBlack) {
- map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
- }
- }
- var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
- if (!isIdentityUnicode) {
- this.toUnicode.forEach(function (charCode, unicodeCharCode) {
- map[+charCode] = unicodeCharCode;
- });
- }
- this.toFontChar = map;
- this.toUnicode = new ToUnicodeMap(map);
- } else if (/Symbol/i.test(fontName)) {
- this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(), properties.differences);
- } else if (/Dingbats/i.test(fontName)) {
- if (/Wingdings/i.test(name)) {
- warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
- }
- this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), properties.differences);
- } else if (isStandardFont) {
- this.toFontChar = buildToFontChar(properties.defaultEncoding, getGlyphsUnicode(), properties.differences);
- } else {
- glyphsUnicodeMap = getGlyphsUnicode();
- this.toUnicode.forEach(function (charCode, unicodeCharCode) {
- if (!this.composite) {
- glyphName = properties.differences[charCode] || properties.defaultEncoding[charCode];
- unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
- if (unicode !== -1) {
- unicodeCharCode = unicode;
- }
- }
- this.toFontChar[charCode] = unicodeCharCode;
- }.bind(this));
- }
- this.loadedName = fontName.split('-')[0];
- this.loading = false;
- this.fontType = getFontType(type, subtype);
- return;
- }
- if (subtype === 'Type1C') {
- if (type !== 'Type1' && type !== 'MMType1') {
- if (isTrueTypeFile(file)) {
- subtype = 'TrueType';
- } else {
- type = 'Type1';
- }
- } else if (isOpenTypeFile(file)) {
- type = subtype = 'OpenType';
- }
- }
- if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
- type = 'CIDFontType0';
- }
- if (subtype === 'OpenType') {
- type = 'OpenType';
- }
- if (type === 'CIDFontType0') {
- if (isType1File(file)) {
- subtype = 'CIDFontType0';
- } else if (isOpenTypeFile(file)) {
- type = subtype = 'OpenType';
- } else {
- subtype = 'CIDFontType0C';
- }
- }
- var data;
- switch (type) {
- case 'MMType1':
- info('MMType1 font (' + name + '), falling back to Type1.');
- case 'Type1':
- case 'CIDFontType0':
- this.mimetype = 'font/opentype';
- var cff = subtype === 'Type1C' || subtype === 'CIDFontType0C' ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
- adjustWidths(properties);
- data = this.convert(name, cff, properties);
- break;
- case 'OpenType':
- case 'TrueType':
- case 'CIDFontType2':
- this.mimetype = 'font/opentype';
- data = this.checkAndRepair(name, file, properties);
- if (this.isOpenType) {
- adjustWidths(properties);
- type = 'OpenType';
- }
- break;
- default:
- error('Font ' + type + ' is not supported');
- break;
- }
- this.data = data;
- this.fontType = getFontType(type, subtype);
- this.fontMatrix = properties.fontMatrix;
- this.widths = properties.widths;
- this.defaultWidth = properties.defaultWidth;
- this.toUnicode = properties.toUnicode;
- this.encoding = properties.baseEncoding;
- this.seacMap = properties.seacMap;
- this.loading = true;
- }
- Font.getFontID = function () {
- var ID = 1;
- return function Font_getFontID() {
- return String(ID++);
- };
- }();
- function int16(b0, b1) {
- return (b0 << 8) + b1;
- }
- function signedInt16(b0, b1) {
- var value = (b0 << 8) + b1;
- return value & 1 << 15 ? value - 0x10000 : value;
- }
- function int32(b0, b1, b2, b3) {
- return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
- }
- function string16(value) {
- return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
- }
- function safeString16(value) {
- value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value;
- return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
- }
- function isTrueTypeFile(file) {
- var header = file.peekBytes(4);
- return readUint32(header, 0) === 0x00010000;
- }
- function isOpenTypeFile(file) {
- var header = file.peekBytes(4);
- return bytesToString(header) === 'OTTO';
- }
- function isType1File(file) {
- var header = file.peekBytes(2);
- if (header[0] === 0x25 && header[1] === 0x21) {
- return true;
- }
- if (header[0] === 0x80 && header[1] === 0x01) {
- return true;
- }
- return false;
- }
- function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
- var toFontChar = [],
- unicode;
- for (var i = 0, ii = encoding.length; i < ii; i++) {
- unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
- if (unicode !== -1) {
- toFontChar[i] = unicode;
- }
- }
- for (var charCode in differences) {
- unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
- if (unicode !== -1) {
- toFontChar[+charCode] = unicode;
- }
- }
- return toFontChar;
- }
- function isProblematicUnicodeLocation(code) {
- var i = 0,
- j = ProblematicCharRanges.length - 1;
- while (i < j) {
- var c = i + j + 1 >> 1;
- if (code < ProblematicCharRanges[c]) {
- j = c - 1;
- } else {
- i = c;
- }
- }
- return !(i & 1);
- }
- function adjustMapping(charCodeToGlyphId, properties) {
- var toUnicode = properties.toUnicode;
- var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
- var isIdentityUnicode = properties.toUnicode instanceof IdentityToUnicodeMap;
- var newMap = Object.create(null);
- var toFontChar = [];
- var usedFontCharCodes = [];
- var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
- for (var originalCharCode in charCodeToGlyphId) {
- originalCharCode |= 0;
- var glyphId = charCodeToGlyphId[originalCharCode];
- var fontCharCode = originalCharCode;
- var hasUnicodeValue = false;
- if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
- hasUnicodeValue = true;
- var unicode = toUnicode.get(fontCharCode);
- if (unicode.length === 1) {
- fontCharCode = unicode.charCodeAt(0);
- }
- }
- if ((usedFontCharCodes[fontCharCode] !== undefined || isProblematicUnicodeLocation(fontCharCode) || isSymbolic && !hasUnicodeValue) && nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) {
- do {
- fontCharCode = nextAvailableFontCharCode++;
- if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
- fontCharCode = 0xF020;
- nextAvailableFontCharCode = fontCharCode + 1;
- }
- } while (usedFontCharCodes[fontCharCode] !== undefined && nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
- }
- newMap[fontCharCode] = glyphId;
- toFontChar[originalCharCode] = fontCharCode;
- usedFontCharCodes[fontCharCode] = true;
- }
- return {
- toFontChar: toFontChar,
- charCodeToGlyphId: newMap,
- nextAvailableFontCharCode: nextAvailableFontCharCode
- };
- }
- function getRanges(glyphs, numGlyphs) {
- var codes = [];
- for (var charCode in glyphs) {
- if (glyphs[charCode] >= numGlyphs) {
- continue;
- }
- codes.push({
- fontCharCode: charCode | 0,
- glyphId: glyphs[charCode]
- });
- }
- codes.sort(function fontGetRangesSort(a, b) {
- return a.fontCharCode - b.fontCharCode;
- });
- var ranges = [];
- var length = codes.length;
- for (var n = 0; n < length;) {
- var start = codes[n].fontCharCode;
- var codeIndices = [codes[n].glyphId];
- ++n;
- var end = start;
- while (n < length && end + 1 === codes[n].fontCharCode) {
- codeIndices.push(codes[n].glyphId);
- ++end;
- ++n;
- if (end === 0xFFFF) {
- break;
- }
- }
- ranges.push([start, end, codeIndices]);
- }
- return ranges;
- }
- function createCmapTable(glyphs, numGlyphs) {
- var ranges = getRanges(glyphs, numGlyphs);
- var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
- var cmap = '\x00\x00' + string16(numTables) + '\x00\x03' + '\x00\x01' + string32(4 + numTables * 8);
- var i, ii, j, jj;
- for (i = ranges.length - 1; i >= 0; --i) {
- if (ranges[i][0] <= 0xFFFF) {
- break;
- }
- }
- var bmpLength = i + 1;
- if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
- ranges[i][1] = 0xFFFE;
- }
- var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
- var segCount = bmpLength + trailingRangesCount;
- var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
- var startCount = '';
- var endCount = '';
- var idDeltas = '';
- var idRangeOffsets = '';
- var glyphsIds = '';
- var bias = 0;
- var range, start, end, codes;
- for (i = 0, ii = bmpLength; i < ii; i++) {
- range = ranges[i];
- start = range[0];
- end = range[1];
- startCount += string16(start);
- endCount += string16(end);
- codes = range[2];
- var contiguous = true;
- for (j = 1, jj = codes.length; j < jj; ++j) {
- if (codes[j] !== codes[j - 1] + 1) {
- contiguous = false;
- break;
- }
- }
- if (!contiguous) {
- var offset = (segCount - i) * 2 + bias * 2;
- bias += end - start + 1;
- idDeltas += string16(0);
- idRangeOffsets += string16(offset);
- for (j = 0, jj = codes.length; j < jj; ++j) {
- glyphsIds += string16(codes[j]);
- }
- } else {
- var startCode = codes[0];
- idDeltas += string16(startCode - start & 0xFFFF);
- idRangeOffsets += string16(0);
- }
- }
- if (trailingRangesCount > 0) {
- endCount += '\xFF\xFF';
- startCount += '\xFF\xFF';
- idDeltas += '\x00\x01';
- idRangeOffsets += '\x00\x00';
- }
- var format314 = '\x00\x00' + string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + '\x00\x00' + startCount + idDeltas + idRangeOffsets + glyphsIds;
- var format31012 = '';
- var header31012 = '';
- if (numTables > 1) {
- cmap += '\x00\x03' + '\x00\x0A' + string32(4 + numTables * 8 + 4 + format314.length);
- format31012 = '';
- for (i = 0, ii = ranges.length; i < ii; i++) {
- range = ranges[i];
- start = range[0];
- codes = range[2];
- var code = codes[0];
- for (j = 1, jj = codes.length; j < jj; ++j) {
- if (codes[j] !== codes[j - 1] + 1) {
- end = range[0] + j - 1;
- format31012 += string32(start) + string32(end) + string32(code);
- start = end + 1;
- code = codes[j];
- }
- }
- format31012 += string32(start) + string32(range[1]) + string32(code);
- }
- header31012 = '\x00\x0C' + '\x00\x00' + string32(format31012.length + 16) + '\x00\x00\x00\x00' + string32(format31012.length / 12);
- }
- return cmap + '\x00\x04' + string16(format314.length + 4) + format314 + header31012 + format31012;
- }
- function validateOS2Table(os2) {
- var stream = new Stream(os2.data);
- var version = stream.getUint16();
- stream.getBytes(60);
- var selection = stream.getUint16();
- if (version < 4 && selection & 0x0300) {
- return false;
- }
- var firstChar = stream.getUint16();
- var lastChar = stream.getUint16();
- if (firstChar > lastChar) {
- return false;
- }
- stream.getBytes(6);
- var usWinAscent = stream.getUint16();
- if (usWinAscent === 0) {
- return false;
- }
- os2.data[8] = os2.data[9] = 0;
- return true;
- }
- function createOS2Table(properties, charstrings, override) {
- override = override || {
- unitsPerEm: 0,
- yMax: 0,
- yMin: 0,
- ascent: 0,
- descent: 0
- };
- var ulUnicodeRange1 = 0;
- var ulUnicodeRange2 = 0;
- var ulUnicodeRange3 = 0;
- var ulUnicodeRange4 = 0;
- var firstCharIndex = null;
- var lastCharIndex = 0;
- if (charstrings) {
- for (var code in charstrings) {
- code |= 0;
- if (firstCharIndex > code || !firstCharIndex) {
- firstCharIndex = code;
- }
- if (lastCharIndex < code) {
- lastCharIndex = code;
- }
- var position = getUnicodeRangeFor(code);
- if (position < 32) {
- ulUnicodeRange1 |= 1 << position;
- } else if (position < 64) {
- ulUnicodeRange2 |= 1 << position - 32;
- } else if (position < 96) {
- ulUnicodeRange3 |= 1 << position - 64;
- } else if (position < 123) {
- ulUnicodeRange4 |= 1 << position - 96;
- } else {
- error('Unicode ranges Bits > 123 are reserved for internal usage');
- }
- }
- } else {
- firstCharIndex = 0;
- lastCharIndex = 255;
- }
- var bbox = properties.bbox || [0, 0, 0, 0];
- var unitsPerEm = override.unitsPerEm || 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
- var scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;
- var typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3]));
- var typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1]));
- if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
- typoDescent = -typoDescent;
- }
- var winAscent = override.yMax || typoAscent;
- var winDescent = -override.yMin || -typoDescent;
- return '\x00\x03' + '\x02\x24' + '\x01\xF4' + '\x00\x05' + '\x00\x00' + '\x02\x8A' + '\x02\xBB' + '\x00\x00' + '\x00\x8C' + '\x02\x8A' + '\x02\xBB' + '\x00\x00' + '\x01\xDF' + '\x00\x31' + '\x01\x02' + '\x00\x00' + '\x00\x00\x06' + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + '\x00\x00\x00\x00\x00\x00' + string32(ulUnicodeRange1) + string32(ulUnicodeRange2) + string32(ulUnicodeRange3) + string32(ulUnicodeRange4) + '\x2A\x32\x31\x2A' + string16(properties.italicAngle ? 1 : 0) + string16(firstCharIndex || properties.firstChar) + string16(lastCharIndex || properties.lastChar) + string16(typoAscent) + string16(typoDescent) + '\x00\x64' + string16(winAscent) + string16(winDescent) + '\x00\x00\x00\x00' + '\x00\x00\x00\x00' + string16(properties.xHeight) + string16(properties.capHeight) + string16(0) + string16(firstCharIndex || properties.firstChar) + '\x00\x03';
- }
- function createPostTable(properties) {
- var angle = Math.floor(properties.italicAngle * Math.pow(2, 16));
- return '\x00\x03\x00\x00' + string32(angle) + '\x00\x00' + '\x00\x00' + string32(properties.fixedPitch) + '\x00\x00\x00\x00' + '\x00\x00\x00\x00' + '\x00\x00\x00\x00' + '\x00\x00\x00\x00';
- }
- function createNameTable(name, proto) {
- if (!proto) {
- proto = [[], []];
- }
- var strings = [proto[0][0] || 'Original licence', proto[0][1] || name, proto[0][2] || 'Unknown', proto[0][3] || 'uniqueID', proto[0][4] || name, proto[0][5] || 'Version 0.11', proto[0][6] || '', proto[0][7] || 'Unknown', proto[0][8] || 'Unknown', proto[0][9] || 'Unknown'];
- var stringsUnicode = [];
- var i, ii, j, jj, str;
- for (i = 0, ii = strings.length; i < ii; i++) {
- str = proto[1][i] || strings[i];
- var strBufUnicode = [];
- for (j = 0, jj = str.length; j < jj; j++) {
- strBufUnicode.push(string16(str.charCodeAt(j)));
- }
- stringsUnicode.push(strBufUnicode.join(''));
- }
- var names = [strings, stringsUnicode];
- var platforms = ['\x00\x01', '\x00\x03'];
- var encodings = ['\x00\x00', '\x00\x01'];
- var languages = ['\x00\x00', '\x04\x09'];
- var namesRecordCount = strings.length * platforms.length;
- var nameTable = '\x00\x00' + string16(namesRecordCount) + string16(namesRecordCount * 12 + 6);
- var strOffset = 0;
- for (i = 0, ii = platforms.length; i < ii; i++) {
- var strs = names[i];
- for (j = 0, jj = strs.length; j < jj; j++) {
- str = strs[j];
- var nameRecord = platforms[i] + encodings[i] + languages[i] + string16(j) + string16(str.length) + string16(strOffset);
- nameTable += nameRecord;
- strOffset += str.length;
- }
- }
- nameTable += strings.join('') + stringsUnicode.join('');
- return nameTable;
- }
- Font.prototype = {
- name: null,
- font: null,
- mimetype: null,
- encoding: null,
- get renderer() {
- var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
- return shadow(this, 'renderer', renderer);
- },
- exportData: function Font_exportData() {
- var data = {};
- for (var i in this) {
- if (this.hasOwnProperty(i)) {
- data[i] = this[i];
- }
- }
- return data;
- },
- checkAndRepair: function Font_checkAndRepair(name, font, properties) {
- function readTableEntry(file) {
- var tag = bytesToString(file.getBytes(4));
- var checksum = file.getInt32() >>> 0;
- var offset = file.getInt32() >>> 0;
- var length = file.getInt32() >>> 0;
- var previousPosition = file.pos;
- file.pos = file.start ? file.start : 0;
- file.skip(offset);
- var data = file.getBytes(length);
- file.pos = previousPosition;
- if (tag === 'head') {
- data[8] = data[9] = data[10] = data[11] = 0;
- data[17] |= 0x20;
- }
- return {
- tag: tag,
- checksum: checksum,
- length: length,
- offset: offset,
- data: data
- };
- }
- function readOpenTypeHeader(ttf) {
- return {
- version: bytesToString(ttf.getBytes(4)),
- numTables: ttf.getUint16(),
- searchRange: ttf.getUint16(),
- entrySelector: ttf.getUint16(),
- rangeShift: ttf.getUint16()
- };
- }
- function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
- if (!cmap) {
- warn('No cmap table available.');
- return {
- platformId: -1,
- encodingId: -1,
- mappings: [],
- hasShortCmap: false
- };
- }
- var segment;
- var start = (font.start ? font.start : 0) + cmap.offset;
- font.pos = start;
- font.getUint16();
- var numTables = font.getUint16();
- var potentialTable;
- var canBreak = false;
- for (var i = 0; i < numTables; i++) {
- var platformId = font.getUint16();
- var encodingId = font.getUint16();
- var offset = font.getInt32() >>> 0;
- var useTable = false;
- if (platformId === 0 && encodingId === 0) {
- useTable = true;
- } else if (platformId === 1 && encodingId === 0) {
- useTable = true;
- } else if (platformId === 3 && encodingId === 1 && (hasEncoding || !potentialTable)) {
- useTable = true;
- if (!isSymbolicFont) {
- canBreak = true;
- }
- } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
- useTable = true;
- canBreak = true;
- }
- if (useTable) {
- potentialTable = {
- platformId: platformId,
- encodingId: encodingId,
- offset: offset
- };
- }
- if (canBreak) {
- break;
- }
- }
- if (potentialTable) {
- font.pos = start + potentialTable.offset;
- }
- if (!potentialTable || font.peekByte() === -1) {
- warn('Could not find a preferred cmap table.');
- return {
- platformId: -1,
- encodingId: -1,
- mappings: [],
- hasShortCmap: false
- };
- }
- var format = font.getUint16();
- font.getUint16();
- font.getUint16();
- var hasShortCmap = false;
- var mappings = [];
- var j, glyphId;
- if (format === 0) {
- for (j = 0; j < 256; j++) {
- var index = font.getByte();
- if (!index) {
- continue;
- }
- mappings.push({
- charCode: j,
- glyphId: index
- });
- }
- hasShortCmap = true;
- } else if (format === 4) {
- var segCount = font.getUint16() >> 1;
- font.getBytes(6);
- var segIndex,
- segments = [];
- for (segIndex = 0; segIndex < segCount; segIndex++) {
- segments.push({ end: font.getUint16() });
- }
- font.getUint16();
- for (segIndex = 0; segIndex < segCount; segIndex++) {
- segments[segIndex].start = font.getUint16();
- }
- for (segIndex = 0; segIndex < segCount; segIndex++) {
- segments[segIndex].delta = font.getUint16();
- }
- var offsetsCount = 0;
- for (segIndex = 0; segIndex < segCount; segIndex++) {
- segment = segments[segIndex];
- var rangeOffset = font.getUint16();
- if (!rangeOffset) {
- segment.offsetIndex = -1;
- continue;
- }
- var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
- segment.offsetIndex = offsetIndex;
- offsetsCount = Math.max(offsetsCount, offsetIndex + segment.end - segment.start + 1);
- }
- var offsets = [];
- for (j = 0; j < offsetsCount; j++) {
- offsets.push(font.getUint16());
- }
- for (segIndex = 0; segIndex < segCount; segIndex++) {
- segment = segments[segIndex];
- start = segment.start;
- var end = segment.end;
- var delta = segment.delta;
- offsetIndex = segment.offsetIndex;
- for (j = start; j <= end; j++) {
- if (j === 0xFFFF) {
- continue;
- }
- glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];
- glyphId = glyphId + delta & 0xFFFF;
- mappings.push({
- charCode: j,
- glyphId: glyphId
- });
- }
- }
- } else if (format === 6) {
- var firstCode = font.getUint16();
- var entryCount = font.getUint16();
- for (j = 0; j < entryCount; j++) {
- glyphId = font.getUint16();
- var charCode = firstCode + j;
- mappings.push({
- charCode: charCode,
- glyphId: glyphId
- });
- }
- } else {
- warn('cmap table has unsupported format: ' + format);
- return {
- platformId: -1,
- encodingId: -1,
- mappings: [],
- hasShortCmap: false
- };
- }
- mappings.sort(function (a, b) {
- return a.charCode - b.charCode;
- });
- for (i = 1; i < mappings.length; i++) {
- if (mappings[i - 1].charCode === mappings[i].charCode) {
- mappings.splice(i, 1);
- i--;
- }
- }
- return {
- platformId: potentialTable.platformId,
- encodingId: potentialTable.encodingId,
- mappings: mappings,
- hasShortCmap: hasShortCmap
- };
- }
- function sanitizeMetrics(font, header, metrics, numGlyphs) {
- if (!header) {
- if (metrics) {
- metrics.data = null;
- }
- return;
- }
- font.pos = (font.start ? font.start : 0) + header.offset;
- font.pos += header.length - 2;
- var numOfMetrics = font.getUint16();
- if (numOfMetrics > numGlyphs) {
- info('The numOfMetrics (' + numOfMetrics + ') should not be ' + 'greater than the numGlyphs (' + numGlyphs + ')');
- numOfMetrics = numGlyphs;
- header.data[34] = (numOfMetrics & 0xff00) >> 8;
- header.data[35] = numOfMetrics & 0x00ff;
- }
- var numOfSidebearings = numGlyphs - numOfMetrics;
- var numMissing = numOfSidebearings - (metrics.length - numOfMetrics * 4 >> 1);
- if (numMissing > 0) {
- var entries = new Uint8Array(metrics.length + numMissing * 2);
- entries.set(metrics.data);
- metrics.data = entries;
- }
- }
- function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, hintsValid) {
- if (sourceEnd - sourceStart <= 12) {
- return 0;
- }
- var glyf = source.subarray(sourceStart, sourceEnd);
- var contoursCount = glyf[0] << 8 | glyf[1];
- if (contoursCount & 0x8000) {
- dest.set(glyf, destStart);
- return glyf.length;
- }
- var i,
- j = 10,
- flagsCount = 0;
- for (i = 0; i < contoursCount; i++) {
- var endPoint = glyf[j] << 8 | glyf[j + 1];
- flagsCount = endPoint + 1;
- j += 2;
- }
- var instructionsStart = j;
- var instructionsLength = glyf[j] << 8 | glyf[j + 1];
- j += 2 + instructionsLength;
- var instructionsEnd = j;
- var coordinatesLength = 0;
- for (i = 0; i < flagsCount; i++) {
- var flag = glyf[j++];
- if (flag & 0xC0) {
- glyf[j - 1] = flag & 0x3F;
- }
- var xyLength = (flag & 2 ? 1 : flag & 16 ? 0 : 2) + (flag & 4 ? 1 : flag & 32 ? 0 : 2);
- coordinatesLength += xyLength;
- if (flag & 8) {
- var repeat = glyf[j++];
- i += repeat;
- coordinatesLength += repeat * xyLength;
- }
- }
- if (coordinatesLength === 0) {
- return 0;
- }
- var glyphDataLength = j + coordinatesLength;
- if (glyphDataLength > glyf.length) {
- return 0;
- }
- if (!hintsValid && instructionsLength > 0) {
- dest.set(glyf.subarray(0, instructionsStart), destStart);
- dest.set([0, 0], destStart + instructionsStart);
- dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2);
- glyphDataLength -= instructionsLength;
- if (glyf.length - glyphDataLength > 3) {
- glyphDataLength = glyphDataLength + 3 & ~3;
- }
- return glyphDataLength;
- }
- if (glyf.length - glyphDataLength > 3) {
- glyphDataLength = glyphDataLength + 3 & ~3;
- dest.set(glyf.subarray(0, glyphDataLength), destStart);
- return glyphDataLength;
- }
- dest.set(glyf, destStart);
- return glyf.length;
- }
- function sanitizeHead(head, numGlyphs, locaLength) {
- var data = head.data;
- var version = int32(data[0], data[1], data[2], data[3]);
- if (version >> 16 !== 1) {
- info('Attempting to fix invalid version in head table: ' + version);
- data[0] = 0;
- data[1] = 1;
- data[2] = 0;
- data[3] = 0;
- }
- var indexToLocFormat = int16(data[50], data[51]);
- if (indexToLocFormat < 0 || indexToLocFormat > 1) {
- info('Attempting to fix invalid indexToLocFormat in head table: ' + indexToLocFormat);
- var numGlyphsPlusOne = numGlyphs + 1;
- if (locaLength === numGlyphsPlusOne << 1) {
- data[50] = 0;
- data[51] = 0;
- } else if (locaLength === numGlyphsPlusOne << 2) {
- data[50] = 0;
- data[51] = 1;
- } else {
- warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
- }
- }
- }
- function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry) {
- var itemSize, itemDecode, itemEncode;
- if (isGlyphLocationsLong) {
- itemSize = 4;
- itemDecode = function fontItemDecodeLong(data, offset) {
- return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
- };
- itemEncode = function fontItemEncodeLong(data, offset, value) {
- data[offset] = value >>> 24 & 0xFF;
- data[offset + 1] = value >> 16 & 0xFF;
- data[offset + 2] = value >> 8 & 0xFF;
- data[offset + 3] = value & 0xFF;
- };
- } else {
- itemSize = 2;
- itemDecode = function fontItemDecode(data, offset) {
- return data[offset] << 9 | data[offset + 1] << 1;
- };
- itemEncode = function fontItemEncode(data, offset, value) {
- data[offset] = value >> 9 & 0xFF;
- data[offset + 1] = value >> 1 & 0xFF;
- };
- }
- var locaData = loca.data;
- var locaDataSize = itemSize * (1 + numGlyphs);
- if (locaData.length !== locaDataSize) {
- locaData = new Uint8Array(locaDataSize);
- locaData.set(loca.data.subarray(0, locaDataSize));
- loca.data = locaData;
- }
- var oldGlyfData = glyf.data;
- var oldGlyfDataLength = oldGlyfData.length;
- var newGlyfData = new Uint8Array(oldGlyfDataLength);
- var startOffset = itemDecode(locaData, 0);
- var writeOffset = 0;
- var missingGlyphData = Object.create(null);
- itemEncode(locaData, 0, writeOffset);
- var i, j;
- for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
- var endOffset = itemDecode(locaData, j);
- if (endOffset > oldGlyfDataLength && (oldGlyfDataLength + 3 & ~3) === endOffset) {
- endOffset = oldGlyfDataLength;
- }
- if (endOffset > oldGlyfDataLength) {
- itemEncode(locaData, j, writeOffset);
- startOffset = endOffset;
- continue;
- }
- if (startOffset === endOffset) {
- missingGlyphData[i] = true;
- }
- var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset, newGlyfData, writeOffset, hintsValid);
- writeOffset += newLength;
- itemEncode(locaData, j, writeOffset);
- startOffset = endOffset;
- }
- if (writeOffset === 0) {
- var simpleGlyph = new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
- for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
- itemEncode(locaData, j, simpleGlyph.length);
- }
- glyf.data = simpleGlyph;
- return missingGlyphData;
- }
- if (dupFirstEntry) {
- var firstEntryLength = itemDecode(locaData, itemSize);
- if (newGlyfData.length > firstEntryLength + writeOffset) {
- glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
- } else {
- glyf.data = new Uint8Array(firstEntryLength + writeOffset);
- glyf.data.set(newGlyfData.subarray(0, writeOffset));
- }
- glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
- itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength);
- } else {
- glyf.data = newGlyfData.subarray(0, writeOffset);
- }
- return missingGlyphData;
- }
- function readPostScriptTable(post, properties, maxpNumGlyphs) {
- var start = (font.start ? font.start : 0) + post.offset;
- font.pos = start;
- var length = post.length,
- end = start + length;
- var version = font.getInt32();
- font.getBytes(28);
- var glyphNames;
- var valid = true;
- var i;
- switch (version) {
- case 0x00010000:
- glyphNames = MacStandardGlyphOrdering;
- break;
- case 0x00020000:
- var numGlyphs = font.getUint16();
- if (numGlyphs !== maxpNumGlyphs) {
- valid = false;
- break;
- }
- var glyphNameIndexes = [];
- for (i = 0; i < numGlyphs; ++i) {
- var index = font.getUint16();
- if (index >= 32768) {
- valid = false;
- break;
- }
- glyphNameIndexes.push(index);
- }
- if (!valid) {
- break;
- }
- var customNames = [];
- var strBuf = [];
- while (font.pos < end) {
- var stringLength = font.getByte();
- strBuf.length = stringLength;
- for (i = 0; i < stringLength; ++i) {
- strBuf[i] = String.fromCharCode(font.getByte());
- }
- customNames.push(strBuf.join(''));
- }
- glyphNames = [];
- for (i = 0; i < numGlyphs; ++i) {
- var j = glyphNameIndexes[i];
- if (j < 258) {
- glyphNames.push(MacStandardGlyphOrdering[j]);
- continue;
- }
- glyphNames.push(customNames[j - 258]);
- }
- break;
- case 0x00030000:
- break;
- default:
- warn('Unknown/unsupported post table version ' + version);
- valid = false;
- if (properties.defaultEncoding) {
- glyphNames = properties.defaultEncoding;
- }
- break;
- }
- properties.glyphNames = glyphNames;
- return valid;
- }
- function readNameTable(nameTable) {
- var start = (font.start ? font.start : 0) + nameTable.offset;
- font.pos = start;
- var names = [[], []];
- var length = nameTable.length,
- end = start + length;
- var format = font.getUint16();
- var FORMAT_0_HEADER_LENGTH = 6;
- if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
- return names;
- }
- var numRecords = font.getUint16();
- var stringsStart = font.getUint16();
- var records = [];
- var NAME_RECORD_LENGTH = 12;
- var i, ii;
- for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {
- var r = {
- platform: font.getUint16(),
- encoding: font.getUint16(),
- language: font.getUint16(),
- name: font.getUint16(),
- length: font.getUint16(),
- offset: font.getUint16()
- };
- if (r.platform === 1 && r.encoding === 0 && r.language === 0 || r.platform === 3 && r.encoding === 1 && r.language === 0x409) {
- records.push(r);
- }
- }
- for (i = 0, ii = records.length; i < ii; i++) {
- var record = records[i];
- if (record.length <= 0) {
- continue;
- }
- var pos = start + stringsStart + record.offset;
- if (pos + record.length > end) {
- continue;
- }
- font.pos = pos;
- var nameIndex = record.name;
- if (record.encoding) {
- var str = '';
- for (var j = 0, jj = record.length; j < jj; j += 2) {
- str += String.fromCharCode(font.getUint16());
- }
- names[1][nameIndex] = str;
- } else {
- names[0][nameIndex] = bytesToString(font.getBytes(record.length));
- }
- }
- return names;
- }
- var TTOpsStackDeltas = [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];
- function sanitizeTTProgram(table, ttContext) {
- var data = table.data;
- var i = 0,
- j,
- n,
- b,
- funcId,
- pc,
- lastEndf = 0,
- lastDeff = 0;
- var stack = [];
- var callstack = [];
- var functionsCalled = [];
- var tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;
- var inFDEF = false,
- ifLevel = 0,
- inELSE = 0;
- for (var ii = data.length; i < ii;) {
- var op = data[i++];
- if (op === 0x40) {
- n = data[i++];
- if (inFDEF || inELSE) {
- i += n;
- } else {
- for (j = 0; j < n; j++) {
- stack.push(data[i++]);
- }
- }
- } else if (op === 0x41) {
- n = data[i++];
- if (inFDEF || inELSE) {
- i += n * 2;
- } else {
- for (j = 0; j < n; j++) {
- b = data[i++];
- stack.push(b << 8 | data[i++]);
- }
- }
- } else if ((op & 0xF8) === 0xB0) {
- n = op - 0xB0 + 1;
- if (inFDEF || inELSE) {
- i += n;
- } else {
- for (j = 0; j < n; j++) {
- stack.push(data[i++]);
- }
- }
- } else if ((op & 0xF8) === 0xB8) {
- n = op - 0xB8 + 1;
- if (inFDEF || inELSE) {
- i += n * 2;
- } else {
- for (j = 0; j < n; j++) {
- b = data[i++];
- stack.push(b << 8 | data[i++]);
- }
- }
- } else if (op === 0x2B && !tooComplexToFollowFunctions) {
- if (!inFDEF && !inELSE) {
- funcId = stack[stack.length - 1];
- ttContext.functionsUsed[funcId] = true;
- if (funcId in ttContext.functionsStackDeltas) {
- stack.length += ttContext.functionsStackDeltas[funcId];
- } else if (funcId in ttContext.functionsDefined && functionsCalled.indexOf(funcId) < 0) {
- callstack.push({
- data: data,
- i: i,
- stackTop: stack.length - 1
- });
- functionsCalled.push(funcId);
- pc = ttContext.functionsDefined[funcId];
- if (!pc) {
- warn('TT: CALL non-existent function');
- ttContext.hintsValid = false;
- return;
- }
- data = pc.data;
- i = pc.i;
- }
- }
- } else if (op === 0x2C && !tooComplexToFollowFunctions) {
- if (inFDEF || inELSE) {
- warn('TT: nested FDEFs not allowed');
- tooComplexToFollowFunctions = true;
- }
- inFDEF = true;
- lastDeff = i;
- funcId = stack.pop();
- ttContext.functionsDefined[funcId] = {
- data: data,
- i: i
- };
- } else if (op === 0x2D) {
- if (inFDEF) {
- inFDEF = false;
- lastEndf = i;
- } else {
- pc = callstack.pop();
- if (!pc) {
- warn('TT: ENDF bad stack');
- ttContext.hintsValid = false;
- return;
- }
- funcId = functionsCalled.pop();
- data = pc.data;
- i = pc.i;
- ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;
- }
- } else if (op === 0x89) {
- if (inFDEF || inELSE) {
- warn('TT: nested IDEFs not allowed');
- tooComplexToFollowFunctions = true;
- }
- inFDEF = true;
- lastDeff = i;
- } else if (op === 0x58) {
- ++ifLevel;
- } else if (op === 0x1B) {
- inELSE = ifLevel;
- } else if (op === 0x59) {
- if (inELSE === ifLevel) {
- inELSE = 0;
- }
- --ifLevel;
- } else if (op === 0x1C) {
- if (!inFDEF && !inELSE) {
- var offset = stack[stack.length - 1];
- if (offset > 0) {
- i += offset - 1;
- }
- }
- }
- if (!inFDEF && !inELSE) {
- var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] : op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
- if (op >= 0x71 && op <= 0x75) {
- n = stack.pop();
- if (!isNaN(n)) {
- stackDelta = -n * 2;
- }
- }
- while (stackDelta < 0 && stack.length > 0) {
- stack.pop();
- stackDelta++;
- }
- while (stackDelta > 0) {
- stack.push(NaN);
- stackDelta--;
- }
- }
- }
- ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
- var content = [data];
- if (i > data.length) {
- content.push(new Uint8Array(i - data.length));
- }
- if (lastDeff > lastEndf) {
- warn('TT: complementing a missing function tail');
- content.push(new Uint8Array([0x22, 0x2D]));
- }
- foldTTTable(table, content);
- }
- function checkInvalidFunctions(ttContext, maxFunctionDefs) {
- if (ttContext.tooComplexToFollowFunctions) {
- return;
- }
- if (ttContext.functionsDefined.length > maxFunctionDefs) {
- warn('TT: more functions defined than expected');
- ttContext.hintsValid = false;
- return;
- }
- for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
- if (j > maxFunctionDefs) {
- warn('TT: invalid function id: ' + j);
- ttContext.hintsValid = false;
- return;
- }
- if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
- warn('TT: undefined function: ' + j);
- ttContext.hintsValid = false;
- return;
- }
- }
- }
- function foldTTTable(table, content) {
- if (content.length > 1) {
- var newLength = 0;
- var j, jj;
- for (j = 0, jj = content.length; j < jj; j++) {
- newLength += content[j].length;
- }
- newLength = newLength + 3 & ~3;
- var result = new Uint8Array(newLength);
- var pos = 0;
- for (j = 0, jj = content.length; j < jj; j++) {
- result.set(content[j], pos);
- pos += content[j].length;
- }
- table.data = result;
- table.length = newLength;
- }
- }
- function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {
- var ttContext = {
- functionsDefined: [],
- functionsUsed: [],
- functionsStackDeltas: [],
- tooComplexToFollowFunctions: false,
- hintsValid: true
- };
- if (fpgm) {
- sanitizeTTProgram(fpgm, ttContext);
- }
- if (prep) {
- sanitizeTTProgram(prep, ttContext);
- }
- if (fpgm) {
- checkInvalidFunctions(ttContext, maxFunctionDefs);
- }
- if (cvt && cvt.length & 1) {
- var cvtData = new Uint8Array(cvt.length + 1);
- cvtData.set(cvt.data);
- cvt.data = cvtData;
- }
- return ttContext.hintsValid;
- }
- font = new Stream(new Uint8Array(font.getBytes()));
- var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
- var header = readOpenTypeHeader(font);
- var numTables = header.numTables;
- var cff, cffFile;
- var tables = Object.create(null);
- tables['OS/2'] = null;
- tables['cmap'] = null;
- tables['head'] = null;
- tables['hhea'] = null;
- tables['hmtx'] = null;
- tables['maxp'] = null;
- tables['name'] = null;
- tables['post'] = null;
- var table;
- for (var i = 0; i < numTables; i++) {
- table = readTableEntry(font);
- if (VALID_TABLES.indexOf(table.tag) < 0) {
- continue;
- }
- if (table.length === 0) {
- continue;
- }
- tables[table.tag] = table;
- }
- var isTrueType = !tables['CFF '];
- if (!isTrueType) {
- if (header.version === 'OTTO' && !properties.composite || !tables['head'] || !tables['hhea'] || !tables['maxp'] || !tables['post']) {
- cffFile = new Stream(tables['CFF '].data);
- cff = new CFFFont(cffFile, properties);
- adjustWidths(properties);
- return this.convert(name, cff, properties);
- }
- delete tables['glyf'];
- delete tables['loca'];
- delete tables['fpgm'];
- delete tables['prep'];
- delete tables['cvt '];
- this.isOpenType = true;
- } else {
- if (!tables['loca']) {
- error('Required "loca" table is not found');
- }
- if (!tables['glyf']) {
- warn('Required "glyf" table is not found -- trying to recover.');
- tables['glyf'] = {
- tag: 'glyf',
- data: new Uint8Array(0)
- };
- }
- this.isOpenType = false;
- }
- if (!tables['maxp']) {
- error('Required "maxp" table is not found');
- }
- font.pos = (font.start || 0) + tables['maxp'].offset;
- var version = font.getInt32();
- var numGlyphs = font.getUint16();
- var maxFunctionDefs = 0;
- if (version >= 0x00010000 && tables['maxp'].length >= 22) {
- font.pos += 8;
- var maxZones = font.getUint16();
- if (maxZones > 2) {
- tables['maxp'].data[14] = 0;
- tables['maxp'].data[15] = 2;
- }
- font.pos += 4;
- maxFunctionDefs = font.getUint16();
- }
- var dupFirstEntry = false;
- if (properties.type === 'CIDFontType2' && properties.toUnicode && properties.toUnicode.get(0) > '\u0000') {
- dupFirstEntry = true;
- numGlyphs++;
- tables['maxp'].data[4] = numGlyphs >> 8;
- tables['maxp'].data[5] = numGlyphs & 255;
- }
- var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'], tables['cvt '], maxFunctionDefs);
- if (!hintsValid) {
- delete tables['fpgm'];
- delete tables['prep'];
- delete tables['cvt '];
- }
- sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
- if (!tables['head']) {
- error('Required "head" table is not found');
- }
- sanitizeHead(tables['head'], numGlyphs, isTrueType ? tables['loca'].length : 0);
- var missingGlyphs = Object.create(null);
- if (isTrueType) {
- var isGlyphLocationsLong = int16(tables['head'].data[50], tables['head'].data[51]);
- missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'], numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry);
- }
- if (!tables['hhea']) {
- error('Required "hhea" table is not found');
- }
- if (tables['hhea'].data[10] === 0 && tables['hhea'].data[11] === 0) {
- tables['hhea'].data[10] = 0xFF;
- tables['hhea'].data[11] = 0xFF;
- }
- var metricsOverride = {
- unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
- yMax: int16(tables['head'].data[42], tables['head'].data[43]),
- yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
- ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
- descent: signedInt16(tables['hhea'].data[6], tables['hhea'].data[7])
- };
- this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
- this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
- if (tables['post']) {
- var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
- if (!valid) {
- tables['post'] = null;
- }
- }
- var charCodeToGlyphId = [],
- charCode;
- var toUnicode = properties.toUnicode,
- widths = properties.widths;
- var skipToUnicode = toUnicode instanceof IdentityToUnicodeMap || toUnicode.length === 0x10000;
- function hasGlyph(glyphId, charCode, widthCode) {
- if (!missingGlyphs[glyphId]) {
- return true;
- }
- if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
- return true;
- }
- if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
- return true;
- }
- return false;
- }
- if (properties.composite) {
- var cidToGidMap = properties.cidToGidMap || [];
- var isCidToGidMapEmpty = cidToGidMap.length === 0;
- properties.cMap.forEach(function (charCode, cid) {
- assert(cid <= 0xffff, 'Max size of CID is 65,535');
- var glyphId = -1;
- if (isCidToGidMapEmpty) {
- glyphId = cid;
- } else if (cidToGidMap[cid] !== undefined) {
- glyphId = cidToGidMap[cid];
- }
- if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId, charCode, cid)) {
- charCodeToGlyphId[charCode] = glyphId;
- }
- });
- if (dupFirstEntry && (isCidToGidMapEmpty || !charCodeToGlyphId[0])) {
- charCodeToGlyphId[0] = numGlyphs - 1;
- }
- } else {
- var cmapTable = readCmapTable(tables['cmap'], font, this.isSymbolicFont, properties.hasEncoding);
- var cmapPlatformId = cmapTable.platformId;
- var cmapEncodingId = cmapTable.encodingId;
- var cmapMappings = cmapTable.mappings;
- var cmapMappingsLength = cmapMappings.length;
- if (properties.hasEncoding && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0) || cmapPlatformId === -1 && cmapEncodingId === -1 && !!getEncoding(properties.baseEncodingName)) {
- var baseEncoding = [];
- if (properties.baseEncodingName === 'MacRomanEncoding' || properties.baseEncodingName === 'WinAnsiEncoding') {
- baseEncoding = getEncoding(properties.baseEncodingName);
- }
- var glyphsUnicodeMap = getGlyphsUnicode();
- for (charCode = 0; charCode < 256; charCode++) {
- var glyphName, standardGlyphName;
- if (this.differences && charCode in this.differences) {
- glyphName = this.differences[charCode];
- } else if (charCode in baseEncoding && baseEncoding[charCode] !== '') {
- glyphName = baseEncoding[charCode];
- } else {
- glyphName = StandardEncoding[charCode];
- }
- if (!glyphName) {
- continue;
- }
- standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
- var unicodeOrCharCode,
- isUnicode = false;
- if (cmapPlatformId === 3 && cmapEncodingId === 1) {
- unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
- isUnicode = true;
- } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
- unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
- }
- var found = false;
- for (i = 0; i < cmapMappingsLength; ++i) {
- if (cmapMappings[i].charCode !== unicodeOrCharCode) {
- continue;
- }
- var code = isUnicode ? charCode : unicodeOrCharCode;
- if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
- charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
- found = true;
- break;
- }
- }
- if (!found && properties.glyphNames) {
- var glyphId = properties.glyphNames.indexOf(glyphName);
- if (glyphId === -1 && standardGlyphName !== glyphName) {
- glyphId = properties.glyphNames.indexOf(standardGlyphName);
- }
- if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
- charCodeToGlyphId[charCode] = glyphId;
- found = true;
- }
- }
- if (!found) {
- charCodeToGlyphId[charCode] = 0;
- }
- }
- } else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
- for (i = 0; i < cmapMappingsLength; ++i) {
- charCodeToGlyphId[cmapMappings[i].charCode] = cmapMappings[i].glyphId;
- }
- } else {
- for (i = 0; i < cmapMappingsLength; ++i) {
- charCode = cmapMappings[i].charCode & 0xFF;
- charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
- }
- }
- }
- if (charCodeToGlyphId.length === 0) {
- charCodeToGlyphId[0] = 0;
- }
- var newMapping = adjustMapping(charCodeToGlyphId, properties);
- this.toFontChar = newMapping.toFontChar;
- tables['cmap'] = {
- tag: 'cmap',
- data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
- };
- if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
- tables['OS/2'] = {
- tag: 'OS/2',
- data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride)
- };
- }
- if (!tables['post']) {
- tables['post'] = {
- tag: 'post',
- data: createPostTable(properties)
- };
- }
- if (!isTrueType) {
- try {
- cffFile = new Stream(tables['CFF '].data);
- var parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED);
- cff = parser.parse();
- var compiler = new CFFCompiler(cff);
- tables['CFF '].data = compiler.compile();
- } catch (e) {
- warn('Failed to compile font ' + properties.loadedName);
- }
- }
- if (!tables['name']) {
- tables['name'] = {
- tag: 'name',
- data: createNameTable(this.name)
- };
- } else {
- var namePrototype = readNameTable(tables['name']);
- tables['name'].data = createNameTable(name, namePrototype);
- }
- var builder = new OpenTypeFileBuilder(header.version);
- for (var tableTag in tables) {
- builder.addTable(tableTag, tables[tableTag].data);
- }
- return builder.toArray();
- },
- convert: function Font_convert(fontName, font, properties) {
- properties.fixedPitch = false;
- if (properties.builtInEncoding) {
- adjustToUnicode(properties, properties.builtInEncoding);
- }
- var mapping = font.getGlyphMapping(properties);
- var newMapping = adjustMapping(mapping, properties);
- this.toFontChar = newMapping.toFontChar;
- var numGlyphs = font.numGlyphs;
- function getCharCodes(charCodeToGlyphId, glyphId) {
- var charCodes = null;
- for (var charCode in charCodeToGlyphId) {
- if (glyphId === charCodeToGlyphId[charCode]) {
- if (!charCodes) {
- charCodes = [];
- }
- charCodes.push(charCode | 0);
- }
- }
- return charCodes;
- }
- function createCharCode(charCodeToGlyphId, glyphId) {
- for (var charCode in charCodeToGlyphId) {
- if (glyphId === charCodeToGlyphId[charCode]) {
- return charCode | 0;
- }
- }
- newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = glyphId;
- return newMapping.nextAvailableFontCharCode++;
- }
- var seacs = font.seacs;
- if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
- var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
- var charset = font.getCharset();
- var seacMap = Object.create(null);
- for (var glyphId in seacs) {
- glyphId |= 0;
- var seac = seacs[glyphId];
- var baseGlyphName = StandardEncoding[seac[2]];
- var accentGlyphName = StandardEncoding[seac[3]];
- var baseGlyphId = charset.indexOf(baseGlyphName);
- var accentGlyphId = charset.indexOf(accentGlyphName);
- if (baseGlyphId < 0 || accentGlyphId < 0) {
- continue;
- }
- var accentOffset = {
- x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
- y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
- };
- var charCodes = getCharCodes(mapping, glyphId);
- if (!charCodes) {
- continue;
- }
- for (var i = 0, ii = charCodes.length; i < ii; i++) {
- var charCode = charCodes[i];
- var charCodeToGlyphId = newMapping.charCodeToGlyphId;
- var baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId);
- var accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId);
- seacMap[charCode] = {
- baseFontCharCode: baseFontCharCode,
- accentFontCharCode: accentFontCharCode,
- accentOffset: accentOffset
- };
- }
- }
- properties.seacMap = seacMap;
- }
- var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
- var builder = new OpenTypeFileBuilder('\x4F\x54\x54\x4F');
- builder.addTable('CFF ', font.data);
- builder.addTable('OS/2', createOS2Table(properties, newMapping.charCodeToGlyphId));
- builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId, numGlyphs));
- builder.addTable('head', '\x00\x01\x00\x00' + '\x00\x00\x10\x00' + '\x00\x00\x00\x00' + '\x5F\x0F\x3C\xF5' + '\x00\x00' + safeString16(unitsPerEm) + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + '\x00\x00' + safeString16(properties.descent) + '\x0F\xFF' + safeString16(properties.ascent) + string16(properties.italicAngle ? 2 : 0) + '\x00\x11' + '\x00\x00' + '\x00\x00' + '\x00\x00');
- builder.addTable('hhea', '\x00\x01\x00\x00' + safeString16(properties.ascent) + safeString16(properties.descent) + '\x00\x00' + '\xFF\xFF' + '\x00\x00' + '\x00\x00' + '\x00\x00' + safeString16(properties.capHeight) + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + '\x00\x00' + '\x00\x00' + '\x00\x00' + '\x00\x00' + '\x00\x00' + '\x00\x00' + string16(numGlyphs));
- builder.addTable('hmtx', function fontFieldsHmtx() {
- var charstrings = font.charstrings;
- var cffWidths = font.cff ? font.cff.widths : null;
- var hmtx = '\x00\x00\x00\x00';
- for (var i = 1, ii = numGlyphs; i < ii; i++) {
- var width = 0;
- if (charstrings) {
- var charstring = charstrings[i - 1];
- width = 'width' in charstring ? charstring.width : 0;
- } else if (cffWidths) {
- width = Math.ceil(cffWidths[i] || 0);
- }
- hmtx += string16(width) + string16(0);
- }
- return hmtx;
- }());
- builder.addTable('maxp', '\x00\x00\x50\x00' + string16(numGlyphs));
- builder.addTable('name', createNameTable(fontName));
- builder.addTable('post', createPostTable(properties));
- return builder.toArray();
- },
- get spaceWidth() {
- if ('_shadowWidth' in this) {
- return this._shadowWidth;
- }
- var possibleSpaceReplacements = ['space', 'minus', 'one', 'i', 'I'];
- var width;
- for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
- var glyphName = possibleSpaceReplacements[i];
- if (glyphName in this.widths) {
- width = this.widths[glyphName];
- break;
- }
- var glyphsUnicodeMap = getGlyphsUnicode();
- var glyphUnicode = glyphsUnicodeMap[glyphName];
- var charcode = 0;
- if (this.composite) {
- if (this.cMap.contains(glyphUnicode)) {
- charcode = this.cMap.lookup(glyphUnicode);
- }
- }
- if (!charcode && this.toUnicode) {
- charcode = this.toUnicode.charCodeOf(glyphUnicode);
- }
- if (charcode <= 0) {
- charcode = glyphUnicode;
- }
- width = this.widths[charcode];
- if (width) {
- break;
- }
- }
- width = width || this.defaultWidth;
- this._shadowWidth = width;
- return width;
- },
- charToGlyph: function Font_charToGlyph(charcode, isSpace) {
- var fontCharCode, width, operatorListId;
- var widthCode = charcode;
- if (this.cMap && this.cMap.contains(charcode)) {
- widthCode = this.cMap.lookup(charcode);
- }
- width = this.widths[widthCode];
- width = isNum(width) ? width : this.defaultWidth;
- var vmetric = this.vmetrics && this.vmetrics[widthCode];
- var unicode = this.toUnicode.get(charcode) || charcode;
- if (typeof unicode === 'number') {
- unicode = String.fromCharCode(unicode);
- }
- var isInFont = charcode in this.toFontChar;
- fontCharCode = this.toFontChar[charcode] || charcode;
- if (this.missingFile) {
- fontCharCode = mapSpecialUnicodeValues(fontCharCode);
- }
- if (this.isType3Font) {
- operatorListId = fontCharCode;
- }
- var accent = null;
- if (this.seacMap && this.seacMap[charcode]) {
- isInFont = true;
- var seac = this.seacMap[charcode];
- fontCharCode = seac.baseFontCharCode;
- accent = {
- fontChar: String.fromCharCode(seac.accentFontCharCode),
- offset: seac.accentOffset
- };
- }
- var fontChar = String.fromCharCode(fontCharCode);
- var glyph = this.glyphCache[charcode];
- if (!glyph || !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont)) {
- glyph = new Glyph(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont);
- this.glyphCache[charcode] = glyph;
- }
- return glyph;
- },
- charsToGlyphs: function Font_charsToGlyphs(chars) {
- var charsCache = this.charsCache;
- var glyphs, glyph, charcode;
- if (charsCache) {
- glyphs = charsCache[chars];
- if (glyphs) {
- return glyphs;
- }
- }
- if (!charsCache) {
- charsCache = this.charsCache = Object.create(null);
- }
- glyphs = [];
- var charsCacheKey = chars;
- var i = 0,
- ii;
- if (this.cMap) {
- var c = Object.create(null);
- while (i < chars.length) {
- this.cMap.readCharCode(chars, i, c);
- charcode = c.charcode;
- var length = c.length;
- i += length;
- var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
- glyph = this.charToGlyph(charcode, isSpace);
- glyphs.push(glyph);
- }
- } else {
- for (i = 0, ii = chars.length; i < ii; ++i) {
- charcode = chars.charCodeAt(i);
- glyph = this.charToGlyph(charcode, charcode === 0x20);
- glyphs.push(glyph);
- }
- }
- return charsCache[charsCacheKey] = glyphs;
- }
- };
- return Font;
-}();
-var ErrorFont = function ErrorFontClosure() {
- function ErrorFont(error) {
- this.error = error;
- this.loadedName = 'g_font_error';
- this.loading = false;
- }
- ErrorFont.prototype = {
- charsToGlyphs: function ErrorFont_charsToGlyphs() {
- return [];
- },
- exportData: function ErrorFont_exportData() {
- return { error: this.error };
- }
- };
- return ErrorFont;
-}();
-function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
- var charCodeToGlyphId = Object.create(null);
- var glyphId, charCode, baseEncoding;
- var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
- if (properties.baseEncodingName) {
- baseEncoding = getEncoding(properties.baseEncodingName);
- for (charCode = 0; charCode < baseEncoding.length; charCode++) {
- glyphId = glyphNames.indexOf(baseEncoding[charCode]);
- if (glyphId >= 0) {
- charCodeToGlyphId[charCode] = glyphId;
- } else {
- charCodeToGlyphId[charCode] = 0;
- }
- }
- } else if (isSymbolicFont) {
- for (charCode in builtInEncoding) {
- charCodeToGlyphId[charCode] = builtInEncoding[charCode];
- }
- } else {
- baseEncoding = StandardEncoding;
- for (charCode = 0; charCode < baseEncoding.length; charCode++) {
- glyphId = glyphNames.indexOf(baseEncoding[charCode]);
- if (glyphId >= 0) {
- charCodeToGlyphId[charCode] = glyphId;
- } else {
- charCodeToGlyphId[charCode] = 0;
- }
- }
- }
- var differences = properties.differences,
- glyphsUnicodeMap;
- if (differences) {
- for (charCode in differences) {
- var glyphName = differences[charCode];
- glyphId = glyphNames.indexOf(glyphName);
- if (glyphId === -1) {
- if (!glyphsUnicodeMap) {
- glyphsUnicodeMap = getGlyphsUnicode();
- }
- var standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
- if (standardGlyphName !== glyphName) {
- glyphId = glyphNames.indexOf(standardGlyphName);
- }
- }
- if (glyphId >= 0) {
- charCodeToGlyphId[charCode] = glyphId;
- } else {
- charCodeToGlyphId[charCode] = 0;
- }
- }
- }
- return charCodeToGlyphId;
-}
-var Type1Font = function Type1FontClosure() {
- function findBlock(streamBytes, signature, startIndex) {
- var streamBytesLength = streamBytes.length;
- var signatureLength = signature.length;
- var scanLength = streamBytesLength - signatureLength;
- var i = startIndex,
- j,
- found = false;
- while (i < scanLength) {
- j = 0;
- while (j < signatureLength && streamBytes[i + j] === signature[j]) {
- j++;
- }
- if (j >= signatureLength) {
- i += j;
- while (i < streamBytesLength && isSpace(streamBytes[i])) {
- i++;
- }
- found = true;
- break;
- }
- i++;
- }
- return {
- found: found,
- length: i
- };
- }
- function getHeaderBlock(stream, suggestedLength) {
- var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63];
- var streamStartPos = stream.pos;
- var headerBytes, headerBytesLength, block;
- try {
- headerBytes = stream.getBytes(suggestedLength);
- headerBytesLength = headerBytes.length;
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- }
- if (headerBytesLength === suggestedLength) {
- block = findBlock(headerBytes, EEXEC_SIGNATURE, suggestedLength - 2 * EEXEC_SIGNATURE.length);
- if (block.found && block.length === suggestedLength) {
- return {
- stream: new Stream(headerBytes),
- length: suggestedLength
- };
- }
- }
- warn('Invalid "Length1" property in Type1 font -- trying to recover.');
- stream.pos = streamStartPos;
- var SCAN_BLOCK_LENGTH = 2048;
- var actualLength;
- while (true) {
- var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
- block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
- if (block.length === 0) {
- break;
- }
- stream.pos += block.length;
- if (block.found) {
- actualLength = stream.pos - streamStartPos;
- break;
- }
- }
- stream.pos = streamStartPos;
- if (actualLength) {
- return {
- stream: new Stream(stream.getBytes(actualLength)),
- length: actualLength
- };
- }
- warn('Unable to recover "Length1" property in Type1 font -- using as is.');
- return {
- stream: new Stream(stream.getBytes(suggestedLength)),
- length: suggestedLength
- };
- }
- function getEexecBlock(stream, suggestedLength) {
- var eexecBytes = stream.getBytes();
- return {
- stream: new Stream(eexecBytes),
- length: eexecBytes.length
- };
- }
- function Type1Font(name, file, properties) {
- var PFB_HEADER_SIZE = 6;
- var headerBlockLength = properties.length1;
- var eexecBlockLength = properties.length2;
- var pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
- var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
- if (pfbHeaderPresent) {
- file.skip(PFB_HEADER_SIZE);
- headerBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
- }
- var headerBlock = getHeaderBlock(file, headerBlockLength);
- headerBlockLength = headerBlock.length;
- var headerBlockParser = new Type1Parser(headerBlock.stream, false, SEAC_ANALYSIS_ENABLED);
- headerBlockParser.extractFontHeader(properties);
- if (pfbHeaderPresent) {
- pfbHeader = file.getBytes(PFB_HEADER_SIZE);
- eexecBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
- }
- var eexecBlock = getEexecBlock(file, eexecBlockLength);
- eexecBlockLength = eexecBlock.length;
- var eexecBlockParser = new Type1Parser(eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED);
- var data = eexecBlockParser.extractFontProgram();
- for (var info in data.properties) {
- properties[info] = data.properties[info];
- }
- var charstrings = data.charstrings;
- var type2Charstrings = this.getType2Charstrings(charstrings);
- var subrs = this.getType2Subrs(data.subrs);
- this.charstrings = charstrings;
- this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties);
- this.seacs = this.getSeacs(data.charstrings);
- }
- Type1Font.prototype = {
- get numGlyphs() {
- return this.charstrings.length + 1;
- },
- getCharset: function Type1Font_getCharset() {
- var charset = ['.notdef'];
- var charstrings = this.charstrings;
- for (var glyphId = 0; glyphId < charstrings.length; glyphId++) {
- charset.push(charstrings[glyphId].glyphName);
- }
- return charset;
- },
- getGlyphMapping: function Type1Font_getGlyphMapping(properties) {
- var charstrings = this.charstrings;
- var glyphNames = ['.notdef'],
- glyphId;
- for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
- glyphNames.push(charstrings[glyphId].glyphName);
- }
- var encoding = properties.builtInEncoding;
- if (encoding) {
- var builtInEncoding = Object.create(null);
- for (var charCode in encoding) {
- glyphId = glyphNames.indexOf(encoding[charCode]);
- if (glyphId >= 0) {
- builtInEncoding[charCode] = glyphId;
- }
- }
- }
- return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
- },
- getSeacs: function Type1Font_getSeacs(charstrings) {
- var i, ii;
- var seacMap = [];
- for (i = 0, ii = charstrings.length; i < ii; i++) {
- var charstring = charstrings[i];
- if (charstring.seac) {
- seacMap[i + 1] = charstring.seac;
- }
- }
- return seacMap;
- },
- getType2Charstrings: function Type1Font_getType2Charstrings(type1Charstrings) {
- var type2Charstrings = [];
- for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
- type2Charstrings.push(type1Charstrings[i].charstring);
- }
- return type2Charstrings;
- },
- getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
- var bias = 0;
- var count = type1Subrs.length;
- if (count < 1133) {
- bias = 107;
- } else if (count < 33769) {
- bias = 1131;
- } else {
- bias = 32768;
- }
- var type2Subrs = [];
- var i;
- for (i = 0; i < bias; i++) {
- type2Subrs.push([0x0B]);
- }
- for (i = 0; i < count; i++) {
- type2Subrs.push(type1Subrs[i]);
- }
- return type2Subrs;
- },
- wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
- var cff = new CFF();
- cff.header = new CFFHeader(1, 0, 4, 4);
- cff.names = [name];
- var topDict = new CFFTopDict();
- topDict.setByName('version', 391);
- topDict.setByName('Notice', 392);
- topDict.setByName('FullName', 393);
- topDict.setByName('FamilyName', 394);
- topDict.setByName('Weight', 395);
- topDict.setByName('Encoding', null);
- topDict.setByName('FontMatrix', properties.fontMatrix);
- topDict.setByName('FontBBox', properties.bbox);
- topDict.setByName('charset', null);
- topDict.setByName('CharStrings', null);
- topDict.setByName('Private', null);
- cff.topDict = topDict;
- var strings = new CFFStrings();
- strings.add('Version 0.11');
- strings.add('See original notice');
- strings.add(name);
- strings.add(name);
- strings.add('Medium');
- cff.strings = strings;
- cff.globalSubrIndex = new CFFIndex();
- var count = glyphs.length;
- var charsetArray = [0];
- var i, ii;
- for (i = 0; i < count; i++) {
- var index = CFFStandardStrings.indexOf(charstrings[i].glyphName);
- if (index === -1) {
- index = 0;
- }
- charsetArray.push(index >> 8 & 0xff, index & 0xff);
- }
- cff.charset = new CFFCharset(false, 0, [], charsetArray);
- var charStringsIndex = new CFFIndex();
- charStringsIndex.add([0x8B, 0x0E]);
- for (i = 0; i < count; i++) {
- var glyph = glyphs[i];
- if (glyph.length === 0) {
- charStringsIndex.add([0x8B, 0x0E]);
- continue;
- }
- charStringsIndex.add(glyph);
- }
- cff.charStrings = charStringsIndex;
- var privateDict = new CFFPrivateDict();
- privateDict.setByName('Subrs', null);
- var fields = ['BlueValues', 'OtherBlues', 'FamilyBlues', 'FamilyOtherBlues', 'StemSnapH', 'StemSnapV', 'BlueShift', 'BlueFuzz', 'BlueScale', 'LanguageGroup', 'ExpansionFactor', 'ForceBold', 'StdHW', 'StdVW'];
- for (i = 0, ii = fields.length; i < ii; i++) {
- var field = fields[i];
- if (!(field in properties.privateData)) {
- continue;
- }
- var value = properties.privateData[field];
- if (isArray(value)) {
- for (var j = value.length - 1; j > 0; j--) {
- value[j] -= value[j - 1];
- }
- }
- privateDict.setByName(field, value);
- }
- cff.topDict.privateDict = privateDict;
- var subrIndex = new CFFIndex();
- for (i = 0, ii = subrs.length; i < ii; i++) {
- subrIndex.add(subrs[i]);
- }
- privateDict.subrsIndex = subrIndex;
- var compiler = new CFFCompiler(cff);
- return compiler.compile();
- }
- };
- return Type1Font;
-}();
-var CFFFont = function CFFFontClosure() {
- function CFFFont(file, properties) {
- this.properties = properties;
- var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
- this.cff = parser.parse();
- var compiler = new CFFCompiler(this.cff);
- this.seacs = this.cff.seacs;
- try {
- this.data = compiler.compile();
- } catch (e) {
- warn('Failed to compile font ' + properties.loadedName);
- this.data = file;
- }
- }
- CFFFont.prototype = {
- get numGlyphs() {
- return this.cff.charStrings.count;
- },
- getCharset: function CFFFont_getCharset() {
- return this.cff.charset.charset;
- },
- getGlyphMapping: function CFFFont_getGlyphMapping() {
- var cff = this.cff;
- var properties = this.properties;
- var charsets = cff.charset.charset;
- var charCodeToGlyphId;
- var glyphId;
- if (properties.composite) {
- charCodeToGlyphId = Object.create(null);
- if (cff.isCIDFont) {
- for (glyphId = 0; glyphId < charsets.length; glyphId++) {
- var cid = charsets[glyphId];
- var charCode = properties.cMap.charCodeOf(cid);
- charCodeToGlyphId[charCode] = glyphId;
- }
- } else {
- for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
- charCodeToGlyphId[glyphId] = glyphId;
- }
- }
- return charCodeToGlyphId;
- }
- var encoding = cff.encoding ? cff.encoding.encoding : null;
- charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
- return charCodeToGlyphId;
- }
- };
- return CFFFont;
-}();
-(function checkSeacSupport() {
- if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
- SEAC_ANALYSIS_ENABLED = true;
- }
-})();
-(function checkChromeWindows() {
- if (typeof navigator !== 'undefined' && /Windows.*Chrome/.test(navigator.userAgent)) {
- SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
- }
-})();
-exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED;
-exports.PRIVATE_USE_OFFSET_START = PRIVATE_USE_OFFSET_START;
-exports.PRIVATE_USE_OFFSET_END = PRIVATE_USE_OFFSET_END;
-exports.ErrorFont = ErrorFont;
-exports.Font = Font;
-exports.FontFlags = FontFlags;
-exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
-exports.ProblematicCharRanges = ProblematicCharRanges;
-exports.ToUnicodeMap = ToUnicodeMap;
-exports.getFontType = getFontType;
-
-/***/ }),
-/* 27 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreColorSpace = __w_pdfjs_require__(3);
-var coreStream = __w_pdfjs_require__(2);
-var coreJpx = __w_pdfjs_require__(15);
-var ImageKind = sharedUtil.ImageKind;
-var assert = sharedUtil.assert;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var isArray = sharedUtil.isArray;
-var warn = sharedUtil.warn;
-var Name = corePrimitives.Name;
-var isStream = corePrimitives.isStream;
-var ColorSpace = coreColorSpace.ColorSpace;
-var DecodeStream = coreStream.DecodeStream;
-var JpegStream = coreStream.JpegStream;
-var JpxImage = coreJpx.JpxImage;
-var PDFImage = function PDFImageClosure() {
- function handleImageData(image, nativeDecoder) {
- if (nativeDecoder && nativeDecoder.canDecode(image)) {
- return nativeDecoder.decode(image);
- }
- return Promise.resolve(image);
- }
- function decodeAndClamp(value, addend, coefficient, max) {
- value = addend + value * coefficient;
- return value < 0 ? 0 : value > max ? max : value;
- }
- function resizeImageMask(src, bpc, w1, h1, w2, h2) {
- var length = w2 * h2;
- var dest = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
- var xRatio = w1 / w2;
- var yRatio = h1 / h2;
- var i,
- j,
- py,
- newIndex = 0,
- oldIndex;
- var xScaled = new Uint16Array(w2);
- var w1Scanline = w1;
- for (i = 0; i < w2; i++) {
- xScaled[i] = Math.floor(i * xRatio);
- }
- for (i = 0; i < h2; i++) {
- py = Math.floor(i * yRatio) * w1Scanline;
- for (j = 0; j < w2; j++) {
- oldIndex = py + xScaled[j];
- dest[newIndex++] = src[oldIndex];
- }
- }
- return dest;
- }
- function PDFImage(xref, res, image, inline, smask, mask, isMask) {
- this.image = image;
- var dict = image.dict;
- if (dict.has('Filter')) {
- var filter = dict.get('Filter').name;
- if (filter === 'JPXDecode') {
- var jpxImage = new JpxImage();
- jpxImage.parseImageProperties(image.stream);
- image.stream.reset();
- image.bitsPerComponent = jpxImage.bitsPerComponent;
- image.numComps = jpxImage.componentsCount;
- } else if (filter === 'JBIG2Decode') {
- image.bitsPerComponent = 1;
- image.numComps = 1;
- }
- }
- this.width = dict.get('Width', 'W');
- this.height = dict.get('Height', 'H');
- if (this.width < 1 || this.height < 1) {
- error('Invalid image width: ' + this.width + ' or height: ' + this.height);
- }
- this.interpolate = dict.get('Interpolate', 'I') || false;
- this.imageMask = dict.get('ImageMask', 'IM') || false;
- this.matte = dict.get('Matte') || false;
- var bitsPerComponent = image.bitsPerComponent;
- if (!bitsPerComponent) {
- bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
- if (!bitsPerComponent) {
- if (this.imageMask) {
- bitsPerComponent = 1;
- } else {
- error('Bits per component missing in image: ' + this.imageMask);
- }
- }
- }
- this.bpc = bitsPerComponent;
- if (!this.imageMask) {
- var colorSpace = dict.get('ColorSpace', 'CS');
- if (!colorSpace) {
- info('JPX images (which do not require color spaces)');
- switch (image.numComps) {
- case 1:
- colorSpace = Name.get('DeviceGray');
- break;
- case 3:
- colorSpace = Name.get('DeviceRGB');
- break;
- case 4:
- colorSpace = Name.get('DeviceCMYK');
- break;
- default:
- error('JPX images with ' + this.numComps + ' color components not supported.');
- }
- }
- this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
- this.numComps = this.colorSpace.numComps;
- }
- this.decode = dict.getArray('Decode', 'D');
- this.needsDecode = false;
- if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode) || isMask && !ColorSpace.isDefaultDecode(this.decode, 1))) {
- this.needsDecode = true;
- var max = (1 << bitsPerComponent) - 1;
- this.decodeCoefficients = [];
- this.decodeAddends = [];
- for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
- var dmin = this.decode[i];
- var dmax = this.decode[i + 1];
- this.decodeCoefficients[j] = dmax - dmin;
- this.decodeAddends[j] = max * dmin;
- }
- }
- if (smask) {
- this.smask = new PDFImage(xref, res, smask, false);
- } else if (mask) {
- if (isStream(mask)) {
- var maskDict = mask.dict,
- imageMask = maskDict.get('ImageMask', 'IM');
- if (!imageMask) {
- warn('Ignoring /Mask in image without /ImageMask.');
- } else {
- this.mask = new PDFImage(xref, res, mask, false, null, null, true);
- }
- } else {
- this.mask = mask;
- }
- }
- }
- PDFImage.buildImage = function PDFImage_buildImage(handler, xref, res, image, inline, nativeDecoder) {
- var imagePromise = handleImageData(image, nativeDecoder);
- var smaskPromise;
- var maskPromise;
- var smask = image.dict.get('SMask');
- var mask = image.dict.get('Mask');
- if (smask) {
- smaskPromise = handleImageData(smask, nativeDecoder);
- maskPromise = Promise.resolve(null);
- } else {
- smaskPromise = Promise.resolve(null);
- if (mask) {
- if (isStream(mask)) {
- maskPromise = handleImageData(mask, nativeDecoder);
- } else if (isArray(mask)) {
- maskPromise = Promise.resolve(mask);
- } else {
- warn('Unsupported mask format.');
- maskPromise = Promise.resolve(null);
- }
- } else {
- maskPromise = Promise.resolve(null);
- }
- }
- return Promise.all([imagePromise, smaskPromise, maskPromise]).then(function (results) {
- var imageData = results[0];
- var smaskData = results[1];
- var maskData = results[2];
- return new PDFImage(xref, res, imageData, inline, smaskData, maskData);
- });
- };
- PDFImage.createMask = function PDFImage_createMask(imgArray, width, height, imageIsFromDecodeStream, inverseDecode) {
- var computedLength = (width + 7 >> 3) * height;
- var actualLength = imgArray.byteLength;
- var haveFullData = computedLength === actualLength;
- var data, i;
- if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
- data = imgArray;
- } else if (!inverseDecode) {
- data = new Uint8Array(actualLength);
- data.set(imgArray);
- } else {
- data = new Uint8Array(computedLength);
- data.set(imgArray);
- for (i = actualLength; i < computedLength; i++) {
- data[i] = 0xff;
- }
- }
- if (inverseDecode) {
- for (i = 0; i < actualLength; i++) {
- data[i] = ~data[i];
- }
- }
- return {
- data: data,
- width: width,
- height: height
- };
- };
- PDFImage.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 PDFImage_decodeBuffer(buffer) {
- var bpc = this.bpc;
- var numComps = this.numComps;
- var decodeAddends = this.decodeAddends;
- var decodeCoefficients = this.decodeCoefficients;
- var max = (1 << bpc) - 1;
- var i, ii;
- if (bpc === 1) {
- for (i = 0, ii = buffer.length; i < ii; i++) {
- buffer[i] = +!buffer[i];
- }
- return;
- }
- var index = 0;
- for (i = 0, ii = this.width * this.height; i < ii; i++) {
- for (var j = 0; j < numComps; j++) {
- buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max);
- index++;
- }
- }
- },
- getComponents: function PDFImage_getComponents(buffer) {
- var bpc = this.bpc;
- if (bpc === 8) {
- return buffer;
- }
- var width = this.width;
- var height = this.height;
- var numComps = this.numComps;
- var length = width * height * numComps;
- var bufferPos = 0;
- var output = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
- var rowComps = width * numComps;
- var max = (1 << bpc) - 1;
- var i = 0,
- ii,
- buf;
- if (bpc === 1) {
- var mask, loop1End, loop2End;
- for (var j = 0; j < height; j++) {
- loop1End = i + (rowComps & ~7);
- loop2End = i + rowComps;
- while (i < loop1End) {
- buf = buffer[bufferPos++];
- output[i] = buf >> 7 & 1;
- output[i + 1] = buf >> 6 & 1;
- output[i + 2] = buf >> 5 & 1;
- output[i + 3] = buf >> 4 & 1;
- output[i + 4] = buf >> 3 & 1;
- output[i + 5] = buf >> 2 & 1;
- output[i + 6] = buf >> 1 & 1;
- output[i + 7] = buf & 1;
- i += 8;
- }
- if (i < loop2End) {
- buf = buffer[bufferPos++];
- mask = 128;
- while (i < loop2End) {
- output[i++] = +!!(buf & mask);
- mask >>= 1;
- }
- }
- }
- } else {
- var bits = 0;
- buf = 0;
- for (i = 0, ii = length; i < ii; ++i) {
- if (i % rowComps === 0) {
- buf = 0;
- bits = 0;
- }
- while (bits < bpc) {
- buf = buf << 8 | buffer[bufferPos++];
- bits += 8;
- }
- var remainingBits = bits - bpc;
- var value = buf >> remainingBits;
- output[i] = value < 0 ? 0 : value > max ? max : value;
- buf = buf & (1 << remainingBits) - 1;
- bits = remainingBits;
- }
- }
- return output;
- },
- fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, actualHeight, image) {
- var smask = this.smask;
- var mask = this.mask;
- var alphaBuf, sw, sh, i, ii, j;
- if (smask) {
- sw = smask.width;
- sh = smask.height;
- alphaBuf = new Uint8Array(sw * sh);
- smask.fillGrayBuffer(alphaBuf);
- if (sw !== width || sh !== height) {
- alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
- }
- } else if (mask) {
- if (mask instanceof PDFImage) {
- sw = mask.width;
- sh = mask.height;
- alphaBuf = new Uint8Array(sw * sh);
- mask.numComps = 1;
- mask.fillGrayBuffer(alphaBuf);
- for (i = 0, ii = sw * sh; i < ii; ++i) {
- alphaBuf[i] = 255 - alphaBuf[i];
- }
- if (sw !== width || sh !== height) {
- alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
- }
- } else if (isArray(mask)) {
- alphaBuf = new Uint8Array(width * height);
- var numComps = this.numComps;
- for (i = 0, ii = width * height; i < ii; ++i) {
- var opacity = 0;
- var imageOffset = i * numComps;
- for (j = 0; j < numComps; ++j) {
- var color = image[imageOffset + j];
- var maskOffset = j * 2;
- if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
- opacity = 255;
- break;
- }
- }
- alphaBuf[i] = opacity;
- }
- } else {
- error('Unknown mask format.');
- }
- }
- if (alphaBuf) {
- for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
- rgbaBuf[j] = alphaBuf[i];
- }
- } else {
- for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
- rgbaBuf[j] = 255;
- }
- }
- },
- undoPreblend: function PDFImage_undoPreblend(buffer, width, height) {
- var matte = this.smask && this.smask.matte;
- if (!matte) {
- return;
- }
- var matteRgb = this.colorSpace.getRgb(matte, 0);
- var matteR = matteRgb[0];
- var matteG = matteRgb[1];
- var matteB = matteRgb[2];
- var length = width * height * 4;
- var r, g, b;
- for (var i = 0; i < length; i += 4) {
- var alpha = buffer[i + 3];
- if (alpha === 0) {
- buffer[i] = 255;
- buffer[i + 1] = 255;
- buffer[i + 2] = 255;
- continue;
- }
- var k = 255 / alpha;
- r = (buffer[i] - matteR) * k + matteR;
- g = (buffer[i + 1] - matteG) * k + matteG;
- b = (buffer[i + 2] - matteB) * k + matteB;
- buffer[i] = r <= 0 ? 0 : r >= 255 ? 255 : r | 0;
- buffer[i + 1] = g <= 0 ? 0 : g >= 255 ? 255 : g | 0;
- buffer[i + 2] = b <= 0 ? 0 : b >= 255 ? 255 : b | 0;
- }
- },
- createImageData: function PDFImage_createImageData(forceRGBA) {
- var drawWidth = this.drawWidth;
- var drawHeight = this.drawHeight;
- var imgData = {
- width: drawWidth,
- height: drawHeight
- };
- var numComps = this.numComps;
- var originalWidth = this.width;
- var originalHeight = this.height;
- var bpc = this.bpc;
- var rowBytes = originalWidth * numComps * bpc + 7 >> 3;
- var imgArray;
- if (!forceRGBA) {
- var kind;
- if (this.colorSpace.name === 'DeviceGray' && bpc === 1) {
- kind = ImageKind.GRAYSCALE_1BPP;
- } else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8 && !this.needsDecode) {
- kind = ImageKind.RGB_24BPP;
- }
- if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
- imgData.kind = kind;
- imgArray = this.getImageBytes(originalHeight * rowBytes);
- if (this.image instanceof DecodeStream) {
- imgData.data = imgArray;
- } else {
- var newArray = new Uint8Array(imgArray.length);
- newArray.set(imgArray);
- imgData.data = newArray;
- }
- if (this.needsDecode) {
- assert(kind === ImageKind.GRAYSCALE_1BPP);
- var buffer = imgData.data;
- for (var i = 0, ii = buffer.length; i < ii; i++) {
- buffer[i] ^= 0xff;
- }
- }
- return imgData;
- }
- if (this.image instanceof JpegStream && !this.smask && !this.mask && (this.colorSpace.name === 'DeviceGray' || this.colorSpace.name === 'DeviceRGB' || this.colorSpace.name === 'DeviceCMYK')) {
- imgData.kind = ImageKind.RGB_24BPP;
- imgData.data = this.getImageBytes(originalHeight * rowBytes, drawWidth, drawHeight, true);
- return imgData;
- }
- }
- imgArray = this.getImageBytes(originalHeight * rowBytes);
- var actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
- var comps = this.getComponents(imgArray);
- var alpha01, maybeUndoPreblend;
- if (!forceRGBA && !this.smask && !this.mask) {
- imgData.kind = ImageKind.RGB_24BPP;
- imgData.data = new Uint8Array(drawWidth * drawHeight * 3);
- alpha01 = 0;
- maybeUndoPreblend = false;
- } else {
- imgData.kind = ImageKind.RGBA_32BPP;
- imgData.data = new Uint8Array(drawWidth * drawHeight * 4);
- alpha01 = 1;
- maybeUndoPreblend = true;
- this.fillOpacity(imgData.data, drawWidth, drawHeight, actualHeight, comps);
- }
- if (this.needsDecode) {
- this.decodeBuffer(comps);
- }
- this.colorSpace.fillRgb(imgData.data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
- if (maybeUndoPreblend) {
- this.undoPreblend(imgData.data, drawWidth, actualHeight);
- }
- return imgData;
- },
- fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
- var numComps = this.numComps;
- if (numComps !== 1) {
- error('Reading gray scale from a color image: ' + numComps);
- }
- var width = this.width;
- var height = this.height;
- var bpc = this.bpc;
- var rowBytes = width * numComps * bpc + 7 >> 3;
- var imgArray = this.getImageBytes(height * rowBytes);
- var comps = this.getComponents(imgArray);
- var i, length;
- if (bpc === 1) {
- length = width * height;
- if (this.needsDecode) {
- for (i = 0; i < length; ++i) {
- buffer[i] = comps[i] - 1 & 255;
- }
- } else {
- for (i = 0; i < length; ++i) {
- buffer[i] = -comps[i] & 255;
- }
- }
- return;
- }
- if (this.needsDecode) {
- this.decodeBuffer(comps);
- }
- length = width * height;
- var scale = 255 / ((1 << bpc) - 1);
- for (i = 0; i < length; ++i) {
- buffer[i] = scale * comps[i] | 0;
- }
- },
- getImageBytes: function PDFImage_getImageBytes(length, drawWidth, drawHeight, forceRGB) {
- this.image.reset();
- this.image.drawWidth = drawWidth || this.width;
- this.image.drawHeight = drawHeight || this.height;
- this.image.forceRGB = !!forceRGB;
- return this.image.getBytes(length);
- }
- };
- return PDFImage;
-}();
-exports.PDFImage = PDFImage;
-
-/***/ }),
-/* 28 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreArithmeticDecoder = __w_pdfjs_require__(10);
-var error = sharedUtil.error;
-var log2 = sharedUtil.log2;
-var readInt8 = sharedUtil.readInt8;
-var readUint16 = sharedUtil.readUint16;
-var readUint32 = sharedUtil.readUint32;
-var shadow = sharedUtil.shadow;
-var ArithmeticDecoder = coreArithmeticDecoder.ArithmeticDecoder;
-var Jbig2Image = function Jbig2ImageClosure() {
- function ContextCache() {}
- ContextCache.prototype = {
- getContexts: function (id) {
- if (id in this) {
- return this[id];
- }
- return this[id] = new Int8Array(1 << 16);
- }
- };
- function DecodingContext(data, start, end) {
- this.data = data;
- this.start = start;
- this.end = end;
- }
- DecodingContext.prototype = {
- get decoder() {
- var decoder = new ArithmeticDecoder(this.data, this.start, this.end);
- return shadow(this, 'decoder', decoder);
- },
- get contextCache() {
- var cache = new ContextCache();
- return shadow(this, 'contextCache', cache);
- }
- };
- function decodeInteger(contextCache, procedure, decoder) {
- var contexts = contextCache.getContexts(procedure);
- var prev = 1;
- function readBits(length) {
- var v = 0;
- for (var i = 0; i < length; i++) {
- var bit = decoder.readBit(contexts, prev);
- prev = prev < 256 ? prev << 1 | bit : (prev << 1 | bit) & 511 | 256;
- v = v << 1 | bit;
- }
- return v >>> 0;
- }
- var sign = readBits(1);
- var value = readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(32) + 4436 : readBits(12) + 340 : readBits(8) + 84 : readBits(6) + 20 : readBits(4) + 4 : readBits(2);
- return sign === 0 ? value : value > 0 ? -value : null;
- }
- function decodeIAID(contextCache, decoder, codeLength) {
- var contexts = contextCache.getContexts('IAID');
- var prev = 1;
- for (var i = 0; i < codeLength; i++) {
- var bit = decoder.readBit(contexts, prev);
- prev = prev << 1 | bit;
- }
- if (codeLength < 31) {
- return prev & (1 << codeLength) - 1;
- }
- return prev & 0x7FFFFFFF;
- }
- var SegmentTypes = ['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'];
- var CodingTemplates = [[{
- 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
- }]];
- var RefinementTemplates = [{
- 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
- }]
- }];
- var ReusedContexts = [0x9B25, 0x0795, 0x00E5, 0x0195];
- var RefinementReusedContexts = [0x0020, 0x0008];
- function decodeBitmapTemplate0(width, height, decodingContext) {
- var decoder = decodingContext.decoder;
- var contexts = decodingContext.contextCache.getContexts('GB');
- var contextLabel,
- i,
- j,
- pixel,
- row,
- row1,
- row2,
- bitmap = [];
- var OLD_PIXEL_MASK = 0x7BF7;
- for (i = 0; i < height; i++) {
- row = bitmap[i] = new Uint8Array(width);
- row1 = i < 1 ? row : bitmap[i - 1];
- row2 = i < 2 ? row : bitmap[i - 2];
- contextLabel = row2[0] << 13 | row2[1] << 12 | row2[2] << 11 | row1[0] << 7 | row1[1] << 6 | row1[2] << 5 | row1[3] << 4;
- for (j = 0; j < width; j++) {
- row[j] = pixel = decoder.readBit(contexts, contextLabel);
- contextLabel = (contextLabel & OLD_PIXEL_MASK) << 1 | (j + 3 < width ? row2[j + 3] << 11 : 0) | (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel;
- }
- }
- return bitmap;
- }
- function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) {
- if (mmr) {
- error('JBIG2 error: MMR encoding is not supported');
- }
- if (templateIndex === 0 && !skip && !prediction && at.length === 4 && at[0].x === 3 && at[0].y === -1 && at[1].x === -3 && at[1].y === -1 && at[2].x === 2 && at[2].y === -2 && at[3].x === -2 && at[3].y === -2) {
- return decodeBitmapTemplate0(width, height, decodingContext);
- }
- var useskip = !!skip;
- var template = CodingTemplates[templateIndex].concat(at);
- template.sort(function (a, b) {
- return a.y - b.y || a.x - b.x;
- });
- var templateLength = template.length;
- var templateX = new Int8Array(templateLength);
- var templateY = new Int8Array(templateLength);
- var changingTemplateEntries = [];
- var reuseMask = 0,
- minX = 0,
- maxX = 0,
- minY = 0;
- var c, k;
- for (k = 0; k < templateLength; k++) {
- templateX[k] = template[k].x;
- templateY[k] = template[k].y;
- minX = Math.min(minX, template[k].x);
- maxX = Math.max(maxX, template[k].x);
- minY = Math.min(minY, template[k].y);
- if (k < templateLength - 1 && template[k].y === template[k + 1].y && template[k].x === template[k + 1].x - 1) {
- reuseMask |= 1 << templateLength - 1 - k;
- } else {
- changingTemplateEntries.push(k);
- }
- }
- var changingEntriesLength = changingTemplateEntries.length;
- var changingTemplateX = new Int8Array(changingEntriesLength);
- var changingTemplateY = new Int8Array(changingEntriesLength);
- var changingTemplateBit = new Uint16Array(changingEntriesLength);
- for (c = 0; c < changingEntriesLength; c++) {
- k = changingTemplateEntries[c];
- changingTemplateX[c] = template[k].x;
- changingTemplateY[c] = template[k].y;
- changingTemplateBit[c] = 1 << templateLength - 1 - k;
- }
- var sbb_left = -minX;
- var sbb_top = -minY;
- var sbb_right = width - maxX;
- var pseudoPixelContext = ReusedContexts[templateIndex];
- var row = new Uint8Array(width);
- var bitmap = [];
- var decoder = decodingContext.decoder;
- var contexts = decodingContext.contextCache.getContexts('GB');
- var ltp = 0,
- j,
- i0,
- j0,
- contextLabel = 0,
- bit,
- shift;
- for (var i = 0; i < height; i++) {
- if (prediction) {
- var sltp = decoder.readBit(contexts, pseudoPixelContext);
- ltp ^= sltp;
- if (ltp) {
- bitmap.push(row);
- continue;
- }
- }
- row = new Uint8Array(row);
- bitmap.push(row);
- for (j = 0; j < width; j++) {
- if (useskip && skip[i][j]) {
- row[j] = 0;
- continue;
- }
- if (j >= sbb_left && j < sbb_right && i >= sbb_top) {
- contextLabel = contextLabel << 1 & reuseMask;
- for (k = 0; k < changingEntriesLength; k++) {
- i0 = i + changingTemplateY[k];
- j0 = j + changingTemplateX[k];
- bit = bitmap[i0][j0];
- if (bit) {
- bit = changingTemplateBit[k];
- contextLabel |= bit;
- }
- }
- } else {
- contextLabel = 0;
- shift = templateLength - 1;
- for (k = 0; k < templateLength; k++, shift--) {
- j0 = j + templateX[k];
- if (j0 >= 0 && j0 < width) {
- i0 = i + templateY[k];
- if (i0 >= 0) {
- bit = bitmap[i0][j0];
- if (bit) {
- contextLabel |= bit << shift;
- }
- }
- }
- }
- }
- var pixel = decoder.readBit(contexts, contextLabel);
- row[j] = pixel;
- }
- }
- return bitmap;
- }
- function decodeRefinement(width, height, templateIndex, referenceBitmap, offsetX, offsetY, prediction, at, decodingContext) {
- var codingTemplate = RefinementTemplates[templateIndex].coding;
- if (templateIndex === 0) {
- codingTemplate = codingTemplate.concat([at[0]]);
- }
- var codingTemplateLength = codingTemplate.length;
- var codingTemplateX = new Int32Array(codingTemplateLength);
- var codingTemplateY = new Int32Array(codingTemplateLength);
- var k;
- for (k = 0; k < codingTemplateLength; k++) {
- codingTemplateX[k] = codingTemplate[k].x;
- codingTemplateY[k] = codingTemplate[k].y;
- }
- var referenceTemplate = RefinementTemplates[templateIndex].reference;
- if (templateIndex === 0) {
- referenceTemplate = referenceTemplate.concat([at[1]]);
- }
- var referenceTemplateLength = referenceTemplate.length;
- var referenceTemplateX = new Int32Array(referenceTemplateLength);
- var referenceTemplateY = new Int32Array(referenceTemplateLength);
- for (k = 0; k < referenceTemplateLength; k++) {
- referenceTemplateX[k] = referenceTemplate[k].x;
- referenceTemplateY[k] = referenceTemplate[k].y;
- }
- var referenceWidth = referenceBitmap[0].length;
- var referenceHeight = referenceBitmap.length;
- var pseudoPixelContext = RefinementReusedContexts[templateIndex];
- var bitmap = [];
- var decoder = decodingContext.decoder;
- var contexts = decodingContext.contextCache.getContexts('GR');
- var ltp = 0;
- for (var i = 0; i < height; i++) {
- if (prediction) {
- var sltp = decoder.readBit(contexts, pseudoPixelContext);
- ltp ^= sltp;
- if (ltp) {
- error('JBIG2 error: prediction is not supported');
- }
- }
- var row = new Uint8Array(width);
- bitmap.push(row);
- for (var j = 0; j < width; j++) {
- var i0, j0;
- var contextLabel = 0;
- for (k = 0; k < codingTemplateLength; k++) {
- i0 = i + codingTemplateY[k];
- j0 = j + codingTemplateX[k];
- if (i0 < 0 || j0 < 0 || j0 >= width) {
- contextLabel <<= 1;
- } else {
- contextLabel = contextLabel << 1 | bitmap[i0][j0];
- }
- }
- for (k = 0; k < referenceTemplateLength; k++) {
- i0 = i + referenceTemplateY[k] + offsetY;
- j0 = j + referenceTemplateX[k] + offsetX;
- if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) {
- contextLabel <<= 1;
- } else {
- contextLabel = contextLabel << 1 | referenceBitmap[i0][j0];
- }
- }
- var pixel = decoder.readBit(contexts, contextLabel);
- row[j] = pixel;
- }
- }
- return bitmap;
- }
- function decodeSymbolDictionary(huffman, refinement, symbols, numberOfNewSymbols, numberOfExportedSymbols, huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext) {
- if (huffman) {
- error('JBIG2 error: huffman is not supported');
- }
- var newSymbols = [];
- var currentHeight = 0;
- var symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
- var decoder = decodingContext.decoder;
- var contextCache = decodingContext.contextCache;
- while (newSymbols.length < numberOfNewSymbols) {
- var deltaHeight = decodeInteger(contextCache, 'IADH', decoder);
- currentHeight += deltaHeight;
- var currentWidth = 0;
- while (true) {
- var deltaWidth = decodeInteger(contextCache, 'IADW', decoder);
- if (deltaWidth === null) {
- break;
- }
- currentWidth += deltaWidth;
- var bitmap;
- if (refinement) {
- var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder);
- if (numberOfInstances > 1) {
- bitmap = decodeTextRegion(huffman, refinement, currentWidth, currentHeight, 0, numberOfInstances, 1, symbols.concat(newSymbols), symbolCodeLength, 0, 0, 1, 0, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext);
- } else {
- var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
- var rdx = decodeInteger(contextCache, 'IARDX', decoder);
- var rdy = decodeInteger(contextCache, 'IARDY', decoder);
- var symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length];
- bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext);
- }
- } else {
- bitmap = decodeBitmap(false, currentWidth, currentHeight, templateIndex, false, null, at, decodingContext);
- }
- newSymbols.push(bitmap);
- }
- }
- var exportedSymbols = [];
- var flags = [],
- currentFlag = false;
- var totalSymbolsLength = symbols.length + numberOfNewSymbols;
- while (flags.length < totalSymbolsLength) {
- var runLength = decodeInteger(contextCache, 'IAEX', decoder);
- while (runLength--) {
- flags.push(currentFlag);
- }
- currentFlag = !currentFlag;
- }
- for (var i = 0, ii = symbols.length; i < ii; i++) {
- if (flags[i]) {
- exportedSymbols.push(symbols[i]);
- }
- }
- for (var j = 0; j < numberOfNewSymbols; i++, j++) {
- if (flags[i]) {
- exportedSymbols.push(newSymbols[j]);
- }
- }
- return exportedSymbols;
- }
- function decodeTextRegion(huffman, refinement, width, height, defaultPixelValue, numberOfSymbolInstances, stripSize, inputSymbols, symbolCodeLength, transposed, dsOffset, referenceCorner, combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext) {
- if (huffman) {
- error('JBIG2 error: huffman is not supported');
- }
- var bitmap = [];
- var i, row;
- for (i = 0; i < height; i++) {
- row = new Uint8Array(width);
- if (defaultPixelValue) {
- for (var j = 0; j < width; j++) {
- row[j] = defaultPixelValue;
- }
- }
- bitmap.push(row);
- }
- var decoder = decodingContext.decoder;
- var contextCache = decodingContext.contextCache;
- var stripT = -decodeInteger(contextCache, 'IADT', decoder);
- var firstS = 0;
- i = 0;
- while (i < numberOfSymbolInstances) {
- var deltaT = decodeInteger(contextCache, 'IADT', decoder);
- stripT += deltaT;
- var deltaFirstS = decodeInteger(contextCache, 'IAFS', decoder);
- firstS += deltaFirstS;
- var currentS = firstS;
- do {
- var currentT = stripSize === 1 ? 0 : decodeInteger(contextCache, 'IAIT', decoder);
- var t = stripSize * stripT + currentT;
- var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
- var applyRefinement = refinement && decodeInteger(contextCache, 'IARI', decoder);
- var symbolBitmap = inputSymbols[symbolId];
- var symbolWidth = symbolBitmap[0].length;
- var symbolHeight = symbolBitmap.length;
- if (applyRefinement) {
- var rdw = decodeInteger(contextCache, 'IARDW', decoder);
- var rdh = decodeInteger(contextCache, 'IARDH', decoder);
- var rdx = decodeInteger(contextCache, 'IARDX', decoder);
- var rdy = decodeInteger(contextCache, 'IARDY', decoder);
- symbolWidth += rdw;
- symbolHeight += rdh;
- symbolBitmap = decodeRefinement(symbolWidth, symbolHeight, refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx, (rdh >> 1) + rdy, false, refinementAt, decodingContext);
- }
- var offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight);
- var offsetS = currentS - (referenceCorner & 2 ? symbolWidth : 0);
- var s2, t2, symbolRow;
- if (transposed) {
- for (s2 = 0; s2 < symbolHeight; s2++) {
- row = bitmap[offsetS + s2];
- if (!row) {
- continue;
- }
- symbolRow = symbolBitmap[s2];
- var maxWidth = Math.min(width - offsetT, symbolWidth);
- switch (combinationOperator) {
- case 0:
- for (t2 = 0; t2 < maxWidth; t2++) {
- row[offsetT + t2] |= symbolRow[t2];
- }
- break;
- case 2:
- for (t2 = 0; t2 < maxWidth; t2++) {
- row[offsetT + t2] ^= symbolRow[t2];
- }
- break;
- default:
- error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
- }
- }
- currentS += symbolHeight - 1;
- } else {
- for (t2 = 0; t2 < symbolHeight; t2++) {
- row = bitmap[offsetT + t2];
- if (!row) {
- continue;
- }
- symbolRow = symbolBitmap[t2];
- switch (combinationOperator) {
- case 0:
- for (s2 = 0; s2 < symbolWidth; s2++) {
- row[offsetS + s2] |= symbolRow[s2];
- }
- break;
- case 2:
- for (s2 = 0; s2 < symbolWidth; s2++) {
- row[offsetS + s2] ^= symbolRow[s2];
- }
- break;
- default:
- error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
- }
- }
- currentS += symbolWidth - 1;
- }
- i++;
- var deltaS = decodeInteger(contextCache, 'IADS', decoder);
- if (deltaS === null) {
- break;
- }
- currentS += deltaS + dsOffset;
- } while (true);
- }
- return bitmap;
- }
- function readSegmentHeader(data, start) {
- var segmentHeader = {};
- segmentHeader.number = readUint32(data, start);
- var flags = data[start + 4];
- var segmentType = flags & 0x3F;
- if (!SegmentTypes[segmentType]) {
- error('JBIG2 error: invalid segment type: ' + segmentType);
- }
- segmentHeader.type = segmentType;
- segmentHeader.typeName = SegmentTypes[segmentType];
- segmentHeader.deferredNonRetain = !!(flags & 0x80);
- var pageAssociationFieldSize = !!(flags & 0x40);
- var referredFlags = data[start + 5];
- var referredToCount = referredFlags >> 5 & 7;
- var retainBits = [referredFlags & 31];
- var position = start + 6;
- if (referredFlags === 7) {
- referredToCount = readUint32(data, position - 1) & 0x1FFFFFFF;
- position += 3;
- var bytes = referredToCount + 7 >> 3;
- retainBits[0] = data[position++];
- while (--bytes > 0) {
- retainBits.push(data[position++]);
- }
- } else if (referredFlags === 5 || referredFlags === 6) {
- error('JBIG2 error: invalid referred-to flags');
- }
- segmentHeader.retainBits = retainBits;
- var referredToSegmentNumberSize = segmentHeader.number <= 256 ? 1 : segmentHeader.number <= 65536 ? 2 : 4;
- var referredTo = [];
- var i, ii;
- for (i = 0; i < referredToCount; i++) {
- var number = referredToSegmentNumberSize === 1 ? data[position] : referredToSegmentNumberSize === 2 ? readUint16(data, position) : readUint32(data, position);
- referredTo.push(number);
- position += referredToSegmentNumberSize;
- }
- segmentHeader.referredTo = referredTo;
- if (!pageAssociationFieldSize) {
- segmentHeader.pageAssociation = data[position++];
- } else {
- segmentHeader.pageAssociation = readUint32(data, position);
- position += 4;
- }
- segmentHeader.length = readUint32(data, position);
- position += 4;
- if (segmentHeader.length === 0xFFFFFFFF) {
- if (segmentType === 38) {
- var genericRegionInfo = readRegionSegmentInformation(data, position);
- var genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength];
- var genericRegionMmr = !!(genericRegionSegmentFlags & 1);
- var searchPatternLength = 6;
- var searchPattern = new Uint8Array(searchPatternLength);
- if (!genericRegionMmr) {
- searchPattern[0] = 0xFF;
- searchPattern[1] = 0xAC;
- }
- searchPattern[2] = genericRegionInfo.height >>> 24 & 0xFF;
- searchPattern[3] = genericRegionInfo.height >> 16 & 0xFF;
- searchPattern[4] = genericRegionInfo.height >> 8 & 0xFF;
- searchPattern[5] = genericRegionInfo.height & 0xFF;
- for (i = position, ii = data.length; i < ii; i++) {
- var j = 0;
- while (j < searchPatternLength && searchPattern[j] === data[i + j]) {
- j++;
- }
- if (j === searchPatternLength) {
- segmentHeader.length = i + searchPatternLength;
- break;
- }
- }
- if (segmentHeader.length === 0xFFFFFFFF) {
- error('JBIG2 error: segment end was not found');
- }
- } else {
- error('JBIG2 error: invalid unknown segment length');
- }
- }
- segmentHeader.headerEnd = position;
- return segmentHeader;
- }
- function readSegments(header, data, start, end) {
- var segments = [];
- var position = start;
- while (position < end) {
- var segmentHeader = readSegmentHeader(data, position);
- position = segmentHeader.headerEnd;
- var segment = {
- header: segmentHeader,
- data: data
- };
- if (!header.randomAccess) {
- segment.start = position;
- position += segmentHeader.length;
- segment.end = position;
- }
- segments.push(segment);
- if (segmentHeader.type === 51) {
- break;
- }
- }
- if (header.randomAccess) {
- for (var i = 0, ii = segments.length; i < ii; i++) {
- segments[i].start = position;
- position += segments[i].header.length;
- segments[i].end = position;
- }
- }
- return segments;
- }
- function readRegionSegmentInformation(data, start) {
- return {
- width: readUint32(data, start),
- height: readUint32(data, start + 4),
- x: readUint32(data, start + 8),
- y: readUint32(data, start + 12),
- combinationOperator: data[start + 16] & 7
- };
- }
- var RegionSegmentInformationFieldLength = 17;
- function processSegment(segment, visitor) {
- var header = segment.header;
- var data = segment.data,
- position = segment.start,
- end = segment.end;
- var args, at, i, atLength;
- switch (header.type) {
- case 0:
- var dictionary = {};
- var dictionaryFlags = readUint16(data, position);
- dictionary.huffman = !!(dictionaryFlags & 1);
- dictionary.refinement = !!(dictionaryFlags & 2);
- dictionary.huffmanDHSelector = dictionaryFlags >> 2 & 3;
- dictionary.huffmanDWSelector = dictionaryFlags >> 4 & 3;
- dictionary.bitmapSizeSelector = dictionaryFlags >> 6 & 1;
- dictionary.aggregationInstancesSelector = dictionaryFlags >> 7 & 1;
- dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256);
- dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512);
- dictionary.template = dictionaryFlags >> 10 & 3;
- dictionary.refinementTemplate = dictionaryFlags >> 12 & 1;
- position += 2;
- if (!dictionary.huffman) {
- atLength = dictionary.template === 0 ? 4 : 1;
- at = [];
- for (i = 0; i < atLength; i++) {
- at.push({
- x: readInt8(data, position),
- y: readInt8(data, position + 1)
- });
- position += 2;
- }
- dictionary.at = at;
- }
- if (dictionary.refinement && !dictionary.refinementTemplate) {
- at = [];
- for (i = 0; i < 2; i++) {
- at.push({
- x: readInt8(data, position),
- y: readInt8(data, position + 1)
- });
- position += 2;
- }
- dictionary.refinementAt = at;
- }
- dictionary.numberOfExportedSymbols = readUint32(data, position);
- position += 4;
- dictionary.numberOfNewSymbols = readUint32(data, position);
- position += 4;
- args = [dictionary, header.number, header.referredTo, data, position, end];
- break;
- case 6:
- case 7:
- var textRegion = {};
- textRegion.info = readRegionSegmentInformation(data, position);
- position += RegionSegmentInformationFieldLength;
- var textRegionSegmentFlags = readUint16(data, position);
- position += 2;
- textRegion.huffman = !!(textRegionSegmentFlags & 1);
- textRegion.refinement = !!(textRegionSegmentFlags & 2);
- textRegion.stripSize = 1 << (textRegionSegmentFlags >> 2 & 3);
- textRegion.referenceCorner = textRegionSegmentFlags >> 4 & 3;
- textRegion.transposed = !!(textRegionSegmentFlags & 64);
- textRegion.combinationOperator = textRegionSegmentFlags >> 7 & 3;
- textRegion.defaultPixelValue = textRegionSegmentFlags >> 9 & 1;
- textRegion.dsOffset = textRegionSegmentFlags << 17 >> 27;
- textRegion.refinementTemplate = textRegionSegmentFlags >> 15 & 1;
- if (textRegion.huffman) {
- var textRegionHuffmanFlags = readUint16(data, position);
- position += 2;
- textRegion.huffmanFS = textRegionHuffmanFlags & 3;
- textRegion.huffmanDS = textRegionHuffmanFlags >> 2 & 3;
- textRegion.huffmanDT = textRegionHuffmanFlags >> 4 & 3;
- textRegion.huffmanRefinementDW = textRegionHuffmanFlags >> 6 & 3;
- textRegion.huffmanRefinementDH = textRegionHuffmanFlags >> 8 & 3;
- textRegion.huffmanRefinementDX = textRegionHuffmanFlags >> 10 & 3;
- textRegion.huffmanRefinementDY = textRegionHuffmanFlags >> 12 & 3;
- textRegion.huffmanRefinementSizeSelector = !!(textRegionHuffmanFlags & 14);
- }
- if (textRegion.refinement && !textRegion.refinementTemplate) {
- at = [];
- for (i = 0; i < 2; i++) {
- at.push({
- x: readInt8(data, position),
- y: readInt8(data, position + 1)
- });
- position += 2;
- }
- textRegion.refinementAt = at;
- }
- textRegion.numberOfSymbolInstances = readUint32(data, position);
- position += 4;
- if (textRegion.huffman) {
- error('JBIG2 error: huffman is not supported');
- }
- args = [textRegion, header.referredTo, data, position, end];
- break;
- case 38:
- case 39:
- var genericRegion = {};
- genericRegion.info = readRegionSegmentInformation(data, position);
- position += RegionSegmentInformationFieldLength;
- var genericRegionSegmentFlags = data[position++];
- genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
- genericRegion.template = genericRegionSegmentFlags >> 1 & 3;
- genericRegion.prediction = !!(genericRegionSegmentFlags & 8);
- if (!genericRegion.mmr) {
- atLength = genericRegion.template === 0 ? 4 : 1;
- at = [];
- for (i = 0; i < atLength; i++) {
- at.push({
- x: readInt8(data, position),
- y: readInt8(data, position + 1)
- });
- position += 2;
- }
- genericRegion.at = at;
- }
- args = [genericRegion, data, position, end];
- break;
- case 48:
- var pageInfo = {
- width: readUint32(data, position),
- height: readUint32(data, position + 4),
- resolutionX: readUint32(data, position + 8),
- resolutionY: readUint32(data, position + 12)
- };
- if (pageInfo.height === 0xFFFFFFFF) {
- delete pageInfo.height;
- }
- var pageSegmentFlags = data[position + 16];
- readUint16(data, position + 17);
- pageInfo.lossless = !!(pageSegmentFlags & 1);
- pageInfo.refinement = !!(pageSegmentFlags & 2);
- pageInfo.defaultPixelValue = pageSegmentFlags >> 2 & 1;
- pageInfo.combinationOperator = pageSegmentFlags >> 3 & 3;
- pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
- pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
- args = [pageInfo];
- break;
- case 49:
- break;
- case 50:
- break;
- case 51:
- break;
- case 62:
- break;
- default:
- error('JBIG2 error: segment type ' + header.typeName + '(' + header.type + ') is not implemented');
- }
- var callbackName = 'on' + header.typeName;
- if (callbackName in visitor) {
- visitor[callbackName].apply(visitor, args);
- }
- }
- function processSegments(segments, visitor) {
- for (var i = 0, ii = segments.length; i < ii; i++) {
- processSegment(segments[i], visitor);
- }
- }
- function parseJbig2(data, start, end) {
- var position = start;
- if (data[position] !== 0x97 || data[position + 1] !== 0x4A || data[position + 2] !== 0x42 || data[position + 3] !== 0x32 || data[position + 4] !== 0x0D || data[position + 5] !== 0x0A || data[position + 6] !== 0x1A || data[position + 7] !== 0x0A) {
- error('JBIG2 error: invalid header');
- }
- var header = {};
- position += 8;
- var flags = data[position++];
- header.randomAccess = !(flags & 1);
- if (!(flags & 2)) {
- header.numberOfPages = readUint32(data, position);
- position += 4;
- }
- readSegments(header, data, position, end);
- error('Not implemented');
- }
- function parseJbig2Chunks(chunks) {
- var visitor = new SimpleSegmentVisitor();
- for (var i = 0, ii = chunks.length; i < ii; i++) {
- var chunk = chunks[i];
- var segments = readSegments({}, chunk.data, chunk.start, chunk.end);
- processSegments(segments, visitor);
- }
- return visitor.buffer;
- }
- function SimpleSegmentVisitor() {}
- SimpleSegmentVisitor.prototype = {
- onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) {
- this.currentPageInfo = info;
- var rowSize = info.width + 7 >> 3;
- var buffer = new Uint8Array(rowSize * info.height);
- if (info.defaultPixelValue) {
- for (var i = 0, ii = buffer.length; i < ii; i++) {
- buffer[i] = 0xFF;
- }
- }
- this.buffer = buffer;
- },
- drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) {
- var pageInfo = this.currentPageInfo;
- var width = regionInfo.width,
- height = regionInfo.height;
- var rowSize = pageInfo.width + 7 >> 3;
- var combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator;
- var buffer = this.buffer;
- var mask0 = 128 >> (regionInfo.x & 7);
- var offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3);
- var i, j, mask, offset;
- switch (combinationOperator) {
- case 0:
- for (i = 0; i < height; i++) {
- mask = mask0;
- offset = offset0;
- for (j = 0; j < width; j++) {
- if (bitmap[i][j]) {
- buffer[offset] |= mask;
- }
- mask >>= 1;
- if (!mask) {
- mask = 128;
- offset++;
- }
- }
- offset0 += rowSize;
- }
- break;
- case 2:
- for (i = 0; i < height; i++) {
- mask = mask0;
- offset = offset0;
- for (j = 0; j < width; j++) {
- if (bitmap[i][j]) {
- buffer[offset] ^= mask;
- }
- mask >>= 1;
- if (!mask) {
- mask = 128;
- offset++;
- }
- }
- offset0 += rowSize;
- }
- break;
- default:
- error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
- }
- },
- onImmediateGenericRegion: function SimpleSegmentVisitor_onImmediateGenericRegion(region, data, start, end) {
- var regionInfo = region.info;
- var decodingContext = new DecodingContext(data, start, end);
- var bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height, region.template, region.prediction, null, region.at, decodingContext);
- this.drawBitmap(regionInfo, bitmap);
- },
- onImmediateLosslessGenericRegion: function SimpleSegmentVisitor_onImmediateLosslessGenericRegion() {
- this.onImmediateGenericRegion.apply(this, arguments);
- },
- onSymbolDictionary: function SimpleSegmentVisitor_onSymbolDictionary(dictionary, currentSegment, referredSegments, data, start, end) {
- var huffmanTables;
- if (dictionary.huffman) {
- error('JBIG2 error: huffman is not supported');
- }
- var symbols = this.symbols;
- if (!symbols) {
- this.symbols = symbols = {};
- }
- var inputSymbols = [];
- for (var i = 0, ii = referredSegments.length; i < ii; i++) {
- inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
- }
- var decodingContext = new DecodingContext(data, start, end);
- symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols, dictionary.numberOfExportedSymbols, huffmanTables, dictionary.template, dictionary.at, dictionary.refinementTemplate, dictionary.refinementAt, decodingContext);
- },
- onImmediateTextRegion: function SimpleSegmentVisitor_onImmediateTextRegion(region, referredSegments, data, start, end) {
- var regionInfo = region.info;
- var huffmanTables;
- var symbols = this.symbols;
- var inputSymbols = [];
- for (var i = 0, ii = referredSegments.length; i < ii; i++) {
- inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
- }
- var symbolCodeLength = log2(inputSymbols.length);
- var decodingContext = new DecodingContext(data, start, end);
- var bitmap = decodeTextRegion(region.huffman, region.refinement, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.numberOfSymbolInstances, region.stripSize, inputSymbols, symbolCodeLength, region.transposed, region.dsOffset, region.referenceCorner, region.combinationOperator, huffmanTables, region.refinementTemplate, region.refinementAt, decodingContext);
- this.drawBitmap(regionInfo, bitmap);
- },
- onImmediateLosslessTextRegion: function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
- this.onImmediateTextRegion.apply(this, arguments);
- }
- };
- function Jbig2Image() {}
- Jbig2Image.prototype = {
- parseChunks: function Jbig2Image_parseChunks(chunks) {
- return parseJbig2Chunks(chunks);
- }
- };
- return Jbig2Image;
-}();
-exports.Jbig2Image = Jbig2Image;
-
-/***/ }),
-/* 29 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var warn = sharedUtil.warn;
-var error = sharedUtil.error;
-var JpegImage = function JpegImageClosure() {
- var dctZigZag = 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]);
- var dctCos1 = 4017;
- var dctSin1 = 799;
- var dctCos3 = 3406;
- var dctSin3 = 2276;
- var dctCos6 = 1567;
- var dctSin6 = 3784;
- var dctSqrt2 = 5793;
- var dctSqrt1d2 = 2896;
- function JpegImage() {
- this.decodeTransform = null;
- this.colorTransform = -1;
- }
- function buildHuffmanTable(codeLengths, values) {
- var k = 0,
- code = [],
- i,
- j,
- length = 16;
- while (length > 0 && !codeLengths[length - 1]) {
- length--;
- }
- code.push({
- children: [],
- index: 0
- });
- var p = code[0],
- q;
- for (i = 0; i < length; i++) {
- for (j = 0; j < codeLengths[i]; j++) {
- p = code.pop();
- p.children[p.index] = values[k];
- while (p.index > 0) {
- p = code.pop();
- }
- p.index++;
- code.push(p);
- while (code.length <= i) {
- code.push(q = {
- children: [],
- index: 0
- });
- p.children[p.index] = q.children;
- p = q;
- }
- k++;
- }
- if (i + 1 < length) {
- code.push(q = {
- children: [],
- index: 0
- });
- p.children[p.index] = q.children;
- p = q;
- }
- }
- return code[0].children;
- }
- function getBlockBufferOffset(component, row, col) {
- return 64 * ((component.blocksPerLine + 1) * row + col);
- }
- function decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successivePrev, successive) {
- var mcusPerLine = frame.mcusPerLine;
- var progressive = frame.progressive;
- var startOffset = offset,
- bitsData = 0,
- bitsCount = 0;
- function readBit() {
- if (bitsCount > 0) {
- bitsCount--;
- return bitsData >> bitsCount & 1;
- }
- bitsData = data[offset++];
- if (bitsData === 0xFF) {
- var nextByte = data[offset++];
- if (nextByte) {
- error('JPEG error: unexpected marker ' + (bitsData << 8 | nextByte).toString(16));
- }
- }
- bitsCount = 7;
- return bitsData >>> 7;
- }
- function decodeHuffman(tree) {
- var node = tree;
- while (true) {
- node = node[readBit()];
- if (typeof node === 'number') {
- return node;
- }
- if (typeof node !== 'object') {
- error('JPEG error: invalid huffman sequence');
- }
- }
- }
- function receive(length) {
- var n = 0;
- while (length > 0) {
- n = n << 1 | readBit();
- length--;
- }
- return n;
- }
- function receiveAndExtend(length) {
- if (length === 1) {
- return readBit() === 1 ? 1 : -1;
- }
- var n = receive(length);
- if (n >= 1 << length - 1) {
- return n;
- }
- return n + (-1 << length) + 1;
- }
- function decodeBaseline(component, offset) {
- var t = decodeHuffman(component.huffmanTableDC);
- var diff = t === 0 ? 0 : receiveAndExtend(t);
- component.blockData[offset] = component.pred += diff;
- var k = 1;
- while (k < 64) {
- var rs = decodeHuffman(component.huffmanTableAC);
- var s = rs & 15,
- r = rs >> 4;
- if (s === 0) {
- if (r < 15) {
- break;
- }
- k += 16;
- continue;
- }
- k += r;
- var z = dctZigZag[k];
- component.blockData[offset + z] = receiveAndExtend(s);
- k++;
- }
- }
- function decodeDCFirst(component, offset) {
- var t = decodeHuffman(component.huffmanTableDC);
- var diff = t === 0 ? 0 : receiveAndExtend(t) << successive;
- component.blockData[offset] = component.pred += diff;
- }
- function decodeDCSuccessive(component, offset) {
- component.blockData[offset] |= readBit() << successive;
- }
- var eobrun = 0;
- function decodeACFirst(component, offset) {
- if (eobrun > 0) {
- eobrun--;
- return;
- }
- var k = spectralStart,
- e = spectralEnd;
- while (k <= e) {
- var rs = decodeHuffman(component.huffmanTableAC);
- var s = rs & 15,
- r = rs >> 4;
- if (s === 0) {
- if (r < 15) {
- eobrun = receive(r) + (1 << r) - 1;
- break;
- }
- k += 16;
- continue;
- }
- k += r;
- var z = dctZigZag[k];
- component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive);
- k++;
- }
- }
- var successiveACState = 0,
- successiveACNextValue;
- function decodeACSuccessive(component, offset) {
- var k = spectralStart;
- var e = spectralEnd;
- var r = 0;
- var s;
- var rs;
- while (k <= e) {
- var z = dctZigZag[k];
- switch (successiveACState) {
- case 0:
- rs = decodeHuffman(component.huffmanTableAC);
- s = rs & 15;
- r = rs >> 4;
- if (s === 0) {
- if (r < 15) {
- eobrun = receive(r) + (1 << r);
- successiveACState = 4;
- } else {
- r = 16;
- successiveACState = 1;
- }
- } else {
- if (s !== 1) {
- error('JPEG error: invalid ACn encoding');
- }
- successiveACNextValue = receiveAndExtend(s);
- successiveACState = r ? 2 : 3;
- }
- continue;
- case 1:
- case 2:
- if (component.blockData[offset + z]) {
- component.blockData[offset + z] += readBit() << successive;
- } else {
- r--;
- if (r === 0) {
- successiveACState = successiveACState === 2 ? 3 : 0;
- }
- }
- break;
- case 3:
- if (component.blockData[offset + z]) {
- component.blockData[offset + z] += readBit() << successive;
- } else {
- component.blockData[offset + z] = successiveACNextValue << successive;
- successiveACState = 0;
- }
- break;
- case 4:
- if (component.blockData[offset + z]) {
- component.blockData[offset + z] += readBit() << successive;
- }
- break;
- }
- k++;
- }
- if (successiveACState === 4) {
- eobrun--;
- if (eobrun === 0) {
- successiveACState = 0;
- }
- }
- }
- function decodeMcu(component, decode, mcu, row, col) {
- var mcuRow = mcu / mcusPerLine | 0;
- var mcuCol = mcu % mcusPerLine;
- var blockRow = mcuRow * component.v + row;
- var blockCol = mcuCol * component.h + col;
- var offset = getBlockBufferOffset(component, blockRow, blockCol);
- decode(component, offset);
- }
- function decodeBlock(component, decode, mcu) {
- var blockRow = mcu / component.blocksPerLine | 0;
- var blockCol = mcu % component.blocksPerLine;
- var offset = getBlockBufferOffset(component, blockRow, blockCol);
- decode(component, offset);
- }
- var componentsLength = components.length;
- var component, i, j, k, n;
- var decodeFn;
- if (progressive) {
- if (spectralStart === 0) {
- decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
- } else {
- decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
- }
- } else {
- decodeFn = decodeBaseline;
- }
- var mcu = 0,
- fileMarker;
- var mcuExpected;
- if (componentsLength === 1) {
- mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
- } else {
- mcuExpected = mcusPerLine * frame.mcusPerColumn;
- }
- var h, v;
- while (mcu < mcuExpected) {
- var mcuToRead = resetInterval ? Math.min(mcuExpected - mcu, resetInterval) : mcuExpected;
- for (i = 0; i < componentsLength; i++) {
- components[i].pred = 0;
- }
- eobrun = 0;
- if (componentsLength === 1) {
- component = components[0];
- for (n = 0; n < mcuToRead; n++) {
- decodeBlock(component, decodeFn, mcu);
- mcu++;
- }
- } else {
- for (n = 0; n < mcuToRead; n++) {
- for (i = 0; i < componentsLength; i++) {
- component = components[i];
- h = component.h;
- v = component.v;
- for (j = 0; j < v; j++) {
- for (k = 0; k < h; k++) {
- decodeMcu(component, decodeFn, mcu, j, k);
- }
- }
- }
- mcu++;
- }
- }
- bitsCount = 0;
- fileMarker = findNextFileMarker(data, offset);
- if (fileMarker && fileMarker.invalid) {
- warn('decodeScan - unexpected MCU data, next marker is: ' + fileMarker.invalid);
- offset = fileMarker.offset;
- }
- var marker = fileMarker && fileMarker.marker;
- if (!marker || marker <= 0xFF00) {
- error('JPEG error: marker was not found');
- }
- if (marker >= 0xFFD0 && marker <= 0xFFD7) {
- offset += 2;
- } else {
- break;
- }
- }
- fileMarker = findNextFileMarker(data, offset);
- if (fileMarker && fileMarker.invalid) {
- warn('decodeScan - unexpected Scan data, next marker is: ' + fileMarker.invalid);
- offset = fileMarker.offset;
- }
- return offset - startOffset;
- }
- function quantizeAndInverse(component, blockBufferOffset, p) {
- var qt = component.quantizationTable,
- blockData = component.blockData;
- var v0, v1, v2, v3, v4, v5, v6, v7;
- var p0, p1, p2, p3, p4, p5, p6, p7;
- var t;
- if (!qt) {
- error('JPEG error: missing required Quantization Table.');
- }
- for (var row = 0; row < 64; row += 8) {
- p0 = blockData[blockBufferOffset + row];
- p1 = blockData[blockBufferOffset + row + 1];
- p2 = blockData[blockBufferOffset + row + 2];
- p3 = blockData[blockBufferOffset + row + 3];
- p4 = blockData[blockBufferOffset + row + 4];
- p5 = blockData[blockBufferOffset + row + 5];
- p6 = blockData[blockBufferOffset + row + 6];
- p7 = blockData[blockBufferOffset + row + 7];
- p0 *= qt[row];
- if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
- t = dctSqrt2 * p0 + 512 >> 10;
- p[row] = t;
- p[row + 1] = t;
- p[row + 2] = t;
- p[row + 3] = t;
- p[row + 4] = t;
- p[row + 5] = t;
- p[row + 6] = t;
- p[row + 7] = t;
- continue;
- }
- p1 *= qt[row + 1];
- p2 *= qt[row + 2];
- p3 *= qt[row + 3];
- p4 *= qt[row + 4];
- p5 *= qt[row + 5];
- p6 *= qt[row + 6];
- p7 *= qt[row + 7];
- v0 = dctSqrt2 * p0 + 128 >> 8;
- v1 = dctSqrt2 * p4 + 128 >> 8;
- v2 = p2;
- v3 = p6;
- v4 = dctSqrt1d2 * (p1 - p7) + 128 >> 8;
- v7 = dctSqrt1d2 * (p1 + p7) + 128 >> 8;
- v5 = p3 << 4;
- v6 = p5 << 4;
- v0 = v0 + v1 + 1 >> 1;
- v1 = v0 - v1;
- t = v2 * dctSin6 + v3 * dctCos6 + 128 >> 8;
- v2 = v2 * dctCos6 - v3 * dctSin6 + 128 >> 8;
- v3 = t;
- v4 = v4 + v6 + 1 >> 1;
- v6 = v4 - v6;
- v7 = v7 + v5 + 1 >> 1;
- v5 = v7 - v5;
- v0 = v0 + v3 + 1 >> 1;
- v3 = v0 - v3;
- v1 = v1 + v2 + 1 >> 1;
- v2 = v1 - v2;
- t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
- v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
- v7 = t;
- t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
- v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
- v6 = t;
- p[row] = v0 + v7;
- p[row + 7] = v0 - v7;
- p[row + 1] = v1 + v6;
- p[row + 6] = v1 - v6;
- p[row + 2] = v2 + v5;
- p[row + 5] = v2 - v5;
- p[row + 3] = v3 + v4;
- p[row + 4] = v3 - v4;
- }
- for (var col = 0; col < 8; ++col) {
- p0 = p[col];
- p1 = p[col + 8];
- p2 = p[col + 16];
- p3 = p[col + 24];
- p4 = p[col + 32];
- p5 = p[col + 40];
- p6 = p[col + 48];
- p7 = p[col + 56];
- if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
- t = dctSqrt2 * p0 + 8192 >> 14;
- t = t < -2040 ? 0 : t >= 2024 ? 255 : t + 2056 >> 4;
- blockData[blockBufferOffset + col] = t;
- blockData[blockBufferOffset + col + 8] = t;
- blockData[blockBufferOffset + col + 16] = t;
- blockData[blockBufferOffset + col + 24] = t;
- blockData[blockBufferOffset + col + 32] = t;
- blockData[blockBufferOffset + col + 40] = t;
- blockData[blockBufferOffset + col + 48] = t;
- blockData[blockBufferOffset + col + 56] = t;
- continue;
- }
- v0 = dctSqrt2 * p0 + 2048 >> 12;
- v1 = dctSqrt2 * p4 + 2048 >> 12;
- v2 = p2;
- v3 = p6;
- v4 = dctSqrt1d2 * (p1 - p7) + 2048 >> 12;
- v7 = dctSqrt1d2 * (p1 + p7) + 2048 >> 12;
- v5 = p3;
- v6 = p5;
- v0 = (v0 + v1 + 1 >> 1) + 4112;
- v1 = v0 - v1;
- t = v2 * dctSin6 + v3 * dctCos6 + 2048 >> 12;
- v2 = v2 * dctCos6 - v3 * dctSin6 + 2048 >> 12;
- v3 = t;
- v4 = v4 + v6 + 1 >> 1;
- v6 = v4 - v6;
- v7 = v7 + v5 + 1 >> 1;
- v5 = v7 - v5;
- v0 = v0 + v3 + 1 >> 1;
- v3 = v0 - v3;
- v1 = v1 + v2 + 1 >> 1;
- v2 = v1 - v2;
- t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
- v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
- v7 = t;
- t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
- v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
- v6 = t;
- p0 = v0 + v7;
- p7 = v0 - v7;
- p1 = v1 + v6;
- p6 = v1 - v6;
- p2 = v2 + v5;
- p5 = v2 - v5;
- p3 = v3 + v4;
- p4 = v3 - v4;
- p0 = p0 < 16 ? 0 : p0 >= 4080 ? 255 : p0 >> 4;
- p1 = p1 < 16 ? 0 : p1 >= 4080 ? 255 : p1 >> 4;
- p2 = p2 < 16 ? 0 : p2 >= 4080 ? 255 : p2 >> 4;
- p3 = p3 < 16 ? 0 : p3 >= 4080 ? 255 : p3 >> 4;
- p4 = p4 < 16 ? 0 : p4 >= 4080 ? 255 : p4 >> 4;
- p5 = p5 < 16 ? 0 : p5 >= 4080 ? 255 : p5 >> 4;
- p6 = p6 < 16 ? 0 : p6 >= 4080 ? 255 : p6 >> 4;
- p7 = p7 < 16 ? 0 : p7 >= 4080 ? 255 : p7 >> 4;
- blockData[blockBufferOffset + col] = p0;
- blockData[blockBufferOffset + col + 8] = p1;
- blockData[blockBufferOffset + col + 16] = p2;
- blockData[blockBufferOffset + col + 24] = p3;
- blockData[blockBufferOffset + col + 32] = p4;
- blockData[blockBufferOffset + col + 40] = p5;
- blockData[blockBufferOffset + col + 48] = p6;
- blockData[blockBufferOffset + col + 56] = p7;
- }
- }
- function buildComponentData(frame, component) {
- var blocksPerLine = component.blocksPerLine;
- var blocksPerColumn = component.blocksPerColumn;
- var computationBuffer = new Int16Array(64);
- for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
- for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
- var offset = getBlockBufferOffset(component, blockRow, blockCol);
- quantizeAndInverse(component, offset, computationBuffer);
- }
- }
- return component.blockData;
- }
- function clamp0to255(a) {
- return a <= 0 ? 0 : a >= 255 ? 255 : a;
- }
- function findNextFileMarker(data, currentPos, startPos) {
- function peekUint16(pos) {
- return data[pos] << 8 | data[pos + 1];
- }
- var maxPos = data.length - 1;
- var newPos = startPos < currentPos ? startPos : currentPos;
- if (currentPos >= maxPos) {
- return null;
- }
- var currentMarker = peekUint16(currentPos);
- if (currentMarker >= 0xFFC0 && currentMarker <= 0xFFFE) {
- return {
- invalid: null,
- marker: currentMarker,
- offset: currentPos
- };
- }
- var newMarker = peekUint16(newPos);
- while (!(newMarker >= 0xFFC0 && newMarker <= 0xFFFE)) {
- if (++newPos >= maxPos) {
- return null;
- }
- newMarker = peekUint16(newPos);
- }
- return {
- invalid: currentMarker.toString(16),
- marker: newMarker,
- offset: newPos
- };
- }
- JpegImage.prototype = {
- parse: function parse(data) {
- function readUint16() {
- var value = data[offset] << 8 | data[offset + 1];
- offset += 2;
- return value;
- }
- function readDataBlock() {
- var length = readUint16();
- var endOffset = offset + length - 2;
- var fileMarker = findNextFileMarker(data, endOffset, offset);
- if (fileMarker && fileMarker.invalid) {
- warn('readDataBlock - incorrect length, next marker is: ' + fileMarker.invalid);
- endOffset = fileMarker.offset;
- }
- var array = data.subarray(offset, endOffset);
- offset += array.length;
- return array;
- }
- function prepareComponents(frame) {
- var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
- var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
- for (var i = 0; i < frame.components.length; i++) {
- component = frame.components[i];
- var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
- var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV);
- var blocksPerLineForMcu = mcusPerLine * component.h;
- var blocksPerColumnForMcu = mcusPerColumn * component.v;
- var blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
- component.blockData = new Int16Array(blocksBufferSize);
- component.blocksPerLine = blocksPerLine;
- component.blocksPerColumn = blocksPerColumn;
- }
- frame.mcusPerLine = mcusPerLine;
- frame.mcusPerColumn = mcusPerColumn;
- }
- var offset = 0;
- var jfif = null;
- var adobe = null;
- var frame, resetInterval;
- var quantizationTables = [];
- var huffmanTablesAC = [],
- huffmanTablesDC = [];
- var fileMarker = readUint16();
- if (fileMarker !== 0xFFD8) {
- error('JPEG error: SOI not found');
- }
- fileMarker = readUint16();
- while (fileMarker !== 0xFFD9) {
- var i, j, l;
- switch (fileMarker) {
- case 0xFFE0:
- case 0xFFE1:
- case 0xFFE2:
- case 0xFFE3:
- case 0xFFE4:
- case 0xFFE5:
- case 0xFFE6:
- case 0xFFE7:
- case 0xFFE8:
- case 0xFFE9:
- case 0xFFEA:
- case 0xFFEB:
- case 0xFFEC:
- case 0xFFED:
- case 0xFFEE:
- case 0xFFEF:
- case 0xFFFE:
- var appData = readDataBlock();
- if (fileMarker === 0xFFE0) {
- if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && appData[3] === 0x46 && appData[4] === 0) {
- jfif = {
- version: {
- major: appData[5],
- minor: appData[6]
- },
- densityUnits: appData[7],
- xDensity: appData[8] << 8 | appData[9],
- yDensity: appData[10] << 8 | appData[11],
- thumbWidth: appData[12],
- thumbHeight: appData[13],
- thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
- };
- }
- }
- if (fileMarker === 0xFFEE) {
- if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && appData[3] === 0x62 && appData[4] === 0x65) {
- adobe = {
- version: appData[5] << 8 | appData[6],
- flags0: appData[7] << 8 | appData[8],
- flags1: appData[9] << 8 | appData[10],
- transformCode: appData[11]
- };
- }
- }
- break;
- case 0xFFDB:
- var quantizationTablesLength = readUint16();
- var quantizationTablesEnd = quantizationTablesLength + offset - 2;
- var z;
- while (offset < quantizationTablesEnd) {
- var quantizationTableSpec = data[offset++];
- var tableData = new Uint16Array(64);
- if (quantizationTableSpec >> 4 === 0) {
- for (j = 0; j < 64; j++) {
- z = dctZigZag[j];
- tableData[z] = data[offset++];
- }
- } else if (quantizationTableSpec >> 4 === 1) {
- for (j = 0; j < 64; j++) {
- z = dctZigZag[j];
- tableData[z] = readUint16();
- }
- } else {
- error('JPEG error: DQT - invalid table spec');
- }
- quantizationTables[quantizationTableSpec & 15] = tableData;
- }
- break;
- case 0xFFC0:
- case 0xFFC1:
- case 0xFFC2:
- if (frame) {
- error('JPEG error: Only single frame JPEGs supported');
- }
- readUint16();
- frame = {};
- frame.extended = fileMarker === 0xFFC1;
- frame.progressive = fileMarker === 0xFFC2;
- frame.precision = data[offset++];
- frame.scanLines = readUint16();
- frame.samplesPerLine = readUint16();
- frame.components = [];
- frame.componentIds = {};
- var componentsCount = data[offset++],
- componentId;
- var maxH = 0,
- maxV = 0;
- for (i = 0; i < componentsCount; i++) {
- componentId = data[offset];
- var h = data[offset + 1] >> 4;
- var v = data[offset + 1] & 15;
- if (maxH < h) {
- maxH = h;
- }
- if (maxV < v) {
- maxV = v;
- }
- var qId = data[offset + 2];
- l = frame.components.push({
- h: h,
- v: v,
- quantizationId: qId,
- quantizationTable: null
- });
- frame.componentIds[componentId] = l - 1;
- offset += 3;
- }
- frame.maxH = maxH;
- frame.maxV = maxV;
- prepareComponents(frame);
- break;
- case 0xFFC4:
- var huffmanLength = readUint16();
- for (i = 2; i < huffmanLength;) {
- var huffmanTableSpec = data[offset++];
- var codeLengths = new Uint8Array(16);
- var codeLengthSum = 0;
- for (j = 0; j < 16; j++, offset++) {
- codeLengthSum += codeLengths[j] = data[offset];
- }
- var huffmanValues = new Uint8Array(codeLengthSum);
- for (j = 0; j < codeLengthSum; j++, offset++) {
- huffmanValues[j] = data[offset];
- }
- i += 17 + codeLengthSum;
- (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues);
- }
- break;
- case 0xFFDD:
- readUint16();
- resetInterval = readUint16();
- break;
- case 0xFFDA:
- readUint16();
- var selectorsCount = data[offset++];
- var components = [],
- component;
- for (i = 0; i < selectorsCount; i++) {
- var componentIndex = frame.componentIds[data[offset++]];
- component = frame.components[componentIndex];
- var tableSpec = data[offset++];
- component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
- component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
- components.push(component);
- }
- var spectralStart = data[offset++];
- var spectralEnd = data[offset++];
- var successiveApproximation = data[offset++];
- var processed = decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15);
- offset += processed;
- break;
- case 0xFFFF:
- if (data[offset] !== 0xFF) {
- offset--;
- }
- break;
- default:
- if (data[offset - 3] === 0xFF && data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
- offset -= 3;
- break;
- }
- error('JPEG error: unknown marker ' + fileMarker.toString(16));
- }
- fileMarker = readUint16();
- }
- this.width = frame.samplesPerLine;
- this.height = frame.scanLines;
- this.jfif = jfif;
- this.adobe = adobe;
- this.components = [];
- for (i = 0; i < frame.components.length; i++) {
- component = frame.components[i];
- var quantizationTable = quantizationTables[component.quantizationId];
- if (quantizationTable) {
- component.quantizationTable = quantizationTable;
- }
- this.components.push({
- output: buildComponentData(frame, component),
- scaleX: component.h / frame.maxH,
- scaleY: component.v / frame.maxV,
- blocksPerLine: component.blocksPerLine,
- blocksPerColumn: component.blocksPerColumn
- });
- }
- this.numComponents = this.components.length;
- },
- _getLinearizedBlockData: function getLinearizedBlockData(width, height) {
- var scaleX = this.width / width,
- scaleY = this.height / height;
- var component, componentScaleX, componentScaleY, blocksPerScanline;
- var x, y, i, j, k;
- var index;
- var offset = 0;
- var output;
- var numComponents = this.components.length;
- var dataLength = width * height * numComponents;
- var data = new Uint8Array(dataLength);
- var xScaleBlockOffset = new Uint32Array(width);
- var mask3LSB = 0xfffffff8;
- for (i = 0; i < numComponents; i++) {
- component = this.components[i];
- componentScaleX = component.scaleX * scaleX;
- componentScaleY = component.scaleY * scaleY;
- offset = i;
- output = component.output;
- blocksPerScanline = component.blocksPerLine + 1 << 3;
- for (x = 0; x < width; x++) {
- j = 0 | x * componentScaleX;
- xScaleBlockOffset[x] = (j & mask3LSB) << 3 | j & 7;
- }
- for (y = 0; y < height; y++) {
- j = 0 | y * componentScaleY;
- index = blocksPerScanline * (j & mask3LSB) | (j & 7) << 3;
- for (x = 0; x < width; x++) {
- data[offset] = output[index + xScaleBlockOffset[x]];
- offset += numComponents;
- }
- }
- }
- var transform = this.decodeTransform;
- if (transform) {
- for (i = 0; i < dataLength;) {
- for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
- data[i] = (data[i] * transform[k] >> 8) + transform[k + 1];
- }
- }
- }
- return data;
- },
- _isColorConversionNeeded: function isColorConversionNeeded() {
- if (this.adobe && this.adobe.transformCode) {
- return true;
- } else if (this.numComponents === 3) {
- if (!this.adobe && this.colorTransform === 0) {
- return false;
- }
- return true;
- }
- if (!this.adobe && this.colorTransform === 1) {
- return true;
- }
- return false;
- },
- _convertYccToRgb: function convertYccToRgb(data) {
- var Y, Cb, Cr;
- for (var i = 0, length = data.length; i < length; i += 3) {
- Y = data[i];
- Cb = data[i + 1];
- Cr = data[i + 2];
- data[i] = clamp0to255(Y - 179.456 + 1.402 * Cr);
- data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
- data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb);
- }
- return data;
- },
- _convertYcckToRgb: function convertYcckToRgb(data) {
- var Y, Cb, Cr, k;
- var offset = 0;
- for (var i = 0, length = data.length; i < length; i += 4) {
- Y = data[i];
- Cb = data[i + 1];
- Cr = data[i + 2];
- k = data[i + 3];
- var r = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776);
- var g = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665);
- var b = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407);
- data[offset++] = clamp0to255(r);
- data[offset++] = clamp0to255(g);
- data[offset++] = clamp0to255(b);
- }
- return data;
- },
- _convertYcckToCmyk: function convertYcckToCmyk(data) {
- var Y, Cb, Cr;
- for (var i = 0, length = data.length; i < length; i += 4) {
- Y = data[i];
- Cb = data[i + 1];
- Cr = data[i + 2];
- data[i] = clamp0to255(434.456 - Y - 1.402 * Cr);
- data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
- data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb);
- }
- return data;
- },
- _convertCmykToRgb: function convertCmykToRgb(data) {
- var c, m, y, k;
- var offset = 0;
- var min = -255 * 255 * 255;
- var scale = 1 / 255 / 255;
- for (var i = 0, length = data.length; i < length; i += 4) {
- c = data[i];
- m = data[i + 1];
- y = data[i + 2];
- k = data[i + 3];
- var r = c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 72734.4411664936) + m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 1401.7366389350734) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 4465.541406466231) - k * (21.86122147463605 * k + 48317.86113160301);
- var g = c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 20220.756542821975) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 48691.05921601825) + y * (4.444339102852739 * y + 9.8632861493405 * k - 6341.191035517494) - k * (20.737325471181034 * k + 47890.15695978492);
- var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 3616.812083916688) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 28620.90484698408) + y * (0.03296041114873217 * y + 115.60384449646641 * k - 49363.43385999684) - k * (22.33816807309886 * k + 45932.16563550634);
- data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0;
- data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0;
- data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0;
- }
- return data;
- },
- getData: function getData(width, height, forceRGBoutput) {
- if (this.numComponents > 4) {
- error('JPEG error: Unsupported color mode');
- }
- var data = this._getLinearizedBlockData(width, height);
- if (this.numComponents === 1 && forceRGBoutput) {
- var dataLength = data.length;
- var rgbData = new Uint8Array(dataLength * 3);
- var offset = 0;
- for (var i = 0; i < dataLength; i++) {
- var grayColor = data[i];
- rgbData[offset++] = grayColor;
- rgbData[offset++] = grayColor;
- rgbData[offset++] = grayColor;
- }
- return rgbData;
- } else if (this.numComponents === 3 && this._isColorConversionNeeded()) {
- return this._convertYccToRgb(data);
- } else if (this.numComponents === 4) {
- if (this._isColorConversionNeeded()) {
- if (forceRGBoutput) {
- return this._convertYcckToRgb(data);
- }
- return this._convertYcckToCmyk(data);
- } else if (forceRGBoutput) {
- return this._convertCmykToRgb(data);
- }
- }
- return data;
- }
- };
- return JpegImage;
-}();
-exports.JpegImage = JpegImage;
-
-/***/ }),
-/* 30 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var getLookupTableFactory = sharedUtil.getLookupTableFactory;
-var getMetrics = getLookupTableFactory(function (t) {
- t['Courier'] = 600;
- t['Courier-Bold'] = 600;
- t['Courier-BoldOblique'] = 600;
- t['Courier-Oblique'] = 600;
- t['Helvetica'] = getLookupTableFactory(function (t) {
- t['space'] = 278;
- t['exclam'] = 278;
- t['quotedbl'] = 355;
- t['numbersign'] = 556;
- t['dollar'] = 556;
- t['percent'] = 889;
- t['ampersand'] = 667;
- t['quoteright'] = 222;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 389;
- t['plus'] = 584;
- t['comma'] = 278;
- t['hyphen'] = 333;
- t['period'] = 278;
- t['slash'] = 278;
- t['zero'] = 556;
- t['one'] = 556;
- t['two'] = 556;
- t['three'] = 556;
- t['four'] = 556;
- t['five'] = 556;
- t['six'] = 556;
- t['seven'] = 556;
- t['eight'] = 556;
- t['nine'] = 556;
- t['colon'] = 278;
- t['semicolon'] = 278;
- t['less'] = 584;
- t['equal'] = 584;
- t['greater'] = 584;
- t['question'] = 556;
- t['at'] = 1015;
- t['A'] = 667;
- t['B'] = 667;
- t['C'] = 722;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 611;
- t['G'] = 778;
- t['H'] = 722;
- t['I'] = 278;
- t['J'] = 500;
- t['K'] = 667;
- t['L'] = 556;
- t['M'] = 833;
- t['N'] = 722;
- t['O'] = 778;
- t['P'] = 667;
- t['Q'] = 778;
- t['R'] = 722;
- t['S'] = 667;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 667;
- t['W'] = 944;
- t['X'] = 667;
- t['Y'] = 667;
- t['Z'] = 611;
- t['bracketleft'] = 278;
- t['backslash'] = 278;
- t['bracketright'] = 278;
- t['asciicircum'] = 469;
- t['underscore'] = 556;
- t['quoteleft'] = 222;
- t['a'] = 556;
- t['b'] = 556;
- t['c'] = 500;
- t['d'] = 556;
- t['e'] = 556;
- t['f'] = 278;
- t['g'] = 556;
- t['h'] = 556;
- t['i'] = 222;
- t['j'] = 222;
- t['k'] = 500;
- t['l'] = 222;
- t['m'] = 833;
- t['n'] = 556;
- t['o'] = 556;
- t['p'] = 556;
- t['q'] = 556;
- t['r'] = 333;
- t['s'] = 500;
- t['t'] = 278;
- t['u'] = 556;
- t['v'] = 500;
- t['w'] = 722;
- t['x'] = 500;
- t['y'] = 500;
- t['z'] = 500;
- t['braceleft'] = 334;
- t['bar'] = 260;
- t['braceright'] = 334;
- t['asciitilde'] = 584;
- t['exclamdown'] = 333;
- t['cent'] = 556;
- t['sterling'] = 556;
- t['fraction'] = 167;
- t['yen'] = 556;
- t['florin'] = 556;
- t['section'] = 556;
- t['currency'] = 556;
- t['quotesingle'] = 191;
- t['quotedblleft'] = 333;
- t['guillemotleft'] = 556;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 500;
- t['fl'] = 500;
- t['endash'] = 556;
- t['dagger'] = 556;
- t['daggerdbl'] = 556;
- t['periodcentered'] = 278;
- t['paragraph'] = 537;
- t['bullet'] = 350;
- t['quotesinglbase'] = 222;
- t['quotedblbase'] = 333;
- t['quotedblright'] = 333;
- t['guillemotright'] = 556;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 611;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 1000;
- t['ordfeminine'] = 370;
- t['Lslash'] = 556;
- t['Oslash'] = 778;
- t['OE'] = 1000;
- t['ordmasculine'] = 365;
- t['ae'] = 889;
- t['dotlessi'] = 278;
- t['lslash'] = 222;
- t['oslash'] = 611;
- t['oe'] = 944;
- t['germandbls'] = 611;
- t['Idieresis'] = 278;
- t['eacute'] = 556;
- t['abreve'] = 556;
- t['uhungarumlaut'] = 556;
- t['ecaron'] = 556;
- t['Ydieresis'] = 667;
- t['divide'] = 584;
- t['Yacute'] = 667;
- t['Acircumflex'] = 667;
- t['aacute'] = 556;
- t['Ucircumflex'] = 722;
- t['yacute'] = 500;
- t['scommaaccent'] = 500;
- t['ecircumflex'] = 556;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 556;
- t['Uacute'] = 722;
- t['uogonek'] = 556;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 737;
- t['Emacron'] = 667;
- t['ccaron'] = 500;
- t['aring'] = 556;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 222;
- t['agrave'] = 556;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 722;
- t['atilde'] = 556;
- t['Edotaccent'] = 667;
- t['scaron'] = 500;
- t['scedilla'] = 500;
- t['iacute'] = 278;
- t['lozenge'] = 471;
- t['Rcaron'] = 722;
- t['Gcommaaccent'] = 778;
- t['ucircumflex'] = 556;
- t['acircumflex'] = 556;
- t['Amacron'] = 667;
- t['rcaron'] = 333;
- t['ccedilla'] = 500;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 667;
- t['Omacron'] = 778;
- t['Racute'] = 722;
- t['Sacute'] = 667;
- t['dcaron'] = 643;
- t['Umacron'] = 722;
- t['uring'] = 556;
- t['threesuperior'] = 333;
- t['Ograve'] = 778;
- t['Agrave'] = 667;
- t['Abreve'] = 667;
- t['multiply'] = 584;
- t['uacute'] = 556;
- t['Tcaron'] = 611;
- t['partialdiff'] = 476;
- t['ydieresis'] = 500;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 556;
- t['edieresis'] = 556;
- t['cacute'] = 500;
- t['nacute'] = 556;
- t['umacron'] = 556;
- t['Ncaron'] = 722;
- t['Iacute'] = 278;
- t['plusminus'] = 584;
- t['brokenbar'] = 260;
- t['registered'] = 737;
- t['Gbreve'] = 778;
- t['Idotaccent'] = 278;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 333;
- t['omacron'] = 556;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 722;
- t['lcommaaccent'] = 222;
- t['tcaron'] = 317;
- t['eogonek'] = 556;
- t['Uogonek'] = 722;
- t['Aacute'] = 667;
- t['Adieresis'] = 667;
- t['egrave'] = 556;
- t['zacute'] = 500;
- t['iogonek'] = 222;
- t['Oacute'] = 778;
- t['oacute'] = 556;
- t['amacron'] = 556;
- t['sacute'] = 500;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 778;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 556;
- t['twosuperior'] = 333;
- t['Odieresis'] = 778;
- t['mu'] = 556;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 556;
- t['Eogonek'] = 667;
- t['dcroat'] = 556;
- t['threequarters'] = 834;
- t['Scedilla'] = 667;
- t['lcaron'] = 299;
- t['Kcommaaccent'] = 667;
- t['Lacute'] = 556;
- t['trademark'] = 1000;
- t['edotaccent'] = 556;
- t['Igrave'] = 278;
- t['Imacron'] = 278;
- t['Lcaron'] = 556;
- t['onehalf'] = 834;
- t['lessequal'] = 549;
- t['ocircumflex'] = 556;
- t['ntilde'] = 556;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 556;
- t['gbreve'] = 556;
- t['onequarter'] = 834;
- t['Scaron'] = 667;
- t['Scommaaccent'] = 667;
- t['Ohungarumlaut'] = 778;
- t['degree'] = 400;
- t['ograve'] = 556;
- t['Ccaron'] = 722;
- t['ugrave'] = 556;
- t['radical'] = 453;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 333;
- t['Ntilde'] = 722;
- t['otilde'] = 556;
- t['Rcommaaccent'] = 722;
- t['Lcommaaccent'] = 556;
- t['Atilde'] = 667;
- t['Aogonek'] = 667;
- t['Aring'] = 667;
- t['Otilde'] = 778;
- t['zdotaccent'] = 500;
- t['Ecaron'] = 667;
- t['Iogonek'] = 278;
- t['kcommaaccent'] = 500;
- t['minus'] = 584;
- t['Icircumflex'] = 278;
- t['ncaron'] = 556;
- t['tcommaaccent'] = 278;
- t['logicalnot'] = 584;
- t['odieresis'] = 556;
- t['udieresis'] = 556;
- t['notequal'] = 549;
- t['gcommaaccent'] = 556;
- t['eth'] = 556;
- t['zcaron'] = 500;
- t['ncommaaccent'] = 556;
- t['onesuperior'] = 333;
- t['imacron'] = 278;
- t['Euro'] = 556;
- });
- t['Helvetica-Bold'] = getLookupTableFactory(function (t) {
- t['space'] = 278;
- t['exclam'] = 333;
- t['quotedbl'] = 474;
- t['numbersign'] = 556;
- t['dollar'] = 556;
- t['percent'] = 889;
- t['ampersand'] = 722;
- t['quoteright'] = 278;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 389;
- t['plus'] = 584;
- t['comma'] = 278;
- t['hyphen'] = 333;
- t['period'] = 278;
- t['slash'] = 278;
- t['zero'] = 556;
- t['one'] = 556;
- t['two'] = 556;
- t['three'] = 556;
- t['four'] = 556;
- t['five'] = 556;
- t['six'] = 556;
- t['seven'] = 556;
- t['eight'] = 556;
- t['nine'] = 556;
- t['colon'] = 333;
- t['semicolon'] = 333;
- t['less'] = 584;
- t['equal'] = 584;
- t['greater'] = 584;
- t['question'] = 611;
- t['at'] = 975;
- t['A'] = 722;
- t['B'] = 722;
- t['C'] = 722;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 611;
- t['G'] = 778;
- t['H'] = 722;
- t['I'] = 278;
- t['J'] = 556;
- t['K'] = 722;
- t['L'] = 611;
- t['M'] = 833;
- t['N'] = 722;
- t['O'] = 778;
- t['P'] = 667;
- t['Q'] = 778;
- t['R'] = 722;
- t['S'] = 667;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 667;
- t['W'] = 944;
- t['X'] = 667;
- t['Y'] = 667;
- t['Z'] = 611;
- t['bracketleft'] = 333;
- t['backslash'] = 278;
- t['bracketright'] = 333;
- t['asciicircum'] = 584;
- t['underscore'] = 556;
- t['quoteleft'] = 278;
- t['a'] = 556;
- t['b'] = 611;
- t['c'] = 556;
- t['d'] = 611;
- t['e'] = 556;
- t['f'] = 333;
- t['g'] = 611;
- t['h'] = 611;
- t['i'] = 278;
- t['j'] = 278;
- t['k'] = 556;
- t['l'] = 278;
- t['m'] = 889;
- t['n'] = 611;
- t['o'] = 611;
- t['p'] = 611;
- t['q'] = 611;
- t['r'] = 389;
- t['s'] = 556;
- t['t'] = 333;
- t['u'] = 611;
- t['v'] = 556;
- t['w'] = 778;
- t['x'] = 556;
- t['y'] = 556;
- t['z'] = 500;
- t['braceleft'] = 389;
- t['bar'] = 280;
- t['braceright'] = 389;
- t['asciitilde'] = 584;
- t['exclamdown'] = 333;
- t['cent'] = 556;
- t['sterling'] = 556;
- t['fraction'] = 167;
- t['yen'] = 556;
- t['florin'] = 556;
- t['section'] = 556;
- t['currency'] = 556;
- t['quotesingle'] = 238;
- t['quotedblleft'] = 500;
- t['guillemotleft'] = 556;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 611;
- t['fl'] = 611;
- t['endash'] = 556;
- t['dagger'] = 556;
- t['daggerdbl'] = 556;
- t['periodcentered'] = 278;
- t['paragraph'] = 556;
- t['bullet'] = 350;
- t['quotesinglbase'] = 278;
- t['quotedblbase'] = 500;
- t['quotedblright'] = 500;
- t['guillemotright'] = 556;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 611;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 1000;
- t['ordfeminine'] = 370;
- t['Lslash'] = 611;
- t['Oslash'] = 778;
- t['OE'] = 1000;
- t['ordmasculine'] = 365;
- t['ae'] = 889;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 611;
- t['oe'] = 944;
- t['germandbls'] = 611;
- t['Idieresis'] = 278;
- t['eacute'] = 556;
- t['abreve'] = 556;
- t['uhungarumlaut'] = 611;
- t['ecaron'] = 556;
- t['Ydieresis'] = 667;
- t['divide'] = 584;
- t['Yacute'] = 667;
- t['Acircumflex'] = 722;
- t['aacute'] = 556;
- t['Ucircumflex'] = 722;
- t['yacute'] = 556;
- t['scommaaccent'] = 556;
- t['ecircumflex'] = 556;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 556;
- t['Uacute'] = 722;
- t['uogonek'] = 611;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 737;
- t['Emacron'] = 667;
- t['ccaron'] = 556;
- t['aring'] = 556;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 278;
- t['agrave'] = 556;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 722;
- t['atilde'] = 556;
- t['Edotaccent'] = 667;
- t['scaron'] = 556;
- t['scedilla'] = 556;
- t['iacute'] = 278;
- t['lozenge'] = 494;
- t['Rcaron'] = 722;
- t['Gcommaaccent'] = 778;
- t['ucircumflex'] = 611;
- t['acircumflex'] = 556;
- t['Amacron'] = 722;
- t['rcaron'] = 389;
- t['ccedilla'] = 556;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 667;
- t['Omacron'] = 778;
- t['Racute'] = 722;
- t['Sacute'] = 667;
- t['dcaron'] = 743;
- t['Umacron'] = 722;
- t['uring'] = 611;
- t['threesuperior'] = 333;
- t['Ograve'] = 778;
- t['Agrave'] = 722;
- t['Abreve'] = 722;
- t['multiply'] = 584;
- t['uacute'] = 611;
- t['Tcaron'] = 611;
- t['partialdiff'] = 494;
- t['ydieresis'] = 556;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 556;
- t['edieresis'] = 556;
- t['cacute'] = 556;
- t['nacute'] = 611;
- t['umacron'] = 611;
- t['Ncaron'] = 722;
- t['Iacute'] = 278;
- t['plusminus'] = 584;
- t['brokenbar'] = 280;
- t['registered'] = 737;
- t['Gbreve'] = 778;
- t['Idotaccent'] = 278;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 389;
- t['omacron'] = 611;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 722;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 389;
- t['eogonek'] = 556;
- t['Uogonek'] = 722;
- t['Aacute'] = 722;
- t['Adieresis'] = 722;
- t['egrave'] = 556;
- t['zacute'] = 500;
- t['iogonek'] = 278;
- t['Oacute'] = 778;
- t['oacute'] = 611;
- t['amacron'] = 556;
- t['sacute'] = 556;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 778;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 611;
- t['twosuperior'] = 333;
- t['Odieresis'] = 778;
- t['mu'] = 611;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 611;
- t['Eogonek'] = 667;
- t['dcroat'] = 611;
- t['threequarters'] = 834;
- t['Scedilla'] = 667;
- t['lcaron'] = 400;
- t['Kcommaaccent'] = 722;
- t['Lacute'] = 611;
- t['trademark'] = 1000;
- t['edotaccent'] = 556;
- t['Igrave'] = 278;
- t['Imacron'] = 278;
- t['Lcaron'] = 611;
- t['onehalf'] = 834;
- t['lessequal'] = 549;
- t['ocircumflex'] = 611;
- t['ntilde'] = 611;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 556;
- t['gbreve'] = 611;
- t['onequarter'] = 834;
- t['Scaron'] = 667;
- t['Scommaaccent'] = 667;
- t['Ohungarumlaut'] = 778;
- t['degree'] = 400;
- t['ograve'] = 611;
- t['Ccaron'] = 722;
- t['ugrave'] = 611;
- t['radical'] = 549;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 389;
- t['Ntilde'] = 722;
- t['otilde'] = 611;
- t['Rcommaaccent'] = 722;
- t['Lcommaaccent'] = 611;
- t['Atilde'] = 722;
- t['Aogonek'] = 722;
- t['Aring'] = 722;
- t['Otilde'] = 778;
- t['zdotaccent'] = 500;
- t['Ecaron'] = 667;
- t['Iogonek'] = 278;
- t['kcommaaccent'] = 556;
- t['minus'] = 584;
- t['Icircumflex'] = 278;
- t['ncaron'] = 611;
- t['tcommaaccent'] = 333;
- t['logicalnot'] = 584;
- t['odieresis'] = 611;
- t['udieresis'] = 611;
- t['notequal'] = 549;
- t['gcommaaccent'] = 611;
- t['eth'] = 611;
- t['zcaron'] = 500;
- t['ncommaaccent'] = 611;
- t['onesuperior'] = 333;
- t['imacron'] = 278;
- t['Euro'] = 556;
- });
- t['Helvetica-BoldOblique'] = getLookupTableFactory(function (t) {
- t['space'] = 278;
- t['exclam'] = 333;
- t['quotedbl'] = 474;
- t['numbersign'] = 556;
- t['dollar'] = 556;
- t['percent'] = 889;
- t['ampersand'] = 722;
- t['quoteright'] = 278;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 389;
- t['plus'] = 584;
- t['comma'] = 278;
- t['hyphen'] = 333;
- t['period'] = 278;
- t['slash'] = 278;
- t['zero'] = 556;
- t['one'] = 556;
- t['two'] = 556;
- t['three'] = 556;
- t['four'] = 556;
- t['five'] = 556;
- t['six'] = 556;
- t['seven'] = 556;
- t['eight'] = 556;
- t['nine'] = 556;
- t['colon'] = 333;
- t['semicolon'] = 333;
- t['less'] = 584;
- t['equal'] = 584;
- t['greater'] = 584;
- t['question'] = 611;
- t['at'] = 975;
- t['A'] = 722;
- t['B'] = 722;
- t['C'] = 722;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 611;
- t['G'] = 778;
- t['H'] = 722;
- t['I'] = 278;
- t['J'] = 556;
- t['K'] = 722;
- t['L'] = 611;
- t['M'] = 833;
- t['N'] = 722;
- t['O'] = 778;
- t['P'] = 667;
- t['Q'] = 778;
- t['R'] = 722;
- t['S'] = 667;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 667;
- t['W'] = 944;
- t['X'] = 667;
- t['Y'] = 667;
- t['Z'] = 611;
- t['bracketleft'] = 333;
- t['backslash'] = 278;
- t['bracketright'] = 333;
- t['asciicircum'] = 584;
- t['underscore'] = 556;
- t['quoteleft'] = 278;
- t['a'] = 556;
- t['b'] = 611;
- t['c'] = 556;
- t['d'] = 611;
- t['e'] = 556;
- t['f'] = 333;
- t['g'] = 611;
- t['h'] = 611;
- t['i'] = 278;
- t['j'] = 278;
- t['k'] = 556;
- t['l'] = 278;
- t['m'] = 889;
- t['n'] = 611;
- t['o'] = 611;
- t['p'] = 611;
- t['q'] = 611;
- t['r'] = 389;
- t['s'] = 556;
- t['t'] = 333;
- t['u'] = 611;
- t['v'] = 556;
- t['w'] = 778;
- t['x'] = 556;
- t['y'] = 556;
- t['z'] = 500;
- t['braceleft'] = 389;
- t['bar'] = 280;
- t['braceright'] = 389;
- t['asciitilde'] = 584;
- t['exclamdown'] = 333;
- t['cent'] = 556;
- t['sterling'] = 556;
- t['fraction'] = 167;
- t['yen'] = 556;
- t['florin'] = 556;
- t['section'] = 556;
- t['currency'] = 556;
- t['quotesingle'] = 238;
- t['quotedblleft'] = 500;
- t['guillemotleft'] = 556;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 611;
- t['fl'] = 611;
- t['endash'] = 556;
- t['dagger'] = 556;
- t['daggerdbl'] = 556;
- t['periodcentered'] = 278;
- t['paragraph'] = 556;
- t['bullet'] = 350;
- t['quotesinglbase'] = 278;
- t['quotedblbase'] = 500;
- t['quotedblright'] = 500;
- t['guillemotright'] = 556;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 611;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 1000;
- t['ordfeminine'] = 370;
- t['Lslash'] = 611;
- t['Oslash'] = 778;
- t['OE'] = 1000;
- t['ordmasculine'] = 365;
- t['ae'] = 889;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 611;
- t['oe'] = 944;
- t['germandbls'] = 611;
- t['Idieresis'] = 278;
- t['eacute'] = 556;
- t['abreve'] = 556;
- t['uhungarumlaut'] = 611;
- t['ecaron'] = 556;
- t['Ydieresis'] = 667;
- t['divide'] = 584;
- t['Yacute'] = 667;
- t['Acircumflex'] = 722;
- t['aacute'] = 556;
- t['Ucircumflex'] = 722;
- t['yacute'] = 556;
- t['scommaaccent'] = 556;
- t['ecircumflex'] = 556;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 556;
- t['Uacute'] = 722;
- t['uogonek'] = 611;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 737;
- t['Emacron'] = 667;
- t['ccaron'] = 556;
- t['aring'] = 556;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 278;
- t['agrave'] = 556;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 722;
- t['atilde'] = 556;
- t['Edotaccent'] = 667;
- t['scaron'] = 556;
- t['scedilla'] = 556;
- t['iacute'] = 278;
- t['lozenge'] = 494;
- t['Rcaron'] = 722;
- t['Gcommaaccent'] = 778;
- t['ucircumflex'] = 611;
- t['acircumflex'] = 556;
- t['Amacron'] = 722;
- t['rcaron'] = 389;
- t['ccedilla'] = 556;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 667;
- t['Omacron'] = 778;
- t['Racute'] = 722;
- t['Sacute'] = 667;
- t['dcaron'] = 743;
- t['Umacron'] = 722;
- t['uring'] = 611;
- t['threesuperior'] = 333;
- t['Ograve'] = 778;
- t['Agrave'] = 722;
- t['Abreve'] = 722;
- t['multiply'] = 584;
- t['uacute'] = 611;
- t['Tcaron'] = 611;
- t['partialdiff'] = 494;
- t['ydieresis'] = 556;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 556;
- t['edieresis'] = 556;
- t['cacute'] = 556;
- t['nacute'] = 611;
- t['umacron'] = 611;
- t['Ncaron'] = 722;
- t['Iacute'] = 278;
- t['plusminus'] = 584;
- t['brokenbar'] = 280;
- t['registered'] = 737;
- t['Gbreve'] = 778;
- t['Idotaccent'] = 278;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 389;
- t['omacron'] = 611;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 722;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 389;
- t['eogonek'] = 556;
- t['Uogonek'] = 722;
- t['Aacute'] = 722;
- t['Adieresis'] = 722;
- t['egrave'] = 556;
- t['zacute'] = 500;
- t['iogonek'] = 278;
- t['Oacute'] = 778;
- t['oacute'] = 611;
- t['amacron'] = 556;
- t['sacute'] = 556;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 778;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 611;
- t['twosuperior'] = 333;
- t['Odieresis'] = 778;
- t['mu'] = 611;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 611;
- t['Eogonek'] = 667;
- t['dcroat'] = 611;
- t['threequarters'] = 834;
- t['Scedilla'] = 667;
- t['lcaron'] = 400;
- t['Kcommaaccent'] = 722;
- t['Lacute'] = 611;
- t['trademark'] = 1000;
- t['edotaccent'] = 556;
- t['Igrave'] = 278;
- t['Imacron'] = 278;
- t['Lcaron'] = 611;
- t['onehalf'] = 834;
- t['lessequal'] = 549;
- t['ocircumflex'] = 611;
- t['ntilde'] = 611;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 556;
- t['gbreve'] = 611;
- t['onequarter'] = 834;
- t['Scaron'] = 667;
- t['Scommaaccent'] = 667;
- t['Ohungarumlaut'] = 778;
- t['degree'] = 400;
- t['ograve'] = 611;
- t['Ccaron'] = 722;
- t['ugrave'] = 611;
- t['radical'] = 549;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 389;
- t['Ntilde'] = 722;
- t['otilde'] = 611;
- t['Rcommaaccent'] = 722;
- t['Lcommaaccent'] = 611;
- t['Atilde'] = 722;
- t['Aogonek'] = 722;
- t['Aring'] = 722;
- t['Otilde'] = 778;
- t['zdotaccent'] = 500;
- t['Ecaron'] = 667;
- t['Iogonek'] = 278;
- t['kcommaaccent'] = 556;
- t['minus'] = 584;
- t['Icircumflex'] = 278;
- t['ncaron'] = 611;
- t['tcommaaccent'] = 333;
- t['logicalnot'] = 584;
- t['odieresis'] = 611;
- t['udieresis'] = 611;
- t['notequal'] = 549;
- t['gcommaaccent'] = 611;
- t['eth'] = 611;
- t['zcaron'] = 500;
- t['ncommaaccent'] = 611;
- t['onesuperior'] = 333;
- t['imacron'] = 278;
- t['Euro'] = 556;
- });
- t['Helvetica-Oblique'] = getLookupTableFactory(function (t) {
- t['space'] = 278;
- t['exclam'] = 278;
- t['quotedbl'] = 355;
- t['numbersign'] = 556;
- t['dollar'] = 556;
- t['percent'] = 889;
- t['ampersand'] = 667;
- t['quoteright'] = 222;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 389;
- t['plus'] = 584;
- t['comma'] = 278;
- t['hyphen'] = 333;
- t['period'] = 278;
- t['slash'] = 278;
- t['zero'] = 556;
- t['one'] = 556;
- t['two'] = 556;
- t['three'] = 556;
- t['four'] = 556;
- t['five'] = 556;
- t['six'] = 556;
- t['seven'] = 556;
- t['eight'] = 556;
- t['nine'] = 556;
- t['colon'] = 278;
- t['semicolon'] = 278;
- t['less'] = 584;
- t['equal'] = 584;
- t['greater'] = 584;
- t['question'] = 556;
- t['at'] = 1015;
- t['A'] = 667;
- t['B'] = 667;
- t['C'] = 722;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 611;
- t['G'] = 778;
- t['H'] = 722;
- t['I'] = 278;
- t['J'] = 500;
- t['K'] = 667;
- t['L'] = 556;
- t['M'] = 833;
- t['N'] = 722;
- t['O'] = 778;
- t['P'] = 667;
- t['Q'] = 778;
- t['R'] = 722;
- t['S'] = 667;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 667;
- t['W'] = 944;
- t['X'] = 667;
- t['Y'] = 667;
- t['Z'] = 611;
- t['bracketleft'] = 278;
- t['backslash'] = 278;
- t['bracketright'] = 278;
- t['asciicircum'] = 469;
- t['underscore'] = 556;
- t['quoteleft'] = 222;
- t['a'] = 556;
- t['b'] = 556;
- t['c'] = 500;
- t['d'] = 556;
- t['e'] = 556;
- t['f'] = 278;
- t['g'] = 556;
- t['h'] = 556;
- t['i'] = 222;
- t['j'] = 222;
- t['k'] = 500;
- t['l'] = 222;
- t['m'] = 833;
- t['n'] = 556;
- t['o'] = 556;
- t['p'] = 556;
- t['q'] = 556;
- t['r'] = 333;
- t['s'] = 500;
- t['t'] = 278;
- t['u'] = 556;
- t['v'] = 500;
- t['w'] = 722;
- t['x'] = 500;
- t['y'] = 500;
- t['z'] = 500;
- t['braceleft'] = 334;
- t['bar'] = 260;
- t['braceright'] = 334;
- t['asciitilde'] = 584;
- t['exclamdown'] = 333;
- t['cent'] = 556;
- t['sterling'] = 556;
- t['fraction'] = 167;
- t['yen'] = 556;
- t['florin'] = 556;
- t['section'] = 556;
- t['currency'] = 556;
- t['quotesingle'] = 191;
- t['quotedblleft'] = 333;
- t['guillemotleft'] = 556;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 500;
- t['fl'] = 500;
- t['endash'] = 556;
- t['dagger'] = 556;
- t['daggerdbl'] = 556;
- t['periodcentered'] = 278;
- t['paragraph'] = 537;
- t['bullet'] = 350;
- t['quotesinglbase'] = 222;
- t['quotedblbase'] = 333;
- t['quotedblright'] = 333;
- t['guillemotright'] = 556;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 611;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 1000;
- t['ordfeminine'] = 370;
- t['Lslash'] = 556;
- t['Oslash'] = 778;
- t['OE'] = 1000;
- t['ordmasculine'] = 365;
- t['ae'] = 889;
- t['dotlessi'] = 278;
- t['lslash'] = 222;
- t['oslash'] = 611;
- t['oe'] = 944;
- t['germandbls'] = 611;
- t['Idieresis'] = 278;
- t['eacute'] = 556;
- t['abreve'] = 556;
- t['uhungarumlaut'] = 556;
- t['ecaron'] = 556;
- t['Ydieresis'] = 667;
- t['divide'] = 584;
- t['Yacute'] = 667;
- t['Acircumflex'] = 667;
- t['aacute'] = 556;
- t['Ucircumflex'] = 722;
- t['yacute'] = 500;
- t['scommaaccent'] = 500;
- t['ecircumflex'] = 556;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 556;
- t['Uacute'] = 722;
- t['uogonek'] = 556;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 737;
- t['Emacron'] = 667;
- t['ccaron'] = 500;
- t['aring'] = 556;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 222;
- t['agrave'] = 556;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 722;
- t['atilde'] = 556;
- t['Edotaccent'] = 667;
- t['scaron'] = 500;
- t['scedilla'] = 500;
- t['iacute'] = 278;
- t['lozenge'] = 471;
- t['Rcaron'] = 722;
- t['Gcommaaccent'] = 778;
- t['ucircumflex'] = 556;
- t['acircumflex'] = 556;
- t['Amacron'] = 667;
- t['rcaron'] = 333;
- t['ccedilla'] = 500;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 667;
- t['Omacron'] = 778;
- t['Racute'] = 722;
- t['Sacute'] = 667;
- t['dcaron'] = 643;
- t['Umacron'] = 722;
- t['uring'] = 556;
- t['threesuperior'] = 333;
- t['Ograve'] = 778;
- t['Agrave'] = 667;
- t['Abreve'] = 667;
- t['multiply'] = 584;
- t['uacute'] = 556;
- t['Tcaron'] = 611;
- t['partialdiff'] = 476;
- t['ydieresis'] = 500;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 556;
- t['edieresis'] = 556;
- t['cacute'] = 500;
- t['nacute'] = 556;
- t['umacron'] = 556;
- t['Ncaron'] = 722;
- t['Iacute'] = 278;
- t['plusminus'] = 584;
- t['brokenbar'] = 260;
- t['registered'] = 737;
- t['Gbreve'] = 778;
- t['Idotaccent'] = 278;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 333;
- t['omacron'] = 556;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 722;
- t['lcommaaccent'] = 222;
- t['tcaron'] = 317;
- t['eogonek'] = 556;
- t['Uogonek'] = 722;
- t['Aacute'] = 667;
- t['Adieresis'] = 667;
- t['egrave'] = 556;
- t['zacute'] = 500;
- t['iogonek'] = 222;
- t['Oacute'] = 778;
- t['oacute'] = 556;
- t['amacron'] = 556;
- t['sacute'] = 500;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 778;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 556;
- t['twosuperior'] = 333;
- t['Odieresis'] = 778;
- t['mu'] = 556;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 556;
- t['Eogonek'] = 667;
- t['dcroat'] = 556;
- t['threequarters'] = 834;
- t['Scedilla'] = 667;
- t['lcaron'] = 299;
- t['Kcommaaccent'] = 667;
- t['Lacute'] = 556;
- t['trademark'] = 1000;
- t['edotaccent'] = 556;
- t['Igrave'] = 278;
- t['Imacron'] = 278;
- t['Lcaron'] = 556;
- t['onehalf'] = 834;
- t['lessequal'] = 549;
- t['ocircumflex'] = 556;
- t['ntilde'] = 556;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 556;
- t['gbreve'] = 556;
- t['onequarter'] = 834;
- t['Scaron'] = 667;
- t['Scommaaccent'] = 667;
- t['Ohungarumlaut'] = 778;
- t['degree'] = 400;
- t['ograve'] = 556;
- t['Ccaron'] = 722;
- t['ugrave'] = 556;
- t['radical'] = 453;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 333;
- t['Ntilde'] = 722;
- t['otilde'] = 556;
- t['Rcommaaccent'] = 722;
- t['Lcommaaccent'] = 556;
- t['Atilde'] = 667;
- t['Aogonek'] = 667;
- t['Aring'] = 667;
- t['Otilde'] = 778;
- t['zdotaccent'] = 500;
- t['Ecaron'] = 667;
- t['Iogonek'] = 278;
- t['kcommaaccent'] = 500;
- t['minus'] = 584;
- t['Icircumflex'] = 278;
- t['ncaron'] = 556;
- t['tcommaaccent'] = 278;
- t['logicalnot'] = 584;
- t['odieresis'] = 556;
- t['udieresis'] = 556;
- t['notequal'] = 549;
- t['gcommaaccent'] = 556;
- t['eth'] = 556;
- t['zcaron'] = 500;
- t['ncommaaccent'] = 556;
- t['onesuperior'] = 333;
- t['imacron'] = 278;
- t['Euro'] = 556;
- });
- t['Symbol'] = getLookupTableFactory(function (t) {
- t['space'] = 250;
- t['exclam'] = 333;
- t['universal'] = 713;
- t['numbersign'] = 500;
- t['existential'] = 549;
- t['percent'] = 833;
- t['ampersand'] = 778;
- t['suchthat'] = 439;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asteriskmath'] = 500;
- t['plus'] = 549;
- t['comma'] = 250;
- t['minus'] = 549;
- t['period'] = 250;
- t['slash'] = 278;
- t['zero'] = 500;
- t['one'] = 500;
- t['two'] = 500;
- t['three'] = 500;
- t['four'] = 500;
- t['five'] = 500;
- t['six'] = 500;
- t['seven'] = 500;
- t['eight'] = 500;
- t['nine'] = 500;
- t['colon'] = 278;
- t['semicolon'] = 278;
- t['less'] = 549;
- t['equal'] = 549;
- t['greater'] = 549;
- t['question'] = 444;
- t['congruent'] = 549;
- t['Alpha'] = 722;
- t['Beta'] = 667;
- t['Chi'] = 722;
- t['Delta'] = 612;
- t['Epsilon'] = 611;
- t['Phi'] = 763;
- t['Gamma'] = 603;
- t['Eta'] = 722;
- t['Iota'] = 333;
- t['theta1'] = 631;
- t['Kappa'] = 722;
- t['Lambda'] = 686;
- t['Mu'] = 889;
- t['Nu'] = 722;
- t['Omicron'] = 722;
- t['Pi'] = 768;
- t['Theta'] = 741;
- t['Rho'] = 556;
- t['Sigma'] = 592;
- t['Tau'] = 611;
- t['Upsilon'] = 690;
- t['sigma1'] = 439;
- t['Omega'] = 768;
- t['Xi'] = 645;
- t['Psi'] = 795;
- t['Zeta'] = 611;
- t['bracketleft'] = 333;
- t['therefore'] = 863;
- t['bracketright'] = 333;
- t['perpendicular'] = 658;
- t['underscore'] = 500;
- t['radicalex'] = 500;
- t['alpha'] = 631;
- t['beta'] = 549;
- t['chi'] = 549;
- t['delta'] = 494;
- t['epsilon'] = 439;
- t['phi'] = 521;
- t['gamma'] = 411;
- t['eta'] = 603;
- t['iota'] = 329;
- t['phi1'] = 603;
- t['kappa'] = 549;
- t['lambda'] = 549;
- t['mu'] = 576;
- t['nu'] = 521;
- t['omicron'] = 549;
- t['pi'] = 549;
- t['theta'] = 521;
- t['rho'] = 549;
- t['sigma'] = 603;
- t['tau'] = 439;
- t['upsilon'] = 576;
- t['omega1'] = 713;
- t['omega'] = 686;
- t['xi'] = 493;
- t['psi'] = 686;
- t['zeta'] = 494;
- t['braceleft'] = 480;
- t['bar'] = 200;
- t['braceright'] = 480;
- t['similar'] = 549;
- t['Euro'] = 750;
- t['Upsilon1'] = 620;
- t['minute'] = 247;
- t['lessequal'] = 549;
- t['fraction'] = 167;
- t['infinity'] = 713;
- t['florin'] = 500;
- t['club'] = 753;
- t['diamond'] = 753;
- t['heart'] = 753;
- t['spade'] = 753;
- t['arrowboth'] = 1042;
- t['arrowleft'] = 987;
- t['arrowup'] = 603;
- t['arrowright'] = 987;
- t['arrowdown'] = 603;
- t['degree'] = 400;
- t['plusminus'] = 549;
- t['second'] = 411;
- t['greaterequal'] = 549;
- t['multiply'] = 549;
- t['proportional'] = 713;
- t['partialdiff'] = 494;
- t['bullet'] = 460;
- t['divide'] = 549;
- t['notequal'] = 549;
- t['equivalence'] = 549;
- t['approxequal'] = 549;
- t['ellipsis'] = 1000;
- t['arrowvertex'] = 603;
- t['arrowhorizex'] = 1000;
- t['carriagereturn'] = 658;
- t['aleph'] = 823;
- t['Ifraktur'] = 686;
- t['Rfraktur'] = 795;
- t['weierstrass'] = 987;
- t['circlemultiply'] = 768;
- t['circleplus'] = 768;
- t['emptyset'] = 823;
- t['intersection'] = 768;
- t['union'] = 768;
- t['propersuperset'] = 713;
- t['reflexsuperset'] = 713;
- t['notsubset'] = 713;
- t['propersubset'] = 713;
- t['reflexsubset'] = 713;
- t['element'] = 713;
- t['notelement'] = 713;
- t['angle'] = 768;
- t['gradient'] = 713;
- t['registerserif'] = 790;
- t['copyrightserif'] = 790;
- t['trademarkserif'] = 890;
- t['product'] = 823;
- t['radical'] = 549;
- t['dotmath'] = 250;
- t['logicalnot'] = 713;
- t['logicaland'] = 603;
- t['logicalor'] = 603;
- t['arrowdblboth'] = 1042;
- t['arrowdblleft'] = 987;
- t['arrowdblup'] = 603;
- t['arrowdblright'] = 987;
- t['arrowdbldown'] = 603;
- t['lozenge'] = 494;
- t['angleleft'] = 329;
- t['registersans'] = 790;
- t['copyrightsans'] = 790;
- t['trademarksans'] = 786;
- t['summation'] = 713;
- t['parenlefttp'] = 384;
- t['parenleftex'] = 384;
- t['parenleftbt'] = 384;
- t['bracketlefttp'] = 384;
- t['bracketleftex'] = 384;
- t['bracketleftbt'] = 384;
- t['bracelefttp'] = 494;
- t['braceleftmid'] = 494;
- t['braceleftbt'] = 494;
- t['braceex'] = 494;
- t['angleright'] = 329;
- t['integral'] = 274;
- t['integraltp'] = 686;
- t['integralex'] = 686;
- t['integralbt'] = 686;
- t['parenrighttp'] = 384;
- t['parenrightex'] = 384;
- t['parenrightbt'] = 384;
- t['bracketrighttp'] = 384;
- t['bracketrightex'] = 384;
- t['bracketrightbt'] = 384;
- t['bracerighttp'] = 494;
- t['bracerightmid'] = 494;
- t['bracerightbt'] = 494;
- t['apple'] = 790;
- });
- t['Times-Roman'] = getLookupTableFactory(function (t) {
- t['space'] = 250;
- t['exclam'] = 333;
- t['quotedbl'] = 408;
- t['numbersign'] = 500;
- t['dollar'] = 500;
- t['percent'] = 833;
- t['ampersand'] = 778;
- t['quoteright'] = 333;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 500;
- t['plus'] = 564;
- t['comma'] = 250;
- t['hyphen'] = 333;
- t['period'] = 250;
- t['slash'] = 278;
- t['zero'] = 500;
- t['one'] = 500;
- t['two'] = 500;
- t['three'] = 500;
- t['four'] = 500;
- t['five'] = 500;
- t['six'] = 500;
- t['seven'] = 500;
- t['eight'] = 500;
- t['nine'] = 500;
- t['colon'] = 278;
- t['semicolon'] = 278;
- t['less'] = 564;
- t['equal'] = 564;
- t['greater'] = 564;
- t['question'] = 444;
- t['at'] = 921;
- t['A'] = 722;
- t['B'] = 667;
- t['C'] = 667;
- t['D'] = 722;
- t['E'] = 611;
- t['F'] = 556;
- t['G'] = 722;
- t['H'] = 722;
- t['I'] = 333;
- t['J'] = 389;
- t['K'] = 722;
- t['L'] = 611;
- t['M'] = 889;
- t['N'] = 722;
- t['O'] = 722;
- t['P'] = 556;
- t['Q'] = 722;
- t['R'] = 667;
- t['S'] = 556;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 722;
- t['W'] = 944;
- t['X'] = 722;
- t['Y'] = 722;
- t['Z'] = 611;
- t['bracketleft'] = 333;
- t['backslash'] = 278;
- t['bracketright'] = 333;
- t['asciicircum'] = 469;
- t['underscore'] = 500;
- t['quoteleft'] = 333;
- t['a'] = 444;
- t['b'] = 500;
- t['c'] = 444;
- t['d'] = 500;
- t['e'] = 444;
- t['f'] = 333;
- t['g'] = 500;
- t['h'] = 500;
- t['i'] = 278;
- t['j'] = 278;
- t['k'] = 500;
- t['l'] = 278;
- t['m'] = 778;
- t['n'] = 500;
- t['o'] = 500;
- t['p'] = 500;
- t['q'] = 500;
- t['r'] = 333;
- t['s'] = 389;
- t['t'] = 278;
- t['u'] = 500;
- t['v'] = 500;
- t['w'] = 722;
- t['x'] = 500;
- t['y'] = 500;
- t['z'] = 444;
- t['braceleft'] = 480;
- t['bar'] = 200;
- t['braceright'] = 480;
- t['asciitilde'] = 541;
- t['exclamdown'] = 333;
- t['cent'] = 500;
- t['sterling'] = 500;
- t['fraction'] = 167;
- t['yen'] = 500;
- t['florin'] = 500;
- t['section'] = 500;
- t['currency'] = 500;
- t['quotesingle'] = 180;
- t['quotedblleft'] = 444;
- t['guillemotleft'] = 500;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 556;
- t['fl'] = 556;
- t['endash'] = 500;
- t['dagger'] = 500;
- t['daggerdbl'] = 500;
- t['periodcentered'] = 250;
- t['paragraph'] = 453;
- t['bullet'] = 350;
- t['quotesinglbase'] = 333;
- t['quotedblbase'] = 444;
- t['quotedblright'] = 444;
- t['guillemotright'] = 500;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 444;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 889;
- t['ordfeminine'] = 276;
- t['Lslash'] = 611;
- t['Oslash'] = 722;
- t['OE'] = 889;
- t['ordmasculine'] = 310;
- t['ae'] = 667;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 500;
- t['oe'] = 722;
- t['germandbls'] = 500;
- t['Idieresis'] = 333;
- t['eacute'] = 444;
- t['abreve'] = 444;
- t['uhungarumlaut'] = 500;
- t['ecaron'] = 444;
- t['Ydieresis'] = 722;
- t['divide'] = 564;
- t['Yacute'] = 722;
- t['Acircumflex'] = 722;
- t['aacute'] = 444;
- t['Ucircumflex'] = 722;
- t['yacute'] = 500;
- t['scommaaccent'] = 389;
- t['ecircumflex'] = 444;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 444;
- t['Uacute'] = 722;
- t['uogonek'] = 500;
- t['Edieresis'] = 611;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 760;
- t['Emacron'] = 611;
- t['ccaron'] = 444;
- t['aring'] = 444;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 278;
- t['agrave'] = 444;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 667;
- t['atilde'] = 444;
- t['Edotaccent'] = 611;
- t['scaron'] = 389;
- t['scedilla'] = 389;
- t['iacute'] = 278;
- t['lozenge'] = 471;
- t['Rcaron'] = 667;
- t['Gcommaaccent'] = 722;
- t['ucircumflex'] = 500;
- t['acircumflex'] = 444;
- t['Amacron'] = 722;
- t['rcaron'] = 333;
- t['ccedilla'] = 444;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 556;
- t['Omacron'] = 722;
- t['Racute'] = 667;
- t['Sacute'] = 556;
- t['dcaron'] = 588;
- t['Umacron'] = 722;
- t['uring'] = 500;
- t['threesuperior'] = 300;
- t['Ograve'] = 722;
- t['Agrave'] = 722;
- t['Abreve'] = 722;
- t['multiply'] = 564;
- t['uacute'] = 500;
- t['Tcaron'] = 611;
- t['partialdiff'] = 476;
- t['ydieresis'] = 500;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 611;
- t['adieresis'] = 444;
- t['edieresis'] = 444;
- t['cacute'] = 444;
- t['nacute'] = 500;
- t['umacron'] = 500;
- t['Ncaron'] = 722;
- t['Iacute'] = 333;
- t['plusminus'] = 564;
- t['brokenbar'] = 200;
- t['registered'] = 760;
- t['Gbreve'] = 722;
- t['Idotaccent'] = 333;
- t['summation'] = 600;
- t['Egrave'] = 611;
- t['racute'] = 333;
- t['omacron'] = 500;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 667;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 326;
- t['eogonek'] = 444;
- t['Uogonek'] = 722;
- t['Aacute'] = 722;
- t['Adieresis'] = 722;
- t['egrave'] = 444;
- t['zacute'] = 444;
- t['iogonek'] = 278;
- t['Oacute'] = 722;
- t['oacute'] = 500;
- t['amacron'] = 444;
- t['sacute'] = 389;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 722;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 500;
- t['twosuperior'] = 300;
- t['Odieresis'] = 722;
- t['mu'] = 500;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 500;
- t['Eogonek'] = 611;
- t['dcroat'] = 500;
- t['threequarters'] = 750;
- t['Scedilla'] = 556;
- t['lcaron'] = 344;
- t['Kcommaaccent'] = 722;
- t['Lacute'] = 611;
- t['trademark'] = 980;
- t['edotaccent'] = 444;
- t['Igrave'] = 333;
- t['Imacron'] = 333;
- t['Lcaron'] = 611;
- t['onehalf'] = 750;
- t['lessequal'] = 549;
- t['ocircumflex'] = 500;
- t['ntilde'] = 500;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 611;
- t['emacron'] = 444;
- t['gbreve'] = 500;
- t['onequarter'] = 750;
- t['Scaron'] = 556;
- t['Scommaaccent'] = 556;
- t['Ohungarumlaut'] = 722;
- t['degree'] = 400;
- t['ograve'] = 500;
- t['Ccaron'] = 667;
- t['ugrave'] = 500;
- t['radical'] = 453;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 333;
- t['Ntilde'] = 722;
- t['otilde'] = 500;
- t['Rcommaaccent'] = 667;
- t['Lcommaaccent'] = 611;
- t['Atilde'] = 722;
- t['Aogonek'] = 722;
- t['Aring'] = 722;
- t['Otilde'] = 722;
- t['zdotaccent'] = 444;
- t['Ecaron'] = 611;
- t['Iogonek'] = 333;
- t['kcommaaccent'] = 500;
- t['minus'] = 564;
- t['Icircumflex'] = 333;
- t['ncaron'] = 500;
- t['tcommaaccent'] = 278;
- t['logicalnot'] = 564;
- t['odieresis'] = 500;
- t['udieresis'] = 500;
- t['notequal'] = 549;
- t['gcommaaccent'] = 500;
- t['eth'] = 500;
- t['zcaron'] = 444;
- t['ncommaaccent'] = 500;
- t['onesuperior'] = 300;
- t['imacron'] = 278;
- t['Euro'] = 500;
- });
- t['Times-Bold'] = getLookupTableFactory(function (t) {
- t['space'] = 250;
- t['exclam'] = 333;
- t['quotedbl'] = 555;
- t['numbersign'] = 500;
- t['dollar'] = 500;
- t['percent'] = 1000;
- t['ampersand'] = 833;
- t['quoteright'] = 333;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 500;
- t['plus'] = 570;
- t['comma'] = 250;
- t['hyphen'] = 333;
- t['period'] = 250;
- t['slash'] = 278;
- t['zero'] = 500;
- t['one'] = 500;
- t['two'] = 500;
- t['three'] = 500;
- t['four'] = 500;
- t['five'] = 500;
- t['six'] = 500;
- t['seven'] = 500;
- t['eight'] = 500;
- t['nine'] = 500;
- t['colon'] = 333;
- t['semicolon'] = 333;
- t['less'] = 570;
- t['equal'] = 570;
- t['greater'] = 570;
- t['question'] = 500;
- t['at'] = 930;
- t['A'] = 722;
- t['B'] = 667;
- t['C'] = 722;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 611;
- t['G'] = 778;
- t['H'] = 778;
- t['I'] = 389;
- t['J'] = 500;
- t['K'] = 778;
- t['L'] = 667;
- t['M'] = 944;
- t['N'] = 722;
- t['O'] = 778;
- t['P'] = 611;
- t['Q'] = 778;
- t['R'] = 722;
- t['S'] = 556;
- t['T'] = 667;
- t['U'] = 722;
- t['V'] = 722;
- t['W'] = 1000;
- t['X'] = 722;
- t['Y'] = 722;
- t['Z'] = 667;
- t['bracketleft'] = 333;
- t['backslash'] = 278;
- t['bracketright'] = 333;
- t['asciicircum'] = 581;
- t['underscore'] = 500;
- t['quoteleft'] = 333;
- t['a'] = 500;
- t['b'] = 556;
- t['c'] = 444;
- t['d'] = 556;
- t['e'] = 444;
- t['f'] = 333;
- t['g'] = 500;
- t['h'] = 556;
- t['i'] = 278;
- t['j'] = 333;
- t['k'] = 556;
- t['l'] = 278;
- t['m'] = 833;
- t['n'] = 556;
- t['o'] = 500;
- t['p'] = 556;
- t['q'] = 556;
- t['r'] = 444;
- t['s'] = 389;
- t['t'] = 333;
- t['u'] = 556;
- t['v'] = 500;
- t['w'] = 722;
- t['x'] = 500;
- t['y'] = 500;
- t['z'] = 444;
- t['braceleft'] = 394;
- t['bar'] = 220;
- t['braceright'] = 394;
- t['asciitilde'] = 520;
- t['exclamdown'] = 333;
- t['cent'] = 500;
- t['sterling'] = 500;
- t['fraction'] = 167;
- t['yen'] = 500;
- t['florin'] = 500;
- t['section'] = 500;
- t['currency'] = 500;
- t['quotesingle'] = 278;
- t['quotedblleft'] = 500;
- t['guillemotleft'] = 500;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 556;
- t['fl'] = 556;
- t['endash'] = 500;
- t['dagger'] = 500;
- t['daggerdbl'] = 500;
- t['periodcentered'] = 250;
- t['paragraph'] = 540;
- t['bullet'] = 350;
- t['quotesinglbase'] = 333;
- t['quotedblbase'] = 500;
- t['quotedblright'] = 500;
- t['guillemotright'] = 500;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 500;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 1000;
- t['ordfeminine'] = 300;
- t['Lslash'] = 667;
- t['Oslash'] = 778;
- t['OE'] = 1000;
- t['ordmasculine'] = 330;
- t['ae'] = 722;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 500;
- t['oe'] = 722;
- t['germandbls'] = 556;
- t['Idieresis'] = 389;
- t['eacute'] = 444;
- t['abreve'] = 500;
- t['uhungarumlaut'] = 556;
- t['ecaron'] = 444;
- t['Ydieresis'] = 722;
- t['divide'] = 570;
- t['Yacute'] = 722;
- t['Acircumflex'] = 722;
- t['aacute'] = 500;
- t['Ucircumflex'] = 722;
- t['yacute'] = 500;
- t['scommaaccent'] = 389;
- t['ecircumflex'] = 444;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 500;
- t['Uacute'] = 722;
- t['uogonek'] = 556;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 747;
- t['Emacron'] = 667;
- t['ccaron'] = 444;
- t['aring'] = 500;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 278;
- t['agrave'] = 500;
- t['Tcommaaccent'] = 667;
- t['Cacute'] = 722;
- t['atilde'] = 500;
- t['Edotaccent'] = 667;
- t['scaron'] = 389;
- t['scedilla'] = 389;
- t['iacute'] = 278;
- t['lozenge'] = 494;
- t['Rcaron'] = 722;
- t['Gcommaaccent'] = 778;
- t['ucircumflex'] = 556;
- t['acircumflex'] = 500;
- t['Amacron'] = 722;
- t['rcaron'] = 444;
- t['ccedilla'] = 444;
- t['Zdotaccent'] = 667;
- t['Thorn'] = 611;
- t['Omacron'] = 778;
- t['Racute'] = 722;
- t['Sacute'] = 556;
- t['dcaron'] = 672;
- t['Umacron'] = 722;
- t['uring'] = 556;
- t['threesuperior'] = 300;
- t['Ograve'] = 778;
- t['Agrave'] = 722;
- t['Abreve'] = 722;
- t['multiply'] = 570;
- t['uacute'] = 556;
- t['Tcaron'] = 667;
- t['partialdiff'] = 494;
- t['ydieresis'] = 500;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 500;
- t['edieresis'] = 444;
- t['cacute'] = 444;
- t['nacute'] = 556;
- t['umacron'] = 556;
- t['Ncaron'] = 722;
- t['Iacute'] = 389;
- t['plusminus'] = 570;
- t['brokenbar'] = 220;
- t['registered'] = 747;
- t['Gbreve'] = 778;
- t['Idotaccent'] = 389;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 444;
- t['omacron'] = 500;
- t['Zacute'] = 667;
- t['Zcaron'] = 667;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 722;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 416;
- t['eogonek'] = 444;
- t['Uogonek'] = 722;
- t['Aacute'] = 722;
- t['Adieresis'] = 722;
- t['egrave'] = 444;
- t['zacute'] = 444;
- t['iogonek'] = 278;
- t['Oacute'] = 778;
- t['oacute'] = 500;
- t['amacron'] = 500;
- t['sacute'] = 389;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 778;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 556;
- t['twosuperior'] = 300;
- t['Odieresis'] = 778;
- t['mu'] = 556;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 500;
- t['Eogonek'] = 667;
- t['dcroat'] = 556;
- t['threequarters'] = 750;
- t['Scedilla'] = 556;
- t['lcaron'] = 394;
- t['Kcommaaccent'] = 778;
- t['Lacute'] = 667;
- t['trademark'] = 1000;
- t['edotaccent'] = 444;
- t['Igrave'] = 389;
- t['Imacron'] = 389;
- t['Lcaron'] = 667;
- t['onehalf'] = 750;
- t['lessequal'] = 549;
- t['ocircumflex'] = 500;
- t['ntilde'] = 556;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 444;
- t['gbreve'] = 500;
- t['onequarter'] = 750;
- t['Scaron'] = 556;
- t['Scommaaccent'] = 556;
- t['Ohungarumlaut'] = 778;
- t['degree'] = 400;
- t['ograve'] = 500;
- t['Ccaron'] = 722;
- t['ugrave'] = 556;
- t['radical'] = 549;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 444;
- t['Ntilde'] = 722;
- t['otilde'] = 500;
- t['Rcommaaccent'] = 722;
- t['Lcommaaccent'] = 667;
- t['Atilde'] = 722;
- t['Aogonek'] = 722;
- t['Aring'] = 722;
- t['Otilde'] = 778;
- t['zdotaccent'] = 444;
- t['Ecaron'] = 667;
- t['Iogonek'] = 389;
- t['kcommaaccent'] = 556;
- t['minus'] = 570;
- t['Icircumflex'] = 389;
- t['ncaron'] = 556;
- t['tcommaaccent'] = 333;
- t['logicalnot'] = 570;
- t['odieresis'] = 500;
- t['udieresis'] = 556;
- t['notequal'] = 549;
- t['gcommaaccent'] = 500;
- t['eth'] = 500;
- t['zcaron'] = 444;
- t['ncommaaccent'] = 556;
- t['onesuperior'] = 300;
- t['imacron'] = 278;
- t['Euro'] = 500;
- });
- t['Times-BoldItalic'] = getLookupTableFactory(function (t) {
- t['space'] = 250;
- t['exclam'] = 389;
- t['quotedbl'] = 555;
- t['numbersign'] = 500;
- t['dollar'] = 500;
- t['percent'] = 833;
- t['ampersand'] = 778;
- t['quoteright'] = 333;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 500;
- t['plus'] = 570;
- t['comma'] = 250;
- t['hyphen'] = 333;
- t['period'] = 250;
- t['slash'] = 278;
- t['zero'] = 500;
- t['one'] = 500;
- t['two'] = 500;
- t['three'] = 500;
- t['four'] = 500;
- t['five'] = 500;
- t['six'] = 500;
- t['seven'] = 500;
- t['eight'] = 500;
- t['nine'] = 500;
- t['colon'] = 333;
- t['semicolon'] = 333;
- t['less'] = 570;
- t['equal'] = 570;
- t['greater'] = 570;
- t['question'] = 500;
- t['at'] = 832;
- t['A'] = 667;
- t['B'] = 667;
- t['C'] = 667;
- t['D'] = 722;
- t['E'] = 667;
- t['F'] = 667;
- t['G'] = 722;
- t['H'] = 778;
- t['I'] = 389;
- t['J'] = 500;
- t['K'] = 667;
- t['L'] = 611;
- t['M'] = 889;
- t['N'] = 722;
- t['O'] = 722;
- t['P'] = 611;
- t['Q'] = 722;
- t['R'] = 667;
- t['S'] = 556;
- t['T'] = 611;
- t['U'] = 722;
- t['V'] = 667;
- t['W'] = 889;
- t['X'] = 667;
- t['Y'] = 611;
- t['Z'] = 611;
- t['bracketleft'] = 333;
- t['backslash'] = 278;
- t['bracketright'] = 333;
- t['asciicircum'] = 570;
- t['underscore'] = 500;
- t['quoteleft'] = 333;
- t['a'] = 500;
- t['b'] = 500;
- t['c'] = 444;
- t['d'] = 500;
- t['e'] = 444;
- t['f'] = 333;
- t['g'] = 500;
- t['h'] = 556;
- t['i'] = 278;
- t['j'] = 278;
- t['k'] = 500;
- t['l'] = 278;
- t['m'] = 778;
- t['n'] = 556;
- t['o'] = 500;
- t['p'] = 500;
- t['q'] = 500;
- t['r'] = 389;
- t['s'] = 389;
- t['t'] = 278;
- t['u'] = 556;
- t['v'] = 444;
- t['w'] = 667;
- t['x'] = 500;
- t['y'] = 444;
- t['z'] = 389;
- t['braceleft'] = 348;
- t['bar'] = 220;
- t['braceright'] = 348;
- t['asciitilde'] = 570;
- t['exclamdown'] = 389;
- t['cent'] = 500;
- t['sterling'] = 500;
- t['fraction'] = 167;
- t['yen'] = 500;
- t['florin'] = 500;
- t['section'] = 500;
- t['currency'] = 500;
- t['quotesingle'] = 278;
- t['quotedblleft'] = 500;
- t['guillemotleft'] = 500;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 556;
- t['fl'] = 556;
- t['endash'] = 500;
- t['dagger'] = 500;
- t['daggerdbl'] = 500;
- t['periodcentered'] = 250;
- t['paragraph'] = 500;
- t['bullet'] = 350;
- t['quotesinglbase'] = 333;
- t['quotedblbase'] = 500;
- t['quotedblright'] = 500;
- t['guillemotright'] = 500;
- t['ellipsis'] = 1000;
- t['perthousand'] = 1000;
- t['questiondown'] = 500;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 1000;
- t['AE'] = 944;
- t['ordfeminine'] = 266;
- t['Lslash'] = 611;
- t['Oslash'] = 722;
- t['OE'] = 944;
- t['ordmasculine'] = 300;
- t['ae'] = 722;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 500;
- t['oe'] = 722;
- t['germandbls'] = 500;
- t['Idieresis'] = 389;
- t['eacute'] = 444;
- t['abreve'] = 500;
- t['uhungarumlaut'] = 556;
- t['ecaron'] = 444;
- t['Ydieresis'] = 611;
- t['divide'] = 570;
- t['Yacute'] = 611;
- t['Acircumflex'] = 667;
- t['aacute'] = 500;
- t['Ucircumflex'] = 722;
- t['yacute'] = 444;
- t['scommaaccent'] = 389;
- t['ecircumflex'] = 444;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 500;
- t['Uacute'] = 722;
- t['uogonek'] = 556;
- t['Edieresis'] = 667;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 747;
- t['Emacron'] = 667;
- t['ccaron'] = 444;
- t['aring'] = 500;
- t['Ncommaaccent'] = 722;
- t['lacute'] = 278;
- t['agrave'] = 500;
- t['Tcommaaccent'] = 611;
- t['Cacute'] = 667;
- t['atilde'] = 500;
- t['Edotaccent'] = 667;
- t['scaron'] = 389;
- t['scedilla'] = 389;
- t['iacute'] = 278;
- t['lozenge'] = 494;
- t['Rcaron'] = 667;
- t['Gcommaaccent'] = 722;
- t['ucircumflex'] = 556;
- t['acircumflex'] = 500;
- t['Amacron'] = 667;
- t['rcaron'] = 389;
- t['ccedilla'] = 444;
- t['Zdotaccent'] = 611;
- t['Thorn'] = 611;
- t['Omacron'] = 722;
- t['Racute'] = 667;
- t['Sacute'] = 556;
- t['dcaron'] = 608;
- t['Umacron'] = 722;
- t['uring'] = 556;
- t['threesuperior'] = 300;
- t['Ograve'] = 722;
- t['Agrave'] = 667;
- t['Abreve'] = 667;
- t['multiply'] = 570;
- t['uacute'] = 556;
- t['Tcaron'] = 611;
- t['partialdiff'] = 494;
- t['ydieresis'] = 444;
- t['Nacute'] = 722;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 667;
- t['adieresis'] = 500;
- t['edieresis'] = 444;
- t['cacute'] = 444;
- t['nacute'] = 556;
- t['umacron'] = 556;
- t['Ncaron'] = 722;
- t['Iacute'] = 389;
- t['plusminus'] = 570;
- t['brokenbar'] = 220;
- t['registered'] = 747;
- t['Gbreve'] = 722;
- t['Idotaccent'] = 389;
- t['summation'] = 600;
- t['Egrave'] = 667;
- t['racute'] = 389;
- t['omacron'] = 500;
- t['Zacute'] = 611;
- t['Zcaron'] = 611;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 667;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 366;
- t['eogonek'] = 444;
- t['Uogonek'] = 722;
- t['Aacute'] = 667;
- t['Adieresis'] = 667;
- t['egrave'] = 444;
- t['zacute'] = 389;
- t['iogonek'] = 278;
- t['Oacute'] = 722;
- t['oacute'] = 500;
- t['amacron'] = 500;
- t['sacute'] = 389;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 722;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 500;
- t['twosuperior'] = 300;
- t['Odieresis'] = 722;
- t['mu'] = 576;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 500;
- t['Eogonek'] = 667;
- t['dcroat'] = 500;
- t['threequarters'] = 750;
- t['Scedilla'] = 556;
- t['lcaron'] = 382;
- t['Kcommaaccent'] = 667;
- t['Lacute'] = 611;
- t['trademark'] = 1000;
- t['edotaccent'] = 444;
- t['Igrave'] = 389;
- t['Imacron'] = 389;
- t['Lcaron'] = 611;
- t['onehalf'] = 750;
- t['lessequal'] = 549;
- t['ocircumflex'] = 500;
- t['ntilde'] = 556;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 667;
- t['emacron'] = 444;
- t['gbreve'] = 500;
- t['onequarter'] = 750;
- t['Scaron'] = 556;
- t['Scommaaccent'] = 556;
- t['Ohungarumlaut'] = 722;
- t['degree'] = 400;
- t['ograve'] = 500;
- t['Ccaron'] = 667;
- t['ugrave'] = 556;
- t['radical'] = 549;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 389;
- t['Ntilde'] = 722;
- t['otilde'] = 500;
- t['Rcommaaccent'] = 667;
- t['Lcommaaccent'] = 611;
- t['Atilde'] = 667;
- t['Aogonek'] = 667;
- t['Aring'] = 667;
- t['Otilde'] = 722;
- t['zdotaccent'] = 389;
- t['Ecaron'] = 667;
- t['Iogonek'] = 389;
- t['kcommaaccent'] = 500;
- t['minus'] = 606;
- t['Icircumflex'] = 389;
- t['ncaron'] = 556;
- t['tcommaaccent'] = 278;
- t['logicalnot'] = 606;
- t['odieresis'] = 500;
- t['udieresis'] = 556;
- t['notequal'] = 549;
- t['gcommaaccent'] = 500;
- t['eth'] = 500;
- t['zcaron'] = 389;
- t['ncommaaccent'] = 556;
- t['onesuperior'] = 300;
- t['imacron'] = 278;
- t['Euro'] = 500;
- });
- t['Times-Italic'] = getLookupTableFactory(function (t) {
- t['space'] = 250;
- t['exclam'] = 333;
- t['quotedbl'] = 420;
- t['numbersign'] = 500;
- t['dollar'] = 500;
- t['percent'] = 833;
- t['ampersand'] = 778;
- t['quoteright'] = 333;
- t['parenleft'] = 333;
- t['parenright'] = 333;
- t['asterisk'] = 500;
- t['plus'] = 675;
- t['comma'] = 250;
- t['hyphen'] = 333;
- t['period'] = 250;
- t['slash'] = 278;
- t['zero'] = 500;
- t['one'] = 500;
- t['two'] = 500;
- t['three'] = 500;
- t['four'] = 500;
- t['five'] = 500;
- t['six'] = 500;
- t['seven'] = 500;
- t['eight'] = 500;
- t['nine'] = 500;
- t['colon'] = 333;
- t['semicolon'] = 333;
- t['less'] = 675;
- t['equal'] = 675;
- t['greater'] = 675;
- t['question'] = 500;
- t['at'] = 920;
- t['A'] = 611;
- t['B'] = 611;
- t['C'] = 667;
- t['D'] = 722;
- t['E'] = 611;
- t['F'] = 611;
- t['G'] = 722;
- t['H'] = 722;
- t['I'] = 333;
- t['J'] = 444;
- t['K'] = 667;
- t['L'] = 556;
- t['M'] = 833;
- t['N'] = 667;
- t['O'] = 722;
- t['P'] = 611;
- t['Q'] = 722;
- t['R'] = 611;
- t['S'] = 500;
- t['T'] = 556;
- t['U'] = 722;
- t['V'] = 611;
- t['W'] = 833;
- t['X'] = 611;
- t['Y'] = 556;
- t['Z'] = 556;
- t['bracketleft'] = 389;
- t['backslash'] = 278;
- t['bracketright'] = 389;
- t['asciicircum'] = 422;
- t['underscore'] = 500;
- t['quoteleft'] = 333;
- t['a'] = 500;
- t['b'] = 500;
- t['c'] = 444;
- t['d'] = 500;
- t['e'] = 444;
- t['f'] = 278;
- t['g'] = 500;
- t['h'] = 500;
- t['i'] = 278;
- t['j'] = 278;
- t['k'] = 444;
- t['l'] = 278;
- t['m'] = 722;
- t['n'] = 500;
- t['o'] = 500;
- t['p'] = 500;
- t['q'] = 500;
- t['r'] = 389;
- t['s'] = 389;
- t['t'] = 278;
- t['u'] = 500;
- t['v'] = 444;
- t['w'] = 667;
- t['x'] = 444;
- t['y'] = 444;
- t['z'] = 389;
- t['braceleft'] = 400;
- t['bar'] = 275;
- t['braceright'] = 400;
- t['asciitilde'] = 541;
- t['exclamdown'] = 389;
- t['cent'] = 500;
- t['sterling'] = 500;
- t['fraction'] = 167;
- t['yen'] = 500;
- t['florin'] = 500;
- t['section'] = 500;
- t['currency'] = 500;
- t['quotesingle'] = 214;
- t['quotedblleft'] = 556;
- t['guillemotleft'] = 500;
- t['guilsinglleft'] = 333;
- t['guilsinglright'] = 333;
- t['fi'] = 500;
- t['fl'] = 500;
- t['endash'] = 500;
- t['dagger'] = 500;
- t['daggerdbl'] = 500;
- t['periodcentered'] = 250;
- t['paragraph'] = 523;
- t['bullet'] = 350;
- t['quotesinglbase'] = 333;
- t['quotedblbase'] = 556;
- t['quotedblright'] = 556;
- t['guillemotright'] = 500;
- t['ellipsis'] = 889;
- t['perthousand'] = 1000;
- t['questiondown'] = 500;
- t['grave'] = 333;
- t['acute'] = 333;
- t['circumflex'] = 333;
- t['tilde'] = 333;
- t['macron'] = 333;
- t['breve'] = 333;
- t['dotaccent'] = 333;
- t['dieresis'] = 333;
- t['ring'] = 333;
- t['cedilla'] = 333;
- t['hungarumlaut'] = 333;
- t['ogonek'] = 333;
- t['caron'] = 333;
- t['emdash'] = 889;
- t['AE'] = 889;
- t['ordfeminine'] = 276;
- t['Lslash'] = 556;
- t['Oslash'] = 722;
- t['OE'] = 944;
- t['ordmasculine'] = 310;
- t['ae'] = 667;
- t['dotlessi'] = 278;
- t['lslash'] = 278;
- t['oslash'] = 500;
- t['oe'] = 667;
- t['germandbls'] = 500;
- t['Idieresis'] = 333;
- t['eacute'] = 444;
- t['abreve'] = 500;
- t['uhungarumlaut'] = 500;
- t['ecaron'] = 444;
- t['Ydieresis'] = 556;
- t['divide'] = 675;
- t['Yacute'] = 556;
- t['Acircumflex'] = 611;
- t['aacute'] = 500;
- t['Ucircumflex'] = 722;
- t['yacute'] = 444;
- t['scommaaccent'] = 389;
- t['ecircumflex'] = 444;
- t['Uring'] = 722;
- t['Udieresis'] = 722;
- t['aogonek'] = 500;
- t['Uacute'] = 722;
- t['uogonek'] = 500;
- t['Edieresis'] = 611;
- t['Dcroat'] = 722;
- t['commaaccent'] = 250;
- t['copyright'] = 760;
- t['Emacron'] = 611;
- t['ccaron'] = 444;
- t['aring'] = 500;
- t['Ncommaaccent'] = 667;
- t['lacute'] = 278;
- t['agrave'] = 500;
- t['Tcommaaccent'] = 556;
- t['Cacute'] = 667;
- t['atilde'] = 500;
- t['Edotaccent'] = 611;
- t['scaron'] = 389;
- t['scedilla'] = 389;
- t['iacute'] = 278;
- t['lozenge'] = 471;
- t['Rcaron'] = 611;
- t['Gcommaaccent'] = 722;
- t['ucircumflex'] = 500;
- t['acircumflex'] = 500;
- t['Amacron'] = 611;
- t['rcaron'] = 389;
- t['ccedilla'] = 444;
- t['Zdotaccent'] = 556;
- t['Thorn'] = 611;
- t['Omacron'] = 722;
- t['Racute'] = 611;
- t['Sacute'] = 500;
- t['dcaron'] = 544;
- t['Umacron'] = 722;
- t['uring'] = 500;
- t['threesuperior'] = 300;
- t['Ograve'] = 722;
- t['Agrave'] = 611;
- t['Abreve'] = 611;
- t['multiply'] = 675;
- t['uacute'] = 500;
- t['Tcaron'] = 556;
- t['partialdiff'] = 476;
- t['ydieresis'] = 444;
- t['Nacute'] = 667;
- t['icircumflex'] = 278;
- t['Ecircumflex'] = 611;
- t['adieresis'] = 500;
- t['edieresis'] = 444;
- t['cacute'] = 444;
- t['nacute'] = 500;
- t['umacron'] = 500;
- t['Ncaron'] = 667;
- t['Iacute'] = 333;
- t['plusminus'] = 675;
- t['brokenbar'] = 275;
- t['registered'] = 760;
- t['Gbreve'] = 722;
- t['Idotaccent'] = 333;
- t['summation'] = 600;
- t['Egrave'] = 611;
- t['racute'] = 389;
- t['omacron'] = 500;
- t['Zacute'] = 556;
- t['Zcaron'] = 556;
- t['greaterequal'] = 549;
- t['Eth'] = 722;
- t['Ccedilla'] = 667;
- t['lcommaaccent'] = 278;
- t['tcaron'] = 300;
- t['eogonek'] = 444;
- t['Uogonek'] = 722;
- t['Aacute'] = 611;
- t['Adieresis'] = 611;
- t['egrave'] = 444;
- t['zacute'] = 389;
- t['iogonek'] = 278;
- t['Oacute'] = 722;
- t['oacute'] = 500;
- t['amacron'] = 500;
- t['sacute'] = 389;
- t['idieresis'] = 278;
- t['Ocircumflex'] = 722;
- t['Ugrave'] = 722;
- t['Delta'] = 612;
- t['thorn'] = 500;
- t['twosuperior'] = 300;
- t['Odieresis'] = 722;
- t['mu'] = 500;
- t['igrave'] = 278;
- t['ohungarumlaut'] = 500;
- t['Eogonek'] = 611;
- t['dcroat'] = 500;
- t['threequarters'] = 750;
- t['Scedilla'] = 500;
- t['lcaron'] = 300;
- t['Kcommaaccent'] = 667;
- t['Lacute'] = 556;
- t['trademark'] = 980;
- t['edotaccent'] = 444;
- t['Igrave'] = 333;
- t['Imacron'] = 333;
- t['Lcaron'] = 611;
- t['onehalf'] = 750;
- t['lessequal'] = 549;
- t['ocircumflex'] = 500;
- t['ntilde'] = 500;
- t['Uhungarumlaut'] = 722;
- t['Eacute'] = 611;
- t['emacron'] = 444;
- t['gbreve'] = 500;
- t['onequarter'] = 750;
- t['Scaron'] = 500;
- t['Scommaaccent'] = 500;
- t['Ohungarumlaut'] = 722;
- t['degree'] = 400;
- t['ograve'] = 500;
- t['Ccaron'] = 667;
- t['ugrave'] = 500;
- t['radical'] = 453;
- t['Dcaron'] = 722;
- t['rcommaaccent'] = 389;
- t['Ntilde'] = 667;
- t['otilde'] = 500;
- t['Rcommaaccent'] = 611;
- t['Lcommaaccent'] = 556;
- t['Atilde'] = 611;
- t['Aogonek'] = 611;
- t['Aring'] = 611;
- t['Otilde'] = 722;
- t['zdotaccent'] = 389;
- t['Ecaron'] = 611;
- t['Iogonek'] = 333;
- t['kcommaaccent'] = 444;
- t['minus'] = 675;
- t['Icircumflex'] = 333;
- t['ncaron'] = 500;
- t['tcommaaccent'] = 278;
- t['logicalnot'] = 675;
- t['odieresis'] = 500;
- t['udieresis'] = 500;
- t['notequal'] = 549;
- t['gcommaaccent'] = 500;
- t['eth'] = 500;
- t['zcaron'] = 389;
- t['ncommaaccent'] = 500;
- t['onesuperior'] = 300;
- t['imacron'] = 278;
- t['Euro'] = 500;
- });
- t['ZapfDingbats'] = getLookupTableFactory(function (t) {
- t['space'] = 278;
- t['a1'] = 974;
- t['a2'] = 961;
- t['a202'] = 974;
- t['a3'] = 980;
- t['a4'] = 719;
- t['a5'] = 789;
- t['a119'] = 790;
- t['a118'] = 791;
- t['a117'] = 690;
- t['a11'] = 960;
- t['a12'] = 939;
- t['a13'] = 549;
- t['a14'] = 855;
- t['a15'] = 911;
- t['a16'] = 933;
- t['a105'] = 911;
- t['a17'] = 945;
- t['a18'] = 974;
- t['a19'] = 755;
- t['a20'] = 846;
- t['a21'] = 762;
- t['a22'] = 761;
- t['a23'] = 571;
- t['a24'] = 677;
- t['a25'] = 763;
- t['a26'] = 760;
- t['a27'] = 759;
- t['a28'] = 754;
- t['a6'] = 494;
- t['a7'] = 552;
- t['a8'] = 537;
- t['a9'] = 577;
- t['a10'] = 692;
- t['a29'] = 786;
- t['a30'] = 788;
- t['a31'] = 788;
- t['a32'] = 790;
- t['a33'] = 793;
- t['a34'] = 794;
- t['a35'] = 816;
- t['a36'] = 823;
- t['a37'] = 789;
- t['a38'] = 841;
- t['a39'] = 823;
- t['a40'] = 833;
- t['a41'] = 816;
- t['a42'] = 831;
- t['a43'] = 923;
- t['a44'] = 744;
- t['a45'] = 723;
- t['a46'] = 749;
- t['a47'] = 790;
- t['a48'] = 792;
- t['a49'] = 695;
- t['a50'] = 776;
- t['a51'] = 768;
- t['a52'] = 792;
- t['a53'] = 759;
- t['a54'] = 707;
- t['a55'] = 708;
- t['a56'] = 682;
- t['a57'] = 701;
- t['a58'] = 826;
- t['a59'] = 815;
- t['a60'] = 789;
- t['a61'] = 789;
- t['a62'] = 707;
- t['a63'] = 687;
- t['a64'] = 696;
- t['a65'] = 689;
- t['a66'] = 786;
- t['a67'] = 787;
- t['a68'] = 713;
- t['a69'] = 791;
- t['a70'] = 785;
- t['a71'] = 791;
- t['a72'] = 873;
- t['a73'] = 761;
- t['a74'] = 762;
- t['a203'] = 762;
- t['a75'] = 759;
- t['a204'] = 759;
- t['a76'] = 892;
- t['a77'] = 892;
- t['a78'] = 788;
- t['a79'] = 784;
- t['a81'] = 438;
- t['a82'] = 138;
- t['a83'] = 277;
- t['a84'] = 415;
- t['a97'] = 392;
- t['a98'] = 392;
- t['a99'] = 668;
- t['a100'] = 668;
- t['a89'] = 390;
- t['a90'] = 390;
- t['a93'] = 317;
- t['a94'] = 317;
- t['a91'] = 276;
- t['a92'] = 276;
- t['a205'] = 509;
- t['a85'] = 509;
- t['a206'] = 410;
- t['a86'] = 410;
- t['a87'] = 234;
- t['a88'] = 234;
- t['a95'] = 334;
- t['a96'] = 334;
- t['a101'] = 732;
- t['a102'] = 544;
- t['a103'] = 544;
- t['a104'] = 910;
- t['a106'] = 667;
- t['a107'] = 760;
- t['a108'] = 760;
- t['a112'] = 776;
- t['a111'] = 595;
- t['a110'] = 694;
- t['a109'] = 626;
- t['a120'] = 788;
- t['a121'] = 788;
- t['a122'] = 788;
- t['a123'] = 788;
- t['a124'] = 788;
- t['a125'] = 788;
- t['a126'] = 788;
- t['a127'] = 788;
- t['a128'] = 788;
- t['a129'] = 788;
- t['a130'] = 788;
- t['a131'] = 788;
- t['a132'] = 788;
- t['a133'] = 788;
- t['a134'] = 788;
- t['a135'] = 788;
- t['a136'] = 788;
- t['a137'] = 788;
- t['a138'] = 788;
- t['a139'] = 788;
- t['a140'] = 788;
- t['a141'] = 788;
- t['a142'] = 788;
- t['a143'] = 788;
- t['a144'] = 788;
- t['a145'] = 788;
- t['a146'] = 788;
- t['a147'] = 788;
- t['a148'] = 788;
- t['a149'] = 788;
- t['a150'] = 788;
- t['a151'] = 788;
- t['a152'] = 788;
- t['a153'] = 788;
- t['a154'] = 788;
- t['a155'] = 788;
- t['a156'] = 788;
- t['a157'] = 788;
- t['a158'] = 788;
- t['a159'] = 788;
- t['a160'] = 894;
- t['a161'] = 838;
- t['a163'] = 1016;
- t['a164'] = 458;
- t['a196'] = 748;
- t['a165'] = 924;
- t['a192'] = 748;
- t['a166'] = 918;
- t['a167'] = 927;
- t['a168'] = 928;
- t['a169'] = 928;
- t['a170'] = 834;
- t['a171'] = 873;
- t['a172'] = 828;
- t['a173'] = 924;
- t['a162'] = 924;
- t['a174'] = 917;
- t['a175'] = 930;
- t['a176'] = 931;
- t['a177'] = 463;
- t['a178'] = 883;
- t['a179'] = 836;
- t['a193'] = 836;
- t['a180'] = 867;
- t['a199'] = 867;
- t['a181'] = 696;
- t['a200'] = 696;
- t['a182'] = 874;
- t['a201'] = 874;
- t['a183'] = 760;
- t['a184'] = 946;
- t['a197'] = 771;
- t['a185'] = 865;
- t['a194'] = 771;
- t['a198'] = 888;
- t['a186'] = 967;
- t['a195'] = 888;
- t['a187'] = 831;
- t['a188'] = 873;
- t['a189'] = 927;
- t['a190'] = 970;
- t['a191'] = 918;
- });
-});
-exports.getMetrics = getMetrics;
-
-/***/ }),
-/* 31 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var Uint32ArrayView = sharedUtil.Uint32ArrayView;
-var MurmurHash3_64 = function MurmurHash3_64Closure(seed) {
- var MASK_HIGH = 0xffff0000;
- var MASK_LOW = 0xffff;
- function MurmurHash3_64(seed) {
- var SEED = 0xc3d2e1f0;
- this.h1 = seed ? seed & 0xffffffff : SEED;
- this.h2 = seed ? seed & 0xffffffff : SEED;
- }
- var alwaysUseUint32ArrayView = false;
- try {
- new Uint32Array(new Uint8Array(5).buffer, 0, 1);
- } catch (e) {
- alwaysUseUint32ArrayView = true;
- }
- MurmurHash3_64.prototype = {
- update: function MurmurHash3_64_update(input) {
- var useUint32ArrayView = alwaysUseUint32ArrayView;
- var i;
- if (typeof input === 'string') {
- var data = new Uint8Array(input.length * 2);
- var length = 0;
- for (i = 0; i < input.length; i++) {
- var code = input.charCodeAt(i);
- if (code <= 0xff) {
- data[length++] = code;
- } else {
- data[length++] = code >>> 8;
- data[length++] = code & 0xff;
- }
- }
- } else if (input instanceof Uint8Array) {
- data = input;
- length = data.length;
- } else if (typeof input === 'object' && 'length' in input) {
- data = input;
- length = data.length;
- useUint32ArrayView = true;
- } else {
- throw new Error('Wrong data format in MurmurHash3_64_update. ' + 'Input must be a string or array.');
- }
- var blockCounts = length >> 2;
- var tailLength = length - blockCounts * 4;
- var dataUint32 = useUint32ArrayView ? new Uint32ArrayView(data, blockCounts) : new Uint32Array(data.buffer, 0, blockCounts);
- var k1 = 0;
- var k2 = 0;
- var h1 = this.h1;
- var h2 = this.h2;
- var C1 = 0xcc9e2d51;
- var C2 = 0x1b873593;
- var C1_LOW = C1 & MASK_LOW;
- var C2_LOW = C2 & MASK_LOW;
- for (i = 0; i < blockCounts; i++) {
- if (i & 1) {
- k1 = dataUint32[i];
- k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
- k1 = k1 << 15 | k1 >>> 17;
- k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
- h1 ^= k1;
- h1 = h1 << 13 | h1 >>> 19;
- h1 = h1 * 5 + 0xe6546b64;
- } else {
- k2 = dataUint32[i];
- k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
- k2 = k2 << 15 | k2 >>> 17;
- k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
- h2 ^= k2;
- h2 = h2 << 13 | h2 >>> 19;
- h2 = h2 * 5 + 0xe6546b64;
- }
- }
- k1 = 0;
- switch (tailLength) {
- case 3:
- k1 ^= data[blockCounts * 4 + 2] << 16;
- case 2:
- k1 ^= data[blockCounts * 4 + 1] << 8;
- case 1:
- k1 ^= data[blockCounts * 4];
- k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
- k1 = k1 << 15 | k1 >>> 17;
- k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
- if (blockCounts & 1) {
- h1 ^= k1;
- } else {
- h2 ^= k1;
- }
- }
- this.h1 = h1;
- this.h2 = h2;
- return this;
- },
- hexdigest: function MurmurHash3_64_hexdigest() {
- var h1 = this.h1;
- var h2 = this.h2;
- h1 ^= h2 >>> 1;
- h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
- h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
- h1 ^= h2 >>> 1;
- h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
- h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
- h1 ^= h2 >>> 1;
- for (var i = 0, arr = [h1, h2], str = ''; i < arr.length; i++) {
- var hex = (arr[i] >>> 0).toString(16);
- while (hex.length < 8) {
- hex = '0' + hex;
- }
- str += hex;
- }
- return str;
- }
- };
- return MurmurHash3_64;
-}();
-exports.MurmurHash3_64 = MurmurHash3_64;
-
-/***/ }),
-/* 32 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var coreFunction = __w_pdfjs_require__(6);
-var coreColorSpace = __w_pdfjs_require__(3);
-var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
-var MissingDataException = sharedUtil.MissingDataException;
-var Util = sharedUtil.Util;
-var assert = sharedUtil.assert;
-var error = sharedUtil.error;
-var info = sharedUtil.info;
-var warn = sharedUtil.warn;
-var isStream = corePrimitives.isStream;
-var PDFFunction = coreFunction.PDFFunction;
-var ColorSpace = coreColorSpace.ColorSpace;
-var ShadingType = {
- FUNCTION_BASED: 1,
- AXIAL: 2,
- RADIAL: 3,
- FREE_FORM_MESH: 4,
- LATTICE_FORM_MESH: 5,
- COONS_PATCH_MESH: 6,
- TENSOR_PATCH_MESH: 7
-};
-var Pattern = function PatternClosure() {
- function Pattern() {
- error('should not call Pattern constructor');
- }
- Pattern.prototype = {
- getPattern: function Pattern_getPattern(ctx) {
- error('Should not call Pattern.getStyle: ' + ctx);
- }
- };
- Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref, res, handler) {
- var dict = isStream(shading) ? shading.dict : shading;
- var type = dict.get('ShadingType');
- try {
- switch (type) {
- case ShadingType.AXIAL:
- case ShadingType.RADIAL:
- return new Shadings.RadialAxial(dict, matrix, xref, res);
- case ShadingType.FREE_FORM_MESH:
- case ShadingType.LATTICE_FORM_MESH:
- case ShadingType.COONS_PATCH_MESH:
- case ShadingType.TENSOR_PATCH_MESH:
- return new Shadings.Mesh(shading, matrix, xref, res);
- default:
- throw new Error('Unsupported ShadingType: ' + type);
- }
- } catch (ex) {
- if (ex instanceof MissingDataException) {
- throw ex;
- }
- handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.shadingPattern });
- warn(ex);
- return new Shadings.Dummy();
- }
- };
- return Pattern;
-}();
-var Shadings = {};
-Shadings.SMALL_NUMBER = 1e-6;
-Shadings.RadialAxial = function RadialAxialClosure() {
- function RadialAxial(dict, matrix, xref, res) {
- this.matrix = matrix;
- this.coordsArr = dict.getArray('Coords');
- this.shadingType = dict.get('ShadingType');
- this.type = 'Pattern';
- var cs = dict.get('ColorSpace', 'CS');
- cs = ColorSpace.parse(cs, xref, res);
- this.cs = cs;
- var t0 = 0.0,
- t1 = 1.0;
- if (dict.has('Domain')) {
- var domainArr = dict.getArray('Domain');
- t0 = domainArr[0];
- t1 = domainArr[1];
- }
- var extendStart = false,
- extendEnd = false;
- if (dict.has('Extend')) {
- var extendArr = dict.getArray('Extend');
- extendStart = extendArr[0];
- extendEnd = extendArr[1];
- }
- if (this.shadingType === ShadingType.RADIAL && (!extendStart || !extendEnd)) {
- var x1 = this.coordsArr[0];
- var y1 = this.coordsArr[1];
- var r1 = this.coordsArr[2];
- var x2 = this.coordsArr[3];
- var y2 = this.coordsArr[4];
- var r2 = this.coordsArr[5];
- var distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
- if (r1 <= r2 + distance && r2 <= r1 + distance) {
- warn('Unsupported radial gradient.');
- }
- }
- this.extendStart = extendStart;
- this.extendEnd = extendEnd;
- var fnObj = dict.get('Function');
- var fn = PDFFunction.parseArray(xref, fnObj);
- var diff = t1 - t0;
- var step = diff / 10;
- var colorStops = this.colorStops = [];
- if (t0 >= t1 || step <= 0) {
- info('Bad shading domain.');
- return;
- }
- var color = new Float32Array(cs.numComps),
- ratio = new Float32Array(1);
- var rgbColor;
- for (var i = t0; i <= t1; i += step) {
- ratio[0] = i;
- fn(ratio, 0, color, 0);
- rgbColor = cs.getRgb(color, 0);
- var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
- colorStops.push([(i - t0) / diff, cssColor]);
- }
- var background = 'transparent';
- if (dict.has('Background')) {
- rgbColor = cs.getRgb(dict.get('Background'), 0);
- background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
- }
- if (!extendStart) {
- colorStops.unshift([0, background]);
- colorStops[1][0] += Shadings.SMALL_NUMBER;
- }
- if (!extendEnd) {
- colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER;
- colorStops.push([1, background]);
- }
- this.colorStops = colorStops;
- }
- RadialAxial.prototype = {
- getIR: function RadialAxial_getIR() {
- var coordsArr = this.coordsArr;
- var shadingType = this.shadingType;
- var type, p0, p1, r0, r1;
- if (shadingType === ShadingType.AXIAL) {
- p0 = [coordsArr[0], coordsArr[1]];
- p1 = [coordsArr[2], coordsArr[3]];
- r0 = null;
- r1 = null;
- type = 'axial';
- } else if (shadingType === ShadingType.RADIAL) {
- p0 = [coordsArr[0], coordsArr[1]];
- p1 = [coordsArr[3], coordsArr[4]];
- r0 = coordsArr[2];
- r1 = coordsArr[5];
- type = 'radial';
- } else {
- error('getPattern type unknown: ' + shadingType);
- }
- var matrix = this.matrix;
- if (matrix) {
- p0 = Util.applyTransform(p0, matrix);
- p1 = Util.applyTransform(p1, matrix);
- if (shadingType === ShadingType.RADIAL) {
- var scale = Util.singularValueDecompose2dScale(matrix);
- r0 *= scale[0];
- r1 *= scale[1];
- }
- }
- return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1];
- }
- };
- return RadialAxial;
-}();
-Shadings.Mesh = function MeshClosure() {
- function MeshStreamReader(stream, context) {
- this.stream = stream;
- this.context = context;
- this.buffer = 0;
- this.bufferLength = 0;
- var numComps = context.numComps;
- this.tmpCompsBuf = new Float32Array(numComps);
- var csNumComps = context.colorSpace.numComps;
- this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : this.tmpCompsBuf;
- }
- MeshStreamReader.prototype = {
- get hasData() {
- if (this.stream.end) {
- return this.stream.pos < this.stream.end;
- }
- if (this.bufferLength > 0) {
- return true;
- }
- var nextByte = this.stream.getByte();
- if (nextByte < 0) {
- return false;
- }
- this.buffer = nextByte;
- this.bufferLength = 8;
- return true;
- },
- readBits: function MeshStreamReader_readBits(n) {
- var buffer = this.buffer;
- var bufferLength = this.bufferLength;
- if (n === 32) {
- if (bufferLength === 0) {
- return (this.stream.getByte() << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte()) >>> 0;
- }
- buffer = buffer << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte();
- var nextByte = this.stream.getByte();
- this.buffer = nextByte & (1 << bufferLength) - 1;
- return (buffer << 8 - bufferLength | (nextByte & 0xFF) >> bufferLength) >>> 0;
- }
- if (n === 8 && bufferLength === 0) {
- return this.stream.getByte();
- }
- while (bufferLength < n) {
- buffer = buffer << 8 | this.stream.getByte();
- bufferLength += 8;
- }
- bufferLength -= n;
- this.bufferLength = bufferLength;
- this.buffer = buffer & (1 << bufferLength) - 1;
- return buffer >> bufferLength;
- },
- align: function MeshStreamReader_align() {
- this.buffer = 0;
- this.bufferLength = 0;
- },
- readFlag: function MeshStreamReader_readFlag() {
- return this.readBits(this.context.bitsPerFlag);
- },
- readCoordinate: function MeshStreamReader_readCoordinate() {
- var bitsPerCoordinate = this.context.bitsPerCoordinate;
- var xi = this.readBits(bitsPerCoordinate);
- var yi = this.readBits(bitsPerCoordinate);
- var decode = this.context.decode;
- var scale = bitsPerCoordinate < 32 ? 1 / ((1 << bitsPerCoordinate) - 1) : 2.3283064365386963e-10;
- return [xi * scale * (decode[1] - decode[0]) + decode[0], yi * scale * (decode[3] - decode[2]) + decode[2]];
- },
- readComponents: function MeshStreamReader_readComponents() {
- var numComps = this.context.numComps;
- var bitsPerComponent = this.context.bitsPerComponent;
- var scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10;
- var decode = this.context.decode;
- var components = this.tmpCompsBuf;
- for (var i = 0, j = 4; i < numComps; i++, j += 2) {
- var ci = this.readBits(bitsPerComponent);
- components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j];
- }
- var color = this.tmpCsCompsBuf;
- if (this.context.colorFn) {
- this.context.colorFn(components, 0, color, 0);
- }
- return this.context.colorSpace.getRgb(color, 0);
- }
- };
- function decodeType4Shading(mesh, reader) {
- var coords = mesh.coords;
- var colors = mesh.colors;
- var operators = [];
- var ps = [];
- var verticesLeft = 0;
- while (reader.hasData) {
- var f = reader.readFlag();
- var coord = reader.readCoordinate();
- var color = reader.readComponents();
- if (verticesLeft === 0) {
- assert(0 <= f && f <= 2, 'Unknown type4 flag');
- switch (f) {
- case 0:
- verticesLeft = 3;
- break;
- case 1:
- ps.push(ps[ps.length - 2], ps[ps.length - 1]);
- verticesLeft = 1;
- break;
- case 2:
- ps.push(ps[ps.length - 3], ps[ps.length - 1]);
- verticesLeft = 1;
- break;
- }
- operators.push(f);
- }
- ps.push(coords.length);
- coords.push(coord);
- colors.push(color);
- verticesLeft--;
- reader.align();
- }
- mesh.figures.push({
- type: 'triangles',
- coords: new Int32Array(ps),
- colors: new Int32Array(ps)
- });
- }
- function decodeType5Shading(mesh, reader, verticesPerRow) {
- var coords = mesh.coords;
- var colors = mesh.colors;
- var ps = [];
- while (reader.hasData) {
- var coord = reader.readCoordinate();
- var color = reader.readComponents();
- ps.push(coords.length);
- coords.push(coord);
- colors.push(color);
- }
- mesh.figures.push({
- type: 'lattice',
- coords: new Int32Array(ps),
- colors: new Int32Array(ps),
- verticesPerRow: verticesPerRow
- });
- }
- var MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
- var MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20;
- var TRIANGLE_DENSITY = 20;
- var getB = function getBClosure() {
- function buildB(count) {
- var lut = [];
- for (var i = 0; i <= count; i++) {
- var t = i / count,
- t_ = 1 - t;
- lut.push(new Float32Array([t_ * t_ * t_, 3 * t * t_ * t_, 3 * t * t * t_, t * t * t]));
- }
- return lut;
- }
- var cache = [];
- return function getB(count) {
- if (!cache[count]) {
- cache[count] = buildB(count);
- }
- return cache[count];
- };
- }();
- function buildFigureFromPatch(mesh, index) {
- var figure = mesh.figures[index];
- assert(figure.type === 'patch', 'Unexpected patch mesh figure');
- var coords = mesh.coords,
- colors = mesh.colors;
- var pi = figure.coords;
- var ci = figure.colors;
- var figureMinX = Math.min(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
- var figureMinY = Math.min(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
- var figureMaxX = Math.max(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
- var figureMaxY = Math.max(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
- var splitXBy = Math.ceil((figureMaxX - figureMinX) * TRIANGLE_DENSITY / (mesh.bounds[2] - mesh.bounds[0]));
- splitXBy = Math.max(MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy));
- var splitYBy = Math.ceil((figureMaxY - figureMinY) * TRIANGLE_DENSITY / (mesh.bounds[3] - mesh.bounds[1]));
- splitYBy = Math.max(MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy));
- var verticesPerRow = splitXBy + 1;
- var figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
- var figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
- var k = 0;
- var cl = new Uint8Array(3),
- cr = new Uint8Array(3);
- var c0 = colors[ci[0]],
- c1 = colors[ci[1]],
- c2 = colors[ci[2]],
- c3 = colors[ci[3]];
- var bRow = getB(splitYBy),
- bCol = getB(splitXBy);
- for (var row = 0; row <= splitYBy; row++) {
- cl[0] = (c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy | 0;
- cl[1] = (c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy | 0;
- cl[2] = (c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy | 0;
- cr[0] = (c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy | 0;
- cr[1] = (c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy | 0;
- cr[2] = (c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy | 0;
- for (var col = 0; col <= splitXBy; col++, k++) {
- if ((row === 0 || row === splitYBy) && (col === 0 || col === splitXBy)) {
- continue;
- }
- var x = 0,
- y = 0;
- var q = 0;
- for (var i = 0; i <= 3; i++) {
- for (var j = 0; j <= 3; j++, q++) {
- var m = bRow[row][i] * bCol[col][j];
- x += coords[pi[q]][0] * m;
- y += coords[pi[q]][1] * m;
- }
- }
- figureCoords[k] = coords.length;
- coords.push([x, y]);
- figureColors[k] = colors.length;
- var newColor = new Uint8Array(3);
- newColor[0] = (cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy | 0;
- newColor[1] = (cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy | 0;
- newColor[2] = (cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy | 0;
- colors.push(newColor);
- }
- }
- figureCoords[0] = pi[0];
- figureColors[0] = ci[0];
- figureCoords[splitXBy] = pi[3];
- figureColors[splitXBy] = ci[1];
- figureCoords[verticesPerRow * splitYBy] = pi[12];
- figureColors[verticesPerRow * splitYBy] = ci[2];
- figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
- figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
- mesh.figures[index] = {
- type: 'lattice',
- coords: figureCoords,
- colors: figureColors,
- verticesPerRow: verticesPerRow
- };
- }
- function decodeType6Shading(mesh, reader) {
- var coords = mesh.coords;
- var colors = mesh.colors;
- var ps = new Int32Array(16);
- var cs = new Int32Array(4);
- while (reader.hasData) {
- var f = reader.readFlag();
- assert(0 <= f && f <= 3, 'Unknown type6 flag');
- var i, ii;
- var pi = coords.length;
- for (i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) {
- coords.push(reader.readCoordinate());
- }
- var ci = colors.length;
- for (i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
- colors.push(reader.readComponents());
- }
- var tmp1, tmp2, tmp3, tmp4;
- switch (f) {
- case 0:
- ps[12] = pi + 3;
- ps[13] = pi + 4;
- ps[14] = pi + 5;
- ps[15] = pi + 6;
- ps[8] = pi + 2;
- ps[11] = pi + 7;
- ps[4] = pi + 1;
- ps[7] = pi + 8;
- ps[0] = pi;
- ps[1] = pi + 11;
- ps[2] = pi + 10;
- ps[3] = pi + 9;
- cs[2] = ci + 1;
- cs[3] = ci + 2;
- cs[0] = ci;
- cs[1] = ci + 3;
- break;
- case 1:
- tmp1 = ps[12];
- tmp2 = ps[13];
- tmp3 = ps[14];
- tmp4 = ps[15];
- ps[12] = tmp4;
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = tmp3;
- ps[11] = pi + 3;
- ps[4] = tmp2;
- ps[7] = pi + 4;
- ps[0] = tmp1;
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- tmp1 = cs[2];
- tmp2 = cs[3];
- cs[2] = tmp2;
- cs[3] = ci;
- cs[0] = tmp1;
- cs[1] = ci + 1;
- break;
- case 2:
- tmp1 = ps[15];
- tmp2 = ps[11];
- ps[12] = ps[3];
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = ps[7];
- ps[11] = pi + 3;
- ps[4] = tmp2;
- ps[7] = pi + 4;
- ps[0] = tmp1;
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- tmp1 = cs[3];
- cs[2] = cs[1];
- cs[3] = ci;
- cs[0] = tmp1;
- cs[1] = ci + 1;
- break;
- case 3:
- ps[12] = ps[0];
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = ps[1];
- ps[11] = pi + 3;
- ps[4] = ps[2];
- ps[7] = pi + 4;
- ps[0] = ps[3];
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- cs[2] = cs[0];
- cs[3] = ci;
- cs[0] = cs[1];
- cs[1] = ci + 1;
- break;
- }
- ps[5] = coords.length;
- coords.push([(-4 * coords[ps[0]][0] - coords[ps[15]][0] + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / 9, (-4 * coords[ps[0]][1] - coords[ps[15]][1] + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / 9]);
- ps[6] = coords.length;
- coords.push([(-4 * coords[ps[3]][0] - coords[ps[12]][0] + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / 9, (-4 * coords[ps[3]][1] - coords[ps[12]][1] + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / 9]);
- ps[9] = coords.length;
- coords.push([(-4 * coords[ps[12]][0] - coords[ps[3]][0] + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / 9, (-4 * coords[ps[12]][1] - coords[ps[3]][1] + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / 9]);
- ps[10] = coords.length;
- coords.push([(-4 * coords[ps[15]][0] - coords[ps[0]][0] + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / 9, (-4 * coords[ps[15]][1] - coords[ps[0]][1] + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / 9]);
- mesh.figures.push({
- type: 'patch',
- coords: new Int32Array(ps),
- colors: new Int32Array(cs)
- });
- }
- }
- function decodeType7Shading(mesh, reader) {
- var coords = mesh.coords;
- var colors = mesh.colors;
- var ps = new Int32Array(16);
- var cs = new Int32Array(4);
- while (reader.hasData) {
- var f = reader.readFlag();
- assert(0 <= f && f <= 3, 'Unknown type7 flag');
- var i, ii;
- var pi = coords.length;
- for (i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) {
- coords.push(reader.readCoordinate());
- }
- var ci = colors.length;
- for (i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
- colors.push(reader.readComponents());
- }
- var tmp1, tmp2, tmp3, tmp4;
- switch (f) {
- case 0:
- ps[12] = pi + 3;
- ps[13] = pi + 4;
- ps[14] = pi + 5;
- ps[15] = pi + 6;
- ps[8] = pi + 2;
- ps[9] = pi + 13;
- ps[10] = pi + 14;
- ps[11] = pi + 7;
- ps[4] = pi + 1;
- ps[5] = pi + 12;
- ps[6] = pi + 15;
- ps[7] = pi + 8;
- ps[0] = pi;
- ps[1] = pi + 11;
- ps[2] = pi + 10;
- ps[3] = pi + 9;
- cs[2] = ci + 1;
- cs[3] = ci + 2;
- cs[0] = ci;
- cs[1] = ci + 3;
- break;
- case 1:
- tmp1 = ps[12];
- tmp2 = ps[13];
- tmp3 = ps[14];
- tmp4 = ps[15];
- ps[12] = tmp4;
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = tmp3;
- ps[9] = pi + 9;
- ps[10] = pi + 10;
- ps[11] = pi + 3;
- ps[4] = tmp2;
- ps[5] = pi + 8;
- ps[6] = pi + 11;
- ps[7] = pi + 4;
- ps[0] = tmp1;
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- tmp1 = cs[2];
- tmp2 = cs[3];
- cs[2] = tmp2;
- cs[3] = ci;
- cs[0] = tmp1;
- cs[1] = ci + 1;
- break;
- case 2:
- tmp1 = ps[15];
- tmp2 = ps[11];
- ps[12] = ps[3];
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = ps[7];
- ps[9] = pi + 9;
- ps[10] = pi + 10;
- ps[11] = pi + 3;
- ps[4] = tmp2;
- ps[5] = pi + 8;
- ps[6] = pi + 11;
- ps[7] = pi + 4;
- ps[0] = tmp1;
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- tmp1 = cs[3];
- cs[2] = cs[1];
- cs[3] = ci;
- cs[0] = tmp1;
- cs[1] = ci + 1;
- break;
- case 3:
- ps[12] = ps[0];
- ps[13] = pi + 0;
- ps[14] = pi + 1;
- ps[15] = pi + 2;
- ps[8] = ps[1];
- ps[9] = pi + 9;
- ps[10] = pi + 10;
- ps[11] = pi + 3;
- ps[4] = ps[2];
- ps[5] = pi + 8;
- ps[6] = pi + 11;
- ps[7] = pi + 4;
- ps[0] = ps[3];
- ps[1] = pi + 7;
- ps[2] = pi + 6;
- ps[3] = pi + 5;
- cs[2] = cs[0];
- cs[3] = ci;
- cs[0] = cs[1];
- cs[1] = ci + 1;
- break;
- }
- mesh.figures.push({
- type: 'patch',
- coords: new Int32Array(ps),
- colors: new Int32Array(cs)
- });
- }
- }
- function updateBounds(mesh) {
- var minX = mesh.coords[0][0],
- minY = mesh.coords[0][1],
- maxX = minX,
- maxY = minY;
- for (var i = 1, ii = mesh.coords.length; i < ii; i++) {
- var x = mesh.coords[i][0],
- y = mesh.coords[i][1];
- minX = minX > x ? x : minX;
- minY = minY > y ? y : minY;
- maxX = maxX < x ? x : maxX;
- maxY = maxY < y ? y : maxY;
- }
- mesh.bounds = [minX, minY, maxX, maxY];
- }
- function packData(mesh) {
- var i, ii, j, jj;
- var coords = mesh.coords;
- var coordsPacked = new Float32Array(coords.length * 2);
- for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
- var xy = coords[i];
- coordsPacked[j++] = xy[0];
- coordsPacked[j++] = xy[1];
- }
- mesh.coords = coordsPacked;
- var colors = mesh.colors;
- var colorsPacked = new Uint8Array(colors.length * 3);
- for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
- var c = colors[i];
- colorsPacked[j++] = c[0];
- colorsPacked[j++] = c[1];
- colorsPacked[j++] = c[2];
- }
- mesh.colors = colorsPacked;
- var figures = mesh.figures;
- for (i = 0, ii = figures.length; i < ii; i++) {
- var figure = figures[i],
- ps = figure.coords,
- cs = figure.colors;
- for (j = 0, jj = ps.length; j < jj; j++) {
- ps[j] *= 2;
- cs[j] *= 3;
- }
- }
- }
- function Mesh(stream, matrix, xref, res) {
- assert(isStream(stream), 'Mesh data is not a stream');
- var dict = stream.dict;
- this.matrix = matrix;
- this.shadingType = dict.get('ShadingType');
- this.type = 'Pattern';
- this.bbox = dict.getArray('BBox');
- var cs = dict.get('ColorSpace', 'CS');
- cs = ColorSpace.parse(cs, xref, res);
- this.cs = cs;
- this.background = dict.has('Background') ? cs.getRgb(dict.get('Background'), 0) : null;
- var fnObj = dict.get('Function');
- var fn = fnObj ? PDFFunction.parseArray(xref, fnObj) : null;
- this.coords = [];
- this.colors = [];
- this.figures = [];
- var decodeContext = {
- bitsPerCoordinate: dict.get('BitsPerCoordinate'),
- bitsPerComponent: dict.get('BitsPerComponent'),
- bitsPerFlag: dict.get('BitsPerFlag'),
- decode: dict.getArray('Decode'),
- colorFn: fn,
- colorSpace: cs,
- numComps: fn ? 1 : cs.numComps
- };
- var reader = new MeshStreamReader(stream, decodeContext);
- var patchMesh = false;
- switch (this.shadingType) {
- case ShadingType.FREE_FORM_MESH:
- decodeType4Shading(this, reader);
- break;
- case ShadingType.LATTICE_FORM_MESH:
- var verticesPerRow = dict.get('VerticesPerRow') | 0;
- assert(verticesPerRow >= 2, 'Invalid VerticesPerRow');
- decodeType5Shading(this, reader, verticesPerRow);
- break;
- case ShadingType.COONS_PATCH_MESH:
- decodeType6Shading(this, reader);
- patchMesh = true;
- break;
- case ShadingType.TENSOR_PATCH_MESH:
- decodeType7Shading(this, reader);
- patchMesh = true;
- break;
- default:
- error('Unsupported mesh type.');
- break;
- }
- if (patchMesh) {
- updateBounds(this);
- for (var i = 0, ii = this.figures.length; i < ii; i++) {
- buildFigureFromPatch(this, i);
- }
- }
- updateBounds(this);
- packData(this);
- }
- Mesh.prototype = {
- getIR: function Mesh_getIR() {
- return ['Mesh', this.shadingType, this.coords, this.colors, this.figures, this.bounds, this.matrix, this.bbox, this.background];
- }
- };
- return Mesh;
-}();
-Shadings.Dummy = function DummyClosure() {
- function Dummy() {
- this.type = 'Pattern';
- }
- Dummy.prototype = {
- getIR: function Dummy_getIR() {
- return ['Dummy'];
- }
- };
- return Dummy;
-}();
-function getTilingPatternIR(operatorList, dict, args) {
- var matrix = dict.getArray('Matrix');
- var bbox = dict.getArray('BBox');
- var xstep = dict.get('XStep');
- var ystep = dict.get('YStep');
- var paintType = dict.get('PaintType');
- var tilingType = dict.get('TilingType');
- return ['TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, paintType, tilingType];
-}
-exports.Pattern = Pattern;
-exports.getTilingPatternIR = getTilingPatternIR;
-
-/***/ }),
-/* 33 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreStream = __w_pdfjs_require__(2);
-var coreChunkedStream = __w_pdfjs_require__(12);
-var coreDocument = __w_pdfjs_require__(24);
-var warn = sharedUtil.warn;
-var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
-var shadow = sharedUtil.shadow;
-var NotImplementedException = sharedUtil.NotImplementedException;
-var MissingDataException = sharedUtil.MissingDataException;
-var createPromiseCapability = sharedUtil.createPromiseCapability;
-var Util = sharedUtil.Util;
-var Stream = coreStream.Stream;
-var ChunkedStreamManager = coreChunkedStream.ChunkedStreamManager;
-var PDFDocument = coreDocument.PDFDocument;
-var BasePdfManager = function BasePdfManagerClosure() {
- function BasePdfManager() {
- throw new Error('Cannot initialize BaseManagerManager');
- }
- BasePdfManager.prototype = {
- get docId() {
- return this._docId;
- },
- get password() {
- return this._password;
- },
- get docBaseUrl() {
- var docBaseUrl = null;
- if (this._docBaseUrl) {
- var absoluteUrl = createValidAbsoluteUrl(this._docBaseUrl);
- if (absoluteUrl) {
- docBaseUrl = absoluteUrl.href;
- } else {
- warn('Invalid absolute docBaseUrl: "' + this._docBaseUrl + '".');
- }
- }
- return shadow(this, 'docBaseUrl', docBaseUrl);
- },
- onLoadedStream: function BasePdfManager_onLoadedStream() {
- throw new NotImplementedException();
- },
- ensureDoc: function BasePdfManager_ensureDoc(prop, args) {
- return this.ensure(this.pdfDocument, prop, args);
- },
- ensureXRef: function BasePdfManager_ensureXRef(prop, args) {
- return this.ensure(this.pdfDocument.xref, prop, args);
- },
- ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
- return this.ensure(this.pdfDocument.catalog, prop, args);
- },
- getPage: function BasePdfManager_getPage(pageIndex) {
- return this.pdfDocument.getPage(pageIndex);
- },
- cleanup: function BasePdfManager_cleanup() {
- return this.pdfDocument.cleanup();
- },
- ensure: function BasePdfManager_ensure(obj, prop, args) {
- return new NotImplementedException();
- },
- requestRange: function BasePdfManager_requestRange(begin, end) {
- return new NotImplementedException();
- },
- requestLoadedStream: function BasePdfManager_requestLoadedStream() {
- return new NotImplementedException();
- },
- sendProgressiveData: function BasePdfManager_sendProgressiveData(chunk) {
- return new NotImplementedException();
- },
- updatePassword: function BasePdfManager_updatePassword(password) {
- this._password = password;
- },
- terminate: function BasePdfManager_terminate() {
- return new NotImplementedException();
- }
- };
- return BasePdfManager;
-}();
-var LocalPdfManager = function LocalPdfManagerClosure() {
- function LocalPdfManager(docId, data, password, evaluatorOptions, docBaseUrl) {
- this._docId = docId;
- this._password = password;
- this._docBaseUrl = docBaseUrl;
- this.evaluatorOptions = evaluatorOptions;
- var stream = new Stream(data);
- this.pdfDocument = new PDFDocument(this, stream);
- this._loadedStreamCapability = createPromiseCapability();
- this._loadedStreamCapability.resolve(stream);
- }
- Util.inherit(LocalPdfManager, BasePdfManager, {
- ensure: function LocalPdfManager_ensure(obj, prop, args) {
- return new Promise(function (resolve, reject) {
- try {
- var value = obj[prop];
- var result;
- if (typeof value === 'function') {
- result = value.apply(obj, args);
- } else {
- result = value;
- }
- resolve(result);
- } catch (e) {
- reject(e);
- }
- });
- },
- requestRange: function LocalPdfManager_requestRange(begin, end) {
- return Promise.resolve();
- },
- requestLoadedStream: function LocalPdfManager_requestLoadedStream() {},
- onLoadedStream: function LocalPdfManager_onLoadedStream() {
- return this._loadedStreamCapability.promise;
- },
- terminate: function LocalPdfManager_terminate() {}
- });
- return LocalPdfManager;
-}();
-var NetworkPdfManager = function NetworkPdfManagerClosure() {
- function NetworkPdfManager(docId, pdfNetworkStream, args, evaluatorOptions, docBaseUrl) {
- this._docId = docId;
- this._password = args.password;
- this._docBaseUrl = docBaseUrl;
- this.msgHandler = args.msgHandler;
- this.evaluatorOptions = evaluatorOptions;
- var params = {
- msgHandler: args.msgHandler,
- url: args.url,
- length: args.length,
- disableAutoFetch: args.disableAutoFetch,
- rangeChunkSize: args.rangeChunkSize
- };
- this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params);
- this.pdfDocument = new PDFDocument(this, this.streamManager.getStream());
- }
- Util.inherit(NetworkPdfManager, BasePdfManager, {
- ensure: function NetworkPdfManager_ensure(obj, prop, args) {
- var pdfManager = this;
- return new Promise(function (resolve, reject) {
- function ensureHelper() {
- try {
- var result;
- var value = obj[prop];
- if (typeof value === 'function') {
- result = value.apply(obj, args);
- } else {
- result = value;
- }
- resolve(result);
- } catch (e) {
- if (!(e instanceof MissingDataException)) {
- reject(e);
- return;
- }
- pdfManager.streamManager.requestRange(e.begin, e.end).then(ensureHelper, reject);
- }
- }
- ensureHelper();
- });
- },
- requestRange: function NetworkPdfManager_requestRange(begin, end) {
- return this.streamManager.requestRange(begin, end);
- },
- requestLoadedStream: function NetworkPdfManager_requestLoadedStream() {
- this.streamManager.requestAllChunks();
- },
- sendProgressiveData: function NetworkPdfManager_sendProgressiveData(chunk) {
- this.streamManager.onReceiveData({ chunk: chunk });
- },
- onLoadedStream: function NetworkPdfManager_onLoadedStream() {
- return this.streamManager.onLoadedStream();
- },
- terminate: function NetworkPdfManager_terminate() {
- this.streamManager.abort();
- }
- });
- return NetworkPdfManager;
-}();
-exports.LocalPdfManager = LocalPdfManager;
-exports.NetworkPdfManager = NetworkPdfManager;
-
-/***/ }),
-/* 34 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var corePrimitives = __w_pdfjs_require__(1);
-var error = sharedUtil.error;
-var isSpace = sharedUtil.isSpace;
-var EOF = corePrimitives.EOF;
-var PostScriptParser = function PostScriptParserClosure() {
- function PostScriptParser(lexer) {
- this.lexer = lexer;
- this.operators = [];
- this.token = null;
- this.prev = null;
- }
- PostScriptParser.prototype = {
- nextToken: function PostScriptParser_nextToken() {
- this.prev = this.token;
- this.token = this.lexer.getToken();
- },
- accept: function PostScriptParser_accept(type) {
- if (this.token.type === type) {
- this.nextToken();
- return true;
- }
- return false;
- },
- expect: function PostScriptParser_expect(type) {
- if (this.accept(type)) {
- return true;
- }
- error('Unexpected symbol: found ' + this.token.type + ' expected ' + type + '.');
- },
- parse: function PostScriptParser_parse() {
- this.nextToken();
- this.expect(PostScriptTokenTypes.LBRACE);
- this.parseBlock();
- this.expect(PostScriptTokenTypes.RBRACE);
- return this.operators;
- },
- parseBlock: function PostScriptParser_parseBlock() {
- while (true) {
- if (this.accept(PostScriptTokenTypes.NUMBER)) {
- this.operators.push(this.prev.value);
- } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
- this.operators.push(this.prev.value);
- } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
- this.parseCondition();
- } else {
- return;
- }
- }
- },
- parseCondition: function PostScriptParser_parseCondition() {
- var conditionLocation = this.operators.length;
- this.operators.push(null, null);
- this.parseBlock();
- this.expect(PostScriptTokenTypes.RBRACE);
- if (this.accept(PostScriptTokenTypes.IF)) {
- this.operators[conditionLocation] = this.operators.length;
- this.operators[conditionLocation + 1] = 'jz';
- } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
- var jumpLocation = this.operators.length;
- this.operators.push(null, null);
- var endOfTrue = this.operators.length;
- this.parseBlock();
- this.expect(PostScriptTokenTypes.RBRACE);
- this.expect(PostScriptTokenTypes.IFELSE);
- this.operators[jumpLocation] = this.operators.length;
- this.operators[jumpLocation + 1] = 'j';
- this.operators[conditionLocation] = endOfTrue;
- this.operators[conditionLocation + 1] = 'jz';
- } else {
- error('PS Function: error parsing conditional.');
- }
- }
- };
- return PostScriptParser;
-}();
-var PostScriptTokenTypes = {
- LBRACE: 0,
- RBRACE: 1,
- NUMBER: 2,
- OPERATOR: 3,
- IF: 4,
- IFELSE: 5
-};
-var PostScriptToken = function PostScriptTokenClosure() {
- function PostScriptToken(type, value) {
- this.type = type;
- this.value = value;
- }
- var opCache = Object.create(null);
- PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
- var opValue = opCache[op];
- if (opValue) {
- return opValue;
- }
- return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
- };
- PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE, '{');
- PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE, '}');
- PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
- PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE, 'IFELSE');
- return PostScriptToken;
-}();
-var PostScriptLexer = function PostScriptLexerClosure() {
- function PostScriptLexer(stream) {
- this.stream = stream;
- this.nextChar();
- this.strBuf = [];
- }
- PostScriptLexer.prototype = {
- nextChar: function PostScriptLexer_nextChar() {
- return this.currentChar = this.stream.getByte();
- },
- getToken: function PostScriptLexer_getToken() {
- var comment = false;
- var ch = this.currentChar;
- while (true) {
- if (ch < 0) {
- return EOF;
- }
- if (comment) {
- if (ch === 0x0A || ch === 0x0D) {
- comment = false;
- }
- } else if (ch === 0x25) {
- comment = true;
- } else if (!isSpace(ch)) {
- break;
- }
- ch = this.nextChar();
- }
- switch (ch | 0) {
- case 0x30:
- case 0x31:
- case 0x32:
- case 0x33:
- case 0x34:
- case 0x35:
- case 0x36:
- case 0x37:
- case 0x38:
- case 0x39:
- case 0x2B:
- case 0x2D:
- case 0x2E:
- return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber());
- case 0x7B:
- this.nextChar();
- return PostScriptToken.LBRACE;
- case 0x7D:
- this.nextChar();
- return PostScriptToken.RBRACE;
- }
- var strBuf = this.strBuf;
- strBuf.length = 0;
- strBuf[0] = String.fromCharCode(ch);
- while ((ch = this.nextChar()) >= 0 && (ch >= 0x41 && ch <= 0x5A || ch >= 0x61 && ch <= 0x7A)) {
- strBuf.push(String.fromCharCode(ch));
- }
- var str = strBuf.join('');
- switch (str.toLowerCase()) {
- case 'if':
- return PostScriptToken.IF;
- case 'ifelse':
- return PostScriptToken.IFELSE;
- default:
- return PostScriptToken.getOperator(str);
- }
- },
- getNumber: function PostScriptLexer_getNumber() {
- var ch = this.currentChar;
- var strBuf = this.strBuf;
- strBuf.length = 0;
- strBuf[0] = String.fromCharCode(ch);
- while ((ch = this.nextChar()) >= 0) {
- if (ch >= 0x30 && ch <= 0x39 || ch === 0x2D || ch === 0x2E) {
- strBuf.push(String.fromCharCode(ch));
- } else {
- break;
- }
- }
- var value = parseFloat(strBuf.join(''));
- if (isNaN(value)) {
- error('Invalid floating point number: ' + value);
- }
- return value;
- }
- };
- return PostScriptLexer;
-}();
-exports.PostScriptLexer = PostScriptLexer;
-exports.PostScriptParser = PostScriptParser;
-
-/***/ }),
-/* 35 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var sharedUtil = __w_pdfjs_require__(0);
-var coreStream = __w_pdfjs_require__(2);
-var coreEncodings = __w_pdfjs_require__(4);
-var warn = sharedUtil.warn;
-var isSpace = sharedUtil.isSpace;
-var Stream = coreStream.Stream;
-var getEncoding = coreEncodings.getEncoding;
-var HINTING_ENABLED = false;
-var Type1CharString = function Type1CharStringClosure() {
- var COMMAND_MAP = {
- '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]
- };
- function Type1CharString() {
- this.width = 0;
- this.lsb = 0;
- this.flexing = false;
- this.output = [];
- this.stack = [];
- }
- Type1CharString.prototype = {
- convert: function Type1CharString_convert(encoded, subrs, seacAnalysisEnabled) {
- var count = encoded.length;
- var error = false;
- var wx, sbx, subrNumber;
- for (var i = 0; i < count; i++) {
- var value = encoded[i];
- if (value < 32) {
- if (value === 12) {
- value = (value << 8) + encoded[++i];
- }
- switch (value) {
- case 1:
- if (!HINTING_ENABLED) {
- this.stack = [];
- break;
- }
- error = this.executeCommand(2, COMMAND_MAP.hstem);
- break;
- case 3:
- if (!HINTING_ENABLED) {
- this.stack = [];
- break;
- }
- error = this.executeCommand(2, COMMAND_MAP.vstem);
- break;
- case 4:
- if (this.flexing) {
- if (this.stack.length < 1) {
- error = true;
- break;
- }
- var dy = this.stack.pop();
- this.stack.push(0, dy);
- break;
- }
- error = this.executeCommand(1, COMMAND_MAP.vmoveto);
- break;
- case 5:
- error = this.executeCommand(2, COMMAND_MAP.rlineto);
- break;
- case 6:
- error = this.executeCommand(1, COMMAND_MAP.hlineto);
- break;
- case 7:
- error = this.executeCommand(1, COMMAND_MAP.vlineto);
- break;
- case 8:
- error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
- break;
- case 9:
- this.stack = [];
- break;
- case 10:
- if (this.stack.length < 1) {
- error = true;
- break;
- }
- subrNumber = this.stack.pop();
- error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled);
- break;
- case 11:
- return error;
- case 13:
- if (this.stack.length < 2) {
- error = true;
- break;
- }
- wx = this.stack.pop();
- sbx = this.stack.pop();
- this.lsb = sbx;
- this.width = wx;
- this.stack.push(wx, sbx);
- error = this.executeCommand(2, COMMAND_MAP.hmoveto);
- break;
- case 14:
- this.output.push(COMMAND_MAP.endchar[0]);
- break;
- case 21:
- if (this.flexing) {
- break;
- }
- error = this.executeCommand(2, COMMAND_MAP.rmoveto);
- break;
- case 22:
- if (this.flexing) {
- this.stack.push(0);
- break;
- }
- error = this.executeCommand(1, COMMAND_MAP.hmoveto);
- break;
- case 30:
- error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
- break;
- case 31:
- error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
- break;
- case (12 << 8) + 0:
- this.stack = [];
- break;
- case (12 << 8) + 1:
- if (!HINTING_ENABLED) {
- this.stack = [];
- break;
- }
- error = this.executeCommand(2, COMMAND_MAP.vstem);
- break;
- case (12 << 8) + 2:
- if (!HINTING_ENABLED) {
- this.stack = [];
- break;
- }
- error = this.executeCommand(2, COMMAND_MAP.hstem);
- break;
- case (12 << 8) + 6:
- if (seacAnalysisEnabled) {
- this.seac = this.stack.splice(-4, 4);
- error = this.executeCommand(0, COMMAND_MAP.endchar);
- } else {
- error = this.executeCommand(4, COMMAND_MAP.endchar);
- }
- break;
- case (12 << 8) + 7:
- if (this.stack.length < 4) {
- error = true;
- break;
- }
- this.stack.pop();
- wx = this.stack.pop();
- var sby = this.stack.pop();
- sbx = this.stack.pop();
- this.lsb = sbx;
- this.width = wx;
- this.stack.push(wx, sbx, sby);
- error = this.executeCommand(3, COMMAND_MAP.rmoveto);
- break;
- case (12 << 8) + 12:
- if (this.stack.length < 2) {
- error = true;
- break;
- }
- var num2 = this.stack.pop();
- var num1 = this.stack.pop();
- this.stack.push(num1 / num2);
- break;
- case (12 << 8) + 16:
- if (this.stack.length < 2) {
- error = true;
- break;
- }
- subrNumber = this.stack.pop();
- var numArgs = this.stack.pop();
- if (subrNumber === 0 && numArgs === 3) {
- var flexArgs = this.stack.splice(this.stack.length - 17, 17);
- this.stack.push(flexArgs[2] + flexArgs[0], flexArgs[3] + flexArgs[1], flexArgs[4], flexArgs[5], flexArgs[6], flexArgs[7], flexArgs[8], flexArgs[9], flexArgs[10], flexArgs[11], flexArgs[12], flexArgs[13], flexArgs[14]);
- error = this.executeCommand(13, COMMAND_MAP.flex, true);
- this.flexing = false;
- this.stack.push(flexArgs[15], flexArgs[16]);
- } else if (subrNumber === 1 && numArgs === 0) {
- this.flexing = true;
- }
- break;
- case (12 << 8) + 17:
- break;
- case (12 << 8) + 33:
- this.stack = [];
- break;
- default:
- warn('Unknown type 1 charstring command of "' + value + '"');
- break;
- }
- if (error) {
- break;
- }
- continue;
- } else if (value <= 246) {
- value = value - 139;
- } else if (value <= 250) {
- value = (value - 247) * 256 + encoded[++i] + 108;
- } else if (value <= 254) {
- value = -((value - 251) * 256) - encoded[++i] - 108;
- } else {
- value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
- }
- this.stack.push(value);
- }
- return error;
- },
- executeCommand: function (howManyArgs, command, keepStack) {
- var stackLength = this.stack.length;
- if (howManyArgs > stackLength) {
- return true;
- }
- var start = stackLength - howManyArgs;
- for (var i = start; i < stackLength; i++) {
- var value = this.stack[i];
- if (value === (value | 0)) {
- this.output.push(28, value >> 8 & 0xff, value & 0xff);
- } else {
- value = 65536 * value | 0;
- this.output.push(255, value >> 24 & 0xFF, value >> 16 & 0xFF, value >> 8 & 0xFF, value & 0xFF);
- }
- }
- this.output.push.apply(this.output, command);
- if (keepStack) {
- this.stack.splice(start, howManyArgs);
- } else {
- this.stack.length = 0;
- }
- return false;
- }
- };
- return Type1CharString;
-}();
-var Type1Parser = function Type1ParserClosure() {
- var EEXEC_ENCRYPT_KEY = 55665;
- var CHAR_STRS_ENCRYPT_KEY = 4330;
- function isHexDigit(code) {
- return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
- }
- function decrypt(data, key, discardNumber) {
- if (discardNumber >= data.length) {
- return new Uint8Array(0);
- }
- var r = key | 0,
- c1 = 52845,
- c2 = 22719,
- i,
- j;
- for (i = 0; i < discardNumber; i++) {
- r = (data[i] + r) * c1 + c2 & (1 << 16) - 1;
- }
- var count = data.length - discardNumber;
- var decrypted = new Uint8Array(count);
- for (i = discardNumber, j = 0; j < count; i++, j++) {
- var value = data[i];
- decrypted[j] = value ^ r >> 8;
- r = (value + r) * c1 + c2 & (1 << 16) - 1;
- }
- return decrypted;
- }
- function decryptAscii(data, key, discardNumber) {
- var r = key | 0,
- c1 = 52845,
- c2 = 22719;
- var count = data.length,
- maybeLength = count >>> 1;
- var decrypted = new Uint8Array(maybeLength);
- var i, j;
- for (i = 0, j = 0; i < count; i++) {
- var digit1 = data[i];
- if (!isHexDigit(digit1)) {
- continue;
- }
- i++;
- var digit2;
- while (i < count && !isHexDigit(digit2 = data[i])) {
- i++;
- }
- if (i < count) {
- var value = parseInt(String.fromCharCode(digit1, digit2), 16);
- decrypted[j++] = value ^ r >> 8;
- r = (value + r) * c1 + c2 & (1 << 16) - 1;
- }
- }
- return Array.prototype.slice.call(decrypted, discardNumber, j);
- }
- function isSpecial(c) {
- return c === 0x2F || c === 0x5B || c === 0x5D || c === 0x7B || c === 0x7D || c === 0x28 || c === 0x29;
- }
- function Type1Parser(stream, encrypted, seacAnalysisEnabled) {
- if (encrypted) {
- var data = stream.getBytes();
- var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) && isHexDigit(data[2]) && isHexDigit(data[3]));
- stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
- }
- this.seacAnalysisEnabled = !!seacAnalysisEnabled;
- this.stream = stream;
- this.nextChar();
- }
- Type1Parser.prototype = {
- readNumberArray: function Type1Parser_readNumberArray() {
- this.getToken();
- var array = [];
- while (true) {
- var token = this.getToken();
- if (token === null || token === ']' || token === '}') {
- break;
- }
- array.push(parseFloat(token || 0));
- }
- return array;
- },
- readNumber: function Type1Parser_readNumber() {
- var token = this.getToken();
- return parseFloat(token || 0);
- },
- readInt: function Type1Parser_readInt() {
- var token = this.getToken();
- return parseInt(token || 0, 10) | 0;
- },
- readBoolean: function Type1Parser_readBoolean() {
- var token = this.getToken();
- return token === 'true' ? 1 : 0;
- },
- nextChar: function Type1_nextChar() {
- return this.currentChar = this.stream.getByte();
- },
- getToken: function Type1Parser_getToken() {
- var comment = false;
- var ch = this.currentChar;
- while (true) {
- if (ch === -1) {
- return null;
- }
- if (comment) {
- if (ch === 0x0A || ch === 0x0D) {
- comment = false;
- }
- } else if (ch === 0x25) {
- comment = true;
- } else if (!isSpace(ch)) {
- break;
- }
- ch = this.nextChar();
- }
- if (isSpecial(ch)) {
- this.nextChar();
- return String.fromCharCode(ch);
- }
- var token = '';
- do {
- token += String.fromCharCode(ch);
- ch = this.nextChar();
- } while (ch >= 0 && !isSpace(ch) && !isSpecial(ch));
- return token;
- },
- extractFontProgram: function Type1Parser_extractFontProgram() {
- var stream = this.stream;
- var subrs = [],
- charstrings = [];
- var privateData = Object.create(null);
- privateData['lenIV'] = 4;
- var program = {
- subrs: [],
- charstrings: [],
- properties: { 'privateData': privateData }
- };
- var token, length, data, lenIV, encoded;
- while ((token = this.getToken()) !== null) {
- if (token !== '/') {
- continue;
- }
- token = this.getToken();
- switch (token) {
- case 'CharStrings':
- this.getToken();
- this.getToken();
- this.getToken();
- this.getToken();
- while (true) {
- token = this.getToken();
- if (token === null || token === 'end') {
- break;
- }
- if (token !== '/') {
- continue;
- }
- var glyph = this.getToken();
- length = this.readInt();
- this.getToken();
- data = stream.makeSubStream(stream.pos, length);
- lenIV = program.properties.privateData['lenIV'];
- encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
- stream.skip(length);
- this.nextChar();
- token = this.getToken();
- if (token === 'noaccess') {
- this.getToken();
- }
- charstrings.push({
- glyph: glyph,
- encoded: encoded
- });
- }
- break;
- case 'Subrs':
- this.readInt();
- this.getToken();
- while ((token = this.getToken()) === 'dup') {
- var index = this.readInt();
- length = this.readInt();
- this.getToken();
- data = stream.makeSubStream(stream.pos, length);
- lenIV = program.properties.privateData['lenIV'];
- encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
- stream.skip(length);
- this.nextChar();
- token = this.getToken();
- if (token === 'noaccess') {
- this.getToken();
- }
- subrs[index] = encoded;
- }
- break;
- case 'BlueValues':
- case 'OtherBlues':
- case 'FamilyBlues':
- case 'FamilyOtherBlues':
- var blueArray = this.readNumberArray();
- if (blueArray.length > 0 && blueArray.length % 2 === 0 && HINTING_ENABLED) {
- program.properties.privateData[token] = blueArray;
- }
- break;
- case 'StemSnapH':
- case 'StemSnapV':
- program.properties.privateData[token] = this.readNumberArray();
- break;
- case 'StdHW':
- case 'StdVW':
- program.properties.privateData[token] = this.readNumberArray()[0];
- break;
- case 'BlueShift':
- case 'lenIV':
- case 'BlueFuzz':
- case 'BlueScale':
- case 'LanguageGroup':
- case 'ExpansionFactor':
- program.properties.privateData[token] = this.readNumber();
- break;
- case 'ForceBold':
- program.properties.privateData[token] = this.readBoolean();
- break;
- }
- }
- for (var i = 0; i < charstrings.length; i++) {
- glyph = charstrings[i].glyph;
- encoded = charstrings[i].encoded;
- var charString = new Type1CharString();
- var error = charString.convert(encoded, subrs, this.seacAnalysisEnabled);
- var output = charString.output;
- if (error) {
- output = [14];
- }
- program.charstrings.push({
- glyphName: glyph,
- charstring: output,
- width: charString.width,
- lsb: charString.lsb,
- seac: charString.seac
- });
- }
- return program;
- },
- extractFontHeader: function Type1Parser_extractFontHeader(properties) {
- var token;
- while ((token = this.getToken()) !== null) {
- if (token !== '/') {
- continue;
- }
- token = this.getToken();
- switch (token) {
- case 'FontMatrix':
- var matrix = this.readNumberArray();
- properties.fontMatrix = matrix;
- break;
- case 'Encoding':
- var encodingArg = this.getToken();
- var encoding;
- if (!/^\d+$/.test(encodingArg)) {
- encoding = getEncoding(encodingArg);
- } else {
- encoding = [];
- var size = parseInt(encodingArg, 10) | 0;
- this.getToken();
- for (var j = 0; j < size; j++) {
- token = this.getToken();
- while (token !== 'dup' && token !== 'def') {
- token = this.getToken();
- if (token === null) {
- return;
- }
- }
- if (token === 'def') {
- break;
- }
- var index = this.readInt();
- this.getToken();
- var glyph = this.getToken();
- encoding[index] = glyph;
- this.getToken();
- }
- }
- properties.builtInEncoding = encoding;
- break;
- case 'FontBBox':
- var fontBBox = this.readNumberArray();
- properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
- properties.descent = Math.min(fontBBox[1], fontBBox[3]);
- properties.ascentScaled = true;
- break;
- }
- }
- }
- };
- return Type1Parser;
-}();
-exports.Type1Parser = Type1Parser;
-
-/***/ }),
-/* 36 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-
-
-var pdfjsVersion = '1.8.172';
-var pdfjsBuild = '8ff1fbe7';
-var pdfjsCoreWorker = __w_pdfjs_require__(8);
-{
- __w_pdfjs_require__(19);
-}
-exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
-
-/***/ }),
-/* 37 */
-/***/ (function(module, exports, __w_pdfjs_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
- var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
- var userAgent = typeof navigator !== 'undefined' && navigator.userAgent || '';
- var isAndroid = /Android/.test(userAgent);
- var isAndroidPre3 = /Android\s[0-2][^\d]/.test(userAgent);
- var isAndroidPre5 = /Android\s[0-4][^\d]/.test(userAgent);
- var isChrome = userAgent.indexOf('Chrom') >= 0;
- var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(userAgent);
- var isIOSChrome = userAgent.indexOf('CriOS') >= 0;
- var isIE = userAgent.indexOf('Trident') >= 0;
- var isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
- var isOpera = userAgent.indexOf('Opera') >= 0;
- var isSafari = /Safari\//.test(userAgent) && !/(Chrome\/|Android\s)/.test(userAgent);
- var hasDOM = typeof window === 'object' && typeof document === 'object';
- if (typeof PDFJS === 'undefined') {
- globalScope.PDFJS = {};
- }
- PDFJS.compatibilityChecked = true;
- (function checkTypedArrayCompatibility() {
- if (typeof Uint8Array !== 'undefined') {
- if (typeof Uint8Array.prototype.subarray === 'undefined') {
- Uint8Array.prototype.subarray = function subarray(start, end) {
- return new Uint8Array(this.slice(start, end));
- };
- Float32Array.prototype.subarray = function subarray(start, end) {
- return new Float32Array(this.slice(start, end));
- };
- }
- if (typeof Float64Array === 'undefined') {
- globalScope.Float64Array = Float32Array;
- }
- return;
- }
- function subarray(start, end) {
- return new TypedArray(this.slice(start, end));
- }
- function setArrayOffset(array, offset) {
- if (arguments.length < 2) {
- offset = 0;
- }
- for (var i = 0, n = array.length; i < n; ++i, ++offset) {
- this[offset] = array[i] & 0xFF;
- }
- }
- function TypedArray(arg1) {
- var result, i, n;
- if (typeof arg1 === 'number') {
- result = [];
- for (i = 0; i < arg1; ++i) {
- result[i] = 0;
- }
- } else if ('slice' in arg1) {
- result = arg1.slice(0);
- } else {
- result = [];
- for (i = 0, n = arg1.length; i < n; ++i) {
- result[i] = arg1[i];
- }
- }
- result.subarray = subarray;
- result.buffer = result;
- result.byteLength = result.length;
- result.set = setArrayOffset;
- if (typeof arg1 === 'object' && arg1.buffer) {
- result.buffer = arg1.buffer;
- }
- return result;
- }
- globalScope.Uint8Array = TypedArray;
- globalScope.Int8Array = TypedArray;
- globalScope.Uint32Array = TypedArray;
- globalScope.Int32Array = TypedArray;
- globalScope.Uint16Array = TypedArray;
- globalScope.Float32Array = TypedArray;
- globalScope.Float64Array = TypedArray;
- })();
- (function normalizeURLObject() {
- if (!globalScope.URL) {
- globalScope.URL = globalScope.webkitURL;
- }
- })();
- (function checkObjectDefinePropertyCompatibility() {
- if (typeof Object.defineProperty !== 'undefined') {
- var definePropertyPossible = true;
- try {
- if (hasDOM) {
- Object.defineProperty(new Image(), 'id', { value: 'test' });
- }
- var Test = function Test() {};
- Test.prototype = {
- get id() {}
- };
- Object.defineProperty(new Test(), 'id', {
- value: '',
- configurable: true,
- enumerable: true,
- writable: false
- });
- } catch (e) {
- definePropertyPossible = false;
- }
- if (definePropertyPossible) {
- return;
- }
- }
- Object.defineProperty = function objectDefineProperty(obj, name, def) {
- delete obj[name];
- if ('get' in def) {
- obj.__defineGetter__(name, def['get']);
- }
- if ('set' in def) {
- obj.__defineSetter__(name, def['set']);
- }
- if ('value' in def) {
- obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
- this.__defineGetter__(name, function objectDefinePropertyGetter() {
- return value;
- });
- return value;
- });
- obj[name] = def.value;
- }
- };
- })();
- (function checkXMLHttpRequestResponseCompatibility() {
- if (typeof XMLHttpRequest === 'undefined') {
- return;
- }
- var xhrPrototype = XMLHttpRequest.prototype;
- var xhr = new XMLHttpRequest();
- if (!('overrideMimeType' in xhr)) {
- Object.defineProperty(xhrPrototype, 'overrideMimeType', {
- value: function xmlHttpRequestOverrideMimeType(mimeType) {}
- });
- }
- if ('responseType' in xhr) {
- return;
- }
- Object.defineProperty(xhrPrototype, 'responseType', {
- get: function xmlHttpRequestGetResponseType() {
- return this._responseType || 'text';
- },
- set: function xmlHttpRequestSetResponseType(value) {
- if (value === 'text' || value === 'arraybuffer') {
- this._responseType = value;
- if (value === 'arraybuffer' && typeof this.overrideMimeType === 'function') {
- this.overrideMimeType('text/plain; charset=x-user-defined');
- }
- }
- }
- });
- if (typeof VBArray !== 'undefined') {
- Object.defineProperty(xhrPrototype, 'response', {
- get: function xmlHttpRequestResponseGet() {
- if (this.responseType === 'arraybuffer') {
- return new Uint8Array(new VBArray(this.responseBody).toArray());
- }
- return this.responseText;
- }
- });
- return;
- }
- Object.defineProperty(xhrPrototype, 'response', {
- get: function xmlHttpRequestResponseGet() {
- if (this.responseType !== 'arraybuffer') {
- return this.responseText;
- }
- var text = this.responseText;
- var i,
- n = text.length;
- var result = new Uint8Array(n);
- for (i = 0; i < n; ++i) {
- result[i] = text.charCodeAt(i) & 0xFF;
- }
- return result.buffer;
- }
- });
- })();
- (function checkWindowBtoaCompatibility() {
- if ('btoa' in globalScope) {
- return;
- }
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- globalScope.btoa = function (chars) {
- var buffer = '';
- var i, n;
- for (i = 0, n = chars.length; i < n; i += 3) {
- var b1 = chars.charCodeAt(i) & 0xFF;
- var b2 = chars.charCodeAt(i + 1) & 0xFF;
- var b3 = chars.charCodeAt(i + 2) & 0xFF;
- var d1 = b1 >> 2,
- d2 = (b1 & 3) << 4 | b2 >> 4;
- var d3 = i + 1 < n ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
- var d4 = i + 2 < n ? b3 & 0x3F : 64;
- buffer += digits.charAt(d1) + digits.charAt(d2) + digits.charAt(d3) + digits.charAt(d4);
- }
- return buffer;
- };
- })();
- (function checkWindowAtobCompatibility() {
- if ('atob' in globalScope) {
- return;
- }
- var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- globalScope.atob = function (input) {
- input = input.replace(/=+$/, '');
- if (input.length % 4 === 1) {
- throw new Error('bad atob input');
- }
- for (var bc = 0, bs, buffer, idx = 0, output = ''; buffer = input.charAt(idx++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
- buffer = digits.indexOf(buffer);
- }
- return output;
- };
- })();
- (function checkFunctionPrototypeBindCompatibility() {
- if (typeof Function.prototype.bind !== 'undefined') {
- return;
- }
- Function.prototype.bind = function functionPrototypeBind(obj) {
- var fn = this,
- headArgs = Array.prototype.slice.call(arguments, 1);
- var bound = function functionPrototypeBindBound() {
- var args = headArgs.concat(Array.prototype.slice.call(arguments));
- return fn.apply(obj, args);
- };
- return bound;
- };
- })();
- (function checkDatasetProperty() {
- if (!hasDOM) {
- return;
- }
- var div = document.createElement('div');
- if ('dataset' in div) {
- return;
- }
- Object.defineProperty(HTMLElement.prototype, 'dataset', {
- get: function () {
- if (this._dataset) {
- return this._dataset;
- }
- var dataset = {};
- for (var j = 0, jj = this.attributes.length; j < jj; j++) {
- var attribute = this.attributes[j];
- if (attribute.name.substring(0, 5) !== 'data-') {
- continue;
- }
- var key = attribute.name.substring(5).replace(/\-([a-z])/g, function (all, ch) {
- return ch.toUpperCase();
- });
- dataset[key] = attribute.value;
- }
- Object.defineProperty(this, '_dataset', {
- value: dataset,
- writable: false,
- enumerable: false
- });
- return dataset;
- },
- enumerable: true
- });
- })();
- (function checkClassListProperty() {
- function changeList(element, itemName, add, remove) {
- var s = element.className || '';
- var list = s.split(/\s+/g);
- if (list[0] === '') {
- list.shift();
- }
- var index = list.indexOf(itemName);
- if (index < 0 && add) {
- list.push(itemName);
- }
- if (index >= 0 && remove) {
- list.splice(index, 1);
- }
- element.className = list.join(' ');
- return index >= 0;
- }
- if (!hasDOM) {
- return;
- }
- var div = document.createElement('div');
- if ('classList' in div) {
- return;
- }
- var classListPrototype = {
- add: function (name) {
- changeList(this.element, name, true, false);
- },
- contains: function (name) {
- return changeList(this.element, name, false, false);
- },
- remove: function (name) {
- changeList(this.element, name, false, true);
- },
- toggle: function (name) {
- changeList(this.element, name, true, true);
- }
- };
- Object.defineProperty(HTMLElement.prototype, 'classList', {
- get: function () {
- if (this._classList) {
- return this._classList;
- }
- var classList = Object.create(classListPrototype, {
- element: {
- value: this,
- writable: false,
- enumerable: true
- }
- });
- Object.defineProperty(this, '_classList', {
- value: classList,
- writable: false,
- enumerable: false
- });
- return classList;
- },
- enumerable: true
- });
- })();
- (function checkWorkerConsoleCompatibility() {
- if (typeof importScripts === 'undefined' || 'console' in globalScope) {
- return;
- }
- var consoleTimer = {};
- var workerConsole = {
- log: function log() {
- var args = Array.prototype.slice.call(arguments);
- globalScope.postMessage({
- targetName: 'main',
- action: 'console_log',
- data: args
- });
- },
- error: function error() {
- var args = Array.prototype.slice.call(arguments);
- globalScope.postMessage({
- targetName: 'main',
- action: 'console_error',
- data: args
- });
- },
- time: function time(name) {
- consoleTimer[name] = Date.now();
- },
- timeEnd: function timeEnd(name) {
- var time = consoleTimer[name];
- if (!time) {
- throw new Error('Unknown timer name ' + name);
- }
- this.log('Timer:', name, Date.now() - time);
- }
- };
- globalScope.console = workerConsole;
- })();
- (function checkConsoleCompatibility() {
- if (!hasDOM) {
- return;
- }
- if (!('console' in window)) {
- window.console = {
- log: function () {},
- error: function () {},
- warn: function () {}
- };
- return;
- }
- if (!('bind' in console.log)) {
- console.log = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.log);
- console.error = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.error);
- console.warn = function (fn) {
- return function (msg) {
- return fn(msg);
- };
- }(console.warn);
- return;
- }
- })();
- (function checkOnClickCompatibility() {
- function ignoreIfTargetDisabled(event) {
- if (isDisabled(event.target)) {
- event.stopPropagation();
- }
- }
- function isDisabled(node) {
- return node.disabled || node.parentNode && isDisabled(node.parentNode);
- }
- if (isOpera) {
- document.addEventListener('click', ignoreIfTargetDisabled, true);
- }
- })();
- (function checkOnBlobSupport() {
- if (isIE || isIOSChrome) {
- PDFJS.disableCreateObjectURL = true;
- }
- })();
- (function checkNavigatorLanguage() {
- if (typeof navigator === 'undefined') {
- return;
- }
- if ('language' in navigator) {
- return;
- }
- PDFJS.locale = navigator.userLanguage || 'en-US';
- })();
- (function checkRangeRequests() {
- if (isSafari || isAndroidPre3 || isChromeWithRangeBug || isIOS) {
- PDFJS.disableRange = true;
- PDFJS.disableStream = true;
- }
- })();
- (function checkHistoryManipulation() {
- if (!hasDOM) {
- return;
- }
- if (!history.pushState || isAndroidPre3) {
- PDFJS.disableHistory = true;
- }
- })();
- (function checkSetPresenceInImageData() {
- if (!hasDOM) {
- return;
- }
- if (window.CanvasPixelArray) {
- if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
- window.CanvasPixelArray.prototype.set = function (arr) {
- for (var i = 0, ii = this.length; i < ii; i++) {
- this[i] = arr[i];
- }
- };
- }
- } else {
- var polyfill = false,
- versionMatch;
- if (isChrome) {
- versionMatch = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
- polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
- } else if (isAndroid) {
- polyfill = isAndroidPre5;
- } else if (isSafari) {
- versionMatch = userAgent.match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
- polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
- }
- if (polyfill) {
- var contextPrototype = window.CanvasRenderingContext2D.prototype;
- var createImageData = contextPrototype.createImageData;
- contextPrototype.createImageData = function (w, h) {
- var imageData = createImageData.call(this, w, h);
- imageData.data.set = function (arr) {
- for (var i = 0, ii = this.length; i < ii; i++) {
- this[i] = arr[i];
- }
- };
- return imageData;
- };
- contextPrototype = null;
- }
- }
- })();
- (function checkRequestAnimationFrame() {
- function installFakeAnimationFrameFunctions() {
- window.requestAnimationFrame = function (callback) {
- return window.setTimeout(callback, 20);
- };
- window.cancelAnimationFrame = function (timeoutID) {
- window.clearTimeout(timeoutID);
- };
- }
- if (!hasDOM) {
- return;
- }
- if (isIOS) {
- installFakeAnimationFrameFunctions();
- return;
- }
- if ('requestAnimationFrame' in window) {
- return;
- }
- window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
- if (!('requestAnimationFrame' in window)) {
- installFakeAnimationFrameFunctions();
- }
- })();
- (function checkCanvasSizeLimitation() {
- if (isIOS || isAndroid) {
- PDFJS.maxCanvasPixels = 5242880;
- }
- })();
- (function checkFullscreenSupport() {
- if (!hasDOM) {
- return;
- }
- if (isIE && window.parent !== window) {
- PDFJS.disableFullscreen = true;
- }
- })();
- (function checkCurrentScript() {
- if (!hasDOM) {
- return;
- }
- if ('currentScript' in document) {
- return;
- }
- Object.defineProperty(document, 'currentScript', {
- get: function () {
- var scripts = document.getElementsByTagName('script');
- return scripts[scripts.length - 1];
- },
- enumerable: true,
- configurable: true
- });
- })();
- (function checkInputTypeNumberAssign() {
- if (!hasDOM) {
- return;
- }
- var el = document.createElement('input');
- try {
- el.type = 'number';
- } catch (ex) {
- var inputProto = el.constructor.prototype;
- var typeProperty = Object.getOwnPropertyDescriptor(inputProto, 'type');
- Object.defineProperty(inputProto, 'type', {
- get: function () {
- return typeProperty.get.call(this);
- },
- set: function (value) {
- typeProperty.set.call(this, value === 'number' ? 'text' : value);
- },
- enumerable: true,
- configurable: true
- });
- }
- })();
- (function checkDocumentReadyState() {
- if (!hasDOM) {
- return;
- }
- if (!document.attachEvent) {
- return;
- }
- var documentProto = document.constructor.prototype;
- var readyStateProto = Object.getOwnPropertyDescriptor(documentProto, 'readyState');
- Object.defineProperty(documentProto, 'readyState', {
- get: function () {
- var value = readyStateProto.get.call(this);
- return value === 'interactive' ? 'loading' : value;
- },
- set: function (value) {
- readyStateProto.set.call(this, value);
- },
- enumerable: true,
- configurable: true
- });
- })();
- (function checkChildNodeRemove() {
- if (!hasDOM) {
- return;
- }
- if (typeof Element.prototype.remove !== 'undefined') {
- return;
- }
- Element.prototype.remove = function () {
- if (this.parentNode) {
- this.parentNode.removeChild(this);
- }
- };
- })();
- (function checkPromise() {
- if (globalScope.Promise) {
- if (typeof globalScope.Promise.all !== 'function') {
- globalScope.Promise.all = function (iterable) {
- var count = 0,
- results = [],
- resolve,
- reject;
- var promise = new globalScope.Promise(function (resolve_, reject_) {
- resolve = resolve_;
- reject = reject_;
- });
- iterable.forEach(function (p, i) {
- count++;
- p.then(function (result) {
- results[i] = result;
- count--;
- if (count === 0) {
- resolve(results);
- }
- }, reject);
- });
- if (count === 0) {
- resolve(results);
- }
- return promise;
- };
- }
- if (typeof globalScope.Promise.resolve !== 'function') {
- globalScope.Promise.resolve = function (value) {
- return new globalScope.Promise(function (resolve) {
- resolve(value);
- });
- };
- }
- if (typeof globalScope.Promise.reject !== 'function') {
- globalScope.Promise.reject = function (reason) {
- return new globalScope.Promise(function (resolve, reject) {
- reject(reason);
- });
- };
- }
- if (typeof globalScope.Promise.prototype.catch !== 'function') {
- globalScope.Promise.prototype.catch = function (onReject) {
- return globalScope.Promise.prototype.then(undefined, onReject);
- };
- }
- return;
- }
- var STATUS_PENDING = 0;
- var STATUS_RESOLVED = 1;
- var STATUS_REJECTED = 2;
- var REJECTION_TIMEOUT = 500;
- var HandlerManager = {
- handlers: [],
- running: false,
- unhandledRejections: [],
- pendingRejectionCheck: false,
- scheduleHandlers: function scheduleHandlers(promise) {
- if (promise._status === STATUS_PENDING) {
- return;
- }
- this.handlers = this.handlers.concat(promise._handlers);
- promise._handlers = [];
- if (this.running) {
- return;
- }
- this.running = true;
- setTimeout(this.runHandlers.bind(this), 0);
- },
- runHandlers: function runHandlers() {
- var RUN_TIMEOUT = 1;
- var timeoutAt = Date.now() + RUN_TIMEOUT;
- while (this.handlers.length > 0) {
- var handler = this.handlers.shift();
- var nextStatus = handler.thisPromise._status;
- var nextValue = handler.thisPromise._value;
- try {
- if (nextStatus === STATUS_RESOLVED) {
- if (typeof handler.onResolve === 'function') {
- nextValue = handler.onResolve(nextValue);
- }
- } else if (typeof handler.onReject === 'function') {
- nextValue = handler.onReject(nextValue);
- nextStatus = STATUS_RESOLVED;
- if (handler.thisPromise._unhandledRejection) {
- this.removeUnhandeledRejection(handler.thisPromise);
- }
- }
- } catch (ex) {
- nextStatus = STATUS_REJECTED;
- nextValue = ex;
- }
- handler.nextPromise._updateStatus(nextStatus, nextValue);
- if (Date.now() >= timeoutAt) {
- break;
- }
- }
- if (this.handlers.length > 0) {
- setTimeout(this.runHandlers.bind(this), 0);
- return;
- }
- this.running = false;
- },
- addUnhandledRejection: function addUnhandledRejection(promise) {
- this.unhandledRejections.push({
- promise: promise,
- time: Date.now()
- });
- this.scheduleRejectionCheck();
- },
- removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
- promise._unhandledRejection = false;
- for (var i = 0; i < this.unhandledRejections.length; i++) {
- if (this.unhandledRejections[i].promise === promise) {
- this.unhandledRejections.splice(i);
- i--;
- }
- }
- },
- scheduleRejectionCheck: function scheduleRejectionCheck() {
- if (this.pendingRejectionCheck) {
- return;
- }
- this.pendingRejectionCheck = true;
- setTimeout(function rejectionCheck() {
- this.pendingRejectionCheck = false;
- var now = Date.now();
- for (var i = 0; i < this.unhandledRejections.length; i++) {
- if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
- var unhandled = this.unhandledRejections[i].promise._value;
- var msg = 'Unhandled rejection: ' + unhandled;
- if (unhandled.stack) {
- msg += '\n' + unhandled.stack;
- }
- try {
- throw new Error(msg);
- } catch (_) {
- console.warn(msg);
- }
- this.unhandledRejections.splice(i);
- i--;
- }
- }
- if (this.unhandledRejections.length) {
- this.scheduleRejectionCheck();
- }
- }.bind(this), REJECTION_TIMEOUT);
- }
- };
- var Promise = function Promise(resolver) {
- this._status = STATUS_PENDING;
- this._handlers = [];
- try {
- resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
- } catch (e) {
- this._reject(e);
- }
- };
- Promise.all = function Promise_all(promises) {
- var resolveAll, rejectAll;
- var deferred = new Promise(function (resolve, reject) {
- resolveAll = resolve;
- rejectAll = reject;
- });
- var unresolved = promises.length;
- var results = [];
- if (unresolved === 0) {
- resolveAll(results);
- return deferred;
- }
- function reject(reason) {
- if (deferred._status === STATUS_REJECTED) {
- return;
- }
- results = [];
- rejectAll(reason);
- }
- for (var i = 0, ii = promises.length; i < ii; ++i) {
- var promise = promises[i];
- var resolve = function (i) {
- return function (value) {
- if (deferred._status === STATUS_REJECTED) {
- return;
- }
- results[i] = value;
- unresolved--;
- if (unresolved === 0) {
- resolveAll(results);
- }
- };
- }(i);
- if (Promise.isPromise(promise)) {
- promise.then(resolve, reject);
- } else {
- resolve(promise);
- }
- }
- return deferred;
- };
- Promise.isPromise = function Promise_isPromise(value) {
- return value && typeof value.then === 'function';
- };
- Promise.resolve = function Promise_resolve(value) {
- return new Promise(function (resolve) {
- resolve(value);
- });
- };
- Promise.reject = function Promise_reject(reason) {
- return new Promise(function (resolve, reject) {
- reject(reason);
- });
- };
- Promise.prototype = {
- _status: null,
- _value: null,
- _handlers: null,
- _unhandledRejection: null,
- _updateStatus: function Promise__updateStatus(status, value) {
- if (this._status === STATUS_RESOLVED || this._status === STATUS_REJECTED) {
- return;
- }
- if (status === STATUS_RESOLVED && Promise.isPromise(value)) {
- value.then(this._updateStatus.bind(this, STATUS_RESOLVED), this._updateStatus.bind(this, STATUS_REJECTED));
- return;
- }
- this._status = status;
- this._value = value;
- if (status === STATUS_REJECTED && this._handlers.length === 0) {
- this._unhandledRejection = true;
- HandlerManager.addUnhandledRejection(this);
- }
- HandlerManager.scheduleHandlers(this);
- },
- _resolve: function Promise_resolve(value) {
- this._updateStatus(STATUS_RESOLVED, value);
- },
- _reject: function Promise_reject(reason) {
- this._updateStatus(STATUS_REJECTED, reason);
- },
- then: function Promise_then(onResolve, onReject) {
- var nextPromise = new Promise(function (resolve, reject) {
- this.resolve = resolve;
- this.reject = reject;
- });
- this._handlers.push({
- thisPromise: this,
- onResolve: onResolve,
- onReject: onReject,
- nextPromise: nextPromise
- });
- HandlerManager.scheduleHandlers(this);
- return nextPromise;
- },
- catch: function Promise_catch(onReject) {
- return this.then(undefined, onReject);
- }
- };
- globalScope.Promise = Promise;
- })();
- (function checkWeakMap() {
- if (globalScope.WeakMap) {
- return;
- }
- var id = 0;
- function WeakMap() {
- this.id = '$weakmap' + id++;
- }
- WeakMap.prototype = {
- has: function (obj) {
- return !!Object.getOwnPropertyDescriptor(obj, this.id);
- },
- get: function (obj, defaultValue) {
- return this.has(obj) ? obj[this.id] : defaultValue;
- },
- set: function (obj, value) {
- Object.defineProperty(obj, this.id, {
- value: value,
- enumerable: false,
- configurable: true
- });
- },
- delete: function (obj) {
- delete obj[this.id];
- }
- };
- globalScope.WeakMap = WeakMap;
- })();
- (function checkURLConstructor() {
- var hasWorkingUrl = false;
- try {
- if (typeof URL === 'function' && typeof URL.prototype === 'object' && 'origin' in URL.prototype) {
- var u = new URL('b', 'http://a');
- u.pathname = 'c%20d';
- hasWorkingUrl = u.href === 'http://a/c%20d';
- }
- } catch (e) {}
- if (hasWorkingUrl) {
- return;
- }
- var relative = Object.create(null);
- relative['ftp'] = 21;
- relative['file'] = 0;
- relative['gopher'] = 70;
- relative['http'] = 80;
- relative['https'] = 443;
- relative['ws'] = 80;
- relative['wss'] = 443;
- var relativePathDotMapping = Object.create(null);
- relativePathDotMapping['%2e'] = '.';
- relativePathDotMapping['.%2e'] = '..';
- relativePathDotMapping['%2e.'] = '..';
- relativePathDotMapping['%2e%2e'] = '..';
- function isRelativeScheme(scheme) {
- return relative[scheme] !== undefined;
- }
- function invalid() {
- clear.call(this);
- this._isInvalid = true;
- }
- function IDNAToASCII(h) {
- if (h === '') {
- invalid.call(this);
- }
- return h.toLowerCase();
- }
- function percentEscape(c) {
- var unicode = c.charCodeAt(0);
- if (unicode > 0x20 && unicode < 0x7F && [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) === -1) {
- return c;
- }
- return encodeURIComponent(c);
- }
- function percentEscapeQuery(c) {
- var unicode = c.charCodeAt(0);
- if (unicode > 0x20 && unicode < 0x7F && [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) === -1) {
- return c;
- }
- return encodeURIComponent(c);
- }
- var EOF,
- ALPHA = /[a-zA-Z]/,
- ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
- function parse(input, stateOverride, base) {
- function err(message) {
- errors.push(message);
- }
- var state = stateOverride || 'scheme start',
- cursor = 0,
- buffer = '',
- seenAt = false,
- seenBracket = false,
- errors = [];
- loop: while ((input[cursor - 1] !== EOF || cursor === 0) && !this._isInvalid) {
- var c = input[cursor];
- switch (state) {
- case 'scheme start':
- if (c && ALPHA.test(c)) {
- buffer += c.toLowerCase();
- state = 'scheme';
- } else if (!stateOverride) {
- buffer = '';
- state = 'no scheme';
- continue;
- } else {
- err('Invalid scheme.');
- break loop;
- }
- break;
- case 'scheme':
- if (c && ALPHANUMERIC.test(c)) {
- buffer += c.toLowerCase();
- } else if (c === ':') {
- this._scheme = buffer;
- buffer = '';
- if (stateOverride) {
- break loop;
- }
- if (isRelativeScheme(this._scheme)) {
- this._isRelative = true;
- }
- if (this._scheme === 'file') {
- state = 'relative';
- } else if (this._isRelative && base && base._scheme === this._scheme) {
- state = 'relative or authority';
- } else if (this._isRelative) {
- state = 'authority first slash';
- } else {
- state = 'scheme data';
- }
- } else if (!stateOverride) {
- buffer = '';
- cursor = 0;
- state = 'no scheme';
- continue;
- } else if (c === EOF) {
- break loop;
- } else {
- err('Code point not allowed in scheme: ' + c);
- break loop;
- }
- break;
- case 'scheme data':
- if (c === '?') {
- this._query = '?';
- state = 'query';
- } else if (c === '#') {
- this._fragment = '#';
- state = 'fragment';
- } else {
- if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._schemeData += percentEscape(c);
- }
- }
- break;
- case 'no scheme':
- if (!base || !isRelativeScheme(base._scheme)) {
- err('Missing scheme.');
- invalid.call(this);
- } else {
- state = 'relative';
- continue;
- }
- break;
- case 'relative or authority':
- if (c === '/' && input[cursor + 1] === '/') {
- state = 'authority ignore slashes';
- } else {
- err('Expected /, got: ' + c);
- state = 'relative';
- continue;
- }
- break;
- case 'relative':
- this._isRelative = true;
- if (this._scheme !== 'file') {
- this._scheme = base._scheme;
- }
- if (c === EOF) {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = base._query;
- this._username = base._username;
- this._password = base._password;
- break loop;
- } else if (c === '/' || c === '\\') {
- if (c === '\\') {
- err('\\ is an invalid code point.');
- }
- state = 'relative slash';
- } else if (c === '?') {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = '?';
- this._username = base._username;
- this._password = base._password;
- state = 'query';
- } else if (c === '#') {
- this._host = base._host;
- this._port = base._port;
- this._path = base._path.slice();
- this._query = base._query;
- this._fragment = '#';
- this._username = base._username;
- this._password = base._password;
- state = 'fragment';
- } else {
- var nextC = input[cursor + 1];
- var nextNextC = input[cursor + 2];
- if (this._scheme !== 'file' || !ALPHA.test(c) || nextC !== ':' && nextC !== '|' || nextNextC !== EOF && nextNextC !== '/' && nextNextC !== '\\' && nextNextC !== '?' && nextNextC !== '#') {
- this._host = base._host;
- this._port = base._port;
- this._username = base._username;
- this._password = base._password;
- this._path = base._path.slice();
- this._path.pop();
- }
- state = 'relative path';
- continue;
- }
- break;
- case 'relative slash':
- if (c === '/' || c === '\\') {
- if (c === '\\') {
- err('\\ is an invalid code point.');
- }
- if (this._scheme === 'file') {
- state = 'file host';
- } else {
- state = 'authority ignore slashes';
- }
- } else {
- if (this._scheme !== 'file') {
- this._host = base._host;
- this._port = base._port;
- this._username = base._username;
- this._password = base._password;
- }
- state = 'relative path';
- continue;
- }
- break;
- case 'authority first slash':
- if (c === '/') {
- state = 'authority second slash';
- } else {
- err('Expected \'/\', got: ' + c);
- state = 'authority ignore slashes';
- continue;
- }
- break;
- case 'authority second slash':
- state = 'authority ignore slashes';
- if (c !== '/') {
- err('Expected \'/\', got: ' + c);
- continue;
- }
- break;
- case 'authority ignore slashes':
- if (c !== '/' && c !== '\\') {
- state = 'authority';
- continue;
- } else {
- err('Expected authority, got: ' + c);
- }
- break;
- case 'authority':
- if (c === '@') {
- if (seenAt) {
- err('@ already seen.');
- buffer += '%40';
- }
- seenAt = true;
- for (var i = 0; i < buffer.length; i++) {
- var cp = buffer[i];
- if (cp === '\t' || cp === '\n' || cp === '\r') {
- err('Invalid whitespace in authority.');
- continue;
- }
- if (cp === ':' && this._password === null) {
- this._password = '';
- continue;
- }
- var tempC = percentEscape(cp);
- if (this._password !== null) {
- this._password += tempC;
- } else {
- this._username += tempC;
- }
- }
- buffer = '';
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- cursor -= buffer.length;
- buffer = '';
- state = 'host';
- continue;
- } else {
- buffer += c;
- }
- break;
- case 'file host':
- if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- if (buffer.length === 2 && ALPHA.test(buffer[0]) && (buffer[1] === ':' || buffer[1] === '|')) {
- state = 'relative path';
- } else if (buffer.length === 0) {
- state = 'relative path start';
- } else {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'relative path start';
- }
- continue;
- } else if (c === '\t' || c === '\n' || c === '\r') {
- err('Invalid whitespace in file host.');
- } else {
- buffer += c;
- }
- break;
- case 'host':
- case 'hostname':
- if (c === ':' && !seenBracket) {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'port';
- if (stateOverride === 'hostname') {
- break loop;
- }
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
- this._host = IDNAToASCII.call(this, buffer);
- buffer = '';
- state = 'relative path start';
- if (stateOverride) {
- break loop;
- }
- continue;
- } else if (c !== '\t' && c !== '\n' && c !== '\r') {
- if (c === '[') {
- seenBracket = true;
- } else if (c === ']') {
- seenBracket = false;
- }
- buffer += c;
- } else {
- err('Invalid code point in host/hostname: ' + c);
- }
- break;
- case 'port':
- if (/[0-9]/.test(c)) {
- buffer += c;
- } else if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#' || stateOverride) {
- if (buffer !== '') {
- var temp = parseInt(buffer, 10);
- if (temp !== relative[this._scheme]) {
- this._port = temp + '';
- }
- buffer = '';
- }
- if (stateOverride) {
- break loop;
- }
- state = 'relative path start';
- continue;
- } else if (c === '\t' || c === '\n' || c === '\r') {
- err('Invalid code point in port: ' + c);
- } else {
- invalid.call(this);
- }
- break;
- case 'relative path start':
- if (c === '\\') {
- err('\'\\\' not allowed in path.');
- }
- state = 'relative path';
- if (c !== '/' && c !== '\\') {
- continue;
- }
- break;
- case 'relative path':
- if (c === EOF || c === '/' || c === '\\' || !stateOverride && (c === '?' || c === '#')) {
- if (c === '\\') {
- err('\\ not allowed in relative path.');
- }
- var tmp;
- if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
- buffer = tmp;
- }
- if (buffer === '..') {
- this._path.pop();
- if (c !== '/' && c !== '\\') {
- this._path.push('');
- }
- } else if (buffer === '.' && c !== '/' && c !== '\\') {
- this._path.push('');
- } else if (buffer !== '.') {
- if (this._scheme === 'file' && this._path.length === 0 && buffer.length === 2 && ALPHA.test(buffer[0]) && buffer[1] === '|') {
- buffer = buffer[0] + ':';
- }
- this._path.push(buffer);
- }
- buffer = '';
- if (c === '?') {
- this._query = '?';
- state = 'query';
- } else if (c === '#') {
- this._fragment = '#';
- state = 'fragment';
- }
- } else if (c !== '\t' && c !== '\n' && c !== '\r') {
- buffer += percentEscape(c);
- }
- break;
- case 'query':
- if (!stateOverride && c === '#') {
- this._fragment = '#';
- state = 'fragment';
- } else if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._query += percentEscapeQuery(c);
- }
- break;
- case 'fragment':
- if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
- this._fragment += c;
- }
- break;
- }
- cursor++;
- }
- }
- function clear() {
- this._scheme = '';
- this._schemeData = '';
- this._username = '';
- this._password = null;
- this._host = '';
- this._port = '';
- this._path = [];
- this._query = '';
- this._fragment = '';
- this._isInvalid = false;
- this._isRelative = false;
- }
- function JURL(url, base) {
- if (base !== undefined && !(base instanceof JURL)) {
- base = new JURL(String(base));
- }
- this._url = url;
- clear.call(this);
- var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
- parse.call(this, input, null, base);
- }
- JURL.prototype = {
- toString: function () {
- return this.href;
- },
- get href() {
- if (this._isInvalid) {
- return this._url;
- }
- var authority = '';
- if (this._username !== '' || this._password !== null) {
- authority = this._username + (this._password !== null ? ':' + this._password : '') + '@';
- }
- return this.protocol + (this._isRelative ? '//' + authority + this.host : '') + this.pathname + this._query + this._fragment;
- },
- set href(href) {
- clear.call(this);
- parse.call(this, href);
- },
- get protocol() {
- return this._scheme + ':';
- },
- set protocol(protocol) {
- if (this._isInvalid) {
- return;
- }
- parse.call(this, protocol + ':', 'scheme start');
- },
- get host() {
- return this._isInvalid ? '' : this._port ? this._host + ':' + this._port : this._host;
- },
- set host(host) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, host, 'host');
- },
- get hostname() {
- return this._host;
- },
- set hostname(hostname) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, hostname, 'hostname');
- },
- get port() {
- return this._port;
- },
- set port(port) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- parse.call(this, port, 'port');
- },
- get pathname() {
- return this._isInvalid ? '' : this._isRelative ? '/' + this._path.join('/') : this._schemeData;
- },
- set pathname(pathname) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- this._path = [];
- parse.call(this, pathname, 'relative path start');
- },
- get search() {
- return this._isInvalid || !this._query || this._query === '?' ? '' : this._query;
- },
- set search(search) {
- if (this._isInvalid || !this._isRelative) {
- return;
- }
- this._query = '?';
- if (search[0] === '?') {
- search = search.slice(1);
- }
- parse.call(this, search, 'query');
- },
- get hash() {
- return this._isInvalid || !this._fragment || this._fragment === '#' ? '' : this._fragment;
- },
- set hash(hash) {
- if (this._isInvalid) {
- return;
- }
- this._fragment = '#';
- if (hash[0] === '#') {
- hash = hash.slice(1);
- }
- parse.call(this, hash, 'fragment');
- },
- get origin() {
- var host;
- if (this._isInvalid || !this._scheme) {
- return '';
- }
- switch (this._scheme) {
- case 'data':
- case 'file':
- case 'javascript':
- case 'mailto':
- return 'null';
- }
- host = this.host;
- if (!host) {
- return '';
- }
- return this._scheme + '://' + host;
- }
- };
- var OriginalURL = globalScope.URL;
- if (OriginalURL) {
- JURL.createObjectURL = function (blob) {
- return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
- };
- JURL.revokeObjectURL = function (url) {
- OriginalURL.revokeObjectURL(url);
- };
- }
- globalScope.URL = JURL;
- })();
-}
-/* WEBPACK VAR INJECTION */}.call(exports, __w_pdfjs_require__(9)))
-
-/***/ })
-/******/ ]);
-}); \ No newline at end of file
diff --git a/vendor/assets/javascripts/pdf.worker.min.js b/vendor/assets/javascripts/pdf.worker.min.js
deleted file mode 100755
index 3503a8f46ca..00000000000
--- a/vendor/assets/javascripts/pdf.worker.min.js
+++ /dev/null
@@ -1,19 +0,0 @@
-!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/assets/javascripts/visual_review_toolbar.js b/vendor/assets/javascripts/visual_review_toolbar.js
new file mode 100644
index 00000000000..12a3a4c9672
--- /dev/null
+++ b/vendor/assets/javascripts/visual_review_toolbar.js
@@ -0,0 +1,377 @@
+///////////////////////////////////////////////
+/////////////////// STYLES ////////////////////
+///////////////////////////////////////////////
+
+// this style must be applied inline
+const buttonClearStyles = `
+ -webkit-appearance: none;
+`;
+
+///////////////////////////////////////////////
+/////////////////// STATE ////////////////////
+///////////////////////////////////////////////
+const data = {};
+
+///////////////////////////////////////////////
+///////////////// COMPONENTS //////////////////
+///////////////////////////////////////////////
+const note = `
+ <p id='gitlab-validation-note' class='gitlab-message'></p>
+`;
+
+const comment = `
+ <div>
+ <textarea id='gitlab-comment' name='gitlab-comment' rows='3' placeholder='Enter your feedback or idea' class='gitlab-input'></textarea>
+ ${note}
+ <p class='gitlab-metadata-note'>Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
+ </div>
+ <div class='gitlab-button-wrapper''>
+ <button class='gitlab-button gitlab-button-secondary' style='${buttonClearStyles}' type='button' id='gitlab-logout-button'> Logout </button>
+ <button class='gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-comment-button'> Send feedback </button>
+ </div>
+`;
+
+const commentIcon = `
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
+`;
+
+const compressIcon = `
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
+`;
+
+const collapseButton = `
+ <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
+`;
+
+const form = content => `
+ <div id='gitlab-form-wrapper'>
+ ${content}
+ </div>
+`;
+
+const login = `
+ <div>
+ <label for='gitlab-token' class='gitlab-label'>Enter your <a class='gitlab-link' href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label>
+ <input class='gitlab-input' type='password' id='gitlab-token' name='gitlab-token'>
+ ${note}
+ </div>
+ <div class='gitlab-checkbox-wrapper'>
+ <input type="checkbox" id="remember_token" name="remember_token" value="remember">
+ <label for="remember_token" class='gitlab-checkbox-label'>Remember me</label>
+ </div>
+ <div class='gitlab-button-wrapper'>
+ <button class='gitlab-button-wide gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-login'> Submit </button>
+ </div>
+`;
+
+///////////////////////////////////////////////
+//////////////// INTERACTIONS /////////////////
+///////////////////////////////////////////////
+
+// from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator
+function getBrowserId(sUsrAg) {
+ var aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'],
+ nIdx = aKeys.length - 1;
+
+ for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx--);
+ return aKeys[nIdx];
+}
+
+function addCommentForm() {
+ const formWrapper = document.getElementById('gitlab-form-wrapper');
+ formWrapper.innerHTML = comment;
+}
+
+function addLoginForm() {
+ const formWrapper = document.getElementById('gitlab-form-wrapper');
+ formWrapper.innerHTML = login;
+}
+
+function authorizeUser() {
+ // Clear any old errors
+ clearNote('gitlab-token');
+
+ const token = document.getElementById('gitlab-token').value;
+ const rememberMe = document.getElementById('remember_token').checked;
+
+ if (!token) {
+ postError('Please enter your token.', 'gitlab-token');
+ return;
+ }
+
+ if (rememberMe) {
+ storeToken(token);
+ }
+
+ authSuccess(token);
+ return;
+}
+
+function authSuccess(token) {
+ data.token = token;
+ addCommentForm();
+}
+
+function clearNote(inputId) {
+ const note = document.getElementById('gitlab-validation-note');
+ note.innerText = '';
+ note.style.color = '';
+
+ if (inputId) {
+ const field = document.getElementById(inputId);
+ field.style.borderColor = '';
+ }
+}
+
+function confirmAndClear(mergeRequestId) {
+ const commentButton = document.getElementById('gitlab-comment-button');
+ const note = document.getElementById('gitlab-validation-note');
+
+ commentButton.innerText = 'Feedback sent';
+ note.innerText = `Your comment was successfully posted to merge request #${mergeRequestId}`;
+
+ setTimeout(resetCommentButton, 1000);
+}
+
+function getInitialState() {
+ const { localStorage } = window;
+
+ try {
+ let token = localStorage.getItem('token');
+
+ if (token) {
+ data.token = token;
+ return comment;
+ }
+
+ return login;
+ } catch (err) {
+ return login;
+ }
+}
+
+function getProjectDetails() {
+ const {
+ innerWidth,
+ innerHeight,
+ location: { href },
+ navigator: { platform, userAgent },
+ } = window;
+ const browser = getBrowserId(userAgent);
+
+ const scriptEl = document.getElementById('review-app-toolbar-script');
+ const { projectId, mergeRequestId, mrUrl } = scriptEl.dataset;
+
+ return {
+ href,
+ platform,
+ browser,
+ userAgent,
+ innerWidth,
+ innerHeight,
+ projectId,
+ mergeRequestId,
+ mrUrl,
+ };
+}
+
+function logoutUser() {
+ const { localStorage } = window;
+
+ // All the browsers we support have localStorage, so let's silently fail
+ // and go on with the rest of the functionality.
+ try {
+ localStorage.removeItem('token');
+ } catch (err) {
+ return;
+ }
+
+ addLoginForm();
+}
+
+function postComment({
+ href,
+ platform,
+ browser,
+ userAgent,
+ innerWidth,
+ innerHeight,
+ projectId,
+ mergeRequestId,
+ mrUrl,
+}) {
+ // Clear any old errors
+ clearNote('gitlab-comment');
+
+ setInProgressState();
+
+ const commentText = document.getElementById('gitlab-comment').value.trim();
+
+ if (!commentText) {
+ postError('Your comment appears to be empty.', 'gitlab-comment');
+ resetCommentBox();
+ return;
+ }
+
+ const detailText = `
+ \n
+<details>
+ <summary>Metadata</summary>
+ Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
+ <br /><br />
+ <em>User agent: ${userAgent}</em>
+</details>
+ `;
+
+ const url = `
+ ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
+
+ const body = `${commentText} ${detailText}`;
+
+ fetch(url, {
+ method: 'POST',
+ headers: {
+ 'PRIVATE-TOKEN': data.token,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ body }),
+ })
+ .then(response => {
+ if (response.ok) {
+ confirmAndClear(mergeRequestId);
+ return;
+ }
+
+ throw new Error(`${response.status}: ${response.statusText}`);
+ })
+ .catch(err => {
+ postError(
+ `The feedback was not sent successfully. Please try again. Error: ${err.message}`,
+ 'gitlab-comment',
+ );
+ resetCommentBox();
+ });
+}
+
+function postError(message, inputId) {
+ const note = document.getElementById('gitlab-validation-note');
+ const field = document.getElementById(inputId);
+ field.style.borderColor = '#db3b21';
+ note.style.color = '#db3b21';
+ note.innerText = message;
+}
+
+function resetCommentBox() {
+ const commentBox = document.getElementById('gitlab-comment');
+ const commentButton = document.getElementById('gitlab-comment-button');
+
+ commentButton.innerText = 'Send feedback';
+ commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
+ commentButton.style.opacity = 1;
+
+ commentBox.style.pointerEvents = 'auto';
+ commentBox.style.color = 'rgba(0, 0, 0, 1)';
+}
+
+function resetCommentButton() {
+ const commentBox = document.getElementById('gitlab-comment');
+ const note = document.getElementById('gitlab-validation-note');
+
+ commentBox.value = '';
+ note.innerText = '';
+ resetCommentBox();
+}
+
+function setInProgressState() {
+ const commentButton = document.getElementById('gitlab-comment-button');
+ const commentBox = document.getElementById('gitlab-comment');
+
+ commentButton.innerText = 'Sending feedback';
+ commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
+ commentButton.style.opacity = 0.5;
+ commentBox.style.color = 'rgba(223, 223, 223, 0.5)';
+ commentBox.style.pointerEvents = 'none';
+}
+
+function storeToken(token) {
+ const { localStorage } = window;
+
+ // All the browsers we support have localStorage, so let's silently fail
+ // and go on with the rest of the functionality.
+ try {
+ localStorage.setItem('token', token);
+ } catch (err) {
+ return;
+ }
+}
+
+function toggleForm() {
+ const container = document.getElementById('gitlab-review-container');
+ const collapseButton = document.getElementById('gitlab-collapse');
+ const form = document.getElementById('gitlab-form-wrapper');
+ const OPEN = 'open';
+ const CLOSED = 'closed';
+
+ const stateVals = {
+ [OPEN]: {
+ buttonClasses: ['gitlab-collapse-closed', 'gitlab-collapse-open'],
+ containerClasses: ['gitlab-closed-wrapper', 'gitlab-open-wrapper'],
+ icon: compressIcon,
+ display: 'flex',
+ backgroundColor: 'rgba(255, 255, 255, 1)',
+ },
+ [CLOSED]: {
+ buttonClasses: ['gitlab-collapse-open', 'gitlab-collapse-closed'],
+ containerClasses: ['gitlab-open-wrapper', 'gitlab-closed-wrapper'],
+ icon: commentIcon,
+ display: 'none',
+ backgroundColor: 'rgba(255, 255, 255, 0)',
+ },
+ };
+
+ const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
+
+ container.classList.replace(...stateVals[nextState].containerClasses);
+ container.style.backgroundColor = stateVals[nextState].backgroundColor;
+ form.style.display = stateVals[nextState].display;
+ collapseButton.classList.replace(...stateVals[nextState].buttonClasses);
+ collapseButton.innerHTML = stateVals[nextState].icon;
+}
+
+///////////////////////////////////////////////
+///////////////// INJECTION //////////////////
+///////////////////////////////////////////////
+
+function noop() {}
+
+const eventLookup = ({ target: { id } }) => {
+ switch (id) {
+ case 'gitlab-collapse':
+ return toggleForm;
+ case 'gitlab-comment-button':
+ const projectDetails = getProjectDetails();
+ return postComment.bind(null, projectDetails);
+ case 'gitlab-login':
+ return authorizeUser;
+ case 'gitlab-logout-button':
+ return logoutUser;
+ default:
+ return noop;
+ }
+};
+
+window.addEventListener('load', () => {
+ const content = getInitialState();
+ const container = document.createElement('div');
+
+ container.setAttribute('id', 'gitlab-review-container');
+ container.insertAdjacentHTML('beforeend', collapseButton);
+ container.insertAdjacentHTML('beforeend', form(content));
+
+ document.body.insertBefore(container, document.body.firstChild);
+
+ document.getElementById('gitlab-review-container').addEventListener('click', event => {
+ eventLookup(event)();
+ });
+
+});
diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml
index 781d6e3042f..0fbf36b39cc 100644
--- a/vendor/jupyter/values.yaml
+++ b/vendor/jupyter/values.yaml
@@ -4,19 +4,53 @@ rbac:
hub:
extraEnv:
JUPYTER_ENABLE_LAB: 1
- SINGLEUSER_IMAGE: 'registry.gitlab.com/gitlab-org/jupyterhub-user-image:latest'
extraConfig: |
c.KubeSpawner.cmd = ['jupyter-labhub']
+ c.GitLabOAuthenticator.scope = ['api read_repository write_repository']
+
+ async def add_auth_env(spawner):
+ '''
+ We set user's id, login and access token on single user image to
+ enable repository integration for JupyterHub.
+ See: https://gitlab.com/gitlab-org/gitlab-ce/issues/47138#note_154294790
+ '''
+ auth_state = await spawner.user.get_auth_state()
+
+ if not auth_state:
+ spawner.log.warning("No auth state for %s", spawner.user)
+ return
+
+ spawner.environment['GITLAB_ACCESS_TOKEN'] = auth_state['access_token']
+ spawner.environment['GITLAB_USER_LOGIN'] = auth_state['gitlab_user']['username']
+ spawner.environment['GITLAB_USER_ID'] = str(auth_state['gitlab_user']['id'])
+ spawner.environment['GITLAB_USER_EMAIL'] = auth_state['gitlab_user']['email']
+ spawner.environment['GITLAB_USER_NAME'] = auth_state['gitlab_user']['name']
+
+ c.KubeSpawner.pre_spawn_hook = add_auth_env
auth:
type: gitlab
+ state:
+ enabled: true
singleuser:
defaultUrl: "/lab"
+ image:
+ name: registry.gitlab.com/gitlab-org/jupyterhub-user-image
+ tag: latest
lifecycleHooks:
postStart:
exec:
- command: ["sh", "-c", "git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true"]
+ command:
+ - "sh"
+ - "-c"
+ - >
+ git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true;
+ echo "https://${GITLAB_USER_LOGIN}:${GITLAB_ACCESS_TOKEN}@${GITLAB_HOST}" > ~/.git-credentials;
+ git config --global credential.helper store;
+ git config --global user.email "${GITLAB_USER_EMAIL}";
+ git config --global user.name "${GITLAB_USER_NAME}";
+ jupyter serverextension enable --py jupyterlab_git
ingress:
enabled: true
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index ed79ec5bac3..0c52cb5a947 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -520,6 +520,7 @@ hashie-forbidden_attributes,0.1.1,MIT
he,1.1.1,MIT
health_check,2.6.0,MIT
highlight.js,9.13.1,New BSD
+hipchat,1.5.2,MIT
hmac-drbg,1.0.1,MIT
hoopy,0.1.4,MIT
html-pipeline,2.8.4,MIT
@@ -606,7 +607,6 @@ iterall,1.2.2,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
jquery,3.3.1,MIT
-jquery-atwho-rails,1.3.2,MIT
jquery-ujs,1.2.2,MIT
jquery.waitforimages,2.2.0,MIT
js-beautify,1.8.8,MIT
diff --git a/vendor/project_templates/android.tar.gz b/vendor/project_templates/android.tar.gz
new file mode 100644
index 00000000000..277aedaa1ca
--- /dev/null
+++ b/vendor/project_templates/android.tar.gz
Binary files differ
diff --git a/vendor/project_templates/dotnetcore.tar.gz b/vendor/project_templates/dotnetcore.tar.gz
index b5dae407f83..078ea91bbe4 100644
--- a/vendor/project_templates/dotnetcore.tar.gz
+++ b/vendor/project_templates/dotnetcore.tar.gz
Binary files differ
diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz
index fb357639a69..619e8a69695 100644
--- a/vendor/project_templates/express.tar.gz
+++ b/vendor/project_templates/express.tar.gz
Binary files differ
diff --git a/vendor/project_templates/gitbook.tar.gz b/vendor/project_templates/gitbook.tar.gz
index 73062fca038..07037a83db6 100644
--- a/vendor/project_templates/gitbook.tar.gz
+++ b/vendor/project_templates/gitbook.tar.gz
Binary files differ
diff --git a/vendor/project_templates/gomicro.tar.gz b/vendor/project_templates/gomicro.tar.gz
new file mode 100644
index 00000000000..c7d8687fdd8
--- /dev/null
+++ b/vendor/project_templates/gomicro.tar.gz
Binary files differ
diff --git a/vendor/project_templates/hexo.tar.gz b/vendor/project_templates/hexo.tar.gz
index b32c4945366..033f363b8df 100644
--- a/vendor/project_templates/hexo.tar.gz
+++ b/vendor/project_templates/hexo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/hugo.tar.gz b/vendor/project_templates/hugo.tar.gz
index 4bdb03f5b2f..f479ea12900 100644
--- a/vendor/project_templates/hugo.tar.gz
+++ b/vendor/project_templates/hugo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/iosswift.tar.gz b/vendor/project_templates/iosswift.tar.gz
new file mode 100644
index 00000000000..76f32a3a681
--- /dev/null
+++ b/vendor/project_templates/iosswift.tar.gz
Binary files differ
diff --git a/vendor/project_templates/jekyll.tar.gz b/vendor/project_templates/jekyll.tar.gz
index ab61ddd03ea..c323ce6fac6 100644
--- a/vendor/project_templates/jekyll.tar.gz
+++ b/vendor/project_templates/jekyll.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfgitbook.tar.gz b/vendor/project_templates/nfgitbook.tar.gz
index e10873e1a9e..71f526ac43d 100644
--- a/vendor/project_templates/nfgitbook.tar.gz
+++ b/vendor/project_templates/nfgitbook.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfhexo.tar.gz b/vendor/project_templates/nfhexo.tar.gz
index 25632f241cc..79cc74f8d72 100644
--- a/vendor/project_templates/nfhexo.tar.gz
+++ b/vendor/project_templates/nfhexo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfhugo.tar.gz b/vendor/project_templates/nfhugo.tar.gz
index 14e6289b841..1a4aab028a8 100644
--- a/vendor/project_templates/nfhugo.tar.gz
+++ b/vendor/project_templates/nfhugo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfjekyll.tar.gz b/vendor/project_templates/nfjekyll.tar.gz
index 3b93f8661b5..56bf955afbe 100644
--- a/vendor/project_templates/nfjekyll.tar.gz
+++ b/vendor/project_templates/nfjekyll.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfplainhtml.tar.gz b/vendor/project_templates/nfplainhtml.tar.gz
index cdf5ea9fe12..3a90983bd06 100644
--- a/vendor/project_templates/nfplainhtml.tar.gz
+++ b/vendor/project_templates/nfplainhtml.tar.gz
Binary files differ
diff --git a/vendor/project_templates/plainhtml.tar.gz b/vendor/project_templates/plainhtml.tar.gz
index 6927ae74de8..1ed17ddc140 100644
--- a/vendor/project_templates/plainhtml.tar.gz
+++ b/vendor/project_templates/plainhtml.tar.gz
Binary files differ
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
index 8454d2fc03b..c1f90c3f8f7 100644
--- a/vendor/project_templates/rails.tar.gz
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ
diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz
index 55e25fdbe7c..c1198bf13b7 100644
--- a/vendor/project_templates/spring.tar.gz
+++ b/vendor/project_templates/spring.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index 3143524e331..b6d31ea08e6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,41 +2,41 @@
# yarn lockfile v1
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.0.0-beta.35":
+"@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@>=7.1.0", "@babel/core@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687"
- integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==
+"@babel/core@>=7.1.0", "@babel/core@^7.1.0", "@babel/core@^7.4.4":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a"
+ integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.2.2"
- "@babel/helpers" "^7.2.0"
- "@babel/parser" "^7.2.2"
- "@babel/template" "^7.2.2"
- "@babel/traverse" "^7.2.2"
- "@babel/types" "^7.2.2"
+ "@babel/generator" "^7.4.4"
+ "@babel/helpers" "^7.4.4"
+ "@babel/parser" "^7.4.5"
+ "@babel/template" "^7.4.4"
+ "@babel/traverse" "^7.4.5"
+ "@babel/types" "^7.4.4"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0", "@babel/generator@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.2.tgz#18c816c70962640eab42fe8cae5f3947a5c65ccc"
- integrity sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==
+"@babel/generator@^7.4.0", "@babel/generator@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
+ integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
dependencies:
- "@babel/types" "^7.2.2"
+ "@babel/types" "^7.4.4"
jsesc "^2.5.1"
- lodash "^4.17.10"
+ lodash "^4.17.11"
source-map "^0.5.0"
trim-right "^1.0.1"
@@ -55,34 +55,35 @@
"@babel/helper-explode-assignable-expression" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-call-delegate@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a"
- integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==
+"@babel/helper-call-delegate@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43"
+ integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==
dependencies:
- "@babel/helper-hoist-variables" "^7.0.0"
- "@babel/traverse" "^7.1.0"
- "@babel/types" "^7.0.0"
+ "@babel/helper-hoist-variables" "^7.4.4"
+ "@babel/traverse" "^7.4.4"
+ "@babel/types" "^7.4.4"
-"@babel/helper-create-class-features-plugin@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz#2b01a81b3adc2b1287f9ee193688ef8dc71e718f"
- integrity sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==
+"@babel/helper-create-class-features-plugin@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
+ integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-member-expression-to-functions" "^7.0.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.2.3"
+ "@babel/helper-replace-supers" "^7.4.4"
+ "@babel/helper-split-export-declaration" "^7.4.4"
-"@babel/helper-define-map@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
- integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==
+"@babel/helper-define-map@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
+ integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==
dependencies:
"@babel/helper-function-name" "^7.1.0"
- "@babel/types" "^7.0.0"
- lodash "^4.17.10"
+ "@babel/types" "^7.4.4"
+ lodash "^4.17.11"
"@babel/helper-explode-assignable-expression@^7.1.0":
version "7.1.0"
@@ -108,12 +109,12 @@
dependencies:
"@babel/types" "^7.0.0"
-"@babel/helper-hoist-variables@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88"
- integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==
+"@babel/helper-hoist-variables@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a"
+ integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==
dependencies:
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.4.4"
"@babel/helper-member-expression-to-functions@^7.0.0":
version "7.0.0"
@@ -129,17 +130,17 @@
dependencies:
"@babel/types" "^7.0.0"
-"@babel/helper-module-transforms@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787"
- integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==
+"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8"
+ integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/template" "^7.1.0"
- "@babel/types" "^7.0.0"
- lodash "^4.17.10"
+ "@babel/helper-split-export-declaration" "^7.4.4"
+ "@babel/template" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ lodash "^4.17.11"
"@babel/helper-optimise-call-expression@^7.0.0":
version "7.0.0"
@@ -153,12 +154,12 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
-"@babel/helper-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27"
- integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==
+"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2"
+ integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==
dependencies:
- lodash "^4.17.10"
+ lodash "^4.17.11"
"@babel/helper-remap-async-to-generator@^7.1.0":
version "7.1.0"
@@ -171,15 +172,15 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5"
- integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
+ integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.0.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
- "@babel/traverse" "^7.2.3"
- "@babel/types" "^7.0.0"
+ "@babel/traverse" "^7.4.4"
+ "@babel/types" "^7.4.4"
"@babel/helper-simple-access@^7.1.0":
version "7.1.0"
@@ -189,12 +190,12 @@
"@babel/template" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-split-export-declaration@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813"
- integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==
+"@babel/helper-split-export-declaration@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
+ integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
dependencies:
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.4.4"
"@babel/helper-wrap-function@^7.1.0":
version "7.1.0"
@@ -206,14 +207,14 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helpers@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.2.0.tgz#8335f3140f3144270dc63c4732a4f8b0a50b7a21"
- integrity sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==
+"@babel/helpers@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5"
+ integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==
dependencies:
- "@babel/template" "^7.1.2"
- "@babel/traverse" "^7.1.5"
- "@babel/types" "^7.2.0"
+ "@babel/template" "^7.4.4"
+ "@babel/traverse" "^7.4.4"
+ "@babel/types" "^7.4.4"
"@babel/highlight@^7.0.0":
version "7.0.0"
@@ -224,10 +225,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
- integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
+ integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -238,12 +239,12 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
-"@babel/plugin-proposal-class-properties@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd"
- integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==
+"@babel/plugin-proposal-class-properties@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
+ integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.3.0"
+ "@babel/helper-create-class-features-plugin" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-json-strings@^7.2.0":
@@ -254,10 +255,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
-"@babel/plugin-proposal-object-rest-spread@^7.3.1":
- version "7.3.1"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.1.tgz#f69fb6a1ea6a4e1c503994a91d9cf76f3c4b36e8"
- integrity sha512-Nmmv1+3LqxJu/V5jU9vJmxR/KIRWFk2qLHmbB56yRRRFhlaSuOVXscX3gUmhaKgUhzA3otOHVubbIEVYsZ0eZg==
+"@babel/plugin-proposal-object-rest-spread@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005"
+ integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -270,22 +271,22 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-"@babel/plugin-proposal-private-methods@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.3.0.tgz#da373257a66525cb76544c37ab2ce4c611568841"
- integrity sha512-j6luy/F0MX6kd71e9hz97my2tBXTa+czAz+sscJVCRmjB9e9g2D4JN+tyfcwMCXUM2afj/tYCjzNaxwWJ4SdYg==
+"@babel/plugin-proposal-private-methods@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.4.4.tgz#307b7db29d8ae2d259e7c0e6e665b1922d7ac856"
+ integrity sha512-EIV4fDVP3XwdizJ/H6308Km+d8xdLAUCAvsY8mjxhat9I3vNgssGhZuhgn/jw7IK5/91sN8PHtVGxMjeTSrSng==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.3.0"
+ "@babel/helper-create-class-features-plugin" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-proposal-unicode-property-regex@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520"
- integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==
+"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
+ integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-regex" "^7.0.0"
- regexpu-core "^4.2.0"
+ "@babel/helper-regex" "^7.4.4"
+ regexpu-core "^4.5.4"
"@babel/plugin-syntax-async-generators@^7.2.0":
version "7.2.0"
@@ -315,7 +316,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-object-rest-spread@^7.2.0":
+"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==
@@ -336,10 +337,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-async-to-generator@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff"
- integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==
+"@babel/plugin-transform-async-to-generator@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894"
+ integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -352,26 +353,26 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-block-scoping@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz#f17c49d91eedbcdf5dd50597d16f5f2f770132d4"
- integrity sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==
+"@babel/plugin-transform-block-scoping@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d"
+ integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
-"@babel/plugin-transform-classes@^7.2.0":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz#6c90542f210ee975aa2aa8c8b5af7fa73a126953"
- integrity sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==
+"@babel/plugin-transform-classes@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6"
+ integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
- "@babel/helper-define-map" "^7.1.0"
+ "@babel/helper-define-map" "^7.4.4"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.4.4"
+ "@babel/helper-split-export-declaration" "^7.4.4"
globals "^11.1.0"
"@babel/plugin-transform-computed-properties@^7.2.0":
@@ -381,21 +382,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-destructuring@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz#e75269b4b7889ec3a332cd0d0c8cff8fed0dc6f3"
- integrity sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==
+"@babel/plugin-transform-destructuring@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f"
+ integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-dotall-regex@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49"
- integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==
+"@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3"
+ integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-regex" "^7.0.0"
- regexpu-core "^4.1.3"
+ "@babel/helper-regex" "^7.4.4"
+ regexpu-core "^4.5.4"
"@babel/plugin-transform-duplicate-keys@^7.2.0":
version "7.2.0"
@@ -412,17 +413,17 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-for-of@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9"
- integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==
+"@babel/plugin-transform-for-of@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
+ integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-function-name@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a"
- integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==
+"@babel/plugin-transform-function-name@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad"
+ integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -434,6 +435,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-transform-member-expression-literals@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d"
+ integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-transform-modules-amd@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6"
@@ -442,21 +450,21 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-commonjs@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404"
- integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==
+"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e"
+ integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==
dependencies:
- "@babel/helper-module-transforms" "^7.1.0"
+ "@babel/helper-module-transforms" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
-"@babel/plugin-transform-modules-systemjs@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz#912bfe9e5ff982924c81d0937c92d24994bb9068"
- integrity sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==
+"@babel/plugin-transform-modules-systemjs@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405"
+ integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==
dependencies:
- "@babel/helper-hoist-variables" "^7.0.0"
+ "@babel/helper-hoist-variables" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-modules-umd@^7.2.0":
@@ -467,17 +475,17 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50"
- integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.4.tgz#5611d96d987dfc4a3a81c4383bb173361037d68d"
+ integrity sha512-Ki+Y9nXBlKfhD+LXaRS7v95TtTGYRAf9Y1rTDiE75zf8YQz4GDaWRXosMfJBXxnk88mGFjWdCRIeqDbon7spYA==
dependencies:
regexp-tree "^0.1.0"
-"@babel/plugin-transform-new-target@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a"
- integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==
+"@babel/plugin-transform-new-target@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5"
+ integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -489,21 +497,35 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-replace-supers" "^7.1.0"
-"@babel/plugin-transform-parameters@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz#0d5ad15dc805e2ea866df4dd6682bfe76d1408c2"
- integrity sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==
+"@babel/plugin-transform-parameters@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16"
+ integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==
dependencies:
- "@babel/helper-call-delegate" "^7.1.0"
+ "@babel/helper-call-delegate" "^7.4.4"
"@babel/helper-get-function-arity" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-regenerator@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1"
- integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==
+"@babel/plugin-transform-property-literals@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905"
+ integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-regenerator@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.4.tgz#5b4da4df79391895fca9e28f99e87e22cfc02072"
+ integrity sha512-Zz3w+pX1SI0KMIiqshFZkwnVGUhDZzpX2vtPzfJBKQQq8WsP/Xy9DNdELWivxcKOCX/Pywge4SiEaPaLtoDT4g==
dependencies:
- regenerator-transform "^0.13.3"
+ regenerator-transform "^0.13.4"
+
+"@babel/plugin-transform-reserved-words@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634"
+ integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-shorthand-properties@^7.2.0":
version "7.2.0"
@@ -527,10 +549,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-regex" "^7.0.0"
-"@babel/plugin-transform-template-literals@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b"
- integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==
+"@babel/plugin-transform-template-literals@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0"
+ integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -542,128 +564,147 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-unicode-regex@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b"
- integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==
+"@babel/plugin-transform-unicode-regex@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f"
+ integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-regex" "^7.0.0"
- regexpu-core "^4.1.3"
+ "@babel/helper-regex" "^7.4.4"
+ regexpu-core "^4.5.4"
-"@babel/preset-env@^7.3.1":
- version "7.3.1"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
- integrity sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==
+"@babel/preset-env@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.4.tgz#b6f6825bfb27b3e1394ca3de4f926482722c1d6f"
+ integrity sha512-FU1H+ACWqZZqfw1x2G1tgtSSYSfxJLkpaUQL37CenULFARDo+h4xJoVHzRoHbK+85ViLciuI7ME4WTIhFRBBlw==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-async-generator-functions" "^7.2.0"
"@babel/plugin-proposal-json-strings" "^7.2.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.3.1"
+ "@babel/plugin-proposal-object-rest-spread" "^7.4.4"
"@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
- "@babel/plugin-proposal-unicode-property-regex" "^7.2.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
"@babel/plugin-syntax-async-generators" "^7.2.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-transform-arrow-functions" "^7.2.0"
- "@babel/plugin-transform-async-to-generator" "^7.2.0"
+ "@babel/plugin-transform-async-to-generator" "^7.4.4"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
- "@babel/plugin-transform-block-scoping" "^7.2.0"
- "@babel/plugin-transform-classes" "^7.2.0"
+ "@babel/plugin-transform-block-scoping" "^7.4.4"
+ "@babel/plugin-transform-classes" "^7.4.4"
"@babel/plugin-transform-computed-properties" "^7.2.0"
- "@babel/plugin-transform-destructuring" "^7.2.0"
- "@babel/plugin-transform-dotall-regex" "^7.2.0"
+ "@babel/plugin-transform-destructuring" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
"@babel/plugin-transform-duplicate-keys" "^7.2.0"
"@babel/plugin-transform-exponentiation-operator" "^7.2.0"
- "@babel/plugin-transform-for-of" "^7.2.0"
- "@babel/plugin-transform-function-name" "^7.2.0"
+ "@babel/plugin-transform-for-of" "^7.4.4"
+ "@babel/plugin-transform-function-name" "^7.4.4"
"@babel/plugin-transform-literals" "^7.2.0"
+ "@babel/plugin-transform-member-expression-literals" "^7.2.0"
"@babel/plugin-transform-modules-amd" "^7.2.0"
- "@babel/plugin-transform-modules-commonjs" "^7.2.0"
- "@babel/plugin-transform-modules-systemjs" "^7.2.0"
+ "@babel/plugin-transform-modules-commonjs" "^7.4.4"
+ "@babel/plugin-transform-modules-systemjs" "^7.4.4"
"@babel/plugin-transform-modules-umd" "^7.2.0"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0"
- "@babel/plugin-transform-new-target" "^7.0.0"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.4"
+ "@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.2.0"
- "@babel/plugin-transform-parameters" "^7.2.0"
- "@babel/plugin-transform-regenerator" "^7.0.0"
+ "@babel/plugin-transform-parameters" "^7.4.4"
+ "@babel/plugin-transform-property-literals" "^7.2.0"
+ "@babel/plugin-transform-regenerator" "^7.4.4"
+ "@babel/plugin-transform-reserved-words" "^7.2.0"
"@babel/plugin-transform-shorthand-properties" "^7.2.0"
"@babel/plugin-transform-spread" "^7.2.0"
"@babel/plugin-transform-sticky-regex" "^7.2.0"
- "@babel/plugin-transform-template-literals" "^7.2.0"
+ "@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
- "@babel/plugin-transform-unicode-regex" "^7.2.0"
- browserslist "^4.3.4"
+ "@babel/plugin-transform-unicode-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ browserslist "^4.5.2"
+ core-js-compat "^3.0.0"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
- semver "^5.3.0"
+ semver "^5.5.0"
-"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
- integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
+"@babel/standalone@^7.0.0":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.3.4.tgz#b622c1e522acef91b2a14f22bdcdd4f935a1a474"
+ integrity sha512-4L9c5i4WlGqbrjOVX0Yp8TIR5cEiw1/tPYYZENW/iuO2uI6viY38U7zALidzNfGdZIwNc+A/AWqMEWKeScWkBg==
+
+"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237"
+ integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.2.2"
- "@babel/types" "^7.2.2"
+ "@babel/parser" "^7.4.4"
+ "@babel/types" "^7.4.4"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
- integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
+ integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.2.2"
+ "@babel/generator" "^7.4.4"
"@babel/helper-function-name" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.2.3"
- "@babel/types" "^7.2.2"
+ "@babel/helper-split-export-declaration" "^7.4.4"
+ "@babel/parser" "^7.4.5"
+ "@babel/types" "^7.4.4"
debug "^4.1.0"
globals "^11.1.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
- integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
+"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
+ integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
dependencies:
esutils "^2.0.2"
- lodash "^4.17.10"
+ lodash "^4.17.11"
to-fast-properties "^2.0.0"
-"@gitlab/csslab@^1.8.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.8.0.tgz#54a2457fdc80f006665f0e578a5532780954ccfa"
- integrity sha512-RZylRElufH1kwsBQlIDaVcrcXMyD5IEGrU6ABUd8W3LG8/F9jJ4Y3Ys7EPTpK/qFJyx86AutTtFGRxRNlMx85w==
+"@cnakazawa/watch@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
+ integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==
dependencies:
- bootstrap "4.1.3"
+ exec-sh "^0.3.2"
+ minimist "^1.2.0"
-"@gitlab/eslint-config@^1.4.0":
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.4.0.tgz#2e59e55a7cd024e3a450d2a896060ec4d763a5dc"
- integrity sha512-nkecTWRNS/KD9q5lHFSc3J6zO/g1/OV9DaKiay+0nLjnGO9jQVRArRIYpnzgbUz2p15jOMVToVafW0YbbHZkwg==
+"@gitlab/csslab@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
+ integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg==
+ dependencies:
+ bootstrap "^4.1.3"
+
+"@gitlab/eslint-config@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.6.0.tgz#1fd247d6ab477d53d4c330e05f007e3afa303689"
+ integrity sha512-EZffCwsRZmRWPP6N3wp20EJDVGYLG1v43/W7fF/gYQpUjcRclC8ks/jEv8UppasSDlanDmkh1bLWoE9CelSyyw==
dependencies:
babel-eslint "^10.0.1"
eslint-config-airbnb-base "^13.1.0"
eslint-config-prettier "^3.3.0"
eslint-plugin-filenames "^1.3.2"
- eslint-plugin-import "^2.14.0"
- eslint-plugin-promise "^4.0.1"
+ eslint-plugin-import "^2.16.0"
+ eslint-plugin-promise "^4.1.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.54.0":
- version "1.54.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf"
- integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ==
+"@gitlab/svgs@^1.63.0":
+ version "1.63.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.63.0.tgz#9dd544026d203e4ce6efed72b05db68f710c4d49"
+ integrity sha512-YztrReFTg31B7v5wtUC5j15KHNcMebtW+kACytEU42XomMaIwk4USIbygqWlq0VRHA2VHJrHApfJHIjxiCCQcA==
-"@gitlab/ui@^2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.0.4.tgz#ba86f6e5868ef7bc7f504cef9ca504c2d2f6bffd"
- integrity sha512-dJ+KKpeqIAPYZtYZeciXhC/whNiGPVRjp5IgjQRddh3zsreqmfwQq58nSH7HepAAIepaqTe0UFuzBgrSWvVM6w==
+"@gitlab/ui@^3.11.0":
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.11.0.tgz#7bba82c893f47abbfe7995281dc0ce95290dcc4e"
+ integrity sha512-55Qxyj2wZILznZJUTUxY1SUuw028IgmP6ZyLd5XF3xk91HWSyq5/zrlr/qRTFGL1cABhxoBLScmXsnOc2CIO0w==
dependencies:
- babel-standalone "^6.26.0"
+ "@babel/standalone" "^7.0.0"
+ "@gitlab/vue-toasted" "^1.2.1"
bootstrap-vue "^2.0.0-rc.11"
copy-to-clipboard "^3.0.8"
echarts "^4.2.0-rc.2"
@@ -671,9 +712,160 @@
js-beautify "^1.8.8"
lodash "^4.17.11"
url-search-params-polyfill "^5.0.0"
- vue "^2.5.21"
+ vue "^2.6.10"
vue-loader "^15.4.2"
+"@gitlab/vue-toasted@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263"
+ integrity sha512-ve2PLxKqrwNpsd+4bV5zGJT5+H5N/VJBZoFS2Vp1mH5cUDBYIHTzDmbS6AbBGUDh0F3TxmFMiqfXfpO/1VjBNQ==
+
+"@jest/console@^24.7.1":
+ version "24.7.1"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545"
+ integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==
+ dependencies:
+ "@jest/source-map" "^24.3.0"
+ chalk "^2.0.1"
+ slash "^2.0.0"
+
+"@jest/core@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b"
+ integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/reporters" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.1"
+ exit "^0.1.2"
+ graceful-fs "^4.1.15"
+ jest-changed-files "^24.8.0"
+ jest-config "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve-dependencies "^24.8.0"
+ jest-runner "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ jest-watcher "^24.8.0"
+ micromatch "^3.1.10"
+ p-each-series "^1.0.0"
+ pirates "^4.0.1"
+ realpath-native "^1.1.0"
+ rimraf "^2.5.4"
+ strip-ansi "^5.0.0"
+
+"@jest/environment@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac"
+ integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==
+ dependencies:
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+
+"@jest/fake-timers@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1"
+ integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==
+ dependencies:
+ "@jest/types" "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-mock "^24.8.0"
+
+"@jest/reporters@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729"
+ integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==
+ dependencies:
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ chalk "^2.0.1"
+ exit "^0.1.2"
+ glob "^7.1.2"
+ istanbul-lib-coverage "^2.0.2"
+ istanbul-lib-instrument "^3.0.1"
+ istanbul-lib-report "^2.0.4"
+ istanbul-lib-source-maps "^3.0.1"
+ istanbul-reports "^2.1.1"
+ jest-haste-map "^24.8.0"
+ jest-resolve "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
+ node-notifier "^5.2.1"
+ slash "^2.0.0"
+ source-map "^0.6.0"
+ string-length "^2.0.0"
+
+"@jest/source-map@^24.3.0":
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28"
+ integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==
+ dependencies:
+ callsites "^3.0.0"
+ graceful-fs "^4.1.15"
+ source-map "^0.6.0"
+
+"@jest/test-result@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3"
+ integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/types" "^24.8.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+
+"@jest/test-sequencer@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b"
+ integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==
+ dependencies:
+ "@jest/test-result" "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-runner "^24.8.0"
+ jest-runtime "^24.8.0"
+
+"@jest/transform@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5"
+ integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==
+ dependencies:
+ "@babel/core" "^7.1.0"
+ "@jest/types" "^24.8.0"
+ babel-plugin-istanbul "^5.1.0"
+ chalk "^2.0.1"
+ convert-source-map "^1.4.0"
+ fast-json-stable-stringify "^2.0.0"
+ graceful-fs "^4.1.15"
+ jest-haste-map "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-util "^24.8.0"
+ micromatch "^3.1.10"
+ realpath-native "^1.1.0"
+ slash "^2.0.0"
+ source-map "^0.6.1"
+ write-file-atomic "2.4.1"
+
+"@jest/types@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
+ integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^12.0.9"
+
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -697,25 +889,73 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==
-"@types/async@2.0.50":
- version "2.0.50"
- resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.50.tgz#117540e026d64e1846093abbd5adc7e27fda7bcb"
- integrity sha512-VMhZMMQgV1zsR+lX/0IBfAk+8Eb7dPVMWiQGFAt3qjo5x7Ml6b77jUo0e1C3ToD+XRDXqtrfw+6AB0uUsPEr3Q==
+"@types/babel__core@^7.1.0":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
+ integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc"
+ integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
+ integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.6.tgz#328dd1a8fc4cfe3c8458be9477b219ea158fd7b2"
+ integrity sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==
+ dependencies:
+ "@babel/types" "^7.3.0"
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
-"@types/glob@^5":
- version "5.0.35"
- resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a"
- integrity sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==
+"@types/glob@5 - 7":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
+ integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
"@types/node" "*"
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
+ integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+
+"@types/istanbul-lib-report@*":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c"
+ integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
+ integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+ "@types/istanbul-lib-report" "*"
+
"@types/jquery@^2.0.40":
version "2.0.48"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef"
@@ -741,15 +981,10 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
-"@types/strip-bom@^3.0.0":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
- integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=
-
-"@types/strip-json-comments@0.0.30":
- version "0.0.30"
- resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
- integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
+"@types/stack-utils@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
+ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/tapable@*":
version "1.0.4"
@@ -796,25 +1031,30 @@
"@types/uglify-js" "*"
source-map "^0.6.0"
+"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
+ version "12.0.12"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
+ integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
+
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
-"@vue/component-compiler-utils@^2.0.0":
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz#bbbb7ed38a9a8a7c93abe7ef2e54a90a04b631b4"
- integrity sha512-pS4zlcdD7BvedyB+IfiTfrbi6C977UMIfulSk8r6uL0BU46ZE2+fUj/zbSNSfVxeaj9ElmnSni5OMwF9np+b+w==
+"@vue/component-compiler-utils@^2.4.0", "@vue/component-compiler-utils@^2.5.1":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
+ integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
lru-cache "^4.1.2"
merge-source-map "^1.1.0"
- postcss "^6.0.20"
- postcss-selector-parser "^3.1.1"
- prettier "1.13.7"
- source-map "^0.5.6"
- vue-template-es2015-compiler "^1.6.0"
+ postcss "^7.0.14"
+ postcss-selector-parser "^5.0.0"
+ prettier "1.16.3"
+ source-map "~0.6.1"
+ vue-template-es2015-compiler "^1.9.0"
"@vue/test-utils@^1.0.0-beta.25":
version "1.0.0-beta.25"
@@ -1080,9 +1320,9 @@ ansi-escapes@^1.1.0:
integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
ansi-escapes@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
- integrity sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
+ integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
ansi-html@0.0.7:
version "0.0.7"
@@ -1099,10 +1339,10 @@ ansi-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
-ansi-regex@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
- integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
+ansi-regex@^4.0.0, ansi-regex@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+ integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^2.2.1:
version "2.2.1"
@@ -1124,109 +1364,101 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
-apollo-boost@^0.1.20:
- version "0.1.20"
- resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.1.20.tgz#cc3e418ebd2bea857656685d32a7a20443493363"
- integrity sha512-n2MiEY5IGpD/cy0RH+pM9vbmobM/JZ5qz38XQAUA41FxxMPlLFQxf0IUMm0tijLOJvJJBub3pDt+Of4TVPBCqA==
- dependencies:
- apollo-cache "^1.1.20"
- apollo-cache-inmemory "^1.3.9"
- apollo-client "^2.4.5"
- apollo-link "^1.0.6"
- apollo-link-error "^1.0.3"
- apollo-link-http "^1.3.1"
- apollo-link-state "^0.4.0"
- graphql-tag "^2.4.2"
-
-apollo-cache-inmemory@^1.3.9:
- version "1.3.9"
- resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.3.9.tgz#10738ba6a04faaeeb0da21bbcc1f7c0b5902910c"
- integrity sha512-Q2k84p/OqIuMUyeWGc6XbVXXZu0erYOO+wTx9p+CnQUspnNvf7zmvFNgFnmudXzfuG1m1CSzePk6fC/M1ehOqQ==
- dependencies:
- apollo-cache "^1.1.20"
- apollo-utilities "^1.0.25"
- optimism "^0.6.6"
-
-apollo-cache@1.1.20, apollo-cache@^1.1.20:
- version "1.1.20"
- resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.20.tgz#6152cc4baf6a63e376efee79f75de4f5c84bf90e"
- integrity sha512-+Du0/4kUSuf5PjPx0+pvgMGV12ezbHA8/hubYuqRQoy/4AWb4faa61CgJNI6cKz2mhDd9m94VTNKTX11NntwkQ==
- dependencies:
- apollo-utilities "^1.0.25"
-
-apollo-client@^2.4.5:
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.5.tgz#545beda1ef60814943b5622f0feabc9f29ee9822"
- integrity sha512-nUm06EGa4TP/IY68OzmC3lTD32TqkjLOQdb69uYo+lHl8NnwebtrAw3qFtsQtTEz6ueBp/Z/HasNZng4jwafVQ==
+apollo-cache-inmemory@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953"
+ integrity sha512-D3bdpPmWfaKQkWy8lfwUg+K8OBITo3sx0BHLs1B/9vIdOIZ7JNCKq3EUcAgAfInomJUdN0QG1yOfi8M8hxkN1g==
+ dependencies:
+ apollo-cache "^1.2.1"
+ apollo-utilities "^1.2.1"
+ optimism "^0.6.9"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
+
+apollo-cache@1.2.1, apollo-cache@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.2.1.tgz#aae71eb4a11f1f7322adc343f84b1a39b0693644"
+ integrity sha512-nzFmep/oKlbzUuDyz6fS6aYhRmfpcHWqNkkA9Bbxwk18RD6LXC4eZkuE0gXRX0IibVBHNjYVK+Szi0Yied4SpQ==
+ dependencies:
+ apollo-utilities "^1.2.1"
+ tslib "^1.9.3"
+
+apollo-client@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.5.1.tgz#36126ed1d32edd79c3713c6684546a3bea80e6d1"
+ integrity sha512-MNcQKiqLHdGmNJ0rZ0NXaHrToXapJgS/5kPk0FygXt+/FmDCdzqcujI7OPxEC6e9Yw5S/8dIvOXcRNuOMElHkA==
dependencies:
"@types/zen-observable" "^0.8.0"
- apollo-cache "1.1.20"
+ apollo-cache "1.2.1"
apollo-link "^1.0.0"
apollo-link-dedup "^1.0.0"
- apollo-utilities "1.0.25"
+ apollo-utilities "1.2.1"
symbol-observable "^1.0.2"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
zen-observable "^0.8.0"
- optionalDependencies:
- "@types/async" "2.0.50"
-apollo-link-dedup@^1.0.0:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae"
- integrity sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w==
+apollo-link-batch-http@^1.2.11:
+ version "1.2.11"
+ resolved "https://registry.yarnpkg.com/apollo-link-batch-http/-/apollo-link-batch-http-1.2.11.tgz#ae42dbcc02820658e1e267d05bf2aae7ac208088"
+ integrity sha512-f+KEdbP51I3AeEaBDW2lKS3eaPK/1IXaTM9F2moj02s1hgC/TzeUORRuUeOExW8ggXveW1Jzp6aYMJ2SQkZJyA==
dependencies:
- apollo-link "^1.2.3"
+ apollo-link "^1.2.11"
+ apollo-link-batch "^1.1.12"
+ apollo-link-http-common "^0.2.13"
+ tslib "^1.9.3"
-apollo-link-error@^1.0.3:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.1.tgz#69d7124d4dc11ce60f505c940f05d4f1aa0945fb"
- integrity sha512-/yPcaQWcBdB94vpJ4FsiCJt1dAGGRm+6Tsj3wKwP+72taBH+UsGRQQZk7U/1cpZwl1yqhHZn+ZNhVOebpPcIlA==
+apollo-link-batch@^1.1.12:
+ version "1.1.12"
+ resolved "https://registry.yarnpkg.com/apollo-link-batch/-/apollo-link-batch-1.1.12.tgz#64eb231082f182b0395ef7ab903600627f6c7fe8"
+ integrity sha512-6NqLiB9tEGxRiyhtnX/7CPHkmFG0IXfEP7pC5kirhjV+4KxqBaWvJnJGKpGp7Owgdph7KJlV+9+niOKEkcwreg==
dependencies:
- apollo-link "^1.2.3"
+ apollo-link "^1.2.11"
+ tslib "^1.9.3"
-apollo-link-http-common@^0.2.5:
- version "0.2.5"
- resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.5.tgz#d094beb7971523203359bf830bfbfa7b4e7c30ed"
- integrity sha512-6FV1wr5AqAyJ64Em1dq5hhGgiyxZE383VJQmhIoDVc3MyNcFL92TkhxREOs4rnH2a9X2iJMko7nodHSGLC6d8w==
+apollo-link-dedup@^1.0.0:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae"
+ integrity sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w==
dependencies:
apollo-link "^1.2.3"
-apollo-link-http@^1.3.1:
- version "1.5.5"
- resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.5.tgz#7dbe851821771ad67fa29e3900c57f38cbd80da8"
- integrity sha512-C5N6N/mRwmepvtzO27dgMEU3MMtRKSqcljBkYNZmWwH11BxkUQ5imBLPM3V4QJXNE7NFuAQAB5PeUd4ligivTQ==
+apollo-link-http-common@^0.2.13, apollo-link-http-common@^0.2.8:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
+ integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
dependencies:
- apollo-link "^1.2.3"
- apollo-link-http-common "^0.2.5"
+ apollo-link "^1.2.11"
+ ts-invariant "^0.3.2"
+ tslib "^1.9.3"
-apollo-link-state@^0.4.0:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.2.tgz#ac00e9be9b0ca89eae0be6ba31fe904b80bbe2e8"
- integrity sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==
+apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.3, apollo-link@^1.2.6:
+ version "1.2.11"
+ resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
+ integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
dependencies:
- apollo-utilities "^1.0.8"
- graphql-anywhere "^4.1.0-alpha.0"
+ apollo-utilities "^1.2.1"
+ ts-invariant "^0.3.2"
+ tslib "^1.9.3"
+ zen-observable-ts "^0.8.18"
-apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d"
- integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw==
+apollo-upload-client@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-10.0.0.tgz#6cc3d0ea2aef40bc237b655f5042809cacee1859"
+ integrity sha512-N0SENiEkZXoY4nl9xxrXFcj/cL0AVkSNQ4aYXSaruCBWE0aKpK6aCe4DBmiEHrK3FAsMxZPEJxBRIWNbsXT8dw==
dependencies:
- apollo-utilities "^1.0.0"
- zen-observable-ts "^0.8.10"
+ apollo-link "^1.2.6"
+ apollo-link-http-common "^0.2.8"
+ extract-files "^5.0.0"
-apollo-utilities@1.0.25, apollo-utilities@^1.0.0, apollo-utilities@^1.0.25, apollo-utilities@^1.0.8:
- version "1.0.25"
- resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.25.tgz#899b00f5f990fb451675adf84cb3de82eb6372ea"
- integrity sha512-AXvqkhni3Ir1ffm4SA1QzXn8k8I5BBl4PVKEyak734i4jFdp+xgfUyi2VCqF64TJlFTA/B73TRDUvO2D+tKtZg==
+apollo-utilities@1.2.1, apollo-utilities@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.2.1.tgz#1c3a1ebf5607d7c8efe7636daaf58e7463b41b3c"
+ integrity sha512-Zv8Udp9XTSFiN8oyXOjf6PMHepD4yxxReLsl6dPUy5Ths7jti3nmlBzZUOxuTWRwZn0MoclqL7RQ5UEJN8MAxg==
dependencies:
fast-json-stable-stringify "^2.0.0"
-
-append-transform@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991"
- integrity sha1-126/jKlNJ24keja61EpLdKthGZE=
- dependencies:
- default-require-extensions "^1.0.0"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
append-transform@^1.0.0:
version "1.0.0"
@@ -1241,9 +1473,9 @@ aproba@^1.0.3, aproba@^1.1.1:
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
are-we-there-yet@~1.1.2:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
- integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+ integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
dependencies:
delegates "^1.0.0"
readable-stream "^2.0.6"
@@ -1255,19 +1487,12 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
-arr-diff@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
- integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=
- dependencies:
- arr-flatten "^1.0.1"
-
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
-arr-flatten@^1.0.1, arr-flatten@^1.1.0:
+arr-flatten@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
@@ -1382,6 +1607,11 @@ async-each@^1.0.0:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
+async-foreach@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+ integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
+
async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
@@ -1392,18 +1622,23 @@ async@1.x, async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-async@^2.0.0, async@^2.1.4, async@^2.5.0, async@^2.6.1:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
- integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+async@^2.0.0, async@^2.6.1:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
+ integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
dependencies:
- lodash "^4.17.10"
+ lodash "^4.17.11"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+at.js@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/at.js/-/at.js-1.5.4.tgz#8fc60cc80eadbe4874449b166a818e7ae1d784c1"
+ integrity sha512-G8mgUb/PqShPoH8AyjuxsTGvIr1o716BtQUKDM44C8qN2W615y7KGJ68MlTGamd0J0D/m28emUkzagaHTdrGZw==
+
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@@ -1460,36 +1695,6 @@ babel-code-frame@^6.26.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
-babel-core@^6.0.0, babel-core@^6.26.0:
- version "6.26.3"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
- integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
- dependencies:
- babel-code-frame "^6.26.0"
- babel-generator "^6.26.0"
- babel-helpers "^6.24.1"
- babel-messages "^6.23.0"
- babel-register "^6.26.0"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- convert-source-map "^1.5.1"
- debug "^2.6.9"
- json5 "^0.5.1"
- lodash "^4.17.4"
- minimatch "^3.0.4"
- path-is-absolute "^1.0.1"
- private "^0.1.8"
- slash "^1.0.0"
- source-map "^0.5.7"
-
-babel-core@^7.0.0-bridge:
- version "7.0.0-bridge.0"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
- integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-
babel-eslint@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
@@ -1502,35 +1707,18 @@ babel-eslint@^10.0.1:
eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
-babel-generator@^6.18.0, babel-generator@^6.26.0:
- version "6.26.1"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
- integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
- dependencies:
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.17.4"
- source-map "^0.5.7"
- trim-right "^1.0.1"
-
-babel-helpers@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
- integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=
- dependencies:
- babel-runtime "^6.22.0"
- babel-template "^6.24.1"
-
-babel-jest@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1"
- integrity sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==
+babel-jest@^24.1.0, babel-jest@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589"
+ integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==
dependencies:
- babel-plugin-istanbul "^4.1.6"
- babel-preset-jest "^23.2.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/babel__core" "^7.1.0"
+ babel-plugin-istanbul "^5.1.0"
+ babel-preset-jest "^24.6.0"
+ chalk "^2.4.2"
+ slash "^2.0.0"
babel-loader@^8.0.5:
version "8.0.5"
@@ -1542,13 +1730,6 @@ babel-loader@^8.0.5:
mkdirp "^0.5.1"
util.promisify "^1.0.0"
-babel-messages@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
- integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
- dependencies:
- babel-runtime "^6.22.0"
-
babel-plugin-dynamic-import-node@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz#c0adfb07d95f4a4495e9aaac6ec386c4d7c2524e"
@@ -1556,16 +1737,6 @@ babel-plugin-dynamic-import-node@^2.2.0:
dependencies:
object.assign "^4.1.0"
-babel-plugin-istanbul@^4.1.6:
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
- integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==
- dependencies:
- babel-plugin-syntax-object-rest-spread "^6.13.0"
- find-up "^2.1.0"
- istanbul-lib-instrument "^1.10.1"
- test-exclude "^4.2.1"
-
babel-plugin-istanbul@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz#6892f529eff65a3e2d33d87dc5888ffa2ecd4a30"
@@ -1575,39 +1746,18 @@ babel-plugin-istanbul@^5.1.0:
istanbul-lib-instrument "^3.0.0"
test-exclude "^5.0.0"
-babel-plugin-jest-hoist@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
- integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=
+babel-plugin-jest-hoist@^24.6.0:
+ version "24.6.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019"
+ integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==
+ dependencies:
+ "@types/babel__traverse" "^7.0.6"
babel-plugin-rewire@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==
-babel-plugin-syntax-object-rest-spread@^6.13.0:
- version "6.13.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
- integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-
-babel-plugin-transform-es2015-modules-commonjs@^6.26.0, babel-plugin-transform-es2015-modules-commonjs@^6.26.2:
- version "6.26.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
- integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==
- dependencies:
- babel-plugin-transform-strict-mode "^6.24.1"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-types "^6.26.0"
-
-babel-plugin-transform-strict-mode@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
- integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=
- dependencies:
- babel-runtime "^6.22.0"
- babel-types "^6.24.1"
-
babel-polyfill@6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
@@ -1617,28 +1767,15 @@ babel-polyfill@6.23.0:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
-babel-preset-jest@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
- integrity sha1-jsegOhOPABoaj7HoETZSvxpV2kY=
+babel-preset-jest@^24.6.0:
+ version "24.6.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984"
+ integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==
dependencies:
- babel-plugin-jest-hoist "^23.2.0"
- babel-plugin-syntax-object-rest-spread "^6.13.0"
-
-babel-register@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
- integrity sha1-btAhFz4vy0htestFxgCahW9kcHE=
- dependencies:
- babel-core "^6.26.0"
- babel-runtime "^6.26.0"
- core-js "^2.5.0"
- home-or-tmp "^2.0.0"
- lodash "^4.17.4"
- mkdirp "^0.5.1"
- source-map-support "^0.4.15"
+ "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
+ babel-plugin-jest-hoist "^24.6.0"
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@^6.22.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -1646,57 +1783,11 @@ babel-runtime@^6.22.0, babel-runtime@^6.26.0:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
-babel-standalone@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-standalone/-/babel-standalone-6.26.0.tgz#15fb3d35f2c456695815ebf1ed96fe7f015b6886"
- integrity sha1-Ffs9NfLEVmlYFevx7Zb+fwFbaIY=
-
-babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
- integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
- dependencies:
- babel-runtime "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- lodash "^4.17.4"
-
-babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
- integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
- dependencies:
- babel-code-frame "^6.26.0"
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- debug "^2.6.8"
- globals "^9.18.0"
- invariant "^2.2.2"
- lodash "^4.17.4"
-
-babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
- integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
- dependencies:
- babel-runtime "^6.26.0"
- esutils "^2.0.2"
- lodash "^4.17.4"
- to-fast-properties "^1.0.3"
-
babylon@7.0.0-beta.19:
version "7.0.0-beta.19"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==
-babylon@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
- integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
@@ -1789,6 +1880,13 @@ blob@0.0.4:
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
+ dependencies:
+ inherits "~2.0.0"
+
bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
@@ -1839,10 +1937,10 @@ bootstrap-vue@^2.0.0-rc.11:
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
-bootstrap@4.1.3, bootstrap@^4.1.1:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
- integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==
+bootstrap@4.3.1, bootstrap@^4.1.1, bootstrap@^4.1.3:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
+ integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
boxen@^1.2.1:
version "1.3.0"
@@ -1872,27 +1970,16 @@ braces@^0.1.2:
dependencies:
expand-range "^0.1.0"
-braces@^1.8.2:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
- integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=
- dependencies:
- expand-range "^1.8.1"
- preserve "^0.2.0"
- repeat-element "^1.1.2"
-
braces@^2.3.0, braces@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb"
- integrity sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
+ integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
dependencies:
arr-flatten "^1.1.0"
array-unique "^0.3.2"
- define-property "^1.0.0"
extend-shallow "^2.0.1"
fill-range "^4.0.0"
isobject "^3.0.1"
- kind-of "^6.0.2"
repeat-element "^1.1.2"
snapdragon "^0.8.1"
snapdragon-node "^2.0.1"
@@ -1974,14 +2061,21 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.3.4, browserslist@^4.4.1:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062"
- integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==
+browserslist@^4.4.1, browserslist@^4.5.2, browserslist@^4.5.4:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.0.tgz#5274028c26f4d933d5b1323307c1d1da5084c9ff"
+ integrity sha512-Jk0YFwXBuMOOol8n6FhgkDzn3mY9PYLYGk29zybF05SbRTsMgPqmTNeQQhOghCxq5oFqAXE3u4sYddr4C0uRhg==
dependencies:
- caniuse-lite "^1.0.30000929"
- electron-to-chromium "^1.3.103"
- node-releases "^1.1.3"
+ caniuse-lite "^1.0.30000967"
+ electron-to-chromium "^1.3.133"
+ node-releases "^1.1.19"
+
+bs-logger@0.x:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+ integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
+ dependencies:
+ fast-json-stable-stringify "2.x"
bser@^2.0.0:
version "2.0.0"
@@ -1990,10 +2084,10 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
-buffer-from@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
- integrity sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==
+buffer-from@1.x, buffer-from@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+ integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer-indexof@^1.0.0:
version "1.1.0"
@@ -2124,6 +2218,19 @@ callsites@^2.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
+callsites@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3"
+ integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==
+
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
camelcase-keys@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77"
@@ -2133,27 +2240,37 @@ camelcase-keys@^4.0.0:
map-obj "^2.0.0"
quick-lru "^1.0.0"
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+ integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+ integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
+
camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
camelcase@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
- integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000932:
- version "1.0.30000936"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b"
- integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==
+caniuse-lite@^1.0.30000932, caniuse-lite@^1.0.30000967:
+ version "1.0.30000969"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz#7664f571f2072657bde70b00a1fc1ba41f1942a9"
+ integrity sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ==
-capture-exit@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f"
- integrity sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=
+capture-exit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
+ integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==
dependencies:
- rsvp "^3.3.3"
+ rsvp "^4.8.4"
capture-stack-trace@^1.0.0:
version "1.0.0"
@@ -2177,7 +2294,7 @@ ccount@^1.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff"
integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
@@ -2227,7 +2344,7 @@ chardet@^0.5.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029"
integrity sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==
-"charenc@>= 0.0.1":
+"charenc@>= 0.0.1", charenc@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
@@ -2280,7 +2397,7 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
optionalDependencies:
fsevents "^1.2.2"
-chownr@^1.0.1, chownr@^1.1.1:
+chownr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==
@@ -2297,6 +2414,11 @@ ci-info@^1.5.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
+ci-info@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+ integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -2366,10 +2488,19 @@ clipboard@^1.5.5, clipboard@^1.7.1:
select "^1.1.2"
tiny-emitter "^2.0.0"
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
cliui@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
- integrity sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+ integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
dependencies:
string-width "^2.1.1"
strip-ansi "^4.0.0"
@@ -2390,11 +2521,6 @@ clone-response@1.0.2:
dependencies:
mimic-response "^1.0.0"
-clone@2.x:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
- integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
-
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -2473,10 +2599,10 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0:
- version "2.19.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
- integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
+commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0, commander@~2.20.0:
+ version "2.20.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+ integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@~2.17.1:
version "2.17.1"
@@ -2629,7 +2755,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1:
+convert-source-map@^1.1.0, convert-source-map@^1.4.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
@@ -2670,11 +2796,36 @@ copy-to-clipboard@^3.0.8:
dependencies:
toggle-selection "^1.0.3"
-core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
+core-js-compat@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.0.1.tgz#bff73ba31ca8687431b9c88f78d3362646fb76f0"
+ integrity sha512-2pC3e+Ht/1/gD7Sim/sqzvRplMiRnFQVlPpDVaHtY9l7zZP7knamr3VRD6NyGfHd84MrDC0tAM9ulNxYMW0T3g==
+ dependencies:
+ browserslist "^4.5.4"
+ core-js "3.0.1"
+ core-js-pure "3.0.1"
+ semver "^6.0.0"
+
+core-js-pure@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.1.tgz#37358fb0d024e6b86d443d794f4e37e949098cbe"
+ integrity sha512-mSxeQ6IghKW3MoyF4cz19GJ1cMm7761ON+WObSyLfTu/Jn3x7w4NwNFnrZxgl4MTSvYYepVLNuRtlB4loMwJ5g==
+
+core-js@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
+ integrity sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==
+
+core-js@^2.2.0, core-js@^2.4.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==
+core-js@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.3.tgz#95700bca5f248f5f78c0ec63e784eca663ec4138"
+ integrity sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==
+
core-js@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
@@ -2739,6 +2890,14 @@ cropper@^2.3.0:
dependencies:
jquery ">= 1.9.1"
+cross-spawn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
+ integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI=
+ dependencies:
+ lru-cache "^4.0.1"
+ which "^1.2.9"
+
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -2759,7 +2918,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-"crypt@>= 0.0.1":
+"crypt@>= 0.0.1", crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
@@ -2862,22 +3021,27 @@ custom-event@~1.0.0:
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
+custom-jquery-matchers@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/custom-jquery-matchers/-/custom-jquery-matchers-2.1.0.tgz#e5988fa9715c416b0986b372563f872d9e91e024"
+ integrity sha512-f2vQrncnwqbeDne9ag/BRjrmG9BxIEKnZ9Z8TyG5l9B8wwyINWd7LvuHDlTkMPqLTd3ghcvqFoTsxjhOP371JQ==
+
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
-d3-array@1, d3-array@1.2.1, d3-array@^1.2.0, d3-array@^1.2.1:
+d3-array@1, d3-array@1.2.1, d3-array@^1.1.1, d3-array@^1.2.0, d3-array@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
integrity sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==
-d3-axis@1.0.8, d3-axis@^1.0.8:
+d3-axis@1, d3-axis@1.0.8, d3-axis@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa"
integrity sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=
-d3-brush@1.0.4, d3-brush@^1.0.4:
+d3-brush@1, d3-brush@1.0.4, d3-brush@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4"
integrity sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=
@@ -2888,7 +3052,7 @@ d3-brush@1.0.4, d3-brush@^1.0.4:
d3-selection "1"
d3-transition "1"
-d3-chord@1.0.4:
+d3-chord@1, d3-chord@1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c"
integrity sha1-fexPC6iG9xP+ERxF92NBT290yiw=
@@ -2906,6 +3070,13 @@ d3-color@1, d3-color@1.0.3:
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
integrity sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=
+d3-contour@1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3"
+ integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==
+ dependencies:
+ d3-array "^1.1.1"
+
d3-dispatch@1, d3-dispatch@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8"
@@ -2928,12 +3099,19 @@ d3-dsv@1, d3-dsv@1.0.8:
iconv-lite "0.4"
rw "1"
-d3-ease@1, d3-ease@1.0.3:
+d3-ease@1, d3-ease@1.0.3, d3-ease@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e"
integrity sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=
-d3-force@1.1.0:
+d3-fetch@1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.1.2.tgz#957c8fbc6d4480599ba191b1b2518bf86b3e1be2"
+ integrity sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==
+ dependencies:
+ d3-dsv "1"
+
+d3-force@1, d3-force@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3"
integrity sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==
@@ -2948,14 +3126,14 @@ d3-format@1, d3-format@1.2.2:
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==
-d3-geo@1.9.1:
+d3-geo@1, d3-geo@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
integrity sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA==
dependencies:
d3-array "1"
-d3-hierarchy@1.1.5:
+d3-hierarchy@1, d3-hierarchy@1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26"
integrity sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=
@@ -2972,7 +3150,7 @@ d3-path@1, d3-path@1.0.5:
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764"
integrity sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=
-d3-polygon@1.0.3:
+d3-polygon@1, d3-polygon@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62"
integrity sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=
@@ -2987,7 +3165,7 @@ d3-queue@3.0.7:
resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618"
integrity sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=
-d3-random@1.1.0:
+d3-random@1, d3-random@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3"
integrity sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=
@@ -3002,6 +3180,14 @@ d3-request@1.0.6:
d3-dsv "1"
xmlhttprequest "1"
+d3-scale-chromatic@1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz#dad4366f0edcb288f490128979c3c793583ed3c0"
+ integrity sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==
+ dependencies:
+ d3-color "1"
+ d3-interpolate "1"
+
d3-scale@1.0.7, d3-scale@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
@@ -3015,12 +3201,24 @@ d3-scale@1.0.7, d3-scale@^1.0.7:
d3-time "1"
d3-time-format "2"
+d3-scale@2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
+ integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==
+ dependencies:
+ d3-array "^1.2.0"
+ d3-collection "1"
+ d3-format "1"
+ d3-interpolate "1"
+ d3-time "1"
+ d3-time-format "2"
+
d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==
-d3-shape@1.2.0, d3-shape@^1.2.0:
+d3-shape@1, d3-shape@1.2.0, d3-shape@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
integrity sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=
@@ -3044,7 +3242,7 @@ d3-timer@1, d3-timer@1.0.7:
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531"
integrity sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==
-d3-transition@1, d3-transition@1.1.1:
+d3-transition@1, d3-transition@1.1.1, d3-transition@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039"
integrity sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==
@@ -3056,12 +3254,12 @@ d3-transition@1, d3-transition@1.1.1:
d3-selection "^1.1.0"
d3-timer "1"
-d3-voronoi@1.1.2:
+d3-voronoi@1, d3-voronoi@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c"
integrity sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=
-d3-zoom@1.7.1:
+d3-zoom@1, d3-zoom@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63"
integrity sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ==
@@ -3108,6 +3306,43 @@ d3@^4.13.0:
d3-voronoi "1.1.2"
d3-zoom "1.7.1"
+d3@^5.7.0:
+ version "5.9.2"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.2.tgz#64e8a7e9c3d96d9e6e4999d2c8a2c829767e67f5"
+ integrity sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ==
+ dependencies:
+ d3-array "1"
+ d3-axis "1"
+ d3-brush "1"
+ d3-chord "1"
+ d3-collection "1"
+ d3-color "1"
+ d3-contour "1"
+ d3-dispatch "1"
+ d3-drag "1"
+ d3-dsv "1"
+ d3-ease "1"
+ d3-fetch "1"
+ d3-force "1"
+ d3-format "1"
+ d3-geo "1"
+ d3-hierarchy "1"
+ d3-interpolate "1"
+ d3-path "1"
+ d3-polygon "1"
+ d3-quadtree "1"
+ d3-random "1"
+ d3-scale "2"
+ d3-scale-chromatic "1"
+ d3-selection "1"
+ d3-shape "1"
+ d3-time "1"
+ d3-time-format "2"
+ d3-timer "1"
+ d3-transition "1"
+ d3-voronoi "1"
+ d3-zoom "1"
+
dagre-d3-renderer@^0.5.8:
version "0.5.8"
resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3"
@@ -3160,21 +3395,21 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-debug@^3.1.0, debug@^3.2.5:
+debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
-debug@^4.0.0, debug@^4.0.1, debug@^4.1.0:
+debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -3196,7 +3431,7 @@ decamelize-keys@^1.0.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
-decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0:
+decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -3248,13 +3483,6 @@ default-gateway@^2.6.0:
execa "^0.10.0"
ip-regex "^2.1.0"
-default-require-extensions@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
- integrity sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=
- dependencies:
- strip-bom "^2.0.0"
-
default-require-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7"
@@ -3359,13 +3587,6 @@ detect-file@^1.0.0:
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-detect-indent@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
- dependencies:
- repeating "^2.0.0"
-
detect-libc@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@@ -3386,6 +3607,11 @@ di@^0.0.1:
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
+diff-sequences@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
+ integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
+
diff@^3.2.0, diff@^3.4.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -3583,10 +3809,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.103:
- version "1.3.113"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9"
- integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==
+electron-to-chromium@^1.3.133:
+ version "1.3.135"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz#f5799b95f2bcd8de17cde47d63392d83a4477041"
+ integrity sha512-xXLNstRdVsisPF3pL3H9TVZo2XkMILfqtD6RiWIUmDK2sFX1Bjwqmd8LBp0Kuo2FgKO63JXPoEVGm8WyYdwP0Q==
elliptic@^6.0.0:
version "6.4.0"
@@ -3718,24 +3944,25 @@ error-ex@^1.2.0, error-ex@^1.3.1:
is-arrayish "^0.2.1"
es-abstract@^1.5.1, es-abstract@^1.6.1:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
- integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
+ integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
dependencies:
- es-to-primitive "^1.1.1"
+ es-to-primitive "^1.2.0"
function-bind "^1.1.1"
- has "^1.0.1"
- is-callable "^1.1.3"
+ has "^1.0.3"
+ is-callable "^1.1.4"
is-regex "^1.0.4"
+ object-keys "^1.0.12"
-es-to-primitive@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
- integrity sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=
+es-to-primitive@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
+ integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
dependencies:
- is-callable "^1.1.1"
+ is-callable "^1.1.4"
is-date-object "^1.0.1"
- is-symbol "^1.0.1"
+ is-symbol "^1.0.2"
es6-promise@~3.0.2:
version "3.0.2"
@@ -3806,7 +4033,7 @@ eslint-import-resolver-jest@^2.1.1:
micromatch "^3.1.6"
resolve "^1.5.0"
-eslint-import-resolver-node@^0.3.1:
+eslint-import-resolver-node@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==
@@ -3830,13 +4057,13 @@ eslint-import-resolver-webpack@^0.10.1:
resolve "^1.4.0"
semver "^5.3.0"
-eslint-module-utils@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746"
- integrity sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=
+eslint-module-utils@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49"
+ integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==
dependencies:
debug "^2.6.8"
- pkg-dir "^1.0.0"
+ pkg-dir "^2.0.0"
eslint-plugin-filenames@^1.3.2:
version "1.3.2"
@@ -3855,36 +4082,36 @@ eslint-plugin-html@5.0.0:
dependencies:
htmlparser2 "^3.10.0"
-eslint-plugin-import@^2.14.0:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8"
- integrity sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==
+eslint-plugin-import@^2.14.0, eslint-plugin-import@^2.16.0:
+ version "2.16.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f"
+ integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==
dependencies:
contains-path "^0.1.0"
- debug "^2.6.8"
+ debug "^2.6.9"
doctrine "1.5.0"
- eslint-import-resolver-node "^0.3.1"
- eslint-module-utils "^2.2.0"
- has "^1.0.1"
- lodash "^4.17.4"
- minimatch "^3.0.3"
+ eslint-import-resolver-node "^0.3.2"
+ eslint-module-utils "^2.3.0"
+ has "^1.0.3"
+ lodash "^4.17.11"
+ minimatch "^3.0.4"
read-pkg-up "^2.0.0"
- resolve "^1.6.0"
+ resolve "^1.9.0"
eslint-plugin-jasmine@^2.10.1:
version "2.10.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz#5733b709e751f4bc40e31e1c16989bd2cdfbec97"
integrity sha1-VzO3CedR9LxA4x4cFpib0s377Jc=
-eslint-plugin-jest@^22.1.0:
- version "22.1.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.1.0.tgz#9a4dfa3367563e8301560a7fb92ec309096dbca3"
- integrity sha512-WcQd5LxEoAS20zuWEAd8CX0pVC+gGInZPcsoYvK5w7BrEJNmltyTxYYh1r0ct4GsahD2GvNySlcTcLtK2amFZA==
+eslint-plugin-jest@^22.3.0:
+ version "22.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.3.0.tgz#a10f10dedfc92def774ec9bb5bfbd2fb8e1c96d2"
+ integrity sha512-P1mYVRNlOEoO5T9yTqOfucjOYf1ktmJ26NjwjH8sxpCFQa6IhBGr5TpKl3hcAAT29hOsRJVuMWmTsHoUVo9FoA==
-eslint-plugin-promise@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2"
- integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==
+eslint-plugin-promise@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
+ integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
eslint-plugin-vue@^5.0.0:
version "5.0.0"
@@ -4056,12 +4283,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
-exec-sh@^0.2.0:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36"
- integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==
- dependencies:
- merge "^1.2.0"
+exec-sh@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b"
+ integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==
execa@^0.10.0:
version "0.10.0"
@@ -4089,6 +4314,19 @@ execa@^0.7.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+execa@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+ integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
+ dependencies:
+ cross-spawn "^6.0.0"
+ get-stream "^4.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"
+
execall@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73"
@@ -4110,13 +4348,6 @@ expand-braces@^0.1.1:
array-unique "^0.2.1"
braces "^0.1.2"
-expand-brackets@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
- integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=
- dependencies:
- is-posix-bracket "^0.1.0"
-
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -4138,13 +4369,6 @@ expand-range@^0.1.0:
is-number "^0.1.1"
repeat-string "^0.2.2"
-expand-range@^1.8.1:
- version "1.8.2"
- resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
- integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=
- dependencies:
- fill-range "^2.1.0"
-
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -4152,17 +4376,17 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
-expect@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98"
- integrity sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==
+expect@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d"
+ integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==
dependencies:
+ "@jest/types" "^24.8.0"
ansi-styles "^3.2.0"
- jest-diff "^23.6.0"
- jest-get-type "^22.1.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
+ jest-get-type "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-regex-util "^24.3.0"
exports-loader@^0.7.0:
version "0.7.0"
@@ -4246,13 +4470,6 @@ external-editor@^3.0.0:
iconv-lite "^0.4.22"
tmp "^0.0.33"
-extglob@^0.3.1:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
- integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=
- dependencies:
- is-extglob "^1.0.0"
-
extglob@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
@@ -4267,6 +4484,11 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+extract-files@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-5.0.1.tgz#c9492a8410be643e260a376f0151361993d5f659"
+ integrity sha512-qRW6y9eKF0VbCyOoOEtFhzJ3uykAw8GKwQVXyAIqwocyEWW4m+v+evec34RwtUkkxxHh7NKBLJ6AnXM8W4dH5w==
+
extract-from-css@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/extract-from-css/-/extract-from-css-0.4.4.tgz#1ea7df2e7c7c6eb9922fa08e8adaea486f6f8f92"
@@ -4296,7 +4518,7 @@ fast-glob@^2.2.6:
merge2 "^1.2.3"
micromatch "^3.1.10"
-fast-json-stable-stringify@^2.0.0:
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
@@ -4374,12 +4596,7 @@ file-loader@^3.0.1:
loader-utils "^1.0.2"
schema-utils "^1.0.0"
-filename-regex@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
- integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=
-
-fileset@^2.0.2, fileset@^2.0.3:
+fileset@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=
@@ -4392,17 +4609,6 @@ filesize@^3.6.1:
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==
-fill-range@^2.1.0:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
- integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==
- dependencies:
- is-number "^2.1.0"
- isobject "^2.0.0"
- randomatic "^3.0.0"
- repeat-element "^1.1.2"
- repeat-string "^1.5.2"
-
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -4439,14 +4645,6 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
-find-babel-config@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355"
- integrity sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=
- dependencies:
- json5 "^0.5.1"
- path-exists "^3.0.0"
-
find-cache-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
@@ -4532,18 +4730,11 @@ follow-redirects@^1.2.5:
dependencies:
debug "^3.1.0"
-for-in@^1.0.1, for-in@^1.0.2:
+for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
-for-own@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
- integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=
- dependencies:
- for-in "^1.0.1"
-
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -4601,9 +4792,9 @@ fs-access@^1.0.0:
null-check "^1.0.0"
fs-minipass@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
- integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
+ integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
dependencies:
minipass "^2.2.1"
@@ -4622,15 +4813,25 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-fsevents@^1.2.2, fsevents@^1.2.3:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
- integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==
+fsevents@^1.2.2, fsevents@^1.2.7:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
+ integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
dependencies:
- nan "^2.9.2"
- node-pre-gyp "^0.10.0"
+ nan "^2.12.1"
+ node-pre-gyp "^0.12.0"
-function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
+fstream@^1.0.0, fstream@^1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
+function-bind@^1.1.0, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
@@ -4659,10 +4860,22 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
+gaze@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
+ integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==
+ dependencies:
+ globule "^1.0.0"
+
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"
- integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+ integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+ integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
get-stdin@^6.0.0:
version "6.0.0"
@@ -4674,6 +4887,13 @@ get-stream@3.0.0, get-stream@^3.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+get-stream@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+ integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+ dependencies:
+ pump "^3.0.0"
+
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -4686,42 +4906,27 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
-gettext-extractor-vue@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f"
- integrity sha512-UnkWVO5jQQrs17L7HSlKj3O7U8C4+AQFzE05MK/I+JkMZdQdB6JMjA0IK0c4GObSlkgx4aiCCG6zWqIBnDR95w==
+gettext-extractor-vue@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.2.tgz#16e1cdbdaf37e5bdf3cb0aff63685bdc5e74e906"
+ integrity sha512-tnTAU1TdQFREv4Q4hfBDuB329eugeFsYmV7lE9U1jkZEyxcf4oPgimLHNZVNaEUg4+JJwhB8B9HIeqbcbSW32g==
dependencies:
bluebird "^3.5.1"
glob "^7.1.2"
- vue-template-compiler "^2.5.0"
+ vue-template-compiler "^2.5.20"
-gettext-extractor@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.3.2.tgz#d5172ba8d175678bd40a5abe7f908fa2a9d9473b"
- integrity sha1-1RcrqNF1Z4vUClq+f5CPoqnZRzs=
+gettext-extractor@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.4.3.tgz#882679cefc71888eb6e69297e6b2dc14c0384fef"
+ integrity sha512-YSNdTCHmzm58Rc21thtXj7jRIOlqINftM3XbtvNK28C88i35EnEB89iOeV9Vetv7wcb/wiPAtcq/6iSnt2pMyw==
dependencies:
- "@types/glob" "^5"
+ "@types/glob" "5 - 7"
"@types/parse5" "^5"
css-selector-parser "^1.3"
glob "5 - 7"
parse5 "^5"
pofile "^1"
- typescript "^2"
-
-glob-base@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
- integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
- dependencies:
- glob-parent "^2.0.0"
- is-glob "^2.0.0"
-
-glob-parent@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
- integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
- dependencies:
- is-glob "^2.0.0"
+ typescript "2 - 3"
glob-parent@^3.1.0:
version "3.1.0"
@@ -4736,10 +4941,10 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
-"glob@5 - 7", glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
- version "7.1.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
- integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
+ version "7.1.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
+ integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -4808,14 +5013,9 @@ global-prefix@^3.0.0:
which "^1.3.1"
globals@^11.1.0, globals@^11.7.0:
- version "11.7.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673"
- integrity sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==
-
-globals@^9.18.0:
- version "9.18.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
- integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globby@^5.0.0:
version "5.0.0"
@@ -4858,6 +5058,15 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=
+globule@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
+ integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==
+ dependencies:
+ glob "~7.1.1"
+ lodash "~4.17.10"
+ minimatch "~3.0.2"
+
gonzales-pe@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2"
@@ -4924,14 +5133,7 @@ graphlibrary@^2.2.0:
dependencies:
lodash "^4.17.5"
-graphql-anywhere@^4.1.0-alpha.0:
- version "4.1.22"
- resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.22.tgz#1c831ba3c9e5664a0dd24d10d23a9e9512d92056"
- integrity sha512-qm2/1cKM8nfotxDhm4J0r1znVlK0Yge/yEKt26EVVBgpIhvxjXYFALCGbr7cvfDlvzal1iSPpaYa+8YTtjsxQA==
- dependencies:
- apollo-utilities "^1.0.25"
-
-graphql-tag@^2.10.0, graphql-tag@^2.4.2:
+graphql-tag@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae"
integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w==
@@ -4961,12 +5163,12 @@ handle-thing@^2.0.0:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
-handlebars@^4.0.1, handlebars@^4.0.11, handlebars@^4.0.3:
- version "4.0.12"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
- integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==
+handlebars@^4.0.1, handlebars@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
+ integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
dependencies:
- async "^2.5.0"
+ neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
@@ -5067,12 +5269,12 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
-has@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
- integrity sha1-hGFzP1OLCDfJNh45qauelwTcLyg=
+has@^1.0.1, has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
- function-bind "^1.0.2"
+ function-bind "^1.1.1"
hash-base@^2.0.0:
version "2.0.2"
@@ -5102,10 +5304,10 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
-he@^1.1.0, he@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
- integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
+he@^1.1.0, he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
highlight.js@^9.13.1, highlight.js@~9.13.0:
version "9.13.1"
@@ -5121,14 +5323,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-home-or-tmp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
- integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg=
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.1"
-
homedir-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
@@ -5321,14 +5515,6 @@ import-lazy@^3.1.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc"
integrity sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==
-import-local@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
- integrity sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==
- dependencies:
- pkg-dir "^2.0.0"
- resolve-cwd "^2.0.0"
-
import-local@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@@ -5350,6 +5536,18 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+in-publish@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
+ integrity sha1-4g/146KvwmkDILbcVSaCqcf631E=
+
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
+ dependencies:
+ repeating "^2.0.0"
+
indent-string@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
@@ -5373,7 +5571,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@@ -5523,7 +5721,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.1.5:
+is-buffer@^1.1.5, is-buffer@~1.1.1:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -5533,7 +5731,7 @@ is-buffer@^2.0.0:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
-is-callable@^1.1.1, is-callable@^1.1.3:
+is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
@@ -5545,6 +5743,13 @@ is-ci@^1.0.10:
dependencies:
ci-info "^1.5.0"
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -5592,18 +5797,6 @@ is-directory@^0.3.1:
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
-is-dotfile@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
- integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=
-
-is-equal-shallow@^0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
- integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=
- dependencies:
- is-primitive "^2.0.0"
-
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -5616,11 +5809,6 @@ is-extendable@^1.0.1:
dependencies:
is-plain-object "^2.0.4"
-is-extglob@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
- integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
-
is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -5645,17 +5833,10 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
-is-generator-fn@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a"
- integrity sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=
-
-is-glob@^2.0.0, is-glob@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
- integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
- dependencies:
- is-extglob "^1.0.0"
+is-generator-fn@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
+ integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
is-glob@^3.1.0:
version "3.1.0"
@@ -5694,13 +5875,6 @@ is-number@^0.1.1:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
integrity sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=
-is-number@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
- integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=
- dependencies:
- kind-of "^3.0.2"
-
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -5708,11 +5882,6 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
-is-number@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
- integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
-
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@@ -5723,13 +5892,6 @@ is-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
-is-odd@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24"
- integrity sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==
- dependencies:
- is-number "^4.0.0"
-
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -5761,16 +5923,6 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
-is-posix-bracket@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
- integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
-
-is-primitive@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
- integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
-
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -5813,7 +5965,7 @@ is-supported-regexp-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca"
integrity sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==
-is-symbol@^1.0.1:
+is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
@@ -5887,145 +6039,76 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-istanbul-api@^1.3.1:
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
- integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==
- dependencies:
- async "^2.1.4"
- fileset "^2.0.2"
- istanbul-lib-coverage "^1.2.1"
- istanbul-lib-hook "^1.2.2"
- istanbul-lib-instrument "^1.10.2"
- istanbul-lib-report "^1.1.5"
- istanbul-lib-source-maps "^1.2.6"
- istanbul-reports "^1.5.1"
- js-yaml "^3.7.0"
- mkdirp "^0.5.1"
- once "^1.4.0"
-
istanbul-api@^2.0.5:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.0.6.tgz#cd7b33ee678f6c01531d05f5e567ebbcd25f8ecc"
- integrity sha512-8W5oeAGWXhtTJjAyVfvavOLVyZCTNCKsyF6GON/INKlBdO7uJ/bv3qnPj5M6ERKzmMCJS1kntnjjGuJ86fn3rQ==
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0"
+ integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==
dependencies:
async "^2.6.1"
compare-versions "^3.2.1"
fileset "^2.0.3"
- istanbul-lib-coverage "^2.0.1"
- istanbul-lib-hook "^2.0.1"
- istanbul-lib-instrument "^3.0.0"
- istanbul-lib-report "^2.0.2"
- istanbul-lib-source-maps "^2.0.1"
- istanbul-reports "^2.0.1"
+ istanbul-lib-coverage "^2.0.3"
+ istanbul-lib-hook "^2.0.3"
+ istanbul-lib-instrument "^3.1.0"
+ istanbul-lib-report "^2.0.4"
+ istanbul-lib-source-maps "^3.0.2"
+ istanbul-reports "^2.1.1"
js-yaml "^3.12.0"
make-dir "^1.3.0"
+ minimatch "^3.0.4"
once "^1.4.0"
-istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
- integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==
-
-istanbul-lib-coverage@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#2aee0e073ad8c5f6a0b00e0dfbf52b4667472eda"
- integrity sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==
-
-istanbul-lib-hook@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
- integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==
- dependencies:
- append-transform "^0.4.0"
+istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3, istanbul-lib-coverage@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
+ integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
-istanbul-lib-hook@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.1.tgz#918a57b75a0f951d552a08487ca1fa5336433d72"
- integrity sha512-ufiZoiJ8CxY577JJWEeFuxXZoMqiKpq/RqZtOAYuQLvlkbJWscq9n3gc4xrCGH9n4pW0qnTxOz1oyMmVtk8E1w==
+istanbul-lib-hook@^2.0.3:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
+ integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==
dependencies:
append-transform "^1.0.0"
-istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
- integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==
- dependencies:
- babel-generator "^6.18.0"
- babel-template "^6.16.0"
- babel-traverse "^6.18.0"
- babel-types "^6.18.0"
- babylon "^6.18.0"
- istanbul-lib-coverage "^1.2.1"
- semver "^5.3.0"
-
-istanbul-lib-instrument@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz#b5f066b2a161f75788be17a9d556f40a0cf2afc9"
- integrity sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==
- dependencies:
- "@babel/generator" "^7.0.0"
- "@babel/parser" "^7.0.0"
- "@babel/template" "^7.0.0"
- "@babel/traverse" "^7.0.0"
- "@babel/types" "^7.0.0"
- istanbul-lib-coverage "^2.0.1"
- semver "^5.5.0"
-
-istanbul-lib-report@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
- integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==
- dependencies:
- istanbul-lib-coverage "^1.2.1"
- mkdirp "^0.5.1"
- path-parse "^1.0.5"
- supports-color "^3.1.2"
-
-istanbul-lib-report@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.2.tgz#430a2598519113e1da7af274ba861bd42dd97535"
- integrity sha512-rJ8uR3peeIrwAxoDEbK4dJ7cqqtxBisZKCuwkMtMv0xYzaAnsAi3AHrHPAAtNXzG/bcCgZZ3OJVqm1DTi9ap2Q==
- dependencies:
- istanbul-lib-coverage "^2.0.1"
- make-dir "^1.3.0"
- supports-color "^5.4.0"
-
-istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
- integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==
- dependencies:
- debug "^3.1.0"
- istanbul-lib-coverage "^1.2.1"
- mkdirp "^0.5.1"
- rimraf "^2.6.1"
- source-map "^0.5.3"
+istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630"
+ integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==
+ dependencies:
+ "@babel/generator" "^7.4.0"
+ "@babel/parser" "^7.4.3"
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.3"
+ "@babel/types" "^7.4.0"
+ istanbul-lib-coverage "^2.0.5"
+ semver "^6.0.0"
+
+istanbul-lib-report@^2.0.4:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33"
+ integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==
+ dependencies:
+ istanbul-lib-coverage "^2.0.5"
+ make-dir "^2.1.0"
+ supports-color "^6.1.0"
-istanbul-lib-source-maps@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.1.tgz#ce8b45131d8293fdeaa732f4faf1852d13d0a97e"
- integrity sha512-30l40ySg+gvBLcxTrLzR4Z2XTRj3HgRCA/p2rnbs/3OiTaoj054gAbuP5DcLOtwqmy4XW8qXBHzrmP2/bQ9i3A==
+istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
+ integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==
dependencies:
- debug "^3.1.0"
- istanbul-lib-coverage "^2.0.1"
- make-dir "^1.3.0"
- rimraf "^2.6.2"
+ debug "^4.1.1"
+ istanbul-lib-coverage "^2.0.5"
+ make-dir "^2.1.0"
+ rimraf "^2.6.3"
source-map "^0.6.1"
-istanbul-reports@^1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
- integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==
- dependencies:
- handlebars "^4.0.3"
-
-istanbul-reports@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.0.1.tgz#fb8d6ea850701a3984350b977a969e9a556116a7"
- integrity sha512-CT0QgMBJqs6NJLF678ZHcquUAZIoBIUNzdJrRJfpkI9OnzG6MkUfHxbJC3ln981dMswC7/B1mfX3LNkhgJxsuw==
+istanbul-reports@^2.1.1:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af"
+ integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==
dependencies:
- handlebars "^4.0.11"
+ handlebars "^4.1.2"
istanbul@^0.4.5:
version "0.4.5"
@@ -6091,333 +6174,372 @@ jed@^1.1.1:
resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
integrity sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=
-jest-changed-files@^23.4.2:
- version "23.4.2"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
- integrity sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==
+jest-changed-files@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b"
+ integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==
dependencies:
+ "@jest/types" "^24.8.0"
+ execa "^1.0.0"
throat "^4.0.0"
-jest-cli@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4"
- integrity sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==
+jest-cli@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989"
+ integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==
dependencies:
- ansi-escapes "^3.0.0"
+ "@jest/core" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
exit "^0.1.2"
- glob "^7.1.2"
- graceful-fs "^4.1.11"
- import-local "^1.0.0"
- is-ci "^1.0.10"
- istanbul-api "^1.3.1"
- istanbul-lib-coverage "^1.2.0"
- istanbul-lib-instrument "^1.10.1"
- istanbul-lib-source-maps "^1.2.4"
- jest-changed-files "^23.4.2"
- jest-config "^23.6.0"
- jest-environment-jsdom "^23.4.0"
- jest-get-type "^22.1.0"
- jest-haste-map "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
- jest-resolve-dependencies "^23.6.0"
- jest-runner "^23.6.0"
- jest-runtime "^23.6.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- jest-watcher "^23.4.0"
- jest-worker "^23.2.0"
- micromatch "^2.3.11"
- node-notifier "^5.2.1"
- prompts "^0.1.9"
- realpath-native "^1.0.0"
- rimraf "^2.5.4"
- slash "^1.0.0"
- string-length "^2.0.0"
- strip-ansi "^4.0.0"
- which "^1.2.12"
- yargs "^11.0.0"
-
-jest-config@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d"
- integrity sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==
- dependencies:
- babel-core "^6.0.0"
- babel-jest "^23.6.0"
+ import-local "^2.0.0"
+ is-ci "^2.0.0"
+ jest-config "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ prompts "^2.0.1"
+ realpath-native "^1.1.0"
+ yargs "^12.0.2"
+
+jest-config@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f"
+ integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==
+ dependencies:
+ "@babel/core" "^7.1.0"
+ "@jest/test-sequencer" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ babel-jest "^24.8.0"
chalk "^2.0.1"
glob "^7.1.1"
- jest-environment-jsdom "^23.4.0"
- jest-environment-node "^23.4.0"
- jest-get-type "^22.1.0"
- jest-jasmine2 "^23.6.0"
- jest-regex-util "^23.3.0"
- jest-resolve "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- micromatch "^2.3.11"
- pretty-format "^23.6.0"
-
-jest-diff@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d"
- integrity sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==
+ jest-environment-jsdom "^24.8.0"
+ jest-environment-node "^24.8.0"
+ jest-get-type "^24.8.0"
+ jest-jasmine2 "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ micromatch "^3.1.10"
+ pretty-format "^24.8.0"
+ realpath-native "^1.1.0"
+
+jest-diff@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
+ integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
dependencies:
chalk "^2.0.1"
- diff "^3.2.0"
- jest-get-type "^22.1.0"
- pretty-format "^23.6.0"
+ diff-sequences "^24.3.0"
+ jest-get-type "^24.8.0"
+ pretty-format "^24.8.0"
-jest-docblock@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
- integrity sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=
+jest-docblock@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd"
+ integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==
dependencies:
detect-newline "^2.1.0"
-jest-each@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575"
- integrity sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==
+jest-each@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775"
+ integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==
dependencies:
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
- pretty-format "^23.6.0"
-
-jest-environment-jsdom@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
- integrity sha1-BWp5UrP+pROsYqFAosNox52eYCM=
- dependencies:
- jest-mock "^23.2.0"
- jest-util "^23.4.0"
+ jest-get-type "^24.8.0"
+ jest-util "^24.8.0"
+ pretty-format "^24.8.0"
+
+jest-environment-jsdom@^24.0.0, jest-environment-jsdom@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857"
+ integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==
+ dependencies:
+ "@jest/environment" "^24.8.0"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-util "^24.8.0"
jsdom "^11.5.1"
-jest-environment-node@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
- integrity sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=
+jest-environment-node@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231"
+ integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==
dependencies:
- jest-mock "^23.2.0"
- jest-util "^23.4.0"
+ "@jest/environment" "^24.8.0"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-util "^24.8.0"
-jest-get-type@^22.1.0:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
- integrity sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==
+jest-get-type@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
+ integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
-jest-haste-map@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16"
- integrity sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==
+jest-haste-map@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800"
+ integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ==
dependencies:
+ "@jest/types" "^24.8.0"
+ anymatch "^2.0.0"
fb-watchman "^2.0.0"
- graceful-fs "^4.1.11"
+ graceful-fs "^4.1.15"
invariant "^2.2.4"
- jest-docblock "^23.2.0"
- jest-serializer "^23.0.1"
- jest-worker "^23.2.0"
- micromatch "^2.3.11"
- sane "^2.0.0"
-
-jest-jasmine2@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0"
- integrity sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==
- dependencies:
- babel-traverse "^6.0.0"
+ jest-serializer "^24.4.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
+ micromatch "^3.1.10"
+ sane "^4.0.3"
+ walker "^1.0.7"
+ optionalDependencies:
+ fsevents "^1.2.7"
+
+jest-jasmine2@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898"
+ integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==
+ dependencies:
+ "@babel/traverse" "^7.1.0"
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
co "^4.6.0"
- expect "^23.6.0"
- is-generator-fn "^1.0.0"
- jest-diff "^23.6.0"
- jest-each "^23.6.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- pretty-format "^23.6.0"
-
-jest-junit@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-5.2.0.tgz#980401db7aa69999cf117c6d740a8135c22ae379"
- integrity sha512-Mdg0Qpdh1Xm/FA1B/mcLlmEmlr3XzH5pZg7MvcAwZhjHijPRd1z/UwYwkwNHmCV7o4ZOWCf77nLu7ZkhHHrtJg==
+ expect "^24.8.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ pretty-format "^24.8.0"
+ throat "^4.0.0"
+
+jest-junit@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-6.3.0.tgz#99e64ebc54eddcb21238f0cc49f5820c89a8c785"
+ integrity sha512-3PH9UkpaomX6CUzqjlnk0m4yBCW/eroxV6v61OM6LkCQFO848P3YUhfIzu8ypZSBKB3vvCbB4WaLTKT0BrIf8A==
dependencies:
- jest-config "^23.6.0"
- jest-validate "^23.0.1"
+ jest-validate "^24.0.0"
mkdirp "^0.5.1"
strip-ansi "^4.0.0"
xml "^1.0.1"
-jest-leak-detector@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de"
- integrity sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==
+jest-leak-detector@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980"
+ integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==
dependencies:
- pretty-format "^23.6.0"
+ pretty-format "^24.8.0"
-jest-matcher-utils@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80"
- integrity sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==
+jest-matcher-utils@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495"
+ integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==
dependencies:
chalk "^2.0.1"
- jest-get-type "^22.1.0"
- pretty-format "^23.6.0"
+ jest-diff "^24.8.0"
+ jest-get-type "^24.8.0"
+ pretty-format "^24.8.0"
-jest-message-util@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
- integrity sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=
+jest-message-util@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b"
+ integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==
dependencies:
- "@babel/code-frame" "^7.0.0-beta.35"
+ "@babel/code-frame" "^7.0.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/stack-utils" "^1.0.1"
chalk "^2.0.1"
- micromatch "^2.3.11"
- slash "^1.0.0"
+ micromatch "^3.1.10"
+ slash "^2.0.0"
stack-utils "^1.0.1"
-jest-mock@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
- integrity sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=
+jest-mock@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56"
+ integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==
+ dependencies:
+ "@jest/types" "^24.8.0"
-jest-regex-util@^23.3.0:
- version "23.3.0"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
- integrity sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=
+jest-pnp-resolver@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
+ integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==
-jest-resolve-dependencies@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d"
- integrity sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==
+jest-regex-util@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36"
+ integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==
+
+jest-resolve-dependencies@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0"
+ integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==
dependencies:
- jest-regex-util "^23.3.0"
- jest-snapshot "^23.6.0"
+ "@jest/types" "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-snapshot "^24.8.0"
-jest-resolve@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae"
- integrity sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==
+jest-resolve@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f"
+ integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==
dependencies:
+ "@jest/types" "^24.8.0"
browser-resolve "^1.11.3"
chalk "^2.0.1"
- realpath-native "^1.0.0"
-
-jest-runner@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38"
- integrity sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==
- dependencies:
+ jest-pnp-resolver "^1.2.1"
+ realpath-native "^1.1.0"
+
+jest-runner@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb"
+ integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ chalk "^2.4.2"
exit "^0.1.2"
- graceful-fs "^4.1.11"
- jest-config "^23.6.0"
- jest-docblock "^23.2.0"
- jest-haste-map "^23.6.0"
- jest-jasmine2 "^23.6.0"
- jest-leak-detector "^23.6.0"
- jest-message-util "^23.4.0"
- jest-runtime "^23.6.0"
- jest-util "^23.4.0"
- jest-worker "^23.2.0"
+ graceful-fs "^4.1.15"
+ jest-config "^24.8.0"
+ jest-docblock "^24.3.0"
+ jest-haste-map "^24.8.0"
+ jest-jasmine2 "^24.8.0"
+ jest-leak-detector "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-resolve "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
source-map-support "^0.5.6"
throat "^4.0.0"
-jest-runtime@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082"
- integrity sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==
- dependencies:
- babel-core "^6.0.0"
- babel-plugin-istanbul "^4.1.6"
+jest-runtime@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620"
+ integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/environment" "^24.8.0"
+ "@jest/source-map" "^24.3.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/yargs" "^12.0.2"
chalk "^2.0.1"
- convert-source-map "^1.4.0"
exit "^0.1.2"
- fast-json-stable-stringify "^2.0.0"
- graceful-fs "^4.1.11"
- jest-config "^23.6.0"
- jest-haste-map "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
- jest-resolve "^23.6.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- micromatch "^2.3.11"
- realpath-native "^1.0.0"
- slash "^1.0.0"
- strip-bom "3.0.0"
- write-file-atomic "^2.1.0"
- yargs "^11.0.0"
-
-jest-serializer@^23.0.1:
- version "23.0.1"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
- integrity sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=
-
-jest-snapshot@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a"
- integrity sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==
- dependencies:
- babel-types "^6.0.0"
+ glob "^7.1.3"
+ graceful-fs "^4.1.15"
+ jest-config "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ realpath-native "^1.1.0"
+ slash "^2.0.0"
+ strip-bom "^3.0.0"
+ yargs "^12.0.2"
+
+jest-serializer@^24.4.0:
+ version "24.4.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3"
+ integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==
+
+jest-snapshot@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6"
+ integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
- jest-diff "^23.6.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-resolve "^23.6.0"
+ expect "^24.8.0"
+ jest-diff "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-resolve "^24.8.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
- pretty-format "^23.6.0"
+ pretty-format "^24.8.0"
semver "^5.5.0"
-jest-util@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
- integrity sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=
- dependencies:
- callsites "^2.0.0"
+jest-transform-graphql@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/jest-transform-graphql/-/jest-transform-graphql-2.1.0.tgz#903cb66bb27bc2772fd3e5dd4f7e9b57230f5829"
+ integrity sha1-kDy2a7J7wncv0+XdT36bVyMPWCk=
+
+jest-util@^24.0.0, jest-util@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1"
+ integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/source-map" "^24.3.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ callsites "^3.0.0"
chalk "^2.0.1"
- graceful-fs "^4.1.11"
- is-ci "^1.0.10"
- jest-message-util "^23.4.0"
+ graceful-fs "^4.1.15"
+ is-ci "^2.0.0"
mkdirp "^0.5.1"
- slash "^1.0.0"
+ slash "^2.0.0"
source-map "^0.6.0"
-jest-validate@^23.0.1, jest-validate@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474"
- integrity sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==
+jest-validate@^24.0.0, jest-validate@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849"
+ integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==
dependencies:
+ "@jest/types" "^24.8.0"
+ camelcase "^5.0.0"
chalk "^2.0.1"
- jest-get-type "^22.1.0"
+ jest-get-type "^24.8.0"
leven "^2.1.0"
- pretty-format "^23.6.0"
+ pretty-format "^24.8.0"
-jest-watcher@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
- integrity sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=
+jest-watcher@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4"
+ integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==
dependencies:
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/yargs" "^12.0.9"
ansi-escapes "^3.0.0"
chalk "^2.0.1"
+ jest-util "^24.8.0"
string-length "^2.0.0"
-jest-worker@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9"
- integrity sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=
+jest-worker@^24.6.0:
+ version "24.6.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3"
+ integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==
dependencies:
merge-stream "^1.0.1"
+ supports-color "^6.1.0"
-jest@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d"
- integrity sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==
+jest@^24.1.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081"
+ integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==
dependencies:
- import-local "^1.0.0"
- jest-cli "^23.6.0"
+ import-local "^2.0.0"
+ jest-cli "^24.8.0"
jquery-ujs@1.2.2:
version "1.2.2"
@@ -6426,6 +6548,11 @@ jquery-ujs@1.2.2:
dependencies:
jquery ">=1.8.0"
+jquery.caret@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/jquery.caret/-/jquery.caret-0.3.1.tgz#9c093318faf327eff322e826ca9f3241368bc7b8"
+ integrity sha1-nAkzGPrzJ+/zIugmyp8yQTaLx7g=
+
jquery.waitforimages@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b"
@@ -6436,7 +6563,12 @@ jquery.waitforimages@^2.2.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
-js-beautify@^1.6.14, js-beautify@^1.8.8:
+js-base64@^2.1.8:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
+ integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
+
+js-beautify@^1.8.8:
version "1.8.9"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==
@@ -6467,10 +6599,10 @@ js-tokens@^3.0.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0:
- version "3.12.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
- integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
+js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.9.0:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -6542,15 +6674,10 @@ jsdom@^11.5.1:
ws "^5.2.0"
xml-name-validator "^3.0.0"
-jsesc@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
- integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-
jsesc@^2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe"
- integrity sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
jsesc@~0.5.0:
version "0.5.0"
@@ -6592,18 +6719,18 @@ json3@^3.3.2:
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
-json5@^0.5.0, json5@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
- integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
-
-json5@^2.1.0:
+json5@2.x, json5@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
dependencies:
minimist "^1.2.0"
+json5@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+ integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -6769,10 +6896,10 @@ klaw@~2.0.0:
dependencies:
graceful-fs "^4.1.9"
-kleur@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
- integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==
+kleur@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
known-css-properties@^0.11.0:
version "0.11.0"
@@ -6786,13 +6913,6 @@ latest-version@^3.0.0:
dependencies:
package-json "^4.0.0"
-lazy-cache@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
- integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
- dependencies:
- set-getter "^0.1.0"
-
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -6965,7 +7085,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@4.x, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -7058,6 +7178,19 @@ make-dir@^1.0.0, make-dir@^1.3.0:
dependencies:
pify "^3.0.0"
+make-dir@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+ integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
+ dependencies:
+ pify "^4.0.1"
+ semver "^5.6.0"
+
+make-error@1.x:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
+ integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
+
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -7066,9 +7199,9 @@ makeerror@1.0.x:
tmpl "1.0.x"
map-age-cleaner@^0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74"
- integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+ integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
dependencies:
p-defer "^1.0.0"
@@ -7077,7 +7210,7 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
-map-obj@^1.0.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"
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
@@ -7120,11 +7253,6 @@ marked@^0.3.12, marked@~0.3.6:
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
-math-random@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
- integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w=
-
mathml-tag-names@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c"
@@ -7138,6 +7266,15 @@ md5.js@^1.3.4:
hash-base "^3.0.0"
inherits "^2.0.1"
+md5@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
+ integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
+ dependencies:
+ charenc "~0.0.1"
+ crypt "~0.0.1"
+ is-buffer "~1.1.1"
+
mdast-util-compact@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649"
@@ -7163,13 +7300,13 @@ mem@^1.1.0:
mimic-fn "^1.0.0"
mem@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf"
- integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+ integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
dependencies:
map-age-cleaner "^0.1.1"
- mimic-fn "^1.0.0"
- p-is-promise "^1.1.0"
+ mimic-fn "^2.0.0"
+ p-is-promise "^2.0.0"
memory-fs@^0.2.0:
version "0.2.0"
@@ -7184,6 +7321,22 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+meow@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
+ 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"
+
meow@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4"
@@ -7223,49 +7376,25 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-merge@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
- integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
-
-mermaid@^8.0.0-rc.8:
- version "8.0.0-rc.8"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72"
- integrity sha512-GbF9jHWfqE7YGx9vQySmBxy2Ahlclxmpk4tJ9ntNyafENl96s96ggUK/NQS5ydYoFab6MavTm4YMTIPKqWVvPQ==
+mermaid@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0.tgz#8f6c75017e788a8c3997e20c5e5046c2b88d1a8f"
+ integrity sha512-vUQRykev0A6RtxIVqQT3a9TDxcSbdZbQF5JDyKgidnYuJy8BE8jp6LM+HKDSQuroKm6buu4NlpMO+qhxIP/cTg==
dependencies:
- d3 "^4.13.0"
+ d3 "^5.7.0"
dagre-d3-renderer "^0.5.8"
dagre-layout "^0.8.8"
graphlibrary "^2.2.0"
- he "^1.1.1"
- lodash "^4.17.5"
- moment "^2.21.0"
- scope-css "^1.0.5"
+ he "^1.2.0"
+ lodash "^4.17.11"
+ moment "^2.23.0"
+ scope-css "^1.2.1"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@^2.3.11:
- version "2.3.11"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
- integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=
- dependencies:
- arr-diff "^2.0.0"
- array-unique "^0.2.1"
- braces "^1.8.2"
- expand-brackets "^0.1.4"
- extglob "^0.3.1"
- filename-regex "^2.0.0"
- is-extglob "^1.0.0"
- is-glob "^2.0.1"
- kind-of "^3.0.2"
- normalize-path "^2.0.1"
- object.omit "^2.0.0"
- parse-glob "^3.0.4"
- regex-cache "^0.4.2"
-
micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -7316,9 +7445,14 @@ mime@^2.0.3, mime@^2.3.1:
integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==
mimic-fn@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
- integrity sha1-5md4PZLonb00KBi1IwudYqZyrRg=
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+ integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+
+mimic-fn@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-response@^1.0.0:
version "1.0.0"
@@ -7335,7 +7469,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -7352,7 +7486,7 @@ minimist-options@^3.0.1:
minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
- resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@1.1.x:
@@ -7360,23 +7494,23 @@ minimist@1.1.x:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
-minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
+minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-minipass@^2.2.1, minipass@^2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
- integrity sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==
+minipass@^2.2.1, minipass@^2.3.4:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
+ integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
-minizlib@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
- integrity sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==
+minizlib@^1.1.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
+ integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
dependencies:
minipass "^2.2.1"
@@ -7404,17 +7538,17 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
-moment@2.x, moment@^2.10.2, moment@^2.21.0:
- version "2.23.0"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.23.0.tgz#759ea491ac97d54bac5ad776996e2a58cc1bc225"
- integrity sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==
+moment@2.x, moment@^2.10.2, moment@^2.23.0:
+ version "2.24.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
+ integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
monaco-editor-webpack-plugin@^1.7.0:
version "1.7.0"
@@ -7473,22 +7607,21 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-nan@^2.9.2:
- version "2.10.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
- integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
+nan@^2.12.1, nan@^2.13.2:
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
+ integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nanomatch@^1.2.9:
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
- integrity sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==
+ version "1.2.13"
+ resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
+ integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
dependencies:
arr-diff "^4.0.0"
array-unique "^0.3.2"
define-property "^2.0.2"
extend-shallow "^3.0.2"
fragment-cache "^0.2.1"
- is-odd "^2.0.0"
is-windows "^1.0.2"
kind-of "^6.0.2"
object.pick "^1.3.0"
@@ -7502,11 +7635,11 @@ natural-compare@^1.4.0:
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
needle@^2.2.1:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
- integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
+ integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
- debug "^2.1.2"
+ debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
@@ -7521,17 +7654,14 @@ neo-async@^2.5.0, neo-async@^2.6.0:
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
nice-try@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
- integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+ integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-node-cache@^4.1.1:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.2.0.tgz#48ac796a874e762582692004a376d26dfa875811"
- integrity sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==
- dependencies:
- clone "2.x"
- lodash "4.x"
+node-ensure@^0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
+ integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
node-fetch@1.6.3:
version "1.6.3"
@@ -7546,6 +7676,24 @@ node-forge@0.6.33:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc"
integrity sha1-RjgRh59XPUUVWtap9D3ClujoXrw=
+node-gyp@^3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
+ integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "^2.87.0"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -7580,20 +7728,26 @@ node-int64@^0.4.0:
util "^0.10.3"
vm-browserify "0.0.4"
+node-modules-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
+ integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
+
node-notifier@^5.2.1:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01"
- integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a"
+ integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==
dependencies:
growly "^1.3.0"
+ is-wsl "^1.1.0"
semver "^5.5.0"
shellwords "^0.1.1"
which "^1.3.0"
-node-pre-gyp@^0.10.0:
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
- integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==
+node-pre-gyp@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
+ integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
@@ -7606,13 +7760,36 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.3.tgz#aad9ce0dcb98129c753f772c0aa01360fb90fbd2"
- integrity sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==
+node-releases@^1.1.19:
+ version "1.1.19"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.19.tgz#c492d1e381fea0350b338b646c27867e88e91b3d"
+ integrity sha512-SH/B4WwovHbulIALsQllAVwqZZD1kPmKCqrhGfR29dXjLAVZMHvBjD3S6nL9D/J9QkmZ1R92/0wCMDKXUUvyyA==
dependencies:
semver "^5.3.0"
+node-sass@^4.12.0:
+ version "4.12.0"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
+ integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
+ dependencies:
+ async-foreach "^0.1.3"
+ chalk "^1.1.1"
+ cross-spawn "^3.0.0"
+ gaze "^1.0.0"
+ get-stdin "^4.0.1"
+ glob "^7.0.3"
+ in-publish "^2.0.0"
+ lodash "^4.17.11"
+ meow "^3.7.0"
+ mkdirp "^0.5.1"
+ nan "^2.13.2"
+ node-gyp "^3.8.0"
+ npmlog "^4.0.0"
+ request "^2.88.0"
+ sass-graph "^2.2.4"
+ stdout-stream "^1.4.0"
+ "true-case-path" "^1.0.2"
+
nodemon@^1.18.9:
version "1.18.9"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.9.tgz#90b467efd3b3c81b9453380aeb2a2cba535d0ead"
@@ -7629,7 +7806,7 @@ nodemon@^1.18.9:
undefsafe "^2.0.2"
update-notifier "^2.5.0"
-nopt@3.x:
+"nopt@2 || 3", nopt@3.x:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
@@ -7661,7 +7838,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
-normalize-path@^2.0.1, normalize-path@^2.1.1:
+normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
@@ -7693,14 +7870,14 @@ normalize-url@2.0.1:
sort-keys "^2.0.0"
npm-bundled@^1.0.1:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
- integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
+ integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
npm-packlist@^1.1.6:
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
- integrity sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
+ integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
@@ -7712,7 +7889,7 @@ npm-run-path@^2.0.0:
dependencies:
path-key "^2.0.0"
-npmlog@^4.0.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -7747,7 +7924,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -7767,9 +7944,9 @@ object-copy@^0.1.0:
kind-of "^3.0.3"
object-keys@^1.0.11, object-keys@^1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
- integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object-visit@^1.0.0:
version "1.0.1"
@@ -7806,14 +7983,6 @@ object.getownpropertydescriptors@^2.0.3:
define-properties "^1.1.2"
es-abstract "^1.5.1"
-object.omit@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
- integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=
- dependencies:
- for-own "^0.1.4"
- is-extendable "^0.1.1"
-
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@@ -7884,10 +8053,10 @@ opn@^5.1.0:
dependencies:
is-wsl "^1.1.0"
-optimism@^0.6.6:
- version "0.6.8"
- resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.8.tgz#0780b546da8cd0a72e5207e0c3706c990c8673a6"
- integrity sha512-bN5n1KCxSqwBDnmgDnzMtQTHdL+uea2HYFx1smvtE+w2AMl0Uy31g0aXnP/Nt85OINnMJPRpJyfRQLTCqn5Weg==
+optimism@^0.6.9:
+ version "0.6.9"
+ resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.9.tgz#19258ff8b3be0cea29ac35f06bff818e026e30bb"
+ integrity sha512-xoQm2lvXbCA9Kd7SCx6y713Y7sZ6fUc5R6VYpoL5M6svKJbTuvtNopexK8sO8K4s0EOUYHuPN2+yAEsNyRggkQ==
dependencies:
immutable-tuple "^0.4.9"
@@ -7933,6 +8102,13 @@ os-homedir@^1.0.0:
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
+ 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"
@@ -7943,20 +8119,20 @@ os-locale@^2.0.0:
mem "^1.1.0"
os-locale@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.0.1.tgz#3b014fbf01d87f60a1e5348d80fe870dc82c4620"
- integrity sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+ integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
dependencies:
- execa "^0.10.0"
+ execa "^1.0.0"
lcid "^2.0.0"
mem "^4.0.0"
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
-osenv@^0.1.4:
+osenv@0, osenv@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
@@ -7974,6 +8150,13 @@ p-defer@^1.0.0:
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
+p-each-series@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
+ integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=
+ dependencies:
+ p-reduce "^1.0.0"
+
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -7984,6 +8167,11 @@ p-is-promise@^1.1.0:
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
+p-is-promise@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+ integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
+
p-limit@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
@@ -7992,9 +8180,9 @@ p-limit@^1.1.0:
p-try "^1.0.0"
p-limit@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec"
- integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
+ integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
dependencies:
p-try "^2.0.0"
@@ -8017,6 +8205,11 @@ p-map@^1.1.1:
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
integrity sha1-BfXkrpegaDcbwqXMhr+9vBnErno=
+p-reduce@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
+ integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
+
p-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
@@ -8030,9 +8223,9 @@ p-try@^1.0.0:
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
- integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-json@^4.0.0:
version "4.0.1"
@@ -8081,16 +8274,6 @@ parse-entities@^1.0.2, parse-entities@^1.1.0:
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
-parse-glob@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
- integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
- dependencies:
- glob-base "^0.3.0"
- is-dotfile "^1.0.0"
- is-extglob "^1.0.0"
- is-glob "^2.0.0"
-
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
@@ -8167,7 +8350,7 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -8182,7 +8365,7 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-path-parse@^1.0.5, path-parse@^1.0.6:
+path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
@@ -8226,6 +8409,14 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+pdfjs-dist@^2.0.943:
+ version "2.0.943"
+ resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.0.943.tgz#32fb9a2d863df5a1d89521a0b3cd900c16e7edde"
+ integrity sha512-iLhNcm4XceTHRaSU5o22ZGCm4YpuW5+rf4+BJFH/feBhMQLbCGBry+Jet8Q419QDI4qgARaIQzXuiNrsNWS8Yw==
+ dependencies:
+ node-ensure "^0.0.0"
+ worker-loader "^2.0.0"
+
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -8265,6 +8456,13 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+pirates@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
+ integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
+ dependencies:
+ node-modules-regexp "^1.0.0"
+
pixelmatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"
@@ -8272,13 +8470,6 @@ pixelmatch@^4.0.2:
dependencies:
pngjs "^3.0.0"
-pkg-dir@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
- integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q=
- 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"
@@ -8313,10 +8504,10 @@ pofile@^1:
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==
-popper.js@^1.12.9, popper.js@^1.14.3:
- version "1.14.3"
- resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
- integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=
+popper.js@^1.12.9, popper.js@^1.14.7:
+ version "1.14.7"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
+ integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==
portfinder@^1.0.9:
version "1.0.13"
@@ -8434,7 +8625,7 @@ postcss-scss@^2.0.0:
dependencies:
postcss "^7.0.0"
-postcss-selector-parser@^3.1.0, postcss-selector-parser@^3.1.1:
+postcss-selector-parser@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865"
integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=
@@ -8462,7 +8653,7 @@ postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
-postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23:
+postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
@@ -8495,28 +8686,25 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-preserve@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
- integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
-
-prettier@1.13.7:
- version "1.13.7"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
- integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
+prettier@1.16.3:
+ version "1.16.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
+ integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
-prettier@1.16.1:
- version "1.16.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.1.tgz#534c2c9d7853f8845e5e078384e71973bd74089f"
- integrity sha512-XXUITwIkGb3CPJ2hforHah/zTINRyie5006Jd2HKy2qz7snEJXl0KLfsJZW/wst9g6R2rFvqba3VpNYdu1hDcA==
+prettier@1.16.4:
+ version "1.16.4"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
+ integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
-pretty-format@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"
- integrity sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==
+pretty-format@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
+ integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
dependencies:
- ansi-regex "^3.0.0"
+ "@jest/types" "^24.8.0"
+ ansi-regex "^4.0.0"
ansi-styles "^3.2.0"
+ react-is "^16.8.4"
prismjs@^1.6.0:
version "1.6.0"
@@ -8525,7 +8713,7 @@ prismjs@^1.6.0:
optionalDependencies:
clipboard "^1.5.5"
-private@^0.1.6, private@^0.1.8:
+private@^0.1.6:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
@@ -8555,13 +8743,13 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
-prompts@^0.1.9:
- version "0.1.14"
- resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
- integrity sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==
+prompts@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.1.0.tgz#bf90bc71f6065d255ea2bdc0fe6520485c1b45db"
+ integrity sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==
dependencies:
- kleur "^2.0.1"
- sisteransi "^0.1.1"
+ kleur "^3.0.2"
+ sisteransi "^1.0.0"
prosemirror-commands@^1.0.7:
version "1.0.7"
@@ -8807,15 +8995,6 @@ quick-lru@^1.0.0:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=
-randomatic@^3.0.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
- integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
- dependencies:
- is-number "^4.0.0"
- kind-of "^6.0.0"
- math-random "^1.0.1"
-
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
@@ -8876,6 +9055,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-is@^16.8.4:
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
+ integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
+
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -8979,13 +9163,21 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
-realpath-native@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560"
- integrity sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==
+realpath-native@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
+ integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==
dependencies:
util.promisify "^1.0.0"
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
redent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
@@ -8994,10 +9186,10 @@ redent@^2.0.0:
indent-string "^3.0.0"
strip-indent "^2.0.0"
-regenerate-unicode-properties@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
- integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==
+regenerate-unicode-properties@^8.0.2:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
+ integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
dependencies:
regenerate "^1.4.0"
@@ -9016,20 +9208,13 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-regenerator-transform@^0.13.3:
- version "0.13.3"
- resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
- integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==
+regenerator-transform@^0.13.4:
+ version "0.13.4"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
+ integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==
dependencies:
private "^0.1.6"
-regex-cache@^0.4.2:
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
- integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==
- dependencies:
- is-equal-shallow "^0.1.3"
-
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -9061,17 +9246,17 @@ regexpu-core@^1.0.0:
regjsgen "^0.2.0"
regjsparser "^0.1.4"
-regexpu-core@^4.1.3, regexpu-core@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d"
- integrity sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==
+regexpu-core@^4.5.4:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
+ integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==
dependencies:
regenerate "^1.4.0"
- regenerate-unicode-properties "^7.0.0"
- regjsgen "^0.4.0"
- regjsparser "^0.3.0"
+ regenerate-unicode-properties "^8.0.2"
+ regjsgen "^0.5.0"
+ regjsparser "^0.6.0"
unicode-match-property-ecmascript "^1.0.4"
- unicode-match-property-value-ecmascript "^1.0.2"
+ unicode-match-property-value-ecmascript "^1.1.0"
registry-auth-token@^3.0.1:
version "3.3.2"
@@ -9093,10 +9278,10 @@ regjsgen@^0.2.0:
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=
-regjsgen@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561"
- integrity sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==
+regjsgen@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd"
+ integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==
regjsparser@^0.1.4:
version "0.1.5"
@@ -9105,10 +9290,10 @@ regjsparser@^0.1.4:
dependencies:
jsesc "~0.5.0"
-regjsparser@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.3.0.tgz#3c326da7fcfd69fa0d332575a41c8c0cdf588c96"
- integrity sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==
+regjsparser@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c"
+ integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==
dependencies:
jsesc "~0.5.0"
@@ -9168,16 +9353,16 @@ remove-trailing-separator@^1.0.1:
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
repeat-element@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
- integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+ integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
repeat-string@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae"
integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4=
-repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1:
+repeat-string@^1.5.4, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@@ -9210,7 +9395,7 @@ request-promise-native@^1.0.5:
stealthy-require "^1.1.0"
tough-cookie ">=2.3.3"
-request@^2.87.0:
+request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -9306,10 +9491,10 @@ resolve@1.1.7, resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
- integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
+resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.9.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
+ integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
dependencies:
path-parse "^1.0.6"
@@ -9338,7 +9523,7 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
-rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -9358,10 +9543,10 @@ rope-sequence@^1.2.0:
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.2.2.tgz#49c4e5c2f54a48e990b050926771e2871bcb31ce"
integrity sha1-ScTlwvVKSOmQsFCSZ3HihxvLMc4=
-rsvp@^3.3.3:
- version "3.6.2"
- resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
- integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==
+rsvp@^4.8.4:
+ version "4.8.4"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911"
+ integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==
run-async@^2.2.0:
version "2.3.0"
@@ -9416,21 +9601,20 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sane@^2.0.0:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa"
- integrity sha1-tNwYYcIbQn6SlQej51HiosuKs/o=
+sane@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded"
+ integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==
dependencies:
+ "@cnakazawa/watch" "^1.0.3"
anymatch "^2.0.0"
- capture-exit "^1.2.0"
- exec-sh "^0.2.0"
+ capture-exit "^2.0.0"
+ exec-sh "^0.3.2"
+ execa "^1.0.0"
fb-watchman "^2.0.0"
micromatch "^3.1.4"
minimist "^1.1.1"
walker "~1.0.5"
- watch "~0.18.0"
- optionalDependencies:
- fsevents "^1.2.3"
sanitize-html@^1.16.1:
version "1.16.3"
@@ -9445,6 +9629,16 @@ sanitize-html@^1.16.1:
srcset "^1.0.0"
xtend "^4.0.0"
+sass-graph@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
+ integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=
+ dependencies:
+ glob "^7.0.0"
+ lodash "^4.0.0"
+ scss-tokenizer "^0.2.3"
+ yargs "^7.0.0"
+
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -9467,7 +9661,7 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
-scope-css@^1.0.5:
+scope-css@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e"
integrity sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q==
@@ -9476,6 +9670,14 @@ scope-css@^1.0.5:
slugify "^1.3.1"
strip-css-comments "^3.0.0"
+scss-tokenizer@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
+ integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE=
+ dependencies:
+ js-base64 "^2.1.8"
+ source-map "^0.4.2"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -9505,10 +9707,20 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
- integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
+ version "5.7.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
+ integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
+
+semver@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
+ integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==
+
+semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+ integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
send@0.16.2:
version "0.16.2"
@@ -9562,13 +9774,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
-set-getter@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
- integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=
- dependencies:
- to-object-path "^0.3.0"
-
set-immediate-shim@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
@@ -9652,15 +9857,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-sisteransi@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce"
- integrity sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==
-
-slash@^1.0.0:
+sisteransi@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
- integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
+ integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==
slash@^2.0.0:
version "2.0.0"
@@ -9707,9 +9907,9 @@ snapdragon-util@^3.0.1:
kind-of "^3.2.0"
snapdragon@^0.8.1:
- version "0.8.1"
- resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370"
- integrity sha1-4StUh/re0+PeoKyR6UAL91tAE3A=
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
+ integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
dependencies:
base "^0.11.1"
debug "^2.2.0"
@@ -9718,7 +9918,7 @@ snapdragon@^0.8.1:
map-cache "^0.2.2"
source-map "^0.5.6"
source-map-resolve "^0.5.0"
- use "^2.0.0"
+ use "^3.1.0"
socket.io-adapter@~1.1.0:
version "1.1.1"
@@ -9814,17 +10014,10 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
-source-map-support@^0.4.15:
- version "0.4.18"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
- integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
- dependencies:
- source-map "^0.5.6"
-
source-map-support@^0.5.6, source-map-support@~0.5.6:
- version "0.5.9"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
- integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==
+ version "0.5.12"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+ integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -9839,7 +10032,14 @@ source-map@0.5.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY=
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ integrity sha1-66T12pwNyZneaAMti092FzZSA2s=
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -9976,6 +10176,13 @@ statuses@~1.3.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=
+stdout-stream@^1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de"
+ integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==
+ dependencies:
+ readable-stream "^2.0.1"
+
stealthy-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
@@ -10050,7 +10257,7 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
+"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@@ -10104,16 +10311,11 @@ strip-ansi@^4.0.0:
ansi-regex "^3.0.0"
strip-ansi@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
- integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+ integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
- ansi-regex "^4.0.0"
-
-strip-bom@3.0.0, strip-bom@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
- integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+ ansi-regex "^4.1.0"
strip-bom@^2.0.0:
version "2.0.0"
@@ -10122,6 +10324,11 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+ integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+
strip-css-comments@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-css-comments/-/strip-css-comments-3.0.0.tgz#7a5625eff8a2b226cf8947a11254da96e13dae89"
@@ -10134,12 +10341,19 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
+ dependencies:
+ get-stdin "^4.0.1"
+
strip-indent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
-strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
+strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -10162,10 +10376,15 @@ stylelint-config-recommended@^2.1.0:
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz#f526d5c771c6811186d9eaedbed02195fee30858"
integrity sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA==
-stylelint-scss@^3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.3.tgz#e158b3061eeec26d7f6088f346998a797432f3c8"
- integrity sha512-QESQUOY1ldU5tlJTTM3Megz/QtJ39S58ByjZ7dZobGDq9qMjy5jbC7PDUasrv/T7pB1UbpPojpxX9K1OR7IPEg==
+stylelint-error-string-formatter@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/stylelint-error-string-formatter/-/stylelint-error-string-formatter-1.0.2.tgz#3076c6703d3e0170daeb55fe85030d63c834745e"
+ integrity sha512-xN69xRB0eTgYcGKVHWIiH2L+Xx8fRPliTiLjaS4YlbfBqkdTuZh2wjtfvNCkCzBTNeINWa5GpSa9RFYXdtwV6w==
+
+stylelint-scss@^3.5.4:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.4.tgz#ff3ee989ac48f5c4f57313523b5ace059ffd6cc2"
+ integrity sha512-hEdEOfFXVqxWcUbenBONW/cAw5cJcEDasY8tGwKNAAn1GDHoZO1ATdWpr+iIk325mPGIQqVb1sUxsRxuL70trw==
dependencies:
lodash "^4.17.11"
postcss-media-query-parser "^0.2.3"
@@ -10238,7 +10457,7 @@ supports-color@^2.0.0:
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-supports-color@^3.1.0, supports-color@^3.1.2:
+supports-color@^3.1.0:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=
@@ -10304,15 +10523,24 @@ tapable@^1.0.0, tapable@^1.1.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c"
integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==
+tar@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
tar@^4:
- version "4.4.4"
- resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd"
- integrity sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==
+ version "4.4.8"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
+ integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
dependencies:
- chownr "^1.0.1"
+ chownr "^1.1.1"
fs-minipass "^1.2.5"
- minipass "^2.3.3"
- minizlib "^1.1.0"
+ minipass "^2.3.4"
+ minizlib "^1.1.1"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.2"
@@ -10347,17 +10575,6 @@ terser@^3.8.1:
source-map "~0.6.1"
source-map-support "~0.5.6"
-test-exclude@^4.2.1:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
- integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==
- dependencies:
- arrify "^1.0.1"
- micromatch "^2.3.11"
- object-assign "^4.1.0"
- read-pkg-up "^1.0.1"
- require-main-filename "^1.0.1"
-
test-exclude@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.0.0.tgz#cdce7cece785e0e829cd5c2b27baf18bc583cfb7"
@@ -10518,11 +10735,6 @@ to-arraybuffer@^1.0.0:
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
-to-fast-properties@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
- integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -10580,6 +10792,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+ integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
+
trim-newlines@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20"
@@ -10605,22 +10822,48 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24"
integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==
+"true-case-path@^1.0.2":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
+ integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==
+ dependencies:
+ glob "^7.1.2"
+
tryer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc=
-tsconfig@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
- integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==
+ts-invariant@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.2.1.tgz#3d587f9d6e3bded97bf9ec17951dd9814d5a9d3f"
+ integrity sha512-Z/JSxzVmhTo50I+LKagEISFJW3pvPCqsMWLamCTX8Kr3N5aMrnGOqcflbe5hLUzwjvgPfnLzQtHZv0yWQ+FIHg==
dependencies:
- "@types/strip-bom" "^3.0.0"
- "@types/strip-json-comments" "0.0.30"
- strip-bom "^3.0.0"
- strip-json-comments "^2.0.0"
+ tslib "^1.9.3"
-tslib@^1.9.0:
+ts-invariant@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
+ integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
+ dependencies:
+ tslib "^1.9.3"
+
+ts-jest@24.0.0, ts-jest@^23.10.5:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.0.tgz#3f26bf2ec1fa584863a5a9c29bd8717d549efbf6"
+ integrity sha512-o8BO3TkMREpAATaFTrXkovMsCpBl2z4NDBoLJuWZcJJj1ijI49UnvDMfVpj+iogn/Jl8Pbhuei5nc/Ti+frEHw==
+ dependencies:
+ bs-logger "0.x"
+ buffer-from "1.x"
+ fast-json-stable-stringify "2.x"
+ json5 "2.x"
+ make-error "1.x"
+ mkdirp "0.x"
+ resolve "1.x"
+ semver "^5.5"
+ yargs-parser "10.x"
+
+tslib@^1.9.0, tslib@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
@@ -10662,10 +10905,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@^2:
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
- integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
+"typescript@2 - 3":
+ version "3.3.4000"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0"
+ integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.5"
@@ -10673,11 +10916,11 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
uglify-js@^3.1.4:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
- integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+ version "3.5.15"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.15.tgz#fe2b5378fd0b09e116864041437bff889105ce24"
+ integrity sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==
dependencies:
- commander "~2.17.1"
+ commander "~2.20.0"
source-map "~0.6.1"
ultron@~1.1.0:
@@ -10735,10 +10978,10 @@ unicode-match-property-ecmascript@^1.0.4:
unicode-canonical-property-names-ecmascript "^1.0.4"
unicode-property-aliases-ecmascript "^1.0.4"
-unicode-match-property-value-ecmascript@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4"
- integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==
+unicode-match-property-value-ecmascript@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
+ integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
unicode-property-aliases-ecmascript@^1.0.4:
version "1.0.4"
@@ -10933,14 +11176,10 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
-use@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
- integrity sha1-riig1y+TvyJCKhii43mZMRLeyOg=
- dependencies:
- define-property "^0.2.5"
- isobject "^3.0.0"
- lazy-cache "^2.0.2"
+use@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
+ integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
useragent@2.2.1:
version "2.2.1"
@@ -11046,10 +11285,10 @@ void-elements@^2.0.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
-vue-apollo@^3.0.0-beta.25:
- version "3.0.0-beta.25"
- resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-beta.25.tgz#05a9a699b2ba6103639e9bd6c3bb88ca04c4b637"
- integrity sha512-M7/l3h0NlFvaZ/s/wrtRiOt3xXMbaNNuteGaCY+U5D0ABrQqvCgy5mayIZHurQxbloluNkbCt18wRKAgJTAuKA==
+vue-apollo@^3.0.0-beta.28:
+ version "3.0.0-beta.28"
+ resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-beta.28.tgz#be6a3a1504be2096cbfb23996537e2fc95c8c239"
+ integrity sha512-ceCc1xTyxpNtiSeJMQgSkfgJue6pnv+TIvp75CwZlwMxRtNZjITj4MGvBWFwnoIEhVrUAw45ff/5udgJ8z9sdQ==
dependencies:
chalk "^2.4.1"
throttle-debounce "^2.0.0"
@@ -11076,28 +11315,24 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
integrity sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==
-vue-jest@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.2.tgz#c64bf5da9abd0d3ee16071217037696d14b0689c"
- integrity sha512-5XIQ1xQFW0ZnWxHWM7adVA2IqbDsdw1vhgZfGFX4oWd75J38KIS3YT41PtiE7lpMLmNM6+VJ0uprT2mhHjUgkA==
+vue-jest@^4.0.0-beta.2:
+ version "4.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.0-beta.2.tgz#f2120ea9d24224aad3a100c2010b0760d47ee6fe"
+ integrity sha512-SywBIciuIfqsCb8Eb9UQ02s06+NV8Ry8KnbyhAfnvnyFFulIuh7ujtga9eJYq720nCS4Hz4TpVtS4pD1ZbUILQ==
dependencies:
- babel-plugin-transform-es2015-modules-commonjs "^6.26.0"
+ "@babel/plugin-transform-modules-commonjs" "^7.2.0"
+ "@vue/component-compiler-utils" "^2.4.0"
chalk "^2.1.0"
extract-from-css "^0.4.4"
- find-babel-config "^1.1.0"
- js-beautify "^1.6.14"
- node-cache "^4.1.1"
- object-assign "^4.1.1"
source-map "^0.5.6"
- tsconfig "^7.0.0"
- vue-template-es2015-compiler "^1.6.0"
+ ts-jest "^23.10.5"
-vue-loader@^15.4.2:
- version "15.4.2"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2"
- integrity sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg==
+vue-loader@^15.4.2, vue-loader@^15.7.0:
+ version "15.7.0"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
+ integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
dependencies:
- "@vue/component-compiler-utils" "^2.0.0"
+ "@vue/component-compiler-utils" "^2.5.1"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
@@ -11123,33 +11358,33 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
- integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
+vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
+ integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
-vue-template-es2015-compiler@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
- integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
+vue-template-es2015-compiler@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
+ integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-vue-virtual-scroll-list@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a"
- integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno=
+vue-virtual-scroll-list@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76"
+ integrity sha512-PMTxiK9/P1LtgoWWw4n1QnmDDkYqIdWWCNdt1L4JD9g6rwDgnsGsSV10bAnd5n7DQLHGWHjRex+zAbjXWT8t0g==
-vue@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
- integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
+vue@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
+ integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
-vuex@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
- integrity sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w==
+vuex@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9"
+ integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==
w3c-hr-time@^1.0.1:
version "1.0.1"
@@ -11163,21 +11398,13 @@ w3c-keyname@^1.1.8:
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
-walker@~1.0.5:
+walker@^1.0.7, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=
dependencies:
makeerror "1.0.x"
-watch@~0.18.0:
- version "0.18.0"
- resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"
- integrity sha1-KAlUdsbffJDJYxOJkMClQj60uYY=
- dependencies:
- exec-sh "^0.2.0"
- minimist "^1.2.0"
-
watchpack@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed"
@@ -11375,12 +11602,17 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+ integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
+
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
+which@1, which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -11388,11 +11620,11 @@ which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1
isexe "^2.0.0"
wide-align@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
- integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+ integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
dependencies:
- string-width "^1.0.2"
+ string-width "^1.0.2 || 2"
widest-line@^2.0.0:
version "2.0.0"
@@ -11440,10 +11672,10 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-write-file-atomic@^2.0.0, write-file-atomic@^2.1.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"
- integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==
+write-file-atomic@2.4.1, write-file-atomic@^2.0.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529"
+ integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==
dependencies:
graceful-fs "^4.1.11"
imurmurhash "^0.1.4"
@@ -11557,11 +11789,11 @@ yallist@^2.1.2:
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^3.0.0, yallist@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
- integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
+ integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
-yargs-parser@^10.0.0, yargs-parser@^10.1.0:
+yargs-parser@10.x, yargs-parser@^10.0.0, yargs-parser@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==
@@ -11576,6 +11808,13 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+ integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=
+ dependencies:
+ camelcase "^3.0.0"
+
yargs-parser@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
@@ -11583,13 +11822,6 @@ yargs-parser@^8.1.0:
dependencies:
camelcase "^4.1.0"
-yargs-parser@^9.0.2:
- version "9.0.2"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
- integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=
- dependencies:
- camelcase "^4.1.0"
-
yargs@12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
@@ -11626,25 +11858,7 @@ yargs@^10.0.3:
y18n "^3.2.1"
yargs-parser "^8.1.0"
-yargs@^11.0.0:
- version "11.1.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
- integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==
- dependencies:
- cliui "^4.0.0"
- decamelize "^1.1.1"
- find-up "^2.1.0"
- get-caller-file "^1.0.1"
- os-locale "^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 "^9.0.2"
-
-yargs@^12.0.4:
+yargs@^12.0.2, yargs@^12.0.4:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
@@ -11662,10 +11876,29 @@ yargs@^12.0.4:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
-yarn-deduplicate@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.0.tgz#bdfdcc5a2473556c0232996424dfe039293f2f44"
- integrity sha512-YTZzmzzUgDK7IllsKxgnTQ7zAGbTVnj3bnH3nxoqZ2dE0IY7NpaFpFYXR+BuBeDtxIgMhwJJvH1LTWm3k3fWpg==
+yargs@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+ integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^5.0.0"
+
+yarn-deduplicate@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d"
+ integrity sha512-2FDJ1dFmtvqhRmfja89ohYzpaheCYg7BFBSyaUq+kxK0y61C9oHv1XaQovCWGJtP2WU8PksQOgzMVV7oQOobzw==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
commander "^2.10.0"
@@ -11676,11 +11909,12 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
-zen-observable-ts@^0.8.10:
- version "0.8.10"
- resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829"
- integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ==
+zen-observable-ts@^0.8.18:
+ version "0.8.18"
+ resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
+ integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
dependencies:
+ tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable@^0.8.0: